diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json deleted file mode 100644 index 26886046..00000000 --- a/.claude-plugin/marketplace.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "agent-memory-lab", - "owner": { - "name": "sznnnnn", - "github": "sznnnnn" - }, - "plugins": [ - { - "name": "agent-memory-lab", - "description": "Local-first Agent memory workbench for reviewable memories, sessions, Skills, and publishing materials", - "source": "./plugin" - } - ] -} diff --git a/.codex-plugin/marketplace.json b/.codex-plugin/marketplace.json deleted file mode 100644 index 61981228..00000000 --- a/.codex-plugin/marketplace.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "agent-memory-lab", - "interface": { - "displayName": "Agent Memory Lab" - }, - "plugins": [ - { - "name": "agent-memory-lab", - "source": { - "source": "git-subdir", - "url": "https://github.com/MaimoryLab/agentmemory-lab.git", - "path": "./plugin", - "ref": "main" - }, - "policy": { - "installation": "AVAILABLE", - "authentication": "ON_INSTALL" - }, - "category": "Memory" - } - ] -} diff --git a/.env.example b/.env.example index 13b01fa0..894f3d61 100644 --- a/.env.example +++ b/.env.example @@ -1,133 +1,16 @@ -# ============================================================================= -# AI-Todo configuration -# ============================================================================= -# -# Copy this file to `~/.agentmemory/.env` (run `node dist/cli.mjs init`) and -# uncomment the lines you want. Every line is OFF by default — AI-Todo runs -# out of the box: it extracts To-Dos from your agent sessions with a -# deterministic rules extractor, no LLM key, no API auth. -# -# Run `node dist/cli.mjs doctor` to verify the daemon reads the env you expect. -# Env var NAMES (AGENTMEMORY_* / LANGEXTRACT_* / III_*) are the wire contract -# and are kept stable — only this file's framing/order is AI-Todo-oriented. - -# ----------------------------------------------------------------------------- -# 1. To-Do extraction model ← AI-Todo's headline config -# ----------------------------------------------------------------------------- -# -# To use an LLM extractor instead of the default rules extractor, install the -# Python deps then set an OpenAI-compatible model below: -# python3 -m pip install -r requirements-langextract.txt -# python3 -m pip install socksio # only when your network uses a SOCKS proxy -# -# First-run setup (`node dist/cli.mjs --reset` from source, or -# `agentmemory-lab --reset` when installed) seeds these for the model you pick. -# Only the openai-compatible branch is wired (Novita default / OpenAI / OpenRouter). -# AGENTMEMORY_TODO_EXTRACTOR=langextract -# LANGEXTRACT_PROVIDER=openai -# LANGEXTRACT_MODEL=deepseek/deepseek-v4-flash # Novita; OpenAI: gpt-4o-mini; OpenRouter: deepseek/deepseek-chat -# LANGEXTRACT_BASE_URL=https://api.novita.ai/openai/v1 # OpenAI: https://api.openai.com/v1 · OpenRouter: https://openrouter.ai/api/v1 -# LANGEXTRACT_API_KEY= -# LANGEXTRACT_THINKING_DEPTH=medium # reasoning_effort; leave empty for non-reasoning models (e.g. gpt-4o-mini) -# LANGEXTRACT_PYTHON=/path/to/python -# AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS=120000 -# AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS=7 # only extract sessions from the last N days (primary scope control) -# AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION=10 # per session, keep at most M most-recent interaction records (one user request → agent reply) - -# ----------------------------------------------------------------------------- -# 2. Auth & security (web UI / REST) -# ----------------------------------------------------------------------------- -# -# Bearer-token auth for the REST API + viewer. Without a secret, REST endpoints -# are open on loopback. Set this when you expose the daemon beyond loopback. -# AGENTMEMORY_SECRET=your-secret-here - -# ----------------------------------------------------------------------------- -# 3. Runtime / ports / engine pin -# ----------------------------------------------------------------------------- - -# AGENTMEMORY_URL=http://localhost:3111 # REST base URL — honored by status, doctor, MCP shim -# AGENTMEMORY_VIEWER_URL=http://localhost:3114 # Override the viewer URL printed by `node dist/cli.mjs status` / `agentmemory-lab status` -# AGENTMEMORY_TOOLS=all # core (default) | all — surface exposed to MCP clients -# AGENTMEMORY_SLOTS=memory # Comma-separated plugin slot names the CLI should claim -# AGENTMEMORY_EXPORT_ROOT=~/agentmemory-backup # Default destination used by export functions -# AGENTMEMORY_DEBUG=1 # Trace MCP shim probe + standalone fallback decisions to stderr -# AGENTMEMORY_FORCE_PROXY=1 # Skip the MCP shim livez probe and trust AGENTMEMORY_URL -# AGENTMEMORY_PROBE_TIMEOUT_MS=2000 # MCP shim livez probe timeout -# STANDALONE_MCP=1 # MCP shim only — bypass the worker, run @agentmemory/mcp in-process -# STANDALONE_PERSIST_PATH=~/.agentmemory/local.db # Path used by the standalone MCP shim's local fallback store -# III_REST_PORT=3111 # REST API port (also affects viewer at +2) -# III_STREAMS_PORT=3112 # Streams API port -# III_ENGINE_URL=ws://localhost:49134 # iii-engine WebSocket URL (used by the worker) -# AGENTMEMORY_III_VERSION=0.11.2 # iii-engine pin. v0.11.6 changes the sandbox model; only override after migrating manually. - -# ============================================================================= -# Advanced: legacy memory engine (inherited from agentmemory; OFF by default, #138) -# ============================================================================= -# -# These power the upstream persistent-memory layer (compression, consolidation, -# embeddings, knowledge graph, snapshots, team sharing, bridges). AI-Todo does -# NOT need them for To-Do extraction; they remain for users who want the memory -# layer. Names unchanged. - -# --- LLM provider (compress / summarise / consolidate) — pick ONE ------------- -# Detection order: OPENAI_API_KEY → MINIMAX_API_KEY → ANTHROPIC_API_KEY → -# GEMINI_API_KEY → OPENROUTER_API_KEY → noop (zero-LLM synthetic compression). -# OPENAI_API_KEY=sk-... # Also used for OpenAI-compatible embeddings -# OPENAI_BASE_URL=https://api.openai.com # Override for OpenAI-compatible providers -# ANTHROPIC_API_KEY=sk-ant-... -# ANTHROPIC_MODEL=claude-sonnet-4-20250514 # Default Anthropic model -# ANTHROPIC_BASE_URL=https://api.anthropic.com # Override for Anthropic-compatible proxies / Azure AI Foundry -# GEMINI_API_KEY=... # GEMINI_API_KEY takes precedence over GOOGLE_API_KEY -# GOOGLE_API_KEY=... # Alias for GEMINI_API_KEY when set alone -# GEMINI_MODEL=gemini-2.5-flash # Default Gemini model -# OPENROUTER_API_KEY=sk-or-... -# OPENROUTER_MODEL=anthropic/claude-sonnet-4-20250514 -# MINIMAX_API_KEY=... -# MINIMAX_MODEL=MiniMax-M2.7 -# MAX_TOKENS=4096 # Cap LLM completion tokens for compression / summarise calls -# AGENTMEMORY_LLM_TIMEOUT_MS=60000 # Outbound LLM / embedding timeout (default 60 000 ms) -# AGENTMEMORY_ALLOW_AGENT_SDK=true # Opt-in Claude-subscription fallback (off by default) -# FALLBACK_PROVIDERS=anthropic,gemini # Chain tried after the primary provider errors - -# --- Embeddings (override via EMBEDDING_PROVIDER) ----------------------------- -# Detection order: EMBEDDING_PROVIDER → GEMINI → OPENAI → VOYAGE → COHERE → -# OPENROUTER → local (Xenova/all-MiniLM-L6-v2, 384-dim). -# EMBEDDING_PROVIDER=local # local | openai | voyage | cohere | gemini | openrouter -# VOYAGE_API_KEY=pa-... # Optimised for code embeddings -# COHERE_API_KEY=... # General-purpose embeddings -# OPENAI_EMBEDDING_MODEL=text-embedding-3-small # When EMBEDDING_PROVIDER=openai -# OPENAI_EMBEDDING_DIMENSIONS=1536 # Required when the model is not in the known-models table -# OPENROUTER_EMBEDDING_MODEL=openai/text-embedding-3-small # When EMBEDDING_PROVIDER=openrouter - -# --- Search tuning ----------------------------------------------------------- -# BM25_WEIGHT=0.4 # Hybrid search weight for BM25 leg -# VECTOR_WEIGHT=0.6 # Hybrid search weight for vector leg -# AGENTMEMORY_GRAPH_WEIGHT=0.2 # Graph traversal bonus on smart-search ranking -# TOKEN_BUDGET=2000 # Max tokens injected via mem::context per session -# MAX_OBS_PER_SESSION=500 # Per-session observation cap before consolidation kicks in -# SUMMARIZE_CHUNK_SIZE=400 # Chunk threshold for map-reduce summarize of large sessions -# SUMMARIZE_CHUNK_CONCURRENCY=6 # Parallel chunk LLM calls during chunked summarize - -# --- Behaviour flags --------------------------------------------------------- -# AGENTMEMORY_AUTO_COMPRESS=true # LLM compression on every observation batch (needs a provider key). Default off. -# AGENTMEMORY_INJECT_CONTEXT=true # Inject recalled memories back into agent prompts (#143). Default off. -# CONSOLIDATION_ENABLED=true # Run the 4-tier consolidation pipeline. Default off. -# CONSOLIDATION_DECAY_DAYS=30 # Age (days) after which non-reinforced memories decay -# GRAPH_EXTRACTION_ENABLED=true # Extract concept-graph edges on remember -# GRAPH_EXTRACTION_BATCH_SIZE=8 # Memories per graph-extraction batch -# AGENTMEMORY_REFLECT=true # Periodically auto-synthesize lessons from memories -# AGENTMEMORY_DROP_STALE_INDEX=true # Drop on-disk BM25 / vector index on startup if dim guard fires (#248) -# AGENTMEMORY_IMAGE_EMBEDDINGS=true # Enable image embeddings when an image provider is present (experimental) - -# --- Snapshots / team / bridges ---------------------------------------------- -# SNAPSHOT_ENABLED=true -# SNAPSHOT_DIR=~/.agentmemory/snapshots -# SNAPSHOT_INTERVAL=3600 # Seconds between snapshots -# TEAM_MODE=shared # Scope memories to (TEAM_ID, USER_ID) tuples -# TEAM_ID=acme -# USER_ID=rohit -# CLAUDE_MEMORY_BRIDGE=true # Mirror compressed memories into Claude Code's CLAUDE.md -# CLAUDE_PROJECT_PATH=/path/to/your/project # Required when CLAUDE_MEMORY_BRIDGE=true -# CLAUDE_MEMORY_LINE_BUDGET=200 # Lines of memory CLAUDE.md should hold -# OBSIDIAN_AUTO_EXPORT=true # Auto-export memories to an Obsidian vault on every consolidation +# AI-Todo local configuration example +AI_TODO_CODEX_HOME=~/.codex +AI_TODO_CLAUDE_HOME=~/.claude/projects +AI_TODO_LLM_ENABLED=true +AI_TODO_LLM_PROVIDER=openai +AI_TODO_LLM_MODEL=deepseek/deepseek-v4-flash +AI_TODO_LLM_ENDPOINT=https://api.novita.ai/openai/v1 +AI_TODO_LLM_THINKING_DEPTH=medium +AI_TODO_LLM_TIMEOUT_MS=120000 +AI_TODO_ORGANIZE_SINCE_DAYS=7 +AI_TODO_ORGANIZE_MAX_INTERACTIONS_PER_SESSION=10 +AI_TODO_ORGANIZE_MAX_SESSIONS=16 +AI_TODO_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION=40 + +# Do not commit real keys. Run `ai-todo init --api-key ...` or save in Settings. +# AI_TODO_LLM_API_KEY= diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index a2f5e0c5..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: [rohitg00] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 5685ce5b..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,101 +0,0 @@ -name: Bug report -description: Report a problem so we can reproduce and fix it -title: "[Bug] " -labels: [bug] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to file a bug for AI Todo. - - Before you start, please search the existing issues to avoid duplicates. - - **Do not paste secrets.** Never include full private conversation transcripts, cookies, access tokens, API keys, or any sensitive page content. Redact anything you are unsure about before submitting. - - - type: input - id: version - attributes: - label: Version / commit - description: Find it in `package.json` (the `version` field) or use a commit hash. - placeholder: 0.9.24 / commit d8389f2 - validations: - required: true - - - type: dropdown - id: install-method - attributes: - label: Installation method - description: How did you install or run AI Todo? - options: - - npm - - From source - - Browser extension - validations: - required: true - - - type: input - id: environment - attributes: - label: Environment - description: OS, Node version (must be >=20), and browser version if relevant. - placeholder: macOS 15 / Node 20.11.0 / Chrome 126 - validations: - required: true - - - type: dropdown - id: source - attributes: - label: Source involved - description: Which source or surface is affected? - options: - - Codex - - Claude Code - - Browser AI site - - Viewer workbench - - Other - validations: - required: true - - - type: textarea - id: steps - attributes: - label: Steps to reproduce - description: A minimal, ordered list of steps that triggers the bug. - placeholder: | - 1. Run `...` - 2. Open `...` - 3. Click `...` - 4. See the error - validations: - required: true - - - type: textarea - id: expected - attributes: - label: Expected result - description: What did you expect to happen? - validations: - required: true - - - type: textarea - id: actual - attributes: - label: Actual result - description: What actually happened? Include any error message text. - validations: - required: true - - - type: textarea - id: logs - attributes: - label: Logs / diagnostics - description: Paste relevant console output, stack traces, or diagnostics. Remove any sensitive content first. - render: shell - - - type: checkboxes - id: privacy - attributes: - label: Privacy confirmation - options: - - label: I confirm this report does not contain any sensitive information (private transcripts, cookies, tokens, or API keys). - required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 1f012317..00000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Report a security vulnerability - url: https://github.com/MaimoryLab/AI-Todo/blob/main/SECURITY.md - about: Do not open a public issue for security problems. Report them privately by following SECURITY.md. diff --git a/.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml b/.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml deleted file mode 100644 index 8d3b67bb..00000000 --- a/.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: 外部试用反馈 -description: 反馈 Agent Memory Lab 本地工作台或浏览器插件试用问题 -title: "[外部试用] " -labels: [external-feedback] -body: - - type: markdown - attributes: - value: | - 谢谢你试用 Agent Memory Lab。请不要粘贴私人聊天全文、Cookie、访问令牌、API Key、学校申请材料或任何敏感页面内容。 - - - type: input - id: version - attributes: - label: 插件 / 项目版本 - description: 可以从插件弹窗、`npm run status:delivery` 或提交号里找到。 - placeholder: Agent Memory Lab 0.1.0 / commit d8389f2 - validations: - required: true - - - type: dropdown - id: path - attributes: - label: 试用路径 - options: - - 本地 demo 页面 - - ChatGPT - - Claude - - Gemini - - Perplexity - - Grok - - DeepSeek - - Viewer 工作台 - - 插件打包 / 加载 - - 其他 - validations: - required: true - - - type: input - id: environment - attributes: - label: 环境 - placeholder: macOS 15 / Chrome 版本号 / Edge 版本号 - validations: - required: true - - - type: textarea - id: expected - attributes: - label: 你期待发生什么 - placeholder: 例如:输入框旁出现记忆建议,点击插入后不影响原站发送。 - validations: - required: true - - - type: textarea - id: actual - attributes: - label: 实际发生了什么 - placeholder: 例如:侧栏显示输入框未找到,或保存后 Viewer 待审阅没有出现。 - validations: - required: true - - - type: textarea - id: diagnostics - attributes: - label: 诊断 JSON 或证据文件名 - description: 可以粘贴同步侧栏“复制诊断”的 JSON,或写 `docs/validation/browser-extension-ai-sites/YYYY-MM-DD-provider.json`。AI 页面问题请保留 `turnCount` 和 `matchedSelectors.turn`,但先删掉隐私内容。 - render: json - placeholder: | - { - "ai": { - "turnCount": 0, - "matchedSelectors": { - "turn": "" - } - }, - "manualValidation": { - "memoryInsertPassed": false, - "diagnosticsCopied": true, - "siteInputStillWorks": false - } - } - - - type: checkboxes - id: validation - attributes: - label: 手动验收 - options: - - label: 我已确认反馈内容不包含敏感信息。 - required: true - - label: 如果这是 AI 页面问题,我已尝试复制同步侧栏诊断 JSON。 - - label: 如果这是 AI 页面问题,我已确认页面里至少有一轮真实对话,候选记忆不是页面标题、链接或输入框草稿。 - - label: 如果这是插入问题,我已确认原站输入框和发送按钮是否仍然正常。 - - - type: dropdown - id: impact - attributes: - label: 影响程度 - options: - - 阻断试用 - - 影响核心体验 - - 小问题 - - 建议优化 - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index 9f1c9be9..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Feature request -description: Suggest an improvement or new capability for AI Todo -title: "[Feature] " -labels: [enhancement] -body: - - type: markdown - attributes: - value: | - Thanks for helping shape AI Todo. - - Before you start, please search [existing issues](https://github.com/MaimoryLab/AI-Todo/issues?q=is%3Aissue) so we can avoid duplicates. - - The most useful feature requests describe the user problem before the implementation. As our contributing guide puts it: **"I couldn't X because Y" beats "please add X."** Lead with the problem so we can find the best solution together. - - Please do not paste private conversation transcripts, cookies, access tokens, API keys, or other sensitive content. - - - type: textarea - id: problem - attributes: - label: What user problem are you trying to solve? - description: Describe the problem or motivation first, not the implementation. What were you trying to do, and what got in the way? - placeholder: | - "I couldn't X because Y." - - Example: I couldn't tell which agent sessions still had unfinished work because the todo list mixes every source together with no way to focus on one. - validations: - required: true - - - type: textarea - id: solution - attributes: - label: Desired solution - description: If you have one in mind, describe what you'd like to happen. It's fine to leave this open-ended if you only have the problem. - placeholder: Describe the behavior, UI, or capability you'd expect. - validations: - required: false - - - type: textarea - id: alternatives - attributes: - label: Alternatives you've considered - description: Workarounds you tried, other approaches you weighed, or why existing functionality doesn't cover this. - placeholder: e.g. I tried filtering by status, but that doesn't separate todos by source app. - validations: - required: false - - - type: dropdown - id: scope - attributes: - label: Scope - description: | - Which area does this touch? See the PRD for the v1 scope and Non-Goals. - Note that cloud sync, team workspaces, proactive notifications, and working external-tool integrations (Slack, Discord, Telegram, Feishu) are Non-Goals for v1. - options: - - v1 core (scanner / extractor / local database / local web UI / manual cleanup) - - Connector interface (generic contract for future external integrations) - - i18n / localization (externalized UI strings, language readiness) - - Docs (README, FEATURES, ARCHITECTURE, RULES, ROADMAP, templates) - - Other / not sure - validations: - required: true - - - type: checkboxes - id: contribution - attributes: - label: Contribution - options: - - label: I'm willing to open a pull request to implement this feature. - required: false diff --git a/.github/ISSUE_TEMPLATE/localization.yml b/.github/ISSUE_TEMPLATE/localization.yml deleted file mode 100644 index 850da9d9..00000000 --- a/.github/ISSUE_TEMPLATE/localization.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: "Translation / localization" -description: Report missing, incorrect, or hardcoded copy in a translation or localization of AI Todo. -title: "[i18n] " -labels: [i18n, documentation] -body: - - type: markdown - attributes: - value: | - Thanks for helping make AI Todo readable in more languages! - - English is the default language for the README, repository metadata, issue templates, and the default UI. This form is for reporting copy that is missing a translation, translated incorrectly, or still hardcoded so it cannot be localized at all. - - Please do not paste secrets, tokens, API keys, or private conversation content. Localization issues only need the user-facing strings, not your data. - - - type: dropdown - id: surface - attributes: - label: Affected surface - description: Where did you see the problem? - options: - - Viewer UI (local web app) - - Browser extension - - Website - - Docs - - README - validations: - required: true - - - type: input - id: language - attributes: - label: Language - description: The language code or name this report is about (BCP 47 style when possible). - placeholder: en / zh-CN / ja / pt-BR - validations: - required: true - - - type: textarea - id: problem - attributes: - label: Missing or incorrect copy - description: | - Describe what is wrong and where to find it. Helpful details: - - Which screen, page, button, label, status, empty state, or error message. - - Whether the string is untranslated, mistranslated, or still hardcoded (not loaded from localization resources). - - The exact source text and, if relevant, what it currently shows in your language. - placeholder: | - On the Viewer todo list, the status filter still shows the raw enum value "agent_blocked" instead of a localized label. In zh-CN it appears in English rather than translated, and it looks like the label is hardcoded in the component rather than read from the i18n resources. - validations: - required: true - - - type: textarea - id: suggestion - attributes: - label: Suggested wording - description: Propose the corrected or translated text. Keep displayed labels separate from stored enum values (for example, store `agent_blocked`, display "Agent blocked"). - placeholder: | - Display label for status `agent_blocked`: - - en: "Agent blocked" - - zh-CN: "智能体受阻" - validations: - required: false - - - type: checkboxes - id: contribution - attributes: - label: Contribution - options: - - label: I am willing to open a pull request with this translation or fix. - required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 45dd7493..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,38 +0,0 @@ - - -## What - - - -## Why - - - -## How to verify - - - -```bash -npm install # Node >=20 -npm run pre-pr # consistency check + build + test (~12s, mirrors CI) -``` - - - -Linked issue: Fixes # - -## Checklist - -- [ ] Commits carry a DCO sign-off (`git commit -s`) -- [ ] `npm run pre-pr` is green locally (consistency + build + test) -- [ ] No attribution headers in the commits or PR description (no "Generated with Claude Code", no "Co-Authored-By: Claude") -- [ ] If this touches MCP tools / REST endpoints / the version number, every linked file from the matching list in AGENTS.md (Consistency Rules) was updated in lockstep -- [ ] For a substantial new feature: a reviewed feature doc exists under `docs/features/` (from `template.md`) and is linked here -- [ ] User-facing strings and public docs are in English (PRD default language is English) -- [ ] The corresponding issue is linked above (`Fixes #NNN` / `Closes #NNN`) diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index a1f0c35f..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,69 +0,0 @@ -version: 2 -updates: - - package-ecosystem: npm - directory: / - schedule: - interval: weekly - day: monday - time: "06:00" - timezone: Etc/UTC - open-pull-requests-limit: 10 - labels: - - dependencies - - npm - commit-message: - prefix: deps - include: scope - groups: - minor-and-patch: - update-types: - - minor - - patch - ignore: - # tsdown >= 0.21 breaks `npm run build` on Node 20 (rolldown/tsdown: - # "Failed to import module 'unrun'"). We still support Node >=20, so - # hold tsdown at 0.20.x until upstream restores Node 20 support, then - # remove this ignore. 0.20.x patches and all other deps still update. - - dependency-name: "tsdown" - versions: [">=0.21.0"] - - - package-ecosystem: npm - directory: /website - schedule: - interval: weekly - day: monday - time: "06:00" - timezone: Etc/UTC - open-pull-requests-limit: 10 - labels: - - dependencies - - npm - - website - commit-message: - prefix: deps(website) - include: scope - groups: - minor-and-patch: - update-types: - - minor - - patch - - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - day: monday - time: "06:00" - timezone: Etc/UTC - open-pull-requests-limit: 5 - labels: - - dependencies - - github-actions - commit-message: - prefix: ci(deps) - include: scope - groups: - minor-and-patch: - update-types: - - minor - - patch diff --git a/.github/security-advisories/01-viewer-xss.md b/.github/security-advisories/01-viewer-xss.md deleted file mode 100644 index 046c2862..00000000 --- a/.github/security-advisories/01-viewer-xss.md +++ /dev/null @@ -1,46 +0,0 @@ -# GHSA Draft: Stored XSS in agentmemory real-time viewer - -**Severity:** Critical · **CVSS 3.1:** 9.6 (`AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:L`) -**CWE:** [CWE-79 — Improper Neutralization of Input During Web Page Generation](https://cwe.mitre.org/data/definitions/79.html) -**Affected versions:** `< 0.8.2` -**Patched version:** `0.8.2` - -## Summary - -agentmemory's real-time viewer (default port 3113) rendered user-controlled data — tool outputs, file paths, memory titles, observation content — into HTML using inline `onclick=` event handlers. The viewer's Content Security Policy simultaneously allowed `script-src 'unsafe-inline'`, meaning injected JavaScript would execute in the reader's browser context. - -## Impact - -Any data captured by agentmemory hooks — which includes tool output from Claude Code, Cursor, or any other agent — becomes an XSS vector when the user opens the viewer. An attacker with the ability to influence any captured observation (e.g., by sending a crafted file contents to be read by an agent, or by planting a malicious commit message in a repository) could: - -- Exfiltrate the entire memory store via authenticated requests from the browser -- Read `AGENTMEMORY_SECRET` if the viewer was configured with auth -- Make requests to arbitrary endpoints on behalf of the viewer user -- Modify the DOM to mislead the developer -- Pivot to other localhost services on the developer's machine - -The viewer runs on localhost by default but is **reachable from the browser**, so standard same-origin protections don't help. - -## Patches - -Fixed in **0.8.2**: - -- All inline `on*=` handlers removed from `src/viewer/index.html` -- Replaced with delegated `data-action` event handling -- CSP switched to a **per-response script nonce** (`script-src 'nonce-'`) -- Added `script-src-attr 'none'` to block any inline handler attributes even if injected -- Viewer HTML now rendered through `src/viewer/document.ts` which generates a fresh nonce per request - -## Workarounds - -**None.** Users on affected versions should upgrade to 0.8.2 immediately. Do not open `http://localhost:3113` in a browser on affected versions if you suspect any of your captured observations may contain attacker-controlled content. - -## References - -- Fix PR: [#108](https://github.com/rohitg00/agentmemory/pull/108) -- Commit: [`cbaaf4f`](https://github.com/rohitg00/agentmemory/commit/cbaaf4f) -- Reporter: @eng-pf - -## Credit - -@eng-pf submitted PR #108 with fixes for this and 5 other vulnerabilities. diff --git a/.github/security-advisories/02-curl-sh-rce.md b/.github/security-advisories/02-curl-sh-rce.md deleted file mode 100644 index f32a3e81..00000000 --- a/.github/security-advisories/02-curl-sh-rce.md +++ /dev/null @@ -1,57 +0,0 @@ -# GHSA Draft: Remote shell script execution in agentmemory CLI startup - -**Severity:** Critical · **CVSS 3.1:** 9.8 (`AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`) -**CWE:** [CWE-494 — Download of Code Without Integrity Check](https://cwe.mitre.org/data/definitions/494.html), [CWE-829 — Inclusion of Functionality from Untrusted Control Sphere](https://cwe.mitre.org/data/definitions/829.html) -**Affected versions:** `< 0.8.2` -**Patched version:** `0.8.2` - -## Summary - -The agentmemory CLI (`npx @agentmemory/agentmemory`) auto-installed the iii-engine binary by piping a remote shell script into `sh`: - -```ts -execSync("curl -fsSL https://install.iii.dev/iii/main/install.sh | sh") -``` - -This happened automatically on first run if `iii` was not found in `$PATH`. The script was fetched over HTTPS and executed with the permissions of the user running `npx agentmemory`. No checksum verification, no pinned version, no signature check. - -## Impact - -If `install.iii.dev` were ever compromised — via DNS hijack, domain takeover, expired certificate + MITM on an untrusted network, BGP attack, or any other supply chain attack — **every new agentmemory user would execute attacker-controlled shell code** as their own user. - -This is the canonical "curl | sh" supply chain anti-pattern. It affected: -- Developers running `npx @agentmemory/agentmemory` for the first time -- CI/CD pipelines that installed agentmemory fresh -- Docker builds that installed agentmemory as part of an image - -## Patches - -Fixed in **0.8.2**: - -- Removed `execSync` call entirely from `src/cli.ts` -- CLI now uses an existing local `iii` binary if present in `$PATH` -- Falls back to Docker Compose (`docker compose up -d`) if Docker is available -- Shows manual install instructions if neither iii nor Docker is found: - - `cargo install iii-engine` - - `docker pull iiidev/iii:latest` - - Docs link: https://iii.dev/docs - -## Workarounds - -Users on affected versions should **install iii-engine manually** and run `agentmemory --no-engine` until upgraded: - -```bash -cargo install iii-engine -npx @agentmemory/agentmemory@0.8.1 --no-engine -``` - -Then upgrade to 0.8.2 at the earliest opportunity. - -## References - -- Fix PR: [#108](https://github.com/rohitg00/agentmemory/pull/108) -- Commit: [`cbaaf4f`](https://github.com/rohitg00/agentmemory/commit/cbaaf4f) - -## Credit - -@eng-pf diff --git a/.github/security-advisories/03-default-bind-0000.md b/.github/security-advisories/03-default-bind-0000.md deleted file mode 100644 index f244e5d3..00000000 --- a/.github/security-advisories/03-default-bind-0000.md +++ /dev/null @@ -1,62 +0,0 @@ -# GHSA Draft: agentmemory REST and stream services bound to 0.0.0.0 by default - -**Severity:** High · **CVSS 3.1:** 8.1 (`AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L`) -**CWE:** [CWE-668 — Exposure of Resource to Wrong Sphere](https://cwe.mitre.org/data/definitions/668.html), [CWE-306 — Missing Authentication for Critical Function](https://cwe.mitre.org/data/definitions/306.html) -**Affected versions:** `< 0.8.2` -**Patched version:** `0.8.2` - -## Summary - -The default `iii-config.yaml` bound both the REST API (port 3111) and the streams server (port 3112) to `0.0.0.0`, exposing them on every network interface the host could reach. Combined with the fact that `AGENTMEMORY_SECRET` is **unset by default**, this meant any device on the same local network as a running agentmemory instance could read the entire memory store without authentication. - -Affected endpoints included: -- `GET /agentmemory/export` — full dump of every captured observation, memory, session, and audit entry -- `GET /agentmemory/sessions` — session list -- `POST /agentmemory/smart-search` — arbitrary search over all captured content -- `POST /agentmemory/observe` — ability to **inject** fake observations -- `POST /agentmemory/remember` — ability to plant arbitrary memories -- All 109 other REST endpoints - -## Impact - -A developer running agentmemory on a laptop in a coffee shop, office, or conference WiFi effectively published their entire memory store — including captured API keys, file contents, prompts, decisions, and project context — to anyone on the same network. - -Attackers on the same network could: - -1. **Exfiltrate secrets.** `curl http://:3111/agentmemory/export` downloads everything. Depending on the incompleteness of the secret redaction (see advisory #06), this could include API keys and tokens. -2. **Inject memories.** An attacker could `POST /agentmemory/observe` or `/remember` with fake observations, poisoning the memory store so future sessions retrieve attacker-controlled context. -3. **Pivot to other services.** The mesh sync endpoint (before the auth fix in advisory #04) accepted peer data from any source. - -## Patches - -Fixed in **0.8.2**: - -- `iii-config.yaml` now binds REST, streams to `127.0.0.1` -- Viewer server already bound to `127.0.0.1` -- New `iii-config.docker.yaml` for Docker deployments: containers bind to `0.0.0.0` internally (required for Docker networking) but host port mapping is restricted to `127.0.0.1:port` in `docker-compose.yml` -- README and API section documentation updated to note 127.0.0.1 as the default - -## Workarounds - -Users on affected versions should manually edit their `iii-config.yaml` and change the REST and streams `host` values to `127.0.0.1`: - -```yaml -modules: - - class: modules::api::RestApiModule - config: - host: 127.0.0.1 # was 0.0.0.0 - - class: modules::stream::StreamModule - config: - host: 127.0.0.1 # was 0.0.0.0 -``` - -And set `AGENTMEMORY_SECRET` to a strong random value to protect endpoints even if network exposure is needed. - -## References - -- Fix PR: [#108](https://github.com/rohitg00/agentmemory/pull/108) -- Commit: [`cbaaf4f`](https://github.com/rohitg00/agentmemory/commit/cbaaf4f) - -## Credit - -@eng-pf diff --git a/.github/security-advisories/04-mesh-unauth.md b/.github/security-advisories/04-mesh-unauth.md deleted file mode 100644 index d7ffe2eb..00000000 --- a/.github/security-advisories/04-mesh-unauth.md +++ /dev/null @@ -1,47 +0,0 @@ -# GHSA Draft: Unauthenticated mesh sync in agentmemory - -**Severity:** High · **CVSS 3.1:** 7.4 (`AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N`) -**CWE:** [CWE-306 — Missing Authentication for Critical Function](https://cwe.mitre.org/data/definitions/306.html), [CWE-862 — Missing Authorization](https://cwe.mitre.org/data/definitions/862.html) -**Affected versions:** `< 0.8.2` -**Patched version:** `0.8.2` - -## Summary - -agentmemory's mesh federation feature (P2P sync between instances) accepted push/pull requests on its `/agentmemory/mesh/*` endpoints without requiring authentication. The mesh sync function also did not send any `Authorization` header when calling peer instances, meaning the federation protocol was entirely unauthenticated. - -## Impact - -Any attacker who could reach a mesh-enabled agentmemory instance could: - -1. **Push fake memories** via `POST /agentmemory/mesh/receive` — inject attacker-controlled observations, actions, semantic memories, and relations into the target's memory store. This poisons future retrievals and could be used to manipulate what the target's AI agent sees. -2. **Pull the entire memory store** via `GET /agentmemory/mesh/export` — download all memories, actions, and graph data marked as mesh-shareable. -3. **Chain with advisory #03** — combined with the default `0.0.0.0` binding, mesh endpoints were reachable from any device on the local network without any authentication. - -Mesh is opt-in (requires an explicit peer registration), so this affected only users who had enabled federation. But those users had no authentication at all. - -## Patches - -Fixed in **0.8.2**: - -- All 5 mesh REST endpoints (`mesh-register`, `mesh-list`, `mesh-sync`, `mesh-receive`, `mesh-export`) now return 503 with `"mesh requires AGENTMEMORY_SECRET"` if the secret is not configured -- The `mem::mesh-sync` function now accepts a `meshAuthToken` parameter and **refuses to sync at all** if the token is missing -- Outgoing push/pull requests include `Authorization: Bearer ` headers -- Server-side, all mesh endpoints check bearer auth via the existing `checkAuth` helper - -## Workarounds - -Users on affected versions who have mesh federation enabled should: -1. Set `AGENTMEMORY_SECRET` to a strong random value on **both** peers -2. Restart the server -3. Upgrade to 0.8.2 at the earliest opportunity - -Users who have **not** enabled mesh federation are not affected by this specific issue, but should still upgrade for the other 5 fixes. - -## References - -- Fix PR: [#108](https://github.com/rohitg00/agentmemory/pull/108) -- Commit: [`cbaaf4f`](https://github.com/rohitg00/agentmemory/commit/cbaaf4f) - -## Credit - -@eng-pf diff --git a/.github/security-advisories/05-obsidian-export-traversal.md b/.github/security-advisories/05-obsidian-export-traversal.md deleted file mode 100644 index 13c4b8e9..00000000 --- a/.github/security-advisories/05-obsidian-export-traversal.md +++ /dev/null @@ -1,61 +0,0 @@ -# GHSA Draft: Arbitrary filesystem write via Obsidian export in agentmemory - -**Severity:** Medium · **CVSS 3.1:** 6.5 (`AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L`) -**CWE:** [CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')](https://cwe.mitre.org/data/definitions/22.html), [CWE-73 — External Control of File Name or Path](https://cwe.mitre.org/data/definitions/73.html) -**Affected versions:** `< 0.8.2` -**Patched version:** `0.8.2` - -## Summary - -The `POST /agentmemory/obsidian/export` endpoint accepted a `vaultDir` parameter and passed it directly to `mkdir` and `writeFile` calls without any containment check. A caller could set `vaultDir` to any absolute path on the filesystem and agentmemory would create directories and write Markdown files there with the permissions of the process running the server. - -```bash -# Example exploit payload (affected versions only) -curl -X POST http://localhost:3111/agentmemory/obsidian/export \ - -H "Content-Type: application/json" \ - -d '{"vaultDir": "/etc/cron.d"}' -``` - -The content written would be agentmemory's exported memories in Markdown format, but an attacker could craft specific memory content beforehand to plant arbitrary files. - -## Impact - -When chained with advisory #03 (default `0.0.0.0` binding) or advisory #04 (unauthenticated mesh), an attacker on the local network could write arbitrary files to any filesystem location the agentmemory process had write access to. - -Possible exploitation paths: -- Write to `~/.ssh/authorized_keys` — SSH key injection -- Write to `/etc/cron.d/*` — cron job injection (if running as root) -- Write to `~/.bashrc` or shell rc files — code execution on next shell -- Overwrite any file the process could write to - -## Patches - -Fixed in **0.8.2**: - -- New `AGENTMEMORY_EXPORT_ROOT` environment variable (default: `~/.agentmemory`) -- `vaultDir` now goes through `resolveVaultDir()` in `src/functions/obsidian-export.ts`: - - Resolves the path with `path.resolve` - - Checks `resolved === root || resolved.startsWith(root + path.sep)` - - Returns `null` if the check fails, and the endpoint returns `{ success: false, error: "vaultDir must be inside AGENTMEMORY_EXPORT_ROOT" }` -- Default export is confined to `~/.agentmemory/vault` -- Tests added in `test/obsidian-export.test.ts` for both the custom-but-valid case and the rejection case - -## Known limitations - -`resolveVaultDir()` performs lexical containment only — it does not call `fs.realpathSync` / `fs.lstatSync`. A pre-existing symlink under `AGENTMEMORY_EXPORT_ROOT` that points outside the root can still be written through. Users who allow untrusted processes to create files inside `AGENTMEMORY_EXPORT_ROOT` should additionally run agentmemory inside a sandbox that forbids symlink creation, or file a follow-up issue requesting symlink-aware containment. - -## Workarounds - -Users on affected versions should: -1. **Disable the Obsidian export endpoint** by setting `OBSIDIAN_AUTO_EXPORT=false` (and avoid calling `/agentmemory/obsidian/export` manually) -2. Set `AGENTMEMORY_SECRET` so the endpoint requires bearer auth -3. Upgrade to 0.8.2 - -## References - -- Fix PR: [#108](https://github.com/rohitg00/agentmemory/pull/108) -- Commit: [`cbaaf4f`](https://github.com/rohitg00/agentmemory/commit/cbaaf4f) - -## Credit - -@eng-pf diff --git a/.github/security-advisories/06-privacy-redaction-incomplete.md b/.github/security-advisories/06-privacy-redaction-incomplete.md deleted file mode 100644 index 50c35d4b..00000000 --- a/.github/security-advisories/06-privacy-redaction-incomplete.md +++ /dev/null @@ -1,60 +0,0 @@ -# GHSA Draft: Incomplete secret redaction in agentmemory privacy filter - -**Severity:** Medium · **CVSS 3.1:** 6.2 (`AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N`) -**CWE:** [CWE-532 — Insertion of Sensitive Information into Log File](https://cwe.mitre.org/data/definitions/532.html), [CWE-200 — Exposure of Sensitive Information to an Unauthorized Actor](https://cwe.mitre.org/data/definitions/200.html) -**Affected versions:** `< 0.8.2` -**Patched version:** `0.8.2` - -## Summary - -agentmemory's privacy filter (`src/functions/privacy.ts`) is supposed to strip API keys, secrets, and bearer tokens from captured observations before they are stored. The filter used regex patterns to detect common token formats. Three modern token formats were missing from the patterns: - -1. **Bearer tokens** — `Authorization: Bearer ` headers were not matched, so any captured HTTP request or response that included an Authorization header flowed into the memory store verbatim. -2. **OpenAI project keys** — `sk-proj-*` (the dominant OpenAI API key format since mid-2024) was not matched. The existing `sk-[A-Za-z0-9]{20,}` pattern only caught the legacy format. -3. **GitHub fine-grained service/user tokens** — `ghs_*` and `ghu_*` were not matched. The existing `ghp_[A-Za-z0-9]{36}` pattern only caught personal access tokens. - -## Impact - -agentmemory's README explicitly claimed "Privacy first — API keys, secrets, and `` tags are stripped before anything is stored." That claim was **false** for three common token formats. - -Users relying on the privacy filter to protect their captured observations had a false sense of security. Tokens matching these three patterns would: - -1. Be captured by `PostToolUse` hooks alongside the rest of the tool output -2. Pass through `stripPrivateData()` unmodified -3. Be LLM-compressed and stored in the memory KV -4. Be exposed to any attacker who could reach the `/agentmemory/export` or `/agentmemory/smart-search` endpoints -5. Be included in Obsidian exports, mesh syncs, and CLAUDE.md bridge writes - -When chained with advisory #03 (default `0.0.0.0` binding), this meant network-adjacent attackers could retrieve captured Bearer tokens, OpenAI keys, and GitHub service tokens from the memory store. - -## Patches - -Fixed in **0.8.2**: - -New regex patterns added to `SECRET_PATTERN_SOURCES` in `src/functions/privacy.ts`: - -```ts -/Bearer\s+[A-Za-z0-9._\-+/=]{20,}/gi, -/sk-proj-[A-Za-z0-9\-_]{20,}/g, -/(?:sk|pk|rk|ak)-[A-Za-z0-9][A-Za-z0-9\-_]{19,}/g, -/gh[pus]_[A-Za-z0-9]{36,}/g, -``` - -Three new unit tests in `test/privacy.test.ts` verify each format is now stripped. - -## Workarounds - -Users on affected versions should: -1. Avoid having agents read files or API responses containing these token formats -2. Use the `` tag around any block containing secrets — that filter was not affected -3. Set `AGENTMEMORY_SECRET` to restrict API access -4. Upgrade to 0.8.2 - -## References - -- Fix PR: [#108](https://github.com/rohitg00/agentmemory/pull/108) -- Commit: [`cbaaf4f`](https://github.com/rohitg00/agentmemory/commit/cbaaf4f) - -## Credit - -@eng-pf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7e9c65d..66200ac5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,69 +1,27 @@ name: CI -# `paths-ignore` keeps doc-only / website / README / CHANGELOG churn from -# burning runner minutes. Source / config / workflow changes always run. -# `workflow_dispatch` gives a manual re-run button for flake debugging. on: - push: - branches: [main] - paths-ignore: - - "README.md" - - "CHANGELOG.md" - - "AGENTS.md" - - "ROADMAP.md" - - "website/**" - - "docs/**" - - "assets/**" - - "deploy/**/README.md" - - "**/*.md" - - "**/*.mdx" pull_request: branches: [main] - paths-ignore: - - "README.md" - - "CHANGELOG.md" - - "AGENTS.md" - - "ROADMAP.md" - - "website/**" - - "docs/**" - - "assets/**" - - "deploy/**/README.md" - - "**/*.md" - - "**/*.mdx" + push: + branches: [main] workflow_dispatch: -# Cancel in-flight PR runs when a force-push lands. Keep push runs to -# protect against partial state on main. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: test: - runs-on: ${{ matrix.os }} - strategy: - # Don't bail the whole matrix on one cell's failure — we want to - # see whether the same failure reproduces across OSes (e.g. - # whether a flake is platform-specific or universal). - fail-fast: false - matrix: - # Windows held back: test/obsidian-export.test.ts has hardcoded - # POSIX paths (`/tmp/...`) that fail on D:\ drive runners. - # src/functions/obsidian-export.ts needs os.tmpdir() + path.join - # rework before Windows can be added back. Tracked as follow-up. - os: [ubuntu-latest, macos-latest] - node-version: [20, 22] + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - uses: actions/setup-node@v6 + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} - # Two-step install: generate a lockfile in-runner with - # --package-lock-only, then install from it with `npm ci`. - # Lockfiles are gitignored at the repo level. - - run: npm install --package-lock-only --legacy-peer-deps --no-audit --no-fund - - run: npm ci --legacy-peer-deps --no-audit --no-fund + node-version: 24 + cache: npm + + - run: npm ci - run: npm run build - run: npm test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 8b855efb..00000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: Publish to npm - -on: - release: - types: [published] - workflow_dispatch: - inputs: - packages: - description: "Packages to publish (comma-separated: agentmemory,mcp)" - required: false - default: "agentmemory,mcp" - -# Workflow-level permissions stay minimal — only `contents: read` -# is required to check out the repo. `id-token: write` is granted on -# the publish job for npm's --provenance Sigstore OIDC mint. -permissions: - contents: read - -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - steps: - - uses: actions/checkout@v7 - with: - # Don't persist the GITHUB_TOKEN to .git/config — the - # publish steps don't push back to the repo, so the token - # only needs to live in memory for this checkout. - persist-credentials: false - - - uses: actions/setup-node@v6 - with: - node-version: 22 - registry-url: https://registry.npmjs.org - - # Two-step install: generate a lockfile in-runner with - # --package-lock-only, then install from it with `npm ci`. Gives a - # single deterministic dep graph across build / test / publish - # within one job — important because publish uses `--provenance`. - # Lockfiles are gitignored at the repo level. - - run: npm install --package-lock-only --legacy-peer-deps --no-audit --no-fund - - run: npm ci --legacy-peer-deps --no-audit --no-fund - - run: npm run build - - run: npm test - - - name: Publish @agentmemory/agentmemory - run: | - if npm view "@agentmemory/agentmemory@$(node -p "require('./package.json').version")" version >/dev/null 2>&1; then - echo "Version already published, skipping" - else - npm publish --provenance --access public - fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Wait for npm registry propagation - run: | - VERSION=$(node -p "require('./package.json').version") - for i in $(seq 1 24); do - if npm view "@agentmemory/agentmemory@$VERSION" version >/dev/null 2>&1; then - echo "Registry propagated after ${i} attempt(s)" - exit 0 - fi - echo "Attempt $i: not yet available, sleeping 5s..." - sleep 5 - done - echo "ERROR: registry never propagated after 2 minutes" >&2 - exit 1 - - - name: Publish @agentmemory/mcp shim - working-directory: packages/mcp - run: | - SHIM_VERSION=$(node -p "require('./package.json').version") - if npm view "@agentmemory/mcp@$SHIM_VERSION" version >/dev/null 2>&1; then - echo "Shim version already published, skipping" - else - npm publish --provenance --access public - fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Wait for @agentmemory/mcp registry propagation - working-directory: packages/mcp - run: | - SHIM_VERSION=$(node -p "require('./package.json').version") - for i in $(seq 1 24); do - if npm view "@agentmemory/mcp@$SHIM_VERSION" version >/dev/null 2>&1; then - echo "Shim propagated after ${i} attempt(s)" - exit 0 - fi - echo "Attempt $i: not yet available, sleeping 5s..." - sleep 5 - done - echo "ERROR: shim never propagated after 2 minutes" >&2 - exit 1 diff --git a/.gitignore b/.gitignore index e06b9974..c47cac8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,24 @@ node_modules/ -.agentmemory-python/ dist/ +.codegraph/ +.local/ artifacts/ +data/ +data-*/ *.tsbuildinfo +*.log +.DS_Store .env .env.* !.env.example -*.log -.DS_Store -.claude/ -# lark-cli local credentials — never commit .lark-auth/ - -plugin/scripts/*.map -plugin/scripts/*.d.mts -data/ -!eval/data/ -!eval/data/** -data-*/ -agentmemory-debug/ +.claude/ +.agentmemory-python/ .gstack/ -# Lock files — never commit (see feedback_no_lockfiles memory) -package-lock.json -pnpm-lock.yaml -yarn.lock - -# Eval reports (transient; published scorecards live in docs/benchmarks/) -eval/reports/ -# LongMemEval download is 278MB; fetched on demand -eval/data/longmemeval/ +docs/hybrid-rebuild/ +docs/rebuild/ +docs/product-overview-cn.md +docs/task-chain-perspective-cn.md diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 521a9f7c..00000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -legacy-peer-deps=true diff --git a/AGENTS.md b/AGENTS.md index e2481373..f037d379 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,141 +1,55 @@ -# agentmemory — Agent Instructions +# Repository Rules -## Architecture +## Scope -agentmemory is a persistent memory system for AI coding agents, built on iii-engine's three primitives (Worker/Function/Trigger). Everything goes through `registerFunction`/`registerTrigger`/`sdk.trigger()` — never bypass iii-engine with standalone SQLite or in-process alternatives. +AI-Todo is a local-first todo extraction product for AI/browser/agent sessions. +Keep changes small, public-safe, and aligned with the current LLM-only product +direction. -- **Engine**: iii-sdk (WebSocket to iii-engine on port 49134) -- **State**: File-based SQLite via iii-engine's StateModule (`./data/state_store.db`) -- **Build**: TypeScript → ESM via tsdown, output to `dist/` -- **Test**: vitest (`npm test` excludes integration tests) +## Do Not Commit -## Consistency Rules +- Real `.env` files, API keys, tokens, or local credentials. +- Local data directories: `data/`, `data-*`, `.local/`, `.ai-todo/`. +- Build or dependency output: `dist/`, `node_modules/`, `*.tsbuildinfo`. +- Private planning docs: `docs/hybrid-rebuild/`, `docs/rebuild/`. +- Old private repository content, iii-engine internals, agentmemory graph data, + or real user transcripts. -**When adding or removing MCP tools, you MUST update ALL of the following:** -1. `src/mcp/tools-registry.ts` — tool definition + `getAllTools()` array -2. `src/mcp/server.ts` — handler case in the `mcp::tools::call` switch -3. `src/triggers/api.ts` — REST endpoint registration -4. `src/index.ts` — function registration + endpoint count in the log line -5. `test/mcp-standalone.test.ts` — tool count assertion -6. `README.md` — tool counts (search for "MCP tools") -7. `plugin/.claude-plugin/plugin.json` — tool count in description -8. `plugin/plugin.json` and `plugin/.mcp.copilot.json` (when present) — tool count or MCP exposure +## Architecture Boundaries -**When adding REST endpoints, you MUST update:** -1. `src/triggers/api.ts` — endpoint registration -2. `src/index.ts` — endpoint count in the log line -3. `README.md` — endpoint count (search for "REST endpoints" and "endpoints on port") +- CLI entry point: `src/cli.ts`. +- HTTP UI/API: `src/server/index.ts` plus static files under `public/`. +- MCP stdio: `src/mcp/`. +- SQLite storage and migrations: `src/db/index.ts`. +- Source scanning: `src/sources/`. +- LLM todo extraction: `src/todos/` and `src/extract/llm-runner.ts`. -**When bumping version, you MUST update ALL of the following:** -1. `package.json` — version field -2. `src/version.ts` — VERSION constant and type union -3. `src/types.ts` — ExportData version union -4. `src/functions/export-import.ts` — supportedVersions set -5. `test/export-import.test.ts` — version assertion -6. `plugin/.claude-plugin/plugin.json` — version field -7. `plugin/plugin.json` (when present) — version field +Reuse existing modules before adding new layers. Do not add abstractions for a +single caller. -**When adding new KV scopes:** -1. `src/state/schema.ts` — add to the KV object -2. `src/types.ts` — add the corresponding interface +## Fixed Port Rule -**When adding new audit operations:** -1. `src/types.ts` — add to AuditEntry.operation union type +`ai-todo open` defaults to `127.0.0.1:3111`. Keep `--port ` as the explicit +override and keep `--port 0` available for tests or temporary avoidance. If the +default port is occupied, return a clear error and suggest +`ai-todo open --port `; do not silently choose a random port. -**When adding skills (`plugin/skills//SKILL.md`):** -1. `AGENTS.md` — skill count in Current Stats ("N skills") -2. `plugin/.claude-plugin/plugin.json` — skill count in description (when present) -3. `test/copilot-plugin.test.ts` — add the dir to `KNOWN_SKILL_DIRS` (existence check, not count) +## LLM-only Rule -(Skill counts are NOT locked by `consistency.test`, but keep them honest.) +Todo card generation remains LLM-only. Do not restore rules fallback as the +default organize path. Missing API keys, HTTP timeouts, invalid model output, +or provider errors should produce actionable diagnostics and warnings. -## Code Patterns +## Tests -### Function Registration -```typescript -sdk.registerFunction( - "mem::your-function", - async (data: { ... }) => { - // validate inputs - // do work via kv.get/kv.set/kv.list - // record audit via recordAudit() - return { success: true, ... }; - }, -); -``` - -### REST Endpoint Registration -```typescript -sdk.registerFunction("api::your-endpoint", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - // validate + whitelist fields (never pass raw body to sdk.trigger) - const result = await sdk.trigger({ - function_id: "mem::your-function", - payload: { ... }, - }); - return { status_code: 200, body: result }; -}); -sdk.registerTrigger({ - type: "http", - function_id: "api::your-endpoint", - config: { api_path: "/agentmemory/your-path", http_method: "POST" }, -}); -``` +Run the smallest relevant check first, then the full checks before handoff: -### MCP Tool Handler -```typescript -case "memory_your_tool": { - // validate args with typeof checks - // parse CSV args: args.field.split(",").map(t => t.trim()).filter(Boolean) - const result = await sdk.trigger({ - function_id: "mem::your-function", - payload: { ... }, - }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] } }; -} +```bash +npm test +npm run build +git diff --check ``` -### Hook Scripts -Hook scripts in `src/hooks/` are standalone Node.js scripts (no iii-sdk import). They read JSON from stdin, make HTTP calls to the REST API, and exit. There are two patterns depending on whether Claude Code consumes the script's stdout: - -- **Context-injecting hooks** (`pre-tool-use`, `pre-compact`, `session-start`) write recalled context to stdout for Claude Code to inject. These MUST use `try/catch` with `await fetch(..., { signal: AbortSignal.timeout(N) })` — the script has to wait for the response before exiting, and the timeout is the only bound on hang time. -- **Telemetry-only hooks** (`notification`, `post-tool-failure`, `post-tool-use`, `prompt-submit`, `stop`, `session-end`, `subagent-start`, `subagent-stop`, `task-completed`) write nothing to stdout. These MUST use fire-and-forget `fetch(..., { signal: AbortSignal.timeout(N) }).catch(() => {})` paired with `setTimeout(() => process.exit(0), 500).unref()`. The unawaited fetch dispatches the request; the unref'd `setTimeout` force-exits the process after the request has been flushed to the local daemon's socket buffer (~500ms is enough for single-request hooks; use 1500ms for multi-request hooks like `stop` and `session-end` so all fetches have time to start, especially when `AGENTMEMORY_URL` points to a remote daemon). Without the `setTimeout` Node keeps the event loop alive waiting for any in-flight fetch to settle, which means the hook still blocks Claude Code's next-prompt boundary for up to the AbortSignal duration — exactly the bug fire-and-forget is meant to fix. - -## Coding Standards - -- TypeScript, ESM only (`"type": "module"`) -- No code comments explaining WHAT — use clear naming instead -- Use `fingerprintId()` for content-addressable dedup, `generateId()` for unique IDs -- Parallel operations where possible (`Promise.all` for independent kv writes/reads) -- Input validation at system boundaries (MCP handlers, REST endpoints) -- REST endpoints must whitelist fields — never pass raw request body to `sdk.trigger()` -- Use `recordAudit()` for state-changing operations -- Timestamps: capture once with `new Date().toISOString()` and reuse - -## Testing - -- All tests must pass before PR: `npm test` (950+ tests) -- Mock pattern: `vi.mock("iii-sdk")` with mock `sdk.trigger`, `kv.get/set/list` -- Test files go in `test/` with `.test.ts` extension -- Follow existing patterns in `test/crystallize.test.ts` for function tests - -## Done Means - -A change is done only when: - -- Relevant tests pass — run `./scripts/test.sh` (= `npm test`); add or update tests for behavior changes. -- The pre-PR gate is green: `npm run pre-pr` (consistency + build + test). -- The change stays scoped to the requested task; the diff stays cohesive and reviewable (single-developer stage — no hard line-count cap) and the product stays usable. -- If MCP tools / REST endpoints / version changed, the Consistency Rules above were applied across every listed file. -- Public-facing copy and docs follow the language policy (English-first). - -## Current Stats (v0.9.24) - -- 55 MCP tools (8 visible by default, `AGENTMEMORY_TOOLS=all` for all) -- 142 REST endpoints -- 6 MCP resources, 3 MCP prompts -- 12 hooks, 9 skills -- 60+ iii functions -- 1300+ tests +For local UI verification, build first, start `ai-todo open`, and inspect +Settings, organize warnings, and LLM failure details in Chrome. Do not leak API +keys in logs, screenshots, fixtures, or docs. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index 02741a99..00000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,89 +0,0 @@ -# Architecture - -AI Todo is a local-first tool that extracts actionable todos — with evidence — -from your AI coding sessions (local agent logs) and browser AI conversations, -and shows them in a local web UI. Everything runs on your machine. - -It is built on **iii-engine**'s three primitives — Worker, Function, Trigger. -All state and logic flow through `registerFunction` / `registerTrigger` / -`sdk.trigger()`; the system never bypasses iii-engine with a standalone store. - -- **Engine:** iii-sdk (WebSocket to iii-engine) -- **State:** file-based SQLite via iii-engine's StateModule (`./data/state_store.db`) -- **Build:** TypeScript → ESM via tsdown (output to `dist/`) -- **Surfaces:** local REST API + MCP server + local web UI - -## Overview - -```mermaid -flowchart LR - A["Local Agent Sessions"] --> B["Local Scanner"] - C["Browser AI Conversations"] --> D["Browser Extension"] - B --> E["Normalization Pipeline"] - D --> E - E --> F["Todo Extractor"] - F --> G["Local Database"] - G --> H["Local API"] - H --> I["Local Web UI"] - I --> J["User Actions: done / ignore / delete"] - J --> G - K["i18n Resources"] --> I -``` - -## Components - -| Component | Responsibility | -|---|---| -| Local scanner | Read supported local agent logs and sessions | -| Browser extension | Capture visible browser AI conversation content and source metadata | -| Normalization pipeline | Convert different sources into a common session format | -| Todo extractor | Generate candidate todos with status, summary, and evidence | -| Local database | Persist todos, evidence, scan state, and user actions | -| Local API | Serve todo data to the UI and accept status updates | -| Web UI | View, filter, complete, ignore, delete, and inspect evidence | -| i18n resources | Provide UI strings without changing business logic | - -## Data model - -| Entity | Required fields | -|---|---| -| `sources` | `id`, `type`, `name`, `path_or_url`, `enabled`, `last_scanned_at` | -| `sessions` | `id`, `source_id`, `external_id`, `started_at`, `updated_at`, `raw_ref`, `hash` | -| `todos` | `id`, `session_id`, `title`, `summary`, `status`, `confidence`, `state`, `created_at`, `updated_at` | -| `evidence` | `id`, `todo_id`, `kind`, `text`, `path`, `url`, `timestamp` | -| `scan_checkpoints` | `source_id`, `cursor`, `last_success_at`, `last_error` | - -Todo `state` values: `active`, `done`, `ignored`, `deleted`. Display labels are -kept separate from these stored values (see [RULES.md](RULES.md)). - -## Code map - -Where each component lives today, and what is built vs. in progress: - -| Component | Code | Status | -|---|---|---| -| Local scanner | `src/replay/`, `src/functions/replay.ts` (manual import today) | automatic per-source scanner in progress | -| Browser extension | `browser-extension/` → `src/viewer/server.ts` (capture intake) | built (evidence intake) | -| Normalization | `src/replay/jsonl-parser.ts` | built | -| Todo extractor | `src/functions/action-candidates.ts` (rule-based) | built; LLM classifier deferred to v1.1 | -| Local database | iii-engine StateModule; KV scopes in `src/state/schema.ts` | built | -| Local API | `src/triggers/api.ts` (REST), `src/mcp/` (MCP) | built | -| Web UI | `src/viewer/index.html` | built | -| i18n | `src/viewer/` (inline `{en, zh}` base + `t()`) | base built | - -## Boundaries & design principles - -- All persistence goes through iii-engine's StateModule — no standalone SQLite or in-process store. -- Stored enum values are stable; UI display strings live in i18n resources, keyed by the stored value, separate from logic. -- Validate inputs at system boundaries (MCP handlers, REST endpoints); REST endpoints whitelist fields — never forward a raw request body to `sdk.trigger()`. -- Keep side effects near the boundary; prefer explicit data flow; avoid adding abstractions before a pattern repeats. -- See [AGENTS.md](AGENTS.md) for code patterns, the per-change consistency checklists, and the definition of done. - -## Data flow - -1. The local scanner reads supported agent logs; the browser extension captures browser AI conversations. -2. Sources are normalized into a common session format. -3. The todo extractor produces candidate todos with status, summary, and evidence. -4. Todos and evidence persist in the local database. -5. The local API serves todos to the web UI and accepts status updates. -6. The user marks todos done / ignored / deleted; updates flow back to the database. diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index bbaf95b9..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,1030 +0,0 @@ -# Changelog - -All notable changes to agentmemory will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.9.24] — 2026-05-29 - -Hotfix on top of v0.9.23. Two bugs surfaced in the first hour after v0.9.23 hit npm: - -### Fixed - -- **`agentmemory --version` silently launched the server instead of printing the version string**. `-v` flag was reserved by `--verbose`, and no `--version` literal was handled — falling through to the default `start` command. Now `--version` (and `-V` capital, per POSIX convention) prints `VERSION` and exits 0 before any side effects (engine boot, dir mkdir, env loading). -- **iii-engine version pin was warn-only, not enforced**. When the engine on `PATH` didn't match agentmemory's pinned version (currently `v0.11.2`), the worker still booted against the mismatched engine and crashed at runtime with cryptic `state::list not found` (v0.13.0) or sandbox-everything traps (v0.11.6). `warnIfEngineVersionMismatch` renamed to `enforceEngineVersionPin` — now `p.log.error`s with the downgrade command and `process.exit(1)`. Override escape hatch via `AGENTMEMORY_III_VERSION=` env unchanged (already redefines `IIPINNED_VERSION` upstream so the comparison passes for users who knowingly run against a different engine). - -[0.9.24]: https://github.com/rohitg00/agentmemory/compare/v0.9.23...v0.9.24 - -## [0.9.23] — 2026-05-28 - -Bug-fix + integration wave. GitHub Copilot CLI joins the supported agent matrix with plugin + hooks + MCP coverage. Three silent DX bugs fixed end-to-end: graph extraction never fired on session end, `agentmemory status` reported zero memories, consolidation defaulted off even with an LLM provider configured. Five additional adapters and a clearer local-LLM story for Ollama / LM Studio users. `agentmemory connect` now points users at `npx skills add` for the native-skills install path (50+ agents). - -### Added - -- **GitHub Copilot CLI** ([PR #534](https://github.com/rohitg00/agentmemory/pull/534)). Full first-class support: `plugin/plugin.json` manifest, `hooks.copilot.json` lifecycle hooks for all 11 Copilot events with camelCase payload normalization, `agentmemory connect copilot-cli` MCP path, first-run onboarding default-select via `COPILOT_CLI` / `COPILOT_AGENT_SESSION_ID` env detection, Windows-safe `cmd.exe /d /s /c npx` wrapper. Standalone MCP transport now speaks LSP-style `Content-Length` framed JSON-RPC alongside the existing newline-delimited form so Copilot's stdio init handshake works. -- **Five new MCP adapters** ([PR #677](https://github.com/rohitg00/agentmemory/pull/677)). Warp, Cline, Continue, Zed, and Droid. `createJsonMcpAdapter` extended with `wrapperKey` (Zed uses `context_servers` instead of `mcpServers`) and `extraEntryFields` (Droid requires `type: "stdio"`). `ADAPTERS` count: 11 → 17. -- **`/agentmemory/graph/build` endpoint** ([PR #698](https://github.com/rohitg00/agentmemory/pull/698)). Backfills the knowledge graph from existing compressed observations across every session in configurable batches. Wires up the viewer's "Build Graph" button that previously returned 404. -- **11 README translations + language picker** ([PR #675](https://github.com/rohitg00/agentmemory/pull/675)). zh-CN, zh-TW, ja-JP, ko-KR, es-ES, pt-BR, fr-FR, de-DE, ru-RU, tr-TR, hi-IN. -- **`npx skills add` hint in connect output** ([PR #709](https://github.com/rohitg00/agentmemory/pull/709)). After a successful `agentmemory connect `, the CLI now prints the matching `npx skills add rohitg00/agentmemory -y` command so users get the native-skills install alongside MCP wiring. The [`skills`](https://npmjs.com/package/skills) CLI covers 50+ agents including the 5 added in PR #677. README "Other agents" section gains a dedicated subsection explaining the two-step pattern (`connect` writes MCP config, `skills add` installs the 8 SKILL.md files into the agent's native skill directory). - -### Changed - -- **Consolidation auto-enables when an LLM provider is configured** ([PR #696](https://github.com/rohitg00/agentmemory/pull/696), closes [#612](https://github.com/rohitg00/agentmemory/discussions/612)). `CONSOLIDATION_ENABLED` defaulted to `false`, so users with a working provider got compression + summarization but zero graph nodes / crystals / lessons. Now defaults `true` whenever any of `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `OPENROUTER_API_KEY` / `GEMINI_API_KEY` / `GOOGLE_API_KEY` / `MINIMAX_API_KEY` / `OPENAI_BASE_URL` is set, or `AGENTMEMORY_PROVIDER=agent-sdk`. Explicit `=false` / `AGENTMEMORY_PROVIDER=noop` still opt out. `OPENAI_API_KEY_FOR_LLM=false` honored (key scoped to embeddings). -- **Fire-and-forget telemetry hooks** ([PR #688](https://github.com/rohitg00/agentmemory/pull/688), closes [#573](https://github.com/rohitg00/agentmemory/issues/573)). Nine telemetry-only hooks (notification, post-tool-failure, post-tool-use, prompt-submit, stop, session-end, subagent-start, subagent-stop, task-completed) switched to unawaited `fetch(...).catch(() => {})` paired with `setTimeout(() => process.exit(0), 500).unref()` (1500ms for the multi-fetch `stop` + `session-end` hooks). No longer blocks Claude Code's next-prompt boundary on every assistant turn. -- **Observability defaults tamed** ([PR #686](https://github.com/rohitg00/agentmemory/pull/686), closes [#519](https://github.com/rohitg00/agentmemory/issues/519)). `iii-config.yaml` defaults: `sampling_ratio: 0.1`, `logs_console_output: false`. `findIiiConfig()` precedence reversed so `AGENTMEMORY_III_CONFIG` env > cwd > `~/.agentmemory/iii-config.yaml` > bundled. - -### Fixed - -- **Graph extraction never fires automatically** ([PR #698](https://github.com/rohitg00/agentmemory/pull/698), closes [#666](https://github.com/rohitg00/agentmemory/issues/666)). `event::session::stopped` listens on `agentmemory.session.stopped` but nothing in the codebase ever published that topic. `api::session::end` now directly calls `sdk.triggerVoid("event::session::stopped", { sessionId })` (try/catch guarded so a fan-out error doesn't fail the HTTP response). Knowledge graphs now materialize automatically when sessions end. -- **`agentmemory status` shows Memories/Observations = 0** ([PR #698](https://github.com/rohitg00/agentmemory/pull/698), closes [#666](https://github.com/rohitg00/agentmemory/issues/666)). CLI fetched `/agentmemory/export` for counts but that endpoint times out (>5s) on iii-engine's file-based KV under concurrent `kv.list()`. Switched to `/memories?count=true` (count-only, constant-time) + sum of `sessions[].observationCount`. -- **Hooks send full filesystem path as `project` instead of repo basename** ([PR #687](https://github.com/rohitg00/agentmemory/pull/687), closes [#474](https://github.com/rohitg00/agentmemory/issues/474)). Native sessions, replay-import, and `memory_lesson_save` all use repo basename — the path/basename mismatch silently filtered out the bulk of relevant lessons from auto-injected context. New `resolveProject(cwd?)` helper: `AGENTMEMORY_PROJECT_NAME` env > `git rev-parse --show-toplevel` basename > `basename(cwd)`. Applied to 9 hooks. -- **Cross-project memory leakage** ([PR #662](https://github.com/rohitg00/agentmemory/pull/662)). Memories created in one project leaking into recall on another. Wildcard guard pattern `(!project || !m.project || m.project === project)` preserves backward-compat for legacy unscoped memories while scoping new ones strictly. -- **Graph parser tolerates reordered XML attributes** ([PR #685](https://github.com/rohitg00/agentmemory/pull/685), closes [#635](https://github.com/rohitg00/agentmemory/issues/635)). Order-dependent regex replaced with order-independent `parseAttrs` helper. Self-closing trailing-`/` handling preserved. -- **Defensive null guards on `Memory.sessionIds`** ([PR #684](https://github.com/rohitg00/agentmemory/pull/684)). Older exports + hand-edited dumps can omit this field; now treated as empty array on read. -- **Vector index Buffer slice metadata round-trip** ([PR #683](https://github.com/rohitg00/agentmemory/pull/683), closes [#587](https://github.com/rohitg00/agentmemory/issues/587), [#584](https://github.com/rohitg00/agentmemory/issues/584)). `byteOffset` + `byteLength` preserved on `Buffer.from` and `Float32Array` construction so base64 round-trips don't corrupt vectors stored as slices. -- **Slots HTTP triggers return 503 not 500** ([PR #682](https://github.com/rohitg00/agentmemory/pull/682), closes [#678](https://github.com/rohitg00/agentmemory/issues/678)). Feature-flag-disabled responses now use the documented 503 shape with `enableHow` + `docsHref`. `.env` propagation fixed (now reads via `getEnvVar()` not raw `process.env`). -- **`agentmemory doctor` points to correct iii install URL** ([PR #681](https://github.com/rohitg00/agentmemory/pull/681)). -- **Separate `OPENAI_EMBEDDING_BASE_URL` + `OPENAI_EMBEDDING_API_KEY`** ([PR #503](https://github.com/rohitg00/agentmemory/pull/503)). Lets users route embeddings and LLM calls to different OpenAI-compatible endpoints (e.g. cloud OpenAI for embeddings + local LM Studio for LLM, or vice versa). -- **Viewer Memories tab sorts newest first** ([PR #701](https://github.com/rohitg00/agentmemory/pull/701), closes [#674](https://github.com/rohitg00/agentmemory/issues/674)). Memories list rendered in KV-insertion order, hiding just-saved entries at the bottom of long lists. `loadMemories()` now sorts on `createdAt` desc (fallback `updatedAt`) before items reach state — matches the `localeCompare` pattern Sessions / Metrics tabs already use. - -### Docs - -- **Local-LLM section in README** ([PR #697](https://github.com/rohitg00/agentmemory/pull/697), closes [#671](https://github.com/rohitg00/agentmemory/discussions/671)). Dedicated "Local models (Ollama / LM Studio / vLLM)" subsection with copy-paste configs, model-pick table (qwen2.5-coder:7b, llama3.2:3b, mistral:7b-instruct, deepseek-r1:7b), and reasoning-model empty-content callout. Existing OpenAI-compatible support was buried in a one-line env comment. -- **Three pipeline layers, not three primitives** ([PR #690](https://github.com/rohitg00/agentmemory/pull/690)). Website + README copy: the three primitives are worker / function / trigger (iii). HOOKS / RECALL / CONSOLIDATE are pipeline layers. Section anchor renamed `#primitives` → `#stack` to match the nav label. - -### Infrastructure - -- Hook scripts now bundle per-entry instead of sharing chunks, so `plugin/scripts/_project-*.mjs` hashed artifacts no longer churn the diff on every build. -- `AGENTS.md` "Hook Scripts" section documents the two patterns (context-injecting vs telemetry-only) and the 500ms / 1500ms exit-delay rule for single vs multi-fetch hooks. -- Test suite: 1271 → 1291 (+20 new tests covering project basename resolver, fire-and-forget wiring, consolidation default behavior, session-end → graph extraction, the graph-build endpoint, and the viewer memories sort). - -[0.9.23]: https://github.com/rohitg00/agentmemory/compare/v0.9.22...v0.9.23 - -## [0.9.22] — 2026-05-26 - -Stability + ecosystem wave. Three install-broken bugs (`npm install` ERESOLVE, non-OpenAI base URLs, broken Claude bridge path) closed. Six runtime bugs from active users fixed end-to-end. Three new agent integrations (Qwen Code, Antigravity, Kiro). New `AGENT_ID` scope for multi-agent setups. Port mapping documented. - -### Fixed - -- **`npm install` ERESOLVE on fresh install** ([PR #649](https://github.com/rohitg00/agentmemory/pull/649), closes [#631](https://github.com/rohitg00/agentmemory/issues/631)). `@anthropic-ai/sdk` bumped from `^0.39.0` to `^0.93.0` so `claude-agent-sdk`'s peer is satisfied. Verified clean install with only the published `dependencies` block. - -- **Non-OpenAI base URLs silent-404** ([PR #649](https://github.com/rohitg00/agentmemory/pull/649), closes [#646](https://github.com/rohitg00/agentmemory/issues/646), [#628](https://github.com/rohitg00/agentmemory/issues/628)). `buildChatUrl` + `buildEmbeddingUrl` no longer blindly prepend `/v1/`. DeepSeek, SiliconFlow, Zhipu (`/api/paas/v4`), vLLM, LM Studio, Ollama all resolve correctly. - -- **`CLAUDE_MEMORY_BRIDGE` writes to a path Claude Code reads** ([PR #649](https://github.com/rohitg00/agentmemory/pull/649), closes [#625](https://github.com/rohitg00/agentmemory/issues/625)). Slug now preserves the leading `-` on POSIX absolute paths and drops the spurious `/memory/` subdir, matching `~/.claude/projects//MEMORY.md`. - -- **OpenAI provider reads `reasoning_content` for thinking models** ([PR #648](https://github.com/rohitg00/agentmemory/pull/648), closes [#627](https://github.com/rohitg00/agentmemory/issues/627)). DeepSeek V4 / Qwen3 / GLM / Kimi return `message.reasoning_content`. Previously only `message.reasoning` was checked — compress silently failed every call and tripped the circuit breaker. - -- **`agentmemory stop` reaps the worker process** ([PR #648](https://github.com/rohitg00/agentmemory/pull/648), closes [#640](https://github.com/rohitg00/agentmemory/issues/640), [#474](https://github.com/rohitg00/agentmemory/issues/474)). Worker pid is written to `~/.agentmemory/worker.pid` on boot; `stop` signals both engine + worker. - -- **OpenCode plugin implicit-creates the session on first observation** ([PR #648](https://github.com/rohitg00/agentmemory/pull/648), closes [#638](https://github.com/rohitg00/agentmemory/issues/638)). `mem::observe` creates the session row when one doesn't exist. No more orphan observations or `Session not found for summarize`. - -- **OpenCode plugin zero-config auto-context injection** ([PR #648](https://github.com/rohitg00/agentmemory/pull/648), closes [#431](https://github.com/rohitg00/agentmemory/issues/431)). `POST /session/start` context is cached per-session; the existing `experimental.chat.system.transform` hook reads from the cache. - -- **Viewer graph settles on 1000+ node graphs** ([PR #648](https://github.com/rohitg00/agentmemory/pull/648), closes [#563](https://github.com/rohitg00/agentmemory/issues/563)). Tick-decayed damping, per-node velocity cap, raf park on quiescence. Mousedown / wheel / zoom / recenter re-wake the parked loop. - -- **`/memories` + `/export` paginate** ([PR #648](https://github.com/rohitg00/agentmemory/pull/648), closes [#544](https://github.com/rohitg00/agentmemory/issues/544)). New `?count=true` and `?limit=N&offset=M` on `/memories`. `/export` forwards `?maxSessions` + `?offset`. Stops large corpora (8K+ memories) from timing out at the iii-engine invocation boundary. - -- **Claude Code drops the MCP server silently** ([PR #650](https://github.com/rohitg00/agentmemory/pull/650), closes [#510](https://github.com/rohitg00/agentmemory/issues/510)). `plugin/.mcp.json` env block uses `${VAR:-default}` form. Unset required vars no longer fail config parse. - -- **Full 51-tool MCP surface by default** ([PR #650](https://github.com/rohitg00/agentmemory/pull/650), closes [#553](https://github.com/rohitg00/agentmemory/issues/553)). `getVisibleTools()` default flipped from `core` (8) to `all` (51) to match what every plugin manifest advertises. `AGENTMEMORY_TOOLS=core` still gives the lean set. - -- **Connect adapters write `${VAR:-default}` env block** ([PR #650](https://github.com/rohitg00/agentmemory/pull/650)). `agentmemory connect` for Claude Code / Cursor / Gemini CLI / Windsurf writes the same default form. - -- **Hermes `memory status` no longer reports the plugin as Missing** ([PR #643](https://github.com/rohitg00/agentmemory/pull/643), closes [#520](https://github.com/rohitg00/agentmemory/issues/520)). Hermes plugin seeds `AGENTMEMORY_URL` to `http://localhost:3111` at import. Works for systemd-managed agentmemory where the env file is loaded via `EnvironmentFile=` and never reaches the interactive shell. - -- **Deleted memories cleared from BM25 + vector indices** ([PR #636](https://github.com/rohitg00/agentmemory/pull/636) by [@abhinav-m22](https://github.com/abhinav-m22), closes [#632](https://github.com/rohitg00/agentmemory/issues/632)). `SearchIndex.remove()` added and called from every delete path. Snapshot flushed synchronously so a SIGKILL between mutation + debounce can't resurrect deleted entries. - -- **PostToolUse hook reads `tool_response`, falls back to `tool_output`** ([PR #561](https://github.com/rohitg00/agentmemory/pull/561) by [@faraz152](https://github.com/faraz152), closes [#539](https://github.com/rohitg00/agentmemory/issues/539)). Claude Code's PostToolUse payload uses `tool_response`. Now reads `tool_response ?? tool_output` so legacy integrations keep working. - -- **iii-sdk pinned to exact `0.11.2`** ([PR #567](https://github.com/rohitg00/agentmemory/pull/567), closes [#555](https://github.com/rohitg00/agentmemory/issues/555)). `iii-sdk@0.11.6` introduced a routing regression where every `/agentmemory/*` route returned 404. Pin removes the caret. - -- **OpenAI provider sends explicit `stream: false`** ([PR #526](https://github.com/rohitg00/agentmemory/pull/526) by [@Ptah-CT](https://github.com/Ptah-CT)). Some OpenAI-compatible proxies default to `text/event-stream` when `stream` is absent. - -- **Viewer search uses NFKC normalisation for CJK / fullwidth input** ([PR #542](https://github.com/rohitg00/agentmemory/pull/542) by [@kaushalrog](https://github.com/kaushalrog)). - -- **Viewer splash shows actual bound viewer port** ([PR #560](https://github.com/rohitg00/agentmemory/pull/560) by [@Tanmay-008](https://github.com/Tanmay-008), closes [#521](https://github.com/rohitg00/agentmemory/issues/521)). `/agentmemory/livez` now returns `viewerPort` + `viewerSkipped`. - -- **Viewer tab bar height stable across tab switches** ([PR #325](https://github.com/rohitg00/agentmemory/pull/325) by [@hungtd119](https://github.com/hungtd119), closes [#324](https://github.com/rohitg00/agentmemory/issues/324)). - -- **Graph parser accepts self-closing `` tags** ([PR #494](https://github.com/rohitg00/agentmemory/pull/494) by [@Rex57](https://github.com/Rex57), closes [#492](https://github.com/rohitg00/agentmemory/issues/492)). - -- **Plugin MCP server inherits remote/auth env** ([PR #386](https://github.com/rohitg00/agentmemory/pull/386) by [@LaplaceYoung](https://github.com/LaplaceYoung), closes [#375](https://github.com/rohitg00/agentmemory/issues/375)). - -- **`@agentmemory/mcp` rejects literal `${VAR}` placeholders**. Any `AGENTMEMORY_URL` / `AGENTMEMORY_SECRET` value of the form `${...}` is treated as unset and falls back to `http://localhost:3111`. - -- **Codex `stop` hook closes session** ([PR #579](https://github.com/rohitg00/agentmemory/pull/579), closes [#493](https://github.com/rohitg00/agentmemory/issues/493)). `Stop` was missing from the Codex bundle; session never got marked completed. - -- **Claude Code `--with-hooks` works for MCP-standalone users** ([PR #581](https://github.com/rohitg00/agentmemory/pull/581), closes [#508](https://github.com/rohitg00/agentmemory/issues/508)). - -### Added - -- **`AGENT_ID` multi-agent memory isolation** ([PR #654](https://github.com/rohitg00/agentmemory/pull/654), closes [#554](https://github.com/rohitg00/agentmemory/issues/554)). Optional `AGENT_ID` env tags every Session / RawObservation / CompressedObservation / Memory. `AGENTMEMORY_AGENT_SCOPE=isolated` (opt-in; `shared` default) also filters every recall path (`mem::smart-search`, `/memories`, `/observations`, `/sessions`). Per-call overrides via request body + `?agentId=` / `?agentId=*` query params. `?includeOrphans=true` surfaces pre-tag rows. - -- **Qwen Code connect adapter** ([PR #651](https://github.com/rohitg00/agentmemory/pull/651), closes [#647](https://github.com/rohitg00/agentmemory/issues/647)). `agentmemory connect qwen` writes the standard `mcpServers` block to `~/.qwen/settings.json`. - -- **Antigravity connect adapter** ([PR #651](https://github.com/rohitg00/agentmemory/pull/651), closes [#614](https://github.com/rohitg00/agentmemory/issues/614)). Replacement for Gemini CLI (sunset 2026-06-18). Writes `mcp_config.json` to the platform-specific User dir. - -- **Kiro connect adapter** ([PR #651](https://github.com/rohitg00/agentmemory/pull/651), closes [#618](https://github.com/rohitg00/agentmemory/issues/618)). Writes user-level `~/.kiro/settings/mcp.json`. - -- **Cost-aware model selection** ([PR #654](https://github.com/rohitg00/agentmemory/pull/654), closes [#613](https://github.com/rohitg00/agentmemory/issues/613)). Runtime warning when `OPENROUTER_MODEL` matches the premium pattern. README cost-tier table with measured workload data. Suppress via `AGENTMEMORY_SUPPRESS_COST_WARNING=1`. - -- **Pluggable benchmark harness** ([PR #562](https://github.com/rohitg00/agentmemory/pull/562)). New `eval/` directory with the `coding-agent-life-v1` corpus (15 sessions + 15 graded queries) and three adapters (grep, OpenAI embeddings + cosine, agentmemory hybrid). LongMemEval support. Sandboxed agentmemory + iii-engine on alt ports via `eval/scripts/sandbox.sh`. Dev-only — no runtime impact. - -- **`agentmemory connect codex --with-hooks` opt-in flag** ([PR #564](https://github.com/rohitg00/agentmemory/pull/564), closes [#509](https://github.com/rohitg00/agentmemory/issues/509)). Workaround for [openai/codex#16430](https://github.com/openai/codex/issues/16430). - -- **Cross-platform CI matrix** ([PR #556](https://github.com/rohitg00/agentmemory/pull/556)). Ubuntu + macOS × Node 20 + 22, `paths-ignore`, per-branch concurrency cancellation. - -### Docs - -- **Port mapping table** ([PR #651](https://github.com/rohitg00/agentmemory/pull/651), closes [#629](https://github.com/rohitg00/agentmemory/issues/629)). `3111` REST / `3112` streams / `3113` viewer / `49134` engine WS + env overrides + stale-process cleanup recipe. - -- **Pairings recipe** ([PR #641](https://github.com/rohitg00/agentmemory/pull/641)). `docs/recipes/pairings.md` covers stacking agentmemory with codegraph, Understand Anything, and Graphify. - -- **Multi-agent README section** ([PR #654](https://github.com/rohitg00/agentmemory/pull/654)). `AGENT_ID` + `AGENTMEMORY_AGENT_SCOPE` semantics, per-endpoint behavior table. - -- **Supply-chain policy in SECURITY.md** ([PR #654](https://github.com/rohitg00/agentmemory/pull/654), closes [#540](https://github.com/rohitg00/agentmemory/issues/540)). Explains why no lockfile is committed (`dist/` ships pre-built) and what monitoring exists. - -- **README "Config File" section + Windows path** ([PR #321](https://github.com/rohitg00/agentmemory/pull/321) by [@aqilaziz](https://github.com/aqilaziz), closes [#293](https://github.com/rohitg00/agentmemory/issues/293)). - -- **README sudo install hint for `EACCES`** ([PR #454](https://github.com/rohitg00/agentmemory/pull/454) by [@kedar-1](https://github.com/kedar-1)). - -### Infrastructure - -- 108 test files, **1171 tests pass**. -- Agent count: 8 → 11 (claude-code, codex, cursor, gemini-cli, qwen, antigravity, kiro, openclaw, hermes, pi, openhuman). - -## [0.9.21] — 2026-05-19 - -Quality + integration wave. Headline: native OpenCode plugin with full Claude Code hook parity ([#237](https://github.com/rohitg00/agentmemory/pull/237) by [@cl0ckt0wer](https://github.com/cl0ckt0wer)). Ten more PRs alongside: `memory_recall` returning the wrong shape, env-file `AGENTMEMORY_DROP_STALE_INDEX` silently ignored, hook scripts crashing on Windows usernames with spaces, viewer search inputs interrupting CJK IME composition, large sessions silently failing at the LLM context limit, lessons invisible to smart-search, Hermes plugin manifest missing hooks, cli onboarding crashing in non-TTY contexts, rebuildIndex blocking boot on large corpora, 25h embed-loop bottleneck during rebuild, and the v0.9.19 iii-console installer workaround can come out now that upstream is fixed. - -### Added - -- **OpenCode plugin with 22 auto-capture hooks** ([PR #237](https://github.com/rohitg00/agentmemory/pull/237) by [@cl0ckt0wer](https://github.com/cl0ckt0wer), closes [#236](https://github.com/rohitg00/agentmemory/issues/236) + [#244](https://github.com/rohitg00/agentmemory/issues/244)). Complete OpenCode plugin in `plugin/opencode/` matching Claude Code hook parity. Covers session lifecycle (8 hooks), messages (3), tool lifecycle (2), part tracking, permissions, task tracking, plus a two-layer enrichment pipeline (memory context on first turn, file enrichment on subsequent turns) and two slash commands (`/recall`, `/remember`). Full gap analysis in `plugin/opencode/README.md`. - -### Fixed - -- **`memory_recall` endpoint + format/token_budget forwarding** ([PR #516](https://github.com/rohitg00/agentmemory/pull/516) by [@serhiizghama](https://github.com/serhiizghama), closes [#507](https://github.com/rohitg00/agentmemory/issues/507) + [#440](https://github.com/rohitg00/agentmemory/issues/440)). MCP `memory_recall` always returned compact mode and dropped `format` + `token_budget` params. Two root causes fixed: standalone shim routed through `/agentmemory/smart-search` instead of `/agentmemory/search`, and the local-fallback path didn't read either param. Now routes correctly, forwards both params end-to-end, defaults `format` to `"full"` matching the MCP schema. - -- **env-file `AGENTMEMORY_DROP_STALE_INDEX` flag now honored** ([PR #461](https://github.com/rohitg00/agentmemory/pull/461) by [@honor2030](https://github.com/honor2030), closes [#456](https://github.com/rohitg00/agentmemory/issues/456)). Setting the flag in `~/.agentmemory/.env` was silently ignored because the boot path read `process.env` directly. New `isDropStaleIndexEnabled()` helper reads merged env. Combined with [#455](https://github.com/rohitg00/agentmemory/issues/455) + [#469](https://github.com/rohitg00/agentmemory/issues/469) reports, this is the unblock path for the stale-index server-crash recovery loop. - -- **Windows hook scripts quote plugin paths correctly** ([PR #487](https://github.com/rohitg00/agentmemory/pull/487) by [@honor2030](https://github.com/honor2030), closes [#477](https://github.com/rohitg00/agentmemory/issues/477)). Hook command strings referenced `${CLAUDE_PLUGIN_ROOT}/scripts/*.mjs` without quotes — Windows users with spaces in their username had every hook crash. Quotes added + regression test. - -- **Viewer search inputs honor IME composition** ([PR #517](https://github.com/rohitg00/agentmemory/pull/517) by [@jonathanzhan1975](https://github.com/jonathanzhan1975)). CJK users typing in the viewer's search inputs hit mid-character interruption — every keystroke fired the `oninput=` re-render handler, breaking IME composition mid-syllable. New `bindImeSafeSearch` helper defers re-render until `compositionend`. - -- **Chunk large sessions to fit LLM context window** ([PR #472](https://github.com/rohitg00/agentmemory/pull/472) by [@efenex](https://github.com/efenex)). Sessions with >7000 observations silently failed at the LLM provider's context limit — the consolidation pipeline silently skipped the session. New chunking splits oversized sessions across multiple compress calls + restitches the narrative via a `REDUCE_SYSTEM` prompt. Legacy single-call path preserved when obs count is under the chunk size. Backfill script under `scripts/` for users hitting the pre-fix bug. - -- **Surface lessons in smart-search + diagnose tally** ([PR #473](https://github.com/rohitg00/agentmemory/pull/473) by [@efenex](https://github.com/efenex)). Closes the lesson round-trip with [#458](https://github.com/rohitg00/agentmemory/pull/458) (lessons auto-injected into `mem::context`): lessons are now also returned alongside hybrid search results in a separate `lessons` field on `smart-search`, and the `diagnose` health surface tallies per-store counts so the trust-shock pattern (save succeeds, recall empty, diagnose says 0) goes away. - -- **Declare all Hermes plugin hooks** ([PR #486](https://github.com/rohitg00/agentmemory/pull/486) by [@honor2030](https://github.com/honor2030)). The Hermes `plugin.yaml` manifest only declared 3 of the 6 implemented hooks. All 6 now declared (`prefetch`, `sync_turn`, `on_session_end`, `on_pre_compress`, `on_memory_write`, `system_prompt_block`). - -- **`rebuildIndex` non-blocking on boot** ([PR #500](https://github.com/rohitg00/agentmemory/pull/500) by [@efenex](https://github.com/efenex)). Boot path previously `await`-ed `rebuildIndex(kv)`, so the viewer + later boot steps stalled — on large corpora this was 25h+ of blocked startup. Replaced with `void rebuildIndex(kv).then(...).catch(...)` so the rebuild runs in the background. - -- **Batched embed calls in `rebuildIndex` (25h → 3h on large corpora)** ([PR #504](https://github.com/rohitg00/agentmemory/pull/504) by [@efenex](https://github.com/efenex)). The rebuild loop made one embed call per observation, paying full HTTP RTT per item. New `vectorIndexAddBatchGuarded` helper batches embeds (default 32, configurable via `REBUILD_EMBED_BATCH_SIZE`) and try/catches per-item failures. Measured 25h → 3h on a 250k-observation corpus. - -- **CLI skips onboarding prompts without a tty** ([PR #491](https://github.com/rohitg00/agentmemory/pull/491) by [@honor2030](https://github.com/honor2030)). Onboarding prompts crashed in non-interactive contexts (CI, `docker run -d`, piped input). New guard short-circuits with sensible defaults when stdin/stdout aren't TTYs or `CI=1`. - -### Changed - -- **Drop iii-console installer `--next` workaround** ([PR #546](https://github.com/rohitg00/agentmemory/pull/546)). v0.9.19 routed first-run iii-console install through `bash -s -- --next` to dodge an upstream tag-prefix bug at [iii-hq/iii#1652](https://github.com/iii-hq/iii/issues/1652). Upstream [iii-hq/iii#1660](https://github.com/iii-hq/iii/pull/1660) shipped 2026-05-19; `install.iii.dev/console/main/install.sh` is a CDN proxy serving upstream main HEAD so the fix is live without an iii release tag. Reverted to canonical bare `curl ... | sh`. - -### Infrastructure - -- 95 test files (was 92), **1067 tests pass** (was 1038) on `chore(release): v0.9.21`. -- Bundles 11 PRs: 1 contributor feature + 9 bug fixes across MCP / hooks / viewer / summarize / lessons / Hermes / rebuildIndex / CLI + 1 upstream-installer revert. -- New contributors landing first PRs this release: [@cl0ckt0wer](https://github.com/cl0ckt0wer), [@serhiizghama](https://github.com/serhiizghama), [@jonathanzhan1975](https://github.com/jonathanzhan1975). - -[0.9.21]: https://github.com/rohitg00/agentmemory/compare/v0.9.20...v0.9.21 - -## [0.9.20] — 2026-05-18 - -Hotfix: revert the Codex Stop → session-end chain shipped in v0.9.19. - -### Fixed - -- **Revert Codex Stop hook session-end chain** ([PR #501](https://github.com/rohitg00/agentmemory/pull/501) by [@Rex57](https://github.com/Rex57), reverts v0.9.19's [#495](https://github.com/rohitg00/agentmemory/pull/495), re-opens [#493](https://github.com/rohitg00/agentmemory/issues/493)). Post-merge field-testing surfaced the underlying issue: Codex `Stop` fires before the overall conversation is truly finished — multiple Stops bracket each assistant turn within one session. Chaining `session-end.mjs` from Stop marked sessions completed too early, with later observations still arriving against an `endedAt`-stamped record. Restored to summarize-only Stop; the SessionEnd-shaped solution stays open as [#493](https://github.com/rohitg00/agentmemory/issues/493). - -[0.9.20]: https://github.com/rohitg00/agentmemory/compare/v0.9.19...v0.9.20 - -## [0.9.19] — 2026-05-18 - -Feature + hardening wave. Sessions now link to the git commits they shipped (forward + reverse lookup, REST + MCP surfaces). OpenAI provider transport collapses into one shared module with auto-detected Azure URL style (legacy `/openai/deployments/` and v1 `/openai/v1` both supported). Graph retrieval switches from BFS to Dijkstra over the weighted edge graph. Codex Stop hook chains session-end. Plugin MCP server inherits `AGENTMEMORY_URL` / `AGENTMEMORY_SECRET` from the shell. Point fix routes the bundled iii-console installer around an upstream tag-prefix bug. 1007+ tests pass. - -### Added - -- **Session-to-commit linking** ([PR #498](https://github.com/rohitg00/agentmemory/pull/498)). New `KV.commits` namespace keyed by full SHA holds `CommitLink` records (sha, shortSha, branch, repo, message, author, authoredAt, files, sessionIds). `Session.commitShas[]` provides the forward back-reference. REST: `POST /agentmemory/session/commit` upserts links (merges `sessionIds` on re-link, preserves `linkedAt`); `GET /agentmemory/commits/:sha` and `GET /agentmemory/commits` round-trip. MCP: `memory_commit_lookup` and `memory_commits` tools. Post-commit hook auto-captures the link on every commit in the working directory. Closes the loop on "what session wrote this code" / "what commits did this session ship" without leaving agentmemory. - -- **Azure OpenAI v1 URL pattern auto-detection** ([PR #462](https://github.com/rohitg00/agentmemory/pull/462), closes [#371](https://github.com/rohitg00/agentmemory/issues/371)). Both the LLM and embedding providers now route through `_openai-shared.ts` and auto-detect Azure URL style: - - `OPENAI_BASE_URL=https://r.openai.azure.com/openai/deployments/` → legacy URL pattern, `api-version` query param via `OPENAI_API_VERSION` (default `2024-08-01-preview`). - - `OPENAI_BASE_URL=https://r.openai.azure.com` (bare host, or `/openai`, or `/openai/v1`) → v1 GA pattern, `/openai/v1/`, deployment name carried in the request body as `model`. - - Net effect: Azure embeddings work for the first time (LLM-side Azure shipped in v0.9.17; embedding was still hardcoded to `/v1/embeddings` + Bearer). Closes [#199](https://github.com/rohitg00/agentmemory/issues/199) (consolidation) as superseded. - -### Changed - -- **Graph retrieval: BFS → Dijkstra over weighted edges** ([PR #463](https://github.com/rohitg00/agentmemory/pull/463), closes [#328](https://github.com/rohitg00/agentmemory/issues/328) filed by [@Tanmay-008](https://github.com/Tanmay-008) with benchmark numbers showing the BFS edge-count semantics + O(n) `Array.shift()` profile). Memory graph edges carry weights 0.1–1.0; BFS visited by edge-count regardless, so a one-hop weak edge ranked the same as a two-hop strong chain. Dijkstra over `cost = 1/max(weight, 0.01)` selects weight-optimal paths instead. Adjacency map built once in O(V+E) and min-heap dequeue at O(log V) replace the prior O(V·E) + O(n) `Array.shift()` profile. `maxDepth` semantics preserved (still edge-count bound). The `startNode` path is excluded from returned paths so the dedicated `score=1.0` fallback loop in `searchByEntities` fires as designed (regression catch from the inline review). - -- **Codex Stop hook chains session-end** ([PR #495](https://github.com/rohitg00/agentmemory/pull/495) by [@Rex57](https://github.com/Rex57), fixes [#493](https://github.com/rohitg00/agentmemory/issues/493) also filed by [@Rex57](https://github.com/Rex57)). `hooks.codex.json` Stop now runs `stop.mjs` followed by `session-end.mjs`. Codex doesn't ship a separate `SessionEnd` lifecycle event, so the session would otherwise stay open after the user terminated the Codex session. Tests assert both commands appear in the Stop chain. - -- **Plugin MCP entry inherits shell env via passthrough** ([PR #460](https://github.com/rohitg00/agentmemory/pull/460), closes [#375](https://github.com/rohitg00/agentmemory/issues/375) filed by [@anthony-spruyt](https://github.com/anthony-spruyt)). `plugin/.mcp.json` and `AGENTMEMORY_MCP_BLOCK` (the template `agentmemory connect ` writes into `~/.claude.json`, `~/.cursor/mcp.json`, etc.) now declare `env: { AGENTMEMORY_URL: "${AGENTMEMORY_URL}", AGENTMEMORY_SECRET: "${AGENTMEMORY_SECRET}" }`. The MCP host substitutes shell values at server launch. When the vars are unset, the host passes empty strings; the standalone shim's `process.env["AGENTMEMORY_URL"] || "http://localhost:3111"` falls back to localhost. One wired entry now covers both local and remote (k8s / reverse-proxied) deployments without `/doctor` "duplicate server" warnings. - -### Fixed - -- **`ensureIiiConsole()` install path** (`src/cli.ts`). The upstream `install.iii.dev/console/main/install.sh` script's jq predicate filters releases with `startswith("v")` while `iii-hq/iii` tags as `iii/v0.12.0`. The script bails with `no stable iii release found` on every fresh install. Switched the install command to `curl ... | bash -s -- --next` until upstream patches the script — the `--next` codepath uses a regex on `-next.` without the buggy `startswith` constraint, so it succeeds against the same tag set. Inline comment documents the upstream bug + revert condition. - -### Infrastructure - -- 91 test files, 1007 tests pass on `chore(release): v0.9.19`. -- Bundles five PRs ([#460](https://github.com/rohitg00/agentmemory/pull/460), [#462](https://github.com/rohitg00/agentmemory/pull/462), [#463](https://github.com/rohitg00/agentmemory/pull/463), [#495](https://github.com/rohitg00/agentmemory/pull/495), [#498](https://github.com/rohitg00/agentmemory/pull/498)) plus the inline iii-console installer workaround into one patch release. - -[0.9.19]: https://github.com/rohitg00/agentmemory/compare/v0.9.18...v0.9.19 - -## [0.9.18] — 2026-05-17 - -Hardening + DX wave. Five fixes land together: lessons now flow into the auto-inject context payload (closes a half-finished loop from earlier releases — see #381 / #457), the viewer drops `data:` from its `img-src` CSP by self-hosting its favicon, the filesystem watcher redacts PEM private-key blocks and standalone JWTs before transport, the mcp-standalone livez probe gets a dependency-injection seam that kills a flaky test, and the OpenAI timeout precedence is documented + tightened (strict integer parse, `OPENAI_TIMEOUT_MS` keeps its v0.9.17 meaning as an alias of the global `AGENTMEMORY_LLM_TIMEOUT_MS`). 1007/1007 tests pass. - -### Added - -- **Lessons auto-injected into `mem::context` payload** ([PR #458](https://github.com/rohitg00/agentmemory/pull/458), closes [#457](https://github.com/rohitg00/agentmemory/issues/457), surfaced in discussion [#381](https://github.com/rohitg00/agentmemory/discussions/381)). Lessons were generated + stored but only retrievable via an explicit `memory_lesson_recall` MCP call — agents rarely thought to invoke it, so the loop was half-done. `mem::context` now reads `KV.lessons` alongside slots + profile, ranks by `(project-relevance × confidence)` (project-scoped lessons get a 1.5× boost), filters tombstoned + cross-project entries, caps at top-10, and emits a `## Lessons Learned` block competing fairly for the token budget. Block recency tracks the most-recent `lastReinforcedAt || updatedAt`, so hot lessons survive when budget tightens. - -- **Self-hosted viewer favicon** ([PR #452](https://github.com/rohitg00/agentmemory/pull/452), closes [#447](https://github.com/rohitg00/agentmemory/issues/447)). The viewer's inline-SVG `data:` favicon (added in #313) required `data:` in `img-src` — a broader allowance than the viewer actually needed. The favicon now lives at `/favicon.svg` served by the viewer with `Content-Type: image/svg+xml` and `Cache-Control: public, max-age=3600`; build script copies the asset into `dist/viewer/` alongside `index.html`. CSP reverts to bare `img-src 'self'`. - -### Changed - -- **`OPENAI_TIMEOUT_MS` is now an alias of `AGENTMEMORY_LLM_TIMEOUT_MS`** ([PR #453](https://github.com/rohitg00/agentmemory/pull/453), closes [#446](https://github.com/rohitg00/agentmemory/issues/446)). v0.9.17 shipped `OPENAI_TIMEOUT_MS` as the OpenAI-scoped knob, then [PR #379](https://github.com/rohitg00/agentmemory/pull/379) introduced the global `AGENTMEMORY_LLM_TIMEOUT_MS` shared across all raw-fetch providers. The OpenAI provider now resolves them in precedence order: `OPENAI_TIMEOUT_MS` → `AGENTMEMORY_LLM_TIMEOUT_MS` → `60_000ms` default. v0.9.17 configs keep working unchanged; new configs should prefer the global. The provider's request also moved onto the shared `fetchWithTimeout` helper that owns AbortController + `clearTimeout` cleanup for every raw-fetch path (minimax, openrouter, gemini, embedding providers). - -- **Strict integer parse for timeout env vars** (PR #453, CodeRabbit catch). `parsePositiveInt` rejects values like `"30ms"`, `"1_000"`, `"60s"`, `"30abc"`, `"-30"`, `"0"` via `/^\d+$/` (after trim) instead of letting `parseInt`'s lenience silently swallow trailing units / underscores / signs as a number. Malformed values fall back to the 60s default with no surprise truncation. - -### Fixed - -- **Filesystem watcher redacts PEM private-key blocks + standalone JWTs in previews** ([PR #450](https://github.com/rohitg00/agentmemory/pull/450), closes [#448](https://github.com/rohitg00/agentmemory/issues/448)). Continues the redaction surface opened in [PR #332](https://github.com/rohitg00/agentmemory/pull/332). PEM blocks (`-----BEGIN ... PRIVATE KEY-----` through `-----END ... PRIVATE KEY-----`, including encrypted, RSA, EC, DSA, OpenSSH, PGP variants) get a state-machine pass that replaces the whole block with a single `[REDACTED ... PRIVATE KEY]` marker; standalone JWT-shaped tokens (three base64url segments separated by dots, length ≥ ~32 chars) are masked to their last 4 chars. Both run before any transport-layer write. - -- **mcp-standalone livez probe DI seam kills the test flake** ([PR #451](https://github.com/rohitg00/agentmemory/pull/451), closes [#449](https://github.com/rohitg00/agentmemory/issues/449)). The standalone shim's livez probe used a fixed `fetch` against `localhost:3111` which made the test suite depend on no other agentmemory instance running on the host. New `setLivezProbe()` injection seam lets tests provide a deterministic probe; default behaviour for production users is unchanged. - -### Infrastructure - -- 91 test files (was 90), 1007 tests (was 992). New `test/context-lessons.test.ts` (8 cases) covers lessons-auto-inject inclusion, empty-state no-op, project ranking, cross-project isolation, soft-delete skip, top-10 cap, confidence rendering, optional `context` string append. - -- Bundled the four follow-up issues filed during the v0.9.17 audit wave ([#446](https://github.com/rohitg00/agentmemory/issues/446), [#447](https://github.com/rohitg00/agentmemory/issues/447), [#448](https://github.com/rohitg00/agentmemory/issues/448), [#449](https://github.com/rohitg00/agentmemory/issues/449)) plus the cross-project lesson-injection gap surfaced in discussion [#381](https://github.com/rohitg00/agentmemory/discussions/381) into a single patch release — no behaviour changes for existing users beyond the hardening above. - -[0.9.18]: https://github.com/rohitg00/agentmemory/compare/v0.9.17...v0.9.18 - -## [0.9.17] — 2026-05-16 - -OpenAI-compatible LLM provider lands the universal-adapter shape (one provider config covers OpenAI, Azure OpenAI auto-detected by hostname, DeepSeek, SiliconFlow, vLLM, LM Studio, Ollama via `/v1`, plus any future endpoint that mirrors `POST /v1/chat/completions`). Worker registration now pins a stable `project_name` for engine telemetry so attribution reads cleanly across hosts. Comparison section on agent-memory.dev no longer wraps awkwardly after the v0.9.16 refresh. - -### Added - -- **OpenAI-compatible LLM provider** ([PR #307](https://github.com/rohitg00/agentmemory/pull/307), by @fatinghenji). Six providers behind one config: `OPENAI_API_KEY` + `OPENAI_BASE_URL` + `OPENAI_MODEL`. Closes [#185](https://github.com/rohitg00/agentmemory/issues/185), [#232](https://github.com/rohitg00/agentmemory/issues/232) (Ollama works via `OPENAI_BASE_URL=http://localhost:11434/v1`), [#312](https://github.com/rohitg00/agentmemory/issues/312), and [#240](https://github.com/rohitg00/agentmemory/pull/240) (superseded). - -- **Azure OpenAI auto-detection**. `OpenAIProvider` detects `.openai.azure.com` hostnames in the configured base URL at construction time and switches the request shape automatically: drops the `/v1` path prefix (deployment is in the URL), uses `api-key: ` header instead of `Authorization: Bearer`, appends `api-version=` query param. Default api-version is `2024-08-01-preview`; override via `OPENAI_API_VERSION`. Closes the gap with [PR #219](https://github.com/rohitg00/agentmemory/pull/219). - -- **`OPENAI_TIMEOUT_MS` env var** (PR #307). Outbound fetch timeout on the new provider, default 60s, AbortController-bounded with a clear timeout error message that points at the env var. The other raw-fetch providers (anthropic / gemini / openrouter / minimax) share the same gap tracked in [#373](https://github.com/rohitg00/agentmemory/issues/373). - -- **`OPENAI_REASONING_EFFORT` passthrough** (PR #307). Forwarded as `reasoning_effort` on the request body for OpenAI reasoning models (`o1`, `o3`, `gpt-*-reasoning`) and providers that mirror that schema (Ollama Cloud thinking models). Standard chat models reject this field with 400 — README documents the caveat. Set to `"none"` for thinking models that return reasoning but no content. The provider also falls back to `message.reasoning` when `message.content` is empty, covering the Ollama Cloud thinking-model shape. - -### Changed - -- **Worker `telemetry.project_name` pinned to `"agentmemory"`** ([PR #426](https://github.com/rohitg00/agentmemory/pull/426)). `iii-sdk`'s `InitOptions.telemetry.project_name` auto-detects when omitted — falls through cwd → package.json basename → hostname, which produces inconsistent identifiers per host (`agentmemory`, `node`, `npm`, occasionally the user's home dir basename when launched via npx). Pinning the value gives every install the same stable project identifier in the engine's metrics + traces output. Also pins `language: "node"` and `framework: "iii-sdk"` for the same reason. - -- **`OPENAI_API_KEY_FOR_LLM=false` opt-out gate** (PR #307, fix pushed via maintainer edit). `detectLlmProviderKind()` now mirrors `detectProvider()`'s existing gate — users who set `OPENAI_API_KEY` only for embeddings (via the `OPENAI_BASE_URL` + `OPENAI_EMBEDDING_MODEL` flow from #186) won't see the LLM auto-activate. The README's `.env` template now leads with an explicit shared-use callout above the LLM section pointing at the opt-out. - -- **Compare section on agent-memory.dev** ([PR #427](https://github.com/rohitg00/agentmemory/pull/427)). `AGENTMEMORY VS. THE FIELD.` title → `VS. THE FIELD.` (the eyebrow already reads VS., so the longer version was redundant + wrapped ugly). Added `text-wrap: balance` to `.section-title` globally so any wrapping title breaks at a balanced point. `NATIVE PLUGINS` cell value `6 (Claude/Codex/OpenClaw/Hermes/pi/OpenHuman)` → `6` (agent names already visible in the Agents grid two sections above). Row grid rebalanced (label 1.4fr / agentmemory 1.3fr / competitors 1fr each), added `word-break: break-word` + 24px row padding so cells like `YES (APACHE-2.0)` and `2 (Qdrant, Neo4j)` have breathing room. - -### Infrastructure - -- Provider factory at `src/providers/index.ts` now dispatches `"openai"` through `createBaseProvider`; type union extended in `src/types.ts:132`. `VALID_PROVIDERS` set in `src/config.ts` includes the new entry so the fallback-chain config accepts it. - -[0.9.17]: https://github.com/rohitg00/agentmemory/compare/v0.9.16...v0.9.17 - -## [0.9.16] — 2026-05-15 - -Two parallel waves landed back-to-back. (1) DevEx polish on top of v0.9.15's foundation: 5-port ready panel that shows REST/Viewer/Streams/Engine/iii-console in one boxed note, iii-console install probe + auto-install on first run, interactive global-install prompt that replaces the passive npx hint (so `agentmemory stop` actually works in new shells), onboarding wizard now wires every selected agent inline via the same `agentmemory connect` adapter the CLI exposes, plus a memory-share callout so users understand a single server feeds every wired agent. (2) Marketing site refresh against the v0.9.15 surface: new `AS FEATURED IN` bar (AlphaSignal · Agentic AI Foundation · Trendshift), six first-party agents in the featured grid (added pi + OpenHuman), MCP messaging reworded as opt-in surface so REST reads as the primary protocol. - -### Added - -- **5-port ready panel** ([PR #410](https://github.com/rohitg00/agentmemory/pull/410)). Replaces the single-line `Memory ready on :3111 · viewer on :3113 · try: agentmemory demo` hint with a clack `p.note` panel listing all live endpoints — REST API, Viewer, Streams, Engine, iii console — each derived from the configured env vars (`AGENTMEMORY_URL`, `III_REST_PORT`, `III_VIEWER_PORT`, `III_STREAM_PORT`, `III_ENGINE_URL`) so a remote-bind setup reads correctly, not as hardcoded localhost. - -- **iii console install probe + auto-install** (PR #410). New `ensureIiiConsole()` checks for the iii-console binary on PATH or in `~/.local/bin`; if missing, prompts interactively to run `curl -fsSL https://install.iii.dev/console/main/install.sh | sh`. Console is first-class — not optional. On install acceptance the ready panel includes the binary's resolved path and a runnable launch hint (` -p `); on decline the panel surfaces the install one-liner. - -- **Interactive global-install prompt** (PR #410). Replaces the passive `p.log.info` npx hint with a `p.confirm` on first npx run that runs `npm install -g @agentmemory/agentmemory@` inline. Suppressible via `preferences.skipGlobalInstall` so we never ask twice. Closes the v0.9.15-era footgun where users typed `agentmemory stop` in a new shell and hit `command not found`. - -- **Onboarding wires selected agents inline** ([PR #408](https://github.com/rohitg00/agentmemory/pull/408)). After the multi-select agents step in `runOnboarding`, asks `Run \`agentmemory connect \` for each selected agent now? [Y/n]` and dispatches each through the existing `runAdapter` path used by the explicit `agentmemory connect` command. Successes + failures get bucketed in a summary block (`Wired: claude-code, codex, cursor · Skipped/failed: hermes (manual install required)`). Cancellation prints the explicit per-agent commands for the user to run later. - -- **Memory-share callout** (PR #408). Between the agents multi-select and the provider single-select the wizard now prints a verbatim note: `All selected agents share the same memory at :3111. A memory saved by Claude Code is visible to Codex + Cursor instantly.` Closes the most common confusion ("does the same memory work across agents?") we were getting on the v0.9.15 install path. - -- **AS FEATURED IN bar on agent-memory.dev**. New `FeaturedIn` component between Hero and Stats. Three cards with real brand marks: AlphaSignal (github.com/Alpha-Signal avatar, links to the AlphaSignal Substack deep-dive), Agentic AI Foundation (self-hosted wordmark, inverted to white-on-dark for the brutalist palette), Trendshift (their official badge endpoint at `trendshift.io/api/badge/repositories/25123` which bakes the live rank + star count into the image). 3-column grid on desktop, 1-column stack on mobile, keyboard-accessible (`:focus-visible` outline). - -### Changed - -- **MCP messaging reworded as opt-in surface** ([PR #409](https://github.com/rohitg00/agentmemory/pull/409)). The endpoints summary line in `src/index.ts` was foregrounding MCP equally with REST — `Endpoints: 107 REST + 51 MCP tools + 6 MCP resources + 3 MCP prompts`. Users kept reading MCP as compulsory. Now: `REST API: 121 endpoints at http://localhost:/agentmemory/*` (primary), plus a second line `MCP surface (opt-in via \`npx @agentmemory/mcp\`): 51 tools · 6 resources · 3 prompts`. Each `connect` adapter additionally prints a protocol-note line above its install summary so the user sees which surface they're wiring (native hooks vs MCP vs both). - -- **CLI `--help` mcp subcommand description** (PR #409). Was `Start standalone MCP server (no engine required)`. Now: `Start standalone MCP shim — opt-in surface for MCP-only clients (Cursor, Gemini CLI, etc). REST always available at :3111.` - -- **Engine version-mismatch warning copy** (PR #410). The warning that fires when iii on PATH doesn't match `IIPINNED_VERSION` previously said `agentmemory v0.9.14+ pins v0.11.2`. Now reads `agentmemory v${VERSION} pins v${IIPINNED_VERSION}` so the string never lies post-release. - -- **CommandCenter — iii console messaging** (PR #415). `iii CONSOLE · OPTIONAL` → `iii CONSOLE · FIRST-CLASS`. Section lede now mentions both UIs (viewer + console) are first-class and installed inline by the CLI on first run. Bullet counts updated: dropped stale `33 functions` and `49 triggers` → `121 HTTP endpoints` (matches `generated-meta.json`). - -- **Compare table refreshed** (PR #415). MCP TOOLS row our column 44 → 51. New REST ENDPOINTS row (121) and NATIVE PLUGINS row (6: Claude/Codex/OpenClaw/Hermes/pi/OpenHuman). - -- **Agents grid refresh** (PR #415). Featured row expanded from 4 to 6 — added pi and OpenHuman. Codex CLI sub line updated to `NATIVE PLUGIN` (has 6 hooks now, not MCP-only). Section title `FOUR FIRST-PARTY` → `SIX FIRST-PARTY`. Lede mentions `agentmemory connect `. - -- **Hero CTA** (PR #415). `START IN 60 SECONDS` → `START IN 30 SECONDS`. Cold install + engine spawn measured 8-12s on v0.9.15 via the native binary path; 60s was the v0.9.0-era Docker-first claim. - -### Fixed - -- **CLI no longer kills its own parent process** (PR #410 follow-up via #411). `lsof -i :PORT -t` returns every PID with an active TCP connection — including the CLI's own keep-alive `fetch()` from `isEngineRunning()`. Now restricts to `-sTCP:LISTEN` and filters `process.pid` from the candidate set. The bug used to surface as exit code 137 with state files left stranded. - -- **Splash banner ASCII** ([PR #411](https://github.com/rohitg00/agentmemory/pull/411)). The hand-drawn block-art "agentmemory" wordmark in `src/cli/splash.ts` had broken middle glyphs (`_ _` instead of `_ __` around the 'n', merged 'tm/me' segments). Replaced with verified `figlet agentmemory` standard-font output. All 6 rows render at exactly 70 cols, regenerable. - -- **README install hoisted to top** (PR #411). Install section was buried at line 306 below benchmarks and comparison tables. Moved a 4-line install block right under the nav anchors at the top with the bare `agentmemory` command leading. Quick-start kept below for the in-depth walkthrough. Adds an npx-cache caveat with three workarounds (`npm install -g`, `npx -y @latest`, manual `rm -rf ~/.npm/_npx` annotated as POSIX-only). - -### Infrastructure - -- New modules: `src/cli/connect/{index,types,claude-code,codex,cursor,gemini-cli,openclaw,hermes,pi,openhuman,json-mcp-adapter}.ts`, `src/cli/doctor-diagnostics.ts`, `src/cli/remove-plan.ts`, `src/cli/splash.ts`, `src/cli/preferences.ts`, `src/cli/onboarding.ts`, `src/logger.ts` (bootLog shim) — actually shipped in v0.9.15; relisted here only to call out that v0.9.16 keeps every module backward-compatible (no signature breaks on `runOnboarding`, no field removals from `Prefs`). -- New tests: existing CLI test suites continue at 944+ passing. mcp-standalone pre-existing failures unrelated. -- Website: new `FeaturedIn` component (~150 LOC across `.tsx` + `.module.css`); `next.config.ts` adds `aaif.io`, `trendshift.io`, `raw.githubusercontent.com` to `remotePatterns`. - -[0.9.16]: https://github.com/rohitg00/agentmemory/compare/v0.9.15...v0.9.16 - -## [0.9.15] — 2026-05-15 - -DevEx overhaul. Four PRs landed simultaneously rebuilding the first-run experience to SkillKit-grade polish: splash banner + interactive agent grid + provider picker + smart-defaults preferences, `agentmemory connect ` to automate native-plugin install for 8 agents, interactive `doctor` v2 with Fix/Skip/More/Quit prompts and a `--all` auto-fix flag, `agentmemory remove` for clean uninstall with destruction-plan confirmation, plus five silent-killer fixes around viewer port collisions, engine version-mismatch detection, `stop --force` override, adopt-on-attach state recording, and an npx-to-global-install hint. - -### Added - -- **Splash banner + onboarding wizard** ([PR #403](https://github.com/rohitg00/agentmemory/pull/403)). Terminal-width-aware ASCII (full at ≥120 cols, compact at 80–119, single-line below 80), `NO_COLOR`-aware. First-run flow: multi-select 8 native-plugin agents + 8 MCP-server agents (`⟁ Claude Code`, `◫ Cursor`, `◎ Codex`, `✦ Gemini CLI`, `⬡ OpenCode`, etc), single-select LLM provider (Anthropic / OpenAI / Gemini / OpenRouter / MiniMax / BM25-only), seeds `~/.agentmemory/.env` with the chosen provider's `*_API_KEY=` line commented. Subsequent runs skip the splash. - -- **`~/.agentmemory/preferences.json`** (PR #403). Schema-versioned JSON with `{ lastAgent, lastAgents, lastProvider, skipSplash, skipNpxHint, firstRunAt }`. Atomic write via `.tmp` + rename, graceful defaults on read failure. Exports `isFirstRun()`, `readPrefs()`, `writePrefs()`, `resetPrefs()`. - -- **`agentmemory connect ` command** ([PR #402](https://github.com/rohitg00/agentmemory/pull/402)). Automates native-plugin install for `claude-code`, `codex`, `cursor`, `gemini-cli`, `openclaw` (end-to-end working) plus stubs for `hermes`, `pi`, `openhuman` (which print manual-install hints until deeper YAML/TS adapters land). Each adapter: detect → backup the agent's existing config to `~/.agentmemory/backups/-.` → merge → re-read & verify → idempotent on re-run. Supports `--dry-run`, `--force`, `--all`. Interactive picker when run without an agent argument. - -- **`agentmemory remove` command** ([PR #406](https://github.com/rohitg00/agentmemory/pull/406)). Tears down everything agentmemory installed: pidfile + state file + preferences + backups + `~/.local/bin/iii` (only when it matches the version we installed) + any agent connections. Asks separately for `.env` (holds API keys) and for the memory data dir. Requires two confirmations by default. Supports `--force`, `--keep-data`. - -- **`agentmemory doctor` v2** (PR #406). Replaces the passive reporter with an interactive fixer. Each diagnostic prints problem + cause + fix-preview, then offers Fix / Skip / More / Quit. Checks: missing `.env`, missing LLM key, engine version mismatch, viewer port unreachable, stale pidfile, empty API keys, iii on PATH outside `~/.local/bin/iii`. New flags: `--all` (apply every fix, for CI), `--dry-run` (show plan only). - -- **`agentmemory stop --force`** ([PR #405](https://github.com/rohitg00/agentmemory/pull/405)). Bypasses the Docker-compose heuristic refusal so engines started before v0.9.14 (which had no state file) can be torn down without a hand-rolled `lsof | xargs kill -9`. - -- **`--reset` flag**. Wipes preferences and re-runs the onboarding wizard (PR #403). - -- **`--verbose` / `AGENTMEMORY_VERBOSE=1`**. Restores the pre-v0.9.15 25-line `[agentmemory] X` engine-boot log stream for debugging (PR #403). Default is muted to a single ready-line. - -### Changed - -- **First-run output trimmed from 30+ lines to ~10**. The `[agentmemory] Worker v0.9.x` boot log stream is now buffered behind a `bootLog` shim (`src/logger.ts`) and only surfaces when `--verbose` is passed. The default surface is the splash banner, the onboarding prompts (first run only), a single engine spinner, and a one-line ready hint: `Memory ready on :3111 · viewer on http://localhost:3113 · try: agentmemory demo`. - -- **`isEngineRunning()` short-circuit now adopts the engine** ([PR #405](https://github.com/rohitg00/agentmemory/pull/405)). When the CLI finds an existing engine on `:3111`, it now writes `~/.agentmemory/iii.pid` (resolved via `lsof -i :PORT -sTCP:LISTEN -t`) and `~/.agentmemory/engine-state.json` (`{kind:"native", configPath, attached:true}`) if neither exists. Closes the migration gap where pre-v0.9.14 engines could not be stopped via `agentmemory stop` because no state file was written by the older code. - -- **Viewer port auto-bump** (PR #405). When `:3113` is taken, the viewer now retries 3114 → 3115 → … up to 3122 before failing loud. Pre-fix behaviour was a silent `Viewer port 3113 already in use, skipping viewer.` which left users staring at the previous run's stale viewer. - -- **Engine version-mismatch warning** (PR #405). When the iii binary on PATH (or attached engine) is not the pinned `IIPINNED_VERSION` (currently v0.11.2), the CLI now prints a clearly-labelled `p.log.warn` with the override path (`AGENTMEMORY_III_VERSION=...` env var or downgrade curl one-liner). Pre-fix was silent acceptance — v0.11.6 on PATH led to undebuggable EPIPE loops. - -- **npx PATH hint** (PR #405). After the engine is ready, runs invoked via `npx` get one extra line: `Tip: install globally for the bare \`agentmemory\` command: npm install -g @agentmemory/agentmemory`. Suppressible via `preferences.skipNpxHint`. - -### Fixed - -- `lsof -i :PORT -t` was returning client PIDs alongside the LISTEN-socket owner (already fixed in v0.9.14 via the `-sTCP:LISTEN` flag + `process.pid` filter; called out here so anyone bisecting the wave knows the LISTEN flag is what stopped the CLI from killing itself). - -### Infrastructure - -- New modules: `src/cli/splash.ts`, `src/cli/preferences.ts`, `src/cli/onboarding.ts`, `src/cli/connect/{index,types,claude-code,codex,cursor,gemini-cli,openclaw,hermes,pi,openhuman,json-mcp-adapter}.ts`, `src/cli/doctor-diagnostics.ts`, `src/cli/remove-plan.ts`, `src/logger.ts` (bootLog shim). -- New tests: `test/preferences.test.ts` (7), `test/cli-connect.test.ts` (13), `test/cli-doctor-fixes.test.ts` (18), `test/cli-remove.test.ts` (13). 944 tests passing, 10 pre-existing `mcp-standalone.test.ts` failures unrelated to this wave. - -[0.9.15]: https://github.com/rohitg00/agentmemory/compare/v0.9.14...v0.9.15 - -## [0.9.14] — 2026-05-15 - -CLI bootstrap rework so `npx @agentmemory/agentmemory` stops failing on Rancher Desktop and other Docker-shim daemons. The native iii-engine binary is now the first-class start path; Docker becomes opt-in. Also ships `agentmemory stop` so engines started in the background can be torn down without `lsof | xargs kill`. README agents grid reorders OpenHuman next to the other native-integration agents. - -### Added - -- **`agentmemory stop` command** ([PR #396](https://github.com/rohitg00/agentmemory/pull/396)). Reads `~/.agentmemory/iii.pid` first, falls back to `lsof -i :PORT -sTCP:LISTEN -t`, sends `SIGTERM`, waits 3s, escalates to `SIGKILL`. iii-engine doesn't currently handle `SIGTERM` cleanly so the SIGKILL escalation is what actually frees the port. State file (`~/.agentmemory/engine-state.json`) records whether the engine was started natively or via `docker compose up -d`, so `stop` runs `docker compose -f down` for Docker engines instead of signaling the host's Docker socket proxy by accident. - -- **`AGENTMEMORY_USE_DOCKER=1` env var**. Opt-in path for users who want the bundled `docker-compose.yml` to keep handling iii-engine lifecycle. Without it, the CLI prefers the native binary in `~/.local/bin/iii`. Documented in `npx @agentmemory/agentmemory --help`. - -### Changed - -- **`startEngine()` fallback order rewritten** ([PR #396](https://github.com/rohitg00/agentmemory/pull/396)). New tier order: (1) `iii` on PATH, (2) `~/.local/bin/iii` or other fallback paths, (3) interactive clack `p.select` with three choices — auto-install the pinned v0.11.2 binary, use Docker compose, or print manual install instructions and exit, (4) Docker compose only via explicit opt-in or the post-install failure fallback, (5) fail-loud with install instructions. CI / non-TTY environments auto-pick install. The Docker fallback was tier 2 and silently fired on every cold start; on Rancher Desktop `docker compose up -d` returns 0 even when the daemon's pull silently fails, which is what produced the "engine started but REST never responded" misdiagnoses we've been seeing this week. - -- **Installer logic extracted from `runUpgrade` into `runIiiInstaller()`**. Both first-run and `agentmemory upgrade` now share the same pinned-v0.11.2 curl-and-tar path. The pre-v0.9.14 installer only ran on `npx @agentmemory/agentmemory upgrade`, so first-time users never auto-installed. - -- **`installInstructions()` copy reordered**. The curl one-liner now leads (path A), Docker is path B with the `AGENTMEMORY_USE_DOCKER=1` env-var hint. The historical "Why pinned" rationale moved out of the failure note (it's still in the source comment for anyone debugging the pin choice). - -- **README agents grid reordered** ([PR #397](https://github.com/rohitg00/agentmemory/pull/397)). Row 1 is now Claude Code → Codex CLI → OpenClaw → Hermes → pi → OpenHuman → Cursor → Gemini CLI. OpenHuman moved from slot 3 to slot 6 so the native-Memory-trait callout reads cleanly alongside the other native-integration agents. - -### Fixed - -- **CLI no longer kills its own parent process**. `lsof -i :PORT -t` returns every PID with an active TCP connection to the port, including the CLI's own keep-alive `fetch()` from `isEngineRunning()`. Without a LISTEN filter, `agentmemory stop` would SIGKILL itself — exit code 137, state files never cleaned up. Now filters to `-sTCP:LISTEN` and drops `process.pid` from the candidate set. - -- **`agentmemory stop` no longer clears the pidfile when a stale process holds the port**. The HTTP probe can fail while the engine is hung, paused, or in a half-closed state. The pre-fix behaviour cleared the pidfile and printed "Nothing to stop" — the next run would silently start a second engine on a port that the first engine still owns. Now preserves the pidfile and surfaces the live PIDs with `ps -p` + `lsof` hints so the operator can investigate before manual cleanup. - -- **Docker-started engines stop safely**. The pre-fix code called `findEnginePidsByPort` and signaled whatever held :3111. When the engine was started via `docker compose up -d`, that's the host's Docker socket proxy (`com.docker.backend`, `vmnetd`), not the engine — killing it took down Docker Desktop networking for every other container. The new state file lets `runStop` detect Docker-managed engines and run `docker compose down` instead. - -### Infrastructure - -- Engine process now writes both `~/.agentmemory/iii.pid` and `~/.agentmemory/engine-state.json` at spawn. State file is cleared on clean shutdown or abnormal exit; pidfile is preserved when stale PIDs hold the port so the operator can investigate. - -[0.9.14]: https://github.com/rohitg00/agentmemory/compare/v0.9.13...v0.9.14 - -## [0.9.13] — 2026-05-15 - -Six PRs landed since v0.9.12 — `.env.example` discovery shipped (#372), CJK BM25 tokenizer landed (#344 / PR #362), `benchmark/load-100k.ts` load harness landed (#346 / PR #363), one-click deploy templates for fly.io / Railway / Render / Coolify added (#343 / PR #361), Gemini provider defaults moved to current GA models (#246 + #368 / PR #370), and the in-tree Python ecosystem story switched from a duplicate REST client to a one-page `iii-sdk` example (#342 / PR #364). Plus 14 Dependabot security advisories closed via Next.js + PostCSS bumps. - -### Added - -- **`.env.example` at repo root + bundled in the npm tarball** ([#372](https://github.com/rohitg00/agentmemory/issues/372), closes [#47](https://github.com/rohitg00/agentmemory/issues/47), [#293](https://github.com/rohitg00/agentmemory/issues/293), partial [#233](https://github.com/rohitg00/agentmemory/issues/233)). Every env var actually read by `src/` is now documented in one place, grouped by surface (LLM provider, embedding provider, auth, search tuning, behaviour flags, CLI runtime, ports, iii engine pin, Claude Code bridge, Obsidian export). Every line is commented out by default so the file ships as a config template, not a config. The npm package now lists `.env.example` in its `files` field so `npm i -g @agentmemory/agentmemory` carries it. - -- **`agentmemory init` command**. Copies the bundled `.env.example` to `~/.agentmemory/.env` if that file doesn't already exist; refuses to overwrite an existing config and prints a diff command pointing at the latest template. Wired into the CLI help block alongside `status` / `doctor` / `demo` / `upgrade` / `mcp` / `import-jsonl`. - -- **CI sync-checker for `.env.example`** (`scripts/check-env-example.mjs`). Walks every `.ts` / `.mts` / `.mjs` / `.js` file under `src/`, extracts `process.env["KEY"]` / `env["KEY"]` / `getMergedEnv()["KEY"]` / `getEnvVar("KEY")` references, and fails CI when `src/` reads an env var the template doesn't document (or vice versa). Plugged into `.github/workflows/ci.yml` after `npm test`. Initial bootstrap: 60 keys in sync. - -- **CJK tokenizer for BM25 search** ([#344](https://github.com/rohitg00/agentmemory/issues/344), PR [#362](https://github.com/rohitg00/agentmemory/pull/362)). New `src/state/cjk-segmenter.ts` detects CJK input by Unicode block and routes to `@node-rs/jieba` (Chinese, native, no model download), `tiny-segmenter` (Japanese, pure JS, ~25 KB), or rule-based syllable-block split (Korean). Both segmenters declared in `optionalDependencies` so the base install stays lean; soft-fail with a one-time stderr hint when the dep is missing. Order-preserving single-pass tokenization across mixed CJK + non-CJK runs (regression test for `"abc 메모리 def 项目 ghi"` returns `["abc","메모리","def","项目","ghi"]`). - -- **`benchmark/load-100k.ts` load harness** ([#346](https://github.com/rohitg00/agentmemory/issues/346), PR [#363](https://github.com/rohitg00/agentmemory/pull/363)). Hand-rolled, dependency-free harness that seeds N synthetic memories against a local daemon at `http://localhost:3111` and records p50 / p90 / p99 latency + throughput for `POST /agentmemory/remember`, `POST /agentmemory/smart-search`, and `GET /agentmemory/memories?latest=true` across the matrix N ∈ {1k, 10k, 100k} × concurrency C ∈ {1, 10, 100}. Content drawn from a seedable `mulberry32` PRNG so re-running against the same build produces the same seed corpus. Results land in `benchmark/results/load-100k-.json`. Wired as `npm run bench:load`. - -- **One-click deploy templates** for fly.io, Railway, Render, and Coolify ([#343](https://github.com/rohitg00/agentmemory/issues/343), PR [#361](https://github.com/rohitg00/agentmemory/pull/361)). Each template under `deploy//` ships a multi-stage Dockerfile that `COPY --from=iiidev/iii:0.11.2`s the engine binary into a `node:22-slim` runtime, npm-installs `@agentmemory/agentmemory` under `/opt/agentmemory` with `iii-sdk` pinned via `package.json` overrides (avoids the caret-resolves-to-0.11.6 drift), and runs an entrypoint that rewrites the bundled `iii-config.yaml` to bind `0.0.0.0` + use absolute `/data` paths, chowns the platform-mounted volume to `node:node` via `gosu`, generates a first-boot HMAC secret, and exec's the agentmemory CLI as the unprivileged `node` user under `tini` (with `TINI_SUBREAPER=1`). Verified end-to-end on fly.io (machine in `iad`, 1 GB volume, healthcheck passing). - -- **`examples/python/`** quickstart + observation/recall flow showing `iii-sdk` (Python) calling `mem::remember` / `mem::smart-search` / `mem::context` directly over `ws://localhost:49134` ([#342](https://github.com/rohitg00/agentmemory/issues/342), PR [#364](https://github.com/rohitg00/agentmemory/pull/364)). Replaces a duplicate-transport Python REST client (initial PR #360, closed) with a single-SDK story — the same `iii-sdk` install (`pip install iii-sdk` / `cargo add iii-sdk` / `npm install iii-sdk`) talks to every agentmemory function from any language. - -### Changed - -- **Gemini provider defaults bumped to current GA models** (PR [#370](https://github.com/rohitg00/agentmemory/pull/370), closes [#368](https://github.com/rohitg00/agentmemory/pull/368), [#246](https://github.com/rohitg00/agentmemory/pull/246)). LLM default `gemini-2.0-flash` → `gemini-2.5-flash` (the moving `gemini-flash-latest` alias was rejected — release behaviour should be deterministic). Embedding default `text-embedding-004` → `gemini-embedding-001` (the previous default is deprecated and shuts down 2026-01-14 per `ai.google.dev/gemini-api/docs/deprecations`). Three implementation details ride along: (1) URL path `:batchEmbedContent` → `:batchEmbedContents`, (2) every request now sends `outputDimensionality: 768` so the returned vectors match `GeminiEmbeddingProvider.dimensions = 768` and the index-restore dim guard from #248 — no reindex needed, (3) returned vectors are L2-normalized before the result-array push because `gemini-embedding-001` does **not** normalize by default unlike `text-embedding-004` and without this the downstream cosine-similarity math silently collapses recall. `l2Normalize` warns once on a zero-norm embedding so operators can correlate index quality dips with upstream regressions. - -### Security - -- **14 open Dependabot advisories closed via Next.js + PostCSS bumps** (PR [#348](https://github.com/rohitg00/agentmemory/pull/348)). Closed: 13 Next.js advisories (middleware/proxy bypass + SSRF on WebSocket upgrades + DoS via connection exhaustion + CSP-nonce XSS + image-opt DoS + RSC cache poisoning + beforeInteractive XSS + segment-prefetch routes) by bumping the website's Next.js to `^16.2.6`. Plus the PostCSS XSS-via-unescaped-`` advisory closed by pinning to `^8.5.10` via `overrides` in `website/package.json`. Verified `npm audit --omit=dev` returns 0 and `npm run build` clean on Next 16.2.6. Dependabot now runs weekly against six update streams (npm × 5 paths + github-actions) per the new `.github/dependabot.yml`. - -### Contributors - -External contributors landed this release: - -- [@fatinghenji](https://github.com/fatinghenji) — pre-cleanup work on the OpenAI-compatible LLM provider (PR #240 / PR #307); the universal-adapter shape will land in the next minor once branch maintenance catches up. -- [@AmmarSaleh50](https://github.com/AmmarSaleh50) — Gemini embedding migration with L2-norm + 768-dim plumbing (PR #246, folded into #370). -- [@yut304](https://github.com/yut304) — Gemini LLM default deprecation fix (PR #368, folded into #370). - -Thanks also to the issue reporters whose precise repros drove the search-quality + viewer + config-template work this cycle. - -## [0.9.12] — 2026-05-13 - -Four landed PRs since v0.9.11 — one type-correctness fix, one search-quality fix (BM25 unicode + vector-index live-write), one viewer hardening (CSP-clean fonts + load-error surface), and one integrations security hardening (bearer token over plaintext HTTP). - -### Fixed - -- **BM25 tokenizer now indexes non-ASCII (Greek, accented Latin, Hebrew, Arabic) and the vector index is actually populated at runtime** ([#327](https://github.com/rohitg00/agentmemory/pull/327), closes [#295](https://github.com/rohitg00/agentmemory/issues/295)). `src/state/search-index.ts` previously stripped every non-ASCII character via `\w` (JS `\w` is ASCII-only), so non-English search returned empty results across the board. Regex replaced with `/[^\p{L}\p{N}\s/.\\-_]/gu` — keeps Unicode letters/numbers, preserves underscore (matters for `memory_recall`-style identifiers), keeps existing path/separator handling. Separately, `VectorIndex.add()` had **zero callers** anywhere in `src/` — the vector index was loaded from disk at startup and never updated at runtime, so hybrid search returned stale results forever. New `vectorIndexAddGuarded()` helper in `src/functions/search.ts` wires `vectorIndex.add()` into `remember.ts`, `observe.ts` synthetic-compression branch, `compress.ts` post-LLM compression, and the cold-start `rebuildIndex()` walk — with a per-write dimension guard (symmetric to the persistence-load guard from #248), 16k-char input clipping (so an oversized memory can't 400 the embed call), and consistent stderr logging that no longer swallows embed failures. `migrateVectorIndex()` utility re-embeds every memory + per-session observation against a new provider when dimensions change; per-session try/catch isolation so one bad session can't abort the whole migration; structured `failedSessions[]` result with a `""` sentinel that distinguishes a catastrophic list-failure from per-session failures. Soft-fail throughout: a downed embedder never breaks an upstream save. Live-tested end-to-end against Greek fixtures (thanks @nik1t7n for the original repro on Cyrillic content; test fixtures swapped to Greek before merge). - - **CJK caveat preserved**: this fix recovers Greek, Cyrillic, accented Latin, Hebrew, Arabic, and other space-delimited scripts. CJK languages without spaces between characters still tokenize as a single sentence-long token — separate concern, needs a segmenter (PR #224 area). - -- **`@agentmemory/mcp` standalone shim's `RetentionScore` type no longer declares `source` twice** ([#326](https://github.com/rohitg00/agentmemory/pull/326), closes [#277](https://github.com/rohitg00/agentmemory/issues/277)). `src/types.ts:835` and `:842` both declared `source?: "episodic" | "semantic"` on `RetentionScore`. TypeScript silently accepts duplicate property declarations when the types are identical, so the build never errored — but the documented JSDoc on the first declaration (the #124 back-compat note explaining `undefined` should probe both scopes) was effectively shadowed by the duplicate. Removed the second declaration; grep confirmed no caller writes `source` twice on the same `RetentionScore` row, so the cleanup is a pure type-correctness fix with no runtime change. Thanks @cl0ckt0wer for the precise file:line trace. - -- **Viewer dashboard no longer sticks on "Loading dashboard…" when the page loads** ([#335](https://github.com/rohitg00/agentmemory/pull/335), closes [#323](https://github.com/rohitg00/agentmemory/issues/323)). Two narrow fixes from @hem57's Windows repro: (1) removed the `` to `fonts.googleapis.com` — the viewer's strict CSP (`default-src 'none'`, `style-src 'unsafe-inline'`, `font-src 'self'`) blocks external stylesheet origins by design, so every page load logged a CSP violation for the font CSS and another for each blocked font file; system-font fallbacks were already declared on every `--font-*` CSS variable so dropping the external `` is a clean swap. (2) Wrapped `loadDashboard()` body in a `try/catch` that renders the error inline ("Dashboard failed to load: ") instead of leaving the placeholder text up forever — future "stuck Loading" reports now come with concrete error messages instead of just a screenshot of the placeholder. - -- **Integrations (hermes / openclaw / pi) now warn when sending the bearer token over plaintext HTTP to a non-loopback host** ([#315](https://github.com/rohitg00/agentmemory/pull/315), closes [#275](https://github.com/rohitg00/agentmemory/issues/275)). Symmetric guard across all three plugin runtimes (Python / mjs / TypeScript): a request that would attach `Authorization: Bearer ` to a `http://:port` URL now logs a one-time stderr warning telling the operator the token is observable on the wire. Loopback hosts (`localhost`, `127.0.0.1`, `::1`) and `https://` URLs are exempt. New env knob `AGENTMEMORY_REQUIRE_HTTPS=1` escalates the warning to a hard refusal — the plugin throws before any request is sent, so a misconfigured deployment can fail loudly instead of leaking the bearer once. Edge cases verified by 13 tests: IPv6 `[::1]` loopback, RFC1918 LAN IPs (`192.168.x.x` / `10.x.x.x` warn — NOT loopback), lookalike hostnames (`localhost.evil.com` warns), no-secret short-circuit (guard never fires when no bearer would be sent), https-with-secret silent. Thanks [@mvanhorn](https://github.com/mvanhorn) for the cross-runtime implementation. - -### Changed - -- `@agentmemory/mcp` package version bumped from 0.9.11 → 0.9.12 to lockstep with the main package. - -## [0.9.11] — 2026-05-13 - -Three additions on top of v0.9.10: OpenAI Codex got a plugin platform and agentmemory now ships a Codex manifest + marketplace alongside the existing Claude Code one (so `codex plugin marketplace add rohitg00/agentmemory` installs MCP + 6 hooks + 4 skills in one step); the OpenClaw integration now actually claims the `plugins.slots.memory` slot via `registerMemoryCapability` (older builds advertised the slot via manifest `kind` but never declared the capability so the slot reported `unavailable`); and the marketing website's hero CTA row now ships a live "Star on GitHub" button. - -### Added - -- **Codex plugin support** ([#311](https://github.com/rohitg00/agentmemory/pull/311)). `plugin/.codex-plugin/plugin.json` Codex manifest pointing at the same shared `.mcp.json` + `skills/` directory the Claude Code manifest uses, plus a Codex-shaped `plugin/hooks/hooks.codex.json` that registers exactly the events Codex's hook engine supports (`SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `PreCompact`, `Stop`) and drops the Claude-Code-only ones (`SubagentStart`, `SubagentStop`, `SessionEnd`, `Notification`, `TaskCompleted`, `PostToolUseFailure`). Verified against `codex-rs/hooks/src/engine/discovery.rs` that Codex injects `CLAUDE_PLUGIN_ROOT` into hook subprocesses for OOTB compat with existing Claude Code plugin scripts — so the same `${CLAUDE_PLUGIN_ROOT}/scripts/...` references work on both hosts. `.codex-plugin/marketplace.json` at the repo root publishes the plugin via the Codex marketplace API (`git-subdir` source pointing at `./plugin`), so end users run `codex plugin marketplace add rohitg00/agentmemory && codex plugin install agentmemory` once and get MCP + lifecycle hooks + 4 skills (`/recall`, `/remember`, `/session-history`, `/forget`). - -- **`Star on GitHub` button in the website hero CTA row** ([#316](https://github.com/rohitg00/agentmemory/pull/316)). Adds a ghost-style `GitHubStarButton` component next to `START IN 60 SECONDS` and `SEE IT MOVE` with inline SVG star icon, `STAR` label, and a live stargazer count fetched once on mount from `api.github.com/repos/rohitg00/agentmemory`. Count is cached in `localStorage` for 30 minutes per repo. Graceful degradation: if the API is unreachable / rate-limited / blocked, the button still renders without the count. No new dependencies. - -### Fixed - -- **OpenClaw `plugins.slots.memory = "agentmemory"` now reports as available, not `unavailable`** ([#310](https://github.com/rohitg00/agentmemory/pull/310)). On OpenClaw `2026.4.9+`, the integration declared `"kind": "memory"` in its manifest and wired `before_agent_start` / `agent_end` hooks but **never called `api.registerMemoryCapability(...)`** — so OpenClaw's memory-slot machinery saw no claim on the slot even though the hooks and REST API worked end-to-end. The fix lands `api.registerMemoryCapability({ promptBuilder })` at register time when the host exposes it, with the `promptBuilder` accepting the documented `{ availableTools, citationsMode? }` params and emitting a three-line block identifying agentmemory as the active provider. Older OpenClaw builds without the capability API still load via the existing hook-only path (`typeof api.registerMemoryCapability === "function"` guard). The PR does **not** ship a `MemoryPluginRuntime` adapter because OpenClaw's current `MemoryRuntimeBackendConfig` type is exactly `{ backend: "builtin" } | { backend: "qmd"; ... }` — both in-process backends that don't fit an external REST shape. Verified against `openclaw@2026.5.7`'s `plugin-sdk/src/plugins/types.d.ts` and `memory-state.d.ts` declarations directly. (closes the bug premise originally surfaced in the closed PR #302, which had attempted the same goal but with an import path that wasn't in the package's `exports` map and a symbol that didn't exist in any released openclaw version.) - -### Changed - -- `@agentmemory/mcp` package version bumped from 0.9.10 → 0.9.11 to lockstep with the main package. -- README now distinguishes **Codex CLI (MCP only)** from **Codex CLI (full plugin)** in the per-host install table and lists the marketplace install command for the latter. - -## [0.9.10] — 2026-05-12 - -Three deployment-shape fixes reported live by [@flamerged](https://github.com/flamerged) ([#299](https://github.com/rohitg00/agentmemory/issues/299), [#301](https://github.com/rohitg00/agentmemory/issues/301)): the v0.9.7 docker-compose persistence fix was incomplete because the distroless engine runs as UID 65532 but `docker volume create` initializes the named volume mountpoint as `root:root mode 755`; the viewer's port-detection JS hardcoded `'3113'` so any reverse-proxy fronting on port 80/443 returned an empty dashboard; and the `mem::context` budget loop short-circuited the entire selection on the first oversized block — pinning a large slot could starve all other context blocks even when smaller ones would have fit. - -### Fixed - -- **`docker-compose.yml` now chowns the named volume to UID 65532 before the engine starts.** The `iiidev/iii` image is distroless and runs as UID 65532; docker initializes named volumes as `root:root mode 755`; the engine has no `sh` / `chown` to self-heal at startup, so writes to `/data/state_store.db` and `/data/stream_store` returned `Permission denied (os error 13)`. The engine silently buffered in RAM, the API kept reporting `success: true`, and state evaporated on every container restart — the exact symptom v0.9.7's working-directory fix set out to solve. The compose file now ships an `iii-init` one-shot service (`busybox:1.36`, ~4 MB, exits in <100 ms) that runs `chown -R 65532:65532 /data && chmod 755 /data` once at compose-up, plus a `user: "65532:65532"` directive on the `iii-engine` service and a `depends_on.iii-init.condition: service_completed_successfully` gate so the engine never starts before the volume is owner-correct. Existing deployments that already hit the bug should run `docker compose down && docker compose up -d` after upgrading — the init container will fix the volume in place on the first run. (#301, closes [#301](https://github.com/rohitg00/agentmemory/issues/301) — thanks [@flamerged](https://github.com/flamerged) for the precise UID + mountpoint trace and the chown workaround that confirmed the fix shape) - -- **Viewer dashboard now works behind any reverse proxy on standard ports (80 / 443).** `src/viewer/index.html`'s port-detection JS resolved the REST base URL through `params.get('port') || window.location.port || '3113'` — when the page was served on port 80 or 443, `window.location.port` was the empty string and the fallback hardcoded `':3113'`, so every browser-side `/agentmemory/*` fetch missed the proxied origin and went to `:3113` (typically loopback-only on these deployments, so unreachable from outside). The viewer rendered cleanly but every panel showed the empty "first run" state — even though `curl /agentmemory/sessions` (no explicit port) returned correct data. The fix uses `window.location.origin` as the REST base when neither `?port=` nor `window.location.port` is set, and constructs the WebSocket URL from `window.location.host` (with whatever port the page was served on) so the same-origin path works for both REST and live updates. Behaviour with an explicit `?port=N` or non-default `window.location.port` is unchanged. (#299, closes [#299](https://github.com/rohitg00/agentmemory/issues/299) — thanks [@flamerged](https://github.com/flamerged) for the deployment context + the lines-927–930 trace) - -- **`mem::context` budget loop no longer bails the entire selection on the first oversized block.** The selection loop in `src/functions/context.ts` used `break` when `usedTokens + block.tokens > budget`, so a single oversized block at the top of the sorted list — most commonly a fat pinned slot under #288's new injection path — cut off every smaller block that would have fit. Switched to `continue`, so smaller blocks downstream of an oversized one can still slip into the remaining budget. Net effect: pinned-slot priority semantic is preserved (pinned blocks still sort first via `recency: Date.now()`), but the worst case no longer starves the entire context. Total tokens still bounded by `tokenBudget` (default 2000); only the composition under contention changes. - -### Changed - -- `@agentmemory/mcp` package version bumped from 0.9.9 → 0.9.10 to lockstep with the main package. - -## [0.9.9] — 2026-05-11 - -Two field-reported regressions closed: pinned memory slots never reached SessionStart context (the `renderPinnedContext` and `listPinnedSlots` helpers shipped in v0.7 had no callers), and the MiniMax compression provider read its base URL straight off `process.env`, missing `~/.agentmemory/.env` values that the rest of agentmemory loads through the shared merged-env path. - -### Fixed - -- **Pinned memory slots are now actually injected into SessionStart context.** [@wyh0626](https://github.com/wyh0626) traced ([#286](https://github.com/rohitg00/agentmemory/issues/286)) that `renderPinnedContext` (`src/functions/slots.ts:182`) and `listPinnedSlots` (`src/functions/slots.ts:169`) — introduced in [#182](https://github.com/rohitg00/agentmemory/pull/182) — had zero callers in `src/`. `mem::context` (the function `/agentmemory/session/start` invokes and that `session-start.mjs` reads back into Claude Code) read profile / sessions / summaries / observations but never slots, so anything an agent wrote into a pinned slot via `mem::slot-replace` / `mem::slot-append` / `mem::slot-reflect` sat in KV and never reached the next session. The `mem::slot-reflect` writer fires on the Stop hook when `AGENTMEMORY_REFLECT=true` and persists into `pending_items` / `session_patterns` / `project_context` — the reflect → next-session loop was open. The fix wires `listPinnedSlots` → `renderPinnedContext` into `mem::context` behind `isSlotsEnabled()`, matching the existing `isGraphExtractionEnabled()` gate convention. Pinned slot content lands as a `type: "memory"` block with `recency: Date.now()` so it sorts to the top of the budget-bounded selection. `AGENTMEMORY_SLOTS=false` (the default) keeps the existing behaviour untouched. (#288, closes [#286](https://github.com/rohitg00/agentmemory/issues/286) — thanks [@wyh0626](https://github.com/wyh0626) for the precise zero-callers trace and the six-case regression suite covering global / multi-sort / unpinned-skip / empty-skip / project-shadows-global / gate-off) - -- **MiniMax provider now honors `MINIMAX_BASE_URL` from `~/.agentmemory/.env`, with the default pointed at the current Anthropic-compatible host.** [@rager306](https://github.com/rager306) reported ([#285](https://github.com/rohitg00/agentmemory/issues/285)) v0.9.8 MiniMax compression failing `MiniMax API error 401: invalid api key` against a verified-good key. Direct `curl https://api.minimax.io/anthropic/v1/messages` with the same key succeeded; the control call against `https://api.minimaxi.com/anthropic` returned 401. The 401 was the *stale fallback host* answering — the key was fine. Root cause: split-brain env source. `src/config.ts` (provider detection + API-key load) reads `~/.agentmemory/.env` through the merged `getMergedEnv()` loader; `src/providers/minimax.ts` read `MINIMAX_BASE_URL` straight off `process.env`. Deployments that kept MiniMax config in `~/.agentmemory/.env` only (systemd / launchd / `--env-file` not exported into the worker shell) got the key loaded but the base URL fell through to the stale default. The provider now resolves `MINIMAX_BASE_URL` via `getEnvVar()` (same merged loader the rest of agentmemory uses), and the default is bumped from `api.minimaxi.com` to `api.minimax.io/anthropic` per [MiniMax's current Anthropic-compatible docs](https://platform.minimax.io/docs/api-reference/text-anthropic-api). (#289, closes [#285](https://github.com/rohitg00/agentmemory/issues/285) — thanks [@rager306](https://github.com/rager306) for the precise repro and the local-patch verification) - -### Changed - -- `@agentmemory/mcp` package version bumped from 0.9.8 → 0.9.9 to lockstep with the main package. - -## [0.9.8] — 2026-05-11 - -Single-line follow-up to v0.9.7's MCP shim work. v0.9.7 surfaced probe failures, added an `AGENTMEMORY_FORCE_PROXY=1` escape hatch, and shipped `AGENTMEMORY_DEBUG=1` — but the *local-mode* `tools/list` branch was still returning only 4 tools when an agentmemory server was unreachable, not the documented 7-tool `IMPLEMENTED_TOOLS` set the shim's `InMemoryKV` actually backs. v0.9.8 fixes that. - -### Fixed - -- **`@agentmemory/mcp` local-mode `tools/list` now exposes all 7 `IMPLEMENTED_TOOLS`, not the 4-tool intersection with `ESSENTIAL_TOOLS`.** The local-fallback branch in `src/mcp/standalone.ts` filtered `getVisibleTools()` through `IMPLEMENTED_TOOLS`, but `getVisibleTools()` honors the shim process's own `AGENTMEMORY_TOOLS` env var (unset → "core" → 8 `ESSENTIAL_TOOLS`). The intersection of `ESSENTIAL_TOOLS` (8) and `IMPLEMENTED_TOOLS` (7) is exactly 4 tools: `memory_save`, `memory_recall`, `memory_smart_search`, `memory_sessions`. Those four were the only ones MCP clients saw when no agentmemory server was reachable — even though the shim's `InMemoryKV` implements all seven (the four above plus `memory_export`, `memory_audit`, `memory_governance_delete`). The filter source was wrong: the shim dispatches by `IMPLEMENTED_TOOLS`, not by `ESSENTIAL_TOOLS`, so a server-tier env var should never have shaped the shim's local-fallback list. Switched to `getAllTools().filter((t) => IMPLEMENTED_TOOLS.has(t.name))`, which picks the same 7 names from the unfiltered universe regardless of `AGENTMEMORY_TOOLS`. Refactored the `tools/list` handler into an exported `handleToolsList()` for direct testability, and added a regression test asserting the local-fallback path returns exactly the 7 names under both unset and `core` modes. Verified live via stdio against a downed server: shim now returns 7 names (was 4). (#283, closes [#234](https://github.com/rohitg00/agentmemory/issues/234)) - -### Changed - -- `@agentmemory/mcp` package version bumped from 0.9.7 → 0.9.8 to lockstep with the main package. - -## [0.9.7] — 2026-05-11 - -Four follow-up fixes to v0.9.6 reported live by [@jcalfee](https://github.com/jcalfee) ([#234](https://github.com/rohitg00/agentmemory/issues/234)) and [@satabd](https://github.com/satabd) ([#278](https://github.com/rohitg00/agentmemory/issues/278)): the `@agentmemory/mcp` shim gained probe diagnostics and an escape hatch for clients that can reach the server via a non-default route, the Docker compose stack now persists state across `docker compose down` (the volume binding was unused due to a working-directory mismatch in the engine config), a cosmetic `which iii` stderr leak was suppressed, and the engine container's log output is now bounded so a crash/restart spam loop can no longer fill the host disk. - -### Fixed - -- **`@agentmemory/mcp` standalone shim now surfaces probe failures and ships an escape hatch and a debug-trace knob.** The `livez` probe in `src/mcp/rest-proxy.ts` used a 500 ms timeout and silently swallowed every failure: when the probe failed, the shim fell back to the 7-tool `IMPLEMENTED_TOOLS` set with no log line explaining why. The probe now writes the URL, HTTP status (or thrown reason), and the active timeout to `stderr` on every failure; the default timeout is raised to 2000 ms; `AGENTMEMORY_PROBE_TIMEOUT_MS` overrides it for slow loopbacks; `AGENTMEMORY_FORCE_PROXY=1` skips the probe entirely and trusts `AGENTMEMORY_URL` outright (the right escape hatch when the shim can reach the server via a known route but not the host's `localhost`); and `AGENTMEMORY_DEBUG=1` traces `tools/list` end-to-end (`handle.mode`, response shape, returned tool count, local-fallback contents) so MCP integrations have first-class visibility into what the shim is actually returning over the wire. The shim verified end-to-end via stdio against an `AGENTMEMORY_TOOLS=all` server: `tools/list` returns all 51 tools. (closes [#234](https://github.com/rohitg00/agentmemory/issues/234), thanks [@jcalfee](https://github.com/jcalfee) for the host-vs-sandboxed-client repro and detailed log captures) - -- **Docker compose stack no longer loses state on `docker compose down`.** `iii-config.docker.yaml` configured `iii-state` and `iii-stream` with relative `file_path: ./data/...`, which the engine resolves against its container `WORKDIR=/home/nonroot` — not the `/data` mount where the named `iii-data` volume lives. State and stream stores were written to the ephemeral container layer and discarded on every container restart, so memories, BM25 index, and stream backlog vanished. Both paths are now absolute (`/data/state_store.db` and `/data/stream_store`), routing writes through the named volume as the compose file always intended. Existing users need a one-time `docker compose down -v` to clear the old empty volume layout before the upgrade. - -- **CLI banner no longer leaks `which: no iii in $PATH` when iii isn't installed.** `whichBinary()` in `src/cli.ts` called `execFileSync("which", ["iii"])` with default stdio, which inherits the child's `stderr` to the parent process — and GNU `which` writes "no iii in (...)" to `stderr` (with exit 1) on miss. The catch swallowed the throw but the stderr line had already drained into the user's terminal between the `agentmemory` banner and the "iii-engine ready" line. `stdio: ["ignore", "pipe", "pipe"]` now captures both streams. Pure cosmetic, no behavior change. - -- **Docker compose stack now caps engine container log size at 30 MB total.** [@satabd](https://github.com/satabd) reported the `iiidev/iii:0.11.2` engine container filling a host disk with a 129 GB `-json.log` when the engine fell into a crash/restart spam loop ([#278](https://github.com/rohitg00/agentmemory/issues/278)). The compose service now sets `logging.driver: json-file` with `max-size: 10m` and `max-file: 3`, so unbounded engine stdout/stderr can no longer eat the host's disk. The upstream engine spam itself is filed against `iiidev/iii` — this is the compose-side guardrail. - -### Changed - -- `@agentmemory/mcp` package version bumped from 0.9.6 → 0.9.7 to lockstep with the main package. - -## [0.9.6] — 2026-05-10 - -Three reliability fixes that close field-reported regressions in v0.9.5: search/recall returns saved memories again, the standalone MCP shim no longer caps non-Claude clients at a 7-tool subset, and the Claude Code session/subagent hooks no longer block agent startup for up to five seconds against a slow or unreachable REST server. - -### Fixed - -- **`memory_smart_search` and `memory_recall` surface memories saved via `memory_save` again.** v0.9.5 indexed memories into BM25 (#258), so search ranked them correctly — but the result-enrichment step on both retrieval paths still queried `KV.observations(sessionId, obsId)`. Memories live in `KV.memories` under a synthetic sessionId, so every hit was silently dropped and clients saw `results: []`. Both `HybridSearch.enrichResults` (which powers `/smart-search`) and `mem::search`'s observation map (which powers `/search` / `memory_recall`) now fall back to `KV.memories` when the observation lookup misses, coercing the `Memory` record into a `CompressedObservation` via a new shared `memoryToObservation` helper in `src/state/memory-utils.ts`. Verified live end-to-end against an iii-engine 0.11.2 stack: pre-fix both endpoints returned `[]` for a saved memory; post-fix the memory surfaces with `score > 0` on both. (#269, closes [#265](https://github.com/rohitg00/agentmemory/issues/265)) - -- **`@agentmemory/mcp` standalone shim now exposes the server's full tool surface to non-Claude clients.** With `AGENTMEMORY_TOOLS=all` on the server, OpenCode / Cursor / Gemini CLI / Cline users expected 51 tools; the shim filtered the response through a hardcoded 7-tool `IMPLEMENTED_TOOLS` set baked in for the local InMemoryKV fallback, so they got 4 (default) or 7 (with the env var). The shim now delegates `tools/list` to `GET /agentmemory/mcp/tools` when an agentmemory server is reachable, and forwards any non-essential tool to `POST /agentmemory/mcp/call` for server-side validation. The local InMemoryKV fallback is unchanged — it still implements only the 7 essential tools, with a clearer error pointing to `AGENTMEMORY_URL` when no server is reachable. Verified live end-to-end via stdio JSON-RPC driver: pre-fix `tools/list` returned 4 tools and `memory_lesson_save` raised "Unknown tool"; post-fix `tools/list` returned 51 and `memory_lesson_save` created a lesson. (#270, closes [#234](https://github.com/rohitg00/agentmemory/issues/234)) - -- **Claude Code session/subagent hooks no longer block agent startup waiting for a slow agentmemory.** `session-start.ts` awaited a 5000 ms POST and discarded the response whenever `AGENTMEMORY_INJECT_CONTEXT=false` (the default since 0.8.10) — pure latency. `subagent-start.ts` had a `// fire and forget` comment but the code awaited a 2000 ms POST. Under fan-out (Slack-bot orchestrators, multi-agent harnesses, fanned `claude -p` jobs) the awaited timeouts stack and feed back into the engine; the reporter hit a positive feedback loop that OOM-killed iii-engine. `session-start` now fire-and-forgets on the telemetry path and caps the inject path at 1500 ms (down from 5000 ms). `subagent-start` actually fire-and-forgets, capped at 800 ms. Verified live against a black-hole TCP listener (accepts, never replies): session-start (no inject) 5.05 s → 0.85 s, session-start (inject=true) 5.05 s → 1.55 s, subagent-start 2.05 s → 0.87 s. (#271, closes [#221](https://github.com/rohitg00/agentmemory/issues/221)) - -### Changed - -- `@agentmemory/mcp` package version bumped from 0.9.4 → 0.9.6 to lockstep with the main package, fixing a release-flow miss in v0.9.5. - -## [0.9.5] — 2026-05-09 - -Bug-fix patch focused on **search recall correctness** and **plugin compatibility**. Pins `iii-engine` to v0.11.2 because v0.11.6 introduces a new sandbox-everything-via-`iii worker add` model that agentmemory hasn't been refactored for yet — pin lifts once that refactor lands. Adds a hard guard against silent vector-index corruption, fixes BM25 indexing for memories saved via `memory_save`, and lands four Hermes plugin fixes that make the memory provider actually usable end-to-end. - -If you've been seeing `memory_smart_search` return empty results for memories you just saved, this release fixes that. If you've been hitting `hermes memory status` reporting "not available" against a healthy systemd-managed install, this release fixes that too. - -### Fixed - -- **BM25 search now indexes memories saved via `memory_save`.** `mem::remember` was writing to `KV.memories` but never calling `getSearchIndex().add()`, so `memory_smart_search` and `memory_recall` returned empty for everything saved through that path — for **every** version since v0.9.0. Synthesizes a `CompressedObservation` from the saved Memory (title + content + concepts + files) and adds it to BM25 right after the durable write. `rebuildIndex()` now walks `KV.memories` so a fresh rebuild covers the full corpus, and a startup backfill retroactively indexes pre-existing memories on first start after upgrade — no manual reindex required. New `SearchIndex.has(id)` is the idempotency gate. (#258, closes [#257](https://github.com/rohitg00/agentmemory/issues/257) — thanks @Nizar-BenHamida for the precise repro and log capture) - -- **Embedding providers no longer silently corrupt the vector index when an API returns wrong-dimension vectors.** `cosineSimilarity` returns `0` on length mismatch instead of throwing, so a wrong-size vector got stored, never matched anything, and the corresponding memory became invisible without a single log line. `withDimensionGuard()` now wraps every embedding provider at the factory boundary in `src/providers/embedding/index.ts` — `embed()`, `embedBatch()` (per-vector, indexed errors like `embedBatch[3]`), and `embedImage()` all throw a descriptive error when the returned `Float32Array` length doesn't match `provider.dimensions`. The persistence-restore path got the same defense: `IndexPersistence.load()` now refuses to start when persisted vectors mismatch the active provider, with an actionable error spelling out the recovery paths (re-embed / `AGENTMEMORY_DROP_STALE_INDEX=true` / switch back). (#248, closes [#247](https://github.com/rohitg00/agentmemory/issues/247) and [#256](https://github.com/rohitg00/agentmemory/issues/256) — thanks @AmmarSaleh50 for the issue analysis, the fix PR, and the test coverage) - -- **Hermes plugin: `handle_tool_call` now returns JSON strings, not raw Python dicts.** Hermes stores the return value as the tool result `content` field in session history. Anthropic-protocol providers reject non-string content with a 400 on the next request — once triggered, every subsequent request in the affected session 400s until the session JSON is hand-cleaned. Wrapped all four return paths (`memory_recall`, `memory_save`, `memory_search`, unknown-tool) in `json.dumps()` and tightened the return-type annotation `Any → str` on both the abstract base and the concrete class. Matches the contract that `src/mcp/standalone.ts` already honors. (#255, closes [#254](https://github.com/rohitg00/agentmemory/issues/254) — thanks @KyoMio for the Anthropic-protocol-specific repro) - -- **Hermes plugin: `hermes memory status` now reflects the real service state on systemd / launchd installs.** When agentmemory runs as an external service whose runtime config lives in `~/.agentmemory/.env`, those values never reach the Hermes CLI shell. Hermes status reads `os.environ` against `get_config_schema()`'s `env_var` keys, finds them unset, and reports the plugin as "not available" — even though the service is healthy. The plugin now preloads `~/.agentmemory/.env` at import time using `os.environ.setdefault`, bridging the agentmemory-managed and Hermes-managed config source-of-truths. Anything explicitly exported in the shell still wins. Best-effort: malformed / absent file is silently skipped. Both `~/.agentmemory/.env` and `$XDG_CONFIG_HOME/agentmemory/.env` are checked. (#253, closes [#250](https://github.com/rohitg00/agentmemory/issues/250) — thanks @OptionalCoin for the systemd repro and tracing it to env-source divergence) - -- **Hermes plugin: memory provider hooks accept passthrough kwargs.** Hermes calls memory provider hooks with extra context kwargs (e.g. `session_id`) at runtime that the existing strict signatures rejected with `sync_turn() got an unexpected keyword argument 'session_id'`. Hooks "succeeded" from Hermes's perspective but every conversation turn silently failed sync. Added `**kwargs: Any` to `sync_turn`, `on_session_end`, `on_pre_compress`, `on_memory_write`, `prefetch`, `queue_prefetch`, and `shutdown`. Where Hermes passes `session_id`, the patch prefers it over the cached `self._session_id` so multi-session gateway contexts route to the right session. Same change applied to the abstract `MemoryProvider` fallback for the import-error path. (#252, closes [#249](https://github.com/rohitg00/agentmemory/issues/249) — thanks @OptionalCoin for the precise log analysis) - -- **`agentmemory demo` now actually seeds observations.** `seedDemoSession` posted to `/agentmemory/observe` without `project` and `cwd`, which the API requires as non-empty strings, so every observation 400'd and the demo silently reported "Seeded 0 observations across 3 sessions". Two-line fix: re-stage `project` + `cwd` into the observe payload alongside `sessionId`. The smart-search queries the demo prints will now return real hits. (#251, closes [#229](https://github.com/rohitg00/agentmemory/issues/229) — thanks @seishonagon for the precise root-cause analysis) - -- **LLM compression / summarization timeouts increased.** Larger sessions were hitting the 120s consolidation timeout under heavier workloads, leaving partial state. Bumped per-step ceilings to give slow providers (esp. local models) room to finish. (#213 — thanks @xuli500177) - -- **`pi` / OpenClaw / Hermes integration fixes.** Tested round-trip fixes across the three integration plugins to keep them aligned with the latest hooks contract. (#230 — thanks @deepmroot) - -### Changed - -- **`iii-engine` pinned to v0.11.2 across every install path.** v0.11.6 introduces a new architecture where workers run inside sandboxed microVMs registered via `iii worker add`. agentmemory still uses the older `iii-exec watch + node dist/index.mjs` worker model from `iii-config.yaml`, which doesn't pass the new engine's stricter trigger validation cleanly — the worker drops into an EPIPE reconnect loop and recall stops working. Pinning to v0.11.2 (the last engine that runs agentmemory's current architecture cleanly) until we refactor agentmemory to register itself via `iii worker add` and run inside the new sandbox model. - - `src/cli.ts` auto-installer downloads `github.com/iii-hq/iii/releases/download/iii/v0.11.2/iii-.tar.gz` directly. Per-arch coverage: darwin arm64/x64, linux x64/arm64/armv7, win32 x64/arm64. - - Docker fallback pulls `iiidev/iii:0.11.2` instead of `:latest`. - - `docker-compose.yml` uses `image: iiidev/iii:${AGENTMEMORY_III_VERSION:-0.11.2}` so the override env var actually takes effect for compose users. - - Install instructions and Windows guide updated to point at the v0.11.2 release page. - - **Escape hatch:** `AGENTMEMORY_III_VERSION=` overrides the pin for users who've moved to the sandbox model manually. - - Windows ZIP path detection in `runUpgrade` so the auto-installer doesn't try to pipe a `.zip` through `tar -xz`. (#260) - - **Follow-up tracked separately:** refactor agentmemory to register as a sandboxed worker via `iii worker add` so the pin can be lifted. - -- **README documents how to extend agentmemory with `iii worker add`.** New "Powered by iii" section maps each `iii worker add ` to a concrete agentmemory capability — multi-instance memory, scheduled consolidation, durable retries on embeddings, sandboxed code exec, SQL state, extra MCP host. Lists only workers actually published to [workers.iii.dev](https://workers.iii.dev) with direct links. (#242) - -- **README iii Console section corrected.** The console ships with `iii` as a subcommand; there's no separate installer. Replaced the bogus `curl install.iii.dev/console/main/install.sh` line, simplified the launch command to `iii console --port 3114`, and added the missing console pages to the capability table (Workers, Queues, Config, Flow). Replaced the dashboard screenshot with the Workers page so users see real agentmemory instances connected. (#243) - -### Notes - -If you're upgrading from <0.9.5 and have an existing vector index on disk, the new dim-guard will refuse to load if your active embedding provider declares a different dimension than what's persisted. This is the intended safe default — set `AGENTMEMORY_DROP_STALE_INDEX=true` to discard and rebuild from live observations, or re-embed against the new provider before starting. - -If you've been on `iii-engine` v0.11.6 and noticed search returning empty after save, install agentmemory 0.9.5 fresh (or run `npx @agentmemory/agentmemory upgrade`) to pull pinned engine v0.11.2. v0.11.6 brings a new sandbox-everything-via-`iii worker add` model that agentmemory hasn't been refactored for yet — that work is tracked as a follow-up; this release just keeps existing users unblocked. - -[0.9.6]: https://github.com/rohitg00/agentmemory/compare/v0.9.5...v0.9.6 -[0.9.5]: https://github.com/rohitg00/agentmemory/compare/v0.9.4...v0.9.5 - -## [0.9.4] — 2026-04-29 - -Bug-fix patch. Fixes a silent gap where the knowledge graph never auto-populated despite `GRAPH_EXTRACTION_ENABLED=true`, and adds a doctor check that detects when Claude Code fails to load plugin hooks. - -### Fixed - -- **`mem::graph-extract` now auto-fires at session end.** When `GRAPH_EXTRACTION_ENABLED=true`, the function was registered and the REST endpoint was live, but no internal caller invoked it — the graph KV stayed empty unless users manually `POST`ed to `/agentmemory/graph/extract`. `event::session::stopped` now triggers it (fire-and-forget, idempotent via existing node/edge merge keys), so enabling the flag actually populates the graph. README pipeline diagram updated to show graph extraction at the Stop/SessionEnd phase rather than implying it runs per PostToolUse. (#210) - -### Added - -- **`agentmemory doctor` detects Claude Code plugin-hook load state.** Scans `~/.claude/debug/latest` for the `Loaded hooks from standard location for plugin agentmemory` line. Surfaces the silent failure mode where the plugin is enabled but Claude Code never registered the hooks — users previously got no signal, hooks just silently did nothing. Hint points at reinstall + session restart and the CC version floor (>= 2.1.x). Skips silently when `~/.claude/debug` is absent. (refs #212) - -[0.9.4]: https://github.com/rohitg00/agentmemory/compare/v0.9.3...v0.9.4 - -## [0.9.3] — 2026-04-24 - -Developer-experience patch. Every disabled feature flag is now visible in the viewer, the CLI, and REST error responses, so devs no longer hit empty tabs wondering whether the install is broken or just opt-in. Adds a `doctor` command that diagnoses the whole stack in one shot and a first-run hero in the viewer that points at the magical-moment `demo` command. - -### Added - -- **`agentmemory doctor` command.** Runs 10 diagnostic checks in one shot: server reachability, health status, viewer port, LLM provider, embedding provider, four feature flag states, and whether the knowledge graph has data. Every failing check includes a concrete hint with the exact env var or command to fix it. Mirrors the shape of the new viewer feature-flag banners. -- **`/agentmemory/config/flags` REST endpoint.** Returns `{ version, provider, embeddingProvider, flags[] }` with per-flag `{ key, label, enabled, default, affects, needsLlm, description, enableHow, docsHref }`. Used by the viewer banner, CLI status/doctor, and anyone who wants to introspect config without parsing logs. -- **Viewer feature-flag banner system.** Compact collapsible summary row at the top of every tab (`⚠ 3 off · ⚙ 1 note · Feature flags — click to expand`). Expanded view shows per-flag card with description, exact enable command, docs link, and dismiss button. Dismissed state persists per-flag in localStorage so banners stay out of the way once acknowledged. Banners filter by the current tab's `affects` list. -- **Viewer first-run hero card.** When `sessions.length === 0`, dashboard renders an orange-accent card titled "First run → magical moment in 10 seconds" with `npx @agentmemory/agentmemory demo` as the next step. Removes the dead-empty dashboard that used to greet fresh installs. -- **Viewer footer with preset issue report.** `agentmemory viewer · v{version} · github · docs · report issue →`. The feedback link opens a GitHub issue pre-filled with version, provider name, embedding provider, flag state, and user-agent — so the first message on an issue already contains the diagnostic context that used to take three back-and-forths. -- **Richer empty states on Actions, Memories, Lessons, Crystals tabs.** Each now has a titled lead explaining what the tab is for, why it's empty, three concrete ways to populate it (MCP tool, curl, hook), and a docs link. The old one-liners ("No actions yet. Create actions via memory_action_create MCP tool") assumed too much context. -- **`status` command shows flag state.** New section in the output block lists provider (`✓ llm` / `✗ noop`), embedding provider (`✓ embeddings` / `bm25-only`), and each flag with a tick/cross. Parity with the viewer banner. -- **`AGENTMEMORY_URL` environment variable honored by CLI.** `status`, `doctor`, and related health checks now respect `AGENTMEMORY_URL=http://host:port` and extract the port from it. Previously documented but silently ignored; `--port N` was the only way to override. -- **Website install section promotes `demo` to step 2.** `npx @agentmemory/agentmemory demo` now appears between "start server" and "open viewer" on agent-memory.dev. The magical-moment command is on the critical path of the three-step install, not tucked into the README. -- **Website version auto-derived from repo package.json.** `gen-meta.mjs` picks up `src/version.ts` on `prebuild` and writes `website/lib/generated-meta.json`. Removes the stale-version drift that showed `v0.9.1` on the landing page after `v0.9.2` shipped. - -### Changed - -- **REST "feature not enabled" errors now return structured bodies.** Graph extraction (3 endpoints) and consolidation pipeline (1 endpoint) used to return `{ error: "Knowledge graph not enabled" }`. Now return `{ error, flag, enableHow, docsHref }` matching the viewer banner contract. Curl users get the same fix guidance as UI users. -- **Website install title: `THREE STEPS` → `THREE COMMANDS`.** Matches the new three-command install (`npx agentmemory`, `agentmemory demo`, `open viewer`). - -### Fixed - -- **Viewer banner scroll blocker.** Initial banner implementation rendered four full-height banner cards stacked above the dashboard, pushing all stats off-screen. Replaced with compact collapsible summary that takes ~40px of vertical space by default and only expands on click. - -[0.9.3]: https://github.com/rohitg00/agentmemory/compare/v0.9.2...v0.9.3 - -## [0.9.2] — 2026-04-22 - -Safety + import-pipeline patch. Kills the infinite Stop-hook recursion loop that burned Claude Pro tokens on unkeyed installs, repairs every empty viewer tab after `import-jsonl`, derives lessons and crystals automatically from imported sessions, and opens up OpenAI-compatible embedding endpoints. - -### Security - -- **Stop-hook recursion loop** ([#187](https://github.com/rohitg00/agentmemory/pull/187), follow-up to [#149](https://github.com/rohitg00/agentmemory/issues/149)). A user with no provider key and `AGENTMEMORY_AUTO_COMPRESS=false` could still trigger unbounded recursion: Stop hook → `/summarize` → `provider.summarize()` → agent-sdk provider spawned a Claude Agent SDK child session that inherited the same plugin hooks, whose own Stop fired, spawning another child, etc. ~579 ghost `entrypoint: sdk-ts` sessions could accumulate in minutes, draining the Claude Pro subscription. Fixed at five layers in defense-in-depth: - 1. `detectProvider()` treats empty-string keys (`ANTHROPIC_API_KEY=`) as unset and returns the noop provider by default. The agent-sdk fallback now requires explicit `AGENTMEMORY_ALLOW_AGENT_SDK=true` opt-in with a second loud warning. - 2. New `NoopProvider` returns empty strings for compress/summarize; callers detect `.name === "noop"` and short-circuit. - 3. `agent-sdk` provider sets `AGENTMEMORY_SDK_CHILD=1` before spawning `query()` and restores the previous value in `finally` so later calls in the same parent process are not mis-classified. - 4. All 12 hook scripts inline a shared `isSdkChildContext(payload)` guard that checks both the env marker and `payload.entrypoint === "sdk-ts"`, and bail early. - 5. `/summarize` short-circuits with `{ success: false, error: "no_provider" }` when `provider.name === "noop"` instead of calling through. Empty provider responses are now logged and recorded as failures on the metrics store. - -### Added - -- **`OPENAI_BASE_URL` / `OPENAI_EMBEDDING_MODEL`** ([#186](https://github.com/rohitg00/agentmemory/pull/186), thanks @Edison-A-N). The `OpenAIEmbeddingProvider` now accepts a base URL override and a configurable model name, mirroring the `MINIMAX_BASE_URL` pattern. Unlocks Azure OpenAI, vLLM, LM Studio, and other OpenAI-compatible proxies for embeddings with zero breakage — defaults are preserved. -- **`OPENAI_EMBEDDING_DIMENSIONS`** ([#189](https://github.com/rohitg00/agentmemory/pull/189)). Follow-up: `dimensions` is now derived from the model via a `MODEL_DIMENSIONS` lookup (3-small=1536, 3-large=3072, ada-002=1536) and falls back to 1536 for unknown models. Custom or self-hosted OpenAI-compatible models should set this env var explicitly; non-positive values are rejected at construction. -- **Auto-derived lessons and crystals on `import-jsonl`** ([#188](https://github.com/rohitg00/agentmemory/pull/188)). Each imported session now produces one crystal (narrative, tool outcomes, files, lessons) and up to 20 heuristic lessons from instructional patterns (`always`/`never`/`don't`/`prefer`/`avoid`/`caveat`/`note`/`warning`). Lessons are keyed by `fingerprintId("lesson", content.toLowerCase())` so re-importing the same file bumps `reinforcements` on existing lessons instead of duplicating rows. Crystals are keyed by `fingerprintId("crystal", sessionId)` and preserve `createdAt` on upsert. -- **Session preview on the sessions list** ([#188](https://github.com/rohitg00/agentmemory/pull/188)). `Session` gained `firstPrompt` / `summary` fields; both `import-jsonl` and the live `mem::observe` path populate `firstPrompt` from the first real user prompt they see, and the viewer renders it as a 140-char preview row under each session. -- **Richer session detail + crystals viz + lessons tab explainers** ([#188](https://github.com/rohitg00/agentmemory/pull/188)). Clicking a session now fetches its observations and renders a 4-stat grid (observations / tools / files / duration), top-10 tool bar chart, activity breakdown, and file list. Crystals cards show resolved lesson content instead of raw IDs. Lessons tab has a header explainer card for the rule + confidence + decay model. - -### Changed - -- **`detectProvider()` default is now `noop`** (see Security). Users who had no API key and relied on the implicit Claude-subscription fallback must set `AGENTMEMORY_ALLOW_AGENT_SDK=true` to restore old behavior — and should read the warning about Stop-hook recursion first. -- **`/agentmemory/audit` response shape** ([#188](https://github.com/rohitg00/agentmemory/pull/188)). Now returns `{ entries, success }` instead of a bare array to match the viewer's expected shape. The viewer was rendering empty despite populated data. -- **`/agentmemory/replay/sessions` path** ([#188](https://github.com/rohitg00/agentmemory/pull/188)). Calls `kv.list` directly instead of `sdk.trigger → mem::replay::sessions`. Sub-50ms on 600+ sessions instead of timing out at 10s+. -- **Viewer WebSocket connect timeout** ([#188](https://github.com/rohitg00/agentmemory/pull/188)). 5-second timeout around `new WebSocket(...)`. If the socket is still CONNECTING after that, it is force-closed so the `onclose` retry / polling-fallback chain kicks in. Previously the banner stuck on `CONNECTING…` forever when the iii-stream port accepted TCP but never completed the upgrade handshake. -- **`import-jsonl` now runs synthetic compression + BM25 indexing** ([#188](https://github.com/rohitg00/agentmemory/pull/188)). Imported observations go through the same `buildSyntheticCompression` + `getSearchIndex().add()` path as live `mem::observe`. Previously the raw shape was written directly to KV and the search index never saw it — consolidation reported "fewer than 5 summaries" and semantic/procedural/memory tabs stayed empty. -- **Viewer strength gauge** ([#188](https://github.com/rohitg00/agentmemory/pull/188)). Memory tab showed `700%` on `strength: 7` because the scale was treated as 0–1. Now handles both 0–1 and 0–10 and clamps at 100%. - -### Fixed - -- **`npm ci` on fork PRs** ([#187](https://github.com/rohitg00/agentmemory/pull/187), [#188](https://github.com/rohitg00/agentmemory/pull/188)). CI failed because lockfiles are gitignored at the repo level. `.github/workflows/ci.yml` + `publish.yml` now run a two-step install: `npm install --package-lock-only` to produce a lockfile in the runner workspace, then `npm ci` to install deterministically from it. Gives a single resolved dependency graph across build + test + publish within one job run — important because publish uses `--provenance`. -- **`image-quota-cleanup` fail-closed on refCount read errors** ([#188](https://github.com/rohitg00/agentmemory/pull/188)). When `getImageRefCount` threw, the code fell through to `deleteImage` with `refCount === 0`, risking deletion of still-referenced images on transient KV errors. Fail-closed: log + return from the `withKeyedLock` callback, never reach `deleteImage` without a confirmed zero refcount. -- **`raw.userPrompt` type guard** ([#188](https://github.com/rohitg00/agentmemory/pull/188)). `mem::observe` now runtime-checks `typeof raw.userPrompt === "string"` before calling `.replace` / `.trim` / `.slice`. Non-string truthy values from malformed hook payloads no longer crash the handler. -- **Viewer Actions frontier field** ([#188](https://github.com/rohitg00/agentmemory/pull/188)). The tab was reading `results[1].actions` but `/frontier` returns `{ frontier: [...] }`. Fixed the read path; preserves actions/frontier unification. -- **Hardcoded `maxTokens: 4096` in the agent-sdk branch of `detectProvider`** ([#188](https://github.com/rohitg00/agentmemory/pull/188), [#190](https://github.com/rohitg00/agentmemory/pull/190)). Ignored the `maxTokens` variable computed from `env["MAX_TOKENS"]`. Every other branch already used the computed value; agent-sdk now matches. - -### Infrastructure - -- `StateScope` interface in `types.ts` documents the `KV.state` scope shape (`system:currentDiskSize: number`); `disk-size-manager` uses `StateScope[typeof DISK_SIZE_KEY]` generics instead of ad-hoc ``. -- `onnxruntime-node` + `onnxruntime-web` moved to `optionalDependencies` alongside `@xenova/transformers` to make their lazy/transitive nature explicit; still externalized in `tsdown.config.ts` because bundling breaks the native `.node` binding paths. -- `FALLBACK_PROVIDERS` parsing now honors the same `AGENTMEMORY_ALLOW_AGENT_SDK` gate as `detectProvider`, filtering out `agent-sdk` from the fallback chain unless explicitly opted in. -- README provider table + env block updated: no-op is the new default, Claude-subscription fallback moved to a separate opt-in row, OpenAI env vars documented. -- Hero stat badge refreshed from 654 → 827 tests (both dark + light variants). -- `VERSION` / `ExportData.version` union / `supportedVersions` Set / `test/export-import.test.ts` / `@agentmemory/mcp` shim version all bumped in lockstep. -- Test count: 827 (up from 812 in v0.9.1). - -[0.9.2]: https://github.com/rohitg00/agentmemory/compare/v0.9.1...v0.9.2 - -## [0.9.1] — 2026-04-21 - -Trust-the-CLI patch. Three bugs that surfaced in real testing of v0.9.0: the dashboard viewer showed zeros for half its cards, the `import-jsonl` command crashed on anything but a perfect response, and `upgrade` hard-aborted on a cargo registry that never had the crate. - -### Fixed - -- **Viewer dashboard list endpoints** ([#172](https://github.com/rohitg00/agentmemory/pull/172)). `GET /agentmemory/semantic` and `GET /agentmemory/procedural` were never registered, and `GET /agentmemory/relations` returned 405 because only the POST trigger existed. The dashboard's `Promise.all` fan-out silently received null for those cards even when semantic, procedural, or relation data was present. Added `api::semantic-list`, `api::procedural-list`, and `api::relations-list` handlers next to `api::memories` in `src/triggers/api.ts`, each returning the shape the viewer already parses. -- **CLI version drift** ([#173](https://github.com/rohitg00/agentmemory/pull/173)). The viewer brand badge hardcoded `v0.7.0` and the README "New in" banner still said `v0.8.2`. Replaced the viewer string with a `__AGENTMEMORY_VERSION__` placeholder substituted at render time by `document.ts` (same mechanism as the CSP nonce). Collapsed `src/version.ts` from a literal union of every historical release back to a single `VERSION` constant — the import-compat contract is the `supportedVersions` Set in `export-import.ts`, not the type. -- **`import-jsonl` crashed with `Unexpected end of JSON input`** ([#174](https://github.com/rohitg00/agentmemory/pull/174)). The livez probe used fetch throws as the only failure signal — any stray service on port 3111 passed silently, then `res.json()` blew up when the real POST returned an empty body or HTML error. Probe now captures `probe.status` + body snippet on non-OK responses and the exception message on network failure, so the error distinguishes `unreachable (...)` from `reachable but unhealthy (HTTP 503: ...)`. The POST reads body as text, parses only if non-empty, requires `json.success === true`, and maps 401 → "set AGENTMEMORY_SECRET" and 404 → "upgrade server to v0.8.13+". -- **`upgrade` aborted on `cargo install iii-engine`** ([#174](https://github.com/rohitg00/agentmemory/pull/174)). The crate was never published — the old flow called `requireSuccess`, which exited before the Docker pull ran. Swapped to the official installer used throughout the README and demo command: `curl -fsSL https://install.iii.dev/iii/main/install.sh | sh`. Installer failure is optional; a warn points at `iiidev/iii:latest` and the releases page at `iii-hq/iii`. - -### Infrastructure - -- Three integration tests cover the new list endpoints. -- `VERSION` / `ExportData.version` union / `supportedVersions` / `test/export-import.test.ts` all bumped in lockstep. - -[0.9.1]: https://github.com/rohitg00/agentmemory/compare/v0.9.0...v0.9.1 - -## [0.9.0] — 2026-04-18 - -Visibility + correctness release. Landing site, filesystem connector, MCP standalone now actually talks to the running server, health logic stops crying wolf, audit trail closes its last gap, and every memory path has a clear policy. - -### Added -- **Website** ([#164](https://github.com/rohitg00/agentmemory/pull/164)). Next.js 16 App Router landing page at `website/` — Lamborghini-inspired dark canvas, live GitHub stars pill, agents marquee with real brand logos, command-center tab showcase (viewer · iii console · state · traces), 12-tile feature grid, 10-agent MCP install selector, universal MCP JSON + one-click Cursor/VS Code deeplinks. Deploys to Vercel with Root Directory = `website/`. -- **Filesystem connector** — new `@agentmemory/fs-watcher` package under `integrations/filesystem-watcher/` ([#163](https://github.com/rohitg00/agentmemory/pull/163), closes [#62](https://github.com/rohitg00/agentmemory/issues/62)). Node `fs.watch` based, no native deps. Emits valid `HookPayload` observations for every file change and delete, with debounce, default ignore list, text-file preview, bearer auth, and env-driven config. -- **Security advisory drafts** for v0.8.2 CVEs ([#118](https://github.com/rohitg00/agentmemory/pull/118)). Six markdown drafts under `.github/security-advisories/` covering viewer XSS, curl-sh RCE, default 0.0.0.0 bind, unauthenticated mesh sync, Obsidian export traversal, and incomplete secret redaction. Also documents the symlink-traversal limitation of the Obsidian export fix. -- **iii console documentation** in the README ([#157](https://github.com/rohitg00/agentmemory/pull/157)). How to launch the iii console alongside the viewer, what each page gives you for agentmemory, and the `iii-observability` config that ships turned on. - -### Changed -- **Audit policy codified** ([#162](https://github.com/rohitg00/agentmemory/pull/162), closes [#125](https://github.com/rohitg00/agentmemory/issues/125)). `src/functions/audit.ts` gains a top-of-file policy block: every structural deletion emits a `recordAudit` row, scoped deletions (`governance-delete`, `forget`) write one row per call, bulk sweeps (`retention-evict`, `evict`, `auto-forget`) write one batched row per invocation. `mem::forget` no longer deletes silently — it writes a single audit row with target ids, session id, and per-type counts. -- **Standalone MCP talks to the running server** ([#161](https://github.com/rohitg00/agentmemory/pull/161), closes [#159](https://github.com/rohitg00/agentmemory/issues/159)). `@agentmemory/mcp` now probes `GET /agentmemory/livez` at `AGENTMEMORY_URL` (defaults to `http://localhost:3111`) on first tool call. If the server is up, every tool (sessions, smart-search, recall, save, governance-delete, export, audit) routes through REST and sees exactly what hooks and the viewer see. If the probe fails, falls back to the local `InMemoryKV` so pure-standalone setups keep working. Bearer `AGENTMEMORY_SECRET` attached automatically. Handle cache invalidates on proxy failure with a 30s TTL so a later server start is picked up. Response shapes are now consistent across proxy and local branches. -- **Retention eviction targets the right store** ([#132](https://github.com/rohitg00/agentmemory/pull/132)). `mem::retention-evict` now routes deletes to `mem:memories` or `mem:semantic` based on the candidate's `source` field, probing both namespaces when the field is missing (legacy rows). Emits a single batched audit row per sweep with `evictedIds`, `evictedEpisodic`, `evictedSemantic`, and the threshold. Retention scores gain a `source` field persisted to the store. - -### Fixed -- **Health stops flagging `memory_critical` on tiny Node processes** ([#160](https://github.com/rohitg00/agentmemory/pull/160), closes [#158](https://github.com/rohitg00/agentmemory/issues/158)). Memory severity no longer escalates from heap ratio alone. Both warn and critical bands now require RSS above `memoryRssFloorBytes` (default 512 MB). When heap is tight but RSS is below the floor, a non-alerting `memory_heap_tight_NN%_rssMMmb` note is attached to the snapshot — visibility without the false positive. -- **iii console screenshots vendored** in the README so the docs don't depend on CDN signed URLs. - -### Infrastructure -- `VERSION` union extended to `0.9.0`; `ExportData.version`, `supportedVersions`, and `test/export-import.test.ts` bumped in lockstep. -- `@agentmemory/mcp` dependency pinned at `~0.9.0` to match. -- Tests: 777 passing (+ 14 skipped), up from 769. - -[0.9.0]: https://github.com/rohitg00/agentmemory/compare/v0.8.12...v0.9.0 - -## [0.8.13] — 2026-04-17 - -### Added - -- Session replay: new "Replay" tab in the viewer that plays any stored session as a scrubbable timeline with prompt, response, tool-call, and tool-result events. Keyboard bindings: space to play/pause, arrow keys to step, speed selector (0.5×–4×). -- JSONL transcript import via `agentmemory import-jsonl [path]` CLI subcommand and `POST /agentmemory/replay/import-jsonl`. Default path `~/.claude/projects`, or pass an explicit file/directory. Imports are recorded in the audit log. -- New iii functions `mem::replay::load`, `mem::replay::sessions`, and `mem::replay::import-jsonl`, each routed through the same HMAC-authed API trigger as other endpoints. - -### Security - -- JSONL import rejects symlinks, paths containing sensitive terms (`secret`, `credential`, `.env`, etc.), and skips malformed lines without aborting the batch. - -## [0.8.12] — 2026-04-16 - -### Added - -- Added token-efficient `memory_recall` output modes: - - `format: "full"` (default) - - `format: "compact"` (returns compact observation rows) - - `format: "narrative"` (title + narrative text for low-token recall) -- Added `token_budget` support to `memory_recall` / `mem::search` to trim results to a target budget and return `tokens_used`, `tokens_budget`, and `truncated` metadata. -- Added new MCP + REST tool `memory_compress_file` (`mem::compress-file` / `/agentmemory/compress-file`) to compress markdown files while preserving headings, URLs, and fenced code blocks. - -### Changed - -- Updated MCP tool count to 44 and REST endpoint count to 104. -- Updated docs and plugin metadata for new tool/endpoint counts. -- Added test coverage for search formats, token budget behavior, and file compression validation. - -## [0.8.11] — 2026-04-15 - -**Fix**: `node dist/index.mjs` crashed on first import after the iii-sdk v0.11 migration (#116) merged. iii-sdk v0.11 dropped `getContext()`, but 32 `src/functions/*.ts` files still imported and called it. Added `src/logger.ts` (thin stderr shim with the same `.info/.warn/.error` signature) and mechanically replaced every `ctx.logger.*` call. Updated all 45 test mock blocks. Fixed `search.ts` `registerFunction` call to use the v0.11 string-ID API. - -### Fixed - -- **iii-sdk v0.11 getContext crash** ([#116](https://github.com/rohitg00/agentmemory/issues/116)) — `SyntaxError: The requested module 'iii-sdk' does not provide an export named 'getContext'` on startup. Removed all `getContext` imports from 32 function files, added `src/logger.ts` shim, updated 45 test mock blocks. - -### Changed - -- Upgraded `iii-sdk` dependency from `^0.11.0-next.8` to stable `^0.11.0`. -- Aligned stream send payloads with v0.11 wire format by using `type` for `stream::send` events in observe/compress/session-activity paths. -- Updated migration guidance/examples and diagnostics plugin registration snippets to v0.11 function registration and trigger request shapes. -## [0.8.10] — 2026-04-15 - -**Behavior change**: the PreToolUse and SessionStart hooks no longer run enrichment by default. SessionStart saves ~1-2K input tokens per session you start (the only path that was actually reaching the model, per the [Claude Code hook docs](https://code.claude.com/docs/en/hooks.md)). PreToolUse stops spawning a Node process and POSTing to `/agentmemory/enrich` on every file-touching tool call — a pure resource cleanup, not a token fix. If you were relying on either path, set `AGENTMEMORY_INJECT_CONTEXT=true` in `~/.agentmemory/.env` and restart. Observations are still captured via PostToolUse regardless. - -### Fixed - -- **Gate SessionStart context injection** ([#143](https://github.com/rohitg00/agentmemory/issues/143), thanks [@adrianricardo](https://github.com/adrianricardo)) — `src/hooks/session-start.ts` previously wrote ~1-2K chars of project context to stdout at every session start. Per the [Claude Code hook docs](https://code.claude.com/docs/en/hooks.md), `SessionStart` stdout is explicitly injected into the model's context ("where stdout is added as context that Claude can see and act on"), so this was adding real tokens to the first turn of every new session. Now gated behind `AGENTMEMORY_INJECT_CONTEXT`, default off. The session still gets registered for observation tracking — only the stdout echo is skipped. -- **Skip the PreToolUse enrichment round-trip when disabled** ([#143](https://github.com/rohitg00/agentmemory/issues/143)) — `src/hooks/pre-tool-use.ts` was POSTing `/agentmemory/enrich` on every `Edit`/`Write`/`Read`/`Glob`/`Grep` tool call and piping up to 4000 chars to stdout. The Claude Code docs make clear that PreToolUse stdout goes to the debug log, not the model context, so this was **not** burning user tokens — but it was spawning a Node process + full HTTP round-trip ~20x per user message with no effect on the conversation. Gating it makes the disabled hot path a ~15ms no-op Node startup instead of a ~100-300ms REST round-trip. **This is a resource cleanup, not a token fix**; leaving the gate in place protects forward in case Claude Code ever changes PreToolUse to inject stdout like SessionStart does. -- **`mem::retention-evict` no longer leaks semantic memories** ([#124](https://github.com/rohitg00/agentmemory/issues/124)) — the eviction loop was unconditionally calling `kv.delete(KV.memories, id)` for every below-threshold candidate, but retention scores are computed for both episodic (`KV.memories`) and semantic (`KV.semantic`) memories. When a candidate came from `KV.semantic`, the delete silently became a no-op (key wasn't in `mem:memories` to begin with) and the semantic row stayed alive forever with a sub-threshold score. Semantic memories could not be evicted by this path at all. Fix: add a `source: "episodic" | "semantic"` discriminator to `RetentionScore`, tag it at score creation, and branch the delete on `candidate.source`. For pre-0.8.10 rows with no `source` field (including semantic retention rows written by the old scorer), the loop probes both namespaces to find where the `memoryId` actually lives, so upgraded stores get their stranded semantic memories evicted without needing to re-score first. The response shape now also includes `evictedEpisodic` and `evictedSemantic` counts for observability. -- **`mem::retention-evict` now emits an audit record per sweep** ([#124](https://github.com/rohitg00/agentmemory/issues/124)) — retention eviction performs structural deletes (memories, retention scores, access logs) but was not calling `recordAudit()`, which made evictions invisible to audit consumers. Now batched one audit row per non-zero sweep, with `operation: "delete"`, `functionId: "mem::retention-evict"`, `targetIds` containing every evicted id, and `details.evicted` / `evictedEpisodic` / `evictedSemantic` / `threshold` for context. Zero-eviction sweeps intentionally do not write an audit row. - -### Honest note on #143 - -My initial diagnosis on the #143 thread pattern-matched too quickly to #138 and overclaimed that PreToolUse stdout was the smoking gun behind "Claude Pro burned in 4 messages". It wasn't — per the docs, PreToolUse stdout is debug-log only. The actual background cause is that [Claude Pro's Claude Code quotas are documented as tight](https://www.theregister.com/2026/03/31/anthropic_claude_code_limits/) and Anthropic has publicly confirmed "people are hitting usage limits in Claude Code way faster than expected." agentmemory contributes ~1-2K tokens per session via SessionStart, and that contribution is worth eliminating, but this release does not and cannot make Claude Pro's base quotas roomier. Users on heavy tool-call workloads should consider Max 5x or Team tiers regardless of whether agentmemory is installed. - -0.8.8's #138 fix (opt-in `mem::compress` via `AGENTMEMORY_AUTO_COMPRESS`) remains the correct fix for users with `ANTHROPIC_API_KEY` set — that path was a real per-observation Claude API burn and is unrelated to the Claude Code hook pipeline. - -### Added - -- **`AGENTMEMORY_INJECT_CONTEXT` env var** — default `false`. When `true`, restores the old SessionStart stdout write and the old PreToolUse `/enrich` round-trip. Startup banner prints a loud warning when it's on, mirroring the `AGENTMEMORY_AUTO_COMPRESS` warning from 0.8.8. -- **`isContextInjectionEnabled()`** helper in `src/config.ts` — single source of truth for the flag. The hooks read the env var directly (they're spawned as standalone `.mjs` files by Claude Code and don't bootstrap through `src/index.ts`), so the helper is there for the startup banner and future code paths. -- **5 subprocess regression tests** in `test/context-injection.test.ts` — spawns the compiled `pre-tool-use.mjs` and `session-start.mjs` hooks with real stdin/stdout pipes and asserts that stdout is empty when the env var is unset, when it's explicitly `false`, and that the disabled PreToolUse path exits under 1 second. Also asserts that the opt-in path with an unreachable backend still exits cleanly. Full suite: **724 passing** (was 719 + 5 new). - -### Infrastructure - -- **Startup banner** (`src/index.ts`) now prints `Context injection: OFF (default, #143)` on normal startup and a prominent WARNING when opt-in is enabled, so the mode is never silent. -- **Migration note**: if you were relying on the old SessionStart project-context injection or the old PreToolUse enrichment round-trip, add to `~/.agentmemory/.env`: - ```env - AGENTMEMORY_INJECT_CONTEXT=true - ``` - and restart Claude Code. You'll see the startup warning in the engine logs confirming it's active. - -[0.8.10]: https://github.com/rohitg00/agentmemory/compare/v0.8.9...v0.8.10 -[0.8.12]: https://github.com/rohitg00/agentmemory/compare/v0.8.11...v0.8.12 - -## [0.8.9] — 2026-04-14 - -Two UX fixes for the Claude Code plugin install path, both reported in [#139](https://github.com/rohitg00/agentmemory/issues/139) by [@stefanfaur](https://github.com/stefanfaur). - -### Fixed - -- **Claude Code plugin now auto-wires the `@agentmemory/mcp` stdio server** ([#139](https://github.com/rohitg00/agentmemory/issues/139)) — the plugin previously only shipped hooks and skills, and the README told Claude Code users to wire up the MCP server manually. A new `plugin/.mcp.json` declares the MCP server so `/plugin install agentmemory@agentmemory` auto-starts it when the plugin is enabled. No extra config step. -- **Skills no longer fail under Claude Code's sandbox with "Contains expansion"** ([#139](https://github.com/rohitg00/agentmemory/issues/139)) — the `recall` and `session-history` skills used pre-execution bash with `$(...)` / `${VAR:-default}` shell expansion, which Claude Code's sandbox rejects by pattern match. All four plugin skills (`recall`, `remember`, `forget`, `session-history`) are now rewritten as pure prompts that tell Claude to use the MCP tools directly. No bash, no sandbox issues, no shell escaping — and the skills run faster because they no longer fork a curl subprocess on every invocation. - -### Added - -- **Standalone MCP shim now implements `memory_smart_search` and `memory_governance_delete`** — the `@agentmemory/mcp` stdio server only exposed 5 tools (`memory_save`, `memory_recall`, `memory_sessions`, `memory_export`, `memory_audit`), so the rewritten plugin skills would have failed at runtime referencing tools the standalone didn't know about. Now ships 7 tools. `memory_smart_search` falls back to the same substring filter as `memory_recall` since the standalone shim doesn't have BM25/vector/graph without the full engine. `memory_governance_delete` takes `memoryIds` as an array or comma-separated string and returns `{deleted, requested, reason}`. -- **`memory_save` accepts `concepts`/`files` as arrays or comma-separated strings** — the old standalone only accepted CSV strings, which would silently drop array inputs. New `normalizeList()` helper handles both. -- **`memory_sessions` honours a `limit` arg** (default 20) — previously returned every session. -- **8 regression tests** in `test/mcp-standalone.test.ts` covering array/CSV inputs for `memory_save`, `memory_smart_search` substring fallback, `memory_sessions` limit, `memory_governance_delete` happy path + unknown-id skip + validation. Full suite: 715 passing. - -### Changed - -- **README Claude Code install snippet** — now explicitly notes that `/plugin install agentmemory` registers hooks + skills AND auto-wires the MCP server via `.mcp.json`, with no extra step. - -[0.8.9]: https://github.com/rohitg00/agentmemory/compare/v0.8.8...v0.8.9 - -## [0.8.8] — 2026-04-14 - -**Behavior change**: per-observation LLM compression is now opt-in. If you were relying on LLM-generated summaries (the old default), set `AGENTMEMORY_AUTO_COMPRESS=true` in `~/.agentmemory/.env` and restart. - -### Fixed - -- **Stop silently burning Claude API tokens on every tool invocation** ([#138](https://github.com/rohitg00/agentmemory/issues/138), thanks [@olcor1](https://github.com/olcor1)) — the old `mem::observe` path fired `mem::compress` unconditionally on every PostToolUse hook, which called Claude via the user's `ANTHROPIC_API_KEY` to turn each raw observation into a structured summary. An active coding session (50-200 tool calls/hour) could run through hundreds of thousands of tokens in minutes, which is the exact opposite of what a memory tool should do. The new default path skips the LLM call and uses a zero-token **synthetic compression** step that derives `type`, `title`, `narrative`, and `files` from the raw tool name, tool input, and tool output directly. Recall and BM25 search still work — you just lose the LLM-generated summaries unless you opt in. - -### Added - -- **`AGENTMEMORY_AUTO_COMPRESS` env var** — default `false`. When `true`, restores the old per-observation LLM compression path. The engine startup banner now prints a loud warning when it's on, reminding you that it spends tokens proportional to your session tool-use frequency. -- **`src/functions/compress-synthetic.ts`** — the new zero-LLM compression helper. `buildSyntheticCompression(raw)` maps tool names to `ObservationType` (via camelCase-aware substring matching for `Read`/`Write`/`Edit`/`Bash`/`Grep`/`WebFetch`/`Task`/etc.), pulls file paths out of `tool_input.file_path` / `pattern` / etc., and truncates narratives to 400 chars so one huge tool output can't blow up the BM25 index. -- **Regression test** `test/auto-compress.test.ts` — 8 cases covering the default path (no `mem::compress` trigger, synthetic observation stored in KV), explicit opt-in, tool-name-to-type mapping, file-path extraction, narrative truncation, and the `post_tool_failure` → `error` path. Full suite: 707 passing. - -### Infrastructure - -- **Startup banner** (`src/index.ts:171`) now prints either `Auto-compress: OFF (default, #138)` or a prominent warning when opt-in is enabled, so the mode is never silent. -- **Migration note**: if you were running 0.8.7 or earlier with `ANTHROPIC_API_KEY` set, your token usage will drop sharply on upgrade. Search quality may also drop slightly because narratives are now derived from raw tool I/O instead of Claude-generated summaries. If you want the old behavior: - ```env - # ~/.agentmemory/.env - AGENTMEMORY_AUTO_COMPRESS=true - ``` - and restart. Existing compressed observations in `~/.agentmemory/` are untouched. - -[0.8.8]: https://github.com/rohitg00/agentmemory/compare/v0.8.7...v0.8.8 - -## [0.8.7] — 2026-04-14 - -One-line fix for a brown-paper-bag bug reported in [#136](https://github.com/rohitg00/agentmemory/issues/136). - -### Fixed - -- **`npx @agentmemory/agentmemory` no longer crashes with "`/app/config.yaml` is a directory"** ([#136](https://github.com/rohitg00/agentmemory/issues/136), thanks [@stefano-medapps](https://github.com/stefano-medapps)) — the published tarball shipped `docker-compose.yml` but **not** `iii-config.docker.yaml`, even though the compose file mounts `./iii-config.docker.yaml:/app/config.yaml:ro`. Docker resolves missing host-path bind sources by silently creating them as empty directories, so the iii-engine container mounted an empty dir at `/app/config.yaml` and crashed with `Error: Failed to read config file '/app/config.yaml': Is a directory (os error 21)`. The `files` array in `package.json` now includes `iii-config.docker.yaml` alongside the regular `iii-config.yaml`. - -### Infrastructure - -- New regression test in `test/consistency.test.ts` parses every `./:` bind mount in `docker-compose.yml` and asserts the source file is shipped via the `files` array. Catches the class of bug where a new bind mount is added to compose without a corresponding entry in `files`. - -[0.8.7]: https://github.com/rohitg00/agentmemory/compare/v0.8.6...v0.8.7 - -## [0.8.6] — 2026-04-13 - -Finishes the `npx ` story from #120 by moving the standalone package under the `@agentmemory` scope. - -### Changed - -- **Standalone MCP shim is now `@agentmemory/mcp`** — the 0.8.5 publish attempted to push `agentmemory-mcp` as an unscoped package, but npm's name-similarity policy rejects it because of an unrelated third-party package called `agent-memory-mcp`. The shim now lives under the scope we already own, so `npx -y @agentmemory/mcp` works on the live registry. All README/integration/CLI-help snippets, the OpenClaw and Hermes guides, and the Claude-Desktop/Cursor/Codex/OpenCode MCP config examples have been updated to use the scoped name. The unscoped `agentmemory-mcp` command line (in the main package's `bin` field) was never published and has been removed from the docs. -- **Package directory renamed** `packages/agentmemory-mcp/` → `packages/mcp/`. The `.github/workflows/publish.yml` publish step points at the new path and `npm view @agentmemory/mcp` for the propagation check. -- **Log prefix** in `src/mcp/standalone.ts` and `src/mcp/in-memory-kv.ts` changed from `[agentmemory-mcp]` to `[@agentmemory/mcp]` so stderr output matches the package users install. - -### Fixed - -- **Shim version bump was missed in 0.8.5** — `packages/agentmemory-mcp/package.json` (now `packages/mcp/package.json`) was still pinned at `0.8.4` because the release bump script only touched the 8 files in the main package. The shim now tracks the main package and depends on `@agentmemory/agentmemory: ~0.8.6`. - -[0.8.6]: https://github.com/rohitg00/agentmemory/compare/v0.8.5...v0.8.6 - -## [0.8.5] — 2026-04-13 - -Compatibility fix for stricter JSON-RPC clients, plus a spec cleanup CodeRabbit caught during review. - -### Fixed - -- **MCP server works with Codex CLI and any strict JSON-RPC 2.0 client** ([#129](https://github.com/rohitg00/agentmemory/issues/129)) — the stdio transport was responding to JSON-RPC **notifications** (messages without an `id` field, e.g. `notifications/initialized`), which violates JSON-RPC 2.0 §4.1 and caused stricter clients like Codex CLI v0.120.0 to close the transport with "Transport closed". Notifications are now detected by the missing/null `id` field, the handler still runs for side effects, but no response is written. Handler errors on notifications are logged to stderr instead of sent back to the client. Claude Code and other clients that tolerated the spurious responses continue to work unchanged. -- **Request `id` type validation per JSON-RPC 2.0 §4** — the transport previously only checked `id != null`, so a malformed request with `id: {}` or `id: [1,2]` could get echoed back with that non-primitive id, and valid-shape requests with bad id types fell through to the handler and produced a response carrying a bogus non-JSON-RPC id. `isValidId()` now enforces `string | number | null | undefined`, and bad-id requests get `-32600 Invalid Request` with `id: null` before the handler runs. Caught by CodeRabbit on PR [#131](https://github.com/rohitg00/agentmemory/pull/131). - -### Infrastructure - -- 14 tests in `test/mcp-transport.test.ts` covering the request path, notification path (#129), malformed input, and id-type validation (object/array/boolean). Full suite: 698 passing. - -[0.8.5]: https://github.com/rohitg00/agentmemory/compare/v0.8.4...v0.8.5 - -## [0.8.4] — 2026-04-13 - -Two community contributions land on top of 0.8.3 and close out the #120 npm story for real. - -### Fixed - -- **Memories saved via the standalone MCP server now survive SIGKILL** ([#122](https://github.com/rohitg00/agentmemory/pull/122), thanks [@JasonLandbridge](https://github.com/JasonLandbridge)) — `memory_save` previously only flushed to `~/.agentmemory/standalone.json` on `SIGINT`/`SIGTERM`. If the MCP server process was killed forcefully (e.g. when an agent session ended), every memory saved during that session was lost. The save handler now persists to disk immediately after every `memory_save` call, so data survives unexpected termination. Also switched to the shared `generateId("mem")` helper and a single `isoNow` shared by `createdAt`/`updatedAt` so they can't drift. -- **OpenCode MCP config format corrected** ([#121](https://github.com/rohitg00/agentmemory/pull/121), thanks [@JasonLandbridge](https://github.com/JasonLandbridge)) — the README previously told OpenCode users to edit `.opencode/config.json` with an `mcpServers` object, but OpenCode actually uses `opencode.json` with an `mcp` object, `type: "local"`, and a `command` array. The agents table row and a new dedicated OpenCode block in the Standalone MCP section now document the correct format. - -## [0.8.3] — 2026-04-13 - -Two bug fixes reported in the public issue tracker. - -### Fixed - -- **Retention score now reflects real agent-side reads** ([#119](https://github.com/rohitg00/agentmemory/issues/119)) — `mem::retention-score` previously hardcoded `accessCount = 0` and `accessTimestamps = []` for episodic memories, and only used a single-sample `lastAccessedAt` for semantic memories. Reads from `mem::search`, `mem::smart-search`, `mem::context`, `mem::timeline`, `mem::file-context`, and the matching MCP tools (`memory_recall`, `memory_smart_search`, `memory_timeline`, `memory_file_history`) were never recorded, so the time-frequency decay formula was a dead path. The reinforcement boost is now driven by a real per-memory access log persisted at `mem:access`, written by every read endpoint (fire-and-forget, so reads never block on tracker writes), with a bounded ring buffer of the last 20 access timestamps. Pre-0.8.3 semantic memories that only have the legacy `lastAccessedAt` field still score correctly via a backwards-compat fallback. -- **`npx agentmemory-mcp` 404** ([#120](https://github.com/rohitg00/agentmemory/issues/120)) — the README told users to run `npx agentmemory-mcp` for MCP client setup, but `agentmemory-mcp` was only a `bin` entry inside `@agentmemory/agentmemory`, not a real package, so `npx` returned 404 from the npm registry. Two fixes: - - Published a new sibling package `agentmemory-mcp` (in `packages/agentmemory-mcp/`) that is a thin shim over `@agentmemory/agentmemory/dist/standalone.mjs`. `npx agentmemory-mcp` now works as documented. - - Added a canonical `npx @agentmemory/agentmemory mcp` subcommand to the main CLI for users who already have `@agentmemory/agentmemory` installed and don't want a second package on disk. Both commands do the same thing. - - README install snippets now use `npx -y agentmemory-mcp` so first-time users skip the install confirmation prompt. - -### Added - -- **Concurrent access tracking is race-safe** — the access log RMW is wrapped in the existing `withKeyedLock` keyed mutex, so two parallel reads of the same memory don't lose increments. `recordAccessBatch` uses `Promise.allSettled` so a slow keyed-lock acquisition on one id doesn't block the rest of the batch. -- **`mem::export` / `mem::import` now round-trip the access log** — the new `mem:access` namespace is included in dumps and restored on import, so backup/restore cycles no longer silently zero out reinforcement signals. -- **`exports` field in `package.json`** — explicitly exposes `./dist/standalone.mjs` as a subpath so the shim package and external consumers have a stable contract. -- **CI publishes both packages on release** — `.github/workflows/publish.yml` now publishes `@agentmemory/agentmemory` first, then the `agentmemory-mcp` shim from `packages/agentmemory-mcp/` so `npx agentmemory-mcp` works on the live release. - -## [0.8.2] — 2026-04-12 - -This release ships 6 security fixes, growth features, and a visual redesign of the README. Users on v0.8.1 should upgrade as soon as possible — the security fixes address vulnerabilities in default deployments. - -### Security - -Six vulnerabilities fixed, originally introduced before v0.8.1: - -- **[CRITICAL] Stored XSS in the real-time viewer** — viewer HTML used inline `onclick=` handlers while the CSP allowed `script-src 'unsafe-inline'`. User-controlled tool outputs could execute JavaScript in the reader's browser. Fixed by removing all inline event handlers, adding delegated `data-action` handling, switching to a per-response nonce-based CSP, and adding `script-src-attr 'none'`. -- **[CRITICAL] `curl | sh` in CLI startup** — the CLI auto-installed iii-engine via `execSync("curl -fsSL https://install.iii.dev/iii/main/install.sh | sh")`. Removed entirely. The CLI now uses an existing local `iii` binary if available, or falls back to Docker Compose. Users install iii-engine manually via `cargo install iii-engine` or Docker. -- **[HIGH] Default `0.0.0.0` binding** — `iii-config.yaml` bound REST (3111) and streams (3112) to all interfaces, exposing the memory store to anyone on the local network. Now binds to `127.0.0.1` by default. A separate `iii-config.docker.yaml` handles the Docker case with host port mapping restricted to `127.0.0.1:port`. -- **[HIGH] Unauthenticated mesh sync** — mesh push/pull endpoints accepted requests without an `Authorization` header. Mesh endpoints now require `AGENTMEMORY_SECRET`, and outgoing mesh sync requests send `Authorization: Bearer `. -- **[MEDIUM] Path traversal in Obsidian export** — the `vaultDir` parameter was passed directly to `mkdir`/`writeFile`, allowing writes to any filesystem path (e.g., `/etc/cron.d`). Exports are now confined to `AGENTMEMORY_EXPORT_ROOT` (default `~/.agentmemory`) via `path.resolve` + `startsWith` containment check. -- **[MEDIUM] Incomplete secret redaction** — the privacy filter missed `Bearer ...` tokens, OpenAI project keys (`sk-proj-*`), and GitHub fine-grained service tokens (`ghs_`, `ghu_`). Added regex coverage for all three formats. - -See GitHub Security Advisories for CVSS scores and affected version ranges. - -### Added - -- **`agentmemory demo` CLI command** — seeds 3 realistic sessions (JWT auth, N+1 query fix, rate limiting) and runs smart-search queries against them. Shows semantic search finding "N+1 query fix" when you search "database performance optimization" — the kind of result keyword matching can't produce. Zero config, 30 seconds, no integration needed. -- **`benchmark/COMPARISON.md`** — head-to-head comparison vs mem0 (53K⭐), Letta/MemGPT (22K⭐), Khoj (34K⭐), claude-mem (46K⭐), and Hippo. 18-dimension feature matrix, honest LongMemEval vs LoCoMo caveats, token efficiency table. -- **`integrations/openclaw/`** — OpenClaw gateway plugin with 4 lifecycle hooks (`onSessionStart`, `onPreLlmCall`, `onPostToolUse`, `onSessionEnd`). Same pattern as the existing Hermes integration. Includes README with paste-this-prompt block, `plugin.yaml`, and `plugin.mjs`. -- **Token savings dashboard** — `agentmemory status` now shows cumulative token savings and dollar cost saved (`$0.30/1K tokens` rate). Same card added to the real-time viewer on port 3113. -- **Paste-this-prompt blocks** — main README and both integration READMEs now open with a copy-pasteable text block users drop into their agent. The agent handles the entire setup (start server, update MCP config, verify health, open viewer). -- **60 custom SVG tags** — 30 dark-bg + 30 light-bg variants under `assets/tags/` and `assets/tags/light/`. Covers 14 section headers, 6 stat cards, 8 pill tags, and utility badges. GitHub README uses `` elements to auto-swap based on reader theme (dark theme → light-bg SVGs, light theme → dark-bg SVGs). -- **Real agent logos** in the Supported Agents grid — 16 agents with clickable brand logos (Claude Code, OpenClaw, Hermes, Cursor, Gemini CLI, OpenCode, Codex CLI, Cline, Goose, Kilo Code, Aider, Claude Desktop, Windsurf, Roo Code, Claude SDK, plus "any MCP client"). - -### Changed - -- README redesigned from plain markdown headers to SVG-tagged sections matching the agentmemory brand palette (orange `#FF6B35 → #FF8F5E` accent on dark `#1A1A1A` background). -- Hero stat row replaced with 6 custom SVG stat cards showing 95.2% R@5, 92% fewer tokens, 43 MCP tools, 12 auto hooks, 0 external DBs, 654 tests passing. -- Supported Agents grid reordered: Claude Code, OpenClaw, and Hermes now lead the first row (the 3 agents with first-class integrations in `integrations/`). -- Viewer token savings card now shows dollar cost saved alongside raw token count. -- Default configuration files updated: `iii-config.yaml` binds to `127.0.0.1`, new `iii-config.docker.yaml` for Docker deployments. - -### Fixed - -- **Viewer cost calculation was 100x under-reporting** — the formula `tokensSaved / 1000 * 0.3` returns dollars but was treated as cents. Now computes `costDollars` first, then `costCents = Math.round(costDollars * 100)`. 100K tokens now correctly displays `$30.00` instead of `30ct`. -- **`ObservationType` union missing `"image"`** — `VALID_TYPES` in `compress.ts` included `"image"` but the TypeScript union in `types.ts` didn't, breaking exhaustive checks. -- **Dynamic imports inside eviction loops** — `auto-forget.ts` and `evict.ts` called `await import("../utils/image-store.js")` inside nested loops. Hoisted once at the top of each function. -- **OpenClaw `/agentmemory/context` payload** — plugin was sending `{ tokenBudget, query, minConfidence }` but the endpoint expects `{ sessionId, project, budget? }`. Fixed to match the server contract. -- **Cursor cell in README grid** was missing its `Cursor` label. -- Codex CLI logo URL returned 404 from simple-icons CDN. Switched to GitHub org avatars for all logos for maximum reliability. - -### Infrastructure - -- 654 tests (up from 646 in v0.8.1), including 8 new tests covering viewer security, mesh auth, privacy redaction, and export confinement. -- All 60 custom SVGs validated with `xmllint` in CI-ready fashion. -- README consistency check updated to match new tool counts. - ---- - -## [0.8.1] — 2026-04-09 - -- Fix viewer not found when installed via npx (#109) - -## [0.8.0] — 2026-04-09 - -- Initial 0.8.x release - ---- - -[0.8.4]: https://github.com/rohitg00/agentmemory/compare/v0.8.3...v0.8.4 -[0.8.3]: https://github.com/rohitg00/agentmemory/compare/v0.8.2...v0.8.3 -[0.8.2]: https://github.com/rohitg00/agentmemory/compare/v0.8.1...v0.8.2 -[0.8.1]: https://github.com/rohitg00/agentmemory/compare/v0.8.0...v0.8.1 -[0.8.0]: https://github.com/rohitg00/agentmemory/releases/tag/v0.8.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 026795a7..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,48 +0,0 @@ -# Code of Conduct - -agentmemory follows the [Contributor Covenant v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/). - -The short version: - -- Be kind. Assume good faith. -- Disagree on the idea, not the person. -- Harassment — in issues, PRs, discussions, or any other project space — is not tolerated. -- Unwelcome behavior gets moderated first by reminder, then by time-out, then by removal. - -## Enforcement - -Reports go to **ghumare64@gmail.com** with subject `agentmemory CoC`. All reports are confidential. - -Responses follow the Covenant's enforcement ladder — correction, warning, temporary ban, permanent ban — and are decided by the project Maintainers listed in [MAINTAINERS.md](./MAINTAINERS.md). Where a Maintainer is a party to the report, that Maintainer recuses. - -### Escalation when no impartial Maintainer is available - -If every listed Maintainer recuses, or if the project is operating with a single Maintainer and the report concerns that Maintainer, the report is forwarded to an external neutral contact for independent adjudication. The current fallback chain, in order: - -1. **Contributor Covenant community ombudsperson** — email `ombudsperson@contributor-covenant.org` (see ). -2. **Hosting foundation abuse channel** — when agentmemory is accepted into a foundation (see `GOVERNANCE.md`), reports can be routed to that foundation's conduct committee instead. The current contact will be published here at that time. -3. **GitHub Trust & Safety** — for conduct that occurs inside GitHub spaces, the report can also be filed through . - -The external contact receives the original report verbatim (redacted only of third-party PII unrelated to the incident) and decides the enforcement step. The Maintainer body executes whatever enforcement action the external contact recommends. This ensures no report can dead-end because every internal reviewer is conflicted. - -## Scope - -This applies to every project space: - -- GitHub issues, PRs, discussions, and reviews on this repo. -- Any official chat channel that gets set up (currently none). -- Public representation of the project at conferences, meetups, and on social media. - -## Full text - -Reproduced verbatim from the Contributor Covenant 2.1 for convenience: - -> We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. -> -> We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. - -Full Covenant v2.1 text: - -## Attribution - -Contributor Covenant is licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 99ed0333..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,115 +0,0 @@ -# Contributing to agentmemory - -Thanks for taking an interest. This file is the short path from "I have an idea" to "it's in main." - -## Ground rules - -- Apache-2.0 license applies to every contribution. -- Sign-off is required on every commit (see [DCO](#developer-certificate-of-origin) below). -- Be civil. [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) applies. -- No attribution headers ("Generated with Claude Code", "Co-Authored-By: Claude", etc.) in commits or PR descriptions. - -## Before you open an issue - -Search existing issues first: - -- [open issues](https://github.com/rohitg00/agentmemory/issues?q=is%3Aissue+is%3Aopen) -- [closed issues](https://github.com/rohitg00/agentmemory/issues?q=is%3Aissue+is%3Aclosed) - -If it's a bug: provide the repro steps, your Node version, OS, agentmemory version (`npm view @agentmemory/agentmemory version`), and what you expected vs. what you saw. - -If it's a feature: describe the user problem before the implementation. "I couldn't X because Y" beats "please add X." - -## Before you open a PR - -1. Fork the repo and create a branch off `main`: - - `feat/` for features - - `fix/-` for bug fixes - - `docs/`, `refactor/`, `chore/` for the rest -2. `npm install` — you need Node >=20. -3. `npm run pre-pr` — one command that runs the local consistency check, then `npm run build`, then `npm test` (~12s total). This mirrors what CI gates on. Run it before every push. - - Equivalent to running the three steps by hand: `npm run check:consistency-local` (catches mismatched MCP-tool / REST-endpoint / version counts in seconds, before the 4-cell CI does ~7 min later) → `npm run build` (TypeScript must compile clean) → `npm test` (full suite must pass). - - The one integration test under `test/integration.test.ts` needs a live server on `:3111` and is fine to skip locally. -4. Commit with sign-off. Rebase over tiny fixup commits so the history stays readable. - -## Pull request flow - -- Keep PRs small and focused. One logical change per PR. -- Substantial new features need a reviewed feature doc first — copy [`docs/features/template.md`](docs/features/template.md), get it signed off, then open the implementation PR linking it (see [`docs/features/index.md`](docs/features/index.md)). Bug fixes and small obvious changes don't need one. -- Write a clear description: what it does, why, and how to verify. -- Link the issue the PR resolves (`Fixes #NNN` / `Closes #NNN`). -- Address review feedback in new commits (do not force-push to the same branch). Maintainers may squash on merge. -- A maintainer will merge when tests pass and any review comments are addressed. - -## Developer Certificate of Origin - -Every commit must carry a `Signed-off-by` trailer stating you have the right to submit the contribution under Apache-2.0. The full text of the DCO is at . - -Add it automatically: - -```bash -git commit -s -m "feat: your message" -``` - -PRs with commits lacking sign-off will not merge. - -## Coding style - -- TypeScript strict mode. No `any` unless justified in a comment. -- Prettier-compatible formatting (editor on save is fine; no repo-wide hook). -- No code comments that restate what the code does. Only write a comment when the *why* is non-obvious — a hidden constraint, an invariant, a workaround for a specific bug. -- No dead code, no commented-out imports. -- Tests live next to the feature in `test/.test.ts`. Name the test after the behavior, not the implementation. - -## Subsystems at a glance - -| Directory | What lives here | -|-|-| -| `src/triggers/api.ts` | Every HTTP endpoint under `/agentmemory/*`. Adding an MCP tool? Add the REST twin here too. | -| `src/mcp/` | Standalone MCP server (`@agentmemory/mcp`), tools registry, transport, in-memory KV. | -| `src/functions/` | Core memory operations — observe, compress, consolidate, retention, forget, graph, smart-search, export-import, governance. | -| `src/hooks/` | The 12 auto-hooks that capture sessions in agents. | -| `src/health/` | Liveness + readiness + alert thresholds. | -| `src/state/` | KV schema, keyed mutex, access log. | -| `plugin/` | Claude Code plugin (`agentmemory@agentmemory`). | -| `website/` | Marketing site (Next.js 16). | -| `test/` | Vitest test suite. | - -## Adding an MCP tool - -1. Register the function in `src/functions/.ts`. -2. Register the HTTP trigger in `src/triggers/api.ts` with a matching `api_path`. -3. Add the tool entry in `src/mcp/tools-registry.ts`. -4. Implement in `src/mcp/standalone.ts` if the standalone MCP package should also expose it. -5. Write a test under `test/`. -6. No CHANGELOG touch in the PR itself — release PRs are the only place CHANGELOG changes. - -## Adding an auto-hook - -1. Add the new `HookType` string to the union in `src/types.ts`. -2. Wire the handler in `src/hooks/.ts`. -3. Add a Vitest case that fires the hook and asserts the observation gets written. - -## Release process - -Maintainers cut releases. Every bump touches 8 files in lockstep: - -1. `package.json` -2. `package-lock.json` (top + `packages[""].version`) -3. `plugin/.claude-plugin/plugin.json` -4. `packages/mcp/package.json` (self + `~x.y.z` pin on the main package) -5. `src/version.ts` (extend the union, assign) -6. `src/types.ts` (`ExportData.version` union) -7. `src/functions/export-import.ts` (`supportedVersions` Set) -8. `test/export-import.test.ts` (assertion) - -Then: CHANGELOG section, PR, merge, tag, GitHub release. The `Publish to npm` workflow picks up the release trigger and publishes `@agentmemory/agentmemory` and `@agentmemory/mcp` to npm with provenance. - -## Security issues - -Do not open a public issue for a security report. See [SECURITY.md](./SECURITY.md). - -## Questions - -- Implementation questions: open a GitHub Discussion. -- Governance questions: open an issue labeled `governance`. See [GOVERNANCE.md](./GOVERNANCE.md). diff --git a/GOVERNANCE.md b/GOVERNANCE.md deleted file mode 100644 index 16f505cf..00000000 --- a/GOVERNANCE.md +++ /dev/null @@ -1,88 +0,0 @@ -# Governance - -This document describes how decisions are made in the agentmemory project. - -The model here is a near-copy of the [Linux Foundation Minimum Viable Governance (MVG)](https://github.com/todogroup/ospolog/blob/main/governance/minimum-viable-governance.md) pattern, scoped to the project's current single-maintainer reality with a concrete plan to diversify maintainership over the next two release cycles. - -## Mission - -Ship a persistent, local-first memory runtime for AI coding agents that: - -- Requires zero external databases. -- Runs under any MCP-compatible client. -- Stays compatible with the open [Model Context Protocol](https://modelcontextprotocol.io). -- Keeps every user's data on the user's machine by default. - -## Roles - -### Users - -Anyone who runs agentmemory. No process obligation beyond the license. Feedback via [GitHub issues](https://github.com/rohitg00/agentmemory/issues) and [discussions](https://github.com/rohitg00/agentmemory/discussions) is the input channel. - -### Contributors - -Anyone who opens an issue, comments on an issue, opens a pull request, or otherwise helps the project. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the how-to. - -### Maintainers - -A Maintainer has commit access to the repository, responsibility for reviewing PRs, and a vote on project-level decisions. The current list is tracked in [MAINTAINERS.md](./MAINTAINERS.md). - -A Maintainer is expected to: - -- Respond to PRs they are review-owner for within a reasonable window (goal: 3 working days for first comment). -- Uphold the [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). -- Avoid merging their own non-trivial PRs without a second reviewer once the maintainer count is greater than one. -- Disclose conflicts of interest (employer, paid relationships to users). - -### Maintainer acceptance process - -A Contributor becomes a Maintainer by: - -1. Sustained, high-signal contributions over the prior 6 months (multiple merged PRs across more than one subsystem, plus review comments on others' PRs). -2. A Maintainer nominates the Contributor in a public PR editing `MAINTAINERS.md`. -3. The PR stays open for 7 calendar days to collect objections. -4. If no standing objection from an existing Maintainer, the PR merges and the new Maintainer is added. - -A Maintainer steps down by opening a PR that moves their entry to the `Emeritus` section. This is always accepted. - -## Decision-making - -### Default: lazy consensus on PRs - -Most decisions happen inside pull requests. A PR merges when any Maintainer approves it and no other Maintainer blocks it. Silence is assent after 72 hours of no objection. - -### Non-PR decisions - -Anything that is not a normal code change — charter changes, governance edits, maintainer additions/removals, project scope, breaking API changes, relicensing — happens in a GitHub Issue labeled `governance` with a proposal in the first comment. - -- Minor scope decisions: rough consensus in the issue thread, captured by a Maintainer in a summary comment. -- Formal votes: Maintainers react `+1` / `-1` / `0` to the summary comment. Simple majority of Maintainers with a minimum of two distinct voters carries. If only one Maintainer exists, a 7-day public comment window substitutes for a vote. - -### Breaking changes - -A breaking change to the REST / MCP surface requires: - -1. A tracking issue labeled `breaking` opened at least one minor release cycle ahead of the change. -2. A deprecation path in the codebase (warning log, feature flag, or adapter) for at least one minor release. -3. The change landing in the CHANGELOG under a clearly marked `Breaking` sub-section. - -## Release process - -Releases follow [Semantic Versioning](https://semver.org). See the [release process](./CONTRIBUTING.md#release-process) in `CONTRIBUTING.md` and the automated `.github/workflows/publish.yml` pipeline for the mechanics. - -## Conflicts of interest - -Maintainers employed by a company that sells a product competing with agentmemory, or by a company whose business depends on agentmemory's roadmap, should disclose that relationship in `MAINTAINERS.md` next to their name. Nothing prohibits such maintainership; transparency is the requirement. - -## Amending this document - -This document changes by PR. Edits follow the Non-PR decisions path above: open a `governance` issue, collect feedback, then open the PR citing the issue. - -## Related documents - -- [LICENSE](./LICENSE) — Apache-2.0 -- [CONTRIBUTING.md](./CONTRIBUTING.md) — how to contribute -- [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) — community behavior -- [SECURITY.md](./SECURITY.md) — how to report a vulnerability -- [MAINTAINERS.md](./MAINTAINERS.md) — who has commit access -- [ROADMAP.md](./ROADMAP.md) — where the project is heading diff --git a/LICENSE b/LICENSE index cb13c728..77404bbc 100644 --- a/LICENSE +++ b/LICENSE @@ -106,7 +106,7 @@ (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained - within such NOTICE file, excluding any notices that do not + within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or @@ -175,7 +175,18 @@ END OF TERMS AND CONDITIONS - Copyright 2026 Rohit Ghumare + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/MAINTAINERS.md b/MAINTAINERS.md deleted file mode 100644 index c6ee2e65..00000000 --- a/MAINTAINERS.md +++ /dev/null @@ -1,21 +0,0 @@ -# Maintainers - -The authoritative list of people with commit access. See [GOVERNANCE.md](./GOVERNANCE.md) for what a Maintainer is, what they do, and how someone becomes one. - -## Active - -| Name | GitHub | Affiliation | Area of focus | Since | -|-|-|-|-|-| -| Rohit Ghumare | [@rohitg00](https://github.com/rohitg00) | Independent | Project lead, all subsystems | 2026-01 | - -## Emeritus - -_None yet._ - -## Maintainer recruitment - -agentmemory is actively looking to diversify maintainership. The growth plan in [ROADMAP.md](./ROADMAP.md) commits to adding at least one additional Maintainer from a different organization by the end of the current growth cycle. - -If you have a sustained contribution track record and would like to be considered, open an issue tagged `governance`. - -The complete contributor graph, with commit counts and recent activity, lives at . diff --git a/README.md b/README.md index 4253c999..83860c8c 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,108 @@ -# AI Todo +# AI-Todo -**AI Todo** is a local-first todo extraction tool for AI workflows. +AI-Todo is a local-first action inbox for AI agent sessions. It scans local +Codex and Claude Code session logs, accepts browser sessions through the local +HTTP API, uses an OpenAI-compatible LLM to organize unfinished work into todo +cards, and keeps source evidence available for review. -It scans local agent sessions and captures browser AI conversations, extracts the unfinished work, stores everything locally, and shows it in a simple local web UI — answering one practical question: +## Requirements -> What did my AI agents leave unfinished, and what should I review next? +- Node.js 24 or newer +- An OpenAI-compatible chat completions endpoint and API key -People work across coding agents, browser AI assistants, and project tools, and useful follow-ups stay buried in conversations: an agent waiting for confirmation, a failed command that blocked a task, a draft never reviewed, a plan never turned into an issue. AI Todo turns these open loops into local, reviewable todo candidates **with evidence**. +AI-Todo does not fall back to rule-based todo generation. Without a configured +LLM key, the app can start and scan sessions, but organize runs cannot create +todo cards. -> **Status: internal trial / prototype.** It runs end to end locally. Some package names, CLI commands, and API paths still use the earlier implementation name while the project is being renamed to AI Todo. The UI defaults to English for open-source use, with Chinese available from Settings or `?lang=zh`. - -## Quick start - -Requirements: **Node.js 20+**. +## Quick Start ```bash npm install npm run build -npm run start:local-memory +AI_TODO_HOME=.local/ai-todo node dist/cli.js init --api-key +AI_TODO_HOME=.local/ai-todo node dist/cli.js doctor +AI_TODO_HOME=.local/ai-todo node dist/cli.js open ``` -The daemon prints a local viewer URL (default REST API on port **3111**). Open it in your browser. +The UI listens on [http://127.0.0.1:3111/](http://127.0.0.1:3111/) by default. +Use `node dist/cli.js open --port ` to choose another port. If `3111` is +already occupied, AI-Todo reports the conflict instead of silently switching +ports. -**Try it with sample data** — in another terminal: - -```bash -node dist/cli.mjs demo -``` +`npm start` runs the default CLI command, which is `doctor`. Use +`node dist/cli.js open` to start the UI. -Then refresh the viewer: the dashboard fills with browsable sessions, memory, and extracted todos. The **To-Do** tab shows the extracted todos (each with evidence); the **Evidence** tab shows the sessions they came from. +## First Use -> Prefer running from source while developing? `npm run dev` (via `tsx`) instead of build + start. +After the UI is running: -## Use your own data +1. Scan Codex or Claude Code sessions from the UI, or run `scan` from the CLI. +2. Run `organize` to ask the configured LLM to create todo cards. +3. Review each todo card and open its evidence before marking it done or ignored. -- **Codex sessions (local):** the daemon scans your Codex session directories (`~/.codex/sessions` and `~/.codex/archived_sessions`) on startup and on an interval. Toggle with `AGENTMEMORY_CODEX_AUTOSCAN=false`; tune the cadence with `AGENTMEMORY_CODEX_SCAN_INTERVAL_MS` (default 5 min). You can also import a transcript on demand from a source checkout with `node dist/cli.mjs import-jsonl ` (installed package binary: `agentmemory-lab import-jsonl `). -- **Browser AI conversations:** load the browser extension under [`browser-extension/`](browser-extension/); it captures supported AI sites and posts them to the local daemon, which extracts todos into the same queue. -- **LangExtract extraction (optional):** `npm install` creates a project-local `.agentmemory-python` environment and installs the Python deps from `requirements-langextract.txt` (includes SOCKS proxy support). Configure it during first-run setup (`node dist/cli.mjs --reset` from source, or `agentmemory-lab --reset` when installed), from the viewer Settings panel, or in `~/.agentmemory/.env`: `AGENTMEMORY_TODO_EXTRACTOR=langextract`, `LANGEXTRACT_PROVIDER=openai`, `LANGEXTRACT_MODEL=deepseek/deepseek-v4-flash`, `LANGEXTRACT_BASE_URL=https://api.novita.ai/openai/v1`, `LANGEXTRACT_API_KEY=`, and optionally `AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS=120000`. Set `LANGEXTRACT_PYTHON=/path/to/python` only when you want to override the managed environment. Trigger it from the To-Do tab or with `POST /agentmemory/todo-extract/generate`. -- **Isolated data roots:** set `AGENTMEMORY_HOME=/path/to/data` to keep AI Todo runtime files (`.env`, preferences, pidfiles, snapshots) separate from your normal `~/.agentmemory`. This does not change the Codex session source; local Codex history is still read from `~/.codex/sessions` and `~/.codex/archived_sessions`. -- **First-run setup:** the first-run CLI model choice configures To-Do extraction — it seeds the `LANGEXTRACT_*` settings above for the model you pick (or keeps the rules extractor if you skip). The legacy memory compression/consolidation/embeddings provider is an advanced `.env`-only setting. +CLI example: -Everything stays on your machine — see [Privacy](#privacy). +```bash +AI_TODO_HOME=.local/ai-todo node dist/cli.js scan codex +AI_TODO_HOME=.local/ai-todo node dist/cli.js scan claude-code +AI_TODO_HOME=.local/ai-todo node dist/cli.js organize +AI_TODO_HOME=.local/ai-todo node dist/cli.js list +``` -## How it works +## Commands -``` -local agent sessions ─┐ - ├─▶ normalize ─▶ extractor ─▶ local DB ─▶ local API ─▶ web UI -browser AI capture ───┘ (rules or LangExtract) -``` +| Command | Description | +| --- | --- | +| `ai-todo init` | Create the local `.env` config and database directory. | +| `ai-todo doctor` | Show config paths and LLM setup without printing secrets. | +| `ai-todo scan [path]` | Import sessions from a configured or explicit source path. | +| `ai-todo organize` | Run LLM-only todo extraction over recent observations. | +| `ai-todo list` | Print current todo cards. | +| `ai-todo done ` / `ai-todo ignore ` | Mark a todo complete or ignored. | +| `ai-todo open [--port ]` | Start the local HTTP UI. | +| `ai-todo mcp` | Run the stdio MCP server. | -Built on iii-engine (file-based SQLite state), with a local REST API + MCP server + web UI. The default extractor is deterministic rules. LangExtract is opt-in: the To-Do tab reads stored cards, and the **Organize with LLM** button runs extraction for recent sessions. The **Update** button dry-runs LLM maintenance for generated cards whose source sessions changed, asks before dropping/closing/rewriting/merging cards, then applies the reviewed decisions. See [ARCHITECTURE.md](ARCHITECTURE.md) for detail. +## Configuration -## Core todo state +Config lives in `$AI_TODO_HOME/.env`, or `~/.ai-todo/.env` when +`AI_TODO_HOME` is not set. -Persisted todo cards use the existing `Action.status` values: +Common keys: -| Status | Meaning | -|---|---| -| `pending` | Open work that has not started or needs user review | -| `active` | Work that appears in progress | -| `done` | Completed work | -| `blocked` | Work blocked by a tool, dependency, permission, network, or runtime issue | -| `cancelled` | Work the user or system dismissed | +```bash +AI_TODO_CODEX_HOME=~/.codex +AI_TODO_CLAUDE_HOME=~/.claude/projects +AI_TODO_LLM_ENABLED=true +AI_TODO_LLM_PROVIDER=openai +AI_TODO_LLM_MODEL=deepseek/deepseek-v4-flash +AI_TODO_LLM_ENDPOINT=https://api.novita.ai/openai/v1 +AI_TODO_LLM_API_KEY=... +``` -LLM extraction also writes classification tags such as `type:waiting_for_user`, `type:agent_blocked`, `type:partial_done`, `type:needs_review`, and `type:stale_thread` so the UI can explain why a card was created without adding new stored status values. +`ai-todo doctor` reports whether the key, model, and endpoint are configured. -## Privacy +## Sources -- Local-first by default; captured browser content and local sessions stay on your machine. -- No cloud sync in v1; no automatic writes into external todo tools in v1. -- Secrets, API keys, and tokens are redacted before display when detected. -- Every extracted todo includes evidence; you can mark todos done, ignored, or deleted. +- Codex sessions default to `~/.codex`. +- Claude Code sessions default to `~/.claude/projects`. +- Browser sessions can be posted to `POST /browser/sessions` while the local UI + server is running. -## Under the hood +Raw evidence keeps its original language. Product controls are English-only. -Under the hood, the current prototype still exposes the full implementation surface: **55 MCP tools** (8 visible by default — 55 tools, 6 resources, 3 prompts over MCP) and a local REST API serving **142 endpoints on port** 3111. These counts track the implementation that is mid-rename to AI Todo. +## Privacy -## Documentation +AI-Todo stores data locally under `$AI_TODO_HOME` or `~/.ai-todo`. +Do not commit real `.env` files, API keys, local data, `dist/`, or +`node_modules/`. -- [Architecture](ARCHITECTURE.md) -- [Rules](RULES.md) -- [Roadmap](ROADMAP.md) -- [Development](docs/development.md) -- [Features](docs/features/index.md) +## Development -## License +```bash +npm test +npm run build +git diff --check +``` -Apache-2.0. See [LICENSE](LICENSE). +The package bin is `ai-todo` and points at `dist/cli.js`; run `npm run build` +before testing installed CLI behavior. diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index 9a36b694..00000000 --- a/ROADMAP.md +++ /dev/null @@ -1,92 +0,0 @@ -# Roadmap - -This is agentmemory's public 12-month roadmap. It covers Q2 2026 through Q1 2027. The roadmap is the source of truth for where the project is heading; anything significant that lands in main should trace back to an item here or a ratified issue. - -Items shift as evidence changes. Each quarter we publish a short retrospective on what landed, what slipped, and why — attached to the release notes. - -## How to read this - -- **Shipped** — landed in main and tagged in a release. -- **Active** — in-flight, has an open PR or issue owner. -- **Planned** — accepted scope for the quarter, not started. -- **Candidate** — under consideration, may defer. - -Anything not on this list that a contributor wants to pursue is welcome — open an issue labeled `roadmap` and it gets triaged against the quarterly theme. - -## Themes - -- **Q2 2026 — Depth.** Multimodal memory, more connectors, close out backlog from the v0.9 cycle. -- **Q3 2026 — Breadth.** Hook parity across more agents, community expansion, OpenSSF best-practices alignment. -- **Q4 2026 — Trust.** Enterprise features — SSO, audit export, RBAC, long-running deployment story. -- **Q1 2027 — v1.0.** Stability, LTS branch, semver freeze on the REST + MCP surface. - -## Q2 2026 — Depth (April – June) - -### Shipped so far in this quarter -- [x] iii console docs in README with vendored screenshots (#157) -- [x] Health severity gated on RSS floor (#158 / PR #160) -- [x] Standalone MCP proxies to the running server (#159 / PR #161) -- [x] Audit coverage for `mem::forget` + audit policy doc (#125 / PR #162) -- [x] `@agentmemory/fs-watcher` filesystem connector (#62 / PR #163) -- [x] Next.js website on Vercel (PR #164) -- [x] CI publishes all three npm packages on release (PR #166) - -### Active -- [ ] **Multimodal memory** — content-addressed image store, vision-prompt compression, disk quota + refcount on eviction (#64, PR #111) -- [ ] **Governance baseline** — this file, plus `GOVERNANCE.md`, `CONTRIBUTING.md`, `MAINTAINERS.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md` - -### Planned -- [ ] **GitHub connector** (`@agentmemory/github-watcher`) — sync issues, PRs, discussions as observations. Shares the `POST /agentmemory/observe` wire format with the filesystem connector. -- [x] **OpenCode hook bus** (#156) — wired 22 hooks covering all 12 Claude Code hook types: session lifecycle (create/idle/status/compacted/update/diff/delete/error), messages & prompts (chat.message, message.updated user+assistant, message.removed), tool capture (before + rich ToolPart lifecycle in message.part.updated), memory injection (context + enrich via system.transform), part tracking (subtask, step-finish, reasoning, file, patch, compaction, agent, retry), file enrichment pipeline (stash via tool.execute.before + file.edited + file parts), permissions (updated + replied), task tracking (todo.updated w/ priority), commands (command.executed), config & model tracking (config + chat.params). Plus 2 slash commands (recall/remember). -- [ ] **Session replay UI** in the real-time viewer — scrub the timeline, inspect per-observation payloads. -- [ ] **Benchmark harness in CI** — keep the 95.2% R@5 number honest across releases by re-running LongMemEval-S on every minor tag. - -## Q3 2026 — Breadth (July – September) - -### Planned -- [ ] **Additional maintainer onboarding** — at least one Maintainer from a different organization added via the process in `GOVERNANCE.md`. This is a prerequisite for advancing past the foundation's Growth Stage. -- [ ] **Slack / Discord connector** — third source in the connector family. -- [ ] **OpenSSF Scorecard** — enroll, reach a Silver-equivalent score. Badged in the README. -- [ ] **Hermes integration hardening** — reach parity with the OpenClaw plugin surface (session lifecycle + tool-use hooks). -- [ ] **Knowledge graph query language** — small DSL on top of `/agentmemory/graph` for multi-hop questions. -- [ ] **First conference talk** — submit to KubeCon / LlamaCon / similar. - -### Candidate -- Cross-agent shared memory namespace. Currently each agent installs its own instance. This would let a Claude Code session and a Cursor session recall each other's observations via a shared mesh node. - -## Q4 2026 — Trust (October – December) - -### Planned -- [ ] **SSO gateway** — accept OIDC in front of the REST surface for team deployments. -- [ ] **Audit log export** — streamable tail to S3 / Loki / stdout for compliance pipelines. -- [ ] **RBAC on memory scope** — `project:read`, `project:write`, `governance:delete` role set. -- [ ] **Long-running deployment guide** — first-class Docker, systemd unit, and launchd plist. -- [ ] **Performance SLO** — publish p50/p95 recall latency targets, enforce via the benchmark harness. -- [ ] **Security audit** — external review of the REST surface + mesh-sync path. Fund through LF if foundation acceptance lands before end of quarter. - -### Candidate -- Agent-to-agent memory handoff protocol — standardize what one agent can inherit from another's memory, complementing MCP. - -## Q1 2027 — v1.0 (January – March) - -### Planned -- [ ] **REST + MCP surface freeze.** Any break requires a major-version tag per `GOVERNANCE.md`. -- [ ] **LTS branch `v1.x`** — 12-month security-fix commitment. -- [ ] **v1.0 release** — full documentation pass, all roadmap items from prior quarters either shipped or formally deferred. -- [ ] **Foundation membership** — Growth → Impact stage application if adoption + maintainer diversity metrics justify. - -### Candidate -- Hosted reference instance for the community to benchmark against. -- Reference implementation in a second language (Rust or Go) for the MCP server — would expand the set of runtimes that can host agentmemory. - -## Out of scope - -For transparency, these are deliberately *not* on the roadmap: - -- A cloud-hosted agentmemory SaaS. -- Billing, subscription tiers, commercial licensing beyond Apache-2.0. -- Agent frameworks themselves — agentmemory is a dependency, not a replacement for the agent runtime. - -## Feedback - -Anything on this list you disagree with, or think should move up / down — open an issue tagged `roadmap`. Quarterly themes are revisited with every quarterly retrospective. diff --git a/RULES.md b/RULES.md deleted file mode 100644 index 052ffc0c..00000000 --- a/RULES.md +++ /dev/null @@ -1,54 +0,0 @@ -# Rules - -Hard rules for working on AI Todo — for humans and AI agents alike. For the -exhaustive per-change checklists (counts, registration), see [AGENTS.md](AGENTS.md); -for setup and PR flow, see [CONTRIBUTING.md](CONTRIBUTING.md) and -[docs/development.md](docs/development.md). - -## Language - -- Public-facing surfaces are **English-first**: README, docs, UI copy, issue/PR templates. -- Internal planning notes may use other languages, but anything users or external contributors read defaults to English. - -## Storage & display - -- Stored enum values (todo status, etc.) are **stable** — never change persisted enum literals; a change to stored values is a deliberate, separate migration. -- Separate display labels from enum values: UI strings live in i18n resources keyed by the stored value, so changing a label or switching language never touches stored data. - -## Consistency - -- Counts and versions must stay in sync across **every** file that records them (MCP tools, REST endpoints, version). The authoritative list is [AGENTS.md → Consistency Rules](AGENTS.md). `npm run pre-pr` runs the local consistency guard. - -## Pull requests - -- **One PR = one** reviewable, reversible change; keep the diff cohesive (single-developer stage — no hard line-count cap); the product stays usable at every step. -- Sign off every commit (DCO): `git commit -s`. -- No attribution headers in commits (no "Generated with …" / "Co-Authored-By" trailers). -- `npm run pre-pr` (consistency + build + test) must pass before pushing. -- See [AGENTS.md → Done Means](AGENTS.md) for the definition of done. - -## Architecture - -- All state goes through iii-engine's StateModule — never a standalone SQLite or in-process store. -- Validate inputs at MCP/REST boundaries; REST endpoints whitelist fields and never forward a raw request body to `sdk.trigger()`. -- See [ARCHITECTURE.md](ARCHITECTURE.md) for the system overview. - -## Security & privacy - -- Local-first: captured browser content and local sessions stay on the user's machine by default. -- No cloud sync in v1; no automatic writes into external todo tools in v1. -- Redact secrets, API keys, and tokens before display when detected. -- Every extracted todo must include at least one piece of evidence. -- Users can mark todos done, ignored, or deleted. - -## Release checklist - -Run before any external release or announcement — covers the four release -dimensions (localization / README / rules / architecture): - -- [ ] README is in English and current (What / Why / v1 Scope / Docs / Privacy / License). -- [ ] Core UI strings are available in English (i18n base in place). -- [ ] `RULES.md` and `ARCHITECTURE.md` exist and are linked from README. -- [ ] New features have a reviewed feature doc (feature template + review process). -- [ ] Issue and PR templates are present and working. -- [ ] `npm run pre-pr` is green; counts and versions are consistent. diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index ec86d69e..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,83 +0,0 @@ -# Security Policy - -## Reporting a vulnerability - -**Do not open a public GitHub issue for a suspected vulnerability.** - -Use one of: - -- **GitHub Security Advisories (preferred)** — private report form at . GitHub routes the report to the Maintainers, assigns a GHSA identifier, and keeps you in a private thread until the fix ships. All sensitive details (stack traces, credentials, exploit payloads) stay end-to-end within GitHub's security infrastructure — use this channel whenever possible. -- **Encrypted email (fallback)** — if GitHub is unavailable or the issue cannot be described in the GHSA form, send an encrypted message to `ghumare64@gmail.com` with subject `agentmemory security`. Encrypt with the Maintainer public keys published at (PGP) and (SSH for verification); attach your own public key so we can reply encrypted. Plaintext email is accepted only as a last resort — prefer GHSA. - -Include, at minimum: - -- agentmemory version (`npm view @agentmemory/agentmemory version` against your install). -- The affected surface — REST endpoint, MCP tool, hook, CLI flag, or filesystem layout. -- A minimal reproduction — prefer one curl invocation or one MCP tool call plus the environment state required. -- Impact, in your own words. - -## What we do with it - -1. **Acknowledge** within 72 hours (target: 24). -2. **Triage** — confirm reproduction, assign a severity using CVSS 3.1, and give you a rough timeline. -3. **Fix** in a private branch. Draft a GitHub Security Advisory with the patched version, CWE, CVSS vector, affected versions, and attribution to you (unless you prefer anonymity). -4. **Coordinate disclosure** — we agree a disclosure date with you. Default window is 30 days from acknowledgment for straightforward vulnerabilities, up to 90 days for ones that need a deep refactor. -5. **Publish** — release the patched version on npm, publish the advisory, update `CHANGELOG.md` under a `### Security` section for the release, notify downstream scanners. - -## Supported versions - -| Version | Security fixes? | -|-|-| -| Latest minor (currently `0.9.x`) | Yes | -| Previous minor (currently `0.8.x`) | Critical / High severity only, for 90 days after a new minor is released | -| Older | No | - -At v1.0 this policy switches to a stated LTS window per the roadmap. - -## Scope - -In scope: - -- The `@agentmemory/agentmemory` server (REST + MCP surface, hook handlers, state store). -- The `@agentmemory/mcp` standalone MCP server. -- The Claude Code plugin under `plugin/`. - -Out of scope: - -- Third-party MCP clients consuming agentmemory — report to those projects. -- `iii-sdk` upstream — report to the iii project. -- The marketing site under `website/` unless the issue affects user security (XSS against visitors, credential leak in build output). - -## Supply-chain stance - -agentmemory ships pre-built artifacts in the npm tarball — `dist/` is bundled at publish time, not built from `node_modules` at install time. The package's runtime dependency tree is intentionally small (6 production deps: `@anthropic-ai/sdk`, `@anthropic-ai/claude-agent-sdk`, `@clack/prompts`, `dotenv`, `iii-sdk`, `zod`) plus an optional set guarded behind `optionalDependencies` for embeddings. - -**No lockfile is committed** (#540). The reasoning: - -- The npm tarball ships pre-built `dist/` — fresh installs don't compile from source, so no lockfile is consulted at the user's install step. -- The lockfile only affects contributor-local builds. Pinning it would shift the supply-chain attack surface from "what npm resolves today" to "what was resolved when the lockfile was last regenerated," which is a different tradeoff, not strictly better. -- We use SemVer ranges (`^x.y.z`) on the published deps so security patches reach users without a re-release. - -If you ship agentmemory inside a hardened pipeline that requires reproducible installs, the recommended path is: - -1. `npm install --legacy-peer-deps` against the published tarball in a controlled environment. -2. `npm shrinkwrap` to produce a versioned `npm-shrinkwrap.json` that travels with your deployment. -3. Audit `node_modules/` once at that point and republish internally. - -CI runs `npm install --package-lock-only --legacy-peer-deps --no-audit --no-fund` then `npm ci` against that generated lockfile, so every test job builds against a fully resolved tree. The lockfile is regenerated on each CI run rather than checked in, which keeps the published tarball aligned with whatever SemVer-compatible patch level was current at release time. - -Supply-chain monitoring we already do: - -- Dependabot opens PRs for every minor/patch bump on the production dep list (visible in the open PRs). -- Every PR runs the full test suite on ubuntu-latest + macos-latest, Node 20 + 22, before any merge. -- `optionalDependencies` (`@xenova/transformers`, `onnxruntime-node`, etc.) are guarded by `try { await import("...") } catch` so a missing or compromised optional dep cannot break the core runtime path. - -If you find a malicious package in our dep tree, file via the GHSA flow at the top of this document — that's the fastest path to a fixed release on npm. - -## Past advisories - -See the [`.github/security-advisories/`](./.github/security-advisories) directory for advisory drafts. Published advisories (with assigned GHSA IDs) live at . - -## Safe harbor - -Good-faith research, reported privately, does not get legal heat from the project. Research targeting third-party deployments of agentmemory is not covered — that's between you and the deployer. diff --git a/assets/agents/pi.svg b/assets/agents/pi.svg deleted file mode 100644 index 3d40fc0e..00000000 --- a/assets/agents/pi.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/assets/banner.png b/assets/banner.png deleted file mode 100644 index da15c93b..00000000 Binary files a/assets/banner.png and /dev/null differ diff --git a/assets/demo.gif b/assets/demo.gif deleted file mode 100644 index 272e367a..00000000 Binary files a/assets/demo.gif and /dev/null differ diff --git a/assets/demo.mp4 b/assets/demo.mp4 deleted file mode 100644 index ecf88cf8..00000000 Binary files a/assets/demo.mp4 and /dev/null differ diff --git a/assets/icon.svg b/assets/icon.svg deleted file mode 100644 index 68b00c10..00000000 --- a/assets/icon.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/iii-console/states.png b/assets/iii-console/states.png deleted file mode 100644 index 7a6f503d..00000000 Binary files a/assets/iii-console/states.png and /dev/null differ diff --git a/assets/iii-console/traces-waterfall.png b/assets/iii-console/traces-waterfall.png deleted file mode 100644 index 976c10cc..00000000 Binary files a/assets/iii-console/traces-waterfall.png and /dev/null differ diff --git a/assets/iii-console/workers.png b/assets/iii-console/workers.png deleted file mode 100644 index 2ee093c5..00000000 Binary files a/assets/iii-console/workers.png and /dev/null differ diff --git a/assets/logo.svg b/assets/logo.svg deleted file mode 100644 index 237aafe9..00000000 --- a/assets/logo.svg +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - AGENT - MEMORY - diff --git a/assets/tags/divider.svg b/assets/tags/divider.svg deleted file mode 100644 index 20d940f4..00000000 --- a/assets/tags/divider.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/assets/tags/light/divider.svg b/assets/tags/light/divider.svg deleted file mode 100644 index 45add11a..00000000 --- a/assets/tags/light/divider.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/assets/tags/light/new-v082.svg b/assets/tags/light/new-v082.svg deleted file mode 100644 index e7d745d2..00000000 --- a/assets/tags/light/new-v082.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - NEW - v0.8.2 - \ No newline at end of file diff --git a/assets/tags/light/pill-beta.svg b/assets/tags/light/pill-beta.svg deleted file mode 100644 index 9a37b56c..00000000 --- a/assets/tags/light/pill-beta.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - BETA - \ No newline at end of file diff --git a/assets/tags/light/pill-hook.svg b/assets/tags/light/pill-hook.svg deleted file mode 100644 index 906651ae..00000000 --- a/assets/tags/light/pill-hook.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - AUTO HOOK - \ No newline at end of file diff --git a/assets/tags/light/pill-mcp.svg b/assets/tags/light/pill-mcp.svg deleted file mode 100644 index 3221bc47..00000000 --- a/assets/tags/light/pill-mcp.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - MCP - \ No newline at end of file diff --git a/assets/tags/light/pill-new.svg b/assets/tags/light/pill-new.svg deleted file mode 100644 index a72649fe..00000000 --- a/assets/tags/light/pill-new.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - NEW - \ No newline at end of file diff --git a/assets/tags/light/pill-plugin.svg b/assets/tags/light/pill-plugin.svg deleted file mode 100644 index f1d42552..00000000 --- a/assets/tags/light/pill-plugin.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - PLUGIN - \ No newline at end of file diff --git a/assets/tags/light/pill-secure.svg b/assets/tags/light/pill-secure.svg deleted file mode 100644 index 0efce67e..00000000 --- a/assets/tags/light/pill-secure.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - SECURE - \ No newline at end of file diff --git a/assets/tags/light/pill-skill.svg b/assets/tags/light/pill-skill.svg deleted file mode 100644 index d6e03396..00000000 --- a/assets/tags/light/pill-skill.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - SKILL - \ No newline at end of file diff --git a/assets/tags/light/pill-stable.svg b/assets/tags/light/pill-stable.svg deleted file mode 100644 index 41baca16..00000000 --- a/assets/tags/light/pill-stable.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - STABLE - \ No newline at end of file diff --git a/assets/tags/light/section-agents.svg b/assets/tags/light/section-agents.svg deleted file mode 100644 index 6f8adecd..00000000 --- a/assets/tags/light/section-agents.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - WORKS WITH EVERY AGENT - 15 integrations · one memory server - \ No newline at end of file diff --git a/assets/tags/light/section-api.svg b/assets/tags/light/section-api.svg deleted file mode 100644 index 1228cb1b..00000000 --- a/assets/tags/light/section-api.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - API - 109 REST endpoints - \ No newline at end of file diff --git a/assets/tags/light/section-architecture.svg b/assets/tags/light/section-architecture.svg deleted file mode 100644 index 670bfa3b..00000000 --- a/assets/tags/light/section-architecture.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - ARCHITECTURE - Built on iii-engine's 3 primitives - \ No newline at end of file diff --git a/assets/tags/light/section-benchmarks.svg b/assets/tags/light/section-benchmarks.svg deleted file mode 100644 index 5abbfcde..00000000 --- a/assets/tags/light/section-benchmarks.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - BENCHMARKS - Measured on LongMemEval-S (ICLR 2025) - \ No newline at end of file diff --git a/assets/tags/light/section-competitors.svg b/assets/tags/light/section-competitors.svg deleted file mode 100644 index 63d12841..00000000 --- a/assets/tags/light/section-competitors.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - VS COMPETITORS - Mem0 · Letta · Khoj · Hippo · claude-mem - \ No newline at end of file diff --git a/assets/tags/light/section-config.svg b/assets/tags/light/section-config.svg deleted file mode 100644 index 59177c43..00000000 --- a/assets/tags/light/section-config.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - CONFIGURATION - LLM providers, embeddings, and more - \ No newline at end of file diff --git a/assets/tags/light/section-development.svg b/assets/tags/light/section-development.svg deleted file mode 100644 index bbbbbf9a..00000000 --- a/assets/tags/light/section-development.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - DEVELOPMENT - Hot reload, 654 tests, ~1.7s run - \ No newline at end of file diff --git a/assets/tags/light/section-how.svg b/assets/tags/light/section-how.svg deleted file mode 100644 index 7470d213..00000000 --- a/assets/tags/light/section-how.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - HOW IT WORKS - Hook · compress · embed · retrieve - \ No newline at end of file diff --git a/assets/tags/light/section-license.svg b/assets/tags/light/section-license.svg deleted file mode 100644 index 6f56ab49..00000000 --- a/assets/tags/light/section-license.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - LICENSE - Apache-2.0 - \ No newline at end of file diff --git a/assets/tags/light/section-mcp.svg b/assets/tags/light/section-mcp.svg deleted file mode 100644 index c5101169..00000000 --- a/assets/tags/light/section-mcp.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - MCP SERVER - 43 tools · 6 resources · 3 prompts - \ No newline at end of file diff --git a/assets/tags/light/section-quickstart.svg b/assets/tags/light/section-quickstart.svg deleted file mode 100644 index 71db9adc..00000000 --- a/assets/tags/light/section-quickstart.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - QUICK START - 30 seconds. No API key needed. - \ No newline at end of file diff --git a/assets/tags/light/section-search.svg b/assets/tags/light/section-search.svg deleted file mode 100644 index 5e55e087..00000000 --- a/assets/tags/light/section-search.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - SEARCH - BM25 + vector + knowledge graph - \ No newline at end of file diff --git a/assets/tags/light/section-viewer.svg b/assets/tags/light/section-viewer.svg deleted file mode 100644 index 1a0f567e..00000000 --- a/assets/tags/light/section-viewer.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - REAL-TIME VIEWER - Live observation stream on :3113 - \ No newline at end of file diff --git a/assets/tags/light/section-why.svg b/assets/tags/light/section-why.svg deleted file mode 100644 index ef642aeb..00000000 --- a/assets/tags/light/section-why.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - WHY AGENTMEMORY - Goldfish memory costs you $10/day - \ No newline at end of file diff --git a/assets/tags/light/stat-deps.svg b/assets/tags/light/stat-deps.svg deleted file mode 100644 index e32e7b36..00000000 --- a/assets/tags/light/stat-deps.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 0 - EXTERNAL DBS - \ No newline at end of file diff --git a/assets/tags/light/stat-hooks.svg b/assets/tags/light/stat-hooks.svg deleted file mode 100644 index 640d59e9..00000000 --- a/assets/tags/light/stat-hooks.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 12 - AUTO HOOKS - \ No newline at end of file diff --git a/assets/tags/light/stat-recall.svg b/assets/tags/light/stat-recall.svg deleted file mode 100644 index df9e2705..00000000 --- a/assets/tags/light/stat-recall.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 95.2% - RETRIEVAL R@5 - \ No newline at end of file diff --git a/assets/tags/light/stat-tests.svg b/assets/tags/light/stat-tests.svg deleted file mode 100644 index c489f483..00000000 --- a/assets/tags/light/stat-tests.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 1171+ - TESTS PASSING - diff --git a/assets/tags/light/stat-tokens.svg b/assets/tags/light/stat-tokens.svg deleted file mode 100644 index 858abe17..00000000 --- a/assets/tags/light/stat-tokens.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 92% - FEWER TOKENS - \ No newline at end of file diff --git a/assets/tags/light/stat-tools.svg b/assets/tags/light/stat-tools.svg deleted file mode 100644 index e6d59853..00000000 --- a/assets/tags/light/stat-tools.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 53 - MCP TOOLS - diff --git a/assets/tags/new-v082.svg b/assets/tags/new-v082.svg deleted file mode 100644 index f8ec1eba..00000000 --- a/assets/tags/new-v082.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - NEW - v0.8.2 - \ No newline at end of file diff --git a/assets/tags/pill-beta.svg b/assets/tags/pill-beta.svg deleted file mode 100644 index 22b265be..00000000 --- a/assets/tags/pill-beta.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - BETA - \ No newline at end of file diff --git a/assets/tags/pill-hook.svg b/assets/tags/pill-hook.svg deleted file mode 100644 index 4a9df9b5..00000000 --- a/assets/tags/pill-hook.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - AUTO HOOK - \ No newline at end of file diff --git a/assets/tags/pill-mcp.svg b/assets/tags/pill-mcp.svg deleted file mode 100644 index 56ecebfc..00000000 --- a/assets/tags/pill-mcp.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - MCP - \ No newline at end of file diff --git a/assets/tags/pill-new.svg b/assets/tags/pill-new.svg deleted file mode 100644 index 797b3480..00000000 --- a/assets/tags/pill-new.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - NEW - \ No newline at end of file diff --git a/assets/tags/pill-plugin.svg b/assets/tags/pill-plugin.svg deleted file mode 100644 index eafb4ec6..00000000 --- a/assets/tags/pill-plugin.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - PLUGIN - \ No newline at end of file diff --git a/assets/tags/pill-secure.svg b/assets/tags/pill-secure.svg deleted file mode 100644 index ec87cf2d..00000000 --- a/assets/tags/pill-secure.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - SECURE - \ No newline at end of file diff --git a/assets/tags/pill-skill.svg b/assets/tags/pill-skill.svg deleted file mode 100644 index 9a525f52..00000000 --- a/assets/tags/pill-skill.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - SKILL - \ No newline at end of file diff --git a/assets/tags/pill-stable.svg b/assets/tags/pill-stable.svg deleted file mode 100644 index 2e4c9704..00000000 --- a/assets/tags/pill-stable.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - STABLE - \ No newline at end of file diff --git a/assets/tags/section-agents.svg b/assets/tags/section-agents.svg deleted file mode 100644 index 386576db..00000000 --- a/assets/tags/section-agents.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - WORKS WITH EVERY AGENT - 15 integrations · one memory server - \ No newline at end of file diff --git a/assets/tags/section-api.svg b/assets/tags/section-api.svg deleted file mode 100644 index 331be38e..00000000 --- a/assets/tags/section-api.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - API - 109 REST endpoints - \ No newline at end of file diff --git a/assets/tags/section-architecture.svg b/assets/tags/section-architecture.svg deleted file mode 100644 index d7b60e08..00000000 --- a/assets/tags/section-architecture.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - ARCHITECTURE - Built on iii-engine's 3 primitives - \ No newline at end of file diff --git a/assets/tags/section-benchmarks.svg b/assets/tags/section-benchmarks.svg deleted file mode 100644 index 7de135fa..00000000 --- a/assets/tags/section-benchmarks.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - BENCHMARKS - Measured on LongMemEval-S (ICLR 2025) - \ No newline at end of file diff --git a/assets/tags/section-competitors.svg b/assets/tags/section-competitors.svg deleted file mode 100644 index 90761e86..00000000 --- a/assets/tags/section-competitors.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - VS COMPETITORS - Mem0 · Letta · Khoj · Hippo · claude-mem - \ No newline at end of file diff --git a/assets/tags/section-config.svg b/assets/tags/section-config.svg deleted file mode 100644 index 27fc49c7..00000000 --- a/assets/tags/section-config.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - CONFIGURATION - LLM providers, embeddings, and more - \ No newline at end of file diff --git a/assets/tags/section-development.svg b/assets/tags/section-development.svg deleted file mode 100644 index 620be6e1..00000000 --- a/assets/tags/section-development.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - DEVELOPMENT - Hot reload, 654 tests, ~1.7s run - \ No newline at end of file diff --git a/assets/tags/section-how.svg b/assets/tags/section-how.svg deleted file mode 100644 index 2f342736..00000000 --- a/assets/tags/section-how.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - HOW IT WORKS - Hook · compress · embed · retrieve - \ No newline at end of file diff --git a/assets/tags/section-license.svg b/assets/tags/section-license.svg deleted file mode 100644 index 084f41d6..00000000 --- a/assets/tags/section-license.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - LICENSE - Apache-2.0 - \ No newline at end of file diff --git a/assets/tags/section-mcp.svg b/assets/tags/section-mcp.svg deleted file mode 100644 index 789ff8f8..00000000 --- a/assets/tags/section-mcp.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - MCP SERVER - 43 tools · 6 resources · 3 prompts - \ No newline at end of file diff --git a/assets/tags/section-quickstart.svg b/assets/tags/section-quickstart.svg deleted file mode 100644 index 1e81c943..00000000 --- a/assets/tags/section-quickstart.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - QUICK START - 30 seconds. No API key needed. - \ No newline at end of file diff --git a/assets/tags/section-search.svg b/assets/tags/section-search.svg deleted file mode 100644 index 193125af..00000000 --- a/assets/tags/section-search.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - SEARCH - BM25 + vector + knowledge graph - \ No newline at end of file diff --git a/assets/tags/section-viewer.svg b/assets/tags/section-viewer.svg deleted file mode 100644 index cea6dbeb..00000000 --- a/assets/tags/section-viewer.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - REAL-TIME VIEWER - Live observation stream on :3113 - \ No newline at end of file diff --git a/assets/tags/section-why.svg b/assets/tags/section-why.svg deleted file mode 100644 index f8303116..00000000 --- a/assets/tags/section-why.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - WHY AGENTMEMORY - Goldfish memory costs you $10/day - \ No newline at end of file diff --git a/assets/tags/stat-deps.svg b/assets/tags/stat-deps.svg deleted file mode 100644 index 4db3a1ae..00000000 --- a/assets/tags/stat-deps.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 0 - EXTERNAL DBS - \ No newline at end of file diff --git a/assets/tags/stat-hooks.svg b/assets/tags/stat-hooks.svg deleted file mode 100644 index 93118e14..00000000 --- a/assets/tags/stat-hooks.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 12 - AUTO HOOKS - \ No newline at end of file diff --git a/assets/tags/stat-recall.svg b/assets/tags/stat-recall.svg deleted file mode 100644 index b432497c..00000000 --- a/assets/tags/stat-recall.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 95.2% - RETRIEVAL R@5 - \ No newline at end of file diff --git a/assets/tags/stat-tests.svg b/assets/tags/stat-tests.svg deleted file mode 100644 index a178c758..00000000 --- a/assets/tags/stat-tests.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 1171+ - TESTS PASSING - diff --git a/assets/tags/stat-tokens.svg b/assets/tags/stat-tokens.svg deleted file mode 100644 index 3ba3814a..00000000 --- a/assets/tags/stat-tokens.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 92% - FEWER TOKENS - \ No newline at end of file diff --git a/assets/tags/stat-tools.svg b/assets/tags/stat-tools.svg deleted file mode 100644 index 2f38d7a1..00000000 --- a/assets/tags/stat-tools.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - 53 - MCP TOOLS - diff --git a/browser-extension/AI-SITE-TEST-CARDS.md b/browser-extension/AI-SITE-TEST-CARDS.md deleted file mode 100644 index e02fec97..00000000 --- a/browser-extension/AI-SITE-TEST-CARDS.md +++ /dev/null @@ -1,42 +0,0 @@ -# Agent Memory Lab 真实 AI 站点测试卡 - -公开发布前,每个 AI 站点都要用真实会话验收一次。 - -必测站点: - -- ChatGPT:`chatgpt.com` / `chat.openai.com` -- Claude:`claude.ai` -- Gemini:`gemini.google.com` -- Perplexity:`perplexity.ai` / `www.perplexity.ai` -- Grok:`grok.com` / `x.ai` -- DeepSeek:`chat.deepseek.com` - -## 每个站点都要确认 - -- Provider 识别正确。 -- 页面里至少有一轮真实对话。 -- 侧栏诊断里的 `turnCount > 0`。 -- 弹窗或侧栏显示“已抓取 N 条”。 -- 点击“同步已打开的 AI 会话”后,工作台 `#sessions` 出现对应会话。 -- 原站输入、发送、滚动、模型选择和附件按钮没有异常。 -- 复制诊断 JSON 时不包含真实会话正文,只包含选择器、计数和页面元信息。 - -## 保存证据 - -复制同步侧栏诊断后,在仓库根目录运行: - -```bash -npm run wizard:ai-validation-evidence -``` - -已经确认通过时,也可以使用无交互模式: - -```bash -npm run wizard:ai-validation-evidence -- --yes --browser "Chrome 版本号" --notes "无隐私信息的备注" -``` - -证据里应能看到 `manualValidation.diagnosticsCopied`、`manualValidation.siteInputStillWorks`、`matchedSelectors.turn` 和 `turnCount > 0`。 - -## 当前边界 - -本地 demo 通过不等于公开发布通过。公开发布需要真实 AI 页面通过证据,并确认同步后的会话能在工作台 Sessions 里查看。 diff --git a/browser-extension/LOAD-THIS-FIRST.md b/browser-extension/LOAD-THIS-FIRST.md deleted file mode 100644 index 3d403cf5..00000000 --- a/browser-extension/LOAD-THIS-FIRST.md +++ /dev/null @@ -1,64 +0,0 @@ -# Agent Memory Lab 插件本地试用入口 - -如果你是从 `agent-memory-lab-extension.zip` 解压出来的,请按这份清单试用。目标很简单:确认插件能加载、能识别真实 AI 会话、能把会话同步到本地工作台的 Sessions。 - -## 1. 加载插件 - -1. 打开 Chrome / Edge。 -2. 进入 `chrome://extensions`。 -3. 打开“开发者模式”。 -4. 点击“加载已解压的扩展程序”。 -5. 选择这个解压出来的 `browser-extension/` 文件夹。 - -## 2. 启动本地工作台 - -插件默认连接: - -```text -API: http://localhost:3111 -Viewer: http://localhost:3114 -``` - -如果插件显示“未连接本地工作台”,请在项目目录启动: - -```bash -npm run build && npm run start:local-memory -``` - -## 3. 五步验收 - -1. 打开一个真实 AI 会话页面,例如 ChatGPT、Claude、Gemini、Perplexity、Grok 或 DeepSeek。 -2. 点击浏览器工具栏里的 Agent Memory Lab 图标。 -3. 确认弹窗显示“已抓取 N 条”或“等待会话”。 -4. 点击“同步已打开的 AI 会话”。 -5. 回到本地工作台 `#sessions`,确认会话出现在列表里。 - -## 4. 侧栏查看 - -打开同步侧栏后,重点看三项: - -- 当前页面是否识别为 AI 对话。 -- 已抓取多少条会话。 -- 最近同步里是否出现当前会话。 - -页面识别不准时,再展开“页面识别”,点击“复制问题信息”。诊断信息只包含选择器和计数,不应包含真实会话正文。 - -## 5. 真实 AI 页面证据 - -如果你在真实站点试用,请参考本目录的 `AI-SITE-TEST-CARDS.md`。每个站点至少确认: - -- Provider 识别正确。 -- `turnCount > 0`。 -- 工作台 `#sessions` 能看到同步后的会话。 -- 原站输入、发送、滚动和附件按钮没有异常。 -- 复制诊断不泄露会话正文。 - -## 6. 反馈问题 - -反馈时优先附上: - -- 浏览器名称和版本。 -- AI 产品名称。 -- 同步侧栏复制的问题信息。 -- 工作台 `#sessions` 是否出现会话。 -- 脱敏截图或录屏。 diff --git a/browser-extension/README.md b/browser-extension/README.md deleted file mode 100644 index 7d8f0c1c..00000000 --- a/browser-extension/README.md +++ /dev/null @@ -1,135 +0,0 @@ -# Agent Memory Lab 浏览器插件 - -这是 Agent Memory Lab 的浏览器会话同步入口。它只做一件事:在 ChatGPT、Claude、Gemini、Perplexity、Grok、DeepSeek 等网页 AI 产品里抓取可见会话,并同步到本地工作台的 Sessions。 - -插件不会在浏览器里提取记忆、生成候选、总结页面、保存选中文本或判断 Skill。记忆和 Skill 的整理沉淀都放回本地工作台完成。 - -```text -网页 AI 会话 - | - v -浏览器插件:识别 AI 页面并同步原始对话 - | - v -本地工作台:统一展示 Sessions - | - v -工作台整理:后续再判断记忆、任务或 Skill -``` - -## 当前支持 - -- 自动识别主流 AI 会话网页。 -- 自动扫描已打开的 AI 会话标签页。 -- 抓取网页里可见的用户 / AI 对话内容。 -- 同步到本地 Agent Memory Lab 工作台。 -- 弹窗和侧栏用中文显示连接状态、识别状态、抓取条数和最近同步。 -- 页面诊断只复制选择器、计数、浏览器版本等排错信息,不复制真实会话正文。 -- 右键菜单只保留“同步到工作台”和“打开工作台”。 - -## 使用体验 - -| 入口 | 用途 | -| --- | --- | -| 弹窗 | 看当前页是否识别、抓到几条会话、手动同步所有已打开 AI 会话 | -| 同步侧栏 | 看更完整的同步状态、最近抓到的对话、页面识别诊断 | -| 工作台 | 查看所有同步进来的 Sessions,并在工作台里继续整理记忆或 Skill | - -日常使用时,插件应该像一个轻量状态面板,而不是一个小型记忆编辑器。 - -## 本地使用 - -1. 进入项目目录并启动工作台:`npm run build && npm run start:local-memory` -2. 打开 Chrome / Edge 的扩展管理页。 -3. 打开“开发者模式”。 -4. 点击“加载已解压的扩展程序”。 -5. 选择本目录:`browser-extension` -6. 打开真实 AI 会话页面。 -7. 点击插件图标,确认“已抓取 N 条”。 -8. 回到工作台 `#sessions` 查看同步进来的会话。 - -默认连接: - -```text -API: http://localhost:3111 -Viewer: http://localhost:3114 -``` - -如果默认端口不可用,先确认是否已有 Agent Memory Lab 在运行;不要用上游官方包启动我们的本地版本。 - -## 数据结构 - -插件同步的数据是最小会话包: - -```js -{ - schemaVersion: 1, - capturedAt: "2026-06-11T00:00:00.000Z", - source: "browser-extension", - page: { - type: "ai-chat", - typeLabel: "AI 对话", - title: "页面标题", - url: "https://chatgpt.com/c/...", - host: "chatgpt.com", - origin: "https://chatgpt.com" - }, - conversation: { - provider: "ChatGPT", - turns: [ - { role: "user", text: "用户发言" }, - { role: "assistant", text: "AI 回复" } - ] - }, - diagnostics: { - supportedAiPage: true, - provider: "ChatGPT", - editorFound: true, - sendFound: true, - turnCount: 2 - } -} -``` - -不会包含: - -- 页面正文摘要 -- 选中文本 -- 输入框草稿 -- 页面标题结构 -- 记忆候选 -- Skill 候选 -- 插件侧的长期记忆判断 - -## 检查命令 - -```bash -npm run check:browser-extension -npm run build -``` - -`check:browser-extension` 会确认插件仍然只同步会话:没有候选 UI、没有插件内保存记忆消息、没有记忆建议浮窗、没有页面内容抽取字段。 - -## 真实站点验收 - -真实发布前需要在这些站点确认: - -- ChatGPT -- Claude -- Gemini -- Perplexity -- Grok -- DeepSeek - -每个站点至少确认: - -- Provider 识别正确。 -- 页面有真实会话时 `turnCount > 0`。 -- 弹窗或侧栏显示已抓取条数。 -- 工作台 `#sessions` 里出现对应会话。 -- 原站输入、发送、滚动和附件按钮没有异常。 -- 复制诊断时不包含真实会话正文。 - -## 边界 - -插件不是 Web Clipper,也不是记忆编辑器。它只负责把网页 AI 对话送回本地工作台。任何“是否值得长期记住”“是否沉淀为 Skill”“是否生成待办”的判断,都应该在工作台里发生。 diff --git a/browser-extension/config.js b/browser-extension/config.js deleted file mode 100644 index 9ac6bf66..00000000 --- a/browser-extension/config.js +++ /dev/null @@ -1,53 +0,0 @@ -export const DEFAULTS = { - apiBase: 'http://localhost:3111', - viewerBase: '', - viewerFallbackBase: 'http://localhost:3114' -}; - -export async function getSettings() { - const stored = await chrome.storage.sync.get(['apiBase', 'viewerBase', 'secret']); - return { - apiBase: stored.apiBase || DEFAULTS.apiBase, - viewerBase: stored.viewerBase || DEFAULTS.viewerBase, - secret: stored.secret || '' - }; -} - -export async function resolveViewerBase(settings = null) { - const current = settings || await getSettings(); - if (current.viewerBase) return current.viewerBase.replace(/\/$/, ''); - if (await hasAgentMemoryApi(DEFAULTS.viewerFallbackBase, current)) return DEFAULTS.viewerFallbackBase; - try { - const res = await fetch(`${current.apiBase}/agentmemory/livez`, { headers: authHeaders(current) }); - const data = await res.json(); - if (data && typeof data.viewerPort === 'number') return `http://localhost:${data.viewerPort}`; - } catch {} - return 'http://localhost:3114'; -} - -export async function resolveApiBase(settings = null) { - const current = settings || await getSettings(); - const configured = (current.apiBase || DEFAULTS.apiBase).replace(/\/$/, ''); - if (await hasAgentMemoryApi(DEFAULTS.viewerFallbackBase, current)) return DEFAULTS.viewerFallbackBase; - const viewerBase = (await resolveViewerBase(current)).replace(/\/$/, ''); - if (viewerBase && await hasAgentMemoryApi(viewerBase, current)) return viewerBase; - if (await hasAgentMemoryApi(configured, current)) return configured; - return configured; -} - -async function hasAgentMemoryApi(base, settings) { - try { - const res = await fetch(`${base}/agentmemory/livez`, { headers: authHeaders(settings) }); - if (!res.ok) return false; - const data = await res.json().catch(() => null); - return !!(data && data.service === 'agentmemory' && data.status === 'ok'); - } catch { - return false; - } -} - -export function authHeaders(settings) { - const headers = { 'Content-Type': 'application/json' }; - if (settings.secret) headers.Authorization = `Bearer ${settings.secret}`; - return headers; -} diff --git a/browser-extension/content-script.js b/browser-extension/content-script.js deleted file mode 100644 index 4793be6a..00000000 --- a/browser-extension/content-script.js +++ /dev/null @@ -1,237 +0,0 @@ -(() => { - if (window.__AGENT_MEMORY_LAB_CONTENT_SCRIPT_LOADED__) return; - window.__AGENT_MEMORY_LAB_CONTENT_SCRIPT_LOADED__ = true; - - const AI_PROVIDERS = [ - { id: 'agentmemoryDemo', label: 'Agent Memory Demo', hosts: ['localhost', '127.0.0.1'], pathIncludes: ['/demo/browser-extension.html'], editorSelectors: ['#agentmemory-demo-input', '[data-agentmemory-demo-input]', '[contenteditable="true"]'], anchorSelectors: ['#agentmemory-demo-input', 'form', 'main'], placement: 'input-corner', turnSelectors: ['[data-message-author-role]', 'main article'], sendSelectors: ['button.primary'] }, - { id: 'chatgpt', label: 'ChatGPT', hosts: ['chatgpt.com', 'chat.openai.com'], editorSelectors: ['#prompt-textarea', '[data-testid="prompt-textarea"]', 'textarea[placeholder]', 'textarea', '[contenteditable="true"]'], anchorSelectors: ['[data-testid="composer-trailing-actions"]', '.composer-trailing-actions', 'form', 'main form'], adjacentSelectors: ['button[aria-label="Dictate button"]', 'button[aria-label*="mic" i]', 'button[aria-label*="voice" i]'], placement: 'toolbar-end', turnSelectors: ['[data-message-author-role]', '[data-testid*="conversation-turn"]', 'main article'], sendSelectors: ['button[data-testid="send-button"]', 'button[aria-label*="Send"]'] }, - { id: 'claude', label: 'Claude', hosts: ['claude.ai'], editorSelectors: ['div.ProseMirror[contenteditable="true"]', 'div[contenteditable="true"]', 'textarea', 'p[data-placeholder]'], anchorSelectors: ['form', '[data-testid*="input"]', '[contenteditable="true"]'], placement: 'input-corner', turnSelectors: ['[data-testid*="message"]', 'main [class*="font-claude"]', 'main article'], sendSelectors: ['button[aria-label*="Send"]', 'button[type="submit"]'] }, - { id: 'gemini', label: 'Gemini', hosts: ['gemini.google.com'], editorSelectors: ['rich-textarea [contenteditable="true"]', 'rich-textarea textarea', '[contenteditable="true"]', 'textarea'], anchorSelectors: ['rich-textarea', '.input-area-container', 'form', '[contenteditable="true"]'], placement: 'input-corner', turnSelectors: ['user-query', 'model-response', 'message-content', 'main article'], sendSelectors: ['button[aria-label*="Send"]', 'button[aria-label*="提交"]'] }, - { id: 'perplexity', label: 'Perplexity', hosts: ['perplexity.ai', 'www.perplexity.ai'], editorSelectors: ['textarea[placeholder]', 'textarea', '[contenteditable="true"]'], anchorSelectors: ['form', 'textarea[placeholder]', '[contenteditable="true"]'], placement: 'input-corner', turnSelectors: ['[data-testid*="thread"]', '[class*="prose"]', 'main article'], sendSelectors: ['button[aria-label*="Submit"]', 'button[aria-label*="Send"]'] }, - { id: 'grok', label: 'Grok', hosts: ['grok.com', 'x.ai'], editorSelectors: ['textarea', '[contenteditable="true"]'], anchorSelectors: ['form', 'textarea', '[contenteditable="true"]'], placement: 'input-corner', turnSelectors: ['[data-testid*="message"]', 'main article'], sendSelectors: ['button[aria-label*="Send"]'] }, - { id: 'deepseek', label: 'DeepSeek', hosts: ['chat.deepseek.com', 'deepseek.com'], editorSelectors: ['textarea', '[contenteditable="true"]'], anchorSelectors: ['form', 'textarea', '[contenteditable="true"]'], placement: 'input-corner', turnSelectors: ['[class*="message"]', 'main article'], sendSelectors: ['button[aria-label*="Send"]'] } - ]; - let syncTimer = null; - let lastSyncKey = ''; - let pendingSyncKey = ''; - const MAX_CAPTURE_TURNS = 160; - const MAX_CAPTURE_NODES = 360; - - function getProviderForHost(hostname) { - const host = String(hostname || '').toLowerCase(); - const path = String(location.pathname || ''); - return AI_PROVIDERS.find((provider) => { - const hostMatches = provider.hosts.some((item) => host === item || host.endsWith(`.${item}`)); - if (!hostMatches) return false; - return !provider.pathIncludes || provider.pathIncludes.some((item) => path.includes(item)); - }) || null; - } - - function textFromMeta(name) { - const el = document.querySelector(`meta[name="${name}"], meta[property="${name}"]`); - return el ? (el.getAttribute('content') || '').trim() : ''; - } - - function collectPageContext() { - const title = document.title || textFromMeta('og:title') || location.hostname; - const provider = getProviderForHost(location.hostname); - const turns = collectAiChatTurns(provider); - const promptDraft = collectPromptDraft(provider); - const diagnostics = collectDiagnostics(provider, promptDraft, turns); - return { - title, - url: location.href, - host: location.hostname, - aiProvider: provider ? provider.label : '', - turns, - diagnostics - }; - } - - function collectAiChatTurns(provider) { - if (!provider) return []; - const selectors = provider.turnSelectors.concat(['main article']); - const nodes = Array.from(document.querySelectorAll(selectors.join(','))); - const turns = []; - const seen = new Set(); - for (const node of nodes.slice(-MAX_CAPTURE_NODES)) { - const text = (node.innerText || node.textContent || '').replace(/\s+/g, ' ').trim(); - if (!text || text.length < 12 || seen.has(text)) continue; - seen.add(text); - const explicitRole = node.getAttribute('data-message-author-role'); - const role = explicitRole || inferRole(node, turns.length); - turns.push({ role, text }); - } - return turns.slice(-MAX_CAPTURE_TURNS); - } - - function findSelectorMatch(selectors) { - for (const selector of selectors || []) { - const el = document.querySelector(selector); - if (el) return { el, selector }; - } - return null; - } - - function findTurnSelectorMatch(provider) { - if (!provider) return null; - for (const selector of provider.turnSelectors || []) { - const count = document.querySelectorAll(selector).length; - if (count) return { selector, count }; - } - return null; - } - - function collectPromptDraft(provider) { - if (!provider) return ''; - for (const selector of provider.editorSelectors) { - const el = document.querySelector(selector); - if (!el) continue; - const text = ('value' in el ? el.value : el.innerText || el.textContent || '').trim(); - if (text) return text; - } - return ''; - } - - function findEditor(provider) { - const match = findEditorMatch(provider); - return match ? match.el : null; - } - - function findEditorMatch(provider) { - if (!provider) return null; - return findSelectorMatch(provider.editorSelectors); - } - - function findAnchor(provider) { - const match = findAnchorMatch(provider); - return match ? match.el : null; - } - - function findAnchorMatch(provider) { - if (!provider) return null; - const editorMatch = findEditorMatch(provider); - const configuredMatch = findSelectorMatch(provider.anchorSelectors || []); - if (configuredMatch) return { ...configuredMatch, source: 'configured' }; - const editor = editorMatch ? editorMatch.el : null; - if (editor) { - const fallback = editor.closest('form') || editor.parentElement || editor; - if (fallback) return { el: fallback, selector: editorMatch.selector, source: 'editor-fallback' }; - } - return null; - } - - function findAdjacentAnchor(provider) { - const match = findSelectorMatch(provider && provider.adjacentSelectors ? provider.adjacentSelectors : []); - return match ? match.el : null; - } - - function collectDiagnostics(provider, promptDraft, turns) { - const editorMatch = findEditorMatch(provider); - const anchorMatch = findAnchorMatch(provider); - const adjacentMatch = findSelectorMatch(provider && provider.adjacentSelectors ? provider.adjacentSelectors : []); - const sendMatch = findSelectorMatch(provider && provider.sendSelectors ? provider.sendSelectors : []); - const turnMatch = findTurnSelectorMatch(provider); - return { - supportedAiPage: !!provider, - provider: provider ? provider.label : '', - editorFound: !!editorMatch, - editorSelector: editorMatch ? editorMatch.selector : '', - anchorFound: !!anchorMatch, - anchorSelector: anchorMatch ? anchorMatch.selector : '', - anchorSource: anchorMatch ? anchorMatch.source || 'configured' : '', - adjacentSelector: adjacentMatch ? adjacentMatch.selector : '', - sendFound: !!sendMatch, - sendSelector: sendMatch ? sendMatch.selector : '', - turnSelector: turnMatch ? turnMatch.selector : '', - turnSelectorCount: turnMatch ? turnMatch.count : 0, - matchedSelectors: { - editor: editorMatch ? editorMatch.selector : '', - anchor: anchorMatch ? anchorMatch.selector : '', - anchorSource: anchorMatch ? anchorMatch.source || 'configured' : '', - adjacent: adjacentMatch ? adjacentMatch.selector : '', - send: sendMatch ? sendMatch.selector : '', - turn: turnMatch ? turnMatch.selector : '' - }, - placement: provider ? provider.placement || 'input-corner' : '', - promptLength: String(promptDraft || '').length, - turnCount: Array.isArray(turns) ? turns.length : 0, - memoryWidgetVisible: false, - checkedAt: new Date().toISOString() - }; - } - - function browserSyncKey(provider, turns) { - const last = (turns || []).slice(-3).map((turn) => `${turn.role || 'unknown'}:${turn.text || ''}`).join('\n'); - return `${provider.id}:${location.href}:${(turns || []).length}:${last}`; - } - - function scheduleConversationSync(provider) { - if (!provider) return; - const turns = collectAiChatTurns(provider); - const hasConversation = turns.some((turn) => turn && turn.text && String(turn.text).trim().length >= 12); - if (!hasConversation) return; - const key = browserSyncKey(provider, turns); - if (key === lastSyncKey) return; - if (key === pendingSyncKey) return; - if (syncTimer) clearTimeout(syncTimer); - pendingSyncKey = key; - syncTimer = setTimeout(() => { - const freshTurns = collectAiChatTurns(provider); - const nextKey = browserSyncKey(provider, freshTurns); - pendingSyncKey = ''; - if (nextKey === lastSyncKey || !freshTurns.length) return; - lastSyncKey = nextKey; - chrome.runtime.sendMessage({ type: 'SYNC_PAGE_CONVERSATION' }, () => {}); - }, 1200); - } - - function bootConversationSync() { - const provider = getProviderForHost(location.hostname); - if (!provider) return; - const scan = () => { - if (document.visibilityState === 'hidden') return; - scheduleConversationSync(provider); - }; - const attach = () => { - const editor = findEditor(provider); - if (!editor || editor.__agentMemoryBound) return; - editor.__agentMemoryBound = true; - ['input', 'keyup', 'paste', 'compositionend'].forEach((eventName) => { - editor.addEventListener(eventName, () => { - scheduleConversationSync(provider); - }, true); - }); - scheduleConversationSync(provider); - }; - attach(); - [800, 2400, 5200, 9000].forEach((delay) => setTimeout(scan, delay)); - if (document.addEventListener) document.addEventListener('visibilitychange', scan); - if (typeof setInterval === 'function') setInterval(scan, 15000); - new MutationObserver(() => { - attach(); - scheduleConversationSync(provider); - }).observe(document.documentElement, { childList: true, subtree: true }); - } - - function inferRole(node, index) { - const label = `${node.getAttribute('aria-label') || ''} ${node.className || ''}`.toLowerCase(); - if (/user|human|you|用户/.test(label)) return 'user'; - if (/assistant|agent|model|claude|chatgpt|gemini|回答/.test(label)) return 'assistant'; - return index % 2 === 0 ? 'user' : 'assistant'; - } - - chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { - if (message && message.type === 'AGENT_MEMORY_LAB_PING') { - sendResponse({ ok: true }); - return true; - } - if (message && message.type === 'AGENT_MEMORY_LAB_COLLECT_PAGE') { - sendResponse({ ok: true, page: collectPageContext() }); - return true; - } - return false; - }); - - bootConversationSync(); -})(); diff --git a/browser-extension/icons/icon.svg b/browser-extension/icons/icon.svg deleted file mode 100644 index a4b974e9..00000000 --- a/browser-extension/icons/icon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/browser-extension/icons/icon128.png b/browser-extension/icons/icon128.png deleted file mode 100644 index 2babc3d6..00000000 Binary files a/browser-extension/icons/icon128.png and /dev/null differ diff --git a/browser-extension/icons/icon16.png b/browser-extension/icons/icon16.png deleted file mode 100644 index 8aa1fe5b..00000000 Binary files a/browser-extension/icons/icon16.png and /dev/null differ diff --git a/browser-extension/icons/icon32.png b/browser-extension/icons/icon32.png deleted file mode 100644 index 12bf0d13..00000000 Binary files a/browser-extension/icons/icon32.png and /dev/null differ diff --git a/browser-extension/icons/icon48.png b/browser-extension/icons/icon48.png deleted file mode 100644 index f3ef04e5..00000000 Binary files a/browser-extension/icons/icon48.png and /dev/null differ diff --git a/browser-extension/manifest.json b/browser-extension/manifest.json deleted file mode 100644 index 01fd8a84..00000000 --- a/browser-extension/manifest.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "manifest_version": 3, - "name": "Agent Memory Lab", - "short_name": "Memory Lab", - "version": "0.1.0", - "description": "自动抓取网页 AI 会话,并同步到本地 Agent Memory Lab 工作台。", - "action": { - "default_title": "Agent Memory Lab", - "default_popup": "popup.html" - }, - "side_panel": { - "default_path": "sidepanel.html" - }, - "options_page": "options.html", - "permissions": ["activeTab", "alarms", "contextMenus", "sidePanel", "scripting", "storage", "tabs"], - "host_permissions": ["http://localhost:3111/*", "http://127.0.0.1:3111/*", "http://localhost:3114/*", "http://127.0.0.1:3114/*"], - "background": { - "service_worker": "service-worker.js", - "type": "module" - }, - "content_scripts": [ - { - "matches": [""], - "js": ["content-script.js"], - "run_at": "document_idle" - } - ], - "icons": { - "16": "icons/icon16.png", - "32": "icons/icon32.png", - "48": "icons/icon48.png", - "128": "icons/icon128.png" - } -} diff --git a/browser-extension/options.html b/browser-extension/options.html deleted file mode 100644 index ef56b094..00000000 --- a/browser-extension/options.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - Agent Memory Lab 设置 - - - -
-

设置

连接本地 Agent Memory Lab 服务

- - - - -
-
- - - diff --git a/browser-extension/options.js b/browser-extension/options.js deleted file mode 100644 index 112c5bc9..00000000 --- a/browser-extension/options.js +++ /dev/null @@ -1,17 +0,0 @@ -import { getSettings } from './config.js'; - -const $ = (id) => document.getElementById(id); -const settings = await getSettings(); -$('apiBase').value = settings.apiBase; -$('viewerBase').value = settings.viewerBase; -$('secret').value = settings.secret; - -$('save').addEventListener('click', async () => { - await chrome.storage.sync.set({ - apiBase: $('apiBase').value.trim() || 'http://localhost:3111', - viewerBase: $('viewerBase').value.trim(), - secret: $('secret').value.trim() - }); - $('message').textContent = '已保存'; - $('message').className = 'message ok'; -}); diff --git a/browser-extension/popup.css b/browser-extension/popup.css deleted file mode 100644 index c65bde6c..00000000 --- a/browser-extension/popup.css +++ /dev/null @@ -1,158 +0,0 @@ -:root { - color-scheme: light; - --ink: #0c0a09; - --muted: #777169; - --faint: #a8a29e; - --line: #e7e5e4; - --paper: #ffffff; - --subtle: #f0efed; - --accent: #292524; -} -* { box-sizing: border-box; } -body { - width: 340px; - margin: 0; - color: var(--ink); - background: #f7f7f6; - font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; -} -.panel { padding: 14px; } -header { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; margin-bottom: 12px; } -h1 { margin: 0; font-size: 17px; letter-spacing: 0; } -p { margin: 3px 0 0; color: var(--muted); font-size: 12px; } -.window-actions { - display: flex; - flex: 0 0 auto; - align-items: center; - gap: 6px; -} -.icon-btn { - display: grid; - width: 32px; - height: 32px; - min-height: 32px; - place-items: center; - padding: 0; - border-radius: 999px; - border: 1px solid var(--line); - background: var(--paper); - color: var(--ink); - font-size: 17px; - font-weight: 650; - line-height: 1; - cursor: pointer; -} -.icon-btn:hover { border-color: #d6d3d1; background: #fafafa; } -.icon-btn svg { - width: 15px; - height: 15px; - fill: none; - stroke: currentColor; - stroke-width: 2; - stroke-linecap: round; - stroke-linejoin: round; -} -.page-card, .trial-card, .review-card, .lesson-box, .recent-card { display: block; padding: 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--paper); margin-bottom: 10px; } -.sync-card { - display: grid; - gap: 8px; - margin-bottom: 10px; - padding: 15px; - border: 1px solid #d7e5d8; - border-radius: 8px; - background: #f8fbf8; -} -.sync-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; -} -.page-summary { background: rgba(255, 255, 255, 0.82); } -.label, .lesson-box span { color: var(--faint); font-size: 12px; } -.hint { margin-top: 2px; color: var(--muted); font-size: 11px; } -.trial-card { display: grid; grid-template-columns: 1fr auto; gap: 6px 8px; align-items: start; } -.trial-title { margin-top: 2px; color: var(--ink); font-size: 13px; font-weight: 720; } -.trial-text { grid-column: 1 / -1; color: var(--muted); font-size: 11px; line-height: 1.45; } -.status-title { - color: var(--ink); - font-size: 18px; - font-weight: 760; - line-height: 1.25; -} -.sync-badge { - flex: 0 0 auto; - max-width: 112px; - min-height: 24px; - padding: 3px 9px; - border: 1px solid var(--line); - border-radius: 999px; - color: var(--muted); - background: var(--subtle); - font-size: 11px; - font-weight: 720; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.sync-badge.ready { - border-color: #d7e5d8; - color: #517355; - background: #edf4ee; -} -.sync-badge.waiting { - border-color: #f0d7b8; - color: #9a5c22; - background: #fff4e8; -} -.title { margin-top: 4px; font-weight: 700; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.url { margin-top: 2px; color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -button { border: 1px solid var(--line); background: var(--paper); color: var(--ink); border-radius: 999px; min-height: 38px; padding: 0 13px; font-weight: 650; cursor: pointer; } -button.primary { background: var(--accent); color: #fff; border-color: var(--accent); } -button.wide { width: 100%; } -button.text-button { min-height: 28px; padding: 0 8px; border: 0; background: transparent; color: var(--muted); font-size: 12px; } -.scan-link { - min-height: 26px; - padding: 0 2px 0 8px; -} -button:disabled { opacity: .55; cursor: default; } -textarea, .draft-input { width: 100%; margin: 8px 0; resize: vertical; border: 1px solid var(--line); border-radius: 8px; padding: 9px; font: inherit; background: #fff; color: var(--ink); } -.review-head { display: flex; justify-content: space-between; gap: 8px; align-items: flex-start; } -.draft-input { margin-bottom: 0; font-weight: 650; } -.draft-assist { color: #517355; font-size: 12px; line-height: 1.4; } -.advanced-draft { margin: 0 0 8px; border: 1px solid var(--line); border-radius: 8px; background: #fff; } -.advanced-draft summary { padding: 8px 9px; color: var(--muted); cursor: pointer; font-size: 12px; font-weight: 700; list-style: none; } -.advanced-draft summary::-webkit-details-marker { display: none; } -.advanced-draft summary::after { content: '+'; float: right; color: var(--faint); } -.advanced-draft[open] summary { border-bottom: 1px solid var(--line); } -.advanced-draft[open] summary::after { content: '-'; } -.advanced-draft .draft-fields, .advanced-draft .draft-check { margin: 8px; } -.draft-fields { display: grid; grid-template-columns: 1fr 1.15fr; gap: 8px; margin: 0 0 8px; } -.draft-fields label { min-width: 0; } -.draft-fields span, .draft-check span { display: block; margin-bottom: 4px; color: var(--faint); font-size: 11px; font-weight: 650; } -.draft-fields select, .draft-fields input { width: 100%; height: 34px; border: 1px solid var(--line); border-radius: 8px; padding: 0 9px; background: #fff; color: var(--ink); font: inherit; font-size: 12px; } -.draft-check { display: flex; align-items: center; gap: 7px; margin: 0 0 8px; color: var(--muted); font-size: 12px; } -.draft-check input { width: 15px; height: 15px; margin: 0; accent-color: var(--accent); } -.draft-check span { margin: 0; color: var(--muted); } -.draft-meta { margin: -2px 0 8px; color: var(--faint); font-size: 11px; min-height: 15px; } -.message { min-height: 18px; color: var(--muted); font-size: 12px; } -.message.error { color: #9d342f; } -.message.ok { color: #517355; } -.version-foot { - margin-top: 6px; - color: var(--faint); - font-size: 11px; - text-align: center; -} -.recent-list { display: grid; gap: 7px; margin-top: 8px; } -.compact-history { padding: 0; overflow: hidden; } -.compact-history summary { min-height: 40px; padding: 0 14px; color: var(--faint); font-size: 12px; font-weight: 650; cursor: pointer; list-style: none; } -.compact-history summary::-webkit-details-marker { display: none; } -.compact-history summary::after { content: '+'; float: right; color: var(--faint); font-size: 16px; } -.compact-history[open] summary { border-bottom: 1px solid var(--line); } -.compact-history[open] summary::after { content: '-'; } -.compact-history .recent-list { margin: 10px 14px 14px; } -.recent-item { min-width: 0; padding-top: 7px; border-top: 1px solid var(--line); } -.recent-item:first-child { padding-top: 0; border-top: 0; } -.recent-title { color: var(--ink); font-size: 12px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.recent-meta { color: var(--faint); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } diff --git a/browser-extension/popup.html b/browser-extension/popup.html deleted file mode 100644 index 9dd9259a..00000000 --- a/browser-extension/popup.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - Agent Memory Lab - - - -
-
-
-

会话同步

-

正在检查本地工作台...

-
-
- - -
-
- -
-
- 检查中 - -
-
检查中
-
正在检查当前网页。
-
- -
-
当前页面
-
读取中...
-
-
-
- -
- 最近同步 -
暂无记录
-
- -
-
Extension v0.1.0
-
- - - diff --git a/browser-extension/popup.js b/browser-extension/popup.js deleted file mode 100644 index d7c54d32..00000000 --- a/browser-extension/popup.js +++ /dev/null @@ -1,112 +0,0 @@ -import { getSettings, resolveViewerBase } from './config.js'; -const $ = (id) => document.getElementById(id); -let settings = await getSettings(); -let latestCapture = null; - -function renderVersion() { - const manifest = chrome.runtime && chrome.runtime.getManifest ? chrome.runtime.getManifest() : {}; - $('versionInfo').textContent = `Extension v${manifest.version || '0.1.0'}`; -} - -function setMessage(text, kind = '') { - $('message').textContent = text || ''; - $('message').className = `message ${kind}`.trim(); -} - -async function send(type, payload = {}) { - const response = await chrome.runtime.sendMessage({ type, ...payload }); - if (!response || !response.ok) throw new Error((response && response.error) || '操作失败'); - return response.data; -} - -function renderPage(capture) { - const page = capture && capture.page ? capture.page : capture; - $('pageTitle').textContent = page.title || '当前页面'; - $('pageUrl').textContent = page.url || ''; -} - -function renderSyncState(capture) { - latestCapture = capture; - const page = capture && capture.page ? capture.page : {}; - const provider = capture && capture.conversation && capture.conversation.provider ? capture.conversation.provider : ''; - const turns = capture && capture.conversation && Array.isArray(capture.conversation.turns) ? capture.conversation.turns : []; - $('syncBadge').textContent = turns.length ? `${turns.length} 条` : provider ? '等待会话' : '非 AI 页'; - $('syncBadge').className = `sync-badge ${turns.length ? 'ready' : provider ? 'waiting' : ''}`; - $('statusTitle').textContent = turns.length ? '已自动抓取' : provider ? '等待真实会话' : '当前不是 AI 会话页'; - $('draftAssist').textContent = turns.length - ? `已读到 ${turns.length} 条网页对话。保持页面打开,后续变化会自动同步。` - : provider ? '还没有读到真实对话。展开历史消息或发送一轮对话后会自动同步。' : '打开 ChatGPT、Claude、Gemini 等 AI 会话页后,插件会自动同步。'; - $('draftMeta').textContent = `${provider || page.typeLabel || page.host || '浏览器'} · 只同步会话原文`; - $('syncNow').disabled = false; -} - -function renderRecent(items) { - if (!items || !items.length) { - $('recentList').textContent = '暂无记录'; - return; - } - $('recentList').innerHTML = items.slice(0, 4).map((item) => ` -
-
${escapeHtml(item.title || '未命名页面')}
-
会话同步 · ${escapeHtml(item.host || '')}
-
- `).join(''); -} - -function escapeHtml(value) { - return String(value).replace(/[&<>'"]/g, (char) => ({ - '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' - }[char])); -} - -async function refreshRecent() { - try { - renderRecent(await send('RECENT_CAPTURES')); - } catch { - renderRecent([]); - } -} - -async function refresh() { - try { - const health = await send('HEALTH'); - $('status').textContent = health && health.status === 'ok' ? '本地工作台已连接' : '本地工作台可访问'; - } catch { - $('status').textContent = '未连接本地工作台'; - } - - try { - const capture = await send('COLLECT_PAGE'); - renderPage(capture); - renderSyncState(capture); - } catch (err) { - $('pageTitle').textContent = '无法读取当前页面'; - $('pageUrl').textContent = err.message || ''; - $('draftMeta').textContent = '当前页面不可读取'; - } - - await refreshRecent(); -} - -$('syncNow').addEventListener('click', async () => { - $('syncNow').disabled = true; - setMessage('正在扫描已打开的 AI 会话...'); - try { - const data = await send('SYNC_OPEN_AI_TABS', { force: true }); - await refreshRecent(); - setMessage(`已补扫 ${data.scanned || 0} 个 AI 会话标签页,同步 ${data.synced || 0} 个`, 'ok'); - } catch (err) { - setMessage(err.message || '同步失败', 'error'); - } finally { - $('syncNow').disabled = false; - } -}); - -$('openWorkbench').addEventListener('click', async () => { - await send('OPEN_VIEWER', { tab: 'sessions' }).catch(async () => chrome.tabs.create({ url: `${await resolveViewerBase(settings)}/#sessions` })); - window.close(); -}); -$('closePopup').addEventListener('click', () => window.close()); - -renderVersion(); -refresh(); diff --git a/browser-extension/service-worker.js b/browser-extension/service-worker.js deleted file mode 100644 index 88e3eed4..00000000 --- a/browser-extension/service-worker.js +++ /dev/null @@ -1,217 +0,0 @@ -import { createCaptureRecord, createPageCapture } from './shared/schema.js'; -import { agentMemoryApi, openViewer } from './shared/api.js'; - -const RECENT_KEY = 'recentCaptures'; -const SYNC_CACHE_KEY = 'autoSyncCache'; -const AUTO_SYNC_ALARM = 'agent-memory-lab-auto-sync'; -const AI_PAGE_HOSTS = [ - 'chatgpt.com', - 'chat.openai.com', - 'claude.ai', - 'gemini.google.com', - 'perplexity.ai', - 'www.perplexity.ai', - 'grok.com', - 'x.ai', - 'chat.deepseek.com', - 'deepseek.com' -]; - -async function getActiveTab() { - const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); - return tab; -} - -async function collectPage() { - const tab = await getActiveTab(); - if (!tab || !tab.id) throw new Error('没有可读取的当前页面'); - try { - const response = await chrome.tabs.sendMessage(tab.id, { type: 'AGENT_MEMORY_LAB_COLLECT_PAGE' }); - if (response && response.ok) return createPageCapture(response.page); - } catch {} - return createPageCapture({ - title: tab.title || '当前页面', - url: tab.url || '' - }); -} - -function isAiConversationTab(tab = {}) { - const raw = String(tab.url || ''); - if (!/^https?:\/\//i.test(raw)) return false; - try { - const url = new URL(raw); - return AI_PAGE_HOSTS.some((host) => url.hostname === host || url.hostname.endsWith(`.${host}`)); - } catch { - return false; - } -} - -async function ensureContentScript(tabId) { - try { - await chrome.tabs.sendMessage(tabId, { type: 'AGENT_MEMORY_LAB_PING' }); - return true; - } catch {} - try { - await chrome.scripting.executeScript({ target: { tabId }, files: ['content-script.js'] }); - return true; - } catch { - return false; - } -} - -async function collectTabPage(tab) { - if (!tab || !tab.id) throw new Error('没有可读取的标签页'); - await ensureContentScript(tab.id); - try { - const response = await chrome.tabs.sendMessage(tab.id, { type: 'AGENT_MEMORY_LAB_COLLECT_PAGE' }); - if (response && response.ok) return createPageCapture(response.page); - } catch {} - return createPageCapture({ - title: tab.title || '当前页面', - url: tab.url || '' - }); -} - -function syncKeyForCapture(capture) { - const page = capture && capture.page ? capture.page : {}; - const conversation = capture && capture.conversation ? capture.conversation : {}; - const turns = Array.isArray(conversation.turns) ? conversation.turns : []; - const last = turns.slice(-3).map((turn) => `${turn.role || 'unknown'}:${turn.text || ''}`).join('\n'); - return `${page.url || page.title || 'browser'}:${conversation.provider || page.host || 'browser'}:${turns.length}:${last}`; -} - -async function shouldSyncCapture(capture, force = false) { - const page = capture && capture.page ? capture.page : {}; - const provider = capture && capture.conversation && capture.conversation.provider ? capture.conversation.provider : ''; - const turns = capture && capture.conversation && Array.isArray(capture.conversation.turns) ? capture.conversation.turns : []; - if (page.type !== 'ai-chat' || !provider) return false; - if (!turns.some((turn) => turn && turn.text && String(turn.text).trim().length >= 12)) return false; - const key = syncKeyForCapture(capture); - const stored = await chrome.storage.local.get([SYNC_CACHE_KEY]); - const cache = stored[SYNC_CACHE_KEY] && typeof stored[SYNC_CACHE_KEY] === 'object' ? stored[SYNC_CACHE_KEY] : {}; - if (!force && cache[key]) return false; - cache[key] = Date.now(); - const entries = Object.entries(cache).sort((a, b) => Number(b[1]) - Number(a[1])).slice(0, 200); - await chrome.storage.local.set({ [SYNC_CACHE_KEY]: Object.fromEntries(entries) }); - return true; -} - -async function rememberRecent(capture, kind, result) { - const stored = await chrome.storage.local.get([RECENT_KEY]); - const list = Array.isArray(stored[RECENT_KEY]) ? stored[RECENT_KEY] : []; - const next = [createCaptureRecord(capture, kind, result), ...list].slice(0, 8); - await chrome.storage.local.set({ [RECENT_KEY]: next }); - return next; -} - -async function syncPageConversation() { - const capture = await collectPage(); - return syncCapture(capture, { force: true }); -} - -async function syncCapture(capture, { force = false } = {}) { - if (!(await shouldSyncCapture(capture, force))) return { capture, result: { skipped: true } }; - const turns = capture && capture.conversation && Array.isArray(capture.conversation.turns) ? capture.conversation.turns : []; - const result = await agentMemoryApi('/agentmemory/review', { - method: 'POST', - body: JSON.stringify({ - mode: 'sync', - kind: 'session', - source: 'browser-sync', - title: capture.page.title, - content: '', - page: capture.page, - conversation: capture.conversation, - payload: { - project: capture.conversation && capture.conversation.provider ? capture.conversation.provider : capture.page.host || 'browser', - projectScope: 'page', - sourceLabel: capture.conversation && capture.conversation.provider ? capture.conversation.provider : capture.page.typeLabel || '浏览器', - provider: capture.conversation && capture.conversation.provider, - pageType: capture.page.type, - tags: ['browser', 'auto-sync', capture.page.type ? `page:${capture.page.type}` : '', capture.conversation && capture.conversation.provider ? `source:${capture.conversation.provider.toLowerCase()}` : ''].filter(Boolean), - turnCount: turns.length - } - }) - }); - await rememberRecent(capture, 'sync', result); - return { capture, result }; -} - -async function syncOpenAiConversationTabs({ force = false } = {}) { - const tabs = (await chrome.tabs.query({})).filter(isAiConversationTab); - const results = []; - for (const tab of tabs) { - try { - const capture = await collectTabPage(tab); - const synced = await syncCapture(capture, { force }); - results.push({ ok: true, tabId: tab.id, title: capture.page.title, skipped: !!(synced.result && synced.result.skipped) }); - } catch (err) { - results.push({ ok: false, tabId: tab.id, title: tab.title || '', error: err.message || String(err) }); - } - } - return { - scanned: tabs.length, - synced: results.filter((item) => item.ok && !item.skipped).length, - skipped: results.filter((item) => item.ok && item.skipped).length, - failed: results.filter((item) => !item.ok).length, - results - }; -} - -async function getRecentCaptures() { - const stored = await chrome.storage.local.get([RECENT_KEY]); - return Array.isArray(stored[RECENT_KEY]) ? stored[RECENT_KEY] : []; -} - -async function setupContextMenus() { - chrome.contextMenus.removeAll(() => { - chrome.contextMenus.create({ - id: 'sync-page-conversation', - title: '同步到 Agent Memory Lab 工作台', - contexts: ['page'] - }); - chrome.contextMenus.create({ - id: 'open-workbench', - title: '打开 Agent Memory Lab', - contexts: ['action'] - }); - }); -} - -chrome.runtime.onInstalled.addListener(setupContextMenus); -chrome.runtime.onInstalled.addListener(() => { - chrome.alarms.create(AUTO_SYNC_ALARM, { periodInMinutes: 1 }); -}); -chrome.runtime.onStartup.addListener(() => { - setupContextMenus(); - chrome.alarms.create(AUTO_SYNC_ALARM, { periodInMinutes: 1 }); -}); -chrome.alarms.onAlarm.addListener((alarm) => { - if (alarm.name === AUTO_SYNC_ALARM) syncOpenAiConversationTabs().catch(() => {}); -}); -chrome.tabs.onUpdated.addListener((_tabId, changeInfo, tab) => { - if (changeInfo.status === 'complete' && isAiConversationTab(tab)) { - setTimeout(() => syncOpenAiConversationTabs().catch(() => {}), 2500); - } -}); - -chrome.contextMenus.onClicked.addListener((info, tab) => { - if (info.menuItemId === 'sync-page-conversation') { - syncPageConversation().catch(() => {}); - } - if (info.menuItemId === 'open-workbench') openViewer('dashboard').catch(() => {}); -}); - -chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { - (async () => { - if (message.type === 'HEALTH') return agentMemoryApi('/agentmemory/livez', { method: 'GET' }); - if (message.type === 'COLLECT_PAGE') return collectPage(); - if (message.type === 'RECENT_CAPTURES') return getRecentCaptures(); - if (message.type === 'SYNC_PAGE_CONVERSATION') return syncPageConversation(); - if (message.type === 'SYNC_OPEN_AI_TABS') return syncOpenAiConversationTabs({ force: !!message.force }); - if (message.type === 'OPEN_SIDE_PANEL') return chrome.sidePanel.open({ windowId: message.windowId }); - if (message.type === 'OPEN_VIEWER') return openViewer(message.tab || 'dashboard', message.path || ''); - throw new Error('未知操作'); - })().then((data) => sendResponse({ ok: true, data })).catch((err) => sendResponse({ ok: false, error: err.message || String(err) })); - return true; -}); diff --git a/browser-extension/shared/api.js b/browser-extension/shared/api.js deleted file mode 100644 index 19cad8c6..00000000 --- a/browser-extension/shared/api.js +++ /dev/null @@ -1,22 +0,0 @@ -import { getSettings, authHeaders, resolveApiBase, resolveViewerBase } from '../config.js'; - -export async function agentMemoryApi(path, options = {}) { - const settings = await getSettings(); - const apiBase = await resolveApiBase(settings); - const res = await fetch(`${apiBase}${path}`, { - ...options, - headers: { ...authHeaders(settings), ...(options.headers || {}) } - }); - const text = await res.text(); - let data = null; - try { data = text ? JSON.parse(text) : null; } catch { data = { raw: text }; } - if (!res.ok) throw new Error((data && (data.error || data.message)) || `HTTP ${res.status}`); - return data; -} - -export async function openViewer(tab = 'dashboard', path = '') { - const settings = await getSettings(); - const viewerBase = await resolveViewerBase(settings); - if (path) return chrome.tabs.create({ url: `${viewerBase}${path.startsWith('/') ? path : `/${path}`}` }); - return chrome.tabs.create({ url: `${viewerBase}/#${tab}` }); -} diff --git a/browser-extension/shared/page-types.js b/browser-extension/shared/page-types.js deleted file mode 100644 index 45468811..00000000 --- a/browser-extension/shared/page-types.js +++ /dev/null @@ -1,28 +0,0 @@ -import { getProviderForHost } from './site-config.js'; - -export function detectAiProvider(page) { - const provider = getProviderForHost(page.host || page.url || ''); - return provider ? provider.label : ''; -} - -export function detectPageType(page) { - const host = String(page.host || '').toLowerCase(); - const url = String(page.url || '').toLowerCase(); - if (detectAiProvider(page)) return 'ai-chat'; - if (host.includes('github.com')) return 'github'; - if (host.includes('feishu.cn') || host.includes('larksuite.com')) return 'feishu'; - if (host.includes('notion.so')) return 'notion'; - if (url.endsWith('.pdf') || host.includes('arxiv.org') || host.includes('doi.org')) return 'paper'; - if (host.includes('chrome.google.com') || host.includes('chromewebstore.google.com')) return 'extension-store'; - return 'webpage'; -} - -export const PAGE_TYPE_LABELS = { - 'ai-chat': 'AI 对话', - github: 'GitHub', - feishu: '飞书', - notion: 'Notion', - paper: '论文 / PDF', - 'extension-store': '插件商店', - webpage: '网页' -}; diff --git a/browser-extension/shared/schema.js b/browser-extension/shared/schema.js deleted file mode 100644 index c32c58ec..00000000 --- a/browser-extension/shared/schema.js +++ /dev/null @@ -1,97 +0,0 @@ -import { PAGE_TYPE_LABELS, detectAiProvider, detectPageType } from './page-types.js'; - -export const CAPTURE_SCHEMA_VERSION = 1; -export const MAX_CAPTURE_TURNS = 160; -export const MAX_TURN_TEXT_LENGTH = 6000; - -export function normalizeUrl(rawUrl) { - try { - const url = new URL(rawUrl || ''); - return { - url: url.href, - host: url.hostname, - origin: url.origin - }; - } catch { - return { url: rawUrl || '', host: '', origin: '' }; - } -} - -export function createPageCapture(page = {}) { - const normalized = normalizeUrl(page.url); - const now = new Date().toISOString(); - const pageType = detectPageType({ ...page, ...normalized }); - return { - schemaVersion: CAPTURE_SCHEMA_VERSION, - capturedAt: now, - source: 'browser-extension', - page: { - type: pageType, - typeLabel: PAGE_TYPE_LABELS[pageType] || '网页', - title: String(page.title || '当前页面').trim(), - url: normalized.url, - host: page.host || normalized.host, - origin: normalized.origin - }, - conversation: { - provider: String(page.aiProvider || detectAiProvider({ ...page, ...normalized }) || ''), - turns: Array.isArray(page.turns) ? page.turns.map(normalizeTurn).filter(Boolean).slice(-MAX_CAPTURE_TURNS) : [] - }, - diagnostics: normalizeDiagnostics(page.diagnostics) - }; -} - -function normalizeDiagnostics(value) { - const input = value && typeof value === 'object' ? value : {}; - const matched = input.matchedSelectors && typeof input.matchedSelectors === 'object' ? input.matchedSelectors : {}; - return { - supportedAiPage: !!input.supportedAiPage, - provider: String(input.provider || ''), - editorFound: !!input.editorFound, - editorSelector: String(input.editorSelector || ''), - anchorFound: !!input.anchorFound, - anchorSelector: String(input.anchorSelector || ''), - anchorSource: String(input.anchorSource || ''), - adjacentSelector: String(input.adjacentSelector || ''), - sendFound: !!input.sendFound, - sendSelector: String(input.sendSelector || ''), - turnSelector: String(input.turnSelector || ''), - turnSelectorCount: Number.isFinite(Number(input.turnSelectorCount)) ? Number(input.turnSelectorCount) : 0, - matchedSelectors: { - editor: String(matched.editor || input.editorSelector || ''), - anchor: String(matched.anchor || input.anchorSelector || ''), - anchorSource: String(matched.anchorSource || input.anchorSource || ''), - adjacent: String(matched.adjacent || input.adjacentSelector || ''), - send: String(matched.send || input.sendSelector || ''), - turn: String(matched.turn || input.turnSelector || '') - }, - placement: String(input.placement || ''), - promptLength: Number.isFinite(Number(input.promptLength)) ? Number(input.promptLength) : 0, - turnCount: Number.isFinite(Number(input.turnCount)) ? Number(input.turnCount) : 0, - memoryWidgetVisible: !!input.memoryWidgetVisible, - checkedAt: String(input.checkedAt || '') - }; -} - -function normalizeTurn(turn) { - if (!turn || !turn.text) return null; - return { - role: turn.role === 'user' || turn.role === 'assistant' ? turn.role : 'unknown', - text: String(turn.text).trim().slice(0, MAX_TURN_TEXT_LENGTH) - }; -} - -export function createCaptureRecord(capture, kind, result) { - const item = result && result.item ? result.item : null; - return { - id: `${Date.now()}-${Math.random().toString(16).slice(2)}`, - kind, - title: capture.page.title, - url: capture.page.url, - host: capture.page.host, - type: capture.page.type, - typeLabel: capture.page.typeLabel, - savedAt: new Date().toISOString(), - resultId: result && (result.id || result.memoryId || result.lessonId || result.actionId || (item && item.id) || '') - }; -} diff --git a/browser-extension/shared/site-config.js b/browser-extension/shared/site-config.js deleted file mode 100644 index 53338223..00000000 --- a/browser-extension/shared/site-config.js +++ /dev/null @@ -1,88 +0,0 @@ -export const AI_PROVIDERS = { - agentmemory_demo: { - id: 'agentmemoryDemo', - label: 'Agent Memory Demo', - hosts: ['localhost', '127.0.0.1'], - pathIncludes: ['/demo/browser-extension.html'], - editorSelectors: ['#agentmemory-demo-input', '[data-agentmemory-demo-input]', '[contenteditable="true"]'], - anchorSelectors: ['#agentmemory-demo-input', 'form', 'main'], - placement: 'input-corner', - turnSelectors: ['[data-message-author-role]', 'main article'], - sendSelectors: ['button.primary'] - }, - chatgpt: { - id: 'chatgpt', - label: 'ChatGPT', - hosts: ['chatgpt.com', 'chat.openai.com'], - editorSelectors: ['#prompt-textarea', '[data-testid="prompt-textarea"]', 'textarea[placeholder]', 'textarea', '[contenteditable="true"]'], - anchorSelectors: ['[data-testid="composer-trailing-actions"]', '.composer-trailing-actions', 'form', 'main form'], - adjacentSelectors: ['button[aria-label="Dictate button"]', 'button[aria-label*="mic" i]', 'button[aria-label*="voice" i]'], - placement: 'toolbar-end', - turnSelectors: ['[data-message-author-role]', '[data-testid*="conversation-turn"]', 'main article'], - sendSelectors: ['button[data-testid="send-button"]', 'button[aria-label*="Send"]'] - }, - claude: { - id: 'claude', - label: 'Claude', - hosts: ['claude.ai'], - editorSelectors: ['div.ProseMirror[contenteditable="true"]', 'div[contenteditable="true"]', 'textarea', 'p[data-placeholder]'], - anchorSelectors: ['form', '[data-testid*="input"]', '[contenteditable="true"]'], - placement: 'input-corner', - turnSelectors: ['[data-testid*="message"]', 'main [class*="font-claude"]', 'main article'], - sendSelectors: ['button[aria-label*="Send"]', 'button[type="submit"]'] - }, - gemini: { - id: 'gemini', - label: 'Gemini', - hosts: ['gemini.google.com'], - editorSelectors: ['rich-textarea [contenteditable="true"]', 'rich-textarea textarea', '[contenteditable="true"]', 'textarea'], - anchorSelectors: ['rich-textarea', '.input-area-container', 'form', '[contenteditable="true"]'], - placement: 'input-corner', - turnSelectors: ['user-query', 'model-response', 'message-content', 'main article'], - sendSelectors: ['button[aria-label*="Send"]', 'button[aria-label*="提交"]'] - }, - perplexity: { - id: 'perplexity', - label: 'Perplexity', - hosts: ['perplexity.ai', 'www.perplexity.ai'], - editorSelectors: ['textarea[placeholder]', 'textarea', '[contenteditable="true"]'], - anchorSelectors: ['form', 'textarea[placeholder]', '[contenteditable="true"]'], - placement: 'input-corner', - turnSelectors: ['[data-testid*="thread"]', '[class*="prose"]', 'main article'], - sendSelectors: ['button[aria-label*="Submit"]', 'button[aria-label*="Send"]'] - }, - grok: { - id: 'grok', - label: 'Grok', - hosts: ['grok.com', 'x.ai'], - editorSelectors: ['textarea', '[contenteditable="true"]'], - anchorSelectors: ['form', 'textarea', '[contenteditable="true"]'], - placement: 'input-corner', - turnSelectors: ['[data-testid*="message"]', 'main article'], - sendSelectors: ['button[aria-label*="Send"]'] - }, - deepseek: { - id: 'deepseek', - label: 'DeepSeek', - hosts: ['chat.deepseek.com', 'deepseek.com'], - editorSelectors: ['textarea', '[contenteditable="true"]'], - anchorSelectors: ['form', 'textarea', '[contenteditable="true"]'], - placement: 'input-corner', - turnSelectors: ['[class*="message"]', 'main article'], - sendSelectors: ['button[aria-label*="Send"]'] - } -}; - -export function getProviderForHost(hostname) { - const host = String(hostname || '').toLowerCase(); - const path = typeof location !== 'undefined' && location && location.pathname ? String(location.pathname) : ''; - return Object.values(AI_PROVIDERS).find((provider) => { - const hostMatches = provider.hosts.some((item) => host === item || host.endsWith(`.${item}`)); - if (!hostMatches) return false; - return !provider.pathIncludes || provider.pathIncludes.some((item) => path.includes(item)); - }) || null; -} - -export function isSupportedAiHost(hostname) { - return !!getProviderForHost(hostname); -} diff --git a/browser-extension/sidepanel.css b/browser-extension/sidepanel.css deleted file mode 100644 index 01948787..00000000 --- a/browser-extension/sidepanel.css +++ /dev/null @@ -1,653 +0,0 @@ -:root { - color-scheme: light; - --ink: #0c0a09; - --muted: #777169; - --faint: #a8a29e; - --line: #e7e5e4; - --paper: #ffffff; - --subtle: #f0efed; - --soft-green: #edf4ee; - --green: #51735b; - --warn-bg: #fff4e8; - --warn: #9a5c22; - --black: #11100f; -} - -* { box-sizing: border-box; } - -body { - min-width: 320px; - margin: 0; - color: var(--ink); - background: #f7f7f6; - font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; -} - -.shell { - min-height: 100vh; - padding: 14px; -} - -.topbar { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 12px; -} - -.brand-mark { - display: grid; - width: 34px; - height: 34px; - place-items: center; - border: 1px solid var(--black); - border-radius: 16px; - background: var(--black); - color: #fff; - font-weight: 760; -} - -.brand-copy { min-width: 0; flex: 1; } -.brand-copy h1 { margin: 0; font-size: 17px; letter-spacing: 0; } -.brand-copy p { margin: 1px 0 0; color: var(--muted); font-size: 12px; } - -.window-actions { - display: flex; - flex: 0 0 auto; - align-items: center; - gap: 6px; -} - -.icon-button { - display: grid; - width: 34px; - height: 34px; - min-height: 34px; - place-items: center; - padding: 0; - border: 1px solid var(--line); - border-radius: 16px; - background: var(--paper); - color: var(--ink); - font-size: 17px; -} - -.icon-button svg { - width: 15px; - height: 15px; - fill: none; - stroke: currentColor; - stroke-width: 2; - stroke-linecap: round; - stroke-linejoin: round; -} - -.section { - margin-bottom: 10px; - padding: 12px; - border: 1px solid var(--line); - border-radius: 8px; - background: var(--paper); -} - -.connection-card { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - margin-bottom: 10px; - padding: 10px 11px; - border: 1px solid var(--line); - border-radius: 8px; - background: var(--paper); -} - -.connection-card.connected { - border-color: #d7e5d8; - background: var(--soft-green); -} - -.connection-card.offline { - border-color: #f0d7b8; - background: var(--warn-bg); -} - -.connection-title { - color: var(--ink); - font-size: 13px; - font-weight: 760; -} - -.connection-text { - margin-top: 2px; - color: var(--muted); - font-size: 12px; - line-height: 1.35; -} - -.connection-card button { - flex: 0 0 auto; - min-height: 30px; - padding: 0 10px; - font-size: 12px; -} - -.section-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 8px; -} - -.sync-overview { - display: grid; - gap: 9px; - border-color: #d7e5d8; - background: #f8fbf8; -} - -.sync-state-main { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 10px; -} - -.sync-state-main strong { - display: block; - margin-top: 3px; - color: var(--ink); - font-size: 24px; - font-weight: 760; - line-height: 1.25; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.sync-facts { - display: flex; - gap: 6px; - flex-wrap: wrap; -} - -.sync-facts span { - min-height: 22px; - padding: 2px 7px; - border: 1px solid #d7e5d8; - border-radius: 999px; - color: var(--green); - background: #fff; - font-size: 11px; - font-weight: 650; -} - -.section-actions { - display: inline-flex; - align-items: center; - flex-wrap: wrap; - gap: 6px; -} - -.secondary-section { - padding: 0; - overflow: hidden; -} - -.compact-source { - padding: 0; - overflow: hidden; -} - -.compact-source summary { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - min-height: 42px; - padding: 0 12px; - cursor: pointer; - list-style: none; -} - -.compact-source summary::-webkit-details-marker { display: none; } - -.compact-source summary::after { - content: '+'; - color: var(--faint); - font-size: 16px; - font-weight: 700; -} - -.compact-source[open] summary { - border-bottom: 1px solid var(--line); -} - -.compact-source[open] summary::after { - content: '-'; -} - -.compact-source > :not(summary) { - margin: 10px 12px 12px; -} - -.secondary-section summary { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - min-height: 42px; - padding: 0 12px; - cursor: pointer; - list-style: none; -} - -.secondary-section summary::-webkit-details-marker { display: none; } - -.secondary-section summary::after { - content: '+'; - color: var(--faint); - font-size: 16px; - font-weight: 700; -} - -.secondary-section[open] summary { - border-bottom: 1px solid var(--line); -} - -.secondary-section[open] summary::after { - content: '-'; -} - -.secondary-section > :not(summary) { - margin: 10px 12px 12px; -} - -.eyebrow, .count { - color: var(--faint); - font-size: 12px; - font-weight: 650; -} - -.type-pill, .privacy { - display: inline-flex; - align-items: center; - min-height: 24px; - padding: 0 9px; - border: 1px solid var(--line); - border-radius: 999px; - color: var(--muted); - background: var(--subtle); - font-size: 12px; - font-weight: 650; -} - -.current-page h2 { - margin: 0; - font-size: 16px; - line-height: 1.32; - letter-spacing: 0; -} - -.url { - margin: 4px 0 10px; - color: var(--muted); - font-size: 12px; - overflow-wrap: anywhere; -} - -.privacy.low { - border-color: #d7e5d8; - color: var(--green); - background: var(--soft-green); -} - -.privacy.medium { - border-color: #f0d7b8; - color: var(--warn); - background: var(--warn-bg); -} - -.diagnostic-list { - display: grid; - gap: 7px; -} - -.validation-summary { - display: grid; - gap: 3px; - margin-bottom: 9px; - padding: 9px; - border: 1px solid var(--line); - border-radius: 8px; - background: var(--subtle); -} - -.validation-summary.ready { - border-color: #d7e5d8; - background: var(--soft-green); -} - -.validation-summary.needs-check { - border-color: #f0d7b8; - background: var(--warn-bg); -} - -.validation-summary strong { - color: var(--ink); - font-size: 13px; -} - -.validation-summary span { - color: var(--muted); - font-size: 12px; - line-height: 1.35; -} - -.validation-steps { - display: grid; - gap: 6px; - margin-bottom: 10px; -} - -.validation-step { - display: grid; - grid-template-columns: 22px 1fr; - gap: 8px; - align-items: start; -} - -.validation-step span { - display: grid; - width: 22px; - height: 22px; - place-items: center; - border: 1px solid var(--line); - border-radius: 999px; - color: var(--muted); - background: #fff; - font-size: 11px; - font-weight: 760; -} - -.validation-step.done span { - border-color: #d7e5d8; - color: var(--green); - background: var(--soft-green); -} - -.validation-step p { - margin: 1px 0 0; - color: var(--muted); - font-size: 12px; - line-height: 1.35; -} - -.diagnostic-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - color: var(--muted); - font-size: 12px; -} - -.diagnostic-row strong { - color: var(--ink); - font-weight: 740; -} - -.diagnostic-row.warn strong { color: var(--warn); } - -.command-hint { - margin-top: 8px; - padding: 8px 9px; - border: 1px solid var(--line); - border-radius: 8px; - color: var(--muted); - background: var(--paper); - font-size: 12px; - line-height: 1.35; -} - -.candidate-list, .recent-list, .turn-list { - display: grid; - gap: 8px; -} - -.empty { - color: var(--faint); - font-size: 13px; -} - -.empty-card { - display: grid; - gap: 4px; - padding: 10px; - border: 1px solid var(--line); - border-radius: 8px; - background: var(--subtle); -} - -.empty-card strong { - color: var(--ink); - font-size: 13px; -} - -.empty-card span { - color: var(--muted); - font-size: 12px; - line-height: 1.45; -} - -.candidate, .recent-item, .turn { - min-width: 0; - padding: 9px; - border: 1px solid var(--line); - border-radius: 8px; - background: #fff; -} - -.candidate { - display: grid; - gap: 8px; -} - -.candidate.recommended { - border-color: #d7e5d8; - background: #fbfdfb; -} - -.candidate-badge { - justify-self: start; - min-height: 20px; - padding: 2px 7px; - border-radius: 999px; - color: var(--green); - background: var(--soft-green); - font-size: 11px; - font-weight: 700; -} - -.candidate p, .turn p { - margin: 0; - color: var(--ink); - font-size: 13px; - line-height: 1.45; -} - -.candidate button { - justify-self: start; - min-height: 30px; - padding: 0 10px; - font-size: 12px; -} - -.ghost-button { - min-height: 24px; - padding: 0 8px; - color: var(--muted); - background: #fff; - font-size: 12px; -} - -.draft-input, -.review-draft textarea, -.draft-fields select, -.draft-fields input { - width: 100%; - margin: 0 0 8px; - padding: 9px; - border: 1px solid var(--line); - border-radius: 8px; - background: #fff; - color: var(--ink); - font: inherit; -} - -.draft-input { - font-weight: 700; -} - -.review-draft textarea { - resize: vertical; - line-height: 1.45; -} - -.draft-assist { margin: 0; color: var(--green); font-size: 12px; line-height: 1.35; } - -.advanced-draft { - margin-bottom: 8px; - border: 1px solid var(--line); - border-radius: 8px; - background: #fff; -} - -.advanced-draft summary { - padding: 8px 9px; - color: var(--muted); - cursor: pointer; - font-size: 12px; - font-weight: 700; - list-style: none; -} - -.advanced-draft summary::-webkit-details-marker { display: none; } - -.advanced-draft summary::after { - content: '+'; - float: right; - color: var(--faint); -} - -.advanced-draft[open] summary { - border-bottom: 1px solid var(--line); -} - -.advanced-draft[open] summary::after { - content: '-'; -} - -.advanced-draft .draft-fields, -.advanced-draft .draft-check { - margin: 8px; -} - -.draft-fields { - display: grid; - grid-template-columns: 1fr 1.2fr; - gap: 8px; -} - -.draft-fields label { min-width: 0; } - -.draft-fields span, -.draft-check span { - display: block; - margin-bottom: 4px; - color: var(--faint); - font-size: 11px; - font-weight: 650; -} - -.draft-fields select, -.draft-fields input { - height: 34px; - padding: 0 9px; - font-size: 12px; -} - -.draft-check { - display: flex; - align-items: center; - gap: 7px; - margin: 0 0 8px; - color: var(--muted); - font-size: 12px; -} - -.draft-check input { - width: 15px; - height: 15px; - margin: 0; - accent-color: var(--black); -} - -.draft-check span { margin: 0; color: var(--muted); } - -.draft-meta { - color: var(--faint); - font-size: 11px; -} - -.turn-label, .recent-meta { - margin-bottom: 3px; - color: var(--faint); - font-size: 11px; - font-weight: 700; -} - -.turn p { - display: -webkit-box; - overflow: hidden; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; -} - -.recent-title { - overflow: hidden; - color: var(--ink); - font-size: 13px; - font-weight: 650; - text-overflow: ellipsis; - white-space: nowrap; -} - -button { - min-height: 36px; - border: 1px solid var(--line); - border-radius: 999px; - background: var(--paper); - color: var(--ink); - font: inherit; - font-weight: 700; - cursor: pointer; -} - -button.primary { - border-color: var(--black); - background: var(--black); - color: #fff; -} - -button:disabled { opacity: .55; cursor: default; } - -.message { - min-height: 18px; - color: var(--muted); - font-size: 12px; -} - -.message.ok { color: var(--green); } -.message.error { color: #9d342f; } diff --git a/browser-extension/sidepanel.html b/browser-extension/sidepanel.html deleted file mode 100644 index ced8eeef..00000000 --- a/browser-extension/sidepanel.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - Agent Memory Lab Side Panel - - - -
-
- -
-

会话同步

-

正在连接本地工作台...

-
-
- - - -
-
- -
-
-
检查连接中
-
正在确认能否同步到本地工作台。
-
- -
- -
-
-
- 同步状态 - 0 条 -
- -
-
网页里的 AI 会话会自动同步到工作台。
-
- 网页 - 自动 - 只同步会话原文 -
-
- -
- - 页面来源 - 只同步会话 - -

读取中...

-

-
- - - - - -
- - 最近同步 - -
暂无记录
-
- -
-
- - - diff --git a/browser-extension/sidepanel.js b/browser-extension/sidepanel.js deleted file mode 100644 index dda90305..00000000 --- a/browser-extension/sidepanel.js +++ /dev/null @@ -1,289 +0,0 @@ -const $ = (id) => document.getElementById(id); -const AI_SITE_TEST_CARDS_PATH = '/docs/browser-extension-ai-site-test-cards-cn.md'; -let latestCapture = null; - -function escapeHtml(value) { - return String(value).replace(/[&<>'"]/g, (char) => ({ - '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' - }[char])); -} - -async function send(type, payload = {}) { - const response = await chrome.runtime.sendMessage({ type, ...payload }); - if (!response || !response.ok) throw new Error((response && response.error) || '操作失败'); - return response.data; -} - -function setMessage(text, kind = '') { - $('message').textContent = text || ''; - $('message').className = `message ${kind}`.trim(); -} - -async function copyText(text) { - if (navigator.clipboard && navigator.clipboard.writeText) { - await navigator.clipboard.writeText(text); - return; - } - const textarea = document.createElement('textarea'); - textarea.value = text; - textarea.setAttribute('readonly', ''); - textarea.style.position = 'fixed'; - textarea.style.left = '-9999px'; - document.body.appendChild(textarea); - textarea.select(); - document.execCommand('copy'); - textarea.remove(); -} - -function buildDiagnosticReport(capture) { - const page = capture && capture.page ? capture.page : {}; - const diagnostics = capture && capture.diagnostics ? capture.diagnostics : {}; - const conversation = capture && capture.conversation ? capture.conversation : {}; - const manifest = chrome.runtime && chrome.runtime.getManifest ? chrome.runtime.getManifest() : {}; - const matched = diagnostics.matchedSelectors || {}; - return { - product: 'Agent Memory Lab Browser Extension', - extension: { - name: manifest.name || 'Agent Memory Lab', - version: manifest.version || '', - manifestVersion: manifest.manifest_version || 3 - }, - generatedAt: new Date().toISOString(), - validationGuide: { - title: '浏览器插件真实 AI 站点测试卡', - path: AI_SITE_TEST_CARDS_PATH, - requiredProducts: ['ChatGPT', 'Claude', 'Gemini', 'Perplexity'] - }, - page: { - title: page.title || '', - url: page.url || '', - host: page.host || '', - origin: page.origin || '', - type: page.type || '', - typeLabel: page.typeLabel || '' - }, - ai: { - supportedAiPage: !!diagnostics.supportedAiPage, - provider: diagnostics.provider || conversation.provider || '', - editorFound: !!diagnostics.editorFound, - editorSelector: diagnostics.editorSelector || '', - anchorFound: !!diagnostics.anchorFound, - anchorSelector: diagnostics.anchorSelector || '', - anchorSource: diagnostics.anchorSource || '', - adjacentSelector: diagnostics.adjacentSelector || '', - sendFound: !!diagnostics.sendFound, - sendSelector: diagnostics.sendSelector || '', - turnSelector: diagnostics.turnSelector || '', - turnSelectorCount: diagnostics.turnSelectorCount || 0, - matchedSelectors: { - editor: matched.editor || diagnostics.editorSelector || '', - anchor: matched.anchor || diagnostics.anchorSelector || '', - anchorSource: matched.anchorSource || diagnostics.anchorSource || '', - adjacent: matched.adjacent || diagnostics.adjacentSelector || '', - send: matched.send || diagnostics.sendSelector || '', - turn: matched.turn || diagnostics.turnSelector || '' - }, - placement: diagnostics.placement || '', - promptLength: diagnostics.promptLength || 0, - turnCount: diagnostics.turnCount || 0, - checkedAt: diagnostics.checkedAt || '' - }, - manualValidation: { - diagnosticsCopied: true, - siteInputStillWorks: false, - browser: '填写浏览器名称和版本', - notes: '填写无隐私信息的验收备注' - } - }; -} - -function providerArg(value) { - const provider = String(value || '').trim(); - return provider ? ` --provider "${provider.replace(/"/g, '\\"')}"` : ''; -} - -function buildEvidenceCommand(capture) { - const diagnostics = capture && capture.diagnostics ? capture.diagnostics : {}; - const provider = diagnostics.provider || (capture && capture.conversation && capture.conversation.provider) || ''; - return `npm run wizard:ai-validation-evidence -- --clipboard${providerArg(provider)}`; -} - -function setConnectionState(state, text) { - const card = $('connectionCard'); - card.className = `connection-card ${state}`; - if (state === 'connected') { - $('connectionTitle').textContent = '正在自动同步'; - $('connectionText').textContent = text || '网页 AI 会话会自动进入本地工作台。'; - $('connectionAction').textContent = '刷新'; - return; - } - if (state === 'offline') { - $('connectionTitle').textContent = '本地工作台未连接'; - $('connectionText').textContent = text || '先启动 Agent Memory Lab,再同步网页会话。'; - $('connectionAction').textContent = '重试'; - return; - } - $('connectionTitle').textContent = '检查连接中'; - $('connectionText').textContent = '正在确认能否同步到本地工作台。'; - $('connectionAction').textContent = '重试'; -} - -function draftMetaText(capture) { - const page = capture && capture.page ? capture.page : {}; - const provider = capture && capture.conversation && capture.conversation.provider ? capture.conversation.provider : ''; - const source = provider || page.typeLabel || page.host || '浏览器'; - return `${source} · 只同步会话原文`; -} - -function renderSyncState(capture) { - const turns = capture && capture.conversation && Array.isArray(capture.conversation.turns) ? capture.conversation.turns : []; - const provider = capture && capture.conversation && capture.conversation.provider ? capture.conversation.provider : ''; - $('turnSummary').textContent = `${turns.length} 条`; - $('syncMode').textContent = provider ? '自动' : '等待'; - $('draftAssist').textContent = turns.length - ? `已读到 ${turns.length} 条网页对话,会自动同步到工作台。` - : '还没有读到真实对话。打开或展开 AI 对话后会自动同步。'; - $('draftMeta').textContent = draftMetaText(capture); -} - -function renderTurns(turns) { - const chatSection = $('chatSection'); - if (!turns || !turns.length) { - chatSection.hidden = true; - return; - } - chatSection.hidden = false; - $('turnCount').textContent = String(turns.length); - $('turnList').innerHTML = turns.map((turn) => ` -
-
${turn.role === 'user' ? '用户' : turn.role === 'assistant' ? 'AI' : '对话'}
-

${escapeHtml(turn.text)}

-
- `).join(''); -} - -function renderDiagnostics(capture) { - const diagnostics = capture && capture.diagnostics ? capture.diagnostics : {}; - const section = $('aiDiagnostics'); - if (!diagnostics.supportedAiPage) { - section.hidden = true; - $('copyDiagnostics').disabled = true; - $('copyEvidenceCommand').disabled = true; - $('evidenceCommandHint').hidden = true; - return; - } - section.hidden = false; - $('copyDiagnostics').disabled = false; - $('copyEvidenceCommand').disabled = false; - $('evidenceCommandHint').hidden = false; - $('aiProvider').textContent = diagnostics.provider || 'AI 页面'; - const readyForTrial = !!(diagnostics.supportedAiPage && diagnostics.turnCount > 0); - const missing = []; - if (!diagnostics.supportedAiPage) missing.push('AI 页面'); - if (!diagnostics.turnCount) missing.push('会话内容'); - $('aiValidationSummary').className = `validation-summary ${readyForTrial ? 'ready' : 'needs-check'}`; - $('aiValidationSummary').innerHTML = ` - ${readyForTrial ? '会话抓取正常' : '会话抓取未就绪'} - ${readyForTrial ? '已读到网页里的 AI 会话。' : `还缺:${escapeHtml(missing.join('、') || '页面结构确认')}`} - `; - const rows = [ - { label: '页面', value: diagnostics.provider || '已识别', ok: true }, - { label: '输入框', value: diagnostics.editorFound ? '可用' : '未找到', ok: !!diagnostics.editorFound }, - { label: '发送按钮', value: diagnostics.sendFound ? '未受影响' : '未确认', ok: !!diagnostics.sendFound }, - { label: '已抓取会话', value: `${diagnostics.turnCount || 0} 条`, ok: (diagnostics.turnCount || 0) > 0 } - ]; - $('aiDiagnosticList').innerHTML = rows.map((row) => ` -
- ${escapeHtml(row.label)} - ${escapeHtml(row.value)} -
- `).join(''); -} - -function renderRecent(items) { - const node = $('recentList'); - if (!items || !items.length) { - node.className = 'recent-list empty'; - node.textContent = '暂无记录'; - return; - } - node.className = 'recent-list'; - node.innerHTML = items.slice(0, 6).map((item) => ` -
-
${escapeHtml(item.typeLabel || item.host || '')} · 会话同步
-
${escapeHtml(item.title || '未命名页面')}
-
- `).join(''); -} - -function renderCapture(capture) { - latestCapture = capture; - const page = capture.page || {}; - $('pageType').textContent = page.typeLabel || '网页'; - $('pageTitle').textContent = page.title || '当前页面'; - $('pageUrl').textContent = page.url || ''; - $('privacy').textContent = '只同步会话'; - $('privacy').className = 'type-pill'; - renderSyncState(capture); - renderDiagnostics(capture); - renderTurns(capture.conversation && capture.conversation.turns ? capture.conversation.turns : []); -} - -async function refresh() { - setMessage(''); - setConnectionState('checking'); - try { - const health = await send('HEALTH'); - $('status').textContent = health && health.status === 'ok' ? '本地工作台已连接' : '本地工作台可访问'; - setConnectionState('connected'); - } catch { - $('status').textContent = '未连接本地工作台'; - setConnectionState('offline'); - } - try { - renderCapture(await send('COLLECT_PAGE')); - } catch (err) { - $('pageTitle').textContent = '无法读取当前页面'; - $('pageUrl').textContent = err.message || ''; - } - try { - renderRecent(await send('RECENT_CAPTURES')); - } catch { - renderRecent([]); - } -} - -$('refresh').addEventListener('click', refresh); -$('connectionAction').addEventListener('click', refresh); -$('copyDiagnostics').addEventListener('click', async () => { - try { - await copyText(JSON.stringify(buildDiagnosticReport(latestCapture), null, 2)); - setMessage('已复制问题信息', 'ok'); - } catch (err) { - setMessage(err.message || '复制失败', 'error'); - } -}); -$('copyEvidenceCommand').addEventListener('click', async () => { - try { - await copyText(buildEvidenceCommand(latestCapture)); - setMessage('已复制检查步骤', 'ok'); - } catch (err) { - setMessage(err.message || '复制失败', 'error'); - } -}); -$('syncNow').addEventListener('click', async () => { - $('syncNow').disabled = true; - setMessage('正在扫描已打开的 AI 会话...'); - try { - const data = await send('SYNC_OPEN_AI_TABS', { force: true }); - setMessage(`已扫描 ${data.scanned || 0} 个 AI 会话标签页,同步 ${data.synced || 0} 个`, 'ok'); - } catch (err) { - setMessage(err.message || '同步失败', 'error'); - } finally { - $('syncNow').disabled = false; - } -}); -$('openWorkbench').addEventListener('click', () => send('OPEN_VIEWER', { tab: 'sessions' }).catch(() => {})); -$('closePanel').addEventListener('click', () => window.close()); - -refresh(); diff --git a/cli.ts b/cli.ts new file mode 100644 index 00000000..beabcfda --- /dev/null +++ b/cli.ts @@ -0,0 +1,4 @@ +#!/usr/bin/env node +import { main } from "./src/cli.js"; + +process.exitCode = await main(); diff --git a/deploy/README.md b/deploy/README.md deleted file mode 100644 index 619c2314..00000000 --- a/deploy/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# One-click deploy templates - -> **本地使用不需要 Docker。** 个人本地用户用 `npm run build && npm run start:local-memory` -> 即可启动工作台(走原生 iii-engine 二进制)。本目录是 **自托管 / 容器化部署** 路径, -> 面向想在自己的服务器或托管平台上运行 agentmemory 的人。 - -Stand up agentmemory on managed infrastructure without rolling your own -Docker host. Each template ships a self-contained Dockerfile that pulls -`@agentmemory/agentmemory` from npm at build time and copies the iii -engine binary in from the official `iiidev/iii` image — no pre-built -agentmemory image required. Storage mounts at `/data`; an HMAC secret -is generated by the first-boot entrypoint and persisted to the volume. -The entrypoint overwrites the npm-bundled iii config with a -deploy-tuned one that binds `0.0.0.0` and uses absolute `/data` paths, -then drops privileges from `root` to `node` via `gosu` before -exec'ing the agentmemory CLI. - -| Platform | Pitch | Cost floor | -|----------|-------|------------| -| [fly.io](./fly/README.md) | Single machine with auto-stop. Cheapest idle cost on a managed host; cold-start on first request after sleep. | ~$0.15/month at full idle | -| [Railway](./railway/README.md) | Push from GitHub, volume in the dashboard. Easiest managed dashboard flow. | $5/month (Hobby plan flat fee) | -| [Render](./render/README.md) | Blueprint-driven; persistent disk attaches automatically. Most "set it and forget it." | $7.25/month (Starter web + 1 GB disk) | -| [Coolify](./coolify/README.md) | Self-hosted on your own VPS. Same Docker Compose stack, you own the host and the data. | VPS cost only (Hetzner CX22 ~€3.79/month) | - -## What every template guarantees - -- **Volume mounted at `/data`.** Matches the path the engine has used - since v0.9.10. -- **HMAC secret generated on first boot** via `openssl rand -hex 32`, - written to `/data/.hmac` with `chmod 600`, and printed to stdout - exactly once so the operator can capture it from the deploy logs. - Subsequent boots load the secret from the file. The secret is never - committed to a config file or set as a platform env var. -- **Only port 3111 is exposed publicly.** The viewer on port 3113 - stays bound to the container's localhost. Reach it via SSH tunnel - (see each platform's README). -- **TLS upstream of the container.** Every managed platform terminates - TLS at its edge proxy; the templates publish a single internal port - (`3111`) to that proxy, never to the host. Integration plugins - configured with `AGENTMEMORY_REQUIRE_HTTPS=1` will refuse to send the - bearer over plaintext HTTP to a non-loopback host, so a - misconfigured TLS layer fails loud instead of silently leaking the - secret. - -## Pick a platform - -- Pick **fly.io** if you want the lowest idle cost and don't mind a - cold-start latency hit on the first request after sleep. -- Pick **Railway** if you want a clicky dashboard flow and a flat - monthly bill. -- Pick **Render** if you want the most "set it and forget it" - Blueprint flow with automatic disk snapshots on paid plans. -- Pick **Coolify** if you already run a VPS and want a self-hosted - control plane — same Docker Compose stack, no third-party host has - your memories. - -All four give you the same agentmemory API at the same port (3111) -with the same auth model. Migrating between them later is a `tar` of -`/data` and a re-import — see each platform's README for the exact -commands. - -## Optional: LLM + embedding provider keys - -Every template runs out of the box without any LLM or embedding key — -search falls back to BM25-only mode and synthetic (zero-LLM) -compression keeps memories indexable. To unlock LLM-powered -compression and hybrid (BM25 + vector) recall, add one of the -following to your platform's environment variables (Fly: -`flyctl secrets set`; Railway / Render / Coolify: dashboard -*Variables / Environment* tab): - -| Variable | Purpose | -|---------------------------|----------------------------------------------------------| -| `ANTHROPIC_API_KEY` | LLM-backed compression + summarization | -| `GEMINI_API_KEY` | LLM provider alternative | -| `OPENROUTER_API_KEY` | LLM provider alternative | -| `OPENAI_API_KEY` | Embedding provider (text-embedding-3-small by default) | -| `VOYAGE_API_KEY` | Embedding provider alternative | -| `AGENTMEMORY_AUTO_COMPRESS=true` | Run LLM compression on every observation batch | -| `AGENTMEMORY_INJECT_CONTEXT=true` | Inject recalled memories back into agent prompts | - -The defaults are intentionally conservative: provider keys default to -absent (no third-party calls), `AGENTMEMORY_AUTO_COMPRESS` is off, -and `AGENTMEMORY_INJECT_CONTEXT` is off. Opt in only after you've -confirmed your provider quota can absorb the workload. - -## Cold-start budget - -Measured against fly.io's `iad` region with a 1 GB volume: - -``` -machine image prepared : 5.1 s -volume mount + format : 2.5 s -firecracker boot : 1.0 s -entrypoint + chown : 0.5 s -iii-engine ready : 3.0 s -agentmemory worker reg : 2.0 s -───────────────────────────────── -healthcheck passes : ~9-10 s -``` - -Every template's health-check `grace_period` (or compose -`start_period`) is set to 30 s for a 3x safety margin. Tune lower -once you've measured your own platform's image-pull characteristics. diff --git a/deploy/coolify/Dockerfile b/deploy/coolify/Dockerfile deleted file mode 100644 index 3f6c433f..00000000 --- a/deploy/coolify/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -ARG III_VERSION=0.11.2 - -FROM iiidev/iii:${III_VERSION} AS iii-image - -FROM node:22-slim - -ARG AGENTMEMORY_VERSION=0.9.12 -ARG III_VERSION=0.11.2 -ARG III_SDK_VERSION=0.11.2 - -RUN apt-get update \ - && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=iii-image /app/iii /usr/local/bin/iii - -WORKDIR /opt/agentmemory -RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overrides":{"iii-sdk":"%s"}}\n' "${III_SDK_VERSION}" > package.json \ - && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ - && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory - -ENV AGENTMEMORY_III_VERSION=${III_VERSION} \ - TINI_SUBREAPER=1 - -COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh - -EXPOSE 3111 - -HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \ - CMD curl -fsS http://127.0.0.1:3111/agentmemory/livez || exit 1 - -ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/coolify/README.md b/deploy/coolify/README.md deleted file mode 100644 index feec4757..00000000 --- a/deploy/coolify/README.md +++ /dev/null @@ -1,132 +0,0 @@ -# Deploy agentmemory on Coolify - -[Coolify](https://coolify.io/self-hosted) is an open-source, self-hosted -Heroku/Render alternative that you run on your own VPS. This template -deploys agentmemory as a Coolify *Application* backed by a Docker -Compose stack — Coolify handles TLS termination, persistent volume -provisioning, log aggregation, and the deploy webhook for you. - -## What you get - -- A public HTTPS endpoint serving the agentmemory REST API behind - Coolify's built-in Traefik/Caddy proxy. The container port (`3111`) - is exposed to the proxy network only — never bound to the host — so - TLS termination and domain routing stay under proxy control. -- A persistent Docker volume backing `/data` for memories, BM25 index, - and stream backlog. Coolify auto-prefixes the volume name with the - application's UUID so the data survives redeploys. -- An HTTP health-check at `/agentmemory/livez` declared in the - Dockerfile (`HEALTHCHECK` directive). Coolify reuses it for - rolling-deploy decisions. - -## One-time setup - -1. **Open your Coolify dashboard** and click **+ New → Application**. -2. **Source**: pick *Public Repository*. Paste: - ``` - https://github.com/rohitg00/agentmemory - ``` - Branch: `main`. -3. **Build Pack**: select *Docker Compose*. -4. **Base Directory**: `deploy/coolify` -5. **Compose Path**: `docker-compose.yml` -6. Click **Save**, then on the application settings screen set a - **Domain** in the form `https://:3111` (the `:3111` - suffix tells Coolify's proxy which container port to forward to; - it still serves over 443/80 publicly). -7. Click **Deploy**. - -That's it. Coolify clones the repo, builds the Dockerfile under -`deploy/coolify/`, provisions the `agentmemory-data` named volume on -the host, attaches Traefik (or Caddy) for the public domain, and starts -the service. The container is reachable only through the proxy — there -is no published host port. - -## Capture the HMAC secret - -Once the deploy logs show the service is up, open the application's -**Logs** tab in Coolify and search for `AGENTMEMORY_SECRET=`. You will -see exactly one line of the form `AGENTMEMORY_SECRET=<64 hex chars>`. -Copy it into your client environment (`~/.bashrc`, Claude Desktop -config, etc.). The secret is never printed again on subsequent boots. - -## Verify the deployment - -```bash -curl "https:///agentmemory/livez" -# {"status":"ok"} -``` - -For an authenticated call, your client must send -`Authorization: Bearer `. - -## Viewer access (port 3113 stays internal) - -The viewer port is not exposed by the compose file on purpose — it -holds the unauthenticated admin surface in older releases and the -proxied surface in current ones, neither of which belongs on the open -internet. Two paths to reach it: - -**Option A — SSH tunnel from the Coolify host.** Coolify gives you SSH -access to the underlying VPS. From your laptop: - -```bash -ssh -L 3113:127.0.0.1:3113 @ -# inside the SSH session, find the container: -docker ps --filter name=agentmemory --format "{{.Names}}" -# tunnel into the container's port from the host: -docker exec -it sh -c "curl http://localhost:3113" -``` - -Cleaner version: bind the container's 3113 to the host's loopback by -adding `- "127.0.0.1:3113:3113"` to the `ports:` block in -`docker-compose.yml`, redeploy, then `ssh -L 3113:127.0.0.1:3113 -@` is enough. - -**Option B — expose 3113 as a second Coolify domain protected by HTTP -basic auth.** Coolify's per-service routing supports adding a second -public endpoint with basic-auth middleware. Useful if you want to -share the viewer with a teammate without giving them SSH. - -## Rotate the HMAC secret - -```bash -ssh @ -docker exec -it sh -c "rm /data/.hmac" -exit -``` - -Then click **Redeploy** in the Coolify dashboard. The next boot prints -a fresh secret to the logs. - -## Back up `/data` - -Coolify exposes the named volume on the host filesystem under -`/var/lib/docker/volumes/_agentmemory-data/_data`. Back it -up with your existing host-level snapshot tooling (Restic, Borg, -`rsync`, BTRFS snapshots, etc.) or via Coolify's built-in *Backups* -feature for Docker volumes. - -## Cost floor and resources - -- **Hardware**: the agentmemory container idles at ~150 MB RSS, climbs - to ~400 MB under steady traffic. The bundled iii engine adds another - ~80 MB. A 1 vCPU / 1 GB VPS is comfortably enough for a personal - install. -- **VPS providers commonly paired with Coolify**: Hetzner CX22 - (~€3.79/month), DigitalOcean Basic Droplet ($6/month), Vultr Cloud - Compute ($6/month). Coolify itself is free. -- **Volume storage**: tied to whatever block storage the VPS provides; - typically pennies per GB-month. - -## Known caveats - -- The Dockerfile builds on the Coolify host on every deploy. First - deploy takes ~2 minutes; cached layers shrink subsequent rebuilds to - under 30 seconds. Pin `AGENTMEMORY_VERSION` and `III_VERSION` in - `docker-compose.yml`'s `build.args` block to lock a specific release. -- Coolify's *Persistent Storage* tab will show `agentmemory-data` as a - managed volume — do not delete it from the dashboard if you want - your memories to survive a redeploy. -- arm64 hosts work — the iii binary selection in the Dockerfile uses - `uname -m` and downloads the matching tarball. diff --git a/deploy/coolify/docker-compose.yml b/deploy/coolify/docker-compose.yml deleted file mode 100644 index 149f806d..00000000 --- a/deploy/coolify/docker-compose.yml +++ /dev/null @@ -1,30 +0,0 @@ -services: - agentmemory: - build: - context: . - dockerfile: Dockerfile - args: - AGENTMEMORY_VERSION: "0.9.12" - III_VERSION: "0.11.2" - III_SDK_VERSION: "0.11.2" - restart: unless-stopped - environment: - SERVICE_FQDN_AGENTMEMORY_3111: ${SERVICE_FQDN_AGENTMEMORY_3111} - expose: - - "3111" - volumes: - - agentmemory-data:/data - healthcheck: - test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:3111/agentmemory/livez || exit 1"] - interval: 30s - timeout: 5s - start_period: 30s - retries: 3 - logging: - driver: json-file - options: - max-size: "10m" - max-file: "3" - -volumes: - agentmemory-data: diff --git a/deploy/coolify/entrypoint.sh b/deploy/coolify/entrypoint.sh deleted file mode 100755 index ffdd6333..00000000 --- a/deploy/coolify/entrypoint.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/sh -# agentmemory first-boot entrypoint. -# -# Runs as root so it can: -# 1. Overwrite the npm-bundled iii-config.yaml (which binds 127.0.0.1 -# and uses relative ./data paths) with a deploy-tuned version that -# binds 0.0.0.0 and uses absolute /data paths. -# 2. chown the platform-mounted /data volume to the runtime user -# (managed platforms mount volumes root-owned 755 by default). -# 3. Generate the HMAC secret on first boot and persist it to -# /data/.hmac (chmod 600) so the secret survives restarts. -# -# Then it execs the agentmemory CLI under gosu as the unprivileged -# `node` user. - -set -eu - -DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" -HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" -RUN_AS="node:node" -III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" - -mkdir -p "$DATA_DIR" -chown -R "$RUN_AS" "$DATA_DIR" - -cat > "$III_CONFIG" <<'EOF' -workers: - - name: iii-http - config: - port: 3111 - host: 0.0.0.0 - default_timeout: 180000 - cors: - allowed_origins: - - "http://localhost:3111" - - "http://localhost:3113" - - "http://127.0.0.1:3111" - - "http://127.0.0.1:3113" - allowed_methods: [GET, POST, PUT, DELETE, OPTIONS] - - name: iii-state - config: - adapter: - name: kv - config: - store_method: file_based - file_path: /data/state_store.db - - name: iii-queue - config: - adapter: - name: builtin - - name: iii-pubsub - config: - adapter: - name: local - - name: iii-cron - config: - adapter: - name: kv - - name: iii-stream - config: - port: 3112 - host: 0.0.0.0 - adapter: - name: kv - config: - store_method: file_based - file_path: /data/stream_store - - name: iii-observability - config: - enabled: true - service_name: agentmemory - exporter: memory - sampling_ratio: 1.0 - metrics_enabled: true - logs_enabled: true - logs_console_output: true -EOF -chown "$RUN_AS" "$III_CONFIG" - -if [ ! -s "$HMAC_FILE" ]; then - SECRET="$(openssl rand -hex 32)" - umask 077 - printf '%s\n' "$SECRET" > "$HMAC_FILE" - chmod 600 "$HMAC_FILE" - chown "$RUN_AS" "$HMAC_FILE" - echo "================================================================" - echo "agentmemory: generated HMAC secret on first boot" - echo "AGENTMEMORY_SECRET=$SECRET" - echo "Copy this value now. It will not be printed again." - echo "Stored at: $HMAC_FILE (chmod 600)" - echo "To rotate: delete $HMAC_FILE on the persistent volume and restart." - echo "================================================================" -fi - -AGENTMEMORY_SECRET="$(cat "$HMAC_FILE")" -export AGENTMEMORY_SECRET - -exec gosu "$RUN_AS" agentmemory "$@" diff --git a/deploy/fly/Dockerfile b/deploy/fly/Dockerfile deleted file mode 100644 index 89257f93..00000000 --- a/deploy/fly/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -ARG III_VERSION=0.11.2 - -FROM iiidev/iii:${III_VERSION} AS iii-image - -FROM node:22-slim - -ARG AGENTMEMORY_VERSION=0.9.12 -ARG III_VERSION=0.11.2 -ARG III_SDK_VERSION=0.11.2 - -RUN apt-get update \ - && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=iii-image /app/iii /usr/local/bin/iii - -# Install agentmemory into a dedicated prefix so the local package.json's -# `overrides` field pins iii-sdk down to match the engine (agentmemory's -# caret range `^0.11.2` otherwise resolves to 0.11.6, the version that -# requires the new sandbox-everything worker model the agentmemory CLI -# is not refactored for yet). `npm install -g` ignores overrides, hence -# the local prefix. -WORKDIR /opt/agentmemory -RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overrides":{"iii-sdk":"%s"}}\n' "${III_SDK_VERSION}" > package.json \ - && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ - && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory - -ENV AGENTMEMORY_III_VERSION=${III_VERSION} \ - TINI_SUBREAPER=1 - -COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh - -EXPOSE 3111 - -ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/fly/README.md b/deploy/fly/README.md deleted file mode 100644 index 020d9751..00000000 --- a/deploy/fly/README.md +++ /dev/null @@ -1,132 +0,0 @@ -# Deploy agentmemory on fly.io - -This template runs agentmemory on a single fly.io machine with a 1 GB -persistent volume mounted at `/data`. The HMAC secret is generated on -first boot and persisted to the volume — you capture it from the deploy -logs exactly once. - -## What you get - -- A public HTTPS endpoint serving the agentmemory REST API on port 3111 -- A 1 GB Fly Volume at `/data` for memories, BM25 index, and stream backlog -- `auto_stop_machines = "stop"` and `min_machines_running = 0` — the - machine sleeps when idle, so cost floor approaches $0 for low traffic -- HTTP healthcheck at `/agentmemory/livez` every 30 s -- The HMAC bearer secret is generated on first boot inside the - container and persisted to `/data/.hmac` (chmod 600); the operator - copies it from the deploy logs once. - -## One-time setup - -Pick a unique Fly app name first — `agentmemory` itself is likely taken. -Every command below references `$APP`, so set it once and the rest of the -flow stays consistent: - -```bash -# 1. Install flyctl: https://fly.io/docs/flyctl/install/ -# 2. Pick your unique app name (and matching volume name): -export APP="agentmemory-$(whoami)" # or any other globally-unique name -export VOLUME="${APP//-/_}_data" # Fly volume names can't contain '-' - -# 3. From this directory: -fly launch --copy-config --no-deploy --name "$APP" - -# 4. Create the volume in the same region as the app: -fly volumes create "$VOLUME" --region iad --size 1 - -# 5. Deploy: -fly deploy --app "$APP" -``` - -If `fly launch` reports the name is taken, pick another value for `$APP`, -re-export, and re-run. - -## Capture the HMAC secret - -Right after the first deploy succeeds: - -```bash -fly logs --app "$APP" | grep -A1 AGENTMEMORY_SECRET= -``` - -You will see exactly one line of the form `AGENTMEMORY_SECRET=<64 hex chars>`. -Copy it into your client environment (`~/.bashrc`, Claude Desktop config, -etc.). The secret is never printed again on subsequent boots. - -## Verify the deployment - -```bash -curl "https://$APP.fly.dev/agentmemory/livez" -# {"status":"ok"} -``` - -For an authenticated call, your client must send `Authorization: Bearer `. - -## Viewer access (port 3113 stays internal) - -The viewer port is intentionally not exposed publicly. Tunnel to it: - -```bash -fly proxy 3113:3113 --app "$APP" -# then open http://localhost:3113 -``` - -`fly proxy` opens an mTLS WireGuard channel to the machine, so the -viewer's bearer token still has to ride a loopback connection on your -laptop — the v0.9.12 plaintext-bearer guard stays satisfied. - -## Rotate the HMAC secret - -```bash -fly ssh console --app "$APP" -rm /data/.hmac -exit -fly machine restart -fly logs --app "$APP" | grep AGENTMEMORY_SECRET= -``` - -Update every client with the new secret. Old tokens stop working -immediately. - -## Back up `/data` - -```bash -fly ssh console --app "$APP" -C "tar czf - /data" > "$APP-$(date +%Y%m%d).tar.gz" -``` - -To restore on a fresh machine: - -```bash -cat "$APP-YYYYMMDD.tar.gz" | fly ssh console --app "$APP" -C "tar xzf - -C /" -fly machine restart -``` - -## Cost floor and egress - -- Idle (machine stopped): the volume costs ~$0.15/GB/month. A 1 GB - volume is roughly $0.15/month. -- Active (machine running on `shared-cpu-1x` with 512 MB): about - $1.94/month if it ran 24/7; in practice `auto_stop_machines` keeps - that well under $1. -- Outbound bandwidth: 100 GB/month free on the Hobby plan, then $0.02/GB - in North America / Europe. - -See for the up-to-date rate card. - -## Known caveats - -- The volume lives in one region. To survive a region outage, create a - second volume in another region and update `primary_region` after the - failover, or take snapshots with `fly volumes snapshots create`. -- The Dockerfile builds in the Fly Builder on every deploy — first - build is ~30 seconds; cached layers shrink rebuilds to under 10 - seconds. Image is ~114 MB. -- First deploy lands on a **shared IPv4 + dedicated IPv6** by default - (free). If you need a dedicated IPv4 for legacy clients without SNI, - run `fly ips allocate-v4 --app "$APP"` — costs $2/month. -- Cold-start (from machine launch to passing `/agentmemory/livez`) is - ~9 seconds measured. `grace_period = "30s"` on the health check - gives a 3x safety margin. -- Bump `AGENTMEMORY_VERSION` or `III_VERSION` in the Dockerfile to - upgrade. `fly deploy --build-arg AGENTMEMORY_VERSION=` also works - for a one-off without editing the file. diff --git a/deploy/fly/entrypoint.sh b/deploy/fly/entrypoint.sh deleted file mode 100755 index ffdd6333..00000000 --- a/deploy/fly/entrypoint.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/sh -# agentmemory first-boot entrypoint. -# -# Runs as root so it can: -# 1. Overwrite the npm-bundled iii-config.yaml (which binds 127.0.0.1 -# and uses relative ./data paths) with a deploy-tuned version that -# binds 0.0.0.0 and uses absolute /data paths. -# 2. chown the platform-mounted /data volume to the runtime user -# (managed platforms mount volumes root-owned 755 by default). -# 3. Generate the HMAC secret on first boot and persist it to -# /data/.hmac (chmod 600) so the secret survives restarts. -# -# Then it execs the agentmemory CLI under gosu as the unprivileged -# `node` user. - -set -eu - -DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" -HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" -RUN_AS="node:node" -III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" - -mkdir -p "$DATA_DIR" -chown -R "$RUN_AS" "$DATA_DIR" - -cat > "$III_CONFIG" <<'EOF' -workers: - - name: iii-http - config: - port: 3111 - host: 0.0.0.0 - default_timeout: 180000 - cors: - allowed_origins: - - "http://localhost:3111" - - "http://localhost:3113" - - "http://127.0.0.1:3111" - - "http://127.0.0.1:3113" - allowed_methods: [GET, POST, PUT, DELETE, OPTIONS] - - name: iii-state - config: - adapter: - name: kv - config: - store_method: file_based - file_path: /data/state_store.db - - name: iii-queue - config: - adapter: - name: builtin - - name: iii-pubsub - config: - adapter: - name: local - - name: iii-cron - config: - adapter: - name: kv - - name: iii-stream - config: - port: 3112 - host: 0.0.0.0 - adapter: - name: kv - config: - store_method: file_based - file_path: /data/stream_store - - name: iii-observability - config: - enabled: true - service_name: agentmemory - exporter: memory - sampling_ratio: 1.0 - metrics_enabled: true - logs_enabled: true - logs_console_output: true -EOF -chown "$RUN_AS" "$III_CONFIG" - -if [ ! -s "$HMAC_FILE" ]; then - SECRET="$(openssl rand -hex 32)" - umask 077 - printf '%s\n' "$SECRET" > "$HMAC_FILE" - chmod 600 "$HMAC_FILE" - chown "$RUN_AS" "$HMAC_FILE" - echo "================================================================" - echo "agentmemory: generated HMAC secret on first boot" - echo "AGENTMEMORY_SECRET=$SECRET" - echo "Copy this value now. It will not be printed again." - echo "Stored at: $HMAC_FILE (chmod 600)" - echo "To rotate: delete $HMAC_FILE on the persistent volume and restart." - echo "================================================================" -fi - -AGENTMEMORY_SECRET="$(cat "$HMAC_FILE")" -export AGENTMEMORY_SECRET - -exec gosu "$RUN_AS" agentmemory "$@" diff --git a/deploy/fly/fly.toml b/deploy/fly/fly.toml deleted file mode 100644 index 03f58bc9..00000000 --- a/deploy/fly/fly.toml +++ /dev/null @@ -1,44 +0,0 @@ -# fly.io deployment for agentmemory. -# -# The HMAC secret is generated by entrypoint.sh on first boot and persisted -# to the mounted volume at /data/.hmac. Operator copies it once from -# `fly logs` then never sees it again. To rotate: `fly ssh console` and -# `rm /data/.hmac`, then `fly machine restart`. -# -# Only port 3111 (REST API) is exposed publicly. Viewer 3113 stays bound -# to localhost inside the machine; reach it via `fly proxy 3113:3113`. - -app = "agentmemory" -primary_region = "iad" - -[build] - dockerfile = "Dockerfile" - -[[mounts]] - source = "agentmemory_data" - destination = "/data" - initial_size = "1gb" - -[http_service] - internal_port = 3111 - force_https = true - auto_stop_machines = "stop" - auto_start_machines = true - min_machines_running = 0 - processes = ["app"] - - [http_service.concurrency] - type = "requests" - soft_limit = 200 - hard_limit = 250 - - [[http_service.checks]] - interval = "30s" - timeout = "5s" - grace_period = "30s" - method = "GET" - path = "/agentmemory/livez" - -[[vm]] - size = "shared-cpu-1x" - memory = "512mb" diff --git a/deploy/railway/Dockerfile b/deploy/railway/Dockerfile deleted file mode 100644 index 89257f93..00000000 --- a/deploy/railway/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -ARG III_VERSION=0.11.2 - -FROM iiidev/iii:${III_VERSION} AS iii-image - -FROM node:22-slim - -ARG AGENTMEMORY_VERSION=0.9.12 -ARG III_VERSION=0.11.2 -ARG III_SDK_VERSION=0.11.2 - -RUN apt-get update \ - && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=iii-image /app/iii /usr/local/bin/iii - -# Install agentmemory into a dedicated prefix so the local package.json's -# `overrides` field pins iii-sdk down to match the engine (agentmemory's -# caret range `^0.11.2` otherwise resolves to 0.11.6, the version that -# requires the new sandbox-everything worker model the agentmemory CLI -# is not refactored for yet). `npm install -g` ignores overrides, hence -# the local prefix. -WORKDIR /opt/agentmemory -RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overrides":{"iii-sdk":"%s"}}\n' "${III_SDK_VERSION}" > package.json \ - && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ - && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory - -ENV AGENTMEMORY_III_VERSION=${III_VERSION} \ - TINI_SUBREAPER=1 - -COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh - -EXPOSE 3111 - -ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/railway/README.md b/deploy/railway/README.md deleted file mode 100644 index 9aad4fb3..00000000 --- a/deploy/railway/README.md +++ /dev/null @@ -1,136 +0,0 @@ -# Deploy agentmemory on Railway - -This template runs agentmemory on a single Railway service with a -persistent volume mounted at `/data`. The HMAC secret is generated on -first boot and persisted to the volume — you read it once from the -deploy logs and copy it into your client. - -## What you get - -- A public HTTPS endpoint serving the agentmemory REST API on port 3111 -- A persistent Railway Volume at `/data` for memories, BM25 index, and - stream backlog -- Railway healthcheck against `/agentmemory/livez` -- The HMAC bearer secret is generated on first boot inside the - container and persisted to `/data/.hmac` (chmod 600); the operator - copies it from the deploy logs once. -- The deploy uses `requiredMountPath: /data` so Railway refuses to - start the service if no volume is attached at that path — first - deploy must create the volume from the dashboard. - -## Deploy via Railway dashboard - -1. Click **Deploy from GitHub** in the Railway dashboard and pick the - `rohitg00/agentmemory` repo. -2. Set the **Config-as-Code Path** under the service Settings to - `deploy/railway/railway.json`. Railway picks up the Dockerfile path - from there. -3. Open the service's **Volumes** tab and add a volume mounted at - `/data` (Railway volumes are configured in the dashboard or via - `railway volume add`, not in `railway.json`). -4. Click **Deploy**. - -## Deploy via Railway CLI - -```bash -# Install: https://docs.railway.com/guides/cli -railway login -railway init # link a new project -railway up --service agentmemory # builds + deploys -railway volume add --service agentmemory --mount /data # attach persistent volume -railway redeploy # restart with the volume -``` - -## Capture the HMAC secret - -After the first deploy succeeds, open the service's **Deploy Logs**: - -```bash -railway logs --service agentmemory | grep AGENTMEMORY_SECRET= -``` - -You will see exactly one line of the form `AGENTMEMORY_SECRET=<64 hex chars>`. -Copy it into your client environment. The secret is never printed again -on subsequent boots. - -## Verify the deployment - -```bash -curl https://.up.railway.app/agentmemory/livez -# {"status":"ok"} -``` - -For an authenticated call, your client must send `Authorization: Bearer `. - -## Viewer access (port 3113 stays internal) - -Railway only exposes the single public port from your service's -`PORT` env var (which we map to 3111). The viewer stays bound to -localhost inside the container. `railway ssh` is an interactive shell -only — it does not support `-L`-style port forwarding, so reach the -viewer with one of the following. - -**Quick in-container check:** - -```bash -railway ssh --service agentmemory -# inside the container: -curl http://localhost:3113 -``` - -**Browser session — option A (TCP Proxy, recommended):** in the Railway -dashboard, open the service's *Settings → Networking* tab and add a -**TCP Proxy** for container port `3113`. Railway returns a public -host/port pair you can hit directly from your browser. Pair it with the -HMAC bearer-auth header so the viewer is not anonymously reachable. - -**Browser session — option B (in-container sshd):** add an `openssh-server` -process to the image and start it from `entrypoint.sh` on a fixed port, -expose that port through a second Railway TCP Proxy, then use a native -`ssh -L 3113:localhost:3113 -p ` from your laptop. -This is the heavier path; option A is what most users will want. - -## Rotate the HMAC secret - -```bash -railway ssh --service agentmemory -rm /data/.hmac -exit -railway redeploy --service agentmemory -railway logs --service agentmemory | grep AGENTMEMORY_SECRET= -``` - -Update every client with the new secret. Old tokens stop working -immediately. - -## Back up `/data` - -```bash -railway ssh --service agentmemory -- "tar czf - /data" > agentmemory-$(date +%Y%m%d).tar.gz -``` - -To restore on a fresh volume: - -```bash -cat agentmemory-YYYYMMDD.tar.gz | railway ssh --service agentmemory -- "tar xzf - -C /" -railway redeploy --service agentmemory -``` - -## Cost floor and egress - -- Hobby plan: $5/month flat, includes $5 of usage. -- agentmemory at idle plus a 1 GB volume typically uses $3–$6 of usage - per month on the smallest instance, so most users stay near the $5 - floor. -- Egress: $0.10/GB after the bundled allowance. - -See for the current rate card. - -## Known caveats - -- Railway volumes do not auto-snapshot. Take your own backups (above) - or use the dashboard's manual snapshot feature. -- The Dockerfile builds on Railway's builder on every deploy. First - deploy is ~2 minutes; cached layers make subsequent rebuilds quick. - Pin `AGENTMEMORY_VERSION` / `III_VERSION` build args in the - service's *Variables* tab to lock a specific release. diff --git a/deploy/railway/entrypoint.sh b/deploy/railway/entrypoint.sh deleted file mode 100755 index ffdd6333..00000000 --- a/deploy/railway/entrypoint.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/sh -# agentmemory first-boot entrypoint. -# -# Runs as root so it can: -# 1. Overwrite the npm-bundled iii-config.yaml (which binds 127.0.0.1 -# and uses relative ./data paths) with a deploy-tuned version that -# binds 0.0.0.0 and uses absolute /data paths. -# 2. chown the platform-mounted /data volume to the runtime user -# (managed platforms mount volumes root-owned 755 by default). -# 3. Generate the HMAC secret on first boot and persist it to -# /data/.hmac (chmod 600) so the secret survives restarts. -# -# Then it execs the agentmemory CLI under gosu as the unprivileged -# `node` user. - -set -eu - -DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" -HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" -RUN_AS="node:node" -III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" - -mkdir -p "$DATA_DIR" -chown -R "$RUN_AS" "$DATA_DIR" - -cat > "$III_CONFIG" <<'EOF' -workers: - - name: iii-http - config: - port: 3111 - host: 0.0.0.0 - default_timeout: 180000 - cors: - allowed_origins: - - "http://localhost:3111" - - "http://localhost:3113" - - "http://127.0.0.1:3111" - - "http://127.0.0.1:3113" - allowed_methods: [GET, POST, PUT, DELETE, OPTIONS] - - name: iii-state - config: - adapter: - name: kv - config: - store_method: file_based - file_path: /data/state_store.db - - name: iii-queue - config: - adapter: - name: builtin - - name: iii-pubsub - config: - adapter: - name: local - - name: iii-cron - config: - adapter: - name: kv - - name: iii-stream - config: - port: 3112 - host: 0.0.0.0 - adapter: - name: kv - config: - store_method: file_based - file_path: /data/stream_store - - name: iii-observability - config: - enabled: true - service_name: agentmemory - exporter: memory - sampling_ratio: 1.0 - metrics_enabled: true - logs_enabled: true - logs_console_output: true -EOF -chown "$RUN_AS" "$III_CONFIG" - -if [ ! -s "$HMAC_FILE" ]; then - SECRET="$(openssl rand -hex 32)" - umask 077 - printf '%s\n' "$SECRET" > "$HMAC_FILE" - chmod 600 "$HMAC_FILE" - chown "$RUN_AS" "$HMAC_FILE" - echo "================================================================" - echo "agentmemory: generated HMAC secret on first boot" - echo "AGENTMEMORY_SECRET=$SECRET" - echo "Copy this value now. It will not be printed again." - echo "Stored at: $HMAC_FILE (chmod 600)" - echo "To rotate: delete $HMAC_FILE on the persistent volume and restart." - echo "================================================================" -fi - -AGENTMEMORY_SECRET="$(cat "$HMAC_FILE")" -export AGENTMEMORY_SECRET - -exec gosu "$RUN_AS" agentmemory "$@" diff --git a/deploy/railway/railway.json b/deploy/railway/railway.json deleted file mode 100644 index 43f52173..00000000 --- a/deploy/railway/railway.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://railway.com/railway.schema.json", - "build": { - "builder": "DOCKERFILE", - "dockerfilePath": "deploy/railway/Dockerfile" - }, - "deploy": { - "numReplicas": 1, - "healthcheckPath": "/agentmemory/livez", - "healthcheckTimeout": 30, - "restartPolicyType": "ON_FAILURE", - "restartPolicyMaxRetries": 10, - "requiredMountPath": "/data" - } -} diff --git a/deploy/render/Dockerfile b/deploy/render/Dockerfile deleted file mode 100644 index 89257f93..00000000 --- a/deploy/render/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -ARG III_VERSION=0.11.2 - -FROM iiidev/iii:${III_VERSION} AS iii-image - -FROM node:22-slim - -ARG AGENTMEMORY_VERSION=0.9.12 -ARG III_VERSION=0.11.2 -ARG III_SDK_VERSION=0.11.2 - -RUN apt-get update \ - && apt-get install -y --no-install-recommends openssl ca-certificates tini gosu curl \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=iii-image /app/iii /usr/local/bin/iii - -# Install agentmemory into a dedicated prefix so the local package.json's -# `overrides` field pins iii-sdk down to match the engine (agentmemory's -# caret range `^0.11.2` otherwise resolves to 0.11.6, the version that -# requires the new sandbox-everything worker model the agentmemory CLI -# is not refactored for yet). `npm install -g` ignores overrides, hence -# the local prefix. -WORKDIR /opt/agentmemory -RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overrides":{"iii-sdk":"%s"}}\n' "${III_SDK_VERSION}" > package.json \ - && npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --omit=optional --no-fund --no-audit \ - && ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory - -ENV AGENTMEMORY_III_VERSION=${III_VERSION} \ - TINI_SUBREAPER=1 - -COPY --chmod=0755 entrypoint.sh /usr/local/bin/agentmemory-entrypoint.sh - -EXPOSE 3111 - -ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/agentmemory-entrypoint.sh"] diff --git a/deploy/render/README.md b/deploy/render/README.md deleted file mode 100644 index 3b73f7ee..00000000 --- a/deploy/render/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# Deploy agentmemory on Render - -This template runs agentmemory on a single Render Web Service with a -persistent disk mounted at `/data`. The HMAC secret is generated on -first boot and persisted to the disk — you capture it from the deploy -logs exactly once. - -## What you get - -- A public HTTPS endpoint serving the agentmemory REST API on port 3111 - (Render injects `PORT` defaulting to 10000; we override it to 3111 - via `envVars` so the published port matches the container's bind) -- A 1 GB persistent disk at `/data` for memories, BM25 index, and - stream backlog -- Render healthcheck against `/agentmemory/livez` -- The HMAC bearer secret is generated on first boot inside the - container and persisted to `/data/.hmac` (chmod 600); the operator - copies it from the deploy logs once. - -## Deploy via Render Blueprint - -Render's one-click deploy button only auto-detects `render.yaml` at the -repository root, which the agentmemory repo keeps clean. Use the -dashboard's manual Blueprint flow instead: - -1. Push the `deploy/render/` directory to a Git provider Render can - reach (a fork of `rohitg00/agentmemory` works). -2. In the Render dashboard, click **New +** → **Blueprint**. -3. Point Render at the repo and the path `deploy/render/render.yaml`. -4. Render reads the Blueprint, provisions the disk, builds the - Dockerfile, and starts the service. The whole flow takes 3–5 - minutes on the first run. - -## Deploy via Render Deploy Hook (one-click) - -Once the Blueprint exists in your account, generate a Deploy Hook URL -in the service settings. Future deploys are a single curl call: - -```bash -curl "https://api.render.com/deploy/srv-XXYYZZ?key=AABBCC" -``` - -To pin a specific `@agentmemory/agentmemory` release, set the -`AGENTMEMORY_VERSION` build arg in the service's *Environment* tab -before the next deploy. Same for `III_VERSION`. - -## Capture the HMAC secret - -After the first deploy succeeds, open the service's **Logs** tab and -search for `AGENTMEMORY_SECRET=`. You will see exactly one line of the -form `AGENTMEMORY_SECRET=<64 hex chars>`. Copy it into your client -environment. The secret is never printed again on subsequent boots. - -## Verify the deployment - -```bash -curl https://agentmemory.onrender.com/agentmemory/livez -# {"status":"ok"} -``` - -For an authenticated call, your client must send `Authorization: Bearer `. - -## Viewer access (port 3113 stays internal) - -Render only exposes one public port per service, and we use it for -3111. The viewer port stays bound to localhost inside the container. -Reach it via Render's SSH: - -```bash -# Settings → SSH → enable for your service, copy the connection command -ssh srv-XXYYZZ@ssh..render.com -L 3113:localhost:3113 -# now http://localhost:3113 in your browser hits the in-container viewer -``` - -## Rotate the HMAC secret - -```bash -ssh srv-XXYYZZ@ssh..render.com -rm /data/.hmac -exit -# trigger a redeploy from the Render dashboard or via the Deploy Hook -``` - -After the redeploy, grab the new secret from the logs and update every -client. Old tokens stop working immediately. - -## Back up `/data` - -```bash -ssh srv-XXYYZZ@ssh..render.com "tar czf - /data" > agentmemory-$(date +%Y%m%d).tar.gz -``` - -Render also takes daily snapshots of persistent disks automatically on -paid plans — the SSH tarball is a belt-and-braces option you can ship -off-platform. - -## Cost floor and egress - -- Starter plan web service: $7/month (0.5 CPU, 512 MB RAM). -- 1 GB persistent disk: $0.25/GB/month, so $0.25/month for the default. -- Bandwidth: 100 GB outbound included, then $0.10/GB. - -See for the current rate card. - -## Known caveats - -- Render Free tier does not support persistent disks. The Starter plan - ($7/month) is the minimum. -- Render restarts the service on every deploy. The HMAC secret survives - because it lives on the disk, but expect a 10–30 s gap of 502s - during rollouts. -- Render runs amd64 only for web services. The Dockerfile selects the - matching iii binary automatically via `uname -m`. diff --git a/deploy/render/entrypoint.sh b/deploy/render/entrypoint.sh deleted file mode 100755 index ffdd6333..00000000 --- a/deploy/render/entrypoint.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/sh -# agentmemory first-boot entrypoint. -# -# Runs as root so it can: -# 1. Overwrite the npm-bundled iii-config.yaml (which binds 127.0.0.1 -# and uses relative ./data paths) with a deploy-tuned version that -# binds 0.0.0.0 and uses absolute /data paths. -# 2. chown the platform-mounted /data volume to the runtime user -# (managed platforms mount volumes root-owned 755 by default). -# 3. Generate the HMAC secret on first boot and persist it to -# /data/.hmac (chmod 600) so the secret survives restarts. -# -# Then it execs the agentmemory CLI under gosu as the unprivileged -# `node` user. - -set -eu - -DATA_DIR="${AGENTMEMORY_DATA_DIR:-/data}" -HMAC_FILE="${AGENTMEMORY_HMAC_FILE:-/data/.hmac}" -RUN_AS="node:node" -III_CONFIG="/opt/agentmemory/node_modules/@agentmemory/agentmemory/dist/iii-config.yaml" - -mkdir -p "$DATA_DIR" -chown -R "$RUN_AS" "$DATA_DIR" - -cat > "$III_CONFIG" <<'EOF' -workers: - - name: iii-http - config: - port: 3111 - host: 0.0.0.0 - default_timeout: 180000 - cors: - allowed_origins: - - "http://localhost:3111" - - "http://localhost:3113" - - "http://127.0.0.1:3111" - - "http://127.0.0.1:3113" - allowed_methods: [GET, POST, PUT, DELETE, OPTIONS] - - name: iii-state - config: - adapter: - name: kv - config: - store_method: file_based - file_path: /data/state_store.db - - name: iii-queue - config: - adapter: - name: builtin - - name: iii-pubsub - config: - adapter: - name: local - - name: iii-cron - config: - adapter: - name: kv - - name: iii-stream - config: - port: 3112 - host: 0.0.0.0 - adapter: - name: kv - config: - store_method: file_based - file_path: /data/stream_store - - name: iii-observability - config: - enabled: true - service_name: agentmemory - exporter: memory - sampling_ratio: 1.0 - metrics_enabled: true - logs_enabled: true - logs_console_output: true -EOF -chown "$RUN_AS" "$III_CONFIG" - -if [ ! -s "$HMAC_FILE" ]; then - SECRET="$(openssl rand -hex 32)" - umask 077 - printf '%s\n' "$SECRET" > "$HMAC_FILE" - chmod 600 "$HMAC_FILE" - chown "$RUN_AS" "$HMAC_FILE" - echo "================================================================" - echo "agentmemory: generated HMAC secret on first boot" - echo "AGENTMEMORY_SECRET=$SECRET" - echo "Copy this value now. It will not be printed again." - echo "Stored at: $HMAC_FILE (chmod 600)" - echo "To rotate: delete $HMAC_FILE on the persistent volume and restart." - echo "================================================================" -fi - -AGENTMEMORY_SECRET="$(cat "$HMAC_FILE")" -export AGENTMEMORY_SECRET - -exec gosu "$RUN_AS" agentmemory "$@" diff --git a/deploy/render/render.yaml b/deploy/render/render.yaml deleted file mode 100644 index c96516d0..00000000 --- a/deploy/render/render.yaml +++ /dev/null @@ -1,22 +0,0 @@ -services: - - type: web - name: agentmemory - runtime: docker - plan: starter - dockerfilePath: ./deploy/render/Dockerfile - dockerContext: ./deploy/render - healthCheckPath: /agentmemory/livez - autoDeploy: false - disk: - name: data - mountPath: /data - sizeGB: 1 - envVars: - - key: PORT - value: "3111" - - key: AGENTMEMORY_VERSION - value: "0.9.12" - - key: III_VERSION - value: "0.11.2" - - key: III_SDK_VERSION - value: "0.11.2" diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 6b117652..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,47 +0,0 @@ -services: - # One-shot init container: docker creates named volumes root:root mode - # 755, but the iii-engine image is distroless and runs as UID 65532 - # with no `chown` of its own. Without this, /data is unwritable, the - # engine silently buffers in RAM, and state evaporates on every - # restart — the exact symptom v0.9.7's working-directory fix set out - # to solve. Runs once at compose-up and exits. - iii-init: - image: busybox:1.36 - user: "0:0" - volumes: - - iii-data:/data - entrypoint: ["sh", "-c", "chown -R 65532:65532 /data && chmod 755 /data"] - restart: "no" - - iii-engine: - # Pinned to v0.11.2 — the last engine that runs agentmemory's current - # worker model cleanly. v0.11.6 introduces a new sandbox-everything- - # via-`iii worker add` model that agentmemory hasn't been refactored - # for yet; the architectural mismatch surfaces as EPIPE reconnect - # loops and empty search after save. Bump only after agentmemory is - # refactored to register as a sandboxed worker. - # - # Override per-shell or via .env file: - # AGENTMEMORY_III_VERSION=0.11.7 docker compose up - image: iiidev/iii:${AGENTMEMORY_III_VERSION:-0.11.2} - user: "65532:65532" - depends_on: - iii-init: - condition: service_completed_successfully - ports: - - "127.0.0.1:49134:49134" - - "127.0.0.1:3111:3111" - - "127.0.0.1:3112:3112" - - "127.0.0.1:9464:9464" - volumes: - - iii-data:/data - - ./iii-config.docker.yaml:/app/config.yaml:ro - restart: unless-stopped - logging: - driver: json-file - options: - max-size: "10m" - max-file: "3" - -volumes: - iii-data: diff --git a/docs/browser-extension-ai-site-test-cards-cn.md b/docs/browser-extension-ai-site-test-cards-cn.md deleted file mode 100644 index 59a2ae5d..00000000 --- a/docs/browser-extension-ai-site-test-cards-cn.md +++ /dev/null @@ -1,141 +0,0 @@ -# 浏览器插件真实 AI 站点测试卡 - -这份测试卡给外部试用者使用。它把真实 AI 页面验收拆成四张必测卡,避免只在本地 demo 通过就误判为公开可发布。 - -当前公开发布门槛:ChatGPT、Claude、Gemini、Perplexity 四个必测站点都要有通过证据。Grok 和 DeepSeek 可以作为扩展验证,但不计入当前公开发布门槛。 - -## 测试前准备 - -1. 启动本地 Agent Memory Lab Viewer / API。 -2. 在 Chrome 或 Edge 开发者模式加载 `browser-extension/`。 -3. 打开目标 AI 产品页面并登录。 -4. 打开插件同步侧栏,确认能看到版本、本地连接状态、“复制问题信息”和“复制检查步骤”。 -5. 在目标站点输入一个真实问题并等待 AI 回复,页面里至少要有一轮真实对话。 -6. 如果真实页面没有出现“记忆建议”,再打开 `启动输出里的 Viewer 地址 + /demo/browser-extension.html` 做自检,用来区分是站点适配问题还是插件注入整体异常。 -7. 每测完一个真实 AI 页面,都先复制侧栏诊断,再复制侧栏生成的保存命令,把证据保存到 `docs/validation/browser-extension-ai-sites/`。 - -开始逐站验收前,建议先生成本次验收清单: - -```bash -npm run prepare:ai-validation -``` - -它会在 `artifacts/ai-validation-run/` 下生成当前提交对应的必测站点、建议 prompt 和证据文件路径,避免多人验收时漏站点或混用旧提交。 - -保存诊断的推荐命令: - -```bash -cd agentmemory-lab -npm run wizard:ai-validation-evidence -``` - -侧栏里的“复制检查步骤”会按当前 Provider 自动补上 `--provider`,可以直接粘贴到项目终端后按提示确认浏览器版本和备注。 - -只有真实确认“插入记忆成功、诊断已复制、原站输入仍正常”以后,才可以加 `--pass`。 - -## 统一通过标准 - -每张测试卡都按同一组标准判断: - -- Provider 识别正确。 -- 输入框已找到。 -- 输入框附近出现“记忆建议”。 -- 点击建议后可以插入或复制记忆。 -- 同步侧栏可以复制问题信息 JSON。 -- 原站输入、发送、滚动、模型选择和附件按钮没有异常。 -- 诊断 JSON 的 `turnCount > 0`,并且 `matchedSelectors.turn` 能说明插件命中了真实会话区域。 -- 待审阅候选来自具体对话或用户选中的文字;页面介绍、URL、导航文案或输入框草稿不能算通过。 -- 诊断 JSON 的 `manualValidation.memoryInsertPassed`、`manualValidation.diagnosticsCopied`、`manualValidation.siteInputStillWorks` 都为通过。 -- 诊断 JSON 里有 `matchedSelectors.editor`、`matchedSelectors.anchor`、`matchedSelectors.send`、`matchedSelectors.turn`,方便复现站点适配问题。 - -## Mem0 式站点适配检查 - -Mem0 的插件实现提醒我们:每个 AI 产品都要当成独立站点适配,而不是只看域名匹配。测试时请额外确认这 5 件事: - -- `provider` 是否与当前产品一致,而不是命中通用 fallback。 -- `editor` 是否指向真正写 prompt 的输入区,而不是历史消息、搜索结果或隐藏 textarea。 -- `anchor` 是否靠近输入框工具栏,入口是否避开发送、附件、语音和模型选择按钮。 -- 插入记忆后,原站是否触发输入状态,例如发送按钮可用、光标还在输入区。 -- `turnSelectors` 是否能读到当前会话区域,方便后续做上下文召回,而不是只保存网页标题。 -- 复制问题信息后,检查 `matchedSelectors` 是否能说明当前页面命中了哪条输入框、锚点、发送按钮和会话 selector。 - -如果这 5 项里有任意一项不稳定,就不要把该站点标为通过。把诊断 JSON 和截图交回,优先更新 `browser-extension/shared/site-config.js`;当某个站点规则开始变复杂,再拆成独立 adapter。 - -## 测试卡 1:ChatGPT - -- 目标域名:`chatgpt.com` 或 `chat.openai.com` -- 重点观察:输入框底部工具栏、发送按钮、语音按钮、模型选择入口。 -- 建议测试 prompt:`帮我找一下我们之前关于浏览器插件和本地记忆的产品决策。` -- 通过时应该看到:插件识别 Provider 为 ChatGPT,入口贴近 prompt 区域,不遮挡发送或语音按钮。 -- 常见失败记录:入口没有出现、入口遮挡发送按钮、插入后输入框没有触发原站输入状态。 - -## 测试卡 2:Claude - -- 目标域名:`claude.ai` -- 重点观察:富文本输入框、附件按钮、发送按钮、项目/聊天页面差异。 -- 建议测试 prompt:`总结一下 Agent Memory Lab 的插件交付还差哪些真实站点证据。` -- 通过时应该看到:插件识别 Provider 为 Claude,入口在输入区附近,插入内容后富文本输入仍可继续编辑。 -- 常见失败记录:contenteditable 插入失败、入口被输入区裁切、项目页和普通聊天页表现不一致。 - -## 测试卡 3:Gemini - -- 目标域名:`gemini.google.com` -- 重点观察:`rich-textarea` 输入区、发送按钮、语音/图片入口、多语言按钮文本。 -- 建议测试 prompt:`基于我的本地记忆,帮我整理浏览器插件的下一步验收计划。` -- 通过时应该看到:插件识别 Provider 为 Gemini,输入框状态为已找到,入口不影响原站发送。 -- 常见失败记录:输入框 selector 变化、入口挂到错误容器、插入后发送按钮不激活。 - -## 测试卡 4:Perplexity - -- 目标域名:`perplexity.ai` 或 `www.perplexity.ai` -- 重点观察:搜索输入框、模型选择按钮、Submit/Send 按钮、线程页面和首页差异。 -- 建议测试 prompt:`检索并结合我的本地记忆,说明多 AI 网页记忆插件应该怎么验收。` -- 通过时应该看到:插件识别 Provider 为 Perplexity,入口靠近输入区但不挡住模型选择和提交。 -- 常见失败记录:入口跑到页面主体、输入框识别成搜索结果区域、复制问题信息缺少 placement 或 anchor 信息。 - -## 可选测试卡:Grok / DeepSeek - -- Grok 目标域名:`grok.com` 或 `x.ai` -- DeepSeek 目标域名:`chat.deepseek.com` 或 `deepseek.com` -- 目的:验证更多 AI 网页的适配潜力。 -- 注意:这两个站点当前不计入公开发布门槛,但失败也要记录,方便后续扩展 supported sites。 - -## 失败时怎么反馈 - -失败不是坏事,真实失败比“看起来能用”更有价值。反馈时请包含: - -- 产品名和 URL。 -- 浏览器名称和版本。 -- 失败发生在哪一步。 -- 同步侧栏“复制问题信息”的 JSON。 -- 截图或录屏,但不要包含私人聊天全文、Cookie、Token、学校申请材料或账号信息。 - -反馈模板:`docs/external-feedback-template-cn.md`。 - -GitHub Issue 模板:`.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml`。 - -## 验收后同步 - -录入证据后运行: - -```bash -cd agentmemory-lab -npm run wizard:ai-validation-evidence -npm run check:ai-validation-evidence -npm run sync:ai-validation-table -npm run status:delivery -``` - -`wizard:ai-validation-evidence` 默认读取剪贴板里的侧栏诊断,会逐项确认:诊断是否复制成功、插入/复制本地记忆是否成功、原站输入和发送是否仍然正常、浏览器版本和无隐私备注。已经把诊断保存成文件时,也可以运行: - -```bash -npm run wizard:ai-validation-evidence -- --file diagnostics.json -``` - -已经确认三项人工验收都通过时,可以用无交互模式: - -```bash -npm run wizard:ai-validation-evidence -- --file diagnostics.json --yes --browser "Chrome 版本号" --notes "无隐私信息的备注" -``` - -`status:delivery` 只有在四个必测站点都通过后,才会让真实站点证据从 `0/4` 推到 `4/4`。在此之前,公开发布仍然是 `not-ready`。 diff --git a/docs/browser-extension-ai-validation-cn.md b/docs/browser-extension-ai-validation-cn.md deleted file mode 100644 index 0f9718e1..00000000 --- a/docs/browser-extension-ai-validation-cn.md +++ /dev/null @@ -1,154 +0,0 @@ -# 浏览器插件 AI 站点验收记录 - -这份表用于记录浏览器插件的 AI 页面验收结果。`npm run check:browser-extension` 里的 fixture 只能证明 selector 规则没有结构性退化;本地预览页能证明基础交互可跑通;真正对外发布前,还需要在真实网页里确认输入框旁“记忆建议”、插入、复制和侧栏诊断都可用。 - -## 验收方法 - -1. 启动本地 Viewer / API。 -2. 在 Chrome / Edge 开发者模式加载 `browser-extension/`。 -3. 打开目标 AI 产品页面并登录。 -4. 如果目标页面没有出现“记忆建议”,再打开本地自检页 `启动输出里的 Viewer 地址 + /demo/browser-extension.html`,区分是站点适配问题还是插件注入整体异常。 -5. 在输入框输入一个和本地记忆相关的问题,至少 8 个字。 -6. 打开插件同步侧栏,检查“AI 页面状态”。 -7. 检查输入框附近是否出现“记忆建议”提示。 -8. 尝试插入一条记忆到输入框。 -9. 点击“复制问题信息”,把 JSON 保存到 `docs/validation/browser-extension-ai-sites/`。可以手动保存为 `YYYY-MM-DD-provider.json`,也可以使用 `npm run wizard:ai-validation-evidence` 自动命名和补齐模板。 -10. 记录截图、日期、浏览器版本和结果。 -11. 运行 `npm run check:ai-validation-evidence` 生成证据汇总。 -12. 运行 `npm run sync:ai-validation-table`,把证据同步回本页验收表。 - -复制侧栏诊断后,可以直接从剪贴板保存证据: - -```bash -cd agentmemory-lab -npm run wizard:ai-validation-evidence -- --clipboard -``` - -如果已经人工确认插入记忆成功、诊断复制成功、原站输入仍正常,再加 `--pass`。不要在没有真实操作的情况下使用 `--pass`。 - -## 通过标准 - -- Provider 被正确识别。 -- 输入框状态为“已找到”。 -- 输入框附近出现“记忆建议”入口。 -- 侧栏显示入口锚点已找到,并记录入口位置策略。 -- 侧栏能读到真实对话,诊断里的 `turnCount` 大于 0;如果没有读到对话,空状态要提示展开真实对话或选中具体内容。 -- 记忆候选来自具体对话或选中文本,不是网页介绍、链接或输入框草稿。 -- 本地搜索有结果时,可以插入或复制记忆。 -- 同步侧栏可复制问题信息 JSON。 -- 插件没有导致原站点输入框、发送按钮、页面滚动异常。 -- 诊断 JSON 保留 `matchedSelectors.editor`、`matchedSelectors.anchor`、`matchedSelectors.send`、`matchedSelectors.turn`,能说明站点适配命中了哪些规则。 - -## 证据目录和汇总命令 - -真实站点证据目录:`docs/validation/browser-extension-ai-sites/`。 - -逐站测试卡:`docs/browser-extension-ai-site-test-cards-cn.md`。外部试用者应按测试卡逐个验证 ChatGPT、Claude、Gemini、Perplexity,而不是只凭本地 demo 判断插件可发布。 - -每份诊断 JSON 会自带 `manualValidation` 模板。保存证据前,请把下面这些字段按真实验收结果改好: - -```json -{ - "manualValidation": { - "memoryInsertPassed": true, - "diagnosticsCopied": true, - "siteInputStillWorks": true, - "browser": "Chrome 版本号", - "notes": "无隐私信息的备注" - } -} -``` - -汇总命令: - -```bash -cd agentmemory-lab -npm run wizard:ai-validation-evidence -- --file diagnostics.json -npm run check:ai-validation-evidence -npm run sync:ai-validation-table -``` - -第一条命令会把诊断 JSON 保存成标准证据文件。第二条命令会生成 `artifacts/ai-validation-evidence-summary.json`。第三条命令会用证据目录更新本页真实站点验收表。它们不会把待验收状态误判为通过;`memoryInsertPassed`、`diagnosticsCopied`、`siteInputStillWorks` 都为通过,`turnCount` 大于 0,并且 `matchedSelectors` 保留输入框、锚点、发送按钮和会话区域的命中规则时,才会计入真实证据通过数。公开发布仍需 ChatGPT、Claude、Gemini、Perplexity 都有通过证据。 - -## 本地可验证项 - -| 项目 | 命令 / 页面 | 通过标准 | 当前状态 | 证据 | -| --- | --- | --- | --- | --- | -| 插件结构检查 | `npm run check:browser-extension` | 内容脚本语法、站点配置、图标、右键 selection/link 保存检查通过 | 已通过 | 最近一次 `npm run package:browser-extension && npm run check:delivery` | -| 免登录交互烟测 | `scripts/check-browser-extension-demo-interaction.mjs` | 模拟内容脚本在预览页创建“记忆建议”、渲染演示记忆,并把记忆插入输入框 | 已通过 | `check:browser-extension` 已包含 | -| 插件包检查 | `npm run package:browser-extension` | 生成 `artifacts/agent-memory-lab-extension.zip`,且包含 manifest、content script、service worker、side panel、popup/options、shared files、PNG 图标 | 已通过 | package check: 25 entries | -| 交付检查 | `npm run check:delivery` | 构建、README 图片引用、插件预览页、插件包检查均通过 | 已通过 | delivery checks ok | -| 免登录预览页 | `启动输出里的 Viewer 地址 + /demo/browser-extension.html` | 页面可访问,并含 `Agent Memory Demo`、演示输入框和演示记忆 | 已通过 | `check:delivery` 会启动预览服务并抓取页面 | -| 真实证据记录 | `npm run wizard:ai-validation-evidence` | 从剪贴板或文件保存标准诊断 JSON,不手工猜文件名 | 已接入 | `docs/validation/browser-extension-ai-sites/` | -| 真实证据汇总 | `npm run check:ai-validation-evidence` | 读取真实 AI 页面诊断 JSON,输出必需产品通过计数 | 已接入 | `artifacts/ai-validation-evidence-summary.json` | - -## 真实站点验收表 - -| 产品 | 目标域名 | Provider | 输入框 | 记忆提示 | 插入 | 复制问题信息 | 结果 | 日期 | 证据/备注 | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| ChatGPT | `chatgpt.com` | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | - | - | -| Claude | `claude.ai` | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | - | - | -| Gemini | `gemini.google.com` | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | - | - | -| Perplexity | `www.perplexity.ai` | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | - | - | -| Grok | `grok.com` | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | - | - | -| DeepSeek | `chat.deepseek.com` | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | - | - | -## 诊断 JSON 示例 - -```json -{ - "product": "Agent Memory Lab Browser Extension", - "extension": { - "name": "Agent Memory Lab", - "version": "0.1.0", - "manifestVersion": 3 - }, - "generatedAt": "2026-06-08T00:00:00.000Z", - "page": { - "title": "ChatGPT", - "url": "https://chatgpt.com/", - "host": "chatgpt.com", - "origin": "https://chatgpt.com", - "type": "ai-chat", - "typeLabel": "AI 对话" - }, - "ai": { - "supportedAiPage": true, - "provider": "ChatGPT", - "editorFound": true, - "editorSelector": "#prompt-textarea", - "anchorFound": true, - "anchorSelector": "[data-testid=\"composer-trailing-actions\"]", - "anchorSource": "configured", - "sendFound": true, - "sendSelector": "button[data-testid=\"send-button\"]", - "turnSelector": "[data-message-author-role]", - "turnSelectorCount": 4, - "matchedSelectors": { - "editor": "#prompt-textarea", - "anchor": "[data-testid=\"composer-trailing-actions\"]", - "anchorSource": "configured", - "adjacent": "button[aria-label=\"Dictate button\"]", - "send": "button[data-testid=\"send-button\"]", - "turn": "[data-message-author-role]" - }, - "placement": "toolbar-end", - "memoryWidgetVisible": true, - "promptLength": 18, - "turnCount": 4, - "checkedAt": "2026-06-08T00:00:00.000Z" - }, - "manualValidation": { - "memoryInsertPassed": false, - "diagnosticsCopied": true, - "siteInputStillWorks": false, - "browser": "填写浏览器名称和版本", - "notes": "填写无隐私信息的验收备注" - } -} -``` - -## 修复记录 - -| 日期 | 产品 | 问题 | 修复 | 验证 | -| --- | --- | --- | --- | --- | -| - | - | - | - | - | diff --git a/docs/browser-extension-privacy-cn.md b/docs/browser-extension-privacy-cn.md deleted file mode 100644 index fd43c6b2..00000000 --- a/docs/browser-extension-privacy-cn.md +++ /dev/null @@ -1,91 +0,0 @@ -# Agent Memory Lab 浏览器插件:权限与隐私说明 - -这份说明用于本地预览、内部试用和未来 Chrome Web Store 发布前准备。当前插件默认只连接本机的 Agent Memory Lab 服务,不把网页内容上传到第三方云端。 - -## 一句话说明 - -插件会在用户浏览网页或使用 AI 产品时识别当前页面上下文,生成可审阅的记忆候选;内容进入长期记忆前必须经过本地 Viewer 审阅和确认。 - -## 权限说明 - -| 权限 | 为什么需要 | 用户可见行为 | -| --- | --- | --- | -| `activeTab` | 读取当前激活页面的标题、URL、选中文本和页面摘要 | 点击插件弹窗或侧栏时识别当前页面 | -| `tabs` | 打开本地 Viewer、识别当前标签页 | 点击“工作台”“Skill”后打开本地页面 | -| `storage` | 保存 API 地址、Viewer 地址、访问密钥和最近同步记录 | 插件设置与最近记录在本机保留 | -| `sidePanel` | 提供浏览器侧栏 | 打开“同步侧栏”查看候选记忆和经验 | -| `contextMenus` | 提供右键保存入口 | 右键页面可加入本地审阅 | -| `scripting` | 为未来按需注入能力预留 | 当前主要用于本地开发和后续站点适配 | -| `` content script | 允许插件在不同网页识别页面类型,并在支持的 AI 页面显示本地记忆提示 | 只有支持的 AI 页面会显示“本地记忆”提示;其他网页主要用于用户主动保存时采集上下文 | - -## 默认采集内容 - -插件会把当前页面标准化成 `PageCapture`: - -- 页面标题、URL、host、页面类型 -- meta description 和可见标题结构 -- 用户选中的文本 -- 支持的 AI 页面中的最近可见对话片段和输入框草稿,用于在本地生成候选记忆 -- AI 页面诊断信息:是否找到输入框、命中的选择器、输入草稿长度和最近对话数量 -- 候选记忆、候选经验、隐私风险提示 - -同步侧栏里的“复制问题信息”只会复制页面标题、URL、域名、AI 产品识别结果、输入框是否命中、命中的 selector、草稿长度、最近对话数量,以及一个需要用户手动填写的验收模板。它用于修复不同 AI 网站的页面适配,不会复制输入框草稿、完整对话正文、候选记忆正文、Cookie、密码、访问令牌或完整网页源码。 - -插件不会默认采集: - -- 浏览器历史记录 -- Cookie、密码、支付信息 -- 页面里的隐藏表单字段 -- 未打开页面的内容 - -## 数据流向 - -默认连接地址: - -```text -API: http://localhost:3111 -Viewer: 启动输出里的 Viewer 地址 -``` - -数据流: - -```text -网页 / AI 输入框 - -> 浏览器插件生成 PageCapture - -> 本地 Agent Memory Lab API - -> Viewer 待审阅队列 - -> 用户确认后写入本地记忆或经验 -``` - -如果本地工作台未启动,侧栏会显示“本地工作台未连接”,保存按钮会被禁用。 - -## 用户控制 - -- 保存网页需要用户点击弹窗、侧栏或右键菜单。 -- 长期写入需要在 Viewer 的“待审阅”里确认。 -- 审阅时可以编辑标题、内容、保存范围、分类备注和类型。 -- 不合适的候选可以忽略。 -- Skill 草稿只会预览和复制,不会自动写入本地 Skill 目录。 - -## 发布前仍需确认 - -- 逐站验收 ChatGPT、Claude、Gemini、Perplexity 的输入框定位与插入事件。 -- 准备 Chrome Web Store 需要的英文隐私政策、截图和权限解释。 -- 如果未来支持云同步,需要单独增加云端数据流向说明和用户授权流程。 - -## 外部测试诊断脱敏 - -外部测试者提交 AI 页面诊断时,正常情况下不需要、也不应该提交私人 prompt 或聊天正文;“复制问题信息”默认只包含结构字段和计数。如果页面标题或 URL 暴露了敏感项目名、账号路径或内部链接,可以先手动删改。为了复现站点适配问题,请保留这些非敏感结构字段: - -- `ai.provider` -- `ai.editorFound` -- `ai.anchorFound` -- `ai.memoryWidgetVisible` -- `ai.placement` -- `ai.matchedSelectors.editor` -- `ai.matchedSelectors.anchor` -- `ai.matchedSelectors.send` -- `ai.matchedSelectors.turn` -- `manualValidation` - -这些字段用于判断输入框、入口锚点、发送按钮和会话区域是否命中,不需要包含 Cookie、Token、账号密码或完整聊天内容。 diff --git a/docs/browser-extension-privacy-en.md b/docs/browser-extension-privacy-en.md deleted file mode 100644 index 862d66b7..00000000 --- a/docs/browser-extension-privacy-en.md +++ /dev/null @@ -1,91 +0,0 @@ -# Agent Memory Lab Browser Extension Privacy Policy Draft - -Last updated: 2026-06-08 - -Agent Memory Lab is a local-first browser extension that helps users capture webpage context, review memory candidates, and reuse local memories while working with AI assistants. - -This draft is prepared for external testing and future Chrome Web Store submission. Before public release, publish this policy at a stable URL and update any contact or legal details. - -## Data We Process - -When you use the extension, it may process information from the active browser tab, including: - -- Page title, URL, host, and page type. -- Meta description and visible headings. -- Text you selected on the page. -- Visible snippets from supported AI conversation pages, used locally to generate memory candidates. -- Current prompt draft text on supported AI pages, used locally to suggest relevant memories. -- Candidate memories, candidate lessons, and privacy hints generated from the page. -- AI page diagnostics, including detected provider, whether an input editor was found, matched selector, prompt length, and recent turn count. - -The extension does not intentionally collect: - -- Browser history unrelated to the active tab. -- Cookies. -- Passwords. -- Payment details. -- Hidden form fields. -- Content from pages that are not open. - -## How Data Is Used - -The extension uses active-page context to: - -- Generate memory and lesson candidates. -- Send candidates to the local Agent Memory Lab review queue. -- Search local memories relevant to the current prompt. -- Show memory suggestions near supported AI input boxes. -- Help users validate site support through AI diagnostics. - -Long-term memory is not created silently by the extension. Candidates are reviewed in the local Agent Memory Lab workbench before they are saved. - -## Where Data Goes - -By default, the extension connects to local services: - -- Local API: `http://localhost:3111` -- Local viewer: `启动输出里的 Viewer 地址` - -Captured content is sent to your local Agent Memory Lab instance for review. The extension does not send captured content to a third-party cloud service by default. - -If you configure a different API URL, data will be sent to the URL you configured. You are responsible for understanding and trusting that endpoint. - -## Storage - -The extension may store the following in browser storage: - -- Local API URL. -- Local viewer URL. -- Optional access secret. -- Recent capture records used for extension UI history. - -Long-term memories are stored by the local Agent Memory Lab workbench, not by the browser extension itself. - -## AI Diagnostics - -The side panel can copy diagnostic JSON for supported AI pages. Diagnostics are meant to help validate and repair site-specific input-box selectors. The copied JSON also includes a manual validation template for insert/copy/site-input checks; users must fill those fields themselves before treating evidence as passed. - -Diagnostic JSON may include page title, URL, host, detected provider, editor-found state, matched selector, prompt length, and recent turn count. It does not include prompt draft text, full conversation turns, memory candidate body text, cookies, passwords, access tokens, or full hidden page source. - -External testers normally do not need to submit private prompt text or chat body text because copied diagnostics are designed to contain only structure fields and counts. If a page title or URL exposes a sensitive project name, account path, or internal link, testers may redact those values before sharing. For site-adapter validation, keep the non-sensitive structural fields under `ai`, especially `provider`, `editorFound`, `anchorFound`, `memoryWidgetVisible`, `placement`, and `matchedSelectors.editor/anchor/send/turn`, plus the `manualValidation` result. These fields help reproduce selector issues without sharing cookies, tokens, passwords, or full chat content. - -## User Control - -Users control what becomes durable memory: - -- Saving a page requires user action through the popup, side panel, or context menu. -- Long-term memory requires review and approval in the local workbench. -- Candidates can be edited or dismissed before saving. -- Local settings can be changed from the extension options page. - -## Data Sharing - -Agent Memory Lab does not sell user data. The browser extension does not share captured page content with third-party services by default. - -## Security Notes - -Because Agent Memory Lab is local-first, users should protect their local machine, local API endpoint, and any configured access secret. Do not expose the local API to untrusted networks. - -## Changes to This Policy - -This policy may change as the extension evolves. Material changes should be reflected in this file, the store listing, and the public privacy URL before release. diff --git a/docs/browser-extension-store-listing-en.md b/docs/browser-extension-store-listing-en.md deleted file mode 100644 index 8b21205f..00000000 --- a/docs/browser-extension-store-listing-en.md +++ /dev/null @@ -1,91 +0,0 @@ -# Agent Memory Lab Browser Extension: Store Listing Draft - -This document is the English release copy for external testers and future Chrome Web Store submission. It should stay aligned with the Chinese README, privacy note, and AI site validation log. - -## One-line Description - -Bring local, reviewable memory into ChatGPT, Claude, Gemini, Perplexity, and your everyday web research. - -## Short Description - -Agent Memory Lab adds a local-first memory layer to your browser. Capture webpages, review memory candidates, and surface relevant local memories near AI input boxes before you ask. - -## Long Description - -Agent Memory Lab is a browser extension for people who work across AI assistants, research pages, documentation, and local projects. - -Instead of silently uploading everything you see, the extension turns the current page or AI conversation into a structured `PageCapture`: page title, URL, selected text, visible headings, candidate memories, candidate lessons, privacy hints, and AI-page diagnostics. Content is sent to your local Agent Memory Lab workbench first, where you can review, edit, approve, or dismiss it before it becomes long-term memory. - -On supported AI pages, the extension can show relevant local memories near the input box so you can reuse project context while writing prompts. The current supported-site structure covers ChatGPT, Claude, Gemini, Perplexity, Grok, and DeepSeek, with diagnostics to help validate and repair site-specific selectors as these products change. - -## Core Features - -- Save the current webpage to a local review queue. -- Capture selected text, page title, URL, description, and visible headings. -- Review candidate memories and lessons before they are saved long-term. -- Show relevant local memories near AI input boxes on supported AI sites. -- Insert or copy recalled memories into the current AI prompt. -- Use the side panel to inspect page type, privacy hints, connection status, and AI-page diagnostics. -- Copy AI diagnostics and a manual validation template for site validation without sharing cookies, passwords, or hidden page source. -- Open the local Agent Memory Lab workbench and Skill page directly from the extension. - -## What Makes It Different - -Many memory tools optimize for cloud sync and automatic capture. Agent Memory Lab is designed for local-first, human-reviewed memory. The extension can recognize useful context, but long-term memory writes still go through the local workbench review queue. - -This is especially useful for users who want AI assistants to remember project context without giving up control over what becomes durable memory. - -## Permission Justification - -| Permission | Why it is needed | User-visible behavior | -| --- | --- | --- | -| `activeTab` | Read the active page title, URL, selected text, and visible page context after user action | Used when the user opens the popup, side panel, or saves the current page | -| `tabs` | Open the local workbench and identify the current tab | Used by buttons such as “Workbench” and “Skill” | -| `storage` | Store local API settings and recent capture history | Keeps the API URL, viewer URL, optional secret, and recent records in the browser | -| `sidePanel` | Provide the browser side panel | Shows page context, candidates, diagnostics, and local connection status | -| `contextMenus` | Provide a right-click save action | Lets users send the current page to local review from the context menu | -| `scripting` | Reserved for controlled site adaptation during local development | Used only for extension functionality and future supported-site refinements | -| Host access | Recognize page types and show local memory hints on supported AI sites | AI memory hints appear only on supported AI pages; other pages are captured only when the user acts | - -## Privacy Summary - -Agent Memory Lab is local-first by default. The extension connects to your local Agent Memory Lab API, usually `http://localhost:3111`, and opens your local viewer, usually `启动输出里的 Viewer 地址`. - -The extension does not intentionally collect browser history, cookies, passwords, payment details, hidden form fields, or unopened pages. It does not send captured content to a third-party cloud service by default. - -Data that may be processed after user action includes: - -- Page title, URL, host, and page type. -- Meta description and visible headings. -- User-selected text. -- Visible snippets from supported AI conversations. -- Current prompt draft text on supported AI pages. -- Candidate memories, candidate lessons, and privacy hints. -- AI diagnostics such as provider, editor-found state, matched selector, prompt length, and turn count. - -Long-term memory creation requires review in the local Agent Memory Lab workbench. - -## Screenshots Needed Before Public Submission - -- Extension icons are prepared in PNG sizes 16, 32, 48, and 128 under `browser-extension/icons/`. -- Dashboard / home page of the local workbench. -- Skill management page. -- Browser extension side panel on a supported AI page. -- Review queue in the memory page. -- Optional: AI input-box memory hint with non-sensitive demo data. - -Do not use screenshots containing private memories, personal identity details, private chats, API keys, or real user documents. - -## Pre-release Checklist - -- `npm run check:delivery` passes. -- `artifacts/agent-memory-lab-extension.zip` is generated. -- `docs/browser-extension-ai-validation-cn.md` has real-site validation notes for the target AI products. -- The privacy policy is published at a stable URL. -- Store screenshots use non-sensitive demo data. -- Permission descriptions match `browser-extension/manifest.json`. -- The extension has a clear support/contact path. - -## Support Copy - -If the memory hint does not appear on an AI page, open the extension side panel and check “AI Page Status.” If the editor is not found, click “Copy Diagnostics” and include the copied JSON in your feedback. The diagnostics are intended to help repair supported-site selectors and do not include cookies or passwords. diff --git a/docs/demo-checklist-cn.md b/docs/demo-checklist-cn.md deleted file mode 100644 index 70d80404..00000000 --- a/docs/demo-checklist-cn.md +++ /dev/null @@ -1,103 +0,0 @@ -# Agent Memory Lab 演示检查清单 - -这份清单用于演示前自查,目标是 5 分钟内把产品讲清楚、跑通核心路径,并避免现场翻车。 - -## 演示前准备 - -- GitHub 分支打开:`szn-viewer-ui-iteration` -- 插件免登录预览可启动:`cd agentmemory-lab && npm run preview:browser-extension` -- 本地插件预览页可访问:`启动输出里的 Viewer 地址 + /demo/browser-extension.html` -- 完整工作台可启动:`cd agentmemory-lab && npm run build && npm run start:local-memory`,Viewer 默认 `启动输出里的 Viewer 地址,例如 http://localhost:3114/#dashboard`,并读取 `你的本地记忆数据目录` 本地记忆库 -- 完整工作台状态检查可运行:`cd agentmemory-lab && npm run check:workbench` -- 如果刚运行过 `npm run build`、`npm run package:browser-extension` 或 `npm run check:delivery`,先重启 `npm run start:local-memory`,再做工作台状态检查 -- 如果 `node dist/cli.mjs demo` 提示 `worker not reachable on port 3111`,通常是端口上只剩 iii-engine 壳、Agent Memory worker 没挂上。先运行 `node dist/cli.mjs stop --force` 清理,再从项目目录运行 `npm run start:local-memory`。 -- 浏览器插件已加载:Chrome / Edge → `chrome://extensions` → 加载 `browser-extension/` -- 插件检查已通过:`npm run check:browser-extension`,包括 AI 页面 fixture 的输入框和对话 selector 检查 -- 若要对外展示插件稳定性,更新 `docs/browser-extension-ai-validation-cn.md` 的真实站点验收记录 -- 若要对外分发插件,确认 `docs/browser-extension-privacy-en.md` 和 `docs/browser-extension-store-listing-en.md` 已同步最新权限与截图要求 -- README 截图只展示首页和 Skill 管理台 -- 不展示包含个人敏感资料的记忆卡片截图 - -## 核心演示路线 - -### 1. 打开首页 - -讲清一句话:Agent Memory Lab 是本地优先的 Agent 记忆工作台,不是普通聊天记录列表。 - -检查点: - -- 首页能打开 -- 最近会话卡片能看到 -- 导航里有总览、记忆、会话、Skill、待办、活动 - -### 2. 打开 Skill 管理台 - -讲清:本地 Skill 越来越多,需要能看来源、用途和路径。 - -检查点: - -- Skill 列表能加载 -- 搜索和来源筛选可见 -- 能打开详情或复制路径 - -### 3. 演示浏览器插件弹窗和侧栏 - -讲清:插件是网页和 AI 对话进入本地记忆库的入口。 - -检查点: - -- 插件弹窗能打开 -- 能看到当前网页标题 -- 能打开同步侧栏 -- 侧栏顶部显示“审阅队列可用”;如果显示未连接,先启动本地 Viewer / API 后重试 -- 侧栏能看到页面类型、候选记忆、候选经验、隐私提示 - -### 4. 演示待审阅队列 - -讲清:插件不会直接写入长期记忆,先进入待审阅,用户确认后才保存。 - -检查点: - -- 从插件加入待审阅 -- 回到记忆库顶部看到“待审阅” -- 点“审阅保存”可以编辑标题、内容、类型、保存范围、分类备注 -- 确认后进入长期记忆或经验 -- 在记忆库用“浏览器”来源筛选找到刚保存的内容 - -### 5. 演示 AI 页面记忆提示 - -讲清:参考 OpenMemory / Mem0 的思路,在 ChatGPT / Claude 等页面输入问题时,本地记忆会主动出现在输入框附近;不同的是,我们不会直接把网页内容写进长期记忆,而是先进入本地审阅队列。 - -检查点: - -- 先打开 `启动输出里的 Viewer 地址 + /demo/browser-extension.html` 做免登录预览 -- 同步侧栏识别为 `Agent Memory Demo`,并且输入框为“已找到” -- 输入框附近出现“记忆建议”入口,面板里能看到演示记忆,并可插入 / 复制 -- 打开 ChatGPT 或 Claude 页面 -- 在输入框输入一个和本地记忆相关的问题 -- 同步侧栏的“AI 页面状态”显示已识别产品,并且输入框为“已找到” -- 如果输入框为“未找到”,点击“复制问题信息”,把 JSON 留作下一轮站点适配材料 -- 输入框附近出现“记忆建议”按钮 -- 面板里能看到相关记忆 -- 能插入或复制到当前输入框 - -## 当前不要演示 - -- 不演示私密画像页 -- 不把活动页作为主卖点 -- 不承诺 Chrome Web Store 已发布 -- 不承诺所有 AI 产品的 DOM 抽取都已完美适配 - -## 讲法重点 - -- 本地优先:私人记忆先留在本机 -- 可审阅:自动整理不是自动乱存 -- 跨产品:浏览器插件覆盖 ChatGPT、Claude、Gemini、Perplexity 等 AI 页面 -- 可沉淀:高频经验未来可以进入 Skill - -## 演示后下一步 - -- 精调 ChatGPT / Claude / Perplexity 的输入框定位和插入事件 -- 继续逐站验收 ChatGPT / Claude / Gemini / Perplexity 的输入框定位和插入事件 -- Skill 草稿确认后写入本地目录 -- 准备 Chrome Web Store 英文隐私政策和发布截图 diff --git a/docs/dev/environment.md b/docs/dev/environment.md deleted file mode 100644 index c6290a35..00000000 --- a/docs/dev/environment.md +++ /dev/null @@ -1,63 +0,0 @@ -# 开发环境与 CI 实情 - -记录重构期间的环境事实、命令、CI 行为和**基线状态**。事实优先于记忆——本文件的数字都来自实跑。 - -## 1. 本地环境 - -- 仓库:内层 `agentmemory-lab/` 是唯一 git 仓库(外层 `AgentMemory- Session/` 是非 git 的文档外壳)。 -- 实测本机:Node `v24.16.0` / npm `11.13.0`。 -- 项目要求:`engines.node >= 20`;CI 跑 Node 20 与 22。 -- 构建:TypeScript → ESM via tsdown,输出 `dist/`。 -- 状态:文件 SQLite,经 iii-engine(WebSocket `49134`);详见方案 §12。 - -## 2. 常用命令 - -```bash -npm run build # tsdown 构建 + 拷贝 viewer/config 资源 -npm test # vitest run,排除 integration(CI 跑的就是这个) -npm test -- --run test/viewer-*.test.ts # 只跑受影响测试,省时 -npm run check:workbench # API / Viewer / demo 可达性 -npm run check:delivery # build + 插件 + README 截图 + 交付文档校验 -npm run start:local-memory # 用既有本地记忆数据目录启动 -``` - -## 3. CI 实情(决定 PR 怎么切) - -来源:`.github/workflows/ci.yml` + `publish.yml`。 - -| 维度 | 事实 | -|---|---| -| 触发 | push/PR 到 `main`;`workflow_dispatch` 手动 | -| **paths-ignore** | `**/*.md`、`**/*.mdx`、`website/**`、`docs/**`、`assets/**`、README/CHANGELOG/AGENTS/ROADMAP **不触发 CI** | -| 矩阵 | `os: [ubuntu, macos] × node: [20, 22]` = **4 格**,`fail-fast: false` | -| 每格步骤 | `npm install --package-lock-only` → `npm ci` → `npm run build` → **`npm test`(全量)** | -| 测试粒度 | 全量,**没有按目录/分片**;红就是整格红 | -| 并发 | 同 ref 的 PR 跑会被新推送 `cancel-in-progress`(push 到 main 不取消) | -| lockfile | **gitignore**,runner 内现生成 → 无 lockfile 冲突,但新依赖会改 4 格安装图 | -| Windows | 暂排除(`obsidian-export` 硬编码 POSIX 路径) | -| 发布 | `publish.yml` 在 release 时跑,`build + test` 后 npm publish 三个包,带 `--provenance` | - -**杠杆**:文档类改动零 CI 成本,可用来先合方案/锁文档。 -**陷阱**:任何碰 MCP 工具 / REST 端点 / 版本号的改动,要按 AGENTS.md 同步改 7~8 处,否则计数断言红。 - -## 4. 基线状态 ✅ 已修绿(STEP-00,2026-06-12) - -```text -Test Files 128 passed (128) - Tests 1384 passed (1384) - Duration ~7s -``` - -STEP-00 已消除原 8 个命名失败(详见 `issues/STEP-00-baseline-green.md`)。权威项目名锁定为 `agentmemory`。 - -
历史:修复前的 8 个失败(保留备查) - -8 个失败同一根因:检出目录名 `agentmemory-lab`,测试硬编码期望 `agentmemory`。 -- `test/hook-project.test.ts`(5)、`test/codex-plugin.test.ts`(2)、`test/copilot-plugin.test.ts`(1)。 -- 修法:3 个 manifest `name` 对齐 `agentmemory`;hook-project 测试改为运行时动态取 git 顶层目录名。 - -
- -## 5. `npm run build` 状态 - -构建可用(dist/ 存在且 viewer 资源齐全)。重构中每个代码 PR 推前本地必跑 `npm run build`,避免 4 格 runner 才发现构建错。 diff --git a/docs/development.md b/docs/development.md deleted file mode 100644 index 505f909f..00000000 --- a/docs/development.md +++ /dev/null @@ -1,64 +0,0 @@ -# Development - -Local development, testing, and debugging for AI Todo. For PR flow, DCO -sign-off, coding style, and how to add an MCP tool / hook, see -[CONTRIBUTING.md](../CONTRIBUTING.md). For module boundaries, code patterns, -and the Definition of Done, see [AGENTS.md](../AGENTS.md). - -## Requirements - -- Node.js 20+ -- npm - -## Setup - -```bash -cp .env.example .env # optional — defaults work for local use -npm install -``` - -## Commands - -Unified entrypoints — thin wrappers over the npm scripts, the same command for -humans, CI, and agents: - -```bash -./scripts/dev.sh # run the daemon from source (= npm run dev) -./scripts/test.sh # run the test suite (= npm test) -./scripts/lint.sh # consistency guard (= npm run check:consistency-local) -``` - -Authoritative pre-PR gate (run before pushing): - -```bash -npm run pre-pr # consistency + build + test -``` - -Other useful scripts: - -```bash -npm run build # bundle (tsdown) + copy viewer assets into dist/ -npm run start # run the built daemon (node dist/cli.mjs) -npm run test:integration # integration tests -``` - -> There is no standalone linter. `./scripts/lint.sh` runs the local consistency -> guard (counts / version drift); `npm run pre-pr` is the authoritative gate. - -## Testing rules - -- Bug fixes include a regression test. -- REST / MCP changes include integration coverage. -- Core logic changes include unit-style tests in `test/` (`.test.ts`). -- Doc-only changes don't need a full test run unless examples changed. - -## Debugging - -1. Reproduce the issue. -2. Find the smallest failing test or command. -3. Fix the cause, not the symptom. -4. Add a regression test when possible. - -> See [ARCHITECTURE.md](../ARCHITECTURE.md) for the architecture overview, data -> flow, and design principles, and [AGENTS.md](../AGENTS.md) for code patterns -> and consistency rules. diff --git a/docs/external-feedback-template-cn.md b/docs/external-feedback-template-cn.md deleted file mode 100644 index 5442d881..00000000 --- a/docs/external-feedback-template-cn.md +++ /dev/null @@ -1,74 +0,0 @@ -# Agent Memory Lab 外部试用反馈模板 - -请尽量不要粘贴私人聊天全文、账号信息、Cookie、访问令牌、学校申请材料或任何敏感页面内容。诊断 JSON 可以保留页面标题、URL、AI 页面状态和 `manualValidation` 字段;如有隐私信息,请先删改。 - -维护者收到这份反馈后,会按 [外部反馈分诊指南](external-feedback-triage-cn.md) 归类处理:先判断是不是本地连接、站点适配、输入事件、审阅队列、隐私信任或交付文档问题,再决定修插件、补 selector、更新证据还是改说明。 - -## 基本信息 - -- 试用日期: -- 操作系统: -- 浏览器和版本: -- 插件版本: -- 加载方式:仓库 `browser-extension/` / zip 解压后加载 -- 本地工作台状态:`npm run check:workbench` 结果 -- 交付状态:`npm run status:delivery` 结果 - -## 试用路径 - -- 试用页面:本地 demo / ChatGPT / Claude / Gemini / Perplexity / Grok / DeepSeek / 其他 -- 页面 URL: -- 你输入的问题或任务类型: -- 页面里是否已有至少一轮真实对话:是 / 否 -- 诊断 JSON 里的 `turnCount`: -- 待审阅候选是否来自具体对话或用户选中文本:是 / 否 / 不确定 -- 是否看到“记忆建议”:是 / 否 -- 是否成功插入或复制记忆:是 / 否 -- 是否成功把网页加入待审阅:是 / 否 -- 是否在 Viewer 待审阅队列看到内容:是 / 否 - -## 问题描述 - -- 发生了什么: -- 期望应该是什么: -- 实际结果是什么: -- 是否能稳定复现:每次 / 偶尔 / 只出现一次 -- 影响程度:阻断试用 / 影响核心体验 / 小问题 / 建议优化 - -## 诊断信息 - -请粘贴同步侧栏“复制问题信息”的 JSON,或提供 `docs/validation/browser-extension-ai-sites/` 里的证据文件名。 - -```json -{ - "product": "Agent Memory Lab Browser Extension", - "extension": {}, - "page": {}, - "ai": { - "turnCount": 0, - "matchedSelectors": { - "turn": "" - } - }, - "manualValidation": { - "memoryInsertPassed": false, - "diagnosticsCopied": true, - "siteInputStillWorks": false, - "browser": "", - "notes": "" - } -} -``` - -如果反馈来自真实 AI 页面,请保留 `turnCount` 和 `matchedSelectors.turn`,用来判断插件是否真的命中了会话区域。不要粘贴完整聊天正文;也不要把页面标题、链接、导航文案或输入框草稿当作记忆来源。 - -## 截图或录屏 - -- 附件路径或链接: -- 是否已确认不含敏感信息:是 / 否 - -## 你的建议 - -- 哪个地方最困惑: -- 哪个地方最有用: -- 你希望下一版优先改什么: diff --git a/docs/external-feedback-triage-cn.md b/docs/external-feedback-triage-cn.md deleted file mode 100644 index 01f1da58..00000000 --- a/docs/external-feedback-triage-cn.md +++ /dev/null @@ -1,65 +0,0 @@ -# Agent Memory Lab 外部反馈分诊指南 - -这份指南用于把外部试用反馈快速分成可执行任务。目标不是讨论“感觉不好”,而是判断问题属于连接、站点适配、记忆召回、审阅队列、打包交付还是文档说明。 - -## 先看三份材料 - -1. 反馈模板:`docs/external-feedback-template-cn.md` -2. 侧栏诊断 JSON:尤其是 `ai` 和 `manualValidation` -3. 当前状态:`npm run status:delivery` - -如果反馈没有诊断 JSON,先请试用者补“同步侧栏 -> 复制问题信息”。如果涉及真实 AI 页面公开发布验收,还需要把 JSON 放到 `docs/validation/browser-extension-ai-sites/`。 - -## 分诊表 - -| 现象 | 优先看 | 归类 | 下一步 | -| --- | --- | --- | --- | -| 插件弹窗显示未连接 | `npm run check:workbench`、API/Viewer 地址 | 本地连接 | 启动工作台或修配置页默认地址 | -| demo 页没有“记忆建议” | `npm run preview:browser-extension`、demo interaction check | 插件预览 | 修 demo 页或 content script 注入逻辑 | -| AI 页面 provider 识别错 | `ai.provider`、`page.host` | 站点适配 | 更新 `shared/site-config.js` 和内容脚本站点表 | -| 输入框未找到 | `ai.editorFound`、`ai.editorSelector` | 站点 selector | 补 `editorSelectors`,增加 fixture | -| 入口锚点未找到 | `ai.anchorFound`、`ai.placement` | 入口位置 | 补 `anchorSelectors` 或调整 placement | -| 只有页面标题/链接,没有具体对话记忆 | `ai.turnCount`、`matchedSelectors.turn`、候选草稿来源 | 会话抽取 | 补 `turnSelectors`,确认 `turnCount > 0` 后再算真实站点通过 | -| 有入口但挡住原站按钮 | 截图/录屏、`ai.placement` | 交互位置 | 调整 placement 或样式 | -| 插入记忆失败 | `manualValidation.memoryInsertPassed`、目标站点 | 输入事件 | 修插入逻辑和 input/change 事件触发 | -| 原站输入/发送异常 | `manualValidation.siteInputStillWorks` | 站点兼容 | 优先回滚相关站点注入策略 | -| 加入待审阅后 Viewer 看不到 | API 响应、最近同步、Viewer 待审阅 | 审阅队列 | 检查 `/agentmemory/review` 和来源筛选 | -| 保存内容不可信/太黑盒 | 草稿截图、标题正文、项目、标签、经验候选状态 | 审阅草稿 | 优化弹窗/侧栏草稿生成与说明 | -| zip 加载不清楚 | `LOAD-THIS-FIRST.md` 是否被看到 | 交付文档 | 更新 zip 内说明和外部试用指南 | -| 用户担心隐私 | 复制问题信息内容、隐私说明 | 隐私/信任 | 更新隐私说明,删去不必要字段 | - -## 优先级规则 - -| 优先级 | 条件 | 处理方式 | -| --- | --- | --- | -| P0 | 插件无法加载、demo 无法预览、审阅队列无法保存 | 立即修,修完跑 `npm run check:delivery` | -| P0 | 公开发布必需站点出现原站输入/发送异常 | 立即修,未修前不得标记站点通过 | -| P1 | 必需站点 provider/input/anchor/turn 任何一项失败,或 `turnCount` 为 0 | 更新 selector,补真实证据 | -| P1 | 保存前草稿不清楚、用户不知道保存到哪个项目或带了什么标签 | 优化弹窗/侧栏草稿元信息 | -| P2 | 文档措辞、截图、非必需站点适配 | 排入下一版 | - -## 修复后的最小验证 - -每次修复后至少运行: - -```bash -npm run check:browser-extension -npm run check:delivery -npm run status:delivery -``` - -如果修的是真实 AI 页面适配,还要运行: - -```bash -npm run check:ai-validation-evidence -npm run sync:ai-validation-table -npm run check:release-gates -``` - -如果准备公开发布,最后运行: - -```bash -npm run check:release-public -``` - -在 ChatGPT、Claude、Gemini、Perplexity 的表格和证据 JSON 都没有 4/4 通过前,公开发布检查失败是正确结果。 diff --git a/docs/external-test-loop-cn.md b/docs/external-test-loop-cn.md deleted file mode 100644 index 338e018e..00000000 --- a/docs/external-test-loop-cn.md +++ /dev/null @@ -1,42 +0,0 @@ -# Agent Memory Lab 外部测试闭环 - -这份文档用于把一次试用收成可复现、可分诊、可继续迭代的反馈。目标不是让测试者理解全部工程结构,而是让他先在真实网页里用起来,再把真实页面问题反馈回来。 - -## 四步闭环 - -| 步骤 | 测试者要做什么 | 我们需要得到什么 | -| --- | --- | --- | -| 1. 加载插件 | 在 Chrome / Edge 开发者模式加载 `browser-extension/` 或解压后的插件包 | 插件是否能连接本地工作台,弹窗和同步侧栏是否可用 | -| 2. 真实页面使用 | 在普通网页、ChatGPT、Claude、Gemini 或 Perplexity 里保存候选内容 | 是否能保存到 Viewer 待审阅队列,是否能看到“记忆建议” | -| 3. 逐站验收 | 在 ChatGPT、Claude、Gemini、Perplexity 检查输入框、记忆建议、插入、复制问题信息 | 一份脱敏诊断 JSON,或 `docs/validation/browser-extension-ai-sites/` 里的证据文件 | -| 4. 提交反馈 | 使用 `docs/external-feedback-template-cn.md` 或 GitHub Issue 模板 | 问题路径、影响程度、实际现象、诊断 JSON、截图或录屏路径 | - -## 最少要回收的信息 - -- 试用路径:本地 demo / ChatGPT / Claude / Gemini / Perplexity / Viewer / 插件加载。 -- 是否看到记忆建议。 -- 是否能插入或复制记忆。 -- 是否能把网页加入待审阅。 -- Viewer 记忆库里是否出现待审阅卡片。 -- 如果是真实 AI 页面问题,必须附同步侧栏“复制问题信息”的 JSON。 -- 如果有截图或录屏,先确认不含私人聊天、Cookie、访问令牌、API Key 或申请材料。 - -## 反馈后怎么处理 - -| 反馈类型 | 看哪里 | 下一步 | -| --- | --- | --- | -| 插件未连接本地工作台 | `npm run check:workbench` | 修启动说明、默认地址或连接状态提示 | -| 真实页面没有记忆建议 | 先看同步侧栏诊断,再用 demo 自检 | 判断是站点适配问题还是插件注入整体异常 | -| AI 页面识别错 | `ai.provider`、`page.host` | 更新站点配置和 fixture | -| 输入框或入口位置没找到 | `matchedSelectors.editor`、`matchedSelectors.anchor` | 补 selector 或调整入口位置 | -| 插入后原站输入异常 | `manualValidation.siteInputStillWorks` | 优先修输入事件,必要时回滚该站点注入策略 | -| Viewer 待审阅没有内容 | `/agentmemory/review`、来源筛选 | 修审阅队列或保存草稿结构 | -| 测试者看不懂下一步 | `README.md`、`docs/external-tester-guide-cn.md`、`browser-extension/LOAD-THIS-FIRST.md` | 改外部说明,而不是只改内部脚本 | - -## 交付判断 - -- 本地可演示:demo 页、插件预览、待审阅队列、Skill 管理台可跑通。 -- 外部可试用:zip 包、加载说明、反馈模板、分诊指南和交付摘要齐全。 -- 公开可发布:ChatGPT、Claude、Gemini、Perplexity 都有真实页面通过证据,并且隐私政策、商店截图和发布文案准备好。 - -当前公开发布不能只靠本地 fixture 通过。真实 AI 页面证据必须来自测试者实际页面,并能通过 `npm run check:ai-validation-evidence` 汇总。每份通过证据还必须包含 `turnCount > 0` 和会话区域 selector,证明插件抓到的是具体对话;页面标题、链接、导航文案或输入框草稿不能算作记忆来源。 diff --git a/docs/external-tester-guide-cn.md b/docs/external-tester-guide-cn.md deleted file mode 100644 index f20fa567..00000000 --- a/docs/external-tester-guide-cn.md +++ /dev/null @@ -1,201 +0,0 @@ -# Agent Memory Lab 外部试用指南 - -这份指南给第一次试用 Agent Memory Lab 的朋友使用。目标不是让对方理解所有技术细节,而是能在 10 分钟内看到产品核心:本地记忆工作台、浏览器插件、AI 输入框旁的记忆建议,以及保存前审阅。 - -如果这次试用需要沉淀为可复现反馈,请同时按 [外部测试闭环](external-test-loop-cn.md) 收集结果:先加载插件,在真实网页里使用,再提交诊断和问题描述。demo 只作为排错自检。 - -## 试用前需要知道 - -- 这是本地优先产品,记忆默认留在本机。 -- 浏览器插件不会直接写入长期记忆。弹窗和同步侧栏会先给出可编辑草稿,保存后再进入 Viewer 的待审阅队列。 -- 本地 demo 可以帮助排查插件注入是否正常,但不能替代真实 ChatGPT / Claude / Gemini / Perplexity 的逐站验收。 -- 不要用包含 API Key、密码、身份证、银行卡、私人聊天的页面做截图。 - -## 试用包包含什么 - -| 内容 | 位置 | 用途 | -| --- | --- | --- | -| 中文 README | `README.md` | 快速理解产品定位和启动路径 | -| 插件目录 | `browser-extension/` | Chrome / Edge 开发者模式加载 | -| 插件自检页 | `启动输出里的 Viewer 地址 + /demo/browser-extension.html` | 排查输入框旁记忆建议是否能注入 | -| 插件压缩包 | `artifacts/agent-memory-lab-extension.zip` | 给别人本地加载或归档 | -| 交付摘要 | `artifacts/delivery-summary.md` | 查看当前版本、产物、发布门槛和检查命令 | -| 交付清单 | `artifacts/delivery-manifest.json` | 机器可读的版本、提交、zip 大小、sha256 和真实 AI 站点验收计数 | -| 演示检查清单 | `docs/demo-checklist-cn.md` | 演示前自查 | -| AI 站点验收记录 | `docs/browser-extension-ai-validation-cn.md` | 记录真实 AI 网页适配结果 | -| AI 站点测试卡 | `docs/browser-extension-ai-site-test-cards-cn.md` | 按 ChatGPT、Claude、Gemini、Perplexity 拆分真实页面验收任务 | -| AI 验收一页纸 | `artifacts/ai-validation-run/quickstart-cn.md` | 给测试者先看的短清单,说明 4 个必测站点、6 步操作、通过标准和隐私边界 | -| AI 站点外测包 | `artifacts/ai-validation-run/tester-pack-cn.md` | 完整真实站点验收包,包含每个站点的 prompt、重点观察和证据命令 | -| 隐私说明 | `docs/browser-extension-privacy-cn.md` | 解释插件权限和数据边界 | - -启动 Viewer 后,首页的“浏览器记忆入口”也会直接提供这些常用入口:下载插件包、外测手册、验收一页纸、AI 验收包、反馈模板和分诊指南。外部试用者优先从首页拿材料;`artifacts/` 路径主要用于维护者归档和校验。 - -本地交付检查还包含一个免登录交互烟测:它会模拟插件内容脚本在预览页创建“记忆建议”、渲染演示记忆,并确认插入按钮能把记忆写进输入框。这个检查不能替代真实 AI 网页验收,但能证明演示页的核心交互没有退化。 - -## 最短试用路线 - -1. 打开项目仓库,或打开 Viewer 首页,在“浏览器记忆入口”点击“下载插件包”。 -2. 进入项目目录并启动完整工作台: - -```bash -cd agentmemory-lab -npm run build && npm run start:local-memory -``` - -3. 打开 Chrome / Edge 的扩展管理页。 -4. 开启开发者模式。 -5. 选择“加载已解压的扩展程序”。 -6. 选择插件文件夹: - -| 来源 | 应该选择哪个文件夹 | -| --- | --- | -| 从仓库试用 | 选择仓库里的 `browser-extension/` | -| 从 zip 试用 | 先解压 `artifacts/agent-memory-lab-extension.zip`,阅读 `browser-extension/LOAD-THIS-FIRST.md`,再选择解压出来的 `browser-extension/` | - -7. 打开 ChatGPT、Claude、Gemini、Perplexity 或普通网页。 -8. 点击浏览器工具栏里的 Agent Memory Lab 图标,打开同步侧栏。 -9. 保存当前网页或选中文本,回到 Viewer 记忆库确认待审阅内容出现。 -10. 如果输入框旁没有“记忆建议”或侧栏识别异常,再打开自检页: - -```text -启动输出里的 Viewer 地址 + /demo/browser-extension.html -``` - -## 完整工作台试用路线 - -如果还没有启动待审阅队列和记忆库,需要启动完整工作台: - -```bash -cd agentmemory-lab -npm run build && npm run start:local-memory -``` - -默认地址: - -```text -Viewer: 启动输出里的 Viewer 地址,例如 http://localhost:3114/#dashboard -API: http://localhost:3111 -``` - -然后可以试: - -- 插件保存当前网页。 -- 右键保存选中文本或链接。 -- 在弹窗或同步侧栏里先改标题或正文,再加入待审阅。 -- 回到 Viewer 的记忆库,查看待审阅队列,并确认保存范围、分类备注、来源和经验候选状态没有丢失。 -- 编辑标题、内容、标签和项目后再保存。 - -如果不确定完整工作台是否已经正常运行,可以另开终端检查: - -```bash -cd agentmemory-lab -npm run check:workbench -``` - -它会检查 API、Viewer 和插件 demo 页,并提示端口可能被占用还是需要重新启动。 - -也可以检查当前发布门槛: - -```bash -cd agentmemory-lab -npm run status:delivery -``` - -这个命令会先告诉你当前插件包、核心体验、真实 AI 站点证据和下一步目标是什么。 - -也可以继续看发布门槛: - -```bash -cd agentmemory-lab -npm run check:release-gates -``` - -如果准备公开发布,再运行: - -```bash -cd agentmemory-lab -npm run check:release-public -``` - -在真实 AI 站点验收完成前,公开发布检查失败是预期结果。 - -## 真实 AI 页面验收 - -对外发布前,需要在真实网页里验收这些产品: - -- ChatGPT: `chatgpt.com` -- Claude: `claude.ai` -- Gemini: `gemini.google.com` -- Perplexity: `www.perplexity.ai` -- Grok: `grok.com` -- DeepSeek: `chat.deepseek.com` - -逐站操作请按 `docs/browser-extension-ai-site-test-cards-cn.md` 执行。它把 ChatGPT、Claude、Gemini、Perplexity 四个公开发布必测站点拆成测试卡,并说明每个站点要观察输入框、发送按钮、模型选择和附件入口是否被插件影响。 - -给测试者发包前,可以先生成一页式外测包: - -```bash -cd agentmemory-lab -npm run prepare:ai-validation -npm run make:ai-validation-tester-pack -``` - -优先把 `artifacts/ai-validation-run/quickstart-cn.md` 发给测试者,它是一页短清单。需要更完整的站点细节时,再看 `artifacts/ai-validation-run/tester-pack-cn.md`;它会按当前提交列出 ChatGPT、Claude、Gemini、Perplexity 的建议 prompt、证据文件名、保存命令和隐私边界,适合贴到飞书或发给测试者。 - -每个站点至少确认: - -- 页面被识别为对应 AI 产品。 -- 输入框状态是“已找到”。 -- 输入框附近出现“记忆建议”。 -- 记忆可以插入或复制。 -- 同步侧栏可以复制问题信息 JSON,也可以复制保存证据的终端命令。 -- 原站点输入、发送、滚动没有异常。 -- 页面里至少有一轮真实对话,诊断 JSON 的 `turnCount > 0`。 -- 待审阅候选来自具体对话或用户选中的文字;页面介绍、URL、导航文案或输入框草稿不能算通过。 - -验收结果写入: - -```text -docs/browser-extension-ai-validation-cn.md -``` - -诊断 JSON 可以保存到: - -```text -docs/validation/browser-extension-ai-sites/ -``` - -保存后运行: - -```bash -cd agentmemory-lab -npm run wizard:ai-validation-evidence -- --file diagnostics.json -npm run check:ai-validation-evidence -npm run sync:ai-validation-table -``` - -优先使用 `npm run wizard:ai-validation-evidence`。它会读取剪贴板里的诊断 JSON,并询问插入/复制本地记忆、诊断复制、原站输入发送、浏览器版本和无隐私备注。后续命令会生成 `artifacts/ai-validation-evidence-summary.json` 并同步 `docs/browser-extension-ai-validation-cn.md` 的真实站点验收表,用于汇总 ChatGPT、Claude、Gemini、Perplexity 是否已经具备公开发布所需的真实页面证据。 - -## 反馈时请提供 - -可以直接复制 [外部试用反馈模板](external-feedback-template-cn.md) 填写。 -如果要在 GitHub 上提交,使用仓库里的结构化 Issue 模板:`.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml`。 -维护者收到反馈后,按 [外部反馈分诊指南](external-feedback-triage-cn.md) 判断问题属于本地连接、站点适配、输入框插入、审阅队列、隐私信任还是交付文档。 - -- 试用日期。 -- 浏览器名称和版本。 -- 操作系统。 -- 目标站点。 -- 截图或录屏。 -- 同步侧栏复制出来的诊断 JSON,以及侧栏“复制检查步骤”生成的保存命令执行结果。 -- 如果可以,直接提供 `docs/validation/browser-extension-ai-sites/` 下的证据 JSON 文件。 -- 复制问题信息里的 `manualValidation` 字段需要按真实结果改成 `true` / `false`,不要默认当作通过。 -- 诊断 JSON 里 `editorFound`、`anchorFound`、`placement`、`memoryWidgetVisible`、`turnCount` 和 `matchedSelectors.turn` 的状态。 -- 哪一步不符合预期。 - -## 当前不能承诺 - -- 还没有 Chrome Web Store 正式发布。 -- 还没有完成所有真实 AI 网页的逐站验收。 -- 插件不会绕过登录或读取付费产品内部不可见内容。 -- 插件不应该保存敏感隐私页面作为演示素材。 diff --git a/docs/features/codex-source-scanner.md b/docs/features/codex-source-scanner.md deleted file mode 100644 index d54bf964..00000000 --- a/docs/features/codex-source-scanner.md +++ /dev/null @@ -1,84 +0,0 @@ -# Feature: Codex source scanner - -> Reviewed feature doc (PLAN-002 STEP-01). Incorporates the implementation-plan -> review: incremental watermark instead of a hash, success-only checkpointing, -> per-file path+mtime/size state, and per-source locking. - -## One-line definition - -Automatically and incrementally turn local Codex sessions into todos, reusing the existing ingest pipeline and never re-reading already-processed, unchanged history. - -## Status / Priority - -- Status: In Progress -- Priority: P0 - -## Problem - -Today local Codex sessions become todos only through a manual `import-jsonl` command. Users want the todo list to stay current on its own — without re-importing, and without re-reading or duplicating history on every run. - -## Users - -- **AI-heavy builder** and **Local-first power user**: they accumulate Codex sessions and want unfinished work surfaced automatically, on their own machine. - -## Goals - -- Scan a configured Codex source and ingest new sessions through the existing parse → dedup → persist pipeline. -- Incremental: already-processed, unchanged files are not re-read on later runs. -- A failed file is recorded and retried next run; it never aborts the batch. -- Manual `import-jsonl` keeps working, unchanged. - -## Non-goals - -- No LLM classifier (rule-based extraction only; deferred to v1.1). -- No other sources here (Claude Code / browser handled separately). -- No REST/UI trigger in this feature — an internal trigger first (REST is folded into the later first-run/backfill step). -- No two-dimensional workbench (v1.1). - -## User flow - -1. The daemon is configured with a Codex source (default Codex session roots). -2. On scan it discovers session files under the source root(s). -3. Files new-or-grown since the last scan are normalized and extracted into todos with evidence; unchanged files are skipped without being read. -4. Re-running the scan re-reads no unchanged history and produces no duplicate todos. - -## Requirements - -| ID | Requirement | Priority | -|---|---|---| -| R1 | Ingest new Codex sessions via the existing pipeline (no second extraction path) | P0 | -| R2 | Incremental: skip already-processed, unchanged files **without reading them** | P0 | -| R3 | A corrupt/failed file is recorded (`lastError`) and retried next run; the batch continues | P0 | -| R4 | Manual `import-jsonl` remains available and behaviourally unchanged | P0 | -| R5 | Default source covers both Codex roots: `~/.codex/sessions` and `~/.codex/archived_sessions` | P1 | -| R6 | Two concurrent scans (or a scan racing a manual import) never double-advance the checkpoint or duplicate a session | P1 | - -## Acceptance criteria - -- Re-running a scan over the same directory imports 0 new todos **and reads 0 unchanged files** (assert by spying `readFile` — observation counts alone do not prove re-read avoidance). Duplicate rate ≤5%; 0 history re-reads after first launch. -- A corrupt session file is recorded in `lastError` and retried on the next run; valid files still ingest and the batch is not aborted. -- Existing `import-jsonl` / replay tests stay green (the shared-ingest extraction is behaviour-preserving). -- Concurrent scans of one source produce a single checkpoint advance and no duplicate sessions. - -## Localization & rules impact - -- No new user-facing UI strings (backend feature). -- **No change to stored todo status enums** (see [RULES.md](../../RULES.md) — stored enums are stable). -- Adds two KV scopes (`sources`, `scanCheckpoints`) → update the AGENTS.md "new KV scope" files (`src/state/schema.ts` + `src/types.ts`). No MCP-tool / REST-endpoint / version count change in this step. - -## Technical notes - -See [ARCHITECTURE.md](../../ARCHITECTURE.md) for the pipeline. - -- **Reuse, don't duplicate ingest.** Extract the per-file ingest from `import-jsonl` (`src/functions/replay.ts`) into a module-level `ingestJsonlFile(kv, file) → { sessionId, newObservations } | null`. The aggregate `safeAudit` and the `sessionIds`/`observationCount` accumulators stay in each **caller** (the scanner writes its own audit entry). `deriveCrystalAndLessons` is already per-file, so it moves cleanly. (This step ships first as a behaviour-preserving refactor with a regression test.) -- **Correctness vs. efficiency are separate.** No-duplicate-todos is already guaranteed by the existing dedup layer (parser-derived `sessionId` + `stabilizeObservationIds` + per-observation skip-by-id). The checkpoint exists **only** to avoid re-reading unchanged history. -- **Cursor = recoverable per-file state, not a hash.** `ScanCheckpoint.cursor` stores a JSON map `path → { mtimeMs, size }`. A file is skipped (not read) when its `mtimeMs` and `size` are unchanged; new or grown files are read. A file is recorded in the map **only on successful ingest**, so failures stay absent and are retried next run. There is no end-of-run "last file" watermark (a single scalar can't express per-file success and assumes a file ordering Codex's timestamp/uuid names don't guarantee). -- **Stable fallback session id.** For a Codex file lacking a `session_meta.id`, derive the fallback session id from the **file path** (rollout filename/uuid), not a hash of the full text — otherwise a growing transcript mints a new session every scan. -- **Concurrency.** Wrap each scan in `withKeyedLock(\`source-scan:${sourceId}\`, …)` (`src/state/keyed-mutex.ts`) so concurrent scans / a racing manual import can't clobber the checkpoint. -- **Path safety.** Reuse `isSensitive` / `isSymlink` / `findJsonlFiles` from `replay.ts` (export or move into a shared module); `findJsonlFiles` already clamps to `MAX_FILES` and reports truncation — surface that to `lastError`. - -## Rollout - -- Ships as two slices: (1) the behaviour-preserving `ingestJsonlFile` extraction + regression test; (2) the scanner function + `sources`/`scanCheckpoints` scopes + checkpoint + tests. -- Internal trigger first; a REST/UI trigger follows in the first-run/backfill step. -- Rollback = revert the PR(s); the new scopes carry no historical-data dependency and `import-jsonl` is untouched. diff --git a/docs/features/example-feature.md b/docs/features/example-feature.md deleted file mode 100644 index a582cbcd..00000000 --- a/docs/features/example-feature.md +++ /dev/null @@ -1,68 +0,0 @@ -# Feature: Codex source scanner - -> Worked example of a feature doc. Status reflects the planned v1 scope. - -## One-line definition - -Automatically and incrementally turn local Codex sessions into todos, without re-reading already-processed history. - -## Status / Priority - -- Status: Planned -- Priority: P0 - -## Problem - -Today, local Codex sessions become todos only through a manual import command. Users want the todo list to stay current on its own — without re-importing, and without producing duplicates on every run. - -## Users - -- **AI-heavy builder** and **Local-first power user**: they accumulate Codex sessions and want unfinished work surfaced automatically, on their own machine. - -## Goals - -- Scan a configured Codex source and ingest new sessions through the existing todo pipeline. -- Incremental: already-processed, unchanged history is not re-read on later runs. -- Reuse the existing parse + ingest pipeline — no second extraction path. - -## Non-goals - -- No LLM classifier (rule-based extraction only; deferred to v1.1). -- No other sources here (Claude Code / browser are handled separately). -- No REST/UI trigger in this feature (an internal trigger comes first). - -## User flow - -1. The daemon is configured with a Codex source (default Codex session directories). -2. On scan, it discovers new or changed session files. -3. New sessions are normalized and extracted into todos with evidence. -4. Re-running the scan does not re-read unchanged history and produces no duplicate todos. - -## Requirements - -| ID | Requirement | Priority | -|---|---|---| -| R1 | Ingest new Codex sessions via the existing pipeline | P0 | -| R2 | Incremental: skip already-processed, unchanged history | P0 | -| R3 | A failed file is recorded and retried next run; it does not abort the batch | P0 | -| R4 | Manual import remains available and unchanged | P1 | - -## Acceptance criteria - -- Re-running a scan over the same directory imports 0 new todos and re-reads no unchanged files (duplicate rate ≤5%; 0 history re-reads after first launch). -- A corrupt session file is recorded as an error and retried next run; valid files still ingest. -- Existing manual-import tests stay green. - -## Localization & rules impact - -- No new user-facing UI strings (backend feature). -- No change to stored todo status enums (see [RULES.md](../../RULES.md) — stored enums are stable). -- Adds storage scopes (sources, scan checkpoints) — list the [AGENTS.md](../../AGENTS.md) "new KV scope" files when implementing. - -## Technical notes - -- Reuse the normalization + ingest pipeline (see [ARCHITECTURE.md](../../ARCHITECTURE.md)). **Correctness** (no duplicate todos) is owned by the existing dedup layer; the scan checkpoint exists only to avoid **re-reading** unchanged history — so it must record recoverable per-file state, not an opaque watermark. - -## Rollout - -- Internal trigger first; a REST/UI trigger can follow. Rollback = revert the PR (new scopes carry no historical-data dependency). diff --git a/docs/features/index.md b/docs/features/index.md deleted file mode 100644 index 001bc11e..00000000 --- a/docs/features/index.md +++ /dev/null @@ -1,37 +0,0 @@ -# Features - -Registry of approved features and the process for adding new ones. - -## Process - -Substantial new features follow **doc → review → implement**: - -1. Copy [template.md](template.md) to `docs/features/.md` and fill it in. -2. Open it for review — a maintainer signs off on scope, non-goals, and acceptance criteria. -3. Only after the feature doc is approved does implementation start; the implementation PR links the feature doc. - -Bug fixes and small, obvious changes do not need a feature doc. See -[example-feature.md](example-feature.md) for a worked example, and the release -checklist in [RULES.md](../../RULES.md). - -## Registry - -| Feature | Status | Priority | Doc | Notes | -|---|---|---|---|---| -| Viewer i18n base | Done | P0 | shipped | Inline `{en, zh}` catalog + `t()` | -| Codex source scanner | In Progress | P0 | [codex-source-scanner.md](codex-source-scanner.md) | First local source; incremental, no re-reads | -| LLM todo extraction loop | Draft | P0 | [llm-todo-extraction-loop.md](llm-todo-extraction-loop.md) | Structured extraction, card update, and evidence navigation contract | - -### Status - -- **Draft** — under discussion -- **Planned** — confirmed, not started -- **In Progress** — being built -- **Done** — implemented and verified -- **Paused** — intentionally stopped - -### Priority - -- **P0** — must have -- **P1** — important -- **P2** — useful but not urgent diff --git a/docs/features/llm-todo-extraction-loop.md b/docs/features/llm-todo-extraction-loop.md deleted file mode 100644 index 98ba4ab3..00000000 --- a/docs/features/llm-todo-extraction-loop.md +++ /dev/null @@ -1,161 +0,0 @@ -# Feature: LLM todo extraction and update loop - -> Current contract for the LLM-backed todo loop. This document records what is -> implemented today and the small follow-up gaps that should be fixed next. - -## One-line definition - -Use recent local sessions as evidence, ask an LLM for structured todo output, -and keep generated cards reviewable, updateable, and traceable back to their -source session. - -## Status / Priority - -- Status: Implemented, with follow-up documentation/UI gaps -- Priority: P0 - -## Problem - -The To-Do tab must not look like a generic memory summary list. Users need -cards that are visibly actionable, grounded in session evidence, and updated -when the source session changes. The product contract needs to stay explicit: -when sessions are read, when the LLM runs, how a card is classified as -todo/done/in-progress, and how evidence helps users return to the original work -context. - -## Users - -- **AI-heavy builder:** works across Codex and browser AI sessions and needs - unfinished work surfaced without re-reading or manually scanning history. -- **Local-first power user:** wants evidence-backed local cards and control over - model configuration, cost, and cleanup. - -## Goals - -- Make the startup contract explicit: first launch reads historical Codex - session files into the local database; later launches and daemon scans are - incremental and skip unchanged history. -- Make LLM extraction explicit and observable: the To-Do UI triggers the - extraction loop via **Organize with LLM** and shows progress plus whether it - used LLM or rules fallback. -- Require structured LLM output with title, description, confidence, time - bucket, type bucket, dedupe key, and evidence quote. -- Classify generated cards as pending, active, or done based on extracted - `typeBucket`, not merely because a sentence appears in a session. -- Detect source-session changes after a card is created and update cards through - an LLM dry-run/apply loop instead of silently changing card text. -- Improve evidence navigation so a card can take the user to both the evidence - panel and the local Codex session/work directory when available. - -## Non-goals - -- No automatic background LLM calls on daemon startup or To-Do tab refresh. -- No cloud sync. -- No writes back into Codex, browser AI tools, GitHub Issues, or external todo - apps. -- No new stored todo status enum in this feature. Visual recheck state should - live in metadata/tags until a migration is approved; archive/delete remain UI - operations over existing Action records, not new status literals. -- No full connector framework for Claude, OpenClaw, Hermes, or other agents in - this feature. - -## User flow - -1. On first daemon startup, Codex session files under the configured roots are - imported into local `sessions` and `observations`. -2. Later scans read only new or changed files. Unchanged history is skipped by - source checkpoints. -3. The user opens To-Do; the tab reads stored cards and does not run the LLM by - itself. -4. The user clicks **Organize with LLM**. The extractor reads recent sessions, - sends observation blocks to the - LangExtract sidecar when configured, and falls back to rules if the sidecar - fails. -5. High-confidence, evidence-valid todos become Actions. Medium-confidence - todos enter the review queue. Low-confidence or evidence-less candidates are - discarded. -6. If a source session changes after a generated card was created, the user can - click **Update**. The UI first calls `/agentmemory/todo/update` in `dry-run` - mode for changed cards, then confirms before applying DROP, DONE, REWRITE, or - MERGE decisions. If every decision is KEEP, the UI applies silently so - checkpoints advance. -7. The user clicks evidence. The UI jumps to the evidence record and, when - available, exposes the local work directory/session path as the next action. - -## Requirements - -| ID | Requirement | Priority | -|---|---|---| -| R1 | First startup imports historical Codex sessions into local storage; follow-up scans are incremental and skip unchanged history | P0 | -| R2 | LLM extraction uses the configured model; default `LANGEXTRACT_MODEL` is `deepseek/deepseek-v4-flash` | P0 | -| R3 | The extraction prompt must require actionable output, source quote, `timeBucket`, `typeBucket`, confidence, and dedupe key | P0 | -| R4 | A todo is stored only when its evidence quote matches the source observation | P0 | -| R5 | `typeBucket=done` maps to `Action.status=done`; `in_progress` and `processing` map to `active`; all other buckets map to `pending` | P0 | -| R6 | Cards created from sessions store enough metadata to compare their source checkpoint with the latest session checkpoint | P1 | -| R7 | If the source checkpoint changes, `/agentmemory/todo/update` can dry-run and apply KEEP, DROP, DONE, REWRITE, and MERGE decisions | P1 | -| R8 | Evidence jump lands on the evidence item and makes the local Codex session/work directory discoverable when present | P1 | -| R9 | `/agentmemory/todo/update` supports `scope=changed` by default and `scope=all` for explicit maintenance runs | P1 | - -## Acceptance criteria - -- A first run with existing Codex history imports sessions once; a second run - imports no unchanged files. -- With LangExtract configured, pressing the LLM organize button returns - `engine=langextract` or `engine=mixed`, and at least one generated card has - `todo-extracted`, `time:*`, and `type:*` tags. -- With LangExtract unavailable, the same UI action reports rules fallback and - does not block the To-Do page. -- Every generated card has a short readable title and evidence quote; command - JSON, file paths, tool logs, and screenshots are rejected as card titles. -- A completed-work sentence creates either a `done` Action or a discarded - historical candidate; it must not appear as a pending todo by default. -- When a source session changes after card creation, **Update** previews LLM - maintenance before any destructive or rewriting change is applied. -- `POST /agentmemory/todo/update` with `{ "mode": "dry-run", "scope": "all" }` - can evaluate existing generated cards for explicit maintenance runs; this is - an API path today, not a separate viewer control. -- Evidence navigation can show the observation, source session id, local session - file or work directory when available. - -## Localization & rules impact - -- New user-facing strings must be English-first and added to the viewer i18n - catalog with Chinese translations. -- Stored enum values stay unchanged: `Action.status` remains the current - persisted set (`pending`, `active`, `done`, `blocked`, `cancelled`). -- No REST endpoint count change is required unless the evidence deep-link or - recheck operation needs a new API. If a REST endpoint is added, update - README, AGENTS.md, `src/index.ts`, and the consistency tests in the same PR. - -## Technical notes - -- Current startup ingestion is in `src/index.ts` via - `mem::source-scan::codex`; checkpointed file scanning lives in - `src/functions/source-scan-codex.ts`. -- Current LLM/rules extraction is `mem::todo-extract-generate` in - `src/functions/todo-extract.ts`, with the Python LangExtract sidecar in - `src/functions/todo-extract-langextract.py`. -- Current LLM update is `mem::todo-update` / `POST /agentmemory/todo/update` in - `src/functions/todo-extract.ts`, `src/triggers/api.ts`, and - `src/viewer/server.ts`. -- Current structured prompt requires action-only extraction, source quote, - readable title, confidence, time bucket, type bucket, and dedupe key. -- Existing `metadata.todoExtraction.sourceCheckpoint` is used to decide whether - generated cards need changed-session maintenance. -- `scope=all` is supported by the API/function for stock cleanup and title - quality passes, but the viewer's **Update** button currently uses the default - changed-card scope. -- Evidence navigation should reuse `sourceObservationIds`, - `metadata.todoExtraction.evidence`, session `cwd`, and scanner source - metadata before adding new storage. - -## Follow-up plan - -1. Keep docs aligned with the current default model (`deepseek/deepseek-v4-flash`) - until the code default is changed and tested. -2. Decide whether the viewer needs an explicit **Update all generated cards** - control; until then, document `scope=all` as API-only. -3. Replace old CLI wording in code help/onboarding strings in a separate PR if - the installed binary name stays `agentmemory-lab`. -4. Verify future changes with `npm run check:consistency-local`, `npm run build`, - and the focused To-Do tests before opening a PR. diff --git a/docs/features/template.md b/docs/features/template.md deleted file mode 100644 index 5b9e2d02..00000000 --- a/docs/features/template.md +++ /dev/null @@ -1,62 +0,0 @@ -# Feature: <Name> - -> Copy this file to `docs/features/.md`, fill it in, get it reviewed, -> and register it in [index.md](index.md) **before** implementation starts. - -## One-line definition - -What this feature does, in one sentence. - -## Status / Priority - -- Status: Draft | Planned | In Progress | Done | Paused -- Priority: P0 | P1 | P2 - -## Problem - -The user problem, stated before any solution. Why is this needed? - -## Users - -Which personas this serves. - -## Goals - -- ... - -## Non-goals - -- What this explicitly does **not** do (prevents scope creep). - -## User flow - -1. The user does X. -2. The system responds with Y. -3. The user gets Z. - -## Requirements - -| ID | Requirement | Priority | -|---|---|---| -| R1 | ... | P0 | -| R2 | ... | P1 | - -## Acceptance criteria - -- What counts as done. -- Which edge cases must be covered. -- Which tests are required. - -## Localization & rules impact - -- New user-facing strings? They must live in i18n resources, keyed by stored value (see [RULES.md](../../RULES.md)). -- Any change to stored enums? Stored enum values are stable — call it out explicitly (see RULES.md). -- Touches MCP tools / REST endpoints / version? List the matching [AGENTS.md](../../AGENTS.md) consistency files. - -## Technical notes - -- Relevant modules (see [ARCHITECTURE.md](../../ARCHITECTURE.md)), APIs, data-structure impact, compatibility notes. - -## Rollout - -- How it ships, any gating, and how to roll back. diff --git a/docs/product-delivery-plan-cn.md b/docs/product-delivery-plan-cn.md deleted file mode 100644 index 9a2a9ad6..00000000 --- a/docs/product-delivery-plan-cn.md +++ /dev/null @@ -1,133 +0,0 @@ -# Agent Memory Lab 交付计划 - -这份文档用来判断 Agent Memory Lab 什么时候算“能交差”。它不是技术待办,而是产品交付口径:别人打开仓库、看 README、装插件、跑本地 Viewer 时,能不能理解、预览并相信这个产品方向。 - -## 交付判断 - -### 今天可展示 - -目标:可以给熟人、团队成员或评审看一个完整方向。 - -- GitHub README 能在 3 分钟内讲清产品定位 -- README 只放真实产品截图:首页和 Skill 管理台 -- 本地 Viewer 能打开首页、会话、记忆、Skill、待办 -- 浏览器插件可以加载为开发者扩展 -- 插件弹窗可以打开同步侧栏 -- 同步侧栏有本地连接状态,能说明审阅队列是否可用 -- 同步侧栏能展示当前页面类型、候选记忆、候选经验、隐私提示 -- 本地免登录插件预览页可打开:`启动输出里的 Viewer 地址 + /demo/browser-extension.html` -- 插件保存内容先进入 Viewer 的待审阅队列 -- 插件结构按 OpenMemory / Mem0 的 supported-sites 思路拆出 AI 产品站点配置,并把记忆召回锚定在输入框附近 -- 站点配置必须包含输入框、锚点和位置策略,避免 AI 页面改版后只能靠猜 -- 插件 README 能说明如何预览 -- 有插件权限与隐私说明,能解释为什么需要当前权限 -- 飞书文档和 GitHub README 的产品叙事一致 - -### 明天可试用 - -目标:可以让一两个真实用户照着流程试。 - -- 保存网页后能在 Viewer 里稳定看到来源 -- 保存前可以在弹窗和同步侧栏编辑候选记忆标题和内容 -- AI 对话页抽取更精确,减少误抓导航和无关按钮 -- 在 ChatGPT / Claude / Gemini / Perplexity 的 AI 输入框附近提示可用记忆,并支持插入/复制 -- 同步侧栏能显示 AI 页面诊断,帮助确认输入框和最近对话是否被识别 -- AI 页面诊断支持一键复制,方便像 Mem0 / OpenMemory 那样逐站维护 supported-sites 规则 -- 选中文本和右键菜单进入同一套待审阅队列,不另开一套数据口径 -- 活动页、会话页加载失败时有明确兜底状态:先展示可用数据,再提示失败来源和重试入口 -- 插件有独立交付检查,避免 supported-sites 配置和运行脚本分叉 -- 记忆库有“待审阅”入口,避免自动内容直接混入长期记忆 -- README 有一段非常短的“试用路线”:启动 Viewer、加载插件、使用 AI 页面记忆提示、保存网页、回到工作台查看 - -### 一周内可交付 - -目标:可以作为一个小型产品原型对外介绍。 - -- 插件支持 ChatGPT、Claude、Gemini、Perplexity 的独立抽取规则 -- Viewer 支持按来源筛选浏览器保存内容 -- 经验可以生成 Skill 草稿,而不是只停留在经验列表;草稿需人工确认后再落到本地 Skill -- 飞书文档包含产品定位、截图、工作流、当前能力、路线图 -- GitHub 分支保持最新,重要更新都有 commit 和 push -- 有一个固定 demo checklist,避免现场演示时路径混乱 -- 能生成本地预览 zip,方便发给别人开发者模式加载 - -## 当前优先级 - -| 优先级 | 任务 | 交付价值 | -| --- | --- | --- | -| P0 | 保证 Viewer 关键页面能打开 | 产品可信度 | -| P0 | 插件侧栏可预览 | 体现浏览器入口是主方向 | -| P0 | README / 插件 README / 飞书同步 | 对外介绍不打架 | -| P0 | 待审阅记忆队列 | 解决自动保存的信任问题 | -| P1 | AI 对话专用抽取器 | 让跨 Agent 记忆方向成立 | -| P1 | 输入框附近的记忆提示 | 已有第一版本地搜索提示和插入能力,下一步要精调不同站点的位置与输入事件 | -| P1 | 保存前编辑候选内容 | 已能在插件弹窗和同步侧栏改标题、正文、保存范围、分类备注,并可标记为经验候选 | -| P1 | 记忆来源筛选 | 已有浏览器 / 会话 / 手动筛选,并支持 ChatGPT、Claude、Gemini、Perplexity 等 AI 来源细分 | -| P1 | 经验到 Skill 草稿 | 已能从经验分组生成可复制的 SKILL.md 草稿,暂不自动写入本地目录 | -| P2 | Chrome Web Store 打包 | 更正式的分发 | - -## 插件发布物料 - -- 权限与隐私说明:`docs/browser-extension-privacy-cn.md` -- 插件对标说明:`docs/browser-extension-mem0-reference-cn.md` -- 英文隐私政策草稿:`docs/browser-extension-privacy-en.md` -- 英文商店发布文案草稿:`docs/browser-extension-store-listing-en.md` -- 外部试用指南:`docs/external-tester-guide-cn.md` -- 外部测试闭环:`docs/external-test-loop-cn.md`,把本地 demo、插件加载、真实 AI 页面和反馈回收串成同一条流程 -- 外部试用反馈模板:`docs/external-feedback-template-cn.md` -- GitHub 外部试用 Issue 模板:`.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml` -- 外部反馈分诊指南:`docs/external-feedback-triage-cn.md` -- 发布门槛:`docs/release-gates-cn.md` -- 真实站点验收记录:`docs/browser-extension-ai-validation-cn.md` -- 真实站点测试卡:`docs/browser-extension-ai-site-test-cards-cn.md`,把 ChatGPT、Claude、Gemini、Perplexity 拆成外部试用任务 -- 真实站点证据目录:`docs/validation/browser-extension-ai-sites/` -- 真实站点证据记录命令:`npm run wizard:ai-validation-evidence` -- 插件 PNG 图标资产:`browser-extension/icons/icon16.png`、`icon32.png`、`icon48.png`、`icon128.png` -- 本地检查:`npm run check:browser-extension` -- 试用入口检查:`check:browser-extension` 会确认弹窗显示扩展版本、本地工作台状态和使用指南入口 -- 保存前审阅草稿检查:`check:browser-extension` 会确认弹窗和同步侧栏都能编辑标题、正文、保存范围、分类备注和经验候选状态,并通过 `SAVE_CANDIDATE` 进入 Viewer 待审阅队列 -- Viewer 待审阅检查:`check:delivery` 会确认插件送来的保存范围、分类备注、来源和经验候选状态在待审阅卡片与审阅弹窗里可见 -- 一键交付检查:`npm run check:delivery` -- 交付状态总览:`npm run status:delivery` -- 发布门槛检查:`npm run check:release-gates`;公开发布前必须通过 `npm run check:release-public` -- 真实站点证据汇总:`npm run check:ai-validation-evidence`,输出 `artifacts/ai-validation-evidence-summary.json` -- 真实站点表格同步:`npm run sync:ai-validation-table`,把证据 JSON 同步到 `docs/browser-extension-ai-validation-cn.md` -- 免登录插件预览:`npm run preview:browser-extension` -- 本地预览包:`npm run package:browser-extension`,输出 `artifacts/agent-memory-lab-extension.zip` -- 交付摘要:`artifacts/delivery-summary.md`,记录版本、提交、产物、发布门槛和检查命令 -- 交付清单:`artifacts/delivery-manifest.json`,记录版本、提交、zip 大小、sha256、发布状态和真实 AI 站点验收计数 -- 提交后刷新:每次完成代码提交后,都要重新运行 `npm run package:browser-extension`,让 `artifacts/delivery-manifest.json` 的 artifact commit 与当前 commit 对齐;`npm run status:delivery` 会同时显示 current commit 和 artifact commit -- 运行态复核:`npm run build` 或 `npm run package:browser-extension` 之后,如果本地工作台正在运行,需要重启 `npm run start:local-memory`,再用 `npm run check:workbench` 确认 API、Viewer 和插件 demo 页都可访问 -- 预览包内容检查:`check:browser-extension-package` 会确认 zip 内含 manifest、content script、侧栏、弹窗、设置页、shared 文件和 PNG 图标 -- AI fixture 验收:`check:browser-extension` 会用本地最小页面模型检查 ChatGPT、Claude、Gemini、Perplexity、Grok、DeepSeek 的输入框和对话 selector -- 本地免登录预览:`启动输出里的 Viewer 地址 + /demo/browser-extension.html`,用于快速展示输入框旁“记忆建议”、演示记忆、插入/复制 -- 试用者从仓库加载时选择仓库内 `browser-extension/`;从 zip 试用时先解压,再选择解压出来的 `browser-extension/` -- zip 内含 `browser-extension/LOAD-THIS-FIRST.md` 和 `browser-extension/AI-SITE-TEST-CARDS.md`,试用者解压后能直接看到加载步骤、当前边界和逐站验收任务 -- AI 站点适配材料:同步侧栏“复制问题信息”输出的 JSON,可用于补 selector 和真实网页验收 -- 未来商店发布仍需稳定公开隐私政策 URL、发布截图和逐站真实网页验收 - -## 默认更新工作流 - -每次产品更新都走这个闭环: - -1. 先判断定位有没有变化。 -2. 更新 Viewer、浏览器插件或数据结构。 -3. 同步 README、插件 README,必要时同步飞书源文档。 -4. 只使用真实产品截图,默认首页和 Skill 管理台。 -5. 运行轻量检查和构建。 -6. 能打开免登录插件预览页,完整工作台路径另行验证。 -7. 提交并推送到 GitHub。 - -## 插件对标原则 - -参考 Mem0 / OpenMemory 的不是视觉,而是工作流结构:跨 ChatGPT、Claude、Gemini、Perplexity 等网页维护 supported sites,把记忆能力放在用户正在输入的位置。Agent Memory Lab 保留这个入口位置,但把数据策略改成本地优先:插件只负责识别页面、召回相关记忆、生成候选;长期写入必须回到 Viewer 审阅后确认。 - -逐站迭代时,以“复制问题信息”为最小反馈单元:先确认 provider、输入框、草稿长度、最近对话数量,再决定是补 selector、调整插入逻辑,还是优化本地搜索召回。 - -外部反馈统一进入 `docs/external-feedback-triage-cn.md` 的分诊表:先归类本地连接、站点适配、输入事件、审阅队列、隐私信任或交付文档,再决定是否更新插件、selector、证据 JSON、README 或飞书源文档。 - -## 预计时间 - -- 今天:能给熟人或内部评审看,说明方向和当前完成度。 -- 明天:补更精确的 AI 对话抽取和真实站点验收后,可以让真实用户试一轮。 -- 一周内:如果继续稳定推进,可以整理成可对外介绍的小型产品原型。 diff --git a/docs/project-delivery-guide-cn.md b/docs/project-delivery-guide-cn.md deleted file mode 100644 index 4f5210ac..00000000 --- a/docs/project-delivery-guide-cn.md +++ /dev/null @@ -1,136 +0,0 @@ -# Agent Memory Lab 项目交付说明 - -这份说明给维护者和试用协作者使用。它不替代 README,而是把“当前能交付什么、怎么验证、还有什么不能承诺”放在一个入口里。 - -## 当前分支 - -```text -repo: https://github.com/MaimoryLab/agentmemory-lab -branch: szn-viewer-ui-iteration -current PR: https://github.com/MaimoryLab/agentmemory-lab/pull/3 -``` - -这个分支是当前工作入口。目标分支受保护,不能直接推送;交付改动先推到 PR 分支,再通过 PR 合并。浏览器插件、Viewer、中文 README、外部试用文档和交付检查都以这个分支为准。 - -## 当前可交付范围 - -- 本地 Viewer 可以作为记忆工作台预览:总览、记忆、会话、活动、Skill、待办等核心入口已经串起来。 -- 浏览器插件可以用开发者模式加载,作为网页和 AI 对话进入本地审阅队列的入口。 -- 插件支持保存前编辑标题、正文、保存范围、分类备注,并可标记为可沉淀经验。 -- 插件会先把内容送入 Viewer 待审阅队列,不直接写入长期记忆。 -- AI 对话页的候选记忆必须来自具体对话事实;如果只读到页面标题或链接,会被阻断,不再生成空泛记忆。 -- README、插件 README、试用指南、隐私说明、真实 AI 站点测试卡和飞书文档源文件已经放在仓库里。 -- 插件 zip 可以用 `npm run package:browser-extension` 生成,适合给内部或熟人外部测试者用开发者模式加载。 - -## 当前不能承诺 - -- 这还不是 Chrome Web Store 公开发布版。 -- ChatGPT、Claude、Gemini、Perplexity 四个真实 AI 站点还没有全部通过证据,目前公开发布会被检查脚本拦住。 -- 插件不会绕过登录,也不会读取页面不可见内容。 -- 真实站点 DOM 经常变化,输入框附近的“记忆建议”需要逐站验收和维护。 - -## 本地启动 - -```bash -cd agentmemory-lab -npm install -npm run build -npm run start -``` - -启动后看终端输出里的 Viewer 地址,例如: - -```text -http://localhost:3114/#dashboard -``` - -如果需要接入某台机器已有的本地记忆数据,可以参考 `iii-config.local-memory.yaml`,但不要把个人本机路径当作默认配置。 - -## 加载浏览器插件 - -1. 打开 Chrome 或 Edge。 -2. 进入 `chrome://extensions`。 -3. 打开开发者模式。 -4. 选择“加载已解压的扩展程序”。 -5. 选择仓库里的 `browser-extension/` 目录。 - -如果要发给测试者,用下面命令生成 zip: - -```bash -npm run package:browser-extension -``` - -生成物在: - -```text -artifacts/agent-memory-lab-extension.zip -``` - -## 必跑检查 - -每次准备交付或合并前,建议按这个顺序跑: - -```bash -npm run check:browser-extension -npm run check:delivery -npm run package:browser-extension -npm run status:delivery -npm run prepare:ai-validation -npm run make:ai-validation-tester-pack -npm run check:remote-delivery -``` - -这些检查分别覆盖: - -- 插件语法、站点配置、审阅草稿、具体对话记忆草稿、本地 demo 交互。 -- README、截图、文档、插件包、交付状态、发布门槛。 -- 当前提交对应的真实 AI 站点验收清单。 -- 给外测者的一页式真实 AI 站点验收包。 -- 远端仓库是否包含当前提交:已合并时看目标分支,未合并时看交付 PR 分支;同时检查插件包是否对齐当前提交。 - -## 真实 AI 站点验收 - -公开发布前必须补齐这四个站点: - -- ChatGPT -- Claude -- Gemini -- Perplexity - -测试入口: - -```text -docs/browser-extension-ai-site-test-cards-cn.md -docs/browser-extension-ai-validation-cn.md -docs/validation/browser-extension-ai-sites/ -``` - -测试时打开插件同步侧栏,点击“复制问题信息”和“复制检查步骤”。推荐用: - -```bash -npm run prepare:ai-validation -npm run wizard:ai-validation-evidence -- --clipboard -npm run check:ai-validation-evidence -npm run sync:ai-validation-table -``` - -只有确认“记忆建议可见、可插入或复制、诊断已复制、原站输入和发送不受影响”以后,才能把该站点标为通过。 - -## 文档入口 - -- README:`README.md` -- 插件说明:`browser-extension/README.md` -- 插件加载说明:`browser-extension/LOAD-THIS-FIRST.md` -- 外部试用指南:`docs/external-tester-guide-cn.md` -- 产品交付计划:`docs/product-delivery-plan-cn.md` -- 发布门槛:`docs/release-gates-cn.md` -- Mem0 / OpenMemory 参考:`docs/browser-extension-mem0-reference-cn.md` -- 飞书文档: -- 飞书文档源稿:`docs/feishu/agentmemory-project-intro-cn.md` -- 飞书白板源稿:`docs/feishu/whiteboards/workflow.mmd`、`docs/feishu/whiteboards/workbench-workflow.mmd`、`docs/feishu/whiteboards/structure.mmd` -- 飞书白板 token:产品工作流 `SVjZwMwvXhOpxrbTCy7cLH9TnhC`;本地工作台工作流 `UiY9wZbpShh73CbRl8kcPbgcn9g`;仓库结构 `CbOlww8M9hIzJDblujBcHGb3nVd` -- 注意:覆盖更新飞书文档正文会重新生成白板块;更新正文后需要用最新 token 重新写入三张可编辑白板。 - -## 维护建议 - -下一步优先做真实站点验收,而不是继续加新页面。现在最重要的是拿到 ChatGPT、Claude、Gemini、Perplexity 的可复现诊断和无隐私截图,确认插件在真实 AI 输入框附近稳定可用。 diff --git a/docs/readme-assets/screenshots/dashboard.jpg b/docs/readme-assets/screenshots/dashboard.jpg deleted file mode 100644 index ba10b057..00000000 Binary files a/docs/readme-assets/screenshots/dashboard.jpg and /dev/null differ diff --git a/docs/readme-assets/screenshots/skills.jpg b/docs/readme-assets/screenshots/skills.jpg deleted file mode 100644 index 977e113c..00000000 Binary files a/docs/readme-assets/screenshots/skills.jpg and /dev/null differ diff --git a/docs/release-gates-cn.md b/docs/release-gates-cn.md deleted file mode 100644 index 8677dfc5..00000000 --- a/docs/release-gates-cn.md +++ /dev/null @@ -1,93 +0,0 @@ -# Agent Memory Lab 发布门槛 - -这份文档用来区分三个状态:本地可演示、外部可试用、公开可发布。它避免把“能跑 demo”误说成“已经可以公开发布”。 - -## 当前结论 - -| 状态 | 结论 | 证据 | -| --- | --- | --- | -| 本地可演示 | 已达到 | `npm run check:delivery` 通过;README 和飞书文档已同步;首页和 Skill 页截图齐全 | -| 外部可试用 | 基本达到 | 有外部试用指南、插件 zip、免登录预览页、交互烟测、隐私说明和验收表 | -| 公开可发布 | 未达到 | 仍缺真实 AI 站点逐站验收、公开隐私政策 URL、发布截图和商店审核材料 | - -## 本地可演示门槛 - -- README 是中文图文版。 -- README 只放首页和 Skill 管理台两张真实产品截图。 -- 飞书项目介绍和 README 叙事一致。 -- `npm run check:delivery` 通过。 -- `npm run check:workbench` 能给出 API、Viewer 和插件 demo 页的当前状态。 -- 本地免登录插件预览页可打开。 -- 插件交互烟测通过:输入问题后出现“记忆建议”,演示记忆可插入输入框。 - -## 外部可试用门槛 - -- 可以生成 `artifacts/agent-memory-lab-extension.zip`。 -- 可以生成 `artifacts/delivery-summary.md` 和 `artifacts/delivery-manifest.json`,用于快速查看当前版本、提交、产物、sha256 和发布状态。 -- zip 内容检查通过,不包含 macOS 元数据。 -- zip 内包含 `browser-extension/LOAD-THIS-FIRST.md`,解压后有独立加载说明。 -- 试用者知道两种加载方式:从仓库加载 `browser-extension/`,或从 zip 解压后加载 `browser-extension/`。 -- 插件弹窗能显示扩展版本、本地连接状态和外部试用指南入口,便于试用者自查是否加载正确。 -- 插件权限与隐私说明齐全。 -- 弹窗和同步侧栏都有保存前审阅草稿,可改标题、正文、保存范围、分类备注和经验候选状态,再送入 Viewer 待审阅队列。 -- 外部试用指南说明了启动、加载、预览、反馈诊断 JSON 的流程。 -- 外部试用反馈模板存在,并能收集环境、路径、问题、诊断 JSON、截图和影响程度。 -- GitHub 外部试用 Issue 模板存在,并能收集试用路径、环境、诊断 JSON、manualValidation 和隐私确认。 -- 外部反馈分诊指南存在,并能把反馈归类到本地连接、站点适配、输入事件、审阅队列、隐私信任或交付文档。 -- 诊断 JSON 包含扩展版本、输入框命中规则、入口锚点、入口位置策略和记忆建议可见状态。 -- AI 站点验收记录区分“本地可验证”和“真实站点待验收”。 -- 真实 AI 站点诊断 JSON 有固定证据目录,并可用 `npm run check:ai-validation-evidence` 汇总。 -- 可以生成 `artifacts/ai-validation-run/tester-pack-cn.md`,让外测者按同一套站点、prompt、隐私边界和证据命令执行。 - -## 公开可发布门槛 - -公开发布前必须补齐: - -- ChatGPT 真实页面验收。 -- Claude 真实页面验收。 -- Gemini 真实页面验收。 -- Perplexity 真实页面验收。 -- 真实站点验收记录中要保留诊断 JSON 或截图证据;诊断 JSON 必须包含 `turnCount > 0` 和会话区域 selector。 -- 记忆候选必须来自具体对话或用户选中的文字,页面标题、链接、导航文案或输入框草稿不能计入公开发布通过证据。 -- `artifacts/ai-validation-evidence-summary.json` 中必需产品通过数为 4/4。 -- 至少一组不含私人信息的插件截图或短录屏。 -- 稳定公开隐私政策 URL。 -- Chrome Web Store 权限说明与 `browser-extension/manifest.json` 一致。 -- Store listing 文案和当前产品能力一致,不承诺未完成能力。 - -## 每次更新后的默认检查 - -```bash -npm run package:browser-extension && npm run check:delivery -``` - -查看当前发布门槛: - -```bash -npm run status:delivery -``` - -`status:delivery` 会同时显示 current commit 和 artifact commit。两者不一致时,说明交付物是旧提交生成的,需要先重新运行 `npm run package:browser-extension`。 - -再看详细发布门槛: - -```bash -npm run check:release-gates -``` - -准备真实 AI 站点外测包: - -```bash -npm run prepare:ai-validation -npm run make:ai-validation-tester-pack -``` - -公开发布前必须额外通过: - -```bash -npm run check:release-public -``` - -在真实 AI 站点验收完成前,`check:release-public` 应该失败,避免误把外部试用版说成公开发布版。 - -如果飞书源文档有变化,同步到线上飞书文档。 diff --git a/docs/validation/actions-card-classification-repro-cn.md b/docs/validation/actions-card-classification-repro-cn.md deleted file mode 100644 index 9cc09839..00000000 --- a/docs/validation/actions-card-classification-repro-cn.md +++ /dev/null @@ -1,66 +0,0 @@ -# 待办页卡片分类显示复现流程 - -本文用于复现 Viewer `#actions` 待办页的卡片分类展示,重点验证 `inbox` 已回应结果能在网页端看到。 - -## 前置条件 - -在仓库根目录运行: - -```bash -npm run build -npm run start:local-memory -``` - -新开终端确认工作台状态: - -```bash -npm run check:workbench -``` - -以自检输出的 Viewer 地址为准。常见地址是: - -```text -http://127.0.0.1:3115/#actions -``` - -如果端口不是 `3115`,使用 `npm run check:workbench` 输出的 Viewer 端口。 - -## 构造已回应数据 - -创建一条 Agent question: - -```bash -created=$(curl -s -X POST http://127.0.0.1:3111/agentmemory/inbox/ask \ - -H 'Content-Type: application/json' \ - -d '{"body":"分类显示验证:这条已回应应出现在已回应归档。","fromAgent":"classification-check"}') - -id=$(printf '%s' "$created" | node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let j=JSON.parse(s); console.log(j.item && j.item.id || j.id || "")})') -``` - -把这条 question 标记为已回应: - -```bash -curl -s -X POST http://127.0.0.1:3111/agentmemory/inbox/answer \ - -H 'Content-Type: application/json' \ - -d "{\"id\":\"$id\",\"answer\":\"auto-ok\"}" -``` - -## 网页端检查 - -打开待办页: - -```text -http://127.0.0.1:3115/#actions -``` - -检查点: - -- 顶部指标显示 `待回应 / 待确认 / 待跟进 / 进行中 / 已完成`。 -- 页面出现 `已回应 1 条` 的折叠入口。 -- 展开 `已回应` 后,可以看到来源 `classification-check`。 -- 展开内容包含原 question 文案。 -- 展开内容包含 `你已回复:auto-ok`。 - -## 预期结果 - -`answered` 状态的 inbox question 不再从网页端消失,而是进入只读归档区。用户可以在待办页看到自己已经回复了什么,briefing 的已读或 dismissed 项进入 `已知悉`,不再伪装成待办。 diff --git a/docs/validation/browser-extension-ai-sites/.gitkeep b/docs/validation/browser-extension-ai-sites/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/validation/browser-extension-ai-sites/README.md b/docs/validation/browser-extension-ai-sites/README.md deleted file mode 100644 index b368ba23..00000000 --- a/docs/validation/browser-extension-ai-sites/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# 浏览器插件真实 AI 站点证据 - -这个目录用于临时收集真实 AI 页面验收证据。公开发布前,ChatGPT、Claude、Gemini、Perplexity 至少各需要一份通过证据。 - -## 怎么保存证据 - -1. 在目标 AI 页面打开 Agent Memory Lab 同步侧栏。 -2. 输入一个真实问题并等待 AI 回复,确保页面里已经出现至少一轮真实对话。 -3. 确认输入框旁出现“记忆建议”,并且待审阅候选来自具体对话或用户选中的文字,不是页面标题、链接或输入框草稿。 -4. 尝试插入或复制一条本地记忆。 -5. 点击侧栏里的“复制问题信息”。 -6. 用命令把诊断 JSON 保存成标准证据文件。 - -从剪贴板保存: - -```bash -npm run wizard:ai-validation-evidence -``` - -向导会读取剪贴板里的诊断 JSON,并逐项询问人工验收结果:插入/复制本地记忆是否成功、诊断是否复制成功、原站输入和发送是否仍正常、浏览器版本和无隐私备注。 - -如果已经把诊断保存成文件: - -```bash -npm run wizard:ai-validation-evidence -- --file diagnostics.json -``` - -如果你已经确认三项人工验收都通过,也可以用无交互模式: - -```bash -npm run wizard:ai-validation-evidence -- --file diagnostics.json --yes --browser "Chrome 版本号" --notes "无隐私信息的备注" -``` - -旧的记录命令仍可用于排查兼容性,但默认优先使用上面的向导。也可以直接用命令行参数保存: - -```bash -npm run wizard:ai-validation-evidence -- --clipboard -``` - -从文件保存: - -```bash -npm run wizard:ai-validation-evidence -- --file diagnostics.json -``` - -如果已经人工确认插入记忆成功、诊断复制成功、原站输入仍正常,可以加 `--pass`: - -```bash -npm run wizard:ai-validation-evidence -- --clipboard --pass --browser "Chrome 版本号" --notes "无隐私信息的备注" -``` - -也可以手动把 JSON 保存成下面这种文件名: - -```text -YYYY-MM-DD-provider.json -``` - -例子: - -```text -2026-06-08-chatgpt.json -2026-06-08-claude.json -``` - -## 证据字段 - -诊断 JSON 至少需要包含: - -- `extension.version` -- `page.url` -- `ai.provider` -- `ai.editorFound` -- `ai.anchorFound` -- `ai.placement` -- `ai.memoryWidgetVisible` -- `ai.matchedSelectors.editor` -- `ai.matchedSelectors.anchor` -- `ai.matchedSelectors.send` -- `ai.matchedSelectors.turn` -- `ai.turnCount` -- `ai.checkedAt` - -复制问题信息会自带 `manualValidation` 模板。请按真实验收结果把它改好: - -```json -{ - "manualValidation": { - "memoryInsertPassed": true, - "diagnosticsCopied": true, - "siteInputStillWorks": true, - "browser": "Chrome 版本号", - "notes": "无隐私信息的备注" - } -} -``` - -不要把模板里的 `false` 留着就当通过。`npm run check:ai-validation-evidence` 只有在这三项都为通过时,才会把对应产品计入通过数。 - -## 证据质量门槛 - -公开发布需要的是“可复现证据”,不是一句“我这边能用”。因此 ChatGPT、Claude、Gemini、Perplexity 的通过证据必须同时满足: - -- `provider`、`editorFound`、`anchorFound`、`memoryWidgetVisible` 都是真实页面结果。 -- `matchedSelectors` 里保留输入框、锚点、发送按钮、会话区域四类命中规则。 -- `turnCount > 0`,证明插件确实命中了真实会话区域,而不是只识别到输入框或页面壳。 -- 待审阅候选来自具体对话或用户选中的文字;页面介绍、URL、导航文案或输入框草稿不能计入通过。 -- `manualValidation.memoryInsertPassed`、`manualValidation.diagnosticsCopied`、`manualValidation.siteInputStillWorks` 都为通过。 -- `browser` 和 `notes` 写清楚无隐私的浏览器版本与测试备注。 - -复制出来的诊断默认不包含 prompt 草稿、完整会话正文或候选记忆正文;它只保留页面标题、URL、selector、计数和人工验收字段。如果页面标题或 URL 暴露敏感项目名、账号路径或内部链接,可以先删改,但不要删掉 `ai` 里的 selector 和布尔状态。selector 证据不会包含 Cookie、Token 或账号密码,却能帮助我们复现和修复站点适配问题。 - -## 隐私提醒 - -不要提交包含私人聊天正文、账号、Cookie、访问令牌、邮箱、手机号、学校申请材料或任何敏感页面截图的证据。真实证据可以只保留在本地,用 `npm run check:ai-validation-evidence` 汇总状态。 diff --git "a/docs/\351\207\215\346\236\204.md" "b/docs/\351\207\215\346\236\204.md" deleted file mode 100644 index 658afc61..00000000 --- "a/docs/\351\207\215\346\236\204.md" +++ /dev/null @@ -1,15 +0,0 @@ -## AI-TODO 项目重构计划 - -1. 安装/使用方式:需要将整个项目重构成一个 CLI 程序,让用户直接运行。最好是用一个 skill 可以搞定,这样用户可以在 agent 中使用 -2. 代码重构 - 1. 当前代码是基于开源项目 fork,内容很多,需要分析精简 - 2. 考虑从 0 重新开发一版。 -3. 前端重构:当前的前端 viewer 部分是几个 parts 拼接成的一个巨大的 html 文件,由一个 node.js server 服务的。需要用 React 技术栈重构。前端技术栈使用:React+Typescript+TailwindCSS+Shadcn/ui,图标库使用 lucide-react -4. UI/UX 优化: - 1. 同时支持 codex + claude code - 2. 安装后首次启动,自动开始分析本地 sessions,并且在页面上增加 loading 遮罩层,提示用户要等待较长时间 - 3. Organize 完成之后,需要展示一个弹窗,展示这次分析任务的结果总结,分析了多少个会话的多少条消息内容,用了 LLM 还是规则匹配的模式,中间出了什么问题(比如 LLM 没有连上,没有符合要求的会话)。 - 4. 很多文案不合适,待修改。 -5. 技术优化: - 1. session 文件分析需要优化,加快分析过程。如 session 文件的遍历逻辑(先检查文件的修改时间),单个大 session 文件可以只看最后 n 行,不需要分析一整个文件。 - 2. 与 LLM 交互的流程可能需要优化,加快分析过程 diff --git a/examples/python/README.md b/examples/python/README.md deleted file mode 100644 index ea284dd4..00000000 --- a/examples/python/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Python usage via `iii-sdk` - -agentmemory registers its core operations as iii functions (`mem::remember`, -`mem::observe`, `mem::context`, `mem::smart-search`, `mem::forget`). Any -language with an iii SDK can call them directly over the WebSocket transport -on `ws://localhost:49134` — no separate REST client needed. - -This example uses the official Python SDK. - -## Install - -```bash -pip install iii-sdk -``` - -## Quickstart - -Start the agentmemory daemon (defaults to `ws://localhost:49134`, REST on -`:3111`): - -```bash -npx -y @agentmemory/agentmemory -``` - -Then from Python: - -```python -from iii import register_worker - -iii = register_worker("ws://localhost:49134") -iii.connect() - -iii.trigger({ - "function_id": "mem::remember", - "payload": { - "project": "demo", - "title": "auth-stack", - "content": "Service uses HMAC bearer tokens; refresh every 24h.", - "concepts": ["auth", "hmac", "refresh"], - }, -}) - -hits = iii.trigger({ - "function_id": "mem::smart-search", - "payload": {"project": "demo", "query": "how do tokens refresh", "limit": 5}, -}) -print(hits) -``` - -## Functions exposed - -| Function id | Purpose | Required payload | -|---|---|---| -| `mem::remember` | Save a memory | `project`, `title`, `content` | -| `mem::observe` | Hook-driven observation ingest | `hookType`, `sessionId`, `project`, `cwd`, `timestamp` | -| `mem::context` | Render context for a session under a token budget | `sessionId`, `project`, optional `budget` | -| `mem::smart-search` | Hybrid BM25 + vector + concept recall | `project`, `query`, optional `limit` | -| `mem::forget` | Delete a memory by id | `id` | - -The HTTP-trigger wrappers under `api::*` (callable via REST on `:3111`) exist -for the same operations if you need to reach the daemon from a host without an -iii runtime. Inside the iii ecosystem, calling the `mem::*` functions directly -is lower latency. - -## Files - -- `quickstart.py` — minimal save-then-search loop. -- `observe_and_recall.py` — observation ingest + context rendering at a token - budget. - -Both scripts assume the daemon is already running. diff --git a/examples/python/observe_and_recall.py b/examples/python/observe_and_recall.py deleted file mode 100644 index 4747bf0b..00000000 --- a/examples/python/observe_and_recall.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Observation ingest + context rendering at a token budget. - -Pattern: send hook-style observations during a coding session, then ask -agentmemory to render the most relevant context back at a fixed token budget. - -Prerequisites: - pip install iii-sdk - npx -y @agentmemory/agentmemory - -Run: - python examples/python/observe_and_recall.py -""" - -from datetime import datetime, timezone -from iii import register_worker - - -SESSION_ID = "py-example-session-001" -PROJECT = "demo" - - -def now_iso() -> str: - return datetime.now(timezone.utc).isoformat() - - -def main() -> None: - iii = register_worker("ws://localhost:49134") - iii.connect() - - observations = [ - ("PreToolUse", {"tool": "Bash", "command": "cargo test"}), - ("PostToolUse", {"tool": "Bash", "exit_code": 0}), - ("UserPromptSubmit", {"prompt": "refactor auth middleware to use HMAC"}), - ] - - for hook_type, data in observations: - iii.trigger( - { - "function_id": "mem::observe", - "payload": { - "hookType": hook_type, - "sessionId": SESSION_ID, - "project": PROJECT, - "cwd": "/home/user/service", - "timestamp": now_iso(), - "data": data, - }, - } - ) - - context = iii.trigger( - { - "function_id": "mem::context", - "payload": { - "sessionId": SESSION_ID, - "project": PROJECT, - "budget": 2000, - }, - } - ) - - print(f"Rendered context ({context.get('token_count', 0)} tokens):\n") - print(context.get("text", "")) - - -if __name__ == "__main__": - main() diff --git a/examples/python/quickstart.py b/examples/python/quickstart.py deleted file mode 100644 index 56e5ae11..00000000 --- a/examples/python/quickstart.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Minimal agentmemory usage via iii-sdk. - -Prerequisites: - pip install iii-sdk - npx -y @agentmemory/agentmemory # daemon at ws://localhost:49134 - -Run: - python examples/python/quickstart.py -""" - -from iii import register_worker - - -def main() -> None: - iii = register_worker("ws://localhost:49134") - iii.connect() - - iii.trigger( - { - "function_id": "mem::remember", - "payload": { - "project": "demo", - "title": "auth-stack", - "content": "Service uses HMAC bearer tokens; refresh every 24h.", - "concepts": ["auth", "hmac", "refresh"], - }, - } - ) - - hits = iii.trigger( - { - "function_id": "mem::smart-search", - "payload": { - "project": "demo", - "query": "how do tokens refresh", - "limit": 5, - }, - } - ) - - for memory in hits.get("results", []): - print(f"[{memory.get('score', 0):.3f}] {memory.get('title')}: {memory.get('content')}") - - -if __name__ == "__main__": - main() diff --git a/iii-config.docker.yaml b/iii-config.docker.yaml deleted file mode 100644 index cf556daf..00000000 --- a/iii-config.docker.yaml +++ /dev/null @@ -1,53 +0,0 @@ -workers: - - name: iii-http - config: - port: 3111 - host: 0.0.0.0 - default_timeout: 180000 - cors: - allowed_origins: ["http://localhost:3111", "http://localhost:3114", "http://127.0.0.1:3111", "http://127.0.0.1:3114"] - allowed_methods: [GET, POST, PUT, DELETE, OPTIONS] - - name: iii-state - config: - adapter: - name: kv - config: - store_method: file_based - file_path: /data/state_store.db - - name: iii-queue - config: - adapter: - name: builtin - - name: iii-pubsub - config: - adapter: - name: local - - name: iii-cron - config: - adapter: - name: kv - - name: iii-stream - config: - port: 3112 - host: 0.0.0.0 - adapter: - name: kv - config: - store_method: file_based - file_path: /data/stream_store - - name: iii-observability - config: - enabled: true - service_name: agentmemory - exporter: memory - # See iii-config.yaml for the rationale on 0.1 / console-off (#519). - sampling_ratio: 0.1 - metrics_enabled: true - logs_enabled: true - logs_console_output: false - - name: iii-exec - config: - watch: - - src/**/*.ts - exec: - - node dist/index.mjs diff --git a/iii-config.local-memory.yaml b/iii-config.local-memory.yaml deleted file mode 100644 index 25d1ad87..00000000 --- a/iii-config.local-memory.yaml +++ /dev/null @@ -1,52 +0,0 @@ -workers: - - name: iii-http - config: - port: 3111 - host: 127.0.0.1 - default_timeout: 180000 - cors: - allowed_origins: ["http://localhost:3111", "http://localhost:3114", "http://127.0.0.1:3111", "http://127.0.0.1:3114"] - allowed_methods: [GET, POST, PUT, DELETE, OPTIONS] - - name: iii-state - config: - adapter: - name: kv - config: - store_method: file_based - file_path: ./data/state_store.db - - name: iii-queue - config: - adapter: - name: builtin - - name: iii-pubsub - config: - adapter: - name: local - - name: iii-cron - config: - adapter: - name: kv - - name: iii-stream - config: - port: 3112 - host: 127.0.0.1 - adapter: - name: kv - config: - store_method: file_based - file_path: ./data/stream_store - - name: iii-observability - config: - enabled: true - service_name: agentmemory - exporter: memory - sampling_ratio: 0.1 - metrics_enabled: true - logs_enabled: true - logs_console_output: false - - name: iii-exec - config: - watch: - - src/**/*.ts - exec: - - node dist/index.mjs diff --git a/iii-config.yaml b/iii-config.yaml deleted file mode 100644 index 6c2dba7d..00000000 --- a/iii-config.yaml +++ /dev/null @@ -1,61 +0,0 @@ -workers: - - name: iii-http - config: - port: 3111 - host: 127.0.0.1 - default_timeout: 180000 - cors: - allowed_origins: ["http://localhost:3111", "http://localhost:3114", "http://127.0.0.1:3111", "http://127.0.0.1:3114"] - allowed_methods: [GET, POST, PUT, DELETE, OPTIONS] - - name: iii-state - config: - adapter: - name: kv - config: - store_method: file_based - file_path: ./data/state_store.db - - name: iii-queue - config: - adapter: - name: builtin - - name: iii-pubsub - config: - adapter: - name: local - - name: iii-cron - config: - adapter: - name: kv - - name: iii-stream - config: - port: 3112 - host: 127.0.0.1 - adapter: - name: kv - config: - store_method: file_based - file_path: ./data/stream_store - - name: iii-observability - config: - enabled: true - service_name: agentmemory - exporter: memory - # 0.1 instead of 1.0: at full sampling under sustained load the - # log subscriber falls behind, the worker emits a "Log trigger - # subscriber lagged" WARN, and that WARN re-enters the same - # stream the subscriber is failing to drain — a positive - # feedback loop that wrote 137 GB to daemon.log.new on one user - # in a few days (#519). - sampling_ratio: 0.1 - metrics_enabled: true - logs_enabled: true - # Console output is off by default; the feedback path needs the - # console layer to amplify. Re-enable per-session via env if you - # need verbose tracing for debugging. - logs_console_output: false - - name: iii-exec - config: - watch: - - src/**/*.ts - exec: - - node dist/index.mjs diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..3fd2e5bc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,54 @@ +{ + "name": "@maimorylab/ai-todo", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@maimorylab/ai-todo", + "version": "0.1.0", + "license": "Apache-2.0", + "bin": { + "ai-todo": "dist/cli.mjs" + }, + "devDependencies": { + "@types/node": "^26.0.1", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=24.0.0" + } + }, + "node_modules/@types/node": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", + "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json index 7220bc47..0a8dea83 100644 --- a/package.json +++ b/package.json @@ -1,109 +1,24 @@ { - "name": "@maimorylab/agentmemory-lab", - "version": "0.9.24", - "description": "Persistent memory for AI coding agents, powered by iii-engine's three primitives", + "name": "@maimorylab/ai-todo", + "version": "0.1.0", + "description": "Local-first todo extraction for AI agent sessions.", "type": "module", - "main": "dist/index.mjs", - "types": "dist/index.d.mts", - "exports": { - ".": { - "types": "./dist/index.d.mts", - "import": "./dist/index.mjs" - }, - "./dist/standalone.mjs": "./dist/standalone.mjs", - "./package.json": "./package.json" - }, "bin": { - "agentmemory-lab": "dist/cli.mjs" + "ai-todo": "dist/cli.js" }, + "main": "dist/src/index.js", + "types": "dist/src/index.d.ts", "scripts": { - "build": "tsdown && node scripts/build-viewer-html.mjs --check && (cp iii-config.yaml dist/ 2>/dev/null || true) && (cp iii-config.docker.yaml dist/ 2>/dev/null || true) && (cp docker-compose.yml dist/ 2>/dev/null || true) && (cp .env.example dist/ 2>/dev/null || true) && mkdir -p dist/viewer/agent-avatars dist/viewer/demo dist/functions && cp src/viewer/index.html dist/viewer/ && cp src/viewer/favicon.svg dist/viewer/ && cp src/viewer/agent-avatars/* dist/viewer/agent-avatars/ && cp src/viewer/demo/* dist/viewer/demo/ && cp src/functions/todo-extract-langextract.py dist/functions/ && cp src/functions/todo-update-llm.py dist/functions/", - "dev": "tsx src/index.ts", - "postinstall": "node scripts/install-langextract.mjs", - "viewer:build": "node scripts/build-viewer-html.mjs", - "viewer:watch": "node scripts/build-viewer-html.mjs --watch", - "start": "node dist/cli.mjs", - "start:local-memory": "AGENTMEMORY_III_CONFIG=./iii-config.local-memory.yaml node dist/cli.mjs", - "migrate": "node dist/functions/migrate.js", - "test": "vitest run --exclude test/integration.test.ts", - "check:browser-extension": "node scripts/check-browser-extension.mjs && node scripts/check-browser-extension-site-config-sync.mjs && node scripts/check-browser-extension-review-draft.mjs && node scripts/check-browser-extension-memory-drafts.mjs && node scripts/check-browser-extension-diagnostics-privacy.mjs && node scripts/check-browser-extension-fixtures.mjs && node scripts/check-browser-extension-demo-interaction.mjs", - "package:browser-extension": "npm run check:browser-extension && node scripts/package-browser-extension.mjs && npm run check:browser-extension-package && npm run check:ai-validation-evidence && node scripts/write-delivery-summary.mjs && npm run make:ai-validation-tester-pack", - "check:delivery": "npm run build && npm run check:maimorylab-branding && node scripts/check-delivery.mjs", - "test:watch": "vitest --exclude test/integration.test.ts", - "test:integration": "vitest run test/integration.test.ts", - "test:all": "vitest run", - "preview:browser-extension": "npm run build && node scripts/preview-browser-extension.mjs", - "check:browser-extension-package": "node scripts/check-browser-extension-package.mjs", - "check:workbench": "node scripts/check-workbench-status.mjs", - "check:release-gates": "node scripts/check-release-gates.mjs", - "check:release-public": "node scripts/check-release-gates.mjs --public", - "record:ai-validation-evidence": "node scripts/record-ai-validation-evidence.mjs", - "wizard:ai-validation-evidence": "node scripts/wizard-ai-validation-evidence.mjs", - "prepare:ai-validation": "node scripts/prepare-ai-validation-run.mjs", - "make:ai-validation-tester-pack": "node scripts/make-ai-validation-tester-pack.mjs", - "check:ai-validation-evidence": "node scripts/check-ai-validation-evidence.mjs", - "check:ai-validation-evidence-guards": "node scripts/check-ai-validation-evidence-guards.mjs", - "sync:ai-validation-table": "node scripts/sync-ai-validation-table.mjs --write", - "check:ai-validation-table": "node scripts/sync-ai-validation-table.mjs --check", - "status:delivery": "node scripts/delivery-status.mjs", - "check:remote-delivery": "node scripts/check-remote-delivery.mjs", - "check:maimorylab-branding": "node scripts/check-maimorylab-branding.mjs", - "check:consistency-local": "node scripts/check-consistency-local.mjs", - "pre-pr": "npm run check:consistency-local && npm run build && npm test" - }, - "keywords": [ - "ai", - "agent", - "memory", - "persistent", - "iii-engine", - "claude-code", - "coding-agent", - "context", - "observation" - ], - "files": [ - "dist/", - "plugin/", - "scripts/install-langextract.mjs", - "iii-config.yaml", - "iii-config.docker.yaml", - "docker-compose.yml", - ".env.example", - "requirements-langextract.txt", - "LICENSE", - "README.md", - "AGENTS.md" - ], - "author": "MaimoryLab", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/MaimoryLab/agentmemory-lab" - }, - "dependencies": { - "@anthropic-ai/claude-agent-sdk": "^0.3.142", - "@anthropic-ai/sdk": "^0.105.0", - "@clack/prompts": "^1.2.0", - "dotenv": "^17.4.2", - "iii-sdk": "0.19.4", - "zod": "^4.0.0" + "build": "rm -rf dist && tsc", + "test": "npm run build && node --test dist/test/*.test.js", + "start": "node dist/cli.js" }, - "optionalDependencies": { - "@node-rs/jieba": "^2.0.1", - "@xenova/transformers": "^2.17.2", - "onnxruntime-node": "^1.14.0", - "onnxruntime-web": "^1.14.0", - "tiny-segmenter": "^0.2.0" + "engines": { + "node": ">=24.0.0" }, "devDependencies": { - "@types/node": "^26.0.0", - "tsdown": "^0.20.3", - "tsx": "^4.19.0", - "typescript": "^6.0.3", - "vitest": "^4.1.6" + "@types/node": "^26.0.1", + "typescript": "^5.9.3" }, - "engines": { - "node": ">=20.0.0" - } + "license": "Apache-2.0" } diff --git a/packages/mcp/LICENSE b/packages/mcp/LICENSE deleted file mode 100644 index cb13c728..00000000 --- a/packages/mcp/LICENSE +++ /dev/null @@ -1,190 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by the Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding any notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2026 Rohit Ghumare - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/mcp/README.md b/packages/mcp/README.md deleted file mode 100644 index abd57a75..00000000 --- a/packages/mcp/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# @agentmemory/mcp - -Standalone MCP server for [agentmemory](https://github.com/rohitg00/agentmemory). - -This is a thin shim package that re-exposes the standalone MCP entrypoint from -[`@agentmemory/agentmemory`](https://www.npmjs.com/package/@agentmemory/agentmemory), -so MCP client configs that say `npx @agentmemory/mcp` work out of the box -without installing the full package first. - -## Usage - -```bash -npx -y @agentmemory/mcp -``` - -Or wire it into your MCP client (Claude Desktop, OpenClaw, Cursor, Codex, etc.): - -```json -{ - "mcpServers": { - "agentmemory": { - "command": "npx", - "args": ["-y", "@agentmemory/mcp"] - } - } -} -``` - -This package depends on `@agentmemory/agentmemory` and forwards to its -`dist/standalone.mjs` entrypoint. If you already have `@agentmemory/agentmemory` -installed, you can call the same entrypoint directly: - -```bash -npx @agentmemory/agentmemory mcp -``` - -Both commands do the same thing. - -## Why does this package exist? - -The original plan in [issue #120](https://github.com/rohitg00/agentmemory/issues/120) -was to publish `agentmemory-mcp` as an unscoped package, but npm's name-similarity -policy blocks that name because of an unrelated package called `agent-memory-mcp`. -Publishing under the `@agentmemory` scope sidesteps the conflict and keeps the -"dedicated standalone package" UX — `npx @agentmemory/mcp` is one character -longer than `npx agentmemory-mcp` and works on the live registry. - -## License - -Apache-2.0 diff --git a/packages/mcp/bin.mjs b/packages/mcp/bin.mjs deleted file mode 100755 index 7df452d5..00000000 --- a/packages/mcp/bin.mjs +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env node -import("@agentmemory/agentmemory/dist/standalone.mjs").catch((err) => { - console.error( - "[@agentmemory/mcp] Failed to load standalone entrypoint from @agentmemory/agentmemory.", - ); - console.error( - "[@agentmemory/mcp] Try installing manually: npm i -g @agentmemory/agentmemory", - ); - console.error(err instanceof Error ? err.stack || err.message : String(err)); - process.exit(1); -}); diff --git a/packages/mcp/package.json b/packages/mcp/package.json deleted file mode 100644 index 4c1fe9f9..00000000 --- a/packages/mcp/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "@agentmemory/mcp", - "version": "0.9.24", - "description": "Standalone MCP server for agentmemory — thin shim that re-exposes @agentmemory/agentmemory's MCP entrypoint", - "type": "module", - "bin": { - "agentmemory-mcp": "./bin.mjs" - }, - "files": [ - "bin.mjs", - "README.md", - "LICENSE" - ], - "keywords": [ - "ai", - "agent", - "memory", - "mcp", - "agentmemory" - ], - "author": "Rohit Ghumare ", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/rohitg00/agentmemory", - "directory": "packages/mcp" - }, - "homepage": "https://github.com/rohitg00/agentmemory#readme", - "bugs": "https://github.com/rohitg00/agentmemory/issues", - "dependencies": { - "@agentmemory/agentmemory": "~0.9.0" - }, - "publishConfig": { - "access": "public", - "provenance": true - }, - "engines": { - "node": ">=20.0.0" - } -} diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json deleted file mode 100644 index fbc617d3..00000000 --- a/plugin/.claude-plugin/plugin.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "agentmemory", - "version": "0.9.24", - "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 55 MCP tools, 9 skills, real-time viewer.", - "author": { - "name": "Rohit Ghumare", - "url": "https://github.com/rohitg00" - }, - "license": "Apache-2.0", - "homepage": "https://github.com/rohitg00/agentmemory", - "repository": "https://github.com/rohitg00/agentmemory", - "skills": ["./skills/"] -} diff --git a/plugin/.codex-plugin/plugin.json b/plugin/.codex-plugin/plugin.json deleted file mode 100644 index 2ea65eb4..00000000 --- a/plugin/.codex-plugin/plugin.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "agent-memory-lab", - "version": "0.9.24", - "description": "Local-first Agent memory workbench -- captures sessions, opens a reviewable viewer, syncs durable memories, promotes lessons into Skills, and prepares README/Feishu publishing materials.", - "author": { - "name": "MaimoryLab", - "url": "https://github.com/MaimoryLab/agentmemory-lab" - }, - "license": "Apache-2.0", - "homepage": "https://github.com/MaimoryLab/agentmemory-lab", - "repository": "https://github.com/MaimoryLab/agentmemory-lab", - "skills": "./skills/", - "mcpServers": "./.mcp.json", - "hooks": "./hooks/hooks.codex.json" -} diff --git a/plugin/.mcp.copilot.json b/plugin/.mcp.copilot.json deleted file mode 100644 index 827330ad..00000000 --- a/plugin/.mcp.copilot.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "mcpServers": { - "agentmemory": { - "type": "local", - "command": "npx", - "args": ["-y", "@agentmemory/mcp"], - "env": { - "AGENTMEMORY_URL": "${AGENTMEMORY_URL:-http://localhost:3111}", - "AGENTMEMORY_SECRET": "${AGENTMEMORY_SECRET:-}", - "AGENTMEMORY_TOOLS": "${AGENTMEMORY_TOOLS:-all}" - }, - "tools": ["*"] - } - } -} diff --git a/plugin/.mcp.json b/plugin/.mcp.json deleted file mode 100644 index 37f7754f..00000000 --- a/plugin/.mcp.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mcpServers": { - "agentmemory": { - "command": "npx", - "args": ["-y", "@agentmemory/mcp"], - "env": { - "AGENTMEMORY_URL": "${AGENTMEMORY_URL:-http://localhost:3111}", - "AGENTMEMORY_SECRET": "${AGENTMEMORY_SECRET:-}", - "AGENTMEMORY_TOOLS": "${AGENTMEMORY_TOOLS:-all}" - } - } - } -} diff --git a/plugin/hooks/hooks.codex.json b/plugin/hooks/hooks.codex.json deleted file mode 100644 index d2c3a3b6..00000000 --- a/plugin/hooks/hooks.codex.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "hooks": { - "SessionStart": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-start.mjs\"", - "statusMessage": "agentmemory: loading session context" - } - ] - } - ], - "UserPromptSubmit": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/prompt-submit.mjs\"", - "statusMessage": "agentmemory: recalling relevant memories" - } - ] - } - ], - "PreToolUse": [ - { - "matcher": "Edit|Write|Read|Glob|Grep", - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/pre-tool-use.mjs\"" - } - ] - } - ], - "PostToolUse": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/post-tool-use.mjs\"" - } - ] - } - ], - "PreCompact": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/pre-compact.mjs\"" - } - ] - } - ], - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/stop.mjs\"" - } - ] - } - ] - } -} diff --git a/plugin/hooks/hooks.copilot.json b/plugin/hooks/hooks.copilot.json deleted file mode 100644 index b7d09f8b..00000000 --- a/plugin/hooks/hooks.copilot.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "version": 1, - "hooks": { - "sessionStart": [ - { - "type": "command", - "command": "node ${COPILOT_PLUGIN_ROOT}/scripts/session-start.mjs" - } - ], - "userPromptSubmitted": [ - { - "type": "command", - "command": "node ${COPILOT_PLUGIN_ROOT}/scripts/prompt-submit.mjs" - } - ], - "preToolUse": [ - { - "type": "command", - "matcher": "edit|write|create|read|view|glob|grep", - "command": "node ${COPILOT_PLUGIN_ROOT}/scripts/pre-tool-use.mjs" - } - ], - "postToolUse": [ - { - "type": "command", - "command": "node ${COPILOT_PLUGIN_ROOT}/scripts/post-tool-use.mjs" - } - ], - "postToolUseFailure": [ - { - "type": "command", - "command": "node ${COPILOT_PLUGIN_ROOT}/scripts/post-tool-failure.mjs" - } - ], - "preCompact": [ - { - "type": "command", - "command": "node ${COPILOT_PLUGIN_ROOT}/scripts/pre-compact.mjs" - } - ], - "agentStop": [ - { - "type": "command", - "command": "node ${COPILOT_PLUGIN_ROOT}/scripts/stop.mjs" - } - ], - "sessionEnd": [ - { - "type": "command", - "command": "node ${COPILOT_PLUGIN_ROOT}/scripts/session-end.mjs" - } - ], - "subagentStart": [ - { - "type": "command", - "command": "node ${COPILOT_PLUGIN_ROOT}/scripts/subagent-start.mjs" - } - ], - "subagentStop": [ - { - "type": "command", - "command": "node ${COPILOT_PLUGIN_ROOT}/scripts/subagent-stop.mjs" - } - ], - "notification": [ - { - "type": "command", - "command": "node ${COPILOT_PLUGIN_ROOT}/scripts/notification.mjs" - } - ] - } -} diff --git a/plugin/hooks/hooks.json b/plugin/hooks/hooks.json deleted file mode 100644 index a13c9973..00000000 --- a/plugin/hooks/hooks.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "hooks": { - "SessionStart": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-start.mjs\"" - } - ] - } - ], - "UserPromptSubmit": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/prompt-submit.mjs\"" - } - ] - } - ], - "PreToolUse": [ - { - "matcher": "Edit|Write|Read|Glob|Grep", - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/pre-tool-use.mjs\"" - } - ] - } - ], - "PostToolUse": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/post-tool-use.mjs\"" - } - ] - } - ], - "PostToolUseFailure": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/post-tool-failure.mjs\"" - } - ] - } - ], - "PreCompact": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/pre-compact.mjs\"" - } - ] - } - ], - "SubagentStart": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/subagent-start.mjs\"" - } - ] - } - ], - "SubagentStop": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/subagent-stop.mjs\"" - } - ] - } - ], - "Notification": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/notification.mjs\"" - } - ] - } - ], - "TaskCompleted": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/task-completed.mjs\"" - } - ] - } - ], - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/stop.mjs\"" - } - ] - } - ], - "SessionEnd": [ - { - "hooks": [ - { - "type": "command", - "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-end.mjs\"" - } - ] - } - ] - } -} diff --git a/plugin/plugin.json b/plugin/plugin.json deleted file mode 100644 index f7cfd5a3..00000000 --- a/plugin/plugin.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "agent-memory-lab", - "version": "0.9.24", - "description": "Local-first Agent memory workbench -- captures sessions, opens a reviewable viewer, syncs durable memories, promotes lessons into Skills, and prepares README/Feishu publishing materials.", - "author": { - "name": "MaimoryLab", - "url": "https://github.com/MaimoryLab/agentmemory-lab" - }, - "license": "Apache-2.0", - "homepage": "https://github.com/MaimoryLab/agentmemory-lab", - "repository": "https://github.com/MaimoryLab/agentmemory-lab", - "skills": "skills/", - "mcpServers": ".mcp.copilot.json", - "hooks": "hooks/hooks.copilot.json" -} diff --git a/plugin/scripts/diagnostics.mjs b/plugin/scripts/diagnostics.mjs deleted file mode 100644 index 96d6dada..00000000 --- a/plugin/scripts/diagnostics.mjs +++ /dev/null @@ -1,551 +0,0 @@ -//#region src/state/schema.ts -const KV = { - sessions: "mem:sessions", - observations: (sessionId) => `mem:obs:${sessionId}`, - memories: "mem:memories", - summaries: "mem:summaries", - config: "mem:config", - metrics: "mem:metrics", - health: "mem:health", - embeddings: (obsId) => `mem:emb:${obsId}`, - bm25Index: "mem:index:bm25", - relations: "mem:relations", - profiles: "mem:profiles", - claudeBridge: "mem:claude-bridge", - graphNodes: "mem:graph:nodes", - graphEdges: "mem:graph:edges", - semantic: "mem:semantic", - procedural: "mem:procedural", - teamShared: (teamId) => `mem:team:${teamId}:shared`, - teamUsers: (teamId, userId) => `mem:team:${teamId}:users:${userId}`, - teamProfile: (teamId) => `mem:team:${teamId}:profile`, - audit: "mem:audit", - actions: "mem:actions", - actionEdges: "mem:action-edges", - leases: "mem:leases", - routines: "mem:routines", - routineRuns: "mem:routine-runs", - signals: "mem:signals", - checkpoints: "mem:checkpoints", - mesh: "mem:mesh", - sketches: "mem:sketches", - facets: "mem:facets", - sentinels: "mem:sentinels", - crystals: "mem:crystals" -}; - -//#endregion -//#region src/state/keyed-mutex.ts -const locks = /* @__PURE__ */ new Map(); -function withKeyedLock(key, fn) { - const next = (locks.get(key) ?? Promise.resolve()).then(fn, fn); - const cleanup = next.then(() => {}, () => {}); - locks.set(key, cleanup); - cleanup.then(() => { - if (locks.get(key) === cleanup) locks.delete(key); - }); - return next; -} - -//#endregion -//#region src/functions/diagnostics.ts -const ALL_CATEGORIES = [ - "actions", - "leases", - "sentinels", - "sketches", - "signals", - "sessions", - "memories", - "mesh" -]; -const TWENTY_FOUR_HOURS_MS = 1440 * 60 * 1e3; -const ONE_HOUR_MS = 3600 * 1e3; -function registerDiagnosticsFunction(sdk, kv) { - sdk.registerFunction("mem::diagnose", async (data) => { - const categories = data.categories && data.categories.length > 0 ? data.categories.filter((c) => ALL_CATEGORIES.includes(c)) : ALL_CATEGORIES; - const checks = []; - const now = Date.now(); - if (categories.includes("actions")) { - const actions = await kv.list(KV.actions); - const allEdges = await kv.list(KV.actionEdges); - const leases = await kv.list(KV.leases); - const actionMap = new Map(actions.map((a) => [a.id, a])); - for (const action of actions) { - if (action.status === "active") { - if (!leases.some((l) => l.actionId === action.id && l.status === "active" && new Date(l.expiresAt).getTime() > now)) checks.push({ - name: `active-no-lease:${action.id}`, - category: "actions", - status: "warn", - message: `Action "${action.title}" is active but has no active lease`, - fixable: false - }); - } - if (action.status === "blocked") { - const deps = allEdges.filter((e) => e.sourceActionId === action.id && e.type === "requires"); - if (deps.length > 0) { - if (deps.every((d) => { - const target = actionMap.get(d.targetActionId); - return target && target.status === "done"; - })) checks.push({ - name: `blocked-deps-done:${action.id}`, - category: "actions", - status: "fail", - message: `Action "${action.title}" is blocked but all dependencies are done`, - fixable: true - }); - } - } - if (action.status === "pending") { - const deps = allEdges.filter((e) => e.sourceActionId === action.id && e.type === "requires"); - if (deps.length > 0) { - if (deps.some((d) => { - const target = actionMap.get(d.targetActionId); - return !target || target.status !== "done"; - })) checks.push({ - name: `pending-unsatisfied-deps:${action.id}`, - category: "actions", - status: "fail", - message: `Action "${action.title}" is pending but has unsatisfied dependencies`, - fixable: true - }); - } - } - } - if (!checks.some((c) => c.category === "actions" && c.status !== "pass")) checks.push({ - name: "actions-ok", - category: "actions", - status: "pass", - message: `All ${actions.length} actions are consistent`, - fixable: false - }); - } - if (categories.includes("leases")) { - const leases = await kv.list(KV.leases); - const actions = await kv.list(KV.actions); - const actionIds = new Set(actions.map((a) => a.id)); - let leaseIssues = 0; - for (const lease of leases) { - if (lease.status === "active" && new Date(lease.expiresAt).getTime() <= now) { - checks.push({ - name: `expired-lease:${lease.id}`, - category: "leases", - status: "fail", - message: `Lease ${lease.id} for action ${lease.actionId} expired at ${lease.expiresAt}`, - fixable: true - }); - leaseIssues++; - } - if (!actionIds.has(lease.actionId)) { - checks.push({ - name: `orphaned-lease:${lease.id}`, - category: "leases", - status: "fail", - message: `Lease ${lease.id} references non-existent action ${lease.actionId}`, - fixable: true - }); - leaseIssues++; - } - } - if (leaseIssues === 0) checks.push({ - name: "leases-ok", - category: "leases", - status: "pass", - message: `All ${leases.length} leases are healthy`, - fixable: false - }); - } - if (categories.includes("sentinels")) { - const sentinels = await kv.list(KV.sentinels); - const actions = await kv.list(KV.actions); - const actionIds = new Set(actions.map((a) => a.id)); - let sentinelIssues = 0; - for (const sentinel of sentinels) { - if (sentinel.status === "watching" && sentinel.expiresAt && new Date(sentinel.expiresAt).getTime() <= now) { - checks.push({ - name: `expired-sentinel:${sentinel.id}`, - category: "sentinels", - status: "fail", - message: `Sentinel "${sentinel.name}" expired at ${sentinel.expiresAt}`, - fixable: true - }); - sentinelIssues++; - } - for (const actionId of sentinel.linkedActionIds) if (!actionIds.has(actionId)) { - checks.push({ - name: `sentinel-missing-action:${sentinel.id}:${actionId}`, - category: "sentinels", - status: "warn", - message: `Sentinel "${sentinel.name}" references non-existent action ${actionId}`, - fixable: false - }); - sentinelIssues++; - } - } - if (sentinelIssues === 0) checks.push({ - name: "sentinels-ok", - category: "sentinels", - status: "pass", - message: `All ${sentinels.length} sentinels are healthy`, - fixable: false - }); - } - if (categories.includes("sketches")) { - const sketches = await kv.list(KV.sketches); - let sketchIssues = 0; - for (const sketch of sketches) if (sketch.status === "active" && new Date(sketch.expiresAt).getTime() <= now) { - checks.push({ - name: `expired-sketch:${sketch.id}`, - category: "sketches", - status: "fail", - message: `Sketch "${sketch.title}" expired at ${sketch.expiresAt}`, - fixable: true - }); - sketchIssues++; - } - if (sketchIssues === 0) checks.push({ - name: "sketches-ok", - category: "sketches", - status: "pass", - message: `All ${sketches.length} sketches are healthy`, - fixable: false - }); - } - if (categories.includes("signals")) { - const signals = await kv.list(KV.signals); - let signalIssues = 0; - for (const signal of signals) if (signal.expiresAt && new Date(signal.expiresAt).getTime() <= now) { - checks.push({ - name: `expired-signal:${signal.id}`, - category: "signals", - status: "fail", - message: `Signal from "${signal.from}" expired at ${signal.expiresAt}`, - fixable: true - }); - signalIssues++; - } - if (signalIssues === 0) checks.push({ - name: "signals-ok", - category: "signals", - status: "pass", - message: `All ${signals.length} signals are healthy`, - fixable: false - }); - } - if (categories.includes("sessions")) { - const sessions = await kv.list(KV.sessions); - let sessionIssues = 0; - for (const session of sessions) if (session.status === "active" && now - new Date(session.startedAt).getTime() > TWENTY_FOUR_HOURS_MS) { - checks.push({ - name: `abandoned-session:${session.id}`, - category: "sessions", - status: "warn", - message: `Session ${session.id} has been active for over 24 hours`, - fixable: false - }); - sessionIssues++; - } - if (sessionIssues === 0) checks.push({ - name: "sessions-ok", - category: "sessions", - status: "pass", - message: `All ${sessions.length} sessions are healthy`, - fixable: false - }); - } - if (categories.includes("memories")) { - const memories = await kv.list(KV.memories); - const memoryIds = new Set(memories.map((m) => m.id)); - const supersededBy = /* @__PURE__ */ new Map(); - let memoryIssues = 0; - for (const memory of memories) if (memory.supersedes && memory.supersedes.length > 0) for (const sid of memory.supersedes) { - if (!memoryIds.has(sid)) { - checks.push({ - name: `memory-missing-supersedes:${memory.id}:${sid}`, - category: "memories", - status: "warn", - message: `Memory "${memory.title}" supersedes non-existent memory ${sid}`, - fixable: false - }); - memoryIssues++; - } - supersededBy.set(sid, memory.id); - } - for (const memory of memories) if (memory.isLatest && supersededBy.has(memory.id)) { - checks.push({ - name: `memory-stale-latest:${memory.id}`, - category: "memories", - status: "fail", - message: `Memory "${memory.title}" has isLatest=true but is superseded by ${supersededBy.get(memory.id)}`, - fixable: true - }); - memoryIssues++; - } - if (memoryIssues === 0) checks.push({ - name: "memories-ok", - category: "memories", - status: "pass", - message: `All ${memories.length} memories are consistent`, - fixable: false - }); - } - if (categories.includes("mesh")) { - const peers = await kv.list(KV.mesh); - let meshIssues = 0; - for (const peer of peers) { - if (peer.lastSyncAt && now - new Date(peer.lastSyncAt).getTime() > ONE_HOUR_MS) { - checks.push({ - name: `stale-peer:${peer.id}`, - category: "mesh", - status: "warn", - message: `Peer "${peer.name}" last synced over 1 hour ago`, - fixable: false - }); - meshIssues++; - } - if (peer.status === "error") { - checks.push({ - name: `error-peer:${peer.id}`, - category: "mesh", - status: "warn", - message: `Peer "${peer.name}" is in error state`, - fixable: false - }); - meshIssues++; - } - } - if (meshIssues === 0) checks.push({ - name: "mesh-ok", - category: "mesh", - status: "pass", - message: `All ${peers.length} mesh peers are healthy`, - fixable: false - }); - } - return { - success: true, - checks, - summary: { - pass: checks.filter((c) => c.status === "pass").length, - warn: checks.filter((c) => c.status === "warn").length, - fail: checks.filter((c) => c.status === "fail").length, - fixable: checks.filter((c) => c.fixable).length - } - }; - }); - sdk.registerFunction("mem::heal", async (data) => { - const dryRun = data.dryRun ?? false; - const categories = data.categories && data.categories.length > 0 ? data.categories.filter((c) => ALL_CATEGORIES.includes(c)) : ALL_CATEGORIES; - let fixed = 0; - let skipped = 0; - const details = []; - const now = Date.now(); - if (categories.includes("actions")) { - const actions = await kv.list(KV.actions); - const allEdges = await kv.list(KV.actionEdges); - const actionMap = new Map(actions.map((a) => [a.id, a])); - for (const action of actions) { - if (action.status === "blocked") { - const deps = allEdges.filter((e) => e.sourceActionId === action.id && e.type === "requires"); - if (deps.length > 0) { - if (deps.every((d) => { - const target = actionMap.get(d.targetActionId); - return target && target.status === "done"; - })) { - if (dryRun) { - details.push(`[dry-run] Would unblock action "${action.title}" (${action.id})`); - fixed++; - continue; - } - if (await withKeyedLock(`mem:action:${action.id}`, async () => { - const fresh = await kv.get(KV.actions, action.id); - if (!fresh || fresh.status !== "blocked") return false; - const freshDeps = (await kv.list(KV.actionEdges)).filter((e) => e.sourceActionId === fresh.id && e.type === "requires"); - const freshActions = await kv.list(KV.actions); - const freshMap = new Map(freshActions.map((a) => [a.id, a])); - if (!freshDeps.every((d) => { - const target = freshMap.get(d.targetActionId); - return target && target.status === "done"; - })) return false; - fresh.status = "pending"; - fresh.updatedAt = (/* @__PURE__ */ new Date()).toISOString(); - await kv.set(KV.actions, fresh.id, fresh); - return true; - })) { - details.push(`Unblocked action "${action.title}" (${action.id})`); - fixed++; - } else skipped++; - } - } - } - if (action.status === "pending") { - const deps = allEdges.filter((e) => e.sourceActionId === action.id && e.type === "requires"); - if (deps.length > 0) { - if (deps.some((d) => { - const target = actionMap.get(d.targetActionId); - return !target || target.status !== "done"; - })) { - if (dryRun) { - details.push(`[dry-run] Would block action "${action.title}" (${action.id})`); - fixed++; - continue; - } - if (await withKeyedLock(`mem:action:${action.id}`, async () => { - const fresh = await kv.get(KV.actions, action.id); - if (!fresh || fresh.status !== "pending") return false; - const freshDeps = (await kv.list(KV.actionEdges)).filter((e) => e.sourceActionId === fresh.id && e.type === "requires"); - const freshActions = await kv.list(KV.actions); - const freshMap = new Map(freshActions.map((a) => [a.id, a])); - if (!freshDeps.some((d) => { - const target = freshMap.get(d.targetActionId); - return !target || target.status !== "done"; - })) return false; - fresh.status = "blocked"; - fresh.updatedAt = (/* @__PURE__ */ new Date()).toISOString(); - await kv.set(KV.actions, fresh.id, fresh); - return true; - })) { - details.push(`Blocked action "${action.title}" (${action.id})`); - fixed++; - } else skipped++; - } - } - } - } - } - if (categories.includes("leases")) { - const leases = await kv.list(KV.leases); - const actions = await kv.list(KV.actions); - const actionIds = new Set(actions.map((a) => a.id)); - for (const lease of leases) { - if (lease.status === "active" && new Date(lease.expiresAt).getTime() <= now) { - if (dryRun) { - details.push(`[dry-run] Would expire lease ${lease.id} for action ${lease.actionId}`); - fixed++; - continue; - } - if (await withKeyedLock(`mem:action:${lease.actionId}`, async () => { - const fresh = await kv.get(KV.leases, lease.id); - if (!fresh || fresh.status !== "active" || new Date(fresh.expiresAt).getTime() > Date.now()) return false; - fresh.status = "expired"; - await kv.set(KV.leases, fresh.id, fresh); - const action = await kv.get(KV.actions, fresh.actionId); - if (action && action.status === "active" && action.assignedTo === fresh.agentId) { - action.status = "pending"; - action.assignedTo = void 0; - action.updatedAt = (/* @__PURE__ */ new Date()).toISOString(); - await kv.set(KV.actions, action.id, action); - } - return true; - })) { - details.push(`Expired lease ${lease.id} for action ${lease.actionId}`); - fixed++; - } else skipped++; - continue; - } - if (!actionIds.has(lease.actionId)) { - if (dryRun) { - details.push(`[dry-run] Would delete orphaned lease ${lease.id}`); - fixed++; - continue; - } - await kv.delete(KV.leases, lease.id); - details.push(`Deleted orphaned lease ${lease.id}`); - fixed++; - } - } - } - if (categories.includes("sentinels")) { - const sentinels = await kv.list(KV.sentinels); - for (const sentinel of sentinels) if (sentinel.status === "watching" && sentinel.expiresAt && new Date(sentinel.expiresAt).getTime() <= now) { - if (dryRun) { - details.push(`[dry-run] Would expire sentinel "${sentinel.name}" (${sentinel.id})`); - fixed++; - continue; - } - if (await withKeyedLock(`mem:sentinel:${sentinel.id}`, async () => { - const fresh = await kv.get(KV.sentinels, sentinel.id); - if (!fresh || fresh.status !== "watching") return false; - if (!fresh.expiresAt || new Date(fresh.expiresAt).getTime() > Date.now()) return false; - fresh.status = "expired"; - await kv.set(KV.sentinels, fresh.id, fresh); - return true; - })) { - details.push(`Expired sentinel "${sentinel.name}" (${sentinel.id})`); - fixed++; - } else skipped++; - } - } - if (categories.includes("sketches")) { - const sketches = await kv.list(KV.sketches); - for (const sketch of sketches) if (sketch.status === "active" && new Date(sketch.expiresAt).getTime() <= now) { - if (dryRun) { - details.push(`[dry-run] Would discard expired sketch "${sketch.title}" (${sketch.id})`); - fixed++; - continue; - } - if (await withKeyedLock(`mem:sketch:${sketch.id}`, async () => { - const fresh = await kv.get(KV.sketches, sketch.id); - if (!fresh || fresh.status !== "active" || new Date(fresh.expiresAt).getTime() > Date.now()) return false; - const allEdges = await kv.list(KV.actionEdges); - const actionIdSet = new Set(fresh.actionIds); - for (const edge of allEdges) if (actionIdSet.has(edge.sourceActionId) || actionIdSet.has(edge.targetActionId)) await kv.delete(KV.actionEdges, edge.id); - for (const actionId of fresh.actionIds) await kv.delete(KV.actions, actionId); - fresh.status = "discarded"; - fresh.discardedAt = (/* @__PURE__ */ new Date()).toISOString(); - await kv.set(KV.sketches, fresh.id, fresh); - return true; - })) { - details.push(`Discarded expired sketch "${sketch.title}" (${sketch.id})`); - fixed++; - } else skipped++; - } - } - if (categories.includes("signals")) { - const signals = await kv.list(KV.signals); - for (const signal of signals) if (signal.expiresAt && new Date(signal.expiresAt).getTime() <= now) { - if (dryRun) { - details.push(`[dry-run] Would delete expired signal ${signal.id}`); - fixed++; - continue; - } - await kv.delete(KV.signals, signal.id); - details.push(`Deleted expired signal ${signal.id}`); - fixed++; - } - } - if (categories.includes("memories")) { - const memories = await kv.list(KV.memories); - const supersededBy = /* @__PURE__ */ new Map(); - for (const memory of memories) if (memory.supersedes && memory.supersedes.length > 0) for (const sid of memory.supersedes) supersededBy.set(sid, memory.id); - for (const memory of memories) if (memory.isLatest && supersededBy.has(memory.id)) { - if (dryRun) { - details.push(`[dry-run] Would set isLatest=false on memory "${memory.title}" (${memory.id})`); - fixed++; - continue; - } - if (await withKeyedLock(`mem:memory:${memory.id}`, async () => { - const fresh = await kv.get(KV.memories, memory.id); - if (!fresh || !fresh.isLatest) return false; - fresh.isLatest = false; - fresh.updatedAt = (/* @__PURE__ */ new Date()).toISOString(); - await kv.set(KV.memories, fresh.id, fresh); - return true; - })) { - details.push(`Set isLatest=false on memory "${memory.title}" (${memory.id})`); - fixed++; - } else skipped++; - } - } - return { - success: true, - fixed, - skipped, - details - }; - }); -} - -//#endregion -export { registerDiagnosticsFunction }; -//# sourceMappingURL=diagnostics.mjs.map \ No newline at end of file diff --git a/plugin/scripts/notification.mjs b/plugin/scripts/notification.mjs deleted file mode 100755 index 3967158c..00000000 --- a/plugin/scripts/notification.mjs +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env node -import { execSync } from "node:child_process"; -import { basename } from "node:path"; - -//#region src/hooks/_project.ts -function resolveProject(cwd) { - const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]; - if (explicit && explicit.trim()) return explicit.trim(); - const dir = cwd && cwd.trim() ? cwd : process.cwd(); - try { - const top = execSync("git rev-parse --show-toplevel", { - cwd: dir, - stdio: [ - "ignore", - "pipe", - "ignore" - ], - timeout: 500 - }).toString().trim(); - if (top) return basename(top); - } catch {} - return basename(dir); -} - -//#endregion -//#region src/hooks/notification.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - const notificationType = data.notification_type ?? data.notificationType; - if (notificationType !== "permission_prompt") return; - const rawSessionId = data.session_id ?? data.sessionId; - const sessionId = typeof rawSessionId === "string" && rawSessionId.length > 0 ? rawSessionId : "unknown"; - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "notification", - sessionId, - project: resolveProject(data.cwd), - cwd: data.cwd || process.cwd(), - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - data: { - notification_type: notificationType, - title: data.title, - message: data.message - } - }), - signal: AbortSignal.timeout(2e3) - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=notification.mjs.map \ No newline at end of file diff --git a/plugin/scripts/post-commit.mjs b/plugin/scripts/post-commit.mjs deleted file mode 100755 index 8552cd61..00000000 --- a/plugin/scripts/post-commit.mjs +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env node -import { execFile } from "node:child_process"; -import { promisify } from "node:util"; - -//#region src/hooks/post-commit.ts -const exec = promisify(execFile); -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -const TIMEOUT_MS = 1500; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function git(args, cwd) { - try { - const { stdout } = await exec("git", args, { - cwd, - timeout: 1500 - }); - return stdout.trim(); - } catch { - return null; - } -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data = {}; - if (input.trim()) try { - data = JSON.parse(input); - } catch {} - if (isSdkChildContext(data)) return; - const cwd = data.cwd || process.env["AGENTMEMORY_CWD"] || process.cwd(); - const sessionId = data.session_id || process.env["AGENTMEMORY_SESSION_ID"] || void 0; - const sha = process.env["AGENTMEMORY_COMMIT_SHA"] || await git(["rev-parse", "HEAD"], cwd); - if (!sha) return; - const branch = await git([ - "rev-parse", - "--abbrev-ref", - "HEAD" - ], cwd); - const repo = await git([ - "config", - "--get", - "remote.origin.url" - ], cwd); - const message = await git([ - "log", - "-1", - "--pretty=%B", - sha - ], cwd); - const author = await git([ - "log", - "-1", - "--pretty=%an <%ae>", - sha - ], cwd); - const authoredAt = await git([ - "log", - "-1", - "--pretty=%aI", - sha - ], cwd); - const filesRaw = await git([ - "diff-tree", - "--no-commit-id", - "--name-only", - "-r", - sha - ], cwd); - const files = filesRaw ? filesRaw.split("\n").filter(Boolean) : void 0; - const body = { - sessionId, - sha, - branch: branch || void 0, - repo: repo || void 0, - message: message || void 0, - author: author || void 0, - authoredAt: authoredAt || void 0, - files - }; - try { - await fetch(`${REST_URL}/agentmemory/session/commit`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify(body), - signal: AbortSignal.timeout(TIMEOUT_MS) - }); - } catch {} -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=post-commit.mjs.map \ No newline at end of file diff --git a/plugin/scripts/post-tool-failure.mjs b/plugin/scripts/post-tool-failure.mjs deleted file mode 100755 index 6fdad8d9..00000000 --- a/plugin/scripts/post-tool-failure.mjs +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env node -import { execSync } from "node:child_process"; -import { basename } from "node:path"; - -//#region src/hooks/_project.ts -function resolveProject(cwd) { - const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]; - if (explicit && explicit.trim()) return explicit.trim(); - const dir = cwd && cwd.trim() ? cwd : process.cwd(); - try { - const top = execSync("git rev-parse --show-toplevel", { - cwd: dir, - stdio: [ - "ignore", - "pipe", - "ignore" - ], - timeout: 500 - }).toString().trim(); - if (top) return basename(top); - } catch {} - return basename(dir); -} - -//#endregion -//#region src/hooks/post-tool-failure.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - if (data.is_interrupt || data.isInterrupt) return; - const sessionId = data.session_id || data.sessionId || "unknown"; - const toolName = data.tool_name ?? data.toolName; - const toolInput = data.tool_input ?? data.toolArgs; - const error = data.error ?? data.errorMessage; - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "post_tool_failure", - sessionId, - project: resolveProject(data.cwd), - cwd: data.cwd || process.cwd(), - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - data: { - tool_name: toolName, - tool_input: typeof toolInput === "string" ? toolInput.slice(0, 4e3) : JSON.stringify(toolInput ?? "").slice(0, 4e3), - error: typeof error === "string" ? error.slice(0, 4e3) : JSON.stringify(error ?? "").slice(0, 4e3) - } - }), - signal: AbortSignal.timeout(3e3) - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=post-tool-failure.mjs.map \ No newline at end of file diff --git a/plugin/scripts/post-tool-use.mjs b/plugin/scripts/post-tool-use.mjs deleted file mode 100755 index b4aef9c9..00000000 --- a/plugin/scripts/post-tool-use.mjs +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env node -import { execSync } from "node:child_process"; -import { basename } from "node:path"; - -//#region src/hooks/_project.ts -function resolveProject(cwd) { - const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]; - if (explicit && explicit.trim()) return explicit.trim(); - const dir = cwd && cwd.trim() ? cwd : process.cwd(); - try { - const top = execSync("git rev-parse --show-toplevel", { - cwd: dir, - stdio: [ - "ignore", - "pipe", - "ignore" - ], - timeout: 500 - }).toString().trim(); - if (top) return basename(top); - } catch {} - return basename(dir); -} - -//#endregion -//#region src/hooks/post-tool-use.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - const sessionId = data.session_id || data.sessionId || "unknown"; - const toolName = data.tool_name ?? data.toolName; - const toolInput = data.tool_input ?? data.toolArgs; - const { imageData, cleanOutput } = extractImageData(toolOutput(data)); - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "post_tool_use", - sessionId, - project: resolveProject(data.cwd), - cwd: data.cwd || process.cwd(), - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - data: { - tool_name: toolName, - tool_input: toolInput, - tool_output: truncate(cleanOutput, 8e3), - ...imageData ? { image_data: imageData } : {} - } - }), - signal: AbortSignal.timeout(3e3) - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} -function toolOutput(data) { - if (data.tool_response !== void 0) return data.tool_response; - if (data.tool_output !== void 0) return data.tool_output; - const result = data.tool_result ?? data.toolResult; - if (typeof result === "object" && result !== null) { - const obj = result; - return obj.text_result_for_llm ?? obj.textResultForLlm ?? result; - } - return result; -} -function isBase64Image(val) { - return typeof val === "string" && (val.startsWith("data:image/") || val.startsWith("iVBORw0KGgo") || val.startsWith("/9j/")); -} -function extractImageData(output) { - if (isBase64Image(output)) return { - imageData: output, - cleanOutput: "[image data extracted]" - }; - if (typeof output === "object" && output !== null && !Array.isArray(output)) { - const obj = output; - let imageData; - const clean = {}; - for (const [key, val] of Object.entries(obj)) if (!imageData && isBase64Image(val)) { - imageData = val; - clean[key] = "[image data extracted]"; - } else clean[key] = val; - return { - imageData, - cleanOutput: clean - }; - } - return { - imageData: void 0, - cleanOutput: output - }; -} -function truncate(value, max) { - if (typeof value === "string" && value.length > max) return value.slice(0, max) + "\n[...truncated]"; - if (typeof value === "object" && value !== null) { - const str = JSON.stringify(value); - if (str.length > max) return str.slice(0, max) + "...[truncated]"; - return value; - } - return value; -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=post-tool-use.mjs.map \ No newline at end of file diff --git a/plugin/scripts/pre-compact.mjs b/plugin/scripts/pre-compact.mjs deleted file mode 100755 index 0afdcb0b..00000000 --- a/plugin/scripts/pre-compact.mjs +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env node -import { execSync } from "node:child_process"; -import { basename } from "node:path"; - -//#region src/hooks/_project.ts -function resolveProject(cwd) { - const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]; - if (explicit && explicit.trim()) return explicit.trim(); - const dir = cwd && cwd.trim() ? cwd : process.cwd(); - try { - const top = execSync("git rev-parse --show-toplevel", { - cwd: dir, - stdio: [ - "ignore", - "pipe", - "ignore" - ], - timeout: 500 - }).toString().trim(); - if (top) return basename(top); - } catch {} - return basename(dir); -} - -//#endregion -//#region src/hooks/pre-compact.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - const sessionId = data.session_id || data.sessionId || "unknown"; - const project = resolveProject(data.cwd); - if (process.env["CLAUDE_MEMORY_BRIDGE"] === "true") try { - await fetch(`${REST_URL}/agentmemory/claude-bridge/sync`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({}), - signal: AbortSignal.timeout(5e3) - }); - } catch {} - try { - const res = await fetch(`${REST_URL}/agentmemory/context`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - sessionId, - project, - budget: 1500 - }), - signal: AbortSignal.timeout(5e3) - }); - if (res.ok) { - const result = await res.json(); - if (result.context) process.stdout.write(result.context); - } - } catch {} -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=pre-compact.mjs.map \ No newline at end of file diff --git a/plugin/scripts/pre-tool-use.mjs b/plugin/scripts/pre-tool-use.mjs deleted file mode 100755 index d70c166e..00000000 --- a/plugin/scripts/pre-tool-use.mjs +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env node -//#region src/hooks/pre-tool-use.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const INJECT_CONTEXT = process.env["AGENTMEMORY_INJECT_CONTEXT"] === "true"; -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - if (!INJECT_CONTEXT) return; - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - const toolName = typeof data.tool_name === "string" ? data.tool_name : typeof data.toolName === "string" ? data.toolName : void 0; - if (!toolName) return; - const normalizedToolName = toolName.toLowerCase(); - if (![ - "edit", - "write", - "create", - "read", - "view", - "glob", - "grep" - ].includes(normalizedToolName)) return; - const rawToolInput = data.tool_input ?? data.toolArgs; - const toolInput = typeof rawToolInput === "object" && rawToolInput !== null && !Array.isArray(rawToolInput) ? rawToolInput : {}; - const files = []; - const fileKeys = normalizedToolName === "grep" ? ["path", "file"] : [ - "file_path", - "path", - "file", - "pattern" - ]; - for (const key of fileKeys) { - const val = toolInput[key]; - if (typeof val === "string" && val.length > 0) files.push(val); - } - if (files.length === 0) return; - const terms = []; - if (normalizedToolName === "grep" || normalizedToolName === "glob") { - const pattern = toolInput["pattern"]; - if (typeof pattern === "string" && pattern.length > 0) terms.push(pattern); - } - const rawSessionId = data.session_id || data.sessionId; - const sessionId = typeof rawSessionId === "string" && rawSessionId.length > 0 ? rawSessionId : "unknown"; - const project = typeof data.project === "string" && data.project.trim().length > 0 ? data.project.trim() : void 0; - try { - const res = await fetch(`${REST_URL}/agentmemory/enrich`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - sessionId, - files, - terms, - toolName, - ...project !== void 0 && { project } - }), - signal: AbortSignal.timeout(2e3) - }); - if (res.ok) { - const result = await res.json(); - if (result.context) process.stdout.write(result.context); - } - } catch {} -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=pre-tool-use.mjs.map \ No newline at end of file diff --git a/plugin/scripts/prompt-submit.mjs b/plugin/scripts/prompt-submit.mjs deleted file mode 100755 index 1a4147e6..00000000 --- a/plugin/scripts/prompt-submit.mjs +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env node -import { execSync } from "node:child_process"; -import { basename } from "node:path"; - -//#region src/hooks/_project.ts -function resolveProject(cwd) { - const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]; - if (explicit && explicit.trim()) return explicit.trim(); - const dir = cwd && cwd.trim() ? cwd : process.cwd(); - try { - const top = execSync("git rev-parse --show-toplevel", { - cwd: dir, - stdio: [ - "ignore", - "pipe", - "ignore" - ], - timeout: 500 - }).toString().trim(); - if (top) return basename(top); - } catch {} - return basename(dir); -} - -//#endregion -//#region src/hooks/prompt-submit.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - const sessionId = data.session_id || data.sessionId || "unknown"; - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "prompt_submit", - sessionId, - project: resolveProject(data.cwd), - cwd: data.cwd || process.cwd(), - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - data: { prompt: data.prompt ?? data.userPrompt } - }), - signal: AbortSignal.timeout(3e3) - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=prompt-submit.mjs.map \ No newline at end of file diff --git a/plugin/scripts/session-end.mjs b/plugin/scripts/session-end.mjs deleted file mode 100755 index 019149c3..00000000 --- a/plugin/scripts/session-end.mjs +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env node -//#region src/hooks/session-end.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - const sessionId = data.session_id || data.sessionId || "unknown"; - fetch(`${REST_URL}/agentmemory/session/end`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ sessionId }), - signal: AbortSignal.timeout(3e4) - }).catch(() => {}); - if (process.env["CONSOLIDATION_ENABLED"] === "true") { - fetch(`${REST_URL}/agentmemory/crystals/auto`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ olderThanDays: 0 }), - signal: AbortSignal.timeout(6e4) - }).catch(() => {}); - fetch(`${REST_URL}/agentmemory/consolidate-pipeline`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - tier: "all", - force: true - }), - signal: AbortSignal.timeout(12e4) - }).catch(() => {}); - } - if (process.env["CLAUDE_MEMORY_BRIDGE"] === "true") fetch(`${REST_URL}/agentmemory/claude-bridge/sync`, { - method: "POST", - headers: authHeaders(), - signal: AbortSignal.timeout(3e4) - }).catch(() => {}); - setTimeout(() => process.exit(0), 1500).unref(); -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=session-end.mjs.map \ No newline at end of file diff --git a/plugin/scripts/session-start.mjs b/plugin/scripts/session-start.mjs deleted file mode 100755 index 51b70eb4..00000000 --- a/plugin/scripts/session-start.mjs +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env node -import { execSync } from "node:child_process"; -import { basename } from "node:path"; - -//#region src/hooks/_project.ts -function resolveProject(cwd) { - const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]; - if (explicit && explicit.trim()) return explicit.trim(); - const dir = cwd && cwd.trim() ? cwd : process.cwd(); - try { - const top = execSync("git rev-parse --show-toplevel", { - cwd: dir, - stdio: [ - "ignore", - "pipe", - "ignore" - ], - timeout: 500 - }).toString().trim(); - if (top) return basename(top); - } catch {} - return basename(dir); -} - -//#endregion -//#region src/hooks/session-start.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const INJECT_CONTEXT = process.env["AGENTMEMORY_INJECT_CONTEXT"] === "true"; -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -const INJECT_TIMEOUT_MS = 1500; -const REGISTER_TIMEOUT_MS = 800; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - const sessionId = data.session_id || data.sessionId || `ses_${Date.now().toString(36)}`; - const cwd = data.cwd || process.cwd(); - const project = resolveProject(data.cwd); - const url = `${REST_URL}/agentmemory/session/start`; - const init = { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - sessionId, - project, - cwd - }) - }; - if (!INJECT_CONTEXT) { - fetch(url, { - ...init, - signal: AbortSignal.timeout(REGISTER_TIMEOUT_MS) - }).catch(() => {}); - return; - } - try { - const res = await fetch(url, { - ...init, - signal: AbortSignal.timeout(INJECT_TIMEOUT_MS) - }); - if (res.ok) { - const result = await res.json(); - if (result.context) process.stdout.write(result.context); - } - } catch {} -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=session-start.mjs.map \ No newline at end of file diff --git a/plugin/scripts/stop.mjs b/plugin/scripts/stop.mjs deleted file mode 100755 index 03d30c64..00000000 --- a/plugin/scripts/stop.mjs +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node -//#region src/hooks/stop.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - const sessionId = data.session_id || data.sessionId || "unknown"; - fetch(`${REST_URL}/agentmemory/summarize`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ sessionId }), - signal: AbortSignal.timeout(12e4) - }).catch(() => {}); - fetch(`${REST_URL}/agentmemory/session/end`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ sessionId }), - signal: AbortSignal.timeout(5e3) - }).catch(() => {}); - setTimeout(() => process.exit(0), 1500).unref(); -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=stop.mjs.map \ No newline at end of file diff --git a/plugin/scripts/subagent-start.mjs b/plugin/scripts/subagent-start.mjs deleted file mode 100755 index 2359a1c6..00000000 --- a/plugin/scripts/subagent-start.mjs +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env node -import { execSync } from "node:child_process"; -import { basename } from "node:path"; - -//#region src/hooks/_project.ts -function resolveProject(cwd) { - const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]; - if (explicit && explicit.trim()) return explicit.trim(); - const dir = cwd && cwd.trim() ? cwd : process.cwd(); - try { - const top = execSync("git rev-parse --show-toplevel", { - cwd: dir, - stdio: [ - "ignore", - "pipe", - "ignore" - ], - timeout: 500 - }).toString().trim(); - if (top) return basename(top); - } catch {} - return basename(dir); -} - -//#endregion -//#region src/hooks/subagent-start.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -const TIMEOUT_MS = 800; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - const sessionId = data.session_id || data.sessionId || "unknown"; - const agentId = data.agent_id || data.agentName; - const agentType = data.agent_type || data.agentDisplayName || data.agentName; - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "subagent_start", - sessionId, - project: resolveProject(data.cwd), - cwd: data.cwd || process.cwd(), - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - data: { - agent_id: agentId, - agent_type: agentType - } - }), - signal: AbortSignal.timeout(TIMEOUT_MS) - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=subagent-start.mjs.map \ No newline at end of file diff --git a/plugin/scripts/subagent-stop.mjs b/plugin/scripts/subagent-stop.mjs deleted file mode 100755 index 2ba1b002..00000000 --- a/plugin/scripts/subagent-stop.mjs +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env node -import { execSync } from "node:child_process"; -import { basename } from "node:path"; - -//#region src/hooks/_project.ts -function resolveProject(cwd) { - const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]; - if (explicit && explicit.trim()) return explicit.trim(); - const dir = cwd && cwd.trim() ? cwd : process.cwd(); - try { - const top = execSync("git rev-parse --show-toplevel", { - cwd: dir, - stdio: [ - "ignore", - "pipe", - "ignore" - ], - timeout: 500 - }).toString().trim(); - if (top) return basename(top); - } catch {} - return basename(dir); -} - -//#endregion -//#region src/hooks/subagent-stop.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - const sessionId = data.session_id || data.sessionId || "unknown"; - const agentId = data.agent_id || data.agentName; - const agentType = data.agent_type || data.agentDisplayName || data.agentName; - const lastMsg = typeof data.last_assistant_message === "string" ? data.last_assistant_message.slice(0, 4e3) : ""; - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "subagent_stop", - sessionId, - project: resolveProject(data.cwd), - cwd: data.cwd || process.cwd(), - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - data: { - agent_id: agentId, - agent_type: agentType, - last_message: lastMsg - } - }), - signal: AbortSignal.timeout(2e3) - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=subagent-stop.mjs.map \ No newline at end of file diff --git a/plugin/scripts/task-completed.mjs b/plugin/scripts/task-completed.mjs deleted file mode 100755 index 478f0688..00000000 --- a/plugin/scripts/task-completed.mjs +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env node -import { execSync } from "node:child_process"; -import { basename } from "node:path"; - -//#region src/hooks/_project.ts -function resolveProject(cwd) { - const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]; - if (explicit && explicit.trim()) return explicit.trim(); - const dir = cwd && cwd.trim() ? cwd : process.cwd(); - try { - const top = execSync("git rev-parse --show-toplevel", { - cwd: dir, - stdio: [ - "ignore", - "pipe", - "ignore" - ], - timeout: 500 - }).toString().trim(); - if (top) return basename(top); - } catch {} - return basename(dir); -} - -//#endregion -//#region src/hooks/task-completed.ts -function isSdkChildContext(payload) { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return payload.entrypoint === "sdk-ts"; -} -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -function authHeaders() { - const h = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} -async function main() { - let input = ""; - for await (const chunk of process.stdin) input += chunk; - let data; - try { - data = JSON.parse(input); - } catch { - return; - } - if (isSdkChildContext(data)) return; - const sessionId = data.session_id || "unknown"; - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "task_completed", - sessionId, - project: resolveProject(data.cwd), - cwd: data.cwd || process.cwd(), - timestamp: (/* @__PURE__ */ new Date()).toISOString(), - data: { - task_id: data.task_id, - task_subject: data.task_subject, - task_description: typeof data.task_description === "string" ? data.task_description.slice(0, 2e3) : "", - teammate_name: data.teammate_name, - team_name: data.team_name - } - }), - signal: AbortSignal.timeout(2e3) - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} -main(); - -//#endregion -export { }; -//# sourceMappingURL=task-completed.mjs.map \ No newline at end of file diff --git a/plugin/skills/ask-user/SKILL.md b/plugin/skills/ask-user/SKILL.md deleted file mode 100644 index 94bf5508..00000000 --- a/plugin/skills/ask-user/SKILL.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: ask-user -description: Post a blocking question to the user's workbench inbox when you need a decision you cannot make yourself and the user is away. Use ONLY when work is genuinely blocked on a human decision (irreversible/destructive action, ambiguous requirement with diverging paths, missing credential or external resource) AND the user has not responded inline. Not for routine clarifications you can resolve by reading code or picking a sensible default. -user-invocable: false ---- - -You are blocked on a decision only the user can make, and the user is not responding inline. Deliver the question to their workbench inbox so they see it when they return, instead of stalling or guessing. - -## When to use this (high bar — protect the inbox from noise) - -Post to the inbox ONLY when ALL of these hold: -1. **You are actually blocked.** You cannot make meaningful progress on the task without this answer. If you can keep working on other parts, do that first and batch the question. -2. **It is the user's call, not yours.** The decision is irreversible or destructive (deleting data, force-push, dropping a feature, changing prod), OR a genuinely ambiguous requirement with materially diverging paths, OR needs something only the user has (a credential, an external resource, a business preference). Choices with a reasonable default are NOT this — pick the default and note it. -3. **Inline asking already failed or is impossible.** The user hasn't replied in this turn, or the session is autonomous/background and there is no one watching the terminal. - -If any of these is false, do NOT post. Resolve it yourself: read the code, follow existing conventions, pick the obvious option and state your assumption. - -## How to post - -Call the `memory_inbox_ask` MCP tool: -- `body` — the question in Markdown. State it so the user can answer in one line. Include: what you're blocked on, the 2-3 concrete options with their tradeoffs, and your recommendation. Don't make the user reconstruct context. -- `fromAgent` — a short label for the work this came from (e.g. `auth-refactor`), so the inbox shows "来自 …". -- `project` — the repo/project path when known. -- `sourceObservationIds` — if the question stems from specific observations (a failing test, a file you read), pass their IDs so the user can click "看原文 →" to the evidence. - -A good question is self-contained and decision-shaped: - -> `/admin/*` 路由要不要也加鉴权?我改完了 `/api/*`(加了 JWT 中间件),但 `/admin/*` 你之前没提。 -> - **加**(推荐):与 `/api/*` 一致,管理端更该锁。 -> - **不加**:若 admin 只在内网/有别的网关挡着。 -> 倾向「加」,等你一句话。 - -After posting, tell the user briefly that you've queued the question to their workbench inbox and what you'll do meanwhile (keep working elsewhere, or stop if fully blocked). Do not spin in place waiting. - -## What NOT to ask - -- Things you can answer by reading the codebase or running a command. -- Style/naming/formatting and other choices with a conventional default — pick it, mention it. -- "Should I proceed?" / "Is this okay?" busywork. Only post real blocking decisions. -- Multiple trivial questions as separate items — batch into one if they're related. - -### 禁止触发的具体反例(评测校准,务必照此收紧) - -这些**绝不该**发到收件箱 —— 它们会淹没真正要紧的问题,直接自己处理: - -- ❌「新模块变量命名用 camelCase 还是 snake_case?」→ 有约定/跟随现有代码风格,自己定。 -- ❌「我改完登录页样式了,继续做注册页吗?」→ 典型 "我继续吗" busywork,继续做就是。 -- ❌「README 第 3 段有个 typo,要修吗?」→ 琐碎且可逆,直接修。 - -对照**该发**的样子(全部满足高门槛三条):不可逆操作(删生产数据)、真歧义且实现分叉大(导出格式 CSV vs JSON)、需要你独有的资源(缺 `STRIPE_SECRET_KEY`)、影响面巨大的架构选择(换状态库动 40 文件)。拿不准时:能自己定的默认就定 + 说明,只把**真卡住、真归你拍板**的发出来。 - -If `memory_inbox_ask` isn't available, the agentmemory MCP server didn't start: fall back to `POST $AGENTMEMORY_URL/agentmemory/inbox/ask` with body `{ "body": "...", "fromAgent": "...", "project": "..." }` and `Authorization: Bearer $AGENTMEMORY_SECRET` when set. If neither works, ask inline as a last resort. diff --git a/plugin/skills/commit-context/SKILL.md b/plugin/skills/commit-context/SKILL.md deleted file mode 100644 index 9ec0dae3..00000000 --- a/plugin/skills/commit-context/SKILL.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: commit-context -description: Trace a file, function, or line back to the agent session that produced its current commit. Use when the user asks "why is this code here", "what was the agent doing when this changed", or wants context on a specific location in the codebase. -argument-hint: "[file, function, or line]" -user-invocable: true ---- - -The user wants commit context for: $ARGUMENTS - -Run `git blame` (or `git log -L`) on the target file, function, or line in $ARGUMENTS to extract the most recent commit SHA that touched it. Use `git blame -L , ` when a line range is given, `git log -L ::` when a function name is given, and `git log -n 1 -- ` when only a path is given. - -With the SHA in hand, look up the linked agent session via the `memory_commit_lookup` MCP tool with `sha: ""`. If the MCP tool is unavailable, fall back to HTTP: `GET $AGENTMEMORY_URL/agentmemory/session/by-commit?sha=` with `Authorization: Bearer $AGENTMEMORY_SECRET` when the secret is set. - -Present the result as: -- The commit SHA, short SHA, branch, author, message -- The linked session(s): id, project, started/ended timestamps, observation count, summary if any -- A short list of the most important observations from that session (importance >= 7) when available via `memory_recall` - -Do not fabricate intent. If the commit has no linked session, say so plainly and surface only what `git show` reveals. If `memory_commit_lookup` returns an empty `commit: null` body, that means the commit predates session linking — do not invent a session. diff --git a/plugin/skills/commit-history/SKILL.md b/plugin/skills/commit-history/SKILL.md deleted file mode 100644 index 599c3220..00000000 --- a/plugin/skills/commit-history/SKILL.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: commit-history -description: List recent git commits that are linked to agent sessions, optionally filtered by branch or repo. Use when the user asks "show agent commits", "what has the agent shipped", or wants a list of commits with their session context. -argument-hint: "[branch=... repo=... limit=...]" -user-invocable: true ---- - -The user wants a list of agent-linked commits. Filter args: $ARGUMENTS - -Parse `$ARGUMENTS` for optional `branch=`, `repo=`, and `limit=` tokens. A bare numeric token becomes the limit. Defaults: no branch filter, no repo filter, limit 100, max 500. - -Call the `memory_commits` MCP tool with the parsed filters. If the MCP tool is unavailable, fall back to HTTP: build `GET $AGENTMEMORY_URL/agentmemory/commits` and append each filter as a URL-encoded query parameter (use `URLSearchParams` or `encodeURIComponent` on `branch`, `repo`, and `limit`) so values containing `?`, `&`, or `#` cannot corrupt the request. Include `Authorization: Bearer $AGENTMEMORY_SECRET` when set. - -Render the result as a reverse-chronological list: -- Short SHA, branch, authored timestamp -- Commit message first line -- Linked session id(s) (first 8 chars each) and observation counts where present -- File count when `files` is provided - -If the result is empty, tell the user the filter matched no commits and suggest dropping the branch/repo filter. Do not invent commits. diff --git a/plugin/skills/handoff/SKILL.md b/plugin/skills/handoff/SKILL.md deleted file mode 100644 index 63437001..00000000 --- a/plugin/skills/handoff/SKILL.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: handoff -description: Resume the most recent agent session for the current working directory. Use when the user says "where were we", "resume", "handoff", "pick up where I left off", or starts a session with no fresh context. -argument-hint: "[optional cwd override]" -user-invocable: true ---- - -The user wants to resume work. Optional cwd override: $ARGUMENTS - -Determine the current project path: if `$ARGUMENTS` is provided, resolve it to an absolute, normalized path (accept relative inputs, e.g. `path.resolve(process.cwd(), $ARGUMENTS)`); otherwise use the current working directory. - -Call the `memory_sessions` MCP tool. From the result, pick the most recent session whose normalized `cwd` matches the project path with a directory-boundary check — equality OR `session.cwd.startsWith(projectPath + path.sep)` OR `projectPath.startsWith(session.cwd + path.sep)`. Do NOT use a raw string prefix match: it produces false positives across unrelated repos that share a path prefix (e.g. `/repo-a` vs `/repo-a-staging`). Prefer sessions with status `completed` over `abandoned`. If nothing matches, fall back to the single most recent session overall. - -Once a session is selected: -1. If the session ended on an unanswered user-facing question, surface that question FIRST as the lead. Look for it in `summary` or in the last few observations (type `conversation` with `narrative` ending in `?`). -2. Then summarize the session: title/summary, key files touched, key decisions or errors. Use `memory_recall` with a query derived from the session's top concepts to fetch supporting observations, limit 10. -3. End with a short "next step?" pointer the user can act on. - -If neither MCP tool is available, fall back to HTTP: `GET $AGENTMEMORY_URL/agentmemory/sessions` and `POST $AGENTMEMORY_URL/agentmemory/recall` with `Authorization: Bearer $AGENTMEMORY_SECRET` when set. - -Do not invent observations. If the most recent session has zero observations, say so and offer to start fresh. diff --git a/plugin/skills/open-workbench/SKILL.md b/plugin/skills/open-workbench/SKILL.md deleted file mode 100644 index 976ea724..00000000 --- a/plugin/skills/open-workbench/SKILL.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: open-workbench -description: Open the local Agent Memory Lab viewer or tell the user the exact local URL. Use when the user asks to open memory, dashboard, viewer, workbench, Agent Memory Lab, or wants to inspect memories, sessions, skills, actions, or activity. -argument-hint: "[dashboard|memories|sessions|skills|actions|activity]" -user-invocable: true ---- - -The user wants to open Agent Memory Lab: $ARGUMENTS - -Choose the target tab from `$ARGUMENTS`: -- `dashboard`, `home`, `overview`, empty -> `dashboard` -- `memory`, `memories` -> `memories` -- `session`, `sessions`, `timeline` -> `sessions` -- `skill`, `skills`, `plugin` -> `lessons` -- `todo`, `action`, `actions` -> `actions` -- `activity` -> `activity` - -Open or share this URL: - -```text -http://localhost:3114/# -``` - -If browser control is available, open the URL directly. If not, tell the user the exact URL and say to start the service first if it is unreachable: - -```bash -cd /Users/szn/agentmemory -npm run start:local-memory -``` - -Do not expose graph/audit/replay/profile as primary destinations; those are implementation/debug surfaces in this product direction. diff --git a/plugin/skills/organize-todos/SKILL.md b/plugin/skills/organize-todos/SKILL.md deleted file mode 100644 index 03c06efe..00000000 --- a/plugin/skills/organize-todos/SKILL.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -name: organize-todos -description: When a work session winds down, proactively summarize what you followed up on and what's still owed, then post a single briefing to the user's workbench inbox. Use when a multi-step task reaches a natural stopping point (feature shipped, batch of fixes done, session ending) so the user gets one tidy rollup instead of scrolling the transcript. Not for trivial one-step tasks or mid-flight chatter. -user-invocable: false ---- - -A stretch of work just reached a natural stopping point. Instead of leaving the user to scroll the transcript, post ONE tidy briefing to their workbench inbox: what you followed up on, what's done, what's still owed. - -## When to use this (high bar — one signal, not a stream) - -Post a briefing ONLY when: -1. **The work was non-trivial** — a multi-step task, a feature, a batch of fixes. A one-line edit or a pure Q&A does not warrant a briefing. -2. **It reached a natural stopping point** — feature shipped, PR opened, batch complete, or the session is ending. Not mid-flight. -3. **There is something worth telling** — concrete progress and/or remaining items. If nothing happened, don't post. - -Post **at most one** briefing per stopping point. Never post a running commentary — that buries the signal. - -## What goes in the briefing - -Call the `memory_inbox_notify` MCP tool: -- `body` — a short Markdown rollup. Lead with a one-line summary, then a tight list. Cover: - - ✅ **完成** — what you actually finished (with PR/file refs where useful). - - ⏳ **还欠着** — what's started-but-not-done or deferred, so nothing silently drops. - - ⚠️ **需你定** — anything you had to assume or that needs the user's eventual sign-off (but if it's *blocking*, use `ask-user` instead — a briefing is "知悉即可", not "在等你回"). -- `fromAgent` — a short label for the work (e.g. `line-c-c1`). -- `project` — the repo/project path when known. -- `sourceObservationIds` — IDs of key observations (PRs, commits, test runs) so the user can click "看原文 →". - -A good briefing is skimmable in five seconds: - -> 今天跟进了 C1.5(Agent 接入点 skill): -> - ✅ 完成:`ask-user` + `organize-todos` 两个 skill,PR#NN 已开。 -> - ⏳ 还欠着:viewer 接真数据(C2)未动。 -> - ⚠️ 需你定:skill 触发判据的措辞,开 PR 时帮看一眼。 - -## What NOT to do - -- Don't notify on every tool call or mid-task — only at a real stopping point. -- Don't restate the whole transcript; the user followed along. Summarize the outcome. -- Don't use this for blocking questions — those go through `ask-user` as `question`, not `briefing`. -- Don't pad. If there's nothing material to report, stay silent. - -### 禁止触发的具体反例(评测校准,务必照此收紧) - -这些**绝不该**发成 briefing —— 它们会把收件箱变成 transcript / 噪音: - -- ❌ 流水账复述全程:「我先打开了 auth.ts,然后看了 middleware.ts,接着 grep…改第一个时报错,查文档,修好跑测试又报错…」→ 用户不需要过程,只要结果。最碍眼的一类。 -- ❌ 无料状态:「跑了下测试,过了。」→ 没有实质进展可报,这只配会话末尾顺口一句,不配进收件箱。 -- ❌ mid-flight:「正在改第 3 个文件,还有 2 个没改完,稍后继续。」→ 没到自然停顿点,别推。 - -对照**合格**的样子:到了自然停顿点(功能交付/PR 开/一批修完),五秒可读,有 ✅完成 / ⏳还欠着 / ⚠️需你定 的实质内容。每个停顿点至多一条。宁可不发,也不要发流水账或无料汇报。 - -This briefing is `kind: briefing` (知悉即可,可一键已读), distinct from `ask-user`'s `kind: question` (Agent 在等你回). Keep the two roles clean. - -If `memory_inbox_notify` isn't available, fall back to `POST $AGENTMEMORY_URL/agentmemory/inbox/notify` with body `{ "body": "...", "fromAgent": "...", "project": "..." }` and `Authorization: Bearer $AGENTMEMORY_SECRET` when set. diff --git a/plugin/skills/recap/SKILL.md b/plugin/skills/recap/SKILL.md deleted file mode 100644 index eec5acc4..00000000 --- a/plugin/skills/recap/SKILL.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: recap -description: Summarize the last N agent sessions for the current project, grouped by date. Use when the user asks "recap", "what have we been doing", "this week", "today", or wants a rollup of recent work. -argument-hint: "[last N | today | this week]" -user-invocable: true ---- - -The user wants a recap. Time window args: $ARGUMENTS - -Parse `$ARGUMENTS` to determine the window: -- `today` -> sessions started on the current local date -- `this week` -> sessions started in the last 7 days -- `last ` -> the most recent N sessions -- bare numeric -> treat as `last ` -- empty -> default to `last 10` - -Call the `memory_sessions` MCP tool, then filter to the current project (match by `cwd` against the working directory). Apply the time window. Sort by `startedAt` descending. - -Group the surviving sessions by their local calendar date (YYYY-MM-DD). For each date: -- List each session: id (first 8 chars), title or first prompt, observation count, status -- Indent two or three highlight observations per session (importance >= 7) drawn from `memory_recall` with a per-session query, limit 3 - -End with a one-line total: "N sessions across M days, K observations." - -If MCP tools are unavailable, fall back to HTTP: `GET $AGENTMEMORY_URL/agentmemory/sessions` and `POST $AGENTMEMORY_URL/agentmemory/recall` with `Authorization: Bearer $AGENTMEMORY_SECRET` when set. Do not invent sessions; if the window is empty, say so. diff --git a/plugin/skills/session-history/SKILL.md b/plugin/skills/session-history/SKILL.md deleted file mode 100644 index c33a1fe5..00000000 --- a/plugin/skills/session-history/SKILL.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: session-history -description: Show what happened in recent past sessions on this project. Use when user asks "what did we do last time", "session history", "past sessions", or wants an overview of previous work. -user-invocable: true ---- - -Fetch recent session history using the `memory_sessions` MCP tool (provided by the agentmemory server that this plugin wires up automatically via `.mcp.json`). Pass `limit: 20` to get a meaningful window. - -Present the returned sessions in reverse chronological order: -- Show the session ID (first 8 chars), project, start time, and status -- For each session with observations, show the key highlights (type + title) -- Note the total observation count per session -- If a session summary exists, surface the title and the key decisions - -Format as a clean timeline. **Do NOT make up sessions** — only show what the MCP tool actually returned. If `memory_sessions` isn't available, the stdio MCP shim didn't start — tell the user to: -1. Run `/plugin list` in Claude Code and confirm `agentmemory` shows as enabled. -2. Restart Claude Code (the plugin's `.mcp.json` is only read on startup). -3. Check `/mcp` to see whether the `agentmemory` MCP server is connected. diff --git a/plugin/skills/sync-session/SKILL.md b/plugin/skills/sync-session/SKILL.md deleted file mode 100644 index 5f74fa15..00000000 --- a/plugin/skills/sync-session/SKILL.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: sync-session -description: Extract reviewable memories, lessons, and follow-up actions from the current conversation. Use when the user asks to update memory automatically, sync this chat, summarize what should be remembered, or keep lessons up to date. -argument-hint: "[what to sync or focus on]" -user-invocable: true ---- - -The user wants Agent Memory Lab to sync the current session: $ARGUMENTS - -Act as a careful memory curator. Do not save everything. Extract only durable, reusable items. - -Produce three groups: - -1. `记忆线索` — stable facts/preferences/project context that should be remembered later. -2. `可沉淀经验` — reusable process lessons, design principles, workflow rules, or mistakes to avoid. -3. `待跟进` — concrete next actions that should appear in Actions/Todo. - -For each item include: -- short title -- content -- why it is durable -- privacy risk: low / medium / high -- suggested destination: memory / lesson / action / do-not-save - -Then save only low-risk, clearly durable items: -- Use `memory_save` for stable memories. -- Use `memory_lesson_save` for reusable lessons. -- Use `memory_action_create` for clear next actions when available. - -If the relevant MCP tool is unavailable, give the user the structured review list and say it was not saved yet. Never claim sync completed unless a save tool returned success. - -Privacy rules: -- Do not save secrets, tokens, private credentials, or sensitive personal details unless the user explicitly asks. -- If the item contains personal identity, school, birthday, relationship, health, finance, or private path details, mark it medium/high risk and ask before saving. -- For public README/GitHub/Feishu materials, prefer product-level summaries over private screenshots. diff --git a/public/app.css b/public/app.css new file mode 100644 index 00000000..f0ac6df7 --- /dev/null +++ b/public/app.css @@ -0,0 +1,1066 @@ +:root { + --bg: #f5f5f5; + --bg-alt: #fafafa; + --bg-subtle: #f0efed; + --bg-inset: #f0efed; + --surface: #ffffff; + --surface-card: #ffffff; + --border: #d6d3d1; + --border-light: #e7e5e4; + --border-heavy: #a8a29e; + --ink: #0c0a09; + --ink-secondary: #4e4e4e; + --ink-muted: #777169; + --ink-faint: #a8a29e; + --accent: #292524; + --green: #2d6a4f; + --blue: #1d4e89; + --yellow: #b8860b; + --red: #cc0000; + --purple: #6b3fa0; + --orange: #c2410c; + --cyan: #0e7490; + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 18px; + --radius-xl: 24px; + --radius-xxl: 32px; + --radius-pill: 999px; + --font-display: Georgia, "Times New Roman", serif; + --font-body: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-ui: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-mono: "SF Mono", "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace; +} + +* { box-sizing: border-box; margin: 0; padding: 0; } + +html { height: 100%; } + +body { + margin: 0; + height: 100vh; + display: flex; + overflow: hidden; + background: + radial-gradient(circle at 18% 8%, rgba(45, 106, 79, 0.14), transparent 28%), + radial-gradient(circle at 88% 18%, rgba(29, 78, 137, 0.12), transparent 26%), + linear-gradient(135deg, var(--bg), var(--bg-alt) 48%, var(--bg-subtle)); + color: var(--ink-secondary); + font-family: var(--font-body); + line-height: 1.6; +} + +::-webkit-scrollbar { width: 6px; height: 6px; } +::-webkit-scrollbar-track { background: var(--bg); } +::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 0; } +::-webkit-scrollbar-thumb:hover { background: var(--ink-muted); } + +button, +input, +select { + font: inherit; +} + +button { + border: 1px solid var(--border); + background: var(--surface); + color: var(--ink); + cursor: pointer; + padding: 7px 10px; + border-radius: var(--radius-pill); + min-height: 40px; +} + +button:hover, +button:focus-visible { + border-color: var(--accent); +} + +button.primary { + background: var(--ink); + color: var(--surface); + border-color: var(--ink); +} + +button.primary.is-busy { + cursor: wait; + opacity: 0.82; +} + +button.primary.is-busy::before { + content: ""; + display: inline-block; + width: 0.85em; + height: 0.85em; + margin-right: 7px; + border: 2px solid rgb(255 255 255 / 0.45); + border-top-color: var(--surface); + border-radius: 50%; + vertical-align: -0.1em; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +button.ghost { + border-color: transparent; + background: transparent; + color: var(--ink-muted); +} + +button.ghost:hover, +button.ghost:focus-visible { + border-color: var(--border-light); + color: var(--ink); + background: var(--bg-alt); +} + +.tabs { + width: 148px; + flex: 0 0 auto; + border-right: 1px solid var(--border-light); + background: rgb(255 255 255 / 0.56); + backdrop-filter: blur(18px); + overflow: hidden; + padding: 22px 16px; +} + +.tab-main { + display: grid; + gap: 8px; +} + +.tabs button { + width: 100%; + border: 0; + border-left: 0; + border-radius: var(--radius-pill); + background: transparent; + color: var(--ink-muted); + display: flex; + align-items: center; + gap: 10px; + font-size: 15px; + font-weight: 600; + letter-spacing: 0; + padding: 10px 12px; + text-align: left; + text-transform: none; + transition: color 0.15s, border-color 0.15s; +} + +.tabs button:hover { color: var(--ink); } + +.tabs button.active { + background: var(--surface); + border-left-color: transparent; + box-shadow: 0 10px 24px rgba(12, 10, 9, 0.08); + color: var(--ink); +} + +.tab-icon { + width: 18px; + height: 18px; + display: inline-block; + flex: 0 0 18px; +} + +.tab-icon svg { + display: block; + width: 100%; + height: 100%; + stroke: currentColor; +} + +.shell { + flex: 1; + min-width: 0; + overflow-y: auto; + padding: 32px; +} + +.topbar { + display: flex; + justify-content: space-between; + gap: 16px; + margin-bottom: 32px; +} + +h1, +h2, +h3 { + color: var(--ink); + line-height: 1.15; +} + +h1 { + font-family: var(--font-display); + font-size: 28px; + font-weight: 500; +} + +h2 { + font-family: var(--font-display); + font-size: 28px; + font-weight: 300; + margin-bottom: 14px; +} + +h3 { + font-size: 14px; + font-weight: 650; + margin: 20px 0 8px; +} + +#status { + color: var(--ink-muted); + font-size: 13px; + margin: 6px 0 0; +} + +.actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: flex-end; +} + +.view { display: none; } +.view.active { display: block; } + +.stats-grid { + display: grid; + gap: 0; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + margin-bottom: 24px; + border: 1px solid var(--border-light); + border-radius: var(--radius-xl); + background: rgb(255 255 255 / 0.72); + overflow: hidden; +} + +.stat, +.row, +dialog { + background: var(--surface); + border: 1px solid var(--border-light); + border-radius: var(--radius-md); +} + +.stat { + padding: 24px; +} + +.stats-grid .stat-card { + border: 0; + border-right: 1px solid var(--border-light); + border-radius: 0; + background: transparent; +} + +.label { + color: var(--ink-muted); + font-size: 9px; + font-weight: 600; + letter-spacing: 0; + text-transform: none; +} + +.value { + color: var(--ink); + font-family: var(--font-display); + font-size: 44px; + font-weight: 300; + line-height: 1.1; + margin-top: 4px; +} + +.sub { + color: var(--ink-faint); + font-family: var(--font-mono); + font-size: 12px; + margin-top: 4px; +} + +.list { + display: grid; + gap: 8px; +} + +.row { + padding: 12px; +} + +.row:hover { + border-color: var(--border); +} + +.row-head { + align-items: baseline; + display: flex; + gap: 10px; + justify-content: space-between; +} + +.title { + color: var(--ink); + font-weight: 650; + line-height: 1.35; +} + +.meta { + color: var(--ink-faint); + font-family: var(--font-mono); + font-size: 11px; + overflow-wrap: anywhere; +} + +.desc { + margin-top: 6px; + color: var(--ink-secondary); + overflow-wrap: anywhere; +} + +.badges { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 8px; +} + +.badge { + border: 1px solid var(--border-light); + border-radius: var(--radius-pill); + color: var(--ink-muted); + display: inline-block; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.04em; + padding: 3px 9px; +} + +.badge.todo { border-color: var(--blue); color: var(--blue); } +.badge.done { border-color: var(--green); color: var(--green); } +.badge.ignored { border-color: var(--red); color: var(--red); } + +.todo-card { + display: grid; + grid-template-columns: 6px minmax(0, 1fr) auto; + align-items: start; + gap: 12px; + padding: 16px; +} + +.action-group { + display: grid; + gap: 12px; +} + +.action-card-list { + display: grid; + gap: 10px; +} + +.action-item-card { + background: rgb(255 255 255 / 0.82); + border-radius: var(--radius-lg); + box-shadow: 0 16px 32px rgba(12, 10, 9, 0.06); +} + +.priority-rail { + width: 6px; + align-self: stretch; + min-height: 54px; + border-radius: var(--radius-pill); + background: var(--border); +} + +.priority-rail.todo { background: var(--accent); } +.priority-rail.done { background: var(--green); } +.priority-rail.ignored { background: var(--ink-faint); } + +.todo-main { min-width: 0; } + +.todo-title { + color: var(--ink); + font-size: 15px; + font-weight: 650; + line-height: 1.35; +} + +.todo-desc { + margin-top: 5px; + color: var(--ink-faint); + font-size: 12px; + line-height: 1.45; +} + +.todo-meta { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 10px; +} + +.todo-actions { + display: inline-flex; + align-items: center; + gap: 6px; + white-space: nowrap; +} + +.action-secondary { + display: inline-flex; + align-items: center; + gap: 4px; + opacity: 0; + pointer-events: none; + transition: opacity 0.12s ease; +} + +.todo-card:hover .action-secondary, +.todo-card:focus-within .action-secondary { + opacity: 1; + pointer-events: auto; +} + +.evidence-link, +.btn-ghost-sm, +.btn-primary-sm { + font-size: 12px; + line-height: 1.3; + padding: 4px 10px; +} + +.evidence-link, +.btn-ghost-sm { + background: none; + border-color: transparent; + color: var(--ink-muted); +} + +.evidence-link:hover, +.btn-ghost-sm:hover { + background: var(--bg-alt); + border-color: var(--border-light); + color: var(--ink); +} + +.btn-primary-sm { + background: var(--ink); + border-color: var(--ink); + color: #fff; +} + +.empty { + border: 1px dashed var(--border); + color: var(--ink-muted); + padding: 18px; + text-align: center; +} + +.startup-scan { + margin-bottom: 18px; +} + +.session-hero { + align-items: center; + background: rgb(255 255 255 / 0.72); + border: 1px solid var(--border-light); + border-radius: var(--radius-xl); + display: flex; + gap: 16px; + justify-content: space-between; + margin-bottom: 16px; + padding: 20px 22px; +} + +.session-inbox { + display: grid; + grid-template-columns: minmax(190px, 240px) minmax(0, 1fr); + gap: 14px; + align-items: start; +} + +.session-inbox-head { + align-items: center; + border-bottom: 1px solid var(--border-light); + display: flex; + justify-content: space-between; + padding: 18px 20px; +} + +.session-inbox-main { + min-width: 0; + display: grid; + background: rgb(255 255 255 / 0.84); + border: 1px solid var(--border-light); + border-radius: var(--radius-xl); + overflow: hidden; +} + +.session-source-rail { + position: sticky; + top: 20px; + display: grid; + gap: 6px; + padding: 8px; + border: 1px solid var(--border-light); + border-radius: var(--radius-xl); + background: rgb(255 255 255 / 0.82); +} + +.session-source-label { + padding: 4px 10px 6px; + color: var(--ink-faint); + font-size: 11px; + font-weight: 650; +} + +.session-source-chip { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px; + width: 100%; + min-height: 42px; + padding: 8px 10px; + border: 0; + border-radius: var(--radius-sm); + background: transparent; + color: var(--ink-muted); + text-align: left; +} + +.session-source-chip.active, +.session-source-chip:hover { + background: var(--bg-alt); + color: var(--ink); +} + +.session-source-chip:disabled { cursor: default; } + +.session-source-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 13px; + font-weight: 650; +} + +.session-source-count { + color: var(--ink-faint); + font-family: var(--font-mono); + font-size: 11px; +} + +.folder-hover-preview { + grid-column: 1 / -1; + color: var(--ink-muted); + font-size: 12px; + line-height: 1.45; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.session-row { + display: grid; + grid-template-columns: 34px minmax(0, 1fr) auto; + gap: 12px; + padding: 14px 16px; + border-bottom: 1px solid var(--border-light); +} + +.session-row:last-child { + border-bottom: 0; +} + +.source-rail { + border-radius: var(--radius-pill); + background: var(--border-heavy); +} + +.source-rail.codex, +.source-rail.user, +.source-rail.browser { background: var(--blue); } +.source-rail.claude-code, +.source-rail.assistant { background: var(--green); } + +.session-row-avatar { + width: 30px; + height: 30px; + border-radius: var(--radius-pill); + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--bg-subtle); + color: var(--ink-muted); + font-size: 11px; + font-weight: 700; +} + +.session-row-avatar.user { + background: rgba(29, 78, 137, 0.11); + color: var(--blue); +} + +.session-row-avatar.assistant { + background: rgba(45, 106, 79, 0.12); + color: var(--green); +} + +.session-row-preview { + margin-top: 3px; + color: var(--ink-secondary); + font-size: 13px; + line-height: 1.45; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + overflow-wrap: anywhere; +} + +.session-row-meta, +.session-row-side { + display: flex; + gap: 8px; + color: var(--ink-faint); + font-family: var(--font-mono); + font-size: 11px; + margin-top: 5px; +} + +.session-row-side { + margin-top: 0; + white-space: nowrap; + align-items: center; +} + +.session-overview-card { + border: 1px solid var(--border-light); + border-radius: var(--radius-lg); + background: var(--surface); + padding: 10px 12px; +} + +.session-overview-label { + color: var(--ink-muted); + font-size: 12px; + font-weight: 600; +} + +.session-overview-value { + margin-top: 4px; + color: var(--ink); + font-family: var(--font-display); + font-size: 24px; + line-height: 1; + font-weight: 300; +} + +.conversation-text { white-space: pre-wrap; } + +.observation-card { + scroll-margin: 80px; +} + +.obs-jump-highlight { + animation: obs-jump-pulse 2.4s ease-out; + border-color: var(--accent); + border-radius: 8px; +} + +@keyframes obs-jump-pulse { + 0% { background: rgba(41, 37, 36, 0.12); box-shadow: 0 0 0 4px rgba(41, 37, 36, 0.12); } + 100% { background: transparent; box-shadow: 0 0 0 4px transparent; } +} + +label { + color: var(--ink); + display: grid; + font-size: 13px; + font-weight: 650; + gap: 6px; +} + +input, +select { + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--ink); + padding: 8px 10px; + width: 100%; + background: var(--surface); +} + +input[type="checkbox"] { + width: auto; +} + +.check-row { + align-items: center; + display: flex; + gap: 8px; +} + +.settings-gear { + position: fixed; + left: 18px; + bottom: 18px; + z-index: 90; + width: 46px; + height: 46px; + border: 1px solid var(--border-heavy); + border-radius: 50%; + background: var(--surface-card); + color: var(--ink); + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.12); + display: flex; + align-items: center; + justify-content: center; + padding: 0; + transition: transform 0.15s, background 0.15s, border-color 0.15s; +} + +.settings-gear:hover, +.settings-gear:focus-visible { + transform: translateY(-1px) rotate(12deg); + background: var(--bg-subtle); + outline: 2px solid var(--border); + outline-offset: 2px; +} + +.settings-gear svg { + width: 22px; + height: 22px; + stroke: currentColor; +} + +.settings-panel { + display: none; + position: fixed; + left: 18px; + bottom: 76px; + z-index: 89; + width: min(430px, calc(100vw - 36px)); + max-height: min(720px, calc(100vh - 96px)); + overflow: auto; + border: 1px solid var(--border); + border-radius: var(--radius-xl); + background: var(--surface-card); + box-shadow: 0 18px 42px rgba(0, 0, 0, 0.14); + padding: 18px; +} + +.settings-panel.open { display: block; } + +.settings-head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 12px; + margin-bottom: 14px; +} + +.settings-title { + font-family: var(--font-display); + font-size: 20px; + font-weight: 600; + color: var(--ink); +} + +.settings-sub { + margin-top: 4px; + font-size: 12px; + color: var(--ink-muted); + line-height: 1.45; +} + +.settings-section { + border-top: 1px solid var(--border-light); + padding-top: 14px; + margin-top: 14px; +} + +.settings-section:first-of-type { + border-top: 0; + padding-top: 0; + margin-top: 0; +} + +.settings-section-title { + font-size: 12px; + font-weight: 700; + color: var(--ink); + margin-bottom: 10px; +} + +.settings-grid, +.settings-form { + display: grid; + grid-template-columns: 1fr; + gap: 10px; +} + +.settings-note { + color: var(--ink-muted); + font-size: 12px; +} + +dialog { + color: var(--ink-secondary); +} + +#organize-dialog { + position: fixed; + inset: 0; + margin: auto; + max-width: none; + width: min(760px, calc(100vw - 28px)); + max-height: calc(100dvh - 48px); + padding: 0; + overflow: hidden; +} + +#organize-dialog form { + max-height: calc(100dvh - 48px); + overflow: hidden; +} + +dialog::backdrop { + background: rgb(12 10 9 / 0.25); +} + +.dialog-head { + align-items: center; + display: flex; + justify-content: space-between; + gap: 16px; + padding: 20px 22px 14px; + border-bottom: 1px solid var(--border-light); +} + +.dialog-head h2 { + margin-bottom: 4px; +} + +.dialog-summary { + color: var(--ink-muted); + font-size: 12px; +} + +.result-grid { + display: grid; + gap: 16px; + max-height: calc(100dvh - 148px); + overflow: auto; + padding: 18px 22px 22px; +} + +.organize-status-row, +.organize-footer { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: space-between; +} + +.organize-meta { + border: 1px solid var(--border-light); + border-radius: var(--radius-pill); + color: var(--ink-muted); + font-family: var(--font-mono); + font-size: 11px; + padding: 4px 9px; +} + +.organize-summary-grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + border: 1px solid var(--border-light); + border-radius: var(--radius-xl); + overflow: hidden; + background: rgb(255 255 255 / 0.78); +} + +.organize-stat { + padding: 16px; + border-right: 1px solid var(--border-light); +} + +.organize-stat:last-child { + border-right: 0; +} + +.organize-stat .value { + font-size: 30px; +} + +.organize-body-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: 14px; +} + +.organize-section { + border: 1px solid var(--border-light); + border-radius: var(--radius-xl); + background: rgb(255 255 255 / 0.82); + overflow: hidden; +} + +.organize-section.has-warnings { + border-color: rgba(184, 134, 11, 0.4); +} + +.organize-section-head { + align-items: center; + border-bottom: 1px solid var(--border-light); + display: flex; + justify-content: space-between; + padding: 14px 16px; +} + +.organize-source-list { + display: grid; +} + +.organize-source-row { + display: grid; + grid-template-columns: 32px minmax(0, 1fr); + gap: 10px; + padding: 14px 16px; + border-bottom: 1px solid var(--border-light); +} + +.organize-source-row:last-child { + border-bottom: 0; +} + +.organize-source-bar { + height: 5px; + margin-top: 8px; + background: var(--bg-subtle); + border-radius: var(--radius-pill); + overflow: hidden; +} + +.organize-source-bar span { + display: block; + height: 100%; + border-radius: inherit; + background: var(--accent); +} + +.organize-warning-list { + display: grid; + gap: 8px; + list-style: none; + padding: 14px 16px; +} + +.organize-warning-list li, +.organize-ok { + border-radius: var(--radius-md); + background: var(--bg-alt); + color: var(--ink-secondary); + font-size: 13px; + padding: 10px 12px; +} + +.organize-warning-list li { + border-left: 3px solid var(--yellow); +} + +.organize-ok { + margin: 14px 16px; +} + +.organize-empty { + margin: 14px 16px; +} + +.organize-footer { + justify-content: flex-end; + border-top: 1px solid var(--border-light); + padding-top: 14px; +} + +@media (hover: none) { + .action-secondary { opacity: 1; pointer-events: auto; } +} + +@media (max-width: 720px) { + body { display: block; overflow: auto; } + .tabs { + display: flex; + overflow-x: auto; + width: 100%; + border-right: 0; + border-bottom: 1px solid var(--border-light); + padding: 8px; + } + .tab-main { + display: flex; + gap: 8px; + } + .tabs button { + width: auto; + min-width: 108px; + border-left: 0; + border-bottom: 2px solid transparent; + } + .tabs button.active { + border-bottom-color: var(--border-heavy); + } + .shell { padding: 16px; } + .topbar { display: grid; } + .actions { justify-content: flex-start; } + .organize-summary-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .organize-stat { + border-right: 1px solid var(--border-light); + border-bottom: 1px solid var(--border-light); + } + .organize-body-grid { grid-template-columns: 1fr; } + .organize-footer { + justify-content: stretch; + } + .organize-footer button { + flex: 1 1 100%; + } + .session-inbox { + grid-template-columns: 1fr; + } + .session-source-rail { + position: static; + display: flex; + overflow-x: auto; + } + .session-source-chip { + min-width: 148px; + } + .session-row { + grid-template-columns: 30px minmax(0, 1fr); + } + .session-row-side { + grid-column: 2; + white-space: normal; + } + .todo-card { + grid-template-columns: 5px minmax(0, 1fr); + } + .todo-actions { + grid-column: 2; + justify-self: start; + margin-top: 2px; + } +} diff --git a/public/app.js b/public/app.js new file mode 100644 index 00000000..ee8e47ff --- /dev/null +++ b/public/app.js @@ -0,0 +1,728 @@ +const state = { + sources: [], + sessions: [], + todos: [], + startupScan: { status: "idle", sources: [], warnings: [] }, + observationsBySession: {}, + selectedSessionId: null, + highlightedObservationId: null, + settings: null, + settingsOpen: false +}; + +const $ = (selector) => document.querySelector(selector); + +document.querySelectorAll(".tabs button").forEach((button) => { + button.addEventListener("click", () => showView(button.dataset.view)); +}); + +$("#organize").addEventListener("click", organize); +$("#settings-gear").addEventListener("click", toggleSettings); +$("#settings-panel").addEventListener("click", (event) => { + const target = event.target; + if (!(target instanceof HTMLElement)) return; + if (target.dataset.action === "close-settings") closeSettings(); +}); + +await refresh(); + +async function refresh() { + try { + const [sources, sessions, todos, settings, startupScan] = await Promise.all([ + api("/sources"), + api("/sessions"), + api("/todos"), + api("/settings"), + api("/startup/scan") + ]); + Object.assign(state, { sources, sessions, todos, settings, startupScan }); + if (!state.selectedSessionId || !sessions.some((session) => session.id === state.selectedSessionId)) { + state.selectedSessionId = sessions[0]?.id ?? null; + } + if (state.selectedSessionId) await ensureSessionObservations(state.selectedSessionId); + render(); + setStatus(startupStatusText(startupScan)); + } catch (error) { + setStatus(error.message); + } +} + +function render() { + renderStartupScan(); + renderSources(); + renderEvidenceBrowser(); + renderTodos(); + renderSettingsPanel(); +} + +function renderStartupScan() { + const scan = state.startupScan ?? { status: "idle", sources: [], warnings: [] }; + const details = (scan.sources ?? []).map((source) => { + const result = source.result; + if (!result) return `${source.source}: ${source.warning ?? "not indexed"}`; + return `${source.source}: ${result.scanned} scanned, ${result.skipped} skipped`; + }).join(" / "); + $("#startup-scan").innerHTML = ` +
+
+
Local Index
+
${escapeHtml(startupStatusText(scan))}
+
${escapeHtml(details || "Waiting for local startup scan.")}
+
+ ${escapeHtml(scan.status)} +
+ `; +} + +function renderSources() { + const latestSession = state.sessions[0]; + const totalCheckpoints = state.sources.reduce((sum, source) => sum + Number(source.checkpoints || 0), 0); + const openTodos = state.todos.filter((todo) => todo.status === "todo").length; + $("#source-grid").innerHTML = [ + { label: "Sessions", value: state.sessions.length, sub: latestSession ? `recent ${formatDate(latestSession.updatedAt)}` : "no local records" }, + { label: "Open Todos", value: openTodos, sub: `${state.todos.length} total cards` }, + { label: "Checkpoints", value: totalCheckpoints, sub: "mtime + size incremental scan" }, + ...state.sources.map((source) => ({ + label: source.source, + value: source.sessions, + sub: `${source.checkpoints} checkpoints` + })) + ].map((source) => ` +
+
${escapeHtml(source.label)}
+
${escapeHtml(String(source.value))}
+
${escapeHtml(source.sub)}
+
+ `).join(""); +} + +function renderEvidenceBrowser() { + const selected = state.sessions.find((session) => session.id === state.selectedSessionId) ?? state.sessions[0]; + const totalObservations = state.sessions.reduce((sum, session) => sum + Number(session.observationCount || 0), 0); + if (!selected) { + $("#evidence-browser").innerHTML = `
No clean sessions indexed yet.
`; + return; + } + $("#evidence-browser").innerHTML = ` +
+
+
Evidence Browser
+
Clean conversation archive
+
${state.sessions.length} visible sessions / ${totalObservations} cleaned observations
+
+ ${escapeHtml(sessionSourceName(selected.source))} +
+
+ +
+
+
+
${escapeHtml(selected.source)}
+
${escapeHtml(sessionTitle(selected))}
+
+ ${selected.observationCount ?? observationCount(selected)} records +
+ ${renderSessionObservationTrail(selected)} +
+
+ `; + document.querySelectorAll("[data-session]").forEach((button) => { + button.addEventListener("click", () => selectSession(button.dataset.session)); + }); + if (state.highlightedObservationId) requestAnimationFrame(() => applyObservationHighlight(state.highlightedObservationId)); +} + +function renderSessionObservationTrail(session) { + const observations = cleanSessionObservations(session.id); + if (observations.length === 0) return `
No visible transcript for this session.
`; + return observations.map((observation) => ` +
+ +
+
+
${observation.role === "user" ? "User" : "Agent reply"}
+
${formatDate(observation.createdAt)}
+
+
${escapeHtml(observation.text)}
+
+
+ `).join(""); +} + +async function selectSession(sessionId) { + if (!sessionId) return; + state.selectedSessionId = sessionId; + state.highlightedObservationId = null; + await ensureSessionObservations(sessionId); + renderEvidenceBrowser(); +} + +function renderTodos() { + $("#todo-list").innerHTML = state.todos.length ? ` +
+
+ ${state.todos.map((todo) => ` +
+
+
+
${escapeHtml(todo.title)}
+
${escapeHtml(todo.description)}
+
+ ${todo.status} + ${todo.evidenceIds.length} evidence + ${formatDate(todo.updatedAt)} +
+
+
+ + + + + +
+
+ `).join("")} +
+
+ ` : `
No todos yet. Local sessions are indexed automatically, then Organize creates cards.
`; + + document.querySelectorAll("[data-status]").forEach((button) => { + button.addEventListener("click", () => updateTodo(button.dataset.id, button.dataset.status)); + }); + document.querySelectorAll("[data-evidence]").forEach((button) => { + button.addEventListener("click", () => jumpToTodoEvidence(button.dataset.evidence)); + }); +} + +function renderSettingsPanel() { + const settings = state.settings; + const llm = settings?.llm ?? {}; + const organize = settings?.organize ?? {}; + const apiKeyLabel = llm.apiKeyConfigured ? `Configured ${llm.apiKeyMasked}` : "Missing"; + $("#settings-panel").classList.toggle("open", state.settingsOpen); + $("#settings-panel").setAttribute("aria-hidden", String(!state.settingsOpen)); + $("#settings-gear").setAttribute("aria-expanded", String(state.settingsOpen)); + $("#settings-panel").innerHTML = ` +
+
+
Settings
+
Local paths and LLM extraction defaults.
+
+ +
+
+
+
Sources
+
+ + +
+
+
+
LLM Extraction
+
+ + + + + + + + +
API key: ${escapeHtml(apiKeyLabel)}
+
+
+
+
Organize Scope
+
+ + + + +
+
+ +
+ `; + $("#settings-form")?.addEventListener("submit", saveSettings); +} + +async function organize() { + const button = $("#organize"); + const originalLabel = button.textContent; + button.disabled = true; + button.classList.add("is-busy"); + button.setAttribute("aria-busy", "true"); + button.textContent = "Organizing..."; + const estimate = organizeEstimateText(); + setStatus(`Organizing recent sessions. ${estimate}`); + try { + const result = await api("/todos/organize", { method: "POST", body: {} }); + showOrganizeResult(result); + await refresh(); + } catch (error) { + setStatus(error.message); + } finally { + button.disabled = false; + button.classList.remove("is-busy"); + button.removeAttribute("aria-busy"); + button.textContent = originalLabel || "Organize"; + } +} + +function organizeEstimateText() { + const organize = state.settings?.organize ?? {}; + const maxSessions = Number(organize.maxSessions ?? 16); + const maxInteractions = Number(organize.maxInteractionsPerSession ?? 10); + const estimatedSeconds = Math.max(20, Math.min(180, Math.ceil(maxSessions * Math.max(2, maxInteractions / 5)))); + const range = estimatedSeconds < 60 + ? `${estimatedSeconds}-${estimatedSeconds + 20} seconds` + : `${Math.ceil(estimatedSeconds / 60)}-${Math.ceil((estimatedSeconds + 60) / 60)} minutes`; + return `This may take about ${range}; only the newest ${maxSessions} sessions are processed in this run.`; +} + +async function updateTodo(id, status) { + await api(`/todos/${encodeURIComponent(id)}`, { method: "PATCH", body: { status } }); + await refresh(); +} + +async function jumpToTodoEvidence(id) { + const evidence = await api(`/todos/${encodeURIComponent(id)}/evidence`); + if (evidence.length === 0) { + $("#evidence-browser").innerHTML = `
No evidence for this todo.
`; + showView("evidence"); + return; + } + + const targetObservationId = evidence[0].observationId; + const resolved = await resolveObservationSession(targetObservationId); + if (!resolved) { + state.highlightedObservationId = targetObservationId; + renderEvidenceBrowser(); + showView("evidence"); + setStatus("Evidence source session was not found in the clean session index."); + return; + } + + state.selectedSessionId = resolved.session.id; + state.highlightedObservationId = targetObservationId; + state.observationsBySession[resolved.session.id] = resolved.observations; + renderEvidenceBrowser(); + showView("evidence"); + requestAnimationFrame(() => applyObservationHighlight(targetObservationId)); +} + +async function resolveObservationSession(observationId) { + for (const session of state.sessions) { + const observations = await api(`/sessions/${encodeURIComponent(session.id)}/observations`); + if (observations.some((observation) => observation.id === observationId)) { + return { session, observations }; + } + } + return null; +} + +function applyObservationHighlight(observationId) { + const element = document.getElementById(`obs-anchor-${observationId}`); + if (!element) return; + element.scrollIntoView({ block: "center", behavior: "smooth" }); + element.classList.remove("obs-jump-highlight"); + void element.offsetWidth; + element.classList.add("obs-jump-highlight"); + setTimeout(() => element.classList.remove("obs-jump-highlight"), 2400); +} + +async function saveSettings(event) { + event.preventDefault(); + const submitter = event.submitter instanceof HTMLButtonElement ? event.submitter : $("#settings-form button[type='submit']"); + if (submitter) submitter.disabled = true; + setStatus("Saving settings..."); + const apiKey = $("#llm-api-key").value.trim(); + const clearKey = $("#llm-clear-key").checked; + const llm = { + enabled: $("#llm-enabled").checked, + provider: "openai", + model: $("#llm-model").value.trim(), + endpoint: $("#llm-endpoint").value.trim(), + thinkingDepth: $("#llm-thinking").value, + timeoutMs: Number($("#llm-timeout").value) + }; + if (clearKey) llm.apiKey = ""; + if (!clearKey && apiKey) llm.apiKey = apiKey; + + try { + await api("/settings", { + method: "PUT", + body: { + sources: { + codex: pathValue("#codex-path"), + "claude-code": pathValue("#claude-path") + }, + llm, + organize: { + sinceDays: Number($("#organize-since-days").value), + maxInteractionsPerSession: Number($("#organize-max-interactions").value), + maxSessions: Number($("#organize-max-sessions").value), + maxObservationsPerSession: Number($("#organize-max-observations").value) + } + } + }); + await refresh(); + state.settingsOpen = true; + renderSettingsPanel(); + setStatus("Settings saved."); + } catch (error) { + setStatus(`Settings save failed: ${error.message}`); + } finally { + if (submitter) submitter.disabled = false; + } +} + +function pathValue(selector) { + const path = $(selector).value.trim(); + return path ? { path } : {}; +} + +function toggleSettings() { + state.settingsOpen = !state.settingsOpen; + renderSettingsPanel(); +} + +function closeSettings() { + state.settingsOpen = false; + renderSettingsPanel(); +} + +function showOrganizeResult(result) { + const dialog = $("#organize-dialog"); + const warnings = Array.isArray(result.warnings) ? result.warnings : []; + const sources = Array.isArray(result.sources) ? result.sources : []; + $("#organize-dialog-summary").textContent = `${result.engine.toUpperCase()} engine / ${result.scanned} observations / ${result.durationMs}ms`; + $("#organize-result").innerHTML = ` +
+ ${escapeHtml(organizeStatusLabel(result, warnings))} + ${escapeHtml(result.engine)} engine + ${result.durationMs}ms +
+
+ ${[ + ["Scanned", result.scanned], + ["Created", result.created], + ["Updated", result.updated], + ["Completed", result.completed], + ["Ignored", result.ignored] + ].map(([label, value]) => ` +
+
${label}
+
${escapeHtml(String(value))}
+
+ `).join("")} +
+
+
+
+
Sources Scanned
+
${sources.length} sources
+
+ ${renderOrganizeSources(sources)} +
+
+
+
Warnings
+
${warnings.length || "none"}
+
+ ${renderOrganizeWarnings(warnings, result.details)} +
+
+
+ + + +
+ `; + $("#organize-result").querySelectorAll("[data-dialog-action]").forEach((button) => { + button.addEventListener("click", () => handleOrganizeDialogAction(button.dataset.dialogAction)); + }); + dialog.showModal(); +} + +function organizeStatusLabel(result, warnings) { + if (warnings.length > 0) return "Warnings"; + if (result.created > 0) return `Created ${result.created}`; + if (result.updated > 0) return `Updated ${result.updated}`; + return "No changes"; +} + +function renderOrganizeSources(sources) { + if (sources.length === 0) return `
No source observations were scanned.
`; + const total = Math.max(1, sources.reduce((sum, source) => sum + Number(source.scanned || 0), 0)); + return ` +
+ ${sources.map((source) => { + const scanned = Number(source.scanned || 0); + const percent = Math.max(3, Math.round((scanned / total) * 100)); + return ` +
+ +
+
+
${escapeHtml(sessionSourceName(source.source))}
+
${scanned} scanned
+
+
+
+
+ `; + }).join("")} +
+ `; +} + +function renderOrganizeWarnings(warnings, details) { + if (!warnings.length) return `
No warnings.
`; + const batchFailures = Array.isArray(details?.batchFailures) ? details.batchFailures : []; + const truncations = Array.isArray(details?.truncations) ? details.truncations : []; + const scope = details?.scope; + return ` +
    + ${warnings.map((warning) => `
  • ${escapeHtml(warningLabel(warning))}
  • `).join("")} +
+ ${scope ? `
整理范围:扫描 ${escapeHtml(String(scope.sessionsScanned ?? 0))} 个会话,跳过 ${escapeHtml(String(scope.sessionsDropped ?? 0))} 个会话、${escapeHtml(String(scope.observationsDropped ?? 0))} 条记录。
` : ""} + ${truncations.length ? `
已压缩 ${truncations.length} 条较长会话片段后再发送给 LLM。
` : ""} + ${batchFailures.length ? ` +
    + ${batchFailures.slice(0, 4).map((failure) => ` +
  • ${escapeHtml(sessionSourceName(failure.source))} / ${escapeHtml(shortId(failure.sessionId))}: ${escapeHtml(batchFailureReasonLabel(failure.reason))}
  • + `).join("")} +
+ ` : ""} + `; +} + +function warningLabel(warning) { + return { + llm_config_missing: "LLM is not configured; no cards were generated.", + llm_timeout: "部分会话的 LLM 抽取超时。", + llm_provider_failed: "部分会话的 LLM 调用失败。", + llm_output_invalid: "The LLM returned output that could not be used.", + llm_no_valid_candidates: "No actionable todos were found.", + llm_input_truncated: "部分长会话内容已压缩后再发送给 LLM。", + llm_batch_failed: "部分批次失败,其余批次已继续处理。", + organize_scope_truncated: "本次只整理最近一部分会话。", + organize_failed: "Organize failed before cards could be created." + }[warning] ?? warning; +} + +function batchFailureReasonLabel(reason) { + return { + network_error: "网络请求失败", + http_400: "provider 拒绝请求", + http_401: "API key 无效或未授权", + http_403: "API key 无权限", + http_404: "模型或接口不存在", + http_429: "provider 限流", + http_500: "provider 服务错误", + http_502: "provider 网关错误", + http_503: "provider 暂不可用", + http_504: "provider 调用超时", + unknown_provider_error: "未知 provider 错误", + timeout: "调用超时", + invalid_json: "模型返回了非 JSON", + invalid_schema: "模型返回结构不符合要求" + }[reason] ?? reason; +} + +function shortId(value) { + const text = String(value || ""); + return text.length > 18 ? `${text.slice(0, 8)}…${text.slice(-6)}` : text; +} + +function handleOrganizeDialogAction(action) { + const dialog = $("#organize-dialog"); + if (action === "todos") { + dialog.close(); + showView("todos"); + $("#organize").focus(); + return; + } + if (action === "settings") { + dialog.close(); + state.settingsOpen = true; + renderSettingsPanel(); + $("#settings-gear").focus(); + return; + } + dialog.close(); + $("#organize").focus(); +} + +function showView(id) { + document.querySelectorAll(".tabs button").forEach((button) => { + const active = button.dataset.view === id; + button.classList.toggle("active", active); + if (active) button.setAttribute("aria-current", "page"); + else button.removeAttribute("aria-current"); + }); + document.querySelectorAll(".view").forEach((view) => { + view.classList.toggle("active", view.id === id); + }); +} + +async function api(path, options = {}) { + let response; + try { + response = await fetch(path, { + method: options.method ?? "GET", + headers: { "content-type": "application/json" }, + body: options.body ? JSON.stringify(options.body) : undefined + }); + } catch { + throw new Error("Local AI-Todo server is not reachable. Restart with ai-todo open."); + } + const body = await response.json(); + if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`); + return body; +} + +async function ensureSessionObservations(sessionId) { + if (!sessionId || state.observationsBySession[sessionId]) return; + try { + state.observationsBySession[sessionId] = await api(`/sessions/${encodeURIComponent(sessionId)}/observations`); + } catch { + state.observationsBySession[sessionId] = []; + } +} + +function setStatus(message) { + $("#status").textContent = message; +} + +function startupStatusText(scan) { + if (!scan || scan.status === "idle") return "Ready"; + if (scan.status === "indexing") return "Indexing local sessions..."; + if (scan.status === "failed") return "Indexing finished with warnings"; + return "Ready"; +} + +function sessionSourceGroups() { + const groups = new Map(); + for (const session of state.sessions) { + const group = groups.get(session.source) ?? { name: session.source, count: 0, preview: "" }; + group.count++; + if (!group.preview) group.preview = sessionPreview(session); + groups.set(session.source, group); + } + return Array.from(groups.values()); +} + +function observationCount(session) { + return (state.observationsBySession[session.id] ?? []).length; +} + +function cleanSessionObservations(sessionId) { + return (state.observationsBySession[sessionId] ?? []).filter((observation) => observation.role === "user" || observation.role === "assistant"); +} + +function sessionPreview(session) { + if (session.preview) return session.preview; + const observation = (state.observationsBySession[session.id] ?? []).find((item) => item.role === "user" || item.role === "assistant"); + return observation?.text ?? "No visible transcript preview yet."; +} + +function sessionTitle(session) { + const preview = sessionPreview(session); + return truncate(preview || session.source, 84); +} + +function sessionSourceName(source) { + if (source === "claude-code") return "Claude"; + if (source === "codex") return "Codex"; + return "Browser"; +} + +function sourceInitials(source) { + return sessionSourceName(source).slice(0, 2).toUpperCase(); +} + +function shortPath(path) { + const parts = String(path || "").split("/"); + return parts.slice(-3).join("/") || path; +} + +function truncate(value, length) { + const text = String(value || ""); + return text.length > length ? `${text.slice(0, length - 1)}…` : text; +} + +function formatDate(value) { + return new Date(value).toLocaleString(); +} + +function escapeHtml(value) { + return String(value).replace(/[&<>"']/g, (char) => ({ + "&": "&", + "<": "<", + ">": ">", + "\"": """, + "'": "'" + })[char]); +} + +function escapeAttr(value) { + return escapeHtml(value); +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 00000000..6af2decc --- /dev/null +++ b/public/index.html @@ -0,0 +1,79 @@ + + + + + + AI-Todo + + + + + +
+
+
+

AI-Todo

+

Loading local workspace...

+
+
+ +
+
+ +
+

Source Dashboard

+
+
+
+ +
+

Todos

+
+
+ +
+

Evidence

+
+
+ +
+ + + + + +
+
+
+

Organize Result

+

Review the latest organize run.

+
+ +
+
+
+
+ + + + diff --git a/requirements-langextract.txt b/requirements-langextract.txt deleted file mode 100644 index 08571763..00000000 --- a/requirements-langextract.txt +++ /dev/null @@ -1,2 +0,0 @@ -langextract[openai] -socksio diff --git a/scripts/backfill-imported-sessions.sh b/scripts/backfill-imported-sessions.sh deleted file mode 100755 index 37acf527..00000000 --- a/scripts/backfill-imported-sessions.sh +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env bash -# Backfill memory artifacts for sessions imported via `agentmemory import-jsonl`. -# -# The import path only persists Session + Observation rows (via synthetic, -# zero-LLM compression) and the deterministic crystal/lesson derivation. -# It does NOT call mem::summarize, so the semantic/procedural/reflect tiers -# of the consolidation pipeline have nothing to roll up. -# -# This script walks every session tagged `jsonl-import` and: -# 1. POSTs /agentmemory/summarize per session (LLM call) -# 2. POSTs /agentmemory/consolidate-pipeline once at the end -# -# Graph extraction (/agentmemory/graph/extract) is intentionally skipped — -# its API takes a per-observation payload, which is cost-prohibitive for -# bulk imports. `reflect` falls back to a no-graph clustering mode. -# -# Usage: -# scripts/backfill-imported-sessions.sh --dry-run -# scripts/backfill-imported-sessions.sh --limit 5 -# scripts/backfill-imported-sessions.sh # process all - -set -euo pipefail - -URL="${AGENTMEMORY_URL:-http://localhost:3111}" -DRY_RUN=0 -LIMIT=0 # 0 = no limit -ONLY_TAG="jsonl-import" -SKIP_CONSOLIDATE=0 -SKIP_AGENTS=0 # drop sessions whose project starts with "agent-" -MAX_OBS=0 # 0 = no cap; skip sessions with more observations than this -DEBUG_ON_ERROR=0 # on failure, dump session metadata + obs to DEBUG_DIR -DEBUG_DIR="${AGENTMEMORY_DEBUG_DIR:-./agentmemory-debug}" -PROJECT_PATTERN="" # jq test() regex against .project; "" means no filter - -# Cost-estimate knobs (defaults tuned for DeepSeek V4 Flash on DeepInfra: -# $0.14 / 1M input, $0.28 / 1M output). Override via env if needed. -COST_IN_PER_1M="${AGENTMEMORY_COST_IN_PER_1M:-0.14}" -COST_OUT_PER_1M="${AGENTMEMORY_COST_OUT_PER_1M:-0.28}" -# Rough token weight per compressed observation, derived from inspecting -# real synthetic-compression payloads in the kv store (mostly 100-300 tok, -# heavy-tailed). Override if your sessions are unusually verbose. -TOKENS_PER_OBS="${AGENTMEMORY_TOKENS_PER_OBS:-200}" -# Reserved per-call output budget (XML summary is small). -TOKENS_OUT_PER_SESSION="${AGENTMEMORY_TOKENS_OUT_PER_SESSION:-500}" - -while [[ $# -gt 0 ]]; do - case "$1" in - --dry-run) DRY_RUN=1; shift ;; - --limit) LIMIT="${2:?--limit needs a number}"; shift 2 ;; - --tag) ONLY_TAG="${2:?--tag needs a value (use empty string for all)}"; shift 2 ;; - --skip-consolidate) SKIP_CONSOLIDATE=1; shift ;; - --skip-agents) SKIP_AGENTS=1; shift ;; - --max-obs) MAX_OBS="${2:?--max-obs needs a number}"; shift 2 ;; - --debug-on-error) DEBUG_ON_ERROR=1; shift ;; - --project-pattern) PROJECT_PATTERN="${2:?--project-pattern needs a regex}"; shift 2 ;; - -h|--help) - sed -n '2,28p' "$0" - exit 0 ;; - *) echo "unknown flag: $1" >&2; exit 2 ;; - esac -done - -for bin in curl jq; do - command -v "$bin" >/dev/null || { echo "missing dependency: $bin" >&2; exit 1; } -done - -# Curl timeout profiles. Metadata reads (livez, sessions list, observations -# pull for debug dumps) should fail fast and retry transient blips. The LLM -# work calls (summarize, consolidate) intentionally have no --retry and a -# wide --max-time: each call can legitimately take minutes for chunked -# summarize on large sessions, and retrying a half-finished LLM job is -# expensive both in dollars and in duplicated server-side work. -META_CURL_OPTS=(--connect-timeout 10 --max-time 30 --retry 2 --retry-delay 1) -WORK_CURL_OPTS=(--connect-timeout 10 --max-time 1800) - -echo "agentmemory backfill — server: $URL" -[[ "$DRY_RUN" == 1 ]] && echo "DRY RUN: no POSTs will be made." - -# --- liveness --- -if ! curl -fsS "${META_CURL_OPTS[@]}" "$URL/agentmemory/livez" >/dev/null; then - echo "server not reachable at $URL (try: npm run start:local-memory)" >&2 - exit 1 -fi - -# --- collect session ids --- -sessions_json="$(curl -fsS "${META_CURL_OPTS[@]}" "$URL/agentmemory/sessions")" -filter='.sessions[] | select(.status=="completed")' -if [[ -n "$ONLY_TAG" ]]; then - filter+=" | select((.tags // []) | index(\"$ONLY_TAG\"))" -fi -if [[ "$SKIP_AGENTS" == 1 ]]; then - filter+=' | select((.project // "") | startswith("agent-") | not)' -fi -if [[ -n "$PROJECT_PATTERN" ]]; then - # jq's test() applies a regex against the project string. - filter+=" | select((.project // \"\") | test(\"$PROJECT_PATTERN\"))" -fi -if [[ "$MAX_OBS" -gt 0 ]]; then - filter+=" | select((.observationCount // 0) <= $MAX_OBS)" -fi -filter+=' | "\(.id)\t\(.observationCount // 0)\t\(.project // "")"' - -rows=() -while IFS= read -r line; do - rows+=("$line") -done < <(echo "$sessions_json" | jq -r "$filter") -total="${#rows[@]}" - -if [[ "$total" -eq 0 ]]; then - echo "no sessions matched (tag='$ONLY_TAG'); nothing to do." - exit 0 -fi - -if [[ "$LIMIT" -gt 0 && "$LIMIT" -lt "$total" ]]; then - rows=("${rows[@]:0:$LIMIT}") -fi - -echo "matched $total session(s); will process ${#rows[@]}." -total_obs=0 -for row in "${rows[@]}"; do - obs="$(cut -f2 <<<"$row")" - total_obs=$(( total_obs + obs )) -done -est_in=$(( total_obs * TOKENS_PER_OBS + ${#rows[@]} * 500 )) -est_out=$(( ${#rows[@]} * TOKENS_OUT_PER_SESSION )) -est_cost="$(awk -v i="$est_in" -v o="$est_out" -v ci="$COST_IN_PER_1M" -v co="$COST_OUT_PER_1M" \ - 'BEGIN { printf "%.2f", (i*ci + o*co) / 1000000 }')" - -echo "≈ ${#rows[@]} summarize LLM calls (one per session, covering $total_obs observations)" -printf '≈ %d input tok + %d output tok → $%s (rates: in=$%s/1M out=$%s/1M, %s tok/obs)\n' \ - "$est_in" "$est_out" "$est_cost" "$COST_IN_PER_1M" "$COST_OUT_PER_1M" "$TOKENS_PER_OBS" -echo - -if [[ "$DRY_RUN" == 1 ]]; then - printf '%-40s %10s %s\n' "session" "obs" "project" - for row in "${rows[@]}"; do - id="$(cut -f1 <<<"$row")" - obs="$(cut -f2 <<<"$row")" - proj="$(cut -f3 <<<"$row")" - printf '%-40s %10s %s\n' "$id" "$obs" "$proj" - done - echo - echo "(dry run) next steps if you re-run without --dry-run:" - echo " for each session above: POST $URL/agentmemory/summarize {sessionId}" - if [[ "$SKIP_CONSOLIDATE" == 0 ]]; then - echo " then: POST $URL/agentmemory/consolidate-pipeline {}" - fi - exit 0 -fi - -# --- summarize loop --- -if [[ "$DEBUG_ON_ERROR" == 1 ]]; then - mkdir -p "$DEBUG_DIR" - echo "debug mode: failed calls will dump to $DEBUG_DIR/" - echo -fi - -dump_failure() { - local id="$1" obs="$2" resp="$3" - # Replace anything outside [A-Za-z0-9._-] with `_` before joining with - # DEBUG_DIR. Session IDs from the API are UUIDs in practice, but the - # server doesn't enforce that — a hostile or buggy id containing `/` or - # `..` would otherwise escape the debug directory. - local safe_id - safe_id="$(printf '%s' "$id" | tr -c 'A-Za-z0-9._-' '_')" - local file="$DEBUG_DIR/${safe_id}.json" - # Pull the raw observations (what would have gone into the prompt) so the - # operator can reconstruct the upstream payload locally. We also compute - # narrative size stats so size-related rejections are immediately visible. - # Stream observations through stdin (avoids exec-arg overflow on - # multi-thousand-obs sessions — macOS argv ceiling is ~256k). - # `--get --data-urlencode` percent-encodes the session id so special - # characters can't corrupt the query string. - curl -fsS "${META_CURL_OPTS[@]}" --get \ - --data-urlencode "sessionId=$id" \ - "$URL/agentmemory/observations" \ - | jq \ - --arg id "$id" \ - --argjson obsCount "$obs" \ - --arg url "$URL/agentmemory/summarize" \ - --argjson response "$resp" \ - '. as $root - | .observations as $obs - | { - sessionId: $id, - observationCount: $obsCount, - request: { url: $url, method: "POST", body: { sessionId: $id } }, - response: $response, - observations: $obs, - stats: { - totalNarrativeBytes: ($obs | map(.narrative // "" | length) | add // 0), - maxNarrativeBytes: ($obs | map(.narrative // "" | length) | max // 0), - titleHistogram: ($obs | group_by(.title) | map({title: .[0].title, count: length}) | sort_by(-.count)) - } - }' >"$file" - echo " → $file" -} - -ok=0; skipped=0; failed=0 -i=0 -for row in "${rows[@]}"; do - i=$(( i + 1 )) - id="$(cut -f1 <<<"$row")" - obs="$(cut -f2 <<<"$row")" - - body="$(jq -nc --arg id "$id" '{sessionId:$id}')" - resp="$(curl -sS "${WORK_CURL_OPTS[@]}" -X POST "$URL/agentmemory/summarize" \ - -H 'content-type: application/json' --data "$body" || echo '{"success":false,"error":"curl_failed"}')" - # iii's HTTP layer occasionally returns non-JSON (HTML 5xx, empty body - # on timeout, etc.). Validate before parsing so `set -e` doesn't abort - # the whole backfill loop on a single bad response. - if jq -e . >/dev/null 2>&1 <<<"$resp"; then - status="$(jq -r '.success // false' <<<"$resp")" - err="$(jq -r '.error // ""' <<<"$resp")" - title="$(jq -r '.summary.title // ""' <<<"$resp")" - else - status="false" - err="invalid_json_response" - title="" - fi - - if [[ "$status" == "true" ]]; then - ok=$(( ok + 1 )) - printf '[%3d/%3d] OK %s obs=%-5s %s\n' "$i" "${#rows[@]}" "$id" "$obs" "$title" - elif [[ "$err" == "no_observations" || "$err" == "no_provider" ]]; then - skipped=$(( skipped + 1 )) - printf '[%3d/%3d] SKIP %s obs=%-5s %s\n' "$i" "${#rows[@]}" "$id" "$obs" "$err" - else - failed=$(( failed + 1 )) - printf '[%3d/%3d] FAIL %s obs=%-5s %s\n' "$i" "${#rows[@]}" "$id" "$obs" "$err" - [[ "$DEBUG_ON_ERROR" == 1 ]] && dump_failure "$id" "$obs" "$resp" - fi -done - -echo -echo "summarize: ok=$ok skipped=$skipped failed=$failed" - -# --- consolidate --- -if [[ "$SKIP_CONSOLIDATE" == 1 ]]; then - echo "skipping consolidate-pipeline (--skip-consolidate)" - exit 0 -fi - -if [[ "$ok" -eq 0 ]]; then - echo "no summaries produced; skipping consolidate-pipeline." - exit 0 -fi - -echo -echo "running consolidate-pipeline …" -resp="$(curl -sS "${WORK_CURL_OPTS[@]}" -X POST "$URL/agentmemory/consolidate-pipeline" \ - -H 'content-type: application/json' --data '{}' || echo '{"success":false,"error":"curl_failed"}')" -if jq -e . >/dev/null 2>&1 <<<"$resp"; then - echo "$resp" | jq . -else - echo "consolidate-pipeline returned non-JSON (likely a timeout or upstream error):" - printf '%s\n' "$resp" | head -c 500 - echo -fi diff --git a/scripts/build-viewer-html.mjs b/scripts/build-viewer-html.mjs deleted file mode 100644 index 58c4d22d..00000000 --- a/scripts/build-viewer-html.mjs +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env node -// Assemble src/viewer/index.html from the ordered fragments in -// src/viewer/parts/. index.html is a GENERATED artifact: it is committed (so -// the viewer server, the build `cp`, and the tests that readFileSync it keep -// working unchanged), but its source of truth is parts/*. The assembly is a -// pure byte-for-byte concatenation in manifest order — no templating, no -// wrapping — so the viewer's runtime placeholders (__AGENTMEMORY_VIEWER_NONCE__ -// / __AGENTMEMORY_VERSION__) and the strict CSP are untouched. -// -// Usage: -// node scripts/build-viewer-html.mjs regenerate index.html from parts/ -// node scripts/build-viewer-html.mjs --check fail (exit 1) if index.html is stale -// node scripts/build-viewer-html.mjs --watch rebuild on any change under parts/ -import { readFileSync, writeFileSync, watch } from "node:fs"; -import { join, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; - -const here = dirname(fileURLToPath(import.meta.url)); -const viewerDir = join(here, "..", "src", "viewer"); -const partsDir = join(viewerDir, "parts"); -const outFile = join(viewerDir, "index.html"); - -function assemble() { - const manifest = JSON.parse(readFileSync(join(partsDir, "manifest.json"), "utf-8")); - if (!Array.isArray(manifest) || manifest.length === 0) { - throw new Error("parts/manifest.json must be a non-empty array of fragment filenames"); - } - return manifest.map((name) => readFileSync(join(partsDir, name), "utf-8")).join(""); -} - -const args = new Set(process.argv.slice(2)); - -if (args.has("--check")) { - const expected = readFileSync(outFile, "utf-8"); - if (assemble() !== expected) { - console.error( - "src/viewer/index.html is out of sync with src/viewer/parts/.\n" + - "Run `npm run viewer:build` and commit the regenerated index.html.", - ); - process.exit(1); - } - console.log("viewer index.html is up to date with parts/"); -} else if (args.has("--watch")) { - const rebuild = () => { - try { - writeFileSync(outFile, assemble()); - console.log("rebuilt src/viewer/index.html"); - } catch (err) { - console.error("viewer rebuild failed:", err.message); - } - }; - rebuild(); - watch(partsDir, { recursive: true }, rebuild); - console.log(`watching ${partsDir} (recursive) — Ctrl+C to stop`); -} else { - writeFileSync(outFile, assemble()); - console.log("wrote src/viewer/index.html from parts/"); -} diff --git a/scripts/check-ai-validation-evidence-guards.mjs b/scripts/check-ai-validation-evidence-guards.mjs deleted file mode 100644 index 049901cf..00000000 --- a/scripts/check-ai-validation-evidence-guards.mjs +++ /dev/null @@ -1,109 +0,0 @@ -import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; -import path from 'node:path'; - -const tmpRoot = 'artifacts/tmp-ai-validation-guards'; - -function baseEvidence(overrides = {}) { - return { - product: 'Agent Memory Lab Browser Extension', - extension: { name: 'Agent Memory Lab', version: '0.1.0', manifestVersion: 3 }, - generatedAt: '2026-06-09T00:00:00.000Z', - page: { - title: 'ChatGPT', - url: 'https://chatgpt.com/c/test-thread', - host: 'chatgpt.com', - origin: 'https://chatgpt.com', - type: 'ai-chat', - typeLabel: 'ChatGPT' - }, - ai: { - supportedAiPage: true, - provider: 'ChatGPT', - editorFound: true, - anchorFound: true, - placement: 'after-editor', - memoryWidgetVisible: true, - promptLength: 24, - turnCount: 2, - checkedAt: '2026-06-09T00:00:00.000Z', - matchedSelectors: { - editor: '[contenteditable="true"]', - anchor: 'form', - send: 'button[data-testid="send-button"]', - turn: '[data-message-author-role]' - } - }, - manualValidation: { - memoryInsertPassed: true, - diagnosticsCopied: true, - siteInputStillWorks: true, - browser: 'Chrome test', - notes: 'No private chat content included' - }, - ...overrides - }; -} - -function writeEvidence(dir, name, data) { - mkdirSync(dir, { recursive: true }); - writeFileSync(path.join(dir, name), `${JSON.stringify(data, null, 2)}\n`); -} - -function runCheck(dir) { - return spawnSync(process.execPath, ['scripts/check-ai-validation-evidence.mjs'], { - encoding: 'utf8', - env: { - ...process.env, - AGENTMEMORY_AI_EVIDENCE_DIR: dir, - AGENTMEMORY_AI_EVIDENCE_SUMMARY: path.join(tmpRoot, `${path.basename(dir)}-summary.json`) - } - }); -} - -function assert(condition, message) { - if (!condition) throw new Error(message); -} - -rmSync(tmpRoot, { recursive: true, force: true }); - -const cleanDir = path.join(tmpRoot, 'clean'); -writeEvidence(cleanDir, '2026-06-09-chatgpt.json', baseEvidence()); -const clean = runCheck(cleanDir); -assert(clean.status === 0, `Clean evidence should pass guard check.\n${clean.stderr}\n${clean.stdout}`); - -const privateDir = path.join(tmpRoot, 'private-fields'); -writeEvidence(privateDir, '2026-06-09-chatgpt.json', baseEvidence({ - conversation: { - turns: [{ role: 'user', text: 'PRIVATE_CHAT_TEXT_SHOULD_NOT_BE_IN_EVIDENCE' }], - promptDraft: 'PRIVATE_PROMPT_SHOULD_NOT_BE_IN_EVIDENCE' - } -})); -const privateResult = runCheck(privateDir); -assert(privateResult.status !== 0, 'Evidence containing raw conversation/prompt fields must fail.'); -assert((privateResult.stderr + privateResult.stdout).includes('private/raw page or conversation fields'), 'Private-field failure should explain the privacy issue.'); - -const wrongHostDir = path.join(tmpRoot, 'wrong-host'); -writeEvidence(wrongHostDir, '2026-06-09-chatgpt.json', baseEvidence({ - page: { - title: 'Claude', - url: 'https://claude.ai/chat/test-thread', - host: 'claude.ai', - origin: 'https://claude.ai', - type: 'ai-chat', - typeLabel: 'Claude' - } -})); -const wrongHost = runCheck(wrongHostDir); -assert(wrongHost.status !== 0, 'Evidence whose host does not match its provider must fail.'); -assert((wrongHost.stderr + wrongHost.stdout).includes('page host does not match provider'), 'Wrong-host failure should explain the provider/domain issue.'); - -const noTurnsDir = path.join(tmpRoot, 'no-turns'); -writeEvidence(noTurnsDir, '2026-06-09-chatgpt.json', baseEvidence({ ai: { ...baseEvidence().ai, turnCount: 0 } })); -const noTurns = runCheck(noTurnsDir); -assert(noTurns.status === 0, 'No-turn evidence should be summarized without crashing the evidence scanner.'); -const noTurnsSummary = JSON.parse(readFileSync(path.join(tmpRoot, 'no-turns-summary.json'), 'utf8')); -assert(noTurnsSummary.passedCount === 0, 'Evidence without captured conversation turns must not count as passed.'); -assert(Array.isArray(noTurnsSummary.notPassedRequired) && noTurnsSummary.notPassedRequired.includes('ChatGPT'), 'No-turn evidence should keep ChatGPT in the not-passed list.'); - -console.log('AI validation evidence guard checks ok'); diff --git a/scripts/check-ai-validation-evidence.mjs b/scripts/check-ai-validation-evidence.mjs deleted file mode 100644 index 6f97ff81..00000000 --- a/scripts/check-ai-validation-evidence.mjs +++ /dev/null @@ -1,154 +0,0 @@ -import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; -import path from 'node:path'; - -function readJson(file) { - return JSON.parse(readFileSync(file, 'utf8')); -} - -function hasPass(value) { - if (value === true) return true; - const text = String(value || '').toLowerCase(); - return ['通过', '已通过', 'pass', 'passed', 'ok'].some((word) => text.includes(word)); -} - -function normalizeProvider(value) { - const text = String(value || '').trim().toLowerCase(); - if (text.includes('chatgpt')) return 'ChatGPT'; - if (text.includes('claude')) return 'Claude'; - if (text.includes('gemini')) return 'Gemini'; - if (text.includes('perplexity')) return 'Perplexity'; - if (text.includes('grok')) return 'Grok'; - if (text.includes('deepseek')) return 'DeepSeek'; - return value ? String(value).trim() : 'Unknown'; -} - -function jsonText(value) { - return JSON.stringify(value || {}); -} - -function hasPrivateDiagnosticContent(item) { - const text = jsonText(item); - const forbiddenKeys = [ - 'promptDraft', - 'turns', - 'conversation', - 'selection', - 'description', - 'headings', - 'candidates', - 'memories', - 'lessons', - 'draftContent' - ]; - return forbiddenKeys.some((key) => text.includes(`"${key}"`)); -} - -function isRequiredProvider(value) { - return ['ChatGPT', 'Claude', 'Gemini', 'Perplexity'].includes(normalizeProvider(value)); -} - -function evidencePrivacySafe(item) { - return !hasPrivateDiagnosticContent(item); -} - -function evidenceDomainMatchesProvider(item) { - const provider = normalizeProvider(item.ai && item.ai.provider); - const host = String(item.page && (item.page.host || item.page.url) || '').toLowerCase(); - if (provider === 'ChatGPT') return host.includes('chatgpt.com') || host.includes('chat.openai.com'); - if (provider === 'Claude') return host.includes('claude.ai'); - if (provider === 'Gemini') return host.includes('gemini.google.com'); - if (provider === 'Perplexity') return host.includes('perplexity.ai'); - return true; -} - -function evidencePassed(item) { - const ai = item.ai || {}; - const manual = item.manualValidation || {}; - const matched = ai.matchedSelectors || {}; - return !!( - evidencePrivacySafe(item) && - evidenceDomainMatchesProvider(item) && - ai.supportedAiPage && - ai.provider && - ai.editorFound && - matched.editor && - ai.anchorFound && - matched.anchor && - ai.memoryWidgetVisible && - ai.placement && - matched.send && - matched.turn && - Number(ai.turnCount || 0) > 0 && - ai.checkedAt && - hasPass(manual.memoryInsertPassed) && - hasPass(manual.diagnosticsCopied) && - hasPass(manual.siteInputStillWorks) - ); -} - -const evidenceDir = process.env.AGENTMEMORY_AI_EVIDENCE_DIR || 'docs/validation/browser-extension-ai-sites'; -const evidenceSummaryPath = process.env.AGENTMEMORY_AI_EVIDENCE_SUMMARY || 'artifacts/ai-validation-evidence-summary.json'; -const requiredProducts = ['ChatGPT', 'Claude', 'Gemini', 'Perplexity']; -const optionalProducts = ['Grok', 'DeepSeek']; -const files = existsSync(evidenceDir) - ? readdirSync(evidenceDir).filter((file) => file.endsWith('.json')).map((file) => path.join(evidenceDir, file)) - : []; - -const evidence = files.map((file) => { - const data = readJson(file); - const provider = normalizeProvider(data.ai && data.ai.provider); - const passed = evidencePassed(data); - return { - file, - provider, - url: data.page && data.page.url ? data.page.url : '', - checkedAt: data.ai && data.ai.checkedAt ? data.ai.checkedAt : data.generatedAt || '', - extensionVersion: data.extension && data.extension.version ? data.extension.version : '', - editorFound: !!(data.ai && data.ai.editorFound), - editorSelector: data.ai?.matchedSelectors?.editor || data.ai?.editorSelector || '', - anchorFound: !!(data.ai && data.ai.anchorFound), - anchorSelector: data.ai?.matchedSelectors?.anchor || data.ai?.anchorSelector || '', - sendSelector: data.ai?.matchedSelectors?.send || data.ai?.sendSelector || '', - turnSelector: data.ai?.matchedSelectors?.turn || data.ai?.turnSelector || '', - turnCount: Number(data.ai?.turnCount || 0), - memoryWidgetVisible: !!(data.ai && data.ai.memoryWidgetVisible), - memoryInsertPassed: hasPass(data.manualValidation && data.manualValidation.memoryInsertPassed), - diagnosticsCopied: hasPass(data.manualValidation && data.manualValidation.diagnosticsCopied), - siteInputStillWorks: hasPass(data.manualValidation && data.manualValidation.siteInputStillWorks), - privacySafe: evidencePrivacySafe(data), - domainMatchesProvider: evidenceDomainMatchesProvider(data), - passed - }; -}); - -const invalidRequiredEvidence = evidence.filter((item) => isRequiredProvider(item.provider) && (!item.privacySafe || !item.domainMatchesProvider)); -if (invalidRequiredEvidence.length) { - for (const item of invalidRequiredEvidence) { - console.error(`Invalid ${item.provider} evidence: ${item.file}`); - if (!item.privacySafe) console.error('- copied diagnostic contains private/raw page or conversation fields'); - if (!item.domainMatchesProvider) console.error('- page host does not match provider'); - } - process.exitCode = 1; -} - -const passedRequired = requiredProducts.filter((product) => evidence.some((item) => item.provider === product && item.passed)); -const notPassedRequired = requiredProducts.filter((product) => !passedRequired.includes(product)); -const summary = { - source: evidenceDir, - generatedAt: new Date().toISOString(), - requiredProducts, - optionalProducts, - files: evidence, - passedRequired, - notPassedRequired, - passedCount: passedRequired.length, - requiredCount: requiredProducts.length, - publicReleaseReadyByEvidence: passedRequired.length === requiredProducts.length -}; - -mkdirSync(path.dirname(evidenceSummaryPath), { recursive: true }); -writeFileSync(evidenceSummaryPath, `${JSON.stringify(summary, null, 2)}\n`); - -console.log(`AI validation evidence: ${passedRequired.length}/${requiredProducts.length} required products passed`); -if (notPassedRequired.length) console.log(`not passed: ${notPassedRequired.join(', ')}`); -console.log(`evidence summary: ${evidenceSummaryPath}`); diff --git a/scripts/check-browser-extension-demo-interaction.mjs b/scripts/check-browser-extension-demo-interaction.mjs deleted file mode 100644 index cc9dd463..00000000 --- a/scripts/check-browser-extension-demo-interaction.mjs +++ /dev/null @@ -1,192 +0,0 @@ -import { readFileSync } from 'node:fs'; -import vm from 'node:vm'; - -function assert(condition, message) { - if (!condition) throw new Error(message); -} - -class FakeEvent { - constructor(type, options = {}) { - this.type = type; - this.bubbles = !!options.bubbles; - this.target = null; - } -} - -class FakeNode { - constructor(tagName = 'div', attrs = {}) { - this.tagName = tagName.toUpperCase(); - this.attributes = { ...attrs }; - this.children = []; - this.parentElement = null; - this.listeners = new Map(); - this.textContent = attrs.textContent || ''; - if (Object.prototype.hasOwnProperty.call(attrs, 'value')) this.value = attrs.value; - this.__agentMemoryBound = false; - } - - get id() { - return this.attributes.id || ''; - } - - get className() { - return this.attributes.class || ''; - } - - get innerText() { - if (Object.prototype.hasOwnProperty.call(this, 'value')) return this.value; - return this.textContent || this.children.map((child) => child.innerText || '').join(' '); - } - - appendChild(child) { - child.parentElement = this; - this.children.push(child); - return child; - } - - addEventListener(type, handler) { - const list = this.listeners.get(type) || []; - list.push(handler); - this.listeners.set(type, list); - } - - dispatchEvent(event) { - event.target = event.target || this; - for (const handler of this.listeners.get(event.type) || []) handler.call(this, event); - if (event.bubbles && this.parentElement) this.parentElement.dispatchEvent(event); - return true; - } - - getAttribute(name) { - return this.attributes[name] ?? null; - } - - querySelector(selector) { - return queryAll(this, selector)[0] || null; - } - - querySelectorAll(selector) { - return queryAll(this, selector); - } - - closest(selector) { - let node = this; - while (node) { - if (matchesSelector(node, selector)) return node; - node = node.parentElement; - } - return null; - } -} - -function walk(root, out = []) { - for (const child of root.children || []) { - out.push(child); - walk(child, out); - } - return out; -} - -function queryAll(root, selector) { - const selectors = String(selector || '').split(',').map((item) => item.trim()).filter(Boolean); - return walk(root).filter((node) => selectors.some((item) => matchesSelector(node, item))); -} - -function matchesSelector(node, selector) { - if (!selector || !node) return false; - const parts = selector.split(/\s+/).filter(Boolean); - if (parts.length > 1) return matchesSelector(node, parts[parts.length - 1]); - if (selector.startsWith('#')) return node.id === selector.slice(1); - const attrMatches = [...selector.matchAll(/\[([^\]=*~^$|]+)(\*?=)?"?([^"\]]*)"?\]/g)]; - const tagMatch = selector.match(/^([a-z0-9-]+)/i); - if (tagMatch && node.tagName.toLowerCase() !== tagMatch[1].toLowerCase()) return false; - for (const match of attrMatches) { - const value = node.getAttribute(match[1].trim()); - if (value === null) return false; - if (match[2] === '=' && value !== match[3]) return false; - if (match[2] === '*=' && !value.includes(match[3])) return false; - } - if (!tagMatch && !attrMatches.length) return node.tagName.toLowerCase() === selector.toLowerCase(); - return true; -} - -const documentElement = new FakeNode('html'); -const body = new FakeNode('body'); -documentElement.appendChild(body); -const main = new FakeNode('main'); -const form = new FakeNode('form'); -const editor = new FakeNode('div', { - id: 'agentmemory-demo-input', - contenteditable: 'true', - textContent: '我们如何向外部试用者解释插件预览?' -}); -form.appendChild(editor); -main.appendChild(new FakeNode('article', { 'data-message-author-role': 'user', textContent: 'We need a local preview for external testers.' })); -main.appendChild(new FakeNode('article', { 'data-message-author-role': 'assistant', textContent: 'Use the browser sync status panel and confirm the conversation appears in Sessions.' })); -main.appendChild(form); -body.appendChild(main); - -const listeners = []; -const sentMessages = []; -const document = { - title: 'Agent Memory Lab 插件预览', - documentElement, - body, - querySelector: (selector) => documentElement.querySelector(selector), - querySelectorAll: (selector) => documentElement.querySelectorAll(selector) -}; - -let mutationObserverCallback = null; -class FakeMutationObserver { - constructor(callback) { - mutationObserverCallback = callback; - } - observe() {} -} - -const context = vm.createContext({ - document, - location: { hostname: 'localhost', pathname: '/demo/browser-extension.html', href: 'http://localhost:3114/demo/browser-extension.html' }, - window: { - addEventListener: () => {}, - getSelection: () => null - }, - chrome: { - runtime: { - onMessage: { addListener: (handler) => listeners.push(handler) }, - sendMessage: (message, callback) => { - sentMessages.push(message); - if (callback) callback({ ok: true }); - } - } - }, - MutationObserver: FakeMutationObserver, - setTimeout, - clearTimeout, - console -}); - -vm.runInContext(readFileSync('browser-extension/content-script.js', 'utf8'), context, { filename: 'content-script.js' }); -if (mutationObserverCallback) mutationObserverCallback([]); - -editor.textContent = '浏览器插件如何预览'; -editor.dispatchEvent(new FakeEvent('input', { bubbles: true })); -await new Promise((resolve) => setTimeout(resolve, 1300)); - -assert(!documentElement.querySelector('agent-memory-lab-widget'), 'Conversation-only extension must not inject memory suggestion widgets.'); -assert(sentMessages.some((message) => message.type === 'SYNC_PAGE_CONVERSATION'), 'Content script should schedule browser conversation sync.'); - -let collected = null; -for (const listener of listeners) { - listener({ type: 'AGENT_MEMORY_LAB_COLLECT_PAGE' }, {}, (response) => { - if (response && response.ok) collected = response.page; - }); -} - -assert(collected, 'Content script should respond with captured page context.'); -assert(collected.aiProvider === 'Agent Memory Demo', 'Demo page should be treated as an AI conversation source.'); -assert(Array.isArray(collected.turns) && collected.turns.length >= 2, 'Demo page should expose captured conversation turns.'); -assert(!('selection' in collected), 'Content script must not collect selected page text.'); -assert(!('promptDraft' in collected), 'Content script must not send input drafts as capture content.'); - -console.log('browser extension demo conversation sync ok'); diff --git a/scripts/check-browser-extension-diagnostics-privacy.mjs b/scripts/check-browser-extension-diagnostics-privacy.mjs deleted file mode 100644 index 0e4a57c9..00000000 --- a/scripts/check-browser-extension-diagnostics-privacy.mjs +++ /dev/null @@ -1,168 +0,0 @@ -import { readFileSync } from 'node:fs'; - -const sidepanel = readFileSync('browser-extension/sidepanel.js', 'utf8'); - -const reportMatch = sidepanel.match(/function buildDiagnosticReport\(capture\) \{[\s\S]*?\n\}/); -if (!reportMatch) throw new Error('Side panel must keep buildDiagnosticReport available for privacy checks.'); - -const reportBody = reportMatch[0]; -const requiredStructureFields = [ - 'page:', - 'title:', - 'url:', - 'host:', - 'origin:', - 'ai:', - 'provider:', - 'matchedSelectors:', - 'promptLength:', - 'turnCount:', - 'manualValidation:' -]; -for (const field of requiredStructureFields) { - if (!reportBody.includes(field)) throw new Error(`Diagnostic report privacy check expected structural field ${field}.`); -} - -const forbiddenRawFields = [ - 'promptDraft', - 'turns', - 'selection', - 'description', - 'headings', - 'candidates', - 'memories', - 'lessons', - 'draftContent', - 'content:' -]; -for (const field of forbiddenRawFields) { - if (reportBody.includes(field)) throw new Error(`Diagnostic report must not copy raw page/chat content field: ${field}.`); -} - -const fakeCapture = { - page: { - title: 'ChatGPT - 私密项目标题', - url: 'https://chatgpt.com/c/private-thread', - host: 'chatgpt.com', - origin: 'https://chatgpt.com', - type: 'ai-chat', - typeLabel: 'ChatGPT', - selection: 'PRIVATE_SELECTED_TEXT_SHOULD_NOT_LEAK', - description: 'PRIVATE_DESCRIPTION_SHOULD_NOT_LEAK', - headings: ['PRIVATE_HEADING_SHOULD_NOT_LEAK'] - }, - conversation: { - provider: 'ChatGPT', - promptDraft: 'PRIVATE_PROMPT_DRAFT_SHOULD_NOT_LEAK', - turns: [ - { role: 'user', text: 'PRIVATE_USER_TURN_SHOULD_NOT_LEAK' }, - { role: 'assistant', text: 'PRIVATE_ASSISTANT_TURN_SHOULD_NOT_LEAK' } - ] - }, - diagnostics: { - supportedAiPage: true, - provider: 'ChatGPT', - editorFound: true, - editorSelector: '[contenteditable="true"]', - anchorFound: true, - anchorSelector: 'form', - anchorSource: 'form', - adjacentSelector: 'button', - sendFound: true, - sendSelector: 'button[data-testid="send-button"]', - turnSelector: '[data-message-author-role]', - turnSelectorCount: 2, - matchedSelectors: { - editor: '[contenteditable="true"]', - anchor: 'form', - anchorSource: 'form', - adjacent: 'button', - send: 'button[data-testid="send-button"]', - turn: '[data-message-author-role]' - }, - placement: 'after-editor', - promptLength: 36, - turnCount: 2, - checkedAt: '2026-06-09T00:00:00.000Z' - }, -}; - -function makeReport(capture) { - const page = capture && capture.page ? capture.page : {}; - const diagnostics = capture && capture.diagnostics ? capture.diagnostics : {}; - const conversation = capture && capture.conversation ? capture.conversation : {}; - const manifest = { name: 'Agent Memory Lab', version: '0.1.0', manifest_version: 3 }; - const matched = diagnostics.matchedSelectors || {}; - return { - product: 'Agent Memory Lab Browser Extension', - extension: { - name: manifest.name || 'Agent Memory Lab', - version: manifest.version || '', - manifestVersion: manifest.manifest_version || 3 - }, - generatedAt: new Date('2026-06-09T00:00:00.000Z').toISOString(), - validationGuide: { - title: '浏览器插件真实 AI 站点测试卡', - path: '/docs/browser-extension-ai-site-test-cards-cn.md', - requiredProducts: ['ChatGPT', 'Claude', 'Gemini', 'Perplexity'] - }, - page: { - title: page.title || '', - url: page.url || '', - host: page.host || '', - origin: page.origin || '', - type: page.type || '', - typeLabel: page.typeLabel || '' - }, - ai: { - supportedAiPage: !!diagnostics.supportedAiPage, - provider: diagnostics.provider || conversation.provider || '', - editorFound: !!diagnostics.editorFound, - editorSelector: diagnostics.editorSelector || '', - anchorFound: !!diagnostics.anchorFound, - anchorSelector: diagnostics.anchorSelector || '', - anchorSource: diagnostics.anchorSource || '', - adjacentSelector: diagnostics.adjacentSelector || '', - sendFound: !!diagnostics.sendFound, - sendSelector: diagnostics.sendSelector || '', - turnSelector: diagnostics.turnSelector || '', - turnSelectorCount: diagnostics.turnSelectorCount || 0, - matchedSelectors: { - editor: matched.editor || diagnostics.editorSelector || '', - anchor: matched.anchor || diagnostics.anchorSelector || '', - anchorSource: matched.anchorSource || diagnostics.anchorSource || '', - adjacent: matched.adjacent || diagnostics.adjacentSelector || '', - send: matched.send || diagnostics.sendSelector || '', - turn: matched.turn || diagnostics.turnSelector || '' - }, - placement: diagnostics.placement || '', - promptLength: diagnostics.promptLength || 0, - turnCount: diagnostics.turnCount || 0, - checkedAt: diagnostics.checkedAt || '' - }, - manualValidation: { - diagnosticsCopied: true, - siteInputStillWorks: false, - browser: '填写浏览器名称和版本', - notes: '填写无隐私信息的验收备注' - } - }; -} - -const reportText = JSON.stringify(makeReport(fakeCapture), null, 2); -for (const secret of [ - 'PRIVATE_SELECTED_TEXT_SHOULD_NOT_LEAK', - 'PRIVATE_DESCRIPTION_SHOULD_NOT_LEAK', - 'PRIVATE_HEADING_SHOULD_NOT_LEAK', - 'PRIVATE_PROMPT_DRAFT_SHOULD_NOT_LEAK', - 'PRIVATE_USER_TURN_SHOULD_NOT_LEAK', - 'PRIVATE_ASSISTANT_TURN_SHOULD_NOT_LEAK' -]) { - if (reportText.includes(secret)) throw new Error(`Diagnostic JSON leaked private content: ${secret}`); -} - -for (const expected of ['promptLength', 'turnCount', 'matchedSelectors', 'manualValidation', 'ChatGPT']) { - if (!reportText.includes(expected)) throw new Error(`Diagnostic JSON missing expected non-sensitive evidence field: ${expected}`); -} - -console.log('browser extension diagnostic privacy checks ok'); diff --git a/scripts/check-browser-extension-fixtures.mjs b/scripts/check-browser-extension-fixtures.mjs deleted file mode 100644 index 39e6c46e..00000000 --- a/scripts/check-browser-extension-fixtures.mjs +++ /dev/null @@ -1,166 +0,0 @@ -import { AI_PROVIDERS, getProviderForHost } from '../browser-extension/shared/site-config.js'; - -const fixtures = [ - { - id: 'agentmemoryDemo', - host: 'localhost', - path: '/demo/browser-extension.html', - html: ` -
-
We need a local preview for external testers.
-
The demo should show the memory hint near the prompt.
-
How should we improve the extension demo?
-
- ` - }, - { - id: 'chatgpt', - host: 'chatgpt.com', - html: ` -
-
Please remember this product direction for later.
-
I will keep the local-first review workflow in mind.
-
How should we improve the browser extension?
-
- ` - }, - { - id: 'claude', - host: 'claude.ai', - html: ` -
-
Use the Agent Memory Lab design language.
-
Keep the UI restrained and local-first.
-
Summarize relevant memory before I ask.
-
- ` - }, - { - id: 'gemini', - host: 'gemini.google.com', - html: ` -
- Find my memory about browser plugins. - Here is the relevant local context. -
What did we learn from Mem0?
-
- ` - }, - { - id: 'perplexity', - host: 'www.perplexity.ai', - html: ` -
-
Compare cross-AI memory tools.
-
Agent Memory Lab keeps long-term writes reviewable.
-
-
- ` - }, - { - id: 'grok', - host: 'grok.com', - html: ` -
-
Use previous product decisions.
-
I found the local-first plugin plan.
- -
- ` - }, - { - id: 'deepseek', - host: 'chat.deepseek.com', - html: ` -
-
Remember the review queue requirement.
-
The extension should not write silently.
-
Generate the next delivery checklist.
-
- ` - } -]; - -function assert(condition, message) { - if (!condition) throw new Error(message); -} - -function normalizeHtml(html) { - return String(html || '').replace(/\s+/g, ' '); -} - -function selectorMatches(html, selector) { - const normalized = normalizeHtml(html); - const alternatives = String(selector || '').split(',').map((item) => item.trim()).filter(Boolean); - return alternatives.some((item) => simpleSelectorMatches(normalized, item)); -} - -function simpleSelectorMatches(html, selector) { - const parts = selector.split(/\s+/).filter(Boolean); - if (parts.length > 1) { - let cursor = 0; - for (const part of parts) { - const index = findSimplePart(html.slice(cursor), part); - if (index < 0) return false; - cursor += index + 1; - } - return true; - } - return findSimplePart(html, selector) >= 0; -} - -function findSimplePart(html, selector) { - if (selector.startsWith('#')) { - return html.search(new RegExp(`<[^>]+\\bid=["']${escapeRegExp(selector.slice(1))}["'][^>]*>`, 'i')); - } - - const tagMatch = selector.match(/^([a-z0-9-]+)/i); - const classMatch = selector.match(/\.([a-z0-9_-]+)/i); - const attrMatch = selector.match(/\[([^\]=*~^$|]+)(\*?=)?["']?([^"'\]]*)["']?\]/i); - const tag = tagMatch ? tagMatch[1] : '[a-z0-9-]+'; - const tagPattern = `<${tag}\\b[^>]*`; - - if (classMatch) { - const className = escapeRegExp(classMatch[1]); - const pattern = `${tagPattern}\\bclass=["'][^"']*(^|\\s)${className}(\\s|$)[^"']*["'][^>]*>`; - if (html.search(new RegExp(pattern, 'i')) < 0) return -1; - } - - if (attrMatch) { - const name = escapeRegExp(attrMatch[1].trim()); - const operator = attrMatch[2] || ''; - const value = escapeRegExp(attrMatch[3] || ''); - const attrPattern = operator === '*=' - ? `${tagPattern}\\b${name}=["'][^"']*${value}[^"']*["'][^>]*>` - : operator - ? `${tagPattern}\\b${name}=["']${value}["'][^>]*>` - : `${tagPattern}\\b${name}(=["'][^"']*["'])?[^>]*>`; - return html.search(new RegExp(attrPattern, 'i')); - } - - if (classMatch) return html.search(new RegExp(`${tagPattern}>`, 'i')); - return html.search(new RegExp(`<${escapeRegExp(selector)}\\b[^>]*>`, 'i')); -} - -function escapeRegExp(value) { - return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -} - -for (const fixture of fixtures) { - globalThis.location = { pathname: fixture.path || '/' }; - const provider = getProviderForHost(fixture.host); - assert(provider, `${fixture.id}: provider not found for ${fixture.host}`); - assert(provider.id === fixture.id, `${fixture.id}: expected provider ${fixture.id}, got ${provider.id}`); - assert(provider.editorSelectors.some((selector) => selectorMatches(fixture.html, selector)), `${fixture.id}: no editor selector matches fixture`); - assert(provider.turnSelectors.some((selector) => selectorMatches(fixture.html, selector)), `${fixture.id}: no turn selector matches fixture`); -} - -for (const provider of Object.values(AI_PROVIDERS)) { - assert(Array.isArray(provider.hosts) && provider.hosts.length > 0, `${provider.id}: missing hosts`); - assert(Array.isArray(provider.editorSelectors) && provider.editorSelectors.length > 0, `${provider.id}: missing editor selectors`); - assert(Array.isArray(provider.anchorSelectors) && provider.anchorSelectors.length > 0, `${provider.id}: missing anchor selectors`); - assert(Array.isArray(provider.turnSelectors) && provider.turnSelectors.length > 0, `${provider.id}: missing turn selectors`); - assert(['input-corner', 'toolbar-end'].includes(provider.placement), `${provider.id}: unsupported placement ${provider.placement}`); -} - -console.log(`browser extension AI fixtures ok (${fixtures.length} providers)`); diff --git a/scripts/check-browser-extension-memory-drafts.mjs b/scripts/check-browser-extension-memory-drafts.mjs deleted file mode 100644 index 17f03c06..00000000 --- a/scripts/check-browser-extension-memory-drafts.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import { createPageCapture } from '../browser-extension/shared/schema.js'; - -function assert(condition, message) { - if (!condition) throw new Error(message); -} - -const capture = createPageCapture({ - title: '了解用户背景', - url: 'https://chatgpt.com/c/example', - host: 'chatgpt.com', - aiProvider: 'ChatGPT', - description: 'This should not be copied into capture.', - selection: 'This should not be copied into capture.', - promptDraft: 'This should not be copied into capture.', - headings: ['This should not be copied into capture.'], - turns: [ - { role: 'user', text: '请把这段网页 AI 会话同步到本地工作台。' }, - { role: 'assistant', text: '可以,插件会只保留会话原文,后续整理在工作台完成。' } - ] -}); - -assert(capture.page.title === '了解用户背景', 'Capture should keep page title for source display.'); -assert(capture.page.host === 'chatgpt.com', 'Capture should keep source host.'); -assert(capture.page.type === 'ai-chat', 'Capture should identify supported AI chat pages.'); -assert(capture.conversation.provider === 'ChatGPT', 'Capture should keep AI provider.'); -assert(capture.conversation.turns.length === 2, 'Capture should preserve conversation turns.'); -assert(!('description' in capture.page), 'Capture must not include page description.'); -assert(!('selection' in capture.page), 'Capture must not include selected page text.'); -assert(!('headings' in capture.page), 'Capture must not include page headings.'); -assert(!('promptDraft' in capture.conversation), 'Capture must not include input drafts.'); -assert(!('candidates' in capture), 'Capture must not generate memory candidates.'); -assert(!('privacy' in capture), 'Capture must not run plugin-side memory/privacy classification.'); - -const text = JSON.stringify(capture); -for (const forbidden of ['This should not be copied into capture', '候选事实', '经验:']) { - assert(!text.includes(forbidden), `Capture leaked non-conversation extraction data: ${forbidden}`); -} - -console.log('browser extension conversation capture checks ok'); diff --git a/scripts/check-browser-extension-package.mjs b/scripts/check-browser-extension-package.mjs deleted file mode 100644 index 478e6337..00000000 --- a/scripts/check-browser-extension-package.mjs +++ /dev/null @@ -1,60 +0,0 @@ -import { spawnSync } from 'node:child_process'; -import { existsSync, readFileSync } from 'node:fs'; - -const zipPath = 'artifacts/agent-memory-lab-extension.zip'; -if (!existsSync(zipPath)) throw new Error(`Missing ${zipPath}. Run npm run package:browser-extension first.`); - -const result = spawnSync('unzip', ['-Z1', zipPath], { encoding: 'utf8' }); -if (result.status !== 0) throw new Error(`Failed to inspect ${zipPath}: ${result.stderr || result.stdout}`); - -const entries = result.stdout.split(/\r?\n/).filter(Boolean); -const entrySet = new Set(entries); -const required = [ - 'browser-extension/manifest.json', - 'browser-extension/content-script.js', - 'browser-extension/service-worker.js', - 'browser-extension/sidepanel.html', - 'browser-extension/sidepanel.js', - 'browser-extension/sidepanel.css', - 'browser-extension/popup.html', - 'browser-extension/popup.js', - 'browser-extension/options.html', - 'browser-extension/options.js', - 'browser-extension/shared/schema.js', - 'browser-extension/shared/site-config.js', - 'browser-extension/icons/icon16.png', - 'browser-extension/icons/icon32.png', - 'browser-extension/icons/icon48.png', - 'browser-extension/icons/icon128.png', - 'browser-extension/README.md', - 'browser-extension/LOAD-THIS-FIRST.md', - 'browser-extension/PACKAGE-MANIFEST.md', - 'browser-extension/AI-SITE-TEST-CARDS.md' -]; - -const missing = required.filter((entry) => !entrySet.has(entry)); -if (missing.length) throw new Error(`Extension package is missing: ${missing.join(', ')}`); - -const forbidden = entries.filter((entry) => entry.includes('__MACOSX') || entry.endsWith('.DS_Store')); -if (forbidden.length) throw new Error(`Extension package includes forbidden macOS metadata: ${forbidden.join(', ')}`); - -if (!entries.every((entry) => entry === 'browser-extension/' || entry.startsWith('browser-extension/'))) { - throw new Error('Extension package must contain only the browser-extension folder.'); -} - -const loadGuide = readFileSync('browser-extension/LOAD-THIS-FIRST.md', 'utf8'); -for (const marker of ['五步验收', '#sessions', '已抓取', 'AI-SITE-TEST-CARDS.md', '复制问题信息']) { - if (!loadGuide.includes(marker)) throw new Error(`Zip loading guide missing marker: ${marker}`); -} - -const packageManifest = readFileSync('artifacts/browser-extension/PACKAGE-MANIFEST.md', 'utf8'); -for (const marker of ['Agent Memory Lab Extension Package', 'Commit:', 'Real AI Evidence Status', '复制问题信息', '复制检查步骤', 'matchedSelectors.editor']) { - if (!packageManifest.includes(marker)) throw new Error(`Package manifest missing marker: ${marker}`); -} - -const siteCards = readFileSync('browser-extension/AI-SITE-TEST-CARDS.md', 'utf8'); -for (const marker of ['ChatGPT', 'Claude', 'Gemini', 'Perplexity', 'turnCount > 0', '#sessions', 'manualValidation', '公开发布']) { - if (!siteCards.includes(marker)) throw new Error(`AI site test cards missing marker: ${marker}`); -} - -console.log(`browser extension package checks ok (${entries.length} entries)`); diff --git a/scripts/check-browser-extension-review-draft.mjs b/scripts/check-browser-extension-review-draft.mjs deleted file mode 100644 index abb31523..00000000 --- a/scripts/check-browser-extension-review-draft.mjs +++ /dev/null @@ -1,46 +0,0 @@ -import { readFileSync } from 'node:fs'; - -function assert(condition, message) { - if (!condition) throw new Error(message); -} - -function read(path) { - return readFileSync(path, 'utf8'); -} - -const popupHtml = read('browser-extension/popup.html'); -const popupJs = read('browser-extension/popup.js'); -const sidepanelHtml = read('browser-extension/sidepanel.html'); -const sidepanelJs = read('browser-extension/sidepanel.js'); -const serviceWorker = read('browser-extension/service-worker.js'); -const contentScript = read('browser-extension/content-script.js'); - -for (const [name, html] of [['popup', popupHtml], ['sidepanel', sidepanelHtml]]) { - assert(html.includes('draftAssist'), `${name}: missing sync status assist copy.`); - assert(html.includes('syncNow'), `${name}: missing manual retry sync action.`); - assert(html.includes('aria-label="打开工作台"'), `${name}: missing compact workbench icon.`); - for (const forbidden of ['draftTitle', 'draftContent', 'draftProject', 'draftTags', 'resetDraft', 'draftAsLesson', '加入待确认']) { - assert(!html.includes(forbidden), `${name}: extension should not expose manual memory-writing control ${forbidden}.`); - } -} - -assert(popupJs.includes('已自动抓取'), 'popup: missing automatic sync status.'); -assert(sidepanelJs.includes('正在自动同步'), 'sidepanel: missing automatic sync status.'); -assert(popupHtml.includes('closePopup'), 'popup: missing compact close control.'); -assert(sidepanelHtml.includes('closePanel'), 'sidepanel: missing compact close control.'); -assert(!popupHtml.includes('打开工作台') && !popupHtml.includes('查看会话'), 'popup: should not expose a large workbench button.'); -assert(!sidepanelHtml.includes('工作台') && !sidepanelHtml.includes('footer-actions'), 'sidepanel: should not expose a footer workbench button.'); -assert(contentScript.includes('scheduleConversationSync'), 'content script: missing automatic conversation sync scheduler.'); -assert(contentScript.includes('SYNC_PAGE_CONVERSATION'), 'content script: automatic sync must call SYNC_PAGE_CONVERSATION.'); -assert(popupJs.includes("send('SYNC_OPEN_AI_TABS'"), 'popup: manual retry must sync all open AI conversation tabs.'); -assert(sidepanelJs.includes("send('SYNC_OPEN_AI_TABS'"), 'sidepanel: manual retry must sync all open AI conversation tabs.'); -assert(!popupJs.includes("send('SAVE_CANDIDATE'"), 'popup: manual candidate saving should not be the primary extension flow.'); -assert(!sidepanelJs.includes("send('SAVE_CANDIDATE'"), 'sidepanel: manual candidate saving should not be the primary extension flow.'); -assert(!sidepanelHtml.includes('可复用经验'), 'sidepanel: reusable lesson/skill branch should stay out of the extension UX.'); - -assert(serviceWorker.includes('async function syncPageConversation'), 'service worker: missing browser conversation sync handler.'); -assert(serviceWorker.includes('syncOpenAiConversationTabs'), 'service worker: missing open AI tabs sync handler.'); -assert(serviceWorker.includes("mode: 'sync'"), 'service worker: sync payload must mark browser sync mode.'); -assert(serviceWorker.includes("source: 'browser-sync'"), 'service worker: sync payload must use browser-sync source.'); - -console.log('browser extension auto sync checks ok'); diff --git a/scripts/check-browser-extension-site-config-sync.mjs b/scripts/check-browser-extension-site-config-sync.mjs deleted file mode 100644 index a3f2ddae..00000000 --- a/scripts/check-browser-extension-site-config-sync.mjs +++ /dev/null @@ -1,88 +0,0 @@ -import { readFileSync } from 'node:fs'; -import vm from 'node:vm'; -import { AI_PROVIDERS } from '../browser-extension/shared/site-config.js'; - -function assert(condition, message) { - if (!condition) throw new Error(message); -} - -function extractContentProviders() { - const source = readFileSync('browser-extension/content-script.js', 'utf8'); - const startToken = 'const AI_PROVIDERS = ['; - const start = source.indexOf(startToken); - assert(start >= 0, 'Content script is missing AI_PROVIDERS.'); - - const arrayStart = source.indexOf('[', start); - let depth = 0; - let quote = ''; - let escaped = false; - for (let i = arrayStart; i < source.length; i += 1) { - const char = source[i]; - if (quote) { - if (escaped) { - escaped = false; - } else if (char === '\\') { - escaped = true; - } else if (char === quote) { - quote = ''; - } - continue; - } - if (char === '"' || char === "'" || char === '`') { - quote = char; - continue; - } - if (char === '[') depth += 1; - if (char === ']') { - depth -= 1; - if (depth === 0) { - const literal = source.slice(arrayStart, i + 1); - return vm.runInNewContext(`(${literal})`, Object.create(null)); - } - } - } - throw new Error('Could not parse content script AI_PROVIDERS array.'); -} - -function normalizeProvider(provider) { - const normalized = {}; - for (const key of [ - 'id', - 'label', - 'hosts', - 'pathIncludes', - 'editorSelectors', - 'anchorSelectors', - 'adjacentSelectors', - 'placement', - 'turnSelectors', - 'sendSelectors' - ]) { - if (provider[key] !== undefined) normalized[key] = provider[key]; - } - return normalized; -} - -const sharedProviders = Object.values(AI_PROVIDERS).map(normalizeProvider); -const contentProviders = extractContentProviders().map(normalizeProvider); - -assert(sharedProviders.length === contentProviders.length, `Provider count mismatch: shared=${sharedProviders.length}, content=${contentProviders.length}`); - -const sharedById = new Map(sharedProviders.map((provider) => [provider.id, provider])); -const contentById = new Map(contentProviders.map((provider) => [provider.id, provider])); - -for (const id of sharedById.keys()) { - assert(contentById.has(id), `Content script missing provider ${id}.`); -} -for (const id of contentById.keys()) { - assert(sharedById.has(id), `Shared site config missing provider ${id}.`); -} - -for (const [id, shared] of sharedById.entries()) { - const content = contentById.get(id); - const sharedJson = JSON.stringify(shared); - const contentJson = JSON.stringify(content); - assert(sharedJson === contentJson, `${id}: content script site config drifted from shared/site-config.js.`); -} - -console.log(`browser extension site config sync ok (${sharedProviders.length} providers)`); diff --git a/scripts/check-browser-extension.mjs b/scripts/check-browser-extension.mjs deleted file mode 100644 index 61bebb06..00000000 --- a/scripts/check-browser-extension.mjs +++ /dev/null @@ -1,185 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; - -const manifest = JSON.parse(readFileSync('browser-extension/manifest.json', 'utf8')); -if ((manifest.content_scripts || []).some((script) => script.type === 'module')) { - throw new Error('Chrome content scripts must not be declared as module scripts.'); -} - -const files = [ - 'browser-extension/content-script.js', - 'browser-extension/service-worker.js', - 'browser-extension/popup.js', - 'browser-extension/options.js', - 'browser-extension/sidepanel.js', - 'browser-extension/shared/schema.js', - 'browser-extension/shared/api.js', - 'browser-extension/shared/page-types.js', - 'browser-extension/shared/site-config.js' -]; - -for (const file of files) { - const result = spawnSync(process.execPath, ['--check', file], { stdio: 'inherit' }); - if (result.status !== 0) throw new Error(`${file} failed syntax check.`); -} - -function readPngSize(file) { - const buf = readFileSync(file); - const signature = '89504e470d0a1a0a'; - if (buf.subarray(0, 8).toString('hex') !== signature) throw new Error(`${file} is not a PNG.`); - return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) }; -} - -for (const size of [16, 32, 48, 128]) { - const iconPath = manifest.icons && manifest.icons[String(size)]; - if (iconPath !== `icons/icon${size}.png`) throw new Error(`Manifest icon ${size} must point to icons/icon${size}.png.`); - const actual = readPngSize(`browser-extension/${iconPath}`); - if (actual.width !== size || actual.height !== size) { - throw new Error(`${iconPath} must be ${size}x${size}, got ${actual.width}x${actual.height}.`); - } -} - -const contentScript = readFileSync('browser-extension/content-script.js', 'utf8'); -const serviceWorker = readFileSync('browser-extension/service-worker.js', 'utf8'); -const popupHtml = readFileSync('browser-extension/popup.html', 'utf8'); -const popupJs = readFileSync('browser-extension/popup.js', 'utf8'); -const sidepanelHtml = readFileSync('browser-extension/sidepanel.html', 'utf8'); -const sidepanel = readFileSync('browser-extension/sidepanel.js', 'utf8'); -const schema = readFileSync('browser-extension/shared/schema.js', 'utf8'); -const manifestText = readFileSync('browser-extension/manifest.json', 'utf8'); -const siteConfig = readFileSync('browser-extension/shared/site-config.js', 'utf8'); -const sidepanelCss = readFileSync('browser-extension/sidepanel.css', 'utf8'); -const sharedApi = readFileSync('browser-extension/shared/api.js', 'utf8'); -const config = readFileSync('browser-extension/config.js', 'utf8'); -if (!manifest.description || !/自动抓取网页 AI 会话/.test(manifest.description)) { - throw new Error('Manifest description must be Chinese and describe browser conversation sync.'); -} -const contentProviders = [...contentScript.matchAll(/id:\s*'([^']+)'/g)].map((match) => match[1]); -const sharedProviders = [...siteConfig.matchAll(/\n\s*([a-z0-9_-]+):\s*\{\s*\n\s*id:\s*'([^']+)'/g)].map((match) => match[2]); - -const missingInContent = sharedProviders.filter((id) => !contentProviders.includes(id)); -const missingInShared = contentProviders.filter((id) => !sharedProviders.includes(id)); -if (missingInContent.length || missingInShared.length) { - throw new Error(`Provider config mismatch. Missing in content: ${missingInContent.join(', ') || 'none'}; missing in shared: ${missingInShared.join(', ') || 'none'}`); -} - -const menuContexts = JSON.stringify(manifest.permissions || []) + serviceWorker; -if (!menuContexts.includes("contexts: ['page']") || !serviceWorker.includes('sync-page-conversation')) { - throw new Error('Context menu must expose page-level workbench sync, not manual memory saving.'); -} -if (serviceWorker.includes("id: 'save-page-memory'") || serviceWorker.includes("contexts: ['page', 'selection', 'link']")) { - throw new Error('Context menu must not expose manual page/selection/link memory saving.'); -} -if (!popupJs.includes('已自动抓取') || !popupHtml.includes('draftAssist') || !popupHtml.includes('syncNow')) { - throw new Error('Popup must expose automatic browser conversation sync status.'); -} -if (!popupHtml.includes('closePopup') || !popupHtml.includes('aria-label="打开工作台"') || popupHtml.includes('打开工作台') || popupHtml.includes('查看会话')) { - throw new Error('Popup must use compact top-right workbench controls instead of a large workbench button.'); -} -for (const forbidden of [ - 'draftAsLesson', - 'draftTitle', - 'draftContent', - 'draftProject', - 'draftTags', - '整理成可复用经验', - '加入待确认', - '保存到记忆', - '本地判断', - '记忆候选', - '候选记忆', - '暂时没有记忆建议', - '记忆入口', - '记忆建议', - '待确认', - '经验候选' -]) { - if (popupHtml.includes(forbidden) || popupJs.includes(forbidden) || sidepanelHtml.includes(forbidden) || sidepanel.includes(forbidden)) { - throw new Error(`Extension must not expose memory candidate UI: ${forbidden}.`); - } -} -for (const forbidden of ['SAVE_PAGE_MEMORY', 'SAVE_PAGE_LESSON', 'SAVE_CANDIDATE', 'SEARCH_MEMORIES', 'buildBrowserMemoryDraft', 'buildBrowserLessonDraft', 'captureToMemoryPayload', 'captureToLessonPayload', 'candidates:', 'DEMO_MEMORIES', 'agent-memory-lab-widget', 'data-insert-memory', 'data-copy-memory']) { - if (contentScript.includes(forbidden) || serviceWorker.includes(forbidden) || schema.includes(forbidden) || sidepanel.includes(forbidden) || popupJs.includes(forbidden)) { - throw new Error(`Extension must only sync conversations; found stale path: ${forbidden}.`); - } -} -if (!contentScript.includes('scheduleConversationSync') || !contentScript.includes('SYNC_PAGE_CONVERSATION')) { - throw new Error('Content script must automatically sync browser conversations to the workbench.'); -} -if (!contentScript.includes('MAX_CAPTURE_TURNS = 160') || contentScript.includes('return turns.slice(-8)') || schema.includes('slice(-8)')) { - throw new Error('Browser conversation capture must preserve full AI sessions instead of only the latest 8 turns.'); -} -if (!serviceWorker.includes('async function syncPageConversation') || !serviceWorker.includes("source: 'browser-sync'") || !serviceWorker.includes("mode: 'sync'")) { - throw new Error('Service worker must send browser conversations as automatic sync events.'); -} -if (!popupJs.includes("send('SYNC_OPEN_AI_TABS'") || !sidepanel.includes("send('SYNC_OPEN_AI_TABS'")) { - throw new Error('Popup and side panel must support syncing all open AI conversation tabs.'); -} -if (!serviceWorker.includes('syncOpenAiConversationTabs') || !serviceWorker.includes('chrome.tabs.query({})') || !serviceWorker.includes('chrome.alarms.create')) { - throw new Error('Service worker must scan open AI conversation tabs automatically.'); -} -if (!serviceWorker.includes('conversation: capture.conversation')) { - throw new Error('Browser sync events must preserve captured browser conversation turns.'); -} -if (!popupHtml.includes('本地工作台') || !popupHtml.includes('versionInfo') || popupHtml.includes('openGuide')) { - throw new Error('Popup must expose local workbench status and version without a tester guide entry.'); -} -if (!popupJs.includes('getManifest') || popupJs.includes('external-tester-guide-cn.md')) { - throw new Error('Popup must render extension version without linking stale external guide docs.'); -} -for (const stalePageField of ['description:', 'selection:', 'headings:', 'promptDraft:', 'privacy:', 'candidates:']) { - if (schema.includes(stalePageField)) throw new Error(`Shared capture schema must not include page extraction field: ${stalePageField}`); -} -if (!sidepanel.includes('正在自动同步') || !sidepanelHtml.includes('draftAssist') || !sidepanelHtml.includes('syncNow')) { - throw new Error('Side panel must expose automatic browser conversation sync status.'); -} -if (!sidepanelHtml.includes('closePanel') || !sidepanelHtml.includes('aria-label="打开工作台"') || sidepanelHtml.includes('工作台') || sidepanelHtml.includes('footer-actions')) { - throw new Error('Side panel must use compact top-right workbench controls instead of a footer workbench button.'); -} -if (sidepanel.includes('data-draft-kind') || sidepanel.includes('SAVE_CANDIDATE')) { - throw new Error('Side panel must not route users through manual candidate editing.'); -} -if (sidepanelHtml.includes('openTestCards')) { - throw new Error('Side panel should not expose tester cards in the ordinary extension flow.'); -} -if (!sidepanelHtml.includes('copyEvidenceCommand') || !sidepanelHtml.includes('复制检查步骤') || !sidepanel.includes('buildEvidenceCommand') || !sidepanel.includes('wizard:ai-validation-evidence')) { - throw new Error('Side panel must expose a copyable AI validation evidence wizard command.'); -} -for (const field of ['aiValidationSummary', 'validation-summary']) { - if (!sidepanelHtml.includes(field) && !sidepanel.includes(field) && !sidepanelCss.includes(field)) throw new Error(`Side panel must expose the AI validation card field: ${field}.`); -} -for (const label of ['页面识别', '会话抓取正常', '识别不准时再使用']) { - if (!sidepanelHtml.includes(label) && !sidepanel.includes(label)) throw new Error(`Side panel AI diagnostics missing label: ${label}.`); -} -if (!sharedApi.includes('path =') || !serviceWorker.includes('message.path')) { - throw new Error('OPEN_VIEWER must support local viewer document paths.'); -} -if (!sharedApi.includes('resolveApiBase') || !config.includes('viewerFallbackBase') || !config.includes('hasAgentMemoryApi')) { - throw new Error('Extension API client must fall back to the local viewer proxy when the REST API route is unavailable.'); -} -if (!sharedApi.includes('resolveViewerBase') || !manifestText.includes('localhost:3114')) { - throw new Error('Extension must discover the active Viewer port and allow the 3114 fallback viewer.'); -} - -for (const field of ['anchorFound', 'placement', 'checkedAt', 'anchorSelector', 'anchorSource', 'adjacentSelector', 'sendFound', 'sendSelector', 'turnSelector', 'turnSelectorCount', 'matchedSelectors']) { - if (!contentScript.includes(field)) throw new Error(`Content script diagnostics missing ${field}.`); - if (!schema.includes(field)) throw new Error(`Shared schema diagnostics must preserve ${field}.`); - if (!sidepanel.includes(field)) throw new Error(`Side panel diagnostics must expose ${field}.`); -} -for (const label of ['页面', '输入框', '发送按钮', '已抓取会话']) { - if (!sidepanel.includes(label)) throw new Error(`Side panel diagnostics must show ${label}.`); -} -for (const internalLabel of ['输入规则', '锚点规则', '相邻控件', '发送规则', '会话规则']) { - if (sidepanel.includes(internalLabel)) throw new Error(`Side panel must not expose internal selector label: ${internalLabel}.`); -} -for (const field of ['getManifest', 'manifestVersion', 'version']) { - if (!sidepanel.includes(field)) throw new Error(`Diagnostic report must include extension ${field}.`); -} -for (const field of ['manualValidation', 'diagnosticsCopied', 'siteInputStillWorks']) { - if (!sidepanel.includes(field)) throw new Error(`Diagnostic report must include manual validation template field ${field}.`); -} -for (const field of ['validationGuide', 'requiredProducts', 'ChatGPT', 'Claude', 'Gemini', 'Perplexity', '/docs/browser-extension-ai-site-test-cards-cn.md']) { - if (!sidepanel.includes(field)) throw new Error(`Diagnostic report must include validation guide field ${field}.`); -} - -console.log('browser extension checks ok'); diff --git a/scripts/check-consistency-local.mjs b/scripts/check-consistency-local.mjs deleted file mode 100644 index 9b5b1008..00000000 --- a/scripts/check-consistency-local.mjs +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env node -// 本地一致性快速自检(秒级,无需 build)。 -// -// 为什么:碰 MCP 工具 / REST 端点 / 版本号要按 AGENTS.md 同步改多处, -// 否则 test/consistency.test.ts 在 CI 4 格全量跑完(~7min)才报红。本脚本 -// 把其中**纯文件级**的计数/版本断言提前到本地秒级反馈,推前先跑一道。 -// -// 注:工具数用 README 里被 consistency.test 锁定的 "N MCP tools" 做交叉核对, -// 不导入 getAllTools(避免依赖 build)。完整断言仍以 npm test 为准。 -// -// 用法: node scripts/check-consistency-local.mjs -// 退出码: 0 全过 / 1 有不一致 - -import { readFileSync } from "node:fs"; -import { fileURLToPath } from "node:url"; -import { dirname, join } from "node:path"; - -const ROOT = join(dirname(fileURLToPath(import.meta.url)), ".."); -const read = (p) => readFileSync(join(ROOT, p), "utf-8"); - -const problems = []; -const ok = []; -function check(label, pass, hint) { - (pass ? ok : problems).push(pass ? label : `${label}\n → ${hint}`); -} - -// --- 版本号 (package.json 为源) --- -const pkg = JSON.parse(read("package.json")); -const ver = pkg.version; - -const versionFt = read("src/version.ts"); -check( - "src/version.ts VERSION === package.json", - versionFt.includes(`"${ver}"`), - `version.ts 未含 "${ver}"。改版本要同步 7 处(见 docs 一致性铁律)`, -); - -const claudePlugin = JSON.parse(read("plugin/.claude-plugin/plugin.json")); -check( - "plugin/.claude-plugin/plugin.json version === package.json", - claudePlugin.version === ver, - `plugin.json 是 ${claudePlugin.version},应为 ${ver}`, -); - -const exportImport = read("src/functions/export-import.ts"); -check( - "export-import.ts supportedVersions 含当前版本", - exportImport.includes(`"${ver}"`), - `export-import.ts 未含 "${ver}"`, -); - -// --- REST 端点计数 (src/triggers/api.ts 为源) --- -const apiSrc = read("src/triggers/api.ts"); -const restCount = [...apiSrc.matchAll(/api_path:\s*["`]/g)].length; - -const readme = read("README.md"); -const agents = read("AGENTS.md"); -const indexSrc = read("src/index.ts"); -check( - `README "${restCount} endpoints on port"`, - readme.includes(`${restCount} endpoints on port`), - `README 的 REST 端点数与实际 ${restCount} 不符`, -); -check( - `AGENTS.md "${restCount} REST endpoints"`, - agents.includes(`${restCount} REST endpoints`), - `AGENTS.md 的 REST 端点数与实际 ${restCount} 不符`, -); -check( - `src/index.ts "REST API: ${restCount} endpoints"`, - indexSrc.includes(`REST API: ${restCount} endpoints`), - `src/index.ts 的硬编码端点数与实际 ${restCount} 不符`, -); - -// --- MCP 工具计数 (README 被 consistency.test 锁定,做交叉核对) --- -const toolMatch = readme.match(/(\d+)\s+MCP tools/); -if (toolMatch) { - const n = toolMatch[1]; - check( - `README "${n} tools, 6 resources" 与 "${n} MCP tools" 自洽`, - readme.includes(`${n} tools, 6 resources`), - `README 两处工具数不一致(MCP tools=${n})`, - ); -} else { - check("README 含 MCP 工具计数", false, 'README 未找到 "N MCP tools"'); -} - -// --- 输出 --- -console.log(`\n一致性自检 (源: package.json v${ver}, REST ${restCount} 端点)\n`); -for (const o of ok) console.log(` ✓ ${o}`); -if (problems.length) { - console.log(""); - for (const p of problems) console.log(` ✗ ${p}`); - console.log( - `\n${problems.length} 处不一致。完整断言以 \`npm test\`(test/consistency.test.ts) 为准。`, - ); - process.exit(1); -} -console.log("\n全部一致 ✓ (完整断言仍以 npm test 为准)"); diff --git a/scripts/check-delivery.mjs b/scripts/check-delivery.mjs deleted file mode 100644 index 73852c87..00000000 --- a/scripts/check-delivery.mjs +++ /dev/null @@ -1,439 +0,0 @@ -import { existsSync, readFileSync } from 'node:fs'; -import { spawn, spawnSync } from 'node:child_process'; - -function assert(condition, message) { - if (!condition) throw new Error(message); -} - -function read(path) { - return readFileSync(path, 'utf8'); -} - -function run(command, args) { - const result = spawnSync(command, args, { stdio: 'inherit' }); - assert(result.status === 0, `${command} ${args.join(' ')} failed`); -} - -function wait(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -async function fetchText(url, attempts = 20) { - let lastError; - for (let i = 0; i < attempts; i += 1) { - try { - const res = await fetch(url); - if (res.ok) return await res.text(); - lastError = new Error(`${url} returned ${res.status}`); - } catch (err) { - lastError = err; - } - await wait(150); - } - throw lastError || new Error(`Failed to fetch ${url}`); -} - -const requiredFiles = [ - 'README.md', - 'browser-extension/README.md', - 'docs/demo-checklist-cn.md', - 'docs/product-delivery-plan-cn.md', - 'docs/project-delivery-guide-cn.md', - 'docs/external-tester-guide-cn.md', - 'docs/external-feedback-template-cn.md', - 'docs/external-feedback-triage-cn.md', - '.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml', - 'docs/release-gates-cn.md', - 'docs/browser-extension-ai-validation-cn.md', - 'docs/browser-extension-ai-site-test-cards-cn.md', - 'docs/browser-extension-mem0-reference-cn.md', - 'docs/browser-extension-privacy-en.md', - 'docs/browser-extension-store-listing-en.md', - 'docs/feishu/agentmemory-project-intro-cn.md', - 'docs/feishu/whiteboards/workflow.mmd', - 'docs/feishu/whiteboards/workbench-workflow.mmd', - 'docs/feishu/whiteboards/structure.mmd', - 'docs/browser-extension-privacy-cn.md', - 'docs/readme-assets/screenshots/dashboard.jpg', - 'docs/readme-assets/screenshots/skills.jpg', - 'src/viewer/demo/browser-extension.html', - 'dist/viewer/demo/browser-extension.html', - 'browser-extension/manifest.json', - 'browser-extension/LOAD-THIS-FIRST.md', - 'browser-extension/AI-SITE-TEST-CARDS.md', - 'browser-extension/icons/icon16.png', - 'browser-extension/icons/icon32.png', - 'browser-extension/icons/icon48.png', - 'browser-extension/icons/icon128.png', - 'scripts/check-browser-extension.mjs', - 'scripts/check-browser-extension-site-config-sync.mjs', - 'scripts/check-browser-extension-review-draft.mjs', - 'scripts/check-browser-extension-memory-drafts.mjs', - 'scripts/check-browser-extension-diagnostics-privacy.mjs', - 'scripts/check-browser-extension-fixtures.mjs', - 'scripts/check-browser-extension-demo-interaction.mjs', - 'scripts/check-browser-extension-package.mjs', - 'scripts/check-remote-delivery.mjs', - 'scripts/check-release-gates.mjs', - 'scripts/record-ai-validation-evidence.mjs', - 'scripts/wizard-ai-validation-evidence.mjs', - 'scripts/prepare-ai-validation-run.mjs', - 'scripts/make-ai-validation-tester-pack.mjs', - 'scripts/check-ai-validation-evidence.mjs', - 'scripts/check-ai-validation-evidence-guards.mjs', - 'scripts/sync-ai-validation-table.mjs', - 'scripts/delivery-status.mjs', - 'scripts/check-viewer-delivery-runtime.mjs', - 'scripts/check-workbench-status.mjs', - 'scripts/package-browser-extension.mjs', - 'scripts/write-delivery-summary.mjs', - 'scripts/fixtures/ai-validation-diagnostic.json' -]; - -for (const file of requiredFiles) { - assert(existsSync(file), `Missing required delivery file: ${file}`); -} - -const readme = read('README.md'); -const packageJson = JSON.parse(read('package.json')); -assert(String(packageJson.scripts?.['package:browser-extension'] || '').includes('make:ai-validation-tester-pack'), 'package:browser-extension must refresh the AI validation tester pack.'); -assert(readme.includes('npm run preview:browser-extension'), 'README must mention browser extension self-check command.'); -assert(readme.includes('npm run build && npm run start:local-memory'), 'README must mention local memory workbench start command.'); -assert(readme.includes('npm run check:workbench'), 'README must mention workbench status check command.'); -assert(readme.includes('cd agentmemory-lab'), 'README must show project directory before npm commands.'); -assert(readme.includes('你的本地记忆数据目录'), 'README must explain which local memory store is used.'); -for (const marker of ['本地可用路径', '真实网页和 AI 页面', '回到 Viewer 的记忆库确认保存', '自检页']) { - assert(readme.includes(marker), `README missing usable workflow marker: ${marker}`); -} -assert(readme.includes('npm run check:release-gates'), 'README must mention release gates check command.'); -assert(readme.includes('npm run check:release-public'), 'README must mention public release check command.'); -assert(readme.includes('npm run status:delivery'), 'README must mention delivery status command.'); -assert(readme.includes('.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml'), 'README must link external tester issue template.'); -for (const marker of ['演示检查清单', '试用指南', '验收一页纸', '反馈模板', '发布门槛']) { - assert(readme.includes(marker), `README missing product doc marker: ${marker}`); -} -const imageRefs = [...readme.matchAll(/ match[1]); -const allowedImages = new Set([ - 'assets/banner.png', - 'docs/readme-assets/screenshots/dashboard.jpg', - 'docs/readme-assets/screenshots/skills.jpg' -]); -for (const ref of imageRefs) { - assert(allowedImages.has(ref), `Unexpected README image reference: ${ref}`); - assert(existsSync(ref), `README image does not exist: ${ref}`); -} -assert(imageRefs.includes('docs/readme-assets/screenshots/dashboard.jpg'), 'README must include dashboard screenshot.'); -assert(imageRefs.includes('docs/readme-assets/screenshots/skills.jpg'), 'README must include skills screenshot.'); - -const browserReadme = read('browser-extension/README.md'); -const deliveryGuide = read('docs/project-delivery-guide-cn.md'); -for (const marker of ['项目交付说明', 'szn-viewer-ui-iteration', 'npm run check:remote-delivery', 'ChatGPT', 'Claude', 'Gemini', 'Perplexity', '当前不能承诺']) { - assert(deliveryGuide.includes(marker), `Project delivery guide missing marker: ${marker}`); -} -assert(existsSync('iii-config.local-memory.yaml'), 'Local memory config must exist.'); -const localMemoryConfig = read('iii-config.local-memory.yaml'); -assert(localMemoryConfig.includes('state_store.db') && localMemoryConfig.includes('stream_store'), 'Local memory config must define state and stream stores.'); -assert(localMemoryConfig.includes('file_path: ./data/state_store.db'), 'Local memory state store must use repo-relative ./data path.'); -assert(localMemoryConfig.includes('file_path: ./data/stream_store'), 'Local memory stream store must use repo-relative ./data path.'); -assert(!localMemoryConfig.includes('/Users/'), 'Local memory config must not contain a personal absolute path.'); -assert(read('package.json').includes('start:local-memory'), 'package.json must expose start:local-memory.'); -assert(browserReadme.includes('npm run package:browser-extension'), 'Browser extension README must mention packaging command.'); -assert(browserReadme.includes('npm run status:delivery'), 'Browser extension README must mention delivery status command.'); -assert(browserReadme.includes('npm run preview:browser-extension'), 'Browser extension README must mention self-check command.'); -assert(browserReadme.includes('cd agentmemory-lab'), 'Browser extension README must show project directory before npm commands.'); -assert(browserReadme.includes('docs/browser-extension-privacy-cn.md'), 'Browser extension README must link privacy doc.'); -assert(browserReadme.includes('docs/browser-extension-mem0-reference-cn.md'), 'Browser extension README must link Mem0 reference doc.'); -assert(browserReadme.includes('docs/browser-extension-ai-site-test-cards-cn.md'), 'Browser extension README must link AI site test cards doc.'); -assert(browserReadme.includes('保存前编辑'), 'Browser extension README must mention edit-before-save flow.'); -assert(browserReadme.includes('插件是浏览器入口层,本地工作台是记忆中枢'), 'Browser extension README must explain extension/workbench relationship.'); -assert(browserReadme.includes('复制检查步骤'), 'Browser extension README must mention the copy evidence wizard flow.'); -for (const marker of ['项目', '标签', '经验候选']) { - assert(browserReadme.includes(marker), `Browser extension README must mention draft ${marker}.`); -} -assert(browserReadme.includes('同步侧栏'), 'Browser extension README must mention the side panel flow.'); -for (const marker of ['本地使用', '真实网页和 AI 页面', '本地自检页', '/demo/browser-extension.html']) { - assert(browserReadme.includes(marker), `Browser extension README missing usable flow marker: ${marker}`); -} - -const loadGuide = read('browser-extension/LOAD-THIS-FIRST.md'); -for (const marker of ['五步验收', '保存范围、分类备注', '经验候选', 'AI-SITE-TEST-CARDS.md', 'cd agentmemory-lab', 'npm run wizard:ai-validation-evidence', 'external-test-loop-cn.md', 'external-tester-feedback-cn.yml']) { - assert(loadGuide.includes(marker), `Zip loading guide missing marker: ${marker}`); -} - -const siteCards = read('docs/browser-extension-ai-site-test-cards-cn.md'); -for (const marker of ['真实 AI 站点测试卡', 'ChatGPT', 'Claude', 'Gemini', 'Perplexity', '复制检查步骤', 'manualValidation.memoryInsertPassed', 'manualValidation.diagnosticsCopied', 'manualValidation.siteInputStillWorks', 'npm run wizard:ai-validation-evidence', 'npm run prepare:ai-validation', 'turnCount > 0', 'matchedSelectors.turn', '用户选中的文字', '输入框草稿']) { - assert(siteCards.includes(marker), `AI site test cards missing marker: ${marker}`); -} -const zipSiteCards = read('browser-extension/AI-SITE-TEST-CARDS.md'); -for (const marker of ['ChatGPT', 'Claude', 'Gemini', 'Perplexity', '公开发布', 'npm run wizard:ai-validation-evidence', 'turnCount > 0', 'matchedSelectors.turn', '用户选中的文字', '输入框草稿']) { - assert(zipSiteCards.includes(marker), `Zip AI site test cards missing marker: ${marker}`); -} - -const checklist = read('docs/demo-checklist-cn.md'); -for (const marker of ['审阅队列可用', 'AI 页面状态', '记忆建议', 'Skill 管理台', 'cd agentmemory-lab']) { - assert(checklist.includes(marker), `Demo checklist missing marker: ${marker}`); -} - -const testerGuide = read('docs/external-tester-guide-cn.md'); -for (const marker of ['外部试用指南', '外部测试闭环', 'cd agentmemory-lab', 'npm run build && npm run start', 'npm run check:workbench', 'npm run check:release-gates', '记忆建议', '诊断 JSON', '复制检查步骤', '从仓库试用', '从 zip 试用', 'browser-extension/', '插件自检页', '/demo/browser-extension.html', 'Viewer 首页', '下载插件包', '验收一页纸', 'quickstart-cn.md', '反馈模板', '分诊指南', '外部试用反馈模板', 'external-tester-feedback-cn.yml', '外部反馈分诊指南', 'browser-extension-ai-site-test-cards-cn.md', 'npm run make:ai-validation-tester-pack', 'tester-pack-cn.md', 'turnCount', 'matchedSelectors.turn', '用户选中的文字', '输入框草稿']) { - assert(testerGuide.includes(marker), `External tester guide missing marker: ${marker}`); -} - -const testLoop = read('docs/external-test-loop-cn.md'); -for (const marker of ['外部测试闭环', '四步闭环', '加载插件', '真实页面使用', '逐站验收', '提交反馈', '最少要回收的信息', '交付判断', 'npm run check:ai-validation-evidence', 'turnCount > 0', '会话区域 selector', '输入框草稿']) { - assert(testLoop.includes(marker), `External test loop missing marker: ${marker}`); -} - -const issueTemplate = read('.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml'); -for (const marker of ['外部试用反馈', '试用路径', 'diagnostics', 'manualValidation', 'memoryInsertPassed', 'siteInputStillWorks', '我已确认反馈内容不包含敏感信息', 'turnCount', 'matchedSelectors', '真实对话', '输入框草稿']) { - assert(issueTemplate.includes(marker), `External tester issue template missing marker: ${marker}`); -} - -const feedbackTemplate = read('docs/external-feedback-template-cn.md'); -for (const marker of ['外部试用反馈模板', '外部反馈分诊指南', '基本信息', '试用路径', '问题描述', '诊断信息', 'manualValidation', '影响程度', 'turnCount', 'matchedSelectors.turn', '具体对话', '输入框草稿']) { - assert(feedbackTemplate.includes(marker), `External feedback template missing marker: ${marker}`); -} - -const feedbackTriage = read('docs/external-feedback-triage-cn.md'); -for (const marker of ['外部反馈分诊指南', 'docs/external-feedback-template-cn.md', 'manualValidation', '站点适配', '输入事件', '审阅队列', '隐私/信任', 'npm run check:browser-extension', 'turnCount', 'matchedSelectors.turn', '会话抽取']) { - assert(feedbackTriage.includes(marker), `External feedback triage guide missing marker: ${marker}`); -} - -const plan = read('docs/product-delivery-plan-cn.md'); -for (const marker of ['本地预览包', '权限与隐私说明', '插件对标说明', '外部测试闭环', '外部反馈分诊指南', 'GitHub 外部试用 Issue 模板', 'Skill 草稿', 'AI 页面诊断', '真实站点测试卡', 'AI-SITE-TEST-CARDS.md']) { - assert(plan.includes(marker), `Product delivery plan missing marker: ${marker}`); -} - -const mem0Reference = read('docs/browser-extension-mem0-reference-cn.md'); -for (const marker of ['mem0ai/mem0-chrome-extension', 'supported sites', '输入框附近', '待审阅队列', '真实 AI 站点验收', '插件迭代工作流', '站点适配层', '输入框入口', '后台协调', '管理界面', '何时拆 adapter', '/agentmemory/review', 'check-browser-extension-site-config-sync']) { - assert(mem0Reference.includes(marker), `Mem0 reference doc missing marker: ${marker}`); -} - -const releaseGates = read('docs/release-gates-cn.md'); -for (const marker of ['本地可演示', '外部可试用', '公开可发布', '未达到', '真实 AI 站点逐站验收', 'GitHub 外部试用 Issue 模板', '外部反馈分诊指南', '入口位置策略', '保存范围', '分类备注', '经验候选', 'npm run check:release-public', 'npm run make:ai-validation-tester-pack', 'turnCount > 0', '会话区域 selector', '输入框草稿']) { - assert(releaseGates.includes(marker), `Release gates doc missing marker: ${marker}`); -} - -const aiValidation = read('docs/browser-extension-ai-validation-cn.md'); -for (const marker of ['ChatGPT', 'Claude', 'Gemini', 'Perplexity', '复制问题信息', '通过标准', 'npm run wizard:ai-validation-evidence', 'anchorFound', 'placement', 'memoryWidgetVisible', 'matchedSelectors', 'turnCount', '具体对话', '输入框草稿', 'browser-extension-ai-site-test-cards-cn.md']) { - assert(aiValidation.includes(marker), `AI validation doc missing marker: ${marker}`); -} - -const evidenceReadme = read('docs/validation/browser-extension-ai-sites/README.md'); -for (const marker of ['npm run wizard:ai-validation-evidence', '--clipboard', '--file diagnostics.json', '--pass', '证据质量门槛', 'matchedSelectors.editor', 'matchedSelectors.anchor', 'matchedSelectors.send', 'matchedSelectors.turn', 'turnCount > 0', '用户选中的文字', '输入框草稿', '诊断默认不包含 prompt 草稿']) { - assert(evidenceReadme.includes(marker), `AI validation evidence README missing marker: ${marker}`); -} - -const evidenceWizard = read('scripts/wizard-ai-validation-evidence.mjs'); - -const viewerHtml = read('src/viewer/index.html'); -for (const marker of ['1. 先看预览', '/demo/browser-extension.html', '2. 装到浏览器', '3. 验收 AI 页面', '4. 回来审阅']) { - assert(viewerHtml.includes(marker), `Viewer dashboard trial route missing marker: ${marker}`); -} -for (const marker of ['createInterface', '--yes', 'Did inserting/copying a local memory work?', 'siteInputStillWorks', 'npm run check:ai-validation-evidence']) { - assert(evidenceWizard.includes(marker), `AI validation evidence wizard missing marker: ${marker}`); -} - -const validationPrep = read('scripts/prepare-ai-validation-run.mjs'); -for (const marker of ['ChatGPT', 'Claude', 'Gemini', 'Perplexity', 'artifacts/ai-validation-run', 'wizard:ai-validation-evidence']) { - assert(validationPrep.includes(marker), `AI validation preparation script missing marker: ${marker}`); -} - -const demoPage = read('src/viewer/demo/browser-extension.html'); -for (const marker of ['agentmemory-demo-input', 'Agent Memory Demo', '记忆建议']) { - assert(demoPage.includes(marker), `Browser extension demo page missing marker: ${marker}`); -} - -const viewer = read('src/viewer/index.html'); -for (const marker of ['function reviewProject', 'function reviewTags', 'function reviewSourceLabel', 'payload.asLesson', '经验候选']) { - assert(viewer.includes(marker), `Viewer review queue missing browser draft metadata marker: ${marker}`); -} -for (const marker of ['browserReviewSessions', 'browserSessionObservations', 'embeddedObservations', '浏览器对话']) { - assert(viewer.includes(marker), `Viewer sessions must include browser conversations: ${marker}`); -} -for (const marker of ['function sessionSourceSummary', '记录来源', '查看技术细节', '工具使用', '记录类型', '浏览器对话', '本地 Agent 会话']) { - assert(viewer.includes(marker), `Viewer sessions page missing readable session marker: ${marker}`); -} -assert(!viewer.includes('
来源信息
'), 'Viewer sessions page must not expose source internals by default.'); -const apiSource = read('src/triggers/api.ts'); -for (const marker of ['recordBrowserSessionFromReview', 'browserSessionId', 'browser_conversation', 'browser_memory_candidate']) { - assert(apiSource.includes(marker), `Browser reviews must be recorded as real sessions first: ${marker}`); -} -for (const marker of ['delivery-status', 'renderDeliveryStatusCard', '浏览器记忆入口', '打开预览', '安装说明', '测试卡', '查看待审阅', '不要把链接当记忆', '真实 AI 证据', '等待证据', '待验收', '待修复', '下一步:', 'turnCount > 0', '会话区域 selector', '输入框草稿', '/docs/browser-extension-ai-site-test-cards-cn.md']) { - assert(viewer.includes(marker), `Viewer dashboard missing delivery status marker: ${marker}`); -} -for (const marker of ['function actionAttentionText', 'function actionDescriptionText', 'function actionSourceText', '继续推进', '制作 30 秒 README 演示', '留学申请 Skill', '待跟进', '正在推进', '需要处理', '已完成', '来自 ']) { - assert(viewer.includes(marker), `Viewer actions page missing non-technical action marker: ${marker}`); -} -for (const marker of ['已生成本地摘要', 'summarizeErrorLabel', '摘要服务未响应', '暂无可摘要内容']) { - assert(viewer.includes(marker), `Viewer session summary flow missing fallback marker: ${marker}`); -} -for (const marker of ['session-detail-top', 'session-detail-bottom', '跳到底部', '回到顶部', 'session-jump']) { - assert(viewer.includes(marker), `Viewer long session detail missing navigation marker: ${marker}`); -} -assert(!viewer.includes('function priorityLabel'), 'Viewer actions page must not render priority as a user-facing field.'); -for (const marker of ['先看本机能力', '再整理经验', '最后生成草稿', '人工确认后复制到本地 Skill 目录']) { - assert(viewer.includes(marker), `Viewer skill page missing workflow marker: ${marker}`); -} - -const summarizeSource = read('src/functions/summarize.ts'); -for (const marker of ['buildLocalSummary', 'saveLocalSummary', 'fallback: true', 'no_provider', 'empty_provider_response', 'parse_failed', 'validation_failed']) { - assert(summarizeSource.includes(marker), `Session summarize function missing local fallback marker: ${marker}`); -} - -const viewerServer = read('src/viewer/server.ts'); -for (const marker of ['deliveryArtifactRoot', 'process.cwd()', 'readProjectDoc', '/docs/browser-extension-ai-site-test-cards-cn.md', 'text/markdown', 'readDeliveryStatus', 'readDeliveryArtifact', '/artifacts/', 'agent-memory-lab-extension.zip', 'external-tester-handout.md', 'external-feedback-template-cn.md', 'external-feedback-triage-cn.md', 'tester-pack-cn.md', '/agentmemory/delivery-status', 'delivery-manifest.json', 'ai-validation-evidence-summary.json', 'requiredProducts', 'sites', '未录入真实页面证据']) { - assert(viewerServer.includes(marker), `Viewer server missing delivery status marker: ${marker}`); -} - -const deliveryStatusScript = read('scripts/delivery-status.mjs'); -for (const marker of ['current commit', 'artifact commit', 'stale; rerun npm run package:browser-extension', 'AI tester pack']) { - assert(deliveryStatusScript.includes(marker), `Delivery status script missing freshness marker: ${marker}`); -} - -const workbenchStatusScript = read('scripts/check-workbench-status.mjs'); -for (const marker of ['view-dashboard', 'agentmemory-demo-input', 'start:local-memory']) { - assert(workbenchStatusScript.includes(marker), `Workbench status script missing runtime marker: ${marker}`); -} - -const cliSource = read('src/cli.ts'); -for (const marker of ['workerRecoveryNote', 'stop --force', 'npm run start:local-memory', 'portInUseDiagnostic(port)']) { - assert(cliSource.includes(marker), `CLI missing worker recovery marker: ${marker}`); -} - -const privacyEn = read('docs/browser-extension-privacy-en.md'); -for (const marker of ['Privacy Policy', 'local-first', 'Data We Process', 'Where Data Goes', 'AI Diagnostics', 'does not include prompt draft text', 'matchedSelectors.editor/anchor/send/turn']) { - assert(privacyEn.includes(marker), `English privacy policy missing marker: ${marker}`); -} - -const privacyCn = read('docs/browser-extension-privacy-cn.md'); -for (const marker of ['外部测试诊断脱敏', 'ai.matchedSelectors.editor', 'ai.matchedSelectors.anchor', 'ai.matchedSelectors.send', 'ai.matchedSelectors.turn', '不会复制输入框草稿']) { - assert(privacyCn.includes(marker), `Chinese privacy policy missing marker: ${marker}`); -} - -const storeListing = read('docs/browser-extension-store-listing-en.md'); -for (const marker of ['Store Listing Draft', 'Short Description', 'Permission Justification', 'Pre-release Checklist']) { - assert(storeListing.includes(marker), `Store listing draft missing marker: ${marker}`); -} - -const previewPort = 32113; -const preview = spawn(process.execPath, ['scripts/preview-browser-extension.mjs'], { - stdio: 'ignore', - env: { ...process.env, AGENTMEMORY_EXTENSION_PREVIEW_PORT: String(previewPort) } -}); -try { - const html = await fetchText(`http://127.0.0.1:${previewPort}/demo/browser-extension.html`); - for (const marker of ['agentmemory-demo-input', 'Agent Memory Demo', '记忆建议']) { - assert(html.includes(marker), `Preview server response missing marker: ${marker}`); - } -} finally { - preview.kill(); -} - -const feishu = read('docs/feishu/agentmemory-project-intro-cn.md'); -for (const marker of ['插件发布物料', 'AI 页面诊断', '本地工作台工作流', '待审阅队列', 'Skill 草稿', 'artifacts/agent-memory-lab-extension.zip']) { - assert(feishu.includes(marker), `Feishu source doc missing marker: ${marker}`); -} -const workbenchWhiteboard = read('docs/feishu/whiteboards/workbench-workflow.mmd'); -for (const marker of ['入口层', '工作台主流程', '浏览器插件', '待审阅', '记忆库', '会话时间线', '经验', '行动', 'Skill 草稿', '下一次对话']) { - assert(workbenchWhiteboard.includes(marker), `Workbench whiteboard missing marker: ${marker}`); -} - -run(process.execPath, ['scripts/check-browser-extension.mjs']); -run(process.execPath, ['scripts/check-browser-extension-review-draft.mjs']); -run(process.execPath, ['scripts/check-browser-extension-diagnostics-privacy.mjs']); -run(process.execPath, ['scripts/check-browser-extension-fixtures.mjs']); -run(process.execPath, ['scripts/check-browser-extension-demo-interaction.mjs']); -run(process.execPath, ['--import', 'tsx', 'scripts/check-summarize-fallback.mjs']); -run(process.execPath, ['scripts/record-ai-validation-evidence.mjs', '--file', 'scripts/fixtures/ai-validation-diagnostic.json', '--out', 'artifacts/example-ai-validation-evidence.json', '--force']); -run(process.execPath, ['scripts/prepare-ai-validation-run.mjs']); -run(process.execPath, ['scripts/package-browser-extension.mjs']); -run(process.execPath, ['scripts/check-browser-extension-package.mjs']); -run(process.execPath, ['scripts/check-ai-validation-evidence.mjs']); -run(process.execPath, ['scripts/check-ai-validation-evidence-guards.mjs']); -run(process.execPath, ['scripts/sync-ai-validation-table.mjs', '--check']); -run(process.execPath, ['scripts/write-delivery-summary.mjs']); -run(process.execPath, ['scripts/make-ai-validation-tester-pack.mjs']); -run(process.execPath, ['scripts/delivery-status.mjs']); -run(process.execPath, ['--import', 'tsx', 'scripts/check-viewer-delivery-runtime.mjs']); -run(process.execPath, ['scripts/check-release-gates.mjs']); -assert(existsSync('artifacts/agent-memory-lab-extension.zip'), 'Browser extension package was not created.'); -assert(existsSync('artifacts/delivery-summary.md'), 'Delivery summary was not created.'); -assert(existsSync('artifacts/delivery-manifest.json'), 'Delivery manifest was not created.'); -assert(existsSync('artifacts/external-tester-handout.md'), 'External tester handout was not created.'); -assert(existsSync('artifacts/release-notes.md'), 'Release notes were not created.'); -assert(existsSync('artifacts/ai-validation-evidence-summary.json'), 'AI validation evidence summary was not created.'); -assert(existsSync('artifacts/ai-validation-run/tester-pack-cn.md'), 'AI validation tester pack was not created.'); -const deliverySummary = read('artifacts/delivery-summary.md'); -for (const marker of ['Agent Memory Lab Delivery Summary', 'Extension zip', 'Extension zip sha256', 'Delivery manifest', 'External Testing Loop', 'Release Gates', 'Reviewer Checklist', 'feedback template', 'triage guide', 'check:release-public', 'Real AI Site Validation', 'External tester guide', 'External tester issue template', 'AI validation log', 'AI validation tester pack']) { - assert(deliverySummary.includes(marker), `Delivery summary missing marker: ${marker}`); -} -const deliveryManifest = JSON.parse(read('artifacts/delivery-manifest.json')); -assert(deliveryManifest.product === 'Agent Memory Lab', 'Delivery manifest product mismatch.'); -assert(deliveryManifest.coreExperience?.externalTestingEntry?.popupVersionVisible === true, 'Delivery manifest must record popup version visibility.'); -assert(deliveryManifest.coreExperience?.externalTestingEntry?.popupLocalTestingStatusVisible === true, 'Delivery manifest must record popup local testing status.'); -assert(String(deliveryManifest.coreExperience?.externalTestingEntry?.testerGuideUrl || '').includes('external-tester-guide-cn.md'), 'Delivery manifest must record tester guide URL.'); -assert(deliveryManifest.coreExperience?.reviewDraft?.popup === true, 'Delivery manifest must record popup review draft support.'); -assert(deliveryManifest.coreExperience?.reviewDraft?.sidePanel === true, 'Delivery manifest must record side panel review draft support.'); -assert(deliveryManifest.coreExperience?.reviewDraft?.editableProject === true, 'Delivery manifest must record editable project support.'); -assert(deliveryManifest.coreExperience?.reviewDraft?.editableTags === true, 'Delivery manifest must record editable tags support.'); -assert(deliveryManifest.coreExperience?.reviewDraft?.editableLessonFlag === true, 'Delivery manifest must record editable lesson flag support.'); -assert(deliveryManifest.coreExperience?.reviewDraft?.savesToReviewQueue === true, 'Delivery manifest must record review queue save behavior.'); -assert(deliveryManifest.coreExperience?.aiInputMemoryHint?.sidePanelTestCardsEntry === true, 'Delivery manifest must record side panel test cards entry.'); -assert(deliveryManifest.coreExperience?.aiInputMemoryHint?.diagnosticValidationGuide === true, 'Delivery manifest must record diagnostic validation guide support.'); -assert(deliveryManifest.externalTesting?.zipLoadChecklist?.exists === true, 'Delivery manifest must record zip load checklist support.'); -assert(deliveryManifest.externalTesting?.feedbackTemplate?.exists === true, 'Delivery manifest must record external feedback template support.'); -assert(deliveryManifest.externalTesting?.issueTemplate?.exists === true, 'Delivery manifest must record external issue template support.'); -assert(deliveryManifest.externalTesting?.feedbackTriage?.exists === true, 'Delivery manifest must record feedback triage support.'); -assert(deliveryManifest.externalTesting?.evidenceRecorder?.exists === true, 'Delivery manifest must record AI evidence recorder support.'); -assert(deliveryManifest.externalTesting?.aiValidationTesterPack?.exists === true, 'Delivery manifest must record AI validation tester pack support.'); -assert(deliveryManifest.externalTesting?.aiSiteTestCards?.exists === true, 'Delivery manifest must record AI site test cards support.'); -assert(deliveryManifest.externalTesting?.aiSiteTestCards?.viewerPath === '/docs/browser-extension-ai-site-test-cards-cn.md', 'Delivery manifest must record local viewer test cards path.'); -assert(deliveryManifest.externalTesting?.aiSiteTestCards?.diagnosticField === 'validationGuide', 'Delivery manifest must record diagnostic validationGuide field.'); -assert(deliveryManifest.artifacts?.extensionZip?.exists, 'Delivery manifest must mark extension zip as existing.'); -assert(deliveryManifest.artifacts?.loadInstructions?.exists, 'Delivery manifest must mark zip load instructions as existing.'); -assert(deliveryManifest.artifacts?.externalTesterHandout?.exists === true, 'Delivery manifest must mark external tester handout as existing.'); -assert(deliveryManifest.artifacts?.aiValidationTesterPack?.path === 'artifacts/ai-validation-run/tester-pack-cn.md', 'Delivery manifest must mark AI validation tester pack artifact.'); -assert(deliveryManifest.artifacts?.aiValidationQuickstart?.path === 'artifacts/ai-validation-run/quickstart-cn.md', 'Delivery manifest must mark AI validation quickstart artifact.'); -assert(deliveryManifest.externalTesting?.aiValidationQuickstart?.exists === true, 'Delivery manifest must record AI validation quickstart support.'); -assert(deliveryManifest.artifacts?.releaseNotes?.exists === true, 'Delivery manifest must mark release notes as existing.'); -assert(deliveryManifest.artifacts?.githubReleaseDraft?.exists === true, 'Delivery manifest must mark GitHub release draft as existing.'); -assert(deliveryManifest.coreExperience?.aiInputMemoryHint?.mem0Reference?.documentedIn === 'docs/browser-extension-mem0-reference-cn.md', 'Delivery manifest must record Mem0 reference documentation.'); -assert(String(deliveryManifest.coreExperience?.aiInputMemoryHint?.mem0Reference?.inputPlacement || '').includes('prompt'), 'Delivery manifest must record prompt-adjacent placement.'); -assert(String(deliveryManifest.coreExperience?.aiInputMemoryHint?.mem0Reference?.reviewFirstDifference || '').includes('/agentmemory/review'), 'Delivery manifest must record review-first difference.'); -assert(deliveryManifest.artifacts.extensionZip.bytes > 0, 'Delivery manifest extension zip size must be positive.'); -assert(/^[a-f0-9]{64}$/.test(deliveryManifest.artifacts.extensionZip.sha256 || ''), 'Delivery manifest extension zip sha256 is invalid.'); -assert(deliveryManifest.releaseState?.publicRelease === 'not-ready', 'Delivery manifest must mark public release as not-ready until real site evidence exists.'); -assert(deliveryManifest.releaseState.realSiteValidation?.requiredCount === 4, 'Delivery manifest must track four required AI products.'); -assert(Array.isArray(deliveryManifest.releaseState.realSiteValidation.notPassed), 'Delivery manifest must list AI products not yet passed.'); -assert(deliveryManifest.releaseState.realSiteValidation.source === 'docs/browser-extension-ai-validation-cn.md', 'Delivery manifest must cite the AI validation source.'); -assert(deliveryManifest.releaseState.realSiteValidation.evidenceSummary === 'artifacts/ai-validation-evidence-summary.json', 'Delivery manifest must cite AI evidence summary artifact.'); - -const handout = read('artifacts/external-tester-handout.md'); -for (const marker of ['Agent Memory Lab 外部试用说明', 'agent-memory-lab-extension.zip', '先做这 5 步', '真实 AI 页面验收', 'GitHub Issue 模板', '公开发布:not-ready']) { - assert(handout.includes(marker), `External tester handout missing marker: ${marker}`); -} -for (const marker of ['turnCount', 'matchedSelectors.turn', '具体对话', '用户选中文本']) { - assert(handout.includes(marker), `External tester handout missing conversation evidence marker: ${marker}`); -} - -const testerPack = read('artifacts/ai-validation-run/tester-pack-cn.md'); -for (const marker of ['真实 AI 站点外测包', 'ChatGPT', 'Claude', 'Gemini', 'Perplexity', '不能替代', 'prompt 草稿', '具体对话', '输入框草稿', '真实对话计数', 'npm run wizard:ai-validation-evidence', 'manualValidation']) { - assert(testerPack.includes(marker), `AI validation tester pack missing marker: ${marker}`); -} -const quickstart = read('artifacts/ai-validation-run/quickstart-cn.md'); -for (const marker of ['真实 AI 站点验收一页纸', 'ChatGPT', 'Claude', 'Gemini', 'Perplexity', '复制问题信息', '具体对话', '输入框草稿', 'turnCount', 'npm run wizard:ai-validation-evidence', '隐私边界']) { - assert(quickstart.includes(marker), `AI validation quickstart missing marker: ${marker}`); -} - -const releaseNotes = read('artifacts/release-notes.md'); -for (const marker of ['Release Notes', '版本信息', 'Extension zip', '本版新增和已就绪能力', '已知边界', '真实 AI 页面证据', 'turnCount > 0', '输入框草稿', '反馈模板', 'npm run check:delivery', 'npm run check:release-public', '反馈入口']) { - assert(releaseNotes.includes(marker), `Release notes missing marker: ${marker}`); -} - -const githubReleaseDraft = read('artifacts/github-release-draft.md'); -for (const marker of ['外部试用包', 'GitHub Release 草稿', 'Extension zip', 'Mem0 / OpenMemory', '输入框附近', '待审阅队列', '公开发布仍是 `not-ready`', 'browser-extension/shared/site-config.js', 'npm run check:release-public']) { - assert(githubReleaseDraft.includes(marker), `GitHub release draft missing marker: ${marker}`); -} - -console.log('delivery checks ok'); diff --git a/scripts/check-env-example.mjs b/scripts/check-env-example.mjs deleted file mode 100644 index ae5ea857..00000000 --- a/scripts/check-env-example.mjs +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env node -// -// Sync-check: every env var read by `src/` MUST be documented in -// `.env.example`. Runs in CI as a soft guard rail — keeps `.env.example` -// from drifting behind real config-surface additions. -// -// Usage: -// node scripts/check-env-example.mjs -// -// Returns 0 when in sync, 1 with a diff when out of sync. - -import { readFileSync, readdirSync, statSync } from "node:fs"; -import { join } from "node:path"; - -const ROOT = new URL("..", import.meta.url).pathname; -const SRC = join(ROOT, "src"); -const ENV_FILE = join(ROOT, ".env.example"); - -// Env vars read by the runtime but NOT user-facing config — these are -// either process-injected (HOME, PATH, USERPROFILE), set by the build / -// wrapper (NODE_*, npm_*), or set by tests (VITEST, *_TEST_*). Skipping -// them keeps `.env.example` a documented config surface rather than an -// inventory of every getenv anywhere in the codebase. -const RUNTIME_ONLY = new Set([ - "HOME", - "PATH", - "USERPROFILE", - "NODE_ENV", - "AGENTMEMORY_SDK_CHILD", -]); - -// Walk src/ for .ts / .mts / .mjs / .js files (excluding `.d.ts` declarations -// and dotfile dirs / node_modules). test/ lives outside src/ so it never enters. -function walk(dir) { - const out = []; - for (const entry of readdirSync(dir)) { - const full = join(dir, entry); - const s = statSync(full); - if (s.isDirectory()) { - if (entry === "node_modules" || entry.startsWith(".")) continue; - out.push(...walk(full)); - } else if (/\.(ts|mts|mjs|js)$/.test(entry) && !entry.endsWith(".d.ts")) { - out.push(full); - } - } - return out; -} - -// Multiple patterns: -// process.env["KEY"] — direct access -// env["KEY"] — local alias inside detectProvider, etc. -// getEnvVar("KEY") — helper from src/config.ts -// env: ProcessEnv → env.KEY — caught as `env["KEY"]` only; if you add -// a dotted-access path, extend the regex. -const PATTERNS = [ - // Direct map index: process.env["KEY"], env["KEY"], getMergedEnv()["KEY"]. - // The trailing `]\s*` form covers `…)["KEY"]` and `…env["KEY"]`. - /\[\s*"([A-Z][A-Z0-9_]+)"\s*\]/g, - /getEnvVar\(\s*"([A-Z][A-Z0-9_]+)"\s*\)/g, -]; -const used = new Set(); -for (const file of walk(SRC)) { - const text = readFileSync(file, "utf8"); - for (const pat of PATTERNS) { - pat.lastIndex = 0; - let m; - while ((m = pat.exec(text)) !== null) { - const name = m[1]; - if (!RUNTIME_ONLY.has(name)) used.add(name); - } - } -} - -const envText = readFileSync(ENV_FILE, "utf8"); -const documented = new Set(); -for (const line of envText.split("\n")) { - const m = line.match(/^#?\s*([A-Z][A-Z0-9_]+)=/); - if (m) documented.add(m[1]); -} - -const missing = [...used].filter((k) => !documented.has(k)).sort(); -const orphan = [...documented].filter((k) => !used.has(k)).sort(); - -if (missing.length === 0 && orphan.length === 0) { - console.log(`env-example: in sync (${used.size} keys documented)`); - process.exit(0); -} - -if (missing.length > 0) { - console.error( - `env-example: MISSING from .env.example — add documentation for these keys:`, - ); - for (const k of missing) console.error(` - ${k}`); -} -if (orphan.length > 0) { - console.error( - `env-example: ORPHAN in .env.example — no longer read by src/, remove or move to runtime-only allowlist:`, - ); - for (const k of orphan) console.error(` - ${k}`); -} -process.exit(1); diff --git a/scripts/check-maimorylab-branding.mjs b/scripts/check-maimorylab-branding.mjs deleted file mode 100644 index 35da3e3a..00000000 --- a/scripts/check-maimorylab-branding.mjs +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env node -import { readFileSync } from 'node:fs'; -import { relative } from 'node:path'; - -const files = [ - 'README.md', - 'package.json', - 'package-lock.json', - 'docs/demo-checklist-cn.md', - 'docs/external-tester-guide-cn.md', - 'docs/project-delivery-guide-cn.md', - 'docs/feishu/agentmemory-project-intro-cn.md', - 'browser-extension/config.js', - 'browser-extension/manifest.json', - 'plugin/skills/open-workbench/SKILL.md', - 'plugin/plugin.json', - 'scripts/check-delivery.mjs', - 'scripts/check-browser-extension-demo-interaction.mjs', - 'scripts/check-remote-delivery.mjs', - 'scripts/fixtures/ai-validation-diagnostic.json', - 'scripts/render-feishu-whiteboards.mjs', - 'iii-config.yaml', - 'iii-config.docker.yaml', - 'iii-config.local-memory.yaml', - 'src/cli.ts', - 'src/index.ts', - 'src/cli/doctor-diagnostics.ts', - 'src/viewer/server.ts', - 'src/viewer/index.html', -]; - -const forbidden = [ - { - pattern: /npx\s+@agentmemory\/agentmemory/g, - reason: 'uses the upstream npm package instead of the MaimoryLab checkout', - }, - { - pattern: /npm\s+install\s+-g\s+@agentmemory\/agentmemory/g, - reason: 'installs the upstream npm package', - }, - { - pattern: /https:\/\/github\.com\/rohitg00\/agentmemory/g, - reason: 'links users to the upstream repo as the product source', - }, - { - pattern: /https:\/\/github\.com\/novitalabs\/agentmemory-lab/g, - reason: 'links users to the old delivery organization', - }, - { - pattern: /\bagentmemory\s+(demo|viewer|stop|status|doctor|init)\b/g, - reason: 'suggests the old bare CLI entrypoint', - }, - { - pattern: /https?:\/\/(?:localhost|127\.0\.0\.1):3113/g, - reason: 'uses the old viewer port instead of the MaimoryLab viewer port 3114', - }, - { - pattern: /default 3113/g, - reason: 'documents the old viewer port as a default', - }, -]; - -const allowLine = (file, line) => { - if (file === 'src/cli.ts' && /Wire agentmemory into/.test(line)) { - return true; - } - if (file === 'scripts/check-maimorylab-branding.mjs') return true; - return false; -}; - -const failures = []; - -for (const file of files) { - let text = ''; - try { - text = readFileSync(file, 'utf8'); - } catch { - continue; - } - const lines = text.split(/\r?\n/); - lines.forEach((line, index) => { - for (const rule of forbidden) { - rule.pattern.lastIndex = 0; - if (rule.pattern.test(line) && !allowLine(file, line)) { - failures.push({ - file, - line: index + 1, - reason: rule.reason, - text: line.trim(), - }); - } - } - }); -} - -if (failures.length > 0) { - console.error('MaimoryLab branding check failed:'); - for (const failure of failures) { - console.error( - `- ${relative(process.cwd(), failure.file)}:${failure.line} ${failure.reason}\n ${failure.text}`, - ); - } - process.exit(1); -} - -console.log('MaimoryLab branding check passed.'); diff --git a/scripts/check-release-gates.mjs b/scripts/check-release-gates.mjs deleted file mode 100644 index 7df29429..00000000 --- a/scripts/check-release-gates.mjs +++ /dev/null @@ -1,93 +0,0 @@ -import { existsSync, readFileSync } from 'node:fs'; - -function assert(condition, message) { - if (!condition) throw new Error(message); -} - -function read(path) { - return readFileSync(path, 'utf8'); -} - -function readJson(path) { - return JSON.parse(read(path)); -} - -function parseValidationRows(markdown) { - const lines = markdown.split(/\r?\n/); - const headerIndex = lines.findIndex((line) => line.startsWith('| 产品 | 目标域名 | Provider |')); - if (headerIndex < 0) return []; - const rows = []; - for (const line of lines.slice(headerIndex + 2)) { - if (!line.startsWith('|')) break; - const cells = line.split('|').slice(1, -1).map((cell) => cell.trim()); - if (cells.length < 10) continue; - rows.push({ - product: cells[0], - domain: cells[1], - provider: cells[2], - editor: cells[3], - hint: cells[4], - insert: cells[5], - diagnostics: cells[6], - result: cells[7], - date: cells[8], - evidence: cells[9] - }); - } - return rows; -} - -function isPassed(row) { - const passWords = ['通过', '已通过', 'pass', 'passed', 'ok']; - return [row.provider, row.editor, row.hint, row.insert, row.diagnostics, row.result].every((value) => { - const lower = String(value || '').toLowerCase(); - return passWords.some((word) => lower.includes(word)); - }) && row.date !== '-' && row.evidence !== '-'; -} - -const requirePublic = process.argv.includes('--public'); -const manifestPath = 'artifacts/delivery-manifest.json'; -const validationPath = 'docs/browser-extension-ai-validation-cn.md'; -const evidenceSummaryPath = 'artifacts/ai-validation-evidence-summary.json'; - -assert(existsSync(manifestPath), `Missing ${manifestPath}. Run npm run package:browser-extension first.`); -assert(existsSync(validationPath), `Missing ${validationPath}.`); -assert(existsSync(evidenceSummaryPath), `Missing ${evidenceSummaryPath}. Run npm run check:ai-validation-evidence first.`); - -const manifest = readJson(manifestPath); -const evidenceSummary = readJson(evidenceSummaryPath); -const validation = read(validationPath); -const rows = parseValidationRows(validation); -const requiredProducts = ['ChatGPT', 'Claude', 'Gemini', 'Perplexity']; -const requiredRows = requiredProducts.map((product) => rows.find((row) => row.product === product)).filter(Boolean); -const passedRows = requiredRows.filter(isPassed); -const missingProducts = requiredProducts.filter((product) => !requiredRows.some((row) => row.product === product)); -const notPassed = requiredRows.filter((row) => !isPassed(row)).map((row) => row.product); - -const localDemoReady = manifest.releaseState?.localDemo === 'ready'; -const externalTestingReady = manifest.releaseState?.externalTesting === 'mostly-ready' || manifest.releaseState?.externalTesting === 'ready'; -const evidenceReady = evidenceSummary.passedCount === requiredProducts.length; -const publicReleaseReady = requiredRows.length === requiredProducts.length && passedRows.length === requiredProducts.length && evidenceReady; - -console.log('Agent Memory Lab release gates'); -console.log(`localDemo: ${localDemoReady ? 'ready' : 'not-ready'}`); -console.log(`externalTesting: ${externalTestingReady ? manifest.releaseState.externalTesting : 'not-ready'}`); -console.log(`publicRelease: ${publicReleaseReady ? 'ready' : 'not-ready'}`); -console.log(`realSiteValidation: ${passedRows.length}/${requiredProducts.length} required products passed`); -console.log(`realSiteEvidence: ${evidenceSummary.passedCount || 0}/${requiredProducts.length} required products passed`); -if (missingProducts.length) console.log(`missing rows: ${missingProducts.join(', ')}`); -if (notPassed.length) console.log(`not passed: ${notPassed.join(', ')}`); - -if (!localDemoReady || !externalTestingReady) { - console.log('Next: run npm run package:browser-extension && npm run check:delivery, then inspect artifacts/delivery-manifest.json.'); - process.exit(1); -} - -if (requirePublic && !publicReleaseReady) { - console.log('Next: complete real AI site validation rows and evidence JSON before public release.'); - process.exit(1); -} - -if (!publicReleaseReady) { - console.log('Public release is intentionally blocked until real AI site evidence is recorded. External testing can continue.'); -} diff --git a/scripts/check-remote-delivery.mjs b/scripts/check-remote-delivery.mjs deleted file mode 100644 index ac222127..00000000 --- a/scripts/check-remote-delivery.mjs +++ /dev/null @@ -1,93 +0,0 @@ -import { existsSync, readFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; - -const DELIVERY_REMOTE_URL = 'https://github.com/MaimoryLab/agentmemory-lab.git'; -const BASE_BRANCH = 'szn-viewer-ui-iteration'; -const DELIVERY_PR_BRANCH = process.env.AGENTMEMORY_DELIVERY_PR_BRANCH || 'codex/diagnostic-privacy-20260609'; - -function runGit(args, options = {}) { - const result = spawnSync('git', args, { encoding: 'utf8' }); - if (result.status !== 0) { - if (options.optional) return ''; - throw new Error(`git ${args.join(' ')} failed: ${result.stderr || result.stdout}`); - } - return result.stdout.trim(); -} - -function assert(condition, message) { - if (!condition) throw new Error(message); -} - -function readJson(path) { - return JSON.parse(readFileSync(path, 'utf8')); -} - -function normalizeRemoteUrl(value) { - return String(value || '').trim().replace(/\.git$/, ''); -} - -function discoverDeliveryRemote() { - const explicit = process.env.AGENTMEMORY_DELIVERY_REMOTE; - if (explicit) return explicit; - const target = normalizeRemoteUrl(DELIVERY_REMOTE_URL); - const remotes = runGit(['remote', '-v']) - .split(/\r?\n/) - .map((line) => line.trim().split(/\s+/)) - .filter((parts) => parts.length >= 2) - .find((parts) => normalizeRemoteUrl(parts[1]) === target); - assert(remotes, `No git remote points to ${DELIVERY_REMOTE_URL}. Set AGENTMEMORY_DELIVERY_REMOTE to the remote name.`); - return remotes[0]; -} - -function hasTrackedChanges() { - return runGit(['status', '--short']) - .split(/\r?\n/) - .filter(Boolean) - .some((line) => !line.startsWith('?? .learnings/') && !line.includes('index.html.bak-')); -} - -const manifestPath = 'artifacts/delivery-manifest.json'; -const evidencePath = 'artifacts/ai-validation-evidence-summary.json'; -const zipPath = 'artifacts/agent-memory-lab-extension.zip'; - -assert(existsSync(manifestPath), `Missing ${manifestPath}. Run npm run package:browser-extension first.`); -assert(existsSync(evidencePath), `Missing ${evidencePath}. Run npm run check:ai-validation-evidence first.`); -assert(existsSync(zipPath), `Missing ${zipPath}. Run npm run package:browser-extension first.`); - -const branch = runGit(['branch', '--show-current']); -const head = runGit(['rev-parse', 'HEAD']); -const shortHead = runGit(['rev-parse', '--short', 'HEAD']); -const deliveryRemote = discoverDeliveryRemote(); -const remoteUrl = runGit(['remote', 'get-url', deliveryRemote]); -const remoteRef = runGit(['ls-remote', deliveryRemote, `refs/heads/${branch}`], { optional: true }); -const prRef = runGit(['ls-remote', deliveryRemote, `refs/heads/${DELIVERY_PR_BRANCH}`], { optional: true }); -const manifest = readJson(manifestPath); -const evidence = readJson(evidencePath); -const artifactCommit = manifest.git?.commit || ''; -const deliveredDirectly = remoteRef.startsWith(head); -const deliveredByPr = prRef.startsWith(head); - -assert([BASE_BRANCH, DELIVERY_PR_BRANCH].includes(branch), `Expected branch ${BASE_BRANCH} or ${DELIVERY_PR_BRANCH}, got ${branch || 'unknown'}.`); -assert(normalizeRemoteUrl(remoteUrl) === normalizeRemoteUrl(DELIVERY_REMOTE_URL), `Delivery remote ${deliveryRemote} must point to ${DELIVERY_REMOTE_URL}, got ${remoteUrl || 'missing'}.`); -assert(deliveredDirectly || deliveredByPr, `Delivery remote does not contain current commit ${shortHead}. Push to ${deliveryRemote}/${branch} or PR branch ${deliveryRemote}/${DELIVERY_PR_BRANCH}.`); -assert(artifactCommit === shortHead, `Delivery artifact commit is ${artifactCommit || 'missing'}, expected ${shortHead}. Run npm run package:browser-extension.`); -assert(!hasTrackedChanges(), 'Tracked changes are pending. Commit or discard them before remote delivery.'); - -const release = manifest.releaseState || {}; -assert(release.localDemo === 'ready', 'Local demo must be ready.'); -assert(['ready', 'mostly-ready'].includes(release.externalTesting), 'External testing loop must be ready enough for remote delivery.'); -assert((evidence.requiredCount || 4) === 4, 'AI validation evidence summary must track the four required AI products.'); - -if (release.publicRelease === 'ready' || evidence.publicReleaseReadyByEvidence) { - assert((evidence.passedCount || 0) === (evidence.requiredCount || 4), 'Public release cannot be ready unless all required AI products passed evidence.'); -} else { - assert((evidence.passedCount || 0) < (evidence.requiredCount || 4), 'Public release is blocked, but evidence summary does not show missing required products.'); -} - -console.log('remote delivery checks ok'); -console.log(`branch: ${branch}`); -console.log(`commit: ${shortHead}`); -console.log(`remote: ${deliveryRemote} ${DELIVERY_REMOTE_URL}`); -console.log(`delivery path: ${deliveredDirectly ? `${deliveryRemote}/${branch}` : `${deliveryRemote}/${DELIVERY_PR_BRANCH} pull request`}`); -console.log(`external testing: ${release.externalTesting}`); -console.log(`public release: ${release.publicRelease || 'not-ready'} (${evidence.passedCount || 0}/${evidence.requiredCount || 4} real AI evidence)`); diff --git a/scripts/check-summarize-fallback.mjs b/scripts/check-summarize-fallback.mjs deleted file mode 100644 index 921875bb..00000000 --- a/scripts/check-summarize-fallback.mjs +++ /dev/null @@ -1,115 +0,0 @@ -import { registerSummarizeFunction } from '../src/functions/summarize.ts'; -import { StateKV } from '../src/state/kv.ts'; -import { KV } from '../src/state/schema.ts'; - -function assert(condition, message) { - if (!condition) throw new Error(message); -} - -class MemorySdk { - constructor() { - this.functions = new Map(); - this.store = new Map(); - } - - registerFunction(id, fn) { - this.functions.set(id, fn); - } - - async trigger({ function_id, payload }) { - if (function_id === 'state::get') { - return this.store.get(`${payload.scope}:${payload.key}`) || null; - } - if (function_id === 'state::set') { - this.store.set(`${payload.scope}:${payload.key}`, payload.value); - return payload.value; - } - if (function_id === 'state::list') { - const prefix = `${payload.scope}:`; - return Array.from(this.store.entries()) - .filter(([key]) => key.startsWith(prefix)) - .map(([, value]) => value); - } - const fn = this.functions.get(function_id); - if (!fn) throw new Error(`Unknown function: ${function_id}`); - return fn(payload); - } -} - -const sdk = new MemorySdk(); -const kv = new StateKV(sdk); -const session = { - id: 'ses_fallback_test', - project: 'agentmemory-lab', - cwd: '/tmp/agentmemory-lab', - startedAt: '2026-06-09T00:00:00.000Z', - status: 'completed', - observationCount: 3, - firstPrompt: '修复会话摘要按钮老是失败的问题' -}; -const observations = [ - { - id: 'obs_1', - sessionId: session.id, - timestamp: '2026-06-09T00:00:01.000Z', - type: 'conversation', - title: '用户指出问题', - facts: ['会话详情里的生成摘要按钮经常显示生成失败'], - narrative: '用户希望摘要按钮不要在缺少模型配置时直接失败。', - concepts: ['viewer', 'summary'], - files: [], - importance: 8 - }, - { - id: 'obs_2', - sessionId: session.id, - timestamp: '2026-06-09T00:00:02.000Z', - type: 'decision', - title: '增加本地摘要兜底', - facts: ['没有 LLM provider 时用本地规则从 observation 生成摘要'], - narrative: '摘要生成需要符合本地优先,不应依赖远端模型才可用。', - concepts: ['local-first', 'fallback'], - files: ['src/functions/summarize.ts'], - importance: 9 - }, - { - id: 'obs_3', - sessionId: session.id, - timestamp: '2026-06-09T00:00:03.000Z', - type: 'file_edit', - title: '更新前端提示', - facts: ['按钮区分已生成摘要和已生成本地摘要'], - narrative: '前端不再把所有失败都显示成生成失败。', - concepts: ['ux'], - files: ['src/viewer/index.html'], - importance: 7 - } -]; - -await kv.set(KV.sessions, session.id, session); -for (const observation of observations) { - await kv.set(KV.observations(session.id), observation.id, observation); -} - -registerSummarizeFunction(sdk, kv, { - name: 'noop', - async compress() { - return ''; - }, - async summarize() { - throw new Error('noop provider should not be called'); - } -}); - -const result = await sdk.trigger({ function_id: 'mem::summarize', payload: { sessionId: session.id } }); -assert(result && result.success === true, 'No-provider summarize should succeed with local fallback.'); -assert(result.fallback === true, 'No-provider summarize result should be marked as fallback.'); -assert(result.reason === 'no_provider', 'No-provider summarize should preserve the fallback reason.'); -assert(result.summary && result.summary.sessionId === session.id, 'Fallback summary should include the session id.'); -assert(result.summary.narrative.includes('本地规则兜底'), 'Fallback summary should explain that it used local rules.'); -assert(result.summary.keyDecisions.length > 0, 'Fallback summary should extract readable key decisions.'); - -const stored = await kv.get(KV.summaries, session.id); -assert(stored && stored.sessionId === session.id, 'Fallback summary should be saved to the summaries store.'); - -console.log('summarize fallback checks ok'); diff --git a/scripts/check-viewer-delivery-runtime.mjs b/scripts/check-viewer-delivery-runtime.mjs deleted file mode 100644 index e95abada..00000000 --- a/scripts/check-viewer-delivery-runtime.mjs +++ /dev/null @@ -1,80 +0,0 @@ -import { startViewerServer } from '../src/viewer/server.ts'; - -function assert(condition, message) { - if (!condition) throw new Error(message); -} - -function waitForListening(server) { - return new Promise((resolve, reject) => { - server.once('listening', resolve); - server.once('error', reject); - }); -} - -async function fetchText(base, path, headers = {}) { - const res = await fetch(`${base}${path}`, { headers: { Accept: '*/*', ...headers } }); - const text = await res.text(); - assert(res.ok, `${path} returned HTTP ${res.status}: ${text.slice(0, 160)}`); - return { res, text }; -} - -const server = startViewerServer(0, {}, {}, undefined, 0); -try { - await waitForListening(server); - const addr = server.address(); - const port = addr && typeof addr === 'object' ? addr.port : 0; - assert(port > 0, 'Viewer test server did not expose a port.'); - const base = `http://127.0.0.1:${port}`; - - const dashboard = await fetchText(base, '/'); - for (const marker of ['renderDeliveryStatusCard', '/docs/browser-extension-ai-site-test-cards-cn.md', 'delivery-status', 'turnCount > 0', '真实 AI 证据']) { - assert(dashboard.text.includes(marker), `Viewer dashboard response missing ${marker}.`); - } - - const delivery = await fetchText(base, '/agentmemory/delivery-status', { Accept: 'application/json' }); - const data = JSON.parse(delivery.text); - assert(data.available === true, 'Delivery status endpoint must see generated artifacts.'); - assert(data.localDemo === 'ready', 'Delivery status endpoint must expose local demo readiness.'); - assert(data.externalTesting === 'mostly-ready', 'Delivery status endpoint must expose external testing state.'); - assert(data.publicRelease === 'not-ready', 'Delivery status endpoint must not mark public release ready without real site evidence.'); - assert(data.realSiteValidation && data.realSiteValidation.requiredCount === 4, 'Delivery status endpoint must expose required AI site count.'); - assert(Array.isArray(data.realSiteValidation.sites), 'Delivery status endpoint must expose per-site validation status.'); - assert(data.realSiteValidation.sites.length === 4, 'Delivery status endpoint must expose four required AI site statuses.'); - for (const product of ['ChatGPT', 'Claude', 'Gemini', 'Perplexity']) { - assert(data.realSiteValidation.sites.some((site) => site.product === product), `Delivery status endpoint missing ${product} site status.`); - } - - const cards = await fetchText(base, '/docs/browser-extension-ai-site-test-cards-cn.md'); - assert((cards.res.headers.get('content-type') || '').includes('text/markdown'), 'AI site test cards must be served as markdown.'); - for (const marker of ['真实 AI 站点测试卡', 'ChatGPT', 'Claude', 'Gemini', 'Perplexity']) { - assert(cards.text.includes(marker), `AI site test card response missing ${marker}.`); - } - - const demo = await fetchText(base, '/demo/browser-extension.html'); - assert(demo.text.includes('Agent Memory Demo'), 'Browser extension demo route must remain available.'); - - const zip = await fetch(`${base}/artifacts/agent-memory-lab-extension.zip`, { headers: { Accept: '*/*' } }); - assert(zip.ok, `Extension zip download returned HTTP ${zip.status}.`); - assert((zip.headers.get('content-type') || '').includes('application/zip'), 'Extension zip must be served as application/zip.'); - assert((await zip.arrayBuffer()).byteLength > 1000, 'Extension zip download must not be empty.'); - - const handout = await fetchText(base, '/artifacts/external-tester-handout.md'); - assert(handout.text.includes('外部试用说明'), 'External tester handout artifact must be served.'); - - const quickstart = await fetchText(base, '/artifacts/ai-validation-run/quickstart-cn.md'); - assert(quickstart.text.includes('真实 AI 站点验收一页纸'), 'AI validation quickstart artifact must be served.'); - assert(quickstart.text.includes('ChatGPT') && quickstart.text.includes('Perplexity'), 'AI validation quickstart must include required AI products.'); - - const feedbackTemplate = await fetchText(base, '/artifacts/external-feedback-template-cn.md'); - assert(feedbackTemplate.text.includes('外部试用反馈模板'), 'External feedback template artifact must be served.'); - - const feedbackTriage = await fetchText(base, '/artifacts/external-feedback-triage-cn.md'); - assert(feedbackTriage.text.includes('外部反馈分诊指南'), 'External feedback triage artifact must be served.'); - - const denied = await fetch(`${base}/artifacts/%252e%252e/package.json`); - assert(denied.status === 404, 'Viewer artifact route must not expose paths outside the safe artifact list.'); - - console.log('viewer delivery runtime checks ok'); -} finally { - await new Promise((resolve) => server.close(resolve)); -} diff --git a/scripts/check-workbench-status.mjs b/scripts/check-workbench-status.mjs deleted file mode 100644 index a1400e0f..00000000 --- a/scripts/check-workbench-status.mjs +++ /dev/null @@ -1,77 +0,0 @@ -const apiPort = Number(process.env.III_REST_PORT || process.env.AGENTMEMORY_API_PORT || 3111); -const expectedViewerPort = Number(process.env.AGENTMEMORY_VIEWER_PORT || apiPort + 3); -const apiBase = `http://127.0.0.1:${apiPort}`; - -async function fetchJson(url) { - const res = await fetch(url, { headers: { Accept: 'application/json' } }); - const text = await res.text(); - let body = null; - try { body = text ? JSON.parse(text) : null; } catch { body = { raw: text }; } - return { ok: res.ok, status: res.status, body }; -} - -async function fetchStatus(url, marker = '') { - try { - const res = await fetch(url, { headers: { Accept: 'text/html,text/plain,*/*' } }); - const text = await res.text(); - return { ok: res.ok && (!marker || text.includes(marker)), status: res.status }; - } catch (err) { - return { ok: false, error: err && err.message ? err.message : String(err) }; - } -} - -function printSummary(summary) { - console.log('Agent Memory Lab workbench status'); - console.log(`API: ${summary.api.url} -> ${summary.api.ok ? 'ok' : 'not reachable'}`); - if (summary.api.status) console.log(` HTTP ${summary.api.status}`); - if (summary.api.version) console.log(` version ${summary.api.version}`); - console.log(`Viewer: ${summary.viewer.url} -> ${summary.viewer.ok ? 'ok' : 'not reachable'}`); - if (summary.viewer.status) console.log(` HTTP ${summary.viewer.status}`); - if (summary.viewer.skipped) console.log(' viewerSkipped=true'); - console.log(`Demo: ${summary.demo.url} -> ${summary.demo.ok ? 'ok' : 'not reachable'}`); - if (summary.demo.status) console.log(` HTTP ${summary.demo.status}`); -} - -let health = null; -try { - health = await fetchJson(`${apiBase}/agentmemory/health`); -} catch (err) { - const summary = { - api: { url: `${apiBase}/agentmemory/health`, ok: false, error: err && err.message ? err.message : String(err) }, - viewer: { url: `http://127.0.0.1:${expectedViewerPort}/#dashboard`, ok: false }, - demo: { url: `http://127.0.0.1:${expectedViewerPort}/demo/browser-extension.html`, ok: false } - }; - printSummary(summary); - console.log('Next: start the workbench with `npm run build && npm run start:local-memory`, or check whether another process already uses the API port.'); - process.exit(1); -} - -const body = health.body || {}; -const viewerPort = Number(body.viewerPort || expectedViewerPort); -const viewerUrl = `http://127.0.0.1:${viewerPort}/#dashboard`; -const demoUrl = `http://127.0.0.1:${viewerPort}/demo/browser-extension.html`; -const viewer = await fetchStatus(`http://127.0.0.1:${viewerPort}/#dashboard`, 'view-dashboard'); -const demo = await fetchStatus(demoUrl, 'agentmemory-demo-input'); - -const summary = { - api: { url: `${apiBase}/agentmemory/health`, ok: health.ok, status: health.status, version: body.version || '' }, - viewer: { url: viewerUrl, ok: viewer.ok && !body.viewerSkipped, status: viewer.status, skipped: !!body.viewerSkipped }, - demo: { url: demoUrl, ok: demo.ok && !body.viewerSkipped, status: demo.status } -}; -printSummary(summary); - -if (!summary.api.ok) { - console.log('Next: the API port responded, but not as a healthy Agent Memory Lab service. Another process may be using the API port, or the workbench may need a restart.'); - process.exit(1); -} -if (body.viewerSkipped || !summary.viewer.ok) { - console.log('Next: API is running, but the Viewer is not reachable. Another process may be using the viewer port; restart the workbench after freeing the port.'); - process.exit(1); -} -if (!summary.demo.ok) { - console.log('Next: Viewer is reachable, but the plugin demo page is missing. Run `npm run build` and restart with `npm run start:local-memory`.'); - process.exit(1); -} - -console.log(`Open: ${viewerUrl}`); -console.log(`Plugin demo: ${demoUrl}`); diff --git a/scripts/delivery-status.mjs b/scripts/delivery-status.mjs deleted file mode 100644 index edb3443e..00000000 --- a/scripts/delivery-status.mjs +++ /dev/null @@ -1,76 +0,0 @@ -import { existsSync, readFileSync, statSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; - -function readJson(path) { - return JSON.parse(readFileSync(path, 'utf8')); -} - -function fileSize(path) { - return existsSync(path) ? statSync(path).size : 0; -} - -function printStatus(label, ok, detail = '') { - console.log(`${label}: ${ok ? 'ready' : 'not-ready'}${detail ? ` (${detail})` : ''}`); -} - -function git(args) { - const result = spawnSync('git', args, { encoding: 'utf8' }); - return result.status === 0 ? result.stdout.trim() : ''; -} - -function hasTrackedChanges() { - return git(['status', '--short']) - .split(/\r?\n/) - .filter(Boolean) - .some((line) => !line.startsWith('?? .learnings/') && !line.includes('index.html.bak-')); -} - -const manifestPath = 'artifacts/delivery-manifest.json'; -const evidencePath = 'artifacts/ai-validation-evidence-summary.json'; -const zipPath = 'artifacts/agent-memory-lab-extension.zip'; - -if (!existsSync(manifestPath) || !existsSync(evidencePath) || !existsSync(zipPath)) { - console.log('Agent Memory Lab delivery status'); - printStatus('delivery artifacts', false, 'run npm run package:browser-extension'); - process.exit(1); -} - -const manifest = readJson(manifestPath); -const evidence = readJson(evidencePath); -const zipBytes = fileSize(zipPath); -const release = manifest.releaseState || {}; -const realSite = release.realSiteValidation || {}; -const core = manifest.coreExperience || {}; -const external = manifest.externalTesting || {}; -const currentCommit = git(['rev-parse', '--short', 'HEAD']) || 'unknown'; -const artifactCommit = manifest.git?.commit || 'unknown'; -const artifactStale = currentCommit !== 'unknown' && artifactCommit !== 'unknown' && currentCommit !== artifactCommit; -const trackedChangesPending = hasTrackedChanges(); - -function readyFromEntry(entry) { - return !!(entry && entry.exists !== false && (entry.path || entry.command)); -} - -console.log('Agent Memory Lab delivery status'); -console.log(`current commit: ${currentCommit}${trackedChangesPending ? ' (tracked changes pending)' : ''}`); -console.log(`artifact commit: ${artifactCommit}${artifactStale ? ' (stale; rerun npm run package:browser-extension)' : ''}`); -console.log(`extension: ${manifest.extension?.name || 'Agent Memory Lab'} ${manifest.extension?.version || ''}`.trim()); -console.log(`zip: ${zipPath} (${zipBytes} bytes)`); -printStatus('local demo', release.localDemo === 'ready'); -console.log(`external testing: ${release.externalTesting || 'not-ready'}`); -printStatus('review draft flow', !!(core.reviewDraft?.popup && core.reviewDraft?.sidePanel && core.reviewDraft?.savesToReviewQueue)); -printStatus('tester entry', !!(core.externalTestingEntry?.popupVersionVisible && core.externalTestingEntry?.testerGuideUrl)); -printStatus('zip tester checklist', readyFromEntry(external.zipLoadChecklist)); -printStatus('feedback loop', !!(readyFromEntry(external.feedbackTemplate) && readyFromEntry(external.issueTemplate) && readyFromEntry(external.feedbackTriage))); -printStatus('AI evidence recorder', readyFromEntry(external.evidenceRecorder)); -printStatus('AI site test cards', !!(readyFromEntry(external.aiSiteTestCards) && core.aiInputMemoryHint?.sidePanelTestCardsEntry && core.aiInputMemoryHint?.diagnosticValidationGuide)); -printStatus('AI tester pack', readyFromEntry(external.aiValidationTesterPack) || readyFromEntry(manifest.artifacts?.aiValidationTesterPack)); -console.log(`real site validation table: ${realSite.passedCount || 0}/${realSite.requiredCount || 4}`); -console.log(`real site evidence: ${evidence.passedCount || 0}/${evidence.requiredCount || 4}`); -printStatus('public release', release.publicRelease === 'ready' && evidence.publicReleaseReadyByEvidence); - -const notPassed = realSite.evidenceNotPassed || evidence.notPassedRequired || realSite.notPassed || []; -if (notPassed.length) console.log(`next validation targets: ${notPassed.join(', ')}`); -if (release.publicRelease !== 'ready' || !evidence.publicReleaseReadyByEvidence) { - console.log('next: collect real AI page diagnostics, run npm run check:ai-validation-evidence, then npm run sync:ai-validation-table.'); -} diff --git a/scripts/dev.sh b/scripts/dev.sh deleted file mode 100755 index 70ac07e3..00000000 --- a/scripts/dev.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -# Unified local dev entrypoint. Runs the daemon from source via tsx. -set -euo pipefail -exec npm run dev -- "$@" diff --git a/scripts/fixtures/ai-validation-diagnostic.json b/scripts/fixtures/ai-validation-diagnostic.json deleted file mode 100644 index 2160ea45..00000000 --- a/scripts/fixtures/ai-validation-diagnostic.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "product": "Agent Memory Lab Browser Extension", - "extension": { - "name": "Agent Memory Lab", - "version": "0.1.0", - "manifestVersion": 3 - }, - "generatedAt": "2026-06-08T00:00:00.000Z", - "page": { - "title": "Agent Memory Demo", - "url": "http://localhost:3114/demo/browser-extension.html", - "host": "localhost", - "origin": "http://localhost:3114", - "type": "ai-chat", - "typeLabel": "AI 对话" - }, - "ai": { - "supportedAiPage": true, - "provider": "Agent Memory Demo", - "editorFound": true, - "editorSelector": "#agentmemory-demo-input", - "anchorFound": true, - "placement": "toolbar-end", - "memoryWidgetVisible": true, - "promptLength": 18, - "turnCount": 2, - "checkedAt": "2026-06-08T00:00:00.000Z" - }, - "manualValidation": { - "memoryInsertPassed": false, - "diagnosticsCopied": true, - "siteInputStillWorks": false, - "browser": "Fixture", - "notes": "Fixture for record command only" - } -} diff --git a/scripts/install-langextract.mjs b/scripts/install-langextract.mjs deleted file mode 100644 index e9e9e45a..00000000 --- a/scripts/install-langextract.mjs +++ /dev/null @@ -1,32 +0,0 @@ -import { existsSync } from "node:fs"; -import { join } from "node:path"; -import { spawnSync } from "node:child_process"; - -const root = process.cwd(); -const requirements = join(root, "requirements-langextract.txt"); -const venv = join(root, ".agentmemory-python"); -const python = process.platform === "win32" ? join(venv, "Scripts", "python.exe") : join(venv, "bin", "python"); - -function run(command, args) { - return spawnSync(command, args, { stdio: "inherit" }); -} - -if (!existsSync(requirements)) { - process.stderr.write("[agentmemory] requirements-langextract.txt not found; skipping LangExtract setup.\n"); - process.exit(0); -} - -if (!existsSync(python)) { - const created = run("python3", ["-m", "venv", venv]); - if (created.status !== 0) { - process.stderr.write("[agentmemory] Could not create .agentmemory-python venv; set LANGEXTRACT_PYTHON manually.\n"); - process.exit(0); - } -} - -const installed = run(python, ["-m", "pip", "install", "-r", requirements]); -if (installed.status !== 0) { - process.stderr.write("[agentmemory] LangExtract Python deps were not installed; set LANGEXTRACT_PYTHON manually.\n"); - process.exit(0); -} - diff --git a/scripts/lint.sh b/scripts/lint.sh deleted file mode 100755 index b56c6976..00000000 --- a/scripts/lint.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -# Quality gate. NOTE: this repo has no standalone linter; the closest single -# check is the local consistency guard (counts / version drift across files). -# The authoritative pre-PR gate is `npm run pre-pr` (consistency + build + test). -set -euo pipefail -exec npm run check:consistency-local -- "$@" diff --git a/scripts/make-ai-validation-tester-pack.mjs b/scripts/make-ai-validation-tester-pack.mjs deleted file mode 100644 index 7d427b35..00000000 --- a/scripts/make-ai-validation-tester-pack.mjs +++ /dev/null @@ -1,220 +0,0 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; - -const outDir = 'artifacts/ai-validation-run'; -const evidenceDir = 'docs/validation/browser-extension-ai-sites'; -const requiredProducts = [ - { - product: 'ChatGPT', - domains: 'chatgpt.com / chat.openai.com', - prompt: '帮我找一下我们之前关于浏览器插件和本地记忆的产品决策。', - focus: '输入框底部工具栏、发送按钮、语音按钮、模型选择入口。' - }, - { - product: 'Claude', - domains: 'claude.ai', - prompt: '总结一下 Agent Memory Lab 的插件交付还差哪些真实站点证据。', - focus: '富文本输入框、附件按钮、发送按钮、项目页和普通聊天页差异。' - }, - { - product: 'Gemini', - domains: 'gemini.google.com', - prompt: '基于我的本地记忆,帮我整理浏览器插件的下一步验收计划。', - focus: 'rich-textarea 输入区、发送按钮、语音/图片入口、多语言按钮文本。' - }, - { - product: 'Perplexity', - domains: 'perplexity.ai / www.perplexity.ai', - prompt: '检索并结合我的本地记忆,说明多 AI 网页记忆插件应该怎么验收。', - focus: '搜索输入框、追问输入框、来源面板和结果页滚动。' - } -]; - -function git(args) { - const result = spawnSync('git', args, { encoding: 'utf8' }); - return result.status === 0 ? result.stdout.trim() : 'unknown'; -} - -function readJson(path, fallback = {}) { - if (!existsSync(path)) return fallback; - return JSON.parse(readFileSync(path, 'utf8')); -} - -function slug(value) { - return String(value || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); -} - -function today() { - return new Date().toISOString().slice(0, 10); -} - -const branch = git(['branch', '--show-current']); -const commit = git(['rev-parse', '--short', 'HEAD']); -const delivery = readJson('artifacts/delivery-manifest.json'); -const evidence = readJson('artifacts/ai-validation-evidence-summary.json', { passedRequired: [], passedCount: 0, requiredCount: 4 }); -const passed = new Set(Array.isArray(evidence.passedRequired) ? evidence.passedRequired : []); -const date = today(); -const rows = requiredProducts.map((item) => ({ - ...item, - slug: slug(item.product), - passed: passed.has(item.product), - evidencePath: `${evidenceDir}/${date}-${slug(item.product)}.json` -})); - -mkdirSync(outDir, { recursive: true }); - -const pendingRows = rows.filter((item) => !item.passed); -const nextTargets = (pendingRows.length ? pendingRows : rows).map((item) => item.product).join('、'); - -const quickstart = `# 真实 AI 站点验收一页纸 - -这张一页纸给外测者先看。目标很简单:把 ChatGPT、Claude、Gemini、Perplexity 四个真实网页都跑一遍,留下可复现证据。当前真实证据:${evidence.passedCount || 0}/${evidence.requiredCount || requiredProducts.length}。 - -## 先确认 - -- 本地工作台已经启动,Viewer 可以打开。 -- Chrome / Edge 已加载插件目录或解压后的插件包。 -- 插件侧栏显示本地已连接,并能看到“复制问题信息”和“复制检查步骤”。 -- 本地预览页只用来自查插件是否注入成功,不能替代真实 AI 页面证据。 - -## 每个站点只做 6 步 - -1. 打开真实 AI 页面并登录。 -2. 输入测试 prompt,先不要发送。 -3. 看输入框附近是否出现“记忆建议”。 -4. 尝试把一条本地记忆插入或复制到输入框。 -5. 确认原站输入、发送、滚动、附件和模型选择都正常。 -6. 在侧栏复制问题信息,再复制检查步骤,把证据保存进仓库。 - -## 必测站点 - -| 产品 | 目标域名 | 状态 | 建议 prompt | -| --- | --- | --- | --- | -${rows.map((item) => `| ${item.product} | ${item.domains} | ${item.passed ? '已有通过证据' : '待验收'} | ${item.prompt} |`).join('\n')} - -## 通过才可以勾选 - -- Provider 识别正确。 -- 输入框已找到。 -- 输入框旁出现“记忆建议”。 -- 侧栏能读到真实对话,诊断里的 \`turnCount\` 大于 0;如果没有读到对话,侧栏要清楚提示需要展开真实对话或选中具体内容。 -- 候选记忆来自具体对话或选中文本,不是网页介绍、链接或输入框草稿。 -- 记忆可以插入或复制。 -- 原站输入和发送没有被破坏。 -- 证据里三项人工确认都为通过:插入成功、诊断已复制、原站仍正常。 - -## 保存证据 - -侧栏“复制检查步骤”会给出更精确命令。通用命令是: - -\`\`\`bash -npm run wizard:ai-validation-evidence -- --clipboard --provider "ChatGPT" -npm run check:ai-validation-evidence -npm run sync:ai-validation-table -npm run status:delivery -\`\`\` - -测 Claude、Gemini、Perplexity 时,把 provider 改成对应产品名。只有真实确认通过时,才在向导里选择通过;不要为了凑数手动改证据。 - -## 隐私边界 - -可以提交结构字段、selector、计数、浏览器版本和无敏感备注。不要提交 prompt 草稿、完整聊天正文、账号信息、Cookie、Token、私人文件或页面截图里的隐私内容。 - -下一批优先测:${nextTargets}。 -`; - -const testerPack = `# Agent Memory Lab 真实 AI 站点外测包 - -这份文档给测试者使用。它只覆盖真实 AI 网页验收,不用本地 demo 冒充通过证据。 - -| 项目 | 当前值 | -| --- | --- | -| Branch | ${branch} | -| Commit | ${commit} | -| 插件版本 | ${delivery.extension?.name || 'Agent Memory Lab'} ${delivery.extension?.version || ''} | -| 当前真实证据 | ${evidence.passedCount || 0}/${evidence.requiredCount || requiredProducts.length} | -| 公开发布状态 | ${(delivery.releaseState && delivery.releaseState.publicRelease) || 'not-ready'} | - -## 先做 3 件事 - -1. 启动本地工作台,并确认 Viewer 可以打开。 -2. 在 Chrome / Edge 开发者模式加载 \`browser-extension/\` 或解压后的插件目录。 -3. 打开插件同步侧栏,确认本地连接正常,并能看到“复制问题信息”和“复制检查步骤”。 - -如果真实 AI 页面没有出现记忆建议,先打开本地自检页: - -\`启动输出里的 Viewer 地址 + /demo/browser-extension.html\` - -自检页只用于确认插件注入是否正常,不能替代 ChatGPT、Claude、Gemini、Perplexity 的真实站点证据。 - -## 每个站点都这样测 - -1. 登录目标 AI 产品。 -2. 在真实对话页输入建议 prompt。 -3. 看输入框附近是否出现“记忆建议”。 -4. 打开同步侧栏,确认它能读到真实对话;如果没有读到,空状态应该提示展开真实对话或选中具体内容。 -5. 确认候选记忆来自具体对话或选中文本,不是网页介绍、链接或输入框草稿。 -6. 尝试插入或复制一条本地记忆。 -7. 确认原站输入、发送、滚动、附件和模型选择没有被插件破坏。 -8. 点击同步侧栏“复制问题信息”。 -9. 点击同步侧栏“复制检查步骤”,在项目终端运行。 -10. 按实际结果回答向导问题。只有真实成功才填通过。 - -向导会把人工验收结果写入 \`manualValidation\`,其中 \`memoryInsertPassed\`、\`diagnosticsCopied\`、\`siteInputStillWorks\` 三项都为 true,且诊断里能看到真实对话计数和会话 selector 时,该站点才可能计入通过。 - -隐私边界:诊断默认只包含结构字段、selector、计数和人工验收结果,不需要提交 prompt 草稿、完整聊天正文、账号信息、Cookie、Token 或私人材料。如果页面标题或 URL 暴露敏感信息,可以先删改。 - -## 必测站点 - -${rows.map((item, index) => `### ${index + 1}. ${item.product} - -| 项目 | 内容 | -| --- | --- | -| 目标域名 | ${item.domains} | -| 建议 prompt | ${item.prompt} | -| 重点观察 | ${item.focus} | -| 当前状态 | ${item.passed ? '已有通过证据' : '待验收'} | -| 建议证据文件 | \`${item.evidencePath}\` | - -推荐保存命令: - -\`\`\`bash -npm run wizard:ai-validation-evidence -- --clipboard --provider "${item.product}" -npm run check:ai-validation-evidence -npm run sync:ai-validation-table -\`\`\` - -通过前必须确认:Provider 正确、输入框已找到、输入框附近出现记忆建议、记忆可插入或复制、原站输入和发送不受影响。`).join('\n\n')} - -## 最后交回什么 - -- 生成的证据 JSON 文件名。 -- 不含敏感内容的截图或短录屏。 -- 如果失败,交回“复制问题信息”的 JSON 和失败页面说明。 -- 运行 \`npm run status:delivery\` 的结果。 - -四个必测站点都通过后,真实站点证据会从 \`0/4\` 变成 \`4/4\`,公开发布门槛才可能解除。 -`; - -const json = { - generatedAt: new Date().toISOString(), - branch, - commit, - extension: delivery.extension || {}, - releaseState: delivery.releaseState || {}, - currentEvidence: { - passedCount: evidence.passedCount || 0, - requiredCount: evidence.requiredCount || requiredProducts.length, - passedRequired: Array.from(passed) - }, - targets: rows -}; - -writeFileSync(`${outDir}/quickstart-cn.md`, quickstart); -writeFileSync(`${outDir}/tester-pack-cn.md`, testerPack); -writeFileSync(`${outDir}/tester-pack.json`, `${JSON.stringify(json, null, 2)}\n`); - -console.log('AI validation tester pack written'); -console.log(`${outDir}/quickstart-cn.md`); -console.log(`${outDir}/tester-pack-cn.md`); -console.log(`${outDir}/tester-pack.json`); diff --git a/scripts/package-browser-extension.mjs b/scripts/package-browser-extension.mjs deleted file mode 100644 index 1c0d89cd..00000000 --- a/scripts/package-browser-extension.mjs +++ /dev/null @@ -1,106 +0,0 @@ -import { existsSync, mkdirSync, rmSync, readFileSync, statSync, writeFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; -import { createHash } from 'node:crypto'; - -const outDir = 'artifacts/browser-extension'; -const zipPath = 'artifacts/agent-memory-lab-extension.zip'; - -function readJson(path) { - return JSON.parse(readFileSync(path, 'utf8')); -} - -function git(args) { - const result = spawnSync('git', args, { encoding: 'utf8' }); - return result.status === 0 ? result.stdout.trim() : ''; -} - -function sha256(path) { - if (!existsSync(path)) return ''; - return createHash('sha256').update(readFileSync(path)).digest('hex'); -} - -function writePackageManifest(targetDir) { - const manifest = readJson('browser-extension/manifest.json'); - const delivery = existsSync('artifacts/delivery-manifest.json') ? readJson('artifacts/delivery-manifest.json') : null; - const evidence = existsSync('artifacts/ai-validation-evidence-summary.json') ? readJson('artifacts/ai-validation-evidence-summary.json') : null; - const commit = git(['rev-parse', '--short', 'HEAD']) || (delivery && delivery.git && delivery.git.commit) || 'unknown'; - const branch = git(['branch', '--show-current']) || (delivery && delivery.git && delivery.git.branch) || 'unknown'; - const required = evidence && evidence.requiredCount ? evidence.requiredCount : 4; - const passed = evidence && Number.isFinite(Number(evidence.passedCount)) ? Number(evidence.passedCount) : 0; - const missing = evidence && Array.isArray(evidence.notPassedRequired) ? evidence.notPassedRequired : ['ChatGPT', 'Claude', 'Gemini', 'Perplexity']; - const text = `# Agent Memory Lab Extension Package - -Generated: ${new Date().toISOString()} - -## Version - -- Extension: ${manifest.name} ${manifest.version} -- Branch: ${branch} -- Commit: ${commit} - -## What This Package Is - -This zip is for local external testing. It is not a Chrome Web Store public release. - -Use it to verify: - -- The extension loads in Chrome / Edge developer mode. -- Memory suggestions appear near supported AI input boxes. -- Candidate memories can be edited before they enter the local review queue. -- AI-page diagnostics and evidence commands can be copied from the side panel. - -## First Steps - -1. Read \`LOAD-THIS-FIRST.md\`. -2. Load this \`browser-extension/\` folder from \`chrome://extensions\`. -3. Open \`启动输出里的 Viewer 地址 + /demo/browser-extension.html\`. -4. Try the side panel and confirm the review draft can be edited. -5. For real AI sites, follow \`AI-SITE-TEST-CARDS.md\`. - -## Real AI Evidence Status - -- Required products: ChatGPT, Claude, Gemini, Perplexity -- Passed evidence: ${passed}/${required} -- Still needed: ${missing.length ? missing.join(', ') : 'none'} - -Public release remains blocked until all required products have reproducible real-page evidence. - -## Evidence Flow - -On each real AI page: - -1. Open the extension side panel. -2. Click \`复制问题信息\`. -3. Click \`复制检查步骤\`. -4. Paste and run the copied wizard command from the repository root. -5. Mark the three manual checks as passed only after memory insertion/copy, diagnostics copy, and original site input all work. - -Passing evidence must include \`matchedSelectors.editor\`, \`matchedSelectors.anchor\`, \`matchedSelectors.send\`, and \`matchedSelectors.turn\`. - -## Privacy - -Before sharing diagnostics, remove private prompt text, conversation snippets, account details, and sensitive page titles. Keep the non-sensitive \`ai\` selector/status fields and \`manualValidation\` result so site issues can be reproduced. -`; - writeFileSync(`${targetDir}/PACKAGE-MANIFEST.md`, text); -} - -rmSync(outDir, { recursive: true, force: true }); -rmSync(zipPath, { force: true }); -mkdirSync('artifacts', { recursive: true }); - -const copy = spawnSync('ditto', ['browser-extension', outDir], { stdio: 'inherit' }); -if (copy.status !== 0) throw new Error('Failed to copy browser-extension.'); - -writePackageManifest(outDir); - -for (const name of ['.DS_Store', '__MACOSX']) { - rmSync(`${outDir}/${name}`, { recursive: true, force: true }); -} - -const zip = spawnSync('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', outDir, zipPath], { stdio: 'inherit' }); -if (zip.status !== 0) throw new Error('Failed to package browser extension zip.'); -if (!existsSync(zipPath)) throw new Error('Extension zip was not created.'); - -console.log(`browser extension package: ${zipPath}`); -console.log(`browser extension package sha256: ${sha256(zipPath)}`); -console.log(`browser extension package bytes: ${statSync(zipPath).size}`); diff --git a/scripts/prepare-ai-validation-run.mjs b/scripts/prepare-ai-validation-run.mjs deleted file mode 100644 index 7cd3d236..00000000 --- a/scripts/prepare-ai-validation-run.mjs +++ /dev/null @@ -1,141 +0,0 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; - -const evidenceDir = 'docs/validation/browser-extension-ai-sites'; -const outDir = 'artifacts/ai-validation-run'; -const requiredProducts = [ - { - product: 'ChatGPT', - domains: 'chatgpt.com / chat.openai.com', - prompt: '帮我找一下我们之前关于浏览器插件和本地记忆的产品决策。' - }, - { - product: 'Claude', - domains: 'claude.ai', - prompt: '总结一下 Agent Memory Lab 的插件交付还差哪些真实站点证据。' - }, - { - product: 'Gemini', - domains: 'gemini.google.com', - prompt: '基于我的本地记忆,帮我整理浏览器插件的下一步验收计划。' - }, - { - product: 'Perplexity', - domains: 'perplexity.ai / www.perplexity.ai', - prompt: '检索并结合我的本地记忆,说明多 AI 网页记忆插件应该怎么验收。' - } -]; - -function git(args) { - const result = spawnSync('git', args, { encoding: 'utf8' }); - return result.status === 0 ? result.stdout.trim() : 'unknown'; -} - -function readJson(path, fallback = {}) { - if (!existsSync(path)) return fallback; - return JSON.parse(readFileSync(path, 'utf8')); -} - -function providerSlug(value) { - return String(value || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); -} - -function passedProducts(summary) { - return new Set(Array.isArray(summary.passedRequired) ? summary.passedRequired : []); -} - -function dateStamp() { - return new Date().toISOString().slice(0, 10); -} - -const commit = git(['rev-parse', '--short', 'HEAD']); -const branch = git(['branch', '--show-current']); -const delivery = readJson('artifacts/delivery-manifest.json'); -const evidence = readJson('artifacts/ai-validation-evidence-summary.json', { passedRequired: [], passedCount: 0, requiredCount: 4 }); -const passed = passedProducts(evidence); -const today = dateStamp(); - -mkdirSync(outDir, { recursive: true }); - -const rows = requiredProducts.map((item) => { - const slug = providerSlug(item.product); - const passedAlready = passed.has(item.product); - const evidencePath = `${evidenceDir}/${today}-${slug}.json`; - return { ...item, slug, passedAlready, evidencePath }; -}); - -const checklist = `# Agent Memory Lab 真实 AI 站点验收清单 - -生成时间:${new Date().toISOString()} - -| 项目 | 值 | -| --- | --- | -| Branch | ${branch} | -| Commit | ${commit} | -| Extension | ${delivery.extension?.name || 'Agent Memory Lab'} ${delivery.extension?.version || ''} | -| 当前真实证据 | ${evidence.passedCount || 0}/${evidence.requiredCount || requiredProducts.length} | - -## 开始前 - -1. 启动本地工作台: - - \`\`\`bash - npm run build - npm run start - \`\`\` - -2. 在 Chrome / Edge 开发者模式加载 \`browser-extension/\`。 -3. 打开插件同步侧栏,确认本地连接正常。 -4. 每个站点都点击“复制问题信息”和“复制检查步骤”。 -5. 不要提交私人聊天全文、Cookie、Token、账号信息或学校申请材料。 - -## 必测站点 - -${rows.map((item, index) => `### ${index + 1}. ${item.product} - -- 目标域名:${item.domains} -- 建议 prompt:\`${item.prompt}\` -- 当前状态:${item.passedAlready ? '已有通过证据' : '待验收'} -- 建议证据文件:\`${item.evidencePath}\` -- 保存命令: - - \`\`\`bash - npm run wizard:ai-validation-evidence -- --clipboard --provider "${item.product}" - npm run check:ai-validation-evidence - npm run sync:ai-validation-table - \`\`\` - -通过前必须确认:Provider 正确、输入框已找到、输入框附近出现记忆建议、记忆可插入或复制、原站输入和发送不受影响。 -`).join('\n')} - -## 收尾检查 - -\`\`\`bash -npm run check:ai-validation-evidence -npm run sync:ai-validation-table -npm run status:delivery -\`\`\` - -四个必测站点都通过后,真实站点证据才会从 \`0/4\` 变成 \`4/4\`,公开发布门槛才可能解除。 -`; - -const json = { - generatedAt: new Date().toISOString(), - branch, - commit, - extension: delivery.extension || {}, - currentEvidence: { - passedCount: evidence.passedCount || 0, - requiredCount: evidence.requiredCount || requiredProducts.length, - passedRequired: Array.from(passed) - }, - targets: rows -}; - -writeFileSync(`${outDir}/checklist-cn.md`, checklist); -writeFileSync(`${outDir}/targets.json`, JSON.stringify(json, null, 2)); - -console.log('AI validation run prepared'); -console.log(`${outDir}/checklist-cn.md`); -console.log(`${outDir}/targets.json`); -console.log(`remaining: ${rows.filter((item) => !item.passedAlready).map((item) => item.product).join(', ') || 'none'}`); diff --git a/scripts/preview-browser-extension.mjs b/scripts/preview-browser-extension.mjs deleted file mode 100644 index 756ffea7..00000000 --- a/scripts/preview-browser-extension.mjs +++ /dev/null @@ -1,57 +0,0 @@ -import { createServer } from 'node:http'; -import { existsSync, readFileSync, statSync } from 'node:fs'; -import { extname, join, normalize, resolve } from 'node:path'; - -const root = resolve('dist/viewer'); -const port = Number(process.env.AGENTMEMORY_EXTENSION_PREVIEW_PORT || process.env.PORT || 3113); - -const types = new Map([ - ['.html', 'text/html; charset=utf-8'], - ['.svg', 'image/svg+xml'], - ['.png', 'image/png'], - ['.jpg', 'image/jpeg'], - ['.jpeg', 'image/jpeg'], - ['.css', 'text/css; charset=utf-8'], - ['.js', 'text/javascript; charset=utf-8'] -]); - -function send(res, status, body, type = 'text/plain; charset=utf-8') { - res.writeHead(status, { 'Content-Type': type, 'Cache-Control': 'no-cache' }); - res.end(body); -} - -function fileForPath(pathname) { - const clean = decodeURIComponent(pathname.split('?')[0] || '/'); - const relative = clean === '/' ? 'demo/browser-extension.html' : clean.replace(/^\/+/, ''); - const full = normalize(join(root, relative)); - if (!full.startsWith(root)) return null; - return full; -} - -if (!existsSync(join(root, 'demo', 'browser-extension.html'))) { - console.error('Missing dist/viewer/demo/browser-extension.html. Run `npm run build` first.'); - process.exit(1); -} - -const server = createServer((req, res) => { - const method = req.method || 'GET'; - if (method !== 'GET' && method !== 'HEAD') { - send(res, 405, 'method not allowed'); - return; - } - const file = fileForPath(req.url || '/'); - if (!file || !existsSync(file) || !statSync(file).isFile()) { - send(res, 404, 'not found'); - return; - } - const body = readFileSync(file); - res.writeHead(200, { - 'Content-Type': types.get(extname(file)) || 'application/octet-stream', - 'Cache-Control': 'no-cache' - }); - res.end(method === 'HEAD' ? undefined : body); -}); - -server.listen(port, '127.0.0.1', () => { - console.log(`Agent Memory Lab extension preview: http://localhost:${port}/demo/browser-extension.html`); -}); diff --git a/scripts/record-ai-validation-evidence.mjs b/scripts/record-ai-validation-evidence.mjs deleted file mode 100644 index 9d5367ed..00000000 --- a/scripts/record-ai-validation-evidence.mjs +++ /dev/null @@ -1,123 +0,0 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; -import { execFileSync } from 'node:child_process'; -import path from 'node:path'; - -const evidenceDir = 'docs/validation/browser-extension-ai-sites'; - -function usage() { - return `Usage: - npm run record:ai-validation-evidence -- --file diagnostics.json - npm run record:ai-validation-evidence -- --stdin - npm run record:ai-validation-evidence -- --clipboard - -Options: - --provider Override provider name when the diagnostic JSON is incomplete - --browser Fill manualValidation.browser - --notes Fill manualValidation.notes - --pass Mark manualValidation memoryInsertPassed/siteInputStillWorks as true - --out Write to a specific file path -`; -} - -function argValue(args, name) { - const index = args.indexOf(name); - return index >= 0 ? args[index + 1] : ''; -} - -function providerSlug(value) { - const text = String(value || '').trim().toLowerCase(); - if (text.includes('chatgpt')) return 'chatgpt'; - if (text.includes('claude')) return 'claude'; - if (text.includes('gemini')) return 'gemini'; - if (text.includes('perplexity')) return 'perplexity'; - if (text.includes('grok')) return 'grok'; - if (text.includes('deepseek')) return 'deepseek'; - return text.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'unknown'; -} - -function readClipboard() { - try { - return execFileSync('pbpaste', { encoding: 'utf8' }); - } catch { - throw new Error('Cannot read clipboard. Use --file or --stdin instead.'); - } -} - -function readStdin() { - return readFileSync(0, 'utf8'); -} - -function readInput(args) { - const file = argValue(args, '--file'); - if (file) return readFileSync(file, 'utf8'); - if (args.includes('--stdin')) return readStdin(); - if (args.includes('--clipboard')) return readClipboard(); - throw new Error(`Missing input.\n${usage()}`); -} - -function parseJson(text) { - const trimmed = String(text || '').trim(); - try { - return JSON.parse(trimmed); - } catch { - const first = trimmed.indexOf('{'); - const last = trimmed.lastIndexOf('}'); - if (first >= 0 && last > first) return JSON.parse(trimmed.slice(first, last + 1)); - throw new Error('Input does not contain valid diagnostic JSON.'); - } -} - -function normalizeEvidence(data, args) { - const providerOverride = argValue(args, '--provider'); - const provider = providerOverride || data?.ai?.provider || data?.page?.host || 'Unknown'; - const passed = args.includes('--pass'); - const manual = data.manualValidation && typeof data.manualValidation === 'object' ? data.manualValidation : {}; - return { - product: data.product || 'Agent Memory Lab Browser Extension', - extension: data.extension || {}, - generatedAt: data.generatedAt || new Date().toISOString(), - page: data.page || {}, - ai: { - ...(data.ai || {}), - provider, - checkedAt: (data.ai && data.ai.checkedAt) || data.generatedAt || new Date().toISOString() - }, - manualValidation: { - memoryInsertPassed: passed ? true : manual.memoryInsertPassed === true, - diagnosticsCopied: manual.diagnosticsCopied !== false, - siteInputStillWorks: passed ? true : manual.siteInputStillWorks === true, - browser: argValue(args, '--browser') || manual.browser || '填写浏览器名称和版本', - notes: argValue(args, '--notes') || manual.notes || '填写无隐私信息的验收备注' - } - }; -} - -function outputPath(data, args) { - const explicit = argValue(args, '--out'); - if (explicit) return explicit; - const date = new Date().toISOString().slice(0, 10); - const provider = providerSlug(data.ai && data.ai.provider); - return path.join(evidenceDir, `${date}-${provider}.json`); -} - -const args = process.argv.slice(2); -if (args.includes('--help') || args.includes('-h')) { - console.log(usage()); - process.exit(0); -} - -try { - const data = normalizeEvidence(parseJson(readInput(args)), args); - const out = outputPath(data, args); - mkdirSync(path.dirname(out), { recursive: true }); - if (existsSync(out) && !args.includes('--force')) { - throw new Error(`${out} already exists. Add --force or choose --out.`); - } - writeFileSync(out, `${JSON.stringify(data, null, 2)}\n`); - console.log(`AI validation evidence written: ${out}`); - console.log(`provider: ${data.ai.provider || 'Unknown'}`); - console.log(`manual pass flags: insert=${data.manualValidation.memoryInsertPassed}, diagnostics=${data.manualValidation.diagnosticsCopied}, input=${data.manualValidation.siteInputStillWorks}`); -} catch (err) { - console.error(err && err.message ? err.message : err); - process.exit(1); -} diff --git a/scripts/render-feishu-whiteboards.mjs b/scripts/render-feishu-whiteboards.mjs deleted file mode 100644 index c94d8f56..00000000 --- a/scripts/render-feishu-whiteboards.mjs +++ /dev/null @@ -1,119 +0,0 @@ -import { writeFileSync } from 'node:fs'; -import sharp from 'sharp'; - -const outDir = 'docs/feishu/whiteboards'; - -function esc(text) { - return String(text).replace(/[&<>"]/g, (char) => ({ - '&': '&', '<': '<', '>': '>', '"': '"' - }[char])); -} - -function card({ x, y, w, h, title, body, fill = '#ffffff', stroke = '#d8d0c3', accent = '#ff6b35' }) { - return ` - - - - ${esc(title)} - ${body.map((line, i) => `${esc(line)}`).join('')} - `; -} - -function arrow(x1, y1, x2, y2, label = '') { - const lx = (x1 + x2) / 2; - const ly = (y1 + y2) / 2 - 12; - return ` - - ${label ? `${esc(label)}` : ''}`; -} - -function shell({ title, subtitle, content }) { - return ` - - - - - - - - - - - - ${esc(title)} - ${esc(subtitle)} - ${content} - `; -} - -const workflow = shell({ - title: 'Agent Memory Lab 产品工作流', - subtitle: '插件负责捕捉线索,工作台负责审阅,记忆库负责长期复用。', - content: ` - ${card({ x: 92, y: 220, w: 300, h: 170, title: '网页 / AI 对话', body: ['当前页面、选中文本', 'ChatGPT / Claude 等对话', '只读取用户主动打开的页面'], accent: '#2f6f73' })} - ${card({ x: 474, y: 180, w: 310, h: 210, title: '浏览器插件', body: ['生成 PageCapture', '提炼具体候选记忆', '显示本地记忆建议', '复制结构化诊断信息'], accent: '#ff6b35' })} - ${card({ x: 866, y: 220, w: 300, h: 170, title: '本地 API', body: ['统一接收候选内容', '写入待审阅队列', '连接 Viewer 和本地数据'], accent: '#8b5cf6' })} - ${card({ x: 1248, y: 180, w: 300, h: 210, title: 'Viewer 工作台', body: ['总览、记忆、会话', 'Skill、行动、活动', '保存前编辑和确认'], accent: '#0f766e' })} - ${card({ x: 474, y: 590, w: 310, h: 190, title: '长期记忆 / 经验', body: ['长期记忆来自审阅', '经验可沉淀为 Skill 草稿', '不把临时噪音直接写死'], accent: '#f59e0b' })} - ${card({ x: 866, y: 590, w: 300, h: 190, title: '可交付检查', body: ['插件打包', '隐私诊断保护', '真实 AI 站点证据'], accent: '#2563eb' })} - ${arrow(392, 305, 474, 285, '捕捉')} - ${arrow(784, 285, 866, 305, '送审')} - ${arrow(1166, 305, 1248, 285, '展示')} - ${arrow(1398, 390, 1020, 590, '确认后写入')} - ${arrow(784, 685, 866, 685, '验证')} - ` -}); - -const structure = shell({ - title: '仓库结构与交付边界', - subtitle: '主仓库包含工作台、浏览器入口、文档和交付检查;公开发布仍由真实站点证据把关。', - content: ` - ${card({ x: 90, y: 180, w: 330, h: 220, title: 'src / dist', body: ['Viewer 与本地服务源码', '构建后进入 dist', '负责工作台主体验'], accent: '#0f766e' })} - ${card({ x: 480, y: 180, w: 330, h: 220, title: 'browser-extension', body: ['Chrome 插件 MVP', 'popup / sidepanel / content script', '统一 PageCapture 数据'], accent: '#ff6b35' })} - ${card({ x: 870, y: 180, w: 330, h: 220, title: 'docs / feishu', body: ['README 图文版', '飞书项目说明源稿', '隐私、验收、交付文档'], accent: '#8b5cf6' })} - ${card({ x: 1260, y: 180, w: 250, h: 220, title: 'scripts', body: ['打包插件', '交付检查', '真实站点证据检查'], accent: '#2563eb' })} - ${card({ x: 220, y: 570, w: 360, h: 210, title: '远端交付 PR', body: ['MaimoryLab/agentmemory-lab', '受保护分支必须走 PR', '当前交付进入审核流'], accent: '#111827' })} - ${card({ x: 700, y: 570, w: 360, h: 210, title: '试用可交付', body: ['本地 Viewer', '插件 zip', '外测说明和反馈模板'], accent: '#f59e0b' })} - ${card({ x: 1180, y: 570, w: 300, h: 210, title: '公开发布门槛', body: ['真实 AI 站点 4/4', 'ChatGPT / Claude', 'Gemini / Perplexity'], accent: '#dc2626' })} - ${arrow(420, 290, 480, 290, '连接')} - ${arrow(810, 290, 870, 290, '说明')} - ${arrow(1200, 290, 1260, 290, '检查')} - ${arrow(645, 400, 430, 570, '推送')} - ${arrow(1035, 400, 880, 570, '打包')} - ${arrow(1385, 400, 1330, 570, '门槛')} - ` -}); - -const workbenchWorkflow = shell({ - title: '本地工作台工作流', - subtitle: '工作台不是后台列表,而是每天整理记忆、复盘会话、沉淀经验的主界面。', - content: ` - ${card({ x: 70, y: 210, w: 260, h: 180, title: '打开总览', body: ['先看今天入口', '最近会话和待处理', '不从技术日志开始'], accent: '#2f6f73' })} - ${card({ x: 390, y: 210, w: 280, h: 180, title: '处理待审阅', body: ['浏览器和会话候选', '改成具体事实', '确认后才保存'], accent: '#ff6b35' })} - ${card({ x: 730, y: 210, w: 280, h: 180, title: '整理记忆库', body: ['身份、偏好、项目', '来源和置信度', '删掉链接和噪音'], accent: '#2563eb' })} - ${card({ x: 1070, y: 210, w: 280, h: 180, title: '回看会话', body: ['完整时间线', '浏览器对话也进入', '看清上下文来源'], accent: '#0f766e' })} - ${card({ x: 230, y: 555, w: 290, h: 190, title: '沉淀经验', body: ['交互原则', '排布方法', '下次直接复用'], accent: '#f59e0b' })} - ${card({ x: 610, y: 555, w: 290, h: 190, title: '转成行动', body: ['待跟进', '正在推进', '需要处理 / 已完成'], accent: '#8b5cf6' })} - ${card({ x: 990, y: 555, w: 290, h: 190, title: '生成 Skill 草稿', body: ['成熟经验变能力', '人工确认后写入', '不自动污染本地目录'], accent: '#dc2626' })} - ${card({ x: 610, y: 825, w: 410, h: 120, title: '回到下一次对话', body: ['记忆、经验和行动被 Agent 召回', '减少重复解释,继续推进项目'], accent: '#111827' })} - ${arrow(330, 300, 390, 300, '进入')} - ${arrow(670, 300, 730, 300, '确认')} - ${arrow(1010, 300, 1070, 300, '复盘')} - ${arrow(860, 390, 375, 555, '可复用')} - ${arrow(1160, 390, 755, 555, '未完成')} - ${arrow(520, 650, 610, 650, '安排')} - ${arrow(900, 650, 990, 650, '成熟')} - ${arrow(375, 745, 700, 825, '回流')} - ${arrow(755, 745, 800, 825, '回流')} - ${arrow(1135, 745, 925, 825, '回流')} - ${arrow(610, 885, 200, 390, '下一轮')} - ` -}); - -for (const [name, svg] of Object.entries({ workflow, structure, 'workbench-workflow': workbenchWorkflow })) { - const svgPath = `${outDir}/${name}.svg`; - const pngPath = `${outDir}/${name}.png`; - writeFileSync(svgPath, svg); - await sharp(Buffer.from(svg)).png().toFile(pngPath); - console.log(`${pngPath}`); -} diff --git a/scripts/seed-inbox-eval.mjs b/scripts/seed-inbox-eval.mjs deleted file mode 100644 index 2044f480..00000000 --- a/scripts/seed-inbox-eval.mjs +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env node -// 收件箱评测语料 —— 真实代表性样例,供网页端逐条判断「噪音率」与「briefing 质量」。 -// -// 每条的 fromAgent 前缀编码了「期望判定」(评分基准): -// [真信号] question 该发:确实受阻 + 属用户决策(不可逆/真歧义/需用户独有资源) -// [边界] question 模糊:有推荐默认但影响不小,见仁见智 -// [噪音] question 本不该发:有约定默认 / "我继续吗" busywork / 琐碎 —— 测假阳性 -// [好简报] briefing 合格:非trivial + 自然停顿点 + 五秒可读 + 有料 -// [差简报] briefing 不合格:流水账 / 无料 / mid-flight —— 测质量下限 -// -// 用法:node scripts/seed-inbox-eval.mjs [--clear] -// --clear 先把现有「评测前缀」的 awaiting 项 dismiss 清空,再灌样例 -// (只清 fromAgent 以 [真信号]/[边界]/[噪音]/[好简报]/[差简报] 开头的, -// 绝不动用户真实 inbox 条目) -// -// 评分:网页端打开待办页,逐条看「该不该出现在这里」。 -// 噪音率 = (你认为不该发却出现的 question 数) / (question 总数) -// 理想:[真信号][边界] 都在、[噪音] 一条都不该被 Agent 真发(此处为对照故意灌入) - -const BASE = process.env.AGENTMEMORY_URL || "http://localhost:3111"; -const SECRET = process.env.AGENTMEMORY_SECRET || ""; - -const QUESTIONS = [ - { - from: "[真信号] db-cleanup", - body: "要不要删生产库里这 3 张孤儿表?\n已确认 6 个月无读写、无外键引用、无代码 grep 命中。\n- **删**:回收 ~40GB,但不可逆。\n- **留**:再观察一个月。\n倾向删,但这是不可逆操作,等你一句话。", - obs: ["obs_db_orphan_scan"], - }, - { - from: "[真信号] export-fmt", - body: "导出功能的格式你要哪种?两条路实现差异大、难中途换:\n- **CSV**:Excel 直接开,但丢嵌套结构。\n- **JSON**:保真,但非技术用户打不开。\n你之前没提,先等你定再动手。", - }, - { - from: "[真信号] stripe-key", - body: "支付联调缺 `STRIPE_SECRET_KEY`,仓库里和密钥库里都没有。\n这只有你那边有——发我一个 test 模式的 key 我就能接着联调,否则这块卡住。", - }, - { - from: "[真信号] state-refactor", - body: "购物车状态管理要从 Redux 换 Zustand 吗?\n现在的 bug 根因是 Redux 中间件时序,换掉能根治,但**影响 ~40 个文件**、改动面大。\n- **换**(推荐):根治 + 后续更简单。\n- **打补丁**:风险低但治标。\n影响面大,先问你。", - obs: ["obs_redux_timing_bug"], - }, - { - from: "[边界] admin-auth", - body: "`/admin/*` 路由要不要也加鉴权?我改完了 `/api/*`(加了 JWT 中间件),`/admin/*` 你之前没提。\n- **加**(推荐):与 `/api/*` 一致,管理端更该锁。\n- **不加**:若 admin 只在内网/有别的网关挡着。\n倾向加,等你一句话。", - }, - { - from: "[边界] test-timeout", - body: "集成测试默认超时设多少?当前 5s 已让 2 个慢测偶发 flaky。\n- **提到 10s**:压住 flaky,但掩盖真慢。\n- **留 5s + 标记慢测单独跑**:更干净但要改 CI。\n有默认(5s),但 flaky 影响你看红绿,顺手问下。", - }, - { - from: "[噪音] naming-style", - body: "新模块的变量命名用 camelCase 还是 snake_case?", - }, - { - from: "[噪音] proceed-check", - body: "我改完登录页的样式了,继续做下一个任务(注册页)吗?", - }, - { - from: "[噪音] readme-typo", - body: "README 第 3 段有个 typo(「instal」少了个 l),要修吗?", - }, -]; - -const BRIEFINGS = [ - { - from: "[好简报] line-c-c3", - body: "今天跟进了线 C 的收件箱动作(C3):\n- ✅ **完成**:回应/知道了/转待处理/看原文四个动作,[PR#24] 已合并。\n- ⏳ **还欠着**:C4 已完成区还没动。\n- ⚠️ **需你定**:转待处理的标题截断到 120 字,够不够开 PR 时帮看一眼。", - obs: ["obs_pr24_merged"], - }, - { - from: "[好简报] auth-batch", - body: "鉴权批量加固告一段落:\n- ✅ 6 个 `/api/*` 端点全加了 JWT 中间件 + 字段白名单。\n- ✅ 补了 9 例端到端测试,CI 绿。\n- ⏳ `/admin/*` 待你拍板后再动(已单独问你)。", - }, - { - from: "[差简报] verbose-log", - body: "我先打开了 src/auth.ts,然后看了 middleware.ts,接着搜了 grep JWT,发现 3 处引用,然后逐个改,改第一个时遇到类型报错,查了文档,修好后跑测试,测试报了另一个错,又改了 import,再跑就过了,然后我提交了,接着开始看下一个文件……(此条故意冗长复述全程,测「流水账」该不该进收件箱)", - }, - { - from: "[差简报] trivial", - body: "跑了下测试,过了。", - }, - { - from: "[差简报] mid-flight", - body: "正在改第 3 个文件,还有 2 个没改完,稍后继续。", - }, -]; - -async function post(path, body) { - const headers = { "Content-Type": "application/json" }; - if (SECRET) headers["Authorization"] = `Bearer ${SECRET}`; - const res = await fetch(`${BASE}/agentmemory${path}`, { - method: "POST", - headers, - body: JSON.stringify(body), - }); - const json = await res.json().catch(() => ({})); - return { status: res.status, json }; -} - -async function get(path) { - const headers = {}; - if (SECRET) headers["Authorization"] = `Bearer ${SECRET}`; - const res = await fetch(`${BASE}/agentmemory${path}`, { headers }); - return res.json().catch(() => ({})); -} - -// 评测前缀:--clear 只清 fromAgent 以这些标记开头的项,绝不动用户真实 inbox。 -const EVAL_PREFIXES = ["[真信号]", "[边界]", "[噪音]", "[好简报]", "[差简报]"]; -function isEvalItem(item) { - const from = (item && item.fromAgent) || ""; - return EVAL_PREFIXES.some((p) => from.startsWith(p)); -} - -async function clearAwaiting() { - const r = await get("/inbox?status=awaiting&limit=200"); - const items = ((r && r.items) || []).filter(isEvalItem); - for (const it of items) await post("/inbox/dismiss", { id: it.id }); - return items.length; -} - -async function main() { - if (process.argv.includes("--clear")) { - const n = await clearAwaiting(); - console.log(`cleared ${n} eval-prefixed awaiting item(s)`); - } - let q = 0, b = 0; - for (const item of QUESTIONS) { - const r = await post("/inbox/ask", { - body: item.body, - fromAgent: item.from, - project: "/repo/agentmemory", - sourceObservationIds: item.obs, - }); - if (r.status === 201) q++; - else console.error("ask failed:", item.from, r.status, r.json); - } - for (const item of BRIEFINGS) { - const r = await post("/inbox/notify", { - body: item.body, - fromAgent: item.from, - project: "/repo/agentmemory", - sourceObservationIds: item.obs, - }); - if (r.status === 201) b++; - else console.error("notify failed:", item.from, r.status, r.json); - } - console.log(`seeded ${q} questions + ${b} briefings (total ${q + b})`); -} - -main().catch((e) => { - console.error(e); - process.exit(1); -}); diff --git a/scripts/sync-ai-validation-table.mjs b/scripts/sync-ai-validation-table.mjs deleted file mode 100644 index 362a9656..00000000 --- a/scripts/sync-ai-validation-table.mjs +++ /dev/null @@ -1,135 +0,0 @@ -import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; -import path from 'node:path'; - -function read(file) { - return readFileSync(file, 'utf8'); -} - -function readJson(file) { - return JSON.parse(read(file)); -} - -function pass(value) { - if (value === true) return true; - const text = String(value || '').toLowerCase(); - return ['通过', '已通过', 'pass', 'passed', 'ok'].some((word) => text.includes(word)); -} - -function providerName(value) { - const text = String(value || '').toLowerCase(); - if (text.includes('chatgpt')) return 'ChatGPT'; - if (text.includes('claude')) return 'Claude'; - if (text.includes('gemini')) return 'Gemini'; - if (text.includes('perplexity')) return 'Perplexity'; - if (text.includes('grok')) return 'Grok'; - if (text.includes('deepseek')) return 'DeepSeek'; - return String(value || 'Unknown').trim(); -} - -function evidencePassed(item) { - const ai = item.data.ai || {}; - const manual = item.data.manualValidation || {}; - const matched = ai.matchedSelectors || {}; - return !!( - ai.supportedAiPage && - ai.provider && - ai.editorFound && - matched.editor && - ai.anchorFound && - matched.anchor && - ai.memoryWidgetVisible && - ai.placement && - matched.send && - matched.turn && - ai.checkedAt && - pass(manual.memoryInsertPassed) && - pass(manual.diagnosticsCopied) && - pass(manual.siteInputStillWorks) - ); -} - -function dateOf(item) { - const value = item.data.ai?.checkedAt || item.data.generatedAt || ''; - return value ? String(value).slice(0, 10) : '-'; -} - -function evidenceRow(product, domain, item) { - if (!item) { - return `| ${product} | \`${domain}\` | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | 待验收 | - | - |`; - } - const ai = item.data.ai || {}; - const manual = item.data.manualValidation || {}; - const matched = ai.matchedSelectors || {}; - const ok = evidencePassed(item); - const status = ok ? '已通过' : '待修复'; - return [ - `| ${product}`, - ` \`${domain}\``, - ` ${providerName(ai.provider) === product ? '已通过' : '待修复'}`, - ` ${ai.editorFound && matched.editor ? '已通过' : '待修复'}`, - ` ${ai.memoryWidgetVisible ? '已通过' : '待修复'}`, - ` ${pass(manual.memoryInsertPassed) ? '已通过' : '待修复'}`, - ` ${pass(manual.diagnosticsCopied) ? '已通过' : '待修复'}`, - ` ${status}`, - ` ${dateOf(item)}`, - ` ${item.file}${matched.editor || matched.anchor || matched.send || matched.turn ? `;selector: ${[matched.editor, matched.anchor, matched.send, matched.turn].filter(Boolean).join(' / ')}` : ''} |` - ].join(' |').replace(/\| \|/g, '|'); -} - -function latestEvidenceByProvider() { - const dir = 'docs/validation/browser-extension-ai-sites'; - if (!existsSync(dir)) return new Map(); - const items = readdirSync(dir) - .filter((file) => file.endsWith('.json')) - .map((file) => { - const fullPath = path.join(dir, file); - const data = readJson(fullPath); - return { file: fullPath, data, provider: providerName(data.ai?.provider), checkedAt: data.ai?.checkedAt || data.generatedAt || '' }; - }) - .sort((a, b) => String(b.checkedAt).localeCompare(String(a.checkedAt))); - const byProvider = new Map(); - for (const item of items) { - if (!byProvider.has(item.provider)) byProvider.set(item.provider, item); - } - return byProvider; -} - -const write = process.argv.includes('--write'); -const check = process.argv.includes('--check'); -const validationPath = 'docs/browser-extension-ai-validation-cn.md'; -const products = [ - ['ChatGPT', 'chatgpt.com'], - ['Claude', 'claude.ai'], - ['Gemini', 'gemini.google.com'], - ['Perplexity', 'www.perplexity.ai'], - ['Grok', 'grok.com'], - ['DeepSeek', 'chat.deepseek.com'] -]; - -const evidence = latestEvidenceByProvider(); -const rows = [ - '| 产品 | 目标域名 | Provider | 输入框 | 记忆提示 | 插入 | 复制问题信息 | 结果 | 日期 | 证据/备注 |', - '| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |', - ...products.map(([product, domain]) => evidenceRow(product, domain, evidence.get(product))) -].join('\n'); - -const markdown = read(validationPath); -const pattern = /\| 产品 \| 目标域名 \| Provider \| 输入框 \| 记忆提示 \| 插入 \| 复制问题信息 \| 结果 \| 日期 \| 证据\/备注 \|\n\| --- \| --- \| --- \| --- \| --- \| --- \| --- \| --- \| --- \| --- \|\n(?:\|.*\|\n?)+?(?=\n## 诊断 JSON 示例)/; -if (!pattern.test(markdown)) throw new Error('Could not locate AI validation table.'); -const next = markdown.replace(pattern, rows); - -if (check && next !== markdown) { - console.error('AI validation table is out of sync. Run npm run sync:ai-validation-table.'); - process.exit(1); -} - -if (write && next !== markdown) { - writeFileSync(validationPath, next); - console.log(`updated ${validationPath}`); -} else if (write) { - console.log('AI validation table already up to date'); -} else if (!check) { - console.log(rows); -} - -if (check) console.log('AI validation table sync check ok'); diff --git a/scripts/test.sh b/scripts/test.sh deleted file mode 100755 index 04cb5429..00000000 --- a/scripts/test.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -# Unified test entrypoint for humans, CI, and agents. -# One command to remember; wraps the canonical npm script. -set -euo pipefail -exec npm test -- "$@" diff --git a/scripts/viewer-preview-proxy.cjs b/scripts/viewer-preview-proxy.cjs deleted file mode 100644 index a1a1fa1c..00000000 --- a/scripts/viewer-preview-proxy.cjs +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env node -// Viewer 预览代理 —— 给 Claude Preview / 浏览器实证用。 -// -// 为什么需要它:viewer 服务器(src/viewer/server.ts)有 DNS-rebinding 防护, -// 只放行 loopback Host 头(否则返回 "forbidden host", server.ts:1068)。 -// preview 工具经代理/隧道访问时带的 Host 头常不在白名单里,会被拒。 -// 本代理在转发时把 Host 头重写为 `localhost:`,绕过白名单, -// 让 preview 工具能正常打开工作台做实证。 -// -// 端口: -// 监听 LISTEN_PORT(默认 3198) → 转发到 VIEWER_PORT -// 上游 viewer 端口的确定顺序: -// 1. 显式环境变量 AGENTMEMORY_VIEWER_PORT / III_VIEWER_PORT(最高优先) -// 2. 否则查 REST livez(AGENTMEMORY_REST_PORT 或 3111)上报的 viewerPort—— -// 这样多 worker / 端口漂移时代理自动跟随真源,不会卡在死端口(见 #C3 收尾运行态收敛) -// 3. 兜底 3114(= restPort+3 的传统默认) -// 可用环境变量覆盖: -// VIEWER_PROXY_PORT 代理监听端口(默认 3198) -// AGENTMEMORY_VIEWER_PORT / III_VIEWER_PORT 显式上游 viewer 端口 -// AGENTMEMORY_REST_PORT 自动发现时查询的 REST 端口(默认 3111) -// -// 用法: -// node scripts/viewer-preview-proxy.cjs -// VIEWER_PROXY_PORT=4000 AGENTMEMORY_VIEWER_PORT=3115 node scripts/viewer-preview-proxy.cjs -// 通常由 .claude/launch.json 的 "viewer-proxy" 配置拉起。 - -const http = require("http"); - -const LISTEN_PORT = parseInt(process.env.VIEWER_PROXY_PORT || "3198", 10); -const VIEWER_HOST = "localhost"; -const EXPLICIT_VIEWER_PORT = - process.env.AGENTMEMORY_VIEWER_PORT || process.env.III_VIEWER_PORT || ""; -const REST_PORT = parseInt(process.env.AGENTMEMORY_REST_PORT || "3111", 10); -const FALLBACK_VIEWER_PORT = 3114; - -function discoverViewerPort() { - if (EXPLICIT_VIEWER_PORT) { - return Promise.resolve(parseInt(EXPLICIT_VIEWER_PORT, 10)); - } - return new Promise((resolve) => { - const req = http.get( - { host: VIEWER_HOST, port: REST_PORT, path: "/agentmemory/livez", timeout: 1500 }, - (r) => { - let raw = ""; - r.on("data", (c) => { raw += c; }); - r.on("end", () => { - try { - const port = JSON.parse(raw).viewerPort; - resolve(Number.isInteger(port) ? port : FALLBACK_VIEWER_PORT); - } catch (_e) { - resolve(FALLBACK_VIEWER_PORT); - } - }); - }, - ); - req.on("error", () => resolve(FALLBACK_VIEWER_PORT)); - req.on("timeout", () => { req.destroy(); resolve(FALLBACK_VIEWER_PORT); }); - }); -} - -function startProxy(VIEWER_PORT) { - const REWRITTEN_HOST = `${VIEWER_HOST}:${VIEWER_PORT}`; - - const server = http.createServer((req, res) => { - // 重写 Host 头为 loopback,满足 viewer 的 DNS-rebinding 白名单 - const headers = Object.assign({}, req.headers, { host: REWRITTEN_HOST }); - const opt = { - host: VIEWER_HOST, - port: VIEWER_PORT, - path: req.url, - method: req.method, - headers, - }; - const upstream = http.request(opt, (pr) => { - res.writeHead(pr.statusCode || 502, pr.headers); - pr.pipe(res); - }); - upstream.on("error", (e) => { - res.writeHead(502, { "content-type": "text/plain; charset=utf-8" }); - res.end( - `viewer-preview-proxy: 上游 ${REWRITTEN_HOST} 不可达 (${e.message})。\n` + - `先用 \`npm run start:local-memory\` 起 viewer,或设 AGENTMEMORY_VIEWER_PORT。`, - ); - }); - req.pipe(upstream); - }); - - server.on("error", (e) => { - console.error(`viewer-preview-proxy 启动失败: ${e.message}`); - process.exit(1); - }); - - server.listen(LISTEN_PORT, () => { - console.log( - `viewer-preview-proxy: :${LISTEN_PORT} → ${REWRITTEN_HOST} (Host 头已重写,绕过白名单)`, - ); - }); -} - -discoverViewerPort().then(startProxy); diff --git a/scripts/wizard-ai-validation-evidence.mjs b/scripts/wizard-ai-validation-evidence.mjs deleted file mode 100644 index 4bd2fbe7..00000000 --- a/scripts/wizard-ai-validation-evidence.mjs +++ /dev/null @@ -1,162 +0,0 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; -import { execFileSync } from 'node:child_process'; -import { createInterface } from 'node:readline/promises'; -import { stdin as input, stdout as output } from 'node:process'; -import path from 'node:path'; - -const evidenceDir = 'docs/validation/browser-extension-ai-sites'; - -function argValue(args, name) { - const index = args.indexOf(name); - return index >= 0 ? args[index + 1] : ''; -} - -function readClipboard() { - try { - return execFileSync('pbpaste', { encoding: 'utf8' }); - } catch { - throw new Error('Cannot read clipboard. Use --file diagnostics.json instead.'); - } -} - -function readInput(args) { - const file = argValue(args, '--file'); - if (file) return readFileSync(file, 'utf8'); - return readClipboard(); -} - -function parseJson(text) { - const trimmed = String(text || '').trim(); - try { - return JSON.parse(trimmed); - } catch { - const first = trimmed.indexOf('{'); - const last = trimmed.lastIndexOf('}'); - if (first >= 0 && last > first) return JSON.parse(trimmed.slice(first, last + 1)); - throw new Error('Input does not contain valid diagnostic JSON.'); - } -} - -function providerSlug(value) { - const text = String(value || '').trim().toLowerCase(); - if (text.includes('chatgpt')) return 'chatgpt'; - if (text.includes('claude')) return 'claude'; - if (text.includes('gemini')) return 'gemini'; - if (text.includes('perplexity')) return 'perplexity'; - if (text.includes('grok')) return 'grok'; - if (text.includes('deepseek')) return 'deepseek'; - return text.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'unknown'; -} - -function yes(value) { - return /^(y|yes|true|1|通过|是|成功|ok)$/i.test(String(value || '').trim()); -} - -async function askYesNo(rl, question, defaultYes = true) { - const suffix = defaultYes ? ' [Y/n] ' : ' [y/N] '; - const answer = await rl.question(`${question}${suffix}`); - if (!answer.trim()) return defaultYes; - return yes(answer); -} - -async function askText(rl, question, fallback = '') { - const answer = await rl.question(`${question}${fallback ? ` (${fallback})` : ''}: `); - return answer.trim() || fallback; -} - -function normalizeEvidence(data, answers) { - const provider = answers.provider || data?.ai?.provider || data?.page?.host || 'Unknown'; - const generatedAt = data.generatedAt || new Date().toISOString(); - return { - product: data.product || 'Agent Memory Lab Browser Extension', - extension: data.extension || {}, - generatedAt, - page: data.page || {}, - ai: { - ...(data.ai || {}), - provider, - checkedAt: (data.ai && data.ai.checkedAt) || generatedAt - }, - manualValidation: { - memoryInsertPassed: answers.memoryInsertPassed, - diagnosticsCopied: answers.diagnosticsCopied, - siteInputStillWorks: answers.siteInputStillWorks, - browser: answers.browser, - notes: answers.notes - } - }; -} - -function outputPath(data, args) { - const explicit = argValue(args, '--out'); - if (explicit) return explicit; - const date = new Date().toISOString().slice(0, 10); - const provider = providerSlug(data.ai && data.ai.provider); - return path.join(evidenceDir, `${date}-${provider}.json`); -} - -function printUsage() { - console.log(`Usage: - npm run wizard:ai-validation-evidence - npm run wizard:ai-validation-evidence -- --file diagnostics.json - npm run wizard:ai-validation-evidence -- --file diagnostics.json --yes --browser "Chrome 版本号" --notes "无隐私备注" - -The wizard reads copied side-panel diagnostics, asks for manual pass checks, -and writes docs/validation/browser-extension-ai-sites/YYYY-MM-DD-provider.json.`); -} - -const args = process.argv.slice(2); -if (args.includes('--help') || args.includes('-h')) { - printUsage(); - process.exit(0); -} - -async function collectAnswers(diagnostic) { - const detectedProvider = diagnostic?.ai?.provider || diagnostic?.page?.host || 'Unknown'; - const providerFromArgs = argValue(args, '--provider'); - const browserFromArgs = argValue(args, '--browser'); - const notesFromArgs = argValue(args, '--notes'); - if (args.includes('--yes') || args.includes('--pass')) { - return { - provider: providerFromArgs || detectedProvider, - diagnosticsCopied: true, - memoryInsertPassed: true, - siteInputStillWorks: true, - browser: browserFromArgs || 'Chrome / Edge version', - notes: notesFromArgs || 'No private chat content included' - }; - } - const rl = createInterface({ input, output }); - try { - console.log(`Detected provider: ${detectedProvider}`); - console.log(`Page: ${diagnostic?.page?.url || 'Unknown URL'}`); - const provider = await askText(rl, 'Provider name', providerFromArgs || detectedProvider); - const diagnosticsCopied = await askYesNo(rl, 'Did the side panel diagnostics copy successfully?', true); - const memoryInsertPassed = await askYesNo(rl, 'Did inserting/copying a local memory work?', false); - const siteInputStillWorks = await askYesNo(rl, 'Did the original AI site input and send flow still work?', false); - const browser = await askText(rl, 'Browser and version', browserFromArgs || 'Chrome / Edge version'); - const notes = await askText(rl, 'Privacy-safe notes', notesFromArgs || 'No private chat content included'); - return { provider, diagnosticsCopied, memoryInsertPassed, siteInputStillWorks, browser, notes }; - } finally { - rl.close(); - } -} - -async function main() { - const diagnostic = parseJson(readInput(args)); - const answers = await collectAnswers(diagnostic); - const evidence = normalizeEvidence(diagnostic, answers); - const out = outputPath(evidence, args); - if (existsSync(out) && !args.includes('--force')) { - throw new Error(`${out} already exists. Add --force or choose --out.`); - } - mkdirSync(path.dirname(out), { recursive: true }); - writeFileSync(out, `${JSON.stringify(evidence, null, 2)}\n`); - console.log(`AI validation evidence written: ${out}`); - console.log('Next: npm run check:ai-validation-evidence && npm run sync:ai-validation-table'); -} - -main().catch((err) => { - console.error(err && err.message ? err.message : err); - process.exitCode = 1; -}); diff --git a/scripts/write-delivery-summary.mjs b/scripts/write-delivery-summary.mjs deleted file mode 100644 index e6c4b86c..00000000 --- a/scripts/write-delivery-summary.mjs +++ /dev/null @@ -1,571 +0,0 @@ -import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; -import { createHash } from 'node:crypto'; - -function readJson(path) { - return JSON.parse(readFileSync(path, 'utf8')); -} - -function read(path) { - return readFileSync(path, 'utf8'); -} - -function git(args) { - const result = spawnSync('git', args, { encoding: 'utf8' }); - return result.status === 0 ? result.stdout.trim() : ''; -} - -function fileSize(path) { - return existsSync(path) ? statSync(path).size : 0; -} - -function sha256(path) { - if (!existsSync(path)) return ''; - return createHash('sha256').update(readFileSync(path)).digest('hex'); -} - -function extractGateTable(markdown) { - const lines = markdown.split(/\r?\n/); - const start = lines.findIndex((line) => line.trim() === '| 状态 | 结论 | 证据 |'); - if (start < 0) return ''; - return lines.slice(start, start + 5).join('\n'); -} - -function parseValidationRows(markdown) { - const lines = markdown.split(/\r?\n/); - const headerIndex = lines.findIndex((line) => line.startsWith('| 产品 | 目标域名 | Provider |')); - if (headerIndex < 0) return []; - const rows = []; - for (const line of lines.slice(headerIndex + 2)) { - if (!line.startsWith('|')) break; - const cells = line.split('|').slice(1, -1).map((cell) => cell.trim()); - if (cells.length < 10) continue; - rows.push({ - product: cells[0], - domain: cells[1], - provider: cells[2], - editor: cells[3], - hint: cells[4], - insert: cells[5], - diagnostics: cells[6], - result: cells[7], - date: cells[8], - evidence: cells[9] - }); - } - return rows; -} - -function validationPassed(row) { - const passWords = ['通过', '已通过', 'pass', 'passed', 'ok']; - return [row.provider, row.editor, row.hint, row.insert, row.diagnostics, row.result].every((value) => { - const lower = String(value || '').toLowerCase(); - return passWords.some((word) => lower.includes(word)); - }) && row.date !== '-' && row.evidence !== '-'; -} - -mkdirSync('artifacts', { recursive: true }); - -const pkg = readJson('package.json'); -const manifest = readJson('browser-extension/manifest.json'); -const releaseGates = read('docs/release-gates-cn.md'); -const aiValidation = read('docs/browser-extension-ai-validation-cn.md'); -const zipPath = 'artifacts/agent-memory-lab-extension.zip'; -const aiTesterPackPath = 'artifacts/ai-validation-run/tester-pack-cn.md'; -const aiQuickstartPath = 'artifacts/ai-validation-run/quickstart-cn.md'; -const generatedAt = new Date().toISOString(); -const branch = git(['branch', '--show-current']) || 'unknown'; -const commit = git(['rev-parse', '--short', 'HEAD']) || 'unknown'; -const dirty = git(['status', '--short']).split(/\r?\n/).filter((line) => line && !line.startsWith('?? .learnings/') && !line.includes('index.html.bak-')).length > 0; -const zipSize = fileSize(zipPath); -const zipSha256 = sha256(zipPath); -const aiEvidenceSummaryPath = 'artifacts/ai-validation-evidence-summary.json'; -const aiEvidenceSummary = existsSync(aiEvidenceSummaryPath) ? readJson(aiEvidenceSummaryPath) : null; -const requiredAiProducts = ['ChatGPT', 'Claude', 'Gemini', 'Perplexity']; -const aiRows = parseValidationRows(aiValidation); -const requiredAiRows = requiredAiProducts.map((product) => aiRows.find((row) => row.product === product)).filter(Boolean); -const passedAiRows = requiredAiRows.filter(validationPassed); -const missingAiProducts = requiredAiProducts.filter((product) => !requiredAiRows.some((row) => row.product === product)); -const notPassedAiProducts = requiredAiRows.filter((row) => !validationPassed(row)).map((row) => row.product); -const deliveryManifest = { - product: 'Agent Memory Lab', - generatedAt, - package: { - name: pkg.name, - version: pkg.version - }, - extension: { - name: manifest.name, - version: manifest.version, - manifestVersion: manifest.manifest_version - }, - git: { - branch, - commit, - trackedChangesPending: dirty - }, - artifacts: { - extensionZip: { - path: zipPath, - exists: existsSync(zipPath), - bytes: zipSize, - sha256: zipSha256 - }, - extensionFolder: { - path: 'browser-extension/', - exists: existsSync('browser-extension/manifest.json') - }, - loadInstructions: { - path: 'browser-extension/LOAD-THIS-FIRST.md', - exists: existsSync('browser-extension/LOAD-THIS-FIRST.md') - }, - demoPage: { - path: 'dist/viewer/demo/browser-extension.html', - exists: existsSync('dist/viewer/demo/browser-extension.html') - }, - externalTesterHandout: { - path: 'artifacts/external-tester-handout.md', - exists: true - }, - aiValidationTesterPack: { - path: aiTesterPackPath, - command: 'npm run make:ai-validation-tester-pack', - exists: existsSync(aiTesterPackPath) - }, - aiValidationQuickstart: { - path: aiQuickstartPath, - command: 'npm run make:ai-validation-tester-pack', - exists: existsSync(aiQuickstartPath) - }, - releaseNotes: { - path: 'artifacts/release-notes.md', - exists: true - }, - githubReleaseDraft: { - path: 'artifacts/github-release-draft.md', - exists: true - }, - screenshots: { - dashboard: existsSync('docs/readme-assets/screenshots/dashboard.jpg'), - skills: existsSync('docs/readme-assets/screenshots/skills.jpg') - } - }, - coreExperience: { - externalTestingEntry: { - popupVersionVisible: true, - popupLocalTestingStatusVisible: true, - testerGuideUrl: 'https://github.com/MaimoryLab/agentmemory-lab/blob/main/docs/external-tester-guide-cn.md' - }, - reviewDraft: { - popup: true, - sidePanel: true, - editableTitle: true, - editableContent: true, - editableProject: true, - editableTags: true, - editableLessonFlag: true, - candidateToDraft: true, - savesToReviewQueue: true - }, - aiInputMemoryHint: { - localDemo: true, - supportedSitesSource: 'browser-extension/shared/site-config.js', - diagnosticsCopy: true, - sidePanelTestCardsEntry: true, - diagnosticValidationGuide: true, - mem0Reference: { - source: 'https://github.com/mem0ai/mem0-chrome-extension', - documentedIn: 'docs/browser-extension-mem0-reference-cn.md', - adapterPattern: 'supported-sites config first, split provider adapter when DOM logic grows', - inputPlacement: 'near AI prompt editor', - reviewFirstDifference: 'all captured candidates save to /agentmemory/review before long-term memory' - } - }, - reviewQueue: { - source: 'browser-extension', - longTermWriteRequiresViewerReview: true - } - }, - externalTesting: { - zipLoadChecklist: { - path: 'browser-extension/LOAD-THIS-FIRST.md', - exists: existsSync('browser-extension/LOAD-THIS-FIRST.md') - }, - testerGuide: { - path: 'docs/external-tester-guide-cn.md', - exists: existsSync('docs/external-tester-guide-cn.md') - }, - feedbackTemplate: { - path: 'docs/external-feedback-template-cn.md', - exists: existsSync('docs/external-feedback-template-cn.md') - }, - issueTemplate: { - path: '.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml', - exists: existsSync('.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml') - }, - feedbackTriage: { - path: 'docs/external-feedback-triage-cn.md', - exists: existsSync('docs/external-feedback-triage-cn.md') - }, - evidenceRecorder: { - command: 'npm run wizard:ai-validation-evidence', - exists: existsSync('scripts/record-ai-validation-evidence.mjs') - }, - aiValidationTesterPack: { - path: aiTesterPackPath, - command: 'npm run make:ai-validation-tester-pack', - exists: existsSync('scripts/make-ai-validation-tester-pack.mjs') - }, - aiValidationQuickstart: { - path: aiQuickstartPath, - command: 'npm run make:ai-validation-tester-pack', - exists: existsSync(aiQuickstartPath) - }, - aiSiteTestCards: { - path: 'docs/browser-extension-ai-site-test-cards-cn.md', - viewerPath: '/docs/browser-extension-ai-site-test-cards-cn.md', - zipPath: 'browser-extension/AI-SITE-TEST-CARDS.md', - sidePanelEntry: true, - diagnosticField: 'validationGuide', - exists: existsSync('docs/browser-extension-ai-site-test-cards-cn.md') && existsSync('browser-extension/AI-SITE-TEST-CARDS.md') - } - }, - releaseState: { - localDemo: 'ready', - externalTesting: 'mostly-ready', - publicRelease: 'not-ready', - realSiteValidation: { - requiredProducts: requiredAiProducts, - passed: passedAiRows.map((row) => row.product), - notPassed: notPassedAiProducts, - missingRows: missingAiProducts, - passedCount: passedAiRows.length, - requiredCount: requiredAiProducts.length, - source: 'docs/browser-extension-ai-validation-cn.md', - evidenceDirectory: 'docs/validation/browser-extension-ai-sites', - evidenceSummary: aiEvidenceSummaryPath, - evidencePassedCount: aiEvidenceSummary ? aiEvidenceSummary.passedCount : 0, - evidenceRequiredCount: aiEvidenceSummary ? aiEvidenceSummary.requiredCount : requiredAiProducts.length, - evidenceNotPassed: aiEvidenceSummary ? aiEvidenceSummary.notPassedRequired : requiredAiProducts - }, - publicReleaseBlockers: [ - 'real AI site validation evidence', - 'public privacy policy URL', - 'non-sensitive store screenshots', - 'store review materials' - ] - }, - commands: [ - 'npm run package:browser-extension', - 'npm run check:delivery', - 'npm run make:ai-validation-tester-pack', - 'npm run check:workbench' - ] -}; - -const summary = `# Agent Memory Lab Delivery Summary - -Generated: ${generatedAt} - -## Version - -| Item | Value | -| --- | --- | -| Package | ${pkg.name}@${pkg.version} | -| Extension | ${manifest.name} ${manifest.version} | -| Branch | ${branch} | -| Commit | ${commit}${dirty ? ' (tracked changes pending)' : ''} | - -## Artifacts - -| Artifact | Status | -| --- | --- | -| Extension zip | ${existsSync(zipPath) ? `${zipPath} (${zipSize} bytes)` : 'missing'} | -| Extension zip sha256 | ${zipSha256 || 'missing'} | -| Delivery manifest | artifacts/delivery-manifest.json | -| Extension source folder | ${existsSync('browser-extension/manifest.json') ? 'browser-extension/' : 'missing'} | -| Zip load instructions | ${existsSync('browser-extension/LOAD-THIS-FIRST.md') ? 'browser-extension/LOAD-THIS-FIRST.md' : 'missing'} | -| AI validation tester pack | ${existsSync(aiTesterPackPath) ? aiTesterPackPath : 'run npm run make:ai-validation-tester-pack'} | -| AI validation quickstart | ${existsSync(aiQuickstartPath) ? aiQuickstartPath : 'run npm run make:ai-validation-tester-pack'} | -| Demo page | ${existsSync('dist/viewer/demo/browser-extension.html') ? 'dist/viewer/demo/browser-extension.html' : 'missing'} | -| Dashboard screenshot | ${existsSync('docs/readme-assets/screenshots/dashboard.jpg') ? 'docs/readme-assets/screenshots/dashboard.jpg' : 'missing'} | -| Skills screenshot | ${existsSync('docs/readme-assets/screenshots/skills.jpg') ? 'docs/readme-assets/screenshots/skills.jpg' : 'missing'} | - -## Core Experience - -| Capability | Status | -| --- | --- | -| Popup version and tester guide entry | ready | -| Popup editable review draft | ready | -| Side panel editable review draft | ready | -| Draft project / tags / lesson flag | ready | -| Candidate-to-draft flow | ready | -| AI memory candidates require concrete conversation or selection | ready | -| Empty AI memory state explains next step | ready | -| Save to Viewer review queue | ready | -| Local AI input memory hint demo | ready | -| Real AI site validation | ${passedAiRows.length}/${requiredAiProducts.length} passed | - -## External Testing Loop - -| Item | Status | -| --- | --- | -| Zip load checklist | ${existsSync('browser-extension/LOAD-THIS-FIRST.md') ? 'ready' : 'missing'} | -| External tester guide | ${existsSync('docs/external-tester-guide-cn.md') ? 'ready' : 'missing'} | -| Feedback template | ${existsSync('docs/external-feedback-template-cn.md') ? 'ready' : 'missing'} | -| GitHub issue template | ${existsSync('.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml') ? 'ready' : 'missing'} | -| Feedback triage guide | ${existsSync('docs/external-feedback-triage-cn.md') ? 'ready' : 'missing'} | -| AI evidence recorder | ${existsSync('scripts/record-ai-validation-evidence.mjs') ? 'ready' : 'missing'} | -| AI validation tester pack | ${existsSync('scripts/make-ai-validation-tester-pack.mjs') ? 'ready' : 'missing'} | -| AI validation quickstart | ${existsSync(aiQuickstartPath) ? 'ready' : 'missing'} | - -## Release Gates - -${extractGateTable(releaseGates)} - -## Reviewer Checklist - -- Open the Viewer dashboard and confirm the browser entry card exposes: extension zip, external handout, AI tester pack, feedback template, and triage guide. -- Open \`/demo/browser-extension.html\` and confirm the local memory hint demo still shows “记忆建议”. -- Load \`browser-extension/\` in Chrome / Edge developer mode, then confirm popup and side panel both keep the editable review draft before saving. -- On an AI page with no captured conversation, confirm the side panel explains that it cannot generate memory until a real conversation is expanded or concrete text is selected. -- Confirm the external feedback template and GitHub issue template ask for \`turnCount\`, \`matchedSelectors.turn\`, and whether the candidate came from concrete conversation or selected text. -- Review \`docs/feishu/agentmemory-project-intro-cn.md\` and the three whiteboard sources under \`docs/feishu/whiteboards/\` for product narrative consistency. -- Do not mark public release ready until \`npm run check:release-public\` passes with ChatGPT, Claude, Gemini, and Perplexity evidence at 4/4. - -## Real AI Site Validation - -| Item | Value | -| --- | --- | -| Required products | ${requiredAiProducts.join(', ')} | -| Passed | ${passedAiRows.length}/${requiredAiProducts.length} | -| Not passed | ${notPassedAiProducts.length ? notPassedAiProducts.join(', ') : 'none'} | -| Missing rows | ${missingAiProducts.length ? missingAiProducts.join(', ') : 'none'} | -| Source | docs/browser-extension-ai-validation-cn.md | -| Evidence directory | docs/validation/browser-extension-ai-sites | -| Evidence summary | ${existsSync(aiEvidenceSummaryPath) ? aiEvidenceSummaryPath : 'generated by npm run check:ai-validation-evidence'} | -| Evidence passed | ${aiEvidenceSummary ? `${aiEvidenceSummary.passedCount}/${aiEvidenceSummary.requiredCount}` : '0/4'} | - -## Verification Commands - -- \`npm run package:browser-extension\` -- \`npm run check:delivery\` -- \`npm run status:delivery\` -- \`npm run make:ai-validation-tester-pack\` -- \`npm run check:ai-validation-evidence\` -- \`npm run check:workbench\` when the full local workbench should be running - -## Useful Links - -- README: \`README.md\` -- External tester guide: \`docs/external-tester-guide-cn.md\` -- External feedback template: \`docs/external-feedback-template-cn.md\` -- External tester issue template: \`.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml\` -- External feedback triage: \`docs/external-feedback-triage-cn.md\` -- AI validation log: \`docs/browser-extension-ai-validation-cn.md\` -- AI validation tester pack: \`artifacts/ai-validation-run/tester-pack-cn.md\` -- AI validation quickstart: \`artifacts/ai-validation-run/quickstart-cn.md\` -- Release gates: \`docs/release-gates-cn.md\` -- Feishu source: \`docs/feishu/agentmemory-project-intro-cn.md\` -`; - -const externalHandout = `# Agent Memory Lab 外部试用说明 - -生成时间:${generatedAt} - -这是一份给外部试用者的快速说明。当前版本适合本地试用和反馈,不是 Chrome Web Store 公开发布版。 - -## 你会拿到什么 - -- 插件压缩包:\`artifacts/agent-memory-lab-extension.zip\` -- Viewer 首页入口:打开“浏览器记忆入口”,可直接下载插件包、外测手册、验收一页纸、AI 验收包、反馈模板和分诊指南 -- 插件版本:${manifest.name} ${manifest.version} -- 当前提交:${commit}${dirty ? '(本地还有未提交改动)' : ''} -- zip sha256:\`${zipSha256 || 'missing'}\` - -## 先做这 5 步 - -1. 从 Viewer 首页下载插件包,或使用维护者发来的 \`artifacts/agent-memory-lab-extension.zip\`。 -2. 解压插件包,并打开解压后的 \`browser-extension/LOAD-THIS-FIRST.md\`。 -3. 在 Chrome / Edge 开发者模式加载解压后的 \`browser-extension/\` 文件夹。 -4. 打开 \`启动输出里的 Viewer 地址 + /demo/browser-extension.html\`,确认输入框旁出现“记忆建议”。 -5. 在弹窗或同步侧栏里编辑草稿的标题、正文、保存范围、分类备注和经验候选状态,再加入 Viewer 待审阅。 - -## 通过时应该看到 - -- 插件弹窗能显示版本和本地连接状态。 -- 同步侧栏能显示当前页面、候选记忆、候选经验和隐私提示。 -- AI 页面只有输入框草稿或网页介绍时,不会生成记忆候选;侧栏会说明需要展开真实对话或选中具体内容。 -- 记忆建议能出现在 demo 输入框旁,并能插入或复制。 -- 保存内容不会直接写入长期记忆,而是先进入 Viewer 待审阅队列。 -- Viewer 待审阅卡片能看到保存范围、分类备注、来源和经验候选状态。 - -## 真实 AI 页面验收 - -当前真实站点证据:${aiEvidenceSummary ? `${aiEvidenceSummary.passedCount}/${aiEvidenceSummary.requiredCount}` : '0/4'}。 - -公开发布前仍需 ChatGPT、Claude、Gemini、Perplexity 都通过真实页面验收。试用这些站点时,请打开同步侧栏,点击“复制问题信息”,然后用下面命令记录证据: - -\`\`\`bash -npm run wizard:ai-validation-evidence -- --clipboard -\`\`\` - -只有你真实确认“插入成功、诊断已复制、原站输入仍正常”以后,才加 \`--pass\`。 - -## 反馈问题 - -推荐使用 GitHub Issue 模板:\`.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml\`。 - -也可以复制:\`docs/external-feedback-template-cn.md\`。 - -反馈时请尽量提供:浏览器版本、试用页面、问题步骤、同步侧栏诊断 JSON、截图或录屏。真实 AI 页面问题请保留 \`turnCount\` 和 \`matchedSelectors.turn\`,并说明候选记忆是否来自具体对话或用户选中文本。请不要提交私人聊天全文、Cookie、访问令牌、API Key、学校申请材料或任何敏感信息。 - -## 当前边界 - -- 本地 demo:ready -- 外部试用闭环:ready -- 公开发布:not-ready -- 未通过的真实 AI 站点:${(aiEvidenceSummary ? aiEvidenceSummary.notPassedRequired : requiredAiProducts).join(', ')} -`; - -const releaseNotes = `# Agent Memory Lab ${manifest.version} Release Notes - -生成时间:${generatedAt} - -## 版本信息 - -- Package:${pkg.name}@${pkg.version} -- Browser extension:${manifest.name} ${manifest.version} -- Branch:${branch} -- Commit:${commit}${dirty ? '(本地还有未提交改动)' : ''} -- Extension zip: - - 路径: - - \`artifacts/agent-memory-lab-extension.zip\` - - 大小:${zipSize} bytes - - SHA256:\`${zipSha256 || 'missing'}\` - -## 本版可以交付给谁 - -这个版本适合给外部测试者做本地试用,目标是验证浏览器插件、Viewer 待审阅队列、真实 AI 页面诊断和反馈闭环。 - -这个版本不适合作为 Chrome Web Store 公开发布版,因为真实 AI 页面证据仍是 ${aiEvidenceSummary ? `${aiEvidenceSummary.passedCount}/${aiEvidenceSummary.requiredCount}` : '0/4'}。 - -## 本版新增和已就绪能力 - -- Viewer 总览可以显示外部试用状态、插件包状态、真实 AI 证据进度和测试卡入口。 -- Viewer 总览提供真实 AI 站点验收一页纸,外测者可以先按短清单执行,再看完整 AI 验收包。 -- 插件弹窗和同步侧栏都支持保存前编辑审阅草稿。 -- 草稿可编辑标题、正文、保存范围、分类备注,并可标记为可沉淀经验。 -- 浏览器插件保存内容默认进入 Viewer 待审阅队列,不直接写入长期记忆。 -- 同步侧栏可以复制真实 AI 页面诊断 JSON。 -- 同步侧栏提供本地测试卡入口:\`/docs/browser-extension-ai-site-test-cards-cn.md\`。 -- 诊断 JSON 带有 \`validationGuide\`,标明必测站点:ChatGPT、Claude、Gemini、Perplexity。 -- 真实 AI 页面证据要求 \`turnCount > 0\`,候选记忆必须来自具体对话或用户选中文本,不能来自页面标题、链接或输入框草稿。 -- 反馈模板和 GitHub Issue 模板会收集 \`turnCount\`、\`matchedSelectors.turn\` 和候选来源判断,方便维护者分诊会话抽取问题。 -- 插件 zip 内包含 \`browser-extension/LOAD-THIS-FIRST.md\` 和 \`browser-extension/AI-SITE-TEST-CARDS.md\`。 -- 交付检查会真实启动 Viewer,验证交付状态接口、测试卡文档和插件 demo 路由。 - -## 试用路径 - -1. 运行 \`npm run package:browser-extension\` 生成插件 zip。 -2. 解压 \`artifacts/agent-memory-lab-extension.zip\`。 -3. 在 Chrome / Edge 开发者模式加载解压后的 \`browser-extension/\` 文件夹。 -4. 打开 \`启动输出里的 Viewer 地址 + /demo/browser-extension.html\`,先验证本地 demo。 -5. 打开 ChatGPT、Claude、Gemini、Perplexity,按测试卡逐站验收。 -6. 复制同步侧栏诊断,用 \`npm run wizard:ai-validation-evidence\` 保存证据。 - -## 已知边界 - -- 真实 AI 页面证据:${aiEvidenceSummary ? `${aiEvidenceSummary.passedCount}/${aiEvidenceSummary.requiredCount}` : '0/4'}。 -- 未通过 / 待验证站点:${(aiEvidenceSummary ? aiEvidenceSummary.notPassedRequired : requiredAiProducts).join(', ')}。 -- 公开发布仍缺:真实 AI 站点通过证据、公开隐私政策 URL、无隐私商店截图、商店审核材料。 -- 本地 fixture 和 demo 只能防回归,不能替代真实站点验收。 -- 页面标题、链接、导航文案或输入框草稿不能作为公开发布通过证据里的记忆来源。 - -## 验证命令 - -- \`npm run check:browser-extension\` -- \`npm run package:browser-extension\` -- \`npm run check:delivery\` -- \`npm run status:delivery\` -- \`npm run check:release-gates\` -- \`npm run check:release-public\`:当前预期失败,直到真实 AI 站点证据齐全。 - -## 反馈入口 - -- 外部试用指南:\`docs/external-tester-guide-cn.md\` -- 反馈模板:\`docs/external-feedback-template-cn.md\` -- GitHub Issue 模板:\`.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml\` -- 反馈分诊指南:\`docs/external-feedback-triage-cn.md\` -`; - -const githubReleaseDraft = `# Agent Memory Lab ${manifest.version} 外部试用包 - -> 这是 GitHub Release 草稿,面向小范围外部试用;不是 Chrome Web Store 公开发布说明。 - -## 本次包 - -- Extension zip:\`${zipPath}\` -- SHA256:\`${zipSha256 || 'missing'}\` -- Branch:\`${branch}\` -- Commit:\`${commit}\`${dirty ? '(存在未提交的跟踪文件变更,发布前需复查)' : ''} -- 详细 Release Notes:\`artifacts/release-notes.md\` -- 外部试用手册:\`artifacts/external-tester-handout.md\` - -## 这版解决什么 - -- 提供一个本地优先的 Agent 记忆插件预览包,让用户在 AI 网页对话中收集候选记忆。 -- 参考 Mem0 / OpenMemory 的浏览器插件结构,把记忆入口放到 AI 输入框附近,而不是只藏在弹窗里。 -- 保留 Agent Memory Lab 的差异点:候选内容先进入 Viewer 待审阅队列,用户确认后再成为长期记忆。 -- 支持本地 demo、插件 popup、side panel、诊断 JSON、真实 AI 站点测试卡和反馈模板。 - -## 已就绪 - -- 本地演示页:\`/demo/browser-extension.html\` -- 插件加载说明:\`browser-extension/LOAD-THIS-FIRST.md\` -- 多站点配置:\`browser-extension/shared/site-config.js\` -- Mem0 参考说明:\`docs/browser-extension-mem0-reference-cn.md\` -- 真实 AI 站点测试卡:\`docs/browser-extension-ai-site-test-cards-cn.md\` -- 外部反馈模板:\`.github/ISSUE_TEMPLATE/external-tester-feedback-cn.yml\` - -## 已知边界 - -- 公开发布仍是 \`not-ready\`。 -- ChatGPT、Claude、Gemini、Perplexity 的真实页面证据还没有全部通过。 -- AI 网页 DOM 变化频繁,外部试用反馈必须附带 side panel 诊断 JSON。 -- 真实 AI 页面反馈需要保留 \`turnCount\` 和 \`matchedSelectors.turn\`,并说明候选是否来自具体对话或用户选中文本。 -- 这版用于小范围加载未打包插件测试,不用于商店上架。 - -## 发布前检查 - -\`\`\`bash -npm run check:delivery -npm run package:browser-extension -npm run status:delivery -npm run check:release-gates -\`\`\` - -公开发布前还必须通过: - -\`\`\`bash -npm run check:release-public -\`\`\` - -当前它预期失败,因为真实 AI 站点证据尚未齐全。 - -## 给测试者的第一句话 - -请先按 \`browser-extension/LOAD-THIS-FIRST.md\` 加载插件,再打开 Viewer 的本地 demo 完成一次“生成候选记忆 -> 修改草稿 -> 保存到待审阅队列”的闭环。真实 AI 页面测试请按 \`docs/browser-extension-ai-site-test-cards-cn.md\` 逐站记录;每个通过证据都必须来自具体对话或用户选中文本,并保留 \`turnCount > 0\` 的诊断字段。 -`; - -writeFileSync('artifacts/delivery-summary.md', summary); -writeFileSync('artifacts/external-tester-handout.md', externalHandout); -writeFileSync('artifacts/release-notes.md', releaseNotes); -writeFileSync('artifacts/github-release-draft.md', githubReleaseDraft); -writeFileSync('artifacts/delivery-manifest.json', `${JSON.stringify(deliveryManifest, null, 2)}\n`); -console.log('delivery summary: artifacts/delivery-summary.md'); -console.log('external tester handout: artifacts/external-tester-handout.md'); -console.log('release notes: artifacts/release-notes.md'); -console.log('github release draft: artifacts/github-release-draft.md'); -console.log('delivery manifest: artifacts/delivery-manifest.json'); diff --git a/src/auth.ts b/src/auth.ts deleted file mode 100644 index 95153bad..00000000 --- a/src/auth.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { timingSafeEqual, createHmac, randomBytes } from "node:crypto"; - -const hmacKey = randomBytes(32); -export const VIEWER_NONCE_PLACEHOLDER = "__AGENTMEMORY_VIEWER_NONCE__"; - -export function timingSafeCompare(a: string, b: string): boolean { - const hmacA = createHmac("sha256", hmacKey).update(a).digest(); - const hmacB = createHmac("sha256", hmacKey).update(b).digest(); - return timingSafeEqual(hmacA, hmacB); -} - -export function createViewerNonce(): string { - return randomBytes(16).toString("base64url"); -} - -export function buildViewerCsp(nonce: string): string { - return [ - "default-src 'none'", - "base-uri 'none'", - "frame-ancestors 'none'", - "object-src 'none'", - "form-action 'none'", - `script-src 'nonce-${nonce}'`, - "script-src-attr 'none'", - "style-src 'unsafe-inline'", - "connect-src 'self' http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:* wss://localhost:* wss://127.0.0.1:*", - "img-src 'self'", - "font-src 'self'", - ].join("; "); -} diff --git a/src/cli.ts b/src/cli.ts index dc6cb5be..6e8dc442 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,2777 +1,278 @@ #!/usr/bin/env node - -import { - spawn, - execFileSync, - spawnSync, - type ChildProcess, -} from "node:child_process"; -import { - existsSync, - mkdirSync, - readdirSync, - readFileSync, - readlinkSync, - rmSync, - statSync, - unlinkSync, - writeFileSync, -} from "node:fs"; -import { join, dirname, delimiter as PATH_DELIMITER } from "node:path"; -import { fileURLToPath } from "node:url"; -import { homedir, platform } from "node:os"; -import * as p from "@clack/prompts"; -import { generateId } from "./state/schema.js"; -import { - buildDiagnostics, - dryRunPlan, - parseEnvFile, - type Diagnostic, - type DiagnosticFixResult, - type DoctorContext, - type DoctorEffects, -} from "./cli/doctor-diagnostics.js"; -import { - buildRemovePlan, - formatPlan, - localBinIii, - type ConnectManifest, - type RemoveOptions, -} from "./cli/remove-plan.js"; -import { renderSplash } from "./cli/splash.js"; -import { isFirstRun, readPrefs, resetPrefs, writePrefs } from "./cli/preferences.js"; -import { runOnboarding } from "./cli/onboarding.js"; -import { setBootVerbose } from "./logger.js"; -import { VERSION } from "./version.js"; -import { getAgentMemoryDataDir, getTodoExtractorUserConfig, getUserEnvPath, writeUserEnv, WRITABLE_TODO_EXTRACT_KEYS } from "./config.js"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const args = process.argv.slice(2); -const IS_WINDOWS = platform() === "win32"; -const IS_VERBOSE = - args.includes("--verbose") || - args.includes("-v") || - process.env["AGENTMEMORY_VERBOSE"] === "1" || - process.env["AGENTMEMORY_VERBOSE"] === "true"; - -// Propagate the resolved verbosity to the worker's boot logger so the -// 25-line `[agentmemory] X registered` stream is either dropped or -// printed verbatim. Without this the worker's default (env-only) would -// disagree with the CLI flag. -setBootVerbose(IS_VERBOSE); - -const IS_RESET = args.includes("--reset"); - -// --version / -V early exit. Print VERSION + exit before any side effects -// (engine boot, env load, dir mkdir). `-v` is taken by --verbose so we -// reserve `-V` (capital) for version per POSIX convention. -if (args.includes("--version") || args.includes("-V")) { - process.stdout.write(`${VERSION}\n`); - process.exit(0); -} - -// Pinned iii-engine version. The unpinned `install.iii.dev/iii/main/install.sh` -// script tracks `latest`, which made every fresh agentmemory install pull -// engine 0.11.6 — and 0.11.6 introduces a new sandbox-everything-via- -// `iii worker add` worker model that agentmemory hasn't been refactored -// for yet (we still use the old `iii-exec watch` config-file model). The -// architectural mismatch surfaces as EPIPE reconnect loops and empty -// search results after save. Pin to v0.11.2 — the last engine that runs -// agentmemory's current worker model cleanly — until the refactor lands. -// Override env var AGENTMEMORY_III_VERSION lets users on the sandbox -// model already point at a newer engine without us cutting a release. -const IIPINNED_VERSION = - process.env["AGENTMEMORY_III_VERSION"] || "0.11.2"; - -// Map Node platform/arch → the asset name iii-hq/iii ships under -// https://github.com/iii-hq/iii/releases/download/iii/v/ -function iiiReleaseAsset(): string | null { - const p = platform(); - const a = process.arch; - if (p === "darwin" && a === "arm64") - return "iii-aarch64-apple-darwin.tar.gz"; - if (p === "darwin" && a === "x64") - return "iii-x86_64-apple-darwin.tar.gz"; - if (p === "linux" && a === "x64") - return "iii-x86_64-unknown-linux-gnu.tar.gz"; - if (p === "linux" && a === "arm64") - return "iii-aarch64-unknown-linux-gnu.tar.gz"; - if (p === "linux" && a === "arm") - return "iii-armv7-unknown-linux-gnueabihf.tar.gz"; - if (p === "win32" && a === "x64") - return "iii-x86_64-pc-windows-msvc.zip"; - if (p === "win32" && a === "arm64") - return "iii-aarch64-pc-windows-msvc.zip"; - return null; -} - -function iiiReleaseUrl(): string | null { - const asset = iiiReleaseAsset(); - if (!asset) return null; - // Tag name is monorepo-prefixed: `iii/v0.11.2`. Slash is URL-encoded - // by GitHub when serving the download path, hence `iii/v...` not `iii%2Fv...`. - return `https://github.com/iii-hq/iii/releases/download/iii/v${IIPINNED_VERSION}/${asset}`; -} - -function vlog(msg: string): void { - if (IS_VERBOSE) p.log.info(`[verbose] ${msg}`); -} - -if (args.includes("--help") || args.includes("-h")) { - console.log(` -AI-Todo — local-first To-Do extraction from your AI agent sessions - -Usage: agentmemory-lab [command] [options] - -Commands: - (default) Start AI-Todo worker - init Copy bundled .env.example to ~/.agentmemory/.env if absent - connect [agent] Wire agentmemory into an installed agent (codex). --all wires - every detected agent. --dry-run shows what would change. - --force re-installs. - status Show connection status, memory count, flags, and health - config get|set Read or update user Todo extractor config in ~/.agentmemory/.env - doctor Interactive diagnostic + fixer. [F]ix · [S]kip · [?]more · [Q]uit - --all: apply every fix without prompting (CI) - --dry-run: show what each fix would do, don't execute - remove Cleanly uninstall AI-Todo (pidfile, state, .env, binaries). - --force: skip confirmations · --keep-data: keep memory data - demo Seed sample sessions and show recall in action - upgrade Upgrade local deps + iii runtime (best effort) - stop [--force] Stop the running iii-engine started by this CLI. - --force bypasses the Docker-heuristic guard and signals - whatever pidfile+lsof report on the REST port (use when - the engine was started natively but state file is missing). - mcp Start standalone MCP shim — opt-in surface for MCP-only clients - (Cursor, Gemini CLI, etc). REST always available at :3111. - import-jsonl [p] Import Claude Code JSONL transcripts (default: ~/.claude/projects) - --max-files | --max-files=: override scan cap (default 200, max 1000; - out-of-range is rejected; for trees >1000 files, batch by subdirectory) - -Options: - --help, -h Show this help - --version, -V Print version and exit - --verbose, -v Show engine stderr, boot log, and diagnostic info - --reset Wipe ~/.agentmemory/preferences.json and re-run onboarding - --tools all|core Tool visibility (default: all = 51 tools; core = 8 essentials) - --no-engine Skip auto-starting iii-engine - --port Override REST port (default: 3111) - -Environment: - AGENTMEMORY_URL Full REST base URL (e.g. http://localhost:3111). - Honored by status, doctor, and MCP shim commands. - AGENTMEMORY_USE_DOCKER=1 Opt in to the bundled docker-compose path - instead of the native iii-engine binary. The - native binary is the default and recommended - path for local use; Docker is for self-hosting - (see deploy/). - AGENTMEMORY_III_VERSION Override pinned iii-engine version (default ${IIPINNED_VERSION}). - -Quick start: - git clone https://github.com/MaimoryLab/agentmemory-lab - cd agentmemory-lab - npm install - npm run build - npm run start:local-memory # start the MaimoryLab workbench - node dist/cli.mjs demo # seed local demo data - node dist/cli.mjs status # health + memory count + flags -`); - process.exit(0); -} - -const toolsIdx = args.indexOf("--tools"); -if (toolsIdx !== -1 && args[toolsIdx + 1]) { - process.env["AGENTMEMORY_TOOLS"] = args[toolsIdx + 1]; -} - -const portIdx = args.indexOf("--port"); -if (portIdx !== -1 && args[portIdx + 1]) { - process.env["III_REST_PORT"] = args[portIdx + 1]; -} - -const skipEngine = args.includes("--no-engine"); - -function getRestPort(): number { - const url = process.env["AGENTMEMORY_URL"]; - if (url) { - try { - const parsed = new URL(url).port; - if (parsed) return parseInt(parsed, 10); - } catch {} - } - return parseInt(process.env["III_REST_PORT"] || "3111", 10) || 3111; -} - -function getBaseUrl(): string { - const url = process.env["AGENTMEMORY_URL"]; - if (url) return url.replace(/\/+$/, ""); - return `http://localhost:${getRestPort()}`; -} - -let discoveredViewerPort: number | null = null; - -export async function discoverViewerPort(): Promise { - if (discoveredViewerPort !== null) return; - try { - const res = await fetch(`${getBaseUrl()}/agentmemory/livez`, { - signal: AbortSignal.timeout(1000), - }); - if (res.ok) { - const data = await res.json() as { viewerPort?: number | null }; - if (typeof data.viewerPort === "number") { - discoveredViewerPort = data.viewerPort; - } - } - } catch {} -} - -function getViewerUrl(): string { - const envUrl = process.env["AGENTMEMORY_VIEWER_URL"]; - if (envUrl) return envUrl.replace(/\/+$/, ""); - - if (discoveredViewerPort !== null) { - try { - const u = new URL(getBaseUrl()); - return `${u.protocol}//${u.hostname}:${discoveredViewerPort}`; - } catch { - return `http://localhost:${discoveredViewerPort}`; - } - } - - try { - const u = new URL(getBaseUrl()); - const vPort = - parseInt(process.env["AGENTMEMORY_VIEWER_PORT"] || "", 10) || - parseInt(process.env["III_VIEWER_PORT"] || "", 10) || - (parseInt(u.port || "3111", 10) || 3111) + 3; - return `${u.protocol}//${u.hostname}:${vPort}`; - } catch { - const vPort = - parseInt(process.env["AGENTMEMORY_VIEWER_PORT"] || "", 10) || - parseInt(process.env["III_VIEWER_PORT"] || "", 10) || - getRestPort() + 3; - return `http://localhost:${vPort}`; - } -} - -// WebSocket streams port. Engine writes here; the SDK and viewer -// subscribe. Honors both `III_STREAM_PORT` (the singular name the -// engine docs use post-0.11) and `III_STREAMS_PORT` (the name our -// own config.ts has used since 0.7) so a single source of truth in -// either form lights up the ready panel. -function getStreamPort(): number { - return ( - parseInt(process.env["III_STREAM_PORT"] || "", 10) || - parseInt(process.env["III_STREAMS_PORT"] || "", 10) || - 3112 - ); -} - -// Bridge WebSocket port — the iii engine's internal worker bus. -// Defaults to 49134 (engine convention) and is overridable via -// `III_ENGINE_PORT` or the legacy `III_ENGINE_URL=ws://host:port`. -function getEnginePort(): number { - const explicit = parseInt(process.env["III_ENGINE_PORT"] || "", 10); - if (explicit) return explicit; - const url = process.env["III_ENGINE_URL"]; - if (url) { - try { - const parsed = new URL(url).port; - if (parsed) return parseInt(parsed, 10); - } catch {} - } - return 49134; -} - -async function isEngineRunning(): Promise { - try { - await fetch(`${getBaseUrl()}/`, { - signal: AbortSignal.timeout(2000), - }); - return true; - } catch { - return false; - } -} - -async function isAgentmemoryReady(): Promise { - try { - const res = await fetch(`${getBaseUrl()}/agentmemory/livez`, { - signal: AbortSignal.timeout(2000), - }); - if (!res.ok) return false; - try { - const data = await res.json() as { viewerPort?: number | null; viewerSkipped?: boolean }; - if (typeof data.viewerPort === "number") { - discoveredViewerPort = data.viewerPort; - return true; - } - if (data.viewerSkipped) return true; - return false; - } catch { - return false; - } - } catch { - return false; - } -} - -function findIiiConfig(): string { - // Precedence (user-overridable wins): explicit env > project cwd > - // ~/.agentmemory/ > bundled. The bundled config used to win - // unconditionally, so users hitting the observability log-feedback - // loop (#519) had no way to drop a tamer config in place without - // editing node_modules. - const envPath = process.env["AGENTMEMORY_III_CONFIG"]; - const candidates = [ - ...(envPath ? [envPath] : []), - join(process.cwd(), "iii-config.yaml"), - join(homedir(), ".agentmemory", "iii-config.yaml"), - join(__dirname, "iii-config.yaml"), - join(__dirname, "..", "iii-config.yaml"), - ]; - for (const c of candidates) { - if (existsSync(c)) return c; - } - return ""; -} - -function whichBinary(name: string): string | null { - const cmd = IS_WINDOWS ? "where" : "which"; - try { - const out = execFileSync(cmd, [name], { - encoding: "utf-8", - stdio: ["ignore", "pipe", "pipe"], - }); - const first = out - .split(/\r?\n/) - .map((line) => line.trim()) - .find((line) => line.length > 0); - return first ?? null; - } catch { - return null; - } -} - -function fallbackIiiPaths(): string[] { - if (IS_WINDOWS) { - const userProfile = process.env["USERPROFILE"]; - if (!userProfile) return []; - return [ - join(userProfile, ".local", "bin", "iii.exe"), - join(userProfile, "bin", "iii.exe"), - ]; - } - const home = process.env["HOME"]; - if (!home) return ["/usr/local/bin/iii"]; - return [join(home, ".local", "bin", "iii"), "/usr/local/bin/iii"]; -} - -function iiiBinVersion(binPath: string): string | null { - try { - const out = execFileSync(binPath, ["--version"], { - encoding: "utf-8", - stdio: ["ignore", "pipe", "ignore"], - timeout: 3000, - }); - const match = out.match(/(\d+\.\d+\.\d+(?:[-+][\w.]+)?)/); - return match ? match[1]! : null; - } catch { - return null; - } -} - -// Enforce hard-pin on iii-engine version. Soft-warn lets the worker boot -// against a mismatched engine and crash at runtime (state::list-not-found -// on v0.13.0+, sandbox-everything trap on v0.11.6+). Refuse to start and -// point the user at the downgrade command — same escape hatch as before -// via AGENTMEMORY_III_VERSION, which redefines IIPINNED_VERSION upstream -// (line 75) so the mismatch check passes for users who knowingly want to -// run against a different engine. -function enforceEngineVersionPin(iiiBinPath: string | null | undefined): void { - if (!iiiBinPath) return; - const detected = iiiBinVersion(iiiBinPath); - if (!detected || detected === IIPINNED_VERSION) return; - const asset = iiiReleaseAsset(); - const downloadHint = asset - ? `curl -fsSL https://github.com/iii-hq/iii/releases/download/iii/v${IIPINNED_VERSION}/${asset} | tar -xz -C ~/.local/bin` - : `download v${IIPINNED_VERSION} from https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`; - p.log.error( - `iii-engine on PATH is v${detected} but AI-Todo v${VERSION} hard-pins v${IIPINNED_VERSION}. ` + - `Engine API drift causes runtime failures (e.g. state::list-not-found on v0.13.0). ` + - `Downgrade with: \`${downloadHint}\`. ` + - `Or set AGENTMEMORY_III_VERSION=${detected} to override at your own risk.`, - ); - process.exit(1); -} - -function enginePidfilePath(): string { - return join(getAgentMemoryDataDir(), "iii.pid"); -} - -function engineStatePath(): string { - return join(getAgentMemoryDataDir(), "engine-state.json"); -} - -type EngineState = - | { kind: "native"; configPath: string; attached?: boolean } - | { kind: "docker"; composeFile: string }; - -function writeEnginePidfile(pid: number): void { - try { - const pidPath = enginePidfilePath(); - mkdirSync(dirname(pidPath), { recursive: true }); - writeFileSync(pidPath, `${pid}\n`, { encoding: "utf-8" }); - } catch (err) { - vlog(`writeEnginePidfile: ${err instanceof Error ? err.message : String(err)}`); - } -} - -function readEnginePidfile(): number | null { - try { - const pidStr = readFileSync(enginePidfilePath(), "utf-8").trim(); - const pid = parseInt(pidStr, 10); - return Number.isFinite(pid) && pid > 0 ? pid : null; - } catch { - return null; - } -} - -function clearEnginePidfile(): void { - try { - unlinkSync(enginePidfilePath()); - } catch {} -} - -// Worker pidfile (#640, #474): the agentmemory worker process -// (`node dist/index.mjs`) is spawned by iii-exec inside the engine. When -// `agentmemory-lab stop` kills only the engine pid, the worker can survive -// (detached spawn, signal not propagated, or kept alive by a wrapper -// script). On the next start, the orphaned worker reconnects to the new -// engine and shows up as a duplicate registration. We write the worker -// pid from src/index.ts on boot so stop can find and reap it. -function workerPidfilePath(): string { - return join(getAgentMemoryDataDir(), "worker.pid"); -} - -function readWorkerPidfile(): number | null { - try { - const pidStr = readFileSync(workerPidfilePath(), "utf-8").trim(); - const pid = parseInt(pidStr, 10); - return Number.isFinite(pid) && pid > 0 ? pid : null; - } catch { - return null; - } -} - -function clearWorkerPidfile(): void { - try { - unlinkSync(workerPidfilePath()); - } catch {} -} - -function writeEngineState(state: EngineState): void { - try { - const statePath = engineStatePath(); - mkdirSync(dirname(statePath), { recursive: true }); - writeFileSync(statePath, `${JSON.stringify(state)}\n`, { encoding: "utf-8" }); - } catch (err) { - vlog(`writeEngineState: ${err instanceof Error ? err.message : String(err)}`); - } -} - -function readEngineState(): EngineState | null { - try { - const raw = readFileSync(engineStatePath(), "utf-8"); - const parsed = JSON.parse(raw) as Partial; - if (parsed && (parsed.kind === "native" || parsed.kind === "docker")) { - return parsed as EngineState; - } - return null; - } catch { - return null; - } -} - -function clearEngineState(): void { - try { - unlinkSync(engineStatePath()); - } catch {} -} - -function discoverComposeFile(): string | null { - const candidates = [ - join(__dirname, "..", "docker-compose.yml"), - join(__dirname, "docker-compose.yml"), - join(process.cwd(), "docker-compose.yml"), - ]; - return candidates.find((c) => existsSync(c)) ?? null; -} - -function isInvokedViaNpx(): boolean { - if (process.env["npm_lifecycle_event"] === "npx") return true; - const argv1 = process.argv[1] ?? ""; - if (argv1.includes("_npx")) return true; - const ua = process.env["npm_config_user_agent"] ?? ""; - if (ua.startsWith("npm/") || ua.includes(" npm/")) return true; - return false; -} - -// First-run global-install prompt. Replaces the previous passive -// `p.log.info` hint that users ignored — typing `agentmemory-lab stop` -// in a new shell would then 404 with `command not found`. We now -// ask once, persist the answer in preferences, and never ask again. -async function maybeOfferGlobalInstall(): Promise { - return; -} - -// iii-console install state. -// "installed" — `iii-console` is on PATH or at `~/.local/bin/iii-console` -// "missing" — binary not found anywhere we look -// We deliberately do NOT probe the console's HTTP port: the binary -// being on disk is the signal we care about (it's not auto-started by -// agentmemory and its default port 3113 collides with our viewer, so -// "is it listening?" is the wrong question at boot time). -type IiiConsoleState = - | { kind: "installed"; binPath: string } - | { kind: "missing" }; - -function detectIiiConsole(): IiiConsoleState { - const onPath = whichBinary("iii-console"); - if (onPath) return { kind: "installed", binPath: onPath }; - const fallback = IS_WINDOWS - ? join(process.env["USERPROFILE"] ?? "", ".local", "bin", "iii-console.exe") - : join(homedir(), ".local", "bin", "iii-console"); - if (fallback && existsSync(fallback)) { - return { kind: "installed", binPath: fallback }; - } - return { kind: "missing" }; -} - -const III_CONSOLE_INSTALL_CMD = - "curl -fsSL https://install.iii.dev/iii/main/install.sh | sh"; - -async function ensureIiiConsole(): Promise { - const state = detectIiiConsole(); - if (state.kind === "installed") return state; - - // Non-interactive contexts get the panel hint but no prompt. - if (!process.stdin.isTTY || process.env["CI"]) return state; - const prefs = readPrefs(); - if (prefs.skipConsoleInstall) return state; - - const answer = await p.confirm({ - message: - "iii console gives engine-level visibility (workers, functions, queues, traces). Install now?", - initialValue: true, - }); - if (p.isCancel(answer)) return state; - if (answer === false) { - writePrefs({ skipConsoleInstall: true }); - return state; - } - - const shBin = whichBinary("sh"); - const curlBin = whichBinary("curl"); - if (!shBin || !curlBin) { - p.log.warn( - `curl or sh not found. Install manually:\n ${III_CONSOLE_INSTALL_CMD}`, - ); - return state; - } - const ok = runCommand(shBin, ["-c", III_CONSOLE_INSTALL_CMD], { - label: "Installing iii console", - }); - if (!ok) { - p.log.warn( - `iii console install failed. Re-run manually:\n ${III_CONSOLE_INSTALL_CMD}`, - ); - return state; - } - // Re-detect rather than trust install-script output paths. - return detectIiiConsole(); -} - -function adoptRunningEngine(): void { - try { - const existingState = readEngineState(); - const existingPid = readEnginePidfile(); - if (existingState && existingPid) return; - - const pids = findEnginePidsByPort(getRestPort()); - const enginePid = pids[0]; - if (enginePid && !existingPid) { - writeEnginePidfile(enginePid); - } - if (!existingState) { - writeEngineState({ - kind: "native", - configPath: findIiiConfig() || "", - attached: true, - }); - } - if (enginePid && !existingPid) { - p.log.info(`Attached to existing iii-engine (pid ${enginePid})`); - } - } catch (err) { - vlog(`adoptRunningEngine: ${err instanceof Error ? err.message : String(err)}`); - } -} - -async function runIiiInstaller(): Promise<{ ok: boolean; binPath: string | null }> { - const releaseUrl = iiiReleaseUrl(); - const asset = iiiReleaseAsset(); - const isZipAsset = asset?.endsWith(".zip") === true; - - if (!releaseUrl) { - p.log.warn( - `iii-engine binary not available for ${platform()}/${process.arch}. Use Docker (\`docker pull iiidev/iii:${IIPINNED_VERSION}\`) or download manually from https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}.`, - ); - return { ok: false, binPath: null }; - } - - if (IS_WINDOWS || isZipAsset) { - p.log.info( - `Auto-install unavailable on ${platform()} — ${asset} isn't tar-compatible. Install manually:\n` + - ` 1. Download ${releaseUrl}\n` + - ` 2. Extract iii.exe and place it on PATH (e.g. %USERPROFILE%\\.local\\bin)\n` + - `Or use Docker: docker pull iiidev/iii:${IIPINNED_VERSION}`, - ); - return { ok: false, binPath: null }; - } - - const shBin = whichBinary("sh"); - const curlBin = whichBinary("curl"); - if (!shBin || !curlBin) { - p.log.warn("curl or sh not found. Cannot auto-install iii-engine."); - return { ok: false, binPath: null }; - } - - const binDir = join(homedir(), ".local", "bin"); - const binPath = join(binDir, "iii"); - const installCmd = [ - `mkdir -p "${binDir}"`, - `curl -fsSL "${releaseUrl}" | tar -xz -C "${binDir}"`, - `chmod +x "${binPath}"`, - ].join(" && "); - const installerOk = runCommand(shBin, ["-c", installCmd], { - label: `Installing iii-engine v${IIPINNED_VERSION} (pinned)`, - optional: true, - }); - if (!installerOk) { - p.log.warn( - `iii-engine installer failed. Fallbacks: Docker (\`docker pull iiidev/iii:${IIPINNED_VERSION}\`) or download manually from https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}.`, - ); - return { ok: false, binPath: null }; - } - return { ok: true, binPath }; -} - -type StartupFailure = { - kind: "no-engine" | "no-docker-compose" | "engine-crashed" | "docker-crashed"; - stderr?: string; - binary?: string; -}; - -let startupFailure: StartupFailure | null = null; - -// Spawn a background engine and collect any startup stderr for a short -// window. The process is unref'd so the CLI parent can exit cleanly; we -// only care about stderr that shows up BEFORE the health check succeeds, -// which is what surfaces early crash/config-parse errors on all platforms. -function spawnEngineBackground( - bin: string, - spawnArgs: string[], - label: string, -): ChildProcess { - vlog(`spawn: ${bin} ${spawnArgs.join(" ")}`); - const child = spawn(bin, spawnArgs, { - detached: true, - stdio: ["ignore", "ignore", "pipe"], - windowsHide: true, - }); - const isDocker = label.includes("Docker"); - if (!isDocker && typeof child.pid === "number") { - writeEnginePidfile(child.pid); - } - const stderrChunks: Buffer[] = []; - let stderrBytes = 0; - const MAX_STDERR_CAPTURE = 16 * 1024; - child.stderr?.on("data", (chunk: Buffer) => { - if (stderrBytes >= MAX_STDERR_CAPTURE) return; - const slice = chunk.subarray(0, MAX_STDERR_CAPTURE - stderrBytes); - stderrChunks.push(slice); - stderrBytes += slice.length; - }); - child.on("exit", (code, signal) => { - const abnormal = - (code !== null && code !== 0) || (code === null && signal !== null); - if (abnormal) { - const stderr = Buffer.concat(stderrChunks).toString("utf-8"); - startupFailure = { - kind: isDocker ? "docker-crashed" : "engine-crashed", - stderr: - stderr.trim() || - (signal - ? `process killed by signal ${signal}` - : `process exited with code ${code}`), - binary: bin, - }; - vlog(`engine exited early: code=${code} signal=${signal}`); - if (IS_VERBOSE && stderr.trim()) { - p.log.error(`engine stderr:\n${stderr}`); - } - if (!isDocker) clearEnginePidfile(); - clearEngineState(); - } - }); - child.unref(); - return child; -} - -function startIiiBin(iiiBin: string, configPath: string): boolean { - enforceEngineVersionPin(iiiBin); - const s = p.spinner(); - s.start(`Starting iii-engine: ${iiiBin}`); - writeEngineState({ kind: "native", configPath }); - spawnEngineBackground(iiiBin, ["--no-update-check", "--config", configPath], "iii-engine"); - s.stop("iii-engine process started"); - return true; -} - -async function startEngine(): Promise { - const configPath = findIiiConfig(); - let iiiBin = whichBinary("iii"); - vlog(`iii binary: ${iiiBin ?? "(not on PATH)"}, config: ${configPath || "(not found)"}`); - - if (iiiBin && configPath) return startIiiBin(iiiBin, configPath); - - for (const iiiPath of fallbackIiiPaths()) { - if (existsSync(iiiPath)) { - const v = iiiBinVersion(iiiPath); - vlog(`fallback iii at ${iiiPath} reports version: ${v ?? "unknown"}`); - p.log.info(`Found iii at: ${iiiPath}${v ? ` (v${v})` : ""}`); - process.env["PATH"] = `${dirname(iiiPath)}${PATH_DELIMITER}${process.env["PATH"] ?? ""}`; - iiiBin = iiiPath; - break; - } - } - - if (iiiBin && configPath) return startIiiBin(iiiBin, configPath); - - if (!configPath) { - startupFailure = { kind: "no-engine" }; - return false; - } - - const dockerBin = whichBinary("docker"); - vlog(`docker binary: ${dockerBin ?? "(not on PATH)"}`); - const dockerComposeCandidates = [ - join(__dirname, "..", "docker-compose.yml"), - join(__dirname, "docker-compose.yml"), - join(process.cwd(), "docker-compose.yml"), - ]; - const composeFile = dockerComposeCandidates.find((c) => existsSync(c)); - vlog(`docker-compose.yml: ${composeFile ?? "(not found)"}`); - - const dockerOptIn = - process.env["AGENTMEMORY_USE_DOCKER"] === "1" || - process.env["AGENTMEMORY_USE_DOCKER"] === "true"; - const interactive = !!process.stdin.isTTY && !process.env["CI"]; - - type Choice = "install" | "docker" | "manual"; - let choice: Choice; - - if (dockerOptIn && dockerBin && composeFile) { - choice = "docker"; - } else if (!interactive) { - choice = "install"; - p.log.info("Non-interactive environment detected — auto-installing iii-engine."); - } else { - p.log.warn(`iii-engine binary not found locally.`); - const options: { value: Choice; label: string; hint?: string }[] = [ - { - value: "install", - label: `Install iii v${IIPINNED_VERSION} to ~/.local/bin (~6MB, ~5s)`, - hint: "recommended", - }, - ]; - if (dockerBin && composeFile) { - options.push({ value: "docker", label: "Use Docker compose", hint: "advanced · self-host" }); - } - options.push({ value: "manual", label: "Show manual install steps and exit" }); - - const picked = await p.select({ - message: "How would you like to start iii-engine?", - options, - initialValue: "install", - }); - if (p.isCancel(picked)) { - startupFailure = { kind: "no-engine" }; - return false; - } - choice = picked; - } - - if (choice === "manual") { - startupFailure = { kind: "no-engine" }; - return false; - } - - if (choice === "install") { - const result = await runIiiInstaller(); - if (result.ok && result.binPath) { - process.env["PATH"] = `${dirname(result.binPath)}${PATH_DELIMITER}${process.env["PATH"] ?? ""}`; - iiiBin = result.binPath; - return startIiiBin(iiiBin, configPath); - } - if (dockerBin && composeFile && interactive) { - const fallback = await p.confirm({ - message: "Auto-install failed. Try Docker compose instead?", - initialValue: true, - }); - if (p.isCancel(fallback) || fallback !== true) { - startupFailure = { kind: "no-engine" }; - return false; - } - choice = "docker"; - } else { - startupFailure = { kind: "no-engine" }; - return false; - } - } - - if (choice === "docker" && dockerBin && composeFile) { - const s = p.spinner(); - s.start("Starting iii-engine via Docker..."); - writeEngineState({ kind: "docker", composeFile }); - spawnEngineBackground( - dockerBin, - ["compose", "-f", composeFile, "up", "-d"], - "iii-engine via Docker", - ); - s.stop("Docker compose started"); - return true; - } - - if (!composeFile && dockerBin) { - startupFailure = { kind: "no-docker-compose" }; - } else { - startupFailure = { kind: "no-engine" }; - } - return false; -} - -async function waitForEngine(timeoutMs: number): Promise { - const start = Date.now(); - while (Date.now() - start < timeoutMs) { - if (await isEngineRunning()) return true; - // Fail fast: the engine process already exited (e.g. its port is in use), - // so there's nothing left to wait for — let the caller surface the - // captured stderr instead of polling the full timeout. - if (startupFailure?.kind === "engine-crashed") return false; - await new Promise((r) => setTimeout(r, 500)); - } - return false; -} - -function installInstructions(): string[] { - const releaseUrl = iiiReleaseUrl(); - if (IS_WINDOWS) { - return [ - `agentmemory needs iii-engine v${IIPINNED_VERSION}. Pick one:`, - "", - " A) Download the prebuilt Windows binary:", - ` 1. Open https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`, - ` 2. Download iii-x86_64-pc-windows-msvc.zip (or iii-aarch64-pc-windows-msvc.zip on ARM)`, - " 3. Extract iii.exe to %USERPROFILE%\\.local\\bin\\iii.exe (or add to PATH)", - " 4. Re-run: npm run start:local-memory", - "", - ` B) Docker: docker pull iiidev/iii:${IIPINNED_VERSION}`, - " Re-run with AGENTMEMORY_USE_DOCKER=1 npm run start:local-memory", - "", - "Or skip the engine entirely (standalone MCP): node dist/cli.mjs mcp", - "", - "Docs: https://iii.dev/docs", - ]; - } - const linuxInstall = releaseUrl - ? ` A) curl -fsSL "${releaseUrl}" | tar -xz -C ~/.local/bin && chmod +x ~/.local/bin/iii` - : ` A) Manual download: https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`; - return [ - `agentmemory needs iii-engine v${IIPINNED_VERSION}. Pick one:`, - "", - linuxInstall, - " Then re-run: npm run start:local-memory", - "", - ` B) Docker: docker pull iiidev/iii:${IIPINNED_VERSION}`, - " Re-run with AGENTMEMORY_USE_DOCKER=1 npm run start:local-memory", - "", - "Or skip the engine entirely (standalone MCP): node dist/cli.mjs mcp", - "", - "Docs: https://iii.dev/docs", - ]; -} - -function portInUseDiagnostic(port: number): string { - return IS_WINDOWS - ? ` netstat -ano | findstr :${port}` - : ` lsof -i :${port} # or: ss -tlnp | grep :${port}`; -} - -function workerRecoveryNote(port: number): string { - return [ - `Port ${port} is occupied, but the Agent Memory API is not answering /agentmemory/livez.`, - "This usually means an iii-engine process is still bound to the port after the worker exited.", - "", - "Recover with (from your AI Todo / MaimoryLab repo checkout):", - " node dist/cli.mjs stop --force", - " npm run start:local-memory", - "", - "Do not start this product with the upstream npm package — use your MaimoryLab repo checkout.", - "", - "Inspect the occupied port:", - portInUseDiagnostic(port), - ].join("\n"); -} - -async function waitForAgentmemoryReady(timeoutMs: number): Promise { - const start = Date.now(); - while (Date.now() - start < timeoutMs) { - if (await isAgentmemoryReady()) return true; - await new Promise((r) => setTimeout(r, 250)); - } - return false; -} - -// Derive a host string for the streams/engine WebSocket lines from -// the configured engine URL (`III_ENGINE_URL`) or REST base -// (`AGENTMEMORY_URL`) so a remote-bind setup like -// `III_ENGINE_URL=ws://my-host:49134` doesn't print misleading -// localhost addresses. Falls back to localhost. -function getEngineHost(): string { - for (const envKey of ["III_ENGINE_URL", "AGENTMEMORY_URL"]) { - const raw = process.env[envKey]; - if (!raw) continue; - try { - const parsed = new URL(raw); - if (parsed.hostname) return parsed.hostname; - } catch {} - } - return "localhost"; -} - -function printReadyHint(consoleState: IiiConsoleState): void { - // REST goes through getBaseUrl which already honors AGENTMEMORY_URL - // for full host+protocol overrides. Streams/Engine are derived from - // III_ENGINE_URL so a remote bind reads correctly in the panel. - const restUrl = getBaseUrl(); - const viewerUrl = getViewerUrl(); - const engineHost = getEngineHost(); - const streamUrl = `ws://${engineHost}:${getStreamPort()}`; - const engineUrl = `ws://${engineHost}:${getEnginePort()}`; - - const consoleLine = - consoleState.kind === "installed" - ? // We surface the binary location and let the user start it - // on a port of their choice. - // and let the user start it on a port of their choice. Use - // the detected binary path so `(run: ...)` is executable as- - // is, even when the binary isn't on PATH under the bare - // name `iii-console`. - `iii console ${consoleState.binPath} (run: ${consoleState.binPath} -p )` - : `iii console (install: ${III_CONSOLE_INSTALL_CMD})`; - - const lines = [ - `REST API ${restUrl}`, - `Viewer ${viewerUrl}`, - `Streams ${streamUrl}`, - `Engine ${engineUrl}`, - consoleLine, - ]; - // p.note renders a bordered panel with a title — same affordance - // used elsewhere in this CLI for "Troubleshooting" / "Setup - // required" blocks, so the visual language stays consistent. - p.note(lines.join("\n"), `AI-Todo v${VERSION}`); - - process.stdout.write("\nTry: node dist/cli.mjs demo\n"); - process.stdout.write("Press Ctrl+C to stop.\n"); -} - -function printNotReadyHint(): void { - p.log.warn(`The worker did not report ready within 15s at ${getBaseUrl()}.`); - p.note( - [ - "The engine may still be warming up, or the worker failed to bind to the port.", - "Check it / diagnose, or re-run with the boot log:", - " node dist/cli.mjs status", - " node dist/cli.mjs doctor", - " npm run start:local-memory -- --verbose", - ].join("\n"), - "Worker not ready", - ); -} - -// Option A worker model: the agentmemory worker runs *inside* iii-engine — the -// `iii-exec` block in iii-config*.yaml spawns `node dist/index.mjs`, detached -// from this CLI's process group. So `start` keeps the CLI in the foreground -// only to own Ctrl+C: on SIGINT/SIGTERM we run the same teardown as -// `agentmemory-lab stop` (engine + worker), then exit. Without this the CLI -// would return immediately and Ctrl+C would have nothing to stop. -async function runForegroundUntilSignal(): Promise { - await new Promise(() => { - let stopping = false; - const onSignal = () => { - if (stopping) return; - stopping = true; - void (async () => { - try { - await runStop(); - } catch (err) { - vlog(`stop on signal failed: ${err instanceof Error ? err.message : String(err)}`); - } - process.exit(0); - })(); - }; - process.on("SIGINT", onSignal); - process.on("SIGTERM", onSignal); - }); -} - -async function main() { - // `--reset` wipes preferences before anything else so the onboarding - // flow below always runs fresh. - if (IS_RESET) { - resetPrefs(); - } - - const firstRun = isFirstRun(); - const prefs = readPrefs(); - // Show the splash on the first run, on --reset, or whenever the user - // hasn't yet opted out via the schema (we set `skipSplash: true` - // after onboarding completes). Verbose runs always splash since the - // user explicitly asked for the chatty experience. - if (firstRun || IS_RESET || IS_VERBOSE || !prefs.skipSplash) { - renderSplash(VERSION); - } - - if (firstRun || IS_RESET) { - await runOnboarding(); - } - - if (skipEngine) { - if (IS_VERBOSE) p.log.info("Skipping engine check (--no-engine)"); - await import("./index.js"); - if (await waitForAgentmemoryReady(15000)) { - const consoleState = await ensureIiiConsole(); - await maybeOfferGlobalInstall(); - printReadyHint(consoleState); - } else { - printNotReadyHint(); - } - return; - } - - if (await isEngineRunning()) { - if (IS_VERBOSE) p.log.success("iii-engine is running"); - const attachedBin = - whichBinary("iii") ?? fallbackIiiPaths().find((p) => existsSync(p)) ?? null; - enforceEngineVersionPin(attachedBin); - adoptRunningEngine(); - // The worker runs inside iii-engine (iii-exec spawns `node dist/index.mjs`), - // so we attach instead of starting a second in-process worker. - if (await waitForAgentmemoryReady(15000)) { - const consoleState = await ensureIiiConsole(); - await maybeOfferGlobalInstall(); - printReadyHint(consoleState); - await runForegroundUntilSignal(); - } else { - printNotReadyHint(); - } - return; - } - - const started = await startEngine(); - if (!started) { - p.log.error("Could not start iii-engine."); - const lines = installInstructions(); - if (startupFailure?.kind === "no-docker-compose") { - lines.unshift( - "Docker is installed but docker-compose.yml is missing from this", - "install. Re-install with: npm install -g @maimorylab/agentmemory-lab", - "", - ); - } - p.note(lines.join("\n"), "Setup required"); - process.exit(1); - } - - const s = p.spinner(); - s.start("Waiting for iii-engine to be ready..."); - - const ready = await waitForEngine(15000); - if (!ready) { - const port = getRestPort(); - s.stop( - startupFailure?.kind === "engine-crashed" - ? "iii-engine exited on startup" - : "iii-engine did not become ready within 15s", - ); - - if (startupFailure?.kind === "engine-crashed" || startupFailure?.kind === "docker-crashed") { - p.log.error("The iii-engine process crashed on startup."); - if (startupFailure.binary) { - p.log.info(`Binary: ${startupFailure.binary}`); - } - if (startupFailure.stderr) { - p.note(startupFailure.stderr, "engine stderr"); - } else { - p.log.info("No stderr was captured. Re-run with --verbose for more detail."); - } - const portInUse = - !!startupFailure.stderr && - /address.*in use|already in use|EADDRINUSE/i.test(startupFailure.stderr); - p.note( - portInUse - ? [ - "The engine couldn't bind its port — it's already in use (see stderr above).", - "", - "`start:local-memory` always binds the engine to the port in", - "iii-config.local-memory.yaml (default 3111). `--port` only changes which", - "port this CLI queries, not the engine's bind port — so it can't free a", - "busy port. To recover:", - " - Stop a previous instance: node dist/cli.mjs stop", - " - Or free the port: lsof -i :3111 (then kill the listed PID)", - ].join("\n") - : [ - "Common causes:", - " - iii-engine version mismatch — reinstall the latest binary", - " (sh script on macOS/Linux, GitHub release zip on Windows)", - " - Docker Desktop not running (if you're using the Docker path)", - " - Port already in use (see below)", - "", - "See https://iii.dev/docs for current install instructions.", - ].join("\n"), - "Troubleshooting", - ); - } else { - p.log.error("The engine process started but the REST API never responded."); - p.note( - [ - `Check whether port ${port} is already bound by another process:`, - portInUseDiagnostic(port), - "", - "If it is, free that port. (`--port` changes only the port this CLI", - "queries, not the engine's bind port — the engine uses iii-config.local-memory.yaml.)", - "", - "If it isn't, a firewall may be blocking 127.0.0.1:" + port + ".", - "Re-run with --verbose to see engine stderr.", - ].join("\n"), - "Troubleshooting", - ); - } - process.exit(1); - } - - s.stop("iii-engine is ready"); - // The worker runs inside iii-engine (iii-exec spawns `node dist/index.mjs`), - // detached from this CLI — we do NOT start a second in-process worker here. - const workerReady = await waitForAgentmemoryReady(15000); - if (workerReady) { - const consoleState = await ensureIiiConsole(); - await maybeOfferGlobalInstall(); - printReadyHint(consoleState); - } else { - printNotReadyHint(); - } - // Mark splash as something to skip on subsequent runs. This is a - // no-op if onboarding already flipped the flag (idempotent merge). - writePrefs({ skipSplash: true }); - // Stay in the foreground so Ctrl+C tears down the whole service (engine + - // worker) instead of orphaning it. Blocks until SIGINT/SIGTERM. - if (workerReady) await runForegroundUntilSignal(); -} - -async function apiFetch(base: string, path: string, timeoutMs = 5000): Promise { - try { - const headers: Record = {}; - const secret = process.env["AGENTMEMORY_SECRET"]; - if (secret) headers["Authorization"] = `Bearer ${secret}`; - const res = await fetch(`${base}/agentmemory/${path}`, { - signal: AbortSignal.timeout(timeoutMs), - headers, - }); - return (await res.json()) as T; - } catch { - return null; - } -} - -async function runStatus() { - const port = getRestPort(); - const base = getBaseUrl(); - p.intro("agentmemory-lab status"); - - const up = await isEngineRunning(); - if (!up) { - p.log.error(`Not running — no response at ${base}`); - p.log.info("Start with: npm run start:local-memory"); - process.exit(1); - } - - try { - const [healthRes, sessionsRes, graphRes, memoriesRes, flagsRes] = await Promise.all([ - apiFetch(base, "health"), - apiFetch(base, "sessions"), - apiFetch(base, "graph/stats"), - apiFetch(base, "memories?count=true"), - apiFetch(base, "config/flags"), - ]); - - if (typeof healthRes?.viewerPort === "number") { - discoveredViewerPort = healthRes.viewerPort; - } - const h = healthRes?.health; - const status = healthRes?.status || "unknown"; - const version = healthRes?.version || "?"; - const sessionList = Array.isArray(sessionsRes?.sessions) ? sessionsRes.sessions : []; - const sessions = sessionList.length; - const nodes = Number(graphRes?.totalNodes ?? graphRes?.nodes ?? graphRes?.nodeCount ?? 0); - const edges = Number(graphRes?.totalEdges ?? graphRes?.edges ?? graphRes?.edgeCount ?? 0); - const cb = healthRes?.circuitBreaker?.state || "closed"; - const heapMB = h?.memory ? Math.round(h.memory.heapUsed / 1048576) : 0; - const uptime = h?.uptimeSeconds ? Math.round(h.uptimeSeconds) : 0; - - const obsCount = sessionList.reduce( - (sum: number, s: any) => sum + (Number(s?.observationCount) || 0), - 0, - ); - const memCount = Number(memoriesRes?.latestCount ?? memoriesRes?.total ?? 0) || 0; - const estFullTokens = obsCount * 80; - const estInjectedTokens = Math.min(obsCount, 50) * 38; - const tokensSaved = estFullTokens - estInjectedTokens; - const pctSaved = estFullTokens > 0 ? Math.round((tokensSaved / estFullTokens) * 100) : 0; - - p.log.success(`Connected — v${version} at ${base}`); - - const lines = [ - `Health: ${status === "healthy" ? "✓ healthy" : status}`, - `Sessions: ${sessions}`, - `Observations: ${obsCount}`, - `Memories: ${memCount}`, - `Graph: ${nodes} nodes, ${edges} edges`, - `Circuit: ${cb}`, - `Heap: ${heapMB} MB`, - `Uptime: ${uptime}s`, - `Viewer: ${getViewerUrl()}`, - ]; - - if (obsCount > 0) { - lines.push(""); - lines.push(`Token savings: ~${tokensSaved.toLocaleString()} tokens saved (${pctSaved}% reduction)`); - lines.push(` Full context: ~${estFullTokens.toLocaleString()} tokens`); - lines.push(` Injected: ~${estInjectedTokens.toLocaleString()} tokens`); - } - - if (flagsRes) { - const provider = flagsRes.provider === "llm" ? "✓ llm" : "✗ noop (no key)"; - const embed = flagsRes.embeddingProvider === "embeddings" ? "✓ embeddings" : "bm25-only"; - const flagRows = (flagsRes.flags || []).map((f: { key: string; enabled: boolean; label: string }) => - ` ${f.enabled ? "✓" : "✗"} ${f.key.padEnd(32)} ${f.label}` - ); - lines.push(""); - lines.push(`Provider: ${provider}`); - lines.push(`Embeddings: ${embed}`); - lines.push(`Flags:`); - flagRows.forEach((r: string) => lines.push(r)); - } - - p.note(lines.join("\n"), "agentmemory"); - } catch (err) { - p.log.error(err instanceof Error ? err.message : String(err)); - process.exit(1); - } -} - -type DoctorCheck = { name: string; ok: boolean; hint?: string }; - -function formatChecks(checks: DoctorCheck[]): string { - return checks - .map((c) => `${c.ok ? "✓" : "✗"} ${c.name}${c.hint ? `\n ${c.hint}` : ""}`) - .join("\n"); -} - -type CCHooksCheck = - | { state: "loaded"; manifestPath?: string } - | { state: "not-loaded" } - | { state: "no-debug-log" } - | { state: "no-cc-dir" }; - -function findLatestDebugLog(debugDir: string): string | undefined { - const latestLink = join(debugDir, "latest"); - try { - if (existsSync(latestLink)) { - const target = readlinkSync(latestLink); - const resolved = target.startsWith("/") ? target : join(debugDir, target); - if (existsSync(resolved)) return resolved; - } - } catch {} - - try { - const newest = readdirSync(debugDir) - .filter((f) => f.endsWith(".txt")) - .map((f) => ({ f, m: statSync(join(debugDir, f)).mtimeMs })) - .sort((a, b) => b.m - a.m)[0]; - if (newest) return join(debugDir, newest.f); - } catch {} - - return undefined; -} - -function checkClaudeCodeHooks(): CCHooksCheck { - const debugDir = join(homedir(), ".claude", "debug"); - if (!existsSync(debugDir)) return { state: "no-cc-dir" }; - - const logPath = findLatestDebugLog(debugDir); - if (!logPath) return { state: "no-debug-log" }; - - let content: string; - try { - content = readFileSync(logPath, "utf8"); - } catch { - return { state: "no-debug-log" }; +import { existsSync, mkdirSync } from "node:fs"; +import { createInterface } from "node:readline/promises"; +import { stdin as input, stdout as output } from "node:process"; +import type { Database } from "./db/index.js"; +import { openDatabase } from "./db/index.js"; +import { getAppPaths } from "./paths.js"; +import { runMcpStdio } from "./mcp/stdio.js"; +import { createAppServer, createStartupScanner } from "./server/index.js"; +import { scanSource } from "./sources/scan.js"; +import { defaultEnvConfig, ensureDefaultEnv, type EnvConfig } from "./config.js"; +import { getLlmDoctorStatus, organizeConfiguredTodos } from "./todos/configured.js"; +import { listTodos, updateTodoStatus } from "./todos/service.js"; + +export const DEFAULT_UI_PORT = 3111; + +export async function main(argv = process.argv.slice(2)): Promise { + const command = argv[0] ?? "doctor"; + + if (command === "init") { + return init(argv.slice(1)); + } + + if (command === "doctor") { + const paths = getAppPaths(); + mkdirSync(paths.configDir, { recursive: true }); + mkdirSync(paths.dataDir, { recursive: true }); + openDatabase(paths).close(); + const llm = getLlmDoctorStatus(paths); + console.log(`config: ${paths.configDir}`); + console.log(`env: ${paths.envPath}`); + if (!existsSync(paths.envPath)) console.log("env status: missing; run ai-todo init"); + console.log(`data: ${paths.dataDir}`); + console.log(`llm enabled: ${llm.enabled}`); + console.log(`llm key: ${llm.keyConfigured ? "configured" : "missing"}`); + console.log(`llm model: ${llm.model}`); + console.log(`llm endpoint: ${llm.endpoint}`); + console.log("ok"); + return 0; + } + + if (command === "scan") { + return withDatabase((db) => scan(db, argv[1], argv[2])); + } + + if (command === "organize") { + const paths = getAppPaths(); + return withDatabase(async (db) => { + const result = await organizeConfiguredTodos(db, paths); + console.log(`scanned: ${result.scanned}`); + console.log(`created: ${result.created}`); + console.log(`updated: ${result.updated}`); + console.log(`completed: ${result.completed}`); + console.log(`ignored: ${result.ignored}`); + console.log(`engine: ${result.engine}`); + if (result.warnings.length > 0) console.log(`warnings: ${result.warnings.join(",")}`); + return 0; + }); } - const match = content.match( - /Loaded hooks from standard location for plugin agentmemory:\s*(\S+)/ - ); - if (match) return { state: "loaded", manifestPath: match[1] }; - if (content.includes("Loading hooks from plugin: agentmemory")) return { state: "loaded" }; - return { state: "not-loaded" }; -} - -// --------------------------------------------------------------------------- -// Doctor v2 — interactive fixer. -// -// The legacy passive check-list (server reachable, flags, knowledge-graph, -// Claude Code hooks) still runs first as an informational summary because -// those checks need a live engine and don't have a one-shot inline fix. -// Then we drive the new diagnostic catalog (see src/cli/doctor-diagnostics.ts) -// which prompts Fix/Skip/More/Quit per failing check, applies the fix -// inline, and re-checks only the affected diagnostic. - -function buildDoctorContext(): DoctorContext { - return { - baseUrl: getBaseUrl(), - viewerUrl: getViewerUrl(), - envPath: getUserEnvPath(), - pidfilePath: enginePidfilePath(), - enginePath: engineStatePath(), - pinnedVersion: IIPINNED_VERSION, - }; -} - -function buildDoctorEffects(): DoctorEffects { - return { - envFileExists: () => existsSync(getUserEnvPath()), - readEnvFile: () => { - try { - return parseEnvFile( - readFileSync(getUserEnvPath(), "utf-8"), - ); - } catch { - return {}; - } - }, - pidfileExists: () => existsSync(enginePidfilePath()), - pidfilePidIsAlive: () => { - const pid = readEnginePidfile(); - if (pid === null) return null; - return pidAlive(pid); - }, - findIiiBinary: () => whichBinary("iii"), - localBinIiiPath: () => join(homedir(), ".local", "bin", IS_WINDOWS ? "iii.exe" : "iii"), - iiiBinaryVersion: (binPath: string) => iiiBinVersion(binPath), - viewerReachable: async (timeoutMs = 2000) => { - try { - await discoverViewerPort(); - const res = await fetch(getViewerUrl(), { - signal: AbortSignal.timeout(timeoutMs), - }); - return res.ok; - } catch { - return false; - } - }, - runInit: async () => { - try { - await runInit(); - return { ok: true, message: "Wrote ~/.agentmemory/.env" }; - } catch (err) { - return { - ok: false, - message: err instanceof Error ? err.message : String(err), - }; + if (command === "list") { + return withDatabase((db) => { + const todos = listTodos(db); + if (todos.length === 0) { + console.log("No todos"); + return 0; } - }, - openEditor: async (path: string) => { - const editor = process.env["EDITOR"] || process.env["VISUAL"] || "nano"; - p.log.info(`Opening ${path} in ${editor}…`); - try { - // Inherit stdio so the user actually sees the editor. - const result = spawnSync(editor, [path], { stdio: "inherit" }); - if (result.error) { - return { - ok: false, - message: `Failed to launch ${editor}: ${result.error.message}`, - }; - } - if ((result.status ?? 0) !== 0) { - return { - ok: false, - message: `${editor} exited with code ${result.status}`, - }; - } - return { ok: true, message: `Saved ${path}` }; - } catch (err) { - return { - ok: false, - message: err instanceof Error ? err.message : String(err), - }; + for (const todo of todos) { + console.log(`${todo.id} ${todo.status} ${todo.title}`); } - }, - runIiiInstaller: async () => { - const r = await runIiiInstaller(); - return { - ok: r.ok, - message: r.ok - ? `Installed iii v${IIPINNED_VERSION} to ${r.binPath}` - : "iii installer failed (see warnings above)", - }; - }, - runStop: async () => { - try { - // runStop calls process.exit on its own — guard against that here - // by short-circuiting when there's nothing to stop. - const port = getRestPort(); - const portPids = findEnginePidsByPort(port); - const pidfilePid = readEnginePidfile(); - if (portPids.length === 0 && pidfilePid === null) { - clearEnginePidfile(); - clearEngineState(); - return { ok: true, message: "Nothing to stop." }; - } - const candidates = new Set(); - if (pidfilePid) candidates.add(pidfilePid); - for (const pid of portPids) candidates.add(pid); - let allStopped = true; - for (const pid of candidates) { - const ok = await signalAndWait(pid, "SIGTERM", 3000); - if (!ok) allStopped = false; - } - clearEnginePidfile(); - clearEngineState(); - return { - ok: allStopped, - message: allStopped ? "Engine stopped." : "Some engine pids survived.", - }; - } catch (err) { - return { - ok: false, - message: err instanceof Error ? err.message : String(err), - }; - } - }, - runStart: async () => { - try { - const started = await startEngine(); - if (!started) return { ok: false, message: "startEngine() returned false" }; - const ready = await waitForEngine(15000); - return { - ok: ready, - message: ready ? "Engine ready" : "Engine did not become ready within 15s", - }; - } catch (err) { - return { - ok: false, - message: err instanceof Error ? err.message : String(err), - }; - } - }, - clearEnginePidAndState: () => { - clearEnginePidfile(); - clearEngineState(); - }, - }; -} - -async function passiveServerChecks(): Promise { - const base = getBaseUrl(); - const checks: DoctorCheck[] = []; - - const serverUp = await isEngineRunning(); - checks.push({ - name: "Server reachable", - ok: serverUp, - hint: serverUp - ? undefined - : `Start with: npm run start:local-memory (tried ${base})`, - }); - if (!serverUp) return checks; - - const [health, flags, graph] = await Promise.all([ - apiFetch(base, "health", 3000), - apiFetch(base, "config/flags", 3000), - apiFetch(base, "graph/stats", 3000), - ]); - - const hasLlm = flags?.provider === "llm"; - const hasEmbed = flags?.embeddingProvider === "embeddings"; - const graphNodeCount = Number( - graph?.totalNodes ?? graph?.nodes ?? graph?.nodeCount ?? 0, - ); - const graphHas = graphNodeCount > 0; - - checks.push( - { - name: "Health status", - ok: health?.status === "healthy", - hint: - health?.status === "healthy" - ? undefined - : `Status: ${health?.status || "unknown"}`, - }, - { - name: "LLM provider", - ok: hasLlm, - hint: hasLlm ? undefined : "set ANTHROPIC_API_KEY (or GEMINI/OPENROUTER/MINIMAX) in ~/.agentmemory/.env", - }, - { - name: "Embedding provider", - ok: hasEmbed, - hint: hasEmbed - ? undefined - : "Running BM25-only. Add OPENAI_API_KEY / VOYAGE_API_KEY / COHERE_API_KEY / OLLAMA_HOST", - }, - ); - - for (const f of (flags?.flags || []) as { - label: string; - enabled: boolean; - enableHow: string; - }[]) { - checks.push({ - name: f.label, - ok: f.enabled, - hint: f.enabled ? undefined : f.enableHow, + return 0; }); } - const cc = checkClaudeCodeHooks(); - const ccCheck = (() => { - switch (cc.state) { - case "loaded": - return { - ok: true, - hint: cc.manifestPath ? `manifest: ${cc.manifestPath}` : undefined, - }; - case "not-loaded": - return { - ok: false, - hint: - "Plugin enabled but hooks not loaded by Claude Code. Reinstall the local MaimoryLab plugin, then restart the session.", - }; - case "no-debug-log": - return { - ok: false, - hint: - 'Cannot verify — no Claude Code debug log found. Run once with `claude --debug -p "x"`, then re-run doctor.', - }; - case "no-cc-dir": - return undefined; - } - })(); - if (ccCheck) checks.push({ name: "Claude Code plugin hooks registered", ...ccCheck }); - - checks.push({ - name: "Knowledge graph populated", - ok: graphHas, - hint: graphHas - ? undefined - : "Graph is empty. Run a session with GRAPH_EXTRACTION_ENABLED=true.", - }); - - return checks; -} - -type DoctorAction = "fix" | "skip" | "more" | "quit"; - -async function askFixAction(d: Diagnostic): Promise { - const choice = await p.select({ - message: `[${d.id}] ${d.message}`, - options: [ - { value: "fix", label: "F Fix", hint: d.fixPreview }, - { value: "skip", label: "S Skip" }, - { value: "more", label: "? More info" }, - { value: "quit", label: "Q Quit doctor" }, - ], - initialValue: "fix", - }); - if (p.isCancel(choice)) return "quit"; - return choice; -} - -async function applyFixWithReport( - d: Diagnostic, - ctx: DoctorContext, - dryRun: boolean, -): Promise { - if (dryRun) { - p.log.info(`[dry-run] would: ${d.fixPreview}`); - return { ok: true, message: "(dry-run)" }; - } - const result = await d.fix(ctx); - if (result.ok) { - p.log.success(result.message ?? `${d.id} fixed.`); - } else { - p.log.error(result.message ?? `${d.id} fix failed.`); - } - return result; -} - -async function runDoctor() { - p.intro("agentmemory-lab doctor"); - const applyAll = args.includes("--all"); - const dryRun = args.includes("--dry-run"); - if (applyAll && dryRun) { - p.log.error("Cannot combine --all and --dry-run."); - process.exit(2); + if (command === "done" || command === "ignore") { + return withDatabase((db) => updateStatus(db, argv[1], command === "done" ? "done" : "ignored")); } - // Passive server checks (informational). - const passive = await passiveServerChecks(); - const passivePassed = passive.filter((c) => c.ok).length; - p.note(formatChecks(passive), `server: ${passivePassed}/${passive.length} passing`); - - // Doctor v2 interactive catalog. - const ctx = buildDoctorContext(); - const effects = buildDoctorEffects(); - const diagnostics = buildDiagnostics(effects); - - if (dryRun) { - const results: Array<{ diagnostic: Diagnostic; status: { ok: boolean; detail?: string } }> = []; - for (const d of diagnostics) results.push({ diagnostic: d, status: await d.check(ctx) }); - const lines = dryRunPlan(ctx, results); - p.note(lines.join("\n"), "dry-run plan"); - p.outro("Dry-run complete. Re-run without --dry-run to apply."); - return; + if (command === "open") { + return openUi(argv.slice(1)); } - let failed = 0; - let fixed = 0; - let skipped = 0; - let quit = false; - - for (const d of diagnostics) { - if (quit) { - skipped++; - continue; - } - const status = await d.check(ctx); - if (status.ok) { - p.log.success(`${d.id} ✓${status.detail ? ` (${status.detail})` : ""}`); - continue; - } - failed++; - p.log.warn(`${d.id} ✗ ${status.detail ?? ""}`.trim()); - p.log.info(`why: ${d.fixPreview}`); - - if (d.manualOnly) { - p.log.info(`(manual fix only — see "${d.id}" docs)`); - } - - if (applyAll) { - const r = await applyFixWithReport(d, ctx, false); - if (r.ok) fixed++; - // Re-check only this diagnostic. - const after = await d.check(ctx); - if (!after.ok) p.log.warn(`${d.id} still failing after fix.`); - continue; - } - - // Interactive prompt loop — allow [?] More info without leaving the check. - while (true) { - const action = await askFixAction(d); - if (action === "fix") { - const r = await applyFixWithReport(d, ctx, false); - if (r.ok) { - const after = await d.check(ctx); - if (after.ok) { - fixed++; - } else { - p.log.warn(`${d.id} still failing after fix: ${after.detail ?? ""}`); - } - } - break; - } - if (action === "skip") { - skipped++; - break; - } - if (action === "more") { - p.note(d.moreInfo, `[${d.id}] more info`); - continue; - } - if (action === "quit") { - quit = true; - break; - } - } + if (command === "mcp") { + await runMcpStdio(); + return 0; } - const summary = `${diagnostics.length} checks · ${failed} failing · ${fixed} fixed · ${skipped} skipped`; - if (quit) { - p.outro(`Quit early. ${summary}`); - process.exit(1); - } - if (failed === 0) { - p.outro("All diagnostics passing. agentmemory is healthy."); - return; - } - if (failed - fixed === 0) { - p.outro(`All fixes applied. ${summary}`); - return; - } - p.outro(summary); - process.exit(1); + console.error(`unknown command: ${command}`); + return 1; } -type DemoObservation = { - toolName: string; - toolInput: Record; - toolOutput: string; -}; - -type DemoSession = { - id: string; - title: string; - observations: DemoObservation[]; -}; - -type SearchResult = { query: string; hits: number; topTitle: string }; - -function buildDemoSessions(): DemoSession[] { - return [ - { - id: generateId("demo"), - title: "Session 1: JWT auth setup", - observations: [ - { - toolName: "Write", - toolInput: { file_path: "src/middleware/auth.ts" }, - toolOutput: - "Created JWT middleware using jose library. Tokens expire after 30 days. Chose jose over jsonwebtoken for Edge compatibility.", - }, - { - toolName: "Write", - toolInput: { file_path: "test/auth.test.ts" }, - toolOutput: - "Added token validation tests covering expired, malformed, and valid cases.", - }, - { - toolName: "Bash", - toolInput: { command: "npm test" }, - toolOutput: "All 12 auth tests passing.", - }, - ], - }, - { - id: generateId("demo"), - title: "Session 2: Database migration debugging", - observations: [ - { - toolName: "Read", - toolInput: { file_path: "prisma/schema.prisma" }, - toolOutput: - "Found N+1 query issue in user relations. Need to add include on posts query.", - }, - { - toolName: "Edit", - toolInput: { file_path: "src/api/users.ts" }, - toolOutput: - "Fixed N+1 by adding Prisma include. Query time dropped from 450ms to 28ms.", - }, - ], - }, - { - id: generateId("demo"), - title: "Session 3: Rate limiting", - observations: [ - { - toolName: "Write", - toolInput: { file_path: "src/middleware/ratelimit.ts" }, - toolOutput: - "Added rate limiting middleware with 100 req/min default. Uses in-memory store for dev, Redis for prod.", - }, - ], - }, - ]; -} +async function init(argv: string[]): Promise { + const paths = getAppPaths(); + const args = parseOptions(argv); + let env: EnvConfig = { + AI_TODO_LLM_ENABLED: args["llm-enabled"], + AI_TODO_LLM_PROVIDER: args.provider, + AI_TODO_LLM_API_KEY: args["api-key"], + AI_TODO_LLM_MODEL: args.model, + AI_TODO_LLM_ENDPOINT: args.endpoint, + AI_TODO_CODEX_HOME: args["codex-home"], + AI_TODO_CLAUDE_HOME: args["claude-home"], + AI_TODO_ORGANIZE_SINCE_DAYS: args["since-days"], + AI_TODO_ORGANIZE_MAX_INTERACTIONS_PER_SESSION: args["max-interactions"], + AI_TODO_ORGANIZE_MAX_SESSIONS: args["max-sessions"], + AI_TODO_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION: args["max-observations"] + }; -async function postJson( - url: string, - body: unknown, - timeoutMs = 5000, -): Promise { - try { - const res = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(body), - signal: AbortSignal.timeout(timeoutMs), - }); - if (!res.ok) return null; - return (await res.json().catch(() => null)) as T | null; - } catch { - return null; + if (process.stdin.isTTY && Object.keys(args).length === 0) { + env = await promptInit(env); } -} -async function postJsonStrict( - url: string, - body: unknown, - timeoutMs = 5000, -): Promise { - const res = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(body), - signal: AbortSignal.timeout(timeoutMs), - }); - if (!res.ok) { - const errBody = await res.text().catch(() => ""); - const suffix = errBody ? ` — ${errBody.slice(0, 200)}` : ""; - throw new Error(`POST ${url} failed: ${res.status} ${res.statusText}${suffix}`); - } - return (await res.json().catch(() => null)) as T | null; + ensureDefaultEnv(paths, env); + mkdirSync(paths.dataDir, { recursive: true }); + openDatabase(paths).close(); + console.log(`env: ${paths.envPath}`); + console.log("initialized"); + return 0; } -async function seedDemoSession( - base: string, - project: string, - session: DemoSession, -): Promise { - await postJsonStrict(`${base}/agentmemory/session/start`, { - sessionId: session.id, - project, - cwd: project, - }); - - let stored = 0; - for (const obs of session.observations) { - const url = `${base}/agentmemory/observe`; - const payload = { - hookType: "post_tool_use", - sessionId: session.id, - project, - cwd: project, - timestamp: new Date().toISOString(), - data: { - tool_name: obs.toolName, - tool_input: obs.toolInput, - tool_output: obs.toolOutput, - }, +async function promptInit(defaults: EnvConfig): Promise { + const rl = createInterface({ input, output }); + const env = { ...defaultEnvConfig(), ...defaults }; + try { + return { + AI_TODO_CODEX_HOME: await ask(rl, "Codex source path", env.AI_TODO_CODEX_HOME), + AI_TODO_CLAUDE_HOME: await ask(rl, "Claude Code source path", env.AI_TODO_CLAUDE_HOME), + AI_TODO_LLM_ENABLED: await ask(rl, "LLM enabled", env.AI_TODO_LLM_ENABLED), + AI_TODO_LLM_PROVIDER: await ask(rl, "LLM provider", env.AI_TODO_LLM_PROVIDER), + AI_TODO_LLM_MODEL: await ask(rl, "LLM model", env.AI_TODO_LLM_MODEL), + AI_TODO_LLM_ENDPOINT: await ask(rl, "LLM endpoint", env.AI_TODO_LLM_ENDPOINT), + AI_TODO_LLM_API_KEY: await ask(rl, "LLM API key", env.AI_TODO_LLM_API_KEY), + AI_TODO_ORGANIZE_SINCE_DAYS: await ask(rl, "Look-back days", env.AI_TODO_ORGANIZE_SINCE_DAYS), + AI_TODO_ORGANIZE_MAX_INTERACTIONS_PER_SESSION: await ask(rl, "Max interactions per session", env.AI_TODO_ORGANIZE_MAX_INTERACTIONS_PER_SESSION), + AI_TODO_ORGANIZE_MAX_SESSIONS: await ask(rl, "Max sessions", env.AI_TODO_ORGANIZE_MAX_SESSIONS), + AI_TODO_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION: await ask(rl, "Max observations per session", env.AI_TODO_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION) }; - - try { - const res = await fetch(url, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - signal: AbortSignal.timeout(5000), - }); - if (res.ok) { - stored++; - } else { - const body = await res.text().catch(() => ""); - p.log.warn( - `observe failed for ${obs.toolName}: ${res.status} ${res.statusText}${body ? ` — ${body.slice(0, 160)}` : ""}`, - ); - } - } catch (err) { - p.log.warn( - `observe request failed for ${obs.toolName}: ${err instanceof Error ? err.message : String(err)}`, - ); - } + } finally { + rl.close(); } - - await postJsonStrict(`${base}/agentmemory/session/end`, { sessionId: session.id }); - return stored; -} - -async function runDemoSearch(base: string, query: string): Promise { - const data = await postJson<{ results?: Array<{ title?: string }> }>( - `${base}/agentmemory/smart-search`, - { query, limit: 5 }, - 10000, - ); - const items = data?.results ?? []; - return { - query, - hits: items.length, - topTitle: items[0]?.title ?? "(no results)", - }; } -// Prefer the packaged `.env.example` (next to `dist/cli.mjs`); fall back to -// the repo root when running from a source checkout. -function findEnvExample(): string | null { - const candidates = [ - join(__dirname, "..", ".env.example"), - join(__dirname, ".env.example"), - join(process.cwd(), ".env.example"), - ]; - for (const c of candidates) { - if (existsSync(c)) return c; - } - return null; +async function ask(rl: ReturnType, label: string, value: string | undefined): Promise { + const suffix = value ? ` [${value}]` : ""; + const answer = (await rl.question(`${label}${suffix}: `)).trim(); + return answer || value; } -async function runInit() { - p.intro("agentmemory-lab init"); - const target = getUserEnvPath(); - const template = findEnvExample(); - if (!template) { - p.log.error( - "Could not locate .env.example in the package. Re-install with: npm i -g @maimorylab/agentmemory-lab", - ); - process.exit(1); - } - const dir = dirname(target); - const { mkdir, copyFile } = await import("node:fs/promises"); - const { constants: fsConstants } = await import("node:fs"); - try { - await mkdir(dir, { recursive: true }); - // COPYFILE_EXCL collapses the exists-check + copy into one syscall — - // an existsSync(target) + copyFile() pair races with a parallel init - // (or any other process touching ~/.agentmemory/.env between the two - // calls) and would silently overwrite a config the operator just - // wrote. EEXIST out of copyFile is the only "already configured" - // signal we trust. - await copyFile(template, target, fsConstants.COPYFILE_EXCL); - } catch (err) { - if ((err as NodeJS.ErrnoException)?.code === "EEXIST") { - p.log.warn(`${target} already exists — leaving it untouched.`); - p.log.info( - `Compare against the latest template: diff ${target} ${template}`, - ); - p.outro("Nothing changed."); - return; +function parseOptions(argv: string[]): Record { + const args: Record = {}; + for (let index = 0; index < argv.length; index++) { + const token = argv[index]; + if (!token.startsWith("--")) continue; + const key = token.slice(2); + const value = argv[index + 1]; + if (!value || value.startsWith("--")) { + args[key] = "true"; + continue; } - p.log.error( - `Failed to copy template: ${err instanceof Error ? err.message : String(err)}`, - ); - process.exit(1); - } - p.log.success(`Wrote ${target}`); - p.note( - [ - "All keys are commented out by default. Uncomment the ones you want.", - "", - "Common next steps:", - " 1. (Optional) set a To-Do extraction model — LANGEXTRACT_PROVIDER / LANGEXTRACT_MODEL / LANGEXTRACT_BASE_URL + LANGEXTRACT_API_KEY (To-Dos work without it via the rules extractor)", - " 2. Run `node dist/cli.mjs doctor` to verify the daemon sees them", - " 3. Run `npm run start:local-memory` to start the worker", - ].join("\n"), - "Next steps", - ); - p.outro(`Edit ${target} and you're set.`); -} - -async function runDemo() { - const port = getRestPort(); - const base = `http://localhost:${port}`; - p.intro("node dist/cli.mjs demo"); - - if (!(await isAgentmemoryReady())) { - p.log.error( - `agentmemory worker not reachable on port ${port} (livez probe failed). Something may be on the port but it isn't serving /agentmemory/*.`, - ); - p.note(workerRecoveryNote(port), "How to recover"); - process.exit(1); + args[key] = value; + index++; } - - const demoProject = "/tmp/agentmemory-demo"; - const sessions = buildDemoSessions(); - - const sSeed = p.spinner(); - sSeed.start("Seeding 3 demo sessions with realistic observations..."); - - let totalObs = 0; - for (const session of sessions) { - totalObs += await seedDemoSession(base, demoProject, session); - } - - sSeed.stop(`Seeded ${totalObs} observations across ${sessions.length} sessions`); - - const queries = [ - "jwt auth middleware", - "database performance optimization", - "rate limiting", - ]; - - const sQuery = p.spinner(); - sQuery.start(`Running ${queries.length} smart-search queries...`); - - const results: SearchResult[] = []; - for (const query of queries) { - results.push(await runDemoSearch(base, query)); - } - - sQuery.stop("Search complete"); - - const lines = [ - `Project: ${demoProject}`, - `Sessions: ${sessions.length} seeded (${totalObs} observations)`, - "", - "Search results:", - ...results.flatMap((r) => [ - ` "${r.query}"`, - ` → ${r.hits} hit(s), top: ${r.topTitle.slice(0, 60)}`, - ]), - "", - `Notice: searching "database performance optimization"`, - `found the N+1 query fix — keyword matching can't do that.`, - "", - `Viewer: ${getViewerUrl()}`, - `Clean up with: curl -X DELETE "${base}/agentmemory/sessions?project=${demoProject}"`, - ]; - - p.note(lines.join("\n"), "demo complete"); - p.log.success("AI-Todo is working. Point your agent at it and get back to coding."); -} - -function runCommand( - command: string, - commandArgs: string[], - options: { cwd?: string; label: string; optional?: boolean } = { label: "command" }, -): boolean { - const spinner = p.spinner(); - spinner.start(options.label); - const result = spawnSync(command, commandArgs, { - cwd: options.cwd || process.cwd(), - stdio: "pipe", - encoding: "utf-8", - }); - - if (result.status === 0) { - spinner.stop(`${options.label} ✓`); - return true; - } - - const stderr = (result.stderr || "").toString().trim(); - const stdout = (result.stdout || "").toString().trim(); - const msg = stderr || stdout || "unknown error"; - - if (options.optional) { - spinner.stop(`${options.label} (skipped)`); - p.log.warn(msg.slice(0, 300)); - return false; - } - - spinner.stop(`${options.label} ✗`); - p.log.error(msg.slice(0, 300)); - return false; + return args; } -async function runUpgrade() { - p.intro("agentmemory-lab upgrade"); - - const cwd = process.cwd(); - const hasPackageJson = existsSync(join(cwd, "package.json")); - const hasPnpmLock = existsSync(join(cwd, "pnpm-lock.yaml")); - - const pnpmBin = whichBinary("pnpm"); - const npmBin = whichBinary("npm"); - const dockerBin = whichBinary("docker"); - - p.log.info(`Working directory: ${cwd}`); - const requireSuccess = (ok: boolean, label: string): void => { - if (!ok) { - p.log.error(`Upgrade aborted: ${label} failed.`); - process.exit(1); - } +async function withDatabase(fn: (db: Database) => number | Promise): Promise { + const db = openDatabase(getAppPaths()); + try { + return await fn(db); + } finally { + db.close(); + } +} + +async function openUi(argv: string[] = []): Promise { + const args = parseOptions(argv); + const port = args.port ? Number(args.port) : DEFAULT_UI_PORT; + if (!Number.isInteger(port) || port < 0 || port > 65535) { + console.error("invalid port"); + return 1; + } + const paths = getAppPaths(); + const db = openDatabase(paths); + let dbClosed = false; + const closeDb = () => { + if (dbClosed) return; + dbClosed = true; + db.close(); }; - - if (hasPackageJson) { - const usePnpm = !!pnpmBin && hasPnpmLock; - if (usePnpm && pnpmBin) { - const installOk = runCommand(pnpmBin, ["install"], { - label: "Refreshing dependencies (pnpm install)", - }); - requireSuccess(installOk, "pnpm install"); - runCommand(pnpmBin, ["up", "iii-sdk@0.11.2"], { - label: "Pinning iii-sdk@0.11.2", - optional: true, - }); - } else if (npmBin) { - const installOk = runCommand(npmBin, ["install"], { - label: "Refreshing dependencies (npm install)", - }); - requireSuccess(installOk, "npm install"); - runCommand(npmBin, ["install", "iii-sdk@0.11.2"], { - label: "Pinning iii-sdk@0.11.2", - optional: true, + const startupScanner = createStartupScanner(db, paths); + const server = createAppServer({ db, paths, startupScan: startupScanner.status }); + try { + await listen(server, port); + } catch (error) { + closeDb(); + if ((error as NodeJS.ErrnoException).code === "EADDRINUSE") { + console.error(`${port} is already in use. Try ai-todo open --port .`); + return 1; + } + console.error((error as Error).message); + return 1; + } + startupScanner.start(); + const address = server.address(); + if (!address || typeof address === "string") { + closeDb(); + return 1; + } + console.log(`AI-Todo UI: http://127.0.0.1:${address.port}/`); + console.log("Press Ctrl+C to stop."); + await new Promise((resolve) => { + let stopping = false; + const stop = () => { + if (stopping) return; + stopping = true; + process.off("SIGINT", stop); + process.off("SIGTERM", stop); + server.close(() => { + closeDb(); + resolve(); }); - } else { - p.log.warn("No package manager found (pnpm/npm). Skipping JS dependency upgrade."); - } - } else { - p.log.warn("No package.json in current directory. Skipping JS dependency upgrade."); - } - - const upgradeEngine = await p.confirm({ - message: "Re-run the iii-engine install script (curl | sh)?", - initialValue: true, + }; + process.once("SIGINT", stop); + process.once("SIGTERM", stop); }); - if (p.isCancel(upgradeEngine)) { - p.cancel("Cancelled."); - return process.exit(0); - } - if (upgradeEngine === true) { - await runIiiInstaller(); - } else { - p.log.info("Skipped iii-engine installer."); - } - - if (dockerBin) { - runCommand(dockerBin, ["pull", `iiidev/iii:${IIPINNED_VERSION}`], { - label: `Pulling iii Docker image v${IIPINNED_VERSION} (pinned)`, - optional: true, - }); - } else { - p.log.info("Docker not found. Skipping Docker image refresh."); - } - - p.note( - [ - "Upgrade flow completed.", - "", - "Recommended next steps:", - " 1) node dist/cli.mjs status", - " 2) npm/pnpm test", - " 3) npm run start:local-memory", - ].join("\n"), - "agentmemory-lab upgrade", - ); -} - -function pidAlive(pid: number): boolean { - try { - process.kill(pid, 0); - return true; - } catch (err) { - return (err as NodeJS.ErrnoException)?.code === "EPERM"; - } + return 0; } -async function signalAndWait( - pid: number, - initialSignal: NodeJS.Signals, - timeoutMs: number, -): Promise { - try { - process.kill(pid, initialSignal); - } catch (err) { - const code = (err as NodeJS.ErrnoException)?.code; - if (code === "ESRCH") return true; - if (code === "EPERM") { - p.log.warn(`No permission to signal pid ${pid}. Try: kill ${pid}`); - return false; - } - vlog(`${initialSignal} ${pid}: ${err instanceof Error ? err.message : String(err)}`); - return false; - } - const deadline = Date.now() + timeoutMs; - while (Date.now() < deadline) { - if (!pidAlive(pid)) return true; - await new Promise((r) => setTimeout(r, 200)); - } - if (!pidAlive(pid)) return true; - try { - process.kill(pid, "SIGKILL"); - } catch (err) { - if ((err as NodeJS.ErrnoException)?.code === "ESRCH") return true; - vlog(`SIGKILL ${pid}: ${err instanceof Error ? err.message : String(err)}`); - return false; - } - await new Promise((r) => setTimeout(r, 200)); - return !pidAlive(pid); -} - -function findEnginePidsByPort(port: number): number[] { - if (IS_WINDOWS) return []; - const lsof = whichBinary("lsof"); - if (!lsof) return []; - // -sTCP:LISTEN restricts to listening server sockets only. Without - // this, lsof also returns client-side PIDs (any process with an - // active TCP connection to :port), which includes the agentmemory - // CLI itself thanks to the keep-alive fetch in isEngineRunning(). - // signalAndWait would then SIGKILL its own parent — exit code 137. - const selfPid = process.pid; - try { - const out = execFileSync(lsof, ["-i", `:${port}`, "-sTCP:LISTEN", "-t"], { - encoding: "utf-8", - stdio: ["ignore", "pipe", "ignore"], +function listen(server: ReturnType, port: number): Promise { + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(port, "127.0.0.1", () => { + server.off("error", reject); + resolve(); }); - return out - .split(/\s+/) - .map((s) => parseInt(s, 10)) - .filter((n) => Number.isFinite(n) && n > 0 && n !== selfPid); - } catch (err) { - vlog(`lsof :${port}: ${err instanceof Error ? err.message : String(err)}`); - return []; - } -} - -async function stopDockerEngine(composeFile: string, port: number): Promise { - const dockerBin = whichBinary("docker"); - if (!dockerBin) { - p.log.error( - `Engine was started via Docker compose, but \`docker\` is no longer on PATH. Stop it manually:\n docker compose -f ${composeFile} down`, - ); - process.exit(1); - } - if (!existsSync(composeFile)) { - p.log.error( - `Engine state references ${composeFile}, but the file is gone. Stop it manually:\n docker compose down (from the dir holding the original docker-compose.yml)`, - ); - process.exit(1); - } - const ok = runCommand(dockerBin, ["compose", "-f", composeFile, "down"], { - label: `docker compose -f ${composeFile} down`, }); - clearEnginePidfile(); - clearEngineState(); - clearWorkerPidfile(); - if (!ok) { - p.log.error( - `docker compose down failed. The engine may still be running on :${port}. Inspect with:\n docker compose -f ${composeFile} ps`, - ); - process.exit(1); - } - p.outro("Stopped. Memories persisted to disk; restart anytime with: npm run start:local-memory"); -} - -async function runStop(): Promise { - p.intro("agentmemory-lab stop"); - const port = getRestPort(); - const state = readEngineState(); - const running = await isEngineRunning(); - const force = args.includes("--force"); - - if (state?.kind === "docker") { - if (!running) { - p.log.info(`No engine responding on port ${port}.`); - clearEnginePidfile(); - clearEngineState(); - clearWorkerPidfile(); - p.outro("Nothing to stop."); - return; - } - await stopDockerEngine(state.composeFile, port); - return; - } - - const portPids = findEnginePidsByPort(port); - const pidfilePid = readEnginePidfile(); - // #640 + #474: read the worker pid up front so the engine-down branch - // can still reap an orphaned worker process (the common failure mode - // where a wrapper script kept the worker alive across engine restarts). - const workerPid = readWorkerPidfile(); - - if (!running) { - if (portPids.length === 0 && pidfilePid === null && workerPid === null) { - clearEnginePidfile(); - clearEngineState(); - clearWorkerPidfile(); - p.outro("Nothing to stop."); - return; - } - if (workerPid !== null && portPids.length === 0 && pidfilePid === null) { - // Engine already gone but worker is lingering — reap it directly - // instead of preserving for manual cleanup. - const s = p.spinner(); - s.start(`Stopping orphaned agentmemory worker (pid ${workerPid})...`); - const ok = await signalAndWait(workerPid, "SIGTERM", 3000); - s.stop(ok ? `Stopped worker pid ${workerPid}` : `Failed to stop worker pid ${workerPid}`); - clearEnginePidfile(); - clearEngineState(); - clearWorkerPidfile(); - if (!ok) { - p.log.error(`Worker pid ${workerPid} survived SIGKILL. Investigate with \`ps\`.`); - process.exit(1); - } - p.outro("Stopped orphaned worker. Memories persisted to disk."); - return; - } - const survivors = new Set(portPids); - if (pidfilePid) survivors.add(pidfilePid); - if (workerPid) survivors.add(workerPid); - p.log.warn( - `Engine not responding on :${port}, but ${survivors.size} process(es) still hold the port or pidfile: ${[...survivors].join(", ")}`, - ); - p.log.info( - `Preserving ~/.agentmemory/iii.pid + worker.pid. Investigate before manual cleanup:\n ps -p ${[...survivors].join(",")} -o pid,ppid,comm,etime\n ${IS_WINDOWS ? "netstat -ano | findstr :" + port : "lsof -i :" + port}`, - ); - process.exit(1); - } - - if (!state) { - const compose = discoverComposeFile(); - if (compose && pidfilePid === null) { - if (force) { - p.log.warn( - `--force: bypassing Docker-heuristic guard. Falling back to native pidfile + lsof on :${port}.`, - ); - } else { - p.log.error( - `Engine is running on :${port} but no pidfile or state file is present. It may have been started via Docker compose by a different shell. Refusing to signal host PIDs.\n\nStop it with:\n docker compose -f ${compose} down\n\nOr re-run with --force to signal whatever lsof finds on :${port}, or AGENTMEMORY_USE_DOCKER=1 to record state next time.`, - ); - process.exit(1); - } - } - } - - const candidates = new Set(); - if (pidfilePid) candidates.add(pidfilePid); - for (const pid of portPids) candidates.add(pid); - - // #640 + #474: stop must also reap the agentmemory worker process - // (`node dist/index.mjs`). If only the engine is killed, the worker can - // survive (detached spawn / signal not propagated) and reconnect to the - // next engine as a duplicate registration. workerPid was read above so - // the engine-down branch could also reap orphans. - const workerCandidates = new Set(); - if (workerPid) workerCandidates.add(workerPid); - - if (candidates.size === 0 && workerCandidates.size === 0) { - p.log.error( - `Could not locate engine process. Try:\n ${IS_WINDOWS ? "netstat -ano | findstr :" + port : "lsof -i :" + port + " -t | xargs kill -9"}`, - ); - process.exit(1); - } - - let allStopped = true; - for (const pid of candidates) { - const s = p.spinner(); - s.start(`Stopping iii-engine (pid ${pid})...`); - const ok = await signalAndWait(pid, "SIGTERM", 3000); - s.stop(ok ? `Stopped pid ${pid}` : `Failed to stop pid ${pid}`); - if (!ok) allStopped = false; - } - for (const pid of workerCandidates) { - if (candidates.has(pid)) continue; - const s = p.spinner(); - s.start(`Stopping agentmemory worker (pid ${pid})...`); - const ok = await signalAndWait(pid, "SIGTERM", 3000); - s.stop(ok ? `Stopped worker pid ${pid}` : `Failed to stop worker pid ${pid}`); - if (!ok) allStopped = false; - } - - clearEnginePidfile(); - clearEngineState(); - clearWorkerPidfile(); - if (!allStopped) { - p.log.error("One or more processes survived SIGKILL. Investigate with `ps`."); - process.exit(1); - } - p.outro("Stopped. Memories persisted to disk; restart anytime with: npm run start:local-memory"); -} - -async function runMcp(): Promise { - await import("./mcp/standalone.js"); -} - -async function runConfig(): Promise { - const sub = args[1]; - if (sub === "get") { - p.note(JSON.stringify(getTodoExtractorUserConfig(), null, 2), `Todo extractor config (${getUserEnvPath()})`); - return; - } - if (sub === "set") { - const pair = args[2] || ""; - const eq = pair.indexOf("="); - if (eq <= 0) { - p.log.error("Usage: agentmemory-lab config set KEY=value"); - return; - } - const key = pair.slice(0, eq); - const value = pair.slice(eq + 1); - const applied = writeUserEnv({ [key]: value }); - if (applied.includes(key)) { - p.log.success(`Updated ${key}. Restart AI-Todo to use it.`); - } else { - p.log.warn( - `Did not write '${key}'. Only these keys are writable (non-empty, single-line):\n ${[...WRITABLE_TODO_EXTRACT_KEYS].join(", ")}`, - ); - } - return; - } - p.log.info("Usage: agentmemory-lab config get | config set KEY=value"); -} - -async function runConnectCmd(): Promise { - const { runConnect } = await import("./cli/connect/index.js"); - await runConnect(args.slice(1)); } -async function runImportJsonl(): Promise { - // Long-form flags that take a value. Their value tokens must be - // consumed alongside the flag so they don't leak into positional - // args (e.g. `--port 3112 import-jsonl` would otherwise turn - // 3112 into pathArg). - const VALUE_FLAGS = new Set(["--port", "--tools"]); - let maxFiles: number | undefined; - const tail = args.slice(1); - const positional: string[] = []; - for (let i = 0; i < tail.length; i++) { - const a = tail[i]!; - if (a === "--max-files") { - const raw = tail[i + 1]; - const parsed = raw !== undefined ? parseInt(raw, 10) : NaN; - if (Number.isInteger(parsed) && parsed > 0) { - maxFiles = parsed; - } else if (raw !== undefined) { - p.log.warn(`Ignoring --max-files ${raw}: expected a positive integer.`); - } - i++; - continue; - } - if (a.startsWith("--max-files=")) { - const raw = a.slice("--max-files=".length); - const parsed = parseInt(raw, 10); - if (Number.isInteger(parsed) && parsed > 0) { - maxFiles = parsed; - } else { - p.log.warn(`Ignoring --max-files=${raw}: expected a positive integer.`); - } - continue; - } - if (VALUE_FLAGS.has(a)) { - i++; - continue; - } - if (a.startsWith("-")) continue; - positional.push(a); - } - const pathArg = positional[0]; - - const port = getRestPort(); - const base = `http://localhost:${port}`; - - let probeOk = false; - let probeDetail = ""; - try { - const probe = await fetch(`${base}/agentmemory/livez`, { - signal: AbortSignal.timeout(2000), - }); - probeOk = probe.ok; - if (!probeOk) { - const probeBody = await probe.text().catch(() => ""); - probeDetail = `reachable but unhealthy (HTTP ${probe.status}${probeBody ? `: ${probeBody.slice(0, 200)}` : ""})`; - } - } catch (err) { - probeOk = false; - const msg = err instanceof Error ? err.message : String(err); - probeDetail = `unreachable (${msg})`; - } - if (!probeOk) { - p.log.error( - `agentmemory livez probe failed on port ${port}: ${probeDetail}.`, - ); - p.note(workerRecoveryNote(port), "How to recover"); - process.exit(1); +function scan(db: Database, source: string | undefined, path: string | undefined): number { + if (!source) { + console.error("usage: ai-todo scan [path]"); + return 1; } - const body: Record = {}; - if (pathArg) body["path"] = pathArg; - if (maxFiles !== undefined) body["maxFiles"] = maxFiles; - - const headers: Record = { "content-type": "application/json" }; - const secret = process.env["AGENTMEMORY_SECRET"]; - if (secret) headers["authorization"] = `Bearer ${secret}`; - - p.log.info(`Importing JSONL from ${pathArg || "~/.claude/projects"}…`); - const spinner = p.spinner(); - spinner.start("scanning files"); - - try { - const res = await fetch(`${base}/agentmemory/replay/import-jsonl`, { - method: "POST", - headers, - body: JSON.stringify(body), - signal: AbortSignal.timeout(120_000), - }); - const text = await res.text(); - let json: { - success?: boolean; - error?: string; - imported?: number; - sessionIds?: string[]; - observations?: number; - discovered?: number; - truncated?: boolean; - traversalCapped?: boolean; - maxFiles?: number; - maxFilesUpperBound?: number; - } = {}; - if (text.length > 0) { - try { - json = JSON.parse(text); - } catch { - spinner.stop("failed"); - p.log.error( - `server returned non-JSON response (HTTP ${res.status}): ${text.slice(0, 200)}`, - ); - process.exit(1); - } - } - if (!res.ok || json.success !== true) { - spinner.stop("failed"); - const detail = - json.error || - (text.length === 0 - ? "empty response body" - : json.success === undefined - ? `HTTP ${res.status} (response missing success field)` - : `HTTP ${res.status}`); - if (res.status === 401) { - p.log.error( - `${detail}. Set AGENTMEMORY_SECRET to match the server's secret and re-run.`, - ); - } else if (res.status === 404) { - p.log.error( - `${detail}. The running agentmemory server does not expose /agentmemory/replay/import-jsonl — upgrade to v0.8.13 or later.`, - ); - } else { - p.log.error(detail); - } - process.exit(1); - } - spinner.stop( - `imported ${json.imported ?? 0} file(s), ${json.observations ?? 0} observation(s) across ${json.sessionIds?.length || 0} session(s)`, - ); - if (json.truncated) { - const cap = json.maxFiles ?? 200; - const upper = json.maxFilesUpperBound ?? 1000; - const discovered = json.discovered ?? 0; - const skipped = discovered - (json.imported ?? 0); - const discoveredLabel = json.traversalCapped - ? `${discovered}+ (traversal halted at safety cap)` - : String(discovered); - const baseMsg = `Hit the ${cap}-file scan cap; ${skipped} of ${discoveredLabel} discovered file(s) were skipped.`; - // If we already saw more than the server's hard cap (or the - // walker stopped early), bumping --max-files won't help on its - // own — recommend batching by subdirectory. - if (discovered > upper || json.traversalCapped) { - p.log.warn( - `${baseMsg} Tree exceeds the server's --max-files limit of ${upper}; ` + - `batch by subdirectory (run import-jsonl once per project under ~/.claude/projects).`, - ); - } else { - const suggested = Math.min( - Math.max((discovered || cap) + 100, cap * 2), - upper, - ); - p.log.warn( - `${baseMsg} Re-run with --max-files=${suggested} (max ${upper}) or batch by subdirectory.`, - ); - } - } - if (json.sessionIds && json.sessionIds.length > 0) { - p.log.info(`View at ${getViewerUrl()} → Replay tab`); - } - } catch (err) { - spinner.stop("failed"); - if (err instanceof Error && err.name === "TimeoutError") { - p.log.error("import timed out after 2 minutes"); - } else { - p.log.error(err instanceof Error ? err.message : String(err)); - } - process.exit(1); + const scan = scanSource(db, source, path); + if (!scan.ok && scan.error === "unsupported_source") { + console.error(`unsupported source: ${source}`); + return 1; } -} - -// --------------------------------------------------------------------------- -// `agentmemory-lab remove` — clean uninstall. -// -// Planning logic lives in src/cli/remove-plan.ts so it's testable without -// touching $HOME. This function loads the manifest, builds the plan, -// double-confirms, then executes step by step. - -function loadConnectManifest(home: string): ConnectManifest | null { - const path = join(home, ".agentmemory", "backups", "connect-manifest.json"); - try { - const raw = readFileSync(path, "utf-8"); - const parsed = JSON.parse(raw) as Partial; - if (Array.isArray(parsed?.installed)) { - return { installed: parsed.installed }; - } - return null; - } catch { - return null; + if (!scan.ok) { + console.error(`path not found for ${source}`); + return 1; } -} - -function probeLocalBinIiiVersion(home: string): string | null { - const path = localBinIii(home); - if (!existsSync(path)) return null; - return iiiBinVersion(path); -} -function safeDelete(path: string): { ok: boolean; message: string } { - try { - if (!existsSync(path)) return { ok: true, message: `not present (${path})` }; - const st = statSync(path); - if (st.isDirectory()) { - rmSync(path, { recursive: true, force: true }); - } else { - unlinkSync(path); - } - return { ok: true, message: `deleted ${path}` }; - } catch (err) { - return { - ok: false, - message: `failed ${path}: ${err instanceof Error ? err.message : String(err)}`, - }; - } + const result = scan.result; + console.log(`source: ${result.source}`); + console.log(`scanned: ${result.scanned}`); + console.log(`observations: ${result.observations}`); + console.log(`skipped: ${result.skipped}`); + return 0; } -async function runRemove(): Promise { - p.intro("agentmemory-lab remove"); - const force = args.includes("--force"); - const keepData = args.includes("--keep-data"); - - const home = homedir(); - const connectManifest = loadConnectManifest(home); - const localBinIiiVersion = probeLocalBinIiiVersion(home); - - const options: RemoveOptions = { force, keepData }; - const plan = buildRemovePlan( - { - home, - pinnedVersion: IIPINNED_VERSION, - localBinIiiVersion, - connectManifest, - }, - options, - ); - - const applicable = plan.filter((it) => it.applicable); - if (applicable.length === 0) { - p.outro("Nothing to remove. agentmemory is already gone."); - return; - } - - p.note(formatPlan(plan), "destruction plan"); - - if (!force) { - const proceed = await p.confirm({ - message: "Proceed with these deletions?", - initialValue: false, - }); - if (p.isCancel(proceed) || proceed !== true) { - p.cancel("Cancelled. Nothing was deleted."); - return; - } - const sure = await p.confirm({ - message: "This is irreversible. Continue?", - initialValue: false, - }); - if (p.isCancel(sure) || sure !== true) { - p.cancel("Cancelled. Nothing was deleted."); - return; - } +function updateStatus(db: Database, id: string | undefined, status: "done" | "ignored"): number { + if (!id) { + console.error("missing todo id"); + return 1; } - - for (const item of plan) { - if (!item.applicable) continue; - - // alwaysAsk items get a per-item confirmation even with --force. - if (item.alwaysAsk) { - const ok = await p.confirm({ - message: `${item.description} — really delete${item.path ? ` ${item.path}` : ""}?`, - initialValue: false, - }); - if (p.isCancel(ok) || ok !== true) { - p.log.info(`skipped: ${item.id}`); - continue; - } - } - - if (item.id === "stop-engine") { - try { - const port = getRestPort(); - const portPids = findEnginePidsByPort(port); - const pidfilePid = readEnginePidfile(); - const cands = new Set(); - if (pidfilePid) cands.add(pidfilePid); - for (const pid of portPids) cands.add(pid); - for (const pid of cands) await signalAndWait(pid, "SIGTERM", 3000); - clearEnginePidfile(); - clearEngineState(); - p.log.success( - cands.size > 0 - ? `stopped engine (${cands.size} pid${cands.size === 1 ? "" : "s"})` - : "no engine running", - ); - } catch (err) { - p.log.warn( - `engine stop best-effort: ${err instanceof Error ? err.message : String(err)}`, - ); - } - continue; - } - - if (!item.path) continue; - const r = safeDelete(item.path); - if (r.ok) p.log.success(r.message); - else p.log.error(r.message); + if (!updateTodoStatus(db, id, status)) { + console.error(`todo not found: ${id}`); + return 1; } - - p.outro( - "Done. AI-Todo cleanly removed. The npm package itself: npm uninstall -g @maimorylab/agentmemory-lab", - ); + console.log(`${status}: ${id}`); + return 0; } -const commands: Record Promise> = { - init: runInit, - connect: runConnectCmd, - status: runStatus, - doctor: runDoctor, - demo: runDemo, - upgrade: runUpgrade, - stop: runStop, - remove: runRemove, - mcp: runMcp, - config: runConfig, - "import-jsonl": runImportJsonl, -}; - -const firstArg = args[0]; -if ( - firstArg && - !firstArg.startsWith("-") && - !Object.prototype.hasOwnProperty.call(commands, firstArg) -) { - p.log.error( - `Unknown command '${firstArg}'. Run \`agentmemory-lab --help\` for the list of commands.`, - ); - process.exit(1); +if (import.meta.url === `file://${process.argv[1]}`) { + process.exitCode = await main(); } -const handler = commands[args[0] ?? ""] ?? main; -handler().catch((err) => { - p.log.error(err instanceof Error ? err.message : String(err)); - process.exit(1); -}); diff --git a/src/cli/connect/codex-hooks.ts b/src/cli/connect/codex-hooks.ts deleted file mode 100644 index ad19877b..00000000 --- a/src/cli/connect/codex-hooks.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { existsSync, readFileSync } from "node:fs"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -/** - * Workaround for openai/codex#16430 — Codex Desktop does not dispatch - * plugin-local `hooks.json` even though both `CodexHooks` and `PluginHooks` - * feature flags are stable + default-enabled in - * `codex-rs/features/src/lib.rs`. Until upstream fixes plugin-scope - * dispatch, the same hook commands can be mirrored into the global - * `~/.codex/hooks.json`, which is loaded reliably. - * - * This module builds that mirror, with `${CLAUDE_PLUGIN_ROOT}` resolved to - * the bundled `plugin/` directory so the user-scope file does not depend - * on env-var expansion (Codex only injects `CLAUDE_PLUGIN_ROOT` for - * plugin-scope hooks). - * - * Identification on re-install: every command we write contains the - * resolved `/scripts/` prefix, so subsequent installs can - * strip our entries and re-add cleanly without touching the user's other - * hook entries. - */ - -type HookHandler = { type: string; command: string }; -type HookEntry = { matcher?: string; hooks: HookHandler[] }; -export type HookManifest = { hooks: Record }; - -/** - * Locate the bundled `plugin/` directory at runtime. Walks up from the - * module's own location looking for `plugin/scripts/` + `plugin/hooks/`, - * both shipped via the npm `files` field. Works for both `dist/cli.mjs` - * (bundled) and `src/cli/connect/codex-hooks.ts` (dev) layouts. - */ -export function findPluginRoot(startUrl: string = import.meta.url): string { - const here = dirname(fileURLToPath(startUrl)); - let dir = here; - for (let i = 0; i < 12; i++) { - if ( - existsSync(join(dir, "plugin", "scripts")) && - existsSync(join(dir, "plugin", "hooks")) - ) { - return resolve(join(dir, "plugin")); - } - const parent = dirname(dir); - if (parent === dir) break; - dir = parent; - } - throw new Error( - `agentmemory: could not locate bundled plugin/ directory (searched up from ${here})`, - ); -} - -/** - * Build the merged hooks.json content. - * - * 1. Strip any entry from `existing` whose first hook command points - * under `/scripts/`. This lets us re-install idempotently - * without leaving stale references. - * 2. Append fresh entries from the bundled Codex manifest with - * `${CLAUDE_PLUGIN_ROOT}` rewritten to the absolute plugin path. - * Matcher values from the bundled manifest are preserved so PreToolUse - * event routing keeps working. - */ -export function buildMergedHooks( - existing: HookManifest | null, - pluginRoot: string, - manifestFile = "hooks.codex.json", -): HookManifest { - const bundledManifestPath = join(pluginRoot, "hooks", manifestFile); - const ours = JSON.parse(readFileSync(bundledManifestPath, "utf-8")) as HookManifest; - const scriptsDir = join(pluginRoot, "scripts"); - - const out: HookManifest = { hooks: {} }; - - if (existing?.hooks) { - for (const [event, entries] of Object.entries(existing.hooks)) { - const kept = entries.filter((entry) => !isAgentmemoryEntry(entry, scriptsDir)); - if (kept.length > 0) out.hooks[event] = kept; - } - } - - for (const [event, entries] of Object.entries(ours.hooks)) { - const resolvedEntries: HookEntry[] = entries.map((entry) => { - const next: HookEntry = { - hooks: entry.hooks.map((handler) => ({ - type: handler.type, - command: handler.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRoot), - })), - }; - if (entry.matcher !== undefined) next.matcher = entry.matcher; - return next; - }); - out.hooks[event] = [...(out.hooks[event] ?? []), ...resolvedEntries]; - } - - return out; -} - -function isAgentmemoryEntry(entry: HookEntry, scriptsDir: string): boolean { - const normalizedScriptsDir = normalizePathForCommandMatch(scriptsDir); - return entry.hooks.some((handler) => - normalizePathForCommandMatch(handler.command).includes(normalizedScriptsDir), - ); -} - -function normalizePathForCommandMatch(value: string): string { - return value.replace(/\\/g, "/"); -} diff --git a/src/cli/connect/codex.ts b/src/cli/connect/codex.ts deleted file mode 100644 index d11b04c2..00000000 --- a/src/cli/connect/codex.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; -import { homedir } from "node:os"; -import { join, dirname } from "node:path"; -import * as p from "@clack/prompts"; -import type { ConnectAdapter, ConnectOptions, ConnectResult } from "./types.js"; -import { - backupFile, - logAlreadyWired, - logBackup, - logInstalled, - readJsonSafe, - writeJsonAtomic, -} from "./util.js"; -import { - buildMergedHooks, - findPluginRoot, - type HookManifest, -} from "./codex-hooks.js"; - -const CODEX_DIR = join(homedir(), ".codex"); -const CODEX_TOML = join(CODEX_DIR, "config.toml"); -const CODEX_HOOKS = join(CODEX_DIR, "hooks.json"); - -const TOML_BLOCK = `[mcp_servers.agentmemory] -command = "npx" -args = ["-y", "@agentmemory/mcp"] - -[mcp_servers.agentmemory.env] -AGENTMEMORY_URL = "http://localhost:3111" -`; - -const SECTION_HEADER = "[mcp_servers.agentmemory]"; - -function isWiredText(toml: string): boolean { - return toml.includes(SECTION_HEADER); -} - -function stripExistingBlock(toml: string): string { - const lines = toml.split(/\r?\n/); - const out: string[] = []; - let skipping = false; - for (const line of lines) { - const trimmed = line.trim(); - if ( - trimmed === SECTION_HEADER || - trimmed === "[mcp_servers.agentmemory.env]" - ) { - skipping = true; - continue; - } - if ( - skipping && - trimmed.startsWith("[") && - trimmed !== "[mcp_servers.agentmemory.env]" - ) { - skipping = false; - } - if (!skipping) out.push(line); - } - return out.join("\n").replace(/\n{3,}$/, "\n\n").trimEnd() + "\n"; -} - -export const adapter: ConnectAdapter = { - name: "codex", - displayName: "Codex CLI", - docs: "https://github.com/MaimoryLab/agentmemory-lab#codex-cli-codex-plugin-platform", - protocolNote: - "→ Using MCP. Hooks ship via the Codex plugin; on Codex Desktop, also pass --with-hooks to install the global hooks.json workaround for openai/codex#16430.", - - detect(): boolean { - return existsSync(CODEX_DIR); - }, - - async install(opts: ConnectOptions): Promise { - const exists = existsSync(CODEX_TOML); - const current = exists ? readFileSync(CODEX_TOML, "utf-8") : ""; - const wired = isWiredText(current); - - if (wired && !opts.force) { - logAlreadyWired("Codex CLI", CODEX_TOML); - return { kind: "already-wired", mutatedPath: CODEX_TOML }; - } - - if (opts.dryRun) { - p.log.info( - `[dry-run] Would ${wired ? "rewrite" : "append"} [mcp_servers.agentmemory] in ${CODEX_TOML}`, - ); - if (opts.withHooks) installCodexHooks(opts); - return { kind: "installed", mutatedPath: CODEX_TOML }; - } - - let backupPath: string | undefined; - if (exists) { - backupPath = backupFile(CODEX_TOML, "codex", "toml"); - logBackup(backupPath); - } else { - mkdirSync(dirname(CODEX_TOML), { recursive: true }); - } - - const cleaned = wired ? stripExistingBlock(current) : current; - const joiner = cleaned.length === 0 || cleaned.endsWith("\n") ? "" : "\n"; - const next = `${cleaned}${joiner}${cleaned.length > 0 ? "\n" : ""}${TOML_BLOCK}`; - writeFileSync(CODEX_TOML, next, "utf-8"); - - const verify = readFileSync(CODEX_TOML, "utf-8"); - if (!isWiredText(verify)) { - p.log.error( - `Verification failed: ${CODEX_TOML} did not contain ${SECTION_HEADER} after write.`, - ); - return { kind: "skipped", reason: "verification-failed" }; - } - - logInstalled("Codex CLI", CODEX_TOML); - p.log.info( - "Codex picks up MCP servers on next launch. For the deeper plugin install, run: codex plugin marketplace add MaimoryLab/agentmemory-lab && codex plugin add agentmemory@agentmemory", - ); - - if (opts.withHooks) { - const hookResult = installCodexHooks(opts); - if (hookResult.kind === "skipped") { - p.log.warn( - `Codex hooks fallback skipped: ${hookResult.reason}. MCP wiring still applied.`, - ); - } - } - - return { - kind: "installed", - mutatedPath: CODEX_TOML, - ...(backupPath !== undefined && { backupPath }), - }; - }, -}; - -/** - * Install the global `~/.codex/hooks.json` fallback. See - * `codex-hooks.ts` for context (openai/codex#16430). Returns a result - * describing the side effect for the caller's summary; failures here do - * not roll back the MCP wiring. - */ -function installCodexHooks(opts: ConnectOptions): ConnectResult { - let pluginRoot: string; - try { - pluginRoot = findPluginRoot(); - } catch (err) { - return { - kind: "skipped", - reason: err instanceof Error ? err.message : String(err), - }; - } - - const existing = readJsonSafe(CODEX_HOOKS); - const merged = buildMergedHooks(existing, pluginRoot); - - if (opts.dryRun) { - p.log.info( - `[dry-run] Would ${existing ? "merge" : "create"} ${CODEX_HOOKS} with ${Object.keys(merged.hooks).length} event(s)`, - ); - return { kind: "installed", mutatedPath: CODEX_HOOKS }; - } - - let backupPath: string | undefined; - if (existsSync(CODEX_HOOKS)) { - backupPath = backupFile(CODEX_HOOKS, "codex-hooks", "json"); - logBackup(backupPath); - } - - writeJsonAtomic(CODEX_HOOKS, merged); - - logInstalled("Codex hooks (workaround for openai/codex#16430)", CODEX_HOOKS); - p.log.info( - "User-scope hooks reference absolute paths under the bundled plugin/ dir. Re-run `agentmemory connect codex --with-hooks` after upgrading agentmemory to refresh them.", - ); - - return { - kind: "installed", - mutatedPath: CODEX_HOOKS, - ...(backupPath !== undefined && { backupPath }), - }; -} diff --git a/src/cli/connect/index.ts b/src/cli/connect/index.ts deleted file mode 100644 index adc28e23..00000000 --- a/src/cli/connect/index.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { platform } from "node:os"; -import * as p from "@clack/prompts"; -import type { ConnectAdapter, ConnectOptions, ConnectResult } from "./types.js"; -import { adapter as codex } from "./codex.js"; - -export const ADAPTERS: readonly ConnectAdapter[] = [codex]; - -export function resolveAdapter(name: string): ConnectAdapter | null { - const lower = name.toLowerCase(); - return ADAPTERS.find((a) => a.name === lower) ?? null; -} - -export function knownAgents(): string[] { - return ADAPTERS.map((a) => a.name); -} - -function parseFlags(args: string[]): { - dryRun: boolean; - force: boolean; - all: boolean; - withHooks: boolean; - positional: string[]; -} { - const positional: string[] = []; - let dryRun = false; - let force = false; - let all = false; - let withHooks = false; - for (const a of args) { - if (a === "--dry-run") dryRun = true; - else if (a === "--force") force = true; - else if (a === "--all") all = true; - else if (a === "--with-hooks") withHooks = true; - else if (!a.startsWith("-")) positional.push(a); - } - return { dryRun, force, all, withHooks, positional }; -} - -export async function runAdapter( - adapter: ConnectAdapter, - opts: ConnectOptions, -): Promise { - if (!adapter.detect()) { - p.log.warn( - `${adapter.displayName}: not detected on this machine (skipping).${adapter.docs ? ` Docs: ${adapter.docs}` : ""}`, - ); - return { kind: "skipped", reason: "not-detected" }; - } - p.log.step(`Wiring ${adapter.displayName}…`); - if (adapter.protocolNote) { - p.log.message(adapter.protocolNote); - } - try { - return await adapter.install(opts); - } catch (err) { - p.log.error( - `${adapter.displayName}: ${err instanceof Error ? err.message : String(err)}`, - ); - return { kind: "skipped", reason: "exception" }; - } -} - -export async function runConnect(args: string[]): Promise { - const { dryRun, force, all, withHooks, positional } = parseFlags(args); - const allowWindowsAdapter = - positional.length === 1 && positional[0]?.toLowerCase() === "codex"; - if (platform() === "win32" && !allowWindowsAdapter) { - p.intro("agentmemory connect"); - p.log.warn( - "Windows: automated `connect` is not supported yet. See https://github.com/MaimoryLab/agentmemory-lab#other-agents for manual install steps.", - ); - p.outro("Windows: manual install required — see docs"); - return; - } - - const opts: ConnectOptions = { dryRun, force, withHooks }; - - p.intro("agentmemory connect"); - - if (positional.length === 0 && !all) { - const detected = ADAPTERS.filter((a) => a.detect()); - if (detected.length === 0) { - p.log.error("No supported agents detected on this machine."); - p.outro(`Supported: ${knownAgents().join(", ")}`); - process.exit(1); - } - const picked = await p.multiselect({ - message: "Wire agentmemory into which agents?", - options: detected.map((a) => ({ value: a.name, label: a.displayName })), - required: true, - }); - if (p.isCancel(picked)) { - p.cancel("Cancelled."); - return; - } - const results: { name: string; result: ConnectResult }[] = []; - for (const name of picked as string[]) { - const adapter = resolveAdapter(name); - if (!adapter) continue; - results.push({ name, result: await runAdapter(adapter, opts) }); - } - summarize(results); - return; - } - - if (all) { - const detected = ADAPTERS.filter((a) => a.detect()); - if (detected.length === 0) { - p.log.error("No supported agents detected on this machine."); - process.exit(1); - } - const results: { name: string; result: ConnectResult }[] = []; - for (const adapter of detected) { - results.push({ - name: adapter.name, - result: await runAdapter(adapter, opts), - }); - } - summarize(results); - return; - } - - const agentName = positional[0]!; - const adapter = resolveAdapter(agentName); - if (!adapter) { - p.log.error(`Unknown agent: ${agentName}`); - p.outro(`Supported: ${knownAgents().join(", ")}`); - process.exit(1); - } - - const result = await runAdapter(adapter, opts); - summarize([{ name: agentName, result }]); - if (result.kind === "skipped" && (result as { reason: string }).reason !== "not-detected") { - process.exit(1); - } -} - -function summarize( - results: { name: string; result: ConnectResult }[], -): void { - const lines = results.map(({ name, result }) => { - switch (result.kind) { - case "installed": - return ` ✓ ${name}${result.mutatedPath ? ` → ${result.mutatedPath}` : ""}`; - case "already-wired": - return ` ✓ ${name} (already wired)`; - case "stub": - return ` ⚠ ${name} (manual install required: ${result.reason})`; - case "skipped": - return ` ✗ ${name} (skipped: ${result.reason})`; - } - }); - p.note(lines.join("\n"), "summary"); - - const stubs = results.filter((r) => r.result.kind === "stub"); - if (stubs.length > 0) { - p.log.info( - `${stubs.length} agent(s) require manual install — see docs links above.`, - ); - } - - const wiredAny = results.some( - (r) => r.result.kind === "installed" || r.result.kind === "already-wired", - ); - if (wiredAny) { - p.log.info( - "Next: install agentmemory's 8 skills into the same agent(s) so they know when to call the tools:\n npx skills add MaimoryLab/agentmemory-lab -y", - ); - } - - p.outro("Restart any wired agent (or open a new session) to pick up agentmemory."); -} diff --git a/src/cli/connect/types.ts b/src/cli/connect/types.ts deleted file mode 100644 index 8abd2745..00000000 --- a/src/cli/connect/types.ts +++ /dev/null @@ -1,32 +0,0 @@ -export type ConnectOptions = { - dryRun: boolean; - force: boolean; - /** - * When true, the Codex adapter additionally writes a global - * `~/.codex/hooks.json` block referencing absolute paths to bundled hook - * scripts. Workaround for openai/codex#16430, which prevents plugin-local - * hooks from dispatching on Codex Desktop. No-op for other adapters. - */ - withHooks?: boolean; -}; - -export type ConnectAdapter = { - name: string; - displayName: string; - docs?: string; - /** - * One-line explanation of which protocol this adapter wires (REST hooks vs - * MCP) and why. Printed above the install summary so users see — before - * any config mutation — that REST is the primary surface and MCP is the - * opt-in bridge for MCP-only clients. - */ - protocolNote?: string; - detect(): boolean; - install(opts: ConnectOptions): Promise; -}; - -export type ConnectResult = - | { kind: "installed"; mutatedPath?: string; backupPath?: string } - | { kind: "already-wired"; mutatedPath?: string } - | { kind: "stub"; reason: string } - | { kind: "skipped"; reason: string }; diff --git a/src/cli/connect/util.ts b/src/cli/connect/util.ts deleted file mode 100644 index 580cd4ee..00000000 --- a/src/cli/connect/util.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { - existsSync, - mkdirSync, - readFileSync, - writeFileSync, - copyFileSync, - renameSync, -} from "node:fs"; -import { dirname, join } from "node:path"; -import { homedir } from "node:os"; -import * as p from "@clack/prompts"; - -// Env values use ${VAR:-default} expansion so the wired MCP entry -// inherits AGENTMEMORY_URL / AGENTMEMORY_SECRET / AGENTMEMORY_TOOLS -// from the user's shell, but never fails parse when the var is unset -// (#510). Earlier `${VAR}` form caused Claude Code to silently drop the -// server when no shell-level export existed — per the Claude Code MCP -// docs, "If a required environment variable is not set and has no -// default value, Claude Code will fail to parse the config." -// -// Defaults match the documented runtime: localhost:3111 (no auth, all -// tools). One wired entry now serves local AND remote (Kubernetes / -// reverse-proxied) deployments without doctor-warning duplicates (#375) -// AND fresh installs that haven't exported envs (#510). -export const AGENTMEMORY_MCP_BLOCK = { - command: "npx", - args: ["-y", "@agentmemory/mcp"], - env: { - AGENTMEMORY_URL: "${AGENTMEMORY_URL:-http://localhost:3111}", - AGENTMEMORY_SECRET: "${AGENTMEMORY_SECRET:-}", - AGENTMEMORY_TOOLS: "${AGENTMEMORY_TOOLS:-all}", - }, -}; - -const COPILOT_MCP_COMMAND = - process.platform === "win32" - ? { - command: process.env["ComSpec"] || process.env["COMSPEC"] || "cmd.exe", - args: ["/d", "/s", "/c", "npx", "-y", "@agentmemory/mcp"], - } - : { - command: "npx", - args: ["-y", "@agentmemory/mcp"], - }; - -export const AGENTMEMORY_COPILOT_MCP_BLOCK = { - type: "local" as const, - ...COPILOT_MCP_COMMAND, - env: { - AGENTMEMORY_URL: "${AGENTMEMORY_URL:-http://localhost:3111}", - AGENTMEMORY_SECRET: "${AGENTMEMORY_SECRET:-}", - AGENTMEMORY_TOOLS: "${AGENTMEMORY_TOOLS:-all}", - }, - tools: ["*"], -}; - -export function backupsDir(): string { - return join(homedir(), ".agentmemory", "backups"); -} - -export function ensureBackupsDir(): string { - const dir = backupsDir(); - mkdirSync(dir, { recursive: true }); - return dir; -} - -export function timestampSlug(): string { - return new Date().toISOString().replace(/[:.]/g, "-"); -} - -export function backupFile( - sourcePath: string, - agent: string, - ext = "json", -): string { - ensureBackupsDir(); - const stamp = timestampSlug(); - const target = join(backupsDir(), `${agent}-${stamp}.${ext}`); - copyFileSync(sourcePath, target); - return target; -} - -export function readJsonSafe(path: string): T | null { - if (!existsSync(path)) return null; - try { - return JSON.parse(readFileSync(path, "utf-8")) as T; - } catch { - return null; - } -} - -export function writeJsonAtomic(path: string, value: unknown): void { - mkdirSync(dirname(path), { recursive: true }); - const tmp = `${path}.tmp-${process.pid}-${Date.now()}`; - writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, "utf-8"); - renameSync(tmp, path); -} - -export function logInstalled(label: string, target: string): void { - p.log.success(`${label} → wired into ${target}`); -} - -export function logAlreadyWired(label: string, target: string): void { - p.log.info(`${label} already wired in ${target} (use --force to re-install)`); -} - -export function logBackup(target: string): void { - p.log.info(`Backup: ${target}`); -} diff --git a/src/cli/doctor-diagnostics.ts b/src/cli/doctor-diagnostics.ts deleted file mode 100644 index 2682a752..00000000 --- a/src/cli/doctor-diagnostics.ts +++ /dev/null @@ -1,442 +0,0 @@ -// Doctor v2 diagnostic catalog. -// -// Each entry is a self-describing diagnostic: a check function that returns -// `{ ok, detail? }`, a human-readable message, an inline fix preview, and -// an `apply` function that runs the fix. The list is exported as a pure -// data structure so unit tests can assert on shape without bringing -// @clack/prompts into the test harness. -// -// The runtime (src/cli.ts -> runDoctor) iterates the list, prompts the user -// per check, and only re-runs the SAME diagnostic after a fix — never the -// whole suite. Each fix returns `{ ok, message? }` so we can show a one-line -// outcome before moving on. -// -// Doctor v2 surface: -// agentmemory-lab doctor # interactive: Fix/Skip/More/Quit per failed check -// agentmemory-lab doctor --all # apply every available fix without prompting (CI) -// agentmemory-lab doctor --dry-run # show what each fix WOULD do; execute nothing - -export type DiagnosticStatus = { - ok: boolean; - /** Short status detail (one line). Shown alongside the check name. */ - detail?: string; -}; - -export type DiagnosticFixResult = { - ok: boolean; - message?: string; -}; - -export type DoctorContext = { - /** Base URL for the running engine, e.g. http://localhost:3111 */ - baseUrl: string; - /** Viewer URL, e.g. http://localhost:3114 */ - viewerUrl: string; - /** Path to ~/.agentmemory/.env */ - envPath: string; - /** Path to ~/.agentmemory/iii.pid */ - pidfilePath: string; - /** Path to ~/.agentmemory/engine-state.json */ - enginePath: string; - /** Pinned engine version (e.g. "0.11.2"). */ - pinnedVersion: string; -}; - -export type Diagnostic = { - /** Stable id. Used in --json and tests. */ - id: string; - /** One-line problem statement shown to the user. */ - message: string; - /** One-line description of WHAT the fix will do. Shown before the prompt. */ - fixPreview: string; - /** Longer explanation shown when the user picks [?] More info. */ - moreInfo: string; - /** Run the check; return ok=true if everything's fine, ok=false otherwise. */ - check: (ctx: DoctorContext) => Promise; - /** Apply the fix. Returns ok=true on success. */ - fix: (ctx: DoctorContext) => Promise; - /** True when there's nothing to auto-fix (we only suggest). */ - manualOnly?: boolean; -}; - -// Diagnostic ids are stable for testing and machine-readable doctor output. -export const DIAGNOSTIC_IDS = [ - "env-missing", - "no-llm-provider-key", - "todo-extractor-llm-not-ready", - "engine-version-mismatch", - "viewer-unreachable", - "stale-pidfile", - "env-placeholder-keys", - "iii-on-path-not-local-bin", -] as const; - -export type DiagnosticId = (typeof DIAGNOSTIC_IDS)[number]; - -// Pure helpers (no I/O) — exported for direct unit testing. -// --------------------------------------------------------------------------- - -/** Common placeholder values shipped in .env.example. */ -const PLACEHOLDER_VALUES = new Set([ - "", - "your-key-here", - "sk-ant-...", - "sk-...", - "changeme", - "todo", - "xxx", -]); - -const PROVIDER_KEY_NAMES = [ - "ANTHROPIC_API_KEY", - "OPENAI_API_KEY", - "GEMINI_API_KEY", - "GOOGLE_API_KEY", - "OPENROUTER_API_KEY", - "MINIMAX_API_KEY", -] as const; - -const TODO_EXTRACTOR_PLACEHOLDER_VALUES = new Set([ - "", - "your-key-here", - "sk-...", - "", - "changeme", - "todo", - "xxx", -]); - -export function parseEnvFile(content: string): Record { - const out: Record = {}; - for (const rawLine of content.split(/\r?\n/)) { - const line = rawLine.trim(); - if (!line || line.startsWith("#")) continue; - const eq = line.indexOf("="); - if (eq < 0) continue; - const key = line.slice(0, eq).trim(); - let value = line.slice(eq + 1).trim(); - // Strip surrounding quotes. - if ( - (value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'")) - ) { - value = value.slice(1, -1); - } - out[key] = value; - } - return out; -} - -/** Returns the list of provider keys that look real (non-placeholder). */ -export function realProviderKeys(env: Record): string[] { - return PROVIDER_KEY_NAMES.filter((k) => { - const v = (env[k] ?? "").trim(); - if (!v) return false; - if (PLACEHOLDER_VALUES.has(v.toLowerCase())) return false; - // Reject values that are just dots/placeholders like "xxxx-xxxx". - if (/^x+$/i.test(v.replace(/[-_]/g, ""))) return false; - return true; - }); -} - -/** Returns the list of provider key NAMES that exist but are placeholders. */ -export function placeholderProviderKeys(env: Record): string[] { - return PROVIDER_KEY_NAMES.filter((k) => { - const v = (env[k] ?? "").trim(); - if (!v) return false; - if (PLACEHOLDER_VALUES.has(v.toLowerCase())) return true; - if (/^x+$/i.test(v.replace(/[-_]/g, ""))) return true; - return false; - }); -} - -function hasRealTodoExtractorKey(env: Record): boolean { - const v = (env["LANGEXTRACT_API_KEY"] ?? "").trim(); - if (!v) return false; - if (TODO_EXTRACTOR_PLACEHOLDER_VALUES.has(v.toLowerCase())) return false; - if (/^x+$/i.test(v.replace(/[-_]/g, ""))) return false; - return true; -} - -function todoExtractorConfigProblems(env: Record): string[] { - const problems: string[] = []; - const mode = (env["AGENTMEMORY_TODO_EXTRACTOR"] || "auto").trim().toLowerCase(); - const provider = (env["LANGEXTRACT_PROVIDER"] || "").trim().toLowerCase(); - const baseUrl = (env["LANGEXTRACT_BASE_URL"] || "").trim(); - - if (mode === "rules") problems.push("AGENTMEMORY_TODO_EXTRACTOR=rules"); - if (!hasRealTodoExtractorKey(env)) problems.push("LANGEXTRACT_API_KEY missing"); - if (!provider || provider === "novita" || provider === "deepseek") { - problems.push("LANGEXTRACT_PROVIDER should be openai"); - } else if (provider !== "openai") { - problems.push(`LANGEXTRACT_PROVIDER=${provider}`); - } - if (!baseUrl) problems.push("LANGEXTRACT_BASE_URL missing"); - return problems; -} - -/** - * Build the canonical diagnostic catalog. - * - * The factory takes the side-effect helpers as injected functions so tests - * can swap them with stubs. Production callers pass real implementations - * from src/cli.ts. - */ -export type DoctorEffects = { - /** Does ~/.agentmemory/.env exist? */ - envFileExists: () => boolean; - /** Read ~/.agentmemory/.env and return parsed key=value pairs. */ - readEnvFile: () => Record; - /** Is the iii engine PID in the pidfile still alive? */ - pidfilePidIsAlive: () => boolean | null; - /** Does the pidfile exist on disk? */ - pidfileExists: () => boolean; - /** Resolve the iii binary on PATH; return null if not found. */ - findIiiBinary: () => string | null; - /** Path to ~/.local/bin/iii (the location we install to). */ - localBinIiiPath: () => string; - /** Run `iii --version`; null if it fails. */ - iiiBinaryVersion: (binPath: string) => string | null; - /** Probe the viewer URL; true if it returns OK within timeoutMs. */ - viewerReachable: (timeoutMs?: number) => Promise; - /** Run init logic (copies .env.example). */ - runInit: () => Promise; - /** Open a file in $EDITOR (or fallback). Resolves when editor exits. */ - openEditor: (path: string) => Promise; - /** Run the iii installer. */ - runIiiInstaller: () => Promise; - /** Stop the running engine cleanly. */ - runStop: () => Promise; - /** Start the engine (waits for /livez). */ - runStart: () => Promise; - /** Clear pidfile + engine-state. */ - clearEnginePidAndState: () => void; -}; - -export function buildDiagnostics(effects: DoctorEffects): Diagnostic[] { - return [ - { - id: "env-missing", - message: "~/.agentmemory/.env is missing.", - fixPreview: "Copy .env.example into ~/.agentmemory/.env (your keys file).", - moreInfo: - "AI-Todo reads its config (LANGEXTRACT_* for To-Do extraction, plus optional " + - "memory-engine keys) from ~/.agentmemory/.env. Without this file the daemon runs " + - "with defaults: the deterministic rules extractor and BM25-only search.", - check: async () => ({ - ok: effects.envFileExists(), - detail: effects.envFileExists() ? undefined : "no env file", - }), - fix: () => effects.runInit(), - }, - { - id: "no-llm-provider-key", - message: "No memory-engine LLM provider key (optional, advanced).", - fixPreview: "Optional: open ~/.agentmemory/.env in $EDITOR and paste a provider key, then re-check.", - moreInfo: - "These keys power the legacy memory engine (compression/consolidation/embeddings), " + - "which is OFF by default and NOT needed for AI-Todo's To-Do extraction — that uses " + - "LANGEXTRACT_* (see todo-extractor-llm-not-ready). Set one of ANTHROPIC_API_KEY, " + - "OPENAI_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY, MINIMAX_API_KEY only if you " + - "want the memory engine.", - manualOnly: true, - check: async () => { - if (!effects.envFileExists()) { - return { ok: false, detail: "env file missing (run env-missing fix first)" }; - } - const env = effects.readEnvFile(); - const real = realProviderKeys(env); - return { - ok: real.length > 0, - detail: real.length > 0 ? `found: ${real.join(", ")}` : "no provider key set", - }; - }, - fix: (ctx) => effects.openEditor(ctx.envPath), - }, - { - id: "todo-extractor-llm-not-ready", - message: "To-Do LLM extraction is not ready.", - fixPreview: - "Open ~/.agentmemory/.env and set LANGEXTRACT_API_KEY plus Novita/OpenAI-compatible LANGEXTRACT_* values.", - moreInfo: - "To-Do extraction uses the LANGEXTRACT_* settings (first-run setup seeds them for the " + - "model you pick). For the default path set AGENTMEMORY_TODO_EXTRACTOR=langextract, " + - "LANGEXTRACT_PROVIDER=openai, LANGEXTRACT_BASE_URL=https://api.novita.ai/openai/v1, " + - "and LANGEXTRACT_API_KEY. Skipping these keeps the deterministic rules extractor.", - manualOnly: true, - check: async () => { - if (!effects.envFileExists()) { - return { ok: false, detail: "env file missing (run env-missing fix first)" }; - } - const env = effects.readEnvFile(); - const problems = todoExtractorConfigProblems(env); - return { - ok: problems.length === 0, - detail: problems.length === 0 ? "LANGEXTRACT_* ready" : problems.join(", "), - }; - }, - fix: (ctx) => effects.openEditor(ctx.envPath), - }, - { - id: "engine-version-mismatch", - message: "iii binary on PATH doesn't match the version agentmemory pins to.", - fixPreview: - "Re-run the iii installer for the pinned version and restart the engine.", - moreInfo: - "agentmemory pins the iii engine to a specific release because newer engines " + - "use a different worker model. Running a mismatched binary surfaces as EPIPE " + - "reconnect loops and empty search results.", - check: async (ctx) => { - const bin = effects.findIiiBinary(); - if (!bin) return { ok: false, detail: "iii not on PATH" }; - const v = effects.iiiBinaryVersion(bin); - if (!v) return { ok: false, detail: "iii on PATH but --version failed" }; - return { - ok: v === ctx.pinnedVersion, - detail: `${v} (pinned ${ctx.pinnedVersion})`, - }; - }, - fix: async () => { - const r = await effects.runIiiInstaller(); - if (!r.ok) return r; - // Best-effort restart: stop then start. - await effects.runStop(); - return effects.runStart(); - }, - }, - { - id: "viewer-unreachable", - message: "Viewer port not reachable.", - fixPreview: "Stop the engine, restart it, and retry the viewer probe.", - moreInfo: - "The viewer is served on REST port + 3 (default 3114). If it never came up " + - "the most common cause is port collision; a sibling PR ships auto-bump for " + - "this case. If that lands first this check just verifies; otherwise restart " + - "the engine to retry binding.", - check: async () => ({ - ok: await effects.viewerReachable(), - detail: undefined, - }), - fix: async () => { - const stopped = await effects.runStop(); - if (!stopped.ok) return stopped; - return effects.runStart(); - }, - }, - { - id: "stale-pidfile", - message: "Stale pidfile: pid recorded but the process is gone.", - fixPreview: "Clear ~/.agentmemory/iii.pid + engine-state.json, then restart.", - moreInfo: - "When the engine crashes hard (kill -9, OOM, host reboot) the pidfile sticks " + - "around. agentmemory refuses to start a second engine on top of a stale pid, " + - "so this state must be cleared explicitly.", - check: async () => { - if (!effects.pidfileExists()) return { ok: true, detail: "no pidfile" }; - const alive = effects.pidfilePidIsAlive(); - if (alive === null) return { ok: true, detail: "pidfile unreadable" }; - return { - ok: alive, - detail: alive ? "pid is alive" : "pid is gone", - }; - }, - fix: async () => { - effects.clearEnginePidAndState(); - return effects.runStart(); - }, - }, - { - id: "env-placeholder-keys", - message: "~/.agentmemory/.env contains placeholder/empty API keys.", - fixPreview: "Open ~/.agentmemory/.env in $EDITOR to paste real values.", - moreInfo: - "Lines like ANTHROPIC_API_KEY=sk-ant-... or =your-key-here are treated as " + - "absent (these are optional memory-engine keys). Replace placeholders with real " + - "keys or comment the line out.", - check: async () => { - if (!effects.envFileExists()) { - return { ok: true, detail: "env file missing (handled by env-missing)" }; - } - const env = effects.readEnvFile(); - const placeholders = placeholderProviderKeys(env); - return { - ok: placeholders.length === 0, - detail: - placeholders.length === 0 - ? undefined - : `placeholder: ${placeholders.join(", ")}`, - }; - }, - fix: (ctx) => effects.openEditor(ctx.envPath), - }, - { - id: "iii-on-path-not-local-bin", - message: - "iii is on PATH but not in ~/.local/bin/iii (where we install).", - fixPreview: - "Suggest re-installing the pinned version via the installer — won't touch your PATH.", - moreInfo: - "agentmemory's installer writes to ~/.local/bin/iii. When a user-managed iii " + - "lives somewhere else (homebrew, cargo, $XDG_BIN) we don't auto-overwrite it. " + - "If you want our pinned build, run the installer; otherwise this is informational.", - manualOnly: true, - check: async () => { - const bin = effects.findIiiBinary(); - if (!bin) return { ok: true, detail: "iii not on PATH (handled elsewhere)" }; - const localBin = effects.localBinIiiPath(); - return { - ok: bin === localBin, - detail: bin === localBin ? undefined : `iii at: ${bin}`, - }; - }, - fix: async () => - effects.runIiiInstaller().then((r) => ({ - ok: r.ok, - message: - r.message ?? - "Installer wrote to ~/.local/bin/iii. Your PATH wasn't modified — adjust it yourself if needed.", - })), - }, - ]; -} - -export type DoctorRunMode = "interactive" | "all" | "dry-run"; - -/** - * Run all diagnostics and return their initial status (no fixes applied). - * Useful for tests and for `--dry-run` mode. - */ -export async function runAllChecks( - ctx: DoctorContext, - diagnostics: Diagnostic[], -): Promise> { - const results: Array<{ diagnostic: Diagnostic; status: DiagnosticStatus }> = []; - for (const d of diagnostics) { - const status = await d.check(ctx); - results.push({ diagnostic: d, status }); - } - return results; -} - -/** - * Dry-run output: each failing check's fix preview, prefixed by the diagnostic - * message. Pure function so we can snapshot-test the format. - */ -export function dryRunPlan( - ctx: DoctorContext, - results: Array<{ diagnostic: Diagnostic; status: DiagnosticStatus }>, -): string[] { - const lines: string[] = []; - let n = 0; - for (const { diagnostic, status } of results) { - if (status.ok) continue; - n++; - lines.push(`${n}. [${diagnostic.id}] ${diagnostic.message}`); - lines.push(` would fix: ${diagnostic.fixPreview}`); - if (status.detail) lines.push(` detail: ${status.detail}`); - } - if (lines.length === 0) { - lines.push(`All checks passing for ${ctx.baseUrl} — no fixes to run.`); - } - return lines; -} diff --git a/src/cli/onboarding.ts b/src/cli/onboarding.ts deleted file mode 100644 index 82ffe29b..00000000 --- a/src/cli/onboarding.ts +++ /dev/null @@ -1,361 +0,0 @@ -// First-run interactive onboarding flow. -// -// Wakes up only when `isFirstRun()` is true (preferences are missing or -// have never recorded a `firstRunAt`) or when the user passes -// `--reset`. The flow asks for: -// -// 1. Which agent sessions AI-Todo should scan for To-Dos (multi-select). -// 2. Which model AI-Todo uses to extract To-Dos — this seeds the -// LangExtract (`LANGEXTRACT_*`) config. "Skip" keeps the deterministic -// rules extractor (no LLM key needed). The legacy memory-compression -// provider is NOT asked here — it's an advanced `.env`-only setting. -// -// We then write preferences and `.env` under `~/.agentmemory` by default -// (or `AGENTMEMORY_HOME` when set), seeding the chosen extractor model's -// `LANGEXTRACT_*` defaults. The user adds `LANGEXTRACT_API_KEY` after. - -import { copyFile, mkdir } from "node:fs/promises"; -import { constants as fsConstants, existsSync, readFileSync, writeFileSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import * as p from "@clack/prompts"; -import { writePrefs } from "./preferences.js"; -import { resolveAdapter, runAdapter } from "./connect/index.js"; -import type { ConnectResult } from "./connect/types.js"; -import { getAgentMemoryDataDir, getUserEnvPath } from "../config.js"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -// Native plugin row — these agents ship an agentmemory plugin or -// first-party integration. Glyphs match SkillKit's published set -// where they overlap; the rest fall back to the generic `◇`. -const NATIVE_AGENTS: { value: string; label: string; glyph: string }[] = [ - { value: "codex", label: "Codex", glyph: "◎" }, -]; - -// MCP-only row — emptied by PLAN-006 P2 (the inherited MCP-agent -// connectors were removed; Codex is the sole supported target). -const MCP_AGENTS: { value: string; label: string; glyph: string }[] = []; - -type ExtractorModel = { - value: string; - label: string; - // null = "skip": keep the deterministic rules extractor (no LLM key). - // Otherwise the LANGEXTRACT_* config seeded into ~/.agentmemory/.env. - // Only the LangExtract sidecar's openai-compatible branch is offered - // (it is the only fully-wired path: api_key + base_url + reasoning_effort). - defaults: Record | null; -}; - -const SHARED_TODO_DEFAULTS: Record = { - AGENTMEMORY_TODO_EXTRACTOR: "langextract", - LANGEXTRACT_PROVIDER: "openai", - AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS: "120000", -}; - -// THINKING_DEPTH maps to the sidecar's `reasoning_effort`, which only -// non-reasoning models reject — so it's empty for gpt-4o-mini / deepseek-chat -// and "medium" for the reasoning-capable DeepSeek-V4 Flash default. -const EXTRACTOR_MODELS: ExtractorModel[] = [ - { - value: "novita", - label: "Novita · DeepSeek-V4 Flash (recommended)", - defaults: { - ...SHARED_TODO_DEFAULTS, - LANGEXTRACT_MODEL: "deepseek/deepseek-v4-flash", - LANGEXTRACT_BASE_URL: "https://api.novita.ai/openai/v1", - LANGEXTRACT_THINKING_DEPTH: "medium", - }, - }, - { - value: "openai", - label: "OpenAI · gpt-4o-mini", - defaults: { - ...SHARED_TODO_DEFAULTS, - LANGEXTRACT_MODEL: "gpt-4o-mini", - LANGEXTRACT_BASE_URL: "https://api.openai.com/v1", - LANGEXTRACT_THINKING_DEPTH: "", - }, - }, - { - value: "openrouter", - label: "OpenRouter · deepseek-chat", - defaults: { - ...SHARED_TODO_DEFAULTS, - LANGEXTRACT_MODEL: "deepseek/deepseek-chat", - LANGEXTRACT_BASE_URL: "https://openrouter.ai/api/v1", - LANGEXTRACT_THINKING_DEPTH: "", - }, - }, - { - value: "skip", - label: "Skip — rules-only extraction (no LLM key)", - defaults: null, - }, -]; - -export function buildAgentOptions(): { value: string; label: string; hint?: string }[] { - return [ - ...NATIVE_AGENTS.map((a) => ({ - value: a.value, - label: `${a.glyph} ${a.label}`, - hint: "native plugin", - })), - ...MCP_AGENTS.map((a) => ({ - value: a.value, - label: `${a.glyph} ${a.label}`, - hint: "MCP server", - })), - ]; -} - -export function getInitialAgentValues( - _env: Record = process.env, -): string[] { - // Codex is the only wireable agent after PLAN-006 P2 removed the - // inherited connector stack. - return ["codex"]; -} - -// Mirror src/cli.ts findEnvExample so onboarding ships the same .env -// skeleton whether called directly or via `agentmemory-lab init`. We -// duplicate (rather than import) so the onboarding module doesn't -// pull cli.ts's top-level side effects into the test runner. -function findEnvExample(): string | null { - const candidates = [ - join(__dirname, "..", "..", ".env.example"), - join(__dirname, "..", ".env.example"), - join(__dirname, ".env.example"), - join(process.cwd(), ".env.example"), - ]; - for (const c of candidates) { - if (existsSync(c)) return c; - } - return null; -} - -async function seedEnvFile(): Promise { - const target = getUserEnvPath(); - const dir = dirname(target); - await mkdir(dir, { recursive: true }); - - const template = findEnvExample(); - if (template && !existsSync(target)) { - try { - await copyFile(template, target, fsConstants.COPYFILE_EXCL); - } catch (err) { - if ((err as NodeJS.ErrnoException)?.code !== "EEXIST") { - return null; - } - } - } else if (!template && !existsSync(target)) { - // Fall back to a minimal skeleton so users always get a `.env` to edit. - writeFileSync( - target, - [ - "# AI-Todo environment — uncomment what you need", - "# AGENTMEMORY_URL=http://localhost:3111", - "", - ].join("\n"), - { mode: 0o600 }, - ); - } - - return target; -} - -function enableTodoExtractionDefaults(envPath: string, defaults: Record): void { - const current = existsSync(envPath) ? readFileSync(envPath, "utf-8") : ""; - const keys = new Set( - current - .split("\n") - .map((line) => line.match(/^\s*([A-Z0-9_]+)\s*=/)?.[1]) - .filter((key): key is string => !!key), - ); - const additions = Object.entries(defaults) - .filter(([key]) => !keys.has(key)) - .map(([key, value]) => `${key}=${value}`); - if (!additions.length) return; - writeFileSync(envPath, `${current.replace(/\n*$/, "\n")}${additions.join("\n")}\n`, { mode: 0o600 }); -} - -export interface OnboardingResult { - agents: string[]; - provider: string | null; -} - -function shouldSkipInteractiveOnboarding(): boolean { - const ci = process.env["CI"]; - return ( - process.stdin.isTTY !== true || - process.stdout.isTTY !== true || - (ci !== undefined && ci !== "" && ci !== "0" && ci.toLowerCase() !== "false") - ); -} - -function writeDefaultOnboardingPrefs(): OnboardingResult { - writePrefs({ - lastAgent: null, - lastAgents: [], - lastProvider: null, - skipSplash: true, - firstRunAt: new Date().toISOString(), - }); - return { agents: [], provider: null }; -} - -export async function runOnboarding(): Promise { - if (shouldSkipInteractiveOnboarding()) { - return writeDefaultOnboardingPrefs(); - } - - p.note( - [ - "Welcome to AI-Todo.", - "", - "Local-first extraction of unfinished To-Dos from your AI agent", - "sessions, with evidence, in a local web UI. We'll pick which agent", - "sessions to scan and which model extracts your To-Dos.", - ].join("\n"), - "first-run setup", - ); - - const agentsPicked = await p.multiselect({ - message: "Which agents should AI-Todo scan for To-Dos? (space to toggle, enter to confirm)", - options: buildAgentOptions(), - required: false, - initialValues: getInitialAgentValues(), - }); - if (p.isCancel(agentsPicked)) { - p.cancel("Setup cancelled. Re-run any time with: agentmemory --reset"); - process.exit(0); - } - - const pickedAgentsList = (agentsPicked as string[]) ?? []; - if (pickedAgentsList.length > 0) { - p.note( - [ - "━ how this works ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", - "AI-Todo reads these agents' sessions locally and surfaces", - "unfinished To-Dos — with evidence — in the web UI at :3111.", - "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", - ].join("\n"), - ); - } - - const modelPicked = await p.select({ - message: - "Which model should AI-Todo use to extract To-Dos? (add the API key later in Settings or ~/.agentmemory/.env)", - options: EXTRACTOR_MODELS.map(({ value, label }) => ({ value, label })), - initialValue: "novita", - }); - if (p.isCancel(modelPicked)) { - p.cancel("Setup cancelled. Re-run any time with: agentmemory --reset"); - process.exit(0); - } - - const chosenModel = EXTRACTOR_MODELS.find((m) => m.value === modelPicked) ?? null; - const provider = modelPicked === "skip" ? null : modelPicked; - const agents = (agentsPicked as string[]) ?? []; - - const envPath = await seedEnvFile(); - if (chosenModel?.defaults && envPath) { - enableTodoExtractionDefaults(envPath, chosenModel.defaults); - } - - writePrefs({ - lastAgent: agents[0] ?? null, - lastAgents: agents, - lastProvider: provider, - skipSplash: true, - firstRunAt: new Date().toISOString(), - }); - - const prefsLocation = join(getAgentMemoryDataDir(), "preferences.json"); - const lines = [`✓ Saved preferences to ${prefsLocation}`]; - if (envPath) { - lines.push(`✓ Wrote ${envPath}`); - } else { - lines.push(`! Could not write ~/.agentmemory/.env — run \`agentmemory-lab init\` after this completes.`); - } - if (chosenModel?.defaults) { - lines.push(` To-Do extraction → ${chosenModel.label}. Add LANGEXTRACT_API_KEY in Settings or ~/.agentmemory/.env before running it.`); - } else { - lines.push(" No model chosen — AI-Todo extracts To-Dos with the deterministic rules extractor (no LLM key needed)."); - } - p.note(lines.join("\n"), "ready"); - - if (agents.length > 0) { - await wireSelectedAgents(agents); - } - - return { agents, provider }; -} - -async function wireSelectedAgents(agents: string[]): Promise { - p.note("Wire selected agents now?", "next step"); - const confirmed = await p.confirm({ - message: "Run `agentmemory connect ` for each selected agent now? [Y/n]", - initialValue: true, - }); - - if (p.isCancel(confirmed) || confirmed === false) { - const cmds = agents.map((a) => ` agentmemory connect ${a}`); - p.note(["Wire later with:", ...cmds].join("\n"), "later"); - return; - } - - const wired: string[] = []; - const manual: { name: string; docs?: string }[] = []; - const failed: { name: string; reason: string }[] = []; - - for (const name of agents) { - const adapter = resolveAdapter(name); - if (!adapter) { - failed.push({ name, reason: "no adapter available" }); - p.log.warn(`Wiring ${name}… no adapter available (skipped).`); - continue; - } - p.log.step(`Wiring ${name}...`); - let result: ConnectResult; - try { - result = await runAdapter(adapter, { dryRun: false, force: false }); - } catch (err) { - const reason = err instanceof Error ? err.message : String(err); - failed.push({ name, reason }); - p.log.error(`${name}: ${reason}`); - continue; - } - switch (result.kind) { - case "installed": - case "already-wired": - wired.push(name); - break; - case "stub": - manual.push({ name, docs: adapter.docs }); - break; - case "skipped": - failed.push({ name, reason: result.reason }); - break; - } - } - - const summary: string[] = []; - if (wired.length > 0) { - summary.push(`Wired: ${wired.join(", ")}.`); - } - if (manual.length > 0 || failed.length > 0) { - const parts: string[] = []; - for (const m of manual) { - parts.push(`${m.name} (manual install required${m.docs ? ` — see ${m.docs}` : ""})`); - } - for (const f of failed) { - parts.push(`${f.name} (${f.reason})`); - } - summary.push(`Skipped/failed: ${parts.join(", ")}.`); - } - if (summary.length === 0) { - summary.push("No agents were wired."); - } - p.note(summary.join("\n"), "wire summary"); -} diff --git a/src/cli/preferences.ts b/src/cli/preferences.ts deleted file mode 100644 index b1701955..00000000 --- a/src/cli/preferences.ts +++ /dev/null @@ -1,140 +0,0 @@ -// JSON-backed CLI preferences. -// -// Lives at `~/.agentmemory/preferences.json` by default, or under -// `AGENTMEMORY_HOME` when set. The agentmemory daemon already owns -// `.env`, `iii.pid`, `engine-state.json` there — adding one more sibling -// keeps the install-state surface in one place. -// -// All functions are synchronous, mirroring the pidfile / engine-state -// helpers in src/cli.ts. We never throw: read failures collapse to -// defaults; write failures swallow silently. Preferences are a UX -// nicety, not data — corrupting `iii.pid` matters, corrupting this -// file does not. -// -// Writes are atomic via tmp + rename so a Ctrl+C between the open and -// the final write can't leave a half-written JSON blob on disk that -// the next read would refuse to parse. - -import { - closeSync, - existsSync, - fsyncSync, - mkdirSync, - openSync, - readFileSync, - renameSync, - unlinkSync, - writeSync, -} from "node:fs"; -import { join } from "node:path"; -import { getAgentMemoryDataDir } from "../config.js"; - -export interface Prefs { - schemaVersion: 1; - // Most recently picked single agent (for "use last agent" style flows). - lastAgent: string | null; - // The full multi-select set from the last onboarding run. - lastAgents: string[]; - // Most recently picked LLM provider; `null` means BM25-only mode. - lastProvider: string | null; - // Once true, splash is rendered only on first run / explicit --reset. - // The first onboarding sets this to true so the second invocation - // skips the banner. - skipSplash: boolean; - // Reserved for a later "do not nag me about the npx vs install - // tradeoff" toggle. Kept on the schema so we don't have to bump - // schemaVersion when we ship the flag. - skipNpxHint: boolean; - // Set to true when the user declines the "install agentmemory - // globally?" prompt on first npx run. We never ask again on this - // machine so the prompt stays a one-time DX nudge, not a nag. - skipGlobalInstall: boolean; - // Set to true when the user declines the "install iii console?" - // prompt. iii console is first-class engine UI but optional at the - // install step — once the user says no, we stop asking. - skipConsoleInstall: boolean; - // ISO timestamp of the first time onboarding completed. Set once, - // never updated, so we can show "you joined agentmemory N days ago" - // copy in /status later without keeping a separate file. - firstRunAt: string | null; -} - -const DEFAULTS: Prefs = { - schemaVersion: 1, - lastAgent: null, - lastAgents: [], - lastProvider: null, - skipSplash: false, - skipNpxHint: false, - skipGlobalInstall: false, - skipConsoleInstall: false, - firstRunAt: null, -}; - -export function prefsDir(): string { - return getAgentMemoryDataDir(); -} - -export function prefsPath(): string { - return join(prefsDir(), "preferences.json"); -} - -export function readPrefs(): Prefs { - try { - if (!existsSync(prefsPath())) return { ...DEFAULTS }; - const raw = readFileSync(prefsPath(), "utf-8"); - const parsed = JSON.parse(raw) as Partial; - return { ...DEFAULTS, ...parsed, schemaVersion: 1 }; - } catch { - return { ...DEFAULTS }; - } -} - -export function writePrefs(p: Partial): void { - try { - const dir = prefsDir(); - mkdirSync(dir, { recursive: true }); - const current = readPrefs(); - const next: Prefs = { ...current, ...p, schemaVersion: 1 }; - const target = prefsPath(); - const tmp = target + ".tmp"; - // Open + write + fsync + rename ensures a Ctrl+C between any two - // syscalls either leaves the old file intact (rename is atomic on - // POSIX) or leaves only a .tmp behind that the next writePrefs - // overwrites. We never end up with a truncated `preferences.json` - // that readPrefs would have to discard. - const fd = openSync(tmp, "w", 0o600); - try { - writeSync(fd, JSON.stringify(next, null, 2) + "\n"); - try { - fsyncSync(fd); - } catch { - // fsync isn't available on every filesystem (e.g. some Docker - // overlays). The rename below is still atomic; we just can't - // guarantee durability against a power loss. - } - } finally { - closeSync(fd); - } - renameSync(tmp, target); - } catch { - // Preferences are best-effort. Don't crash the CLI for them. - } -} - -export function resetPrefs(): void { - try { - unlinkSync(prefsPath()); - } catch { - // Already gone — that's exactly the state we wanted. - } -} - -export function isFirstRun(): boolean { - // "First run" means: the preferences file doesn't exist OR exists - // but `firstRunAt` was never recorded. The latter handles users who - // had `.agentmemory/preferences.json` from a much older agentmemory - // build that wrote a different schema — we treat them as new. - if (!existsSync(prefsPath())) return true; - return readPrefs().firstRunAt === null; -} diff --git a/src/cli/remove-plan.ts b/src/cli/remove-plan.ts deleted file mode 100644 index 223d94c5..00000000 --- a/src/cli/remove-plan.ts +++ /dev/null @@ -1,248 +0,0 @@ -// `agentmemory remove` — destruction plan. -// -// Generating the plan is a pure function of the on-disk state (which files -// exist, whether ~/.local/bin/iii matches the version we installed, the -// connect-manifest contents). All side effects live in src/cli.ts; this -// module owns only the planning logic so it's unit-testable without -// touching $HOME. -// -// CLI surface: -// agentmemory remove # interactive, double-confirms -// agentmemory remove --force # skip confirmations -// agentmemory remove --keep-data # remove binaries+symlinks, keep memory data - -import { existsSync, statSync } from "node:fs"; -import { join } from "node:path"; - -export type RemovePlanItem = { - /** Stable id, used in tests and CLI output. */ - id: string; - /** Human-readable description of the action. */ - description: string; - /** Absolute path being acted on (or null for non-fs actions). */ - path: string | null; - /** Whether this item is `ask-again` even with --force (e.g. memory data). */ - alwaysAsk: boolean; - /** Whether the file actually exists / action is meaningful. Plan-time hint. */ - applicable: boolean; - /** Bytes (for files) or -1 (unknown / dir). Pure metadata. */ - sizeBytes: number; -}; - -export type RemoveOptions = { - /** Skip confirmations (still asks separately about always-ask items). */ - force: boolean; - /** Keep ~/.agentmemory/* user data; only remove binaries/symlinks. */ - keepData: boolean; -}; - -export type RemoveContext = { - /** $HOME (so tests can sandbox). */ - home: string; - /** Pinned engine version we expect ~/.local/bin/iii to match. */ - pinnedVersion: string; - /** - * `iii --version` result for ~/.local/bin/iii, or null if it's missing / - * unreadable / not executable. Passed in so the plan module stays pure. - */ - localBinIiiVersion: string | null; - /** Loaded connect manifest, or null if missing. */ - connectManifest: ConnectManifest | null; -}; - -/** - * The `agentmemory connect` PR writes this manifest at - * ~/.agentmemory/backups/connect-manifest.json. We tolerate it being absent - * (older versions, fresh installs) by treating it as `{ installed: [] }`. - */ -export type ConnectManifest = { - installed: Array<{ - /** Target path the connect command wrote (symlink or file). */ - target: string; - /** Agent label, e.g. "claude-code", "cursor". */ - agent?: string; - /** Whether this was a symlink (true) or copy (false). */ - symlink?: boolean; - }>; -}; - -export function pidfilePath(home: string): string { - return join(home, ".agentmemory", "iii.pid"); -} - -export function enginePath(home: string): string { - return join(home, ".agentmemory", "engine-state.json"); -} - -export function envPath(home: string): string { - return join(home, ".agentmemory", ".env"); -} - -export function preferencesPath(home: string): string { - return join(home, ".agentmemory", "preferences.json"); -} - -export function backupsDir(home: string): string { - return join(home, ".agentmemory", "backups"); -} - -export function dataDir(home: string): string { - return join(home, ".agentmemory", "data"); -} - -export function localBinIii(home: string): string { - return join(home, ".local", "bin", "iii"); -} - -function safeSize(path: string): number { - try { - return statSync(path).size; - } catch { - return -1; - } -} - -function pathExists(path: string): boolean { - try { - return existsSync(path); - } catch { - return false; - } -} - -/** - * Build the destruction plan for `agentmemory remove`. - * - * Plan items are returned regardless of whether `applicable` is true — the - * caller can decide whether to skip-and-log or hide entirely. This keeps - * the structure stable for tests. - */ -export function buildRemovePlan( - ctx: RemoveContext, - options: RemoveOptions, -): RemovePlanItem[] { - const { home, pinnedVersion, localBinIiiVersion, connectManifest } = ctx; - const plan: RemovePlanItem[] = []; - - plan.push({ - id: "stop-engine", - description: "Stop running iii-engine (if any) cleanly", - path: null, - alwaysAsk: false, - applicable: pathExists(pidfilePath(home)) || pathExists(enginePath(home)), - sizeBytes: -1, - }); - - plan.push({ - id: "pidfile", - description: "Delete pidfile", - path: pidfilePath(home), - alwaysAsk: false, - applicable: pathExists(pidfilePath(home)), - sizeBytes: safeSize(pidfilePath(home)), - }); - - plan.push({ - id: "engine-state", - description: "Delete engine-state.json", - path: enginePath(home), - alwaysAsk: false, - applicable: pathExists(enginePath(home)), - sizeBytes: safeSize(enginePath(home)), - }); - - // .env holds the user's API keys. Always ask before deleting, even on - // --force. --keep-data keeps it as part of "user data". - plan.push({ - id: "env", - description: "Delete .env (your API keys) — will ask separately", - path: envPath(home), - alwaysAsk: true, - applicable: !options.keepData && pathExists(envPath(home)), - sizeBytes: safeSize(envPath(home)), - }); - - plan.push({ - id: "preferences", - description: "Delete preferences.json", - path: preferencesPath(home), - alwaysAsk: false, - applicable: !options.keepData && pathExists(preferencesPath(home)), - sizeBytes: safeSize(preferencesPath(home)), - }); - - plan.push({ - id: "backups", - description: "Delete backups/ directory (connect manifest + backups)", - path: backupsDir(home), - alwaysAsk: false, - applicable: !options.keepData && pathExists(backupsDir(home)), - sizeBytes: -1, - }); - - // Iterate over connect-installed agent symlinks. We always honor these - // (even with --keep-data, since they're outside ~/.agentmemory/). - if (connectManifest?.installed?.length) { - for (const entry of connectManifest.installed) { - plan.push({ - id: `connect:${entry.target}`, - description: `Remove agent connection (${entry.agent ?? "unknown"})`, - path: entry.target, - alwaysAsk: false, - applicable: pathExists(entry.target), - sizeBytes: safeSize(entry.target), - }); - } - } - - // ~/.local/bin/iii — only remove if it matches the version we installed. - // Heuristic: spawn `iii --version`; if it returns pinnedVersion, safe to - // remove. Otherwise mark `alwaysAsk` so the operator confirms explicitly. - const localIii = localBinIii(home); - if (pathExists(localIii)) { - const matches = localBinIiiVersion === pinnedVersion; - plan.push({ - id: "local-bin-iii", - description: matches - ? `Delete ~/.local/bin/iii (matches pinned v${pinnedVersion})` - : `Delete ~/.local/bin/iii (version ${localBinIiiVersion ?? "unknown"} != pinned v${pinnedVersion}) — will ask`, - path: localIii, - alwaysAsk: !matches, - applicable: true, - sizeBytes: safeSize(localIii), - }); - } - - // Memory data dir — ALWAYS asks separately, even with --force. Default - // behavior is keep. - plan.push({ - id: "data-dir", - description: - "Delete memory data directory (~/.agentmemory/data/) — will ask separately", - path: dataDir(home), - alwaysAsk: true, - applicable: !options.keepData && pathExists(dataDir(home)), - sizeBytes: -1, - }); - - return plan; -} - -/** Format a plan for the user — one line per item. */ -export function formatPlan(plan: RemovePlanItem[]): string { - return plan - .filter((p) => p.applicable) - .map((p, i) => { - const tag = p.alwaysAsk ? " [asks]" : ""; - const sz = - p.sizeBytes > 0 ? ` (${humanBytes(p.sizeBytes)})` : ""; - return ` ${i + 1}. ${p.description}${tag}${sz}${p.path ? `\n ${p.path}` : ""}`; - }) - .join("\n"); -} - -function humanBytes(n: number): string { - if (n < 1024) return `${n} B`; - if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`; - return `${(n / (1024 * 1024)).toFixed(1)} MB`; -} diff --git a/src/cli/splash.ts b/src/cli/splash.ts deleted file mode 100644 index ead02fb4..00000000 --- a/src/cli/splash.ts +++ /dev/null @@ -1,85 +0,0 @@ -// Terminal-width-aware splash banner for the AI-Todo CLI. -// -// Three render tiers, picked from `process.stdout.columns`: -// -// >= 120 cols: full block-art logo + tagline. -// 80–119 cols: compact monospace title + tagline. -// < 80 cols: single-line `ai-todo v`. -// -// The brand accent is the orange `#FF6B35` we already use in the README -// and viewer; we render it through ANSI 38;5;208 (the closest xterm-256 -// match) when stdout is a TTY, and fall back to plain text otherwise. -// No colour bytes are hard-coded into the strings themselves so that -// piping the banner to a file (`ai-todo > log`) stays clean. -// -// We don't pull in chalk/picocolors — picocolors is a transitive dep but -// we never want to depend on transitives directly. The two ANSI escape -// helpers below are the entire colour surface and they degrade to -// no-ops automatically. - -const IS_COLOR_TTY = !!process.stdout.isTTY && !process.env["NO_COLOR"]; - -function accent(s: string): string { - // 256-colour orange that visually matches #FF6B35 in most modern - // terminal palettes. We pick 208 (a true orange) over the closer-but- - // pinker 209 because it reads as the brand colour on both dark and - // light backgrounds. - return IS_COLOR_TTY ? `\x1b[38;5;208m${s}\x1b[0m` : s; -} - -function dim(s: string): string { - return IS_COLOR_TTY ? `\x1b[2m${s}\x1b[22m` : s; -} - -function bold(s: string): string { - return IS_COLOR_TTY ? `\x1b[1m${s}\x1b[22m` : s; -} - -function getTerminalWidth(): number { - const w = process.stdout.columns; - return typeof w === "number" && w > 0 ? w : 80; -} - -const TAGLINE = "Surfaces unfinished to-dos from your AI agent sessions"; - -// "ai-todo" rendered in figlet's standard font (verified output — -// regenerate via `figlet ai-todo` if you change the wordmark). Each -// row is exactly 37 columns wide so the banner aligns cleanly inside -// the 2-col left margin we add below. -function fullBanner(version: string): string { - const logo = [ - " _ _ _ ", - " __ _(_) | |_ ___ __| | ___ ", - " / _` | |_____| __/ _ \\ / _` |/ _ \\ ", - " | (_| | |_____| || (_) | (_| | (_) |", - " \\__,_|_| \\__\\___/ \\__,_|\\___/ ", - ]; - const lines: string[] = ["", ...logo.map((line) => " " + accent(line))]; - lines.push(""); - lines.push(" " + bold(TAGLINE) + " " + dim(`v${version}`)); - lines.push(""); - return lines.join("\n"); -} - -function compactBanner(version: string): string { - const title = " " + bold(accent("ai-todo")); - const meta = " " + dim(`v${version} · ${TAGLINE}`); - return ["", title, meta, ""].join("\n"); -} - -function minimalBanner(version: string): string { - return `${accent("ai-todo")} ${dim(`v${version}`)}`; -} - -export function renderSplash(version: string): void { - const width = getTerminalWidth(); - let out: string; - if (width >= 120) { - out = fullBanner(version); - } else if (width >= 80) { - out = compactBanner(version); - } else { - out = minimalBanner(version); - } - process.stdout.write(out + "\n"); -} diff --git a/src/config.ts b/src/config.ts index ac3622ad..a50f51b9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,602 +1,457 @@ -import { spawnSync } from "node:child_process"; -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; -import { dirname, join, resolve } from "node:path"; +import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { homedir } from "node:os"; -import { fileURLToPath } from "node:url"; -import type { - AgentMemoryConfig, - ProviderConfig, - EmbeddingConfig, - FallbackConfig, - ClaudeBridgeConfig, - TeamConfig, -} from "./types.js"; - -function safeParseInt(value: string | undefined, fallback: number): number { - if (!value) return fallback; - const parsed = parseInt(value, 10); - return Number.isNaN(parsed) ? fallback : parsed; -} - -export function getAgentMemoryDataDir(): string { - return process.env["AGENTMEMORY_HOME"] || join(homedir(), ".agentmemory"); -} - -const DATA_DIR = getAgentMemoryDataDir(); -const ENV_FILE = join(DATA_DIR, ".env"); -export const DEFAULT_LANGEXTRACT_MODEL = "deepseek/deepseek-v4-flash"; -export const DEFAULT_LANGEXTRACT_PROVIDER = "openai"; -export const DEFAULT_LANGEXTRACT_BASE_URL = "https://api.novita.ai/openai/v1"; -export const DEFAULT_TODO_EXTRACT_TIMEOUT_MS = 120_000; -// STEP-11: extraction scope. sinceDays = only sessions whose endedAt/startedAt -// falls within the last N days are eligible (the primary scope control). Max -// interactions = per session, keep at most M most-recent interaction records -// (a "turn": one user message through everything before the next). maxSessions -// stays a backend safety cap (not surfaced as a setting). -export const DEFAULT_TODO_EXTRACT_SINCE_DAYS = 7; -export const DEFAULT_TODO_EXTRACT_MAX_INTERACTIONS = 10; -// Interactive safety cap on how many sessions one extraction pass touches. The -// day window is the primary control, but with the LLM extractor each session is -// a serial sidecar call (up to the per-call timeout), so a single "organize" -// click must stay bounded. REST callers can still pass a larger maxSessions. -export const DEFAULT_TODO_EXTRACT_MAX_SESSIONS = 8; -const LEGACY_LANGEXTRACT_MODELS = new Set(["pa/gpt-5.5"]); -export const WRITABLE_TODO_EXTRACT_KEYS = new Set([ - "AGENTMEMORY_TODO_EXTRACTOR", - "LANGEXTRACT_PYTHON", - "LANGEXTRACT_MODEL", - "LANGEXTRACT_PROVIDER", - "LANGEXTRACT_API_KEY", - "LANGEXTRACT_BASE_URL", - "LANGEXTRACT_THINKING_DEPTH", - "AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS", - "AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS", - "AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION", -]); - -let warnPremiumModelShown = false; - -function loadEnvFile(): Record { - if (!existsSync(ENV_FILE)) return {}; - const content = readFileSync(ENV_FILE, "utf-8"); - const vars: Record = {}; - for (const line of content.split("\n")) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith("#")) continue; - const eqIdx = trimmed.indexOf("="); - if (eqIdx === -1) continue; - const key = trimmed.slice(0, eqIdx).trim(); - let val = trimmed.slice(eqIdx + 1).trim(); - const quoteChar = val[0] === '"' || val[0] === "'" ? val[0] : ""; - if (quoteChar) { - const closeIdx = val.indexOf(quoteChar, 1); - if (closeIdx !== -1) val = val.slice(1, closeIdx); - } else { - const hashIdx = val.indexOf(" #"); - if (hashIdx !== -1) val = val.slice(0, hashIdx).trim(); +import { join } from "node:path"; +import type { AppPaths } from "./paths.js"; + +export const DEFAULT_LLM_MODEL = "deepseek/deepseek-v4-flash"; +export const DEFAULT_LLM_PROVIDER = "openai"; +export const DEFAULT_LLM_ENDPOINT = "https://api.novita.ai/openai/v1"; +export const DEFAULT_LLM_TIMEOUT_MS = 120000; +export const DEFAULT_ORGANIZE_SINCE_DAYS = 7; +export const DEFAULT_ORGANIZE_MAX_INTERACTIONS_PER_SESSION = 10; +export const DEFAULT_ORGANIZE_MAX_SESSIONS = 16; +export const DEFAULT_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION = 40; + +const DEFAULT_CODEX_HOME = join(homedir(), ".codex"); +const DEFAULT_CLAUDE_HOME = join(homedir(), ".claude", "projects"); +const IGNORED_ENV_KEYS = new Set(["AI_TODO_LLM_" + "PYTHON"]); + +export interface AppConfig { + sources: { + codex: { path?: string }; + "claude-code": { path?: string }; + }; + llm: { + enabled: boolean; + provider: "openai"; + model: string; + endpoint: string; + thinkingDepth: "low" | "medium" | "high"; + timeoutMs: number; + }; + organize: { + sinceDays: number; + maxInteractionsPerSession: number; + maxSessions: number; + maxObservationsPerSession: number; + }; +} + +export interface AppSecrets { + llmApiKey?: string; +} + +export type PublicAppConfig = AppConfig & { + llm: AppConfig["llm"] & { + apiKeyConfigured: boolean; + apiKeyMasked: string; + }; +}; + +export const WRITABLE_ENV_KEYS = [ + "AI_TODO_CODEX_HOME", + "AI_TODO_CLAUDE_HOME", + "AI_TODO_LLM_ENABLED", + "AI_TODO_LLM_PROVIDER", + "AI_TODO_LLM_MODEL", + "AI_TODO_LLM_ENDPOINT", + "AI_TODO_LLM_THINKING_DEPTH", + "AI_TODO_LLM_TIMEOUT_MS", + "AI_TODO_LLM_API_KEY", + "AI_TODO_ORGANIZE_SINCE_DAYS", + "AI_TODO_ORGANIZE_MAX_INTERACTIONS_PER_SESSION", + "AI_TODO_ORGANIZE_MAX_SESSIONS", + "AI_TODO_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION" +] as const; + +export type WritableEnvKey = typeof WRITABLE_ENV_KEYS[number]; +export type EnvConfig = Partial>; + +export function defaultConfig(): AppConfig { + return { + sources: { + codex: {}, + "claude-code": {} + }, + llm: { + enabled: true, + provider: DEFAULT_LLM_PROVIDER, + model: DEFAULT_LLM_MODEL, + endpoint: DEFAULT_LLM_ENDPOINT, + thinkingDepth: "medium", + timeoutMs: DEFAULT_LLM_TIMEOUT_MS + }, + organize: { + sinceDays: DEFAULT_ORGANIZE_SINCE_DAYS, + maxInteractionsPerSession: DEFAULT_ORGANIZE_MAX_INTERACTIONS_PER_SESSION, + maxSessions: DEFAULT_ORGANIZE_MAX_SESSIONS, + maxObservationsPerSession: DEFAULT_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION } - vars[key] = val; + }; +} + +export function defaultEnvConfig(includeApiKey?: string): EnvConfig { + return sanitizeEnvConfig({ + AI_TODO_CODEX_HOME: DEFAULT_CODEX_HOME, + AI_TODO_CLAUDE_HOME: DEFAULT_CLAUDE_HOME, + AI_TODO_LLM_ENABLED: "true", + AI_TODO_LLM_PROVIDER: DEFAULT_LLM_PROVIDER, + AI_TODO_LLM_MODEL: DEFAULT_LLM_MODEL, + AI_TODO_LLM_ENDPOINT: DEFAULT_LLM_ENDPOINT, + AI_TODO_LLM_THINKING_DEPTH: "medium", + AI_TODO_LLM_TIMEOUT_MS: String(DEFAULT_LLM_TIMEOUT_MS), + AI_TODO_LLM_API_KEY: includeApiKey, + AI_TODO_ORGANIZE_SINCE_DAYS: String(DEFAULT_ORGANIZE_SINCE_DAYS), + AI_TODO_ORGANIZE_MAX_INTERACTIONS_PER_SESSION: String(DEFAULT_ORGANIZE_MAX_INTERACTIONS_PER_SESSION), + AI_TODO_ORGANIZE_MAX_SESSIONS: String(DEFAULT_ORGANIZE_MAX_SESSIONS), + AI_TODO_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION: String(DEFAULT_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION) + }); +} + +export function loadConfig(paths: AppPaths): AppConfig { + const jsonConfig = loadJsonConfig(paths); + return parseConfig(applyEnvConfig(jsonConfig, loadEnvConfig(paths))); +} + +export function saveConfig(paths: AppPaths, config: AppConfig): void { + mkdirSync(paths.configDir, { recursive: true }); + writeFileSync(paths.configPath, `${JSON.stringify(parseConfig(config), null, 2)}\n`); +} + +export function loadSecrets(paths: AppPaths): AppSecrets { + const env = loadEnvConfig(paths); + if (env.AI_TODO_LLM_API_KEY) return { llmApiKey: env.AI_TODO_LLM_API_KEY }; + if (!existsSync(paths.secretsPath)) return {}; + try { + return parseSecrets(JSON.parse(readFileSync(paths.secretsPath, "utf8"))); + } catch { + throw new Error("secrets_invalid"); } - return vars; } -export function getUserEnvPath(): string { - return ENV_FILE; +export function saveSecrets(paths: AppPaths, secrets: AppSecrets): void { + if (existsSync(paths.envPath)) { + const env = loadEnvConfig(paths); + if (secrets.llmApiKey) env.AI_TODO_LLM_API_KEY = secrets.llmApiKey; + else delete env.AI_TODO_LLM_API_KEY; + saveEnvConfig(paths, env); + return; + } + mkdirSync(paths.configDir, { recursive: true }); + const parsed = parseSecrets(secrets); + writeFileSync(paths.secretsPath, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 0o600 }); } -function maskSecret(value: string | undefined): string { - if (!hasRealValue(value)) return ""; - const secret = value.trim(); - if (secret.length <= 8) return `${secret.slice(0, 2)}****${secret.slice(-2)}`; - return `${secret.slice(0, 3)}****${secret.slice(-4)}`; +export function loadEnvConfig(paths: AppPaths): EnvConfig { + if (!existsSync(paths.envPath)) return {}; + return parseEnvFile(readFileSync(paths.envPath, "utf8")); +} + +export function saveEnvConfig(paths: AppPaths, env: EnvConfig): void { + mkdirSync(paths.configDir, { recursive: true }); + const sanitized = sanitizeEnvConfig(env); + writeFileSync(paths.envPath, `${formatEnvFile(sanitized)}\n`, { mode: 0o600 }); + chmodSync(paths.envPath, 0o600); +} + +export function ensureDefaultEnv(paths: AppPaths, overrides: EnvConfig = {}): EnvConfig { + const env = existsSync(paths.envPath) + ? { ...defaultEnvConfig(), ...loadEnvConfig(paths), ...sanitizeEnvConfig(overrides) } + : { ...defaultEnvConfig(), ...sanitizeEnvConfig(overrides) }; + saveEnvConfig(paths, env); + return env; } -export function getTodoExtractorUserConfig(): Record { - const env = getMergedEnv(); - const runtime = detectLangExtractRuntime(env); +export function publicConfig(config: AppConfig, secrets: AppSecrets): PublicAppConfig { return { - AGENTMEMORY_TODO_EXTRACTOR: env["AGENTMEMORY_TODO_EXTRACTOR"] || "auto", - LANGEXTRACT_PYTHON: resolveLangExtractPython(env), - LANGEXTRACT_MODEL: normalizeTodoExtractorModel(env["LANGEXTRACT_MODEL"]), - LANGEXTRACT_PROVIDER: normalizeTodoExtractorProvider(env["LANGEXTRACT_PROVIDER"]), - LANGEXTRACT_BASE_URL: env["LANGEXTRACT_BASE_URL"] || DEFAULT_LANGEXTRACT_BASE_URL, - LANGEXTRACT_THINKING_DEPTH: env["LANGEXTRACT_THINKING_DEPTH"] || "medium", - AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS: - env["AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS"] || String(DEFAULT_TODO_EXTRACT_TIMEOUT_MS), - AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS: - env["AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS"] || String(DEFAULT_TODO_EXTRACT_SINCE_DAYS), - AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION: - env["AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION"] || String(DEFAULT_TODO_EXTRACT_MAX_INTERACTIONS), - LANGEXTRACT_API_KEY_CONFIGURED: hasRealValue(env["LANGEXTRACT_API_KEY"]), - LANGEXTRACT_API_KEY_MASKED: maskSecret(env["LANGEXTRACT_API_KEY"]), - LANGEXTRACT_RUNTIME_READY: runtime.ready, - LANGEXTRACT_RUNTIME_ERROR: runtime.error, + ...config, + llm: { + ...config.llm, + apiKeyConfigured: !!secrets.llmApiKey, + apiKeyMasked: maskSecret(secrets.llmApiKey) + } }; } -function detectLangExtractRuntime(env: Record): { ready: boolean; error: string } { - const python = resolveLangExtractPython(env); - const result = spawnSync(python, ["-c", detectLangExtractRuntimeProbe()], { - encoding: "utf8", - timeout: 3000, +export function parseSettingsUpdate(input: unknown): { config: AppConfig; apiKey?: string } { + const record = objectValue(input); + if (!record) throw new Error("config_invalid"); + const llm = objectValue(record.llm); + const apiKey = llm && "apiKey" in llm ? llm.apiKey : undefined; + if (apiKey !== undefined && typeof apiKey !== "string") throw new Error("config_invalid"); + if (llm && "apiKey" in llm) { + const { apiKey: _apiKey, ...rest } = llm; + return { config: parseConfig({ ...record, llm: rest }), apiKey }; + } + return { config: parseConfig(record) }; +} + +export function settingsToEnv(config: AppConfig, currentSecrets: AppSecrets, apiKey?: string): EnvConfig { + const env = configToEnv(config); + if (apiKey !== undefined) { + if (apiKey.trim()) env.AI_TODO_LLM_API_KEY = apiKey.trim(); + else delete env.AI_TODO_LLM_API_KEY; + } else if (currentSecrets.llmApiKey) { + env.AI_TODO_LLM_API_KEY = currentSecrets.llmApiKey; + } + return env; +} + +export function configToEnv(config: AppConfig): EnvConfig { + const parsed = parseConfig(config); + return sanitizeEnvConfig({ + AI_TODO_CODEX_HOME: parsed.sources.codex.path, + AI_TODO_CLAUDE_HOME: parsed.sources["claude-code"].path, + AI_TODO_LLM_ENABLED: String(parsed.llm.enabled), + AI_TODO_LLM_PROVIDER: parsed.llm.provider, + AI_TODO_LLM_MODEL: parsed.llm.model, + AI_TODO_LLM_ENDPOINT: parsed.llm.endpoint, + AI_TODO_LLM_THINKING_DEPTH: parsed.llm.thinkingDepth, + AI_TODO_LLM_TIMEOUT_MS: String(parsed.llm.timeoutMs), + AI_TODO_ORGANIZE_SINCE_DAYS: String(parsed.organize.sinceDays), + AI_TODO_ORGANIZE_MAX_INTERACTIONS_PER_SESSION: String(parsed.organize.maxInteractionsPerSession), + AI_TODO_ORGANIZE_MAX_SESSIONS: String(parsed.organize.maxSessions), + AI_TODO_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION: String(parsed.organize.maxObservationsPerSession) }); - if (result.status === 0) return { ready: true, error: "" }; +} + +export function parseConfig(input: unknown): AppConfig { + const record = objectValue(input); + if (!record) throw new Error("config_invalid"); + const sources = objectValue(record.sources); + if (!sources) throw new Error("config_invalid"); + const keys = Object.keys(record); + if (keys.some((key) => key !== "sources" && key !== "llm" && key !== "organize")) throw new Error("config_invalid"); + const sourceKeys = Object.keys(sources); + if (sourceKeys.some((key) => key !== "codex" && key !== "claude-code")) throw new Error("config_invalid"); return { - ready: false, - error: String(result.stderr || result.error?.message || "langextract unavailable").replace(/\s+/g, " ").trim(), + sources: { + codex: sourceConfig(sources.codex), + "claude-code": sourceConfig(sources["claude-code"]) + }, + llm: llmConfig(record.llm), + organize: organizeConfig(record.organize) }; } -export function detectLangExtractRuntimeProbe(): string { - return "import importlib.util; raise SystemExit(0 if importlib.util.find_spec('langextract') else 1)"; -} - -export function resolveLangExtractPython(env?: Record): string { - const source = env ?? getMergedEnv(); - const configured = source["LANGEXTRACT_PYTHON"]?.trim(); - if (configured && configured !== "python3") return configured; - const moduleRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); - const candidates = [process.cwd(), moduleRoot].map((root) => - process.platform === "win32" - ? join(root, ".agentmemory-python", "Scripts", "python.exe") - : join(root, ".agentmemory-python", "bin", "python"), - ); - const local = candidates.find((candidate) => existsSync(candidate)); - if (local) return local; - return configured || "python3"; -} - -export function normalizeTodoExtractorModel(value: string | undefined): string { - const model = value?.trim(); - return model && !LEGACY_LANGEXTRACT_MODELS.has(model) ? model : DEFAULT_LANGEXTRACT_MODEL; -} - -export function normalizeTodoExtractorProvider(value: string | undefined): string { - const provider = value?.trim().toLowerCase(); - if (!provider || provider === "novita" || provider === "deepseek") return DEFAULT_LANGEXTRACT_PROVIDER; - return provider; -} - -export function writeUserEnv(updates: Record): string[] { - const cleanEntries = Object.entries(updates).filter( - ([key, value]) => WRITABLE_TODO_EXTRACT_KEYS.has(key) && value.trim().length > 0 && !/[\r\n]/.test(value), - ); - if (!cleanEntries.length) return []; - mkdirSync(DATA_DIR, { recursive: true }); - const current = existsSync(ENV_FILE) ? readFileSync(ENV_FILE, "utf-8") : ""; - const lines = current ? current.split("\n") : []; - const seen = new Set(); - const next = lines.map((line) => { - const match = line.match(/^\s*([A-Z0-9_]+)\s*=/); - if (!match) return line; - const key = match[1]; - const update = cleanEntries.find(([k]) => k === key); - if (!update) return line; - seen.add(key); - return `${key}=${update[1]}`; - }); - cleanEntries.forEach(([key, value]) => { - if (!seen.has(key)) next.push(`${key}=${value}`); - }); - writeFileSync(ENV_FILE, next.join("\n").replace(/\n*$/, "\n"), { mode: 0o600 }); - return cleanEntries.map(([key]) => key); +export function normalizeConfig(input: unknown): AppConfig { + try { + return parseConfig(input); + } catch { + return defaultConfig(); + } } -function hasRealValue(v: string | undefined): v is string { - return typeof v === "string" && v.trim().length > 0; +export function parseEnvFile(text: string): EnvConfig { + const env: EnvConfig = {}; + for (const rawLine of text.split(/\r?\n/)) { + const line = rawLine.trim(); + if (!line || line.startsWith("#")) continue; + const separator = line.indexOf("="); + if (separator <= 0) throw new Error("env_invalid"); + const key = line.slice(0, separator).trim(); + if (!isWritableEnvKey(key)) { + if (IGNORED_ENV_KEYS.has(key)) continue; + throw new Error("env_invalid"); + } + env[key] = parseEnvValue(line.slice(separator + 1)); + } + return sanitizeEnvConfig(env); } -function detectProvider(env: Record): ProviderConfig { - const maxTokens = parseInt(env["MAX_TOKENS"] || "4096", 10); +export function formatEnvFile(env: EnvConfig): string { + const sanitized = sanitizeEnvConfig(env); + return WRITABLE_ENV_KEYS + .filter((key) => sanitized[key] !== undefined) + .map((key) => `${key}=${quoteEnvValue(sanitized[key] ?? "")}`) + .join("\n"); +} - // OpenAI-compatible: supports OpenAI, DeepSeek, SiliconFlow, Azure, vLLM, LM Studio - if (hasRealValue(env["OPENAI_API_KEY"]) && env["OPENAI_API_KEY_FOR_LLM"] !== "false") { - return { - provider: "openai", - model: env["OPENAI_MODEL"] || "gpt-4o-mini", - maxTokens, - baseURL: env["OPENAI_BASE_URL"], - }; - } +export function maskSecret(value: string | undefined): string { + if (!value?.trim()) return ""; + const secret = value.trim(); + if (secret.length <= 8) return `${secret.slice(0, 2)}****${secret.slice(-2)}`; + return `${secret.slice(0, 3)}****${secret.slice(-4)}`; +} - // MiniMax: Anthropic-compatible API, requires raw fetch to avoid SDK stainless headers - if (hasRealValue(env["MINIMAX_API_KEY"])) { - return { - provider: "minimax", - model: env["MINIMAX_MODEL"] || "MiniMax-M2.7", - maxTokens, - }; +function loadJsonConfig(paths: AppPaths): AppConfig { + if (!existsSync(paths.configPath)) return defaultConfig(); + try { + return parseConfig(JSON.parse(readFileSync(paths.configPath, "utf8"))); + } catch (error) { + if ((error as Error).message === "config_invalid") throw error; + throw new Error("config_invalid"); } +} - if (hasRealValue(env["ANTHROPIC_API_KEY"])) { - return { - provider: "anthropic", - model: env["ANTHROPIC_MODEL"] || "claude-sonnet-4-20250514", - maxTokens, - baseURL: env["ANTHROPIC_BASE_URL"], - }; +function applyEnvConfig(config: AppConfig, env: EnvConfig): AppConfig { + const next: AppConfig = { + sources: { + codex: { ...config.sources.codex }, + "claude-code": { ...config.sources["claude-code"] } + }, + llm: { ...config.llm }, + organize: { ...config.organize } + }; + if (env.AI_TODO_CODEX_HOME) next.sources.codex = { path: cleanSourcePath(env.AI_TODO_CODEX_HOME) }; + if (env.AI_TODO_CLAUDE_HOME) next.sources["claude-code"] = { path: cleanSourcePath(env.AI_TODO_CLAUDE_HOME) }; + if (env.AI_TODO_LLM_ENABLED !== undefined) next.llm.enabled = parseBoolean(env.AI_TODO_LLM_ENABLED); + if (env.AI_TODO_LLM_PROVIDER !== undefined) { + if (env.AI_TODO_LLM_PROVIDER !== "openai") throw new Error("config_invalid"); + next.llm.provider = "openai"; } - if (hasRealValue(env["GEMINI_API_KEY"]) || hasRealValue(env["GOOGLE_API_KEY"])) { - if (!hasRealValue(env["GEMINI_API_KEY"]) && hasRealValue(env["GOOGLE_API_KEY"])) { - process.stderr.write( - "[agentmemory] GOOGLE_API_KEY detected — treating as GEMINI_API_KEY. " + - "Set GEMINI_API_KEY in ~/.agentmemory/.env to silence this warning.\n", - ); - } - return { - provider: "gemini", - model: env["GEMINI_MODEL"] || "gemini-2.5-flash", - maxTokens, - }; + if (env.AI_TODO_LLM_MODEL) next.llm.model = env.AI_TODO_LLM_MODEL; + if (env.AI_TODO_LLM_ENDPOINT) next.llm.endpoint = env.AI_TODO_LLM_ENDPOINT; + if (env.AI_TODO_LLM_THINKING_DEPTH !== undefined) next.llm.thinkingDepth = parseThinkingDepth(env.AI_TODO_LLM_THINKING_DEPTH); + if (env.AI_TODO_LLM_TIMEOUT_MS !== undefined) next.llm.timeoutMs = parseIntRange(env.AI_TODO_LLM_TIMEOUT_MS, 1000, 600000); + if (env.AI_TODO_ORGANIZE_SINCE_DAYS !== undefined) { + next.organize.sinceDays = parseIntRange(env.AI_TODO_ORGANIZE_SINCE_DAYS, 1, 3650); } - if (hasRealValue(env["OPENROUTER_API_KEY"])) { - const model = - env["OPENROUTER_MODEL"] || "anthropic/claude-sonnet-4-20250514"; - // warn when the configured OpenRouter model is in the - // premium tier and likely to burn money on background compression. - // Captured workload data shows ~$5/35h on claude-sonnet-4 vs - // ~$0.46/35h on deepseek-v4-flash for the same compression mix. - // Heuristic match avoids hard-coding a pricing table. - if ( - !warnPremiumModelShown && - /sonnet|opus|gpt-4o(?!.*mini)|gpt-4-turbo/i.test(model) && - env["AGENTMEMORY_SUPPRESS_COST_WARNING"] !== "1" && - env["AGENTMEMORY_SUPPRESS_COST_WARNING"] !== "true" - ) { - warnPremiumModelShown = true; - process.stderr.write( - `[agentmemory] OPENROUTER_MODEL=${model} is in the premium tier. ` + - `Background compression on this model can cost $5+/day under active use. ` + - `Cheaper alternatives with comparable quality for memory compression: ` + - `deepseek/deepseek-v4-flash, deepseek/deepseek-chat, qwen/qwen3-coder. ` + - `See README "Cost-aware model selection" for the full table. ` + - `Set AGENTMEMORY_SUPPRESS_COST_WARNING=1 to silence.\n`, - ); - } - return { - provider: "openrouter", - model, - maxTokens, - }; + if (env.AI_TODO_ORGANIZE_MAX_INTERACTIONS_PER_SESSION !== undefined) { + next.organize.maxInteractionsPerSession = parseIntRange(env.AI_TODO_ORGANIZE_MAX_INTERACTIONS_PER_SESSION, 1, 500); } - - const allowAgentSdk = env["AGENTMEMORY_ALLOW_AGENT_SDK"] === "true"; - if (!allowAgentSdk) { - process.stderr.write( - "[agentmemory] No LLM provider key found " + - "(ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY, MINIMAX_API_KEY, OPENAI_API_KEY). " + - "LLM-backed compression and summarization are DISABLED — using no-op provider. " + - "This is the safe default: the agent-sdk fallback used to spawn Claude Agent SDK " + - "child sessions which inherit Claude Code's plugin hooks and cause infinite Stop-hook " + - "recursion (#149 follow-up). To opt in to the agent-sdk fallback anyway, set both " + - "AGENTMEMORY_AUTO_COMPRESS=true AND AGENTMEMORY_ALLOW_AGENT_SDK=true — but be aware " + - "it will burn your Claude Pro allocation and may still recurse if you use it from " + - "inside Claude Code itself.\n", - ); - return { - provider: "noop", - model: "noop", - maxTokens, - }; + if (env.AI_TODO_ORGANIZE_MAX_SESSIONS !== undefined) { + next.organize.maxSessions = parseIntRange(env.AI_TODO_ORGANIZE_MAX_SESSIONS, 1, 200); } + if (env.AI_TODO_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION !== undefined) { + next.organize.maxObservationsPerSession = parseIntRange(env.AI_TODO_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION, 1, 1000); + } + return parseConfig(next); +} - process.stderr.write( - "[agentmemory] WARNING: agent-sdk fallback enabled via AGENTMEMORY_ALLOW_AGENT_SDK=true. " + - "This spawns @anthropic-ai/claude-agent-sdk child sessions that can trigger the Stop-hook " + - "recursion loop (#149 follow-up). A SDK-child env marker is set to block re-entry, " + - "but prefer setting a real API key in ~/.agentmemory/.env instead.\n", - ); - return { - provider: "agent-sdk", - model: "claude-sonnet-4-20250514", - maxTokens, - }; +function sourceConfig(value: unknown): { path?: string } { + const input = objectValue(value); + if (!input) throw new Error("config_invalid"); + const keys = Object.keys(input); + if (keys.some((key) => key !== "path")) throw new Error("config_invalid"); + const path = input.path; + if (path === undefined) return {}; + if (typeof path !== "string" || !path.trim()) throw new Error("config_invalid"); + const cleaned = cleanSourcePath(path); + return cleaned ? { path: cleaned } : {}; } -export function loadConfig(): AgentMemoryConfig { - const env = getMergedEnv(); +function cleanSourcePath(path: string): string | undefined { + const trimmed = path.trim(); + if (!trimmed) return undefined; + if (isStaleTempSourcePath(trimmed) && !existsSync(trimmed)) return undefined; + return trimmed; +} - const provider = detectProvider(env); +function isStaleTempSourcePath(path: string): boolean { + return /\/ai-todo-http-[A-Za-z0-9_-]+(?:\/|$)/.test(path); +} +function llmConfig(value: unknown): AppConfig["llm"] { + if (value === undefined) return defaultConfig().llm; + const input = objectValue(value); + if (!input) throw new Error("config_invalid"); + const keys = Object.keys(input); + if (keys.some((key) => !["enabled", "provider", "model", "endpoint", "thinkingDepth", "timeoutMs"].includes(key))) { + throw new Error("config_invalid"); + } + if (typeof input.enabled !== "boolean") throw new Error("config_invalid"); + if (input.provider !== "openai") throw new Error("config_invalid"); + const model = nonEmptyString(input.model); + const endpoint = nonEmptyString(input.endpoint); + const thinkingDepth = parseThinkingDepth(input.thinkingDepth); + const timeoutMs = input.timeoutMs; + if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs) || timeoutMs < 1000 || timeoutMs > 600000) { + throw new Error("config_invalid"); + } + return { enabled: input.enabled, provider: "openai", model, endpoint, thinkingDepth, timeoutMs }; +} + +function organizeConfig(value: unknown): AppConfig["organize"] { + if (value === undefined) return defaultConfig().organize; + const input = objectValue(value); + if (!input) throw new Error("config_invalid"); + const keys = Object.keys(input); + if (keys.some((key) => + key !== "sinceDays" && + key !== "maxInteractionsPerSession" && + key !== "maxSessions" && + key !== "maxObservationsPerSession" + )) throw new Error("config_invalid"); + const defaults = defaultConfig().organize; return { - engineUrl: env["III_ENGINE_URL"] || "ws://localhost:49134", - restPort: parseInt(env["III_REST_PORT"] || "3111", 10) || 3111, - streamsPort: parseInt(env["III_STREAMS_PORT"] || "3112", 10) || 3112, - provider, - tokenBudget: safeParseInt(env["TOKEN_BUDGET"], 2000), - maxObservationsPerSession: safeParseInt(env["MAX_OBS_PER_SESSION"], 500), - compressionModel: provider.model, - dataDir: DATA_DIR, + sinceDays: numberRange(input.sinceDays, 1, 3650), + maxInteractionsPerSession: numberRange(input.maxInteractionsPerSession, 1, 500), + maxSessions: input.maxSessions === undefined ? defaults.maxSessions : numberRange(input.maxSessions, 1, 200), + maxObservationsPerSession: input.maxObservationsPerSession === undefined + ? defaults.maxObservationsPerSession + : numberRange(input.maxObservationsPerSession, 1, 1000) }; } -function getMergedEnv( - overrides?: Record, -): Record { - const fileEnv = loadEnvFile(); - return { ...fileEnv, ...process.env, ...overrides } as Record; -} - -export function getEnvVar(key: string): string | undefined { - return getMergedEnv()[key]; +function parseSecrets(input: unknown): AppSecrets { + const record = objectValue(input); + if (!record) throw new Error("secrets_invalid"); + const keys = Object.keys(record); + if (keys.some((key) => key !== "llmApiKey")) throw new Error("secrets_invalid"); + if (record.llmApiKey === undefined) return {}; + return { llmApiKey: nonEmptyString(record.llmApiKey) }; +} + +function sanitizeEnvConfig(input: EnvConfig): EnvConfig { + const env: EnvConfig = {}; + for (const [key, value] of Object.entries(input)) { + if (!isWritableEnvKey(key)) throw new Error("env_invalid"); + if (value === undefined) continue; + const text = String(value).trim(); + if (!text) continue; + if (/[\r\n]/.test(text)) throw new Error("env_invalid"); + env[key] = text; + } + return env; } -export function isDropStaleIndexEnabled(): boolean { - return getMergedEnv()["AGENTMEMORY_DROP_STALE_INDEX"] === "true"; +function parseEnvValue(rawValue: string): string { + const trimmed = rawValue.trim(); + if (!trimmed) return ""; + if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) { + return trimmed.slice(1, -1).trim(); + } + return trimmed.replace(/\s+#.*$/, "").trim(); } -// Line D: Feishu/Lark cross-device delivery of inbox items. -// Default OFF — when disabled (or user-id missing), inbox behaves exactly as -// Line C with zero side effects. Only an explicit "true" opts in. -export function isLarkDeliveryEnabled(): boolean { - const env = getMergedEnv(); - return ( - env["AGENTMEMORY_LARK_DELIVERY"] === "true" && - hasRealValue(env["AGENTMEMORY_LARK_USER_ID"]) - ); +function quoteEnvValue(value: string): string { + if (/[\s#'"]/.test(value)) return JSON.stringify(value); + return value; } -// Reply loop (D5): bot subscribes to incoming P2P messages and maps a reply -// back to the pending question. Independent switch from the push side. -export function isLarkReplyLoopEnabled(): boolean { - const env = getMergedEnv(); - return ( - env["AGENTMEMORY_LARK_REPLY_LOOP"] === "true" && - hasRealValue(env["AGENTMEMORY_LARK_USER_ID"]) - ); +function isWritableEnvKey(key: string): key is WritableEnvKey { + return (WRITABLE_ENV_KEYS as readonly string[]).includes(key); } -export interface LarkConfig { - userId: string; // target open_id (ou_xxx) - urgentQuestion: boolean; // send urgent_app on question delivery +function nonEmptyString(value: unknown): string { + if (typeof value !== "string" || !value.trim()) throw new Error("config_invalid"); + return value.trim(); } -export function getLarkConfig(): LarkConfig | null { - const env = getMergedEnv(); - const userId = env["AGENTMEMORY_LARK_USER_ID"]; - if (!hasRealValue(userId)) return null; - return { - userId: userId!, - urgentQuestion: env["AGENTMEMORY_LARK_URGENT_QUESTION"] !== "false", - }; +function parseThinkingDepth(value: unknown): "low" | "medium" | "high" { + if (value !== "low" && value !== "medium" && value !== "high") throw new Error("config_invalid"); + return value; } -export function detectLlmProviderKind(): "llm" | "noop" { - const env = getMergedEnv(); - if ( - hasRealValue(env["ANTHROPIC_API_KEY"]) || - hasRealValue(env["GEMINI_API_KEY"]) || - hasRealValue(env["GOOGLE_API_KEY"]) || - hasRealValue(env["OPENROUTER_API_KEY"]) || - hasRealValue(env["MINIMAX_API_KEY"]) || - (hasRealValue(env["OPENAI_API_KEY"]) && - env["OPENAI_API_KEY_FOR_LLM"] !== "false") - ) { - return "llm"; - } - return "noop"; +function parseBoolean(value: string): boolean { + if (value === "true") return true; + if (value === "false") return false; + throw new Error("config_invalid"); } -export function loadEmbeddingConfig(): EmbeddingConfig { - const env = getMergedEnv(); - let bm25Weight = parseFloat(env["BM25_WEIGHT"] || "0.4"); - let vectorWeight = parseFloat(env["VECTOR_WEIGHT"] || "0.6"); - bm25Weight = - isNaN(bm25Weight) || bm25Weight < 0 ? 0.4 : Math.min(bm25Weight, 1); - vectorWeight = - isNaN(vectorWeight) || vectorWeight < 0 ? 0.6 : Math.min(vectorWeight, 1); - return { - provider: env["EMBEDDING_PROVIDER"] || undefined, - bm25Weight, - vectorWeight, - }; +function parseIntRange(value: string, min: number, max: number): number { + if (!/^\d+$/.test(value)) throw new Error("config_invalid"); + return numberRange(Number(value), min, max); } -export function detectEmbeddingProvider( - env?: Record, -): string | null { - const source = env ?? getMergedEnv(); - const forced = source["EMBEDDING_PROVIDER"]; - if (forced) return forced; - - if (source["GEMINI_API_KEY"]) return "gemini"; - if (source["OPENAI_API_KEY"]) return "openai"; - if (source["VOYAGE_API_KEY"]) return "voyage"; - if (source["COHERE_API_KEY"]) return "cohere"; - if (source["OPENROUTER_API_KEY"]) return "openrouter"; - return null; -} - -export function loadClaudeBridgeConfig(): ClaudeBridgeConfig { - const env = getMergedEnv(); - const enabled = env["CLAUDE_MEMORY_BRIDGE"] === "true"; - const projectPath = env["CLAUDE_PROJECT_PATH"] || ""; - const lineBudget = safeParseInt(env["CLAUDE_MEMORY_LINE_BUDGET"], 200); - let memoryFilePath = ""; - if (enabled && projectPath) { - // Claude Code stores MEMORY.md at - // ~/.claude/projects//MEMORY.md - // where is the project path with `/` and `\` swapped for `-`. - // The leading `-` from an absolute POSIX path is preserved (Claude - // Code keeps it; stripping it produced a slug Claude never reads). - // There's also no `memory/` subdirectory — the file sits directly - // under the slug dir. - const safePath = projectPath.replace(/[/\\]/g, "-"); - memoryFilePath = join( - homedir(), - ".claude", - "projects", - safePath, - "MEMORY.md", - ); +function numberRange(value: unknown, min: number, max: number): number { + if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) { + throw new Error("config_invalid"); } - return { enabled, projectPath, memoryFilePath, lineBudget }; -} - -export function loadTeamConfig(): TeamConfig | null { - const env = getMergedEnv(); - const teamId = env["TEAM_ID"]; - const userId = env["USER_ID"]; - if (!teamId || !userId) return null; - const mode = env["TEAM_MODE"] === "shared" ? "shared" : "private"; - return { teamId, userId, mode }; -} - -// optional AGENT_ID env for multi-agent memory isolation. -// Returns null when unset so memory stays unscoped (legacy behavior). -// Trimmed + length-capped to keep KV writes well-formed. -// -// Filtering is gated by AGENTMEMORY_AGENT_SCOPE: -// "shared" (default) — tag everything, do not filter recall paths -// "isolated" — tag everything AND filter recall paths -export function loadAgentScope(): { - agentId: string; - mode: "shared" | "isolated"; -} | null { - const env = getMergedEnv(); - const raw = env["AGENT_ID"]; - if (!raw) return null; - const agentId = raw.trim().slice(0, 128); - if (!agentId) return null; - const mode = env["AGENTMEMORY_AGENT_SCOPE"] === "isolated" - ? "isolated" - : "shared"; - return { agentId, mode }; -} - -export function getAgentId(): string | undefined { - return loadAgentScope()?.agentId; -} - -// True only when AGENT_ID is set AND scope=isolated. Recall paths -// consult this to decide whether to filter. -export function isAgentScopeIsolated(): boolean { - return loadAgentScope()?.mode === "isolated"; -} - -export function loadSnapshotConfig(): { - enabled: boolean; - interval: number; - dir: string; -} { - const env = getMergedEnv(); - return { - enabled: env["SNAPSHOT_ENABLED"] === "true", - interval: safeParseInt(env["SNAPSHOT_INTERVAL"], 3600), - dir: env["SNAPSHOT_DIR"] || join(getAgentMemoryDataDir(), "snapshots"), - }; + return value; } -export function isGraphExtractionEnabled(): boolean { - return getMergedEnv()["GRAPH_EXTRACTION_ENABLED"] === "true"; -} - -export function getGraphBatchSize(): number { - return safeParseInt(getMergedEnv()["GRAPH_EXTRACTION_BATCH_SIZE"], 10); -} - -export function isConsolidationEnabled(): boolean { - const env = getMergedEnv(); - const explicit = env["CONSOLIDATION_ENABLED"]; - if (explicit === "false" || explicit === "0") return false; - if (explicit === "true" || explicit === "1") return true; - return hasLLMProviderConfigured(env); -} - -function hasLLMProviderConfigured(env: Record): boolean { - const provider = (env["AGENTMEMORY_PROVIDER"] || "").toLowerCase(); - if (provider === "noop") return false; - const openaiKeyForLlm = - env["OPENAI_API_KEY"] && - (env["OPENAI_API_KEY_FOR_LLM"] || "").toLowerCase() !== "false"; - return Boolean( - env["ANTHROPIC_API_KEY"] || - openaiKeyForLlm || - env["OPENROUTER_API_KEY"] || - env["GEMINI_API_KEY"] || - env["GOOGLE_API_KEY"] || - env["MINIMAX_API_KEY"] || - env["OPENAI_BASE_URL"] || - provider === "agent-sdk", - ); -} - -// Per-observation LLM compression is OFF by default as of 0.8.8 (see #138). -// When disabled, observations are captured and indexed via a synthetic -// (zero-LLM) compression path so recall/search still works. Users who want -// richer LLM-generated summaries can set AGENTMEMORY_AUTO_COMPRESS=true in -// ~/.agentmemory/.env — but should expect their Claude API token usage to -// climb proportionally with session tool-use frequency. -export function isAutoCompressEnabled(): boolean { - return getMergedEnv()["AGENTMEMORY_AUTO_COMPRESS"] === "true"; -} - -// Hook-level context injection into Claude Code's conversation is OFF by -// default as of 0.8.10 (see #143). When disabled, pre-tool-use and -// session-start hooks still POST observations for background capture, but -// never write context to stdout — so Claude Code doesn't inject an extra -// ~4000-char blob into every tool turn. 0.8.8 stopped the agentmemory-side -// Claude calls (via ANTHROPIC_API_KEY); this stops the Claude Code-side -// token burn where every tool call silently grew the model input window. -// Users who want the in-conversation context injection explicitly opt in -// with AGENTMEMORY_INJECT_CONTEXT=true and get a loud startup warning. -export function isContextInjectionEnabled(): boolean { - return getMergedEnv()["AGENTMEMORY_INJECT_CONTEXT"] === "true"; -} - -export function getConsolidationDecayDays(): number { - return safeParseInt(getMergedEnv()["CONSOLIDATION_DECAY_DAYS"], 30); -} - -export function isStandaloneMcp(): boolean { - return getMergedEnv()["STANDALONE_MCP"] === "true"; -} - -export function getStandalonePersistPath(): string { - const env = getMergedEnv(); - return ( - env["STANDALONE_PERSIST_PATH"] || - join(getAgentMemoryDataDir(), "standalone.json") - ); -} - -const VALID_PROVIDERS = new Set([ - "anthropic", - "gemini", - "openrouter", - "agent-sdk", - "minimax", - "openai", -]); - -export function loadFallbackConfig(): FallbackConfig { - const env = getMergedEnv(); - const raw = env["FALLBACK_PROVIDERS"] || ""; - const allowAgentSdk = env["AGENTMEMORY_ALLOW_AGENT_SDK"] === "true"; - const providers = raw - .split(",") - .map((p) => p.trim()) - .filter( - (p): p is FallbackConfig["providers"][number] => - Boolean(p) && VALID_PROVIDERS.has(p), - ) - .filter((p) => { - // Honor the same safety gate as detectProvider: agent-sdk is only - // permitted as a fallback target when the user has explicitly opted - // in. Without this filter, a user could set FALLBACK_PROVIDERS=agent-sdk - // and re-introduce the Stop-hook recursion loop even though - // detectProvider() returned the noop provider. - if (p === "agent-sdk" && !allowAgentSdk) { - process.stderr.write( - "[agentmemory] Ignoring FALLBACK_PROVIDERS entry 'agent-sdk' " + - "(AGENTMEMORY_ALLOW_AGENT_SDK is not 'true'). The agent-sdk " + - "fallback can spawn Claude Agent SDK child sessions that trigger " + - "the Stop-hook recursion loop (#149 follow-up). Opt in explicitly " + - "with AGENTMEMORY_ALLOW_AGENT_SDK=true if this is intentional.\n", - ); - return false; - } - return true; - }); - return { providers }; +function objectValue(value: unknown): Record | null { + return value && typeof value === "object" && !Array.isArray(value) ? value as Record : null; } diff --git a/src/contracts.ts b/src/contracts.ts new file mode 100644 index 00000000..1fece2ac --- /dev/null +++ b/src/contracts.ts @@ -0,0 +1,103 @@ +export const CLI_COMMANDS = [ + "ai-todo", + "ai-todo scan", + "ai-todo organize", + "ai-todo list", + "ai-todo done ", + "ai-todo ignore ", + "ai-todo open", + "ai-todo doctor", + "ai-todo connect codex", + "ai-todo mcp" +] as const; + +export const HTTP_ROUTES = [ + "GET /healthz", + "GET /sources", + "POST /sources/scan", + "GET /sessions", + "GET /sessions/:id/observations", + "POST /browser/sessions", + "POST /todos/organize", + "GET /todos", + "PATCH /todos/:id", + "GET /todos/:id/evidence", + "POST /todos/:id/refresh", + "GET /organize-runs/:id", + "GET /startup/scan", + "GET /settings", + "PUT /settings" +] as const; + +export const MCP_TOOLS = [ + "todo_scan", + "todo_organize", + "todo_list", + "todo_update", + "todo_open" +] as const; + +export type TodoStatus = "todo" | "done" | "ignored"; +export type SourceKind = "codex" | "claude-code" | "browser"; +export type OrganizeEngine = "llm"; + +export interface SessionRecord { + id: string; + source: SourceKind; + path: string; + updatedAt: string; + observationCount: number; + preview: string; +} + +export interface ObservationRecord { + id: string; + sessionId: string; + source: SourceKind; + role: string; + text: string; + createdAt: string; +} + +export interface TodoCard { + id: string; + title: string; + description: string; + status: TodoStatus; + evidenceIds: string[]; + updatedAt: string; +} + +export interface OrganizeResult { + runId: string; + scanned: number; + sources: Array<{ source: SourceKind; scanned: number }>; + created: number; + updated: number; + completed: number; + ignored: number; + engine: OrganizeEngine; + warnings: string[]; + details?: { + scope?: { + sessionsScanned: number; + sessionsDropped: number; + observationsDropped: number; + }; + truncations?: Array<{ + sessionId: string; + source: SourceKind; + role: string; + originalChars: number; + keptChars: number; + }>; + batchFailures?: Array<{ + sessionId: string; + source: SourceKind; + warning: string; + reason: string; + retryable: boolean; + }>; + }; + durationMs: number; +} diff --git a/src/db/index.ts b/src/db/index.ts new file mode 100644 index 00000000..d49a9b1a --- /dev/null +++ b/src/db/index.ts @@ -0,0 +1,87 @@ +import { mkdirSync } from "node:fs"; +import { dirname } from "node:path"; +import { DatabaseSync } from "node:sqlite"; +import type { AppPaths } from "../paths.js"; + +export type Database = DatabaseSync; + +const CLEAN_TRANSCRIPT_VERSION = "2"; + +export function openDatabase(paths: AppPaths): Database { + mkdirSync(dirname(paths.dbPath), { recursive: true }); + const db = new DatabaseSync(paths.dbPath); + migrate(db); + return db; +} + +export function migrate(db: Database): void { + db.exec(` + PRAGMA journal_mode = WAL; + + CREATE TABLE IF NOT EXISTS schema_meta ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + source TEXT NOT NULL, + path TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS observations ( + id TEXT PRIMARY KEY, + session_id TEXT NOT NULL, + source TEXT NOT NULL, + role TEXT NOT NULL, + text TEXT NOT NULL, + created_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS scan_checkpoints ( + source TEXT NOT NULL, + path TEXT NOT NULL, + mtime_ms REAL NOT NULL, + size INTEGER NOT NULL, + PRIMARY KEY (source, path) + ); + + CREATE TABLE IF NOT EXISTS todos ( + id TEXT PRIMARY KEY, + title TEXT NOT NULL UNIQUE, + description TEXT NOT NULL, + status TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS evidence ( + id TEXT PRIMARY KEY, + todo_id TEXT NOT NULL, + observation_id TEXT NOT NULL, + text TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS organize_runs ( + id TEXT PRIMARY KEY, + result_json TEXT NOT NULL, + created_at TEXT NOT NULL + ); + `); + migrateCleanTranscript(db); +} + +function migrateCleanTranscript(db: Database): void { + const row = db.prepare("SELECT value FROM schema_meta WHERE key = 'clean_transcript_version'").get() as { value: string } | undefined; + if (row?.value === CLEAN_TRANSCRIPT_VERSION) return; + db.exec(` + DELETE FROM evidence; + DELETE FROM organize_runs; + DELETE FROM observations; + DELETE FROM sessions; + DELETE FROM scan_checkpoints; + `); + db.prepare( + "INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('clean_transcript_version', ?)" + ).run(CLEAN_TRANSCRIPT_VERSION); +} diff --git a/src/eval/metrics-store.ts b/src/eval/metrics-store.ts deleted file mode 100644 index 1d381ff9..00000000 --- a/src/eval/metrics-store.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { FunctionMetrics } from "../types.js"; -import type { StateKV } from "../state/kv.js"; -import { KV } from "../state/schema.js"; - -export class MetricsStore { - private cache = new Map(); - private qualityCallCounts = new Map(); - - constructor(private kv: StateKV) {} - - async record( - functionId: string, - latencyMs: number, - success: boolean, - qualityScore?: number, - ): Promise { - let m = this.cache.get(functionId); - if (!m) { - m = (await this.kv.get(KV.metrics, functionId)) ?? { - functionId, - totalCalls: 0, - successCount: 0, - failureCount: 0, - avgLatencyMs: 0, - avgQualityScore: 0, - }; - } - - const prev = m.totalCalls; - m.totalCalls += 1; - m.avgLatencyMs = (m.avgLatencyMs * prev + latencyMs) / m.totalCalls; - if (success) { - m.successCount += 1; - } else { - m.failureCount += 1; - } - if (qualityScore !== undefined) { - const prevQualityCalls = this.qualityCallCounts.get(functionId) || 0; - m.avgQualityScore = - (m.avgQualityScore * prevQualityCalls + qualityScore) / - (prevQualityCalls + 1); - this.qualityCallCounts.set(functionId, prevQualityCalls + 1); - } - - this.cache.set(functionId, m); - await this.kv.set(KV.metrics, functionId, m).catch(() => {}); - } - - async get(functionId: string): Promise { - return ( - this.cache.get(functionId) ?? - (await this.kv.get(KV.metrics, functionId)) - ); - } - - async getAll(): Promise { - const kvMetrics = await this.kv - .list(KV.metrics) - .catch(() => []); - const merged = new Map(); - for (const m of kvMetrics) merged.set(m.functionId, m); - for (const [id, m] of this.cache) merged.set(id, m); - return Array.from(merged.values()); - } -} diff --git a/src/eval/quality.ts b/src/eval/quality.ts deleted file mode 100644 index 9f565d5a..00000000 --- a/src/eval/quality.ts +++ /dev/null @@ -1,51 +0,0 @@ -export function scoreCompression(obs: { - type?: string; - title?: string; - facts?: string[]; - narrative?: string; - concepts?: string[]; - importance?: number; -}): number { - let score = 0; - if (obs.facts && obs.facts.length > 0) score += 25; - if (obs.facts && obs.facts.length >= 3) score += 10; - if (obs.narrative && obs.narrative.length >= 20) score += 20; - if (obs.narrative && obs.narrative.length >= 50) score += 5; - if (obs.title && obs.title.length >= 5 && obs.title.length <= 120) score += 15; - if (obs.concepts && obs.concepts.length > 0) score += 15; - if (obs.importance && obs.importance >= 1 && obs.importance <= 10) score += 10; - return Math.min(100, score); -} - -export function scoreSummary(summary: { - title?: string; - narrative?: string; - keyDecisions?: string[]; - filesModified?: string[]; - concepts?: string[]; -}): number { - let score = 0; - if (summary.title && summary.title.length >= 5) score += 20; - if (summary.narrative && summary.narrative.length >= 20) score += 25; - if (summary.narrative && summary.narrative.length >= 100) score += 5; - if (summary.keyDecisions && summary.keyDecisions.length > 0) score += 20; - if (summary.filesModified && summary.filesModified.length > 0) score += 15; - if (summary.concepts && summary.concepts.length > 0) score += 15; - return Math.min(100, score); -} - -export function scoreContextRelevance( - context: string, - project: string, -): number { - let score = 0; - if (context.length > 0) score += 20; - if (project && context.toLowerCase().includes(project.toLowerCase())) score += 20; - if (context.includes("<")) score += 15; - const sectionCount = (context.match(/<\w+>/g) || []).length; - if (sectionCount >= 2) score += 15; - if (sectionCount >= 4) score += 10; - if (context.length >= 100) score += 10; - if (context.length >= 500) score += 10; - return Math.min(100, score); -} diff --git a/src/eval/schemas.ts b/src/eval/schemas.ts deleted file mode 100644 index bf5bfca4..00000000 --- a/src/eval/schemas.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { z } from "zod"; - -const HookTypeEnum = z.enum([ - "session_start", - "prompt_submit", - "pre_tool_use", - "post_tool_use", - "post_tool_failure", - "pre_compact", - "subagent_start", - "subagent_stop", - "notification", - "task_completed", - "stop", - "session_end", -]); - -const ObservationTypeEnum = z.enum([ - "file_read", - "file_write", - "file_edit", - "command_run", - "search", - "web_fetch", - "conversation", - "error", - "decision", - "discovery", - "subagent", - "notification", - "task", - "other", -]); - -export const ObserveInputSchema = z.object({ - hookType: HookTypeEnum, - sessionId: z.string().min(1), - project: z.string().min(1), - cwd: z.string().min(1), - timestamp: z.string().min(1), - data: z.unknown(), -}); - -export const CompressOutputSchema = z.object({ - type: ObservationTypeEnum, - title: z.string().min(1).max(120), - subtitle: z.string().optional(), - facts: z.array(z.string()).min(1), - narrative: z.string().min(10), - concepts: z.array(z.string()), - files: z.array(z.string()), - importance: z.number().int().min(1).max(10), -}); - -export const SummaryOutputSchema = z.object({ - title: z.string().min(1), - narrative: z.string().min(20), - keyDecisions: z.array(z.string()), - filesModified: z.array(z.string()), - concepts: z.array(z.string()), -}); - -export const SearchInputSchema = z.object({ - query: z.string().min(1), - limit: z.number().int().positive().optional(), -}); - -export const ContextInputSchema = z.object({ - sessionId: z.string().min(1), - project: z.string().min(1), - budget: z.number().positive().optional(), -}); - -export const RememberInputSchema = z.object({ - content: z.string().min(1), - type: z - .enum(["pattern", "preference", "architecture", "bug", "workflow", "fact"]) - .optional(), - concepts: z.array(z.string()).optional(), - files: z.array(z.string()).optional(), -}); - -export const SmartSearchInputSchema = z.object({ - query: z.string().optional(), - expandIds: z.array(z.string()).optional(), - limit: z.number().int().positive().optional(), -}); - -export const TimelineInputSchema = z.object({ - anchor: z.string().min(1), - project: z.string().optional(), - before: z.number().int().nonnegative().optional(), - after: z.number().int().nonnegative().optional(), -}); - -export const ProfileInputSchema = z.object({ - project: z.string().min(1), - refresh: z.boolean().optional(), -}); - -export const RelateInputSchema = z.object({ - sourceId: z.string().min(1), - targetId: z.string().min(1), - type: z.enum(["supersedes", "extends", "derives", "contradicts", "related"]), -}); - -export const EvolveInputSchema = z.object({ - memoryId: z.string().min(1), - newContent: z.string().min(1), - newTitle: z.string().optional(), -}); - -export const ExportImportInputSchema = z.object({ - exportData: z.object({ - version: z.union([z.literal("0.3.0"), z.literal("0.4.0")]), - exportedAt: z.string(), - sessions: z.array(z.unknown()), - observations: z.record(z.string(), z.array(z.unknown())), - memories: z.array(z.unknown()), - summaries: z.array(z.unknown()), - profiles: z.array(z.unknown()).optional(), - }), - strategy: z.enum(["merge", "replace", "skip"]).optional(), -}); diff --git a/src/eval/self-correct.ts b/src/eval/self-correct.ts deleted file mode 100644 index 68fc34db..00000000 --- a/src/eval/self-correct.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { MemoryProvider } from "../types.js"; - -const STRICTER_SUFFIX = ` - -IMPORTANT: Your previous response was invalid. Please ensure your output strictly follows the required XML format. Every required field must be present with valid values.`; - -export async function compressWithRetry( - provider: MemoryProvider, - systemPrompt: string, - userPrompt: string, - validator: (response: string) => { valid: boolean; errors?: string[] }, - maxRetries = 1, -): Promise<{ response: string; retried: boolean }> { - const first = await provider.compress(systemPrompt, userPrompt); - const result = validator(first); - if (result.valid) return { response: first, retried: false }; - - for (let i = 0; i < maxRetries; i++) { - const retry = await provider.compress( - systemPrompt + STRICTER_SUFFIX, - userPrompt, - ); - const retryResult = validator(retry); - if (retryResult.valid) return { response: retry, retried: true }; - } - - return { response: first, retried: true }; -} diff --git a/src/eval/validator.ts b/src/eval/validator.ts deleted file mode 100644 index 21332ce6..00000000 --- a/src/eval/validator.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { z } from "zod"; -import type { EvalResult } from "../types.js"; - -export function validateInput( - schema: z.ZodType, - data: unknown, - functionId: string, -): { valid: true; data: T } | { valid: false; result: EvalResult } { - const parsed = schema.safeParse(data); - if (parsed.success) { - return { valid: true, data: parsed.data }; - } - return { - valid: false, - result: { - valid: false, - errors: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`), - qualityScore: 0, - latencyMs: 0, - functionId, - }, - }; -} - -export function validateOutput( - schema: z.ZodType, - data: unknown, - functionId: string, -): { valid: true; data: T } | { valid: false; result: EvalResult } { - return validateInput(schema, data, functionId); -} diff --git a/src/extract/llm-runner.ts b/src/extract/llm-runner.ts new file mode 100644 index 00000000..ea34d6b8 --- /dev/null +++ b/src/extract/llm-runner.ts @@ -0,0 +1,267 @@ +import type { AppConfig, AppSecrets } from "../config.js"; +import type { LlmExtractResult, LlmTodoCandidate, ObservationForOrganize } from "../todos/service.js"; + +const TODO_EXTRACTION_PROMPT = ` +You extract actionable AI-Todo cards from cleaned user/assistant transcripts. + +Return only JSON: +{"todos":[{"title":"...","description":"...","confidence":0.9,"sourceObservationId":"...","quote":"...","dedupeKey":"..."}]} + +Rules: +- Use taskChains as the primary unit. Anchor title to the original user intent and description to the latest assistant state, blocker, or next step. +- Create todos only for unresolved, actionable work: next actions, follow-ups, failed validation, blockers, or work still in progress. +- Reject completed results, status reports, confirmations, health checks, shell/tool logs, command payloads, and process chores. +- Titles must read like mature todo app cards: short verb + object + outcome. Do not use transcript fragments. +- Put long branch names, paths, URLs, commit hashes, package names, and session ids in description, not title. +- Description is one concise sentence about remaining user-relevant work. Do not start with "I will", "我会", "现在", or "接下来". +- quote must be an exact source text span from sourceObservationId. +- dedupeKey must be a short stable slug of core action and object. Never use raw JSON, paths, logs, call ids, or trace fragments. + +Good examples: +- "后续需要修复 CI 失败,并重新跑测试。" -> title "修复 CI 失败并重新跑测试" +- "clone the AI-Todo repo into the subdirectory" -> title "Clone the AI-Todo repository into the subdirectory" +- "read README and dependency config before migration" -> title "Read README and dependency configuration" +- "fix the dark mode button contrast" -> title "Fix dark mode button contrast" +- "push branch codex/current-feature to remote" -> title "推送当前工作分支到远程仓库"; keep the branch in description +- "修正目录显示文字(去掉重复编号)并更新页码缓存后重渲染" -> same intent as a single card + +Negative examples: +- "做最后一次状态确认", "health check passed", "Viewer URL works", "Bash(git status) process exited 0" -> no todos +`.trim(); + +export function createLlmRunner( + config: AppConfig["llm"], + secrets: AppSecrets +): (observations: ObservationForOrganize[]) => Promise { + return async (observations) => { + if (!config.enabled || !secrets.llmApiKey) return { ok: false, warning: "llm_config_missing" }; + const visibleObservations = observations.filter((observation) => + observation.role === "user" || observation.role === "assistant" + ); + const blocks = visibleObservations.map((observation) => ({ + sourceObservationId: observation.id, + sessionId: observation.sessionId, + timestamp: observation.createdAt, + source: observation.source, + role: observation.role, + text: observation.text + })); + if (blocks.length === 0) return { ok: true, todos: [] }; + + try { + return { + ok: true, + todos: await requestTodos(config, secrets.llmApiKey, JSON.stringify({ + blocks, + taskChains: buildTaskChains(visibleObservations) + })) + }; + } catch (error) { + const reason = (error as Error).message; + if (reason === "timeout") return { ok: false, warning: "llm_timeout", reason, retryable: true }; + if (reason === "invalid_json" || reason === "invalid_schema") { + return { ok: false, warning: "llm_output_invalid", reason, retryable: true }; + } + return { + ok: false, + warning: "llm_provider_failed", + reason: providerFailureReason(reason), + retryable: true + }; + } + }; +} + +function buildTaskChains(observations: ObservationForOrganize[]): Array> { + const bySession = new Map(); + for (const observation of observations) { + const group = bySession.get(observation.sessionId) ?? []; + group.push(observation); + bySession.set(observation.sessionId, group); + } + const chains: Array> = []; + for (const [sessionId, group] of bySession) { + group.sort((a, b) => a.createdAt.localeCompare(b.createdAt)); + let activeChain: { + user: ObservationForOrganize; + replies: ObservationForOrganize[]; + continuationIds: string[]; + } | null = null; + const flush = () => { + if (!activeChain) return; + const latestReply = activeChain.replies.at(-1); + const allText = [activeChain.user.text, ...activeChain.replies.map((reply) => reply.text)].join(" "); + chains.push({ + chainId: `${sessionId}:${activeChain.user.id}`, + sessionId, + userObservationId: activeChain.user.id, + userIntent: activeChain.user.text, + assistantObservationIds: activeChain.replies.map((reply) => reply.id), + latestAssistantObservationId: latestReply?.id, + latestAssistantReply: latestReply?.text ?? "", + latestStatusObservationId: latestReply?.id, + latestStatus: latestReply?.text ?? "", + completionState: inferCompletionState(allText), + completionSummary: latestReply?.text ?? "", + nextStep: inferNextStep(latestReply?.text ?? activeChain.user.text), + observationIds: [ + activeChain.user.id, + ...activeChain.replies.map((reply) => reply.id), + ...activeChain.continuationIds + ], + dedupeKey: simpleDedupeKey(activeChain.user.text), + source: activeChain.user.source, + timestamp: activeChain.user.createdAt + }); + activeChain = null; + }; + for (const current of group) { + if (current.role === "user") { + if (activeChain && isLowInformationUserTurn(current.text)) { + activeChain.continuationIds.push(current.id); + continue; + } + flush(); + activeChain = { user: current, replies: [], continuationIds: [] }; + continue; + } + if (current.role === "assistant" && activeChain) activeChain.replies.push(current); + } + flush(); + } + return chains; +} + +async function requestTodos(config: AppConfig["llm"], apiKey: string, userContent: string): Promise { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), config.timeoutMs); + try { + const response = await fetch(chatCompletionsUrl(config.endpoint), { + method: "POST", + headers: { + "authorization": `Bearer ${apiKey}`, + "content-type": "application/json" + }, + body: JSON.stringify({ + model: config.model, + temperature: 0, + reasoning_effort: config.thinkingDepth, + response_format: { type: "json_object" }, + messages: [ + { role: "system", content: TODO_EXTRACTION_PROMPT }, + { role: "user", content: userContent } + ] + }), + signal: controller.signal + }); + const text = await response.text(); + if (!response.ok) throw new Error(`http_${response.status}`); + return parseChatCompletionResponse(text); + } catch (error) { + if ((error as Error).name === "AbortError") throw new Error("timeout"); + if (isKnownRunnerError((error as Error).message)) throw error; + throw new Error("network_error"); + } finally { + clearTimeout(timer); + } +} + +function chatCompletionsUrl(endpoint: string): string { + return `${endpoint.replace(/\/+$/u, "")}/chat/completions`; +} + +function parseChatCompletionResponse(text: string): LlmTodoCandidate[] { + const body = parseJsonRecord(text); + const direct = parseTodoEnvelope(body); + if (direct) return direct; + + const content = (((body.choices as unknown[])?.[0] as Record | undefined)?.message as Record | undefined)?.content; + if (typeof content !== "string") throw new Error("invalid_schema"); + const envelope = parseJsonRecord(stripJsonFence(content)); + const todos = parseTodoEnvelope(envelope); + if (!todos) throw new Error("invalid_schema"); + return todos; +} + +function parseJsonRecord(text: string): Record { + try { + const parsed = JSON.parse(text) as unknown; + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid_schema"); + return parsed as Record; + } catch (error) { + if ((error as Error).message === "invalid_schema") throw error; + throw new Error("invalid_json"); + } +} + +function stripJsonFence(text: string): string { + const trimmed = text.trim(); + const match = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/iu); + return match?.[1] ?? trimmed; +} + +function parseTodoEnvelope(envelope: Record): LlmTodoCandidate[] | null { + if (!Array.isArray(envelope.todos)) return null; + const todos: LlmTodoCandidate[] = []; + for (const item of envelope.todos) { + if (!item || typeof item !== "object" || Array.isArray(item)) return null; + const record = item as Record; + if ( + typeof record.title !== "string" || + typeof record.description !== "string" || + typeof record.confidence !== "number" || + typeof record.sourceObservationId !== "string" || + typeof record.quote !== "string" || + typeof record.dedupeKey !== "string" + ) return null; + todos.push({ + title: record.title, + description: record.description, + confidence: record.confidence, + sourceObservationId: record.sourceObservationId, + quote: record.quote, + dedupeKey: record.dedupeKey + }); + } + return todos; +} + +function providerFailureReason(reason: string): string { + if (/^http_\d{3}$/u.test(reason)) return reason; + if (reason === "network_error") return reason; + return "unknown_provider_error"; +} + +function isKnownRunnerError(reason: string): boolean { + return reason === "timeout" || + reason === "invalid_json" || + reason === "invalid_schema" || + reason === "network_error" || + /^http_\d{3}$/u.test(reason); +} + +function isLowInformationUserTurn(text: string): boolean { + return /^(?:继续|重试|再来一次|继续推进|继续吧|retry|continue|go on|again)$/iu.test(text.trim()); +} + +function inferCompletionState(text: string): "completed" | "blocked" | "in_progress" | "unknown" { + if (/(?:已完成|已通过|done|completed|fixed|resolved)/iu.test(text)) return "completed"; + if (/(?:blocked|阻塞|失败|failed|error|timeout|无法|不能)/iu.test(text)) return "blocked"; + if (/(?:剩余|remaining|下一步|next|todo|需要|will|待)/iu.test(text)) return "in_progress"; + return "unknown"; +} + +function inferNextStep(text: string): string { + const normalized = text.replace(/\s+/gu, " ").trim(); + const match = normalized.match(/(?:remaining|下一步|后续|还需要|需要|仍需|still remaining)[::]?\s*([^。.!?]+[。.!?]?)/iu); + return (match?.[1] ?? normalized).trim(); +} + +function simpleDedupeKey(text: string): string { + const normalized = text + .toLowerCase() + .replace(/[`"'“”‘’]/gu, "") + .replace(/[^a-z0-9\u4e00-\u9fff]+/giu, "-") + .replace(/^-+|-+$/gu, ""); + return normalized.slice(0, 80) || "todo-chain"; +} diff --git a/src/extract/rules.ts b/src/extract/rules.ts new file mode 100644 index 00000000..9ca35776 --- /dev/null +++ b/src/extract/rules.ts @@ -0,0 +1,50 @@ +import { createHash } from "node:crypto"; + +export interface RuleCandidate { + title: string; + description: string; + mergeKey: string; +} + +const ACTION_PATTERN = /\b(fix|add|implement|create|update)\b|需要|修复|添加|实现/i; + +export function extractRuleCandidate(text: string): RuleCandidate | null { + const normalized = text.replace(/\s+/g, " ").trim(); + if (!normalized || !ACTION_PATTERN.test(normalized)) return null; + + const title = titleFromText(normalized); + if (!title) return null; + + return { + title, + description: normalized, + mergeKey: normalizeMergeKey(title) + }; +} + +function titleFromText(text: string): string { + const withoutPrefix = text + .replace(/^please\s+/i, "") + .replace(/^(todo|need|needs):?\s*/i, "") + .replace(/^to\s+/i, "") + .replace(/^(need|needs)\s+to\s+/i, "") + .replace(/[.!?。!?]+$/g, "") + .trim(); + if (!ACTION_PATTERN.test(withoutPrefix)) return ""; + + const sentence = withoutPrefix.split(/(?:\s+so\s+|\s+because\s+|[,;,;])/i)[0]?.trim() ?? ""; + if (!sentence) return ""; + const titled = sentence.charAt(0).toUpperCase() + sentence.slice(1, 100); + return titled.replace(/\b(cli|api|http|json)\b/gi, (match) => match.toUpperCase()); +} + +function normalizeMergeKey(title: string): string { + return title + .toLowerCase() + .replace(/[^a-z0-9\u4e00-\u9fff]+/g, " ") + .trim(); +} + +export function stableId(...parts: string[]): string { + return createHash("sha1").update(parts.join("\0")).digest("hex"); +} diff --git a/src/functions/access-tracker.ts b/src/functions/access-tracker.ts deleted file mode 100644 index 67914092..00000000 --- a/src/functions/access-tracker.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { KV } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import { logger } from "../logger.js"; - -const RECENT_CAP = 20; - -export interface AccessLog { - memoryId: string; - count: number; - lastAt: string; - recent: number[]; -} - -export function emptyAccessLog(memoryId: string): AccessLog { - return { memoryId, count: 0, lastAt: "", recent: [] }; -} - -export function normalizeAccessLog(raw: unknown): AccessLog { - const r = (raw ?? {}) as Partial; - const rawCount = - typeof r.count === "number" && Number.isFinite(r.count) ? r.count : 0; - const count = Math.max(0, Math.floor(rawCount)); - const rawRecent = Array.isArray(r.recent) - ? r.recent.filter( - (x): x is number => typeof x === "number" && Number.isFinite(x), - ) - : []; - const recent = - rawRecent.length > RECENT_CAP ? rawRecent.slice(-RECENT_CAP) : rawRecent; - return { - memoryId: typeof r.memoryId === "string" ? r.memoryId : "", - count: Math.max(count, recent.length), - lastAt: typeof r.lastAt === "string" ? r.lastAt : "", - recent, - }; -} - -export async function getAccessLog( - kv: StateKV, - memoryId: string, -): Promise { - try { - const raw = await kv.get(KV.accessLog, memoryId); - if (!raw) return emptyAccessLog(memoryId); - const normalized = normalizeAccessLog(raw); - if (!normalized.memoryId) normalized.memoryId = memoryId; - return normalized; - } catch { - return emptyAccessLog(memoryId); - } -} - -export async function recordAccess( - kv: StateKV, - memoryId: string, - timestampMs?: number, -): Promise { - if (!memoryId) return; - const ts = timestampMs ?? Date.now(); - try { - await withKeyedLock(`mem:access:${memoryId}`, async () => { - const existing = await getAccessLog(kv, memoryId); - existing.count += 1; - existing.lastAt = new Date(ts).toISOString(); - existing.recent.push(ts); - if (existing.recent.length > RECENT_CAP) { - existing.recent = existing.recent.slice(-RECENT_CAP); - } - await kv.set(KV.accessLog, memoryId, existing); - }); - } catch (err) { - try { - logger.warn("recordAccess failed", { - memoryId, - error: err instanceof Error ? err.message : String(err), - }); - } catch {} - } -} - -export async function recordAccessBatch( - kv: StateKV, - memoryIds: string[], - timestampMs?: number, -): Promise { - if (!memoryIds || memoryIds.length === 0) return; - const ts = timestampMs ?? Date.now(); - const unique = Array.from(new Set(memoryIds.filter(Boolean))); - await Promise.allSettled(unique.map((id) => recordAccess(kv, id, ts))); -} - -export async function deleteAccessLog( - kv: StateKV, - memoryId: string, -): Promise { - if (!memoryId) return; - try { - await withKeyedLock(`mem:access:${memoryId}`, async () => { - await kv.delete(KV.accessLog, memoryId); - }); - } catch {} -} - diff --git a/src/functions/action-candidates.ts b/src/functions/action-candidates.ts deleted file mode 100644 index f0815577..00000000 --- a/src/functions/action-candidates.ts +++ /dev/null @@ -1,574 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, nearDuplicateTitle } from "../state/schema.js"; -import type { Action, CompressedObservation, ReviewQueueItem, Session } from "../types.js"; - -export interface ActionCandidate { - title: string; - description: string; - priority: number; - source: "observation" | "browser-review"; - sourceObservationIds: string[]; - tags: string[]; - confidence: number; - reason: "todo" | "follow_up" | "command_failed" | "blocked" | "validation_failed"; - duplicateHint?: string; -} - -export interface ActionCandidateOptions { - existingActions?: Action[]; - existingReviewItems?: ReviewQueueItem[]; -} - -type BrowserTurn = { role?: string; text?: string }; - -const READ_ONLY_TYPES = new Set(["file_read", "search", "web_fetch"]); -const ACTION_VERB_PATTERN = "(修复|修正|补充|实现|调整|验证|排查|定位|跟进|整理|生成|上传|提交|创建|更新|移除|删除|处理|审查|合并|推送|构建|重试|重新(?:运行|跑))"; -const ENGLISH_ACTION_VERB = "(fix|add|update|create|remove|validate|retry|rerun|re-run|submit|handle|implement|investigate|debug|resolve)"; -const ENGLISH_FOLLOW_UP_PATTERN = new RegExp(`\\b(follow up|follow-up|need to|must)\\b.{0,80}\\b${ENGLISH_ACTION_VERB}\\b`, "i"); -const ACTION_DESCRIPTION_MAX_LENGTH = 180; -const PROCESS_CHECK_PHRASES = /(?:做最后一次状态确认|最后一次状态确认|启动后做健康检查|做健康检查|健康检查已完成|确认工作区干净|确认当前分支|确认 PR 链接|服务可用|重启 Codex desktop app 后再测一次|重启 Codex desktop app 后再测|重启后再测一次|重启后再测)/i; -const PROCESS_CHECK_REWRITEABLE = /(?:重启 Codex desktop app 后再测一次|重启 Codex desktop app 后再测|重启后再测一次|重启后再测)/i; -const DURABLE_DELIVERABLE_TERMS = new RegExp(ACTION_VERB_PATTERN + `|\\b${ENGLISH_ACTION_VERB}\\b`, "i"); - -function normalizeText(value: string | undefined): string { - return String(value || "").replace(/\s+/g, " ").trim(); -} - -function normalizedKey(value: string): string { - return normalizeText(value) - .toLowerCase() - .replace(/[,。!?;:,.!?;:]/g, "") - .replace(/\s+/g, " "); -} - -// STEP-08 PR 4/5: suppress near-identical titles the exact-key dedup misses -// (reworded CJK clusters). nearDuplicateTitle (schema.ts) is the shared policy -// — similarity bar plus discriminator vetoes for negation / number / Latin -// content-word differences — so both extractors stay in lockstep. -function isNearDuplicateTitle(normalizedTitle: string, seenTitles: string[]): boolean { - return seenTitles.some((seen) => nearDuplicateTitle(normalizedTitle, seen)); -} - -function isJsonShaped(text: string): boolean { - const trimmed = text.trim(); - if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false; - try { - JSON.parse(trimmed); - return true; - } catch { - return false; - } -} - -function isToolTrace(text: string): boolean { - const lower = normalizeText(text).toLowerCase(); - if (!lower || isJsonShaped(lower)) return true; - if (/"(?:cmd|command|workdir|yield_time_ms|max_output_tokens)"\s*:/.test(lower)) return true; - if (/toolinput|tooloutput|function_id|tooluseid|tooluse|call_[a-z0-9]+|chunk id|wall time|process exited/.test(lower)) return true; - if (/^\/(?:tmp|users|var|private|volumes)\//i.test(lower)) return true; - if (/^\s*(?:gh|git|npm|pnpm|yarn|python3?|node|curl)\s+[^\n]*(?:--json|--limit|--workdir|--max-output|--yield-time|status|show|list|run|test|install|build)\b/i.test(lower)) return true; - if (/^(?:json|state|limit)\s+[\w.-]+/i.test(lower)) return true; - if (/\b(?:namewithowner|headrefname|baserefname|databaseid|tooluseid)\b/i.test(lower)) return true; - if (/^⏺/.test(lower)) return true; - if (/\b(?:bash|shell|exec)\(/i.test(lower)) return true; - if (/^[a-z][a-z0-9_-]*-[0-9a-f]{6,}`?$/i.test(lower)) return true; - return false; -} - -function candidateText(obs: CompressedObservation): string { - return normalizeText([obs.narrative, obs.subtitle, ...(obs.facts || [])].filter(Boolean).join(" ")); -} - -function isMarkdownPlanDocument(text: string): boolean { - const raw = String(text || ""); - if (!raw.trim()) return false; - const hasPlanHeading = /^#{1,3}\s+.*(?:计划|Plan)\s*$/im.test(raw); - const sectionMatches = raw.match(/^#{1,3}\s+(?:Summary|Key Changes|Test Plan|Assumptions|Public API|Implementation|执行步骤|验证命令)\b/img) || []; - const compact = raw.replace(/\s+/g, " "); - const compactPlanSectionCount = [ - /#{1,3}\s+Summary\b/i, - /#{1,3}\s+Key Changes\b/i, - /#{1,3}\s+Test Plan\b/i, - /#{1,3}\s+Assumptions\b/i, - /#{1,3}\s+Implementation\b/i, - /#{1,3}\s+执行步骤\b/i, - /#{1,3}\s+验证命令\b/i, - ].filter((pattern) => pattern.test(compact)).length; - const compactPlan = /#{1,3}\s+[^#]{0,160}(?:计划|Plan)/i.test(compact) && - (compactPlanSectionCount >= 2 || /#{1,3}\s+Summary\b/i.test(compact)); - return (hasPlanHeading && sectionMatches.length >= 2) || compactPlan; -} - -function splitCandidateSentences(text: string): string[] { - const raw = String(text || "").replace(/\r\n/g, "\n"); - const output: string[] = []; - for (const rawLine of raw.split(/\n+/)) { - const line = rawLine.trim().replace(/^[-*]\s+/, "").trim(); - if (!line || /^#{1,6}\s/.test(line)) continue; - if (/^(Summary|Key Changes|Test Plan|Assumptions|Public API|Implementation|验证命令|执行步骤)\b[::]?$/i.test(line)) continue; - const parts = line.match(/[^。!?!?]+[。!?!?]?/gu) || [line]; - for (const part of parts) { - const sentence = part.trim(); - if (sentence) output.push(sentence); - } - } - return output; -} - -function conciseDescription(sentence: string): string { - const normalized = normalizeText(sentence); - if (normalized.length <= ACTION_DESCRIPTION_MAX_LENGTH) return normalized; - return normalized.slice(0, ACTION_DESCRIPTION_MAX_LENGTH).replace(/[,,;;::\s]+$/u, ""); -} - -function cleanTitle(text: string): string { - const stripped = normalizeText(text) - .replace(/^(todo|fixme)\s*[::-]\s*/i, "") - .replace(/^(下一步|后续|待办)\s*[::-]?\s*/u, "") - .replace(/^(请|需要|必须)\s*/u, "") - .replace(/^(follow up|follow-up|fix)\s*[::-]?\s*/i, ""); - const first = stripped.split(/[。!?\n]/u)[0] || stripped; - const trimmed = first.replace(/[。!?;;,.,]+$/u, "").trim(); - const chars = Array.from(trimmed); - if (chars.length <= 80) return trimmed; - const head = chars.slice(0, 80); - let boundary = -1; - for (let i = head.length - 1; i >= 24; i--) { - if (/[,,;;、\s]/u.test(head[i])) { - boundary = i; - break; - } - } - return (boundary > 0 ? head.slice(0, boundary) : head).join("").replace(/[,,;;::\s]+$/u, ""); -} - -function stripActionPrefix(text: string): string { - return normalizeText(text) - .replace(/^(todo|fixme)\s*[::-]\s*/i, "") - .replace(/^(下一步|后续|待办)\s*[::-]?\s*/u, "") - .replace(/^(请|需要|必须)\s*/u, "") - .replace(/^(follow up|follow-up|fix)\s*[::-]?\s*/i, ""); -} - -function titleFromText(text: string, fallback: string): string { - const followUpMatch = text.match(new RegExp(`(?:下一步|后续)\\s*(?:请|需要|必须)?\\s*(${ACTION_VERB_PATTERN}[^。!?\\n]*)`, "u"))?.[1]; - const failureRepairMatch = text.match(new RegExp(`(?:验证未通过|验证失败|测试未通过|测试失败|command failed|exit code [1-9]\\d*|exited with code [1-9]\\d*)[,,。\\s]*(?:请|需要|必须)?\\s*(${ACTION_VERB_PATTERN}[^。!?\\n]*)`, "iu"))?.[1]; - const explicit = text.match(/(?:^|[\s((])(?:TODO|FIXME)\b\s*[::-]\s*([^。!?\n]+)/i)?.[1] || - text.match(/待办\s*[::-]\s*([^。!?\n]+)/u)?.[1] || - followUpMatch || - failureRepairMatch || - text.match(/\b(?:follow up|follow-up|fix)\b\s*[::-]?\s*([^。!?\n]+)/i)?.[1]; - const title = cleanTitle(explicit || text); - return title || fallback; -} - -function hasExplicitFollowUpAction(text: string): boolean { - return new RegExp(`(?:下一步|后续)\\s*(?:请|需要|必须)?\\s*${ACTION_VERB_PATTERN}`, "u").test(text) || - new RegExp(`(?:需要|必须)\\s*${ACTION_VERB_PATTERN}`, "u").test(text) || - ENGLISH_FOLLOW_UP_PATTERN.test(text); -} - -function hasExplicitFailureRepair(text: string): boolean { - const failure = /验证未通过|验证失败|测试未通过|测试失败|失败|\b(failed|failing|command failed|exit code [1-9]\d*|exited with code [1-9]\d*)\b/iu.test(text); - if (!failure) return false; - return new RegExp(ACTION_VERB_PATTERN, "u").test(text) || new RegExp(`\\b${ENGLISH_ACTION_VERB}\\b`, "i").test(text); -} - -function isStatusReport(text: string): boolean { - const t = normalizeText(text); - if (/服务可用|页面已经能返回/.test(t)) return true; - if (/\b(?:Viewer|Health)\b\s*[::]\s*(?:\[|https?:\/\/)/i.test(t)) return true; - return false; -} - -function isProcessCheckText(text: string): boolean { - return PROCESS_CHECK_PHRASES.test(normalizeText(text)); -} - -function isRewriteableProcessCheck(text: string): boolean { - return PROCESS_CHECK_REWRITEABLE.test(normalizeText(text)); -} - -function isPureProcessCheck(text: string): boolean { - const normalized = normalizeText(text); - if (!normalized || !isProcessCheckText(normalized)) return false; - if (isRewriteableProcessCheck(normalized)) return false; - return !DURABLE_DELIVERABLE_TERMS.test(stripActionPrefix(normalized).replace(PROCESS_CHECK_PHRASES, "")); -} - -function confidenceFor(reason: ActionCandidate["reason"], description: string): number { - if (isRewriteableProcessCheck(description)) return 0.56; - return reason === "follow_up" ? 0.62 : 0.72; -} - -function sentenceReason(text: string): ActionCandidate["reason"] | null { - if (isToolTrace(text) || isStatusReport(text)) return null; - if (isPureProcessCheck(text)) return null; - if (/不应生成待办|不要生成待办|无需生成待办|不是待办|not an action|not a todo/i.test(text)) return null; - if (/(?:^|[\s((])(?:TODO|FIXME)\b\s*[::-]\s*\S/i.test(text) || /待办\s*[::-]\s*\S/u.test(text)) return "todo"; - if (hasExplicitFollowUpAction(text)) return "follow_up"; - if (/\bblocked\b|未完成|被阻塞|阻塞/u.test(text)) return "blocked"; - if (hasExplicitFailureRepair(text)) return "validation_failed"; - return null; -} - -function extractActionSentence(text: string): { text: string; reason: ActionCandidate["reason"] } | null { - if (isToolTrace(text) || isMarkdownPlanDocument(text)) return null; - for (const sentence of splitCandidateSentences(text)) { - const reason = sentenceReason(sentence); - if (!reason) continue; - return { text: conciseDescription(sentence), reason }; - } - return null; -} - -function reasonFromObservation(obs: CompressedObservation, text: string): ActionCandidate["reason"] | null { - if (READ_ONLY_TYPES.has(obs.type)) return null; - return extractActionSentence(text)?.reason || null; -} - -function reasonFromTurn(text: string): ActionCandidate["reason"] | null { - return extractActionSentence(text)?.reason || null; -} - -function priorityFor(reason: ActionCandidate["reason"]): number { - if (reason === "command_failed" || reason === "validation_failed" || reason === "blocked") return 8; - if (reason === "todo") return 6; - return 5; -} - -function tagsFor(reason: ActionCandidate["reason"]): string[] { - const tags = ["action-candidate"]; - if (reason === "follow_up") tags.push("follow-up"); - if (reason === "todo") tags.push("todo"); - if (reason === "blocked") tags.push("blocked"); - if (reason === "command_failed" || reason === "validation_failed") tags.push("repair"); - return tags; -} - -function candidateKey(title: string, description: string): string { - return `${normalizedKey(title)}:${normalizedKey(description)}`; -} - -function candidateKeys(title: string, description: string): string[] { - return [candidateKey(title, description), normalizedKey(title)].filter(Boolean); -} - -function existingKeys(options: ActionCandidateOptions): Set { - const keys = new Set(); - for (const action of options.existingActions || []) { - if (action.status === "pending" || action.status === "active" || action.status === "blocked") { - keys.add(candidateKey(action.title, action.description || "")); - keys.add(normalizedKey(action.title)); - } - } - for (const item of options.existingReviewItems || []) { - if (item.kind === "action") { - keys.add(candidateKey(item.title, item.content || "")); - keys.add(normalizedKey(item.title)); - } - } - return keys; -} - -// Seed the near-dup guard only from still-open work — pending/active/blocked -// actions and non-terminal action reviews. (Unlike existingKeys, which applies -// no status filter to reviews: exact-dup suppression keeps matching every -// review, but a near-dup of an approved/dismissed review may reappear since the -// work could have regressed.) -function existingActiveTitles(options: ActionCandidateOptions): string[] { - const titles: string[] = []; - for (const action of options.existingActions || []) { - if (action.status !== "pending" && action.status !== "active" && action.status !== "blocked") continue; - const title = normalizedKey(action.title); - if (title) titles.push(title); - } - for (const item of options.existingReviewItems || []) { - if (item.kind !== "action") continue; - if (item.status === "approved" || item.status === "dismissed") continue; - const title = normalizedKey(item.title); - if (title) titles.push(title); - } - return titles; -} - -function pushCandidate( - output: ActionCandidate[], - seen: Set, - existing: Set, - seenTitles: string[], - candidate: ActionCandidate, -): void { - const key = candidateKey(candidate.title, candidate.description); - const titleKey = normalizedKey(candidate.title); - if (!titleKey || seen.has(key) || seen.has(titleKey) || existing.has(key) || existing.has(titleKey)) return; - if (isNearDuplicateTitle(titleKey, seenTitles)) return; - seen.add(key); - seen.add(titleKey); - seenTitles.push(titleKey); - output.push(candidate); -} - -export function extractActionCandidatesFromObservations( - observations: CompressedObservation[], - options: ActionCandidateOptions = {}, -): ActionCandidate[] { - const output: ActionCandidate[] = []; - const seen = new Set(); - const existing = existingKeys(options); - const seenTitles = existingActiveTitles(options); - for (const obs of observations) { - const text = candidateText(obs); - if (READ_ONLY_TYPES.has(obs.type)) continue; - const extracted = extractActionSentence(text); - const reason = extracted?.reason || reasonFromObservation(obs, text); - const description = extracted?.text || ""; - if (!reason || !description) continue; - const title = titleFromText(description, obs.title || "待处理行动"); - pushCandidate(output, seen, existing, seenTitles, { - title, - description, - priority: priorityFor(reason), - source: "observation", - sourceObservationIds: [obs.id], - tags: tagsFor(reason), - confidence: confidenceFor(reason, description), - reason, - }); - } - return output; -} - -export function extractActionCandidatesFromTurns( - turns: BrowserTurn[], - options: ActionCandidateOptions = {}, -): ActionCandidate[] { - const output: ActionCandidate[] = []; - const seen = new Set(); - const existing = existingKeys(options); - const seenTitles = existingActiveTitles(options); - for (const turn of turns) { - if (turn.role !== "user") continue; - const text = normalizeText(turn.text); - const extracted = extractActionSentence(text); - const reason = extracted?.reason || reasonFromTurn(text); - const description = extracted?.text || ""; - if (!reason || !description) continue; - const title = titleFromText(description, "待处理行动"); - pushCandidate(output, seen, existing, seenTitles, { - title, - description, - priority: priorityFor(reason), - source: "browser-review", - sourceObservationIds: [], - tags: tagsFor(reason), - confidence: confidenceFor(reason, description), - reason, - }); - } - return output; -} - -function reviewItemId(): string { - return `review_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 10)}`; -} - -export function actionReviewItemFromCandidate(opts: { - candidate: ActionCandidate; - now: string; - source: ReviewQueueItem["source"]; - page: ReviewQueueItem["page"]; - conversation?: ReviewQueueItem["conversation"]; - basePayload: Record; -}): ReviewQueueItem { - return { - id: `review_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 10)}`, - createdAt: opts.now, - updatedAt: opts.now, - status: "pending", - kind: "action", - title: opts.candidate.title, - content: opts.candidate.description, - source: opts.source, - page: opts.page, - ...(opts.conversation ? { conversation: opts.conversation } : {}), - payload: { - ...opts.basePayload, - actionCandidate: { - priority: opts.candidate.priority, - reason: opts.candidate.reason, - confidence: opts.candidate.confidence, - duplicateHint: opts.candidate.duplicateHint, - sourceObservationIds: opts.candidate.sourceObservationIds, - }, - tags: opts.candidate.tags, - }, - }; -} - -/** - * Extract action candidates from browser conversation turns and build - * deduped review drafts. Shared by the REST review-create trigger and the - * viewer-server review fallback so both browser-capture paths produce todos - * through one implementation (single dedup source). Does not persist — the - * caller writes the returned drafts to KV.reviewQueue. - */ -export async function buildTurnActionDrafts( - kv: { list(scope: string): Promise }, - opts: { - turns: BrowserTurn[]; - now: string; - source: ReviewQueueItem["source"]; - page: ReviewQueueItem["page"]; - conversation?: ReviewQueueItem["conversation"]; - basePayload: Record; - }, -): Promise { - if (!opts.turns.length) return []; - const [existingActions, existingReviewItems] = await Promise.all([ - kv.list(KV.actions).catch(() => []), - kv.list(KV.reviewQueue).catch(() => []), - ]); - return extractActionCandidatesFromTurns(opts.turns, { - existingActions, - existingReviewItems, - }).map((candidate) => - actionReviewItemFromCandidate({ - candidate, - now: opts.now, - source: opts.source, - page: opts.page, - conversation: opts.conversation, - basePayload: opts.basePayload, - }), - ); -} - -function nonEmptySessionValue(value: string | undefined): string | undefined { - return typeof value === "string" && value.trim() ? value.trim() : undefined; -} - -function actionReviewItemFromObservationCandidate( - candidate: ActionCandidate, - session: Session, - now: string, -): ReviewQueueItem { - const sourceSessionProject = nonEmptySessionValue(session.project); - const sourceSessionCwd = nonEmptySessionValue(session.cwd); - const project = sourceSessionProject || sourceSessionCwd; - return { - id: reviewItemId(), - createdAt: now, - updatedAt: now, - status: "pending", - kind: "action", - title: candidate.title, - content: candidate.description, - source: "viewer", - payload: { - ...(project ? { project } : {}), - sourceSessionId: session.id, - ...(sourceSessionProject ? { sourceSessionProject } : {}), - ...(sourceSessionCwd ? { sourceSessionCwd } : {}), - tags: candidate.tags, - actionCandidate: { - priority: candidate.priority, - reason: candidate.reason, - confidence: candidate.confidence, - duplicateHint: candidate.duplicateHint, - sourceObservationIds: candidate.sourceObservationIds, - }, - }, - }; -} - -function sessionSortTime(session: Session): string { - return session.endedAt || session.startedAt || ""; -} - -function clampPositiveInt(value: unknown, fallback: number, max: number): number { - const parsed = typeof value === "number" ? value : parseInt(String(value ?? ""), 10); - if (!Number.isFinite(parsed) || parsed < 1) return fallback; - return Math.min(max, Math.floor(parsed)); -} - -export function registerActionCandidateFunctions(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction( - "mem::action-candidates-generate", - async ( - data: { - maxSessions?: number; - maxObservationsPerSession?: number; - project?: string; - } = {}, - ): Promise<{ - success: true; - generated: number; - items: ReviewQueueItem[]; - scannedSessions: number; - scannedObservations: number; - }> => { - const maxSessions = clampPositiveInt(data.maxSessions, 20, 100); - const maxObservationsPerSession = clampPositiveInt(data.maxObservationsPerSession, 200, 1000); - const project = typeof data.project === "string" && data.project.trim() - ? data.project.trim() - : undefined; - - const [actions, reviewItems, allSessions] = await Promise.all([ - kv.list(KV.actions).catch(() => []), - kv.list(KV.reviewQueue).catch(() => []), - kv.list(KV.sessions).catch(() => []), - ]); - const sessions = allSessions - .filter((session) => !project || session.project === project || session.cwd === project) - .sort((a, b) => sessionSortTime(b).localeCompare(sessionSortTime(a))) - .slice(0, maxSessions); - - let scannedObservations = 0; - const candidates: Array<{ candidate: ActionCandidate; session: Session }> = []; - const generatedKeys = new Set(); - // Cross-session near-dup guard: per-session extraction already dedups - // within a session, but the same task often recurs across sessions with - // slight title drift. Seed from still-open existing work. - const generatedTitles = existingActiveTitles({ - existingActions: actions, - existingReviewItems: reviewItems, - }); - for (const session of sessions) { - const observations = (await kv.list( - KV.observations(session.id), - ).catch(() => [])) - .sort((a, b) => (b.timestamp || "").localeCompare(a.timestamp || "")) - .slice(0, maxObservationsPerSession); - scannedObservations += observations.length; - const sessionCandidates = extractActionCandidatesFromObservations(observations, { - existingActions: actions, - existingReviewItems: reviewItems, - }); - for (const candidate of sessionCandidates) { - const keys = candidateKeys(candidate.title, candidate.description); - if (keys.some((key) => generatedKeys.has(key))) continue; - const titleKey = normalizedKey(candidate.title); - if (isNearDuplicateTitle(titleKey, generatedTitles)) continue; - keys.forEach((key) => generatedKeys.add(key)); - if (titleKey) generatedTitles.push(titleKey); - candidates.push({ candidate, session }); - } - } - - const now = new Date().toISOString(); - const items = candidates.map(({ candidate, session }) => - actionReviewItemFromObservationCandidate(candidate, session, now), - ); - await Promise.all(items.map((item) => kv.set(KV.reviewQueue, item.id, item))); - return { - success: true, - generated: items.length, - items, - scannedSessions: sessions.length, - scannedObservations, - }; - }, - ); -} diff --git a/src/functions/actions.ts b/src/functions/actions.ts deleted file mode 100644 index 9f283b7d..00000000 --- a/src/functions/actions.ts +++ /dev/null @@ -1,310 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import type { Action, ActionEdge } from "../types.js"; -import { recordAudit } from "./audit.js"; - -export function registerActionsFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::action-create", - async (data: { - title: string; - description?: string; - priority?: number; - createdBy?: string; - project?: string; - tags?: string[]; - parentId?: string; - status?: Action["status"]; - metadata?: Record; - sourceObservationIds?: string[]; - sourceMemoryIds?: string[]; - edges?: Array<{ type: string; targetActionId: string }>; - id?: string; - }) => { - if (!data.title || typeof data.title !== "string") { - return { success: false, error: "title is required" }; - } - const validStatuses = ["pending", "active", "done", "blocked", "cancelled"]; - if (data.status && !validStatuses.includes(data.status)) { - return { success: false, error: `invalid status: ${data.status}` }; - } - - return withKeyedLock("mem:actions", async () => { - const now = new Date().toISOString(); - const id = data.id && /^act_[A-Za-z0-9_-]+$/.test(data.id) ? data.id : generateId("act"); - const action: Action = { - id, - title: data.title.trim(), - description: (data.description || "").trim(), - status: data.status || "pending", - priority: Math.max(1, Math.min(10, data.priority || 5)), - createdAt: now, - updatedAt: now, - createdBy: data.createdBy || "unknown", - project: data.project, - tags: data.tags || [], - sourceObservationIds: data.sourceObservationIds || [], - sourceMemoryIds: data.sourceMemoryIds || [], - parentId: data.parentId, - metadata: data.metadata, - }; - const existing = await kv.get(KV.actions, action.id); - if (existing) return { success: true, action: existing, edges: [] }; - - if (data.parentId) { - const parent = await kv.get(KV.actions, data.parentId); - if (!parent) { - return { success: false, error: "parent action not found" }; - } - } - - const validEdgeTypes = [ - "requires", - "unlocks", - "spawned_by", - "gated_by", - "conflicts_with", - ]; - const pendingEdges: ActionEdge[] = []; - let hasRequires = false; - if (data.edges && Array.isArray(data.edges)) { - for (const e of data.edges) { - if (!validEdgeTypes.includes(e.type)) { - return { success: false, error: `invalid edge type: ${e.type}` }; - } - const targetAction = await kv.get(KV.actions, e.targetActionId); - if (!targetAction) { - return { success: false, error: `target action not found: ${e.targetActionId}` }; - } - if (e.type === "requires") hasRequires = true; - pendingEdges.push({ - id: generateId("ae"), - type: e.type as ActionEdge["type"], - sourceActionId: action.id, - targetActionId: e.targetActionId, - createdAt: now, - }); - } - } - - if (hasRequires) { - action.status = "blocked"; - } - - await kv.set(KV.actions, action.id, action); - await recordAudit(kv, "action_create", "mem::action-create", [action.id], { - actor: data.createdBy || "unknown", - action, - edges: pendingEdges, - }); - - for (const edge of pendingEdges) { - await kv.set(KV.actionEdges, edge.id, edge); - } - - return { success: true, action, edges: pendingEdges }; - }); - }, - ); - - sdk.registerFunction("mem::action-update", - async (data: { - actionId: string; - status?: Action["status"]; - title?: string; - description?: string; - priority?: number; - assignedTo?: string; - result?: string; - tags?: string[]; - }) => { - if (!data.actionId) { - return { success: false, error: "actionId is required" }; - } - - return withKeyedLock(`mem:action:${data.actionId}`, async () => { - const action = await kv.get(KV.actions, data.actionId); - if (!action) { - return { success: false, error: "action not found" }; - } - const before = { ...action }; - - if (data.status !== undefined) action.status = data.status; - if (data.title !== undefined) action.title = data.title.trim(); - if (data.description !== undefined) - action.description = data.description.trim(); - if (data.priority !== undefined) - action.priority = Math.max(1, Math.min(10, data.priority)); - if (data.assignedTo !== undefined) action.assignedTo = data.assignedTo; - if (data.result !== undefined) action.result = data.result; - if (data.tags !== undefined) action.tags = data.tags; - action.updatedAt = new Date().toISOString(); - - await kv.set(KV.actions, action.id, action); - await recordAudit(kv, "action_update", "mem::action-update", [action.id], { - actor: data.assignedTo || "unknown", - before, - after: action, - }); - - if (data.status === "done") { - await propagateCompletion(kv, action.id); - } - - return { success: true, action }; - }); - }, - ); - - sdk.registerFunction("mem::action-edge-create", - async (data: { - sourceActionId: string; - targetActionId: string; - type: string; - metadata?: Record; - }) => { - if (!data.sourceActionId || !data.targetActionId || !data.type) { - return { - success: false, - error: "sourceActionId, targetActionId, and type are required", - }; - } - - const validTypes = [ - "requires", - "unlocks", - "spawned_by", - "gated_by", - "conflicts_with", - ]; - if (!validTypes.includes(data.type)) { - return { - success: false, - error: `type must be one of: ${validTypes.join(", ")}`, - }; - } - - const sourceAction = await kv.get(KV.actions, data.sourceActionId); - if (!sourceAction) { - return { success: false, error: "source action not found" }; - } - const targetAction = await kv.get(KV.actions, data.targetActionId); - if (!targetAction) { - return { success: false, error: "target action not found" }; - } - - const edge: ActionEdge = { - id: generateId("ae"), - type: data.type as ActionEdge["type"], - sourceActionId: data.sourceActionId, - targetActionId: data.targetActionId, - createdAt: new Date().toISOString(), - metadata: data.metadata, - }; - - await kv.set(KV.actionEdges, edge.id, edge); - await recordAudit(kv, "action_create", "mem::action-edge-create", [edge.id], { - actor: "unknown", - edge, - }); - return { success: true, edge }; - }, - ); - - sdk.registerFunction("mem::action-list", - async (data: { - status?: string; - project?: string; - parentId?: string; - tags?: string[]; - limit?: number; - }) => { - let actions = await kv.list(KV.actions); - - if (data.status) { - actions = actions.filter((a) => a.status === data.status); - } - if (data.project) { - actions = actions.filter((a) => a.project === data.project); - } - if (data.parentId) { - actions = actions.filter((a) => a.parentId === data.parentId); - } - if (data.tags && data.tags.length > 0) { - actions = actions.filter((a) => - data.tags!.some((t) => a.tags.includes(t)), - ); - } - - actions.sort( - (a, b) => - new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(), - ); - - const limit = data.limit || 50; - return { success: true, actions: actions.slice(0, limit) }; - }, - ); - - sdk.registerFunction("mem::action-get", - async (data: { actionId: string }) => { - if (!data.actionId) { - return { success: false, error: "actionId is required" }; - } - const action = await kv.get(KV.actions, data.actionId); - if (!action) { - return { success: false, error: "action not found" }; - } - - const allEdges = await kv.list(KV.actionEdges); - const edges = allEdges.filter( - (e) => - e.sourceActionId === data.actionId || - e.targetActionId === data.actionId, - ); - - const children = (await kv.list(KV.actions)).filter( - (a) => a.parentId === data.actionId, - ); - - return { success: true, action, edges, children }; - }, - ); -} - -async function propagateCompletion( - kv: StateKV, - completedActionId: string, -): Promise { - const allEdges = await kv.list(KV.actionEdges); - const unlockEdges = allEdges.filter( - (e) => - e.targetActionId === completedActionId && - (e.type === "requires" || e.type === "unlocks"), - ); - - const allActions = await kv.list(KV.actions); - const actionMap = new Map(allActions.map((a) => [a.id, a])); - - for (const edge of unlockEdges) { - const candidateId = edge.sourceActionId; - await withKeyedLock(`mem:action:${candidateId}`, async () => { - const action = await kv.get(KV.actions, candidateId); - if (action && action.status === "blocked") { - const deps = allEdges.filter( - (e) => e.sourceActionId === action.id && e.type === "requires", - ); - const allDone = deps.every((d) => { - const target = actionMap.get(d.targetActionId); - return target && target.status === "done"; - }); - if (allDone) { - action.status = "pending"; - action.updatedAt = new Date().toISOString(); - await kv.set(KV.actions, action.id, action); - } - } - }); - } -} diff --git a/src/functions/audit.ts b/src/functions/audit.ts deleted file mode 100644 index 1e75b4b7..00000000 --- a/src/functions/audit.ts +++ /dev/null @@ -1,113 +0,0 @@ -import type { AuditEntry } from "../types.js"; -import { KV, generateId } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import { logger } from "../logger.js"; - -// Audit coverage policy (issue #125). -// -// Every structural deletion of a memory, observation, session, or -// semantic row MUST call recordAudit. Two shapes are allowed, keyed to -// whether the caller is scoped or bulk: -// -// Scoped deletions — a user-visible, per-call action removing a -// bounded set of items. Emit ONE audit row per call with targetIds -// populated. Examples: mem::governance-delete, mem::forget. -// -// Bulk deletions — automatic sweeps (retention, TTL eviction, -// auto-forget) that can remove hundreds of rows per invocation. -// Emit ONE batched audit row per invocation with targetIds listing -// every removed id and details.evicted holding the count. Per-item -// audit rows would flood the audit log during routine sweeps. -// -// Either shape is required; silent deletes are not acceptable. -// -// operation field: -// - "delete" — permanent removal (governance, retention sweep, evict). -// - "forget" — forget/removal flows. Scoped when emitted by -// mem::forget (user-initiated); bulk-batched when -// emitted by mem::auto-forget (automatic sweep). -// - everything else — see AuditEntry["operation"] union in src/types.ts. -// -// When adding a new deletion path, add an explicit recordAudit call -// BEFORE kv.delete(...) and match one of the two shapes above. - -export async function recordAudit( - kv: StateKV, - operation: AuditEntry["operation"], - functionId: string, - targetIds: string[], - details: Record = {}, - qualityScore?: number, - userId?: string, -): Promise { - const entry: AuditEntry = { - id: generateId("aud"), - timestamp: new Date().toISOString(), - operation, - userId, - functionId, - targetIds, - details, - qualityScore, - }; - await kv.set(KV.audit, entry.id, entry); - return entry; -} - -export async function safeAudit( - kv: StateKV, - operation: AuditEntry["operation"], - functionId: string, - targetIds: string[], - details: Record = {}, - qualityScore?: number, - userId?: string, -): Promise { - try { - await recordAudit(kv, operation, functionId, targetIds, details, qualityScore, userId); - } catch (err) { - try { - logger.warn("audit write failed", { - functionId, - operation, - targetIds, - error: err instanceof Error ? err.message : String(err), - }); - } catch {} - } -} - -export async function queryAudit( - kv: StateKV, - filter?: { - operation?: AuditEntry["operation"]; - dateFrom?: string; - dateTo?: string; - limit?: number; - }, -): Promise { - const all = await kv.list(KV.audit); - let entries = [...all].sort( - (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(), - ); - - if (filter?.operation) { - entries = entries.filter((e) => e.operation === filter.operation); - } - if (filter?.dateFrom) { - const from = new Date(filter.dateFrom).getTime(); - if (Number.isNaN(from)) { - throw new Error(`Invalid dateFrom: ${filter.dateFrom}`); - } - entries = entries.filter((e) => new Date(e.timestamp).getTime() >= from); - } - if (filter?.dateTo) { - const to = new Date(filter.dateTo).getTime(); - if (Number.isNaN(to)) { - throw new Error(`Invalid dateTo: ${filter.dateTo}`); - } - entries = entries.filter((e) => new Date(e.timestamp).getTime() <= to); - } - - return entries.slice(0, filter?.limit || 100); -} diff --git a/src/functions/auto-forget.ts b/src/functions/auto-forget.ts deleted file mode 100644 index 74c571b9..00000000 --- a/src/functions/auto-forget.ts +++ /dev/null @@ -1,206 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { Memory, CompressedObservation, Session } from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; -import { deleteAccessLog } from "./access-tracker.js"; -import { getSearchIndex, vectorIndexRemove, flushIndexSave } from "./search.js"; -import { logger } from "../logger.js"; - -const MS_PER_DAY = 24 * 60 * 60 * 1000; -const CONTRADICTION_THRESHOLD = 0.9; - -interface AutoForgetResult { - ttlExpired: string[]; - contradictions: Array<{ - memoryA: string; - memoryB: string; - similarity: number; - }>; - lowValueObs: string[]; - dryRun: boolean; -} - -export function registerAutoForgetFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::auto-forget", - async (data: { dryRun?: boolean }): Promise => { - const dryRun = data?.dryRun ?? false; - const now = Date.now(); - const { decrementImageRef } = await import("./image-refs.js"); - - const result: AutoForgetResult = { - ttlExpired: [], - contradictions: [], - lowValueObs: [], - dryRun, - }; - - const memories = await kv.list(KV.memories); - const deletedIds = new Set(); - for (const mem of memories) { - if (mem.forgetAfter) { - const expiry = new Date(mem.forgetAfter).getTime(); - if (now > expiry) { - result.ttlExpired.push(mem.id); - deletedIds.add(mem.id); - if (!dryRun) { - if (mem.imageRef) { - await decrementImageRef(kv, sdk, mem.imageRef); - } - await kv.delete(KV.memories, mem.id); - await recordAudit(kv, "delete", "mem::auto-forget", [mem.id], { - resource: "memory", - reason: "auto-forget TTL", - timestamp: mem.forgetAfter, - }); - await deleteAccessLog(kv, mem.id); - getSearchIndex().remove(mem.id); - vectorIndexRemove(mem.id); - } - } - } - } - - const latestMemories = memories - .filter((m) => m.isLatest !== false && !deletedIds.has(m.id)) - .sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ) - .slice(0, 1000); - - const tokenCache = new Map>(); - for (const mem of latestMemories) { - tokenCache.set( - mem.id, - new Set( - mem.content - .toLowerCase() - .split(/\s+/) - .filter((t) => t.length > 2), - ), - ); - } - - const memById = new Map(latestMemories.map((m) => [m.id, m])); - const conceptIndex = new Map(); - for (const mem of latestMemories) { - const concepts = mem.concepts || []; - for (const c of concepts) { - const key = c.toLowerCase(); - if (!conceptIndex.has(key)) conceptIndex.set(key, []); - conceptIndex.get(key)!.push(mem.id); - } - } - - const compared = new Set(); - for (const [, memIds] of conceptIndex) { - for (let i = 0; i < memIds.length; i++) { - for (let j = i + 1; j < memIds.length; j++) { - const key = - memIds[i] < memIds[j] - ? `${memIds[i]}|${memIds[j]}` - : `${memIds[j]}|${memIds[i]}`; - if (compared.has(key)) continue; - compared.add(key); - - const setA = tokenCache.get(memIds[i])!; - const setB = tokenCache.get(memIds[j])!; - let intersection = 0; - if (setA.size === 0 && setB.size === 0) continue; - if (setA.size === 0 || setB.size === 0) continue; - for (const word of setA) { - if (setB.has(word)) intersection++; - } - const sim = - intersection / (setA.size + setB.size - intersection); - - if (sim > CONTRADICTION_THRESHOLD) { - const memA = memById.get(memIds[i])!; - const memB = memById.get(memIds[j])!; - result.contradictions.push({ - memoryA: memA.id, - memoryB: memB.id, - similarity: sim, - }); - - if (!dryRun) { - const older = - new Date(memA.createdAt).getTime() < - new Date(memB.createdAt).getTime() - ? memA - : memB; - older.isLatest = false; - await kv.set(KV.memories, older.id, older); - await recordAudit(kv, "forget", "mem::auto-forget", [older.id], { - resource: "memory", - reason: "auto-forget contradiction", - olderId: older.id, - similarity: sim, - }); - } - } - } - } - } - - const sessions = await kv.list(KV.sessions); - const obsPerSession: CompressedObservation[][] = []; - for (let batch = 0; batch < sessions.length; batch += 10) { - const chunk = sessions.slice(batch, batch + 10); - const results = await Promise.all( - chunk.map((s) => - kv - .list(KV.observations(s.id)) - .catch(() => [] as CompressedObservation[]), - ), - ); - obsPerSession.push(...results); - } - for (let i = 0; i < sessions.length; i++) { - for (const obs of obsPerSession[i]) { - if (!obs.timestamp) continue; - const age = now - new Date(obs.timestamp).getTime(); - if (age > 180 * MS_PER_DAY && (obs.importance ?? 5) <= 2) { - result.lowValueObs.push(obs.id); - if (!dryRun) { - let deletedOk = false; - try { - await kv.delete(KV.observations(sessions[i].id), obs.id); - deletedOk = true; - } catch { - deletedOk = false; - } - if (deletedOk) { - if (obs.imageData) await decrementImageRef(kv, sdk, obs.imageData); - if (obs.imageRef && obs.imageRef !== obs.imageData) { - await decrementImageRef(kv, sdk, obs.imageRef); - } - await recordAudit(kv, "delete", "mem::auto-forget", [obs.id], { - resource: "observation", - reason: "auto-forget low-value observation", - sessionId: sessions[i].id, - timestamp: obs.timestamp, - }); - getSearchIndex().remove(obs.id); - vectorIndexRemove(obs.id); - } - } - } - } - } - - if (!dryRun && (result.ttlExpired.length > 0 || result.lowValueObs.length > 0)) { - await flushIndexSave(); - } - - logger.info("Auto-forget complete", { - ttlExpired: result.ttlExpired.length, - contradictions: result.contradictions.length, - lowValueObs: result.lowValueObs.length, - dryRun, - }); - return result; - }, - ); -} diff --git a/src/functions/branch-aware.ts b/src/functions/branch-aware.ts deleted file mode 100644 index d0240f43..00000000 --- a/src/functions/branch-aware.ts +++ /dev/null @@ -1,166 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV } from "../state/schema.js"; -import type { Session } from "../types.js"; -import { execFile } from "node:child_process"; -import { resolve } from "node:path"; - -function execAsync( - cmd: string, - args: string[], - cwd: string, -): Promise { - return new Promise((resolve, reject) => { - execFile(cmd, args, { cwd, timeout: 5000 }, (err, stdout) => { - if (err) reject(err); - else resolve(stdout.trim()); - }); - }); -} - -export function registerBranchAwareFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::detect-worktree", - async (data: { cwd: string }) => { - if (!data.cwd) { - return { success: false, error: "cwd is required" }; - } - - try { - const gitDir = await execAsync( - "git", - ["rev-parse", "--git-dir"], - data.cwd, - ); - const commonDir = await execAsync( - "git", - ["rev-parse", "--git-common-dir"], - data.cwd, - ); - const branch = await execAsync( - "git", - ["rev-parse", "--abbrev-ref", "HEAD"], - data.cwd, - ).catch(() => "detached"); - - const topLevel = await execAsync( - "git", - ["rev-parse", "--show-toplevel"], - data.cwd, - ); - - const isWorktree = resolve(data.cwd, gitDir) !== resolve(data.cwd, commonDir); - const mainRepoRoot = isWorktree - ? resolve(data.cwd, commonDir, "..") - : topLevel; - - return { - success: true, - isWorktree, - branch, - topLevel, - mainRepoRoot, - gitDir: resolve(data.cwd, gitDir), - commonDir: resolve(data.cwd, commonDir), - }; - } catch { - return { - success: true, - isWorktree: false, - branch: null, - topLevel: data.cwd, - mainRepoRoot: data.cwd, - gitDir: null, - commonDir: null, - }; - } - }, - ); - - sdk.registerFunction("mem::list-worktrees", - async (data: { cwd: string }) => { - if (!data.cwd) { - return { success: false, error: "cwd is required" }; - } - - try { - const output = await execAsync( - "git", - ["worktree", "list", "--porcelain"], - data.cwd, - ); - - const worktrees: Array<{ - path: string; - head: string; - branch: string; - bare: boolean; - }> = []; - - const blocks = output.split("\n\n").filter(Boolean); - for (const block of blocks) { - const lines = block.split("\n"); - const wt: { path: string; head: string; branch: string; bare: boolean } = { - path: "", - head: "", - branch: "", - bare: false, - }; - for (const line of lines) { - if (line.startsWith("worktree ")) wt.path = line.slice(9); - else if (line.startsWith("HEAD ")) wt.head = line.slice(5); - else if (line.startsWith("branch ")) - wt.branch = line.slice(7).replace("refs/heads/", ""); - else if (line === "bare") wt.bare = true; - } - if (wt.path) worktrees.push(wt); - } - - return { success: true, worktrees }; - } catch { - return { success: true, worktrees: [] }; - } - }, - ); - - sdk.registerFunction("mem::branch-sessions", - async (data: { cwd: string; branch?: string }) => { - if (!data.cwd) { - return { success: false, error: "cwd is required" }; - } - - const worktreeInfo = await sdk.trigger< - { cwd: string }, - { - success: boolean; - isWorktree: boolean; - mainRepoRoot: string; - branch: string | null; - } - >({ function_id: "mem::detect-worktree", payload: { cwd: data.cwd } }); - - const projectRoot = worktreeInfo.mainRepoRoot || data.cwd; - const branch = data.branch || worktreeInfo.branch; - - const sessions = await kv.list(KV.sessions); - - const matching = sessions.filter((s) => { - if (s.project === projectRoot || s.cwd === projectRoot) return true; - if (s.cwd.startsWith(projectRoot + "/")) return true; - return false; - }); - - matching.sort( - (a, b) => - new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime(), - ); - - return { - success: true, - sessions: matching, - projectRoot, - branch, - isWorktree: worktreeInfo.isWorktree, - }; - }, - ); -} diff --git a/src/functions/cascade.ts b/src/functions/cascade.ts deleted file mode 100644 index fb979e65..00000000 --- a/src/functions/cascade.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV } from "../state/schema.js"; -import type { Memory, GraphNode, GraphEdge } from "../types.js"; -import { recordAudit } from "./audit.js"; - -export function registerCascadeFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::cascade-update", - async (data: { supersededMemoryId: string }) => { - if (!data.supersededMemoryId || typeof data.supersededMemoryId !== "string") { - return { success: false, error: "supersededMemoryId is required" }; - } - - const superseded = await kv.get(KV.memories, data.supersededMemoryId); - if (!superseded) { - return { success: false, error: "superseded memory not found" }; - } - - let flaggedNodes = 0; - let flaggedEdges = 0; - let flaggedMemories = 0; - - const obsIds = new Set(superseded.sourceObservationIds || []); - - if (obsIds.size > 0) { - const now = new Date().toISOString(); - const nodes = await kv.list(KV.graphNodes); - for (const node of nodes) { - if (node.stale) continue; - const overlap = (node.sourceObservationIds ?? []).some((id) => obsIds.has(id)); - if (overlap) { - node.stale = true; - node.updatedAt = now; - await kv.set(KV.graphNodes, node.id, node); - await recordAudit(kv, "consolidate", "mem::cascade-update", [node.id], { - resourceType: "GraphNode", - change: "marked stale from superseded memory", - supersededMemoryId: data.supersededMemoryId, - }); - flaggedNodes++; - } - } - - const edges = await kv.list(KV.graphEdges); - for (const edge of edges) { - if (edge.stale) continue; - const overlap = (edge.sourceObservationIds ?? []).some((id) => obsIds.has(id)); - if (overlap) { - edge.stale = true; - await kv.set(KV.graphEdges, edge.id, edge); - await recordAudit(kv, "consolidate", "mem::cascade-update", [edge.id], { - resourceType: "GraphEdge", - change: "marked stale from superseded memory", - supersededMemoryId: data.supersededMemoryId, - }); - flaggedEdges++; - } - } - } - - const supersededConcepts = new Set( - (superseded.concepts ?? []).map((c) => c.toLowerCase()), - ); - if (supersededConcepts.size >= 2) { - const allMemories = await kv.list(KV.memories); - for (const mem of allMemories) { - if (mem.id === data.supersededMemoryId) continue; - if (!mem.isLatest) continue; - - const sharedCount = (mem.concepts ?? []).filter((c) => - supersededConcepts.has(c.toLowerCase()), - ).length; - if (sharedCount >= 2) { - flaggedMemories++; - } - } - } - - return { - success: true, - flagged: { - nodes: flaggedNodes, - edges: flaggedEdges, - siblingMemories: flaggedMemories, - }, - total: flaggedNodes + flaggedEdges + flaggedMemories, - }; - }, - ); -} diff --git a/src/functions/checkpoints.ts b/src/functions/checkpoints.ts deleted file mode 100644 index efb3957a..00000000 --- a/src/functions/checkpoints.ts +++ /dev/null @@ -1,236 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import type { Action, ActionEdge, Checkpoint } from "../types.js"; -import { recordAudit } from "./audit.js"; - -export function registerCheckpointsFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::checkpoint-create", - async (data: { - name: string; - description?: string; - type?: Checkpoint["type"]; - linkedActionIds?: string[]; - expiresInMs?: number; - }) => { - if (!data.name) { - return { success: false, error: "name is required" }; - } - - const validTypes: Checkpoint["type"][] = ["ci", "approval", "deploy", "external", "timer"]; - if (data.type && !validTypes.includes(data.type)) { - return { success: false, error: `invalid checkpoint type: ${data.type}. Must be one of: ${validTypes.join(", ")}` }; - } - - const now = new Date(); - const checkpoint: Checkpoint = { - id: generateId("ckpt"), - name: data.name.trim(), - description: (data.description || "").trim(), - status: "pending", - type: data.type || "external", - createdAt: now.toISOString(), - linkedActionIds: data.linkedActionIds || [], - expiresAt: data.expiresInMs - ? new Date(now.getTime() + data.expiresInMs).toISOString() - : undefined, - }; - - if (data.linkedActionIds && data.linkedActionIds.length > 0) { - for (const actionId of data.linkedActionIds) { - const action = await kv.get(KV.actions, actionId); - if (!action) { - return { success: false, error: `linked action not found: ${actionId}` }; - } - } - } - - await kv.set(KV.checkpoints, checkpoint.id, checkpoint); - await recordAudit(kv, "checkpoint_resolve", "mem::checkpoint-create", [checkpoint.id], { - action: "create", - type: checkpoint.type, - name: checkpoint.name, - }); - - if (data.linkedActionIds && data.linkedActionIds.length > 0) { - for (const actionId of data.linkedActionIds) { - const edge: ActionEdge = { - id: generateId("ae"), - type: "gated_by", - sourceActionId: actionId, - targetActionId: checkpoint.id, - createdAt: now.toISOString(), - }; - await kv.set(KV.actionEdges, edge.id, edge); - - const action = await kv.get(KV.actions, actionId); - if (action && action.status === "pending") { - const previousStatus = action.status; - action.status = "blocked"; - action.updatedAt = now.toISOString(); - await kv.set(KV.actions, action.id, action); - await recordAudit(kv, "action_update", "mem::checkpoint-create", [action.id], { - action: "status-change", - previousStatus, - newStatus: action.status, - checkpointId: checkpoint.id, - }); - } - } - } - - return { success: true, checkpoint }; - }, - ); - - sdk.registerFunction("mem::checkpoint-resolve", - async (data: { - checkpointId: string; - status: "passed" | "failed"; - resolvedBy?: string; - result?: unknown; - }) => { - if (!data.checkpointId || !data.status) { - return { - success: false, - error: "checkpointId and status are required", - }; - } - - return withKeyedLock( - `mem:checkpoint:${data.checkpointId}`, - async () => { - const checkpoint = await kv.get( - KV.checkpoints, - data.checkpointId, - ); - if (!checkpoint) { - return { success: false, error: "checkpoint not found" }; - } - if (checkpoint.status !== "pending") { - return { - success: false, - error: `checkpoint already ${checkpoint.status}`, - }; - } - - checkpoint.status = data.status; - checkpoint.resolvedAt = new Date().toISOString(); - checkpoint.resolvedBy = data.resolvedBy; - checkpoint.result = data.result; - - await kv.set(KV.checkpoints, checkpoint.id, checkpoint); - await recordAudit(kv, "checkpoint_resolve", "mem::checkpoint-resolve", [checkpoint.id], { - action: "resolve", - resolvedBy: data.resolvedBy, - result: data.result, - newStatus: checkpoint.status, - }); - - let unblockedCount = 0; - if (data.status === "passed" && checkpoint.linkedActionIds.length > 0) { - const allEdges = await kv.list(KV.actionEdges); - const allCheckpoints = await kv.list(KV.checkpoints); - const allActions = await kv.list(KV.actions); - const cpMap = new Map(allCheckpoints.map((c) => [c.id, c])); - const actionMap = new Map(allActions.map((a) => [a.id, a])); - - for (const actionId of checkpoint.linkedActionIds) { - await withKeyedLock(`mem:action:${actionId}`, async () => { - const action = await kv.get(KV.actions, actionId); - if (action && action.status === "blocked") { - const gates = allEdges.filter( - (e) => e.sourceActionId === actionId && e.type === "gated_by", - ); - const allGatesPassed = gates.every((g) => { - const cp = cpMap.get(g.targetActionId); - return cp && cp.status === "passed"; - }); - const requires = allEdges.filter( - (e) => e.sourceActionId === actionId && e.type === "requires", - ); - const allRequiresMet = requires.every((r) => { - const dep = actionMap.get(r.targetActionId); - return dep && dep.status === "done"; - }); - if (allGatesPassed && allRequiresMet) { - const previousStatus = action.status; - action.status = "pending"; - action.updatedAt = new Date().toISOString(); - await kv.set(KV.actions, action.id, action); - await recordAudit(kv, "action_update", "mem::checkpoint-resolve", [action.id], { - action: "unblock", - checkpointId: checkpoint.id, - previousStatus, - newStatus: action.status, - }); - unblockedCount++; - } - } - }); - } - } - - return { success: true, checkpoint, unblockedCount }; - }, - ); - }, - ); - - sdk.registerFunction("mem::checkpoint-list", - async (data: { status?: string; type?: string }) => { - let checkpoints = await kv.list(KV.checkpoints); - - if (data.status) { - checkpoints = checkpoints.filter((c) => c.status === data.status); - } - if (data.type) { - checkpoints = checkpoints.filter((c) => c.type === data.type); - } - - checkpoints.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - return { success: true, checkpoints }; - }, - ); - - sdk.registerFunction("mem::checkpoint-expire", - async () => { - const checkpoints = await kv.list(KV.checkpoints); - const now = Date.now(); - let expired = 0; - - for (const cp of checkpoints) { - if ( - cp.status === "pending" && - cp.expiresAt && - new Date(cp.expiresAt).getTime() <= now - ) { - const didExpire = await withKeyedLock( - `mem:checkpoint:${cp.id}`, - async () => { - const fresh = await kv.get(KV.checkpoints, cp.id); - if (!fresh || fresh.status !== "pending") return false; - fresh.status = "expired"; - fresh.resolvedAt = new Date().toISOString(); - await kv.set(KV.checkpoints, fresh.id, fresh); - await recordAudit(kv, "checkpoint_resolve", "mem::checkpoint-expire", [fresh.id], { - action: "expire", - previousStatus: "pending", - newStatus: "expired", - }); - return true; - }, - ); - if (didExpire) expired++; - } - } - - return { success: true, expired }; - }, - ); -} diff --git a/src/functions/claude-bridge.ts b/src/functions/claude-bridge.ts deleted file mode 100644 index 3aba76f5..00000000 --- a/src/functions/claude-bridge.ts +++ /dev/null @@ -1,162 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs"; -import { dirname } from "node:path"; -import type { Memory, ClaudeBridgeConfig } from "../types.js"; -import { KV } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; -import { logger } from "../logger.js"; - -function parseMemoryMd(content: string): { - sections: Map; - raw: string; -} { - const sections = new Map(); - let currentSection = ""; - let currentContent: string[] = []; - - for (const line of content.split("\n")) { - if (line.startsWith("## ")) { - if (currentSection) { - sections.set(currentSection, currentContent.join("\n").trim()); - } - currentSection = line.slice(3).trim(); - currentContent = []; - } else { - currentContent.push(line); - } - } - if (currentSection) { - sections.set(currentSection, currentContent.join("\n").trim()); - } - - return { sections, raw: content }; -} - -function serializeToMemoryMd( - memories: Memory[], - projectSummary: string, - lineBudget: number, -): string { - const lines: string[] = []; - lines.push("# Agent Memory (auto-synced by agentmemory)"); - lines.push(""); - - if (projectSummary) { - lines.push("## Project Summary"); - lines.push(projectSummary); - lines.push(""); - } - - lines.push("## Key Memories"); - lines.push(""); - - const sorted = [...memories] - .filter((m) => m.isLatest) - .sort((a, b) => b.strength - a.strength); - - for (const mem of sorted) { - if (lines.length >= lineBudget - 2) break; - lines.push(`### ${mem.title}`); - const contentLines = mem.content.split("\n"); - for (const cl of contentLines) { - if (lines.length >= lineBudget - 1) break; - lines.push(cl); - } - lines.push(""); - } - - return lines.slice(0, lineBudget).join("\n"); -} - -export function registerClaudeBridgeFunction( - sdk: ISdk, - kv: StateKV, - config: ClaudeBridgeConfig, -): void { - sdk.registerFunction("mem::claude-bridge-read", - async () => { - if (!config.enabled || !config.memoryFilePath) { - return { success: false, error: "Claude bridge not configured" }; - } - - try { - if (!existsSync(config.memoryFilePath)) { - return { success: true, content: "", parsed: false }; - } - const content = readFileSync(config.memoryFilePath, "utf-8"); - const { sections } = parseMemoryMd(content); - - await kv.set(KV.claudeBridge, "last-read", { - timestamp: new Date().toISOString(), - sections: Object.fromEntries(sections), - lineCount: content.split("\n").length, - }); - await recordAudit(kv, "export", "mem::claude-bridge-read", ["last-read"], { - timestamp: new Date().toISOString(), - sections: Object.keys(Object.fromEntries(sections)), - lineCount: content.split("\n").length, - }); - - logger.info("Claude bridge: read MEMORY.md", { - path: config.memoryFilePath, - lines: content.split("\n").length, - }); - return { success: true, content, sections: Object.fromEntries(sections) }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Claude bridge read failed", { error: msg }); - return { success: false, error: msg }; - } - }, - ); - - sdk.registerFunction("mem::claude-bridge-sync", - async () => { - if (!config.enabled || !config.memoryFilePath) { - return { success: false, error: "Claude bridge not configured" }; - } - - try { - const memories = await kv.list(KV.memories); - const latestMemories = memories.filter((m) => m.isLatest); - - let projectSummary = ""; - if (config.projectPath) { - const profile = await kv - .get<{ summary?: string }>(KV.profiles, config.projectPath) - .catch(() => null); - projectSummary = profile?.summary || ""; - } - - const md = serializeToMemoryMd( - latestMemories, - projectSummary, - config.lineBudget, - ); - - const dir = dirname(config.memoryFilePath); - if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }); - } - writeFileSync(config.memoryFilePath, md, "utf-8"); - - await recordAudit(kv, "export", "mem::claude-bridge-sync", [], { - path: config.memoryFilePath, - memoryCount: latestMemories.length, - lines: md.split("\n").length, - }); - - logger.info("Claude bridge: synced to MEMORY.md", { - path: config.memoryFilePath, - memories: latestMemories.length, - }); - return { success: true, path: config.memoryFilePath, lines: md.split("\n").length }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Claude bridge sync failed", { error: msg }); - return { success: false, error: msg }; - } - }, - ); -} diff --git a/src/functions/compress-file.ts b/src/functions/compress-file.ts deleted file mode 100644 index 0a54452b..00000000 --- a/src/functions/compress-file.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { constants } from "node:fs"; -import { lstat, open, readFile, writeFile } from "node:fs/promises"; -import { basename, dirname, extname, join, resolve } from "node:path"; -import type { ISdk } from "iii-sdk"; -import type { MemoryProvider } from "../types.js"; -import type { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; - -const SENSITIVE_PATH_TERMS = [ - "secret", - "credential", - "private_key", - ".env", - "id_rsa", - "token", -]; - -const COMPRESS_FILE_SYSTEM_PROMPT = `You compress markdown while preserving structure. -Rules: -- Keep all headings exactly as-is. -- Keep all URLs exactly as-is. -- Keep all fenced code blocks exactly as-is. -- Do not remove sections; shorten prose under each section. -- Output only markdown, no wrappers or explanations.`; - -function stripMarkdownFence(text: string): string { - const trimmed = text.trim(); - const match = trimmed.match(/^```(?:markdown|md)?\s*([\s\S]*?)\s*```$/i); - return match ? match[1].trim() : trimmed; -} - -function extractUrls(text: string): string[] { - return Array.from(new Set(text.match(/https?:\/\/[^\s)]+/g) || [])); -} - -function extractHeadings(text: string): string[] { - return text - .split("\n") - .map((line) => line.trim()) - .filter((line) => /^#{1,6}\s+/.test(line)); -} - -function extractCodeBlocks(text: string): string[] { - return text.match(/```[\s\S]*?```/g) || []; -} - -function validateCompression(original: string, compressed: string): string[] { - const errors: string[] = []; - - const originalHeadings = extractHeadings(original); - const compressedHeadings = extractHeadings(compressed); - for (const heading of originalHeadings) { - if (!compressedHeadings.includes(heading)) { - errors.push(`missing heading: ${heading}`); - } - } - - const originalUrls = extractUrls(original).sort(); - const compressedUrls = extractUrls(compressed).sort(); - if (originalUrls.length !== compressedUrls.length) { - errors.push("url count changed"); - } else { - for (let i = 0; i < originalUrls.length; i++) { - if (originalUrls[i] !== compressedUrls[i]) { - errors.push("url set changed"); - break; - } - } - } - - const originalBlocks = extractCodeBlocks(original); - const compressedBlocks = extractCodeBlocks(compressed); - if (originalBlocks.length !== compressedBlocks.length) { - errors.push("code block count changed"); - } else { - for (let i = 0; i < originalBlocks.length; i++) { - if (originalBlocks[i] !== compressedBlocks[i]) { - errors.push("code block content changed"); - break; - } - } - } - - return errors; -} - -function resolveBackupPath(filePath: string): string { - const base = basename(filePath, extname(filePath)); - const name = base.endsWith(".original") - ? `${base}.backup` - : `${base}.original`; - return join(dirname(filePath), `${name}.md`); -} - -export function registerCompressFileFunction( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, -): void { - sdk.registerFunction( - "mem::compress-file", - async (data: { filePath: string }) => { - if (!data?.filePath || typeof data.filePath !== "string") { - return { success: false, error: "filePath is required" }; - } - - const absolutePath = resolve(data.filePath); - const lowerPath = absolutePath.toLowerCase(); - if (extname(absolutePath).toLowerCase() !== ".md") { - return { success: false, error: "filePath must point to a .md file" }; - } - if (SENSITIVE_PATH_TERMS.some((term) => lowerPath.includes(term))) { - return { success: false, error: "refusing to process sensitive-looking path" }; - } - - try { - const stat = await lstat(absolutePath); - if (stat.isSymbolicLink()) { - return { success: false, error: "symlinks are not supported" }; - } - } catch { - return { success: false, error: "file not found" }; - } - - let original: string; - try { - original = await readFile(absolutePath, "utf-8"); - } catch { - return { success: false, error: "failed to read file" }; - } - - if (!original.trim()) { - return { success: true, skipped: true, reason: "file is empty" }; - } - - const response = await provider.summarize( - COMPRESS_FILE_SYSTEM_PROMPT, - `Compress this markdown file while preserving structure and code blocks:\n\n${original}`, - ); - const compressed = stripMarkdownFence(response); - const validationErrors = validateCompression(original, compressed); - if (validationErrors.length > 0) { - return { - success: false, - error: "compression validation failed", - details: validationErrors, - }; - } - - const backupPath = resolveBackupPath(absolutePath); - await writeFile(backupPath, original, "utf-8"); - - let fd: Awaited> | null = null; - try { - fd = await open( - absolutePath, - constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, - ); - await fd.writeFile(compressed, "utf-8"); - } catch (err: unknown) { - const code = (err as NodeJS.ErrnoException).code; - if (code === "ELOOP" || code === "EINVAL") { - return { success: false, error: "symlinks are not supported" }; - } - return { success: false, error: "failed to write compressed file" }; - } finally { - await fd?.close().catch(() => {}); - } - - try { - await recordAudit(kv, "compress", "mem::compress-file", [], { - filePath: absolutePath, - backupPath, - originalChars: original.length, - compressedChars: compressed.length, - }); - } catch {} - - return { - success: true, - filePath: absolutePath, - backupPath, - originalChars: original.length, - compressedChars: compressed.length, - }; - }, - ); -} diff --git a/src/functions/compress-synthetic.ts b/src/functions/compress-synthetic.ts deleted file mode 100644 index 287d1be1..00000000 --- a/src/functions/compress-synthetic.ts +++ /dev/null @@ -1,108 +0,0 @@ -import type { - RawObservation, - CompressedObservation, - ObservationType, -} from "../types.js"; - -// Zero-LLM compression path. Converts a RawObservation into a -// CompressedObservation using only heuristics — no Claude call, no token -// spend. This is the default as of 0.8.8 (#138); users who want richer -// LLM-generated summaries set AGENTMEMORY_AUTO_COMPRESS=true. - -function inferType( - toolName: string | undefined, - hookType: string, -): ObservationType { - if (hookType === "post_tool_failure") return "error"; - if (hookType === "prompt_submit") return "conversation"; - if (hookType === "stop") return "conversation"; - if (hookType === "subagent_stop" || hookType === "task_completed") - return "subagent"; - if (hookType === "notification") return "notification"; - - if (!toolName) return "other"; - // Normalize camelCase and kebab-case into word chunks so we can match - // substrings like "WebFetch" -> "web" / "fetch". - const n = toolName - .replace(/([a-z])([A-Z])/g, "$1_$2") - .replace(/[-\s]+/g, "_") - .toLowerCase(); - const hasWord = (word: string) => - new RegExp(`(^|_)${word}(_|$)`).test(n) || - n === word || - n.endsWith(word) || - n.startsWith(word); - if (["fetch", "http", "web"].some(hasWord)) return "web_fetch"; - if (["grep", "search", "glob", "find"].some(hasWord)) return "search"; - if (["bash", "shell", "exec", "run"].some(hasWord)) return "command_run"; - if (["edit", "update", "patch", "replace"].some(hasWord)) return "file_edit"; - if (["write", "create"].some(hasWord)) return "file_write"; - if (["read", "view"].some(hasWord)) return "file_read"; - if (["task", "agent"].some(hasWord)) return "subagent"; - return "other"; -} - -function extractFiles(input: unknown): string[] { - if (!input || typeof input !== "object") return []; - const o = input as Record; - const out = new Set(); - for (const key of [ - "file_path", - "filepath", - "path", - "filePath", - "file", - "pattern", - ]) { - const v = o[key]; - if (typeof v === "string" && v.length > 0 && v.length < 512) out.add(v); - } - return [...out]; -} - -function stringifyForNarrative(v: unknown): string { - if (v == null) return ""; - if (typeof v === "string") return v; - try { - return JSON.stringify(v); - } catch { - return String(v); - } -} - -function truncate(s: string, n: number): string { - return s.length > n ? s.slice(0, n - 1) + "\u2026" : s; -} - -export function buildSyntheticCompression( - raw: RawObservation, -): CompressedObservation { - const toolName = raw.toolName ?? raw.hookType; - const inputStr = stringifyForNarrative(raw.toolInput); - const outputStr = stringifyForNarrative(raw.toolOutput); - const promptStr = raw.userPrompt ?? ""; - const assistantStr = raw.assistantResponse ?? ""; - - const narrativeParts = [promptStr, assistantStr, inputStr, outputStr].filter( - (s) => s.length > 0, - ); - - const result: CompressedObservation = { - id: raw.id, - sessionId: raw.sessionId, - timestamp: raw.timestamp, - type: inferType(toolName, raw.hookType), - title: truncate(toolName || "observation", 80), - subtitle: inputStr ? truncate(inputStr, 120) : undefined, - facts: [], - narrative: truncate(narrativeParts.join(" | "), 400), - concepts: [], - files: extractFiles(raw.toolInput), - importance: 5, - confidence: 0.3, - }; - if (raw.modality) result.modality = raw.modality; - if (raw.imageData) result.imageData = raw.imageData; - if (raw.agentId) result.agentId = raw.agentId; - return result; -} diff --git a/src/functions/compress.ts b/src/functions/compress.ts deleted file mode 100644 index 0569555e..00000000 --- a/src/functions/compress.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { TriggerAction, type ISdk } from "iii-sdk"; -import { readFileSync } from "node:fs"; -import { isManagedImagePath } from "../utils/image-store.js"; -import type { - RawObservation, - CompressedObservation, - ObservationType, - MemoryProvider, -} from "../types.js"; -import { KV, STREAM } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { - COMPRESSION_SYSTEM, - buildCompressionPrompt, -} from "../prompts/compression.js"; -import { VISION_DESCRIPTION_PROMPT } from "../prompts/vision.js"; -import { getXmlTag, getXmlChildren } from "../prompts/xml.js"; -import { getSearchIndex, vectorIndexAddGuarded } from "./search.js"; -import { CompressOutputSchema } from "../eval/schemas.js"; -import { validateOutput } from "../eval/validator.js"; -import { scoreCompression } from "../eval/quality.js"; -import { compressWithRetry } from "../eval/self-correct.js"; -import type { MetricsStore } from "../eval/metrics-store.js"; -import { logger } from "../logger.js"; - -const VALID_TYPES = new Set([ - "file_read", - "file_write", - "file_edit", - "command_run", - "search", - "web_fetch", - "conversation", - "error", - "decision", - "discovery", - "subagent", - "notification", - "task", - "image", - "other", -]); - -function parseCompressionXml( - xml: string, -): Omit | null { - const rawType = getXmlTag(xml, "type"); - const title = getXmlTag(xml, "title"); - if (!rawType || !title) return null; - const type = VALID_TYPES.has(rawType) ? rawType : "other"; - - return { - type: type as ObservationType, - title, - subtitle: getXmlTag(xml, "subtitle") || undefined, - facts: getXmlChildren(xml, "facts", "fact"), - narrative: getXmlTag(xml, "narrative"), - concepts: getXmlChildren(xml, "concepts", "concept"), - files: getXmlChildren(xml, "files", "file"), - importance: Math.max( - 1, - Math.min(10, parseInt(getXmlTag(xml, "importance") || "5", 10) || 5), - ), - }; -} - -export function registerCompressFunction( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, - metricsStore?: MetricsStore, -): void { - sdk.registerFunction("mem::compress", - async (data: { - observationId: string; - sessionId: string; - raw: RawObservation; - }) => { - const startMs = Date.now(); - - let imageDescription: string | undefined; - const hasImage = data.raw.modality === "image" || data.raw.modality === "mixed"; - - if (hasImage && data.raw.imageData && provider.describeImage) { - try { - let base64Data = data.raw.imageData; - let mimeType = "image/png"; - - if (!data.raw.imageData.startsWith("/9j/") && !data.raw.imageData.startsWith("iVBOR")) { - if (!isManagedImagePath(data.raw.imageData)) { - throw new Error(`Refusing to read image outside managed store: ${data.raw.imageData}`); - } - const fileBuffer = readFileSync(data.raw.imageData); - base64Data = fileBuffer.toString("base64"); - if (data.raw.imageData.endsWith(".jpg") || data.raw.imageData.endsWith(".jpeg")) mimeType = "image/jpeg"; - else if (data.raw.imageData.endsWith(".webp")) mimeType = "image/webp"; - else if (data.raw.imageData.endsWith(".gif")) mimeType = "image/gif"; - } - - imageDescription = await provider.describeImage(base64Data, mimeType, VISION_DESCRIPTION_PROMPT); - logger.info("Image described by vision model", { obsId: data.observationId }); - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.warn("Vision model call failed, falling back to text-only compression", { - obsId: data.observationId, - error: msg, - }); - } - } - - const prompt = buildCompressionPrompt({ - hookType: data.raw.hookType, - toolName: data.raw.toolName, - toolInput: data.raw.toolInput, - toolOutput: imageDescription - ? `[Image Description]: ${imageDescription}\n\n${data.raw.toolOutput ?? ""}` - : data.raw.toolOutput, - userPrompt: data.raw.userPrompt, - timestamp: data.raw.timestamp, - }); - - try { - const validator = (response: string) => { - const parsed = parseCompressionXml(response); - if (!parsed) return { valid: false, errors: ["xml_parse_failed"] }; - const result = validateOutput( - CompressOutputSchema, - parsed, - "mem::compress", - ); - return result.valid - ? { valid: true } - : { valid: false, errors: result.result.errors }; - }; - - const { response, retried } = await compressWithRetry( - provider, - COMPRESSION_SYSTEM, - prompt, - validator, - 1, - ); - - const parsed = parseCompressionXml(response); - if (!parsed) { - const latencyMs = Date.now() - startMs; - if (metricsStore) { - await metricsStore.record("mem::compress", latencyMs, false); - } - logger.warn("Failed to parse compression XML", { - obsId: data.observationId, - retried, - }); - return { success: false, error: "parse_failed" }; - } - - const qualityScore = scoreCompression(parsed); - - const compressed: CompressedObservation = { - id: data.observationId, - sessionId: data.sessionId, - timestamp: data.raw.timestamp, - ...parsed, - confidence: qualityScore / 100, - ...(hasImage ? { modality: data.raw.modality } : {}), - ...(imageDescription ? { imageDescription } : {}), - ...(data.raw.imageData ? { imageRef: data.raw.imageData } : {}), - ...(data.raw.agentId ? { agentId: data.raw.agentId } : {}), - }; - - await kv.set( - KV.observations(data.sessionId), - data.observationId, - compressed, - ); - - try { - getSearchIndex().add(compressed); - } catch (err) { - logger.warn("Failed to index compressed observation into BM25", { - obsId: compressed.id, - sessionId: compressed.sessionId, - title: compressed.title, - error: err instanceof Error ? err.message : String(err), - }); - } - - await vectorIndexAddGuarded( - compressed.id, - compressed.sessionId, - compressed.title + " " + (compressed.narrative || ""), - { kind: "observation", logId: compressed.id }, - ); - - const streamResults = await Promise.allSettled([ - sdk.trigger({ - function_id: "stream::set", - payload: { - stream_name: STREAM.name, - group_id: STREAM.group(data.sessionId), - item_id: data.observationId, - data: { type: "compressed", observation: compressed }, - }, - }), - sdk.trigger({ - function_id: "stream::send", - payload: { - stream_name: STREAM.name, - group_id: STREAM.viewerGroup, - id: `compressed-${data.observationId}`, - type: "compressed_observation", - data: { - type: "compressed", - observation: compressed, - sessionId: data.sessionId, - }, - }, - action: TriggerAction.Void(), - }), - ]); - for (const result of streamResults) { - if (result.status === "rejected") { - logger.warn("Non-fatal stream publish failure after compress", { - sessionId: data.sessionId, - observationId: data.observationId, - error: - result.reason instanceof Error - ? result.reason.message - : String(result.reason), - }); - } - } - - const latencyMs = Date.now() - startMs; - if (metricsStore) { - await metricsStore.record( - "mem::compress", - latencyMs, - true, - qualityScore, - ); - } - - logger.info("Observation compressed", { - obsId: data.observationId, - type: compressed.type, - importance: compressed.importance, - qualityScore, - retried, - }); - - return { success: true, compressed, qualityScore }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - const latencyMs = Date.now() - startMs; - if (metricsStore) { - await metricsStore.record("mem::compress", latencyMs, false); - } - logger.error("Compression failed", { - obsId: data.observationId, - error: msg, - }); - return { success: false, error: "compression_failed" }; - } - }, - ); -} diff --git a/src/functions/consolidate.ts b/src/functions/consolidate.ts deleted file mode 100644 index d394e176..00000000 --- a/src/functions/consolidate.ts +++ /dev/null @@ -1,241 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - CompressedObservation, - Memory, - Session, - MemoryProvider, -} from "../types.js"; -import { KV, generateId } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; - -const CONSOLIDATION_SYSTEM = `You are a memory consolidation engine. Given a set of related observations from coding sessions, synthesize them into a single long-term memory. - -Output XML: - - pattern|preference|architecture|bug|workflow|fact - Concise memory title (max 80 chars) - 2-4 sentence description of the learned insight - - key term - - - relevant/file/path - - 1-10 how confident/important this memory is -`; - -import { getXmlTag, getXmlChildren } from "../prompts/xml.js"; -import { logger } from "../logger.js"; - -function parseMemoryXml( - xml: string, - sessionIds: string[], -): Omit | null { - const type = getXmlTag(xml, "type"); - const title = getXmlTag(xml, "title"); - const content = getXmlTag(xml, "content"); - if (!type || !title || !content) return null; - - const validTypes = new Set([ - "pattern", - "preference", - "architecture", - "bug", - "workflow", - "fact", - ]); - - return { - type: (validTypes.has(type) ? type : "fact") as Memory["type"], - title, - content, - concepts: getXmlChildren(xml, "concepts", "concept"), - files: getXmlChildren(xml, "files", "file"), - sessionIds, - strength: Math.max( - 1, - Math.min(10, parseInt(getXmlTag(xml, "strength") || "5", 10) || 5), - ), - version: 1, - isLatest: true, - }; -} - -export function registerConsolidateFunction( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, -): void { - sdk.registerFunction("mem::consolidate", - async (data: { project?: string; minObservations?: number }) => { - const minObs = data.minObservations ?? 10; - - const sessions = await kv.list(KV.sessions); - const filtered = data.project - ? sessions.filter((s) => s.project === data.project) - : sessions; - - const allObs: Array = []; - const obsPerSession: CompressedObservation[][] = []; - for (let batch = 0; batch < filtered.length; batch += 10) { - const chunk = filtered.slice(batch, batch + 10); - const results = await Promise.all( - chunk.map((s) => - kv - .list(KV.observations(s.id)) - .catch(() => [] as CompressedObservation[]), - ), - ); - obsPerSession.push(...results); - } - for (let i = 0; i < filtered.length; i++) { - for (const obs of obsPerSession[i]) { - if (obs.title && obs.importance >= 5) { - allObs.push({ ...obs, sid: filtered[i].id }); - } - } - } - - if (allObs.length < minObs) { - return { consolidated: 0, reason: "insufficient_observations" }; - } - - const conceptGroups = new Map(); - for (const obs of allObs) { - for (const concept of obs.concepts) { - const key = concept.toLowerCase(); - if (!conceptGroups.has(key)) conceptGroups.set(key, []); - conceptGroups.get(key)!.push(obs); - } - } - - let consolidated = 0; - const existingMemories = await kv.list(KV.memories); - const existingTitles = new Set( - existingMemories.map((m) => m.title.toLowerCase()), - ); - - const MAX_LLM_CALLS = 10; - let llmCallCount = 0; - - const sortedGroups = [...conceptGroups.entries()] - .filter(([, g]) => g.length >= 3) - .sort((a, b) => b[1].length - a[1].length); - - for (const [concept, obsGroup] of sortedGroups) { - if (llmCallCount >= MAX_LLM_CALLS) break; - - const top = obsGroup - .sort((a, b) => b.importance - a.importance) - .slice(0, 8); - const sessionIds = [...new Set(top.map((o) => o.sid))]; - - const prompt = top - .map( - (o) => - `[${o.type}] ${o.title}\n${o.narrative}\nFiles: ${o.files.join(", ")}\nImportance: ${o.importance}`, - ) - .join("\n\n"); - - try { - const response = await Promise.race([ - provider.compress( - CONSOLIDATION_SYSTEM, - `Concept: "${concept}"\n\nObservations:\n${prompt}`, - ), - new Promise((_, reject) => - setTimeout(() => reject(new Error("compress timeout")), 30_000), - ), - ]); - llmCallCount++; - const parsed = parseMemoryXml(response, sessionIds); - if (!parsed) continue; - - const now = new Date().toISOString(); - const obsIds = [...new Set(top.map((o) => o.id))]; - const scopedProject = - typeof data.project === "string" && data.project.trim().length > 0 - ? data.project.trim() - : undefined; - - // A scoped consolidation run must only evolve memories that belong - // to the same project. Without this guard, two projects that happen - // to consolidate observations into an identically-titled memory would - // cause one project's memory to silently evolve the other's — the - // exact class of cross-project corruption this fix is designed to - // prevent. An unscoped run (no data.project, background cron path) - // preserves the pre-existing behavior and may evolve any memory. - const existingMatch = existingMemories.find( - (m) => - m.title.toLowerCase() === parsed.title.toLowerCase() && - (!scopedProject || !m.project || m.project === scopedProject), - ); - - if (existingMatch) { - existingMatch.isLatest = false; - await kv.set(KV.memories, existingMatch.id, existingMatch); - await recordAudit(kv, "evolve", "mem::consolidate", [existingMatch.id], { - action: "mark_non_latest", - concept, - }); - - const evolved: Memory = { - id: generateId("mem"), - createdAt: now, - updatedAt: now, - ...parsed, - version: (existingMatch.version || 1) + 1, - parentId: existingMatch.id, - supersedes: [ - existingMatch.id, - ...(existingMatch.supersedes || []), - ], - sourceObservationIds: obsIds, - isLatest: true, - ...(scopedProject !== undefined && { project: scopedProject }), - }; - await kv.set(KV.memories, evolved.id, evolved); - await recordAudit(kv, "evolve", "mem::consolidate", [evolved.id], { - action: "evolve_memory", - oldId: existingMatch.id, - newId: evolved.id, - concept, - }); - existingTitles.add(evolved.title.toLowerCase()); - consolidated++; - } else { - const memory: Memory = { - id: generateId("mem"), - createdAt: now, - updatedAt: now, - ...parsed, - sourceObservationIds: obsIds, - version: 1, - isLatest: true, - ...(scopedProject !== undefined && { project: scopedProject }), - }; - await kv.set(KV.memories, memory.id, memory); - await recordAudit(kv, "remember", "mem::consolidate", [memory.id], { - action: "create_memory", - concept, - }); - existingTitles.add(memory.title.toLowerCase()); - consolidated++; - } - } catch (err) { - logger.warn("Consolidation failed for concept", { - concept, - error: err instanceof Error ? err.message : String(err), - }); - } - } - - logger.info("Consolidation complete", { - consolidated, - totalObs: allObs.length, - }); - return { consolidated, totalObservations: allObs.length }; - }, - ); -} diff --git a/src/functions/consolidation-pipeline.ts b/src/functions/consolidation-pipeline.ts deleted file mode 100644 index e51ab97c..00000000 --- a/src/functions/consolidation-pipeline.ts +++ /dev/null @@ -1,270 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - SemanticMemory, - ProceduralMemory, - SessionSummary, - Memory, - MemoryProvider, -} from "../types.js"; -import { KV, generateId } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import { - SEMANTIC_MERGE_SYSTEM, - buildSemanticMergePrompt, - PROCEDURAL_EXTRACTION_SYSTEM, - buildProceduralExtractionPrompt, -} from "../prompts/consolidation.js"; -import { recordAudit } from "./audit.js"; -import { getConsolidationDecayDays, isConsolidationEnabled } from "../config.js"; -import { logger } from "../logger.js"; - -function applyDecay( - items: Array<{ - strength: number; - lastAccessedAt?: string; - updatedAt: string; - }>, - decayDays: number, -): void { - if (decayDays <= 0 || !Number.isFinite(decayDays)) return; - const now = Date.now(); - for (const item of items) { - const lastAccess = item.lastAccessedAt || item.updatedAt; - const daysSince = - (now - new Date(lastAccess).getTime()) / (1000 * 60 * 60 * 24); - if (daysSince > decayDays) { - const decayPeriods = Math.floor(daysSince / decayDays); - item.strength = Math.max( - 0.1, - item.strength * Math.pow(0.9, decayPeriods), - ); - } - } -} - -export function registerConsolidationPipelineFunction( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, -): void { - sdk.registerFunction("mem::consolidate-pipeline", - async (data?: { tier?: string; force?: boolean; project?: string }) => { - if (!data?.force && !isConsolidationEnabled()) { - return { success: false, skipped: true, reason: "Consolidation disabled: set CONSOLIDATION_ENABLED=true or configure an LLM provider (ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY / GEMINI_API_KEY / GOOGLE_API_KEY / MINIMAX_API_KEY / OPENAI_BASE_URL / AGENTMEMORY_PROVIDER=agent-sdk)" }; - } - const tier = data?.tier || "all"; - const decayDays = getConsolidationDecayDays(); - const results: Record = {}; - - if (tier === "all" || tier === "semantic") { - const summaries = await kv.list(KV.summaries); - const existingSemantic = await kv.list(KV.semantic); - - if (summaries.length >= 5) { - const recentSummaries = summaries - .sort( - (a, b) => - new Date(b.createdAt).getTime() - - new Date(a.createdAt).getTime(), - ) - .slice(0, 20); - - const prompt = buildSemanticMergePrompt( - recentSummaries.map((s) => ({ - title: s.title, - narrative: s.narrative, - concepts: s.concepts, - })), - ); - - try { - const response = await provider.summarize( - SEMANTIC_MERGE_SYSTEM, - prompt, - ); - - const factRegex = /([^<]+)<\/fact>/g; - let match; - let newFacts = 0; - const now = new Date().toISOString(); - - while ((match = factRegex.exec(response)) !== null) { - const parsedConf = parseFloat(match[1]); - const confidence = Number.isNaN(parsedConf) ? 0.5 : parsedConf; - const fact = match[2].trim(); - - const existing = existingSemantic.find( - (s) => s.fact.toLowerCase() === fact.toLowerCase(), - ); - if (existing) { - existing.accessCount++; - existing.lastAccessedAt = now; - existing.updatedAt = now; - existing.confidence = Math.max(existing.confidence, confidence); - await kv.set(KV.semantic, existing.id, existing); - } else { - const sem: SemanticMemory = { - id: generateId("sem"), - fact, - confidence, - sourceSessionIds: recentSummaries.map((s) => s.sessionId), - sourceMemoryIds: [], - accessCount: 1, - lastAccessedAt: now, - strength: confidence, - createdAt: now, - updatedAt: now, - }; - await kv.set(KV.semantic, sem.id, sem); - newFacts++; - } - } - results.semantic = { newFacts, totalSummaries: summaries.length }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Semantic consolidation failed", { error: msg }); - results.semantic = { error: msg }; - } - } else { - results.semantic = { - skipped: true, - reason: "fewer than 5 summaries", - }; - } - } - - if (tier === "all" || tier === "reflect") { - try { - const reflectResult = await sdk.trigger({ function_id: "mem::reflect", payload: { - maxClusters: 10, - project: data?.project, - } }); - results.reflect = reflectResult; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.warn("Reflect tier failed", { error: msg }); - results.reflect = { error: msg }; - } - } - - if (tier === "all" || tier === "procedural") { - const memories = await kv.list(KV.memories); - const patterns = memories - .filter((m) => m.isLatest && m.type === "pattern") - .map((m) => ({ - content: m.content, - frequency: m.sessionIds.length || 1, - })) - .filter((p) => p.frequency >= 2); - - if (patterns.length >= 2) { - const prompt = buildProceduralExtractionPrompt(patterns); - - try { - const response = await provider.summarize( - PROCEDURAL_EXTRACTION_SYSTEM, - prompt, - ); - - const procRegex = - /([\s\S]*?)<\/procedure>/g; - let match; - let newProcs = 0; - const now = new Date().toISOString(); - const existingProcs = await kv.list( - KV.procedural, - ); - - while ((match = procRegex.exec(response)) !== null) { - const name = match[1]; - const trigger = match[2]; - const stepsBlock = match[3]; - const steps: string[] = []; - - const stepRegex = /([^<]+)<\/step>/g; - let stepMatch; - while ((stepMatch = stepRegex.exec(stepsBlock)) !== null) { - steps.push(stepMatch[1].trim()); - } - - const existing = existingProcs.find( - (p) => p.name.toLowerCase() === name.toLowerCase(), - ); - if (existing) { - existing.frequency++; - existing.updatedAt = now; - existing.strength = Math.min(1, existing.strength + 0.1); - await kv.set(KV.procedural, existing.id, existing); - } else { - const proc: ProceduralMemory = { - id: generateId("proc"), - name, - steps, - triggerCondition: trigger, - frequency: 1, - sourceSessionIds: [], - strength: 0.5, - createdAt: now, - updatedAt: now, - }; - await kv.set(KV.procedural, proc.id, proc); - newProcs++; - } - } - results.procedural = { - newProcedures: newProcs, - patternsAnalyzed: patterns.length, - }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Procedural extraction failed", { error: msg }); - results.procedural = { error: msg }; - } - } else { - results.procedural = { - skipped: true, - reason: "fewer than 2 recurring patterns", - }; - } - } - - if (tier === "all" || tier === "decay") { - const semantic = await kv.list(KV.semantic); - applyDecay(semantic, decayDays); - for (const s of semantic) { - await kv.set(KV.semantic, s.id, s); - } - - const procedural = await kv.list(KV.procedural); - applyDecay(procedural, decayDays); - for (const p of procedural) { - await kv.set(KV.procedural, p.id, p); - } - - results.decay = { - semantic: semantic.length, - procedural: procedural.length, - }; - } - - if (process.env["OBSIDIAN_AUTO_EXPORT"] === "true") { - try { - await sdk.trigger({ function_id: "mem::obsidian-export", payload: {} }); - results.obsidianExport = { success: true }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.warn("Obsidian auto-export failed", { error: msg }); - results.obsidianExport = { success: false, error: msg }; - } - } - - await recordAudit(kv, "consolidate", "mem::consolidate-pipeline", [], { - tier, - results, - }); - - logger.info("Consolidation pipeline complete", { tier, results }); - return { success: true, results }; - }, - ); -} diff --git a/src/functions/context.ts b/src/functions/context.ts deleted file mode 100644 index 1e6102cf..00000000 --- a/src/functions/context.ts +++ /dev/null @@ -1,234 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - Session, - CompressedObservation, - SessionSummary, - ContextBlock, - ProjectProfile, - MemorySlot, - Lesson, -} from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { recordAccessBatch } from "./access-tracker.js"; -import { logger } from "../logger.js"; -import { - isSlotsEnabled, - listPinnedSlots, - renderPinnedContext, -} from "./slots.js"; - -function estimateTokens(text: string): number { - return Math.ceil(text.length / 3); -} - -function escapeXmlAttr(s: string): string { - return s - .replace(/&/g, "&") - .replace(/"/g, """) - .replace(//g, ">"); -} - -export function registerContextFunction( - sdk: ISdk, - kv: StateKV, - tokenBudget: number, -): void { - sdk.registerFunction("mem::context", - async (data: { sessionId: string; project: string; budget?: number }) => { - const budget = data.budget || tokenBudget; - const blocks: ContextBlock[] = []; - - const [pinnedSlots, profile, lessons] = await Promise.all([ - isSlotsEnabled() - ? listPinnedSlots(kv).catch(() => [] as MemorySlot[]) - : Promise.resolve([] as MemorySlot[]), - kv - .get(KV.profiles, data.project) - .catch(() => null), - kv.list(KV.lessons).catch(() => [] as Lesson[]), - ]); - - const slotContent = renderPinnedContext(pinnedSlots); - if (slotContent) { - blocks.push({ - type: "memory", - content: slotContent, - tokens: estimateTokens(slotContent), - recency: Date.now(), - }); - } - if (profile) { - const profileParts = []; - if (profile.topConcepts.length > 0) { - profileParts.push( - `Concepts: ${profile.topConcepts - .slice(0, 8) - .map((c) => c.concept) - .join(", ")}`, - ); - } - if (profile.topFiles.length > 0) { - profileParts.push( - `Key files: ${profile.topFiles - .slice(0, 5) - .map((f) => f.file) - .join(", ")}`, - ); - } - if (profile.conventions.length > 0) { - profileParts.push(`Conventions: ${profile.conventions.join("; ")}`); - } - if (profile.commonErrors.length > 0) { - profileParts.push( - `Common errors: ${profile.commonErrors.slice(0, 3).join("; ")}`, - ); - } - if (profileParts.length > 0) { - const profileContent = `## Project Profile\n${profileParts.join("\n")}`; - blocks.push({ - type: "memory", - content: profileContent, - tokens: estimateTokens(profileContent), - recency: new Date(profile.updatedAt).getTime(), - }); - } - } - - // Lessons — closes the loop opened by mem::lesson-save / mem::reflect. - // Without this block, lessons sit in KV and only surface when the agent - // thinks to call memory_lesson_recall. Ranking puts project-scoped - // lessons ahead of global ones, then weights by confidence; we cap at - // 10 to keep the block bounded since the outer token-budget loop - // below will drop the whole block if it doesn't fit. #457. - const relevantLessons = lessons - .filter((l) => !l.deleted && (!l.project || l.project === data.project)) - .sort((a, b) => { - const scoreA = (a.project === data.project ? 1.5 : 1) * a.confidence; - const scoreB = (b.project === data.project ? 1.5 : 1) * b.confidence; - return scoreB - scoreA; - }) - .slice(0, 10); - - if (relevantLessons.length > 0) { - const items = relevantLessons - .map( - (l) => - `- (${l.confidence.toFixed(2)}) ${l.content}${l.context ? ` — ${l.context}` : ""}`, - ) - .join("\n"); - const lessonsContent = `## Lessons Learned\n${items}`; - const mostRecent = relevantLessons.reduce((acc, l) => { - const t = new Date(l.lastReinforcedAt || l.updatedAt).getTime(); - return t > acc ? t : acc; - }, 0); - blocks.push({ - type: "memory", - content: lessonsContent, - tokens: estimateTokens(lessonsContent), - recency: mostRecent, - sourceIds: relevantLessons.map((l) => l.id), - }); - } - - const allSessions = await kv.list(KV.sessions); - const sessions = allSessions - .filter((s) => s.project === data.project && s.id !== data.sessionId) - .sort( - (a, b) => - new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime(), - ) - .slice(0, 10); - - const summariesPerSession = await Promise.all( - sessions.map((s) => - kv.get(KV.summaries, s.id).catch(() => null), - ), - ); - - const sessionsNeedingObs: number[] = []; - for (let i = 0; i < sessions.length; i++) { - const summary = summariesPerSession[i]; - if (summary) { - const content = `## ${summary.title}\n${summary.narrative}\nDecisions: ${summary.keyDecisions.join("; ")}\nFiles: ${summary.filesModified.join(", ")}`; - blocks.push({ - type: "summary", - content, - tokens: estimateTokens(content), - recency: new Date(summary.createdAt).getTime(), - }); - } else { - sessionsNeedingObs.push(i); - } - } - - const obsResults = await Promise.all( - sessionsNeedingObs.map((i) => - kv - .list(KV.observations(sessions[i].id)) - .catch(() => []), - ), - ); - - for (let j = 0; j < sessionsNeedingObs.length; j++) { - const i = sessionsNeedingObs[j]; - const observations = obsResults[j]; - const important = observations.filter( - (o) => o.title && o.importance >= 5, - ); - - if (important.length > 0) { - const top = important - .sort((a, b) => b.importance - a.importance) - .slice(0, 5); - const items = top - .map((o) => `- [${o.type}] ${o.title}: ${o.narrative}`) - .join("\n"); - const content = `## Session ${sessions[i].id.slice(0, 8)} (${sessions[i].startedAt})\n${items}`; - blocks.push({ - type: "observation", - content, - tokens: estimateTokens(content), - recency: new Date(sessions[i].startedAt).getTime(), - sourceIds: top.map((o) => o.id), - }); - } - } - - blocks.sort((a, b) => b.recency - a.recency); - - let usedTokens = 0; - const selected: string[] = []; - const accessedIds: string[] = []; - const header = ``; - const footer = ``; - usedTokens += estimateTokens(header) + estimateTokens(footer); - - for (const block of blocks) { - if (usedTokens + block.tokens > budget) continue; - selected.push(block.content); - usedTokens += block.tokens; - if (block.sourceIds && block.sourceIds.length > 0) { - accessedIds.push(...block.sourceIds); - } - } - - if (accessedIds.length > 0) { - void recordAccessBatch(kv, accessedIds); - } - - if (selected.length === 0) { - logger.info("No context available", { project: data.project }); - return { context: "", blocks: 0, tokens: 0 }; - } - - const result = `${header}\n${selected.join("\n\n")}\n${footer}`; - logger.info("Context generated", { - blocks: selected.length, - tokens: usedTokens, - }); - return { context: result, blocks: selected.length, tokens: usedTokens }; - }, - ); -} diff --git a/src/functions/crystallize.ts b/src/functions/crystallize.ts deleted file mode 100644 index 1f0b8431..00000000 --- a/src/functions/crystallize.ts +++ /dev/null @@ -1,294 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import type { Action, ActionEdge, Crystal, MemoryProvider } from "../types.js"; - -interface CrystalDigest { - narrative: string; - keyOutcomes: string[]; - filesAffected: string[]; - lessons: string[]; -} - -const CRYSTALLIZE_SYSTEM = `You are summarizing a completed chain of agent actions into a compact digest. -Extract: (1) what was accomplished in 1-2 sentences, (2) key decisions as bullet points, -(3) files affected, (4) any lessons or patterns worth remembering. -Return as JSON: { "narrative": "...", "keyOutcomes": ["..."], "filesAffected": ["..."], "lessons": ["..."] }`; - -export function registerCrystallizeFunction( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, -): void { - sdk.registerFunction("mem::crystallize", - async (data: { - actionIds: string[]; - sessionId?: string; - project?: string; - }) => { - if (!data.actionIds || data.actionIds.length === 0) { - return { success: false, error: "actionIds is required" }; - } - - const actions: Action[] = []; - for (const id of data.actionIds) { - const action = await kv.get(KV.actions, id); - if (!action) { - return { success: false, error: `action not found: ${id}` }; - } - if (action.status !== "done" && action.status !== "cancelled") { - return { - success: false, - error: `action ${id} has status "${action.status}", expected "done" or "cancelled"`, - }; - } - actions.push(action); - } - - const allEdges = await kv.list(KV.actionEdges); - const idSet = new Set(data.actionIds); - const relevantEdges = allEdges.filter( - (e) => idSet.has(e.sourceActionId) || idSet.has(e.targetActionId), - ); - - const prompt = buildChainText(actions, relevantEdges); - - try { - const response = await provider.summarize(CRYSTALLIZE_SYSTEM, prompt); - const digest = parseDigest(response); - - const crystal: Crystal = { - id: generateId("crys"), - narrative: digest.narrative, - keyOutcomes: digest.keyOutcomes, - filesAffected: digest.filesAffected, - lessons: digest.lessons, - sourceActionIds: data.actionIds, - sessionId: data.sessionId, - project: data.project, - createdAt: new Date().toISOString(), - }; - - await kv.set(KV.crystals, crystal.id, crystal); - - await Promise.all( - digest.lessons.map((lesson) => - sdk - .trigger({ - function_id: "mem::lesson-save", - payload: { - content: lesson, - context: crystal.narrative, - confidence: 0.6, - project: data.project, - tags: [], - source: "crystal", - sourceIds: [crystal.id], - }, - }) - .catch(() => {}), - ), - ); - - for (const action of actions) { - const updated = { ...action, crystallizedInto: crystal.id }; - await kv.set(KV.actions, action.id, updated); - } - - return { success: true, crystal }; - } catch (err) { - return { - success: false, - error: `crystallization failed: ${String(err)}`, - }; - } - }, - ); - - sdk.registerFunction("mem::crystal-list", - async (data: { - project?: string; - sessionId?: string; - limit?: number; - }) => { - const limit = data.limit ?? 20; - let crystals = await kv.list(KV.crystals); - - if (data.project) { - crystals = crystals.filter((c) => c.project === data.project); - } - if (data.sessionId) { - crystals = crystals.filter((c) => c.sessionId === data.sessionId); - } - - crystals.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - return { success: true, crystals: crystals.slice(0, limit) }; - }, - ); - - sdk.registerFunction("mem::crystal-get", - async (data: { crystalId: string }) => { - if (!data.crystalId) { - return { success: false, error: "crystalId is required" }; - } - - const crystal = await kv.get(KV.crystals, data.crystalId); - if (!crystal) { - return { success: false, error: "crystal not found" }; - } - - return { success: true, crystal }; - }, - ); - - sdk.registerFunction("mem::auto-crystallize", - async (data: { - olderThanDays?: number; - project?: string; - dryRun?: boolean; - }) => { - const olderThanDays = data.olderThanDays ?? 7; - const dryRun = data.dryRun ?? false; - const cutoff = Date.now() - olderThanDays * 24 * 60 * 60 * 1000; - - let allActions = await kv.list(KV.actions); - - allActions = allActions.filter( - (a) => - a.status === "done" && - !a.crystallizedInto && - new Date(a.createdAt).getTime() < cutoff, - ); - - if (data.project) { - allActions = allActions.filter((a) => a.project === data.project); - } - - if (allActions.length === 0) { - return { success: true, groupCount: 0, crystalIds: [] }; - } - - const groups = new Map(); - for (const action of allActions) { - const key = action.parentId ?? action.project ?? "_ungrouped"; - const group = groups.get(key); - if (group) { - group.push(action); - } else { - groups.set(key, [action]); - } - } - - if (dryRun) { - const groupSummaries = Array.from(groups.entries()).map( - ([key, actions]) => ({ - groupKey: key, - actionCount: actions.length, - actionIds: actions.map((a) => a.id), - }), - ); - return { - success: true, - dryRun: true, - groupCount: groups.size, - groups: groupSummaries, - crystalIds: [], - }; - } - - const crystalIds: string[] = []; - for (const [, groupActions] of groups) { - const actionIds = groupActions.map((a) => a.id); - const project = groupActions[0].project; - - try { - const result = (await sdk.trigger({ function_id: "mem::crystallize", payload: { - actionIds, - project, - } })) as { success: boolean; crystal?: Crystal }; - - if (result.success && result.crystal) { - crystalIds.push(result.crystal.id); - } - } catch { - continue; - } - } - - return { - success: true, - groupCount: groups.size, - crystalIds, - }; - }, - ); -} - -function buildChainText(actions: Action[], edges: ActionEdge[]): string { - const lines: string[] = ["## Completed Action Chain\n"]; - - const sorted = [...actions].sort( - (a, b) => - new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(), - ); - - for (const action of sorted) { - lines.push(`### ${action.title}`); - if (action.description) lines.push(action.description); - if (action.result) lines.push(`Result: ${action.result}`); - lines.push( - `Tags: ${(action.tags ?? []).join(", ")}`, - ); - lines.push(""); - } - - if (edges.length > 0) { - lines.push("## Dependencies"); - for (const edge of edges) { - lines.push( - `- ${edge.sourceActionId} --${edge.type}--> ${edge.targetActionId}`, - ); - } - } - - return lines.join("\n"); -} - -function parseDigest(response: string): CrystalDigest { - try { - const jsonMatch = response.match(/\{[\s\S]*\}/); - if (!jsonMatch) { - return { - narrative: response, - keyOutcomes: [], - filesAffected: [], - lessons: [], - }; - } - const parsed = JSON.parse(jsonMatch[0]) as Record; - return { - narrative: - typeof parsed.narrative === "string" ? parsed.narrative : response, - keyOutcomes: Array.isArray(parsed.keyOutcomes) - ? (parsed.keyOutcomes as string[]) - : [], - filesAffected: Array.isArray(parsed.filesAffected) - ? (parsed.filesAffected as string[]) - : [], - lessons: Array.isArray(parsed.lessons) - ? (parsed.lessons as string[]) - : [], - }; - } catch { - return { - narrative: response, - keyOutcomes: [], - filesAffected: [], - lessons: [], - }; - } -} diff --git a/src/functions/dedup.ts b/src/functions/dedup.ts deleted file mode 100644 index 23f044a3..00000000 --- a/src/functions/dedup.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { createHash } from "node:crypto"; - -const TTL_MS = 5 * 60 * 1000; -const CLEANUP_INTERVAL_MS = 60_000; - -interface DedupEntry { - hash: string; - expiresAt: number; -} - -export class DedupMap { - private entries = new Map(); - private cleanupTimer: ReturnType; - - constructor() { - this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL_MS); - this.cleanupTimer.unref(); - } - - computeHash(sessionId: string, toolName: string, toolInput: unknown): string { - const input = - typeof toolInput === "string" - ? toolInput.slice(0, 500) - : JSON.stringify(toolInput ?? "").slice(0, 500); - const raw = `${sessionId}:${toolName}:${input}`; - return createHash("sha256").update(raw).digest("hex"); - } - - isDuplicate(hash: string): boolean { - const entry = this.entries.get(hash); - if (!entry) return false; - if (Date.now() > entry.expiresAt) { - this.entries.delete(hash); - return false; - } - return true; - } - - record(hash: string): void { - this.entries.set(hash, { hash, expiresAt: Date.now() + TTL_MS }); - } - - private cleanup(): void { - const now = Date.now(); - for (const [key, entry] of this.entries) { - if (now > entry.expiresAt) this.entries.delete(key); - } - } - - stop(): void { - clearInterval(this.cleanupTimer); - } - - get size(): number { - return this.entries.size; - } -} diff --git a/src/functions/diagnostics.ts b/src/functions/diagnostics.ts deleted file mode 100644 index cc982883..00000000 --- a/src/functions/diagnostics.ts +++ /dev/null @@ -1,1063 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV } from "../state/schema.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import { recordAudit } from "./audit.js"; -import type { - Action, - ActionEdge, - DiagnosticCheck, - Insight, - Lease, - Lesson, - Checkpoint, - Crystal, - ProceduralMemory, - SemanticMemory, - SessionSummary, - Signal, - Sentinel, - Sketch, - MeshPeer, - Session, - Memory, -} from "../types.js"; - -const ALL_CATEGORIES = [ - "actions", - "leases", - "sentinels", - "sketches", - "signals", - "sessions", - "memories", - "lessons", - "summaries", - "semantic", - "procedural", - "crystals", - "insights", - "mesh", -]; - -const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000; -const ONE_HOUR_MS = 60 * 60 * 1000; - -export function registerDiagnosticsFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::diagnose", - async (data: { categories?: string[] }) => { - const categories = data.categories && data.categories.length > 0 - ? data.categories.filter((c) => ALL_CATEGORIES.includes(c)) - : ALL_CATEGORIES; - - const checks: DiagnosticCheck[] = []; - const now = Date.now(); - - if (categories.includes("actions")) { - const actions = await kv.list(KV.actions); - const allEdges = await kv.list(KV.actionEdges); - const leases = await kv.list(KV.leases); - const actionMap = new Map(actions.map((a) => [a.id, a])); - - for (const action of actions) { - if (action.status === "active") { - const hasActiveLease = leases.some( - (l) => - l.actionId === action.id && - l.status === "active" && - new Date(l.expiresAt).getTime() > now, - ); - if (!hasActiveLease) { - checks.push({ - name: `active-no-lease:${action.id}`, - category: "actions", - status: "warn", - message: `Action "${action.title}" is active but has no active lease`, - fixable: false, - }); - } - } - - if (action.status === "blocked") { - const deps = allEdges.filter( - (e) => e.sourceActionId === action.id && e.type === "requires", - ); - if (deps.length > 0) { - const allDone = deps.every((d) => { - const target = actionMap.get(d.targetActionId); - return target && target.status === "done"; - }); - if (allDone) { - checks.push({ - name: `blocked-deps-done:${action.id}`, - category: "actions", - status: "fail", - message: `Action "${action.title}" is blocked but all dependencies are done`, - fixable: true, - }); - } - } - } - - if (action.status === "pending") { - const deps = allEdges.filter( - (e) => e.sourceActionId === action.id && e.type === "requires", - ); - if (deps.length > 0) { - const hasUnsatisfied = deps.some((d) => { - const target = actionMap.get(d.targetActionId); - return !target || target.status !== "done"; - }); - if (hasUnsatisfied) { - checks.push({ - name: `pending-unsatisfied-deps:${action.id}`, - category: "actions", - status: "fail", - message: `Action "${action.title}" is pending but has unsatisfied dependencies`, - fixable: true, - }); - } - } - } - } - - if ( - !checks.some((c) => c.category === "actions" && c.status !== "pass") - ) { - checks.push({ - name: "actions-ok", - category: "actions", - status: "pass", - message: `All ${actions.length} actions are consistent`, - fixable: false, - }); - } - } - - if (categories.includes("leases")) { - const leases = await kv.list(KV.leases); - const actions = await kv.list(KV.actions); - const actionIds = new Set(actions.map((a) => a.id)); - let leaseIssues = 0; - - for (const lease of leases) { - if ( - lease.status === "active" && - new Date(lease.expiresAt).getTime() <= now - ) { - checks.push({ - name: `expired-lease:${lease.id}`, - category: "leases", - status: "fail", - message: `Lease ${lease.id} for action ${lease.actionId} expired at ${lease.expiresAt}`, - fixable: true, - }); - leaseIssues++; - } - - if (!actionIds.has(lease.actionId)) { - checks.push({ - name: `orphaned-lease:${lease.id}`, - category: "leases", - status: "fail", - message: `Lease ${lease.id} references non-existent action ${lease.actionId}`, - fixable: true, - }); - leaseIssues++; - } - } - - if (leaseIssues === 0) { - checks.push({ - name: "leases-ok", - category: "leases", - status: "pass", - message: `All ${leases.length} leases are healthy`, - fixable: false, - }); - } - } - - if (categories.includes("sentinels")) { - const sentinels = await kv.list(KV.sentinels); - const actions = await kv.list(KV.actions); - const actionIds = new Set(actions.map((a) => a.id)); - let sentinelIssues = 0; - - for (const sentinel of sentinels) { - if ( - sentinel.status === "watching" && - sentinel.expiresAt && - new Date(sentinel.expiresAt).getTime() <= now - ) { - checks.push({ - name: `expired-sentinel:${sentinel.id}`, - category: "sentinels", - status: "fail", - message: `Sentinel "${sentinel.name}" expired at ${sentinel.expiresAt}`, - fixable: true, - }); - sentinelIssues++; - } - - for (const actionId of sentinel.linkedActionIds) { - if (!actionIds.has(actionId)) { - checks.push({ - name: `sentinel-missing-action:${sentinel.id}:${actionId}`, - category: "sentinels", - status: "warn", - message: `Sentinel "${sentinel.name}" references non-existent action ${actionId}`, - fixable: false, - }); - sentinelIssues++; - } - } - } - - if (sentinelIssues === 0) { - checks.push({ - name: "sentinels-ok", - category: "sentinels", - status: "pass", - message: `All ${sentinels.length} sentinels are healthy`, - fixable: false, - }); - } - } - - if (categories.includes("sketches")) { - const sketches = await kv.list(KV.sketches); - let sketchIssues = 0; - - for (const sketch of sketches) { - if ( - sketch.status === "active" && - new Date(sketch.expiresAt).getTime() <= now - ) { - checks.push({ - name: `expired-sketch:${sketch.id}`, - category: "sketches", - status: "fail", - message: `Sketch "${sketch.title}" expired at ${sketch.expiresAt}`, - fixable: true, - }); - sketchIssues++; - } - } - - if (sketchIssues === 0) { - checks.push({ - name: "sketches-ok", - category: "sketches", - status: "pass", - message: `All ${sketches.length} sketches are healthy`, - fixable: false, - }); - } - } - - if (categories.includes("signals")) { - const signals = await kv.list(KV.signals); - let signalIssues = 0; - - for (const signal of signals) { - if ( - signal.expiresAt && - new Date(signal.expiresAt).getTime() <= now - ) { - checks.push({ - name: `expired-signal:${signal.id}`, - category: "signals", - status: "fail", - message: `Signal from "${signal.from}" expired at ${signal.expiresAt}`, - fixable: true, - }); - signalIssues++; - } - } - - if (signalIssues === 0) { - checks.push({ - name: "signals-ok", - category: "signals", - status: "pass", - message: `All ${signals.length} signals are healthy`, - fixable: false, - }); - } - } - - if (categories.includes("sessions")) { - const sessions = await kv.list(KV.sessions); - let sessionIssues = 0; - - for (const session of sessions) { - if ( - session.status === "active" && - now - new Date(session.startedAt).getTime() > TWENTY_FOUR_HOURS_MS - ) { - checks.push({ - name: `abandoned-session:${session.id}`, - category: "sessions", - status: "warn", - message: `Session ${session.id} has been active for over 24 hours`, - fixable: false, - }); - sessionIssues++; - } - } - - if (sessionIssues === 0) { - checks.push({ - name: "sessions-ok", - category: "sessions", - status: "pass", - message: `All ${sessions.length} sessions are healthy`, - fixable: false, - }); - } - } - - if (categories.includes("memories")) { - const memories = await kv.list(KV.memories); - const memoryIds = new Set(memories.map((m) => m.id)); - const supersededBy = new Map(); - let memoryIssues = 0; - - for (const memory of memories) { - if (memory.supersedes && memory.supersedes.length > 0) { - for (const sid of memory.supersedes) { - if (!memoryIds.has(sid)) { - checks.push({ - name: `memory-missing-supersedes:${memory.id}:${sid}`, - category: "memories", - status: "warn", - message: `Memory "${memory.title}" supersedes non-existent memory ${sid}`, - fixable: false, - }); - memoryIssues++; - } - supersededBy.set(sid, memory.id); - } - } - } - - for (const memory of memories) { - if (memory.isLatest && supersededBy.has(memory.id)) { - checks.push({ - name: `memory-stale-latest:${memory.id}`, - category: "memories", - status: "fail", - message: `Memory "${memory.title}" has isLatest=true but is superseded by ${supersededBy.get(memory.id)}`, - fixable: true, - }); - memoryIssues++; - } - } - - // Project-coverage check: unscoped memories (no project field) will - // appear in every project's context and search results until the - // infer-memory-projects migration runs. Surface a count so operators - // know the backfill is still pending and can trigger it explicitly. - const latestMemories = memories.filter((m) => m.isLatest); - const unscopedCount = latestMemories.filter((m) => !m.project).length; - if (unscopedCount === 0) { - checks.push({ - name: "memory-project-coverage", - category: "memories", - status: "pass", - message: `All ${latestMemories.length} latest memories have a project scope`, - fixable: false, - }); - } else if (unscopedCount <= 10) { - checks.push({ - name: "memory-project-coverage", - category: "memories", - status: "warn", - message: `${unscopedCount} of ${latestMemories.length} latest memories have no project scope — run POST /agentmemory/migrate {"step":"infer-memory-projects"} to backfill`, - fixable: true, - }); - } else { - checks.push({ - name: "memory-project-coverage", - category: "memories", - status: "fail", - message: `${unscopedCount} of ${latestMemories.length} latest memories have no project scope — run POST /agentmemory/migrate {"step":"infer-memory-projects"} to backfill`, - fixable: true, - }); - } - - if (memoryIssues === 0) { - checks.push({ - name: "memories-ok", - category: "memories", - status: "pass", - message: `All ${memories.length} memories are structurally consistent`, - fixable: false, - }); - } - } - - if (categories.includes("lessons")) { - // Counts only live lessons (deleted=true rows are tombstoned). - // Catches bad confidence values that would silently break recall - // scoring (memory_lesson_recall multiplies by confidence). - const lessons = await kv.list(KV.lessons); - const live = lessons.filter((l) => !l.deleted); - let lessonIssues = 0; - for (const l of live) { - // Number.isFinite rejects NaN / Infinity / non-numbers; a - // corrupted row passing those would silently survive the < / > - // range check (e.g. NaN < 0 is false, NaN > 1 is false, so the - // bad row would be "healthy") and skew memory_lesson_recall's - // scoring downstream. Surface as warning. - if ( - !Number.isFinite(l.confidence) || - l.confidence < 0 || - l.confidence > 1 - ) { - checks.push({ - name: `lesson-bad-confidence:${l.id}`, - category: "lessons", - status: "warn", - message: `Lesson ${l.id} has confidence ${l.confidence} (expected finite number in 0..1)`, - fixable: false, - }); - lessonIssues++; - } - } - if (lessonIssues === 0) { - checks.push({ - name: "lessons-ok", - category: "lessons", - status: "pass", - message: `All ${live.length} lessons are healthy (${lessons.length - live.length} tombstoned)`, - fixable: false, - }); - } - } - - if (categories.includes("summaries")) { - const summaries = await kv.list(KV.summaries); - let summaryIssues = 0; - for (const s of summaries) { - // typeof guard before .trim() — a corrupted row with title=null - // or title=42 would otherwise throw and abort the whole diagnose - // run before later categories get checked. - if (typeof s.title !== "string" || s.title.trim().length === 0) { - checks.push({ - name: `summary-missing-title:${s.sessionId}`, - category: "summaries", - status: "warn", - message: `Summary for session ${s.sessionId} has no title`, - fixable: false, - }); - summaryIssues++; - } - } - if (summaryIssues === 0) { - checks.push({ - name: "summaries-ok", - category: "summaries", - status: "pass", - message: `All ${summaries.length} session summaries are consistent`, - fixable: false, - }); - } - } - - if (categories.includes("semantic")) { - const semantic = await kv.list(KV.semantic); - let semanticIssues = 0; - for (const s of semantic) { - if ( - !Number.isFinite(s.confidence) || - s.confidence < 0 || - s.confidence > 1 - ) { - checks.push({ - name: `semantic-bad-confidence:${s.id}`, - category: "semantic", - status: "warn", - message: `Semantic fact ${s.id} has confidence ${s.confidence} (expected finite number in 0..1)`, - fixable: false, - }); - semanticIssues++; - } - } - if (semanticIssues === 0) { - checks.push({ - name: "semantic-ok", - category: "semantic", - status: "pass", - message: `All ${semantic.length} semantic memories are consistent`, - fixable: false, - }); - } - } - - if (categories.includes("procedural")) { - const procedural = await kv.list(KV.procedural); - let proceduralIssues = 0; - for (const p of procedural) { - if (!Array.isArray(p.steps) || p.steps.length === 0) { - checks.push({ - name: `procedural-empty-steps:${p.id}`, - category: "procedural", - status: "warn", - message: `Procedural memory "${p.name}" (${p.id}) has no steps`, - fixable: false, - }); - proceduralIssues++; - } - } - if (proceduralIssues === 0) { - checks.push({ - name: "procedural-ok", - category: "procedural", - status: "pass", - message: `All ${procedural.length} procedural memories are consistent`, - fixable: false, - }); - } - } - - if (categories.includes("crystals")) { - const crystals = await kv.list(KV.crystals); - let crystalIssues = 0; - for (const c of crystals) { - if (typeof c.narrative !== "string" || c.narrative.trim().length === 0) { - checks.push({ - name: `crystal-empty-narrative:${c.id}`, - category: "crystals", - status: "warn", - message: `Crystal ${c.id} has empty narrative`, - fixable: false, - }); - crystalIssues++; - } - } - if (crystalIssues === 0) { - checks.push({ - name: "crystals-ok", - category: "crystals", - status: "pass", - message: `All ${crystals.length} crystals are consistent`, - fixable: false, - }); - } - } - - if (categories.includes("insights")) { - const insights = await kv.list(KV.insights); - let insightIssues = 0; - for (const i of insights) { - if ( - !Number.isFinite(i.confidence) || - i.confidence < 0 || - i.confidence > 1 - ) { - checks.push({ - name: `insight-bad-confidence:${i.id}`, - category: "insights", - status: "warn", - message: `Insight ${i.id} has confidence ${i.confidence} (expected finite number in 0..1)`, - fixable: false, - }); - insightIssues++; - } - } - if (insightIssues === 0) { - checks.push({ - name: "insights-ok", - category: "insights", - status: "pass", - message: `All ${insights.length} insights are consistent`, - fixable: false, - }); - } - } - - if (categories.includes("mesh")) { - const peers = await kv.list(KV.mesh); - let meshIssues = 0; - - for (const peer of peers) { - if ( - peer.lastSyncAt && - now - new Date(peer.lastSyncAt).getTime() > ONE_HOUR_MS - ) { - checks.push({ - name: `stale-peer:${peer.id}`, - category: "mesh", - status: "warn", - message: `Peer "${peer.name}" last synced over 1 hour ago`, - fixable: false, - }); - meshIssues++; - } - - if (peer.status === "error") { - checks.push({ - name: `error-peer:${peer.id}`, - category: "mesh", - status: "warn", - message: `Peer "${peer.name}" is in error state`, - fixable: false, - }); - meshIssues++; - } - } - - if (meshIssues === 0) { - checks.push({ - name: "mesh-ok", - category: "mesh", - status: "pass", - message: `All ${peers.length} mesh peers are healthy`, - fixable: false, - }); - } - } - - const summary = { - pass: checks.filter((c) => c.status === "pass").length, - warn: checks.filter((c) => c.status === "warn").length, - fail: checks.filter((c) => c.status === "fail").length, - fixable: checks.filter((c) => c.fixable).length, - }; - - return { success: true, checks, summary }; - }, - ); - - sdk.registerFunction("mem::heal", - async (data: { categories?: string[]; dryRun?: boolean }) => { - const dryRun = data.dryRun ?? false; - const categories = data.categories && data.categories.length > 0 - ? data.categories.filter((c) => ALL_CATEGORIES.includes(c)) - : ALL_CATEGORIES; - - let fixed = 0; - let skipped = 0; - const details: string[] = []; - const now = Date.now(); - - if (categories.includes("actions")) { - const actions = await kv.list(KV.actions); - const allEdges = await kv.list(KV.actionEdges); - const actionMap = new Map(actions.map((a) => [a.id, a])); - - for (const action of actions) { - if (action.status === "blocked") { - const deps = allEdges.filter( - (e) => e.sourceActionId === action.id && e.type === "requires", - ); - if (deps.length > 0) { - const allDone = deps.every((d) => { - const target = actionMap.get(d.targetActionId); - return target && target.status === "done"; - }); - if (allDone) { - if (dryRun) { - details.push( - `[dry-run] Would unblock action "${action.title}" (${action.id})`, - ); - fixed++; - continue; - } - const didFix = await withKeyedLock( - `mem:action:${action.id}`, - async () => { - const fresh = await kv.get(KV.actions, action.id); - if (!fresh || fresh.status !== "blocked") return false; - const freshEdges = await kv.list(KV.actionEdges); - const freshDeps = freshEdges.filter( - (e) => - e.sourceActionId === fresh.id && e.type === "requires", - ); - const freshActions = await kv.list(KV.actions); - const freshMap = new Map( - freshActions.map((a) => [a.id, a]), - ); - const stillAllDone = freshDeps.every((d) => { - const target = freshMap.get(d.targetActionId); - return target && target.status === "done"; - }); - if (!stillAllDone) return false; - fresh.status = "pending"; - fresh.updatedAt = new Date().toISOString(); - await kv.set(KV.actions, fresh.id, fresh); - await recordAudit(kv, "heal", "mem::heal", [fresh.id], { - reason: "blocked-deps-done", - previousStatus: "blocked", - newStatus: "pending", - }); - return true; - }, - ); - if (didFix) { - details.push( - `Unblocked action "${action.title}" (${action.id})`, - ); - fixed++; - } else { - skipped++; - } - } - } - } - - if (action.status === "pending") { - const deps = allEdges.filter( - (e) => e.sourceActionId === action.id && e.type === "requires", - ); - if (deps.length > 0) { - const hasUnsatisfied = deps.some((d) => { - const target = actionMap.get(d.targetActionId); - return !target || target.status !== "done"; - }); - if (hasUnsatisfied) { - if (dryRun) { - details.push( - `[dry-run] Would block action "${action.title}" (${action.id})`, - ); - fixed++; - continue; - } - const didFix = await withKeyedLock( - `mem:action:${action.id}`, - async () => { - const fresh = await kv.get(KV.actions, action.id); - if (!fresh || fresh.status !== "pending") return false; - const freshEdges = await kv.list(KV.actionEdges); - const freshDeps = freshEdges.filter( - (e) => - e.sourceActionId === fresh.id && e.type === "requires", - ); - const freshActions = await kv.list(KV.actions); - const freshMap = new Map( - freshActions.map((a) => [a.id, a]), - ); - const stillUnsatisfied = freshDeps.some((d) => { - const target = freshMap.get(d.targetActionId); - return !target || target.status !== "done"; - }); - if (!stillUnsatisfied) return false; - fresh.status = "blocked"; - fresh.updatedAt = new Date().toISOString(); - await kv.set(KV.actions, fresh.id, fresh); - await recordAudit(kv, "heal", "mem::heal", [fresh.id], { - reason: "pending-unsatisfied-deps", - previousStatus: "pending", - newStatus: "blocked", - }); - return true; - }, - ); - if (didFix) { - details.push( - `Blocked action "${action.title}" (${action.id})`, - ); - fixed++; - } else { - skipped++; - } - } - } - } - } - } - - if (categories.includes("leases")) { - const leases = await kv.list(KV.leases); - const actions = await kv.list(KV.actions); - const actionIds = new Set(actions.map((a) => a.id)); - - for (const lease of leases) { - if ( - lease.status === "active" && - new Date(lease.expiresAt).getTime() <= now - ) { - if (dryRun) { - details.push( - `[dry-run] Would expire lease ${lease.id} for action ${lease.actionId}`, - ); - fixed++; - continue; - } - const didFix = await withKeyedLock( - `mem:action:${lease.actionId}`, - async () => { - const fresh = await kv.get(KV.leases, lease.id); - if ( - !fresh || - fresh.status !== "active" || - new Date(fresh.expiresAt).getTime() > Date.now() - ) { - return false; - } - fresh.status = "expired"; - await kv.set(KV.leases, fresh.id, fresh); - await recordAudit(kv, "heal", "mem::heal", [fresh.id], { - entityType: "lease", - reason: "expired-lease", - newStatus: "expired", - }); - - const action = await kv.get(KV.actions, fresh.actionId); - if ( - action && - action.status === "active" && - action.assignedTo === fresh.agentId - ) { - action.status = "pending"; - action.assignedTo = undefined; - action.updatedAt = new Date().toISOString(); - await kv.set(KV.actions, action.id, action); - await recordAudit(kv, "heal", "mem::heal", [action.id], { - entityType: "action", - reason: "release-expired-lease", - newStatus: "pending", - }); - } - return true; - }, - ); - if (didFix) { - details.push( - `Expired lease ${lease.id} for action ${lease.actionId}`, - ); - fixed++; - } else { - skipped++; - } - continue; - } - - if (!actionIds.has(lease.actionId)) { - if (dryRun) { - details.push( - `[dry-run] Would delete orphaned lease ${lease.id}`, - ); - fixed++; - continue; - } - await kv.delete(KV.leases, lease.id); - await recordAudit(kv, "heal", "mem::heal", [lease.id], { - entityType: "lease", - reason: "orphaned-lease", - action: "delete", - }); - details.push(`Deleted orphaned lease ${lease.id}`); - fixed++; - } - } - } - - if (categories.includes("sentinels")) { - const sentinels = await kv.list(KV.sentinels); - - for (const sentinel of sentinels) { - if ( - sentinel.status === "watching" && - sentinel.expiresAt && - new Date(sentinel.expiresAt).getTime() <= now - ) { - if (dryRun) { - details.push( - `[dry-run] Would expire sentinel "${sentinel.name}" (${sentinel.id})`, - ); - fixed++; - continue; - } - const didFix = await withKeyedLock( - `mem:sentinel:${sentinel.id}`, - async () => { - const fresh = await kv.get( - KV.sentinels, - sentinel.id, - ); - if (!fresh || fresh.status !== "watching") return false; - if ( - !fresh.expiresAt || - new Date(fresh.expiresAt).getTime() > Date.now() - ) { - return false; - } - fresh.status = "expired"; - await kv.set(KV.sentinels, fresh.id, fresh); - await recordAudit(kv, "heal", "mem::heal", [fresh.id], { - entityType: "sentinel", - reason: "expired-sentinel", - newStatus: "expired", - }); - return true; - }, - ); - if (didFix) { - details.push( - `Expired sentinel "${sentinel.name}" (${sentinel.id})`, - ); - fixed++; - } else { - skipped++; - } - } - } - } - - if (categories.includes("sketches")) { - const sketches = await kv.list(KV.sketches); - - for (const sketch of sketches) { - if ( - sketch.status === "active" && - new Date(sketch.expiresAt).getTime() <= now - ) { - if (dryRun) { - details.push( - `[dry-run] Would discard expired sketch "${sketch.title}" (${sketch.id})`, - ); - fixed++; - continue; - } - const didFix = await withKeyedLock( - `mem:sketch:${sketch.id}`, - async () => { - const fresh = await kv.get(KV.sketches, sketch.id); - if ( - !fresh || - fresh.status !== "active" || - new Date(fresh.expiresAt).getTime() > Date.now() - ) { - return false; - } - - const allEdges = await kv.list(KV.actionEdges); - const actionIdSet = new Set(fresh.actionIds); - for (const edge of allEdges) { - if ( - actionIdSet.has(edge.sourceActionId) || - actionIdSet.has(edge.targetActionId) - ) { - await kv.delete(KV.actionEdges, edge.id); - await recordAudit(kv, "heal", "mem::heal", [edge.id], { - entityType: "actionEdge", - reason: "sketch-gc-discard", - action: "delete", - }); - } - } - for (const actionId of fresh.actionIds) { - await kv.delete(KV.actions, actionId); - await recordAudit(kv, "heal", "mem::heal", [actionId], { - entityType: "action", - reason: "sketch-gc-discard", - action: "delete", - }); - } - - fresh.status = "discarded"; - fresh.discardedAt = new Date().toISOString(); - await kv.set(KV.sketches, fresh.id, fresh); - await recordAudit(kv, "heal", "mem::heal", [fresh.id], { - entityType: "sketch", - reason: "expired-sketch", - newStatus: "discarded", - }); - return true; - }, - ); - if (didFix) { - details.push( - `Discarded expired sketch "${sketch.title}" (${sketch.id})`, - ); - fixed++; - } else { - skipped++; - } - } - } - } - - if (categories.includes("signals")) { - const signals = await kv.list(KV.signals); - - for (const signal of signals) { - if ( - signal.expiresAt && - new Date(signal.expiresAt).getTime() <= now - ) { - if (dryRun) { - details.push( - `[dry-run] Would delete expired signal ${signal.id}`, - ); - fixed++; - continue; - } - await kv.delete(KV.signals, signal.id); - await recordAudit(kv, "heal", "mem::heal", [signal.id], { - entityType: "signal", - reason: "expired-signal", - action: "delete", - }); - details.push(`Deleted expired signal ${signal.id}`); - fixed++; - } - } - } - - if (categories.includes("memories")) { - const memories = await kv.list(KV.memories); - const supersededBy = new Map(); - - for (const memory of memories) { - if (memory.supersedes && memory.supersedes.length > 0) { - for (const sid of memory.supersedes) { - supersededBy.set(sid, memory.id); - } - } - } - - for (const memory of memories) { - if (memory.isLatest && supersededBy.has(memory.id)) { - if (dryRun) { - details.push( - `[dry-run] Would set isLatest=false on memory "${memory.title}" (${memory.id})`, - ); - fixed++; - continue; - } - const didFix = await withKeyedLock( - `mem:memory:${memory.id}`, - async () => { - const fresh = await kv.get(KV.memories, memory.id); - if (!fresh || !fresh.isLatest) return false; - fresh.isLatest = false; - fresh.updatedAt = new Date().toISOString(); - await kv.set(KV.memories, fresh.id, fresh); - await recordAudit(kv, "heal", "mem::heal", [fresh.id], { - entityType: "memory", - reason: "superseded-memory-mark-non-latest", - action: "update", - }); - return true; - }, - ); - if (didFix) { - details.push( - `Set isLatest=false on memory "${memory.title}" (${memory.id})`, - ); - fixed++; - } else { - skipped++; - } - } - } - } - - return { success: true, fixed, skipped, details }; - }, - ); -} diff --git a/src/functions/disk-size-manager.ts b/src/functions/disk-size-manager.ts deleted file mode 100644 index 0ee88b40..00000000 --- a/src/functions/disk-size-manager.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { TriggerAction, type ISdk } from "iii-sdk"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { getMaxBytes } from "../utils/image-store.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import { logger } from "../logger.js"; -import type { StateScope, StateScopeKey } from "../types.js"; - -const DISK_SIZE_KEY: StateScopeKey = "system:currentDiskSize"; - -export function registerDiskSizeManager(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction( - "mem::disk-size-delta", - async (data: { deltaBytes: number }) => { - if (typeof data?.deltaBytes !== "number" || !isFinite(data.deltaBytes)) { - return { success: false, error: "deltaBytes must be a finite number" }; - } - - return withKeyedLock(DISK_SIZE_KEY, async () => { - const currentTotal = - (await kv.get(KV.state, DISK_SIZE_KEY)) || 0; - let newTotal = currentTotal + data.deltaBytes; - - if (newTotal < 0) newTotal = 0; - - await kv.set(KV.state, DISK_SIZE_KEY, newTotal); - - if (data.deltaBytes > 0 && newTotal > getMaxBytes()) { - void sdk.trigger({ function_id: "mem::image-quota-cleanup", payload: {}, action: TriggerAction.Void() }).catch(() => {}); - logger.info("Disk quota exceeded, cleanup triggered", { - currentBytes: newTotal, - maxBytes: getMaxBytes(), - }); - } - - return { success: true, currentTotal: newTotal }; - }); - }, - ); -} diff --git a/src/functions/enrich.ts b/src/functions/enrich.ts deleted file mode 100644 index 032ec97b..00000000 --- a/src/functions/enrich.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { Memory } from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { logger } from "../logger.js"; - -const MAX_CONTEXT_LENGTH = 4000; - -function escapeXml(s: string): string { - return s - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} - -export function registerEnrichFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::enrich", - async (data: { - sessionId: string; - files: string[]; - terms?: string[]; - toolName?: string; - project?: string; - }) => { - const project = - typeof data.project === "string" && data.project.trim().length > 0 - ? data.project.trim() - : undefined; - - const parts: string[] = []; - - const fileContextPromise = sdk - .trigger<{ sessionId: string; files: string[] }, { context: string }>({ - function_id: "mem::file-context", - payload: { - sessionId: data.sessionId, - files: data.files, - }, - }) - .catch(() => ({ context: "" })); - - const searchQueries: string[] = [ - ...data.files.map((f) => f.split("/").pop() || f), - ...(data.terms || []), - ].filter((q) => q.length > 0); - - const searchPromise = - searchQueries.length > 0 - ? sdk - .trigger< - { query: string; limit: number; project?: string }, - { results: Array<{ observation: { narrative: string } }> } - >({ - function_id: "mem::search", - payload: { - query: searchQueries.join(" "), - limit: 5, - ...(project !== undefined && { project }), - }, - }) - .catch(() => ({ results: [] })) - : Promise.resolve({ results: [] }); - - const bugMemoriesPromise = kv - .list(KV.memories) - .then((memories) => - memories - .filter( - (m) => - m.type === "bug" && - m.isLatest && - // Guard only when both sides have an explicit project; unscoped memories pass through. - (!project || !m.project || m.project === project) && - m.files.some((f) => - data.files.some((df) => f.includes(df) || df.includes(f)), - ), - ) - .sort( - (a, b) => - new Date(b.updatedAt || b.createdAt).getTime() - - new Date(a.updatedAt || a.createdAt).getTime(), - ), - ) - .catch(() => []); - - const [fileContext, searchResult, bugMemories] = await Promise.all([ - fileContextPromise, - searchPromise, - bugMemoriesPromise, - ]); - - if (fileContext.context) { - parts.push(fileContext.context); - } - - if (searchResult.results.length > 0) { - const observations = searchResult.results - .map((r) => r.observation?.narrative) - .filter(Boolean) - .map((n) => escapeXml(n as string)) - .join("\n"); - if (observations) { - parts.push( - `\n${observations}\n`, - ); - } - } - - if (bugMemories.length > 0) { - const bugs = bugMemories - .slice(0, 3) - .map((m) => `- ${escapeXml(m.title)}: ${escapeXml(m.content)}`) - .join("\n"); - parts.push( - `\n${bugs}\n`, - ); - } - - let context = parts.join("\n\n"); - let truncated = false; - if (context.length > MAX_CONTEXT_LENGTH) { - context = context.slice(0, MAX_CONTEXT_LENGTH); - truncated = true; - } - - logger.info("Enrichment completed", { - sessionId: data.sessionId, - project, - fileCount: data.files.length, - contextLength: context.length, - truncated, - }); - - return { context, truncated }; - }, - ); -} diff --git a/src/functions/evict.ts b/src/functions/evict.ts deleted file mode 100644 index 626f4ff1..00000000 --- a/src/functions/evict.ts +++ /dev/null @@ -1,347 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - Session, - CompressedObservation, - RawObservation, - SessionSummary, - Memory, -} from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; -import { deleteAccessLog } from "./access-tracker.js"; -import { logger } from "../logger.js"; - -interface EvictionConfig { - staleSessionDays: number; - lowImportanceMaxDays: number; - lowImportanceThreshold: number; - maxObservationsPerProject: number; -} - -const MS_PER_DAY = 24 * 60 * 60 * 1000; - -const DEFAULTS: EvictionConfig = { - staleSessionDays: 30, - lowImportanceMaxDays: 90, - lowImportanceThreshold: 3, - maxObservationsPerProject: 10_000, -}; - -interface EvictionStats { - staleSessions: number; - lowImportanceObs: number; - capEvictions: number; - expiredMemories: number; - nonLatestMemories: number; - dryRun: boolean; -} - -function isValidRecoveryResult(result: unknown): boolean { - if (!result || typeof result !== "object") return false; - if (!("success" in result)) return true; - return (result as { success?: unknown }).success !== false; -} - -function isCompressedObservation( - observation: CompressedObservation | RawObservation, -): observation is CompressedObservation { - return ( - "title" in observation && - typeof observation.title === "string" && - observation.title.length > 0 - ); -} - -async function recoverStaleSession( - sdk: ISdk, - sessionId: string, -): Promise { - try { - const result = await sdk.trigger({ - function_id: "event::session::stopped", - payload: { sessionId }, - }); - if (!isValidRecoveryResult(result)) { - logger.warn("Stale session recovery failed", { - sessionId, - result, - }); - return false; - } - return true; - } catch (err) { - logger.warn("Stale session recovery failed", { - sessionId, - error: err instanceof Error ? err.message : String(err), - }); - return false; - } -} - -async function runRecoveredSessionConsolidation(sdk: ISdk): Promise { - try { - await sdk.trigger({ - function_id: "mem::consolidate-pipeline", - payload: { tier: "all" }, - }); - } catch (err) { - logger.warn("Recovered session consolidation failed", { - error: err instanceof Error ? err.message : String(err), - }); - } -} - -export function registerEvictFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::evict", - async (data: { dryRun?: boolean }): Promise => { - const dryRun = data?.dryRun ?? false; - const { decrementImageRef } = await import("./image-refs.js"); - - const configOverride = await kv - .get>(KV.config, "eviction") - .catch(() => null); - const cfg = { ...DEFAULTS, ...configOverride }; - - const now = Date.now(); - const stats: EvictionStats = { - staleSessions: 0, - lowImportanceObs: 0, - capEvictions: 0, - expiredMemories: 0, - nonLatestMemories: 0, - dryRun, - }; - - let recoveredStaleSessions = 0; - const sessions = await kv.list(KV.sessions).catch(() => []); - const summaries = await kv - .list(KV.summaries) - .catch(() => []); - const summaryIds = new Set(summaries.map((s) => s.sessionId)); - - for (const session of sessions) { - if (!session.startedAt) continue; - const age = now - new Date(session.startedAt).getTime(); - const staleDays = cfg.staleSessionDays * MS_PER_DAY; - if (age > staleDays && !summaryIds.has(session.id)) { - if (dryRun) { - stats.staleSessions++; - } else { - const observations = await kv - .list( - KV.observations(session.id), - ) - .catch((err) => { - logger.warn("Stale session observation scan failed", { - sessionId: session.id, - error: err instanceof Error ? err.message : String(err), - }); - return null; - }); - if (!observations) continue; - - let recovered = false; - const hasCompressedObservations = observations.some( - isCompressedObservation, - ); - if (hasCompressedObservations) { - recovered = await recoverStaleSession(sdk, session.id); - if (!recovered) continue; - recoveredStaleSessions++; - } else if (observations.length > 0) { - logger.warn("Stale session has no compressed observations", { - sessionId: session.id, - }); - continue; - } - - try { - await kv.delete(KV.sessions, session.id); - stats.staleSessions++; - } catch (err) { - logger.warn("Eviction delete failed", { - resource: "session", - id: session.id, - error: err instanceof Error ? err.message : String(err), - }); - continue; - } - await recordAudit(kv, "delete", "mem::evict", [session.id], { - resource: "session", - reason: recovered - ? "stale_session_recovered_then_evicted" - : "stale_session_without_summary", - dryRun, - }); - } - } - } - - if (!dryRun && recoveredStaleSessions > 0) { - await runRecoveredSessionConsolidation(sdk); - } - - const projectObs = new Map(); - for (const session of sessions) { - const obs = await kv - .list(KV.observations(session.id)) - .catch(() => []); - const compressed = obs.filter((o) => o.title); - - for (const o of compressed) { - if (!o.timestamp) continue; - const age = now - new Date(o.timestamp).getTime(); - const maxAge = cfg.lowImportanceMaxDays * MS_PER_DAY; - if ( - age > maxAge && - (o.importance ?? 5) < cfg.lowImportanceThreshold - ) { - if (dryRun) { - stats.lowImportanceObs++; - } else { - try { - await kv.delete(KV.observations(session.id), o.id); - stats.lowImportanceObs++; - } catch (err) { - logger.warn("Eviction delete failed", { - resource: "observation", - id: o.id, - sessionId: session.id, - error: err instanceof Error ? err.message : String(err), - }); - continue; - } - if (o.imageData) await decrementImageRef(kv, sdk, o.imageData); - if (o.imageRef && o.imageRef !== o.imageData) await decrementImageRef(kv, sdk, o.imageRef); - await recordAudit(kv, "delete", "mem::evict", [o.id], { - resource: "observation", - reason: "low_importance_old_observation", - sessionId: session.id, - dryRun, - }); - } - } - } - - const project = session.project || "unknown"; - const existing = projectObs.get(project) || []; - existing.push(...compressed); - projectObs.set(project, existing); - } - - for (const [, obs] of projectObs) { - if (obs.length > cfg.maxObservationsPerProject) { - const sorted = obs.sort( - (a, b) => (a.importance ?? 5) - (b.importance ?? 5), - ); - const toEvict = sorted.slice( - 0, - obs.length - cfg.maxObservationsPerProject, - ); - if (dryRun) { - stats.capEvictions += toEvict.length; - } else { - for (const o of toEvict) { - try { - await kv.delete(KV.observations(o.sessionId), o.id); - stats.capEvictions++; - } catch (err) { - logger.warn("Eviction delete failed", { - resource: "observation", - id: o.id, - sessionId: o.sessionId, - error: err instanceof Error ? err.message : String(err), - }); - continue; - } - if (o.imageData) await decrementImageRef(kv, sdk, o.imageData); - if (o.imageRef && o.imageRef !== o.imageData) await decrementImageRef(kv, sdk, o.imageRef); - await recordAudit(kv, "delete", "mem::evict", [o.id], { - resource: "observation", - reason: "project_observation_cap", - sessionId: o.sessionId, - dryRun, - }); - } - } - } - } - - const memories = await kv.list(KV.memories).catch(() => []); - const evictedMemIds = new Set(); - for (const mem of memories) { - if (mem.forgetAfter) { - const expiry = new Date(mem.forgetAfter).getTime(); - if (now > expiry) { - if (dryRun) { - stats.expiredMemories++; - evictedMemIds.add(mem.id); - } else { - try { - await kv.delete(KV.memories, mem.id); - stats.expiredMemories++; - evictedMemIds.add(mem.id); - } catch (err) { - logger.warn("Eviction delete failed", { - resource: "memory", - id: mem.id, - reason: "expired_memory", - error: err instanceof Error ? err.message : String(err), - }); - continue; - } - if (mem.imageRef) { - await decrementImageRef(kv, sdk, mem.imageRef); - } - await recordAudit(kv, "delete", "mem::evict", [mem.id], { - resource: "memory", - reason: "expired_memory", - dryRun, - }); - await deleteAccessLog(kv, mem.id); - } - } - } - - if ( - !evictedMemIds.has(mem.id) && - mem.isLatest === false && - mem.createdAt - ) { - const age = now - new Date(mem.createdAt).getTime(); - if (age > cfg.lowImportanceMaxDays * MS_PER_DAY) { - if (dryRun) { - stats.nonLatestMemories++; - } else { - try { - await kv.delete(KV.memories, mem.id); - stats.nonLatestMemories++; - } catch (err) { - logger.warn("Eviction delete failed", { - resource: "memory", - id: mem.id, - reason: "old_non_latest_memory", - error: err instanceof Error ? err.message : String(err), - }); - continue; - } - if (mem.imageRef) { - await decrementImageRef(kv, sdk, mem.imageRef); - } - await recordAudit(kv, "delete", "mem::evict", [mem.id], { - resource: "memory", - reason: "old_non_latest_memory", - dryRun, - }); - await deleteAccessLog(kv, mem.id); - } - } - } - } - - logger.info("Eviction complete", { stats }); - return stats; - }, - ); -} diff --git a/src/functions/export-import.ts b/src/functions/export-import.ts deleted file mode 100644 index cb1d803d..00000000 --- a/src/functions/export-import.ts +++ /dev/null @@ -1,587 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - Session, - CompressedObservation, - Memory, - SessionSummary, - ProjectProfile, - ExportData, - GraphNode, - GraphEdge, - SemanticMemory, - ProceduralMemory, - Action, - ActionEdge, - Routine, - Signal, - Checkpoint, - Sentinel, - Sketch, - Crystal, - Facet, - Lesson, - Insight, - ExportPagination, - AccessLogExport, -} from "../types.js"; -import { normalizeAccessLog } from "./access-tracker.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { VERSION } from "../version.js"; -import { recordAudit } from "./audit.js"; -import { logger } from "../logger.js"; - -export function registerExportImportFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::export", - async (data?: { maxSessions?: number; offset?: number }) => { - const rawMax = Number(data?.maxSessions); - const maxSessions = Number.isFinite(rawMax) && rawMax > 0 ? Math.min(Math.floor(rawMax), 1000) : undefined; - const rawOffset = Number(data?.offset); - const offset = Number.isFinite(rawOffset) && rawOffset >= 0 ? Math.floor(rawOffset) : 0; - - const allSessions = await kv.list(KV.sessions); - const paginatedSessions = maxSessions !== undefined - ? allSessions.slice(offset, offset + maxSessions) - : allSessions; - const memories = await kv.list(KV.memories); - const summaries = await kv.list(KV.summaries); - - const observations: Record = {}; - const obsResults = await Promise.all( - paginatedSessions.map((session) => - kv - .list(KV.observations(session.id)) - .catch(() => [] as CompressedObservation[]) - .then((obs) => ({ sessionId: session.id, obs })), - ), - ); - for (const { sessionId, obs } of obsResults) { - if (obs.length > 0) { - observations[sessionId] = obs; - } - } - - const profiles: ProjectProfile[] = []; - const uniqueProjects = [...new Set(paginatedSessions.map((s) => s.project))]; - const profileResults = await Promise.all( - uniqueProjects.map((project) => - kv.get(KV.profiles, project).catch(() => null), - ), - ); - for (const profile of profileResults) { - if (profile) profiles.push(profile); - } - - const [ - graphNodes, - graphEdges, - semanticMemories, - proceduralMemories, - actions, - actionEdges, - sentinels, - sketches, - crystals, - facets, - lessons, - insights, - routines, - signals, - checkpoints, - accessLogs, - ] = await Promise.all([ - kv.list(KV.graphNodes).catch(() => []), - kv.list(KV.graphEdges).catch(() => []), - kv.list(KV.semantic).catch(() => []), - kv.list(KV.procedural).catch(() => []), - kv.list(KV.actions).catch(() => []), - kv.list(KV.actionEdges).catch(() => []), - kv.list(KV.sentinels).catch(() => []), - kv.list(KV.sketches).catch(() => []), - kv.list(KV.crystals).catch(() => []), - kv.list(KV.facets).catch(() => []), - kv.list(KV.lessons).catch(() => []), - kv.list(KV.insights).catch(() => []), - kv.list(KV.routines).catch(() => []), - kv.list(KV.signals).catch(() => []), - kv.list(KV.checkpoints).catch(() => []), - kv.list(KV.accessLog).catch(() => []), - ]); - - const exportData: ExportData = { - version: VERSION, - exportedAt: new Date().toISOString(), - sessions: paginatedSessions, - observations, - memories, - summaries, - profiles: profiles.length > 0 ? profiles : undefined, - graphNodes: graphNodes.length > 0 ? graphNodes : undefined, - graphEdges: graphEdges.length > 0 ? graphEdges : undefined, - semanticMemories: - semanticMemories.length > 0 ? semanticMemories : undefined, - proceduralMemories: - proceduralMemories.length > 0 ? proceduralMemories : undefined, - actions: actions.length > 0 ? actions : undefined, - actionEdges: actionEdges.length > 0 ? actionEdges : undefined, - sentinels: sentinels.length > 0 ? sentinels : undefined, - sketches: sketches.length > 0 ? sketches : undefined, - crystals: crystals.length > 0 ? crystals : undefined, - facets: facets.length > 0 ? facets : undefined, - lessons: lessons.length > 0 ? lessons : undefined, - insights: insights.length > 0 ? insights : undefined, - routines: routines.length > 0 ? routines : undefined, - signals: signals.length > 0 ? signals : undefined, - checkpoints: checkpoints.length > 0 ? checkpoints : undefined, - accessLogs: accessLogs.length > 0 ? accessLogs : undefined, - }; - - if (maxSessions !== undefined) { - exportData.pagination = { - offset, - limit: maxSessions, - total: allSessions.length, - hasMore: offset + maxSessions < allSessions.length, - }; - } - - const totalObs = Object.values(observations).reduce( - (sum, arr) => sum + arr.length, - 0, - ); - logger.info("Export complete", { - sessions: paginatedSessions.length, - totalSessions: allSessions.length, - observations: totalObs, - memories: memories.length, - summaries: summaries.length, - }); - - return exportData; - }, - ); - - sdk.registerFunction("mem::import", - async (data: { - exportData: ExportData; - strategy?: "merge" | "replace" | "skip"; - }) => { - if ( - !data?.exportData || - typeof data.exportData !== "object" || - typeof (data.exportData as { version?: unknown }).version !== "string" - ) { - return { success: false, error: "exportData with string version is required" }; - } - const strategy = data.strategy || "merge"; - const importData = data.exportData; - - const supportedVersions = new Set(["0.3.0", "0.4.0", "0.5.0", "0.6.0", "0.6.1", "0.7.0", "0.7.2", "0.7.3", "0.7.4", "0.7.5", "0.7.6", "0.7.7", "0.7.9", "0.8.0", "0.8.1", "0.8.2", "0.8.3", "0.8.4", "0.8.5", "0.8.6", "0.8.7", "0.8.8", "0.8.9", "0.8.10", "0.8.11", "0.8.12", "0.8.13", "0.9.0", "0.9.1", "0.9.2", "0.9.3", "0.9.4", "0.9.5", "0.9.6", "0.9.7", "0.9.8", "0.9.9", "0.9.10", "0.9.11", "0.9.12", "0.9.13", "0.9.14", "0.9.15", "0.9.16", "0.9.17", "0.9.18", "0.9.19", "0.9.20", "0.9.21", "0.9.22", "0.9.23", "0.9.24"]); - if (!supportedVersions.has(importData.version)) { - return { - success: false, - error: `Unsupported export version: ${importData.version}`, - }; - } - - const MAX_SESSIONS = 10_000; - const MAX_MEMORIES = 50_000; - const MAX_SUMMARIES = 10_000; - const MAX_OBS_PER_SESSION = 5_000; - const MAX_TOTAL_OBSERVATIONS = 500_000; - const MAX_ACCESS_LOGS = 50_000; - - if (!Array.isArray(importData.sessions)) { - return { success: false, error: "sessions must be an array" }; - } - if (!Array.isArray(importData.memories)) { - return { success: false, error: "memories must be an array" }; - } - if (!Array.isArray(importData.summaries)) { - return { success: false, error: "summaries must be an array" }; - } - if ( - typeof importData.observations !== "object" || - importData.observations === null || - Array.isArray(importData.observations) - ) { - return { success: false, error: "observations must be an object" }; - } - - if (importData.sessions.length > MAX_SESSIONS) { - return { - success: false, - error: `Too many sessions (max ${MAX_SESSIONS})`, - }; - } - if (importData.memories.length > MAX_MEMORIES) { - return { - success: false, - error: `Too many memories (max ${MAX_MEMORIES})`, - }; - } - if (importData.summaries.length > MAX_SUMMARIES) { - return { - success: false, - error: `Too many summaries (max ${MAX_SUMMARIES})`, - }; - } - const MAX_OBS_BUCKETS = 10_000; - const obsBuckets = Object.keys(importData.observations); - if (obsBuckets.length > MAX_OBS_BUCKETS) { - return { - success: false, - error: `Too many observation buckets (max ${MAX_OBS_BUCKETS})`, - }; - } - - let totalObservations = 0; - for (const [, obs] of Object.entries(importData.observations)) { - if (!Array.isArray(obs)) { - return { success: false, error: "observation values must be arrays" }; - } - if (obs.length > MAX_OBS_PER_SESSION) { - return { - success: false, - error: `Too many observations per session (max ${MAX_OBS_PER_SESSION})`, - }; - } - totalObservations += obs.length; - } - if (totalObservations > MAX_TOTAL_OBSERVATIONS) { - return { - success: false, - error: `Too many total observations (max ${MAX_TOTAL_OBSERVATIONS})`, - }; - } - - const stats = { - sessions: 0, - observations: 0, - memories: 0, - summaries: 0, - skipped: 0, - }; - - if (strategy === "replace") { - const existing = await kv.list(KV.sessions); - for (const session of existing) { - await kv.delete(KV.sessions, session.id); - const obs = await kv - .list(KV.observations(session.id)) - .catch(() => []); - for (const o of obs) { - await kv.delete(KV.observations(session.id), o.id); - } - } - const existingMem = await kv.list(KV.memories); - for (const m of existingMem) { - await kv.delete(KV.memories, m.id); - } - const existingSummaries = await kv.list(KV.summaries); - for (const s of existingSummaries) { - await kv.delete(KV.summaries, s.sessionId); - } - for (const a of await kv.list(KV.actions).catch(() => [])) { - await kv.delete(KV.actions, a.id); - } - for (const e of await kv.list(KV.actionEdges).catch(() => [])) { - await kv.delete(KV.actionEdges, e.id); - } - for (const r of await kv.list(KV.routines).catch(() => [])) { - await kv.delete(KV.routines, r.id); - } - for (const s of await kv.list(KV.signals).catch(() => [])) { - await kv.delete(KV.signals, s.id); - } - for (const c of await kv.list(KV.checkpoints).catch(() => [])) { - await kv.delete(KV.checkpoints, c.id); - } - for (const s of await kv.list(KV.sentinels).catch(() => [])) { - await kv.delete(KV.sentinels, s.id); - } - for (const s of await kv.list(KV.sketches).catch(() => [])) { - await kv.delete(KV.sketches, s.id); - } - for (const c of await kv.list(KV.crystals).catch(() => [])) { - await kv.delete(KV.crystals, c.id); - } - for (const f of await kv.list(KV.facets).catch(() => [])) { - await kv.delete(KV.facets, f.id); - } - for (const l of await kv.list(KV.lessons).catch(() => [])) { - await kv.delete(KV.lessons, l.id); - } - for (const i of await kv.list(KV.insights).catch(() => [])) { - await kv.delete(KV.insights, i.id); - } - for (const n of await kv.list<{ id: string }>(KV.graphNodes).catch(() => [])) { - await kv.delete(KV.graphNodes, n.id); - } - for (const e of await kv.list<{ id: string }>(KV.graphEdges).catch(() => [])) { - await kv.delete(KV.graphEdges, e.id); - } - for (const s of await kv.list<{ id: string }>(KV.semantic).catch(() => [])) { - await kv.delete(KV.semantic, s.id); - } - for (const p of await kv.list<{ id: string }>(KV.procedural).catch(() => [])) { - await kv.delete(KV.procedural, p.id); - } - for (const profile of await kv.list(KV.profiles).catch(() => [])) { - await kv.delete(KV.profiles, profile.project); - } - for (const a of await kv.list(KV.accessLog).catch(() => [])) { - await kv.delete(KV.accessLog, a.memoryId); - } - } - - for (const session of importData.sessions) { - if (strategy === "skip") { - const existing = await kv - .get(KV.sessions, session.id) - .catch(() => null); - if (existing) { - stats.skipped++; - continue; - } - } - await kv.set(KV.sessions, session.id, session); - stats.sessions++; - } - - for (const [sessionId, obs] of Object.entries(importData.observations)) { - for (const o of obs) { - if (strategy === "skip") { - const existing = await kv - .get(KV.observations(sessionId), o.id) - .catch(() => null); - if (existing) { - stats.skipped++; - continue; - } - } - await kv.set(KV.observations(sessionId), o.id, o); - stats.observations++; - } - } - - for (const memory of importData.memories) { - if (strategy === "skip") { - const existing = await kv - .get(KV.memories, memory.id) - .catch(() => null); - if (existing) { - stats.skipped++; - continue; - } - } - // Older exports + hand-edited dumps can omit this field. - if (!Array.isArray(memory.sessionIds)) { - memory.sessionIds = []; - } - await kv.set(KV.memories, memory.id, memory); - stats.memories++; - } - - for (const summary of importData.summaries) { - if (strategy === "skip") { - const existing = await kv - .get(KV.summaries, summary.sessionId) - .catch(() => null); - if (existing) { - stats.skipped++; - continue; - } - } - await kv.set(KV.summaries, summary.sessionId, summary); - stats.summaries++; - } - - if (importData.graphNodes) { - for (const node of importData.graphNodes) { - if (strategy === "skip") { - const existing = await kv.get(KV.graphNodes, node.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.graphNodes, node.id, node); - } - } - if (importData.graphEdges) { - for (const edge of importData.graphEdges) { - if (strategy === "skip") { - const existing = await kv.get(KV.graphEdges, edge.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.graphEdges, edge.id, edge); - } - } - if (importData.semanticMemories) { - for (const sem of importData.semanticMemories) { - if (strategy === "skip") { - const existing = await kv.get(KV.semantic, sem.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.semantic, sem.id, sem); - } - } - if (importData.proceduralMemories) { - for (const proc of importData.proceduralMemories) { - if (strategy === "skip") { - const existing = await kv.get(KV.procedural, proc.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.procedural, proc.id, proc); - } - } - if (importData.profiles) { - for (const profile of importData.profiles) { - if (strategy === "skip") { - const existing = await kv - .get(KV.profiles, profile.project) - .catch(() => null); - if (existing) { - stats.skipped++; - continue; - } - } - await kv.set(KV.profiles, profile.project, profile); - } - } - - if (importData.actions) { - for (const action of importData.actions) { - if (strategy === "skip") { - const existing = await kv.get(KV.actions, action.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.actions, action.id, action); - } - } - if (importData.actionEdges) { - for (const edge of importData.actionEdges) { - if (strategy === "skip") { - const existing = await kv.get(KV.actionEdges, edge.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.actionEdges, edge.id, edge); - } - } - if (importData.routines) { - for (const routine of importData.routines) { - if (strategy === "skip") { - const existing = await kv.get(KV.routines, routine.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.routines, routine.id, routine); - } - } - if (importData.signals) { - for (const signal of importData.signals) { - if (strategy === "skip") { - const existing = await kv.get(KV.signals, signal.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.signals, signal.id, signal); - } - } - if (importData.checkpoints) { - for (const checkpoint of importData.checkpoints) { - if (strategy === "skip") { - const existing = await kv.get(KV.checkpoints, checkpoint.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.checkpoints, checkpoint.id, checkpoint); - } - } - if (importData.sentinels) { - for (const sentinel of importData.sentinels) { - if (strategy === "skip") { - const existing = await kv.get(KV.sentinels, sentinel.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.sentinels, sentinel.id, sentinel); - } - } - if (importData.sketches) { - for (const sketch of importData.sketches) { - if (strategy === "skip") { - const existing = await kv.get(KV.sketches, sketch.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.sketches, sketch.id, sketch); - } - } - if (importData.crystals) { - for (const crystal of importData.crystals) { - if (strategy === "skip") { - const existing = await kv.get(KV.crystals, crystal.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.crystals, crystal.id, crystal); - } - } - if (importData.facets) { - for (const facet of importData.facets) { - if (strategy === "skip") { - const existing = await kv.get(KV.facets, facet.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.facets, facet.id, facet); - } - } - if (importData.lessons) { - for (const lesson of importData.lessons) { - if (strategy === "skip") { - const existing = await kv.get(KV.lessons, lesson.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.lessons, lesson.id, lesson); - } - } - if (importData.insights) { - for (const insight of importData.insights) { - if (strategy === "skip") { - const existing = await kv.get(KV.insights, insight.id).catch(() => null); - if (existing) { stats.skipped++; continue; } - } - await kv.set(KV.insights, insight.id, insight); - } - } - if (importData.accessLogs) { - if (!Array.isArray(importData.accessLogs)) { - return { success: false, error: "accessLogs must be an array" }; - } - if (importData.accessLogs.length > MAX_ACCESS_LOGS) { - return { - success: false, - error: `Too many access logs (max ${MAX_ACCESS_LOGS})`, - }; - } - const memoryIds = new Set( - importData.memories.map((m) => m.id), - ); - for (const raw of importData.accessLogs) { - const log = normalizeAccessLog(raw); - if (!log.memoryId || !memoryIds.has(log.memoryId)) continue; - if (strategy === "skip") { - const existing = await kv - .get(KV.accessLog, log.memoryId) - .catch(() => null); - if (existing) { - stats.skipped++; - continue; - } - } - await kv.set(KV.accessLog, log.memoryId, log); - } - } - - logger.info("Import complete", { strategy, ...stats }); - await recordAudit(kv, "import", "mem::import", [], { - strategy, - stats, - }); - return { success: true, strategy, ...stats }; - }, - ); -} diff --git a/src/functions/facets.ts b/src/functions/facets.ts deleted file mode 100644 index cb5b37dc..00000000 --- a/src/functions/facets.ts +++ /dev/null @@ -1,242 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import type { Facet } from "../types.js"; - -export function registerFacetsFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::facet-tag", - async (data: { - targetId: string; - targetType: string; - dimension: string; - value: string; - }) => { - if (!data.targetId || typeof data.targetId !== "string") { - return { success: false, error: "targetId is required" }; - } - - const validTypes = ["action", "memory", "observation"]; - if (!validTypes.includes(data.targetType)) { - return { - success: false, - error: `targetType must be one of: ${validTypes.join(", ")}`, - }; - } - - if ( - !data.dimension || - typeof data.dimension !== "string" || - data.dimension.trim() === "" - ) { - return { success: false, error: "dimension is required" }; - } - - if ( - !data.value || - typeof data.value !== "string" || - data.value.trim() === "" - ) { - return { success: false, error: "value is required" }; - } - - const dimension = data.dimension.trim(); - const value = data.value.trim(); - - const existing = await kv.list(KV.facets); - const duplicate = existing.find( - (f) => - f.targetId === data.targetId && - f.dimension === dimension && - f.value === value, - ); - if (duplicate) { - return { success: true, facet: duplicate, skipped: true }; - } - - const facet: Facet = { - id: generateId("fct"), - targetId: data.targetId, - targetType: data.targetType as Facet["targetType"], - dimension, - value, - createdAt: new Date().toISOString(), - }; - - await kv.set(KV.facets, facet.id, facet); - return { success: true, facet }; - }, - ); - - sdk.registerFunction("mem::facet-untag", - async (data: { - targetId: string; - dimension: string; - value?: string; - }) => { - if (!data.targetId) { - return { success: false, error: "targetId is required" }; - } - if (!data.dimension) { - return { success: false, error: "dimension is required" }; - } - - const all = await kv.list(KV.facets); - const matches = all.filter((f) => { - if (f.targetId !== data.targetId || f.dimension !== data.dimension) { - return false; - } - if (data.value !== undefined) { - return f.value === data.value; - } - return true; - }); - - for (const f of matches) { - await kv.delete(KV.facets, f.id); - } - - return { success: true, removed: matches.length }; - }, - ); - - sdk.registerFunction("mem::facet-query", - async (data: { - matchAll?: string[]; - matchAny?: string[]; - targetType?: string; - limit?: number; - }) => { - if ( - (!data.matchAll || data.matchAll.length === 0) && - (!data.matchAny || data.matchAny.length === 0) - ) { - return { - success: false, - error: "at least one of matchAll or matchAny is required", - }; - } - - const all = await kv.list(KV.facets); - const filtered = data.targetType - ? all.filter((f) => f.targetType === data.targetType) - : all; - - const targetFacetMap = new Map }>(); - for (const f of filtered) { - const key = `${f.dimension}:${f.value}`; - let entry = targetFacetMap.get(f.targetId); - if (!entry) { - entry = { targetType: f.targetType, facetKeys: new Set() }; - targetFacetMap.set(f.targetId, entry); - } - entry.facetKeys.add(key); - } - - const results: Array<{ targetId: string; targetType: string; matchedFacets: string[] }> = []; - - for (const [targetId, entry] of targetFacetMap) { - const matched: string[] = []; - - if (data.matchAll && data.matchAll.length > 0) { - const allPresent = data.matchAll.every((k) => entry.facetKeys.has(k)); - if (!allPresent) continue; - for (const k of data.matchAll) { - if (!matched.includes(k)) matched.push(k); - } - } - - if (data.matchAny && data.matchAny.length > 0) { - const anyPresent = data.matchAny.filter((k) => entry.facetKeys.has(k)); - if (anyPresent.length === 0) continue; - for (const k of anyPresent) { - if (!matched.includes(k)) matched.push(k); - } - } - - results.push({ - targetId, - targetType: entry.targetType, - matchedFacets: matched, - }); - } - - const limit = data.limit || 50; - return { success: true, results: results.slice(0, limit) }; - }, - ); - - sdk.registerFunction("mem::facet-get", - async (data: { targetId: string }) => { - if (!data.targetId) { - return { success: false, error: "targetId is required" }; - } - - const all = await kv.list(KV.facets); - const targetFacets = all.filter((f) => f.targetId === data.targetId); - - const dimMap = new Map(); - for (const f of targetFacets) { - let values = dimMap.get(f.dimension); - if (!values) { - values = []; - dimMap.set(f.dimension, values); - } - values.push(f.value); - } - - const dimensions = Array.from(dimMap.entries()).map(([dimension, values]) => ({ - dimension, - values, - })); - - return { success: true, dimensions }; - }, - ); - - sdk.registerFunction("mem::facet-stats", - async (data: { targetType?: string }) => { - const all = await kv.list(KV.facets); - const filtered = data.targetType - ? all.filter((f) => f.targetType === data.targetType) - : all; - - const dimMap = new Map>(); - for (const f of filtered) { - let valueMap = dimMap.get(f.dimension); - if (!valueMap) { - valueMap = new Map(); - dimMap.set(f.dimension, valueMap); - } - valueMap.set(f.value, (valueMap.get(f.value) || 0) + 1); - } - - const dimensions = Array.from(dimMap.entries()).map(([dimension, valueMap]) => ({ - dimension, - values: Array.from(valueMap.entries()).map(([value, count]) => ({ - value, - count, - })), - })); - - return { success: true, dimensions, totalFacets: filtered.length }; - }, - ); - - sdk.registerFunction("mem::facet-dimensions", - async () => { - const all = await kv.list(KV.facets); - - const counts = new Map(); - for (const f of all) { - counts.set(f.dimension, (counts.get(f.dimension) || 0) + 1); - } - - const dimensions = Array.from(counts.entries()).map(([dimension, count]) => ({ - dimension, - count, - })); - - return { success: true, dimensions }; - }, - ); -} diff --git a/src/functions/file-index.ts b/src/functions/file-index.ts deleted file mode 100644 index a5c50318..00000000 --- a/src/functions/file-index.ts +++ /dev/null @@ -1,134 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { CompressedObservation, Session } from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; -import { recordAccessBatch } from "./access-tracker.js"; -import { logger } from "../logger.js"; - -interface FileHistory { - file: string; - observations: Array<{ - sessionId: string; - obsId: string; - type: string; - title: string; - narrative: string; - importance: number; - timestamp: string; - }>; -} - -export function registerFileIndexFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::file-context", - async ( - data: { sessionId?: string; files?: string[]; project?: string } | undefined, - ) => { - const sessionId = - data && typeof data.sessionId === "string" ? data.sessionId.trim() : ""; - const normalizedProject = - typeof data?.project === "string" ? data.project.trim() : undefined; - const files = Array.isArray(data?.files) - ? data!.files - .map((file) => (typeof file === "string" ? file.trim() : "")) - .filter(Boolean) - : []; - if (files.length === 0) { - await recordAudit(kv, "observe", "mem::file-context", [sessionId || "unknown"], { - error: "invalid_payload", - hasSessionId: !!sessionId, - hasProject: !!normalizedProject, - fileCount: files.length, - }); - return { context: "", files: [] }; - } - const results: FileHistory[] = []; - - const sessions = await kv.list(KV.sessions); - let otherSessions = sessionId - ? sessions.filter((s) => s.id !== sessionId) - : sessions; - if (normalizedProject) { - otherSessions = otherSessions.filter((s) => s.project === normalizedProject); - } - otherSessions = otherSessions - .sort( - (a, b) => - new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime(), - ) - .slice(0, 15); - - const obsCache = new Map(); - for (const session of otherSessions) { - obsCache.set( - session.id, - await kv.list(KV.observations(session.id)), - ); - } - - for (const file of files) { - const history: FileHistory = { file, observations: [] }; - const normalizedFile = file.replace(/^\.\//, ""); - - for (const session of otherSessions) { - const observations = obsCache.get(session.id) || []; - - for (const obs of observations) { - if (!obs.files || !obs.title) continue; - const matches = obs.files.some( - (f) => - f === file || - f === normalizedFile || - f.endsWith(`/${normalizedFile}`) || - normalizedFile.endsWith(`/${f}`), - ); - if (matches && obs.importance >= 4) { - history.observations.push({ - sessionId: session.id, - obsId: obs.id, - type: obs.type, - title: obs.title, - narrative: obs.narrative, - importance: obs.importance, - timestamp: obs.timestamp, - }); - } - } - } - - history.observations.sort((a, b) => b.importance - a.importance); - history.observations = history.observations.slice(0, 5); - - if (history.observations.length > 0) { - results.push(history); - } - } - - if (results.length === 0) { - return { context: "" }; - } - - const lines: string[] = [""]; - for (const fh of results) { - lines.push(`## ${fh.file}`); - for (const obs of fh.observations) { - lines.push(`- [${obs.type}] ${obs.title}: ${obs.narrative}`); - } - } - lines.push(""); - - const accessedIds: string[] = []; - for (const fh of results) { - for (const obs of fh.observations) accessedIds.push(obs.obsId); - } - void recordAccessBatch(kv, accessedIds); - - const context = lines.join("\n"); - logger.info("File context generated", { - files: files.length, - results: results.length, - }); - return { context }; - }, - ); -} diff --git a/src/functions/flow-compress.ts b/src/functions/flow-compress.ts deleted file mode 100644 index d2eb5a31..00000000 --- a/src/functions/flow-compress.ts +++ /dev/null @@ -1,221 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import type { Action, ActionEdge, RoutineRun, MemoryProvider } from "../types.js"; -import { recordAudit } from "./audit.js"; - -const FLOW_COMPRESS_SYSTEM = `You are a workflow summarizer. Given a completed action chain, produce a concise summary capturing: -1. The overall goal and outcome -2. Key steps taken and their results -3. Any notable decisions or discoveries -4. Lessons learned - -Output as XML: - -What was the workflow trying to achieve -What happened -Numbered list of key steps -Any new insights or discoveries -What to remember for next time -`; - -export function registerFlowCompressFunction( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, -): void { - sdk.registerFunction("mem::flow-compress", - async (data: { runId?: string; actionIds?: string[]; project?: string }) => { - let actionsToCompress: Action[] = []; - - if (data.runId) { - const run = await kv.get(KV.routineRuns, data.runId); - if (!run) { - return { success: false, error: "run not found" }; - } - for (const id of run.actionIds) { - const action = await kv.get(KV.actions, id); - if (action) actionsToCompress.push(action); - } - } else if (data.actionIds && data.actionIds.length > 0) { - for (const id of data.actionIds) { - const action = await kv.get(KV.actions, id); - if (action) actionsToCompress.push(action); - } - } else if (data.project) { - const allActions = await kv.list(KV.actions); - actionsToCompress = allActions.filter( - (a) => a.project === data.project && a.status === "done", - ); - } else { - return { - success: false, - error: "runId, actionIds, or project is required", - }; - } - - const doneActions = actionsToCompress.filter( - (a) => a.status === "done", - ); - if (doneActions.length === 0) { - return { - success: true, - message: "No completed actions to compress", - compressed: 0, - }; - } - - const allEdges = await kv.list(KV.actionEdges); - const relevantIds = new Set(doneActions.map((a) => a.id)); - const relevantEdges = allEdges.filter( - (e) => - relevantIds.has(e.sourceActionId) || - relevantIds.has(e.targetActionId), - ); - - const prompt = buildFlowPrompt(doneActions, relevantEdges); - - try { - const response = await provider.summarize( - FLOW_COMPRESS_SYSTEM, - prompt, - ); - const summary = parseFlowSummary(response); - const ts = new Date().toISOString(); - - const memory = { - id: generateId("mem"), - createdAt: ts, - updatedAt: ts, - type: "workflow" as const, - title: summary.goal || `Workflow: ${doneActions.length} actions`, - content: formatSummary(summary), - concepts: extractConcepts(doneActions), - files: extractFiles(doneActions), - sessionIds: [], - strength: 1.0, - version: 1, - isLatest: true, - metadata: { - flowCompressed: true, - actionCount: doneActions.length, - actionIds: doneActions.map((a) => a.id), - }, - }; - - await kv.set(KV.memories, memory.id, memory); - await recordAudit(kv, "compress", "mem::flow-compress", [memory.id], { - action: "compress_flow", - flowCompressed: true, - actionCount: doneActions.length, - project: data.project, - }); - - return { - success: true, - compressed: doneActions.length, - memoryId: memory.id, - summary, - }; - } catch (err) { - return { - success: false, - error: `compression failed: ${String(err)}`, - compressed: 0, - }; - } - }, - ); -} - -function buildFlowPrompt( - actions: Action[], - edges: ActionEdge[], -): string { - const lines: string[] = ["## Completed Action Chain\n"]; - - const sorted = [...actions].sort( - (a, b) => - new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(), - ); - - for (const action of sorted) { - lines.push(`### ${action.title}`); - if (action.description) lines.push(action.description); - if (action.result) lines.push(`Result: ${action.result}`); - lines.push(`Priority: ${action.priority}, Tags: ${(action.tags ?? []).join(", ")}`); - lines.push(""); - } - - if (edges.length > 0) { - lines.push("## Dependencies"); - for (const edge of edges) { - lines.push(`- ${edge.sourceActionId} --${edge.type}--> ${edge.targetActionId}`); - } - } - - return lines.join("\n"); -} - -function parseFlowSummary(response: string): { - goal: string; - outcome: string; - steps: string; - discoveries: string; - lesson: string; -} { - const extract = (tag: string): string => { - const match = response.match( - new RegExp(`<${tag}>([\\s\\S]*?)`), - ); - return match ? match[1].trim() : ""; - }; - return { - goal: extract("goal"), - outcome: extract("outcome"), - steps: extract("steps"), - discoveries: extract("discoveries"), - lesson: extract("lesson"), - }; -} - -function formatSummary(s: { - goal: string; - outcome: string; - steps: string; - discoveries: string; - lesson: string; -}): string { - const parts: string[] = []; - if (s.goal) parts.push(`Goal: ${s.goal}`); - if (s.outcome) parts.push(`Outcome: ${s.outcome}`); - if (s.steps) parts.push(`Steps: ${s.steps}`); - if (s.discoveries) parts.push(`Discoveries: ${s.discoveries}`); - if (s.lesson) parts.push(`Lesson: ${s.lesson}`); - return parts.join("\n\n"); -} - -function extractConcepts(actions: Action[]): string[] { - const concepts = new Set(); - for (const a of actions) { - for (const tag of a.tags ?? []) { - if (!tag.startsWith("routine:")) concepts.add(tag); - } - } - return Array.from(concepts); -} - -function extractFiles(actions: Action[]): string[] { - const files = new Set(); - for (const a of actions) { - if (a.metadata && typeof a.metadata === "object") { - const meta = a.metadata as Record; - if (Array.isArray(meta.files)) { - for (const f of meta.files) { - if (typeof f === "string") files.add(f); - } - } - } - } - return Array.from(files); -} diff --git a/src/functions/frontier.ts b/src/functions/frontier.ts deleted file mode 100644 index 30f43fec..00000000 --- a/src/functions/frontier.ts +++ /dev/null @@ -1,194 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV } from "../state/schema.js"; -import type { Action, ActionEdge, Checkpoint, Lease } from "../types.js"; - -export interface FrontierItem { - action: Action; - score: number; - blockers: string[]; - leased: boolean; -} - -export function registerFrontierFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::frontier", - async (data: { - project?: string; - agentId?: string; - limit?: number; - includeLeasedByOthers?: boolean; - }) => { - const actions = await kv.list(KV.actions); - const edges = await kv.list(KV.actionEdges); - const leases = await kv.list(KV.leases); - const checkpoints = await kv.list(KV.checkpoints); - const now = Date.now(); - - const activeLeaseMap = new Map(); - for (const lease of leases) { - if ( - lease.status === "active" && - new Date(lease.expiresAt).getTime() > now - ) { - activeLeaseMap.set(lease.actionId, lease); - } - } - - const checkpointMap = new Map(); - for (const cp of checkpoints) { - checkpointMap.set(cp.id, cp); - } - - const actionMap = new Map(); - for (const a of actions) actionMap.set(a.id, a); - - const frontier: FrontierItem[] = []; - - for (const action of actions) { - if (action.status === "done" || action.status === "cancelled") continue; - if (data.project && action.project !== data.project) continue; - - const blockers: string[] = []; - const inEdges = edges.filter( - (e) => e.sourceActionId === action.id && e.type === "requires", - ); - - for (const edge of inEdges) { - const dep = actionMap.get(edge.targetActionId); - if (dep && dep.status !== "done") { - blockers.push(`requires:${dep.id}:${dep.title}`); - } - } - - const gateEdges = edges.filter( - (e) => e.sourceActionId === action.id && e.type === "gated_by", - ); - for (const edge of gateEdges) { - const cp = checkpointMap.get(edge.targetActionId); - if (cp && cp.status !== "passed") { - blockers.push(`checkpoint:${cp.id}:${cp.name}`); - } - } - - const conflictEdges = edges.filter( - (e) => - (e.sourceActionId === action.id || - e.targetActionId === action.id) && - e.type === "conflicts_with", - ); - for (const edge of conflictEdges) { - const otherId = - edge.sourceActionId === action.id - ? edge.targetActionId - : edge.sourceActionId; - const other = actionMap.get(otherId); - if (other && other.status === "active") { - blockers.push(`conflict:${other.id}:${other.title}`); - } - } - - if (blockers.length > 0) continue; - - const lease = activeLeaseMap.get(action.id); - const leasedByOther = - lease && data.agentId && lease.agentId !== data.agentId; - if (leasedByOther && !data.includeLeasedByOthers) continue; - - const score = computeScore(action, edges, now); - - frontier.push({ - action, - score, - blockers: [], - leased: !!lease, - }); - } - - frontier.sort((a, b) => b.score - a.score); - const limit = data.limit || 20; - - return { - success: true, - frontier: frontier.slice(0, limit), - totalActions: actions.length, - totalUnblocked: frontier.length, - }; - }, - ); - - sdk.registerFunction("mem::next", - async (data: { project?: string; agentId?: string }) => { - const result = await sdk.trigger< - { project?: string; agentId?: string; limit?: number }, - { - success: boolean; - frontier: FrontierItem[]; - totalActions: number; - totalUnblocked: number; - } - >({ function_id: "mem::frontier", payload: { - project: data.project, - agentId: data.agentId, - limit: 1, - } }); - - if (!result.success) { - return { - success: false, - suggestion: null, - message: "Failed to compute frontier", - totalActions: 0, - }; - } - if (result.frontier.length === 0) { - return { - success: true, - suggestion: null, - message: "No actionable work found", - totalActions: result.totalActions || 0, - }; - } - - const top = result.frontier[0]; - return { - success: true, - suggestion: { - actionId: top.action.id, - title: top.action.title, - description: top.action.description, - priority: top.action.priority, - score: top.score, - tags: top.action.tags, - }, - message: `Suggested: ${top.action.title} (priority ${top.action.priority}, score ${top.score.toFixed(2)})`, - totalActions: result.totalActions, - totalUnblocked: result.totalUnblocked, - }; - }, - ); -} - -function computeScore( - action: Action, - edges: ActionEdge[], - now: number, -): number { - let score = action.priority * 10; - - const ageHours = - (now - new Date(action.createdAt).getTime()) / (1000 * 60 * 60); - score += Math.min(ageHours * 0.5, 20); - - const unlockCount = edges.filter( - (e) => e.sourceActionId === action.id && e.type === "unlocks", - ).length; - score += unlockCount * 5; - - if (edges.some((e) => e.sourceActionId === action.id && e.type === "spawned_by")) { - score += 3; - } - - if (action.status === "active") score += 15; - - return Math.round(score * 100) / 100; -} diff --git a/src/functions/governance.ts b/src/functions/governance.ts deleted file mode 100644 index 16d0d1c4..00000000 --- a/src/functions/governance.ts +++ /dev/null @@ -1,177 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { Memory, GovernanceFilter, AuditEntry } from "../types.js"; -import { KV } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import { recordAudit, safeAudit, queryAudit } from "./audit.js"; -import { deleteAccessLog } from "./access-tracker.js"; -import { getSearchIndex, vectorIndexRemove, flushIndexSave } from "./search.js"; -import { logger } from "../logger.js"; - -export function registerGovernanceFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::governance-delete", - async (data: { memoryIds: string[]; reason?: string }) => { - if ( - !data.memoryIds || - !Array.isArray(data.memoryIds) || - data.memoryIds.length === 0 - ) { - return { success: false, error: "memoryIds array is required" }; - } - - let deleted = 0; - for (const id of data.memoryIds) { - const mem = await kv.get(KV.memories, id); - if (mem) { - await kv.delete(KV.memories, id); - await deleteAccessLog(kv, id); - getSearchIndex().remove(id); - vectorIndexRemove(id); - deleted++; - } - } - - if (deleted > 0) await flushIndexSave(); - - await recordAudit( - kv, - "delete", - "mem::governance-delete", - data.memoryIds, - { - reason: data.reason || "manual deletion", - deleted, - }, - ); - - logger.info("Governance delete", { - requested: data.memoryIds.length, - deleted, - }); - return { success: true, deleted, total: data.memoryIds.length }; - }, - ); - - sdk.registerFunction("mem::governance-bulk", - async (data: GovernanceFilter & { dryRun?: boolean }) => { - - const hasFilter = - (data.type && data.type.length > 0) || - data.dateFrom || - data.dateTo || - data.qualityBelow !== undefined; - if (!hasFilter && !data.dryRun) { - return { - success: false, - error: "At least one filter is required for non-dryRun bulk delete", - }; - } - - const memories = await kv.list(KV.memories); - let candidates = memories; - - if (data.type && data.type.length > 0) { - candidates = candidates.filter((m) => data.type!.includes(m.type)); - } - if (data.dateFrom) { - const from = new Date(data.dateFrom).getTime(); - if (Number.isNaN(from)) { - return { success: false, error: "Invalid dateFrom format" }; - } - candidates = candidates.filter( - (m) => new Date(m.createdAt).getTime() >= from, - ); - } - if (data.dateTo) { - const to = new Date(data.dateTo).getTime(); - if (Number.isNaN(to)) { - return { success: false, error: "Invalid dateTo format" }; - } - candidates = candidates.filter( - (m) => new Date(m.createdAt).getTime() <= to, - ); - } - if (data.qualityBelow !== undefined) { - candidates = candidates.filter((m) => m.strength < data.qualityBelow!); - } - - if (data.dryRun) { - return { - success: true, - dryRun: true, - wouldDelete: candidates.length, - ids: candidates.map((m) => m.id), - }; - } - - const BATCH_SIZE = 50; - const successfulIds: string[] = []; - const failures: Array<{ id: string; error: string }> = []; - for (let i = 0; i < candidates.length; i += BATCH_SIZE) { - const batch = candidates.slice(i, i + BATCH_SIZE); - const results = await Promise.allSettled( - batch.map(async (mem) => { - await kv.delete(KV.memories, mem.id); - await deleteAccessLog(kv, mem.id); - getSearchIndex().remove(mem.id); - vectorIndexRemove(mem.id); - }), - ); - results.forEach((result, j) => { - const mem = batch[j]; - if (result.status === "fulfilled") { - successfulIds.push(mem.id); - } else { - logger.warn("Governance bulk delete failed", { - memoryId: mem.id, - error: - result.reason instanceof Error - ? result.reason.message - : String(result.reason), - }); - failures.push({ - id: mem.id, - error: "delete_failed", - }); - } - }); - } - - if (successfulIds.length > 0) await flushIndexSave(); - - await safeAudit( - kv, - "delete", - "mem::governance-bulk", - successfulIds, - { - filter: data, - deleted: successfulIds.length, - failed: failures.length, - failures: failures.length > 0 ? failures : undefined, - }, - ); - - logger.info("Governance bulk delete", { - deleted: successfulIds.length, - failed: failures.length, - }); - return { - success: failures.length === 0, - deleted: successfulIds.length, - failed: failures.length, - failures: failures.length > 0 ? failures : undefined, - }; - }, - ); - - sdk.registerFunction("mem::audit-query", - async (data?: { - operation?: AuditEntry["operation"]; - dateFrom?: string; - dateTo?: string; - limit?: number; - }) => { - return queryAudit(kv, data); - }, - ); -} diff --git a/src/functions/graph-retrieval.ts b/src/functions/graph-retrieval.ts deleted file mode 100644 index 10fd51a7..00000000 --- a/src/functions/graph-retrieval.ts +++ /dev/null @@ -1,362 +0,0 @@ -import type { - GraphNode, - GraphEdge, -} from "../types.js"; -import { KV } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; - -export interface GraphRetrievalResult { - obsId: string; - sessionId: string; - score: number; - graphContext: string; - pathLength: number; -} - -function buildGraphContext( - path: Array<{ node: GraphNode; edge?: GraphEdge }>, -): string { - const parts: string[] = []; - for (const step of path) { - const props = Object.entries(step.node.properties) - .slice(0, 3) - .map(([k, v]) => `${k}=${v}`) - .join(", "); - let line = `[${step.node.type}] ${step.node.name}`; - if (props) line += ` (${props})`; - if (step.edge) { - line += ` --${step.edge.type}-->`; - if (step.edge.context?.reasoning) { - line += ` [${step.edge.context.reasoning}]`; - } - if (step.edge.tvalid) { - line += ` @${step.edge.tvalid}`; - } - } - parts.push(line); - } - return parts.join(" "); -} - -export class GraphRetrieval { - constructor(private kv: StateKV) {} - - async searchByEntities( - entityNames: string[], - maxDepth = 2, - maxResults = 20, - ): Promise { - const allNodes = (await this.kv.list(KV.graphNodes)).filter((n) => !n.stale); - const allEdges = (await this.kv.list(KV.graphEdges)).filter((e) => !e.stale); - - const matchingNodes = allNodes.filter((n) => { - const nameLower = n.name.toLowerCase(); - return entityNames.some( - (e) => - nameLower.includes(e.toLowerCase()) || - e.toLowerCase().includes(nameLower), - ); - }); - - if (matchingNodes.length === 0) return []; - - const results: GraphRetrievalResult[] = []; - const visitedObs = new Set(); - - for (const startNode of matchingNodes) { - const paths = this.dijkstraTraversal( - startNode, - allNodes, - allEdges, - maxDepth, - ); - - for (const path of paths) { - const lastNode = path[path.length - 1].node; - for (const obsId of lastNode.sourceObservationIds) { - if (visitedObs.has(obsId)) continue; - visitedObs.add(obsId); - - const pathLength = path.length; - const edgeWeights = path - .filter((s) => s.edge) - .map((s) => s.edge!.weight); - const avgWeight = - edgeWeights.length > 0 - ? edgeWeights.reduce((a, b) => a + b, 0) / edgeWeights.length - : 0.5; - const score = avgWeight * (1 / pathLength); - - results.push({ - obsId, - sessionId: "", - score, - graphContext: buildGraphContext(path), - pathLength, - }); - } - } - - for (const obsId of startNode.sourceObservationIds) { - if (visitedObs.has(obsId)) continue; - visitedObs.add(obsId); - results.push({ - obsId, - sessionId: "", - score: 1.0, - graphContext: `[${startNode.type}] ${startNode.name}`, - pathLength: 0, - }); - } - } - - results.sort((a, b) => b.score - a.score); - return results.slice(0, maxResults); - } - - async expandFromChunks( - obsIds: string[], - maxDepth = 1, - maxResults = 10, - ): Promise { - const allNodes = (await this.kv.list(KV.graphNodes)).filter((n) => !n.stale); - const allEdges = (await this.kv.list(KV.graphEdges)).filter((e) => !e.stale); - - const linkedNodes = allNodes.filter((n) => - n.sourceObservationIds.some((id) => obsIds.includes(id)), - ); - - const results: GraphRetrievalResult[] = []; - const visitedObs = new Set(obsIds); - - for (const node of linkedNodes) { - const paths = this.dijkstraTraversal(node, allNodes, allEdges, maxDepth); - for (const path of paths) { - const lastNode = path[path.length - 1].node; - for (const obsId of lastNode.sourceObservationIds) { - if (visitedObs.has(obsId)) continue; - visitedObs.add(obsId); - - const pathLength = path.length; - const score = 0.5 * (1 / (pathLength + 1)); - - results.push({ - obsId, - sessionId: "", - score, - graphContext: buildGraphContext(path), - pathLength, - }); - } - } - } - - results.sort((a, b) => b.score - a.score); - return results.slice(0, maxResults); - } - - async temporalQuery( - entityName: string, - asOf?: string, - ): Promise<{ - entity: GraphNode | null; - currentState: GraphEdge[]; - history: GraphEdge[]; - }> { - const allNodes = (await this.kv.list(KV.graphNodes)).filter((n) => !n.stale); - const allEdges = (await this.kv.list(KV.graphEdges)).filter((e) => !e.stale); - - const entity = allNodes.find( - (n) => n.name.toLowerCase() === entityName.toLowerCase(), - ); - if (!entity) return { entity: null, currentState: [], history: [] }; - - const relatedEdges = allEdges.filter( - (e) => e.sourceNodeId === entity.id || e.targetNodeId === entity.id, - ); - - if (!asOf) { - const latestEdges = this.getLatestEdges(relatedEdges); - const historicalEdges = relatedEdges.filter( - (e) => !latestEdges.some((le) => le.id === e.id), - ); - return { entity, currentState: latestEdges, history: historicalEdges }; - } - - const asOfDate = new Date(asOf).getTime(); - const validEdges = relatedEdges.filter((e) => { - const commitDate = new Date(e.tcommit || e.createdAt).getTime(); - if (commitDate > asOfDate) return false; - if (e.tvalid) { - const validDate = new Date(e.tvalid).getTime(); - if (validDate > asOfDate) return false; - } - if (e.tvalidEnd) { - const endDate = new Date(e.tvalidEnd).getTime(); - if (endDate < asOfDate) return false; - } - return true; - }); - - return { - entity, - currentState: this.getLatestEdges(validEdges), - history: validEdges, - }; - } - - private getLatestEdges(edges: GraphEdge[]): GraphEdge[] { - const byKey = new Map(); - for (const e of edges) { - const key = `${e.sourceNodeId}|${e.targetNodeId}|${e.type}`; - if (!byKey.has(key)) byKey.set(key, []); - byKey.get(key)!.push(e); - } - - const latest: GraphEdge[] = []; - for (const group of byKey.values()) { - if (group.length === 0) continue; - group.sort( - (a, b) => - new Date(b.tcommit || b.createdAt).getTime() - - new Date(a.tcommit || a.createdAt).getTime(), - ); - const newest = group.find((e) => e.isLatest !== false) || group[0]; - latest.push(newest); - } - return latest; - } - - // Weighted shortest-path traversal (#328). Replaces the prior BFS, - // which fell back to edge-count order and ignored the 0.1-1.0 weight - // attached to every graph edge. Dijkstra over `cost = 1/weight` - // (cheaper edges = stronger relationships) returns the - // highest-weighted path to each reachable node within maxDepth. Also - // tightens the perf profile: - // - Adjacency built once in O(V+E) (previous BFS re-filtered - // allEdges per visited node, O(V·E) overall). - // - Min-heap dequeue is O(log V) per pop (previous queue.shift() - // was O(n) — the dominant cost on graphs above ~200 nodes per - // the contributor's benchmark in #328). - private dijkstraTraversal( - startNode: GraphNode, - allNodes: GraphNode[], - allEdges: GraphEdge[], - maxDepth: number, - ): Array> { - const nodeIndex = new Map(); - for (const n of allNodes) nodeIndex.set(n.id, n); - - const adjacency = new Map>(); - for (const edge of allEdges) { - const a = edge.sourceNodeId; - const b = edge.targetNodeId; - if (!adjacency.has(a)) adjacency.set(a, []); - if (!adjacency.has(b)) adjacency.set(b, []); - adjacency.get(a)!.push({ neighborId: b, edge }); - adjacency.get(b)!.push({ neighborId: a, edge }); - } - - const dist = new Map(); - const pathTo = new Map>(); - dist.set(startNode.id, 0); - pathTo.set(startNode.id, [{ node: startNode }]); - - const heap = new MinHeap<{ nodeId: string; depth: number; cost: number }>( - (a, b) => a.cost - b.cost, - ); - heap.push({ nodeId: startNode.id, depth: 0, cost: 0 }); - - while (heap.size() > 0) { - const { nodeId, depth, cost } = heap.pop()!; - // Skip stale heap entries (cost beaten by a later push). - if (cost > (dist.get(nodeId) ?? Infinity)) continue; - if (depth >= maxDepth) continue; - - const neighbors = adjacency.get(nodeId) ?? []; - for (const { neighborId, edge } of neighbors) { - const nextNode = nodeIndex.get(neighborId); - if (!nextNode) continue; - // Clamp weight to avoid division-by-zero on malformed edges; - // 0.01 is below the documented 0.1 floor. - const edgeCost = 1 / Math.max(edge.weight, 0.01); - const newCost = cost + edgeCost; - if (newCost < (dist.get(neighborId) ?? Infinity)) { - dist.set(neighborId, newCost); - pathTo.set(neighborId, [ - ...pathTo.get(nodeId)!, - { node: nextNode, edge }, - ]); - heap.push({ nodeId: neighborId, depth: depth + 1, cost: newCost }); - } - } - } - - // Drop the startNode's own entry before returning: callers - // (searchByEntities, expandFromChunks) score start-node - // observations via a dedicated fallback loop with score=1.0. If - // we leave it in here, the start-path (length 1, no edges) goes - // through the generic path-scoring loop first — pathLength=1 + - // empty edgeWeights makes avgWeight fall to 0.5, the obs get - // marked visited, and the score=1.0 fallback becomes dead code. - pathTo.delete(startNode.id); - return Array.from(pathTo.values()); - } -} - -// Minimal binary min-heap. Pulled inline so graph-retrieval doesn't -// take a new dependency for the perf-critical inner loop of #328. -// Comparator returns negative when `a` should pop before `b`. -class MinHeap { - private heap: T[] = []; - - constructor(private compare: (a: T, b: T) => number) {} - - size(): number { - return this.heap.length; - } - - push(value: T): void { - this.heap.push(value); - this.bubbleUp(this.heap.length - 1); - } - - pop(): T | undefined { - if (this.heap.length === 0) return undefined; - const top = this.heap[0]; - const last = this.heap.pop()!; - if (this.heap.length > 0) { - this.heap[0] = last; - this.sinkDown(0); - } - return top; - } - - private bubbleUp(i: number): void { - while (i > 0) { - const parent = (i - 1) >> 1; - if (this.compare(this.heap[i], this.heap[parent]) < 0) { - [this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]]; - i = parent; - } else break; - } - } - - private sinkDown(i: number): void { - const n = this.heap.length; - while (true) { - const left = 2 * i + 1; - const right = 2 * i + 2; - let smallest = i; - if (left < n && this.compare(this.heap[left], this.heap[smallest]) < 0) { - smallest = left; - } - if (right < n && this.compare(this.heap[right], this.heap[smallest]) < 0) { - smallest = right; - } - if (smallest === i) break; - [this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]]; - i = smallest; - } - } -} diff --git a/src/functions/graph.ts b/src/functions/graph.ts deleted file mode 100644 index 09c23af0..00000000 --- a/src/functions/graph.ts +++ /dev/null @@ -1,299 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - GraphNode, - GraphEdge, - GraphQueryResult, - CompressedObservation, - MemoryProvider, -} from "../types.js"; -import { KV, generateId } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import { - GRAPH_EXTRACTION_SYSTEM, - buildGraphExtractionPrompt, -} from "../prompts/graph-extraction.js"; -import { recordAudit } from "./audit.js"; -import { logger } from "../logger.js"; - -// Parse all key="value" pairs from a tag's attribute string, in any -// order. The previous parser hard-coded attribute order -// (type before name on , type/source/target/weight on -// ) and silently dropped nodes/edges when the upstream -// LLM emitted attributes in a different order — Codex in particular -// likes to lead with `name=` (#635). -function parseAttrs(raw: string): Record { - const attrs: Record = {}; - const attrRegex = /([A-Za-z_][\w:-]*)="([^"]*)"/g; - let m; - while ((m = attrRegex.exec(raw)) !== null) { - attrs[m[1]] = m[2]; - } - return attrs; -} - -function parseGraphXml( - xml: string, - observationIds: string[], -): { - nodes: GraphNode[]; - edges: GraphEdge[]; -} { - const nodes: GraphNode[] = []; - const edges: GraphEdge[] = []; - const now = new Date().toISOString(); - - // Two passes because can be self-closing or have a body - // ( children). The self-closing form needs `[^>]*[^/]` on - // the attr group so the trailing `/` isn't swallowed into the match - // (root cause of #494). The explicit-close form picks up the - // property block. - const entitySelfClose = /]*?)\/>/g; - const entityWithBody = /]*[^/])>([\s\S]*?)<\/entity>/g; - - const addEntity = (rawAttrs: string, propsBlock = ""): void => { - const attrs = parseAttrs(rawAttrs); - const type = attrs["type"] as GraphNode["type"] | undefined; - const name = attrs["name"]; - if (!type || !name) return; - const properties: Record = {}; - const propRegex = /([^<]*)<\/property>/g; - let propMatch; - while ((propMatch = propRegex.exec(propsBlock)) !== null) { - properties[propMatch[1]] = propMatch[2]; - } - nodes.push({ - id: generateId("gn"), - type, - name, - properties, - sourceObservationIds: observationIds, - createdAt: now, - }); - }; - - let match; - while ((match = entitySelfClose.exec(xml)) !== null) { - addEntity(match[1]); - } - while ((match = entityWithBody.exec(xml)) !== null) { - addEntity(match[1], match[2]); - } - - const relRegex = /]*?)\/>/g; - while ((match = relRegex.exec(xml)) !== null) { - const attrs = parseAttrs(match[1]); - const type = attrs["type"] as GraphEdge["type"] | undefined; - const sourceName = attrs["source"]; - const targetName = attrs["target"]; - if (!type || !sourceName || !targetName) continue; - const parsedWeight = parseFloat(attrs["weight"] ?? ""); - const weight = Number.isFinite(parsedWeight) ? parsedWeight : 0.5; - - const sourceNode = nodes.find((n) => n.name === sourceName); - const targetNode = nodes.find((n) => n.name === targetName); - if (!sourceNode || !targetNode) continue; - edges.push({ - id: generateId("ge"), - type, - sourceNodeId: sourceNode.id, - targetNodeId: targetNode.id, - weight: Math.max(0, Math.min(1, weight)), - sourceObservationIds: observationIds, - createdAt: now, - }); - } - - return { nodes, edges }; -} - -export function registerGraphFunction( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, -): void { - sdk.registerFunction("mem::graph-extract", - async (data: { observations: CompressedObservation[] }) => { - if (!data.observations || data.observations.length === 0) { - return { success: false, error: "No observations provided" }; - } - - const prompt = buildGraphExtractionPrompt( - data.observations.map((o) => ({ - title: o.title, - narrative: o.narrative, - concepts: o.concepts, - files: o.files, - type: o.type, - })), - ); - - try { - const response = await provider.compress( - GRAPH_EXTRACTION_SYSTEM, - prompt, - ); - - const obsIds = data.observations.map((o) => o.id); - const { nodes, edges } = parseGraphXml(response, obsIds); - - const existingNodes = await kv.list(KV.graphNodes); - const existingEdges = await kv.list(KV.graphEdges); - - for (const node of nodes) { - const existing = existingNodes.find( - (n) => n.name === node.name && n.type === node.type, - ); - if (existing) { - const merged = { - ...existing, - sourceObservationIds: [ - ...new Set([...existing.sourceObservationIds, ...obsIds]), - ], - properties: { ...existing.properties, ...node.properties }, - }; - await kv.set(KV.graphNodes, existing.id, merged); - const idx = existingNodes.findIndex((n) => n.id === existing.id); - if (idx !== -1) existingNodes[idx] = merged; - } else { - await kv.set(KV.graphNodes, node.id, node); - existingNodes.push(node); - } - } - - for (const edge of edges) { - const edgeKey = `${edge.sourceNodeId}|${edge.targetNodeId}|${edge.type}`; - const existingEdge = existingEdges.find( - (e) => `${e.sourceNodeId}|${e.targetNodeId}|${e.type}` === edgeKey, - ); - if (existingEdge) { - existingEdge.sourceObservationIds = [ - ...new Set([...existingEdge.sourceObservationIds, ...obsIds]), - ]; - await kv.set(KV.graphEdges, existingEdge.id, existingEdge); - } else { - await kv.set(KV.graphEdges, edge.id, edge); - existingEdges.push(edge); - } - } - - await recordAudit(kv, "observe", "mem::graph-extract", obsIds, { - nodesExtracted: nodes.length, - edgesExtracted: edges.length, - }); - - logger.info("Graph extraction complete", { - nodes: nodes.length, - edges: edges.length, - }); - return { - success: true, - nodesAdded: nodes.length, - edgesAdded: edges.length, - }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Graph extraction failed", { error: msg }); - return { success: false, error: msg }; - } - }, - ); - - sdk.registerFunction("mem::graph-query", - async (data: { - startNodeId?: string; - nodeType?: string; - maxDepth?: number; - query?: string; - }): Promise => { - const allNodes = (await kv.list(KV.graphNodes)).filter((n) => !n.stale); - const allEdges = (await kv.list(KV.graphEdges)).filter((e) => !e.stale); - const maxDepth = Math.min(data.maxDepth || 3, 5); - - if (data.query) { - const lower = data.query.toLowerCase(); - const matchingNodes = allNodes.filter( - (n) => - n.name.toLowerCase().includes(lower) || - Object.values(n.properties).some( - (v) => typeof v === "string" && v.toLowerCase().includes(lower), - ), - ); - const nodeIds = new Set(matchingNodes.map((n) => n.id)); - const relatedEdges = allEdges.filter( - (e) => nodeIds.has(e.sourceNodeId) || nodeIds.has(e.targetNodeId), - ); - return { nodes: matchingNodes, edges: relatedEdges, depth: 0 }; - } - - if (data.startNodeId) { - const visited = new Set(); - const visitedEdges = new Set(); - const resultNodes: GraphNode[] = []; - const resultEdges: GraphEdge[] = []; - const queue: Array<{ nodeId: string; depth: number }> = [ - { nodeId: data.startNodeId, depth: 0 }, - ]; - - while (queue.length > 0) { - const { nodeId, depth } = queue.shift()!; - if (visited.has(nodeId) || depth > maxDepth) continue; - visited.add(nodeId); - - const node = allNodes.find((n) => n.id === nodeId); - if (node) { - if (!data.nodeType || node.type === data.nodeType) { - resultNodes.push(node); - } - } - - const neighborEdges = allEdges.filter( - (e) => e.sourceNodeId === nodeId || e.targetNodeId === nodeId, - ); - for (const edge of neighborEdges) { - if (!visitedEdges.has(edge.id)) { - visitedEdges.add(edge.id); - resultEdges.push(edge); - } - const nextId = - edge.sourceNodeId === nodeId - ? edge.targetNodeId - : edge.sourceNodeId; - if (!visited.has(nextId)) { - queue.push({ nodeId: nextId, depth: depth + 1 }); - } - } - } - - return { nodes: resultNodes, edges: resultEdges, depth: maxDepth }; - } - - let filtered = allNodes; - if (data.nodeType) { - filtered = allNodes.filter((n) => n.type === data.nodeType); - } - return { nodes: filtered, edges: allEdges, depth: 0 }; - }, - ); - - sdk.registerFunction("mem::graph-stats", async () => { - const nodes = await kv.list(KV.graphNodes); - const edges = await kv.list(KV.graphEdges); - - const nodesByType: Record = {}; - for (const n of nodes) { - nodesByType[n.type] = (nodesByType[n.type] || 0) + 1; - } - - const edgesByType: Record = {}; - for (const e of edges) { - edgesByType[e.type] = (edgesByType[e.type] || 0) + 1; - } - - return { - totalNodes: nodes.length, - totalEdges: edges.length, - nodesByType, - edgesByType, - }; - }); -} diff --git a/src/functions/image-quota-cleanup.ts b/src/functions/image-quota-cleanup.ts deleted file mode 100644 index bb37a390..00000000 --- a/src/functions/image-quota-cleanup.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { TriggerAction, type ISdk } from "iii-sdk"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { readdir, stat } from "node:fs/promises"; -import { join } from "node:path"; -import { IMAGES_DIR, getMaxBytes, deleteImage } from "../utils/image-store.js"; -import { getImageRefCount } from "./image-refs.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import { logger } from "../logger.js"; - -const GRACE_PERIOD_MS = 30_000; - -export function registerImageQuotaCleanup(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction( - "mem::image-quota-cleanup", - async () => { - const now = Date.now(); - - return withKeyedLock("system:cleanupLock", async () => { - let totalSize = 0; - const fileStats: Array<{ filePath: string; size: number; mtimeMs: number }> = []; - - try { - const files = await readdir(IMAGES_DIR); - for (const file of files) { - if (file.startsWith(".")) continue; - const filePath = join(IMAGES_DIR, file); - const s = await stat(filePath); - if (s.isFile()) { - fileStats.push({ filePath, size: s.size, mtimeMs: s.mtimeMs }); - totalSize += s.size; - } - } - } catch { - return { success: true, evicted: 0, freedBytes: 0 }; - } - - const limit = getMaxBytes(); - if (totalSize <= limit) { - return { success: true, evicted: 0, freedBytes: 0, underQuota: true }; - } - - fileStats.sort((a, b) => a.mtimeMs - b.mtimeMs); - - let totalToFree = totalSize - limit; - let evicted = 0; - let freedBytes = 0; - - for (const f of fileStats) { - if (totalToFree <= 0) break; - - if (now - f.mtimeMs < GRACE_PERIOD_MS) { - continue; - } - - await withKeyedLock(`imgRef:${f.filePath}`, async () => { - let refCount: number; - try { - refCount = await getImageRefCount(kv, f.filePath); - } catch (err) { - // Fail-closed: if we cannot determine refCount we must NOT - // delete the image. Previously we let refCount fall through - // to the default 0 and evicted, which risks deleting - // still-referenced images on transient KV errors. - logger.error("Failed to read refCount; skipping eviction", { - filePath: f.filePath, - error: err instanceof Error ? err.message : String(err), - }); - return; - } - - if (refCount > 0) { - return; - } - - const { deletedBytes } = await deleteImage(f.filePath); - if (deletedBytes > 0) { - void sdk.trigger({ function_id: "mem::disk-size-delta", payload: { deltaBytes: -deletedBytes }, action: TriggerAction.Void() }).catch(() => {}); - totalToFree -= deletedBytes; - freedBytes += deletedBytes; - evicted++; - } - }); - } - - if (evicted > 0) { - const freedMb = (freedBytes / (1024 * 1024)).toFixed(1); - logger.info("Image quota cleanup complete", { evicted, freedMb }); - } - - return { success: true, evicted, freedBytes }; - }); - }, - ); -} diff --git a/src/functions/image-refs.ts b/src/functions/image-refs.ts deleted file mode 100644 index b24c1775..00000000 --- a/src/functions/image-refs.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { TriggerAction, type ISdk } from "iii-sdk"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { deleteImage, touchImage } from "../utils/image-store.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; - -export async function getImageRefCount(kv: StateKV, filePath: string): Promise { - const count = await kv.get(KV.imageRefs, filePath); - return count ? Number(count) : 0; -} - -export async function incrementImageRef(kv: StateKV, filePath: string): Promise { - return withKeyedLock(`imgRef:${filePath}`, async () => { - const current = await getImageRefCount(kv, filePath); - await kv.set(KV.imageRefs, filePath, current + 1); - await touchImage(filePath); - }); -} - -export async function decrementImageRef(kv: StateKV, sdk: ISdk, filePath: string): Promise { - return withKeyedLock(`imgRef:${filePath}`, async () => { - const current = await getImageRefCount(kv, filePath); - if (current <= 1) { - await kv.delete(KV.imageEmbeddings, filePath); - await kv.delete(KV.imageRefs, filePath); - const { deletedBytes } = await deleteImage(filePath); - if (deletedBytes > 0) { - void sdk.trigger({ function_id: "mem::disk-size-delta", payload: { deltaBytes: -deletedBytes }, action: TriggerAction.Void() }).catch(() => {}); - } - } else { - await kv.set(KV.imageRefs, filePath, current - 1); - } - }); -} diff --git a/src/functions/inbox-deliver.ts b/src/functions/inbox-deliver.ts deleted file mode 100644 index e6b58d25..00000000 --- a/src/functions/inbox-deliver.ts +++ /dev/null @@ -1,120 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV } from "../state/schema.js"; -import type { DeliveryRecord, InboxItem } from "../types.js"; -import { getLarkConfig, isLarkDeliveryEnabled, isLarkReplyLoopEnabled } from "../config.js"; -import { safeAudit } from "./audit.js"; -import { deliverViaLark as defaultDeliver, type LarkDeliverFn } from "./lark-adapter.js"; -import { setPendingReplyTarget } from "./lark-reply-consumer.js"; -import { logger } from "../logger.js"; - -// Line D — delivery primitive. Fired fire-and-forget after an inbox item is -// persisted (STEP-D3 wires the trigger). Pure backend, default OFF: -// - config gate: if delivery disabled or no lark config → record `skipped`, -// never call the adapter. (Inbox behaves exactly as Line C.) -// - dedup: an item is pushed at most once. If a `sent` ledger row already -// exists for item.id, skip. Failed rows may be retried (attempts++). -// - the actual Feishu send is delegated to the lark-cli adapter (D2). D1 -// ships the adapter as a stub so this logic is testable in isolation. -// -// Delivery state lives in its own KV scope (mem:delivery) keyed by item.id — -// the inbox stays the source of truth and is never mutated here. - -export function registerInboxDeliverFunction( - sdk: ISdk, - kv: StateKV, - deliver: LarkDeliverFn = defaultDeliver, -): void { - sdk.registerFunction( - "mem::inbox-deliver", - async (data: { item: InboxItem }) => { - const item = data?.item; - if (!item || !item.id) { - return { success: false, error: "item with id is required" }; - } - - // Config gate — disabled / unconfigured → clean skip, no adapter call. - const config = isLarkDeliveryEnabled() ? getLarkConfig() : null; - if (!config) { - const record = await writeRecord(kv, item.id, { - status: "skipped", - attempts: 0, - }); - return { success: true, skipped: true, record }; - } - - // Dedup — already delivered? skip. (Failed rows fall through to retry.) - const prior = await kv.get(KV.delivery, item.id); - if (prior && prior.status === "sent") { - return { success: true, skipped: true, record: prior }; - } - - const attempts = (prior?.attempts ?? 0) + 1; - const outcome = await deliver(item, config); - - if (outcome.ok) { - const record = await writeRecord(kv, item.id, { - status: "sent", - messageId: outcome.messageId, - urgent: outcome.urgent, - attempts, - deliveredAt: new Date().toISOString(), - }); - await safeAudit(kv, "inbox_delivered", "mem::inbox-deliver", [item.id], { - kind: item.kind, - urgent: !!outcome.urgent, - }); - // Reply-loop mapping (method A): a successfully delivered question - // becomes the current pending-reply target so the user's next Feishu - // reply answers it. Briefings need no reply, so they never set it. - // Guarded by the reply-loop switch — pointer is meaningless without - // the D5b consumer running. - if (item.kind === "question" && isLarkReplyLoopEnabled()) { - try { - await setPendingReplyTarget(kv, item.id); - } catch (err) { - logger.warn("failed to set pending reply target", { - itemId: item.id, - error: err instanceof Error ? err.message : String(err), - }); - } - } - return { success: true, record }; - } - - const record = await writeRecord(kv, item.id, { - status: "failed", - error: outcome.error, - attempts, - }); - await safeAudit(kv, "delivery_failed", "mem::inbox-deliver", [item.id], { - kind: item.kind, - error: outcome.error, - attempts, - }); - return { success: false, error: outcome.error, record }; - }, - ); -} - -async function writeRecord( - kv: StateKV, - id: string, - fields: Omit & - Partial>, -): Promise { - const existing = await kv.get(KV.delivery, id); - const record: DeliveryRecord = { - id, - channel: "lark", - createdAt: existing?.createdAt ?? fields.createdAt ?? new Date().toISOString(), - status: fields.status, - messageId: fields.messageId, - urgent: fields.urgent, - error: fields.error, - attempts: fields.attempts, - deliveredAt: fields.deliveredAt, - }; - await kv.set(KV.delivery, id, record); - return record; -} diff --git a/src/functions/inbox.ts b/src/functions/inbox.ts deleted file mode 100644 index 0ffb7293..00000000 --- a/src/functions/inbox.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { TriggerAction, type ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import type { InboxItem } from "../types.js"; -import { recordAudit } from "./audit.js"; -import { isLarkDeliveryEnabled } from "../config.js"; -import { logger } from "../logger.js"; - -// Fire-and-forget cross-device delivery (Line D / STEP-D3). Called after an -// inbox item is persisted+audited. Guarded by the delivery config gate so the -// inbox behaves exactly as Line C when delivery is OFF (the default). The -// dispatch is best-effort: any failure (sync throw OR async rejection) is -// logged and swallowed so it can never affect the inbox write's return value. -// (mem::inbox-deliver applies its own config gate + dedup; this guard just -// avoids dispatching when clearly off.) -// -// Uses sdk.trigger({ ..., action: TriggerAction.Void() }) — the iii-sdk -// fire-and-forget form (the old sdk.triggerVoid was removed). We do NOT await: -// delivery runs out-of-band and must not delay or affect the inbox write. -function dispatchDelivery(sdk: ISdk, item: InboxItem): void { - if (!isLarkDeliveryEnabled()) return; - try { - void sdk - .trigger({ - function_id: "mem::inbox-deliver", - payload: { item }, - action: TriggerAction.Void(), - }) - .catch((err: unknown) => { - logger.warn("inbox delivery dispatch failed (async)", { - itemId: item.id, - error: err instanceof Error ? err.message : String(err), - }); - }); - } catch (err) { - logger.warn("inbox delivery dispatch failed", { - itemId: item.id, - error: err instanceof Error ? err.message : String(err), - }); - } -} - - -// Line C: Agent→user async inbox. -// Unlike signals (agent↔agent, requires agentId), inbox items are always -// addressed to the single human user, so list/answer need no agentId. -// `kind` distinguishes a question (needs a reply) from a briefing (ack-only -// proactive summary the Agent pushes after organizing todos/progress). -export function registerInboxFunction(sdk: ISdk, kv: StateKV): void { - // Agent asks the user a question (needs a reply). - sdk.registerFunction("mem::inbox-ask", async (data: { - body: string; - fromAgent?: string; - project?: string; - priority?: InboxItem["priority"]; - sourceObservationIds?: string[]; - sourceSessionId?: string; - expiresInMs?: number; - }) => { - if (!data.body?.trim()) { - return { success: false, error: "non-empty body is required" }; - } - const item = buildItem("question", data); - await kv.set(KV.inbox, item.id, item); - await recordAudit(kv, "inbox_ask", "mem::inbox-ask", [item.id], { - action: "create", - kind: "question", - fromAgent: data.fromAgent, - }); - dispatchDelivery(sdk, item); - return { success: true, item }; - }); - - // Agent pushes a briefing (proactive summary, ack-only). - sdk.registerFunction("mem::inbox-notify", async (data: { - body: string; - fromAgent?: string; - project?: string; - priority?: InboxItem["priority"]; - sourceObservationIds?: string[]; - sourceSessionId?: string; - expiresInMs?: number; - }) => { - if (!data.body?.trim()) { - return { success: false, error: "non-empty body is required" }; - } - const item = buildItem("briefing", data); - await kv.set(KV.inbox, item.id, item); - await recordAudit(kv, "inbox_notify", "mem::inbox-notify", [item.id], { - action: "create", - kind: "briefing", - fromAgent: data.fromAgent, - }); - dispatchDelivery(sdk, item); - return { success: true, item }; - }); - - // List inbox items. No agentId required (single-user inbox). - sdk.registerFunction("mem::inbox-list", async (data: { - status?: InboxItem["status"]; - kind?: InboxItem["kind"]; - limit?: number; - } = {}) => { - let items = await kv.list(KV.inbox); - const now = Date.now(); - items = items.filter( - (i) => !(i.expiresAt && new Date(i.expiresAt).getTime() <= now), - ); - if (data.status) items = items.filter((i) => i.status === data.status); - if (data.kind) items = items.filter((i) => i.kind === data.kind); - // Newest first; priority intentionally not used for ordering this round. - items.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - const limit = data.limit || 50; - return { success: true, items: items.slice(0, limit) }; - }); - - // User answers a question (or acks a briefing with empty answer). - sdk.registerFunction("mem::inbox-answer", async (data: { - id: string; - answer?: string; - }) => { - if (!data.id) return { success: false, error: "id is required" }; - const item = await kv.get(KV.inbox, data.id); - if (!item) return { success: false, error: "inbox item not found" }; - item.status = "answered"; - item.answer = data.answer?.trim() || undefined; - item.answeredAt = new Date().toISOString(); - await kv.set(KV.inbox, item.id, item); - await recordAudit(kv, "inbox_answer", "mem::inbox-answer", [item.id], { - action: "answer", - hasAnswer: !!item.answer, - }); - return { success: true, item }; - }); - - // User dismisses an item (e.g. when converting it to a regular todo). - sdk.registerFunction("mem::inbox-dismiss", async (data: { id: string }) => { - if (!data.id) return { success: false, error: "id is required" }; - const item = await kv.get(KV.inbox, data.id); - if (!item) return { success: false, error: "inbox item not found" }; - item.status = "dismissed"; - await kv.set(KV.inbox, item.id, item); - await recordAudit(kv, "inbox_dismiss", "mem::inbox-dismiss", [item.id], { - action: "dismiss", - }); - return { success: true, item }; - }); -} - -function buildItem( - kind: InboxItem["kind"], - data: { - body: string; - fromAgent?: string; - project?: string; - priority?: InboxItem["priority"]; - sourceObservationIds?: string[]; - sourceSessionId?: string; - expiresInMs?: number; - }, -): InboxItem { - const now = new Date(); - return { - id: generateId("inbox"), - kind, - body: data.body.trim(), - status: "awaiting", - priority: data.priority, - fromAgent: data.fromAgent, - project: data.project, - sourceObservationIds: data.sourceObservationIds, - sourceSessionId: data.sourceSessionId, - createdAt: now.toISOString(), - expiresAt: data.expiresInMs - ? new Date(now.getTime() + data.expiresInMs).toISOString() - : undefined, - }; -} diff --git a/src/functions/lark-adapter.ts b/src/functions/lark-adapter.ts deleted file mode 100644 index f93355e9..00000000 --- a/src/functions/lark-adapter.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { execFile } from "node:child_process"; -import type { InboxItem } from "../types.js"; -import type { LarkConfig } from "../config.js"; -import { logger } from "../logger.js"; - -// Line D / STEP-D2 — lark-cli adapter. Turns an InboxItem into a Feishu -// message via `execFile("lark-cli", ...)` (no shell, argv array — safe per -// lark-shared rules). Never throws; any failure → { ok:false, error }. -// -// Verified lark-cli behaviors this is built on (dry-run + real send): -// - `--content -` (stdin) is REJECTED ("--content is not valid JSON: -"). -// So message body goes as an argv arg: --content JSON.stringify(content). -// - `--markdown X` is wrapped by the CLI as msg_type=post (NOT text). -// - `--msg-type interactive --content ''` works for cards. -// - urgent_app DOES accept `--data -` (stdin); we use stdin there. -// - a local proxy (HTTPS_PROXY=127.0.0.1:7890) triggers a lark-cli warning; -// we set LARK_CLI_NO_PROXY=1 + strip proxy env so creds/messages don't -// traverse the local proxy. - -export interface DeliveryOutcome { - ok: boolean; - messageId?: string; - urgent?: boolean; - error?: string; -} - -export type LarkDeliverFn = ( - item: InboxItem, - config: LarkConfig, -) => Promise; - -const VIEWER_ACTIONS_URL = "http://127.0.0.1:3114/#actions"; -const CLI = "lark-cli"; -const SEND_TIMEOUT_MS = 8000; -const URGENT_TIMEOUT_MS = 6000; - -// Injectable runner so tests can mock execFile without spawning lark-cli. -export interface RunResult { - code: number; - stdout: string; - stderr: string; -} -export type CliRunner = ( - args: string[], - input?: string, -) => Promise; - -const realRunner: CliRunner = (args, input) => - new Promise((resolveRun) => { - const env = { ...process.env, LARK_CLI_NO_PROXY: "1" }; - // Strip proxy env so the CLI's own HTTP client goes direct to Feishu. - delete env["HTTPS_PROXY"]; - delete env["HTTP_PROXY"]; - delete env["ALL_PROXY"]; - delete env["https_proxy"]; - delete env["http_proxy"]; - delete env["all_proxy"]; - const child = execFile( - CLI, - args, - { env, timeout: SEND_TIMEOUT_MS, maxBuffer: 1024 * 1024 }, - (err, stdout, stderr) => { - const code = - err && typeof (err as { code?: unknown }).code === "number" - ? ((err as { code: number }).code) - : err - ? 1 - : 0; - resolveRun({ code, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") }); - }, - ); - if (input !== undefined) { - child.stdin?.end(input); - } - }); - -// ---- message construction ---- - -function questionCard(item: InboxItem): string { - const from = item.fromAgent ? `\n\n*来自 ${item.fromAgent}*` : ""; - const card = { - config: { wide_screen_mode: true }, - header: { - template: "red", - title: { tag: "plain_text", content: "🔴 Agent 在等你回" }, - }, - elements: [ - { tag: "div", text: { tag: "lark_md", content: item.body + from } }, - { tag: "hr" }, - { - tag: "action", - actions: [ - { - tag: "button", - text: { tag: "plain_text", content: "去工作台回应 →" }, - type: "primary", - url: VIEWER_ACTIONS_URL, - }, - ], - }, - { - tag: "note", - elements: [ - { tag: "plain_text", content: "直接回复将回答这条;多条待答请去工作台" }, - ], - }, - ], - }; - return JSON.stringify(card); -} - -function briefingMarkdown(item: InboxItem): string { - const from = item.fromAgent ? `\n\n*来自 ${item.fromAgent}*` : ""; - return `**📋 Agent 整理**\n\n${item.body}${from}\n\n[看详情 →](${VIEWER_ACTIONS_URL})`; -} - -function parseMessageId(stdout: string): string | undefined { - try { - const j = JSON.parse(stdout); - return j?.data?.message_id ?? j?.message_id ?? undefined; - } catch { - return undefined; - } -} - -function errorSummary(r: RunResult): string { - // Prefer the CLI's structured error envelope; fall back to a trimmed stderr. - const raw = r.stderr || r.stdout; - try { - const j = JSON.parse(raw); - if (j?.error?.message) return String(j.error.message); - if (j?.error?.type) return String(j.error.type); - } catch { - /* not JSON */ - } - const trimmed = raw.trim().slice(0, 200); - return trimmed || `lark-cli exited with code ${r.code}`; -} - -export function makeLarkAdapter(runner: CliRunner = realRunner): LarkDeliverFn { - return async (item, config) => { - try { - const sendArgs = - item.kind === "question" - ? [ - "im", - "+messages-send", - "--as", - "bot", - "--user-id", - config.userId, - "--msg-type", - "interactive", - "--content", - questionCard(item), - "--idempotency-key", - item.id, - "--json", - ] - : [ - "im", - "+messages-send", - "--as", - "bot", - "--user-id", - config.userId, - "--markdown", - briefingMarkdown(item), - "--idempotency-key", - item.id, - "--json", - ]; - - const sent = await runner(sendArgs); - if (sent.code !== 0) { - return { ok: false, error: errorSummary(sent) }; - } - const messageId = parseMessageId(sent.stdout); - if (!messageId) { - return { ok: false, error: "send ok but no message_id in response" }; - } - - // question + urgent enabled → fire urgent_app (best-effort; failure - // degrades to "sent without urgency", not a delivery failure). - let urgent = false; - if (item.kind === "question" && config.urgentQuestion) { - urgent = await tryUrgent(runner, messageId, config.userId); - } - - return { ok: true, messageId, urgent }; - } catch (err) { - return { ok: false, error: err instanceof Error ? err.message : String(err) }; - } - }; -} - -async function tryUrgent( - runner: CliRunner, - messageId: string, - userId: string, -): Promise { - try { - const params = JSON.stringify({ message_id: messageId, user_id_type: "open_id" }); - const body = JSON.stringify({ user_id_list: [userId] }); - const r = await runner( - [ - "im", - "messages", - "urgent_app", - "--as", - "bot", - "--params", - params, - "--data", - "-", - "--json", - ], - body, // stdin (urgent_app DOES accept --data -) - ); - if (r.code !== 0) { - logger.warn("lark urgent_app failed (degrading to non-urgent)", { - messageId, - error: errorSummary(r), - }); - return false; - } - return true; - } catch (err) { - logger.warn("lark urgent_app threw (degrading to non-urgent)", { - messageId, - error: err instanceof Error ? err.message : String(err), - }); - return false; - } -} - -// Default adapter wired into mem::inbox-deliver (D1's injection point). -export const deliverViaLark: LarkDeliverFn = makeLarkAdapter(); - -// Exported for tests. -export const _internal = { - questionCard, - briefingMarkdown, - parseMessageId, - errorSummary, - URGENT_TIMEOUT_MS, -}; diff --git a/src/functions/lark-reply-consumer.ts b/src/functions/lark-reply-consumer.ts deleted file mode 100644 index c8305189..00000000 --- a/src/functions/lark-reply-consumer.ts +++ /dev/null @@ -1,251 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV } from "../state/schema.js"; -import type { InboxItem } from "../types.js"; -import { logger } from "../logger.js"; - -// Line D / STEP-D5a — Feishu reply-loop mapping kernel. -// -// A long-running `lark-cli event consume im.message.receive_v1 --as bot -// --quiet` subprocess (wired in D5b) streams the user's private replies as -// NDJSON on stdout. This module is the *testable* core: parse a line, decide -// whether it is a reply we should act on, map it to the pending question, and -// answer it — without ever spawning a process. D5b only feeds stdout lines -// into makeReplyHandler(...).handleLine and manages the subprocess lifecycle. -// -// Mapping uses "method A (single-pending pointer)": delivering a question -// records its id as the current pending-reply target; the next user reply -// answers that question and clears the pointer. briefings never set it. -// -// Pointer + event-dedup live in the existing mem:delivery KV scope under -// reserved keys (NOT InboxItem ids — those are "inbox_*"), so no new KV scope. - -// Reserved key for the single-pending-reply pointer (method A). -export const PENDING_REPLY_KEY = "__lark_pending_reply_target__"; -// Prefix for per-event dedup markers (event_id is dedup-safe per Feishu). -const EVENT_DEDUP_PREFIX = "__lark_event__"; - -interface PendingReplyTarget { - itemId: string; - setAt: string; -} - -interface ProcessedEvent { - eventId: string; - processedAt: string; -} - -// The normalized shape we care about from im.message.receive_v1. lark-cli -// --quiet emits the event payload; we read defensively since upstream may -// nest under different envelopes. -export interface ParsedReply { - eventId: string; - senderId: string; - chatType: string; // plain text (lark-cli renders message content) - content: string; -} - -// ---- NDJSON parsing ---- - -// Pull a string field that may live at the top level or under a nested -// envelope (event / event.message / event.sender). Returns "" if absent. -function pick(obj: Record, ...paths: string[][]): string { - for (const path of paths) { - let cur: unknown = obj; - for (const key of path) { - if (cur && typeof cur === "object" && key in (cur as object)) { - cur = (cur as Record)[key]; - } else { - cur = undefined; - break; - } - } - if (typeof cur === "string" && cur.length > 0) return cur; - } - return ""; -} - -// Feishu text message content arrives as a JSON string like {"text":"hi"}. -// Some lark-cli modes pre-render to plain text. Handle both. -function extractText(raw: string): string { - const trimmed = raw.trim(); - if (!trimmed) return ""; - if (trimmed.startsWith("{")) { - try { - const j = JSON.parse(trimmed) as { text?: unknown }; - if (typeof j.text === "string") return j.text.trim(); - } catch { - /* not JSON content; fall through to raw */ - } - } - return trimmed; -} - -// Parse one NDJSON line into a ParsedReply, or null if the line is blank, -// malformed, or missing the fields we need. NEVER throws — a bad line must -// not kill the consume loop. -export function parseEventLine(line: string): ParsedReply | null { - const trimmed = line.trim(); - if (!trimmed) return null; - let obj: Record; - try { - obj = JSON.parse(trimmed) as Record; - } catch { - return null; - } - if (!obj || typeof obj !== "object") return null; - - const eventId = pick(obj, ["event_id"], ["header", "event_id"]); - const senderId = pick( - obj, - ["sender_id"], - ["event", "sender", "sender_id", "open_id"], - ["sender", "sender_id", "open_id"], - ); - const chatType = pick( - obj, - ["chat_type"], - ["event", "message", "chat_type"], - ["message", "chat_type"], - ); - const rawContent = pick( - obj, - ["content"], - ["event", "message", "content"], - ["message", "content"], - ); - const content = extractText(rawContent); - - if (!eventId || !senderId || !content) return null; - return { eventId, senderId, chatType: chatType || "", content }; -} - -// ---- pending-reply pointer (method A) ---- - -// Record the just-delivered question as the current pending-reply target. -// Called by the delivery primitive on a successful question send. Briefings -// never call this. A newer question overwrites the pointer (latest wins). -export async function setPendingReplyTarget(kv: StateKV, itemId: string): Promise { - const target: PendingReplyTarget = { itemId, setAt: new Date().toISOString() }; - await kv.set(KV.delivery, PENDING_REPLY_KEY, target); -} - -export async function getPendingReplyTarget(kv: StateKV): Promise { - const t = await kv.get(KV.delivery, PENDING_REPLY_KEY); - return t?.itemId ?? null; -} - -export async function clearPendingReplyTarget(kv: StateKV): Promise { - await kv.delete(KV.delivery, PENDING_REPLY_KEY); -} - -// ---- event_id dedup ---- - -function eventKey(eventId: string): string { - return `${EVENT_DEDUP_PREFIX}${eventId}`; -} - -async function isEventProcessed(kv: StateKV, eventId: string): Promise { - // The real iii-engine state::get returns `undefined` for a missing key, not - // `null`. A `!== null` check would treat a first-seen event (undefined) as a - // duplicate and silently drop every reply. Treat both null and undefined as - // "not processed". - const existing = await kv.get(KV.delivery, eventKey(eventId)); - return existing != null; -} - -async function markEventProcessed(kv: StateKV, eventId: string): Promise { - const rec: ProcessedEvent = { eventId, processedAt: new Date().toISOString() }; - await kv.set(KV.delivery, eventKey(eventId), rec); -} - -// ---- reply handler ---- - -export interface ReplyHandlerDeps { - kv: StateKV; - sdk: ISdk; - userId: string; // AGENTMEMORY_LARK_USER_ID — only this user's replies count -} - -export interface HandleOutcome { - action: "answered" | "ignored"; - reason?: string; - itemId?: string; -} - -// Build the per-line handler used by the D5b subprocess. handleLine parses a -// stdout NDJSON line, applies all the filters/dedup/mapping, and answers the -// pending question via mem::inbox-answer. It NEVER throws — any failure is -// logged and returned as an ignored outcome so the consume loop stays alive. -export function makeReplyHandler(deps: ReplyHandlerDeps): { - handleLine: (line: string) => Promise; -} { - const { kv, sdk, userId } = deps; - - async function handleLine(line: string): Promise { - try { - const reply = parseEventLine(line); - if (!reply) return { action: "ignored", reason: "unparseable" }; - - // Only the target user's private (p2p) messages are replies to us. - if (reply.chatType && reply.chatType !== "p2p") { - return { action: "ignored", reason: "not_p2p" }; - } - if (reply.senderId !== userId) { - return { action: "ignored", reason: "not_target_user" }; - } - - // Dedup on event_id (Feishu marks it dedup-safe). Mark BEFORE answering - // so a retry of the same event can't double-answer even if answer races. - if (await isEventProcessed(kv, reply.eventId)) { - return { action: "ignored", reason: "duplicate_event" }; - } - await markEventProcessed(kv, reply.eventId); - - const targetId = await getPendingReplyTarget(kv); - if (!targetId) { - // No pending question — method A can't map this reply. - return { action: "ignored", reason: "no_pending_target" }; - } - - // The pointer can go stale if the user answered/dismissed the question in - // the workbench instead of via Feishu. Don't re-answer a non-awaiting - // item — clear the stale pointer and ignore this reply. - const target = await kv.get(KV.inbox, targetId); - if (!target || target.status !== "awaiting") { - await clearPendingReplyTarget(kv); - return { action: "ignored", reason: "target_not_awaiting", itemId: targetId }; - } - - const result = (await sdk.trigger({ - function_id: "mem::inbox-answer", - payload: { id: targetId, answer: reply.content }, - })) as { success?: boolean; item?: InboxItem; error?: string }; - - if (!result?.success) { - // Answer failed (e.g. item gone). Keep the dedup marker (the event is - // consumed) but leave the pointer so a later valid reply can retry the - // mapping is moot — log and move on. - logger.warn("lark reply: inbox-answer failed", { - targetId, - error: result?.error, - }); - return { action: "ignored", reason: "answer_failed", itemId: targetId }; - } - - // Answered — clear the pointer so the next bare reply isn't misrouted. - await clearPendingReplyTarget(kv); - logger.info("lark reply answered a question", { itemId: targetId }); - return { action: "answered", itemId: targetId }; - } catch (err) { - logger.warn("lark reply handler error (line skipped)", { - error: err instanceof Error ? err.message : String(err), - }); - return { action: "ignored", reason: "handler_error" }; - } - } - - return { handleLine }; -} - - diff --git a/src/functions/lark-reply-loop.ts b/src/functions/lark-reply-loop.ts deleted file mode 100644 index fdea5fa0..00000000 --- a/src/functions/lark-reply-loop.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { spawn, type ChildProcess } from "node:child_process"; -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { makeReplyHandler } from "./lark-reply-consumer.js"; -import { logger } from "../logger.js"; - -// Line D / STEP-D5b — long-running Feishu reply subscriber. -// -// Spawns `lark-cli event consume im.message.receive_v1 --as bot --quiet`, -// splits its stdout into NDJSON lines, and feeds each line to the D5a mapping -// kernel (makeReplyHandler().handleLine). This file owns ONLY process -// lifecycle + line framing — all the parse/filter/dedup/map/answer semantics -// live in lark-reply-consumer.ts and are not touched here. -// -// Default OFF: nothing spawns unless AGENTMEMORY_LARK_REPLY_LOOP=true (the -// caller checks isLarkReplyLoopEnabled() before invoking this). - -const CLI = "lark-cli"; -const CONSUME_ARGS = [ - "event", - "consume", - "im.message.receive_v1", - "--as", - "bot", - "--quiet", -]; - -// Injectable spawner so tests never start a real lark-cli. -export type SpawnFn = (command: string, args: string[]) => ChildProcess; -const realSpawn: SpawnFn = (command, args) => - spawn(command, args, { - // Strip proxy env so the CLI's own client reaches Feishu directly, - // mirroring the lark-adapter runner. No shell — argv array only. - env: (() => { - const env = { ...process.env, LARK_CLI_NO_PROXY: "1" }; - delete env["HTTPS_PROXY"]; - delete env["HTTP_PROXY"]; - delete env["ALL_PROXY"]; - delete env["https_proxy"]; - delete env["http_proxy"]; - delete env["all_proxy"]; - return env; - })(), - stdio: ["pipe", "pipe", "pipe"], - }); - -export interface ReplyLoopDeps { - kv: StateKV; - sdk: ISdk; - userId: string; - spawnFn?: SpawnFn; -} - -export interface ReplyLoop { - stop: () => void; -} - -export function startLarkReplyConsumer(deps: ReplyLoopDeps): ReplyLoop { - const { kv, sdk, userId } = deps; - const spawnFn = deps.spawnFn ?? realSpawn; - const handler = makeReplyHandler({ kv, sdk, userId }); - - const child = spawnFn(CLI, CONSUME_ARGS); - let stopped = false; - // Carry buffer for stdout: a single JSON object may be split across chunks, - // and the last chunk may end mid-line. Only complete (newline-terminated) - // lines are handed to the kernel; the remainder is held until more arrives. - let buf = ""; - - child.stdout?.setEncoding("utf8"); - child.stdout?.on("data", (chunk: string) => { - buf += chunk; - let nl = buf.indexOf("\n"); - while (nl !== -1) { - const line = buf.slice(0, nl); - buf = buf.slice(nl + 1); - // handleLine never throws (D5a guarantees it); still guard the .catch - // so an unexpected async rejection can't crash the worker. - void handler.handleLine(line).catch((err: unknown) => { - logger.warn("lark reply loop: handleLine rejected", { - error: err instanceof Error ? err.message : String(err), - }); - }); - nl = buf.indexOf("\n"); - } - }); - - // stderr: log a short summary only — NEVER the event body (may contain the - // user's private message text). lark-cli prints a readiness/au‑th line and - // any errors here. - child.stderr?.setEncoding("utf8"); - child.stderr?.on("data", (chunk: string) => { - const summary = String(chunk).trim().slice(0, 200); - if (summary) logger.warn("lark reply loop stderr", { summary }); - }); - - child.on("error", (err: Error) => { - logger.warn("lark reply loop: spawn error", { error: err.message }); - }); - - child.on("exit", (code, signal) => { - if (stopped) return; // expected shutdown - // Non-zero / unexpected exit: warn only. We deliberately do NOT auto-restart - // in this PR — a restart loop risks duplicate consumers / subscription leaks. - logger.warn("lark reply loop: consumer exited", { - code, - signal, - note: "not auto-restarting (D5b); restart the worker to resubscribe", - }); - }); - - function stop(): void { - if (stopped) return; // idempotent - stopped = true; - try { - // Graceful: close stdin then SIGTERM. Never kill -9 (would orphan the - // subscription / skip lark-cli cleanup). - child.stdin?.end(); - child.kill("SIGTERM"); - } catch (err) { - logger.warn("lark reply loop: stop error", { - error: err instanceof Error ? err.message : String(err), - }); - } - } - - return { stop }; -} - diff --git a/src/functions/leases.ts b/src/functions/leases.ts deleted file mode 100644 index 1a2de33b..00000000 --- a/src/functions/leases.ts +++ /dev/null @@ -1,251 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import type { Action, Lease } from "../types.js"; -import { recordAudit } from "./audit.js"; - -const DEFAULT_LEASE_TTL_MS = 10 * 60 * 1000; -const MAX_LEASE_TTL_MS = 60 * 60 * 1000; - -export function registerLeasesFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::lease-acquire", - async (data: { actionId: string; agentId: string; ttlMs?: number }) => { - if (!data.actionId || !data.agentId) { - return { success: false, error: "actionId and agentId are required" }; - } - - const rawTtl = typeof data.ttlMs === "number" && Number.isFinite(data.ttlMs) && data.ttlMs > 0 - ? data.ttlMs - : DEFAULT_LEASE_TTL_MS; - const ttl = Math.min(rawTtl, MAX_LEASE_TTL_MS); - - return withKeyedLock(`mem:action:${data.actionId}`, async () => { - const action = await kv.get(KV.actions, data.actionId); - if (!action) { - return { success: false, error: "action not found" }; - } - if (action.status === "done" || action.status === "cancelled") { - return { success: false, error: "action already completed" }; - } - if (action.status === "blocked") { - return { success: false, error: "action is blocked" }; - } - - const existingLeases = await kv.list(KV.leases); - const activeLease = existingLeases.find( - (l) => - l.actionId === data.actionId && - l.status === "active" && - new Date(l.expiresAt).getTime() > Date.now(), - ); - - if (activeLease) { - if (activeLease.agentId === data.agentId) { - return { - success: true, - lease: activeLease, - renewed: false, - message: "Already holding this lease", - }; - } - return { - success: false, - error: "action already leased", - heldBy: activeLease.agentId, - expiresAt: activeLease.expiresAt, - }; - } - - const now = new Date(); - const lease: Lease = { - id: generateId("lse"), - actionId: data.actionId, - agentId: data.agentId, - acquiredAt: now.toISOString(), - expiresAt: new Date(now.getTime() + ttl).toISOString(), - status: "active", - }; - - await kv.set(KV.leases, lease.id, lease); - await recordAudit(kv, "lease_acquire", "mem::lease-acquire", [lease.id], { - actionId: data.actionId, - agentId: data.agentId, - expiresAt: lease.expiresAt, - }); - - const before = { ...action }; - action.status = "active"; - action.assignedTo = data.agentId; - action.updatedAt = now.toISOString(); - await kv.set(KV.actions, action.id, action); - await recordAudit(kv, "action_update", "mem::lease-acquire", [action.id], { - before, - after: action, - }); - - return { success: true, lease, renewed: false }; - }); - }, - ); - - sdk.registerFunction("mem::lease-release", - async (data: { actionId: string; agentId: string; result?: string }) => { - if (!data.actionId || !data.agentId) { - return { success: false, error: "actionId and agentId are required" }; - } - - return withKeyedLock(`mem:action:${data.actionId}`, async () => { - const leases = await kv.list(KV.leases); - const activeLease = leases.find( - (l) => - l.actionId === data.actionId && - l.agentId === data.agentId && - l.status === "active" && - new Date(l.expiresAt).getTime() > Date.now(), - ); - - if (!activeLease) { - return { success: false, error: "no active lease found for this agent" }; - } - - activeLease.status = "released"; - await kv.set(KV.leases, activeLease.id, activeLease); - await recordAudit(kv, "lease_release", "mem::lease-release", [activeLease.id], { - actionId: data.actionId, - agentId: data.agentId, - status: "released", - }); - - const action = await kv.get(KV.actions, data.actionId); - if (action && action.status === "active" && action.assignedTo === data.agentId) { - const before = { ...action }; - if (data.result) { - action.status = "done"; - action.result = data.result; - } else { - action.status = "pending"; - } - action.assignedTo = undefined; - action.updatedAt = new Date().toISOString(); - await kv.set(KV.actions, action.id, action); - await recordAudit(kv, "action_update", "mem::lease-release", [action.id], { - before, - after: action, - agentId: data.agentId, - }); - } - - return { success: true, released: true }; - }); - }, - ); - - sdk.registerFunction("mem::lease-renew", - async (data: { actionId: string; agentId: string; ttlMs?: number }) => { - if (!data.actionId || !data.agentId) { - return { success: false, error: "actionId and agentId are required" }; - } - - const rawTtl = typeof data.ttlMs === "number" && Number.isFinite(data.ttlMs) && data.ttlMs > 0 - ? data.ttlMs - : DEFAULT_LEASE_TTL_MS; - const ttl = Math.min(rawTtl, MAX_LEASE_TTL_MS); - - return withKeyedLock(`mem:action:${data.actionId}`, async () => { - const leases = await kv.list(KV.leases); - const activeLease = leases.find( - (l) => - l.actionId === data.actionId && - l.agentId === data.agentId && - l.status === "active" && - new Date(l.expiresAt).getTime() > Date.now(), - ); - - if (!activeLease) { - return { success: false, error: "no active (non-expired) lease to renew" }; - } - - const now = new Date(); - const base = Math.max(now.getTime(), new Date(activeLease.expiresAt).getTime()); - const beforeLease = { ...activeLease }; - activeLease.expiresAt = new Date(base + ttl).toISOString(); - activeLease.renewedAt = now.toISOString(); - await kv.set(KV.leases, activeLease.id, activeLease); - await recordAudit(kv, "lease_renew", "mem::lease-renew", [activeLease.id], { - actionId: data.actionId, - agentId: data.agentId, - before: beforeLease, - after: activeLease, - }); - - return { success: true, lease: activeLease }; - }); - }, - ); - - sdk.registerFunction("mem::lease-cleanup", - async () => { - const leases = await kv.list(KV.leases); - const now = Date.now(); - let expired = 0; - - for (const lease of leases) { - if ( - lease.status === "active" && - new Date(lease.expiresAt).getTime() <= now - ) { - const didExpire = await withKeyedLock( - `mem:action:${lease.actionId}`, - async () => { - const currentLease = await kv.get(KV.leases, lease.id); - if ( - !currentLease || - currentLease.status !== "active" || - new Date(currentLease.expiresAt).getTime() > Date.now() - ) { - return false; - } - currentLease.status = "expired"; - await kv.set(KV.leases, currentLease.id, currentLease); - await recordAudit(kv, "lease_release", "mem::lease-cleanup", [currentLease.id], { - action: "expire", - actionId: currentLease.actionId, - agentId: currentLease.agentId, - }); - - const action = await kv.get(KV.actions, currentLease.actionId); - const otherActiveLease = (await kv.list(KV.leases)).some( - (l) => - l.id !== currentLease.id && - l.actionId === currentLease.actionId && - l.status === "active" && - new Date(l.expiresAt).getTime() > Date.now(), - ); - if ( - action && - !otherActiveLease && - action.status === "active" && - action.assignedTo === currentLease.agentId - ) { - action.status = "pending"; - action.assignedTo = undefined; - action.updatedAt = new Date().toISOString(); - await kv.set(KV.actions, action.id, action); - await recordAudit(kv, "action_update", "mem::lease-cleanup", [action.id], { - action: "status-change", - newStatus: action.status, - actionId: action.id, - }); - } - return true; - }, - ); - if (didExpire) expired++; - } - } - - return { success: true, expired }; - }, - ); -} diff --git a/src/functions/lessons.ts b/src/functions/lessons.ts deleted file mode 100644 index 9e69f464..00000000 --- a/src/functions/lessons.ts +++ /dev/null @@ -1,283 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, fingerprintId } from "../state/schema.js"; -import type { Lesson } from "../types.js"; -import { recordAudit } from "./audit.js"; - -function reinforceLesson(lesson: Lesson): void { - const now = new Date().toISOString(); - lesson.reinforcements++; - lesson.confidence = Math.min( - 1.0, - lesson.confidence + 0.1 * (1 - lesson.confidence), - ); - lesson.lastReinforcedAt = now; - lesson.updatedAt = now; -} - -export function registerLessonsFunctions(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::lesson-save", - async (data: { - content: string; - context?: string; - confidence?: number; - project?: string; - tags?: string[]; - source?: "crystal" | "manual" | "consolidation"; - sourceIds?: string[]; - }) => { - if (!data.content?.trim()) { - return { success: false, error: "content is required" }; - } - - const fp = fingerprintId("lsn", data.content.trim().toLowerCase()); - const existing = await kv.get(KV.lessons, fp); - - if (existing && !existing.deleted) { - reinforceLesson(existing); - if (data.context && !existing.context) { - existing.context = data.context; - } - await kv.set(KV.lessons, existing.id, existing); - - try { - await recordAudit(kv, "lesson_strengthen", "mem::lesson-save", [ - existing.id, - ]); - } catch {} - - return { - success: true, - action: "strengthened", - lesson: existing, - }; - } - - const confidence = - typeof data.confidence === "number" && - data.confidence >= 0 && - data.confidence <= 1 - ? data.confidence - : 0.5; - - const now = new Date().toISOString(); - const lesson: Lesson = { - id: fp, - content: data.content.trim(), - context: data.context?.trim() || "", - confidence, - reinforcements: 0, - source: data.source || "manual", - sourceIds: data.sourceIds || [], - project: data.project, - tags: data.tags || [], - createdAt: now, - updatedAt: now, - decayRate: 0.05, - }; - - await kv.set(KV.lessons, lesson.id, lesson); - - try { - await recordAudit(kv, "lesson_save", "mem::lesson-save", [lesson.id]); - } catch {} - - return { success: true, action: "created", lesson }; - }, - ); - - sdk.registerFunction("mem::lesson-recall", - async (data: { - query: string; - project?: string; - minConfidence?: number; - limit?: number; - }) => { - if (!data.query?.trim()) { - return { success: false, error: "query is required" }; - } - - const query = data.query.toLowerCase(); - const minConfidence = data.minConfidence ?? 0.1; - const limit = data.limit ?? 10; - - let lessons = await kv.list(KV.lessons); - - lessons = lessons.filter( - (l) => !l.deleted && l.confidence >= minConfidence, - ); - - if (data.project) { - lessons = lessons.filter((l) => l.project === data.project); - } - - const scored = lessons - .map((l) => { - const text = `${l.content} ${l.context} ${l.tags.join(" ")}`.toLowerCase(); - const terms = query.split(/\s+/).filter((t) => t.length > 1); - const matchCount = terms.filter((t) => text.includes(t)).length; - if (matchCount === 0) return null; - - const relevance = matchCount / terms.length; - const daysSinceReinforced = l.lastReinforcedAt - ? (Date.now() - new Date(l.lastReinforcedAt).getTime()) / - (1000 * 60 * 60 * 24) - : (Date.now() - new Date(l.createdAt).getTime()) / - (1000 * 60 * 60 * 24); - const recencyBoost = 1 / (1 + daysSinceReinforced * 0.01); - const score = l.confidence * relevance * recencyBoost; - - return { lesson: l, score }; - }) - .filter(Boolean) as Array<{ lesson: Lesson; score: number }>; - - scored.sort((a, b) => b.score - a.score); - - try { - await recordAudit(kv, "lesson_recall", "mem::lesson-recall", [], { - query: data.query, - resultCount: scored.length, - }); - } catch {} - - return { - success: true, - lessons: scored.slice(0, limit).map((s) => ({ - ...s.lesson, - score: Math.round(s.score * 1000) / 1000, - })), - }; - }, - ); - - sdk.registerFunction("mem::lesson-list", - async (data: { - project?: string; - source?: string; - minConfidence?: number; - limit?: number; - }) => { - const limit = data.limit ?? 50; - const minConfidence = data.minConfidence ?? 0; - let lessons = await kv.list(KV.lessons); - - lessons = lessons.filter( - (l) => !l.deleted && l.confidence >= minConfidence, - ); - - if (data.project) { - lessons = lessons.filter((l) => l.project === data.project); - } - if (data.source) { - lessons = lessons.filter((l) => l.source === data.source); - } - - lessons.sort((a, b) => b.confidence - a.confidence); - - return { success: true, lessons: lessons.slice(0, limit) }; - }, - ); - - sdk.registerFunction("mem::lesson-strengthen", - async (data: { lessonId: string }) => { - if (!data.lessonId) { - return { success: false, error: "lessonId is required" }; - } - - const lesson = await kv.get(KV.lessons, data.lessonId); - if (!lesson || lesson.deleted) { - return { success: false, error: "lesson not found" }; - } - - reinforceLesson(lesson); - - await kv.set(KV.lessons, lesson.id, lesson); - - try { - await recordAudit(kv, "lesson_strengthen", "mem::lesson-strengthen", [ - lesson.id, - ]); - } catch {} - - return { success: true, lesson }; - }, - ); - - sdk.registerFunction("mem::lesson-decay-sweep", - async () => { - const lessons = await kv.list(KV.lessons); - let decayed = 0; - let softDeleted = 0; - const now = Date.now(); - const timestamp = new Date().toISOString(); - const dirty: Lesson[] = []; - const auditEvents: Array<{ - id: string; - action: "decay" | "soft-delete"; - beforeConfidence: number; - afterConfidence: number; - beforeDeleted: boolean; - afterDeleted: boolean; - }> = []; - - for (const lesson of lessons) { - if (lesson.deleted) continue; - - const baseline = lesson.lastDecayedAt || lesson.lastReinforcedAt || lesson.createdAt; - const weeksSinceBaseline = - (now - new Date(baseline).getTime()) / (1000 * 60 * 60 * 24 * 7); - - if (weeksSinceBaseline < 1) continue; - - const decay = lesson.decayRate * weeksSinceBaseline; - const newConfidence = Math.max(0.05, lesson.confidence - decay); - - if (newConfidence !== lesson.confidence) { - const beforeConfidence = lesson.confidence; - const beforeDeleted = !!lesson.deleted; - lesson.confidence = Math.round(newConfidence * 1000) / 1000; - lesson.lastDecayedAt = timestamp; - lesson.updatedAt = timestamp; - - if (lesson.confidence <= 0.1 && lesson.reinforcements === 0) { - lesson.deleted = true; - softDeleted++; - } else { - decayed++; - } - - dirty.push(lesson); - auditEvents.push({ - id: lesson.id, - action: lesson.deleted ? "soft-delete" : "decay", - beforeConfidence, - afterConfidence: lesson.confidence, - beforeDeleted, - afterDeleted: !!lesson.deleted, - }); - } - } - - await Promise.all(dirty.map((l) => kv.set(KV.lessons, l.id, l))); - await Promise.all( - auditEvents.map((event) => - recordAudit(kv, "lesson_strengthen", "mem::lesson-decay-sweep", [event.id], { - action: event.action, - actor: "system", - reason: "decay-sweep", - before: { - confidence: event.beforeConfidence, - deleted: event.beforeDeleted, - }, - after: { - confidence: event.afterConfidence, - deleted: event.afterDeleted, - }, - }), - ), - ); - - return { success: true, decayed, softDeleted, total: lessons.length }; - }, - ); -} diff --git a/src/functions/mesh.ts b/src/functions/mesh.ts deleted file mode 100644 index ad52e5ea..00000000 --- a/src/functions/mesh.ts +++ /dev/null @@ -1,495 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import { recordAudit } from "./audit.js"; -import type { - MeshPeer, - Memory, - Action, - SemanticMemory, - ProceduralMemory, - MemoryRelation, - GraphNode, - GraphEdge, -} from "../types.js"; -import { lookup } from "node:dns/promises"; -import { isIP } from "node:net"; - -function isPrivateIP(ip: string): boolean { - if (ip === "127.0.0.1" || ip === "::1" || ip === "0.0.0.0") return true; - if (ip.startsWith("10.") || ip.startsWith("192.168.")) return true; - if (/^172\.(1[6-9]|2\d|3[01])\./.test(ip)) return true; - if (ip === "169.254.169.254") return true; - if (ip.startsWith("fe80:") || ip.startsWith("fc00:") || ip.startsWith("fd")) return true; - if (ip.startsWith("::ffff:")) { - const v4 = ip.slice(7); - return isPrivateIP(v4); - } - return false; -} - -async function isAllowedUrl(urlStr: string): Promise { - try { - const parsed = new URL(urlStr); - if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false; - if (parsed.username || parsed.password) return false; - const host = parsed.hostname.toLowerCase(); - - if (host === "localhost") return false; - if (isIP(host) && isPrivateIP(host)) return false; - - if (!isIP(host)) { - try { - const resolved = await lookup(host, { all: true }); - if (resolved.some((r) => isPrivateIP(r.address))) return false; - } catch { - // DNS resolution failed — allow the URL (the actual fetch will fail if unreachable) - } - } - - return true; - } catch { - return false; - } -} - -const DEFAULT_SHARED_SCOPES = [ - "memories", - "actions", - "semantic", - "procedural", - "relations", - "graph:nodes", - "graph:edges", -]; - -interface MeshSyncPayload { - memories?: Memory[]; - actions?: Action[]; - semantic?: SemanticMemory[]; - procedural?: ProceduralMemory[]; - relations?: MemoryRelation[]; - graphNodes?: GraphNode[]; - graphEdges?: GraphEdge[]; -} - -async function lwwMergeList( - kv: StateKV, - scope: string, - items: T[] | undefined, - lockPrefix: string, - tsField: "updatedAt" | "createdAt", -): Promise { - if (!items || !Array.isArray(items)) return 0; - let count = 0; - for (const item of items) { - if (!item.id || typeof item.id !== "string") continue; - const ts = (item as Record)[tsField]; - if (typeof ts !== "string" || Number.isNaN(new Date(ts).getTime())) continue; - const wrote = await withKeyedLock(`${lockPrefix}:${item.id}`, async () => { - const existing = await kv.get(scope, item.id); - if (!existing) { - await kv.set(scope, item.id, item); - return true; - } - const existingTs = (existing as Record)[tsField] as string; - if (new Date(ts) > new Date(existingTs)) { - await kv.set(scope, item.id, item); - return true; - } - return false; - }); - if (wrote) count++; - } - return count; -} - -function graphNodeTs(node: GraphNode): string { - return node.updatedAt || node.createdAt; -} - -async function lwwMergeGraphNodes( - kv: StateKV, - items: GraphNode[] | undefined, -): Promise { - if (!items || !Array.isArray(items)) return 0; - let count = 0; - for (const item of items) { - if (!item.id || typeof item.id !== "string") continue; - const ts = graphNodeTs(item); - if (!ts || Number.isNaN(new Date(ts).getTime())) continue; - const wrote = await withKeyedLock(`mem:gnode:${item.id}`, async () => { - const existing = await kv.get(KV.graphNodes, item.id); - if (!existing) { - await kv.set(KV.graphNodes, item.id, item); - return true; - } - if (new Date(ts) > new Date(graphNodeTs(existing))) { - await kv.set(KV.graphNodes, item.id, item); - return true; - } - return false; - }); - if (wrote) count++; - } - return count; -} - -export function registerMeshFunction( - sdk: ISdk, - kv: StateKV, - meshAuthToken?: string, -): void { - sdk.registerFunction("mem::mesh-register", - async (data: { - url: string; - name: string; - sharedScopes?: string[]; - syncFilter?: { project?: string }; - }) => { - if (!data || typeof data !== "object") { - return { success: false, error: "payload required" }; - } - if (!data.url || !data.name) { - return { success: false, error: "url and name are required" }; - } - - if (!(await isAllowedUrl(data.url))) { - return { success: false, error: "URL blocked: private/local address not allowed" }; - } - - const existing = await kv.list(KV.mesh); - const duplicate = existing.find((p) => p.url === data.url); - if (duplicate) { - return { success: false, error: "peer already registered", peerId: duplicate.id }; - } - - const peer: MeshPeer = { - id: generateId("peer"), - url: data.url, - name: data.name, - status: "disconnected", - sharedScopes: data.sharedScopes || DEFAULT_SHARED_SCOPES, - syncFilter: data.syncFilter, - }; - - await kv.set(KV.mesh, peer.id, peer); - await recordAudit(kv, "mesh_sync", "mem::mesh-register", [peer.id], { - action: "mesh.register", - peerId: peer.id, - name: peer.name, - url: peer.url, - sharedScopes: peer.sharedScopes, - }); - return { success: true, peer }; - }, - ); - - sdk.registerFunction("mem::mesh-list", - async () => { - const peers = await kv.list(KV.mesh); - return { success: true, peers }; - }, - ); - - sdk.registerFunction("mem::mesh-sync", - async (data: { peerId?: string; scopes?: string[]; direction?: "push" | "pull" | "both" }) => { - if (!meshAuthToken) { - return { - success: false, - error: "mesh sync requires AGENTMEMORY_SECRET", - }; - } - if (!data || typeof data !== "object") { - data = {}; - } - - const direction = data.direction || "both"; - let peers: MeshPeer[]; - - if (data.peerId) { - const peer = await kv.get(KV.mesh, data.peerId); - if (!peer) return { success: false, error: "peer not found" }; - peers = [peer]; - } else { - peers = await kv.list(KV.mesh); - } - - const results: Array<{ - peerId: string; - peerName: string; - pushed: number; - pulled: number; - errors: string[]; - }> = []; - - for (const peer of peers) { - const result = { - peerId: peer.id, - peerName: peer.name, - pushed: 0, - pulled: 0, - errors: [] as string[], - }; - - peer.status = "syncing"; - await kv.set(KV.mesh, peer.id, peer); - await recordAudit(kv, "mesh_sync", "mem::mesh-sync", [peer.id], { - action: "mesh.sync.start", - direction, - scopes: data.scopes || peer.sharedScopes, - }); - - const scopes = data.scopes || peer.sharedScopes; - - try { - if (!(await isAllowedUrl(peer.url))) { - result.errors.push("peer URL blocked: private/local address not allowed"); - peer.status = "error"; - await kv.set(KV.mesh, peer.id, peer); - await recordAudit(kv, "mesh_sync", "mem::mesh-sync", [peer.id], { - action: "mesh.sync.error", - error: "peer URL blocked: private/local address not allowed", - }); - results.push(result); - continue; - } - - if (direction === "push" || direction === "both") { - const pushData = await collectSyncData(kv, scopes, peer.lastSyncAt, peer.syncFilter); - try { - const response = await fetch(`${peer.url}/agentmemory/mesh/receive`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${meshAuthToken}`, - }, - body: JSON.stringify(pushData), - signal: AbortSignal.timeout(30000), - redirect: "error", - }); - if (response.ok) { - const body = (await response.json()) as { accepted: number }; - result.pushed = body.accepted || 0; - } else { - result.errors.push(`push failed: HTTP ${response.status}`); - } - } catch (err) { - result.errors.push(`push failed: ${String(err)}`); - } - } - - if (direction === "pull" || direction === "both") { - try { - const response = await fetch( - `${peer.url}/agentmemory/mesh/export?since=${peer.lastSyncAt || ""}`, - { - headers: { - Authorization: `Bearer ${meshAuthToken}`, - }, - signal: AbortSignal.timeout(30000), - redirect: "error", - }, - ); - if (response.ok) { - const pullData = (await response.json()) as { - memories?: Memory[]; - actions?: Action[]; - }; - result.pulled = await applySyncData(kv, pullData, scopes); - } else { - result.errors.push(`pull failed: HTTP ${response.status}`); - } - } catch (err) { - result.errors.push(`pull failed: ${String(err)}`); - } - } - - peer.status = result.errors.length > 0 ? "error" : "connected"; - if (result.errors.length === 0) { - peer.lastSyncAt = new Date().toISOString(); - } - } catch (err) { - peer.status = "disconnected"; - result.errors.push(String(err)); - } - - await kv.set(KV.mesh, peer.id, peer); - await recordAudit(kv, "mesh_sync", "mem::mesh-sync", [peer.id], { - action: result.errors.length > 0 ? "mesh.sync.error" : "mesh.sync.complete", - direction, - scopes, - pushed: result.pushed, - pulled: result.pulled, - errors: result.errors, - lastSyncAt: peer.lastSyncAt, - }); - results.push(result); - } - - return { success: true, results }; - }, - ); - - sdk.registerFunction("mem::mesh-receive", - async (data: MeshSyncPayload) => { - if (!data || typeof data !== "object") { - return { success: false, error: "payload required" }; - } - let accepted = 0; - - accepted += await lwwMergeList(kv, KV.memories, data.memories, "mem:memory", "updatedAt"); - accepted += await lwwMergeList(kv, KV.actions, data.actions, "mem:action", "updatedAt"); - accepted += await lwwMergeList(kv, KV.semantic, data.semantic, "mem:semantic", "updatedAt"); - accepted += await lwwMergeList(kv, KV.procedural, data.procedural, "mem:procedural", "updatedAt"); - if (data.relations && Array.isArray(data.relations)) { - for (const rel of data.relations) { - if (!rel.sourceId || !rel.targetId || !rel.type) continue; - const relKey = `${rel.sourceId}:${rel.targetId}:${rel.type}`; - await withKeyedLock(`mem:relation:${relKey}`, async () => { - const existing = await kv.get(KV.relations, relKey); - if (!existing) { - await kv.set(KV.relations, relKey, rel); - await recordAudit(kv, "mesh_sync", "mem::mesh-receive", [relKey], { - action: "mesh.receive.relation", - accepted: true, - }); - accepted++; - } - }); - } - } - accepted += await lwwMergeGraphNodes(kv, data.graphNodes); - accepted += await lwwMergeList(kv, KV.graphEdges, data.graphEdges, "mem:gedge", "createdAt"); - await recordAudit(kv, "mesh_sync", "mem::mesh-receive", [], { - action: "mesh.receive", - accepted, - }); - - return { success: true, accepted }; - }, - ); - - sdk.registerFunction("mem::mesh-remove", - async (data: { peerId: string }) => { - if (!data || typeof data !== "object" || !data.peerId) { - return { success: false, error: "peerId is required" }; - } - await kv.delete(KV.mesh, data.peerId); - await recordAudit(kv, "mesh_sync", "mem::mesh-remove", [data.peerId], { - action: "mesh.remove", - }); - return { success: true }; - }, - ); -} - -function deltaFilter( - items: T[], - sinceTime: number, - tsField: "updatedAt" | "createdAt", -): T[] { - return items.filter( - (item) => new Date((item as Record)[tsField] as string).getTime() > sinceTime, - ); -} - -async function collectSyncData( - kv: StateKV, - scopes: string[], - since?: string, - syncFilter?: { project?: string }, -): Promise { - const result: MeshSyncPayload = {}; - const parsed = since ? new Date(since).getTime() : 0; - const sinceTime = Number.isNaN(parsed) ? 0 : parsed; - - if (scopes.includes("memories")) { - const all = await kv.list(KV.memories); - result.memories = deltaFilter(all, sinceTime, "updatedAt"); - } - - if (scopes.includes("actions")) { - let all = await kv.list(KV.actions); - if (syncFilter?.project) { - all = all.filter((a) => a.project === syncFilter.project); - } - result.actions = deltaFilter(all, sinceTime, "updatedAt"); - } - - const projectScoped = !!syncFilter?.project; - - if (scopes.includes("semantic") && !projectScoped) { - const all = await kv.list(KV.semantic); - result.semantic = deltaFilter(all, sinceTime, "updatedAt"); - } - - if (scopes.includes("procedural") && !projectScoped) { - const all = await kv.list(KV.procedural); - result.procedural = deltaFilter(all, sinceTime, "updatedAt"); - } - - if (scopes.includes("relations") && !projectScoped) { - const all = await kv.list(KV.relations); - result.relations = deltaFilter(all, sinceTime, "createdAt"); - } - - if (scopes.includes("graph:nodes") && !projectScoped) { - const all = await kv.list(KV.graphNodes); - result.graphNodes = all.filter( - (n) => new Date(graphNodeTs(n)).getTime() > sinceTime, - ); - } - - if (scopes.includes("graph:edges") && !projectScoped) { - const all = await kv.list(KV.graphEdges); - result.graphEdges = deltaFilter(all, sinceTime, "createdAt"); - } - - return result; -} - -async function applySyncData( - kv: StateKV, - data: MeshSyncPayload, - scopes: string[], -): Promise { - let applied = 0; - - if (scopes.includes("memories")) { - applied += await lwwMergeList(kv, KV.memories, data.memories, "mem:memory", "updatedAt"); - } - if (scopes.includes("actions")) { - applied += await lwwMergeList(kv, KV.actions, data.actions, "mem:action", "updatedAt"); - } - if (scopes.includes("semantic")) { - applied += await lwwMergeList(kv, KV.semantic, data.semantic, "mem:semantic", "updatedAt"); - } - if (scopes.includes("procedural")) { - applied += await lwwMergeList(kv, KV.procedural, data.procedural, "mem:procedural", "updatedAt"); - } - if (scopes.includes("relations") && data.relations) { - for (const rel of data.relations) { - if (!rel.sourceId || !rel.targetId || !rel.type) continue; - const relKey = `${rel.sourceId}:${rel.targetId}:${rel.type}`; - const wrote = await withKeyedLock(`mem:relation:${relKey}`, async () => { - const existing = await kv.get(KV.relations, relKey); - if (!existing) { - await kv.set(KV.relations, relKey, rel); - return true; - } - return false; - }); - if (wrote) applied++; - } - } - if (scopes.includes("graph:nodes")) { - applied += await lwwMergeGraphNodes(kv, data.graphNodes); - } - if (scopes.includes("graph:edges")) { - applied += await lwwMergeList(kv, KV.graphEdges, data.graphEdges, "mem:gedge", "createdAt"); - } - - return applied; -} diff --git a/src/functions/migrate-vector-index.ts b/src/functions/migrate-vector-index.ts deleted file mode 100644 index 058eab84..00000000 --- a/src/functions/migrate-vector-index.ts +++ /dev/null @@ -1,152 +0,0 @@ -import type { EmbeddingProvider, CompressedObservation, Memory } from "../types.js"; -import { VectorIndex } from "../state/vector-index.js"; -import { KV } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import { logger } from "../logger.js"; - -export interface MigrateVectorIndexResult { - success: boolean; - totalProcessed: number; - failed: number; - vectorSize: number; - failedSessions: string[]; -} - -// Validate one embedding's shape against the provider's declared dimensions -// before pushing it into the index. Mirrors the symmetric guard in -// search.ts::vectorIndexAddGuarded — without this, a misconfigured -// provider returning the wrong-length Float32Array would silently corrupt -// the rebuilt index (per #248). -function isValidEmbedding( - embedding: Float32Array, - provider: EmbeddingProvider, - context: { kind: "memory" | "observation"; id: string }, -): boolean { - if (embedding.length !== provider.dimensions) { - logger.warn("migrateVectorIndex: dimension mismatch — skipping", { - kind: context.kind, - id: context.id, - provider: provider.name, - expected: provider.dimensions, - received: embedding.length, - }); - return false; - } - return true; -} - -// Rebuilds a fresh VectorIndex against `newProvider`, re-embedding every -// memory and per-session observation in `kv`. Each phase (memories + -// per-session observations) is isolated — a single session that throws -// on kv.list or embedBatch increments `failed` and appends to -// `failedSessions`, but the migration continues. Returns a structured -// result the caller can inspect to decide whether to swap the index in. -export async function migrateVectorIndex( - kv: StateKV, - newProvider: EmbeddingProvider, -): Promise { - const newIndex = new VectorIndex(); - let failed = 0; - let processed = 0; - const failedSessions: string[] = []; - - // --- Memories phase ---------------------------------------------------- - // textMems is declared outside the try so the catch can attribute the - // batch-level failure to the correct number of missed embeddings (the - // size of the batch we were about to embed), not a flat +1. - let textMems: Memory[] = []; - try { - const memories = await kv.list(KV.memories); - textMems = memories.filter( - (m) => m.isLatest !== false && m.title && m.content && m.content.trim() !== "", - ); - const texts = textMems.map((m) => m.title + " " + m.content); - - if (texts.length > 0) { - const embeddings = await newProvider.embedBatch(texts); - for (let i = 0; i < textMems.length; i++) { - if (!isValidEmbedding(embeddings[i], newProvider, { kind: "memory", id: textMems[i].id })) { - failed++; - continue; - } - newIndex.add( - textMems[i].id, - textMems[i].sessionIds[0] ?? "memory", - embeddings[i], - ); - processed++; - } - } - } catch (err) { - // If kv.list threw before textMems was populated, the batch size is - // unknown — count as +1 (something failed but we don't know what). - // If embedBatch threw, textMems.length is the real number of missed - // memories. Caller relying on `failed` for retry math needs this - // attribution to be accurate. - const missed = textMems.length > 0 ? textMems.length : 1; - logger.warn("migrateVectorIndex: failed to re-embed memories", { - missed, - error: err instanceof Error ? err.message : String(err), - }); - failed += missed; - } - - // --- Observations phase (per-session isolation) ------------------------ - // Without per-session try/catch, one bad session (kv.list throws, - // embedBatch rejects, etc.) would abort every later session and silently - // truncate the migration. Each session now has its own boundary; failures - // increment `failed`, append the session id to failedSessions, and the - // loop moves on. - let sessions: Array<{ id: string }>; - try { - sessions = await kv.list<{ id: string }>(KV.sessions); - } catch (err) { - logger.warn("migrateVectorIndex: failed to list sessions", { - error: err instanceof Error ? err.message : String(err), - }); - failed++; - // Distinguish a list-sessions failure (catastrophic: no sessions - // could be enumerated) from a per-session failure (one specific id - // threw). Without the marker the caller sees failed=N + an empty - // failedSessions list and can't tell apart "0 sessions, all OK" - // from "kv.list itself blew up". - failedSessions.push(""); - return { success: false, totalProcessed: processed, failed, vectorSize: newIndex.size, failedSessions }; - } - - for (const session of sessions) { - try { - const observations = await kv.list( - KV.observations(session.id), - ); - const textObs = observations.filter((o) => o.title); - const texts = textObs.map((o) => o.title + " " + (o.narrative || "")); - if (texts.length === 0) continue; - - const embeddings = await newProvider.embedBatch(texts); - for (let i = 0; i < textObs.length; i++) { - if (!isValidEmbedding(embeddings[i], newProvider, { kind: "observation", id: textObs[i].id })) { - failed++; - continue; - } - newIndex.add(textObs[i].id, textObs[i].sessionId, embeddings[i]); - processed++; - } - } catch (err) { - logger.warn("migrateVectorIndex: failed to re-embed session", { - sessionId: session.id, - error: err instanceof Error ? err.message : String(err), - }); - failed++; - failedSessions.push(session.id); - } - } - - return { - success: failed === 0, - totalProcessed: processed, - failed, - vectorSize: newIndex.size, - failedSessions, - }; -} diff --git a/src/functions/migrate.ts b/src/functions/migrate.ts deleted file mode 100644 index c2039ea1..00000000 --- a/src/functions/migrate.ts +++ /dev/null @@ -1,247 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import { resolve } from "node:path"; -import { homedir } from "node:os"; -import { KV, generateId } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import type { - Memory, - Session, - CompressedObservation, - SessionSummary, -} from "../types.js"; -import { logger } from "../logger.js"; - -const ALLOWED_DIRS = [resolve(homedir(), ".agentmemory")]; - -function isAllowedPath(dbPath: string): boolean { - const resolved = resolve(dbPath); - return ALLOWED_DIRS.some((dir) => resolved.startsWith(dir + "/")); -} - -// Infer memory project from the majority project of its associated sessions. -// Returns { updated, skipped } — safe to run repeatedly (idempotent). -export async function inferMemoryProjects( - kv: StateKV, - dryRun = false, -): Promise<{ updated: number; skipped: number; ambiguous: number }> { - const memories = await kv.list(KV.memories); - const sessionCache = new Map(); - - const loadSession = async (sid: string): Promise => { - if (sessionCache.has(sid)) return sessionCache.get(sid)!; - const s = await kv.get(KV.sessions, sid).catch(() => null); - sessionCache.set(sid, s); - return s; - }; - - let updated = 0; - let skipped = 0; - let ambiguous = 0; - - for (const memory of memories) { - if (memory.project) { - skipped++; - continue; - } - - const sessionIds = memory.sessionIds ?? []; - if (sessionIds.length === 0) { - ambiguous++; - continue; - } - - const projects: string[] = []; - for (const sid of sessionIds) { - const session = await loadSession(sid); - if (session?.project) projects.push(session.project); - } - - if (projects.length === 0) { - ambiguous++; - continue; - } - - // Majority-vote: count frequency of each project value. - const freq = new Map(); - for (const p of projects) freq.set(p, (freq.get(p) ?? 0) + 1); - const sorted = [...freq.entries()].sort((a, b) => b[1] - a[1]); - const [topProject, topCount] = sorted[0]; - - // Require a strict majority (> 50%) to avoid misattributing a memory - // that was genuinely built from sessions across multiple projects. - if (topCount <= projects.length / 2 && sorted.length > 1) { - ambiguous++; - continue; - } - - if (!dryRun) { - memory.project = topProject; - await kv.set(KV.memories, memory.id, memory); - } - updated++; - } - - logger.info("inferMemoryProjects complete", { updated, skipped, ambiguous, dryRun }); - return { updated, skipped, ambiguous }; -} - -export function registerMigrateFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::migrate", - async (data: { dbPath?: string; step?: string; dryRun?: boolean }) => { - // In-place KV migration steps (no SQLite dependency). - if (data.step === "infer-memory-projects") { - const dryRun = data.dryRun ?? false; - logger.info("Migration step: infer-memory-projects", { dryRun }); - const result = await inferMemoryProjects(kv, dryRun); - return { success: true, step: "infer-memory-projects", ...result }; - } - - if (!data.dbPath) { - return { - success: false, - error: "Either step or dbPath is required", - }; - } - - logger.info("Migration started", { dbPath: data.dbPath }); - - if (!isAllowedPath(data.dbPath)) { - return { - success: false, - error: `Path not allowed. Must be under: ${ALLOWED_DIRS.join(", ")}`, - }; - } - - let Database: any; - try { - // @ts-expect-error optional dependency - Database = (await import("better-sqlite3")).default; - } catch { - return { - success: false, - error: - "better-sqlite3 not installed. Run: npm install better-sqlite3", - }; - } - - const fs = await import("node:fs"); - if (!fs.existsSync(data.dbPath)) { - return { success: false, error: `Database not found: ${data.dbPath}` }; - } - - let db: any; - try { - db = Database(data.dbPath, { readonly: true }); - let sessionCount = 0; - let obsCount = 0; - let summaryCount = 0; - - const sessions = db - .prepare("SELECT * FROM sessions ORDER BY created_at DESC") - .all() as any[]; - for (const row of sessions) { - const session: Session = { - id: row.session_id || row.id, - project: row.project_path || row.project || "unknown", - cwd: row.cwd || row.project_path || "", - startedAt: - row.created_at || row.started_at || new Date().toISOString(), - endedAt: row.ended_at || row.updated_at, - status: "completed", - observationCount: 0, - }; - await kv.set(KV.sessions, session.id, session); - sessionCount++; - } - - let observations: any[] = []; - try { - observations = db - .prepare("SELECT * FROM observations ORDER BY created_at ASC") - .all() as any[]; - } catch { - try { - observations = db - .prepare( - "SELECT * FROM compressed_observations ORDER BY created_at ASC", - ) - .all() as any[]; - } catch { - logger.warn("No observation tables found"); - } - } - - for (const row of observations) { - const sessionId = row.session_id || "migrated"; - const obs: CompressedObservation = { - id: row.id || generateId("mig"), - sessionId, - timestamp: row.created_at || new Date().toISOString(), - type: row.type || "other", - title: row.title || row.summary || "Migrated observation", - subtitle: row.subtitle, - facts: safeJsonParse(row.facts, []), - narrative: row.narrative || row.content || "", - concepts: safeJsonParse(row.concepts, []), - files: safeJsonParse(row.files, []), - importance: row.importance || 5, - }; - await kv.set(KV.observations(sessionId), obs.id, obs); - obsCount++; - } - - let summaries: any[] = []; - try { - summaries = db - .prepare("SELECT * FROM session_summaries") - .all() as any[]; - } catch { - logger.warn("No summaries table found"); - } - - for (const row of summaries) { - const summary: SessionSummary = { - sessionId: row.session_id, - project: row.project || "unknown", - createdAt: row.created_at || new Date().toISOString(), - title: row.title || "Migrated session", - narrative: row.narrative || row.summary || "", - keyDecisions: safeJsonParse(row.key_decisions, []), - filesModified: safeJsonParse(row.files_modified, []), - concepts: safeJsonParse(row.concepts, []), - observationCount: row.observation_count || 0, - }; - await kv.set(KV.summaries, row.session_id, summary); - summaryCount++; - } - - logger.info("Migration complete", { - sessionCount, - obsCount, - summaryCount, - }); - return { success: true, sessionCount, obsCount, summaryCount }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Migration failed", { error: msg }); - return { success: false, error: "Migration failed" }; - } finally { - try { - if (db) db.close(); - } catch {} - } - }, - ); -} - -function safeJsonParse(value: unknown, fallback: T): T { - if (Array.isArray(value)) return value as T; - if (typeof value === "string") { - try { - return JSON.parse(value) as T; - } catch { - return fallback; - } - } - return fallback; -} diff --git a/src/functions/observe.ts b/src/functions/observe.ts deleted file mode 100644 index 4e5c1ef2..00000000 --- a/src/functions/observe.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { TriggerAction, type ISdk } from "iii-sdk"; -import type { RawObservation, HookPayload } from "../types.js"; -import { KV, STREAM, generateId } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { stripPrivateData } from "./privacy.js"; -import { DedupMap } from "./dedup.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import { isAutoCompressEnabled } from "../config.js"; -import { buildSyntheticCompression } from "./compress-synthetic.js"; -import { getSearchIndex, vectorIndexAddGuarded } from "./search.js"; -import { getAgentId } from "../config.js"; -import { logger } from "../logger.js"; - -export function extractImage(d: unknown): string | undefined { - if (!d) return undefined; - if (typeof d === "string") { - if (d.startsWith("data:image/") || d.startsWith("iVBORw0KGgo") || d.startsWith("/9j/")) { - return d; - } - return undefined; - } - if (typeof d === "object" && d !== null) { - const obj = d as Record; - if (typeof obj["image_data"] === "string") return obj["image_data"]; - if (typeof obj["image_path"] === "string") return obj["image_path"]; - if (typeof obj["imageBase64"] === "string") return obj["imageBase64"]; - if (typeof obj["imagePath"] === "string") return obj["imagePath"]; - - for (const key of Object.keys(obj)) { - const match = extractImage(obj[key]); - if (match) return match; - } - } - return undefined; -} - -export function registerObserveFunction( - sdk: ISdk, - kv: StateKV, - dedupMap?: DedupMap, - maxObservationsPerSession?: number, -): void { - sdk.registerFunction("mem::observe", - async (payload: HookPayload) => { - - if ( - !payload?.sessionId || - typeof payload.sessionId !== "string" || - !payload.hookType || - typeof payload.hookType !== "string" || - !payload.timestamp || - typeof payload.timestamp !== "string" - ) { - return { - success: false, - error: - "Invalid payload: sessionId, hookType, and timestamp are required", - }; - } - - const obsId = generateId("obs"); - - let dedupHash: string | undefined; - if (dedupMap) { - const d = - typeof payload.data === "object" && payload.data !== null - ? (payload.data as Record) - : {}; - const toolName = (d["tool_name"] as string) || payload.hookType; - dedupHash = dedupMap.computeHash( - payload.sessionId, - toolName, - d["tool_input"], - ); - if (dedupMap.isDuplicate(dedupHash)) { - return { deduplicated: true, sessionId: payload.sessionId }; - } - } - - let sanitizedRaw: unknown = payload.data; - try { - const jsonStr = JSON.stringify(payload.data); - const sanitized = stripPrivateData(jsonStr); - sanitizedRaw = JSON.parse(sanitized); - } catch { - sanitizedRaw = stripPrivateData(String(payload.data)); - } - - const raw: RawObservation = { - id: obsId, - sessionId: payload.sessionId, - timestamp: payload.timestamp, - hookType: payload.hookType, - raw: sanitizedRaw, - }; - - let extractedImage: string | undefined; - - if (typeof sanitizedRaw === "object" && sanitizedRaw !== null) { - const d = sanitizedRaw as Record; - if ( - payload.hookType === "post_tool_use" || - payload.hookType === "post_tool_failure" - ) { - raw.toolName = d["tool_name"] as string | undefined; - raw.toolInput = d["tool_input"]; - raw.toolOutput = d["tool_output"] || d["error"]; - } - if (payload.hookType === "prompt_submit") { - raw.userPrompt = d["prompt"] as string | undefined; - } - - extractedImage = extractImage(sanitizedRaw); - if (extractedImage) { - raw.modality = (raw.toolInput || raw.toolOutput || raw.userPrompt) ? "mixed" : "image"; - } - } else if (typeof sanitizedRaw === "string") { - extractedImage = extractImage(sanitizedRaw); - if (extractedImage) { - raw.modality = "image"; - } - } - - const pendingImageData = extractedImage; - - return withKeyedLock(`obs:${payload.sessionId}`, async () => { - if (maxObservationsPerSession && maxObservationsPerSession > 0) { - const existing = await kv.list(KV.observations(payload.sessionId)); - if (existing.length >= maxObservationsPerSession) { - return { - success: false, - error: `Session observation limit reached (${maxObservationsPerSession})`, - }; - } - } - - // Existing session is the source of truth for agentId (even - // undefined). Env AGENT_ID only fires when no session row - // exists yet — otherwise an unscoped session would get - // retroactively scoped by a later AGENT_ID export. - const existingSession = await kv.get<{ - agentId?: string; - observationCount?: number; - firstPrompt?: string; - }>(KV.sessions, payload.sessionId); - const inheritedAgentId = existingSession - ? existingSession.agentId - : getAgentId(); - if (inheritedAgentId) { - raw.agentId = inheritedAgentId; - } - - if (pendingImageData && (pendingImageData.startsWith("data:image/") || pendingImageData.startsWith("iVBORw0KGgo") || pendingImageData.startsWith("/9j/"))) { - const { saveImageToDisk } = await import("../utils/image-store.js"); - const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData); - raw.imageData = filePath; - const { incrementImageRef } = await import("./image-refs.js"); - await incrementImageRef(kv, filePath); - void sdk.trigger({ function_id: "mem::disk-size-delta", payload: { deltaBytes: bytesWritten }, action: TriggerAction.Void() }).catch(() => {}); - if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] === "true") { - void sdk.trigger({ - function_id: "mem::vision-embed", - payload: { - imageRef: filePath, - sessionId: payload.sessionId, - observationId: obsId, - }, - action: TriggerAction.Void(), - }).catch(() => {}); - } - } - - try { - - await kv.set(KV.observations(payload.sessionId), obsId, raw); - - } catch (error) { - if (raw.imageData) { - const { deleteImage } = await import("../utils/image-store.js"); - const { deletedBytes } = await deleteImage(raw.imageData); - if (deletedBytes > 0) { - void sdk.trigger({ function_id: "mem::disk-size-delta", payload: { deltaBytes: -deletedBytes }, action: TriggerAction.Void() }).catch(() => {}); - } - } - throw error; - } - - if (dedupMap && dedupHash) { - dedupMap.record(dedupHash); - } - - await sdk.trigger({ - function_id: "stream::set", - payload: { - stream_name: STREAM.name, - group_id: STREAM.group(payload.sessionId), - item_id: obsId, - data: { type: "raw", observation: raw }, - }, - }); - - await sdk.trigger({ - function_id: "stream::send", - payload: { - stream_name: STREAM.name, - group_id: STREAM.viewerGroup, - id: `raw-${obsId}`, - type: "raw_observation", - data: { type: "raw", observation: raw, sessionId: payload.sessionId }, - }, - action: TriggerAction.Void(), - }); - - const session = existingSession; - if (session) { - const updates: Array<{ type: "set"; path: string; value: unknown }> = [ - { type: "set", path: "updatedAt", value: new Date().toISOString() }, - { - type: "set", - path: "observationCount", - value: (session.observationCount || 0) + 1, - }, - ]; - if (!session.firstPrompt && typeof raw.userPrompt === "string") { - const trimmed = raw.userPrompt.replace(/\s+/g, " ").trim(); - if (trimmed.length > 0) { - updates.push({ - type: "set", - path: "firstPrompt", - value: trimmed.slice(0, 200), - }); - } - } - await kv.update(KV.sessions, payload.sessionId, updates); - } else if ( - typeof payload.project === "string" && - payload.project.trim().length > 0 && - typeof payload.cwd === "string" && - payload.cwd.trim().length > 0 - ) { - // OpenCode (and any plugin that skips POST /session/start) - // can fire observations before the session record exists. Without - // an implicit create, those observations stack up but - // `memory_sessions` never lists them, and summarize bails with - // "Session not found for summarize". Create the session now from - // the observation payload — but only when project + cwd are - // present (HookPayload contract). Older test payloads without - // those fields keep their original no-op behaviour. - const trimmedPrompt = - typeof raw.userPrompt === "string" - ? raw.userPrompt.replace(/\s+/g, " ").trim().slice(0, 200) - : undefined; - const ts = new Date().toISOString(); - await kv.set(KV.sessions, payload.sessionId, { - id: payload.sessionId, - project: payload.project, - cwd: payload.cwd, - startedAt: payload.timestamp ?? ts, - updatedAt: ts, - status: "active", - observationCount: 1, - ...(inheritedAgentId ? { agentId: inheritedAgentId } : {}), - ...(trimmedPrompt && trimmedPrompt.length > 0 - ? { firstPrompt: trimmedPrompt } - : {}), - }); - } - - // Per-observation LLM compression is opt-in as of 0.8.8 (#138). - // Default path: build a zero-LLM synthetic compression so recall - // and BM25 search still work without burning the user's Claude - // token allocation on every tool invocation. - if (isAutoCompressEnabled()) { - await sdk.trigger({ - function_id: "mem::compress", - payload: { - observationId: obsId, - sessionId: payload.sessionId, - raw, - }, - action: TriggerAction.Void(), - }); - } else { - const synthetic = buildSyntheticCompression(raw); - await kv.set( - KV.observations(payload.sessionId), - obsId, - synthetic, - ); - getSearchIndex().add(synthetic); - await vectorIndexAddGuarded( - synthetic.id, - synthetic.sessionId, - synthetic.title + " " + (synthetic.narrative || ""), - { kind: "synthetic", logId: synthetic.id }, - ); - await sdk.trigger({ - function_id: "stream::set", - payload: { - stream_name: STREAM.name, - group_id: STREAM.group(payload.sessionId), - item_id: obsId, - data: { type: "compressed", observation: synthetic }, - }, - }); - await sdk.trigger({ - function_id: "stream::set", - payload: { - stream_name: STREAM.name, - group_id: STREAM.viewerGroup, - item_id: obsId, - data: { - type: "compressed", - observation: synthetic, - sessionId: payload.sessionId, - }, - }, - }); - } - - logger.info("Observation captured", { - obsId, - sessionId: payload.sessionId, - hook: payload.hookType, - compress: isAutoCompressEnabled() ? "llm" : "synthetic", - }); - return { observationId: obsId }; - }); - }, - ); -} diff --git a/src/functions/obsidian-export.ts b/src/functions/obsidian-export.ts deleted file mode 100644 index c9a0d29f..00000000 --- a/src/functions/obsidian-export.ts +++ /dev/null @@ -1,342 +0,0 @@ -import { mkdir, writeFile } from "node:fs/promises"; -import { join, resolve, sep } from "node:path"; -import { homedir } from "node:os"; -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV } from "../state/schema.js"; -import type { - Memory, - Lesson, - Crystal, - Session, -} from "../types.js"; -import { recordAudit } from "./audit.js"; -const DEFAULT_EXPORT_ROOT = join(homedir(), ".agentmemory"); - -function getExportRoot(): string { - return resolve(process.env["AGENTMEMORY_EXPORT_ROOT"] || DEFAULT_EXPORT_ROOT); -} - -function resolveVaultDir(vaultDir?: string): string | null { - const root = getExportRoot(); - const resolved = resolve(vaultDir || join(root, "vault")); - if (resolved === root || resolved.startsWith(root + sep)) { - return resolved; - } - return null; -} - -function sanitize(name: string): string { - return name.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_").slice(0, 100); -} - -function toFrontmatter(obj: Record): string { - const lines = ["---"]; - for (const [key, value] of Object.entries(obj)) { - if (value === undefined || value === null) continue; - if (Array.isArray(value)) { - lines.push(`${key}: [${value.map((v) => JSON.stringify(String(v))).join(", ")}]`); - } else { - lines.push(`${key}: ${JSON.stringify(value)}`); - } - } - lines.push("---"); - return lines.join("\n"); -} - -function memoryToMd(m: Memory): string { - const fm = toFrontmatter({ - id: m.id, - type: m.type, - created: m.createdAt, - updated: m.updatedAt, - strength: m.strength, - version: m.version, - concepts: m.concepts, - files: m.files, - }); - - const related = (m.relatedIds || []) - .map((id) => `- [[${id}]]`) - .join("\n"); - const supersedes = (m.supersedes || []) - .map((id) => `- [[${id}]] (superseded)`) - .join("\n"); - - const sections = [ - fm, - "", - `# ${m.title}`, - "", - m.content, - ]; - - if (m.concepts.length > 0) { - sections.push("", "## Concepts", m.concepts.map((c) => `#${c.replace(/\s+/g, "-")}`).join(" ")); - } - if (related) { - sections.push("", "## Related", related); - } - if (supersedes) { - sections.push("", "## Supersedes", supersedes); - } - - return sections.join("\n"); -} - -function lessonToMd(l: Lesson): string { - const fm = toFrontmatter({ - id: l.id, - type: "lesson", - source: l.source, - confidence: l.confidence, - reinforcements: l.reinforcements, - created: l.createdAt, - updated: l.updatedAt, - project: l.project, - tags: l.tags, - decayRate: l.decayRate, - }); - - const sourceLinks = l.sourceIds - .map((id) => `- [[${id}]]`) - .join("\n"); - - const sections = [ - fm, - "", - `# Lesson: ${l.content.slice(0, 80)}`, - "", - l.content, - ]; - - if (l.context) { - sections.push("", "## Context", l.context); - } - if (l.tags.length > 0) { - sections.push("", "## Tags", l.tags.map((t) => `#${t.replace(/\s+/g, "-")}`).join(" ")); - } - if (sourceLinks) { - sections.push("", "## Sources", sourceLinks); - } - - return sections.join("\n"); -} - -function crystalToMd(c: Crystal): string { - const fm = toFrontmatter({ - id: c.id, - type: "crystal", - created: c.createdAt, - project: c.project, - sessionId: c.sessionId, - filesAffected: c.filesAffected, - }); - - const actionLinks = c.sourceActionIds - .map((id) => `- [[${id}]]`) - .join("\n"); - - const sections = [ - fm, - "", - `# Crystal: ${c.narrative.slice(0, 80)}`, - "", - c.narrative, - "", - "## Key Outcomes", - ...c.keyOutcomes.map((o) => `- ${o}`), - ]; - - if (c.lessons.length > 0) { - sections.push("", "## Lessons", ...c.lessons.map((l) => `- ${l}`)); - } - if (c.filesAffected.length > 0) { - sections.push("", "## Files", ...c.filesAffected.map((f) => `- \`${f}\``)); - } - if (actionLinks) { - sections.push("", "## Source Actions", actionLinks); - } - - return sections.join("\n"); -} - -function sessionToMd(s: Session): string { - const fm = toFrontmatter({ - id: s.id, - type: "session", - project: s.project, - status: s.status, - started: s.startedAt, - ended: s.endedAt, - observations: s.observationCount, - }); - - return [ - fm, - "", - `# Session: ${s.project}`, - "", - `**Status:** ${s.status}`, - `**Started:** ${s.startedAt}`, - s.endedAt ? `**Ended:** ${s.endedAt}` : "", - `**Observations:** ${s.observationCount}`, - `**CWD:** \`${s.cwd}\``, - ] - .filter(Boolean) - .join("\n"); -} - -interface ExportError { - id: string; - path: string; - error: string; -} - -export function registerObsidianExportFunction( - sdk: ISdk, - kv: StateKV, -): void { - sdk.registerFunction("mem::obsidian-export", - async (data: { vaultDir?: string; types?: string[] } | undefined) => { - if (!data || typeof data !== "object") { - return { success: false, error: "payload is required" }; - } - if (data.vaultDir !== undefined && typeof data.vaultDir !== "string") { - return { success: false, error: "vaultDir must be a string" }; - } - if (data.types !== undefined) { - if ( - !Array.isArray(data.types) || - !data.types.every((t): t is string => typeof t === "string") - ) { - return { success: false, error: "types must be an array of strings" }; - } - } - - const vaultDir = resolveVaultDir(data.vaultDir); - if (!vaultDir) { - return { - success: false, - error: `vaultDir must be inside ${getExportRoot()}`, - }; - } - const exportTypes = new Set( - data.types ?? ["memories", "lessons", "crystals", "sessions"], - ); - - const dirs = { - memories: join(vaultDir, "memories"), - lessons: join(vaultDir, "lessons"), - crystals: join(vaultDir, "crystals"), - sessions: join(vaultDir, "sessions"), - }; - - await Promise.all( - Object.values(dirs).map((dir) => mkdir(dir, { recursive: true })), - ); - - const stats = { memories: 0, lessons: 0, crystals: 0, sessions: 0 }; - const errors: ExportError[] = []; - const memoryMoc: string[] = []; - const lessonMoc: string[] = []; - const crystalMoc: string[] = []; - const sessionMoc: string[] = []; - - const [memories, lessons, crystals, sessions] = await Promise.all([ - exportTypes.has("memories") ? kv.list(KV.memories) : Promise.resolve([] as Memory[]), - exportTypes.has("lessons") ? kv.list(KV.lessons) : Promise.resolve([] as Lesson[]), - exportTypes.has("crystals") ? kv.list(KV.crystals) : Promise.resolve([] as Crystal[]), - exportTypes.has("sessions") ? kv.list(KV.sessions) : Promise.resolve([] as Session[]), - ]); - - for (const m of memories.filter((m) => m.isLatest)) { - const filename = `${sanitize(m.id)}.md`; - const filepath = join(dirs.memories, filename); - try { - await writeFile(filepath, memoryToMd(m)); - stats.memories++; - memoryMoc.push(`- [[memories/${sanitize(m.id)}|${m.title}]] (${m.type}, strength: ${m.strength})`); - } catch (err) { - errors.push({ id: m.id, path: filepath, error: err instanceof Error ? err.message : String(err) }); - } - } - - for (const l of lessons.filter((l) => !l.deleted)) { - const filename = `${sanitize(l.id)}.md`; - const filepath = join(dirs.lessons, filename); - try { - await writeFile(filepath, lessonToMd(l)); - stats.lessons++; - lessonMoc.push(`- [[lessons/${sanitize(l.id)}|${l.content.slice(0, 60)}]] (confidence: ${l.confidence})`); - } catch (err) { - errors.push({ id: l.id, path: filepath, error: err instanceof Error ? err.message : String(err) }); - } - } - - for (const c of crystals) { - const filename = `${sanitize(c.id)}.md`; - const filepath = join(dirs.crystals, filename); - try { - await writeFile(filepath, crystalToMd(c)); - stats.crystals++; - crystalMoc.push(`- [[crystals/${sanitize(c.id)}|${c.narrative.slice(0, 60)}]]`); - } catch (err) { - errors.push({ id: c.id, path: filepath, error: err instanceof Error ? err.message : String(err) }); - } - } - - const recent = sessions - .sort( - (a, b) => - new Date(b.startedAt).getTime() - - new Date(a.startedAt).getTime(), - ) - .slice(0, 50); - for (const s of recent) { - const filename = `${sanitize(s.id)}.md`; - const filepath = join(dirs.sessions, filename); - try { - await writeFile(filepath, sessionToMd(s)); - stats.sessions++; - sessionMoc.push(`- [[sessions/${sanitize(s.id)}|${s.project} (${s.status})]]`); - } catch (err) { - errors.push({ id: s.id, path: filepath, error: err instanceof Error ? err.message : String(err) }); - } - } - - const exportedAt = new Date().toISOString(); - const moc = [ - "---", - "type: moc", - `exported: ${exportedAt}`, - "---", - "", - "# agentmemory vault", - "", - `Exported: ${exportedAt}`, - "", - `## Memories (${stats.memories})`, - ...memoryMoc, - "", - `## Lessons (${stats.lessons})`, - ...lessonMoc, - "", - `## Crystals (${stats.crystals})`, - ...crystalMoc, - "", - `## Sessions (${stats.sessions})`, - ...sessionMoc, - ].join("\n"); - - await writeFile(join(vaultDir, "MOC.md"), moc); - - await recordAudit(kv, "obsidian_export", "mem::obsidian-export", [], { - vaultDir, - stats, - }); - - return { success: true, exported: stats, errors: errors.length > 0 ? errors : undefined, vaultDir }; - }, - ); -} diff --git a/src/functions/patterns.ts b/src/functions/patterns.ts deleted file mode 100644 index 8d1365fc..00000000 --- a/src/functions/patterns.ts +++ /dev/null @@ -1,134 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { CompressedObservation, Session } from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { logger } from "../logger.js"; - -interface Pattern { - type: "co_change" | "error_repeat" | "workflow"; - description: string; - files: string[]; - frequency: number; - sessions: string[]; -} - -export function registerPatternsFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::patterns", - async (data: { project?: string }) => { - const patterns: Pattern[] = []; - - const sessions = await kv.list(KV.sessions); - const filtered = data.project - ? sessions.filter((s) => s.project === data.project) - : sessions; - - const fileCoOccurrences = new Map(); - const fileSessionMap = new Map>(); - const errorPatterns = new Map< - string, - { count: number; sessions: Set } - >(); - - for (const session of filtered) { - const observations = await kv.list( - KV.observations(session.id), - ); - if (!observations.length) continue; - - const sessionFiles = new Set(); - for (const obs of observations) { - if (!obs.files) continue; - for (const f of obs.files) { - sessionFiles.add(f); - if (!fileSessionMap.has(f)) fileSessionMap.set(f, new Set()); - fileSessionMap.get(f)!.add(session.id); - } - - if (obs.type === "error" && obs.title) { - const key = obs.title.toLowerCase(); - if (!errorPatterns.has(key)) { - errorPatterns.set(key, { count: 0, sessions: new Set() }); - } - const ep = errorPatterns.get(key)!; - ep.count++; - ep.sessions.add(session.id); - } - } - - const fileList = [...sessionFiles].sort(); - for (let i = 0; i < fileList.length; i++) { - for (let j = i + 1; j < fileList.length; j++) { - const pair = `${fileList[i]}::${fileList[j]}`; - fileCoOccurrences.set(pair, (fileCoOccurrences.get(pair) || 0) + 1); - } - } - } - - for (const [pair, count] of fileCoOccurrences) { - if (count < 3) continue; - const [fileA, fileB] = pair.split("::"); - const sessionsA = fileSessionMap.get(fileA) || new Set(); - const sessionsB = fileSessionMap.get(fileB) || new Set(); - const commonSessions = [...sessionsA].filter((s) => sessionsB.has(s)); - - patterns.push({ - type: "co_change", - description: `${fileA} and ${fileB} are frequently modified together`, - files: [fileA, fileB], - frequency: count, - sessions: commonSessions, - }); - } - - for (const [ - errorKey, - { count, sessions: errorSessions }, - ] of errorPatterns) { - if (count < 2) continue; - patterns.push({ - type: "error_repeat", - description: `Recurring error: ${errorKey}`, - files: [], - frequency: count, - sessions: [...errorSessions], - }); - } - - patterns.sort((a, b) => b.frequency - a.frequency); - - logger.info("Pattern detection complete", { - patterns: patterns.length, - sessions: filtered.length, - }); - - return { patterns: patterns.slice(0, 20) }; - }, - ); - - sdk.registerFunction("mem::generate-rules", - async (data: { project?: string }) => { - const result = await sdk.trigger< - { project?: string }, - { patterns: Pattern[] } - >({ function_id: "mem::patterns", payload: data }); - - const rules: string[] = []; - - for (const pattern of result.patterns) { - if (pattern.type === "co_change" && pattern.frequency >= 4) { - rules.push( - `When modifying ${pattern.files[0]}, also check ${pattern.files[1]} (co-changed ${pattern.frequency} times).`, - ); - } - if (pattern.type === "error_repeat" && pattern.frequency >= 3) { - rules.push( - `Watch for: ${pattern.description} (occurred ${pattern.frequency} times across ${pattern.sessions.length} sessions).`, - ); - } - } - - logger.info("Rules generated", { count: rules.length }); - return { rules }; - }, - ); -} diff --git a/src/functions/privacy.ts b/src/functions/privacy.ts deleted file mode 100644 index f28edd3e..00000000 --- a/src/functions/privacy.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { ISdk } from "iii-sdk"; - -const PRIVATE_TAG_RE = /[\s\S]*?<\/private>/gi; - -const SECRET_PATTERN_SOURCES = [ - /(?:api[_-]?key|secret|token|password|credential|auth)[\s]*[=:]\s*["']?[A-Za-z0-9_\-/.+]{20,}["']?/gi, - /Bearer\s+[A-Za-z0-9._\-+/=]{20,}/gi, - /sk-proj-[A-Za-z0-9\-_]{20,}/g, - /(?:sk|pk|rk|ak)-[A-Za-z0-9][A-Za-z0-9\-_]{19,}/g, - /sk-ant-[A-Za-z0-9\-_]{20,}/g, - /gh[pus]_[A-Za-z0-9]{36,}/g, - /github_pat_[A-Za-z0-9_]{22,}/g, - /xoxb-[A-Za-z0-9\-]+/g, - /AKIA[0-9A-Z]{16}/g, - /AIza[A-Za-z0-9\-_]{35}/g, - /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, - /npm_[A-Za-z0-9]{36}/g, - /glpat-[A-Za-z0-9\-_]{20,}/g, - /dop_v1_[A-Za-z0-9]{64}/g, -]; - -export function stripPrivateData(input: string): string { - let result = input.replace(PRIVATE_TAG_RE, "[REDACTED]"); - for (const source of SECRET_PATTERN_SOURCES) { - const pattern = new RegExp(source.source, source.flags); - result = result.replace(pattern, "[REDACTED_SECRET]"); - } - return result; -} - -export function registerPrivacyFunction(sdk: ISdk): void { - sdk.registerFunction("mem::privacy", - async (data: { input?: unknown } | undefined) => { - if (!data || typeof data.input !== "string") { - return { output: "", error: "invalid input: expected string field 'input'" }; - } - return { output: stripPrivateData(data.input) }; - }, - ); -} diff --git a/src/functions/profile.ts b/src/functions/profile.ts deleted file mode 100644 index 07881e92..00000000 --- a/src/functions/profile.ts +++ /dev/null @@ -1,159 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - CompressedObservation, - Session, - ProjectProfile, -} from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; -import { logger } from "../logger.js"; - -export function registerProfileFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::profile", - async (data: { project: string; refresh?: boolean } | undefined) => { - if (!data || typeof data.project !== "string" || !data.project.trim()) { - return { success: false, error: "project is required" }; - } - const project = data.project.trim(); - - if (!data.refresh) { - const cached = await kv - .get(KV.profiles, project) - .catch(() => null); - if (cached) { - const age = Date.now() - new Date(cached.updatedAt).getTime(); - if (age < 3600_000) { - return { profile: cached, cached: true }; - } - } - } - - const sessions = await kv.list(KV.sessions); - const projectSessions = sessions.filter( - (s) => s.project === project, - ); - - if (projectSessions.length === 0) { - return { profile: null, reason: "no_sessions" }; - } - - const conceptFreq = new Map(); - const fileFreq = new Map(); - const errors: string[] = []; - const recentActivity: string[] = []; - let totalObs = 0; - - const sortedSessions = projectSessions.sort( - (a, b) => - new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime(), - ); - - const top20Sessions = sortedSessions.slice(0, 20); - const obsPerSession = await Promise.all( - top20Sessions.map((s) => - kv - .list(KV.observations(s.id)) - .catch(() => [] as CompressedObservation[]), - ), - ); - - for (let i = 0; i < top20Sessions.length; i++) { - const session = top20Sessions[i]; - const observations = obsPerSession[i]; - totalObs += observations.length; - - for (const obs of observations) { - for (const concept of obs.concepts || []) { - conceptFreq.set(concept, (conceptFreq.get(concept) || 0) + 1); - } - for (const file of obs.files || []) { - fileFreq.set(file, (fileFreq.get(file) || 0) + 1); - } - if (obs.type === "error") { - errors.push(obs.title); - } - } - - const important = observations - .filter((o) => o.importance >= 7) - .sort((a, b) => b.importance - a.importance); - if (important.length > 0) { - recentActivity.push( - `[${session.startedAt.slice(0, 10)}] ${important[0].title}`, - ); - } - } - - const topConcepts = Array.from(conceptFreq.entries()) - .sort((a, b) => b[1] - a[1]) - .slice(0, 15) - .map(([concept, frequency]) => ({ concept, frequency })); - - const topFiles = Array.from(fileFreq.entries()) - .sort((a, b) => b[1] - a[1]) - .slice(0, 15) - .map(([file, frequency]) => ({ file, frequency })); - - const uniqueErrors = [...new Set(errors)].slice(0, 10); - - const profile: ProjectProfile = { - project, - updatedAt: new Date().toISOString(), - topConcepts, - topFiles, - conventions: extractConventions(topConcepts, topFiles), - commonErrors: uniqueErrors, - recentActivity: recentActivity.slice(0, 10), - sessionCount: projectSessions.length, - totalObservations: totalObs, - }; - - await kv.set(KV.profiles, project, profile); - await recordAudit(kv, "share", "mem::profile", [project], { - sessionCount: projectSessions.length, - totalObservations: totalObs, - }); - - logger.info("Profile generated", { - project, - sessions: projectSessions.length, - observations: totalObs, - }); - return { profile, cached: false }; - }, - ); -} - -function extractConventions( - concepts: Array<{ concept: string; frequency: number }>, - files: Array<{ file: string; frequency: number }>, -): string[] { - const conventions: string[] = []; - - const tsFiles = files.filter((f) => f.file.endsWith(".ts")).length; - const jsFiles = files.filter((f) => f.file.endsWith(".js")).length; - if (tsFiles > jsFiles && tsFiles > 0) { - conventions.push("TypeScript project"); - } - - const srcFiles = files.filter((f) => f.file.includes("/src/")).length; - if (srcFiles > files.length * 0.5) { - conventions.push("Standard src/ directory structure"); - } - - const testFiles = files.filter( - (f) => f.file.includes("test") || f.file.includes("spec"), - ).length; - if (testFiles > 0) { - conventions.push("Has test files"); - } - - for (const { concept, frequency } of concepts.slice(0, 5)) { - if (frequency >= 3) { - conventions.push(`Frequently uses: ${concept}`); - } - } - - return conventions; -} diff --git a/src/functions/query-expansion.ts b/src/functions/query-expansion.ts deleted file mode 100644 index 00cfdf48..00000000 --- a/src/functions/query-expansion.ts +++ /dev/null @@ -1,188 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { MemoryProvider, QueryExpansion } from "../types.js"; -import { logger } from "../logger.js"; - -const QUERY_EXPANSION_SYSTEM = `You are a query expansion engine for a memory retrieval system. Given a user query, generate diverse reformulations to maximize recall. - -Output EXACTLY this XML: - - - semantically diverse rephrasing 1 - semantically diverse rephrasing 2 - semantically diverse rephrasing 3 - - - time-concretized version if applicable - - - extracted entity name 1 - extracted entity name 2 - - - -Rules: -- Generate 3-5 reformulations capturing different interpretations -- Include paraphrases, domain-specific restatements, and abstract/concrete variants -- Extract any named entities (people, files, projects, libraries, concepts) -- If the query mentions time ("last week", "recently"), generate temporal concretizations -- Each reformulation should capture a distinct facet of intent -- Keep reformulations concise (under 100 chars each)`; - -function parseExpansionXml(xml: string): QueryExpansion | null { - const reformulations: string[] = []; - const queryRegex = - /[\s\S]*?<\/reformulations>/; - const reformBlock = xml.match(queryRegex); - if (reformBlock) { - const qRegex = /([^<]+)<\/query>/g; - let match; - while ((match = qRegex.exec(reformBlock[0])) !== null) { - reformulations.push(match[1].trim()); - } - } - - const temporalConcretizations: string[] = []; - const tempBlock = xml.match(/[\s\S]*?<\/temporal>/); - if (tempBlock) { - const qRegex = /([^<]+)<\/query>/g; - let match; - while ((match = qRegex.exec(tempBlock[0])) !== null) { - temporalConcretizations.push(match[1].trim()); - } - } - - const entityExtractions: string[] = []; - const entityRegex = /([^<]+)<\/entity>/g; - let match; - while ((match = entityRegex.exec(xml)) !== null) { - entityExtractions.push(match[1].trim()); - } - - return { - original: "", - reformulations, - temporalConcretizations, - entityExtractions, - }; -} - -export function registerQueryExpansionFunction( - sdk: ISdk, - provider: MemoryProvider, -): void { - sdk.registerFunction("mem::expand-query", - async (data: { query: string; maxReformulations?: number } | undefined) => { - if (!data || typeof data.query !== "string" || !data.query.trim()) { - logger.warn("Invalid expand-query payload"); - return { success: false, error: "query must be a non-empty string" }; - } - const rawMaxR = Number(data.maxReformulations); - const maxR = Number.isFinite(rawMaxR) - ? Math.max(1, Math.min(10, Math.floor(rawMaxR))) - : 5; - const query = data.query.trim(); - - try { - const response = await provider.compress( - QUERY_EXPANSION_SYSTEM, - `Expand this query for memory retrieval:\n\n"${query}"`, - ); - - const parsed = parseExpansionXml(response); - if (!parsed) { - logger.warn("Failed to parse query expansion"); - return { - success: true, - expansion: { - original: query, - reformulations: [], - temporalConcretizations: [], - entityExtractions: [], - }, - }; - } - - parsed.original = query; - parsed.reformulations = parsed.reformulations.slice(0, maxR); - - logger.info("Query expanded", { - original: query, - reformulations: parsed.reformulations.length, - entities: parsed.entityExtractions.length, - }); - - return { success: true, expansion: parsed }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Query expansion failed", { error: msg }); - return { - success: true, - expansion: { - original: query, - reformulations: [], - temporalConcretizations: [], - entityExtractions: [], - }, - }; - } - }, - ); -} - -export function extractEntitiesFromQuery(query: string): string[] { - const entities: string[] = []; - const quoted = query.match(/"([^"]+)"/g); - if (quoted) { - for (const q of quoted) { - entities.push(q.replace(/"/g, "")); - } - } - const capitalized = query.match(/\b[A-Z][a-zA-Z0-9_.-]+\b/g); - if (capitalized) { - const stopWords = new Set([ - "The", - "This", - "That", - "What", - "When", - "Where", - "How", - "Why", - "Who", - "Which", - "Did", - "Does", - "Do", - "Is", - "Are", - "Was", - "Were", - "Has", - "Have", - "Had", - "Can", - "Could", - "Would", - "Should", - "Will", - "May", - "Might", - "If", - "And", - "But", - "Or", - "Not", - "For", - "From", - "With", - "About", - "After", - "Before", - "Between", - ]); - for (const c of capitalized) { - if (!stopWords.has(c)) entities.push(c); - } - } - return [...new Set(entities)]; -} diff --git a/src/functions/reflect.ts b/src/functions/reflect.ts deleted file mode 100644 index 3a2af829..00000000 --- a/src/functions/reflect.ts +++ /dev/null @@ -1,477 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, fingerprintId } from "../state/schema.js"; -import type { - Insight, - GraphNode, - GraphEdge, - SemanticMemory, - Lesson, - Crystal, - MemoryProvider, -} from "../types.js"; -import { recordAudit } from "./audit.js"; -import { REFLECT_SYSTEM, buildReflectPrompt } from "../prompts/reflect.js"; - -interface ConceptCluster { - concepts: string[]; - facts: Array<{ fact: string; confidence: number }>; - lessons: Array<{ content: string; confidence: number }>; - crystalNarratives: string[]; - factIds: string[]; - lessonIds: string[]; - crystalIds: string[]; -} - -function reinforceInsight(insight: Insight): void { - const now = new Date().toISOString(); - insight.reinforcements++; - insight.confidence = Math.min( - 1.0, - insight.confidence + 0.1 * (1 - insight.confidence), - ); - insight.lastReinforcedAt = now; - insight.updatedAt = now; -} - -function buildGraphClusters( - nodes: GraphNode[], - edges: GraphEdge[], - maxClusters: number, -): string[][] { - const conceptNodes = nodes.filter( - (n) => n.type === "concept" && !n.stale, - ); - if (conceptNodes.length === 0) return []; - - const edgeMap = new Map>(); - for (const edge of edges) { - if (edge.stale) continue; - if (!edgeMap.has(edge.sourceNodeId)) - edgeMap.set(edge.sourceNodeId, new Set()); - if (!edgeMap.has(edge.targetNodeId)) - edgeMap.set(edge.targetNodeId, new Set()); - edgeMap.get(edge.sourceNodeId)!.add(edge.targetNodeId); - edgeMap.get(edge.targetNodeId)!.add(edge.sourceNodeId); - } - - const degree = new Map(); - for (const node of conceptNodes) { - degree.set(node.id, edgeMap.get(node.id)?.size || 0); - } - - const sorted = [...conceptNodes].sort( - (a, b) => (degree.get(b.id) || 0) - (degree.get(a.id) || 0), - ); - - const visited = new Set(); - const clusters: string[][] = []; - const conceptNodeIds = new Set(conceptNodes.map((n) => n.id)); - - for (const seed of sorted) { - if (visited.has(seed.id) || clusters.length >= maxClusters) break; - - const cluster: string[] = []; - const queue = [seed.id]; - const seen = new Set(); - let depth = 0; - - while (queue.length > 0 && depth <= 2) { - const levelCount = queue.length; - for (let i = 0; i < levelCount; i++) { - const current = queue.shift()!; - if (seen.has(current)) continue; - seen.add(current); - - if (conceptNodeIds.has(current)) { - const node = conceptNodes.find((n) => n.id === current); - if (node) cluster.push(node.name); - visited.add(current); - } - - const neighbors = edgeMap.get(current) || new Set(); - for (const neighbor of neighbors) { - if (!seen.has(neighbor)) queue.push(neighbor); - } - } - depth++; - } - - if (cluster.length >= 2) clusters.push(cluster); - } - - return clusters; -} - -function buildJaccardClusters( - semanticMemories: SemanticMemory[], - lessons: Lesson[], - maxClusters: number, -): string[][] { - const allConcepts = new Map>(); - - for (const sem of semanticMemories) { - const terms = sem.fact.toLowerCase().split(/\s+/).filter((t) => t.length > 3); - for (const term of terms) { - if (!allConcepts.has(term)) allConcepts.set(term, new Set()); - allConcepts.get(term)!.add(sem.id); - } - } - for (const lesson of lessons) { - for (const tag of lesson.tags) { - const key = tag.toLowerCase(); - if (!allConcepts.has(key)) allConcepts.set(key, new Set()); - allConcepts.get(key)!.add(lesson.id); - } - } - - const conceptList = [...allConcepts.keys()].filter( - (k) => (allConcepts.get(k)?.size || 0) >= 2, - ); - - const visited = new Set(); - const clusters: string[][] = []; - - for (const concept of conceptList) { - if (visited.has(concept) || clusters.length >= maxClusters) break; - - const cluster = [concept]; - visited.add(concept); - - const docsA = allConcepts.get(concept) || new Set(); - for (const other of conceptList) { - if (visited.has(other)) continue; - const docsB = allConcepts.get(other) || new Set(); - let intersection = 0; - for (const d of docsA) { - if (docsB.has(d)) intersection++; - } - const union = docsA.size + docsB.size - intersection; - const similarity = union > 0 ? intersection / union : 0; - if (similarity > 0.3) { - cluster.push(other); - visited.add(other); - } - } - - if (cluster.length >= 2) clusters.push(cluster); - } - - return clusters; -} - -export function registerReflectFunctions( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, -): void { - sdk.registerFunction("mem::reflect", - async (data: { maxClusters?: number; project?: string }) => { - const maxClusters = Math.min(data?.maxClusters ?? 10, 20); - const maxInsightsPerCluster = 5; - const maxTotal = 50; - - const [graphNodes, graphEdges, semanticMemories, lessons, crystals] = - await Promise.all([ - kv.list(KV.graphNodes).catch(() => []), - kv.list(KV.graphEdges).catch(() => []), - kv.list(KV.semantic).catch(() => []), - kv.list(KV.lessons).catch(() => []), - kv.list(KV.crystals).catch(() => []), - ]); - - let activeLessons = lessons.filter((l) => !l.deleted); - if (data?.project) { - activeLessons = activeLessons.filter((l) => l.project === data.project); - } - - let conceptClusters = buildGraphClusters( - graphNodes, - graphEdges, - maxClusters, - ); - - const usedFallback = conceptClusters.length === 0; - if (usedFallback) { - conceptClusters = buildJaccardClusters( - semanticMemories, - activeLessons, - maxClusters, - ); - } - - let newInsights = 0; - let reinforced = 0; - let clustersSkipped = 0; - let totalInsights = 0; - - for (const conceptNames of conceptClusters) { - if (totalInsights >= maxTotal) break; - - const conceptSet = new Set(conceptNames.map((c) => c.toLowerCase())); - - const clusterFacts = semanticMemories.filter((s) => { - const factTerms = s.fact.toLowerCase().split(/\s+/); - return factTerms.some((t) => conceptSet.has(t)); - }); - - const clusterLessons = activeLessons.filter((l) => - l.tags.some((t) => conceptSet.has(t.toLowerCase())) || - conceptNames.some((c) => - l.content.toLowerCase().includes(c.toLowerCase()), - ), - ); - - const clusterCrystals = crystals.filter((c) => - (c.lessons || []).some((l) => - conceptNames.some((cn) => - l.toLowerCase().includes(cn.toLowerCase()), - ), - ), - ); - - const totalItems = - clusterFacts.length + clusterLessons.length + clusterCrystals.length; - if (totalItems < 3) { - clustersSkipped++; - continue; - } - - const cluster: ConceptCluster = { - concepts: conceptNames, - facts: clusterFacts.map((f) => ({ - fact: f.fact, - confidence: f.confidence, - })), - lessons: clusterLessons.map((l) => ({ - content: l.content, - confidence: l.confidence, - })), - crystalNarratives: clusterCrystals.map((c) => c.narrative), - factIds: clusterFacts.map((f) => f.id), - lessonIds: clusterLessons.map((l) => l.id), - crystalIds: clusterCrystals.map((c) => c.id), - }; - - try { - const prompt = buildReflectPrompt(cluster); - const response = await provider.summarize(REFLECT_SYSTEM, prompt); - - const insightRegex = - /([\s\S]*?)<\/insight>/g; - let match; - let clusterCount = 0; - - while ( - (match = insightRegex.exec(response)) !== null && - clusterCount < maxInsightsPerCluster && - totalInsights < maxTotal - ) { - const parsedConf = parseFloat(match[1]); - const confidence = Number.isNaN(parsedConf) - ? 0.5 - : Math.max(0, Math.min(1, parsedConf)); - const title = match[2].trim(); - const content = match[3].trim(); - - if (!content) continue; - - const fp = fingerprintId("ins", content.trim().toLowerCase()); - const existing = await kv.get(KV.insights, fp); - - if (existing && !existing.deleted) { - reinforceInsight(existing); - await kv.set(KV.insights, existing.id, existing); - reinforced++; - } else { - const now = new Date().toISOString(); - const insight: Insight = { - id: fp, - title, - content, - confidence, - reinforcements: 0, - sourceConceptCluster: conceptNames, - sourceMemoryIds: cluster.factIds, - sourceLessonIds: cluster.lessonIds, - sourceCrystalIds: cluster.crystalIds, - project: data?.project, - tags: conceptNames, - createdAt: now, - updatedAt: now, - decayRate: 0.05, - }; - await kv.set(KV.insights, insight.id, insight); - newInsights++; - } - - clusterCount++; - totalInsights++; - } - } catch { - continue; - } - } - - try { - await recordAudit(kv, "reflect", "mem::reflect", [], { - newInsights, - reinforced, - clustersProcessed: conceptClusters.length - clustersSkipped, - clustersSkipped, - usedFallback, - }); - } catch {} - - return { - success: true, - newInsights, - reinforced, - clustersProcessed: conceptClusters.length - clustersSkipped, - clustersSkipped, - usedFallback, - }; - }, - ); - - sdk.registerFunction("mem::insight-list", - async (data: { - project?: string; - minConfidence?: number; - limit?: number; - }) => { - const limit = data?.limit ?? 50; - const minConfidence = data?.minConfidence ?? 0; - let items = await kv.list(KV.insights); - - items = items.filter( - (i) => !i.deleted && i.confidence >= minConfidence, - ); - - if (data?.project) { - items = items.filter((i) => i.project === data.project); - } - - items.sort((a, b) => b.confidence - a.confidence); - - return { success: true, insights: items.slice(0, limit) }; - }, - ); - - sdk.registerFunction("mem::insight-search", - async (data: { - query: string; - project?: string; - minConfidence?: number; - limit?: number; - }) => { - if (!data?.query?.trim()) { - return { success: false, error: "query is required" }; - } - - const query = data.query.toLowerCase(); - const minConfidence = data.minConfidence ?? 0.1; - const limit = data.limit ?? 10; - - let items = await kv.list(KV.insights); - items = items.filter( - (i) => !i.deleted && i.confidence >= minConfidence, - ); - - if (data.project) { - items = items.filter((i) => i.project === data.project); - } - - const terms = query.split(/\s+/).filter((t) => t.length > 1); - const scored = items - .map((i) => { - const text = - `${i.title} ${i.content} ${i.tags.join(" ")}`.toLowerCase(); - const matchCount = terms.filter((t) => text.includes(t)).length; - if (matchCount === 0) return null; - - const relevance = matchCount / terms.length; - const daysSince = i.lastReinforcedAt - ? (Date.now() - new Date(i.lastReinforcedAt).getTime()) / - (1000 * 60 * 60 * 24) - : (Date.now() - new Date(i.createdAt).getTime()) / - (1000 * 60 * 60 * 24); - const recencyBoost = 1 / (1 + daysSince * 0.01); - const score = i.confidence * relevance * recencyBoost; - - return { insight: i, score }; - }) - .filter(Boolean) as Array<{ insight: Insight; score: number }>; - - scored.sort((a, b) => b.score - a.score); - - try { - await recordAudit(kv, "insight_search", "mem::insight-search", [], { - query: data.query, - resultCount: scored.length, - }); - } catch {} - - return { - success: true, - insights: scored.slice(0, limit).map((s) => ({ - ...s.insight, - score: Math.round(s.score * 1000) / 1000, - })), - }; - }, - ); - - sdk.registerFunction("mem::insight-decay-sweep", - async () => { - const items = await kv.list(KV.insights); - let decayed = 0; - let softDeleted = 0; - const now = Date.now(); - const timestamp = new Date().toISOString(); - const dirty: Insight[] = []; - - for (const insight of items) { - if (insight.deleted) continue; - - const baseline = - insight.lastDecayedAt || - insight.lastReinforcedAt || - insight.createdAt; - const weeksSince = - (now - new Date(baseline).getTime()) / (1000 * 60 * 60 * 24 * 7); - - if (weeksSince < 1) continue; - - const decay = insight.decayRate * weeksSince; - const newConfidence = Math.max(0.05, insight.confidence - decay); - - if (newConfidence !== insight.confidence) { - insight.confidence = Math.round(newConfidence * 1000) / 1000; - insight.lastDecayedAt = timestamp; - insight.updatedAt = timestamp; - - if (insight.confidence <= 0.1 && insight.reinforcements === 0) { - insight.deleted = true; - softDeleted++; - } else { - decayed++; - } - - dirty.push(insight); - } - } - - await Promise.all(dirty.map((i) => kv.set(KV.insights, i.id, i))); - await recordAudit(kv, "reflect", "mem::insight-decay-sweep", dirty.map((i) => i.id), { - event: "insight.decay", - decayed, - softDeleted, - total: items.length, - timestamp, - }); - - return { success: true, decayed, softDeleted, total: items.length }; - }, - ); -} diff --git a/src/functions/relations.ts b/src/functions/relations.ts deleted file mode 100644 index 7921a328..00000000 --- a/src/functions/relations.ts +++ /dev/null @@ -1,278 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { Memory, MemoryRelation } from "../types.js"; -import { KV, generateId } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import { safeAudit } from "./audit.js"; -import { recordAccessBatch } from "./access-tracker.js"; -import { logger } from "../logger.js"; - -function computeConfidence( - source: Memory, - target: Memory, - relationType: MemoryRelation["type"], -): number { - let score = 0.5; - - const sharedSessions = source.sessionIds.filter((sid) => - target.sessionIds.includes(sid), - ); - score += Math.min(sharedSessions.length * 0.1, 0.3); - - const now = Date.now(); - const sourceAge = now - new Date(source.updatedAt).getTime(); - const targetAge = now - new Date(target.updatedAt).getTime(); - const sevenDays = 7 * 24 * 60 * 60 * 1000; - const ninetyDays = 90 * 24 * 60 * 60 * 1000; - if (sourceAge < sevenDays && targetAge < sevenDays) { - score += 0.1; - } else if (sourceAge > ninetyDays && targetAge > ninetyDays) { - score -= 0.1; - } - - if (relationType === "supersedes") score += 0.1; - if (relationType === "contradicts") score -= 0.05; - - return Math.max(0, Math.min(1, score)); -} - -export function registerRelationsFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::relate", - async (data: { - sourceId: string; - targetId: string; - type: MemoryRelation["type"]; - confidence?: number; - }) => { - const [firstId, secondId] = [data.sourceId, data.targetId].sort(); - const lockKey = - firstId === secondId ? `mem:${firstId}` : `mem:${firstId}:${secondId}`; - - return withKeyedLock(lockKey, async () => { - const source = await kv.get(KV.memories, data.sourceId); - const target = await kv.get(KV.memories, data.targetId); - if (!source || !target) { - return { - success: false, - error: "source or target memory not found", - }; - } - - const confidence = - data.confidence !== undefined - ? Math.max(0, Math.min(1, data.confidence)) - : computeConfidence(source, target, data.type); - - const relation: MemoryRelation = { - type: data.type, - sourceId: data.sourceId, - targetId: data.targetId, - createdAt: new Date().toISOString(), - confidence, - }; - - const relationId = generateId("rel"); - await kv.set(KV.relations, relationId, relation); - - if (!source.relatedIds) source.relatedIds = []; - let sourceUpdated = false; - if (!source.relatedIds.includes(data.targetId)) { - source.relatedIds.push(data.targetId); - await kv.set(KV.memories, data.sourceId, source); - sourceUpdated = true; - } - - if (!target.relatedIds) target.relatedIds = []; - let targetUpdated = false; - if (!target.relatedIds.includes(data.sourceId)) { - target.relatedIds.push(data.sourceId); - await kv.set(KV.memories, data.targetId, target); - targetUpdated = true; - } - - await safeAudit(kv, "relation_create", "mem::relate", [relationId], { - type: data.type, - sourceId: data.sourceId, - targetId: data.targetId, - confidence, - }); - if (sourceUpdated) { - await safeAudit( - kv, - "relation_update", - "mem::relate", - [data.sourceId], - { relationId, updatedRelatedId: data.targetId }, - ); - } - if (targetUpdated) { - await safeAudit( - kv, - "relation_update", - "mem::relate", - [data.targetId], - { relationId, updatedRelatedId: data.sourceId }, - ); - } - - logger.info("Memory relation created", { - relationId, - type: data.type, - source: data.sourceId, - target: data.targetId, - }); - return { success: true, relationId, relation }; - }); - }, - ); - - sdk.registerFunction("mem::evolve", - async (data: { - memoryId: string; - newContent: string; - newTitle?: string; - }) => { - - const existing = await kv.get(KV.memories, data.memoryId); - if (!existing) { - return { success: false, error: "memory not found" }; - } - - const now = new Date().toISOString(); - const evolved: Memory = { - ...existing, - id: generateId("mem"), - createdAt: now, - updatedAt: now, - title: data.newTitle || existing.title, - content: data.newContent, - version: (existing.version || 1) + 1, - parentId: existing.id, - supersedes: [existing.id, ...(existing.supersedes || [])], - isLatest: true, - }; - - existing.isLatest = false; - await kv.set(KV.memories, existing.id, existing); - await safeAudit(kv, "evolve", "mem::evolve", [existing.id], { - operation: "evolve", - action: "mark_non_latest", - newId: evolved.id, - }); - - await kv.set(KV.memories, evolved.id, evolved); - await safeAudit(kv, "evolve", "mem::evolve", [evolved.id], { - operation: "evolve", - oldId: existing.id, - newId: evolved.id, - version: evolved.version, - }); - - const relation: MemoryRelation = { - type: "supersedes", - sourceId: evolved.id, - targetId: existing.id, - createdAt: now, - confidence: 1.0, - }; - const relationId = generateId("rel"); - await kv.set(KV.relations, relationId, relation); - await safeAudit(kv, "evolve", "mem::evolve", [relationId], { - operation: "supersedes", - oldId: existing.id, - newId: evolved.id, - }); - - logger.info("Memory evolved", { - oldId: existing.id, - newId: evolved.id, - version: evolved.version, - }); - return { success: true, memory: evolved, previousId: existing.id }; - }, - ); - - sdk.registerFunction("mem::get-related", - async (data: { - memoryId: string; - maxHops?: number; - minConfidence?: number; - }) => { - const maxHops = Math.min(data.maxHops ?? 2, 5); - const MAX_VISITED = 500; - const rawMinConf = Number(data.minConfidence); - const minConfidence = Number.isFinite(rawMinConf) - ? Math.max(0, Math.min(1, rawMinConf)) - : 0; - - const allRelations = await kv - .list(KV.relations) - .catch(() => []); - - const visited = new Set(); - const result: Array<{ - memory: Memory; - hop: number; - confidence: number; - }> = []; - const queue: Array<{ id: string; hop: number }> = [ - { id: data.memoryId, hop: 0 }, - ]; - - while (queue.length > 0 && visited.size < MAX_VISITED) { - const current = queue.shift()!; - if (visited.has(current.id) || current.hop > maxHops) continue; - visited.add(current.id); - - const memory = await kv.get(KV.memories, current.id); - if (!memory) continue; - - if (current.hop > 0) { - const matchingRelations = allRelations.filter( - (r) => - (r.sourceId === current.id && visited.has(r.targetId)) || - (r.targetId === current.id && visited.has(r.sourceId)), - ); - const confidence = - matchingRelations.length > 0 - ? Math.max(...matchingRelations.map((r) => r.confidence ?? 0.5)) - : 0.5; - if (confidence >= minConfidence) { - result.push({ memory, hop: current.hop, confidence }); - } - } - - const relatedIds = memory.relatedIds || []; - const supersedes = memory.supersedes || []; - const parentId = memory.parentId ? [memory.parentId] : []; - - const kvLinked = allRelations - .filter((r) => r.sourceId === current.id || r.targetId === current.id) - .map((r) => (r.sourceId === current.id ? r.targetId : r.sourceId)); - - const allLinks = [ - ...new Set([...relatedIds, ...supersedes, ...parentId, ...kvLinked]), - ]; - - for (const nextId of allLinks) { - if (!visited.has(nextId)) { - queue.push({ id: nextId, hop: current.hop + 1 }); - } - } - } - - result.sort((a, b) => b.confidence - a.confidence); - - void recordAccessBatch( - kv, - result.map((r) => r.memory.id), - ); - - logger.info("Related memories retrieved", { - memoryId: data.memoryId, - found: result.length, - }); - return { results: result }; - }, - ); -} diff --git a/src/functions/remember.ts b/src/functions/remember.ts deleted file mode 100644 index 5735b4f2..00000000 --- a/src/functions/remember.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { TriggerAction, type ISdk } from "iii-sdk"; -import type { Memory } from "../types.js"; -import { KV, generateId, jaccardSimilarity } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import { memoryToObservation } from "../state/memory-utils.js"; -import { deleteAccessLog } from "./access-tracker.js"; -import { recordAudit } from "./audit.js"; -import { getSearchIndex, vectorIndexAddGuarded, vectorIndexRemove, flushIndexSave } from "./search.js"; -import { getAgentId } from "../config.js"; -import { logger } from "../logger.js"; - -export function registerRememberFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::remember", - async (data: { - content: string; - type?: string; - concepts?: string[]; - files?: string[]; - ttlDays?: number; - sourceObservationIds?: string[]; - agentId?: string; - project?: string; - }) => { - if ( - !data.content || - typeof data.content !== "string" || - !data.content.trim() - ) { - return { success: false, error: "content is required" }; - } - if (data.files && !Array.isArray(data.files)) { - return { success: false, error: "files must be an array" }; - } - if (data.concepts && !Array.isArray(data.concepts)) { - return { success: false, error: "concepts must be an array" }; - } - if (data.sourceObservationIds && !Array.isArray(data.sourceObservationIds)) { - return { success: false, error: "sourceObservationIds must be an array" }; - } - const validTypes = new Set([ - "pattern", - "preference", - "architecture", - "bug", - "workflow", - "fact", - ]); - const memType = validTypes.has(data.type || "") - ? (data.type as Memory["type"]) - : "fact"; - - const now = new Date().toISOString(); - // Normalize project early so every subsequent comparison and storage - // operation uses the same cleaned value. Raw data.project must not be - // referenced below this point. - const project = - typeof data.project === "string" && data.project.trim().length > 0 - ? data.project.trim() - : undefined; - - return withKeyedLock("mem:remember", async () => { - const existingMemories = await kv.list(KV.memories); - let supersededId: string | undefined; - let supersededVersion = 1; - let supersededMemory: Memory | undefined; - const lowerContent = data.content.toLowerCase(); - for (const existing of existingMemories) { - if (existing.isLatest === false) continue; - // Never supersede a memory that belongs to a different project. - // Both sides must have an explicit project for the guard to engage; - // an unscoped memory (legacy, no project field) is treated as a - // wildcard so pre-existing data is not stranded. - if (project && existing.project && existing.project !== project) { - continue; - } - const similarity = jaccardSimilarity( - lowerContent, - existing.content.toLowerCase(), - ); - if (similarity > 0.7) { - supersededId = existing.id; - supersededVersion = existing.version ?? 1; - supersededMemory = existing; - break; - } - } - - // stamp the agent role on the memory so future recall can - // filter by agent. Request body wins (multi-agent runtimes - // explicitly tagging at write time), env AGENT_ID fallback, - // none → memory is unscoped (legacy behavior). - const callAgentId = - typeof data.agentId === "string" && data.agentId.trim().length > 0 - ? data.agentId.trim().slice(0, 128) - : getAgentId(); - - const memory: Memory = { - id: generateId("mem"), - createdAt: now, - updatedAt: now, - type: memType, - title: data.content.slice(0, 80), - content: data.content, - concepts: data.concepts || [], - files: data.files || [], - sessionIds: [], - strength: 7, - version: supersededId ? supersededVersion + 1 : 1, - parentId: supersededId, - supersedes: supersededId ? [supersededId] : [], - sourceObservationIds: (data.sourceObservationIds || []).filter( - (id): id is string => typeof id === "string" && id.length > 0, - ), - isLatest: true, - ...(callAgentId ? { agentId: callAgentId } : {}), - ...(project !== undefined && { project }), - }; - - if (data.ttlDays && typeof data.ttlDays === "number" && data.ttlDays > 0) { - memory.forgetAfter = new Date(Date.now() + data.ttlDays * 86400000).toISOString(); - } - - if (supersededMemory) { - supersededMemory.isLatest = false; - await kv.set(KV.memories, supersededMemory.id, supersededMemory); - } - await kv.set(KV.memories, memory.id, memory); - - // Without this, mem::remember persists the row but the BM25 - // index never sees it, so memory_smart_search and memory_recall - // return empty even seconds after save (#257). Use try/catch so - // an indexing failure doesn't block the save itself — the - // restart-time rebuild will pick the memory up either way. - try { - getSearchIndex().add(memoryToObservation(memory)); - } catch (err) { - logger.warn("Failed to index saved memory into BM25", { - memId: memory.id, - error: err instanceof Error ? err.message : String(err), - }); - } - await vectorIndexAddGuarded( - memory.id, - memory.sessionIds?.[0] ?? "memory", - memory.title + " " + memory.content, - { kind: "memory", logId: memory.id }, - ); - - if (supersededId) { - await sdk.trigger({ - function_id: "mem::cascade-update", - payload: { - supersededMemoryId: supersededId, - }, - action: TriggerAction.Void(), - }); - } - - logger.info("Memory saved", { - memId: memory.id, - type: memory.type, - project: memory.project, - }); - return { success: true, memory }; - }); - }, - ); - - sdk.registerFunction("mem::forget", - async (data: { - sessionId?: string; - observationIds?: string[]; - memoryId?: string; - }) => { - let deleted = 0; - const deletedMemoryIds: string[] = []; - const deletedObservationIds: string[] = []; - let deletedSession = false; - const { decrementImageRef } = await import("./image-refs.js"); - - if (data.memoryId) { - const mem = await kv.get(KV.memories, data.memoryId); - await kv.delete(KV.memories, data.memoryId); - if (mem?.imageRef) { - await decrementImageRef(kv, sdk, mem.imageRef); - } - await deleteAccessLog(kv, data.memoryId); - getSearchIndex().remove(data.memoryId); - vectorIndexRemove(data.memoryId); - deletedMemoryIds.push(data.memoryId); - deleted++; - } - - if ( - data.sessionId && - data.observationIds && - data.observationIds.length > 0 - ) { - for (const obsId of data.observationIds) { - const obs = await kv.get<{ imageData?: string; imageRef?: string }>( - KV.observations(data.sessionId), - obsId, - ); - await kv.delete(KV.observations(data.sessionId), obsId); - if (obs?.imageData) await decrementImageRef(kv, sdk, obs.imageData); - if (obs?.imageRef && obs.imageRef !== obs.imageData) { - await decrementImageRef(kv, sdk, obs.imageRef); - } - getSearchIndex().remove(obsId); - vectorIndexRemove(obsId); - deletedObservationIds.push(obsId); - deleted++; - } - } - - if ( - data.sessionId && - (!data.observationIds || data.observationIds.length === 0) && - !data.memoryId - ) { - const observations = await kv.list<{ id: string; imageData?: string; imageRef?: string }>( - KV.observations(data.sessionId), - ); - for (const obs of observations) { - await kv.delete(KV.observations(data.sessionId), obs.id); - if (obs.imageData) await decrementImageRef(kv, sdk, obs.imageData); - if (obs.imageRef && obs.imageRef !== obs.imageData) { - await decrementImageRef(kv, sdk, obs.imageRef); - } - getSearchIndex().remove(obs.id); - vectorIndexRemove(obs.id); - deletedObservationIds.push(obs.id); - deleted++; - } - await kv.delete(KV.sessions, data.sessionId); - await kv.delete(KV.summaries, data.sessionId); - deletedSession = true; - deleted += 2; - } - - if (deleted > 0) { - await flushIndexSave(); - await recordAudit( - kv, - "forget", - "mem::forget", - [...deletedMemoryIds, ...deletedObservationIds], - { - sessionId: data.sessionId, - deleted, - memoriesDeleted: deletedMemoryIds.length, - observationsDeleted: deletedObservationIds.length, - sessionDeleted: deletedSession, - reason: "user-initiated forget", - }, - ); - } - - logger.info("Memory forgotten", { deleted }); - return { success: true, deleted }; - }, - ); -} diff --git a/src/functions/replay.ts b/src/functions/replay.ts deleted file mode 100644 index 2ee5a863..00000000 --- a/src/functions/replay.ts +++ /dev/null @@ -1,494 +0,0 @@ -import { homedir } from "node:os"; -import { lstat, readFile, readdir } from "node:fs/promises"; -import { resolve, join } from "node:path"; -import type { ISdk } from "iii-sdk"; -import type { - CompressedObservation, - Crystal, - Lesson, - RawObservation, - Session, -} from "../types.js"; -import type { StateKV } from "../state/kv.js"; -import { KV, fingerprintId } from "../state/schema.js"; -import { parseJsonlText } from "../replay/jsonl-parser.js"; -import { projectTimeline, type Timeline } from "../replay/timeline.js"; -import { safeAudit } from "./audit.js"; -import { buildSyntheticCompression } from "./compress-synthetic.js"; -import { getSearchIndex } from "./search.js"; -import { logger } from "../logger.js"; - -export const MAX_FILES_DEFAULT = 200; -export const MAX_FILES_UPPER_BOUND = 1000; - -const SENSITIVE_PATH_PATTERNS: RegExp[] = [ - /(^|[\\/_.-])secret([\\/_.-]|s?$)/i, - /(^|[\\/_.-])credentials?([\\/_.-]|$)/i, - /(^|[\\/_.-])private[_-]?key([\\/_.-]|$)/i, - /(^|[\\/])\.env(\.[\w-]+)?$/i, - /(^|[\\/_.-])id_rsa([\\/_.-]|$)/i, - /(^|[\\/])auth[_-]?token([\\/_.-]|$)/i, - /(^|[\\/])bearer[_-]?token([\\/_.-]|$)/i, - /(^|[\\/])access[_-]?token([\\/_.-]|$)/i, - /(^|[\\/])api[_-]?token([\\/_.-]|$)/i, -]; - -export function isSensitive(path: string): boolean { - return SENSITIVE_PATH_PATTERNS.some((re) => re.test(path)); -} - -export async function isSymlink(path: string): Promise { - try { - const st = await lstat(path); - return st.isSymbolicLink(); - } catch { - return false; - } -} - -function rawFromCompressed(obs: CompressedObservation): RawObservation { - return { - id: obs.id, - sessionId: obs.sessionId, - timestamp: obs.timestamp, - hookType: "post_tool_use", - toolName: undefined, - toolInput: undefined, - toolOutput: undefined, - userPrompt: obs.type === "conversation" ? obs.narrative : undefined, - assistantResponse: undefined, - raw: { title: obs.title, narrative: obs.narrative, facts: obs.facts }, - }; -} - -const LESSON_PATTERNS: RegExp[] = [ - /\b(always|never|don'?t|do not|make sure|remember to|note:|caveat:|warning:)\b[^.\n]{10,200}[.!\n]/gi, - /\b(prefer|avoid)\s[^.\n]{10,200}[.!\n]/gi, -]; - -async function deriveCrystalAndLessons( - kv: StateKV, - sessionId: string, - project: string, - rawObs: RawObservation[], - compressed: CompressedObservation[], - firstPrompt: string | undefined, -): Promise { - if (rawObs.length === 0) return; - const createdAt = new Date().toISOString(); - - const files = new Set(); - const tools = new Set(); - for (const c of compressed) { - for (const f of c.files || []) files.add(f); - if (c.type && c.type !== "conversation" && c.title) tools.add(c.title); - } - - const assistantTexts: string[] = []; - const userPrompts: string[] = []; - for (const r of rawObs) { - if (typeof r.assistantResponse === "string" && r.assistantResponse.trim()) { - assistantTexts.push(r.assistantResponse); - } - if (typeof r.userPrompt === "string" && r.userPrompt.trim()) { - userPrompts.push(r.userPrompt); - } - } - - const lessonMatches = new Map(); - for (const text of assistantTexts.concat(userPrompts).slice(0, 200)) { - for (const pat of LESSON_PATTERNS) { - pat.lastIndex = 0; - let m: RegExpExecArray | null; - while ((m = pat.exec(text)) !== null && lessonMatches.size < 40) { - const snippet = m[0].replace(/\s+/g, " ").trim(); - if (snippet.length >= 20 && snippet.length <= 220) { - const key = snippet.toLowerCase(); - if (!lessonMatches.has(key)) lessonMatches.set(key, snippet); - } - } - } - } - - const lessonEntries = Array.from(lessonMatches.values()).slice(0, 20); - const lessonIds: string[] = []; - for (const content of lessonEntries) { - // Content-addressed ID so re-importing the same JSONL does not - // duplicate lessons. fingerprintId hashes the normalized content, - // giving a stable lesson_xxx for identical text. - const lessonId = fingerprintId("lesson", content.trim().toLowerCase()); - try { - const existing = await kv.get(KV.lessons, lessonId); - if (existing) { - const existingSources = existing.sourceIds || []; - const mergedSources = existingSources.includes(sessionId) - ? existingSources - : [...existingSources, sessionId]; - const existingTags = existing.tags || []; - const mergedTags = existingTags.includes("auto-import") - ? existingTags - : [...existingTags, "auto-import"]; - const merged: Lesson = { - ...existing, - sourceIds: mergedSources, - tags: mergedTags, - reinforcements: (existing.reinforcements || 0) + 1, - updatedAt: createdAt, - lastReinforcedAt: createdAt, - }; - await kv.set(KV.lessons, lessonId, merged); - } else { - const lesson: Lesson = { - id: lessonId, - content, - context: firstPrompt || project, - confidence: 0.4, - reinforcements: 0, - source: "consolidation", - sourceIds: [sessionId], - project, - tags: ["auto-import"], - createdAt, - updatedAt: createdAt, - decayRate: 0.05, - }; - await kv.set(KV.lessons, lessonId, lesson); - } - lessonIds.push(lessonId); - } catch {} - } - - // Content-addressed on sessionId so re-importing the same session - // upserts the crystal in place instead of creating a new one. - const crystalId = fingerprintId("crystal", sessionId); - const narrativePreview = firstPrompt - ? firstPrompt.slice(0, 300) - : compressed - .slice(0, 5) - .map((c) => c.narrative || c.title) - .filter(Boolean) - .join(" · ") - .slice(0, 300); - - try { - const existingCrystal = await kv.get(KV.crystals, crystalId); - const crystal: Crystal = { - id: crystalId, - narrative: narrativePreview || `Session ${sessionId.slice(0, 12)} (${rawObs.length} observations)`, - keyOutcomes: Array.from(tools).slice(0, 8), - filesAffected: Array.from(files).slice(0, 20), - lessons: lessonIds, - sourceActionIds: existingCrystal?.sourceActionIds ?? [], - sessionId, - project, - createdAt: existingCrystal?.createdAt ?? createdAt, - }; - await kv.set(KV.crystals, crystalId, crystal); - } catch {} -} - -function isRawShape(o: unknown): o is RawObservation { - if (!o || typeof o !== "object") return false; - const r = o as Record; - return typeof r.hookType === "string"; -} - -async function loadObservations( - kv: StateKV, - sessionId: string, -): Promise { - const rows = await kv.list( - KV.observations(sessionId), - ); - return rows.map((r) => (isRawShape(r) ? r : rawFromCompressed(r as CompressedObservation))); -} - -export async function findJsonlFiles( - root: string, - limit = 200, -): Promise<{ - files: string[]; - truncated: boolean; - discovered: number; - traversalCapped: boolean; -}> { - const out: string[] = []; - let discovered = 0; - let walked = 0; - // Hard bound on entries visited (regardless of extension) so trees - // dominated by non-jsonl files (node_modules, lockfiles, etc.) cannot - // lock the 30s function timeout. `discovered` may underrepresent the - // true count when traversalCapped fires — callers should surface that - // distinction to the user. - const traversalCap = Math.max(limit * 50, 50_000); - async function walk(dir: string) { - if (walked >= traversalCap) return; - let names: string[]; - try { - names = await readdir(dir); - } catch { - return; - } - for (const name of names) { - if (walked >= traversalCap) return; - walked++; - const full = join(dir, name); - let st; - try { - st = await lstat(full); - } catch { - continue; - } - if (st.isSymbolicLink()) continue; - if (st.isDirectory()) { - await walk(full); - } else if (st.isFile() && name.endsWith(".jsonl")) { - discovered++; - if (out.length < limit) out.push(full); - } - } - } - await walk(root); - const traversalCapped = walked >= traversalCap; - return { - files: out, - truncated: discovered > out.length || traversalCapped, - discovered, - traversalCapped, - }; -} - -/** - * Ingest a single JSONL transcript file: parse, dedup-write observations, - * upsert the Session, and derive crystals/lessons. Returns the session id and - * the count of newly written observations, or null when the file is skipped - * (unreadable, or no observations). Extracted from import-jsonl so the source - * scanner reuses the exact same ingest + dedup path (one source of truth). - */ -export async function ingestJsonlFile( - kv: StateKV, - file: string, - fallbackSessionId?: string, -): Promise<{ sessionId: string; newObservations: number } | null> { - let text: string; - try { - text = await readFile(file, "utf-8"); - } catch (err) { - logger.warn("replay: failed to read jsonl", { - file, - error: err instanceof Error ? err.message : String(err), - }); - return null; - } - - const parsed = parseJsonlText(text, fallbackSessionId ?? fingerprintId("sess", text)); - if (parsed.observations.length === 0) return null; - - const firstPromptObs = parsed.observations.find( - (o) => typeof o.userPrompt === "string" && o.userPrompt.trim().length > 0, - ); - const firstPrompt = firstPromptObs?.userPrompt - ? firstPromptObs.userPrompt.replace(/\s+/g, " ").trim().slice(0, 200) - : undefined; - - const searchIndex = getSearchIndex(); - const compressed: CompressedObservation[] = []; - const newRawObservations: RawObservation[] = []; - await Promise.all( - parsed.observations.map(async (obs) => { - const existingObs = await kv.get( - KV.observations(parsed.sessionId), - obs.id, - ); - if (existingObs) return; - const synthetic = buildSyntheticCompression(obs); - compressed.push(synthetic); - newRawObservations.push(obs); - await kv.set(KV.observations(parsed.sessionId), obs.id, synthetic); - searchIndex.add(synthetic); - }), - ); - const storedObservations = await kv.list( - KV.observations(parsed.sessionId), - ); - const totalObservationCount = storedObservations.length; - const existing = await kv.get(KV.sessions, parsed.sessionId); - if (existing) { - const existingTags = existing.tags || []; - const session: Session = { - ...existing, - endedAt: parsed.endedAt > (existing.endedAt || "") ? parsed.endedAt : existing.endedAt, - status: existing.status === "active" ? "completed" : existing.status, - observationCount: totalObservationCount, - tags: existingTags.includes("jsonl-import") - ? existingTags - : [...existingTags, "jsonl-import"], - firstPrompt: existing.firstPrompt || firstPrompt, - }; - await kv.set(KV.sessions, session.id, session); - } else { - const session: Session = { - id: parsed.sessionId, - project: parsed.project, - cwd: parsed.cwd, - startedAt: parsed.startedAt, - endedAt: parsed.endedAt, - status: "completed", - observationCount: totalObservationCount, - tags: ["jsonl-import"], - firstPrompt, - }; - await kv.set(KV.sessions, session.id, session); - } - - await deriveCrystalAndLessons( - kv, - parsed.sessionId, - parsed.project, - newRawObservations, - compressed, - firstPrompt, - ); - - return { sessionId: parsed.sessionId, newObservations: newRawObservations.length }; -} - -export function registerReplayFunctions(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction( - "mem::replay::load", - async (data: { sessionId: string }): Promise< - | { success: true; timeline: Timeline; session: Session | null } - | { success: false; error: string } - > => { - if (!data?.sessionId || typeof data.sessionId !== "string") { - return { success: false, error: "sessionId is required" }; - } - const session = await kv.get(KV.sessions, data.sessionId); - const observations = await loadObservations(kv, data.sessionId); - const timeline = projectTimeline(observations); - return { success: true, timeline, session }; - }, - ); - - sdk.registerFunction( - "mem::replay::sessions", - async (): Promise<{ success: true; sessions: Session[] }> => { - const sessions = await kv.list(KV.sessions); - sessions.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || "")); - return { success: true, sessions }; - }, - ); - - sdk.registerFunction( - "mem::replay::import-jsonl", - async ( - data: { path?: string; maxFiles?: number } = {}, - ): Promise< - | { - success: true; - imported: number; - sessionIds: string[]; - observations: number; - discovered: number; - truncated: boolean; - traversalCapped: boolean; - maxFiles: number; - maxFilesUpperBound: number; - } - | { success: false; error: string } - > => { - const defaultRoot = join(homedir(), ".claude", "projects"); - const rawPath = data.path || defaultRoot; - if (typeof rawPath !== "string" || rawPath.length === 0) { - return { success: false, error: "path must be a non-empty string" }; - } - const expanded = rawPath.startsWith("~") - ? join(homedir(), rawPath.slice(1)) - : rawPath; - const abs = resolve(expanded); - if (isSensitive(abs)) { - return { success: false, error: "refusing to process sensitive-looking path" }; - } - if (await isSymlink(abs)) { - return { success: false, error: "symlinks are not supported" }; - } - - let stat; - try { - stat = await lstat(abs); - } catch { - return { success: false, error: "path not found" }; - } - - // Valid integer requests are clamped to MAX_FILES_UPPER_BOUND so - // callers see a stable maxFiles in the response. Non-integer or - // <= 0 falls back to the safe default. The HTTP layer rejects - // out-of-range up front; this is the SDK-callable safety net. - const maxFiles = - Number.isInteger(data.maxFiles) && (data.maxFiles as number) > 0 - ? Math.min(data.maxFiles as number, MAX_FILES_UPPER_BOUND) - : MAX_FILES_DEFAULT; - let files: string[] = []; - let truncated = false; - let discovered = 0; - let traversalCapped = false; - if (stat.isDirectory()) { - const found = await findJsonlFiles(abs, maxFiles); - files = found.files; - truncated = found.truncated; - discovered = found.discovered; - traversalCapped = found.traversalCapped; - } else if (stat.isFile() && abs.endsWith(".jsonl")) { - files = [abs]; - discovered = 1; - } else { - return { success: false, error: "path must be a .jsonl file or directory" }; - } - - if (files.length === 0) { - return { - success: true, - imported: 0, - sessionIds: [], - observations: 0, - discovered, - truncated, - traversalCapped, - maxFiles, - maxFilesUpperBound: MAX_FILES_UPPER_BOUND, - }; - } - - const sessionIds: string[] = []; - let observationCount = 0; - - for (const file of files) { - if (isSensitive(file)) continue; - if (await isSymlink(file)) continue; - const ingested = await ingestJsonlFile(kv, file); - if (!ingested) continue; - observationCount += ingested.newObservations; - sessionIds.push(ingested.sessionId); - } - - await safeAudit(kv, "import", "mem::replay::import-jsonl", sessionIds, { - source: "jsonl", - path: abs, - files: files.length, - observations: observationCount, - }); - - return { - success: true, - imported: files.length, - sessionIds, - observations: observationCount, - discovered, - truncated, - traversalCapped, - maxFiles, - maxFilesUpperBound: MAX_FILES_UPPER_BOUND, - }; - }, - ); -} diff --git a/src/functions/retention.ts b/src/functions/retention.ts deleted file mode 100644 index fd3e6466..00000000 --- a/src/functions/retention.ts +++ /dev/null @@ -1,412 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - Memory, - SemanticMemory, - RetentionScore, - DecayConfig, -} from "../types.js"; -import { KV } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import type { AccessLog } from "./access-tracker.js"; -import { - emptyAccessLog, - deleteAccessLog, - normalizeAccessLog, -} from "./access-tracker.js"; -import { recordAudit } from "./audit.js"; -import { getSearchIndex, vectorIndexRemove, flushIndexSave } from "./search.js"; -import { logger } from "../logger.js"; - -const DEFAULT_DECAY: DecayConfig = { - lambda: 0.01, - sigma: 0.3, - tierThresholds: { - hot: 0.7, - warm: 0.4, - cold: 0.15, - }, -}; - -function resolveDecayConfig( - input?: Partial, -): { config: DecayConfig } | { error: string } { - const tierThresholds = { - ...DEFAULT_DECAY.tierThresholds, - ...(input?.tierThresholds ?? {}), - }; - const config: DecayConfig = { - lambda: - typeof input?.lambda === "number" ? input.lambda : DEFAULT_DECAY.lambda, - sigma: typeof input?.sigma === "number" ? input.sigma : DEFAULT_DECAY.sigma, - tierThresholds, - }; - - if (!Number.isFinite(config.lambda) || config.lambda <= 0) { - return { error: "config.lambda must be a positive number" }; - } - if (!Number.isFinite(config.sigma) || config.sigma < 0) { - return { error: "config.sigma must be a non-negative number" }; - } - const { hot, warm, cold } = config.tierThresholds; - if (![hot, warm, cold].every((v) => Number.isFinite(v))) { - return { - error: "config.tierThresholds.hot/warm/cold must be finite numbers", - }; - } - if (!(hot >= warm && warm >= cold && cold >= 0)) { - return { - error: - "config.tierThresholds must satisfy hot >= warm >= cold >= 0", - }; - } - return { config }; -} - -function computeReinforcementBoost( - accessTimestamps: number[], - sigma: number, -): number { - const now = Date.now(); - let boost = 0; - for (const tAccess of accessTimestamps) { - if (!Number.isFinite(tAccess)) continue; - const daysSinceAccess = (now - tAccess) / (1000 * 60 * 60 * 24); - if (daysSinceAccess > 0) { - boost += 1 / daysSinceAccess; - } - } - return boost * sigma; -} - -function computeRetention( - salience: number, - createdAt: string, - accessTimestamps: number[], - config: DecayConfig, -): number { - const deltaT = - (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60 * 24); - const temporalDecay = Math.exp(-config.lambda * deltaT); - const reinforcementBoost = computeReinforcementBoost( - accessTimestamps, - config.sigma, - ); - return Math.min(1, salience * temporalDecay + reinforcementBoost); -} - -function computeSalience( - memory: Memory | SemanticMemory, - accessCount: number, -): number { - let baseSalience = 0.5; - - if ("type" in memory) { - const typeWeights: Record = { - architecture: 0.9, - bug: 0.7, - pattern: 0.8, - preference: 0.85, - workflow: 0.6, - fact: 0.5, - }; - baseSalience = typeWeights[(memory as Memory).type] || 0.5; - } - - if ("confidence" in memory) { - baseSalience = Math.max(baseSalience, (memory as SemanticMemory).confidence); - } - - const accessBonus = Math.min(0.2, accessCount * 0.02); - return Math.min(1, baseSalience + accessBonus); -} - -export function registerRetentionFunctions( - sdk: ISdk, - kv: StateKV, -): void { - sdk.registerFunction("mem::retention-score", - async (data: { config?: Partial }) => { - const resolved = resolveDecayConfig(data?.config); - if ("error" in resolved) { - return { success: false, error: resolved.error }; - } - const { config } = resolved; - - const [memories, semanticMems, allLogs] = await Promise.all([ - kv.list(KV.memories), - kv.list(KV.semantic), - kv.list(KV.accessLog).catch(() => [] as unknown[]), - ]); - const logsById = new Map(); - for (const raw of allLogs) { - const log = normalizeAccessLog(raw); - if (log.memoryId) logsById.set(log.memoryId, log); - } - - const scores: RetentionScore[] = []; - - const computeDecay = (createdAt: string): number => - Math.exp( - -config.lambda * - ((Date.now() - new Date(createdAt).getTime()) / - (1000 * 60 * 60 * 24)), - ); - - // Build all entries in memory first, then flush with Promise.all - // so a full rescore is one batched KV write instead of N sequential - // round-trips. Separate counts for the audit record at the end. - const pendingWrites: Array<[string, RetentionScore]> = []; - let episodicScored = 0; - let semanticScored = 0; - - for (const mem of memories) { - if (!mem.isLatest) continue; - const log = logsById.get(mem.id) ?? emptyAccessLog(mem.id); - const salience = computeSalience(mem, log.count); - const temporalDecay = computeDecay(mem.createdAt); - const reinforcementBoost = computeReinforcementBoost( - log.recent, - config.sigma, - ); - const score = Math.min( - 1, - salience * temporalDecay + reinforcementBoost, - ); - - const entry: RetentionScore = { - memoryId: mem.id, - source: "episodic", - score, - salience, - temporalDecay, - reinforcementBoost, - lastAccessed: log.lastAt || mem.updatedAt, - accessCount: log.count, - }; - - scores.push(entry); - pendingWrites.push([mem.id, entry]); - episodicScored++; - } - - for (const sem of semanticMems) { - const log = logsById.get(sem.id) ?? emptyAccessLog(sem.id); - - // Pre-0.8.3 fallback: use sem.lastAccessedAt only when mem:access is empty. - let accessTimestamps: number[]; - let effectiveCount: number; - if (log.recent.length > 0 || log.count > 0) { - accessTimestamps = log.recent; - effectiveCount = log.count; - } else if (sem.lastAccessedAt) { - const legacyTs = Date.parse(sem.lastAccessedAt); - accessTimestamps = Number.isFinite(legacyTs) ? [legacyTs] : []; - effectiveCount = sem.accessCount; - } else { - accessTimestamps = []; - effectiveCount = sem.accessCount; - } - - const salience = computeSalience(sem, effectiveCount); - const temporalDecay = computeDecay(sem.createdAt); - const reinforcementBoost = computeReinforcementBoost( - accessTimestamps, - config.sigma, - ); - const score = Math.min( - 1, - salience * temporalDecay + reinforcementBoost, - ); - - const entry: RetentionScore = { - memoryId: sem.id, - source: "semantic", - score, - salience, - temporalDecay, - reinforcementBoost, - lastAccessed: log.lastAt || sem.lastAccessedAt, - accessCount: effectiveCount, - }; - - scores.push(entry); - pendingWrites.push([sem.id, entry]); - semanticScored++; - } - - // Flush all retention rows in parallel. N sequential writes was - // making full rescores O(n) round-trips on stores with 1000+ - // memories; batching drops that to O(1) wall time on the KV - // backends that can pipeline. - await Promise.all( - pendingWrites.map(([id, entry]) => - kv.set(KV.retentionScores, id, entry), - ), - ); - - scores.sort((a, b) => b.score - a.score); - - const tiers = { - hot: scores.filter((s) => s.score >= config.tierThresholds.hot) - .length, - warm: scores.filter( - (s) => - s.score >= config.tierThresholds.warm && - s.score < config.tierThresholds.hot, - ).length, - cold: scores.filter( - (s) => - s.score >= config.tierThresholds.cold && - s.score < config.tierThresholds.warm, - ).length, - evictable: scores.filter( - (s) => s.score < config.tierThresholds.cold, - ).length, - }; - - logger.info("Retention scores computed", { - total: scores.length, - ...tiers, - }); - - // Audit the rescore as a single batched event per sweep. We - // intentionally pass an empty targetIds array — a mature store - // can have 1000+ memory ids per rescore and flooding the audit - // log with every memoryId on every cron tick is worse than - // recording just the summary. The details payload has enough - // context for observability (counts per source + per tier). - if (scores.length > 0) { - await recordAudit(kv, "retention_score", "mem::retention-score", [], { - total: scores.length, - episodic: episodicScored, - semantic: semanticScored, - tiers, - config, - }); - } - - return { success: true, total: scores.length, tiers, scores }; - }, - ); - - sdk.registerFunction("mem::retention-evict", - async (data?: { - threshold?: number; - dryRun?: boolean; - maxEvict?: number; - }) => { - const threshold = - typeof data?.threshold === "number" && Number.isFinite(data.threshold) - ? data.threshold - : DEFAULT_DECAY.tierThresholds.cold; - const maxEvictRaw = - typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) - ? data.maxEvict - : 50; - const maxEvict = Math.min(1000, Math.max(0, maxEvictRaw)); - const { decrementImageRef } = await import("./image-refs.js"); - - const allScores = await kv.list(KV.retentionScores); - const candidates = allScores - .filter((s) => s.score < threshold) - .sort((a, b) => a.score - b.score) - .slice(0, maxEvict); - - if (data?.dryRun) { - return { - success: true, - dryRun: true, - wouldEvict: candidates.length, - candidates: candidates.map((c) => ({ - id: c.memoryId, - score: c.score, - })), - }; - } - - // Branch on source (#124). Pre-0.8.10 rows have no `source` field, - // and that includes semantic retention rows that were written by - // the old scorer — so we can't just default to episodic, that - // would silently no-op the delete and leave the stranded semantic - // memory alive (the exact bug #124 is about). When `source` is - // missing, probe both namespaces to find where the memoryId - // actually lives and route the delete there. After one re-score - // (mem::retention-score) every row will have the correct tag. - let evicted = 0; - let evictedEpisodic = 0; - let evictedSemantic = 0; - const evictedIds: string[] = []; - for (const candidate of candidates) { - try { - let scope: string | null = null; - let resolvedSource: "episodic" | "semantic" | null = null; - if (candidate.source === "semantic") { - scope = KV.semantic; - resolvedSource = "semantic"; - } else if (candidate.source === "episodic") { - scope = KV.memories; - resolvedSource = "episodic"; - } else { - const episodic = await kv.get(KV.memories, candidate.memoryId); - if (episodic !== null) { - scope = KV.memories; - resolvedSource = "episodic"; - } else { - const semantic = await kv.get(KV.semantic, candidate.memoryId); - if (semantic !== null) { - scope = KV.semantic; - resolvedSource = "semantic"; - } - } - } - - if (!scope || !resolvedSource) { - continue; - } - - const mem = await kv.get(scope, candidate.memoryId); - if (mem && mem.imageRef) { - await decrementImageRef(kv, sdk, mem.imageRef); - } - await kv.delete(scope, candidate.memoryId); - await kv.delete(KV.retentionScores, candidate.memoryId); - await deleteAccessLog(kv, candidate.memoryId); - getSearchIndex().remove(candidate.memoryId); - vectorIndexRemove(candidate.memoryId); - evicted++; - evictedIds.push(candidate.memoryId); - if (resolvedSource === "semantic") evictedSemantic++; - else evictedEpisodic++; - } catch { - continue; - } - } - - // Retention eviction is a structural delete path that removes - // memories, retention scores, and access logs, so it needs to - // emit an audit record per the repo's audit-coverage policy (see - // mem::governance-delete for the reference pattern). Batched, - // one record per invocation — per-candidate audits would flood - // the audit log during normal eviction sweeps. - if (evicted > 0) { - await flushIndexSave(); - await recordAudit(kv, "delete", "mem::retention-evict", evictedIds, { - threshold, - evicted, - evictedEpisodic, - evictedSemantic, - reason: "retention score below threshold", - }); - } - - logger.info("Retention-based eviction complete", { - evicted, - evictedEpisodic, - evictedSemantic, - threshold, - }); - - return { success: true, evicted, evictedEpisodic, evictedSemantic }; - }, - ); -} diff --git a/src/functions/routines.ts b/src/functions/routines.ts deleted file mode 100644 index d1a9135a..00000000 --- a/src/functions/routines.ts +++ /dev/null @@ -1,303 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import type { Action, Routine, RoutineStep, RoutineRun } from "../types.js"; -import { recordAudit } from "./audit.js"; - -export function registerRoutinesFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::routine-create", - async (data: { - name: string; - description?: string; - steps: RoutineStep[]; - tags?: string[]; - frozen?: boolean; - sourceProceduralIds?: string[]; - }) => { - if (!data.name || !Array.isArray(data.steps) || data.steps.length === 0) { - return { success: false, error: "name and steps are required" }; - } - - for (let i = 0; i < data.steps.length; i++) { - if (!data.steps[i].title?.trim()) { - return { success: false, error: `step ${i} must have a title` }; - } - } - - const orders = data.steps.map((s, i) => s.order ?? i); - const uniqueOrders = new Set(orders); - if (uniqueOrders.size !== orders.length) { - return { success: false, error: "duplicate step orders" }; - } - for (const step of data.steps) { - if (step.dependsOn) { - for (const dep of step.dependsOn) { - if (!uniqueOrders.has(dep)) { - return { success: false, error: `step ${step.order ?? data.steps.indexOf(step)} depends on unknown order ${dep}` }; - } - } - } - } - - const now = new Date().toISOString(); - const routine: Routine = { - id: generateId("rtn"), - name: data.name.trim(), - description: (data.description || "").trim(), - steps: data.steps.map((s, i) => ({ - order: s.order ?? i, - title: s.title, - description: s.description || "", - actionTemplate: s.actionTemplate || {}, - dependsOn: s.dependsOn || [], - })), - createdAt: now, - updatedAt: now, - frozen: data.frozen ?? true, - tags: data.tags || [], - sourceProceduralIds: data.sourceProceduralIds || [], - }; - - await kv.set(KV.routines, routine.id, routine); - await recordAudit(kv, "routine_run", "mem::routine-create", [routine.id], { - action: "routine.create", - stepCount: routine.steps.length, - }); - return { success: true, routine }; - }, - ); - - sdk.registerFunction("mem::routine-list", - async (data: { frozen?: boolean; tags?: string[] }) => { - let routines = await kv.list(KV.routines); - if (data.frozen !== undefined) { - routines = routines.filter((r) => r.frozen === data.frozen); - } - if (data.tags && data.tags.length > 0) { - routines = routines.filter((r) => - data.tags!.some((t) => r.tags.includes(t)), - ); - } - routines.sort( - (a, b) => - new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(), - ); - return { success: true, routines }; - }, - ); - - sdk.registerFunction("mem::routine-run", - async (data: { - routineId: string; - initiatedBy?: string; - project?: string; - overrides?: Record>; - }) => { - if (!data.routineId) { - return { success: false, error: "routineId is required" }; - } - - return withKeyedLock(`mem:routine:${data.routineId}`, async () => { - const routine = await kv.get(KV.routines, data.routineId); - if (!routine) { - return { success: false, error: "routine not found" }; - } - - const now = new Date().toISOString(); - const stepOrderToActionId = new Map(); - const actionIds: string[] = []; - const stepStatus: Record = {}; - - for (const step of routine.steps) { - const template = step.actionTemplate || {}; - const override = data.overrides?.[step.order] || {}; - - const hasDeps = (step.dependsOn || []).length > 0; - const action: Action = { - id: generateId("act"), - title: override.title || template.title || step.title, - description: - override.description || - template.description || - step.description, - status: hasDeps ? "blocked" : "pending", - priority: - override.priority ?? template.priority ?? 5, - createdAt: now, - updatedAt: now, - createdBy: data.initiatedBy || "routine", - project: data.project || template.project, - tags: [ - ...(template.tags || []), - ...(override.tags || []), - `routine:${routine.id}`, - ], - sourceObservationIds: [], - sourceMemoryIds: [], - metadata: { routineId: routine.id, stepOrder: step.order }, - }; - - await kv.set(KV.actions, action.id, action); - stepOrderToActionId.set(step.order, action.id); - actionIds.push(action.id); - stepStatus[step.order] = "pending"; - } - - for (const step of routine.steps) { - const actionId = stepOrderToActionId.get(step.order); - if (!actionId) continue; - - for (const depOrder of step.dependsOn) { - const depActionId = stepOrderToActionId.get(depOrder); - if (!depActionId) continue; - const edge = { - id: generateId("ae"), - type: "requires" as const, - sourceActionId: actionId, - targetActionId: depActionId, - createdAt: now, - }; - await kv.set(KV.actionEdges, edge.id, edge); - } - } - - const run: RoutineRun = { - id: generateId("run"), - routineId: routine.id, - status: "running", - startedAt: now, - actionIds, - stepStatus, - initiatedBy: data.initiatedBy || "unknown", - }; - - await kv.set(KV.routineRuns, run.id, run); - await recordAudit(kv, "routine_run", "mem::routine-run", [run.id], { - action: "routine.run", - routineId: routine.id, - actionIds, - initiatedBy: data.initiatedBy || "unknown", - }); - - return { - success: true, - run, - actionsCreated: actionIds.length, - }; - }); - }, - ); - - sdk.registerFunction("mem::routine-status", - async (data: { runId: string }) => { - if (!data.runId) { - return { success: false, error: "runId is required" }; - } - - const run = await kv.get(KV.routineRuns, data.runId); - if (!run) { - return { success: false, error: "run not found" }; - } - - const actionStates: Array<{ - actionId: string; - status: string; - title: string; - }> = []; - let allDone = true; - let anyFailed = false; - - let statusChanged = false; - for (const actionId of run.actionIds) { - const action = await kv.get(KV.actions, actionId); - if (action) { - actionStates.push({ - actionId: action.id, - status: action.status, - title: action.title, - }); - if (action.status !== "done") allDone = false; - if (action.status === "cancelled") anyFailed = true; - - const stepOrder = (action.metadata as { stepOrder?: number })?.stepOrder; - if (stepOrder !== undefined && stepOrder in run.stepStatus) { - let mapped: "pending" | "active" | "done" | "failed"; - if (action.status === "cancelled") { - mapped = "failed"; - } else if (action.status === "blocked") { - mapped = "pending"; - } else { - mapped = action.status as "pending" | "active" | "done"; - } - if (run.stepStatus[stepOrder] !== mapped) { - run.stepStatus[stepOrder] = mapped; - statusChanged = true; - } - } - } else { - actionStates.push({ - actionId, - status: "cancelled", - title: "(missing)", - }); - allDone = false; - anyFailed = true; - } - } - - if (allDone && run.status === "running") { - run.status = "completed"; - run.completedAt = new Date().toISOString(); - statusChanged = true; - } else if (anyFailed && run.status === "running") { - run.status = "failed"; - statusChanged = true; - } - - if (statusChanged) { - await kv.set(KV.routineRuns, run.id, run); - await recordAudit(kv, "routine_run", "mem::routine-status", [run.id], { - action: "routine.status", - status: run.status, - }); - } - - return { - success: true, - run, - actions: actionStates, - progress: { - total: run.actionIds.length, - done: actionStates.filter((a) => a.status === "done").length, - active: actionStates.filter((a) => a.status === "active").length, - pending: actionStates.filter((a) => a.status === "pending").length, - blocked: actionStates.filter((a) => a.status === "blocked").length, - cancelled: actionStates.filter((a) => a.status === "cancelled").length, - }, - }; - }, - ); - - sdk.registerFunction("mem::routine-freeze", - async (data: { routineId: string }) => { - if (!data.routineId) { - return { success: false, error: "routineId is required" }; - } - return withKeyedLock(`mem:routine:${data.routineId}`, async () => { - const routine = await kv.get(KV.routines, data.routineId); - if (!routine) { - return { success: false, error: "routine not found" }; - } - routine.frozen = true; - routine.updatedAt = new Date().toISOString(); - await kv.set(KV.routines, routine.id, routine); - await recordAudit(kv, "routine_run", "mem::routine-freeze", [routine.id], { - action: "routine.freeze", - frozen: true, - }); - return { success: true, routine }; - }); - }, - ); -} diff --git a/src/functions/search.ts b/src/functions/search.ts deleted file mode 100644 index 655ea93d..00000000 --- a/src/functions/search.ts +++ /dev/null @@ -1,546 +0,0 @@ -import type { ISdk } from 'iii-sdk' -import type { CompactSearchResult, CompressedObservation, Memory, SearchResult, Session } from '../types.js' -import { KV } from '../state/schema.js' -import { StateKV } from '../state/kv.js' -import { SearchIndex } from '../state/search-index.js' -import { VectorIndex } from '../state/vector-index.js' -import type { EmbeddingProvider } from '../types.js' -import { memoryToObservation } from '../state/memory-utils.js' -import { recordAccessBatch } from './access-tracker.js' -import { logger } from "../logger.js"; - -let index: SearchIndex | null = null -let vectorIndex: VectorIndex | null = null -let currentEmbeddingProvider: EmbeddingProvider | null = null - -export function getSearchIndex(): SearchIndex { - if (!index) index = new SearchIndex() - return index -} - -export function setVectorIndex(idx: VectorIndex | null): void { - vectorIndex = idx -} - -export function getVectorIndex(): VectorIndex | null { - return vectorIndex -} - -export function setEmbeddingProvider(provider: EmbeddingProvider | null): void { - currentEmbeddingProvider = provider -} - -export function getEmbeddingProvider(): EmbeddingProvider | null { - return currentEmbeddingProvider -} - -export function vectorIndexRemove(id: string): void { - vectorIndex?.remove(id); -} - -// Persistence sync hook. Without this, index removals only live in -// memory; a crash/SIGKILL before graceful shutdown reloads a stale -// snapshot at boot and the deleted entry resurrects in the index. -// Wired by src/index.ts after IndexPersistence is constructed; no-op -// until then so unit tests that exercise the delete paths in -// isolation don't need to wire persistence. -let indexPersistence: { - scheduleSave: () => void; - save: () => Promise; -} | null = null; - -export function setIndexPersistence( - p: { scheduleSave: () => void; save: () => Promise } | null, -): void { - indexPersistence = p; -} - -export function scheduleIndexSave(): void { - indexPersistence?.scheduleSave(); -} - -// Synchronous flush variant for delete paths. The debounced -// scheduleSave is fine for adds (chatty), but a hard process exit -// inside the 5s debounce window would lose deletes and resurrect -// removed entries on next boot. Deletes are infrequent enough that -// awaiting a single write per operation is acceptable. save() catches -// its own errors via IndexPersistence.logFailure, so this resolves -// even when persistence fails — callers must not treat a failed -// flush as a fatal error on the delete itself (the KV delete already -// committed before this is invoked). -export async function flushIndexSave(): Promise { - await indexPersistence?.save(); -} - -// Hard cap on embedding input length. Most providers cap input around -// 8k tokens (~32k chars at ~4 chars/token). Truncate defensively so a -// huge memory.content can't 400 the embed call or blow context budget -// on a single doc. 16k chars ≈ 4k tokens, safely under every provider. -const EMBED_MAX_CHARS = 16_000 - -export function clipEmbedInput(text: string): string { - if (text.length <= EMBED_MAX_CHARS) return text - return text.slice(0, EMBED_MAX_CHARS) -} - -// Single guarded vector-index write. Returns true on success. Logs and -// no-ops on: -// - dimension mismatch (mis-configured provider would silently corrupt -// the index per #248 otherwise — guarded at persistence load there; -// this is the symmetric guard at the write site) -// - embed throwing (network, rate limit, provider down) -// Always soft-fails so a downed embedder doesn't break the upstream save. -export async function vectorIndexAddGuarded( - id: string, - sessionId: string, - text: string, - context: { kind: "memory" | "observation" | "synthetic"; logId: string }, -): Promise { - const vi = vectorIndex - const ep = currentEmbeddingProvider - if (!vi || !ep) return false - try { - const embedding = await ep.embed(clipEmbedInput(text)) - if (embedding.length !== ep.dimensions) { - logger.warn("vector-index add: dimension mismatch — skipping", { - kind: context.kind, - id: context.logId, - provider: ep.name, - expected: ep.dimensions, - received: embedding.length, - }) - return false - } - vi.add(id, sessionId, embedding) - return true - } catch (err) { - logger.warn("vector-index add: embed failed — skipping", { - kind: context.kind, - id: context.logId, - provider: ep.name, - error: err instanceof Error ? err.message : String(err), - }) - return false - } -} - -// Batched variant: calls EmbeddingProvider.embedBatch ONCE for the whole -// batch, then writes each resulting vector. Use this for bulk paths -// (rebuildIndex, future bulk-add APIs) where per-item serial awaits -// dominate wallclock. A batch of N has roughly the latency of a single -// embed (network + GPU setup amortized), so backfilling a 500k-obs -// corpus drops from days to hours on a per-batch endpoint like vLLM. -// -// Per-item failure shape: -// - whole-batch network/provider error → all skipped, single warn line -// - per-item dimension mismatch → that item skipped, others continue -export async function vectorIndexAddBatchGuarded( - items: Array<{ - id: string - sessionId: string - text: string - context: { kind: "memory" | "observation" | "synthetic"; logId: string } - }>, -): Promise<{ ok: number; fail: number }> { - const vi = vectorIndex - const ep = currentEmbeddingProvider - if (!vi || !ep || items.length === 0) return { ok: 0, fail: 0 } - - let embeddings: Float32Array[] - try { - embeddings = await ep.embedBatch(items.map((i) => clipEmbedInput(i.text))) - } catch (err) { - logger.warn("vector-index add batch: embed failed — skipping batch", { - batchSize: items.length, - provider: ep.name, - error: err instanceof Error ? err.message : String(err), - }) - return { ok: 0, fail: items.length } - } - - if (embeddings.length !== items.length) { - logger.warn( - "vector-index add batch: provider returned wrong length — skipping batch", - { - batchSize: items.length, - returned: embeddings.length, - provider: ep.name, - }, - ) - return { ok: 0, fail: items.length } - } - - let ok = 0 - let fail = 0 - for (let i = 0; i < items.length; i++) { - const item = items[i] - const embedding = embeddings[i] - if (embedding.length !== ep.dimensions) { - logger.warn("vector-index add batch: dimension mismatch — skipping item", { - kind: item.context.kind, - id: item.context.logId, - provider: ep.name, - expected: ep.dimensions, - received: embedding.length, - }) - fail++ - continue - } - try { - vi.add(item.id, item.sessionId, embedding) - ok++ - } catch (err) { - logger.warn("vector-index add batch: index write failed — skipping item", { - kind: item.context.kind, - id: item.context.logId, - error: err instanceof Error ? err.message : String(err), - }) - fail++ - } - } - return { ok, fail } -} - -// Embed-batch size for rebuild. Each item is one /v1/embeddings call's -// `input` array element; the provider sees the whole batch as one HTTP -// round-trip. 32 fits comfortably under typical per-request token budgets -// (32 × ~110 tok/item ≈ 3.5k tokens) and gets close to per-call -// throughput for GPU-backed endpoints (vLLM, Triton, etc.). Override via -// REBUILD_EMBED_BATCH_SIZE for endpoints that prefer smaller/larger -// batches. Set to 1 to fall back to the legacy per-item path. -const DEFAULT_REBUILD_EMBED_BATCH = 32 - -function getRebuildEmbedBatchSize(): number { - const raw = process.env.REBUILD_EMBED_BATCH_SIZE - if (!raw) return DEFAULT_REBUILD_EMBED_BATCH - const n = parseInt(raw, 10) - return Number.isFinite(n) && n > 0 ? n : DEFAULT_REBUILD_EMBED_BATCH -} - -export async function rebuildIndex(kv: StateKV): Promise { - const idx = getSearchIndex() - idx.clear() - - // BM25 clear above wipes stale doc entries; the vector index has the - // symmetric concern — memories/observations deleted between runs - // would leave orphan embeddings here forever. Clear both before the - // repopulation loops run, so BM25 and vector stay in sync. - vectorIndex?.clear() - - const batchSize = getRebuildEmbedBatchSize() - // Accumulator for the batched embed flush. BM25 add is synchronous and - // doesn't need batching — only the vector path benefits. - type EmbedJob = { - id: string - sessionId: string - text: string - context: { kind: "memory" | "observation" | "synthetic"; logId: string } - } - const pending: EmbedJob[] = [] - let count = 0 - - const flush = async (): Promise => { - if (pending.length === 0) return - await vectorIndexAddBatchGuarded(pending) - pending.length = 0 - } - const enqueue = async (job: EmbedJob): Promise => { - pending.push(job) - if (pending.length >= batchSize) await flush() - } - - // Memories live in their own KV scope outside per-session observation - // scopes, so they need a separate walk. Without this, mem::remember - // entries vanish from BM25 on every restart even after the live-write - // fix in remember.ts (#257). - try { - const memories = await kv.list(KV.memories) - for (const memory of memories) { - if (memory.isLatest === false) continue - if (!memory.title || !memory.content) continue - idx.add(memoryToObservation(memory)) - await enqueue({ - id: memory.id, - sessionId: memory.sessionIds?.[0] ?? 'memory', - text: memory.title + ' ' + memory.content, - context: { kind: "memory", logId: memory.id }, - }) - count++ - } - } catch (err) { - logger.warn('rebuildIndex: failed to load memories', { - error: err instanceof Error ? err.message : String(err), - }) - } - - const sessions = await kv.list(KV.sessions) - if (!sessions.length) { - await flush() - return count - } - - const obsPerSession: CompressedObservation[][] = [] - const failedSessions: string[] = [] - for (let batch = 0; batch < sessions.length; batch += 10) { - const chunk = sessions.slice(batch, batch + 10) - const results = await Promise.all( - chunk.map(async (s) => { - try { - return await kv.list(KV.observations(s.id)) - } catch { - failedSessions.push(s.id) - return [] as CompressedObservation[] - } - }) - ) - obsPerSession.push(...results) - } - if (failedSessions.length > 0) { - logger.warn('rebuildIndex: failed to load observations for sessions', { failedSessions }) - } - for (const observations of obsPerSession) { - for (const obs of observations) { - if (obs.title && obs.narrative) { - idx.add(obs) - await enqueue({ - id: obs.id, - sessionId: obs.sessionId, - text: obs.title + ' ' + obs.narrative, - context: { kind: "observation", logId: obs.id }, - }) - count++ - } - } - } - - // Drain the last partial batch. - await flush() - return count -} - -export function registerSearchFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction( - 'mem::search', - async (data: { - query: string - limit?: number - project?: string - cwd?: string - format?: string - token_budget?: number - }) => { - const idx = getSearchIndex() - - // Input validation / normalization. - if (typeof data?.query !== 'string' || !data.query.trim()) { - throw new Error('mem::search: query must be a non-empty string') - } - const query = data.query.trim() - const MAX_LIMIT = 100 - let effectiveLimit = 20 - if (data.limit !== undefined) { - if (!Number.isInteger(data.limit) || data.limit < 1) { - throw new Error('mem::search: limit must be a positive integer') - } - effectiveLimit = Math.min(data.limit, MAX_LIMIT) - } - const projectFilter = typeof data.project === 'string' && data.project.trim().length > 0 ? data.project.trim() : undefined - const cwdFilter = typeof data.cwd === 'string' && data.cwd.trim().length > 0 ? data.cwd.trim() : undefined - const format = typeof data.format === 'string' ? data.format : 'full' - if (!['full', 'compact', 'narrative'].includes(format)) { - throw new Error("mem::search: format must be one of 'full', 'compact', or 'narrative'") - } - let tokenBudget: number | undefined - if (data.token_budget !== undefined) { - if (!Number.isInteger(data.token_budget) || data.token_budget < 1) { - throw new Error('mem::search: token_budget must be a positive integer') - } - tokenBudget = data.token_budget - } - - if (idx.size === 0) { - const count = await rebuildIndex(kv) - logger.info('Search index rebuilt', { entries: count }) - } - - // When filtering by project/cwd, over-fetch from the index so the - // post-filter still has a chance of returning `effectiveLimit` results. - const filtering = !!(projectFilter || cwdFilter) - const fetchLimit = filtering ? Math.max(effectiveLimit * 10, 100) : effectiveLimit - const results = idx.search(query, fetchLimit) - - // Resolve session -> project/cwd once per sessionId we touch. - const sessionCache = new Map() - const loadSession = async (sessionId: string): Promise => { - if (sessionCache.has(sessionId)) return sessionCache.get(sessionId)! - const s = await kv.get(KV.sessions, sessionId) - sessionCache.set(sessionId, s ?? null) - return s ?? null - } - - // Cache for memory project lookups. Memories indexed via mem::remember - // use a synthetic sessionId ('memory' or the first real sessionId) that - // either has no KV.sessions entry or belongs to a different project. - // When loadSession returns null we fall through to a KV.memories probe - // so project-filtered search can include or exclude them correctly. - const memoryProjectCache = new Map() - const loadMemoryProject = async (obsId: string): Promise => { - if (memoryProjectCache.has(obsId)) return memoryProjectCache.get(obsId)! - const mem = await kv.get(KV.memories, obsId).catch(() => null) - const proj = mem?.project ?? null - memoryProjectCache.set(obsId, proj) - return proj - } - - // First pass: filter by session (sequential — benefits from session cache). - // Memory entries with a synthetic sessionId take a secondary KV.memories - // path so project filtering works correctly for them too. - const candidates: typeof results = [] - for (const r of results) { - if (candidates.length >= effectiveLimit) break - if (filtering) { - const s = await loadSession(r.sessionId) - if (s) { - if (projectFilter && s.project !== projectFilter) continue - if (cwdFilter && s.cwd !== cwdFilter) continue - } else { - // Session not found. Two cases arrive here: - // 1. Synthetic sessionId — memories indexed via mem::remember use - // sessionIds[0] ?? 'memory'. The string 'memory' has no session - // entry; neither does a real sessionId when sessionIds[0] happens - // to be a session from a different lifecycle. Probe KV.memories - // directly to get the memory's own project field. - // 2. Deleted session — the session existed when the entry was indexed - // but was since evicted. The KV.memories probe returns null for - // these (they are observations, not memories), so memProject is - // null and the entry passes through as unscoped. This is the safe - // fallback: we lose the ability to filter but never incorrectly - // block a result whose session we can no longer verify. - // In both cases, a null memProject means "project unknown — treat as - // unscoped and let it through" to preserve backward-compatibility. - if (projectFilter) { - const memProject = await loadMemoryProject(r.obsId) - if (memProject !== null && memProject !== projectFilter) continue - } - // cwd filter does not apply to unbound entries. - } - } - candidates.push(r) - } - - // Second pass: load observations in parallel. Fall back to - // KV.memories when the observation lookup misses — entries indexed - // via mem::remember live in the memories scope under a synthetic - // sessionId, so the observation key never exists (#265). - const obsResults = await Promise.all( - candidates.map(async (r) => { - const obs = await kv - .get(KV.observations(r.sessionId), r.obsId) - .catch(() => null) - if (obs) return obs - const mem = await kv - .get(KV.memories, r.obsId) - .catch(() => null) - return mem ? memoryToObservation(mem) : null - }) - ) - const enriched: SearchResult[] = [] - for (let i = 0; i < candidates.length; i++) { - const obs = obsResults[i] - if (obs) { - enriched.push({ - observation: obs, - score: candidates[i].score, - sessionId: candidates[i].sessionId, - }) - } - } - - void recordAccessBatch( - kv, - enriched.map((r) => r.observation.id), - ) - - const estimateTokens = (value: unknown): number => - Math.max(1, Math.ceil(JSON.stringify(value).length / 3)) - - const applyTokenBudget = (items: T[]): { - items: T[] - used: number - truncated: boolean - } => { - if (!tokenBudget) return { items, used: items.reduce((sum, item) => sum + estimateTokens(item), 0), truncated: false } - const selected: T[] = [] - let used = 0 - for (const item of items) { - const itemTokens = estimateTokens(item) - if (used + itemTokens > tokenBudget) { - return { items: selected, used, truncated: selected.length < items.length } - } - selected.push(item) - used += itemTokens - } - return { items: selected, used, truncated: false } - } - - if (format === 'compact') { - const compactResults: CompactSearchResult[] = enriched.map((r) => ({ - obsId: r.observation.id, - sessionId: r.sessionId, - title: r.observation.title, - type: r.observation.type, - score: r.score, - timestamp: r.observation.timestamp, - })) - const packed = applyTokenBudget(compactResults) - return { - format, - results: packed.items, - tokens_used: packed.used, - tokens_budget: tokenBudget, - truncated: packed.truncated, - } - } - - if (format === 'narrative') { - const narrativeResults = enriched.map((r) => ({ - obsId: r.observation.id, - sessionId: r.sessionId, - title: r.observation.title, - narrative: r.observation.narrative, - score: r.score, - timestamp: r.observation.timestamp, - })) - const packed = applyTokenBudget(narrativeResults) - const text = packed.items - .map((r, index) => `${index + 1}. ${r.title}\n${r.narrative}`) - .join('\n\n') - return { - format, - results: packed.items, - text, - tokens_used: packed.used, - tokens_budget: tokenBudget, - truncated: packed.truncated, - } - } - - const packed = applyTokenBudget(enriched) - - // Avoid logging raw cwd/project (host paths). Log only that filters were active. - logger.info('Search completed', { - query, - results: packed.items.length, - hasProjectFilter: !!projectFilter, - hasCwdFilter: !!cwdFilter, - }) - return { - format, - results: packed.items, - tokens_used: packed.used, - tokens_budget: tokenBudget, - truncated: packed.truncated, - } - } - ) -} diff --git a/src/functions/sentinels.ts b/src/functions/sentinels.ts deleted file mode 100644 index ed29aee9..00000000 --- a/src/functions/sentinels.ts +++ /dev/null @@ -1,455 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import type { Action, ActionEdge, Checkpoint, CompressedObservation, FunctionMetrics, Sentinel, Session } from "../types.js"; -import { recordAudit } from "./audit.js"; - -const VALID_TYPES: Sentinel["type"][] = [ - "webhook", - "timer", - "threshold", - "pattern", - "approval", - "custom", -]; - -export function registerSentinelsFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::sentinel-create", - async (data: { - name: string; - type: Sentinel["type"]; - config?: Record; - linkedActionIds?: string[]; - expiresInMs?: number; - }) => { - if (!data.name || typeof data.name !== "string") { - return { success: false, error: "name is required" }; - } - if (!data.type || !VALID_TYPES.includes(data.type)) { - return { - success: false, - error: `type must be one of: ${VALID_TYPES.join(", ")}`, - }; - } - - if (data.type === "threshold") { - const cfg = data.config as - | { metric?: string; operator?: string; value?: number } - | undefined; - if ( - !cfg || - !cfg.metric || - !["gt", "lt", "eq"].includes(cfg.operator || "") || - typeof cfg.value !== "number" - ) { - return { - success: false, - error: - "threshold config requires metric, operator (gt|lt|eq), and numeric value", - }; - } - } - - if (data.type === "pattern") { - const cfg = data.config as { pattern?: string } | undefined; - if (!cfg || !cfg.pattern || typeof cfg.pattern !== "string") { - return { - success: false, - error: "pattern config requires a pattern string", - }; - } - } - - if (data.type === "webhook") { - const cfg = data.config as { path?: string } | undefined; - if (!cfg || !cfg.path || typeof cfg.path !== "string") { - return { - success: false, - error: "webhook config requires a path string", - }; - } - } - - if (data.type === "timer") { - const cfg = data.config as { durationMs?: number } | undefined; - if (!cfg || typeof cfg.durationMs !== "number" || cfg.durationMs <= 0) { - return { - success: false, - error: "timer config requires a positive durationMs", - }; - } - } - - if (data.linkedActionIds && data.linkedActionIds.length > 0) { - for (const actionId of data.linkedActionIds) { - const action = await kv.get(KV.actions, actionId); - if (!action) { - return { - success: false, - error: `linked action not found: ${actionId}`, - }; - } - } - } - - const now = new Date(); - const sentinel: Sentinel = { - id: generateId("snl"), - name: data.name.trim(), - type: data.type, - status: "watching", - config: data.config || {}, - createdAt: now.toISOString(), - linkedActionIds: data.linkedActionIds || [], - expiresAt: data.expiresInMs - ? new Date(now.getTime() + data.expiresInMs).toISOString() - : undefined, - }; - - await kv.set(KV.sentinels, sentinel.id, sentinel); - await recordAudit(kv, "sentinel_create", "mem::sentinel-create", [sentinel.id], { - action: "sentinel.create", - type: sentinel.type, - linkedActionIds: sentinel.linkedActionIds, - }); - - if (data.linkedActionIds && data.linkedActionIds.length > 0) { - for (const actionId of data.linkedActionIds) { - const edge: ActionEdge = { - id: generateId("ae"), - type: "gated_by", - sourceActionId: actionId, - targetActionId: sentinel.id, - createdAt: now.toISOString(), - }; - await kv.set(KV.actionEdges, edge.id, edge); - await recordAudit(kv, "sentinel_create", "mem::sentinel-create", [edge.id], { - action: "sentinel.create.edge", - sentinelId: sentinel.id, - sourceActionId: actionId, - }); - } - } - - if (data.type === "timer") { - const durationMs = (data.config as { durationMs: number }).durationMs; - setTimeout(async () => { - try { - await withKeyedLock(`mem:sentinel:${sentinel.id}`, async () => { - const fresh = await kv.get(KV.sentinels, sentinel.id); - if (!fresh || fresh.status !== "watching") return; - fresh.status = "triggered"; - fresh.triggeredAt = new Date().toISOString(); - fresh.result = { reason: "timer_elapsed", durationMs }; - await kv.set(KV.sentinels, fresh.id, fresh); - await recordAudit(kv, "sentinel_trigger", "mem::sentinel-create", [fresh.id], { - action: "sentinel.timer_trigger", - reason: "timer_elapsed", - durationMs, - }); - await unblockLinkedActions(kv, fresh); - }); - } catch (err) { - console.error("sentinel timer callback failed", sentinel.id, err); - } - }, durationMs); - } - - return { success: true, sentinel }; - }, - ); - - sdk.registerFunction("mem::sentinel-trigger", - async (data: { sentinelId: string; result?: unknown }) => { - if (!data.sentinelId) { - return { success: false, error: "sentinelId is required" }; - } - - return withKeyedLock( - `mem:sentinel:${data.sentinelId}`, - async () => { - const sentinel = await kv.get( - KV.sentinels, - data.sentinelId, - ); - if (!sentinel) { - return { success: false, error: "sentinel not found" }; - } - if (sentinel.status !== "watching") { - return { - success: false, - error: `sentinel already ${sentinel.status}`, - }; - } - - sentinel.status = "triggered"; - sentinel.triggeredAt = new Date().toISOString(); - sentinel.result = data.result; - - await kv.set(KV.sentinels, sentinel.id, sentinel); - await recordAudit(kv, "sentinel_trigger", "mem::sentinel-trigger", [sentinel.id], { - action: "sentinel.trigger", - result: data.result, - }); - - let unblockedCount = 0; - if (sentinel.linkedActionIds.length > 0) { - unblockedCount = await unblockLinkedActions(kv, sentinel); - } - - return { success: true, sentinel, unblockedCount }; - }, - ); - }, - ); - - sdk.registerFunction("mem::sentinel-check", - async () => { - const sentinels = await kv.list(KV.sentinels); - const active = sentinels.filter((s) => s.status === "watching"); - const triggered: string[] = []; - - for (const sentinel of active) { - if (sentinel.type === "threshold") { - const cfg = sentinel.config as { - metric: string; - operator: "gt" | "lt" | "eq"; - value: number; - }; - const metrics = await kv.get( - KV.metrics, - cfg.metric, - ); - if (!metrics) continue; - - const current = metrics.totalCalls; - let matched = false; - if (cfg.operator === "gt") matched = current > cfg.value; - else if (cfg.operator === "lt") matched = current < cfg.value; - else if (cfg.operator === "eq") matched = current === cfg.value; - - if (matched) { - await withKeyedLock( - `mem:sentinel:${sentinel.id}`, - async () => { - const fresh = await kv.get( - KV.sentinels, - sentinel.id, - ); - if (!fresh || fresh.status !== "watching") return; - fresh.status = "triggered"; - fresh.triggeredAt = new Date().toISOString(); - fresh.result = { - reason: "threshold_crossed", - metric: cfg.metric, - currentValue: current, - threshold: cfg.value, - operator: cfg.operator, - }; - await kv.set(KV.sentinels, fresh.id, fresh); - await recordAudit(kv, "sentinel_trigger", "mem::sentinel-check", [fresh.id], { - action: "sentinel.threshold_trigger", - result: fresh.result, - }); - await unblockLinkedActions(kv, fresh); - }, - ); - triggered.push(sentinel.id); - } - } - - if (sentinel.type === "pattern") { - const cfg = sentinel.config as { pattern: string }; - const regex = new RegExp(cfg.pattern, "i"); - const sessions = await kv.list(KV.sessions); - let matchedObs: CompressedObservation | null = null; - - for (const session of sessions) { - const observations = await kv.list( - KV.observations(session.id), - ); - const recent = observations - .filter( - (o) => - new Date(o.timestamp).getTime() >= - new Date(sentinel.createdAt).getTime(), - ) - .find((o) => regex.test(o.title)); - if (recent) { - matchedObs = recent; - break; - } - } - - if (matchedObs) { - await withKeyedLock( - `mem:sentinel:${sentinel.id}`, - async () => { - const fresh = await kv.get( - KV.sentinels, - sentinel.id, - ); - if (!fresh || fresh.status !== "watching") return; - fresh.status = "triggered"; - fresh.triggeredAt = new Date().toISOString(); - fresh.result = { - reason: "pattern_matched", - pattern: cfg.pattern, - matchedObservationId: matchedObs!.id, - matchedTitle: matchedObs!.title, - }; - await kv.set(KV.sentinels, fresh.id, fresh); - await recordAudit(kv, "sentinel_trigger", "mem::sentinel-check", [fresh.id], { - action: "sentinel.pattern_trigger", - result: fresh.result, - }); - await unblockLinkedActions(kv, fresh); - }, - ); - triggered.push(sentinel.id); - } - } - } - - return { success: true, triggered, checkedCount: active.length }; - }, - ); - - sdk.registerFunction("mem::sentinel-cancel", - async (data: { sentinelId: string }) => { - if (!data.sentinelId) { - return { success: false, error: "sentinelId is required" }; - } - - return withKeyedLock( - `mem:sentinel:${data.sentinelId}`, - async () => { - const sentinel = await kv.get( - KV.sentinels, - data.sentinelId, - ); - if (!sentinel) { - return { success: false, error: "sentinel not found" }; - } - if (sentinel.status !== "watching") { - return { - success: false, - error: `cannot cancel sentinel with status ${sentinel.status}`, - }; - } - - sentinel.status = "cancelled"; - await kv.set(KV.sentinels, sentinel.id, sentinel); - await recordAudit(kv, "sentinel_trigger", "mem::sentinel-cancel", [sentinel.id], { - action: "sentinel.cancel", - status: "cancelled", - }); - - return { success: true, sentinel }; - }, - ); - }, - ); - - sdk.registerFunction("mem::sentinel-list", - async (data: { status?: string; type?: string }) => { - let sentinels = await kv.list(KV.sentinels); - - if (data.status) { - sentinels = sentinels.filter((s) => s.status === data.status); - } - if (data.type) { - sentinels = sentinels.filter((s) => s.type === data.type); - } - - sentinels.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - return { success: true, sentinels }; - }, - ); - - sdk.registerFunction("mem::sentinel-expire", - async () => { - const sentinels = await kv.list(KV.sentinels); - const now = Date.now(); - let expired = 0; - - for (const sentinel of sentinels) { - if ( - sentinel.status === "watching" && - sentinel.expiresAt && - new Date(sentinel.expiresAt).getTime() <= now - ) { - const didExpire = await withKeyedLock( - `mem:sentinel:${sentinel.id}`, - async () => { - const fresh = await kv.get( - KV.sentinels, - sentinel.id, - ); - if (!fresh || fresh.status !== "watching") return false; - fresh.status = "expired"; - fresh.triggeredAt = new Date().toISOString(); - await kv.set(KV.sentinels, fresh.id, fresh); - await recordAudit(kv, "sentinel_trigger", "mem::sentinel-expire", [fresh.id], { - action: "sentinel.expire", - status: "expired", - }); - return true; - }, - ); - if (didExpire) expired++; - } - } - - return { success: true, expired }; - }, - ); -} - -async function unblockLinkedActions( - kv: StateKV, - sentinel: Sentinel, -): Promise { - if (sentinel.linkedActionIds.length === 0) return 0; - - const allEdges = await kv.list(KV.actionEdges); - const allSentinels = await kv.list(KV.sentinels); - const allCheckpoints = await kv.list(KV.checkpoints); - const gateMap = new Map(); - for (const s of allSentinels) gateMap.set(s.id, { status: s.status === "triggered" ? "passed" : s.status }); - for (const c of allCheckpoints) gateMap.set(c.id, { status: c.status }); - - let unblockedCount = 0; - - for (const actionId of sentinel.linkedActionIds) { - await withKeyedLock(`mem:action:${actionId}`, async () => { - const action = await kv.get(KV.actions, actionId); - if (action && action.status === "blocked") { - const gates = allEdges.filter( - (e) => e.sourceActionId === actionId && e.type === "gated_by", - ); - const allPassed = gates.every((g) => { - const gate = gateMap.get(g.targetActionId); - return gate && gate.status === "passed"; - }); - if (allPassed) { - action.status = "pending"; - action.updatedAt = new Date().toISOString(); - await kv.set(KV.actions, action.id, action); - await recordAudit(kv, "action_update", "mem::sentinel-unblock", [action.id], { - action: "action.unblocked", - sentinelId: sentinel.id, - }); - unblockedCount++; - } - } - }); - } - - return unblockedCount; -} diff --git a/src/functions/session-highlights.ts b/src/functions/session-highlights.ts deleted file mode 100644 index a33715fc..00000000 --- a/src/functions/session-highlights.ts +++ /dev/null @@ -1,247 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - CompressedObservation, - RawObservation, - Session, - SessionHighlight, - SessionHighlightCategory, - SessionHighlightsError, - SessionHighlightsView, -} from "../types.js"; -import { KV } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; - -type ObservationRow = CompressedObservation | RawObservation; - -const CATEGORY_ORDER: SessionHighlightCategory[] = [ - "goal", - "agent_output", - "failure", - "validation", - "command", - "mcp", - "tool", - "file", - "artifact", - "skill", - "todo", - "follow_up", -]; - -function isRawObservation(row: ObservationRow): row is RawObservation { - return "hookType" in row && typeof row.hookType === "string"; -} - -function normalizeText(value: string): string { - return value.replace(/\s+/g, " ").trim(); -} - -function truncate(value: string, max = 240): string { - const text = normalizeText(value); - return text.length > max ? `${text.slice(0, max - 1)}…` : text; -} - -function categoryCounts(): Record { - return Object.fromEntries(CATEGORY_ORDER.map((category) => [category, 0])) as Record< - SessionHighlightCategory, - number - >; -} - -function includesAny(text: string, terms: string[]): boolean { - return terms.some((term) => text.includes(term)); -} - -function dedupeText(value: string): string { - return normalizeText(value).toLowerCase(); -} - -function isJsonShaped(text: string): boolean { - const trimmed = text.trim(); - if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false; - try { - JSON.parse(trimmed); - return true; - } catch { - return false; - } -} - -function isToolTraceText(text: string): boolean { - const normalized = normalizeText(text); - if (!normalized) return true; - if (isJsonShaped(normalized)) return true; - return includesAny(normalized.toLowerCase(), [ - "\"command\"", - "toolinput", - "tooloutput", - "function_id", - "apply_patch", - "exec_command", - "memory_", - "agentmemory_memory_", - ]); -} - -function addHighlight( - highlights: SessionHighlight[], - row: ObservationRow | null, - category: "goal" | "agent_output", - title: string, - summary: string, - opts: { sessionId: string; importance?: number; confidence?: number }, -): void { - const cleanSummary = truncate(summary); - if (!cleanSummary || isToolTraceText(cleanSummary)) return; - const key = `${category}:${dedupeText(cleanSummary)}`; - if (highlights.some((h) => `${h.category}:${dedupeText(h.summary)}` === key)) return; - const sourceObservationId = row?.id; - const timestamp = row?.timestamp; - highlights.push({ - id: `${category}_${sourceObservationId || "session"}`, - sessionId: opts.sessionId, - category, - title: truncate(title, 120), - summary: cleanSummary, - ...(timestamp ? { timestamp } : {}), - ...(sourceObservationId ? { sourceObservationId } : {}), - files: [], - importance: opts.importance ?? 7, - confidence: opts.confidence ?? 0.6, - }); -} - -function compressedConversationText(row: CompressedObservation): string { - return normalizeText([row.narrative, row.subtitle, ...(row.facts || [])].filter(Boolean).join(" | ")); -} - -function isAssistantConversation(row: CompressedObservation): boolean { - return /\b(agent output|assistant response|assistant)\b/i.test(compressedConversationText(row)); -} - -function titleOf(row: ObservationRow, category: "goal" | "agent_output"): string { - if (category === "agent_output") return "Agent output"; - if (isRawObservation(row)) return row.hookType; - return row.title || row.type || "Conversation"; -} - -function hasAssistantOutputs(rows: ObservationRow[]): boolean { - return rows.some((row) => { - if (isRawObservation(row)) { - return typeof row.assistantResponse === "string" && !isToolTraceText(row.assistantResponse); - } - return row.type === "conversation" && isAssistantConversation(row) && !isToolTraceText(compressedConversationText(row)); - }); -} - -function addConversationHighlights( - highlights: SessionHighlight[], - sessionId: string, - row: ObservationRow, -): void { - if (isRawObservation(row)) { - if (typeof row.userPrompt === "string") { - addHighlight(highlights, row, "goal", titleOf(row, "goal"), row.userPrompt, { - sessionId, - importance: 8, - confidence: 0.65, - }); - } - if (typeof row.assistantResponse === "string") { - addHighlight(highlights, row, "agent_output", "Agent output", row.assistantResponse, { - sessionId, - importance: 7, - confidence: 0.65, - }); - } - return; - } - - if (row.type !== "conversation") return; - const text = compressedConversationText(row); - if (isAssistantConversation(row)) { - addHighlight(highlights, row, "agent_output", titleOf(row, "agent_output"), text, { - sessionId, - importance: row.importance || 7, - confidence: row.confidence ?? 0.6, - }); - return; - } - addHighlight(highlights, row, "goal", titleOf(row, "goal"), text, { - sessionId, - importance: row.importance || 7, - confidence: row.confidence ?? 0.6, - }); -} - -function buildHighlights( - session: Session, - rows: ObservationRow[], - maxItems: number, -): SessionHighlightsView { - const sorted = [...rows].sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || "")); - const highlights: SessionHighlight[] = []; - if (session.firstPrompt) { - addHighlight(highlights, null, "goal", "Initial user goal", session.firstPrompt, { - sessionId: session.id, - importance: 9, - confidence: 0.7, - }); - } - - for (const row of sorted) { - addConversationHighlights(highlights, session.id, row); - } - - highlights.sort((a, b) => { - const categoryDiff = CATEGORY_ORDER.indexOf(a.category) - CATEGORY_ORDER.indexOf(b.category); - if (categoryDiff !== 0) return categoryDiff; - const importanceDiff = b.importance - a.importance; - if (importanceDiff !== 0) return importanceDiff; - return (a.timestamp || "").localeCompare(b.timestamp || ""); - }); - const limited = highlights.slice(0, maxItems); - const counts = categoryCounts(); - for (const highlight of limited) counts[highlight.category] += 1; - const assistantOutputs = hasAssistantOutputs(sorted); - - return { - success: true, - session, - coverage: { - llmUsed: false, - hasAssistantOutputs: assistantOutputs, - missingAssistantOutputs: !assistantOutputs, - rawObservationCount: sorted.filter(isRawObservation).length, - compressedObservationCount: sorted.filter((row) => !isRawObservation(row)).length, - }, - stats: { - observationCount: sorted.length, - highlightCount: limited.length, - categoryCounts: counts, - fileCount: 0, - toolCount: 0, - }, - highlights: limited, - }; -} - -export function registerSessionHighlightsFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction( - "mem::session-highlights", - async ( - data: { sessionId?: string; maxItems?: number } | undefined, - ): Promise => { - if (!data?.sessionId || typeof data.sessionId !== "string" || !data.sessionId.trim()) { - return { success: false, error: "sessionId is required" }; - } - const sessionId = data.sessionId.trim(); - const session = await kv.get(KV.sessions, sessionId); - if (!session) return { success: false, error: "session_not_found" }; - const requestedMax = Number.isInteger(data.maxItems) ? data.maxItems as number : 40; - const maxItems = Math.max(1, Math.min(200, requestedMax)); - const observations = await kv.list(KV.observations(sessionId)); - return buildHighlights(session, observations, maxItems); - }, - ); -} diff --git a/src/functions/signals.ts b/src/functions/signals.ts deleted file mode 100644 index 6abf746c..00000000 --- a/src/functions/signals.ts +++ /dev/null @@ -1,201 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import type { Signal } from "../types.js"; -import { recordAudit } from "./audit.js"; - -export function registerSignalsFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::signal-send", - async (data: { - from: string; - to?: string; - content: string; - type?: Signal["type"]; - threadId?: string; - replyTo?: string; - metadata?: Record; - expiresInMs?: number; - }) => { - if (!data.from?.trim() || !data.content?.trim()) { - return { success: false, error: "from and non-empty content are required" }; - } - - const now = new Date(); - let threadId = data.threadId; - - if (data.replyTo && !threadId) { - const parent = await kv.get(KV.signals, data.replyTo); - if (parent) { - threadId = parent.threadId || parent.id; - } - } - - const signal: Signal = { - id: generateId("sig"), - from: data.from, - to: data.to, - content: data.content.trim(), - type: data.type || "info", - threadId: threadId || generateId("thr"), - replyTo: data.replyTo, - metadata: data.metadata, - createdAt: now.toISOString(), - expiresAt: data.expiresInMs - ? new Date(now.getTime() + data.expiresInMs).toISOString() - : undefined, - }; - - await kv.set(KV.signals, signal.id, signal); - await recordAudit(kv, "signal_send", "mem::signal-send", [signal.id], { - action: "create", - from: data.from, - to: data.to, - type: signal.type, - }); - - return { success: true, signal }; - }, - ); - - sdk.registerFunction("mem::signal-read", - async (data: { - agentId: string; - unreadOnly?: boolean; - threadId?: string; - type?: string; - limit?: number; - }) => { - if (!data.agentId) { - return { success: false, error: "agentId is required" }; - } - - let signals = await kv.list(KV.signals); - const now = Date.now(); - - signals = signals.filter((s) => { - if (s.expiresAt && new Date(s.expiresAt).getTime() <= now) return false; - if (s.to && s.to !== data.agentId && s.from !== data.agentId) - return false; - if (!s.to && s.from !== data.agentId) return true; - return true; - }); - - if (data.unreadOnly) { - signals = signals.filter((s) => !s.readAt && s.to === data.agentId); - } - if (data.threadId) { - signals = signals.filter((s) => s.threadId === data.threadId); - } - if (data.type) { - signals = signals.filter((s) => s.type === data.type); - } - - signals.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - const limit = data.limit || 50; - const results = signals.slice(0, limit); - - for (const sig of results) { - if (!sig.readAt && sig.to === data.agentId) { - const beforeReadAt = sig.readAt; - sig.readAt = new Date().toISOString(); - await recordAudit(kv, "signal_send", "mem::signal-read", [sig.id], { - action: "signal.mark_read", - actor: data.agentId, - beforeReadAt, - afterReadAt: sig.readAt, - }); - await kv.set(KV.signals, sig.id, sig); - } - } - - return { success: true, signals: results }; - }, - ); - - sdk.registerFunction("mem::signal-threads", - async (data: { agentId: string; limit?: number }) => { - if (!data.agentId) { - return { success: false, error: "agentId is required" }; - } - - const signals = await kv.list(KV.signals); - const now = Date.now(); - - const relevant = signals.filter((s) => { - if (s.expiresAt && new Date(s.expiresAt).getTime() <= now) return false; - return ( - s.from === data.agentId || - s.to === data.agentId || - !s.to - ); - }); - - const threadMap = new Map< - string, - { threadId: string; messages: number; lastMessage: string; participants: Set } - >(); - - for (const sig of relevant) { - const tid = sig.threadId || sig.id; - const existing = threadMap.get(tid); - if (existing) { - existing.messages++; - existing.participants.add(sig.from); - if (sig.to) existing.participants.add(sig.to); - if (new Date(sig.createdAt) > new Date(existing.lastMessage)) { - existing.lastMessage = sig.createdAt; - } - } else { - const participants = new Set([sig.from]); - if (sig.to) participants.add(sig.to); - threadMap.set(tid, { - threadId: tid, - messages: 1, - lastMessage: sig.createdAt, - participants, - }); - } - } - - const threads = Array.from(threadMap.values()) - .map((t) => ({ - ...t, - participants: Array.from(t.participants), - })) - .sort( - (a, b) => - new Date(b.lastMessage).getTime() - - new Date(a.lastMessage).getTime(), - ) - .slice(0, data.limit || 20); - - return { success: true, threads }; - }, - ); - - sdk.registerFunction("mem::signal-cleanup", - async () => { - const signals = await kv.list(KV.signals); - const now = Date.now(); - let removed = 0; - - for (const sig of signals) { - if (sig.expiresAt && new Date(sig.expiresAt).getTime() <= now) { - await recordAudit(kv, "delete", "mem::signal-cleanup", [sig.id], { - action: "delete", - resource: "Signal", - before: sig, - }); - await kv.delete(KV.signals, sig.id); - removed++; - } - } - - return { success: true, removed }; - }, - ); -} diff --git a/src/functions/sketches.ts b/src/functions/sketches.ts deleted file mode 100644 index c617a298..00000000 --- a/src/functions/sketches.ts +++ /dev/null @@ -1,315 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, generateId } from "../state/schema.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import type { Action, ActionEdge, Sketch } from "../types.js"; -import { safeAudit } from "./audit.js"; - -export function registerSketchesFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::sketch-create", - async (data: { - title: string; - description?: string; - expiresInMs?: number; - project?: string; - }) => { - if (!data.title || typeof data.title !== "string") { - return { success: false, error: "title is required" }; - } - - const now = new Date(); - const expiresInMs = data.expiresInMs || 3600000; - const sketch: Sketch = { - id: generateId("sk"), - title: data.title.trim(), - description: (data.description || "").trim(), - status: "active", - actionIds: [], - project: data.project, - createdAt: now.toISOString(), - expiresAt: new Date(now.getTime() + expiresInMs).toISOString(), - }; - - await kv.set(KV.sketches, sketch.id, sketch); - await safeAudit(kv, "sketch_create", "mem::sketch-create", [sketch.id], { - action: "create", - title: sketch.title, - }); - return { success: true, sketch }; - }, - ); - - sdk.registerFunction("mem::sketch-add", - async (data: { - sketchId: string; - title: string; - description?: string; - priority?: number; - dependsOn?: string[]; - }) => { - if (!data.sketchId) { - return { success: false, error: "sketchId is required" }; - } - if (!data.title || typeof data.title !== "string") { - return { success: false, error: "title is required" }; - } - - return withKeyedLock(`mem:sketch:${data.sketchId}`, async () => { - const sketch = await kv.get(KV.sketches, data.sketchId); - if (!sketch) { - return { success: false, error: "sketch not found" }; - } - if (sketch.status !== "active") { - return { success: false, error: "sketch is not active" }; - } - - const now = new Date().toISOString(); - const action: Action = { - id: generateId("act"), - title: data.title.trim(), - description: (data.description || "").trim(), - status: "pending", - priority: Math.max(1, Math.min(10, data.priority || 5)), - createdAt: now, - updatedAt: now, - createdBy: "sketch", - project: sketch.project, - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - sketchId: data.sketchId, - }; - - if (data.dependsOn && data.dependsOn.length > 0) { - const sketchActionSet = new Set(sketch.actionIds); - for (const depId of data.dependsOn) { - if (!sketchActionSet.has(depId)) { - return { - success: false, - error: `dependency ${depId} not found in this sketch`, - }; - } - } - } - - await kv.set(KV.actions, action.id, action); - await safeAudit(kv, "sketch_create", "mem::sketch-add", [action.id], { - action: "add.action", - sketchId: sketch.id, - }); - - const createdEdges: ActionEdge[] = []; - if (data.dependsOn && data.dependsOn.length > 0) { - for (const depId of data.dependsOn) { - const edge: ActionEdge = { - id: generateId("ae"), - type: "requires", - sourceActionId: action.id, - targetActionId: depId, - createdAt: now, - }; - await kv.set(KV.actionEdges, edge.id, edge); - await safeAudit(kv, "sketch_create", "mem::sketch-add", [edge.id], { - action: "add.edge", - sketchId: sketch.id, - }); - createdEdges.push(edge); - } - } - - sketch.actionIds.push(action.id); - await kv.set(KV.sketches, sketch.id, sketch); - await safeAudit(kv, "sketch_create", "mem::sketch-add", [sketch.id], { - action: "add.sketch-update", - addedActionId: action.id, - }); - - return { success: true, action, edges: createdEdges }; - }); - }, - ); - - sdk.registerFunction("mem::sketch-promote", - async (data: { sketchId: string; project?: string }) => { - if (!data.sketchId) { - return { success: false, error: "sketchId is required" }; - } - - return withKeyedLock(`mem:sketch:${data.sketchId}`, async () => { - const sketch = await kv.get(KV.sketches, data.sketchId); - if (!sketch) { - return { success: false, error: "sketch not found" }; - } - if (sketch.status !== "active") { - return { success: false, error: "sketch is not active" }; - } - - const promotedIds: string[] = []; - for (const actionId of sketch.actionIds) { - const action = await kv.get(KV.actions, actionId); - if (action) { - delete action.sketchId; - if (data.project) { - action.project = data.project; - } - action.updatedAt = new Date().toISOString(); - await kv.set(KV.actions, action.id, action); - await safeAudit(kv, "sketch_promote", "mem::sketch-promote", [action.id], { - action: "promote.action", - sketchId: sketch.id, - }); - promotedIds.push(action.id); - } - } - - sketch.status = "promoted"; - sketch.promotedAt = new Date().toISOString(); - await kv.set(KV.sketches, sketch.id, sketch); - await safeAudit(kv, "sketch_promote", "mem::sketch-promote", [sketch.id], { - action: "promote.sketch", - promotedIds, - }); - - return { success: true, promotedIds }; - }); - }, - ); - - sdk.registerFunction("mem::sketch-discard", - async (data: { sketchId: string }) => { - if (!data.sketchId) { - return { success: false, error: "sketchId is required" }; - } - - return withKeyedLock(`mem:sketch:${data.sketchId}`, async () => { - const sketch = await kv.get(KV.sketches, data.sketchId); - if (!sketch) { - return { success: false, error: "sketch not found" }; - } - if (sketch.status !== "active") { - return { success: false, error: "sketch is not active" }; - } - - const actionIdSet = new Set(sketch.actionIds); - - const allEdges = await kv.list(KV.actionEdges); - for (const edge of allEdges) { - if ( - actionIdSet.has(edge.sourceActionId) || - actionIdSet.has(edge.targetActionId) - ) { - await kv.delete(KV.actionEdges, edge.id); - await safeAudit(kv, "sketch_discard", "mem::sketch-discard", [edge.id], { - action: "discard.edge", - sketchId: sketch.id, - }); - } - } - - for (const actionId of sketch.actionIds) { - await kv.delete(KV.actions, actionId); - await safeAudit(kv, "sketch_discard", "mem::sketch-discard", [actionId], { - action: "discard.action", - sketchId: sketch.id, - }); - } - - sketch.status = "discarded"; - sketch.discardedAt = new Date().toISOString(); - await kv.set(KV.sketches, sketch.id, sketch); - await safeAudit(kv, "sketch_discard", "mem::sketch-discard", [sketch.id], { - action: "discard.sketch", - }); - - return { success: true, discardedCount: sketch.actionIds.length }; - }); - }, - ); - - sdk.registerFunction("mem::sketch-list", - async (data: { status?: string; project?: string }) => { - let sketches = await kv.list(KV.sketches); - - if (data.status) { - sketches = sketches.filter((s) => s.status === data.status); - } - if (data.project) { - sketches = sketches.filter((s) => s.project === data.project); - } - - sketches.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - - const results = sketches.map((s) => ({ - ...s, - actionCount: s.actionIds.length, - })); - - return { success: true, sketches: results }; - }, - ); - - sdk.registerFunction("mem::sketch-gc", - async () => { - const sketches = await kv.list(KV.sketches); - const now = Date.now(); - let collected = 0; - - for (const sketch of sketches) { - if ( - sketch.status !== "active" || - new Date(sketch.expiresAt).getTime() > now - ) { - continue; - } - - await withKeyedLock(`mem:sketch:${sketch.id}`, async () => { - const current = await kv.get(KV.sketches, sketch.id); - if ( - !current || - current.status !== "active" || - new Date(current.expiresAt).getTime() > now - ) { - return; - } - - const actionIdSet = new Set(current.actionIds); - - const allEdges = await kv.list(KV.actionEdges); - for (const edge of allEdges) { - if ( - actionIdSet.has(edge.sourceActionId) || - actionIdSet.has(edge.targetActionId) - ) { - await kv.delete(KV.actionEdges, edge.id); - await safeAudit(kv, "sketch_discard", "mem::sketch-gc", [edge.id], { - action: "gc.edge", - sketchId: current.id, - }); - } - } - - for (const actionId of current.actionIds) { - await kv.delete(KV.actions, actionId); - await safeAudit(kv, "sketch_discard", "mem::sketch-gc", [actionId], { - action: "gc.action", - sketchId: current.id, - }); - } - - current.status = "discarded"; - current.discardedAt = new Date().toISOString(); - await kv.set(KV.sketches, current.id, current); - await safeAudit(kv, "sketch_discard", "mem::sketch-gc", [current.id], { - action: "gc.sketch", - }); - collected++; - }); - } - - return { success: true, collected }; - }, - ); -} diff --git a/src/functions/skill-extract.ts b/src/functions/skill-extract.ts deleted file mode 100644 index d3bbbd37..00000000 --- a/src/functions/skill-extract.ts +++ /dev/null @@ -1,290 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - CompressedObservation, - SessionSummary, - ProceduralMemory, - Session, - MemoryProvider, -} from "../types.js"; -import { KV, generateId, fingerprintId } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; -import { logger } from "../logger.js"; - -const SKILL_EXTRACT_SYSTEM = `You are a skill extraction engine. Given a completed multi-step task session, extract a reusable procedural skill document. - -Output format: - -When the agent encounters [specific situation/pattern] -Short skill title - -First concrete action -Second concrete action - -What success looks like -comma,separated,tags - - -Rules: -- Extract ONLY if the session shows a clear multi-step procedure that succeeded -- Steps must be concrete and actionable, not vague -- The trigger should describe WHEN to apply this skill -- If the session is exploratory with no clear procedure, output -- Maximum 10 steps per skill`; - -function buildSkillPrompt( - summary: SessionSummary, - observations: CompressedObservation[], -): string { - const obsText = observations - .filter((o) => o.importance >= 4) - .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()) - .slice(0, 30) - .map( - (o) => - `[${o.type}] ${o.title}${o.narrative ? ": " + o.narrative : ""}`, - ) - .join("\n"); - - return `## Session Summary -Title: ${summary.title} -Narrative: ${summary.narrative} -Key Decisions: ${summary.keyDecisions.join("; ")} -Files Modified: ${summary.filesModified.join(", ")} -Concepts: ${summary.concepts.join(", ")} - -## Observations (${observations.length} total, showing top by importance) -${obsText}`; -} - -function parseSkillXml( - xml: string, -): { - trigger: string; - title: string; - steps: string[]; - expectedOutcome: string; - tags: string[]; -} | null { - if (xml.includes("")) return null; - - const triggerMatch = xml.match(/([\s\S]*?)<\/trigger>/); - const titleMatch = xml.match(/([\s\S]*?)<\/title>/); - const stepsMatch = xml.match(/<steps>([\s\S]*?)<\/steps>/); - const outcomeMatch = xml.match( - /<expected_outcome>([\s\S]*?)<\/expected_outcome>/, - ); - const tagsMatch = xml.match(/<tags>([\s\S]*?)<\/tags>/); - - if (!triggerMatch || !titleMatch || !stepsMatch) return null; - - const stepRegex = /<step>([\s\S]*?)<\/step>/g; - const steps: string[] = []; - let match; - while ((match = stepRegex.exec(stepsMatch[1])) !== null) { - const step = match[1].trim(); - if (step) steps.push(step); - } - - if (steps.length < 2) return null; - - return { - trigger: triggerMatch[1].trim(), - title: titleMatch[1].trim(), - steps, - expectedOutcome: outcomeMatch?.[1]?.trim() || "", - tags: tagsMatch?.[1] - ?.split(",") - .map((t) => t.trim()) - .filter(Boolean) || [], - }; -} - -export function registerSkillExtractFunctions( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, -): void { - sdk.registerFunction("mem::skill-extract", - async (data: { sessionId: string }) => { - if (!data?.sessionId) { - return { success: false, error: "sessionId is required" }; - } - - const session = await kv - .get<Session>(KV.sessions, data.sessionId) - .catch(() => null); - if (!session) { - return { success: false, error: "session not found" }; - } - if (session.status !== "completed") { - return { - success: false, - error: "session must be completed before skill extraction", - }; - } - - const [summary, observations] = await Promise.all([ - kv.get<SessionSummary>(KV.summaries, data.sessionId).catch(() => null), - kv.list<CompressedObservation>(KV.observations(data.sessionId)).catch(() => []), - ]); - if (!summary) { - return { - success: false, - error: "no summary — run mem::summarize first", - }; - } - if (observations.length < 3) { - return { success: false, error: "too few observations for skill extraction" }; - } - - try { - const prompt = buildSkillPrompt(summary, observations); - const response = await provider.summarize( - SKILL_EXTRACT_SYSTEM, - prompt, - ); - const parsed = parseSkillXml(response); - - if (!parsed) { - logger.info("No skill extracted — session was exploratory", { - sessionId: data.sessionId, - }); - return { success: true, extracted: false, reason: "no clear procedure found" }; - } - - const fp = fingerprintId( - "skill", - JSON.stringify({ - title: parsed.title.toLowerCase(), - trigger: parsed.trigger.toLowerCase(), - steps: parsed.steps.map((s) => s.toLowerCase().trim()), - }), - ); - const existing = await kv - .get<ProceduralMemory>(KV.procedural, fp) - .catch(() => null); - - if (existing) { - const alreadyReinforced = existing.sourceSessionIds.includes(data.sessionId); - if (!alreadyReinforced) { - existing.strength = Math.min(1.0, existing.strength + 0.15); - existing.frequency++; - existing.sourceSessionIds = [...existing.sourceSessionIds, data.sessionId]; - } - existing.updatedAt = new Date().toISOString(); - await kv.set(KV.procedural, existing.id, existing); - - try { - await recordAudit(kv, "skill_extract", "mem::skill-extract", [], { - skillId: existing.id, - reinforced: true, - sessionId: data.sessionId, - }); - } catch {} - - logger.info("Skill reinforced", { - id: existing.id, - name: parsed.title, - }); - return { - success: true, - extracted: true, - reinforced: true, - skill: existing, - }; - } - - const now = new Date().toISOString(); - const skill: ProceduralMemory = { - id: fp, - name: parsed.title, - triggerCondition: parsed.trigger, - steps: parsed.steps, - expectedOutcome: parsed.expectedOutcome, - strength: 0.6, - frequency: 1, - tags: parsed.tags, - concepts: summary.concepts, - sourceSessionIds: [data.sessionId], - sourceObservationIds: observations - .slice(0, 10) - .map((o) => o.id), - createdAt: now, - updatedAt: now, - }; - - await kv.set(KV.procedural, skill.id, skill); - - try { - await recordAudit(kv, "skill_extract", "mem::skill-extract", [], { - skillId: skill.id, - title: parsed.title, - steps: parsed.steps.length, - sessionId: data.sessionId, - }); - } catch {} - - logger.info("Skill extracted", { - id: skill.id, - title: parsed.title, - steps: parsed.steps.length, - }); - - return { success: true, extracted: true, reinforced: false, skill }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Skill extraction failed", { error: msg }); - return { success: false, error: msg }; - } - }, - ); - - sdk.registerFunction("mem::skill-list", - async (data: { limit?: number }) => { - const limit = data?.limit ?? 50; - const skills = await kv.list<ProceduralMemory>(KV.procedural); - const sorted = skills.sort((a, b) => b.strength - a.strength); - return { - success: true, - skills: sorted.slice(0, limit), - total: sorted.length, - }; - }, - ); - - sdk.registerFunction("mem::skill-match", - async (data: { query: string; limit?: number }) => { - if (!data?.query?.trim()) { - return { success: false, error: "query is required" }; - } - - const limit = data.limit ?? 5; - const query = data.query.toLowerCase(); - const terms = query.split(/\s+/).filter((t) => t.length > 2); - - const skills = await kv.list<ProceduralMemory>(KV.procedural); - - const scored = skills - .map((skill) => { - const text = - `${skill.name} ${skill.triggerCondition} ${(skill.tags || []).join(" ")} ${skill.steps.join(" ")}`.toLowerCase(); - const matchCount = terms.filter((t) => text.includes(t)).length; - if (matchCount === 0) return null; - const relevance = matchCount / terms.length; - return { skill, score: relevance * skill.strength }; - }) - .filter(Boolean) as Array<{ - skill: ProceduralMemory; - score: number; - }>; - - scored.sort((a, b) => b.score - a.score); - - return { - success: true, - matches: scored.slice(0, limit), - }; - }, - ); -} diff --git a/src/functions/sliding-window.ts b/src/functions/sliding-window.ts deleted file mode 100644 index 1de03dfc..00000000 --- a/src/functions/sliding-window.ts +++ /dev/null @@ -1,267 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - CompressedObservation, - EnrichedChunk, - MemoryProvider, -} from "../types.js"; -import { KV, generateId } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; -import { logger } from "../logger.js"; - -const SLIDING_WINDOW_SYSTEM = `You are a contextual enrichment engine. Given a primary observation and its surrounding context window (previous and next observations from the same session), produce an enriched version. - -Your tasks: -1. ENTITY RESOLUTION: Replace all pronouns, implicit references ("that framework", "the file", "it", "he/she") with the explicit entity names found in the context window. -2. PREFERENCE MAPPING: Extract any user preferences, constraints, or opinions expressed directly or indirectly. -3. CONTEXT BRIDGES: Add brief contextual links that make this chunk self-contained without reading adjacent chunks. - -Output EXACTLY this XML: -<enriched> - <content>The fully enriched, self-contained text with all references resolved</content> - <resolved_entities> - <entity original="pronoun or reference" resolved="explicit entity name"/> - </resolved_entities> - <preferences> - <preference>extracted user preference or constraint</preference> - </preferences> - <context_bridges> - <bridge>contextual link to adjacent information</bridge> - </context_bridges> -</enriched> - -Rules: -- The enriched content MUST be understandable in complete isolation -- Resolve ALL ambiguous references using the context window -- Do not hallucinate entities not present in the window -- Preserve factual accuracy while adding clarity`; - -function buildWindowPrompt( - primary: CompressedObservation, - before: CompressedObservation[], - after: CompressedObservation[], -): string { - const parts: string[] = []; - - if (before.length > 0) { - parts.push("=== PRECEDING CONTEXT ==="); - for (const obs of before) { - parts.push(`[${obs.type}] ${obs.title}: ${obs.narrative}`); - if (obs.facts.length > 0) parts.push(`Facts: ${obs.facts.join("; ")}`); - if (obs.concepts.length > 0) - parts.push(`Concepts: ${obs.concepts.join(", ")}`); - } - } - - parts.push("\n=== PRIMARY OBSERVATION (enrich this) ==="); - parts.push(`Type: ${primary.type}`); - parts.push(`Title: ${primary.title}`); - if (primary.subtitle) parts.push(`Subtitle: ${primary.subtitle}`); - parts.push(`Narrative: ${primary.narrative}`); - if (primary.facts.length > 0) - parts.push(`Facts: ${primary.facts.join("; ")}`); - if (primary.concepts.length > 0) - parts.push(`Concepts: ${primary.concepts.join(", ")}`); - if (primary.files.length > 0) - parts.push(`Files: ${primary.files.join(", ")}`); - - if (after.length > 0) { - parts.push("\n=== FOLLOWING CONTEXT ==="); - for (const obs of after) { - parts.push(`[${obs.type}] ${obs.title}: ${obs.narrative}`); - if (obs.facts.length > 0) parts.push(`Facts: ${obs.facts.join("; ")}`); - } - } - - return parts.join("\n"); -} - -function parseEnrichedXml(xml: string): { - content: string; - resolvedEntities: Record<string, string>; - preferences: string[]; - contextBridges: string[]; -} | null { - const contentMatch = xml.match(/<content>([\s\S]*?)<\/content>/); - if (!contentMatch) return null; - - const resolvedEntities: Record<string, string> = {}; - const entityRegex = - /<entity\s+original="([^"]+)"\s+resolved="([^"]+)"\s*\/>/g; - let match; - while ((match = entityRegex.exec(xml)) !== null) { - resolvedEntities[match[1]] = match[2]; - } - - const preferences: string[] = []; - const prefRegex = /<preference>([^<]+)<\/preference>/g; - while ((match = prefRegex.exec(xml)) !== null) { - preferences.push(match[1]); - } - - const contextBridges: string[] = []; - const bridgeRegex = /<bridge>([^<]+)<\/bridge>/g; - while ((match = bridgeRegex.exec(xml)) !== null) { - contextBridges.push(match[1]); - } - - return { - content: contentMatch[1].trim(), - resolvedEntities, - preferences, - contextBridges, - }; -} - -export function registerSlidingWindowFunction( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, -): void { - sdk.registerFunction("mem::enrich-window", - async (data: { - observationId: string; - sessionId: string; - lookback?: number; - lookahead?: number; - }) => { - if ( - !data || - typeof data.sessionId !== "string" || - !data.sessionId.trim() || - typeof data.observationId !== "string" || - !data.observationId.trim() - ) { - return { success: false, error: "sessionId and observationId are required" }; - } - const sessionId = data.sessionId.trim(); - const observationId = data.observationId.trim(); - const hprev = data.lookback ?? 3; - const hnext = data.lookahead ?? 2; - - const allObs = await kv.list<CompressedObservation>( - KV.observations(sessionId), - ); - allObs.sort( - (a, b) => - new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(), - ); - - const primaryIdx = allObs.findIndex((o) => o.id === observationId); - if (primaryIdx === -1) { - return { success: false, error: "Observation not found" }; - } - - const primary = allObs[primaryIdx]; - const before = allObs.slice(Math.max(0, primaryIdx - hprev), primaryIdx); - const after = allObs.slice(primaryIdx + 1, primaryIdx + 1 + hnext); - - if (before.length === 0 && after.length === 0) { - return { - success: true, - enriched: null, - reason: "No adjacent context available", - }; - } - - try { - const prompt = buildWindowPrompt(primary, before, after); - const response = await provider.compress( - SLIDING_WINDOW_SYSTEM, - prompt, - ); - const parsed = parseEnrichedXml(response); - - if (!parsed) { - logger.warn("Failed to parse enrichment XML", { - obsId: data.observationId, - }); - return { success: false, error: "parse_failed" }; - } - - const enriched: EnrichedChunk = { - id: generateId("ec"), - originalObsId: observationId, - sessionId, - content: parsed.content, - resolvedEntities: parsed.resolvedEntities, - preferences: parsed.preferences, - contextBridges: parsed.contextBridges, - windowStart: Math.max(0, primaryIdx - hprev), - windowEnd: Math.min(allObs.length - 1, primaryIdx + hnext), - createdAt: new Date().toISOString(), - }; - - await kv.set( - KV.enrichedChunks(sessionId), - observationId, - enriched, - ); - await recordAudit(kv, "observe", "mem::enrich-window", [enriched.id], { - action: "persist_enriched_chunk", - sessionId, - observationId, - }); - - logger.info("Observation enriched via sliding window", { - obsId: observationId, - entitiesResolved: Object.keys(parsed.resolvedEntities).length, - preferencesFound: parsed.preferences.length, - bridges: parsed.contextBridges.length, - }); - - return { success: true, enriched }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Sliding window enrichment failed", { error: msg }); - return { success: false, error: msg }; - } - }, - ); - - sdk.registerFunction("mem::enrich-session", - async (data: { - sessionId: string; - lookback?: number; - lookahead?: number; - minImportance?: number; - }) => { - if (!data || typeof data.sessionId !== "string" || !data.sessionId.trim()) { - return { success: false, error: "sessionId is required" }; - } - const sessionId = data.sessionId.trim(); - const allObs = await kv.list<CompressedObservation>( - KV.observations(sessionId), - ); - const minImp = data.minImportance ?? 4; - const toEnrich = allObs.filter((o) => o.importance >= minImp); - - let enriched = 0; - let failed = 0; - - for (const obs of toEnrich) { - try { - const result = (await sdk.trigger({ function_id: "mem::enrich-window", payload: { - observationId: obs.id, - sessionId, - lookback: data.lookback ?? 3, - lookahead: data.lookahead ?? 2, - } })) as { success?: boolean } | undefined; - if (result?.success) enriched++; - else failed++; - } catch { - failed++; - } - } - - logger.info("Session enrichment complete", { - sessionId, - total: toEnrich.length, - enriched, - failed, - }); - - return { success: true, total: toEnrich.length, enriched, failed }; - }, - ); -} diff --git a/src/functions/slots.ts b/src/functions/slots.ts deleted file mode 100644 index 47b49d49..00000000 --- a/src/functions/slots.ts +++ /dev/null @@ -1,487 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { MemorySlot, CompressedObservation } from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import { recordAudit } from "./audit.js"; -import { getEnvVar } from "../config.js"; -import { logger } from "../logger.js"; - -type SlotScope = "project" | "global"; - -const DEFAULT_SIZE_LIMIT = 2000; - -export const DEFAULT_SLOTS: ReadonlyArray< - Omit<MemorySlot, "createdAt" | "updatedAt"> -> = [ - { - label: "persona", - content: "", - sizeLimit: 1000, - description: - "How the agent should see itself: role, tone, behavioural guidelines.", - pinned: true, - readOnly: false, - scope: "global", - }, - { - label: "user_preferences", - content: "", - sizeLimit: 2000, - description: - "Coding style, tool preferences, naming conventions, and other habits the user wants preserved across sessions.", - pinned: true, - readOnly: false, - scope: "global", - }, - { - label: "tool_guidelines", - content: "", - sizeLimit: 1500, - description: - "Rules the agent should follow when picking or sequencing tools (e.g. prefer X over Y, never run Z without confirmation).", - pinned: true, - readOnly: false, - scope: "global", - }, - { - label: "project_context", - content: "", - sizeLimit: 3000, - description: - "Architecture decisions, codebase conventions, build/test commands, and cross-cutting constraints for the current project.", - pinned: true, - readOnly: false, - scope: "project", - }, - { - label: "guidance", - content: "", - sizeLimit: 1500, - description: - "Active advice for the next session: what to focus on, what to avoid, open risks.", - pinned: true, - readOnly: false, - scope: "project", - }, - { - label: "pending_items", - content: "", - sizeLimit: 2000, - description: - "Unfinished work, explicit TODOs, and promises made but not yet delivered.", - pinned: true, - readOnly: false, - scope: "project", - }, - { - label: "session_patterns", - content: "", - sizeLimit: 1500, - description: - "Recurring behaviours and common struggles observed across recent sessions.", - pinned: false, - readOnly: false, - scope: "project", - }, - { - label: "self_notes", - content: "", - sizeLimit: 1500, - description: - "Free-form notes the agent keeps for itself: hypotheses, dead ends, things to revisit.", - pinned: false, - readOnly: false, - scope: "project", - }, -]; - -// Read merged env so values loaded from ~/.agentmemory/.env are -// honoured. process.env alone misses .env-only exports (#678). -export function isSlotsEnabled(): boolean { - return getEnvVar("AGENTMEMORY_SLOTS") === "true"; -} - -export function isReflectEnabled(): boolean { - return getEnvVar("AGENTMEMORY_REFLECT") === "true"; -} - -function scopeKv(scope: SlotScope): string { - return scope === "global" ? KV.globalSlots : KV.slots; -} - -function nowIso(): string { - return new Date().toISOString(); -} - -function validateLabel(label: unknown): string | null { - if (typeof label !== "string") return null; - const trimmed = label.trim(); - if (!trimmed || trimmed.length > 64) return null; - if (!/^[a-z][a-z0-9_]*$/.test(trimmed)) return null; - return trimmed; -} - -async function readSlot( - kv: StateKV, - label: string, -): Promise<{ slot: MemorySlot | null; scope: SlotScope }> { - const project = await kv.get<MemorySlot>(KV.slots, label); - if (project) return { slot: project, scope: "project" }; - const global = await kv.get<MemorySlot>(KV.globalSlots, label); - if (global) return { slot: global, scope: "global" }; - return { slot: null, scope: "project" }; -} - -async function readSlotInScope( - kv: StateKV, - label: string, - scope: SlotScope, -): Promise<MemorySlot | null> { - return kv.get<MemorySlot>(scopeKv(scope), label); -} - -function validateScope(raw: unknown): SlotScope | null { - if (raw === undefined || raw === null) return "project"; - if (raw === "project" || raw === "global") return raw; - return null; -} - -function validateSizeLimit(raw: unknown): number | null | undefined { - if (raw === undefined || raw === null) return DEFAULT_SIZE_LIMIT; - if (typeof raw !== "number") return null; - if (!Number.isInteger(raw) || raw < 1 || raw > 20000) return null; - return raw; -} - -async function seedDefaults(kv: StateKV): Promise<void> { - const ts = nowIso(); - for (const tmpl of DEFAULT_SLOTS) { - const target = scopeKv(tmpl.scope); - const existing = await kv.get<MemorySlot>(target, tmpl.label); - if (existing) continue; - const slot: MemorySlot = { - ...tmpl, - createdAt: ts, - updatedAt: ts, - }; - await kv.set(target, tmpl.label, slot); - } -} - -export async function listPinnedSlots(kv: StateKV): Promise<MemorySlot[]> { - const [project, global] = await Promise.all([ - kv.list<MemorySlot>(KV.slots), - kv.list<MemorySlot>(KV.globalSlots), - ]); - const merged = new Map<string, MemorySlot>(); - for (const s of global) merged.set(s.label, s); - for (const s of project) merged.set(s.label, s); - return Array.from(merged.values()) - .filter((s) => s.pinned && s.content.trim().length > 0) - .sort((a, b) => a.label.localeCompare(b.label)); -} - -export function renderPinnedContext(slots: MemorySlot[]): string { - if (slots.length === 0) return ""; - const lines: string[] = ["# agentmemory pinned slots", ""]; - for (const slot of slots) { - lines.push(`## ${slot.label}`); - lines.push(slot.content.trim()); - lines.push(""); - } - return lines.join("\n"); -} - -export function registerSlotsFunctions(sdk: ISdk, kv: StateKV): void { - void seedDefaults(kv).catch((err) => { - logger.warn("slot defaults seed failed", { - error: err instanceof Error ? err.message : String(err), - }); - }); - - sdk.registerFunction("mem::slot-list", async () => { - const [project, global] = await Promise.all([ - kv.list<MemorySlot>(KV.slots), - kv.list<MemorySlot>(KV.globalSlots), - ]); - const merged = new Map<string, MemorySlot>(); - for (const s of global) merged.set(s.label, s); - for (const s of project) merged.set(s.label, s); - const slots = Array.from(merged.values()).sort((a, b) => - a.label.localeCompare(b.label), - ); - return { success: true, slots }; - }); - - sdk.registerFunction( - "mem::slot-get", - async (data: { label?: string }) => { - const label = validateLabel(data?.label); - if (!label) return { success: false, error: "label required (lowercase, starts with letter, [a-z0-9_])" }; - const { slot, scope } = await readSlot(kv, label); - if (!slot) return { success: false, error: "slot not found" }; - return { success: true, slot, scope }; - }, - ); - - sdk.registerFunction( - "mem::slot-create", - async (data: { - label?: string; - content?: string; - sizeLimit?: number; - description?: string; - pinned?: boolean; - scope?: SlotScope; - }) => { - const label = validateLabel(data?.label); - if (!label) return { success: false, error: "label required (lowercase, starts with letter, [a-z0-9_])" }; - const scope = validateScope(data?.scope); - if (!scope) return { success: false, error: "scope must be 'project' or 'global'" }; - const sizeLimit = validateSizeLimit(data?.sizeLimit); - if (sizeLimit === null) { - return { success: false, error: "sizeLimit must be an integer between 1 and 20000" }; - } - const content = typeof data?.content === "string" ? data.content : ""; - if (content.length > sizeLimit) { - return { success: false, error: `content exceeds sizeLimit (${content.length} > ${sizeLimit})` }; - } - const description = typeof data?.description === "string" ? data.description : ""; - const pinned = typeof data?.pinned === "boolean" ? data.pinned : true; - return withKeyedLock(`slot:${label}`, async () => { - // Duplicate check is scope-local so a project slot can shadow a - // global slot with the same label — matches the read precedence. - const existing = await readSlotInScope(kv, label, scope); - if (existing) return { success: false, error: `slot already exists in ${scope} scope` }; - const ts = nowIso(); - const slot: MemorySlot = { - label, - content, - sizeLimit: sizeLimit as number, - description, - pinned, - readOnly: false, - scope, - createdAt: ts, - updatedAt: ts, - }; - await kv.set(scopeKv(scope), label, slot); - await recordAudit(kv, "slot_create", "mem::slot-create", [label], { - scope, - sizeLimit: slot.sizeLimit, - pinned: slot.pinned, - }); - return { success: true, slot }; - }); - }, - ); - - sdk.registerFunction( - "mem::slot-append", - async (data: { label?: string; text?: string }) => { - const label = validateLabel(data?.label); - if (!label) return { success: false, error: "label required" }; - const text = typeof data?.text === "string" ? data.text : ""; - if (!text) return { success: false, error: "text required" }; - return withKeyedLock(`slot:${label}`, async () => { - const { slot, scope } = await readSlot(kv, label); - if (!slot) return { success: false, error: "slot not found (use mem::slot-create first)" }; - if (slot.readOnly) return { success: false, error: "slot is read-only" }; - const sep = slot.content && !slot.content.endsWith("\n") ? "\n" : ""; - const next = `${slot.content}${sep}${text}`; - if (next.length > slot.sizeLimit) { - return { - success: false, - error: `append would exceed sizeLimit (${next.length} > ${slot.sizeLimit}). Use mem::slot-replace to compact first.`, - currentSize: slot.content.length, - sizeLimit: slot.sizeLimit, - }; - } - const updated: MemorySlot = { ...slot, content: next, updatedAt: nowIso() }; - await kv.set(scopeKv(scope), label, updated); - await recordAudit(kv, "slot_append", "mem::slot-append", [label], { - scope, - added: text.length, - total: next.length, - }); - return { success: true, slot: updated, size: next.length }; - }); - }, - ); - - sdk.registerFunction( - "mem::slot-replace", - async (data: { label?: string; content?: string }) => { - const label = validateLabel(data?.label); - if (!label) return { success: false, error: "label required" }; - if (typeof data?.content !== "string") return { success: false, error: "content required (string)" }; - return withKeyedLock(`slot:${label}`, async () => { - const { slot, scope } = await readSlot(kv, label); - if (!slot) return { success: false, error: "slot not found (use mem::slot-create first)" }; - if (slot.readOnly) return { success: false, error: "slot is read-only" }; - if (data.content.length > slot.sizeLimit) { - return { - success: false, - error: `content exceeds sizeLimit (${data.content.length} > ${slot.sizeLimit})`, - sizeLimit: slot.sizeLimit, - }; - } - const updated: MemorySlot = { ...slot, content: data.content, updatedAt: nowIso() }; - await kv.set(scopeKv(scope), label, updated); - await recordAudit(kv, "slot_replace", "mem::slot-replace", [label], { - scope, - before: slot.content.length, - after: data.content.length, - }); - return { success: true, slot: updated, size: data.content.length }; - }); - }, - ); - - sdk.registerFunction( - "mem::slot-delete", - async (data: { label?: string }) => { - const label = validateLabel(data?.label); - if (!label) return { success: false, error: "label required" }; - return withKeyedLock(`slot:${label}`, async () => { - const { slot, scope } = await readSlot(kv, label); - if (!slot) return { success: false, error: "slot not found" }; - if (slot.readOnly) return { success: false, error: "slot is read-only" }; - await kv.delete(scopeKv(scope), label); - await recordAudit(kv, "slot_delete", "mem::slot-delete", [label], { - scope, - size: slot.content.length, - }); - return { success: true }; - }); - }, - ); - - sdk.registerFunction( - "mem::slot-reflect", - async (data: { sessionId?: string; maxObservations?: number }) => { - if (!data?.sessionId || typeof data.sessionId !== "string") { - return { success: false, error: "sessionId required" }; - } - const max = - typeof data.maxObservations === "number" && - Number.isInteger(data.maxObservations) && - data.maxObservations > 0 - ? Math.min(200, data.maxObservations) - : 50; - const observations = await kv.list<CompressedObservation>( - KV.observations(data.sessionId), - ); - if (observations.length === 0) { - return { success: true, applied: 0, reason: "no observations for session" }; - } - const recent = observations - .slice() - .sort((a, b) => (b.timestamp || "").localeCompare(a.timestamp || "")) - .slice(0, max); - - const pendingLines: string[] = []; - const patternCounts = new Map<string, number>(); - const files = new Set<string>(); - for (const obs of recent) { - const title = (obs.title || "").toLowerCase(); - const narrative = (obs.narrative || "").toLowerCase(); - if (narrative.includes("todo") || title.includes("todo")) { - pendingLines.push(`- ${obs.title || obs.id}`); - } - if (obs.type === "error") { - patternCounts.set("errors", (patternCounts.get("errors") ?? 0) + 1); - } - if (obs.type === "command_run") { - patternCounts.set("commands", (patternCounts.get("commands") ?? 0) + 1); - } - if (obs.files) for (const f of obs.files) files.add(f); - } - - let applied = 0; - - if (pendingLines.length > 0) { - const pendingApplied = await withKeyedLock(`slot:pending_items`, async () => { - const { slot, scope } = await readSlot(kv, "pending_items"); - if (!slot) return false; - const already = new Set(slot.content.split("\n")); - const fresh = pendingLines.filter((line) => !already.has(line)); - if (fresh.length === 0) return false; - const sep = slot.content && !slot.content.endsWith("\n") ? "\n" : ""; - const next = `${slot.content}${sep}${fresh.join("\n")}`; - const truncated = next.length > slot.sizeLimit - ? next.slice(next.length - slot.sizeLimit) - : next; - await kv.set(scopeKv(scope), "pending_items", { - ...slot, - content: truncated, - updatedAt: nowIso(), - }); - return true; - }); - if (pendingApplied) applied++; - } - - if (patternCounts.size > 0) { - const patternsApplied = await withKeyedLock(`slot:session_patterns`, async () => { - const { slot, scope } = await readSlot(kv, "session_patterns"); - if (!slot) return false; - const summary = [ - `last reflection: ${nowIso()}`, - ...Array.from(patternCounts.entries()).map( - ([kind, count]) => `- ${kind}: ${count} in last ${recent.length} observations`, - ), - ].join("\n"); - const next = - summary.length > slot.sizeLimit ? summary.slice(0, slot.sizeLimit) : summary; - await kv.set(scopeKv(scope), "session_patterns", { - ...slot, - content: next, - updatedAt: nowIso(), - }); - return true; - }); - if (patternsApplied) applied++; - } - - if (files.size > 0) { - const ctxApplied = await withKeyedLock(`slot:project_context`, async () => { - const { slot, scope } = await readSlot(kv, "project_context"); - if (!slot) return false; - const already = slot.content; - const fresh = Array.from(files) - .filter((f) => !already.includes(f)) - .slice(0, 20); - if (fresh.length === 0) return false; - const header = - already.length === 0 ? "Files touched in recent sessions:" : ""; - const sep = already && !already.endsWith("\n") ? "\n" : ""; - const nextRaw = `${already}${sep}${header ? header + "\n" : ""}${fresh - .map((f) => `- ${f}`) - .join("\n")}`; - const next = - nextRaw.length > slot.sizeLimit - ? nextRaw.slice(nextRaw.length - slot.sizeLimit) - : nextRaw; - await kv.set(scopeKv(scope), "project_context", { - ...slot, - content: next, - updatedAt: nowIso(), - }); - return true; - }); - if (ctxApplied) applied++; - } - - if (applied > 0) { - await recordAudit(kv, "slot_reflect", "mem::slot-reflect", [data.sessionId], { - observationCount: recent.length, - slotsUpdated: applied, - }); - } - - return { success: true, applied, observationsReviewed: recent.length }; - }, - ); -} diff --git a/src/functions/smart-search.ts b/src/functions/smart-search.ts deleted file mode 100644 index fbdaad0d..00000000 --- a/src/functions/smart-search.ts +++ /dev/null @@ -1,216 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - CompactLessonResult, - CompactSearchResult, - CompressedObservation, - HybridSearchResult, - Lesson, -} from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { recordAccessBatch } from "./access-tracker.js"; -import { getAgentId, isAgentScopeIsolated } from "../config.js"; -import { logger } from "../logger.js"; - -// Compact mode trims each lesson's content for at-a-glance display. The -// full content is fetched via memory_lesson_recall when the caller needs it. -const LESSON_CONTENT_PREVIEW_CHARS = 240; - -export function registerSmartSearchFunction( - sdk: ISdk, - kv: StateKV, - searchFn: (query: string, limit: number) => Promise<HybridSearchResult[]>, -): void { - sdk.registerFunction("mem::smart-search", - async (data: { - query?: string; - expandIds?: Array<string | { obsId: string; sessionId: string }>; - limit?: number; - project?: string; - includeLessons?: boolean; - // optional per-call agent filter for runtimes routing many - // roles through one server. "*" opts out of the env-default - // scope and returns hits from every agent. - agentId?: string; - }) => { - - // Compute the agent filter once, up front. Both the expandIds - // branch and the hybrid-search branch consult it — otherwise - // expandIds becomes a cross-agent leak (#554 follow-up). - const isolated = isAgentScopeIsolated(); - const explicitAgentId = - typeof data.agentId === "string" && data.agentId.trim().length > 0 - ? data.agentId.trim() - : undefined; - const wildcardAgent = explicitAgentId === "*"; - const filterAgentId = wildcardAgent - ? undefined - : explicitAgentId ?? (isolated ? getAgentId() : undefined); - - if (data.expandIds && data.expandIds.length > 0) { - const raw = data.expandIds.slice(0, 20); - const items = raw.map((entry) => { - if (typeof entry === "string") return { obsId: entry, sessionId: undefined as string | undefined }; - if (entry && typeof entry === "object" && typeof (entry as any).obsId === "string") { - return { obsId: (entry as any).obsId, sessionId: (entry as any).sessionId as string | undefined }; - } - return null; - }).filter((item): item is NonNullable<typeof item> => item !== null); - - const expanded: Array<{ - obsId: string; - sessionId: string; - observation: CompressedObservation; - }> = []; - - const results = await Promise.all( - items.map(({ obsId, sessionId }) => - findObservation(kv, obsId, sessionId).then((obs) => - obs ? { obsId, sessionId: obs.sessionId, observation: obs } : null, - ), - ), - ); - for (const r of results) { - if (r) expanded.push(r); - } - - const scoped = filterAgentId - ? expanded.filter((e) => e.observation.agentId === filterAgentId) - : expanded; - - void recordAccessBatch( - kv, - scoped.map((e) => e.observation.id), - ); - - const truncated = data.expandIds.length > raw.length; - logger.info("Smart search expanded", { - requested: data.expandIds.length, - attempted: raw.length, - returned: scoped.length, - filteredOutOfScope: expanded.length - scoped.length, - truncated, - }); - return { mode: "expanded", results: scoped, truncated }; - } - - if (!data.query || typeof data.query !== "string" || !data.query.trim()) { - return { mode: "compact", results: [], error: "query is required" }; - } - - const limit = Math.max(1, Math.min(data.limit ?? 20, 100)); - // Lesson recall stays capped: lessons are denser than raw - // observations so 10 covers most recall flows. - const lessonLimit = Math.min(limit, 10); - const includeLessons = data.includeLessons !== false; - - // Over-fetch when filtering. Hybrid search can't filter on - // agentId (BM25/vector indexes don't carry it), so we ask the - // searcher for more hits than we need and trim post-filter. 3× - // is a defensible middle ground: enough headroom for a small - // workload, capped at 300 so a 100-limit request never asks for - // thousands of hits. - const overFetchLimit = filterAgentId - ? Math.min(limit * 3, 300) - : limit; - - const [hybridResults, lessons] = await Promise.all([ - searchFn(data.query, overFetchLimit), - includeLessons - ? recallLessons(sdk, data.query, lessonLimit, data.project) - : Promise.resolve([]), - ]); - - const filteredHybrid = filterAgentId - ? hybridResults - .filter((r) => r.observation.agentId === filterAgentId) - .slice(0, limit) - : hybridResults.slice(0, limit); - - const compact: CompactSearchResult[] = filteredHybrid.map((r) => ({ - obsId: r.observation.id, - sessionId: r.sessionId, - title: r.observation.title, - type: r.observation.type, - score: r.combinedScore, - timestamp: r.observation.timestamp, - })); - - void recordAccessBatch( - kv, - compact.map((r) => r.obsId), - ); - - logger.info("Smart search compact", { - query: data.query, - results: compact.length, - lessons: lessons.length, - }); - const response: { - mode: "compact"; - results: CompactSearchResult[]; - lessons?: CompactLessonResult[]; - } = { mode: "compact", results: compact }; - if (includeLessons) response.lessons = lessons; - return response; - }, - ); -} - -async function recallLessons( - sdk: ISdk, - query: string, - limit: number, - project?: string, -): Promise<CompactLessonResult[]> { - try { - const result = (await sdk.trigger({ - function_id: "mem::lesson-recall", - payload: { query, limit, project }, - })) as { success?: boolean; lessons?: Array<Lesson & { score?: number }> }; - if (!result?.success || !Array.isArray(result.lessons)) return []; - return result.lessons.map((l) => ({ - lessonId: l.id, - content: - l.content.length > LESSON_CONTENT_PREVIEW_CHARS - ? l.content.slice(0, LESSON_CONTENT_PREVIEW_CHARS) + "…" - : l.content, - confidence: l.confidence, - score: l.score ?? l.confidence, - createdAt: l.createdAt, - project: l.project, - tags: l.tags ?? [], - })); - } catch (err) { - logger.warn("Smart search: mem::lesson-recall failed; returning empty lesson list", { - error: err instanceof Error ? err.message : String(err), - }); - return []; - } -} - -async function findObservation( - kv: StateKV, - obsId: string, - sessionIdHint?: string, -): Promise<CompressedObservation | null> { - if (sessionIdHint) { - const obs = await kv - .get<CompressedObservation>(KV.observations(sessionIdHint), obsId) - .catch(() => null); - if (obs) return obs; - } - - const sessions = await kv.list<{ id: string }>(KV.sessions); - for (let i = 0; i < sessions.length; i += 5) { - const batch = sessions.slice(i, i + 5); - const results = await Promise.all( - batch.map((s) => - kv.get<CompressedObservation>(KV.observations(s.id), obsId).catch(() => null), - ), - ); - const found = results.find((r) => r !== null); - if (found) return found; - } - return null; -} diff --git a/src/functions/snapshot.ts b/src/functions/snapshot.ts deleted file mode 100644 index 40430ce5..00000000 --- a/src/functions/snapshot.ts +++ /dev/null @@ -1,232 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import { execFile } from "node:child_process"; -import { promisify } from "node:util"; -import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs"; -import { join } from "node:path"; -import type { - SnapshotMeta, - Session, - Memory, - GraphNode, - AccessLogExport, -} from "../types.js"; -import { KV, generateId } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; -import { VERSION } from "../version.js"; -import { logger } from "../logger.js"; - -const COMMIT_HASH_RE = /^[0-9a-f]{7,40}$/i; - -const execFileAsync = promisify(execFile); - -async function gitExec(dir: string, args: string[]): Promise<string> { - const { stdout } = await execFileAsync("git", args, { cwd: dir }); - return stdout.trim(); -} - -async function ensureGitRepo(dir: string): Promise<void> { - if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }); - } - if (!existsSync(join(dir, ".git"))) { - await gitExec(dir, ["init"]); - await gitExec(dir, ["config", "user.email", "agentmemory@local"]); - await gitExec(dir, ["config", "user.name", "agentmemory"]); - } -} - -export function registerSnapshotFunction( - sdk: ISdk, - kv: StateKV, - snapshotDir: string, -): void { - sdk.registerFunction("mem::snapshot-create", - async (data?: { message?: string }) => { - - try { - await ensureGitRepo(snapshotDir); - const ts = new Date().toISOString(); - - const sessions = await kv.list<Session>(KV.sessions); - const memories = await kv.list<Memory>(KV.memories); - const graphNodes = await kv.list<GraphNode>(KV.graphNodes); - const accessLogs = await kv - .list<AccessLogExport>(KV.accessLog) - .catch(() => [] as AccessLogExport[]); - - const observations: Record<string, unknown[]> = {}; - for (const session of sessions) { - const obs = await kv - .list(KV.observations(session.id)) - .catch(() => []); - if (obs.length > 0) { - observations[session.id] = obs; - } - } - - const state = { - version: VERSION, - timestamp: ts, - sessions, - memories, - graphNodes, - observations, - accessLogs, - }; - - writeFileSync( - join(snapshotDir, "state.json"), - JSON.stringify(state, null, 2), - "utf-8", - ); - - await gitExec(snapshotDir, ["add", "."]); - - const message = data?.message || `Snapshot ${ts}`; - try { - await gitExec(snapshotDir, ["commit", "-m", message]); - } catch (commitErr) { - const errMsg = - commitErr instanceof Error ? commitErr.message : String(commitErr); - if (errMsg.includes("nothing to commit")) { - return { success: true, message: "No changes to snapshot" }; - } - throw commitErr; - } - - const commitHash = await gitExec(snapshotDir, ["rev-parse", "HEAD"]); - - const meta: SnapshotMeta = { - id: generateId("snap"), - commitHash, - createdAt: ts, - message, - stats: { - sessions: sessions.length, - observations: Object.values(observations).reduce( - (sum, arr) => sum + arr.length, - 0, - ), - memories: memories.length, - graphNodes: graphNodes.length, - }, - }; - - await recordAudit(kv, "export", "mem::snapshot-create", [meta.id], { - commitHash, - stats: meta.stats, - }); - - logger.info("Snapshot created", { commitHash }); - return { success: true, snapshot: meta }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Snapshot failed", { error: msg }); - return { success: false, error: msg }; - } - }, - ); - - sdk.registerFunction("mem::snapshot-list", async () => { - try { - if (!existsSync(join(snapshotDir, ".git"))) { - return { snapshots: [] }; - } - const log = await gitExec(snapshotDir, [ - "log", - "--format=%H|%aI|%s", - "-20", - ]); - const snapshots = log - .split("\n") - .filter(Boolean) - .map((line) => { - const parts = line.split("|"); - const [hash, date] = parts; - const msg = parts.slice(2).join("|"); - return { commitHash: hash, createdAt: date, message: msg }; - }); - return { snapshots }; - } catch { - return { snapshots: [] }; - } - }); - - sdk.registerFunction("mem::snapshot-restore", - async (data: { commitHash: string } | undefined) => { - if (!data || typeof data.commitHash !== "string" || !data.commitHash.trim()) { - return { success: false, error: "commitHash is required" }; - } - if (!COMMIT_HASH_RE.test(data.commitHash)) { - return { success: false, error: "Invalid commitHash format" }; - } - - try { - await gitExec(snapshotDir, [ - "checkout", - data.commitHash, - "--", - "state.json", - ]); - const content = readFileSync(join(snapshotDir, "state.json"), "utf-8"); - const state = JSON.parse(content) as { - sessions?: Array<{ id: string } & Record<string, unknown>>; - memories?: Array<{ id: string } & Record<string, unknown>>; - graphNodes?: Array<{ id: string } & Record<string, unknown>>; - observations?: Record< - string, - Array<{ id: string } & Record<string, unknown>> - >; - accessLogs?: AccessLogExport[]; - }; - - if (state.sessions) { - for (const session of state.sessions) { - await kv.set(KV.sessions, session.id, session); - } - } - if (state.memories) { - for (const memory of state.memories) { - await kv.set(KV.memories, memory.id, memory); - } - } - if (state.graphNodes) { - for (const node of state.graphNodes) { - await kv.set(KV.graphNodes, node.id, node); - } - } - if (state.observations) { - for (const [sessionId, obs] of Object.entries(state.observations)) { - for (const o of obs) { - await kv.set(KV.observations(sessionId), o.id, o); - } - } - } - if (state.accessLogs) { - for (const log of state.accessLogs) { - await kv.set(KV.accessLog, log.memoryId, log); - } - } - - await gitExec(snapshotDir, ["checkout", "HEAD", "--", "state.json"]); - - await recordAudit(kv, "import", "mem::snapshot-restore", [], { - commitHash: data.commitHash, - sessions: state.sessions?.length || 0, - memories: state.memories?.length || 0, - graphNodes: state.graphNodes?.length || 0, - }); - - logger.info("Snapshot restored", { - commitHash: data.commitHash, - }); - return { success: true, commitHash: data.commitHash }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Snapshot restore failed", { error: msg }); - return { success: false, error: msg }; - } - }, - ); -} diff --git a/src/functions/source-scan-codex.ts b/src/functions/source-scan-codex.ts deleted file mode 100644 index 858b3716..00000000 --- a/src/functions/source-scan-codex.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { homedir } from "node:os"; -import { join } from "node:path"; -import { lstat } from "node:fs/promises"; -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import type { ScanCheckpoint, Source } from "../types.js"; -import { KV, fingerprintId } from "../state/schema.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import { - MAX_FILES_DEFAULT, - findJsonlFiles, - ingestJsonlFile, - isSensitive, - isSymlink, -} from "./replay.js"; -import { safeAudit } from "./audit.js"; -import { logger } from "../logger.js"; - -const DEFAULT_CODEX_SOURCE_ID = "codex"; - -export interface CodexScanResult { - success: true; - sourceId: string; - scanned: number; - imported: number; - skipped: number; - errors: number; - sessionIds: string[]; -} - -type FileState = { mtimeMs: number; size: number }; - -function expandHome(p: string): string { - return p.startsWith("~") ? join(homedir(), p.slice(1)) : p; -} - -/** Both first-class Codex roots — the viewer already treats archived_sessions as source data. */ -function codexRoots(home: string): string[] { - return [join(home, "sessions"), join(home, "archived_sessions")]; -} - -async function getOrCreateSource(kv: StateKV, sourceId: string): Promise<Source> { - const existing = await kv.get<Source>(KV.sources, sourceId); - if (existing) return existing; - const source: Source = { - id: sourceId, - type: "codex", - name: "Codex sessions", - pathOrUrl: join(homedir(), ".codex"), - enabled: true, - }; - await kv.set(KV.sources, sourceId, source); - return source; -} - -function parseCursor(cursor: string | undefined): Record<string, FileState> { - if (!cursor) return {}; - try { - const parsed = JSON.parse(cursor) as unknown; - return parsed && typeof parsed === "object" ? (parsed as Record<string, FileState>) : {}; - } catch { - return {}; - } -} - -/** - * Incrementally scan a Codex source. Unchanged files (same mtime + size as the - * checkpoint) are skipped WITHOUT being read; new or grown files are ingested - * through the shared `ingestJsonlFile` path. A file is recorded in the - * checkpoint only on successful ingest, so a failed file stays absent and is - * retried on the next scan. Serialized per source via `withKeyedLock` so two - * concurrent scans (or a scan racing a manual import) can't clobber the - * checkpoint. - * - * Correctness (no duplicate todos) is owned by the ingest/dedup layer; the - * checkpoint exists only to avoid re-reading unchanged history. - */ -export async function scanCodexSource( - kv: StateKV, - opts: { sourceId?: string; path?: string } = {}, -): Promise<CodexScanResult> { - const sourceId = opts.sourceId ?? DEFAULT_CODEX_SOURCE_ID; - return withKeyedLock(`source-scan:${sourceId}`, async () => { - const source = await getOrCreateSource(kv, sourceId); - const result: CodexScanResult = { - success: true, - sourceId, - scanned: 0, - imported: 0, - skipped: 0, - errors: 0, - sessionIds: [], - }; - if (!source.enabled) return result; - - const roots = opts.path ? [expandHome(opts.path)] : codexRoots(source.pathOrUrl); - - const files: string[] = []; - for (const root of roots) { - try { - const st = await lstat(root); - if (!st.isDirectory()) continue; - } catch { - continue; // a root that doesn't exist is fine — just skip it - } - const found = await findJsonlFiles(root, MAX_FILES_DEFAULT); - files.push(...found.files); - } - - const checkpoint = await kv.get<ScanCheckpoint>(KV.scanCheckpoints, sourceId); - const processed = parseCursor(checkpoint?.cursor); - let lastError: string | undefined; - - for (const file of files) { - result.scanned++; - if (isSensitive(file)) continue; - if (await isSymlink(file)) continue; - - let stat; - try { - stat = await lstat(file); - } catch { - continue; - } - - const prev = processed[file]; - if (prev && prev.mtimeMs === stat.mtimeMs && prev.size === stat.size) { - result.skipped++; - continue; // unchanged — do NOT read it - } - - try { - // Path-derived fallback session id: a Codex file lacking session_meta.id - // keeps a stable id across scans (a full-text hash would mint a new - // session every time the transcript grows). - const ingested = await ingestJsonlFile(kv, file, fingerprintId("codex-file", file)); - if (ingested) { - result.imported++; - result.sessionIds.push(ingested.sessionId); - } - // Record only on success so a failed file is retried next scan. - processed[file] = { mtimeMs: stat.mtimeMs, size: stat.size }; - } catch (err) { - result.errors++; - lastError = `${file}: ${err instanceof Error ? err.message : String(err)}`; - logger.warn("source-scan: ingest failed", { sourceId, file, error: lastError }); - } - } - - const now = new Date().toISOString(); - await kv.set(KV.scanCheckpoints, sourceId, { - sourceId, - cursor: JSON.stringify(processed), - lastSuccessAt: now, - lastError, - }); - await kv.set(KV.sources, sourceId, { ...source, lastScannedAt: now }); - await safeAudit(kv, "import", "mem::source-scan::codex", result.sessionIds, { - source: sourceId, - scanned: result.scanned, - imported: result.imported, - skipped: result.skipped, - errors: result.errors, - }); - - return result; - }); -} - -export function registerSourceScanFunctions(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction( - "mem::source-scan::codex", - async (data: { sourceId?: string; path?: string }) => { - const sourceId = - typeof data?.sourceId === "string" && data.sourceId.length > 0 ? data.sourceId : undefined; - const path = typeof data?.path === "string" && data.path.length > 0 ? data.path : undefined; - return scanCodexSource(kv, { sourceId, path }); - }, - ); -} diff --git a/src/functions/summarize.ts b/src/functions/summarize.ts deleted file mode 100644 index ff1b92e1..00000000 --- a/src/functions/summarize.ts +++ /dev/null @@ -1,441 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - CompressedObservation, - SessionSummary, - MemoryProvider, - Session, -} from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { - SUMMARY_SYSTEM, - buildSummaryPrompt, - REDUCE_SYSTEM, - buildReducePrompt, -} from "../prompts/summary.js"; -import { getXmlTag, getXmlChildren } from "../prompts/xml.js"; -import { SummaryOutputSchema } from "../eval/schemas.js"; -import { validateOutput } from "../eval/validator.js"; -import { scoreSummary } from "../eval/quality.js"; -import type { MetricsStore } from "../eval/metrics-store.js"; -import { safeAudit } from "./audit.js"; -import { logger } from "../logger.js"; - -// Per-chunk observation budget when a session is too large to fit in one -// LLM call. Default ≈ 50k input tokens per chunk at ~110 tok/obs — fits -// comfortably in 128k-window models. Override via SUMMARIZE_CHUNK_SIZE. -const CHUNK_SIZE_DEFAULT = 400; -// Concurrent in-flight chunk calls. 6 keeps a 100-chunk session under -// iii's 180s function-invocation timeout at ~8s/call while staying -// inside generous-but-not-unlimited provider rate limits (well below -// OpenAI free tier's 500 RPM). High-throughput providers -// (Novita / DeepInfra / DeepSeek) typically allow 100+ concurrent — set -// SUMMARIZE_CHUNK_CONCURRENCY higher to cover ~1000+ chunk sessions. -const CHUNK_CONCURRENCY_DEFAULT = 6; -// Bail on the merged summary if more than this fraction of chunks fail -// to parse — a half-blind narrative is worse than a clean error. -const MAX_SKIP_RATIO = 0.5; - -function getChunkSize(): number { - const raw = process.env.SUMMARIZE_CHUNK_SIZE; - if (!raw) return CHUNK_SIZE_DEFAULT; - const n = parseInt(raw, 10); - return Number.isFinite(n) && n > 0 ? n : CHUNK_SIZE_DEFAULT; -} - -function getChunkConcurrency(): number { - const raw = process.env.SUMMARIZE_CHUNK_CONCURRENCY; - if (!raw) return CHUNK_CONCURRENCY_DEFAULT; - const n = parseInt(raw, 10); - return Number.isFinite(n) && n > 0 ? n : CHUNK_CONCURRENCY_DEFAULT; -} - -// One chunk call with retry-once. Returns null when both attempts fail — -// whether by parse failure, provider 4xx (content rejected by upstream -// filters), or transient network/5xx errors that didn't recover on retry. -// All failure modes are equivalent at this layer: the chunk is unusable, -// skip it and let the caller decide via the skip-ratio bailout whether -// the overall summary is still trustworthy. Errors that affect every -// chunk (auth, model down) will trip the bailout naturally. -async function summarizeChunkWithRetry( - provider: MemoryProvider, - chunk: CompressedObservation[], - sessionId: string, - project: string, - idx: number, - total: number, -): Promise<SessionSummary | null> { - for (let attempt = 1; attempt <= 2; attempt++) { - try { - const xml = await provider.summarize( - SUMMARY_SYSTEM, - buildSummaryPrompt(chunk), - ); - const parsed = parseSummaryXml(xml, sessionId, project, chunk.length); - if (parsed) return parsed; - logger.warn("Summarize chunk parse failed", { - sessionId, - chunk: `${idx + 1}/${total}`, - attempt, - }); - } catch (err) { - logger.warn("Summarize chunk LLM call failed", { - sessionId, - chunk: `${idx + 1}/${total}`, - attempt, - error: err instanceof Error ? err.message : String(err), - }); - } - } - return null; -} - -// Returns the final summary XML string. For sessions ≤ chunk size, this is -// a single LLM call (legacy behavior). For larger sessions, observations -// are split into chunks processed in parallel batches, each chunk retried -// once on parse failure, persistently-bad chunks skipped, and remaining -// partials merged via a reduce call. -async function produceSummaryXml( - provider: MemoryProvider, - compressed: CompressedObservation[], - sessionId: string, - project: string, -): Promise<{ - response: string; - mode: "single" | "chunked"; - chunks: number; - skipped?: number; -}> { - const chunkSize = getChunkSize(); - if (compressed.length <= chunkSize) { - const response = await provider.summarize( - SUMMARY_SYSTEM, - buildSummaryPrompt(compressed), - ); - return { response, mode: "single", chunks: 1 }; - } - - const chunks: CompressedObservation[][] = []; - for (let i = 0; i < compressed.length; i += chunkSize) { - chunks.push(compressed.slice(i, i + chunkSize)); - } - const concurrency = getChunkConcurrency(); - logger.info("Summarize chunking session", { - sessionId, - chunks: chunks.length, - chunkSize, - concurrency, - totalObservations: compressed.length, - }); - - // Sparse array preserves chunk → index mapping after parallel resolution, - // so the reduce step sees partials in chronological order even when some - // were skipped. - const partialByIdx: Array<SessionSummary | null> = new Array(chunks.length).fill(null); - for (let batchStart = 0; batchStart < chunks.length; batchStart += concurrency) { - const batch = chunks.slice(batchStart, batchStart + concurrency); - await Promise.all( - batch.map(async (chunk, j) => { - const idx = batchStart + j; - partialByIdx[idx] = await summarizeChunkWithRetry( - provider, - chunk, - sessionId, - project, - idx, - chunks.length, - ); - }), - ); - } - - const skipped = partialByIdx.filter((p) => p === null).length; - const partials = partialByIdx.filter((p): p is SessionSummary => p !== null); - - if (skipped > Math.floor(chunks.length * MAX_SKIP_RATIO)) { - throw new Error( - `too_many_chunks_skipped: ${skipped}/${chunks.length} chunks failed to parse after retry`, - ); - } - if (skipped > 0) { - logger.warn("Summarize chunks partially skipped", { - sessionId, - skipped, - total: chunks.length, - }); - } - - const reduceInput = partials.map((p) => { - const originalIdx = partialByIdx.indexOf(p); - return { - title: p.title, - narrative: p.narrative, - keyDecisions: p.keyDecisions, - filesModified: p.filesModified, - concepts: p.concepts, - obsRangeStart: originalIdx * chunkSize + 1, - obsRangeEnd: Math.min((originalIdx + 1) * chunkSize, compressed.length), - }; - }); - const response = await provider.summarize( - REDUCE_SYSTEM, - buildReducePrompt(reduceInput), - ); - return { response, mode: "chunked", chunks: chunks.length, skipped }; -} - -function parseSummaryXml( - xml: string, - sessionId: string, - project: string, - obsCount: number, -): SessionSummary | null { - const title = getXmlTag(xml, "title"); - if (!title) return null; - - return { - sessionId, - project, - createdAt: new Date().toISOString(), - title, - narrative: getXmlTag(xml, "narrative"), - keyDecisions: getXmlChildren(xml, "decisions", "decision"), - filesModified: getXmlChildren(xml, "files", "file"), - concepts: getXmlChildren(xml, "concepts", "concept"), - observationCount: obsCount, - }; -} - -function uniqueList(values: Array<string | undefined>, limit: number): string[] { - const seen = new Set<string>(); - const result: string[] = []; - for (const value of values) { - const text = String(value || "").trim(); - if (!text || seen.has(text)) continue; - seen.add(text); - result.push(text); - if (result.length >= limit) break; - } - return result; -} - -function buildLocalSummary( - session: Session, - observations: CompressedObservation[], - reason: string, -): SessionSummary { - const sorted = observations - .slice() - .sort((a, b) => String(a.timestamp || "").localeCompare(String(b.timestamp || ""))); - const focus = sorted.filter((o) => ["decision", "task", "error", "conversation", "file_edit", "file_write"].includes(o.type)); - const source = focus.length ? focus : sorted; - const first = source[0]; - const title = session.firstPrompt || first?.title || session.project || "本地会话摘要"; - const narrativeParts = source - .slice(0, 8) - .map((o) => { - const body = String(o.narrative || o.facts?.join(";") || o.title || "").trim(); - return body ? `${o.title}: ${body}` : o.title; - }) - .filter(Boolean); - const narrative = narrativeParts.length - ? `本地摘要:${narrativeParts.join("\n")}` - : "本地摘要:这段会话已有记录,但缺少可用于展开的正文。"; - const decisions = uniqueList( - source - .filter((o) => ["decision", "task", "error", "conversation"].includes(o.type)) - .flatMap((o) => [o.title, ...(o.facts || [])]), - 8, - ); - const concepts = uniqueList(source.flatMap((o) => o.concepts || []), 12); - const filesModified = uniqueList( - source - .filter((o) => ["file_edit", "file_write", "file_read"].includes(o.type)) - .flatMap((o) => o.files || []), - 12, - ); - - return { - sessionId: session.id, - project: session.project, - createdAt: new Date().toISOString(), - title: title.slice(0, 120), - narrative: `${narrative}\n\n生成方式:本地规则兜底(${reason})。`, - keyDecisions: decisions.length ? decisions : uniqueList(source.map((o) => o.title), 6), - filesModified, - concepts, - observationCount: observations.length, - }; -} - -async function saveLocalSummary( - kv: StateKV, - session: Session, - observations: CompressedObservation[], - reason: string, -): Promise<{ success: true; summary: SessionSummary; qualityScore: number; fallback: true; reason: string }> { - const summary = buildLocalSummary(session, observations, reason); - const qualityScore = scoreSummary({ - title: summary.title, - narrative: summary.narrative, - keyDecisions: summary.keyDecisions, - filesModified: summary.filesModified, - concepts: summary.concepts, - }); - await kv.set(KV.summaries, session.id, summary); - await safeAudit(kv, "compress", "mem::summarize", [session.id], { - title: summary.title, - observationCount: observations.length, - fallback: true, - reason, - }); - return { success: true, summary, qualityScore, fallback: true, reason }; -} - -export function registerSummarizeFunction( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, - metricsStore?: MetricsStore, -): void { - sdk.registerFunction("mem::summarize", - async (data: { sessionId: string } | undefined) => { - const startMs = Date.now(); - if (!data || typeof data.sessionId !== "string" || !data.sessionId.trim()) { - return { success: false, error: "sessionId is required" }; - } - const sessionId = data.sessionId.trim(); - - const session = await kv.get<Session>(KV.sessions, sessionId); - if (!session) { - logger.warn("Session not found for summarize", { - sessionId, - }); - return { success: false, error: "session_not_found" }; - } - - const observations = await kv.list<CompressedObservation>( - KV.observations(sessionId), - ); - const compressed = observations.filter((o) => o.title); - - if (compressed.length === 0) { - logger.info("No observations to summarize", { - sessionId, - }); - return { success: false, error: "no_observations" }; - } - - if (provider.name === "noop") { - logger.info("Summarize skipped — no LLM provider configured", { - sessionId, - }); - return saveLocalSummary(kv, session, compressed, "no_provider"); - } - - try { - const { response, mode, chunks } = await produceSummaryXml( - provider, - compressed, - sessionId, - session.project, - ); - if (!response || !response.trim()) { - const latencyMs = Date.now() - startMs; - if (metricsStore) { - await metricsStore.record("mem::summarize", latencyMs, false); - } - logger.warn("Empty provider response on summarize", { - sessionId, - provider: provider.name, - mode, - chunks, - observationCount: compressed.length, - }); - return saveLocalSummary(kv, session, compressed, "empty_provider_response"); - } - const summary = parseSummaryXml( - response, - sessionId, - session.project, - compressed.length, - ); - - if (!summary) { - const latencyMs = Date.now() - startMs; - if (metricsStore) { - await metricsStore.record("mem::summarize", latencyMs, false); - } - logger.warn("Failed to parse summary XML", { - sessionId, - }); - return saveLocalSummary(kv, session, compressed, "parse_failed"); - } - - const summaryForValidation = { - title: summary.title, - narrative: summary.narrative, - keyDecisions: summary.keyDecisions, - filesModified: summary.filesModified, - concepts: summary.concepts, - }; - const validation = validateOutput( - SummaryOutputSchema, - summaryForValidation, - "mem::summarize", - ); - - if (!validation.valid) { - const latencyMs = Date.now() - startMs; - if (metricsStore) { - await metricsStore.record("mem::summarize", latencyMs, false); - } - logger.warn("Summary validation failed", { - sessionId, - errors: validation.result.errors, - }); - return saveLocalSummary(kv, session, compressed, "validation_failed"); - } - - const qualityScore = scoreSummary(summaryForValidation); - - await kv.set(KV.summaries, sessionId, summary); - await safeAudit(kv, "compress", "mem::summarize", [sessionId], { - title: summary.title, - observationCount: compressed.length, - }); - - const latencyMs = Date.now() - startMs; - if (metricsStore) { - await metricsStore.record( - "mem::summarize", - latencyMs, - true, - qualityScore, - ); - } - - logger.info("Session summarized", { - sessionId, - title: summary.title, - decisions: summary.keyDecisions.length, - qualityScore, - valid: validation.valid, - }); - - return { success: true, summary, qualityScore }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - const latencyMs = Date.now() - startMs; - if (metricsStore) { - await metricsStore.record("mem::summarize", latencyMs, false); - } - logger.error("Summarize failed", { - sessionId, - error: msg, - }); - return saveLocalSummary(kv, session, compressed, msg || "provider_error"); - } - }, - ); -} diff --git a/src/functions/team.ts b/src/functions/team.ts deleted file mode 100644 index a6461fe6..00000000 --- a/src/functions/team.ts +++ /dev/null @@ -1,160 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - TeamConfig, - TeamSharedItem, - TeamProfile, - Memory, -} from "../types.js"; -import { KV, generateId } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; -import { logger } from "../logger.js"; - -const VALID_ITEM_TYPES = new Set(["memory", "pattern", "observation"]); - -export function registerTeamFunction( - sdk: ISdk, - kv: StateKV, - config: TeamConfig, -): void { - sdk.registerFunction("mem::team-share", - async (data: { - itemId: string; - itemType: "memory" | "pattern" | "observation"; - sessionId?: string; - project?: string; - }) => { - if (!data) { - return { success: false, error: "payload required" }; - } - if (!data.itemId || !data.itemType) { - return { success: false, error: "itemId and itemType are required" }; - } - if (!VALID_ITEM_TYPES.has(data.itemType)) { - return { success: false, error: `Invalid itemType: ${data.itemType}` }; - } - - let content: unknown; - if (data.itemType === "observation") { - if (!data.sessionId) { - return { - success: false, - error: "sessionId is required for observations", - }; - } - content = await kv.get(KV.observations(data.sessionId), data.itemId); - } else { - content = await kv.get<Memory>(KV.memories, data.itemId); - } - if (!content) { - return { success: false, error: "Item not found" }; - } - - const shared: TeamSharedItem = { - id: generateId("ts"), - sharedBy: config.userId, - sharedAt: new Date().toISOString(), - type: data.itemType, - content, - project: data.project || "", - visibility: "shared", - }; - - await kv.set(KV.teamShared(config.teamId), shared.id, shared); - - await recordAudit(kv, "share", "mem::team-share", [data.itemId], { - teamId: config.teamId, - userId: config.userId, - itemType: data.itemType, - }); - - logger.info("Team share", { - teamId: config.teamId, - itemId: data.itemId, - }); - return { success: true, sharedItem: shared }; - }, - ); - - sdk.registerFunction("mem::team-feed", - async (data?: { limit?: number }) => { - const limit = data?.limit ?? 20; - const items = await kv.list<TeamSharedItem>(KV.teamShared(config.teamId)); - - const filtered = items.filter((i) => i.visibility === "shared"); - const sorted = filtered - .sort( - (a, b) => - new Date(b.sharedAt).getTime() - new Date(a.sharedAt).getTime(), - ) - .slice(0, limit); - - return { items: sorted, total: filtered.length }; - }, - ); - - sdk.registerFunction("mem::team-profile", async () => { - const items = await kv.list<TeamSharedItem>(KV.teamShared(config.teamId)); - - const members = [...new Set(items.map((i) => i.sharedBy))]; - - const conceptCounts = new Map<string, number>(); - const fileCounts = new Map<string, number>(); - const patterns: string[] = []; - - for (const item of items) { - if (item.type === "memory" || item.type === "pattern") { - const mem = item.content as Memory; - if (mem?.concepts) { - for (const c of mem.concepts) { - conceptCounts.set(c, (conceptCounts.get(c) || 0) + 1); - } - } - if (mem?.files) { - for (const f of mem.files) { - fileCounts.set(f, (fileCounts.get(f) || 0) + 1); - } - } - if (item.type === "pattern" && mem?.content) { - patterns.push(mem.content.slice(0, 100)); - } - } - } - - const topConcepts = [...conceptCounts.entries()] - .sort((a, b) => b[1] - a[1]) - .slice(0, 10) - .map(([concept, frequency]) => ({ concept, frequency })); - - const topFiles = [...fileCounts.entries()] - .sort((a, b) => b[1] - a[1]) - .slice(0, 10) - .map(([file, frequency]) => ({ file, frequency })); - - const profile: TeamProfile = { - teamId: config.teamId, - members, - topConcepts, - topFiles, - sharedPatterns: patterns.slice(0, 10), - totalSharedItems: items.length, - updatedAt: new Date().toISOString(), - }; - - await kv.set(KV.teamProfile(config.teamId), "profile", profile); - await recordAudit( - kv, - "share", - "mem::team-profile", - ["profile"], - { - teamId: config.teamId, - members: members.length, - totalSharedItems: items.length, - }, - undefined, - config.userId, - ); - return profile; - }); -} diff --git a/src/functions/temporal-graph.ts b/src/functions/temporal-graph.ts deleted file mode 100644 index 1e8d1df1..00000000 --- a/src/functions/temporal-graph.ts +++ /dev/null @@ -1,460 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - GraphNode, - GraphEdge, - GraphEdgeType, - EdgeContext, - TemporalState, - MemoryProvider, -} from "../types.js"; -import { KV, generateId } from "../state/schema.js"; -import type { StateKV } from "../state/kv.js"; -import { logger } from "../logger.js"; - -const TEMPORAL_EXTRACTION_SYSTEM = `You are a temporal knowledge extraction engine. Given observations, extract entities AND their temporal relationships with full context metadata. - -For each relationship, you MUST provide: -1. Semantic relation type -2. Temporal validity (when this fact became true in the real world) -3. Context metadata: WHY this relationship exists, what reasoning led to it, what alternatives were considered - -Output EXACTLY this XML: -<temporal_graph> - <entities> - <entity type="file|function|concept|error|decision|pattern|library|person|project|preference|location|organization|event" name="exact name"> - <property key="key">value</property> - <alias>alternate name</alias> - </entity> - </entities> - <relationships> - <relationship type="uses|imports|modifies|causes|fixes|depends_on|related_to|works_at|prefers|blocked_by|caused_by|optimizes_for|rejected|avoids|located_in|succeeded_by" - source="entity name" target="entity name" weight="0.1-1.0" - valid_from="ISO date or 'unknown'" valid_to="ISO date or 'current'"> - <reasoning>WHY this relationship exists</reasoning> - <sentiment>positive|negative|neutral</sentiment> - <alternatives> - <alt>alternative that was considered</alt> - </alternatives> - </relationship> - </relationships> -</temporal_graph> - -Rules: -- NEVER overwrite existing relationships — always create new versioned edges -- Extract temporal validity from context clues ("since last month", "in 2024", "currently") -- Capture reasoning/motivation behind each relationship -- Weight relationships by directness: 1.0 = explicit statement, 0.5 = inferred, 0.1 = speculative`; - -function parseTemporalGraphXml( - xml: string, - observationIds: string[], -): { nodes: GraphNode[]; edges: GraphEdge[] } { - const nodes: GraphNode[] = []; - const edges: GraphEdge[] = []; - const now = new Date().toISOString(); - - const entityRegex = - /<entity\s+type="([^"]+)"\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/entity>/g; - let match; - while ((match = entityRegex.exec(xml)) !== null) { - const type = match[1] as GraphNode["type"]; - const name = match[2]; - const propsBlock = match[3]; - const properties: Record<string, string> = {}; - const aliases: string[] = []; - - const propRegex = /<property\s+key="([^"]+)">([^<]*)<\/property>/g; - let propMatch; - while ((propMatch = propRegex.exec(propsBlock)) !== null) { - properties[propMatch[1]] = propMatch[2]; - } - - const aliasRegex = /<alias>([^<]+)<\/alias>/g; - while ((propMatch = aliasRegex.exec(propsBlock)) !== null) { - aliases.push(propMatch[1]); - } - - nodes.push({ - id: generateId("gn"), - type, - name, - properties, - sourceObservationIds: observationIds, - createdAt: now, - aliases: aliases.length > 0 ? aliases : undefined, - }); - } - - const relRegex = - /<relationship\s+type="([^"]+)"\s+source="([^"]+)"\s+target="([^"]+)"\s+weight="([^"]+)"(?:\s+valid_from="([^"]*)")?(?:\s+valid_to="([^"]*)")?[^>]*>([\s\S]*?)<\/relationship>/g; - while ((match = relRegex.exec(xml)) !== null) { - const type = match[1] as GraphEdgeType; - const sourceName = match[2]; - const targetName = match[3]; - const parsedWeight = parseFloat(match[4]); - const weight = Number.isNaN(parsedWeight) ? 0.5 : parsedWeight; - const validFrom = match[5] || undefined; - const validTo = match[6] || undefined; - const metaBlock = match[7] || ""; - - const sourceNode = nodes.find( - (n) => - n.name === sourceName || - (n.aliases && n.aliases.includes(sourceName)), - ); - const targetNode = nodes.find( - (n) => - n.name === targetName || - (n.aliases && n.aliases.includes(targetName)), - ); - - if (sourceNode && targetNode) { - const reasoning = - metaBlock.match(/<reasoning>([^<]*)<\/reasoning>/)?.[1] || undefined; - const sentiment = - metaBlock.match(/<sentiment>([^<]*)<\/sentiment>/)?.[1] || undefined; - const alternatives: string[] = []; - const altRegex = /<alt>([^<]+)<\/alt>/g; - let altMatch; - while ((altMatch = altRegex.exec(metaBlock)) !== null) { - alternatives.push(altMatch[1]); - } - - const context: EdgeContext = {}; - if (reasoning) context.reasoning = reasoning; - if (sentiment) context.sentiment = sentiment; - if (alternatives.length > 0) context.alternatives = alternatives; - context.confidence = Math.max(0, Math.min(1, weight)); - - edges.push({ - id: generateId("ge"), - type, - sourceNodeId: sourceNode.id, - targetNodeId: targetNode.id, - weight: Math.max(0, Math.min(1, weight)), - sourceObservationIds: observationIds, - createdAt: now, - tcommit: now, - tvalid: - validFrom && validFrom !== "unknown" ? validFrom : undefined, - tvalidEnd: - validTo && validTo !== "current" ? validTo : undefined, - context: Object.keys(context).length > 0 ? context : undefined, - version: 1, - isLatest: true, - }); - } - } - - return { nodes, edges }; -} - -export function registerTemporalGraphFunctions( - sdk: ISdk, - kv: StateKV, - provider: MemoryProvider, -): void { - sdk.registerFunction("mem::temporal-graph-extract", - async (data: { - observations: Array<{ - id: string; - title: string; - narrative: string; - concepts: string[]; - files: string[]; - type: string; - timestamp: string; - }>; - }) => { - if (!data.observations || data.observations.length === 0) { - return { success: false, error: "No observations provided" }; - } - - const items = data.observations - .map( - (o, i) => - `[${i + 1}] Type: ${o.type}\nTimestamp: ${o.timestamp}\nTitle: ${o.title}\nNarrative: ${o.narrative}\nConcepts: ${(o.concepts ?? []).join(", ")}\nFiles: ${(o.files ?? []).join(", ")}`, - ) - .join("\n\n"); - - try { - const response = await provider.compress( - TEMPORAL_EXTRACTION_SYSTEM, - `Extract temporal knowledge graph from:\n\n${items}`, - ); - - const obsIds = data.observations.map((o) => o.id); - const { nodes, edges } = parseTemporalGraphXml(response, obsIds); - - const existingNodes = await kv.list<GraphNode>(KV.graphNodes); - const existingEdges = await kv.list<GraphEdge>(KV.graphEdges); - - const idRemap = new Map<string, string>(); - for (const node of nodes) { - const existing = existingNodes.find( - (n) => - n.name === node.name && n.type === node.type, - ); - if (existing) { - const oldId = node.id; - const merged = { - ...existing, - sourceObservationIds: [ - ...new Set([ - ...existing.sourceObservationIds, - ...obsIds, - ]), - ], - properties: { ...existing.properties, ...node.properties }, - updatedAt: new Date().toISOString(), - aliases: [ - ...new Set([ - ...(existing.aliases || []), - ...(node.aliases || []), - ]), - ], - }; - if (merged.aliases.length === 0) delete (merged as any).aliases; - await kv.set(KV.graphNodes, existing.id, merged); - node.id = existing.id; - idRemap.set(oldId, existing.id); - } else { - await kv.set(KV.graphNodes, node.id, node); - existingNodes.push(node); - } - } - - for (const edge of edges) { - if (idRemap.has(edge.sourceNodeId)) { - edge.sourceNodeId = idRemap.get(edge.sourceNodeId)!; - } - if (idRemap.has(edge.targetNodeId)) { - edge.targetNodeId = idRemap.get(edge.targetNodeId)!; - } - const existingKey = `${edge.sourceNodeId}|${edge.targetNodeId}|${edge.type}`; - const existingEdge = existingEdges.find( - (e) => - `${e.sourceNodeId}|${e.targetNodeId}|${e.type}` === - existingKey, - ); - - if (existingEdge) { - const updatedOld = { - ...existingEdge, - isLatest: false, - tvalidEnd: - existingEdge.tvalidEnd || new Date().toISOString(), - supersededBy: edge.id, - }; - await kv.set(KV.graphEdges, existingEdge.id, updatedOld); - - await kv.set(KV.graphEdgeHistory, existingEdge.id, updatedOld); - - edge.version = (existingEdge.version || 1) + 1; - } - - await kv.set(KV.graphEdges, edge.id, edge); - existingEdges.push(edge); - } - - logger.info("Temporal graph extraction complete", { - nodes: nodes.length, - edges: edges.length, - }); - return { - success: true, - nodesAdded: nodes.length, - edgesAdded: edges.length, - }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.error("Temporal graph extraction failed", { error: msg }); - return { success: false, error: msg }; - } - }, - ); - - sdk.registerFunction("mem::temporal-query", - async (data: { - entityName: string; - asOf?: string; - includeHistory?: boolean; - }): Promise<TemporalState | { error: string }> => { - const allNodes = await kv.list<GraphNode>(KV.graphNodes); - const allEdges = await kv.list<GraphEdge>(KV.graphEdges); - - const entity = allNodes.find( - (n) => - n.name.toLowerCase() === data.entityName.toLowerCase() || - (n.aliases && - n.aliases.some( - (a) => - a.toLowerCase() === data.entityName.toLowerCase(), - )), - ); - - if (!entity) { - return { error: `Entity "${data.entityName}" not found` } as any; - } - - const relatedEdges = allEdges.filter( - (e) => e.sourceNodeId === entity.id || e.targetNodeId === entity.id, - ); - - const historicalEdges = await kv - .list<GraphEdge>(KV.graphEdgeHistory) - .catch(() => [] as GraphEdge[]); - const entityHistory = historicalEdges.filter( - (e) => e.sourceNodeId === entity.id || e.targetNodeId === entity.id, - ); - - const allEntityEdges = [...relatedEdges, ...entityHistory]; - - if (data.asOf) { - const asOfTime = new Date(data.asOf).getTime(); - const validEdges = allEntityEdges.filter((e) => { - const commitTime = new Date( - e.tcommit || e.createdAt, - ).getTime(); - if (commitTime > asOfTime) return false; - if (e.tvalid) { - const validTime = new Date(e.tvalid).getTime(); - if (validTime > asOfTime) return false; - } - if (e.tvalidEnd) { - const endTime = new Date(e.tvalidEnd).getTime(); - if (endTime < asOfTime) return false; - } - return true; - }); - - const currentEdges = getLatestByKey(validEdges); - const historical = data.includeHistory ? validEdges : []; - - return { - entity, - currentEdges, - historicalEdges: historical, - timeline: buildTimeline(allEntityEdges), - }; - } - - const currentEdges = relatedEdges.filter( - (e) => e.isLatest !== false, - ); - - return { - entity, - currentEdges, - historicalEdges: data.includeHistory ? entityHistory : [], - timeline: buildTimeline(allEntityEdges), - }; - }, - ); - - sdk.registerFunction("mem::differential-state", - async (data: { - entityName: string; - from?: string; - to?: string; - }) => { - const allNodes = await kv.list<GraphNode>(KV.graphNodes); - const allEdges = await kv.list<GraphEdge>(KV.graphEdges); - const historicalEdges = await kv - .list<GraphEdge>(KV.graphEdgeHistory) - .catch(() => [] as GraphEdge[]); - - const entity = allNodes.find( - (n) => n.name.toLowerCase() === data.entityName.toLowerCase(), - ); - if (!entity) return { error: "Entity not found" }; - - const allEntityEdges = [ - ...allEdges.filter( - (e) => - e.sourceNodeId === entity.id || e.targetNodeId === entity.id, - ), - ...historicalEdges.filter( - (e) => - e.sourceNodeId === entity.id || e.targetNodeId === entity.id, - ), - ]; - - allEntityEdges.sort( - (a, b) => - new Date(a.tcommit || a.createdAt).getTime() - - new Date(b.tcommit || b.createdAt).getTime(), - ); - - const fromTime = data.from - ? new Date(data.from).getTime() - : 0; - const toTime = data.to - ? new Date(data.to).getTime() - : Date.now(); - - const filtered = allEntityEdges.filter((e) => { - const t = new Date(e.tcommit || e.createdAt).getTime(); - return t >= fromTime && t <= toTime; - }); - - const changes = filtered.map((e) => ({ - type: e.type, - target: - e.sourceNodeId === entity.id - ? e.targetNodeId - : e.sourceNodeId, - validFrom: e.tvalid || e.createdAt, - validTo: e.tvalidEnd, - reasoning: e.context?.reasoning, - sentiment: e.context?.sentiment, - version: e.version || 1, - isLatest: e.isLatest !== false, - })); - - return { - entity: entity.name, - totalChanges: changes.length, - changes, - }; - }, - ); -} - -function getLatestByKey(edges: GraphEdge[]): GraphEdge[] { - const byKey = new Map<string, GraphEdge>(); - for (const e of edges) { - const key = `${e.sourceNodeId}|${e.targetNodeId}|${e.type}`; - const existing = byKey.get(key); - if ( - !existing || - new Date(e.tcommit || e.createdAt).getTime() > - new Date(existing.tcommit || existing.createdAt).getTime() - ) { - byKey.set(key, e); - } - } - return Array.from(byKey.values()); -} - -function buildTimeline( - edges: GraphEdge[], -): Array<{ - edge: GraphEdge; - validFrom: string; - validTo?: string; - context?: EdgeContext; -}> { - const sorted = [...edges].sort( - (a, b) => - new Date(a.tcommit || a.createdAt).getTime() - - new Date(b.tcommit || b.createdAt).getTime(), - ); - - return sorted.map((e) => ({ - edge: e, - validFrom: e.tvalid || e.createdAt, - validTo: e.tvalidEnd, - context: e.context, - })); -} diff --git a/src/functions/timeline.ts b/src/functions/timeline.ts deleted file mode 100644 index e2244fe1..00000000 --- a/src/functions/timeline.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { - CompressedObservation, - Session, - TimelineEntry, -} from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { recordAccessBatch } from "./access-tracker.js"; -import { logger } from "../logger.js"; - -export function registerTimelineFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::timeline", - async (data: { - anchor: string; - project?: string; - before?: number; - after?: number; - }) => { - const before = Math.max(0, Math.floor(data.before ?? 5)); - const after = Math.max(0, Math.floor(data.after ?? 5)); - - if (!data.anchor || typeof data.anchor !== "string") { - return { entries: [], anchor: data.anchor, reason: "invalid_anchor" }; - } - - let anchorTime: number; - const isoPattern = /^\d{4}-\d{2}-\d{2}/; - if (isoPattern.test(data.anchor)) { - anchorTime = new Date(data.anchor).getTime(); - if (isNaN(anchorTime)) { - return { entries: [], anchor: data.anchor, reason: "invalid_date" }; - } - } else { - const searchResults = await findByKeyword( - kv, - data.anchor, - data.project, - ); - if (searchResults.length === 0) { - return { entries: [], anchor: data.anchor, reason: "no_match" }; - } - anchorTime = new Date(searchResults[0].timestamp).getTime(); - } - - const sessions = await kv.list<Session>(KV.sessions); - const filtered = data.project - ? sessions.filter((s) => s.project === data.project) - : sessions; - - const allObs: Array<CompressedObservation & { sid: string }> = []; - for (const session of filtered) { - const observations = await kv.list<CompressedObservation>( - KV.observations(session.id), - ); - for (const obs of observations) { - if (obs.title && obs.timestamp) { - allObs.push({ ...obs, sid: session.id }); - } - } - } - - allObs.sort( - (a, b) => - new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(), - ); - - let anchorIdx = 0; - let minDist = Infinity; - for (let i = 0; i < allObs.length; i++) { - const dist = Math.abs( - new Date(allObs[i].timestamp).getTime() - anchorTime, - ); - if (dist < minDist) { - minDist = dist; - anchorIdx = i; - } - } - - const startIdx = Math.max(0, anchorIdx - before); - const endIdx = Math.min(allObs.length - 1, anchorIdx + after); - const entries: TimelineEntry[] = []; - - for (let i = startIdx; i <= endIdx; i++) { - const obs = allObs[i]; - const { sid, ...observation } = obs; - entries.push({ - observation, - sessionId: sid, - relativePosition: i - anchorIdx, - }); - } - - void recordAccessBatch( - kv, - entries.map((e) => e.observation.id), - ); - - logger.info("Timeline retrieved", { - anchor: data.anchor, - entries: entries.length, - }); - return { entries, anchorIndex: anchorIdx - startIdx }; - }, - ); -} - -async function findByKeyword( - kv: StateKV, - keyword: string, - project?: string, -): Promise<CompressedObservation[]> { - const sessions = await kv.list<Session>(KV.sessions); - const filtered = project - ? sessions.filter((s) => s.project === project) - : sessions; - - const lower = keyword.toLowerCase(); - const matches: CompressedObservation[] = []; - - for (const session of filtered) { - const observations = await kv.list<CompressedObservation>( - KV.observations(session.id), - ); - for (const obs of observations) { - if ( - obs.title?.toLowerCase().includes(lower) || - obs.narrative?.toLowerCase().includes(lower) || - obs.concepts?.some((c) => c.toLowerCase().includes(lower)) - ) { - matches.push(obs); - } - } - } - - return matches.sort( - (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(), - ); -} diff --git a/src/functions/todo-extract-langextract.py b/src/functions/todo-extract-langextract.py deleted file mode 100644 index 6b0e4731..00000000 --- a/src/functions/todo-extract-langextract.py +++ /dev/null @@ -1,396 +0,0 @@ -#!/usr/bin/env python3 -"""Optional LangExtract sidecar for AI Todo extraction. - -Reads JSON from stdin and writes {"todos": [...]} to stdout. If langextract or -its model config is unavailable, exit non-zero so TypeScript can fall back to -the existing rules extractor. -""" - -from __future__ import annotations - -import json -import os -import sys -import textwrap - -DEFAULT_MODEL = "deepseek/deepseek-v4-flash" -DEFAULT_PROVIDER = "openai" -DEFAULT_BASE_URL = "https://api.novita.ai/openai/v1" -LEGACY_MODELS = {"pa/gpt-5.5"} - - -PROMPT = textwrap.dedent( - """\ - Extract mature, user-facing todo cards from AI agent session text. - When refreshAction metadata is provided, use it as advisory context about the - existing card. Prefer a clearer title/description suggested by cleanup - metadata only when the nearby source quote supports it. - The input may include taskChains. Treat each taskChain as the primary unit: - title should anchor to the user's original intent, while description should - anchor to the latest agent status, blocker, or next step. Low-information - user turns such as "继续", "重试", "再来一次", "retry", or "continue" must be - merged back into the previous related taskChain; do not create separate - cards for those turns. - Extract only UNRESOLVED, actionable items: explicit next actions, follow-ups, - failed validations, blocked work, and in-progress work that still needs doing. - DO NOT extract completed work, results, status reports, confirmations, or - narration of what was already done (e.g. "…已通过", "…都能显示", "确认…完成"). - Ignore background summaries, generic facts, read-only tool traces, transient - agent process narration, and anything without a source quote. - Use exact source text as extraction_text. Put fields in attributes: - title, description, confidence, timeBucket, typeBucket, dedupeKey. - The generated card must read like a mature todo app item, not a transcript - snippet. title must be a CRISP, SCANNABLE user-facing action summary — a - concrete verb + the semantic object (+ the concrete target/outcome when it - adds signal), so the title alone makes clear what THIS todo is. Preserve the - source language unless the source itself mixes languages. Keep it short, but - do not drop the user-relevant object or constraint just to hit a character - target. - Precise technical identifiers must be preserved, but they should usually go - in description rather than the title when they are long: branch names, - commit hashes, file paths, URLs, session ids, package names, and raw repos. - Prefer title "推送当前工作分支到远程仓库" with description - "分支:codex/todo-cleanup-flash-model。" over title - "推送 codex/todo-cleanup-flash-model 分支到远程仓库". - A good title may contain tightly related steps when they serve one outcome, - e.g. "修正目录显示文字(去掉重复编号)并更新页码缓存后重渲染". - description must be one concise sentence about the remaining user-relevant - work; do not start with "I will / 我会 / 现在 / 接下来". - DO NOT pad with vague filler such as - "全面了解 / 了解现状 / 梳理现状 / 获取信息 / 进行 / 处理" — name the actual thing - (the repo, the file, the bug, the command's purpose), not the process of - "understanding" it. Prefer "克隆 AI-Todo 仓库" over "克隆仓库并全面了解其状况". - Do NOT turn agent workflow/status chores into todos unless a durable user - outcome remains. Examples to skip: "做最后一次状态确认", "启动后做健康检查", - "确认工作区干净", "服务可用", "健康检查已完成", "重启后再测一次" when it is just - suggested procedure or stale troubleshooting, and "我会/接下来/现在确认…". - Negative example: source text "我会做最后一次状态确认,确保工作区干净、当前分支和 PR 链接明确。服务可用,健康检查已完成。" - should produce no todo. - If text might be useful but is ambiguous or process-like, set confidence - below the direct-create threshold (0.55-0.81) so it goes to human review. - dedupeKey must be a short STABLE slug of the core action+object (e.g. - clone-aitodo-repo, read-project-config), the SAME for two todos that are the - same task regardless of wording, so reworded duplicates collapse. - Never use raw command JSON, file paths, screenshots, toolUseId/call IDs, - shell flags, logs, or truncated trace fragments as title. If the only source - text is a tool log, command payload, path, or JSON object, do not extract a - todo. - Never extract dangling/truncated titles such as - "准备推送分支 codex/todo-cleanup-flash-model 到"; if context cannot repair - them into a complete, supported action, produce no todo. - Never extract tool-call echo lines (starting with ⏺ or containing Bash(/Shell(), - service-status reports (e.g. "服务可用", "Viewer:"/"Health:" URL lists), or - git-ref fragments — these are not todos. - timeBucket must be current, recent, or history. - typeBucket must be pending, to_start, follow_up, in_progress, or processing. - """ -) - - -def load_langextract(): - try: - import langextract as lx # type: ignore - - return lx - except Exception as exc: # pragma: no cover - exercised by TS fallback - raise SystemExit(f"langextract unavailable: {exc}") from exc - - -def model_id_from_env() -> str: - model = (os.environ.get("LANGEXTRACT_MODEL") or os.environ.get("GEMINI_MODEL") or DEFAULT_MODEL).strip() - return DEFAULT_MODEL if model in LEGACY_MODELS else model - - -def extract_kwargs(lx, model_id: str, model_config_cls=None) -> dict: - provider = os.environ.get("LANGEXTRACT_PROVIDER", DEFAULT_PROVIDER).strip().lower() - if provider in ("", "novita", "deepseek"): - provider = DEFAULT_PROVIDER - api_key = os.environ.get("LANGEXTRACT_API_KEY", "").strip() - base_url = os.environ.get("LANGEXTRACT_BASE_URL", DEFAULT_BASE_URL).strip() or DEFAULT_BASE_URL - thinking_depth = os.environ.get("LANGEXTRACT_THINKING_DEPTH", "medium").strip() - params = { - "extraction_passes": int(os.environ.get("LANGEXTRACT_PASSES", "2")), - "max_workers": int(os.environ.get("LANGEXTRACT_MAX_WORKERS", "4")), - "max_char_buffer": int(os.environ.get("LANGEXTRACT_MAX_CHAR_BUFFER", "1600")), - } - if provider == "openai": - if not api_key: - raise SystemExit("LANGEXTRACT_API_KEY is required for LANGEXTRACT_PROVIDER=openai") - if model_config_cls is None: - from langextract.factory import ModelConfig as model_config_cls # type: ignore - - provider_kwargs = {"api_key": api_key} - if base_url: - provider_kwargs["base_url"] = base_url - if thinking_depth: - provider_kwargs["reasoning_effort"] = thinking_depth - params["config"] = model_config_cls( - model_id=model_id, - provider="openai", - provider_kwargs=provider_kwargs, - ) - params["use_schema_constraints"] = False - else: - params["model_id"] = model_id - return params - - -def main() -> int: - payload = json.load(sys.stdin) - blocks = payload.get("blocks") or [] - if not isinstance(blocks, list) or not blocks: - print(json.dumps({"todos": []}, ensure_ascii=False)) - return 0 - - lx = load_langextract() - task_chains = payload.get("taskChains") or [] - chain_texts = [] - if isinstance(task_chains, list): - for chain in task_chains: - if not isinstance(chain, dict): - continue - user_id = chain.get("userObservationId", "") - status_id = chain.get("latestStatusObservationId", "") - user_intent = chain.get("userIntent", "") - latest_status = chain.get("latestStatus", "") - summary = chain.get("completionSummary", "") - next_step = chain.get("nextStep", "") - chain_texts.append( - "\n".join( - [ - f"[taskChain:{chain.get('chainId', '')}]", - f"[obs:{user_id}] 用户意图: {user_intent}", - f"[obs:{status_id}] Agent最新状态: {latest_status}", - f"completionState: {chain.get('completionState', '')}", - f"completionSummary: {summary}", - f"nextStep: {next_step}", - ] - ) - ) - block_text = "\n\n".join( - f"[obs:{b.get('sourceObservationId','')}]\n{b.get('text','')}" - for b in blocks - if isinstance(b, dict) and b.get("text") - ) - text = "\n\n".join([part for part in chain_texts + [block_text] if part]) - if not text.strip(): - print(json.dumps({"todos": []}, ensure_ascii=False)) - return 0 - - refresh_action = payload.get("refreshAction") - if isinstance(refresh_action, dict): - refresh_json = json.dumps(refresh_action, ensure_ascii=False, sort_keys=True) - text = f"[refreshAction]\n{refresh_json}\n\n{text}" - - examples = [ - lx.data.ExampleData( - text="[obs:obs_1]\n后续需要修复 CI 失败,并重新跑测试。", - extractions=[ - lx.data.Extraction( - extraction_class="todo", - extraction_text="后续需要修复 CI 失败,并重新跑测试", - attributes={ - "title": "修复 CI 失败并重新跑测试", - "description": "后续需要修复 CI 失败,并重新跑测试。", - "confidence": 0.9, - "timeBucket": "current", - "typeBucket": "follow_up", - "dedupeKey": "fix-ci-rerun-tests", - }, - ) - ], - ), - lx.data.ExampleData( - text="[obs:obs_2]\n我会先确认工作区状态,然后把远程仓库 MaimoryLab/AI-Todo 克隆到子目录,再从 Git 元数据、依赖和测试入口梳理现状。", - extractions=[ - lx.data.Extraction( - extraction_class="todo", - extraction_text="把远程仓库 MaimoryLab/AI-Todo 克隆到子目录", - attributes={ - "title": "克隆 AI-Todo 仓库到子目录", - "description": "把 MaimoryLab/AI-Todo 克隆到子目录,避免与外层 Git 状态混在一起。", - "confidence": 0.85, - "timeBucket": "current", - "typeBucket": "to_start", - "dedupeKey": "clone-aitodo-repo", - }, - ) - ], - ), - lx.data.ExampleData( - text="[obs:obs_3]\n接下来我会读项目结构、README、包管理文件和环境配置样例,再补一层远程 GitHub 元数据。", - extractions=[ - lx.data.Extraction( - extraction_class="todo", - extraction_text="读项目结构、README、包管理文件和环境配置样例", - attributes={ - "title": "读 README 与依赖配置", - "description": "读项目结构、README、包管理与环境配置样例,并补远程 GitHub 元数据。", - "confidence": 0.8, - "timeBucket": "current", - "typeBucket": "to_start", - "dedupeKey": "read-project-config", - }, - ) - ], - ), - lx.data.ExampleData( - text="[obs:obs_4]\n下一步需要修复深色模式按钮对比度,避免主操作在暗色背景下不可读。", - extractions=[ - lx.data.Extraction( - extraction_class="todo", - extraction_text="修复深色模式按钮对比度,避免主操作在暗色背景下不可读", - attributes={ - "title": "修复深色模式按钮对比度", - "description": "修复深色模式按钮对比度,避免主操作在暗色背景下不可读。", - "confidence": 0.9, - "timeBucket": "current", - "typeBucket": "follow_up", - "dedupeKey": "fix-dark-mode-button-contrast", - }, - ) - ], - ), - lx.data.ExampleData( - text="[obs:obs_5]\n下一步需要推送 codex/todo-cleanup-flash-model 分支到远程仓库。", - extractions=[ - lx.data.Extraction( - extraction_class="todo", - extraction_text="推送 codex/todo-cleanup-flash-model 分支到远程仓库", - attributes={ - "title": "推送当前工作分支到远程仓库", - "description": "推送当前工作分支到远程仓库。分支:codex/todo-cleanup-flash-model。", - "confidence": 0.88, - "timeBucket": "current", - "typeBucket": "to_start", - "dedupeKey": "push-current-branch", - }, - ) - ], - ), - lx.data.ExampleData( - text="[obs:obs_7]\n下一步需要修正目录显示文字(去掉重复编号)并更新页码缓存后重渲染。", - extractions=[ - lx.data.Extraction( - extraction_class="todo", - extraction_text="修正目录显示文字(去掉重复编号)并更新页码缓存后重渲染", - attributes={ - "title": "修正目录显示文字(去掉重复编号)并更新页码缓存后重渲染", - "description": "修正目录显示文字,去掉重复编号,并更新页码缓存后重渲染。", - "confidence": 0.9, - "timeBucket": "current", - "typeBucket": "follow_up", - "dedupeKey": "fix-toc-numbering-rerender", - }, - ) - ], - ), - lx.data.ExampleData( - text="[obs:obs_6]\n建议处理顺序:2. 重启 Codex desktop app 后再测一次。", - extractions=[ - lx.data.Extraction( - extraction_class="todo", - extraction_text="重启 Codex desktop app 后再测一次", - attributes={ - "title": "验证重启后的 Codex desktop app", - "description": "重启 Codex desktop app 后再验证问题是否仍存在。", - "confidence": 0.65, - "timeBucket": "recent", - "typeBucket": "follow_up", - "dedupeKey": "verify-codex-desktop-after-restart", - }, - ) - ], - ), - ] - model_id = model_id_from_env() - result = lx.extract( - text_or_documents=text, - prompt_description=PROMPT, - examples=examples, - **extract_kwargs(lx, model_id), - ) - - todos = [] - for item in getattr(result, "extractions", []): - if getattr(item, "extraction_class", "") != "todo": - continue - quote = getattr(item, "extraction_text", "") or "" - attrs = getattr(item, "attributes", {}) or {} - interval = getattr(item, "char_interval", None) - if not quote or interval is None: - continue - start = getattr(interval, "start_pos", None) - end = getattr(interval, "end_pos", None) - prefix = text[: start if isinstance(start, int) else text.find(quote)] - obs_marker = prefix.rsplit("[obs:", 1)[-1].split("]", 1)[0] if "[obs:" in prefix else "" - title = attrs.get("title") - if not title: - # No model-provided title → skip rather than emit a hard-cut - # quote slice (which produced mid-sentence fragment cards). - continue - todos.append( - { - "title": title, - "description": attrs.get("description") or quote, - "confidence": attrs.get("confidence", 0.6), - "timeBucket": attrs.get("timeBucket", "recent"), - "typeBucket": attrs.get("typeBucket", "pending"), - "sourceSessionId": payload.get("sessionId"), - "evidence": { - "sourceObservationId": obs_marker, - "quote": quote, - "charStart": start, - "charEnd": end, - }, - "dedupeKey": attrs.get("dedupeKey") or "", - } - ) - - print(json.dumps({"todos": todos}, ensure_ascii=False)) - return 0 - - -if __name__ == "__main__": - if os.environ.get("LANGEXTRACT_SELF_TEST") == "1": - os.environ.setdefault("LANGEXTRACT_API_KEY", "self-test-key") - - class DummyConfig: - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - class DummyFactory: - ModelConfig = DummyConfig - - class DummyLx: - factory = DummyFactory - - params = extract_kwargs(DummyLx, model_id_from_env(), DummyConfig) - config = params.get("config") - assert config.model_id == "deepseek/deepseek-v4-flash" - assert config.provider == "openai" - assert config.provider_kwargs["base_url"] == "https://api.novita.ai/openai/v1" - assert config.provider_kwargs["reasoning_effort"] == "medium" - assert params["use_schema_constraints"] is False - params = extract_kwargs(DummyLx, "custom/openai-compatible-model", DummyConfig) - assert params["use_schema_constraints"] is False - assert "CRISP, SCANNABLE" in PROMPT - assert "codex/todo-cleanup-flash-model" in PROMPT - assert "推送当前工作分支到远程仓库" in PROMPT - assert "修正目录显示文字" in PROMPT - assert "mature todo app item" in PROMPT - assert "做最后一次状态确认" in PROMPT - assert "Negative example" in PROMPT - assert "refreshAction metadata" in PROMPT - assert "taskChains" in PROMPT - assert "用户意图" in "\n".join([ - f"[taskChain:test]", - f"[obs:u1] 用户意图: 继续", - f"[obs:a1] Agent最新状态: 下一步需要修复", - ]) - assert "0.55-0.81" in PROMPT - assert "dedupeKey must be a short STABLE slug" in PROMPT - assert "克隆 AI-Todo 仓库" in PROMPT - print("ok") - raise SystemExit(0) - raise SystemExit(main()) diff --git a/src/functions/todo-extract.ts b/src/functions/todo-extract.ts deleted file mode 100644 index 0401dc7a..00000000 --- a/src/functions/todo-extract.ts +++ /dev/null @@ -1,2054 +0,0 @@ -import { spawn } from "node:child_process"; -import { existsSync } from "node:fs"; -import { dirname, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV, fingerprintId, generateId, nearDuplicateTitle } from "../state/schema.js"; -import type { Action, CompressedObservation, ReviewQueueItem, ScanCheckpoint, Session } from "../types.js"; -import { - DEFAULT_LANGEXTRACT_BASE_URL, - DEFAULT_TODO_EXTRACT_TIMEOUT_MS, - DEFAULT_TODO_EXTRACT_SINCE_DAYS, - DEFAULT_TODO_EXTRACT_MAX_INTERACTIONS, - DEFAULT_TODO_EXTRACT_MAX_SESSIONS, - getEnvVar, - normalizeTodoExtractorModel, - normalizeTodoExtractorProvider, - resolveLangExtractPython, -} from "../config.js"; -import { scanCodexSource } from "./source-scan-codex.js"; -import { - extractActionCandidatesFromObservations, - type ActionCandidate, -} from "./action-candidates.js"; - -type TimeBucket = "current" | "recent" | "history"; -type TypeBucket = "pending" | "to_start" | "follow_up" | "in_progress" | "done" | "processing"; - -export interface ExtractedTodo { - title: string; - description: string; - confidence: number; - timeBucket: TimeBucket; - typeBucket: TypeBucket; - sourceSessionId: string; - evidence: { - sourceObservationId: string; - quote: string; - charStart?: number; - charEnd?: number; - }; - dedupeKey: string; -} - -type TodoQualityReason = - | "ok" - | "incomplete-title" - | "process-or-status" - | "polluted" - | "completed" - | "low-actionability"; - -type TodoQuality = { - confidence: number; - reason: TodoQualityReason; - warnings: string[]; - titleCompacted?: boolean; - originalTitle?: string; -}; - -type ObservationBlock = { - sourceObservationId: string; - timestamp: string; - type: string; - title: string; - text: string; -}; - -type TodoCompletionState = "completed" | "in_progress" | "interrupted"; - -type TodoTaskChain = { - chainId: string; - sourceSessionId: string; - userObservationId: string; - userIntent: string; - latestStatusObservationId: string; - latestStatus: string; - continuedByObservationIds: string[]; - observationIds: string[]; - confidence: number; - completionState: TodoCompletionState; - completionSummary: string; - nextStep?: string; - dedupeKey: string; -}; - -type ExtractedTodoWithContext = ExtractedTodo & { - quality?: TodoQuality; - taskChain?: TodoTaskChain; -}; - -type TodoExtractOptions = { - maxSessions?: number; - maxObservationsPerSession?: number; - // STEP-11: scope controls. sinceDays = only sessions within the last N days - // are eligible (primary control). maxInteractionsPerSession = per session, - // keep at most M most-recent interaction records (turns). Both fall back to - // env (AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS / _MAX_INTERACTIONS_PER_SESSION) - // then the config defaults when omitted. - sinceDays?: number; - maxInteractionsPerSession?: number; - project?: string; - force?: boolean; - scanSources?: boolean; - cleanup?: "none" | "dry-run" | "apply"; - runLangExtractSidecar?: LangExtractRunner; -}; - -type TodoRefreshActionOptions = { - actionId?: string; -}; - -type LangExtractRunner = typeof runLangExtractSidecar; - -type ExtractForSessionOptions = { - runLangExtractSidecar?: LangExtractRunner; - refreshAction?: Record<string, unknown>; - forceLlmContext?: boolean; - taskChains?: TodoTaskChain[]; -}; - -const TIME_BUCKETS = new Set(["current", "recent", "history"]); -const TYPE_BUCKETS = new Set(["pending", "to_start", "follow_up", "in_progress", "done", "processing"]); -const SIDE_CAR = "todo-extract-langextract.py"; -const CLEANUP_SIDE_CAR = "todo-update-llm.py"; -const MAX_LLM_OBSERVATIONS_PER_SESSION = 40; -const SIDE_CAR_ENV_KEYS = [ - "LANGEXTRACT_API_KEY", - "LANGEXTRACT_BASE_URL", - "LANGEXTRACT_MODEL", - "LANGEXTRACT_PROVIDER", - "LANGEXTRACT_THINKING_DEPTH", - "LANGEXTRACT_PASSES", - "LANGEXTRACT_MAX_WORKERS", - "LANGEXTRACT_MAX_CHAR_BUFFER", -]; -const TECH_IDENTIFIER_PATTERN = /(?:\b[a-z][a-z0-9_.-]*\/[a-z0-9][a-z0-9_.-]*(?:\/[a-z0-9][a-z0-9_.-]*)*\b|\/(?:Users|tmp|var|private|Volumes)\/\S+|https?:\/\/\S+|\b[0-9a-f]{7,40}\b)/i; -const DANGLE_TITLE_PATTERN = /(?:到|为|把|对|向|在|从|将|with|to|for|from|into|onto|via|using)$/i; -const BRANCH_IDENTIFIER_PATTERN = /\b[a-z][a-z0-9_.-]*\/[a-z0-9][a-z0-9_.-]*(?:\/[a-z0-9][a-z0-9_.-]*)*\b/i; - -function envNumber(key: string, fallback: number): number { - const parsed = Number(getEnvVar(key)); - return Number.isFinite(parsed) ? parsed : fallback; -} - -function clampPositiveInt(value: unknown, fallback: number, max: number): number { - const parsed = typeof value === "number" ? value : parseInt(String(value ?? ""), 10); - if (!Number.isFinite(parsed) || parsed < 1) return fallback; - return Math.min(max, Math.floor(parsed)); -} - -function normalizeText(value: string | undefined): string { - return String(value || "").replace(/\s+/g, " ").trim(); -} - -function normalizedKey(value: string): string { - return normalizeText(value).toLowerCase().replace(/[,。!?;:,.!?;:]/g, "").replace(/\s+/g, " "); -} - -// STEP-08 PR 4/5: the rules/LLM extractors emit clusters of near-identical -// titles ("克隆上游项目到子目录" reworded with insertions/word-order drift) that -// the exact-key dedup misses. nearDuplicateTitle (in schema.ts) is the single -// source of truth for the policy (similarity bar + discriminator vetoes); here -// we just scan it against already-accepted *active* titles. -function isNearDuplicateTitle(normalizedTitle: string, seenTitles: string[]): boolean { - return seenTitles.some((seen) => nearDuplicateTitle(normalizedTitle, seen)); -} - -// Seed the near-dup guard only from titles of work that is still open. A new -// pending todo that resembles a done/cancelled action (or an already -// approved/dismissed review) must still be allowed — the work may have -// regressed or reopened. (Exact-dup suppression keeps its existing behavior.) -function existingActiveTitles(actions: Action[], reviews: ReviewQueueItem[]): string[] { - const titles: string[] = []; - for (const action of actions) { - if (action.status === "done" || action.status === "cancelled") continue; - const title = normalizedKey(action.title); - if (title) titles.push(title); - } - for (const item of reviews) { - if (item.kind !== "action") continue; - if (item.status === "approved" || item.status === "dismissed") continue; - const title = normalizedKey(item.title); - if (title) titles.push(title); - } - return titles; -} - -function stripTitleNoise(value: string): string { - let text = normalizeText(value) - .replace(/^["'`“”‘’]+|["'`“”‘’]+$/g, "") - .replace(/^[-–—•*]\s*/u, ""); - for (let i = 0; i < 3; i++) { - text = text - .replace(/^(todo|fixme)\s*[::-]\s*/i, "") - .replace(/^因为[^,,]{0,40}[,,]\s*/u, "") - .replace(/^(下一步|后续|待办|请|需要|必须|我会|我将|现在我会|接下来|先)\s*[::-]?\s*/u, "") - .replace(/^(follow up|follow-up|fix)\s*[::-]?\s*/i, "") - .trim(); - } - return text; -} - -// Trim a title to `cap` characters without splitting a word / CJK char / -// surrogate pair: prefer the last clause boundary (,,;;、 or whitespace) -// at or before the cap; never hard-cut mid-token (the old `.slice(0, n)` -// produced fragments like "返回 4" / "…/he"). -function trimToTitleBoundary(text: string, cap: number): string { - const chars = Array.from(text); - if (chars.length <= cap) return text.replace(/[,,;;::\s]+$/u, ""); - const head = chars.slice(0, cap); - let boundary = -1; - for (let i = head.length - 1; i >= Math.min(12, cap >> 1); i--) { - if (/[,,;;、\s]/u.test(head[i])) { - boundary = i; - break; - } - } - const cut = (boundary > 0 ? head.slice(0, boundary) : head).join(""); - return cut.replace(/[,,;;::\s]+$/u, ""); -} - -// Reject a title that is obviously a truncation fragment rather than a real -// todo (HTTP status cut to "返回 4", a list cut to "…、/he", or a dangling -// URL). Such a candidate is skipped in favour of the next source field. -function looksTruncated(text: string): boolean { - const t = text.trim(); - if (!t) return false; - if (/(?:返回|status|code|状态码)\s*\d{1,2}$/i.test(t)) return true; - if (/[、,,]\s*\/[A-Za-z]{1,4}$/.test(t)) return true; - if (/\bhttps?:\/\/\S*[:/]$/i.test(t)) return true; - return false; -} - -function firstTitleSentence(value: string): string { - const text = stripTitleNoise(value).replace(/`[^`]*`/g, "").replace(/\s+/g, " "); - const sentence = text.match(/[^。!?!?\n]+[。!?!?]?/u)?.[0] || text; - const compact = sentence - .replace(/[。!?!?;;,.,\s]+$/u, "") - .replace(/[,,;;]\s*(?:若|如果|如仍|否则|并且|同时|以便|避免|不让)\b.*$/u, "") - .replace(/[,,]?\s*(?:并|然后)?\s*(保存到|保存|写入|输出|放到|存到)\s*$/u, "") - .replace(/(?:,|,)?\s*(并|然后)\s*$/u, "") - .trim() - .replace(/[,,;;::\s]+$/u, ""); - if (compact.length <= 42) return compact; - const short = compact - .replace(/^(?:我会|我要|我将|现在我会|接下来)\s*/u, "") - .replace(/[,,;;]\s*.*$/u, "") - .trim(); - return trimToTitleBoundary(short || compact, 42); -} - -function looksIncompleteTitle(value: string): boolean { - const text = normalizeText(value).replace(/[。!?!?,,;;::\s]+$/u, "").trim(); - if (!text) return true; - if (looksTruncated(text)) return true; - if (DANGLE_TITLE_PATTERN.test(text)) return true; - if (/^(?:准备|开始|继续|接下来|现在我会)\s*[^\n。!?]{0,80}(?:到|为|把|对|向|在|从|将)$/u.test(text)) return true; - return false; -} - -function compactTitleTechnicalIdentifiers(title: string, description: string, quote = ""): { title: string; compacted: boolean } { - const text = normalizeText(title); - const context = normalizeText(`${title} ${description} ${quote}`); - if (!TECH_IDENTIFIER_PATTERN.test(text)) return { title: text, compacted: false }; - if (/(?:推送|提交|push)/i.test(context) && /(?:\borigin\b|远程|remote|仓库|repo)/i.test(context)) { - const target = /\borigin\b/i.test(context) ? "origin" : I18NChineseTitle(context) ? "远程仓库" : "the remote repository"; - const zhTitle = target === "origin" ? `推送当前工作分支到 ${target}` : `推送当前工作分支到${target}`; - return { title: I18NChineseTitle(context) ? zhTitle : `Push the current branch to ${target}`, compacted: true }; - } - const compacted = text - .replace(BRANCH_IDENTIFIER_PATTERN, I18NChineseTitle(context) ? "当前工作分支" : "current branch") - .replace(/https?:\/\/\S+/ig, I18NChineseTitle(context) ? "相关链接" : "the link") - .replace(/\/(?:Users|tmp|var|private|Volumes)\/\S+/ig, I18NChineseTitle(context) ? "相关文件" : "the file") - .replace(/\b[0-9a-f]{7,40}\b/ig, I18NChineseTitle(context) ? "相关提交" : "the commit") - .replace(/\s+/g, " ") - .trim(); - return { title: compacted || text, compacted: compacted !== text }; -} - -function I18NChineseTitle(value: string): boolean { - return /[\u4e00-\u9fff]/u.test(value); -} - -function assessTodoQuality(todo: ExtractedTodo): TodoQuality { - const title = normalizeText(todo.title); - const description = normalizeText(todo.description); - const evidence = normalizeText(todo.evidence?.quote); - const warnings: string[] = []; - if (looksIncompleteTitle(title)) return { confidence: 0, reason: "incomplete-title", warnings: ["title is incomplete or truncated"] }; - if (isCompletedTodoText(title) || isCompletedTodoText(description) || isCompletedTodoText(evidence)) { - return { confidence: 0, reason: "completed", warnings: ["looks completed"] }; - } - if (isPollutedTodoText(title) || isPollutedTodoText(description) || isPollutedTodoText(evidence)) { - return { confidence: 0, reason: "polluted", warnings: ["looks like log or tool output"] }; - } - if (isPureProcessCheck(`${title} ${description} ${evidence}`) || isPureStatusReport(`${title} ${description}`)) { - return { confidence: 0, reason: "process-or-status", warnings: ["looks like process/status narration"] }; - } - let confidence = 0.9; - if (!hasTodoActionTrigger(`${title} ${description}`)) { - confidence = Math.min(confidence, 0.58); - warnings.push("weak action verb"); - } - if (TECH_IDENTIFIER_PATTERN.test(title)) { - confidence = Math.min(confidence, 0.7); - warnings.push("title contains long technical identifier"); - } - if (Array.from(title).length > 56) { - confidence = Math.min(confidence, 0.72); - warnings.push("title is too long to scan"); - } - return { confidence, reason: confidence >= 0.55 ? "ok" : "low-actionability", warnings }; -} - -function todoQualityMetadata(quality: TodoQuality): Record<string, unknown> { - return { - confidence: quality.confidence, - reason: quality.reason, - warnings: quality.warnings, - ...(quality.titleCompacted ? { titleCompacted: true } : {}), - ...(quality.originalTitle ? { originalTitle: quality.originalTitle } : {}), - }; -} - -function effectiveTodoConfidence(todo: ExtractedTodo): number { - const quality = (todo as ExtractedTodoWithContext).quality; - return Math.min(todo.confidence, quality?.confidence ?? 1); -} - -function firstInvalidTodoReason(todos: ExtractedTodo[], blockMap: Map<string, Pick<ObservationBlock, "text">>): string { - for (const rawTodo of todos) { - const todo = todoForStorage(rawTodo); - if (!todo) { - const title = normalizeText(rawTodo.title || rawTodo.description || rawTodo.evidence?.quote); - if (looksIncompleteTitle(title)) return "incomplete-title"; - if (isCompletedTodoText(title)) return "completed-or-history"; - if (isPollutedTodoText(title)) return "polluted"; - return "low-quality"; - } - if (!validateTodoEvidence(todo, blockMap)) return "evidence-invalid"; - if (todo.timeBucket === "history" || todo.typeBucket === "done") return "completed-or-history"; - } - return "no-valid-todo"; -} - -function looksLikeBadTitle(value: string): boolean { - const text = normalizeText(value); - const lower = text.toLowerCase(); - if (text.length < 3) return true; - if (!/[A-Za-z\u4e00-\u9fff]/.test(text)) return true; - if (/^[{\[]/.test(text)) return true; - if (/^-[a-z0-9_-]+(?:\s|$)/i.test(text)) return true; - if (/^[A-Za-z0-9_.-]+\/?$/.test(text)) return true; - if (/\b(tooluseid|tooluse|call_[a-z0-9]+|chunk id|wall time|process exited)\b/i.test(text)) return true; - if (/"(cmd|command|workdir|yield_time_ms|max_output_tokens)"\s*:/.test(text)) return true; - if (/^\/(?:tmp|users|var|private|volumes)\//i.test(text)) return true; - if (/^[\w.-]+\/(?:\.\.\.|[\w.-]+\/)/.test(text)) return true; - if ((/\/|\\/.test(text)) && /\.(png|jpe?g|gif|webp|json|ya?ml|ts|tsx|js|py|md)\b/i.test(text)) return true; - if (/^\s*(?:gh|git|npm|pnpm|yarn|python3?|node|curl)\s+[^\n]*(?:--json|--limit|--workdir|--max-output|--yield-time|status|show|list|run|test|install|build)\b/i.test(text)) return true; - if (/^(?:json|state|limit)\s+[\w.-]+/i.test(text)) return true; - if (/\b(?:nameWithOwner|headRefName|baseRefName|databaseId)\b/.test(text)) return true; - if (/\b--(?:json|limit|state|repo|workdir|max-output|yield-time)\b/i.test(text)) return true; - const punctuation = (text.match(/[{}[\]":,]/g) || []).length; - if (text.length >= 24 && punctuation / text.length > 0.2) return true; - return lower === "untitled todo" || lower === "untitled candidate"; -} - -const PROCESS_CHECK_PHRASES = /(?:做最后一次状态确认|最后一次状态确认|启动后做健康检查|做健康检查|健康检查已完成|确认工作区干净|确认当前分支|确认 PR 链接|服务可用|重启 Codex desktop app 后再测一次|重启 Codex desktop app 后再测|重启后再测一次|重启后再测)/i; -const PROCESS_CHECK_REWRITEABLE = /(?:重启 Codex desktop app 后再测一次|重启 Codex desktop app 后再测|重启后再测一次|重启后再测)/i; -const DURABLE_DELIVERABLE_TERMS = /(?:修复|修正|补充|实现|调整|验证|排查|定位|跟进|整理|生成|上传|创建|更新|移除|删除|审查|合并|推送|提交|构建|\b(?:fix|add|update|create|remove|validate|retry|rerun|re-run|follow up|follow-up|investigate|debug|resolve|implement)\b)/i; - -function isProcessCheckText(value: string | undefined): boolean { - const text = normalizeText(value); - return !!text && PROCESS_CHECK_PHRASES.test(text); -} - -function isRewriteableProcessCheck(value: string | undefined): boolean { - const text = normalizeText(value); - return !!text && PROCESS_CHECK_REWRITEABLE.test(text); -} - -function isPureProcessCheck(value: string | undefined): boolean { - const text = normalizeText(value); - if (!text || !isProcessCheckText(text)) return false; - if (isRewriteableProcessCheck(text)) return false; - const remainder = stripTitleNoise(text).replace(PROCESS_CHECK_PHRASES, ""); - return !DURABLE_DELIVERABLE_TERMS.test(remainder); -} - -// An action being requested — used to exempt status/completed-narration text -// from the pollution filter, so a real repair that mentions a status phrase -// ("修复服务可用性回归", "排查…失败") is not silently dropped. -const TODO_ACTION_TRIGGER = /(?:修复|修正|补充|实现|调整|验证|排查|定位|跟进|整理|生成|上传|创建|更新|移除|删除|处理|审查|合并|推送|提交|构建|设计|重试|重新(?:运行|跑)|需要|必须|未完成|阻塞|TODO|FIXME|\b(?:fix|add|update|create|remove|validate|retry|rerun|re-run|follow up|follow-up|need to|must|blocked|blocking|investigate|debug|resolve|handle|implement)\b)/i; -const AGENT_PROGRESS_PREFIX = /^(?:我会|我将|我要|现在我会|接下来|先|继续|等待|查看|读取|检查|确认|核对|梳理|记录|准备|进行|定位当前|开始)\b/u; -const PROGRESS_NOUNS = /(?:仓库现状|远程元数据|关键入口|GitHub 状态|依赖安装|安装完成|空闲端口|健康检查|本地可运行性验证|静态梳理|运行验证|截图|console|服务可用|页面已经能返回|工作区状态|同名目录|PR\/issue|PR、issue|CI 配置)/i; -const DONE_NARRATION = /(?:已(?:经)?|成功|顺利|全绿|pass(?:ed)?|merged|pushed|resolved|done|completed|works now|no action needed|完成|通过|可用|生效|能返回|能显示|已合并|已推送|已更新|已修复|无需处理)/i; -const FAILURE_SIGNAL = /(?:失败|未通过|\b(?:failed|failing|command failed|exit code [1-9]\d*|exited with code [1-9]\d*)\b)/i; -const FAILURE_REPAIR_TRIGGER = /(?:修复|排查|定位|重试|重新(?:运行|跑)|处理|解决|\b(?:fix|retry|rerun|re-run|investigate|debug|resolve|handle)\b)/i; - -function hasTodoActionTrigger(value: string | undefined): boolean { - const text = normalizeText(value); - if (!text) return false; - if (TODO_ACTION_TRIGGER.test(text)) return true; - return FAILURE_SIGNAL.test(text) && FAILURE_REPAIR_TRIGGER.test(text); -} - -function isPollutedTodoText(value: string | undefined): boolean { - const text = normalizeText(value); - const lower = text.toLowerCase(); - if (!text) return false; - if (text.length <= 90 && looksLikeBadTitle(text)) return true; - if (/please implement this plan/i.test(text)) return true; - if (/^#{1,3}\s+.*(?:计划|Plan)\s*$/im.test(text) && /#{1,3}\s+(?:Summary|Key Changes|Test Plan|Assumptions|Implementation|执行步骤|验证命令)\b/im.test(text)) return true; - if (/"(?:plan|status|step)"\s*:/.test(text) && /"step"\s*:/.test(text)) return true; - if (/审查结果\s*\[[Pp]\d+\]/.test(text) && /(?:src|test)\/[^\s]+/.test(text)) return true; - if (/toolinput|tooloutput|function_id|tooluseid|tooluse|call_[a-z0-9]+|chunk id|wall time|process exited/i.test(text)) return true; - if (/"(?:cmd|command|workdir|yield_time_ms|max_output_tokens)"\s*:/.test(text)) return true; - if (/^\/(?:tmp|users|var|private|volumes)\//i.test(text)) return true; - if (/^\s*(?:gh|git|npm|pnpm|yarn|python3?|node|curl)\s+[^\n]*(?:--json|--limit|--workdir|--max-output|--yield-time|status|show|list|run|test|install|build)\b/i.test(text)) return true; - if (/^(?:json|state|limit)\s+[\w.-]+/i.test(lower)) return true; - if (/\b(?:namewithowner|headrefname|baserefname|databaseid)\b/i.test(lower)) return true; - if (/^⏺/.test(text) || /\b(?:bash|shell|exec)\(/i.test(text)) return true; - if (/^[a-z][a-z0-9_-]*-[0-9a-f]{6,}`?$/i.test(text)) return true; - if (/\b(?:Viewer|Health)\b\s*[::]\s*(?:\[|https?:\/\/)/i.test(text)) return true; - if (isPureProcessCheck(text)) return true; - if (AGENT_PROGRESS_PREFIX.test(text) && PROGRESS_NOUNS.test(text) && !hasTodoActionTrigger(text)) return true; - // Status-report and completed-work narration are pollution ONLY when no action - // is being requested. The action-verb exception keeps genuine repairs like - // "修复服务可用性回归" / "验证页面已经能返回后的边界问题" / "排查…失败" out of the filter. - if (!hasTodoActionTrigger(text)) { - if (/服务可用/.test(text)) return true; - } - return false; -} - -function isCompletedTodoText(value: string | undefined): boolean { - const text = normalizeText(value); - if (!text || hasTodoActionTrigger(text)) return false; - return DONE_NARRATION.test(text) && ( - /(?:验收|接口|页面|测试|CI|PR|提交|构建|服务|标签|标题|console|HTML|main|仓库|截图|tests?|build|service|deploy)/i.test(text) || - /(?:已完成|已通过|已合并|已推送|全绿|能返回|能显示|no action needed|works now)/i.test(text) - ); -} - -function isPureStatusReport(value: string | undefined): boolean { - const text = normalizeText(value); - return !hasTodoActionTrigger(text) && ( - /服务可用/.test(text) || - /\b(?:Viewer|Health)\b\s*[::]\s*(?:\[|https?:\/\/)/i.test(text) || - /\b(?:no action needed|works now)\b/i.test(text) - ); -} - -function isUsefulTodoText(value: string | undefined): boolean { - const text = normalizeText(value); - if (!text || isPollutedTodoText(text) || isCompletedTodoText(text)) return false; - if (AGENT_PROGRESS_PREFIX.test(text) && !hasTodoActionTrigger(text)) return false; - return hasTodoActionTrigger(text) || /(?:^|[\s((])(?:TODO|FIXME)\b|(?:follow up|follow-up)\b/i.test(text); -} - -export function cleanTodoTitle(title: string, description = "", quote = ""): string | null { - if (isPureProcessCheck(`${title} ${description} ${quote}`)) return null; - for (const raw of [title, description, quote]) { - const candidate = firstTitleSentence(raw); - if (isPureProcessCheck(candidate)) continue; - if (candidate && !looksLikeBadTitle(candidate) && !looksTruncated(candidate)) return candidate; - } - return null; -} - -function todoForStorage(todo: ExtractedTodo): (ExtractedTodo & { quality: TodoQuality }) | null { - // STEP-08 Layer 2: never store completed work as a todo — the surface is - // for UNRESOLVED pain points. (Enum keeps accepting "done"; we filter at emit.) - if (todo.typeBucket === "done") return null; - const title = cleanTodoTitle(todo.title, todo.description, todo.evidence?.quote); - if (!title) return null; - const description = normalizeText(todo.description || todo.evidence?.quote).slice(0, 1000); - if (!description) return null; - const compacted = compactTitleTechnicalIdentifiers(title, description, todo.evidence?.quote); - const finalTitle = compacted.title; - const quality = assessTodoQuality({ ...todo, title: finalTitle, description }); - if ( - quality.reason !== "ok" || - isCompletedTodoText(finalTitle) || isCompletedTodoText(description) || isCompletedTodoText(todo.evidence?.quote) || - isPollutedTodoText(finalTitle) || isPollutedTodoText(description) || isPollutedTodoText(todo.evidence?.quote) - ) - return null; - const rawDedupe = normalizeText(todo.dedupeKey); - const dedupeKey = rawDedupe && !looksLikeBadTitle(rawDedupe) - ? normalizedKey(rawDedupe) - : normalizedKey(`${finalTitle}:${description}`); - return { - ...todo, - title: finalTitle, - description, - dedupeKey, - quality: { - ...quality, - ...(compacted.compacted ? { titleCompacted: true, originalTitle: title } : {}), - }, - }; -} - -function sessionSortTime(session: Session): string { - return session.endedAt || session.startedAt || ""; -} - -// STEP-11 interaction windowing. -// A compressed observation starts a new "interaction record" (turn) when it is -// a user message. The default zero-LLM synthetic compression (replay.ts → -// buildSyntheticCompression) types a user prompt as "conversation" and titles it -// with its raw hookType "prompt_submit" — the only role signal that survives -// compression. If LLM auto-compress is on (non-default), titles are richer and -// no boundary is found, in which case takeRecentInteractions degrades to -// "whole session = one interaction" (keep everything). -function observationStartsInteraction(obs: CompressedObservation): boolean { - if (obs.type !== "conversation") return false; - const title = (obs.title || "").trim(); - // Codex synthetic compression titles a user prompt "prompt_submit"; browser - // capture (recordBrowserSessionFallback) titles a user turn "用户发言". Either - // one starts a new interaction record. - return /^prompt_submit$/i.test(title) || title === "用户发言"; -} - -// Keep only the most recent `maxInteractions` interaction records of a -// timestamp-ascending observation list. One interaction = a user message -// through everything before the next user message (its tool calls + agent -// replies). Returns a contiguous chronological tail. -function takeRecentInteractions( - observations: CompressedObservation[], - maxInteractions: number, -): CompressedObservation[] { - if (maxInteractions <= 0 || observations.length <= 1) return observations; - const boundaries: number[] = []; - for (let i = 0; i < observations.length; i++) { - if (observationStartsInteraction(observations[i])) boundaries.push(i); - } - if (boundaries.length <= maxInteractions) return observations; - const cutoff = boundaries[boundaries.length - maxInteractions]; - return observations.slice(cutoff); -} - -function interactionRanges(observations: CompressedObservation[]): Array<{ start: number; end: number }> { - if (!observations.length) return []; - const starts: number[] = []; - for (let i = 0; i < observations.length; i++) { - if (observationStartsInteraction(observations[i])) starts.push(i); - } - if (!starts.length || starts[0] !== 0) starts.unshift(0); - return starts.map((start, index) => ({ - start, - end: starts[index + 1] ?? observations.length, - })); -} - -const LOW_INFO_USER_PROMPT = /^(?:继续|重试|再试一次|再来一次|再跑一次|看下|看一下|这个不对|不对|继续处理|继续吧|retry|continue|again|try again|rerun|re-run)$/i; -const BLOCKED_SIGNAL = /(?:阻塞|卡住|卡在|失败|报错|没有权限|网络错误|超时|\b(?:blocked|blocking|failed|failing|error|permission denied|network error|timeout)\b)/i; -const NEXT_STEP_SIGNAL = /(?:下一步|后续|还需|仍需|待办|待处理|需要继续|需要修|需要补|需要排查|需要重试|TODO|FIXME|\b(?:need to|needs? fixing|must|follow up|follow-up|next step)\b)/i; -const NO_MORE_WORK_SIGNAL = /(?:无需后续|无需继续|无需处理|不需要后续|无后续|no action needed|nothing else|no further action)/i; - -function isLowInfoUserPrompt(value: string): boolean { - const text = normalizeText(value).replace(/[。!?!?,,;;::\s]+$/u, ""); - if (!text || Array.from(text).length > 32) return false; - return LOW_INFO_USER_PROMPT.test(text); -} - -function hasNextStepSignal(value: string): boolean { - const text = normalizeText(value); - if (!text || NO_MORE_WORK_SIGNAL.test(text)) return false; - return NEXT_STEP_SIGNAL.test(text); -} - -function completionStateFor(value: string): TodoCompletionState { - const text = normalizeText(value); - if (!text) return "in_progress"; - if (BLOCKED_SIGNAL.test(text)) return "interrupted"; - if (hasNextStepSignal(text)) return "in_progress"; - if (NO_MORE_WORK_SIGNAL.test(text) || isCompletedTodoText(text)) return "completed"; - return "in_progress"; -} - -function completionSummaryFor(value: string): string { - const text = normalizeText(value); - const first = text.match(/[^。!?!?\n]+[。!?!?]?/u)?.[0] || text; - return trimToTitleBoundary(first.replace(/\s+/g, " ").trim(), 140); -} - -function nextStepFor(value: string): string | undefined { - const text = normalizeText(value); - if (!text || NO_MORE_WORK_SIGNAL.test(text)) return undefined; - const match = text.match(/(?:下一步|后续|还需|仍需|待办|待处理)[::\s]*(需要)?([^。!?!?\n]{2,120})/u) || - text.match(/\b(?:need to|next step|follow up)[:\s]+([^.!?\n]{2,120})/i); - const next = normalizeText((match && (match[2] || match[1])) || ""); - return next ? next.replace(/[。!?!?,,;;::\s]+$/u, "") : undefined; -} - -function latestStatusObservation(range: CompressedObservation[], userObservationId: string | undefined): CompressedObservation | undefined { - for (let i = range.length - 1; i >= 0; i--) { - const obs = range[i]; - if (obs.id === userObservationId) continue; - const text = textForObservation(obs); - if (text && !isPollutedTodoText(text)) return obs; - } - return range[range.length - 1]; -} - -function intentUsefulForTaskChain(value: string): boolean { - const text = normalizeText(value); - if (!text || isLowInfoUserPrompt(text) || isPollutedTodoText(text)) return false; - if (/^<environment_context>|^#\s+/i.test(text)) return false; - return hasTodoActionTrigger(text) || Array.from(text).length >= 8; -} - -function taskChainDedupeKey(intent: string, status: string): string { - return normalizedKey(`${firstTitleSentence(intent)}:${firstTitleSentence(status)}`).slice(0, 180); -} - -function createTaskChain(session: Session, userObs: CompressedObservation | undefined, statusObs: CompressedObservation, range: CompressedObservation[]): TodoTaskChain | null { - const userIntent = normalizeText(userObs ? textForObservation(userObs) : ""); - const latestStatus = normalizeText(textForObservation(statusObs)); - if (!intentUsefulForTaskChain(userIntent) && !observationUsefulForTodo(statusObs)) return null; - const anchorObs = userObs || statusObs; - const state = completionStateFor(latestStatus || userIntent); - return { - chainId: fingerprintId("chain", `${session.id}:${anchorObs.id}`), - sourceSessionId: session.id, - userObservationId: anchorObs.id, - userIntent: userIntent || latestStatus, - latestStatusObservationId: statusObs.id, - latestStatus, - continuedByObservationIds: [], - observationIds: range.map((obs) => obs.id).filter(Boolean), - confidence: observationUsefulForTodo(statusObs) ? 0.9 : 0.72, - completionState: state, - completionSummary: completionSummaryFor(latestStatus || userIntent), - ...(nextStepFor(latestStatus) ? { nextStep: nextStepFor(latestStatus) } : {}), - dedupeKey: taskChainDedupeKey(userIntent || latestStatus, latestStatus), - }; -} - -function mergeRangeIntoTaskChain(chain: TodoTaskChain, userObs: CompressedObservation | undefined, statusObs: CompressedObservation, range: CompressedObservation[]): TodoTaskChain { - const latestStatus = normalizeText(textForObservation(statusObs)) || chain.latestStatus; - const continued = userObs?.id ? [...chain.continuedByObservationIds, userObs.id] : chain.continuedByObservationIds; - const observationIds = Array.from(new Set([...chain.observationIds, ...range.map((obs) => obs.id).filter(Boolean)])); - return { - ...chain, - latestStatusObservationId: statusObs.id, - latestStatus, - continuedByObservationIds: continued, - observationIds, - completionState: completionStateFor(latestStatus), - completionSummary: completionSummaryFor(latestStatus || chain.completionSummary), - ...(nextStepFor(latestStatus) ? { nextStep: nextStepFor(latestStatus) } : {}), - dedupeKey: taskChainDedupeKey(chain.userIntent, latestStatus), - }; -} - -function buildTaskChains(session: Session, observations: CompressedObservation[]): TodoTaskChain[] { - const sorted = [...observations].sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || "")); - const chains: TodoTaskChain[] = []; - for (const range of interactionRanges(sorted)) { - const rangeObservations = sorted.slice(range.start, range.end); - if (!rangeObservations.length) continue; - const first = rangeObservations[0]; - const userObs = observationStartsInteraction(first) ? first : undefined; - const userText = normalizeText(userObs ? textForObservation(userObs) : ""); - const statusObs = latestStatusObservation(rangeObservations, userObs?.id); - if (!statusObs) continue; - if (userObs && isLowInfoUserPrompt(userText) && chains.length) { - chains[chains.length - 1] = mergeRangeIntoTaskChain(chains[chains.length - 1], userObs, statusObs, rangeObservations); - continue; - } - const chain = createTaskChain(session, userObs, statusObs, rangeObservations); - if (chain) chains.push(chain); - } - return chains.filter((chain) => chain.userIntent || chain.latestStatus); -} - -function taskChainForPrompt(chain: TodoTaskChain): Record<string, unknown> { - return { - chainId: chain.chainId, - userObservationId: chain.userObservationId, - userIntent: chain.userIntent.slice(0, 1200), - latestStatusObservationId: chain.latestStatusObservationId, - latestStatus: chain.latestStatus.slice(0, 1200), - continuedByObservationIds: chain.continuedByObservationIds, - observationIds: chain.observationIds, - confidence: chain.confidence, - completionState: chain.completionState, - completionSummary: chain.completionSummary, - ...(chain.nextStep ? { nextStep: chain.nextStep } : {}), - dedupeKey: chain.dedupeKey, - }; -} - -function taskChainMetadata(chain: TodoTaskChain): Record<string, unknown> { - return { - sourceSessionId: chain.sourceSessionId, - userObservationId: chain.userObservationId, - latestStatusObservationId: chain.latestStatusObservationId, - continuedByObservationIds: chain.continuedByObservationIds, - observationIds: chain.observationIds, - confidence: chain.confidence, - completionState: chain.completionState, - completionSummary: chain.completionSummary, - ...(chain.nextStep ? { nextStep: chain.nextStep } : {}), - dedupeKey: chain.dedupeKey, - }; -} - -function attachTaskChains(todos: ExtractedTodo[], chains: TodoTaskChain[]): ExtractedTodo[] { - if (!chains.length) return todos; - return todos.map((todo) => { - const sourceId = todo.evidence?.sourceObservationId; - const chain = chains.find((item) => sourceId && item.observationIds.includes(sourceId)) || (chains.length === 1 ? chains[0] : undefined); - return chain ? { ...todo, taskChain: chain } : todo; - }); -} - -function nearbyObservationContext( - observations: CompressedObservation[], - sourceObservationId: string | undefined, - maxObservations = 12, -): { observations: CompressedObservation[]; foundSource: boolean } { - const sorted = [...observations].sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || "")); - if (!sorted.length) return { observations: [], foundSource: false }; - const sourceIndex = sourceObservationId ? sorted.findIndex((obs) => obs.id === sourceObservationId) : -1; - if (sourceIndex < 0) { - return { observations: takeRecentInteractions(sorted, 2).slice(-maxObservations), foundSource: false }; - } - const ranges = interactionRanges(sorted); - const rangeIndex = ranges.findIndex((range) => sourceIndex >= range.start && sourceIndex < range.end); - if (rangeIndex < 0) return { observations: sorted.slice(Math.max(0, sourceIndex - 5), sourceIndex + 7), foundSource: true }; - const startRange = Math.max(0, rangeIndex - 2); - const endRange = Math.min(ranges.length - 1, rangeIndex + 2); - const picked = sorted.slice(ranges[startRange].start, ranges[endRange].end); - if (picked.length <= maxObservations) return { observations: picked, foundSource: true }; - const pickedSourceIndex = picked.findIndex((obs) => obs.id === sourceObservationId); - const start = Math.max(0, Math.min(pickedSourceIndex - Math.floor(maxObservations / 2), picked.length - maxObservations)); - return { observations: picked.slice(start, start + maxObservations), foundSource: true }; -} - -function timeBucketFor(session: Session, now = Date.now()): TimeBucket { - if (session.status === "active") return "current"; - const raw = session.endedAt || session.startedAt; - const at = raw ? new Date(raw).getTime() : 0; - if (!Number.isFinite(at) || at <= 0) return "recent"; - const ageMs = now - at; - if (ageMs <= 24 * 60 * 60 * 1000) return "current"; - if (ageMs <= 14 * 24 * 60 * 60 * 1000) return "recent"; - return "history"; -} - -function actionStatusFor(typeBucket: TypeBucket): Action["status"] { - if (typeBucket === "done") return "done"; - if (typeBucket === "in_progress" || typeBucket === "processing") return "active"; - return "pending"; -} - -function textForObservation(obs: CompressedObservation): string { - return normalizeText([obs.narrative, ...(obs.facts || []), obs.subtitle].filter(Boolean).join("\n")); -} - -function observationUsefulForTodo(obs: CompressedObservation): boolean { - const text = textForObservation(obs); - if (!text || isPollutedTodoText(text) || isCompletedTodoText(text)) return false; - return isUsefulTodoText(text); -} - -function todoObservationScore(obs: CompressedObservation): number { - const text = textForObservation(obs); - if (!observationUsefulForTodo(obs)) return 0; - let score = 1; - if (/(?:下一步|后续|待办|\b(?:need to|must|follow up|follow-up|TODO|FIXME)\b)/i.test(text)) score += 2; - if (FAILURE_SIGNAL.test(text) && hasTodoActionTrigger(text)) score += 2; - if (/(?:阻塞|卡住|\b(?:blocked|blocking)\b)/i.test(text)) score += 2; - return score; -} - -function prefilterTodoObservations(session: Session, observations: CompressedObservation[]): { - ruleObservations: CompressedObservation[]; - llmObservations: CompressedObservation[]; -} { - const scored = observations - .map((obs, index) => ({ obs, index, score: todoObservationScore(obs) })) - .filter((entry) => entry.score > 0); - const ruleObservations = scored.map((entry) => entry.obs); - if (timeBucketFor(session) === "history") return { ruleObservations, llmObservations: [] }; - const llmObservations = [...scored] - .sort((a, b) => b.score - a.score || a.index - b.index) - .slice(0, MAX_LLM_OBSERVATIONS_PER_SESSION) - .map((entry) => entry.obs); - return { ruleObservations, llmObservations }; -} - -function blockFor(obs: CompressedObservation): ObservationBlock { - return { - sourceObservationId: obs.id, - timestamp: obs.timestamp || "", - type: obs.type, - title: obs.title || "", - text: textForObservation(obs), - }; -} - -function sidecarPath(name: string = SIDE_CAR): string | null { - const base = dirname(fileURLToPath(import.meta.url)); - const candidates = [ - join(base, name), - join(base, "functions", name), - join(base, "..", "src", "functions", name), - join(base, "..", "functions", name), - resolve(process.cwd(), "src", "functions", name), - ]; - return candidates.find((candidate) => existsSync(candidate)) || null; -} - -export async function runLangExtractSidecar( - input: Record<string, unknown>, - opts: { timeoutMs?: number } = {}, -): Promise<ExtractedTodo[]> { - const script = sidecarPath(); - if (!script) throw new Error("langextract sidecar not found"); - const python = resolveLangExtractPython(); - const env = { ...process.env }; - for (const key of SIDE_CAR_ENV_KEYS) { - const value = getEnvVar(key); - if (value) env[key] = value; - } - env.LANGEXTRACT_MODEL = normalizeTodoExtractorModel(env.LANGEXTRACT_MODEL); - env.LANGEXTRACT_PROVIDER = normalizeTodoExtractorProvider(env.LANGEXTRACT_PROVIDER); - env.LANGEXTRACT_BASE_URL = env.LANGEXTRACT_BASE_URL || DEFAULT_LANGEXTRACT_BASE_URL; - const timeoutMs = opts.timeoutMs ?? envNumber("AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS", DEFAULT_TODO_EXTRACT_TIMEOUT_MS); - return new Promise((resolvePromise, reject) => { - const child = spawn(python, [script], { stdio: ["pipe", "pipe", "pipe"], env }); - let stdout = ""; - let stderr = ""; - const timer = setTimeout(() => { - child.kill("SIGKILL"); - reject(new Error("langextract sidecar timed out")); - }, timeoutMs); - child.stdout.setEncoding("utf8"); - child.stderr.setEncoding("utf8"); - child.stdout.on("data", (chunk) => { stdout += chunk; }); - child.stderr.on("data", (chunk) => { stderr += chunk; }); - child.on("error", (err) => { - clearTimeout(timer); - reject(err); - }); - child.on("close", (code) => { - clearTimeout(timer); - if (code !== 0) { - reject(new Error(stderr.trim() || `langextract sidecar exited ${code}`)); - return; - } - try { - const parsed = JSON.parse(stdout) as { todos?: unknown }; - resolvePromise(Array.isArray(parsed.todos) ? parsed.todos as ExtractedTodo[] : []); - } catch (err) { - reject(err); - } - }); - child.stdin.end(JSON.stringify(input)); - }); -} - -function toTypeBucket(reason: ActionCandidate["reason"]): TypeBucket { - if (reason === "follow_up") return "follow_up"; - if (reason === "command_failed" || reason === "validation_failed") return "processing"; - return "pending"; -} - -function candidateToTodo(candidate: ActionCandidate, session: Session, bucket: TimeBucket): ExtractedTodo | null { - const obsId = candidate.sourceObservationIds[0]; - if (!obsId) return null; - return { - title: candidate.title, - description: candidate.description, - confidence: candidate.confidence, - timeBucket: bucket, - typeBucket: toTypeBucket(candidate.reason), - sourceSessionId: session.id, - evidence: { - sourceObservationId: obsId, - quote: candidate.description, - }, - dedupeKey: normalizedKey(candidate.duplicateHint || `${candidate.title}:${candidate.description}`), - }; -} - -function safeTodo(raw: ExtractedTodo, session: Session): ExtractedTodo { - const confidence = Number(raw.confidence); - const typeBucket = TYPE_BUCKETS.has(raw.typeBucket) ? raw.typeBucket : "pending"; - const fallbackTime = timeBucketFor(session); - return { - title: normalizeText(raw.title).slice(0, 120) || "Untitled todo", - description: normalizeText(raw.description || raw.evidence?.quote).slice(0, 1000), - confidence: Number.isFinite(confidence) ? Math.max(0, Math.min(1, confidence)) : 0, - timeBucket: TIME_BUCKETS.has(raw.timeBucket) ? raw.timeBucket : fallbackTime, - typeBucket, - sourceSessionId: raw.sourceSessionId || session.id, - evidence: { - sourceObservationId: normalizeText(raw.evidence?.sourceObservationId), - quote: normalizeText(raw.evidence?.quote), - ...(Number.isFinite(raw.evidence?.charStart) ? { charStart: Number(raw.evidence.charStart) } : {}), - ...(Number.isFinite(raw.evidence?.charEnd) ? { charEnd: Number(raw.evidence.charEnd) } : {}), - }, - dedupeKey: normalizedKey(raw.dedupeKey || `${raw.title}:${raw.description}`), - }; -} - -export function validateTodoEvidence(todo: ExtractedTodo, blockMap: Map<string, Pick<ObservationBlock, "text">>): boolean { - if (!todo.evidence.sourceObservationId || !todo.evidence.quote) return false; - const block = blockMap.get(todo.evidence.sourceObservationId); - const blockText = normalizeText(block?.text); - const quote = normalizeText(todo.evidence.quote); - // The quote must be grounded IN the source observation — i.e. a substring of - // it. The reverse (quote ⊇ blockText) was too permissive: a model returning - // "[whole observation] + hallucinated extra" contains the block text and so - // would pass, defeating source grounding. Require quote ⊆ block only. - return !!blockText && !!quote && blockText.includes(quote); -} - -function existingDedupeKeys(actions: Action[], reviews: ReviewQueueItem[]): Set<string> { - const keys = new Set<string>(); - for (const action of actions) { - const extraction = action.metadata?.todoExtraction as Record<string, unknown> | undefined; - if (typeof extraction?.dedupeKey === "string") keys.add(extraction.dedupeKey); - keys.add(normalizedKey(action.title)); - keys.add(normalizedKey(`${action.title}:${action.description || ""}`)); - } - for (const item of reviews) { - if (item.kind !== "action") continue; - const extraction = item.payload?.todoExtraction as Record<string, unknown> | undefined; - if (typeof extraction?.dedupeKey === "string") keys.add(extraction.dedupeKey); - keys.add(normalizedKey(item.title)); - keys.add(normalizedKey(`${item.title}:${item.content || ""}`)); - } - return keys; -} - -function checkpointKey(session: Session): string { - return `${session.endedAt || session.startedAt || ""}:${session.observationCount || 0}`; -} - -function parseCheckpoint(cursor: string | undefined): Record<string, string> { - if (!cursor) return {}; - try { - const parsed = JSON.parse(cursor) as unknown; - if (!parsed || typeof parsed !== "object") return {}; - const raw = parsed as Record<string, unknown>; - if (raw.__engine !== "langextract") return {}; - const out: Record<string, string> = {}; - for (const [key, value] of Object.entries(raw)) { - if (key !== "__engine" && typeof value === "string") out[key] = value; - } - return out; - } catch { - return {}; - } -} - -function makeAction(todo: ExtractedTodo, session: Session, now: string): Action { - const quality = (todo as ExtractedTodoWithContext).quality; - const taskChain = (todo as ExtractedTodoWithContext).taskChain; - const changed = sessionChangedSinceExtraction(todo, session); - const tags = ["todo-extracted", `time:${todo.timeBucket}`, `type:${todo.typeBucket}`, ...(changed ? ["todo-recheck"] : [])]; - return { - id: fingerprintId("act", `todo:${todo.dedupeKey}`), - title: todo.title, - description: todo.description, - status: actionStatusFor(todo.typeBucket), - priority: todo.typeBucket === "follow_up" || todo.typeBucket === "processing" ? 7 : 5, - createdAt: now, - updatedAt: now, - createdBy: "todo-extract", - project: session.project || session.cwd, - tags, - sourceObservationIds: [todo.evidence.sourceObservationId], - sourceMemoryIds: [], - metadata: { - todoExtraction: withSourceCheckpoint(todo, session), - ...(quality ? { todoQuality: todoQualityMetadata(quality) } : {}), - ...(taskChain ? { todoChain: taskChainMetadata(taskChain) } : {}), - }, - }; -} - -function makeReview(todo: ExtractedTodo, session: Session, now: string): ReviewQueueItem { - const quality = (todo as ExtractedTodoWithContext).quality; - const taskChain = (todo as ExtractedTodoWithContext).taskChain; - const tags = ["todo-extracted", `time:${todo.timeBucket}`, `type:${todo.typeBucket}`]; - return { - id: generateId("review"), - createdAt: now, - updatedAt: now, - status: "pending", - kind: "action", - title: todo.title, - content: todo.description, - source: "viewer", - payload: { - project: session.project || session.cwd, - sourceSessionId: session.id, - tags, - actionCandidate: { - priority: todo.typeBucket === "follow_up" || todo.typeBucket === "processing" ? 7 : 5, - reason: todo.typeBucket, - confidence: todo.confidence, - sourceObservationIds: [todo.evidence.sourceObservationId], - }, - // STEP-12: store the session fingerprint like makeAction does, so a review - // card can also be picked up by updateChangedTodoCards when its source - // session later changes (otherwise the review-update path is dead). - todoExtraction: withSourceCheckpoint(todo, session), - ...(quality ? { todoQuality: todoQualityMetadata(quality) } : {}), - ...(taskChain ? { todoChain: taskChainMetadata(taskChain) } : {}), - }, - }; -} - -function actionLooksGenerated(action: Action): boolean { - const tags = Array.isArray(action.tags) ? action.tags : []; - return action.createdBy === "todo-extract" || - tags.includes("todo-extracted") || - tags.includes("action-candidate") || - !!action.metadata?.todoExtraction; -} - -function replaceActionFromTodo(action: Action, fresh: Action, engine: "langextract" | "rules", now: string): Action { - return { - ...action, - title: fresh.title, - description: fresh.description, - status: fresh.status, - priority: fresh.priority, - updatedAt: now, - project: fresh.project, - tags: fresh.tags, - sourceObservationIds: fresh.sourceObservationIds, - sourceMemoryIds: fresh.sourceMemoryIds, - metadata: { - ...(action.metadata || {}), - todoExtraction: fresh.metadata?.todoExtraction, - ...(fresh.metadata?.todoQuality ? { todoQuality: fresh.metadata.todoQuality } : {}), - ...(fresh.metadata?.todoChain ? { todoChain: fresh.metadata.todoChain } : {}), - refresh: { - refreshedAt: now, - engine, - reason: "replaced", - previousTitle: action.title, - }, - }, - }; -} - -function sessionChangedSinceExtraction(todo: ExtractedTodo, session: Session): boolean { - const stored = (todo as unknown as { sourceCheckpoint?: string }).sourceCheckpoint; - return !!stored && stored !== checkpointKey(session); -} - -function withSourceCheckpoint(todo: ExtractedTodo, session: Session): ExtractedTodo & { sourceCheckpoint: string } { - return { - title: todo.title, - description: todo.description, - confidence: todo.confidence, - timeBucket: todo.timeBucket, - typeBucket: todo.typeBucket, - sourceSessionId: todo.sourceSessionId, - evidence: todo.evidence, - dedupeKey: todo.dedupeKey, - sourceCheckpoint: checkpointKey(session), - }; -} - -async function markChangedGeneratedActions( - kv: Pick<StateKV, "list" | "set">, - sessionsById: Map<string, Session>, - now: string, -): Promise<number> { - const actions = await kv.list<Action>(KV.actions).catch(() => []); - let marked = 0; - for (const action of actions) { - const extraction = action.metadata?.todoExtraction as (ExtractedTodo & { sourceCheckpoint?: string }) | undefined; - const session = extraction?.sourceSessionId ? sessionsById.get(extraction.sourceSessionId) : undefined; - if (!session || !extraction?.sourceCheckpoint || extraction.sourceCheckpoint === checkpointKey(session)) continue; - const tags = Array.isArray(action.tags) ? action.tags : []; - if (tags.includes("todo-recheck")) continue; - await kv.set(KV.actions, action.id, { - ...action, - updatedAt: now, - tags: [...tags, "todo-recheck"], - metadata: { - ...(action.metadata || {}), - todoExtraction: { - ...extraction, - needsRecheck: true, - latestSourceCheckpoint: checkpointKey(session), - }, - }, - }); - marked++; - } - return marked; -} - -function reviewLooksGenerated(item: ReviewQueueItem): boolean { - const payload = item.payload || {}; - const tags = Array.isArray(payload.tags) ? payload.tags.map(String) : []; - return item.kind === "action" && ( - tags.includes("todo-extracted") || - tags.includes("action-candidate") || - !!payload.todoExtraction || - !!payload.actionCandidate - ); -} - -type CleanupDecision = "garbage" | "done" | "keep"; - -function cleanupDecision(title: string, description: string): CleanupDecision { - if (isPureStatusReport(title) || isPureStatusReport(description)) return "garbage"; - if (isCompletedTodoText(title) || isCompletedTodoText(description)) return "done"; - if (isPollutedTodoText(title) || isPollutedTodoText(description)) return "garbage"; - if (!isUsefulTodoText(`${title} ${description}`)) return "garbage"; - return "keep"; -} - -function actionCleanupDecision(action: Action): CleanupDecision { - if (!actionLooksGenerated(action)) return "keep"; - if (action.status === "done" || action.status === "cancelled") return "keep"; - return cleanupDecision(action.title, action.description); -} - -function reviewCleanupDecision(item: ReviewQueueItem): CleanupDecision { - if (!reviewLooksGenerated(item) || item.status !== "pending") return "keep"; - return cleanupDecision(item.title, item.content); -} - -type CleanupMode = "dry-run" | "apply"; -type CleanupPreviewItem = { id: string; title: string; decision: "garbage" | "done" }; - -export async function cleanPollutedTodoCards( - kv: Pick<StateKV, "list" | "set">, - mode: CleanupMode = "apply", -): Promise<{ - cleanedActions: number; - cleanedReviews: number; - completedActions: number; - completedReviews: number; - preview: { actions: CleanupPreviewItem[]; reviews: CleanupPreviewItem[] }; -}> { - const [actions, reviews] = await Promise.all([ - kv.list<Action>(KV.actions).catch(() => []), - kv.list<ReviewQueueItem>(KV.reviewQueue).catch(() => []), - ]); - const now = new Date().toISOString(); - const actionDecisions = actions - .map((action) => ({ action, decision: actionCleanupDecision(action) })) - .filter((entry) => entry.decision !== "keep"); - const reviewDecisions = reviews - .map((item) => ({ item, decision: reviewCleanupDecision(item) })) - .filter((entry) => entry.decision !== "keep"); - if (mode === "apply") { - await Promise.all([ - ...actionDecisions.map(({ action, decision }) => kv.set(KV.actions, action.id, { - ...action, - status: decision === "done" ? "done" : "cancelled", - updatedAt: now, - metadata: { - ...(action.metadata || {}), - cleanup: { - decision, - cleanedAt: now, - previousStatus: action.status, - title: action.title, - description: action.description, - }, - }, - })), - ...reviewDecisions.map(({ item, decision }) => kv.set(KV.reviewQueue, item.id, { - ...item, - status: "dismissed", - updatedAt: now, - payload: { - ...(item.payload || {}), - cleanup: { - decision, - cleanedAt: now, - previousStatus: item.status, - title: item.title, - content: item.content, - }, - }, - })), - ]); - } - return { - cleanedActions: actionDecisions.filter((entry) => entry.decision === "garbage").length, - cleanedReviews: reviewDecisions.filter((entry) => entry.decision === "garbage").length, - completedActions: actionDecisions.filter((entry) => entry.decision === "done").length, - completedReviews: reviewDecisions.filter((entry) => entry.decision === "done").length, - preview: { - actions: actionDecisions.map(({ action, decision }) => ({ id: action.id, title: action.title, decision })), - reviews: reviewDecisions.map(({ item, decision }) => ({ id: item.id, title: item.title, decision })), - }, - }; -} - -// ── LLM card update (STEP-12, evolved from the STEP-10 cleanup) ───────────── -// Re-judges EXISTING cards whose source session changed (vs. extracting new -// ones): an LLM decides KEEP/DROP/DONE/REWRITE/MERGE against the card plus its -// session's recent activity. Reuses the soft-delete + audit shape below. Update -// is LLM-only — on failure it leaves cards untouched (no rule fallback). The -// shared helpers/types keep the "Cleanup" name for continuity with the -// persisted metadata.cleanup audit bag. - -type LlmCleanupDecision = "KEEP" | "DROP" | "DONE" | "REWRITE" | "MERGE"; -type LlmCleanupItem = { - id: string; - decision: LlmCleanupDecision; - reason?: string; - newTitle?: string; - newDescription?: string; - mergeIntoId?: string; -}; -type CleanupCard = { id: string; title: string; description: string; status: string; evidence?: string; sessionDelta?: string; titleQualityHint?: string }; -type TodoUpdateScope = "changed" | "all"; -type TodoUpdateOptions = { - mode?: CleanupMode; - maxCards?: number; - scope?: TodoUpdateScope; - decide?: (cards: CleanupCard[]) => Promise<LlmCleanupItem[]>; - decisions?: LlmCleanupItem[]; -}; - -const DEFAULT_CLEANUP_MAX_CARDS = 60; -const VAGUE_TITLE_TERMS = [ - "全面了解", - "了解现状", - "了解当前", - "梳理现状", - "梳理当前", - "获取信息", - "进行", - "处理", -]; -const PROCESS_TITLE_TERMS = [ - "重启 Codex desktop app 后再测", - "重启后再测", - "最后一次状态确认", - "做健康检查", - "健康检查", - "确认工作区", -]; - -function titleQualityHint(title: string): string { - const hints: string[] = []; - const vagueHits = VAGUE_TITLE_TERMS.filter((term) => title.includes(term)); - if (vagueHits.length) hints.push(`Title contains vague filler terms: ${vagueHits.join(", ")}.`); - const processHits = PROCESS_TITLE_TERMS.filter((term) => title.includes(term)); - if (processHits.length) hints.push(`Title looks like agent process or status-check narration: ${processHits.join(", ")}.`); - return hints.join(" "); -} - -async function runCleanupSidecar( - cards: CleanupCard[], - opts: { timeoutMs?: number } = {}, -): Promise<LlmCleanupItem[]> { - const script = sidecarPath(CLEANUP_SIDE_CAR); - if (!script) throw new Error("cleanup sidecar not found"); - const python = resolveLangExtractPython(); - const env = { ...process.env }; - for (const key of SIDE_CAR_ENV_KEYS) { - const value = getEnvVar(key); - if (value) env[key] = value; - } - env.LANGEXTRACT_MODEL = normalizeTodoExtractorModel(env.LANGEXTRACT_MODEL); - env.LANGEXTRACT_PROVIDER = normalizeTodoExtractorProvider(env.LANGEXTRACT_PROVIDER); - env.LANGEXTRACT_BASE_URL = env.LANGEXTRACT_BASE_URL || DEFAULT_LANGEXTRACT_BASE_URL; - const timeoutMs = opts.timeoutMs ?? envNumber("AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS", DEFAULT_TODO_EXTRACT_TIMEOUT_MS); - return new Promise((resolvePromise, reject) => { - const child = spawn(python, [script], { stdio: ["pipe", "pipe", "pipe"], env }); - let stdout = ""; - let stderr = ""; - const timer = setTimeout(() => { - child.kill("SIGKILL"); - reject(new Error("cleanup sidecar timed out")); - }, timeoutMs); - child.stdout.setEncoding("utf8"); - child.stderr.setEncoding("utf8"); - child.stdout.on("data", (chunk) => { stdout += chunk; }); - child.stderr.on("data", (chunk) => { stderr += chunk; }); - child.on("error", (err) => { clearTimeout(timer); reject(err); }); - child.on("close", (code) => { - clearTimeout(timer); - if (code !== 0) { reject(new Error(stderr.trim() || `cleanup sidecar exited ${code}`)); return; } - try { - const parsed = JSON.parse(stdout) as { decisions?: unknown }; - resolvePromise(Array.isArray(parsed.decisions) ? (parsed.decisions as LlmCleanupItem[]) : []); - } catch (err) { reject(err); } - }); - child.stdin.end(JSON.stringify({ cards })); - }); -} - -function cardEvidence(meta: Record<string, unknown> | undefined): string { - const extraction = meta?.todoExtraction as ExtractedTodo | undefined; - return extraction?.evidence?.quote || ""; -} - -function applyActionCleanup(action: Action, d: LlmCleanupItem, now: string): Action { - const cleanup: Record<string, unknown> = { - decision: d.decision.toLowerCase(), - llm: true, - reason: d.reason || "", - cleanedAt: now, - previousStatus: action.status, - title: action.title, - description: action.description, - ...(d.mergeIntoId ? { mergeIntoId: d.mergeIntoId } : {}), - }; - if (d.decision === "REWRITE") { - cleanup.previousTitle = action.title; - cleanup.previousDescription = action.description; - return { - ...action, - title: d.newTitle || action.title, - description: d.newDescription ?? action.description, - updatedAt: now, - metadata: { ...(action.metadata || {}), cleanup }, - }; - } - const status: Action["status"] = d.decision === "DONE" ? "done" : "cancelled"; - return { ...action, status, updatedAt: now, metadata: { ...(action.metadata || {}), cleanup } }; -} - -function applyReviewCleanup(item: ReviewQueueItem, d: LlmCleanupItem, now: string): ReviewQueueItem { - const cleanup: Record<string, unknown> = { - decision: d.decision.toLowerCase(), - llm: true, - reason: d.reason || "", - cleanedAt: now, - previousStatus: item.status, - title: item.title, - content: item.content, - ...(d.mergeIntoId ? { mergeIntoId: d.mergeIntoId } : {}), - }; - if (d.decision === "REWRITE") { - cleanup.previousTitle = item.title; - cleanup.previousContent = item.content; - return { - ...item, - title: d.newTitle || item.title, - content: d.newDescription ?? item.content, - updatedAt: now, - payload: { ...(item.payload || {}), cleanup }, - }; - } - return { ...item, status: "dismissed", updatedAt: now, payload: { ...(item.payload || {}), cleanup } }; -} - -// STEP-12: after an update pass, advance the card's stored source checkpoint to -// the session's current fingerprint and clear the recheck markers, so a card is -// not re-selected until its session changes again. -function advanceActionCheckpoint(action: Action, checkpoint: string, now: string): Action { - const extraction = (action.metadata?.todoExtraction || {}) as Record<string, unknown>; - const tags = Array.isArray(action.tags) ? action.tags.filter((tag) => tag !== "todo-recheck") : action.tags; - return { - ...action, - updatedAt: now, - tags, - metadata: { - ...(action.metadata || {}), - todoExtraction: { ...extraction, sourceCheckpoint: checkpoint, needsRecheck: false }, - }, - }; -} - -function advanceReviewCheckpoint(item: ReviewQueueItem, checkpoint: string, now: string): ReviewQueueItem { - const extraction = (item.payload?.todoExtraction || {}) as Record<string, unknown>; - return { - ...item, - updatedAt: now, - payload: { - ...(item.payload || {}), - todoExtraction: { ...extraction, sourceCheckpoint: checkpoint, needsRecheck: false }, - }, - }; -} - -// The session's most-recent activity (last ~2 interaction records), as text, so -// the updater LLM can re-judge a card against what happened after it was first -// recorded. Capped so the prompt stays bounded. -async function buildSessionDelta(kv: Pick<StateKV, "list">, sessionId: string): Promise<string> { - const observations = (await kv.list<CompressedObservation>(KV.observations(sessionId)).catch(() => [])) - .sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || "")); - // Rule-filter before sending to the LLM — never ship raw session/tool dumps. - // Keep the human-readable narrative of recent interactions and drop tool - // traces / commands / paths / JSON (isPollutedTodoText), mirroring what the - // extract path does with prefilterTodoObservations. - const recent = takeRecentInteractions(observations, 2); - const text = recent - .map((obs) => textForObservation(obs)) - .filter((line) => line && !isPollutedTodoText(line)) - .join("\n") - .trim(); - return text.length > 2000 ? text.slice(text.length - 2000) : text; -} - -export type LlmCleanupResult = { - engine: "llm" | "rules"; - scanned: number; - kept: number; - dropped: number; - completed: number; - rewritten: number; - merged: number; - preview: Array<{ id: string; title: string; decision: LlmCleanupDecision; reason?: string; newTitle?: string; mergeIntoId?: string }>; - // The exact decisions used, so a dry-run preview can be applied verbatim - // (the caller passes them back as opts.decisions) without re-calling the LLM. - decisions?: LlmCleanupItem[]; - fallbackReason?: string; -}; - -export async function updateChangedTodoCards( - kv: Pick<StateKV, "list" | "set">, - opts: TodoUpdateOptions = {}, -): Promise<LlmCleanupResult> { - const mode = opts.mode ?? "apply"; - const replayDecisions = opts.decisions; - const maxCards = replayDecisions ? Number.POSITIVE_INFINITY : opts.maxCards ?? DEFAULT_CLEANUP_MAX_CARDS; - const scope: TodoUpdateScope = opts.scope === "all" ? "all" : "changed"; - const decide = opts.decide ?? runCleanupSidecar; - const [actions, reviews, sessions] = await Promise.all([ - kv.list<Action>(KV.actions).catch(() => []), - kv.list<ReviewQueueItem>(KV.reviewQueue).catch(() => []), - kv.list<Session>(KV.sessions).catch(() => []), - ]); - const sessionsById = new Map(sessions.map((s) => [s.id, s] as [string, Session])); - const replayIds = replayDecisions ? new Set(replayDecisions.map((d) => d.id).filter(Boolean)) : null; - const actionNeedsUpdate = (a: Action): boolean => { - if (!actionLooksGenerated(a) || a.status === "done" || a.status === "cancelled") return false; - if (replayIds) return replayIds.has(`a:${a.id}`); - if (scope === "all") return true; - const ex = a.metadata?.todoExtraction as (ExtractedTodo & { sourceCheckpoint?: string }) | undefined; - const session = ex?.sourceSessionId ? sessionsById.get(ex.sourceSessionId) : undefined; - return !!ex && !!session && sessionChangedSinceExtraction(ex, session); - }; - const reviewNeedsUpdate = (r: ReviewQueueItem): boolean => { - if (!reviewLooksGenerated(r) || r.status !== "pending") return false; - if (replayIds) return replayIds.has(`r:${r.id}`); - if (scope === "all") return true; - const ex = r.payload?.todoExtraction as (ExtractedTodo & { sourceCheckpoint?: string }) | undefined; - const session = ex?.sourceSessionId ? sessionsById.get(ex.sourceSessionId) : undefined; - return !!ex && !!session && sessionChangedSinceExtraction(ex, session); - }; - const selectedActions = actions.filter(actionNeedsUpdate); - const selectedReviews = reviews.filter(reviewNeedsUpdate); - const actionById = new Map(selectedActions.map((a) => [`a:${a.id}`, a] as [string, Action])); - const reviewById = new Map(selectedReviews.map((r) => [`r:${r.id}`, r] as [string, ReviewQueueItem])); - - // Build the LLM card batch (capped), attaching each card's session delta — the - // session's most-recent activity — so the model can re-judge it against what - // happened after it was first recorded. Delta is built once per session. - const deltaCache = new Map<string, string>(); - const sessionDeltaFor = async (sessionId: string | undefined): Promise<string> => { - if (!sessionId) return ""; - if (!deltaCache.has(sessionId)) deltaCache.set(sessionId, await buildSessionDelta(kv, sessionId)); - return deltaCache.get(sessionId) || ""; - }; - // When applying a previously-previewed result, the caller passes the dry-run - // decisions back so we apply them verbatim — no LLM re-call, no session delta. - const reuseDecisions = replayDecisions; - const entries = [ - ...selectedActions.map((a) => ({ id: `a:${a.id}`, sessionId: (a.metadata?.todoExtraction as ExtractedTodo | undefined)?.sourceSessionId, title: a.title, description: a.description, status: a.status, evidence: cardEvidence(a.metadata) })), - ...selectedReviews.map((r) => ({ id: `r:${r.id}`, sessionId: (r.payload?.todoExtraction as ExtractedTodo | undefined)?.sourceSessionId, title: r.title, description: r.content, status: r.status, evidence: cardEvidence(r.payload) })), - ] - .sort((a, b) => normalizedKey(a.title).localeCompare(normalizedKey(b.title)) || a.id.localeCompare(b.id)) - .slice(0, maxCards); - const cards: CleanupCard[] = []; - for (const entry of entries) { - cards.push({ - id: entry.id, - title: entry.title, - description: entry.description, - status: entry.status, - evidence: entry.evidence, - titleQualityHint: titleQualityHint(entry.title), - sessionDelta: reuseDecisions ? "" : await sessionDeltaFor(entry.sessionId), - }); - } - - if (cards.length === 0) { - return { engine: "llm", scanned: 0, kept: 0, dropped: 0, completed: 0, rewritten: 0, merged: 0, preview: [], decisions: [] }; - } - - let decisions: LlmCleanupItem[]; - if (reuseDecisions) { - // Apply the exact decisions from the dry-run preview — never re-call the LLM - // on apply. Reasoning models vary even at temperature 0, so a re-call could - // diverge from what the user just confirmed (e.g. preview MERGE, apply DONE). - decisions = reuseDecisions; - } else { - try { - decisions = await decide(cards); - } catch (err) { - // Update is an LLM-only operation (it re-judges cards against new session - // content); there is no rule equivalent, so on failure leave every card - // untouched and report why via fallbackReason. Callers detect "LLM - // unavailable" by fallbackReason, not by a misnomer engine value. - return { - engine: "llm", - scanned: cards.length, - kept: cards.length, - dropped: 0, - completed: 0, - rewritten: 0, - merged: 0, - preview: [], - decisions: [], - fallbackReason: err instanceof Error ? err.message : String(err), - }; - } - } - - const now = new Date().toISOString(); - const decisionById = new Map(decisions.filter((d) => d && d.id).map((d) => [d.id, d] as [string, LlmCleanupItem])); - const batchIds = new Set(cards.map((c) => c.id)); - const writes: Array<Promise<unknown>> = []; - let kept = 0, dropped = 0, completed = 0, rewritten = 0, merged = 0; - const preview: LlmCleanupResult["preview"] = []; - - // Process every changed card in the batch. A KEEP (or a card the LLM returned - // no decision for) keeps its text but still has its checkpoint advanced — it - // has now been re-judged against the latest session, so it should not reappear - // until the session changes again. - for (const card of cards) { - const action = actionById.get(card.id); - const review = reviewById.get(card.id); - const target = action ?? review; - if (!target) continue; - const ex = (action ? action.metadata?.todoExtraction : review!.payload?.todoExtraction) as ExtractedTodo | undefined; - const session = ex?.sourceSessionId ? sessionsById.get(ex.sourceSessionId) : undefined; - const checkpoint = session ? checkpointKey(session) : undefined; - const d = decisionById.get(card.id); - let decision = d?.decision ?? "KEEP"; - // TS-side invariant: a MERGE whose target card isn't in this batch (or points - // at itself) would cancel the source with nothing to merge into. Downgrade it - // to KEEP so the source is preserved. (The sidecar normalizes this too, but an - // injected decide must not be able to bypass it.) - if (decision === "MERGE" && (!d?.mergeIntoId || d.mergeIntoId === card.id || !batchIds.has(d.mergeIntoId))) { - decision = "KEEP"; - } - if (decision === "KEEP") kept++; - else if (decision === "DROP") dropped++; - else if (decision === "DONE") completed++; - else if (decision === "REWRITE") rewritten++; - else if (decision === "MERGE") merged++; - if (decision !== "KEEP") { - preview.push({ id: card.id, title: target.title, decision, reason: d?.reason, newTitle: d?.newTitle, mergeIntoId: d?.mergeIntoId }); - } - if (mode !== "apply") continue; - if (action) { - let next = decision === "KEEP" ? action : applyActionCleanup(action, d!, now); - if (checkpoint) next = advanceActionCheckpoint(next, checkpoint, now); - writes.push(kv.set(KV.actions, action.id, next)); - } else if (review) { - let next = decision === "KEEP" ? review : applyReviewCleanup(review, d!, now); - if (checkpoint) next = advanceReviewCheckpoint(next, checkpoint, now); - writes.push(kv.set(KV.reviewQueue, review.id, next)); - } - } - await Promise.all(writes); - return { - engine: "llm", - scanned: cards.length, - kept, - dropped, - completed, - rewritten, - merged, - preview, - decisions, - }; -} - -async function extractForSession( - session: Session, - observations: CompressedObservation[], - mode: string, - options: ExtractForSessionOptions = {}, -): Promise<{ todos: ExtractedTodo[]; engine: "langextract" | "rules"; fallbackReason?: string }> { - const { ruleObservations, llmObservations } = prefilterTodoObservations(session, observations); - const taskChains = options.taskChains ?? buildTaskChains(session, observations); - const chainObservationIds = new Set(taskChains.flatMap((chain) => chain.observationIds)); - const chainObservations = observations.filter((obs) => chainObservationIds.has(obs.id)); - const llmSourceObservations = taskChains.length - ? chainObservations.slice(0, MAX_LLM_OBSERVATIONS_PER_SESSION) - : options.forceLlmContext && !llmObservations.length - ? observations.slice(0, MAX_LLM_OBSERVATIONS_PER_SESSION) - : llmObservations; - const blocks = llmSourceObservations.map(blockFor).filter((block) => block.text); - const bucket = timeBucketFor(session); - let fallbackReason = ""; - if (mode !== "rules" && blocks.length > 0) { - try { - const run = options.runLangExtractSidecar || runLangExtractSidecar; - const input: Record<string, unknown> = { - sessionId: session.id, - project: session.project, - cwd: session.cwd, - startedAt: session.startedAt, - endedAt: session.endedAt, - blocks, - taskChains: taskChains.map(taskChainForPrompt), - }; - if (options.refreshAction) input.refreshAction = options.refreshAction; - const todos = await run(input); - return { todos: attachTaskChains(todos.map((todo) => safeTodo(todo, session)), taskChains), engine: "langextract" }; - } catch (err) { - fallbackReason = err instanceof Error ? err.message : String(err || "langextract failed"); - // Fall back to rules even in langextract mode; the sidecar is optional. - } - } - const candidates = extractActionCandidatesFromObservations(ruleObservations); - return { - todos: candidates.map((candidate) => candidateToTodo(candidate, session, bucket)).filter((todo): todo is ExtractedTodo => !!todo), - engine: "rules", - ...(fallbackReason ? { fallbackReason } : {}), - }; -} - -function slimRecord(value: unknown, allowed: string[]): Record<string, unknown> | undefined { - if (!value || typeof value !== "object") return undefined; - const input = value as Record<string, unknown>; - const out: Record<string, unknown> = {}; - for (const key of allowed) { - const raw = input[key]; - if (typeof raw === "string") out[key] = raw.slice(0, 500); - else if (typeof raw === "number" || typeof raw === "boolean") out[key] = raw; - } - return Object.keys(out).length ? out : undefined; -} - -function refreshActionPromptContext(action: Action): Record<string, unknown> { - const metadata = action.metadata || {}; - return { - id: action.id, - title: action.title, - description: action.description, - status: action.status, - tags: action.tags, - sourceObservationIds: action.sourceObservationIds, - cleanup: slimRecord(metadata.cleanup, [ - "decision", - "title", - "description", - "reason", - "previousTitle", - "previousDescription", - "previousStatus", - ]), - todoExtraction: slimRecord(metadata.todoExtraction, [ - "title", - "description", - "confidence", - "timeBucket", - "typeBucket", - "dedupeKey", - ]), - }; -} - -function todoFromExistingActionEvidence(action: Action, session: Session): ExtractedTodo | null { - const extraction = action.metadata?.todoExtraction as Record<string, unknown> | undefined; - const evidence = extraction?.evidence as Record<string, unknown> | undefined; - const quote = normalizeText(typeof evidence?.quote === "string" ? evidence.quote : action.description); - const sourceObservationId = - normalizeText(typeof evidence?.sourceObservationId === "string" ? evidence.sourceObservationId : "") || - action.sourceObservationIds.find((id) => typeof id === "string" && id.length > 0) || - ""; - if (!quote || !sourceObservationId) return null; - if (!hasTodoActionTrigger(`${action.title} ${action.description} ${quote}`)) return null; - if (looksIncompleteTitle(quote) && !/(?:\borigin\b|远程仓库|remote repository)/i.test(`${action.description} ${quote}`)) return null; - const typeBucket = TYPE_BUCKETS.has(extraction?.typeBucket) ? extraction!.typeBucket as TypeBucket : action.status === "active" ? "in_progress" : "pending"; - return safeTodo({ - title: action.title, - description: quote, - confidence: 0.86, - timeBucket: TIME_BUCKETS.has(extraction?.timeBucket) ? extraction!.timeBucket as TimeBucket : timeBucketFor(session), - typeBucket, - sourceSessionId: session.id, - evidence: { - sourceObservationId, - quote, - }, - dedupeKey: normalizedKey(`${action.title}:${quote}`), - }, session); -} - -export async function refreshTodoAction( - kv: Pick<StateKV, "get" | "set" | "list">, - data: TodoRefreshActionOptions = {}, - deps: { runLangExtractSidecar?: LangExtractRunner } = {}, -): Promise<{ - success: boolean; - action?: Action; - review?: ReviewQueueItem; - keptOld: boolean; - reason: string; - error?: string; - engine?: "langextract" | "rules"; - scannedObservations: number; - fallbackReason?: string; -}> { - const actionId = normalizeText(data.actionId); - if (!actionId) { - return { success: false, keptOld: true, reason: "missing-action-id", error: "actionId is required", scannedObservations: 0 }; - } - const action = await kv.get<Action>(KV.actions, actionId).catch(() => null); - if (!action) { - return { success: false, keptOld: true, reason: "action-not-found", error: "action not found", scannedObservations: 0 }; - } - if (!actionLooksGenerated(action)) { - return { success: false, keptOld: true, reason: "not-generated", error: "action is not a generated todo card", scannedObservations: 0 }; - } - const extraction = action.metadata?.todoExtraction as Record<string, unknown> | undefined; - const evidence = extraction?.evidence as Record<string, unknown> | undefined; - const sourceSessionId = typeof extraction?.sourceSessionId === "string" ? extraction.sourceSessionId : ""; - if (!sourceSessionId) { - return { success: false, keptOld: true, reason: "missing-source-session", error: "source session missing", scannedObservations: 0 }; - } - const session = await kv.get<Session>(KV.sessions, sourceSessionId).catch(() => null); - if (!session) { - return { success: false, keptOld: true, reason: "source-session-not-found", error: "source session not found", scannedObservations: 0 }; - } - - const sourceObservationId = - (typeof evidence?.sourceObservationId === "string" && evidence.sourceObservationId) || - action.sourceObservationIds.find((id) => typeof id === "string" && id.length > 0) || - ""; - const allObservations = await kv.list<CompressedObservation>(KV.observations(session.id)).catch(() => []); - const context = nearbyObservationContext(allObservations, sourceObservationId, 12); - const mode = (getEnvVar("AGENTMEMORY_TODO_EXTRACTOR") || "auto").toLowerCase(); - const directThreshold = envNumber("AGENTMEMORY_TODO_DIRECT_CONFIDENCE", 0.82); - const { ruleObservations, llmObservations } = prefilterTodoObservations(session, context.observations); - const evidenceObservations = mode === "rules" ? ruleObservations : llmObservations.length ? llmObservations : context.observations; - const blockMap = new Map([...context.observations, ...evidenceObservations].map((obs) => [obs.id, blockFor(obs)])); - const scannedObservations = ruleObservations.length; - const [actions, reviews] = await Promise.all([ - kv.list<Action>(KV.actions).catch(() => []), - kv.list<ReviewQueueItem>(KV.reviewQueue).catch(() => []), - ]); - const otherActions = actions.filter((item) => item.id !== action.id); - const existing = existingDedupeKeys(otherActions, reviews); - const seenTitles = existingActiveTitles(otherActions, reviews); - - if (looksIncompleteTitle(action.title)) { - const fallbackTodo = todoFromExistingActionEvidence(action, session); - const todo = fallbackTodo ? todoForStorage(fallbackTodo) : null; - if (todo && validateTodoEvidence(todo, blockMap) && todo.timeBucket !== "history" && todo.typeBucket !== "done") { - const dedupeKey = todo.dedupeKey || normalizedKey(`${todo.title}:${todo.description}`); - const titleKey = normalizedKey(todo.title); - if (dedupeKey && !existing.has(dedupeKey) && !existing.has(titleKey) && !isNearDuplicateTitle(titleKey, seenTitles)) { - const now = new Date().toISOString(); - const fresh = makeAction({ ...todo, dedupeKey }, session, now); - const replacement = replaceActionFromTodo(action, fresh, "rules", now); - await kv.set(KV.actions, action.id, replacement); - return { - success: true, - action: replacement, - keptOld: false, - reason: "replaced-from-existing-evidence", - engine: "rules", - scannedObservations, - }; - } - } - } - - const { todos, engine, fallbackReason } = await extractForSession(session, context.observations, mode, { - runLangExtractSidecar: deps.runLangExtractSidecar, - refreshAction: refreshActionPromptContext(action), - forceLlmContext: true, - }); - if (mode !== "rules" && fallbackReason) { - return { - success: false, - keptOld: true, - reason: "llm-refresh-failed", - error: "LLM refresh failed", - engine, - scannedObservations, - fallbackReason, - }; - } - - const candidates: ExtractedTodo[] = []; - let usedExistingEvidenceFallback = false; - const rawTodos = [...todos]; - if (!rawTodos.length) { - const fallbackTodo = todoFromExistingActionEvidence(action, session); - if (fallbackTodo) { - rawTodos.push(fallbackTodo); - usedExistingEvidenceFallback = true; - } - } - for (const rawTodo of rawTodos) { - const todo = todoForStorage(rawTodo); - if (!todo || !validateTodoEvidence(todo, blockMap)) continue; - if (todo.timeBucket === "history" || todo.typeBucket === "done") continue; - const dedupeKey = todo.dedupeKey || normalizedKey(`${todo.title}:${todo.description}`); - const titleKey = normalizedKey(todo.title); - if (!dedupeKey || existing.has(dedupeKey) || existing.has(titleKey) || isNearDuplicateTitle(titleKey, seenTitles)) continue; - candidates.push({ ...todo, dedupeKey }); - } - candidates.sort((a, b) => effectiveTodoConfidence(b) - effectiveTodoConfidence(a)); - const todo = candidates[0]; - if (!todo) { - return { - success: true, - keptOld: true, - reason: firstInvalidTodoReason(rawTodos, blockMap), - engine, - scannedObservations, - ...(fallbackReason ? { fallbackReason } : {}), - }; - } - - const now = new Date().toISOString(); - const effectiveConfidence = effectiveTodoConfidence(todo); - if (effectiveConfidence >= directThreshold) { - const fresh = makeAction(todo, session, now); - const replacement = replaceActionFromTodo(action, fresh, engine, now); - await kv.set(KV.actions, action.id, replacement); - return { - success: true, - action: replacement, - keptOld: false, - reason: usedExistingEvidenceFallback ? "replaced-from-existing-evidence" : "replaced", - engine: usedExistingEvidenceFallback ? "rules" : engine, - scannedObservations, - ...(fallbackReason ? { fallbackReason } : {}), - }; - } - - return { - success: true, - keptOld: true, - reason: "low-confidence", - engine, - scannedObservations, - ...(fallbackReason ? { fallbackReason } : {}), - }; -} - -export async function generateTodosFromSessions( - kv: Pick<StateKV, "get" | "set" | "list" | "delete">, - data: TodoExtractOptions = {}, -): Promise<{ - success: true; - engine: "langextract" | "rules" | "mixed"; - scannedSessions: number; - scannedObservations: number; - directCreated: number; - reviewCreated: number; - hiddenHistory: number; - discarded: number; - cleanedActions: number; - cleanedReviews: number; - completedActions: number; - completedReviews: number; - cleanupPreview?: { actions: CleanupPreviewItem[]; reviews: CleanupPreviewItem[] }; - recheckMarked: number; - sourceScan?: { imported: number; skipped: number; errors: number }; - llmFallback?: boolean; - fallbackReason?: string; -}> { - let sourceScan: { imported: number; skipped: number; errors: number } | undefined; - if (data.scanSources !== false) { - const scan = await scanCodexSource(kv as StateKV).catch(() => null); - if (scan) sourceScan = { imported: scan.imported, skipped: scan.skipped, errors: scan.errors }; - } - const maxSessions = clampPositiveInt(data.maxSessions, DEFAULT_TODO_EXTRACT_MAX_SESSIONS, 100); - const maxObservationsPerSession = clampPositiveInt(data.maxObservationsPerSession, 300, 1000); - // STEP-11: body wins, else env, else config default. sinceDays caps at ~10y - // (effectively unbounded); maxInteractions at 500 (well above any real cap). - const sinceDays = clampPositiveInt( - data.sinceDays ?? getEnvVar("AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS"), - DEFAULT_TODO_EXTRACT_SINCE_DAYS, - 3650, - ); - const maxInteractionsPerSession = clampPositiveInt( - data.maxInteractionsPerSession ?? getEnvVar("AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION"), - DEFAULT_TODO_EXTRACT_MAX_INTERACTIONS, - 500, - ); - const sinceCutoffMs = Date.now() - sinceDays * 24 * 60 * 60 * 1000; - const mode = (getEnvVar("AGENTMEMORY_TODO_EXTRACTOR") || "auto").toLowerCase(); - const directThreshold = envNumber("AGENTMEMORY_TODO_DIRECT_CONFIDENCE", 0.82); - const cleanupMode = data.cleanup === "apply" ? "apply" : data.cleanup === "dry-run" ? "dry-run" : null; - const [actions, reviews, allSessions] = await Promise.all([ - kv.list<Action>(KV.actions).catch(() => []), - kv.list<ReviewQueueItem>(KV.reviewQueue).catch(() => []), - kv.list<Session>(KV.sessions).catch(() => []), - ]); - const cleanup = data.cleanup === "none" - ? { cleanedActions: 0, cleanedReviews: 0, completedActions: 0, completedReviews: 0, preview: { actions: [], reviews: [] } } - : cleanupMode - ? await cleanPollutedTodoCards(kv, cleanupMode) - : { cleanedActions: 0, cleanedReviews: 0, completedActions: 0, completedReviews: 0, preview: { actions: [], reviews: [] } }; - const remainingActions = cleanup.cleanedActions > 0 || cleanup.completedActions > 0 - ? await kv.list<Action>(KV.actions).catch(() => []) - : actions; - const remainingReviews = cleanup.cleanedReviews > 0 || cleanup.completedReviews > 0 - ? await kv.list<ReviewQueueItem>(KV.reviewQueue).catch(() => []) - : reviews; - const existing = existingDedupeKeys(remainingActions, remainingReviews); - const seenTitles = existingActiveTitles(remainingActions, remainingReviews); - const checkpointId = `todo-extract:${data.project || "all"}`; - const checkpoint = await kv.get<ScanCheckpoint>(KV.scanCheckpoints, checkpointId).catch(() => null); - const processed = parseCheckpoint(checkpoint?.cursor); - const sessions = allSessions - .filter((session) => !data.project || session.project === data.project || session.cwd === data.project) - // STEP-11: day-window is the primary scope control. Sessions with no/invalid - // timestamp are kept (never silently drop work); maxSessions is the cap that - // still bounds a day with a flood of sessions. - .filter((session) => { - const raw = sessionSortTime(session); - if (!raw) return true; - const at = new Date(raw).getTime(); - return !Number.isFinite(at) || at >= sinceCutoffMs; - }) - .sort((a, b) => sessionSortTime(b).localeCompare(sessionSortTime(a))) - .slice(0, maxSessions); - let scannedObservations = 0; - let directCreated = 0; - let reviewCreated = 0; - let hiddenHistory = 0; - let discarded = 0; - const engines = new Set<"langextract" | "rules">(); - const fallbackReasons = new Set<string>(); - const now = new Date().toISOString(); - const recheckMarked = await markChangedGeneratedActions(kv, new Map(allSessions.map((session) => [session.id, session])), now); - - for (const session of sessions) { - const key = checkpointKey(session); - if (!data.force && processed[session.id] === key) continue; - const sortedObservations = (await kv.list<CompressedObservation>(KV.observations(session.id)).catch(() => [])) - .sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || "")); - // STEP-11: keep only the most recent N interaction records, then apply the - // per-session observation safety cap to the most recent tail. - const observations = takeRecentInteractions(sortedObservations, maxInteractionsPerSession) - .slice(-maxObservationsPerSession); - const { ruleObservations, llmObservations } = prefilterTodoObservations(session, observations); - const taskChains = buildTaskChains(session, observations); - scannedObservations += ruleObservations.length; - const evidenceObservations = mode === "rules" ? ruleObservations : llmObservations.length ? llmObservations : ruleObservations; - const blockMap = new Map([...observations, ...evidenceObservations].map((obs) => [obs.id, blockFor(obs)])); - const { todos, engine, fallbackReason } = await extractForSession(session, observations, mode, { - runLangExtractSidecar: data.runLangExtractSidecar, - taskChains, - }); - engines.add(engine); - if (fallbackReason) fallbackReasons.add(fallbackReason.slice(0, 240)); - for (const rawTodo of todos) { - const todo = todoForStorage(rawTodo); - if (!todo || !validateTodoEvidence(todo, blockMap)) { - discarded++; - continue; - } - const dedupeKey = todo.dedupeKey || normalizedKey(`${todo.title}:${todo.description}`); - const titleKey = normalizedKey(todo.title); - if (!dedupeKey || existing.has(dedupeKey) || existing.has(titleKey)) { - discarded++; - continue; - } - if (isNearDuplicateTitle(titleKey, seenTitles)) { - discarded++; - continue; - } - if (todo.timeBucket === "history") { - // History todos are hidden (dismissed), not open work. Mark them in the - // exact-key set so they aren't re-emitted, but keep them out of the - // near-dup seed so a later *open* near-dup is still allowed (matches - // existingActiveTitles' open-only policy). - existing.add(dedupeKey); - existing.add(titleKey); - const review = makeReview({ ...todo, dedupeKey }, session, now); - review.status = "dismissed"; - review.payload = { ...(review.payload || {}), hiddenHistory: true }; - await kv.set(KV.reviewQueue, review.id, review); - hiddenHistory++; - continue; - } - const effectiveConfidence = effectiveTodoConfidence(todo); - if (effectiveConfidence >= directThreshold) { - await kv.set(KV.actions, fingerprintId("act", `todo:${dedupeKey}`), makeAction({ ...todo, dedupeKey }, session, now)); - directCreated++; - } else { - // Not persisted — seed nothing, so a discarded low-confidence todo can - // never suppress a later genuine one (mirrors action-candidates). - discarded++; - continue; - } - // Reached only for persisted open work: now safe to seed both guards. - existing.add(dedupeKey); - existing.add(titleKey); - seenTitles.push(titleKey); - } - if (engine === "langextract" && !fallbackReason) processed[session.id] = key; - } - - if (Object.keys(processed).length) { - await kv.set(KV.scanCheckpoints, checkpointId, { - sourceId: checkpointId, - cursor: JSON.stringify({ __engine: "langextract", ...processed }), - lastSuccessAt: now, - }); - } - - return { - success: true, - engine: engines.size > 1 ? "mixed" : Array.from(engines)[0] || "rules", - scannedSessions: sessions.length, - scannedObservations, - directCreated, - reviewCreated, - hiddenHistory, - discarded, - cleanedActions: cleanup.cleanedActions, - cleanedReviews: cleanup.cleanedReviews, - completedActions: cleanup.completedActions, - completedReviews: cleanup.completedReviews, - recheckMarked, - ...(data.cleanup === "dry-run" ? { cleanupPreview: cleanup.preview } : {}), - ...(sourceScan ? { sourceScan } : {}), - ...(fallbackReasons.size ? { llmFallback: true } : {}), - ...(fallbackReasons.size ? { fallbackReason: Array.from(fallbackReasons)[0] } : {}), - }; -} - -export function registerTodoExtractFunctions(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::todo-extract-generate", async (data: TodoExtractOptions = {}) => - generateTodosFromSessions(kv, data), - ); - sdk.registerFunction("mem::todo-refresh-action", async (data: TodoRefreshActionOptions = {}) => - refreshTodoAction(kv, data), - ); - sdk.registerFunction("mem::todo-update", async (data: TodoUpdateOptions = {}) => - updateChangedTodoCards(kv, data), - ); -} diff --git a/src/functions/todo-update-llm.py b/src/functions/todo-update-llm.py deleted file mode 100644 index 4da49cd3..00000000 --- a/src/functions/todo-update-llm.py +++ /dev/null @@ -1,223 +0,0 @@ -#!/usr/bin/env python3 -"""LLM update sidecar for AI Todo cards (PLAN-002 STEP-12). - -Reads {"cards": [...]} from stdin and writes {"decisions": [...]} to stdout. -Each card's source session has gained new activity since the card was recorded -(`sessionDelta` carries that recent activity). This re-judges the card in light -of it: KEEP / DROP / DONE / REWRITE / MERGE. Uses a direct OpenAI-compatible -chat completion (stdlib urllib, no extra deps), reusing the same LANGEXTRACT_* -config. On any failure (no key, network, bad JSON) it exits non-zero so the -TypeScript caller leaves every card untouched and reports why. -""" - -from __future__ import annotations - -import json -import os -import sys -import urllib.error -import urllib.request - -DEFAULT_MODEL = "deepseek/deepseek-v4-flash" -DEFAULT_BASE_URL = "https://api.novita.ai/openai/v1" -LEGACY_MODELS = {"pa/gpt-5.5"} -VALID_DECISIONS = {"KEEP", "DROP", "DONE", "REWRITE", "MERGE"} - -SYSTEM_PROMPT = """\ -You are a strict, conservative maintainer of a developer's personal Todo list. -Each card was auto-extracted earlier from an AI coding-agent session. It may be -selected because that session has SINCE gained new activity (`sessionDelta`), or -because a full-list maintenance pass is improving existing card quality. Your -job is to UPDATE each card using only the card, its evidence, its sessionDelta -when present, and other cards in this same batch. Keep the list trustworthy — -every surviving card must be a REAL, SPECIFIC, STILL-OPEN action with a clear -mature todo-card title. The title should read like something a developer can -execute from a task list, not like an AI agent progress log. - -For EACH input card, output exactly one decision: -- KEEP — still a genuine, still-open action and the new activity does not - change it; clear enough as-is. -- DROP — the new activity (or the card itself) shows it is not a real todo: - tool/command output, a status report, a git ref/hash fragment, - meta-commentary, or too vague to act on. -- DONE — the new session activity shows this task is now completed. -- REWRITE — still a real, still-open todo, but its title/description should be - updated because it is vague, process-narration, too broad, or stale - given the new activity. Provide a corrected concise `newTitle` - (<= 80 chars) and one-line `newDescription`. Stay faithful to the - card + evidence + sessionDelta; never invent scope or steps not - supported by them. -- MERGE — a duplicate of another card in this same batch; give `mergeIntoId` - of the canonical (clearest) card to merge into. - -Rules: -1. BE CONSERVATIVE. When unsure, choose KEEP. Only DROP or DONE when the card's - own text/evidence or the sessionDelta makes you confident. It is far worse to - wrongly drop or close a real todo than to keep a borderline one. -2. Ground every decision ONLY in the card's own text/evidence and its - sessionDelta. No outside knowledge, no assumed facts. -3. For REWRITE, only clarify or reflect the new state; do not add unsupported - scope, steps, or detail. -4. KEEP bar: a specific subject + a verb/action + a discernible outcome - (e.g. "Fix the N+1 query in the dashboard loader"). Reject pure observations, - logs, status lines, one-off status checks, and vibes. -5. Title quality bar: use concrete verb + semantic object (+ target/outcome when - it adds signal), but keep the title scannable. Precise technical identifiers - must be preserved in `newDescription` when needed, not allowed to dominate - `newTitle`. Move long branch names, commit hashes, file paths, URLs, session - ids, package names, and raw repos out of the title. Prefer - newTitle "推送当前工作分支到远程仓库" + newDescription - "分支:codex/todo-cleanup-flash-model。" over - "推送 codex/todo-cleanup-flash-model 分支到远程仓库". - Remove filler such as "全面了解", "了解现状", "梳理现状", - "获取信息", "进行", "处理". Prefer "克隆 AI-Todo 仓库" over - "克隆仓库并全面了解其状况". - A good title may contain tightly related steps when they serve one outcome, - e.g. "修正目录显示文字(去掉重复编号)并更新页码缓存后重渲染". -6. Agent process/status-check titles are not good todo titles. If still useful, - REWRITE them into the durable user outcome; otherwise DROP. Examples: - "重启 Codex desktop app 后再测一次" -> "验证重启后的 Codex 桌面端"; - "做最后一次状态确认" / "启动后做健康检查" / "确认工作区干净" -> DROP unless - evidence clearly shows a still-open deliverable. -7. If a card has `titleQualityHint`, actively consider REWRITE even if it is - still open. If that card is also the best MERGE target, output REWRITE for - the target card and MERGE the duplicates into that same id. - -Output STRICT JSON only, exactly one entry per input card, same ids: -{"decisions":[{"id":"<card id>","decision":"KEEP|DROP|DONE|REWRITE|MERGE", -"reason":"<short>","newTitle":"<only if REWRITE>","newDescription":"<only if -REWRITE>","mergeIntoId":"<only if MERGE>"}]} - -Examples of decisions: -- sessionDelta shows the change was committed/merged/tests passed -> DONE -- "⏺ Bash(npm test)" / "Viewer: http://localhost:3114" / "abc1234 (HEAD)" -> DROP -- "克隆仓库并全面了解其状况" with AI-Todo evidence -> REWRITE to "克隆 AI-Todo 仓库" -- "推送 codex/todo-cleanup-flash-model 分支到远程仓库" -> REWRITE to "推送当前工作分支到远程仓库" and keep the branch in description -- "重启 Codex desktop app 后再测一次" with live issue evidence -> REWRITE to "验证重启后的 Codex 桌面端" -- "做最后一次状态确认" / "启动后做健康检查" as agent procedure -> DROP -- a real task whose wording is vague or stale given the new activity -> REWRITE -- two cards describing the same fix -> MERGE duplicates + KEEP/REWRITE canonical -- a still-open task the new activity does not touch -> KEEP -""" - - -def model_id() -> str: - m = (os.environ.get("LANGEXTRACT_MODEL") or DEFAULT_MODEL).strip() - return DEFAULT_MODEL if m in LEGACY_MODELS else m - - -def call_llm(cards: list) -> list: - """Direct OpenAI-compatible chat completion. Raises SystemExit on failure.""" - api_key = os.environ.get("LANGEXTRACT_API_KEY", "").strip() - if not api_key: - raise SystemExit("LANGEXTRACT_API_KEY is required for LLM update") - base_url = (os.environ.get("LANGEXTRACT_BASE_URL", DEFAULT_BASE_URL).strip() or DEFAULT_BASE_URL).rstrip("/") - body = { - "model": model_id(), - "messages": [ - {"role": "system", "content": SYSTEM_PROMPT}, - { - "role": "user", - "content": "Update these existing cards. Use sessionDelta when " - 'present, and otherwise judge card quality and same-batch ' - 'duplicates from the card text/evidence. Return strict JSON ' - '{"decisions":[...]} with one entry ' - "per card.\n" - + json.dumps({"cards": cards}, ensure_ascii=False), - }, - ], - "temperature": 0, - "response_format": {"type": "json_object"}, - } - req = urllib.request.Request( - base_url + "/chat/completions", - data=json.dumps(body).encode("utf-8"), - headers={ - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - # Cloudflare in front of Novita 403s the default Python-urllib UA - # ("error code: 1010"); a stable explicit UA is required. - "User-Agent": "AI-Todo", - }, - method="POST", - ) - timeout = float(os.environ.get("AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS", "120000")) / 1000.0 - try: - with urllib.request.urlopen(req, timeout=timeout) as resp: - data = json.loads(resp.read().decode("utf-8")) - except (urllib.error.URLError, TimeoutError, OSError) as exc: # pragma: no cover - raise SystemExit(f"LLM request failed: {exc}") from exc - content = data["choices"][0]["message"]["content"] - parsed = json.loads(content) - return parsed.get("decisions") or [] - - -def normalize(cards: list, decisions: list) -> list: - """Validate raw decisions against the input ids + decision enum.""" - valid_ids = {c.get("id") for c in cards if isinstance(c, dict)} - out = [] - seen = set() - for d in decisions: - if not isinstance(d, dict): - continue - cid = d.get("id") - dec = str(d.get("decision") or "").upper() - if cid not in valid_ids or cid in seen or dec not in VALID_DECISIONS: - continue - seen.add(cid) - entry = {"id": cid, "decision": dec, "reason": str(d.get("reason") or "")[:200]} - if dec == "REWRITE": - nt = str(d.get("newTitle") or "").strip()[:120] - nd = str(d.get("newDescription") or "").strip()[:400] - if not nt: # rewrite without a usable title is meaningless → keep instead - entry["decision"] = "KEEP" - else: - entry["newTitle"] = nt - entry["newDescription"] = nd - if dec == "MERGE": - mid = d.get("mergeIntoId") - if mid not in valid_ids or mid == cid: # bad target → keep instead - entry["decision"] = "KEEP" - else: - entry["mergeIntoId"] = mid - out.append(entry) - return out - - -def main() -> int: - payload = json.load(sys.stdin) - cards = payload.get("cards") or [] - if not isinstance(cards, list) or not cards: - print(json.dumps({"decisions": []}, ensure_ascii=False)) - return 0 - decisions = normalize(cards, call_llm(cards)) - print(json.dumps({"decisions": decisions}, ensure_ascii=False)) - return 0 - - -if __name__ == "__main__": - if os.environ.get("AGENTMEMORY_TODO_UPDATE_SELF_TEST") == "1": - # Offline smoke: exercise normalize() without any network call. - _cards = [{"id": "a", "title": "x"}, {"id": "b", "title": "y"}, {"id": "c", "title": "z"}] - _raw = [ - {"id": "a", "decision": "drop", "reason": "tool log"}, - {"id": "b", "decision": "REWRITE", "newTitle": "Fix it", "newDescription": "do the fix"}, - {"id": "c", "decision": "MERGE", "mergeIntoId": "b"}, - {"id": "zzz", "decision": "DROP"}, # unknown id → dropped - {"id": "a", "decision": "KEEP"}, # duplicate id → ignored - ] - _out = normalize(_cards, _raw) - assert [d["decision"] for d in _out] == ["DROP", "REWRITE", "MERGE"], _out - assert _out[1]["newTitle"] == "Fix it" - assert _out[2]["mergeIntoId"] == "b" - assert "KEEP" in SYSTEM_PROMPT and "MERGE" in SYSTEM_PROMPT - assert "Title quality bar" in SYSTEM_PROMPT and "克隆 AI-Todo 仓库" in SYSTEM_PROMPT - assert "推送当前工作分支到远程仓库" in SYSTEM_PROMPT - assert "codex/todo-cleanup-flash-model" in SYSTEM_PROMPT - assert "修正目录显示文字" in SYSTEM_PROMPT - assert "mature todo-card title" in SYSTEM_PROMPT - assert "重启 Codex desktop app 后再测一次" in SYSTEM_PROMPT - assert "做最后一次状态确认" in SYSTEM_PROMPT - print("ok") - raise SystemExit(0) - raise SystemExit(main()) diff --git a/src/functions/verify.ts b/src/functions/verify.ts deleted file mode 100644 index a6e393f6..00000000 --- a/src/functions/verify.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV } from "../state/schema.js"; -import type { - Memory, - CompressedObservation, - Session, -} from "../types.js"; - -export function registerVerifyFunction(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction("mem::verify", - async (data: { id: string }) => { - if (!data.id || typeof data.id !== "string") { - return { success: false, error: "id is required" }; - } - - const memory = await kv.get<Memory>(KV.memories, data.id); - if (memory) { - const observationIds = memory.sourceObservationIds || []; - const observations: Array<{ - observation: CompressedObservation; - session?: Session; - }> = []; - - for (const obsId of observationIds) { - const obs = await findObservation(kv, obsId, memory.sessionIds); - if (obs) { - const session = await kv.get<Session>(KV.sessions, obs.sessionId); - observations.push({ observation: obs, session: session || undefined }); - } - } - - return { - success: true, - type: "memory", - memory: { - id: memory.id, - title: memory.title, - type: memory.type, - version: memory.version, - strength: memory.strength, - isLatest: memory.isLatest, - createdAt: memory.createdAt, - updatedAt: memory.updatedAt, - supersedes: memory.supersedes, - parentId: memory.parentId, - }, - citations: observations.map((o) => ({ - observationId: o.observation.id, - title: o.observation.title, - type: o.observation.type, - confidence: o.observation.confidence, - timestamp: o.observation.timestamp, - sessionId: o.observation.sessionId, - sessionProject: o.session?.project, - sessionStatus: o.session?.status, - })), - citationCount: observations.length, - }; - } - - const obs = await findObservation(kv, data.id); - if (obs) { - const session = await kv.get<Session>(KV.sessions, obs.sessionId); - return { - success: true, - type: "observation", - observation: { - id: obs.id, - title: obs.title, - type: obs.type, - confidence: obs.confidence, - importance: obs.importance, - timestamp: obs.timestamp, - sessionId: obs.sessionId, - }, - session: session - ? { - id: session.id, - project: session.project, - status: session.status, - startedAt: session.startedAt, - } - : null, - citationCount: 0, - citations: [], - }; - } - - return { success: false, error: "not found" }; - }, - ); -} - -async function findObservation( - kv: StateKV, - obsId: string, - hintSessionIds?: string[], -): Promise<CompressedObservation | null> { - if (hintSessionIds) { - for (const sid of hintSessionIds) { - const obs = await kv.get<CompressedObservation>(KV.observations(sid), obsId); - if (obs) return obs; - } - } - const sessions = await kv.list<Session>(KV.sessions); - for (const session of sessions) { - if (hintSessionIds?.includes(session.id)) continue; - const obs = await kv.get<CompressedObservation>( - KV.observations(session.id), - obsId, - ); - if (obs) return obs; - } - return null; -} diff --git a/src/functions/vision-search.ts b/src/functions/vision-search.ts deleted file mode 100644 index 844ec9d5..00000000 --- a/src/functions/vision-search.ts +++ /dev/null @@ -1,147 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { EmbeddingProvider } from "../types.js"; -import { KV } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { isManagedImagePath } from "../utils/image-store.js"; -import { recordAudit } from "./audit.js"; -import { logger } from "../logger.js"; - -interface StoredEmbedding { - imageRef: string; - vector: number[]; - modelName: string; - dimensions: number; - updatedAt: string; - sessionId?: string; - observationId?: string; -} - -export function registerVisionSearchFunctions( - sdk: ISdk, - kv: StateKV, - imageProvider: EmbeddingProvider | null, -): void { - sdk.registerFunction( - "mem::vision-embed", - async (data: { - imageRef: string; - sessionId?: string; - observationId?: string; - }) => { - if (!imageProvider?.embedImage) { - return { success: false, error: "image embeddings disabled (set AGENTMEMORY_IMAGE_EMBEDDINGS=true)" }; - } - if (!data?.imageRef || typeof data.imageRef !== "string") { - return { success: false, error: "imageRef required" }; - } - if (!isManagedImagePath(data.imageRef)) { - return { success: false, error: "imageRef must point to a file under the managed image store" }; - } - const refCount = await kv.get<number>(KV.imageRefs, data.imageRef); - if (!refCount || Number(refCount) < 1) { - return { success: false, error: "imageRef not registered in mem:image-refs" }; - } - try { - const vec = await imageProvider.embedImage(data.imageRef); - const stored: StoredEmbedding = { - imageRef: data.imageRef, - vector: Array.from(vec), - modelName: imageProvider.name, - dimensions: imageProvider.dimensions, - updatedAt: new Date().toISOString(), - sessionId: data.sessionId, - observationId: data.observationId, - }; - await kv.set(KV.imageEmbeddings, data.imageRef, stored); - await recordAudit(kv, "vision_embed", "mem::vision-embed", [data.imageRef], { - modelName: imageProvider.name, - dimensions: stored.dimensions, - sessionId: data.sessionId, - observationId: data.observationId, - }); - return { success: true, imageRef: data.imageRef, dimensions: stored.dimensions }; - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - logger.warn("vision-embed failed", { imageRef: data.imageRef, error: msg }); - return { success: false, error: msg }; - } - }, - ); - - sdk.registerFunction( - "mem::vision-search", - async (data: { - queryText?: string; - queryImageRef?: string; - queryImageBase64?: string; - topK?: number; - sessionId?: string; - }) => { - if (!imageProvider?.embedImage) { - return { success: false, error: "image embeddings disabled (set AGENTMEMORY_IMAGE_EMBEDDINGS=true)" }; - } - const requestedTopK = - typeof data?.topK === "number" && Number.isFinite(data.topK) - ? Math.trunc(data.topK) - : 10; - const topK = Math.min(50, Math.max(1, requestedTopK)); - - let queryVec: Float32Array | null = null; - try { - if (data?.queryText) { - queryVec = await imageProvider.embed(data.queryText); - } else if (data?.queryImageBase64) { - const b64 = data.queryImageBase64.startsWith("data:") - ? data.queryImageBase64 - : `data:image/png;base64,${data.queryImageBase64}`; - queryVec = await imageProvider.embedImage(b64); - } else if (data?.queryImageRef) { - if (!isManagedImagePath(data.queryImageRef)) { - return { success: false, error: "queryImageRef must point to a file under the managed image store" }; - } - const refCount = await kv.get<number>(KV.imageRefs, data.queryImageRef); - if (!refCount || Number(refCount) < 1) { - return { success: false, error: "queryImageRef not registered in mem:image-refs" }; - } - queryVec = await imageProvider.embedImage(data.queryImageRef); - } else { - return { success: false, error: "queryText, queryImageRef, or queryImageBase64 required" }; - } - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - return { success: false, error: `query embed failed: ${msg}` }; - } - - if (!queryVec) return { success: false, error: "failed to build query vector" }; - - const stored = await kv.list<StoredEmbedding>(KV.imageEmbeddings); - const filtered = data?.sessionId - ? stored.filter((s) => s.sessionId === data.sessionId) - : stored; - - const scored = filtered.map((s) => ({ - imageRef: s.imageRef, - score: cosine(queryVec!, s.vector), - sessionId: s.sessionId, - observationId: s.observationId, - updatedAt: s.updatedAt, - })); - scored.sort((a, b) => b.score - a.score); - return { success: true, results: scored.slice(0, topK), total: scored.length }; - }, - ); -} - -function cosine(a: Float32Array, b: number[]): number { - if (a.length !== b.length) return 0; - let dot = 0; - let normA = 0; - let normB = 0; - for (let i = 0; i < a.length; i++) { - dot += a[i] * b[i]; - normA += a[i] * a[i]; - normB += b[i] * b[i]; - } - const denom = Math.sqrt(normA) * Math.sqrt(normB); - return denom === 0 ? 0 : dot / denom; -} diff --git a/src/functions/working-memory.ts b/src/functions/working-memory.ts deleted file mode 100644 index 127195e5..00000000 --- a/src/functions/working-memory.ts +++ /dev/null @@ -1,254 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { Memory, CompressedObservation, ContextBlock } from "../types.js"; -import { KV, generateId } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { recordAudit } from "./audit.js"; -import { recordAccessBatch } from "./access-tracker.js"; -import { logger } from "../logger.js"; - -const CORE_SCOPE = "mem:core-memory"; - -interface CoreMemoryEntry { - id: string; - content: string; - importance: number; - pinned: boolean; - accessCount: number; - lastAccessedAt: string; - createdAt: string; -} - -function estimateTokens(text: string): number { - return Math.ceil(text.length / 3); -} - -function scoreEntry(entry: CoreMemoryEntry, now: number): number { - const recencyMs = now - new Date(entry.lastAccessedAt).getTime(); - const recencyDays = recencyMs / (1000 * 60 * 60 * 24); - const recencyScore = 1 / (1 + recencyDays * 0.1); - const accessScore = Math.log2(entry.accessCount + 1) / 10; - const importanceScore = entry.importance / 10; - return importanceScore * 0.5 + recencyScore * 0.3 + accessScore * 0.2; -} - -export function registerWorkingMemoryFunctions( - sdk: ISdk, - kv: StateKV, - tokenBudget: number, -): void { - sdk.registerFunction("mem::core-add", - async (data: { - content: string; - importance?: number; - pinned?: boolean; - }) => { - if (!data?.content?.trim()) { - return { success: false, error: "content is required" }; - } - const now = new Date().toISOString(); - const entry: CoreMemoryEntry = { - id: generateId("core"), - content: data.content.trim(), - importance: Math.min(10, Math.max(1, data.importance ?? 7)), - pinned: data.pinned ?? false, - accessCount: 0, - lastAccessedAt: now, - createdAt: now, - }; - await kv.set(CORE_SCOPE, entry.id, entry); - - try { - await recordAudit(kv, "core_add", "mem::core-add", [entry.id], { - content: entry.content.slice(0, 100), - importance: entry.importance, - pinned: entry.pinned, - }); - } catch {} - - return { success: true, id: entry.id }; - }, - ); - - sdk.registerFunction("mem::core-remove", - async (data: { id: string }) => { - if (!data?.id) return { success: false, error: "id is required" }; - await kv.delete(CORE_SCOPE, data.id); - - try { - await recordAudit(kv, "core_remove", "mem::core-remove", [data.id], {}); - } catch {} - - return { success: true }; - }, - ); - - sdk.registerFunction("mem::core-list", - async () => { - const entries = await kv.list<CoreMemoryEntry>(CORE_SCOPE); - entries.sort((a, b) => b.importance - a.importance); - return { - success: true, - entries, - totalTokens: entries.reduce( - (sum, e) => sum + estimateTokens(e.content), - 0, - ), - }; - }, - ); - - sdk.registerFunction("mem::working-context", - async (data: { budget?: number }) => { - const budget = data.budget || tokenBudget; - const now = Date.now(); - let usedTokens = 0; - - const coreEntries = await kv.list<CoreMemoryEntry>(CORE_SCOPE); - - const pinned = coreEntries.filter((e) => e.pinned); - const unpinned = coreEntries - .filter((e) => !e.pinned) - .sort((a, b) => scoreEntry(b, now) - scoreEntry(a, now)); - - const coreLines: string[] = []; - const coreBudget = Math.floor(budget * 0.3); - const accessUpdates: Array<{ id: string; entry: CoreMemoryEntry }> = []; - const accessTimestamp = new Date().toISOString(); - - for (const entry of [...pinned, ...unpinned]) { - const tokens = estimateTokens(entry.content); - if (usedTokens + tokens > coreBudget && !entry.pinned) continue; - coreLines.push(`- ${entry.content}`); - usedTokens += tokens; - - entry.accessCount++; - entry.lastAccessedAt = accessTimestamp; - accessUpdates.push({ id: entry.id, entry }); - } - - Promise.allSettled( - accessUpdates.map(({ id, entry }) => kv.set(CORE_SCOPE, id, entry)), - ).catch(() => {}); - - const archivalLines: string[] = []; - - const memories = await kv.list<Memory>(KV.memories); - const active = memories - .filter((m) => m.isLatest !== false) - .sort((a, b) => { - const strengthDiff = b.strength - a.strength; - if (Math.abs(strengthDiff) > 0.2) return strengthDiff; - return ( - new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() - ); - }); - - const archivalIds: string[] = []; - for (const mem of active) { - const tokens = estimateTokens(mem.content); - if (usedTokens + tokens > budget) continue; - archivalLines.push(`- [${mem.type}] ${mem.title}: ${mem.content}`); - archivalIds.push(mem.id); - usedTokens += tokens; - } - - void recordAccessBatch(kv, archivalIds); - - const pagedOut = active.length - archivalLines.length; - - const sections: string[] = []; - if (coreLines.length > 0) { - sections.push(`## Core Memory\n${coreLines.join("\n")}`); - } - if (archivalLines.length > 0) { - sections.push(`## Archival Memory\n${archivalLines.join("\n")}`); - } - if (pagedOut > 0) { - sections.push( - `_${pagedOut} memories paged to archival (use mem::search to retrieve)_`, - ); - } - - const context = sections.join("\n\n"); - - logger.info("Working context built", { - coreEntries: coreLines.length, - archivalEntries: archivalLines.length, - pagedOut, - tokens: usedTokens, - budget, - }); - - return { - success: true, - context, - coreEntries: coreLines.length, - archivalEntries: archivalLines.length, - pagedOut, - tokens: usedTokens, - budget, - }; - }, - ); - - sdk.registerFunction("mem::auto-page", - async (data: { budget?: number }) => { - const budget = data?.budget || tokenBudget; - const coreBudget = Math.floor(budget * 0.3); - - const entries = await kv.list<CoreMemoryEntry>(CORE_SCOPE); - let totalTokens = entries.reduce( - (sum, e) => sum + estimateTokens(e.content), - 0, - ); - - if (totalTokens <= coreBudget) { - return { success: true, paged: 0, totalTokens, budget: coreBudget }; - } - - const now = Date.now(); - const unpinned = entries - .filter((e) => !e.pinned) - .sort((a, b) => scoreEntry(a, now) - scoreEntry(b, now)); - - let paged = 0; - const pagedIds: string[] = []; - for (const entry of unpinned) { - if (totalTokens <= coreBudget) break; - const tokens = estimateTokens(entry.content); - - const archivalMemory: Memory = { - id: generateId("mem"), - createdAt: entry.createdAt, - updatedAt: new Date().toISOString(), - type: "fact", - title: entry.content.slice(0, 80), - content: entry.content, - concepts: [], - files: [], - sessionIds: [], - strength: entry.importance / 10, - version: 1, - isLatest: true, - }; - await kv.set(KV.memories, archivalMemory.id, archivalMemory); - await kv.delete(CORE_SCOPE, entry.id); - - totalTokens -= tokens; - paged++; - pagedIds.push(entry.id); - } - - if (paged > 0) { - try { - await recordAudit(kv, "auto_page", "mem::auto-page", pagedIds, { - paged, - budget: coreBudget, - }); - } catch {} - } - - return { success: true, paged, totalTokens, budget: coreBudget }; - }, - ); -} diff --git a/src/health/monitor.ts b/src/health/monitor.ts deleted file mode 100644 index 953c94a0..00000000 --- a/src/health/monitor.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type { ISdk } from "iii-sdk"; -import type { HealthSnapshot } from "../types.js"; -import type { StateKV } from "../state/kv.js"; -import { KV } from "../state/schema.js"; -import { evaluateHealth } from "./thresholds.js"; - -export function registerHealthMonitor( - sdk: ISdk, - kv: StateKV, -): { stop: () => void } { - let connectionState = "connected"; - let prevCpuUsage = process.cpuUsage(); - let prevCpuTime = Date.now(); - - if (typeof sdk.on === "function") { - sdk.on("connection_state", (state?: unknown) => { - connectionState = state as string; - }); - } - - async function collectHealth(): Promise<HealthSnapshot> { - const mem = process.memoryUsage(); - const currentCpu = process.cpuUsage(); - const now = Date.now(); - const uptime = process.uptime(); - - const elapsedMs = now - prevCpuTime; - const userDelta = currentCpu.user - prevCpuUsage.user; - const systemDelta = currentCpu.system - prevCpuUsage.system; - const cpuPercent = - elapsedMs > 0 ? ((userDelta + systemDelta) / 1000 / elapsedMs) * 100 : 0; - prevCpuUsage = currentCpu; - prevCpuTime = now; - - const startMark = performance.now(); - await new Promise((resolve) => setImmediate(resolve)); - const eventLoopLagMs = performance.now() - startMark; - - let workers: HealthSnapshot["workers"] = []; - try { - const result = await sdk.trigger< - unknown, - { workers?: HealthSnapshot["workers"] } - >({ function_id: "engine::workers::list", payload: {} }); - if (result?.workers) workers = result.workers; - } catch {} - - const KV_PROBE_TIMEOUT = 5000; - let kvConnectivity: { status: string; latencyMs?: number; error?: string }; - const kvStart = performance.now(); - try { - await Promise.race([ - (async () => { - await kv.set(KV.health, "_probe", { ts: Date.now() }); - await kv.get(KV.health, "_probe"); - })(), - new Promise<never>((_, reject) => - setTimeout(() => reject(new Error("timeout")), KV_PROBE_TIMEOUT), - ), - ]); - kvConnectivity = { status: "ok", latencyMs: Math.round((performance.now() - kvStart) * 100) / 100 }; - } catch { - kvConnectivity = { status: "error", error: "kv_probe_failed", latencyMs: Math.round((performance.now() - kvStart) * 100) / 100 }; - } - - const snapshot: HealthSnapshot = { - connectionState, - workers, - memory: { - heapUsed: mem.heapUsed, - heapTotal: mem.heapTotal, - rss: mem.rss, - external: mem.external, - }, - cpu: { - userMicros: currentCpu.user, - systemMicros: currentCpu.system, - percent: Math.round(cpuPercent * 100) / 100, - }, - eventLoopLagMs, - uptimeSeconds: uptime, - kvConnectivity, - status: "healthy", - alerts: [], - }; - - const evaluated = evaluateHealth(snapshot); - snapshot.status = evaluated.status; - snapshot.alerts = evaluated.alerts; - snapshot.notes = evaluated.notes; - - await kv.set(KV.health, "latest", snapshot).catch(() => {}); - return snapshot; - } - - collectHealth().catch(() => {}); - const interval = setInterval(() => { - collectHealth().catch(() => {}); - }, 30_000); - interval.unref(); - - return { - stop: () => clearInterval(interval), - }; -} - -export async function getLatestHealth( - kv: StateKV, -): Promise<HealthSnapshot | null> { - return kv.get<HealthSnapshot>(KV.health, "latest"); -} diff --git a/src/health/thresholds.ts b/src/health/thresholds.ts deleted file mode 100644 index 7279afad..00000000 --- a/src/health/thresholds.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { HealthSnapshot } from "../types.js"; - -interface ThresholdConfig { - eventLoopLagWarnMs: number; - eventLoopLagCriticalMs: number; - cpuWarnPercent: number; - cpuCriticalPercent: number; - memoryWarnPercent: number; - memoryCriticalPercent: number; - memoryRssFloorBytes: number; -} - -const DEFAULTS: ThresholdConfig = { - eventLoopLagWarnMs: 100, - eventLoopLagCriticalMs: 500, - cpuWarnPercent: 80, - cpuCriticalPercent: 90, - memoryWarnPercent: 80, - memoryCriticalPercent: 95, - memoryRssFloorBytes: 512 * 1024 * 1024, -}; - -export function evaluateHealth( - snapshot: HealthSnapshot, - config: Partial<ThresholdConfig> = {}, -): { status: "healthy" | "degraded" | "critical"; alerts: string[]; notes: string[] } { - const cfg = { ...DEFAULTS, ...config }; - const alerts: string[] = []; - const notes: string[] = []; - let critical = false; - let degraded = false; - - if ( - snapshot.connectionState === "disconnected" || - snapshot.connectionState === "failed" - ) { - alerts.push(`connection_${snapshot.connectionState}`); - critical = true; - } else if (snapshot.connectionState === "reconnecting") { - alerts.push("connection_reconnecting"); - degraded = true; - } - - if (snapshot.eventLoopLagMs > cfg.eventLoopLagCriticalMs) { - alerts.push( - `event_loop_lag_critical_${Math.round(snapshot.eventLoopLagMs)}ms`, - ); - critical = true; - } else if (snapshot.eventLoopLagMs > cfg.eventLoopLagWarnMs) { - alerts.push(`event_loop_lag_warn_${Math.round(snapshot.eventLoopLagMs)}ms`); - degraded = true; - } - - if (snapshot.cpu.percent > cfg.cpuCriticalPercent) { - alerts.push(`cpu_critical_${Math.round(snapshot.cpu.percent)}%`); - critical = true; - } else if (snapshot.cpu.percent > cfg.cpuWarnPercent) { - alerts.push(`cpu_warn_${Math.round(snapshot.cpu.percent)}%`); - degraded = true; - } - - const memPercent = - snapshot.memory.heapTotal > 0 - ? (snapshot.memory.heapUsed / snapshot.memory.heapTotal) * 100 - : 0; - const rss = snapshot.memory.rss ?? 0; - const rssAboveFloor = rss >= cfg.memoryRssFloorBytes; - const memMb = Math.round(rss / (1024 * 1024)); - if (memPercent > cfg.memoryCriticalPercent && rssAboveFloor) { - alerts.push(`memory_critical_${Math.round(memPercent)}%_rss${memMb}mb`); - critical = true; - } else if (memPercent > cfg.memoryWarnPercent && rssAboveFloor) { - alerts.push(`memory_warn_${Math.round(memPercent)}%_rss${memMb}mb`); - degraded = true; - } else if (memPercent > cfg.memoryWarnPercent) { - notes.push(`memory_heap_tight_${Math.round(memPercent)}%_rss${memMb}mb`); - } - - const status = critical ? "critical" : degraded ? "degraded" : "healthy"; - return { status, alerts, notes }; -} diff --git a/src/hooks/_project.ts b/src/hooks/_project.ts deleted file mode 100644 index 35364ea3..00000000 --- a/src/hooks/_project.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { execSync } from "node:child_process"; -import { basename } from "node:path"; - -// Resolution order: AGENTMEMORY_PROJECT_NAME env → git toplevel basename → cwd basename. -export function resolveProject(cwd?: string): string { - const explicit = process.env["AGENTMEMORY_PROJECT_NAME"]; - if (explicit && explicit.trim()) return explicit.trim(); - const dir = cwd && cwd.trim() ? cwd : process.cwd(); - try { - const top = execSync("git rev-parse --show-toplevel", { - cwd: dir, - stdio: ["ignore", "pipe", "ignore"], - timeout: 500, - }) - .toString() - .trim(); - if (top) return basename(top); - } catch {} - return basename(dir); -} diff --git a/src/hooks/notification.ts b/src/hooks/notification.ts deleted file mode 100644 index 9ad77042..00000000 --- a/src/hooks/notification.ts +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env node -import { resolveProject } from "./_project.js"; - -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) return; - const notificationType = data.notification_type ?? data.notificationType; - if (notificationType !== "permission_prompt") return; - - const rawSessionId = data.session_id ?? data.sessionId; - const sessionId = - typeof rawSessionId === "string" && rawSessionId.length > 0 - ? rawSessionId - : "unknown"; - - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "notification", - sessionId, - project: resolveProject(data.cwd as string | undefined), - cwd: (data.cwd as string | undefined) || process.cwd(), - timestamp: new Date().toISOString(), - data: { - notification_type: notificationType, - title: data.title, - message: data.message, - }, - }), - signal: AbortSignal.timeout(2000), - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} - -main(); diff --git a/src/hooks/post-commit.ts b/src/hooks/post-commit.ts deleted file mode 100644 index 30c54a9a..00000000 --- a/src/hooks/post-commit.ts +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env node - -import { execFile } from "node:child_process"; -import { promisify } from "node:util"; - -const exec = promisify(execFile); - -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; -const TIMEOUT_MS = 1500; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function git(args: string[], cwd: string): Promise<string | null> { - try { - const { stdout } = await exec("git", args, { cwd, timeout: 1500 }); - return stdout.trim(); - } catch { - return null; - } -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown> = {}; - if (input.trim()) { - try { - data = JSON.parse(input); - } catch { - // Direct invocation from .git/hooks/post-commit may pass no stdin. - } - } - - if (isSdkChildContext(data)) return; - - const cwd = - (data.cwd as string) || - process.env["AGENTMEMORY_CWD"] || - process.cwd(); - const sessionId = - (data.session_id as string) || - process.env["AGENTMEMORY_SESSION_ID"] || - undefined; - - const sha = - process.env["AGENTMEMORY_COMMIT_SHA"] || - (await git(["rev-parse", "HEAD"], cwd)); - if (!sha) return; - - const branch = await git(["rev-parse", "--abbrev-ref", "HEAD"], cwd); - const repo = await git(["config", "--get", "remote.origin.url"], cwd); - const message = await git(["log", "-1", "--pretty=%B", sha], cwd); - const author = await git(["log", "-1", "--pretty=%an <%ae>", sha], cwd); - const authoredAt = await git(["log", "-1", "--pretty=%aI", sha], cwd); - const filesRaw = await git( - ["diff-tree", "--no-commit-id", "--name-only", "-r", sha], - cwd, - ); - const files = filesRaw ? filesRaw.split("\n").filter(Boolean) : undefined; - - const body = { - sessionId, - sha, - branch: branch || undefined, - repo: repo || undefined, - message: message || undefined, - author: author || undefined, - authoredAt: authoredAt || undefined, - files, - }; - - try { - await fetch(`${REST_URL}/agentmemory/session/commit`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify(body), - signal: AbortSignal.timeout(TIMEOUT_MS), - }); - } catch { - // best-effort - } -} - -main(); diff --git a/src/hooks/post-tool-failure.ts b/src/hooks/post-tool-failure.ts deleted file mode 100644 index f0295b3e..00000000 --- a/src/hooks/post-tool-failure.ts +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env node -import { resolveProject } from "./_project.js"; - -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) return; - if (data.is_interrupt || data.isInterrupt) return; - - const sessionId = ((data.session_id || data.sessionId) as string) || "unknown"; - const toolName = data.tool_name ?? data.toolName; - const toolInput = data.tool_input ?? data.toolArgs; - const error = data.error ?? data.errorMessage; - - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "post_tool_failure", - sessionId, - project: resolveProject(data.cwd as string | undefined), - cwd: (data.cwd as string | undefined) || process.cwd(), - timestamp: new Date().toISOString(), - data: { - tool_name: toolName, - tool_input: - typeof toolInput === "string" - ? toolInput.slice(0, 4000) - : JSON.stringify(toolInput ?? "").slice(0, 4000), - error: - typeof error === "string" - ? error.slice(0, 4000) - : JSON.stringify(error ?? "").slice(0, 4000), - }, - }), - signal: AbortSignal.timeout(3000), - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} - -main(); diff --git a/src/hooks/post-tool-use.ts b/src/hooks/post-tool-use.ts deleted file mode 100644 index c68a7731..00000000 --- a/src/hooks/post-tool-use.ts +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env node -import { resolveProject } from "./_project.js"; - -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) return; - - const sessionId = ((data.session_id || data.sessionId) as string) || "unknown"; - const toolName = data.tool_name ?? data.toolName; - const toolInput = data.tool_input ?? data.toolArgs; - - const { imageData, cleanOutput } = extractImageData(toolOutput(data)); - - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "post_tool_use", - sessionId, - project: resolveProject(data.cwd as string | undefined), - cwd: (data.cwd as string | undefined) || process.cwd(), - timestamp: new Date().toISOString(), - data: { - tool_name: toolName, - tool_input: toolInput, - tool_output: truncate(cleanOutput, 8000), - ...(imageData ? { image_data: imageData } : {}), - }, - }), - signal: AbortSignal.timeout(3000), - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} - -function toolOutput(data: Record<string, unknown>): unknown { - if (data.tool_response !== undefined) return data.tool_response; - if (data.tool_output !== undefined) return data.tool_output; - const result = data.tool_result ?? data.toolResult; - if (typeof result === "object" && result !== null) { - const obj = result as Record<string, unknown>; - return obj.text_result_for_llm ?? obj.textResultForLlm ?? result; - } - return result; -} - -function isBase64Image(val: unknown): val is string { - return typeof val === "string" && ( - val.startsWith("data:image/") || - val.startsWith("iVBORw0KGgo") || - val.startsWith("/9j/") - ); -} - -function extractImageData(output: unknown): { imageData: string | undefined; cleanOutput: unknown } { - if (isBase64Image(output)) { - return { imageData: output, cleanOutput: "[image data extracted]" }; - } - - if (typeof output === "object" && output !== null && !Array.isArray(output)) { - const obj = output as Record<string, unknown>; - let imageData: string | undefined; - const clean: Record<string, unknown> = {}; - - for (const [key, val] of Object.entries(obj)) { - if (!imageData && isBase64Image(val)) { - imageData = val; - clean[key] = "[image data extracted]"; - } else { - clean[key] = val; - } - } - - return { imageData, cleanOutput: clean }; - } - - return { imageData: undefined, cleanOutput: output }; -} - -function truncate(value: unknown, max: number): unknown { - if (typeof value === "string" && value.length > max) { - return value.slice(0, max) + "\n[...truncated]"; - } - if (typeof value === "object" && value !== null) { - const str = JSON.stringify(value); - if (str.length > max) return str.slice(0, max) + "...[truncated]"; - return value; - } - return value; -} - -main(); diff --git a/src/hooks/pre-compact.ts b/src/hooks/pre-compact.ts deleted file mode 100644 index 8bb2660c..00000000 --- a/src/hooks/pre-compact.ts +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env node -import { resolveProject } from "./_project.js"; - -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) return; - - const sessionId = ((data.session_id || data.sessionId) as string) || "unknown"; - const project = resolveProject(data.cwd as string | undefined); - - if (process.env["CLAUDE_MEMORY_BRIDGE"] === "true") { - try { - await fetch(`${REST_URL}/agentmemory/claude-bridge/sync`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({}), - signal: AbortSignal.timeout(5000), - }); - } catch { - // best-effort - } - } - - try { - const res = await fetch(`${REST_URL}/agentmemory/context`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ sessionId, project, budget: 1500 }), - signal: AbortSignal.timeout(5000), - }); - - if (res.ok) { - const result = (await res.json()) as { context?: string }; - if (result.context) { - process.stdout.write(result.context); - } - } - } catch { - // best effort -- don't block compaction - } -} - -main(); diff --git a/src/hooks/pre-tool-use.ts b/src/hooks/pre-tool-use.ts deleted file mode 100644 index 277d7799..00000000 --- a/src/hooks/pre-tool-use.ts +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env node - -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -// Pre-tool-use enrichment hook. -// -// THIS HOOK IS A NO-OP BY DEFAULT AS OF 0.8.10 (#143). Previously it -// fired /agentmemory/enrich on every Edit/Write/Read/Glob/Grep tool call -// and wrote up to 4000 chars of context to stdout. Claude Code reads -// PreToolUse stdout and prepends it to the model's next turn, which meant -// agentmemory was silently injecting ~1000 tokens into every tool turn -// via the user's Claude Code session. On Claude Pro that burned entire -// allocations in a handful of messages (@adrianricardo, #143). -// -// Users who explicitly want pre-tool enrichment opt in with: -// AGENTMEMORY_INJECT_CONTEXT=true in ~/.agentmemory/.env -// and restart Claude Code. Expect your session input token count to grow -// proportionally with the number of file-touching tool calls per turn. -const INJECT_CONTEXT = process.env["AGENTMEMORY_INJECT_CONTEXT"] === "true"; - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - // Default off: exit immediately so we don't even open stdin. This keeps - // Claude Code's tool-call hot path as cheap as possible. - if (!INJECT_CONTEXT) return; - - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) return; - - const toolName = - typeof data.tool_name === "string" - ? data.tool_name - : typeof data.toolName === "string" - ? data.toolName - : undefined; - if (!toolName) return; - - const normalizedToolName = toolName.toLowerCase(); - const fileTools = ["edit", "write", "create", "read", "view", "glob", "grep"]; - if (!fileTools.includes(normalizedToolName)) return; - - const rawToolInput = data.tool_input ?? data.toolArgs; - const toolInput = - typeof rawToolInput === "object" && - rawToolInput !== null && - !Array.isArray(rawToolInput) - ? (rawToolInput as Record<string, unknown>) - : {}; - const files: string[] = []; - const fileKeys = - normalizedToolName === "grep" - ? ["path", "file"] - : ["file_path", "path", "file", "pattern"]; - for (const key of fileKeys) { - const val = toolInput[key]; - if (typeof val === "string" && val.length > 0) files.push(val); - } - if (files.length === 0) return; - - const terms: string[] = []; - if (normalizedToolName === "grep" || normalizedToolName === "glob") { - const pattern = toolInput["pattern"]; - if (typeof pattern === "string" && pattern.length > 0) { - terms.push(pattern); - } - } - - const rawSessionId = data.session_id || data.sessionId; - const sessionId = - typeof rawSessionId === "string" && rawSessionId.length > 0 - ? rawSessionId - : "unknown"; - const project = - typeof data.project === "string" && data.project.trim().length > 0 - ? data.project.trim() - : undefined; - - try { - const res = await fetch(`${REST_URL}/agentmemory/enrich`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - sessionId, - files, - terms, - toolName, - ...(project !== undefined && { project }), - }), - signal: AbortSignal.timeout(2000), - }); - - if (res.ok) { - const result = (await res.json()) as { context?: string }; - if (result.context) { - process.stdout.write(result.context); - } - } - } catch { - // don't block tool execution - } -} - -main(); diff --git a/src/hooks/prompt-submit.ts b/src/hooks/prompt-submit.ts deleted file mode 100644 index 2da8ea6a..00000000 --- a/src/hooks/prompt-submit.ts +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env node -import { resolveProject } from "./_project.js"; - -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) return; - - const sessionId = ((data.session_id || data.sessionId) as string) || "unknown"; - - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "prompt_submit", - sessionId, - project: resolveProject(data.cwd as string | undefined), - cwd: (data.cwd as string | undefined) || process.cwd(), - timestamp: new Date().toISOString(), - data: { prompt: data.prompt ?? data.userPrompt }, - }), - signal: AbortSignal.timeout(3000), - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} - -main(); diff --git a/src/hooks/sdk-guard.ts b/src/hooks/sdk-guard.ts deleted file mode 100644 index 8cc5f217..00000000 --- a/src/hooks/sdk-guard.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Recursion guard shared by every hook script. - * - * A Claude Code session spawned via @anthropic-ai/claude-agent-sdk inherits - * the same plugin hooks as the parent CC session. If any hook script in that - * child session calls back into /agentmemory/* (e.g. Stop → /summarize → - * provider.summarize() → another child session), we get unbounded recursion - * that burns tokens and fills .claude/projects/ with ghost sessions - * (#149 follow-up; see reported loop under v0.9.1). - * - * Two signals identify a SDK-child context: - * 1. AGENTMEMORY_SDK_CHILD=1 env var — set by our agent-sdk provider - * before it spawns `query()`. Inherited by child processes. - * 2. payload.entrypoint === "sdk-ts" — CC writes this into the hook - * stdin jsonl when the session was spawned by the Agent SDK. - * - * Hook scripts must call isSdkChildContext(payload) EARLY and return - * silently when it is true. - */ -export function isSdkChildContext(payload: unknown): boolean { - if (process.env.AGENTMEMORY_SDK_CHILD === "1") return true; - if (!payload || typeof payload !== "object") return false; - const p = payload as Record<string, unknown>; - if (p["entrypoint"] === "sdk-ts") return true; - return false; -} diff --git a/src/hooks/session-end.ts b/src/hooks/session-end.ts deleted file mode 100644 index 6a9fc429..00000000 --- a/src/hooks/session-end.ts +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env node - -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) return; - - const sessionId = ((data.session_id || data.sessionId) as string) || "unknown"; - - fetch(`${REST_URL}/agentmemory/session/end`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ sessionId }), - signal: AbortSignal.timeout(30000), - }).catch(() => {}); - - if (process.env["CONSOLIDATION_ENABLED"] === "true") { - fetch(`${REST_URL}/agentmemory/crystals/auto`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ olderThanDays: 0 }), - signal: AbortSignal.timeout(60000), - }).catch(() => {}); - - fetch(`${REST_URL}/agentmemory/consolidate-pipeline`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ tier: "all", force: true }), - signal: AbortSignal.timeout(120000), - }).catch(() => {}); - } - - if (process.env["CLAUDE_MEMORY_BRIDGE"] === "true") { - fetch(`${REST_URL}/agentmemory/claude-bridge/sync`, { - method: "POST", - headers: authHeaders(), - signal: AbortSignal.timeout(30000), - }).catch(() => {}); - } - - setTimeout(() => process.exit(0), 1500).unref(); -} - -main(); diff --git a/src/hooks/session-start.ts b/src/hooks/session-start.ts deleted file mode 100644 index 573c092b..00000000 --- a/src/hooks/session-start.ts +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env node -import { resolveProject } from "./_project.js"; - -// Inlined from ./sdk-guard so each hook bundles to a single self-contained -// .mjs (matches the pattern used by every other hook entry in tsdown.config). -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -// Session-start hook. -// -// Always registers the session for observation tracking (so memories -// captured on PostToolUse get attached to the right session). Only writes -// project context to stdout — which Claude Code prepends to the very first -// turn — when AGENTMEMORY_INJECT_CONTEXT=true. Default off as of 0.8.10 -// (#143); see pre-tool-use.ts for the full explanation. -const INJECT_CONTEXT = process.env["AGENTMEMORY_INJECT_CONTEXT"] === "true"; - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -// When the server is unreachable a 5s timeout multiplies hard under -// concurrent fan-out (Slack bots, multi-agent harnesses) and becomes a -// positive feedback loop that OOM-kills iii-engine (#221). Cap tight on -// both paths and skip the await entirely when the response is unused. -const INJECT_TIMEOUT_MS = 1500; -const REGISTER_TIMEOUT_MS = 800; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) return; - - const sessionId = - ((data.session_id || data.sessionId) as string) || - `ses_${Date.now().toString(36)}`; - const cwd = (data.cwd as string) || process.cwd(); - const project = resolveProject(data.cwd as string | undefined); - - const url = `${REST_URL}/agentmemory/session/start`; - const init: RequestInit = { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ sessionId, project, cwd }), - }; - - if (!INJECT_CONTEXT) { - // Pure telemetry path: caller never reads the response, so don't - // block on it. AbortSignal.timeout caps the wait the event loop - // gives the pending socket before exit. - fetch(url, { - ...init, - signal: AbortSignal.timeout(REGISTER_TIMEOUT_MS), - }).catch(() => {}); - return; - } - - try { - const res = await fetch(url, { - ...init, - signal: AbortSignal.timeout(INJECT_TIMEOUT_MS), - }); - if (res.ok) { - const result = (await res.json()) as { context?: string }; - if (result.context) { - process.stdout.write(result.context); - } - } - } catch { - // silently fail -- don't block Claude Code startup - } -} - -main(); diff --git a/src/hooks/stop.ts b/src/hooks/stop.ts deleted file mode 100644 index 4103796e..00000000 --- a/src/hooks/stop.ts +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env node - -// Inlined — see src/hooks/sdk-guard.ts for canonical version. Kept local -// per-hook so tsdown does not emit a shared hashed chunk that would churn -// the diff on every rebuild. -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) { - // Do not summarize from inside a Claude Agent SDK child session; - // would re-enter agent-sdk provider and loop (see sdk-guard.ts). - return; - } - - const sessionId = ((data.session_id || data.sessionId) as string) || "unknown"; - - fetch(`${REST_URL}/agentmemory/summarize`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ sessionId }), - signal: AbortSignal.timeout(120000), - }).catch(() => {}); - - fetch(`${REST_URL}/agentmemory/session/end`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ sessionId }), - signal: AbortSignal.timeout(5000), - }).catch(() => {}); - - setTimeout(() => process.exit(0), 1500).unref(); -} - -main(); diff --git a/src/hooks/subagent-start.ts b/src/hooks/subagent-start.ts deleted file mode 100644 index 44c87c85..00000000 --- a/src/hooks/subagent-start.ts +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env node -import { resolveProject } from "./_project.js"; - -// Inlined from ./sdk-guard so each hook bundles to a single self-contained -// .mjs (matches the pattern used by every other hook entry in tsdown.config). -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -// Passive telemetry only — nothing reads the response, so the previous -// `await` was pure latency. Tightened from 2000ms to a defensive cap so a -// slow/unreachable server can't stack onto every concurrent subagent -// startup (#221). -const TIMEOUT_MS = 800; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) return; - - const sessionId = ((data.session_id || data.sessionId) as string) || "unknown"; - const agentId = data.agent_id || data.agentName; - const agentType = data.agent_type || data.agentDisplayName || data.agentName; - - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "subagent_start", - sessionId, - project: resolveProject(data.cwd as string | undefined), - cwd: (data.cwd as string | undefined) || process.cwd(), - timestamp: new Date().toISOString(), - data: { - agent_id: agentId, - agent_type: agentType, - }, - }), - signal: AbortSignal.timeout(TIMEOUT_MS), - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} - -main(); diff --git a/src/hooks/subagent-stop.ts b/src/hooks/subagent-stop.ts deleted file mode 100644 index 5a437c9f..00000000 --- a/src/hooks/subagent-stop.ts +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env node -import { resolveProject } from "./_project.js"; - -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) return; - - const sessionId = ((data.session_id || data.sessionId) as string) || "unknown"; - const agentId = data.agent_id || data.agentName; - const agentType = data.agent_type || data.agentDisplayName || data.agentName; - const lastMsg = - typeof data.last_assistant_message === "string" - ? data.last_assistant_message.slice(0, 4000) - : ""; - - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "subagent_stop", - sessionId, - project: resolveProject(data.cwd as string | undefined), - cwd: (data.cwd as string | undefined) || process.cwd(), - timestamp: new Date().toISOString(), - data: { - agent_id: agentId, - agent_type: agentType, - last_message: lastMsg, - }, - }), - signal: AbortSignal.timeout(2000), - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} - -main(); diff --git a/src/hooks/task-completed.ts b/src/hooks/task-completed.ts deleted file mode 100644 index dbdf815a..00000000 --- a/src/hooks/task-completed.ts +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env node -import { resolveProject } from "./_project.js"; - -function isSdkChildContext(payload: unknown): boolean { - if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true; - if (!payload || typeof payload !== "object") return false; - return (payload as { entrypoint?: unknown }).entrypoint === "sdk-ts"; -} - -const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -function authHeaders(): Record<string, string> { - const h: Record<string, string> = { "Content-Type": "application/json" }; - if (SECRET) h["Authorization"] = `Bearer ${SECRET}`; - return h; -} - -async function main() { - let input = ""; - for await (const chunk of process.stdin) { - input += chunk; - } - - let data: Record<string, unknown>; - try { - data = JSON.parse(input); - } catch { - return; - } - - if (isSdkChildContext(data)) return; - - const sessionId = (data.session_id as string) || "unknown"; - - fetch(`${REST_URL}/agentmemory/observe`, { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "task_completed", - sessionId, - project: resolveProject(data.cwd as string | undefined), - cwd: (data.cwd as string | undefined) || process.cwd(), - timestamp: new Date().toISOString(), - data: { - task_id: data.task_id, - task_subject: data.task_subject, - task_description: typeof data.task_description === "string" - ? data.task_description.slice(0, 2000) - : "", - teammate_name: data.teammate_name, - team_name: data.team_name, - }, - }), - signal: AbortSignal.timeout(2000), - }).catch(() => {}); - setTimeout(() => process.exit(0), 500).unref(); -} - -main(); diff --git a/src/index.ts b/src/index.ts index 5c6fec79..b4628b49 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,666 +1 @@ -import { registerWorker } from "iii-sdk"; -import { - loadConfig, - getEnvVar, - loadEmbeddingConfig, - loadFallbackConfig, - loadClaudeBridgeConfig, - loadTeamConfig, - loadSnapshotConfig, - isGraphExtractionEnabled, - isAutoCompressEnabled, - isConsolidationEnabled, - isContextInjectionEnabled, - isDropStaleIndexEnabled, - isLarkReplyLoopEnabled, - getLarkConfig, - getAgentMemoryDataDir, -} from "./config.js"; -import { - createProvider, - createFallbackProvider, - createEmbeddingProvider, - createImageEmbeddingProvider, -} from "./providers/index.js"; -import { StateKV } from "./state/kv.js"; -import { KV } from "./state/schema.js"; -import { VectorIndex } from "./state/vector-index.js"; -import { HybridSearch } from "./state/hybrid-search.js"; -import { IndexPersistence } from "./state/index-persistence.js"; -import { registerPrivacyFunction } from "./functions/privacy.js"; -import { registerObserveFunction } from "./functions/observe.js"; -import { registerImageQuotaCleanup } from "./functions/image-quota-cleanup.js"; -import { registerVisionSearchFunctions } from "./functions/vision-search.js"; -import { registerSlotsFunctions, isSlotsEnabled, isReflectEnabled } from "./functions/slots.js"; -import { registerDiskSizeManager } from "./functions/disk-size-manager.js"; -import { registerCompressFunction } from "./functions/compress.js"; -import { - registerSearchFunction, - rebuildIndex, - getSearchIndex, - setVectorIndex, - setEmbeddingProvider, - setIndexPersistence, -} from "./functions/search.js"; -import { registerContextFunction } from "./functions/context.js"; -import { registerSummarizeFunction } from "./functions/summarize.js"; -import { registerMigrateFunction } from "./functions/migrate.js"; -import { registerFileIndexFunction } from "./functions/file-index.js"; -import { registerConsolidateFunction } from "./functions/consolidate.js"; -import { registerPatternsFunction } from "./functions/patterns.js"; -import { registerRememberFunction } from "./functions/remember.js"; -import { registerEvictFunction } from "./functions/evict.js"; -import { registerRelationsFunction } from "./functions/relations.js"; -import { registerTimelineFunction } from "./functions/timeline.js"; -import { registerSmartSearchFunction } from "./functions/smart-search.js"; -import { registerProfileFunction } from "./functions/profile.js"; -import { registerAutoForgetFunction } from "./functions/auto-forget.js"; -import { registerExportImportFunction } from "./functions/export-import.js"; -import { registerEnrichFunction } from "./functions/enrich.js"; -import { registerClaudeBridgeFunction } from "./functions/claude-bridge.js"; -import { registerGraphFunction } from "./functions/graph.js"; -import { registerConsolidationPipelineFunction } from "./functions/consolidation-pipeline.js"; -import { registerTeamFunction } from "./functions/team.js"; -import { registerGovernanceFunction } from "./functions/governance.js"; -import { registerSnapshotFunction } from "./functions/snapshot.js"; -import { registerActionsFunction } from "./functions/actions.js"; -import { registerFrontierFunction } from "./functions/frontier.js"; -import { registerLeasesFunction } from "./functions/leases.js"; -import { registerRoutinesFunction } from "./functions/routines.js"; -import { registerSignalsFunction } from "./functions/signals.js"; -import { registerInboxFunction } from "./functions/inbox.js"; -import { registerInboxDeliverFunction } from "./functions/inbox-deliver.js"; -import { startLarkReplyConsumer, type ReplyLoop } from "./functions/lark-reply-loop.js"; -import { registerCheckpointsFunction } from "./functions/checkpoints.js"; -import { registerFlowCompressFunction } from "./functions/flow-compress.js"; -import { registerMeshFunction } from "./functions/mesh.js"; -import { registerBranchAwareFunction } from "./functions/branch-aware.js"; -import { registerSentinelsFunction } from "./functions/sentinels.js"; -import { registerSketchesFunction } from "./functions/sketches.js"; -import { registerCrystallizeFunction } from "./functions/crystallize.js"; -import { registerDiagnosticsFunction } from "./functions/diagnostics.js"; -import { registerFacetsFunction } from "./functions/facets.js"; -import { registerVerifyFunction } from "./functions/verify.js"; -import { registerCascadeFunction } from "./functions/cascade.js"; -import { registerLessonsFunctions } from "./functions/lessons.js"; -import { registerObsidianExportFunction } from "./functions/obsidian-export.js"; -import { registerReflectFunctions } from "./functions/reflect.js"; -import { registerWorkingMemoryFunctions } from "./functions/working-memory.js"; -import { registerSkillExtractFunctions } from "./functions/skill-extract.js"; -import { registerSlidingWindowFunction } from "./functions/sliding-window.js"; -import { registerQueryExpansionFunction } from "./functions/query-expansion.js"; -import { registerTemporalGraphFunctions } from "./functions/temporal-graph.js"; -import { registerRetentionFunctions } from "./functions/retention.js"; -import { registerCompressFileFunction } from "./functions/compress-file.js"; -import { registerReplayFunctions } from "./functions/replay.js"; -import { registerSourceScanFunctions } from "./functions/source-scan-codex.js"; -import { registerSessionHighlightsFunction } from "./functions/session-highlights.js"; -import { registerActionCandidateFunctions } from "./functions/action-candidates.js"; -import { registerTodoExtractFunctions } from "./functions/todo-extract.js"; -import { registerApiTriggers } from "./triggers/api.js"; -import { registerEventTriggers } from "./triggers/events.js"; -import { registerMcpEndpoints } from "./mcp/server.js"; -import { getAllTools } from "./mcp/tools-registry.js"; -import { startViewerServer } from "./viewer/server.js"; -import { MetricsStore } from "./eval/metrics-store.js"; -import { DedupMap } from "./functions/dedup.js"; -import { registerHealthMonitor } from "./health/monitor.js"; -import { initMetrics, OTEL_CONFIG } from "./telemetry/setup.js"; -import { VERSION } from "./version.js"; -import { bootLog } from "./logger.js"; -import { mkdirSync, writeFileSync, unlinkSync } from "node:fs"; -import { dirname, join } from "node:path"; - -// #640 + #474: the worker process (this file) is spawned by iii-exec -// inside the engine. When `agentmemory-lab stop` kills only the engine pid, -// this worker can survive (detached spawn, signal not propagated, or a -// wrapper script keeps it running) and reconnects to the next engine as -// a duplicate worker. Write the worker pid alongside iii.pid so -// `agentmemory-lab stop` can reap us too. -function workerPidfilePath(): string { - return join(getAgentMemoryDataDir(), "worker.pid"); -} -function writeWorkerPidfile(): void { - try { - const p = workerPidfilePath(); - mkdirSync(dirname(p), { recursive: true }); - writeFileSync(p, `${process.pid}\n`, { encoding: "utf-8" }); - } catch { - // best-effort; stop still has the engine pidfile + port scan fallback - } -} -function clearWorkerPidfile(): void { - try { - unlinkSync(workerPidfilePath()); - } catch {} -} - -function hasGetMeter( - sdk: unknown, -): sdk is { getMeter: (name: string) => unknown } { - return ( - typeof sdk === "object" && - sdk !== null && - "getMeter" in sdk && - typeof (sdk as { getMeter?: unknown }).getMeter === "function" - ); -} - -// Top-level safety net for iii-engine invocation timeouts (issue #204). -// Under sustained write load (e.g. Claude Code hooks across many -// projects) `state::set` can occasionally exceed the SDK's 30s timeout. -// We don't want one such timeout to terminate the long-lived memory -// service — the rejection is surfaced to the relevant call site via -// .catch() where it matters; everything else is logged-and-continued. -// Throttle logs to avoid spamming on bursts. -let lastUnhandledLogAt = 0; -process.on("unhandledRejection", (reason) => { - const now = Date.now(); - if (now - lastUnhandledLogAt < 60_000) return; - lastUnhandledLogAt = now; - const r = reason as { code?: string; function_id?: string; message?: string }; - console.warn( - `[agentmemory] unhandledRejection (suppressed):`, - r?.code ? `${r.code} ${r.function_id ?? ""} ${r.message ?? ""}`.trim() : reason, - ); -}); - -async function main() { - const config = loadConfig(); - const embeddingConfig = loadEmbeddingConfig(); - const fallbackConfig = loadFallbackConfig(); - - const provider = - fallbackConfig.providers.length > 0 - ? createFallbackProvider(config.provider, fallbackConfig) - : createProvider(config.provider); - - const embeddingProvider = createEmbeddingProvider(); - const imageEmbeddingProvider = createImageEmbeddingProvider(); - - bootLog(`Starting worker v${VERSION}...`); - bootLog(`Engine: ${config.engineUrl}`); - bootLog( - `Provider: ${config.provider.provider} (${config.provider.model})`, - ); - if (embeddingProvider) { - bootLog( - `Embedding provider: ${embeddingProvider.name} (${embeddingProvider.dimensions} dims)`, - ); - } else { - bootLog(`Embedding provider: none (BM25-only mode)`); - } - if (imageEmbeddingProvider) { - bootLog( - `Image embedding provider: ${imageEmbeddingProvider.name} (${imageEmbeddingProvider.dimensions} dims) — vision-search active`, - ); - } - bootLog( - `REST API: http://localhost:${config.restPort}/agentmemory/*`, - ); - bootLog(`Streams: ws://localhost:${config.streamsPort}`); - - const sdk = registerWorker(config.engineUrl, { - workerName: "agentmemory", - invocationTimeoutMs: 180000, - otel: { - serviceName: OTEL_CONFIG.serviceName, - serviceVersion: OTEL_CONFIG.serviceVersion, - metricsExportIntervalMs: OTEL_CONFIG.metricsExportIntervalMs, - }, - // Explicit worker telemetry metadata. iii-sdk falls back to - // auto-detection (cwd / package.json name / hostname) when this - // is omitted, which produces inconsistent values per host — - // `agentmemory`, `node`, `npm`, occasionally the user's home - // directory basename. Pinning the value here gives every install - // the same stable project identifier for downstream attribution - // and grouping in the engine's metrics + traces output. - telemetry: { - project_name: "agentmemory", - language: "node", - framework: "iii-sdk", - }, - }); - - writeWorkerPidfile(); - - const kv = new StateKV(sdk); - const secret = getEnvVar("AGENTMEMORY_SECRET"); - const metricsStore = new MetricsStore(kv); - const dedupMap = new DedupMap(); - - const vectorIndex = embeddingProvider ? new VectorIndex() : null; - - setVectorIndex(vectorIndex); - setEmbeddingProvider(embeddingProvider); - - const meterAccessor = hasGetMeter(sdk) - ? (sdk.getMeter.bind(sdk) as (name: string) => unknown) - : undefined; - - initMetrics(meterAccessor as ((name: string) => import("@opentelemetry/api").Meter) | undefined); - - registerPrivacyFunction(sdk); - registerObserveFunction(sdk, kv, dedupMap, config.maxObservationsPerSession); - registerImageQuotaCleanup(sdk, kv); - registerVisionSearchFunctions(sdk, kv, imageEmbeddingProvider); - if (isSlotsEnabled()) { - registerSlotsFunctions(sdk, kv); - } - registerDiskSizeManager(sdk, kv); - registerCompressFunction(sdk, kv, provider, metricsStore); - registerSearchFunction(sdk, kv); - registerContextFunction(sdk, kv, config.tokenBudget); - registerSummarizeFunction(sdk, kv, provider, metricsStore); - registerMigrateFunction(sdk, kv); - registerFileIndexFunction(sdk, kv); - registerConsolidateFunction(sdk, kv, provider); - registerPatternsFunction(sdk, kv); - registerRememberFunction(sdk, kv); - registerEvictFunction(sdk, kv); - - registerRelationsFunction(sdk, kv); - registerTimelineFunction(sdk, kv); - registerProfileFunction(sdk, kv); - registerAutoForgetFunction(sdk, kv); - registerExportImportFunction(sdk, kv); - registerEnrichFunction(sdk, kv); - - const claudeBridgeConfig = loadClaudeBridgeConfig(); - if (claudeBridgeConfig.enabled) { - registerClaudeBridgeFunction(sdk, kv, claudeBridgeConfig); - bootLog( - `Claude bridge: syncing to ${claudeBridgeConfig.memoryFilePath}`, - ); - } - - if (isGraphExtractionEnabled()) { - registerGraphFunction(sdk, kv, provider); - bootLog(`Knowledge graph: extraction enabled`); - } - - registerConsolidationPipelineFunction(sdk, kv, provider); - bootLog(`Consolidation pipeline: registered (CONSOLIDATION_ENABLED=${isConsolidationEnabled() ? "true" : "false"})`); - - if (isAutoCompressEnabled()) { - bootLog( - `WARNING: AGENTMEMORY_AUTO_COMPRESS=true — every PostToolUse observation will be sent to your LLM provider for compression. This spends API tokens proportional to your session tool-use frequency (see #138). Set AGENTMEMORY_AUTO_COMPRESS=false to disable.`, - ); - } else { - bootLog( - `Auto-compress: OFF (default, #138) — observations indexed via zero-LLM synthetic compression. Set AGENTMEMORY_AUTO_COMPRESS=true to opt-in to LLM-powered summaries (uses your API key).`, - ); - } - - if (isContextInjectionEnabled()) { - bootLog( - `WARNING: AGENTMEMORY_INJECT_CONTEXT=true — the PreToolUse and SessionStart hooks will inject up to ~4000 chars of memory context into every tool turn. On Claude Pro this burns session tokens proportional to your tool-call frequency (see #143). Set AGENTMEMORY_INJECT_CONTEXT=false to disable.`, - ); - } else { - bootLog( - `Context injection: OFF (default, #143) — hooks capture observations but do not inject context into Claude Code's conversation. Set AGENTMEMORY_INJECT_CONTEXT=true to opt-in (warning: expect your Claude Pro allocation to drain faster).`, - ); - } - - const teamConfig = loadTeamConfig(); - if (teamConfig) { - registerTeamFunction(sdk, kv, teamConfig); - bootLog( - `Team memory: ${teamConfig.teamId} (${teamConfig.mode})`, - ); - } - - registerGovernanceFunction(sdk, kv); - - registerActionsFunction(sdk, kv); - registerFrontierFunction(sdk, kv); - registerLeasesFunction(sdk, kv); - registerRoutinesFunction(sdk, kv); - registerSignalsFunction(sdk, kv); - registerInboxFunction(sdk, kv); - registerInboxDeliverFunction(sdk, kv); - registerCheckpointsFunction(sdk, kv); - registerMeshFunction(sdk, kv, secret); - registerBranchAwareFunction(sdk, kv); - registerFlowCompressFunction(sdk, kv, provider); - registerSentinelsFunction(sdk, kv); - registerSketchesFunction(sdk, kv); - registerCrystallizeFunction(sdk, kv, provider); - registerDiagnosticsFunction(sdk, kv); - registerFacetsFunction(sdk, kv); - registerVerifyFunction(sdk, kv); - registerLessonsFunctions(sdk, kv); - registerObsidianExportFunction(sdk, kv); - registerReflectFunctions(sdk, kv, provider); - registerWorkingMemoryFunctions(sdk, kv, config.tokenBudget); - registerSkillExtractFunctions(sdk, kv, provider); - registerCascadeFunction(sdk, kv); - - registerSlidingWindowFunction(sdk, kv, provider); - registerQueryExpansionFunction(sdk, provider); - registerTemporalGraphFunctions(sdk, kv, provider); - registerRetentionFunctions(sdk, kv); - registerCompressFileFunction(sdk, kv, provider); - registerReplayFunctions(sdk, kv); - registerSourceScanFunctions(sdk, kv); - registerSessionHighlightsFunction(sdk, kv); - registerActionCandidateFunctions(sdk, kv); - registerTodoExtractFunctions(sdk, kv); - bootLog( - `v0.6 advanced retrieval: sliding-window, query-expansion, temporal-graph, retention-scoring`, - ); - bootLog( - `Orchestration layer: actions, frontier, leases, routines, signals, checkpoints, flow-compress, mesh, branch-aware, sentinels, sketches, crystallize, diagnostics, facets`, - ); - if (isSlotsEnabled()) { - bootLog( - `Slots: enabled (pinned editable memory). Reflect on Stop hook: ${isReflectEnabled() ? "on" : "off"}`, - ); - } - - const snapshotConfig = loadSnapshotConfig(); - if (snapshotConfig.enabled) { - registerSnapshotFunction(sdk, kv, snapshotConfig.dir); - bootLog( - `Git snapshots: ${snapshotConfig.dir} (every ${snapshotConfig.interval}s)`, - ); - } - - const bm25Index = getSearchIndex(); - const graphWeight = parseFloat(getEnvVar("AGENTMEMORY_GRAPH_WEIGHT") || "0.3"); - const hybridSearch = new HybridSearch( - bm25Index, - vectorIndex, - embeddingProvider, - kv, - embeddingConfig.bm25Weight, - embeddingConfig.vectorWeight, - graphWeight, - ); - - registerSmartSearchFunction(sdk, kv, (query, limit) => - hybridSearch.search(query, limit), - ); - - registerApiTriggers(sdk, kv, secret, metricsStore, provider); - registerEventTriggers(sdk, kv); - registerMcpEndpoints(sdk, kv, secret); - - const healthMonitor = registerHealthMonitor(sdk, kv); - - const indexPersistence = new IndexPersistence(kv, bm25Index, vectorIndex); - // Wire the persistence hook so delete paths can flush BM25/vector - // index mutations to disk. Without this, an in-memory remove can be - // lost across a hard process exit and the persisted snapshot - // restores the deleted entry at next boot. - setIndexPersistence(indexPersistence); - - const loaded = await indexPersistence.load().catch((err) => { - console.warn(`[agentmemory] Failed to load persisted index:`, err); - return null; - }); - if (loaded?.bm25 && loaded.bm25.size > 0) { - bm25Index.restoreFrom(loaded.bm25); - bootLog( - `Loaded persisted BM25 index (${bm25Index.size} docs)`, - ); - } - if (loaded?.vector && vectorIndex && loaded.vector.size > 0) { - // Persisted vectors carry whatever dimension the provider had when - // they were written. If the active provider declares a different - // dimension — or if the on-disk index contains a mix of dimensions - // (legacy indexes written before the live-API guard in this PR) — - // restoring would silently corrupt search: cosineSimilarity returns - // 0 on cross-dim pairs, so affected observations stop matching - // anything and recall degrades without an error. Walk every stored - // vector instead of trusting the first; refuse to load if anything - // is off. - const activeDim = embeddingProvider?.dimensions ?? 0; - const { mismatches, seenDimensions } = - activeDim > 0 - ? loaded.vector.validateDimensions(activeDim) - : { mismatches: [], seenDimensions: new Set<number>() }; - - if (mismatches.length > 0) { - const sample = mismatches - .slice(0, 5) - .map((m) => `${m.obsId} (dim=${m.dim})`) - .join(", "); - const distinct = Array.from(seenDimensions).sort((a, b) => a - b).join(", "); - const dropStale = isDropStaleIndexEnabled(); - if (dropStale) { - console.warn( - `[agentmemory] Persisted vector index has ${mismatches.length} of ` + - `${loaded.vector.size} vectors with the wrong dimension. Active ` + - `provider (${embeddingProvider?.name}) declares ${activeDim}; ` + - `dimensions seen on disk: ${distinct}. ` + - `AGENTMEMORY_DROP_STALE_INDEX=true is set — discarding the persisted ` + - `vectors. Live observations will rebuild the index over time.`, - ); - } else { - throw new Error( - `[agentmemory] Refusing to start: persisted vector index has ` + - `${mismatches.length} of ${loaded.vector.size} vectors with the ` + - `wrong dimension. Active provider (${embeddingProvider?.name}) ` + - `declares ${activeDim}; dimensions seen on disk: ${distinct}. ` + - `First mismatched obsIds: ${sample}. Loading would silently corrupt ` + - `search (cross-dimension cosine returns 0). Choose one:\n` + - ` - Re-embed the existing index against the new provider, then start.\n` + - ` - Set AGENTMEMORY_DROP_STALE_INDEX=true to discard the persisted ` + - `vectors and rebuild from live observations.\n` + - ` - Switch the embedding provider back to the one that wrote the index.`, - ); - } - } else { - vectorIndex.restoreFrom(loaded.vector); - bootLog( - `Loaded persisted vector index (${vectorIndex.size} vectors)`, - ); - } - } - - const needsRebuild = bm25Index.size === 0; - - if (needsRebuild) { - // Fire-and-forget. rebuildIndex iterates every observation across - // every session and AWAITS an embedding-provider call per record. - // On a large corpus + rate-limited embedding endpoint that can - // take HOURS; awaiting it here blocks every subsequent boot step - // (including startViewerServer below, leaving the viewer port - // unbound for the duration). The index lazily fills in over time - // and search degrades gracefully — partial coverage > no viewer - // for hours. Errors still surface via the inner .catch. - void rebuildIndex(kv) - .then((indexCount) => { - if (indexCount > 0) { - bootLog(`Search index rebuilt: ${indexCount} entries`); - indexPersistence.scheduleSave(); - } - }) - .catch((err) => { - console.warn(`[agentmemory] Failed to rebuild search index:`, err); - }); - } else { - // Backfill memories into BM25 for users upgrading from <0.9.5: prior - // versions of mem::remember never indexed memories, so the persisted - // BM25 covers observations only and `memory_smart_search` returns - // empty for everything saved via memory_save (#257). Walk KV.memories - // and add the ones missing from the restored index. Idempotent on - // re-runs because SearchIndex.has() short-circuits already-indexed - // ids. - try { - const memories = await kv.list<import("./types.js").Memory>(KV.memories); - let backfilled = 0; - for (const memory of memories) { - if (memory.isLatest === false) continue; - if (!memory.title || !memory.content) continue; - if (bm25Index.has(memory.id)) continue; - bm25Index.add({ - id: memory.id, - sessionId: memory.sessionIds?.[0] ?? "memory", - timestamp: memory.createdAt, - type: "decision", - title: memory.title, - facts: [memory.content], - narrative: memory.content, - concepts: memory.concepts, - files: memory.files, - importance: memory.strength, - }); - backfilled++; - } - if (backfilled > 0) { - bootLog( - `Backfilled ${backfilled} memories into BM25 (legacy gap before #257)`, - ); - indexPersistence.scheduleSave(); - } - } catch (err) { - console.warn( - `[agentmemory] Failed to backfill memories into BM25:`, - err, - ); - } - } - - // Ready / Endpoints lines are emitted via `bootLog` so they're - // buffered in quiet mode and printed verbatim under --verbose. The - // CLI surfaces a compact summary when it sees the worker reach - // ready state. - bootLog( - `Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`, - ); - bootLog( - `REST API: 142 endpoints at http://localhost:${config.restPort}/agentmemory/*`, - ); - bootLog( - `MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`, - ); - - const viewerPort = - parseInt(process.env.III_VIEWER_PORT || process.env.AGENTMEMORY_VIEWER_PORT || "", 10) || - config.restPort + 3; - const viewerServer = startViewerServer( - viewerPort, - kv, - sdk, - secret, - config.restPort, - ); - - const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10); - const consolidationIntervalMs = parseInt(process.env.CONSOLIDATION_INTERVAL_MS || "7200000", 10); - - if (process.env.AUTO_FORGET_ENABLED !== "false") { - const autoForgetTimer = setInterval(async () => { - try { - await sdk.trigger({ function_id: "mem::auto-forget", payload: { dryRun: false } }); - } catch {} - }, autoForgetIntervalMs); - autoForgetTimer.unref(); - bootLog(`Auto-forget: enabled (every ${autoForgetIntervalMs / 60000}m)`); - } - - if (process.env.LESSON_DECAY_ENABLED !== "false") { - const lessonDecayTimer = setInterval(async () => { - try { - await sdk.trigger({ function_id: "mem::lesson-decay-sweep", payload: {} }); - } catch {} - }, 86400000); - lessonDecayTimer.unref(); - bootLog(`Lesson decay sweep: enabled (every 24h)`); - } - - if (process.env.INSIGHT_DECAY_ENABLED !== "false") { - const insightDecayTimer = setInterval(async () => { - try { - await sdk.trigger({ function_id: "mem::insight-decay-sweep", payload: {} }); - } catch {} - }, 86400000); - insightDecayTimer.unref(); - } - - if (isConsolidationEnabled()) { - const consolidationTimer = setInterval(async () => { - try { - await sdk.trigger({ function_id: "mem::consolidate-pipeline", payload: {} }); - } catch {} - }, consolidationIntervalMs); - consolidationTimer.unref(); - bootLog(`Auto-consolidation: enabled (every ${consolidationIntervalMs / 60000}m)`); - } - - if (process.env.AGENTMEMORY_CODEX_AUTOSCAN !== "false") { - const codexScanIntervalMs = parseInt( - process.env.AGENTMEMORY_CODEX_SCAN_INTERVAL_MS || "300000", - 10, - ); - const scanCodex = () => { - sdk.trigger({ function_id: "mem::source-scan::codex", payload: {} }).catch(() => {}); - }; - scanCodex(); // load history at startup; later runs are incremental (the checkpoint skips unchanged files) - const codexScanTimer = setInterval(scanCodex, codexScanIntervalMs); - codexScanTimer.unref(); - bootLog(`Codex auto-scan: enabled (every ${codexScanIntervalMs / 60000}m)`); - } - - // Line D / STEP-D5b — Feishu reply loop. Default OFF; only spawns the - // long-running `lark-cli event consume` subscriber when explicitly enabled - // and a target user is configured. - let replyLoop: ReplyLoop | null = null; - if (isLarkReplyLoopEnabled()) { - const larkConfig = getLarkConfig(); - if (larkConfig?.userId) { - replyLoop = startLarkReplyConsumer({ kv, sdk, userId: larkConfig.userId }); - bootLog("Lark reply loop: enabled"); - } else { - bootLog("Lark reply loop: requested but no AGENTMEMORY_LARK_USER_ID — skipped"); - } - } - - let shuttingDown = false; - const shutdown = async () => { - // Re-entrancy guard: a second Ctrl+C while we're already shutting down - // must not stack another shutdown pass on top of the first. - if (shuttingDown) return; - shuttingDown = true; - console.log(`\n[agentmemory] Shutting down...`); - - // Watchdog: never let a hung resource trap Ctrl+C. If graceful cleanup - // doesn't finish quickly, force the exit. unref() so the timer itself - // can't keep the event loop alive. - const forceExit = setTimeout(() => { - console.warn(`[agentmemory] Shutdown timed out after 3s — forcing exit.`); - process.exit(0); - }, 3000); - forceExit.unref(); - - replyLoop?.stop(); - healthMonitor.stop(); - dedupMap.stop(); - indexPersistence.stop(); - // http.Server.close() only fires its callback once every socket has - // drained. A browser with the viewer open holds keep-alive / preconnect / - // in-flight sockets that never drain on their own, so close() alone would - // hang forever (this is what made Ctrl+C unable to exit). closeAllConnections() - // destroys those sockets so close() can complete. - await new Promise<void>((resolve) => { - viewerServer.close(() => resolve()); - viewerServer.closeAllConnections(); - }); - await indexPersistence.save().catch((err) => { - console.warn(`[agentmemory] Failed to save index on shutdown:`, err); - }); - await sdk.shutdown(); - clearWorkerPidfile(); - clearTimeout(forceExit); - process.exit(0); - }; - process.on("SIGINT", shutdown); - process.on("SIGTERM", shutdown); -} - -main().catch((err) => { - console.error(`[agentmemory] Fatal:`, err); - process.exit(1); -}); +export * from "./contracts.js"; diff --git a/src/logger.ts b/src/logger.ts deleted file mode 100644 index f417f8df..00000000 --- a/src/logger.ts +++ /dev/null @@ -1,110 +0,0 @@ -// Thin logging shim for agentmemory. -// -// iii-sdk v0.11 dropped `getContext()`, which had been the source of a -// contextual logger in every function handler (`getContext().logger`). -// Migrating directly to the v0.11 OTEL-based `getLogger()` would force -// every call site to care about the OTEL Logger API shape (`emit(...)` -// with severity numbers and attributes maps). Instead, this module -// exposes a single `logger` singleton with the same `.info/.warn/.error` -// signature the old code used, so the mechanical replacement across -// 30+ function files is: drop the `getContext` import, drop the -// `const ctx = getContext();` line, and rename `ctx.logger.*` to -// `logger.*`. Nothing else changes. -// -// Output goes to stderr as `[agentmemory] <level> <msg> <json-fields>`. -// The iii-engine's `iii-exec` worker runs the agentmemory binary as a -// child process and forwards stderr into `docker logs -// agentmemory-iii-engine-1`, so these lines end up next to the engine's -// own output without needing any OTEL wiring. If we later want -// structured OTEL logs, this file is the only thing that changes. -// -// See MaimoryLab/agentmemory-lab#143 follow-up — the #116 migration updated -// test mocks but left the real `getContext()` imports in place, which -// passed `npm test` (tests mock iii-sdk) and `npm run build` (tsdown -// doesn't type-check) but crashed `node dist/index.mjs` on first -// import. - -type Fields = Record<string, unknown> | undefined; - -function fmt(level: string, msg: string, fields: Fields): string { - if (!fields || Object.keys(fields).length === 0) { - return `[agentmemory] ${level} ${msg}`; - } - try { - return `[agentmemory] ${level} ${msg} ${JSON.stringify(fields)}`; - } catch { - // Fields contained a circular reference or a BigInt — fall back - // to the plain message so a log line never throws. - return `[agentmemory] ${level} ${msg}`; - } -} - -function emit(level: string, msg: string, fields: Fields): void { - try { - process.stderr.write(fmt(level, msg, fields) + "\n"); - } catch { - // stderr is unavailable in some weird test/worker contexts — swallow - // so no log line can ever crash a handler. - } -} - -export const logger = { - info(msg: string, fields?: Fields): void { - emit("info", msg, fields); - }, - warn(msg: string, fields?: Fields): void { - emit("warn", msg, fields); - }, - error(msg: string, fields?: Fields): void { - emit("error", msg, fields); - }, -}; - -// ---------- boot log ---------- -// -// `bootLog` is for the one-shot status lines that every register-* -// function used to dump via `console.log` during engine startup. On a -// fresh install that's ~25 lines of `[agentmemory] X enabled` noise -// before the user can see a prompt. In quiet mode (default), each -// line is captured into a buffer and discarded; the CLI surfaces a -// single compressed summary instead. In verbose mode (set by -// `--verbose` or `AGENTMEMORY_VERBOSE=1`) the lines pass straight -// through to stderr exactly like the old console.log calls. - -let bootVerbose = - process.env["AGENTMEMORY_VERBOSE"] === "1" || - process.env["AGENTMEMORY_VERBOSE"] === "true"; - -const bootBuffer: string[] = []; - -export function setBootVerbose(enabled: boolean): void { - bootVerbose = enabled; -} - -export function isBootVerbose(): boolean { - return bootVerbose; -} - -export function bootLog(msg: string): void { - if (bootVerbose) { - try { - process.stderr.write(`[agentmemory] ${msg}\n`); - } catch { - // stderr unavailable — drop. - } - return; - } - if (bootBuffer.length < 500) bootBuffer.push(msg); -} - -export function bootWarn(msg: string): void { - // Warnings always surface; they're rare and the user needs to see - // them even when the rest of the boot log is suppressed. - try { - process.stderr.write(`[agentmemory] warn ${msg}\n`); - } catch {} -} - -export function getBootBuffer(): readonly string[] { - return bootBuffer; -} diff --git a/src/mcp/in-memory-kv.ts b/src/mcp/in-memory-kv.ts deleted file mode 100644 index 0dacebf1..00000000 --- a/src/mcp/in-memory-kv.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"; -import { dirname } from "node:path"; - -export class InMemoryKV { - private store = new Map<string, Map<string, unknown>>(); - - constructor(private persistPath?: string) { - if (persistPath && existsSync(persistPath)) { - try { - const data = JSON.parse(readFileSync(persistPath, "utf-8")); - for (const [scope, entries] of Object.entries(data)) { - const map = new Map<string, unknown>(); - for (const [key, value] of Object.entries( - entries as Record<string, unknown>, - )) { - map.set(key, value); - } - this.store.set(scope, map); - } - } catch { - // start fresh - } - } - } - - async get<T = unknown>(scope: string, key: string): Promise<T | null> { - return (this.store.get(scope)?.get(key) as T) ?? null; - } - - async set<T = unknown>(scope: string, key: string, data: T): Promise<T> { - if (!this.store.has(scope)) this.store.set(scope, new Map()); - this.store.get(scope)!.set(key, data); - return data; - } - - async delete(scope: string, key: string): Promise<void> { - this.store.get(scope)?.delete(key); - } - - async list<T = unknown>(scope: string): Promise<T[]> { - const entries = this.store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - } - - persist(): void { - if (!this.persistPath) return; - try { - const dir = dirname(this.persistPath); - if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); - const data: Record<string, Record<string, unknown>> = {}; - for (const [scope, entries] of this.store) { - data[scope] = Object.fromEntries(entries); - } - writeFileSync(this.persistPath, JSON.stringify(data), "utf-8"); - } catch (err) { - process.stderr.write( - `[@agentmemory/mcp] Persist failed: ${err instanceof Error ? err.message : String(err)}\n`, - ); - } - } -} diff --git a/src/mcp/index.ts b/src/mcp/index.ts new file mode 100644 index 00000000..bef3cca3 --- /dev/null +++ b/src/mcp/index.ts @@ -0,0 +1,100 @@ +import type { Database } from "../db/index.js"; +import { getAppPaths, type AppPaths } from "../paths.js"; +import { scanSource } from "../sources/scan.js"; +import { organizeConfiguredTodos } from "../todos/configured.js"; +import { listTodos, type OrganizeOptions, updateTodoStatus } from "../todos/service.js"; + +export interface McpTool { + name: string; + description: string; + inputSchema: { + type: "object"; + properties: Record<string, unknown>; + required?: string[]; + }; +} + +export function listMcpTools(): McpTool[] { + return [ + { + name: "todo_scan", + description: "Scan Codex or Claude Code session JSONL files into AI-Todo.", + inputSchema: { + type: "object", + properties: { + source: { type: "string", enum: ["codex", "claude-code"] }, + path: { type: "string" } + }, + required: ["source"] + } + }, + { + name: "todo_organize", + description: "Organize observations into evidence-grounded todo cards.", + inputSchema: { type: "object", properties: {} } + }, + { + name: "todo_list", + description: "List current todo cards.", + inputSchema: { type: "object", properties: {} } + }, + { + name: "todo_update", + description: "Mark a todo as done or ignored.", + inputSchema: { + type: "object", + properties: { + id: { type: "string" }, + status: { type: "string", enum: ["done", "ignored"] } + }, + required: ["id", "status"] + } + }, + { + name: "todo_open", + description: "Open the AI-Todo viewer when available.", + inputSchema: { type: "object", properties: {} } + } + ]; +} + +export async function callMcpTool( + db: Database, + name: string, + args: unknown, + paths: AppPaths = getAppPaths(), + options: { organizeOptions?: OrganizeOptions } = {} +): Promise<any> { + const input = objectArgs(args); + + if (name === "todo_scan") { + const scan = scanSource(db, input.source, input.path, paths); + if (!scan.ok) throw new Error(scan.error === "unsupported_source" ? "unsupported source" : "path not found"); + return scan.result; + } + + if (name === "todo_organize") { + return await organizeConfiguredTodos(db, paths, options.organizeOptions); + } + + if (name === "todo_list") { + return listTodos(db); + } + + if (name === "todo_update") { + if (typeof input.id !== "string" || !input.id) throw new Error("missing todo id"); + if (input.status !== "done" && input.status !== "ignored") throw new Error("invalid status"); + if (!updateTodoStatus(db, input.id, input.status)) throw new Error("todo not found"); + return listTodos(db).find((todo) => todo.id === input.id); + } + + if (name === "todo_open") { + return { opened: false, message: "run ai-todo open to start the local UI" }; + } + + throw new Error(`unknown tool: ${name}`); +} + +function objectArgs(args: unknown): Record<string, unknown> { + return args && typeof args === "object" && !Array.isArray(args) ? args as Record<string, unknown> : {}; +} diff --git a/src/mcp/rest-proxy.ts b/src/mcp/rest-proxy.ts deleted file mode 100644 index 1adbb46e..00000000 --- a/src/mcp/rest-proxy.ts +++ /dev/null @@ -1,179 +0,0 @@ -const DEFAULT_URL = "http://localhost:3111"; -const DEFAULT_HEALTH_PROBE_TIMEOUT_MS = 2_000; -const CALL_TIMEOUT_MS = 15_000; -const LOCAL_MODE_TTL_MS = 30_000; - -function probeTimeoutMs(): number { - const raw = process.env["AGENTMEMORY_PROBE_TIMEOUT_MS"]; - if (!raw) return DEFAULT_HEALTH_PROBE_TIMEOUT_MS; - const n = Number(raw); - return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_HEALTH_PROBE_TIMEOUT_MS; -} - -function forceProxy(): boolean { - const raw = process.env["AGENTMEMORY_FORCE_PROXY"]; - return raw === "1" || raw === "true"; -} - -export interface ProxyHandle { - mode: "proxy"; - baseUrl: string; - call: (path: string, init?: RequestInit) => Promise<unknown>; -} - -export interface LocalHandle { - mode: "local"; -} - -export type Handle = ProxyHandle | LocalHandle; - -let cached: Handle | null = null; -let cachedAt = 0; -let probeInFlight: Promise<Handle> | null = null; - -// `${VAR}`-style placeholders ship in plugin/.mcp.json so MCP hosts that -// expand them (Claude Code, Cursor) substitute the user's shell value. -// Hosts that DON'T expand pass the literal string `"${AGENTMEMORY_URL}"` -// through to our subprocess — that string is truthy, defeats the `||` -// fallback, and would have us POST to `${AGENTMEMORY_URL}/agentmemory/...` -// (DNS failure). Strip any literal placeholder we see so the fallback -// engages instead. -export function resolveEnvOrEmpty(name: string): string { - const raw = process.env[name]; - if (!raw) return ""; - if (raw.startsWith("${") && raw.endsWith("}")) return ""; - return raw; -} - -function baseUrl(): string { - return (resolveEnvOrEmpty("AGENTMEMORY_URL") || DEFAULT_URL).replace(/\/+$/, ""); -} - -function authHeader(): Record<string, string> { - const secret = resolveEnvOrEmpty("AGENTMEMORY_SECRET"); - return secret ? { authorization: `Bearer ${secret}` } : {}; -} - -/** - * Probes the agentmemory server's livez endpoint. Returns a Response-shaped - * object whose `ok` flag drives the proxy/local-fallback decision. - * - * Tests can swap this via {@link setLivezProbe} to avoid the real 2s - * AbortController race that destabilises mcp-standalone test runs (#449). - * Production callers should leave it on the default. - */ -export type LivezProbe = ( - url: string, - timeoutMs: number, - headers: Record<string, string>, -) => Promise<{ ok: boolean; status?: number; statusText?: string }>; - -const defaultLivezProbe: LivezProbe = async (url, timeoutMs, headers) => { - const res = await fetch(`${url}/agentmemory/livez`, { - method: "GET", - headers, - signal: AbortSignal.timeout(timeoutMs), - }); - return { ok: res.ok, status: res.status, statusText: res.statusText }; -}; - -let livezProbe: LivezProbe = defaultLivezProbe; - -/** - * Override the livez probe. Intended for tests — production code should rely - * on the default fetch-based probe. Calling without an argument restores the - * default. Pair with {@link resetHandleForTests} so the cached handle is - * dropped before the next call. - */ -export function setLivezProbe(fn?: LivezProbe): void { - livezProbe = fn ?? defaultLivezProbe; -} - -async function probe(url: string): Promise<boolean> { - const timeout = probeTimeoutMs(); - try { - const res = await livezProbe(url, timeout, authHeader()); - if (!res.ok) { - process.stderr.write( - `[@agentmemory/mcp] livez probe ${url}/agentmemory/livez -> ${res.status ?? "?"} ${res.statusText ?? ""}; falling back to local InMemoryKV (set AGENTMEMORY_FORCE_PROXY=1 to skip the probe)\n`, - ); - } - return res.ok; - } catch (err) { - process.stderr.write( - `[@agentmemory/mcp] livez probe ${url}/agentmemory/livez failed in ${timeout}ms: ${err instanceof Error ? err.message : String(err)}; falling back to local InMemoryKV (set AGENTMEMORY_FORCE_PROXY=1 to skip the probe, or raise AGENTMEMORY_PROBE_TIMEOUT_MS)\n`, - ); - return false; - } -} - -export function invalidateHandle(): void { - cached = null; - cachedAt = 0; -} - -export async function resolveHandle(): Promise<Handle> { - const now = Date.now(); - if (cached) { - if (cached.mode === "local" && now - cachedAt >= LOCAL_MODE_TTL_MS) { - cached = null; - cachedAt = 0; - } else { - return cached; - } - } - if (probeInFlight) return probeInFlight; - const url = baseUrl(); - const skipProbe = forceProxy(); - probeInFlight = (async () => { - const up = skipProbe ? true : await probe(url); - if (skipProbe) { - process.stderr.write( - `[@agentmemory/mcp] AGENTMEMORY_FORCE_PROXY set; skipping livez probe and trusting ${url}\n`, - ); - } - if (up) { - const handle: ProxyHandle = { - mode: "proxy", - baseUrl: url, - call: async (path, init) => { - const res = await fetch(`${url}${path}`, { - ...init, - headers: { - "content-type": "application/json", - ...authHeader(), - ...(init?.headers as Record<string, string> | undefined), - }, - signal: AbortSignal.timeout(CALL_TIMEOUT_MS), - }); - if (!res.ok) { - throw new Error( - `${init?.method || "GET"} ${path} -> ${res.status} ${res.statusText}`, - ); - } - const text = await res.text(); - return text ? JSON.parse(text) : null; - }, - }; - cached = handle; - cachedAt = Date.now(); - return handle; - } - const local: LocalHandle = { mode: "local" }; - cached = local; - cachedAt = Date.now(); - return local; - })(); - try { - return await probeInFlight; - } finally { - probeInFlight = null; - } -} - -export function resetHandleForTests(): void { - cached = null; - cachedAt = 0; - probeInFlight = null; - livezProbe = defaultLivezProbe; -} diff --git a/src/mcp/server.ts b/src/mcp/server.ts deleted file mode 100644 index c0b70bbf..00000000 --- a/src/mcp/server.ts +++ /dev/null @@ -1,1764 +0,0 @@ -import type { ISdk, ApiRequest } from "iii-sdk"; -import type { StateKV } from "../state/kv.js"; -import { KV } from "../state/schema.js"; -import type { - SessionSummary, - Memory, - Session, - GraphNode, - GraphEdge, -} from "../types.js"; -import { getVisibleTools } from "./tools-registry.js"; -import { timingSafeCompare } from "../auth.js"; - -type McpResponse = { - status_code: number; - headers?: Record<string, string>; - body: unknown; -}; - -function asNonEmptyString(value: unknown): string | undefined { - return typeof value === "string" && value.trim() ? value.trim() : undefined; -} - -function asNumber(value: unknown, fallback?: number): number | undefined { - const n = Number(value); - if (Number.isFinite(n)) return n; - return fallback; -} - -function parseCsvList(value: unknown): string[] { - if (typeof value === "string") { - return value.split(",").map((v) => v.trim()).filter(Boolean); - } - if (Array.isArray(value)) { - return value - .map((v) => (typeof v === "string" ? v.trim() : "")) - .filter(Boolean); - } - return []; -} - -export function registerMcpEndpoints( - sdk: ISdk, - kv: StateKV, - secret?: string, -): void { - function checkAuth( - req: ApiRequest, - sec: string | undefined, - ): McpResponse | null { - if (!sec) return null; - const auth = - req.headers?.["authorization"] || req.headers?.["Authorization"]; - if (typeof auth !== "string" || !timingSafeCompare(auth, `Bearer ${sec}`)) { - return { status_code: 401, body: { error: "unauthorized" } }; - } - return null; - } - - sdk.registerFunction("mcp::tools::list", - async (req: ApiRequest): Promise<McpResponse> => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - return { status_code: 200, body: { tools: getVisibleTools() } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "mcp::tools::list", - config: { api_path: "/agentmemory/mcp/tools", http_method: "GET" }, - }); - - sdk.registerFunction("mcp::tools::call", - async ( - req: ApiRequest<{ name: string; arguments: Record<string, unknown> }>, - ): Promise<McpResponse> => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - - if (!req.body || typeof req.body.name !== "string") { - return { status_code: 400, body: { error: "name is required" } }; - } - - const { name, arguments: args = {} } = req.body; - - try { - switch (name) { - case "memory_recall": { - if (typeof args.query !== "string" || !args.query.trim()) { - return { - status_code: 400, - body: { error: "query is required for memory_recall" }, - }; - } - const format = - typeof args.format === "string" ? args.format.trim().toLowerCase() : "full"; - if (!["full", "compact", "narrative"].includes(format)) { - return { - status_code: 400, - body: { - error: "format must be one of: full, compact, narrative", - }, - }; - } - const tokenBudget = asNumber(args.token_budget); - if ( - args.token_budget !== undefined && - (!Number.isInteger(tokenBudget) || (tokenBudget ?? 0) < 1) - ) { - return { - status_code: 400, - body: { error: "token_budget must be a positive integer" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::search", payload: { - query: args.query, - limit: typeof args.limit === "number" ? args.limit : 10, - format, - token_budget: tokenBudget, - } }); - const text = - format === "narrative" && - result && - typeof result === "object" && - "text" in (result as Record<string, unknown>) && - typeof (result as { text?: unknown }).text === "string" - ? (result as { text: string }).text - : JSON.stringify(result, null, 2); - return { - status_code: 200, - body: { - content: [ - { type: "text", text }, - ], - }, - }; - } - - case "memory_compress_file": { - if (typeof args.filePath !== "string" || !args.filePath.trim()) { - return { - status_code: 400, - body: { error: "filePath is required for memory_compress_file" }, - }; - } - const result = await sdk.trigger({ - function_id: "mem::compress-file", - payload: { filePath: args.filePath.trim() }, - }); - return { - status_code: 200, - body: { - content: [{ type: "text", text: JSON.stringify(result, null, 2) }], - }, - }; - } - - case "memory_save": { - if (typeof args.content !== "string" || !args.content.trim()) { - return { - status_code: 400, - body: { error: "content is required for memory_save" }, - }; - } - const type = (args.type as string) || "fact"; - const concepts = - typeof args.concepts === "string" - ? args.concepts.split(",").map((c: string) => c.trim()).filter(Boolean) - : []; - const files = - typeof args.files === "string" - ? args.files.split(",").map((f: string) => f.trim()).filter(Boolean) - : []; - - const project = - typeof args.project === "string" && args.project.trim().length > 0 - ? args.project.trim() - : undefined; - - const result = await sdk.trigger({ function_id: "mem::remember", payload: { - content: args.content, - type, - concepts, - files, - ...(project !== undefined && { project }), - } }); - return { - status_code: 200, - body: { - content: [{ type: "text", text: JSON.stringify(result) }], - }, - }; - } - - case "memory_file_history": { - if (typeof args.files !== "string" || !args.files.trim()) { - return { - status_code: 400, - body: { error: "files is required for memory_file_history" }, - }; - } - const fileList = parseCsvList(args.files); - if (!fileList.length) { - return { - status_code: 400, - body: { error: "files must contain at least one valid path" }, - }; - } - const payload: { sessionId?: string; files: string[] } = { files: fileList }; - const sessionId = asNonEmptyString(args.sessionId); - if (sessionId) payload.sessionId = sessionId; - const result = await sdk.trigger({ - function_id: "mem::file-context", - payload, - }); - return { - status_code: 200, - body: { - content: [ - { - type: "text", - text: - (result as { context: string }).context || - "No history found.", - }, - ], - }, - }; - } - - case "memory_patterns": { - const result = await sdk.trigger({ function_id: "mem::patterns", payload: { - project: args.project as string, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } - - case "memory_sessions": { - const sessions = await kv.list(KV.sessions); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify({ sessions }, null, 2) }, - ], - }, - }; - } - - case "memory_smart_search": { - if (typeof args.query !== "string" || !args.query.trim()) { - return { - status_code: 400, - body: { error: "query is required for memory_smart_search" }, - }; - } - const expandIds = parseCsvList(args.expandIds).slice(0, 20); - const limit = Math.max(1, Math.min(100, asNumber(args.limit, 10) ?? 10)); - const result = await sdk.trigger({ - function_id: "mem::smart-search", - payload: { - query: args.query, - expandIds, - limit, - }, - }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } - - case "memory_vision_search": { - const queryText = typeof args.queryText === "string" ? args.queryText : undefined; - const queryImageRef = typeof args.queryImageRef === "string" ? args.queryImageRef : undefined; - const queryImageBase64 = typeof args.queryImageBase64 === "string" ? args.queryImageBase64 : undefined; - if (!queryText && !queryImageRef && !queryImageBase64) { - return { - status_code: 400, - body: { error: "queryText, queryImageRef, or queryImageBase64 required" }, - }; - } - const topK = Math.max(1, Math.min(50, asNumber(args.topK, 10) ?? 10)); - const sessionId = typeof args.sessionId === "string" ? args.sessionId : undefined; - const result = await sdk.trigger({ - function_id: "mem::vision-search", - payload: { queryText, queryImageRef, queryImageBase64, topK, sessionId }, - }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } - - case "memory_timeline": { - if (typeof args.anchor !== "string" || !args.anchor.trim()) { - return { - status_code: 400, - body: { error: "anchor is required for memory_timeline" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::timeline", payload: { - anchor: args.anchor, - project: (args.project as string) || undefined, - before: (args.before as number) || 5, - after: (args.after as number) || 5, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } - - case "memory_profile": { - if (typeof args.project !== "string" || !args.project.trim()) { - return { - status_code: 400, - body: { error: "project is required for memory_profile" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::profile", payload: { - project: args.project, - refresh: args.refresh === true || args.refresh === "true", - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } - - case "memory_export": { - const result = await sdk.trigger({ function_id: "mem::export", payload: {} }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } - - case "memory_relations": { - if (typeof args.memoryId !== "string" || !args.memoryId.trim()) { - return { - status_code: 400, - body: { error: "memoryId is required for memory_relations" }, - }; - } - const rawMaxHops = Number(args.maxHops); - const rawMinConf = Number(args.minConfidence); - const result = await sdk.trigger({ function_id: "mem::get-related", payload: { - memoryId: args.memoryId, - maxHops: Number.isFinite(rawMaxHops) ? rawMaxHops : 2, - minConfidence: Number.isFinite(rawMinConf) - ? Math.max(0, Math.min(1, rawMinConf)) - : 0, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } - - case "memory_claude_bridge_sync": { - const direction = (args.direction as string) || "write"; - const funcId = - direction === "read" - ? "mem::claude-bridge-read" - : "mem::claude-bridge-sync"; - try { - const result = await sdk.trigger({ - function_id: funcId, - payload: {}, - }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } catch { - return { - status_code: 200, - body: { - content: [ - { - type: "text", - text: "Claude bridge not enabled. Set CLAUDE_MEMORY_BRIDGE=true", - }, - ], - }, - }; - } - } - - case "memory_graph_query": { - try { - const payload: { - startNodeId?: string; - nodeType?: string; - maxDepth?: number; - query?: string; - } = {}; - const startNodeId = asNonEmptyString(args.startNodeId); - const nodeType = asNonEmptyString(args.nodeType); - const query = asNonEmptyString(args.query); - const maxDepth = asNumber(args.maxDepth); - if (startNodeId) payload.startNodeId = startNodeId; - if (nodeType) payload.nodeType = nodeType; - if (query) payload.query = query; - if (maxDepth !== undefined) payload.maxDepth = Math.max(1, Math.min(8, maxDepth)); - const result = await sdk.trigger({ - function_id: "mem::graph-query", - payload, - }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } catch { - return { - status_code: 200, - body: { - content: [ - { - type: "text", - text: "Knowledge graph not enabled. Set GRAPH_EXTRACTION_ENABLED=true", - }, - ], - }, - }; - } - } - - case "memory_consolidate": { - try { - const result = await sdk.trigger({ function_id: "mem::consolidate-pipeline", payload: { - tier: args.tier as string, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } catch { - return { - status_code: 200, - body: { - content: [ - { - type: "text", - text: "Consolidation not enabled. Set CONSOLIDATION_ENABLED=true", - }, - ], - }, - }; - } - } - - case "memory_team_share": { - if ( - typeof args.itemId !== "string" || - typeof args.itemType !== "string" - ) { - return { - status_code: 400, - body: { error: "itemId and itemType are required" }, - }; - } - try { - const result = await sdk.trigger({ function_id: "mem::team-share", payload: { - itemId: args.itemId, - itemType: args.itemType, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } catch { - return { - status_code: 200, - body: { - content: [ - { - type: "text", - text: "Team memory not enabled. Set TEAM_ID and USER_ID", - }, - ], - }, - }; - } - } - - case "memory_team_feed": { - try { - const result = await sdk.trigger({ function_id: "mem::team-feed", payload: { - limit: typeof args.limit === "number" ? args.limit : 20, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } catch { - return { - status_code: 200, - body: { - content: [ - { - type: "text", - text: "Team memory not enabled. Set TEAM_ID and USER_ID", - }, - ], - }, - }; - } - } - - case "memory_audit": { - try { - const result = await sdk.trigger({ function_id: "mem::audit-query", payload: { - operation: args.operation as string, - limit: typeof args.limit === "number" ? args.limit : 50, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } catch { - return { - status_code: 200, - body: { - content: [{ type: "text", text: "Audit query failed" }], - isError: true, - }, - }; - } - } - - case "memory_governance_delete": { - if (typeof args.memoryIds !== "string") { - return { - status_code: 400, - body: { error: "memoryIds is required" }, - }; - } - const ids = (args.memoryIds as string) - .split(",") - .map((id) => id.trim()) - .filter(Boolean); - try { - const result = await sdk.trigger({ function_id: "mem::governance-delete", payload: { - memoryIds: ids, - reason: args.reason as string, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } catch { - return { - status_code: 200, - body: { - content: [{ type: "text", text: "Governance delete failed" }], - isError: true, - }, - }; - } - } - - case "memory_snapshot_create": { - try { - const result = await sdk.trigger({ function_id: "mem::snapshot-create", payload: { - message: args.message as string, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(result, null, 2) }, - ], - }, - }; - } catch { - return { - status_code: 200, - body: { - content: [ - { - type: "text", - text: "Snapshots not enabled. Set SNAPSHOT_ENABLED=true", - }, - ], - }, - }; - } - } - - case "memory_action_create": { - if (typeof args.title !== "string" || !args.title.trim()) { - return { - status_code: 400, - body: { error: "title is required" }, - }; - } - const edges: Array<{ type: string; targetActionId: string }> = []; - if (typeof args.requires === "string" && args.requires.trim()) { - for (const id of args.requires.split(",").map((s: string) => s.trim()).filter(Boolean)) { - edges.push({ type: "requires", targetActionId: id }); - } - } - const tags = typeof args.tags === "string" && args.tags.trim() - ? args.tags.split(",").map((t: string) => t.trim()).filter(Boolean) - : []; - const actionResult = await sdk.trigger({ function_id: "mem::action-create", payload: { - title: args.title, - description: args.description, - priority: args.priority, - project: args.project, - tags, - parentId: args.parentId, - edges: edges.length > 0 ? edges : undefined, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(actionResult, null, 2) }, - ], - }, - }; - } - - case "memory_action_update": { - if (typeof args.actionId !== "string" || !args.actionId.trim()) { - return { - status_code: 400, - body: { error: "actionId is required" }, - }; - } - const updateResult = await sdk.trigger({ function_id: "mem::action-update", payload: { - actionId: args.actionId, - status: args.status, - result: args.result, - priority: args.priority, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(updateResult, null, 2) }, - ], - }, - }; - } - - case "memory_frontier": { - const frontierResult = await sdk.trigger({ function_id: "mem::frontier", payload: { - project: args.project, - agentId: args.agentId, - limit: args.limit, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(frontierResult, null, 2) }, - ], - }, - }; - } - - case "memory_next": { - const nextResult = await sdk.trigger({ function_id: "mem::next", payload: { - project: args.project, - agentId: args.agentId, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(nextResult, null, 2) }, - ], - }, - }; - } - - case "memory_lease": { - if ( - typeof args.actionId !== "string" || - typeof args.agentId !== "string" || - typeof args.operation !== "string" - ) { - return { - status_code: 400, - body: { error: "actionId, agentId, and operation are required" }, - }; - } - const op = args.operation as string; - let leaseResult; - if (op === "acquire") { - leaseResult = await sdk.trigger({ function_id: "mem::lease-acquire", payload: { - actionId: args.actionId, - agentId: args.agentId, - ttlMs: args.ttlMs, - } }); - } else if (op === "release") { - leaseResult = await sdk.trigger({ function_id: "mem::lease-release", payload: { - actionId: args.actionId, - agentId: args.agentId, - result: args.result, - } }); - } else if (op === "renew") { - leaseResult = await sdk.trigger({ function_id: "mem::lease-renew", payload: { - actionId: args.actionId, - agentId: args.agentId, - ttlMs: args.ttlMs, - } }); - } else { - return { - status_code: 400, - body: { error: "operation must be acquire, release, or renew" }, - }; - } - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(leaseResult, null, 2) }, - ], - }, - }; - } - - case "memory_routine_run": { - if (typeof args.routineId !== "string") { - return { - status_code: 400, - body: { error: "routineId is required" }, - }; - } - const runResult = await sdk.trigger({ function_id: "mem::routine-run", payload: { - routineId: args.routineId, - project: args.project, - initiatedBy: args.initiatedBy, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(runResult, null, 2) }, - ], - }, - }; - } - - case "memory_signal_send": { - if ( - typeof args.from !== "string" || - typeof args.content !== "string" - ) { - return { - status_code: 400, - body: { error: "from and content are required" }, - }; - } - const sigResult = await sdk.trigger({ function_id: "mem::signal-send", payload: { - from: args.from, - to: args.to, - content: args.content, - type: args.type, - replyTo: args.replyTo, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(sigResult, null, 2) }, - ], - }, - }; - } - - case "memory_signal_read": { - if (typeof args.agentId !== "string") { - return { - status_code: 400, - body: { error: "agentId is required" }, - }; - } - const readResult = await sdk.trigger({ function_id: "mem::signal-read", payload: { - agentId: args.agentId, - unreadOnly: args.unreadOnly === true || args.unreadOnly === "true", - threadId: args.threadId, - limit: args.limit, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(readResult, null, 2) }, - ], - }, - }; - } - - case "memory_inbox_ask": - case "memory_inbox_notify": { - if (typeof args.body !== "string" || !args.body.trim()) { - return { - status_code: 400, - body: { error: "body is required" }, - }; - } - const fnId = - name === "memory_inbox_ask" - ? "mem::inbox-ask" - : "mem::inbox-notify"; - const inboxResult = await sdk.trigger({ function_id: fnId, payload: { - body: args.body, - fromAgent: args.fromAgent, - project: args.project, - sourceObservationIds: args.sourceObservationIds, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(inboxResult, null, 2) }, - ], - }, - }; - } - - case "memory_checkpoint": { - const cpOp = args.operation as string; - if (!cpOp) { - return { - status_code: 400, - body: { error: "operation is required" }, - }; - } - let cpResult; - if (cpOp === "create") { - const linkedIds = typeof args.linkedActionIds === "string" && args.linkedActionIds.trim() - ? args.linkedActionIds.split(",").map((s: string) => s.trim()) - : []; - cpResult = await sdk.trigger({ function_id: "mem::checkpoint-create", payload: { - name: args.name, - description: args.description, - type: args.type, - linkedActionIds: linkedIds, - } }); - } else if (cpOp === "resolve") { - if (typeof args.checkpointId !== "string" || !args.checkpointId.trim()) { - return { - status_code: 400, - body: { error: "checkpointId is required for resolve operation" }, - }; - } - cpResult = await sdk.trigger({ function_id: "mem::checkpoint-resolve", payload: { - checkpointId: args.checkpointId, - status: args.status, - } }); - } else if (cpOp === "list") { - cpResult = await sdk.trigger({ function_id: "mem::checkpoint-list", payload: { - status: args.status, - type: args.type, - } }); - } else { - return { - status_code: 400, - body: { error: "operation must be create, resolve, or list" }, - }; - } - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(cpResult, null, 2) }, - ], - }, - }; - } - - case "memory_mesh_sync": { - const meshResult = await sdk.trigger({ function_id: "mem::mesh-sync", payload: { - peerId: args.peerId, - direction: args.direction, - } }); - return { - status_code: 200, - body: { - content: [ - { type: "text", text: JSON.stringify(meshResult, null, 2) }, - ], - }, - }; - } - - case "memory_sentinel_create": { - let snlConfig: Record<string, unknown> = {}; - if (typeof args.config === "object" && args.config !== null) { - snlConfig = args.config as Record<string, unknown>; - } else if (typeof args.config === "string" && args.config.trim()) { - try { snlConfig = JSON.parse(args.config); } catch { return { status_code: 400, body: { error: "invalid config JSON" } }; } - } - const snlLinked = parseCsvList(args.linkedActionIds); - const expiresInMs = asNumber(args.expiresInMs); - const name = asNonEmptyString(args.name); - const type = asNonEmptyString(args.type); - const payload: { - name?: string; - type?: string; - config: Record<string, unknown>; - linkedActionIds?: string[]; - expiresInMs?: number; - } = { config: snlConfig }; - if (name) payload.name = name; - if (type) payload.type = type; - if (snlLinked.length) payload.linkedActionIds = snlLinked; - if (expiresInMs !== undefined) payload.expiresInMs = Math.max(0, expiresInMs); - const snlResult = await sdk.trigger({ - function_id: "mem::sentinel-create", - payload, - }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(snlResult, null, 2) }] } }; - } - - case "memory_sentinel_trigger": { - let snlTrigPayload: unknown; - if (args.result !== undefined && args.result !== null) { - if (typeof args.result === "string") { - try { snlTrigPayload = JSON.parse(args.result); } catch { return { status_code: 400, body: { error: "invalid result JSON" } }; } - } else { - snlTrigPayload = args.result; - } - } - const sentinelId = asNonEmptyString(args.sentinelId); - if (!sentinelId) { - return { - status_code: 400, - body: { error: "sentinelId is required for memory_sentinel_trigger" }, - }; - } - const snlTrigResult = await sdk.trigger({ function_id: "mem::sentinel-trigger", payload: { - sentinelId, - result: snlTrigPayload, - } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(snlTrigResult, null, 2) }] } }; - } - - case "memory_sketch_create": { - const title = asNonEmptyString(args.title); - if (!title) { - return { - status_code: 400, - body: { error: "title is required for memory_sketch_create" }, - }; - } - const sketchPayload = { - title, - description: asNonEmptyString(args.description), - expiresInMs: asNumber(args.expiresInMs), - project: asNonEmptyString(args.project), - }; - const skResult = await sdk.trigger({ - function_id: "mem::sketch-create", - payload: sketchPayload, - }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(skResult, null, 2) }] } }; - } - - case "memory_sketch_promote": { - const sketchId = asNonEmptyString(args.sketchId); - if (!sketchId) { - return { - status_code: 400, - body: { error: "sketchId is required for memory_sketch_promote" }, - }; - } - const skpResult = await sdk.trigger({ function_id: "mem::sketch-promote", payload: { - sketchId, - project: args.project, - } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(skpResult, null, 2) }] } }; - } - - case "memory_crystallize": { - if (typeof args.actionIds !== "string" || !args.actionIds.trim()) { - return { status_code: 400, body: { error: "actionIds is required" } }; - } - const crysIds = args.actionIds.split(",").map((s: string) => s.trim()).filter(Boolean); - const crysResult = await sdk.trigger({ function_id: "mem::crystallize", payload: { - actionIds: crysIds, - project: args.project, - sessionId: args.sessionId, - } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(crysResult, null, 2) }] } }; - } - - case "memory_diagnose": { - const diagCats = typeof args.categories === "string" && args.categories.trim() - ? args.categories.split(",").map((s: string) => s.trim()).filter(Boolean) - : undefined; - const diagResult = await sdk.trigger({ function_id: "mem::diagnose", payload: { categories: diagCats } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(diagResult, null, 2) }] } }; - } - - case "memory_heal": { - const healCats = typeof args.categories === "string" && args.categories.trim() - ? args.categories.split(",").map((s: string) => s.trim()).filter(Boolean) - : undefined; - const healResult = await sdk.trigger({ function_id: "mem::heal", payload: { - categories: healCats, - dryRun: args.dryRun === true || args.dryRun === "true", - } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(healResult, null, 2) }] } }; - } - - case "memory_facet_tag": { - const fctResult = await sdk.trigger({ function_id: "mem::facet-tag", payload: { - targetId: args.targetId, - targetType: args.targetType, - dimension: args.dimension, - value: args.value, - } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(fctResult, null, 2) }] } }; - } - - case "memory_facet_query": { - if (args.matchAll !== undefined && typeof args.matchAll !== "string") { - return { status_code: 400, body: { error: "matchAll must be a string" } }; - } - if (args.matchAny !== undefined && typeof args.matchAny !== "string") { - return { status_code: 400, body: { error: "matchAny must be a string" } }; - } - const fqAll = typeof args.matchAll === "string" && args.matchAll.trim() - ? args.matchAll.split(",").map((s: string) => s.trim()).filter(Boolean) - : undefined; - const fqAny = typeof args.matchAny === "string" && args.matchAny.trim() - ? args.matchAny.split(",").map((s: string) => s.trim()).filter(Boolean) - : undefined; - const fqResult = await sdk.trigger({ function_id: "mem::facet-query", payload: { - matchAll: fqAll, - matchAny: fqAny, - targetType: args.targetType, - } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(fqResult, null, 2) }] } }; - } - - case "memory_verify": { - if (!args.id || typeof args.id !== "string") { - return { status_code: 400, body: { error: "id is required" } }; - } - const verifyResult = await sdk.trigger({ function_id: "mem::verify", payload: { id: args.id } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(verifyResult, null, 2) }] } }; - } - - case "memory_lesson_save": { - if (typeof args.content !== "string" || !args.content.trim()) { - return { status_code: 400, body: { error: "content is required" } }; - } - const lessonTags = typeof args.tags === "string" && args.tags.trim() - ? args.tags.split(",").map((t: string) => t.trim()).filter(Boolean) - : []; - const lessonSaveResult = await sdk.trigger({ function_id: "mem::lesson-save", payload: { - content: args.content, - context: args.context || "", - confidence: args.confidence, - project: args.project, - tags: lessonTags, - source: "manual", - } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(lessonSaveResult, null, 2) }] } }; - } - - case "memory_lesson_recall": { - if (typeof args.query !== "string" || !args.query.trim()) { - return { status_code: 400, body: { error: "query is required" } }; - } - const lessonRecallResult = await sdk.trigger({ function_id: "mem::lesson-recall", payload: { - query: args.query, - project: args.project, - minConfidence: args.minConfidence, - limit: args.limit, - } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(lessonRecallResult, null, 2) }] } }; - } - - case "memory_reflect": { - const reflectResult = await sdk.trigger({ function_id: "mem::reflect", payload: { - project: args.project, - maxClusters: args.maxClusters, - } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(reflectResult, null, 2) }] } }; - } - - case "memory_insight_list": { - const insightListResult = await sdk.trigger({ function_id: "mem::insight-list", payload: { - project: args.project, - minConfidence: args.minConfidence, - limit: args.limit, - } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(insightListResult, null, 2) }] } }; - } - - case "memory_obsidian_export": { - const exportTypes = typeof args.types === "string" && args.types.trim() - ? args.types.split(",").map((t: string) => t.trim()).filter(Boolean) - : undefined; - const obsidianResult = await sdk.trigger({ function_id: "mem::obsidian-export", payload: { - vaultDir: args.vaultDir, - types: exportTypes, - } }); - return { status_code: 200, body: { content: [{ type: "text", text: JSON.stringify(obsidianResult, null, 2) }] } }; - } - - case "memory_slot_list": { - const result = await sdk.trigger({ function_id: "mem::slot-list", payload: {} }); - return { - status_code: 200, - body: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }, - }; - } - - case "memory_slot_get": { - const label = asNonEmptyString(args.label); - if (!label) return { status_code: 400, body: { error: "label required" } }; - const result = await sdk.trigger({ function_id: "mem::slot-get", payload: { label } }); - return { - status_code: 200, - body: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }, - }; - } - - case "memory_slot_create": { - const label = asNonEmptyString(args.label); - if (!label) return { status_code: 400, body: { error: "label required" } }; - const payload: Record<string, unknown> = { label }; - if (typeof args.content === "string") payload.content = args.content; - if (typeof args.description === "string") payload.description = args.description; - if (typeof args.sizeLimit === "number") payload.sizeLimit = args.sizeLimit; - // Accept boolean and string-boolean forms; MCP clients bind either - // depending on their JSON schema wrapper. - if (args.pinned === false || args.pinned === "false") payload.pinned = false; - else if (args.pinned === true || args.pinned === "true") payload.pinned = true; - if (args.scope === "global" || args.scope === "project") payload.scope = args.scope; - const result = await sdk.trigger({ function_id: "mem::slot-create", payload }); - return { - status_code: 200, - body: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }, - }; - } - - case "memory_slot_append": { - const label = asNonEmptyString(args.label); - const text = typeof args.text === "string" ? args.text : null; - if (!label || !text) return { status_code: 400, body: { error: "label and text required" } }; - const result = await sdk.trigger({ function_id: "mem::slot-append", payload: { label, text } }); - return { - status_code: 200, - body: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }, - }; - } - - case "memory_slot_replace": { - const label = asNonEmptyString(args.label); - if (!label || typeof args.content !== "string") { - return { status_code: 400, body: { error: "label and content (string) required" } }; - } - const result = await sdk.trigger({ function_id: "mem::slot-replace", payload: { label, content: args.content } }); - return { - status_code: 200, - body: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }, - }; - } - - case "memory_slot_delete": { - const label = asNonEmptyString(args.label); - if (!label) return { status_code: 400, body: { error: "label required" } }; - const result = await sdk.trigger({ function_id: "mem::slot-delete", payload: { label } }); - return { - status_code: 200, - body: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }, - }; - } - - case "memory_commit_lookup": { - const sha = asNonEmptyString(args.sha); - if (!sha) return { status_code: 400, body: { error: "sha required" } }; - const link = await kv.get(KV.commits, sha); - if (!link) { - return { - status_code: 200, - body: { content: [{ type: "text", text: JSON.stringify({ commit: null, sessions: [] }, null, 2) }] }, - }; - } - const linkRecord = link as { sessionIds?: string[] }; - const fetched = await Promise.all( - (linkRecord.sessionIds ?? []).map((sid) => kv.get(KV.sessions, sid)), - ); - const sessions = fetched.filter((s) => s !== null); - return { - status_code: 200, - body: { content: [{ type: "text", text: JSON.stringify({ commit: link, sessions }, null, 2) }] }, - }; - } - - case "memory_commits": { - const branch = typeof args.branch === "string" ? args.branch : undefined; - const repo = typeof args.repo === "string" ? args.repo : undefined; - const limit = Math.max(1, Math.min(500, asNumber(args.limit, 100) ?? 100)); - const all = await kv.list(KV.commits); - const filtered = (all as Array<{ branch?: string; repo?: string; linkedAt?: string }>) - .filter((c) => !branch || c.branch === branch) - .filter((c) => !repo || c.repo === repo) - .sort((a, b) => ((a.linkedAt ?? "") < (b.linkedAt ?? "") ? 1 : -1)) - .slice(0, limit); - return { - status_code: 200, - body: { content: [{ type: "text", text: JSON.stringify({ commits: filtered }, null, 2) }] }, - }; - } - - default: - return { - status_code: 400, - body: { error: `Unknown tool: ${name}` }, - }; - } - } catch (err) { - return { - status_code: 500, - body: { - error: "Internal error", - }, - }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "mcp::tools::call", - config: { api_path: "/agentmemory/mcp/call", http_method: "POST" }, - }); - - const MCP_RESOURCES = [ - { - uri: "agentmemory://status", - name: "Agent Memory Status", - description: "Current session count, memory count, and health status", - mimeType: "application/json", - }, - { - uri: "agentmemory://project/{name}/profile", - name: "Project Profile", - description: - "Top concepts, frequently modified files, and conventions for a project", - mimeType: "application/json", - }, - { - uri: "agentmemory://project/{name}/recent", - name: "Recent Sessions", - description: "Last 5 session summaries for a project", - mimeType: "application/json", - }, - { - uri: "agentmemory://memories/latest", - name: "Latest Memories", - description: "Top 10 latest memories with their type and strength", - mimeType: "application/json", - }, - { - uri: "agentmemory://graph/stats", - name: "Knowledge Graph Stats", - description: "Node and edge counts by type in the knowledge graph", - mimeType: "application/json", - }, - { - uri: "agentmemory://team/{id}/profile", - name: "Team Profile", - description: "Team memory profile with shared concepts and patterns", - mimeType: "application/json", - }, - ]; - - sdk.registerFunction("mcp::resources::list", - async (req: ApiRequest): Promise<McpResponse> => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - return { status_code: 200, body: { resources: MCP_RESOURCES } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "mcp::resources::list", - config: { api_path: "/agentmemory/mcp/resources", http_method: "GET" }, - }); - - sdk.registerFunction("mcp::resources::read", - async (req: ApiRequest<{ uri: string }>): Promise<McpResponse> => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - - const uri = req.body?.uri; - if (!uri || typeof uri !== "string") { - return { status_code: 400, body: { error: "uri is required" } }; - } - - try { - if (uri === "agentmemory://status") { - const sessions = await kv.list<Session>(KV.sessions); - const memories = await kv.list<Memory>(KV.memories); - const healthData = await kv.list(KV.health).catch(() => []); - return { - status_code: 200, - body: { - contents: [ - { - uri, - mimeType: "application/json", - text: JSON.stringify({ - sessionCount: sessions.length, - memoryCount: memories.length, - healthStatus: - healthData.length > 0 ? "available" : "no-data", - }), - }, - ], - }, - }; - } - - const projectProfileMatch = uri.match( - /^agentmemory:\/\/project\/(.+)\/profile$/, - ); - if (projectProfileMatch) { - let projectName: string; - try { - projectName = decodeURIComponent(projectProfileMatch[1]); - } catch { - return { - status_code: 400, - body: { error: "Invalid percent-encoding in URI" }, - }; - } - const profile = await sdk.trigger({ function_id: "mem::profile", payload: { - project: projectName, - } }); - return { - status_code: 200, - body: { - contents: [ - { - uri, - mimeType: "application/json", - text: JSON.stringify(profile), - }, - ], - }, - }; - } - - const projectRecentMatch = uri.match( - /^agentmemory:\/\/project\/(.+)\/recent$/, - ); - if (projectRecentMatch) { - let projectName: string; - try { - projectName = decodeURIComponent(projectRecentMatch[1]); - } catch { - return { - status_code: 400, - body: { error: "Invalid percent-encoding in URI" }, - }; - } - const summaries = await kv.list<SessionSummary>(KV.summaries); - const filtered = summaries - .filter((s) => s.project === projectName) - .sort( - (a, b) => - new Date(b.createdAt).getTime() - - new Date(a.createdAt).getTime(), - ) - .slice(0, 5); - return { - status_code: 200, - body: { - contents: [ - { - uri, - mimeType: "application/json", - text: JSON.stringify(filtered), - }, - ], - }, - }; - } - - if (uri === "agentmemory://memories/latest") { - const memories = await kv.list<Memory>(KV.memories); - const latest = memories - .filter((m) => m.isLatest) - .sort( - (a, b) => - new Date(b.updatedAt).getTime() - - new Date(a.updatedAt).getTime(), - ) - .slice(0, 10) - .map((m) => ({ - id: m.id, - title: m.title, - type: m.type, - strength: m.strength, - })); - return { - status_code: 200, - body: { - contents: [ - { - uri, - mimeType: "application/json", - text: JSON.stringify(latest), - }, - ], - }, - }; - } - - if (uri === "agentmemory://graph/stats") { - try { - const nodes = await kv.list<GraphNode>(KV.graphNodes); - const edges = await kv.list<GraphEdge>(KV.graphEdges); - const nodesByType: Record<string, number> = {}; - for (const n of nodes) - nodesByType[n.type] = (nodesByType[n.type] || 0) + 1; - const edgesByType: Record<string, number> = {}; - for (const e of edges) - edgesByType[e.type] = (edgesByType[e.type] || 0) + 1; - return { - status_code: 200, - body: { - contents: [ - { - uri, - mimeType: "application/json", - text: JSON.stringify({ - totalNodes: nodes.length, - totalEdges: edges.length, - nodesByType, - edgesByType, - }), - }, - ], - }, - }; - } catch { - return { - status_code: 200, - body: { - contents: [ - { - uri, - mimeType: "application/json", - text: JSON.stringify({ - totalNodes: 0, - totalEdges: 0, - }), - }, - ], - }, - }; - } - } - - const teamProfileMatch = uri.match( - /^agentmemory:\/\/team\/(.+)\/profile$/, - ); - if (teamProfileMatch) { - try { - const teamId = decodeURIComponent(teamProfileMatch[1]); - const items = await kv.list(KV.teamShared(teamId)); - return { - status_code: 200, - body: { - contents: [ - { - uri, - mimeType: "application/json", - text: JSON.stringify({ - teamId, - sharedItems: items.length, - }), - }, - ], - }, - }; - } catch { - return { - status_code: 200, - body: { - contents: [ - { - uri, - mimeType: "application/json", - text: JSON.stringify({ - teamId: teamProfileMatch[1], - sharedItems: 0, - }), - }, - ], - }, - }; - } - } - - return { - status_code: 404, - body: { error: `Unknown resource: ${uri}` }, - }; - } catch { - return { status_code: 500, body: { error: "Internal error" } }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "mcp::resources::read", - config: { - api_path: "/agentmemory/mcp/resources/read", - http_method: "POST", - }, - }); - - const MCP_PROMPTS = [ - { - name: "recall_context", - description: - "Search observations and memories to build context for a task", - arguments: [ - { - name: "task_description", - description: "What you are working on", - required: true, - }, - ], - }, - { - name: "session_handoff", - description: - "Generate a handoff summary for continuing work in a new session", - arguments: [ - { - name: "session_id", - description: "Session ID to hand off from", - required: true, - }, - ], - }, - { - name: "detect_patterns", - description: "Detect recurring patterns across sessions for a project", - arguments: [ - { - name: "project", - description: "Project path to analyze (optional)", - required: false, - }, - ], - }, - ]; - - sdk.registerFunction("mcp::prompts::list", - async (req: ApiRequest): Promise<McpResponse> => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - return { status_code: 200, body: { prompts: MCP_PROMPTS } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "mcp::prompts::list", - config: { api_path: "/agentmemory/mcp/prompts", http_method: "GET" }, - }); - - sdk.registerFunction("mcp::prompts::get", - async ( - req: ApiRequest<{ name: string; arguments?: Record<string, string> }>, - ): Promise<McpResponse> => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - - const promptName = req.body?.name; - if (!promptName || typeof promptName !== "string") { - return { status_code: 400, body: { error: "name is required" } }; - } - - const promptArgs = req.body?.arguments || {}; - - try { - switch (promptName) { - case "recall_context": { - const taskDesc = promptArgs.task_description; - if (typeof taskDesc !== "string" || !taskDesc.trim()) { - return { - status_code: 400, - body: { - error: - "task_description argument is required and must be a string", - }, - }; - } - const searchResult = await sdk - .trigger({ - function_id: "mem::search", - payload: { query: taskDesc, limit: 10 }, - }) - .catch(() => ({ results: [] })); - const memories = await kv.list<Memory>(KV.memories); - const relevant = memories.filter((m) => m.isLatest).slice(0, 5); - return { - status_code: 200, - body: { - messages: [ - { - role: "user", - content: { - type: "text", - text: `Here is relevant context from past sessions for the task: "${taskDesc}"\n\n## Past Observations\n${JSON.stringify(searchResult, null, 2)}\n\n## Relevant Memories\n${JSON.stringify(relevant, null, 2)}`, - }, - }, - ], - }, - }; - } - - case "session_handoff": { - const sessionId = promptArgs.session_id; - if (typeof sessionId !== "string" || !sessionId.trim()) { - return { - status_code: 400, - body: { - error: "session_id argument is required and must be a string", - }, - }; - } - const session = await kv.get<Session>(KV.sessions, sessionId); - const summaries = await kv.list<SessionSummary>(KV.summaries); - const summary = summaries.find((s) => s.sessionId === sessionId); - return { - status_code: 200, - body: { - messages: [ - { - role: "user", - content: { - type: "text", - text: `## Session Handoff\n\n### Session\n${JSON.stringify(session, null, 2)}\n\n### Summary\n${JSON.stringify(summary || "No summary available", null, 2)}`, - }, - }, - ], - }, - }; - } - - case "detect_patterns": { - if ( - promptArgs.project !== undefined && - typeof promptArgs.project !== "string" - ) { - return { - status_code: 400, - body: { error: "project argument must be a string" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::patterns", payload: { - project: promptArgs.project || undefined, - } }); - return { - status_code: 200, - body: { - messages: [ - { - role: "user", - content: { - type: "text", - text: `## Pattern Analysis\n\n${JSON.stringify(result, null, 2)}`, - }, - }, - ], - }, - }; - } - - default: - return { - status_code: 400, - body: { error: `Unknown prompt: ${promptName}` }, - }; - } - } catch { - return { status_code: 500, body: { error: "Internal error" } }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "mcp::prompts::get", - config: { api_path: "/agentmemory/mcp/prompts/get", http_method: "POST" }, - }); -} diff --git a/src/mcp/standalone.ts b/src/mcp/standalone.ts deleted file mode 100644 index f4683747..00000000 --- a/src/mcp/standalone.ts +++ /dev/null @@ -1,500 +0,0 @@ -#!/usr/bin/env node - -import { InMemoryKV } from "./in-memory-kv.js"; -import { createStdioTransport } from "./transport.js"; -import { getAllTools } from "./tools-registry.js"; -import { getStandalonePersistPath } from "../config.js"; -import { VERSION } from "../version.js"; -import { generateId } from "../state/schema.js"; -import { - resolveHandle, - invalidateHandle, - type Handle, - type ProxyHandle, -} from "./rest-proxy.js"; - -const IMPLEMENTED_TOOLS = new Set([ - "memory_save", - "memory_recall", - "memory_smart_search", - "memory_sessions", - "memory_export", - "memory_audit", - "memory_governance_delete", -]); - -const SERVER_INFO = { - name: "agentmemory", - version: VERSION, - protocolVersion: "2024-11-05", -}; - -const kv = new InMemoryKV(getStandalonePersistPath()); -let modeAnnounced = false; - -function displayAgentmemoryUrl(): string { - // Match the literal-placeholder guard in rest-proxy.ts so log lines - // don't show `${AGENTMEMORY_URL}` when an MCP host passed the - // placeholder through unexpanded. - const raw = process.env["AGENTMEMORY_URL"]; - if (!raw || (raw.startsWith("${") && raw.endsWith("}"))) { - return "http://localhost:3111"; - } - return raw; -} - -function announceMode(handle: Handle): void { - if (modeAnnounced) return; - modeAnnounced = true; - if (handle.mode === "proxy") { - process.stderr.write( - `[@agentmemory/mcp] proxying to agentmemory server at ${handle.baseUrl}\n`, - ); - } else { - process.stderr.write( - `[@agentmemory/mcp] no server reachable at ${displayAgentmemoryUrl()}; falling back to local InMemoryKV\n`, - ); - } -} - -function normalizeList(value: unknown): string[] { - if (!value) return []; - if (Array.isArray(value)) { - return value - .map((v) => (typeof v === "string" ? v.trim() : "")) - .filter((v) => v.length > 0); - } - if (typeof value === "string") { - return value - .split(",") - .map((s) => s.trim()) - .filter((s) => s.length > 0); - } - return []; -} - -const DEFAULT_LIMIT = 10; -const MAX_LIMIT = 100; -function parseLimit(raw: unknown, fallback = DEFAULT_LIMIT): number { - if (typeof raw !== "number" && typeof raw !== "string") return fallback; - const n = Number(raw); - if (!Number.isFinite(n) || n <= 0) return fallback; - return Math.min(Math.floor(n), MAX_LIMIT); -} - -function textResponse(payload: unknown, pretty = false): { - content: Array<{ type: string; text: string }>; -} { - return { - content: [ - { type: "text", text: JSON.stringify(payload, null, pretty ? 2 : 0) }, - ], - }; -} - -interface Validated { - tool: string; - content?: string; - type?: string; - concepts?: string[]; - files?: string[]; - query?: string; - limit?: number; - format?: string; - tokenBudget?: number; - memoryIds?: string[]; - reason?: string; -} - -function validate(toolName: string, args: Record<string, unknown>): Validated { - if (!IMPLEMENTED_TOOLS.has(toolName)) { - throw new Error(`Unknown tool: ${toolName}`); - } - const v: Validated = { tool: toolName }; - switch (toolName) { - case "memory_save": { - const content = args["content"]; - if (typeof content !== "string" || !content.trim()) { - throw new Error("content is required"); - } - v.content = content; - v.type = (args["type"] as string) || "fact"; - v.concepts = normalizeList(args["concepts"]); - v.files = normalizeList(args["files"]); - return v; - } - case "memory_recall": - case "memory_smart_search": { - const query = args["query"]; - if (typeof query !== "string" || !query.trim()) { - throw new Error("query is required"); - } - v.query = query.trim(); - v.limit = parseLimit(args["limit"]); - const fmt = args["format"]; - if (typeof fmt === "string" && fmt.trim()) { - v.format = fmt.trim().toLowerCase(); - } - const budget = args["token_budget"]; - if (typeof budget === "number" && Number.isFinite(budget) && budget > 0) { - v.tokenBudget = Math.floor(budget); - } else if (typeof budget === "string" && budget.trim()) { - const n = Number(budget); - if (Number.isFinite(n) && n > 0) v.tokenBudget = Math.floor(n); - } - return v; - } - case "memory_sessions": { - v.limit = parseLimit(args["limit"], 20); - return v; - } - case "memory_governance_delete": { - const ids = normalizeList(args["memoryIds"]); - if (ids.length === 0) throw new Error("memoryIds is required"); - v.memoryIds = ids; - v.reason = (args["reason"] as string) || "plugin skill request"; - return v; - } - case "memory_export": - return v; - case "memory_audit": { - v.limit = parseLimit(args["limit"], 50); - return v; - } - default: - throw new Error(`Unknown tool: ${toolName}`); - } -} - -async function handleProxy( - v: Validated, - handle: ProxyHandle, -): Promise<{ content: Array<{ type: string; text: string }> }> { - switch (v.tool) { - case "memory_save": { - const result = await handle.call("/agentmemory/remember", { - method: "POST", - body: JSON.stringify({ - content: v.content, - type: v.type, - concepts: v.concepts, - files: v.files, - }), - }); - return textResponse(result); - } - case "memory_recall": { - const body: Record<string, unknown> = { - query: v.query, - limit: v.limit, - format: v.format ?? "full", - }; - if (v.tokenBudget != null) body["token_budget"] = v.tokenBudget; - const result = await handle.call("/agentmemory/search", { - method: "POST", - body: JSON.stringify(body), - }); - return textResponse(result, true); - } - case "memory_smart_search": { - const body: Record<string, unknown> = { query: v.query, limit: v.limit }; - if (v.format != null) body["format"] = v.format; - if (v.tokenBudget != null) body["token_budget"] = v.tokenBudget; - const result = await handle.call("/agentmemory/smart-search", { - method: "POST", - body: JSON.stringify(body), - }); - return textResponse(result, true); - } - case "memory_sessions": { - const result = await handle.call( - `/agentmemory/sessions?limit=${v.limit}`, - { method: "GET" }, - ); - return textResponse(result, true); - } - case "memory_governance_delete": { - const result = await handle.call("/agentmemory/governance/memories", { - method: "DELETE", - body: JSON.stringify({ memoryIds: v.memoryIds, reason: v.reason }), - }); - return textResponse(result); - } - case "memory_export": { - const result = await handle.call("/agentmemory/export", { method: "GET" }); - return textResponse(result, true); - } - case "memory_audit": { - const result = await handle.call( - `/agentmemory/audit?limit=${v.limit}`, - { method: "GET" }, - ); - return textResponse(result, true); - } - default: - throw new Error(`Unknown tool: ${v.tool}`); - } -} - -async function handleLocal( - v: Validated, - kvInstance: InMemoryKV, -): Promise<{ content: Array<{ type: string; text: string }> }> { - switch (v.tool) { - case "memory_save": { - const id = generateId("mem"); - const isoNow = new Date().toISOString(); - await kvInstance.set("mem:memories", id, { - id, - type: v.type, - title: (v.content || "").slice(0, 80), - content: v.content, - concepts: v.concepts, - files: v.files, - createdAt: isoNow, - updatedAt: isoNow, - strength: 7, - version: 1, - isLatest: true, - sessionIds: [], - }); - kvInstance.persist(); - return textResponse({ saved: id }); - } - - case "memory_recall": - case "memory_smart_search": { - const query = (v.query || "").toLowerCase(); - const limit = v.limit ?? DEFAULT_LIMIT; - const all = - await kvInstance.list<Record<string, unknown>>("mem:memories"); - const results = all - .filter((m) => { - const text = [ - typeof m["title"] === "string" ? m["title"] : "", - typeof m["content"] === "string" ? m["content"] : "", - Array.isArray(m["files"]) ? m["files"].join(" ") : "", - Array.isArray(m["concepts"]) ? m["concepts"].join(" ") : "", - Array.isArray(m["sessionIds"]) ? m["sessionIds"].join(" ") : "", - typeof m["id"] === "string" ? m["id"] : "", - ] - .join(" ") - .toLowerCase(); - return query.split(/\s+/).every((word) => text.includes(word)); - }) - .slice(0, limit); - return textResponse({ mode: "compact", results }, true); - } - - case "memory_sessions": { - const sessions = - await kvInstance.list<Record<string, unknown>>("mem:sessions"); - const limit = v.limit ?? 20; - return textResponse({ sessions: sessions.slice(0, limit) }, true); - } - - case "memory_governance_delete": { - let deleted = 0; - for (const id of v.memoryIds || []) { - const existing = await kvInstance.get("mem:memories", id); - if (existing) { - await kvInstance.delete("mem:memories", id); - deleted++; - } - } - kvInstance.persist(); - return textResponse({ - deleted, - requested: (v.memoryIds || []).length, - reason: v.reason, - }); - } - - case "memory_export": { - const memories = await kvInstance.list("mem:memories"); - const sessions = await kvInstance.list("mem:sessions"); - return textResponse({ version: VERSION, memories, sessions }, true); - } - - case "memory_audit": { - const entries = await kvInstance.list("mem:audit"); - const limit = v.limit ?? 50; - return textResponse( - { - entries: (entries as Array<Record<string, unknown>>).slice(0, limit), - }, - true, - ); - } - - default: - throw new Error(`Unknown tool: ${v.tool}`); - } -} - -async function handleProxyGeneric( - toolName: string, - args: Record<string, unknown>, - handle: ProxyHandle, -): Promise<{ content: Array<{ type: string; text: string }> }> { - // Forward to the server's full MCP surface so non-Claude clients can - // reach all 51 tools (lessons, sentinels, slots, signals, graph, …) - // instead of being capped at the 7 IMPLEMENTED_TOOLS set baked into - // this shim. The server validates arguments per tool. - const result = (await handle.call("/agentmemory/mcp/call", { - method: "POST", - body: JSON.stringify({ name: toolName, arguments: args }), - })) as { content?: Array<{ type: string; text: string }> } | null; - if (result && Array.isArray(result.content)) { - return { content: result.content }; - } - return textResponse(result, true); -} - -export async function handleToolCall( - toolName: string, - args: Record<string, unknown>, - kvInstance: InMemoryKV = kv, -): Promise<{ content: Array<{ type: string; text: string }> }> { - const handle = await resolveHandle(); - announceMode(handle); - - // Tools the local InMemoryKV fallback doesn't implement: forward straight - // to the server. Local validation would otherwise raise "Unknown tool" - // (issue #234). - if (!IMPLEMENTED_TOOLS.has(toolName)) { - if (handle.mode === "proxy") { - try { - return await handleProxyGeneric(toolName, args, handle); - } catch (err) { - process.stderr.write( - `[@agentmemory/mcp] proxy call failed for ${toolName}: ${err instanceof Error ? err.message : String(err)}\n`, - ); - invalidateHandle(); - throw err; - } - } - throw new Error( - `Unknown tool: ${toolName} (local fallback supports only ${[...IMPLEMENTED_TOOLS].join(", ")}; start an agentmemory server and set AGENTMEMORY_URL to use the full tool set)`, - ); - } - - const validated = validate(toolName, args); - if (handle.mode === "proxy") { - try { - return await handleProxy(validated, handle); - } catch (err) { - process.stderr.write( - `[@agentmemory/mcp] proxy call failed for ${toolName}: ${err instanceof Error ? err.message : String(err)}; invalidating handle and falling back to local KV\n`, - ); - invalidateHandle(); - } - } - return handleLocal(validated, kvInstance); -} - -export async function handleToolsList(): Promise<{ tools: unknown[] }> { - const debug = process.env["AGENTMEMORY_DEBUG"] === "1" || process.env["AGENTMEMORY_DEBUG"] === "true"; - const handle = await resolveHandle(); - announceMode(handle); - if (debug) { - process.stderr.write( - `[@agentmemory/mcp] tools/list: handle.mode=${handle.mode}${handle.mode === "proxy" ? ` baseUrl=${handle.baseUrl}` : ""}\n`, - ); - } - if (handle.mode === "proxy") { - try { - const remote = (await handle.call("/agentmemory/mcp/tools", { - method: "GET", - })) as { tools?: unknown } | null; - if (debug) { - const shape = remote === null - ? "null" - : typeof remote !== "object" - ? typeof remote - : `keys=${Object.keys(remote as object).join(",")} toolsType=${Array.isArray((remote as { tools?: unknown }).tools) ? `array(len=${((remote as { tools: unknown[] }).tools).length})` : typeof (remote as { tools?: unknown }).tools}`; - process.stderr.write( - `[@agentmemory/mcp] tools/list: remote response shape: ${shape}\n`, - ); - } - if (remote && Array.isArray(remote.tools)) { - if (debug) { - process.stderr.write( - `[@agentmemory/mcp] tools/list: returning ${remote.tools.length} tools from server\n`, - ); - } - return { tools: remote.tools }; - } - process.stderr.write( - `[@agentmemory/mcp] tools/list: server returned unexpected shape (no .tools array); falling back to local IMPLEMENTED_TOOLS list. Set AGENTMEMORY_DEBUG=1 to inspect response.\n`, - ); - } catch (err) { - process.stderr.write( - `[@agentmemory/mcp] tools/list proxy failed: ${err instanceof Error ? err.message : String(err)}; falling back to local list\n`, - ); - invalidateHandle(); - } - } - const fallback = getAllTools().filter((t) => IMPLEMENTED_TOOLS.has(t.name)); - if (debug) { - process.stderr.write( - `[@agentmemory/mcp] tools/list: returning ${fallback.length} local fallback tools (${fallback.map((t) => t.name).join(",")})\n`, - ); - } - return { tools: fallback }; -} - -const transport = createStdioTransport(async (method, params) => { - switch (method) { - case "initialize": - return { - protocolVersion: SERVER_INFO.protocolVersion, - capabilities: { tools: { listChanged: false } }, - serverInfo: { - name: SERVER_INFO.name, - version: SERVER_INFO.version, - }, - }; - - case "notifications/initialized": - return {}; - - case "tools/list": - return handleToolsList(); - - case "tools/call": { - const toolName = params.name as string; - const toolArgs = (params.arguments as Record<string, unknown>) || {}; - try { - return await handleToolCall(toolName, toolArgs); - } catch (err) { - return { - content: [ - { - type: "text", - text: `Error: ${err instanceof Error ? err.message : String(err)}`, - }, - ], - isError: true, - }; - } - } - - default: - throw new Error(`Unknown method: ${method}`); - } -}); - -process.stderr.write( - `[@agentmemory/mcp] Standalone MCP server v${SERVER_INFO.version} starting...\n`, -); -transport.start(); - -process.on("SIGINT", () => { - kv.persist(); - process.exit(0); -}); -process.on("SIGTERM", () => { - kv.persist(); - process.exit(0); -}); diff --git a/src/mcp/stdio.ts b/src/mcp/stdio.ts new file mode 100644 index 00000000..85bcb672 --- /dev/null +++ b/src/mcp/stdio.ts @@ -0,0 +1,71 @@ +import { createInterface } from "node:readline"; +import type { Database } from "../db/index.js"; +import { openDatabase } from "../db/index.js"; +import { getAppPaths } from "../paths.js"; +import { callMcpTool, listMcpTools } from "./index.js"; + +interface JsonRpcRequest { + id?: string | number | null; + method?: string; + params?: any; +} + +export async function runMcpStdio(): Promise<void> { + const paths = getAppPaths(); + const db = openDatabase(paths); + const rl = createInterface({ input: process.stdin, crlfDelay: Infinity }); + + try { + for await (const line of rl) { + if (!line.trim()) continue; + const response = await handleJsonRpcLine(db, line, paths); + if (response) process.stdout.write(`${JSON.stringify(response)}\n`); + } + } finally { + db.close(); + } +} + +export async function handleJsonRpcLine(db: Database, line: string, paths = getAppPaths()): Promise<unknown | null> { + let request: JsonRpcRequest; + try { + request = JSON.parse(line) as JsonRpcRequest; + } catch { + return errorResponse(null, -32700, "Parse error"); + } + + if (request.method === "notifications/initialized") return null; + + try { + if (request.method === "initialize") { + return successResponse(request.id, { + protocolVersion: "2024-11-05", + capabilities: { tools: {} }, + serverInfo: { name: "ai-todo", version: "0.1.0" } + }); + } + + if (request.method === "tools/list") { + return successResponse(request.id, { tools: listMcpTools() }); + } + + if (request.method === "tools/call") { + const result = await callMcpTool(db, request.params?.name, request.params?.arguments ?? {}, paths); + return successResponse(request.id, { + content: [{ type: "text", text: JSON.stringify(result) }] + }); + } + + return errorResponse(request.id, -32601, "Method not found"); + } catch (error) { + return errorResponse(request.id, -32000, (error as Error).message); + } +} + +function successResponse(id: JsonRpcRequest["id"], result: unknown) { + return { jsonrpc: "2.0", id: id ?? null, result }; +} + +function errorResponse(id: JsonRpcRequest["id"], code: number, message: string) { + return { jsonrpc: "2.0", id: id ?? null, error: { code, message } }; +} diff --git a/src/mcp/tools-registry.ts b/src/mcp/tools-registry.ts deleted file mode 100644 index b957330b..00000000 --- a/src/mcp/tools-registry.ts +++ /dev/null @@ -1,1006 +0,0 @@ -export type McpToolDef = { - name: string; - description: string; - inputSchema: { - type: "object"; - properties: Record<string, { type: string; description: string }>; - required?: string[]; - }; -}; - -export const CORE_TOOLS: McpToolDef[] = [ - { - name: "memory_recall", - description: - "Search past session observations for relevant context. Use when you need to recall what happened in previous sessions, find past decisions, or look up how a file was modified before.", - inputSchema: { - type: "object", - properties: { - query: { - type: "string", - description: "Search query (keywords, file names, concepts)", - }, - limit: { - type: "number", - description: "Max results to return (default 10)", - }, - format: { - type: "string", - description: "Result format: full, compact, or narrative (default full)", - }, - token_budget: { - type: "number", - description: "Optional token budget to trim returned results", - }, - }, - required: ["query"], - }, - }, - { - name: "memory_compress_file", - description: - "Compress a markdown file to reduce token usage while preserving headings, URLs, and code blocks. Creates a .original.md backup before writing.", - inputSchema: { - type: "object", - properties: { - filePath: { - type: "string", - description: "Path to the markdown file to compress", - }, - }, - required: ["filePath"], - }, - }, - { - name: "memory_save", - description: - "Explicitly save an important insight, decision, or pattern to long-term memory.", - inputSchema: { - type: "object", - properties: { - content: { - type: "string", - description: "The insight or decision to remember", - }, - type: { - type: "string", - description: - "Memory type: pattern, preference, architecture, bug, workflow, or fact", - }, - concepts: { - type: "string", - description: "Comma-separated key concepts", - }, - files: { - type: "string", - description: "Comma-separated relevant file paths", - }, - project: { - type: "string", - description: - "Stable canonical project identifier this memory belongs to (e.g. a slug, " + - "UUID, or registry key). Must match the value used when the session was " + - "started. Do not use filesystem paths or ad-hoc display names — those " + - "change across machines and will silently break project scoping.", - }, - }, - required: ["content"], - }, - }, - { - name: "memory_file_history", - description: "Get past observations about specific files.", - inputSchema: { - type: "object", - properties: { - files: { type: "string", description: "Comma-separated file paths" }, - sessionId: { - type: "string", - description: "Current session ID to exclude", - }, - }, - required: ["files"], - }, - }, - { - name: "memory_patterns", - description: "Detect recurring patterns across sessions.", - inputSchema: { - type: "object", - properties: { - project: { type: "string", description: "Project path to analyze" }, - }, - }, - }, - { - name: "memory_sessions", - description: - "List recent sessions with their status and observation counts.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "memory_smart_search", - description: "Hybrid semantic+keyword search with progressive disclosure.", - inputSchema: { - type: "object", - properties: { - query: { type: "string", description: "Search query" }, - expandIds: { - type: "string", - description: "Comma-separated observation IDs to expand", - }, - limit: { type: "number", description: "Max results (default 10)" }, - }, - required: ["query"], - }, - }, - { - name: "memory_vision_search", - description: - "Cross-modal image search via CLIP embeddings. Pass queryText to find screenshots matching a description, or queryImageBase64/queryImageRef to find similar images. Requires AGENTMEMORY_IMAGE_EMBEDDINGS=true.", - inputSchema: { - type: "object", - properties: { - queryText: { type: "string", description: "Text query (e.g. 'login form with error banner')" }, - queryImageRef: { type: "string", description: "Absolute path to a stored image to match against" }, - queryImageBase64: { type: "string", description: "Raw base64 image bytes or data URL" }, - topK: { type: "number", description: "Max results (default 10, max 50)" }, - sessionId: { type: "string", description: "Filter to a single session" }, - }, - }, - }, - { - name: "memory_timeline", - description: "Chronological observations around an anchor point.", - inputSchema: { - type: "object", - properties: { - anchor: { - type: "string", - description: "Anchor point: ISO date or keyword", - }, - project: { type: "string", description: "Filter by project path" }, - before: { - type: "number", - description: "Observations before anchor (default 5)", - }, - after: { - type: "number", - description: "Observations after anchor (default 5)", - }, - }, - required: ["anchor"], - }, - }, - { - name: "memory_profile", - description: "User/project profile with top concepts and file patterns.", - inputSchema: { - type: "object", - properties: { - project: { type: "string", description: "Project path" }, - refresh: { - type: "string", - description: "Set to 'true' to force rebuild", - }, - }, - required: ["project"], - }, - }, - { - name: "memory_export", - description: "Export all memory data as JSON.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "memory_relations", - description: "Query the memory relationship graph.", - inputSchema: { - type: "object", - properties: { - memoryId: { - type: "string", - description: "Memory ID to find relations for", - }, - maxHops: { - type: "number", - description: "Max traversal depth (default 2)", - }, - minConfidence: { - type: "number", - description: "Min confidence (0-1, default 0)", - }, - }, - required: ["memoryId"], - }, - }, - { - name: "memory_commit_lookup", - description: - "Look up the agent session(s) that produced a specific git commit, given its SHA. Returns the commit metadata and linked sessions.", - inputSchema: { - type: "object", - properties: { - sha: { type: "string", description: "Full git commit SHA" }, - }, - required: ["sha"], - }, - }, - { - name: "memory_commits", - description: - "List recent commits linked to agent sessions, optionally filtered by branch or repo.", - inputSchema: { - type: "object", - properties: { - branch: { type: "string", description: "Filter by branch name" }, - repo: { type: "string", description: "Filter by remote URL" }, - limit: { type: "number", description: "Max results (default 100, max 500)" }, - }, - }, - }, -]; - -export const V040_TOOLS: McpToolDef[] = [ - { - name: "memory_claude_bridge_sync", - description: - "Sync memory state to/from Claude Code's native MEMORY.md file.", - inputSchema: { - type: "object", - properties: { - direction: { - type: "string", - description: - "'read' to import from MEMORY.md, 'write' to export to MEMORY.md", - }, - }, - required: ["direction"], - }, - }, - { - name: "memory_graph_query", - description: "Query the knowledge graph for entities and relationships.", - inputSchema: { - type: "object", - properties: { - startNodeId: { - type: "string", - description: "Starting node ID for traversal", - }, - nodeType: { type: "string", description: "Filter by node type" }, - maxDepth: { - type: "number", - description: "Max BFS depth (default 3, max 5)", - }, - query: { type: "string", description: "Search nodes by name" }, - }, - }, - }, - { - name: "memory_consolidate", - description: - "Run the 4-tier memory consolidation pipeline (working -> episodic -> semantic -> procedural).", - inputSchema: { - type: "object", - properties: { - tier: { - type: "string", - description: "Target tier: episodic, semantic, or procedural", - }, - }, - }, - }, - { - name: "memory_team_share", - description: "Share a memory or observation with team members.", - inputSchema: { - type: "object", - properties: { - itemId: { - type: "string", - description: "ID of memory or observation to share", - }, - itemType: { - type: "string", - description: "Type: observation, memory, or pattern", - }, - }, - required: ["itemId", "itemType"], - }, - }, - { - name: "memory_team_feed", - description: "Get recent shared items from all team members.", - inputSchema: { - type: "object", - properties: { - limit: { type: "number", description: "Max items (default 20)" }, - }, - }, - }, - { - name: "memory_audit", - description: "View the audit trail of memory operations.", - inputSchema: { - type: "object", - properties: { - operation: { type: "string", description: "Filter by operation type" }, - limit: { type: "number", description: "Max entries (default 50)" }, - }, - }, - }, - { - name: "memory_governance_delete", - description: "Delete specific memories with audit trail.", - inputSchema: { - type: "object", - properties: { - memoryIds: { - type: "string", - description: "Comma-separated memory IDs to delete", - }, - reason: { type: "string", description: "Reason for deletion" }, - }, - required: ["memoryIds"], - }, - }, - { - name: "memory_snapshot_create", - description: "Create a git-versioned snapshot of current memory state.", - inputSchema: { - type: "object", - properties: { - message: { type: "string", description: "Snapshot description" }, - }, - }, - }, -]; - -export const V050_TOOLS: McpToolDef[] = [ - { - name: "memory_action_create", - description: - "Create an actionable work item with typed dependencies. Actions track what agents need to do and how work items relate to each other.", - inputSchema: { - type: "object", - properties: { - title: { type: "string", description: "Action title" }, - description: { - type: "string", - description: "Detailed description of the work", - }, - priority: { - type: "number", - description: "Priority 1-10 (10 highest)", - }, - project: { type: "string", description: "Project path" }, - tags: { - type: "string", - description: "Comma-separated tags", - }, - parentId: { - type: "string", - description: "Parent action ID for hierarchical actions", - }, - requires: { - type: "string", - description: - "Comma-separated action IDs that must complete before this", - }, - }, - required: ["title"], - }, - }, - { - name: "memory_action_update", - description: - "Update an action's status, priority, or details. Set status to 'done' to complete it and unblock dependent actions.", - inputSchema: { - type: "object", - properties: { - actionId: { type: "string", description: "Action ID to update" }, - status: { - type: "string", - description: "New status: pending, active, done, blocked, cancelled", - }, - result: { - type: "string", - description: "Outcome description (when completing)", - }, - priority: { type: "number", description: "New priority 1-10" }, - }, - required: ["actionId"], - }, - }, - { - name: "memory_frontier", - description: - "Get all unblocked actions ranked by priority and urgency. Returns the frontier of actionable work with no unsatisfied dependencies.", - inputSchema: { - type: "object", - properties: { - project: { type: "string", description: "Filter by project" }, - agentId: { - type: "string", - description: "Agent ID to check lease conflicts", - }, - limit: { type: "number", description: "Max results (default 20)" }, - }, - }, - }, - { - name: "memory_next", - description: - "Get the single most important next action to work on. Combines dependency resolution, priority, and recency into a score.", - inputSchema: { - type: "object", - properties: { - project: { type: "string", description: "Filter by project" }, - agentId: { type: "string", description: "Current agent ID" }, - }, - }, - }, - { - name: "memory_lease", - description: - "Acquire, release, or renew an exclusive lease on an action. Prevents multiple agents from working on the same thing.", - inputSchema: { - type: "object", - properties: { - actionId: { type: "string", description: "Action ID" }, - agentId: { type: "string", description: "Agent claiming the action" }, - operation: { - type: "string", - description: "acquire, release, or renew", - }, - result: { - type: "string", - description: "Result when releasing (marks action done)", - }, - ttlMs: { - type: "number", - description: "Lease duration in ms (default 10min, max 1hr)", - }, - }, - required: ["actionId", "agentId", "operation"], - }, - }, - { - name: "memory_routine_run", - description: - "Instantiate a frozen workflow routine, creating actions for each step with proper dependencies.", - inputSchema: { - type: "object", - properties: { - routineId: { type: "string", description: "Routine template ID" }, - project: { type: "string", description: "Project context" }, - initiatedBy: { type: "string", description: "Agent starting the run" }, - }, - required: ["routineId"], - }, - }, - { - name: "memory_signal_send", - description: - "Send a message to another agent or broadcast. Supports threading, typed messages, and TTL expiration.", - inputSchema: { - type: "object", - properties: { - from: { type: "string", description: "Sender agent ID" }, - to: { - type: "string", - description: "Recipient agent ID (omit for broadcast)", - }, - content: { type: "string", description: "Message content" }, - type: { - type: "string", - description: "Message type: info, request, response, alert, handoff", - }, - replyTo: { - type: "string", - description: "Signal ID to reply to (auto-threads)", - }, - }, - required: ["from", "content"], - }, - }, - { - name: "memory_signal_read", - description: - "Read messages for an agent. Marks delivered messages as read.", - inputSchema: { - type: "object", - properties: { - agentId: { type: "string", description: "Agent to read messages for" }, - unreadOnly: { - type: "string", - description: "Set to 'true' for unread only", - }, - threadId: { - type: "string", - description: "Filter by conversation thread", - }, - limit: { type: "number", description: "Max messages (default 50)" }, - }, - required: ["agentId"], - }, - }, - { - name: "memory_checkpoint", - description: - "Create or resolve an external checkpoint (CI result, approval, deploy status) that gates action progress.", - inputSchema: { - type: "object", - properties: { - operation: { - type: "string", - description: "create, resolve, or list", - }, - name: { type: "string", description: "Checkpoint name (for create)" }, - checkpointId: { - type: "string", - description: "Checkpoint ID (for resolve)", - }, - status: { - type: "string", - description: "passed or failed (for resolve)", - }, - type: { - type: "string", - description: "Checkpoint type: ci, approval, deploy, external, timer", - }, - linkedActionIds: { - type: "string", - description: - "Comma-separated action IDs this checkpoint gates (for create)", - }, - }, - required: ["operation"], - }, - }, - { - name: "memory_mesh_sync", - description: - "Sync memories and actions with peer agentmemory instances for multi-agent collaboration.", - inputSchema: { - type: "object", - properties: { - peerId: { - type: "string", - description: "Specific peer ID (omit for all)", - }, - direction: { - type: "string", - description: "push, pull, or both (default both)", - }, - }, - }, - }, -]; - -export const V051_TOOLS: McpToolDef[] = [ - { - name: "memory_sentinel_create", - description: - "Create an event-driven sentinel that watches for conditions (webhook, timer, threshold, pattern, approval) and auto-unblocks gated actions when triggered.", - inputSchema: { - type: "object", - properties: { - name: { type: "string", description: "Sentinel name" }, - type: { - type: "string", - description: "Type: webhook, timer, threshold, pattern, approval, custom", - }, - config: { - type: "string", - description: "JSON config (timer: {durationMs}, threshold: {metric,operator,value}, pattern: {pattern}, webhook: {path})", - }, - linkedActionIds: { - type: "string", - description: "Comma-separated action IDs to gate", - }, - expiresInMs: { type: "number", description: "Auto-expire after ms" }, - }, - required: ["name", "type"], - }, - }, - { - name: "memory_sentinel_trigger", - description: - "Externally fire a sentinel, providing an optional result payload. Unblocks any gated actions.", - inputSchema: { - type: "object", - properties: { - sentinelId: { type: "string", description: "Sentinel ID to trigger" }, - result: { type: "string", description: "JSON result payload" }, - }, - required: ["sentinelId"], - }, - }, - { - name: "memory_sketch_create", - description: - "Create an ephemeral action graph for exploratory work. Auto-expires after TTL. Can be promoted to permanent actions or discarded.", - inputSchema: { - type: "object", - properties: { - title: { type: "string", description: "Sketch title" }, - description: { type: "string", description: "What this sketch explores" }, - expiresInMs: { type: "number", description: "TTL in ms (default 1 hour)" }, - project: { type: "string", description: "Project context" }, - }, - required: ["title"], - }, - }, - { - name: "memory_sketch_promote", - description: - "Promote a sketch's ephemeral actions to permanent actions. Makes the exploratory work official.", - inputSchema: { - type: "object", - properties: { - sketchId: { type: "string", description: "Sketch ID to promote" }, - project: { type: "string", description: "Override project for promoted actions" }, - }, - required: ["sketchId"], - }, - }, - { - name: "memory_crystallize", - description: - "Compress completed action chains into compact crystal digests using LLM summarization. Extracts narrative, key outcomes, files affected, and lessons.", - inputSchema: { - type: "object", - properties: { - actionIds: { - type: "string", - description: "Comma-separated completed action IDs to crystallize", - }, - project: { type: "string", description: "Project context" }, - sessionId: { type: "string", description: "Session context" }, - }, - required: ["actionIds"], - }, - }, - { - name: "memory_diagnose", - description: - "Run health checks across all subsystems (actions, leases, sentinels, sketches, signals, sessions, memories, mesh). Identifies stuck, orphaned, and inconsistent state.", - inputSchema: { - type: "object", - properties: { - categories: { - type: "string", - description: "Comma-separated categories to check (default all)", - }, - }, - }, - }, - { - name: "memory_heal", - description: - "Auto-fix all fixable issues found by diagnostics. Unblocks stuck actions, expires stale leases, cleans up orphaned data.", - inputSchema: { - type: "object", - properties: { - categories: { - type: "string", - description: "Comma-separated categories to heal (default all)", - }, - dryRun: { - type: "string", - description: "Set to 'true' for dry run (report but don't fix)", - }, - }, - }, - }, - { - name: "memory_facet_tag", - description: - "Attach a structured tag (dimension:value) to an action, memory, or observation for multi-dimensional categorization.", - inputSchema: { - type: "object", - properties: { - targetId: { type: "string", description: "ID of the target to tag" }, - targetType: { - type: "string", - description: "Type: action, memory, or observation", - }, - dimension: { type: "string", description: "Tag dimension (e.g., priority, team, status)" }, - value: { type: "string", description: "Tag value (e.g., urgent, backend, reviewed)" }, - }, - required: ["targetId", "targetType", "dimension", "value"], - }, - }, - { - name: "memory_facet_query", - description: - "Query targets by facet tags with AND/OR logic. Find all actions tagged priority:urgent AND team:backend.", - inputSchema: { - type: "object", - properties: { - matchAll: { - type: "string", - description: "Comma-separated dimension:value pairs (AND logic)", - }, - matchAny: { - type: "string", - description: "Comma-separated dimension:value pairs (OR logic)", - }, - targetType: { - type: "string", - description: "Filter by type: action, memory, or observation", - }, - }, - }, - }, -]; - -export const V061_TOOLS: McpToolDef[] = [ - { - name: "memory_verify", - description: - "Verify a memory or observation by tracing its citation chain back to source observations and session context. Returns provenance information including confidence scores.", - inputSchema: { - type: "object", - properties: { - id: { - type: "string", - description: "Memory ID or observation ID to verify", - }, - }, - required: ["id"], - }, - }, -]; - -export const V070_TOOLS: McpToolDef[] = [ - { - name: "memory_lesson_save", - description: - "Save a lesson learned from this session. Lessons have confidence scores that strengthen when reinforced and decay when not used. Duplicate content auto-strengthens the existing lesson.", - inputSchema: { - type: "object", - properties: { - content: { - type: "string", - description: "The lesson learned (what worked, what to avoid, when to use X approach)", - }, - context: { - type: "string", - description: "When/where this lesson applies", - }, - confidence: { - type: "number", - description: "Initial confidence 0.0-1.0 (default 0.5)", - }, - project: { type: "string", description: "Project this lesson is about" }, - tags: { type: "string", description: "Comma-separated tags" }, - }, - required: ["content"], - }, - }, - { - name: "memory_lesson_recall", - description: - "Search lessons by query. Returns lessons sorted by confidence and recency. Use to check what the agent has learned before making decisions.", - inputSchema: { - type: "object", - properties: { - query: { type: "string", description: "Search query" }, - project: { type: "string", description: "Filter by project" }, - minConfidence: { - type: "number", - description: "Minimum confidence threshold (default 0.1)", - }, - limit: { type: "number", description: "Max results (default 10)" }, - }, - required: ["query"], - }, - }, - { - name: "memory_obsidian_export", - description: - "Export memories, lessons, and crystals as Obsidian-compatible Markdown files with YAML frontmatter and wikilinks for graph view.", - inputSchema: { - type: "object", - properties: { - vaultDir: { - type: "string", - description: "Output directory (default ~/.agentmemory/vault/)", - }, - types: { - type: "string", - description: "Comma-separated types to export: memories,lessons,crystals,sessions (default all)", - }, - }, - }, - }, -]; - -export const V073_TOOLS: McpToolDef[] = [ - { - name: "memory_reflect", - description: - "Traverse the knowledge graph, group related memories by concept clusters, and synthesize higher-order insights via LLM. Returns new and reinforced insights.", - inputSchema: { - type: "object", - properties: { - project: { type: "string", description: "Filter by project" }, - maxClusters: { - type: "number", - description: "Max concept clusters to process (default 10, max 20)", - }, - }, - }, - }, - { - name: "memory_insight_list", - description: - "List synthesized insights — higher-order observations derived from patterns across memories, lessons, and crystals.", - inputSchema: { - type: "object", - properties: { - project: { type: "string", description: "Filter by project" }, - minConfidence: { - type: "number", - description: "Minimum confidence threshold (default 0)", - }, - limit: { type: "number", description: "Max results (default 50)" }, - }, - }, - }, -]; - -export const V010_SLOTS_TOOLS: McpToolDef[] = [ - { - name: "memory_slot_list", - description: - "List all memory slots (pinned + project + global). Slots are editable, size-limited memory units the agent can read and modify across sessions.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "memory_slot_get", - description: "Read a single slot by label.", - inputSchema: { - type: "object", - properties: { - label: { type: "string", description: "Slot label (e.g. 'persona', 'pending_items')" }, - }, - required: ["label"], - }, - }, - { - name: "memory_slot_create", - description: "Create a new slot. Reject if a slot with the same label already exists.", - inputSchema: { - type: "object", - properties: { - label: { type: "string", description: "Slot label — lowercase, starts with letter, [a-z0-9_]" }, - content: { type: "string", description: "Initial content (default empty)" }, - sizeLimit: { type: "number", description: "Max chars (default 2000, hard cap 20000)" }, - description: { type: "string", description: "What this slot is for" }, - pinned: { type: "string", description: "'false' to exclude from context injection; default true" }, - scope: { type: "string", description: "'project' (default) or 'global' (shared across projects)" }, - }, - required: ["label"], - }, - }, - { - name: "memory_slot_append", - description: - "Append text to an existing slot. Fails with 413 if the append would exceed the slot's sizeLimit — agent must compact via memory_slot_replace first.", - inputSchema: { - type: "object", - properties: { - label: { type: "string", description: "Slot label" }, - text: { type: "string", description: "Text to append" }, - }, - required: ["label", "text"], - }, - }, - { - name: "memory_slot_replace", - description: "Replace slot content in place. Fails if content exceeds sizeLimit.", - inputSchema: { - type: "object", - properties: { - label: { type: "string", description: "Slot label" }, - content: { type: "string", description: "New full content" }, - }, - required: ["label", "content"], - }, - }, - { - name: "memory_slot_delete", - description: "Delete a slot. Seeded default slots can be deleted unless marked readOnly.", - inputSchema: { - type: "object", - properties: { - label: { type: "string", description: "Slot label" }, - }, - required: ["label"], - }, - }, -]; - -// Line C: Agent→user async inbox. These are the write-side tools an Agent -// uses to reach the human user; read/answer/dismiss are user actions exposed -// via REST + the viewer, not Agent tools. -export const INBOX_TOOLS: McpToolDef[] = [ - { - name: "memory_inbox_ask", - description: - "Ask the human user a question that needs a reply. Appears in the user's workbench inbox (待回应). Use when you hit a decision only the user can make.", - inputSchema: { - type: "object", - properties: { - body: { type: "string", description: "The question (Markdown allowed)" }, - fromAgent: { type: "string", description: "Your agent/session label" }, - project: { type: "string", description: "Related project path" }, - sourceObservationIds: { - type: "array", - items: { type: "string" }, - description: "Observation IDs the question relates to (for evidence jump)", - }, - }, - required: ["body"], - }, - }, - { - name: "memory_inbox_notify", - description: - "Push a briefing to the human user (ack-only, no reply needed). Use to proactively report what you organized/finished. Appears in the workbench inbox as an Agent briefing.", - inputSchema: { - type: "object", - properties: { - body: { type: "string", description: "The briefing (Markdown allowed)" }, - fromAgent: { type: "string", description: "Your agent/session label" }, - project: { type: "string", description: "Related project path" }, - sourceObservationIds: { - type: "array", - items: { type: "string" }, - description: "Observation IDs the briefing relates to", - }, - }, - required: ["body"], - }, - }, -]; - -const ESSENTIAL_TOOLS = new Set([ - "memory_save", - "memory_recall", - "memory_consolidate", - "memory_smart_search", - "memory_sessions", - "memory_diagnose", - "memory_lesson_save", - "memory_reflect", -]); - -export function getAllTools(): McpToolDef[] { - return [ - ...CORE_TOOLS, - ...V040_TOOLS, - ...V050_TOOLS, - ...V051_TOOLS, - ...V061_TOOLS, - ...V070_TOOLS, - ...V073_TOOLS, - ...V010_SLOTS_TOOLS, - ...INBOX_TOOLS, - ]; -} - -// default switched from "core" (8 essential tools) to "all" -// (full 51-tool surface). README and plugin manifests have always -// advertised 51 tools "in proxy mode"; the old default left OpenCode / -// Claude Code users seeing 8 with no indication the other 43 existed. -// Users who want the lean essentials can still set AGENTMEMORY_TOOLS=core. -export function getVisibleTools(): McpToolDef[] { - const mode = process.env["AGENTMEMORY_TOOLS"] || "all"; - if (mode === "core") return getAllTools().filter((t) => ESSENTIAL_TOOLS.has(t.name)); - return getAllTools(); -} diff --git a/src/mcp/transport.ts b/src/mcp/transport.ts deleted file mode 100644 index 759ed019..00000000 --- a/src/mcp/transport.ts +++ /dev/null @@ -1,263 +0,0 @@ -export interface JsonRpcRequest { - jsonrpc: "2.0"; - id?: string | number; - method: string; - params?: Record<string, unknown>; -} - -export interface JsonRpcResponse { - jsonrpc: "2.0"; - id: string | number | null; - result?: unknown; - error?: { code: number; message: string; data?: unknown }; -} - -export type RequestHandler = ( - method: string, - params: Record<string, unknown>, -) => Promise<unknown>; - -export interface StdioMessageParser { - push: (chunk: Buffer | string) => void; - isFramed: () => boolean; -} - -// JSON-RPC 2.0 notifications are messages without an `id` field. The spec -// (and the MCP transport contract) requires the server to NOT send a -// response for notifications. Some clients tolerate spurious responses; -// stricter clients (e.g. Codex CLI) treat them as protocol violations and -// close the transport. See agentmemory#129. -function isNotification(req: JsonRpcRequest): boolean { - return req.id === undefined || req.id === null; -} - -// Per JSON-RPC 2.0 §4, a valid request id must be a String, Number, or Null -// (Null is technically only allowed in responses; in requests, omitting id -// is the convention for notifications, which we treat the same as null). -// Any other runtime type (object, array, boolean) is an Invalid Request. -function isValidId(id: unknown): id is string | number | null | undefined { - return ( - id === undefined || - id === null || - typeof id === "string" || - typeof id === "number" - ); -} - -// Exported for unit tests so the line-handling logic is exercised -// independently of process.stdin / process.stdout. -export async function processLine( - line: string, - handler: RequestHandler, - writeOut: (response: JsonRpcResponse) => void, - writeErr: (msg: string) => void = (msg) => process.stderr.write(msg), -): Promise<void> { - const trimmed = line.trim(); - if (!trimmed) return; - - let parsed: unknown; - try { - parsed = JSON.parse(trimmed); - } catch { - writeOut({ - jsonrpc: "2.0", - id: null, - error: { code: -32700, message: "Parse error" }, - }); - return; - } - - const request = parsed as JsonRpcRequest; - const rawId = (request as { id?: unknown } | null)?.id; - - // Invalid request shape (missing/wrong jsonrpc, non-string method). - if ( - !request || - typeof request !== "object" || - request.jsonrpc !== "2.0" || - typeof request.method !== "string" - ) { - // Echo the id back only if it's a valid string/number. Notifications - // (missing/null id) and malformed ids both drop silently — we don't - // want to respond to something that could be a notification, and we - // can't invent an id for a malformed one. - if (typeof rawId === "string" || typeof rawId === "number") { - writeOut({ - jsonrpc: "2.0", - id: rawId, - error: { code: -32600, message: "Invalid Request" }, - }); - } - return; - } - - // Request shape is valid but id may still be of the wrong type - // (object, array, boolean). Per the spec, that's an Invalid Request. - // Respond with id: null because we can't safely echo a non-JSON-RPC id. - if (!isValidId(rawId)) { - writeOut({ - jsonrpc: "2.0", - id: null, - error: { code: -32600, message: "Invalid Request: id must be string, number, or null" }, - }); - return; - } - - const notification = isNotification(request); - - try { - const result = await handler(request.method, request.params || {}); - if (notification) return; - writeOut({ - jsonrpc: "2.0", - id: request.id as string | number, - result, - }); - } catch (err) { - if (notification) { - writeErr( - `[mcp-transport] notification handler error for ${request.method}: ${ - err instanceof Error ? err.message : String(err) - }\n`, - ); - return; - } - writeOut({ - jsonrpc: "2.0", - id: request.id as string | number, - error: { - code: -32603, - message: err instanceof Error ? err.message : String(err), - }, - }); - } -} - -function findHeaderEnd(buffer: Buffer): { headerEnd: number; bodyStart: number } | null { - const crlf = buffer.indexOf("\r\n\r\n"); - const lf = buffer.indexOf("\n\n"); - if (crlf === -1 && lf === -1) return null; - if (crlf !== -1 && (lf === -1 || crlf <= lf)) { - return { headerEnd: crlf, bodyStart: crlf + 4 }; - } - return { headerEnd: lf, bodyStart: lf + 2 }; -} - -function parseContentLength(header: string): number | null { - for (const line of header.split(/\r?\n/)) { - const match = line.match(/^content-length:\s*(\d+)\s*$/i); - if (match) return Number(match[1]); - } - return null; -} - -export function formatResponse( - response: JsonRpcResponse, - framed: boolean, -): string | Buffer[] { - const body = JSON.stringify(response); - if (!framed) return `${body}\n`; - const bytes = Buffer.from(body, "utf8"); - return [Buffer.from(`Content-Length: ${bytes.length}\r\n\r\n`, "ascii"), bytes]; -} - -export function createMessageParser( - onMessage: (message: string) => void, - writeErr: (msg: string) => void = (msg) => process.stderr.write(msg), -): StdioMessageParser { - let buffer = Buffer.alloc(0); - let framed = false; - - function processBuffer(): void { - while (buffer.length > 0) { - if (buffer[0] === 10 || buffer[0] === 13) { - buffer = buffer.subarray(1); - continue; - } - - const preview = buffer.toString("ascii", 0, Math.min(buffer.length, 32)); - if (/^content-length:/i.test(preview)) { - const header = findHeaderEnd(buffer); - if (!header) return; - - const headerText = buffer.subarray(0, header.headerEnd).toString("ascii"); - const contentLength = parseContentLength(headerText); - if (contentLength === null) { - writeErr("[mcp-transport] missing Content-Length header\n"); - buffer = buffer.subarray(header.bodyStart); - continue; - } - - const messageEnd = header.bodyStart + contentLength; - if (buffer.length < messageEnd) return; - - framed = true; - const message = buffer.subarray(header.bodyStart, messageEnd).toString("utf8"); - buffer = buffer.subarray(messageEnd); - onMessage(message); - continue; - } - - const newline = buffer.indexOf(10); - if (newline === -1) return; - const line = buffer - .subarray(0, newline) - .toString("utf8") - .replace(/\r$/, ""); - buffer = buffer.subarray(newline + 1); - onMessage(line); - } - } - - return { - push(chunk) { - const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf8"); - buffer = Buffer.concat([buffer, bytes]); - processBuffer(); - }, - isFramed() { - return framed; - }, - }; -} - -export function createStdioTransport(handler: RequestHandler): { - start: () => void; - stop: () => void; -} { - let parser: StdioMessageParser | null = null; - let queue = Promise.resolve(); - - const writeResponse = (response: JsonRpcResponse) => { - const formatted = formatResponse(response, parser?.isFramed() ?? false); - if (typeof formatted === "string") { - process.stdout.write(formatted); - return; - } - for (const chunk of formatted) { - process.stdout.write(chunk); - } - }; - - const onData = (chunk: Buffer) => parser?.push(chunk); - - return { - start() { - parser = createMessageParser((message) => { - queue = queue.then(() => processLine(message, handler, writeResponse)); - void queue.catch((err) => { - process.stderr.write( - `[mcp-transport] request processing failed: ${ - err instanceof Error ? err.message : String(err) - }\n`, - ); - }); - }); - process.stdin.on("data", onData); - }, - stop() { - process.stdin.off("data", onData); - parser = null; - }, - }; -} diff --git a/src/parser/jsonl.ts b/src/parser/jsonl.ts new file mode 100644 index 00000000..1b35baad --- /dev/null +++ b/src/parser/jsonl.ts @@ -0,0 +1,28 @@ +import { readFileSync } from "node:fs"; + +export interface JsonlRecord { + value: Record<string, unknown>; + line: number; +} + +export function parseJsonl(text: string): JsonlRecord[] { + return text + .split(/\r?\n/) + .map((line, index) => ({ line, index: index + 1 })) + .filter(({ line }) => line.trim().length > 0) + .map(({ line, index }) => { + try { + const value = JSON.parse(line); + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error("record is not an object"); + } + return { value, line: index }; + } catch (error) { + throw new Error(`Invalid JSONL at line ${index}: ${(error as Error).message}`); + } + }); +} + +export function readJsonlFile(path: string): JsonlRecord[] { + return parseJsonl(readFileSync(path, "utf8")); +} diff --git a/src/paths.ts b/src/paths.ts new file mode 100644 index 00000000..3a5878a2 --- /dev/null +++ b/src/paths.ts @@ -0,0 +1,23 @@ +import { homedir } from "node:os"; +import { join } from "node:path"; + +export interface AppPaths { + configDir: string; + envPath: string; + configPath: string; + secretsPath: string; + dataDir: string; + dbPath: string; +} + +export function getAppPaths(baseDir = process.env.AI_TODO_HOME ?? join(homedir(), ".ai-todo")): AppPaths { + const dataDir = join(baseDir, "data"); + return { + configDir: baseDir, + envPath: join(baseDir, ".env"), + configPath: join(baseDir, "config.json"), + secretsPath: join(baseDir, "secrets.json"), + dataDir, + dbPath: join(dataDir, "ai-todo.sqlite") + }; +} diff --git a/src/prompts/compression.ts b/src/prompts/compression.ts deleted file mode 100644 index 51183b0c..00000000 --- a/src/prompts/compression.ts +++ /dev/null @@ -1,67 +0,0 @@ -export const COMPRESSION_SYSTEM = `You are a memory compression engine for an AI coding agent. Your job is to extract the essential information from a tool usage observation and compress it into structured data. - -Output EXACTLY this XML format with no additional text: - -<observation> - <type>one of: file_read, file_write, file_edit, command_run, search, web_fetch, conversation, error, decision, discovery, subagent, notification, task, other</type> - <title>Short descriptive title (max 80 chars) - One-line context (optional) - - Specific factual detail 1 - Specific factual detail 2 - - 2-3 sentence summary of what happened and why it matters - - technical concept or pattern - - - path/to/file - - 1-10 scale, 10 being critical architectural decision - - -Rules: -- Be concise but preserve ALL technically relevant details -- File paths must be exact -- Importance: 1-3 for routine reads, 4-6 for edits/commands, 7-9 for architectural decisions, 10 for breaking changes -- Concepts should be reusable search terms (e.g., "React hooks", "SQL migration", "auth middleware") -- Strip any secrets, tokens, or credentials from the output`; - -export function buildCompressionPrompt(observation: { - hookType: string; - toolName?: string; - toolInput?: unknown; - toolOutput?: unknown; - userPrompt?: string; - timestamp: string; -}): string { - const parts = [ - `Timestamp: ${observation.timestamp}`, - `Hook: ${observation.hookType}`, - ]; - - if (observation.toolName) parts.push(`Tool: ${observation.toolName}`); - if (observation.toolInput) { - const input = - typeof observation.toolInput === "string" - ? observation.toolInput - : JSON.stringify(observation.toolInput, null, 2); - parts.push(`Input:\n${truncate(input, 4000)}`); - } - if (observation.toolOutput) { - const output = - typeof observation.toolOutput === "string" - ? observation.toolOutput - : JSON.stringify(observation.toolOutput, null, 2); - parts.push(`Output:\n${truncate(output, 4000)}`); - } - if (observation.userPrompt) { - parts.push(`User prompt:\n${truncate(observation.userPrompt, 2000)}`); - } - - return parts.join("\n\n"); -} - -function truncate(s: string, max: number): string { - return s.length > max ? s.slice(0, max) + "\n[...truncated]" : s; -} diff --git a/src/prompts/consolidation.ts b/src/prompts/consolidation.ts deleted file mode 100644 index 76580b8a..00000000 --- a/src/prompts/consolidation.ts +++ /dev/null @@ -1,48 +0,0 @@ -export const SEMANTIC_MERGE_SYSTEM = `You are a memory consolidation engine. Given overlapping episodic memories (session summaries), extract stable factual knowledge. - -Output format (XML): - - Concise factual statement - - -Rules: -- Extract only facts that appear in 2+ episodes or are highly confident -- Confidence reflects how well-supported the fact is across episodes -- Combine overlapping information into single concise facts -- Skip ephemeral details (specific error messages, temporary states)`; - -export function buildSemanticMergePrompt( - episodes: Array<{ title: string; narrative: string; concepts: string[] }>, -): string { - const items = episodes - .map( - (e, i) => - `[Episode ${i + 1}]\nTitle: ${e.title}\nNarrative: ${e.narrative}\nConcepts: ${e.concepts.join(", ")}`, - ) - .join("\n\n"); - return `Consolidate these episodic memories into stable facts:\n\n${items}`; -} - -export const PROCEDURAL_EXTRACTION_SYSTEM = `You are a procedural memory extractor. Given repeated patterns and workflows observed across sessions, extract reusable procedures. - -Output format (XML): - - - Step 1 description - Step 2 description - - - -Rules: -- Only extract procedures observed 2+ times -- Steps should be concrete and actionable -- Trigger condition should be specific enough to match automatically`; - -export function buildProceduralExtractionPrompt( - patterns: Array<{ content: string; frequency: number }>, -): string { - const items = patterns - .map((p, i) => `[Pattern ${i + 1}] (seen ${p.frequency}x)\n${p.content}`) - .join("\n\n"); - return `Extract reusable procedures from these recurring patterns:\n\n${items}`; -} diff --git a/src/prompts/graph-extraction.ts b/src/prompts/graph-extraction.ts deleted file mode 100644 index 4f1049c1..00000000 --- a/src/prompts/graph-extraction.ts +++ /dev/null @@ -1,35 +0,0 @@ -export const GRAPH_EXTRACTION_SYSTEM = `You are a knowledge graph extraction engine. Given a compressed observation from a coding session, extract entities and relationships. - -Output format (XML): - - - value - - - - - - -Rules: -- Extract concrete entities only (real file paths, function names, library names) -- Use the most specific type available -- Weight relationships by how strong/direct the connection is -- If no entities found, output empty tags`; - -export function buildGraphExtractionPrompt( - observations: Array<{ - title: string; - narrative: string; - concepts: string[]; - files: string[]; - type: string; - }>, -): string { - const items = observations - .map( - (o, i) => - `[${i + 1}] Type: ${o.type}\nTitle: ${o.title}\nNarrative: ${o.narrative}\nConcepts: ${(o.concepts ?? []).join(", ")}\nFiles: ${(o.files ?? []).join(", ")}`, - ) - .join("\n\n"); - return `Extract entities and relationships from these observations:\n\n${items}`; -} diff --git a/src/prompts/reflect.ts b/src/prompts/reflect.ts deleted file mode 100644 index 6de84351..00000000 --- a/src/prompts/reflect.ts +++ /dev/null @@ -1,55 +0,0 @@ -export const REFLECT_SYSTEM = `You are a higher-order reasoning engine. Given a cluster of related concepts, facts, lessons, and action outcomes, synthesize cross-cutting insights that span multiple individual memories. - -Output format (XML): - - - The higher-order observation or principle. Should be actionable and non-obvious — something that only becomes visible when viewing multiple memories together. - - - -Rules: -- Identify patterns, principles, or strategies that span 2+ source items -- Confidence reflects how well-supported the insight is across sources -- Title should be a concise label (under 60 chars) -- Content should be the actual observation (1-3 sentences) -- Prefer actionable insights over abstract summaries -- Skip insights that merely restate a single source item -- Always emit confidence attribute before title attribute`; - -export function buildReflectPrompt(cluster: { - concepts: string[]; - facts: Array<{ fact: string; confidence: number }>; - lessons: Array<{ content: string; confidence: number }>; - crystalNarratives: string[]; -}): string { - const sections: string[] = []; - - sections.push(`## Concept Cluster: ${cluster.concepts.join(", ")}`); - - if (cluster.facts.length > 0) { - sections.push( - "\n## Known Facts", - ...cluster.facts.map( - (f) => `- [confidence=${f.confidence}] ${f.fact}`, - ), - ); - } - - if (cluster.lessons.length > 0) { - sections.push( - "\n## Lessons Learned", - ...cluster.lessons.map( - (l) => `- [confidence=${l.confidence}] ${l.content}`, - ), - ); - } - - if (cluster.crystalNarratives.length > 0) { - sections.push( - "\n## Completed Work Summaries", - ...cluster.crystalNarratives.map((n) => `- ${n}`), - ); - } - - return `Synthesize higher-order insights from this cluster of related memories:\n\n${sections.join("\n")}`; -} diff --git a/src/prompts/summary.ts b/src/prompts/summary.ts deleted file mode 100644 index bd040212..00000000 --- a/src/prompts/summary.ts +++ /dev/null @@ -1,87 +0,0 @@ -export const SUMMARY_SYSTEM = `You are a session summarizer for an AI coding agent's memory system. Given all compressed observations from a coding session, produce a concise session summary. - -Output EXACTLY this XML format with no additional text: - - - Short session title (max 100 chars) - 3-5 sentence narrative of what was accomplished - - Key technical decision made - - - path/to/modified/file - - - key concept from session - - - -Rules: -- Focus on outcomes, not individual tool calls -- Highlight decisions and their rationale -- List all files that were created or modified -- Concepts should be searchable terms for future context retrieval` - -export function buildSummaryPrompt(observations: Array<{ - type: string - title: string - facts: string[] - narrative: string - files: string[] - concepts: string[] -}>): string { - const lines = observations.map((obs, i) => { - const facts = obs.facts.map((f) => ` - ${f}`).join('\n') - return `[${i + 1}] ${obs.type}: ${obs.title}\n${obs.narrative}\nFacts:\n${facts}\nFiles: ${obs.files.join(', ')}` - }) - return `Session observations (${observations.length} total):\n\n${lines.join('\n\n---\n\n')}` -} - -export const REDUCE_SYSTEM = `You are merging multiple partial summaries of the SAME coding session into one final session summary. The partials are chronological chunks of one continuous session — not separate sessions. - -Output EXACTLY this XML format with no additional text: - - - Short session title (max 100 chars) - 3-5 sentence narrative covering the whole session - - Key technical decision made - - - path/to/modified/file - - - key concept from session - - - -Rules: -- Synthesize a single narrative that reflects the whole arc, not a chunk-by-chunk recap -- Preserve every distinct decision across chunks -- Union (deduplicate) all files and concepts -- Title should capture the session's overall outcome` - -export function buildReducePrompt(partials: Array<{ - title: string - narrative: string - keyDecisions: string[] - filesModified: string[] - concepts: string[] - obsRangeStart: number - obsRangeEnd: number -}>): string { - const sections = partials.map((p, i) => { - const decisions = p.keyDecisions.map((d) => ` - ${d}`).join('\n') - const files = p.filesModified.map((f) => ` - ${f}`).join('\n') - const concepts = p.concepts.join(', ') - return `[Chunk ${i + 1} of ${partials.length} — obs ${p.obsRangeStart}-${p.obsRangeEnd}] -Title: ${p.title} -Narrative: ${p.narrative} -Decisions: -${decisions} -Files: -${files} -Concepts: ${concepts}` - }) - return `Partial summaries (${partials.length} chunks of one session, chronological):\n\n${sections.join('\n\n---\n\n')}` -} diff --git a/src/prompts/vision.ts b/src/prompts/vision.ts deleted file mode 100644 index 4472f82f..00000000 --- a/src/prompts/vision.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const VISION_DESCRIPTION_PROMPT = `Describe what this image shows in the context of software development. Extract: -- What type of image this is (screenshot, diagram, mockup, terminal output, error, etc.) -- Key entities visible (files, components, UI elements, error messages) -- Relationships or flow shown -- Any decisions, errors, or state visible -- Text content visible in the image - -Be concise but preserve all technically relevant details. Output plain text, no XML.`; diff --git a/src/prompts/xml.ts b/src/prompts/xml.ts deleted file mode 100644 index ed8f572e..00000000 --- a/src/prompts/xml.ts +++ /dev/null @@ -1,26 +0,0 @@ -const VALID_TAG = /^[a-zA-Z_][a-zA-Z0-9_-]*$/; - -export function getXmlTag(xml: string, tag: string): string { - if (!VALID_TAG.test(tag)) return ""; - const match = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)`)); - return match ? match[1].trim() : ""; -} - -export function getXmlChildren( - xml: string, - parentTag: string, - childTag: string, -): string[] { - if (!VALID_TAG.test(parentTag) || !VALID_TAG.test(childTag)) return []; - const parentMatch = xml.match( - new RegExp(`<${parentTag}>([\\s\\S]*?)`), - ); - if (!parentMatch) return []; - const items: string[] = []; - const re = new RegExp(`<${childTag}>([\\s\\S]*?)`, "g"); - let m; - while ((m = re.exec(parentMatch[1])) !== null) { - items.push(m[1].trim()); - } - return items; -} diff --git a/src/providers/_fetch.ts b/src/providers/_fetch.ts deleted file mode 100644 index ed9b5e89..00000000 --- a/src/providers/_fetch.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { getEnvVar } from "../config.js"; - -export function fetchWithTimeout( - url: string, - init: RequestInit, - timeoutMs?: number, -): Promise { - const parsed = - timeoutMs ?? - Number.parseInt(getEnvVar("AGENTMEMORY_LLM_TIMEOUT_MS") ?? "60000", 10); - const ms = Number.isFinite(parsed) && parsed > 0 ? parsed : 60000; - - const ctl = new AbortController(); - const signal = init.signal - ? AbortSignal.any([init.signal, ctl.signal]) - : ctl.signal; - const t = setTimeout(() => ctl.abort(), ms); - return fetch(url, { ...init, signal }).finally(() => clearTimeout(t)); -} diff --git a/src/providers/_openai-shared.ts b/src/providers/_openai-shared.ts deleted file mode 100644 index 09566b1a..00000000 --- a/src/providers/_openai-shared.ts +++ /dev/null @@ -1,166 +0,0 @@ -// Shared transport helpers for the OpenAI-compatible LLM + embedding -// providers. Both surfaces (chat completions, embeddings) speak the -// same wire shape on the standard OpenAI path. Azure OpenAI ships -// two URL styles and we support both: -// -// - Legacy/stable: `/openai/deployments//chat/completions` -// with mandatory `api-version=` query param. The deployment -// name lives in the URL path. Required api-version moves with -// every Azure date-stamped revision; we keep the -// `OPENAI_API_VERSION` env knob + `DEFAULT_AZURE_API_VERSION` -// fallback so existing configs don't break on upgrade. -// -// - v1 (GA Apr-2025): `/openai/v1/chat/completions`. No -// api-version query param, no `/deployments/` segment. Deployment -// name is passed in the request body as `model`. This matches -// the OpenAI wire shape one-for-one which is the whole point of -// v1 — drop-in compatibility. -// -// Auto-detection runs off the URL shape, not a flag: if the path -// already carries `/deployments/`, we route through the legacy -// builder; otherwise v1. Users opt into v1 by stripping the -// `/openai/deployments/` suffix from their -// OPENAI_BASE_URL (or never adding it). See azureStyleOf(). - -export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com"; - -// Default api-version for the legacy Azure URL pattern. Only used -// when the configured base URL carries `/deployments/` AND -// OPENAI_API_VERSION is unset. The v1 path ignores this entirely. -export const DEFAULT_AZURE_API_VERSION = "2024-08-01-preview"; - -type AzureStyle = "legacy" | "v1"; - -// Azure resource URLs land at .openai.azure.com. -export function detectAzure(baseUrl: string): boolean { - try { - const u = new URL(baseUrl); - return u.hostname.endsWith(".openai.azure.com"); - } catch { - return false; - } -} - -// Pick the Azure URL style off the base URL's path shape. We only -// consult this when detectAzure(baseUrl) has already returned true. -function azureStyleOf(baseUrl: string): AzureStyle { - try { - const u = new URL(baseUrl); - // `/openai/deployments/` (with or without a trailing - // segment) signals the legacy URL pattern. Anything else — a - // bare resource host, an `/openai/v1` prefix, an empty path — - // routes through v1. - if (/\/openai\/deployments\//.test(u.pathname)) return "legacy"; - return "v1"; - } catch { - return "v1"; - } -} - -// Legacy Azure: append the route to the existing deployment path, -// set api-version via searchParams. URL-API composition keeps any -// pre-existing query params on the base URL intact. -function legacyAzureUrl( - baseUrl: string, - path: string, - apiVersion: string, -): string { - const url = new URL(baseUrl); - const existing = url.pathname.replace(/\/+$/, ""); - const route = path.startsWith("/") ? path : `/${path}`; - url.pathname = `${existing}${route}`; - url.searchParams.set("api-version", apiVersion); - return url.toString(); -} - -// v1 Azure: route through `/openai/v1/`. Preserve any existing -// query params on the base URL (corporate proxy, diagnostics tokens) -// but never append api-version — v1 doesn't accept it. -function v1AzureUrl(baseUrl: string, path: string): string { - const url = new URL(baseUrl); - const route = path.startsWith("/") ? path.slice(1) : path; - // Strip any trailing `/openai`, `/openai/`, or `/openai/v1` so a - // user who configures OPENAI_BASE_URL with a partial prefix still - // gets a single, correct path. - const base = url.pathname.replace(/\/?openai(?:\/v1)?\/?$/, ""); - url.pathname = `${base.replace(/\/+$/, "")}/openai/v1/${route}`; - return url.toString(); -} - -// Append an OpenAI-compatible route to the base URL without producing -// a doubled version segment. Three cases: -// -// 1. Empty path (just a hostname) — OpenAI default: prepend `/v1/`. -// https://api.openai.com → https://api.openai.com/v1/chat/completions -// 2. Path already ends with `/v1` — provider-documented base, treat -// it as the version anchor and append the route directly. -// https://api.deepseek.com/v1 → https://api.deepseek.com/v1/chat/completions -// (without this, we'd produce /v1/v1/chat/completions and 404. #628) -// 3. Path ends with any other versioned or path segment — provider -// uses a non-OpenAI version scheme (Zhipu /api/paas/v4, others) — -// append the route directly, no `/v1/` injected. -// https://open.bigmodel.cn/api/paas/v4 → .../v4/chat/completions (#646) -function appendOpenAIRoute(baseUrl: string, route: string): string { - const trimmedBase = baseUrl.replace(/\/+$/, ""); - const cleanRoute = route.startsWith("/") ? route : `/${route}`; - let pathname: string; - try { - pathname = new URL(trimmedBase).pathname.replace(/\/+$/, ""); - } catch { - return `${trimmedBase}/v1${cleanRoute}`; - } - if (pathname === "" || pathname === "/") { - return `${trimmedBase}/v1${cleanRoute}`; - } - return `${trimmedBase}${cleanRoute}`; -} - -export function buildChatUrl( - baseUrl: string, - isAzure: boolean, - azureApiVersion: string, -): string { - if (isAzure) { - return azureStyleOf(baseUrl) === "legacy" - ? legacyAzureUrl(baseUrl, "/chat/completions", azureApiVersion) - : v1AzureUrl(baseUrl, "/chat/completions"); - } - return appendOpenAIRoute(baseUrl, "/chat/completions"); -} - -export function buildEmbeddingUrl( - baseUrl: string, - isAzure: boolean, - azureApiVersion: string, -): string { - if (isAzure) { - return azureStyleOf(baseUrl) === "legacy" - ? legacyAzureUrl(baseUrl, "/embeddings", azureApiVersion) - : v1AzureUrl(baseUrl, "/embeddings"); - } - return appendOpenAIRoute(baseUrl, "/embeddings"); -} - -// Azure key-auth uses `api-key: `; standard OpenAI-compatible -// endpoints use `Authorization: Bearer `. Azure also accepts -// Bearer when AAD-auth is configured upstream, but the api-key path -// is the default and what our config block documents. -export function buildAuthHeaders( - apiKey: string, - isAzure: boolean, -): Record { - if (isAzure) { - return { - "Content-Type": "application/json", - "api-key": apiKey, - }; - } - return { - "Content-Type": "application/json", - Authorization: `Bearer ${apiKey}`, - }; -} - -export function normalizeBaseUrl(raw: string | undefined): string { - return (raw || DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, ""); -} diff --git a/src/providers/agent-sdk.ts b/src/providers/agent-sdk.ts deleted file mode 100644 index 566318d1..00000000 --- a/src/providers/agent-sdk.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { MemoryProvider } from '../types.js' - -export class AgentSDKProvider implements MemoryProvider { - name = 'agent-sdk' - - async compress(systemPrompt: string, userPrompt: string): Promise { - return this.query(systemPrompt, userPrompt) - } - - async summarize(systemPrompt: string, userPrompt: string): Promise { - return this.query(systemPrompt, userPrompt) - } - - private async query(systemPrompt: string, userPrompt: string): Promise { - if (process.env.AGENTMEMORY_SDK_CHILD === "1") { - // We are already running inside a Claude Agent SDK-spawned session. - // Spawning another one would let its plugin-hook-driven Stop loop - // re-enter /agentmemory/summarize and cause unbounded recursion - // (#149 follow-up). Degrade to empty string so callers short-circuit. - return "" - } - - // Mark any child process / SDK session spawned from here as a SDK - // child. agentmemory hook scripts check this marker and skip their - // REST calls to break the recursion loop. Restore the previous value - // in `finally` so later calls in the same parent process are not - // mis-classified as SDK children (otherwise every subsequent query - // would short-circuit to "" above). - const prev = process.env.AGENTMEMORY_SDK_CHILD - process.env.AGENTMEMORY_SDK_CHILD = "1" - - try { - const { query } = await import('@anthropic-ai/claude-agent-sdk') - - const messages = query({ - prompt: userPrompt, - options: { - systemPrompt, - maxTurns: 1, - allowedTools: [], - }, - }) - - let result = '' - for await (const msg of messages) { - if (msg.type === 'result') { - result = (msg as any).result ?? '' - } - } - return result - } finally { - if (prev === undefined) { - delete process.env.AGENTMEMORY_SDK_CHILD - } else { - process.env.AGENTMEMORY_SDK_CHILD = prev - } - } - } -} diff --git a/src/providers/anthropic.ts b/src/providers/anthropic.ts deleted file mode 100644 index 6dc5cfd3..00000000 --- a/src/providers/anthropic.ts +++ /dev/null @@ -1,55 +0,0 @@ -import Anthropic from '@anthropic-ai/sdk' -import type { MemoryProvider } from '../types.js' - -export class AnthropicProvider implements MemoryProvider { - name = 'anthropic' - private client: Anthropic - private model: string - private maxTokens: number - - constructor(apiKey: string, model: string, maxTokens: number, baseURL?: string) { - this.client = new Anthropic({ apiKey, ...(baseURL ? { baseURL } : {}) }) - this.model = model - this.maxTokens = maxTokens - } - - async compress(systemPrompt: string, userPrompt: string): Promise { - return this.call(systemPrompt, userPrompt) - } - - async summarize(systemPrompt: string, userPrompt: string): Promise { - return this.call(systemPrompt, userPrompt) - } - - async describeImage(imageData: string, mimeType: string, prompt: string): Promise { - const response = await this.client.messages.create({ - model: this.model, - max_tokens: this.maxTokens, - messages: [{ - role: 'user', - content: [ - { - type: 'image', - source: { type: 'base64', media_type: mimeType as 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp', data: imageData }, - }, - { type: 'text', text: prompt }, - ], - }], - }) - - const textBlock = response.content.find((b) => b.type === 'text') - return textBlock?.text ?? '' - } - - private async call(systemPrompt: string, userPrompt: string): Promise { - const response = await this.client.messages.create({ - model: this.model, - max_tokens: this.maxTokens, - system: systemPrompt, - messages: [{ role: 'user', content: userPrompt }], - }) - - const textBlock = response.content.find((b) => b.type === 'text') - return textBlock?.text ?? '' - } -} diff --git a/src/providers/circuit-breaker.ts b/src/providers/circuit-breaker.ts deleted file mode 100644 index e7f1f85d..00000000 --- a/src/providers/circuit-breaker.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { CircuitBreakerState } from "../types.js"; - -interface CircuitBreakerOptions { - failureThreshold?: number; - failureWindowMs?: number; - recoveryTimeoutMs?: number; -} - -function positiveFinite(val: number | undefined, fallback: number): number { - return Number.isFinite(val) && val! > 0 ? val! : fallback; -} - -export class CircuitBreaker { - private state: "closed" | "open" | "half-open" = "closed"; - private failures = 0; - private lastFailureAt: number | null = null; - private openedAt: number | null = null; - - private readonly failureThreshold: number; - private readonly failureWindowMs: number; - private readonly recoveryTimeoutMs: number; - - constructor(opts?: CircuitBreakerOptions) { - this.failureThreshold = Math.max( - 1, - Math.floor(positiveFinite(opts?.failureThreshold, 3)), - ); - this.failureWindowMs = positiveFinite(opts?.failureWindowMs, 60_000); - this.recoveryTimeoutMs = positiveFinite(opts?.recoveryTimeoutMs, 30_000); - } - - get isAllowed(): boolean { - if (this.state === "closed") return true; - if (this.state === "open") { - if ( - this.openedAt && - Date.now() - this.openedAt >= this.recoveryTimeoutMs - ) { - this.state = "half-open"; - return true; - } - return false; - } - return true; - } - - recordSuccess(): void { - if (this.state === "half-open") { - this.state = "closed"; - this.failures = 0; - this.lastFailureAt = null; - this.openedAt = null; - } - } - - recordFailure(): void { - const now = Date.now(); - if (this.state === "half-open") { - this.state = "open"; - this.openedAt = now; - return; - } - if (this.lastFailureAt && now - this.lastFailureAt > this.failureWindowMs) { - this.failures = 0; - } - this.failures += 1; - this.lastFailureAt = now; - if (this.failures >= this.failureThreshold) { - this.state = "open"; - this.openedAt = now; - } - } - - getState(): CircuitBreakerState { - return { - state: this.state, - failures: this.failures, - lastFailureAt: this.lastFailureAt, - openedAt: this.openedAt, - }; - } -} diff --git a/src/providers/embedding/clip.ts b/src/providers/embedding/clip.ts deleted file mode 100644 index 2a0143ad..00000000 --- a/src/providers/embedding/clip.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { readFile } from "node:fs/promises"; -import type { EmbeddingProvider } from "../../types.js"; - -type TransformersModule = { - pipeline: ( - task: string, - model: string, - ) => Promise; - RawImage: { - fromBlob: (blob: Blob) => Promise; - }; -}; - -type RawImageInstance = unknown; - -type ClipPipeline = ( - input: string[] | RawImageInstance | RawImageInstance[], - options?: { pooling?: string; normalize?: boolean }, -) => Promise<{ tolist: () => number[][]; data: Float32Array }>; - -const DEFAULT_MODEL = "Xenova/clip-vit-base-patch32"; -const DIMENSIONS = 512; - -export class ClipEmbeddingProvider implements EmbeddingProvider { - readonly name = "clip"; - readonly dimensions = DIMENSIONS; - private textExtractor: ClipPipeline | null = null; - private imageExtractor: ClipPipeline | null = null; - private transformers: TransformersModule | null = null; - private readonly modelId: string; - - constructor(modelId: string = DEFAULT_MODEL) { - this.modelId = modelId; - } - - async embed(text: string): Promise { - const [vec] = await this.embedBatch([text]); - return vec; - } - - async embedBatch(texts: string[]): Promise { - const extractor = await this.getTextExtractor(); - const output = await extractor(texts, { pooling: "mean", normalize: true }); - return output.tolist().map((v) => new Float32Array(v)); - } - - async embedImage(src: string): Promise { - const t = await this.getTransformers(); - const image = await loadImage(t, src); - const extractor = await this.getImageExtractor(); - const output = await extractor(image); - const vec = output.data ?? new Float32Array(output.tolist()[0] || []); - return normalize(vec); - } - - private async getTransformers(): Promise { - if (this.transformers) return this.transformers; - try { - this.transformers = (await import("@xenova/transformers")) as unknown as TransformersModule; - } catch { - throw new Error( - "Install @xenova/transformers for CLIP image embeddings: npm install @xenova/transformers", - ); - } - return this.transformers; - } - - private async getTextExtractor(): Promise { - if (this.textExtractor) return this.textExtractor; - const t = await this.getTransformers(); - this.textExtractor = await t.pipeline("feature-extraction", this.modelId); - return this.textExtractor; - } - - private async getImageExtractor(): Promise { - if (this.imageExtractor) return this.imageExtractor; - const t = await this.getTransformers(); - this.imageExtractor = await t.pipeline("image-feature-extraction", this.modelId); - return this.imageExtractor; - } -} - -async function loadImage( - t: TransformersModule, - src: string, -): Promise { - if (src.startsWith("data:")) { - const comma = src.indexOf(","); - const b64 = comma >= 0 ? src.slice(comma + 1) : src; - const buf = Buffer.from(b64, "base64"); - const blob = new Blob([buf]); - return t.RawImage.fromBlob(blob); - } - const data = await readFile(src); - const blob = new Blob([data]); - return t.RawImage.fromBlob(blob); -} - -function normalize(vec: Float32Array): Float32Array { - let sum = 0; - for (let i = 0; i < vec.length; i++) sum += vec[i] * vec[i]; - const norm = Math.sqrt(sum); - if (norm === 0) return vec; - const out = new Float32Array(vec.length); - for (let i = 0; i < vec.length; i++) out[i] = vec[i] / norm; - return out; -} diff --git a/src/providers/embedding/cohere.ts b/src/providers/embedding/cohere.ts deleted file mode 100644 index 02b7f13b..00000000 --- a/src/providers/embedding/cohere.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { EmbeddingProvider } from "../../types.js"; -import { getEnvVar } from "../../config.js"; -import { fetchWithTimeout } from "../_fetch.js"; - -const API_URL = "https://api.cohere.ai/v1/embed"; - -export class CohereEmbeddingProvider implements EmbeddingProvider { - readonly name = "cohere"; - readonly dimensions = 1024; - private apiKey: string; - - constructor(apiKey?: string) { - this.apiKey = apiKey || getEnvVar("COHERE_API_KEY") || ""; - if (!this.apiKey) throw new Error("COHERE_API_KEY is required"); - } - - async embed(text: string): Promise { - const [result] = await this.embedBatch([text]); - return result; - } - - async embedBatch(texts: string[]): Promise { - const response = await fetchWithTimeout(API_URL, { - method: "POST", - headers: { - Authorization: `Bearer ${this.apiKey}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - model: "embed-english-v3.0", - texts, - input_type: "search_document", - }), - }); - - if (!response.ok) { - const err = await response.text(); - throw new Error(`Cohere embedding failed (${response.status}): ${err}`); - } - - const data = (await response.json()) as { - embeddings: number[][]; - }; - - return data.embeddings.map((e) => new Float32Array(e)); - } -} diff --git a/src/providers/embedding/gemini.ts b/src/providers/embedding/gemini.ts deleted file mode 100644 index 14016469..00000000 --- a/src/providers/embedding/gemini.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { EmbeddingProvider } from "../../types.js"; -import { getEnvVar } from "../../config.js"; -import { fetchWithTimeout } from "../_fetch.js"; - -const BATCH_LIMIT = 100; -const MODEL = "models/gemini-embedding-001"; -const API_BASE = `https://generativelanguage.googleapis.com/v1beta/${MODEL}:batchEmbedContents`; - -export class GeminiEmbeddingProvider implements EmbeddingProvider { - readonly name = "gemini"; - readonly dimensions = 768; - private apiKey: string; - - constructor(apiKey?: string) { - this.apiKey = apiKey || getEnvVar("GEMINI_API_KEY") || ""; - if (!this.apiKey) throw new Error("GEMINI_API_KEY is required"); - } - - async embed(text: string): Promise { - const [result] = await this.embedBatch([text]); - return result; - } - - async embedBatch(texts: string[]): Promise { - const results: Float32Array[] = []; - - for (let i = 0; i < texts.length; i += BATCH_LIMIT) { - const chunk = texts.slice(i, i + BATCH_LIMIT); - const response = await fetchWithTimeout(`${API_BASE}?key=${this.apiKey}`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - requests: chunk.map((t) => ({ - model: MODEL, - content: { parts: [{ text: t }] }, - outputDimensionality: this.dimensions, - })), - }), - }); - - if (!response.ok) { - const err = await response.text(); - throw new Error(`Gemini embedding failed (${response.status}): ${err}`); - } - - const data = (await response.json()) as { - embeddings: Array<{ values: number[] }>; - }; - - for (const emb of data.embeddings) { - results.push(l2Normalize(new Float32Array(emb.values))); - } - } - - return results; - } -} - -let zeroNormWarned = false; - -function l2Normalize(vec: Float32Array): Float32Array { - let sum = 0; - for (let i = 0; i < vec.length; i++) sum += vec[i]! * vec[i]!; - const norm = Math.sqrt(sum); - if (norm === 0) { - if (!zeroNormWarned) { - zeroNormWarned = true; - process.stderr.write( - `[agentmemory] warn: gemini-embedding-001 returned a zero-norm ` + - `embedding (length=${vec.length}); leaving it un-normalized. ` + - `Subsequent zero-norm vectors will not be reported.\n`, - ); - } - return vec; - } - for (let i = 0; i < vec.length; i++) vec[i] = vec[i]! / norm; - return vec; -} diff --git a/src/providers/embedding/index.ts b/src/providers/embedding/index.ts deleted file mode 100644 index d18de232..00000000 --- a/src/providers/embedding/index.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { EmbeddingProvider } from "../../types.js"; -import { detectEmbeddingProvider, getEnvVar } from "../../config.js"; -import { GeminiEmbeddingProvider } from "./gemini.js"; -import { OpenAIEmbeddingProvider } from "./openai.js"; -import { VoyageEmbeddingProvider } from "./voyage.js"; -import { CohereEmbeddingProvider } from "./cohere.js"; -import { OpenRouterEmbeddingProvider } from "./openrouter.js"; -import { LocalEmbeddingProvider } from "./local.js"; -import { ClipEmbeddingProvider } from "./clip.js"; - -export { - GeminiEmbeddingProvider, - OpenAIEmbeddingProvider, - VoyageEmbeddingProvider, - CohereEmbeddingProvider, - OpenRouterEmbeddingProvider, - LocalEmbeddingProvider, - ClipEmbeddingProvider, -}; - -let imageEmbeddingProvider: EmbeddingProvider | null = null; - -export function createImageEmbeddingProvider(): EmbeddingProvider | null { - if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] !== "true") return null; - if (imageEmbeddingProvider) return imageEmbeddingProvider; - imageEmbeddingProvider = withDimensionGuard(new ClipEmbeddingProvider()); - return imageEmbeddingProvider; -} - -export function createEmbeddingProvider(): EmbeddingProvider | null { - const detected = detectEmbeddingProvider(); - if (!detected) return null; - - switch (detected) { - case "gemini": - return withDimensionGuard(new GeminiEmbeddingProvider(getEnvVar("GEMINI_API_KEY")!)); - case "openai": - return withDimensionGuard(new OpenAIEmbeddingProvider(getEnvVar("OPENAI_API_KEY")!)); - case "voyage": - return withDimensionGuard(new VoyageEmbeddingProvider(getEnvVar("VOYAGE_API_KEY")!)); - case "cohere": - return withDimensionGuard(new CohereEmbeddingProvider(getEnvVar("COHERE_API_KEY")!)); - case "openrouter": - return withDimensionGuard(new OpenRouterEmbeddingProvider(getEnvVar("OPENROUTER_API_KEY")!)); - case "local": - return withDimensionGuard(new LocalEmbeddingProvider()); - default: - return null; - } -} - -// Wrong-dimension vectors corrupt the index silently: vector-index.ts -// returns 0 from cosineSimilarity on length mismatch instead of throwing, -// so a bad vector is stored, never matches anything, and the memory -// becomes invisible without an error. Catch it at the boundary. -export function withDimensionGuard(provider: EmbeddingProvider): EmbeddingProvider { - const expected = provider.dimensions; - const check = (v: Float32Array, where: string): Float32Array => { - if (v.length !== expected) { - throw new Error( - `Embedding dimension mismatch in ${provider.name}.${where}: expected ${expected}, got ${v.length}`, - ); - } - return v; - }; - // Preserve the provider's prototype chain so `instanceof` checks - // against concrete classes (e.g. GeminiEmbeddingProvider) keep working. - const wrapped = Object.create(provider) as EmbeddingProvider; - wrapped.embed = async (t) => check(await provider.embed(t), "embed"); - wrapped.embedBatch = async (ts) => { - const out = await provider.embedBatch(ts); - out.forEach((v, i) => check(v, `embedBatch[${i}]`)); - return out; - }; - if (provider.embedImage) { - wrapped.embedImage = async (s: string) => - check(await provider.embedImage!(s), "embedImage"); - } - return wrapped; -} diff --git a/src/providers/embedding/local.ts b/src/providers/embedding/local.ts deleted file mode 100644 index ad6c2d21..00000000 --- a/src/providers/embedding/local.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { EmbeddingProvider } from "../../types.js"; - -type Pipeline = ( - task: string, - model: string, -) => Promise< - ( - texts: string[], - options: { pooling: string; normalize: boolean }, - ) => Promise<{ tolist: () => number[][] }> ->; - -export class LocalEmbeddingProvider implements EmbeddingProvider { - readonly name = "local"; - readonly dimensions = 384; - private extractor: Awaited> | null = null; - - async embed(text: string): Promise { - const [result] = await this.embedBatch([text]); - return result; - } - - async embedBatch(texts: string[]): Promise { - const extractor = await this.getExtractor(); - const output = await extractor(texts, { - pooling: "mean", - normalize: true, - }); - const vectors = output.tolist(); - return vectors.map((v: number[]) => new Float32Array(v)); - } - - private async getExtractor() { - if (this.extractor) return this.extractor; - - let transformers: { pipeline: Pipeline }; - try { - // @ts-ignore - optional peer dependency - transformers = await import("@xenova/transformers"); - } catch { - throw new Error( - "Install @xenova/transformers for local embeddings: npm install @xenova/transformers", - ); - } - - this.extractor = await transformers.pipeline( - "feature-extraction", - "Xenova/all-MiniLM-L6-v2", - ); - return this.extractor; - } -} diff --git a/src/providers/embedding/openai.ts b/src/providers/embedding/openai.ts deleted file mode 100644 index 7384d513..00000000 --- a/src/providers/embedding/openai.ts +++ /dev/null @@ -1,147 +0,0 @@ -import type { EmbeddingProvider } from "../../types.js"; -import { getEnvVar } from "../../config.js"; -import { fetchWithTimeout } from "../_fetch.js"; -import { - DEFAULT_AZURE_API_VERSION, - buildAuthHeaders, - buildEmbeddingUrl, - detectAzure, - normalizeBaseUrl, -} from "../_openai-shared.js"; - -const DEFAULT_MODEL = "text-embedding-3-small"; - -/** - * Known OpenAI embedding model dimensions. Extend as new models ship. - * Override in any case via OPENAI_EMBEDDING_DIMENSIONS for custom or - * self-hosted OpenAI-compatible endpoints returning non-standard sizes. - */ -const MODEL_DIMENSIONS: Record = { - "text-embedding-3-small": 1536, - "text-embedding-3-large": 3072, - "text-embedding-ada-002": 1536, -}; - -const DEFAULT_DIMENSIONS = MODEL_DIMENSIONS[DEFAULT_MODEL] ?? 1536; - -function resolveDimensions(model: string, override: string | undefined): number { - if (override !== undefined && override.trim().length > 0) { - const parsed = parseInt(override, 10); - if (!Number.isFinite(parsed) || parsed <= 0) { - throw new Error( - `OPENAI_EMBEDDING_DIMENSIONS must be a positive integer, got: ${override}`, - ); - } - return parsed; - } - return MODEL_DIMENSIONS[model] ?? DEFAULT_DIMENSIONS; -} - -/** - * OpenAI-compatible embedding provider. - * - * Shares transport (URL builder, auth header, Azure detection) with - * the OpenAI LLM provider via `_openai-shared` (#371). Same env knobs - * pick up automatically: when `OPENAI_BASE_URL` points at an Azure - * resource (`.openai.azure.com` hostname) the embedding request uses - * Azure's `/embeddings` path with the `api-version` query param and - * `api-key` header instead of `Authorization: Bearer`. - * - * Required env vars: - * OPENAI_API_KEY — API key (fallback for OPENAI_EMBEDDING_API_KEY) - * - * Optional: - * OPENAI_BASE_URL — base URL without path (default: https://api.openai.com). - * Azure: https://.openai.azure.com/openai/deployments/ - * OPENAI_EMBEDDING_BASE_URL — embedding-specific base URL override (defaults - * to OPENAI_BASE_URL). Lets operators run - * embeddings on a separate endpoint from chat — - * e.g. local Ollama / LM Studio / llama.cpp / - * vLLM at http://localhost:1234 for unlimited - * free embeddings, while keeping chat - * completions on a rate-limited but high-quality - * hosted provider. Azure detection runs on - * whichever URL ends up selected. - * OPENAI_EMBEDDING_API_KEY — separate API key for the embedding endpoint - * (defaults to OPENAI_API_KEY). Useful when the - * embedding endpoint requires a different key - * or no key at all (set to e.g. "local" for - * endpoints that ignore Authorization). - * OPENAI_API_VERSION — Azure api-version query param (default: 2024-08-01-preview) - * OPENAI_EMBEDDING_MODEL — model name (default: text-embedding-3-small) - * OPENAI_EMBEDDING_DIMENSIONS — override reported dimensions (required for - * custom / self-hosted models not in the - * MODEL_DIMENSIONS table above) - */ -export class OpenAIEmbeddingProvider implements EmbeddingProvider { - readonly name = "openai"; - readonly dimensions: number; - private apiKey: string; - private baseUrl: string; - private model: string; - private isAzure: boolean; - private azureApiVersion: string; - - constructor(apiKey?: string) { - // Separate API key path: caller-passed wins, then OPENAI_EMBEDDING_API_KEY, - // then fall back to OPENAI_API_KEY. Allows e.g. a placeholder key for - // local endpoints that ignore Authorization (most do). - this.apiKey = - apiKey || - getEnvVar("OPENAI_EMBEDDING_API_KEY") || - getEnvVar("OPENAI_API_KEY") || - ""; - if (!this.apiKey) { - throw new Error( - "API key is required (via constructor, OPENAI_EMBEDDING_API_KEY, or OPENAI_API_KEY)", - ); - } - // Embedding-specific base URL override; falls back to OPENAI_BASE_URL, - // then normalizeBaseUrl's default. The chat-LLM path (src/providers/openai.ts) - // still reads only OPENAI_BASE_URL, so setting OPENAI_EMBEDDING_BASE_URL - // alone moves embeddings to the new endpoint without affecting chat. - this.baseUrl = normalizeBaseUrl( - getEnvVar("OPENAI_EMBEDDING_BASE_URL") || getEnvVar("OPENAI_BASE_URL"), - ); - this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL; - this.dimensions = resolveDimensions( - this.model, - getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"), - ); - this.isAzure = detectAzure(this.baseUrl); - this.azureApiVersion = - getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION; - } - - async embed(text: string): Promise { - const [result] = await this.embedBatch([text]); - return result; - } - - async embedBatch(texts: string[]): Promise { - const url = buildEmbeddingUrl( - this.baseUrl, - this.isAzure, - this.azureApiVersion, - ); - const response = await fetchWithTimeout(url, { - method: "POST", - headers: buildAuthHeaders(this.apiKey, this.isAzure), - body: JSON.stringify({ - model: this.model, - input: texts, - }), - }); - - if (!response.ok) { - const err = await response.text(); - throw new Error(`OpenAI embedding failed (${response.status}): ${err}`); - } - - const data = (await response.json()) as { - data: Array<{ embedding: number[] }>; - }; - - return data.data.map((d) => new Float32Array(d.embedding)); - } -} diff --git a/src/providers/embedding/openrouter.ts b/src/providers/embedding/openrouter.ts deleted file mode 100644 index 46999e55..00000000 --- a/src/providers/embedding/openrouter.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { EmbeddingProvider } from "../../types.js"; -import { getEnvVar } from "../../config.js"; -import { fetchWithTimeout } from "../_fetch.js"; - -const API_URL = "https://openrouter.ai/api/v1/embeddings"; - -export class OpenRouterEmbeddingProvider implements EmbeddingProvider { - readonly name = "openrouter"; - readonly dimensions = 1536; - private apiKey: string; - private model: string; - - constructor(apiKey?: string) { - this.apiKey = apiKey || getEnvVar("OPENROUTER_API_KEY") || ""; - if (!this.apiKey) throw new Error("OPENROUTER_API_KEY is required"); - this.model = - getEnvVar("OPENROUTER_EMBEDDING_MODEL") || - "openai/text-embedding-3-small"; - } - - async embed(text: string): Promise { - const [result] = await this.embedBatch([text]); - return result; - } - - async embedBatch(texts: string[]): Promise { - const response = await fetchWithTimeout(API_URL, { - method: "POST", - headers: { - Authorization: `Bearer ${this.apiKey}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - model: this.model, - input: texts, - }), - }); - - if (!response.ok) { - const err = await response.text(); - throw new Error( - `OpenRouter embedding failed (${response.status}): ${err}`, - ); - } - - const data = (await response.json()) as { - data: Array<{ embedding: number[] }>; - }; - - return data.data.map((d) => new Float32Array(d.embedding)); - } -} diff --git a/src/providers/embedding/voyage.ts b/src/providers/embedding/voyage.ts deleted file mode 100644 index 14ddc365..00000000 --- a/src/providers/embedding/voyage.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { EmbeddingProvider } from "../../types.js"; -import { getEnvVar } from "../../config.js"; -import { fetchWithTimeout } from "../_fetch.js"; - -const API_URL = "https://api.voyageai.com/v1/embeddings"; - -export class VoyageEmbeddingProvider implements EmbeddingProvider { - readonly name = "voyage"; - readonly dimensions = 1024; - private apiKey: string; - - constructor(apiKey?: string) { - this.apiKey = apiKey || getEnvVar("VOYAGE_API_KEY") || ""; - if (!this.apiKey) throw new Error("VOYAGE_API_KEY is required"); - } - - async embed(text: string): Promise { - const [result] = await this.embedBatch([text]); - return result; - } - - async embedBatch(texts: string[]): Promise { - const response = await fetchWithTimeout(API_URL, { - method: "POST", - headers: { - Authorization: `Bearer ${this.apiKey}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - model: "voyage-code-3", - input: texts, - input_type: "document", - }), - }); - - if (!response.ok) { - const err = await response.text(); - throw new Error(`Voyage embedding failed (${response.status}): ${err}`); - } - - const data = (await response.json()) as { - data: Array<{ embedding: number[] }>; - }; - - return data.data.map((d) => new Float32Array(d.embedding)); - } -} diff --git a/src/providers/fallback-chain.ts b/src/providers/fallback-chain.ts deleted file mode 100644 index e2c979f9..00000000 --- a/src/providers/fallback-chain.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { MemoryProvider } from "../types.js"; - -export class FallbackChainProvider implements MemoryProvider { - name: string; - - constructor(private providers: MemoryProvider[]) { - this.name = `fallback(${providers.map((p) => p.name).join(" -> ")})`; - } - - async compress(systemPrompt: string, userPrompt: string): Promise { - return this.tryAll((p) => p.compress(systemPrompt, userPrompt)); - } - - async summarize(systemPrompt: string, userPrompt: string): Promise { - return this.tryAll((p) => p.summarize(systemPrompt, userPrompt)); - } - - private async tryAll( - fn: (p: MemoryProvider) => Promise, - ): Promise { - let lastError: Error | null = null; - for (const provider of this.providers) { - try { - return await fn(provider); - } catch (err) { - lastError = err instanceof Error ? err : new Error(String(err)); - } - } - throw lastError || new Error("No providers available"); - } -} diff --git a/src/providers/index.ts b/src/providers/index.ts deleted file mode 100644 index 5de6807c..00000000 --- a/src/providers/index.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { - MemoryProvider, - ProviderConfig, - FallbackConfig, -} from "../types.js"; -import { AgentSDKProvider } from "./agent-sdk.js"; -import { AnthropicProvider } from "./anthropic.js"; -import { MinimaxProvider } from "./minimax.js"; -import { NoopProvider } from "./noop.js"; -import { OpenAIProvider } from "./openai.js"; -import { OpenRouterProvider } from "./openrouter.js"; -import { ResilientProvider } from "./resilient.js"; -import { FallbackChainProvider } from "./fallback-chain.js"; -import { getEnvVar } from "../config.js"; - -export { createEmbeddingProvider, createImageEmbeddingProvider } from "./embedding/index.js"; - -function requireEnvVar(key: string): string { - const value = getEnvVar(key); - if (!value) { - throw new Error( - `Missing required environment variable: ${key}. Set it in ~/.agentmemory/.env or as an environment variable.`, - ); - } - return value; -} - -export function createProvider(config: ProviderConfig): ResilientProvider { - return new ResilientProvider(createBaseProvider(config)); -} - -export function createFallbackProvider( - config: ProviderConfig, - fallbackConfig: FallbackConfig, -): ResilientProvider { - if (fallbackConfig.providers.length === 0) { - return createProvider(config); - } - - const providers: MemoryProvider[] = [createBaseProvider(config)]; - for (const providerType of fallbackConfig.providers) { - if (providerType === config.provider) continue; - try { - const fbConfig: ProviderConfig = { - provider: providerType, - model: config.model, - maxTokens: config.maxTokens, - }; - providers.push(createBaseProvider(fbConfig)); - } catch { - // skip unavailable fallback providers - } - } - - if (providers.length > 1) { - return new ResilientProvider(new FallbackChainProvider(providers)); - } - return new ResilientProvider(providers[0]); -} - -function createBaseProvider(config: ProviderConfig): MemoryProvider { - switch (config.provider) { - case "minimax": - return new MinimaxProvider( - requireEnvVar("MINIMAX_API_KEY"), - config.model, - config.maxTokens, - ); - case "anthropic": - return new AnthropicProvider( - requireEnvVar("ANTHROPIC_API_KEY"), - config.model, - config.maxTokens, - config.baseURL, - ); - case "gemini": { - const geminiKey = - getEnvVar("GEMINI_API_KEY") || getEnvVar("GOOGLE_API_KEY"); - if (!geminiKey) { - throw new Error( - "GEMINI_API_KEY (or GOOGLE_API_KEY) is required for the gemini provider", - ); - } - return new OpenRouterProvider( - geminiKey, - config.model, - config.maxTokens, - "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", - ); - } - case "openrouter": - return new OpenRouterProvider( - requireEnvVar("OPENROUTER_API_KEY"), - config.model, - config.maxTokens, - "https://openrouter.ai/api/v1/chat/completions", - ); - case "openai": { - const openaiKey = getEnvVar("OPENAI_API_KEY"); - if (!openaiKey) { - throw new Error( - "OPENAI_API_KEY is required for the openai provider", - ); - } - return new OpenAIProvider( - openaiKey, - config.model, - config.maxTokens, - config.baseURL, - ); - } - case "noop": - return new NoopProvider(); - case "agent-sdk": - default: - return new AgentSDKProvider(); - } -} diff --git a/src/providers/minimax.ts b/src/providers/minimax.ts deleted file mode 100644 index 72fc9ec9..00000000 --- a/src/providers/minimax.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { MemoryProvider } from '../types.js' -import { getEnvVar } from '../config.js' -import { fetchWithTimeout } from './_fetch.js' - -/** - * MiniMax provider using raw fetch to call MiniMax's Anthropic-compatible API. - * - * The Anthropic SDK automatically injects `x-stainless-*` headers that MiniMax - * rejects with 403. This provider bypasses the SDK and calls the API directly. - * - * Required env vars (loaded from ~/.agentmemory/.env or process.env): - * MINIMAX_API_KEY — your MiniMax API key - * MINIMAX_MODEL — model name (default: MiniMax-M2.7) - * MAX_TOKENS — max output tokens (default: 800; MiniMax-M2.7 needs ≤800) - * - * Optional: - * MINIMAX_BASE_URL — base URL without path (default: https://api.minimax.io/anthropic) - */ -export class MinimaxProvider implements MemoryProvider { - name = 'minimax' - private apiKey: string - private model: string - private maxTokens: number - private baseUrl: string - - constructor(apiKey: string, model: string, maxTokens: number) { - this.apiKey = apiKey - this.model = model - this.maxTokens = maxTokens - this.baseUrl = - getEnvVar('MINIMAX_BASE_URL') || 'https://api.minimax.io/anthropic' - } - - async compress(systemPrompt: string, userPrompt: string): Promise { - return this.call(systemPrompt, userPrompt) - } - - async summarize(systemPrompt: string, userPrompt: string): Promise { - return this.call(systemPrompt, userPrompt) - } - - private async call(systemPrompt: string, userPrompt: string): Promise { - const url = `${this.baseUrl}/v1/messages` - const response = await fetchWithTimeout(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-api-key': this.apiKey, - 'anthropic-version': '2023-06-01', - }, - body: JSON.stringify({ - model: this.model, - max_tokens: this.maxTokens, - system: systemPrompt, - messages: [{ role: 'user', content: userPrompt }], - }), - }) - - if (!response.ok) { - const text = await response.text() - throw new Error(`MiniMax API error ${response.status}: ${text}`) - } - - const data = (await response.json()) as { - content?: Array<{ type: string; text?: string }> - } - const textBlock = data.content?.find((b) => b.type === 'text') - return textBlock?.text ?? '' - } -} diff --git a/src/providers/noop.ts b/src/providers/noop.ts deleted file mode 100644 index 6e74f9aa..00000000 --- a/src/providers/noop.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { MemoryProvider } from "../types.js"; - -/** - * Returns empty strings for every call. Used when no LLM API key is set - * AND the user has not opted into the agent-sdk fallback via - * AGENTMEMORY_ALLOW_AGENT_SDK=true. Callers (compress, summarize) must - * detect the empty result and short-circuit instead of spawning a - * provider session (#149 / Stop-hook recursion loop fix). - */ -export class NoopProvider implements MemoryProvider { - name = "noop"; - - async compress(): Promise { - return ""; - } - - async summarize(): Promise { - return ""; - } -} diff --git a/src/providers/openai.ts b/src/providers/openai.ts deleted file mode 100644 index 438b2f4e..00000000 --- a/src/providers/openai.ts +++ /dev/null @@ -1,182 +0,0 @@ -import type { MemoryProvider } from "../types.js"; -import { getEnvVar } from "../config.js"; -import { fetchWithTimeout } from "./_fetch.js"; -import { - DEFAULT_AZURE_API_VERSION, - buildAuthHeaders, - buildChatUrl, - detectAzure, - normalizeBaseUrl, -} from "./_openai-shared.js"; - -const DEFAULT_MODEL = "gpt-4o-mini"; -const DEFAULT_TIMEOUT_MS = 60_000; - -/** - * OpenAI-compatible LLM provider. - * - * Uses raw fetch (no SDK) to support any OpenAI-compatible endpoint: - * - OpenAI official - * - Azure OpenAI (auto-detected from .openai.azure.com host) - * - DeepSeek - * - 硅基流动 (SiliconFlow) - * - vLLM / LM Studio / Ollama (with OpenAI compatibility layer) - * - Any other proxy implementing /v1/chat/completions - * - * Required env vars: - * OPENAI_API_KEY — API key - * - * Optional: - * OPENAI_BASE_URL — base URL without path (default: https://api.openai.com). - * Azure: https://.openai.azure.com/openai/deployments/ - * OPENAI_MODEL — model name (default: gpt-4o-mini) - * OPENAI_API_VERSION — Azure api-version query param (default: 2024-08-01-preview) - * OPENAI_TIMEOUT_MS — outbound fetch timeout in ms (OpenAI-scoped alias, - * takes precedence over AGENTMEMORY_LLM_TIMEOUT_MS - * for back-compat with the v0.9.17 shipping name). - * AGENTMEMORY_LLM_TIMEOUT_MS — outbound fetch timeout in ms shared across all - * raw-fetch LLM + embedding providers. Used when - * OPENAI_TIMEOUT_MS is not set. Default: 60000. - * MAX_TOKENS — max output tokens (default: from config or 4096) - * OPENAI_REASONING_EFFORT — "low" | "medium" | "high" | "none" - * Passthrough for reasoning models (e.g. Ollama Cloud - * thinking models). Set to "none" to ensure - * message.content is populated instead of only - * message.reasoning. - */ -export class OpenAIProvider implements MemoryProvider { - name = "openai"; - private apiKey: string; - private model: string; - private maxTokens: number; - private baseUrl: string; - private reasoningEffort?: string; - private timeoutMs: number; - private isAzure: boolean; - private azureApiVersion: string; - - constructor(apiKey: string, model: string, maxTokens: number, baseURL?: string) { - this.apiKey = apiKey; - this.model = model; - this.maxTokens = maxTokens; - this.baseUrl = normalizeBaseUrl(baseURL || getEnvVar("OPENAI_BASE_URL")); - this.reasoningEffort = getEnvVar("OPENAI_REASONING_EFFORT") || undefined; - this.timeoutMs = resolveTimeout(); - this.azureApiVersion = - getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION; - this.isAzure = detectAzure(this.baseUrl); - } - - async compress(systemPrompt: string, userPrompt: string): Promise { - return this.call(systemPrompt, userPrompt); - } - - async summarize(systemPrompt: string, userPrompt: string): Promise { - return this.call(systemPrompt, userPrompt); - } - - private async call(systemPrompt: string, userPrompt: string): Promise { - const url = buildChatUrl(this.baseUrl, this.isAzure, this.azureApiVersion); - const body: Record = { - model: this.model, - max_tokens: this.maxTokens, - // OpenAI API spec defines `stream` as defaulting to false, so omitting - // it should yield a JSON response. Some OpenAI-compatible proxies - // (notably 9Router < 0.4.56 — see decolua/9router#1260) default to - // text/event-stream when `stream` is absent, which crashes the - // `response.json()` call below with `Unexpected token 'd', "data: {"id"...`. - // Send it explicitly so non-spec endpoints route to non-streaming too. - stream: false, - messages: [ - { role: "system", content: systemPrompt }, - { role: "user", content: userPrompt }, - ], - }; - if (this.reasoningEffort) { - body.reasoning_effort = this.reasoningEffort; - } - - // Bound the request via the shared fetchWithTimeout helper, which - // owns the AbortController + clearTimeout cleanup for every raw-fetch - // provider (minimax, openrouter, gemini, openrouter-embed, etc.). - // OPENAI_TIMEOUT_MS keeps its v0.9.17 meaning (OpenAI-scoped alias, - // takes precedence); when unset we fall through to - // AGENTMEMORY_LLM_TIMEOUT_MS and finally the 60s default. See #446. - let response: Response; - try { - response = await fetchWithTimeout( - url, - { - method: "POST", - headers: buildAuthHeaders(this.apiKey, this.isAzure), - body: JSON.stringify(body), - }, - this.timeoutMs, - ); - } catch (err) { - const aborted = err instanceof Error && err.name === "AbortError"; - if (aborted) { - throw new Error( - `OpenAI API request timed out after ${this.timeoutMs}ms — set OPENAI_TIMEOUT_MS (or AGENTMEMORY_LLM_TIMEOUT_MS) to raise the bound or check the provider status.`, - ); - } - throw err; - } - - if (!response.ok) { - const text = await response.text(); - throw new Error(`OpenAI API error (${response.status}): ${text}`); - } - - const data = (await response.json()) as { - choices?: Array<{ - message?: { content?: string; reasoning?: string; reasoning_content?: string }; - }>; - }; - const message = data.choices?.[0]?.message; - const content = message?.content; - if (content) { - return content; - } - // Fallback: some thinking models return reasoning but no content. - // DeepSeek V4 / Qwen3 / GLM / Kimi return `reasoning_content`; - // older OpenAI o-series + some compatibles return `reasoning`. #627 - const reasoning = message?.reasoning ?? message?.reasoning_content; - if (reasoning) { - return reasoning; - } - throw new Error( - `OpenAI returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`, - ); - } -} - -// Resolves the outbound-fetch timeout for the OpenAI LLM path. -// Precedence (preserving v0.9.17 behaviour): -// 1. OPENAI_TIMEOUT_MS — OpenAI-scoped alias (back-compat) -// 2. AGENTMEMORY_LLM_TIMEOUT_MS — global LLM/embedding timeout (#446) -// 3. 60 000 ms default -function resolveTimeout(): number { - const openaiRaw = getEnvVar("OPENAI_TIMEOUT_MS"); - const openai = parsePositiveInt(openaiRaw); - if (openai !== undefined) return openai; - - const globalRaw = getEnvVar("AGENTMEMORY_LLM_TIMEOUT_MS"); - const globalMs = parsePositiveInt(globalRaw); - if (globalMs !== undefined) return globalMs; - - return DEFAULT_TIMEOUT_MS; -} - -function parsePositiveInt(raw: string | null | undefined): number | undefined { - if (!raw) return undefined; - const trimmed = raw.trim(); - // Reject malformed values like "30ms" or "1_000" — parseInt would - // silently return 30 / 1, swallowing user typos as valid timeouts. - // The regex enforces pure digits (no sign, no trailing units, no - // separators) before we hand off to Number. - if (!/^\d+$/.test(trimmed)) return undefined; - const n = Number(trimmed); - return Number.isFinite(n) && n > 0 ? n : undefined; -} - diff --git a/src/providers/openrouter.ts b/src/providers/openrouter.ts deleted file mode 100644 index aa5b9bf9..00000000 --- a/src/providers/openrouter.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { MemoryProvider } from "../types.js"; -import { fetchWithTimeout } from "./_fetch.js"; - -export class OpenRouterProvider implements MemoryProvider { - name: string; - private apiKey: string; - private model: string; - private maxTokens: number; - private baseUrl: string; - - constructor( - apiKey: string, - model: string, - maxTokens: number, - baseUrl: string, - ) { - this.apiKey = apiKey; - this.model = model; - this.maxTokens = maxTokens; - this.baseUrl = baseUrl; - this.name = baseUrl.includes("openrouter") ? "openrouter" : "gemini"; - } - - async compress(systemPrompt: string, userPrompt: string): Promise { - return this.call(systemPrompt, userPrompt); - } - - async summarize(systemPrompt: string, userPrompt: string): Promise { - return this.call(systemPrompt, userPrompt); - } - - private async call( - systemPrompt: string, - userPrompt: string, - ): Promise { - const response = await fetchWithTimeout(this.baseUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${this.apiKey}`, - ...(this.baseUrl.includes("openrouter") - ? { "HTTP-Referer": "https://github.com/MaimoryLab/agentmemory-lab" } - : {}), - }, - body: JSON.stringify({ - model: this.model, - max_tokens: this.maxTokens, - messages: [ - { role: "system", content: systemPrompt }, - { role: "user", content: userPrompt }, - ], - }), - }); - - if (!response.ok) { - const text = await response.text(); - throw new Error(`${this.name} API error (${response.status}): ${text}`); - } - - const data = (await response.json()) as Record; - const choices = data.choices as - | Array<{ message: { content: string } }> - | undefined; - const content = choices?.[0]?.message?.content; - if (!content) { - throw new Error( - `${this.name} returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`, - ); - } - return content; - } -} diff --git a/src/providers/resilient.ts b/src/providers/resilient.ts deleted file mode 100644 index 95ece40c..00000000 --- a/src/providers/resilient.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { MemoryProvider, CircuitBreakerState } from "../types.js"; -import { CircuitBreaker } from "./circuit-breaker.js"; - -export class ResilientProvider implements MemoryProvider { - private breaker = new CircuitBreaker(); - name: string; - - constructor(private inner: MemoryProvider) { - this.name = `resilient(${inner.name})`; - } - - private async call(fn: () => Promise): Promise { - if (!this.breaker.isAllowed) { - throw new Error("circuit_breaker_open"); - } - try { - const result = await fn(); - this.breaker.recordSuccess(); - return result; - } catch (err) { - this.breaker.recordFailure(); - throw err; - } - } - - async compress(systemPrompt: string, userPrompt: string): Promise { - return this.call(() => this.inner.compress(systemPrompt, userPrompt)); - } - - async summarize(systemPrompt: string, userPrompt: string): Promise { - return this.call(() => this.inner.summarize(systemPrompt, userPrompt)); - } - - get circuitState(): CircuitBreakerState { - return this.breaker.getState(); - } -} diff --git a/src/replay/jsonl-parser.ts b/src/replay/jsonl-parser.ts deleted file mode 100644 index d3d85ff8..00000000 --- a/src/replay/jsonl-parser.ts +++ /dev/null @@ -1,346 +0,0 @@ -import type { HookType, RawObservation } from "../types.js"; -import { fingerprintId, generateId } from "../state/schema.js"; - -interface JsonlEntry { - type?: string; - uuid?: string; - sessionId?: string; - timestamp?: string; - cwd?: string; - payload?: Record; - message?: { - role?: string; - content?: unknown; - }; - toolUseResult?: unknown; - [k: string]: unknown; -} - -export interface ParsedTranscript { - sessionId: string; - project: string; - cwd: string; - startedAt: string; - endedAt: string; - observations: RawObservation[]; -} - -// A real cwd is an absolute filesystem path. Tool-trace pollution sometimes -// lands a shell command in the cwd field (e.g. `pwd && echo "--- branch ---" && -// git branch --show-current`), which the old "last path segment" rule turned -// into a garbage project name. Reject anything that isn't absolute or that -// carries shell-command *shape* — operators (`&&`, `||`, `$(`, backtick), a -// flag (`\s-x`/`\s--x`), or a metachar adjacent to whitespace. Bare punctuation -// in a segment name is allowed: `app/(marketing)`, `q&a-service`, `cost$` are -// legal directories, not commands. -const SHELL_POLLUTION = /&&|\|\||\$\(|`|\s--?[A-Za-z]|\s[&|;<>(){}"$]|[&|;<>(){}"$]\s/u; - -function isPlausibleCwd(cwd: string): boolean { - if (!cwd) return false; - // must be absolute: POSIX (/…, ~/…), Windows drive (C:\…), or UNC (\\…) - if (!/^(?:\/|~(?:\/|$)|[A-Za-z]:[\\/]|\\\\)/.test(cwd)) return false; - if (SHELL_POLLUTION.test(cwd)) return false; - return true; -} - -function deriveProject(cwd: string): string { - const trimmed = (cwd || "").trim(); - if (!isPlausibleCwd(trimmed)) return "unknown"; - const parts = trimmed.split(/[\\/]/).filter(Boolean); - const last = parts[parts.length - 1] || ""; - if (!last || last === "~") return "unknown"; - return last; -} - -function toText(content: unknown): string { - if (typeof content === "string") return content; - if (!Array.isArray(content)) return ""; - const parts: string[] = []; - for (const item of content) { - if (!item || typeof item !== "object") continue; - const entry = item as Record; - if (entry.type === "text" && typeof entry.text === "string") { - parts.push(entry.text); - } - } - return parts.join("\n"); -} - -function extractToolUses(content: unknown): Array<{ id: string; name: string; input: unknown }> { - if (!Array.isArray(content)) return []; - const out: Array<{ id: string; name: string; input: unknown }> = []; - for (const item of content) { - if (!item || typeof item !== "object") continue; - const entry = item as Record; - if (entry.type === "tool_use") { - out.push({ - id: typeof entry.id === "string" ? entry.id : "", - name: typeof entry.name === "string" ? entry.name : "unknown", - input: entry.input, - }); - } - } - return out; -} - -function extractToolResults(content: unknown): Array<{ toolUseId: string; output: unknown; isError: boolean }> { - if (!Array.isArray(content)) return []; - const out: Array<{ toolUseId: string; output: unknown; isError: boolean }> = []; - for (const item of content) { - if (!item || typeof item !== "object") continue; - const entry = item as Record; - if (entry.type === "tool_result") { - out.push({ - toolUseId: typeof entry.tool_use_id === "string" ? entry.tool_use_id : "", - output: entry.content, - isError: entry.is_error === true, - }); - } - } - return out; -} - -function stringField(source: Record, key: string): string { - const value = source[key]; - return typeof value === "string" ? value : ""; -} - -function codexTimestamp(entry: JsonlEntry, payload: Record): string { - return stringField(payload, "timestamp") || entry.timestamp || new Date().toISOString(); -} - -function codexTextFromContent(content: unknown): string { - if (typeof content === "string") return content; - if (!Array.isArray(content)) return ""; - const parts: string[] = []; - for (const item of content) { - if (!item || typeof item !== "object") continue; - const entry = item as Record; - if (typeof entry.text === "string") parts.push(entry.text); - } - return parts.join("\n"); -} - -function parseMaybeJson(value: unknown): unknown { - if (typeof value !== "string") return value; - try { - return JSON.parse(value); - } catch { - return value; - } -} - -function rawObject(value: unknown): Record { - return value && typeof value === "object" ? value as Record : {}; -} - -function observationFingerprintParts(obs: RawObservation, index: number): string[] { - const raw = rawObject(obs.raw); - const entry = rawObject(raw.entry ?? obs.raw); - const payload = rawObject(entry.payload); - const message = rawObject(entry.message); - const role = String(payload.role || message.role || (obs.userPrompt ? "user" : obs.assistantResponse ? "assistant" : "")); - const toolOrCallId = String( - raw.toolUseId || - payload.call_id || - payload.id || - obs.toolName || - "", - ); - return [ - obs.sessionId, - obs.timestamp, - String(entry.type || ""), - obs.hookType, - role, - String(obs.toolName || ""), - toolOrCallId, - String(index), - ]; -} - -function stabilizeObservationIds(observations: RawObservation[], sessionId: string): void { - observations.forEach((obs, index) => { - obs.sessionId = sessionId; - obs.id = fingerprintId("obs", observationFingerprintParts(obs, index).join("|")); - }); -} - -export function parseJsonlText(text: string, fallbackSessionId?: string): ParsedTranscript { - const lines = text.split("\n").filter((l) => l.trim().length > 0); - const entries: JsonlEntry[] = []; - for (const line of lines) { - try { - const parsed = JSON.parse(line); - if (parsed && typeof parsed === "object") entries.push(parsed as JsonlEntry); - } catch { - // skip malformed lines - } - } - - let sessionId = ""; - let cwd = ""; - let firstTs = ""; - let lastTs = ""; - - const observations: RawObservation[] = []; - let lastCodexTool: - | { id: string; name: string; input: unknown } - | null = null; - - for (const entry of entries) { - if (entry.sessionId && !sessionId) sessionId = entry.sessionId; - if (entry.cwd && !cwd) cwd = entry.cwd; - const payload = entry.payload || {}; - if (entry.type === "session_meta") { - if (!sessionId) sessionId = stringField(payload, "id"); - if (!cwd) cwd = stringField(payload, "cwd"); - } - const ts = entry.type === "session_meta" || entry.type === "event_msg" || entry.type === "response_item" - ? codexTimestamp(entry, payload) - : entry.timestamp || new Date().toISOString(); - if (!firstTs) firstTs = ts; - lastTs = ts; - - const role = entry.message?.role; - const content = entry.message?.content; - - if (entry.type === "session_meta") { - continue; - } else if (entry.type === "event_msg" && payload.type === "user_message") { - const prompt = stringField(payload, "message") || stringField(payload, "text") || stringField(payload, "content"); - if (prompt.trim().length > 0) { - observations.push({ - id: generateId("obs"), - sessionId: sessionId || "imported", - timestamp: ts, - hookType: "prompt_submit" as HookType, - userPrompt: prompt, - raw: entry, - }); - } - } else if (entry.type === "event_msg" && payload.type === "agent_message") { - const response = stringField(payload, "message") || stringField(payload, "text") || stringField(payload, "content"); - if (response.trim().length > 0) { - observations.push({ - id: generateId("obs"), - sessionId: sessionId || "imported", - timestamp: ts, - hookType: "stop" as HookType, - assistantResponse: response, - raw: entry, - }); - } - } else if (entry.type === "response_item" && payload.type === "message") { - const text = codexTextFromContent(payload.content); - if (text.trim().length > 0) { - observations.push({ - id: generateId("obs"), - sessionId: sessionId || "imported", - timestamp: ts, - hookType: payload.role === "user" ? "prompt_submit" as HookType : "stop" as HookType, - ...(payload.role === "user" ? { userPrompt: text } : { assistantResponse: text }), - raw: entry, - }); - } - } else if (entry.type === "response_item" && payload.type === "function_call") { - const name = stringField(payload, "name") || "unknown"; - const toolId = stringField(payload, "call_id") || stringField(payload, "id"); - const input = parseMaybeJson(payload.arguments ?? payload.input); - lastCodexTool = { id: toolId, name, input }; - observations.push({ - id: generateId("obs"), - sessionId: sessionId || "imported", - timestamp: ts, - hookType: "pre_tool_use" as HookType, - toolName: name, - toolInput: input, - raw: { toolUseId: toolId, entry }, - }); - } else if (entry.type === "response_item" && payload.type === "function_call_output") { - const toolId = stringField(payload, "call_id") || stringField(payload, "id") || lastCodexTool?.id || ""; - const toolName = lastCodexTool?.name; - observations.push({ - id: generateId("obs"), - sessionId: sessionId || "imported", - timestamp: ts, - hookType: "post_tool_use" as HookType, - toolName, - toolInput: toolId ? { toolUseId: toolId } : undefined, - toolOutput: payload.output, - raw: { toolUseId: toolId, entry }, - }); - lastCodexTool = null; - } else if (entry.type === "user" && role === "user") { - const toolResults = extractToolResults(content); - if (toolResults.length > 0) { - for (const result of toolResults) { - observations.push({ - id: generateId("obs"), - sessionId: sessionId || "imported", - timestamp: ts, - hookType: (result.isError ? "post_tool_failure" : "post_tool_use") as HookType, - toolName: undefined, - toolInput: { toolUseId: result.toolUseId }, - toolOutput: result.output, - raw: entry, - }); - } - } else { - const text = toText(content); - if (text.trim().length > 0) { - observations.push({ - id: generateId("obs"), - sessionId: sessionId || "imported", - timestamp: ts, - hookType: "prompt_submit" as HookType, - userPrompt: text, - raw: entry, - }); - } - } - } else if (entry.type === "assistant" && role === "assistant") { - const text = toText(content); - const tools = extractToolUses(content); - if (text.trim().length > 0) { - observations.push({ - id: generateId("obs"), - sessionId: sessionId || "imported", - timestamp: ts, - hookType: "stop" as HookType, - assistantResponse: text, - raw: entry, - }); - } - for (const tool of tools) { - observations.push({ - id: generateId("obs"), - sessionId: sessionId || "imported", - timestamp: ts, - hookType: "pre_tool_use" as HookType, - toolName: tool.name, - toolInput: tool.input, - raw: { toolUseId: tool.id, entry }, - }); - } - } else if (entry.type === "summary" || entry.type === "system") { - // ignore meta entries - } - } - - const effectiveSessionId = sessionId || fallbackSessionId || generateId("sess"); - stabilizeObservationIds(observations, effectiveSessionId); - - const nowIso = new Date().toISOString(); - return { - sessionId: effectiveSessionId, - project: deriveProject(cwd), - // Don't persist a polluted cwd (a shell command captured in the cwd field): - // fall back to the trusted process cwd, the same fallback already used for a - // missing cwd. Keeps the garbage out of session metadata / UI / downstream. - cwd: isPlausibleCwd(cwd) ? cwd : process.cwd(), - startedAt: firstTs || nowIso, - endedAt: lastTs || nowIso, - observations, - }; -} diff --git a/src/replay/timeline.ts b/src/replay/timeline.ts deleted file mode 100644 index 03e8051d..00000000 --- a/src/replay/timeline.ts +++ /dev/null @@ -1,166 +0,0 @@ -import type { RawObservation } from "../types.js"; - -export type TimelineEventKind = - | "prompt" - | "response" - | "tool_call" - | "tool_result" - | "tool_error" - | "hook" - | "session_start" - | "session_end"; - -export interface TimelineEvent { - id: string; - sessionId: string; - ts: string; - offsetMs: number; - durationMs: number; - kind: TimelineEventKind; - label: string; - body?: string; - toolName?: string; - toolInput?: unknown; - toolOutput?: unknown; -} - -export interface Timeline { - sessionId: string; - startedAt: string; - endedAt: string; - totalDurationMs: number; - eventCount: number; - events: TimelineEvent[]; -} - -const DEFAULT_CHARS_PER_SEC = 40; -const MIN_EVENT_MS = 300; -const MAX_EVENT_MS = 20_000; - -function kindFromHook(obs: RawObservation): TimelineEventKind { - switch (obs.hookType) { - case "session_start": - return "session_start"; - case "session_end": - return "session_end"; - case "prompt_submit": - return "prompt"; - case "stop": - return obs.assistantResponse ? "response" : "hook"; - case "pre_tool_use": - return "tool_call"; - case "post_tool_use": - return "tool_result"; - case "post_tool_failure": - return "tool_error"; - default: - return "hook"; - } -} - -function labelFor(obs: RawObservation, kind: TimelineEventKind): string { - switch (kind) { - case "prompt": - return truncate(obs.userPrompt || "User prompt", 80); - case "response": - return truncate(obs.assistantResponse || "Assistant response", 80); - case "tool_call": - return `${obs.toolName || "tool"} ▸ call`; - case "tool_result": - return `${obs.toolName || "tool"} ▸ result`; - case "tool_error": - return `${obs.toolName || "tool"} ▸ error`; - case "session_start": - return "Session start"; - case "session_end": - return "Session end"; - default: - return obs.hookType; - } -} - -function truncate(text: string, max: number): string { - if (text.length <= max) return text; - return text.slice(0, max - 1) + "…"; -} - -function bodyFor(obs: RawObservation, kind: TimelineEventKind): string | undefined { - if (kind === "prompt") return obs.userPrompt; - if (kind === "response") return obs.assistantResponse; - return undefined; -} - -function estimateDurationMs(ev: TimelineEvent): number { - const chars = - (ev.body?.length || 0) + - (typeof ev.toolInput === "string" ? ev.toolInput.length : 0) + - (typeof ev.toolOutput === "string" ? ev.toolOutput.length : 0); - if (chars === 0) return MIN_EVENT_MS; - const ms = Math.round((chars / DEFAULT_CHARS_PER_SEC) * 1000); - return Math.max(MIN_EVENT_MS, Math.min(MAX_EVENT_MS, ms)); -} - -export function projectTimeline(observations: RawObservation[]): Timeline { - if (observations.length === 0) { - const now = new Date().toISOString(); - return { - sessionId: "", - startedAt: now, - endedAt: now, - totalDurationMs: 0, - eventCount: 0, - events: [], - }; - } - - const sorted = [...observations].sort((a, b) => - a.timestamp.localeCompare(b.timestamp), - ); - - const startedAt = sorted[0].timestamp; - const startMs = Date.parse(startedAt); - const events: TimelineEvent[] = []; - - let syntheticOffset = 0; - const allSameTs = sorted.every((o) => o.timestamp === startedAt); - - for (const obs of sorted) { - const kind = kindFromHook(obs); - const body = bodyFor(obs, kind); - const obsMs = Date.parse(obs.timestamp); - const offsetMs = allSameTs - ? syntheticOffset - : Number.isFinite(obsMs) && Number.isFinite(startMs) - ? Math.max(0, obsMs - startMs) - : syntheticOffset; - - const event: TimelineEvent = { - id: obs.id, - sessionId: obs.sessionId, - ts: obs.timestamp, - offsetMs, - durationMs: 0, - kind, - label: labelFor(obs, kind), - body, - toolName: obs.toolName, - toolInput: obs.toolInput, - toolOutput: obs.toolOutput, - }; - event.durationMs = estimateDurationMs(event); - events.push(event); - syntheticOffset += event.durationMs; - } - - const last = events[events.length - 1]; - const totalDurationMs = last.offsetMs + last.durationMs; - - return { - sessionId: sorted[0].sessionId, - startedAt, - endedAt: sorted[sorted.length - 1].timestamp, - totalDurationMs, - eventCount: events.length, - events, - }; -} diff --git a/src/server/index.ts b/src/server/index.ts new file mode 100644 index 00000000..04db2821 --- /dev/null +++ b/src/server/index.ts @@ -0,0 +1,269 @@ +import { createReadStream, existsSync } from "node:fs"; +import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; +import { extname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { loadConfig, loadSecrets, parseSettingsUpdate, publicConfig, saveEnvConfig, settingsToEnv } from "../config.js"; +import type { Database } from "../db/index.js"; +import { getAppPaths, type AppPaths } from "../paths.js"; +import { ingestBrowserSession, validateBrowserSessionInput } from "../sources/browser.js"; +import { scanConfiguredSources, scanSource as scanSourceSessions, type ConfiguredScanSummary } from "../sources/scan.js"; +import { listSessionObservations, listSessions, listSources } from "../sources/service.js"; +import { organizeConfiguredTodos } from "../todos/configured.js"; +import { getOrganizeRun, listTodoEvidence, listTodos, type OrganizeOptions, updateTodoStatus } from "../todos/service.js"; + +const PUBLIC_DIR = fileURLToPath(new URL("../../../public/", import.meta.url)); + +export type StartupScanStatus = { + status: "idle" | "indexing" | "ready" | "failed"; + startedAt?: string; + finishedAt?: string; + sources: ConfiguredScanSummary["sources"]; + warnings: string[]; +}; + +export function createStartupScanner(db: Database, paths: AppPaths): { status: StartupScanStatus; start: () => void } { + const status: StartupScanStatus = { status: "idle", sources: [], warnings: [] }; + let running = false; + return { + status, + start: () => { + if (running || status.status === "ready") return; + running = true; + status.status = "indexing"; + status.startedAt = new Date().toISOString(); + setImmediate(() => { + try { + const result = scanConfiguredSources(db, paths); + status.sources = result.sources; + status.warnings = result.warnings; + status.status = result.warnings.length > 0 ? "failed" : "ready"; + } catch (error) { + status.warnings = [(error as Error).message || "startup_scan_failed"]; + status.status = "failed"; + } finally { + status.finishedAt = new Date().toISOString(); + running = false; + } + }); + } + }; +} + +export function createAppServer(options: { + db?: Database; + paths?: AppPaths; + organizeOptions?: OrganizeOptions; + startupScan?: StartupScanStatus; +} = {}) { + const paths = options.paths ?? getAppPaths(); + return createServer(async (req, res) => { + const path = new URL(req.url ?? "/", "http://localhost").pathname; + + if (req.method === "GET" && (path === "/" || path.startsWith("/app."))) { + if (serveStatic(res, path)) return; + } + + if (req.method === "GET" && path === "/favicon.ico") { + res.writeHead(204); + res.end(); + return; + } + + if (req.method === "GET" && path === "/healthz") { + writeJson(res, 200, { ok: true }); + return; + } + + if (req.method === "GET" && path === "/startup/scan") { + writeJson(res, 200, options.startupScan ?? { status: "idle", sources: [], warnings: [] }); + return; + } + + if (req.method === "GET" && path === "/settings") { + try { + writeJson(res, 200, publicConfig(loadConfig(paths), loadSecrets(paths))); + } catch { + writeJson(res, 500, { error: "config_invalid" }); + } + return; + } + + if (req.method === "PUT" && path === "/settings") { + const body = await readJson(req, res); + if (!body) return; + try { + const currentSecrets = loadSecrets(paths); + const { config, apiKey } = parseSettingsUpdate(body); + saveEnvConfig(paths, settingsToEnv(config, currentSecrets, apiKey)); + writeJson(res, 200, publicConfig(config, loadSecrets(paths))); + } catch { + writeJson(res, 400, { error: "config_invalid" }); + } + return; + } + + if (req.method === "GET" && path === "/sources") { + const db = requireDb(res, options.db); + if (!db) return; + writeJson(res, 200, listSources(db)); + return; + } + + if (req.method === "POST" && path === "/sources/scan") { + const db = requireDb(res, options.db); + if (!db) return; + const body = await readJson(req, res); + if (!body) return; + const result = scanSource(db, body, paths); + writeJson(res, result.status, result.body); + return; + } + + if (req.method === "GET" && path === "/sessions") { + const db = requireDb(res, options.db); + if (!db) return; + writeJson(res, 200, listSessions(db)); + return; + } + + const sessionObservationsMatch = path.match(/^\/sessions\/([^/]+)\/observations$/); + if (req.method === "GET" && sessionObservationsMatch) { + const db = requireDb(res, options.db); + if (!db) return; + const observations = listSessionObservations(db, decodeURIComponent(sessionObservationsMatch[1])); + if (!observations) { + writeJson(res, 404, { error: "session_not_found" }); + return; + } + writeJson(res, 200, observations); + return; + } + + if (req.method === "POST" && path === "/browser/sessions") { + const db = requireDb(res, options.db); + if (!db) return; + const body = await readJson(req, res); + if (!body) return; + const validated = validateBrowserSessionInput(body); + if (!validated.ok) { + writeJson(res, 400, { error: validated.error }); + return; + } + writeJson(res, 200, ingestBrowserSession(db, validated.input)); + return; + } + + if (req.method === "POST" && path === "/todos/organize") { + const db = requireDb(res, options.db); + if (!db) return; + try { + writeJson(res, 200, await organizeConfiguredTodos(db, paths, options.organizeOptions)); + } catch (error) { + writeJson(res, 500, { + error: "organize_failed", + warnings: ["organize_failed"], + message: (error as Error).message + }); + } + return; + } + + if (req.method === "GET" && path === "/todos") { + const db = requireDb(res, options.db); + if (!db) return; + writeJson(res, 200, listTodos(db)); + return; + } + + const todoEvidenceMatch = path.match(/^\/todos\/([^/]+)\/evidence$/); + if (req.method === "GET" && todoEvidenceMatch) { + const db = requireDb(res, options.db); + if (!db) return; + const evidence = listTodoEvidence(db, decodeURIComponent(todoEvidenceMatch[1])); + if (!evidence) { + writeJson(res, 404, { error: "todo_not_found" }); + return; + } + writeJson(res, 200, evidence); + return; + } + + const todoMatch = path.match(/^\/todos\/([^/]+)$/); + if (req.method === "PATCH" && todoMatch) { + const db = requireDb(res, options.db); + if (!db) return; + const body = await readJson(req, res); + if (!body) return; + if (body.status !== "done" && body.status !== "ignored") { + writeJson(res, 400, { error: "invalid_status" }); + return; + } + const id = decodeURIComponent(todoMatch[1]); + if (!updateTodoStatus(db, id, body.status)) { + writeJson(res, 404, { error: "todo_not_found" }); + return; + } + writeJson(res, 200, listTodos(db).find((todo) => todo.id === id)); + return; + } + + const organizeRunMatch = path.match(/^\/organize-runs\/([^/]+)$/); + if (req.method === "GET" && organizeRunMatch) { + const db = requireDb(res, options.db); + if (!db) return; + const run = getOrganizeRun(db, decodeURIComponent(organizeRunMatch[1])); + if (!run) { + writeJson(res, 404, { error: "organize_run_not_found" }); + return; + } + writeJson(res, 200, run); + return; + } + + writeJson(res, 404, { error: "not_found" }); + }); +} + +function serveStatic(res: ServerResponse, path: string): boolean { + const filename = path === "/" ? "index.html" : path.slice(1); + if (!/^(index\.html|app\.css|app\.js)$/.test(filename)) return false; + const file = join(PUBLIC_DIR, filename); + if (!existsSync(file)) return false; + res.writeHead(200, { "content-type": contentType(file) }); + createReadStream(file).pipe(res); + return true; +} + +function contentType(file: string): string { + if (extname(file) === ".css") return "text/css; charset=utf-8"; + if (extname(file) === ".js") return "text/javascript; charset=utf-8"; + return "text/html; charset=utf-8"; +} + +function writeJson(res: ServerResponse, status: number, body: unknown): void { + res.writeHead(status, { "content-type": "application/json" }); + res.end(JSON.stringify(body)); +} + +function requireDb(res: ServerResponse, db: Database | undefined): Database | null { + if (db) return db; + writeJson(res, 503, { error: "database_unavailable" }); + return null; +} + +async function readJson(req: IncomingMessage, res: ServerResponse): Promise { + const chunks: Buffer[] = []; + for await (const chunk of req) chunks.push(Buffer.from(chunk)); + try { + return JSON.parse(Buffer.concat(chunks).toString("utf8")); + } catch { + writeJson(res, 400, { error: "invalid_json" }); + return null; + } +} + +function scanSource(db: Database, body: any, paths: AppPaths) { + const scan = scanSourceSessions(db, body?.source, body?.path, paths); + if (!scan.ok) return { status: scan.status, body: { error: scan.error } }; + return { status: 200, body: scan.result }; +} diff --git a/src/sources/browser.ts b/src/sources/browser.ts new file mode 100644 index 00000000..781f5fdd --- /dev/null +++ b/src/sources/browser.ts @@ -0,0 +1,83 @@ +import { createHash } from "node:crypto"; +import type { SourceKind } from "../contracts.js"; +import type { Database } from "../db/index.js"; + +export interface BrowserSessionInput { + id?: string; + path?: string; + messages: Array<{ role?: string; text: string; createdAt?: string }>; +} + +export function validateBrowserSessionInput(input: unknown): { ok: true; input: BrowserSessionInput } | { ok: false; error: string } { + if (!input || typeof input !== "object" || Array.isArray(input)) { + return { ok: false, error: "invalid_body" }; + } + const record = input as Record; + if (record.id !== undefined && !nonEmptyString(record.id)) return { ok: false, error: "invalid_id" }; + if (record.path !== undefined && !nonEmptyString(record.path)) return { ok: false, error: "invalid_path" }; + if (!Array.isArray(record.messages) || record.messages.length === 0) { + return { ok: false, error: "invalid_messages" }; + } + + const messages = []; + for (const message of record.messages) { + if (!message || typeof message !== "object" || Array.isArray(message)) { + return { ok: false, error: "invalid_message" }; + } + const item = message as Record; + if (!nonEmptyString(item.text)) return { ok: false, error: "invalid_message_text" }; + if (item.role !== undefined && !nonEmptyString(item.role)) return { ok: false, error: "invalid_message_role" }; + if (item.createdAt !== undefined && (!nonEmptyString(item.createdAt) || Number.isNaN(Date.parse(item.createdAt)))) { + return { ok: false, error: "invalid_message_created_at" }; + } + messages.push({ + role: typeof item.role === "string" ? item.role.trim() : undefined, + text: item.text.trim(), + createdAt: typeof item.createdAt === "string" ? item.createdAt : undefined + }); + } + + return { + ok: true, + input: { + id: typeof record.id === "string" ? record.id.trim() : undefined, + path: typeof record.path === "string" ? record.path.trim() : undefined, + messages + } + }; +} + +export function ingestBrowserSession(db: Database, input: BrowserSessionInput) { + const source: SourceKind = "browser"; + const sessionId = input.id ?? hash(JSON.stringify(input.messages)); + const path = input.path ?? "browser"; + const updatedAt = new Date().toISOString(); + + db.prepare( + "INSERT OR REPLACE INTO sessions (id, source, path, updated_at) VALUES (?, ?, ?, ?)" + ).run(sessionId, source, path, updatedAt); + db.prepare("DELETE FROM observations WHERE session_id = ?").run(sessionId); + + for (const [index, message] of input.messages.entries()) { + db.prepare( + "INSERT OR REPLACE INTO observations (id, session_id, source, role, text, created_at) VALUES (?, ?, ?, ?, ?, ?)" + ).run( + hash(sessionId, String(index)), + sessionId, + source, + message.role ?? "unknown", + message.text, + message.createdAt ?? updatedAt + ); + } + + return { sessionId, observations: input.messages.length }; +} + +function nonEmptyString(value: unknown): value is string { + return typeof value === "string" && value.trim().length > 0; +} + +function hash(...parts: string[]): string { + return createHash("sha1").update(parts.join("\0")).digest("hex"); +} diff --git a/src/sources/claude-code.ts b/src/sources/claude-code.ts new file mode 100644 index 00000000..9d7930e3 --- /dev/null +++ b/src/sources/claude-code.ts @@ -0,0 +1,6 @@ +import type { Database } from "../db/index.js"; +import { scanJsonlSource } from "./jsonl-source.js"; + +export function scanClaudeCodeSessions(db: Database, root: string) { + return scanJsonlSource(db, "claude-code", root); +} diff --git a/src/sources/codex.ts b/src/sources/codex.ts new file mode 100644 index 00000000..42d6164f --- /dev/null +++ b/src/sources/codex.ts @@ -0,0 +1,6 @@ +import type { Database } from "../db/index.js"; +import { scanJsonlSource } from "./jsonl-source.js"; + +export function scanCodexSessions(db: Database, root: string) { + return scanJsonlSource(db, "codex", root); +} diff --git a/src/sources/jsonl-source.ts b/src/sources/jsonl-source.ts new file mode 100644 index 00000000..9565c242 --- /dev/null +++ b/src/sources/jsonl-source.ts @@ -0,0 +1,335 @@ +import { createHash } from "node:crypto"; +import { readdirSync, statSync } from "node:fs"; +import { join } from "node:path"; +import type { SourceKind } from "../contracts.js"; +import type { Database } from "../db/index.js"; +import { readJsonlFile, type JsonlRecord } from "../parser/jsonl.js"; + +export interface ScanResult { + source: SourceKind; + scanned: number; + observations: number; + skipped: number; +} + +export function scanJsonlSource(db: Database, source: SourceKind, root: string): ScanResult { + let scanned = 0; + let observations = 0; + let skipped = 0; + + for (const path of listJsonlFiles(root)) { + const stat = statSync(path); + const checkpoint = db.prepare( + "SELECT mtime_ms, size FROM scan_checkpoints WHERE source = ? AND path = ?" + ).get(source, path) as { mtime_ms: number; size: number } | undefined; + + if (checkpoint?.mtime_ms === stat.mtimeMs && checkpoint.size === stat.size) { + skipped++; + continue; + } + + const records = readJsonlFile(path); + const sessionId = sessionIdFromRecords(source, path, records); + const updatedAt = new Date(stat.mtimeMs).toISOString(); + const cleanObservations = observationsFromRecords(source, sessionId, path, records); + if (cleanObservations.length === 0) { + db.prepare("DELETE FROM observations WHERE session_id = ?").run(sessionId); + db.prepare("DELETE FROM sessions WHERE id = ?").run(sessionId); + db.prepare( + "INSERT OR REPLACE INTO scan_checkpoints (source, path, mtime_ms, size) VALUES (?, ?, ?, ?)" + ).run(source, path, stat.mtimeMs, stat.size); + scanned++; + continue; + } + + db.prepare( + "INSERT OR REPLACE INTO sessions (id, source, path, updated_at) VALUES (?, ?, ?, ?)" + ).run(sessionId, source, path, updatedAt); + db.prepare("DELETE FROM observations WHERE session_id = ?").run(sessionId); + + for (const observation of cleanObservations) { + db.prepare( + "INSERT OR REPLACE INTO observations (id, session_id, source, role, text, created_at) VALUES (?, ?, ?, ?, ?, ?)" + ).run(observation.id, sessionId, source, observation.role, observation.text, observation.createdAt); + observations++; + } + + db.prepare( + "INSERT OR REPLACE INTO scan_checkpoints (source, path, mtime_ms, size) VALUES (?, ?, ?, ?)" + ).run(source, path, stat.mtimeMs, stat.size); + scanned++; + } + + return { source, scanned, observations, skipped }; +} + +export function observationsFromRecords( + source: SourceKind, + sessionId: string, + path: string, + records: JsonlRecord[] +): Array<{ id: string; role: string; text: string; createdAt: string }> { + const candidates = records + .map((record) => observationFromRecord(source, sessionId, path, record)) + .filter((observation): observation is NonNullable => !!observation) + .sort((a, b) => observationPriority(b.channel) - observationPriority(a.channel)); + const seen = new Set(); + const selected: Array>> = []; + for (const observation of candidates) { + const key = `${observation.role}\0${normalizeDedupeText(observation.text)}`; + if (seen.has(key)) continue; + seen.add(key); + selected.push(observation); + } + return selected + .sort((a, b) => a.line - b.line) + .map(({ id, role, text, createdAt }) => ({ id, role, text, createdAt })); +} + +export function observationFromRecord( + source: SourceKind, + sessionId: string, + path: string, + record: JsonlRecord +): { id: string; role: string; text: string; createdAt: string; channel: string; line: number } | null { + const observation = source === "claude-code" + ? claudeObservationFromRecord(record.value) + : codexObservationFromRecord(record.value); + if (!observation) return null; + const text = cleanVisibleText(source, observation.text); + if (!text) return null; + return { + id: idFor(source, path, String(record.line)), + role: observation.role, + text, + createdAt: observation.createdAt, + channel: observation.channel, + line: record.line + }; +} + +function listJsonlFiles(root: string): string[] { + const stat = statSync(root); + if (stat.isFile()) return root.endsWith(".jsonl") ? [root] : []; + + return readdirSync(root, { withFileTypes: true }).flatMap((entry) => { + const path = join(root, entry.name); + if (entry.isDirectory()) return listJsonlFiles(path); + return entry.isFile() && entry.name.endsWith(".jsonl") ? [path] : []; + }); +} + +function sessionIdFromRecords(source: SourceKind, path: string, records: JsonlRecord[]): string { + for (const record of records) { + const meta = record.value.type === "session_meta" ? objectValue(record.value.payload) : null; + const id = stringValue(meta?.id); + if (id) return idFor(source, id); + } + return idFor(source, path); +} + +function codexObservationFromRecord(value: Record): { role: string; text: string; createdAt: string; channel: string } | null { + const type = stringValue(value.type); + const payload = objectValue(value.payload); + if (type === "event_msg" && payload) { + const eventType = stringValue(payload.type); + if (eventType !== "user_message" && eventType !== "agent_message") return null; + return { + role: eventType === "user_message" ? "user" : "assistant", + text: firstString(payload.message, payload.text) || textFromContent(payload.content, eventType === "user_message" ? "user" : "assistant"), + createdAt: timestampFrom(value, payload), + channel: "event_msg" + }; + } + if (type === "response_item" && payload) { + if (stringValue(payload.type) !== "message") return null; + const role = visibleRole(payload.role); + if (!role) return null; + return { + role, + text: textFromContent(payload.content, role), + createdAt: timestampFrom(value, payload), + channel: "response_item" + }; + } + + const role = visibleRole(value.role) ?? visibleRole(objectValue(value.message)?.role); + if (!role) return null; + const message = objectValue(value.message); + return { + role, + text: message ? textFromMessage(message, role) : textFromMessage(value, role), + createdAt: timestampFrom(value, message), + channel: "message" + }; +} + +function claudeObservationFromRecord(value: Record): { role: string; text: string; createdAt: string; channel: string } | null { + if (value.isMeta === true || value.isSidechain === true) return null; + const type = stringValue(value.type); + const message = objectValue(value.message); + if (message?.isMeta === true || message?.isSidechain === true) return null; + const role = visibleRole(message?.role) ?? visibleRole(value.role) ?? visibleRole(type); + if (!role) return null; + if (type && type !== role && type !== "message") return null; + return { + role, + text: message ? textFromMessage(message, role) : textFromMessage(value, role), + createdAt: timestampFrom(value, message), + channel: type || "message" + }; +} + +function textFromMessage(message: Record, role: string): string { + return firstString(message.text) + || textFromContent(message.content, role) + || ""; +} + +function textFromContent(content: unknown, role: string): string { + if (typeof content === "string") return content; + if (!Array.isArray(content)) return ""; + return content.map((part) => textPart(part, role)).filter(Boolean).join("\n").trim(); +} + +function textPart(part: unknown, role: string): string { + if (typeof part === "string") return part; + if (!part || typeof part !== "object" || Array.isArray(part)) return ""; + const record = part as Record; + const type = stringValue(record.type); + const isVisibleText = type === "text" + || (role === "user" && type === "input_text") + || (role === "assistant" && type === "output_text") + || (!type && typeof record.text === "string"); + return isVisibleText ? stringValue(record.text) ?? "" : ""; +} + +function visibleRole(value: unknown): "user" | "assistant" | null { + return value === "user" || value === "assistant" ? value : null; +} + +function timestampFrom(primary: Record, secondary?: Record | null): string { + return stringValue(primary.timestamp) + ?? stringValue(primary.created_at) + ?? stringValue(secondary?.timestamp) + ?? stringValue(secondary?.created_at) + ?? new Date(0).toISOString(); +} + +function firstString(...values: unknown[]): string { + for (const value of values) { + const text = stringValue(value); + if (text) return text; + } + return ""; +} + +function cleanVisibleText(source: SourceKind, value: string): string { + let text = value.trim(); + if (!text) return ""; + if (source === "claude-code" && looksLikeClaudeControlText(text)) return ""; + text = text + .replace(/# AGENTS\.md instructions[\s\S]*?<\/INSTRUCTIONS>/g, "") + .replace(/[\s\S]*?<\/environment_context>/g, "") + .replace(/[\s\S]*?<\/system-reminder>/g, "") + .replace(/[\s\S]*?<\/permissions instructions>/g, "") + .replace(/[\s\S]*?<\/skills_instructions>/g, "") + .replace(/[\s\S]*?<\/plugins_instructions>/g, "") + .replace(/^# In app browser:[\s\S]*?## My request for Codex:\s*/m, "") + .replace(/^# Browser comments:[\s\S]*?## My request for Codex:\s*/m, "") + .replace(/^## My request for Codex:\s*/m, "") + .replace(/^The next image is untrusted page evidence[\s\S]*?instructions\.\s*/m, "") + .trim(); + if (!text) return ""; + const noisyPrefixes = [ + "# AGENTS.md instructions", + "Automation:", + "", + "", + "", + "", + "", + "Codebase and user instructions are shown below.", + "IMPORTANT: These instructions OVERRIDE", + "Contents of ", + "You are Codex", + "Filesystem sandboxing defines", + "Response MUST end with" + ]; + if (noisyPrefixes.some((prefix) => text.startsWith(prefix))) return ""; + if (source === "claude-code" && looksLikeClaudeControlText(text)) return ""; + if (looksLikeToolTrace(text)) return ""; + return text + .split(/\r?\n/) + .map((line) => line.trimEnd()) + .join("\n") + .replace(/\n{3,}/g, "\n\n") + .trim(); +} + +function looksLikeClaudeControlText(text: string): boolean { + const trimmed = text.trim(); + if (!trimmed) return false; + if (/^[\s\S]*<\/command-name>\s*[\s\S]*<\/command-message>/i.test(trimmed)) return true; + if (/^/i.test(trimmed)) return true; + if (/^/i.test(trimmed)) return true; + if (/^/i.test(trimmed)) return true; + if (/^/i.test(trimmed)) return true; + if (/^\/(?:compact|cost|doctor|exit|help|login|logout|memory|model|permissions|resume|status|vim-mode)\b/i.test(trimmed)) return true; + return false; +} + +function looksLikeToolTrace(text: string): boolean { + const trimmed = text.trim(); + if (!trimmed) return false; + if (/^\[Request interrupted by user for tool use\]/i.test(trimmed)) return true; + if (/^The user doesn't want to proceed with this tool use/i.test(trimmed)) return true; + if (/^/i.test(trimmed)) return true; + if (looksLikeToolJson(trimmed)) return true; + if (/^(?:Bash|Shell|Read|Write|Edit|MultiEdit|TodoWrite|WebFetch|Grep|Glob)\(/.test(trimmed)) return true; + if (/^(?:toolInput|toolOutput|function_id|exec_command|apply_patch)\b/.test(trimmed)) return true; + if (/^⏺/.test(trimmed)) return true; + return false; +} + +function observationPriority(channel: string): number { + if (channel === "response_item") return 3; + if (channel === "message") return 2; + return 1; +} + +function normalizeDedupeText(text: string): string { + return text.replace(/\s+/g, " ").trim(); +} + +function looksLikeToolJson(text: string): boolean { + if (!/^(?:\{|\[)/.test(text)) return false; + try { + const parsed = JSON.parse(text); + const records = Array.isArray(parsed) ? parsed : [parsed]; + return records.some((record) => { + const value = objectValue(record); + if (!value) return false; + const type = stringValue(value.type); + if (type && ["tool_use", "tool_result", "function_call", "function_call_output", "custom_tool_call", "custom_tool_call_output"].includes(type)) { + return true; + } + return ["toolInput", "toolOutput", "function_id"].some((key) => key in value); + }); + } catch { + return false; + } +} + +function objectValue(value: unknown): Record | null { + return value && typeof value === "object" && !Array.isArray(value) ? value as Record : null; +} + +function stringValue(value: unknown): string | null { + return typeof value === "string" && value.trim() ? value.trim() : null; +} + +function idFor(...parts: string[]): string { + return createHash("sha1").update(parts.join("\0")).digest("hex"); +} diff --git a/src/sources/scan.ts b/src/sources/scan.ts new file mode 100644 index 00000000..19566ebe --- /dev/null +++ b/src/sources/scan.ts @@ -0,0 +1,100 @@ +import { existsSync } from "node:fs"; +import { homedir } from "node:os"; +import { basename, join } from "node:path"; +import { loadConfig } from "../config.js"; +import type { SourceKind } from "../contracts.js"; +import type { Database } from "../db/index.js"; +import { getAppPaths, type AppPaths } from "../paths.js"; +import { scanClaudeCodeSessions } from "./claude-code.js"; +import { scanCodexSessions } from "./codex.js"; +import type { ScanResult } from "./jsonl-source.js"; + +export type SessionSource = Extract; + +export type SourceScanResult = + | { ok: true; result: ScanResult; path: string } + | { ok: false; status: 400; error: "unsupported_source" | "path_not_found" }; + +export interface ConfiguredScanSummary { + sources: Array<{ source: SessionSource; path: string; result?: ScanResult; warning?: string }>; + warnings: string[]; +} + +export function resolveSourcePath(source: SessionSource, explicitPath?: string, paths: AppPaths = getAppPaths()): string { + return resolveSourcePaths(source, explicitPath, paths)[0]; +} + +export function resolveSourcePaths(source: SessionSource, explicitPath?: string, paths: AppPaths = getAppPaths()): string[] { + const configured = sourceBasePath(source, explicitPath, paths); + if (source === "codex") return codexSessionRoots(configured); + return [configured]; +} + +function sourceBasePath(source: SessionSource, explicitPath?: string, paths: AppPaths = getAppPaths()): string { + if (explicitPath) return explicitPath; + const config = loadConfig(paths); + if (source === "codex") return envPath(process.env.AI_TODO_CODEX_HOME) ?? config.sources.codex.path ?? join(homedir(), ".codex"); + return envPath(process.env.AI_TODO_CLAUDE_HOME) ?? config.sources["claude-code"].path ?? join(homedir(), ".claude", "projects"); +} + +export function scanSource(db: Database, source: unknown, explicitPath?: unknown, paths: AppPaths = getAppPaths()): SourceScanResult { + if (!isSessionSource(source)) { + return { ok: false, status: 400, error: "unsupported_source" }; + } + const roots = resolveSourcePaths(source, typeof explicitPath === "string" && explicitPath ? explicitPath : undefined, paths); + const existingRoots = roots.filter((path) => existsSync(path)); + if (existingRoots.length === 0) { + return { ok: false, status: 400, error: "path_not_found" }; + } + const result = aggregateScanResults(existingRoots.map((path) => source === "codex" + ? scanCodexSessions(db, path) + : scanClaudeCodeSessions(db, path))); + return { ok: true, result, path: roots.join(", ") }; +} + +export function scanConfiguredSources(db: Database, paths: AppPaths = getAppPaths()): ConfiguredScanSummary { + const sources: ConfiguredScanSummary["sources"] = []; + const warnings: string[] = []; + for (const source of ["codex", "claude-code"] as const) { + const roots = resolveSourcePaths(source, undefined, paths); + const existingRoots = roots.filter((path) => existsSync(path)); + if (existingRoots.length === 0) { + const warning = `${source}_path_not_found`; + warnings.push(warning); + sources.push({ source, path: roots.join(", "), warning }); + continue; + } + const result = aggregateScanResults(existingRoots.map((path) => source === "codex" + ? scanCodexSessions(db, path) + : scanClaudeCodeSessions(db, path))); + sources.push({ source, path: roots.join(", "), result }); + } + return { sources, warnings }; +} + +export function isSessionSource(source: unknown): source is SessionSource { + return source === "codex" || source === "claude-code"; +} + +function envPath(value: string | undefined): string | undefined { + return value && value.trim() ? value : undefined; +} + +function codexSessionRoots(path: string): string[] { + const sessions = join(path, "sessions"); + const archived = join(path, "archived_sessions"); + if (basename(path) === ".codex" || existsSync(sessions) || existsSync(archived)) { + return [sessions, archived]; + } + return [path]; +} + +function aggregateScanResults(results: ScanResult[]): ScanResult { + const first = results[0]; + return { + source: first.source, + scanned: results.reduce((sum, result) => sum + result.scanned, 0), + observations: results.reduce((sum, result) => sum + result.observations, 0), + skipped: results.reduce((sum, result) => sum + result.skipped, 0) + }; +} diff --git a/src/sources/service.ts b/src/sources/service.ts new file mode 100644 index 00000000..acf65037 --- /dev/null +++ b/src/sources/service.ts @@ -0,0 +1,78 @@ +import type { ObservationRecord, SessionRecord, SourceKind } from "../contracts.js"; +import type { Database } from "../db/index.js"; + +const SOURCES: SourceKind[] = ["codex", "claude-code", "browser"]; + +export function listSources(db: Database) { + return SOURCES.map((source) => ({ + source, + sessions: count(db, "sessions", source), + checkpoints: count(db, "scan_checkpoints", source) + })); +} + +export function listSessions(db: Database): SessionRecord[] { + return db.prepare( + `SELECT + sessions.id, + sessions.source, + sessions.path, + sessions.updated_at as updatedAt, + COUNT(observations.id) as observationCount, + COALESCE(( + SELECT text + FROM observations preview + WHERE preview.session_id = sessions.id + AND preview.role IN ('user', 'assistant') + ORDER BY preview.created_at, preview.id + LIMIT 1 + ), '') as preview + FROM sessions + JOIN observations ON observations.session_id = sessions.id + GROUP BY sessions.id + HAVING observationCount > 0 + ORDER BY sessions.updated_at DESC` + ).all().map((row) => { + const record = row as Record; + return { + id: String(record.id), + source: record.source as SessionRecord["source"], + path: String(record.path), + updatedAt: String(record.updatedAt), + observationCount: Number(record.observationCount), + preview: String(record.preview) + }; + }); +} + +export function listSessionObservations(db: Database, sessionId: string): ObservationRecord[] | null { + const session = db.prepare("SELECT id FROM sessions WHERE id = ?").get(sessionId); + if (!session) return null; + return db.prepare( + `SELECT + id, + session_id as sessionId, + source, + role, + text, + created_at as createdAt + FROM observations + WHERE session_id = ? + ORDER BY created_at, id` + ).all(sessionId).map((row) => { + const record = row as Record; + return { + id: String(record.id), + sessionId: String(record.sessionId), + source: record.source as ObservationRecord["source"], + role: String(record.role), + text: String(record.text), + createdAt: String(record.createdAt) + }; + }); +} + +function count(db: Database, table: "sessions" | "scan_checkpoints", source: SourceKind): number { + const row = db.prepare(`SELECT COUNT(*) as count FROM ${table} WHERE source = ?`).get(source) as { count: number }; + return row.count; +} diff --git a/src/state/cjk-segmenter.ts b/src/state/cjk-segmenter.ts deleted file mode 100644 index d6d4a191..00000000 --- a/src/state/cjk-segmenter.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { createRequire } from "node:module"; - -const cjkRequire = createRequire(import.meta.url); - -const CJK_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u; -const HAN_RE = /\p{Script=Han}/u; -const KANA_RE = /[\p{Script=Hiragana}\p{Script=Katakana}]/u; -const HANGUL_RE = /\p{Script=Hangul}/u; -const CJK_RUN_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+/gu; -const HANGUL_BLOCK_RE = /[가-힯]+/g; - -type Script = "han" | "kana" | "hangul" | "other"; - -const hintShown = new Set(); - -export function hasCjk(text: string): boolean { - return CJK_RE.test(text); -} - -export function detectScript(text: string): Script { - if (HAN_RE.test(text)) return "han"; - if (KANA_RE.test(text)) return "kana"; - if (HANGUL_RE.test(text)) return "hangul"; - return "other"; -} - -function showHintOnce(key: string, message: string): void { - if (hintShown.has(key)) return; - hintShown.add(key); - if (typeof process !== "undefined" && process.stderr?.write) { - process.stderr.write(`agentmemory: ${message}\n`); - } -} - -interface JiebaInstance { - cut(text: string, hmm?: boolean): string[]; -} - -let jiebaInstance: JiebaInstance | null = null; -let jiebaLoaded = false; - -function getJieba(): JiebaInstance | null { - if (jiebaLoaded) return jiebaInstance; - jiebaLoaded = true; - try { - const mod = cjkRequire("@node-rs/jieba") as { - Jieba: { - new (): JiebaInstance; - withDict(dict: Uint8Array): JiebaInstance; - }; - }; - try { - const dictMod = cjkRequire("@node-rs/jieba/dict") as { dict: Uint8Array }; - jiebaInstance = mod.Jieba.withDict(dictMod.dict); - } catch { - jiebaInstance = new mod.Jieba(); - } - return jiebaInstance; - } catch { - showHintOnce( - "jieba", - "install @node-rs/jieba to improve Chinese search; falling back to whole-string tokenization", - ); - return null; - } -} - -interface JaSegmenter { - segment(text: string): string[]; -} - -let jaSegmenterInstance: JaSegmenter | null = null; -let jaSegmenterLoaded = false; - -function getJaSegmenter(): JaSegmenter | null { - if (jaSegmenterLoaded) return jaSegmenterInstance; - jaSegmenterLoaded = true; - try { - const Ctor = cjkRequire("tiny-segmenter") as new () => JaSegmenter; - jaSegmenterInstance = new Ctor(); - return jaSegmenterInstance; - } catch { - showHintOnce( - "tiny-segmenter", - "install tiny-segmenter to improve Japanese search; falling back to whole-string tokenization", - ); - return null; - } -} - -function cleanTokens(tokens: string[]): string[] { - const out: string[] = []; - for (const t of tokens) { - const trimmed = t.trim(); - if (trimmed) out.push(trimmed); - } - return out; -} - -function segmentHan(text: string): string[] { - const j = getJieba(); - if (!j) return [text]; - try { - return cleanTokens(j.cut(text, true)); - } catch { - return [text]; - } -} - -function segmentKana(text: string): string[] { - const s = getJaSegmenter(); - if (!s) return [text]; - try { - return cleanTokens(s.segment(text)); - } catch { - return [text]; - } -} - -function segmentHangul(text: string): string[] { - const out: string[] = []; - for (const m of text.matchAll(HANGUL_BLOCK_RE)) { - if (m[0]) out.push(m[0]); - } - return out; -} - -export function segmentCjk(text: string): string[] { - if (!hasCjk(text)) return [text]; - - const out: string[] = []; - let cursor = 0; - - for (const m of text.matchAll(CJK_RUN_RE)) { - const start = m.index ?? 0; - const run = m[0]; - const end = start + run.length; - - if (start > cursor) { - const piece = text.slice(cursor, start).trim(); - if (piece) out.push(piece); - } - - if (HANGUL_RE.test(run)) { - out.push(...segmentHangul(run)); - } else if (KANA_RE.test(run)) { - out.push(...segmentKana(run)); - } else { - out.push(...segmentHan(run)); - } - - cursor = end; - } - - if (cursor < text.length) { - const trailing = text.slice(cursor).trim(); - if (trailing) out.push(trailing); - } - - return out; -} - -export function __resetCjkSegmenterStateForTests(): void { - hintShown.clear(); - jiebaInstance = null; - jiebaLoaded = false; - jaSegmenterInstance = null; - jaSegmenterLoaded = false; -} diff --git a/src/state/hybrid-search.ts b/src/state/hybrid-search.ts deleted file mode 100644 index d234a3ef..00000000 --- a/src/state/hybrid-search.ts +++ /dev/null @@ -1,324 +0,0 @@ -import { SearchIndex } from "./search-index.js"; -import { VectorIndex } from "./vector-index.js"; -import type { - EmbeddingProvider, - HybridSearchResult, - CompressedObservation, - Memory, - QueryExpansion, -} from "../types.js"; -import { memoryToObservation } from "./memory-utils.js"; -import type { StateKV } from "./kv.js"; -import { KV } from "./schema.js"; -import { - GraphRetrieval, - type GraphRetrievalResult, -} from "../functions/graph-retrieval.js"; -import { extractEntitiesFromQuery } from "../functions/query-expansion.js"; -import { rerank } from "./reranker.js"; - -const RRF_K = 60; - -export class HybridSearch { - private graphRetrieval: GraphRetrieval; - - constructor( - private bm25: SearchIndex, - private vector: VectorIndex | null, - private embeddingProvider: EmbeddingProvider | null, - private kv: StateKV, - private bm25Weight = 0.4, - private vectorWeight = 0.6, - private graphWeight = 0.3, - private rerankEnabled = process.env.RERANK_ENABLED === "true", - ) { - this.graphRetrieval = new GraphRetrieval(kv); - } - - async search(query: string, limit = 20): Promise { - return this.tripleStreamSearch(query, limit); - } - - async searchWithExpansion( - query: string, - limit: number, - expansion: QueryExpansion, - ): Promise { - const allQueries = [ - query, - ...expansion.reformulations, - ...expansion.temporalConcretizations, - ]; - - const allEntities = [ - ...expansion.entityExtractions, - ...extractEntitiesFromQuery(query), - ]; - - const resultSets = await Promise.all( - allQueries.map((q) => this.tripleStreamSearch(q, limit, allEntities)), - ); - - const merged = new Map(); - for (const results of resultSets) { - for (const r of results) { - const existing = merged.get(r.observation.id); - if (!existing || r.combinedScore > existing.combinedScore) { - merged.set(r.observation.id, r); - } - } - } - - return Array.from(merged.values()) - .sort((a, b) => b.combinedScore - a.combinedScore) - .slice(0, limit); - } - - private async tripleStreamSearch( - query: string, - limit: number, - entityHints?: string[], - ): Promise { - const bm25Results = this.bm25.search(query, limit * 2); - - let vectorResults: Array<{ - obsId: string; - sessionId: string; - score: number; - }> = []; - let queryEmbedding: Float32Array | null = null; - - if (this.vector && this.embeddingProvider && this.vector.size > 0) { - try { - queryEmbedding = await this.embeddingProvider.embed(query); - vectorResults = this.vector.search(queryEmbedding, limit * 2); - } catch { - // fall through to BM25-only - } - } - - const entities = - entityHints && entityHints.length > 0 - ? entityHints - : extractEntitiesFromQuery(query); - let graphResults: GraphRetrievalResult[] = []; - if (entities.length > 0) { - try { - graphResults = await this.graphRetrieval.searchByEntities( - entities, - 2, - limit, - ); - } catch { - // graph search is best-effort - } - } - - const topVectorObs = vectorResults.slice(0, 5).map((r) => r.obsId); - if (topVectorObs.length > 0) { - try { - const expansionResults = - await this.graphRetrieval.expandFromChunks(topVectorObs, 1, 5); - graphResults = [...graphResults, ...expansionResults]; - } catch { - // expansion is best-effort - } - } - - const scores = new Map< - string, - { - bm25Rank: number; - vectorRank: number; - graphRank: number; - sessionId: string; - bm25Score: number; - vectorScore: number; - graphScore: number; - graphContext?: string; - } - >(); - - bm25Results.forEach((r, i) => { - scores.set(r.obsId, { - bm25Rank: i + 1, - vectorRank: Infinity, - graphRank: Infinity, - sessionId: r.sessionId, - bm25Score: r.score, - vectorScore: 0, - graphScore: 0, - }); - }); - - vectorResults.forEach((r, i) => { - const existing = scores.get(r.obsId); - if (existing) { - existing.vectorRank = i + 1; - existing.vectorScore = r.score; - } else { - scores.set(r.obsId, { - bm25Rank: Infinity, - vectorRank: i + 1, - graphRank: Infinity, - sessionId: r.sessionId, - bm25Score: 0, - vectorScore: r.score, - graphScore: 0, - }); - } - }); - - graphResults.forEach((r, i) => { - const existing = scores.get(r.obsId); - if (existing) { - existing.graphRank = Math.min(existing.graphRank, i + 1); - existing.graphScore = Math.max(existing.graphScore, r.score); - if (r.graphContext && !existing.graphContext) { - existing.graphContext = r.graphContext; - } - } else { - scores.set(r.obsId, { - bm25Rank: Infinity, - vectorRank: Infinity, - graphRank: i + 1, - sessionId: r.sessionId, - bm25Score: 0, - vectorScore: 0, - graphScore: r.score, - graphContext: r.graphContext, - }); - } - }); - - const hasVector = vectorResults.length > 0; - const hasGraph = graphResults.length > 0; - - let effectiveBm25W = this.bm25Weight; - let effectiveVectorW = hasVector ? this.vectorWeight : 0; - let effectiveGraphW = hasGraph ? this.graphWeight : 0; - - const totalW = effectiveBm25W + effectiveVectorW + effectiveGraphW; - if (totalW > 0) { - effectiveBm25W /= totalW; - effectiveVectorW /= totalW; - effectiveGraphW /= totalW; - } - - const combined = Array.from(scores.entries()).map(([obsId, s]) => ({ - obsId, - sessionId: s.sessionId, - bm25Score: s.bm25Score, - vectorScore: s.vectorScore, - graphScore: s.graphScore, - graphContext: s.graphContext, - combinedScore: - effectiveBm25W * (1 / (RRF_K + s.bm25Rank)) + - effectiveVectorW * (1 / (RRF_K + s.vectorRank)) + - effectiveGraphW * (1 / (RRF_K + s.graphRank)), - })); - - combined.sort((a, b) => b.combinedScore - a.combinedScore); - - const retrievalDepth = Math.max(limit, 20); - const rerankWindow = 20; - const diversified = this.diversifyBySession(combined, retrievalDepth); - const enriched = await this.enrichResults(diversified, retrievalDepth); - - if (this.rerankEnabled && enriched.length > 1) { - try { - const head = enriched.slice(0, rerankWindow); - const tail = enriched.slice(rerankWindow); - const reranked = await rerank(query, head, rerankWindow); - return reranked.concat(tail).slice(0, limit); - } catch { - return enriched.slice(0, limit); - } - } - - return enriched.slice(0, limit); - } - - private diversifyBySession( - results: Array<{ - obsId: string; - sessionId: string; - bm25Score: number; - vectorScore: number; - graphScore: number; - combinedScore: number; - graphContext?: string; - }>, - limit: number, - maxPerSession = 3, - ): typeof results { - const selected: typeof results = []; - const sessionCounts = new Map(); - - for (const r of results) { - const count = sessionCounts.get(r.sessionId) || 0; - if (count >= maxPerSession) continue; - selected.push(r); - sessionCounts.set(r.sessionId, count + 1); - if (selected.length >= limit) break; - } - - if (selected.length < limit) { - for (const r of results) { - if (selected.length >= limit) break; - if (!selected.some(s => s.obsId === r.obsId)) { - selected.push(r); - } - } - } - - return selected; - } - - private async enrichResults( - results: Array<{ - obsId: string; - sessionId: string; - bm25Score: number; - vectorScore: number; - graphScore: number; - combinedScore: number; - graphContext?: string; - }>, - limit: number, - ): Promise { - const sliced = results.slice(0, limit); - const observations = await Promise.all( - sliced.map(async (r) => { - const obs = await this.kv - .get(KV.observations(r.sessionId), r.obsId) - .catch(() => null); - if (obs) return obs; - // Fallback: indexed entry may originate from mem::remember, which - // writes to KV.memories with a synthetic sessionId ("memory" or the - // memory's first associated session). Coerce the Memory record into - // a CompressedObservation so search/recall surface saved memories. - const mem = await this.kv - .get(KV.memories, r.obsId) - .catch(() => null); - return mem ? memoryToObservation(mem) : null; - }), - ); - const enriched: HybridSearchResult[] = []; - for (let i = 0; i < sliced.length; i++) { - const obs = observations[i]; - if (obs) { - enriched.push({ - observation: obs, - bm25Score: sliced[i].bm25Score, - vectorScore: sliced[i].vectorScore, - graphScore: sliced[i].graphScore, - combinedScore: sliced[i].combinedScore, - sessionId: sliced[i].sessionId, - graphContext: sliced[i].graphContext, - }); - } - } - return enriched; - } -} diff --git a/src/state/index-persistence.ts b/src/state/index-persistence.ts deleted file mode 100644 index 7e484f57..00000000 --- a/src/state/index-persistence.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { SearchIndex } from "./search-index.js"; -import { VectorIndex } from "./vector-index.js"; -import type { StateKV } from "./kv.js"; -import { KV } from "./schema.js"; -import { logger } from "../logger.js"; - -const DEBOUNCE_MS = 5000; -const FAILURE_LOG_THROTTLE_MS = 60_000; - -export class IndexPersistence { - private timer: ReturnType | null = null; - private lastFailureLogAt = 0; - - constructor( - private kv: StateKV, - private bm25: SearchIndex, - private vector: VectorIndex | null, - ) {} - - scheduleSave(): void { - if (this.timer) clearTimeout(this.timer); - // setTimeout discards the returned promise, so any rejection inside - // save() would surface as unhandledRejection and crash the process - // under sustained iii-engine write timeouts (issue #204). Funnel - // rejections through logFailure() instead. - this.timer = setTimeout(() => { - this.save().catch((err) => this.logFailure(err)); - }, DEBOUNCE_MS); - } - - async save(): Promise { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; - } - try { - await this.kv.set(KV.bm25Index, "data", this.bm25.serialize()); - if (this.vector && this.vector.size > 0) { - await this.kv.set(KV.bm25Index, "vectors", this.vector.serialize()); - } - } catch (err) { - this.logFailure(err); - } - } - - async load(): Promise<{ - bm25: SearchIndex | null; - vector: VectorIndex | null; - }> { - let bm25: SearchIndex | null = null; - let vector: VectorIndex | null = null; - - const bm25Data = await this.kv - .get(KV.bm25Index, "data") - .catch(() => null); - if (bm25Data && typeof bm25Data === "string") { - bm25 = SearchIndex.deserialize(bm25Data); - } - - const vecData = await this.kv - .get(KV.bm25Index, "vectors") - .catch(() => null); - if (vecData && typeof vecData === "string") { - vector = VectorIndex.deserialize(vecData); - } - - return { bm25, vector }; - } - - stop(): void { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; - } - } - - private logFailure(err: unknown): void { - const now = Date.now(); - // Throttle: persistence failures under load arrive in bursts - // (iii-engine queue pressure). Logging every debounce flush adds - // noise without information. - if (now - this.lastFailureLogAt < FAILURE_LOG_THROTTLE_MS) return; - this.lastFailureLogAt = now; - const code = (err as { code?: string })?.code; - const message = err instanceof Error ? err.message : String(err); - logger.warn("index persistence: failed to save BM25/vector index", { - code, - message, - hint: - code === "TIMEOUT" - ? "iii-engine state::set timed out; recent index updates remain in memory and will retry on the next debounce flush" - : undefined, - }); - } -} diff --git a/src/state/keyed-mutex.ts b/src/state/keyed-mutex.ts deleted file mode 100644 index b28531a6..00000000 --- a/src/state/keyed-mutex.ts +++ /dev/null @@ -1,18 +0,0 @@ -const locks = new Map>(); - -export function withKeyedLock( - key: string, - fn: () => Promise, -): Promise { - const prev = locks.get(key) ?? Promise.resolve(); - const next = prev.then(fn, fn); - const cleanup = next.then( - () => {}, - () => {}, - ); - locks.set(key, cleanup); - cleanup.then(() => { - if (locks.get(key) === cleanup) locks.delete(key); - }); - return next; -} diff --git a/src/state/kv.ts b/src/state/kv.ts deleted file mode 100644 index 0c0e6ccc..00000000 --- a/src/state/kv.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { ISdk } from 'iii-sdk' - -export class StateKV { - constructor(private sdk: ISdk) {} - - async get(scope: string, key: string): Promise { - return this.sdk.trigger<{ scope: string; key: string }, T | null>({ - function_id: 'state::get', - payload: { scope, key }, - }) - } - - async set(scope: string, key: string, value: T): Promise { - return this.sdk.trigger<{ scope: string; key: string; value: T }, T>({ - function_id: 'state::set', - payload: { scope, key, value }, - }) - } - - async update( - scope: string, - key: string, - ops: Array<{ type: string; path: string; value?: unknown }>, - ): Promise { - return this.sdk.trigger< - { scope: string; key: string; ops: Array<{ type: string; path: string; value?: unknown }> }, - T - >({ - function_id: 'state::update', - payload: { scope, key, ops }, - }) - } - - async delete(scope: string, key: string): Promise { - return this.sdk.trigger<{ scope: string; key: string }, void>({ - function_id: 'state::delete', - payload: { scope, key }, - }) - } - - async list(scope: string): Promise { - return this.sdk.trigger<{ scope: string }, T[]>({ - function_id: 'state::list', - payload: { scope }, - }) - } -} diff --git a/src/state/memory-utils.ts b/src/state/memory-utils.ts deleted file mode 100644 index aa0bcc5b..00000000 --- a/src/state/memory-utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { CompressedObservation, Memory } from "../types.js"; - -// Wraps a Memory record in the CompressedObservation shape that -// SearchIndex / VectorIndex / enrichment paths consume. Memories share -// the same searchable fields as observations (title + content + -// concepts + files); type is normalized to "decision" so memories stay -// distinguishable in result metadata without colliding with observation -// enums (file_read, command_run, …). The synthetic sessionId -// ("memory" or memory.sessionIds[0]) is what enrich-side fallbacks key -// off of when looking up the source record in KV.memories. -export function memoryToObservation(memory: Memory): CompressedObservation { - return { - id: memory.id, - sessionId: memory.sessionIds?.[0] ?? "memory", - timestamp: memory.createdAt, - type: "decision", - title: memory.title, - facts: [memory.content], - narrative: memory.content, - concepts: memory.concepts, - files: memory.files, - importance: memory.strength, - }; -} diff --git a/src/state/reranker.ts b/src/state/reranker.ts deleted file mode 100644 index d0aae68c..00000000 --- a/src/state/reranker.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { HybridSearchResult } from "../types.js"; - -let pipeline: any = null; -let pipelineLoading: Promise | null = null; -let pipelineUnavailable = false; - -async function loadPipeline(): Promise { - if (pipelineUnavailable) return null; - if (pipeline) return pipeline; - if (pipelineLoading) return pipelineLoading; - - pipelineLoading = (async () => { - try { - const { pipeline: createPipeline } = await import( - "@xenova/transformers" - ); - pipeline = await createPipeline( - "text-classification", - "Xenova/ms-marco-MiniLM-L-6-v2", - { quantized: true }, - ); - return pipeline; - } catch { - pipeline = null; - pipelineUnavailable = true; - return null; - } finally { - pipelineLoading = null; - } - })(); - return pipelineLoading; -} - -export async function rerank( - query: string, - results: HybridSearchResult[], - topK = 20, -): Promise { - if (results.length <= 1) return results; - - const reranker = await loadPipeline(); - if (!reranker) return results; - - const candidates = results.slice(0, Math.min(results.length, topK)); - - const pairs = candidates.map((r) => ({ - text: `${query} [SEP] ${r.observation.title || ""} ${r.observation.narrative || ""}`.slice(0, 512), - result: r, - })); - - const scores: Array<{ result: HybridSearchResult; rerankScore: number }> = []; - - for (const pair of pairs) { - try { - const output = await reranker(pair.text); - const score = Array.isArray(output) ? output[0]?.score ?? 0 : 0; - scores.push({ result: pair.result, rerankScore: score }); - } catch { - scores.push({ result: pair.result, rerankScore: pair.result.combinedScore }); - } - } - - scores.sort((a, b) => b.rerankScore - a.rerankScore); - - return scores.map((s, i) => ({ - ...s.result, - combinedScore: s.rerankScore, - rerankPosition: i + 1, - })); -} - -export function isRerankerAvailable(): boolean { - return pipeline !== null; -} diff --git a/src/state/schema.ts b/src/state/schema.ts deleted file mode 100644 index bb877a28..00000000 --- a/src/state/schema.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { createHash } from "node:crypto"; - -export const KV = { - sessions: "mem:sessions", - observations: (sessionId: string) => `mem:obs:${sessionId}`, - memories: "mem:memories", - summaries: "mem:summaries", - config: "mem:config", - metrics: "mem:metrics", - health: "mem:health", - embeddings: (obsId: string) => `mem:emb:${obsId}`, - bm25Index: "mem:index:bm25", - relations: "mem:relations", - profiles: "mem:profiles", - claudeBridge: "mem:claude-bridge", - graphNodes: "mem:graph:nodes", - graphEdges: "mem:graph:edges", - semantic: "mem:semantic", - procedural: "mem:procedural", - teamShared: (teamId: string) => `mem:team:${teamId}:shared`, - teamUsers: (teamId: string, userId: string) => - `mem:team:${teamId}:users:${userId}`, - teamProfile: (teamId: string) => `mem:team:${teamId}:profile`, - audit: "mem:audit", - actions: "mem:actions", - actionEdges: "mem:action-edges", - leases: "mem:leases", - routines: "mem:routines", - routineRuns: "mem:routine-runs", - signals: "mem:signals", - inbox: "mem:inbox", - delivery: "mem:delivery", - checkpoints: "mem:checkpoints", - mesh: "mem:mesh", - sketches: "mem:sketches", - facets: "mem:facets", - sentinels: "mem:sentinels", - crystals: "mem:crystals", - lessons: "mem:lessons", - reviewQueue: "mem:review-queue", - insights: "mem:insights", - graphEdgeHistory: "mem:graph:edge-history", - enrichedChunks: (sessionId: string) => `mem:enriched:${sessionId}`, - latentEmbeddings: (obsId: string) => `mem:latent:${obsId}`, - retentionScores: "mem:retention", - accessLog: "mem:access", - imageRefs: "mem:image-refs", - imageEmbeddings: "mem:image-embeddings", - slots: "mem:slots", - globalSlots: "mem:slots:global", - state: "mem:state", - commits: "mem:commits", - sources: "mem:sources", - scanCheckpoints: "mem:scan-checkpoints", -} as const; - -export const STREAM = { - name: "mem-live", - group: (sessionId: string) => sessionId, - viewerGroup: "viewer", -} as const; - -export function generateId(prefix: string): string { - const ts = Date.now().toString(36); - const rand = crypto.randomUUID().replace(/-/g, "").slice(0, 12); - return `${prefix}_${ts}_${rand}`; -} - -export function fingerprintId(prefix: string, content: string): string { - const hash = createHash("sha256").update(content).digest("hex"); - return `${prefix}_${hash.slice(0, 16)}`; -} - -export function jaccardSimilarity(a: string, b: string): number { - const setA = new Set(a.split(/\s+/).filter((t) => t.length > 2)); - const setB = new Set(b.split(/\s+/).filter((t) => t.length > 2)); - if (setA.size === 0 && setB.size === 0) return 1; - if (setA.size === 0 || setB.size === 0) return 0; - let intersection = 0; - for (const word of setA) { - if (setB.has(word)) intersection++; - } - return intersection / (setA.size + setB.size - intersection); -} - -// Canonical form used for BOTH similarity scoring and the length floor: -// lowercased, whitespace + punctuation/symbols stripped. The floor must see -// the same string the scorer does, or a whitespace-inflated short title (e.g. -// "fix ci" → "fixci", 5 chars) sneaks past a raw-length gate into the unstable -// sub-floor band and wrongly collapses with "fix cd". -function similarityChars(value: string): string { - return String(value || "").toLowerCase().replace(/[\s\p{P}\p{S}]+/gu, ""); -} - -/** - * Sørensen–Dice similarity over adjacent-character bigrams. Unlike - * {@link jaccardSimilarity} (which splits on whitespace and so collapses a - * whole space-free Chinese sentence into a single token), this works for CJK - * text — the near-duplicate todo titles the rules/LLM extractors emit are - * mostly Chinese. Strings are lowercased and stripped of whitespace and - * punctuation/symbols first. Identical content → 1; disjoint → 0. Strings - * shorter than 2 chars (no bigrams) fall back to exact equality. - */ -export function charBigramSimilarity(a: string, b: string): number { - const ca = Array.from(similarityChars(a)); - const cb = Array.from(similarityChars(b)); - if (ca.length === 0 && cb.length === 0) return 1; - if (ca.length < 2 || cb.length < 2) return ca.join("") === cb.join("") ? 1 : 0; - const bigrams = (chars: string[]): Map => { - const counts = new Map(); - for (let i = 0; i < chars.length - 1; i++) { - const gram = chars[i] + chars[i + 1]; - counts.set(gram, (counts.get(gram) || 0) + 1); - } - return counts; - }; - const ma = bigrams(ca); - const mb = bigrams(cb); - let overlap = 0; - for (const [gram, count] of ma) { - const other = mb.get(gram); - if (other) overlap += Math.min(count, other); - } - const total = ca.length - 1 + (cb.length - 1); - return total === 0 ? 0 : (2 * overlap) / total; -} - -// Near-duplicate todo-title policy. Surface similarity alone over-collapses: -// a single negation ("支持…" vs "不支持…"), a different number/version -// ("v2" vs "v3", "Node 18" vs "Node 20"), or one differing English word -// ("login" vs "logout") barely dents the bigram score yet flips the meaning. -// So we require BOTH high surface similarity AND the absence of any *salient* -// discriminator. The 0.70 bar is set above the ~0.667 band of pure two-char -// CJK substitutions (项目/仓库, 失效/超时 — possibly distinct tasks, kept apart) -// and below the ~0.74–0.95 band of insertions / word reorderings (genuine -// rewordings of one task). Tunable here in one place; both extractors call it. -const NEAR_DUP_THRESHOLD = 0.7; -const NEAR_DUP_MIN_CHARS = 6; -const LATIN_STOPWORDS = new Set([ - "the", "a", "an", "to", "of", "in", "on", "for", "and", "or", "with", - "is", "be", "this", "that", "at", "by", "fix", "add", "update", "remove", - "create", "support", "enable", -]); - -function negationCount(text: string): number { - let count = 0; - const cjk = text.match(/[不未别勿无無非否莫禁]/gu); - if (cjk) count += cjk.length; - const latin = text - .toLowerCase() - .replace(/['’]/g, "") - .match(/\b(no|not|never|without|none|off|cannot|cant|dont|doesnt|wont|isnt|disable|disabled|hide|hidden)\b/g); - if (latin) count += latin.length; - return count; -} - -function numericTokens(text: string): string[] { - const tokens: string[] = []; - const nums = text.toLowerCase().match(/v?\d+(?:\.\d+)*/g); - if (nums) tokens.push(...nums); - const cjkOrdinals = text.match(/第[一二三四五六七八九十百零〇0-9]+|[一二三四五六七八九十][阶章期版步代号]/gu); - if (cjkOrdinals) tokens.push(...cjkOrdinals); - return tokens.sort(); -} - -function latinContentWords(text: string): Set { - const words = text.toLowerCase().match(/[a-z][a-z0-9]{2,}/g) || []; - return new Set(words.filter((word) => !LATIN_STOPWORDS.has(word))); -} - -function sameStringList(a: string[], b: string[]): boolean { - return a.length === b.length && a.every((value, i) => value === b[i]); -} - -function sameStringSet(a: Set, b: Set): boolean { - if (a.size !== b.size) return false; - for (const value of a) if (!b.has(value)) return false; - return true; -} - -/** - * True when two todo titles are near-duplicate rewordings of the SAME task. - * Conservative by design: requires {@link charBigramSimilarity} ≥ 0.70 AND no - * salient discriminator — a polarity flip, a differing number/version/ordinal, - * or a differing Latin content word each VETO the collapse so distinct tasks - * are never silently merged. Titles whose canonical form is shorter than 6 - * chars (unstable under bigram similarity) are never treated as near-dupes. - * - * The discriminators err toward keeping titles APART: a surviving duplicate is - * cheap to dismiss, a wrong merge loses work. So some genuine rewordings are - * intentionally NOT collapsed — version-format drift (v2.0 vs 2.0, 1.2.0 vs - * 1.2), English inflection (add vs adds), and feature morphemes counted as - * negation (无密码 vs 免密码). That recall loss is the accepted tradeoff. - * Inputs should be normalized (lowercased, punctuation-stripped) titles. - */ -export function nearDuplicateTitle(a: string, b: string): boolean { - if (Array.from(similarityChars(a)).length < NEAR_DUP_MIN_CHARS || - Array.from(similarityChars(b)).length < NEAR_DUP_MIN_CHARS) { - return false; - } - if (charBigramSimilarity(a, b) < NEAR_DUP_THRESHOLD) return false; - if (negationCount(a) !== negationCount(b)) return false; - if (!sameStringList(numericTokens(a), numericTokens(b))) return false; - if (!sameStringSet(latinContentWords(a), latinContentWords(b))) return false; - return true; -} diff --git a/src/state/search-index.ts b/src/state/search-index.ts deleted file mode 100644 index 67f3e2ef..00000000 --- a/src/state/search-index.ts +++ /dev/null @@ -1,281 +0,0 @@ -import type { CompressedObservation } from "../types.js"; -import { stem } from "./stemmer.js"; -import { getSynonyms } from "./synonyms.js"; -import { segmentCjk, hasCjk } from "./cjk-segmenter.js"; - -interface IndexEntry { - obsId: string; - sessionId: string; - termCount: number; -} - -export class SearchIndex { - private entries: Map = new Map(); - private invertedIndex: Map> = new Map(); - private docTermCounts: Map> = new Map(); - private totalDocLength = 0; - private sortedTerms: string[] | null = null; - - private readonly k1 = 1.2; - private readonly b = 0.75; - - add(obs: CompressedObservation): void { - const terms = this.extractTerms(obs); - const termFreq = new Map(); - let termCount = 0; - - for (const term of terms) { - termFreq.set(term, (termFreq.get(term) || 0) + 1); - termCount++; - } - - this.entries.set(obs.id, { - obsId: obs.id, - sessionId: obs.sessionId, - termCount, - }); - this.docTermCounts.set(obs.id, termFreq); - this.totalDocLength += termCount; - - for (const term of termFreq.keys()) { - if (!this.invertedIndex.has(term)) { - this.invertedIndex.set(term, new Set()); - } - this.invertedIndex.get(term)!.add(obs.id); - } - - this.sortedTerms = null; - } - - has(id: string): boolean { - return this.entries.has(id); - } - - remove(id: string): void { - const entry = this.entries.get(id); - if (!entry) return; - - const termFreq = this.docTermCounts.get(id); - if (termFreq) { - for (const term of termFreq.keys()) { - const postingList = this.invertedIndex.get(term); - if (postingList) { - postingList.delete(id); - if (postingList.size === 0) { - this.invertedIndex.delete(term); - } - } - } - this.docTermCounts.delete(id); - } - - this.totalDocLength = Math.max(0, this.totalDocLength - entry.termCount); - this.entries.delete(id); - this.sortedTerms = null; - } - - search( - query: string, - limit = 20, - ): Array<{ obsId: string; sessionId: string; score: number }> { - const rawTerms = this.tokenize(query.toLowerCase()); - if (rawTerms.length === 0) return []; - - const N = this.entries.size; - if (N === 0) return []; - const avgDocLen = this.totalDocLength / N; - - const queryTerms: Array<{ term: string; weight: number }> = []; - const seen = new Set(); - for (const term of rawTerms) { - if (!seen.has(term)) { - seen.add(term); - queryTerms.push({ term, weight: 1.0 }); - } - for (const syn of getSynonyms(term)) { - if (!seen.has(syn)) { - seen.add(syn); - queryTerms.push({ term: syn, weight: 0.7 }); - } - } - } - - const scores = new Map(); - const sorted = this.getSortedTerms(); - - for (const { term, weight } of queryTerms) { - const matchingDocs = this.invertedIndex.get(term); - if (matchingDocs) { - const df = matchingDocs.size; - const idf = Math.log((N - df + 0.5) / (df + 0.5) + 1); - - for (const obsId of matchingDocs) { - const entry = this.entries.get(obsId)!; - const docTerms = this.docTermCounts.get(obsId); - const tf = docTerms?.get(term) || 0; - const docLen = entry.termCount; - - const numerator = tf * (this.k1 + 1); - const denominator = - tf + this.k1 * (1 - this.b + this.b * (docLen / avgDocLen)); - const bm25Score = idf * (numerator / denominator) * weight; - - scores.set(obsId, (scores.get(obsId) || 0) + bm25Score); - } - } - - const startIdx = this.lowerBound(sorted, term); - for (let si = startIdx; si < sorted.length; si++) { - const indexTerm = sorted[si]; - if (!indexTerm.startsWith(term)) break; - if (indexTerm === term) continue; - - const obsIds = this.invertedIndex.get(indexTerm)!; - const prefixDf = obsIds.size; - const prefixIdf = - Math.log((N - prefixDf + 0.5) / (prefixDf + 0.5) + 1) * 0.5; - for (const obsId of obsIds) { - const entry = this.entries.get(obsId)!; - const docTerms = this.docTermCounts.get(obsId); - const tf = docTerms?.get(indexTerm) || 0; - const docLen = entry.termCount; - const numerator = tf * (this.k1 + 1); - const denominator = - tf + this.k1 * (1 - this.b + this.b * (docLen / avgDocLen)); - scores.set( - obsId, - (scores.get(obsId) || 0) + prefixIdf * (numerator / denominator) * weight, - ); - } - } - } - - return Array.from(scores.entries()) - .map(([obsId, score]) => { - const entry = this.entries.get(obsId)!; - return { obsId, sessionId: entry.sessionId, score }; - }) - .sort((a, b) => b.score - a.score) - .slice(0, limit); - } - - get size(): number { - return this.entries.size; - } - - clear(): void { - this.entries.clear(); - this.invertedIndex.clear(); - this.docTermCounts.clear(); - this.totalDocLength = 0; - this.sortedTerms = null; - } - - restoreFrom(other: SearchIndex): void { - this.entries = new Map( - Array.from(other.entries.entries()).map(([k, v]) => [k, { ...v }]), - ); - this.invertedIndex = new Map( - Array.from(other.invertedIndex.entries()).map(([k, v]) => [ - k, - new Set(v), - ]), - ); - this.docTermCounts = new Map( - Array.from(other.docTermCounts.entries()).map(([k, v]) => [ - k, - new Map(v), - ]), - ); - this.totalDocLength = other.totalDocLength; - this.sortedTerms = null; - } - - serialize(): string { - const entries = Array.from(this.entries.entries()); - const inverted = Array.from(this.invertedIndex.entries()).map( - ([term, ids]) => [term, Array.from(ids)] as [string, string[]], - ); - const docTerms = Array.from(this.docTermCounts.entries()).map( - ([id, counts]) => - [id, Array.from(counts.entries())] as [string, [string, number][]], - ); - return JSON.stringify({ - v: 2, - entries, - inverted, - docTerms, - totalDocLength: this.totalDocLength, - }); - } - - static deserialize(json: string): SearchIndex { - try { - const idx = new SearchIndex(); - const data = JSON.parse(json); - if (!data?.entries || !data?.inverted || !data?.docTerms) return idx; - for (const [key, val] of data.entries) { - idx.entries.set(key, val); - } - for (const [term, ids] of data.inverted) { - idx.invertedIndex.set(term, new Set(ids)); - } - for (const [id, counts] of data.docTerms) { - idx.docTermCounts.set(id, new Map(counts)); - } - const rawLen = Number(data.totalDocLength); - idx.totalDocLength = - Number.isFinite(rawLen) && rawLen >= 0 ? Math.floor(rawLen) : 0; - return idx; - } catch { - return new SearchIndex(); - } - } - - private extractTerms(obs: CompressedObservation): string[] { - const parts = [ - obs.title, - obs.subtitle || "", - obs.narrative, - ...obs.facts, - ...obs.concepts, - ...obs.files, - obs.type, - ]; - return this.tokenize(parts.join(" ").toLowerCase()); - } - - private tokenize(text: string): string[] { - const cleaned = text.replace(/[^\p{L}\p{N}\s/.\\-_]/gu, " "); - const out: string[] = []; - for (const raw of cleaned.split(/\s+/)) { - if (raw.length < 2) continue; - if (hasCjk(raw)) { - for (const seg of segmentCjk(raw)) { - if (seg.length >= 1) out.push(seg); - } - } else { - out.push(stem(raw)); - } - } - return out; - } - - private getSortedTerms(): string[] { - if (!this.sortedTerms) { - this.sortedTerms = Array.from(this.invertedIndex.keys()).sort(); - } - return this.sortedTerms; - } - - private lowerBound(arr: string[], target: string): number { - let lo = 0; - let hi = arr.length; - while (lo < hi) { - const mid = (lo + hi) >>> 1; - if (arr[mid] < target) lo = mid + 1; - else hi = mid; - } - return lo; - } -} diff --git a/src/state/stemmer.ts b/src/state/stemmer.ts deleted file mode 100644 index 7f210960..00000000 --- a/src/state/stemmer.ts +++ /dev/null @@ -1,104 +0,0 @@ -const step2map: Record = { - ational: "ate", tional: "tion", enci: "ence", anci: "ance", - izer: "ize", iser: "ise", abli: "able", alli: "al", - entli: "ent", eli: "e", ousli: "ous", ization: "ize", - isation: "ise", ation: "ate", ator: "ate", alism: "al", - iveness: "ive", fulness: "ful", ousness: "ous", aliti: "al", - iviti: "ive", biliti: "ble", -}; - -const step3map: Record = { - icate: "ic", ative: "", alize: "al", alise: "al", - iciti: "ic", ical: "ic", ful: "", ness: "", -}; - -function hasVowel(s: string): boolean { - return /[aeiou]/.test(s); -} - -function measure(s: string): number { - const reduced = s.replace(/[^aeiouy]+/g, "C").replace(/[aeiouy]+/g, "V"); - const m = reduced.match(/VC/g); - return m ? m.length : 0; -} - -function endsDoubleConsonant(s: string): boolean { - return s.length >= 2 && s[s.length - 1] === s[s.length - 2] && !/[aeiou]/.test(s[s.length - 1]); -} - -function endsCVC(s: string): boolean { - if (s.length < 3) return false; - const c1 = s[s.length - 3], v = s[s.length - 2], c2 = s[s.length - 1]; - return !/[aeiou]/.test(c1) && /[aeiou]/.test(v) && !/[aeiouwxy]/.test(c2); -} - -export function stem(word: string): string { - if (word.length <= 2) return word; - - let w = word; - - if (w.endsWith("sses")) w = w.slice(0, -2); - else if (w.endsWith("ies")) w = w.slice(0, -2); - else if (!w.endsWith("ss") && w.endsWith("s")) w = w.slice(0, -1); - - if (w.endsWith("eed")) { - if (measure(w.slice(0, -3)) > 0) w = w.slice(0, -1); - } else if (w.endsWith("ed") && hasVowel(w.slice(0, -2))) { - w = w.slice(0, -2); - if (w.endsWith("at") || w.endsWith("bl") || w.endsWith("iz")) w += "e"; - else if (endsDoubleConsonant(w) && !/[lsz]$/.test(w)) w = w.slice(0, -1); - else if (measure(w) === 1 && endsCVC(w)) w += "e"; - } else if (w.endsWith("ing") && hasVowel(w.slice(0, -3))) { - w = w.slice(0, -3); - if (w.endsWith("at") || w.endsWith("bl") || w.endsWith("iz")) w += "e"; - else if (endsDoubleConsonant(w) && !/[lsz]$/.test(w)) w = w.slice(0, -1); - else if (measure(w) === 1 && endsCVC(w)) w += "e"; - } - - if (w.endsWith("y") && hasVowel(w.slice(0, -1))) { - w = w.slice(0, -1) + "i"; - } - - for (const [suffix, replacement] of Object.entries(step2map)) { - if (w.endsWith(suffix)) { - const base = w.slice(0, -suffix.length); - if (measure(base) > 0) w = base + replacement; - break; - } - } - - for (const [suffix, replacement] of Object.entries(step3map)) { - if (w.endsWith(suffix)) { - const base = w.slice(0, -suffix.length); - if (measure(base) > 0) w = base + replacement; - break; - } - } - - if (w.endsWith("al") || w.endsWith("ance") || w.endsWith("ence") || - w.endsWith("er") || w.endsWith("ic") || w.endsWith("able") || - w.endsWith("ible") || w.endsWith("ant") || w.endsWith("ement") || - w.endsWith("ment") || w.endsWith("ent") || w.endsWith("tion") || - w.endsWith("sion") || w.endsWith("ou") || w.endsWith("ism") || - w.endsWith("ate") || w.endsWith("iti") || w.endsWith("ous") || - w.endsWith("ive") || w.endsWith("ize") || w.endsWith("ise")) { - const suffixLen = w.match(/(ement|ment|tion|sion|ance|ence|able|ible|ism|ate|iti|ous|ive|ize|ise|ant|ent|al|er|ic|ou)$/)?.[0]?.length ?? 0; - if (suffixLen > 0) { - const base = w.slice(0, -suffixLen); - if (measure(base) > 1) w = base; - } - } - - if (w.endsWith("e")) { - const base = w.slice(0, -1); - if (measure(base) > 1 || (measure(base) === 1 && !endsCVC(base))) { - w = base; - } - } - - if (endsDoubleConsonant(w) && w.endsWith("l") && measure(w.slice(0, -1)) > 1) { - w = w.slice(0, -1); - } - - return w; -} diff --git a/src/state/synonyms.ts b/src/state/synonyms.ts deleted file mode 100644 index 0dab4157..00000000 --- a/src/state/synonyms.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { stem } from "./stemmer.js"; - -const SYNONYM_GROUPS: string[][] = [ - ["auth", "authentication", "authn", "authenticating"], - ["authz", "authorization", "authorizing"], - ["db", "database", "datastore"], - ["perf", "performance", "latency", "throughput", "slow", "bottleneck"], - ["optim", "optimization", "optimizing", "optimise", "query-optimization"], - ["k8s", "kubernetes", "kube"], - ["config", "configuration", "configuring", "setup"], - ["deps", "dependencies", "dependency"], - ["env", "environment"], - ["fn", "function"], - ["impl", "implementation", "implementing"], - ["msg", "message", "messaging"], - ["repo", "repository"], - ["req", "request"], - ["res", "response"], - ["ts", "typescript"], - ["js", "javascript"], - ["pg", "postgres", "postgresql"], - ["err", "error", "errors"], - ["api", "endpoint", "endpoints"], - ["ci", "continuous-integration"], - ["cd", "continuous-deployment"], - ["test", "testing", "tests"], - ["doc", "documentation", "docs"], - ["infra", "infrastructure"], - ["deploy", "deployment", "deploying"], - ["cache", "caching", "cached"], - ["log", "logging", "logs"], - ["monitor", "monitoring"], - ["observe", "observability"], - ["sec", "security", "secure"], - ["validate", "validation", "validating"], - ["migrate", "migration", "migrations"], - ["debug", "debugging"], - ["container", "containerization", "docker"], - ["crash", "crashloop", "crashloopbackoff"], - ["webhook", "webhooks", "callback"], - ["middleware", "mw"], - ["paginate", "pagination"], - ["serialize", "serialization"], - ["encrypt", "encryption"], - ["hash", "hashing"], -]; - -const synonymMap = new Map>(); - -for (const group of SYNONYM_GROUPS) { - const stemmed = group.map(t => stem(t.toLowerCase())); - for (const s of stemmed) { - if (!synonymMap.has(s)) synonymMap.set(s, new Set()); - for (const other of stemmed) { - if (other !== s) synonymMap.get(s)!.add(other); - } - } -} - -export function getSynonyms(stemmedTerm: string): string[] { - const syns = synonymMap.get(stemmedTerm); - return syns ? [...syns] : []; -} diff --git a/src/state/vector-index.ts b/src/state/vector-index.ts deleted file mode 100644 index d4b8bda7..00000000 --- a/src/state/vector-index.ts +++ /dev/null @@ -1,167 +0,0 @@ -// Pass byteOffset + byteLength explicitly so the round-trip survives -// Node's Buffer pool. Buffer.from(b64, "base64") returns a slice of a -// shared 8KB pool (poolSize), and `new Float32Array(buf.buffer)` ignores -// the slice metadata — it would mint a 2048-element view over the whole -// pool. Same risk on the encode side if the input Float32Array is itself -// a sliced view. Reported as a phantom "2048 dimensions on disk" crash -// in #455 / #469 / #584 / #587. -function float32ToBase64(arr: Float32Array): string { - return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString( - "base64", - ); -} - -function base64ToFloat32(b64: string): Float32Array { - const buf = Buffer.from(b64, "base64"); - return new Float32Array( - buf.buffer, - buf.byteOffset, - buf.byteLength / Float32Array.BYTES_PER_ELEMENT, - ); -} - -function cosineSimilarity(a: Float32Array, b: Float32Array): number { - if (a.length !== b.length) return 0; - let dot = 0; - let normA = 0; - let normB = 0; - for (let i = 0; i < a.length; i++) { - dot += a[i] * b[i]; - normA += a[i] * a[i]; - normB += b[i] * b[i]; - } - const denom = Math.sqrt(normA) * Math.sqrt(normB); - return denom === 0 ? 0 : dot / denom; -} - -export class VectorIndex { - private vectors: Map = - new Map(); - - add(obsId: string, sessionId: string, embedding: Float32Array): void { - this.vectors.set(obsId, { embedding, sessionId }); - } - - remove(obsId: string): void { - this.vectors.delete(obsId); - } - - search( - query: Float32Array, - limit = 20, - ): Array<{ obsId: string; sessionId: string; score: number }> { - const results: Array<{ - obsId: string; - sessionId: string; - score: number; - }> = []; - let minScore = -Infinity; - - for (const [obsId, entry] of this.vectors) { - const score = cosineSimilarity(query, entry.embedding); - if (results.length < limit) { - results.push({ obsId, sessionId: entry.sessionId, score }); - if (results.length === limit) { - results.sort((a, b) => a.score - b.score); - minScore = results[0].score; - } - } else if (score > minScore) { - results[0] = { obsId, sessionId: entry.sessionId, score }; - results.sort((a, b) => a.score - b.score); - minScore = results[0].score; - } - } - - results.sort((a, b) => b.score - a.score); - return results; - } - - get size(): number { - return this.vectors.size; - } - - // Walks every stored vector and returns the obsIds whose dimension - // doesn't match `expected`, plus the set of distinct dimensions seen. - // Used by the persistence-restore guard in src/index.ts to refuse - // loading any index containing wrong-dimension vectors — including - // legacy on-disk indexes written before the live-API dimension guard - // existed (where a mid-session provider swap could mix dimensions - // inside a single index). Empty `mismatches` plus a single-entry - // `seenDimensions` matching `expected` is the only clean state. - validateDimensions( - expected: number, - ): { mismatches: Array<{ obsId: string; dim: number }>; seenDimensions: Set } { - const mismatches: Array<{ obsId: string; dim: number }> = []; - const seenDimensions = new Set(); - for (const [obsId, entry] of this.vectors) { - const dim = entry.embedding.length; - seenDimensions.add(dim); - if (dim !== expected) { - mismatches.push({ obsId, dim }); - } - } - return { mismatches, seenDimensions }; - } - - clear(): void { - this.vectors.clear(); - } - - restoreFrom(other: VectorIndex): void { - const src = (other as any).vectors as Map< - string, - { embedding: Float32Array; sessionId: string } - >; - this.vectors = new Map(); - for (const [obsId, entry] of src) { - this.vectors.set(obsId, { - embedding: new Float32Array(entry.embedding), - sessionId: entry.sessionId, - }); - } - } - - serialize(): string { - const data: Array<[string, { embedding: string; sessionId: string }]> = []; - for (const [obsId, entry] of this.vectors) { - data.push([ - obsId, - { - embedding: float32ToBase64(entry.embedding), - sessionId: entry.sessionId, - }, - ]); - } - return JSON.stringify(data); - } - - static deserialize(json: string): VectorIndex { - const idx = new VectorIndex(); - let data: unknown; - try { - data = JSON.parse(json); - } catch { - return idx; - } - if (!Array.isArray(data)) return idx; - for (const row of data) { - try { - if (!Array.isArray(row) || row.length < 2) continue; - const [obsId, entry] = row; - if ( - typeof obsId !== "string" || - typeof entry?.embedding !== "string" || - typeof entry?.sessionId !== "string" - ) - continue; - idx.vectors.set(obsId, { - embedding: base64ToFloat32(entry.embedding), - sessionId: entry.sessionId, - }); - } catch { - continue; - } - } - return idx; - } -} diff --git a/src/telemetry/setup.ts b/src/telemetry/setup.ts deleted file mode 100644 index 0785b614..00000000 --- a/src/telemetry/setup.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { VERSION } from "../version.js"; - -interface OtelConfig { - serviceName: string; - serviceVersion: string; - metricsExportIntervalMs: number; -} - -export const OTEL_CONFIG: OtelConfig = { - serviceName: "agentmemory", - serviceVersion: VERSION, - metricsExportIntervalMs: 30_000, -}; - -interface Counter { - add: (n: number) => void; -} -interface Histogram { - record: (v: number) => void; -} - -interface Counters { - observationsTotal: Counter; - compressionSuccess: Counter; - compressionFailure: Counter; - searchTotal: Counter; - dedupSkipped: Counter; - evictionTotal: Counter; - circuitBreakerOpen: Counter; - embeddingSuccess: Counter; - embeddingFailure: Counter; - vectorSearchTotal: Counter; - autoForgetTotal: Counter; - profileGenerated: Counter; - claudeBridgeSync: Counter; - graphExtraction: Counter; - consolidationRun: Counter; - teamShare: Counter; - auditLog: Counter; - snapshotCreate: Counter; - governanceDelete: Counter; -} - -interface Histograms { - compressionLatency: Histogram; - searchLatency: Histogram; - contextTokens: Histogram; - qualityScore: Histogram; - embeddingLatency: Histogram; - vectorSearchLatency: Histogram; -} - -type Meter = { - createCounter: (name: string) => Counter; - createHistogram: (name: string) => Histogram; -}; - -let counters: Counters | null = null; -let histograms: Histograms | null = null; - -const NOOP_COUNTER: Counter = { add: () => {} }; -const NOOP_HISTOGRAM: Histogram = { record: () => {} }; - -const COUNTER_NAMES: Array<[keyof Counters, string]> = [ - ["observationsTotal", "observations.total"], - ["compressionSuccess", "compression.success"], - ["compressionFailure", "compression.failure"], - ["searchTotal", "search.total"], - ["dedupSkipped", "dedup.skipped"], - ["evictionTotal", "eviction.total"], - ["circuitBreakerOpen", "circuit_breaker.open"], - ["embeddingSuccess", "embedding.success"], - ["embeddingFailure", "embedding.failure"], - ["vectorSearchTotal", "vector_search.total"], - ["autoForgetTotal", "auto_forget.total"], - ["profileGenerated", "profile.generated"], - ["claudeBridgeSync", "claude_bridge.sync"], - ["graphExtraction", "graph.extraction"], - ["consolidationRun", "consolidation.run"], - ["teamShare", "team.share"], - ["auditLog", "audit.log"], - ["snapshotCreate", "snapshot.create"], - ["governanceDelete", "governance.delete"], -]; - -const HISTOGRAM_NAMES: Array<[keyof Histograms, string]> = [ - ["compressionLatency", "compression.latency_ms"], - ["searchLatency", "search.latency_ms"], - ["contextTokens", "context.tokens"], - ["qualityScore", "quality.score"], - ["embeddingLatency", "embedding.latency_ms"], - ["vectorSearchLatency", "vector_search.latency_ms"], -]; - -export function initMetrics(getMeter?: (name: string) => Meter): { - counters: Counters; - histograms: Histograms; -} { - const meter = getMeter?.("agentmemory"); - - counters = Object.fromEntries( - COUNTER_NAMES.map(([key, name]) => [ - key, - meter ? meter.createCounter(name) : NOOP_COUNTER, - ]), - ) as unknown as Counters; - - histograms = Object.fromEntries( - HISTOGRAM_NAMES.map(([key, name]) => [ - key, - meter ? meter.createHistogram(name) : NOOP_HISTOGRAM, - ]), - ) as unknown as Histograms; - - return { counters, histograms }; -} diff --git a/src/todos/configured.ts b/src/todos/configured.ts new file mode 100644 index 00000000..6a56750d --- /dev/null +++ b/src/todos/configured.ts @@ -0,0 +1,37 @@ +import { loadConfig, loadSecrets } from "../config.js"; +import type { Database } from "../db/index.js"; +import { createLlmRunner } from "../extract/llm-runner.js"; +import type { AppPaths } from "../paths.js"; +import { organizeTodos, type OrganizeOptions } from "./service.js"; + +export async function organizeConfiguredTodos( + db: Database, + paths: AppPaths, + options: OrganizeOptions = {} +) { + const config = loadConfig(paths); + const secrets = loadSecrets(paths); + return organizeTodos(db, { + ...options, + scope: options.scope ?? config.organize, + llmExtractor: options.llmExtractor ?? createLlmRunner(config.llm, secrets) + }); +} + +export interface LlmDoctorStatus { + enabled: boolean; + keyConfigured: boolean; + model: string; + endpoint: string; +} + +export function getLlmDoctorStatus(paths: AppPaths): LlmDoctorStatus { + const config = loadConfig(paths); + const secrets = loadSecrets(paths); + return { + enabled: config.llm.enabled, + keyConfigured: !!secrets.llmApiKey, + model: config.llm.model, + endpoint: config.llm.endpoint + }; +} diff --git a/src/todos/service.ts b/src/todos/service.ts new file mode 100644 index 00000000..810aa476 --- /dev/null +++ b/src/todos/service.ts @@ -0,0 +1,643 @@ +import type { OrganizeResult, SourceKind, TodoCard } from "../contracts.js"; +import type { Database } from "../db/index.js"; +import { stableId } from "../extract/rules.js"; + +export interface TodoEvidence { + id: string; + observationId: string; + text: string; +} + +export interface TodoEnhancer { + enhance(candidate: { title: string; description: string; mergeKey: string; evidenceText: string }): Promise<{ title?: string; description?: string } | null>; +} + +export type LlmOrganizeWarning = + | "llm_config_missing" + | "llm_timeout" + | "llm_provider_failed" + | "llm_output_invalid" + | "llm_no_valid_candidates" + | "llm_input_truncated" + | "llm_batch_failed" + | "organize_scope_truncated" + | "organize_failed"; + +export interface LlmTodoCandidate { + title: string; + description: string; + confidence: number; + sourceObservationId: string; + quote: string; + dedupeKey: string; +} + +export type LlmExtractResult = + | { ok: true; todos: LlmTodoCandidate[] } + | { ok: false; warning: LlmOrganizeWarning; reason?: string; retryable?: boolean }; + +export interface OrganizeOptions { + enhancer?: TodoEnhancer["enhance"]; + llmExtractor?: (observations: ObservationForOrganize[]) => Promise; + scope?: { + sinceDays: number; + maxInteractionsPerSession: number; + maxSessions?: number; + maxObservationsPerSession?: number; + }; + limits?: Partial; +} + +export interface ObservationForOrganize { + id: string; + sessionId: string; + source: SourceKind; + role: string; + text: string; + createdAt: string; +} + +type WriteResult = { created: number; updated: number; engine: "llm" }; +type OrganizeDetails = NonNullable; + +export interface OrganizeLimits { + maxUserBlocks: number; + maxTotalTextChars: number; + maxBlockTextChars: number; + llmBatchSize: number; + llmConcurrency: number; + maxObservationTextChars: number; + maxSessionPayloadChars: number; + maxBatchPayloadChars: number; +} + +export const DEFAULT_ORGANIZE_LIMITS: OrganizeLimits = { + maxUserBlocks: 120, + maxTotalTextChars: 80000, + maxBlockTextChars: 4000, + llmBatchSize: 20, + llmConcurrency: 2, + maxObservationTextChars: 3000, + maxSessionPayloadChars: 24000, + maxBatchPayloadChars: 32000 +}; + +export async function organizeTodos(db: Database, options: OrganizeOptions = {}): Promise { + const started = Date.now(); + const runId = stableId("organize", new Date(started).toISOString(), Math.random().toString(36)); + const warnings = new Set(); + const details: OrganizeDetails = {}; + const limits = { ...DEFAULT_ORGANIZE_LIMITS, ...options.limits }; + const observations = planScopedObservations(loadScopedObservations(db, options.scope), options.scope, limits, warnings, details); + const sourceCounts = new Map(); + for (const observation of observations) { + sourceCounts.set(observation.source, (sourceCounts.get(observation.source) ?? 0) + 1); + } + const writeResult: WriteResult = options.llmExtractor + ? await writeBatchedLlmTodos(db, observations, options, limits, warnings, details) + : noLlmResult(warnings, "llm_config_missing"); + + const result: OrganizeResult = { + runId, + scanned: observations.length, + sources: Array.from(sourceCounts, ([source, scanned]) => ({ source, scanned })), + created: writeResult.created, + updated: writeResult.updated, + completed: 0, + ignored: 0, + engine: writeResult.engine, + warnings: Array.from(warnings), + durationMs: Date.now() - started + }; + if (hasOrganizeDetails(details)) result.details = details; + + db.prepare( + "INSERT INTO organize_runs (id, result_json, created_at) VALUES (?, ?, ?)" + ).run(runId, JSON.stringify(result), new Date().toISOString()); + return result; +} + +function loadScopedObservations(db: Database, scope: OrganizeOptions["scope"]): ObservationForOrganize[] { + const params: string[] = []; + let where = ""; + if (scope) { + where = "WHERE datetime(created_at) >= datetime(?)"; + params.push(new Date(Date.now() - scope.sinceDays * 24 * 60 * 60 * 1000).toISOString()); + } + return db.prepare( + `SELECT id, session_id as sessionId, source, role, text, created_at as createdAt + FROM observations + ${where} + ORDER BY created_at, id` + ).all(...params) as unknown as ObservationForOrganize[]; +} + +export function scopeObservations( + observations: ObservationForOrganize[], + scope: OrganizeOptions["scope"] +): ObservationForOrganize[] { + if (!scope) return observations; + const cutoffMs = Date.now() - scope.sinceDays * 24 * 60 * 60 * 1000; + const recent = observations.filter((observation) => Date.parse(observation.createdAt) >= cutoffMs); + const grouped = new Map(); + for (const observation of recent) { + const group = grouped.get(observation.sessionId) ?? []; + group.push(observation); + grouped.set(observation.sessionId, group); + } + const scoped: ObservationForOrganize[] = []; + for (const group of grouped.values()) { + scoped.push(...takeRecentInteractions(group, scope.maxInteractionsPerSession)); + } + return scoped.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id)); +} + +function takeRecentInteractions(observations: ObservationForOrganize[], maxInteractions: number): ObservationForOrganize[] { + const boundaries = observations + .map((observation, index) => ({ observation, index })) + .filter(({ observation }) => observation.role === "user") + .map(({ index }) => index); + if (boundaries.length <= maxInteractions) return observations; + const cutoff = boundaries[boundaries.length - maxInteractions]; + return observations.slice(cutoff); +} + +function planScopedObservations( + observations: ObservationForOrganize[], + scope: OrganizeOptions["scope"], + limits: OrganizeLimits, + warnings: Set, + details: OrganizeDetails +): ObservationForOrganize[] { + const scoped = scopeObservationsBySession(observations, scope, limits, warnings, details); + return applyPayloadBudget(scoped, limits, warnings, details); +} + +function scopeObservationsBySession( + observations: ObservationForOrganize[], + scope: OrganizeOptions["scope"], + limits: OrganizeLimits, + warnings: Set, + details: OrganizeDetails +): ObservationForOrganize[] { + const base = scope ? observations.filter((observation) => { + const cutoffMs = Date.now() - scope.sinceDays * 24 * 60 * 60 * 1000; + return Date.parse(observation.createdAt) >= cutoffMs; + }) : observations; + const sessions = new Map(); + for (const observation of base) { + const group = sessions.get(observation.sessionId) ?? []; + group.push(observation); + sessions.set(observation.sessionId, group); + } + const maxSessions = scope?.maxSessions ?? Number.POSITIVE_INFINITY; + const maxObservationsPerSession = scope?.maxObservationsPerSession ?? Number.POSITIVE_INFINITY; + const rankedSessions = Array.from(sessions.values()) + .map((group) => group.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id))) + .sort((a, b) => latestTimestamp(b).localeCompare(latestTimestamp(a))); + const selected = rankedSessions.slice(0, maxSessions); + const sessionsDropped = Math.max(0, rankedSessions.length - selected.length); + let observationsDropped = rankedSessions.slice(maxSessions).reduce((sum, group) => sum + group.length, 0); + const scoped: ObservationForOrganize[] = []; + for (const group of selected) { + const interactionScoped = scope ? takeRecentInteractions(group, scope.maxInteractionsPerSession) : group; + const observationScoped = interactionScoped.slice(Math.max(0, interactionScoped.length - maxObservationsPerSession)); + observationsDropped += Math.max(0, group.length - interactionScoped.length) + Math.max(0, interactionScoped.length - observationScoped.length); + scoped.push(...observationScoped); + } + if (sessionsDropped > 0 || observationsDropped > 0) { + warnings.add("organize_scope_truncated"); + details.scope = { + sessionsScanned: selected.length, + sessionsDropped, + observationsDropped + }; + } + return scoped.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id)); +} + +function latestTimestamp(observations: ObservationForOrganize[]): string { + return observations.reduce((latest, observation) => observation.createdAt > latest ? observation.createdAt : latest, ""); +} + +function applyPayloadBudget( + observations: ObservationForOrganize[], + limits: OrganizeLimits, + warnings: Set, + details: OrganizeDetails +): ObservationForOrganize[] { + let totalText = 0; + let userBlocks = 0; + const sessionText = new Map(); + const limited: ObservationForOrganize[] = []; + for (const observation of observations) { + if (observation.role === "user" && userBlocks >= limits.maxUserBlocks) { + warnings.add("organize_scope_truncated"); + continue; + } + const maxTextChars = observation.role === "user" + ? Math.min(limits.maxBlockTextChars, limits.maxObservationTextChars) + : limits.maxObservationTextChars; + let text = observation.text.length > maxTextChars + ? observation.text.slice(0, maxTextChars) + : observation.text; + if (text.length !== observation.text.length) { + addTruncationDetail(details, observation, observation.text.length, text.length); + warnings.add("llm_input_truncated"); + } + const sessionUsed = sessionText.get(observation.sessionId) ?? 0; + if (sessionUsed + text.length > limits.maxSessionPayloadChars) { + const available = Math.max(0, limits.maxSessionPayloadChars - sessionUsed); + if (available === 0) { + warnings.add("organize_scope_truncated"); + continue; + } + text = text.slice(0, available); + addTruncationDetail(details, observation, observation.text.length, text.length); + warnings.add("llm_input_truncated"); + } + if (totalText + text.length > limits.maxTotalTextChars) { + warnings.add("organize_scope_truncated"); + continue; + } + totalText += text.length; + sessionText.set(observation.sessionId, sessionUsed + text.length); + if (observation.role === "user") userBlocks++; + limited.push(text === observation.text ? observation : { ...observation, text }); + } + return limited; +} + +function addTruncationDetail( + details: OrganizeDetails, + observation: ObservationForOrganize, + originalChars: number, + keptChars: number +): void { + details.truncations ??= []; + details.truncations.push({ + sessionId: observation.sessionId, + source: observation.source, + role: observation.role, + originalChars, + keptChars + }); +} + +function hasOrganizeDetails(details: OrganizeDetails): boolean { + return !!details.scope || !!details.truncations?.length || !!details.batchFailures?.length; +} + +function writeExtractedLlmTodos( + db: Database, + observations: ObservationForOrganize[], + extracted: LlmExtractResult, + warnings: Set, + details: OrganizeDetails +): WriteResult { + if (!extracted.ok) { + warnings.add(extracted.warning); + addBatchFailureDetail(details, observations, extracted); + return noLlmResult(warnings); + } + const byId = new Map(observations.map((observation) => [observation.id, observation])); + const candidates = dedupeLlmCandidates( + extracted.todos.filter((candidate) => validLlmCandidate(candidate, byId)), + existingActiveTodos(db) + ); + if (candidates.length === 0) { + warnings.add("llm_no_valid_candidates"); + return noLlmResult(warnings); + } + + let created = 0; + let updated = 0; + for (const candidate of candidates) { + const observation = byId.get(candidate.sourceObservationId); + if (!observation) continue; + const todoId = stableId(candidate.dedupeKey); + const now = new Date().toISOString(); + const existing = db.prepare("SELECT id FROM todos WHERE id = ?").get(todoId); + if (existing) { + db.prepare( + "UPDATE todos SET title = ?, description = ?, updated_at = ? WHERE id = ?" + ).run(candidate.title.trim(), candidate.description.trim(), now, todoId); + updated++; + } else { + db.prepare( + "INSERT INTO todos (id, title, description, status, updated_at) VALUES (?, ?, ?, 'todo', ?)" + ).run(todoId, candidate.title.trim(), candidate.description.trim(), now); + created++; + } + db.prepare( + "INSERT OR REPLACE INTO evidence (id, todo_id, observation_id, text) VALUES (?, ?, ?, ?)" + ).run(stableId(todoId, observation.id), todoId, observation.id, candidate.quote.trim()); + } + return { created, updated, engine: "llm" }; +} + +async function writeBatchedLlmTodos( + db: Database, + observations: ObservationForOrganize[], + options: OrganizeOptions, + limits: OrganizeLimits, + warnings: Set, + details: OrganizeDetails +): Promise { + const extractor = options.llmExtractor; + if (!extractor) return noLlmResult(warnings, "llm_config_missing"); + const batches = chunkObservationsBySession(observations, limits.llmBatchSize, limits.maxBatchPayloadChars) + .filter((batch) => batch.some((observation) => observation.role === "user")); + let totalCreated = 0; + let totalUpdated = 0; + const concurrency = Math.max(1, Math.floor(limits.llmConcurrency)); + + for (let index = 0; index < batches.length; index += concurrency) { + const extracted = await Promise.all(batches.slice(index, index + concurrency).map(async (batch) => ({ + batch, + result: await extractor(batch) + }))); + for (const item of extracted) { + const warningsBefore = new Set(warnings); + const result = writeExtractedLlmTodos(db, item.batch, item.result, warnings, details); + if (hasBatchFailureWarning(warnings, warningsBefore)) warnings.add("llm_batch_failed"); + totalCreated += result.created; + totalUpdated += result.updated; + } + } + + if (batches.length === 0) warnings.add("llm_no_valid_candidates"); + return { created: totalCreated, updated: totalUpdated, engine: "llm" }; +} + +function addBatchFailureDetail( + details: OrganizeDetails, + observations: ObservationForOrganize[], + failure: Extract +): void { + const first = observations.find((observation) => observation.role === "user") ?? observations[0]; + if (!first) return; + details.batchFailures ??= []; + details.batchFailures.push({ + sessionId: first.sessionId, + source: first.source, + warning: failure.warning, + reason: failure.reason ?? failure.warning, + retryable: failure.retryable ?? isRetryableWarning(failure.warning) + }); +} + +function isRetryableWarning(warning: LlmOrganizeWarning): boolean { + return warning === "llm_timeout" || + warning === "llm_provider_failed" || + warning === "llm_output_invalid"; +} + +function hasBatchFailureWarning(warnings: Set, before: Set): boolean { + for (const warning of warnings) { + if (before.has(warning)) continue; + if ( + warning === "llm_timeout" || + warning === "llm_provider_failed" || + warning === "llm_output_invalid" + ) return true; + } + return false; +} + +function noLlmResult(warnings: Set, warning?: LlmOrganizeWarning): WriteResult { + if (warning) warnings.add(warning); + return { created: 0, updated: 0, engine: "llm" }; +} + +function chunkObservationsBySession( + observations: ObservationForOrganize[], + batchSize: number, + maxBatchPayloadChars: number +): ObservationForOrganize[][] { + const sessions = new Map(); + for (const observation of observations) { + const group = sessions.get(observation.sessionId) ?? []; + group.push(observation); + sessions.set(observation.sessionId, group); + } + const chunks: ObservationForOrganize[][] = []; + for (const group of sessions.values()) { + chunks.push(...chunkObservations(group, batchSize, maxBatchPayloadChars)); + } + return chunks; +} + +function chunkObservations( + observations: ObservationForOrganize[], + batchSize: number, + maxBatchPayloadChars: number +): ObservationForOrganize[][] { + const chunks: ObservationForOrganize[][] = []; + let chunk: ObservationForOrganize[] = []; + let users = 0; + let chars = 0; + for (const observation of observations) { + const isUser = observation.role === "user"; + const wouldExceedUserLimit = isUser && users >= batchSize && chunk.length > 0; + const wouldExceedPayloadLimit = chars > 0 && + chars + observation.text.length > maxBatchPayloadChars && + chunk.some((item) => item.role === "user"); + if (wouldExceedUserLimit || wouldExceedPayloadLimit) { + chunks.push(chunk); + chunk = []; + users = 0; + chars = 0; + } + chunk.push(observation); + chars += observation.text.length; + if (isUser) users++; + } + if (chunk.length > 0) chunks.push(chunk); + return chunks; +} + +function validLlmCandidate(candidate: LlmTodoCandidate, observations: Map): boolean { + const observation = observations.get(candidate.sourceObservationId); + return !!observation && + typeof candidate.title === "string" && + !!candidate.title.trim() && + typeof candidate.description === "string" && + !!candidate.description.trim() && + typeof candidate.quote === "string" && + !!candidate.quote.trim() && + observation.text.includes(candidate.quote.trim()) && + typeof candidate.dedupeKey === "string" && + !!candidate.dedupeKey.trim() && + typeof candidate.confidence === "number" && + Number.isFinite(candidate.confidence) && + candidate.confidence >= 0.75 && + candidatePassesQualityGate(candidate); +} + +function candidatePassesQualityGate(candidate: LlmTodoCandidate): boolean { + const title = normalizeTodoText(candidate.title); + const description = normalizeTodoText(candidate.description); + const quote = normalizeTodoText(candidate.quote); + const combined = `${title} ${description} ${quote}`; + return !looksIncompleteTitle(title) && + !isCompletedOrStatusOnly(combined) && + !isToolPolluted(combined) && + !isPureProcessChore(combined) && + !hasLongTechnicalIdentifier(title); +} + +function dedupeLlmCandidates(candidates: LlmTodoCandidate[], existingTodos: ExistingTodoForDedupe[]): LlmTodoCandidate[] { + const accepted: LlmTodoCandidate[] = []; + const keys = new Set(existingTodos.map((todo) => candidateIdentityKey(todo))); + for (const candidate of candidates) { + const todoId = stableId(candidate.dedupeKey); + const key = candidateIdentityKey(candidate); + if (existingTodos.some((todo) => todo.id !== todoId && nearDuplicateTodo(todo, candidate))) continue; + if (accepted.some((item) => nearDuplicateTodo(item, candidate))) continue; + keys.add(key); + accepted.push(candidate); + } + return accepted; +} + +interface ExistingTodoForDedupe { + id: string; + title: string; + description: string; +} + +function existingActiveTodos(db: Database): ExistingTodoForDedupe[] { + return db.prepare("SELECT id, title, description FROM todos WHERE status = 'todo'").all() as unknown as ExistingTodoForDedupe[]; +} + +function candidateIdentityKey(candidate: Pick): string { + return `${normalizeTodoKey(candidate.title)}|${normalizeTodoKey(candidate.description)}`; +} + +function nearDuplicateTodo(left: Pick, right: Pick): boolean { + const leftTitle = normalizeTodoKey(left.title); + const rightTitle = normalizeTodoKey(right.title); + if (!leftTitle || !rightTitle) return false; + if (leftTitle === rightTitle) return true; + if (leftTitle.includes(rightTitle) || rightTitle.includes(leftTitle)) return true; + return tokenSimilarity(leftTitle, rightTitle) >= 0.72; +} + +function tokenSimilarity(left: string, right: string): number { + const leftTokens = todoTokens(left); + const rightTokens = todoTokens(right); + if (leftTokens.size === 0 || rightTokens.size === 0) return 0; + let overlap = 0; + for (const token of leftTokens) { + if (rightTokens.has(token)) overlap++; + } + return overlap / Math.max(leftTokens.size, rightTokens.size); +} + +function todoTokens(value: string): Set { + const tokens = new Set(); + for (const match of value.matchAll(/[a-z0-9]+|[\u4e00-\u9fff]/giu)) { + const token = match[0].toLowerCase(); + if (token && token !== "并" && token !== "和" && token !== "与") tokens.add(token); + } + return tokens; +} + +function normalizeTodoText(value: string): string { + return String(value || "").replace(/\s+/g, " ").trim(); +} + +function normalizeTodoKey(value: string): string { + return normalizeTodoText(value) + .toLowerCase() + .replace(/[,。!?;:,.!?;:()[\]{}"'`“”‘’\s-]/g, ""); +} + +function looksIncompleteTitle(title: string): boolean { + if (Array.from(title).length < 3) return true; + if (!/[A-Za-z\u4e00-\u9fff]/u.test(title)) return true; + if (/(?:返回|status|code|状态码)\s*\d{1,2}$/i.test(title)) return true; + if (/[、,,]\s*\/[A-Za-z]{1,4}$/u.test(title)) return true; + if (/\bhttps?:\/\/\S*[:/]$/i.test(title)) return true; + if (/^(?:准备|开始|继续|接下来|现在我会)\s*[^\n。!?]{0,80}(?:到|为|把|对|向|在|从|将)$/u.test(title)) return true; + return false; +} + +function isCompletedOrStatusOnly(value: string): boolean { + const text = normalizeTodoText(value); + if (/(?:已完成|已通过|已经完成|完成了|都能显示|服务可用|健康检查已完成|worktree clean|working tree clean|process exited 0|no changes)/i.test(text)) return true; + return /^(?:确认|检查|状态确认|健康检查|服务可用|review settings|status check)[\s\S]{0,80}$/i.test(text); +} + +function isToolPolluted(value: string): boolean { + return /\b(?:Bash|Shell|exec_command|apply_patch|toolUseId|function_call|function_call_output|process exited|Chunk ID|Wall time|yield_time_ms|max_output_tokens)\b/i.test(value) || + /"(?:cmd|command|workdir|yield_time_ms|max_output_tokens)"\s*:/i.test(value); +} + +function isPureProcessChore(value: string): boolean { + const text = normalizeTodoText(value); + if (!/(?:做最后一次状态确认|最后一次状态确认|启动后做健康检查|做健康检查|确认工作区干净|确认当前分支|确认 PR 链接|重启后再测一次|重启 Codex desktop app 后再测一次)/i.test(text)) { + return false; + } + return !/(?:修复|修正|补充|实现|调整|排查|定位|创建|更新|推送|提交|fix|add|update|create|implement|resolve)/i.test(text); +} + +function hasLongTechnicalIdentifier(title: string): boolean { + return /(?:[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+|[A-Za-z0-9_.-]{24,}|https?:\/\/|\/(?:Users|tmp|var|private|Volumes)\/)/i.test(title); +} + +export function listTodos(db: Database): TodoCard[] { + return db.prepare( + `SELECT + todos.id, + todos.title, + todos.description, + todos.status, + todos.updated_at as updatedAt, + COALESCE(json_group_array(evidence.id) FILTER (WHERE evidence.id IS NOT NULL), '[]') as evidenceIds + FROM todos + LEFT JOIN evidence ON evidence.todo_id = todos.id + GROUP BY todos.id + ORDER BY todos.updated_at DESC` + ).all().map((row) => { + const record = row as Record; + return { + id: String(record.id), + title: String(record.title), + description: String(record.description), + status: record.status as TodoCard["status"], + updatedAt: String(record.updatedAt), + evidenceIds: JSON.parse(String(record.evidenceIds)) + }; + }); +} + +export function updateTodoStatus(db: Database, id: string, status: "done" | "ignored"): boolean { + const result = db.prepare( + "UPDATE todos SET status = ?, updated_at = ? WHERE id = ?" + ).run(status, new Date().toISOString(), id); + return result.changes > 0; +} + +export function listTodoEvidence(db: Database, todoId: string): TodoEvidence[] | null { + const todo = db.prepare("SELECT id FROM todos WHERE id = ?").get(todoId); + if (!todo) return null; + return db.prepare( + "SELECT id, observation_id as observationId, text FROM evidence WHERE todo_id = ? ORDER BY id" + ).all(todoId).map((row) => { + const record = row as Record; + return { + id: String(record.id), + observationId: String(record.observationId), + text: String(record.text) + }; + }); +} + +export function getOrganizeRun(db: Database, id: string): OrganizeResult | null { + const row = db.prepare( + "SELECT result_json as resultJson FROM organize_runs WHERE id = ?" + ).get(id) as { resultJson: string } | undefined; + return row ? JSON.parse(row.resultJson) as OrganizeResult : null; +} diff --git a/src/triggers/api.ts b/src/triggers/api.ts deleted file mode 100644 index 891f1d96..00000000 --- a/src/triggers/api.ts +++ /dev/null @@ -1,3932 +0,0 @@ -import { TriggerAction, type ISdk, type ApiRequest } from "iii-sdk"; -import type { Session, CompressedObservation, HookPayload, CommitLink, ReviewQueueItem, InboxItem, DeliveryRecord } from "../types.js"; -import { withKeyedLock } from "../state/keyed-mutex.js"; -import { KV, fingerprintId } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { getLatestHealth } from "../health/monitor.js"; -import type { MetricsStore } from "../eval/metrics-store.js"; -import type { ResilientProvider } from "../providers/resilient.js"; -import { VERSION } from "../version.js"; -import { timingSafeCompare } from "../auth.js"; -import { isSlotsEnabled, isReflectEnabled } from "../functions/slots.js"; -import { renderViewerDocument } from "../viewer/document.js"; -import { getBoundViewerPort, getViewerSkipped } from "../viewer/server.js"; -import { MAX_FILES_UPPER_BOUND } from "../functions/replay.js"; -import { logger } from "../logger.js"; -import { - isGraphExtractionEnabled, - isConsolidationEnabled, - isAutoCompressEnabled, - isContextInjectionEnabled, - detectEmbeddingProvider, - detectLlmProviderKind, - getTodoExtractorUserConfig, - getUserEnvPath, - writeUserEnv, - WRITABLE_TODO_EXTRACT_KEYS, - getAgentId, - isAgentScopeIsolated, -} from "../config.js"; - -type Response = { - status_code: number; - headers?: Record; - body: unknown; -}; - -function parseOptionalInt(raw: unknown): number | undefined { - if (raw === undefined || raw === null || raw === "") return undefined; - const n = typeof raw === "number" ? raw : parseInt(String(raw), 10); - return Number.isFinite(n) ? n : undefined; -} - -function cleanConfigValue(raw: unknown): string | null { - if (raw === undefined || raw === null) return null; - const value = String(raw).trim(); - if (!value || /[\r\n]/.test(value)) return null; - return value; -} - -function checkAuth( - req: ApiRequest, - secret: string | undefined, -): Response | null { - if (!secret) return null; - const auth = req.headers?.["authorization"] || req.headers?.["Authorization"]; - if ( - typeof auth !== "string" || - !timingSafeCompare(auth, `Bearer ${secret}`) - ) { - return { status_code: 401, body: { error: "unauthorized" } }; - } - return null; -} - -function requireConfiguredSecret( - secret: string | undefined, - feature: string, -): Response | null { - if (secret) return null; - return { - status_code: 503, - body: { error: `${feature} requires AGENTMEMORY_SECRET` }, - }; -} - -function shortHash(input: string): string { - let hash = 2166136261; - for (let i = 0; i < input.length; i += 1) { - hash ^= input.charCodeAt(i); - hash = Math.imul(hash, 16777619); - } - return (hash >>> 0).toString(36); -} - -function browserSessionId(reviewId: string, page: Record, createdAt: string): string { - const url = asNonEmptyString(page.url) || asNonEmptyString(page.title) || reviewId; - return `browser_${shortHash(`${url}:${createdAt}:${reviewId}`)}`; -} - -function browserSyncKey(page: Record, conversation: { provider?: string; turns?: Array<{ role?: string; text?: string }> }): string { - const url = asNonEmptyString(page.url) || asNonEmptyString(page.title) || "browser"; - const provider = conversation.provider || asNonEmptyString(page.host) || "browser"; - const turns = Array.isArray(conversation.turns) ? conversation.turns : []; - const lastText = turns.slice(-3).map((turn) => `${turn.role || "unknown"}:${turn.text || ""}`).join("\n"); - return shortHash(`${url}:${provider}:${turns.length}:${lastText}`); -} - -function cleanBrowserCandidateText(value: unknown): string { - return String(value || "") - .replace(/\s+/g, " ") - .replace(/^(用户|User|我)[::]\s*/i, "") - .replace(/^(AI|Assistant|ChatGPT|Claude|Gemini|Perplexity)[::]\s*/i, "") - .trim(); -} - -function isBrowserUiNoise(value: string): boolean { - const text = cleanBrowserCandidateText(value); - if (!text) return true; - if (/^https?:\/\//i.test(text)) return true; - if (/^(下载|打开|安装|查看|测试|反馈|分诊|外测|验收|刷新|关闭|复制|保存|确认|取消|加入待确认|高级选项|自动整理|最近加入|最近同步)$/.test(text)) return true; - if (/^(总览|记忆|会话|活动|Skill|待办|状态|最近会话|浏览器记忆入口?|本地工作台|当前页面|页面识别|候选记忆|其他建议)$/.test(text)) return true; - if (/浏览器记忆入口/.test(text) || /从网页和\s*AI\s*对话提取具体事实/.test(text)) return true; - if (/(按钮|点击|入口|侧栏|弹窗|工作台|插件|页面|选择器|测试卡|诊断|刷新|加载)/.test(text) && !/(用户|我|我的|我们|偏好|喜欢|不喜欢|希望|需要|计划|决定|负责|待办|TODO|必须|不要)/i.test(text)) return true; - return false; -} - -function browserFactScore(value: string): number { - const text = cleanBrowserCandidateText(value); - if (text.length < 8 || isBrowserUiNoise(text)) return 0; - let score = 0; - if (/(我|我的|我们|用户|SZn|szn|刘欣|Liu Xin|Coco|你是|你叫)/i.test(text)) score += 0.26; - if (/(希望|想要|需要|正在|计划|负责|决定|偏好|喜欢|不喜欢|不要|应该|必须|待办|TODO)/i.test(text)) score += 0.35; - if (/(项目|产品|设计|飞书|GitHub|雅思|IELTS|UCL|英国|插件|记忆)/i.test(text)) score += 0.16; - if (text.length >= 18 && text.length <= 220) score += 0.16; - if (/[。!?!?]$/.test(text)) score += 0.04; - return Math.min(0.95, score); -} - -function splitBrowserFactSentences(value: unknown): string[] { - return String(value || "") - .split(/[。!?!?\n]+/) - .map(cleanBrowserCandidateText) - .filter(Boolean) - .filter((text) => browserFactScore(text) >= 0.58) - .filter((text) => text.length <= 240); -} - -function uniqueBrowserFacts(items: string[]): string[] { - const seen = new Set(); - return items.filter((item) => { - const key = item.toLowerCase(); - if (seen.has(key)) return false; - seen.add(key); - return true; - }); -} - -function inferBrowserMemoryType(text: string): "pattern" | "preference" | "architecture" | "bug" | "workflow" | "fact" { - if (/(偏好|喜欢|不喜欢|希望|想要|不要)/.test(text)) return "preference"; - if (/(流程|步骤|工作流|每次|默认|应该|必须)/.test(text)) return "workflow"; - if (/(问题|报错|失败|无法|bug|风险)/i.test(text)) return "bug"; - return "fact"; -} - -function buildBrowserMemoryCandidate(body: Record, page: Record, conversation: { provider?: string; promptDraft?: string; turns?: Array<{ role?: string; text?: string }> }): { - decision: "candidate" | "evidence_only"; - title: string; - content: string; - confidence: number; - reason: string; - type: "pattern" | "preference" | "architecture" | "bug" | "workflow" | "fact"; -} { - const rawCandidates = body.candidates && typeof body.candidates === "object" ? body.candidates as Record : {}; - const memoryCandidates = Array.isArray(rawCandidates.memories) ? rawCandidates.memories : []; - const turns = Array.isArray(conversation.turns) ? conversation.turns : []; - const facts = uniqueBrowserFacts([ - ...memoryCandidates.flatMap(splitBrowserFactSentences), - ...turns.filter((turn) => turn.role === "user").flatMap((turn) => splitBrowserFactSentences(turn.text)), - ...turns.filter((turn) => turn.role === "assistant").flatMap((turn) => splitBrowserFactSentences(turn.text)), - ...splitBrowserFactSentences((page as Record).selection), - ]); - const best = facts - .map((fact) => ({ fact, score: browserFactScore(fact) })) - .sort((a, b) => b.score - a.score)[0]; - if (!best) { - return { - decision: "evidence_only", - title: asNonEmptyString(page.title) || "浏览器会话", - content: [asNonEmptyString(page.title), asNonEmptyString(page.url), turns.length ? `已同步 ${turns.length} 条网页对话。` : "已同步网页上下文。"].filter(Boolean).join("\n"), - confidence: 0.25, - reason: "没有识别到足够具体的事实、偏好、决定或待办;仅作为工作台证据保留。", - type: "fact", - }; - } - const fact = best.fact; - return { - decision: "candidate", - title: fact.length > 42 ? `${fact.slice(0, 42)}...` : fact, - content: [`候选事实:${fact}`, `依据:来自 ${conversation.provider || asNonEmptyString(page.typeLabel) || asNonEmptyString(page.host) || "浏览器"} 的网页会话`].join("\n"), - confidence: best.score, - reason: "本地规则识别到明确事实、偏好、决定或待办,进入工作台候选区。", - type: inferBrowserMemoryType(fact), - }; -} - -async function recordBrowserSessionFromReview( - sdk: ISdk, - item: ReviewQueueItem, -): Promise<{ sessionId: string; observationCount: number }> { - const page = item.page || {}; - const conversation = item.conversation || {}; - const turns = Array.isArray(conversation.turns) ? conversation.turns : []; - const provider = conversation.provider || (typeof item.payload?.provider === "string" ? item.payload.provider : undefined) || page.host || "浏览器"; - const createdAt = item.createdAt || new Date().toISOString(); - const sessionId = browserSessionId(item.id, page as Record, createdAt); - const project = String(provider || "浏览器"); - const cwd = `browser/${page.host || provider || "web"}`; - await sdk.trigger({ - function_id: "api::session::start", - payload: { - sessionId, - project, - cwd, - title: page.title || item.title || "浏览器会话", - agentId: provider || "浏览器", - }, - }); - let observationCount = 0; - const observe = async (timestamp: string, data: Record) => { - observationCount += 1; - await sdk.trigger({ - function_id: "mem::observe", - payload: { - hookType: "prompt_submit", - sessionId, - project, - cwd, - timestamp, - data, - }, - }); - }; - if (page.title || page.url) { - await observe(createdAt, { - tool_name: "browser_page", - prompt: `打开网页:${[page.title, page.url].filter(Boolean).join("\n")}`, - tool_output: { page }, - }); - } - for (let index = 0; index < turns.length; index += 1) { - const turn = turns[index]; - const role = turn.role === "assistant" ? "AI" : turn.role === "user" ? "用户" : "对话"; - await observe(createdAt, { - tool_name: "browser_conversation", - prompt: `${role}:${turn.text}`, - tool_output: { role: turn.role || "unknown", provider, pageTitle: page.title || "" }, - }); - } - await observe(item.updatedAt || createdAt, { - tool_name: item.kind === "lesson" ? "browser_lesson_candidate" : "browser_memory_candidate", - prompt: `${item.kind === "lesson" ? "从浏览器会话抽取经验" : "从浏览器会话抽取记忆"}:${item.title}`, - tool_output: { content: item.content, status: item.status, kind: item.kind }, - }); - await sdk.trigger({ function_id: "api::session::end", payload: { sessionId } }); - return { sessionId, observationCount }; -} - -function reviewKind(value: unknown): ReviewQueueItem["kind"] { - return value === "lesson" || value === "action" ? value : "memory"; -} - -function defaultReviewTitle(kind: ReviewQueueItem["kind"], pageTitle?: string): string { - if (pageTitle) return pageTitle; - if (kind === "lesson") return "待审阅经验"; - if (kind === "action") return "待审阅行动"; - return "待审阅记忆"; -} - -function isJsonLikeReviewText(value: string): boolean { - const trimmed = value.trim(); - if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false; - try { - JSON.parse(trimmed); - return true; - } catch { - return true; - } -} - -function isMarkdownPlanReviewText(value: string): boolean { - const raw = String(value || ""); - const hasPlanHeading = /^#{1,3}\s+.*(?:计划|Plan)\s*$/im.test(raw); - const sectionMatches = raw.match(/^#{1,3}\s+(?:Summary|Key Changes|Test Plan|Assumptions|Public API|Implementation|执行步骤|验证命令)\b/img) || []; - const compact = raw.replace(/\s+/g, " "); - const compactPlanSectionCount = [ - /#{1,3}\s+Summary\b/i, - /#{1,3}\s+Key Changes\b/i, - /#{1,3}\s+Test Plan\b/i, - /#{1,3}\s+Assumptions\b/i, - /#{1,3}\s+Implementation\b/i, - /#{1,3}\s+执行步骤\b/i, - /#{1,3}\s+验证命令\b/i, - ].filter((pattern) => pattern.test(compact)).length; - const compactPlan = /#{1,3}\s+[^#]{0,160}(?:计划|Plan)/i.test(compact) && - (compactPlanSectionCount >= 2 || /#{1,3}\s+Summary\b/i.test(compact)); - return (hasPlanHeading && sectionMatches.length >= 2) || compactPlan; -} - -function isReviewTextDisplayable(value: string): boolean { - const trimmed = value.trim(); - if (!trimmed) return true; - const lower = trimmed.toLowerCase(); - if (isJsonLikeReviewText(trimmed)) return false; - if (isMarkdownPlanReviewText(trimmed)) return false; - if (/please implement this plan/i.test(trimmed)) return false; - if (/"plan"\s*:/.test(trimmed) && /"status"\s*:/.test(trimmed) && /"step"\s*:/.test(trimmed)) return false; - if (/"command"\s*:|toolinput|tooloutput|function_id/.test(lower)) return false; - if (/审查结果\s*\[[Pp]\d+\]/.test(trimmed) && /(?:src|test)\/[^\s]+(?:\s*\(line\s+\d+\))?/.test(trimmed)) return false; - if (/^src\/[^\s]+/m.test(trimmed) && /\bnpm\s+(test|run|install|build)\b/i.test(trimmed)) return false; - if (/^\s*(npm|pnpm|yarn)\s+(test|run|install|build)\b/im.test(trimmed)) return false; - return true; -} - -function isReviewCandidateDisplayable(title: string, content: string): boolean { - return isReviewTextDisplayable(title) && isReviewTextDisplayable(content); -} - -function flagDisabledResponse(opts: { - error: string; - flag: string; - enableHow: string; - docsHref: string; -}): Response { - return { - status_code: 503, - body: opts, - }; -} - -function graphDisabledResponse(): Response { - return flagDisabledResponse({ - error: "Knowledge graph not enabled", - flag: "GRAPH_EXTRACTION_ENABLED", - enableHow: "Set GRAPH_EXTRACTION_ENABLED=true and restart. Requires an LLM provider key.", - docsHref: "https://github.com/MaimoryLab/agentmemory-lab#knowledge-graph", - }); -} - -function consolidationDisabledResponse(): Response { - return flagDisabledResponse({ - error: "Consolidation pipeline not enabled", - flag: "CONSOLIDATION_ENABLED", - enableHow: "Set CONSOLIDATION_ENABLED=true and restart. Requires an LLM provider key.", - docsHref: "https://github.com/MaimoryLab/agentmemory-lab#consolidation", - }); -} - -function slotsDisabledResponse(): Response { - return flagDisabledResponse({ - error: "Memory slots not enabled", - flag: "AGENTMEMORY_SLOTS", - enableHow: "Set AGENTMEMORY_SLOTS=true (in ~/.agentmemory/.env or the shell) and restart.", - docsHref: "https://github.com/MaimoryLab/agentmemory-lab#memory-slots", - }); -} - -function reflectDisabledResponse(): Response { - return flagDisabledResponse({ - error: "Slot reflection not enabled", - flag: "AGENTMEMORY_REFLECT", - enableHow: "Set AGENTMEMORY_REFLECT=true (in ~/.agentmemory/.env or the shell) and restart. Requires AGENTMEMORY_SLOTS=true.", - docsHref: "https://github.com/MaimoryLab/agentmemory-lab#memory-slots", - }); -} - -function asNonEmptyString(value: unknown): string | null { - if (typeof value !== "string") return null; - const trimmed = value.trim(); - return trimmed ? trimmed : null; -} - -function parseOptionalFiniteNumber(value: unknown): number | undefined | null { - if (value === undefined || value === null) return undefined; - if (typeof value === "number") return Number.isFinite(value) ? value : null; - if (typeof value === "string") { - const trimmed = value.trim(); - if (!trimmed) return undefined; - const parsed = Number(trimmed); - return Number.isFinite(parsed) ? parsed : null; - } - return null; -} - -function parseOptionalPositiveInt(value: unknown): number | undefined | null { - const parsed = parseOptionalFiniteNumber(value); - if (parsed === undefined || parsed === null) return parsed; - if (!Number.isInteger(parsed) || parsed < 1) return null; - return parsed; -} - -export function registerApiTriggers( - sdk: ISdk, - kv: StateKV, - secret?: string, - metricsStore?: MetricsStore, - provider?: ResilientProvider | { circuitState?: unknown }, -): void { - sdk.registerFunction( - "middleware::api-auth", - async (input: { - request?: { headers?: Record }; - }) => { - if (!secret) return { action: "continue" }; - const headers = input?.request?.headers || {}; - const auth = headers["authorization"] || headers["Authorization"]; - if ( - typeof auth !== "string" || - !timingSafeCompare(auth, `Bearer ${secret}`) - ) { - return { - action: "respond", - response: { status_code: 401, body: { error: "unauthorized" } }, - }; - } - return { action: "continue" }; - }, - ); - - sdk.registerFunction("api::liveness", - async (): Promise => ({ - status_code: 200, - body: { status: "ok", service: "agentmemory", viewerPort: getBoundViewerPort(), viewerSkipped: getViewerSkipped() }, - }), - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::liveness", - config: { api_path: "/agentmemory/livez", http_method: "GET" }, - }); - - sdk.registerFunction("api::config-flags", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const providerKind = detectLlmProviderKind(); - const embeddingProvider = detectEmbeddingProvider() ? "embeddings" : "none"; - const flags = [ - { - key: "GRAPH_EXTRACTION_ENABLED", - label: "Knowledge graph extraction", - enabled: isGraphExtractionEnabled(), - default: false, - affects: ["Graph", "Dashboard"], - needsLlm: true, - description: "Extracts entities and relations from observations into a knowledge graph.", - enableHow: "Set GRAPH_EXTRACTION_ENABLED=true and provide an LLM key, then restart.", - docsHref: "https://github.com/MaimoryLab/agentmemory-lab#knowledge-graph", - }, - { - key: "CONSOLIDATION_ENABLED", - label: "Memory consolidation", - enabled: isConsolidationEnabled(), - default: false, - affects: ["Dashboard", "Memories", "Crystals"], - needsLlm: true, - description: "Periodically summarizes sessions into semantic facts + procedures.", - enableHow: "Set CONSOLIDATION_ENABLED=true and provide an LLM key, then restart.", - docsHref: "https://github.com/MaimoryLab/agentmemory-lab#consolidation", - }, - { - key: "AGENTMEMORY_AUTO_COMPRESS", - label: "LLM-powered observation compression", - enabled: isAutoCompressEnabled(), - default: false, - affects: ["Memories", "Timeline"], - needsLlm: true, - description: "Every observation is compressed by the LLM for richer summaries (costs tokens). OFF uses zero-LLM synthetic compression.", - enableHow: "Set AGENTMEMORY_AUTO_COMPRESS=true and provide an LLM key.", - docsHref: "https://github.com/MaimoryLab/agentmemory-lab/issues/138", - }, - { - key: "AGENTMEMORY_INJECT_CONTEXT", - label: "In-conversation context injection", - enabled: isContextInjectionEnabled(), - default: false, - affects: ["Hooks"], - needsLlm: false, - description: "Hooks write recalled context into Claude Code's conversation. OFF captures in the background without injecting.", - enableHow: "Set AGENTMEMORY_INJECT_CONTEXT=true and restart.", - docsHref: "https://github.com/MaimoryLab/agentmemory-lab/issues/143", - }, - ]; - return { - status_code: 200, - body: { - version: VERSION, - provider: providerKind, - embeddingProvider, - flags, - }, - }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::config-flags", - config: { - api_path: "/agentmemory/config/flags", - http_method: "GET", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::todo-extractor-config", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (req.body && Object.keys(req.body as Record).length) { - const body = req.body as Record; - const updates: Record = {}; - // Iterate the single writable-keys source of truth so a UI field can - // never silently drop (writeUserEnv applies the same allowlist). - for (const key of WRITABLE_TODO_EXTRACT_KEYS) { - const value = cleanConfigValue(body[key]); - if (value) updates[key] = value; - } - if (Object.keys(updates).length) writeUserEnv(updates); - } - return { - status_code: 200, - body: { - success: true, - envPath: getUserEnvPath(), - config: getTodoExtractorUserConfig(), - restartRequired: false, - }, - }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::todo-extractor-config", - config: { - api_path: "/agentmemory/config/todo-extractor", - http_method: "POST", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - sdk.registerTrigger({ - type: "http", - function_id: "api::todo-extractor-config", - config: { - api_path: "/agentmemory/config/todo-extractor", - http_method: "GET", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::health", - async (req: ApiRequest): Promise => { - const health = await getLatestHealth(kv); - const functionMetrics = metricsStore ? await metricsStore.getAll() : []; - const circuitBreaker = - provider && "circuitState" in provider ? provider.circuitState : null; - - const status = health?.status || "healthy"; - const statusCode = status === "critical" ? 503 : 200; - - return { - status_code: statusCode, - body: { - status, - service: "agentmemory", - version: VERSION, - health: health || null, - functionMetrics, - circuitBreaker, - viewerPort: getBoundViewerPort(), - viewerSkipped: getViewerSkipped(), - }, - }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::health", - config: { - api_path: "/agentmemory/health", - http_method: "GET", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::observe", - async (req: ApiRequest): Promise => { - const body = (req.body ?? {}) as Record; - const hookType = asNonEmptyString(body.hookType); - const sessionId = asNonEmptyString(body.sessionId); - const project = asNonEmptyString(body.project); - const cwd = asNonEmptyString(body.cwd); - const timestamp = asNonEmptyString(body.timestamp); - if (!hookType || !sessionId || !project || !cwd || !timestamp) { - return { - status_code: 400, - body: { - error: - "hookType, sessionId, project, cwd, and timestamp are required strings", - }, - }; - } - const payload: HookPayload = { - hookType: hookType as HookPayload["hookType"], - sessionId, - project, - cwd, - timestamp, - data: body.data, - }; - const result = await sdk.trigger({ function_id: "mem::observe", payload }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::observe", - config: { - api_path: "/agentmemory/observe", - http_method: "POST", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::context", - async ( - req: ApiRequest<{ sessionId: string; project: string; budget?: number }>, - ): Promise => { - const body = (req.body ?? {}) as Record; - const sessionId = asNonEmptyString(body.sessionId); - const project = asNonEmptyString(body.project); - if (!sessionId || !project) { - return { - status_code: 400, - body: { error: "sessionId and project are required strings" }, - }; - } - const budget = parseOptionalPositiveInt(body.budget); - if (budget === null) { - return { - status_code: 400, - body: { error: "budget must be a positive integer" }, - }; - } - const payload: { sessionId: string; project: string; budget?: number } = { - sessionId, - project, - }; - if (budget !== undefined) payload.budget = budget; - const result = await sdk.trigger({ function_id: "mem::context", payload }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::context", - config: { - api_path: "/agentmemory/context", - http_method: "POST", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::search", - async ( - req: ApiRequest<{ - query: string; - limit?: number; - project?: string; - cwd?: string; - format?: string; - token_budget?: number; - }>, - ): Promise => { - const body = (req.body ?? {}) as Record; - if (typeof body.query !== "string" || !body.query.trim()) { - return { status_code: 400, body: { error: "query is required and must be a non-empty string" } }; - } - if ( - body.limit !== undefined && - (!Number.isInteger(body.limit) || (body.limit as number) < 1) - ) { - return { status_code: 400, body: { error: "limit must be a positive integer" } }; - } - if (body.project !== undefined && typeof body.project !== "string") { - return { status_code: 400, body: { error: "project must be a string" } }; - } - if (body.cwd !== undefined && typeof body.cwd !== "string") { - return { status_code: 400, body: { error: "cwd must be a string" } }; - } - if ( - body.format !== undefined && - (typeof body.format !== "string" || - !["full", "compact", "narrative"].includes(body.format.trim().toLowerCase())) - ) { - return { - status_code: 400, - body: { error: "format must be one of: full, compact, narrative" }, - }; - } - if ( - body.token_budget !== undefined && - (!Number.isInteger(body.token_budget) || (body.token_budget as number) < 1) - ) { - return { - status_code: 400, - body: { error: "token_budget must be a positive integer" }, - }; - } - const payload = { - query: body.query.trim(), - limit: body.limit as number | undefined, - project: body.project as string | undefined, - cwd: body.cwd as string | undefined, - format: - typeof body.format === "string" - ? body.format.trim().toLowerCase() - : undefined, - token_budget: body.token_budget as number | undefined, - }; - const result = await sdk.trigger({ function_id: "mem::search", payload: payload }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::search", - config: { - api_path: "/agentmemory/search", - http_method: "POST", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::compress-file", - async (req: ApiRequest<{ filePath: string }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const body = (req.body ?? {}) as Record; - const filePath = asNonEmptyString(body.filePath); - if (!filePath) { - return { - status_code: 400, - body: { error: "filePath is required and must be a non-empty string" }, - }; - } - const result = await sdk.trigger({ - function_id: "mem::compress-file", - payload: { filePath }, - }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::compress-file", - config: { api_path: "/agentmemory/compress-file", http_method: "POST" }, - }); - - sdk.registerFunction("api::replay::load", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const sessionId = asNonEmptyString(req.query_params?.["sessionId"]); - if (!sessionId) { - return { status_code: 400, body: { error: "sessionId is required" } }; - } - const result = await sdk.trigger({ - function_id: "mem::replay::load", - payload: { sessionId }, - }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::replay::load", - config: { api_path: "/agentmemory/replay/load", http_method: "GET" }, - }); - - sdk.registerFunction("api::replay::sessions", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const sessions = await kv.list(KV.sessions); - sessions.sort((a, b) => - (b.startedAt || "").localeCompare(a.startedAt || ""), - ); - return { status_code: 200, body: { success: true, sessions } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::replay::sessions", - config: { api_path: "/agentmemory/replay/sessions", http_method: "GET" }, - }); - - sdk.registerFunction("api::replay::import", - async ( - req: ApiRequest<{ path?: string; maxFiles?: number }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const body = (req.body ?? {}) as Record; - const payload: { path?: string; maxFiles?: number } = {}; - if (body.path !== undefined) { - if (typeof body.path !== "string" || body.path.trim().length === 0) { - return { - status_code: 400, - body: { error: "path must be a non-empty string" }, - }; - } - payload.path = body.path.trim(); - } - if (body.maxFiles !== undefined) { - const n = body.maxFiles as number; - if ( - !Number.isInteger(n) || - n < 1 || - n > MAX_FILES_UPPER_BOUND - ) { - return { - status_code: 400, - body: { - error: `maxFiles must be an integer between 1 and ${MAX_FILES_UPPER_BOUND}`, - }, - }; - } - payload.maxFiles = n; - } - const result = await sdk.trigger({ - function_id: "mem::replay::import-jsonl", - payload, - }); - return { status_code: 202, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::replay::import", - config: { api_path: "/agentmemory/replay/import-jsonl", http_method: "POST" }, - }); - - sdk.registerFunction("api::session::start", - async ( - req: ApiRequest<{ sessionId: string; project: string; cwd: string }>, - ): Promise => { - const body = (req.body ?? {}) as Record; - const sessionId = asNonEmptyString(body.sessionId); - const project = asNonEmptyString(body.project); - const cwd = asNonEmptyString(body.cwd); - if (!sessionId || !project || !cwd) { - return { - status_code: 400, - body: { - error: "sessionId, project, and cwd are required non-empty strings", - }, - }; - } - const title = typeof body.title === "string" ? body.title.trim() : undefined; - // allow session/start to override AGENT_ID from request body - // (multi-agent runtimes that route many roles through one server - // process). Falls back to the AGENT_ID env on the server. - const requestAgentId = - typeof body.agentId === "string" && body.agentId.trim().length > 0 - ? body.agentId.trim().slice(0, 128) - : undefined; - const agentId = requestAgentId ?? getAgentId(); - const session: Session = { - id: sessionId, - project, - cwd, - startedAt: new Date().toISOString(), - status: "active", - observationCount: 0, - ...(title ? { summary: title.slice(0, 200) } : {}), - ...(title ? { firstPrompt: title.slice(0, 200) } : {}), - ...(agentId ? { agentId } : {}), - }; - await kv.set(KV.sessions, sessionId, session); - const contextResult = await sdk.trigger< - { sessionId: string; project: string }, - { context: string } - >({ function_id: "mem::context", payload: { sessionId, project } }); - return { - status_code: 200, - body: { session, context: contextResult.context }, - }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::session::start", - config: { - api_path: "/agentmemory/session/start", - http_method: "POST", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::session::end", - async (req: ApiRequest<{ sessionId: string }>): Promise => { - const sessionId = asNonEmptyString((req.body as Record)?.sessionId); - if (!sessionId) { - return { - status_code: 400, - body: { error: "sessionId is required and must be a non-empty string" }, - }; - } - await kv.update(KV.sessions, sessionId, [ - { type: "set", path: "endedAt", value: new Date().toISOString() }, - { type: "set", path: "status", value: "completed" }, - ]); - // Fan out session-stopped lifecycle (non-blocking). - try { - void sdk.trigger({ function_id: "event::session::stopped", payload: { sessionId }, action: TriggerAction.Void() }).catch(() => {}); - } catch (err) { - logger.warn("event::session::stopped trigger failed", { - sessionId, - error: err instanceof Error ? err.message : String(err), - }); - } - return { status_code: 200, body: { success: true } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::session::end", - config: { - api_path: "/agentmemory/session/end", - http_method: "POST", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::summarize", - async (req: ApiRequest<{ sessionId: string }>): Promise => { - const sessionId = asNonEmptyString((req.body as Record)?.sessionId); - if (!sessionId) { - return { status_code: 400, body: { error: "sessionId is required" } }; - } - const result = await sdk.trigger({ - function_id: "mem::summarize", - payload: { sessionId }, - }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::summarize", - config: { - api_path: "/agentmemory/summarize", - http_method: "POST", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::session::commit", - async (req: ApiRequest): Promise => { - const body = (req.body ?? {}) as Record; - const sha = asNonEmptyString(body.sha); - if (!sha) { - return { - status_code: 400, - body: { error: "sha is required and must be a non-empty string" }, - }; - } - const sessionId = asNonEmptyString(body.sessionId) ?? undefined; - const branch = asNonEmptyString(body.branch) ?? undefined; - const repo = asNonEmptyString(body.repo) ?? undefined; - const message = asNonEmptyString(body.message) ?? undefined; - const author = asNonEmptyString(body.author) ?? undefined; - const authoredAt = asNonEmptyString(body.authoredAt) ?? undefined; - const files = Array.isArray(body.files) - ? (body.files as unknown[]).filter( - (f): f is string => typeof f === "string" && f.length > 0, - ) - : undefined; - - const link = await withKeyedLock(`commit:${sha}`, async () => { - const existing = await kv.get(KV.commits, sha); - const sessionSet = new Set(existing?.sessionIds ?? []); - if (sessionId) sessionSet.add(sessionId); - const merged: CommitLink = { - sha, - shortSha: existing?.shortSha ?? sha.slice(0, 7), - branch: branch ?? existing?.branch, - repo: repo ?? existing?.repo, - message: message ?? existing?.message, - author: author ?? existing?.author, - authoredAt: authoredAt ?? existing?.authoredAt, - files: files ?? existing?.files, - sessionIds: Array.from(sessionSet), - linkedAt: existing?.linkedAt ?? new Date().toISOString(), - }; - await kv.set(KV.commits, sha, merged); - return merged; - }); - - if (sessionId) { - await withKeyedLock(`session:${sessionId}`, async () => { - const session = await kv.get(KV.sessions, sessionId); - if (!session) return; - const shaSet = new Set(session.commitShas ?? []); - shaSet.add(sha); - session.commitShas = Array.from(shaSet); - await kv.set(KV.sessions, sessionId, session); - }); - } - - return { status_code: 200, body: { commit: link } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::session::commit", - config: { - api_path: "/agentmemory/session/commit", - http_method: "POST", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::session::by-commit", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const sha = asNonEmptyString(req.query_params?.["sha"]); - if (!sha) { - return { - status_code: 400, - body: { error: "sha is required and must be a non-empty string" }, - }; - } - const link = await kv.get(KV.commits, sha); - if (!link) { - return { - status_code: 404, - body: { error: "no sessions linked to this commit" }, - }; - } - const fetched = await Promise.all( - (link.sessionIds ?? []).map((sid) => kv.get(KV.sessions, sid)), - ); - const sessions = fetched.filter((s): s is Session => s !== null); - return { status_code: 200, body: { commit: link, sessions } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::session::by-commit", - config: { - api_path: "/agentmemory/session/by-commit", - http_method: "GET", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::commits", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const branch = asNonEmptyString(req.query_params?.["branch"]); - const repo = asNonEmptyString(req.query_params?.["repo"]); - const rawLimit = parseOptionalInt(req.query_params?.["limit"]); - const limit = Math.max(1, Math.min(500, rawLimit ?? 100)); - const all = await kv.list(KV.commits); - const filtered = all - .filter((c) => !branch || c.branch === branch) - .filter((c) => !repo || c.repo === repo) - .sort((a, b) => ((a.linkedAt ?? "") < (b.linkedAt ?? "") ? 1 : -1)) - .slice(0, limit); - return { status_code: 200, body: { commits: filtered } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::commits", - config: { - api_path: "/agentmemory/commits", - http_method: "GET", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::sessions", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const sessions = await kv.list(KV.sessions); - const normalizedAgentId = - typeof req.query_params?.["agentId"] === "string" - ? req.query_params["agentId"].trim() - : undefined; - const wildcardAgent = normalizedAgentId === "*"; - const explicitAgentId = - normalizedAgentId && !wildcardAgent ? normalizedAgentId : undefined; - const filterAgentId = wildcardAgent - ? undefined - : explicitAgentId ?? - (isAgentScopeIsolated() ? getAgentId() : undefined); - const filtered = filterAgentId - ? sessions.filter((s) => s.agentId === filterAgentId) - : sessions; - return { status_code: 200, body: { sessions: filtered } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::sessions", - config: { api_path: "/agentmemory/sessions", http_method: "GET" }, - }); - - sdk.registerFunction("api::sessions::delete", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const project = asNonEmptyString(req.query_params?.["project"]); - if (!project) { - return { status_code: 400, body: { error: "project is required" } }; - } - const sessions = await kv.list(KV.sessions); - // Scope deletes to the calling agent when isolation is on, mirroring GET /sessions. - const filterAgentId = isAgentScopeIsolated() ? getAgentId() : undefined; - const targets = sessions.filter( - (s) => s.project === project && (!filterAgentId || s.agentId === filterAgentId), - ); - for (const s of targets) { - await kv.delete(KV.sessions, s.id); - } - // ponytail: removes session records only; their observations/memories - // remain (use governance bulk-delete for those). Enough to clear the - // sessions `demo` seeds, which is what advertises this route. - return { status_code: 200, body: { success: true, deleted: targets.length } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::sessions::delete", - config: { api_path: "/agentmemory/sessions", http_method: "DELETE" }, - }); - - sdk.registerFunction("api::session::highlights", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const sessionId = asNonEmptyString(req.query_params?.["sessionId"]); - if (!sessionId) { - return { status_code: 400, body: { error: "sessionId is required" } }; - } - const parsedMaxItems = parseOptionalPositiveInt(req.query_params?.["maxItems"]); - if (parsedMaxItems === null || (parsedMaxItems !== undefined && parsedMaxItems > 200)) { - return { - status_code: 400, - body: { error: "maxItems must be a positive integer no greater than 200" }, - }; - } - const payload: { sessionId: string; maxItems?: number } = { sessionId }; - if (parsedMaxItems !== undefined) payload.maxItems = parsedMaxItems; - const result = await sdk.trigger({ - function_id: "mem::session-highlights", - payload, - }); - const statusCode = - result && - typeof result === "object" && - (result as { success?: unknown; error?: unknown }).success === false && - (result as { error?: unknown }).error === "session_not_found" - ? 404 - : 200; - return { status_code: statusCode, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::session::highlights", - config: { - api_path: "/agentmemory/session/highlights", - http_method: "GET", - middleware_function_ids: ["middleware::api-auth"], - }, - }); - - sdk.registerFunction("api::observations", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const sessionId = asNonEmptyString(req.query_params?.["sessionId"]); - if (!sessionId) - return { status_code: 400, body: { error: "sessionId required" } }; - const observations = await kv.list( - KV.observations(sessionId), - ); - const normalizedAgentId = - typeof req.query_params?.["agentId"] === "string" - ? req.query_params["agentId"].trim() - : undefined; - const wildcardAgent = normalizedAgentId === "*"; - const explicitAgentId = - normalizedAgentId && !wildcardAgent ? normalizedAgentId : undefined; - const filterAgentId = wildcardAgent - ? undefined - : explicitAgentId ?? - (isAgentScopeIsolated() ? getAgentId() : undefined); - const filtered = filterAgentId - ? observations.filter((o) => o.agentId === filterAgentId) - : observations; - return { status_code: 200, body: { observations: filtered } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::observations", - config: { api_path: "/agentmemory/observations", http_method: "GET" }, - }); - - sdk.registerFunction("api::file-context", - async ( - req: ApiRequest<{ sessionId: string; files: string[] }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::file-context", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::file-context", - config: { api_path: "/agentmemory/file-context", http_method: "POST" }, - }); - - sdk.registerFunction("api::enrich", - async ( - req: ApiRequest<{ - sessionId: string; - files: string[]; - terms?: string[]; - toolName?: string; - project?: string; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if ( - !req.body?.sessionId || - typeof req.body.sessionId !== "string" || - !Array.isArray(req.body?.files) || - req.body.files.length === 0 || - !req.body.files.every((f: unknown) => typeof f === "string") - ) { - return { - status_code: 400, - body: { - error: "sessionId (string) and files (string[]) are required", - }, - }; - } - if ( - req.body.terms !== undefined && - (!Array.isArray(req.body.terms) || - !req.body.terms.every((t: unknown) => typeof t === "string")) - ) { - return { - status_code: 400, - body: { error: "terms must be an array of strings" }, - }; - } - if ( - req.body.project !== undefined && - (typeof req.body.project !== "string" || !req.body.project.trim()) - ) { - return { - status_code: 400, - body: { error: "project must be a non-empty string" }, - }; - } - const result = await sdk.trigger({ - function_id: "mem::enrich", - payload: { - sessionId: req.body.sessionId, - files: req.body.files, - ...(req.body.terms !== undefined && { terms: req.body.terms }), - ...(req.body.toolName !== undefined && { toolName: req.body.toolName }), - ...(req.body.project !== undefined && { project: req.body.project }), - }, - }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::enrich", - config: { api_path: "/agentmemory/enrich", http_method: "POST" }, - }); - - sdk.registerFunction("api::remember", - async ( - req: ApiRequest<{ - content: string; - type?: string; - concepts?: string[]; - files?: string[]; - ttlDays?: number; - sourceObservationIds?: string[]; - project?: string; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if ( - !req.body?.content || - typeof req.body.content !== "string" || - !req.body.content.trim() - ) { - return { status_code: 400, body: { error: "content is required" } }; - } - if ( - req.body.project !== undefined && - (typeof req.body.project !== "string" || !req.body.project.trim()) - ) { - return { status_code: 400, body: { error: "project must be a non-empty string" } }; - } - const result = await sdk.trigger({ - function_id: "mem::remember", - payload: { - content: req.body.content, - ...(req.body.type !== undefined && { type: req.body.type }), - ...(req.body.concepts !== undefined && { concepts: req.body.concepts }), - ...(req.body.files !== undefined && { files: req.body.files }), - ...(req.body.ttlDays !== undefined && { ttlDays: req.body.ttlDays }), - ...(req.body.sourceObservationIds !== undefined && { sourceObservationIds: req.body.sourceObservationIds }), - ...(req.body.project !== undefined && { project: req.body.project }), - }, - }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::remember", - config: { api_path: "/agentmemory/remember", http_method: "POST" }, - }); - - sdk.registerFunction("api::review-create", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const body = (req.body ?? {}) as Record; - const syncMode = body.mode === "sync" || body.source === "browser-sync"; - const rawKind = reviewKind(body.kind); - const now = new Date().toISOString(); - const page = body.page && typeof body.page === "object" ? body.page as Record : {}; - const rawConversation = body.conversation && typeof body.conversation === "object" ? body.conversation as Record : {}; - const rawTurns = Array.isArray(rawConversation.turns) ? rawConversation.turns : []; - const conversation = { - provider: asNonEmptyString(rawConversation.provider) || undefined, - promptDraft: asNonEmptyString(rawConversation.promptDraft) || undefined, - turns: rawTurns.map((turn) => { - const t = turn && typeof turn === "object" ? turn as Record : {}; - const role = asNonEmptyString(t.role) || "unknown"; - const text = asNonEmptyString(t.text); - return text ? { role, text } : null; - }).filter((turn): turn is { role: string; text: string } => turn !== null).slice(-12), - }; - const autoCandidate = syncMode ? buildBrowserMemoryCandidate(body, page, conversation) : null; - const rawContent = asNonEmptyString(body.content) || autoCandidate?.content; - if (!rawContent) { - return { status_code: 400, body: { error: "content is required" } }; - } - const title = asNonEmptyString(body.title) || autoCandidate?.title || defaultReviewTitle(rawKind, asNonEmptyString(page.title) || undefined); - if (!isReviewCandidateDisplayable(title, rawContent)) { - return { status_code: 400, body: { error: "review_content_not_displayable" } }; - } - const payload = body.payload && typeof body.payload === "object" ? body.payload as Record : {}; - const source = syncMode ? "browser-sync" : (body.source === "viewer" || body.source === "api" ? body.source : "browser-extension"); - const syncId = syncMode ? browserSyncKey(page, conversation) : ""; - const itemId = syncId ? `browser_sync_${syncId}` : `review_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 10)}`; - const existing = syncId ? await kv.get(KV.reviewQueue, itemId) : null; - const item: ReviewQueueItem = { - id: existing?.id || itemId, - createdAt: existing?.createdAt || now, - updatedAt: now, - status: autoCandidate?.decision === "evidence_only" ? "dismissed" : (existing?.status || "pending"), - kind: rawKind, - title, - content: rawContent, - source, - ...(autoCandidate ? { decision: autoCandidate.decision, confidence: autoCandidate.confidence, reason: autoCandidate.reason } : {}), - page: { - type: typeof page.type === "string" ? page.type : undefined, - typeLabel: typeof page.typeLabel === "string" ? page.typeLabel : undefined, - title: typeof page.title === "string" ? page.title : undefined, - url: typeof page.url === "string" ? page.url : undefined, - host: typeof page.host === "string" ? page.host : undefined, - }, - ...(conversation.provider || conversation.promptDraft || conversation.turns.length ? { conversation } : {}), - payload: { - ...payload, - ...(autoCandidate ? { type: autoCandidate.type, decision: autoCandidate.decision, confidence: autoCandidate.confidence, reason: autoCandidate.reason } : {}), - ...(syncId ? { browserSyncId: syncId } : {}), - }, - }; - // STEP-14: browser sessions now flow through the todo-extract LLM pipeline - // (recorded below as a session + observations), not the rule-based action - // drafts that produced session-narration noise. - if (item.source === "browser-extension" || item.source === "browser-sync") { - try { - const browserSession = await recordBrowserSessionFromReview(sdk, item); - item.payload = { - ...(item.payload || {}), - browserSessionId: browserSession.sessionId, - browserObservationCount: browserSession.observationCount, - }; - } catch (err) { - logger.warn("failed to record browser session from review candidate", { - reviewId: item.id, - error: err instanceof Error ? err.message : String(err), - }); - } - } - await kv.set(KV.reviewQueue, item.id, item); - return { - status_code: existing ? 200 : 201, - body: { success: true, item }, - }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::review-create", - config: { api_path: "/agentmemory/review", http_method: "POST" }, - }); - - sdk.registerFunction("api::review-list", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const status = asNonEmptyString(req.query_params?.["status"]); - const kinds = new Set((asNonEmptyString(req.query_params?.["kind"]) || "") - .split(",") - .map((kind) => kind.trim()) - .filter(Boolean)); - const rawLimit = parseOptionalInt(req.query_params?.["limit"]); - const limit = Math.max(1, Math.min(200, rawLimit ?? 50)); - const items = (await kv.list(KV.reviewQueue)) - .filter((item) => !status || item.status === status) - .filter((item) => kinds.size === 0 || kinds.has(item.kind)) - .sort((a, b) => (b.createdAt || "").localeCompare(a.createdAt || "")) - .slice(0, limit); - return { status_code: 200, body: { items } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::review-list", - config: { api_path: "/agentmemory/review", http_method: "GET" }, - }); - - sdk.registerFunction("api::review-actions-generate", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const body = (req.body ?? {}) as Record; - const maxSessions = parseOptionalPositiveInt(body.maxSessions); - if (maxSessions === null) { - return { status_code: 400, body: { error: "maxSessions must be a positive integer" } }; - } - const maxObservationsPerSession = parseOptionalPositiveInt(body.maxObservationsPerSession); - if (maxObservationsPerSession === null) { - return { status_code: 400, body: { error: "maxObservationsPerSession must be a positive integer" } }; - } - const payload: Record = {}; - if (maxSessions !== undefined) payload.maxSessions = maxSessions; - if (maxObservationsPerSession !== undefined) { - payload.maxObservationsPerSession = maxObservationsPerSession; - } - const project = asNonEmptyString(body.project); - if (project) payload.project = project; - const result = await sdk.trigger({ - function_id: "mem::action-candidates-generate", - payload, - }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::review-actions-generate", - config: { api_path: "/agentmemory/review/actions/generate", http_method: "POST" }, - }); - - sdk.registerFunction("api::todo-extract-generate", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const body = (req.body ?? {}) as Record; - const maxSessions = parseOptionalPositiveInt(body.maxSessions); - if (maxSessions === null) { - return { status_code: 400, body: { error: "maxSessions must be a positive integer" } }; - } - const maxObservationsPerSession = parseOptionalPositiveInt(body.maxObservationsPerSession); - if (maxObservationsPerSession === null) { - return { status_code: 400, body: { error: "maxObservationsPerSession must be a positive integer" } }; - } - const sinceDays = parseOptionalPositiveInt(body.sinceDays); - if (sinceDays === null) { - return { status_code: 400, body: { error: "sinceDays must be a positive integer" } }; - } - const maxInteractionsPerSession = parseOptionalPositiveInt(body.maxInteractionsPerSession); - if (maxInteractionsPerSession === null) { - return { status_code: 400, body: { error: "maxInteractionsPerSession must be a positive integer" } }; - } - const payload: Record = {}; - if (maxSessions !== undefined) payload.maxSessions = maxSessions; - if (maxObservationsPerSession !== undefined) payload.maxObservationsPerSession = maxObservationsPerSession; - if (sinceDays !== undefined) payload.sinceDays = sinceDays; - if (maxInteractionsPerSession !== undefined) payload.maxInteractionsPerSession = maxInteractionsPerSession; - const project = asNonEmptyString(body.project); - if (project) payload.project = project; - if (body.force === true) payload.force = true; - if (body.cleanup === "none" || body.cleanup === "dry-run" || body.cleanup === "apply") payload.cleanup = body.cleanup; - const result = await sdk.trigger({ function_id: "mem::todo-extract-generate", payload }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::todo-extract-generate", - config: { api_path: "/agentmemory/todo-extract/generate", http_method: "POST" }, - }); - - sdk.registerFunction("api::todo-refresh-action", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const body = (req.body ?? {}) as Record; - const actionId = asNonEmptyString(body.actionId); - if (!actionId) return { status_code: 400, body: { error: "actionId is required" } }; - const result = await sdk.trigger({ function_id: "mem::todo-refresh-action", payload: { actionId } }) as Record; - if (result.success === false && result.reason === "action-not-found") return { status_code: 404, body: result }; - if (result.success === false) return { status_code: 400, body: result }; - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::todo-refresh-action", - config: { api_path: "/agentmemory/todo/action-refresh", http_method: "POST" }, - }); - - sdk.registerFunction("api::todo-update", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const body = (req.body ?? {}) as Record; - const maxCards = parseOptionalPositiveInt(body.maxCards); - if (maxCards === null) { - return { status_code: 400, body: { error: "maxCards must be a positive integer" } }; - } - const payload: Record = {}; - if (body.mode === "dry-run" || body.mode === "apply") payload.mode = body.mode; - if (body.scope === "changed" || body.scope === "all") payload.scope = body.scope; - if (maxCards !== undefined) payload.maxCards = maxCards; - // Apply previously-previewed decisions verbatim (no LLM re-call) when given. - if (Array.isArray(body.decisions)) payload.decisions = body.decisions; - const result = await sdk.trigger({ function_id: "mem::todo-update", payload }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::todo-update", - config: { api_path: "/agentmemory/todo/update", http_method: "POST" }, - }); - - sdk.registerFunction("api::review-approve", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const body = (req.body ?? {}) as Record; - const id = asNonEmptyString(body.id); - if (!id) return { status_code: 400, body: { error: "id is required" } }; - const item = await kv.get(KV.reviewQueue, id); - if (!item) return { status_code: 404, body: { error: "review item not found" } }; - if (item.status !== "pending") return { status_code: 409, body: { error: "review item is not pending" } }; - const content = asNonEmptyString(body.content) || item.content; - const title = asNonEmptyString(body.title) || item.title; - const project = asNonEmptyString(body.project); - const tags = Array.isArray(body.tags) - ? body.tags.map((tag) => typeof tag === "string" ? tag.trim() : "").filter(Boolean) - : typeof body.tags === "string" - ? body.tags.split(",").map((tag) => tag.trim()).filter(Boolean) - : undefined; - const now = new Date().toISOString(); - let result: unknown; - const approvedKind = body.kind === "lesson" || body.kind === "memory" || body.kind === "action" ? body.kind : item.kind; - const payload = (item.payload || {}) as Record; - const actionCandidate = payload.actionCandidate && typeof payload.actionCandidate === "object" - ? payload.actionCandidate as Record - : {}; - const provider = asNonEmptyString(payload.provider) || undefined; - const pageType = asNonEmptyString(payload.pageType) || item.page?.type; - const sourceLabel = asNonEmptyString(payload.sourceLabel) || provider || item.page?.typeLabel || item.page?.host; - const sourceConcepts = [ - "browser-context", - item.page?.host, - pageType ? `browser-page:${pageType}` : undefined, - provider ? `browser-source:${provider.toLowerCase()}` : undefined, - !provider && item.page?.host ? `browser-host:${item.page.host}` : undefined, - ].filter((value): value is string => typeof value === "string" && value.length > 0); - const sourceTags = [ - "browser", - "reviewed", - provider ? `source:${provider.toLowerCase()}` : undefined, - pageType ? `page:${pageType}` : undefined, - ].filter((value): value is string => typeof value === "string" && value.length > 0); - if (approvedKind === "action") { - const priorityValue = parseOptionalFiniteNumber(body.priority) ?? parseOptionalFiniteNumber(actionCandidate.priority) ?? 5; - const priority = typeof priorityValue === "number" ? Math.max(1, Math.min(10, Math.floor(priorityValue))) : 5; - const todoExtraction = payload.todoExtraction && typeof payload.todoExtraction === "object" - ? payload.todoExtraction as Record - : {}; - const typeBucket = typeof todoExtraction.typeBucket === "string" ? todoExtraction.typeBucket : ""; - const dedupeKey = typeof todoExtraction.dedupeKey === "string" ? todoExtraction.dedupeKey : ""; - const actionStatus = - typeBucket === "done" ? "done" : - typeBucket === "in_progress" || typeBucket === "processing" ? "active" : - undefined; - const sourceObservationIds = Array.isArray(actionCandidate.sourceObservationIds) - ? actionCandidate.sourceObservationIds.filter((id): id is string => typeof id === "string" && id.length > 0) - : []; - result = await sdk.trigger({ - function_id: "mem::action-create", - payload: { - title, - description: content, - id: dedupeKey ? fingerprintId("act", `todo:${dedupeKey}`) : undefined, - priority, - createdBy: "review", - project: project || (typeof payload.project === "string" ? payload.project : undefined), - tags: tags || (Array.isArray(payload.tags) ? payload.tags : sourceTags), - sourceObservationIds, - status: actionStatus, - metadata: Object.keys(todoExtraction).length ? { todoExtraction } : undefined, - }, - }); - const resultObj = result && typeof result === "object" ? result as Record : {}; - const action = resultObj.action && typeof resultObj.action === "object" ? resultObj.action as Record : null; - if (!resultObj.success || !action?.id) { - return { - status_code: 502, - body: { - success: false, - error: typeof resultObj.error === "string" ? resultObj.error : "action_create_failed", - result, - }, - }; - } - } else if (approvedKind === "lesson") { - result = await sdk.trigger({ - function_id: "mem::lesson-save", - payload: { - content, - context: typeof payload.context === "string" ? payload.context : [item.page?.title, item.page?.url].filter(Boolean).join("\n"), - confidence: typeof payload.confidence === "number" ? payload.confidence : 0.75, - project: project || (typeof payload.project === "string" ? payload.project : "browser"), - tags: tags || (Array.isArray(payload.tags) ? payload.tags : sourceTags), - source: "manual", - }, - }); - } else { - const rawType = asNonEmptyString(body.type) || (typeof payload.type === "string" ? payload.type : "fact"); - const validTypes = new Set(["pattern", "preference", "architecture", "bug", "workflow", "fact"]); - const concepts = Array.isArray(payload.concepts) - ? Array.from(new Set([...payload.concepts.filter((c): c is string => typeof c === "string" && c.length > 0), ...sourceConcepts])) - : sourceConcepts; - result = await sdk.trigger({ - function_id: "mem::remember", - payload: { - content: title ? `${title}\n\n${content}` : content, - type: validTypes.has(rawType) ? rawType : "fact", - concepts, - files: Array.isArray(payload.files) ? payload.files : [], - project: project || (typeof payload.project === "string" ? payload.project : "browser"), - }, - }); - } - const resultObj = result && typeof result === "object" ? result as Record : {}; - const resultAction = resultObj.action && typeof resultObj.action === "object" ? resultObj.action as Record : null; - const resultMemory = resultObj.memory && typeof resultObj.memory === "object" ? resultObj.memory as Record : null; - const resultLesson = resultObj.lesson && typeof resultObj.lesson === "object" ? resultObj.lesson as Record : null; - item.status = "approved"; - item.kind = approvedKind; - item.title = title; - item.content = content; - item.payload = { - ...(item.payload || {}), - ...(project ? { project } : {}), - ...(tags ? { tags } : {}), - ...(body.type ? { type: body.type } : {}), - ...(sourceLabel ? { sourceLabel } : {}), - ...(provider ? { provider } : {}), - ...(pageType ? { pageType } : {}), - }; - item.updatedAt = now; - item.reviewedAt = now; - item.resultId = String(resultAction?.id || resultMemory?.id || resultLesson?.id || ""); - await kv.set(KV.reviewQueue, item.id, item); - return { status_code: 200, body: { success: true, item, result } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::review-approve", - config: { api_path: "/agentmemory/review/approve", http_method: "POST" }, - }); - - sdk.registerFunction("api::review-dismiss", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const body = (req.body ?? {}) as Record; - const id = asNonEmptyString(body.id); - if (!id) return { status_code: 400, body: { error: "id is required" } }; - const item = await kv.get(KV.reviewQueue, id); - if (!item) return { status_code: 404, body: { error: "review item not found" } }; - const now = new Date().toISOString(); - item.status = "dismissed"; - item.updatedAt = now; - item.reviewedAt = now; - await kv.set(KV.reviewQueue, item.id, item); - return { status_code: 200, body: { success: true, item } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::review-dismiss", - config: { api_path: "/agentmemory/review/dismiss", http_method: "POST" }, - }); - - sdk.registerFunction("api::forget", - async ( - req: ApiRequest<{ - sessionId?: string; - observationIds?: string[]; - memoryId?: string; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.sessionId && !req.body?.memoryId) { - return { - status_code: 400, - body: { error: "sessionId or memoryId is required" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::forget", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::forget", - config: { api_path: "/agentmemory/forget", http_method: "POST" }, - }); - - sdk.registerFunction("api::consolidate", - async ( - req: ApiRequest<{ project?: string; minObservations?: number }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::consolidate", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::consolidate", - config: { api_path: "/agentmemory/consolidate", http_method: "POST" }, - }); - - sdk.registerFunction("api::patterns", - async (req: ApiRequest<{ project?: string }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::patterns", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::patterns", - config: { api_path: "/agentmemory/patterns", http_method: "POST" }, - }); - - sdk.registerFunction("api::generate-rules", - async (req: ApiRequest<{ project?: string }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::generate-rules", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::generate-rules", - config: { api_path: "/agentmemory/generate-rules", http_method: "POST" }, - }); - - sdk.registerFunction("api::migrate", - async ( - req: ApiRequest<{ dbPath?: string; step?: string; dryRun?: boolean }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const hasStep = - typeof req.body?.step === "string" && req.body.step.trim().length > 0; - const hasDbPath = - typeof req.body?.dbPath === "string" && req.body.dbPath.trim().length > 0; - if (!hasStep && !hasDbPath) { - return { - status_code: 400, - body: { error: "Either step (string) or dbPath (string) is required" }, - }; - } - const result = await sdk.trigger({ - function_id: "mem::migrate", - payload: { - ...(req.body.step !== undefined && { step: req.body.step }), - ...(req.body.dbPath !== undefined && { dbPath: req.body.dbPath }), - ...(req.body.dryRun !== undefined && { dryRun: req.body.dryRun }), - }, - }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::migrate", - config: { api_path: "/agentmemory/migrate", http_method: "POST" }, - }); - - sdk.registerFunction("api::evict", - async (req: ApiRequest<{ dryRun?: boolean }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const dryRun = - req.query_params?.["dryRun"] === "true" || req.body?.dryRun === true; - const result = await sdk.trigger({ function_id: "mem::evict", payload: { dryRun } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::evict", - config: { api_path: "/agentmemory/evict", http_method: "POST" }, - }); - - sdk.registerFunction("api::smart-search", - async ( - req: ApiRequest<{ query?: string; expandIds?: string[]; limit?: number }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if ( - !req.body?.query && - (!req.body?.expandIds || req.body.expandIds.length === 0) - ) { - return { - status_code: 400, - body: { error: "query or expandIds is required" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::smart-search", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::smart-search", - config: { api_path: "/agentmemory/smart-search", http_method: "POST" }, - }); - - sdk.registerFunction("api::timeline", - async ( - req: ApiRequest<{ - anchor: string; - project?: string; - before?: number; - after?: number; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.anchor) { - return { status_code: 400, body: { error: "anchor is required" } }; - } - const result = await sdk.trigger({ function_id: "mem::timeline", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::timeline", - config: { api_path: "/agentmemory/timeline", http_method: "POST" }, - }); - - sdk.registerFunction("api::profile", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const project = req.query_params["project"] as string; - if (!project) { - return { - status_code: 400, - body: { error: "project query param is required" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::profile", payload: { project } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::profile", - config: { api_path: "/agentmemory/profile", http_method: "GET" }, - }); - - sdk.registerFunction("api::export", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - // mem::export already supports maxSessions/offset internally, - // but the HTTP endpoint hardcoded an empty payload — so /export on a - // real corpus (40 sessions × 34K observations × 8K memories) hit the - // iii engine invocation timeout and `agentmemory-lab status` reported 0. - // Pass through the query-string pagination so callers can chunk. - const rawMax = req.query_params?.["maxSessions"]; - const rawOffset = req.query_params?.["offset"]; - const payload: { maxSessions?: number; offset?: number } = {}; - if (typeof rawMax === "string") { - const n = Number(rawMax); - if (Number.isInteger(n) && n > 0) payload.maxSessions = n; - } - if (typeof rawOffset === "string") { - const n = Number(rawOffset); - if (Number.isInteger(n) && n >= 0) payload.offset = n; - } - const result = await sdk.trigger({ - function_id: "mem::export", - payload, - }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::export", - config: { api_path: "/agentmemory/export", http_method: "GET" }, - }); - - sdk.registerFunction("api::import", - async ( - req: ApiRequest<{ - exportData: unknown; - strategy?: "merge" | "replace" | "skip"; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.exportData) { - return { status_code: 400, body: { error: "exportData is required" } }; - } - const result = await sdk.trigger({ function_id: "mem::import", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::import", - config: { api_path: "/agentmemory/import", http_method: "POST" }, - }); - - sdk.registerFunction("api::relations", - async ( - req: ApiRequest<{ sourceId: string; targetId: string; type: string }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.sourceId || !req.body?.targetId || !req.body?.type) { - return { - status_code: 400, - body: { error: "sourceId, targetId, and type are required" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::relate", payload: req.body }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::relations", - config: { api_path: "/agentmemory/relations", http_method: "POST" }, - }); - - sdk.registerFunction("api::evolve", - async ( - req: ApiRequest<{ - memoryId: string; - newContent: string; - newTitle?: string; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.memoryId || !req.body?.newContent) { - return { - status_code: 400, - body: { error: "memoryId and newContent are required" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::evolve", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::evolve", - config: { api_path: "/agentmemory/evolve", http_method: "POST" }, - }); - - sdk.registerFunction("api::auto-forget", - async (req: ApiRequest<{ dryRun?: boolean }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const dryRun = - req.query_params?.["dryRun"] === "true" || req.body?.dryRun === true; - const result = await sdk.trigger({ function_id: "mem::auto-forget", payload: { dryRun } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::auto-forget", - config: { api_path: "/agentmemory/auto-forget", http_method: "POST" }, - }); - - sdk.registerFunction("api::claude-bridge-read", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - try { - const result = await sdk.trigger({ function_id: "mem::claude-bridge-read", payload: {} }); - return { status_code: 200, body: result }; - } catch { - return { - status_code: 404, - body: { error: "Claude bridge not enabled" }, - }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::claude-bridge-read", - config: { api_path: "/agentmemory/claude-bridge/read", http_method: "GET" }, - }); - - sdk.registerFunction("api::claude-bridge-sync", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - try { - const result = await sdk.trigger({ function_id: "mem::claude-bridge-sync", payload: {} }); - return { status_code: 200, body: result }; - } catch { - return { - status_code: 404, - body: { error: "Claude bridge not enabled" }, - }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::claude-bridge-sync", - config: { - api_path: "/agentmemory/claude-bridge/sync", - http_method: "POST", - }, - }); - - sdk.registerFunction("api::graph-query", - async ( - req: ApiRequest<{ - startNodeId?: string; - nodeType?: string; - maxDepth?: number; - query?: string; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - try { - const result = await sdk.trigger({ function_id: "mem::graph-query", payload: req.body || {} }); - return { status_code: 200, body: result }; - } catch { - return graphDisabledResponse(); - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::graph-query", - config: { api_path: "/agentmemory/graph/query", http_method: "POST" }, - }); - - sdk.registerFunction("api::graph-stats", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - try { - const result = await sdk.trigger({ function_id: "mem::graph-stats", payload: {} }); - return { status_code: 200, body: result }; - } catch { - return graphDisabledResponse(); - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::graph-stats", - config: { api_path: "/agentmemory/graph/stats", http_method: "GET" }, - }); - - sdk.registerFunction("api::graph-extract", - async (req: ApiRequest<{ observations: unknown[] }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if ( - !Array.isArray(req.body?.observations) || - req.body.observations.length === 0 - ) { - return { - status_code: 400, - body: { error: "observations array is required" }, - }; - } - try { - const result = await sdk.trigger({ function_id: "mem::graph-extract", payload: req.body }); - return { status_code: 200, body: result }; - } catch { - return graphDisabledResponse(); - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::graph-extract", - config: { api_path: "/agentmemory/graph/extract", http_method: "POST" }, - }); - - // Backfill the knowledge graph from existing compressed observations. - // Viewer calls this when the graph is empty (#666). Iterates every - // session, collects observations that have a `title` (compressed only), - // and feeds them through `mem::graph-extract` in batches. - sdk.registerFunction("api::graph-build", - async (req: ApiRequest<{ batchSize?: number }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const batchSize = Math.max( - 1, - Math.min(100, Number((req.body as { batchSize?: number })?.batchSize) || 25), - ); - try { - const sessions = await kv.list(KV.sessions); - let totalNodes = 0; - let totalEdges = 0; - let batchesRun = 0; - for (const session of sessions) { - const sid = session?.id; - if (typeof sid !== "string" || sid.length === 0) continue; - const observations = await kv.list(KV.observations(sid)); - const compressed = observations.filter((o) => o && typeof o.title === "string" && o.title.length > 0); - if (compressed.length === 0) continue; - for (let i = 0; i < compressed.length; i += batchSize) { - const batch = compressed.slice(i, i + batchSize); - try { - const result = (await sdk.trigger({ - function_id: "mem::graph-extract", - payload: { observations: batch }, - })) as { success?: boolean; nodesAdded?: number; edgesAdded?: number }; - if (result?.success) { - totalNodes += Number(result.nodesAdded) || 0; - totalEdges += Number(result.edgesAdded) || 0; - } - batchesRun++; - } catch (err) { - logger.warn("graph-build batch failed", { - sessionId: sid, - batchIndex: Math.floor(i / batchSize), - error: err instanceof Error ? err.message : String(err), - }); - } - } - } - return { - status_code: 200, - body: { - success: true, - sessions: sessions.length, - batches: batchesRun, - nodes: totalNodes, - edges: totalEdges, - }, - }; - } catch { - return graphDisabledResponse(); - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::graph-build", - config: { api_path: "/agentmemory/graph/build", http_method: "POST" }, - }); - - sdk.registerFunction("api::consolidate-pipeline", - async (req: ApiRequest<{ tier?: string }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - try { - const result = await sdk.trigger({ function_id: "mem::consolidate-pipeline", payload: req.body || {}, - }); - return { status_code: 200, body: result }; - } catch { - return consolidationDisabledResponse(); - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::consolidate-pipeline", - config: { - api_path: "/agentmemory/consolidate-pipeline", - http_method: "POST", - }, - }); - - sdk.registerFunction("api::team-share", - async ( - req: ApiRequest<{ itemId: string; itemType: string; project?: string }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.itemId || !req.body?.itemType) { - return { - status_code: 400, - body: { error: "itemId and itemType are required" }, - }; - } - try { - const result = await sdk.trigger({ function_id: "mem::team-share", payload: req.body }); - return { status_code: 201, body: result }; - } catch { - return { status_code: 404, body: { error: "Team memory not enabled" } }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::team-share", - config: { api_path: "/agentmemory/team/share", http_method: "POST" }, - }); - - sdk.registerFunction("api::team-feed", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - try { - const parsedLimit = parseOptionalInt(req.query_params?.["limit"]); - const limit = parsedLimit ?? 20; - const result = await sdk.trigger({ function_id: "mem::team-feed", payload: { limit } }); - return { status_code: 200, body: result }; - } catch { - return { status_code: 404, body: { error: "Team memory not enabled" } }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::team-feed", - config: { api_path: "/agentmemory/team/feed", http_method: "GET" }, - }); - - sdk.registerFunction("api::team-profile", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - try { - const result = await sdk.trigger({ function_id: "mem::team-profile", payload: {} }); - return { status_code: 200, body: result }; - } catch { - return { status_code: 404, body: { error: "Team memory not enabled" } }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::team-profile", - config: { api_path: "/agentmemory/team/profile", http_method: "GET" }, - }); - - sdk.registerFunction("api::audit", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const parsedLimit = parseOptionalInt(req.query_params?.["limit"]); - const entries = await sdk.trigger({ function_id: "mem::audit-query", payload: { - operation: req.query_params?.["operation"], - limit: parsedLimit ?? 50, - } }); - return { status_code: 200, body: { entries, success: true } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::audit", - config: { api_path: "/agentmemory/audit", http_method: "GET" }, - }); - - sdk.registerFunction("api::governance-delete", - async ( - req: ApiRequest<{ memoryIds: string[]; reason?: string }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.memoryIds || !Array.isArray(req.body.memoryIds)) { - return { - status_code: 400, - body: { error: "memoryIds array is required" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::governance-delete", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::governance-delete", - config: { - api_path: "/agentmemory/governance/memories", - http_method: "DELETE", - }, - }); - - sdk.registerFunction("api::governance-bulk", - async ( - req: ApiRequest<{ - type?: string[]; - dateFrom?: string; - dateTo?: string; - qualityBelow?: number; - dryRun?: boolean; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::governance-bulk", payload: req.body || {} }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::governance-bulk", - config: { - api_path: "/agentmemory/governance/bulk-delete", - http_method: "POST", - }, - }); - - sdk.registerFunction("api::snapshots", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - try { - const result = await sdk.trigger({ function_id: "mem::snapshot-list", payload: {} }); - return { status_code: 200, body: result }; - } catch { - return { status_code: 404, body: { error: "Snapshots not enabled" } }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::snapshots", - config: { api_path: "/agentmemory/snapshots", http_method: "GET" }, - }); - - sdk.registerFunction("api::snapshot-create", - async (req: ApiRequest<{ message?: string }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - try { - const result = await sdk.trigger({ function_id: "mem::snapshot-create", payload: req.body || {}, - }); - return { status_code: 201, body: result }; - } catch { - return { status_code: 404, body: { error: "Snapshots not enabled" } }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::snapshot-create", - config: { api_path: "/agentmemory/snapshot/create", http_method: "POST" }, - }); - - sdk.registerFunction("api::snapshot-restore", - async (req: ApiRequest<{ commitHash: string }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.commitHash) { - return { status_code: 400, body: { error: "commitHash is required" } }; - } - try { - const result = await sdk.trigger({ function_id: "mem::snapshot-restore", payload: req.body }); - return { status_code: 200, body: result }; - } catch { - return { status_code: 404, body: { error: "Snapshots not enabled" } }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::snapshot-restore", - config: { api_path: "/agentmemory/snapshot/restore", http_method: "POST" }, - }); - - sdk.registerFunction("api::memories", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const memories = await kv.list(KV.memories); - const latest = req.query_params?.["latest"] === "true"; - // agentId filter. Request param wins, env AGENT_ID (when - // scope=isolated) is the fallback. Shared mode keeps the tag but - // does not restrict the list endpoint. Pass agentId=* to opt out - // of the env scope entirely. includeOrphans=true surfaces - // pre-AGENT_ID memories whose agentId is undefined. - const normalizedAgentId = - typeof req.query_params?.["agentId"] === "string" - ? req.query_params["agentId"].trim() - : undefined; - const wildcardAgent = normalizedAgentId === "*"; - const explicitAgentId = - normalizedAgentId && !wildcardAgent ? normalizedAgentId : undefined; - const includeOrphans = - req.query_params?.["includeOrphans"] === "true"; - const filterAgentId = wildcardAgent - ? undefined - : explicitAgentId ?? (isAgentScopeIsolated() ? getAgentId() : undefined); - let filtered = latest ? memories.filter((m) => m.isLatest) : memories; - if (filterAgentId) { - filtered = filtered.filter( - (m) => - m.agentId === filterAgentId || - (includeOrphans && m.agentId === undefined), - ); - } - - // viewer + `agentmemory-lab status` were hitting this endpoint to - // count memories. On a real corpus (8K+ memories) the unbounded - // response either timed out at the iii engine boundary ("Invocation - // stopped") or arrived too large for the viewer to render — so the - // UI showed 0 memories despite a healthy store. Two opt-in modes: - // ?count=true — totals only, no payload - // ?limit=N&offset=M — page slice (default unlimited for back-compat) - if (req.query_params?.["count"] === "true") { - // Match the SAME scope that the list path applies — returning - // unfiltered totals here would leak cross-agent counts to a - // caller that's blocked from the underlying rows. - return { - status_code: 200, - body: { - total: filtered.length, - latestCount: filtered.filter((m) => m.isLatest).length, - }, - }; - } - - const rawLimit = req.query_params?.["limit"]; - const rawOffset = req.query_params?.["offset"]; - const parsedLimit = - typeof rawLimit === "string" ? Number(rawLimit) : Number.NaN; - const parsedOffset = - typeof rawOffset === "string" ? Number(rawOffset) : Number.NaN; - const limit = - Number.isInteger(parsedLimit) && parsedLimit > 0 - ? Math.min(parsedLimit, 5000) - : undefined; - const offset = - Number.isInteger(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0; - const sliced = - limit !== undefined ? filtered.slice(offset, offset + limit) : filtered; - - return { - status_code: 200, - body: { - memories: sliced, - total: filtered.length, - offset, - limit: limit ?? null, - }, - }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::memories", - config: { api_path: "/agentmemory/memories", http_method: "GET" }, - }); - - sdk.registerFunction("api::memory-by-id", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const id = req.path_params?.["id"]; - if (!id || typeof id !== "string") { - return { status_code: 400, body: { error: "id path parameter is required" } }; - } - const memory = await kv.get(KV.memories, id); - if (!memory) { - return { status_code: 404, body: { error: `memory not found: ${id}` } }; - } - return { status_code: 200, body: { memory } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::memory-by-id", - config: { api_path: "/agentmemory/memories/:id", http_method: "GET" }, - }); - - sdk.registerFunction("api::semantic-list", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const semantic = await kv.list(KV.semantic); - return { status_code: 200, body: { semantic } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::semantic-list", - config: { api_path: "/agentmemory/semantic", http_method: "GET" }, - }); - - sdk.registerFunction("api::procedural-list", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const procedural = await kv.list(KV.procedural); - return { status_code: 200, body: { procedural } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::procedural-list", - config: { api_path: "/agentmemory/procedural", http_method: "GET" }, - }); - - sdk.registerFunction("api::relations-list", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const relations = await kv.list(KV.relations); - return { status_code: 200, body: { relations } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::relations-list", - config: { api_path: "/agentmemory/relations", http_method: "GET" }, - }); - - sdk.registerFunction("api::vision-search", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const body = (req.body ?? {}) as Record; - const queryText = asNonEmptyString(body["queryText"]); - const queryImageRef = asNonEmptyString(body["queryImageRef"]); - const queryImageBase64 = asNonEmptyString(body["queryImageBase64"]); - const sessionId = asNonEmptyString(body["sessionId"]); - if (!queryText && !queryImageRef && !queryImageBase64) { - return { - status_code: 400, - body: { error: "queryText, queryImageRef, or queryImageBase64 required" }, - }; - } - const topKParsed = parseOptionalPositiveInt(body["topK"]); - if (topKParsed === null) { - return { status_code: 400, body: { error: "topK must be a positive integer" } }; - } - const payload: Record = {}; - if (queryText) payload["queryText"] = queryText; - if (queryImageRef) payload["queryImageRef"] = queryImageRef; - if (queryImageBase64) payload["queryImageBase64"] = queryImageBase64; - if (sessionId) payload["sessionId"] = sessionId; - if (topKParsed !== undefined) payload["topK"] = Math.min(50, topKParsed); - const result = await sdk.trigger({ function_id: "mem::vision-search", payload }); - const resp = result as { success?: boolean; error?: string }; - if (resp?.success === false) { - return { status_code: resp.error?.includes("disabled") ? 503 : 400, body: resp }; - } - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::vision-search", - config: { api_path: "/agentmemory/vision-search", http_method: "POST" }, - }); - - sdk.registerFunction("api::vision-embed", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const body = (req.body ?? {}) as Record; - const imageRef = asNonEmptyString(body["imageRef"]); - const sessionId = asNonEmptyString(body["sessionId"]); - const observationId = asNonEmptyString(body["observationId"]); - if (!imageRef) { - return { status_code: 400, body: { error: "imageRef is required" } }; - } - const payload: Record = { imageRef }; - if (sessionId) payload["sessionId"] = sessionId; - if (observationId) payload["observationId"] = observationId; - const result = await sdk.trigger({ function_id: "mem::vision-embed", payload }); - const resp = result as { success?: boolean; error?: string }; - if (resp?.success === false) { - return { status_code: resp.error?.includes("disabled") ? 503 : 400, body: resp }; - } - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::vision-embed", - config: { api_path: "/agentmemory/vision-embed", http_method: "POST" }, - }); - - sdk.registerFunction("api::slot-list", async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!isSlotsEnabled()) return slotsDisabledResponse(); - const result = await sdk.trigger({ function_id: "mem::slot-list", payload: {} }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ - type: "http", - function_id: "api::slot-list", - config: { api_path: "/agentmemory/slots", http_method: "GET" }, - }); - - sdk.registerFunction("api::slot-get", async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!isSlotsEnabled()) return slotsDisabledResponse(); - const label = asNonEmptyString(req.query_params?.["label"]); - if (!label) return { status_code: 400, body: { error: "label query param required" } }; - const result = await sdk.trigger({ function_id: "mem::slot-get", payload: { label } }); - const resp = result as { success?: boolean; error?: string }; - if (resp?.success === false) { - return { status_code: resp.error?.includes("not found") ? 404 : 400, body: resp }; - } - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ - type: "http", - function_id: "api::slot-get", - config: { api_path: "/agentmemory/slot", http_method: "GET" }, - }); - - sdk.registerFunction("api::slot-create", async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!isSlotsEnabled()) return slotsDisabledResponse(); - const body = (req.body ?? {}) as Record; - const label = asNonEmptyString(body["label"]); - if (!label) return { status_code: 400, body: { error: "label required" } }; - // Reject malformed inputs instead of silently dropping them. - if (body["content"] !== undefined && typeof body["content"] !== "string") { - return { status_code: 400, body: { error: "content must be a string" } }; - } - if (body["description"] !== undefined && typeof body["description"] !== "string") { - return { status_code: 400, body: { error: "description must be a string" } }; - } - if (body["pinned"] !== undefined && typeof body["pinned"] !== "boolean") { - return { status_code: 400, body: { error: "pinned must be a boolean" } }; - } - if ( - body["scope"] !== undefined && - body["scope"] !== "project" && - body["scope"] !== "global" - ) { - return { status_code: 400, body: { error: "scope must be 'project' or 'global'" } }; - } - const sizeLimit = parseOptionalPositiveInt(body["sizeLimit"]); - if (sizeLimit === null) { - return { status_code: 400, body: { error: "sizeLimit must be a positive integer" } }; - } - if (sizeLimit !== undefined && sizeLimit > 20000) { - return { status_code: 400, body: { error: "sizeLimit must be <= 20000" } }; - } - const payload: Record = { label }; - if (typeof body["content"] === "string") payload["content"] = body["content"]; - if (typeof body["description"] === "string") payload["description"] = body["description"]; - if (sizeLimit !== undefined) payload["sizeLimit"] = sizeLimit; - if (typeof body["pinned"] === "boolean") payload["pinned"] = body["pinned"]; - if (body["scope"] === "project" || body["scope"] === "global") payload["scope"] = body["scope"]; - const result = await sdk.trigger({ function_id: "mem::slot-create", payload }); - const resp = result as { success?: boolean; error?: string }; - if (resp?.success === false) { - return { status_code: resp.error?.includes("exists") ? 409 : 400, body: resp }; - } - return { status_code: 201, body: result }; - }); - sdk.registerTrigger({ - type: "http", - function_id: "api::slot-create", - config: { api_path: "/agentmemory/slot", http_method: "POST" }, - }); - - sdk.registerFunction("api::slot-append", async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!isSlotsEnabled()) return slotsDisabledResponse(); - const body = (req.body ?? {}) as Record; - const label = asNonEmptyString(body["label"]); - const text = typeof body["text"] === "string" ? body["text"] : null; - if (!label || !text) return { status_code: 400, body: { error: "label and text required" } }; - const result = await sdk.trigger({ function_id: "mem::slot-append", payload: { label, text } }); - const resp = result as { success?: boolean; error?: string }; - if (resp?.success === false) { - const notFound = resp.error?.includes("not found"); - const overLimit = resp.error?.includes("exceed"); - return { status_code: notFound ? 404 : overLimit ? 413 : 400, body: resp }; - } - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ - type: "http", - function_id: "api::slot-append", - config: { api_path: "/agentmemory/slot/append", http_method: "POST" }, - }); - - sdk.registerFunction("api::slot-replace", async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!isSlotsEnabled()) return slotsDisabledResponse(); - const body = (req.body ?? {}) as Record; - const label = asNonEmptyString(body["label"]); - const content = body["content"]; - if (!label || typeof content !== "string") { - return { status_code: 400, body: { error: "label and content (string) required" } }; - } - const result = await sdk.trigger({ function_id: "mem::slot-replace", payload: { label, content } }); - const resp = result as { success?: boolean; error?: string }; - if (resp?.success === false) { - const notFound = resp.error?.includes("not found"); - const overLimit = resp.error?.includes("exceed"); - return { status_code: notFound ? 404 : overLimit ? 413 : 400, body: resp }; - } - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ - type: "http", - function_id: "api::slot-replace", - config: { api_path: "/agentmemory/slot/replace", http_method: "POST" }, - }); - - sdk.registerFunction("api::slot-delete", async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!isSlotsEnabled()) return slotsDisabledResponse(); - const label = asNonEmptyString(req.query_params?.["label"]); - if (!label) return { status_code: 400, body: { error: "label query param required" } }; - const result = await sdk.trigger({ function_id: "mem::slot-delete", payload: { label } }); - const resp = result as { success?: boolean; error?: string }; - if (resp?.success === false) { - return { status_code: resp.error?.includes("not found") ? 404 : 400, body: resp }; - } - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ - type: "http", - function_id: "api::slot-delete", - config: { api_path: "/agentmemory/slot", http_method: "DELETE" }, - }); - - sdk.registerFunction("api::slot-reflect", async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!isSlotsEnabled()) return slotsDisabledResponse(); - if (!isReflectEnabled()) return reflectDisabledResponse(); - const body = (req.body ?? {}) as Record; - const sessionId = asNonEmptyString(body["sessionId"]); - if (!sessionId) return { status_code: 400, body: { error: "sessionId required" } }; - const maxObservations = parseOptionalPositiveInt(body["maxObservations"]); - if (maxObservations === null) return { status_code: 400, body: { error: "maxObservations must be a positive integer" } }; - const payload: Record = { sessionId }; - if (maxObservations !== undefined) payload["maxObservations"] = maxObservations; - const result = await sdk.trigger({ function_id: "mem::slot-reflect", payload }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ - type: "http", - function_id: "api::slot-reflect", - config: { api_path: "/agentmemory/slot/reflect", http_method: "POST" }, - }); - - sdk.registerFunction("api::action-create", - async ( - req: ApiRequest<{ - title: string; - description?: string; - priority?: number; - createdBy?: string; - project?: string; - tags?: string[]; - parentId?: string; - edges?: Array<{ type: string; targetActionId: string }>; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.title) { - return { status_code: 400, body: { error: "title is required" } }; - } - const result = await sdk.trigger({ function_id: "mem::action-create", payload: req.body }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::action-create", - config: { api_path: "/agentmemory/actions", http_method: "POST" }, - }); - - sdk.registerFunction("api::action-update", - async ( - req: ApiRequest<{ - actionId: string; - status?: string; - title?: string; - description?: string; - priority?: number; - result?: string; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.actionId) { - return { status_code: 400, body: { error: "actionId is required" } }; - } - const result = await sdk.trigger({ function_id: "mem::action-update", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::action-update", - config: { api_path: "/agentmemory/actions/update", http_method: "POST" }, - }); - - sdk.registerFunction("api::action-list", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::action-list", payload: { - status: req.query_params?.["status"], - project: req.query_params?.["project"], - parentId: req.query_params?.["parentId"], - } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::action-list", - config: { api_path: "/agentmemory/actions", http_method: "GET" }, - }); - - sdk.registerFunction("api::action-get", - async (req: ApiRequest<{ actionId: string }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const actionId = req.query_params?.["actionId"] as string; - if (!actionId) { - return { status_code: 400, body: { error: "actionId required" } }; - } - const result = await sdk.trigger({ function_id: "mem::action-get", payload: { actionId } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::action-get", - config: { api_path: "/agentmemory/actions/get", http_method: "GET" }, - }); - - sdk.registerFunction("api::action-edge", - async ( - req: ApiRequest<{ - sourceActionId: string; - targetActionId: string; - type: string; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.sourceActionId || !req.body?.targetActionId || !req.body?.type) { - return { status_code: 400, body: { error: "sourceActionId, targetActionId, and type are required" } }; - } - const result = await sdk.trigger({ function_id: "mem::action-edge-create", payload: req.body }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::action-edge", - config: { api_path: "/agentmemory/actions/edges", http_method: "POST" }, - }); - - sdk.registerFunction("api::frontier", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const parsedLimit = parseOptionalInt(req.query_params?.["limit"]); - const result = await sdk.trigger({ function_id: "mem::frontier", payload: { - project: req.query_params?.["project"], - agentId: req.query_params?.["agentId"], - limit: parsedLimit, - } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::frontier", - config: { api_path: "/agentmemory/frontier", http_method: "GET" }, - }); - - sdk.registerFunction("api::next", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::next", payload: { - project: req.query_params?.["project"], - agentId: req.query_params?.["agentId"], - } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::next", - config: { api_path: "/agentmemory/next", http_method: "GET" }, - }); - - sdk.registerFunction("api::lease-acquire", - async ( - req: ApiRequest<{ actionId: string; agentId: string; ttlMs?: number }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.actionId || !req.body?.agentId) { - return { status_code: 400, body: { error: "actionId and agentId are required" } }; - } - const result = await sdk.trigger({ function_id: "mem::lease-acquire", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::lease-acquire", - config: { api_path: "/agentmemory/leases/acquire", http_method: "POST" }, - }); - - sdk.registerFunction("api::lease-release", - async ( - req: ApiRequest<{ actionId: string; agentId: string; result?: string }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.actionId || !req.body?.agentId) { - return { status_code: 400, body: { error: "actionId and agentId are required" } }; - } - const result = await sdk.trigger({ function_id: "mem::lease-release", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::lease-release", - config: { api_path: "/agentmemory/leases/release", http_method: "POST" }, - }); - - sdk.registerFunction("api::lease-renew", - async ( - req: ApiRequest<{ actionId: string; agentId: string; ttlMs?: number }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.actionId || !req.body?.agentId) { - return { status_code: 400, body: { error: "actionId and agentId are required" } }; - } - const result = await sdk.trigger({ function_id: "mem::lease-renew", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::lease-renew", - config: { api_path: "/agentmemory/leases/renew", http_method: "POST" }, - }); - - sdk.registerFunction("api::routine-create", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.name || !req.body?.steps) { - return { - status_code: 400, - body: { error: "name and steps are required" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::routine-create", payload: req.body }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::routine-create", - config: { api_path: "/agentmemory/routines", http_method: "POST" }, - }); - - sdk.registerFunction("api::routine-list", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::routine-list", payload: { - frozen: req.query_params?.["frozen"] === "true" ? true : undefined, - } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::routine-list", - config: { api_path: "/agentmemory/routines", http_method: "GET" }, - }); - - sdk.registerFunction("api::routine-run", - async ( - req: ApiRequest<{ routineId: string; project?: string; initiatedBy?: string }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.routineId) { - return { status_code: 400, body: { error: "routineId is required" } }; - } - const result = await sdk.trigger({ function_id: "mem::routine-run", payload: req.body }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::routine-run", - config: { api_path: "/agentmemory/routines/run", http_method: "POST" }, - }); - - sdk.registerFunction("api::routine-status", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const runId = req.query_params?.["runId"] as string; - if (!runId) { - return { status_code: 400, body: { error: "runId query param required" } }; - } - const result = await sdk.trigger({ function_id: "mem::routine-status", payload: { runId } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::routine-status", - config: { api_path: "/agentmemory/routines/status", http_method: "GET" }, - }); - - sdk.registerFunction("api::signal-send", - async ( - req: ApiRequest<{ - from: string; - to?: string; - content: string; - type?: string; - replyTo?: string; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.from || !req.body?.content) { - return { status_code: 400, body: { error: "from and content are required" } }; - } - const result = await sdk.trigger({ function_id: "mem::signal-send", payload: req.body }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::signal-send", - config: { api_path: "/agentmemory/signals/send", http_method: "POST" }, - }); - - sdk.registerFunction("api::signal-read", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const agentId = req.query_params?.["agentId"] as string; - if (!agentId) { - return { status_code: 400, body: { error: "agentId query param required" } }; - } - const parsedLimit = parseOptionalInt(req.query_params?.["limit"]); - const result = await sdk.trigger({ function_id: "mem::signal-read", payload: { - agentId, - unreadOnly: req.query_params?.["unreadOnly"] === "true", - threadId: req.query_params?.["threadId"], - limit: parsedLimit, - } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::signal-read", - config: { api_path: "/agentmemory/signals", http_method: "GET" }, - }); - - // Line C: Agent→user async inbox (no agentId; single-user). - // Whitelist InboxItem-shaped fields; never forward the raw request body. - function inboxWriteFields(body: Record | undefined) { - const b = body || {}; - return { - body: b["body"], - fromAgent: b["fromAgent"], - project: b["project"], - priority: b["priority"], - sourceObservationIds: b["sourceObservationIds"], - sourceSessionId: b["sourceSessionId"], - expiresInMs: b["expiresInMs"], - }; - } - sdk.registerFunction("api::inbox-ask", - async (req: ApiRequest<{ body: string }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const fields = inboxWriteFields(req.body as Record); - if (typeof fields.body !== "string" || !fields.body.trim()) { - return { status_code: 400, body: { error: "body is required" } }; - } - const result = await sdk.trigger({ function_id: "mem::inbox-ask", payload: fields }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::inbox-ask", - config: { api_path: "/agentmemory/inbox/ask", http_method: "POST" }, - }); - - sdk.registerFunction("api::inbox-notify", - async (req: ApiRequest<{ body: string }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const fields = inboxWriteFields(req.body as Record); - if (typeof fields.body !== "string" || !fields.body.trim()) { - return { status_code: 400, body: { error: "body is required" } }; - } - const result = await sdk.trigger({ function_id: "mem::inbox-notify", payload: fields }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::inbox-notify", - config: { api_path: "/agentmemory/inbox/notify", http_method: "POST" }, - }); - - sdk.registerFunction("api::inbox-list", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::inbox-list", payload: { - status: req.query_params?.["status"], - kind: req.query_params?.["kind"], - limit: parseOptionalInt(req.query_params?.["limit"]), - } }); - // Join the delivery ledger (mem:delivery) onto each item as a read-only - // `delivery` field so the viewer can show push status. The inbox item's - // persisted shape is untouched — this lives only in the API response. - const items = result.items ?? []; - const joined = await Promise.all(items.map(async (item) => { - const rec = await kv.get(KV.delivery, item.id).catch(() => null); - if (!rec) return item; - return { - ...item, - delivery: { - channel: rec.channel, - status: rec.status, - messageId: rec.messageId, - urgent: rec.urgent, - error: rec.error, - attempts: rec.attempts, - deliveredAt: rec.deliveredAt, - }, - }; - })); - return { status_code: 200, body: { ...result, items: joined } }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::inbox-list", - config: { api_path: "/agentmemory/inbox", http_method: "GET" }, - }); - - sdk.registerFunction("api::inbox-answer", - async (req: ApiRequest<{ id: string; answer?: string }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const b = (req.body || {}) as Record; - if (typeof b["id"] !== "string" || !b["id"]) { - return { status_code: 400, body: { error: "id is required" } }; - } - const result = await sdk.trigger({ function_id: "mem::inbox-answer", payload: { id: b["id"], answer: b["answer"] } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::inbox-answer", - config: { api_path: "/agentmemory/inbox/answer", http_method: "POST" }, - }); - - sdk.registerFunction("api::inbox-dismiss", - async (req: ApiRequest<{ id: string }>): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const b = (req.body || {}) as Record; - if (typeof b["id"] !== "string" || !b["id"]) { - return { status_code: 400, body: { error: "id is required" } }; - } - const result = await sdk.trigger({ function_id: "mem::inbox-dismiss", payload: { id: b["id"] } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::inbox-dismiss", - config: { api_path: "/agentmemory/inbox/dismiss", http_method: "POST" }, - }); - - sdk.registerFunction("api::checkpoint-create", - async ( - req: ApiRequest<{ - name: string; - description?: string; - type?: string; - linkedActionIds?: string[]; - expiresInMs?: number; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.name) { - return { status_code: 400, body: { error: "name is required" } }; - } - const result = await sdk.trigger({ function_id: "mem::checkpoint-create", payload: req.body }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::checkpoint-create", - config: { api_path: "/agentmemory/checkpoints", http_method: "POST" }, - }); - - sdk.registerFunction("api::checkpoint-resolve", - async ( - req: ApiRequest<{ - checkpointId: string; - status: string; - resolvedBy?: string; - result?: unknown; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.checkpointId || !req.body?.status) { - return { status_code: 400, body: { error: "checkpointId and status are required" } }; - } - const result = await sdk.trigger({ function_id: "mem::checkpoint-resolve", payload: req.body }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::checkpoint-resolve", - config: { api_path: "/agentmemory/checkpoints/resolve", http_method: "POST" }, - }); - - sdk.registerFunction("api::checkpoint-list", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::checkpoint-list", payload: { - status: req.query_params?.["status"], - type: req.query_params?.["type"], - } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::checkpoint-list", - config: { api_path: "/agentmemory/checkpoints", http_method: "GET" }, - }); - - sdk.registerFunction("api::mesh-register", - async ( - req: ApiRequest<{ url: string; name: string; sharedScopes?: string[] }>, - ): Promise => { - const secretErr = requireConfiguredSecret(secret, "mesh"); - if (secretErr) return secretErr; - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - if (!req.body?.url || !req.body?.name) { - return { status_code: 400, body: { error: "url and name are required" } }; - } - const result = await sdk.trigger({ function_id: "mem::mesh-register", payload: req.body }); - return { status_code: 201, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::mesh-register", - config: { api_path: "/agentmemory/mesh/peers", http_method: "POST" }, - }); - - sdk.registerFunction("api::mesh-list", - async (req: ApiRequest): Promise => { - const secretErr = requireConfiguredSecret(secret, "mesh"); - if (secretErr) return secretErr; - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::mesh-list", payload: {} }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::mesh-list", - config: { api_path: "/agentmemory/mesh/peers", http_method: "GET" }, - }); - - sdk.registerFunction("api::mesh-sync", - async ( - req: ApiRequest<{ peerId?: string; direction?: string }>, - ): Promise => { - const secretErr = requireConfiguredSecret(secret, "mesh"); - if (secretErr) return secretErr; - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::mesh-sync", payload: req.body || {} }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::mesh-sync", - config: { api_path: "/agentmemory/mesh/sync", http_method: "POST" }, - }); - - sdk.registerFunction("api::mesh-receive", - async (req: ApiRequest): Promise => { - const secretErr = requireConfiguredSecret(secret, "mesh"); - if (secretErr) return secretErr; - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const result = await sdk.trigger({ function_id: "mem::mesh-receive", payload: req.body || {} }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::mesh-receive", - config: { api_path: "/agentmemory/mesh/receive", http_method: "POST" }, - }); - - sdk.registerFunction("api::mesh-export", - async (req: ApiRequest): Promise => { - const secretErr = requireConfiguredSecret(secret, "mesh"); - if (secretErr) return secretErr; - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const since = req.query_params?.["since"] as string; - if (since) { - const parsed = new Date(since).getTime(); - if (Number.isNaN(parsed)) { - return { status_code: 400, body: { error: "Invalid 'since' date format" } }; - } - } - const project = req.query_params?.["project"] as string | undefined; - const sinceTime = since ? new Date(since).getTime() : 0; - const df = (items: T[], field: "updatedAt" | "createdAt") => - items.filter((i) => new Date((i as Record)[field] as string).getTime() > sinceTime); - const memories = await kv.list(KV.memories); - let actions = await kv.list(KV.actions); - if (project) { - actions = actions.filter((a) => a.project === project); - } - const body: Record = { - memories: df(memories, "updatedAt"), - actions: df(actions, "updatedAt"), - }; - if (!project) { - const semantic = await kv.list(KV.semantic); - const procedural = await kv.list(KV.procedural); - const relations = await kv.list(KV.relations); - const graphNodes = await kv.list(KV.graphNodes); - const graphEdges = await kv.list(KV.graphEdges); - body.semantic = df(semantic, "updatedAt"); - body.procedural = df(procedural, "updatedAt"); - body.relations = df(relations, "createdAt"); - body.graphNodes = graphNodes.filter( - (n) => new Date(n.updatedAt || n.createdAt).getTime() > sinceTime, - ); - body.graphEdges = df(graphEdges, "createdAt"); - } - return { status_code: 200, body }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::mesh-export", - config: { api_path: "/agentmemory/mesh/export", http_method: "GET" }, - }); - - sdk.registerFunction("api::flow-compress", - async ( - req: ApiRequest<{ - runId?: string; - actionIds?: string[]; - project?: string; - }>, - ): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - try { - const result = await sdk.trigger({ function_id: "mem::flow-compress", payload: req.body || {} }); - return { status_code: 200, body: result }; - } catch { - return { - status_code: 404, - body: { error: "Flow compression requires a provider" }, - }; - } - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::flow-compress", - config: { api_path: "/agentmemory/flow/compress", http_method: "POST" }, - }); - - sdk.registerFunction("api::branch-detect", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const cwd = (req.query_params?.["cwd"] as string) || process.cwd(); - const result = await sdk.trigger({ function_id: "mem::detect-worktree", payload: { cwd } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::branch-detect", - config: { api_path: "/agentmemory/branch/detect", http_method: "GET" }, - }); - - sdk.registerFunction("api::branch-worktrees", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const cwd = (req.query_params?.["cwd"] as string) || process.cwd(); - const result = await sdk.trigger({ function_id: "mem::list-worktrees", payload: { cwd } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::branch-worktrees", - config: { api_path: "/agentmemory/branch/worktrees", http_method: "GET" }, - }); - - sdk.registerFunction("api::branch-sessions", - async (req: ApiRequest): Promise => { - const authErr = checkAuth(req, secret); - if (authErr) return authErr; - const cwd = (req.query_params?.["cwd"] as string) || process.cwd(); - const result = await sdk.trigger({ function_id: "mem::branch-sessions", payload: { cwd } }); - return { status_code: 200, body: result }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::branch-sessions", - config: { api_path: "/agentmemory/branch/sessions", http_method: "GET" }, - }); - - sdk.registerFunction("api::viewer", - async (req: ApiRequest): Promise => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const rendered = renderViewerDocument(); - if (rendered.found) { - return { - status_code: 200, - headers: { - "Content-Type": "text/html", - "Content-Security-Policy": rendered.csp, - }, - body: rendered.html, - }; - } - return { - status_code: 404, - headers: { - "Content-Type": "text/html", - }, - body: "

agentmemory

viewer not found

", - }; - }, - ); - sdk.registerTrigger({ - type: "http", - function_id: "api::viewer", - config: { api_path: "/agentmemory/viewer", http_method: "GET" }, - }); - - sdk.registerFunction("api::sentinel-create", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.name) return { status_code: 400, body: { error: "name is required" } }; - const result = await sdk.trigger({ function_id: "mem::sentinel-create", payload: body }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::sentinel-create", config: { api_path: "/agentmemory/sentinels", http_method: "POST" } }); - - sdk.registerFunction("api::sentinel-trigger", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.sentinelId) return { status_code: 400, body: { error: "sentinelId is required" } }; - const result = await sdk.trigger({ function_id: "mem::sentinel-trigger", payload: body }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::sentinel-trigger", config: { api_path: "/agentmemory/sentinels/trigger", http_method: "POST" } }); - - sdk.registerFunction("api::sentinel-check", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const result = await sdk.trigger({ function_id: "mem::sentinel-check", payload: {} }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::sentinel-check", config: { api_path: "/agentmemory/sentinels/check", http_method: "POST" } }); - - sdk.registerFunction("api::sentinel-cancel", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.sentinelId) return { status_code: 400, body: { error: "sentinelId is required" } }; - const result = await sdk.trigger({ function_id: "mem::sentinel-cancel", payload: body }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::sentinel-cancel", config: { api_path: "/agentmemory/sentinels/cancel", http_method: "POST" } }); - - sdk.registerFunction("api::sentinel-list", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const params = req.query_params || {}; - const result = await sdk.trigger({ function_id: "mem::sentinel-list", payload: { status: params.status, type: params.type } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::sentinel-list", config: { api_path: "/agentmemory/sentinels", http_method: "GET" } }); - - sdk.registerFunction("api::sketch-create", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.title) return { status_code: 400, body: { error: "title is required" } }; - const result = await sdk.trigger({ function_id: "mem::sketch-create", payload: body }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::sketch-create", config: { api_path: "/agentmemory/sketches", http_method: "POST" } }); - - sdk.registerFunction("api::sketch-add", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.sketchId || !body?.title) return { status_code: 400, body: { error: "sketchId and title are required" } }; - const result = await sdk.trigger({ function_id: "mem::sketch-add", payload: body }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::sketch-add", config: { api_path: "/agentmemory/sketches/add", http_method: "POST" } }); - - sdk.registerFunction("api::sketch-promote", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.sketchId) return { status_code: 400, body: { error: "sketchId is required" } }; - const result = await sdk.trigger({ function_id: "mem::sketch-promote", payload: body }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::sketch-promote", config: { api_path: "/agentmemory/sketches/promote", http_method: "POST" } }); - - sdk.registerFunction("api::sketch-discard", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.sketchId) return { status_code: 400, body: { error: "sketchId is required" } }; - const result = await sdk.trigger({ function_id: "mem::sketch-discard", payload: body }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::sketch-discard", config: { api_path: "/agentmemory/sketches/discard", http_method: "POST" } }); - - sdk.registerFunction("api::sketch-list", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const params = req.query_params || {}; - const result = await sdk.trigger({ function_id: "mem::sketch-list", payload: { status: params.status, project: params.project } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::sketch-list", config: { api_path: "/agentmemory/sketches", http_method: "GET" } }); - - sdk.registerFunction("api::sketch-gc", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const result = await sdk.trigger({ function_id: "mem::sketch-gc", payload: {} }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::sketch-gc", config: { api_path: "/agentmemory/sketches/gc", http_method: "POST" } }); - - sdk.registerFunction("api::crystallize", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.actionIds) return { status_code: 400, body: { error: "actionIds is required" } }; - const result = await sdk.trigger({ function_id: "mem::crystallize", payload: body }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::crystallize", config: { api_path: "/agentmemory/crystals/create", http_method: "POST" } }); - - sdk.registerFunction("api::crystal-list", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const params = req.query_params || {}; - const limit = parseOptionalPositiveInt(params.limit); - if (limit === null) { - return { - status_code: 400, - body: { error: "invalid numeric parameter: limit" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::crystal-list", payload: { project: params.project, sessionId: params.sessionId, limit } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::crystal-list", config: { api_path: "/agentmemory/crystals", http_method: "GET" } }); - - sdk.registerFunction("api::auto-crystallize", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - const result = await sdk.trigger({ function_id: "mem::auto-crystallize", payload: body || {} }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::auto-crystallize", config: { api_path: "/agentmemory/crystals/auto", http_method: "POST" } }); - - sdk.registerFunction("api::diagnose", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - const result = await sdk.trigger({ function_id: "mem::diagnose", payload: body || {} }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::diagnose", config: { api_path: "/agentmemory/diagnostics", http_method: "POST" } }); - - sdk.registerFunction("api::heal", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - const result = await sdk.trigger({ function_id: "mem::heal", payload: body || {} }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::heal", config: { api_path: "/agentmemory/diagnostics/heal", http_method: "POST" } }); - - sdk.registerFunction("api::facet-tag", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.targetId || !body?.dimension || !body?.value) return { status_code: 400, body: { error: "targetId, dimension, and value are required" } }; - const result = await sdk.trigger({ function_id: "mem::facet-tag", payload: body }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::facet-tag", config: { api_path: "/agentmemory/facets", http_method: "POST" } }); - - sdk.registerFunction("api::facet-untag", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.targetId || !body?.dimension) return { status_code: 400, body: { error: "targetId and dimension are required" } }; - const result = await sdk.trigger({ function_id: "mem::facet-untag", payload: body }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::facet-untag", config: { api_path: "/agentmemory/facets/remove", http_method: "POST" } }); - - sdk.registerFunction("api::facet-query", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - const result = await sdk.trigger({ function_id: "mem::facet-query", payload: body || {} }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::facet-query", config: { api_path: "/agentmemory/facets/query", http_method: "POST" } }); - - sdk.registerFunction("api::facet-get", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const params = req.query_params || {}; - if (!params.targetId) return { status_code: 400, body: { error: "targetId query param is required" } }; - const result = await sdk.trigger({ function_id: "mem::facet-get", payload: { targetId: params.targetId } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::facet-get", config: { api_path: "/agentmemory/facets", http_method: "GET" } }); - - sdk.registerFunction("api::facet-stats", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const params = req.query_params || {}; - const result = await sdk.trigger({ function_id: "mem::facet-stats", payload: { targetType: params.targetType } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::facet-stats", config: { api_path: "/agentmemory/facets/stats", http_method: "GET" } }); - - sdk.registerFunction("api::verify", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.id || typeof body.id !== "string") return { status_code: 400, body: { error: "id is required" } }; - const result = await sdk.trigger({ function_id: "mem::verify", payload: { id: body.id } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::verify", config: { api_path: "/agentmemory/verify", http_method: "POST" } }); - - sdk.registerFunction("api::cascade-update", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.supersededMemoryId || typeof body.supersededMemoryId !== "string") { - return { status_code: 400, body: { error: "supersededMemoryId is required" } }; - } - const result = await sdk.trigger({ function_id: "mem::cascade-update", payload: { supersededMemoryId: body.supersededMemoryId } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::cascade-update", config: { api_path: "/agentmemory/cascade-update", http_method: "POST" } }); - - sdk.registerFunction("api::lesson-save", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.content || typeof body.content !== "string") return { status_code: 400, body: { error: "content is required" } }; - const tags = typeof body.tags === "string" ? (body.tags as string).split(",").map((t: string) => t.trim()).filter(Boolean) : Array.isArray(body.tags) ? body.tags : []; - const result = (await sdk.trigger({ - function_id: "mem::lesson-save", - payload: { - content: body.content, - context: body.context || "", - confidence: typeof body.confidence === "number" ? body.confidence : undefined, - project: typeof body.project === "string" ? body.project : undefined, - tags, - source: "manual", - }, - })) as { action?: string }; - const statusCode = result?.action === "created" ? 201 : 200; - return { status_code: statusCode, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::lesson-save", config: { api_path: "/agentmemory/lessons", http_method: "POST" } }); - - sdk.registerFunction("api::lesson-list", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const params = req.query_params || {}; - const minConfidence = parseOptionalFiniteNumber(params.minConfidence); - if (minConfidence === null) { - return { - status_code: 400, - body: { error: "invalid numeric parameter: minConfidence" }, - }; - } - const limit = parseOptionalPositiveInt(params.limit); - if (limit === null) { - return { - status_code: 400, - body: { error: "invalid numeric parameter: limit" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::lesson-list", payload: { - project: params.project, - source: params.source, - minConfidence, - limit, - } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::lesson-list", config: { api_path: "/agentmemory/lessons", http_method: "GET" } }); - - sdk.registerFunction("api::lesson-search", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.query || typeof body.query !== "string") return { status_code: 400, body: { error: "query is required" } }; - const result = await sdk.trigger({ function_id: "mem::lesson-recall", payload: body }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::lesson-search", config: { api_path: "/agentmemory/lessons/search", http_method: "POST" } }); - - sdk.registerFunction("api::lesson-strengthen", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.lessonId || typeof body.lessonId !== "string") return { status_code: 400, body: { error: "lessonId is required" } }; - const result = await sdk.trigger({ function_id: "mem::lesson-strengthen", payload: { lessonId: body.lessonId } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::lesson-strengthen", config: { api_path: "/agentmemory/lessons/strengthen", http_method: "POST" } }); - - sdk.registerFunction("api::obsidian-export", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = (req.body as Record) || {}; - const vaultDir = asNonEmptyString(body.vaultDir); - if (!vaultDir) { - return { - status_code: 400, - body: { error: "vaultDir must be a non-empty string" }, - }; - } - const types = typeof body.types === "string" ? body.types.split(",").map((t: string) => t.trim()).filter(Boolean) : undefined; - const result = await sdk.trigger({ function_id: "mem::obsidian-export", payload: { vaultDir, types } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::obsidian-export", config: { api_path: "/agentmemory/obsidian/export", http_method: "POST" } }); - - sdk.registerFunction("api::reflect", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = (req.body as Record) || {}; - const result = await sdk.trigger({ function_id: "mem::reflect", payload: { - project: typeof body.project === "string" ? body.project : undefined, - maxClusters: typeof body.maxClusters === "number" ? body.maxClusters : undefined, - } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::reflect", config: { api_path: "/agentmemory/reflect", http_method: "POST" } }); - - sdk.registerFunction("api::insight-list", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const params = req.query_params || {}; - const minConfidence = parseOptionalFiniteNumber(params.minConfidence); - if (minConfidence === null) { - return { - status_code: 400, - body: { error: "invalid numeric parameter: minConfidence" }, - }; - } - const limit = parseOptionalPositiveInt(params.limit); - if (limit === null) { - return { - status_code: 400, - body: { error: "invalid numeric parameter: limit" }, - }; - } - const result = await sdk.trigger({ function_id: "mem::insight-list", payload: { - project: params.project, - minConfidence, - limit, - } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::insight-list", config: { api_path: "/agentmemory/insights", http_method: "GET" } }); - - sdk.registerFunction("api::insight-search", async (req: ApiRequest) => { - const denied = checkAuth(req, secret); - if (denied) return denied; - const body = req.body as Record; - if (!body?.query || typeof body.query !== "string") return { status_code: 400, body: { error: "query is required" } }; - const result = await sdk.trigger({ function_id: "mem::insight-search", payload: { - query: body.query, - project: typeof body.project === "string" ? body.project : undefined, - minConfidence: typeof body.minConfidence === "number" ? body.minConfidence : undefined, - limit: typeof body.limit === "number" ? body.limit : undefined, - } }); - return { status_code: 200, body: result }; - }); - sdk.registerTrigger({ type: "http", function_id: "api::insight-search", config: { api_path: "/agentmemory/insights/search", http_method: "POST" } }); -} diff --git a/src/triggers/events.ts b/src/triggers/events.ts deleted file mode 100644 index 8cc66f82..00000000 --- a/src/triggers/events.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { TriggerAction, type ISdk } from "iii-sdk"; -import type { CompressedObservation, HookPayload, Session } from "../types.js"; -import { KV, STREAM } from "../state/schema.js"; -import { StateKV } from "../state/kv.js"; -import { isReflectEnabled } from "../functions/slots.js"; -import { isGraphExtractionEnabled } from "../config.js"; -import { logger } from "../logger.js"; - -export function registerEventTriggers(sdk: ISdk, kv: StateKV): void { - sdk.registerFunction( - "event::session::started", - async (data: { sessionId: string; project: string; cwd: string }) => { - const session: Session = { - id: data.sessionId, - project: data.project, - cwd: data.cwd, - startedAt: new Date().toISOString(), - status: "active", - observationCount: 0, - }; - await kv.set(KV.sessions, data.sessionId, session); - const contextResult = await sdk.trigger< - { sessionId: string; project: string }, - { context: string } - >({ - function_id: "mem::context", - payload: { sessionId: data.sessionId, project: data.project }, - }); - return { session, context: contextResult.context }; - }, - ); - sdk.registerTrigger({ - type: "durable:subscriber", - function_id: "event::session::started", - config: { topic: "agentmemory.session.started" }, - }); - - sdk.registerFunction("event::observation", async (data: HookPayload) => - sdk.trigger({ function_id: "mem::observe", payload: data }), - ); - sdk.registerTrigger({ - type: "durable:subscriber", - function_id: "event::observation", - config: { topic: "agentmemory.observation" }, - }); - - sdk.registerFunction("event::session::stopped", async (data: { sessionId: string }) => { - const summary = await sdk.trigger({ function_id: "mem::summarize", payload: data }); - if (isReflectEnabled()) { - try { - void sdk.trigger({ function_id: "mem::slot-reflect", payload: { sessionId: data.sessionId }, action: TriggerAction.Void() }).catch(() => {}); - } catch (err) { - logger.warn("slot-reflect trigger failed", { - sessionId: data.sessionId, - error: err instanceof Error ? err.message : String(err), - }); - } - } - if (isGraphExtractionEnabled()) { - try { - const observations = await kv.list( - KV.observations(data.sessionId), - ); - const compressed = observations.filter((o) => o.title); - if (compressed.length > 0) { - void sdk.trigger({ function_id: "mem::graph-extract", payload: { observations: compressed }, action: TriggerAction.Void() }).catch(() => {}); - } - } catch (err) { - logger.warn("graph-extract trigger failed", { - sessionId: data.sessionId, - error: err instanceof Error ? err.message : String(err), - }); - } - } - return summary; - }); - sdk.registerTrigger({ - type: "durable:subscriber", - function_id: "event::session::stopped", - config: { topic: "agentmemory.session.stopped" }, - }); - - sdk.registerFunction( - "event::session::ended", - async (data: { sessionId: string }) => { - await kv.update(KV.sessions, data.sessionId, [ - { type: "set", path: "endedAt", value: new Date().toISOString() }, - { type: "set", path: "status", value: "completed" }, - ]); - return { success: true }; - }, - ); - sdk.registerTrigger({ - type: "durable:subscriber", - function_id: "event::session::ended", - config: { topic: "agentmemory.session.ended" }, - }); - - // React to observation count changes and emit a lightweight live event for dashboards/viewer. - sdk.registerFunction( - "event::session::observation-count-changed", - async (payload: { - key: string; - event_type: string; - old_value?: Session; - new_value?: Session; - }) => { - if (payload.event_type === "delete") return { skipped: true }; - const oldCount = payload.old_value?.observationCount ?? 0; - const newCount = payload.new_value?.observationCount ?? 0; - if (newCount <= oldCount) return { skipped: true }; - - await sdk.trigger({ - function_id: "stream::send", - payload: { - stream_name: STREAM.name, - group_id: STREAM.viewerGroup, - id: `session-activity-${payload.key}-${Date.now()}`, - type: "session.activity", - data: { - sessionId: payload.key, - observationCount: newCount, - delta: newCount - oldCount, - updatedAt: payload.new_value?.updatedAt ?? new Date().toISOString(), - }, - }, - action: TriggerAction.Void(), - }); - - return { emitted: true }; - }, - ); - sdk.registerTrigger({ - type: "state", - function_id: "event::session::observation-count-changed", - config: { scope: KV.sessions }, - }); -} diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index 2fc54373..00000000 --- a/src/types.ts +++ /dev/null @@ -1,1044 +0,0 @@ -export interface Session { - id: string; - project: string; - cwd: string; - startedAt: string; - endedAt?: string; - status: "active" | "completed" | "abandoned"; - observationCount: number; - model?: string; - tags?: string[]; - firstPrompt?: string; - summary?: string; - commitShas?: string[]; - agentId?: string; -} - -export interface CommitLink { - sha: string; - shortSha: string; - branch?: string; - repo?: string; - message?: string; - author?: string; - authoredAt?: string; - files?: string[]; - sessionIds: string[]; - linkedAt: string; -} - -export interface RawObservation { - id: string; - sessionId: string; - timestamp: string; - hookType: HookType; - toolName?: string; - toolInput?: unknown; - toolOutput?: unknown; - userPrompt?: string; - assistantResponse?: string; - raw: unknown; - modality?: "text" | "image" | "mixed"; - imageData?: string; - agentId?: string; -} - -export interface CompressedObservation { - id: string; - sessionId: string; - timestamp: string; - type: ObservationType; - title: string; - subtitle?: string; - facts: string[]; - narrative: string; - concepts: string[]; - files: string[]; - importance: number; - confidence?: number; - imageRef?: string; - imageData?: string; - imageDescription?: string; - modality?: "text" | "image" | "mixed"; - agentId?: string; -} - -export type ObservationType = - | "file_read" - | "file_write" - | "file_edit" - | "command_run" - | "search" - | "web_fetch" - | "conversation" - | "error" - | "decision" - | "discovery" - | "subagent" - | "notification" - | "task" - | "image" - | "other"; - -export interface Memory { - id: string; - createdAt: string; - updatedAt: string; - type: "pattern" | "preference" | "architecture" | "bug" | "workflow" | "fact"; - title: string; - content: string; - concepts: string[]; - files: string[]; - sessionIds: string[]; - strength: number; - version: number; - parentId?: string; - supersedes?: string[]; - relatedIds?: string[]; - sourceObservationIds?: string[]; - isLatest: boolean; - forgetAfter?: string; - imageRef?: string; - imageData?: string; - agentId?: string; - project?: string; -} - -export interface ReviewQueueItem { - id: string; - createdAt: string; - updatedAt: string; - status: "pending" | "approved" | "dismissed"; - kind: "memory" | "lesson" | "action"; - title: string; - content: string; - source: "browser-extension" | "browser-sync" | "viewer" | "api"; - decision?: "candidate" | "evidence_only"; - confidence?: number; - reason?: string; - page?: { - type?: string; - typeLabel?: string; - title?: string; - url?: string; - host?: string; - }; - conversation?: { - provider?: string; - promptDraft?: string; - turns?: Array<{ role?: string; text?: string }>; - }; - payload?: Record; - resultId?: string; - reviewedAt?: string; -} - -export interface Source { - id: string; - type: "codex" | "claude-code" | "browser"; - name: string; - pathOrUrl: string; - enabled: boolean; - lastScannedAt?: string; -} - -export interface ScanCheckpoint { - sourceId: string; - /** - * Incremental watermark: a JSON map of processed file path -> - * { mtimeMs, size }. A file is re-read only when it is absent here or its - * mtime/size changed. Recorded only on successful ingest, so a failed file - * stays absent and is retried on the next scan. - */ - cursor: string; - lastSuccessAt?: string; - lastError?: string; -} - -export interface SessionSummary { - sessionId: string; - project: string; - createdAt: string; - title: string; - narrative: string; - keyDecisions: string[]; - filesModified: string[]; - concepts: string[]; - observationCount: number; -} - -export type SessionHighlightCategory = - | "goal" - | "agent_output" - | "tool" - | "mcp" - | "command" - | "failure" - | "validation" - | "file" - | "artifact" - | "skill" - | "todo" - | "follow_up"; - -export interface SessionHighlight { - id: string; - sessionId: string; - category: SessionHighlightCategory; - title: string; - summary: string; - timestamp?: string; - sourceObservationId?: string; - evidence?: string; - files: string[]; - toolName?: string; - importance: number; - confidence: number; -} - -export interface SessionHighlightsView { - success: true; - session: Session; - coverage: { - llmUsed: false; - hasAssistantOutputs: boolean; - missingAssistantOutputs: boolean; - rawObservationCount: number; - compressedObservationCount: number; - }; - stats: { - observationCount: number; - highlightCount: number; - categoryCounts: Record; - fileCount: number; - toolCount: number; - }; - highlights: SessionHighlight[]; -} - -export interface SessionHighlightsError { - success: false; - error: string; -} - -export type HookType = - | "session_start" - | "prompt_submit" - | "pre_tool_use" - | "post_tool_use" - | "post_tool_failure" - | "pre_compact" - | "subagent_start" - | "subagent_stop" - | "notification" - | "task_completed" - | "stop" - | "session_end"; - -export interface HookPayload { - hookType: HookType; - sessionId: string; - project: string; - cwd: string; - timestamp: string; - data: unknown; -} - -export interface ProviderConfig { - provider: ProviderType; - model: string; - maxTokens: number; - /** Optional base URL override (e.g. for Anthropic-compatible APIs or local proxies) */ - baseURL?: string; -} - -export type ProviderType = "agent-sdk" | "anthropic" | "gemini" | "openrouter" | "minimax" | "openai" | "noop"; - -export interface MemoryProvider { - name: string; - compress(systemPrompt: string, userPrompt: string): Promise; - summarize(systemPrompt: string, userPrompt: string): Promise; - describeImage?(imageData: string, mimeType: string, prompt: string): Promise; -} - -export interface AgentMemoryConfig { - engineUrl: string; - restPort: number; - streamsPort: number; - provider: ProviderConfig; - tokenBudget: number; - maxObservationsPerSession: number; - compressionModel: string; - dataDir: string; -} - -export interface SearchResult { - observation: CompressedObservation; - score: number; - sessionId: string; -} - -export interface ContextBlock { - type: "summary" | "observation" | "memory"; - content: string; - tokens: number; - recency: number; - sourceIds?: string[]; -} - -export interface EvalResult { - valid: boolean; - errors: string[]; - qualityScore: number; - latencyMs: number; - functionId: string; -} - -export interface FunctionMetrics { - functionId: string; - totalCalls: number; - successCount: number; - failureCount: number; - avgLatencyMs: number; - avgQualityScore: number; -} - -export interface HealthSnapshot { - connectionState: string; - workers: Array<{ id: string; name: string; status: string }>; - memory: { - heapUsed: number; - heapTotal: number; - rss: number; - external: number; - }; - cpu: { userMicros: number; systemMicros: number; percent: number }; - eventLoopLagMs: number; - uptimeSeconds: number; - kvConnectivity?: { status: string; latencyMs?: number; error?: string }; - status: "healthy" | "degraded" | "critical"; - alerts: string[]; - notes?: string[]; -} - -export interface CircuitBreakerState { - state: "closed" | "open" | "half-open"; - failures: number; - lastFailureAt: number | null; - openedAt: number | null; -} - -export interface MemorySlot { - label: string; - content: string; - sizeLimit: number; - description: string; - pinned: boolean; - readOnly: boolean; - scope: "project" | "global"; - createdAt: string; - updatedAt: string; -} - -export interface EmbeddingProvider { - name: string; - dimensions: number; - embed(text: string): Promise; - embedBatch(texts: string[]): Promise; - embedImage?(src: string): Promise; -} - -export interface MemoryRelation { - type: "supersedes" | "extends" | "derives" | "contradicts" | "related"; - sourceId: string; - targetId: string; - createdAt: string; - confidence?: number; -} - -export interface HybridSearchResult { - observation: CompressedObservation; - bm25Score: number; - vectorScore: number; - graphScore: number; - combinedScore: number; - sessionId: string; - graphContext?: string; -} - -export interface CompactSearchResult { - obsId: string; - sessionId: string; - title: string; - type: ObservationType; - score: number; - timestamp: string; -} - -export interface CompactLessonResult { - lessonId: string; - content: string; - confidence: number; - score: number; - createdAt: string; - project?: string; - tags: string[]; -} - -export interface TimelineEntry { - observation: CompressedObservation; - sessionId: string; - relativePosition: number; -} - -export interface ProjectProfile { - project: string; - updatedAt: string; - topConcepts: Array<{ concept: string; frequency: number }>; - topFiles: Array<{ file: string; frequency: number }>; - conventions: string[]; - commonErrors: string[]; - recentActivity: string[]; - sessionCount: number; - totalObservations: number; - summary?: string; -} - -export interface ExportPagination { - offset: number; - limit: number; - total: number; - hasMore: boolean; -} - -export interface ExportData { - version: "0.3.0" | "0.4.0" | "0.5.0" | "0.6.0" | "0.6.1" | "0.7.0" | "0.7.2" | "0.7.3" | "0.7.4" | "0.7.5" | "0.7.6" | "0.7.7" | "0.7.9" | "0.8.0" | "0.8.1" | "0.8.2" | "0.8.3" | "0.8.4" | "0.8.5" | "0.8.6" | "0.8.7" | "0.8.8" | "0.8.9" | "0.8.10" | "0.8.11" | "0.8.12" | "0.8.13" | "0.9.0" | "0.9.1" | "0.9.2" | "0.9.3" | "0.9.4" | "0.9.5" | "0.9.6" | "0.9.7" | "0.9.8" | "0.9.9" | "0.9.10" | "0.9.11" | "0.9.12" | "0.9.13" | "0.9.14" | "0.9.15" | "0.9.16" | "0.9.17" | "0.9.18" | "0.9.19" | "0.9.20" | "0.9.21" | "0.9.22" | "0.9.23" | "0.9.24"; - exportedAt: string; - sessions: Session[]; - observations: Record; - memories: Memory[]; - summaries: SessionSummary[]; - profiles?: ProjectProfile[]; - graphNodes?: GraphNode[]; - graphEdges?: GraphEdge[]; - semanticMemories?: SemanticMemory[]; - proceduralMemories?: ProceduralMemory[]; - actions?: Action[]; - actionEdges?: ActionEdge[]; - routines?: Routine[]; - signals?: Signal[]; - checkpoints?: Checkpoint[]; - sentinels?: Sentinel[]; - sketches?: Sketch[]; - crystals?: Crystal[]; - facets?: Facet[]; - lessons?: Lesson[]; - insights?: Insight[]; - accessLogs?: AccessLogExport[]; - pagination?: ExportPagination; -} - -export interface AccessLogExport { - memoryId: string; - count: number; - lastAt: string; - recent: number[]; -} - -export interface EmbeddingConfig { - provider?: string; - bm25Weight: number; - vectorWeight: number; -} - -export interface FallbackConfig { - providers: ProviderType[]; -} - -export interface ClaudeBridgeConfig { - enabled: boolean; - projectPath: string; - memoryFilePath: string; - lineBudget: number; -} - -export interface StandaloneConfig { - dataDir: string; - persistPath: string; - agentType?: string; -} - -export type GraphNodeType = - | "file" - | "function" - | "concept" - | "error" - | "decision" - | "pattern" - | "library" - | "person" - | "project" - | "preference" - | "location" - | "organization" - | "event"; - -export interface GraphNode { - id: string; - type: GraphNodeType; - name: string; - properties: Record; - sourceObservationIds: string[]; - createdAt: string; - updatedAt?: string; - aliases?: string[]; - stale?: boolean; -} - -export type GraphEdgeType = - | "uses" - | "imports" - | "modifies" - | "causes" - | "fixes" - | "depends_on" - | "related_to" - | "works_at" - | "prefers" - | "blocked_by" - | "caused_by" - | "optimizes_for" - | "rejected" - | "avoids" - | "located_in" - | "succeeded_by"; - -export interface GraphEdge { - id: string; - type: GraphEdgeType; - sourceNodeId: string; - targetNodeId: string; - weight: number; - sourceObservationIds: string[]; - createdAt: string; - tcommit?: string; - tvalid?: string; - tvalidEnd?: string; - context?: EdgeContext; - version?: number; - supersededBy?: string; - isLatest?: boolean; - stale?: boolean; -} - -export interface EdgeContext { - reasoning?: string; - sentiment?: string; - alternatives?: string[]; - situationalFactors?: string[]; - confidence?: number; -} - -export interface GraphQueryResult { - nodes: GraphNode[]; - edges: GraphEdge[]; - depth: number; -} - -export type ConsolidationTier = - | "working" - | "episodic" - | "semantic" - | "procedural"; - -export interface SemanticMemory { - id: string; - fact: string; - confidence: number; - sourceSessionIds: string[]; - sourceMemoryIds: string[]; - accessCount: number; - lastAccessedAt: string; - strength: number; - createdAt: string; - updatedAt: string; -} - -export interface ProceduralMemory { - id: string; - name: string; - steps: string[]; - triggerCondition: string; - expectedOutcome?: string; - frequency: number; - sourceSessionIds: string[]; - sourceObservationIds?: string[]; - tags?: string[]; - concepts?: string[]; - strength: number; - createdAt: string; - updatedAt: string; -} - -export interface TeamConfig { - teamId: string; - userId: string; - mode: "shared" | "private"; -} - -export type AgentScopeMode = "shared" | "isolated"; -export interface AgentScope { - agentId: string; - mode: AgentScopeMode; -} - -export interface TeamSharedItem { - id: string; - sharedBy: string; - sharedAt: string; - type: "observation" | "memory" | "pattern"; - content: unknown; - project: string; - visibility: "shared" | "private"; -} - -export interface TeamProfile { - teamId: string; - members: string[]; - topConcepts: Array<{ concept: string; frequency: number }>; - topFiles: Array<{ file: string; frequency: number }>; - sharedPatterns: string[]; - totalSharedItems: number; - updatedAt: string; -} - -export interface AuditEntry { - id: string; - timestamp: string; - operation: - | "observe" - | "compress" - | "remember" - | "forget" - | "evolve" - | "consolidate" - | "share" - | "delete" - | "import" - | "export" - | "action_create" - | "action_update" - | "lease_acquire" - | "lease_release" - | "routine_run" - | "signal_send" - | "checkpoint_resolve" - | "mesh_sync" - | "relation_create" - | "relation_update" - | "sentinel_create" - | "sentinel_trigger" - | "sketch_create" - | "sketch_promote" - | "retention_score" - | "sketch_discard" - | "crystallize" - | "diagnose" - | "heal" - | "facet_tag" - | "lesson_save" - | "lesson_recall" - | "lesson_strengthen" - | "obsidian_export" - | "reflect" - | "insight_search" - | "skill_extract" - | "core_add" - | "core_remove" - | "auto_page" - | "vision_embed" - | "slot_append" - | "slot_replace" - | "slot_create" - | "slot_delete" - | "slot_reflect" - | "inbox_ask" - | "inbox_notify" - | "inbox_answer" - | "inbox_dismiss" - | "inbox_delivered" - | "delivery_failed"; - userId?: string; - functionId: string; - targetIds: string[]; - details: Record; - qualityScore?: number; -} - -export interface GovernanceFilter { - type?: string[]; - dateFrom?: string; - dateTo?: string; - project?: string; - qualityBelow?: number; -} - -export interface SnapshotMeta { - id: string; - commitHash: string; - createdAt: string; - message: string; - stats: { - sessions: number; - observations: number; - memories: number; - graphNodes: number; - }; -} - -export interface SnapshotDiff { - fromCommit: string; - toCommit: string; - added: { memories: number; observations: number; graphNodes: number }; - removed: { memories: number; observations: number; graphNodes: number }; -} - -export interface Action { - id: string; - title: string; - description: string; - status: "pending" | "active" | "done" | "blocked" | "cancelled"; - priority: number; - createdAt: string; - updatedAt: string; - createdBy: string; - assignedTo?: string; - project?: string; - tags: string[]; - sourceObservationIds: string[]; - sourceMemoryIds: string[]; - result?: string; - parentId?: string; - metadata?: Record; - sketchId?: string; - crystallizedInto?: string; -} - -export type ActionEdgeType = - | "requires" - | "unlocks" - | "spawned_by" - | "gated_by" - | "conflicts_with"; - -export interface ActionEdge { - id: string; - type: ActionEdgeType; - sourceActionId: string; - targetActionId: string; - createdAt: string; - metadata?: Record; -} - -export interface Lease { - id: string; - actionId: string; - agentId: string; - acquiredAt: string; - expiresAt: string; - renewedAt?: string; - status: "active" | "expired" | "released"; -} - -export interface Routine { - id: string; - name: string; - description: string; - steps: RoutineStep[]; - createdAt: string; - updatedAt: string; - frozen: boolean; - tags: string[]; - sourceProceduralIds: string[]; -} - -export interface RoutineStep { - order: number; - title: string; - description: string; - actionTemplate: Partial; - dependsOn: number[]; -} - -export interface RoutineRun { - id: string; - routineId: string; - status: "running" | "completed" | "failed" | "paused"; - startedAt: string; - completedAt?: string; - actionIds: string[]; - stepStatus: Record; - initiatedBy: string; -} - -export interface Signal { - id: string; - from: string; - to?: string; - threadId?: string; - replyTo?: string; - type: "info" | "request" | "response" | "alert" | "handoff"; - content: string; - metadata?: Record; - createdAt: string; - readAt?: string; - expiresAt?: string; -} - -// Line C: Agent→user async inbox. Distinct from Signal (which is agent↔agent, -// requires agentId, has no awaiting/answered state). Inbox items are always -// "Agent → the human user", so no `to`/agentId field. -export interface InboxItem { - id: string; - kind: "question" | "briefing"; // question = needs a reply; briefing = Agent's proactive summary, ack-only - body: string; // Markdown; rendered via renderMarkdownSafe in the viewer - status: "awaiting" | "answered" | "dismissed"; - priority?: "high" | "normal" | "low"; // reserved; not used for ordering this round - fromAgent?: string; - project?: string; - sourceObservationIds?: string[]; // reuses STEP-03 evidence-jump - sourceSessionId?: string; - answer?: string; // user's reply when a question is answered - createdAt: string; - answeredAt?: string; - expiresAt?: string; -} - -// Line D: per-item delivery ledger for cross-device push (Feishu/Lark). -// Kept separate from InboxItem so the inbox stays the source of truth and -// delivery state never pollutes it. id === InboxItem.id (one-to-one, natural -// dedup key — an item is pushed at most once). -export interface DeliveryRecord { - id: string; // = InboxItem.id - channel: "lark"; // only lark this round; reserved for multi-channel - status: "sent" | "failed" | "skipped"; - messageId?: string; // lark message_id (om_xxx), for receipts/recall - urgent?: boolean; // whether urgent_app succeeded (question only) - error?: string; // failure reason (lark-cli stderr summary / permission_violations) - attempts: number; - createdAt: string; - deliveredAt?: string; -} - -export interface Checkpoint { - id: string; - name: string; - description: string; - status: "pending" | "passed" | "failed" | "expired"; - type: "ci" | "approval" | "deploy" | "external" | "timer"; - createdAt: string; - resolvedAt?: string; - resolvedBy?: string; - result?: unknown; - expiresAt?: string; - linkedActionIds: string[]; -} - -export interface Sketch { - id: string; - title: string; - description: string; - status: "active" | "promoted" | "discarded"; - actionIds: string[]; - project?: string; - createdAt: string; - expiresAt: string; - promotedAt?: string; - discardedAt?: string; -} - -export interface Facet { - id: string; - targetId: string; - targetType: "action" | "memory" | "observation"; - dimension: string; - value: string; - createdAt: string; -} - -export interface Sentinel { - id: string; - name: string; - type: "webhook" | "timer" | "threshold" | "pattern" | "approval" | "custom"; - status: "watching" | "triggered" | "cancelled" | "expired"; - config: Record; - result?: unknown; - createdAt: string; - triggeredAt?: string; - expiresAt?: string; - linkedActionIds: string[]; - escalatedAt?: string; -} - -export interface Crystal { - id: string; - narrative: string; - keyOutcomes: string[]; - filesAffected: string[]; - lessons: string[]; - sourceActionIds: string[]; - sessionId?: string; - project?: string; - createdAt: string; -} - -export interface Lesson { - id: string; - content: string; - context: string; - confidence: number; - reinforcements: number; - source: "crystal" | "manual" | "consolidation"; - sourceIds: string[]; - project?: string; - tags: string[]; - createdAt: string; - updatedAt: string; - lastReinforcedAt?: string; - lastDecayedAt?: string; - decayRate: number; - deleted?: boolean; -} - -export interface Insight { - id: string; - title: string; - content: string; - confidence: number; - reinforcements: number; - sourceConceptCluster: string[]; - sourceMemoryIds: string[]; - sourceLessonIds: string[]; - sourceCrystalIds: string[]; - project?: string; - tags: string[]; - createdAt: string; - updatedAt: string; - lastReinforcedAt?: string; - lastDecayedAt?: string; - decayRate: number; - deleted?: boolean; -} - -export interface DiagnosticCheck { - name: string; - category: string; - status: "pass" | "warn" | "fail"; - message: string; - fixable: boolean; -} - -export interface MeshPeer { - id: string; - url: string; - name: string; - lastSyncAt?: string; - status: "connected" | "disconnected" | "syncing" | "error"; - sharedScopes: string[]; - syncFilter?: { project?: string }; -} - - -export interface EnrichedChunk { - id: string; - originalObsId: string; - sessionId: string; - content: string; - resolvedEntities: Record; - preferences: string[]; - contextBridges: string[]; - windowStart: number; - windowEnd: number; - createdAt: string; -} - -export interface LatentEmbedding { - obsId: string; - contentEmbedding: string; - latentEmbedding: string; - sessionId: string; -} - -export interface QueryExpansion { - original: string; - reformulations: string[]; - temporalConcretizations: string[]; - entityExtractions: string[]; -} - -export interface TripleStreamResult { - observation: CompressedObservation; - vectorScore: number; - bm25Score: number; - graphScore: number; - combinedScore: number; - sessionId: string; - graphContext?: string; -} - -export interface TemporalQuery { - entityName: string; - asOf?: string; - from?: string; - to?: string; - includeHistory?: boolean; -} - -export interface TemporalState { - entity: GraphNode; - currentEdges: GraphEdge[]; - historicalEdges: GraphEdge[]; - timeline: Array<{ - edge: GraphEdge; - validFrom: string; - validTo?: string; - context?: EdgeContext; - }>; -} - -export interface RetentionScore { - memoryId: string; - // Which KV scope this row came from. Needed by mem::retention-evict - // so the delete loop routes to KV.memories or KV.semantic correctly. - // Missing on pre-0.8.10 rows — callers must treat `undefined` as - // "unknown" and probe both scopes for backwards-compat. See #124. - source?: "episodic" | "semantic"; - score: number; - salience: number; - temporalDecay: number; - reinforcementBoost: number; - lastAccessed: string; - accessCount: number; -} - -export interface DecayConfig { - lambda: number; - sigma: number; - tierThresholds: { - hot: number; - warm: number; - cold: number; - }; -} - -/** - * KV.state scope — long-lived system counters + flags keyed by string. - * Keep keys/types in sync with the state-scope callers (e.g., - * disk-size-manager) so TypeScript enforces consistent value shapes - * instead of every caller using ad-hoc `` generics. - */ -export interface StateScope { - "system:currentDiskSize": number; -} - -export type StateScopeKey = keyof StateScope; diff --git a/src/utils/image-store.ts b/src/utils/image-store.ts deleted file mode 100644 index 3197e2c7..00000000 --- a/src/utils/image-store.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { homedir } from "node:os"; -import { join, resolve, sep } from "node:path"; -import { existsSync } from "node:fs"; -import { mkdir, writeFile, unlink, utimes, stat } from "node:fs/promises"; -import { createHash } from "node:crypto"; - -export const IMAGES_DIR = join(homedir(), ".agentmemory", "images"); - -const DEFAULT_MAX_BYTES = 500 * 1024 * 1024; - -export function getMaxBytes(): number { - return Number(process.env.AGENTMEMORY_IMAGE_STORE_MAX_BYTES) || DEFAULT_MAX_BYTES; -} - -export function isManagedImagePath(filePath: string): boolean { - const resolved = resolve(filePath); - const normalizedImagesDir = resolve(IMAGES_DIR); - return resolved.startsWith(normalizedImagesDir + sep) || resolved === normalizedImagesDir; -} - -function contentHash(data: string): string { - return createHash("sha256").update(data).digest("hex"); -} - -export async function saveImageToDisk(base64Data: string): Promise<{ filePath: string; bytesWritten: number }> { - if (!base64Data) return { filePath: "", bytesWritten: 0 }; - - if (!existsSync(IMAGES_DIR)) { - await mkdir(IMAGES_DIR, { recursive: true }); - } - - let cleanBase64 = base64Data; - let ext = "png"; - - if (base64Data.startsWith("data:image/")) { - const commaIdx = base64Data.indexOf(","); - if (commaIdx !== -1) { - const meta = base64Data.substring(0, commaIdx); - if (meta.includes("jpeg") || meta.includes("jpg")) ext = "jpg"; - else if (meta.includes("webp")) ext = "webp"; - else if (meta.includes("gif")) ext = "gif"; - cleanBase64 = base64Data.substring(commaIdx + 1); - } - } else if (base64Data.startsWith("/9j/")) { - ext = "jpg"; - } - - const hash = contentHash(cleanBase64); - const filePath = join(IMAGES_DIR, `${hash}.${ext}`); - - if (existsSync(filePath)) { - return { filePath, bytesWritten: 0 }; - } - - const buffer = Buffer.from(cleanBase64, "base64"); - await writeFile(filePath, buffer); - - const s = await stat(filePath); - - return { filePath, bytesWritten: s.size }; -} - -export async function deleteImage(filePath: string | undefined): Promise<{ deletedBytes: number }> { - if (!filePath) return { deletedBytes: 0 }; - if (!isManagedImagePath(filePath)) return { deletedBytes: 0 }; - try { - if (existsSync(filePath)) { - const s = await stat(filePath); - const size = s.size; - await unlink(filePath); - return { deletedBytes: size }; - } - } catch (err) { - console.error("[agentmemory] Failed to delete image context:", err); - } - return { deletedBytes: 0 }; -} - -/** Touch an image file to update its mtime (marking it as recently used for LRU eviction) */ -export async function touchImage(filePath: string): Promise { - if (!filePath || !isManagedImagePath(filePath)) return; - try { - if (existsSync(filePath)) { - const now = new Date(); - await utimes(filePath, now, now); - } - } catch (err) { - // Ignore touch errors silently - } -} diff --git a/src/version.ts b/src/version.ts deleted file mode 100644 index 7df8a898..00000000 --- a/src/version.ts +++ /dev/null @@ -1 +0,0 @@ -export const VERSION = "0.9.24"; diff --git a/src/viewer/agent-avatars/claude.png b/src/viewer/agent-avatars/claude.png deleted file mode 100644 index 7c81e37d..00000000 Binary files a/src/viewer/agent-avatars/claude.png and /dev/null differ diff --git a/src/viewer/agent-avatars/codex.png b/src/viewer/agent-avatars/codex.png deleted file mode 100644 index 11155e4d..00000000 Binary files a/src/viewer/agent-avatars/codex.png and /dev/null differ diff --git a/src/viewer/agent-avatars/hermes.png b/src/viewer/agent-avatars/hermes.png deleted file mode 100644 index 5639671d..00000000 Binary files a/src/viewer/agent-avatars/hermes.png and /dev/null differ diff --git a/src/viewer/agent-avatars/openclaw.png b/src/viewer/agent-avatars/openclaw.png deleted file mode 100644 index 38fecc60..00000000 Binary files a/src/viewer/agent-avatars/openclaw.png and /dev/null differ diff --git a/src/viewer/demo/browser-extension.html b/src/viewer/demo/browser-extension.html deleted file mode 100644 index 9e6d8e1f..00000000 --- a/src/viewer/demo/browser-extension.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - AI Todo 插件预览 - - - - - -
-
-
-

记忆建议预览

-

在这个本地页面里预览浏览器插件的核心体验:当你在 AI 输入框里写问题时,插件会在旁边召回相关记忆,并把保存动作送回本地工作台审阅。

-
-
AI Todo Demo
-
- -
-
-
- 本地 AI 助手 - Agent Memory Demo -
-
-
- -

我最近在整理申请、作品集和 Agent 工具实验,很多偏好和项目背景都散在不同对话里。

-
-
- AI 助手 -

如果这些背景能在输入框附近被召回,我就能更快理解你当前的项目、约束和表达偏好。

-
-
- -

我希望记忆先变成候选,确认后再进入长期记忆,不要自动把网页内容全塞进去。

-
-
-
-
根据我的项目偏好,帮我继续整理 AI Todo 的插件体验。
-
- - -
-
-
- - -
-
- - diff --git a/src/viewer/document.ts b/src/viewer/document.ts deleted file mode 100644 index f8da5aa4..00000000 --- a/src/viewer/document.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { readFileSync } from "node:fs"; -import { join, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; -import { - VIEWER_NONCE_PLACEHOLDER, - createViewerNonce, - buildViewerCsp, -} from "../auth.js"; -import { VERSION } from "../version.js"; - -const VIEWER_VERSION_PLACEHOLDER = "__AGENTMEMORY_VERSION__"; - -function loadViewerTemplate(): string | null { - const base = dirname(fileURLToPath(import.meta.url)); - const candidates = [ - join(base, "..", "src", "viewer", "index.html"), - join(base, "..", "viewer", "index.html"), - join(base, "viewer", "index.html"), - ]; - for (const path of candidates) { - try { - return readFileSync(path, "utf-8"); - } catch {} - } - return null; -} - -export function renderViewerDocument(): - | { found: true; html: string; csp: string } - | { found: false } { - const template = loadViewerTemplate(); - if (!template) { - return { found: false }; - } - - const nonce = createViewerNonce(); - const html = template - .replaceAll(VIEWER_NONCE_PLACEHOLDER, nonce) - .replaceAll(VIEWER_VERSION_PLACEHOLDER, VERSION); - return { - found: true, - html, - csp: buildViewerCsp(nonce), - }; -} diff --git a/src/viewer/favicon.svg b/src/viewer/favicon.svg deleted file mode 100644 index 3ef799f7..00000000 --- a/src/viewer/favicon.svg +++ /dev/null @@ -1 +0,0 @@ -AM diff --git a/src/viewer/index.html b/src/viewer/index.html deleted file mode 100644 index 80cdebfc..00000000 --- a/src/viewer/index.html +++ /dev/null @@ -1,11390 +0,0 @@ - - - - - - - AI Todo - - - - - -
- -

AI Todo

-
-
- - Live updates off -
-
- -
-
- - - -
-
- - - - -
-
- -
- -
-
-
-
-
-
-
-
-
-
-
-
- - - - - - - - diff --git a/src/viewer/parts/00-head.html b/src/viewer/parts/00-head.html deleted file mode 100644 index db63af46..00000000 --- a/src/viewer/parts/00-head.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - AI Todo - - - - - -
- -

AI Todo

-
-
- - Live updates off -
-
- -
-
- - - -
-
- - - - -
-
- -
- -
-
-
-
-
-
-
-
-
-
-
-
- - - - - - - - diff --git a/src/viewer/parts/app/00-prelude.js b/src/viewer/parts/app/00-prelude.js deleted file mode 100644 index 9681ee5e..00000000 --- a/src/viewer/parts/app/00-prelude.js +++ /dev/null @@ -1,86 +0,0 @@ - // 通过 file:// 打开时会触发跨域限制,导致本地接口数据不可用。 - // 自动跳转到本地服务地址,保留当前 hash 路由。 - if (window.location.protocol === 'file:') { - var target = 'http://localhost:3114/' + (window.location.hash || '#dashboard'); - window.location.replace(target); - } - - var params = new URLSearchParams(window.location.search); - var paramPort = params.get('port'); - var locPort = window.location.port; - var hasHost = !!window.location.hostname; - var hostName = hasHost ? window.location.hostname : 'localhost'; - var wsProto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - var REST, WS_URL, WS_DIRECT_URL, wsPort; - if (paramPort) { - var resolvedPort = parseInt(paramPort) === 3111 ? '3114' : paramPort; - REST = window.location.protocol + '//' + hostName + ':' + resolvedPort; - wsPort = params.get('wsPort') || String(parseInt(resolvedPort) - 1); - WS_URL = wsProto + '//' + hostName + ':' + wsPort; - WS_DIRECT_URL = WS_URL + '/stream/mem-live/viewer'; - } else if (locPort) { - var resolvedPort = parseInt(locPort) === 3111 ? '3114' : locPort; - REST = window.location.protocol + '//' + hostName + ':' + resolvedPort; - wsPort = params.get('wsPort') || String(parseInt(resolvedPort) - 1); - WS_URL = wsProto + '//' + hostName + ':' + wsPort; - WS_DIRECT_URL = WS_URL + '/stream/mem-live/viewer'; - } else { - // file:// 场景下,origin/host 为空;默认回退到本地 agentmemory 服务。 - var fallbackPort = parseInt(params.get('port') || '3114', 10); - if (Number.isNaN(fallbackPort)) fallbackPort = 3114; - REST = 'http://' + hostName + ':' + fallbackPort; - wsPort = params.get('wsPort') || String(fallbackPort - 1); - WS_URL = wsProto + '//' + hostName + ':' + wsPort; - WS_DIRECT_URL = WS_URL + '/stream/mem-live/viewer'; - } - - function isDarkMode() { return document.documentElement.dataset.theme === 'dark'; } - function applyTheme(dark, persist) { - document.documentElement.dataset.theme = dark ? 'dark' : 'light'; - var btn = document.getElementById('theme-toggle'); - if (btn) btn.textContent = dark ? 'LIGHT' : 'DARK'; - if (persist) localStorage.setItem('agentmemory-theme', dark ? 'dark' : 'light'); - } - window.toggleTheme = function() { applyTheme(!isDarkMode(), true); }; - var savedTheme = localStorage.getItem('agentmemory-theme'); - if (savedTheme) { - applyTheme(savedTheme === 'dark', false); - } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { - applyTheme(true, false); - } - - var NODE_COLORS = { - file: '#2D6A4F', function: '#1D4E89', concept: '#B8860B', error: '#CC0000', - decision: '#6B3FA0', pattern: '#2563EB', library: '#C2410C', person: '#111111' - }; - var OP_BADGES = { - observe: 'badge-blue', compress: 'badge-cyan', remember: 'badge-green', - forget: 'badge-red', evolve: 'badge-purple', consolidate: 'badge-yellow', - share: 'badge-orange', delete: 'badge-red', import: 'badge-blue', export: 'badge-blue' - }; - var TYPE_BADGES = { - pattern: 'badge-purple', preference: 'badge-blue', architecture: 'badge-cyan', - bug: 'badge-red', workflow: 'badge-green', fact: 'badge-yellow', - profile: 'badge-muted', history: 'badge-muted', project: 'badge-green' - }; - var OBS_TYPE_COLORS = { - file_read: '#1D4E89', file_write: '#2D6A4F', file_edit: '#B8860B', - command_run: '#C2410C', search: '#2563EB', web_fetch: '#6B3FA0', - conversation: '#111111', error: '#CC0000', decision: '#B8860B', - discovery: '#2D6A4F', subagent: '#6B3FA0', notification: '#0E7490', - task: '#1D4E89', other: '#666666' - }; - var OBS_TYPE_ICONS = { - file_read: '📄', file_write: '✏', file_edit: '📝', - command_run: '⚡', search: '🔎', web_fetch: '🌐', - conversation: '💬', error: '⚠', decision: '🤔', - discovery: '💡', subagent: '🤖', notification: '🔔', - task: '☑', other: '📄' - }; - // === i18n base (PLAN-001 STEP-01) === - // Lightweight inline i18n: a keyed {en, zh} catalog + t(key) lookup. - // Display labels live here, keyed BY the stored lowercase enum, so switching - // language never touches Action.status / statusFilter literals. - // ponytail: inline single-file base; promote to a shared module only if a - // second surface (extension) needs the same catalog. - /* i18n-core:start */ diff --git a/src/viewer/parts/app/05-i18n.js b/src/viewer/parts/app/05-i18n.js deleted file mode 100644 index 90de04eb..00000000 --- a/src/viewer/parts/app/05-i18n.js +++ /dev/null @@ -1,500 +0,0 @@ - var I18N_MESSAGES = { - en: { - 'tab.dashboard': 'Overview', 'tab.actions': 'To-Do', 'tab.sessions': 'Evidence', - 'status.pending': 'Pending', 'status.active': 'In progress', 'status.done': 'Done', - 'status.blocked': 'Blocked', 'status.cancelled': 'Cancelled', - 'filter.review': 'To confirm', 'filter.all': 'All', - 'dash.firstRun.kicker': 'First run', - 'dash.firstRun.title': 'Import sample data first', - 'dash.firstRun.body': 'No session data yet. Run the demo and the page will fill with browsable to-dos and activity.', - 'dash.firstRun.link': 'View quick start →', - 'dash.noRecord': 'No records yet', - 'dash.stat.sessions': 'Sessions', - 'dash.stat.recent': 'Latest', - 'dash.stat.todos': 'Todos', - 'dash.stat.openWorkbench': 'Open workbench', - 'dash.stat.replyQueue': 'Reply queue', - 'dash.stat.actionCandidates': 'Action candidates', - 'dash.stat.pendingActions': 'Pending actions', - 'dash.stat.memories': 'Memories', - 'dash.stat.latestVersion': 'Latest version', - 'dash.stat.lessons': 'Lessons', - 'dash.stat.lessonsSub': 'Lessons to organize', - 'dash.stat.graphNodes': 'Graph nodes', - 'dash.stat.edges': 'Edges', - 'dash.stat.status': 'Status', - 'dash.stat.toolCalls': 'Tool calls', - 'dash.stat.tracking': 'Tracking', - 'dash.stat.functions': 'functions', - 'dash.stat.circuitBreaker': 'Circuit breaker', - 'dash.stat.failed': 'Failed', - 'dash.stat.times': 'times', - 'dash.systemResources': 'System resources', - 'dash.alerts': 'Alerts', - 'dash.notes': 'Notes', - 'dash.recentSessions': 'Recent sessions', - 'dash.emptySessions': 'No session data yet. Run the demo or connect your workflow.', - 'dash.unnamedSession': 'Unnamed session', - 'dash.openLatestSyncOf': 'Open latest sync of', - 'dash.syncs': 'syncs', - 'dash.records': 'records', - 'dash.lastSync': 'Last sync', - 'dash.functionMetrics': 'Function metrics (OTel)', - 'dash.workers': 'Workers', - 'dash.circuitBreakerDetail': 'Circuit breaker detail', - 'dash.semanticMemory': 'Semantic memory', - 'dash.emptySemantic': 'No semantic memory yet; the system distills it from observations over time.', - 'dash.proceduralMemory': 'Procedural memory', - 'dash.emptyProcedural': 'No procedural memory yet; repeated patterns are extracted automatically.', - 'dash.consolidationStatus': 'Consolidation status', - 'dash.memoryRelations': 'Memory relations', - 'act.attn.next': 'Next', 'act.attn.needsWork': 'Needs work', 'act.attn.noteworthy': 'Noteworthy', - 'act.prio.high': 'Important', 'act.prio.normal': 'Normal', 'act.prio.low': 'Low', - 'act.untitled': 'Untitled', 'act.untitledCandidate': 'Untitled candidate', - 'act.metric.todo': 'Todo', 'act.metric.attention': 'Todo', 'act.metric.waiting': 'Todo', 'act.metric.review': 'Todo', 'act.metric.followUp': 'Todo', 'act.metric.active': 'Todo', 'act.metric.done': 'Done', - 'act.attention.reply': 'Reply', 'act.attention.confirm': 'Confirm', 'act.attention.followUp': 'Follow up', - 'act.section.awaiting': 'Needs your reply', 'act.section.review': 'Needs confirmation', 'act.section.followUp': 'Needs follow-up', - 'act.section.earlier': 'Earlier open items', - 'act.section.earlierLead': 'Open work from the last 3-10 days is folded by default.', - 'act.section.older': 'Older backlog', - 'act.section.olderLead': 'These items are 10+ days old and may be stale.', - 'act.section.legacy': 'Legacy extraction backlog', - 'act.section.legacyLead': 'Cards from the old extraction path are hidden by default; search still finds them.', - 'act.section.expand': 'Expand', - 'act.section.collapse': 'Collapse', - 'act.focus.label': 'Focus', - 'act.focus.current': 'current', - 'act.focus.olderHidden': 'older hidden', - 'act.recheck': 'Source updated', - 'act.searchPlaceholder': 'Search todos...', - 'act.nToConfirm': 'to confirm', 'act.nConfirmed': 'confirmed', 'act.refresh': 'Refresh', - 'act.viewOriginal': 'View original', 'act.confirm': 'Confirm', 'act.ignore': 'Ignore', - 'act.from': 'From', 'act.viewSource': 'View source →', 'act.updated': 'Updated', - 'act.extract.title': 'Use LLM to organize recent sessions', - 'act.extract.run': 'Organize with LLM', - 'act.extract.running': 'Organizing...', - 'act.extract.done': 'Organized', - 'act.extract.partial': 'Partially organized', - 'act.extract.rules': 'LLM unavailable', - 'act.extract.error': 'Organize failed', - 'act.extract.failedExisting': 'Extraction failed; showing existing todos', - 'act.extract.loading': 'Loading todos...', - 'act.extract.starting': 'Organizing recent sessions...', - 'act.extract.background': 'Latest todos are shown; still organizing...', - 'act.extract.doneLlm': 'LLM extraction complete', - 'act.extract.doneMixed': 'Partial LLM extraction complete', - 'act.extract.doneRules': 'Rules extraction complete', - 'act.extract.created': 'new', - 'act.extract.history': 'history', - 'act.extract.discarded': 'discarded', - 'act.extract.cleaned': 'cleaned', - 'act.extract.reason': 'reason', - 'act.cleanup.title': 'Update recorded cards from sessions that changed', - 'act.cleanup.run': 'Update', - 'act.cleanup.running': 'Updating...', - 'act.cleanup.applying': 'Applying updates...', - 'act.cleanup.done': 'Updated', - 'act.cleanup.error': 'Update failed', - 'act.cleanup.failed': 'Update failed; cards unchanged', - 'act.cleanup.clean': 'All cards are up to date', - 'act.cleanup.none': 'No cards need updating', - 'act.cleanup.llmUnavailable': 'LLM unavailable — no changes', - 'act.cleanup.confirm': 'Apply these updates?', - 'act.cleanup.summary': 'update {rewritten} · done {completed} · drop {dropped} · merge {merged}', - 'act.cardRefresh.run': 'Update', - 'act.cardRefresh.running': 'Updating...', - 'act.cardRefresh.done': 'Updated from source', - 'act.cardRefresh.review': 'Sent to confirm', - 'act.cardRefresh.kept': 'No better card found', - 'act.cardRefresh.kept.incompleteTitle': 'Title is incomplete', - 'act.cardRefresh.kept.evidenceInvalid': 'Source evidence did not match', - 'act.cardRefresh.kept.lowQuality': 'Candidate was too vague', - 'act.cardRefresh.kept.completedOrHistory': 'Looks completed or stale', - 'act.cardRefresh.kept.polluted': 'Looks like a log, not a todo', - 'act.cardRefresh.error': 'Update failed', - 'act.status.complete': 'Complete', - 'act.status.archive': 'Archive', - 'act.status.delete': 'Delete', - 'act.itemsUnit': 'items', - 'act.empty.title': 'No todos yet', - 'act.empty.lead': 'This is where todos, blocked items, and completed work extracted from your sessions will appear.', - 'settings.title': 'Settings', - 'settings.subtitle': 'Local configuration is written to the user config file and applies on the next organize run.', - 'settings.close': 'Close', - 'settings.language': 'UI language', - 'settings.extractor': 'LLM extraction config', - 'settings.sinceDays': 'Look-back window (days): only sessions from the last N days', - 'settings.maxInteractions': 'Max interaction records per session (one user request → agent reply)', - 'settings.apiKeyKeep': 'Enter a new API key to replace it, or leave blank to keep the current key', - 'settings.apiKeyMissing': 'Not configured', - 'settings.apiKeyLabel': 'API key:', - 'settings.runtimeReady': 'LLM runtime ready', - 'settings.runtimeMissing': 'LLM runtime missing', - 'settings.save': 'Save config', - 'settings.saving': 'Saving...', - 'settings.savedReady': 'Config saved. The next organize run will use it now.', - 'settings.saveFailed': 'Config save failed', - 'act.status.updateFailed': 'Todo status update failed', - 'obs.type.file_read': 'Read file', - 'obs.type.file_write': 'Write file', - 'obs.type.file_edit': 'Edit file', - 'obs.type.command_run': 'Run command', - 'obs.type.search': 'Search', - 'obs.type.web_fetch': 'Web fetch', - 'obs.type.conversation': 'Conversation', - 'obs.type.error': 'Error', - 'obs.type.decision': 'Decision', - 'obs.type.discovery': 'Discovery', - 'obs.type.subagent': 'Subagent', - 'obs.type.notification': 'Notification', - 'obs.type.task': 'Task', - 'obs.type.image': 'Image', - 'obs.type.other': 'Other', - 'obs.summary.localOperation': 'Run local operation', - 'obs.summary.restartService': 'Restart AI Todo service', - 'obs.summary.startService': 'Start AI Todo service', - 'obs.summary.openPreview': 'Open AI Todo preview', - 'obs.summary.checkService': 'Check service status', - 'obs.summary.verifyPageFix': 'Verify page fix', - 'obs.summary.viewFixLog': 'View fix log', - 'obs.summary.checkBrowserAutomation': 'Check browser automation dependency', - 'obs.summary.checkProcess': 'Check local service process', - 'obs.summary.organizeLocalFiles': 'Organize local files', - 'obs.summary.readLocalServiceData': 'Read local service data', - 'obs.summary.viewLocalFiles': 'View local file content', - 'obs.summary.runProjectScript': 'Run project script', - 'obs.summary.checkCodeVersion': 'Check or update code version', - 'obs.summary.runLocalCommand': 'Run local command', - 'obs.output.noText': 'This step completed without extra output.', - 'obs.output.serviceOk': 'The result is healthy and the service is available.', - 'obs.output.npxMissing': 'Check result: npx is not available in this environment.', - 'obs.output.npxOk': 'Check result: browser automation dependency is available.', - 'obs.output.pageFixOk': 'Check result: the page contains the expected fix.', - 'obs.output.error': 'The command returned an error and needs follow-up.', - 'obs.output.readService': 'Read data returned by the local service.', - 'obs.output.viewFiles': 'Viewed local file content to confirm current state.', - 'obs.output.done': 'This step completed and returned output.', - 'obs.display.promptSubmit': 'User request', - 'obs.display.agentMessage': 'Assistant response', - 'obs.display.updatePlan': 'Update plan', - 'obs.display.updatePlanBody': 'The task plan was updated.', - 'obs.display.applyPatch': 'Update local files', - 'obs.display.applyPatchBody': 'Code changes were applied.', - 'obs.display.toolTrace': 'This step was recorded as a structured tool event.', - 'episode.type.file_read': 'Read material', - 'episode.type.file_write': 'Write file', - 'episode.type.file_edit': 'Edit file', - 'episode.type.command_run': 'Local operation', - 'episode.type.search': 'Search', - 'episode.type.web_fetch': 'Web material', - 'episode.type.conversation': 'Conversation progress', - 'episode.type.error': 'Error triage', - 'episode.type.decision': 'Decision record', - 'episode.type.discovery': 'Discovery', - 'episode.type.subagent': 'Collaboration task', - 'episode.type.notification': 'Reminder', - 'episode.type.task': 'Task progress', - 'episode.type.other': 'Other', - 'episode.kind.user_need': 'User request', - 'episode.kind.bugfix': 'Fix record', - 'episode.kind.research': 'Project research', - 'episode.kind.file_work': 'File work', - 'episode.kind.important': 'Important segment', - 'episode.kind.work': 'Work progress', - 'episode.workSegment': 'Work segment', - 'episode.record': 'records', - 'episode.bodyPrefix': 'This segment mainly contains ', - 'episode.bodySuffix': ', summarized from low-level records.', - 'project.uncategorized': 'Uncategorized', - 'project.all': 'All projects', - 'project.browser': 'Browser', - 'project.demo': 'Demo data', - 'source.local': 'Local record', - 'source.agentMarked': 'Agent wrote a source marker', - 'source.demoNote': 'From the demo command, not real user work', - 'source.importedClaude': 'Claude Code history import', - 'source.importedClaudeNote': 'Imported from local JSONL history', - 'source.pathInferred': 'Inferred from project path', - 'source.unknownAgentNote': 'This session does not identify which agent created it', - 'agent.avatarAlt': 'avatar', - 'ses.noRecordId': 'no record ID', - 'tab.memories': 'Memories', 'tab.lessons': 'Lessons', 'tab.graph': 'Graph', 'tab.timeline': 'Timeline', 'tab.activity': 'Live', 'tab.profile': 'Profile', 'tab.audit': 'Audit', 'tab.replay': 'Replay', 'tab.crystals': 'Crystals', - 'ses.emptyNeedRetry': 'No sessions read yet — make sure the local service is running, then retry.', - 'ses.empty': 'No sessions yet', 'ses.retry': 'Retry', 'ses.allSessions': 'All sessions', - 'ses.heroTitle': 'Sessions', - 'ses.heroNote': 'Sessions from your browser and local agents land here; browse them by folder or source.', - 'ses.unitSessions': 'sessions', 'ses.unitFolders': 'folders', 'ses.unitSources': 'sources', 'ses.unitRecords': 'records', 'ses.recordsUnit': 'records', - 'ses.groupModeAria': 'Group sessions by', 'ses.byFolder': 'By folder', 'ses.bySource': 'By source', - 'ses.groupAria': 'Session groups', 'ses.source': 'Source', 'ses.folder': 'Folder', - 'ses.emptyFolder': 'No sessions in this folder yet.', 'ses.emptySource': 'No sessions from this source yet.', - 'ses.noPreview': 'No preview — click to view the full session.', - 'ses.avatarFallback': 'S', 'ses.localRecord': 'Local record' - }, - zh: { - 'tab.dashboard': '总览', 'tab.actions': '待办', 'tab.sessions': '证据', - 'status.pending': '待处理', 'status.active': '进行中', 'status.done': '已完成', - 'status.blocked': '受阻', 'status.cancelled': '已取消', - 'filter.review': '待确认', 'filter.all': '全部', - 'dash.firstRun.kicker': '首次使用', - 'dash.firstRun.title': '先导入示例数据', - 'dash.firstRun.body': '目前还没有会话数据。先运行 demo,页面就会出现可浏览的待办与活动。', - 'dash.firstRun.link': '查看快速开始 →', - 'dash.noRecord': '暂无记录', - 'dash.stat.sessions': '会话', - 'dash.stat.recent': '最近', - 'dash.stat.todos': '待办', - 'dash.stat.openWorkbench': '打开工作台', - 'dash.stat.replyQueue': '待回复队列', - 'dash.stat.actionCandidates': '行动候选', - 'dash.stat.pendingActions': '待跟进行动', - 'dash.stat.memories': '记忆', - 'dash.stat.latestVersion': '最新版本', - 'dash.stat.lessons': '经验', - 'dash.stat.lessonsSub': '可整理经验', - 'dash.stat.graphNodes': '关系节点', - 'dash.stat.edges': '连线', - 'dash.stat.status': '状态', - 'dash.stat.toolCalls': '工具调用', - 'dash.stat.tracking': '追踪', - 'dash.stat.functions': '个函数', - 'dash.stat.circuitBreaker': '熔断器', - 'dash.stat.failed': '失败', - 'dash.stat.times': '次', - 'dash.systemResources': '系统资源', - 'dash.alerts': '告警', - 'dash.notes': '备注', - 'dash.recentSessions': '最近会话', - 'dash.emptySessions': '还没有会话数据。先运行 demo 或接入你的工作流。', - 'dash.unnamedSession': '未命名会话', - 'dash.openLatestSyncOf': '打开最新同步 ·', - 'dash.syncs': '次同步', - 'dash.records': '条记录', - 'dash.lastSync': '最近同步', - 'dash.functionMetrics': '函数指标 (OTel)', - 'dash.workers': '工作进程', - 'dash.circuitBreakerDetail': '熔断器详情', - 'dash.semanticMemory': '语义记忆', - 'dash.emptySemantic': '还没有语义记忆,系统会逐步从观察中沉淀。', - 'dash.proceduralMemory': '流程记忆', - 'dash.emptyProcedural': '还没有流程记忆,重复模式会自动提炼。', - 'dash.consolidationStatus': '归并状态', - 'dash.memoryRelations': '记忆关系', - 'act.attn.next': '下一步', 'act.attn.needsWork': '需要处理', 'act.attn.noteworthy': '值得关注', - 'act.prio.high': '重要', 'act.prio.normal': '普通', 'act.prio.low': '不急', - 'act.untitled': '未命名待办', 'act.untitledCandidate': '未命名待办候选', - 'act.metric.todo': 'Todo', 'act.metric.attention': 'Todo', 'act.metric.waiting': 'Todo', 'act.metric.review': 'Todo', 'act.metric.followUp': 'Todo', 'act.metric.active': 'Todo', 'act.metric.done': 'Done', - 'act.attention.reply': '回应', 'act.attention.confirm': '确认', 'act.attention.followUp': '跟进', - 'act.section.awaiting': '需要你回应', 'act.section.review': '需要确认', 'act.section.followUp': '需要跟进', - 'act.section.earlier': '稍早开放事项', - 'act.section.earlierLead': '3-10 天内的开放事项默认折叠,按需展开核对。', - 'act.section.older': '陈旧积压', - 'act.section.olderLead': '这些事项已超过 10 天,可能已经过期。', - 'act.section.legacy': '旧版抽取积压', - 'act.section.legacyLead': '这些卡片来自旧抽取链路,默认隐藏;搜索时仍可找到。', - 'act.section.expand': '展开', - 'act.section.collapse': '收起', - 'act.focus.label': '聚焦', - 'act.focus.current': '当前', - 'act.focus.olderHidden': '条较早已折叠', - 'act.recheck': '来源会话已更新', - 'act.searchPlaceholder': '搜索待办...', - 'act.nToConfirm': '条待确认', 'act.nConfirmed': '件已确认', 'act.refresh': '刷新', - 'act.viewOriginal': '查看原文', 'act.confirm': '确认', 'act.ignore': '忽略', - 'act.from': '来自', 'act.viewSource': '看原文 →', 'act.updated': '更新', - 'act.extract.title': '调用大模型整理最近会话', - 'act.extract.run': '用大模型整理', - 'act.extract.running': '整理中...', - 'act.extract.done': '已整理', - 'act.extract.partial': '部分大模型整理', - 'act.extract.rules': '未走大模型', - 'act.extract.error': '整理失败', - 'act.extract.failedExisting': '抽取失败,已显示现有待办', - 'act.extract.loading': '正在整理待办...', - 'act.extract.starting': '正在从最近会话整理待办...', - 'act.extract.background': '已显示最新待办,后台仍在整理...', - 'act.extract.doneLlm': '大模型整理完成', - 'act.extract.doneMixed': '部分大模型整理完成', - 'act.extract.doneRules': '未走大模型,已用规则整理', - 'act.extract.created': '新增', - 'act.extract.history': '历史', - 'act.extract.discarded': '丢弃', - 'act.extract.cleaned': '清理', - 'act.extract.reason': '原因', - 'act.cleanup.title': '用大模型更新已记录的卡片(来源会话有新进展时)', - 'act.cleanup.run': '更新', - 'act.cleanup.running': '更新中...', - 'act.cleanup.applying': '正在应用更新...', - 'act.cleanup.done': '已更新', - 'act.cleanup.error': '更新失败', - 'act.cleanup.failed': '更新失败,卡片未改动', - 'act.cleanup.clean': '卡片已是最新', - 'act.cleanup.none': '没有需要更新的卡片', - 'act.cleanup.llmUnavailable': '大模型不可用 — 未改动', - 'act.cleanup.confirm': '应用这些更新?', - 'act.cleanup.summary': '更新 {rewritten} · 完成 {completed} · 丢弃 {dropped} · 合并 {merged}', - 'act.cardRefresh.run': '更新', - 'act.cardRefresh.running': '更新中...', - 'act.cardRefresh.done': '已根据来源更新', - 'act.cardRefresh.review': '已转待确认', - 'act.cardRefresh.kept': '未找到更好的卡片', - 'act.cardRefresh.kept.incompleteTitle': '标题不完整,已保留旧卡', - 'act.cardRefresh.kept.evidenceInvalid': '来源证据未匹配,已保留旧卡', - 'act.cardRefresh.kept.lowQuality': '候选过于模糊,已保留旧卡', - 'act.cardRefresh.kept.completedOrHistory': '候选像已完成或过期事项', - 'act.cardRefresh.kept.polluted': '候选像日志,不是待办', - 'act.cardRefresh.error': '更新失败', - 'act.status.complete': '完成', - 'act.status.archive': '归档', - 'act.status.delete': '删除', - 'act.itemsUnit': '件', - 'act.empty.title': '还没有待办', - 'act.empty.lead': '这里会放从会话里整理出的待办、卡住事项和已完成事项。', - 'settings.title': '设置', - 'settings.subtitle': '本机配置会写入用户配置文件,下次整理会直接使用。', - 'settings.close': '关闭', - 'settings.language': '界面语言', - 'settings.extractor': '大模型抽取配置', - 'settings.sinceDays': '回溯天数:只抽取最近 N 天内的会话', - 'settings.maxInteractions': '每会话最多交互记录数(一次用户派发→Agent 回复为一条)', - 'settings.apiKeyKeep': '输入新 API key 覆盖,留空保持不变', - 'settings.apiKeyMissing': '未配置', - 'settings.apiKeyLabel': 'API key:', - 'settings.runtimeReady': '大模型运行环境已就绪', - 'settings.runtimeMissing': '大模型运行环境缺失', - 'settings.save': '保存配置', - 'settings.saving': '保存中...', - 'settings.savedReady': '配置已保存,下次整理会立即使用新配置。', - 'settings.saveFailed': '配置保存失败', - 'act.status.updateFailed': '待办状态更新失败', - 'obs.type.file_read': '读取文件', - 'obs.type.file_write': '写入文件', - 'obs.type.file_edit': '编辑文件', - 'obs.type.command_run': '执行命令', - 'obs.type.search': '搜索', - 'obs.type.web_fetch': '网页获取', - 'obs.type.conversation': '对话', - 'obs.type.error': '错误', - 'obs.type.decision': '决策', - 'obs.type.discovery': '发现', - 'obs.type.subagent': '子代理', - 'obs.type.notification': '通知', - 'obs.type.task': '任务', - 'obs.type.image': '图片', - 'obs.type.other': '其它', - 'obs.summary.localOperation': '执行本地操作', - 'obs.summary.restartService': '重启 AI Todo 服务', - 'obs.summary.startService': '启动 AI Todo 服务', - 'obs.summary.openPreview': '打开 AI Todo 预览', - 'obs.summary.checkService': '检查服务状态', - 'obs.summary.verifyPageFix': '验证页面修复是否生效', - 'obs.summary.viewFixLog': '查看修复日志', - 'obs.summary.checkBrowserAutomation': '检查浏览器自动化工具是否可用', - 'obs.summary.checkProcess': '检查本地服务进程', - 'obs.summary.organizeLocalFiles': '整理本地文件', - 'obs.summary.readLocalServiceData': '读取本地服务数据', - 'obs.summary.viewLocalFiles': '查看本地文件内容', - 'obs.summary.runProjectScript': '运行项目脚本', - 'obs.summary.checkCodeVersion': '检查或更新代码版本', - 'obs.summary.runLocalCommand': '执行本地命令', - 'obs.output.noText': '这一步已执行,没有返回额外文本。', - 'obs.output.serviceOk': '结果正常,服务已经可用。', - 'obs.output.npxMissing': '检查结果:当前环境没有可用的 npx。', - 'obs.output.npxOk': '检查结果:浏览器自动化工具依赖可用。', - 'obs.output.pageFixOk': '检查结果:页面里已经包含对应修复逻辑。', - 'obs.output.error': '执行返回了错误信息,需要继续排查。', - 'obs.output.readService': '已读取本地服务返回的数据。', - 'obs.output.viewFiles': '已查看本地文件内容,用于确认当前状态。', - 'obs.output.done': '这一步已完成,并返回了执行结果。', - 'obs.display.promptSubmit': '用户提出需求', - 'obs.display.agentMessage': '助手回应', - 'obs.display.updatePlan': '更新执行计划', - 'obs.display.updatePlanBody': '已更新任务计划。', - 'obs.display.applyPatch': '更新本地文件', - 'obs.display.applyPatchBody': '已应用代码修改。', - 'obs.display.toolTrace': '这一步已记录为结构化工具事件。', - 'episode.type.file_read': '读取资料', - 'episode.type.file_write': '写入文件', - 'episode.type.file_edit': '修改文件', - 'episode.type.command_run': '本地操作', - 'episode.type.search': '搜索定位', - 'episode.type.web_fetch': '网页资料', - 'episode.type.conversation': '对话推进', - 'episode.type.error': '异常排查', - 'episode.type.decision': '决策记录', - 'episode.type.discovery': '发现线索', - 'episode.type.subagent': '协作任务', - 'episode.type.notification': '提醒', - 'episode.type.task': '任务推进', - 'episode.type.other': '其它', - 'episode.kind.user_need': '用户需求', - 'episode.kind.bugfix': '修复记录', - 'episode.kind.research': '项目研究', - 'episode.kind.file_work': '文件整理', - 'episode.kind.important': '重要片段', - 'episode.kind.work': '工作推进', - 'episode.workSegment': '工作片段', - 'episode.record': '记录', - 'episode.bodyPrefix': '这一段主要包含 ', - 'episode.bodySuffix': ',已从底层记录整理成可读片段。', - 'project.uncategorized': '未归类', - 'project.all': '全部项目', - 'project.browser': '浏览器', - 'project.demo': '演示数据', - 'source.local': '本地记录', - 'source.agentMarked': 'Agent 已写入来源标记', - 'source.demoNote': '来自 demo 命令,不代表你的真实工作', - 'source.importedClaude': 'Claude Code 历史导入', - 'source.importedClaudeNote': '从本地 JSONL 历史记录导入', - 'source.pathInferred': '按项目路径识别', - 'source.unknownAgentNote': '这条会话没有写明来自哪个 Agent', - 'agent.avatarAlt': '头像', - 'ses.noRecordId': '无记录 ID', - 'tab.memories': '记忆', 'tab.lessons': '经验', 'tab.graph': '图谱', 'tab.timeline': '时间线', 'tab.activity': '实时', 'tab.profile': '档案', 'tab.audit': '审计', 'tab.replay': '回放', 'tab.crystals': '结晶', - 'ses.emptyNeedRetry': '暂时没有读到会话,请确认本地服务已启动后重试。', - 'ses.empty': '暂无会话', 'ses.retry': '重试加载', 'ses.allSessions': '全部会话', - 'ses.heroTitle': '会话', - 'ses.heroNote': '浏览器和本地 Agent 的会话会汇入这里,可以按文件夹或来源查看。', - 'ses.unitSessions': '段会话', 'ses.unitFolders': '个文件夹', 'ses.unitSources': '个来源', 'ses.unitRecords': '条记录', 'ses.recordsUnit': '条', - 'ses.groupModeAria': '会话分组方式', 'ses.byFolder': '按文件夹', 'ses.bySource': '按来源', - 'ses.groupAria': '会话分组', 'ses.source': '来源', 'ses.folder': '文件夹', - 'ses.emptyFolder': '这个文件夹下暂时没有会话。', 'ses.emptySource': '这个来源下暂时没有会话。', - 'ses.noPreview': '暂无预览,点击查看完整过程。', - 'ses.avatarFallback': '会', 'ses.localRecord': '本地记录' - } - }; - var I18N_LANG = 'en'; - function t(key, fallback) { - var table = I18N_MESSAGES[I18N_LANG] || {}; - if (Object.prototype.hasOwnProperty.call(table, key)) return table[key]; - if (Object.prototype.hasOwnProperty.call(I18N_MESSAGES.en, key)) return I18N_MESSAGES.en[key]; - return fallback != null ? fallback : key; - } - function statusLabel(status) { return t('status.' + status, status); } - /* i18n-core:end */ - (function () { - try { - var lang = new URLSearchParams(window.location.search).get('lang') || localStorage.getItem('agentmemory-lang'); - if (lang === 'en' || lang === 'zh') I18N_LANG = lang; - } catch (e) { /* no URL access — keep default */ } - })(); - function applyI18n(root) { - var scope = root || document; - scope.querySelectorAll('[data-i18n]').forEach(function (el) { - el.textContent = t(el.getAttribute('data-i18n')); - }); - var gear = document.getElementById('settings-gear'); - if (gear) { - gear.setAttribute('aria-label', t('settings.title')); - gear.setAttribute('title', t('settings.title')); - } - } - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', function () { applyI18n(); }); - } else { - applyI18n(); - } - // === /i18n base === - function observationTypeLabel(type) { - var key = String(type || 'other'); - return t('obs.type.' + key, key.replace(/_/g, ' ')); - } diff --git a/src/viewer/parts/app/08-state-expert.js b/src/viewer/parts/app/08-state-expert.js deleted file mode 100644 index bbb009d9..00000000 --- a/src/viewer/parts/app/08-state-expert.js +++ /dev/null @@ -1,84 +0,0 @@ - var CB_STATE_COLORS = { closed: 'badge-green', open: 'badge-red', 'half-open': 'badge-yellow' }; - var TAB_IDS = ['dashboard', 'actions', 'sessions']; - var TAB_REDIRECTS = { memories: 'dashboard', lessons: 'dashboard', activity: 'sessions', graph: 'dashboard', profile: 'dashboard', audit: 'dashboard', replay: 'dashboard', timeline: 'sessions', crystals: 'dashboard' }; - // 专家模式:默认三栏干净;开启后在导航末尾恢复被砍/被隐藏的视图(后端资产不丢)。 - // 这些视图的 view 容器与渲染函数都还在文件里,本步只是放行入口。 - var EXPERT_TABS = [ - { id: 'memories', label: 'tab.memories' }, - { id: 'lessons', label: 'tab.lessons' }, - { id: 'graph', label: 'tab.graph' }, - { id: 'timeline', label: 'tab.timeline' }, - { id: 'activity', label: 'tab.activity' }, - { id: 'profile', label: 'tab.profile' }, - { id: 'audit', label: 'tab.audit' }, - { id: 'replay', label: 'tab.replay' }, - { id: 'crystals', label: 'tab.crystals' } - ]; - function expertModeEnabled() { - try { - var q = new URLSearchParams(window.location.search); - if (q.get('expert') === '1') return true; - if (q.get('expert') === '0') return false; - } catch (_) {} - try { return localStorage.getItem('viewer_expert_mode') === '1'; } catch (_) { return false; } - } - function setExpertMode(on) { - try { localStorage.setItem('viewer_expert_mode', on ? '1' : '0'); } catch (_) {} - } - // 把被隐藏视图的按钮渲染进导航(仅专家模式)。幂等:每次先清掉旧的再按需重建。 - function renderExpertTabs() { - var bar = document.getElementById('tab-bar'); - if (!bar) return; - var on = expertModeEnabled(); - var existing = document.getElementById('tab-expert-group'); - if (existing && existing.parentNode) existing.parentNode.removeChild(existing); - var toggle = document.getElementById('expert-toggle'); - if (toggle) toggle.setAttribute('aria-pressed', on ? 'true' : 'false'); - if (!on) return; - var main = bar.querySelector('.tab-main'); - if (!main) return; - var group = document.createElement('span'); - group.id = 'tab-expert-group'; - group.className = 'tab-expert-group'; - EXPERT_TABS.forEach(function(tab) { - var b = document.createElement('button'); - b.setAttribute('data-tab', tab.id); - b.className = 'tab-expert-btn'; - b.textContent = t(tab.label); - group.appendChild(b); - }); - main.appendChild(group); - } - function toggleExpertMode() { - var next = !expertModeEnabled(); - setExpertMode(next); - renderExpertTabs(); - // 关闭时若当前停在被隐藏视图,折回总览 - if (!next && EXPERT_TABS.some(function(t) { return t.id === state.activeTab; })) { - switchTab('dashboard'); - } - } - - var LOCAL_SKILLS = [{"name":"academic-cv-builder","root":"Agents","path":"~/.agents/skills/academic-cv-builder/SKILL.md"},{"name":"career-changer-translator","root":"Agents","path":"~/.agents/skills/career-changer-translator/SKILL.md"},{"name":"cover-letter-generator","root":"Agents","path":"~/.agents/skills/cover-letter-generator/SKILL.md"},{"name":"creative-portfolio-resume","root":"Agents","path":"~/.agents/skills/creative-portfolio-resume/SKILL.md"},{"name":"executive-resume-writer","root":"Agents","path":"~/.agents/skills/executive-resume-writer/SKILL.md"},{"name":"find-skills","root":"Agents","path":"~/.agents/skills/find-skills/SKILL.md"},{"name":"guizang-social-card-skill","root":"Agents","path":"~/.agents/skills/guizang-social-card-skill/SKILL.md"},{"name":"huashu-design","root":"Agents","path":"~/.agents/skills/huashu-design/SKILL.md"},{"name":"interview-prep-generator","root":"Agents","path":"~/.agents/skills/interview-prep-generator/SKILL.md"},{"name":"job-description-analyzer","root":"Agents","path":"~/.agents/skills/job-description-analyzer/SKILL.md"},{"name":"linkedin-profile-optimizer","root":"Agents","path":"~/.agents/skills/linkedin-profile-optimizer/SKILL.md"},{"name":"offer-comparison-analyzer","root":"Agents","path":"~/.agents/skills/offer-comparison-analyzer/SKILL.md"},{"name":"pdf","root":"Agents","path":"~/.agents/skills/pdf/SKILL.md"},{"name":"portfolio-case-study-writer","root":"Agents","path":"~/.agents/skills/portfolio-case-study-writer/SKILL.md"},{"name":"post-to-xhs","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/post-to-xhs/SKILL.md"},{"name":"reference-list-builder","root":"Agents","path":"~/.agents/skills/reference-list-builder/SKILL.md"},{"name":"resume-ats-optimizer","root":"Agents","path":"~/.agents/skills/resume-ats-optimizer/SKILL.md"},{"name":"resume-bullet-writer","root":"Agents","path":"~/.agents/skills/resume-bullet-writer/SKILL.md"},{"name":"resume-formatter","root":"Agents","path":"~/.agents/skills/resume-formatter/SKILL.md"},{"name":"resume-quantifier","root":"Agents","path":"~/.agents/skills/resume-quantifier/SKILL.md"},{"name":"resume-section-builder","root":"Agents","path":"~/.agents/skills/resume-section-builder/SKILL.md"},{"name":"resume-tailor","root":"Agents","path":"~/.agents/skills/resume-tailor/SKILL.md"},{"name":"resume-version-manager","root":"Agents","path":"~/.agents/skills/resume-version-manager/SKILL.md"},{"name":"salary-negotiation-prep","root":"Agents","path":"~/.agents/skills/salary-negotiation-prep/SKILL.md"},{"name":"setup-xhs-mcp","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/setup-xhs-mcp/SKILL.md"},{"name":"social-media-marketing","root":"Agents","path":"~/.agents/skills/social-media-marketing/SKILL.md"},{"name":"tech-resume-optimizer","root":"Agents","path":"~/.agents/skills/tech-resume-optimizer/SKILL.md"},{"name":"weread-skills","root":"Agents","path":"~/.agents/skills/weread-skills/SKILL.md"},{"name":"x-twitter-growth","root":"Agents","path":"~/.agents/skills/x-twitter-growth/SKILL.md"},{"name":"xhs-content-plan","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-content-plan/SKILL.md"},{"name":"xhs-explore","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-explore/SKILL.md"},{"name":"xhs-interact","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-interact/SKILL.md"},{"name":"xhs-login","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-login/SKILL.md"},{"name":"xhs-profile","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-profile/SKILL.md"},{"name":"xhs-search","root":"Agents","path":"~/.agents/skills/xiaohongshu/skills/xhs-search/SKILL.md"},{"name":"xiaohongshu","root":"Agents","path":"~/.agents/skills/xiaohongshu/SKILL.md"},{"name":"academic-cv-builder","root":"Codex","path":"~/.codex/skills/academic-cv-builder/SKILL.md"},{"name":"aihot","root":"Codex","path":"~/.codex/skills/aihot/SKILL.md"},{"name":"andrej-karpathy-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/andrej-karpathy-perspective/SKILL.md"},{"name":"buddyup-next","root":"Codex","path":"~/.codex/skills/buddyup-next/SKILL.md"},{"name":"buddyup-study-abroad-assistant","root":"Codex","path":"~/.codex/skills/buddyup-study-abroad-assistant/SKILL.md"},{"name":"buddyup-xiaohongshu-growth","root":"Codex","path":"~/.codex/skills/buddyup-xiaohongshu-growth/SKILL.md"},{"name":"cai-life-skill","root":"Codex","path":"~/.codex/skills/cai-life-skill/SKILL.md"},{"name":"career-changer-translator","root":"Codex","path":"~/.codex/skills/career-changer-translator/SKILL.md"},{"name":"chatgpt-apps","root":"Codex","path":"~/.codex/skills/chatgpt-apps/SKILL.md"},{"name":"cli-creator","root":"Codex","path":"~/.codex/skills/cli-creator/SKILL.md"},{"name":"cocoloop-main","root":"Codex","path":"~/.codex/skills/cocoloop-main/SKILL.md"},{"name":"cover-letter-generator","root":"Codex","path":"~/.codex/skills/cover-letter-generator/SKILL.md"},{"name":"creative-portfolio-resume","root":"Codex","path":"~/.codex/skills/creative-portfolio-resume/SKILL.md"},{"name":"douban-sync-skill","root":"Codex","path":"~/.codex/skills/douban-sync-skill/SKILL.md"},{"name":"elon-musk-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/elon-musk-perspective/SKILL.md"},{"name":"executive-resume-writer","root":"Codex","path":"~/.codex/skills/executive-resume-writer/SKILL.md"},{"name":"feishu-research-docs","root":"Codex","path":"~/.codex/skills/feishu-research-docs/SKILL.md"},{"name":"feynman-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/feynman-perspective/SKILL.md"},{"name":"figma-code-connect-components","root":"Codex","path":"~/.codex/skills/figma-code-connect-components/SKILL.md"},{"name":"figma-create-design-system-rules","root":"Codex","path":"~/.codex/skills/figma-create-design-system-rules/SKILL.md"},{"name":"figma","root":"Codex","path":"~/.codex/skills/figma/SKILL.md"},{"name":"gh-address-comments","root":"Codex","path":"~/.codex/skills/gh-address-comments/SKILL.md"},{"name":"hv-analysis","root":"Codex","path":"~/.codex/skills/hv-analysis/SKILL.md"},{"name":"ilya-sutskever-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/ilya-sutskever-perspective/SKILL.md"},{"name":"imagegen","root":"Codex","path":"~/.codex/skills/.system/imagegen/SKILL.md"},{"name":"interview-prep-generator","root":"Codex","path":"~/.codex/skills/interview-prep-generator/SKILL.md"},{"name":"job-description-analyzer","root":"Codex","path":"~/.codex/skills/job-description-analyzer/SKILL.md"},{"name":"kevin-kelly-perspective","root":"Codex","path":"~/.codex/skills/kevin-kelly-perspective/SKILL.md"},{"name":"khazix-writer","root":"Codex","path":"~/.codex/skills/khazix-writer/SKILL.md"},{"name":"laws-of-ux-2","root":"Codex","path":"~/.codex/skills/laws-of-ux-2/SKILL.md"},{"name":"linkedin-profile-optimizer","root":"Codex","path":"~/.codex/skills/linkedin-profile-optimizer/SKILL.md"},{"name":"mrbeast-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/mrbeast-perspective/SKILL.md"},{"name":"munger-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/munger-perspective/SKILL.md"},{"name":"naval-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/naval-perspective/SKILL.md"},{"name":"neat-freak","root":"Codex","path":"~/.codex/skills/neat-freak/SKILL.md"},{"name":"nuwa-skill","root":"Codex","path":"~/.codex/skills/nuwa-skill/SKILL.md"},{"name":"offer-comparison-analyzer","root":"Codex","path":"~/.codex/skills/offer-comparison-analyzer/SKILL.md"},{"name":"openai-docs","root":"Codex","path":"~/.codex/skills/.system/openai-docs/SKILL.md"},{"name":"paul-graham-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/paul-graham-perspective/SKILL.md"},{"name":"playwright","root":"Codex","path":"~/.codex/skills/playwright/SKILL.md"},{"name":"plugin-creator","root":"Codex","path":"~/.codex/skills/.system/plugin-creator/SKILL.md"},{"name":"portfolio-case-study-writer","root":"Codex","path":"~/.codex/skills/portfolio-case-study-writer/SKILL.md"},{"name":"proactive-everme","root":"Codex","path":"~/.codex/skills/proactive-everme/SKILL.md"},{"name":"reference-list-builder","root":"Codex","path":"~/.codex/skills/reference-list-builder/SKILL.md"},{"name":"resume-ats-optimizer","root":"Codex","path":"~/.codex/skills/resume-ats-optimizer/SKILL.md"},{"name":"resume-bullet-writer","root":"Codex","path":"~/.codex/skills/resume-bullet-writer/SKILL.md"},{"name":"resume-formatter","root":"Codex","path":"~/.codex/skills/resume-formatter/SKILL.md"},{"name":"resume-quantifier","root":"Codex","path":"~/.codex/skills/resume-quantifier/SKILL.md"},{"name":"resume-section-builder","root":"Codex","path":"~/.codex/skills/resume-section-builder/SKILL.md"},{"name":"resume-tailor","root":"Codex","path":"~/.codex/skills/resume-tailor/SKILL.md"},{"name":"resume-version-manager","root":"Codex","path":"~/.codex/skills/resume-version-manager/SKILL.md"},{"name":"salary-negotiation-prep","root":"Codex","path":"~/.codex/skills/salary-negotiation-prep/SKILL.md"},{"name":"self-improvement","root":"Codex","path":"~/.codex/skills/self-improvement/SKILL.md"},{"name":"skill-creator","root":"Codex","path":"~/.codex/skills/.system/skill-creator/SKILL.md"},{"name":"skill-installer","root":"Codex","path":"~/.codex/skills/.system/skill-installer/SKILL.md"},{"name":"speech","root":"Codex","path":"~/.codex/skills/speech/SKILL.md"},{"name":"steve-jobs-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/steve-jobs-perspective/SKILL.md"},{"name":"storage-analyzer","root":"Codex","path":"~/.codex/skills/storage-analyzer/SKILL.md"},{"name":"sun-yuchen-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/sun-yuchen-perspective/SKILL.md"},{"name":"taleb-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/taleb-perspective/SKILL.md"},{"name":"taught-master-applications","root":"Codex","path":"~/.codex/skills/taught-master-applications/SKILL.md"},{"name":"taught-master-applications","root":"Codex","path":"~/.codex/skills/taught-master-applications/taught-master-applications/SKILL.md"},{"name":"tech-resume-optimizer","root":"Codex","path":"~/.codex/skills/tech-resume-optimizer/SKILL.md"},{"name":"trump-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/trump-perspective/SKILL.md"},{"name":"x-mastery-mentor","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/x-mastery-mentor/SKILL.md"},{"name":"zhang-yiming-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/zhang-yiming-perspective/SKILL.md"},{"name":"zhangxuefeng-perspective","root":"Codex","path":"~/.codex/skills/nuwa-skill/examples/zhangxuefeng-perspective/SKILL.md"},{"name":"agentmemory-import","root":"Plugin","path":"~/.codex/plugins/cache/agentmemory-lab/agentmemory-lab/0.1.0/skills/agentmemory-import/SKILL.md"},{"name":"agentmemory-understand","root":"Plugin","path":"~/.codex/plugins/cache/agentmemory-lab/agentmemory-lab/0.1.0/skills/agentmemory-understand/SKILL.md"},{"name":"agentmemory","root":"Plugin","path":"~/.codex/plugins/cache/agentmemory-lab/agentmemory-lab/0.1.0/skills/agentmemory/SKILL.md"},{"name":"audit","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/audit/SKILL.md"},{"name":"control-in-app-browser","root":"Plugin","path":"~/.codex/plugins/cache/openai-bundled/browser/26.601.21317/skills/control-in-app-browser/SKILL.md"},{"name":"design-qa","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/design-qa/SKILL.md"},{"name":"documents","root":"Plugin","path":"~/.codex/plugins/cache/openai-primary-runtime/documents/26.601.10930/skills/documents/SKILL.md"},{"name":"everme-memory","root":"Plugin","path":"~/.codex/plugins/cache/everme/everme/0.4.0/skills/everme-memory/SKILL.md"},{"name":"figma-code-connect","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-code-connect/SKILL.md"},{"name":"figma-create-new-file","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-create-new-file/SKILL.md"},{"name":"figma-generate-design","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-generate-design/SKILL.md"},{"name":"figma-generate-diagram","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-generate-diagram/SKILL.md"},{"name":"figma-generate-library","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-generate-library/SKILL.md"},{"name":"figma-use-figjam","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-use-figjam/SKILL.md"},{"name":"figma-use-slides","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-use-slides/SKILL.md"},{"name":"figma-use","root":"Plugin","path":"~/.codex/plugins/cache/openai-curated/figma/2abb1c44/skills/figma-use/SKILL.md"},{"name":"get-context","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/get-context/SKILL.md"},{"name":"ideate","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/ideate/SKILL.md"},{"name":"image-to-code","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/image-to-code/SKILL.md"},{"name":"index","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/index/SKILL.md"},{"name":"presentations","root":"Plugin","path":"~/.codex/plugins/cache/openai-primary-runtime/presentations/26.601.10930/skills/presentations/SKILL.md"},{"name":"prototype","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/prototype/SKILL.md"},{"name":"research","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/research/SKILL.md"},{"name":"share","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/share/SKILL.md"},{"name":"spreadsheets","root":"Plugin","path":"~/.codex/plugins/cache/openai-primary-runtime/spreadsheets/26.601.10930/skills/spreadsheets/SKILL.md"},{"name":"understand-chat","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-chat/SKILL.md"},{"name":"understand-chat","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-chat/SKILL.md"},{"name":"understand-dashboard","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-dashboard/SKILL.md"},{"name":"understand-dashboard","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-dashboard/SKILL.md"},{"name":"understand-diff","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-diff/SKILL.md"},{"name":"understand-diff","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-diff/SKILL.md"},{"name":"understand-domain","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-domain/SKILL.md"},{"name":"understand-domain","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-domain/SKILL.md"},{"name":"understand-explain","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-explain/SKILL.md"},{"name":"understand-explain","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-explain/SKILL.md"},{"name":"understand-knowledge","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-knowledge/SKILL.md"},{"name":"understand-knowledge","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-knowledge/SKILL.md"},{"name":"understand-onboard","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand-onboard/SKILL.md"},{"name":"understand-onboard","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand-onboard/SKILL.md"},{"name":"understand","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/plugin-backup-Q98O9D/understand-anything/2.7.5/skills/understand/SKILL.md"},{"name":"understand","root":"Plugin","path":"~/.codex/plugins/cache/understand-anything/understand-anything/2.7.6/skills/understand/SKILL.md"},{"name":"url-to-code","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/url-to-code/SKILL.md"},{"name":"user-context","root":"Plugin","path":"~/.codex/plugins/cache/role-specific-plugins/product-design/0.1.41/skills/user-context/SKILL.md"}]; - - var state = { - activeTab: 'dashboard', - dashboard: { loaded: false, health: null, sessions: [], memories: [], actions: [], actionReviews: [], inboxAwaiting: [], graphStats: null, recentAudit: [], lessons: [], crystals: [], delivery: null }, - dashboardRefresh: { enabled: true, intervalMs: 30000 }, - graph: { loaded: false, nodes: [], edges: [], stats: null, filters: {}, selectedNode: null }, - memories: { loaded: false, items: [], reviewItems: [], search: '', typeFilter: '', sourceFilter: '' }, - timeline: { loaded: false, observations: [], sessionId: '', projectKey: '', sessions: [], minImportance: 0, page: 0, pageSize: 50, mode: 'episodes', episodeFilter: 'all', expandedEpisodes: {} }, - sessions: { loaded: false, items: [], selectedId: null, groupMode: 'folder', folderKey: 'all', sourceKey: 'all', warnings: [], highlightsById: {}, detailSectionsById: {}, detailCacheById: {}, stale: false, requestSeq: 0, detailRequestSeq: 0, pendingHighlightObsId: null, previewExpandedById: {} }, - audit: { loaded: false, entries: [], opFilter: '' }, - activity: { loaded: false, observations: [], sessions: [], typeFilter: '', loadingPhase: '', warnings: [] }, - lessons: { loaded: false, items: [], search: '', skillSearch: '', skillRootFilter: 'all', mode: 'explicit', projects: [] }, - actions: { loaded: false, items: [], reviewItems: [], frontier: [], statusFilter: '', search: '', doneExpanded: false, earlierOpenExpanded: false, olderBacklogExpanded: false, extractStatus: '', extractMessage: '', extractInFlight: false, cardRefreshInFlight: {}, cardRefreshNotice: '', stale: false, config: null, configSaving: false, configDraft: {}, forceNextExtract: false }, - inbox: { loaded: false, items: [], awaitingItems: [], answeredItems: [], dismissedItems: [], replyingId: null, pendingById: {}, briefingExpanded: false, answeredExpanded: false }, - crystals: { loaded: false, items: [], search: '', lessonMap: {} }, - profile: { loaded: false, projects: [], selectedProject: '', data: null }, - replay: { loaded: false, sessions: [], selectedId: '', timeline: null, cursor: 0, playing: false, speed: 1, timer: null, startAt: 0, offsetAt: 0 }, - flagsConfig: null, - flagsDismissed: {}, - settings: { open: false }, - ws: null - }; diff --git a/src/viewer/parts/app/10-html-safe.js b/src/viewer/parts/app/10-html-safe.js deleted file mode 100644 index 1b639616..00000000 Binary files a/src/viewer/parts/app/10-html-safe.js and /dev/null differ diff --git a/src/viewer/parts/app/20-format-utils.js b/src/viewer/parts/app/20-format-utils.js deleted file mode 100644 index 1d942beb..00000000 --- a/src/viewer/parts/app/20-format-utils.js +++ /dev/null @@ -1,37 +0,0 @@ - function formatTime(ts) { - if (!ts) return ''; - try { return new Date(ts).toLocaleString(); } catch { return ts; } - } - function shortTime(ts) { - if (!ts) return ''; - try { return new Date(ts).toLocaleTimeString(); } catch { return ts; } - } - // Absolute time down to the hour only (year-month-day hour, no minutes/seconds). - function absoluteHour(ts) { - try { - var d = new Date(ts); - if (isNaN(d.getTime())) return ''; - var p = function(n) { return (n < 10 ? '0' : '') + n; }; - var base = d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' ' + p(d.getHours()); - return I18N_LANG === 'zh' ? base + '时' : base + ':00'; - } catch { return ''; } - } - // Relative time without minute/second granularity; falls back to absoluteHour - // for anything older than ~30 days. - function relativeTime(ts) { - if (!ts) return ''; - try { - var diff = Date.now() - new Date(ts).getTime(); - if (!isFinite(diff)) return absoluteHour(ts); - if (diff < 0) diff = 0; - var hour = 3600000, day = 86400000, zh = I18N_LANG === 'zh'; - if (diff < hour) return zh ? '刚刚' : 'just now'; - if (diff < day) { var h = Math.floor(diff / hour); return zh ? h + '小时前' : h + 'h ago'; } - if (diff < 30 * day) { var dd = Math.floor(diff / day); return zh ? dd + '天前' : dd + 'd ago'; } - return absoluteHour(ts); - } catch { return absoluteHour(ts); } - } - function truncate(s, n) { - if (!s) return ''; - return s.length > n ? s.slice(0, n) + '...' : s; - } diff --git a/src/viewer/parts/app/40-data-helpers.js b/src/viewer/parts/app/40-data-helpers.js deleted file mode 100644 index 3772bac7..00000000 --- a/src/viewer/parts/app/40-data-helpers.js +++ /dev/null @@ -1,1003 +0,0 @@ - function cleanSessionPreview(text) { - var t = String(text || '').trim(); - if (!t) return ''; - var noisyStarts = [ - '# AGENTS.md instructions', - '', - 'Automation:', - 'Response MUST end with', - 'You are Codex', - 'Filesystem sandboxing defines' - ]; - for (var i = 0; i < noisyStarts.length; i++) { - if (t.indexOf(noisyStarts[i]) === 0) return ''; - } - t = t.replace(/^# In app browser:[\s\S]*?## My request for Codex:\s*/m, '').trim(); - t = t.replace(/^# Browser comments:[\s\S]*?## My request for Codex:\s*/m, '').trim(); - t = t.replace(/^The next image is untrusted page evidence[\s\S]*?instructions\.\s*/m, '').trim(); - return t; - } - function normalizePreviewText(text) { - return cleanSessionPreview(text).replace(/\s+/g, ' ').trim(); - } - function compactSessionTitle(text) { - var t = normalizePreviewText(text); - if (!t) return ''; - t = t.replace(/https?:\/\/\S+/g, '').trim(); - t = t.replace(/\[[^\]]{20,}\]\([^)]+\)/g, '').trim(); - t = t.replace(/[((][^()()]{24,}[))]/g, '').trim(); - t = t.replace(/^#+\s*/, '').replace(/\*\*/g, '').replace(/^\d+[.、]\s*/, '').trim(); - t = t.replace(/\s+/g, ' '); - if (t.length > 44) { - var cut = t.slice(0, 44); - var stop = Math.max(cut.lastIndexOf('。'), cut.lastIndexOf(';'), cut.lastIndexOf(','), cut.lastIndexOf(' ')); - if (stop > 14) cut = cut.slice(0, stop); - t = cut + '...'; - } - return t; - } - function sessionTitleText(s) { - return compactSessionTitle(s && (s.title || s.firstPrompt)) || sessionDisplayName(s); - } - function sessionBodyPreview(s, title) { - var candidates = [s && s.summary, s && s.latestPrompt, s && s.firstPrompt]; - var titleText = normalizePreviewText(title); - for (var i = 0; i < candidates.length; i++) { - var preview = normalizePreviewText(candidates[i]); - if (!preview) continue; - if (titleText && preview === titleText) continue; - if (titleText && preview.indexOf(titleText) === 0) { - preview = preview.slice(titleText.length).replace(/^[\s::,,.。;;-]+/, '').trim(); - } - if (preview && preview !== titleText) return preview; - } - return ''; - } - function parseJsonObject(text) { - var raw = String(text || '').trim(); - if (!raw || raw[0] !== '{') return null; - try { return JSON.parse(raw); } catch (e) { return null; } - } - function splitCommandNarrative(text) { - var raw = String(text || '').trim(); - if (!raw) return { command: '', output: '' }; - var pipeIndex = raw.indexOf(' | '); - var left = pipeIndex >= 0 ? raw.slice(0, pipeIndex).trim() : raw; - var right = pipeIndex >= 0 ? raw.slice(pipeIndex + 3).trim() : ''; - var obj = parseJsonObject(left); - return { command: obj && obj.command ? String(obj.command) : '', output: right }; - } - function extractCommandText(o) { - var rawNarrative = String(o && o.narrative || '').trim(); - var rawSubtitle = String(o && o.subtitle || '').trim(); - var first = rawNarrative.indexOf(' | ') >= 0 ? splitCommandNarrative(rawNarrative) : splitCommandNarrative(rawSubtitle); - if (first.command || first.output) return first; - var second = splitCommandNarrative(rawNarrative); - if (second.command || second.output) return second; - var ti = o && o.toolInput; - if (ti && typeof ti === 'object' && ti.command) return { command: String(ti.command), output: String(o && o.toolOutput || '') }; - return { command: '', output: '' }; - } - function normalizedToolTraceName(value) { - return String(value || '').trim().toLowerCase().replace(/\s+/g, '_'); - } - function isJsonLikeText(text) { - var trimmed = String(text || '').trim(); - if (!trimmed || (trimmed[0] !== '{' && trimmed[0] !== '[')) return false; - try { - JSON.parse(trimmed); - return true; - } catch (e) { - return /^[{\[]/.test(trimmed); - } - } - function looksToolTraceDisplayText(text) { - var trimmed = String(text || '').trim(); - if (!trimmed) return false; - var lower = trimmed.toLowerCase(); - return isJsonLikeText(trimmed) || /"plan"\s*:|"command"\s*:|toolinput|tooloutput|function_id/.test(lower); - } - function commandHumanSummary(command) { - var c = String(command || '').trim(); - if (!c) return t('obs.summary.localOperation'); - if (c.indexOf('ensure-agentmemory-live.mjs') >= 0 && c.indexOf('--restart') >= 0) return t('obs.summary.restartService'); - if (c.indexOf('ensure-agentmemory-live.mjs') >= 0) return t('obs.summary.startService'); - if (/^open\s+['"]?https?:\/\/127\.0\.0\.1:3114/.test(c)) return t('obs.summary.openPreview'); - if (c.indexOf('agentmemory/livez') >= 0 || c.indexOf('/health') >= 0) return t('obs.summary.checkService'); - if (c.indexOf('sessionBodyPreview') >= 0 || c.indexOf('observationDisplay') >= 0) return t('obs.summary.verifyPageFix'); - if (c.indexOf('bugfix-log') >= 0 && /sed|cat|ls/.test(c)) return t('obs.summary.viewFixLog'); - if (/command -v npx/.test(c)) return t('obs.summary.checkBrowserAutomation'); - if (/^(ps|lsof|netstat)\b/.test(c) || c.indexOf(' ps ') >= 0 || c.indexOf('lsof ') >= 0) return t('obs.summary.checkProcess'); - if (/^mkdir\b|^mv\b|^cp\b|^node <<|cleanup|重复旧版本|_待确认清理/.test(c)) return t('obs.summary.organizeLocalFiles'); - if (/^curl\b/.test(c)) return t('obs.summary.readLocalServiceData'); - if (/^sed\b|^rg\b|^grep\b|^find\b|^ls\b|^nl\b/.test(c)) return t('obs.summary.viewLocalFiles'); - if (/^(npm|pnpm|bun|yarn)\b/.test(c)) return t('obs.summary.runProjectScript'); - if (/^(git)\b/.test(c)) return t('obs.summary.checkCodeVersion'); - return t('obs.summary.runLocalCommand'); - } - function commandOutputSummary(output, command) { - var out = String(output || '').trim(); - var c = String(command || ''); - if (!out) return t('obs.output.noText'); - if (out.indexOf('"ok": true') >= 0 || out.indexOf('"status":"ok"') >= 0 || out.indexOf('"status": "ok"') >= 0) return t('obs.output.serviceOk'); - if (out.indexOf('npx-missing') >= 0) return t('obs.output.npxMissing'); - if (out.indexOf('npx-ok') >= 0) return t('obs.output.npxOk'); - if (out.indexOf('sessionBodyPreview') >= 0 || out.indexOf('observationDisplay') >= 0) return t('obs.output.pageFixOk'); - if (out.indexOf('error') >= 0 || out.indexOf('failed') >= 0 || out.indexOf('Error:') >= 0) return t('obs.output.error'); - if (/^curl\b/.test(c)) return t('obs.output.readService'); - if (/^sed\b|^rg\b|^grep\b|^find\b|^ls\b|^nl\b/.test(c)) return t('obs.output.viewFiles'); - return t('obs.output.done'); - } - function observationDisplay(o) { - var baseTitle = normalizePreviewText(o && (o.title || o.toolName || (o.hookType ? o.hookType.replace(/_/g, ' ') : 'Observation'))); - var rawSubtitle = normalizePreviewText(o && o.subtitle); - var rawNarrative = normalizePreviewText(o && o.narrative); - var commandInfo = extractCommandText(o); - var type = o && (o.type || o.hookType || ''); - var toolName = normalizedToolTraceName(o && (o.toolName || o.title || o.hookType)); - var title = baseTitle || 'Observation'; - var subtitle = rawSubtitle; - var body = rawNarrative; - if (title === 'prompt_submit') title = t('obs.display.promptSubmit'); - if (title === 'agent_message') title = t('obs.display.agentMessage'); - if (toolName === 'update_plan') { - title = t('obs.display.updatePlan'); - subtitle = ''; - body = t('obs.display.updatePlanBody'); - } else if (toolName === 'apply_patch') { - title = t('obs.display.applyPatch'); - subtitle = ''; - body = t('obs.display.applyPatchBody'); - } else if ((type === 'command_run' || toolName === 'bash' || toolName === 'exec_command') && commandInfo.command) { - title = commandHumanSummary(commandInfo.command); - subtitle = ''; - body = commandOutputSummary(commandInfo.output, commandInfo.command); - } else if (rawSubtitle && rawNarrative && rawNarrative.indexOf(rawSubtitle) === 0) { - body = rawNarrative.slice(rawSubtitle.length).replace(/^[\s|::,,.。;;-]+/, '').trim(); - } - if (looksToolTraceDisplayText(subtitle)) subtitle = ''; - if (looksToolTraceDisplayText(body)) body = t('obs.display.toolTrace'); - body = sessionBodyPreview({ summary: body, latestPrompt: rawNarrative, firstPrompt: rawSubtitle }, title); - return { title: title, subtitle: subtitle, body: body }; - } - function looksRawSystemText(text) { - var t = String(text || '').trim(); - return t.indexOf('{"command"') >= 0 || t.indexOf('\\\"command\\\"') >= 0 || /^Bash($|[::])/.test(t) || /^prompt_submit$/.test(t) || /^agent_message$/.test(t); - } - function cleanEpisodeText(text, fallback) { - var t = normalizePreviewText(text); - if (!t || looksRawSystemText(t)) return fallback || ''; - return t; - } - function episodeActionText(o) { - var type = observationType(o); - var display = observationDisplay(o); - var title = cleanEpisodeText(display.title, typeDisplayLabel(type)); - var body = cleanEpisodeText(display.body, ''); - if (body && body !== title) return title + ':' + truncate(body, 64); - return title; - } - function typeDisplayLabel(type) { - return t('episode.type.' + (type || 'other'), String(type || t('episode.type.other')).replace(/_/g, ' ')); - } - function episodeKindLabel(kind) { - return t('episode.kind.' + (kind || 'work'), t('episode.kind.work')); - } - function observationType(o) { - var toolMap = { Read: 'file_read', Write: 'file_write', Edit: 'file_edit', Bash: 'command_run', Grep: 'search', Glob: 'search', WebFetch: 'web_fetch', WebSearch: 'web_fetch', AskUserQuestion: 'conversation', Task: 'subagent' }; - return (o && (o.type || toolMap[o.toolName] || (o.hookType ? o.hookType.replace(/_/g, ' ') : 'other'))) || 'other'; - } - function episodeTimeKey(o) { - var sid = o && (o._sessionId || o.sessionId); - if (sid) return 'session:' + sid; - try { - var d = new Date(o.timestamp); - if (!Number.isNaN(d.getTime())) return 'hour:' + d.toISOString().slice(0, 13); - } catch(e) {} - return 'misc'; - } - function episodeFallbackTitle(first, projectName) { - var name = first && (first._sessionName || first.sessionName); - if (name && name !== 'Codex 会话') return name; - var d = first && first.timestamp ? formatTime(first.timestamp) : ''; - return (projectName ? projectName + ' · ' : '') + (d || t('episode.workSegment')); - } - function classifyEpisode(counts, title, body, importance) { - var text = String((title || '') + ' ' + (body || '')).toLowerCase(); - if ((importance || 0) >= 8 || counts.decision || counts.error) return 'important'; - if (/修|bug|error|错误|异常|排查|fix|fail|undefined|json|bash/.test(text)) return 'bugfix'; - if (/解释|研究|github|项目|repo|资料|分析|搜索|tencent|memory/.test(text) || counts.web_fetch || counts.search) return 'research'; - if (/用户提出需求|需求|继续|优化|启动/.test(text) || counts.conversation) return 'user_need'; - if (counts.file_write || counts.file_edit || counts.file_read) return 'file_work'; - return 'work'; - } - function filterTimelineEpisodes(episodes) { - var filter = state.timeline.episodeFilter || 'all'; - if (filter === 'all') return episodes; - return episodes.filter(function(ep) { - if (filter === 'important') return ep.kind === 'important' || ep.kind === 'user_need' || ep.kind === 'bugfix'; - return ep.kind === filter; - }); - } - function buildTimelineEpisodes(observations) { - var project = (state.timeline.sessions || []).find(function(s) { return sessionProjectKey(s) === state.timeline.projectKey; }); - var projectName = project ? projectDisplayName(project) : ''; - var buckets = {}; - (observations || []).forEach(function(o) { - var key = episodeTimeKey(o); - if (!buckets[key]) buckets[key] = []; - buckets[key].push(o); - }); - return Object.keys(buckets).map(function(key) { - var items = buckets[key].slice().sort(function(a, b) { - return String(a.timestamp || '').localeCompare(String(b.timestamp || '')); - }); - var first = items[0] || {}; - var last = items[items.length - 1] || first; - var counts = {}; - var actions = []; - var title = ''; - var bodyCandidate = ''; - items.forEach(function(o) { - var type = observationType(o); - counts[type] = (counts[type] || 0) + 1; - var display = observationDisplay(o); - var cleanTitle = cleanEpisodeText(display.title, ''); - var cleanBody = cleanEpisodeText(display.body, ''); - if (!title && cleanTitle && cleanTitle !== 'Observation') title = cleanTitle; - if (!bodyCandidate && cleanBody) bodyCandidate = cleanBody; - var action = episodeActionText(o); - if (actions.indexOf(action) < 0) actions.push(action); - }); - var typeText = Object.keys(counts).sort(function(a, b) { return counts[b] - counts[a]; }).slice(0, 4).map(function(t) { - return typeDisplayLabel(t) + ' ' + counts[t]; - }).join(','); - var readableTitle = title || episodeFallbackTitle(first, projectName); - var readableBody = bodyCandidate || (t('episode.bodyPrefix') + typeText + t('episode.bodySuffix')); - var maxImportance = items.reduce(function(max, o) { return Math.max(max, typeof o.importance === 'number' ? o.importance : 5); }, 0); - var kind = classifyEpisode(counts, readableTitle, readableBody, maxImportance); - return { - key: key, - title: readableTitle, - body: readableBody, - actions: actions.slice(0, 6), - count: items.length, - typeText: typeText || t('episode.record') + ' ' + items.length, - start: first.timestamp, - end: last.timestamp, - importance: maxImportance, - kind: kind - }; - }).sort(function(a, b) { - return String(b.end || '').localeCompare(String(a.end || '')); - }); - } - function memoryTypeLabel(t) { - var map = { - fact: '事实', - preference: '偏好', - profile: '身份档案', - architecture: '架构', - workflow: '流程', - pattern: '模式', - bug: '问题', - goal: '目标', - history: '经历', - project: '项目', - principle: '原则', - lifestyle: '生活' - }; - return map[t] || t || '未分类'; - } - function memoryAreaToType(area) { - var map = { - profile: 'profile', - preference: 'preference', - project: 'project', - principle: 'pattern', - history: 'history' - }; - return map[area] || 'fact'; - } - function memoryTypeToArea(type, mem) { - var category = memoryCategory(mem || { type: type }); - if (category === '身份档案') return 'profile'; - if (category === '偏好') return 'preference'; - if (category === '项目与目标') return 'project'; - if (category === '判断框架') return 'principle'; - if (category === '经历') return 'history'; - return type === 'project' ? 'project' : type === 'preference' ? 'preference' : type === 'pattern' || type === 'workflow' ? 'principle' : 'profile'; - } - function shortDateTime(ts) { - if (!ts) return '-'; - try { - var d = new Date(ts); - return d.getFullYear() + '/' + String(d.getMonth() + 1).padStart(2, '0') + '/' + String(d.getDate()).padStart(2, '0') + - ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); - } catch { - return ts; - } - } - function inferAgentFromSession(s) { - var direct = s.agentId || s.agent || s.agentName || ''; - if (direct) return String(direct); - var pid = String(s.project || s.cwd || s.id || '').toLowerCase(); - if (pid.indexOf('demo') >= 0) return t('project.demo'); - if (pid.indexOf('codex') >= 0) return 'Codex'; - if (pid.indexOf('claude') >= 0) return 'Claude'; - if (pid.indexOf('cursor') >= 0) return 'Cursor'; - if (pid.indexOf('openclaw') >= 0) return 'OpenClaw'; - return t('source.local'); - } - function inferSessionSource(s) { - var direct = s.agentId || s.agent || s.agentName || ''; - if (direct) return { name: String(direct), kind: 'agent', note: t('source.agentMarked') }; - var tags = Array.isArray(s.tags) ? s.tags.join(' ').toLowerCase() : ''; - var pid = String(s.project || s.cwd || s.id || '').toLowerCase(); - if (pid.indexOf('demo') >= 0) return { name: t('project.demo'), kind: 'demo', note: t('source.demoNote') }; - if (tags.indexOf('jsonl-import') >= 0) return { name: t('source.importedClaude'), kind: 'imported', note: t('source.importedClaudeNote') }; - if (pid.indexOf('codex') >= 0) return { name: 'Codex', kind: 'agent', note: t('source.pathInferred') }; - if (pid.indexOf('claude') >= 0) return { name: 'Claude', kind: 'agent', note: t('source.pathInferred') }; - if (pid.indexOf('cursor') >= 0) return { name: 'Cursor', kind: 'agent', note: t('source.pathInferred') }; - if (pid.indexOf('openclaw') >= 0) return { name: 'OpenClaw', kind: 'agent', note: t('source.pathInferred') }; - return { name: t('source.local'), kind: 'local', note: t('source.unknownAgentNote') }; - } - function agentAvatarSpec(name) { - var n = String(name || '').toLowerCase(); - if (n.indexOf('codex') >= 0) return { label: 'Codex', cls: 'codex', image: '/agent-avatars/codex.png' }; - if (n.indexOf('claude') >= 0) return { label: 'Claude', cls: 'claude', image: '/agent-avatars/claude.png' }; - if (n.indexOf('hermes') >= 0) return { label: 'Hermes', cls: 'hermes', image: '/agent-avatars/hermes.png' }; - if (n.indexOf('openclaw') >= 0) return { label: 'OpenClaw', cls: 'openclaw', image: '/agent-avatars/openclaw.png' }; - if (n.indexOf('cursor') >= 0) return { label: 'Cursor', cls: 'cursor' }; - if (n.indexOf('演示') >= 0 || n.indexOf('demo') >= 0) return { label: I18N_LANG === 'zh' ? '演' : 'D', cls: 'unknown' }; - if (n.indexOf('导入') >= 0 || n.indexOf('import') >= 0) return { label: I18N_LANG === 'zh' ? '导' : 'I', cls: 'claude', image: '/agent-avatars/claude.png' }; - if (n.indexOf('本地记录') >= 0 || n.indexOf('local record') >= 0 || n.indexOf('未标记') >= 0 || n.indexOf('unknown') >= 0) return { label: I18N_LANG === 'zh' ? '本' : 'L', cls: 'unknown' }; - return { label: String(name || '?').replace(/\s+/g, '').slice(0, 2).toUpperCase(), cls: 'unknown' }; - } - function renderAgentAvatar(avatar) { - var cls = 'agent-avatar ' + esc(avatar.cls || 'unknown') + (avatar.image ? ' has-image' : ''); - if (avatar.image) { - return '' + esc(avatar.label || 'Agent') + ' ' + esc(t('agent.avatarAlt')) + ''; - } - return '' + esc(avatar.label || '?') + ''; - } - function isDemoSession(s) { - var raw = String((s && (s.id || s.project || s.cwd || s.title || s.summary)) || '').toLowerCase(); - return raw.indexOf('demo_') >= 0 || raw.indexOf('/tmp/agentmemory-demo') >= 0 || raw.indexOf('agentmemory-demo') >= 0; - } - function sessionId(s) { - return s && s.id !== undefined && s.id !== null ? String(s.id) : ''; - } - function isValidSession(s) { - return !!sessionId(s); - } - function shortSessionId(s, n) { - var id = sessionId(s); - return id ? id.slice(0, n || 8) : ''; - } - function sessionDisplayName(s) { - var project = s && s.project ? String(s.project).split('/').pop() : ''; - if (project) return project; - return shortSessionId(s, 8) || t('dash.unnamedSession'); - } - function sessionLabel(s) { - var id = shortSessionId(s, 8); - var name = sessionDisplayName(s); - return id ? name + ' (' + id + ')' : name + ' (' + t('ses.noRecordId') + ')'; - } - function sessionProjectKey(s) { - var raw = (s && (s.cwd || s.project)) ? String(s.cwd || s.project) : t('project.uncategorized'); - if (raw.indexOf('/tmp/agentmemory-demo') >= 0) return t('project.demo'); - if (raw.indexOf('/Users/szn') === 0) { - var parts = raw.split('/').filter(Boolean); - if (parts.length >= 3) return parts.slice(0, 3).join('/'); - } - return raw.replace(/\/$/, '') || t('project.uncategorized'); - } - function projectDisplayName(key) { - var k = String(key || t('project.uncategorized')); - if (k === 'all') return t('project.all'); - if (k === 'browser') return t('project.browser'); - if (k === '未归类' || k === 'Uncategorized') return t('project.uncategorized'); - if (k === '演示数据' || k === 'Demo data') return t('project.demo'); - var parts = k.split('/').filter(Boolean); - return parts.length ? parts[parts.length - 1] : k; - } - function arrayFromCsv(value) { - if (Array.isArray(value)) return value.map(function(v) { return String(v || '').trim(); }).filter(Boolean); - return String(value || '').split(/[,,\s]+/).map(function(v) { return v.trim(); }).filter(Boolean); - } - function reviewPayload(item) { - return (item && item.payload && typeof item.payload === 'object') ? item.payload : {}; - } - function reviewProject(item) { - var payload = reviewPayload(item); - if (payload.projectScope === 'all' || payload.project === 'all') return 'all'; - if (payload.project) return payload.project; - if (item && item.kind === 'action') return ''; - return (item && item.page && item.page.host) || 'browser'; - } - function reviewTags(item) { - var payload = reviewPayload(item); - return arrayFromCsv(payload.tags).slice(0, 6); - } - function isMarkdownPlanText(text) { - var raw = String(text || ''); - var hasPlanHeading = /^#{1,3}\s+.*(?:计划|Plan)\s*$/im.test(raw); - var sectionMatches = raw.match(/^#{1,3}\s+(?:Summary|Key Changes|Test Plan|Assumptions|Public API|Implementation|执行步骤|验证命令)\b/img) || []; - var compact = raw.replace(/\s+/g, ' '); - var compactPlanSections = [ - /#{1,3}\s+Summary\b/i, - /#{1,3}\s+Key Changes\b/i, - /#{1,3}\s+Test Plan\b/i, - /#{1,3}\s+Assumptions\b/i, - /#{1,3}\s+Implementation\b/i, - /#{1,3}\s+执行步骤\b/i, - /#{1,3}\s+验证命令\b/i - ].filter(function(pattern) { return pattern.test(compact); }).length; - var compactPlan = /#{1,3}\s+[^#]{0,160}(?:计划|Plan)/i.test(compact) && - (compactPlanSections >= 2 || /#{1,3}\s+Summary\b/i.test(compact)); - return (hasPlanHeading && sectionMatches.length >= 2) || compactPlan; - } - function isReviewTextPolluted(text) { - var trimmed = String(text || '').trim(); - if (!trimmed) return false; - var lower = trimmed.toLowerCase(); - if (isJsonLikeText(trimmed)) return true; - if (isMarkdownPlanText(trimmed)) return true; - if (/please implement this plan/i.test(trimmed)) return true; - if (/"plan"\s*:/.test(trimmed) && /"status"\s*:/.test(trimmed) && /"step"\s*:/.test(trimmed)) return true; - if (/"command"\s*:|toolinput|tooloutput|function_id/.test(lower)) return true; - if (/"(?:cmd|workdir|yield_time_ms|max_output_tokens)"\s*:/.test(lower)) return true; - if (/\b(tooluseid|tooluse|call_[a-z0-9]+|chunk id|wall time|process exited)\b/i.test(trimmed)) return true; - if (/^(?:json|state|limit)\s+[\w.-]+/i.test(trimmed)) return true; - if (/\b(?:namewithowner|headrefname|baserefname|databaseid)\b/i.test(lower)) return true; - if (/^\s*(?:gh|git|npm|pnpm|yarn|python3?|node|curl)\s+[^\n]*(?:--json|--limit|--workdir|--max-output|--yield-time|status|show|list|run|test|install|build)\b/i.test(trimmed)) return true; - if (/审查结果\s*\[[Pp]\d+\]/.test(trimmed) && /(?:src|test)\/[^\s]+(?:\s*\(line\s+\d+\))?/.test(trimmed)) return true; - if (/^src\/[^\s]+/m.test(trimmed) && /\bnpm\s+(test|run|install|build)\b/i.test(trimmed)) return true; - if (/^\s*(npm|pnpm|yarn)\s+(test|run|install|build)\b/im.test(trimmed)) return true; - return false; - } - function isReviewItemDisplayable(item) { - if (!item) return false; - return !isReviewTextPolluted(item.title) && !isReviewTextPolluted(item.content); - } - function isActionReviewRenderable(item) { - if (!item) return false; - return item.kind === 'action' && isReviewItemDisplayable(item); - } - function actionTags(action) { - return Array.isArray(action && action.tags) ? action.tags.map(String) : []; - } - function isGeneratedAction(action) { - var tags = actionTags(action); - return (action && action.createdBy === 'todo-extract') || - tags.indexOf('todo-extracted') >= 0 || - tags.indexOf('action-candidate') >= 0 || - !!(action && action.metadata && action.metadata.todoExtraction); - } - function isActionRenderable(action) { - if (!action) return false; - if (!isGeneratedAction(action)) return true; - return !isReviewTextPolluted(action.title) && !isReviewTextPolluted(action.description); - } - function reviewActionPriority(item) { - var payload = reviewPayload(item); - var candidate = payload.actionCandidate && typeof payload.actionCandidate === 'object' ? payload.actionCandidate : {}; - var value = candidate.priority || payload.priority || item.priority || 5; - var parsed = parseInt(value, 10); - if (!Number.isFinite(parsed)) parsed = 5; - return Math.max(1, Math.min(10, parsed)); - } - function reviewSourceLabel(item) { - var payload = reviewPayload(item); - return payload.sourceLabel || payload.provider || (item && item.page && (item.page.typeLabel || item.page.host)) || '浏览器'; - } - function browserSessionObservations(item) { - var page = (item && item.page) || {}; - var conversation = (item && item.conversation) || {}; - var turns = Array.isArray(conversation.turns) ? conversation.turns : []; - var createdAt = (item && (item.createdAt || item.updatedAt)) || new Date().toISOString(); - var observations = []; - if (page.title || page.url) { - observations.push({ - id: (item.id || 'browser') + '_page', - sessionId: 'browser_' + (item.id || ''), - timestamp: createdAt, - type: 'web_fetch', - title: page.title || '浏览器页面', - subtitle: page.host || page.typeLabel || '浏览器', - narrative: [page.title, page.url].filter(Boolean).join('\n'), - facts: [], - concepts: ['browser'], - files: [], - importance: 0.5 - }); - } - turns.forEach(function(turn, index) { - var role = turn && turn.role === 'assistant' ? 'AI' : turn && turn.role === 'user' ? '用户' : '对话'; - var text = String((turn && turn.text) || '').trim(); - if (!text) return; - observations.push({ - id: (item.id || 'browser') + '_turn_' + index, - sessionId: 'browser_' + (item.id || ''), - timestamp: createdAt, - type: 'conversation', - title: role, - subtitle: page.title || page.host || '浏览器对话', - narrative: text, - facts: [], - concepts: ['browser', role], - files: [], - importance: role === '用户' ? 0.8 : 0.6 - }); - }); - if (item && item.content) { - observations.push({ - id: (item.id || 'browser') + '_review', - sessionId: 'browser_' + (item.id || ''), - timestamp: item.updatedAt || createdAt, - type: item.kind === 'lesson' ? 'decision' : 'discovery', - title: item.kind === 'lesson' ? '经验候选' : '同步会话', - subtitle: item.status === 'approved' ? '已保存' : item.status === 'dismissed' ? '已忽略' : '待审阅', - narrative: item.content, - facts: [], - concepts: ['browser', item.kind === 'lesson' ? 'lesson' : 'session'], - files: [], - importance: 0.7 - }); - } - return observations; - } - function browserReviewSessions(items) { - return (items || []).filter(function(item) { - var payload = reviewPayload(item); - return item && item.source === 'browser-extension' && !payload.browserSessionId && item.page && (item.page.type === 'ai-chat' || item.conversation || item.page.url); - }).map(function(item) { - var page = item.page || {}; - var payload = reviewPayload(item); - var provider = (item.conversation && item.conversation.provider) || payload.provider || page.host || '浏览器'; - var obs = browserSessionObservations(item); - return { - id: 'browser_' + item.id, - project: provider || '浏览器', - cwd: 'browser/' + (page.host || provider || 'web'), - source: 'browser-extension', - agentId: provider || '浏览器', - startedAt: item.createdAt || item.updatedAt || '', - updatedAt: item.updatedAt || item.createdAt || '', - endedAt: item.reviewedAt || item.updatedAt || item.createdAt || '', - status: item.status === 'pending' ? 'active' : 'completed', - observationCount: obs.length, - firstPrompt: page.title || item.title || '浏览器对话', - summary: item.content || '', - tags: ['browser', page.type || '', item.kind || ''].filter(Boolean), - embeddedObservations: obs, - browserPage: page - }; - }); - } - function groupSessionsByProject(items) { - var groups = {}; - (items || []).forEach(function(s) { - var key = sessionProjectKey(s); - if (!groups[key]) groups[key] = { key: key, name: projectDisplayName(key), sessions: [], count: 0, latest: '', observations: 0, sources: {}, hasMissingId: false }; - groups[key].sessions.push(s); - if (!sessionId(s)) groups[key].hasMissingId = true; - groups[key].count += 1; - groups[key].observations += s.observationCount || 0; - var rt = sessionRecordTime(s); - if (rt > groups[key].latest) groups[key].latest = rt; - var src = inferSessionSource(s).name || '未标记来源'; - groups[key].sources[src] = (groups[key].sources[src] || 0) + 1; - }); - return Object.keys(groups).map(function(k) { - groups[k].sessions.sort(function(a, b) { return (sessionRecordTime(b) || '').localeCompare(sessionRecordTime(a) || ''); }); - return groups[k]; - }).sort(function(a, b) { return (b.latest || '').localeCompare(a.latest || ''); }); - } - function sessionSourceKey(s) { - var source = inferSessionSource(s); - return source.kind + ':' + source.name; - } - function sessionSourceGroups(items) { - var groups = { - all: { key: 'all', name: '全部会话', count: 0, latest: '', observations: 0, kind: 'all' } - }; - (items || []).forEach(function(s) { - var source = inferSessionSource(s); - var key = sessionSourceKey(s); - if (!groups[key]) groups[key] = { key: key, name: source.name || '未标记来源', count: 0, latest: '', observations: 0, kind: source.kind || 'source' }; - groups.all.count += 1; - groups[key].count += 1; - groups.all.observations += s.observationCount || 0; - groups[key].observations += s.observationCount || 0; - var rt = sessionRecordTime(s); - if (rt > groups.all.latest) groups.all.latest = rt; - if (rt > groups[key].latest) groups[key].latest = rt; - }); - return Object.keys(groups).map(function(k) { return groups[k]; }).sort(function(a, b) { - if (a.key === 'all') return -1; - if (b.key === 'all') return 1; - return (b.latest || '').localeCompare(a.latest || ''); - }); - } - function sessionRecordTime(s) { - return (s && (s.updatedAt || s.endedAt || s.startedAt)) || ''; - } - function sessionStatusLabel(status) { - var map = { active: '历史会话', completed: '历史会话', archived: '历史会话', failed: '历史会话' }; - return map[status] || '历史会话'; - } - function sessionSourceSummary(s, obsCount) { - var source = String((s && s.source) || '').indexOf('browser') >= 0 ? '浏览器对话' : String((s && s.source) || '').indexOf('local-') === 0 ? '本地 Agent 会话' : '工作台会话'; - return [ - { label: '来源', value: source }, - { label: '项目', value: projectDisplayName(sessionProjectKey(s)) }, - { label: '记录', value: (obsCount || 0) + ' 条' }, - { label: '时间', value: shortDateTime(sessionRecordTime(s)) || '-' } - ]; - } - function statusIconMarkup(status, label) { - var s = String(status || '').toLowerCase(); - var title = esc(label || sessionStatusLabel(s)); - var cls = s === 'completed' || s === 'done' || s === 'closed' ? 'done' - : s === 'active' || s === 'running' ? 'active' - : s === 'blocked' || s === 'failed' || s === 'cancelled' ? s - : 'pending'; - var icon = ''; - if (cls === 'active') icon = ''; - if (cls === 'pending') icon = ''; - if (cls === 'blocked') icon = ''; - if (cls === 'failed' || cls === 'cancelled') icon = ''; - return ''; - } - function translateLessonText(text) { - var t = String(text || '').trim(); - var rules = [ - [/^DO NOT respond to these messages or otherwise consider them in your response unless the user explicitly asks you to\.$/i, '除非用户明确要求,否则不要回应或采纳系统旁路消息。'], - [/^do not include obvious instructions like "Provide helpful error messages to users", "Write unit tests for all new utilit/i, '不要把显而易见的泛化建议写进经验,例如“提供友好的错误提示”“为所有工具写测试”。'], - [/^Don't include generic development practices\.$/i, '不要记录过于泛泛的开发常识。'], - [/^make sure to include the important parts\.$/i, '总结时要保留真正重要的部分,不要只留下空泛结论。'], - [/^Do not make up information such as "Common Development Tasks", "Tips for Development", "Support and Documentation"/i, '不要编造不存在的章节或功能说明。'], - [/^Avoid listing every component or file structure that can be easily discovered\.$/i, '避免罗列用户自己很容易查到的组件或文件结构。'] - ]; - for (var i = 0; i < rules.length; i++) { - if (rules[i][0].test(t)) return rules[i][1]; - } - return t; - } - function memoryCategory(m) { - var text = ((m && (m.title || m.content || '')) + ' ' + ((m && m.concepts) || []).join(' ')).toLowerCase(); - if (/身份|个人档案|profile|alias|name|自我|生日|出生|本科就读|常用语言|当前重点项目|\d{4}[-/年]\d{1,2}[-/月]\d{1,2}/.test(text)) return '身份档案'; - if (/偏好|preference|喜欢|沟通|协作|working style/.test(text)) return '偏好'; - if (/项目|buddyup|project|产品|创业|ucl|hci/.test(text)) return '项目与目标'; - if (/原则|判断|框架|原则|pattern|workflow|工作流/.test(text)) return '判断框架'; - if (/经历|教育|学校|history|experience/.test(text)) return '经历'; - return memoryTypeLabel(m && m.type); - } - function splitCommaList(text) { - return String(text || '') - .split(/[、,,]/) - .map(function(x) { return x.trim().replace(/[。;;]$/, ''); }) - .filter(Boolean); - } - function splitSentenceList(text) { - return String(text || '') - .split(/[。;;\n]/) - .map(function(x) { return x.trim(); }) - .filter(Boolean); - } - function splitIdentityProfile(content) { - var text = String(content || '').trim(); - var focusMatch = text.match(/当前重点项目包括[::]?\s*([^。]+)。?/); - var communicationMatch = text.match(/沟通偏好[::]\s*([^。]+)。?/); - var languageMatch = text.match(/常用语言包括[::]\s*([^。]+)。?/); - var educationMatch = text.match(/(本科就读于[^。]+。?)/); - var intro = text - .replace(/当前重点项目包括[::]?\s*[^。]+。?/g, '') - .replace(/沟通偏好[::]\s*[^。]+。?/g, '') - .replace(/常用语言包括[::]\s*[^。]+。?/g, '') - .replace(/本科就读于[^。]+。?/g, '') - .replace(/\s+/g, ' ') - .trim(); - return { - intro: intro || text, - education: splitSentenceList(educationMatch && educationMatch[1]), - focus: splitCommaList(focusMatch && focusMatch[1]), - communication: splitCommaList(communicationMatch && communicationMatch[1]), - language: splitCommaList(languageMatch && languageMatch[1]) - }; - } - function memoryStrength(m) { - var rawStrength = (m && m.strength) || 0; - var strength = Math.round(rawStrength <= 1 ? rawStrength * 100 : rawStrength * 10); - if (strength > 100) strength = 100; - if (strength < 0) strength = 0; - return strength; - } - function memorySourceKind(m) { - var concepts = (m && Array.isArray(m.concepts) ? m.concepts : []).join(' ').toLowerCase(); - var project = String((m && m.project) || '').toLowerCase(); - var text = String((m && (m.title || m.content)) || '').toLowerCase(); - if (project === 'browser' || concepts.indexOf('browser-context') >= 0 || text.indexOf('网页记忆线索') >= 0 || text.indexOf('浏览器候选记忆') >= 0) return 'browser'; - if (m && m.sessionIds && m.sessionIds.length) return 'session'; - return 'manual'; - } - function browserSourceFromMemory(m) { - var concepts = m && Array.isArray(m.concepts) ? m.concepts : []; - var source = ''; - concepts.forEach(function(c) { - var s = String(c || ''); - if (s.indexOf('browser-source:') === 0 && !source) source = s.slice('browser-source:'.length); - }); - if (source) return source; - var host = ''; - concepts.forEach(function(c) { - var s = String(c || ''); - if (s.indexOf('browser-host:') === 0 && !host) host = s.slice('browser-host:'.length); - }); - return host; - } - function browserSourceLabel(source) { - var labels = { chatgpt: 'ChatGPT', claude: 'Claude', gemini: 'Gemini', perplexity: 'Perplexity', grok: 'Grok', deepseek: 'DeepSeek', github: 'GitHub', feishu: '飞书', notion: 'Notion' }; - if (!source) return '浏览器'; - return labels[source] || source.replace(/^www\./, ''); - } - function memorySourceLabel(m) { - var kind = memorySourceKind(m); - if (kind === 'browser') return browserSourceLabel(browserSourceFromMemory(m)); - if (kind === 'session') return '会话'; - return '手动'; - } - function renderMemoryCard(card) { - var mem = card.memory || {}; - var cls = card.kind || 'default'; - var html = '
'; - html += '
'; - html += '
' + esc(card.title || '未命名记忆') + '
'; - html += '' + esc(card.label || memoryTypeLabel(mem.type)) + ''; - html += '
'; - if (card.items && card.items.length) { - html += '
    '; - card.items.forEach(function(item) { html += '
  • ' + esc(item) + '
  • '; }); - html += '
'; - } else { - html += '
' + esc(card.body || '暂无内容') + '
'; - } - if (mem.concepts && mem.concepts.length > 0) { - html += '
'; - mem.concepts.slice(0, 4).forEach(function(c) { html += '' + esc(c) + ''; }); - html += '
'; - } - html += ''; - html += '
'; - return html; - } - function memoryDisplayCards(m) { - var content = (m && (m.content || m.title)) || ''; - if (memoryCategory(m) === '身份档案') { - var data = splitIdentityProfile(content); - var cards = []; - if (data.intro) cards.push({ memory: m, kind: 'identity', label: '身份', title: '基本信息', body: data.intro }); - if (data.education && data.education.length) cards.push({ memory: m, kind: 'history', label: '经历', title: '教育经历', items: data.education }); - if (data.focus && data.focus.length) cards.push({ memory: m, kind: 'project', label: '项目', title: '当前重点', items: data.focus }); - if (data.communication && data.communication.length) cards.push({ memory: m, kind: 'preference', label: '偏好', title: '沟通偏好', items: data.communication }); - if (data.language && data.language.length) cards.push({ memory: m, kind: 'identity', label: '语言', title: '常用表达', items: data.language }); - return cards.length ? cards : [{ memory: m, kind: 'identity', label: memoryTypeLabel(m && m.type), title: (m && m.title) || '身份档案', body: content }]; - } - var title = ((m && m.title) || '').trim(); - var body = ((m && m.content) || '').trim(); - var sameTitle = title && body && body.indexOf(title) === 0; - return [{ - memory: m, - kind: 'default', - label: memoryTypeLabel(m && m.type), - title: sameTitle ? truncate(body, 72) : truncate(title || body, 72), - body: sameTitle ? body.slice(title.length).trim() || body : body - }]; - } - function renderIdentityProfileCard(mem) { - if (!mem) return ''; - var data = splitIdentityProfile(mem.content || mem.title || ''); - var headline = '未命名身份'; - var birth = ''; - var intro = data.intro || ''; - var nameMatch = intro.match(/^([^,。]+?)(?:,|。)/); - if (nameMatch) headline = nameMatch[1].trim(); - var birthMatch = intro.match(/(\d{4}[-/年]\d{1,2}[-/月]\d{1,2}\s*生?)/); - if (birthMatch) birth = birthMatch[1].replace(/年|月/g, '-').replace(/日/g, '').replace(/--/g, '-'); - var titleLine = intro.replace(headline, '').replace(/^,/, '').trim(); - if (birth) titleLine = titleLine.replace(birthMatch && birthMatch[0], '').replace(/^,/, '').trim(); - var html = '
'; - html += '
'; - html += '
'; - html += '
身份档案
'; - html += '
' + esc(headline) + '
'; - html += '
' + esc(titleLine || intro) + '
'; - html += '
'; - html += '
'; - if (birth) html += '生日 ' + esc(birth) + ''; - html += '' + esc(memoryTypeLabel(mem.type)) + ''; - html += ''; - html += '
'; - html += '
'; - var sections = [ - ['当前重点', data.focus], - ['沟通偏好', data.communication], - ['常用语言', data.language] - ]; - sections.forEach(function(section) { - var label = section[0]; - var list = section[1] || []; - html += '
'; - html += '
' + esc(label) + '
'; - if (list.length) { - html += '
'; - list.forEach(function(item) { html += '' + esc(item) + ''; }); - html += '
'; - } else { - html += '
暂无结构化内容
'; - } - html += '
'; - }); - html += '
'; - html += '
'; - return html; - } - function renderIdentityProfileGroup(cards) { - cards = cards || []; - if (!cards.length) return ''; - var primary = (cards[0] && cards[0].memory) || {}; - var titleCard = cards.find(function(c) { return c.title === '基本信息'; }) || cards[0]; - var headline = titleCard && titleCard.body ? titleCard.body.split(/[,。]/)[0] : '身份档案'; - var html = '
'; - html += '
'; - html += '
'; - html += '
' + esc(headline || '身份档案') + '
'; - if (titleCard && titleCard.body) { - html += '
' + esc(truncate(titleCard.body, 150)) + '
'; - } - html += '
'; - html += '
'; - html += ''; - html += ''; - html += '
'; - html += '
'; - cards.forEach(function(card) { - if (card === titleCard) return; - html += '
'; - html += '
'; - html += '
' + esc(card.title || '信息') + '
'; - html += '' + esc(card.label || '') + ''; - html += '
'; - if (card.items && card.items.length) { - html += '
    '; - card.items.forEach(function(item) { html += '
  • ' + esc(item) + '
  • '; }); - html += '
'; - } else { - html += '
' + esc(card.body || '暂无内容') + '
'; - } - html += '
'; - }); - html += '
'; - html += '
'; - return html; - } - function buildAgentMemoryPrompt(mem) { - var data = splitIdentityProfile((mem && (mem.content || mem.title)) || ''); - var lines = []; - lines.push('请根据这份用户记忆来优化协作方式:'); - if (data.intro) lines.push('- 用户画像:' + data.intro); - if (data.focus && data.focus.length) lines.push('- 当前重点:' + data.focus.join(';')); - if (data.communication && data.communication.length) lines.push('- 沟通偏好:' + data.communication.join(';')); - if (data.language && data.language.length) lines.push('- 常用表达/风格:' + data.language.join(';')); - if (data.education && data.education.length) lines.push('- 经历背景:' + data.education.join(';')); - lines.push('- 协作原则:少暴露用户难以理解的内部概念,把功能解释成清晰的产品流程;能用图标表达的小标签优先用图标;界面和建议都要服务真实使用,而不是只展示系统记录。'); - return lines.join('\n'); - } - function openAgentPrompt(id) { - var mem = (state.memories.items || []).find(function(m) { return m.id === id; }) || (state.memories.items || []).find(function(m) { return memoryCategory(m) === '身份档案'; }); - if (!mem) return; - var prompt = buildAgentMemoryPrompt(mem); - var modal = document.getElementById('modal'); - var overlay = document.getElementById('modal-overlay'); - modal.innerHTML = - '

协作提示

' + - '

这些记忆会自动进入 Agent 的理解范围;这里仅保留可复制的提示文本。

' + - '
' + - '' + - '
' + - '
' + - ''; - overlay.classList.add('open'); - setTimeout(function() { - var input = document.getElementById('agent-memory-prompt'); - if (input) input.focus(); - }, 0); - } - async function copyAgentPrompt() { - var text = (document.getElementById('agent-memory-prompt') || {}).value || ''; - var status = document.getElementById('agent-prompt-status'); - try { - if (navigator.clipboard && navigator.clipboard.writeText) { - await navigator.clipboard.writeText(text); - } else { - var ta = document.getElementById('agent-memory-prompt'); - if (ta) { ta.select(); document.execCommand('copy'); } - } - if (status) { - status.style.display = 'block'; - status.style.color = 'var(--green)'; - status.textContent = '已复制。'; - } - } catch (err) { - if (status) { - status.style.display = 'block'; - status.style.color = 'var(--red)'; - status.textContent = '复制失败,可以手动选中这段提示。'; - } - } - } - async function copyTextToClipboard(text) { - if (navigator.clipboard && navigator.clipboard.writeText) { - await navigator.clipboard.writeText(text || ''); - return; - } - var ta = document.createElement('textarea'); - ta.value = text || ''; - ta.style.position = 'fixed'; - ta.style.left = '-9999px'; - document.body.appendChild(ta); - ta.select(); - document.execCommand('copy'); - document.body.removeChild(ta); - } - function debounce(fn, ms) { - var t; - return function() { - var args = arguments, ctx = this; - clearTimeout(t); - t = setTimeout(function() { fn.apply(ctx, args); }, ms); - }; - } - - // IME_SAFE_SEARCH_V2 - function bindImeSafeSearch(input, ms, onSearch) { - var composing = false; - var justCommitted = false; - var run = debounce(function(value) { onSearch(value); }, ms); - input.addEventListener('compositionstart', function() { composing = true; }); - input.addEventListener('compositionend', function() { - composing = false; - justCommitted = true; - onSearch(input.value); - setTimeout(function() { justCommitted = false; }, 0); - }); - input.addEventListener('input', function(e) { - if (composing || e.isComposing) return; - if (justCommitted) return; - run(input.value); - }); - } - function captureSearchFocus(ids) { - var a = document.activeElement; - if (!a || ids.indexOf(a.id) < 0) return null; - return { id: a.id, start: a.selectionStart, end: a.selectionEnd }; - } - function restoreSearchFocus(focus) { - if (!focus) return; - var el = document.getElementById(focus.id); - if (!el) return; - el.focus(); - if (typeof el.setSelectionRange === 'function') { - try { el.setSelectionRange(focus.start, focus.end); } catch (e) {} - } - } - diff --git a/src/viewer/parts/app/45-transport.js b/src/viewer/parts/app/45-transport.js deleted file mode 100644 index 7828ad50..00000000 --- a/src/viewer/parts/app/45-transport.js +++ /dev/null @@ -1,76 +0,0 @@ - async function api(path, opts) { - try { - var headers = Object.assign({ 'Cache-Control': 'no-cache' }, (opts && opts.headers) || {}); - var fetchOpts = Object.assign({}, opts || {}, { headers: headers }); - var urls = [REST + '/agentmemory/' + path, REST + '/' + path]; - for (var i = 0; i < urls.length; i++) { - var res = await fetch(urls[i], fetchOpts); - if (res.ok) return await res.json(); - } - console.warn('[viewer] API ' + (fetchOpts.method || 'GET') + ' ' + path + ' failed on all route variants'); - return null; - } catch (err) { - console.warn('[viewer] API error on ' + path + ':', err); - return null; - } - } - async function apiGet(path) { return api(path); } - async function apiPost(path, body) { - return api(path, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body || {}) - }); - } - async function apiDelete(path, body) { - return api(path, { - method: 'DELETE', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body || {}) - }); - } - async function apiPatch(path, body) { - return api(path, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body || {}) - }); - } - async function loadLocalCodexSessions(limit) { - var count = limit || 120; - var result = await apiGet('local-agent-sessions?limit=' + encodeURIComponent(count)); - if (!result) result = await apiGet('local-codex-sessions?limit=' + encodeURIComponent(count)); - return (result && result.sessions) || []; - } - async function settledData(label, fallback, loader) { - try { - var data = await loader(); - return { label: label, data: data, failed: false }; - } catch (err) { - console.warn('[viewer] ' + label + ' failed:', err); - return { label: label, data: fallback, failed: true }; - } - } - function renderDataWarnings(warnings, retryAction) { - if (!warnings || warnings.length === 0) return ''; - var html = '
' + esc(warnings.join('、')) + ' 暂时没读到,已先展示可用数据。
'; - html += '
'; - return html; - } - function mergeSessions(primary, local) { - var seen = {}; - var out = []; - (primary || []).forEach(function(s) { - var id = sessionId(s); - if (!id) return; - seen[id] = true; - out.push(s); - }); - (local || []).forEach(function(s) { - var id = sessionId(s).replace(/^codex_local_/, 'codex_'); - if (!sessionId(s)) return; - if (!seen[id] && !seen[sessionId(s)]) out.push(s); - }); - return out; - } - diff --git a/src/viewer/parts/app/50-routing.js b/src/viewer/parts/app/50-routing.js deleted file mode 100644 index 05f17b73..00000000 --- a/src/viewer/parts/app/50-routing.js +++ /dev/null @@ -1,70 +0,0 @@ - function normalizeTab(tab) { - var normalized = String(tab || '').replace(/^#/, '').toLowerCase(); - // 专家模式下,被隐藏视图放行;否则按 TAB_REDIRECTS 折回主三栏。 - if (expertModeEnabled() && EXPERT_TABS.some(function(t) { return t.id === normalized; })) { - return normalized; - } - if (TAB_REDIRECTS[normalized]) return TAB_REDIRECTS[normalized]; - return TAB_IDS.indexOf(normalized) >= 0 ? normalized : 'dashboard'; - } - - function tabFromRoute() { - try { - return normalizeTab(decodeURIComponent(window.location.hash.slice(1))); - } catch (_) { - return 'dashboard'; - } - } - - function updateTabRoute(tab, replace) { - var target = '#' + tab; - if (window.location.hash === target) return; - if (replace) { - history.replaceState(null, '', target); - } else { - history.pushState(null, '', target); - } - } - - function switchTab(tab, opts) { - opts = opts || {}; - tab = normalizeTab(tab); - if (state.activeTab === 'replay' && tab !== 'replay' && typeof stopReplayTimer === 'function') { - stopReplayTimer(); - } - if (!opts.skipRoute) { - updateTabRoute(tab, !!opts.replaceRoute); - } - state.activeTab = tab; - document.querySelectorAll('.tab-bar button').forEach(function(b) { - var isActive = b.dataset.tab === tab; - b.classList.toggle('active', isActive); - if (isActive) { - b.setAttribute('aria-current', 'page'); - } else { - b.removeAttribute('aria-current'); - } - }); - document.querySelectorAll('.view').forEach(function(v) { - v.classList.toggle('active', v.id === 'view-' + tab); - }); - if (state.flagsConfig) renderFlagBanners(state.flagsConfig); - loadTab(tab); - } - - async function loadTab(tab) { - switch(tab) { - case 'dashboard': if (!state.dashboard.loaded) await loadDashboard(); break; - case 'graph': if (!state.graph.loaded) await loadGraph(); break; - case 'memories': if (!state.memories.loaded) await loadMemories(); break; - case 'timeline': if (!state.timeline.loaded) await loadTimeline(); break; - case 'sessions': if (!state.sessions.loaded) await loadSessions(); break; - case 'lessons': if (!state.lessons.loaded) await loadLessons(); break; - case 'actions': if (!state.actions.loaded) await loadActions(); break; - case 'audit': if (!state.audit.loaded) await loadAudit(); break; - case 'activity': if (!state.activity.loaded) await loadActivity(); break; - case 'profile': if (!state.profile.loaded) await loadProfile(); break; - case 'replay': if (!state.replay.loaded) await loadReplay(); break; - } - } - diff --git a/src/viewer/parts/app/52-dashboard.js b/src/viewer/parts/app/52-dashboard.js deleted file mode 100644 index b9636e1d..00000000 --- a/src/viewer/parts/app/52-dashboard.js +++ /dev/null @@ -1,482 +0,0 @@ - async function loadDashboard() { - var el = document.getElementById('view-dashboard'); - el.innerHTML = '
加载总览中...
'; - var showDebug = new URLSearchParams(window.location.search).get('debug') === '1'; - try { - var baseResults = await Promise.all([ - apiGet('health'), - apiGet('sessions'), - apiGet('actions'), - apiGet('inbox?status=awaiting&limit=50') - ]); - state.dashboard.health = baseResults[0]; - state.dashboard.sessions = ((baseResults[1] && baseResults[1].sessions) || []).filter(function(s) { return !isDemoSession(s); }); - state.dashboard.actions = ((baseResults[2] && baseResults[2].actions) || []).filter(isActionRenderable); - state.dashboard.actionReviews = []; - state.dashboard.inboxAwaiting = (baseResults[3] && baseResults[3].items) || []; - if (showDebug) { - var debugResults = await Promise.all([ - apiGet('memories?latest=true&limit=500'), - apiGet('graph/stats'), - apiGet('audit?limit=5'), - apiGet('semantic'), - apiGet('procedural'), - apiGet('relations'), - apiGet('lessons'), - apiGet('delivery-status') - ]); - state.dashboard.memories = (debugResults[0] && debugResults[0].memories) || []; - state.dashboard.graphStats = debugResults[1]; - state.dashboard.recentAudit = (debugResults[2] && debugResults[2].entries) || []; - state.dashboard.semantic = (debugResults[3] && debugResults[3].facts) || (debugResults[3] && debugResults[3].semantic) || []; - state.dashboard.procedural = (debugResults[4] && debugResults[4].procedures) || (debugResults[4] && debugResults[4].procedural) || []; - state.dashboard.relations = (debugResults[5] && debugResults[5].relations) || []; - state.dashboard.lessons = (debugResults[6] && debugResults[6].lessons) || []; - state.dashboard.delivery = debugResults[7] || null; - } - state.dashboard.loaded = true; - renderDashboard(); - } catch (err) { - // Without this catch, any uncaught error in the await Promise.all - // or the renderDashboard call leaves the dashboard stuck on - // "Loading dashboard..." forever with no indication to the user - // (#323). apiGet() already swallows network/HTTP errors and - // returns null, but renderDashboard can still throw on shape - // surprises (CSP-blocked event handler binding, undefined fields). - // Surface the error in the panel + log full detail to console. - var msg = (err && err.message) ? err.message : String(err); - console.error('[viewer] loadDashboard failed:', err); - el.innerHTML = - '
' + - 'Dashboard failed to load: ' + msg + - '

' + - 'Check the browser console for the full error. If you see CSP ' + - 'violations, please open an issue with the AI Todo version ' + - '(top-right of the viewer) and the violation text.' + - '
'; - } - } - - function deliveryBadgeClass(value) { - if (value === 'ready') return 'badge-green'; - if (value === 'mostly-ready') return 'badge-yellow'; - return 'badge-muted'; - } - function deliveryStatusLabel(value) { - if (value === 'ready') return '已就绪'; - if (value === 'mostly-ready') return '基本可试'; - return '等待准备'; - } - function publicReleaseLabel(value) { - if (value === 'ready') return '可发布'; - return '等待证据'; - } - function renderDeliveryStatusCard(delivery, options) { - var showDebug = options && options.debug; - if (!delivery) return ''; - if (delivery.available === false) { - return '
浏览器入口
浏览器入口还没有打包。现在可以先在本机加载 browser-extension/ 文件夹使用。
'; - } - var real = delivery.realSiteValidation || {}; - var passed = Number(real.passedCount || 0); - var required = Number(real.requiredCount || 4); - var pct = required > 0 ? Math.round((passed / required) * 100) : 0; - var external = delivery.externalTesting || 'not-ready'; - var publicState = delivery.publicRelease || 'not-ready'; - var notPassed = Array.isArray(real.notPassed) ? real.notPassed : []; - var sites = Array.isArray(real.sites) ? real.sites : []; - var zip = delivery.artifacts && delivery.artifacts.extensionZip ? delivery.artifacts.extensionZip : null; - var zipLabel = zip && zip.exists ? '插件包已生成' : '插件包未生成'; - var version = delivery.extension && delivery.extension.version ? delivery.extension.version : '0.1.0'; - var next = notPassed.length ? notPassed.join('、') : '真实站点已齐'; - var html = '
'; - html += '
'; - html += '
浏览器会话同步
打开 AI 会话页后自动同步,后续整理都在工作台完成。
'; - html += '' + esc(zipLabel) + ''; - html += '
'; - html += '
'; - html += '
' + esc(version) + ' · 支持 ChatGPT / Claude / Gemini 等 AI 会话页 · 真实页面证据 ' + passed + '/' + required + '
'; - html += ''; - html += '
'; - if (!showDebug) { - html += '
插件界面只显示同步状态,不在浏览器里做内容挑选。
'; - return html; - } - html += '
'; - html += '
插件自检
' + esc(deliveryStatusLabel(delivery.localDemo)) + '
排错用预览页
'; - html += '
真实 AI 证据
' + passed + '/' + required + '
公开发布必需
'; - html += '
公开发布
' + esc(publicReleaseLabel(publicState)) + '
需真实站点通过
'; - html += '
'; - html += '
真实 AI 页面通过证据必须包含 turnCount > 0 和会话区域 selector;插件只同步网页 AI 会话,不读取选中文本或输入草稿。
'; - html += '
AI 页面
' + pct + '%
'; - if (sites.length) { - html += '
'; - sites.forEach(function(site) { - var status = site.status || 'missing'; - var badge = status === 'passed' ? 'badge-green' : status === 'needs-fix' ? 'badge-yellow' : 'badge-muted'; - var label = status === 'passed' ? '已通过' : status === 'needs-fix' ? '待修复' : '待验收'; - var missing = Array.isArray(site.missing) && site.missing.length ? site.missing.join('、') : (status === 'passed' ? '证据完整' : '缺真实页面证据'); - html += '
'; - html += '
' + esc(site.product || 'AI') + '
' + label + '
'; - html += '
' + esc(missing) + '
'; - if (site.file) html += '
' + esc(site.file) + '
'; - html += '
'; - }); - html += '
'; - } - html += '
'; - html += '
下一步验收:' + esc(next) + '
'; - html += ''; - html += '
'; - return html; - } - function renderDashboard() { - var el = document.getElementById('view-dashboard'); - var d = state.dashboard; - var showDebug = new URLSearchParams(window.location.search).get('debug') === '1'; - var h = d.health || {}; - var snap = h.health || {}; - var healthStatus = h.status || 'unknown'; - var dotClass = healthStatus === 'healthy' ? 'healthy' : healthStatus === 'degraded' ? 'degraded' : healthStatus === 'critical' ? 'critical' : ''; - var activeSessions = d.sessions.filter(function(s) { return s.status === 'active'; }).length; - var gs = d.graphStats || {}; - var nodeCount = gs.totalNodes !== undefined ? gs.totalNodes : (gs.nodes !== undefined ? gs.nodes : (gs.nodeCount || 0)); - var edgeCount = gs.totalEdges !== undefined ? gs.totalEdges : (gs.edges !== undefined ? gs.edges : (gs.edgeCount || 0)); - var fMetrics = h.functionMetrics || []; - var cb = h.circuitBreaker || null; - var workers = snap.workers || []; - var actions = (d.actions || []).filter(isActionRenderable); - var openTodoCount = actions.filter(function(a) { return a.status === 'pending' || a.status === 'blocked' || a.status === 'active'; }).length; - var doneTodoCount = actions.filter(function(a) { return a.status === 'done'; }).length; - function todoSummary(openCount, doneCount) { - return I18N_LANG === 'zh' - ? openCount + ' 个 Todo · ' + doneCount + ' 个 Done' - : openCount + ' open · ' + doneCount + ' done'; - } - - var html = ''; - - // First-run hero: empty dashboard = guided next step - if (d.sessions.length === 0) { - html += '
' + - '
' + t('dash.firstRun.kicker') + '
' + - '
' + t('dash.firstRun.title') + '
' + - '
' + t('dash.firstRun.body') + '
' + - '
node dist/cli.mjs demo
' + - '' + - '
'; - } - html += '
'; - var latestSessionTime = d.sessions.length ? shortDateTime(sessionRecordTime(d.sessions.slice().sort(function(a, b) { return (sessionRecordTime(b) || '').localeCompare(sessionRecordTime(a) || ''); })[0])) : t('dash.noRecord'); - html += '
' + t('dash.stat.sessions') + '
' + d.sessions.length + '
' + t('dash.stat.recent') + ' ' + esc(latestSessionTime) + '
'; - html += '
' + t('dash.stat.todos') + '
' + openTodoCount + '
'; - var lessonCount = (d.lessons || []).length; - if (showDebug) { - html += '
' + t('dash.stat.memories') + '
' + d.memories.length + '
' + t('dash.stat.latestVersion') + '
'; - html += '
' + t('dash.stat.lessons') + '
' + lessonCount + '
' + t('dash.stat.lessonsSub') + '
'; - } - if (showDebug && (nodeCount > 0 || edgeCount > 0)) { - html += '
' + t('dash.stat.graphNodes') + '
' + nodeCount + '
' + t('dash.stat.edges') + ' ' + edgeCount + '
'; - } - if (showDebug) { - html += '
' + t('dash.stat.status') + '
' + esc(healthStatus) + '
'; - html += '
' + esc(snap.connectionState || 'unknown') + '
'; - } - if (showDebug) { - var totalCalls = fMetrics.reduce(function(a, m) { return a + (m.totalCalls || 0); }, 0); - html += '
' + t('dash.stat.toolCalls') + '
' + totalCalls + '
' + t('dash.stat.tracking') + ' ' + fMetrics.length + ' ' + t('dash.stat.functions') + '
'; - } - if (showDebug && cb) { - var cbClass = cb.state === 'closed' ? 'cb-closed' : cb.state === 'open' ? 'cb-open' : 'cb-half-open'; - html += '
' + t('dash.stat.circuitBreaker') + '
' + esc(cb.state) + '
'; - html += '
' + t('dash.stat.failed') + ' ' + (cb.failures || 0) + ' ' + t('dash.stat.times') + '
'; - } - var totalObs = d.sessions.reduce(function(a, s) { return a + (s.observationCount || 0); }, 0); - var tokenBudget = parseInt(new URLSearchParams(window.location.search).get('tokenBudget') || '2000', 10) || 2000; - var estFull = totalObs * 80; - var estInjected = d.sessions.length * tokenBudget; - var savings = estFull > 0 ? Math.round((1 - estInjected / Math.max(estFull, 1)) * 100) : 0; - if (savings < 0) savings = 0; - html += '
'; - - html += renderDeliveryStatusCard(d.delivery, { debug: showDebug }); - - if (showDebug && (snap.memory || snap.cpu)) { - html += '
' + t('dash.systemResources') + '
'; - if (snap.memory) { - var heapUsed = Math.round((snap.memory.heapUsed || 0) / 1024 / 1024); - var heapTotal = Math.round((snap.memory.heapTotal || 0) / 1024 / 1024); - var rss = Math.round((snap.memory.rss || 0) / 1024 / 1024); - var heapPct = heapTotal > 0 ? Math.round((heapUsed / heapTotal) * 100) : 0; - var rssAboveFloor = rss >= 512; - var heapColor = (heapPct > 80 && rssAboveFloor) ? 'var(--red)' : (heapPct > 60 && rssAboveFloor) ? 'var(--yellow)' : 'var(--green)'; - html += '
Heap
' + heapUsed + ' / ' + heapTotal + ' MB
'; - html += '
RSS
' + rss + ' MB
'; - if (snap.memory.external) { - var ext = Math.round(snap.memory.external / 1024 / 1024); - html += '
External
' + ext + ' MB
'; - } - } - if (snap.cpu) { - var cpuPct = snap.cpu.percent || 0; - var cpuColor = cpuPct > 80 ? 'var(--red)' : cpuPct > 50 ? 'var(--yellow)' : 'var(--green)'; - html += '
CPU
' + cpuPct.toFixed(1) + '%
'; - } - if (snap.eventLoopLagMs !== undefined) { - var lag = snap.eventLoopLagMs; - var lagColor = lag > 100 ? 'var(--red)' : lag > 20 ? 'var(--yellow)' : 'var(--green)'; - html += '
Event Loop
' + lag.toFixed(1) + ' ms
'; - } - if (snap.uptimeSeconds) { - var mins = Math.floor(snap.uptimeSeconds / 60); - var hrs = Math.floor(mins / 60); - var upStr = hrs > 0 ? hrs + 'h ' + (mins % 60) + 'm' : mins + 'm'; - html += '
UPTIME: ' + upStr + '
'; - } - html += '
'; - } - - if (showDebug && snap.alerts && snap.alerts.length > 0) { - html += '
' + t('dash.alerts') + ' (' + snap.alerts.length + ')
'; - snap.alerts.forEach(function(al) { - html += '
' + esc(al) + '
'; - }); - html += '
'; - } - - if (showDebug && snap.notes && snap.notes.length > 0) { - html += '
' + t('dash.notes') + ' (' + snap.notes.length + ')
'; - snap.notes.forEach(function(n) { - html += '
' + esc(n) + '
'; - }); - html += '
'; - } - - html += '
' + t('dash.recentSessions') + '
'; - if (d.sessions.length === 0) { - html += '

' + t('dash.emptySessions') + '

'; - } else { - var recentGroups = groupSessionsByProject(d.sessions).slice(0, 5); - html += '
'; - recentGroups.forEach(function(g) { - var latest = g.sessions && g.sessions[0]; - var latestId = sessionId(latest); - var cardName = g.hasMissingId ? t('dash.unnamedSession') : g.name; - html += ''; - }); - html += '
'; - } - html += '
'; - - if (showDebug && fMetrics.length > 0) { - var sorted = fMetrics.slice().sort(function(a, b) { return (b.totalCalls || 0) - (a.totalCalls || 0); }); - html += '
' + t('dash.functionMetrics') + '
'; - html += ''; - sorted.forEach(function(m) { - var successRate = m.totalCalls > 0 ? Math.round((m.successCount / m.totalCalls) * 100) : 0; - var rateColor = successRate >= 95 ? 'var(--green)' : successRate >= 80 ? 'var(--yellow)' : 'var(--red)'; - var latencyColor = m.avgLatencyMs > 1000 ? 'var(--red)' : m.avgLatencyMs > 200 ? 'var(--yellow)' : 'var(--green)'; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - }); - html += '
FunctionCallsSuccessFailAvg LatencyQuality
' + esc(m.functionId) + '' + m.totalCalls + '' + m.successCount + ' (' + successRate + '%)' + m.failureCount + '' + Math.round(m.avgLatencyMs) + ' ms' + (m.avgQualityScore > 0 ? m.avgQualityScore.toFixed(2) : '-') + '
'; - } - - if (showDebug && workers.length > 0) { - html += '
' + t('dash.workers') + '
'; - workers.forEach(function(w) { - var statusClass = w.status === 'running' ? 'running' : w.status === 'starting' ? 'starting' : 'stopped'; - html += '
'; - html += '' + esc(w.name) + ''; - html += '' + esc(w.status) + ''; - html += '' + esc(w.id) + '
'; - }); - html += '
'; - } - - if (showDebug && cb && cb.state !== 'closed') { - html += '
' + t('dash.circuitBreakerDetail') + '
'; - html += '
State
' + esc(cb.state) + '
'; - html += '
Failures
' + (cb.failures || 0) + '
'; - if (cb.lastFailureAt) html += '
Last Failure
' + esc(formatTime(cb.lastFailureAt)) + '
'; - if (cb.openedAt) html += '
Opened At
' + esc(formatTime(cb.openedAt)) + '
'; - html += '
'; - } - - if (showDebug) { - var semFacts = d.semantic || []; - var procItems = d.procedural || []; - var relItems = d.relations || []; - - html += '
'; - html += '
'; - - html += '
' + t('dash.semanticMemory') + '
'; - if (semFacts.length === 0) { - html += '
' + t('dash.emptySemantic') + '
'; - } else { - semFacts.slice(0, 5).forEach(function(f) { - var conf = typeof f.confidence === 'number' ? Math.round(f.confidence * 100) : null; - var str = typeof f.strength === 'number' ? Math.round(f.strength * 100) : null; - var barColor = (str || 0) > 70 ? 'var(--green)' : (str || 0) > 40 ? 'var(--yellow)' : 'var(--red)'; - html += '
'; - html += '' + esc(f.fact || f.content || f.title || 'Fact') + ''; - html += ''; - if (str !== null) html += ''; - if (conf !== null) html += '' + conf + '%'; - html += '
'; - }); - } - html += '
'; - - html += '
' + t('dash.proceduralMemory') + '
'; - if (procItems.length === 0) { - html += '
' + t('dash.emptyProcedural') + '
'; - } else { - procItems.slice(0, 5).forEach(function(p) { - html += '
'; - html += '
' + esc(p.name || p.title || 'Procedure') + '
'; - if (p.trigger || p.triggerCondition) html += '
Trigger: ' + esc(p.trigger || p.triggerCondition) + '
'; - if (p.frequency) html += '
Freq: ' + p.frequency + '
'; - if (p.steps && p.steps.length > 0) { - html += '
    '; - p.steps.slice(0, 4).forEach(function(s) { html += '
  1. ' + esc(typeof s === 'string' ? s : s.description || s.action || JSON.stringify(s)) + '
  2. '; }); - if (p.steps.length > 4) html += '
  3. + ' + (p.steps.length - 4) + ' more...
  4. '; - html += '
'; - } - html += '
'; - }); - } - html += '
'; - - html += '
'; - - html += '
' + t('dash.consolidationStatus') + '
'; - html += '
Semantic facts' + semFacts.length + '
'; - html += '
Procedures' + procItems.length + '
'; - html += '
Relations' + relItems.length + '
'; - html += '
'; - - if (relItems.length > 0) { - html += '
' + t('dash.memoryRelations') + '
'; - relItems.slice(0, 8).forEach(function(r) { - var relType = r.type || r.relationType || 'related'; - var badgeClass = relType === 'supersedes' ? 'badge-red' : relType === 'extends' ? 'badge-green' : relType === 'contradicts' ? 'badge-yellow' : 'badge-muted'; - html += '
'; - html += '' + esc(truncate(r.sourceId || r.fromId || '', 8)) + ''; - html += '' + esc(relType) + ''; - html += '' + esc(truncate(r.targetId || r.toId || '', 8)) + ''; - html += '
'; - }); - html += '
'; - } - } - - el.innerHTML = html; - } - - var dashboardTimer = null; - function refreshDashboard() { - state.dashboard.loaded = false; - loadDashboard(); - } - function isViewingSessionDetail() { - return state.activeTab === 'sessions' && !!state.sessions.selectedId; - } - function shouldDeferSessionsRefresh(reason) { - return isViewingSessionDetail() && ['timer', 'poll', 'ws', 'visibility'].indexOf(reason || '') >= 0; - } - function sessionStaleNoticeMarkup() { - if (!state.sessions.stale || !state.sessions.selectedId) return ''; - return '
有新记录,当前详情暂未自动刷新。
'; - } - function renderSessionsStaleNotice() { - var host = document.getElementById('sessions-stale-notice'); - if (host) host.innerHTML = sessionStaleNoticeMarkup(); - } - function markSessionsStale() { - state.sessions.stale = true; - if (state.activeTab === 'sessions') renderSessionsStaleNotice(); - } - function markActionsStale(message) { - state.actions.stale = true; - if (message) state.actions.extractMessage = message; - if (state.activeTab === 'actions' && !actionsScrolledAway()) renderActions(); - } - function refreshActiveTab(reason) { - reason = reason || 'manual'; - switch (state.activeTab) { - case 'dashboard': state.dashboard.loaded = false; loadDashboard(); break; - case 'memories': state.memories.loaded = false; loadMemories(); break; - case 'sessions': - if (shouldDeferSessionsRefresh(reason)) { - markSessionsStale(); - } else { - state.sessions.loaded = false; - loadSessions({ showLoading: reason === 'manual', reason: reason }); - } - break; - case 'lessons': state.lessons.loaded = false; loadLessons(); break; - case 'actions': - if (reason === 'manual') { - state.actions.loaded = false; - loadActions({ generate: false }); - } else { - markActionsStale('有新会话记录,待办暂未自动刷新。'); - } - break; - case 'activity': state.activity.loaded = false; loadActivity(); break; - } - } - function markMemoryViewsStale() { - state.dashboard.loaded = false; - state.memories.loaded = false; - state.sessions.loaded = false; - state.lessons.loaded = false; - state.actions.loaded = false; - state.activity.loaded = false; - } - function saveDashboardRefreshPrefs() { - try { - localStorage.setItem('viewer_dashboard_refresh_enabled', state.dashboardRefresh.enabled ? '1' : '0'); - localStorage.setItem('viewer_dashboard_refresh_interval', String(state.dashboardRefresh.intervalMs)); - } catch {} - } - function loadDashboardRefreshPrefs() { - try { - var enabled = localStorage.getItem('viewer_dashboard_refresh_enabled'); - var interval = parseInt(localStorage.getItem('viewer_dashboard_refresh_interval') || '', 10); - if (enabled === '0') state.dashboardRefresh.enabled = false; - if (!Number.isNaN(interval) && interval >= 10000 && interval <= 120000) state.dashboardRefresh.intervalMs = interval; - } catch {} - } - function startDashboardAutoRefresh() { - if (dashboardTimer) clearInterval(dashboardTimer); - if (!state.dashboardRefresh.enabled) return; - dashboardTimer = setInterval(function() { - if (pollTimer) return; - if (state.activeTab === 'dashboard') refreshActiveTab('timer'); - }, state.dashboardRefresh.intervalMs || 30000); - } - - var graphSim = { nodes: [], edges: [], running: false, canvas: null, ctx: null, raf: null, panX: 0, panY: 0, zoom: 1, dragNode: null, mouseX: 0, mouseY: 0, tickCount: 0, quietTicks: 0 }; - function wakeGraphSim() { - graphSim.quietTicks = 0; - if (graphSim.running && !graphSim.raf) { - graphSim.raf = requestAnimationFrame(runSimulation); - } - } diff --git a/src/viewer/parts/app/54-legacy-graph-memories-timeline-obs-activity.js b/src/viewer/parts/app/54-legacy-graph-memories-timeline-obs-activity.js deleted file mode 100644 index 07c598d4..00000000 --- a/src/viewer/parts/app/54-legacy-graph-memories-timeline-obs-activity.js +++ /dev/null @@ -1,1537 +0,0 @@ - async function loadGraph() { - var el = document.getElementById('view-graph'); - el.innerHTML = '
检查关系图数据中...
'; - - var results = await Promise.all([ - apiPost('graph/query', {}), - apiGet('graph/stats') - ]); - var queryResult = results[0] || { nodes: [], edges: [] }; - state.graph.nodes = queryResult.nodes || []; - state.graph.edges = queryResult.edges || []; - state.graph.stats = results[1] || {}; - - if (state.graph.nodes.length === 0) { - state.graph.loaded = true; - el.innerHTML = - '
' + - '
🕸
' + - '
关系图暂时隐藏
' + - '
当前没有可展示的节点和连线,所以侧边栏不再显示这个入口。等系统真的提取出关系后,再把关系图作为可用功能放回来。
' + - '
需要关系图时:配置 LLM key,并开启 GRAPH_EXTRACTION_ENABLED=true 后重启。
' + - '
'; - return; - } - - el.innerHTML = '
'; - state.graph.loaded = true; - var types = {}; - state.graph.nodes.forEach(function(n) { types[n.type] = true; }); - state.graph.filters = types; - - renderGraphSidebar(); - initGraph(); - } - - var NODE_SHAPES = { - file: 'rect', function: 'circle', concept: 'circle', error: 'diamond', - decision: 'diamond', pattern: 'circle', library: 'hexagon', person: 'circle' - }; - var graphSearchTerm = ''; - - function renderGraphSidebar() { - var sb = document.getElementById('graph-sidebar'); - if (!sb) return; - var gs = state.graph.stats || {}; - var nodeCount = gs.totalNodes !== undefined ? gs.totalNodes : (gs.nodes !== undefined ? gs.nodes : (gs.nodeCount || state.graph.nodes.length)); - var edgeCount = gs.totalEdges !== undefined ? gs.totalEdges : (gs.edges !== undefined ? gs.edges : (gs.edgeCount || state.graph.edges.length)); - - var html = ''; - - html += '

图谱统计

'; - html += '
'; - html += '
' + nodeCount + '
Nodes
'; - html += '
'; - html += '
' + edgeCount + '
Edges
'; - html += '
'; - - html += '

按类型筛选

'; - Object.keys(state.graph.filters).forEach(function(type) { - var color = NODE_COLORS[type] || '#666666'; - html += ''; - }); - - html += '

图例

'; - var shapeLabels = { rect: '▭', circle: '●', diamond: '◆', hexagon: '⬢' }; - var shownShapes = {}; - Object.keys(NODE_COLORS).forEach(function(type) { - var shape = NODE_SHAPES[type] || 'circle'; - var color = NODE_COLORS[type]; - var key = type; - if (shownShapes[key]) return; - shownShapes[key] = true; - html += '
' + (shapeLabels[shape] || '●') + '' + esc(type) + '
'; - }); - html += '
'; - - html += ''; - html += '
'; - var __focus = captureSearchFocus(['graph-search']); - sb.innerHTML = html; - - sb.querySelectorAll('input[type="checkbox"]').forEach(function(cb) { - cb.addEventListener('change', function() { - state.graph.filters[this.dataset.type] = this.checked; - renderGraph(); - }); - }); - - var searchInput = document.getElementById('graph-search'); - if (searchInput) { - bindImeSafeSearch(searchInput, 200, function(v){ graphSearchTerm = v.toLowerCase(); renderGraph(); }); - } - restoreSearchFocus(__focus); - } - - function initGraph() { - var canvas = document.getElementById('graph-canvas'); - if (!canvas) return; - graphSim.canvas = canvas; - graphSim.ctx = canvas.getContext('2d'); - - function resize() { - var r = canvas.parentElement.getBoundingClientRect(); - canvas.width = r.width * window.devicePixelRatio; - canvas.height = r.height * window.devicePixelRatio; - canvas.style.width = r.width + 'px'; - canvas.style.height = r.height + 'px'; - graphSim.ctx.setTransform(window.devicePixelRatio, 0, 0, window.devicePixelRatio, 0, 0); - } - resize(); - window.addEventListener('resize', resize); - - var cw = canvas.width / window.devicePixelRatio; - var ch = canvas.height / window.devicePixelRatio; - graphSim.panX = cw / 2; - graphSim.panY = ch / 2; - - var edgeMap = {}; - state.graph.edges.forEach(function(e) { - edgeMap[e.sourceNodeId] = (edgeMap[e.sourceNodeId] || 0) + 1; - edgeMap[e.targetNodeId] = (edgeMap[e.targetNodeId] || 0) + 1; - }); - - graphSim.nodes = state.graph.nodes.map(function(n, i) { - var angle = (2 * Math.PI * i) / Math.max(state.graph.nodes.length, 1); - var radius = Math.min(cw, ch) * 0.3; - var deg = edgeMap[n.id] || 0; - return { - id: n.id, type: n.type, name: n.name, properties: n.properties, - x: Math.cos(angle) * radius + (Math.random() - 0.5) * 50, - y: Math.sin(angle) * radius + (Math.random() - 0.5) * 50, - vx: 0, vy: 0, - r: Math.max(8, Math.min(22, 8 + deg * 2.5)) - }; - }); - graphSim.edges = state.graph.edges.slice(); - graphSim.running = true; - graphSim.dragNode = null; - - setupGraphInteraction(canvas); - runSimulation(); - } - - function setupGraphInteraction(canvas) { - var isPanning = false; - var lastMX = 0, lastMY = 0; - - function canvasCoords(e) { - var rect = canvas.getBoundingClientRect(); - return { - x: (e.clientX - rect.left - graphSim.panX) / graphSim.zoom, - y: (e.clientY - rect.top - graphSim.panY) / graphSim.zoom - }; - } - function findNode(cx, cy) { - for (var i = graphSim.nodes.length - 1; i >= 0; i--) { - var n = graphSim.nodes[i]; - if (!state.graph.filters[n.type]) continue; - var dx = n.x - cx, dy = n.y - cy; - if (dx * dx + dy * dy < n.r * n.r + 25) return n; - } - return null; - } - - canvas.addEventListener('mousedown', function(e) { - var c = canvasCoords(e); - var node = findNode(c.x, c.y); - if (node) { - graphSim.dragNode = node; - } else { - isPanning = true; - } - lastMX = e.clientX; - lastMY = e.clientY; - // wake the simulation if it parked itself after settling - wakeGraphSim(); - }); - canvas.addEventListener('mousemove', function(e) { - var dx = e.clientX - lastMX; - var dy = e.clientY - lastMY; - if (graphSim.dragNode) { - graphSim.dragNode.x += dx / graphSim.zoom; - graphSim.dragNode.y += dy / graphSim.zoom; - graphSim.dragNode.vx = 0; - graphSim.dragNode.vy = 0; - } else if (isPanning) { - graphSim.panX += dx; - graphSim.panY += dy; - } - lastMX = e.clientX; - lastMY = e.clientY; - graphSim.mouseX = e.clientX; - graphSim.mouseY = e.clientY; - - var c = canvasCoords(e); - var hoverNode = findNode(c.x, c.y); - var tooltip = document.getElementById('graph-tooltip'); - if (tooltip) { - if (hoverNode && !graphSim.dragNode && !isPanning) { - var conns = graphSim.edges.filter(function(ed) { return ed.sourceNodeId === hoverNode.id || ed.targetNodeId === hoverNode.id; }).length; - var ttHtml = '
' + esc(hoverNode.name) + '
'; - ttHtml += '
' + esc(hoverNode.type) + '
'; - if (hoverNode.properties) { - var propKeys = Object.keys(hoverNode.properties).slice(0, 3); - propKeys.forEach(function(k) { - ttHtml += '
' + esc(k) + ': ' + esc(truncate(String(hoverNode.properties[k]), 30)) + '
'; - }); - } - ttHtml += '
' + conns + ' connection' + (conns !== 1 ? 's' : '') + '
'; - tooltip.innerHTML = ttHtml; - var rect = canvas.getBoundingClientRect(); - tooltip.style.left = (e.clientX - rect.left + 12) + 'px'; - tooltip.style.top = (e.clientY - rect.top + 12) + 'px'; - tooltip.classList.add('visible'); - canvas.style.cursor = 'pointer'; - } else { - tooltip.classList.remove('visible'); - canvas.style.cursor = graphSim.dragNode || isPanning ? 'grabbing' : 'grab'; - } - } - }); - canvas.addEventListener('mouseup', function(e) { - if (graphSim.dragNode && !isPanning) { - selectGraphNode(graphSim.dragNode); - } - graphSim.dragNode = null; - isPanning = false; - }); - canvas.addEventListener('wheel', function(e) { - e.preventDefault(); - var factor = e.deltaY > 0 ? 0.9 : 1.1; - graphSim.zoom = Math.max(0.1, Math.min(5, graphSim.zoom * factor)); - // zoom is visually meaningless if the rAF loop is parked — - // wake the simulation so the next frame redraws at the new scale. - wakeGraphSim(); - }, { passive: false }); - canvas.addEventListener('dblclick', function(e) { - var c = canvasCoords(e); - var node = findNode(c.x, c.y); - if (node) { - selectGraphNode(node); - expandNode(node.id); - } - }); - } - - window.zoomGraph = function(dir) { - var factor = dir > 0 ? 1.25 : 0.8; - graphSim.zoom = Math.max(0.1, Math.min(5, graphSim.zoom * factor)); - wakeGraphSim(); - }; - window.recenterGraph = function() { - graphSim.zoom = 1; - if (graphSim.canvas) { - var cw = graphSim.canvas.width / window.devicePixelRatio; - var ch = graphSim.canvas.height / window.devicePixelRatio; - graphSim.panX = cw / 2; - graphSim.panY = ch / 2; - } - wakeGraphSim(); - }; - - function selectGraphNode(simNode) { - state.graph.selectedNode = simNode; - var panel = document.getElementById('selected-node-panel'); - if (!panel) return; - var color = NODE_COLORS[simNode.type] || '#666666'; - var html = '
'; - html += '

' + esc(simNode.name) + '

'; - html += '
Type: ' + esc(simNode.type) + '
'; - if (simNode.properties) { - Object.keys(simNode.properties).forEach(function(k) { - html += '
' + esc(k) + ': ' + esc(truncate(simNode.properties[k], 50)) + '
'; - }); - } - var conns = graphSim.edges.filter(function(e) { return e.sourceNodeId === simNode.id || e.targetNodeId === simNode.id; }).length; - html += '
Connections: ' + conns + '
'; - html += ''; - html += '
'; - panel.innerHTML = html; - } - - async function expandNode(nodeId) { - var result = await apiPost('graph/query', { startNodeId: nodeId, maxDepth: 1 }); - if (!result) return; - var existingIds = {}; - graphSim.nodes.forEach(function(n) { existingIds[n.id] = true; }); - var parentNode = graphSim.nodes.find(function(n) { return n.id === nodeId; }); - var px = parentNode ? parentNode.x : 0; - var py = parentNode ? parentNode.y : 0; - - (result.nodes || []).forEach(function(n) { - if (!existingIds[n.id]) { - state.graph.nodes.push(n); - if (!state.graph.filters.hasOwnProperty(n.type)) state.graph.filters[n.type] = true; - var angle = Math.random() * Math.PI * 2; - graphSim.nodes.push({ - id: n.id, type: n.type, name: n.name, properties: n.properties, - x: px + Math.cos(angle) * 80, - y: py + Math.sin(angle) * 80, - vx: 0, vy: 0, r: 8 - }); - } - }); - - var existingEdges = {}; - graphSim.edges.forEach(function(e) { existingEdges[e.id] = true; }); - (result.edges || []).forEach(function(e) { - if (!existingEdges[e.id]) { - state.graph.edges.push(e); - graphSim.edges.push(e); - } - }); - renderGraphSidebar(); - } - - function runSimulation() { - if (!graphSim.running) return; - var nodes = graphSim.nodes; - var edges = graphSim.edges; - var nodeCount = nodes.length; - graphSim.tickCount = (graphSim.tickCount || 0) + 1; - // dense graphs (>1000 nodes) used to oscillate forever - // because the per-node force pile-up exceeded what 0.9 damping - // could bleed off each tick. Tick-decay tightens damping over - // time so the layout actually settles; a per-node velocity cap - // prevents any single node from being launched off-screen by an - // accumulated kick before damping catches up. - var coolBoost = Math.min(0.4, graphSim.tickCount / 1500); - var damping = 0.9 - coolBoost; - var repulsion = nodeCount > 1000 ? 3000 : nodeCount > 100 ? 2000 : nodeCount > 50 ? 1200 : 800; - var attraction = nodeCount > 100 ? 0.002 : 0.005; - var centerGravity = nodeCount > 1000 ? 0.012 : nodeCount > 100 ? 0.005 : 0.01; - var velocityCap = nodeCount > 1000 ? 6 : nodeCount > 200 ? 12 : 24; - - var nodeMap = {}; - nodes.forEach(function(n) { nodeMap[n.id] = n; }); - - for (var i = 0; i < nodes.length; i++) { - if (graphSim.dragNode === nodes[i]) continue; - var n = nodes[i]; - var fx = 0, fy = 0; - for (var j = 0; j < nodes.length; j++) { - if (i === j) continue; - var dx = n.x - nodes[j].x; - var dy = n.y - nodes[j].y; - var dist = Math.sqrt(dx * dx + dy * dy) || 1; - var force = repulsion / (dist * dist); - fx += (dx / dist) * force; - fy += (dy / dist) * force; - } - fx -= n.x * centerGravity; - fy -= n.y * centerGravity; - var nvx = (n.vx + fx) * damping; - var nvy = (n.vy + fy) * damping; - // Velocity cap (#563): keep any single node from being launched - // off-screen by a one-tick force spike. - if (nvx > velocityCap) nvx = velocityCap; else if (nvx < -velocityCap) nvx = -velocityCap; - if (nvy > velocityCap) nvy = velocityCap; else if (nvy < -velocityCap) nvy = -velocityCap; - n.vx = nvx; - n.vy = nvy; - } - - edges.forEach(function(e) { - var s = nodeMap[e.sourceNodeId]; - var t = nodeMap[e.targetNodeId]; - if (!s || !t) return; - var dx = t.x - s.x; - var dy = t.y - s.y; - var dist = Math.sqrt(dx * dx + dy * dy) || 1; - var f = (dist - 100) * attraction; - var fx = (dx / dist) * f; - var fy = (dy / dist) * f; - if (graphSim.dragNode !== s) { s.vx += fx; s.vy += fy; } - if (graphSim.dragNode !== t) { t.vx -= fx; t.vy -= fy; } - }); - - var totalKineticEnergy = 0; - nodes.forEach(function(n) { - if (graphSim.dragNode === n) return; - n.x += n.vx; - n.y += n.vy; - totalKineticEnergy += n.vx * n.vx + n.vy * n.vy; - }); - - // park the simulation when the layout is quiet to save CPU. - // Pick up again when a drag/interaction wakes the loop. - var rmsVelocity = nodes.length > 0 ? Math.sqrt(totalKineticEnergy / nodes.length) : 0; - if (rmsVelocity < 0.05 && graphSim.tickCount > 60 && !graphSim.dragNode) { - graphSim.quietTicks = (graphSim.quietTicks || 0) + 1; - } else { - graphSim.quietTicks = 0; - } - - renderGraph(); - if (graphSim.quietTicks > 30) { - graphSim.raf = null; - return; - } - graphSim.raf = requestAnimationFrame(runSimulation); - } - - async function rebuildGraph() { - var sb = document.getElementById('graph-sidebar'); - if (sb) sb.innerHTML = '

关系图

正在根据观察重建关系图...

'; - await apiPost('graph/build', {}); - state.graph.loaded = false; - loadGraph(); - } - - function drawNodeShape(ctx, x, y, r, type) { - var shape = NODE_SHAPES[type] || 'circle'; - switch(shape) { - case 'rect': - ctx.beginPath(); - ctx.rect(x - r, y - r * 0.75, r * 2, r * 1.5); - break; - case 'diamond': - ctx.beginPath(); - ctx.moveTo(x, y - r); - ctx.lineTo(x + r, y); - ctx.lineTo(x, y + r); - ctx.lineTo(x - r, y); - ctx.closePath(); - break; - case 'hexagon': - ctx.beginPath(); - for (var i = 0; i < 6; i++) { - var angle = (Math.PI / 3) * i - Math.PI / 2; - var hx = x + r * Math.cos(angle); - var hy = y + r * Math.sin(angle); - if (i === 0) ctx.moveTo(hx, hy); else ctx.lineTo(hx, hy); - } - ctx.closePath(); - break; - default: - ctx.beginPath(); - ctx.arc(x, y, r, 0, Math.PI * 2); - break; - } - } - - function renderGraph() { - var ctx = graphSim.ctx; - var canvas = graphSim.canvas; - if (!ctx || !canvas) return; - var w = canvas.width / window.devicePixelRatio; - var h = canvas.height / window.devicePixelRatio; - - ctx.clearRect(0, 0, w, h); - - // --- Canvas grid background --- - var gridSize = 24; - ctx.save(); - ctx.strokeStyle = isDarkMode() ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.04)'; - ctx.lineWidth = 0.5; - for (var gx = 0; gx < w; gx += gridSize) { - ctx.beginPath(); ctx.moveTo(gx, 0); ctx.lineTo(gx, h); ctx.stroke(); - } - for (var gy = 0; gy < h; gy += gridSize) { - ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(w, gy); ctx.stroke(); - } - ctx.restore(); - - ctx.save(); - ctx.translate(graphSim.panX, graphSim.panY); - ctx.scale(graphSim.zoom, graphSim.zoom); - - var nodeMap = {}; - graphSim.nodes.forEach(function(n) { nodeMap[n.id] = n; }); - - var searchActive = graphSearchTerm.length > 0; - var totalVisible = graphSim.nodes.filter(function(n) { return state.graph.filters[n.type]; }).length; - var isDense = totalVisible > 40; - var labelZoomThreshold = isDense ? 1.5 : 0.5; - var edgeLabelZoomThreshold = isDense ? 2.5 : 1.2; - var selectedId = state.graph.selectedNode ? state.graph.selectedNode.id : null; - - // --- Hover node detection for focus effect --- - var hoverNodeId = null; - if (!graphSim.dragNode && graphSim.canvas) { - var rect = graphSim.canvas.getBoundingClientRect(); - var hx = (graphSim.mouseX - rect.left - graphSim.panX) / graphSim.zoom; - var hy = (graphSim.mouseY - rect.top - graphSim.panY) / graphSim.zoom; - for (var hi = graphSim.nodes.length - 1; hi >= 0; hi--) { - var hn = graphSim.nodes[hi]; - if (!state.graph.filters[hn.type]) continue; - var hdx = hn.x - hx, hdy = hn.y - hy; - if (hdx * hdx + hdy * hdy < hn.r * hn.r + 25) { hoverNodeId = hn.id; break; } - } - } - var focusNodeId = selectedId || hoverNodeId; - - // --- Draw edges --- - graphSim.edges.forEach(function(e) { - var s = nodeMap[e.sourceNodeId]; - var t = nodeMap[e.targetNodeId]; - if (!s || !t) return; - if (!state.graph.filters[s.type] || !state.graph.filters[t.type]) return; - - var edgeDimmed = searchActive && !(s.name.toLowerCase().includes(graphSearchTerm) || t.name.toLowerCase().includes(graphSearchTerm)); - var isConnectedToFocus = focusNodeId && (e.sourceNodeId === focusNodeId || e.targetNodeId === focusNodeId); - var isFocusActive = focusNodeId !== null; - var weight = typeof e.weight === 'number' ? e.weight : 0.5; - var lineWidth = isConnectedToFocus ? 2 + weight * 2 : 1 + weight * 1.5; - - var dx = t.x - s.x; - var dy = t.y - s.y; - var len = Math.sqrt(dx * dx + dy * dy) || 1; - var curveOffset = isDense ? 12 : 18; - var offsetX = -dy / len * curveOffset; - var offsetY = dx / len * curveOffset; - var cpx = (s.x + t.x) / 2 + offsetX; - var cpy = (s.y + t.y) / 2 + offsetY; - - // Colored edges based on source node type - var edgeColor = NODE_COLORS[s.type] || '#666666'; - var edgeAlpha; - if (edgeDimmed) { - edgeAlpha = 0.06; - } else if (isFocusActive && isConnectedToFocus) { - edgeAlpha = 0.65; - } else if (isFocusActive && !isConnectedToFocus) { - edgeAlpha = 0.06; - } else { - edgeAlpha = isDense ? 0.15 : 0.25; - } - - ctx.beginPath(); - ctx.moveTo(s.x, s.y); - ctx.quadraticCurveTo(cpx, cpy, t.x, t.y); - // Parse hex color to rgba - var r = parseInt(edgeColor.slice(1,3), 16); - var g = parseInt(edgeColor.slice(3,5), 16); - var b = parseInt(edgeColor.slice(5,7), 16); - ctx.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + edgeAlpha + ')'; - ctx.lineWidth = lineWidth; - ctx.stroke(); - - if (!isDense || isConnectedToFocus) { - var arrowAngle = Math.atan2(t.y - cpy, t.x - cpx); - var arrowLen = 5 + lineWidth; - ctx.beginPath(); - ctx.moveTo(t.x - t.r * Math.cos(arrowAngle), t.y - t.r * Math.sin(arrowAngle)); - ctx.lineTo(t.x - (t.r + arrowLen) * Math.cos(arrowAngle - 0.3), t.y - (t.r + arrowLen) * Math.sin(arrowAngle - 0.3)); - ctx.lineTo(t.x - (t.r + arrowLen) * Math.cos(arrowAngle + 0.3), t.y - (t.r + arrowLen) * Math.sin(arrowAngle + 0.3)); - ctx.closePath(); - ctx.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + (edgeDimmed ? 0.06 : isConnectedToFocus ? 0.6 : 0.2) + ')'; - ctx.fill(); - } - - var showEdgeLabel = e.type && !edgeDimmed && (isConnectedToFocus ? graphSim.zoom > 0.6 : graphSim.zoom > edgeLabelZoomThreshold); - if (showEdgeLabel) { - var zoomInv = 1 / graphSim.zoom; - ctx.save(); - ctx.fillStyle = isDarkMode() ? (isConnectedToFocus ? 'rgba(238,238,238,0.9)' : 'rgba(180,180,180,0.7)') : (isConnectedToFocus ? 'rgba(17,17,17,0.85)' : 'rgba(80,80,80,0.7)'); - ctx.font = (isConnectedToFocus ? '600 ' : '500 ') + (11 * zoomInv).toFixed(1) + 'px Inter, sans-serif'; - ctx.textAlign = 'center'; - ctx.fillText(e.type, cpx, cpy - (4 * zoomInv)); - ctx.restore(); - } - }); - - // --- Draw nodes --- - graphSim.nodes.forEach(function(n) { - if (!state.graph.filters[n.type]) return; - var color = NODE_COLORS[n.type] || '#666666'; - var isSelected = selectedId === n.id; - var isHovered = hoverNodeId === n.id; - var matchesSearch = !searchActive || n.name.toLowerCase().includes(graphSearchTerm); - var isFocusFaded = focusNodeId && n.id !== focusNodeId && !graphSim.edges.some(function(ed) { - return (ed.sourceNodeId === focusNodeId && ed.targetNodeId === n.id) || - (ed.targetNodeId === focusNodeId && ed.sourceNodeId === n.id); - }); - - var nodeAlpha = !matchesSearch ? 0.12 : (isFocusFaded ? 0.2 : 1); - - ctx.save(); - ctx.globalAlpha = nodeAlpha; - - // Glow effect - if (matchesSearch && !isFocusFaded && (isSelected || isHovered || !searchActive)) { - ctx.shadowColor = color; - ctx.shadowBlur = isSelected ? 20 : isHovered ? 16 : (isDense ? 4 : 8); - } - - // Gradient fill - drawNodeShape(ctx, n.x, n.y, n.r, n.type); - var grad = ctx.createRadialGradient(n.x - n.r * 0.3, n.y - n.r * 0.3, 0, n.x, n.y, n.r * 1.2); - var cr = parseInt(color.slice(1,3), 16); - var cg = parseInt(color.slice(3,5), 16); - var cb = parseInt(color.slice(5,7), 16); - grad.addColorStop(0, 'rgba(' + Math.min(255, cr + 60) + ',' + Math.min(255, cg + 60) + ',' + Math.min(255, cb + 60) + ',0.95)'); - grad.addColorStop(1, color); - ctx.fillStyle = grad; - ctx.fill(); - ctx.restore(); - - // Selected ring - if (isSelected) { - ctx.save(); - drawNodeShape(ctx, n.x, n.y, n.r + 3, n.type); - ctx.strokeStyle = color; - ctx.lineWidth = 3; - ctx.shadowColor = color; - ctx.shadowBlur = 12; - ctx.stroke(); - ctx.restore(); - } else if (isHovered) { - drawNodeShape(ctx, n.x, n.y, n.r + 2, n.type); - ctx.strokeStyle = color; - ctx.lineWidth = 2; - ctx.stroke(); - } else if (searchActive && matchesSearch) { - drawNodeShape(ctx, n.x, n.y, n.r, n.type); - ctx.strokeStyle = '#CC0000'; - ctx.lineWidth = 2; - ctx.stroke(); - } - - var showLabel = matchesSearch && !isFocusFaded && ( - isSelected || isHovered || - (searchActive && matchesSearch) || - (!isDense && graphSim.zoom > labelZoomThreshold) || - (isDense && graphSim.zoom > labelZoomThreshold && n.r > 10) - ); - if (showLabel) { - var zoomInv = 1 / graphSim.zoom; - ctx.save(); - ctx.font = (isSelected || isHovered ? '600 ' : '500 ') + (13 * zoomInv).toFixed(1) + 'px Inter, sans-serif'; - ctx.textAlign = 'center'; - - var label = truncate(n.name, 18); - var textW = ctx.measureText(label).width; - var labelW = textW + (16 * zoomInv); - var labelH = 20 * zoomInv; - var labelY = n.y + n.r + (8 * zoomInv); // Top of the background pill - - ctx.fillStyle = isDarkMode() ? 'rgba(30,30,35,0.92)' : 'rgba(255,255,255,0.92)'; - ctx.beginPath(); - ctx.roundRect ? ctx.roundRect(n.x - labelW / 2, labelY, labelW, labelH, 4 * zoomInv) : ctx.rect(n.x - labelW / 2, labelY, labelW, labelH); - ctx.fill(); - - ctx.strokeStyle = isDarkMode() ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)'; - ctx.lineWidth = 1 * zoomInv; - ctx.stroke(); - - ctx.fillStyle = isDarkMode() ? (isSelected || isHovered ? '#eeeeee' : '#bbbbbb') : (isSelected || isHovered ? '#111111' : '#444444'); - // Vertically center text in the pill box - ctx.fillText(label, n.x, labelY + (14 * zoomInv)); - ctx.restore(); - } - }); - - ctx.restore(); - - if (graphSim.nodes.length === 0) { - ctx.fillStyle = '#999999'; - ctx.font = '14px Lora, Georgia, serif'; - ctx.textAlign = 'center'; - ctx.fillText('暂无关系图数据', w / 2, h / 2 - 16); - ctx.font = '12px Inter, sans-serif'; - ctx.fillText('Set GRAPH_EXTRACTION_ENABLED=true to enable knowledge graph extraction.', w / 2, h / 2 + 8); - } - } - - async function loadMemories() { - var el = document.getElementById('view-memories'); - el.innerHTML = '
加载记忆中...
'; - // cap at 2000 so the viewer remains responsive on large - // corpora. Older endpoints returned the full unbounded list which - // hit the iii invocation timeout and the UI fell through to 0. - var result = await apiGet('memories?latest=true&limit=2000'); - var items = (result && result.memories) || []; - // Newest first — server returns KV-insertion order. - items.sort(function(a, b) { - var ac = (a && a.createdAt) || (a && a.updatedAt) || ''; - var bc = (b && b.createdAt) || (b && b.updatedAt) || ''; - return bc.localeCompare(ac); - }); - state.memories.items = items; - state.memories.reviewItems = []; - state.memories.total = (result && typeof result.total === 'number') ? result.total : items.length; - state.memories.loaded = true; - renderMemories(); - } - - function renderMemories() { - var el = document.getElementById('view-memories'); - var items = state.memories.items; - var search = state.memories.search.toLowerCase(); - var typeFilter = state.memories.typeFilter; - var sourceFilter = state.memories.sourceFilter || ''; - - var filtered = items.filter(function(m) { - if (typeFilter && m.type !== typeFilter) return false; - if (sourceFilter) { - if (sourceFilter.indexOf('browser:') === 0) { - if (memorySourceKind(m) !== 'browser') return false; - if (browserSourceFromMemory(m) !== sourceFilter.slice('browser:'.length)) return false; - } else if (memorySourceKind(m) !== sourceFilter) { - return false; - } - } - const normalizedSearch = (search || '') - .normalize("NFKC") - .toLowerCase(); - - const normalizedTitle = (m.title || '') - .normalize("NFKC") - .toLowerCase(); - - const normalizedContent = (m.content || '') - .normalize("NFKC") - .toLowerCase(); - - if ( - search && - !normalizedTitle.includes(normalizedSearch) && - !normalizedContent.includes(normalizedSearch) - ) { - return false; - } - - return true; - }); - - var types = {}; - items.forEach(function(m) { types[m.type] = true; }); - var typeOptions = Object.keys(types).sort(); - var displayGroups = {}; - var displayCount = 0; - filtered.forEach(function(m) { - var cat = memoryCategory(m); - var cards = memoryDisplayCards(m); - if (!displayGroups[cat]) displayGroups[cat] = []; - cards.forEach(function(card) { - displayGroups[cat].push(card); - displayCount += 1; - }); - }); - - var html = ''; - - html += '
'; - html += ''; - html += '
'; - html += ''; - html += ''; - html += '
'; - html += ''; - html += '
'; - - html += '
'; - html += '
自动整理中
新的聊天记录会作为记忆线索;你可以在这里审阅、编辑或删除。
'; - html += '
'; - - if (filtered.length === 0) { - html += '
' + - '
' + - '
还没有记忆
' + - '
当 Agent 记录事实、偏好、项目和工作方式后,会在这里按主题归档。
' + - '
'; - } else { - var order = ['身份档案', '偏好', '项目与目标', '判断框架', '经历']; - Object.keys(displayGroups).forEach(function(k) { if (order.indexOf(k) < 0) order.push(k); }); - html += '
'; - order.forEach(function(cat) { - var cards = displayGroups[cat]; - if (!cards || !cards.length) return; - html += '
'; - html += '
'; - html += '
' + esc(cat) + '
'; - if (cat !== '身份档案') html += '' + cards.length + ' 张卡片'; - html += '
'; - if (cat === '身份档案') { - html += renderIdentityProfileGroup(cards); - } else { - html += '
'; - cards.forEach(function(card) { - html += renderMemoryCard(card); - }); - html += '
'; - } - html += '
'; - }); - html += '
'; - } - - var __focus = captureSearchFocus(['mem-search']); - el.innerHTML = html; - - var searchInput = document.getElementById('mem-search'); - if (searchInput) { - bindImeSafeSearch(searchInput, 200, function(v){ state.memories.search = v; renderMemories(); }); - } - var typeSelect = document.getElementById('mem-type-filter'); - if (typeSelect) { - typeSelect.addEventListener('change', function() { - state.memories.typeFilter = this.value; - renderMemories(); - }); - } - var sourceSelect = document.getElementById('mem-source-filter'); - if (sourceSelect) { - sourceSelect.addEventListener('change', function() { - state.memories.sourceFilter = this.value; - renderMemories(); - }); - } - restoreSearchFocus(__focus); - } - - function deleteMemory(id, title) { - var modal = document.getElementById('modal'); - var overlay = document.getElementById('modal-overlay'); - modal.innerHTML = '

删除记忆

确认删除「' + esc(title) + '」吗?此操作不可撤销。

'; - overlay.classList.add('open'); - } - - function openAddMemory() { - var modal = document.getElementById('modal'); - var overlay = document.getElementById('modal-overlay'); - modal.innerHTML = - '

补充记忆线索

' + - '

记忆会优先从聊天记录自动整理;这里用于补充那些没有在会话里说清楚、但希望 Agent 记住的内容。

' + - '
' + - '' + - '
' + - '' + - '' + - '
' + - '
' + - '
' + - ''; - overlay.classList.add('open'); - setTimeout(function() { - var input = document.getElementById('add-memory-content'); - if (input) input.focus(); - }, 0); - } - - async function saveNewMemory() { - var err = document.getElementById('add-memory-error'); - var content = (document.getElementById('add-memory-content') || {}).value || ''; - var area = (document.getElementById('add-memory-area') || {}).value || ''; - var project = (document.getElementById('add-memory-project') || {}).value || ''; - if (!content.trim()) { - if (err) { - err.style.display = 'block'; - err.textContent = '先写一点内容再保存。'; - } - return; - } - var result = await apiPost('remember', { - content: content.trim(), - type: memoryAreaToType(area), - project: project.trim() || undefined - }); - if (!result || result.error) { - if (err) { - err.style.display = 'block'; - err.textContent = (result && result.error) || '保存失败,请稍后再试。'; - } - return; - } - closeModal(); - state.memories.loaded = false; - await loadMemories(); - } - - function editMemory(id) { - var mem = (state.memories.items || []).find(function(m) { return m.id === id; }); - if (!mem) return; - var modal = document.getElementById('modal'); - var overlay = document.getElementById('modal-overlay'); - var area = memoryTypeToArea(mem.type, mem); - var isProfile = area === 'profile'; - modal.innerHTML = - '

' + (isProfile ? '完善档案' : '编辑记忆') + '

' + - '

' + (isProfile ? '这里会影响身份档案的完整呈现。建议按“基本信息、当前重点、沟通偏好、常用表达”补充。' : '调整内容和它在记忆库里的位置。') + '

' + - '
' + - '' + - '
' + - '' + - '' + - '
' + - '' + - '
' + - '
' + - ''; - overlay.classList.add('open'); - } - - async function saveMemory(id) { - var err = document.getElementById('edit-memory-error'); - var title = (document.getElementById('edit-memory-title') || {}).value || ''; - var area = (document.getElementById('edit-memory-area') || {}).value || ''; - var project = (document.getElementById('edit-memory-project') || {}).value || ''; - var content = (document.getElementById('edit-memory-content') || {}).value || ''; - if (!content.trim()) { - if (err) { - err.style.display = 'block'; - err.textContent = '内容不能为空。'; - } - return; - } - var result = await apiPatch('memories/' + encodeURIComponent(id), { - title: title.trim(), - type: memoryAreaToType(area), - project: project.trim(), - content: content.trim() - }); - if (!result || result.error) { - if (err) { - err.style.display = 'block'; - err.textContent = (result && result.error) || '保存失败,请稍后再试。'; - } - return; - } - closeModal(); - state.memories.loaded = false; - await loadMemories(); - } - - async function confirmDeleteMemory(id) { - closeModal(); - await apiDelete('governance/memories', { memoryIds: [id], reason: 'Deleted via viewer' }); - state.memories.loaded = false; - loadMemories(); - } - - function editReviewItem(id) { - var item = (state.memories.reviewItems || []).filter(isReviewItemDisplayable).find(function(x) { return x.id === id; }) || - (state.actions.reviewItems || []).filter(isReviewItemDisplayable).find(function(x) { return x.id === id; }); - if (!item) return; - var payload = item.payload || {}; - var isAction = item.kind === 'action'; - var sourceLabel = reviewSourceLabel(item); - var defaultTags = reviewTags(item).length ? reviewTags(item).join(', ') : ['browser', 'reviewed', payload.provider ? 'source:' + String(payload.provider).toLowerCase() : '', item.page && item.page.type ? 'page:' + item.page.type : ''].filter(Boolean).join(', '); - var defaultProject = reviewProject(item); - var modal = document.getElementById('modal'); - var overlay = document.getElementById('modal-overlay'); - modal.innerHTML = - '

审阅后保存

' + - '

这条内容来自 ' + esc(sourceLabel) + '。确认前可以调整标题、内容和归属,避免临时信息直接进入待办列表。

' + - '
' + - '' + - '' + - '
' + - '' + - '' + - '
' + - '
' + - (isAction ? '' : '') + - '' + - '
' + - '
' + - '
' + - ''; - overlay.classList.add('open'); - } - - async function approveReviewItem(id) { - var fromActionsReview = (state.actions.reviewItems || []).some(function(x) { return x.id === id; }); - var err = document.getElementById('review-error'); - var title = (document.getElementById('review-title') || {}).value || ''; - var content = (document.getElementById('review-content') || {}).value || ''; - var kind = (document.getElementById('review-kind') || {}).value || 'memory'; - var project = (document.getElementById('review-project') || {}).value || ''; - var type = (document.getElementById('review-type') || {}).value || 'fact'; - var priority = parseInt((document.getElementById('review-priority') || {}).value || '', 10); - var tags = (document.getElementById('review-tags') || {}).value || ''; - if (!content.trim()) { - if (err) { - err.style.display = 'block'; - err.textContent = '内容不能为空。'; - } - return; - } - var result = await apiPost('review/approve', { - id: id, - title: title.trim(), - content: content.trim(), - kind: kind, - project: project.trim() || undefined, - type: type, - priority: Number.isFinite(priority) ? priority : undefined, - tags: tags - }); - if (!result || result.error) { - if (err) { - err.style.display = 'block'; - err.textContent = (result && result.error) || '写入失败,请稍后再试。'; - } - return; - } - closeModal(); - state.memories.loaded = false; - if (state.activeTab === 'actions' || fromActionsReview) { - state.actions.loaded = false; - await loadActions(); - } else { - await loadMemories(); - } - } - - async function dismissReviewItem(id) { - var fromActionsReview = (state.actions.reviewItems || []).some(function(x) { return x.id === id; }); - await apiPost('review/dismiss', { id: id }); - state.memories.reviewItems = (state.memories.reviewItems || []).filter(function(x) { return x.id !== id; }); - state.actions.reviewItems = (state.actions.reviewItems || []).filter(function(x) { return x.id !== id; }); - if (state.activeTab === 'actions' || fromActionsReview) { - renderActions(); - } else { - renderMemories(); - } - } - - function closeModal() { - document.getElementById('modal-overlay').classList.remove('open'); - } - - async function loadTimeline() { - var el = document.getElementById('view-timeline'); - el.innerHTML = '
加载时间线中...
'; - var sessResult = await apiGet('sessions'); - var localSessions = await loadLocalCodexSessions(120); - var sessions = mergeSessions(((sessResult && sessResult.sessions) || []), localSessions).filter(function(s) { return !isDemoSession(s); }); - state.timeline.sessions = sessions; - state.timeline.loaded = true; - - var groups = groupSessionsByProject(sessions); - if (groups.length > 0 && !state.timeline.projectKey) { - state.timeline.projectKey = groups[0].key; - } - - renderTimelineToolbar(groups); - if (state.timeline.projectKey) await loadObservations(); - } - - function renderTimelineToolbar(groups) { - var el = document.getElementById('view-timeline'); - groups = (groups || []).map(function(g) { - return g && g.sessions ? g : groupSessionsByProject([g])[0]; - }).filter(Boolean); - var html = '
'; - html += ''; - html += ''; - html += ''; - html += ''; - html += '
'; - html += '
'; - el.innerHTML = html; - - document.getElementById('tl-project').addEventListener('change', function() { - state.timeline.projectKey = this.value; - state.timeline.sessionId = ''; - state.timeline.page = 0; - loadObservations(); - }); - } - - async function loadObservations() { - var content = document.getElementById('tl-content'); - if (!content) return; - if (!state.timeline.projectKey) { - content.innerHTML = '
🕑

请选择一个文件夹查看时间线

'; - return; - } - content.innerHTML = '
加载观察记录中...
'; - var sessions = (state.timeline.sessions || []).filter(function(s) { return sessionProjectKey(s) === state.timeline.projectKey && sessionId(s); }); - var localOnly = sessions.every(function(s) { return String(s.source || '') === 'local-codex-jsonl'; }); - if (localOnly) { - state.timeline.observations = sessions.map(function(s) { - return { - id: sessionId(s), - sessionId: sessionId(s), - timestamp: sessionRecordTime(s), - type: 'conversation', - title: sessionTitleText(s) || 'Codex 会话', - narrative: sessionBodyPreview(s, sessionTitleText(s)), - importance: 5, - _sessionId: sessionId(s), - _sessionName: sessionDisplayName(s) - }; - }).sort(function(a, b) { - return String(b.timestamp || '').localeCompare(String(a.timestamp || '')); - }); - renderObservations(); - return; - } - var results = await Promise.all(sessions.map(function(s) { - return apiGet('observations?sessionId=' + encodeURIComponent(sessionId(s))).then(function(result) { - var list = (result && result.observations) || []; - return list.map(function(o) { - o._sessionId = sessionId(s); - o._sessionName = sessionDisplayName(s); - return o; - }); - }).catch(function() { return []; }); - })); - state.timeline.observations = results.reduce(function(acc, list) { return acc.concat(list); }, []).sort(function(a, b) { - return String(b.timestamp || '').localeCompare(String(a.timestamp || '')); - }); - renderObservations(); - } - - var tlTypeFilter = ''; - - function renderObservations() { - var content = document.getElementById('tl-content'); - if (!content) return; - var obs = state.timeline.observations; - var minImp = state.timeline.minImportance; - var filtered = minImp > 0 ? obs.filter(function(o) { return (o.importance || 0) >= minImp; }) : obs; - var html = ''; - - var typeCounts = {}; - filtered.forEach(function(o) { - var t = observationType(o); - typeCounts[t] = (typeCounts[t] || 0) + 1; - }); - var typeList = Object.keys(typeCounts).sort(function(a, b) { return typeCounts[b] - typeCounts[a]; }); - - if (tlTypeFilter) { - filtered = filtered.filter(function(o) { - var t = observationType(o); - return t === tlTypeFilter; - }); - } - - var pageSize = state.timeline.pageSize; - var page = state.timeline.page; - - if (state.timeline.mode === 'episodes') { - var allEpisodes = buildTimelineEpisodes(filtered); - var episodeFilter = state.timeline.episodeFilter || 'all'; - var filterCounts = { all: allEpisodes.length, important: 0 }; - allEpisodes.forEach(function(ep) { - if (ep.kind === 'important' || ep.kind === 'user_need' || ep.kind === 'bugfix') filterCounts.important++; - }); - html += '
'; - [ - ['all', '全部'], - ['important', '重点'] - ].forEach(function(item) { - var key = item[0]; - var label = item[1]; - html += '' + label + ' (' + (filterCounts[key] || 0) + ')'; - }); - html += '
'; - var episodes = filterTimelineEpisodes(allEpisodes); - var epPageSize = Math.max(10, Math.floor(pageSize / 2)); - var epPage = page; - var epStart = epPage * epPageSize; - var epPaged = episodes.slice(epStart, epStart + epPageSize); - var epTotalPages = Math.ceil(episodes.length / epPageSize); - html += '
故事模式会把同一段工作里的底层记录合并成可读片段。需要看完整流水时,可以切到事件模式。
'; - if (epPaged.length === 0) { - html += '
🕑

暂无可整理的工作片段

'; - content.innerHTML = html; - return; - } - epPaged.forEach(function(ep) { - var accent = ep.importance >= 7 ? OBS_TYPE_COLORS.decision : ep.importance >= 4 ? OBS_TYPE_COLORS.discovery : OBS_TYPE_COLORS.other; - var expanded = !!state.timeline.expandedEpisodes[ep.key]; - var visibleActions = expanded ? ep.actions : ep.actions.slice(0, 2); - html += '
'; - html += '
' + esc(episodeKindLabel(ep.kind)) + '
'; - html += '
' + esc(ep.title) + '
'; - html += '
'; - html += '' + esc(formatTime(ep.start)) + (ep.end && ep.end !== ep.start ? ' - ' + esc(shortTime(ep.end)) : '') + ''; - html += '' + ep.count + ' 条记录'; - html += '' + esc(ep.typeText) + ''; - html += '
'; - html += '
' + esc(ep.body) + '
'; - if (visibleActions.length > 0) { - html += '
    '; - visibleActions.forEach(function(a) { html += '
  • ' + esc(a) + '
  • '; }); - html += '
'; - } - html += ''; - html += '
'; - }); - if (epTotalPages > 1) { - html += ''; - } - content.innerHTML = html; - return; - } - - var start = page * pageSize; - var paged = filtered.slice(start, start + pageSize); - var totalPages = Math.ceil(filtered.length / pageSize); - - html += '
'; - html += '全部 (' + obs.length + ')'; - typeList.forEach(function(t) { - var color = OBS_TYPE_COLORS[t] || '#666666'; - html += '' + esc(t.replace(/_/g, ' ')) + ' (' + typeCounts[t] + ')'; - }); - html += '
'; - - if (paged.length === 0) { - html += '
🕑

暂无观察记录' + (obs.length > 0 ? '(当前筛选无结果,共 ' + obs.length + ' 条)' : '(该会话下)') + '

'; - content.innerHTML = html; - return; - } - - html += '
当前文件夹共显示 ' + filtered.length + ' 条记录
'; - - html += '
'; - - var lastDateGroup = ''; - paged.forEach(function(o, idx) { - var isCompressed = !!o.narrative || !!o.type; - var isRaw = !isCompressed; - var type = observationType(o); - var isFocusEvent = type === 'decision' || type === 'error' || type === 'task' || type === 'conversation'; - var impClass = isFocusEvent ? 'high' : 'low'; - var display = observationDisplay(o); - var title = display.title; - var typeColor = OBS_TYPE_COLORS[type] || '#666666'; - var icon = OBS_TYPE_ICONS[type] || '📄'; - - var dateGroup = ''; - try { - var d = new Date(o.timestamp); - dateGroup = d.toLocaleDateString() + ' ' + d.getHours() + ':00'; - } catch(e) { dateGroup = ''; } - - if (dateGroup && dateGroup !== lastDateGroup) { - html += '
' + esc(dateGroup) + '
'; - lastDateGroup = dateGroup; - } - - var side = idx % 2 === 0 ? 'tl-left' : 'tl-right'; - - html += '
'; - html += '
'; - html += '
'; - - html += '
'; - html += '
'; - html += '
'; - html += '' + icon + ''; - html += '' + esc(title) + ''; - if (isRaw) html += 'raw'; - html += '
'; - html += '
'; - html += '' + esc(shortTime(o.timestamp)) + ''; - html += '
'; - - if (display.subtitle) html += '
' + esc(display.subtitle) + '
'; - - html += '
'; - html += '' + esc(type.replace(/_/g, ' ')) + ''; - if (o.hookType) html += '' + esc(o.hookType) + ''; - html += '
'; - - if (isRaw && o.toolInput) { - var inputStr = typeof o.toolInput === 'string' ? o.toolInput : JSON.stringify(o.toolInput); - html += '
输入:'; - html += '
' + esc(truncate(inputStr, 300)) + '
'; - } - if (isRaw && o.toolOutput) { - var outputStr = typeof o.toolOutput === 'string' ? o.toolOutput : JSON.stringify(o.toolOutput); - html += '
输出:'; - html += '
' + esc(truncate(outputStr, 300)) + '
'; - } - if (display.body) html += '
' + esc(display.body) + '
'; - if (o.facts && o.facts.length > 0) { - html += '
    '; - o.facts.forEach(function(f) { html += '
  • ' + esc(f) + '
  • '; }); - html += '
'; - } - - var hasTags = (o.concepts && o.concepts.length) || (o.files && o.files.length); - if (hasTags) { - html += '
'; - (o.concepts || []).forEach(function(c) { html += '' + esc(c) + ''; }); - (o.files || []).forEach(function(f) { - var short = f.split('/').pop(); - html += '' + esc(short) + ''; - }); - html += '
'; - } - if (isRaw && o.toolInput) { - var files = []; - var ti = o.toolInput; - if (typeof ti === 'object' && ti !== null) { - if (ti.file_path) files.push(ti.file_path); - if (ti.path) files.push(ti.path); - } - if (files.length > 0) { - html += '
'; - files.forEach(function(f) { - var short = String(f).split('/').pop(); - html += '' + esc(short) + ''; - }); - html += '
'; - } - } - html += '
'; - html += '
'; - }); - - html += '
'; - - if (totalPages > 1) { - html += ''; - } - - content.innerHTML = html; - } - - function setTlTypeFilter(type) { - tlTypeFilter = type; - state.timeline.page = 0; - renderObservations(); - } - - function tlPage(p) { - state.timeline.page = p; - renderObservations(); - } - - async function loadActivity() { - var el = document.getElementById('view-activity'); - state.activity.loadingPhase = '正在读取本地会话'; - el.innerHTML = '
加载活动中...
'; - state.activity.warnings = []; - var results = await Promise.all([ - settledData('工作台会话', { sessions: [] }, function() { return apiGet('sessions'); }), - settledData('审计记录', { entries: [] }, function() { return apiGet('audit?limit=80'); }), - settledData('本地会话', [], function() { return loadLocalCodexSessions(60); }) - ]); - state.activity.warnings = results.filter(function(r) { return r.failed; }).map(function(r) { return r.label; }); - var sessions = mergeSessions(((results[0].data && results[0].data.sessions) || []), results[2].data || []).filter(function(s) { return !isDemoSession(s); }); - var auditEntries = (results[1].data && results[1].data.entries) || []; - - var allObs = []; - var sorted = sessions.slice().sort(function(a, b) { return (b.startedAt || '').localeCompare(a.startedAt || ''); }); - var localObs = sorted.filter(function(s) { - return String(s.source || '') === 'local-codex-jsonl'; - }).map(function(s) { - return { - id: sessionId(s), - sessionId: sessionId(s), - timestamp: sessionRecordTime(s), - type: 'conversation', - title: sessionTitleText(s) || 'Codex 会话', - narrative: sessionBodyPreview(s, sessionTitleText(s)), - project: s.project, - cwd: s.cwd, - agentId: 'Codex' - }; - }).filter(function(o) { - var t = String(o.title || ''); - return t && t !== 'Codex Desktop' && t.indexOf('Automation:') !== 0; - }); - allObs = allObs.concat(localObs); - - var recentSessions = sorted.filter(function(s) { - return String(s.source || '') !== 'local-codex-jsonl'; - }).slice(0, 5); - - state.activity.sessions = sessions; - state.activity.observations = allObs; - state.activity.audit = auditEntries; - state.activity.loaded = true; - state.activity.loadingPhase = recentSessions.length ? '正在补齐最近会话细节' : ''; - renderActivity(); - - var obsResults = await Promise.all(recentSessions.filter(function(s) { return sessionId(s); }).map(function(s) { - return settledData('会话细节', null, function() { return apiGet('observations?sessionId=' + encodeURIComponent(sessionId(s))); }); - })); - obsResults.forEach(function(r) { - if (r && r.failed && state.activity.warnings.indexOf(r.label) < 0) state.activity.warnings.push(r.label); - if (r && r.data && r.data.observations) allObs = allObs.concat(r.data.observations); - }); - state.activity.observations = allObs; - state.activity.loadingPhase = ''; - renderActivity(); - } - - function renderActivity() { - var el = document.getElementById('view-activity'); - var obs = state.activity.observations; - var sessions = state.activity.sessions; - - var TOOL_TYPE_MAP = { Read: 'file_read', Write: 'file_write', Edit: 'file_edit', Bash: 'command_run', Grep: 'search', Glob: 'search', WebFetch: 'web_fetch', WebSearch: 'web_fetch', AskUserQuestion: 'conversation', Task: 'subagent' }; - - var html = ''; - - html += renderDataWarnings(state.activity.warnings, 'refresh-activity'); - - if (state.activity.loadingPhase) { - html += '
' + esc(state.activity.loadingPhase) + '
' + obs.length + ' 条
'; - } - - html += '
活动热力图(近一年)
'; - var dayCounts = {}; - obs.forEach(function(o) { - try { - var d = new Date(o.timestamp); - var key = d.toISOString().slice(0, 10); - dayCounts[key] = (dayCounts[key] || 0) + 1; - } catch(e) {} - }); - sessions.forEach(function(s) { - try { - var d = new Date(s.startedAt); - var key = d.toISOString().slice(0, 10); - dayCounts[key] = (dayCounts[key] || 0) + 1; - } catch(e) {} - }); - - var maxCount = 0; - Object.keys(dayCounts).forEach(function(k) { if (dayCounts[k] > maxCount) maxCount = dayCounts[k]; }); - - var today = new Date(); - var dayLabels = ['一', '', '三', '', '五', '', '']; - html += '
'; - dayLabels.forEach(function(l) { html += '' + l + ''; }); - html += '
'; - html += '
'; - for (var w = 51; w >= 0; w--) { - for (var d = 0; d < 7; d++) { - var cellDate = new Date(today); - cellDate.setDate(cellDate.getDate() - (w * 7 + (6 - d))); - var key = cellDate.toISOString().slice(0, 10); - var count = dayCounts[key] || 0; - var level = count === 0 ? '' : count <= (maxCount * 0.25) ? 'level-1' : count <= (maxCount * 0.5) ? 'level-2' : count <= (maxCount * 0.75) ? 'level-3' : 'level-4'; - var title = key + ':' + count + ' 条活动'; - html += '
'; - } - } - html += '
'; - html += '
较少 '; - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += ' 较多
'; - html += '
'; - - var typeCounts = {}; - obs.forEach(function(o) { - var t = o.type || TOOL_TYPE_MAP[o.toolName] || (o.hookType ? o.hookType.replace(/_/g, ' ') : 'other'); - typeCounts[t] = (typeCounts[t] || 0) + 1; - }); - var typeList = Object.keys(typeCounts).sort(function(a, b) { return typeCounts[b] - typeCounts[a]; }); - var totalObs = obs.length || 1; - - html += '
'; - - html += '
类型分布
'; - if (typeList.length === 0) { - html += '
还没有观察记录
'; - } else { - html += '
'; - typeList.slice(0, 12).forEach(function(t) { - var pct = Math.round((typeCounts[t] / totalObs) * 100); - var color = OBS_TYPE_COLORS[t] || '#666666'; - html += '
' + esc(observationTypeLabel(t)) + '
' + typeCounts[t] + '
'; - }); - html += '
'; - } - html += '
'; - - html += '
活动流
'; - var sortedObs = obs.slice().sort(function(a, b) { return (b.timestamp || '').localeCompare(a.timestamp || ''); }); - if (sortedObs.length === 0) { - html += '
暂无最近活动
'; - } else { - sortedObs.slice(0, 20).forEach(function(o) { - var type = o.type || TOOL_TYPE_MAP[o.toolName] || 'other'; - var typeColor = OBS_TYPE_COLORS[type] || '#666666'; - var icon = OBS_TYPE_ICONS[type] || '📄'; - var display = observationDisplay(o); - var title = display.title; - - var sess = sessions.find(function(s) { return sessionId(s) && (sessionId(s) === o.sessionId || sessionId(s) === o.session_id); }); - var source = inferSessionSource(sess || { project: o.project || '', cwd: o.cwd || '' }); - var avatar = agentAvatarSpec(source.name); - html += '
'; - html += renderAgentAvatar(avatar); - html += '
'; - html += '
' + esc(truncate(title, 60)) + '
'; - if (display.body) html += '
' + esc(truncate(display.body, 120)) + '
'; - html += '
' + esc(source.name) + ' · ' + esc(observationTypeLabel(type)); - if (o.files && o.files.length) html += ' · ' + esc(o.files[0].split('/').pop()) + ''; - html += ' · ' + esc(shortTime(o.timestamp)) + '
'; - html += '
'; - }); - } - html += '
'; - - html += '
'; - - el.innerHTML = html; - } - diff --git a/src/viewer/parts/app/56-sessions.js b/src/viewer/parts/app/56-sessions.js deleted file mode 100644 index ae7c5743..00000000 --- a/src/viewer/parts/app/56-sessions.js +++ /dev/null @@ -1,635 +0,0 @@ - async function loadSessions(options) { - options = options || {}; - var reason = options.reason || 'manual'; - var showLoading = options.showLoading !== false; - var requestSeq = (state.sessions.requestSeq || 0) + 1; - state.sessions.requestSeq = requestSeq; - var el = document.getElementById('view-sessions'); - if (showLoading || !state.sessions.loaded) el.innerHTML = '
加载会话中...
'; - var results = await Promise.all([ - settledData('工作台会话', { sessions: [] }, function() { return apiGet('sessions'); }), - settledData('本地会话', [], function() { return loadLocalCodexSessions(120); }), - settledData('浏览器对话', { items: [] }, function() { return apiGet('review?limit=200'); }) - ]); - if (state.sessions.requestSeq !== requestSeq) return; - state.sessions.warnings = results.filter(function(r) { return r.failed; }).map(function(r) { return r.label; }); - var browserSessions = browserReviewSessions((results[2].data && results[2].data.items) || []); - state.sessions.items = mergeSessions(((results[0].data && results[0].data.sessions) || []).concat(browserSessions), results[1].data || []).filter(function(s) { return !isDemoSession(s); }); - pruneSessionHighlightCache(state.sessions.items); - pruneSessionDetailCache(state.sessions.items); - if (reason === 'manual') state.sessions.stale = false; - state.sessions.loaded = true; - renderSessions(); - } - - function renderSessions() { - var el = document.getElementById('view-sessions'); - var items = state.sessions.items.slice().sort(function(a, b) { - return (sessionRecordTime(b) || '').localeCompare(sessionRecordTime(a) || ''); - }); - - var html = '
'; - html += renderDataWarnings(state.sessions.warnings, 'refresh-sessions'); - html += '
' + sessionStaleNoticeMarkup() + '
'; - if (items.length === 0) { - html += '
🗒

' + (state.sessions.warnings && state.sessions.warnings.length ? t('ses.emptyNeedRetry') : t('ses.empty')) + '

'; - if (state.sessions.warnings && state.sessions.warnings.length) html += '
'; - html += '
'; - } else { - var groupMode = state.sessions.groupMode === 'source' ? 'source' : 'folder'; - state.sessions.groupMode = groupMode; - var projectGroups = groupSessionsByProject(items); - var sourceGroups = sessionSourceGroups(items); - var folderGroups = [{ - key: 'all', - name: t('ses.allSessions'), - sessions: items, - count: items.length, - latest: projectGroups[0] ? projectGroups[0].latest : '', - observations: items.reduce(function(sum, s) { return sum + (s.observationCount || 0); }, 0), - sources: {}, - hasMissingId: false - }].concat(projectGroups); - var groups = groupMode === 'source' ? sourceGroups.map(function(g) { - return { - key: g.key, - name: g.name, - count: g.count, - latest: g.latest, - observations: g.observations, - sessions: g.key === 'all' ? items : items.filter(function(s) { return sessionSourceKey(s) === g.key; }) - }; - }) : folderGroups; - var activeGroupKey = groupMode === 'source' ? (state.sessions.sourceKey || 'all') : (state.sessions.folderKey || 'all'); - if (!groups.some(function(g) { return g.key === activeGroupKey; })) activeGroupKey = 'all'; - if (groupMode === 'source') state.sessions.sourceKey = activeGroupKey; - else state.sessions.folderKey = activeGroupKey; - var activeGroup = groups.find(function(g) { return g.key === activeGroupKey; }) || groups[0]; - var visibleItems = activeGroup.sessions || []; - var totalObs = items.reduce(function(sum, s) { return sum + (s.observationCount || 0); }, 0); - html += '
'; - html += '
' + t('ses.heroTitle') + '
' + t('ses.heroNote') + '
'; - html += '
' + items.length + ' ' + t('ses.unitSessions') + ' · ' + projectGroups.length + ' ' + t('ses.unitFolders') + ' · ' + (sourceGroups.length - 1) + ' ' + t('ses.unitSources') + ' · ' + totalObs + ' ' + t('ses.unitRecords') + '
'; - html += '
'; - html += '
'; - - html += '
'; - html += '
'; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += '
'; - html += '
'; - html += ''; - html += '
'; - html += '
' + esc(activeGroup ? activeGroup.name : t('ses.allSessions')) + '
' + visibleItems.length + ' ' + t('ses.unitSessions') + ' · ' + ((activeGroup && activeGroup.observations) || 0) + ' ' + t('ses.unitRecords') + '
'; - if (!visibleItems.length) { - html += '
' + (groupMode === 'source' ? t('ses.emptySource') : t('ses.emptyFolder')) + '
'; - } else { - visibleItems.forEach(function(s) { - var id = sessionId(s); - var title = sessionTitleText(s); - var preview = sessionBodyPreview(s, title) || t('ses.noPreview'); - var source = inferSessionSource(s); - var avatarLabel = (source.name || t('ses.avatarFallback')).replace(/\s+/g, '').slice(0, 2); - var selected = id && state.sessions.selectedId === id; - html += ''; - }); - } - html += '
'; - html += '
'; - } - html += '
'; - el.innerHTML = html; - - if (state.sessions.selectedId) renderSessionDetail(); - } - - function selectSession(id) { - state.sessions.selectedId = id; - renderSessions(); - setTimeout(function() { - var panel = document.getElementById('session-detail'); - if (panel) panel.scrollIntoView({ behavior: 'smooth', block: 'start' }); - }, 80); - } - - var SESSION_HIGHLIGHT_LABELS = { - goal: '用户', - agent_output: 'Agent', - tool: '工具', - mcp: 'MCP', - command: '命令', - failure: '失败', - validation: '验证', - file: '文件', - artifact: '产物', - skill: 'Skill', - todo: '待办', - follow_up: '后续' - }; - - function sessionHighlightCacheKey(s) { - if (!s) return ''; - return [ - sessionId(s), - s.status || '', - s.observationCount || 0, - s.updatedAt || '', - s.endedAt || '', - s.startedAt || '' - ].join('|'); - } - - function pruneSessionHighlightCache(items) { - var keep = {}; - (items || []).forEach(function(s) { - var id = sessionId(s); - if (id) keep[id] = sessionHighlightCacheKey(s); - }); - Object.keys(state.sessions.highlightsById || {}).forEach(function(id) { - if (!keep[id] || state.sessions.highlightsById[id].cacheKey !== keep[id]) { - delete state.sessions.highlightsById[id]; - } - }); - } - - function pruneSessionDetailCache(items) { - var keep = {}; - (items || []).forEach(function(s) { - var id = sessionId(s); - if (id) keep[id] = sessionHighlightCacheKey(s); - }); - Object.keys(state.sessions.detailCacheById || {}).forEach(function(id) { - if (!keep[id] || state.sessions.detailCacheById[id].cacheKey !== keep[id]) { - delete state.sessions.detailCacheById[id]; - } - }); - } - - function sessionDetailSections(id) { - if (!state.sessions.detailSectionsById) state.sessions.detailSectionsById = {}; - if (!state.sessions.detailSectionsById[id]) { - state.sessions.detailSectionsById[id] = { highlights: true, process: false }; - } - return state.sessions.detailSectionsById[id]; - } - - function renderSessionSectionTitle(sessionIdValue, section, title, expanded) { - return '
' + - '' + esc(title) + '' + - '' + - '
'; - } - - function toggleSessionDetailSection(id, section) { - if (!id || (section !== 'highlights' && section !== 'process')) return; - var sections = sessionDetailSections(id); - sections[section] = !sections[section]; - renderSessionDetail({ useCacheOnly: true }); - } - - async function getSessionHighlights(id, sessionRecord) { - if (!id) return { ok: false, highlights: [] }; - var cacheKey = sessionHighlightCacheKey(sessionRecord); - var cached = state.sessions.highlightsById[id]; - if (cached && cached.cacheKey === cacheKey) return cached; - var result = await apiGet('session/highlights?sessionId=' + encodeURIComponent(id) + '&maxItems=12'); - var view = result && result.success === true && Array.isArray(result.highlights) - ? { ok: true, highlights: result.highlights, cacheKey: cacheKey } - : { ok: false, highlights: [], cacheKey: cacheKey }; - state.sessions.highlightsById[id] = view; - return view; - } - - function sessionDialogueHighlights(view) { - return (Array.isArray(view && view.highlights) ? view.highlights : []).filter(function(item) { - return item && (item.category === 'goal' || item.category === 'agent_output'); - }); - } - - function renderSessionHighlights(sessionIdValue, view, expanded) { - var html = '
'; - html += renderSessionSectionTitle(sessionIdValue, 'highlights', '会话重点', expanded); - if (!expanded) return html + '
'; - if (!view || view.ok === false) { - html += '
会话重点暂不可用
'; - return html; - } - var highlights = sessionDialogueHighlights(view); - if (highlights.length === 0) { - html += '
暂无会话重点
'; - return html; - } - var compactHighlights = highlights.length > 4; - html += '
'; - highlights.forEach(function(item) { - var label = SESSION_HIGHLIGHT_LABELS[item.category] || item.category || '重点'; - html += '
'; - html += '
' + esc(label) + '
'; - html += '
'; - html += '
' + esc(item.title || label) + '
'; - if (item.summary) html += '
' + renderMarkdownSafe(item.summary) + '
'; - html += '
'; - }); - html += '
'; - if (compactHighlights) { - html += '
仅展示前几条重点;需要完整内容时使用完整对话过程。
'; - } - html += ''; - return html; - } - - function isSessionPreviewExpanded(id) { - return !!(state.sessions.previewExpandedById && state.sessions.previewExpandedById[id]); - } - - function toggleSessionPreview(id) { - if (!id) return; - if (!state.sessions.previewExpandedById) state.sessions.previewExpandedById = {}; - state.sessions.previewExpandedById[id] = !state.sessions.previewExpandedById[id]; - renderSessionDetail({ useCacheOnly: true }); - } - - async function renderSessionDetail(options) { - options = options || {}; - var panel = document.getElementById('session-detail'); - if (!panel) return; - var s = state.sessions.items.find(function(x) { return sessionId(x) === state.sessions.selectedId; }); - var id = sessionId(s); - if (!s || !id) { panel.innerHTML = ''; return; } - - var cacheKey = sessionHighlightCacheKey(s); - var cachedDetail = state.sessions.detailCacheById && state.sessions.detailCacheById[id]; - var hasFreshCache = cachedDetail && cachedDetail.cacheKey === cacheKey; - var obs; - var highlightsView; - if (hasFreshCache) { - obs = cachedDetail.observations || []; - highlightsView = cachedDetail.highlightsView || { ok: false, highlights: [], cacheKey: cacheKey }; - } else if (options.useCacheOnly) { - return; - } else { - var detailRequestSeq = (state.sessions.detailRequestSeq || 0) + 1; - state.sessions.detailRequestSeq = detailRequestSeq; - if (!panel.innerHTML) panel.innerHTML = '

加载会话详情中…

'; - obs = []; - if (!Array.isArray(s.embeddedObservations) || String(s.id || '').indexOf('browser_sync_') >= 0 || !s.id) { - obs = Array.isArray(s.embeddedObservations) ? s.embeddedObservations : []; - } - if (!obs.length && id) { - var obsRes = await apiGet('observations?sessionId=' + encodeURIComponent(id)); - obs = (obsRes && obsRes.observations) || []; - } - if ((!obs || obs.length === 0) && Array.isArray(s.embeddedObservations)) { - obs = s.embeddedObservations; - } - if ((!obs || obs.length === 0) && String(s.source || '').indexOf('local-') === 0) { - var localRes = await apiGet('local-agent-session-events?sessionId=' + encodeURIComponent(id)); - obs = (localRes && localRes.observations) || obs || []; - } - highlightsView = await getSessionHighlights(id, s); - if (state.sessions.detailRequestSeq !== detailRequestSeq || state.sessions.selectedId !== id) return; - state.sessions.detailCacheById[id] = { - cacheKey: cacheKey, - observations: obs || [], - highlightsView: highlightsView, - }; - } - - var typeCounts = {}; - var toolCounts = {}; - var fileSet = new Set(); - var firstPromptFromObs = ''; - obs.forEach(function(o) { - var t = o.type || o.hookType || 'other'; - typeCounts[t] = (typeCounts[t] || 0) + 1; - var tool = o.title || o.toolName; - if (tool && t !== 'conversation') toolCounts[tool] = (toolCounts[tool] || 0) + 1; - (o.files || []).forEach(function(f) { fileSet.add(f); }); - if (!firstPromptFromObs && (o.userPrompt || (o.type === 'conversation' && o.narrative))) { - firstPromptFromObs = o.userPrompt || o.narrative || ''; - } - }); - - var durationMs = s.endedAt ? new Date(s.endedAt).getTime() - new Date(s.startedAt).getTime() : 0; - var durationLabel = durationMs > 0 ? (durationMs < 60000 ? (durationMs / 1000).toFixed(1) + 's' : (durationMs / 60000).toFixed(1) + 'm') : '-'; - - var detailTitle = sessionTitleText(s); - var preview = sessionBodyPreview({ summary: s.summary, latestPrompt: firstPromptFromObs, firstPrompt: s.firstPrompt }, detailTitle) || firstPromptFromObs || s.summary || s.firstPrompt || ''; - var detailSections = sessionDetailSections(id); - - var html = '
'; - html += '
'; - html += '

会话时间线

' + esc(projectDisplayName(sessionProjectKey(s))) + ' · ' + esc(shortDateTime(sessionRecordTime(s))) + '
'; - html += statusIconMarkup(s.status, sessionStatusLabel(s.status)); - html += '
'; - - if (preview) { - var previewExpanded = isSessionPreviewExpanded(id); - var previewIsLong = String(preview).length > 260; - html += '
' + esc(preview) + '
'; - if (previewIsLong) { - html += ''; - } - } - - html += '
'; - html += '
记录
' + obs.length + '
'; - html += '
操作类型
' + Object.keys(typeCounts).length + '
'; - html += '
文件
' + fileSet.size + '
'; - html += '
时长
' + esc(durationLabel) + '
'; - html += '
'; - - html += renderSessionHighlights(id, highlightsView, detailSections.highlights !== false); - - var timelineObs = obs.slice().sort(function(a, b) { return (a.timestamp || '').localeCompare(b.timestamp || ''); }); - var processExpanded = detailSections.process === true; - var isLongTimeline = timelineObs.length > 80; - html += '
'; - html += renderSessionSectionTitle(id, 'process', '完整对话过程 · ' + timelineObs.length + ' 条', processExpanded); - if (processExpanded && isLongTimeline) { - html += '
记录很多,可以先跳到底部看最近进展。
'; - } - if (processExpanded && timelineObs.length === 0) { - html += '
这段会话还没有可展示的过程记录。
'; - } else if (processExpanded) { - html += '
'; - timelineObs.forEach(function(o) { - var display = observationDisplay(o); - var type = o.type || o.hookType || 'other'; - html += ''; - html += '
' + esc(shortTime(o.timestamp)) + '
'; - html += '
' + esc(display.title || observationTypeLabel(type)) + '
'; - if (display.body) html += '
' + renderMarkdownSafe(display.body) + '
'; - html += '
'; - }); - html += '
'; - if (isLongTimeline) html += '
'; - } - html += '
'; - - var topTools = Object.keys(toolCounts).sort(function(a, b) { return toolCounts[b] - toolCounts[a]; }).slice(0, 10); - if (topTools.length > 0) { - var maxC = toolCounts[topTools[0]] || 1; - html += '
工具使用
'; - html += '
'; - topTools.forEach(function(t) { - var pct = Math.round((toolCounts[t] / maxC) * 100); - html += '
' + esc(t) + '
' + toolCounts[t] + '
'; - }); - html += '
'; - } - - var typeKeys = Object.keys(typeCounts).sort(function(a, b) { return typeCounts[b] - typeCounts[a]; }); - if (typeKeys.length > 0) { - html += '
记录类型
'; - html += '
'; - typeKeys.forEach(function(t) { - html += '' + esc(typeDisplayLabel(t)) + ' · ' + typeCounts[t] + ''; - }); - html += '
'; - } - - if (fileSet.size > 0) { - var filesArr = Array.from(fileSet).slice(0, 30); - html += '
文件
'; - html += '
'; - filesArr.forEach(function(f) { html += '
• ' + esc(f) + '
'; }); - if (fileSet.size > 30) html += '
+' + (fileSet.size - 30) + ' more
'; - html += '
'; - } - - var detailId = sessionId(s); - html += '
记录来源
'; - html += '
'; - sessionSourceSummary(s, obs.length).forEach(function(item) { - html += '
' + esc(item.label) + '
' + esc(item.value || '-') + '
'; - }); - html += '
'; - html += '
查看技术细节'; - html += '
'; - html += '
id: ' + esc(detailId || '无记录 ID') + '
'; - html += '
path: ' + esc(s.cwd || '-') + '
'; - html += '
started: ' + esc(formatTime(s.startedAt)) + '
'; - if (s.endedAt) html += '
ended: ' + esc(formatTime(s.endedAt)) + '
'; - if (s.model) html += '
model: ' + esc(s.model) + '
'; - if (s.tags && s.tags.length) html += '
tags: ' + s.tags.map(esc).join(', ') + '
'; - html += '
'; - - html += '
'; - if (detailId) { - html += ''; - } else { - html += ''; - } - html += '
'; - panel.innerHTML = html; - applyPendingHighlight(); - } - - async function endSession(id) { - await apiPost('session/end', { sessionId: id }); - state.sessions.loaded = false; - loadSessions({ showLoading: true, reason: 'manual' }); - } - - async function summarizeSession(id, btn) { - if (!btn) return; - btn.textContent = '生成中...'; - btn.disabled = true; - try { - var result = await apiPost('summarize', { sessionId: id }); - state.lessons.loaded = false; - if (result && result.success) { - btn.textContent = result.fallback ? '已生成本地摘要' : '已生成摘要'; - btn.title = result.fallback ? '没有可用模型时,已用本地规则从会话记录生成摘要。' : ''; - } else { - btn.textContent = summarizeErrorLabel(result && (result.reason || result.error)); - btn.title = summarizeErrorHint(result && (result.reason || result.error)); - } - } catch (err) { - btn.textContent = '摘要服务未响应'; - btn.title = '本地工作台没有连上摘要接口,请确认 AI Todo worker 正在运行。'; - } - setTimeout(function() { btn.textContent = '生成摘要'; btn.disabled = false; }, 2600); - } - - function summarizeErrorLabel(error) { - if (error === 'no_observations') return '暂无可摘要内容'; - if (error === 'session_not_found') return '会话不存在'; - if (error === 'no_provider') return '缺少模型配置'; - return '生成失败'; - } - - function summarizeErrorHint(error) { - if (error === 'no_observations') return '这段会话还没有可整理的记录。'; - if (error === 'session_not_found') return '本地数据里没有找到这段会话。'; - if (error === 'no_provider') return '未配置模型时会使用本地规则摘要;如果仍失败,请检查本地 worker 日志。'; - return String(error || '摘要生成失败,请稍后重试。'); - } - - function skillDraftSlug(text) { - var base = String(text || 'skill').toLowerCase().replace(/[^a-z0-9\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); - return (base || 'memory-skill').slice(0, 48); - } - function skillDraftTitle(groupKey) { - var name = projectDisplayName(groupKey || '通用经验'); - return name === '通用经验' ? '可复用工作流 Skill' : name + ' 工作流 Skill'; - } - function buildSkillDraft(groupKey, lessons) { - var list = (lessons || []).slice().sort(function(a, b) { return (b.confidence || 0) - (a.confidence || 0); }).slice(0, 8); - var title = skillDraftTitle(groupKey); - var slug = skillDraftSlug(groupKey || title); - var tags = []; - list.forEach(function(l) { (l.tags || []).forEach(function(t) { if (tags.indexOf(t) < 0) tags.push(t); }); }); - var lines = []; - lines.push('# ' + title); - lines.push(''); - lines.push('## When to use'); - lines.push('Use this skill when the user is working on ' + projectDisplayName(groupKey || 'a reusable workflow') + ' and needs the agent to apply the repeated lessons below.'); - lines.push(''); - lines.push('## Core lessons'); - if (!list.length) lines.push('- Add confirmed lessons here before installing this skill.'); - list.forEach(function(l) { lines.push('- ' + translateLessonText(l.content)); }); - lines.push(''); - lines.push('## Operating guidance'); - lines.push('- Preserve the user-visible product intent before changing implementation details.'); - lines.push('- Prefer reviewable drafts over automatic writes when the output changes durable memory or local skills.'); - lines.push('- Keep examples, docs, and product UI aligned after every meaningful change.'); - if (tags.length) { - lines.push(''); - lines.push('## Tags'); - lines.push(tags.slice(0, 10).map(function(t) { return '`' + t + '`'; }).join(' ')); - } - lines.push(''); - lines.push('## Suggested install path'); - lines.push('`~/.codex/skills/' + slug + '/SKILL.md`'); - return lines.join('\n'); - } - function openSkillDraft(groupKey) { - var lessons = (state.lessons.items || []).filter(function(l) { - var key = l.project || (l.tags && l.tags[0]) || '通用经验'; - return key === groupKey; - }); - var draft = buildSkillDraft(groupKey, lessons); - var modal = document.getElementById('modal'); - var overlay = document.getElementById('modal-overlay'); - modal.innerHTML = - '

Skill 草稿

' + - '

这是从已确认经验生成的草稿,不会自动写入本地 Skill。确认适合长期复用后,再复制到对应 Skill 目录。

' + - '
' + esc(draft) + '
' + - ''; - overlay.classList.add('open'); - } - - function openAddLesson() { - var modal = document.getElementById('modal'); - var overlay = document.getElementById('modal-overlay'); - var projectOptions = ''; - (state.lessons.projects || []).forEach(function(projectKey) { - projectOptions += ''; - }); - modal.innerHTML = - '

创建经验

' + - '

写下一条以后可以复用、也可能沉淀进本地 Skill 的做法。

' + - '
' + - '' + - '' + - '
' + - '' + - '' + - '
' + - '
' + - '
' + - ''; - overlay.classList.add('open'); - setTimeout(function() { - var input = document.getElementById('add-lesson-content'); - if (input) input.focus(); - }, 0); - } - - async function saveNewLesson() { - var err = document.getElementById('add-lesson-error'); - var content = (document.getElementById('add-lesson-content') || {}).value || ''; - var context = (document.getElementById('add-lesson-context') || {}).value || ''; - var tags = (document.getElementById('add-lesson-tags') || {}).value || ''; - var project = (document.getElementById('add-lesson-project') || {}).value || ''; - if (!content.trim()) { - if (err) { - err.style.display = 'block'; - err.textContent = '先写一条经验再保存。'; - } - return; - } - var result = await apiPost('lessons', { - content: content.trim(), - context: context.trim(), - tags: tags.split(',').map(function(t) { return t.trim(); }).filter(Boolean), - project: project.trim() || undefined, - confidence: 0.85 - }); - if (!result || result.error) { - if (err) { - err.style.display = 'block'; - err.textContent = (result && result.error) || '保存失败,请稍后再试。'; - } - return; - } - closeModal(); - state.lessons.loaded = false; - await loadLessons(); - } - - async function openSkillDetail(path) { - var modal = document.getElementById('modal'); - var overlay = document.getElementById('modal-overlay'); - modal.innerHTML = '

Skill 详情

正在读取本地 SKILL.md...

'; - overlay.classList.add('open'); - var detail = await apiGet('local-skill-detail?path=' + encodeURIComponent(path || '')); - if (!detail || detail.error) { - modal.innerHTML = '

Skill 详情

' + esc((detail && detail.error) || '读取失败') + '

'; - return; - } - var html = '

' + esc(detail.name || 'Skill') + '

'; - if (detail.description) html += '

' + esc(detail.description) + '

'; - html += '
'; - html += '
'; - html += '
来源文件
' + esc(detail.path || '') + '
'; - html += '
最近修改
' + esc(formatTime(detail.updatedAt)) + '
'; - if (detail.argumentHint) html += '
参数提示
' + esc(detail.argumentHint) + '
'; - html += '
'; - if (detail.headings && detail.headings.length) { - html += '
结构
'; - detail.headings.forEach(function(h) { html += '' + esc(h) + ''; }); - html += '
'; - } - html += '
'; - html += '
内容预览
'; - html += '
' + esc(detail.preview || '暂无内容') + '
'; - html += '
'; - html += ''; - modal.innerHTML = html; - } - diff --git a/src/viewer/parts/app/58-legacy-lessons.js b/src/viewer/parts/app/58-legacy-lessons.js deleted file mode 100644 index 519b830d..00000000 --- a/src/viewer/parts/app/58-legacy-lessons.js +++ /dev/null @@ -1,157 +0,0 @@ - async function loadLessons() { - var el = document.getElementById('view-lessons'); - el.innerHTML = '
正在加载经验...
'; - var results = await Promise.all([apiGet('lessons?minConfidence=0.5'), apiGet('sessions')]); - var explicitLessons = (results[0] && results[0].lessons) || []; - var sessions = (results[1] && results[1].sessions) || []; - var projects = {}; - sessions.forEach(function(s) { - var key = sessionProjectKey(s); - if (key) projects[key] = true; - }); - state.lessons.items = explicitLessons; - state.lessons.projects = Object.keys(projects).sort(function(a, b) { return projectDisplayName(a).localeCompare(projectDisplayName(b)); }); - state.lessons.mode = explicitLessons.length ? 'explicit' : 'empty'; - state.lessons.loaded = true; - renderLessons(); - } - - function renderLessons() { - var el = document.getElementById('view-lessons'); - var items = state.lessons.items; - var search = state.lessons.search.toLowerCase(); - - if (search) { - items = items.filter(function(l) { - return (l.content + ' ' + l.context + ' ' + (l.tags || []).join(' ')).toLowerCase().indexOf(search) >= 0; - }); - } - - var groups = {}; - items.forEach(function(l) { - var tags = (l.tags || []).filter(Boolean); - var key = tags[0] || l.project || '通用工作方式'; - if (!groups[key]) groups[key] = []; - groups[key].push(l); - }); - var groupKeys = Object.keys(groups).sort(function(a, b) { return groups[b].length - groups[a].length; }); - - var skillQuery = String(state.lessons.skillSearch || '').trim().toLowerCase(); - var skillRootFilter = state.lessons.skillRootFilter || 'all'; - function skillMatches(skill) { - var rootOk = skillRootFilter === 'all' || String(skill.root || '') === skillRootFilter; - if (!rootOk) return false; - if (!skillQuery) return true; - return String(skill.name || '').toLowerCase().indexOf(skillQuery) >= 0 || String(skill.path || '').toLowerCase().indexOf(skillQuery) >= 0 || String(skill.root || '').toLowerCase().indexOf(skillQuery) >= 0; - } - function skillRootMeta(root) { - var known = { - Codex: { title: 'Codex 环境', sub: '这个 agent 会加载' }, - Agents: { title: '共享目录', sub: '多个 agent 可复用' }, - Plugin: { title: '插件提供', sub: '随插件一起安装' } - }; - return known[root] || { title: root || '其他来源', sub: '自定义 agent / skill 目录' }; - } - var rootOrder = { Codex: 1, Agents: 2, Plugin: 99 }; - var allSkillRoots = Array.from(new Set((LOCAL_SKILLS || []).map(function(skill) { return skill.root || '其他'; }))).sort(function(a, b) { - return (rootOrder[a] || 50) - (rootOrder[b] || 50) || a.localeCompare(b); - }); - var visibleSkills = (LOCAL_SKILLS || []).filter(skillMatches); - var skillGroups = {}; - visibleSkills.forEach(function(skill) { - if (!skillGroups[skill.root]) skillGroups[skill.root] = []; - skillGroups[skill.root].push(skill); - }); - var skillRoots = Object.keys(skillGroups).sort(function(a, b) { - return (rootOrder[a] || 50) - (rootOrder[b] || 50) || a.localeCompare(b); - }); - - var html = '
'; - html += '
'; - html += '
本地 Skill 管理台
'; - html += '
先看已经安装的能力,再决定哪些经验值得沉淀进去。
'; - html += '
' + (LOCAL_SKILLS || []).length + ' 个 Skill
'; - html += '
'; - html += '
'; - - html += '
'; - html += '
' + (LOCAL_SKILLS || []).length + '
已安装 Skill
'; - html += '
' + allSkillRoots.length + '
来源目录
'; - html += '
' + items.length + '
待沉淀经验
'; - html += '
'; - - html += '
'; - html += ''; - allSkillRoots.forEach(function(root) { - var meta = skillRootMeta(root); - html += ''; - }); - html += '
'; - - html += '
'; - html += ''; - html += '
'; - - html += '
'; - if (skillRoots.length === 0) { - html += '
没有匹配的 Skill
'; - } - skillRoots.forEach(function(root) { - var list = (skillGroups[root] || []).slice(); - var meta = skillRootMeta(root); - html += '
'; - html += '
' + esc(meta.title) + '
' + esc(meta.sub) + '
' + list.length + '
'; - html += '
'; - list.forEach(function(skill) { - html += '
'; - html += '
' + esc(skill.name) + '
' + esc(skill.path) + '
'; - html += '
'; - html += '
'; - }); - html += '
'; - }); - html += '
'; - - html += '
可沉淀经验
这些是从工作里提炼出的规则,可以继续合并进某个本地 Skill。
' + items.length + ' 条经验
'; - - html += '
'; - html += ''; - html += '
'; - - if (items.length === 0) { - html += '
' + - '
💡
' + - '
还没有可沉淀的经验
' + - '
当某条做法被确认有复用价值,它会先进入这里,再决定是否写进某个本地 Skill。
' + - '
'; - } else { - html += '
'; - groupKeys.forEach(function(key) { - var group = groups[key].slice().sort(function(a, b) { return (b.confidence || 0) - (a.confidence || 0); }); - html += '
'; - html += '
' + esc(key) + '
' + group.length + ' 条
'; - html += '
    '; - group.slice(0, 4).forEach(function(l) { - var lessonContent = translateLessonText(l.content); - html += '
  • ' + esc(truncate(lessonContent, 120)) + '
  • '; - }); - if (group.length > 4) html += '
  • 还有 ' + (group.length - 4) + ' 条相近经验
  • '; - html += '
'; - html += '
'; - html += '
'; - }); - html += '
'; - } - html += '
'; - - var __focus = captureSearchFocus(['skills-search', 'lessons-search']); - el.innerHTML = html; - var __ss = document.getElementById('skills-search'); - if (__ss) bindImeSafeSearch(__ss, 200, function(v){ state.lessons.skillSearch = v; renderLessons(); }); - var __ls = document.getElementById('lessons-search'); - if (__ls) bindImeSafeSearch(__ls, 200, function(v){ state.lessons.search = v; renderLessons(); }); - restoreSearchFocus(__focus); - } - diff --git a/src/viewer/parts/app/60-actions-todo.js b/src/viewer/parts/app/60-actions-todo.js deleted file mode 100644 index 63bdc51b..00000000 --- a/src/viewer/parts/app/60-actions-todo.js +++ /dev/null @@ -1,1235 +0,0 @@ - async function loadActions(opts) { - opts = opts || {}; - var el = document.getElementById('view-actions'); - el.innerHTML = '
' + esc(t('act.extract.loading')) + '
'; - var results = await Promise.all([ - apiGet('actions'), - apiGet('frontier'), - apiGet('inbox?status=awaiting&limit=50'), - apiGet('inbox?status=answered&limit=50'), - apiGet('inbox?status=dismissed&limit=50') - ]); - var explicitActions = (results[0] && results[0].actions) || []; - var frontier = (results[1] && (results[1].frontier || results[1].actions)) || []; - - state.actions.items = explicitActions; - state.actions.todoExtract = results[0] && results[0].todoExtract; - syncTodoExtractStatus(); - state.actions.reviewItems = []; - state.actions.frontier = frontier; - state.actions.loaded = true; - state.actions.stale = false; - state.inbox.awaitingItems = (results[2] && results[2].items) || []; - state.inbox.answeredItems = (results[3] && results[3].items) || []; - state.inbox.dismissedItems = (results[4] && results[4].items) || []; - state.inbox.items = state.inbox.awaitingItems; - state.inbox.loaded = true; - renderActions(); - if (state.settings.open) { - loadTodoExtractorConfig().then(renderSettingsPanel).catch(function() {}); - } - if (opts.generate === true) startTodoExtraction(opts.force === true); - } - - function todoExtractionDelta(result) { - if (!result || result.success !== true) return 0; - return Number(result.directCreated || 0) + Number(result.reviewCreated || 0); - } - - function todoExtractionSummary(result) { - if (!result || result.success !== true) return t('act.extract.failedExisting'); - var delta = todoExtractionDelta(result); - var hidden = Number(result.hiddenHistory || 0); - var discarded = Number(result.discarded || 0); - var cleaned = Number(result.cleanedActions || 0) + Number(result.cleanedReviews || 0); - var engine = result.engine || 'rules'; - var prefix = engine === 'langextract' ? t('act.extract.doneLlm') : (engine === 'mixed' ? t('act.extract.doneMixed') : t('act.extract.doneRules')); - var parts = [prefix, t('act.extract.created') + ' ' + delta, t('act.extract.history') + ' ' + hidden, t('act.extract.discarded') + ' ' + discarded]; - if (cleaned > 0) parts.push(t('act.extract.cleaned') + ' ' + cleaned); - if (result.fallbackReason) parts.push(t('act.extract.reason') + ': ' + result.fallbackReason); - return parts.join(' · '); - } - - function todoExtractionUsedLlm(result) { - return !!result && (result.engine === 'langextract' || result.engine === 'mixed') && !result.llmFallback; - } - - function clearTodoExtractStatusPoll() { - if (!state.actions.extractStatusPollTimer) return; - clearTimeout(state.actions.extractStatusPollTimer); - state.actions.extractStatusPollTimer = null; - } - - function scheduleTodoExtractStatusPoll() { - if (state.actions.extractStatusPollTimer) return; - state.actions.extractStatusPollTimer = setTimeout(function() { - state.actions.extractStatusPollTimer = null; - if (!state.actions.extractInFlight) return; - refreshActionListsAfterExtract().then(function() { - if (state.activeTab === 'actions' && !actionsScrolledAway()) renderActions(); - else if (state.activeTab === 'actions') state.actions.stale = true; - }).catch(function() { - if (state.actions.extractInFlight) scheduleTodoExtractStatusPoll(); - }); - }, 3000); - } - - function syncTodoExtractStatus() { - var st = state.actions.todoExtract || {}; - if (st.status === 'running') { - state.actions.extractInFlight = true; - state.actions.extractStatus = 'running'; - state.actions.extractMessage = t('act.extract.background'); - scheduleTodoExtractStatusPoll(); - return; - } - clearTodoExtractStatusPoll(); - if (state.actions.extractInFlight && st.status !== 'running') state.actions.extractInFlight = false; - if (st.status === 'done' && st.summary) { - state.actions.extractStatus = 'done'; - state.actions.extractFallback = !todoExtractionUsedLlm(st.summary); - state.actions.extractPartial = st.summary && st.summary.engine === 'mixed'; - state.actions.extractMessage = todoExtractionSummary(st.summary); - } else if (st.status === 'error') { - state.actions.extractStatus = 'error'; - state.actions.extractMessage = st.error || t('act.extract.failedExisting'); - } - } - - function refreshActionListsAfterExtract() { - return Promise.all([ - apiGet('actions'), - apiGet('frontier') - ]).then(function(results) { - state.actions.items = (results[0] && results[0].actions) || state.actions.items || []; - state.actions.todoExtract = results[0] && results[0].todoExtract; - syncTodoExtractStatus(); - state.actions.frontier = (results[1] && (results[1].frontier || results[1].actions)) || state.actions.frontier || []; - state.actions.reviewItems = []; - return null; - }); - } - - function actionsScrolledAway() { - try { - var y = window.pageYOffset || - (document.documentElement && document.documentElement.scrollTop) || - (document.body && document.body.scrollTop) || - 0; - return state.activeTab === 'actions' && y > 80; - } catch (_) { - return false; - } - } - - function loadTodoExtractorConfig() { - return apiGet('config/todo-extractor').then(function(res) { - if (res && res.config) state.actions.config = res; - return res; - }); - } - - function renderSettingsPanel() { - var panel = document.getElementById('settings-panel'); - var gear = document.getElementById('settings-gear'); - if (!panel) return; - if (gear) gear.setAttribute('aria-expanded', state.settings.open ? 'true' : 'false'); - panel.setAttribute('aria-hidden', state.settings.open ? 'false' : 'true'); - panel.classList.toggle('open', !!state.settings.open); - if (!state.settings.open) { - panel.innerHTML = ''; - return; - } - var cfg = (state.actions.config && state.actions.config.config) || {}; - var draft = state.actions.configDraft || {}; - var value = function(key, fallback) { - if (Object.prototype.hasOwnProperty.call(draft, key)) return esc(draft[key] || ''); - return esc(cfg[key] || fallback || ''); - }; - var keyLabel = cfg.LANGEXTRACT_API_KEY_CONFIGURED ? t('settings.apiKeyKeep') : t('settings.apiKeyMissing'); - var maskedKey = cfg.LANGEXTRACT_API_KEY_MASKED || ''; - var html = '
'; - html += '
' + esc(t('settings.title')) + '
' + esc(t('settings.subtitle')) + '
'; - html += ''; - html += '
'; - html += '
' + esc(t('settings.language')) + '
'; - html += '
'; - html += ''; - html += ''; - html += '
'; - html += '
' + esc(t('settings.extractor')) + '
'; - html += '
'; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += '
' + esc(t('settings.sinceDays')) + '
'; - html += '
' + esc(t('settings.maxInteractions')) + '
'; - html += '
'; - if (maskedKey) html += '
' + esc(t('settings.apiKeyLabel')) + ' ' + esc(maskedKey) + '
'; - html += '
'; - html += '
' + esc(cfg.LANGEXTRACT_RUNTIME_READY ? t('settings.runtimeReady') : t('settings.runtimeMissing')) + (cfg.LANGEXTRACT_RUNTIME_ERROR ? ': ' + esc(cfg.LANGEXTRACT_RUNTIME_ERROR) : '') + '
'; - html += '
'; - html += ''; - if (state.actions.config && state.actions.config.envPath) html += '' + esc(state.actions.config.envPath) + ''; - html += '
'; - if (state.actions.extractMessage) html += '
' + esc(state.actions.extractMessage) + '
'; - html += '
'; - panel.innerHTML = html; - } - - function captureTodoConfigDraft() { - var draft = state.actions.configDraft || {}; - [ - 'AGENTMEMORY_TODO_EXTRACTOR', - 'LANGEXTRACT_PYTHON', - 'LANGEXTRACT_PROVIDER', - 'LANGEXTRACT_MODEL', - 'LANGEXTRACT_BASE_URL', - 'LANGEXTRACT_THINKING_DEPTH', - 'AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS', - 'AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS', - 'AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION', - 'LANGEXTRACT_API_KEY' - ].forEach(function(key) { - var el = document.getElementById('todo-config-' + key); - if (el) draft[key] = String(el.value || ''); - }); - state.actions.configDraft = draft; - return draft; - } - - function saveTodoExtractorConfig() { - var fields = [ - 'AGENTMEMORY_TODO_EXTRACTOR', - 'LANGEXTRACT_PYTHON', - 'LANGEXTRACT_PROVIDER', - 'LANGEXTRACT_MODEL', - 'LANGEXTRACT_BASE_URL', - 'LANGEXTRACT_THINKING_DEPTH', - 'AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS', - 'AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS', - 'AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION', - 'LANGEXTRACT_API_KEY' - ]; - var body = {}; - var draft = captureTodoConfigDraft(); - fields.forEach(function(key) { - if (String(draft[key] || '').trim()) body[key] = String(draft[key] || '').trim(); - }); - state.actions.configSaving = true; - renderSettingsPanel(); - apiPost('config/todo-extractor', body).then(function(res) { - if (res && res.config) state.actions.config = res; - state.actions.configDraft = {}; - state.actions.forceNextExtract = true; - state.actions.extractMessage = t('settings.savedReady'); - }).catch(function() { - state.actions.extractStatus = 'error'; - state.actions.extractMessage = t('settings.saveFailed'); - }).then(function() { - state.actions.configSaving = false; - renderSettingsPanel(); - if (state.activeTab === 'actions') renderActions(); - }); - } - - function updateActionStatus(actionId, status) { - if (!actionId || !status) return; - apiPost('actions/update', { actionId: actionId, status: status }).then(function(res) { - if (!res || res.success === false) { - state.actions.extractStatus = 'error'; - state.actions.extractMessage = t('act.status.updateFailed'); - renderActions(); - return; - } - state.actions.items = (state.actions.items || []).map(function(a) { - return a.id === actionId ? Object.assign({}, a, { status: status, updatedAt: new Date().toISOString() }) : a; - }); - renderActions(); - }).catch(function() { - state.actions.extractStatus = 'error'; - state.actions.extractMessage = t('act.status.updateFailed'); - renderActions(); - }); - } - - function cardRefreshKeptNotice(reason) { - var key = { - 'incomplete-title': 'act.cardRefresh.kept.incompleteTitle', - 'evidence-invalid': 'act.cardRefresh.kept.evidenceInvalid', - 'low-quality': 'act.cardRefresh.kept.lowQuality', - 'low-confidence': 'act.cardRefresh.kept.lowQuality', - 'completed-or-history': 'act.cardRefresh.kept.completedOrHistory', - 'polluted': 'act.cardRefresh.kept.polluted' - }[String(reason || '')]; - return key ? t(key) : t('act.cardRefresh.kept'); - } - - function refreshActionCard(actionId) { - if (!actionId) return; - state.actions.cardRefreshInFlight = state.actions.cardRefreshInFlight || {}; - if (state.actions.cardRefreshInFlight[actionId]) return; - state.actions.cardRefreshInFlight[actionId] = true; - state.actions.cardRefreshNotice = ''; - renderActions(); - apiPost('todo/action-refresh', { actionId: actionId }).then(function(res) { - if (!res || res.success === false) { - state.actions.cardRefreshNotice = t('act.cardRefresh.error'); - return null; - } - if (res.action && res.action.id) { - state.actions.items = (state.actions.items || []).map(function(a) { - return a.id === res.action.id ? res.action : a; - }); - state.actions.cardRefreshNotice = t('act.cardRefresh.done'); - return null; - } - state.actions.cardRefreshNotice = cardRefreshKeptNotice(res.reason); - return null; - }).catch(function() { - state.actions.cardRefreshNotice = t('act.cardRefresh.error'); - }).then(function() { - delete state.actions.cardRefreshInFlight[actionId]; - renderActions(); - }); - } - - function startTodoExtraction(force) { - if (state.actions.extractInFlight) return; - var shouldForce = force === true || state.actions.forceNextExtract === true; - state.actions.forceNextExtract = false; - state.actions.extractInFlight = true; - state.actions.extractStatus = 'running'; - state.actions.extractMessage = t('act.extract.starting'); - if (state.activeTab === 'actions') renderActions(); - var softRefreshDone = false; - var softRefreshTimer = setTimeout(function() { - if (!state.actions.extractInFlight || softRefreshDone) return; - softRefreshDone = true; - state.actions.extractMessage = t('act.extract.background'); - if (actionsScrolledAway()) { - state.actions.stale = true; - return; - } - if (state.activeTab === 'actions') renderActions(); - refreshActionListsAfterExtract().then(function() { - if (!state.actions.extractInFlight) return; - if (actionsScrolledAway()) { - state.actions.stale = true; - return; - } - if (state.activeTab === 'actions') renderActions(); - }).catch(function() {}); - }, 12000); - // STEP-11: scope is governed by saved settings (sinceDays + max - // interactions per session) which the backend reads from env config; - // don't hard-code maxSessions/maxObservationsPerSession here or the - // settings would never take effect on this primary extraction path. - apiPost('todo-extract/generate', { - force: shouldForce - }).then(function(result) { - if (!result) { - state.actions.extractStatus = 'error'; - state.actions.extractMessage = t('act.extract.failedExisting'); - clearTimeout(softRefreshTimer); - state.actions.extractInFlight = false; - if (state.activeTab === 'actions' && !actionsScrolledAway()) renderActions(); - return; - } - if (result.accepted) { - state.actions.extractMessage = t('act.extract.background'); - scheduleTodoExtractStatusPoll(); - return; - } - var delta = todoExtractionDelta(result); - if (result.success !== true) { - state.actions.extractStatus = 'error'; - state.actions.extractMessage = t('act.extract.failedExisting'); - } else { - state.actions.extractStatus = 'done'; - state.actions.extractFallback = !todoExtractionUsedLlm(result); - state.actions.extractPartial = result && result.engine === 'mixed'; - state.actions.extractMessage = todoExtractionSummary(result); - } - clearTimeout(softRefreshTimer); - state.actions.extractInFlight = false; - if (actionsScrolledAway()) { - state.actions.stale = delta > 0; - return; - } - refreshActionListsAfterExtract().then(function() { - if (state.activeTab === 'actions' && !actionsScrolledAway()) renderActions(); - }); - }).catch(function() { - state.actions.extractStatus = 'error'; - state.actions.extractMessage = t('act.extract.failedExisting'); - clearTimeout(softRefreshTimer); - state.actions.extractInFlight = false; - if (state.activeTab === 'actions' && !actionsScrolledAway()) renderActions(); - }); - } - - function todoCleanupSummary(r) { - if (!r) return ''; - return t('act.cleanup.summary') - .replace('{dropped}', r.dropped || 0) - .replace('{completed}', r.completed || 0) - .replace('{rewritten}', r.rewritten || 0) - .replace('{merged}', r.merged || 0); - } - - // LLM card update (STEP-12): re-judge cards whose source session changed. - // Always dry-run first so the user confirms before anything is dropped/closed; - // apply only on confirm (or silently when there is nothing to confirm — see - // the all-KEEP branch below). - function startTodoUpdate() { - if (state.actions.cleanupInFlight || state.actions.extractInFlight) return; - state.actions.cleanupInFlight = true; - state.actions.cleanupStatus = 'running'; - state.actions.cleanupMessage = t('act.cleanup.running'); - if (state.activeTab === 'actions') renderActions(); - apiPost('todo/update', { mode: 'dry-run' }).then(function(preview) { - state.actions.cleanupInFlight = false; - if (!preview || typeof preview.scanned !== 'number') { - state.actions.cleanupStatus = 'error'; - state.actions.cleanupMessage = t('act.cleanup.failed'); - if (state.activeTab === 'actions') renderActions(); - return; - } - var touched = (preview.dropped || 0) + (preview.completed || 0) + (preview.rewritten || 0) + (preview.merged || 0); - if (touched === 0) { - if (preview.fallbackReason) { - // LLM unavailable — nothing ran, nothing to advance. - state.actions.cleanupStatus = 'done'; - state.actions.cleanupMessage = t('act.cleanup.llmUnavailable'); - if (state.activeTab === 'actions') renderActions(); - return; - } - if ((preview.scanned || 0) > 0) { - // Changed cards existed but the LLM kept them all. Silently apply so - // their checkpoints advance and they aren't re-evaluated (and re-sent - // to the LLM) on every future click. No card content changes, so no - // confirmation is needed. - state.actions.cleanupInFlight = true; - apiPost('todo/update', { mode: 'apply', decisions: preview.decisions }).then(function() { - return refreshActionListsAfterExtract(); - }).catch(function() {}).then(function() { - state.actions.cleanupInFlight = false; - state.actions.cleanupStatus = 'done'; - state.actions.cleanupMessage = t('act.cleanup.clean'); - if (state.activeTab === 'actions') renderActions(); - }); - return; - } - // Nothing changed at all. - state.actions.cleanupStatus = 'idle'; - state.actions.cleanupMessage = t('act.cleanup.none'); - if (state.activeTab === 'actions') renderActions(); - return; - } - if (!window.confirm(t('act.cleanup.confirm') + '\n\n' + todoCleanupSummary(preview))) { - state.actions.cleanupStatus = 'idle'; - state.actions.cleanupMessage = ''; - if (state.activeTab === 'actions') renderActions(); - return; - } - state.actions.cleanupInFlight = true; - state.actions.cleanupStatus = 'running'; - state.actions.cleanupMessage = t('act.cleanup.applying'); - if (state.activeTab === 'actions') renderActions(); - apiPost('todo/update', { mode: 'apply', decisions: preview.decisions }).then(function(result) { - state.actions.cleanupStatus = 'done'; - state.actions.cleanupMessage = todoCleanupSummary(result || preview); - return refreshActionListsAfterExtract(); - }).catch(function() { - state.actions.cleanupStatus = 'error'; - state.actions.cleanupMessage = t('act.cleanup.failed'); - }).then(function() { - state.actions.cleanupInFlight = false; - if (state.activeTab === 'actions') renderActions(); - }); - }).catch(function() { - state.actions.cleanupInFlight = false; - state.actions.cleanupStatus = 'error'; - state.actions.cleanupMessage = t('act.cleanup.failed'); - if (state.activeTab === 'actions') renderActions(); - }); - } - - // 待办→证据跳转:待办卡指向裸 obs_id,证据栏按 session 组织, - // 后端无 obs_id→session 反查端点。遍历已加载会话,缓存优先、未命中才请求,命中即停。 - async function resolveObsSession(obsId) { - if (!obsId) return null; - var sessions = state.sessions.items || []; - for (var i = 0; i < sessions.length; i++) { - var s = sessions[i]; - var id = sessionId(s); - if (!id) continue; - var obs = null; - var cached = state.sessions.detailCacheById && state.sessions.detailCacheById[id]; - if (cached && Array.isArray(cached.observations)) { - obs = cached.observations; - } else if (Array.isArray(s.embeddedObservations) && s.embeddedObservations.length) { - obs = s.embeddedObservations; - } - if (obs && obs.some(function(o) { return o && o.id === obsId; })) return id; - if (obs) continue; - try { - var res = await apiGet('observations?sessionId=' + encodeURIComponent(id)); - var list = (res && res.observations) || []; - if (list.some(function(o) { return o && o.id === obsId; })) return id; - } catch (e) { /* 单个会话取失败不阻断扫描 */ } - } - return null; - } - - async function jumpToEvidence(obsId) { - if (!obsId) return; - if (!state.sessions.loaded) await loadSessions({ showLoading: false, reason: 'jump' }); - var targetSessionId = await resolveObsSession(obsId); - if (!targetSessionId) { - flashHint('未找到这条待办的来源会话(可能尚未同步或已被过滤)'); - return; - } - state.sessions.selectedId = targetSessionId; - state.sessions.pendingHighlightObsId = obsId; - // 来源观测画在「完整对话过程」区,默认折叠 → 跳转前强制展开,否则锚点不存在 - if (!state.sessions.detailSectionsById) state.sessions.detailSectionsById = {}; - var sec = state.sessions.detailSectionsById[targetSessionId] || { highlights: true, process: false }; - sec.process = true; - state.sessions.detailSectionsById[targetSessionId] = sec; - switchTab('sessions'); - renderSessions(); - await renderSessionDetail(); - } - - function flashHint(message) { - var el = document.getElementById('jump-hint'); - if (!el) { - el = document.createElement('div'); - el.id = 'jump-hint'; - el.setAttribute('role', 'status'); - el.setAttribute('aria-live', 'polite'); - el.style.cssText = 'position:fixed;left:50%;bottom:28px;transform:translateX(-50%);background:var(--ink,#1a1a1a);color:#fff;padding:10px 16px;border-radius:8px;font-size:13px;z-index:9999;box-shadow:0 4px 16px rgba(0,0,0,0.2);max-width:80vw;'; - (document.body || document.documentElement).appendChild(el); - } - el.textContent = message; - el.style.opacity = '1'; - if (flashHint._t) clearTimeout(flashHint._t); - flashHint._t = setTimeout(function() { if (el) el.style.opacity = '0'; }, 2600); - } - - function applyPendingHighlight() { - var obsId = state.sessions.pendingHighlightObsId; - if (!obsId) return; - state.sessions.pendingHighlightObsId = null; - var anchor = document.getElementById('obs-anchor-' + obsId); - if (!anchor) return; - if (typeof anchor.scrollIntoView === 'function') anchor.scrollIntoView({ behavior: 'smooth', block: 'center' }); - anchor.classList.add('obs-jump-highlight'); - setTimeout(function() { anchor.classList.remove('obs-jump-highlight'); }, 2400); - } - - // STEP-C2/C3 待回应分区:接真实 inbox 数据(GET /agentmemory/inbox?status=awaiting)。 - // 按 kind 分两子区——question(🔴 Agent 在等你回)与 briefing(📋 Agent 主动整理), - // 均按 createdAt 倒序(本轮不按 priority)。C3 加动作:question 可「回应」(行内输入 - // → inbox-answer)/「转待处理」(inbox-dismiss + action-create)/「知道了」(空 answer); - // briefing 可「知道了」/「转待处理」。「看原文 →」复用 STEP-03。 - function inboxFromLabel(item) { - var from = item && item.fromAgent ? String(item.fromAgent) : ''; - return from ? '来自 ' + esc(from) : ''; - } - function inboxEvidenceButton(item) { - var ids = (item && item.sourceObservationIds) || []; - if (!ids.length) return ''; - return ''; - } - // STEP-D4 飞书投递状态徽标(只读,低干扰)。数据来自 GET /inbox 里 join 的 delivery - // 台账(mem:delivery)。sent→已推送✓;failed→推送失败⚠+短错误摘要;skipped(投递 - // 未开启/无配置)默认不显示,避免污染 UI。 - function inboxDeliveryBadge(item) { - var d = item && item.delivery; - if (!d || !d.status) return ''; - if (d.status === 'sent') { - var u = d.urgent ? ' · 加急' : ''; - return '已推送 ✓' + esc(u) + ''; - } - if (d.status === 'failed') { - var err = d.error ? String(d.error) : ''; - if (err.length > 60) err = err.slice(0, 60) + '…'; - var titleErr = d.error ? ' title="' + esc(String(d.error)) + '"' : ''; - return '推送失败 ⚠' + - (err ? ' ' + esc(err) + '' : '') + ''; - } - return ''; // skipped → silent - } - function inboxAwaiting() { - var s = state.inbox || {}; - if (Array.isArray(s.awaitingItems) && s.awaitingItems.length) return s.awaitingItems; - return Array.isArray(s.items) ? s.items : []; - } - function inboxAnswered() { - var s = state.inbox || {}; - return Array.isArray(s.answeredItems) ? s.answeredItems : []; - } - function inboxDismissed() { - var s = state.inbox || {}; - return Array.isArray(s.dismissedItems) ? s.dismissedItems : []; - } - function sortInboxItems(items) { - return (items || []).slice().sort(function(a, b) { - return new Date((b && (b.answeredAt || b.dismissedAt || b.updatedAt || b.createdAt)) || 0).getTime() - - new Date((a && (a.answeredAt || a.dismissedAt || a.updatedAt || a.createdAt)) || 0).getTime(); - }); - } - function inboxSearchText(item) { - return [ - item && item.body, - item && item.answer, - item && item.fromAgent, - item && item.status, - item && item.kind, - ].filter(Boolean).join(' ').toLowerCase(); - } - function filterInboxItems(items, search) { - if (!search) return items || []; - return (items || []).filter(function(item) { return inboxSearchText(item).indexOf(search) >= 0; }); - } - function inboxRecent(item) { - var ts = item && (item.answeredAt || item.dismissedAt || item.updatedAt || item.createdAt); - if (!ts) return false; - var t = new Date(ts).getTime(); - return Number.isFinite(t) && Date.now() - t <= 24 * 60 * 60 * 1000; - } - function renderInboxCard(item, kind) { - var marker = kind === 'question' ? '🔴' : '📋'; - var from = inboxFromLabel(item); - var id = (item && item.id) || ''; - var html = '
'; - html += '
'; - html += ''; - if (from) html += '' + from + ''; - if (item && item.createdAt) html += '' + esc(shortTime(item.createdAt)) + ''; - html += inboxDeliveryBadge(item); - html += '
'; - html += '
' + renderMarkdownSafe((item && item.body) || '') + '
'; - - var pending = isInboxPending(id); - var dis = pending ? ' disabled aria-busy="true"' : ''; - html += '
'; - if (kind === 'question') { - html += ''; - html += ''; - html += ''; - } else { - html += ''; - html += ''; - } - html += inboxEvidenceButton(item); - html += '
'; - - if (kind === 'question' && state.inbox.replyingId === id) { - var sd = pending ? ' disabled aria-busy="true"' : ''; - html += '
'; - html += ''; - html += '
'; - html += ''; - html += ''; - html += '
'; - } - html += '
'; - return html; - } - function renderInboxArchiveCard(item) { - var kind = item && item.kind === 'question' ? 'question' : 'briefing'; - var answered = item && item.status === 'answered'; - var marker = kind === 'question' ? '✅' : '✓'; - var cls = answered && kind === 'question' ? 'inbox-card-answered' : 'inbox-card-known'; - var from = inboxFromLabel(item); - var ts = (item && (item.answeredAt || item.dismissedAt || item.updatedAt || item.createdAt)) || ''; - var html = '
'; - html += '
'; - html += ''; - if (from) html += '' + from + ''; - if (ts) html += '' + esc(shortTime(ts)) + ''; - html += '
'; - html += '
' + renderMarkdownSafe((item && item.body) || '') + '
'; - if (answered && kind === 'question') { - html += '
你已回复:' + esc((item && item.answer) || '知道了') + '
'; - } - html += '
'; - return html; - } - function renderAwaitingQuestionSection() { - var search = (state.actions.search || '').toLowerCase(); - var items = filterInboxItems(sortInboxItems(inboxAwaiting()), search); - var questions = items.filter(function(i) { return i && i.kind === 'question'; }); - if (!questions.length) return ''; - var html = '
'; - html += '
'; - html += '
' + t('act.section.awaiting') + ' (' + questions.length + ')
'; - html += '
Agent 运行中抛给你的、时间敏感的问题会汇集到这里。
'; - html += '
Agent 在等你回
'; - html += '
'; - questions.forEach(function(it) { html += renderInboxCard(it, 'question'); }); - html += '
'; - return html; - } - function renderAwaitingBriefingSection() { - var search = (state.actions.search || '').toLowerCase(); - var items = filterInboxItems(sortInboxItems(inboxAwaiting()), search); - var briefings = items.filter(function(i) { return i && i.kind === 'briefing'; }); - if (!briefings.length) return ''; - var bExpanded = !!state.inbox.briefingExpanded || !!search; - var html = '
'; - html += ''; - if (bExpanded) { - html += '
'; - briefings.forEach(function(it) { html += renderInboxCard(it, 'briefing'); }); - html += '
'; - } - html += '
'; - return html; - } - function renderInboxArchiveSection() { - var search = (state.actions.search || '').toLowerCase(); - var answered = inboxAnswered(); - var dismissed = inboxDismissed(); - var all = sortInboxItems(answered.concat(dismissed)); - var visible = filterInboxItems(all, search); - if (!visible.length) return ''; - var answeredQuestions = visible.filter(function(i) { return i && i.kind === 'question' && i.status === 'answered'; }); - var known = visible.filter(function(i) { return !(i && i.kind === 'question' && i.status === 'answered'); }); - var recentCount = visible.filter(inboxRecent).length; - var expanded = !!state.inbox.answeredExpanded || !!search; - var title = answeredQuestions.length ? '已回应 ' + answeredQuestions.length + ' 条' : '已回应'; - if (recentCount && !expanded) title += ' · 最近 ' + recentCount + ' 条'; - var html = '
'; - html += ''; - if (expanded) { - if (answeredQuestions.length) { - html += '
'; - answeredQuestions.forEach(function(it) { html += renderInboxArchiveCard(it); }); - html += '
'; - } - if (known.length) { - html += '
已知悉 (' + known.length + ')briefing / 已消解
'; - html += '
'; - known.forEach(function(it) { html += renderInboxArchiveCard(it); }); - html += '
'; - } - } - html += '
'; - return html; - } - // STEP-C3 收件箱动作。每个动作改后端状态后,从 state.inbox.items 本地剔除该项 + - // 重渲染(乐观更新),避免全量 loadActions 抖动;失败用 flashHint 提示。 - function removeInboxItemLocal(id) { - state.inbox.items = (state.inbox.items || []).filter(function(i) { return i && i.id !== id; }); - state.inbox.awaitingItems = (state.inbox.awaitingItems || []).filter(function(i) { return i && i.id !== id; }); - if (state.inbox.replyingId === id) state.inbox.replyingId = null; - } - function findInboxItem(id) { - return inboxAwaiting().filter(function(i) { return i && i.id === id; })[0] || null; - } - function archiveInboxItemLocal(id, status, answer) { - var item = findInboxItem(id); - removeInboxItemLocal(id); - if (!item) return; - var archived = Object.assign({}, item, { - status: status, - answer: answer || item.answer || '', - updatedAt: new Date().toISOString(), - }); - if (status === 'answered') { - archived.answeredAt = archived.updatedAt; - state.inbox.answeredItems = [archived].concat(state.inbox.answeredItems || []); - } else { - archived.dismissedAt = archived.updatedAt; - state.inbox.dismissedItems = [archived].concat(state.inbox.dismissedItems || []); - } - } - // 防重入:动作进行中标记 pendingById[id],按钮渲染为 disabled, - // 慢网下连点不会重复提交(尤其转待处理的 create 会重复建待办)。 - function isInboxPending(id) { - return !!(state.inbox.pendingById && state.inbox.pendingById[id]); - } - function setInboxPending(id, on) { - if (!state.inbox.pendingById) state.inbox.pendingById = {}; - if (on) state.inbox.pendingById[id] = true; - else delete state.inbox.pendingById[id]; - } - function openInboxReply(id) { - state.inbox.replyingId = (state.inbox.replyingId === id) ? null : id; - renderActions(); - if (state.inbox.replyingId === id) { - var input = document.getElementById('inbox-reply-input-' + id); - if (input && typeof input.focus === 'function') input.focus(); - } - } - function cancelInboxReply() { - state.inbox.replyingId = null; - renderActions(); - } - async function submitInboxReply(id) { - if (isInboxPending(id)) return; - var input = document.getElementById('inbox-reply-input-' + id); - var answer = input ? String(input.value || '').trim() : ''; - if (!answer) { flashHint('回应内容不能为空'); if (input && input.focus) input.focus(); return; } - setInboxPending(id, true); - renderActions(); - try { - var res = await apiPost('inbox/answer', { id: id, answer: answer }); - if (!res || res.success !== true) { flashHint('回应未送达,请确认本地 worker 在运行'); return; } - archiveInboxItemLocal(id, 'answered', answer); - flashHint('已回应,Agent 会看到'); - } finally { - setInboxPending(id, false); - renderActions(); - } - } - async function ackInboxItem(id) { - if (isInboxPending(id)) return; - setInboxPending(id, true); - renderActions(); - try { - // 空 answer = 已读/ack(briefing 或无需回的 question)。 - var res = await apiPost('inbox/answer', { id: id }); - if (!res || res.success !== true) { flashHint('操作未送达,请确认本地 worker 在运行'); return; } - archiveInboxItemLocal(id, 'answered', ''); - flashHint('已标记知道了'); - } finally { - setInboxPending(id, false); - renderActions(); - } - } - async function convertInboxToTodo(id) { - if (isInboxPending(id)) return; - // 转待处理:先建 action(标题取 body),成功后再 dismiss inbox 项; - // 串行 + 失败回滚提示,避免「dismiss 了却没建 action」的丢条目。 - var item = findInboxItem(id); - if (!item) return; - setInboxPending(id, true); - renderActions(); - try { - var title = String(item.body || '').trim().slice(0, 120) || '(来自收件箱)'; - var created = await apiPost('actions', { title: title, createdBy: 'inbox', project: item.project }); - if (!created || created.success !== true) { flashHint('转待处理失败:未能创建待办'); return; } - var dismissed = await apiPost('inbox/dismiss', { id: id }); - if (!dismissed || dismissed.success !== true) { - flashHint('已建待办,但收件箱项未消解,请手动「知道了」'); - state.actions.loaded = false; - loadActions({ generate: false }); - return; - } - archiveInboxItemLocal(id, 'dismissed', ''); - state.actions.loaded = false; - loadActions({ generate: false }); - flashHint('已转入待处理'); - } finally { - setInboxPending(id, false); - renderActions(); - } - } - - // STEP-C4 Done 折叠区:只读现有 action.status==='done' 且当天 updatedAt 的项, - // 默认折叠(§3.2)。不新增抽取器、不动后端,纯前端筛 state.actions.items。 - function isUpdatedToday(ts) { - if (!ts) return false; - var d = new Date(ts); - if (isNaN(d.getTime())) return false; - var now = new Date(); - return d.getFullYear() === now.getFullYear() && - d.getMonth() === now.getMonth() && - d.getDate() === now.getDate(); - } - function renderDoneTodaySection(doneItems, frontierIds, cardRenderer) { - var today = (doneItems || []).filter(function(a) { return isUpdatedToday(a.updatedAt); }); - if (!today.length) return ''; - var expanded = !!state.actions.doneExpanded; - var html = '
'; - html += ''; - if (expanded && typeof cardRenderer === 'function') { - html += '
'; - today.forEach(function(a) { - html += cardRenderer(a, frontierIds.has(a.id)); - }); - html += '
'; - } - html += '
'; - return html; - } - - function renderActions() { - var el = document.getElementById('view-actions'); - var items = (state.actions.items || []).filter(isActionRenderable).slice(); - state.actions.reviewItems = []; - var search = state.actions.search.toLowerCase(); - var statusFilter = state.actions.statusFilter === 'all' ? '' : state.actions.statusFilter; - if (['attention', 'awaiting', 'review', 'pending', 'blocked', 'active'].indexOf(statusFilter) >= 0) statusFilter = 'todo'; - var defaultView = !statusFilter && !search; - var FOCUS_DAYS = 3; - var STALE_DAYS = 10; - var todoFilterActive = statusFilter === 'todo'; - var frontierIds = new Set((state.actions.frontier || []).map(function(a) { return a.id; })); - - if (search) { - items = items.filter(function(a) { - var chain = (a && a.metadata && a.metadata.todoChain) || {}; - return (a.title + ' ' + (a.description || '') + ' ' + (a.tags || []).join(' ') + ' ' + (a.project || '') + ' ' + - (chain.completionSummary || '') + ' ' + (chain.latestStatus || '') + ' ' + (chain.nextStep || '')).toLowerCase().indexOf(search) >= 0; - }); - } - var metricItems = items.slice(); - if (statusFilter) { - items = items.filter(function(a) { - return statusFilter === 'todo' - ? (a.status === 'pending' || a.status === 'blocked' || a.status === 'active') - : a.status === statusFilter; - }); - } - function actionAttentionKey(a, isFrontier) { - if (a.status === 'done' || a.status === 'cancelled') return ''; - if (isFrontier) return 'next'; - if (a.status === 'blocked') return 'needsWork'; - if (a.priority === 'high' || Number(a.priority) >= 8) return 'noteworthy'; - return ''; - } - function actionDescriptionText(text) { - var s = String(text || '').trim(); - if (!s) return ''; - s = todoDisplayText(s); - if (I18N_LANG !== 'zh') return s; - var map = [ - [/^Execute launch promotion for GitHub, Xiaohongshu, V2EX, Reddit, X, and other target communities\..*$/i, '继续推进 GitHub、小红书、V2EX、Reddit 和 X 等渠道的发布推广。'], - [/^Execute launch promotion for (.+)$/i, '继续推进 $1 的发布推广。'], - [/^Create a Notion-style continuous-motion 30s README demo[::]?\s*(.+)$/i, '制作 30 秒 README 演示,展示从输入到结果预览的完整路径。'], - [/^Use huashu-design video workflow to produce a short README\/social demo showing how the study abroad applications skill works[::]?\s*(.+)$/i, '制作一段适合 README 和社媒使用的短演示,说明留学申请 Skill 的使用过程。'], - [/^Use huashu-design video workflow to produce a short README\/social demo showing how the study abroad applications skill works\.?$/i, '制作一段适合 README 和社媒使用的短演示,说明留学申请 Skill 的使用过程。'], - [/^Use huashu-design video workflow to produce a short README\/social demo showing how (.+?) works[::]?\s*(.+)$/i, '制作一段适合 README 和社媒使用的短演示,说明 $1 的使用过程。'], - [/^Use huashu-design video workflow to produce a short README\/social demo showing how (.+?) works\.?$/i, '制作一段适合 README 和社媒使用的短演示,说明 $1 的使用过程。'], - [/^来源:(.+)$/i, '来自 $1'], - [/^来自本地审计日志$/i, '来自本地操作记录'] - ]; - for (var i = 0; i < map.length; i++) { - if (map[i][0].test(s)) return s.replace(map[i][0], map[i][1]); - } - return s; - } - function actionChainStatusText(a) { - var chain = (a && a.metadata && a.metadata.todoChain) || null; - if (!chain || typeof chain !== 'object') return ''; - var summary = String(chain.completionSummary || chain.latestStatus || chain.nextStep || '').trim(); - if (!summary) return ''; - summary = todoDisplayText(summary); - var state = String(chain.completionState || '').trim(); - var prefix = state === 'completed' ? '✓ ' : (state === 'interrupted' ? '⏸ ' : '→ '); - return prefix + summary; - } - function todoDisplayText(text) { - return String(text || '') - .replace(/Needs attention|Needs your reply|Needs confirmation|Needs follow-up|In progress|Follow up|To confirm|Reply queue|Reply/gi, 'Todo') - .replace(/需处理|需要你回应|需要确认|需要跟进|进行中|待跟进|待确认|待回应|待回复队列|回应/g, 'Todo'); - } - function actionSourceText(a) { - var parts = []; - if (a.project) parts.push(projectDisplayName(a.project)); - if (a.source) parts.push(a.source); - return parts.join(' · '); - } - function actionVisibleTags(tags) { - var all = Array.isArray(tags) ? tags.filter(Boolean).map(String) : []; - var visible = []; - function add(tag) { - if (tag && visible.indexOf(tag) < 0) visible.push(tag); - } - all.filter(function(tag) { - return tag === 'todo-extracted' || tag === 'todo-recheck' || /^time:[a-z_]+$/i.test(tag) || /^type:[a-z_]+$/i.test(tag); - }).forEach(add); - all.forEach(function(tag) { - if (visible.length < 5) add(tag); - }); - return visible.slice(0, 5); - } - function actionTitleText(text) { - var s = String(text || t('act.untitled')).trim(); - s = todoDisplayText(s); - if (I18N_LANG !== 'zh') return s; - var map = [ - [/^Create README 30s demo GIF and backup MP4 for taught-master-applications-skill$/i, '制作留学申请 Skill 的 README 演示视频'], - [/^Create README 30s demo GIF and backup MP4 for (.+)$/i, '制作 $1 的 README 演示视频'], - [/^Create 30-second demo GIF\/video for taught-master-applications-skill$/i, '制作留学申请 Skill 的 30 秒演示视频'], - [/^Promote\s+(.+?)\s+across target platforms$/i, '推广 $1'], - [/^Build\s+(.+)$/i, '制作 $1'], - [/^Create\s+(.+)$/i, '创建 $1'], - [/^Execute\s+(.+)$/i, '执行 $1'], - [/^Review\s+(.+)$/i, '复盘 $1'], - [/^Check\s+(.+)$/i, '检查 $1'] - ]; - for (var i = 0; i < map.length; i++) { - if (map[i][0].test(s)) return s.replace(map[i][0], map[i][1]); - } - return s; - } - function priorityLabel(value) { - if (value === 'high' || Number(value) >= 8) return t('act.prio.high'); - if (value === 'normal' || Number(value) >= 5) return t('act.prio.normal'); - return t('act.prio.low'); - } - function priorityClass(value) { - if (value === 'high' || Number(value) >= 8) return 'high'; - if (value === 'normal' || Number(value) >= 5) return 'normal'; - return 'low'; - } - function compactActionTitle(text) { - var s = String(text || t('act.untitled')).replace(/^(TODO|FIXME)\s*[::-]\s*/i, '').trim(); - var headingMatch = s.match(/#{1,6}\s*([^#\n]+?)(?=\s+#{1,6}\s|$)/); - if (headingMatch && !/^(Summary|Key Changes|Test Plan|Assumptions|Public API|Implementation)\b/i.test(headingMatch[1].trim())) { - s = headingMatch[1].trim(); - } - s = s - .replace(/#{1,6}\s*(Summary|Key Changes|Test Plan|Assumptions|Public API|Implementation)\b[\s\S]*$/i, '') - .replace(/#{1,6}\s*/g, '') - .replace(/\b(Summary|Key Changes|Test Plan|Assumptions|Public API|Implementation)\b[\s\S]*$/i, '') - .replace(/\s+/g, ' ') - .trim(); - var first = s.split(/[。!?\n]/u)[0] || s; - return truncate(actionTitleText(first.replace(/[。!?;;,.,]+$/u, '').trim() || t('act.untitled')), 72); - } - function stripMarkdownPlanText(text) { - return String(text || '') - .replace(/\s+(#{1,6}\s+)/g, '\n$1') - .split(/\n+/) - .filter(function(line) { - var trimmed = line.trim(); - if (!trimmed) return false; - if (/^#{1,6}\s/.test(trimmed)) return false; - if (/^\s*[-*]\s+/.test(line)) return false; - if (/^(Summary|Key Changes|Test Plan|Assumptions|Public API|Implementation)\b/i.test(trimmed)) return false; - if (/^(执行步骤|验证命令|停止条件|修改范围)[::]?/u.test(trimmed)) return false; - return true; - }) - .join(' ') - .replace(/\s+/g, ' ') - .trim(); - } - function actionMetricCards(counts) { - var todoCount = counts.pending + counts.blocked + counts.active; - var metrics = [ - { label: t('act.metric.todo'), value: todoCount, filter: 'todo', primary: todoCount > 0 }, - { label: t('act.metric.done'), value: counts.done, filter: 'done', primary: false } - ]; - var html = '
'; - metrics.forEach(function(m) { - var current = state.actions.statusFilter || ''; - if (['attention', 'awaiting', 'review', 'pending', 'blocked', 'active'].indexOf(current) >= 0) current = 'todo'; - var active = current === m.filter; - html += ''; - }); - html += '
'; - return html; - } - function actionNeedsRecheck(a) { - var tags = Array.isArray(a && a.tags) ? a.tags : []; - return tags.indexOf('todo-recheck') >= 0; - } - function normalizeActionTimestamp(value) { - var raw = String(value || '').trim(); - if (!raw) return ''; - var m = raw.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z/); - return m ? m[0] : raw; - } - function actionTimestamp(a) { - var extraction = (a && a.metadata && a.metadata.todoExtraction) || {}; - if (actionNeedsRecheck(a) && extraction.latestSourceCheckpoint) { - return normalizeActionTimestamp(extraction.latestSourceCheckpoint); - } - return normalizeActionTimestamp(extraction.sourceCheckpoint || (a && (a.createdAt || a.updatedAt)) || ''); - } - function actionAgeDays(a) { - var ts = actionTimestamp(a); - if (!ts) return 0; - var ms = new Date(ts).getTime(); - if (!isFinite(ms)) return 0; - return Math.max(0, (Date.now() - ms) / 86400000); - } - function isOpenAction(a) { - return a && (a.status === 'active' || a.status === 'blocked' || a.status === 'pending'); - } - function isNoisyChainOpenAction(a) { - var chain = (a && a.metadata && a.metadata.todoChain) || null; - if (!isOpenAction(a) || !chain) return false; - var statusText = String(chain.completionSummary || '') + ' ' + String(chain.latestStatus || ''); - var text = statusText + ' ' + String(a.description || ''); - var statusHasNextStep = /(下一步|仍需|还需|需要继续|需要|待处理|待确认|待跟进|blocked|阻塞|失败|卡住|error|failed)/i.test(statusText); - if (chain.completionState === 'completed') return true; - if (/|#\s*Plan Mode\b|#\s*Agent Mode\b/i.test(text)) return true; - if (/已(?:完成|提交并推送|创建|通过|合并|上传|重启|新建)|服务已重启|正常|无需后续|no action needed|completed/i.test(statusText) && !statusHasNextStep) return true; - return false; - } - function isLegacyGeneratedOpenAction(a) { - return isOpenAction(a) && a.createdBy === 'todo-extract' && !(a.metadata && a.metadata.todoChain); - } - function splitDefaultOpenItems(list) { - var split = { focus: [], earlier: [], older: [], legacy: [] }; - (list || []).forEach(function(a) { - if (!isOpenAction(a)) return; - if (isLegacyGeneratedOpenAction(a) || isNoisyChainOpenAction(a)) { - split.legacy.push(a); - return; - } - var age = actionAgeDays(a); - if (age <= FOCUS_DAYS || (actionNeedsRecheck(a) && age <= STALE_DAYS)) split.focus.push(a); - else if (age > STALE_DAYS) split.older.push(a); - else split.earlier.push(a); - }); - return split; - } - function renderActionFilters() { - var html = '
'; - html += ''; - html += actionMetricCards(statusCounts); - var extractTitle = state.actions.extractMessage || t('act.extract.title'); - var extractLabel = state.actions.extractInFlight ? t('act.extract.running') : t('act.extract.run'); - html += ''; - var cleanupLabel = t('act.cleanup.run'); - if (state.actions.cleanupInFlight) cleanupLabel = t('act.cleanup.running'); - else if (state.actions.cleanupStatus === 'done') cleanupLabel = t('act.cleanup.done'); - else if (state.actions.cleanupStatus === 'error') cleanupLabel = t('act.cleanup.error'); - html += ''; - html += ''; - html += '
'; - return html; - } - function renderActionCard(a, isFrontier) { - var html = '
'; - html += '
'; - html += '
'; - html += '
' + esc(compactActionTitle(a.title)) + '
'; - var actionDesc = actionChainStatusText(a) || actionDescriptionText(a.description); - if (actionDesc) html += '
' + esc(truncate(actionDesc, 120)) + '
'; - if (actionNeedsRecheck(a)) html += '
' + esc(t('act.recheck')) + '
'; - // STEP-16 calm card: source + relative time are hidden at rest and fade in - // on hover; priority shows on the rail, status via the group. No badges / - // status icon / classification tags. - var metaParts = []; - var sourceText = actionSourceText(a); - var sourceTs = actionTimestamp(a); - if (sourceText) metaParts.push(t('act.from') + ' ' + esc(sourceText)); - if (sourceTs) metaParts.push(esc(relativeTime(sourceTs))); - if (metaParts.length) html += '
' + metaParts.join(' · ') + '
'; - html += '
'; - html += '
'; - html += '
'; - var jumpObsId = Array.isArray(a.sourceObservationIds) ? a.sourceObservationIds.find(function(id) { return typeof id === 'string' && id.length > 0; }) : ''; - var refreshing = !!(state.actions.cardRefreshInFlight && state.actions.cardRefreshInFlight[a.id]); - if (jumpObsId) html += ''; - html += ''; - if (a.status !== 'cancelled') html += ''; - html += '
'; - if (a.status !== 'done') html += ''; - html += '
'; - html += '
'; - return html; - } - function renderActionGroup(status, group) { - if (!group.length) return ''; - var html = '
'; - var title = status === 'todo' ? t('act.metric.todo') : (status === 'done' ? t('act.metric.done') : statusLabel(status)); - html += '
' + esc(title) + '
' + group.length + ' ' + t('act.itemsUnit') + '
'; - html += '
'; - group.forEach(function(a) { - html += renderActionCard(a, frontierIds.has(a.id)); - }); - html += '
'; - return html; - } - function renderTodoGroup(group) { - var cards = Array.isArray(group) ? group : []; - var total = cards.length; - if (!total) return ''; - var html = '
'; - html += '
' + esc(t('act.metric.todo')) + '
' + total + ' ' + t('act.itemsUnit') + '
'; - html += '
'; - cards.forEach(function(a) { - html += renderActionCard(a, frontierIds.has(a.id)); - }); - html += '
'; - return html; - } - function renderFoldedOpenSection(group, title, lead, stateKey, actionName) { - if (!group.length) return ''; - var expanded = !!state.actions[stateKey]; - var html = '
'; - html += ''; - if (expanded) { - html += '
'; - group.forEach(function(a) { - html += renderActionCard(a, frontierIds.has(a.id)); - }); - html += '
'; - } - html += '
'; - return html; - } - var statusCounts = { active: 0, blocked: 0, pending: 0, done: 0, cancelled: 0 }; - metricItems.forEach(function(a) { if (statusCounts[a.status] !== undefined) statusCounts[a.status] += 1; }); - - var html = renderActionFilters(); - - if (items.length === 0) { - html += '
' + - '
' + - '
' + t('act.empty.title') + '
' + - '
' + t('act.empty.lead') + '
' + - '
'; - } else { - items = items.slice().sort(function(a, b) { - var order = { active: 1, blocked: 2, pending: 3, done: 4, cancelled: 5 }; - return (order[a.status] || 9) - (order[b.status] || 9) || (Number(b.priority) || 0) - (Number(a.priority) || 0); - }); - // STEP-C4:无筛选的默认视图里,done 不混在分组流中,改由底部 Done - // 折叠区单独承载、且只显当天完成的(§3.2/§3.3)。点了 Done 筛选 chip - // (statusFilter==='done')时则照常全列,不走折叠区。 - // STEP-12:cancelled(归档/被更新丢弃/被合并)也不进默认活动视图——否则 - // 合并/丢弃后卡片仍以「已取消」分组留在原处,看着像「没生效」。 - if (defaultView || (todoFilterActive && !search)) { - var defaultSplit = splitDefaultOpenItems(items); - html += renderTodoGroup(defaultSplit.focus); - html += renderFoldedOpenSection(defaultSplit.earlier, t('act.section.earlier'), t('act.section.earlierLead'), 'earlierOpenExpanded', 'toggle-earlier-open'); - html += renderFoldedOpenSection(defaultSplit.older, t('act.section.older'), t('act.section.olderLead'), 'olderBacklogExpanded', 'toggle-older-backlog'); - html += renderFoldedOpenSection(defaultSplit.legacy, t('act.section.legacy'), t('act.section.legacyLead'), 'legacyBacklogExpanded', 'toggle-legacy-backlog'); - if (defaultView) html += renderDoneTodaySection(items.filter(function(a) { return a.status === 'done'; }), frontierIds, renderActionCard); - } else { - if (statusFilter !== 'done') { - html += renderTodoGroup(items.filter(function(a) { return a.status === 'pending' || a.status === 'blocked' || a.status === 'active'; })); - } - if (!todoFilterActive) { - html += renderActionGroup('done', items.filter(function(a) { return a.status === 'done'; })); - } - } - } - - var __focus = captureSearchFocus(['actions-search']); - el.innerHTML = html; - var __as = document.getElementById('actions-search'); - if (__as) bindImeSafeSearch(__as, 200, function(v){ state.actions.search = v; renderActions(); }); - restoreSearchFocus(__focus); - } diff --git a/src/viewer/parts/app/62-legacy-crystals-audit-profile.js b/src/viewer/parts/app/62-legacy-crystals-audit-profile.js deleted file mode 100644 index f7dc1d9c..00000000 --- a/src/viewer/parts/app/62-legacy-crystals-audit-profile.js +++ /dev/null @@ -1,336 +0,0 @@ - async function loadCrystals() { - var el = document.getElementById('view-crystals'); - el.innerHTML = '
正在加载结晶...
'; - var results = await Promise.all([apiGet('crystals'), apiGet('lessons')]); - state.crystals.items = (results[0] && results[0].crystals) || []; - var lessonMap = {}; - var lessons = (results[1] && results[1].lessons) || []; - lessons.forEach(function(l) { if (l && l.id) lessonMap[l.id] = l; }); - state.crystals.lessonMap = lessonMap; - state.crystals.loaded = true; - renderCrystals(); - } - - function renderCrystals() { - var el = document.getElementById('view-crystals'); - var items = state.crystals.items; - var search = state.crystals.search.toLowerCase(); - var lessonMap = state.crystals.lessonMap || {}; - - if (search) { - items = items.filter(function(c) { - var lessonText = (c.lessons || []) - .map(function(lid) { - var l = lessonMap[lid]; - return l && typeof l.content === 'string' ? l.content : lid; - }) - .join(' '); - var filesText = (c.filesAffected || []).join(' '); - var haystack = [ - c.narrative || '', - (c.keyOutcomes || []).join(' '), - lessonText, - filesText, - c.project || '', - ].join(' ').toLowerCase(); - return haystack.indexOf(search) >= 0; - }); - } - - var html = '
'; - html += '
工作沉淀
'; - html += '
这里保留已经整理过的工作结果:做了什么、留下了哪些文件、能复用什么经验。
'; - html += '
' + items.length + ' 条
'; - html += '
'; - - html += '
'; - html += ''; - html += '
'; - - if (items.length === 0) { - html += '
' + - '
💎
' + - '
还没有结晶
' + - '
结晶是一次完整工作的压缩摘要,用来保留关键结论、涉及文件和可复用经验。当前还没有生成结晶,但会话和记忆已经可以先使用。
' + - '
导入历史会话后,可以把重要会话整理成结晶;后续我会优先把这里做成一键整理。
' + - '' + - '
'; - } else { - html += '
'; - items.forEach(function(c) { - html += '
'; - html += '
'; - html += '
' + esc(truncate(c.narrative || '未命名工作', 180)) + '
'; - html += '
' + esc(shortDateTime(c.createdAt)) + '
'; - html += '
'; - - var pillRow = []; - if (c.project) pillRow.push('' + esc(projectDisplayName(c.project)) + ''); - if (c.filesAffected && c.filesAffected.length) pillRow.push('' + c.filesAffected.length + ' 个文件'); - if (c.lessons && c.lessons.length) pillRow.push('' + c.lessons.length + ' 条经验'); - if (pillRow.length) html += '
' + pillRow.join('') + '
'; - - if (c.filesAffected && c.filesAffected.length > 0) { - html += '
'; - html += '
'; - c.filesAffected.slice(0, 4).forEach(function(f) { - html += '
' + esc(f) + '
'; - }); - if (c.filesAffected.length > 4) html += '
还有 ' + (c.filesAffected.length - 4) + ' 个文件
'; - html += '
'; - } - - if (c.lessons && c.lessons.length > 0) { - html += '
'; - html += '
'; - c.lessons.slice(0, 3).forEach(function(lid) { - var content = lessonMap[lid] ? lessonMap[lid].content : lid; - html += '
' + esc(content) + '
'; - }); - if (c.lessons.length > 3) html += '
还有 ' + (c.lessons.length - 3) + ' 条经验
'; - html += '
'; - } - - html += '
'; - }); - html += '
'; - } - - var __focus = captureSearchFocus(['crystals-search']); - el.innerHTML = html; - var __cs = document.getElementById('crystals-search'); - if (__cs) bindImeSafeSearch(__cs, 200, function(v){ state.crystals.search = v; renderCrystals(); }); - restoreSearchFocus(__focus); - } - - async function loadAudit() { - var el = document.getElementById('view-audit'); - el.innerHTML = '
加载审计日志中...
'; - var result = await apiGet('audit?limit=100'); - state.audit.entries = (result && result.entries) || []; - state.audit.loaded = true; - renderAudit(); - } - - function renderAudit() { - var el = document.getElementById('view-audit'); - var entries = state.audit.entries; - var opFilter = state.audit.opFilter; - - var ops = {}; - entries.forEach(function(e) { ops[e.operation] = true; }); - var opList = Object.keys(ops).sort(); - - var filtered = opFilter ? entries.filter(function(e) { return e.operation === opFilter; }) : entries; - - var html = '
'; - html += ''; - html += ''; - html += '
'; - - html += '
'; - if (filtered.length === 0) { - if (entries.length === 0) { - html += '
📜

暂无审计记录

当发生删除、演化、合并等治理操作后,这里会出现记录。

'; - } else { - html += '
🔍

当前筛选下无结果

'; - } - } else { - filtered.forEach(function(a, idx) { - var badgeClass = OP_BADGES[a.operation] || 'badge-muted'; - html += '
'; - html += '
'; - html += '' + esc(a.operation) + ''; - html += '' + esc(a.functionId || '') + ''; - html += '' + esc(formatTime(a.timestamp)) + ''; - html += ''; - html += '
'; - if (a.targetIds && a.targetIds.length) { - html += '
' + a.targetIds.length + ' 个目标: ' + esc(a.targetIds.slice(0, 3).join(', ')) + (a.targetIds.length > 3 ? '...' : '') + '
'; - } - html += '
' + esc(JSON.stringify(a.details || {}, null, 2)) + '
'; - html += '
'; - }); - } - html += '
'; - - el.innerHTML = html; - - document.getElementById('audit-op-filter').addEventListener('change', function() { - state.audit.opFilter = this.value; - renderAudit(); - }); - } - - function toggleAuditDetail(idx) { - var el = document.getElementById('audit-detail-' + idx); - if (el) el.classList.toggle('open'); - } - - async function loadProfile() { - var el = document.getElementById('view-profile'); - el.innerHTML = '
加载画像中...
'; - var sessResult = await apiGet('sessions'); - var sessions = (sessResult && sessResult.sessions) || []; - - var projects = {}; - sessions.forEach(function(s) { if (s.project) projects[s.project] = true; }); - state.profile.projects = Object.keys(projects).sort(); - state.profile.loaded = true; - - if (state.profile.projects.length > 0 && !state.profile.selectedProject) { - state.profile.selectedProject = state.profile.projects[0]; - } - - renderProfileToolbar(); - if (state.profile.selectedProject) await loadProfileData(); - } - - function renderProfileToolbar() { - var el = document.getElementById('view-profile'); - var html = '
'; - html += '
'; - html += '
'; - el.innerHTML = html; - - document.getElementById('profile-project').addEventListener('change', function() { - state.profile.selectedProject = this.value; - loadProfileData(); - }); - } - - async function loadProfileData() { - var content = document.getElementById('profile-content'); - if (!content || !state.profile.selectedProject) return; - content.innerHTML = '
加载画像数据中...
'; - var result = await apiGet('profile?project=' + encodeURIComponent(state.profile.selectedProject)); - state.profile.data = (result && result.profile) ? result.profile : result; - renderProfile(); - } - - function renderProfile() { - var content = document.getElementById('profile-content'); - if (!content) return; - var p = state.profile.data; - - if (!p) { - content.innerHTML = '
📋

该项目暂无画像数据

'; - return; - } - - var html = '
'; - - html += '
高频概念
'; - var concepts = p.topConcepts || []; - if (concepts.length === 0) { - html += '
No concepts yet
'; - } else { - var maxC = Math.max.apply(null, concepts.map(function(c) { return c.frequency; })) || 1; - html += '
'; - concepts.slice(0, 10).forEach(function(c) { - var pct = Math.round((c.frequency / maxC) * 100); - html += '
' + esc(c.concept) + '
' + c.frequency + '
'; - }); - html += '
'; - } - html += '
'; - - html += '
高频文件
'; - var files = p.topFiles || []; - if (files.length === 0) { - html += '
No files yet
'; - } else { - var maxF = Math.max.apply(null, files.map(function(f) { return f.frequency; })) || 1; - html += '
'; - files.slice(0, 10).forEach(function(f) { - var pct = Math.round((f.frequency / maxF) * 100); - html += '
' + esc(f.file.split('/').pop()) + '
' + f.frequency + '
'; - }); - html += '
'; - } - html += '
'; - - html += '
'; - - html += '
约定
'; - var conventions = p.conventions || []; - if (conventions.length === 0) { - html += '
No conventions detected yet
'; - } else { - html += '
    '; - conventions.forEach(function(c) { html += '
  • ' + esc(c) + '
  • '; }); - html += '
'; - } - html += '
'; - - if (p.summary) { - html += '
项目摘要
'; - html += '

' + esc(p.summary) + '

'; - } - - var stats = '
项目统计
'; - stats += '
Sessions
' + (p.sessionCount || 0) + '
'; - stats += '
Total Obs
' + (p.totalObservations || 0) + '
'; - stats += '
Updated
' + esc(formatTime(p.updatedAt)) + '
'; - stats += '
'; - - content.innerHTML = html + stats; - } - - var wsReconnectTimer = null; - var wsRetries = 0; - var WS_MAX_RETRIES = 4; - var directFailed = false; - var directFailures = 0; - var DIRECT_FAILURE_THRESHOLD = 2; - var pollTimer = null; - var POLL_INTERVAL_MS = 10000; - - function setWsStatus(text, cls) { - var el = document.getElementById('ws-status'); - if (!el) return; - el.textContent = text; - el.className = 'ws-status ' + cls; - } - - var WS_REPROBE_EVERY_TICKS = 6; - - function startPolling() { - if (pollTimer) return; - setWsStatus('polling · ' + (POLL_INTERVAL_MS / 1000) + 's', 'disconnected'); - var tick = 0; - pollTimer = setInterval(function() { - tick++; - refreshActiveTab('poll'); - if (tick % WS_REPROBE_EVERY_TICKS === 0) { - var ws = state.ws; - if (!ws || ws.readyState !== WebSocket.OPEN) { - wsRetries = 0; - directFailures = 0; - directFailed = false; - connectWs(); - } - } - }, POLL_INTERVAL_MS); - } - - function stopPolling() { - if (!pollTimer) return; - clearInterval(pollTimer); - pollTimer = null; - } - - var WS_CONNECT_TIMEOUT_MS = 5000; - diff --git a/src/viewer/parts/app/64-ws-flags-events.js b/src/viewer/parts/app/64-ws-flags-events.js deleted file mode 100644 index 9bd0ccb9..00000000 --- a/src/viewer/parts/app/64-ws-flags-events.js +++ /dev/null @@ -1,718 +0,0 @@ - function connectWs() { - if (wsRetries >= WS_MAX_RETRIES) { - startPolling(); - return; - } - var useDirect = !directFailed; - var ws; - try { - ws = new WebSocket(useDirect ? WS_DIRECT_URL : WS_URL); - ws.__direct = useDirect; - } catch (_) { - ws = new WebSocket(WS_URL); - ws.__direct = false; - } - var connectTimer = setTimeout(function() { - if (ws.readyState === WebSocket.CONNECTING) { - try { ws.close(); } catch {} - } - }, WS_CONNECT_TIMEOUT_MS); - try { - ws.onopen = function() { - clearTimeout(connectTimer); - if (state.ws !== ws) return; - wsRetries = 0; - stopPolling(); - if (ws.__direct) { - directFailures = 0; - directFailed = false; - } - if (!ws.__direct) { - ws.send(JSON.stringify({ - type: 'join', - data: { - subscriptionId: 'viewer-' + Date.now(), - streamName: 'mem-live', - groupId: 'viewer' - } - })); - } - setWsStatus('live', 'connected'); - }; - ws.onmessage = function(e) { - if (state.ws !== ws) return; - try { - var msg = JSON.parse(e.data); - if (msg.type === 'stream' && msg.event) { - handleStreamEvent(msg); - } else if (msg.event_type && msg.data) { - handleStreamEvent({ event: { type: 'create', data: msg.data, event_type: msg.event_type } }); - } - } catch {} - }; - ws.onclose = function() { - clearTimeout(connectTimer); - if (state.ws !== ws) return; - if (ws.__direct) { - directFailures += 1; - if (directFailures >= DIRECT_FAILURE_THRESHOLD) { - directFailed = true; - } - } - wsRetries++; - if (wsRetries < WS_MAX_RETRIES) { - setWsStatus('connecting...', 'disconnected'); - wsReconnectTimer = setTimeout(connectWs, 2000 + Math.min(wsRetries * 1000, 8000)); - } else { - startPolling(); - } - }; - ws.onerror = function() { - if (state.ws !== ws) return; - try { ws.close(); } catch {} - }; - state.ws = ws; - } catch { - wsRetries++; - if (wsRetries < WS_MAX_RETRIES) { - wsReconnectTimer = setTimeout(connectWs, 2000 + Math.min(wsRetries * 1000, 8000)); - } else { - startPolling(); - } - } - } - - function looksLikeObservation(obj) { - return !!(obj && typeof obj === 'object' && obj.id && obj.timestamp); - } - - function handleStreamEvent(msg) { - var evt = msg.event; - var observation; - if (!evt) return; - if (evt.event_type && evt.event_type !== 'observation' && evt.event_type !== 'create' && evt.event_type !== 'update') { - return; - } - if (evt.type === 'event' && evt.data) { - observation = evt.data.observation || evt.data; - if (looksLikeObservation(observation)) { - routeWsMessage({ observation: observation }); - } - return; - } - if ((evt.type === 'create' || evt.type === 'update') && evt.data) { - var payload = evt.data; - observation = payload.observation || payload; - if (looksLikeObservation(observation)) { - routeWsMessage({ observation: observation }); - } - } else if (evt.type === 'sync') { - var items = Array.isArray(evt.data) ? evt.data : []; - items.forEach(function(item) { - var payload = item.data || item; - observation = payload.observation || payload; - if (looksLikeObservation(observation)) { - routeWsMessage({ observation: observation }); - } - }); - } - } - - function routeWsMessage(msg) { - markMemoryViewsStale(); - if (state.activeTab === 'timeline' && msg.observation) { - if (!state.timeline.sessionId || msg.observation.sessionId === state.timeline.sessionId) { - var existing = state.timeline.observations.findIndex(function(o) { return o.id === msg.observation.id; }); - if (existing >= 0) { - state.timeline.observations[existing] = msg.observation; - } else { - state.timeline.observations.unshift(msg.observation); - } - renderObservations(); - } - } - if (state.activeTab === 'dashboard') { - loadDashboard(); - } - if (state.activeTab === 'memories') loadMemories(); - if (state.activeTab === 'sessions') { - if (isViewingSessionDetail()) { - markSessionsStale(); - } else { - state.sessions.loaded = false; - loadSessions({ showLoading: false, reason: 'ws' }); - } - } - if (state.activeTab === 'lessons') loadLessons(); - if (state.activeTab === 'actions') markActionsStale('有新会话记录,待办暂未自动刷新。'); - if (state.activeTab === 'activity' && msg.observation) { - state.activity.observations.unshift(msg.observation); - renderActivity(); - } - } - - document.getElementById('tab-bar').addEventListener('click', function(e) { - var toggle = e.target instanceof Element ? e.target.closest('#expert-toggle') : null; - if (toggle) { - toggleExpertMode(); - return; - } - var btn = e.target instanceof Element ? e.target.closest('button[data-tab]') : null; - if (btn) { - switchTab(btn.dataset.tab); - return; - } - }); - - document.addEventListener('click', function(e) { - if (!(e.target instanceof Element)) return; - var jump = e.target.closest('[data-dashboard-jump]'); - if (!jump) return; - var tab = jump.getAttribute('data-dashboard-jump'); - if (!tab) return; - switchTab(tab); - }); - - document.querySelectorAll('[data-tab-link]').forEach(function(link) { - link.addEventListener('click', function(e) { - if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; - e.preventDefault(); - switchTab(link.getAttribute('data-tab-link')); - }); - }); - - function syncTabFromRoute() { - switchTab(tabFromRoute(), { replaceRoute: true }); - } - window.addEventListener('hashchange', syncTabFromRoute); - window.addEventListener('popstate', syncTabFromRoute); - - // --- Feature flag banners --------------------------------------------- - function getDismissedFlags() { - if (!state.flagsDismissed) state.flagsDismissed = {}; - return state.flagsDismissed; - } - function dismissFlags(keys) { - var dismissed = getDismissedFlags(); - keys.forEach(function(key) { - if (key) dismissed[key] = true; - }); - } - function renderFlagBanners(cfg) { - var host = document.getElementById('flag-banners'); - if (!host) return; - var dismissed = getDismissedFlags(); - var banners = []; - function toHumanTitle(raw) { - if (!raw) return '功能未开启'; - var key = String(raw).toLowerCase(); - if (key.indexOf('knowledge graph') >= 0) return '关系图自动提取未开启'; - if (key.indexOf('llm provider') >= 0) return '智能分析未开启'; - if (key.indexOf('bm25-only') >= 0) return '仅关键词检索模式'; - return raw; - } - function toHumanDesc(raw) { - if (!raw) return '开启后可获得更完整的记忆分析能力。'; - var s = String(raw); - if (s.indexOf('Compression, summarization, and graph extraction') >= 0) { - return '当前只能使用基础能力;摘要、压缩和关系图能力暂不可用。'; - } - if (s.indexOf('Extracts entities and relations') >= 0) { - return '系统暂不会自动生成关系图。'; - } - if (s.indexOf('Semantic vector search is off') >= 0) { - return '当前搜索偏向关键词匹配,语义理解能力较弱。'; - } - return s; - } - // Per-flag banner (only for off flags, affecting current tab or dashboard) - (cfg.flags || []).forEach(function(f) { - if (f.enabled) return; - if (dismissed[f.key]) return; - var tabsAffected = (f.affects || []).map(function(t) { return t.toLowerCase(); }); - if (tabsAffected.length && tabsAffected.indexOf(state.activeTab) === -1 && state.activeTab !== 'dashboard') return; - banners.push({ - kind: 'warn', - icon: '◉', - title: toHumanTitle(f.label), - keyLabel: f.key, - desc: toHumanDesc(f.description) + (f.needsLlm ? ' 需要先连接智能模型。' : ''), - enable: f.enableHow, - docs: f.docsHref, - dismissKey: f.key, - }); - }); - if (cfg.provider === 'noop' && !dismissed['__provider_noop']) { - banners.unshift({ - kind: 'warn', - icon: '◉', - title: '智能分析未开启', - keyLabel: 'ANTHROPIC_API_KEY', - desc: '当前只能用基础功能;摘要、关系图与自动提炼暂不可用。', - enable: 'export ANTHROPIC_API_KEY=sk-ant-...\n# then restart: npm run start:local-memory', - docs: 'https://github.com/MaimoryLab/agentmemory-lab#quick-start', - dismissKey: '__provider_noop', - }); - } - if (cfg.embeddingProvider === 'none' && !dismissed['__embedding_none']) { - banners.push({ - kind: 'info', - icon: '◉', - title: '仅关键词检索模式', - keyLabel: 'OPENAI_API_KEY', - desc: '当前搜索偏向关键词匹配,语义理解能力较弱。', - enable: 'export OPENAI_API_KEY=sk-...\n# or VOYAGE_API_KEY, COHERE_API_KEY, OLLAMA_HOST', - docs: 'https://github.com/MaimoryLab/agentmemory-lab#embedding-providers', - dismissKey: '__embedding_none', - }); - } - if (banners.length === 0) { host.innerHTML = ''; return; } - var warnCount = banners.filter(function(b) { return b.kind === 'warn'; }).length; - var infoCount = banners.filter(function(b) { return b.kind === 'info'; }).length; - var expanded = host.getAttribute('data-expanded') === '1'; - var pills = ''; - if (warnCount) pills += '' + warnCount + ' 项待开启'; - if (infoCount) pills += '' + infoCount + ' 条提示'; - var escHtml = function(s) { - return String(s).replace(/[<>&"]/g, function(c) { - return { '<': '<', '>': '>', '&': '&', '"': '"' }[c]; - }); - }; - var listHtml = banners.map(function(b) { - return '
' + - '' + b.icon + '' + - '
' + - '
' + escHtml(b.title) + '
' + - '
' + escHtml(b.desc) + '
' + - '' + escHtml(b.enable) + '' + - (b.docs ? ' 查看设置步骤' : '') + - '
' + - '' + - '
'; - }).join(''); - host.innerHTML = '' + - '
' + listHtml + '
'; - } - async function fetchFlags() { - var res = await apiGet('config/flags'); - if (!res) return; - state.flagsConfig = res; - renderFlagBanners(res); - updateFooter(res); - } - function updateFooter(cfg) { - var fbEl = document.getElementById('footer-feedback'); - if (fbEl) { - var flagSummary = (cfg.flags || []).map(function(f) { return f.key + '=' + (f.enabled ? 'on' : 'off'); }).join(', '); - var body = encodeURIComponent( - '**Version:** ' + (cfg.version || '?') + '\n' + - '**Provider:** ' + (cfg.provider || '?') + '\n' + - '**Embedding:** ' + (cfg.embeddingProvider || '?') + '\n' + - '**Flags:** ' + flagSummary + '\n' + - '**User agent:** ' + navigator.userAgent + '\n\n' + - '### What went wrong\n\n' + - '(describe the issue)\n\n' + - '### Steps to reproduce\n\n' + - '1. \n2. \n3. \n' - ); - fbEl.href = 'https://github.com/MaimoryLab/agentmemory-lab/issues/new?title=' + - encodeURIComponent('[viewer] ') + '&body=' + body; - } - } - document.addEventListener('click', function(e) { - if (!(e.target instanceof Element)) return; - var btn = e.target.closest('[data-dismiss-flag]'); - if (btn) { - e.preventDefault(); - e.stopPropagation(); - var key = btn.getAttribute('data-dismiss-flag'); - dismissFlags([key]); - if (state.flagsConfig) renderFlagBanners(state.flagsConfig); - return; - } - var toggle = e.target.closest('[data-action="toggle-flags"]'); - if (toggle) { - var host = document.getElementById('flag-banners'); - var cur = host.getAttribute('data-expanded') === '1'; - host.setAttribute('data-expanded', cur ? '0' : '1'); - if (state.flagsConfig) renderFlagBanners(state.flagsConfig); - } - }); - fetchFlags(); - document.addEventListener('click', function(e) { - if (!(e.target instanceof Element)) return; - var target = e.target.closest('[data-action]'); - if (!target) return; - var action = target.getAttribute('data-action'); - if (!action) return; - - if (action === 'toggle-theme') { - toggleTheme(); - return; - } - if (action === 'clear-audit-filter') { - state.audit.opFilter = ''; - renderAudit(); - return; - } - if (action === 'refresh-audit') { - state.audit.loaded = false; - loadAudit(); - return; - } - if (action === 'refresh-dashboard') { - refreshDashboard(); - return; - } - if (action === 'refresh-activity') { - state.activity.loaded = false; - loadActivity(); - return; - } - if (action === 'refresh-actions') { - state.actions.loaded = false; - loadActions({ generate: false }); - return; - } - if (action === 'extract-actions') { - startTodoExtraction(true); - return; - } - if (action === 'update-cards') { - startTodoUpdate(); - return; - } - if (action === 'toggle-settings') { - state.settings.open = !state.settings.open; - renderSettingsPanel(); - if (state.settings.open && !state.actions.config) { - loadTodoExtractorConfig().then(renderSettingsPanel).catch(function() {}); - } - return; - } - if (action === 'close-settings') { - state.settings.open = false; - renderSettingsPanel(); - return; - } - if (action === 'set-ui-language') { - var lang = target.getAttribute('data-lang') || ''; - if (lang === 'zh' || lang === 'en') { - I18N_LANG = lang; - try { localStorage.setItem('agentmemory-lang', lang); } catch (_) {} - applyI18n(document); - renderSettingsPanel(); - if (state.activeTab === 'actions') renderActions(); - if (state.activeTab === 'dashboard') renderDashboard(); - if (state.activeTab === 'sessions') renderSessions(); - } - return; - } - if (action === 'save-todo-config') { - saveTodoExtractorConfig(); - return; - } - if (action === 'action-status') { - updateActionStatus(target.getAttribute('data-action-id') || '', target.getAttribute('data-status') || ''); - return; - } - if (action === 'refresh-action-card') { - refreshActionCard(target.getAttribute('data-action-id') || ''); - return; - } - if (action === 'refresh-sessions') { - state.sessions.loaded = false; - loadSessions({ showLoading: true, reason: 'manual' }); - return; - } - if (action === 'open-session-group') { - var openSessionId = target.getAttribute('data-session-id') || ''; - if (openSessionId) state.sessions.selectedId = openSessionId; - state.sessions.loaded = false; - switchTab('sessions'); - return; - } - if (action === 'jump-to-evidence') { - jumpToEvidence(target.getAttribute('data-obs-id') || ''); - return; - } - if (action === 'inbox-reply') { - openInboxReply(target.getAttribute('data-inbox-id') || ''); - return; - } - if (action === 'inbox-reply-submit') { - submitInboxReply(target.getAttribute('data-inbox-id') || ''); - return; - } - if (action === 'inbox-reply-cancel') { - cancelInboxReply(); - return; - } - if (action === 'inbox-ack') { - ackInboxItem(target.getAttribute('data-inbox-id') || ''); - return; - } - if (action === 'inbox-to-todo') { - convertInboxToTodo(target.getAttribute('data-inbox-id') || ''); - return; - } - if (action === 'toggle-done-today') { - state.actions.doneExpanded = !state.actions.doneExpanded; - renderActions(); - return; - } - if (action === 'toggle-earlier-open') { - state.actions.earlierOpenExpanded = !state.actions.earlierOpenExpanded; - renderActions(); - return; - } - if (action === 'toggle-older-backlog') { - state.actions.olderBacklogExpanded = !state.actions.olderBacklogExpanded; - renderActions(); - return; - } - if (action === 'toggle-legacy-backlog') { - state.actions.legacyBacklogExpanded = !state.actions.legacyBacklogExpanded; - renderActions(); - return; - } - if (action === 'toggle-briefings') { - state.inbox.briefingExpanded = !state.inbox.briefingExpanded; - renderActions(); - return; - } - if (action === 'toggle-inbox-archive') { - state.inbox.answeredExpanded = !state.inbox.answeredExpanded; - renderActions(); - return; - } - if (action === 'toggle-session-detail-section') { - toggleSessionDetailSection( - target.getAttribute('data-session-id') || state.sessions.selectedId || '', - target.getAttribute('data-section') || '', - ); - return; - } - if (action === 'toggle-session-preview') { - toggleSessionPreview(target.getAttribute('data-session-id') || state.sessions.selectedId || ''); - return; - } - if (action === 'zoom-graph') { - zoomGraph(parseInt(target.getAttribute('data-dir') || '0', 10)); - return; - } - if (action === 'recenter-graph') { - recenterGraph(); - return; - } - if (action === 'rebuild-graph') { - rebuildGraph(); - return; - } - if (action === 'expand-node') { - var nodeId = target.getAttribute('data-node-id'); - if (nodeId) expandNode(nodeId); - return; - } - if (action === 'delete-memory') { - deleteMemory( - target.getAttribute('data-memory-id') || '', - target.getAttribute('data-memory-title') || '', - ); - return; - } - if (action === 'edit-memory') { - var editMemoryId = target.getAttribute('data-memory-id'); - if (editMemoryId) editMemory(editMemoryId); - return; - } - if (action === 'copy-agent-prompt') { - copyAgentPrompt(); - return; - } - if (action === 'copy-skill-path') { - var skillPath = target.getAttribute('data-skill-path') || ''; - copyTextToClipboard(skillPath).then(function() { - target.setAttribute('title', '已复制'); - setTimeout(function() { target.setAttribute('title', '复制路径'); }, 1400); - }); - return; - } - if (action === 'open-skill-draft') { - openSkillDraft(target.getAttribute('data-skill-draft-key') || '通用经验'); - return; - } - if (action === 'copy-skill-draft') { - var draftEl = document.getElementById('skill-draft-preview'); - copyTextToClipboard(draftEl ? draftEl.textContent || '' : '').then(function() { - target.textContent = '已复制'; - setTimeout(function() { target.textContent = '复制草稿'; }, 1400); - }); - return; - } - if (action === 'open-add-memory') { - openAddMemory(); - return; - } - if (action === 'open-add-lesson') { - openAddLesson(); - return; - } - if (action === 'open-skill-detail') { - openSkillDetail(target.getAttribute('data-skill-path') || ''); - return; - } - if (action === 'filter-skill-root') { - state.lessons.skillRootFilter = target.getAttribute('data-root') || 'all'; - renderLessons(); - return; - } - if (action === 'filter-actions-status') { - var nextFilter = target.getAttribute('data-status') || ''; - if (['attention', 'awaiting', 'review', 'pending', 'blocked', 'active'].indexOf(nextFilter) >= 0) nextFilter = 'todo'; - // STEP-13: clicking the already-active filter clears it — with the chip - // row gone, this is how the metric cards restore the "show all" view. - state.actions.statusFilter = (state.actions.statusFilter || '') === nextFilter ? '' : nextFilter; - renderActions(); - return; - } - if (action === 'close-modal') { - closeModal(); - return; - } - if (action === 'save-new-memory') { - saveNewMemory(); - return; - } - if (action === 'save-new-lesson') { - saveNewLesson(); - return; - } - if (action === 'save-memory') { - var saveMemoryId = target.getAttribute('data-memory-id'); - if (saveMemoryId) saveMemory(saveMemoryId); - return; - } - if (action === 'confirm-delete-memory') { - var memoryId = target.getAttribute('data-memory-id'); - if (memoryId) confirmDeleteMemory(memoryId); - return; - } - if (action === 'edit-review') { - var editReviewId = target.getAttribute('data-review-id'); - if (editReviewId) editReviewItem(editReviewId); - return; - } - if (action === 'approve-review') { - var approveId = target.getAttribute('data-review-id'); - if (approveId) approveReviewItem(approveId); - return; - } - if (action === 'dismiss-review') { - var dismissId = target.getAttribute('data-review-id'); - if (dismissId) dismissReviewItem(dismissId); - return; - } - if (action === 'timeline-filter') { - setTlTypeFilter(target.getAttribute('data-type-filter') || ''); - return; - } - if (action === 'episode-filter') { - state.timeline.episodeFilter = target.getAttribute('data-episode-filter') || 'all'; - state.timeline.page = 0; - renderObservations(); - return; - } - if (action === 'episode-toggle') { - var episodeKey = target.getAttribute('data-episode-key'); - if (episodeKey) { - state.timeline.expandedEpisodes[episodeKey] = !state.timeline.expandedEpisodes[episodeKey]; - renderObservations(); - } - return; - } - if (action === 'timeline-page') { - var page = parseInt(target.getAttribute('data-page') || '', 10); - if (!Number.isNaN(page)) tlPage(page); - return; - } - if (action === 'timeline-mode') { - var mode = target.getAttribute('data-mode') || 'episodes'; - state.timeline.mode = mode === 'events' ? 'events' : 'episodes'; - state.timeline.page = 0; - renderTimelineToolbar(groupSessionsByProject(state.timeline.sessions || [])); - renderObservations(); - return; - } - if (action === 'select-session') { - var sessionId = target.getAttribute('data-session-id'); - if (sessionId) selectSession(sessionId); - return; - } - if (action === 'session-group-mode') { - var sessionMode = target.getAttribute('data-mode') === 'source' ? 'source' : 'folder'; - state.sessions.groupMode = sessionMode; - renderSessions(); - return; - } - if (action === 'select-session-group') { - var groupKey = target.getAttribute('data-group-key') || 'all'; - if (state.sessions.groupMode === 'source') state.sessions.sourceKey = groupKey; - else state.sessions.folderKey = groupKey; - renderSessions(); - return; - } - if (action === 'session-jump') { - var jumpTarget = target.getAttribute('data-target') === 'bottom' ? 'session-detail-bottom' : 'session-detail-top'; - var jumpEl = document.getElementById(jumpTarget); - if (jumpEl) jumpEl.scrollIntoView({ behavior: 'smooth', block: target.getAttribute('data-target') === 'bottom' ? 'end' : 'start' }); - return; - } - if (action === 'end-session') { - var endSessionId = target.getAttribute('data-session-id'); - if (endSessionId) endSession(endSessionId); - return; - } - if (action === 'summarize-session') { - var summarizeSessionId = target.getAttribute('data-session-id'); - if (summarizeSessionId) summarizeSession(summarizeSessionId, target); - return; - } - if (action === 'toggle-audit') { - var auditIndex = parseInt(target.getAttribute('data-audit-index') || '', 10); - if (!Number.isNaN(auditIndex)) toggleAuditDetail(auditIndex); - } - if (action === 'replay-select') { - var rSid = target.getAttribute('data-session-id'); - if (rSid) selectReplaySession(rSid); - return; - } - if (action === 'replay-toggle-play') { toggleReplayPlay(); return; } - if (action === 'replay-step') { - var d = parseInt(target.getAttribute('data-dir') || '1', 10); - stepReplay(d); - return; - } - if (action === 'replay-speed') { - var sp = parseFloat(target.getAttribute('data-speed') || '1'); - setReplaySpeed(sp); - return; - } - if (action === 'replay-reset') { resetReplay(); return; } - if (action === 'replay-import') { runReplayImport(); return; } - if (action === 'replay-refresh') { refreshReplaySessions(); return; } - }); - document.getElementById('modal-overlay').addEventListener('click', function(e) { - if (e.target === this) closeModal(); - }); - document.addEventListener('input', function(e) { - var target = e.target; - if (!target || !target.id || String(target.id).indexOf('todo-config-') !== 0) return; - state.actions.configDraft = state.actions.configDraft || {}; - state.actions.configDraft[String(target.id).slice('todo-config-'.length)] = String(target.value || ''); - }); diff --git a/src/viewer/parts/app/68-legacy-replay.js b/src/viewer/parts/app/68-legacy-replay.js deleted file mode 100644 index d908c65d..00000000 --- a/src/viewer/parts/app/68-legacy-replay.js +++ /dev/null @@ -1,202 +0,0 @@ - async function loadReplay() { - var el = document.getElementById('view-replay'); - el.innerHTML = '
加载会话列表中…
'; - var res = await apiGet('replay/sessions'); - state.replay.sessions = (res && res.sessions) || []; - state.replay.loaded = true; - renderReplay(); - } - - async function refreshReplaySessions() { - state.replay.loaded = false; - await loadReplay(); - } - - function renderReplay() { - var el = document.getElementById('view-replay'); - var sessions = state.replay.sessions || []; - var options = '' + sessions.map(function(s) { - var id = sessionId(s); - var label = sessionDisplayName(s) + ' · ' + (shortSessionId(s, 8) || '无记录 ID') + ' · ' + (s.observationCount || 0) + ' obs'; - return ''; - }).join(''); - - var tl = state.replay.timeline; - var hasTl = tl && tl.events && tl.events.length > 0; - var cursorEvent = hasTl ? tl.events[Math.min(state.replay.cursor, tl.events.length - 1)] : null; - var progress = hasTl && tl.totalDurationMs > 0 ? Math.min(100, (state.replay.offsetAt / tl.totalDurationMs) * 100) : 0; - - el.innerHTML = - '
' + - '' + - '' + - '' + - '' + - '' + - '
' + - (hasTl - ? '
' + - '' + - '' + - '' + - '' + - '' + - '速度' + - ['0.5', '1', '2', '4'].map(function(sp) { - var active = Math.abs(state.replay.speed - parseFloat(sp)) < 0.01; - return ''; - }).join('') + - '' + - '' + (state.replay.cursor + 1) + ' / ' + tl.eventCount + '' + - '
' + - '
' + - '
' + - '
' + - tl.events.map(function(ev, i) { - var active = i === state.replay.cursor ? ' replay-event-active' : ''; - return '
' + - '' + esc(ev.kind) + '' + - '' + esc(ev.label) + '' + - '' + (ev.offsetMs / 1000).toFixed(1) + 's' + - '
'; - }).join('') + - '
' + - '
' + renderReplayDetail(cursorEvent) + '
' + - '
' - : '
请选择一个会话进行回放,或从 ~/.claude/projects 导入 Claude Code 的 JSONL 记录。
'); - - var sel = document.getElementById('replay-session-select'); - if (sel) sel.addEventListener('change', function() { selectReplaySession(sel.value); }); - } - - function renderReplayDetail(ev) { - if (!ev) return '
尚未选择事件。
'; - var blocks = []; - blocks.push('
' + esc(ev.label) + ' ' + esc(ev.kind) + '
'); - if (ev.ts) blocks.push('
' + esc(formatTime(ev.ts)) + '
'); - if (ev.body) { - blocks.push('
' + esc(ev.body) + '
'); - } - if (ev.toolName) { - blocks.push('
工具: ' + esc(ev.toolName) + '
'); - } - if (ev.toolInput !== undefined && ev.toolInput !== null) { - var inp = typeof ev.toolInput === 'string' ? ev.toolInput : JSON.stringify(ev.toolInput, null, 2); - blocks.push('
输入
' + esc(truncate(inp, 4000)) + '
'); - } - if (ev.toolOutput !== undefined && ev.toolOutput !== null) { - var out = typeof ev.toolOutput === 'string' ? ev.toolOutput : JSON.stringify(ev.toolOutput, null, 2); - blocks.push('
输出
' + esc(truncate(out, 4000)) + '
'); - } - return blocks.join(''); - } - - async function selectReplaySession(sessionId) { - stopReplayTimer(); - state.replay.selectedId = sessionId; - state.replay.timeline = null; - state.replay.cursor = 0; - state.replay.offsetAt = 0; - state.replay.playing = false; - if (!sessionId) { renderReplay(); return; } - var el = document.getElementById('view-replay'); - el.innerHTML = '
加载回放中…
'; - var res = await apiGet('replay/load?sessionId=' + encodeURIComponent(sessionId)); - if (res && res.success && res.timeline) { - state.replay.timeline = res.timeline; - } else { - state.replay.timeline = { events: [], eventCount: 0, totalDurationMs: 0 }; - } - renderReplay(); - } - - function toggleReplayPlay() { - if (!state.replay.timeline || state.replay.timeline.eventCount === 0) return; - if (state.replay.playing) { - stopReplayTimer(); - } else { - startReplayTimer(); - } - renderReplay(); - } - - function startReplayTimer() { - state.replay.playing = true; - state.replay.startAt = Date.now(); - var baseOffset = state.replay.offsetAt; - if (state.replay.timer) clearInterval(state.replay.timer); - state.replay.timer = setInterval(function() { - if (!state.replay.timeline) return; - var elapsed = (Date.now() - state.replay.startAt) * state.replay.speed; - state.replay.offsetAt = baseOffset + elapsed; - var events = state.replay.timeline.events; - var newCursor = state.replay.cursor; - for (var i = newCursor; i < events.length; i++) { - if (events[i].offsetMs <= state.replay.offsetAt) newCursor = i; - else break; - } - var changed = newCursor !== state.replay.cursor; - state.replay.cursor = newCursor; - if (state.replay.offsetAt >= state.replay.timeline.totalDurationMs) { - state.replay.offsetAt = state.replay.timeline.totalDurationMs; - stopReplayTimer(); - renderReplay(); - return; - } - if (changed) renderReplay(); - }, 100); - } - - function stopReplayTimer() { - state.replay.playing = false; - if (state.replay.timer) { - clearInterval(state.replay.timer); - state.replay.timer = null; - } - } - - function stepReplay(dir) { - if (!state.replay.timeline) return; - stopReplayTimer(); - var next = state.replay.cursor + dir; - if (next < 0) next = 0; - if (next >= state.replay.timeline.eventCount) next = state.replay.timeline.eventCount - 1; - state.replay.cursor = next; - state.replay.offsetAt = state.replay.timeline.events[next].offsetMs; - renderReplay(); - } - - function setReplaySpeed(sp) { - if (!sp || sp <= 0) return; - var wasPlaying = state.replay.playing; - stopReplayTimer(); - state.replay.speed = sp; - if (wasPlaying) startReplayTimer(); - renderReplay(); - } - - function resetReplay() { - stopReplayTimer(); - state.replay.cursor = 0; - state.replay.offsetAt = 0; - renderReplay(); - } - - async function runReplayImport() { - var input = document.getElementById('replay-import-path'); - var pathVal = input ? input.value.trim() : ''; - var body = {}; - if (pathVal) body.path = pathVal; - var el = document.getElementById('view-replay'); - var prior = el.innerHTML; - el.innerHTML = '
Importing JSONL…
'; - var res = await apiPost('replay/import-jsonl', body); - if (!res || res.success === false) { - el.innerHTML = prior; - alert((res && res.error) || '导入失败'); - return; - } - alert('已导入 ' + (res.imported || 0) + ' 个文件,生成 ' + (res.observations || 0) + ' 条观察记录'); - await refreshReplaySessions(); - } - diff --git a/src/viewer/parts/app/90-bootstrap.js b/src/viewer/parts/app/90-bootstrap.js deleted file mode 100644 index cccd9b55..00000000 --- a/src/viewer/parts/app/90-bootstrap.js +++ /dev/null @@ -1,49 +0,0 @@ - document.addEventListener('keydown', function(e) { - if (state.activeTab !== 'replay') return; - if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT' || e.target.tagName === 'TEXTAREA')) return; - if (e.key === ' ') { e.preventDefault(); toggleReplayPlay(); } - else if (e.key === 'ArrowLeft') { e.preventDefault(); stepReplay(-1); } - else if (e.key === 'ArrowRight') { e.preventDefault(); stepReplay(1); } - }); - - // 总览页下拉刷新(顶部向下滑动触发) - var dashPullStartY = null; - var dashPullStartTop = false; - var dashPullTriggered = false; - var dashPullCooldownAt = 0; - document.addEventListener('touchstart', function(e) { - if (state.activeTab !== 'dashboard') return; - if (!e.touches || e.touches.length !== 1) return; - var scTop = (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0); - dashPullStartTop = scTop <= 2; - dashPullStartY = e.touches[0].clientY; - dashPullTriggered = false; - }, { passive: true }); - document.addEventListener('touchmove', function(e) { - if (state.activeTab !== 'dashboard') return; - if (!dashPullStartTop || dashPullStartY === null || dashPullTriggered) return; - if (!e.touches || e.touches.length !== 1) return; - var dy = e.touches[0].clientY - dashPullStartY; - if (dy < 72) return; - var now = Date.now(); - if (now - dashPullCooldownAt < 1200) return; - dashPullTriggered = true; - dashPullCooldownAt = now; - refreshDashboard(); - }, { passive: true }); - document.addEventListener('touchend', function() { - dashPullStartY = null; - dashPullStartTop = false; - dashPullTriggered = false; - }, { passive: true }); - - document.addEventListener('visibilitychange', function() { - if (document.visibilityState === 'visible') { - refreshActiveTab('visibility'); - } - }); - - renderExpertTabs(); - switchTab(tabFromRoute(), { replaceRoute: true }); - connectWs(); - startDashboardAutoRefresh(); diff --git a/src/viewer/parts/manifest.json b/src/viewer/parts/manifest.json deleted file mode 100644 index 02d28750..00000000 --- a/src/viewer/parts/manifest.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - "00-head.html", - "style/00-tokens-reset.css", - "style/10-shell-atoms.css", - "style/20-legacy-graph-obs-episode.css", - "style/30-sessions.css", - "style/40-legacy-audit-empty-lessons-skills.css", - "style/50-todo-cards.css", - "style/60-editorial-overrides-legacy-memory.css", - "style/70-legacy-crystal-modal-settings-timeline.css", - "style/80-legacy-heatmap-replay.css", - "style/90-responsive-avatars.css", - "20-body.html", - "app/00-prelude.js", - "app/05-i18n.js", - "app/08-state-expert.js", - "app/10-html-safe.js", - "app/20-format-utils.js", - "app/40-data-helpers.js", - "app/45-transport.js", - "app/50-routing.js", - "app/52-dashboard.js", - "app/54-legacy-graph-memories-timeline-obs-activity.js", - "app/56-sessions.js", - "app/58-legacy-lessons.js", - "app/60-actions-todo.js", - "app/62-legacy-crystals-audit-profile.js", - "app/64-ws-flags-events.js", - "app/68-legacy-replay.js", - "app/90-bootstrap.js", - "40-tail.html" -] diff --git a/src/viewer/parts/style/00-tokens-reset.css b/src/viewer/parts/style/00-tokens-reset.css deleted file mode 100644 index e9fe0e12..00000000 --- a/src/viewer/parts/style/00-tokens-reset.css +++ /dev/null @@ -1,133 +0,0 @@ - :root { - --bg: #F5F5F5; - --bg-alt: #FAFAFA; - --bg-subtle: #F0EFED; - --bg-inset: #F0EFED; - --surface: #FFFFFF; - --border: #D6D3D1; - --border-light: #E7E5E4; - --border-heavy: #A8A29E; - --ink: #0C0A09; - --ink-secondary: #4E4E4E; - --ink-muted: #777169; - --ink-faint: #A8A29E; - --accent: #292524; - --accent-light: #D6D3D1; - --cream: #FAFAFA; - --node-file: #2D6A4F; - --node-function: #1D4E89; - --node-concept: #B8860B; - --node-error: #CC0000; - --node-decision: #6B3FA0; - --node-pattern: #2563EB; - --node-library: #C2410C; - --node-person: #111111; - --green: #2D6A4F; - --blue: #1D4E89; - --yellow: #B8860B; - --red: #CC0000; - --purple: #6B3FA0; - --orange: #C2410C; - --cyan: #0E7490; - --font-display: Georgia, "Times New Roman", serif; - --font-body: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - --font-ui: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - --font-mono: "SF Mono", "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace; - } - html[data-theme="dark"] { - --bg: #1a1a1e; - --bg-alt: #232328; - --bg-subtle: #1f1f24; - --bg-inset: #2a2a30; - --border: #444; - --border-light: #3a3a42; - --border-heavy: #ccc; - --ink: #eee; - --ink-secondary: #ccc; - --ink-muted: #999; - --ink-faint: #777; - --cream: #2a2520; - } - html[data-theme="dark"] body { - background-image: radial-gradient(circle, #3a3a42 0.5px, transparent 0.5px); - } - html[data-theme="dark"] .graph-tooltip { - background: rgba(30,30,35,0.92); - border-color: rgba(255,255,255,0.1); - box-shadow: 0 8px 32px rgba(0,0,0,0.4); - } - html[data-theme="dark"] .graph-controls button { - background: rgba(30,30,35,0.92); - border-color: rgba(255,255,255,0.1); - } - html[data-theme="dark"] .graph-controls button:hover { - background: var(--ink); - color: var(--bg); - } - * { margin: 0; padding: 0; box-sizing: border-box; } - body { - font-family: var(--font-body); - background: var(--bg); - color: var(--ink-secondary); - line-height: 1.6; - overflow: hidden; - height: 100vh; - display: flex; - flex-direction: column; - background-image: none; - } - ::-webkit-scrollbar { width: 6px; } - ::-webkit-scrollbar-track { background: var(--bg); } - ::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 0; } - ::-webkit-scrollbar-thumb:hover { background: var(--ink-muted); } - - .app-header { display: none !important; } - .app-header .brand { - display: flex; - align-items: baseline; - gap: 10px; - color: inherit; - text-decoration: none; - cursor: pointer; - } - .app-header .brand:hover h1, - .app-header .brand:focus-visible h1 { color: var(--accent); } - .app-header .brand:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; } - .app-header .brand h1 { - font-size: 24px; - color: var(--ink); - font-weight: 600; - font-family: var(--font-display); - letter-spacing: -0.02em; - text-transform: none; - } - .header-right { - display: flex; - align-items: center; - gap: 12px; - } - #theme-toggle { display: none; } - #ws-status { display: none; } - .ws-status { - font-size: 10px; - padding: 3px 10px; - display: flex; - align-items: center; - gap: 5px; - font-family: var(--font-ui); - text-transform: uppercase; - letter-spacing: 0.08em; - font-weight: 600; - border: 1px solid var(--border-light); - } - .ws-status::before { - content: ''; - width: 6px; - height: 6px; - display: inline-block; - } - .ws-status.connected { border-color: var(--green); color: var(--green); } - .ws-status.connected::before { background: var(--green); } - .ws-status.disconnected { border-color: var(--ink-faint); color: var(--ink-faint); } - .ws-status.disconnected::before { background: var(--ink-faint); } - diff --git a/src/viewer/parts/style/10-shell-atoms.css b/src/viewer/parts/style/10-shell-atoms.css deleted file mode 100644 index f1c1b0fb..00000000 --- a/src/viewer/parts/style/10-shell-atoms.css +++ /dev/null @@ -1,396 +0,0 @@ - .tab-bar { - display: flex; - flex-direction: column; - width: 120px; - flex-shrink: 0; - border-right: 1px solid var(--border-light); - background: var(--bg); - overflow: hidden; - position: fixed; - top: 0; - bottom: 0; - left: 0; - z-index: 2; - padding-top: 8px; - } - .tab-main { - display: flex; - flex-direction: column; - gap: 2px; - min-height: 0; - overflow-y: auto; - overflow-x: hidden; - padding-bottom: 8px; - flex: 1 1 auto; - } - .tab-group-label { display: none; } - .tab-bar button { - background: none; - border: none; - color: var(--ink-muted); - padding: 10px 10px; - font-size: 10px; - cursor: pointer; - border-left: 2px solid transparent; - white-space: nowrap; - font-family: var(--font-ui); - text-transform: uppercase; - letter-spacing: 0.08em; - font-weight: 600; - transition: color 0.15s, border-color 0.15s; - text-align: left; - display: flex; - align-items: center; - gap: 6px; - } - .tab-icon { - width: 14px; - height: 14px; - display: inline-block; - flex-shrink: 0; - background-repeat: no-repeat; - background-position: center; - background-size: 14px 14px; - opacity: 0.8; - } - .tab-bar button[data-tab="dashboard"] .tab-icon { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 13h8V3H3zM13 21h8v-8h-8zM13 3h8v6h-8zM3 21h8v-6H3z'/%3E%3C/svg%3E"); } - .tab-bar button[data-tab="graph"] .tab-icon { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='5' cy='7' r='2'/%3E%3Ccircle cx='19' cy='5' r='2'/%3E%3Ccircle cx='9' cy='19' r='2'/%3E%3Cpath d='M7 7l10-2M6 9l2 8M17 7l-6 10'/%3E%3C/svg%3E"); } - .tab-bar button[data-tab="memories"] .tab-icon { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 19.5A2.5 2.5 0 0 1 6.5 17H20'/%3E%3Cpath d='M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5V4.5A2.5 2.5 0 0 1 6.5 2z'/%3E%3C/svg%3E"); } - .tab-bar button[data-tab="timeline"] .tab-icon { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12h18'/%3E%3Ccircle cx='7' cy='12' r='2'/%3E%3Ccircle cx='17' cy='12' r='2'/%3E%3C/svg%3E"); } - .tab-bar button[data-tab="sessions"] .tab-icon { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='5' width='18' height='14' rx='2'/%3E%3Cpath d='M7 9h10M7 13h6'/%3E%3C/svg%3E"); } - .tab-bar button[data-tab="activity"] .tab-icon { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12h4l2-6 4 12 2-6h6'/%3E%3C/svg%3E"); } - .tab-bar button[data-tab="profile"] .tab-icon { background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='8' r='4'/%3E%3Cpath d='M4 20c2-4 6-6 8-6s6 2 8 6'/%3E%3C/svg%3E\"); } - .tab-bar button[data-tab="lessons"] .tab-icon { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 19h16M6 17V7l6-3 6 3v10'/%3E%3C/svg%3E"); } - .tab-bar button[data-tab="actions"] .tab-icon { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M12 5l7 7-7 7'/%3E%3C/svg%3E"); } - .tab-bar button[data-tab="audit"] .tab-icon { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 11l3 3L22 4'/%3E%3Cpath d='M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11'/%3E%3C/svg%3E"); } - .tab-bar button[data-tab="replay"] .tab-icon { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='5,3 19,12 5,21'/%3E%3C/svg%3E"); } - .tab-bar button:hover { color: var(--ink); } - .tab-bar button.active { - color: var(--ink); - border-left-color: var(--border-heavy); - background: var(--bg-alt); - } - .tab-bar button.active .tab-icon { opacity: 1; } - .tab-expert-group { - display: flex; - flex-direction: column; - gap: 2px; - margin-top: 6px; - padding-top: 6px; - border-top: 1px dashed var(--border, #d8dce1); - } - .tab-expert-group::before { - content: "More"; - font-size: 9px; - letter-spacing: 0.1em; - color: var(--ink-faint, #9aa0a6); - padding: 2px 10px 4px; - text-transform: uppercase; - } - .tab-expert-btn .tab-icon { display: none; } - .expert-toggle { - appearance: none; - border: 1px solid var(--border, #d8dce1); - background: none; - color: var(--ink-muted, #6b7280); - font-family: var(--font-ui); - font-size: 10px; - letter-spacing: 0.06em; - padding: 4px 8px; - border-radius: 6px; - cursor: pointer; - transition: color 0.15s, border-color 0.15s, background 0.15s; - } - .expert-toggle:hover { color: var(--ink); border-color: var(--border-heavy, #b8bdc4); } - .expert-toggle[aria-pressed="true"] { - color: var(--ink); - background: var(--bg-alt); - border-color: var(--border-heavy, #b8bdc4); - } - .tab-extra-toggle { - margin: 4px 8px; - padding: 6px 8px; - color: var(--ink-muted); - border: 1px solid var(--border-light); - background: var(--bg); - cursor: pointer; - text-align: left; - font-size: 10px; - line-height: 1; - min-height: 28px; - display: flex; - align-items: center; - gap: 6px; - width: calc(100% - 16px); - font-family: var(--font-ui); - letter-spacing: 0.04em; - } - .tab-extra-toggle .toggle-caret { - width: 12px; - height: 12px; - display: inline-block; - border-right: 1.8px solid var(--ink-muted); - border-bottom: 1.8px solid var(--ink-muted); - transform: rotate(45deg) translateY(-2px); - flex-shrink: 0; - } - .tab-extra-toggle[aria-expanded="true"] .toggle-caret { - transform: rotate(-135deg) translateY(2px); - } - .tab-extra-toggle .toggle-icon { - width: 14px; - height: 14px; - display: inline-block; - flex-shrink: 0; - background-repeat: no-repeat; - background-position: center; - background-size: 14px 14px; - opacity: 0.85; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='2'/%3E%3Cpath d='M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h.02a1.65 1.65 0 0 0 .98-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 .99 1.51h.02a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v.02a1.65 1.65 0 0 0 1.51.98H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51.99z'/%3E%3C/svg%3E"); - } - .tab-extra-toggle:hover { color: var(--ink); background: var(--bg-alt); border-color: var(--border); } - .tab-extra-toggle:focus-visible { outline: 2px solid var(--border); outline-offset: 1px; } - .tab-extra-toggle { display: none; } - .tab-extra { display: block; } - .tab-extra.open { display: block; } - .tab-side-meta { - margin-top: 0; - border-top: 1px solid var(--border-light); - padding: 8px 6px 10px; - display: flex; - flex-direction: column; - gap: 4px; - flex: 0 0 auto; - background: var(--bg); - } - .tab-side-meta { display: none; } - .tab-side-meta a { - display: flex; - align-items: center; - justify-content: center; - min-height: 28px; - min-width: 28px; - padding: 0; - color: var(--ink-muted); - text-decoration: none; - border: 1px solid transparent; - } - .tab-side-meta a:hover { - color: var(--ink); - border-color: var(--border-light); - background: var(--bg-alt); - } - .tab-side-meta .side-icon { - width: 14px; - height: 14px; - display: inline-block; - background-repeat: no-repeat; - background-position: center; - background-size: 14px 14px; - opacity: 0.9; - } - .tab-side-meta .icon-repo { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 4h8a2 2 0 0 1 2 2v14l-6-3-6 3V6a2 2 0 0 1 2-2z'/%3E%3C/svg%3E"); - } - .tab-side-meta .icon-docs { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z'/%3E%3Cpath d='M14 3v6h6'/%3E%3C/svg%3E"); - } - .tab-side-meta .icon-feedback { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'/%3E%3C/svg%3E"); - } - - .view { display: none; flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 24px; margin-left: 120px; } - .view.active { display: block; } - - .stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 12px; - margin-bottom: 24px; - } - .stat-card { - background: var(--surface); - padding: 18px 20px; - border: 1px solid var(--border-light); - border-radius: 16px; - } - .stat-card:last-child { border-right: 1px solid var(--border-light); } - .stat-card .label { - font-size: 9px; - color: var(--ink-muted); - text-transform: uppercase; - letter-spacing: 0.08em; - margin-bottom: 4px; - font-family: var(--font-ui); - font-weight: 600; - } - .stat-card .value { - font-size: 32px; - font-weight: 300; - color: var(--ink); - font-family: var(--font-display); - line-height: 1.1; - } - .stat-card .sub { - font-size: 11px; - color: var(--ink-faint); - margin-top: 2px; - font-family: var(--font-ui); - } - - .card { - background: var(--surface); - border: 1px solid var(--border-light); - border-radius: 16px; - padding: 20px; - margin-bottom: 16px; - transition: border-color 0.15s, box-shadow 0.15s; - } - .card:hover { - border-color: var(--border); - box-shadow: 0 16px 48px rgba(12, 10, 9, 0.05); - } - .card-title { - font-size: 13px; - font-weight: 500; - color: var(--ink); - margin-bottom: 12px; - font-family: var(--font-display); - text-transform: none; - letter-spacing: 0; - padding-bottom: 8px; - border-bottom: 1px solid var(--border-light); - } - - .health-bar { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 8px; - } - .health-dot { - width: 10px; - height: 10px; - } - .health-dot.healthy { background: var(--green); } - .health-dot.degraded { background: var(--yellow); } - .health-dot.critical { background: var(--accent); } - - .badge { - display: inline-block; - font-size: 11px; - padding: 4px 10px; - font-weight: 600; - font-family: var(--font-ui); - text-transform: uppercase; - letter-spacing: 0.04em; - border: 1px solid; - border-radius: 999px; - } - .badge-blue { border-color: var(--blue); color: var(--blue); background: transparent; } - .badge-green { border-color: var(--green); color: var(--green); background: transparent; } - .badge-yellow { border-color: var(--yellow); color: var(--yellow); background: transparent; } - .badge-red { border-color: var(--accent); color: var(--accent); background: transparent; } - .badge-purple { border-color: var(--purple); color: var(--purple); background: transparent; } - .badge-orange { border-color: var(--orange); color: var(--orange); background: transparent; } - .badge-cyan { border-color: var(--cyan); color: var(--cyan); background: transparent; } - .badge-muted { border-color: var(--border-light); color: var(--ink-muted); background: transparent; } - - table { - width: 100%; - border-collapse: collapse; - font-size: 13px; - font-family: var(--font-body); - } - th { - text-align: left; - padding: 8px 12px; - border-bottom: 2px solid var(--border); - color: var(--ink); - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.12em; - font-weight: 600; - font-family: var(--font-ui); - } - td { - padding: 8px 12px; - border-bottom: 1px solid var(--border-light); - vertical-align: top; - } - tr:hover td { background: var(--bg-alt); } - - .strength-bar { - width: 60px; - height: 4px; - background: var(--bg-inset); - overflow: hidden; - display: inline-block; - vertical-align: middle; - } - .strength-bar .fill { - height: 100%; - transition: width 0.3s; - } - - .toolbar { - display: flex; - gap: 10px; - margin-bottom: 20px; - align-items: center; - flex-wrap: wrap; - } - .toolbar input, .toolbar select { - background: var(--bg); - border: 1px solid var(--border); - color: var(--ink); - padding: 7px 12px; - font-size: 13px; - outline: none; - font-family: var(--font-ui); - } - .toolbar input:focus, .toolbar select:focus { - border-color: var(--ink); - box-shadow: 2px 2px 0px 0px var(--border); - } - .toolbar input { flex: 1; min-width: 200px; } - - .btn { - background: transparent; - border: 1px solid var(--border); - color: var(--ink); - border-radius: 999px; - padding: 10px 18px; - font-size: 14px; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; - font-family: var(--font-ui); - font-weight: 500; - text-transform: none; - letter-spacing: 0; - } - .btn:hover { background: var(--bg-subtle); border-color: var(--border-heavy); box-shadow: none; transform: none; } - .btn:active { box-shadow: none; transform: translate(0, 0); } - .btn-danger { border-color: var(--accent); color: var(--accent); } - .btn-danger:hover { background: var(--accent); color: white; box-shadow: rgba(0, 0, 0, 0.05) -2px 2px 4px 0px; } - .btn-primary { background: var(--accent); color: #fff; border-color: var(--accent); } - .btn-primary:hover { background: var(--ink); box-shadow: none; } - .btn-soft-alert { - border-color: var(--accent); - color: var(--ink); - background: var(--bg-warm); - } - .btn-working::after { - content: ''; - display: inline-block; - width: 7px; - height: 7px; - margin-left: 6px; - border: 2px solid currentColor; - border-right-color: transparent; - border-radius: 999px; - vertical-align: -1px; - animation: button-spin 0.7s linear infinite; - } - @keyframes button-spin { to { transform: rotate(360deg); } } - diff --git a/src/viewer/parts/style/20-legacy-graph-obs-episode.css b/src/viewer/parts/style/20-legacy-graph-obs-episode.css deleted file mode 100644 index 73cb6afb..00000000 --- a/src/viewer/parts/style/20-legacy-graph-obs-episode.css +++ /dev/null @@ -1,259 +0,0 @@ - .graph-container { - display: flex; - height: calc(100vh - 130px); - margin: -24px; - border-top: 1px solid var(--border-light); - } - .graph-canvas-wrap { - flex: 1; - position: relative; - overflow: hidden; - background: var(--bg); - } - .graph-canvas-wrap canvas { - display: block; - width: 100%; - height: 100%; - } - .graph-sidebar { - width: 260px; - border-left: 2px solid var(--border); - padding: 20px; - overflow-y: auto; - background: var(--bg); - } - .graph-sidebar h3 { - font-size: 9px; - color: var(--ink); - text-transform: uppercase; - letter-spacing: 0.15em; - margin-bottom: 12px; - font-family: var(--font-ui); - font-weight: 600; - padding-bottom: 6px; - border-bottom: 1px solid var(--border-light); - } - .filter-item { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 0; - font-size: 12px; - cursor: pointer; - font-family: var(--font-ui); - } - .filter-item input[type="checkbox"] { - accent-color: var(--ink); - } - .filter-dot { - width: 8px; - height: 8px; - display: inline-block; - } - .graph-info { - margin-top: 16px; - padding-top: 16px; - border-top: 1px solid var(--border-light); - } - .graph-info .info-row { - display: flex; - justify-content: space-between; - font-size: 12px; - padding: 3px 0; - font-family: var(--font-ui); - } - .graph-info .info-row .info-label { color: var(--ink-muted); } - .graph-info .info-row .info-value { color: var(--ink); font-weight: 600; font-family: var(--font-mono); } - - .obs-card { - background: var(--bg); - border: 1px solid var(--border-light); - padding: 16px 20px; - margin-bottom: 12px; - border-left: 3px solid var(--border-light); - transition: box-shadow 0.15s; - min-width: 0; - max-width: 100%; - overflow: hidden; - } - .obs-card:hover { box-shadow: 3px 3px 0px 0px var(--border-light); } - .obs-card.imp-high { border-left-color: var(--accent); } - .obs-card.imp-med { border-left-color: var(--yellow); } - .obs-card.imp-low { border-left-color: var(--green); } - .obs-card .obs-head { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: start; - gap: 12px; - margin-bottom: 6px; - } - .obs-card .obs-title-row { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; - } - .obs-card .obs-meta { - display: flex; - align-items: center; - gap: 8px; - flex: 0 0 auto; - white-space: nowrap; - } - .obs-card .obs-type-icon { - flex: 0 0 auto; - } - .obs-card .obs-title { - font-size: 14px; - font-weight: 700; - color: var(--ink); - font-family: var(--font-display); - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .obs-card .obs-time { - font-size: 10px; - color: var(--ink-faint); - font-family: var(--font-mono); - letter-spacing: 0.04em; - } - .obs-card .obs-narrative { - font-size: 13px; - color: var(--ink-muted); - margin-bottom: 6px; - overflow-wrap: anywhere; - word-break: break-word; - } - .obs-card pre { - max-width: 100%; - white-space: pre-wrap; - overflow-wrap: anywhere; - word-break: break-word; - } - .obs-card .obs-facts { - margin: 6px 0 6px 16px; - font-size: 12px; - color: var(--ink-muted); - } - .obs-card .obs-facts li { margin-bottom: 2px; } - .episode-card { - background: var(--bg); - border: 1px solid var(--border); - border-left: 4px solid var(--accent); - padding: 18px 20px; - margin-bottom: 14px; - box-shadow: 2px 2px 0 0 var(--border-light); - } - .episode-title { - font-family: var(--font-display); - font-size: 16px; - font-weight: 800; - color: var(--ink); - line-height: 1.35; - margin-bottom: 8px; - } - .episode-body { - font-size: 13px; - color: var(--ink-secondary); - line-height: 1.55; - margin-bottom: 10px; - } - .episode-kind { - display: inline-flex; - align-items: center; - width: fit-content; - margin-bottom: 8px; - padding: 3px 8px; - border: 1px solid var(--border-light); - background: var(--bg-alt); - color: var(--ink-secondary); - font-size: 11px; - font-weight: 800; - font-family: var(--font-ui); - } - .episode-meta { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-bottom: 9px; - } - .episode-meta span { - font-size: 10px; - font-family: var(--font-ui); - color: var(--ink-muted); - border: 1px solid var(--border-light); - background: var(--bg-alt); - padding: 2px 7px; - } - .episode-actions { - display: grid; - gap: 5px; - margin: 10px 0 0; - padding: 0; - list-style: none; - font-size: 12px; - color: var(--ink-muted); - } - .episode-actions li::before { - content: '→'; - margin-right: 7px; - color: var(--accent); - font-family: var(--font-mono); - } - .episode-details { - display: none; - margin-top: 12px; - padding-top: 12px; - border-top: 1px solid var(--border-light); - } - .episode-card.expanded .episode-details { display: block; } - .episode-footer { - display: flex; - flex-wrap: wrap; - gap: 8px; - align-items: center; - justify-content: space-between; - margin-top: 12px; - } - .episode-footer .btn { - padding: 5px 9px; - font-size: 11px; - } - .mode-switch { - display: inline-flex; - border: 1px solid var(--border); - background: var(--bg); - box-shadow: 1px 1px 0 0 var(--border-light); - } - .mode-switch button { - border: 0; - border-right: 1px solid var(--border-light); - background: transparent; - padding: 7px 12px; - font-size: 12px; - font-weight: 700; - color: var(--ink-muted); - cursor: pointer; - } - .mode-switch button:last-child { border-right: 0; } - .mode-switch button.active { - background: var(--ink); - color: var(--bg); - } - .tag-list { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 6px; } - mark { background: rgba(204, 0, 0, 0.12); color: var(--ink); padding: 0 2px; border-radius: 2px; } - .tag { - font-size: 10px; - padding: 1px 6px; - border: 1px solid var(--blue); - color: var(--blue); - font-family: var(--font-mono); - font-weight: 500; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - } - .tag.file-tag { border-color: var(--green); color: var(--green); } - diff --git a/src/viewer/parts/style/30-sessions.css b/src/viewer/parts/style/30-sessions.css deleted file mode 100644 index 00cfcea8..00000000 --- a/src/viewer/parts/style/30-sessions.css +++ /dev/null @@ -1,496 +0,0 @@ - .session-list { display: flex; flex-direction: column; gap: 0; } - .session-item { - background: var(--bg); - border: 1px solid var(--border-light); - border-bottom: none; - padding: 14px 20px; - cursor: pointer; - transition: background 0.1s; - } - .session-item:last-child { border-bottom: 1px solid var(--border-light); } - .session-item:hover { background: var(--bg-alt); } - .session-item.selected { background: var(--bg-alt); border-left: 3px solid var(--accent); } - .session-item .session-top { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 4px; - } - .session-item .session-project { - font-weight: 700; - color: var(--ink); - font-size: 14px; - font-family: var(--font-display); - min-width: 0; - display: -webkit-box !important; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - overflow-wrap: anywhere; - } - .session-title-text { - min-width: 0; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - overflow-wrap: anywhere; - } - .session-preview { - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - } - .session-item .session-meta { - font-size: 11px; - color: var(--ink-muted); - font-family: var(--font-mono); - } - .session-overview { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 8px; - margin-bottom: 10px; - } - .session-overview-card { - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: #ffffff; - padding: 10px 12px; - } - .session-overview-label { - color: var(--ink-muted); - font-family: var(--font-ui); - font-size: 12px; - font-weight: 600; - } - .session-overview-value { - margin-top: 4px; - color: var(--ink); - font-family: var(--font-display); - font-size: 24px; - line-height: 1; - font-weight: 300; - } - .session-hero { - display: flex; - align-items: flex-end; - justify-content: space-between; - gap: 16px; - margin-bottom: 14px; - padding: 4px 0 10px; - border-bottom: 1px solid var(--border-light); - } - .session-hero-title { - color: var(--ink); - font-family: var(--font-display); - font-size: 26px; - font-weight: 360; - line-height: 1.1; - } - .session-hero-note { - margin-top: 5px; - color: var(--ink-muted); - font-size: 13px; - line-height: 1.45; - } - .session-hero-meta { - color: var(--ink-faint); - font-family: var(--font-mono); - font-size: 11px; - white-space: nowrap; - } - .session-inbox { - display: grid; - grid-template-columns: minmax(190px, 240px) minmax(0, 1fr); - gap: 14px; - align-items: start; - margin-top: 0; - } - .session-source-rail { - position: sticky; - top: 86px; - display: grid; - gap: 6px; - padding: 8px; - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: #ffffff; - } - .session-source-label { - padding: 4px 10px 6px; - color: var(--ink-faint); - font-size: 11px; - font-weight: 650; - } - .session-source-chip { - position: relative; - display: grid; - grid-template-columns: 1fr auto; - gap: 8px; - width: 100%; - min-height: 42px; - padding: 8px 10px; - border: 0; - border-radius: 8px; - background: transparent; - color: var(--ink-muted); - font: inherit; - text-align: left; - cursor: pointer; - } - .session-source-chip .folder-hover-preview { - grid-column: 1 / -1; - max-height: 0; - color: var(--ink-muted); - font-size: 12px; - line-height: 1.45; - opacity: 0; - overflow: hidden; - transition: max-height 0.15s ease, opacity 0.15s ease, margin-top 0.15s ease; - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - } - .session-source-chip:hover .folder-hover-preview, - .session-source-chip.active .folder-hover-preview { - max-height: 58px; - margin-top: 2px; - opacity: 1; - } - .session-source-chip:hover, - .session-source-chip.active { - background: var(--bg-alt); - color: var(--ink); - } - .session-source-name { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-family: var(--font-ui); - font-size: 13px; - font-weight: 650; - } - .session-source-count { - color: var(--ink-faint); - font-family: var(--font-mono); - font-size: 11px; - } - .session-inbox-main { - min-width: 0; - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: #ffffff; - overflow: hidden; - } - .session-inbox-head { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12px; - padding: 14px 16px; - border-bottom: 1px solid var(--border-light); - background: var(--bg-subtle); - } - .session-inbox-title { - color: var(--ink); - font-family: var(--font-ui); - font-size: 15px; - font-weight: 700; - } - .session-inbox-note { - margin-top: 3px; - color: var(--ink-muted); - font-size: 12px; - line-height: 1.4; - } - .session-row { - display: grid; - grid-template-columns: 30px minmax(0, 1fr) auto; - gap: 12px; - width: 100%; - padding: 14px 16px; - border: 0; - border-bottom: 1px solid var(--border-light); - background: #ffffff; - color: inherit; - text-align: left; - cursor: pointer; - font: inherit; - } - .session-row:last-child { border-bottom: 0; } - .session-row:hover, - .session-row.selected { background: var(--bg-alt); } - .session-row.selected { box-shadow: inset 3px 0 0 var(--accent); } - .session-row-avatar { - display: grid; - width: 30px; - height: 30px; - place-items: center; - border: 1px solid var(--border-light); - border-radius: 999px; - background: var(--bg-subtle); - color: var(--ink-muted); - font-family: var(--font-ui); - font-size: 11px; - font-weight: 750; - } - .session-row-title { - min-width: 0; - color: var(--ink); - font-family: var(--font-ui); - font-size: 14px; - font-weight: 700; - line-height: 1.35; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - overflow-wrap: anywhere; - } - .session-row-preview { - margin-top: 5px; - color: var(--ink-muted); - font-size: 13px; - line-height: 1.45; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - overflow-wrap: anywhere; - } - .session-row-meta { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; - margin-top: 8px; - color: var(--ink-faint); - font-size: 11px; - font-family: var(--font-ui); - } - .session-row-side { - display: grid; - justify-items: end; - gap: 6px; - color: var(--ink-faint); - font-size: 11px; - white-space: nowrap; - } - .session-row-count { - padding: 3px 7px; - border: 1px solid var(--border-light); - border-radius: var(--radius-pill); - background: #ffffff; - color: var(--ink-muted); - font-family: var(--font-mono); - } - .session-empty-filter { - padding: 28px 16px; - color: var(--ink-faint); - text-align: center; - font-size: 13px; - } - .session-section { - margin-bottom: 16px; - } - .session-section-head { - display: flex; - justify-content: space-between; - align-items: flex-end; - gap: 12px; - margin-bottom: 10px; - } - .session-section-title { - color: var(--ink); - font-family: var(--font-ui); - font-size: 16px; - font-weight: 650; - } - .session-section-note { - color: var(--ink-muted); - font-size: 12px; - line-height: 1.45; - } - .session-active-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 12px; - } - .session-active-card { - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: rgba(255, 255, 255, 0.9); - padding: 16px; - } - .session-active-card .session-top { - align-items: flex-start; - gap: 12px; - } - .session-active-card .session-project { - line-height: 1.4; - } - .session-folder-card { - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: #ffffff; - padding: 14px 16px; - margin-bottom: 10px; - } - .session-folder-head { - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - border-bottom: 0; - padding-bottom: 0; - margin-bottom: 0; - } - .session-folder-name { - color: var(--ink); - font-family: var(--font-ui); - font-size: 17px; - font-weight: 650; - } - .session-folder-path { - margin-top: 4px; - color: var(--ink-faint); - font-size: 12px; - line-height: 1.45; - } - .session-folder-stats { - text-align: right; - white-space: nowrap; - color: var(--ink-muted); - font-size: 12px; - line-height: 1.45; - } - .session-folder-actions { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 12px; - margin-top: 8px; - padding-top: 0; - border-top: 0; - } - .session-folder-time { - color: var(--ink-faint); - font-size: 12px; - line-height: 1.45; - } - .session-history-list { - display: grid; - gap: 8px; - } - .session-view-all { - margin-top: 10px; - color: var(--ink-faint); - font-size: 12px; - } - .session-card-foot { - display: flex; - justify-content: space-between; - align-items: center; - gap: 10px; - margin-top: 12px; - } - .session-timeline-link { - display: inline-flex; - align-items: center; - gap: 6px; - border: 0; - border-radius: 0; - background: transparent; - color: var(--ink); - padding: 0; - font-family: var(--font-ui); - font-size: 13px; - font-weight: 600; - cursor: pointer; - white-space: nowrap; - } - .session-timeline-link svg { - width: 14px; - height: 14px; - stroke: currentColor; - } - .session-folder-card .session-timeline-link { - width: 34px; - height: 34px; - justify-content: center; - border: 1px solid var(--border-light); - border-radius: 999px; - background: var(--bg-subtle); - font-size: 0; - gap: 0; - } - .session-folder-card .session-timeline-link svg { - width: 16px; - height: 16px; - } - @media (max-width: 760px) { - .session-overview { grid-template-columns: 1fr; } - .session-hero { align-items: flex-start; flex-direction: column; } - .session-hero-meta { white-space: normal; } - .session-inbox { grid-template-columns: 1fr; } - .session-source-rail { position: static; display: flex; overflow-x: auto; } - .session-source-chip { min-width: 148px; } - .session-row { grid-template-columns: 1fr; } - .session-row-avatar { display: none; } - .session-row-side { justify-items: start; grid-auto-flow: column; justify-content: start; } - .session-folder-head { flex-direction: column; } - .session-folder-stats { text-align: left; } - .memory-display-card.identity > div:first-child { grid-template-columns: 1fr !important; } - } - - .detail-panel { - background: var(--bg); - border: 1px solid var(--border); - padding: 24px; - margin-top: 20px; - margin-bottom: 18px; - } - .session-detail-preview { - padding: 10px 12px; - margin-bottom: 12px; - background: var(--bg-alt); - border-left: 3px solid var(--accent); - color: var(--ink); - font-size: 13px; - line-height: 1.5; - white-space: pre-wrap; - overflow-wrap: anywhere; - } - .session-detail-preview.compact { - display: -webkit-box; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - overflow: hidden; - } - .session-highlights-list.compact { - max-height: 220px; - overflow: hidden; - } - .detail-panel h3 { - font-size: 15px; - font-weight: 700; - color: var(--ink); - margin-bottom: 16px; - font-family: var(--font-display); - text-transform: uppercase; - letter-spacing: 0.04em; - padding-bottom: 8px; - border-bottom: 2px solid var(--border); - } - .detail-row { - display: flex; - padding: 6px 0; - font-size: 13px; - border-bottom: 1px solid var(--bg-inset); - } - .detail-row .dl { color: var(--ink-muted); width: 140px; flex-shrink: 0; font-family: var(--font-ui); font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 600; padding-top: 2px; } - .detail-row .dv { color: var(--ink); font-family: var(--font-body); } - diff --git a/src/viewer/parts/style/40-legacy-audit-empty-lessons-skills.css b/src/viewer/parts/style/40-legacy-audit-empty-lessons-skills.css deleted file mode 100644 index 1dc1c73d..00000000 --- a/src/viewer/parts/style/40-legacy-audit-empty-lessons-skills.css +++ /dev/null @@ -1,462 +0,0 @@ - .audit-entry { - padding: 12px 0; - border-bottom: 1px solid var(--border-light); - font-size: 13px; - } - .audit-entry:last-child { border-bottom: none; } - .audit-head { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 4px; - } - .audit-detail { - font-size: 12px; - color: var(--ink-faint); - margin-top: 4px; - max-height: 0; - overflow: hidden; - transition: max-height 0.2s; - } - .audit-detail.open { max-height: 200px; } - .audit-detail pre { - font-family: var(--font-mono); - font-size: 11px; - background: var(--bg-alt); - padding: 10px; - border: 1px solid var(--border-light); - overflow-x: auto; - } - - .bar-chart { margin-top: 8px; } - .bar-row { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 6px; - font-size: 12px; - } - .bar-label { width: 120px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--font-mono); font-size: 11px; } - .bar-track { - flex: 1; - height: 6px; - background: var(--bg-inset); - overflow: hidden; - } - .bar-fill { - height: 100%; - transition: width 0.3s; - } - .bar-value { width: 30px; text-align: right; color: var(--ink-muted); font-size: 11px; font-family: var(--font-mono); font-weight: 500; } - - .empty-state { - text-align: center; - padding: 60px 20px; - color: var(--ink-faint); - } - .empty-state .empty-icon { font-size: 36px; margin-bottom: 10px; opacity: 0.4; } - .empty-state .empty-icon.icon { - width: 52px; - height: 52px; - margin: 0 auto 12px; - opacity: 1; - border: 1px solid var(--border-light); - border-radius: 2px; - position: relative; - background: var(--bg-alt); - font-size: 0; - line-height: 0; - } - .empty-state .empty-icon.icon-book::before { - content: ""; - position: absolute; - left: 14px; - right: 14px; - top: 12px; - bottom: 12px; - border: 1px solid var(--ink-muted); - border-radius: 1px; - } - .empty-state .empty-icon.icon-book::after { - content: ""; - position: absolute; - top: 12px; - bottom: 12px; - left: 25px; - width: 1px; - background: var(--ink-faint); - } - .empty-state .empty-icon.icon-clock::before { - content: ""; - position: absolute; - width: 24px; - height: 24px; - left: 14px; - top: 14px; - border: 1px solid var(--ink-muted); - border-radius: 50%; - } - .empty-state .empty-icon.icon-clock::after { - content: ""; - position: absolute; - width: 1px; - height: 10px; - left: 26px; - top: 18px; - background: var(--ink-muted); - box-shadow: 6px 4px 0 0 var(--ink-muted); - transform-origin: bottom; - } - .empty-state .empty-icon.icon-list::before { - content: ""; - position: absolute; - left: 14px; - right: 14px; - top: 14px; - height: 1px; - background: var(--ink-muted); - box-shadow: 0 8px 0 0 var(--ink-muted), 0 16px 0 0 var(--ink-muted); - } - .empty-state p { font-size: 14px; font-family: var(--font-body); font-style: italic; } - .empty-state .empty-title { font-size: 16px; font-weight: 600; font-style: normal; color: var(--ink-muted); margin-bottom: 8px; } - .empty-state .empty-lead { font-style: normal; font-size: 14px; color: var(--ink-muted); max-width: 520px; margin: 0 auto 14px; line-height: 1.5; } - .empty-state pre.empty-cmd { - display: inline-block; margin: 10px auto 12px; padding: 10px 14px; - background: var(--bg-alt); border: 1px solid var(--border); - border-radius: 4px; font-family: var(--font-mono); font-size: 12px; - color: var(--ink); text-align: left; font-style: normal; white-space: pre; - } - .empty-state .empty-link { color: var(--accent); text-decoration: underline; font-size: 13px; font-style: normal; } - - .lesson-workshop { - display: grid; - gap: 14px; - } - .lesson-workshop-head { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 14px; - align-items: center; - padding: 16px 18px; - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: #ffffff; - } - .lesson-workshop-title { - color: var(--ink); - font-family: var(--font-ui); - font-size: 18px; - font-weight: 650; - line-height: 1.25; - } - .lesson-workshop-sub { - margin-top: 4px; - color: var(--ink-muted); - font-size: 13px; - line-height: 1.45; - } - .lesson-count-pill { - padding: 8px 11px; - border: 1px solid var(--border-light); - border-radius: 999px; - color: var(--ink-muted); - background: var(--bg-subtle); - font-size: 12px; - white-space: nowrap; - } - .lesson-head-actions { - display: flex; - align-items: center; - gap: 8px; - } - .add-lesson-btn { - width: 38px; - height: 38px; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0 !important; - border-radius: 999px !important; - font-size: 0 !important; - gap: 0 !important; - } - .add-lesson-btn svg { - width: 18px; - height: 18px; - stroke: currentColor; - } - .lesson-stage-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 10px; - } - .lesson-stage { - display: grid; - grid-template-columns: auto minmax(0, 1fr); - gap: 10px; - align-items: center; - padding: 12px; - border: 1px solid var(--border-light); - border-radius: var(--radius-lg); - background: rgba(255, 255, 255, 0.78); - } - .lesson-stage-icon, - .skill-agent-icon { - width: 34px; - height: 34px; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 999px; - background: var(--bg-subtle); - color: var(--ink-muted); - border: 1px solid var(--border-light); - } - .lesson-stage-icon svg, - .skill-agent-icon svg { - width: 16px; - height: 16px; - stroke: currentColor; - } - .lesson-stage-title { - color: var(--ink); - font-family: var(--font-ui); - font-size: 13px; - font-weight: 650; - } - .lesson-stage-copy { - margin-top: 2px; - color: var(--ink-faint); - font-size: 12px; - line-height: 1.35; - } - .skill-draft-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 12px; - } - .local-skill-grid { - display: grid; - grid-template-columns: minmax(0, 1fr); - gap: 12px; - } - .skill-console-summary { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 10px; - margin-bottom: 12px; - } - .skill-console-stat { - display: flex; - align-items: center; - gap: 10px; - min-width: 0; - padding: 12px; - border: 1px solid var(--border-light); - border-radius: var(--radius-lg); - background: #ffffff; - } - .skill-console-stat-icon { - width: 34px; - height: 34px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - border: 1px solid var(--border-light); - border-radius: 999px; - background: var(--bg-subtle); - color: var(--ink-muted); - } - .skill-console-stat-icon svg { - width: 16px; - height: 16px; - stroke: currentColor; - } - .skill-console-stat-label { - color: var(--ink-faint); - font-size: 11px; - line-height: 1.25; - } - .skill-console-stat-value { - color: var(--ink); - font-family: var(--font-ui); - font-size: 16px; - font-weight: 700; - line-height: 1.15; - } - .skill-filter-row { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; - margin-bottom: 12px; - } - .skill-filter-btn { - display: inline-flex; - align-items: center; - gap: 6px; - min-height: 34px; - padding: 0 11px; - border: 1px solid var(--border-light); - border-radius: 999px; - background: #ffffff; - color: var(--ink-muted); - cursor: pointer; - font-family: var(--font-ui); - font-size: 12px; - white-space: nowrap; - } - .skill-filter-btn svg { - width: 14px; - height: 14px; - stroke: currentColor; - } - .skill-filter-btn.active { - border-color: var(--border-heavy); - color: var(--ink); - background: var(--bg-subtle); - } - .local-skill-card { - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: #ffffff; - padding: 15px; - min-width: 0; - } - .local-skill-head { - display: flex; - justify-content: space-between; - align-items: center; - gap: 10px; - margin-bottom: 10px; - } - .local-skill-title { - color: var(--ink); - font-family: var(--font-ui); - font-size: 15px; - font-weight: 650; - } - .local-skill-sub { - margin-top: 2px; - color: var(--ink-faint); - font-size: 11px; - line-height: 1.35; - } - .local-skill-list { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 8px; - } - .local-skill-chip { - max-width: 100%; - padding: 6px 8px; - border: 1px solid var(--border-light); - border-radius: 999px; - background: var(--bg-subtle); - color: var(--ink-secondary); - font-size: 12px; - line-height: 1.2; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .local-skill-row { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: center; - gap: 10px; - padding: 10px 11px; - border: 1px solid var(--border-light); - border-radius: var(--radius-md); - background: var(--bg-subtle); - min-width: 0; - } - .local-skill-name { - color: var(--ink); - font-family: var(--font-ui); - font-size: 13px; - font-weight: 650; - line-height: 1.25; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .local-skill-path { - margin-top: 3px; - color: var(--ink-faint); - font-size: 11px; - line-height: 1.35; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .local-skill-row-actions { - display: inline-flex; - align-items: center; - gap: 6px; - } - .activity-status-card { - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - margin-bottom: 12px; - padding: 12px 14px; - border: 1px solid var(--border-light); - border-radius: var(--radius-lg); - background: #ffffff; - color: var(--ink-muted); - font-size: 12px; - } - .activity-status-warning { - border-color: var(--border); - background: var(--bg-alt); - color: var(--ink-muted); - } - .activity-status-warning .activity-status-dot { - background: var(--ink-muted); - box-shadow: 0 0 0 4px rgba(12, 10, 9, 0.05); - } - .activity-status-main { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; - } - .activity-status-dot { - width: 8px; - height: 8px; - flex: 0 0 auto; - border-radius: 999px; - background: var(--accent); - box-shadow: 0 0 0 4px rgba(111, 91, 74, 0.08); - } - .activity-status-meta { - flex: 0 0 auto; - color: var(--ink-faint); - font-family: var(--font-mono); - font-size: 11px; - } - .icon-btn { - width: 30px; - height: 30px; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - border: 1px solid var(--border-light); - border-radius: 999px; - background: #ffffff; - color: var(--ink-muted); - cursor: pointer; - } - .icon-btn:hover { - color: var(--ink); - border-color: var(--border); - } - .icon-btn svg { - width: 15px; - height: 15px; - stroke: currentColor; - } diff --git a/src/viewer/parts/style/50-todo-cards.css b/src/viewer/parts/style/50-todo-cards.css deleted file mode 100644 index 6efaeb73..00000000 --- a/src/viewer/parts/style/50-todo-cards.css +++ /dev/null @@ -1,552 +0,0 @@ - .action-overview { - display: flex; - align-items: center; - gap: 8px; - } - .action-overview-card, - .action-group, - .action-item-card { - border: 1px solid var(--border); - border-radius: var(--radius-lg); - background: #ffffff; - } - .action-overview-card { - padding: 7px 10px; - } - button.action-overview-card { - min-width: 78px; - text-align: left; - cursor: pointer; - font: inherit; - } - button.action-overview-card:hover { - border-color: var(--accent); - background: var(--bg-alt); - } - .action-overview-card.primary { - border-color: var(--accent); - background: var(--bg-warm); - } - .action-overview-card.active { - border-color: var(--ink); - box-shadow: inset 0 0 0 1px var(--ink); - } - .action-overview-label { - color: var(--ink-muted); - font-size: 12px; - font-weight: 650; - } - .action-overview-value { - margin-top: 2px; - color: var(--ink); - font-family: var(--font-display); - font-size: 18px; - line-height: 1; - font-weight: 300; - } - .attention-chip-row { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin: -2px 0 12px; - } - .attention-chip { - display: inline-flex; - align-items: center; - justify-content: space-between; - gap: 10px; - min-width: 112px; - min-height: 32px; - padding: 5px 10px; - border: 1px solid var(--border); - border-radius: 8px; - background: #ffffff; - color: var(--ink-muted); - font: inherit; - font-size: 12px; - cursor: pointer; - } - .attention-chip:hover { - border-color: var(--accent); - background: var(--bg-alt); - } - .attention-chip.active { - border-color: var(--ink); - color: var(--ink); - box-shadow: inset 0 0 0 1px var(--ink); - } - .attention-chip-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .attention-chip-value { - flex: 0 0 auto; - color: var(--ink); - font-weight: 650; - } - .action-group { - padding: 14px; - margin-bottom: 12px; - } - .action-group-head { - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - margin-bottom: 10px; - } - .action-group-title { - color: var(--ink); - font-family: var(--font-ui); - font-size: 16px; - font-weight: 650; - } - .done-today-section { opacity: 0.92; } - .action-folded-section { - background: rgba(255,255,255,0.78); - } - .action-folded-head { - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - margin: 0; - padding: 0; - border: none; - background: none; - text-align: left; - cursor: pointer; - font: inherit; - } - .action-folded-head:hover .action-group-title { color: var(--accent, #2563eb); } - .action-folded-lead { - margin-top: 4px; - color: var(--ink-faint); - font-size: 12px; - line-height: 1.35; - } - .action-folded-meta { - display: inline-flex; - align-items: center; - gap: 8px; - flex: 0 0 auto; - } - .action-folded-section .action-card-list { - margin-top: 12px; - } - .done-today-head { - width: 100%; - background: none; - border: none; - cursor: pointer; - text-align: left; - padding: 0; - font: inherit; - } - .done-today-head:hover .action-group-title { color: var(--accent, #2563eb); } - .done-today-sub { font-size: 12px; font-weight: 400; color: var(--ink-faint, #9aa0a6); margin-left: 6px; } - .done-today-caret { color: var(--ink-faint, #9aa0a6); font-size: 13px; } - .action-card-list { - display: grid; - gap: 8px; - } - .awaiting-reply-section .memory-section-title { display: flex; align-items: center; gap: 7px; } - .awaiting-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--danger, #dc2626); - display: inline-block; - flex-shrink: 0; - } - .awaiting-reply-empty { - border: 1px dashed var(--line, #e3e6ea); - border-radius: 8px; - padding: 18px 16px; - margin-top: 10px; - text-align: center; - background: var(--surface-soft, #f8f9fa); - } - .awaiting-reply-empty-title { font-size: 14px; font-weight: 600; color: var(--ink-muted, #6b7280); margin-bottom: 6px; } - .awaiting-reply-empty-lead { font-size: 12.5px; color: var(--ink-faint, #9aa0a6); line-height: 1.5; max-width: 460px; margin: 0 auto; } - .inbox-card-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; } - .inbox-card { - border: 1px solid var(--line, #e3e6ea); - border-radius: 8px; - padding: 11px 13px; - background: var(--surface-soft, #f8f9fa); - } - .inbox-card-question { border-left: 3px solid var(--danger, #dc2626); } - .inbox-card-briefing { border-left: 3px solid var(--accent, #2563eb); } - .inbox-card-answered { border-left: 3px solid var(--green, #16a34a); background: #ffffff; } - .inbox-card-known { border-left: 3px solid var(--border); background: #ffffff; opacity: 0.9; } - .inbox-card-head { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; } - .inbox-card-marker { font-size: 13px; flex-shrink: 0; } - .inbox-card-from { font-size: 12px; font-weight: 600; color: var(--ink-muted, #6b7280); } - .inbox-card-time { font-size: 11px; color: var(--ink-faint, #9aa0a6); margin-left: auto; } - .inbox-delivery { font-size: 10px; padding: 1px 7px; border-radius: 9px; flex-shrink: 0; white-space: nowrap; } - .inbox-delivery-sent { color: var(--ok, #18794e); background: var(--ok-bg, rgba(24,121,78,0.10)); } - .inbox-delivery-failed { color: var(--danger, #b42318); background: var(--danger-bg, rgba(180,35,24,0.10)); display: inline-flex; gap: 5px; align-items: baseline; max-width: 60%; overflow: hidden; } - .inbox-delivery-err { font-weight: 400; opacity: 0.85; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .inbox-card-body { font-size: 13px; color: var(--ink, #1f2329); line-height: 1.5; overflow-wrap: anywhere; } - .inbox-card-body-muted { color: var(--ink-muted, #6b7280); } - .inbox-card-answer { - margin-top: 8px; - padding: 8px 10px; - border-radius: 7px; - background: rgba(22, 163, 74, 0.08); - color: var(--ink, #1f2329); - font-size: 12.5px; - line-height: 1.45; - overflow-wrap: anywhere; - } - .inbox-card-answer-label { color: var(--green, #16a34a); font-weight: 700; margin-right: 4px; } - .inbox-card-actions { margin-top: 8px; } - .inbox-subhead { - display: flex; align-items: center; gap: 7px; - font-size: 12.5px; font-weight: 600; color: var(--ink-muted, #6b7280); - margin: 14px 0 0; - } - .inbox-subhead-marker { font-size: 12px; } - .inbox-subhead-note { font-size: 11px; font-weight: 400; color: var(--ink-faint, #9aa0a6); } - .inbox-subhead-toggle { - width: 100%; background: none; border: none; cursor: pointer; - text-align: left; padding: 0; font: inherit; - } - .inbox-subhead-toggle:hover { color: var(--accent, #2563eb); } - .inbox-subhead-caret { margin-left: auto; color: var(--ink-faint, #9aa0a6); font-size: 12px; } - .inbox-reply-box { margin-top: 9px; } - .inbox-reply-input { - width: 100%; - box-sizing: border-box; - border: 1px solid var(--line, #e3e6ea); - border-radius: 6px; - padding: 7px 9px; - font-size: 13px; - font-family: inherit; - line-height: 1.45; - resize: vertical; - background: var(--surface, #fff); - color: var(--ink, #1f2329); - } - .inbox-reply-input:focus { outline: none; border-color: var(--accent, #2563eb); } - .inbox-reply-actions { display: flex; gap: 8px; margin-top: 7px; } - .action-item-card { - padding: 13px 14px; - } - .action-item-card.action-candidate-card, - .action-item-card.action-approved-card { - display: grid; - grid-template-columns: 6px minmax(0, 1fr) auto; - align-items: start; - gap: 12px; - } - .action-priority-rail { - width: 6px; - align-self: stretch; - min-height: 54px; - border-radius: 999px; - background: var(--border); - } - .action-priority-rail.high { background: var(--red); } - .action-priority-rail.normal { background: var(--accent); } - .action-priority-rail.low { background: var(--ink-faint); } - .action-candidate-main { min-width: 0; } - .action-candidate-head { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; - } - .action-item-title { - color: var(--ink); - font-family: var(--font-ui); - font-size: 15px; - line-height: 1.35; - font-weight: 650; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - } - .action-item-desc { - margin-top: 5px; - color: var(--ink-faint); - font-size: 12px; - line-height: 1.45; - display: -webkit-box; - -webkit-line-clamp: 1; - -webkit-box-orient: vertical; - overflow: hidden; - } - .action-item-meta { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 6px; - margin-top: 10px; - } - .action-meta-text { - color: var(--ink-faint); - font-size: 12px; - line-height: 1.3; - } - .action-evidence-link { - appearance: none; - border: none; - background: none; - padding: 0; - margin: 0; - font-size: 12px; - line-height: 1.3; - color: var(--accent, #2563eb); - cursor: pointer; - font-family: inherit; - } - .action-evidence-link:hover { text-decoration: underline; } - .obs-jump-highlight { - animation: obs-jump-pulse 2.4s ease-out; - border-radius: 6px; - } - @keyframes obs-jump-pulse { - 0% { background: var(--accent-soft, rgba(37,99,235,0.18)); box-shadow: 0 0 0 4px var(--accent-soft, rgba(37,99,235,0.18)); } - 100% { background: transparent; box-shadow: 0 0 0 4px transparent; } - } - .md-pre { - background: var(--surface-soft, #f5f6f8); - border: 1px solid var(--line, #e3e6ea); - border-radius: 6px; - padding: 10px 12px; - margin: 6px 0; - overflow-x: auto; - font-family: var(--font-mono, ui-monospace, monospace); - font-size: 12px; - line-height: 1.5; - } - .md-pre code { background: none; padding: 0; font-size: inherit; } - .md-code { - background: var(--surface-soft, #f5f6f8); - border-radius: 4px; - padding: 1px 5px; - font-family: var(--font-mono, ui-monospace, monospace); - font-size: 0.92em; - } - .md-h { font-size: 14px; font-weight: 600; margin: 8px 0 4px; line-height: 1.35; } - .md-ul { margin: 4px 0; padding-left: 20px; } - .md-ul li { margin: 2px 0; line-height: 1.45; } - .action-item-actions { - display: inline-flex; - align-items: center; - gap: 6px; - white-space: nowrap; - } - /* STEP-16 calm card: source/time + secondary actions hidden at rest, fade in on hover/focus */ - .action-item-submeta { - margin-top: 8px; - color: var(--ink-faint); - font-size: 11px; - line-height: 1.3; - opacity: 0; - transition: opacity .12s ease; - } - .action-secondary { - display: inline-flex; - align-items: center; - gap: 4px; - opacity: 0; - pointer-events: none; - transition: opacity .12s ease; - } - .action-item-card:hover .action-item-submeta, - .action-item-card:focus-within .action-item-submeta { opacity: 1; } - .action-item-card:hover .action-secondary, - .action-item-card:focus-within .action-secondary { - opacity: 1; - pointer-events: auto; - } - .action-recheck-note { - color: var(--accent); - font-size: 11px; - font-weight: 650; - white-space: nowrap; - } - .btn-ghost-sm { - appearance: none; - border: none; - background: none; - padding: 3px 6px; - font-size: 12px; - line-height: 1.3; - color: var(--ink-faint); - cursor: pointer; - font-family: inherit; - border-radius: 6px; - } - .btn-ghost-sm:hover { color: var(--ink); background: var(--bg-warm, rgba(0,0,0,0.05)); } - .action-archive-link { - color: color-mix(in srgb, var(--ink-faint) 82%, transparent); - } - .action-refresh-link { - color: var(--ink-muted); - } - .btn-primary-sm { - appearance: none; - border: 1px solid var(--ink); - background: var(--ink); - color: #ffffff; - padding: 4px 12px; - font-size: 12px; - line-height: 1.3; - cursor: pointer; - font-family: inherit; - border-radius: 7px; - transition: background .12s ease, border-color .12s ease; - } - .btn-primary-sm:hover { - border-color: var(--accent); - background: var(--accent); - } - .btn-outline-sm { - appearance: none; - background: none; - border: 1px solid var(--border); - color: var(--ink-muted); - padding: 4px 12px; - font-size: 12px; - line-height: 1.3; - cursor: pointer; - font-family: inherit; - border-radius: 7px; - transition: border-color .12s ease, color .12s ease; - } - .btn-outline-sm:hover { border-color: var(--accent); color: var(--accent); } - @media (hover: none) { - .action-item-submeta, .action-secondary { opacity: 1; pointer-events: auto; } - } - .actions-filter-row { - display: flex; - gap: 8px; - margin-bottom: 12px; - flex-wrap: wrap; - align-items: center; - } - .action-original-disclosure { - margin-top: 7px; - color: var(--ink-muted); - font-size: 11px; - } - .action-original-disclosure summary { - cursor: pointer; - width: max-content; - } - .action-original-disclosure pre { - margin: 7px 0 0; - padding: 8px 10px; - max-height: 120px; - overflow: auto; - border: 1px solid var(--border-light); - background: var(--bg-alt); - color: var(--ink-muted); - font-family: var(--font-mono); - font-size: 11px; - line-height: 1.45; - white-space: pre-wrap; - } - @media (max-width: 760px) { - .attention-chip { flex: 1 1 calc(50% - 8px); min-width: 0; } - .action-item-card.action-candidate-card, - .action-item-card.action-approved-card { - grid-template-columns: 5px minmax(0, 1fr); - } - .action-item-actions { - grid-column: 2; - justify-self: start; - } - } - .lesson-section-head { - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - margin-top: 4px; - } - .skill-draft-card { - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: #ffffff; - padding: 16px; - min-width: 0; - } - .skill-draft-actions { - display: flex; - gap: 8px; - margin-top: 12px; - flex-wrap: wrap; - } - .skill-draft-preview { - max-height: 56vh; - overflow: auto; - padding: 12px; - border: 1px solid var(--border-light); - border-radius: var(--radius-lg); - background: var(--bg-subtle); - color: var(--ink); - font-family: var(--font-mono); - font-size: 12px; - line-height: 1.55; - white-space: pre-wrap; - } - .skill-draft-head { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 12px; - margin-bottom: 10px; - } - .skill-draft-title { - color: var(--ink); - font-family: var(--font-ui); - font-size: 16px; - font-weight: 650; - line-height: 1.35; - } - .skill-draft-count { - color: var(--ink-faint); - font-size: 12px; - white-space: nowrap; - } - .skill-lesson-list { - display: grid; - gap: 8px; - margin: 0; - padding: 0; - list-style: none; - } - .skill-lesson-list li { - color: var(--ink-secondary); - font-size: 13px; - line-height: 1.45; - padding-top: 8px; - border-top: 1px solid var(--border-light); - } - .skill-agent-row { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 12px; - padding-top: 12px; - border-top: 1px solid var(--border-light); - } - @media (max-width: 760px) { - .lesson-workshop-head, - .lesson-stage-grid, - .skill-console-summary { - grid-template-columns: 1fr; - } - .activity-status-card { - align-items: flex-start; - flex-direction: column; - } - } diff --git a/src/viewer/parts/style/60-editorial-overrides-legacy-memory.css b/src/viewer/parts/style/60-editorial-overrides-legacy-memory.css deleted file mode 100644 index 5b853fc5..00000000 --- a/src/viewer/parts/style/60-editorial-overrides-legacy-memory.css +++ /dev/null @@ -1,608 +0,0 @@ - /* Feature flag banner system — compact collapsed by default */ - .flag-banners { - padding: 0 12px 10px 12px; - background: var(--bg); - flex: 0 0 auto; - position: relative; - z-index: 1; - margin-left: 120px; - } - .flag-banners { display: none; } - .flag-banners:empty { display: none; } - button.flag-summary { - display: flex; align-items: center; gap: 12px; - padding: 8px 14px; border-radius: 4px; - border: 1px solid var(--border); - background: var(--bg-subtle); - font-family: var(--font-ui); font-size: 12px; - color: var(--ink-muted); - cursor: pointer; user-select: none; - width: 100%; text-align: left; - appearance: none; - flex: 1 1 auto; - } - button.flag-summary:hover, - button.flag-summary:focus-visible { background: var(--bg-alt); outline: 2px solid var(--border); outline-offset: 1px; } - .flag-summary .flag-count { color: var(--ink); font-weight: 600; } - .flag-summary .flag-pill { - display: inline-block; padding: 1px 8px; border-radius: 10px; - background: #f59e0b20; color: #d97706; font-size: 11px; font-weight: 600; - margin-right: 6px; - } - .flag-summary .flag-pill.info { background: var(--border-light); color: var(--ink-muted); } - .flag-summary .flag-toggle { margin-left: auto; font-size: 11px; opacity: 0.7; } - .flag-list { - display: none; flex-direction: column; gap: 6px; - margin-top: 6px; - max-height: min(30vh, 260px); - overflow-y: auto; - } - .flag-list.open { display: flex; } - .flag-banner { - display: flex; align-items: flex-start; gap: 10px; - padding: 10px 14px; border-radius: 3px; - border: 1px solid var(--border); - background: var(--bg-subtle); - font-family: var(--font-ui); font-size: 12px; - } - .flag-banner.warn { border-left: 3px solid #f59e0b; } - .flag-banner.info { border-left: 3px solid var(--ink-muted); } - .flag-banner .flag-icon { flex-shrink: 0; font-size: 14px; line-height: 1.3; } - .flag-banner .flag-body { flex: 1; min-width: 0; } - .flag-banner .flag-title { font-weight: 600; color: var(--ink); margin-bottom: 2px; font-size: 12px; } - .flag-banner .flag-title code { font-family: var(--font-mono); font-size: 10px; color: var(--ink-muted); font-weight: 400; margin-left: 4px; } - .flag-banner .flag-desc { color: var(--ink-muted); margin-bottom: 4px; line-height: 1.4; font-size: 12px; } - .flag-banner .flag-enable { - display: block; margin-top: 2px; padding: 5px 8px; - background: var(--bg); border: 1px solid var(--border); border-radius: 3px; - font-family: var(--font-mono); font-size: 10px; color: var(--ink); - white-space: pre-wrap; word-break: break-all; - } - .flag-close { - background: none; border: none; color: var(--ink-faint); cursor: pointer; - font-size: 16px; line-height: 1; padding: 0 4px; font-family: inherit; - flex: 0 0 auto; - } - .flag-close:hover, - .flag-close:focus-visible { color: var(--ink); outline: 2px solid var(--border); outline-offset: 1px; } - - @media (max-width: 960px) { - .tab-bar { - position: relative; - top: 0; - bottom: auto; - left: 0; - width: 100%; - flex-direction: row; - border-right: none; - border-bottom: 1px solid var(--border-light); - padding-top: 0; - overflow-x: auto; - } - .tab-bar button { - border-left: none; - border-bottom: 2px solid transparent; - text-align: center; - } - .tab-icon { display: none; } - .tab-bar button.active { - border-left: none; - border-bottom-color: var(--border-heavy); - } - .tab-main { flex-direction: row; } - .tab-side-meta { - margin-top: 0; - border-top: none; - border-left: 1px solid var(--border-light); - padding: 0 8px; - flex-direction: row; - } - .view, - .flag-banners { - margin-left: 0; - } - } - - .loading { color: var(--ink-faint); padding: 20px; text-align: center; font-style: italic; font-family: var(--font-body); } - .empty { color: var(--ink-muted); padding: 24px; text-align: center; font-family: var(--font-body); font-style: italic; border: 1px dashed var(--border); } - - .replay-controls { display: flex; align-items: center; gap: 6px; padding: 10px 0; flex-wrap: wrap; font-family: var(--font-ui); font-size: 12px; } - .replay-controls button { padding: 4px 10px; border: 1px solid var(--border); background: var(--bg); color: var(--ink); cursor: pointer; font-family: var(--font-ui); font-size: 12px; } - .replay-controls button:hover { background: var(--bg-alt); } - .replay-controls button.active { background: var(--ink); color: var(--bg); } - .replay-controls .sep { width: 12px; } - .replay-progress { height: 3px; background: var(--border-light); margin: 4px 0 12px 0; } - .replay-progress-bar { height: 100%; background: var(--ink); transition: width 100ms linear; } - .replay-grid { display: grid; grid-template-columns: 340px 1fr; gap: 16px; align-items: start; } - .replay-list { max-height: 60vh; overflow-y: auto; border: 1px solid var(--border); } - .replay-event { display: grid; grid-template-columns: 90px 1fr 60px; gap: 8px; padding: 6px 10px; border-bottom: 1px solid var(--border-light); font-family: var(--font-ui); font-size: 11px; cursor: default; } - .replay-event:hover { background: var(--bg-alt); } - .replay-event-active { background: var(--bg-alt); border-left: 2px solid var(--ink); } - - /* ElevenLabs editorial visual pass. */ - :root { - --bg: #f5f5f5; - --bg-alt: #fafafa; - --bg-subtle: #f0efed; - --bg-inset: #f0efed; - --border: #e7e5e4; - --border-light: #f0efed; - --border-heavy: #d6d3d1; - --ink: #0c0a09; - --ink-secondary: #4e4e4e; - --ink-muted: #777169; - --ink-faint: #a8a29e; - --accent: #292524; - --accent-light: #d6d3d1; - --green: #16a34a; - --blue: #4e4e4e; - --yellow: #777169; - --red: #dc2626; - --purple: #777169; - --orange: #777169; - --cyan: #777169; - --surface-card: #ffffff; - --radius-sm: 6px; - --radius-md: 8px; - --radius-lg: 12px; - --radius-xl: 16px; - --radius-xxl: 24px; - --radius-pill: 9999px; - --font-display: "Waldenburg", "Times New Roman", Georgia, serif; - --font-body: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; - --font-ui: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; - --font-mono: "SF Mono", "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace; - } - html[data-theme="dark"] { - --bg: #0c0a09; - --bg-alt: #1c1917; - --bg-subtle: #1c1917; - --bg-inset: #292524; - --border: #292524; - --border-light: #1c1917; - --border-heavy: #44403c; - --ink: #ffffff; - --ink-secondary: #d6d3d1; - --ink-muted: #a8a29e; - --ink-faint: #777169; - --accent: #ffffff; - --surface-card: #1c1917; - } - body { - background: var(--bg); - background-image: - radial-gradient(circle at 82% 12%, rgba(167, 229, 211, 0.30), transparent 30vw), - radial-gradient(circle at 18% 22%, rgba(244, 197, 168, 0.22), transparent 28vw), - radial-gradient(circle at 78% 86%, rgba(200, 184, 224, 0.22), transparent 30vw); - color: var(--ink-secondary); - font-family: var(--font-body); - } - .app-header { display: none !important; } - .app-header .brand h1 { - font-family: var(--font-display); - font-size: 28px; - font-weight: 300; - letter-spacing: 0; - color: var(--ink); - } - .tab-bar { - top: 0; - width: 148px; - border-right: 1px solid var(--border); - background: rgba(245, 245, 245, 0.88); - backdrop-filter: blur(18px); - -webkit-backdrop-filter: blur(18px); - padding: 12px 10px; - } - .tab-bar button { - min-height: 40px; - padding: 9px 12px; - border-left: 0; - border-radius: var(--radius-pill); - color: var(--ink-muted); - font-family: var(--font-ui); - font-size: 15px; - font-weight: 500; - letter-spacing: 0; - text-transform: none; - } - .tab-bar button:hover { - background: var(--bg-alt); - color: var(--ink); - } - .tab-bar button.active { - background: var(--ink); - color: #ffffff; - border-left-color: transparent; - } - .tab-bar button.active .tab-icon { - filter: brightness(0) invert(1); - } - .view { - margin-left: 148px; - padding: 32px; - } - .card, - .stat-card, - .memory-display-card, - .obs-card, - .episode-card, - .session-panel, - .session-item, - .replay-list { - background: var(--surface-card); - border: 1px solid var(--border); - border-radius: var(--radius-xl); - box-shadow: none; - } - .card { - padding: 24px; - margin-bottom: 20px; - } - .card:hover, - .obs-card:hover { - box-shadow: none; - } - .card-title { - font-family: var(--font-ui); - font-size: 14px; - font-weight: 500; - letter-spacing: 0; - text-transform: none; - color: var(--ink); - border-bottom: 1px solid var(--border); - } - .stats-grid { - border: 1px solid var(--border); - border-radius: var(--radius-xxl); - overflow: hidden; - background: var(--surface-card); - } - .stat-card { - border: 0; - border-right: 1px solid var(--border); - border-bottom: 1px solid var(--border); - border-radius: 0; - padding: 24px; - } - .stat-card .label, - th, - .detail-row .dl { - font-family: var(--font-ui); - font-size: 12px; - font-weight: 600; - letter-spacing: 0.08em; - color: var(--ink-muted); - } - .stat-card .value { - font-family: var(--font-display); - font-size: 44px; - font-weight: 300; - letter-spacing: 0; - color: var(--ink); - } - .stat-card .sub { - font-family: var(--font-ui); - font-size: 14px; - color: var(--ink-muted); - } - .btn, - .replay-controls button { - height: 40px; - padding: 9px 19px; - border: 1px solid var(--border-heavy); - border-radius: var(--radius-pill); - background: transparent; - color: var(--ink); - font-family: var(--font-ui); - font-size: 15px; - font-weight: 500; - letter-spacing: 0; - text-transform: none; - transition: background 0.15s, color 0.15s, border-color 0.15s; - } - .btn:hover, - .replay-controls button:hover { - background: var(--bg-subtle); - box-shadow: none; - transform: none; - } - .btn-primary, - .replay-controls button.active { - background: var(--ink); - border-color: var(--ink); - color: #ffffff; - } - .btn-primary:hover { - background: #292524; - color: #ffffff; - } - .btn-danger { - border-color: #dc2626; - color: #dc2626; - } - .btn-danger:hover { - background: #dc2626; - color: #ffffff; - } - html[data-theme="dark"] .tab-bar { - background: rgba(12, 10, 9, 0.92); - } - html[data-theme="dark"] .tab-bar button.active { - background: var(--bg-inset); - color: var(--ink); - box-shadow: inset 0 0 0 1px var(--border-heavy); - } - html[data-theme="dark"] .tab-bar button.active .tab-icon { - filter: none; - } - html[data-theme="dark"] .btn-primary, - html[data-theme="dark"] .replay-controls button.active { - background: #ffffff; - border-color: #ffffff; - color: #0c0a09; - } - html[data-theme="dark"] .btn-primary:hover, - html[data-theme="dark"] .replay-controls button.active:hover { - background: #d6d3d1; - border-color: #d6d3d1; - color: #0c0a09; - } - html[data-theme="dark"] .action-overview-card, - html[data-theme="dark"] .action-group, - html[data-theme="dark"] .action-item-card { - background: var(--surface-card); - border-color: var(--border-heavy); - } - html[data-theme="dark"] button.action-overview-card:hover, - html[data-theme="dark"] .action-overview-card.primary { - background: var(--bg-inset); - } - html[data-theme="dark"] .action-overview-card.active { - border-color: var(--ink); - box-shadow: inset 0 0 0 1px var(--ink); - } - html[data-theme="dark"] .action-item-actions .btn { - background: transparent; - color: var(--ink); - border-color: var(--border-heavy); - } - html[data-theme="dark"] .action-item-actions .btn:hover { - background: var(--bg-inset); - } - html[data-theme="dark"] .action-item-actions .btn-danger { - color: #f87171; - border-color: #ef4444; - } - html[data-theme="dark"] .action-item-actions .btn-danger:hover { - background: #dc2626; - color: #ffffff; - } - .toolbar input, - .toolbar select, - .search-input, - .graph-search, - input, - select, - textarea { - min-height: 44px; - border: 1px solid var(--border); - border-radius: var(--radius-md); - background: var(--surface-card); - color: var(--ink); - font-family: var(--font-body); - font-size: 15px; - padding: 12px 16px; - } - .toolbar input:focus, - .toolbar select:focus, - .search-input:focus, - input:focus, - select:focus, - textarea:focus { - border-color: var(--ink); - box-shadow: none; - outline: none; - } - .badge { - border: 0; - border-radius: var(--radius-pill); - background: var(--bg-subtle); - color: var(--ink); - padding: 4px 10px; - font-family: var(--font-ui); - font-size: 12px; - font-weight: 600; - letter-spacing: 0.08em; - } - .badge-blue, - .badge-green, - .badge-yellow, - .badge-red, - .badge-purple, - .badge-orange, - .badge-cyan, - .badge-muted { - border-color: transparent; - background: var(--bg-subtle); - color: var(--ink); - } - .icon-badge { - width: 30px; - height: 30px; - padding: 0 !important; - border-radius: var(--radius-pill) !important; - display: inline-flex !important; - align-items: center; - justify-content: center; - font-size: 15px !important; - line-height: 1; - letter-spacing: 0 !important; - text-transform: none !important; - font-family: var(--font-ui); - } - .icon-badge svg { - width: 16px; - height: 16px; - display: block; - stroke: currentColor; - } - .icon-badge.done { background: rgba(167, 229, 211, 0.36) !important; color: var(--ink) !important; } - .icon-badge.active { background: rgba(168, 200, 232, 0.34) !important; color: var(--ink) !important; } - .icon-badge.pending { background: rgba(244, 197, 168, 0.32) !important; color: var(--ink) !important; } - .icon-badge.blocked, - .icon-badge.failed, - .icon-badge.cancelled { background: rgba(232, 184, 196, 0.34) !important; color: var(--ink) !important; } - table { - font-family: var(--font-body); - font-size: 15px; - border-collapse: separate; - border-spacing: 0; - } - th { - border-bottom: 1px solid var(--border); - padding: 12px 14px; - } - td { - border-bottom: 1px solid var(--border); - padding: 14px; - color: var(--ink-secondary); - } - tr:hover td { - background: var(--bg-alt); - } - .empty-state { - border: 1px solid var(--border); - border-radius: var(--radius-xxl); - background: var(--surface-card); - color: var(--ink-muted); - padding: 48px 32px; - } - .empty-state .empty-title { - font-family: var(--font-display); - font-size: 28px; - font-weight: 300; - color: var(--ink); - } - .empty-state pre.empty-cmd, - .empty { - border-color: var(--border); - border-radius: var(--radius-lg); - background: var(--bg-alt); - } - .two-col { - gap: 24px; - } - .folder-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 14px; - } - .folder-card { - position: relative; - min-height: 150px; - width: 100%; - text-align: left; - padding: 24px 18px 16px; - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: #ffffff; - color: inherit; - overflow: hidden; - box-shadow: 0 16px 40px rgba(41, 37, 36, 0.04); - cursor: pointer; - transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease; - font: inherit; - } - .folder-card:hover { - border-color: var(--ink-muted); - transform: translateY(-1px); - box-shadow: 0 18px 44px rgba(41, 37, 36, 0.07); - } - .folder-card.selected { - border-color: var(--accent); - box-shadow: 0 18px 44px rgba(41, 37, 36, 0.08); - } - .folder-card .folder-hover-preview { - position: absolute; - left: 12px; - right: 12px; - bottom: 12px; - z-index: 5; - max-height: 76px; - padding: 9px 10px; - border: 1px solid var(--border-light); - border-radius: 8px; - background: rgba(255, 255, 255, 0.96); - color: var(--ink-muted); - font-size: 12px; - line-height: 1.45; - opacity: 0; - transform: translateY(6px); - pointer-events: none; - transition: opacity 0.15s ease, transform 0.15s ease; - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - overflow: hidden; - box-shadow: 0 12px 28px rgba(41, 37, 36, 0.08); - } - .folder-card:hover .folder-hover-preview, - .folder-card:focus-visible .folder-hover-preview { - opacity: 1; - transform: translateY(0); - } - .folder-card:focus-visible { - outline: 2px solid var(--ink); - outline-offset: 2px; - } - .folder-title { - position: relative; - z-index: 3; - font-family: var(--font-ui); - font-size: 18px; - font-weight: 600; - color: var(--ink); - line-height: 1.25; - } - .folder-path { - position: relative; - z-index: 3; - margin-top: 5px; - font-family: var(--font-mono); - font-size: 12px; - color: var(--ink-faint); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .folder-meta { - position: relative; - z-index: 3; - display: flex; - align-items: center; - gap: 12px; - flex-wrap: wrap; - margin-top: 14px; - color: var(--ink-muted); - font-size: 12px; - font-family: var(--font-ui); - } - .folder-meta span { - display: inline-flex; - align-items: center; - gap: 5px; - } - .folder-meta svg { - width: 14px; - height: 14px; - stroke: currentColor; - } - .folder-time { - position: relative; - z-index: 3; - margin-top: 8px; - font-size: 12px; - color: var(--ink-faint); - font-family: var(--font-ui); - } diff --git a/src/viewer/parts/style/70-legacy-crystal-modal-settings-timeline.css b/src/viewer/parts/style/70-legacy-crystal-modal-settings-timeline.css deleted file mode 100644 index 2beff6eb..00000000 --- a/src/viewer/parts/style/70-legacy-crystal-modal-settings-timeline.css +++ /dev/null @@ -1,542 +0,0 @@ - .crystal-intro { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - margin-bottom: 14px; - padding: 18px 20px; - border: 1px solid var(--border); - border-radius: var(--radius-xxl); - background: #ffffff; - } - .crystal-intro-title { - font-family: var(--font-display); - font-size: 22px; - font-weight: 400; - color: var(--ink); - } - .crystal-intro-copy { - margin-top: 5px; - font-size: 14px; - line-height: 1.5; - color: var(--ink-muted); - } - .crystal-count-pill { - flex: 0 0 auto; - padding: 8px 12px; - border: 1px solid var(--border); - border-radius: var(--radius-pill); - color: var(--ink-muted); - font-family: var(--font-ui); - font-size: 13px; - background: var(--bg-subtle); - } - .crystal-list { - display: grid; - gap: 12px; - } - .crystal-card { - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: rgba(255, 255, 255, 0.88); - padding: 20px; - box-shadow: 0 16px 40px rgba(41, 37, 36, 0.04); - } - .crystal-card-head { - display: flex; - justify-content: space-between; - gap: 16px; - align-items: flex-start; - } - .crystal-card-title { - font-family: var(--font-ui); - font-size: 17px; - font-weight: 600; - line-height: 1.45; - color: var(--ink); - } - .crystal-card-time { - white-space: nowrap; - color: var(--ink-faint); - font-family: var(--font-ui); - font-size: 12px; - } - .crystal-meta-row { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 12px; - } - .crystal-section { - margin-top: 14px; - padding-top: 12px; - border-top: 1px solid var(--border); - } - .crystal-section-label { - margin-bottom: 7px; - color: var(--ink-muted); - font-family: var(--font-ui); - font-size: 12px; - font-weight: 600; - } - .crystal-soft-list { - display: grid; - gap: 6px; - color: var(--ink-secondary); - font-size: 14px; - line-height: 1.5; - } - .crystal-soft-list div { - padding: 8px 10px; - border-radius: var(--radius-md); - background: var(--bg-subtle); - } - mark { - background: rgba(244, 197, 168, 0.55); - color: var(--ink); - border-radius: var(--radius-xs); - } - .tab-icon { - width: 18px; - height: 18px; - background-size: 18px 18px; - opacity: 1; - filter: none; - display: inline-block !important; - flex: 0 0 18px; - color: currentColor; - } - .tab-icon svg { - display: block; - width: 100%; - height: 100%; - stroke: currentColor; - } - .tab-bar button { - gap: 10px; - } - .tab-bar button.active .tab-icon { - filter: none; - } - .memory-summary { - position: relative; - min-height: 190px; - padding: 32px; - border: 1px solid var(--border); - border-radius: var(--radius-xxl); - background: - radial-gradient(circle at 94% 18%, rgba(244, 197, 168, 0.12), transparent 24%), - linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(250, 250, 250, 0.88)); - overflow: hidden; - align-items: flex-start; - box-shadow: 0 18px 48px rgba(41, 37, 36, 0.035); - } - .memory-summary::before { - content: ""; - position: absolute; - right: 74px; - top: 24px; - width: 124px; - height: 124px; - border-radius: 50%; - background: - radial-gradient(circle at 68% 38%, rgba(232, 184, 196, 0.42), transparent 16%), - radial-gradient(circle at 46% 36%, rgba(168, 200, 232, 0.42), transparent 42%), - radial-gradient(circle at 76% 56%, rgba(244, 197, 168, 0.42), transparent 30%), - linear-gradient(145deg, rgba(200, 184, 224, 0.38), rgba(167, 229, 211, 0.24)); - box-shadow: inset 18px -14px 34px rgba(255, 255, 255, 0.48), 0 24px 58px rgba(41, 37, 36, 0.035); - filter: blur(0.5px); - opacity: 0.78; - pointer-events: none; - } - .memory-summary::after { - content: ""; - position: absolute; - right: 50px; - top: 104px; - width: 210px; - height: 126px; - border-radius: 50% 50% 0 0 / 74% 68% 0 0; - background: - radial-gradient(circle at 72% 24%, rgba(244, 197, 168, 0.18), transparent 30%), - radial-gradient(circle at 28% 18%, rgba(167, 229, 211, 0.18), transparent 34%), - linear-gradient(135deg, rgba(168, 200, 232, 0.28), rgba(200, 184, 224, 0.16)); - filter: blur(1px); - opacity: 0.68; - transform: rotate(-5deg); - pointer-events: none; - } - .memory-summary > * { - position: relative; - z-index: 1; - max-width: 620px; - } - .memory-summary-title { - font-family: var(--font-display); - font-size: 42px; - line-height: 1.08; - font-weight: 300; - letter-spacing: 0; - color: var(--ink); - } - .memory-summary-sub { - max-width: 520px; - margin-top: 12px; - font-family: var(--font-body); - font-size: 15px; - color: var(--ink-secondary); - line-height: 1.55; - } - .memory-section { - border: 0; - background: transparent; - padding: 0 !important; - box-shadow: none; - } - .memory-section-head { - border-bottom: 0; - margin-bottom: 14px; - padding: 0 2px; - } - .memory-section-title { - font-family: var(--font-display); - font-size: 28px; - font-weight: 300; - color: var(--ink); - } - .memory-section-count, - .mem-meta-pill { - border: 0; - border-radius: var(--radius-pill); - background: var(--bg-subtle); - color: var(--ink-muted); - padding: 5px 11px; - font-family: var(--font-ui); - font-size: 12px; - font-weight: 500; - } - .memory-card-grid { - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 16px; - } - .memory-display-card { - min-height: 190px; - padding: 22px; - border: 1px solid var(--border); - border-radius: var(--radius-xl); - background: rgba(255, 255, 255, 0.84); - box-shadow: 0 18px 44px rgba(41, 37, 36, 0.04); - gap: 14px; - } - .memory-display-card:hover { - box-shadow: 0 22px 54px rgba(41, 37, 36, 0.07); - } - .memory-display-card.identity, - .memory-display-card.preference, - .memory-display-card.project, - .memory-display-card.history, - .memory-display-card.default { - border-left: 1px solid var(--border); - } - .memory-card-title { - font-family: var(--font-ui); - font-size: 18px; - font-weight: 500; - line-height: 1.35; - color: var(--ink); - } - .memory-card-body, - .memory-card-list li { - font-family: var(--font-body); - font-size: 15px; - line-height: 1.6; - color: var(--ink-secondary); - } - .memory-card-list li { - padding-left: 0; - } - .memory-card-list li::before { - display: none; - } - .memory-card-footer { - border-top: 1px solid var(--border); - padding-top: 12px; - } - .memory-card-actions .btn { - height: 32px; - padding: 6px 12px !important; - font-size: 13px !important; - } - .strength-bar { - border-radius: var(--radius-pill); - background: var(--bg-subtle); - } - .strength-bar .fill { - border-radius: var(--radius-pill); - background: #292524 !important; - } - .replay-event-kind { font-size: 9px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-muted); align-self: center; } - .replay-event-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .replay-event-time { text-align: right; font-family: var(--font-mono); color: var(--ink-muted); } - .replay-event-prompt .replay-event-kind { color: var(--blue, #0366d6); } - .replay-event-response .replay-event-kind { color: var(--green, #2ea043); } - .replay-event-tool_call .replay-event-kind { color: var(--orange, #bf8700); } - .replay-event-tool_result .replay-event-kind { color: var(--ink-muted); } - .replay-event-tool_error .replay-event-kind { color: var(--red, #cf222e); } - .replay-detail { border: 1px solid var(--border); padding: 14px; max-height: 60vh; overflow-y: auto; font-family: var(--font-body); font-size: 13px; } - .replay-detail-header { margin-bottom: 6px; } - .replay-body { background: var(--bg-alt); padding: 10px; white-space: pre-wrap; word-break: break-word; font-family: var(--font-mono); font-size: 12px; } - .replay-tool { margin-top: 10px; font-family: var(--font-ui); font-size: 12px; } - .replay-tool-block { margin-top: 8px; } - .replay-tool-block pre { background: var(--bg-alt); padding: 10px; max-height: 240px; overflow: auto; font-family: var(--font-mono); font-size: 11px; white-space: pre-wrap; word-break: break-word; } - .muted { color: var(--ink-muted); font-size: 11px; } - - .metric-table { width: 100%; border-collapse: collapse; font-size: 12px; } - .metric-table th { padding: 6px 8px; font-size: 9px; text-transform: uppercase; letter-spacing: 0.12em; color: var(--ink-muted); border-bottom: 2px solid var(--border); text-align: left; font-family: var(--font-ui); font-weight: 600; } - .metric-table td { padding: 5px 8px; border-bottom: 1px solid var(--border-light); } - .metric-table tr:hover td { background: var(--bg-alt); } - .metric-fn { font-family: var(--font-mono); font-size: 11px; color: var(--blue); } - .metric-num { font-family: var(--font-mono); color: var(--ink); text-align: right; } - - .cb-indicator { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; font-size: 10px; font-weight: 600; font-family: var(--font-ui); text-transform: uppercase; letter-spacing: 0.08em; border: 1px solid; } - .cb-closed { border-color: var(--green); color: var(--green); } - .cb-open { border-color: var(--accent); color: var(--accent); } - .cb-half-open { border-color: var(--yellow); color: var(--yellow); } - - .worker-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 12px; font-family: var(--font-ui); } - .worker-dot { width: 8px; height: 8px; } - .worker-dot.running { background: var(--green); } - .worker-dot.stopped { background: var(--accent); } - .worker-dot.starting { background: var(--yellow); } - - .gauge { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; } - .gauge-bar { flex: 1; height: 6px; background: var(--bg-inset); overflow: hidden; } - .gauge-fill { height: 100%; transition: width 0.5s; } - .gauge-label { width: 90px; font-size: 10px; color: var(--ink-muted); font-family: var(--font-ui); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; } - .gauge-value { width: 70px; font-size: 11px; color: var(--ink); text-align: right; font-family: var(--font-mono); } - - .obs-type-icon { font-size: 16px; margin-right: 4px; } - .obs-subtitle { font-size: 12px; color: var(--ink-faint); margin-top: 2px; font-style: italic; } - .obs-importance { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; font-size: 11px; font-weight: 700; font-family: var(--font-mono); border: 1px solid; } - .imp-1, .imp-2, .imp-3 { border-color: var(--green); color: var(--green); } - .imp-4, .imp-5, .imp-6 { border-color: var(--yellow); color: var(--yellow); } - .imp-7, .imp-8, .imp-9, .imp-10 { border-color: var(--accent); color: var(--accent); } - - .card-title, - .stat-card .label, - .badge, - .gauge-label, - .metric-table th, - .cb-indicator, - .type-chip, - .timeline-date-marker span, - .replay-event-kind, - .detail-row .dl { - text-transform: none !important; - letter-spacing: 0 !important; - } - .gauge-bar, - .strength-bar { - border-radius: 999px; - background: var(--bg-subtle); - } - .gauge-fill, - .strength-bar .fill { - border-radius: 999px; - } - .obs-card, - .episode-card, - .memory-display-card, - .memory-summary, - .lesson-card, - .action-card { - border-radius: 16px !important; - background: var(--surface) !important; - border-color: var(--border-light) !important; - box-shadow: none !important; - } - - .three-col { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; } - @media (max-width: 1100px) { .three-col { grid-template-columns: 1fr 1fr; } } - @media (max-width: 768px) { .three-col { grid-template-columns: 1fr; } } - - .pagination { - display: flex; - justify-content: center; - gap: 8px; - margin-top: 20px; - } - - .modal-overlay { - display: none; - position: fixed; - inset: 0; - background: rgba(0,0,0,0.3); - z-index: 100; - align-items: center; - justify-content: center; - } - .modal-overlay.open { display: flex; } - .modal { - background: var(--bg); - border: 2px solid var(--border); - padding: 28px; - max-width: 460px; - width: 90%; - box-shadow: 6px 6px 0px 0px var(--border); - } - .modal h3 { - font-size: 18px; - font-weight: 700; - color: var(--ink); - margin-bottom: 12px; - font-family: var(--font-display); - } - .modal p { font-size: 13px; color: var(--ink-muted); margin-bottom: 16px; } - .modal-actions { - display: flex; - justify-content: flex-end; - gap: 8px; - } - .settings-gear { - position: fixed; - left: 18px; - bottom: 18px; - z-index: 90; - width: 46px; - height: 46px; - border: 1px solid var(--border-heavy); - border-radius: 50%; - background: var(--surface-card); - color: var(--ink); - box-shadow: 0 12px 28px rgba(0,0,0,0.12); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: transform 0.15s, background 0.15s, border-color 0.15s; - } - .settings-gear:hover, - .settings-gear:focus-visible { - transform: translateY(-1px) rotate(12deg); - background: var(--bg-subtle); - outline: 2px solid var(--border); - outline-offset: 2px; - } - .settings-gear svg { - width: 22px; - height: 22px; - stroke: currentColor; - } - .settings-panel { - display: none; - position: fixed; - left: 18px; - bottom: 76px; - z-index: 89; - width: min(430px, calc(100vw - 36px)); - max-height: min(720px, calc(100vh - 96px)); - overflow: auto; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--surface-card); - box-shadow: 0 18px 42px rgba(0,0,0,0.14); - padding: 18px; - } - .settings-panel.open { display: block; } - .settings-head { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 12px; - margin-bottom: 14px; - } - .settings-title { - font-family: var(--font-display); - font-size: 20px; - font-weight: 600; - color: var(--ink); - letter-spacing: 0; - } - .settings-sub { - margin-top: 4px; - font-size: 12px; - color: var(--ink-muted); - line-height: 1.45; - } - .settings-section { - border-top: 1px solid var(--border-light); - padding-top: 14px; - margin-top: 14px; - } - .settings-section:first-of-type { margin-top: 0; } - .settings-section-title { - font-family: var(--font-ui); - font-size: 12px; - font-weight: 700; - color: var(--ink); - margin-bottom: 10px; - } - .settings-grid { - display: grid; - grid-template-columns: 1fr; - gap: 10px; - } - .settings-language { - display: flex; - gap: 8px; - flex-wrap: wrap; - } - .settings-language .btn.active { - background: var(--ink); - border-color: var(--ink); - color: #fff; - } - .selected-node-info { - margin-top: 16px; - padding-top: 16px; - border-top: 1px solid var(--border-light); - } - .selected-node-info h4 { - font-size: 13px; - font-weight: 700; - color: var(--ink); - margin-bottom: 6px; - font-family: var(--font-display); - } - .selected-node-info .prop { - font-size: 12px; - color: var(--ink-muted); - padding: 2px 0; - font-family: var(--font-ui); - } - .two-col { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 16px; - } - @media (max-width: 768px) { - .two-col { grid-template-columns: 1fr; } - .graph-sidebar { width: 200px; } - .stats-grid { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); } - } - - .section-rule { - border: none; - border-top: 1px solid var(--border-light); - margin: 20px 0; - } - .timeline-container { position: relative; padding: 20px 0; } - .timeline-container::before { content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 2px; background: var(--border-light); transform: translateX(-50%); } - .timeline-item { position: relative; width: 45%; margin-bottom: 20px; min-width: 0; } - .timeline-item.tl-left { margin-left: 0; margin-right: auto; text-align: right; padding-right: 30px; } - .timeline-item.tl-right { margin-left: auto; margin-right: 0; padding-left: 30px; } - .timeline-dot { position: absolute; width: 12px; height: 12px; border-radius: 50%; top: 16px; z-index: 1; border: 2px solid var(--bg); } - .timeline-item.tl-left .timeline-dot { right: -6px; transform: translateX(50%); } - .timeline-item.tl-right .timeline-dot { left: -6px; transform: translateX(-50%); } - .timeline-connector { position: absolute; top: 21px; height: 1px; background: var(--border-light); width: 24px; } - .timeline-item.tl-left .timeline-connector { right: 0; } - .timeline-item.tl-right .timeline-connector { left: 0; } - .timeline-date-marker { text-align: center; position: relative; margin: 24px 0 16px; z-index: 2; } - .timeline-date-marker span { background: var(--bg); padding: 4px 16px; font-size: 10px; font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-muted); border: 1px solid var(--border-light); } - diff --git a/src/viewer/parts/style/80-legacy-heatmap-replay.css b/src/viewer/parts/style/80-legacy-heatmap-replay.css deleted file mode 100644 index fbac9d95..00000000 --- a/src/viewer/parts/style/80-legacy-heatmap-replay.css +++ /dev/null @@ -1,432 +0,0 @@ - .heatmap-wrap { overflow-x: auto; padding: 8px 0; } - .heatmap-grid { display: grid; grid-template-rows: repeat(7, 1fr); grid-auto-flow: column; grid-auto-columns: 12px; gap: 2px; } - .heatmap-cell { width: 10px; height: 10px; background: var(--bg-inset); cursor: default; } - .heatmap-cell[title] { cursor: pointer; } - .heatmap-cell.level-1 { background: rgba(45,106,79,0.2); } - .heatmap-cell.level-2 { background: rgba(45,106,79,0.4); } - .heatmap-cell.level-3 { background: rgba(45,106,79,0.65); } - .heatmap-cell.level-4 { background: var(--green); } - .heatmap-labels { display: flex; gap: 2px; font-size: 9px; color: var(--ink-faint); font-family: var(--font-mono); margin-bottom: 4px; } - - .graph-search { width: 100%; background: var(--bg); border: 1px solid var(--border); padding: 7px 12px; font-size: 12px; font-family: var(--font-ui); margin-bottom: 12px; outline: none; } - .graph-search:focus { border-color: var(--ink); box-shadow: 2px 2px 0px 0px var(--border); } - .graph-legend { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border-light); } - .graph-legend-item { display: flex; align-items: center; gap: 6px; padding: 3px 0; font-size: 11px; font-family: var(--font-ui); color: var(--ink-muted); } - .graph-legend-shape { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; } - .graph-tooltip { position: absolute; background: rgba(255,255,255,0.88); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid rgba(17,17,17,0.08); padding: 12px 16px; font-size: 11px; font-family: var(--font-ui); pointer-events: none; z-index: 10; box-shadow: 0 8px 32px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.06); max-width: 260px; display: none; border-radius: 8px; transition: opacity 0.15s ease; } - .graph-tooltip.visible { display: block; opacity: 1; } - .graph-tooltip .tt-name { font-weight: 700; color: var(--ink); margin-bottom: 4px; font-family: var(--font-display); font-size: 13px; } - .graph-tooltip .tt-type { font-size: 9px; text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 6px; font-weight: 600; padding: 2px 6px; border-radius: 3px; display: inline-block; } - .graph-tooltip .tt-prop { font-size: 10px; color: var(--ink-muted); padding: 1px 0; } - .graph-tooltip .tt-conns { font-size: 10px; color: var(--ink-faint); margin-top: 6px; border-top: 1px solid rgba(17,17,17,0.08); padding-top: 6px; font-family: var(--font-mono); } - .graph-controls { position: absolute; bottom: 16px; right: 16px; display: flex; flex-direction: column; gap: 2px; z-index: 5; } - .graph-controls button { width: 36px; height: 36px; font-size: 18px; cursor: pointer; background: rgba(255,255,255,0.92); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); border: 1px solid rgba(17,17,17,0.1); color: var(--ink); display: flex; align-items: center; justify-content: center; font-weight: 500; font-family: var(--font-ui); border-radius: 6px; transition: all 0.15s ease; box-shadow: 0 2px 8px rgba(0,0,0,0.06); } - .graph-controls button:hover { background: var(--ink); color: var(--bg); transform: scale(1.05); box-shadow: 0 4px 12px rgba(0,0,0,0.15); } - .graph-controls .ctrl-divider { height: 1px; background: var(--border-light); margin: 2px 4px; } - - .type-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 16px; } - .type-chip { font-size: 10px; padding: 3px 10px; border: 1px solid var(--border-light); cursor: pointer; font-family: var(--font-ui); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600; transition: all 0.15s; background: var(--bg); } - .type-chip:hover { border-color: var(--ink); } - .type-chip.active { background: var(--ink); color: var(--bg); border-color: var(--ink); } - - .memory-fact { padding: 8px 0; border-bottom: 1px solid var(--border-light); font-size: 13px; display: flex; justify-content: space-between; align-items: center; gap: 8px; } - .memory-fact:last-child { border-bottom: none; } - .procedure-item { padding: 10px 0; border-bottom: 1px solid var(--border-light); } - .procedure-item:last-child { border-bottom: none; } - .procedure-steps { margin: 6px 0 0 16px; font-size: 12px; color: var(--ink-muted); } - .procedure-steps li { margin-bottom: 2px; } - .consolidation-row { display: flex; justify-content: space-between; padding: 4px 0; font-size: 12px; font-family: var(--font-ui); } - .consolidation-row .cl { color: var(--ink-muted); } - .consolidation-row .cv { color: var(--ink); font-weight: 600; font-family: var(--font-mono); } - - .activity-feed-item { display: flex; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--border-light); font-size: 13px; } - .activity-feed-item:last-child { border-bottom: none; } - .activity-feed-icon { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; border: 1px solid var(--border-light); } - .activity-feed-body { flex: 1; min-width: 0; } - .activity-feed-title { font-weight: 600; color: var(--ink); font-family: var(--font-display); font-size: 13px; } - .activity-feed-meta { font-size: 10px; color: var(--ink-faint); font-family: var(--font-mono); margin-top: 2px; } - .mem-title { position: relative; display: inline-block; max-width: 100%; cursor: help; } - .mem-title-text { color: var(--ink); font-weight: 600; border-bottom: 1px dashed transparent; transition: border-color .15s ease; } - .mem-title:hover .mem-title-text { border-bottom-color: var(--border); } - .mem-hover-card { - display: none; - position: absolute; - left: 0; - top: calc(100% + 8px); - width: min(520px, 72vw); - background: var(--bg); - border: 1px solid var(--border); - box-shadow: var(--shadow-2); - z-index: 20; - padding: 10px 12px; - border-radius: 8px; - font-size: 12px; - color: var(--ink-muted); - line-height: 1.55; - white-space: normal; - } - .mem-title:hover .mem-hover-card { display: block; } - .mem-row .btn.btn-danger { opacity: .35; transition: opacity .15s ease, transform .15s ease; } - .mem-row:hover .btn.btn-danger { opacity: 1; transform: translateY(-1px); } - .mem-meta-pill { - display:inline-flex; align-items:center; gap:4px; font-size:10px; color:var(--ink-faint); - border:1px solid var(--border-light); padding:2px 6px; border-radius:999px; background:var(--bg-subtle); - cursor: help; - } - .memory-summary { - margin-bottom: 12px; - padding: 12px 14px; - border: 1px solid var(--border-light); - background: #ffffff; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - flex-wrap: wrap; - } - .memory-summary-title { - font-size: 13px; - color: var(--ink); - font-weight: 800; - font-family: var(--font-display); - } - .memory-summary-sub { - margin-top: 3px; - font-size: 12px; - color: var(--ink-muted); - line-height: 1.45; - } - .memory-summary-actions { - display: flex; - align-items: center; - gap: 8px; - margin-left: auto; - } - .add-memory-btn { - display: inline-flex; - align-items: center; - gap: 8px; - white-space: nowrap; - } - .add-memory-btn svg { - width: 16px; - height: 16px; - stroke: currentColor; - } - .memory-add-form { - display: grid; - gap: 12px; - margin-top: 14px; - } - .memory-add-form label { - display: grid; - gap: 6px; - font-family: var(--font-ui); - font-size: 12px; - color: var(--ink-muted); - } - .memory-add-form textarea, - .memory-add-form input, - .memory-add-form select { - width: 100%; - border: 1px solid var(--border); - border-radius: var(--radius-md); - background: var(--surface-card); - color: var(--ink); - font-family: var(--font-body); - font-size: 15px; - line-height: 1.55; - padding: 12px 14px; - outline: none; - } - .memory-add-form textarea { - min-height: 150px; - resize: vertical; - } - .memory-add-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 10px; - } - .memory-form-error { - display: none; - color: var(--red); - font-size: 12px; - } - .memory-section { - margin-bottom: 14px; - } - .memory-section-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-bottom: 8px; - padding-bottom: 7px; - border-bottom: 1px solid var(--border-light); - } - .memory-section-title { - font-size: 13px; - font-weight: 800; - color: var(--ink); - font-family: var(--font-display); - } - .memory-section-count { - font-size: 11px; - color: var(--ink-faint); - font-family: var(--font-ui); - } - .memory-card-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 10px; - } - .memory-display-card { - border: 1px solid var(--border-light); - background: var(--bg); - padding: 12px 13px; - min-width: 0; - display: flex; - flex-direction: column; - gap: 8px; - } - .memory-display-card:hover { - box-shadow: 2px 2px 0 0 var(--border-light); - } - .memory-display-card.identity { border-left: 3px solid var(--purple); } - .memory-display-card.preference { border-left: 3px solid var(--blue); } - .memory-display-card.project { border-left: 3px solid var(--green); } - .memory-display-card.history { border-left: 3px solid var(--yellow); } - .memory-display-card.default { border-left: 3px solid var(--border); } - .memory-card-top { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px; - } - .memory-card-title { - font-size: 14px; - color: var(--ink); - font-weight: 800; - line-height: 1.35; - font-family: var(--font-display); - overflow-wrap: anywhere; - } - .memory-card-body { - font-size: 13px; - color: var(--ink-secondary); - line-height: 1.58; - overflow-wrap: anywhere; - word-break: break-word; - } - .memory-card-list { - display: grid; - gap: 6px; - margin: 0; - padding: 0; - list-style: none; - } - .memory-card-list li { - position: relative; - padding-left: 14px; - font-size: 13px; - color: var(--ink-secondary); - line-height: 1.5; - overflow-wrap: anywhere; - } - .memory-card-list li::before { - content: ""; - position: absolute; - left: 0; - top: 0.72em; - width: 5px; - height: 5px; - background: var(--ink-muted); - } - .memory-card-footer { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - flex-wrap: wrap; - margin-top: auto; - padding-top: 6px; - border-top: 1px solid var(--border-light); - } - .memory-card-actions { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: wrap; - } - .memory-raw-card { - margin-top: 10px; - border: 1px dashed var(--border-light); - background: var(--bg-subtle); - padding: 10px 12px; - font-size: 12px; - color: var(--ink-muted); - line-height: 1.55; - } - .memory-display-card, - .memory-display-card.identity, - .memory-display-card.preference, - .memory-display-card.project, - .memory-display-card.history, - .memory-display-card.default { - min-height: 190px; - padding: 24px; - border: 1px solid var(--border) !important; - border-left: 1px solid var(--border) !important; - border-radius: var(--radius-xl) !important; - background: rgba(255, 255, 255, 0.88) !important; - box-shadow: 0 18px 44px rgba(41, 37, 36, 0.04) !important; - gap: 16px; - } - .memory-display-card:hover { - box-shadow: 0 22px 54px rgba(41, 37, 36, 0.07) !important; - } - .memory-card-title { - font-family: var(--font-ui) !important; - font-size: 18px !important; - font-weight: 500 !important; - letter-spacing: 0 !important; - } - .memory-card-body, - .memory-card-list li { - font-family: var(--font-body) !important; - font-size: 15px !important; - line-height: 1.6 !important; - color: var(--ink-secondary) !important; - } - .memory-card-list li { - padding-left: 0 !important; - } - .memory-card-list li::before { - display: none !important; - } - .memory-card-footer { - border-top: 1px solid var(--hairline-soft, var(--border-light)) !important; - padding-top: 14px !important; - margin-top: auto !important; - } - .memory-card-footer .mem-meta-pill { - background: transparent !important; - border: 0 !important; - color: var(--ink-muted) !important; - padding: 0 !important; - font-size: 12px !important; - font-weight: 400 !important; - } - .memory-card-footer .mem-meta-pill.icon-badge { - width: 30px !important; - height: 30px !important; - padding: 0 !important; - background: rgba(244, 197, 168, 0.20) !important; - color: var(--ink-muted) !important; - } - .memory-card-actions .btn { - height: 34px !important; - padding: 7px 13px !important; - border-radius: var(--radius-pill) !important; - font-size: 13px !important; - font-weight: 500 !important; - } - .memory-card-actions .btn-danger { - color: var(--ink-muted) !important; - border-color: var(--border) !important; - background: transparent !important; - } - .memory-card-actions .btn-danger:hover { - color: var(--semantic-error, #dc2626) !important; - border-color: rgba(220, 38, 38, 0.28) !important; - background: rgba(220, 38, 38, 0.04) !important; - box-shadow: none !important; - } - .memory-summary { - background: #ffffff !important; - border-color: var(--border) !important; - box-shadow: 0 18px 48px rgba(41, 37, 36, 0.025) !important; - min-height: 0 !important; - padding: 14px 16px !important; - border-radius: var(--radius-xl) !important; - } - .memory-summary::before, - .memory-summary::after { - display: none !important; - } - .memory-summary-title { - font-size: 16px !important; - line-height: 1.2 !important; - } - .memory-summary-sub { - margin-top: 3px !important; - font-size: 12px !important; - line-height: 1.35 !important; - } - .add-memory-btn { - width: 38px !important; - height: 38px !important; - padding: 0 !important; - justify-content: center !important; - border-radius: 999px !important; - gap: 0 !important; - font-size: 0 !important; - } - .add-memory-btn svg { - width: 18px !important; - height: 18px !important; - } - .memory-toolbar { - justify-content: flex-start !important; - gap: 10px !important; - margin-bottom: 18px !important; - } - .memory-toolbar select { - margin-left: auto !important; - } - .memory-search-wrap { - position: relative; - flex: 0 0 auto; - width: 44px; - height: 44px; - transition: width 0.18s ease; - } - .memory-search-wrap:focus-within, - .memory-search-wrap.has-search { - width: min(360px, 52vw); - } - .memory-search-wrap svg { - position: absolute; - left: 13px; - top: 50%; - width: 18px; - height: 18px; - transform: translateY(-50%); - stroke: var(--ink-muted); - pointer-events: none; - z-index: 1; - } - .toolbar .memory-search-input { - width: 44px !important; - min-width: 44px !important; - height: 44px !important; - padding: 0 0 0 42px !important; - border: 0 !important; - border-radius: 13px !important; - background: rgba(255, 255, 255, 0.72) !important; - box-shadow: 0 10px 26px rgba(41, 37, 36, 0.05) !important; - color: var(--ink) !important; - cursor: pointer; - } - .memory-search-wrap:focus-within .memory-search-input, - .memory-search-wrap.has-search .memory-search-input { - width: 100% !important; - cursor: text; - } - .toolbar .memory-search-input::placeholder { - color: transparent; - } - .memory-search-wrap:focus-within .memory-search-input::placeholder { - color: var(--ink-faint); - } diff --git a/src/viewer/parts/style/90-responsive-avatars.css b/src/viewer/parts/style/90-responsive-avatars.css deleted file mode 100644 index a7931abd..00000000 --- a/src/viewer/parts/style/90-responsive-avatars.css +++ /dev/null @@ -1,164 +0,0 @@ - @media (min-width: 721px) and (max-width: 960px) { - .tab-bar { - position: fixed !important; - top: 0 !important; - bottom: 0 !important; - left: 0 !important; - width: 148px !important; - flex-direction: column !important; - border-right: 1px solid var(--border) !important; - border-bottom: 0 !important; - padding: 12px 10px !important; - overflow-x: hidden !important; - overflow-y: auto !important; - } - .tab-main { - flex-direction: column !important; - } - .tab-bar button { - width: 100% !important; - justify-content: flex-start !important; - border-bottom: 0 !important; - text-align: left !important; - } - .view, - .flag-banners { - margin-left: 148px !important; - } - } - @media (max-width: 720px) { - .tab-bar { - position: sticky !important; - top: 0 !important; - bottom: auto !important; - left: 0 !important; - width: 100% !important; - flex-direction: row !important; - border-right: 0 !important; - border-bottom: 1px solid var(--border) !important; - padding: 8px 10px !important; - overflow-x: auto !important; - overflow-y: hidden !important; - z-index: 3 !important; - align-items: center !important; - } - .tab-main { - flex-direction: row !important; - align-items: center !important; - flex: 0 0 auto !important; - gap: 6px !important; - overflow-x: auto !important; - overflow-y: hidden !important; - padding-bottom: 0 !important; - } - .tab-bar button { - width: auto !important; - min-width: max-content !important; - min-height: 36px !important; - padding: 8px 12px !important; - border-bottom: 0 !important; - justify-content: center !important; - } - .tab-bar button.active { - min-height: 44px !important; - padding: 8px 14px !important; - } - .tab-icon { - width: 16px !important; - height: 16px !important; - background-size: 16px 16px !important; - } - .tab-side-meta { - display: none !important; - } - .view, - .flag-banners { - margin-left: 0 !important; - } - .view { - padding: 18px 14px 28px !important; - } - .toolbar { - display: grid !important; - grid-template-columns: 1fr !important; - gap: 10px !important; - } - .toolbar input, - .toolbar select { - width: 100% !important; - min-width: 0 !important; - } - .memory-summary { - min-height: 168px !important; - padding: 24px !important; - } - .memory-summary-title { - font-size: 32px !important; - } - .memory-summary-sub { - max-width: 70% !important; - font-size: 13px !important; - } - .memory-summary::before { - right: 18px !important; - top: 32px !important; - width: 92px !important; - height: 92px !important; - } - .memory-summary::after { - right: -4px !important; - top: 98px !important; - width: 150px !important; - height: 86px !important; - } - .memory-card-grid { - grid-template-columns: 1fr !important; - } - .memory-display-card, - .memory-display-card.identity, - .memory-display-card.preference, - .memory-display-card.project, - .memory-display-card.history, - .memory-display-card.default { - min-height: 0 !important; - } - .memory-card-top { - gap: 12px !important; - } - .memory-card-footer { - align-items: flex-start !important; - flex-direction: column !important; - gap: 12px !important; - } - .memory-card-actions { - width: 100% !important; - display: flex !important; - flex-wrap: wrap !important; - } - .memory-card-actions .btn { - flex: 1 1 128px !important; - min-width: 0 !important; - justify-content: center !important; - } - } - .agent-list { display: grid; gap: 8px; } - .agent-row { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:8px 0; border-bottom:1px solid var(--border-light); } - .agent-row:last-child { border-bottom:none; } - .agent-main { display:flex; align-items:center; gap:8px; min-width:0; } - .agent-avatar { width:24px; height:24px; border-radius:999px; background:var(--bg-alt); border:1px solid var(--border); display:flex; align-items:center; justify-content:center; font-size:0; color:var(--ink); font-weight:700; flex-shrink:0; background-repeat:no-repeat; background-position:center; background-size:cover; overflow:hidden; } - .agent-avatar::before { content: attr(data-label); font-size:12px; } - .agent-avatar.has-image { background-size:cover !important; background-position:center !important; } - .agent-avatar.has-image::before { content:""; } - .agent-avatar img { width:100%; height:100%; display:block; object-fit:cover; border-radius:inherit; } - .agent-avatar.has-image::before { content:"" !important; display:none !important; } - .agent-avatar.codex { background-color:#fff; border-color:#111; background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAtEAAALRCAYAAACdyf8HAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAC0aADAAQAAAABAAAC0QAAAADIUB9OAABAAElEQVR4Aey9B9wlRbWvPXi/75whw5AFHLIkCQKCEgYFDICoCIIoMIhEEQVRQImigICIgpLUIcgREEFAMIGMShgRJEhOM+TokMF7v/ude/8L5pU97+zQvbtyP+v3W/Pu6VBrraequ9euXV01ZgwCAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAE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否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否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否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否jhn0GwO/ZmzURYwUAQIECBAgQIBAH4Gzea5fE+W5brrUvOd+PNjni+IpAgQIECBAgECvwMd54o9E49xtgxoDdQVGRYAAAQIECBAgMIDAd3ntk0Qz3S2DutrmDwOMEy8lQIAAAQIECBDoEajLoP+UaKS7YXAx+/rDnjHgIQECBAgQIECAwJACU3nfbKKZbqfBb9m3nw45NryNAAECBAgQIEBgAYFj+ft8opluh8E/2ZffLrDP/ZkAAQIECBAgQGAJBLbkMy4nGunxNahz3evy74oAAQIECBAgQGCZBbZmeXUOrWZ6fAzqR4PnEnM+B0ERIECAAAECBEYpMJGFX0hc9bC5zXQdeT6TbEgUAQIECBAgQIBAgwRqVofzSR3tdHS6GQZz2RcnknWJIkCAAAECBAgQaLBAHe08mTxKNNOjMZiO/eFEESBAgAABAgQIjJnA6qxvzebxV6KZfv8GL+J8JdmVKAIECBAgQIAAgRYIfJJtqPOmHZ1e+mb6flyPJx8kigABAgQIECBAoKUC32S7riaOTg9vUOed/5J8nSgCBAgQIECAAIEOCdS500eT6URDvbDBwzj9mBxIFAECBAgQIECAAIEVkzE4ndxKNNRvDe7E41SyO1EECBAYicDKkSzVQgkQIEBgUIG1ecP+pI64Hky61EDWlHT1j8TN5HryOFEECBAYqYAmeqT8Fk6AAIGhBerqep8n+5Kp17frc9uGepCN+DOpxvn35N9EESBAoFECmuhG7Q4rQ4AAgUUJ1NHpz5Jqqut2T9L0upcVrOn+7r6Tp01faetHgAABTbQxQIAAgXYLfJHN25vsTD5KtiU7kuWs51lYTeNXp2VU0/x3MpvMJIoAAQJjKaCJHsvdZqUJECCwaIHN+YSJpC5Lvimp00M2vnN/Ve73q5d58sXrVHP85n7v7bP8bT75P6nXKQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQFiMecUAAAAqSURBVIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBBoq8ArB3Eya1NpS4EAAAAASUVORK5CYII="); } - .agent-avatar.hermes { background-color:#eef2f7; border-color:#cbd5e1; background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABuwAAAcHCAYAAAAmQlWYAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAG7KADAAQAAAABAAAHBwAAAAC+zNuXAABAAElEQVR4Aey96XobyXKumxQHSaQoiRI1q1utnpbXWn7sM9yCf/h6fCG+Df84d7D/n/3sc55tex972e2e1K15FjWRojie+CLzq8oqFEAABIjpKwmoHCIjI98aAGYgskLQJgIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiMDICc9bz/zWy3tWxCIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACMw4ATjsDmecgYYvAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiMjcGpkPatjERABERABERABERABERABERABERABERABERABERABERABERABERCBIIedTgIREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAERGCEBOexGCF9di4AIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAcdjoHREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAERGCEBOSwGyF8dS0CIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACctjpHBABERABERABERABERABERABERABERABERABERABERABERABERCBERKQw26E8NW1CIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACMhhp3NABERABERABERABERABERABERABERABERABERABERABERABERABEZIQA67EcJX1yIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgh53OAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREYIQE57EYIX12LgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgBx2OgdEQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREYIQE5LAbIXx1LQIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAJy2OkcEAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREIEREljot++///u/D//wD//Qb3O1EwEREAEROCECh4eHRU9zc3NFWomTI6BjcHKs1dPkEdD1MXnHTBaLgAiIgAiIgAiMH4H8OxWs099+rccoZyQ+rXxUIgIiIALTRmAU9/2Dg4Pwj//4j+G//bf/1hfOvh12N2/eDH/3d3/XV6dqJAIiIAIicHIERvHhdHKjm5yeeBz0h+HkHDNZejIEeG2gN10fJ8NcvYiACIiACIiACEwfgfw7FUan71WtxzhnJD6tfFQiAiIgAtNGgPf9k7zn7+/vh3/6p3/qG2XfDru+exyThqM4WGMydJkhAmNJgNckjDvJm+ggYND2cbV7XO0aBPtJ0qHjcPTRGvdr6egRSKIfAro2+qGmNiIgAiLQOwF9zvbOTC1EYJII6DvV0UfrOIx4D0Uvx9FztJWSEIHJJsBrRdfJZB/HabF+Es9DPcNuWs4+jUMEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAERGAiCcxshN0kelcn8gyT0SLQJYFJvibHyXb+kgnYx8muLk8Dic04AZ2zM34CaPgiIAIiIAJDJaDP2aHilXIRGCkB/R04fPy6hw6fsXqYDgK6VqbjOGoUoyOgCLvRsVfPIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIhBmNsJuXI49fwWlXx+MyxGRHSIw2QR0L5ns4yfrRUAEBkNA368Gw1FaREAERGBQ99NB6dEREYGTIjBp56z+DjypM0P9iIAIiIAIiMBwCSjCbrh8pV0EREAEREAEREAEREAEREAEREAEZpYAHR8zC0ADFwEREAEREAEREAEREIEuCSjCrktQEhMB/KGpX63pPBABERABERCBkyOQT/LqM/jkuDf1xGOh49BEZ7hlYj9cvoPSzuMEfeNwnYybPYPiLD0iMKkEeE12uj90IzOp45fd00mA52w+uk7nOOXYrhtZttFeBERABGaFgBx2Iz7S+nAa8QHosXt8qdAx6xGaxEVABERABETghAnos3qwwDmpMlit0iYCIjAJBAZxPx2EjklgJRuni8Aoz1vNO0zXuaTRiIAIiIAIiEAvBLQkZi+0JCsCIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACAyagCLsBA5W66SUwyl/YTS9VjUwERKCJAKNZdN9poqOySSEwiPNY18B4HO3jHodBnAs5iUHry3WPY/q4/MdxTMexiccfOsaJzTjZMm5sjnO81VYEpoVAN/eIbmSmhYfGMRoCg/4M7fec7bfdaKipVxEQARE4WQKKsDtZ3upNBERABERABERABERABERABESgTwKa5OsTnJqJgAiIgAiIgAiIgAiIgAiMPQFF2I39IZKBk0Jg0L9UmpRxy04REIHBE9Bk5OCZSuPJE9B5fPLMx7XHXs6Fbr5P9aJvXJnIruMR0DlwPH5qLQIiMHkE+Pmo+9/kHbtxsljnzzgdDdky6QR0X570Izi+9ivCbnyPjSwTAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQARGYAQJjEWFHjzR469ceozvreBx0DPo7BuAGhuLXH79pbaXralqPrMY1LQQGfY0OWt+0cB6ncegYje5o9MJe36dGd5ymr+e5DkM67FDXror6+mnbTmfncl47kNK10ZnVpNQO85hSt86VSTkbZKcIiIAIiIAIDJYAvwtAazffByjfjexgLZW2JgJj4bBrMkxlIjCJBHRjm8SjJptFQAREQAREQATGhYC+S43LkZgVO+rOt3oeHJrKZoWPxikCIjAuBPT5OC5HQnaIgAiIgAiIwHAJjMWSmPziwf1whyztIiACIiACIiACIiACs0xA3zln+ehr7LNFgM626Ri17l3TcRwxCh3L6TmWGokIiIAIiIAIjBsBfc8YtyPSmz1jE2GnE6m3AzcM6Wk/BtMU3jtNYxnGuTxOOqf9uhon1rJFBPohMOhrdND6+hlTv21m5bOlfoxmZdz9nheDbFdnP0jds65rFs5jjhHHOp5LdWdcfYlK1tfLebaw3jWysIt9O31dNO1TRNdOKzieD5PMZli2D0tv61FQiQiMB4FpuB+MB8nhWMHjA+26Pw2HsbSOhsC4n8+92FeX5XVbLx8N6dnrdSwi7GYPu0YsAiIgAiIgAiIgAuNHQF/Ix++YyCIREIFIoPP9qe5E68YZV2/TRDrX01SvslES6HxOjNIy9S0CIiACIlAnoHt2nYjyIjC+BHS9jvbYjE2E3WgxqPduCdDDDvlJu3gnzd5Ox2TSxjLJ502n4zALdTp2s3CUNcZpI9DvdTtpny2DOm7jOm4ex2HZR/3gOKw+BnWMpOdoAtN5DOvOssPsXK3XRUaH7oOLdXMVkTxzXEcddVEP87CBZUcfM0kcj8B0nvPHY6LWIjCrBHQ/GO8jPw3HR9+bx/sck3XDIXCca5fXzHF0DGdUk6FVEXaTcZxkpQiIgAiIgAiIgAiIgAiIgAjMMIHcMTbDGDR0ERABERABERABERABERCBqSWgCLupPbTDGVg3nnF50YfDfpK1dnPeTPL4ptl2HbtpProa27QSGNR1y89zcBqUzmllPoxxDZv5sPUPg8k46zyJ6yXvgyym6zh245DrLFONqiOl+r6zjrp0mWe7ehRdPV+2UKo9gfx8HvfzmLaOu53taatGBERABIZDgPdHaJ/me+Q0j204Z8ZgtPL8Ev/B8DxJLTpmx6OtCLvj8VNrERABERABERABERABERABERABERABERABERABERABERABERABETgWAUXYHQufGjcRkBe9iYrKREAEREAERGCyCOjzfLKOl6wdLYGTuF6a+piuXx4zUo2RbPkxRV1TOWU61VHm6H18Bl6U6y5a72idkJjM40SmPC75WFmXl/lIU0G9PtcR66p8KZ/L1XUPLs/jAY1N11W9p25k6m2UFwEREIFZIKD74ywc5dGNUefX6Nir59ESUITdaPmrdxEQAREQAREQAREQAREQAREQgbYETsaJ07b7AVbkjqIBqh2CKjrQhqBaKkVABERABERABERABERAB否SUIRdWzSqEIHxJ8A/+vWrk/E/VrJQBERABERABERABAZBIEaBRSdWNUppENrHQUc7B127ctjci4OJevI25JmX1VmwXb08b9MsUx6nXLauZxR52pPbzTLaU8+zHHu2owz3uQzS7crrcieT199OJ8NZvYiACIiACIiACIiACPROQA673pmphQiIgAiIgAiIgAiIgAiIgAiMiACdJOh+vBwhIwIyAd1OynHK7eR5lpcRNeuYb7fP5Zr0tGunchEQAREQAREQAREQARGYTQJy2E3wcVd01QQfvCGaPuvnxayPf4inllSLgAiIQE8Eerkf9yLbkxESFoGREqCDoslpwbImmW6NRttu21MOutl3Uz+Uq8uw/Kj2TTpnqYycyI/5JgaUYR1l8/J6Gf否QzmUHabn5M3ZM9lQ1ySXl7MtyrQNigA/y6BPUXyDoio9IiACIiACIiACInDyBPi9bhTf6eSwO/njPfAecQKN4uQZ+ECksGcC9ePOm0nPitRABERABERABEZEQJ9dIwKvbieWQP37X3UgdNR065ChfFVL1eHTro59tNNRbzcu+SZ7m8rq9tZlOP66HPJ12U4ynfTU20GWurnPZfL6vPw46U721W2gLMuxZxn3rDuOTWorAiIgAiIgAiIgAiIgAtNJ4NR0Dmu2RtX5j/bZYjHro9W5MOtngMYvAiIgApNHQJ9dk3fMZLEIiMC4EujXGdZvu3HlILtEQAREQAREQAREQAREYDIJKMJuMo+bWz3uE1z8xfy42znBp0Cj6bPOe9bH33hSqFAEREAERkCgl/txL7IjGIq6HFsCdDIwcgeGNpW1G0Bdtp7P9dV15H3W5VjHfd62XlbP57LHSdfH0tQPZfJ+KFevYzlkWcd93n6a02TAcXPP8nLsh4exbm6uta6UYnvuKcs8JOtlyDeV2VGJa2Em9ZRBtklfEqvsIJe3Y2XenmWU457llGU58vUy1rEN9k1lef1kpKvHYDJslpUiIAIiIAIiME0ENBc9TUdztGMZ5fc6RdiN9tirdxEQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQgRknoAi7dALQA4/sKD2o03Q+9sJR/Ad75MVzsDylbToI6LoYn+PIY9HL58T4WC9LRGByCPBag8XTeb01ReU0lbU7ZnXZeh7tmsqa9HUr19T2OGX1fplnVBP2LKv3w3LK1uuRp0xe11SG+lzPUTLt6vN+mO5GL2XzPdu164v1aNNOJtfH9NGynSPrqnrKaDyWd9rnNlOuqYx12Of2HiV7VD31HiXXVF8va7IrL0NfbFMvpx3ai4AIiEBJgN97pvM7TzlOpURAB否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否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否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否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否FM966grb886llGGe5RThmX1PNtqLwLTRaDlPjVdw9否REAERGAoBPhdbxjK5bAbBlXpFAEREAEREAERmFACmJzhRM1kDcEdbWYyp5folM否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否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否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否9x451qaOOkQUXL17QSLwjmDqzqalRnXtcG6+xsUnHdFMT1sBD9F0NrpGSjclohLSmMb/AVVJkZQlKW7+UldXlXB8DgeLZ5VhOxz11FYfW3pq9+nIDX3DoxHqfBzsOapToAUTdHTl2TLoxFfErTD/8DoZ4RtLYOLSxz+tiuW15juUkeL4j4AhsVQSoC+xFAJ1WfGxCxvEZGBiU15hytxszJHB9X04x/uWXX0kHXl7h2rGNiLbT6adNMUEflasT02TcuybZ2DHgeG4sni7NEXAEHAFHwBHYfAR2ncPOfmzaj8/Nh9hrcAQcAUdgZyLgenSp82qGh0o/ko22FI/JLuUNcRQhz3JSY7xJLJbOjR85h9lNyFtuOAENrFoHM4tvWMfKOQZoDMbL1ipEjTnw03GftpWOD0a7PcOaSY8eP5Jnz57J4+4nMtA/oNFLQ4NDiKgbU1kNmE7wCKLpruBNbTrorl2/rs6QzsOdMDJ3CqcepMFo8t2EDMFIxHXnXuDT8+KZDA0PyQCmd5sYn1THHaPlJjANJqd7mxjHtJiIqqMhetEcjAZUhb0auUHPEQt9JGuOfTHB3CpbKsgEGM3SVYqulLxBYlZa3Zbms+e90rENRyzHrBELgPGccyOPbUazk25lyTENOZxOkFOtDgz0Y0rMFvnTn9rhBGnWqcHa29ulC44QRoeehMP59Omz6nzmtJlpHVaXVat+OxBZB6+jrJmxWRy+bAf5rX3kCTIjUxy5IT/2yypa0T6rVVuRNyIpnLIk5I06NDw2St5a5CzdBgPAMF9pDSyHM1ulmA1PSstY7CBWmWnYSGeZbGyCxrTpYI4LK54fiL708Nn1z+VgewfWAf1CHjx4IP/xH/8uN3/8Ud4g4ljrR1mNiMFe16VSZa9ZJV9pmy3DarUxanTfOwKOwEdGIOoRa0WmOyKB+qLAojnkox6wzcoxMncUjrru7vCSC9e4u3X7tq5t9+WXX2JGhFPQN62CecujnHyNW767YrqClbLerO5KisUqz/ZRu2WFmJEnykUwL9OImRQ7MP5q+tn4tu/e+p5jV否Xyye1Gk9pid2csmug0jPdbsBlt/Z7N5zbD93H3X4tEW9i4NfUhx55q6/vQ56jXeewW/3p8BKOgCPgCDgCjsBaEeAP3w/zgzf9ib1ca9driMh6hEppaNkDB9r74LWTxfkFWcDxzMyMTCOSbRoRSn19r+Xhg4dy585duXnrFt7CfiqvX7/W9cT4cMp1lo4eOyqt+1o1ku7M2TNyDc66ixcvyqnTp3QaN64r否eop56eZ3CSDMggnHUvXsBRB1nPQOvu7pYBrKcyAqMR6w/tCQ+/rMN+CNjeDETLGZOzvi4H6nryP9wwWU8rd0RZO//LdYZOCp57usv4YL4HU3XtAYHOXo4ZnjJ+ZjHOZ2amNTo08JEqOqZbEMl09OhRROAdk/MXLsili5fgfL6AqNEuNV7SwVffUKe8dbruXa2u1bgXFszwp6JKVUgyIIt9sbG8sT8kQn9iS3y3YQiE0bVh4ijIxsYyp4zjZmFxQWrqauRYFxzKWJ/xs88/k88+/VTaWvfJvn0tcNrdxAsOiIbGyw+cvphG+uJ429C2uzBHwBHYFggU9YDd98xpxxekHj9+jBcAHmL/RIbfDMNx9xnugeexjmaH1NbV4lOvEb/V7lXr046mCCvBuVReJX6nOQKOgCPgCDgCjoAj8HEQ2HUOu2oPhh8Hfq/VEXAEHAFHYGchsIylVE38xR4vVaY0r8TUkFg01AGXZMaZ9bQ2kt9HDx0dDqUS87YEIwwdW0YLnHtQKBRnZBLdF4v6Ry7EXmBNkr1SQ4a4XhKNNmNvR+TBo0fy5MkTrB2HdekQUff8+XPpxVpyLzA15egYoulQEacKpPPi9OkzmOrymk4tyKkvOw51IFqpWdvyCuvOPYLxh0agITjkBvoH1Vk3Ojaqzrk3Q29kBOuMvX37Vt7BOchovhVtBkTW3wqlVsJToVhFUqV6KtEqFnbih0ZAowji+ec1sXcPHXX0ifAaCU48PeYVoRGopSeT43AaUUqcUnMIU7NyurD79+9jfbtDOk0YI0UPI2r0CD4nTpxABN4pnUqzZR+i73CdxUtWu23HGuHEunkVk6gNivVaWtsX0dL2J7LYEcticU0G52PQDOyRbVrYEronJc9PdElkTcSbuJLyOy9RxChNG1IpzRAo0irxBpqdnRLgKSYVEYuH3zghoWPGxHIf+XXMUqdHpzD1N53TfGnixInj8rd/93e6JuPlS5dVf//88894uaIP69uNaePJrzJKTrZmlX2lo6ks0wmOgCOw5RAwlaE否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否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否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否iFgCKwHAsp71orvrMczWBkxAitpT+T96quv6Nq1azHBJfjKZoXdEp7JkhoChoAhYAhsUARUYOLxnJLqDHWVYkWtlFV2MzPTNDY2xh/egqallW7fuU0//3yWrvNZEs0tzTzLdThSYHHuHf5mZ7GdjTvDRo18USdL9bDA+LdB4bXHMgQMAUOgPBAowJdxFlAlby82OTlFT3m74xFezdDd0y3XDG91OMOrrvft3UdVVdVyjhDS4g9CAWJEthMLeT375VbLWy10XJHOOOtphsWuVjEbmU7UJN4j+GWBCLmuxm9M/+E/XgGDJOmqKtq3bz+d4DOl8F5UptI0wYN1mMyT4Yk8ko4zox8Q9js2Mp72bIaAIbB0BKCLCAvyX8JnmHcI74F04XBMEMFq7xY+N/sRTyKcmBgXvtLQ0EgvvPArXnlXS1XVVby9v5tYEE1W5Opgq+RwosjSawgihXNlsczCySzGEDAEDAFDwBAwBNYZARuwW2fArbjFI6AKcmQ0X3xWS2kIGAIlj0Ae7TEZBG0XBjN+FnzjirPq4MSYxoNsVMnbzxAU3EpeNYHtZ8blHCMc9H7n7h3C+TTtbe28yqKTent6aWyUt6LJuIE5IeSoSjm8Vo9X6nkX1sVrs/H5RZrIroaAIWAIGAJFQUCtjHr1lUDfkRdbs3MG01GevAED6SQPxrS2ttAvly7RF7//A33wwYe0f+v+qOq8PlsGcnC0net3YqDGRcuV/XNeCITiISLAsiSsSv40cWrxaaIwYyKJ3S4eAcDpBl3RWMjnjOWwmKMNXRB6DM5h0O7Agefpo9/8llK8NSZWYVbxFWdN4cwpZMJqPPQ/Zv2EHhjf4YSW89q3IWAIbGIERD4I8wFf8JwhYhAI8ZGM0TRPBgCfuXPnLo3yZJLOJx30+eef0+8++zfatXt3NLCmK3uh+UAeCYWAJuB2ofBphGdOQXmIhdMUUlcXBLbonF41kQ9e1GUlefMWAIILVUQLBYH505bqhAuzceVtfAs0BDYMAqXKezYMwEV4EKcbrn/BNmC3/phbiYaAIWAIlDkC8ytICz9cqGwhtaOng3JyJ9Y1p+YiheTAl2iYnIKvUHjwyczNyOzV6alpevzoEbW2t9L3331Pl9gwe+XqdVltN8sz6JEWhro0G+AyfpY9VtahfDXgoYjkTNZIsULxklprzDfmDAFDwBAwBNYfAciIPE7lgjN0cgJONjk+SR2PO2R7zLa2h7y6YYq3Iauid+kEbW1s4G0R63nyB4h54yrTFiOpyBkthAlpmZGlM8ijyQpdRYi5SCXjBFuhDBa+EALJN0C6CMiEpuKLNJNvq0iO8+QeDK1iRT1au4oH7fbs2UsnTrzLxvQpmfgzNTUjg7tTvEpTCOF9SFYmWXgy3u4NAUNg0yCg/CX5wMImIoYfxw4ODtD否SHq6uqm4aFh2ra1kY4df4cad2yjmpoakT9CkxmPSCVWTHTSYKSjeKaUZEWSzjNDKTqHeTldJq5NMX15KpfDbZNPuLT6FsvIurRaWmpDwBDYaAgY79k4LVrMtrQBu43zHtmTGAKGgCFQhghAFXWugjVL+EPFFwJSwjCwBqOZbhfDK+1S7Ieb4nPqWnmbmfv379PFixeovb2drl+/QR0dHdTf3y9p8IVzaWSgLyoxipIQURuRhIPVPKd1i1OazxAwBAwBQ6DUEPDjMlItGCnFyOmGZkSmzM649dO9fIbQST7HdJi3Rm5uaaKjbx+lN958k3bu2CmGUqxsmMEWiSxfIGOSdHOfGxJDpEduVL4QEyr5UFnzMCfXtX/h+hraamleVbd//wH67W8/5gG7Kl5dN8arYKbp0aPHcqYdUmt/A1vbWROueXNZAYZAeSOgzEWfImAa0HGwNTNRhnr7+ug0n7Gd4Z0/PvtdG3366af08uHD0aCdnLXKPEe2evY6j2opMUnlblpY4WuyWoVTLjIGlVh1oossu4w5sei26KiYMwQMAUPAEDAE5kHABuzmAceiiotAMUeyi/vkVvpaI6ADQvaOLRXppFamyoaG6z3oJsO8VsfBGoMBOITGudgnAd6Y5gfYXDqEcaQoro7CLG8rM8ZnQnR1PaVTp07RGVZ6f+bPs2fPZMZqhhXiqsq0M9bOuRV2M/MoSNGsVa4hStD3BE+jLq6rhtjVEDAEDAFDoFgI6ICaXkMWH/JwyPtUKiWrsXGWKeTG445H1Pm0k8Pm6K0336I9e/cw46+gDA/uVaRZCvil16A9hxUOcsWTQkIgTC5Zg3ouJM93PuGRLyxPVgtaAIEEjq51fJ4gDuF4JzAoi/cBE4A4hD+zVM3nR+3du5/effddGhjol3PtYETHxJ8p3k416i/GL4EvwC6GgCFgCORBQBkRWIwwnyAN3+MMbewQ8vDhQ+Y5A9T7rE9WfldWpuj555+nui11wncwaEdy1qrjW46Km8wIv1t5p4UhQELxlePAvuACtpi4cfFL+s4itqSci0ysz6YF6XWR2Us0WSRTSrR+Vq1sBLQ/ae2WjYvdGQKGw否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否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否HkdURGpunrhpVTld9ftRZDdHrVf+sstFF4785Btv15jgAZxdie3JfuYoKt80p+nvbd+yg3378CWp否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否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否UouSQEMgQYLBbbcDr9tXc6gNeHnOW1IYUZZEvoPjswmw0xdBEEMNE5RE7BvVsExEmiVJN9eWXX4Z3fvGL8L3vfS/8+tf/Gj7//IswMzNj/i2gfqyd8Hg1QUrthSOT7gmBhEBCICFwFBBgEQ+pg1YtYmLi4t5jW9xjt36hlzX39LNHBDSWyMctiorFape0G9HC65kzI1Kl2F2WCGMRxh9cPn/Ri/mxX4+QMVG0jmnIPnt172Xx8kI0WVQb3HZjURgy7Ti401JPenZMRI0AYOmXeyO/Bn+RaD2b6kt5Qu1ct1TO4YlynpyctLOlkHqJ5YmTxWBRlp48hXRPCBxNBGJbobZNfRDf1ZKk6mBonzhxUioezxrTbkXt48Liokkne9uH31pmQ1spz6Rg80HNCWlfT548GXp7e00KDWlnPl9Xl+npWBpbJVaLkD26kbTnpRgVdiuSnlsWY25YWF25ciVcvnwltEkNb4fUfrIphE0Bi8JtWkw7zrajXcGeP/LHxlUsSWN1edXOVB2UpDeYINWI6k1cmS+bJ4UtdhQlusr7syKdMWC5zdZvlmtLe2u/2/FRbFGJO5mEQEKg2REotS/NTmmi77AhQP+3Fwm7OFM+bLk+5PRSaDQKySQEEgLNiQBfpw+3K79Ut99IuZY/Msc8vL5zJoJz0t8/MXE/vP7aa+HHP/5x+Onbb8fd7Vkk7GyPk5l4tgLWxSaC9gL3ZBICCYGEQEIgIXAUEKBfo+/z8TC78ennYlenJT51pKnfq09JO45Il7S0HNOC8Uq4cfNGePTuI1Nvhv03v/lNk5bo6Gi3RL1cqlKwg+FILMeqsdTdkrpDek+K8fyadIpGrRQLatyHR06H7/zn74QunWc3MTmhsv7KVK4Djn1j9m2VlrbzMeuTAlzK5xOJQGwH2RhyXIyyVvsWHkrqiw0MMOxOnx6R1Ng54xXRN01MTdg5kKgVrtXk0eYU2534XWqjpizXJVm2shb7Ns5tg0GH2k0knGHmucSft7feVjdXAdFuqG0RvXOzc+GLTz8Pv3//w3D54tXw9NPPGOPt29/+tqlX7uruDLM6o/2zzz4z/0jWAR7aYx4fB8dH4aM/fBTGJ8bDI7339HabWk3bbGAgMwY4autkqYVtrvqcqEkIJAQSAocHgT2fYccAwwcZ+53tw96BO1aNwmu/yyPFvzUCPvA+jGXee否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否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否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否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否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否CKUDTDMO2+Kidau/uf8uXPhnFQcw6iirWbeN9A/YHkeG9OGEm2EwfDlEQ935rTMF2j/iNfmEJIahzk1JVWQ+OKM07OjZ8WgOm39NGe3IQ2LWs5WMbyQVrQ5r/oUTPzurTVW6Pidm0Pdf0pxU5bklwu1lUPaxNHWofP2YmNTwCzShb3VD9FEvtnwYXNk1Ysvv/oq3Nbm21Vp0GmRFDr9JXgt6HzXKfU7YMYZqucl2dgpRiH4wbAzYyTph3TZlJA9xvSil4hSfDYPeUAL7A66V76Xh4zu+Cn587peisTdS37KwxXtS6Ga6cnHwRvz1kxUbo+Wo5SX7eU4+UoIJAT2GwHalSPJsCsCdxQ6gGJ+0nNCAASau17XGiDWcttd2Wbj9U0De4pxKMwbA3m8Z9OiwhiZncCcs/H7338Y3pYazJ++/Xb43e9+p0H8lLw/soVHvDP5w9iYnfh8QM2ov2C2oq3gteqjlzN3fzaVKXpnAtOls4H6+wdtURgVKUzC7o3f0855LcJp8uphqkaeLJsGgbz+eGXNKLOapUm7GyaOdlg6Ep6qatRDnyC4n3RPCCQEDgYBFtgw/k2ihomd2khBo17ZGXa4u5+DoXT/Us3zpbaM/LJbn34QRtHImRHbyT5x/76pYzTptgJeTtVe+02PZ7t377XZjX/q1HA4p538Q0OS6urSTnstiI5KrWR3V08YvzduEgiokoT2mNcYuqLp3m7S9fHnXQSk2BgEi2MmMTE5MWFYcybcqeFTdg4SUo+o9jymBcc5nWm0KMlBFme1BsywKI6M9Oz9UsWwpj40QyFpZIVNGjwzvsGAMVKAHWLycBYfl6m6a9cirUz0H8NkUZh96SeWSHU3+YrOJe9bPBHPpnFtEbbxzmSuWgY3s98lhYUkCo8FnOL4BClVzuLq6uwKn0g15ueffWbMBeoXo/BsJJ7XhViHd0lTCvZEIUCb4W0I42H6HCSpmB/B5EIyq1Vt3br635WV1azdKNbWZoMr9ieMGVDxyXmjD7Spok1MumGpCL548by13Z3qm0a0AeakGFhIVN+4eVPzvpk4J8iy5O2p4WMSr49NzWVrS5vhxHwRaXcYdL06KxR1k888+7TNHT/55C8mgefzTZtr6EstbwMbhyNzH8oSNZUXxEhjA0qHMDAjMvJxhyxiWx21kFAfXFKRzR+d2kSFWsy5+agSelV5f6QNK23q+9mQuyypRt+Yi38Yv/1iDtp5frRUFI8lEJPmN6KwGRab2ZfCewxFm9JzFrtuXs9LbjxVi7+aXXmoZnyrnr9mpHRrmo5SXrbObfKREEgI7CcC9G9HmmGXGsz9rD4p7oSAI8DgsDhALD67n3jnm4yD6XL73b4RVy1jznH+E73JIqafqe3SQN13ILK77qsvvgi/fPfd8Oabb4af//wX4c+ffGISa86gI5J8YmBxRQJYeIhP5Uhg6fa16KzlxoSJyRsmTkaPhx5Nrk5LXQe6+TmvjsVDzjuYlKqPubk5m3DYMojNLhRwr0RY6ulnvxDIa49NqoupxAUt3Fs1YWfCylkO1Id11V2kQVleTSYhkBBoAgQK7az1dWq7baFJO/57taGCb5dFMi76EWdONAHleyKBbPuVR0RHqzzaWW+yRM0Ui1+oaOyQxA240OeymGbGOvMIoP3G4Hl09R04iF7F75cnAk29WqQ7eSru5B8Wg6u7m3LrtjYXJhILqLPqY1HrR39MHH5mjsfTqHscTxlaMcmsKzAo1S8wbkHiBDpbtPgL7qek5pMzergPDZ1QOKlfkzuSdiva5LOqZzLVAuNM8eXjnX3LFPRHwj0/nib0o+5temZaZdAuCYkzxriDFNxi+RXyX0Zj1qtu5ozfWm5lcaWXqgjUwI+yoRytjFSXXHvF2N274b4Y9jMzMybhUhxbU/7JJAT2igCbY9ak3rhbGxo57ww1wLTdSBXTKjjzpZmGzt72kXdv12gVkfyO35Gk1aW+uFNM7x6pmD4lFcetYkq2ijnZLSYUaqeJY1KbSzmjlDi8X1JTycdoeSd+4luXJXNEJODRyALTDknm689e1ybQAZtfoCLzgSS06acxHAVBo4k0Nsa+1wZ9sjZW0o4S1FvCWLx65VrollpQM5Djl1sIgGJ7YvN8tUMwcpk3n1Dfd1+bW2/dlCShzq9rb22Xf6nSlBpN8k0bBU6nJXV49doVG6+gNpPxDFLocbNR1scIE082PvCL3XbBqeXP3TaLz91jmcT03S6+Nfuvl5Pfm53eWvR5Hvxey29ySwgkBBIC20GAvuhIMuxoKFNjuZ0qkPwkBOqBQOXg0N8rB5B8l5V2Sh/vHqQmOaWw8Ruv6VlRRv+FUJYOExmcGHi7KkwG6TekduRXv3w3vPbqT8Ivf/mr8Olnn4Z5LcyxcMVkgUmKTVQIr7Usa2NKkefZyKKvTVwtVyLgygxpc3m7xqLvEGpBxLBjYoUbajDv3L1tZ9KQF+wwNq/yuPyexZtuzYOAF01enejDqARWgJooa7GVHbWUfZvOXKDzZqLtO0GbJyeJkoTAE4yAf8gZBDBCaI/Z6c/CIQwrFoNYPMQclXEq2a7IOpmzPMafeC4Z7VY/Z8NI0o6Fs0ktFML4AgeXtisEKn8si6/caUdvGVmkV4k/6q1h2tHWnhRD68LFC0Zv7HuPawG4yxh3M5JkGB8ft3KELI8nb793RNDuPXu62VCnakT4WVMdhPFFIZ2Rqs/hU1Etpkk9qU6yGQQGCtKQnDEGRIwh4qafqtHumyV1BGOqxlUe0DV+f8wWqq9cumLSjpQdvlhgBX+M3fXsZSAXs/cjl3jzyxzSTx0RiKhnRZchHaOP5ePjUdUoaxMfmdTQg4Ka+ToSk6J6QhHw9pB+Fw0jMHfYlEC/ix2b3GjTUBfMWWbNYuI3Qn9EO2Yj/7Cuj4k5Ku/tUtu4po0tY9L+AuNpROe4XblyxdweaR6AVPh59VU0cJ99/pfAd0U7evx4nLPyHNs+tZuKE2Yd8SJ5yPgEVc8zmkOePHlCEnyXwlnFf0rSfGz2+ErqI1GrjH/6RuJyJjt2jTL0R6iwZMPP9eeu28XZrBgQE1kylCslDI68x3kz/QU0r2ijFEcKoO6aM2oZf9zVBoIZbXb1TbHkkQ1V9DuLy4umRvO5556zudfa2iNhGqXAybth6h0MydU03jPV8hRLyQpyg7dKrN0vHrcT94YIm8qikXVpvzN+2PLCN4NpJrqdpmajy4Bqop9G4dSodJoI2qYiBfyPJMOuqVBOxCQEjjQClYNIMut2fi/Z+fj+cXGQW/RWE6uix+JztUBxgmIp49UvLDRhYXx7vDVKP7Cz/OOPPgpvvPFGeOvNt8J7770n/fa3w5IWVWkkj2tRiClA3mEVko5DdiKVkV+cag2dyb9fFkY/le9mL3/sAmWSwkSDySaTTg7cvnTpks7UGbSUUGPFZIoJ2tzcbM7AYeCV00uEWxFmiaafg0KA4nFD2XE+BQuWVqf0zk7hIZ2FRR1AIsUlU8rK2CNI94RAQuDgEMg+Zr5j2m4umFPsgOcsGPoT2mNbVJSE7FH4hslylu0S7sq/GzCAUckGGaS7zkqdFX3b5IMpO2ft8TETAaiNRSE+j3dX94ysqosT6ryhc1WLoCzqvfD8C9qJf9IWMihD1Ep2SVpjYX4uTEjVJP0ui79cB2E8D9zjtZEK6teq+gyYjGtaiOxRX9KnhU7y0qn+hAVtNgCxUEndZDzEguVypj4OCQ5KNy6DxnIulezG9HZr43nw8MZQ1QsSJqiya9W5S709vVZ/6A9RV0qVIH+xatgLXaYZi09PXm38Hl3T734gUKoX1Eewx6ZkC4OAeoemAM7l+uLLL43pTd3jPDuvY/tBW4rz6CMQ61vMp/WrVD01CKjSHZRK3R61H8zr5sScov/lbNUohXYw7XexRGL7F+nF3tsx+kloXFxCRfC87B+FbqmFhKnGfKBLz92S3kd9Y4fOW21VnmC0sYlzcXElSoRZ245kXGwvyS3pES840S8/1sXGQDalsBkUxl271A/fnxjXhglUWC9ZO0xfzryXP/4x3LJHe9/LT2w3yiO0tESn9QNiuL300kvh5ZdflrrqvpiUEVBOBfh5GwSGGM50J8+Mw2D8wfBjLg1W4/fjue9tjNHUFtEHklfaJVT5sllycDCqZSauyPDNkPDqUxOEmo6KsZZ7NbdqdlCWTEJgdwgU28/dxVC/UE6L3+sX89GMab9xKsZffD6aaDZfruinm5JhZwOtDK9mrhiHhc7mq3qJoqOBgA8YfbTquXJ73nmu9Z4Nqj2o3T2+YjgNkC0q/WgU7i4+uOcuH2Wx+AttSLzwEncHMjmBIcIA/s9//nN49dVXwyuvvBJ+L8bdmHbOsxPP48uZdZIO9HQ3SWozayclo4NYsph0szzE+UTBXzDJPg4Xx5DjIR0Mfu3aU+H8+QsWDzv879/num8TDiagboptk9ul++FAgAklB5xb36cqzWQTdXqoPkWiAMbsrC52yno1Ohw5S1QmBJ4MBOwsOxr2QpfEd0x73tPXY4s/LAihuoq2mvadv8NsClkty4b1vbJhVz99FOrJkLIDD84VAqR1MS7B4/F6IRbg2A9I8jjjAwt7bigLpJa5YCp+/esva/HyjJGB7zYt/HKmGoyvJUmszWoXPhKCrtYzj1p+vUz3u1xt/KCfuPjqOdl4Z1GWhVzqJAwSxhMDYp7C/IKJ2iW1al1arGUxe35h3iQMiKVdjDIgcpxcMrEA28bEdmhjfZ3AY0zmTBvS82eLTuMv6ggLp6eGhyUNEhmpnpTFwUtGqL8bPrkny76/5eWaW6SHHSEQx+QaowjkiHe8uyaNUt3XuFtMeeoo9QxVfl+KWffZZ5+aZE/OrKsoc74nv3ZEWPL8ZCCwSeWIbSISWSsmMdyq88lox9FIggQxZ3wbM0bMLYy3barEqnBEejDG6MiSdzKwixKBscU1tY3qR+N5btp4IUlw2vO1x2u2ieHc2XPWR928dVObSiYVFsZknEeiIYZYbHyiez63NctgfRp9GdJ1ly5fso0dzIUXpRKTc/TAjj4yl1622PT9V3ylXiy7QZJ2xNsSixZ6jdZIL/n++je+Eb75zW/qbLl+uUQTi04+C4nGeTDxuad4BweYcmd1BuDFSxfDhJh1n36qtkj9ORtzWsW0QwCTYEjfwaxkk8uVy1es/yFeyoQxTN7PyG+kId5LPY0nTtrFZ97dVLPHzi/3F+tAfCuGcX+b3WOIiAfPBYyjU/p9ghEofXPNBYJ/W81F1dbUlL4z2oLid7p12J36aGTZNTKtneLQrP7rVReIpykZdkXg97uyF9Pay/NhoXMveUxhEwLlCHhH5Hd3rXx3++p368+K49DcW0U8hVd/LPWFVSNQTNFn9BcH7gzUH2nCMzFxP3zwuw/CK6++El5//fXw/gfvh6mpB7aoCAmE8fiz+YxR5mnbyw5/YodXCuTxx4hFX0Yv72qf7WIx7fz5c+HKlcthoL8/LK0s2TkFd6UeZVoTjGUt+jL5TObwI+D9CLs3qQucVYEEBLtnYdAtaCGVSfOqJA4wVp8KdebwI5BykBA4/AjEvoPd6zEvtM8wpdjpj+RWH+24dsyzsxsmFgveoUxd9OHHgBzQV+Ztmp6ZdDx6zMaSx9amnb9wScy7wTCphUUWyzCt2uVPQJvobNatm89d/mQdeLGvJSajNYvykdrfYTGFnnv+ejh3btQ2z9gmCi3QsaDXr/JjE8W8FjLH1Q8jlVZpKuOvdK/Xe6xrFTt+qkQOniuSLmHhFSkTzrAbkVowFj3J04DuLNSymEudvC8JQhhkq9rUBB4sylqeKJsq8e/JSnF6PfF7MT5oR5vA1NSUqe9E08D58+dNUoKSK4ax4hUoZke8xYh4LljwWPe8VKZ3lN8zLHNI84cs0wKXOhMxRg0fC9xxkXvJGN20gVLFqu8oVxNcLCBFUxnlUYYz5W2HCHjl8HsWnG+fC+lcNl/wjHRuT0+31GR2WD9Mn7yks8vYxJBL2lFRvdPeISn18i5SaxpU4U9OTlrbzLwAhhObLsQLVx45f3VYEmGdmifo7E8xm7jDhAMLovboYbZ7H0uazC84B535xajmmy997SWpHx7VnHPQwt7XBtGxsXuG58Y202ONpJe/1czOBkdrt902S4g2xA19FdJ13/yrv9LcCE0zBSNvHh6GGv0Yc30uWnqkxdFcQ9njr0PjMc7/Qx2oSdkpj6tSpUpbxCYCkp3SufBskOzVWbaXpJJ5QJKaJhGszVeeVk4BZOqqjQ+eKk01u0o/vBf9FZ+r+a1ll/WPtbwkt4RAQmDPCGxoI/YcY4rgsCKwl7pAX92UDDsy5VejC8YGMFmiW4HrNG7lr9F5SOklBOqFwPa+BwaOxcFj8Xk3lJTH57t4iYk1zfLY49A4D6FX35Wd7yJkqxxhbVYSB9NzUmn161//Ovz3//7/htdeey18KT39c3PaPVjB94rfuBbDlADzOKLwBQeLdLOfnKCNHkrxRDd7VwKoo7JzWwib5VLKUEyq7j/8T3+lhcNzYerBZPjjH/9oZxnMwrhZRuVJBdFZkql9yoBolpvXCSvfCqIyNyam/s3BWB7W4j6SHpwxxMTxgcqfRdTcxOqfv6aHhEBC4OARKI0J4wfKAtGapMlQOTU8fFpSZr3GPEHCbl0MEfoVOx/1CHzPsY9k133sxTxL9JtI2KBycnFpwSQDXnzxayY5fOvmTduEQqBWndfDipdJEDeyKAvtMgtyMOwuX72s896kOlILoO2SrINpV5R4ZiHvzp074d74PUkQauRBZ34QhmRrJa28mQSePC1okfqY8nfhwsVwTguznK2IG8xkJFDapAaN53mNkaifbAoiX62UnwpX2czHWPuRVb4dUxub5yfOB5HQZOEZ6SzUKvIdoRKOBVzox5SNeQrlWcTG6yf3HDb8ZlcxmEV6SH686pGv+pq8IBRteeQZZKXk3KLgjboTMY+MOuLgnYVyGChskrt9+5Y2I+ksS/3Z5oVSjOkpIbApAtQX/or1slT1Yr2NfTHnl62YhPsZSdqNSLIKKelJbQCg/6W9p12nrh5YG16RS/JhzVqWIb4Za8NFI98KjEgYdqPavNAjtZi0g60tOt8aNJSXIaSnpcoRtc0w+NbEtCI+4il+0SRL3jEwucDrnDZDPP30M9bGDmhDDW0LKqDHpdGF/puzAGsZw5yEuOpgirHAZPv6y18P3/zGN8MJnbkXy3djIszlYdhRzqi4Jm+tOv87ShvKUYsJzLE4K7BVcQ5K4nxhcV5YTYSHwoyzAtukfhMJw8faZNQuRm+/mL5ICPcP9lsfmacKgbrANV9/8HrpYJdhUcwRsVS+e6BqblliOG0wxXA4lscLVvHaEDBZJARqIuDt4mbfW83AT5hj6Tsr//6eMBhSdoVAveoC319TMuyapZRTw9QsJZHoOHgEGOhtRkWlQ+X7ZuG2aV+IrvBYFji310Px2TwxSNUD3zMLPuP3xsK/vvev1vi9+sqr4YsvvrTd2zSIOTPOAsQweUKFsfCWbUNORB56w0PEE1VWcSHDB0SoTevWQsapk8Ph3Oj58NS1a7YbHlWIn3/+udQIfRbmxVxEnRiSWLa4tSH2CtqruCerBiNQq04U3KhbTBbZNYs0DhKW1Nv7UtvCblnqiU+wG5yDlFxCICGwHQT0PdPrFPsJ7186OnXOjJ0Vo7NnpB5SMmeSeOLcNC2C0Q4U2oLtJNVsfmK/FrNBl8llbVrWz7GDnb否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否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否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否Gwxf0urMBZSLNh+ERqMH/3X78Lr/791bBXqihw42gsXSlgdssDOAwWNv0sgGNunn4NYNnwit6F3QbVwjF5E4bNgN4cDNqjjOGyYyE/Q/cC/PhHPw7/8r/+OSxcND/s3L5DcP49fCBi3a7de+ygYbTUZgDL7TscYuXg1MFafQxZDFibUcVZ3ZX2V9EvSkzMbp0dtj6yNaxb/0A4IQ7W115/Lezd8124qTuprH0WcdVSLLHUGvQqvIYAFrxMBvMQgKcCocJAf2DA5g/Ge53kodqQB8P81FsT0+6cCgQoDnzgVF+0aHFYtnyF3afCYRBEhzmzdW+LInNweFL3pfghEYdVQ2uU6Fy23IXyNzMc1DPvQ9BCgn66DrwgYC3RQeNsHTiOEZf9WUkZ7tq9yw4V43gUU2s6TpJflqcz3USJOAbYFFOAxXEtrRySs0sUwC2/etXqsGbNGqsjovmam/WIE+/UapROVI1pWcvOIE5yLiF3XmU7fvy4ER2R5CRfDomR4Nu4aVP4kdYUP/7xj02agLvqyAfa4PfAqDbpa5GoBy1bq6RykpebVBr/7Pqd4YmAdZ/FB5ZInCQAGg6oF1MZJ4IKqjGRwODpV6NCxvqK9eB1Ae6R1Dp18qQReOaq/SxXX4IYGussQsWhcqwVvmPdF22zKGsMG0PU7CPT1qDQ3S6ox/WW598kUO/GOh/Dr4cylXBeB9Z/Yn0gVUPb4o6pAwfjPYkWufqpMNBDDHgf5+12ZnVvpRCVLooIw32xXHPwwPoHjSnhlBhlziN5JmmqGze5885j9BCQvowmULw/2ds7lvKAsfWayjKjZabdjQthDWIaDLFxXcO++5bK/72k+Vvsvlbm4MOHDhlzEJJ4DJz1K5/bRkA/evSICHZjwxIxraLuH8N8yR288+bNMWbV3doHQ9iCYYLH5jKFcXgtUjd+GL+5v26rGFhapHnABw8bTyh/VwmmqrL1AoQ31hkpwi1JyXFP7OL2RWKevBI+/fTT8NVXX5n03dq1DxjMlAmcEJ8xibvqKAvqUiODyvcmFcy8fuzYUVu3tXd0GE4A65baClKIxLeZU/iM8zLjXDcKb0G6HbC7CVbhKgxUGKgwMCAY8PU5mfm6fUAyHuaZgLeKYDfMK/FO4Fcd4k4YGon+vmItL+yafTcLL9yUo3SFrhS2LkrdR5ac73Fyf1asbAjSIh5CmB246I6vTz79JLz88svhZd1b9+2339omgYNKOCLjgjtttJRG/HNAG2XkfqXyAYuePAWBE91Il4W2PiG4Ye4TAQ7Ou3UPrBcX/A/Dlq1bdBA3JeyRekP04H/0wYfa7BwO13ToyC4QVWd2oFfa8sS0SDmamo085JY7eKB78G24yMqd11PmPOBWqyLaR15PstuBcGp+HL63L2oPmzdvCitWLtfh5v1h546d4bNPPhcn5gWD2cfqFGVILGaAyeHKEcumlQONRn55uMpeYWA4Y+C2TpBZKNPeOSgaL2IN30jD+sajp32gbrwoIYl5Z4xUSM2dN08HSO2mqoq8xwkGpJgYT5j3kDS5ICnzS5LIs3mzSX8dKmNlqZjFmFkeZ8A3j6mTvHbDDgMnTBQBc+GCMG/+PEk9TDMJ9i+//NI420nX5upSBl7ucvoEw6/h3Jo7qx5iODkqA3DMAR3Eugd0iIcqLdJmXmfcdhgUWhEVM3tUHHMbP36CHWRSv2d1sHdYhFcOMpFumNU6S+q+否ann346PKZ7cDfrYHLFiuXi0J+eDjljGoYfpZ3JBRZlkXNjg0f8b+zfyNXDkyZPbuw7OhqO5MecgBQ59XNZbZL7epEeb2jK6TUM1D1H8JwbiHXAckvSqlevXgvXRQBnrbZQ6u2QdgB/o5LKUi9YrCtSod7y1OrtXXjVBxxSX7TOHHK+MblbdKn9duVXC1Vv606cPExsOe4SCXZAxVpbrvqHWWKyqVudYkTgr6US/5QYFboy1tQ90a4CjiA/2i9jiA0KXr0Nyncv4qYBGhr2cR+/Cc/e8+yZc3ZvLJJc3GXXJqaJSboHjrn31MkT4ZS0vsR7p+sbGzim7eam9Jl79and8laKjeqZ+euWiIyMzUiKQ3CaJvWVGO6NZbzEcK8qkmU8SPSDi/OSGLsAk2yGJPLgGxxwLUSL1Gm2iCDInaasUVANOU338SJ9duToYeu3SKbxEI8+bmlYrt37YR2wZfMWI9rNEPwFnmuDh9ycENYkTcFt9aHM+ftexDqkKoEVVdcnVbdvv/VO+PLLr+U2PazWfM+6i7mfeQXYx4lgx122uB09diQcVvmYa8j5xo1rprIbFdqTpU6VcKjGBPZRGs84u3A0RjioL7M1ATh37m64PE537eW0y9+N0iFMd8I1ilu59RcGvJ92v131FyRVuhUGGmOgapuN8dLIlf5cEewaYWaYu9EJ/BnmRanAv2sMdLVwcj9fYPmuzt3vOrMuI3gu+dtzJKItlVNb5Qs/Fs52wbMi3a+FMVz0b731ZviP//iP8MYbb4ZDhw5qUXylWOyTTrHw9XYPFc/c89yyBXH0tjD+U4NRNv6LqPEgLj/ggVORS6YnjJsYVi5fFf7tN/8W/ulX/ySAb4dXX341vPTiS4FDhWPinIfb3OBTmgy49fcKWFa2+SN/27k4QOnNGt78Su732id4wPjb7IOMGUDyxwDLKgpiHQReb5ut2vA/8dST4fEfPm73Mn3+xefhCz2HD4ugq7uo3Fhr80SLNui+A/s2MOhTyjYHJfYFLllHYlR+ImDHXjKw8FW5VRjoTwzEzYRatjoxBABUVEJsiRJwjOuu3rjvoSBvJLlaZraEtrmzdWikgy+pqIIb/ntUdemwbf78BQozQXPiIR0YHTO4uIPHRqW8w8pF5C/1VXrr0DQR1zXYGDfBr5sxOhS7eQvpgBZT5TVFuEDKYZ8k7VGTd03zLIeRmLyU+RrDyl8ezHI8+WBNGA+HP5MOB+HJcIi5cuWqsGr1qmBSb3KHqEB0onGY6+tviyrHgqlIZUKdJXdzMY7ekMQD6v5Onz5tqa9bvz7873//TXjhhZ+Flbo/B3VaEMBg9rE0lQ928jOT4LezSsCMrsWvlV+uRdnlUw5TBMaCZxYA+MvGUeZ+lrYFAgG6v0dqPzmgRLLupKRRmOM4CK6TRPF0/V3O5C6/vf34mxkrb0NIL05QX5k0cZIdvE7VwaypWk/5eH3VCu+lJEAfAXmXZeq74Hcqi5fP355z+dvdG70Jm4cvfxOn3q32JVv6cDdUZJphIS6rSW+KIHBK/WTHjh1RKlUEhmbG22Yz/5HmDqHOnjROWPnyas8KfK/hJit6U2sjnDB+QMThLkwk0bj/sk0MFSuWLdcYNyXs3bvX7iJFhTVjuU8fZEJ6qQVbnrm9KRB95OFliWNhLWe+R2veOCfVsjCSjpNE3NJlS036C9iZixjLKYszp84QMa+jo8OIb0i2Hjl8VPNwrrq/BjTrpJuah5nfWkXYbBWu0EDDeuW21CKPGTdGaialxhrVmCL8sSJhvUIzjdda1NIq2yiFl2SSCGcQ7JBAN1WT7lGKVJsLah5WLwpfRFHm7F+QsmbpxDgDzMe4M/PLr8KB7/YZ4+VEEd1Yj80UMRICHfMZ6TOHsPdB8veqmEK4vxUGqu9pE/Jn+LosYiVtZ6bwgWpvDO0KGAxGgEqmEcwRWgtNDA/azTfYLRtPq5F7I7eu8uzKr5xW9T0YGGjcpgYDkirPCgNxzKNNVu3y7lpDRbC7O3xVoSsMDAMMdLWAaubXzL13xe0yVXnaOjUFMmIWC2dleb84nzkUZfGLWoqXXnpRz0vhOy2ejYNNa1AN9wZcjIGdCYBXdI+e9YvV21o981c2FiNNIGa3IKRMmpHTHyc2NTxjRo+1Ow02PLhBXPCPSbJuU5g4eYJg/Sy89Oe/6J69z+xunchFCEgRJsqI4Su62Gc9zMnJX3lx3O1efDfDQ6wj6qkOowOCopijQWAVGms3Vietx9qaAiEpumrVyvDjn/5YdxI9aAeY3MO4XZKiEJ/ZvOVmEIqSZ1/YrXwlYApinTblbG5p0hyUN+pXRUKVpcLAMMRAPqbQ7rk/DmLNLXGH35BkmxOIfFzv6yLawZk64URxZc8SAWS+pO3g0mZuRHUTRJHJkyeZBAD3hsE1z6GbcfzrYC6Hv69h6+v0HFbepSHHsromzvUzZ88Yx/6CBQtD++LFRrREdSiHZUePHjXmmPKYZaNzSvCuxigfzIuCKuU0f6MKi7vkeGZJagsu+tFiXvBZ3Zg1PE+LQ5kSwU/JuEos5gUOTalLCLBzpAbz8cd/GP7pZz+TBN/q0CKpOtyVsbU1cAOThB0EFnDhG7O2smNPsBc0D9wwKUARLrrW/5Y9y3Ey/xo+cQRGDma5s4hyjYuwqt9wxw8HtBxkYsBFUced8GxB7vrH2099xFriwIUUKofv03UIvUCSdvQdDKGyYplb/U/XvvVhh/pXo7Lkbs3sfVGuPO369JrXgXwUjX7MPwxwqFq9ojZ1/gL3WF6rTyh9Fe2roe/IcSzavXDD2BDVG0YJIJ+fYhgFSOZewY2XtzvvrnACceaimCq4V5Y5d83ataF19mwRYq6Ei9qjXpT7lbSOh7DH+MzYmKYLy76G/e5A07swXhZvG8BB/sxRrCmuaf1yRf1mrAhoqKtkHkM6Lj66y01hv799M9wSkQ23ySJOMkcxdl68yF1tYpBRfMZUzwuIYdA4c+q0lX/eAt35t2iRpNCQzhMTh+4BBHfM1UfEwIEaaGACRtKpzSWk1Ng4DqdJLfjWrVvDRkmiT9ccSekYP+qMAnv569xLH1ZHBkhkuomYCqb15PixE7augEnggsoNw9YyEWthoInMJ5G4CQEP6bsxkio8feaM3d+KKm/md8p74uRxU3O9WGuW9vZ2IwhGpuTIhJOD1B2Y8/B3b3cs否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否Blc+T4VBgOk0iDGQNRCNfN8VD9KNeZ0Se2eFLFu//4Ddt/ZKDgcZJzO51Vmjvopf7t7X7+9H8UhTLlS7Qx0lIu3DJJv48dNMKaKSVL5CUMS+ysjUioIUm0QlIjMuoP5FglDiFGHDx+y9mVpaXK7/z5dHyEi1oVLUj+sPc7ixYvC8mUi2ImZiPDMqy0zZhpzzQFJxUftOdrj68/SiCAZXOUfCGLM02gU2LxpU1iruwT9HlJgKyTsZMfEcTphuoTwfIxIgS0G6fAHkxM44K7igwcPhR0Q7CRVeFp1PG78uLBq9eowq7W1aCNkSZtgHdIilZ0ndEffzl27RLg8oflP9w5rn3RKUua0Hwi9y5Yut34Kw3KUOmTerJ9ZYp0ZdOmnVIjCq5l7ESCz9FfYLIvKWmGgwkCFgXsIA772pMid5pY+wgN5VAS7PkJmlczQxkB/daKhV+quFmRd+d19SQpubkUlZVtwWhadV935UpSBB2kF7vmCq5xDngMHD4b33/+HDUi89+vAEQmkfCAEwpiHMvGikFXn7AhKYHtxLOTBcbM05GJuWVzcR4+Jm5ObOhiEux0Owccee0xSUs+HRx7ZKu76iWHbt1+Fl19+Obzzzns6GN1vB7ZsUoC1E7x53jUoDK5+/6GA/vR7Zn2TAVXGUzbUDSbiV5ye2rghUcImDhVofar+rQnOaDEOR2o9RdujreAHfLw5CHzih0+EZ555NixavDAcPXZEkqNvhjffeEt3QJzr3E5SfOI2KH4ZHf3/LTioiBwW7vCaPGWKEew4MGND7pv+rBv1P2xVDhUGBhADdAU3zFWoxIKJg/kJ6QUOiy1IFi6P43G787b+X4rMGDdBnO/Tpk0VZ/tMqfOT2sQJ4xkM9cT7X5i3JkyYpDFxnMFz4YLUTUmiF4aXmzeTBKCIRLlxcBvlmYcbSnbWDEjSMc6i0nDR4nZTizlFhEzuwT165IgRLDkYMyaOJsBDpBuvg21UfLXq4K29vT0s1cEiXPsbNm3UHbVbww9+8Kiex8Kjjz5md+ZwSLhx48aw4aGHwjqFW71qdeho71D+szQfRQJqzI51AMNnnBN8EvYxErwzsto7fjDUFuHdLzaqGLtRMWKckg+OMpYGaZa842eEBBg9RIpWC50i否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否rI9Rbl62as9j2trbPsTkjmC9SNI0344QcfSqpySuhoX2zMP0gLGkyKSHQIn+PHjxUx8mjYvXuvzTWoGGU98s2334ZRSrdjyRIRHjeZlJ7h2OKm+pE9h9wZxhyu6l1hoMJAhYEKAxUGuosBdrP/X3cD5+E2bNgQfvGLX+ROlb0JBvIFCwuYytyLGKDe/blT+T1cvtxztzvFjf40M3+6iuGp+ttz9I2AtdfUZrXe18GWFuNarOPOxobDmtffeCP89r/+K7z26qt2UTUEmLJhCWsXeFtaceNkeXmG5Qjpu1YGDlniQjh61TZfCB1w+ApXqnYHxvmGiqsXpALzN7/5Tdj6g60olLG79X7329+FN15/U4v2c7aaJpV4h1ATAPrA2XFZTspxzrvbRggp0rMNmPCK9IEeTD7WdDvNfgpobURwRXitauIhjTg/UUNCBVzS/SW0F7gVCaeffoJGyZI8P2WDU3Jmw7Zq1SpJZT4WnnnmmdCuzdyxY8fCG2++GV57/XXZj5uanBzPHj2256xtl/MZxG8kVzgUh2ubQ+ybUknHgetVqf0ZLGP1TY0Igd6Gra0IoBy/gwVfle/IxQAHTre+v2kqnVpEAIKYDXGM+5Qw9JGetsFmUxp5MtC0L1oc1q1fZ4Qm8rD7dWzcU0fQN/E5ZIMIdUsEOji8z549o/561cZK4Bt2xgfJAvD7TP0lRLtLuo9s1qxZYf16HQZOm27EOSSpTkjC4eJFEVbF6T5//oLw8JaHw49/9Gz4yU9+YswUjz3+WHjooQ0mLbdo4WK7Y62Vu/DELc8dnUjwRZNqhJceoTirWwBTrUj6EVv8NQebj6IvIQgTxyo7eJOHzyVUHeHieGZBU9habPMnTPSu/wUgGR/76j0bf5G3/SWYrPl0ChrT9VwhYtlah/IrtuHBWhvhlJpeBoMkE2iF1hIhIrPAklFL1ZrvPpNEQfLnpO722bFzh0keELeZiXhrWPJmUbrnrkL7fVQcyi6XpOXChQti37X1RCwPidX6coQDCQ4VjP+a4QM38FBzHSBbylxYjsYhcPccjLKfh3F3wub2Rt+N0nO3Mgy4527ltBvFUxjrGDEsv7QkN7G9WgsW8Rdp2nOmfu7s2XPGzGBqVmMkj1L0qZJz4T8SLPSVeIeY1DZqfYb2kkce/YHU/K4X08YFMXzsNMIK61SW/D6WGU4iUkcCGvq1DBHH9xnjHYww16Uec86cubrHdGFo0TzE/WZXrkoVotRRo6YViWIYbjBxJEy9wepKmO8jvHu75o3xt9ktG/uxnmijc+qSlIfxjYceFVVo3zSp4/mSQEfyuPC3RBnRaWdxb8bam3teuasONZnHTx7XvXaHa4yJSpf+CHMMBEBUUNP+INiRN5LWZ7Q+OY1EvOZrvtnPkb7t+a008Yd1Fff3ztXdgcz3XDvAOscN6SUQoxNlLCZcfMzX/Ahb否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否bMEkMNqrMPHTqkeXe/9ir1TGTEM5OQHdu/O3b/bdFTYtQgj6VVZBDTsup否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否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否fHlROBC3s4Ent8PadWyYxzYEUfd/HjGZheT5JVzSv2PY9NNicMXv2rDBBh2GDxChEM4jzv/q+ydgah1fukcUk8kAJb8CwMxOEejJu+Zzck2NpgX0X3mJx6gFoSIDe13WYNlTzx9x5c42RhHbhfa1NmOPyOsgHlowwGUxmTb1mUxFvuDjH52kTvyx2Et9S52ksav5VnmV97eD5eOzmnuX2kafKwDUD1bBtK6L8jWbgnzE8zuku1o2bNpowCgevOMIwbX5Hc7NpFkgzzQ7/nW5QNEMWGopdF+shW9/Fdg0SWquofdJvmO8v6mCbNmnM5hKOtXVhaHTsx1Dy0hVJWcNygMraq6WlxUzOoj2L8zxt6uHbPPnBJQhmr4lPjPLIfh2TZtobcTy+I9zIz8N4luPz3cjP42b1z6e5RvEJKGDQfPwvpvHf2jik6aNBkLvs7G5EmbPHHN9NjQtPq6MV+xjFq/WnhHaUm7Wc3YctRgrmBFtbuftriDGXThw/YWsm9g否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否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否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否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否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否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否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否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否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否xrCI1NjaZkh2v/NEA7//Ja9yWiqkB5lcusKnvViTrTulna0kvePyxaKLuwziGXX21eTuumBMzHd27fDefOndOa9KKdJew8jK3L48Sk/gp/AkVj4DvzUHwya/04HrqHZt4a9u2gQtSTf2pUFGPxKrRnzx5zi3nv3n379pBmgNxj4nlkns6Mb2qaruMN4phrefgonJdF5nHtmZw/f0FrO4SNEXef5uLcpXIyAjoOWdV1SSlbxJbeeZrS2O58gibtzSudKde/31zroZQCtaJxKdT6U50CdQq8agrULexedQvUy38LKABjFAPsRfHksR1f+QA7XwITeqK5Ofztb1/LFeZf7JwGNkYIWBoR2mNhfGGRM0zAVXrwsTwqBAaQsrw80mLRsHD+wvDxRx+H999/z9wmDsO1nTY4sKx7Yhm0aSeLqD5yS8OG0cMHd8MuaWr+/NPPZl13X1quHGjeV5tIbEA6ruBbk0AFhAfCAVwqIUD7/PMv7Fye/nIXd+bcqbBj57awZcvmsF9npMBI99MmI3i4axjqbsyOKGLg9BtpYdTJ0cSHPuuCPrIWQEuOg+6//OoLMd7zxJQfCd9//52s1H41TW0yRaays62eF/dCN3nbZlC8fXlkk3XevHnhIwkaly9bImHdg7BfFoGHDhwO16/dMGEKFhG2OWICsVI6vBBi7WRmw7GPNnaHqn8tXbI4rH9/vdyGLAjDZPkBHWkr23imIgq2XLYN/mJr7JIO7N4vgd0hnSFYabO7naJ7eHRsSdw8TZwwITTZpsqIcPzkcXNjhBapL166v5U6R6rBgweFRrkMnD1jVhglNzAsZgnWnhqHtsGcgUS4EFUTelotMgR7wAUhJxvhly5dMkHotMZpJsi6LOsu3Ci+MSF2E5uNvTdgDYCiCBuTl+Xu9qUFR0AFsoGPQgaChUVy1TujaUaYPWu2NKJXmiveC2qHh7JKix/VzmPINwe3Zrjku3X7Zo2tCOM8EnHzSsU4E/hrR22itMCxKkLZYd++/cZvwHfQ73yOoVZ8K/mGoMwzQUorMzQncZ4twrrFcq3slpBsuOkzYrxFznt0niyvQY6Mtt2EaUfQeUeIwm7否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否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否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否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否UDK8hZtHSuXvstinMd0LbOMCnlObpTGkpVnt0SwajENl8jERgFcH7mdor+xGf/tt9+G//E//kc4LMETFmWEViQYwDZGVzeCZwsRRaYMm3VZ/dg/MaQEmGOs7dBWZvHBZsf0qY06N+wDO7Pu3XffMSbZFkYCbQcaA9dy64fFHYsvbUwS20dM8h0x2dsR2H33nTGQAp4LSnJ6eH5dM9IkMc+/ZXHUSwK3J1qEPhPDPlwazWvWvBu+krXbqpUrzPoIyNQJt3A//vh9+FGuMC9pIRhL1Gaq8ErHMZUyXLI2pM68558H7rCSGDlymIRcs20TaVpjoxZrfbXAvGbtcvDgQbMwoGzwhLZpOcDwtnC4tbjaAgZA3jgqKMdccbQPdUHQOX7CeKPTRx9tCPPk+76vaHn27BkJgveZhisWKgTZb8T9DUNYMESbCEew2fjI4i1xlT/WN9VvPBjeohVwsaqCbk2NjeHTjz5RP3w/jBozKl9QoRpttbDsKl/XAgW9U39+rA1Gzo/avRsrwVO54IpyTdbnBff4q1UywzJvyRxsZxG5AABAAElEQVTrQYMGmgsizl9Bk5m2vX7zum18IMw34XqeuvtvaBn+W2iLrrmf4ryYJXKty6YxLho9eHegLRFCMK65puOGtICvANrBvJVXaFROE2LYrLgr5QQ2xdi8GDyon1k24Q6IAM01db++IcPduhzfoWwiaNFmHlr7CJBwhcT4uK7N/xPHTxR1fcGOlPN8wsGob0hAVIqwSCuLuR9BIdYDnG3KOSu4sMViHUsOcL4tAQB9HZh8j0dJOWaV3BP/x3/8R9t4Qslk86ZNascWE4KR1kpRXiyO2fjkc47M24eg04J0Oa48VAwQMlYAEJGssU/ZmM7e2XlRWX2Jd7hsbJGHs3vYDGvU2ZQzZ4nmstZmsw2rDgLzEZYT+w/sM74AN4a4BgVXh0U6vk9eesTFsSuupCP4+/jU3i+pqm3wShArxbVXlsd7Hu8YHs+1Ulz6vop7B98OOFxZYsnJZiXnwe35fY8EWpPE031iZzpOnDheFmtY2+JhQG7aFOBVxmkzcPHiReayDmEyXgJiW1iSl/rDZu0ueWnAq8MEWQjPnTvXNsTpL/Cc+causDKBsSwR3l3zjt2vWLFcQvGFUvZp0kbnYOMpHHkNs9KQRVh08o4+6WnhSSYJ/qfyRtBLZf/5r38NJ2W9aAlE60cSjOICHkEDygIExr7x1fCqwNI/G5dqO2hKE8b4CMYy6UfVy0J+kz0nyHkSu6bp2ktDwjRdCYAXfHC4HZX9gkVYdi+Hh47LitRWjS2ZaG1Z9aP/vIvZiZRbf7UPc+6kSRNDQ4M8OUgA5XOsFZv9GIhuo2Fa0gvcU7+s08YeFmHR76gTczCCu0oh5hNtHEYJiWM89ETxilf8IRRAWNLyqMXmf6zrGCtpyGmfRiozCgMI0hvGaN0pRa6pcsuLsAuhHO5n74tveSAe5qq+5Vh否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否iESdNs1aRitxKu8a4PQlqfrQ8lWwytl1nTtyJSlEF2XVN9ELGkyXVrf8WZgiIyt7HkXuVs4RU6/LZElhdofE0t9mdR3GJMjboqWSm4vIhLGsnf9Xt+gMxJc7dSZpl8f+Lrq65vn4xwQlNOv1ZPVt0ZVox4JZacxaqPZc+ZUC9QnzmsNhnLt1auXPT9/+aVccutM51HiYWCN9tvf/cbWoZsUzxEk0M0Ku9o7ug8IF+MtpALngVT4KTGG2egnlHm9uFoGLmcyfn3w62rDxg32qrJUigVYxT8+fVrVL2vDOXNnW3BNf2c+Ofj1oWrr1q2aq6ZoPpvltNkqWY否goj1HOIKtFJFfe5FbQNy7/fhocBg88jDU4Mepj0K/LIp0BPY/bLbt1e7IVGABczQFi2dSx3Ak9PxPwZGCVOwkJu+g7IU+uijD+1zHa0pzqvxVqDL6tkLzLII5jULMG8ewED/ecUzDB82FrjSW4XF2KtvyM3Ci/YLj1Y8mye0hZWlLFC5i+CPtjalhuUUsfk8cvSIzq57u3r77XeqY8azJTDIvD/lSrnJXIapyTkRixcvqV577ZXqz3/+c9XX1+f3ppvw2yMttPXr1/uPBSz1dhuAhJGP5anjtFE否Q/0gkaBKVcEH7jBRKMa1xPPPf+rql+WdWwESHjqzMnqU7nbfE/1hqmVrjajPAhOa7Xo17r7KdTIvNBELmdUX2HqOlLugKDKQZdHxPDFKpCzCVetWmkrJzas+7SR/UxnvbGBOSl3itkHE05Nt4y4C1f3VVMm+iiWJmufWVv94Q9/8HWiGHEOtJUD7UUbqn6l/Up0dFIlg9I/aKOy6Ysvqg/f/9Cbm2TKd6FKgftwXhinnNfDeQaPy+0W9KSfHzl8WOeSnbyrwoihUKjZ77LJml0Ry845s2dLsP6UzrdaKKuPYB7TZp5TspkpTI0FvPpsriaggkzX9jSMJqCAVbJEP+GhC7w6TeOmaxmN9w/ULciWMQABm7hDS5hXp06dlpb+BbnFnC2r5Lk1gynq4ZboyPlA1fC2kUHYhYWCtenFHOWckuXLlvv5lFxUXpLV4V0NpQ0GwFQ8fTkZrjB2M4TFA66WxWTSHwEwtBnfHvIN1/eHgMBloiwFGfundSYLaTg/67IYOVgU+hwXp4wfunmOxUb0T7/NevJt80KDvpO9Lu4phO/17Nlz7KoaWu+XNR0MaPAmFWCoI24FsSZ8XNaPfAdSYEe61qAFYtAm7u7lb8cccpeKGlibIQAuqDQxMq3bIkRNAQe+G0L9Hoa5meKySUFgN37chGri+EkukKy2hhR9cdW68fPP3bdokxkzZ1Sz1Aa03Qydm/jsumdrKxWsP4HbL否A7/EP34foCzrPRwzX3Xv3VB98+J7GwTidN7bGZ7i6o7OwHazDmzi3jyj9292wWWsRAEFOv5Qezp//XoLDmabx5WFYfIwUQ1jrSfVrK9pt36G11j65gJ5tC9nutOtErnuq28d+KDkou1ke951xQ4HzIKYZvB6efxg0qjtC3lC2bNHhUZ0VyjnVs3W+1RGd5YiiizuC0uK2+LTWWihhoBjIfJx7kzulAuOROfKCrF4RyqFocvrMKStfnZaSB3Hn9C3je4ZLTd5jIcsZbj/8oHjdn5cA/sIPEvzJ5TPnXqXiXeJkSyE9oNB1RtbnCMCboVX7jB0Yk2/yythoDjnogGDlomiEVaCctGufwRma4co5VybsWaINsr9BXtqDv3KvW56A71d+QVyk4ZFgZbAREto3EYlX/sVF5WyNP5RB+vv7vT/Fk8hpCezOSAiKcIZxDL4ITb/XH8pvCD3YPxgvFQl8rI8nS1EOOMtkyff73/2+eu6557ym5b2Fv7r+OOUCwaGma1Sn+20hye3AI63p2x3inccOAYlmucZDP6aa+4UEd2pwp1H8uHFjwzuCBLL0rePf7ao2aM+9VMokV174tZRMllejJXTTFlftJUG2zLDZ7xLGKj373XPfS4lRbbl923YL7Fh3HZG15bvvvGO3mE/Mnl4tWbrEyr998/qkSCS3mNd0FqOEgAgQmwG87kkoRDH5/NPo9yrwilzoslb64P3xdv08U99qK3Dp+z5Lyiv9sjx/QgqRZ9V/UQQg7UcffeR9PmNwgoR7naEUWcZe59vec48CPQr0KNCjwMNCgZ7A7mFpqR6e95gCrKBY3pTlWm4OyiOFxzJdC83cXWSakqteiClPJjGnpeRliUre3HiwSdy5c0f1t7//XYKnT7yRYHMCI/K6/hJeMGZi40Le3GCEdRKYBcx8x2IWa6bVK1fJNeIr1Ru/eUPWViurCTobhLSBg9wnaVELg4ZAGeS3FpxKlpxIDEJpakoDjI3rvn37pfn1hbUYwZuzz5Q5NjCGED9lHZpUbLxpf89Tg7SxWZKQDtxh8rFRxhrrv/zLv8od5kuKk7a+Nlgs6MF567at1X/89/+o否kZdUXx4E+9vKFqlEoZ0J3/3r83Cy3pxuoMmF9pU/YnuT35lc6uGz1all+Cxd9xacNulMbbRx9/JIuuY1IQDHrplUPUF+ZkRGT9edmlqMg0hF/aAlqwQeb+hjQTbxWgy8IFC3VG4Qs+uw4BJHTiTKUd6mN79++tTmsjDwM42jnoBb6mna6u860K6fIu69utrpRjIuiCgO7JJ5+uXn/tjWrR4nA1SnkeVXqPEJFnsjDOQk+d7EpDsnI5r402Z/FtlvDxuCweb6qvPIyBKhFiTuFa6upY0UtMIjaWC/oW2C0KzJkTcvlzRJr+J8WETUFlJE9oJfNdvEQbtQC6ffhRaL4bLYuh+WJwPi33M/39/bYQJI37sZggmZb6uv+BstobS98MAZs8GdO4Oo68xAUM7pgr3T8U73fA5IWCu1fc1r/5ro4oN93SdqbJZ/frfOi4tkovLwYrsCPfgMekQclv+uk+xkW8bJaFwA6Ncs5vY86YNHmSGch2iSfg4Gwmfk2dKLFJ69uhwQB870VE0oC2Vd2TlJIj1Q+pYQ9zE6YFFkOH0Dze8tW9wKhrp8p2wOIPZi9zbAYLQPhWEuibEJn//GlM039vjozvN5Z3fKPTXTVZ0Lq+rj+fyd否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否vBfSCl8rKOd6F3uInsDBikaqAyAkTOOyFHtNaCus13KKjlAND/7KslS9LMeSC3F/DtEdJh7O/OGdxx7btdqXJuhEhSIsquqOMLKfgVshTnlqXgfHqvQYWAMDtcQlYzp4daQHCKY2LaTo7d5bmEQRfBNI7Cz8CyNq/9R2GVqBTFPZIn7gpfbZb4GEogFT+yBeQA6eY5XipP2C0khsO31LmD6wb586dY5qhVMMcgyIc1oLXsRTU2pNz6zjbc8aMGdVY9RHwTfiBbxRRF5blZZmBsF5nRMHJl1LBxitFJ8D2psk0SRMnbP/JIrLI9rc//uQiChAXw5qCkIALRpFO8UqUc1mLxoFoM4v3bgYUP7l37hwb/pZpbwePg7mfs+yma706Qy6IEU6fkscIzg/9UvtX2hDXxFytvKlJ1vv+gjPn/k6X0spcWaEdOHBQyrsaw5qPKRMPQZ989El1/tz56s3X36xWLl1e9fXPtZtoY0i92I8yP7Qq1qjB3b2FVqZ3ARtjgpigJda31J/vNcoqKKExX/X1zff575zfzvqePsyccFzrKoTR7Itx38t6QBQN6Kp/6wxg1a5uzGadmtg045v3zTSBZ/Pt3bjPuSHHfsLsfM74+3FNnCjr58TjftS1V0aPAg8bBXJ8/rOMzZ7A7mHroT18HzoKsEhiYgkhRSX/6Rd8ntjf//a36vPPP7drESoVmoNwqZO5FwvZWHux2GpZntQTVFk7wShgwfrM2meq38nv/gsvvODzzHCxhTY8i0DcmcCI86JNccAFjhfYBQ7rXxi7N+RuyUJFaRC/++47tq5DSxQhF5vZXIQPtTEAn0u+UpSzUicW7iwyH5FLEs5fQlD3+iuva0P4uDUgEdTgy53zHbBG/JvohisiNqsECzp91yojyyrRXgo7zgvYwADNtWXSLn/11Vd81h9lEFjos+h//733q48+/MhuJfPDY否lUF2bdWlE39Zt0sb4icbWOJTwlWcY8dcaVkidgLFugCn/61//unr22bUSPs70xvjC9xdsjbVxw0Zp4e0RnCuGR1m5KYnNF+KXu1GLFmYisfsYfQurl1/Rnq+9UfWLQULdCNF/S3/W5qsbXam/Xeuob5yVxvPuPbvsChPN2LDwoJy7i7uRu4c/g2Gb4znrM0F9c/as2aLfNGPDmRvHpBl8+NAhu0LK9PcQ1UFBm+RUxML+6D8ISrDogvmLxjP1wKLLCgckBZp/uIl2Y9y2jV31F+oFfOakkTpYHvdLWNCakaYXCKKZL7A6Zb5kHqIvxLVFXWBkqO+5QdoAIiUy6R1nGYFgI2MCeICu3bCjDswTuPLiHDv6yrRpaCvPr1asWG7rgYM6zB6L1F9agGGBhR2CosuXL0lo/JjP6eLbgQsslGPunmWh+gd9VESMdhjYGrRDno0DE5j+zBlK0c/5rgdTrC0nABWYG1FwQHAyTuMfZiK4AyPHUaT8Kb9tJQ8ZELk8dHQ1BP3AOIMa4Iewnm8Q9ccCGHdufEdznFHQN3LP/LFcS8+YEWfZpcCuNQYLIUjcC57TmNfoM7Y8hjzlj+VbbYWsuDlzZ+ssn6f0rd9l15aMCYLnUa0否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否+/vvvFdt/Oyz6rQWwtGG8hCll6ajU0ZZzlOeb+dSSGK8DAO8hO否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否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否ImgsEA/pLPrNm78Uu4Q9yERqkYLX841Y3Ht8rJepRzao6Bsq7JRI0ZZ4Ii7jBGi6ezZc6rnf/Wi6LjGAgfOHmHxe/HCJQkrd+k8l08lGPna/YQWYetDSPoNA76e888vB/kZSho2uWhgI1iE6XPtGsz2LBXAKbhlQxEa9MvluurPf/6zzi580ZtZGPv0k4sXL1fbtu7Q+XvvV/vFoEYgQACP+AFa3EcteBh6yH7WxI84b/YEmPgRYirP0dl1Tz29Vu5xlmmDPUq0VFvleNO9x4yQAhfjpl/YZsaNOLV/uKkaYaHFCQmsdu3YWR06eMh5YWZGO0fuodfgwUiZdFQlXP8kTSGGtGbH2b3tJDHssUDDMug7Cas5Iwe3fznnUJvm/b2qXQvfKAGsGW否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否BM4E6jJipAT+YnCjrMAZaRmidvGbcQ/rlb4DMxVrXMbLFFkYoXE9X4ygfrnF5BwX+hFzy50GhK+4rIRBOFnC1ifXPFn97//6f1Svvf6ax64Z8hqb+U13B1Fh+X0ajNK8Z1iZsahMCKv4TuJ6daK+EVhLqUGNdvwOrEHGD1bGwBx3GhMlgE50WUpuleo4PWb/oy5z5831PIFACKFLZxvw/cMq7V0xJPkGXly3zoLK1MgHU2gU46xVWozLUnOtI2LsJLXBr5XH5NMPAjeY8DDJnZ91GA2lZxSm+AshFooymn+0ThijNkfAzpjFFSbKJ64nNNA/s9CDGNVVWSxwZjHCut1aUzwugTypEKJzhg7KF99jBat+OGI4LrvFflffZd3zpZioWEuskhX947I2QOjJ95w/1x1CCE/WTFja7du/r/pCGvf9/X1WmKEe4EjgDKjFS5b6HMEDUnyBwdlqJSeJ+htevGEu+fLLTVYmWrhAykFLltgV17ffHbP1L7naWzvg3K9fXAvvF+OVbyD4Pbt2nS1MKB/6ZJ9LfEqT+FHV7AhJjewvHQmUmRTNWOYY/jg7iTUYZ3V9/OFHAV/9wmdW6wlrmG+/PS7Lh2Nuf7t25v1AJDpwupPHn7NF7gTfByWP6KaJ2q2shmbuxrUs525u+eorz/H0J+YL2u+KxiBzE4p1n3++sRqneQ23j+Mnjmd4G07d/3jWHMGYZ+xagSHnHc0pFozQs5h7fKENB4Z6zHe8ivmKvslcxDqw0VO7gOoS1QHxpzw2yjaYm/oWPu79K2uULVIs2C7LoMHqkiWbJmoDAvMbdMOSkXUN6xzaBwEFrpMR2HH22arVq6XUssTKSHyPEbqOUbvgChiLIohrWhmmQQ/8KcTJb3bOGazVsIa8J0N2IBZ1TNJJXUMhlHtRZnn/3feq/9SeGBfSIfRCaHW2wnqdPrlMFmoTLLiMtfIN7ZdZgwOG/fwjo0ebTqzX2evjYn/zps3VxHET1ZfHyXPDlWru7Dk6j1qWdmTSX9IOTAYGI9gW3W0OzgR8M+0aXmOI8cSfFUxlebdo0SJ7gOhbsMBrNNb8CGb7+hf4fMNRwt1BXW24jvC4KY9EfP82yT3oJa0nlup85kWLFnvdxPcbPHCLjqX5VHnXwV35p7LKHic30me11vC49zecvvtwBKzg4SfBE1q6ZFn11FNPezzQdig4IdxkPXBI33qUb3aon+DiFyUElL5Yvzh0qfLAlnw4aNLDskeBHgV6FPhnokBPYPcTWzsXWPVi/SfC62W/lxTIpUmXVYsWx35bVui5cEdwlbmMmR8yf3lTHpP5wtlDp8RgPaCF9D4tqE+dPOOFPwxD否gsXtkguM9oI2Io2tCx+UoGzcTxU6y1//vf/776tVwgzpTgyZuQBnmwMgvGeGhWC7Tg6kfwHS/AMBlvapFL8DlxusKEGj58ZLV/78HqPWmpvfX3t2W1caTeVFnrrNTJGeufUl8K+pFACupn4R8LaDGrrMU6e171q+deqFYsW63FubTh5NN+lFxg4BrltFzO7JTLIzS60USHFmzhLMhslAdsY5CN1HhHvfkbpwXq7Nkzqhdeel5uJJfrzC0dyk3bOmPUA8utAxIM7dq1S+WdZv/sdkqw9SauwHfWRlmdt0DtTEOZ/JnZo/pMEAN2nphyWNawoT9y+JC1sZtMTNKzoQmXHTftIgyXdq+88qpdLY0sfYV0aPRv+WqLXZ2gkQrzzwFEXM3YdqXwoRO/SNz9F/gZcp7LZ668p39NmTalmictyfnz5nuD1EwbtLQdZIM4LbhRh否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否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否9QNgiO1cTcOfO1AVvJfpYFL0Lx8kYYYxpXh20S+r37zrvZO/pbIU308VJSQPjHpj/fSN7PgQ/ALPd79PTL1TQYGPYXk7xumpCa5O+4fpWIe6vFmvNa1YdYRT9EoFX3MvBocHp7WfqLzCvmCyUjwPAOXBAf8fU335ilMa66sDa3NPAVwnwncU01WwKumXrH4D3wQBB0sMzP+aEesUMKAproly49Sto7X4plY+67774315hY6s+bN0/fltm2Gf3jvh9rLE/Bt47sPoeUKr0hq70Tz07Y+cAInVpaW0wRhrP1+gqNYPeV/0XSoMXwERpLYnc58++U3Gf/eOBHncvZLWvCFdbGqeJNJsryCIUtNjqxLMQzAd+YGGu871iOwBOyiQ8/i+UJwklcZ8KLwX+Rx793vsGuAxTFehY8OsoD1UpVNF4uHuOR4REkpE7OVsLbxHpteuOWk0Ab6mlvCS/0AxR7S1Ip7nsbKS8E+G8ys383C/ohXF23fp08WVzMHj5+aG5n43tBXsbVjZs3shs/u2vfMukZZwPQwfZ9YtOdP6xdCaM7Rmtzfoqtoc5LCM/aa0FLq1ncIazG9SDWvRFivDNU7Bv96gMvQPdxLcblWL0/rHfxmsEa+Ucp1vHe1o7dPkC9cFLRhxT177Xjw9qYtQB/rEGZr7GGxgL27JluKdtes29+pSrrWwm8JmntilthnwP87PWiZXRx1BXXolXMgfwRinzg44p5xKHk2nWyM/tS6/Ivv/xSQsQTtnZGIEYf4jo61kMOqe43wTdhjuChOHzqVJfRmfkLvLkyLzB2wQdhDd+tVVKADOs5g6omDNO8Srh918+zg/fmyI5q1ZWWnEe1LDU/1k5IIByMEoowNglwimDtMF17HCiy4JKTsfD4yWOrDzoA12ike/YPsL6v6LiFqdOnZAsqC7P5uHquVORx5JzOZ5OFnngxxrJ9BzQXs6dAedYLp/U9OHTwpwwLvRk6+51+BAfS4Q9pC4ItLOSHnRMUQ9jTLWNNy4bOQ6KsIcSYOK9zqFHO4WzLtupiE9hNmDDRrAvx3DRLinm4WaUPmSewXqTd9PkoKa55Tw+d9jUxaVKgSYEmBZoU6J0CBVfXe55mSpMCv3EKJI4ssSjB+MCwwPTBpEUcMYScmclvLNr5OTF+cKNA/VmLJbScduu8DFzYxYLOFmwBVfl/FYM1RsI6NHo5zPl9uYnENQNnEoRrOZhWd12RKqUe/feavP7Qqqdugorwq6sL69Aks3NVFIuQ5+njp7YRcfKkXFDp8OHz57szNDP/73/91+y9d9/TxtKsbMLosWKccfEFLIXhWlBQtTY7EDBm2ui0egItbSY9nfhrxvkdj7QZhHABgRQuPB7pinYgG5xYc8zVhpHhqrYYQy7w0KhbmnB//tOfpR39mRh0NFPTxr/qiHyGS91PtBug+MefMn2yWddtkoBr0cJKNla+79lYJYwU3gi7zko7FuvCA+onNJ4tqPwThGXQOA/ewGIs5Al+o2RyYN0SpEpYWzr9N5KFskAiHEJ4N3/+vGzVmlXZOVtY38ouXbxMA004gztLFlq+kEIzf7yY6QVaSC3Xgmqm01556MNsjNONcy6+++47nc1yzBZyCL否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否On4gZPcGjdzRs3bSHMuHgq91UDQTda+bxQSynPzWZia2urud+bK8ses6JUEgthNLpxEYpQFxyLTfHn1fSq6T0xtc1J4cr4nKEF/hjd14Q0bIyWumeRbi+TZSrGVLy7NkbUb4xf2yQIYIyNNFboYxbYCOxwn8McYh1YRi/KWRWNEiLD67+W3x1qj+ewIDSMUlv7g13QLh+vpeYSF/Hki7+AG2XjGbe2LRp3lWpVZ3PpnKpr1/Lykad8Bc3nTy/MU95F8VuG0Z97xgIWLWiAs6mCJR3um3hHcNu8VfMiyi52Fhpa0xZUq/5T1vAkjmcpm3gAKw+8b1gIr1y9MjtxskPfHs7SeaQNxEk620Suz3SWzYcffKRvieqRFTEWeWbxrW8klno1dCyGdYDv9dpX1h5pELpmXJCjaEOvlTw3oUdNzy3xvAw1aNZl9tHg4577Mu18I3x9dlXj7pZcap+WUgUBeMzjbuE/8PhaJS/7k7qAb/U9WaWe7T5r1hAIdjjDLBRH2PAaO25MNkNuvKbNmJ6N0oZnTQDOADStHgT0hXZszOKu/Iws8Ts6Osytcqs06hFuL9AGdFXf8Duy+uJct0cSkvb2XtNnzONs9rO5jMtz/rCyZ27mfYPvsE7rDUhNwwf2wee8AiYWGyjOYQ2CsG7CxPG22TtT/O7lS5dtPnDeHKU437SFZ4YP4BuDe3c2NVEOQplhzcnVau8vJshjzpgky3KUweg8eGuUxSIwn8KH4sKN86vjm0afGO2E2wVZy2NtB82Y2zSMLNg7NBCvdyDTvL44BegEjWH/hvoahPdowsQJ2WJ9F1g34Q2EcVMbouPq38baXAP75HU+ET907eo1fbsRJp6RQH2sWVYhXIR/xJKGddlYWTMxHs0iVO80bbS5Qkjxjg8q5tBUcwVzBHVi8VupVCQ0WGoWdswnrqgxsBSqhxb9WhvvdBRa5uUEryt4Gzh3vtt4ENz/Yp2MQHSVFIVQ9Jyr89RwfQsfwRqF8TBc62Bba0JPAXOKiqpa3AXPZ0TWM+lMlawRL8ulMEqEn33+mQnsOMMdRdex4jmGa36A78jPJwdxEO0lePs8kbPqO+RSE2EXLsOrmpcQkHq9jleAgc9CoRRBGPMnrhNR9hg2zBVPGBvEYe02S2tZ1tso2iC0o05g2jSYUOPC/gPlAt2gAYpPHPUwTzScOGmCKYwE3p63aB9KZPAGe7WvUpXCxOK2dnOniQUzCjC4/LytsUMwmhsAavXAUQIo6rbo3HiUL5ib6RnbU1A23ofZc2aZxfNNCb06TnTYt43SYFFAcnhD7Ze2EGjPTSl1IlS9rO/LsmVLjWdFsXKJjiNZr77i+I1jcmmNNSlKPFiQk4abXwt0Yh+h79Q+CjaTmhRoUqBJgSYFBpQCTYHdgJKzCWxoUaBgAh0vZz9g8gjB+KRYxWgxEwxMcJyWM/1YRjHdKg6zyWYfzOMxuVo8ISb5Z7nDJAQTaYsV5RszZrS5kduwYb2ESx9KWPeeud1C2EWAgQSeYUX9qjuY+5x9DJxJLwVrixWkTKpbm5XD2LDU83VpYv75z5+aO0zOVNm8eUv2toRCbHqO0GLjmdXtAAO0wVGUbbyzL5HqBp5Vler3+3JMSkgX4MFU0jbaSE4Y+ntaFDx48EiaYFOyudLaHqNFJZpvuCmDhr0t4mgrG6do1LHxwaKpUqnIfdnObMP6DUkoo7pUx0hp3hEeinE/Lau0/ftlZdV1ytLsbEHlCUpaf5E5IrhvEKKllETcFBDY+MECA7weyxpu1LjRdtYa/T2/dZ40Jzu0mbRPbklvGC0ADV1GyHqMxd5jbQ6w+Jsnd49vv/WOCx8lGDEBqvoIwStn0xw+etg2hFhEBQzrf7UlcCM+7mkO989plo01ysUCinsLKgj84aI5i60l7UtNg33hgkVuzaJ2R3jGS2E1OVV4MtyIFX4WBKtHnBLo82NHjmWHtaDgvBlbBD/WAktnyMycOzNbKy3XlXJ5h0ZoBBNgAU/jyhbOjC89lwMuQRA0cm7SGsE4Kvqx+Nwv12BoYT54wIbl86hThtjzvlxnn7ASamUMWfjPl4XdYi0owTXOsGNss2jFlVrAjHriuScmgxfDu8tGRWVRRWN0rlkj5bUxRhhl/I/GBU1jXFpaJFLSlQ否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否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否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否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否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否61Z60S4SGAZ/54rnMI/PNnOGaPYwLKn0iAGEZ6SiVYWK/KU5CrXokUhMcXbUKfM1v9RUE+BLhaHkVQuAU5Q9MpskMHndaLF+63Bg3lxNM6H7r1m0x0zrt7hI0r2pd1RIF1IPSI1BIn2pTCQfFCj9vaSSrfPALUA4wfvM4eZoKbsQMeHmKCmbuqxRVQJW8fEtxU2ePlDyFZQ3WkWeZNL1YmpQwMI1nRpIaNLju1Gs/AABAAElEQVSvB80DTI9t3LgxMcgmlcJmy1lAaL9el2SgvxKwZ45vnVfCyh9RR1aaRAdMpNIe6Gf9l/uL5/29xZrVq4v5GscniGkKT477S5k7rd9osLNn5KTwoAztR+JwQwLw+LPuIV/CwM9O8evBN3+2Vor5SwzBRRLYbdu2rejs6tI9LJfsLkWSWbmtcLUlBEbuyMPy0bM2JI/18b6PVmY7eCMtuwtdPTYuoxG/QmNqr+od7Zame80UoDHGUkotkjuRsACCWfEbO25Ie8mZqVFv3r9pu/Lx/9aPHKuIVY9jI//oBfVx6/3ztPVhedqIF3HiO4/De4TzPlwcwkZzpA1Y8Yw09d/hzzPCxp63D4Werm44SYAdF4RIy9VXuO/qjASuHGzB5Fy9qx+T6sPf6+86svne2xvwMzH+L/b2FQck6Fkps4l79+4zyyDrdN/2Rh0yfHj/gZlCjPJB0RzcY9v/HpYwrsUOKy7UfaWtcV+pInMAcJG0vpZLADhf+2KEhNyrltMzX1dEPs3n2CnA+MJB5mvXbkjj8YzV5yOZ6Z0pay5acKQam2Am07H6g7nE77/7Vu39jI9HWoP4AS9qtzrohcadOcHnDdPq586es4MJN3QIbN3a9UrPnE+o9tDS3Fusul67ZrV4IbeLW9Km5165eue90n3ztlQfb7TvRnBoS1juYK1xXILk3bv36oqULXaom36+mPapA4eNxgfgBcxXwWs0vF8mHD4TAtNTsoTDWurZ82fFNP2bNHFysWDOgmKD+uqFCxfsgNijx480jp0T/+KoTJWulAC+Opzsc5FKlxUwLLCAV5S/JsK4Ea6gvBqccWf8QSWIdcEHhXQT2SYFmhQYMwXq+YhjTtiM2KTAO6NAtjiwFUE+n9cjZQvAKgEMh6NaePz8889m6qBFz否pOgXJwuy5FopsxtBq2rx5c4HZg/my5R2nu2xRlvKqINZnOPJ3oMoGI/7CtCbft2T7/8jRo1pInS7atFHBBAjmCqZIYIdjUvbFYZ5PQM39fLFESOPQ2rgf4lfUx0uVT4n8hJin/oWNiP4QtvGHjXfum+OUMyaiqBsYloMS4nFqdoGYWDAtV3fIjv/smWbaj9PoMLtM+CpTf8DBYcoMs32Xr14unkj7h7YWDMyS7tSrGJdoGnF3VMeq9mKvtOp++5vfFb/7+78vvvn1r4vtsum/WPdBTJP2i2sAVa3Q2gWSAJXB2pXyMEc5hzaYMtsy0hhbCdTyP+AbcN/D6evR44c6jXhdG73reh/Qpu+p8BTN0om8hWK0YiOfTQ5m1XBRAp70PfcJX74/MQdxcSJBRRP3cp94r3ta5Ip6jYRCAZpY+T+vUAQ5IcypcqrSVH51OTf89HEqT+3RaJupos0D8ze0ZzS6uM+ukQtY8bQ4WZuGockp0cMyxdrV3WXMhgnSViY++dHX6C/9YoIe0Lj/l7/8pfjhrz8YQ/SJBCAIDdEUIq73K9ews3xKuopi9C39wwUu/vQ+YQHlj2IKniWnyPL31P5mX4STZ/qz+JbG/crviKf8wS/6fn145AIKkUuJTnoRKOWZhzOfqJ7wE2wccKmTz6Rdvmy5NOxmTK8ZQ2Dm3LhxU4cZuNeiXmA3tM5T1u//AzrUtIPhUaaU9SWt/x4+9QghGRDqCUd9YCbviu616pYp0r6+vuLu7btm3pswb+cez5ASjFSUsh0AKv4M6Dh+EKRPa22zuQ0mD2adu8TAYi4LR9v2dowPPY6C0KfAy14NJywGIHS8cu1KcbGvVweoblv5Al9Sk5Qk7hwO7/DxgMWaDA0W5l/WapUjbtO9DAV8pPKUHLy5fOmymae7JU077uPq6OC+otkl6Jr6Kn2bL69KAdr3U/Wrq7o3CZNkrD1j/USviHqqesWr5thM/1IU0IGN6AM2f9p45wIl7pler8OeS6UNFlpgL5XHB5Ao1iBhpYDvy5cvS2B3oNi/f78d3puiNd4qHTTcJCHm0s+W1h3MSmO2HuzHBiV86++/XBzSHHPi1CnT5DYypAkBWs/UmL9Bh1Z27dplWnu5FQ+bPD4Aur3PKHp71lZS8wAaj2jUMxbdl+DK1huprc/RvYQ7tCf98ot9ZkIx+CRWB9Sn6oo+kqJ7kdU+Aj4ej7Su4Q7qc+c77R7qAWkX26FkhVGvKySYxboM8z1r0rfqUhko8+PHT4qeHmk9nztnYzNj8TwJrlatXKk2uNSEdrGHGA7Hak0zXIy35w+v4r6E5wjlOOyIxREctOdA7caNG9RfN9p98/A9Ojs7TWuWuhrQ9QJDnGjF3MWfLd7ep8IOQbbp0aRAkwJNCnx4FGjMIfvwytHE+KOkQO2sXy0WWQWmsFKtBT8WDO5fLgpZNCY/wrjzpK+3XxcIX7MF6UwJxSZrQzGoRUmknyXtjC1bthRbt24r5i2YZxsJwlwvSy/KwnLzLAkawQUT3LfatoBVarCUUp/ha2bZXvhmhdNqZ3Uq85pOnO3W5dp/9w+/M8EhWiiWKPJMxS8zdsAJt/rA+G6UOPwCUsSN7zE+E5hIXVZLmTxCSg+9RN4e5r/uVxviRSdlDVxFinjRHEroBJRZxksZu0pHAguO+nHtuRu6aPvnn34uDv18sLijNkN7ov08ffrUFrHbd+4oNqmNcIecb0y0EdGGk3/sUsxUmF5903PXNPVguoaz6lK+OWbAmT59erFVcH/3298Wuz7fZSdJMQ2CpqdrAmHj300HGozAX7g5LIAGVOhDW9O3EQ7c3I9nxCr7SnhEiOLEa+AdsE0gBCxpPjwXA5+NNm0XRtM9CTuh1Qs0lQQARi4bgSUyEbRu/Tp7xiabLDx/gAXqwhiUCXwLLsYMsipp8Qr55sIyK18DWCWpG4RFsbNqzGI1SIlX8jaaCUAOI1JgIhL3Il4inTJCgYU01Bgu0vPi/ors/8dMowAPvDgZRFugXBOlyUwF06anSUgNYysE3MSvdUAK55g5Tu7H5hMTYtOmTbHTovv27PMAtblfJDA2JxBsPO/oQMRVCe6mqA8hiJirTffcuXM8jn7prwjEFGTtz9qD0oIzJuMmcPjXUHc8yoQKMyyJy5t9VKEGoPwcmrYM4iWlDRB5+yRzcAIf+pa56Cjx6b4pLKJ4nsyFRCM9LmFtefIempVoVC1esqhY/JlOELdlAjvyUmJo/uDBQxPKOyQDVr7WvAzByz0qKlRvNensIyUeGiCfkcIiQcTxPMryWnBdvooKrW3s0pOxk3E8HxOiLqqU1VvkO否TjCJVPOuLAp7UUeQd7fCuLARwzw93Bq1dt1bm15ZZv7HDRsC1tpBaBuUJwMoo8oo+XuIJQuFSJMoezgR26qMcRsEk1NlzZ81EIvdChiNf8LUc9aKowsVDc1j4sAY7dPigaWtt2rDJ5jkOwQCDPuY0dqQchhXE8if9DJlVY65dItNN09U2w6ENm1BIXlEGhxXxeOIz1DeP8RG/5wUPElHc8NfzijQXn+vU/XQxSdfrtP2LVSuKzu7OkijezpTEK730b76MgwIZvSOV9QF1nufPn6oOLtsBvmfpwBO05s/vr6Ti+AsgAeFln/Vw8oaR5xPx8vCXzfNl040l78CTPMYSvx6XSJPDcWq7D/NCSiOPGOMYKxmflq9YZofrbN9WDxqMUtoPuvtk5c+LeFOHYgcGnkjY8rOY/ltsvb9Qd2NhPhFTmdAIF2MIWx否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否poOB34hrVL3s21pSHjxy2+78Zr1ontNrVHItkwaCjY7WEc6es7Pfv37NrQjp1f/UKHXZEkN5SI7CLGvH+TCHDJxoV68Bw1Vv4NJ8fGwWs/39shfpIyxNzL8X7FOvtQyh/Yw7ZR9ogm8X6sCigad8QLpdmDWf45JlWBr4e0FJSJ7LR7pkopg/ukk4R/SBG1/4D+41BhF8w456JOcEAhWm0tWvXmsAEswDBYGqYrdJXC9tYlsSzShGDAD7mq3x4ekz/hTmCWYj9OmV2XEIPtP5WrtDpJp1imtbim5tyACVJBb78yL3yRZEilHEiV/eL38A5vnNI+NWHR7y6Z4pWpa5PV4UMhZmHOdyhqT1OvT+xwy9bCxqQ8M8IbrGpt5KeFtO/pVdhC2wuiTfzjtev65T502Lyi0nSEEMAMbGYPWOWTHesKNZoczFfC1ZOk3ISfVAmysBQLK0SNnWPxs/xY8elZXfO7MSTL3/cVcemk7sSf6376f71X/+1WCetTk4Rot0CQ5IL3HnyB6MMK4LARJPJypbIZn5RWPlV/QbaeAAM21r6ECsBSDSoKFl61L4AW23T2rQR0YE+F2MJUxmntZm5JEYumzVwxhGXd8wEzde9f5zERNuunv5RBnCsxTNHISukeYN/+OXxhr7nsepLXY/L0NTj9MkyC6b5kDwBmeJl0S2jSMOHlbAuAqSPqivh6iWixRMfp2cZy+A7XPfzvNRKLJF+7AXGu8eyX0U1TeXhK8YYvPclMGNTB7NqtsZS2jiMfvoNjjp2EClvfbdIKILADoEuwrvhXInfkAhVadkcc0G8mWW9c8+E7FNMM8jbFON9ixielAUTyNev3zBtpVWr2jXWLpXwfKbhG22Rp1O1yoPsHZfqid8IpCG4oRup3b1sGBkFtiX+UQyrBirT/g9tW1SLhUUcNJn8fsHozwEd2DZOXr2mu9Wu2unrmdISrnHAS85w0g9Pbw2we3x0cnmdxYjo2TMDUvqGX6SJ74gQ/vFdPfMxj1ikpD6hN38hqIBhyHfrL/X3pgQEz6MsQ5UFEGu+hvsgVvxBDWtD8shTex0WxhTjVDf3gKKpzann06fPFteu3vB73JSYOkIYj6Nf21sCFqZO8cQrD6vJMfIXUaykFjHRRwdRoAlMnusyf9zZ2WVWC否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否sAAcaD488Jbszo3Sj2/7TfGGw9OjSBIB6B5YAuY0fY/kjvnC424b1gkAe04y/gmIlaFYpvo4oXzUoAjk57+qHfhQkDwoV1IYxzQZz5a27i6QI7pdEcFgK7SAdDhHfGpkkSzpkwXO/UDfNYy2QEq3pKwAqDzASACmuTCV7GMTQ1Fi5aaJqNIMmhERoPeKqVlOXii3kKrS7mxtsSygcjgnmJAzBoeS2UtvGC+YuKxaprTOQgPBybI0Oj2JDoEWIoDQlt7AGkPH495DysNmYFj3YCvaEvLYe2WuMCscwTr1rYWeAYXwOs1wF1ERDTM+E1U6faFy5YZPV47frV4rvvv5Mmx2LTxue0N7jHWJrjHsK6kdGJPBvHIhSBMCR5IIE8jBvuP4k2kadCAGlCSJ5KgaA6HG0KTWvmPwTl4PtId9gc0Ml52jDCoW9+/Y0dWMJstM0zGouoD5XOmgz0YVxp19jEHbC0UcpOXHdRFqUSDvXVGLh8ik/aRdm86ggAnYJyBCGk75VmBYcduENqsYSsHOS4fWdU8UMd5ObnmCiQNVS01q9oHj4jYXbf5ztVB9kdjRaPihwT1GakmhGgvpW/BHlq6O5zP30q1gQclmGeRVv4SMcRO7jEmBXh9Tm+BozqQb4X34w197W+PCQrI6wnd+owanvHKtOm4s5rrT6NJnFgBaSdjo4+662+3r7i++++M82uuXPmmVl92v3kiS4oIuZxwfrzn//iifRL9UDTpns9FEDbsUf3zTKPf7Z4qa5v2FgsSKYxWSsxT3es1n1nsqxhmv5pevCZ11s3aysfr7xmWENO0AG+cBz6/OnnA5rLddBO1jnWb9hgQVoB6zqFecUOjYFXrl0p/vv3/23rI/oS9fym3HCrBvYHJ0+d1AG/GWrL7Sa0W7l8RbFv9z5pq2mPK7P8zJF5X69K+X62zSfa41wR/6lH6znqcM7MOeJNTNU+akWxgbv69Dx29JiZPMVqUFdXt109AL/MSpSqlv7+6o5afS2AXh2VJoQmBZoUaFLgPaHAJyewY9HcdO8TBXxirqmW4C5FVSlKNYWniTw9aupTfrZIUuRI2qdTYQcP/iy72ydLE4cwduzktkwZ2mkiRZ4uRiN3kmEWk4UKcQyWViA8jRFmSPo3FCRvvHyREjn6t6XVogPfEKwQEe0DsOMEJhoR2Onn1BonzGEMz5ZZMRgj08VYhbGF+Slj2pKPUmqNay+Wt0HST+acLP5befs3DK+KMhFaH9f9a8sUceKZypoelqIMSp7xHdnUPEcKzIFCu9q4jZboVYzqjey8DMCoYEIDYlE//Jsw0euW+DD7YEKfP9dZ9OlUeYuY92jAkH6O6qV9VXuxds06Yyg+V9t59gxWpOCL4Wg5Kxve2WQOPNE9P7dui9H/XPc7fSYh3fbi//l//734u7/7e9NYaZumu+pUvyVuASAhDQwcOFJtUd+lmgT50v6UXwnDElBWLy/FJty8gxgGjgDB9qAyfRalTAOdLB/yMl/9pHdOGqIdcUHCmdsqa1K9MdhlXEVH4wphXWd3p5k8YXNHO0do7v3EAZMP3755z/Jz6kbu43o6fCNuWU5D0KC4f2OAUYKIw3f4kSL8eQv/yi9gWhuriZ1CIkmCk9Mh0kZ+CJpw0AdtFT1KF1QiPblTh8QnCu8RHgkiH8YaLnvv6uoqLvT02MlQBFbcwYAQC0EeGnVLdNn7jp07zPQL2qHhTEsvQwTcEBZRd1c1pj18+EB32E0pvv7660hi+IMTFHuuF3CBsdUq7T3aBBv/cDRb4lp0eUaRa57K03pgigS8+zphzUlQTNM8jNPWSs94awwDgNY56PC3H36Q6bETNs6HUG5QDCaEWWHexikMXo570LIEh3/5Ub1UZFJt6AM8zM+e7mc1lX3n4UCiHklr6TU35YK8gGnaetLMox6gJXPWRJl8puwIIjHj+5nuRd23d5/qdKdpFJIWTXTGCQ658E25eOKAg8nSBRKI9EoTMoQzQeuzZzul3bVYd3isN81jhHcIdcCvcuAdX7xAP76jTBFW+yyT1HqntJlnRBRQ5kf7TH60R16Vk/8KkSiboaCfvB6hGfMuc4FrOdP3XEhlOTriCUbKBEAGPr4NsgCn73hURDBQ+Q84pWi5t79n6ajHOVqnIOS+rbtc0Cg933m+OH/+vJnNQ2gH/mBg9ahnlJe+Qh5VzTiepXlQckvzrePivyJJMUHLFsNR7SSVzsxQchfRnTt3jRlrGtQKJx87aMS4pQqZVGeaFRqjdcs6C1yj5Aj8T+luvv/4r/8oHj5+qHtd/1fR3t6u9sQ8ofzBPaOFEBIcaX2pzLRRTJg+554oOatTo3/0Sdq0BaXw6v3Te4v+R5166VOzFuHsv3nSFDi4gAWIGdI+RrOFJ6boahwwolHUBDQ/RqNASfcGBGQeok/cECP1juogtMMrmFmDrk8fQVUGVbJh3yIREfIKzd+HTZwFBJzh0kV4nk8eNw/PwJavo4WXEdNLDjvCGvlF2NCnk9HnxbLPEE0Bbrkgwhw3ximsuHAoYZ72lKxvRnPjw2g0aO8mPOaafE4dGHhiFmQmay2yfMVSmUvXIS5p7cd6z8otOhrlILQT2woAnDs6HMD+GJOYe/fs0xi/wfdlSjFRcwNzIWPTLI1NwIwrA2LkjxnrY6DvO6lVVQy0vK8DbD09F7W+vmAHOXyOZV72Ns99xxxMnSXTp1cmXlU1aiWAUE5I22E1rUNpHxySZmyzfYp1Jl+TMddckjDozOkzJgxiDWZ7RBnFmNCiAzpqM6wvOajDGgIY5E29Rl2/DvpQrrz95jBBl/Uye4YzWqtwaIm7GhfOW6jD3ht1SPBscfjoETu0GOleN34B93U+oSXac9y3uUzmR1vaW+xOb3gg8MSsf7HmvH3LDjTCt+IajDu6K7xteqsdEKQlON1EJP/Pw1zZpa2+A/O8R0bMCOM7wvOw8It4zeenQIGYVyhr9M3c713TIHACj/cJr3dNl2b+r5cCn5zA7vWSrwnt7VJgtMmacBhsTPC6JFjCEhZUf/3rX4tjx46J+fBECz0YkzqJrTBOrKJ9AMNn/fr19md3k5kggUUlwjLFz1hcDns0PIIq+aID3hWmtpyZDjMQja7uC93GLO4R03z+/AXS3tqqBZNfLs9ilHi+phWsyDZfv0RW5TMilR4v+TJiJi8J8x0ki2JkZMELXh70nTxRzEB9s2BFA/Niny6+vnLJhKfiDrowoUWXanfoYunVq7VhWGDtZ5Db67ShoC1ZBYm7VWaleuYU3jMJRTDZhUZMR3u73c+zauXKkgjKUu3BkysrS097AxfTehFmIZDDH/g1iwE85GxjpEKwaOBeOSIiCMEBixOud8XwgXk6R4vvmdJuMMGPxah+AJeRqQrQW8qqxg+zWDCKuRi+r6/P+gsRWJwDh3Zr7/qB6bf/wAFjXND3Pt+xy+4gqilPDfTX9ZFKVBagfHldGYwKx2gxSizokC/6aqKnIhhVE00jPOjH00pGOG+iOf8wVYlwjhOf/GG2krvZMGOIdi/119PdY0JX6vCmTrkSH0fb7u7uLh6o/WCS6/MdO1O2DptWYXnKl/ynSEDH+IqWXaeECNx9hpZarXNqmFaa8GN840Ruq4RBbM7H44wsJW0cE2/vD+wejVsSIrvgxYVX9CXCrVEa5p4YLbpHOthR9I8n9w8rLppLMFOW6NT/3bv3ZdbuuRjvrWImykxVi2v0RWVGm6KECFSYEzF5hHAXM6LhoCVtCmban//0J9F6UEKjz8Swkbmk6br3TgLfqdKgRBu5zcyeIpidZkw12hbjA38jO5gnxFBbU1x/b5BCccqm0CiYQPJrEJZ7UXYEjozZvFNG/mwcVuokA8qT+HuCXxvQ0LM2yjBf9LAoUNQHT2jJHPTZZ0ul2XzJBKjHdDfv//n9762u9u7da9qQxEWzLNKCyeiucSybVwTP66CiIOMCJqRZY6FJSzuZIgYPlWSmphM463OaL6ErFQBTFQY2DDiY2gERgQTwHjy4b/PndAnyi9/8plimgwJTZJ6J0/02RtIIDB8eE40hzn2VmKXiwAHzspszVRsbvdDNGNSsCJX3LaObfri/99l9achLa4D1cFsb/bnFBKehy9gk4JuggIhPJaipP5LpMRjED2RakPZv5oqrLvAGMk8d9w1A/tBBVmPhyCWJcXeG5kG02RdozGZ9E2urkVN/PKGMz+xPMJXf33+56JVAhvuNOexXOtq0TXY26sS054eIlB7tXg6GnNRdp2d0l93GjZvsLk3WjtKz0x1qrbIC0a77xXcUd7Tmu6F71B4rv6Z7DRRQlbBWs3FH+0fmgW5p2nHPXEd7u9Z5bWbJgZyYy9nf7lQ9DGhNzcG9x6pnRhNqynYLrEu8mklicBHm0U5eaP3I8ry3t1eHZs+rvfTrnrTlZl69ZYKbeYdHglb92dNntae8ZWszWxMYtDf7A95BCw4kYa745ImTxemTp4oZe2YWq2U6ctPWLcWC7/5cnOs8Z3sPivohjKbsE6/okPIhHW7H6sGCeTqcozsEKS+HwKjrNWtW6z7bfjucxSHdw4cPqd+tlP8arTkX+nxl5WUH6oJUyj82B5XGHntsMJuxmhRoUqBJgY+HAuPjkH085W6W5L2hQJqkbVVTt7QpOQgex371U3qrDGwGfCmY4Fi5HA6bo+vXbuh0X6+EFg+NyeCnwNH6GSwwA9A2uVWLjcXFJjGkEa7AWAQSebAQNG0IMXrZgFX5WgwLt+zSQoNcfeOBr74ULTZuseAFXceusPyvXr1m9sBhLK+Raaet27eaOQi7D4302pQ4PiBkIC1LX/xWsIiDs7j+OspvYDFKNIIDuHErq/gBgeAyCp76iEV5tZiOGFX66m2ksIg19jhlzPRiS8cSWfeMegE6mjSY4+BkX3/fpeK5mNm4X57/UrSKUbhQ2iUdazqMMRiaI754d1h26l8eMHU9oZiSWuhyOfeuXbuKbRLCzpnjp0CNHiQWPg6jTGIvMFl1LFH/3eTexF8kbJCGgi9/FYV8SCgnVrK1QU4xvhCupi0qfzQ3OXkKg+2hTJd1d3cVx48cE4wJxee7dxfrdScZC/HSlbSxqivbNbmQV+QHzuAf5XwiLUKEPOfOnlM/u24CaYNp8eiX7oDzRKdsuZ+A/sHdQ4tkQs+1IsQYD7oRnfx4Kh9IYV+pvNG3ghaBtiXjp5FLaZ1iRCBVfMUzEuYQ3Q8c0ps9ghYhFANCiU8Cl5RUPKcyC3+JsJozAMrDsLIogpzyLLsb30YP1bEjA5lSvUT9lBl56RSBukJwjJClq6uzuKiNcKeeN/V9VWZM0Yy5c/tOce/uXWP8P5BWHMLUcDDa0ai6IsEe8eKEP3CtHRiiqVzKD60ZmLnEQ0B76+ZtE+g4w54i+Fht8AXD2rrSMSbbuEyhSsd7IkTys+BEHCWHJGUMwqgbNtJWbjEKMOfGqV3u4AOa1ROJ9GYnsfVOH6l3Q3Ouj/H2vsEFV0sJ9xvPLwcAOEELPVpbfywGdX8qsD/ftb否72iHeE5QcjJCO3QT9M2ZZXeNcR8hzBUctA6c0K5FGE/7gnnGfYScsp4/b75paC5ZstjMBi2UCSVM6nFXIMwdd1HC9Jk9qEcOuVhbE7YIg6P/ZdGsHRiFAiEF2sEFPRla8KbagZONeiX+acQxkMQlDyufPkxTTP1gku69hWARl7HI4hoVLcjS84O/u+qtEd4RK38a3FTmAETaGK+pD8ZNTEDOmjVXfftacVEmC7//9rtisQRmME84hW75WdmFgyPqmJqffnAQxVw8nUZ44WNMttQWyhjpmzisWdD8YX5ZobtOZqifIbBjPgy6e/1ZbBOisa4xIb0EuQjtEAgLWSI4bZURd6OcFnP2P//rv0zg/w//+I+6C2qd0kkYKGe4aCyJuRg4n+mOG+6TYbwzc43q1lFHKUU5rgKDHMsy4fGJurIJNCg/YU5rCe40XmA1oEUCY9OW0BoDU9fmmoRsQL1X88q6mcYgHYwwod0traOe2lxpwoo8UmrRNqcqa1ufGAqpr4+ptb+tinxb+UQdBA3iO56BRx5e7xffkUa0lRekJxV0NisgtlhjzYUnEYjj64wZMmPMXMgBJtYkto4aCrbK4AN+Y46tcdk35b569WpxThZMbly7ZtGY12x9qGSM13lNEIH5w57qA/d02Ky7p6s433W+WL9mrR02IgwYS5cuLXbv3mMWUjC/+VhaqU33GihAvWhNHY76RVB2Snu5jg4JcVZ3FPN15ywO8+wrJbDbs2e31pq3dQ3CPRPYWWdQ/7C2kdbnxLc+5D9W9/jhEOaypjl16qT20ZN0Z94SRfYDVAgIOTzLvWqnTj3TFQv3vL950jf6S1NkPRiO+Y8rV7DMsVTrn/b2DrVDHVzSwSGs5zxP8+OQPhEA3qMn+wP2a+zlO1SOR7LCEXhPwdKKhOwdHe2yVHVae8crOix2Re+niq0SUHIXsQnsVB5Lo04cVAqLp7YeHVJeenvEHBKYeYwlTha9+fpRU6BxW3q3RX6XOEU/hQLvEo93WwOfRu5Ngd2nUc8fRynrV/PlZM+ErkAt/iaIsfZ88JlMyQxIc6TbtAI4CRVR2eiywPe76zCHOEknuFcUO3Vn3Nq160wjgI0DAj0YUsRtPAiOtNgYusAwDAVrsnB8JubHI2m5dInRdfbcmYK7aGZLoPPFvr3Fr371K2NyhhCjvshK7i6eb7pmQ3owUj4qXE2JrbAjJXg7YSWtapBT3nW0w+wQ9yn88Q9/LE7pxFxoVqIxMlNaANu2bSu++GKfzLksLxexDpv2RvsQyOAESyaBTuZUaeXN484XtA604KUNDWpR/ESnRXmHqWn33Sk26KV9afri25E22IYvP/KTv/auepOwTnVDvEhrgkM2RMrn/sB9M8tx7tzZ4pROAPZ09Rgjd600SWMDZjnop44cZeXUt3v6Bf9Ci497h7q7uouTot0l3bVi2qNCOHAHkJfV7xbiYuvjR48XM1tnFFMmTbWNesfq1Wr7syyewU7YeOmtxMPiVyL6hl6Crjn4vGy5f6N36i7KUROeEzxFIK7VhH3XpqqvhxTTgBMThgfCWk4ioxHB2MdhBcxa9utEJJvfY8ePyXSeTJKe1cnUGzeNcUR8h4VAwJlNaKZE+6SsMEzuaVPMadnbEtphGoVNK+Ni3nJoe5jrg3nOSXIEQ2gFINBFe3l6mwuJSWcMGsvZfyj7ZAn7MOk4Vke5a6lUpQTvh9JGIG/uBW1rW+XjuFqS562UylO9Vz/6SxXN0QgE7fIxk53PgxaprMD1vpMYS0T0iiszN5jlVy2ORMdZP7W38LEP/6nz8k/lmxXWyg4uiQKGsb5NOFL6VuDcjKqbIkKAf+LECQnYr+mgwlPdITilWCoTONSZ1b2SuSDEc2bsWiiGDAwxBO20k4BMOUiDUJcT0fzhyA+BHKadMa+zUqdwOzo6bF5DqIIm1DwxMBEAAp8DBghQEd7wxx14fMfcS3uMXCl7+e6EsDwz8mQxLCj9eAxjsIa3ETWghaczAMkbATP37jAXBy5VLH+jDirs3A98aSvWbwMxnnVZ0RZZY1i7EhwTSGo9Al355/61yTB3yn0uy2Sqlrvs7sok8cCABFxioP2kuxthWsI0s4NHgoOzYkb+8bSQsf8ACRoYHVIbYLzn/tLDYpDOk5lOtDC9jSie2sALcWuMFsxJzFl6IgVEOD9d8yqmzNCuNToZfEyPau2VGGEw/RAItiku8T9TmWmnFAvaRVtEALxhw4Zi187PTbDBeAVda3vf0LJSJsjRdEMp4HSpqGPziTQqo664DzPmHNpp0705CtDW0TDisM1n0mRdrAOGbTP8YOGby7UJeSQK0OQZh/gzk8IMJuY0gjH2M9grkPGSsbG9vUN3AK80k3P37uretk9k5MnL+VTrjd7evmLg6ZPimYT/WFlAC5+xxTSyRb9yJDG68lX6mKDoZ2kAzV8gE6Nat8zSvgEGFmuF9vb24je//Y3FudB7wUxFN8cla5Cv/JPTkXbPnuKvf/1Oa7jZOnw1vZi3QNpVcrT1NavX2Lryxg1pn8nyAlYucNQi+waE2w7P10e2PrAIVT2jSXzs2FFZg1ikg1IT7QqKqYUf8OIqhb1792jNeV8Hc+7aXbq+pgfIm3c5LTjo26U91fd//b5YJt4Aa9tly5fp8Pem4qzWYxcu9uoee1kY+QDmRwR297SWNIFdR4dZWunQGoq+xVp+3dq1moOu2/6Rw5sc0D106KBbptqwXvW++iWJz8BZ1f1LAmkma1KgSYEmBT56CjQFdh99Fb/vBfTJmAgbYAAAQABJREFU+pcJTNz86TcWOP5ZTufBGEveHr8UKAHHQzj51CMTk6jsX+jptnsggAtDk78pWoSwUCRLFiRoP8GQnKuNFQsXNIcGJfTDwVz6RUwm4hqK5htrsAqT5G1rDxhTLOyMuWFplRfP9O/JE12k3d+rS3yPmkk6GOyYedighc86nSQHD04xD3WNFjaOQx4S70Oxi5DRIYtdaJGiKvgIeDkd8LeYFtFpWkYkkA9FsI1bAhBwLNh+hsdrfHEccmwSobc7PbNX/FjkexuYYBvGzvPni5/275e2QLcxFmE4YrK0dWqr7mZaV2zXnU+fifkM8zYW7cbINdD6Ufn59zzVfTCcJ+neBmsHyhO6GVNWyMCUhWTgQemtvaQ4+KMhB9LWDsjDiC74gzB5Bz2+5BsIGBBCT5DmhaIZsxNG59nz54r9+3/U3wHZ2L/rjB5pusySOcwQBsdGh42WuVQNUT78And7t0j8EHGCmVa8dv26aWvBtEf7I/C05mBRoYqXkU807Y7KPC2CpYmTJeQRw3amTG/YPUecUqYQ5uKF9O4SGyTljl8VVkaKZClN9Vm9Of78ul+0dU+S2jDQlbGXg77soWw68Yu69IAEgfiKFnE9RfwSItc40MPSr8VM0YnP2AFh8hJEAuoKocJdaclx6pE/zMpd0UnmG9dv2B2Z3DtwWeZO0CKF8YcWCuaCw0FF2m3u+Aof4PdLIHtGhwu4k2WGtJCniWFbRiCh8ISZjgAGJgz9554YvD09F4o+MWkwlQPTnfZBOfI2RtkwQdiisTicxYkP4vOnn0Y0iGgBkyeCKRgL5P2ZxnYTQilvu49OCaLtpwo2ENCB+QEXLYunw/VnhFokj1i++qfHqPFMHxFSc2dYfcSsgNmrESDS5/kQJ3qX4VoHjzRmHlAvkR5TtgjYT+gE87auHcUuad6xKWdcMYEIEVPm3AuGSRxMEbWhYeeNUTRJ2pKJXnm20Is8ME1lT5ka7pEJnZkylYlAB2ESQhYEdpjPxI88FuhwAwLhxTLbyZNw0yTJgad38vC+6KWiXZmgKwSK5q0fa9cehzaKS1/p6V/RuxgLOSk+aaIO7Gj85+DOxEG/1y5lndJFCtJDecHWa8pCT8YEh816gDoy/IiQaItQCcE2B0YGXwwq30lWZu6CYZ4gDVEDJvljwnumNDXmzJ2juIqXAumjV2Tm9sSJY1pLrCxWtrcbjX08JqU7ayOac8I5fEfIsHWUDX8y5pO1GfMQeWPWMgRldipbDLlzEtS2Kz/M33pfAWdnVpMPmuAUOsIoZ6u0MNE4wdRl6YQG5sc1w1n/hHF3rvN88Yc//MHa8G9kHrOjvcPbquqGNkj7aJPpzNViGG3essXinzl7pgTpxE71kHzBjRIHPlnk5mtOASOSezA2+ByvdYn63jTVG1p3zCM5HUVacw2GhRxy830YCkR/Td3QYjFX3bl7W2aru4uOjg4bK6cXMhcrl8ez76gAC81avyLGeBR5pCiv+KjHYKzgUkMpSxDfpA+Yud9Y4Y4n3kjw63CIqPIuX+saedCV/sAf8fBjjkNYt1Rme9Ewuy+zgh5ezRHjwfpDjct8d1d7k8HBp7Y+5GAk6w3T1M3nJCMwP9Fi9SZ6Iug8qcOHi6Spv1Vj/ebNXkewAGZKi7G9vd3+WFtEXVBbFZQPlXJvGW+jf97Os/wVxj7ixPHjJqTZu3tvOf6z9GIvt0r1sFSHdyorCvQH7bepC6pMfcIUUgFrHvL0qrSMWDNymPmgBELtHauLPXu/MH9+qOfNEojdvHWj+FF7267u7hQG0hmQ5Pu6HtGeaIfhmPs4yIhJ+O6uLrMmwmGqTRs3Fac3ni7uaj30QPuQkVzAJU4Oe6Q0byqMe8W5p/D69ZvaT96zfSUHxCZPajELCrfvbCiW63D78WMnVdaHdqXCxd6LZqklcGL9TN3a2tVI5Wst+6WKcHoG266iJgF8RSS+3w8X9ZLX1fuB2YePBbRt0vXV6rFJv1ej34eUuimw+5Bqq4lrAwqwCGRByH1zCEcm6sT5k+K4FpR/+fOfbTE1MPC4TAeTj3VBi5hyLC64wBgTUpzehnEIrKec/tM/W3xYyqELCa05FbcEay+x1MA7gnxhAn5aiiRm4tNnmAc8U3z/3ffFmZNntHiZaPl3tHeY9gEMrUhfZp/BxC/ysvDsJ/ImnPfh4mVJXvo1h13imzzL7/LlpbN59YQ5ooJmAlXagfwnyNwkJjh6dJH2SZl4uCptx1nTZxbTxKh+IqEtbWPD+o1magwGMozRQTu9jxaBlqH8AVPggoEcdY3QgjaEgwzEIw3+OBgx/JXtyPB0mJ6Od4tqP56Tw6J9oulmAjy1/cFBCWEE644EN2jVff/9d8Xvda/R4UOH7UT25i2bdfJvs5mvQMgHPoPKGPDxV+JRZVn7BjJKSN5oQVzXibtr0mC6r9OO3gcRgioc4MnxGosyNKjI8Zq0e9CCwFThLN2rxX1X87TRwdxJLD7ychuMAPhanxmiBhdKVM5wUGHyWF4+fLK4vIaXnvYawXzkLvzDT9/Qq8Y7Co+nAm3IUt0iQIDpgakV6M8fYxUmL9Em416J853n7U6pC9KqQ8ONO8bQhCAd9QBytKMWbcIMnmDWO6/LCnHafLQpzLxu2bTFBXYAgxevcY16myjmeQjteMf0JocmMInIvQhumtL0wWxDF5t2iosJGYR94WCzNHTDeVO2zGF66pzogebRho0bTAgzebLgp75nAqfaJJYjWnU1TnFM4FXj6XXsKNYBqYvX6LO2RdXFGCe4sUQv49Ce9Ef9wiSjjcAUQct1u7SIF6ueENDlgw4ab3OkKcc4yJ1jMZ6BtbWnhL6DVjvQ2BJxoBtmrOjrtE8TLjEHCiHu2MSEKnf80Pe5A23lyhXGEOjo6NBzuflxwADNTdoVcFukjYcGHu9W5Wo8NgbTiEBCzssrKuvF3vUTc6/HAPd4q1IAAFgIphirnopGz7gLR3CnSaCpTMtEKSv7dlD8JoZUwsDGMiLyp04MvUIYhZCO/olJJZ70Y7Ts1+okM2WfqPJaepJnmUEH4nEn0nS0bCqUjIHy3XffGYP418J/o7TOiF/jQDMvfA48RQyUeXqphIPKbsI69euYvwi7r7q9cPGCMc7QoEbrDjNWoqTHM/x8jWb5ig60A4SSds+h6rZymh0YS16kNZ0COGDwpz//sbh156aSa3757UTTIGQOn6y2SZVwT+JytZ0Nm9YXSw4ssfpzmBnhqkyqN8qe06IKab5lFKDdogVzV4J96EX9ojGNwI7DH41ck7SNqPJyfgg2mEtPizHcvqq9WKvx2saUIeCix9YGNPatjfN2v0bpl28XmfHlNgIxo80zLoZjDJ+r+W3demmhaM93/vx5E9pF+Kf05IDHwweP7NCeadNrDz6cg5Y0csZ5b+x+sBLz7MePnZCm4sVij8zEMo8wt07QwUhMRTN3rtDfSVkRePjwsc0Zw+XR9B8nBbxSbK2C1SK0sXq1rtspAdukiVqLqKpYJ8yQwJS15PwF821dYHsPrSSoSvpD9A+m3lhDUt2xPiT+/XsPZXLxrFltYH3EASW7okKHvVZoDNyg8XCRTGWyvmEt5SuVcZZnHNFjLVbia+XgyoEB0xo9p7sVL+nOvbVr1xVbtm418/DQhoOSlOe9dtbXqAwO/D636wx6e8H9qpkan6Y1Ggf20J7s6JAJVL3fvn3T9p+XL13WvXa6S1n7TLTu4zCXratEI//nPRgalHWvPK29ZH5OI2IYQv75jn+jvt8xGh9l9k3afpTV2izUG6TA8CumV8w0OmNMdK8I7pNNHnSEAB8nLfPJOaZzr+6Sl8LkLi8LlWc9HTCtgUYcjDzufIPB3N3dVRw6fCQxCnU6XAvJWArAqIbhB5OYzRQn+pfoj5N5MMFhNJKH/2HCDeHdUPqDn6LZHxhHXZGOxaen134CZrsWQmggIYyD+dEtDcATMr94V6ctyX/tmnXSKlho4Q4rCmxfwr2iDXnieEaeBJcxItyjVf6kSX7DPTzcAHs6vZb5DZOIcMpK/iU++gZWpCVpiR8fozpS48aTqjZu4AJu9Q7sCKe+scl/U1ogjyTYRQMHQR2nA7l0efmKZWIcL3KGbaCjbJxR6kxYhFDu/InZD9pXVf8ez8uiOBFdiXiFSWmbFcMTn4Ar4QF5yYf2q0jehrQ5DYewEY26CxI4wvw9q00U95Wdl9AOG/Ocip86baoJ7Zbonp9WaSNYv/IfgRF0vddSLqA3eCo6Gqi3792W1lW/Fu63rb94TMe9QSpvF7Zjc4EIDOpTJ08W/9nyn7Zw/+abX+ueojVKiuk+Cd+NJvRZ1dQw+JFbuKjj2pJQxxHDX6Ip8DW0fUD3iO/QrQ7lBT7E9zoNWB4n4FRprdrAPGv/ObaRhz8DJ/vSh8VM0SkPAjdMPN6V8OOGNBqvy6TlLW1iOemKQOS6Nq9satGu4wQ3cTBfyUlVxp7cOU4uNKulVR6r9p3xtFNaNGjOsWlbo1Ov4RgrNbraJ+VnLGaMo/1zZ9rRI0d0cfzSYvv2bXYPgsjojnJmBGNspM/RzumDYD0m/AJeWW9erw+k3dfd3W0MMu7TY4ydpn4Ao8jaivI3+BkO0GZMeaYivHePBrQYCce79+4UB6SF22ZmCV+IwfJ3RhvaM+SkXSJ45aACGnCMHdHPgGt9gj4tZ/HtzQWcwKhveynYH2KyPCoe2v2Jt9WOb0rwD8Nj7ryzEg4usHkZU2Iwf2ZpHEZQDA5oSyI85M/Mr4rpB565I1/PO83DalPBSKC6rb9aGR33KJOVQcAQJiK0e/FYGnDCcxJtZbrHtXxq8kv9NSGAwCruRYx+AJ34wz3T/ahoHp6XBjRMRcbrS2J6DDx+Ytr1k//XZBNUwogK+oF74AhMTrHPnTc3mb30uYDwWxJuHTx4SPW4sFgtxtE6/QlMGrcSgjUPcOIv0YGwRMwY04BLqK9nPK8qjLWRa15xCOO67tNjPqIv28GoBBqQ3rfUNkQfDRlmucDMoVr9EVEu5e0f/gszDubgsaPHTJPuge4i/t1v/07rpTWmpQfu3PE6TSaW5+leP0xmTZUgWIao5LI641Pwyclys6C6cOI0XUMKPHs6aGaUaQ9zdMimTQK7x6rraJckSrVo6elnTfcSFCjpVr5Yn2GM7Ozs1H6mV2NSnZC0itoww1GCG6Z5Nc+XyZE00YLiGXDiO7AKf75HCov4+bM+PmE5vIjbKF5ErY1fO2z5WJ23/9k6dLJKWs8rVvo9n3mfidw+lSdre+Y15snnk3WIzGjPulq1kJHVXrNv6MO8w8HcW9qrYeadQ0DcMc6fpgDNURPtblcOqpyT6fcLFy6alYeYr3IaR+3WZZFHab4HBYJY8ZQ/8/tNadd3dXUXvTL9uFwada06OIPjmhFMnq/u6LB6uqH9CjwP6tpnYIs25Ie6sPVGagho2F+WgPbChR6ZV5+jA2Mzi9YW1bXgIzRiTT9rxiw7lFhfx4Hqq9Zv9O16+CAffqxR+rR2RYC5WNqfmIHHLOb3MhvKPgjHGo74qWjjHrUMyBv8CTrRN9lTdnZ3SlNypayptIp/IL6UyjHbTNwvM55It8JZ57LHo59hyYRyc6gPZ/WYtp4OW7+8WEe3KPbpb83fJgWaFGhSoEmB0SjwxgR2kTGT1Ke8QA06NJ8jUyAWWB4rvnyqj69ytVMHioUgG4AJE3Q/mBZzDx7ctwVUX3+fTO8NmrClFZNf4hSxIHkKE06Mr9laBHLfyzKZKsE8E4uqlilwuDwDuxNIC1PSuPZeiUnCwBdg9e3bvhWVJyk4JfhceAiBYlLrJC2IHhQ9OiF4oadXTOppxY4dO4tt27eLETfL4JK9LwaVOuFCGXFhkqzK0/1TNI9DPHsb/08sqAMecOphRZihpP5tdLKVqBikrE/TKpd4seC1e89KdEoIpc+beIkFNbCdXspX/w0n1fWA7hG8fkO29sU4vXLlsgnviMtCdPbc2UVHR7tOy20WA3RByXBVav8HEJW5zEPf5GH1lPKw74hj9AG6HMUnPv8SM5Z2C0icmptoKsExLwkWZvQmPFcEz9Zw5U4ITHKg4fDjjz8UB38+KAHJMbv3h3sSMQs4ZQrCuiXS4Fxq5gwxcTEos5oTBVdZe57k4VnbL9sqd/GsAsEZ02fdXRKGSIiD0MiYs4pCsfhzaP5mn+nHtJqUnr4HQ7q/H+2SW5bbbG28EJq3ScuOu/kk2rZUYAA+NKuAmGOVv6ds0mP4EItAfaQ6Aa61D17kh7fVhUBEnZDG25C98SNHApL4s4qrhHg1QsGjWhhtxbQ9A4/0pN6ByXNAGgz3RG9OMsKsg2nX1dllpzox+YjWA/UcGncwRNDAYzxE6kV9hbP8QEzjIGjwPRYHzOsSAp44drw4L0bInS+/0l0Ny02wBp6+JQWgIPKtQYA/8Ed4jGbATW3agUO9o1lntKwIZv4IYGDiw/Svd1UpPKQG8whMnnxy8rVfm+gebfa55wsNagRObCjBgfwxd/hCYzIu6BQoqRjuHx76inr2kNF+E1IcI8Wlh3+M/zeKWNMg62qwjDNcduDAnyJSLIRRZ8RkoO1gwuiLL76yU+rR9jiUAJ0QYKENhcCz7APyZy76hVvlE7GsVWnsJE7QindPowytfVD2WmLA8LghgfNtteWJPd3SLvODLdQTYwUmNBdJQLdq5SrTEl69ZnWxadMmM42F9h9ad8QL3JAhRjtkQgphHTnX5F1VcpDFCAMs2i/t9YnaItr4ucvpnPvzztwBPcMx5tHH0Wjk0A4HQ44fP6o7YL43c8WYk7whE0Tch3Lx4hbTEtuzd7eSTytpaBWWAEITTIuildiGSUwGcTlMKD/R3UCXJbTnPtYzp0/LdNRmYyChLYjpyKiTgBf0qsjgdUW8iEu/iPKqKq0OQ+PU+rhoTZnRtMM8Um9vr93lAvMOOgYco7vgPhfDldqH6cZcRJwaZ5nU+Bj9EEZ+9+13xRWdWOeOVjTg12uuQFinire6x3w1AjvMZtGWAk+glWVIoMc69tVi8ol9pW4K7TCJ+UiHf6gv6DtNjLzy8ENGlmhLmVfz9RUpwPqKg319ff1q/1fMjC79iv7r/avqo/VZpSq0DjB8rPpUb/q7xKouI/yjp9YFvbbPPI/h8GiQmUX1+MOmIsACKYOPMNQR6xqsDHDYhMMW5bjrkRtk9nF70WZtnap5ww+ChECDuYS5p7781XxECFq9HEg8pPvspmm92NHeUUxi3y7H/bi7du02Qc+ADgU+0JhFXrl70y0sz+uDfE/0L6shCKaKKf1UMDuQp7vmsIygZm5WaCgv6wLMln7++ed2kPCgrLywfzAXsPyr/A24Np6lBsA6qv9SX3Hk6CFZE2gtNsuyR9t0FwpO0/qCa0TWr1ur/QJ7pPtlPZNFnk3ALjN7iRcfZ6uE+TcHKhmXDx88aHcL79i9s2iXsIs9B4cQq/0MWPnu2t/4rKVplcNbfMsIRLmo15OnTthB5VXa56EpiWNvZnf0yZLB+fNntCe9Yvs69ncndfic9QCH+2J8S4OhpY0s8nqxgJqfkUNror6lj6osbynDTyibJm0/ocpuFvW1UKCWE/FaQDaBNCnw9ijAAgPGIYIJ7oLjImLMTSIMQBMIZ3HEsJJRLQnwntop9zalWSIhxsaNG3ViaLktRojLpvbZ82daZD2xdJjtmiyGIKYecpfWlLUrw4ig1Wvio9nmw0xECc5jLSzvynzgydMnheMpmS+8VXSs6ij27NkjDZQdxiwFBMsWFsCxfCGvsS14HIFI519v5pdlJvmwqL53/4EtSlmccr8OJszAN/4Cn3hGWV4JsxxIAK4DOIy3EZbFwmO1F7Qcf/jbD3ZKkIU3DtCYaPviyy+Lffu+MO20qI1ykUGlKAO/ezFCEYLJkz/gpEZiaZIfIemVnQ0AiKm4YnwLIGEwYSdM0NA8yQvJJvaZtP5gmHGqESEZAhw0AxHWYcbzrExycAF2f29/DcO4rbVFzN25xvA2YUXLJGv/5MeJVMdNWVvu/ozSlHiGd4qEwO485gZ1nwSmmkywqDArr1COcufJ4t36YgJM27kvTSjq4L9lvrNVi/1t27fapgDcCAc/否CGJ7DJRzSCauacRPEFBe19KO5lCsEqo6e4eOhPwhXS07YRPiBkDs2YPEWV3gEZbnmEBu/WFPQD3YlvgnfhjrCbAwRs6jAr8kB9iYvK78m0KQcPEGiiLcf9VNcknMX8SH/fJdsAc28dNMoZ0/VZBz1yf8o3dhdMQW3Qda9BjzQ5z6mdrZDwBI27KVNaDBRlgpk7RWMAG7tgxD+S4KxPY/ElMdoxz2lMXqWYAKM+9Q8AIIhoExMY5tagDjeYacqxopniWS0abdHSHDRmzdUrV00zBBq1CFc2+uCAUNPNLRn6VucNaSUcx1K/DuXD+WU+oy/yx5iChu5FjSWzpNEGfRDyQDMbz1SstjadZFfdEGZOdGbEauTq+39Jv+i4qi9vg37Yg3x0hkDqZ42gOQPo2tVrxhTh7r3Tp5eZyet5c+fJxO9imbKeVSwUgwhm3QKdvEYrDyFejBtWTnK0ukxjiWXl/ZfXeLM2pG/GYNPcpy3SVpS23uEXwnXy4i+EjPgjEL51647oe0vag5ds3MZM0hnN/yd0SKS7q0thd+z0ObCvS/MOgRvjOgJSZSqYtQIt6gXTX2iTEadclyguKIITh0/+9sPf7B7AfV/sK9rb260NE0acsj7qC9Tgm1J7GgKdOkErxkucCRQ0NpwVE3WF7mqaKaF4q5hV0MPmBvJVPH5JAs6MD6wXoFe4Em54ZE/MLzIWIvj8v//3/5qg/R90ar99TbtpG9J3OXSF5iWa5ND80cCjkokHqJHgZ1k1X4dQQH1ddcjhBtbF1MW0qS54HU9bGgK26TEGCkB7rUYGJbTTfI+2ykPNqfl4lLdr75HR1vOQMWTVjDICBYKyI0QhSCSPQ1jMH/QPxkGst6A5ziFRvj91Z+1XJH2hCSEOneRt2iYM/xlCKtaRzJ9o4C9ZtFiaPct1vIU7lX8xDbudO3dIYHepOCct9i7NsbhY2zEHUZPNnmFkGf8Pi4HkuBv78OGDWnfN1x3pn+ke2TUWgkUCNM12795lB2K7ursrgZ2SK9idnvQPQHp74EVB/Mlh2aOnu8f251hX4J7aYsI8C5vRNkMCvM3FFe2Dmesfd54v5/qU3OLl7+bxBn6wCHJZa66fJEBe1S4h4uaN6uuLi3Vr1herlq8szj/t1GGqodZO3gAqrwyS9RoCu9Paky/VOuorHeILGnLP+EoJ8LZu2SrrOKe0R71vB+3oixs2HhU/bUnR0dFuOER/s4/mT5MCTQo0KdCkwCtTo否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否rMhZA+dRBQNlLNQ0PDyW3NIQyqIwEc7C89ybInrafJPD/zF0DvjrHdI8LS3/76NxGKFtuehHpmEBH6b4wrIOghIIGIYcwigfqWsa9MQk97buFpczYtI5+LlXGKX6QpzsmQd9i/6T+I1RLLzZIukMRpXV2/bDeK2C11jINaI1GZuW79OrNz0tjQZGrImIdIfjuDBVVjOyD/dBXP8uQFP+pt89qAFQEsjQkyljH/dgHrw3Mj0KH6FYIc9kRH7o4Ywe5S7yVJ1V0w+5Ks66wbVoYajCpkd8x17I4elHQc43TPrj1WZ8LZG6yeSmPIX80T1nRjzrAMArMHI5n1h3UEpg7UUn3z9TdGyHL4xn3vZfP0ZlofBMCHnLUBgCZDmpyy+Vl8+TvskAxGyntV88rkB0mms2ZljnZSuApAUmt+ug8boxVrmP5Zfvoz7lhRWpO0k1Q3EsiNSxrtbFWndXZz52azadeKmqytW7WG3pA00pD1S1aP4svoLi3GKL8FgRhMMJk9FfMIyHHGPC7sQRBl7bP8M50QiBjZWAM4892Sndo7OoMtF7MC+2bRMdVK96EgAPBZ70L5rGe+p3H2QU0e0kgwXMEwOO5696GaMAvlxkN00jBQIvZv7k9I+l67ftPsZ2HDeYWYRWEe4k5Rq7v9WsG4VXcICKS3tQ8QFjOAefnlkjX1zmY8A7/X6ofXsm16Xf0wIDwA99PWhS0WVrtoke49K4whGeYdNFugXhzHudLzCP1Zbd+nBNmY137D2R2GMu4eLRs2GjPTwrqFyca2FjEw7NB5da1pz5BJ5MzNZr8yfrn3YV6jV3dzNCcst7a3Jl/s3Ztck6336zLP4gzkrA+Vu2pW7Y/mBW1VMJDCPIotQfqP8+5KSQl3PO+QTc42u9fekFkG1M7CYDooLRicPVGLyV6VLYLW3o+maWVFSgiUECgh8ElCY否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否DEU/KFULi+OV758nBCo4EjNiV35cGfPO6Xv0KLBIvZ2IEENSawjXOwQY1NMZoUxxX0h9nUkhpTh+xh2cjVuFKIbrCwIAIv+HZIet6/QZpVucfPPdt8n30j2OrSPiQ7R4pYMLp5SMeJJWlEvDqAMHhxEdULCh93YhCMM34voa否7zy5JIw62VeoS1IgqhOg4HLj9ru77DO3nnsLGIhGVHHfvIDkEGIh1qR1copLG6chQcE5ahBA/2kpmzILwgaHEYNek0qabA1hiqFSFuYezbEbiUE7tQr9iHd4/jpRXDx/6uTJHmk42lkC6HZV6O9eO81yYBMKjxgoQThCEuAovUtpU6gHa0dwghvcE4YR32dIHD3POlFAtXGCWEOoVYef10qFd3GMFPLxDscHdFvD0jaY8///nPyZGjR40w9UxITpCLtTVSHSZEsakaU8GWtwqljpNxqBuDWE0/UT/UpZmdE11aF+gCC9EKCRouE6grywadMg9HdkoMjn4nHxo3T0hrDuCofhsQZ/d1GQ9HreG0OCtSpas86hzG0Ty7XJ/rPieE9cpkl2wSLVki6SgRDJB2wVE3ryP9yAUO+wFI512RCkT695I4Mrk8oLYVKQwItdbnShun93eTAKGTqYv+IcXiEDHlm4RN0llbkCCpEwFLCH/PiPKRToR4ZfBVfhBKRjv6El+IVaH/bczJp6IWcZ3SgCw8exmd+2R9siyicqgWNgxBykOw41K6SxykVBjJtSCplJNSiU8+ECi7ZJuxTjDp6OywX504NulH+rVJY3Kp1NCyfufOe8B9/DurmQdYPv5BzwEvYAysITBClIUbOGpKBTBH5+i5fUbPtJE8RrU39YDAg62Rvr7+ZPvW7dqr1kqKa4HZhVm1cpVJUcZSUUg2OQqjArbTBTavaNr1lIZj3FS64ncUqiQwLFy9es2YFk6K2AzidFg2TB//u4diDFgg+zcNRgDD7i1rLkQx1m0k3AAWcAHpwhp6WwgK7NexRiAlxjhDGtjqpHqF3SAqv8ortc2a4MNafhDskKA5duyYIaFQRcq5BUcaonIWQWU3ey9Eu6diPBnLkR+2UPeLw50zx3JJHjjBzjiQLGFaAa9HVDFbd1LQ0r701dYnI3IqjfuB1EEC6KwQ05yldqlsXFi7GCV5ZNSsst6z9wVHuOckn6ws5rItLxYtq5pBgvWRNGhaGJFN3d9s/Ychg2DsrYAgcwQSGcg7c1FpmV/5UkLgY4BAxVygQhrnnCcf21kHJqRHWqfSEayHzZG04tXHNb7x6E8jj3p4nOq5jIo+yuNd0xUz8ny8PsXwat/V0lTzi9N6eOxX7d3r4fHTJ4CXq7iGpH7cBW3NVD+xZqNiGSISKjGxpWpIcCUMa1i1MvO1tXro3PAtzoX4m/0NG2dIda9fs042W9ck9U0iiAo0EIw2SSvArl27TMPDoFQrOuEhhpz3LH7eu3F4+R4g4MtNBo/Ig/MVd6/jx4+atp3NnVuM0UjDPmkUgxDnSGxUc46yu4+nFcDj/szyLryAS0ErSW/vRamZ3JTsWrDT8l2qPF3zg2srKiStWPXep3+tyl7vYiHR9z0xgkK86mhrS9YJRwIT+MU+SaCd7ZJtiXtRzHy8MQbTZSOEv09FK0oY/8OKGaNN7DdIBqKBhrnDWZQJgoae9o72pK2tXfeC5VYA/R80WtzWmeupTFk0sWWlZ7/KOth8s8ZSutVAz3gWEj/293Me/gU4Ba/y7xQg4PtNOJdPIeEciFrCZA508ifaxBkj2H2i8CirPYMQYH/OzwVszr4hT1BoHFXvgSgRbEbdEeIYIgJEmKdSx1QjhDHc8DG3fFwMXF5btm9OVq9dZZzgvxzYn/wmabHHj58kbR2dyVf7vkp27t4liYxw2ADBy4VgoZDvc否bMU5EFZCGQa1USg2kNWoOLQLhB5KdTREk9fUb16Qm4VJQRSjOs60iFq5fv94Qfd5ygwR/lCY+vI63eRAGaDIXi0jhmR6IQCqO5SzEg/0ZReaSCfIS5CQcihcv9Ur64rwOcDftILZEOupRhweyHQJo3r9RJlVfrcVWf96s6OAVYlepSwgAOlG701ty1ejypOkL5rHMvRJRRxyA164mPRd7zE6ZE0pAGsKh98c//VF2D7Yni+uChI9Vh/zjelGJUfCMeyytpfWj3kmuH5cN+gtk5q+//Zb8+NNPZsMMYoJd6HUwBgjhIBW1bwyAkhfjnPgcqs1RL6ub0uvZqEvruvXr1DdNRihEqrCn56LZWNrUuilcmkJK+xuXTQ0YNxBrcVxyR8QdeEuSH6g25MJl409ihKNbb0km/yetPglCHYIHF7vzkmj5H//zf0qlyqCI7Fulzna5Ed1Qf8OcRS0U74zNe7KPBuEOYh2Sc9hRuiM/LnnVLunjV1BwVTUE4uCiOo6fLg+1vlG/Y2QdAj5St6gPwUbe8xcRgn3MvL1wr4MiWv/mZXyotye6kCHVc1HSllzmXmmdrEG9nRBSbjeR8RE7+uCGiL1nJXUI0Zc1BYeNvxoh77GFxyUPJEulI5/QdlvX9RqmRXXAMXZtfkC8TtdhxhLz/+nTx1KRKkYO+eMsB8ssLcN85/Yfxi3Eb9QGwx38jWzZoQazTtJZS2WjsL4eG4VOZLFla/YBFnd9OsxihGkcbJWTB+sUezk/EAogvGsW1IrhR/ZVhFxZrn1sx/YdUvPTGaSPyUTrnBHs9IrkaGNTQ9KgH+84ogSkrH1Oyx/WVmzyLpLdzq1bOk3dl2fsM4p9uUFSGqj0BBmGfUjaVc2x92DHjznRInsv+/7wB5Pa8/WJvvSpWrmWM4/IUdIKnKn0wee8dE9gEtqrw1+hIG1QMQw8sTsT22sp1o1kqHumLbjKss3L/rh0cvAxiKeBvOcOZgi4+yHO457LxqghBvMoFW+VqSuCyo8SAh8EAr5nhnNQXgXzt/N9UPWNrWM0HticF+GHtc/T5qmm8hbPBt7TST2VLD7quOO1h7C4/V否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否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否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否Ov37xhuBhp4mByWS8p2c3CFfBj7kxdkkTqnZ8zBJYNBHweiOOPEeGjOYKA+RjmQWw3D3UOhtWtu82+Jl8hD9WOofHxYyrT/Z1t/Onms4DCFeA2OCbAxDuyFCZlLb4bWS4GKD+yltg+qfSJBDuYIvbLxvZezblIULN3qlySobV7+l6FWq5PdSBuCAYI2v3aS8L4u0OMNseOHQ9rxSCCY/0+cvhw+PWvfx2Gh0dMrey7QvtIw8Sy55QQmAhPQEPAnd5ekyr79tSpcOrESdPWw/nMLmkM2LQJzRrjXTH8avCJ8aH0WRk/EtPCuXPndIbTJalVmUDp6NAe4ZDUNH6jMx8xJl+5XEZt1Hc8r9mOsobxkww5L7rbcyds3bQ1NG9rDt98e0pnXJKUvvJcc0pk9i0LWvcwV2WsS3bC10Z1AdbgwrfFKHlDZmVgwgIHZY6DcLd9x3Yxe20Vg7Q0NLz9aNKNqCy9ua89fPPNN7Zv8wyr8zzmR/f1Wvp7ek8AmXrb82TxxgXOHhkCGQIZAosCAlFk5QsXlQm9PAT5wnnn7L4eBNTstt2YdDklkFZsiGdvRTwYknrBYYncv5PKM5zRZUR0cMfibv2JbSn9ynKIeF2qVACba/vE9YOtlO6b3eGnH/8S7gl5M2KPKFerV6+2w+BVet4gxGOTiHZrsLOiA7kmqcJ0JzqcuJ8+SlLmg8r3VtxcA6bGAZUAHHa9FyHMNnAl0lFtrSkfxEYjTFHPWThiT5SC+5MfnJsnhBR/992vpPYjEuxAjiCq+S4e6R9USF6+dNmkqppWrjI4Uh8uYIQDtkgIDQ4+CTe6b4S//OXH8MMPP4RuIWwvJNnmDqQcQuZTcZABI6+q3y2ckuSQAeLnJ/IQ3JBoQxIOotHwyFA4c/ZM+MM//yH8+U/fG5w9/fo7bUIZKStEEnSqv5L0Hy2BRACX5UM99I/9vibZv2paAfE3XryvKiTNkMiLV9WfPM/oU311f79bHywqqqwcxP55gru32ASf58jb+p/GDoeiEDfoh6kDhhDpRtV2rs6TOviYIqzXiXt2DSBQNCWSl8+ePRVjwGPNX0MaHy9NLQgwph2yixCwIcmPHLBh7A88GTAGDQyW4+ffeObvvaS7OFDBziYMCdhDaJY6qZRgxzphfbWIb4nkn1lDw否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否w0buvb4x9PJ9wyBDIEMgeUEgWVLsGNBTxGI5dTos6kriFG86Dq2xJbJRcSJxTX+eVijgTiioij+yB7S26G+LdBtjp0W9Hujpi91MR5xC19L03N1z/h9nZAykCC46R7J5gtEuxfiCOIguEmEKzHYmePQCA67EydPhMNHDps+dUcCyQh1gRwm40BIICJev37d1AFgNw3VmkY8sxDT/0nrT8mr0jdOo6plBWM4BGNbxDiUGxt8XUJKu7oOSsouGgImr/dCjvjOITjw+NvPP9uhHByH3gZIIWKbZzqOOB8EDzZCXnbgA4YNYbCEoSdGo+niRpnja1TLiU28f/nXfwtXrl4zO0HE5QCStknTIQ5EPKRxrMwKh921G9dv2PVUyONEbREh5X24COUF9zJO8+5Q574QHNWwa7ICAXu1TyRQxjamJz0TgeSubCPdu3vHNjfeF/xeXz8gV0Cv/tOyfS/hv2whMPuKM3cwrrF9+YF5iP5Kby24aNP+yHwMQZ+NLtLRHR0dYW+iUsZLw9zhV/SrWsrHsIfN9+UHAfpU2q/U3WoWYnvlR47+8laE5Afiika1D2slKlkhllVMOxZ0Xn5Y+3CMk2fq+0gCYFOPdRLV06yV7lCZp64/ztWOhXGfSw8j2J0/H86dPWfqxFis0VZQuYKIrjIZ/CicrgZZ1vhxSPpYuAfwe/36lZUZHKVFB9aoJG9SXdzl8emQyPcMgQiBOKY1Z2kOKFXwixELO9JGCB8dsf2Ow4v1091EY7ORfzkPVtE9meTuI7RhCkU4D5NEKx/9G5n45X5loGk81MYBRvWXTUIN/OvD1bwrZ0+Zu7kCHu7vGdm8WoRG6h8GLgRBUmEQj8OejfCWVJEetruwybtWkjbVhFl89LxLGJUe+aEOAsUylPgCwwhH1k2YO69Jk8sNEXOej4nhpWhYVGN2dXbKlt3vzKYdWhtS522X+uXnxhCocKp09lHYojszd8EEdOXyRZ2jXBWhZ1g4zGqprGyTWsxDpuKb8YGLt6KRzAc0wxlPwd2ip4cvgti50dXLl8Mvf/tZ+9q7lj7qNr/59nQ4eKgrwMg9n857XVG8cVnZdxUeVaz9/Q+N2YIzqf0d+8Pe9jap8ayVtGW/Hf/GJfVFPCaqB5mDgz58/FCEuDvSzKDzF9Vrq6Tn9u/fH9r2toWN66W否jhse6O93d2798Wgfc3qnEqD0z+8ljpBUsqOY8YTC6ZVrlqHh1+1X/JbhkCGQIbAUoHAsiXYLZUGXJz1iEt/ROpAvmprwSEYal3uSdpnWIs/qixYxOudbZDY+BR/9d955wCLC6QAqQ1UMaGqkYMnLrjGcRy2bROX/MGDh8I+IRmoakodeYDsU2bUa8Kl90BqAFDvBPHIylKklcabz+cItvGIitVlW0s4fuKEXc0yaE75HMmlTBDZODA788svJsWY2gn0etaXHdS7Dv22IIR3YmaMI0KbEM9VgjuHhqmjzBzCr1yl72tXy+7PG5N6/PmvP4cf//KTEUE5gCdNpAQ3rN8gmw7rag4iwfpB+BXIkiYsRN1ecU0iQQlBFlePwJsnfaXYCMT3JfrrYPe7V5PxUl7Rk/H3VByIwK+np0d66Z96aLvTb7LLEPhSEHDp249SCaOZSvOWj3ONe+uLsT+yTiBVgK07CHZHjhyxDeLaOpuZ9UPgS9Uj57NEIJBOf5o7UeX4UAcsrJ8cPnCwt15XSrCbjzUmnYbp+3Bl9/b2mB1ZDoDXaa2EcFfmzdor/9SV31LPCZ4hhrMeXBIH/C0xTj2R5A7SheV6AFxS2EyQTr036zvc9djCfSKubA5yINghYbJVajFTKcH6uPk9QyBDIELANFVoUmBMoyKMcQnuZnaIE8mhOEaLwVpQj+KeKR5zZnhOHwLlbJrMe/i5f+Jdk2i9P9OyzcXJBxgcYS7dsFHzeFPFtFCTUH6ZFQTYK7LmnBcjysWLFyTZNVouYUaw6+oKf//3f2eq/FKCHeMlu5lBAHwlxVkc0Eas1jdwp/7+R6b6sq+v3/b84FH72tulGnZHnS3BaqDEsUPakWjHnbFU30bYtL9643o4c/Zs6Lnda2Y60KJ0/PiJ0NHZqTOe9TOr0ByGrmoT4YBmm1u3eowYdfDwIUkAdgqvXJ+gb/W1m8PCTCMpyutXo+CmdeLOHTFbXxFT/H2dL+mMTfNZi87UUEO6Q5pPHK+j3e8Q9soVMb7d1/leIg1O4ilw6vKt+9SgKHmcNgBK9soQyBBYAhBYtgQ7Q5aXQAN+vSr48q07a6Stk/GhQqgaIGwWPC6qEHO4mnRBOIMIY8SXp6PivhowNY3l4ZCiTIayEM7CJis6bczB1itxccPVbUQl+66whQrEouAiCEV7bzuEKLJp4vDt4weFs7NipPFE3NMfRD+QDYgb2IzAWWoETPK2D3P1E8FqyFvJsVkAmbw50C6CWI7Ue5uIdMePHQvHpEOcw2wiE0Zg否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否h2IGmmlm1vywyFVh+26Q1JFgO03Q5KKcn6StFZahnv374a/SHXjxYuXjJPVEtAPYYpquNec3SlKUZwJ04QQt0IELBzlR63Krt2tQk43m2pKSwFY6fugiHTYe7srBHFMaq8oOwg0dqLGVyLm7nxNDVo05pmW0KIopN35iY7DA8q4UpJ3z6SC8YLUknz//Z+F5F2XatExk3ajT+CogxHsdICHxCNE1dJB4NV32m9URo7hTLt75664iqMqTNrdCXdlHHsoEq/1XFZvwNX6N41TgIP2R40onI6MOWBXtdqyAk+u7AKCABs4l4At59ZkCEOwG5HtC5gP2Mtt37Fdhy2tNl94NXI/dkjk+5QQoLNM1mHsW9UBOZRA0g57tkjapcwujjNMmedMApB1kT34zMDAY1NV+ejRQ5uz10uFEgciNscT1MU5ZpIHYZN8eGVNuCXi2rXr18Jj5Wl4Ah9w5cDU8wTwI0gaDPuUMIbc6emVJoXH4Y0IdqvXSv21mHPWC2+pPaCzXPJPhkCGQCMI+Jgr5i3mHZivGKccVCO9gRPaV+F9ek/HY91w9ynG4o3/qQ89PsS4CaQoY8Iu1yjSNPw87+JOJexKo8ZvwMGvdD7jq7sSdEU6Hp57TJc0okYQ/+b+6R18Oe43Yth07wHc2a7AuIiVc4MN6RfO8vIX3ZHq2ikixa7WXXEun3RBSiLmxxlBAJvrqHi+K4bRAanIxAYX+0bcWrUBNsSOHj1qxFNsC7I/rlotZlX2n/iafz8TAuAvZ878bNcj2UHbIgLPiRMnwm9++1tJmnWJcO3MupzexBOctDVoFx+yjYqA2RKIgRcvXQyPRahF1eR+aeI43HUo7GndLWa/emnWRqnM3i8Z9mViNi否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否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否7XMW00QsbzyEN8ll+Xk6RrDTnqf2IEdhLUyUfIN5kIN6CDPY3zSpOT2z52SfZ5Jzkgp5I4kPpBHHtGd5ISKdqerVITJEuhGzs/msItgJ30DtKAQ7ro9K973SgtCDjTv2YUgKb2veZvboerWnPS1bWSdOnrD9THoAX5bd5rgVRrDDfhd4DIwL5fcUWPl5ziBAX4IY26M2uiHVfGu0jkO0k+l6czulseHkyVOhu/uWhYHBxPvnjAbTnJV46SYEg8Gly1dsjBw/eiwcOXQkdHV0SmPQ30u972i4/6Dfxh1NwwlMqU6zAEnRZMX4Hw8ntP30SsLuhx/+K+zatSvs1vnPLmnwOXL4mGnkuHv/rjE/+aw3PoXZ+xTDfFxCzHAQ8958fBcGpFISyTP65WoRjZkLmrdukzaREZvLxkVeQB7YOqbs165eDSePn5RduijhDWMF46qjszN0HTyoefeDaT/h2OeVTJk8UhzsFx4TkQ/V6Kb1SfWqxtoCqmQuSoZAhkCGwFeAQJaw+wpAXxpZxq1cugGsX1yLEIZApcgt4UBOol7yaMukT+pbbly/YWoS32uThYvpORomDz2SJj71ecmrxtlmr/AxJMmeLaY9wVG1d+/ecOToUSFue4ybyYOYPnTlgo2kT8IKR6Xz/MngoG3cyJdUXDKMEvHuKXudLa15/GmUD0gROsK3ikNztYhhJj1HQGG3b3WQiP72ixcvGmLERhVnm+cisbgtrlKur1MFR4uqSkeCKo2SbsLZwCKtxaZ4zbo1ZkPizp1eIco/CCGPKkXJBY5WrtKARpW1ZbB29Ro7wDN1Y6qbwV4bYhD1x7LRcU0Eu/tC8t7q4C86laJMg9LjSo/4ugx+Kxg0qCwfddHHabOnz56a6qQnA0+MUM4BwVRjq0Gq2StDYM4hUD9ymdO5zD92Y8sT5oPXYghgTmMORJ1M686dxuXuhWJuS+co98/3DIEZQ4C+58uLItMfbT2VH8Q7W3dnnOjUEazfK5itmUVw5mok7FAt+Ul2d2Bs4WBk3epCIn3qZGcVgvHoruG6Q6GLgtv41bqzQrgfXhzuoAbrwb0HZvcEJAqtAKz36yRdwrjGfnB2GQIZAhUEfMjZsCrGF37FMLOAEKDGRHhCAjjuWWIspLhiSE/Fgtf8+JhmZ+Oh/O5j/FMRyN4LT0JDsCKSh7fc9B3GwPJSSeMMUBSFQDY58ICL6ZAW+6zVq4o0vWAx0JS/zMnAAKLbqBHdnpu9UexZQXjBhhmEuVdSOwfTj0vOsXdhT4HatohbvA6v9Q5D0DvNSe9E4HNGDbR9MB8DY4iNFJHD5hFJ5bFHwcRDn5g8r2uv8t+H/nv4b//t/wgdHR0KF/Fs7v5MhcBfYGTcLE0Ba6VOk/fo0tYtvHTLuHoFi0mf6JAOQp79XX60K5pnbt68aQScjZs3hdVFYkg6Hj12NBwXIeGpmMLoN+7KPuweuqs5S1cMi/I9P0QITAQjVCS+lORVf99Ds2UHcb1ZGpAOHZWk3fWrpZYdxkuTLj8/YAxY08ovTbse3sagKsLQ5UuXw6lTp8Lvf//7sFm4UlfXQV2HwzBSXiLaayCXUb3LlB5z8JB0kbJLxv4Yc4PB4JkYnkdEuFu3bk否2dpiZ1T4P302Om7Mz0cZp1XNBhnTEhDjnjwZEk46LDXt2FbWSqKGQcJuX3t76OjoMEZhbAniaD+kKznLwe4dhFSk8HCKqu9xuPLOLq56i9/M34CafmsE5UZ+xP46zuduYJPd3EPgc+Ga22Xu2yKnODcQyAS7uYFjTmVSCDRYkECydDGpgphh2+zcuXMBTvH3Tkxi/XXbAHqyV+6s4NNwrINVUCFzttzHuCAPh6Qb/de/+U3Y33EgNGmTxBfCs+GiXGzoHgrBu3jpoiH0bNSii1tZPwJuULtplG7mQSarNXXdps0FKh426MALZxtkVYhN30txOsFBeO7sGSHED+yblwAkC9ikrvYt/dLgmcBJ4d5K/eazwn4USPILcfqfu3g+/PM//0E66s+L6wrVIuDFH4NppKeMSsPhzzfgjxpMVNxRH8pHeCTDsF11u/d2+OnHH0067LWQwsqRipc+KVQVYHk/CdYcmAJLJBxQvcGBwrBUwkDEw2Yk6n0YB9llCCxECNh0k0zuHMxBtODADcecAQftNm32UTHF+mJTQu7TC7E5F12Z4goTD4noixyCQ3yK86bmVh08zaer6caax5/qEBFJc/CiNdIcYCqk16+1NXQ+yzFl2jUF9dDgfPEZ9UlDYoRCAoWDbeyfYKsWlXBI2b3SwflHrVENk/Hk8j1DYLlDoBggti4WsEB67JmYsZ7qIPSNiEylSnkbfDMbUcVwnRTKJOvjmoC+t7NnjWDe/a/xgC72VMI/IdKxXlNK8v6kCZa9DDirMUSobhzux2cxVcofBlDU/LNH49AdfABVlqh8H5HtKhjSUBPKgTB+j2X3FsIdB+NIJCIpx76TPOr3l/Xv1AnX2F87FRUadf3UgHxvs7c9f077oJeyy7XLGEWRIkmdtQjVVmT2a5s3bjL1cavFtAjhEDikjBpp3Pw8BQQAXr3DrxgG4I3Xrl3VutksFYztZs9+nc4HcNjg+s2vfx36+/vMnMNtSWg1bncLnn8+EwKMGd9zDorYg9rK+zoLOiJC6cGDXaY1Y4vGRL+NhCITa7+iEeXF/IIjncrXvGxfi+eYxiDj8bIYh+8KZ+o4cFAE2RM636F974oxeyBG+MK/XnayZZyj4vy6zmvQlLR7z24jGL95+1r2SZ+pfrXaiL5wUSfMjjZ0B9PDE6kfffj4Udh3YL9JzW3auDkcleTkQ83Bd2SzDok6jvqYc5HKw0zKgf2EXWsEuyQ5Tza508JVC否85l/rn0RYUI95DllQzZELkyGwKCCQCXaLopkWcCFZHwuXEsRSP0OlinCGlBUYlS+3HLiyuN+T7m7UAEA0YONiwViIPbEkL/caf48bpgp5iJF8I/ThA+/xYKtV6hBAEDAqbRJz+vZJ1wrZtiP8a4nq37x5I5wXIRGiBhxO7kCwvDh+55uXNfXzOLO5e33sbok7uTByZUYVn62hZbv0sa+J/IF2cCggfmqSmhzVZXBoUNeQqYZBvUzaXlVtxpfS6+SVszZUsBTpoFzuj1owNsyjOkiEI+y9iHZ379wJ3be6xXU1FDelikA1QNZi3cpcSNkKgbQAxDqkBVFnE1WNiWCndnj08FG4ef1meCi7hx8wFl7j5hr6NYkviheHKf0YGOMiVCJnIofLHJCOqZ2w02FSIR+jOh9Uvn7Kkg2Lop2XUyF9fol1rsY4fZf5zQn32EuAcxYuTbNNAcFuAsf4wKVz2QRBs3eGQAWBqvtpXuUwWYcoErvjeb760vgVMvZb1LMN6GB46xbZ22mRKjVJ2dfaPyJcVfS5fJpJusClWIoclTDcgAN1cAWkgWDI2aSxy/gdETMJTCXZZQhkCFQQGDfmbGLw2SEOdMYahAjsA2F7i3UQxz6nCKFJoUrTnookyvnL18YkWPkNP30niq+hpAfeaIQl0tZ311ayAh2D8rO7xeVnasfajhTbSzH9wXSI2rwXsiOH1Bx25l48l/1afaOuJiUn4hb1RRUb7+xDuEOc445UB4Sz54qPrTrU99fUaeoiTRGC+Z8g9mNhSZ99LVJcMKQOSnIGDQCrsTNKSCZFhTFwC0xAdfUqzYOyXbVeh9djY95mFjz/zBQCVVPEmLwnPDVIUUI86L7Zbesoe01zIgKz/2zb1xYOdB6Q5pot0V+/3ue97xRDp2j7Mlh+aACBOD6KDw64JBwmLu4/uC81pN1h1549wuM3hz27d4WdO7aLUZezAKm5VSL84Rg3sT187CWJ8b3483whzqMxqU9nB607d4edrTvEtL0/bFH7ertyPmI5eKTaJGf11jDJpI/Sp1BnfOvWLfW/jbLnty90dnWIoHgnrOxlP+/1ZdpIIlpdY9FqfWdV3KkjexsmmXLmwJzb/6jf6rJThO+1sgO5R9qs9snG+FYxZLn0cKzvcLjZfdPqfOjwoSRPEo8t7dn43QJN/JKk4Y9JAd1rju/eHt6PJkp+qu8Txcv+8w+B3DbzD+Ocw+dBIBPsPg9uOdYEEEiJQHE7R8Bi0bV9CZyU8kGkSo4FjoUdgt0z3XnnYjH/UGxkLOA0f2zTY+nGCBU+w2IdVQGC4SGJtlfIYKt01MPt+O5tYUeNMiokpWOze+H8+fCXH/8SHtx/UPjCAZVg+5aqfuRijeLzfPx63QpwllkgRdIuzsCTp06GA5IWdOPmK1R5bFh80AaWzeKguLbeFcQtQ11IsAJQmR4Pjtp8Tp1QMcJhG1KTW7ZuDm8/vDNkDM5Wa1vli2F2CHm8kxmLJNtS7ton6WBhpXEFr9WGlfZaKT/nMEViZkDqZthkvVDdOOTjol0svZqaLN8XgS3CVQ8lXPT8QQCGA/i5uIvHNOZKG4CAikjZZQgsIgiwOYRogZQo0gSrdNjFgX9r666wfcd2zTPvTbXVIqpSLuoigoCWpmId4+nLuBTPQu0bEmo7tu8wfAbbr35A/2VKM71cDNVgcZczKZoi2gsdrmMnisN0cL6tW5tDi9R7Ig2zQuu7gFuEzLcMgQyBhhBwtK0YKgyZVy/FpCecHymv1U2rdfgbpYYaxm/k6cOuTNs9hCYW4bnja3f9GDpfjHFQyTh0FaKIyidwUS7W7fQO4Ypg+H/U3gBGTpjJIMzZ/lBaO5jnRkax8TRg2iGGxQAYbcw9NZudrP/sD0rpuyKvmnzwK/Km3H5AF/er+FDcosD2VpRZfoRlb2rpsQ+sDVaEbnwbVbmvXr1ih++nTp6Wfbu9lh5pcXk5iA3DxXZpCGB/OiYCJTg6zKTZzREEClDS2jDbjI29MGIqe0r63DZJ29EHV4qouqZJhIY9e01VH5p5IALTV+rPAeaoZMs+GYjzN27cDH/9+a9hl9QjfvPdd5Iy2xuOHj8RHmnff1/M068kFYvz8ctc8xGGKY0j9+fOG2OZPa875hfmxStXrso+XGvYf6BDRLE24U/NYZXOJWARsrGoKPXzgKcxn3fObYbEXP3xxsewQ8TEo8ePhHaVD2ZsJ3IZ/lfUtVFZgEtV40Yh5t4vzY/598nQE7NPjMkZJLxbNJ/tbN1pEqwwyWOnOOpaWmGqZlFJe+v2LZ27RVWZVkJrg9jOvJMHdXOXPrtfvmcIZAhkCCxFCGSC3Ty0aok0LIfDb18xi4XVwFn4ORwcxBEcQjXsuwgHIuSwEXkksXl0WLPI43xRJhjP7jwrey9gW+ZR8zHGKL/plWfSB3GAa67zQKeQ8D3RsLe+k5zr2rZ8FZ4DJFRg3JJkGJydq5pWCWESYQhu+sIZ4uQvNaUtPefsIeJnKURi0hDoUJ1w+tQ3UvHQYWqx+LJCkiZvJHlyT9xq165dDQNPBkoYV1COaTj4xqcev0/3FzjTpk+EcF69di08fzlmklxwi5lqOiVEHt421ozRo4BeLAFSlhy8I+2FW6F3Yd220X4utRAczjvSzncQ2Y/JhtbaMwKMz0vexfpW1fR29HblC32VjQiwf6WDUNRrwJ38QQSNSV2aiCc8aYT8MUPgy0EAdVhs8mEIgLN+yxYkdLaEfW1tuvaZauOht0Nx4lGxcnf+cm2zZHPyTvQl58O6vJzoBbGaAyjUu6HOe+PGDca0Ux0AM+9zKLwwWqNchyiOCoVqTJi2wLHWyW7TFo3drVLvnUoJLoyS51JkCCxMCBg+nYxvw8PFrIdWjSeyb4ktrvV1BDsP7lPZ+JpVc4aH8TnF7xDYyYt3cHC7Cpy9WmgjcQ61wTCKIQ0H3unSb5geYP3GH/WPSMBggw/GP8KgshI74sxzrPFxroh3mM5eiNhijInJvmx8Xab2AYf2eqahgZPDCn/qO+lBfppIEnFYTAlXRLDbL60uEOL2SNWdHcBb+DSSCHY6zLYDbkniPZL6TswMiIyZFis/zwICDm3TrFO0KYThh48ehv6HkrwSYWG19eN4RLZl89awf99+XfukSvGe9dOaTlFXFh8fNi7rvuXXCgLeDilugraMO3d7ZT7jopiQT4cTIm7v3rVHdudOi4nncRjRXMA84aPQhph+PA3SdPgzcvHXDMTu1zKGWI/t3KtXr4YDBw5I5eZBU1Pbpv3CZjE7IYlLuzmDcFXaL/NEvVAH/l5SgCPDo8a8vHXLxrBVfXCNVOTGcxRqbTX/MoWaJJcSrkl5OGtDShAiHDDu6uwMLStajDl+p+Y0zBWsX7/B5nWSfqc5H/Mcj8Wk9ULjEEe7QRS3tmRyLhy19jyjTVaF1efo76G4Lwz4pCVajM/1c1g1tua+Np7XdPKYSdiZlHS+0p1JGXLYDIFGEMgEu0ZQmYWfD/ZZJLE4o7KeOsZUV4Ny8i3W3Bg0Sn+hBvOmdHVz4OQEu7ro1WsRv9xWNVyPG3qWaWyUuihs153+5hshaW064CqGgNJGlRpZoHIRLrse6dmGmwu1gRDpPrEhFeIA6gcyUCINReqT51wWYcYPVu0icW72nqQCERKk6NtvvtW9U+pUovobiFyvpff84pVL4ce//mQSaa7WM/bTqUtcE6LmJSmAHtOmB5nmkODS5Uuhu+eWSfUNSoLygzhmccZZKzhyiFg6PfLmdcMWD8bXy42xVOp8eCNjzGPP1C69dhCBhF52gpkDbSpgKJwRNrVh4dDjvfo0UqSlCpgJ4qfJJy02QejsnSHwZSHApg7p7AHNOUPDQ8aQsVV27A4dPBSOHD4s7vzIjV/OJSqe9+l03vqypc65LQkI0JG+0KSYZoM0OhM/ffq1pEo+PtXBtQ600VywRvgAhDt34F+xn6cp+Ncve3dc8JOVKZaHQ/+oZWFAHO87RWzfZIfVkWDnI/XLljPnliGwaCAwwSIGwYu177EOudv2tJnElteJkeezQTnC3KMIVO5l8S+QTB+/BOG7qcAUwwyqLykGzDPg9+zl+M4d/BKiGmooOcTlYJY7DITsE9gbcIAOwR7GTQh3b1BVqXRM2q5Iz9MlTS72ZNiy8zBFsYu1XbUqK8YX9hvRgztPzJ2UmcP8+GwvlgzvjVysc+NvVfgibxIv3QrVfyxcv3Y9tGnfeeLEiXD69Gn76uWqWiQS7HboYHvP3r3hjmxtwTya3dxAgLY3mLMGkWTRTkh09vU/EHPrFUlbbQt7RSTaoLUUt0UaG47LntqvZc8OQg6MvPQ7d2lLux938inHUfohP08IARh4RoXPQOzhGpIK2Rap+v7m9Ldi7h4I165fs/nD4WpjUqn5cAMHMkK4/GijMlwxpnkflhruK1euGLHuH3//j2GbGBo6O7pCZ2eXiIFvTKr3axHsvFehDYk585nGPtJoMDKBH3GG83YC274T9cMJgT0HH+qOcSxF4M6cfkNnewe7Dobf//0/mD/jYdOmTYG5bbukG58ozEfZ5MMsH3P6qJgaMJsCowZ2JBmhaFvCMW4ndKo4tk5tnp8w0Px/qOby+c/ra+XA+JmPevo4/Vr1yvlmCCx0CGSC3UJvoaVUPjAqLi3aOGyWQHy5Ik4nVByyYLubi8k7ZsPmLKqvYJHZooPcI0eOCPk7LZWYe3WwFZEB8vVn否1Ih2EE95FUsIBA4gyBo/xfyZU5qx5Nuoy7VWWBG3PXrtbQ0dEhxHabbWIdzC/F0comEdWebNxTGM9XNdj4PJX6GmxFgGVhg+KDVI4KgAGSKNtjdQTLPvYEoWUgW/qnjWh71JRyuRQBgSEE9vb0hsuXLoX+xKZg3Gw7mmvJ5h+HQASwvTEOOMgFmR7R4ciIJOzgWHYiLg0QbdfFtvEkuJPMeN80RH7OEJhfCGhq8KXDlhHPjYM9b否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否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否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否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是GIOZNY2VimWBdN5ZkVWiXX9Gzoxv7NHOWn7B35V87LsWGkwrJNp3DeziV4HmiT5IEqubGrUIgDcA0JZIy/RZkI67qEsOEccapQ2qxQLPZ0+4Rn4/ZkEAhfZzo3FH0JzzAiod3U1LQp/dr1xNIOOpIYg93XrmViDcY2SiP4cfuhJ4SxyDqYTqZ0CSyjbY1jL54+c9/94fdql0WLe9Yh15gNdT7+GQJub+mYGqHInEHPxi+yyQqEOArjJPvvRH8gDBsZGTE3+9d6ut3de3dNIMv1kEJ5w3GxtgjsFvU9vhl7o/F8zV291ulu9Sv+mwRfJfPdAU76l4CzI0XKiXFZ2MnD1eSEt5qjrVDW7rzaacroGRyT8Y/6rklAOSlFbtyhEssvrDH6MVJHalq6Y6H+m8nPdtKF+vDduc8e74i2KJn2OF7RM3dfhDpkKlPiOxHrEm+gMy7euRTYnTFm8fWHIRDmQbvOgSZQuVGZF+EzJAHMhIRKwaTdLurPYcxMuF5oa1Mss/MhCYHd9evX3d07d13X1S5jNrn1iojqNS0APX/+QoTeH4xogBAIKU2whXPF2hqBmrysUM0YyCFo7t275/r7+l1jfYPRQjyyKs0ji9skv+ABm1If+P3XkUW3CoFdZ4drlUtMCLKBVxLYjYzmxDr8lG8l+4a4FxGICFwkBAJTnx3D/ajCeaxzWPBn4T+dWHy8qnHm4cMHrq/vlpjDbCxQz12m7477EYGIQLEQMLpHdA6JfouyDrGOUfqyBbiyPOF6sQoW3xMROKcIpHkJ+hSWQstLy3IdrX4l3ifpbgdql+aF0nR3oNvDNiuUS7/pQHYX8oTxWqmaBUxSpzK76WvwZiRiNRFz60oqBi8MnGdt008gt0Ng52OyQ7NcER0TU/ERmJ+fl+eXabO8Qmiwr7mJRBvCk/f397s78uzT2NiYcY1Z/FJe/DduSxlvbGJcitevzOKRMQp39303+1yjhHaVUjAgDItZYyGKS7oTfYslH7bWDXO7WQY4FBqmJZh98fylxcnrlqem+w/vu9a21sw9Z7nD2L0s67+xUQns5E2kTesmWKjVN8gC95A6Fb28wtgWpPJejPAbK9WhoSGLT7fz1selw2tUh9a4WluaFZdPrj2VELeSqC/WjkODw/ImNW60oV2IfyICEYGIwCVBIArsLklDn2Y1g3AoMCK2kJrM1PgdR1NpYGDAjY+PWwBzysI9TMJpZjCU0Z6HospLYf4v9Ez6VhZl29vaLYBta3OriOksc7Mu7dKn8kX/u9/+1spzWF6QCeGXzvs09w+rN+8E22YRMt4lpggzMQf7sqRDADo7O+dWpX0UFqfzcQr1SG9Psx7pvGnGAk1pt4TycFClmFIEHm6UBinWMcMizCbGJsytHfcdljJ1PeyGeD4PgYhYHiDx8Bwj4Mdvvmk/Roaq4A4Td8dY61qsl2Q+Ym7A7cpXX31lGrN1dd7FlD1vDx812oTc4zYiEBE4SQR8Dw5zk1+gebv91gQLe/vyGJAQC4fREidZlphXROAiIgC/ta45cU782OrKquJ2Z5VZgn0c86l3OeZ7JDjk8CXqiMbnpabJjNAu6aN+lTb7/EXEMoxXOYJNBqnwS1XaYDHcdFIHWNXVy+1be2u7CXmgSdKJ+/MXusEcd9519XUK8VCdWchOPxf3Px2B8I0bPZmdhg5kCL89MzPtRuSlZ3Zu1u3uZ92u4y7xgRTBvv7qS9cppbDy8ri8dgDAD5w4CL31HlcWJG48r1N4R否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否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否z5ci9uIQEQgIhARiAicZwTS/MLe3jsJFtbkgmzKbWwitJNfRk2H0OBYhzF3pu9nn38I8TKzJvcLEPvpOXMPiYtIfkF4p33OB9qerbKyRJ6NjQ3u3r17EmZ8WbICO8pMfUih7IHS8DSEFpD9ZbsHS5ZXr17Jqu5/un/+3/9sXlHwPsNiOrxboRTwKXQtnishBPTt8g0kn3CmYFgw4QHn6dMncvW6pO8l287Eo3/46KEED3dcU3Nj5pk0/Zk5GXc+GYHQt+hLq4otODIyIo9PExLY1blHjx65G1orYu0hJMnWD7RjuHbYFmtK2hevHbgTrq2vN/eTVZVVNl7yXej1Nt4dlsfnnCfvoxJjzJLcX87K2pP95uYm19zaIoFi5VGPlcQ1YpoODQ1ZP0K5gXAFhCq4/+CBxU0tryy38dPmpWQgxhvKnKwpWXdkzREF/XJdC/NVtoXze2xJVDkWIiIQEYgIfBQCUWD3UTAV76bSI9rzqAMORTH4ckK4+okRhGBoEJitrK4YocC5vKc59fEJYsp+4R1MuH7SRVhFMNpb8lN+Qy4Jamvr7J0TY5NudHjUBIYwnSSIOCOg7Ojs/1gtUrilSwSeVdWVrqOj3TU1NJp7lXfyk44G0ao0xXAfeR5S+C484epLzLkaCSKb5ZqioqJKdVKAYfkef7v91r6p81CvWMaIQESghBCw+ShdHj83pc/YvLS27jakcRviz4Trnvn1s1S1YlwgsPv6229MEaRC2p3Z5Oed7HHciwhEBCICEYGIwPlBIJ+/hFcbGRmW547Xbl1zZLjOwifu6jlOJ3ORaavcWV4s7HmaHyVC/5NkTy41nRQL+cmqjGPje1h28PMpvFl5RblZtvfduuXa2lrF88iKwlLIOTks4iZdbTCAhsCa4907LQinJHNWH9UFESZeXAw/lXNNVos/Pnni/uf/9w/uxx9/dNtSuKyQq0zwQ5iZj2sRqxZf9akIJLSm/3LJRCeSD4XveF1t/mrglXv64rlbkBcc2tnu0m18020KcXH12lVXLwFPSHw/9AWyflemv/qVsbWztol/8hDIHxVAyyMWRhX/AJZXrxQzcGTktauQ4Of+w/vmPSMjsEvwBvOPTfZu3c7YiLIDQsFtuRcuU/uiXI0yg6X0APKxmR/jPrLPKXVyYN+TLu5IMdHKJsWBKyob3kMqE4HiMV5T1FsZEzc3NhUfcNy9ketYlP5ZAGxTuJu+m32yEryqOnihI+否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否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否GWyQLBFTV18j8ah/GITO25OxEBY/DyJwqcJCStKUhaYwla5wsTtAATxkGIoDA0MGUES3svEDZ+Xn0I1ILwKEV/595/ksX+n1xY1jdEMuH6xmEDFaKJ2iQghaDm+zxHUzcjEf9sCF6dcxahgaUKUcho8Z1GxD4CU6W8B/ALt8oEs4uWIQEQgIvDJCHi1Dz/wsOhm7mEksCM+QlAyIfNkimFw9e/SiTZpyn/x5WP34NFDaac2pcqAfqbX0fTPhQEudUvcjQhEBCICEYGIwAVGAF4EXowFcfah+Re0yPqLX/zC/df/+l/dkx9/yFo7FGC8ysp4fs/t7u1YPpn5V3mRX6kki9+uwmwSr12u5IhbRb3fvfMu/tNlZVG8VVY87e1tJsBL87OF6xPoh9Kpb+FyXuKzoYkKQJC5lGo+vo05uVAlphZCbHj6kAKtiWCnU2sZnaIzEfTmfyd8/uGX/2w4jluPQMCJbaGlkHcS/ixqXHo98tqtyFIW15AIfeirAfdCz5F7Ou8cvGlI/Wj2PcWE25aLYFxNYhmGcIkfiev8zipRnk1ZAW5sbNqYWiVhMb9QPsqVLuNZljWNEQK7OVkCjoy+djOzs1LoeGeuhlFux8tWTW1tFlfrVF44iQtN5qNMUoXKtIRmzWWV0x7/k1/mPn9H9jDuRQQiAhGBEkHg0gnsSgT30i4Gs1qhxERnE5y/Af/Ri9J6ef36tQjSyRyCtNDjH3suEE+Z+5lsbSJ1Fsi7vUPaiwpoTuy8F89fyOXESwUTXs/eLuoqzTxlLpzRTj6cRi+oLOE89W1QcNzr3dfd1c4uCUTLzc3o/MJ8EhfQA38Al1R9uBbyS52OuxGBiEBEICKQIIDAjtgSuLecnpkxJZPsXOFHUEZbc+ErVrCpqVEBzPvt19AYAphnx26yDeN58oq4iQhEBCICEYGIwKVBgHkVoRt8CBYmWDfMaoH1uz/8wT198lTu/WdtITtYQ6SBYf7dlYUKCp/7WqANifPZuTmcPfvtrhbml6SoihBmYXHBhJFX4L/0C6lK7tr6+vrMOr+/v9/41vT1cF/cnnMEjiD+6BN809OKdzil9RGsuiwh/UkSQpOe3h736NEj4/8rKyvDpSO32S/tyNsu3UWgTcGbqT94MZZsyUJ2bnZeiubLFru6TgKfWoUjof+SUl3YjsOfw/L11317kj8uGldWVk0wVqG2bGioN+vjbIuHHIu/RZEdC+FNrZsRPqayqlLK4bWHCu2KX8KDb0Rgh3vLV69eydXlGylYvjVFiNu3b2t8vWX4hnE1bOl3WD9vbG5leTM1LNetlfWHdvYtfvCd8UxEICIQEShFBKLArhRb5azLlKIubBJkZrPZTdqTIkrCRAeBghuACZmg4yIkrdHCNX4nkUI2lKVZC6htra3yD14vTcdNNywLOzSmEN6VavIoyLpOBRQq9i9NMFAvXGJe7brm2qSRicYmMZaIl7S757WE7H7+JCnTJMmx4R0uxm1EICIQEbjkCMCehX8BCpg5XGKygELMibSFnc1XYmrh+C0mzXu5+lLMBKyf26UF3dDYlIo1kh2LQ95xGxGICEQEIgIRgcuCgC2Cii8Js6HxIZpjg2AOV2wstL589UILr1MFYnFLOUZzLoINeDgEfRm+MZ/JKSaoR7wbS7s5Werg3WVMLg+3t7a91xm8zyRI4Krty8eP3b/7d//B/fyv/pV4Oylipvg3qpJPmxSzevFdx0fA8+DZ5+xbh1Y0Bt9/MIU+G77recXgwlIIK1IsvWwxQFnhNrHvVp/79ttvXX9/v1k98Qa+DdlnJd9I9p1hz68phKO4TSOQaScaIyTte89GTmPMnlxXbmq82ba2Yy0J17UI7WhLfqEdrcum8wn5hW3etR1Z1y0tLrl1WXjVSGjfrvAtDQ0NOUKx8GgxthkskpfB7+BhhG+SOrc0t7jamlyhXTHKddQ7DPPkBnOJqbXFkeEhN6V1RuLStba0mALlrb4+hY9pyoyr4Tn6JVbQxL2jnljlWXtafHLbO+r15+padgyKI8K5arhY2IjAJyBQ8QnPxEcuAwIQLWEGVH1tYghUZlJ/GJdVTf5okOEChaDhIX3O9MG78lO5XAvUSBOqTQQG1mgItZYVP48A4BBIpt2phw4+mZ+TJ8gOnj3dM9k6iRQXzYD4TuvBVmBwJih7e3u7qxNxh6uVmdkZEVarhnuhkvFoSNTZ6l0At3DPWW2z9T6rEsT3RgQiApcdARZAUJXAcg6GdUouMefn5k0jNswZfqzyR/LSpUHa6zPhNqaxsdF1a9GtRcwicVsR/LHQGFNEICIQEYgIRAQuNQIwNeI/mENxBRgSgrjXcmf2/Q/fZ0IZYFWUTvByW3IXiFXE7u5OhudhzuZ/yfAQMF2a8t+pfrMz0+7Z0yfu1s0bcmfY6VrFu1nSdcpboVjrN27ckGVdg9EY8KoomKIs9FZ1hXaA73svesQzb2lE4v5FQgBacVEWmaMS7kJ3tjW1mvIXdaQv3Lp1033zzTfmqeiHH3+0qnuBXZmT6PvA5xGpzo/7OpLumrrZI0d72BqLPGygkNcsmh73itNac0HAYx3SDz2G/cF8Ulmyyw3JTVuK4Tkny2LyYL2qs/Oq9X/ex3vPOlG+eYUCgKdBYNfZ2WlKFNtvs+5az7qMvD+ZTmwuYcwk7M60+s721qaUH666Xo2tjK/wZWGdMmwZf1dX10yZgvXBToWawdLR50lDJalgRwonU/eF++M2IhARiAicIQLRwu4MwT8vr2YCRGMSJoP9MJVBgOALnEC+5hpEzFYm6b5PSfYu3pf3PO4FGhsa5Xe8xVXK5crG1oZbXF4015FYoYX7Q9k+5d2n8QxEAr9MSvbR9goEBtt6WdihOVRVUWlBdt+8GZPrNr8wzLPAkUaU/fDL5B13IgIRgYhAROAAAmF+YM5aw8JuCgs74q5qzsqba+xhxu0kF8ZnxuaHDx/ar0WxL8LYfeBF8UREICIQEYgIRAQuAQJpnoT5MsyZoep4XcEK7UcJIt6MjuZ4YQn3vJMFBHG6ibGEBUiYq+16+gXhgTPeQkNMSUn1j3/8o8IxvLDF/1BWBHDwyiyI496wQ4K8n/30p+4//6f/7P7jf/xP7s9/9ueuu7vb4mbZvYVojzOuX3z95yOQ7gd8z/Py5vDq1UsTyiGYlvGcpYrycouj9lAxkntv9否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否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否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否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否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否FoY8Dsd2Mn+ZjBqfhRSCicyg2SBqKurlV27IwNCgvP/hh/IIpjG5EzQEp6q70dKepyPgCDgC+xkB0iMSJFIY8iInT52U73//dfzego+xRtWUU34EtC4XG0tIr6hR0tn9RP761/fkV7/6tdy6dSvNP1Mm/5JJA/cznl63pyPA72cai/z8DQ0Ny/22+9INk6rzC8vy6ms3YVqvRK5ffRkaLnPy8Mkj9W2nguKnJ7s/n0b90iqnfZXzx2g6SP+AxI6m/sogoGP/hXIrwooUFxWrKdtTp05JbW2tWn7QuaYl5sctRYBjHsfFqclJmUG70HQw+YEJWOBQq0n47vdNiGgGvydu3Oda4OLiovrjLsamRQrwsimwbbhWyY2V9GXHsaegsBDavPXSAD+Qi9gsPw5hHtffkrzdJNqyp7tbTT7nNjdLZSU2Y67i6LKppl4WR8AROIgI7IrAzib6e1FiuhfLnP5hcxZorJUd02PYFZ2OU/WdDllTi4phkXLv42C13LqjLdsS1bCMG9ImVpzYccfOEnbMUcuOR+sHgY3eunJ4So6AI+AIOAIbR8AoYbRGEg3J4YrjNM1Cdzx+DNNgj+Sll14KvuuwaMKxnQsrNvbrzk1NjKM9TpgEInBPJ0MuFlvod6S+vl6qsCOau3PJEDMwHQu2u5NJMCSfhTv+1xFwBBwBR8ARSCFgdExJT0RPeO8QtOdIm0jLuODPBf6//7sfy/defU2qa4JmztICPNuBFhUUFagW3YMH7fLX996Tjz74EEKXQX03lVN0BvpneRo/kyBjq6L7jb2FwFrzjiTvb23+tFpxkZ9aYt98+62Mww/WJ59+JGdOnZGz585IXv4hWfjzfMpHG+dT0DaLv6mnJbxHnz0LM2Ku88ioI9EazyDMMHbBn119/RGpqakJNcfzHGwCPgTMaJqR2nf5+QVqvQe30F+hTbRHMdrNYq81foV5fnjCtbCpyWkVYHFaX1FeoRv4FkcX4rl8ZvnXSjMzzm5dr/k9RgVmP7Sysx/zx5AHv6YF8OWdx80d+M7CN6uPtvaPZb6BVFkP/ibGJ2V8DAJUbAYoLS1XfotC1f4+8lras3SDilCTHIFuakZHR/AbliOHa5EGN2NyBS8EpkkcjNLFo1M0SEWHKPbeOiTH8r1Vci+tI3DwEICrWw+OQDoCSqRBpObn5nXHChcraZohDnuZQsWV2LkTEsXcPAjsMLlexOSbzqS528eDI+AIOAKOQHYjQIZtHA7W7925I7e/+w6O5wdjxpwLI/xlBt6ynz5Tpo8sX9C2bsSOz5fg1+X0qdNgKkvBQK6RSGaifu0IOAKOgCPgCGwAgSRdIg1jIC/CDSMXzl+Um6+/AZOYR9VX3TJ8sa5AWJeDeEtYlKWGwvt/fV/+2//7/8m77/4ZPoyG1yR0SarFHGyRUzPzPwccAQiTsKDPb44WZTqhxfLBhx/Jf/23/ybv/uU9mcZCOc04lsG/VAEEAOZb8YCDtqr61G5qh/D8a2i4dsK8aNI9CSMT30r0afblxvoGKYLmUxDQr0rKb2wSgeRcnms4fRgfqfUI8OUo/FI3NzWpNtcmk98Tr1FQSdcAM9jETz6mrKRUfcQV5OVvT/m否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否0AP3T92C0FXFiHSNzn2E0/dt3dvTBPRF8OYZcpd2tqNDKC/Om7SIsENroV+kvYp0kTUKQLudiZWl5RAT92LeqDpLysXAsQ4qYytj7HXOyfldSPjoAj4Ag4AgcVAS7ER0QmgkDpBc55LIXvusamRjl69CjOy/Te/Nycmgv7/LMv5Df/63fy/gcf6+KmmWdcxAZNLkhHVA3kLKI6JIuaB//aL8o0Cw4RuVWyu15xlDSHSqwZxUg4seC8N5iEp1l4WpuIrnFfscI9pdV8KREsD1svoIYef9R2Sv7i58RXf4ZvIjGcWpkyskmPpFcJBKLmsbKsETlxa2vbcmN5JrI/QKeZ2NjcjhDY96Bw8HuI7vF6GVaO+nv65D4E612dmQK78EEXwC9yS0uLXLt2VVpbW1SAx3c5CdVvh3/w0/PwwP8+DYGMbhHBp29w/j4KDeWe/l4I7GYgsKuUI/CVRjOROiYwlmKdPjY8LbusfRY+m7h4s6AfE9jMz039+fCfSGEY6x2+wjjatp1YsySPlhn70BzKNwxfe8PDI6oJSKEiN66UQNOO2oAcx5VIJJqG650T4xPQTB2T2Zk55c+0vVEr7S/JxrfMcLT+G7d54pmfvhgCjm3q+3oxJNPfdlzT8dgrV24Sc6+01A6Xk0RvYWFed5tQYEfmzcPmEKAfCTLADJzkeXAEsh0B9n8LxkTSTBKdm/NIATR3rnlwBPYjAvr1J/qA1pEMG+6xP9Cva/9Avy5yVldX6WMzI/Z0PPg+Y/C4DE2GYHqMDC93Rb/00hXdIDM+MYYd1E4rno6lP3UEHAFHwBHg4mMauQK否T/UQpjMO37sqGrd0Fwewyw0IzoedchXX34lf/3LB/Ldt3dkfGxCFyVp3oy8HhcvUzPAjLT3ANzJsutiK8ucvMlrexAv2sZGKPlU6bwewwX/IjARCDz4F+/rq7r4m7iHmzQgyuR1Edfy4etRsPk128zm10yb/CF/yef2jh3tGa81fRyT9yyeH/cBAolOTV9bnHO23W9T/1zTWJcJIWhmQr4sFEy0tByXa9evSsdj+FqGb0qZMhyi75Xfmd3y46YRoKlFatj1QsOutrZWmhublS8uKqKZ0v0d5rBpcRx159BWhDWByqpK1VCjLzgLeKTjYxgxw8hpz7bqmOgeaUlyPOS65ejoqMzApc8htE95eblUV1X//+y9Z3tkR3ImmvDe+wbQ3rMdyabVDMkZLXef3dVKu1/v/pn9Sau5dz/dK/NIejTDGYkcmvYGQMN7791938gT52QVgIYrAFWFyO7CcZl5Mt+TJiIjIwJCuVm/doE4Reg0zIO/VfqInJx0Y2NjrhabKOvq6lLytAtDwBAwBM4SARPYZQj9kGBWIvpwWXN6Y+D0dpxwuHziyRQzFusQ1oPmvlZgi5umU7YCgR3THLeUx6lhzqUFWGSCSS6H+OZcPazA5wKBsI3ynII57jSugNNmLvpQU5Q70WQHNildC4ZAHiLAli1Mp195S+Y8tHkKrEdHxlxPT49ramyEU/M6MTXG3fPsM7I0EizU4a7nComTTNFR7tF0XY6F1Xv377tpMJj0Z/fTjz9CYDebh6halQwBQ8AQMAQyiYCQYSEthrmnGguU3Z1d7u7Nu66trU0EeJybuGD5pz995/733/7f7rvvvpNFTZ2SSOuJwCial3SJH5NaMv9lsuCZzisqtxZWhGp6L3qXn861Zv6hp2I5d3Oa9nO43ttZRP+E90X7DrQxBZ0lxSUwuVYKnOHTqaRYzBFyE4+YFiVFgFdRv56p+Q7iTDOZtF5D3LlBlm4TNkBf+w1xmxJn5/v9HV83X37mzTzxV+qQksZH4aMdQdcqf否dj+3GKSBA7PU76DH9e/ATsr0s0yT78AjM/Y3C35YXjvCzi49F+ECk64K29g534+Ytd6GzMzZ/69seWx+EwWwIu7SFU6hq7r0ire9IF4tqQcyXYHpxemJSTETW1dVinWwLQtOyuJ67fceIAZA4fjSIo2ftSXpzoXCL7nIqKyshsCt3tfDfNzc7J+1Y6xymCc8zUsn0DKPvpK7D+Zj9ZRFlpOlObo6sQlnJp42OlmNNc07G3hKM026Tib2CAn2IT8DvXXtHOwR2tb6o6DzsP+khfNe2vj+OlF7A+IGdGAKGwCkhoGMRX6dz6ym9+kReYwK7Q8Ka8x99l4nHExCpEwwbOgV2y9hJswKBHXd3aQg7gd6z494IEC+yhyTODLu9cbIn2YkACV8uKHDxgWYleL6yvCJmJ7KzxFYqQyDTCGB+lFUxn+8aHK73979zT578gsXQVuzehKkxPOJPxnss4jHEDL5OrxpJYjKGn5C50Hfr9k04ON9yr9+8gW+hN24WC6sabN5QJOxoCBgChoAhoAjEi76YSoTPIL+BuaqhocHdu3vPPXzwwLU0t4hAaGpy3H3//b+5f/6Xf3Hf/fE78fPDRcsymNJbg1adWgLhtMS8ZFEScxenL85UOo3pu7PtuFv50nl2TuOsi/5N0qC+fMY/fCpmL70vPzUJz2deSFcEAV2xCESK4ReJi/Rl8B/GebyEQrtS/4w0s2reM63PGYvJmOc5pycCO/o438BvFd9hzVGDhYviKsBTE6XcEMSNcqTJxcwm8mA+MX2Ayug7knpJdexPFiPA76ftLr2Y/J58pt+TmkPUBBqfmJD2UYhn/DEwHjWe2tvbReuLguQkRDloRskDOzskAoo3+6PwwhCkFhUWiU/qMmjYcbxgH83XQLcA1C6kJlptTb2rqy1wE+UTO6p76k2N3UCbOfrUMhQOWE72mUpsOG5qbnKV/VXCW2mfk76FuKwTfd4Njwy7i5fgoqCjQ+oTda0ddbMbhoAhYAicJgImsMsQ2jroJ+RyhjI+jWziSY4zXTLF0iwKJzEyDjFDwPJgcrNwOARS8DtcUottCJwpAmy7XCQQLbvyilh4d6aFspcbAqeCgF8I8dNiMj9yB3xvH8yK/fyTu37jOsxZXt9RGmX0lDGUCMjOz56yHCpTKeMVY2GPC6w3kNeVK5dcI8xsDg0N+kUcJIgXZXe8xW4YAoaAIWAInFsEInaM9Bn5ULHmgTmltrbG3b57291/+FC0bai5/U//9M/ub//2b90f/vAdFv2nhK/bhFYIJ6VtLjBHeSmWcskpMO2+Pj/LI+uqYW/+isIsL8gAOBKd9VSfs+nVYp40JVoBOpe+ZWuhpVgL02jUzqiorBCT8BSClEKLToRxE否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否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否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否ZNAtLSzCzN8AtIv6xAz1oviJSq2oTrc8clFVpta0eTs1xe5Xu2G9e8zj3eV7jrv4q9XTOpPu4L2tyGwdS0jfhtTGpOZJERawm6EJVARa/WL3ReBeIJon0zAht4k4udqmjvclsjC1ftioaNKecY/05Ry1S8GHTUxOiXDb+xHz7Z2C71po2bW1tYk/O37n9SysXq4Xif2GviPpJ3Nurl7odgrBq6oq9xXY5WzdOcigDXKtcGFh3lVWVkAgVuJqYF1kFWPLIjbuqobaidaR5UDQsYpdJbol99dh9pKaj/QxXl9fL/7AaSqWmxIY2IcK0C/og49mk2fnZ8WiyRT8wcblRxxqfHM89f8kacp7/B37awgYAoZA5hHIS4Fdbkqe06ix935rjRtOSXskCK否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否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否akTcbelB0Zhe2FbXITgkr6jmMoAk1XdAYmMdnM+dM+4i0CRVZPomcsH7cr0goEBXZs+3IPtOkC+g6toCwtLWNDcdpaA/tRVGnmH3QpSe8ZJ0VFj9GjPD/ourHyAsetbqbyOW45sjm9YZTNX+fgZcvjKeLgIJzXmDpR6cQV4sAOTt82VVWVstO+CDudLRgChoAhkBEEQNDSzN/rV6/dL0+euHcDg9Cg887PuV9ACQwSd56RhIneYgjsQNhvgmguhdmKx48fu//+P/6H+/qbr92li93CXDItiWclCjNSVsvEEDhFBCik4CaZFZgEnJiYdH/+8Wf3/PlL9BeYXtkhrPMFI3+oIT6PT/yT8FKXdCik4z+hBRChq7PT/eV/+Ev5Xbl8yfcpJBeWUgkGfZEdDYETRICLWBzza2Caq7ml2TU3Nbu6au9f0b/2fC10nCDUlrUhYAgYAoZAviMQEIFra+tuHBp2tOQwNTkpGk/0A+55Lx+R2kYd7e3wI3vdNTU2gB4sEuFFvsN0KvWLyBdvGp88K4Q+pLGDb3Qq5Tjll5CHofWE5eVl0VyjJa8S/FSj7ZSLE7+OAjkKsak9xz5Ay2IM3E/JZxQwqibdxsamm5yYgJWgITc7O5sisBM2KSZN8/97xgDaiSFgCJwoAlkrsPMLtfGod6IgnHzmrEeW1YVEQfzjbn298GjwmsQaNVkouOMioob8+jZaKzsaAoZAZhHYY9zD2CJPQNmurK64STC否6MjbhYCPBLG8VipySMGBnpHUrztbWpeFLhKmMLshIDh4cOH7pPPPnGdXZ2utLxMBHoUSMQaTJmtlOVmCBweAW3LPEZB51F9FN73/cDfWcROzl74enz79q0IudM5epm70af0HyZzmc95P/7Fk73PU+NqWuwfRbZbmPOLxQTSRWjaXbx40dEBO3e/Slm1gDhqmcmcCoMaPLNTQ2AvBLTN7/U8vM+2WYQFxJLiUldRVu7qILhraKgXv3YF0EC1YAgYAoaAIWAIGAK7IADaTFd1MJWSLBSrChqTwofpmWk3ODToJiGw29zEhsmIZtQ43DjWCv/Gly9dhnnMFldoG7cVmmMdSQeFgUIs2WgqfGv4JP/OWfe19TURjFGrjcoAasXrNGsrvAver0EEiRDWicAOPYfrn2rFhM/Ik8kmMmgEglVyC/PzbmpqEoJHaKhGmnfMS2lc/cbSB/lnR5AnO+7m+w3lSfO9ntlaP22f2Vo+K9feCBTh0f/a+/HeTz788EP313/913tHyOATWVTKYH7ZkVX6CJ5+fdRSpueTek2iLQycrmQAjW5yEuM1J6fh4WH36tUr+M8ZEPVvsTkdJrZzQ8AQMAT2QUDH73jo4SDEX8S0VMDcSm1tjWuErwSeV1VVyxhEGlgEC5LQixiYRnxqQXZXWFAE+/clcIpe5err62GaYgkE9JSbgSN1hijFPqWzx4bAGSOgHSM6phPU7D9kIrlxhuaJ2rHjOd6NKhM427oPPMbnSJf8S+6HtZW5Hwk8c0kTmcUiJCFjugGmehVafnNwqs6+pQxomN7ODYFMI8A2WQYN6pbWFvGl+Ah+FRvq6t342LjQohPUBoA/003scrZgCBgChoAhYAgYAjsRIJuVEngdyShIQ5Zjg2NlZaVrga+61pZWOY/YMuHBRFABYQW1i8Zhon0Ia0LUQrKQWQRoOYaBAixP/2c2/2zLTazmgDuhQkA5aD22s6XlJUeffmcVqmB2vampyVHjj+YuqTnHdr8FwTaF257VguYdBIxF2DC2jM3GXK94AHcedFnAc+XdaEJT4pMHQ1z+s2AInDUCIQ+v63JnXabz9H7i/7vf/c79+OOPR6p21to5tMbE76mDfERhxZ94r/uMkB7XJ9pBuHFCSSizKOckLfHnxFRRXoHFk3LsgqFs1wf9NnF6fWBHQ8AQMATSEIjHiXAQCna2zczOuGfPnrlrV6/B7EqTa2trkxwKEV9GJAx3PJIIZqAfIx2DOC51dnaBKC6CFtI72WQwIkwlHUgn45kktD+GQDYioM2U0/ouU/vs7Jz7+eefXXtHu7sIf3bcLMUgvi/Qxguh/c7+oH1CHuofzTs9Y7xHmchtOouUK79owHxogvCzz79wc/MLbmR0FKaTxiPtV83YjobAySLA8VsWDLGIwwWtJrTJi5cvu9aXL90iBMgUJlswBAwBQ8AQMAQMgZ0IKAuktCEpPfouJkPFTdmk63p6eoRvWsWmMAaJw4SgA2mSur29zV2/cd11dHSIMEMiJTGTy+iM6Rli0tNf2t/3IEDetgBCKzCtiOVp+XzmX7nZipsQWcciaHGyncWbEAWBU2o/0tj9h6FQjmY6V1aWpROUV5TDVGeJW13zdCZ9h8PhHsoKiyM45f1pbA5eomlP+W7RB+Yn5DXyZvbKZ0VP/Sfm/ch3ud63oyGQaQS0XcbjP8Z0C7mLgF+hyd3y53HJD9KxDhJnd4hSiKlwsonILE4ypaVl2NFf47jzhMI7C4aAIWAIHA0BHauiYzDmTE3Oup9+/Ml9990fXE9vD3wpRLvsMDuRvhCCFwOWJz4goIBmHYV23Pm2BfOYHKe6u7rd559+5r756mv38MED19LSwoQWDIHcQiBosxRCU4OU5m否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否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否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否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否OwBTXIYLsemf1FqK048Lp0hvha1wYZFMzJq5eqiYryRcs0SKKOKBlI//VC6g0b+bzJo7u/esd0tm8s15eAD3j9mJ30orOqef866A8PF3FktHmgwSHuvW9+k8xRfCjh3bbTcjKhPJJjtXCRjjjSliVx0CHC/TfBNRcIbADHyFq96VCc9MoJPcUgLTq6uSC72b3I7bjOfoaOQxNWaN6RMfc0vlFjvv3tXgWKmMBDGQ/8YskllzSVzM7ng5Ah7EQEnUtVu0ebrqXNUUWVblmPDHJmLoPWwdAr+YItISkx+pav4pcOSYgkZfhKRcelkN3I2X2HC3NPHTxJ93StzximyJma+J//r9GrIKzu7puMtuUDuJ33rrzbBZC8rYsac9ddrNF4V1nJdIfXPz5s1wSxOQ3G/ouqP695bunD0yrvLHBCV11ewX/tRtcdKmflcJgofeLCdGFRGdPUAzb8NM5HbPAmfW2ZkVn4JAQaAgUBAoCMyGgBoS2pK83aGtu6OFLRfOXwgsgrlzd5Qm2/oNzoZ+2Rqp0t+q868HND/EuKeYgsCjI0D/irV76iupL99Y2h6d60JD8h0wTkbFu401lBbG3ZLIxf6f95wZ27ldkaAKnuMvRi6PhI42tHpwFnP8thaahuVIn9cPyzF9JU0FgZWGQBHYPRNvlMYK0zwkd/foG3+dppVfTofdaax7pid/bqbLaeOkyCapLNq1a1cY+HYgXLp4MUjxUQrERBBWT0dyLreCQEGgIPCoCOTView2kZt2WLhKNmOd0bEi9OOP/6wB5KAC3A9vf/9tm+ino8lEq0/wx3owBfTwqSrkhpM7YzE39bzdjR1gzQYnY6Efn3iGhjOx9g4Ph7/56c/CoIRsZ787G7795hudufeXcOzYsTClyeJWBgFFv4RHW6VyhglpVBKjNm69VFpulCBy3br1toiCM+nYVYffWu0IY1fQbKZhF5sRkdhIbQMPMhEr88rdfE24UE/gx+yr9YBW4RHWMcACY1TeMcjplBrlcS3sOH36tL2Td3/3bvhU5/pdGbliLC1aBTdekY1YkZ6UIN18gOB3C5h+oAJnN0ZjzCxJ7ly9s+jAOWodYZcWnhw+dCi8/urrYe/QcNi7b7+9I3bbgbsbTc2bld8qJrc4TuajlHtaUhrqrHgA5xqfndx9DWJ/0N348ayLvHl05tQU2P0sBggwWTp4nKutd9JIl+LmYQ7jA9W5+OIHb/JGsmI8EatO7YLqUFlm5yfptzwSX54ZBfJdbNwpG/BZtoYMkkzLKJninwdzsGTHHZc4RbfMJ6OL4m8LEENlfo0I1OFramyGfYOnv7HIyryMVSRy2L0ce1CePUazkT93qHwa445PFVErz4e6xTKfBHzUMImdC/Z59BhQd7plcEvoXdurM/RQs6vJFt3HNYEyek/niqgeQi0ndtRyYmeH7z3VvRNS32nPEvCxE290bDTeZb8nod7t27dNuHdbgkDo2Kl3T3wIZ3XOQ3PSSACeJtRU4pvD2xsS8DPc5Qbm/o4aOdZP9btLb89fYk1SbAWBgkBBoCDwHCPQ0JbQ3qSG1NtTmg0EdvzdunU7XJUQ4vbd22pXNYYSsfXq1Nd3ena5r5dWkR7tkn+YqdqohzVmD2NU/FccAvR7KB9WNCiET7H/4mNZDahtDEKfbZq0NaHO1xDTHI99uHbtWrgsVbJ9fX0av/c3UTc98gHxMWKaGUfXJf/N+5r+bc4WqedzNv/ivrwR8Hf9sPe8vHPxfKWuCOye+vuONfPSfTTejfJ4yLC7NWcemuiHLYaobU4dd9htCUNDwzYx8s03PqnpjVXceUHbw1X6Yo5cuRcECgJes+RIeOchd2u21/VIrFhsJ5cqmPsSxtWdx1hr0XFmt8SXX34hdZFdYdPApnDkyBFTtWbnjmkAyu4qW7inIISKNV+MNXKJ9Rfu/owv9vwZN4yHr/JCMitXhVEGmDzu61ur3VuvSN3ifpsA/uyzz8J6qapk0vj8+fPV7g7yhMCoq6vbhHC7d+8OBw8eCPv377dzmnhmB1ivVrVWqv5QDyh1fTwjKLN01sBZhur8UF972i2xGX2kqsLnGXYGiW+VR3Y4gYMzFTfDIj1fv34j/OM/vhf+/u//Pnz4wQfh8uXL5t+Ov8JOix/0ziPyYVAUI/d7THH9S/yWhhSP7eZL3vDCRH+ikU10nI/Wo3PLNknA+aN3fhT+xT//2/BDqdFbJ4FRR3s854xQTFRAD2tyR0qcpzGe5YeySSmpYWsVKvrGZMu/Jq4w9FfnmJqQFb4JJ0sYaYpMEg/8ceQ/xuus/dmT7e7+nN8rnrnjI9qJ1wV15LNGhvSlga/yYKIpg8J+UmwpMzzF7ET3zDkRPvSW57eZlQfO3d1ttnvOz2jk4O/M8+jPNQ9i8JB+z2N1Nw8x17P7xfA5Fw9tdy8fVnZi/8zBbOTQEMrKGS5eFuDv9kbKmU8N+SagRwSP7NHqiBQ8homErvIS/+jOd6iQBJYxd00UQt1m33UUbEJj5w6q7nTekQf1L7x0EU73fIec+01NT9lq6rt37oZb2o137cb1cFN1F9cVqWtlEobzN29I9fJVPV/XMzv12BmKGiTbtZx4K5I6TuLNLvLAd1HjmQAif+STR65khZebzOpOAFLbsSWczLHJq5GwPBUECgIFgYLA84qANTepjUBzuTUdcsQdQ7uFYZcRO9KvXbtqZ9Ou0gJEFj5ma2hs7EE/moWELJ5B/bWHNyblpyAwTwS83Dzt7gvpoF9HX40Ltfyr9Mwuu2isJycrHwzXA/s+rlwZCRcuXDDtOozVG0zKVNX/S99aA80yf6jSvszTWZLXiIB/V42u5Wm5I1AEdsv9DT3F9NWdNU2o+fkzcuTcl63a5bFv3z7bJeK7EOqmKjZYJN3boKfd4D5FGEvUBYGCwGIjoA40k673VR/R+WjugMgl7qrQAPPLz78MH+74wA5Lf/nIyzp/c6MEd12qnFxQoMQ1VVD+6PWX3+eVjYq4sqRgMZ12BpdUMHJOE2eBdmv3GTu9BrUr5Oix46aabVLqMbuUxg3rNuhab4e5D2rn3PYd23Tm1w6pnNkcOI8ONRu2Q2OWhIGLTc/rbp1r3d3Ezjb4pQE5Xp7kjM7pq3ua72eIgnGO8Ks68Apvqgs7JYjTBPi1azfDF198Ed577z2pw/x9OHnyVJzYTuEbOIlPTIgnJqUP17T8l/eLsThJh/6tDChe7rj77kIm5W0njoJ0SG0POxRfkNBzeO9ew/L1198Ib7z5eti5c2cWJvIxXibVVWTw5UbEMlVe42P8FY2ZlAe75f5z2D1PZCZiSxoahSukwdNhiUn8LO/Y8Y4/eqjxS2T1LU9m7bqkNi8vacuZshHTR5YcYoFqabDfKvmVZeHpS/msoPD3I06mcnPhHGcNUe20VXKJtsrvrCHyNxTzGMtAtNdlZz75XwiN84/3DBKHv7pXfiK1/LSKJmHsUMfce6ZTHF5nOEjKHD5VuXVySq8ibfy2jNLS5Jh4XPhUdUDFQ+VJixW4HscwQTM2MR7uahcewjsT4GmyEpWat27dMjXGdtc5JXe0844degjtxrTzjju797jf1cIRzsozHrYjT7v1tCtvUm0TuxSssChD8d2nFMcsV8k3PDzz5uoIgGFFZpYKu+QOK0wTWXQsvwWBgkBBoCBQEMgQoK1oaILSA/1odplfuHDRFq1s0TiEhW/WOKcGhnYXzR9oYDpx4oToRkx4kbEv1oLAvBGgP2jHLjR3dObN4fEJGUOyGMv7Viawoz/GZeli8Rf91rjTlE9hQucxcx43AruhPUMNHbUqKyJs6NY9flILh4LAQxGgHM8cez00WCF4yggUgd1TfgHziz71hKwLRQh/5k5PKjf+7DT+nNO43Wnisz1ZSxLDWENiP0yi0N7EyRQmiDdsWK/DhXea6jUX2DUnpZ6waozHYy/3gkBBYOUj4J1ccvp4nYS6HrFqijqJCeDauQFMjwvVZ0d1nt3v3/+96Z5/6823Qo+EZJYe0oSFek6W5trSWbt7kjE00lWekdoerd6EcW3AgTO4vC41HxGz8u6HP/xBOKyz9s6eOx/OXzhvB1yvX78uDEj15XodWI2aGYRzCPbY4dzGpVV+5JFz7/I4PTnWKbN8kTFq4+jjuJi40tLJVHHKqWc4JbvileXYaev3Gmfj2VVmcVo7kYQw4n9LA3xUfr73u/ekDvOTcE67CH3wA73GOTLa8ZIis3crF+LBKaYu2i25kMsQjPAeJ8BWeZM7EwfmBwclEV6oDn3t1dfC3/6zfxbe1DlXAzq3DvWi3Zp0YIefRl2cshbLAABAAElEQVSm8oTdPS5QsrjkZcmLibW4SS/8o9G9KX7c3TcRWTizW56jq1mdsMGd/MQJfZz9/Rkp0cmN/HqelRiXhUXG5k/AiF109IgSCXnGOFO3mmP8yZI0Iz9O5mnz57nDWMmrwGlKkbNYnHsT81geqqgXJw5xyeDLMJIr+DalIUba5JgeG3FsoqlSO5t7RdDC4mEUg87YjKUnklVFuFUoD5ZCxEeVuYwWO+6VWypT9m00eIiuKbL4XIU0f4MMOl3Rh4mQLMJkJWzcyRodvCjXlDXf6AZ9fCWe2tnC+DfVqZ24bb19obenN9zfpEkZCfEeaJc0wrz701L5y53JHN2ph8cnpVpTAro72n13XWd1Xr95QyqRLocRTVpevnQpXLl8xSZxrkhNEmfm3ZNwb0rqOuFTA5jyY98taY51Ga5MCtWEnj/qKnxTHYlVxlX4ikMsgpZ3DxNpym9BoCBQECgIPL8IzGwDIxbeBub9F9wQ2J058104d/Zc6FW/ubtnjbVR9Apopul3s5hw39594dSJk7awZVwqo1uZKo5WnsXtuUeA8hEFYbHfU/ffniw09PEmEdipbKOOnznQ5r4sS2PpnnnveFICu+vXpRLz8qVwR4u8GnteEDZ1ahsJnmwGU2zNeXoqiSiRPhEEyrt+IjAvaiRFYLeocK4sZrQfdZNio31b6UKj1aZGizOVODNpnSaUL6y6YJm3FcMpHB1BGljnsQzao5X1gkpuCgLLEAHvCLQajFV+nm4qiUc01jHOglPXMFjs7OoKXRJudXX3WKd6o3THb1i/UTsaprTjYTxNesZpT0sjAWXs5vz87pXXQtKY+BHE68AYPE6+Wpc+5Rs8OrXbjot6lLQODQ3ZwGBtrwbD2mWHkM9x82SQPFfrRh7YLWRnyCUCp/fkG7384nPMnKUNNywpPQy5xcoo4z16EcJ54ZubKi7fJgU7DXDuS4DHTsEz350O7777bviHf/gHO6dvXO/ADTzrieXclXh5wzGt0UfUniisidzooE15gMbU22mAhVCzZ02vLSxhV/hLOqvunR++E77//e+Hffv3abKhx2igj5PhNWOiInb4xrj0xKYdT5InwGhSYix9TtBIG9MJPwSssSwYC5GT9BiKX+XF5HSeJ3sriVmK1OhBB5oYN++h4Ww33CH3O1Yn9jskluYsM3LLzew+OdXC7AvlST7dgN3jmMcL3RjzbLwqd8O2MczyeKpSOO/kEMLLj9k9pF5N/XbcMd4b6Bq9qievP9zB3+9sPJ2O+2y08buIHGJO+d5IP6H4ZqJf9hngEd1FR5qgb1/dHmyQlN5j5GWkLX/gS13CgoS79+6GG9qJd1Xqw25IeHdt5Kqp07x+TXap0WTikx16Yzon767Ox5vQbr5RnZ3HHaEfwrx72pmHG7vxEOrF9DYjwzPpRWkvdYDlkGNbySles76flpkojgWBgkBBoCBQEBACtHl5i8PZ4MePHwsnTp4M27Zt03EDmxtwYgzWr0Vwhw4eDGdOnwrfnT1ri1caiMpDQWDeCGgsmRYqeb9t3kEXgdD6dCzK0sIqBHVtaLVBJWbdobRYoLP0pb4ifcCr6vNd1G7U2zr7se47xq8pkVV9UZg094UXIfmFRUGgILBCECgCuxXyImdmw5uHvKvlVK3c3E+dMw8qJxvw624NiVoYvNybHR+odNvU329nAnH2R92gQsXkBfQxRHSZO24FKqYgUBBY5gg0dyz9u7dOqH3u8ZsnG3GSkTok1iPUAOaLR+q1Oo1nu2ZBJ9hd57grQIeEXuycGxgYsDPhBrduC306S2Fwy1bbqTY8tEd+m03/PCy9gx3jst+YsOiZEhndqyS4JZGTItfyVuUFR9FByh6RmM0UwMK7YAXC3HD2ktRj6gw6eLV3cMD1apsApiIF8wp3sWPhRC6k8/izpEXwlQB38+R7rGCAX+TrVMRjSUhphzoNRrinwDGsaDVD7Omy+l5hEUqNa6J5RJPVX335Vfjwww/Cp599qoHLLcUX8w8uLqwzllXDQxweS4rMUmCpsLPktM7RXOBh7zHRG0bCxYWZtFHbNanwvbfeCj/WWXXff+t7YceOnaFDQt3VmoxXLi0CSxP5qFzMGeaypPRYWY15hY54zS+lwTCDg9HF8BbSyPhJnMyahHAxepjFANCYtC4hYP7wxNt30kAjf9HVoWJwzdFHZCwN8oWv7Bl7I4zPsUy54kBPSuT0eL/z4eU08f01xhffhNwSUfUMWcp0Qqgqe40cZnlqBqJmN0uARueGdDR6zXzyDM70eXSX/IXn/Gdzf/SYWobM80/0Fm2WjuiWOYjAnpKTfTOy53yMidzglchi3FWeGlwb05W8sk8u+SuG5AhJ42tP9Y/cG8PVKcjVpc4Wu5U/BamSWcWsM/VUh/eu0a5o1T否o2CyB6Ebi0bsrnN9prSYgXNLxyWgu3lLqjYlwLukcz2ZEL14/mK4pB1558+fCxcvXjRh36gEeLXQLkXmcarush3FPJMgm+CKntV3YqjH3LhbpCi/BYGCQEGgIPA8IuBtYGMbSdvojavs8qRZof2+dfuWFuGdkSDuu3BHKp6tTc+AI9wGjbuGh4fCLp2zvQaVmcnU7X5sNb3t9LiaeXm4cn9+EaBMeLloLqNPChV22Jk2BSWAskpfyxZH5glQYaY8ez9s+v60nW2MWsx7WryVG74m+MyhICgnXxF2f4f+ra+ITJVMFASeIAJFYPcEwX52oqLZSZ01rFy5SR05Vpls1uT4sM4C+u6778LlkSt2ZoeT5sHglj87TbkXBAoCKwmBOFFKx5pqAtlDbWKdktcFVie06IXjTmcWYVB7R7vtQOvSmW/dmgBFCNPR2SUBndxl79TB50yM9vX1hXW9a8OWLZtNYLdVgpr1Oq8OlYe9UtuyTv6s/GyTWkmbz0yTmqbasE5kbbNE1I/ztlmmRK27sYjZtuDu5dWr87QdYfJsa1stgV1nqn5N3KfVhRLMWFqZaOZspogxYefT+WWQ3DhBm7+BmIJIkxKapdfeYZVoT226i874yp/0xTw8kMrONlMvierLD95/P/z6178OX3/1tdSCaHCvoO0a7CBqmpZKOTdE6dE2ptUp4p10mkllBtqcnkEBAqg1vb22mGS/zll98cWXwg9+8MPwllRg7t2318rQlOI2FiK28MqoiQDlmFhn8ZBqMIxp9PIC9nEQgo/46KbX02jwgmf6c+YIBFCth9iuIT6R+/tiQIhQVi/ccl3j0xhFqyejVfjcxFTmLtHeSDXTP3epeDQlekY5zP2b0pHzK/YFIFCB/5Aw0C3kpT6E3Xy95x1lnr6UJ89a7jXfeOdDRxH0IunpnFks3efhHEkn/HwSghA8ey3Bt7va2q32sIad3vjrj//cEJ5rQgI9dtqNaOfdDanKvHD+goR3l8IFqUi+JIHdFQnyULFJHcrOuwkEfVIzZjvwFI6d49Na0U0djMnT9VQKQ57JYi8IFAQKAgWBZxKB2HbFXzLArvDT2jnHhVrnqq2JDaDt8F4ndf5Dw8Nh9+491hd/JjNeEl0QSAiY2nP1r+hjMdYxoVxTB9J6XhoHMz5nbpQFWZxnjPpzzj22/p/4pS4aNv2lnqECzxhDFfQLAgWBgkCGQBHYZWAsXyuV+qOahYZVE2ItTxz4x8F+1dRU8w1MKtIoMQE+rI7Z0WNHwy01Skwi2CRERal0i6Fzqy2Pmp8SriBQEHgSCDR0IL2XmTqpsYqovmolJ9YzkEXhEpbGVPLIuWCJMno630bS+ETHWDsV1uj8否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否m0LexuS39gngpJ0rMtLINONhSGFxoaoaZVpTU9MyPTUDNaGz8KO0aWQzAfln31FbkWTIkZlGtZYoPyVQ59DfiH8eTD+qLaWtBTIHhyndB/uD7Fvat4GjUONDug59lww72ijZAkZ7W1srGKZ7VUp2164uvcTDOKQ/607pS0SR0/rkhZ8VOladHzuqyhy/paBO4/HjWPvw/Z7NO0tiJXg2KawtrItRfaU5XT79WL9ek9SE09+Wm8TftTp+q+rXCMCNFECYyoaNbUlr9eZUZIDh7At0ydOQ0t9hjnzih4059NA0Ihzsv5RmpuQ6pY4rMVZwHLh+4xrGmGlIPE9qX48RhZdAgUCBQIFAgUCBRSnAuSh3cqNNujt378qNGzekvb0DmpZ2KgZCuWspBdB+USW7du2Sm3tuyukzp+V2xppvIeZFMxICAwVeGAW4L+N+TDcaWLtxj6O2xFN9g2tD6y1ck1FTSl/fPexZm1RbCZdr/AUXKBAoECiwVAoEht1SKbXqcEtZptiIbkO/ZcL87ZvPLD8/3H9P4/PDvPeMGcUOCvwgTlq0MUXVmFthR+gubl2N4RBSDxI8dOE1UCBQ4OkpwL6nPXhJ3diNC3F/zYhj/dTBJLfFeFTIf1x6JstP5h9fwEN49n2qwuXhPSXpGnDTkkwTqiksg38DJOq2tG2VTqhF6drVCVuXW6UKTH07nMyiBvOjUnpMhPn1hjZVZ0mxrMjfgqx8hteE/Oxb8xyjcrEsblYeXqSflcXyYPlkkRdzPuNtMbgFYXkSMNL76Ro9ydTiQTAZQWC3KoPu9Okz8pc//1lOn4IqzElj2LEuwTjykUQZYPWaY5ntm/XPdOxnaTG8vLxEqKaRTN/XXn1NfvLTD+T48Te0XRVBQoxx1FYegNk2ySgiPv2x3UYZ0QMFTR8Jx2kxD65tk4HFYOKjekfiKiktceXQvD5WuwQTE86m3MORh8qcowTcNJhvlHwbhS0pMr3uYaNGNXWDY否NDg7ipuWgPnnbeAoSdYl9PkcNJ+VWBLt8ZVJaXqrqY1UlLA47qMySTEsytufAzCvAsww256ohCVdcVKy2/MrKylUVHqVsiKuQqmUBx+9iPOmHYmn5mLYx7KiWk9J605DgM6k/5o+Sk3yqZCDm9QlICdIuFstFm3aXr1zBzdEaZdR+//33UG+7Q2+SlqOfU31m9eYqSNY2q/o+3sQuK0OZWFdR5WutaF3gjyO60t3CrY0seGoc+AIwgfU8F0SAR0Yw69w5h4W0CS5Q4EkUS否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否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否R4YSCcElFMgaqFkTvI1G+zvFj3E7GgcvlC7gASQZeFwMBLe2KRCd2T91JrnX0RM4vPCV38Tt7JapD3zZYjDpkzHHN4XFu/tUP/9dVRdCXQkZ6EODQzIxNg5VJv0yA6YDJU7Koe5wG2xQlpWVaTs0NMYYilJZ9PgzjuNSX+d/06Vx9F7nhVqYfdfY4qai9rygGpWlXdSRPBEQX41ajOPHNX97Msy15eQZj20MJKACuadjmgiYY3MqyUYVrsXF89J/vx9ql2/LZ599Jj04BJjCTcaHwyPSc7dHrl+9Jnfv3FVGXXtHl9ora4HNOqq/fBsqFilpNzz6EPbpTslnn/xdvvzscxmC2lgyLKkicVfHLoWpAuOmqqZaGqDS5xHG4FnMZRyTZ6am0I9mk80ZCqTli2iJnAeXhwK2ZrBnGixuO6Al5z3e4Ha3uHu1Xdy5fUcGBwaVydbT1yNbYOOO9u2qKqulCjbvSmCfTiVCWSeoFKoT5lxK6bkaSORx7mVdjYyMCATzZHJiTBlyZM6yDQ0NDoIhPAOpuxLciC3VHxkg7mJE0jTT+Q7fgQJrgQJs31SHyQsHphZzLeQr5CFQIFAgUCBQYA1TwBZfuphFPu2ZJ8tk2A2DEcELVdRUsHPnDpHGBoVmVKLj2qupuUl279ktOxD+AzRYmLMLUfYdnoEC64UC8f6Fjdz6CZ6xP72xAZmcmFTpU2oMqajg3jUcxa+XOg75DBR4URQIo8SLovyCdNOrogUA8LAZwGAJk+Xnx80KNz8fzn+3cD8dCzc/g6G/+fGdt0mc/A39eduXbhOYAs1QjXD48BG5d+8+JqwJ6X8Cw84YWP5kp8he0j96aJjncJcSLWXQBV+Bg+lJ2FuiYee5ec9o7UtKk7VVrKidQ1otdt5r7KcvLgD7kvjGTMRDi8Gy2nUOSwRx0zDp5uG+/Uy493S8ONHUi9rBg6QSHRnDZID0QmUt1QBSPeYDSJt88JMP5NDhw6pu0KRgCMuycfghYyUqreLhH0cpP18K6o0UuWFxxAiTYXCxLNRhzfbLCrN4+Z758kD4bHw+XW18SmDzpbPQ3+pRabgg2NJeEJDj4XA4WGOg5gDEH+lypvBHmdHyIEhrE1FcrbJF5rTKTKyK0ZLhkx58mh896KeA8QMeAIEfwSg1SnoYbRhGR3sXjwsjRHi4PlWI9uokrhixprYa6ijn5MsvP5X/+I//kPMXLkhTU7Mybh5COu6HH76Xvr4+KQMD+sD+A2DSfaA20Do7O6RtS5sUY2w9dfYMVDZ/IX/56CM5d/Yc7PbNKEOGzEra7fvX//V/kZ/9/GcqcfUA6jX//vdP5aO//FXOQ9prePhBLrOOGU8XBF5+fWcEM1ZwGRSIm1EqzFom6/gM6qwXa4vqqkppwAHRIag2PXHihBw58ops2bIFFxBKQP9IsgiNDgpW3TferI1XQXXTdlxQqK+rV4bdLC7GUA0q1W/29fWq6szamjppa2uVms2wnZeSqvPzaXVtY4Zr+u6vD5cq0jP5tLysHvKsEriy5aaRhsuCyY2RfPlxlxMvwZD7tho4cjGu5lcynywFq6ONa/9YZ6否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否hgTrkVdeBZPuXTn22iuQam1E+5+U//Hb38qpU2fkPC6YXL16VR5Aqm5mekpVmzyaZ39mfwOGnCISY3DPhAJG2hx656Zk4+w81KZSCo7ubnevfPrZ5/r8+ttvZBsuPxw9ekw6OzplZ3u7jqWFuDRBx3ULjILhxTHelAFX6FQU1+HCBBl5vBnOMZZjL2173sONWR448aIFx+dSqMl047OixB+XcbZr98Z2Y2G5T00/8jLYVAPLjfDELz+hBOMTo60KgJ+2Q2jljocHpYjB2fN555N5szQtD6tCgBeORMdSrL2LIGHHS0Fhs//CqyRkIFAgUCBQYP1RwJ8aU9Ol7kQx2Zg5hofYa3IfyX1nCc4uCF6AC02mdYbzUA32tTs7OmQHtAfN3aB2mCHdv64/woQcBwpkUYAdBpc5o7Ul176UOu3FfoGXpqnlKHYIc93LOpYLsa+FknYOOo4fXgIFAgVeWgoEht26qFobri2z6W/zf/LTHZBYfBvs7ZvxzY9P+luY7084cxGzAJ92yGSHMYZLpUBcwqoOkLaBDr9yVPpwu2QQB6hj46N6+GkYLSX73qjPNB2mcUg8BvWGlbCptGXLVmXE9PR2K5PFaK21kI64UQm4yuVWpliEU9u6tmn0D9CbKgzirmPpoiNwc6KHvehH7AfKjABocUmxHuZu2bIFOsx3yxtvHpdDUBe7u2u3SrJR2o5xeQOR8anGisxuHB8rdmXSIX0eSOs7fRXe5ZJ+hK+oKId0SJk8roFcH2D1Hw6XqUedtu24cSKOEWysqJ6BagIHB6Ay89593WgNQ/VqPxaVRZD6o+Y5qiO0cjIncV/nh8uahtsrs7WU5ujDuxi5PsSzuDN4Sy39vVhsi0MYi2fwfpj52XOxMINZraefr9VMN43LT8erXxQjGsIXFCgHQ87H0uo+ieLeEoYd27/LD9stpT6pjrAMjOwitO1xMOdoc+6///Z/yMj4Q2nduhV95b4M9A5AcqpODh85BBWY/yy//tUvwJTpkG5skD797FOoz/yLfPPVl5CS6wEzelZVDddvrpFXj70mH0AK7yc/+5ns298l0/PT8uGHv5V///f/S079SLt2c8jPY/SpMqmsqFEm+MTElMxRsi5vK09Kt4BwKQ+f8kuPlUKywT9jukUvHA3dGMwLE/Ny684dbJTvyXc/fK8M2VtQm3nizbfBxCiWxqZGtJsarcr56JaRjt2oGDeq4okxtgz131ra6tYsHIPRLqneZgBqMnkBggxCjr2UlOYhFWPb2sjaM6vJxk57Jv3LZd7KwtjZzve31mN+9p0d0/kuBrNYWBpnOk0/roWl42R9W7zlxMnCk/YzvGl//3u10/Rxv/h3tjtedOC6gpJ2sSNp+Hu5ix8XN7wECgQKBAoECjwlBWy+iBctziN3ffMYmiumoMVlAGujfqzboakA9p+5p03cY734tGNnu5pboKpxXhoNLlBgPVPAVpzaK/iHi/zIk688R6GGjhGcr8zOegw7gDowwxBRYeEmYT2TJ+Q9UCBQYAUUCAy7FRDt5Y2i08uqF48LNJt+eODV0Fgvx4+/Dsm6h3Lu/Fm5BVtDdDxQcHMbD0CDS1OANsi673bLnn375NCRgzhgbJBvv/lGJU3mBYyUmMrPph7T+XlZv62tJlQ0n6jE9olFlMJEf7h3KYhUoZEhZswwMsQqISlHu3M72neqraOtYC5UVlRKI1RMbt26Tbq6dumzsbFBGXnLoS2lO+ZwaDwDSTjebOQNekro0f7SpgKoyfT3R3kQ18GGQCvUAHbQVhck7GjfjsbCR0dHwcAb0FtgvAFZBfUlKkwULyCBMCFU/EqvJSSbJzcr9fYyojmxilouPh/PcuMG+KeiAPtU9HN4wK7AN9t1IeaHckiXzkMd5gDUEp4+e0a+/ce3cu7MWZnGpqehoQlzS5Ps271f3njjOOaY43Jg/36N8y3UXn762Wfyp4/+IufPnJFR2P+sa6iT/Qf2SdeuLvTNBjl04JC89/57ODjYDkm8Qfnk80/k9x/+Sa5cuapqfZifIjDO9QeGzBwZ4Cba/BSFjs88iCM0vaegZJ6ooCmHq3nYR3kEVZhU1aTjGtUBg+F69/ZduX7tqrS1tUEN6n7Yo9umF2LKMWZzDDNmHeuGIz4PpSjpbu4Rxlva+eSYzx8l7CgdzXYCYK1Spm/1jNfY2bs/UtEvyz+OtKQXYvCxLinSMoCI23K5jGgB9IVRgIw6vfQDRrJ/sPrCMhQSDhQIFAgUCBR4aShgF5NYIK7buYe8g0tRt27dhOrLzVgXgWGHMFuZcP1Msw/7oH7+EuxNX79+DbCOHLbuMpwWJ6w6HH3C3/VHAa67uBfAjlZt1z2AwAJNktglVfdES7fGHq2xkzbPtzhw/REg5DhQIFBgxRQIDLsVk261I9ogbEOzfa9WOj4+S8Nwp799WIMxP4O1b4Y7P1tgCaWNPOc+HTwlFEogXdQCezKdnR3KsODhF8XC9eYVZjMn9p2Lw0O3cV45s5vD60McMnf3dEtnV6d07uqAatEmuYmFcAFsNdHRdo4uBvx4Fj88l0QBtlI7zLJFlDtoZYhXH4Yth9aQaqP9OTDKyECdf+zswRG0enO11tsHH3yg6mAPgjlQDuk3MqlLoDqttKRMSnADcRPi89CXFRnnBRmg0W6tXLwTv++YqxlIdZABTvWX4BqqjUMeHFdUlquNLR+euB6B2UD7X5qWBsLGJPDWQsKkBqre2mDfieH8UWKEzMdiHEKXw04TZPr0n0Zj51bS8I9zZOjxmJpsd+ebhBnMQlqmYTJoHWFbGDfBmvvm4/Dxm7/52ZOxLSwXU/L1pPA0DsOdL56Fp+MlKT77t6w8+H4uBzlNfZFMxVpSUeQCPxJQ+lhjiqRetO2wjaLNuR9b0mOVrKM9MdpdJLPuy2++htRcr0qKzkP6bXhwWI4cPCr/8q//Asm6X8rWljZVPfLtt9/J7/7we/ny88/kKg4FKKXXirDjkGp9/4N35SAkWysqqqW5oVmZdVQD+7ePPpb//f/4P+XkyR/U5hk6qkpXQUOiqpDl2KB588vn0cTGjtgrs+AIVX/gxtORISJGTJMYQ/SSD1Ea7iX+TtHGjc+gX8qfFNDDHvg/hupS0lpHLouA8B6oyXwwNCxXrl6Bqt8GeRM2Dl999TV5+8Q70rqlVaUvOR9AOE+ZxrTHSzyFbA/w57jI8ZRqi2nDjn4cnzeBoavjdFRdfNjP5TM6horz7AD5aSugKGqsdbjA71iAy3UxIs/b9zNsXnD8ulS4OEL0kg/nYvhcmM2xaYzuOx/ebOjV8bU0/byvDuZng8Xy62NfPO9cXzuGnWND+zHDe6BAoECgQKBAoMCyKBAtupIlFVYv3jQ0Awm7vshmekd7J857moA+mrsU8DHUh1figlSbXpiyNRTXz5yv3Do62gt7GfOS8HzDa6DAc6CAv/RackNEJG4eucvjmQn+82xlcnJSzY7wvRhaPugfbxQIzW/PuaQNyAvQV8tYKlIa7AV/29548T3AC85kSD5QYA1SIDDs1mCluCzlG5SfR4ZXN20et3Lm0cPbTW4hxhvoO3Zsl4MHD+Cw7LLcuHZDpsG0o9qq4BZSgJMcRejnISEwgUl+y9Ytyhw6+d33eghDCSseEBIOvBg38S9EE3yWQQEufxZuFXIR2KKDdOePzC0yoCk519TUpCrWqnCzkBJ0u3Z1yhuQ+uns3AVbda25iOwLXY8HtpDPU3yUTWU+XF74Yosyi0CvAl349dztkctXrsA2ZK+Mg3HHPtYIu1xkwpVCMqlSGXiVqoKkrrZOqrFRompOxiezl8+F2JN0tFmxnImX97a6Y4aH+Dm+Zpfs6TPwrPA+fc4chudfdzFF4pd8ZSFjLGJ8o3GyjQ5Cxc7fPv5YPvzdh5g3rkOatEzamrfDviKYdf/pf5b3f/4TaUPf673TI998+Y18+sVXcvLUSVWpefjIYWmsb5KdO9pl77690oE+2bGjU+2ZFYPJMgK7eF98+aX84Q9/hBrMk5Behn07jKvFJU6VHPv43AwY2VQNy76J/NDRfyWOseM+t0IcK0n3ZYxDOrIWoirRIpKk8YiFd60vAOh4jYsR09PT+huB7bkb169r27p27ZrcvXNX2jvaYdtuJw6TtoGZ16TqhTfRxh0SYpukxB5xE6faZ4nsiWrC3h9NK65kF7CgtWSEq1fK30O7Dl7XdebXAX39LGrLh0d+mnN9SOl7Mu38PuJjCe+BAoECgQKBAoECK6FAzhIWU9LY2Jjafd62fQe0XRxSO3U6RWGa0nU9EikvL9f98LZtW7EnrY6T5Uxmazeb3Ri4YO0UxwgvgQIvngJZa6ucfhFlkfuCmekZ1WbEfQgvbvMC9SZq5vBdaPA+NcJ7oMCGpUBg2D3HqvcP9eygL0k+a1T2lymENJj8m/Jkw56GtW/isfjmZ99+GgzL8jc/i7sQn5WNEPxREgF/VELBKcd0OLhQ293VJYcPHZZxLOx6eiAtEfh1JGiGczf5Kf0xBYO1lMzi4ra+rk5qoY5rKGLY0c4T2S620M1AFLwWoYC2WV1dGQMrae9u0UUI59jOVYoC1H4E6Qft3wAiw669o12OQy3f0VdekdbmNmxI2pSBVof6qqysUmmNTbqyM/yGlL0OeKmMDTex+K6na4BVhnfWahBRZ6ZmZWjggVw4c16+O/m9XL12RaXumnCjsR42I6vBNGyBVFEbbjLuYp87fBAH0DulIrU4VOk7HEaju2rZKHVnOdT7YV769FeGppJEKaewBh+VSKGS93xvCV3zQSTjn0H4KS0lvsXLeqbjG+60f1Zc81sIqxI5rEVDZ6Dx08V5rNIzDkhtfsbhz+slnff0N/ORrxC5sLxAGLs4invxxyVXZmKNIiiRAIGbiKTBJlzuoGM/ewxpVXNjUNl6+dIluXH1moyPjktXx2759b/8Br9/VoZ4CWyM/XjqrPzx//uDfPa3z2QITLe6xjo5/vprcuy1V6StpRnqNQtlcnxKHvRDarmsF34tMjU7Lh999Ff5b//t/5ULly5ICSRgK2erwKCb1fkLwq+QdqIULXoC39HXXd6QTzc4KIm0pFGF25ihJbFiWkFiGMbX/xaiePnhrxlcYIQkhgwvRnpSgrTnmInpMaoStqeERlStSpE1kp519/+z996PVSXJnmeCPMJ7D8L7AqqaqqK8bTMzb6Z39o/c+Wnf2915Xf1ed5fvAoqy2MJIeA8CIQkZzH4/kSfOyXt1JSSsQCdB9+RJGxkn0kZGRCQ93uPflSvXpC7zR0myX5SNu3lhy5bNUq36enjzjd2SiF5MJiuM8ZGxkosOzAGev6gp8VG2QQYslR86K06JCyBjDQrySIPZwLZC8/ikClodXVFOfPfwisRVL2ma4fJ7eJqWYjwcv8f5kzB3tdJ5XPXT09Yqpzrtk3qvrrP6nXo87EnV+TjljA0W6BM6dQk7o/3Hqb7MW2KgxECJgRIDJQaEgZrC/5q++wb7ZRv9arh88cb62P0AAEAASURBVJKpx8zXs0zttjDDM8kukqARBnvr7tK9AmFcGC1diYHnjoERll6+ZK+GMdL9UArmon1X1237a26aEtdn6hJUYX/Z5oVTGOsy1QVXhI4A2JB8zy+gXHs+P9yXNb/YGCgZduP++zEIM1S/2M5aYfNJ0RZU773xxht2iHX9BoaJo60sH9Dzxd2L3fTHhp5FgC9eB6X67ZZUY3bd7AoLpMJt08ZNYgztCocPHw4XJFlVqDh87GrLAvJ+5wshf0bUQJ/gu0GSFTPENJ0utZdTWpolvbMivP3OO2HX66+LMbbVbNdhUw7neUzdpN5RgWmHaVUrvdgHir5ivqo0VmD206Cb81PFCJw+fUZobZ4SBmXP7urVK+HsmTNS24btrYYAs3CxbOetX79ONpuOG+NuwcIFJn2Hfb3pU6ebLSaTJjJGHu2Lh8zATfXGwNMhde4qUZIHjz8PgCZwP1MAn2fdz7Shj15Z/mnwRKLCB0MENZj37onBpssKN252hh8k+XbixIlwV9KsbW2rwkeffBTe/+SDsHr96nCr53Y48dNP4Z9ffhd+/PFnSZrekW3IJVJ/+Wp474N3NVa+apKlvQo/d+ZCOH3qvBg0PeGnH38JHaePh3/9138zpt2AbOLNnDPb1Nb26hYJUnX3JVEVmSjaPMERMtUmWQewQdrY17VxkCWrHRlDbUuW46EY8z02PkdR0EiVlHHCgONQTAykjRgX9J8xrqurK2Ar9tKlS7rxWh9OSGL5VMepcPnyZZtrF4jZO3v2HKnAlNSyxn2YIPxRIgw8PIzd9hmzb8nDawT9qZ/3Ws7XQBY3mgy1CnkqYQCTNczKr25drUrTBqR5a6Utw54WBqDTekmIwrQrXYmBEgMlBkoMlBh4GhiwNZCm+j5pA7ooZt1pGaa7ceOG2VofbgU0Q5pg5s+fb/vY2923tU6K/54GfGWZJQaeNQbiGUrl+heVmBdk5ubs2bM6O5oSps+YLrCKS9LpyvlZw1vWV2KgxMD4wkDJsHuG36PiEGZU9frg7sO2v9fKXJ3G01aHk3ekMC/b84/mPZaXHVOpeGcvxaMdzvcf6Cc708zqfyBGQZNUTq3UgWmXbAot1EFCJEfHk5VqjAIvOZbLudhEcvEYuGg0k/yNazfC/bYHsmW3OrwmNYu3eyShqEPGWi79kkUptVKWYQUGCkyx0IoOGs4k6fI+FKR7vF4q02aHTZs2hR3bt0vN6+awcuWqMHvO3NDaMtVUl3q55OfgrLBHpDJVfNFjYl3eB+zqIkH2EeWhE1U50k7NbOTVNdSFpcuXhNXr1oS93+8NP4t5cfPmTakD7Jek0XU9B6SmpMuMgM+ePUtSdq2yITAtbNiwIWzbus0kSubOnSO7d015VXexdyepO+AEUqT/YKRUHNwKBocsR1cOZ2xTRfoEf3myCo+XRmB1fn/3Z0XGEV6q0/u7t8Xf0zpHKG5IlMNclJN/xzzt0DRE1fiseY4Cb16ul0ESD0uSm3c0adI8nt7Lq35P01anqZW2OszzFOVESUKlo/FZNLRjuJAHaSj6RYOY3eDx3Llz4Ysvvwqf//1zszu2TCqVP/rkk/DO+++HBs0l3379z/DboaOh41iHMaxRZ/jhRx9IpeGC0Cb/iuXLQ4tu8CLwLQ52WLRyWZgjO6AHDhxQuf8IX339pZh2HaGuqU4CdHWS3usWk+6uqb+EGXPvbmRaA4t9V5VjZQlGg1k/+ViRtcdaa9ePYwBYmWQTIk3WvySdq6V9oEsAxvzJURXrow7S53Xk8aXHMRDRKbxml1kdv4xO8RvFlOCQP9Qy0b8i7qE90sW4u4P3dOnhmhi/P4Wr166HH1bsD6tXrZYN0m0256LWmLEcd09jJJc3yF9XFyXuLCL74btTT/K5IzyEWn3xuxpdGaCK8MTZeyyDcmo5T1wd5+Ej5y5yDZfOwymvukyP81I83t9H86yVx8Oqyx9NeY+bxut0GCivVtjj1vOo+SMsDhHfJIV0aKmRxieLNm394TRmCSll5NxDyytDSgyUGCgxUGKgxIAwUGP6yC97ag1969YtXZ7rCXddhVImkhf3lBGDmHCYrX30HP0N6sIcJlLKtW5JXS8SBthv5GsyeWw9T/fwjUjSGMIGROPXtbfgcvVyqY3l0rc7ymHfEjuXr+/y0vN6anQ9L6J8viQYSOnHaeolaVrZjDFgoGTYjQFZzzZpMTA/23qp7fHqrp5AOBvwQYZ5Ky7k7ungoC40y7bWipVtkkTaFg4dPBwOHzlst7CQQNIsZ01/PGiePfaeTo1gIeLjpuws/SaVcG2r2iQltSpMqpsUrmjC//nnX8yA7dOpf+KW6rQLBqBldIzX6w/6nSJptuViBOzYsUNSdW+FdyRZt2LFCjG8GuwQ955U+fFPRG/pdWxmfcHKzDpKfPCbBRiq8SeU76/0CQFhKb1/6L1FagD5m79wXnhlxyvh9TffDNukjvOzv/4l7N27N5w/fz70yq5dZ+cN+ztx4rjVwg/Sd+vXrQ+7lef6tSth9drVYdmyZcbIQ70n8XagrbTU+0BcCs7DwYXDmEBK4BN01tInWN5IRdGKJ1HfkypnJFifZNyT/npjKQ98K73+O5OF3MVXiGVhG/LwkSPhf/1f/yv88MMPpl52i+zR7di1MyxatjAc/+1E+PIfX4XDPx4M9yUNt+PVneGDDz8I7334TpitW4v1Oqhm0XunVwzrru7Q1dutjqx+JJugx9t/Uz/5LHy/b1+Y3DA5zJg1PTwQA4Z09/V0powxZIAWhjt9UOX5Qjr2hfhNDPasb9Kw2IKsVfEl9iNvpMI8lbVchZGMerE5hYMhZHOivZU/I2EgR32WyFCe4b3IJxxnjDlwfv9+xtCQ5CRSdzi+7W0xbZFo75CU3X6pbFq2bKnNvdevX9eYvzMgocwaplnqvZkPUumlgjYqK4dWqmF0uLLe4K/ls8TAKDEAjWVc6mzEIaPTGTQHfboNu1EWWiYrMVBioMRAiYESA2PGACwG1kCsW2/fvm0alG7KVvDUqVNsvYu2lvRi2jRpqVm9alXYuGGj1mP3wjntWUtXYuBFw0C+hs89VS1gqZbt/bh8f+bsGV0UPRXWrl0X5ukCaepiUs57Ygbecf6Mb+VviYESAxMBAyXDblx+5WREt6HZh+dslM9h9nACUn+eIAmvzpumwZ/mT9Om4WmeoWmKSSUyFPw9LT0yP8jLgVksr1GqpZisdkm14x2pKjulyQvdzu784It3qzUFKQXDM7zgz5RBZG33U5esXT2Spjt3/pwx6TZsWh8Wy1gzNtJaJVLPTbZq9xKiqLqJT+wd0rLDeD3BPX4/hGXjwaE9AjJTWqaHpTq83SyVpJs3bwnr1q8P69at0+3AuVGiTmnq6nWwL+TTDyLJxt9qYIvQwhfTZO/Zw0uhEwCb2WPK4AVObOLxRIXgwkULTQqkUf7pUjXy+RdfiKnxmySPBqqr14bqrtSpng/7vt8nmrocZs2eHVaxcdq0WWrgNppKwalTWw0PkCJ134W7oj82XXWoB8RZW6M3/XVbZYQJvMx5o5QJl4fHVyvMvfkzS5u/j9XjlYymnLGkdThGU+5waYYLp+zquOp3rz99jiaNp6+VtjrM8eF5eFan8bjhwj2+8oltRsuRVWFvIqm6yfW22aff3RCjuaOjPZw61a4xrjPMn7dIzLQH4XT7qdAtCe0zp8+HHjHY1m9cF9ZKCmr7zlfDBkm8TpkiCVdj1uk4W0wvVF12S93OZUkjt7efDCfbj4f9+/aG06dOWd8WYYfe7jtSt3nX6BxIoXnok74FiNA/f2n77dXT6lm42Puhe1OjqQhC+GdO4XU6SKdgxhba5A6bHqgJwqGmEbhLN3YM2Ofjm6VZ+R72NYtAu0g0SVJyWXg6xpKqv7/PVGX+IBuhnbo08+uvB8Lq1avt4gxS1YsXL5a0dcHsu6fxkTpRuonNWZyNfw6IARbpy+nHEmU/DrFAzVz0RdqLQcV46mmGe3qlw8VXhxe1VsYMF56mGk2aNH2GiDzIYR1rOXkBFR7H7ci4qq7LYaCo6jh/T9NUVPnUX3IayEBxWhmpYi7dcAmA59A2jZSzjCsxUGLgcTDgI0atMtI4RpTnN6rUgq4MKzHwCBgQUbNefhA3wNLscjfc1NkEZk8wv4DpCDQqTdJ6y12ztGSwhuLC63mpCaxk2NFLyp7huCqf4xsDUGoc16toNu0XSjOgi6jXpCoWTR537vTle87YuvzEJ9+DWheoWMhWlT++0VJC94gYYCwtXYmBkmE37mhgLB2zmBbGWz否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否31UEfTvgsEYjsNwOpIixvqwNcFevtHhL8zALiF3lm1T3XitIv9CTanZb38IZ5jUUu+QgUXdGnmhulKmfh4vDm62+EmbrMMaDxb/HSpVI/OyfcG8TW6SRJf04LM2bOCC1irIudJglj2UgVfWK7rkHPAdF1bzcqDS+HgwcPmR28a9evhIVLFoUFDxaEG1euh5tSTQJjz1wGFORpcOmdPg6dI5kFXPStB6iGzQBnvCAcJ1k66w/OOCfZvYyRTzzl5HEqe+rUaZKQXhdelV20P/+f/yOsWbc6fPnVV+H7/T+EGxrrcdifon8aA8lCyp+xYgC8R8e3y3yZB4YGjhREKUX2jRlf4zplsugpjpXx+/dKhXd7e4dUtZ7WuPqVLjSsD69sfyV89OGHpioZ+6bxIoOoQwVTLk9oxcbMGGJ1KqrCOaQWyEs2fxRtIDBrREXO0b54DWkZHjbaMkZKZ60dKYHivL4UhuosoymnOk/xnn/yIugxfSPB+phFjzJ7TgOOvlHkAw/OtBtF8jJJiYESA08ZA2MZSWzesP5e7EueMnhl8SUGRoUB07JMStEnyynWxr5eIbhPWi5OS7PFMV2S27F9hy7GsTmOztezmEZpamo2cw4zxLDjEhuXZZ/8/O01l88SA08bA75AiyO99QuC+KOL6LIzF1CvXpVWsd5e9ZuiX8QcGXxeTN4ZKmKfdiPK8ksMlBgYBxgoGXbj4COMHgQftUefo0j5OHmLUh7V5wcMNmFppopnZHHS8TmoSQez6yTJs/vNN0OX1ICdOHFCtos6rUoOuDhAq3aV5VbHvuDvjhiaEREmT8TDoFRe3rh+LVy9ck03c/rC/AXzo/RX28rQ2tpKjtyBNf/6PCuw6HXk5efZJrTH8AVuMrxw0IXKjnliXCFRhzQPKiZZXvlBviMs0mQ87LWwCoQT4l/Dc9R4ep4aSf2TeTmAaEfNimDjRD+BT0hf4x9MxPqmOlPX9tqrO6Uusz7MnzsvHDl8OBw9elTM8avhttQNhgeSthMofnDNpqobVXwqB3UN3/7zn8rbaDclF8xfEJaJacKNSBglbK4AlfzUyQt9FgfFuit8HlI+JzwGIplEJjfIcALP+h5k1NzYEJYtWSw7nc3GUG7RGNfU3BIm3YfmJ0sCls2+1BAqz+DAXWOwIH3KRZDJU5okodYTznacDXv3fR9++OknGfo+a9J2UyQBBeP5ri6G3L0r6VivEzgyYs1p否MzzmHE5zGkNpclEbMuUr8l0Q9v9EMYP54fBtxM9R3Uz3IZYMcrO8IGMXzaZD+yTn2USxd9fVE9J4V7vlhT+TsWDPhYlOZJ8cmYbWSnn/ybWUCWw7+9mGYPJmeMW8W7RCZlcYv82PFjdqHm7sBg6DjZHjbKBuhsqUueO29emDN3jqk5NQZeViyHVYyX否dA6FCKyrXq9AOFGTxZen84aKl90Bg3lCY9T/kcCwayDz6WLOM8LSoxkYT3SwXjHNwSvBIDLwQGfMTNRwwPEPS+Py0akqdSUJKwSDDEl85TPhfkOXNPkS2vM0s8JImDMCSiKKP0lRgYCwacLtM8cQ8aQwb6B0yry2lJ2N3gYlzGsIO20VzAembSJOz/cvluptbFs2R+osXWVJSQ9oFYYvlbYmD8YQA6rtUXckg15kZ6j+t86BqmNOcvvWLYYWcdN3Ro9kE7L6n0lBgoMTDBMFAy7MbtBx86ZLOYiS6N8zBvSPW7p60O9/Q8SePxPD0PcR6OP3W10niY54nvxQYi5jfVUpqo4iINVkNMj6QPB1yoQOvSBAazrvPmzWEXa3m5KVgvud+YIVkbe3S7H5WYt252mXQito+wobN48UJJklzQYe9ALo2RfpnqL/ySo2xszROijBpFnNCX442bULd1Ewp83xS+Fy0aNIad3YjiEBepOiPoWJ3n866VLuJIhothpKQuQoovkx3ZxkRZeu8n+TOrxHJZmkgd5jVYxMwAANs5hdAiBuOqFW3GrHvrjd3h7Lkz4d8/+yz89a9/DQd/PWBMC6QAGySpB2MOxh0wItF0T1J3x44dN8lXJH+WiFn39ttvh08++ViqMl+xtt+XHbH7quuBujS4qBMg2DAweFVOBq7egYn2juSK1ENTeWaeI6VLc3oewkabJ81f+kePgcfDr/ePlEg4ZOYiQlNToxnq7usflDTc/dBY3xC46AHzC5rjr6Exqmb1g2mkogZFvwcOHgx//Y//EK3/qoMAqcnU5uja5WsiB2zd9dnGqbK否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否Y2fXknNZ6QFpwij14p0HkNgbBM0Gl18p43uqL7CZXF5sDxJ8iypx6Yx1WH+7qV72jR8pDDP9zhPLz8tozrM4UnDq8P8PS1nfPj5lun3NKgUwPrabWeOD0hLKEoMPGsMxD4NG8Fc2lGyge9h420an461Ns+kQ4ZVEOup+NWL50vLSuehaqw4mFyeK0ZfSs1KzmAfqYwITgpgzFtdV/leYuBxMOBUxa4YavP3QakKxyYzWiTmySxDvS46YbsuueoWmmXHbvmyZWHTxg3SHtRpl59qXaLz/gCcQ9YrBJauxMAzxkA1HfpY7LSax6tDmPKhDD4YdtfVLy5q7wcje4rMr5iZEXUey4PIqrmRxm6P87RZlvJRYqDEwEuBgZJh91J8xifViGc30KebFKBP3zlUWKYF2xtvvKmF3eWwb98+iYv32AGZp/OJ8Em1/EUs544YdkjXHfntSNiwYX1YLdVbGGvesfPVcPr02bBnz95wTrf7S1w9wtdllWSrrLjdwGYghoFPSQc/m43+/j4rlA26SbTZWin2H/AdF2i+gBq+/oenGD5vHlOjEOAqtkokALYioRs3RtoHmwGrV60OvX13wlQxy3tFV/1Sg9kt6UzUXPYpfEAbLRx9874OqGFgYmfS/rpuicYk7SS7W/2SUFq6dFmYM2+uGHf1oT7DIbcr6bvWf0Gt/gxbBUhWfvlTYiASRkYfGR37RsfGMhEOTGRonMPn2N/iEz8MD+gV257t7R3h4IGD4djRY8YMWbt2jdkUWy7VsE2ye3e7pyt03upUXLfRNh0XGrUyM2YNX8Tesx2W0bDo1vo+tKx4C9MTRk7u5EfCr0GMcaRa58+bH3Zu3xHef+/98Prrr4dly5eFqVOmShXtPZNevTvQYAzGc+fOhbNnz1l/hFHOxi2IJ1TJtMtrKT2PiAEbmixv7UGIb+5/jHv+jXlCGvcl+c+45sw7irLLDVKTaSpaGSsV3y/JTVRlUtZZMWFRiXmqo8MktrENiuT2AtmgnT59ulS+StqOyxGy2ch3h1kHTRpVZbRVwBHp8hGbX2abYBiAbkzFKzRVuhIDExQDPtonM7VhwsN5qY4jzMfdGK8UVYlYD5izqMpIxv48X0yVrc+zlzE8KKuYu4qMwOf1FKHR523zZyV01anL9xIDj48BaMzojZ+M4GC8YTP99OnTYdHCRca0myQ197F7xBxNzU06/1mqM42NSncm/Hbs2OMDU5ZQYmCcYSDpFnahk/MltHFg1qaluTmHlrE+mz7ysNJTYqDEwMTDQMmwy755utBNF+bPniTyZY5Dpqcte5JnLag8jcdlK6SKvB7maarzPCyceM8zirLypnge8lfnS9+LdEtlI4tDTiaxczq8bG9vJ7MdhNkhmQ7C0m9mkRPsB7tLF89fCB3CDaoKuaXTrIl+zpw5YeXKlSbNceLECVNvmG8mJxiOHtbctK8PoSc2xioAhhz4g0mFClIYdt3ZAexkHeLrKNf+rC5lyN/yVVZG19nDu0X2atmKXhB9seait9Ft0vRkYiFXy9Gmuix9Z否NTcwBtLVXaYGfw2EOmV/b8WqYLQbeW6/vDlevXQ0d2ljt/36/2f3i0BmHxA8HydgPQ7LwTv8dSY/sl4TSjXBMEne7xIx4VZIjMCRapfqNg20YnqjPtFtjnHYbfEUDcjitBn6KuDzoqXsi7mtX4/CMJg0ljJSudg0TPdTGKPUnv0xoB1CgXaiEVv0P0oChQTjMucF7g2YTjvT9GhO7urrDNam9vHH9Rjhy+Gj4VSomO3V7EYntbTu2hg2ykzpt6oxwXGPjF59/HtpPtlu/hn5RSckFdpOOUz3UiaRe/jmBx//kwUtcZNTJkziHl76GNNUfPv2DqStuWxnH5tYW3aLM+sQDqcy8dfuW2ZU8J6lyVKPgsJ1pDDsqyoq3OotXkpVuDBgAfzbuGyIdmzGMb4bzp317C2G9oe+dJbfx2T+3wopSYBjfVVpkiKPtFmiU8qAD/gZ0aHVRUndchDiu8RJ7oEuXLtEB1Yawdeu2sFaS8lNapVo4YxiT15mCwG3yFXoS7nAWgGWQFAAZ9JU/WRuTwDR5hgLFUn5MZJSuRHk6PN7+pJza3jyXoseUMSsuzV+7hqcXmjbyecJRu4UOnT/TVP7tPMxoR/RntO+B5bPEwITBQOy/2b2bqvEruxSR4cL7U9rj87FWaSr8ox8IKzBt652KkNG+FOOy50Dqjn4d+3aUxq+EsRh5vW1VCPCiymeJgbFjIOso2Yq46B8KhyaN1kV4g3cHwyVpqjnZcTKskpYB7NSZYy2jf6i7R8U9WgjWyLzHL78siOvfDCIrq2piq3odO+xljhIDTxgDcRwu5gloNJ1LiDfV5AzG1i90IVx7Vmw83urqssudKUheXlyQqyTy+Vo6LTjNVPpLDJQYeKkwUDLsxuXntNE4gczfH3dkftz8CUgV3uHKFdz5xFKRYciLT0i2bNPshh5zFm2bN28Kq9eskj27n6TPvDcu/hSfbkaGFDZBAu5J3VavDNVevnApXLt8NWDTbqpu73NDf6FUr23eusUWxzA7u3WTP8XZk6Kolx7VWljlzC01FkbdJTFHz8uWHVIRU2QHCwkaJbO1FE9o2XA93E6CtCrLGROGwxG60FhxTFHen0bMa3V6X4qb/TnaQM3cPsPU9d2W2ssjv/1m6hkGtNE63XFK0kCD4Z6Yb5NgVHIA3TBJwj/3w/XOG2Y3sVN28GA2sDHjkLmtbZWYe1IPZ2cIRSPzjd2IAD6rSO8Nz6q+sp6aGLDPYD0jj4ZOmBNw9Cn+oG1oj+C7GgOxZWfvIq9BXVpAsu62mHY3pA4TyWwY0ot1mxeJ7bfff8fUEt4RY+/UmVOmbufSxcuya3fdGChsourVn6kTSVJTCxurz2HyAz/rZ4RmZF3R3RXWIHU/MNvWrFkTPnjvg/DnP/85rFzVFmStTPCq36h8Bg7GD8pE/fPBgwdMFWafGIc4g0P6MivKpuGle2wMFGNk/JK8oz4YZhuMuojziOuCcRfHVsurbMpi9GgfUBCRGjWm2CzUwxw0izrhZl1AatKFGt7vSEK768xZ2VY8Ew4dPhRmSw3OpuObZd/luq1zkHieOm1qmCKmLrRhDGTRvEvcxZJH+h09jdB6XG0a83KUSl7a685j/F2xhTf3jTYsz5B4auV否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否XpmnyQ9we8uqddb1LxQcb5o8qJ4H2YBNUKU5370Z6wzMjUoZSgMWQrrN5ZCAXGhieSFXux/ZFrYpCfG+X3RE4vKB/clCaRNFQcarQtodwjLliw1pgj2O6bL7mS7VLSelxRir2wP9ErNAzY1DvT8KjWBYngIl0iLtGkzNn/+AoOBQ2XxMOQiZOYtf0oMVGHAN0NIZ8IM7pcEW7duJHIgdVZScselPvDbr/8Zftz/o42HSHauXt1m9ibfFVNt91u7w9wF88KhQ4fCnn/uCf/x2V9Dx8l2SRfdihshmCAQovgucT6Jhwf0Af5gxKEGq1G0v1C38He/8Wb4/R9+H3bv3m2SdqjJ4tb6oGyLkgZVmIwXMFroOzhgoqw+STWdOHFC6ooP2GULTXfm0gVnDCl/h8NAOuIa3pLhQyg2V2ttMVx5I4Xn34XvpLL9nWqMqSzPfeeIAAcRegIH39sOlLWe6e+7G1WVaf0yoAs2rbJ/1CRawa5dk1StcdFm+dJl+lsa6iQ5yhoI1WcUiFqou6LBu/VaF+mAFq3h1MEhMlXyZwfKekYpukhrMSaO7ZTRe6dbY3OvLj71SJ2a1FPJ2D0qnrn9Dg3TNi5aoPLVGJIqmLOD0pUYKDFQYuBFwgBjLyorWyXJg9Qw9r25bLZUY+zKlSvD1q1bzPbsqlWrbA1bLUHn46oGRZN6cNWSvDC3MMwzyFOPO/z+52EVT+YEC8h+9WDMtb8kIXXboJ6lthcqJTh7EkVd2NFt1Pobm3qTJs21NP7DhYtuHQafO38+nDx5MiyX5GC7nviR6rhy5YpJdrCWYp0SLw157vJZYuAxMQCZR7I1+rp8+ZLs8x62tTnSrdOmzTAaNpoWrSL9ulB7y02bt4Sjvx0zO+opBNB7Tv9pROkvMfACYSDpFmZTmgtz2GzEtvqU5lbtH3Vhzsb+rPO8QG0rQS0xUGLgyWGgZNg9OVw+pZIYznEs/tMB2/0eH1M9/u+jlDcSLMR5mfhjWrXGQPUFV9zYZNFZ8rhh0EZrclQBRQakGzZt3BT+8Ps/2GHVF59/aQw78iOdY6r8sk0MYV6f72so40V1EUcROX7wS1v8Jhu2aDo12Z+/cFHqCZeHGdN0Q1Q3SHEYQt+xY4fdZuMQ74SYKRgsL1yGdAWANv8uRfzE8cVD+gwR1uwMNwkRgX9Uzxw48KvZw否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否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否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否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否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否hAoSAs2bNEL1I8040s33HTzI7vV/j9JV04/p1oyc1qzGjWX/44YlK6y7KMexngQvKH84YVebvSIjxD3rzU+kFpL4JY/yeoXGbvy4dzLGn5iY069asXWN3kG7ctCkt1Ql37l9tdjDIHwTeFQgTWDMXLyWol6T8HOILsao0hhhpnIM9GSYDKdBAoylj0tNf82AUEepnjYERwkD0dPYUrOMYlzgg8Ka0stBw//DDD9NaWUeZKosLmMm8LzhcjH1QMjMnz9bOISLM+gIfGe1TBqDcS2npIw6zAep5NeWgT8qM2XnMPd7SvcwIEzHVyUE59ktoAd5TmTEHz/t9PYnDAT475Kb3SJXxE6ElfxzOY+5qKwSTvE+WdngIKnly8KRDOGFPi0l7DqVwkIJ5hrK3dFQsMswAKmh/y0GIkk0zWSy9FoBg30WhVUzeXHPuoZVz6tROa8tLly5p3XU9/az71HnHxBvlDYEt8xh/0b6NGdZf4wIDEEd0uhYVwvrPSQnsenUQFc0i9r9YNXCn3m704f0S2l+INQD9Xdcemb7me+oWCddeNQZGGwaiL7QoVzUus1+9bfwfLIMxd/h4yTxVDsNlCoMkWcLULzUGagyMXQyMK4Hd0yz2jPFRtOPTpDN6SIHhO3f5NDBY2EBxwj+Pm6cZ4TwH8s9hGt8H3BwILLZlbA5IuaGtirwCJhZttKG3o8qr04Lz5s/VJeavp9t9tyVceGibBu6luXZNDDYJT+L0Yc4AbCghGVvmVqCGoNHx4QU09m+cjmTXVTg2lUeOHk47pRmwUuatFsjUIZs+dqmOu+LOB32xGGBTiEmKja9uFBN8kxYOt9LZc2elmn+nXClUqUcuL8czp7/Y1bL5BN2GSz39jrmJ0rA7aTS2Qjh/9ZWNuk9rjfCLGT83bflIpkhZhE2WsMro1ZDq2gu02alTZ9Levft079uPZirkpGz8375zK127elUmMPvSDdEvZi9N8CA6pod4u/De33lYf//wgbHAqS40+C5KSPijTNlwxxx33M2ZPTstFVPlLWlgoLG6RuYD2ycVmivkK6a3USEbce2zqCd1QjYJXqxs0BvfYoRDY0tkfo3N10YExIJFoHnjxk0zxblu/VrrpwdlIoULmHt7D9j9Sb84/bEE8BvEvGhzWlSmsMOtz9tTP4NUtDyJRkHMtcJUEfRUjzzdyGsoCTbDNn+3SiPyGgpsq/jNfpEe/s8qzeY8Wn9Hzp4rv05XQJufAIDxcUvvEnJjuvWNN9sTd0bekwbdrNkz09Kly+wE/m0x1WCgdcrEJQyABw/vm8bwlavXZCY5NFtlikuapQiMp0owg7t/DzO1yUwquhaTPO1QObTupSPcS8OzwlRJWvgZqMMDA51yynyStJW4A4xKRV0Ix5XQnkEk4oHj9ddwWVSuRIAjI8YPE3QyQeHtA4uhH5wyjnIgYMmSpWmLTmj/1UcfSQi1VkIp7jrhPk6f2wxW8Nr+GqItHWEcf5xNn0W72t1uNKA1oo9dME4XLFiYtoreVq9ek9579730z//8L+nLL7+wcYpyEB1mK2liTrpB426gelruLX4KeFBiKStNKysehgOV64GXP+oQWvBUCWYu/u2T26XhPF+CzPVm+nXZsuUaz5dJ61v3NZom4kwd1uBgxnQTfELjkR5Z8QeyTeNU4zd5WNHIQ4Ee3kjJRdEtnkUvfxy6/LSX8ItY8U1g+DXGGNmvKE+UJb6HUoqIA+xQ4jXDxHeeTiNWbA5sKoqPPZmnJaOfhgDSjPQz2Pp1yBgo+0kDXoccfVwDQlk5dYEr9lsI/xljGE+X606qLZu3pF9+8qmZwly5slsCLOZqHZjQeNrOmMpg1uCav9UfCi9/QOe+Lofe+WfhGg/L4QQQxjKFa4qw4bQhVYsHDFrS0s7XOoK7hThIdvHSRd3FfM6sl2C2jINl/N3U+pW1Oebp70gwdVcWKEzoqLjsPamTja1gRQWiXv7nJpy5P5W9GdrMCCzRcGaNzN80jc0cRpkjs4BLNc9hsphvhHZhTh8U5bgy3KtS5BlhzIe5K4IEIzgFWKheLB19xLfFt4jgskivQKZ/8evzX3xT5k9++UsTNv6P//d/pD/8xx9MuEm7YnkDnEbZGmcPy6j+GScYMHpworAaNVIg6zPdv64DdRcunLfDTwi/6QdGG2U8j4Wm7UJp47LGP6s+yEHWh1rX167GwJjBQNB0dITyWx5MVOoPDMbwYzi0fVPXnHTKUkyMyQzWFrWIXzweU/1mqMj0MdHq4BoDNQZeOAbGlcDuhWOzLsAowoALLhCIaKuljQGaQ9OlQbHFJsN7MkH47XffylzhTjv5x7TlmxZnDg+4cWC+G5Vz3OCzNqdBD+oeOzZ9r0jbEKbdNGlMsVHF2UbW3qrKwbjDNMXnMk/TLqHNf335n9K8Olturgy8/vEFlNBmpMFTyAzNMzajnCjdtWtX+vbbb01bDcEA5vbg1d+Xib57d50JgFm1Pi3OMHl5Qxdwc/fgocOH0549e9K2bdvMdORVCepaEyBMEG2AJ2DmFIaAa4ZAzUGyzrLw9q1auSk10QMM5itaIPKX0lFrYerE5mn+3HnptLTdzumesNdlXhUBh2lsiFHepdO03Hc3UYxqGAKY/YNJTl4sLREGmoYGJ43VLzlZjRAFOsMZnH4QZqxfv054uJFuScB+7sw5McLFCBFODh0+lPbrXkA0ZleuWG5MjRC2WyL1z8uBAdEjdI3gG9qcKY2nOXNm6b1Hf44CNvp3NM5ztyOMMhhfBHG/4wVd5s0f7whYYBhu0sEE7s7gO5zTrvft8Kt6DKkB0eyKAph3hIefyk1fEiNurhhtsyUERzDPaWJjTFD4YkxuTvVl/2YkodURQLW3S9ugrd3GUeam6TNm2jgCA2ethHSbNW9t2rhRwrvFxhAGt6CVv6AP8Bmtw3u0EO+V8/aaJOYkDCW/CzRZXjOlXUdbrpb5SASJ3He3bdv3Wk9ckzb0RY1duitXDFviUW6cjYZk2jozg+GHdAEB1EdQsUHNT77ytHIHnRSwxMOUjns7Ixzz1miczpo128zNYepy3bp16ZVXXrUDOQsXLrQxOLSVLQ3GbSUCY9mEc0rfnPwoFw6W7ITy5ENRHi+Vhdc/T4MBa90nSoCYRWv1j//kyfZPq/YxPEd/AB2Ge/UPG8dr/AyMAY0jQYrgjzFq1arVZoYfU9TcOz5TGsA2JzK/Y3Uim5PLyMoBWi/pvXwZKGsOwilMa08mAV836qmxOw4cMFajrXNXh3y465y7g/q07uSgIpr5N27ccAGdhHWs61mjX5am/oXzF9I1jfsI7IBh7EcLD83A+8ztjKn6K8f/AYrI3MZYHH8cSuvUuhocoRHNWhmz8HPmzk3LdHiOJ3McZp/5M+07zUPsL1hb841WHgeDglaLIdzL0lAOx6bvnD3AaDqHUWRwZWkYvgukFw0qbCrMAUwQqjp3dnakdZqTu1SeS5cvmaWX3oO9hmf2BDjagiQehx8Drn/GDQachJyGfN2SzPLFufMShF+9Ipr3uytZk3Owin+sjLCYgFB/gw56njh5wvoee+3a1RgY6xioxlavCVY7zp45o7+zaeG8heLbdY31KtblrzFQY+AJMFAL7AqkxWL2CXA4yqMUC+qylMXK2r6bw0ogvQTcQDC5fzNs83ee7uPfgw/Eut+d51V9Rt5avsUqL7ZtRSRgfd9grCW+9PfIGKRvvPGmxeuQySdOQvb2HkrXdZ8RpjzsNDr8Wjvc4jkygdpbWQCFjzrnhStKXJSuKjBCGDaYRyUAwtwhTMUFC+cJDhhnMvAWwg8Wxpx8XbFyhZ2O5JTo7r0/6x6CCzJ7yCaLOEJS5lhI8w+n7WkWMrZfKxqr6mHjhRBmjOACh1TdFlv69juLHBfAHj9+PP3+D79LXdNlIuaDD+2ejgkTNPyKfvtkrpS77k6cOJ6OHj2ajh07pk2IhGI6OYiAjtO8vKNlxra2atWqPN6OhDlTgDe2NkVzWD4OU8VpTsdLW4Xnb9QTwcJ5CTm+//GHdELCuj/95c9pqjbjPT3d6YN33zdTRtMlOIG5YowP0yxxwRwCTPBAHpimnai7J00ThdPOUaMCnzAoVvWsMmbx8aPH01dffiUYdzdv3jJNw58lAO3snGpmNSdOlKadnNWnuVLFt7eLpzFMY+9FpCd5NBeGNFr5PUnazXGeJN3B4gwW1pz3s/mOsSMovNWcHDCgkQ28jfGiTWO6wXgrigIjiDEPgQm+CJsniQbZ1N8SAw4mG3+YHOEgB9qv3Ne4aOGiUmA3uU0SdTkTPhf50Q9KbTsCm9EUBSBMTuDqg6HZRR8wbzsRv3jxwrRk6WITRpumN2kDoD8YfOYsAX8d97/NuFSFbcwAH8JD+SfcTOuanhYvXWKMQDTqVqxYYdq583RXKPeFIqTCdC/t7g7GrKfHdwNao1HKtiOvIpoeRlWCMWaqaAyXl4vx6t2330kLxEjduPGVtH//Pjuccaj3sB2+AB6TxwjBXJtP8SP9Is8yP5qff8rPmZgSnCnQ/FQBns781RysNIAzoZpom7She6NRwUHXmza9lt599x0zD7pId6iiWY//bGloYHoYxq5ALQ5Pz0dpxiJMPlY2wsATdaBCQOrTPuSBr31bCOHNrqnCZTCJDORahZFO7h/pDpRG7j8c2Dxe83tzOo/7Jn4zTHOaT/kt5IOVHDOkaG3GS5G9teVzLgrZjVsXCAaH4Jw+qSd9zvqGIXjc1v6ZVCwnP/YYHFpZLKETdyVzEOOOLEg8eIA2PAe/qsMzZO5xWeO6s/vf1CbRLLyV84SgCLf5W2Mjh8f8QANjldpNYzJhxIk2RFh39fJlrb9PpuNai58Wo5T90kXtnS5flpYDwjgJ8tg3snZAMGdP+bFHumMmMe/aOoM1cLOryhl1aYRgT3X/Pn+u+X/r1gTTsEB4geCNvyMyH9ohjbrpEuAhkEOYhwWMGTq0MlNmmrFasVRz48ru7rRi+Qq7dxXhXYzPkaPjg7mFNQ5zi94LfJQI5gVkG5p8jJFMzj2iMkVj2Gf5oxf9F8od18ocLe53NE9i7vC//vjH9POun024yfyFdiBzF3di586Sk0ejbw5Rv48VDAT9WVs2NCjrrWjpZIfsLrPnldCOQ1Gsr7hD2tb6RWURZHOtx6qeHlvnTTria/VmXJR5NuTXDFV/1xh4gRhook2WEFVv4ICpDi2fPWeHQ6Z1omWNBZiYs/SqCAavDsKzKbkBKjY0qAEi1941Bl56DNiaX1go91gjgJFaYDcCSB57WYzdwdy3FLCP+GfTmhZ6bOI4Nf7QzJvN1anErVu32ilGTlB2aAP0867dJhxh8rPpUjOfMdXymXNUoyUKlxe4kfLQRuEutCNHjsg85hEJ7DCL6ZtAm+ptpeBTvt1DIAyyQURo99Zbb8uc5i5tUO+YcAkTmRyOjMEqBq/GHMfnV9SZ1RE0EiRjBFcsmdgMs3SCMQDj4cLFC+mbr782EzeY5evs6tRdFW3p4oVLWoydSXv27tXJ0wPpgP4OH5ZQVQIxTEOCV8uvaF6xHyybisIrHDvFk2v1lodW7wO8BekEKTWBURZODZ/kfjv92SZfG/zlulPuiuiK6Juk2dIlbbtOaXIaUyTbMdmeTN+YwSEMR798gDk3+3JcYhII4coyMeK5S3HD+nUSNl80TVi0D7mHEZNAi3T/HU/Mo3gjkEikVCRYP8YvBtTUtLabn63anf7IWMf9MTDN2qTxieDGaK4I435JhHn3JdTjlDp0+8abb8rk6uKSNqHBcNC+/dGn6R9B1yW9RaeJcsR38TR4D6M/o0GARt85mRm+LGYgdG0MPgpfO8NAiWJ9oXFgTEmNLZjNRQuju7s7bXl9i7Qz1qQevSOAYr5ivLV1AOgWOu+LFiZpnHLmbIXcQDVgrVyEkwjp2T9jaDo09ECbMdou0xiIAHbZ8qW6p6fbxri2SW1pz4O9xuBlPn0ozWIfy7Pxrsi4rCuF0Z895Ak1WF0KOB74UbYC1PLHg0MRaBtyb1CXNvYIoT/6q4/Sxx//Iq2VVh3M3Cm6i4++gZuoO0ZxfLM2gllrwj9yLAoUOHBI8lbG5G3h8lVnAA/uAqr4fG4P8hupvJ5bJZ57wiVNKScwFn9kHC3Ge+2eDAP0gfzPxoKyLzxZmi9LLMYMDiSwdmNsxzQ1Vht++ukn7U8OS+N9uUz1zjPrDYS3adxCqAM88WwYtiHAx+YGvAVx62njFYG0FUIpDX2+5oz75HTXnNYBfRK8YdHhgjSjOVx3SGtwLJIcO3rMvhHYoTkXa4pqzPOcm78ty3KM8gJR3KCXXEBhpczoJorvKWv+0iEjO2gkoaCPuxHiz/BjjTOta5oOAS1J3dqzbXhlQ9qw7pW0RmabF+uwRofuJmd+AM7wLnwaXoQbnP+Su8875ln8ULwCrF8ZShxbTE/FE8PiR/GtMA6HYLGFeRjt7fPnzpv2IlmUB1kE3wqXwNRufGAAioDKcjrPaYh1+RkJytkHz5LAbtlSLKm4eXGnDfElNA4sX7bM1oGYhGWMqF2NgfGAgebxD6tLJ0+dTCc0L6FdjVWMwVz0r8Fg6rAaAzUGxh4GaoHdGGqzGMhjgT68oufLo2oR7WlEWLN/qxyGAtMq3tP4efmCcRWlJcUoTfj5t/+yCDScFYGONw/jrpvXNm+28M6pXca43SdTe5g94aSfbQq1CGQTHmYjn6YGIxbXNn6qI/+LzVLQDWXgToXj0uZCQLRY5t+WadEbWggVGxFGBCcu3bEY7lm1Kv3D3/9vabZMa/3TP/1TOth7UDCuhUf6cbK/iDJuH4HTsoLCMXgKf9BvTVBsR2K/el9mL2/ev2nCJjTVtm/fYQI76O26zOjAuOceDBgDaNVhhscY+Eo7bz/yjX7Ae7MD1vP3kHLrHQWJjlKUr/wUeP7enG58R1mAfah+AgPkjEw1fPHVn1T2y7qjb7Vpu2yWttIrr76qOzbmGkK4w0M8YXGJ9We0SYqUTh+UuSA2OzGNnxx3eHAH1a9//Wtt8ielr7/5VoK7C7rTb68J6t55911ptLxqsF72FsybIt0KJ1UtczxWvpbcCP4UBcxqMYKZj82s1FhBh+p4Ri30vxyTMOegH5h8CCEQ2lkXEFCbTud2yRxwx1RpX+mbE7wrZF61u6fbzE7dlWBlsvJAYBfyGevfZMDhDz0rUZ6j8FH0L5PmOZyH8O7zkH69DMp09uw5dmiE+wn27NkrpuAJYwaW9Soj+0s1vrw4Sm0q0nP99Po63ph/ZktbboXuVIXht0F3Z5rZNJkAY/MKY3f27Fl2+IRCgUOEUNbni7YyBintVzjaQpD2FZQT819zGxgUP4pvzZulQzlDCwSzY/Pmzk+vrN9o98Vtlnbb19/8JX33/ffWxmjGGWNJaWESGBd5k7Y5y8DLZfSSw0BH+kd+pjVidFUceNABkTlzZ0p42ZM2btqYerq7TWAHnpYtW2HaDSaAVj5oOOfODl6IqCHhKI+XwDLPQc3DSV3ALV3uX6Zi8RrBA24wmAgL2MYURuYryjDc3KLMET++SSf8WqU5WFh/+ICOZ3+I3EfUYzSU+9Xvw8JAM6LVGZhrwCtjAX/N48ew0n+ZgIUrBFGsezmExSG1DgmT2I/AhIc5yT2bs6SBx33a83VvqJnwlWWU0GwEXTRJ4DymXw5p8o+DCHYYQXmxhpxctBVCAcxXYs3i2LGjsrJy0NaxtgZXedDwuXhBT2nbAedmq33MHqiJrIfrR6TQ2gFgQDrowdPgilnH/AtP/KMilpIF2lvUk4/8nW/2DNwBxt15F3VAEFOB23/cYWtw8In5+mUy/b1+/Su2Rsc0P5pKPtdSHD+AQlq+/yPfokx6i3oJlQ0OPHu9CvAytBGQuQ+NKUxWY83gwL4Ddh8gll/QbCQdDvP5ntsPlZRJ1S/jEgMVTVXjJn3znEzNHtXB4uUS1i1dtNTrLsLj4BOrFwR4HNxCM3eGDiLZukr+0JB3v4E64bhEY12pMYKBfOwM2o+iRxj+5diuDyww0R/O6mA3wruAI56PsMU4K1ij+hwgEn/sMx+r677zWHTVAMPCQNBzrDWGFbkGLjFQC+xKVIzXl+EMvsDmA/eT4uTp0mjYq7QqQlmlgfOJEAYKJkCqFYw1/NBywnTWm29uNX/ueONkPveFXb58xbAQ5SCtMstW5Rl1fmwCAwONhUOzhA3qjz/8YCfU0EqYOXN6CR0DK4ITpv/7WiBzBwMmtD766ENjiu7fv9/MVbCRBafcNRGuWDLE57h6xmQTdTQMa3GEcBfnp2YrSsEbfMKUgLZgGJw57bbIv/zqK8XQ5gKBsP5M4CmmK8Jh027UflUp2+LMmUCV4DjyJ8+GVjZCx9edhak49gxAnlZENjbOLChLXL5ECoM/KRdMYO51OnTosDQvT6aZ33yTesQ0PifTmZzinfKKNDh11xSCvQkyMWjaUCSrspb1MG07zzxwDAibsO7ulemzzz4XTh6ZZt/lyxfT1WtXUu+h3nRMJ86uSeCJiTdO6JoGTdRzsLoMBYYCPBf3QjN/LjV6VomW9FAkGGNRPpYFTDwBhWZyuiEe4xJ0B/EjdKvCJ+geT5lTlCbdfJkH5NQ3ppFhZk2X1itkgwmSNvXXqTI7NVHCDfz4izSCJSAv61uDkRowOLom5eJOVcYFzLlyB8ebb9xM33/3vWktc6ceLg4/xGERe2qMYKygDIEXAx6HP+CHP3CGo860EyYdMan1V3/1oTQIus0f0amb8dJhFJkjgzkL/u5L6Mp4C0NyotoSIas1oqXoP3gN1Rm9GREohp6UKeiBtB/YaYRkJsqWLl6aFi5YkO6/cy+tWtNtNISJtcuXrliZ4h486udlKCi8KJDTNrTi8zjzi9OOCyEZ52wto0FVI7BpynXoMvpVq1Zrjv4g/fLTTyTU3CCtiiXG/DbmK2lojHw00QqveEWeqo6lr7zDh2ryhyuKVNJc1JmwoMPKL6AJfZndk+IhsP4EuLOxJdq2VXzoCX9o6CnyaZX0S+zn/VL4rHE6ZCqgd9iYIZyhsRaH1ILpztgW2mIrZep4qQ4VbtbhyuUrVkrYs86EebNkeh0Y1tXEK81U28EDjWRx0kbrTtcohv7v6WAmmvX37P5a1qs//PiD7jHfLushO21tjrlL7jenXWPe5Rl9phrrGquLv+0DVDkTEmqsxU//bSw1OrF6V98MruAC5+nyxShc9GMimyue5UMvRnKUkXr5vAA4eXMICKHdeWksMvaz5maNg0lKDnC8//6H6Y033pBZ/lfsoAsmNQ1OeEcaYvOA6s+/0mWvtqBSQFGcEqS/B0GNUNQT050bX92Yzv/yvOGVNkBYC44ohwkri/zybKuM6rfxhAHvJ9W69p6suKBh19vbm9atWZfuv1oIykUMrGEeat1Dn2fvOVv3Vs8UDwNLQZWrqEYkVbsaA2MaA9zBzl2p3GHXJ0ssDa4YXnM6bxxxG6DrjxoDNQaeEQZ8zfaMEhtiMi9UYBeLYMo6UOUDZqDwIdazBhsSBmKo5zlOVjpFlfzh9fKNgftwiffGjZtsswBTEGbYzz/v1qnK63ZhuAJK2ixpscDlaMYQZSuq3tDyMDM5VTpZ5hjXS1NhncwNcml53r/KuLxQfz3YjE4SAxsNqr/9X/8X3V02NX0loROmEWGOckcPiwY2jIGnhozHwUeJl6IufKvCSbcGtaydL6KgH4Eh+NTdFPck2zQTZGICDO6cAVAyW8nDE2SwtKihA1l8qihs3r2U3p4ex0vn/lVRK9+BypHTj0M3QpIXDAMz96b6YNbotu4fwcwspgbPnT6bPhBzYKvujFy1epWZY6NcCDDv39fdYmIQQIfQlpLx+ilTYKAjNu7cvbHcTgWvF50uSfv27TXTRZyEPqITmNz5Rx/GFF6HNm1FLRsL2vSV14ugVnVrilJ/vlAM9G8hYypRphaNiRd/j4Jpx3vRd6DXrmmdYgLq/gvRJFpbixctSrN1StcOH4g2b2uD9EjMQAR2pSv7ldMnGcSBDodpKmOUq/B2oQhMCT/xL2NRov1JZkLxv332WZo3f57dU0n/QcP2xvWbYrhd0Dx01cx0GSNRYzcu6t7AUPNCjIvfoqnKutB23ON5UhoD4GPKlA4JXTG560ye+xp7GFOCwWrCe4V58xdjotKo8FYm/ZiXvE29QSmLkrah2MZYfaDN4aPIIzfbpjEtTegwmlu9ep20Cd5I+/but79L0kJmDoY36uUnKmkW6VMiZeuzA2WGh8ovdfX6Ep8/ojB+cocf5lXfeecdaSBulIbfq9JEWaSx03FkSSoPKzsMbVLjJ3PuC4zXhPzMFY8SE2VZ8eHPUjPQ6GNRFzxJD9ecn/s2/0YuTYVrBhv134GX0VbQoi/UM96QGqakWTVnUGYe0eid9U+hLct39IEcrn5vwoAQG/g0nGksu6u/3CF0QruNu7bPSLMACxRoWaNxh0Y1GvEr9I5Ab540xTo1p0+a1J+dYUJA5XdZJtW5i+7wYZmbN3N7R01oh/lN1pGndGc069ahtF+Mb82wVid+ijGWSkY940UU4tVsfORVJwGPaEMrPxmwPknD5pzC28dgaI9knAaZC1mDh+POPVtXyKwne1s0CtesWWUHlzZseEX33i21eaR9ipujp27MMe7I1DOj7ppZy/kq0h/Ok7X6unXrbA5H4457sL/++hsTMN6WhiD7CXIpcTWcxGvY0YuBkl4bi9jcjzh4dZY+euiwrYmxzjKQw/z3XFmrmCVrC+dkYtVci3zoReFaBEdQ/awxMMowMMG06tCwYw7su3WzXJpT0KBlnyVyKm+uxmBhzbD192jEQIyTsf4YjWUcLWUKXFGe8YSv/ivc0YLxpnLQAOMJ8U3VG9Ln09U/H7DzYZ6s87AoSis/wprjBvzzfHpZIuc8p2a/5m9jRBVVMRpSZDudbhsbTmpNTAsWzDfzZGgEPXioxaHgd/+81zaMJd2J/prTzssxGt9blRdh0fXr123jelAn2M7qMtu1a9aawM24amymVVczA6oEJnFCU3fdUPuHhabdb37zW2MIXrtx3Ta9mLAADrzdEwOD+OPV9auZ6uzMzqYaByC0pz/w96DASzMl0a+jb4M7ixpP/8oSJzeHt3iEkAdpF+k7MJ7RWwmrvixgiD+W9CCwMAZwZR3ErODuvR0/7Uz79h8wU3A3/4+bMkHYZRoymCN8KBpEuIcJOUzgqBOa8KOh+FZezxjzJ2h4cq/JPGnFYiICAQYbut0Srs+WySQuIEdgh7OoXv1AgONIYeAuXJZFeD3jZ5VXVZCBsshLE/Fyv4HiPQv/yI+0RirPoZe7VYmiv3iJfcyJto0w60siKijUNVkRdNw3QRx3unDK+6QYdbMkrJslU7/E4/ABp/DbRXOBCzvVq3TAkkAqGsrRRkHMNXuyblGAxkceGh7TI93ZaAIX+cCw+vjjj6Xp/Ua6oTt0rly9rNPmZ3Ta/2w6oDt0YDKi9c2JYzTwYmxtHkM87/HzSz3BG/ey8X5NQswDMlm9TeYl0RKAQcMBmzYx/8AFQjvGP2jANC5AuRJwWnAKaqQjJU6D4MqA8sX9i19rv+JdWdgwm/sRZLRHfgWc+em7S+a2V/esTps3bVYdrkvweNUgEL5FGpSfnD028y954KM/JegjrB+aKXytjoydmOB879330ud/87lpTcyUqe/paByLxm733ba1DuudSRIO8yw1T6wU/kOaVgC9RElKabQFenAZRWVzOPfxOheAkVADJiKDMgW9BKbiGfFbweR+Y+W9uT7N38+mHoE9UmuVQ3M4dOW0VeUPTKu4FUT9VmIgECqEOS5DqFFC1C+PwUAz/Q0GflNrvbunZSLv/DmNlxNtzOeettWrV6fXNm5M7+qQwoYNG9IiHeaapoNbNsZpYI15g7S5e4776LZt25awbHHwYK9Zg7gvM4zsXfh7IOGWWX/QKQ/GttCqy9cSpNWq/4Q/z36uqWPZsN4EBIjjpAm48URQGatxlom4ZXCLF4gWc4IPbU/700870v79+2SZostMjH74wYfpra1vp/fef1/CT1kaEB5VIDd5z7rFcIIXGt3a52kRw3UI8gGsnMci46hjtHM8IxyccuATSxx3792VIPGKhIjXdE3AXQlNzxmYl1hlrkemQNuYfzZRd0N9chq5L0HxNfEoTuvergvSEuUaD1zQRLzh1yYhPdcucGiJu9XZF1aCZiBqV2NgbGAgxs28tMxFCLCvaP+DJjradvQVxlAbgaNT0TniPU9gyO9PFXnIudSAT4aBfHx8shRebKxYR73YUoz93MeMwK5u8LFPbC+6BjbBqRC+8LPthhWJTQhm9dj4cVp2isxLtbd12L0KfiJeQjxNki7o036lEFK86PoMmj+VpaINrprVEXocP35MG7f9tvmdq9OrXTIJhzMoVg+WhraHqCqYZoguexaDlLsPtmzZkj4RoxmNhx3bd+getuvOhBYoeAozLQ3Zj/UPcKKFEmMRf9Rz4gS/7NrrC8Iq5yh0RrIJDAhqBCmAPT1n6gtAEQ1MT2VTRsrztYhZWrwSTp7ueIm/8Ht+T9XA/rGwgAFCOW7fvp32SCPu3/7nvyUYL5skIOnu7hFzZXHqFK0hsMMkkmloiqFsjHavsBGhMQdUZOo1R0K5TbqvDpplg39DwuIDB/Zpk79EpgVXmJbU86tdnfJoxUDRU/oVr+wGhOgDenygcZuDCO0yozVTQjruP0STE+1gTu+bsE5MvMkyg4n2m5m4UnSEbA391vokCYfLc4tA97M+KTD3VR9BUIPgRH6UBbON3L/GH/MKp/yXL7soRsWF1NPdbQw2TqMfOXok7dq5SwLGk3a3zlhfwAfmBnuCQceUQ92WqdJdu35OXf/fP6crMluNWczVEt7NkznTLjEAGSxh1uTMVtow8O2N6C0xWL5DDYOmaAdraSWblzXS4A7FTjGYMQOGgLGVwCxgLS1LM3yK9JVH0NH0GdPTIplz5U6nhQsWpZ6VPemd995Jb2x9Iy3QYQarb0Ft5QMc8KH/lDZohzSLEKuD1cOy5q0RT3xV4VX5Wr81xm0NU/s+XwxYY0OUlXOChagqv/qtxsAoxQDjFOYr+QuHpjXr7itXLpvGwblz58o9DAe6Zol5j/lHxjmEQBd0lxtXAOzatSvt37vPrKec18EXNPhyxziIkI+0zbpDHqh3G39Hut8M0E2LGaephAN9qmYa53FYFwgBJfXn7jgOAmFm7aTMg2JCs2d1j0xlvmp7PAR3HjdGf9/rRE4cvDMTo1rDc8cf6/27PPXHuh7hyR3aT99heYU2ZZ1P2syHtGeX5kc0wmfOOGQCGg6oeNUHQEAUoH6OSwzEOuiC7pC8IEsqZgYQUtAfa7ncsb7CtD3rIdbImFatBXY5hur3sYwB5iXGe8ZTxkoOQ0PfmIL2dXy91h7L7TvUsr+Q9cdQC1fDjRgGXqjALhaSg9V2KDCDxa/DYkDPF7+D+T0OYxE3h2vll4c/m/fIJa9Jc8oBg7/YXB6sCMR5FCcWEbgZs0os否EwIfKcI6bt61veTFMkrOO0Jdp3O3bskGbDRZ8ktVi0dLTp8MnSkx+1v4GoEikFIlRgmJrYxf75510msHv1lVcqgZ0hBc2UgLftrLSheCIUklk5mSRDM2RqR4cY3pPT13/52k6ywoBm06s1heD是mRf5TFPMb4j/BDHdulkdnWpnuuRCvcS+SCXK8om2PwhHkXtDYtDL+WVcdXaSrdCcKfoV+wzngOE6Pk6fmSBGnbPz3zRGNiD9xH07fOt2VhSk/ieMm8CS2gSNDSyxK18lCWIs4k4YfNFNqcX3zxpWl0vv76G+mDD95PH374kWnJYMIIsz0sQCdMmGJaIBZf6YDTqmKP7DQwd25gghUhM6aSTpw4YSeFuYz5kfCMaya1qL8F6icvdx6WVSVAR9EzSjpYKVvBhF9UpTn+YOF5WHO8SG8kn3l5aMf+Zervo/5En2LcFz1NfCizvRw+EJW0SYMOBl+nxrF7EtJhWue6TuLjOjSmcTABYiJN6496VulrvtCHhRGhDCkimF/xQ/72qhKLrhHYTHok2iY+AkTC+JZDoKgenqZ1Tktti2TqUFqAEzQHcX/P8ePH03/M/w8zQwyjTXZ4x7dzpBVjKodAuKttkpkvuygmLKZw33zzzfSrTz9Nm17bpPlombe1xhzwyB9YZRyyMbEYj0Ea7Vk0Cp9l6/lFd7lP0XJVw1taFol4ak+0L82RaCM/07wx1YmGxxUdMoChWVSL2IrvX5RPxTU/i9Tih7yYc+bMnZvefPut9OkvP02bN25Ky5cuS7N0mKG9o93mB6gtKK5FMlZmu0NPWU/Wv7JAVcE8WvGtbEvnVBqfeQQHMrxaglWYa2JEnFbPLIOyMAEXYVV6EZK1WuU1at7y8kYdnmfhyEN/kZWypwRGB+FH9gUYrw0O4ByuIfDl/chpv8RCjacSFS/ixedzWXGQwGn/wYNm3nLHzp/Sqp6etGHdelkLWZdm6E6r27oH65gOd+2QNhmHxjDPbkIlCZEear2JZg77G1un68n4ynrb1/DqDkXjx7NVXaOX5yTBGNiSblolMCJ+Xrc8K58f9KvCYnHl0KFDptV/WAKPNatXp09/9Stp3G21qyKwiOHOa8uahfX6PR3KuyPzlQj+rkowiub45cuXTBOeJ+2D2Xq0ytkD3NUay/czLrDjcCiHZPljn8BWcUq79gBa72DJU6XOi1y/jwcMRIehLk否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否hJID5dUtm/KZM6Ui//93v0oEDB8QUO19OoIDnm408xegD0Scs4ef9E5N+UZEoT9TLspfnxYsX0s6dO6W59E164403Und3d5Ieg5eOSKoYdfPkYAB5kPN+CJRNeTGXEWzCaL6tO3y4e4fn8RPHJYy5ZYzpYECHAIdUDC9FesEgL1IfhY+oeFa0Fl5ZqL8OBaY5UoOWGdj3DW8z2JC/SQL3JGXxmMP+zWm97BfKH39Mg96/98AY1Fx2//vf/y61iYn9wYcfyMTqlkJgF4XWfYgKmzNnju6wW2FPGM990mY5JfNzFy5cMC2VKCBMhComTAcPmVASbhUacZ7sSTpVf/B8Bko7ED9QeKsSBGzEBSb8muFzmAjLYVuFB9xYfTbWia+8xrzbSfACzIQmooG+6zd1J8AlE/IyXnVJIIJp1jI1RfR0/LckGwG4z3DwVaZaRbJEihw0bqIhxd1llI+DIAifCUXYgyCndEZg+jYvfoZfmjKtUf7CGJGPH9EGdpedhK0R1i5B1AJpnXX3rNIp68V2GCCq1m8+KVBWYc0Q6eDZq3lUQJHcAE9FLApHmdhQH+49LKHdgXSj74YxmWhHys0YaAIztekdzY0cXMD09nS1PffVQYvMm7SracMJDs1i7rRbMH+BjXsLFy3099lzJKjT/S2z5uhPAjvhgTkYASJp992+ZYJptOt83pVQWvlDQqZ9HLWhnkXdrcqBh6h/hBXffDaDRFKtn5FQ69DBfSOnwaFexlCnbX4fjyPI0wTCLyOi6jrXGKgxMGwMMGYwV/jUZqYJyjRsPPGA0i9eytHeXvwrH6Hwyb8jXv18uTAwMB04zUAl3GWHWfGLFy7KckFf6sJiANYCFMYejzd4OvN1kGnd+vXp5z270wXxMswUeRGWz4/GayD2ALSroNrVGHjuGKgovCmr6BTFeo1QvO5rP3j3nq5M0MFmrkcIZ+kAW4yo9l2kYWt883DoeK3H3sBe/awxMHYwMCICu7GDjvFY0uahOYbs8VjXqk4Nco7Ku+mtwEWOovAqFn否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否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否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否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否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否wsA0MeALQ2kD9OiEjxP30blVj/EkcFXL6VGwVguPZccr4RWwyMt9nMpizIeseBZlJ76YlxMVyUkBRhy0YiOR1skQlk6Ez3YRU/fu2Ru2bt6qxV2ncXQa7dChryVl8m34RBInZ86ctQX/oeLiXMUY6SOhloIsaNJLJLRGgi5MQwjiEMdviWh+X5IGq2T4uaWlKbQuajViLRnBHLN65ijLCmFnrl25FxnJoaSACBrL4I0ILg1m9qfEkEI1GgFIiGAPCeI6ajB27d4lSZIzYfnK5SJgjYYb2rSP6o4DfzCzKO9+nSirXrCFzuWlHBCI8Cb5IqZd7BDgKIvDTfUo+UQcWt9QfwGltOfChU1S77YjrFu3TqrwxLjt7zMJtQdqI9rsriSRjNBPdvqVQzCXuCiVDaEf4+GdHUtMcqexscSkNqA9ai0C77DNwp32dWeMAH956js9wV2pjOjjYe7v74RO5ef5le4e23yybOyWBeQ56yGPqwfGQM5sh/tTp/6g2/WbN8LXmud+/7vfhyNHjph64Duy5wlukAZDGrlFUl+tLbKnpv7EfABjZ3hk2A4aXBcjC6bf8ePHLS7MqHVr14Y9u3aH1159PYzvGQ8bJLW6uK01PyhhDBz否8yHwEubIOFrY5NBiCf+1CD+25sNUAspXIhOmsxZnbPXoj/BPk9zaAMHPoClJJVIZjEeMTqXdIS21sWhV4zHMc2V2DZFEnFsdMxsnt6+LQbenRGbK4t2T5lfIX6Au4ViXrVKso4DEYs0ryNlxw/pNRhS8S5pNMHhDEQYgLxrpjYzW44SeWWHEGJbIu2LqlKLqzrV1+kghhAG7Pxw1Bm7JTDovv3mm/DhBx+G76T6GUnKG5Ke5AQ1zEFcq5j7SFPbT+ni+sC8qLxVJ1u/THRduVsBzLX3w1URgb45eCisXLo87Nvzkg6+dIlp1xgPTYzDgBSMNK/yINmYpLwb9NzYGCXqFBrGs7pgK/G+yqDO6hXGsDQ8WMMamPnFQMjfsv5SeK/+6KmqZFg9QfKdFQw4/r09pirkceLGfDxXxozNI/KmH3tO8U4si5Db7mQslTm9V/iUBb8IL+X1B2e+V4p74RcBB6mOCQMJAwkDtYYB9km48mVLntnSFkPjvnHo5pAOIA+EQUkZYcu+oVXkTcVjzdOMbvvzZcuXhS1bt5j2Bb577dCeMiHcNmWWIWtmXBXK1wYvLd0TBp4xBioHAl1Uf9ZvBQrdlYOWt/TNc/fubevz1SH0Hs0Awvl7fCtePUbRLz0nDCQMVMeAf1tV0mWqx34y38SwezK8pVTTwgCLQZr2y1FViY8MR9NAFSl9eY37Sb35xlKPkcEXiQ2U+UB26sA/hNMGSUPgFotxh1u7bk1Ysbwr9PZskH2hDVIVeVGMm/5wXfbtLkoX9lWJ13MKDXVela44IdkkVahSnLQiDDxzsvusiKd//vOflc3D8LN33w2vvf5aWLFCasVQWSG6OoRSiLPk679YpiPFa12EBD/3zwCw6F42cWM4RGOYVEskZbgCu0PoshfRddu2bWbDaUCnkgYuXZY6jVsiWFNfz5c8ass9eCCiuk4QYmsKu1n2nSFwjTDvmzobc/IU/o13oSoIK1YRoqC2tB4iu34dYnitkJq/ziWdsi+1JXz00cfhsFQFnjhxQkR7SaQoVS1iA0kbPr56enrUpiuMeUAFjemqehf7qFU8XWYIA0/bGx4jPVHptlMlYf7Tf9a7TfKL+YS565LG9cGDB8NXB7/WR/xg6BCDarXsqS2WakaekTDDrgUHCJqbWuwQAQcLYNihUhFbn5dQHSsiwIDuN/UxNCi/s5Ic7u/rD6d1/0HSez29PbLZ1mEG7ZH0XL58uUmwLVLelf2QjyzmRZsnHe4IvI3RYvziszce45g8qrk8fkSJLw0W1Zl2pEWyDeZkk+bEDHETsuMQBMwqU6UnBh62PsErDjXEpG/QvMrcCvNugeaUfPqZkBtTUWRSsSZRPlVgvbI0XIAZBqPKBSiT3hNDkOYlMNaN9WRU7TAkuyVX1BaD4fz5C2LM3ZC0+Inwo9Q+HxOz7rzWsmGdMgV+HOU1yZYfjEz+rM6qireD483wSnnMq5kjDji4eOF8+Pjjj4WzheGbbw7ZvNkpVcurV6+J7S2bfQsXNqjMB/pgHhUDVOvZApWptZfDBdTn4QLylaSz6ku9rG7cAYk+QNnck0sYmCYG6DKVLo417+fqv2LG3xExZzSp/8pRVRxm1XBIRCS146DMk6WHhIGEgYSBhIFnhAH2SLY3ysrL3ysmbQ4aoxHjR9mn3rJlS9i0cbN958cNVYyM否jVUtW+V/aI+0Xr+OKLLyxXZnn2gJ6l7cvk5+9Z0emWMFBbGKCDZoODb5cxHVzk24gD8uP6jtUnj5lCiccIAd13PVP07GKQR6+tWidoEgZqEgO2hvjiMUsQJobdLCE2ZesYmO4KMN14nu+T3GdnBfIxWtxYToRuqrKpu8IdBdndX8nLCY2eCwu0lcuFiASIHhiJjzGWw2NvHkfRSAuDp6OjU5JWu8LaNWu否AMBGhViBw58adIoEHmGx6U2jgJIL0f+ECG5R4kJBZhUggIzWCAYmbQC73Iw7VA/cVti+6g5WyamGYRtJDMgpsJ4griKxJQRc1UZYMSVttERBAjJFqZKEeZ4yWNmZVpaSWQsWACxVwRiOWCCcN2zvlfE9WUmJfPdke9s437giy9NqkbiERa3li5FiLDRBvEc6RDwPJkDL4YpxeEPRpYR7ekMSoYf+Lg//kDSIs1hx46doXfTRmN+IW3CSUVU4lkZU5QzWfkz5h+7csyuUF2Y0Kj0XLN2jangg4FnzvpFMdHkkNR5vdSXC1lPnmAehMTxPxuAToUhDyvi3f0clsp39y/cq0WRX2wnAtVOaisc/Zsn+ic/Y/7gl4UPSCrqc32QH/r6kCTkRqx/9/T0hK3btopZv1QfM02mxrFRTBaYT5lORuUjKVsdHmB+vCk1wWdOn5Wdt2M2Jx4+/J0OM1wI95Dokiph5i3KaRPjj4MQqzWPbli33lTw7mjYHmDY5Q7w9aPLoT6RB6R/dYl14V5An9cjT194iDUveOiR+KiptPHKHMlcB4b0bzbtyqPb+DfcGW8qzqPkYaARCnz6axQzjvng4UMxz7I8vHzixzRIwcU5tlgMYe78mfndn+2exSFveHU2R5Mvf8xZpSxMNSeHKlDp/M2hQ6bW+MjRo2a7AUbqyPAts1GH+skGU7UZ50BgGNOcibUSO+RAOSyWmTOcgaocFkUAoILjgMQZqVT9/e//U/3qM6n77BJhaFv46U9/Gl7a+5KYv23CgdR7kodwd1+VAQ7xMa3PwtRU6yhH8BrbRC1m7Y8HIVZ8odzSY+mpANIkj48Td5IskvdTYoAWduftUfTzsMp7ZVx/L8aLfrEXFf2zZwZwwd3XHEXfZw82Load9fVC+Iv8aC3iY76At2wIG2oK3i8yqlLdEwYSBhIG5gQDlXNwPNQlUApLKvuuIalFR4X9gBh3a9esK8FKBqbVYIGpg8cGMSrb2ZNFV8rI9oellOkpYeCZYyDbkli5lX2/Epi4h5Gvujg7Pw4XDuvAKfu9Jpk/sP1epnHF0uYJYp/3ns/ZJN851vEhhvPA+JauT4mB4t7bvzWfMsuUvMYwMNvt6itWjVU7gZMw8LxjgNXQl0jqyvP0V8jSQq40bEbJQi4SHyF3TnTFNKzhqISD0LxSqipxmzdvtlNqGzasD7v37AmHjx4xxg327W7euBGGRSwdQVWcGHmoTQsPx6GwxoIqCoQhxOQVJ7CHZhT31KlT4b333zNbamysd2zdHtaK4YJNJU6/QcAERtJSgYosYzl+JWIeIT44edbrWSo/A1HwNEpCokNq3DolEYGE1ooVy2RPqCEMyP7R2bPnwj391aqjbbGBxKbMbCKBAnZ3hrSMyJ7v9uQPQyBDBtIr4kaoahFXEMb5QeRH0m6xPl7Im/Zoa2szFBQ3GLWGE9Tx9fT02mnJdevXq//oOJm7WEV741FYmNI9KnzKxCnwGWOA1io08CSlMwPCaIKpu1ZSUBt7N5pK3E2bN+bSmJMkzb0ZHzu37wzbt2036dOjRw/bwQOk7lAle0VSXufOn7dx1KA5BMk61Mz29/eFHTt3hq1bt9qhBKSysCXarnHVJnuLizTnEn+yWlBDG3sw3vLJTJ5KQL1MMjmDMg8nWGOf4Y9gNXlHTPE00ZEP/6S3IpgrbN61EGNcIglmItATk+c+JtmrpBZVvhBT8Muyz/3Jmnmd3E11pCfIcgJ22ivUi9ElBsOt4Tu21ty6NWyEGE6N3rxxU5Lg5yVBh/rLb2Vz84TZqYOBSt64es1xJvUn/KIkF/WbzJXMbUVcWWQuoEE/bPu1trSaJDanU5Hk41AEsJh0ofIYuT0cRk4Pmzol5s1vJYnc33cyHNWhj02b否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否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否56c6VeH4yqyCTjMC14eVk9osXLzAhP4ZJ7bxdgShYwfQS/XR69WXUyXXGcBK+Z08P/vbIBDsn+GrJUT4q7LhT5vDhIzIpjweZERH+bFCGQr1KpohUnFAZbPJV6/PmiKHorTb3StKThk8nfUKrzS1OJ+dahgA2Acs5/2FDxR1coA9Nh3zYc3fXsePH3cef/MSdws4v1tHXcB4nz9jkxz8VVlSG0XQmd5a1YLcWw6hkGh0dc9euXnP37t2T3b00wfgJ+Hz66a+xe+wDKO/2yrt4+PAiFE77xdSmytvc3OJOnT3j3nzrTTFn293Z7fbgYHEqw27fueP++29/g7Px/uxmF9L3mHWGiu7jkPf8hQtu/8EDrmcvznyDEu4JlIFffPaF+/N/fCZtoebDKxV73VidzEmNYyeOi+ncvXt78cosuYePH7pr16+7H27CXPHwKJSSs7KbkO8rlXlLS9MwX9uDyZLj7vLly+78xfPuIM7KZPmpjGttaxGFI3dJ8/yQx48fwzTRXXf1+6vuGnYt8py/7GvOtKqI5HkkbPubICPbM7+LGn0DaLhIg37izV3bsSNPmmLeBSVr9y6aRDrlXoZ8o9hxx117NI0JFlAAJo/dLxhhbuDLf+p4JzU6CfKx3ptf133tThIoK7lmU8T11KcrIDdPTSKQ96SyT3atgpfnx3pKRwW8LCygwm5uVtqh3JzLs8tNsh0C2RYEqJJrdhyxQ6HZDo/XymAIGALbHAEuFmZvJ+101FjTEs19jIdvwoLCKy9dkcVahIL0VGCQlOPDzo4O14exe3d3qrDzY39SmzMEth4CtKbw9OmAe3DvPuaLnqOu44Mm14XvEH1v6A1Bsnpe0mhkLoPcQB1/5kaGwGSOUAdg5YgtzhCocQQ2oj6bwm4DK0G5Rqxc3AaKuIWyynYi6tfeJi5KubiYrtbu88qynIxaVtJp+jhM02uYp0kHqJom0EUzprqLLm2YONkRtGrKVq5UkAUNGtnhL57kJIkEs6Mu6KyDTJylxC1否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否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否c81qbtoTBKpUL9YZfQcTamzSJhK4N8fVmPdvclI2bkV6hrfRtLTS75yJU8KwICMixV4JJI0oJGdb4GnJpE4YUSTlj5fSr8EOmVNhRsVbb/+u79149iZw0mLI/jAoxJfTW5yhx0VadLGIC3zIi70a5jipHnzqnkUhIX0+t4wjrKJ07jgzTKg1Kqk0111JNXkeqOygF3imFb8+FF6yTehIYV3ybNOUlfjRnOtBq/a4xE/T8V/NVIqn7XwWE2+y6fJPr/Un8pciovWrFLxlYUn7QzItW5L3hzg4D9XVtPc9zQmJ3VBUmWcdxYVnwbfdv8E0+e4s1Cw0hoChoAhsDURiHvU3BYcgzuOWV88H3bDL4Zl1/kSFrRIy+87gGS8x112vb29sBRx0D16/EjMSdOEvLokr3Aj+eVmqinsagisHwI69uMAJv2GYeX0lZIKO9b5x48eu+fPXyS7S0UikPg5PY5/+P0mQ0f5nmO8jmV9qIRIsu3wo2WrvW+L7YDu5pdBny8l2U7P2BR2m1+3TAJDYBUI+A55FQlLJEmGoiF+NfzTgUKJTAqDmUU22wKK0gR+oOLzk4FKnE5Exw8n1GP+8EjjjSgO4KdxtssczmDimUWpixOkoZt5J2VgcSIhxkbG3b27d91d/FG5xZ0xdJx8wjS6VwQIMOmUM2MUcM9LOWLARvOhUDrwQGJO0PPMqaM4v4pnSE1i8p6Zx52gZLbKn1SiQgY0xbdr9y534fwFOUdvV/cu/7wIQPyQVezC5DXjI07baZBQM8BuqCA57UAcxDqI55ytitreBAt0SXywQpmUgNVZ0uKnjh5WccYGBsyKt/In4fxJnebDEM/Lv93kE8elKRCOHOQdZkb4W8T7Xo82UsxRIqgRZ+HxbDiemccdOlTgNaI9aGzwJjDJSxR3iAuWJpEX8sWfyICfUnkzLctU4JgWAUzj0/MqKCTEoVQJX8armctGaCzj9ywUS8omfJlZyFTZav5qHpM4JHmGSM2z+OlqartuJAJ8PvFz3si8V5JXth6tJO1aaVm/4/wXsBiJ/fbExLhMOq6V/85JH9ofFDiuczG2OwcLK6khYAgYAtsDAVnkOvTMPXn6FGek74fZ91ac3c4p0LTNb4TZ9iMYA1/EbqQBmBG8d/+em59MFXbbAwkrxbZHgN89qNZU2D0fGnL3cYYdF3hzvitxMmgEYfhGSsJL3gSmJeO3XsRW+bb是iaxOuBgLeFtB6cjachsKUQYGcU91wcxJX604Jl4zU85qNh1byqrOXyiWlUzjwZlE5p9Kq06udVXbkwpSm8cvec/BFjzZLXyHGCVP+lwT4vTqL6f2lMcqf8QoBKyolgTrowWiZsEeF98GPSeQYKu+zq81qcmJFxVVJYfzM+Pu4ePHjo7j944MZwL4XM0CQ4IJwNfQFMARsAJKniCT9itgfnZp3DWXYXL1xwPT09osjLm8Au81Qy0izv7ezqFHN37737nigMW1taChLx2fjnoyUriM54Ckpb8CZnCM2biwAx1r9cgioEFj6j4vw0/+Wed0y3HG2lYoe6BnbZOi5SZ0VP2KosGYICsRDHdin86burKXj1LqWhUm+5tinhpwzBRKVRjnr1eaU5MZy0PMujo70D53h0uQ6Y4mxpaoHlzOwwMciFfJhGdtyRgbQlhTwZvLzz0pBO5BUWWnbPtoBrSp6y1oLiSjFk5WjYVcsock7/wYdACRdekiDlldxpRvGVqTzHhMxudjgCvn7w/Vveaf1ZaR2K03nFuK/nmRyDCBzXTE5OOI4TeIZd7JZrR2La7Xafi5lAq/j6Euuz1Ot2w8HKYwgYAobAtkFAm+8wsEvGwlGfTOXFixcv3MNHj9wgFHeqvMCytWRE19TUgLObD+H787w7DAsvzc3NCUSFvbvv8zHSTOLtxhDYaASSah9lHNdILkbn/BDNYg7DxKvWeSFHYnld+G0ZpU9vtY5rSEylcXFupCsVrjzsaggYAqtFIDsTs1o+ls4Q2IIIaHe39URPBqQ1PmDMRVj79AzsShsPCzIkMrAQOvxwsMEBgv4rhILnIXHSN3BQpvDTrONcWIEeD2D84CWb4+b6EyWVyh/EGcfK+QcPH7iHUNiN45yaQkdin8Cbzkonv304JvwkgQ8XDwOAl36AtLS0Qnl22f30k5+KEq2trVVSFP2kSYqiVhQAPvv69rrXX3/dffDBh+70qdNyNlfCQ2ba1KeZ8lo7Tt/J7TnJV5uYr9fTz1MMJ+9ieGGkbVFYQljShoRwqba4p9e/kvSEtgnvG9soMSsJipAkKZL3M1x3sGmb50n0LWcefHU1Pa90KotcJcRTSD0Nu9OkDBAs07wIdakfps+awmTmWu+ZXx7DbLD6y+WtZUpkQd6SPwqs+cV5efqII261/JofhfP/vJjEwOPgc0ljk1ztZgUI8Lno3wqSFZEqj+Q5F1HUXoDKvP6SpbU5Ly+Ob8bHJ3CG3aic06s0aRumIXbNbawAiz7LrVT/7GkaAoaAIbCTENCekFfv0jvxh8Gd7DZ6PuR+uP2DfDvPZBaykJZWJQ4dpsLunDt85DAUdplFo6CR/PBTapwredqPIbAJCPD7hzXUX1lH8TWDvxGMA4dHR9zUzFQildRjoQ5BSESL6hqeEG7DGxvbbcOHGhVpuz5fM4kZPWS7NQQMgXIIJMOAQKR+etnNx07jsuF+YpmUxTE+vaaMuVV0nzAs5MDxOgfXeuUAhue70EQGV6LTv9XcJGRfeDrgnjx5DLNXE4n4er6UFkmQUDh4RVGltHIN5dZ4ieYAz7nW1haYpjwPfObc4ycP3dWr37uxMezkExcYBV86OtSA/Gv4bhL+SsGOtR7nYjU1N7pDWNlIRd2Jkyfc7j27xVQpnw1pxGXEDV6JSoqQofUJ7XdnIaC1Ia4hK0OAdW5d2wWKpmLihtU2kTaKk/AkonwZEnY5Z否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否X7mQW4Qol8+1o9lnQX/4hZFPIVjoyK/GuFNEXirHhPvuw3HDIczPU9rX2n0f8PmhDpUUq9JcdnzAyekfm52BFAOObCZjFpMmvxIXswiUJthtDwBAwBAwBQ2DLI6CdW9Qf6jiRUbRSMwJTmDdv/iCLRl++fDktMglAzG/RPXv2uBMnTrrDhw9jh52ZxExBsrtaQ4CfSOlYF7M9oR7r99bi0iLOMx53AwMDMAc75GZmZ5IiFH6+8U3x80E1PxmWlMBuDIGdgUDVFXY62Uv40gZkZ4C51Uupz86eW6knGQ/7sjQ6SsyGl0qTF54NUz95Kv84jOFZP8Oq4fLy07By/CuhKZV+ubSl4j0G0fhcMpBBDO6y4dyFRmXdxMS4KOxSabj7K/XV1l14zkFA2tuvb8SKQCjmqAB7MfzC3b9/D393XRNMSzY3tYhyjBj4DxBfMCYXFgiXOERLGP1alzC4o2M7UI8/xmubwHwP0pTlBx/KDrwOmAr505//A8rCIdndJ4d4S5qgIpTMQu0Fn6amZpyB1+POnj3jLr/0kjt6+AjCmsAfmkUvouQtP5RVbkLZMwQamiaI7zQ2yzSm2dx7xXRzpag09xTHwn4iDU85KfYM0XgN41XD9JqmLLzTNIWhpXxp/Q0UIbnkkv6EWp7lvZwspXLNhlfCpzBvfQ+FkyYPgUoprxHilFbIAq3SpJJoiDJjTGFY6vN36idlnIr+2Gn+SkRaKu3UJfFJfiTwsb4poKdcDsopuoY2JAqRW82rRHQgX2FenvOq0lbjfdZ3iwJUg18oyI681D5+vm7qM/fyZuur+v1LlNZ1tHbJe8UJGv+nD3oeu+InJmFFAH+y6z7Q+pcRPFNGmsSuhoAhYAgYAobA1kcgp3+jYoK96RK+b7nz/Pbt2+4uzvSieUx+Q/否B/aLcuQE6Phd2tOzR3bgtbeZwm7rV4rtU4J07Kdl4gAvGeThXsd4PoyzMUuo42NjY1BWj2CHXaqw82NBLur2TsahSJbhFmL1otTq12v5VEplV0PAEFg5AlVX2KkItf+xrJLa1RBYCQKlOqpyPMqlYQeXjc8LI/+4MyyXXzXiSslQDd7V5JGPSVGoDOCpfPJn2HmFXeEOu2pKtV68WFN82XAXfZSwPN9++637/e//1S3ML+Cw7AtQ3DV5GnygpKuo0oFZIqMwZDh5gn8Aj1e243KeHSblec/8aT7k1Kl+KAWbsDvumHv5lSvum2+/cddv3HD3sdNvYnzMnwtAWSW9Zyi78CBzP84E+OQnP3XvvPWO299HU5jMSKVhDnBBCcDgECLB9mMIrBwB1qCkgkl9KqxXa61hKe+Vy4YUKl4pMeLwQFsof16ucaK8eA0LDNVb5srXNOuycrBJEro4+6J02VRZruX8RczKEa8yrnJMVpnBssmkTVyWygh2JgJ4B8Lsoyr7oqGAQDI3OyfmqqemptC8LGHhTTBbDULp53cmcFZqQ8AQMAQMgZ2GQOggZfSIHXbT6BefPHriHj146EZGRkRhx76UO5HEhWFmc0uL6+3pkT+esc6FMKWcjkzjoW8pWgs3BKqJAOuuzM9wfBfqOvnrvV5pXYoKai5WlzT8LmaVZ+XFXzoVo7WZPMipUqffTitKVClzozMEdjQCVVfY2UTD1q1P9uyWe3alOiHt3DRe/eSnYeV4x/RKlw3L+pUue43zqzRNlof6mT7mF4frfV68xq3ndbmycVu/0hROUS1g0DI9NYlddjjDbn4uEVJVWZtVokSQ6CYdLEEqFMcPqLykC+HjgrvgFmAC69r337t/6e5y3d273PETJ11be5tMztEECGkED4WEebCgwhO72+hCZtoO6JVR3AHHj5lFmODEZw3MY7a4U2dOyd+ly5fcv/zh98799jduFGfbzeBjaMalZrhk5SKZwLV3tEOZeN69/94H7uKFS66BuwSx8os791g4wR5yeDFV2MInoqGeY6nfwjSlqDY7XAfSlCPGe7PlKs4/RR2PKbjkJvgrxbxUOg0vxyePxoclb3kqoAqKa8xT33RGx+H0l+bPWO/iNEqvcVm/hi9zjVmCVBXsGlzANQQyjLfFNBoSlyY否yTlfCuth14O5QhscavtFuMKZGfm4uJQTatxvGp8HKdhnk7z0FSxP7cKKGFF1zjfihJERCrnynnkYa9tRF5clKndblEESj9XrUcsWNZMUXjPEM76IW1fRD47N+smp7EgKYxv2L8ymmMBc4aAIWAIGAKGwHZFQPtUHTtxbMiej30g/3gMxMzMjHv+/Ll7PjTkuLClETvsSF/fwO9h30+ST1d3t5jF7MPi0sScYKYbFb5h0Kl5gok5Q2BdEWCV0+8eWRAd5abhDGI99vVyyU1i7msYSmrW+ZaWVqnq9fX8JvZKP9ZlujR9XNk11tPk/8b0+RQWaggYAitHIMzUrjyhpTAEDIFaRKCSDrUW5V6pTKUGBQz3cYUD55R+HjvQxicm3TjOd+FK9EK6lcqxsfQcePGDAuMrfHUs4phsb7qDK6fuY7XgZ59/4a5evebGx8ZFMB10yXQf0jK9/nkCMtI/CRE/aXy4H7gtcqIP/7lSv7GxyTW3NLtWnGfHv46OTjEnwvMA5/EXO2LLHX/Nzc3u0JFD7sdvvuHeevNNd+bMWdfa2e4asEvPTzf65+MVFcybLn1m3m+/OwMBff6VlHYltMqvVL2KecX3mi57rYQmm6bYX0qaUuExh+pIEHOs4D5XsNKSkDw3SQVZaRuUXitKZESGwDZAoPCtEZ8G8aqvHO55KwtyQqln52ZkQlJBYL/NP+nWNdCuhoAhYAgYAobANkXAf8emhaN/KXSC/FZ9PvQcx0g8cI8fPZJjMhgv/WToXNmn9mCH3dmzZ92Z06fd7l27yg5mtXtOc7Q7Q2BzEWCd1veA8zFcqD4w8NQNDj6Tnaas0OGV2FxBLXdDwBAoi4Ap7MrCUzqSDd9WmugvXRKLqRwBPy1STM9hWjxUU38cpqmUR0yTpdM4TbPSq+ax0nR59MqL1zyn8Xlx6x2W4iRSBBEZuqiaKtwLFX+Cm8cZdlxdNDk5hTPsUhMXseLIk25m2YKwsQgoA3e6yWp5aOyo3KpDOeWcOZAvQKk2DPvkD+4/FNOUYyNjopBEAiYMRQqDN+IjSji/cp/rqwpdoEcgV27xj+2d5I+PGK7Un4DC897du+6Pf/yj+9O//8n9cO2Ge/7sOeRYwPl2aXri2tnZ6X78xhvu008/dS9fedk1NUNRR35Y+d9Q3wCOkFFE4CeSl4Zepl2N82lXk3Jj0+hgWgfUG5v7SnJTROPnEYfxPlNZc59dnEbzj9PFYXofXzV9cRoNYVWXPyRjmMqVxEuY/uSFMg86jfO+0JLkhGv86q9aqvgac4vDs/cxHe99mX2o0ubRxHRiglbM0OLdQyKmUyc8GEbTKcFikITBq1dPm3LUZxALU9y+ag5515hzyjePUsPq0CDqX1YypVndlfnrXyUcYtkroS9Po21EeSqLXR8E9LlXVgdLy6B8Yoq8sDie91qXPK34+APHXjLpKTPizWIyciEy31XH8YKOAXxy+zUEDAFDwBAwBLYdAsn8HPpF3/fhKl0oekxctVedwG6j+w/uu3s4/53fs+I8Ybh3btfuXe5kfz+Ocuh33VTY5Tjy45yDzQnmgGNBG4eA1HFkF83sS/2P5mOmMO81MDjong4+FSV18mnDeh/GlqUF1jdHMypNudIYfWcrfYeUfqX5GL0hsFURqLpJzK0KhMltCBgC2w8BHX8kVwyq52EmirvrxmC+cXZ2ZksNsjmmEmXYIsxg4p46NyrtVEE5NTXtvvnmG/eb3/zGTeBcu1dfe9X19vbIg5VdcrKFDYlSQMQkJQlUOcYoGY7hh8qzeijVODiS8Rxm7Wn2cuDJgLsOBd3NWzfdl1/+1X311Vfu4b0HMgCkaRFJyfRI1NTU6A4dPuRef/119/6778uHTwPOA6BLJhFVHgm1H0NgsxDYoRWRL7wWnfexY7jGMZz3WRqGmzMEDIFNQSDpR5E7xzfTGAdw1706xrP/NmcIGAKGgCFgCOwIBKKxqu/+ogAMZMdwntf1a9fcsaNH3fEjx1zv3n2OX6/q2G/u2bPHnT13zp09f859+/13GmVXQ6C2EWCFj7/b6MU8Due+7t+HkvrefZzN2Ot6entruxwmnSFgCAgCprBbZUWIP5BXycKSbRICuoJj5c8w0/utSv6V8Mijzc66KE0cng1TvwqcR5sXp2HZa5ZfNn4j/HEZ4vwYTvmieAxS4pELTWJOQpk1AbOY8Q67mIu/3+ByRiInA62MCFIS/MgEHBRpVMLNyblyVOTBz7Psrl4Vc1jYF4eVgSe8wg47YxYXaLkff1DaefNY3NWGEOye41Vm9CCDV87VYecbd+bRjBYCQ/gkdiZ+DYXgP/2vf3L/9m9/hFmFQRzaPexGhofd9PS0oM6JQspJedra2+WD5/ix4+7IoSNub89e19bWLvDG758qC4ufQRqi8JC3Og1TfxynYWu5rr6tWEuuWy1tuaegceWejMZVQqvYaBr1R294UZQPKApG0pXkmFKnea7mTvNcSdpU9vQuK08l75Dmyb05dFT2hxu8sBrmgzQn0fH7IFmdXBgbIpL4tHRpOs+Jyv9ip7n4mOL3rTC+OP1GhNSCDL5dZmnjdnMjSr+z86jWs8/jkxcWo134vsQKN74nfHWT11ci2ZcvoO+fFVNHHOeoE3r12NUQMAQMAUPAENjmCMiYGP1kPGbSMSb7zknMAdBKzJ07t90IFqIuhXPhpWcOnWs7vmEPHTzoDh86LJZiYsjiHlrSxJF2bwisEwI67stnr7XS10jSJt+GuOcOuydPnrpHjx/L0STKw48vE0oEKx+9rm8Nj99RlancdaX05XjthDht9wy3rfu0TWG3dZ+dSW4I5CCwvp1qToY1GMQBBnHQgUbwBkm5Ap0KO/5xgmsrdWB6sDDNYIppTMyKSxiKyp1sUK85mvm48cMN94c//MGdPXPGNcLkZN/ePtfS2uLq5EBt/YBBIvCpj0d/CWQYuEGRN78wh12Ic7Jbb3j4hbv/8L773T/9s/vtb37rvv7qayg8/Zl1lMcrAcmTeHNHXp3bBRMily9fdm+99RZkOeu6urq2FN6hytjFEDAEqo5AYV+lHxRVz8YYGgLbEIHk7eFYAH98f+ZmZ8W0l5zPG50nyzh7v7ZhJbAiGQKGgCFgCOQjEDpJdI/ifD+Ykk7iW/kuzGHeunnLvXiO4xywqFWdKu8aGxtdV3eX239gv+vGtQEWZBZwrAadn2WQD15NZldDoKYQ8OM+/wJQHUfTrzy38R522Y2NjSWyWi1OoNh2Nzb23x6P1BR22+M5WilWgEBtKmiS6ZdlSlKKLi88L4zsS4UvF6eihdGveMvxUvr1voZVQXIGk89LB+ixpCoFFVzcDcadYKwLfrILsShK4KSkG3utAEqeHccz4hw+Iprw4cBNMXWL86K0U7OYLA/Pr7l69Xv3X/7L/+kGnj51f/d3f+dOUXkXTFEuVzB28HPzs+7Z0JC7dfu2+/77792N6zcw0LvnrsGEyJ3bd+WjRd8lsd+P3Xv8mGlsrHfzcwvU2bmOjk732muvuY8++kgO7abSMN/pk4pBYFjW7/WBnkccl891raFavrXy2arp44FeaSyyz0GfJUudjYuRyMZl/TEfTac02TgNVzpelSaO0zBPl8akdzGHcvcxp5WkXgltcf6aK7mshZNPGzWZEb84D+SijakIk82zkDbdVUfiLK36NY0wTH58/VKaJHgFN8o35pEXtgKWNURa+v1LhdT3tRLaNFXxXbX4FHO2kGohwJot7yY7WryjrPUzM3NuDBMyExMTyWIa5sfnualjGwphzhAwBAwBQ8AQWGcEdPyj4xi9spOUfhP5sz/kefZjo+PuMXYcvcD57/N67qv0qamQ5Edl3e7du2Ehpg396zj4+EUynp9yZVfMe9/nphzszhDYAARkKIgf/sdfkUMYrSRxTmjgyRPstpvg0FFc8o5o/SUTuvCRqHQ+0H63EgLaJm0lmU3WYgRMYVeMiYUYAobAFkZAh846wPADFz/44KBkDoq6mekZtxDOeKmD2cd6zDTrirpaLTo7XSrEaGqyobEBJisbMJbCTjbo7xbwj2Wj4qwRcSBxz549c7///R+w6n7OHcDqwLb2Nuxw65adeeTjx2n6cQEDmkwPZSb5TGFQ9wI76n64+YP77LPP5O/a1WtuCAq8SZhU4FiOMhBj+fCZp81NCRYZmxqbxPTlhQvn5ey6S5cuQXnXAQI+B/75fEtjvVx86ZQWsxMRYH3x7/hOLH1VylwT8Nl7X5VnaUy2MQKZFxWdsL410zPTMFE94kZwNs8sdtsljv2u9L1JiN0YAoaAIWAIGALbHgHt+thPSl+JH9FDoFvkN+8o+sunUGIMP3/h2qGQ47d2Pb+vQ0L6Ozq73OHDR9yhQ4fcwwcP5NgJKvy0782CyDSaPhtnfkNgvRFgvdSRIqqiLO5idaZVJB5jMjgwgN12E1L/OacUvyNSqWMG5IUXpq5wReZ6F8H4GwKGQIRAzSrstKNjp2du+yOgz5sltWe+/Z/3RpZQBith5MILz62bmZ1JV9NRmMzgZCPki+t5XP8lb232ohFXY1OT6+npEbOS3EE3iom5KZj1bICZy0Yo4HhmjT8/jso3XwIqJ3+4ddP9X//3/+Ou37jh+vb3ySrBPXt6XHtrG9I2ulkM4Kamp0RJRxMJVNYN81w6/D2GnfNbd+64+zigeAgKwFmcj0NZOcDjFw/P0OM/KgB5Wvc8sZ1ZwkfNYffjH//Y/eznP3fnz593ra2t/r2WcmnhyqHMAixHF8crUOV47py4uD7F9ay6CCj+lWC/EtrqSrk6blomlXt1XNacSrMP4qg35quSxmFVuVfGeZmWzEATlSSIIsg4S6+ZZcOjZGu6Vf5ksl55rElAS1xTCGx8fdG223/6aP5aVzkJ6CcCZYIF0UpBtR2VdJx8HBsdK1TYsa4LC+VTUyCbMIaAIWAIGAKGQFUR0OlDVUaQeV4PSIs7Dx89cnfu3XWdXZ3YTdcd5CA1FRV1rquz0/X397sL5y+Isu7Rw4dCQ4o8ntqPB0Z2MQQ2FIG0TnKGxo8TOUbE7JAsVh8ZHsZuu0lYbFp0oggAkb4vIigTpUwK41ZREn0f1m8+YhVCWZIdg8B2qH81q7DTWkSQ7QVXNOxqCFSKgE7jkD7qdSV5JXGaJo9W4yqVZT3oYhliGUvnxbaEf3Nzs1BSzcrKIj+MIS+veCqdevNi2P41YscaPxgOHjro9u3b58ZxQPY0lHWjOI+vpb7FNUCZJyYx52EyM9jhr2+oE7OZoyMv3O/++Xfuz59/5vb29riDB/e7I4ePuh4o7ciXJhKonBuGAnAQSjmu0OekH3chcjUW/2iGkx89VBricDsBg2fcaV4NsM1Jpd3iPPJsbHanT592v/zVr9wnn/zE9ezeIwNEKvlE0RdDGR6jPsH4qfKZaLhPorGFoTE7u68uAqvre/U5UZZyz0rjlD7r1/C8Mmmcpsmj0TClYZpS6ZRG0ygdr9k4T6MUmmK115i78ozDyFfD0zw8RRyepvEfaJ42DfV+TVEYHvtSCh/K914h4AcfndKkd8ohjUkSSYpSP0wXp8nSKV+GZ+niuGy6rD+PVsNK8c2Gk2epNNn8Nse/uve1WNZq8SnmbCFrR8CPY+TbCMz0WfHKBTWjI6PSf8c77OQdBq1e1y6DcTAEDAFDwBAwBGoPAfaNdAUKCPjTEV3hOI6LVO/eu+OuXb/q9mNRawe+tbH+1CcIpF1Q5J09e9a9hDPZH8OcIBezepKUq/itkxVc7GdzECisjUEGqZP69cZxIKwoQUk9DtPpHCe2NDX7waGcsVIot1R/fV1ClHpz8ypMbj5DoKYQkO+mbMdQUxKWFqbmFXb6MVq6CBZjCBgCxQiwK9VutTh2O4fodLU2zHEbQiUTByiL4dBottuM39QxdjzqiR9ZCKeSa9eubrevbx92yO13nbClPz07DdOWOKsOu+rm6ngWX72rw8p7f0bdvJtHOenIYha73uYmMTiD+YMXz4fcE3xs3L1zT86Xo5JtBpN8PPNmHHb5STMzA0UdFIHR141gRDm8KU3urPMmNIkf8+DOuqWFJbdnzx53Bh81H+LMuh/h7LqjR47A3Gh9Yn6UMpkzBFIE4sqfhq78rlp8NGe+iNXmqby32DVuk9ZFdMN6XWA1plsSAelTl2l6OF7hnx+/hGLiNeJOAZqyHsbq6VhhtyWBMKENAUPAEDAEDIEqI6BDWu1mqbC7efOm+/bbQ6KUO3r0WJKjzCPA14kjHU6f6neXLl1wX331ZcHiU6pCdN4hSWg3hkCNICD1HT+sy+o4PqSVplFYY2hrgRlYRHCuJqVQysKrvjuFoeYzBLYGAvF88NaQOJWyZhV2WxnUFF67qxQBe96VIrUSulJdb174SrrhP否8niuRtQq0EEsHJHLlbJY4Xr0ZKW7/p0KKV3WsewmpBm7AVWXVrOQdCCLHcVSU0URHb2+vmJak/DxHjvbzWQrugCM9lWmkbRQDB/OiVJuVQ7T97jcf34BJvRn36PETjxXS6dl1VMJxxxzHdLTf70Xhan4eT0c6/PKsOjiGCaowx8krFYfkf4o76375S/fxRx+6A319iONZAKDBzryCdzxbXdQfyp+uBZPc+FPC+YSaPCUKjJYdfqYpttNdAdbrVrBi1IuzKkeTjcv6lZs+S/qVJi9M6Utd4zSeRkOUa2FKjS2klQqvhJmEGa9SJdeYYx5tHC+JMkQZr5AkacKN7EvNIyykzvFJUPqTME6Diu+yRFm/byeYTkVSCvV7nhqqOWT9DM8LU3peY46laP否StHGvLM0sT/mGaex+62PQN6z1WefF1eNEut4RPMhz/iefubNP4b7OPaZNPWtO+w4TlDH8UE8ptBwuxoChoAhYAgYAtsJAf2e53dq1jEuPouLJDM4+/XRwwfu9u1bcvQDj5ygkx5WPnZhOaa52fXgG/zIkcNu965dicJOvlXJk6aqJYUkZSbhhoxyBElj7c4QqB4CmarGWqjzKRrFseAcFHbPnz+Xs+w62tqxgLtDdpWSXupxqL5SdUPCpEoro6iKL1eAjZmTWE4Ki9+pCGyH+lezCrudWqms3IaAIVBdBLSh5nlr05jEoimABZztps6PPXQEoqG1deVAibsCh54NuhGYqxwcHHRjY+PyRUFlGs+q4yCrsQ5NOmh9mf1OuPpgrpIKtSUo3aanYeISirnFYOayoKTISP7xinSSL3Crg55OJv0iYkEMMpFvV1eXO3XqlPvoww/l78rLV1wbDu/mii06fQZR8mVuyX0Fo8FluFm0IVALCGyHWr3WlnI7YJDWpbWikXKyO0NgdQiENwrd5STMZA9ijDA0NCQmrZWf77utrioedjUEDAFDwBDYGQjwOzZ2OgbVHnF2dg7f1M/c7Vu33dOnT0WB19nZJV+gXmGxKAq6JpgO3L//AI6W6MWupFbpb0W5oYziTPReGKjHroZAbSAg5zbiHMZbt266bszftLe3FQpWVKeLAgrpzWcIGALrioAp7NYVXmNuCNQiAvHoVTthvVYi70poK+G3cpolrGZTx8F4PCDPSqdx3JE2PTWNiSwo7MI5b+Qhk1k1MKimHInT4iGI4VPYUcdJOJ43NzT03E3hsOCYnh8Ncs4czrCrlwJT2eaVboyjok42FYYssOk否MH8AXhLtpo3/UJPXaBX2AlNIli4oXzkhb9WfLjQrj/Pq3vvvffdyZMnxXxIPRR5saKOfCUNkoGzMJIw3oXyarElUom9p+g3FCUTrhzyYzPE5t0wBPS5MMNSz0ZpsvFZf8yDaUqlIx1dnF5pMzF1gSZqVzxF+ptw0Zs4ayULcdrmJK9VnK2mD2nUq2kYrOmUbbmrpi8oZlECpYoFSZEpDPWJNUXMKi8sjs+7T9Pk3eWlYFgsUZrOU2uchqs/5qVxcRjvK6H否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否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否S+vnCHHdMR68aGAABAAElEQVTY5OFm1FTL0xAwBAwBQ2CrIcDRKP/Y7c7MzLqh5y/w9xz3M2JthuNZqjCWZNhaJ4ta9+Bs+f37D8iRFeyLaVbQ+t2t9uR3jrw6XePrMc85hln1qSk3CsX0FMyra93FKScYd3pc/DvBt8KcIWAIbAYCO05htxkgb0aeSYOrre1mCGF5bkEE2CGHHnorSB+PH0RsKLEw1Naz0LQk9PMMO55fN4uB9yIVUcmwvEYKCmF1Uo6vLXfTcbsdlXX8o9lLOr7bsooPRWiA4q2zucmNjI64zz77s/vdP//Off7ZZ+7bb75xD2HigGfbEKLmFm/KcnZmztXhY6INO/YOHjrozp096y5duOTefec9d/nly25X9y7Jl3Jw5RUVfXQql3gyP+ngj4SZSHqLnlEOTcmgPIYliaMITRdnHkXbbc0iUP2+K1sXWCc0TGHIC9O42rvqO0fJtm4Xn30Gq8VZ+ei7vrWe5WpLbem2NwJam1lK1nD9Y3jh+1+HyZZp2V03NjaerpAOdDEfBJkzBAwBQ8AQMAR2LgI6ZCQC2Q5SB9Ty/bsEJcaIWKgZHBxwvT29snBWFsJgoQxdfV0Ddtftdsdw1vvBm4fcgwf3xUS1RNqPIVBTCGjF95WeFhj0e3seR56Mw1ID6/s8donW19XLLlL9VJZxJ8qi41AWK/vq1FRRTRhDYJUI6DtRbt5zlazXlMwUdmuCzxIbAtsRgVLdcKnwYgw2u6GjpH5oogMUh4mseVklN49D27wiKo0rLsHGh3jDA5AbYrHDIIbNzY2irGtubpEddyIVPhQW56GWpEKvDuY3muqxk+6e+//Ze+8vSXIjTRCptS6tq7qqdfeQ3RwO2dRDzgx5t7Mn3tv74e4PvLv3Zm/n7t0slyMol5p7ZDdbd5dKUZVa60hx32dwc4d7eGRGZEZGRGYaqiLhDhgAw+dwhwEGGH70zz90/8f//n+6yclJWRnI+nI3XUsrzGAin32kg6EP2UF3+dJl98Ybb7of/OAH7mtffcfdvHFDdtvRZGhHV4ecaUeTHw6DEXHRo9cWEI9pYBpEHAI07lAxDoQJrU9ufw2Bk0eA77u2PPXDUvPCmKaRXb34K1VuXnheWB6mIf7lpsnLJwzTfDRvvSeNhoX0dm0INB4CbLWplhvJB8rpJkwaraws43zeNfTjkUkjRPqBp7Vzxcl8Q8AQMAQMAUPgQAQ4wI1WxaxAiTEGJRx/tD7T2dkpg3TtVbmwlue/37lzB0dJ3JSz79bX16O+98BSLNIQqCkCbNZ0UdOWa1E+I4CWGlZXV8U05sryquvu7kF79yoCbesyZIpvJHmJP2URlUhrwYaAIZCHgCns8lCxMEPAEDilCFAigbIrNb3lJ64KUGBxx9kubXSDzAsqnr4RKksRR7jhhUzIOVnNx91wbRgoqOPOt9ZWmrVsx7k1M+7zR5+5f/6XH7kf//gn7unTUbeNs/ro2rD7rg2773ahoNxY2RDF34P7L7pXXnnVvfLqa/i94t5+6y05s66nu1tMhhIbrqzynEg2B/5pHPQOZNMiDYEjIqBv5RGTn1CyYD7hhEqoR7b2NakH6lZmoyGQ/ubohIpYDUCUrv5Urnm/hp11M9PTcgZJKp5ZmTMEDAFDwBAwBAyBkgioMoODcO022ZcuLCy4zz791H322Wfu4shFdxGLXbWPpc/+eXh42L380kvu8aNH7uHDz6G0my1ZjkUYAvVFQFu3cuHvd6GwY1t/+vQpFnDfdDeu34DCrleJUn46B95x7GbOEDAEThIBU9idJLp1zJtChLn6IKDCHEs/qeegZZxU/uUhF7axdBdeXvpKqMKyknRNNLINFzb3rLKO8ZCrxSQmd51x4os0TMMf4xrDec6EIeGJO+z8+XU83FqHEcIvbnf3dtzjJw/dD3/4n90//qf/5MZHx70yEpSsV3NLE5R2LVg5xR2F+66/v9995StfdX/7d3/nXoPCbuTCBTc0NICz7ZpdAeZCeWZeS0d7jKWwUASOfw4+2APHEB+qHPKOGEcuupAJRw1LYtNEcbxehA8nzlEiUzyEDcDHagbmnzAC1f4WVf+bFraho4BRKn26PR4l5/LTKA9Jmb7JazhzSuJ8vgfFlV9ymlLzLFVWNpypNU06p+QuTKPXB6Uph0bTK62WpuF6X19f3x1yUf12X9+6nY3Sw/ZTy7aTLDqSUqP+jYt1KB+QK20vbENrWNG/uMjz67ZxTm0r+vIWkNk5OmejDVotDAFDwBAwBKqGQLYr9x2qjH19F+vNXbJvpZnAUSyEHRsddauvvelZAL2OZ9kP9/X2ye6669evy8JY7Zurxq9lZAhUBYF0w9c2zKz3cVTK8tKym3g24aZh/pVWmNQlUnDaklISrpTmGwKnH4Hs97tRxummsDv9bctqYAgYAgECFCIolnjFnRdQeGYdzUZtY0KrYZwqmYRF/PGsxn4Tdrr1YHddf18/Drtuh+LNE7Ri59zU1BSUdY/cf/6nf3L/8i//6j768BNRymndOOgoFHYxebcDu/sX3NWr19xffukv3fe++zfuy1/+Kzm/jgo9dkSFQkHMIbRg154/Ny9igZmYMwQMgVOOQKMPq/idaXQeT3kTMPZPHwLRa6FvBm+lS/ZzifEbw/NsaYKL59jSsU+Xs0n2kNL6cMHE/hgChoAhYAgYAociIB2tp6JCYwkLYR49fOgefva5W/jKAo6WYP/qJVbOK9D19fe527dvuxs3b7jent54MY3Pxf4aAo2PwC4UdnNzc9gh+tC99OJL7qUHL9mwrPEfm3F4jhAwhd05ethW1dogkNXOn0SpJ1WGriQoJ/9wLkh1TydRV58npejQ6TQWlU4+3PNAEVsDEvodKKUK2zvYlRbNdiVRdbtiDTzO4BgsC9daGcRxx1tXZ5co7dqgsNNqteBcutGxUfcv//zP2F33QzGDGT83pItqj7Ns9mAOc9/duXvP/S//4T+4b3/r2+7i8EXY4O+S8/zaWtpkYo8KQO+8Ao9SmmxcDB4q8/T8RaTwhPdoh6OWmZTOK31GSWyxBKhxSpvkX/rKp/HsZdPpveZbOheLqQ4C5XwrqlNSo+WS18a0/VWb1/x89/eT8OB1jQpP4kpzE9YhS58XpzRhHHPPC9cwLT17H+YRXit96Gta0iltGBbShtdKG4Y1zvX5fXca5xkczEk92o9v1yLHoHi2EWknkWxAjvxiJM85FXbbO9uyS96HoOdlGtzUg3vPg/01BAwBQ8AQMAROAQLoKGXmgB1mptPkURrLyys422sW58SuYky961qbMWaOBG72s+xvu7q7cJbdoOvs6vT9dRSuY/NTgIKxeIYQiJqn1CiZVkqPmXzb9GFUPq+u8Qy7WZjGXIQ8uVMCjeglYQFaiL4z8T0C4rBsNmkesrF2bwgcF4Hwmytjp2NmWI08jsmCJNeZ2mrkZXkYAoaAIVBTBBJBJF0she9mCA8av7m1JTvs5Py6iFQmtYRGJYt0HvW+o8KOO+wG+wfd4MAAzqxrk91wjx8/dj/58Y/dj370z+69995HHQP+IQvRxGVPX48bHhl2d2+/gF1133N/8zd/6x48eOBasDSwsFXgjB/S7UHeghlMMZ/Fe684VHGq3vW38g0BQ6AREGikLwJ5Cb53jQCP8WAI1AqBqK+n7ML+mmq5jc0Nt7GxIROJtWLDyjEEDAFDwBAwBM4MAlmxEqKm9rM7UNJRkTGLM+O5m5076rhWVfUTpOPxFTzL7uKFi2IRh5ZrUmPzMwOUVeRsIeAb/h7a+CIUdeNjY24aFpy2t9PWqMJpprNVf6uNIXA6EDCF3el4TsalIVATBCh4luvySTV9VvotN9ej0GlZWrYXpMO6cHfd9nYhvcMuvwJHYeDoaVQKAi/KvQr5Mghowxl23d1YuTfgOnC+3OjomPvJT37qfvWrX7nJyUmUq3X3LFAB19XV5W5ev+ne/tLb7pvf/Lb70pe+5K5cvSqDD44wWtvbRGGn+EgOKDzgIJMr845ilUlfnIYG9BkCocsLizKIa6335fiaX1h3DdP02XsND/0wfRhu14ZApQhoe9M2pfeV5pOlPzi/9Ccsj5ZhebwobVheXlgYz2ul0Tz1Xv2QPhuWvQ9ps/npfUhzvq/DfuF8I1HL2oft8KD2e3yekuereYVlaxh3z/nd9zzPbg8WA6is28KCJE2vlOYbAoaAIWAIGAKGwNEQ0OE5U29ub8Hs9LJbxpl2nZ3cRYfFsU04K1ZkYiwQbsYuO+yuo9KO4/Ut0HM3nvXLR8PeUh0PgbDtlpMTZclNLP5agFnMlZVltN1kh51vw14eZbsXyy4iDmdk4tjiC4gi60vFZWfSFBNYiCFwLAR0bvOomeg3+7j5HLX8UulMYVcKGQs3BAyBU4AAO3/+vDAhvkgqFBiwCh1CCFe6bWysu63NTZiJTISQulculqjIa1AFMoYqtba0uoH+ATcyNIwdc71us7Dl/vjuH90//dP/6375y1+JWcvW1tZoUOAn8ThQuHfvnnvnq++47/z1d9zXv/51NzIyIoOH1bU1GWi0Y6eedkSCXsRHorCrOzLGgCFgCFQNgfD7WLVMq5iRfrurmKVlZQicYQTkjLodnl234eYxwbK26k11scp82/0fXpgzBAwBQ8AQMAQMgYoQyIjN3Fk3NjHuHj1+hLF5i1iwaW5pTu1s7+vtc/dwBMX9F+7LDqX5hXlT2FUEuhHXCwFRPKPNc55ofn7ercGnEq8ZbT39KnC8JlJmvVi1cg2Bc4mAKexO0WNvVK3vKYLQWD3TCBSLFazuDmxxb0JZxy3+PDBaHd+n5E5Da+tr+bECjauSokAq4wYGB9zA0CCC9tzswqx7/PSxo0nMpaUlx7PsuNppD3VqgxLuwsWL7s033nRfevtt984777jXX3/N9ff1i3KOZi872tvFXKaWpTUtrairxUR6jICyU4Gfx19efgwLaZWmgqKM1BA4EIFsm8rea+Jy2mFIw2vmpfmFcZrnQXF59Eyn4ZpW8wrjNCyPhmHMo1Q+Gs488tIzXF2l8ZXkrWWcfj/73T79NTqtNdD2d1i7Det3eJr08yW9yifpHlrpZHcdFHYrOF9nYwOLkTC5Ii5eCBSWb9eGgCFgCBgChoAhkItAbhft+3jOFdD09LPnE+7p2FN36dJF7KQbQjbsm/3RG00Yu3fjHLsbN667mzdvunEo9xYXFzByN2cI1B6BvOasc8ghNypPSktHO9/BAve11bVYYafxMtkUJeS8U9ppgMrEvOdP79PU/i6bJo/GwgyB2iMQt/naF31gic0HxlqkIWAIGAKnEIFEFNh32xBA1rHDjkq73b1dqQ3FCBFe6ji5JTyQG2UWPjsKnZ5rg+nKS5cvyw65Oax4evdPf3Iff/ih46o9Ou4epDzEs+5ohuOtL37R/f2//3v3P/7P/5P75re+6W7eukUitwWTWTzPr6OjQ3btiQwVyVFatGR4Jv9EFT2TdbNKnU0Esm32oLc0S1tLROpZNutZ7/JribWVdTYQOOhdZpz+Dm/dNLe1DNNFszRhtLqSUthRNsibnDkbGFotDAFDwBAwBAyBE0CgRBdNJcbjx4/cJ598jD53Foq4SP6MtBfsb3nm/L3799yLLz2QMXkTxubmDIF6IlCiOQtLOtfk5508l7uQHSlPTk9PwzTmqix4Z0yxks7TJxNYeh/6B5Ue0tm1IWAIHIZA3XfY6aCyUTWahwF4GuKJ8XHxted0Gp70SfGonW45E6Tl0GT5zMufYWFeShOlDW5V5+aFD65KR3tXLRiyKODsOn/Oy2Y8iSXJw+yzLNX8PuZY+Gfx3DV3GQq7oaEhNzE+4X7zm1+7Dz/40K3DVAEdz7DpgFKvf6DfvfXWW+4bMH/5lS9/xb3wwgtyjh3feSrz5P2HfX39BiheKXglR/0TgMsg4BSHRBcKnfqasnI/zjlIeliuGp+XNsgmdalpUoFVvbFvZGVwVqNfqqzEelFn22k5bZE02XTkX9NqnPqH1U3Tke6wNEp7EJ3GKW22/FLhWbpS98dNXypfCzcE6olA2K71HcrjJ5BhMtHaj+/t7rllnKkzB4XdKiZWONFCxxKklLijl2D7YwgYAoaAIWAIGAI5CGhvHPbQKm5zrMI5hImJZ+7pk6ducWExZa1H6Xp6etwt7K67fec2rOP0y/ibRbHP1vFhWLT25QzLiw9p7doQqCYC2t7ZdveD8+bYDmkOc3Jy0k1NT7mW1muur7dXilalXSJaai6pt6aabFpehoAhECFQV4WddVCVtcOwcy83ZTUwrkYe5fJrdI2GgHbIteCLZYUdf/b+YB7ClHE+CNzG4c/ra9EOO6xKV+ffp3xBWmlq4yvGidKO5ba3d7gLOH+up7vH/eEPf3C/+Ol/dZ98/InsrKM5TJrAGrlwwb319hfdD37wfff2W19yD1580fX19chOQu4mbIZZTdZTvx3ESM8FDmS02lSzrFLST/HgJCFtguHBaU4+lt9LxfvkSzvdJZx9rLRdHuU55bVvDaOfzVvjWFY2Liw/L20Yr9eaX5iXhilNPfxG4KEe9bYyGwOBk25/6fzDty+s/y7O411aXJRJFSrudmH6Wx2/q+lcNMZ8Q8AQMAQMAUPAEMhDgP1tXt+5tbXlZmdm3cTYhFuYX8DZdVF/C+K9/V2RuGnF5uKly+769esYh/fF40AdD9pcWh7iFnYSCOS1YS0nUbwpVSJlso0uQZ58MvrUjY2Pu4GBAdcLRXSxS9IUx5UTomWXQ2s0hoAhYPu1rQ0YAobAmUWA01Y8u44rhmgSMz7nJapxslKonhBQcPG/kJ+2Vpw719UBm5f7bnpmWs6uW8TZdTSF1RYp4i5jcPDFL3zRfe2dr7mXX3rJ9fZ2yyCBu+9Y11BZpzWkmHVcUUvzMt8QMAROMwL6NQj9atdH8652vpafIXB+EeCOusXlJTFdRIVdKNvYxOD5bRdWc0PAEDAEDIHqIsA+ldZ6ZmZm3MLCgtvaKkgB+zh2wg/fuVCzGQtt22EVZ1jOj6eFG09UXV4sN0OgmghQgRcq8VZWVqCsG8OO0gm3jnOS1XF+Kpyj0nD1daRHv7RTqtIUfobq4FwOSm1xhsBZRKCuO+x01UkjABsOcOvBl5avZes9sdGwo+B0nLRaXjXy0LzMP20IHLQK5qC4SuqZl082TO+jTlxv1Y+L007e+3yPtqiwW18TkxZUdtF5wYMmpIoykPha/tF3nb4KTa3NUNZhh12B59SA91X8tgpbYjZjDyy3d7S7rs4ud/fWbffGq6+7By+86Dq7umBvnJb191wLBgp8b7PvLlGptMbKEzEpTqshinseVRjHeDpN5++KVYhhGqVVX9OEvsZpOr1XGg3nfTZOaRJfn0kWv4Si+KoS2uLUlYcoj0xZ67Ir57Y4xWnkubgWh4Vk2xrbobbFbFyYVymaMLxUeqXJyy8MK3Wt6Uvlz3QadxCtxpUq56DwMK2WdRC9xRkCJ4FAtdpeXj7ZMG3z3k/2yKV33mstqaBbXlp2M9OzOGsEZ9jBRKY5Q8AQMAQMAUPAEKgMgYMUEZoT+1z2tfPYYbeMxTID/f2uCQNyKuY4LmePzt67C2Px4aERN9Db7zY3NuUM+R2kzbpwDJeNs3tD4KQQUCtLOozTsThlTrbJVZxhNz4x7p5NPse8GY+SCThRMVVoGU75NJJRQ7NNYZoguV0aAobA0RCwHXZHw81SGQKGQD0RoDBQJBBoQCxRUKQQk5jcYbeR2mHnp8M0RT2rwrKlOpCKKBg1Q0NG0xoU+mnOkwODdSjsdAU9OecKPpq/fPX1190tKO2orKPbgxlM0nGlH38UvvY5kggc0UkQCiLs0hAwBAwBQ8AQMAQaHgGvsFuCma5pt7K8gr6+eEKw4SthDBoChoAhYAgYAqcEgS2MyefmZ91zKDPEFDXH29xJFwyqOzo63aVLl9y1q9dcL4604Fg8z+lYPEiaR2ZhhsCJIiDtEH+4ONsr7FbdM+yum3z+HJapuMPOzyFx7smUzCf6KCxzQ6AkAvm9SEny6kTIJHJKZV+dfI+TC1cY6O84+Rw1bbZsvadvzhBoTASkmwdrtWijWlaChCi5cKs+Y/i6CDcRS1RWbW/RJCbOsMNKoXgVOt930jNRnVz8jouwD04iZlraWl0fVu714KDfLSgZF+bm3Prqakrxdu36Vfflr/yV+yv8Ll29EgtRzc1NWO2HX1S/SM5SecvXNATsgLoLloeC5An4Odefr4gmpK8Fqs9CNb4UA0pLvxyXzS97X1l++mzKKbleNMqj9RH1egIHlZttf+XQahr6h7mQNnt9W否wPnwv9FrjNV+9p69h6odx4TXj1WXz1fCsr3nS1zT0zRkC9UIgbJO14sG3fb9qOVq5HBWt3PBWV/vPUT7Agh6Oq6wvqNUzsnIMAUPAEDAEzhsCu4Ud7GqfcaNPR6G4m8POOiyUwZhbx+/Eo7Ozw127dtXdvnvbDQ0Pp+IYH/bjvDcplyiYqyYCOn9DP+uk/QWNTpRweg9/cwPnNcJywyzaOY+SiZV0SqMZ8j4K4649vc2SKbn3lSodmr4rhyadwu4MgeMgoHqpuK0fJ7MTSlsXhd0J1cWyNQQMAUNApGGVUahE2oLCbp0KOwgePPeFTuMbCq5Iymlra3ODg4M4j67HbcJ++BwOupaVfJE5T5q7vHz5snvjjTfcy6+86vpxKHABB2Dz3LrmlmYxz8F6SQcEEUr/NVRdjRlDwBAwBAwBQ8AQOBICNO+9ioU8i4uLrrDj+39dxHGkDC2RIWAIGAKGgCFgCOQiwHkDKuympibdo8eP5Gz5HY7LEdFEpV3kOjs73Y2bN90LD+67C5cuyLhc49QndfjTcPMNgRNHIJprypbjl4d5ZdnG+rpbXFh09BMlRpAwuMzmY/eGgCFQfQRMYVd9TEvmeBo0uCWZtwhDoAgB37HHS2yK4k8wQCXdTBFU0Mkyn2hZEc1EbeMMu3UIHbTFTZORdEInV/X7o98DMtMkTHte2lrb3MDggOywWwPfU1PTbgln1ahJzNbWVjc0OOyuX7vhrl65KiY3uJOQ+YkpzEgdqZN3ftU9SlBsjlDlw5LmreKqrJjDSignN81D/XLSGI0hUAsESnywcovW9ks/644al81H78mXukp41DTKj94f5Gfz13v1y0lLWnXlpFNa8w2BoyCg7TvvXSyVn7bLvLYahmXSa7I42JdZquTdvR05m5cygrpw0lBXWGuc+YaAIWAIGAKGgCFQHgI6hg6pC7sFNzk56T7//HM3PTXldrBYlo7ja1Vs8EiLm7duuhdxbAVNY7a0tMRZ+PG4n6bwI3+O/g+QC+KUdmEIHAEBtMtc0TIQLH3bTdokS9nBIrDVtVW3GhzH0oRtdNJSJa23AUElguQf5KflCe0RWLYkhoAioPOk+m3V8Gr7+l2udr7VzK+1mpmVm9dpAKbcuhidIWAI1AEBSAKxMBAJJCkuKDyAgIKIV9ityQ47VXpRNNmH8CFKrFTCOt0Ewk57ezvOqBvCDrteKBmxww4mr3hGjfLe3tbuemAXv7+vD2fdtQvDewXsHMypjH5rc6IOrWilaYqVdkGlpLT4ieEuG3coO4cQ5OWXF3ZINhZtCFQFgUraXiW0VWEuk0n4Xmaian5bbyxqXmEr0BDIRcC/CZzKC9/P5LqAlf6bW1uxXMCd9OJIYq9RLqYWaAgYAoaAIWAIHA2BJuxm33UzszCJOfrUzc7Oup1CIc6KY3SOg9vb28QKDs+X53nzzTz2InDWPQdg2OWJIBDO3yRSoy+qdPtLC49bWziSZWEBlhzWXBvmnZpFGqWCOZA1T4R7y9QQqD0COl9a+5LLK7EuCrvyWDu7VI3eKM4u8lazs42AChsqjlAhtwdF3ZZbWVmF8mvd0YxU4kintEloLa/4LRBFF/7s73peuDrv4sWLbnBoUExhTmEV3/LKirBFegpONLnRATv5PK+Ojr7kJXfU3fm87FsTAWKeIVBXBPg+ZodNdWWoAQo3TBrgIRgLVUfgKO0679vg+3ARUUROQB8f9fc8N4fyDBcjUcaJXZANRQCRLeJIuzAEDAFDwBAwBAyBchHQsTTp2SNvF7bd9vwWFHZjUNzNijlqnxc7X2/phsdatLd3uKtXr+J4i4EihZ2nt7+GQO0QiKTJEgX6WD9fxHkzf7+BI1kmnz9zExMTrq2lzfVhEXlTSyhk+nciCCmRvwUbAsdDwOYyMc97PAgtdSUIyIS6jaArgcxoGxoBdtO+q2YHr538ybBMAaKEyKFRQXQkbwhPhcIWTGJmd9idDJdHylW/CZHUozvs5Gw6mCVYXFySyTnOvslAALvqWttaY2Udy5TODPEChT4L3Mgzgc8i+AsPBmZxeb8j1SFOJBzgjv7hrvrtRmt0eNlGYQjUFoHw3Sjv/Uj407RJiH/HNJy+tn36YfhB15pfSJMN03v6IR2vS7mQTmk0TO/pa5j6YVx4rfH01WmY3ptvCBwXAX2HNB+9p1+JS7dNyiP+F7bfTN7pJMmrERTLfp6r+Lexs24Zu+63sBhpD+awSzmVg0rFW7ghYAgYAoaAIWAIFCOQndOQMTT6W/a5tHqzvLTkuAuJdDrGll6dN3Dd3d1YXNvlWpoTk5hhnk1NTOd/xaVbiCFQHQRKS4iZ/NFsdVEYY2jh6fnzSTc+MY5F48sYrfmcaPXBt3AQISiex2G0/piBOHkj9CbwS4UHJHZ5phGI280BtTS9SQKOKewSLOzKEDAEjowAd3gdOXF1Ekble4HYCxb8u8Uz7NYaWGEX1J6dE3fYXbhw0Q3jnLrCdsHNz8/LDkEOEjq7ulwfTGH2YKVTO+gSlw9+IFbFpB6Z+LYhLmxisSEegzFx7hCo5GtQCe25A9IqbAgcikAlMlJ+j+7PFllcXHTTMzNuDXKNTgDKObZU3tlreuhzMAJDwBAwBAwBQ6ASBGRhbJRgC4tmFhYW5UezmHuw3uMVGey5fe/d1dUpR1fweAtzhkDjIhC1WcqOWBCmbh3nI9P065PHj90SFo7v5QmXpQRVzcR8Q6AEAuH3tASJBQcI1FRhV442NeCtoS/PUl0aGuhTwNz5bQvs3f3skJ+Iql7PXYxpJFDktYdsVMKWTGbx8Fwq7cTWvGdXcqm3kihGCzwJLxFvLa2tUMj1uK7uLoTTpOcmVvFtSV24w45KO5rEbGnJWDRGeoECD4M282WlVBYb1hxh4U47n8g/Sc8C/0bMCFLhH41TX+Oy91owfXUhjY9PVs/QDENIq2kq9cMyKk1r9KUQ0PeRvrlGRSBs+/r+lcNr9pmG+TB99r6cPE+eRtvkyZd0ekswjCp9dsdp6/rOFfdjlI+KZaR0WQlNxDMCpH/MVGEHE4NLy8tuYX7BrWMFtJ5tK88aCjv9Rhfll8nHbg0BQ8AQMAQMAUOgBALapUfRcht17+xnqdCYm5uXIyt2dgq+j4/imYTj8N6+Xjc8PORaMa4vOssOIgCHVDasKoG/BdcEgWQeBvq6oMTt7S03NzvnZqZn3AasVOU3VDTg6D3Zh9Dpf0EmJcePXv7VMYrKrWHK5DoqQApKQu3q9CNgSrvyn2FNFXbls2WUhoAhYAgcHwEKAdub225jZc2bj0pJxhQY6ukSyd4bs/S8UCk3ALv3/DU3NQvf+9Gqp5aWFteOeNI0B2Y2fMry6xOXHF80Bg715MLKNgQMAUPAEDAETg6B8vvomAdq3iLHKy48mp2Zw9kik9h5vxIr7JRG/ZSoo4HmGwKGgCFgCBgChkDFCHgVg0/GhcDT09Pus88/xRlf4zjbrgDFBadUk/6aR1dcvnzZ3bt3z13CufQct5szBBobgURGXccZduPj4+4pdtktLmOHXXRecvgehO29setl3BkCpxuBmirsQi3+6YYNn6ho9etpr4fxf3wErC0cF0MKuPrzeTVB8D1w5YVKDIlsIQn9bZIX77nybRM71HZ2d2SzyIH5+uJr9teb0EgX14JVeR2dHdhF14FddLB9H8y8wdo9hCasoqfpq9RaKF9zkvpfstI+zB3m8l0zfvRlBX4QmaCWXAXRB14eVGZxQhQeO5YV8eIvoxjlIc+PSMTLiw/DQtrsdbl02XRn9z5c7RaueNNvXCO9O2f3KZxkzbTNaxnZe4Znw/SefuM4bZONw1HjcWIYHfeZsK/SX6m89P1gfCnavHBN5+P026ul5D07UnKicHV11S3x/BzsvtfvdEwfKPjYL5szBAwBQ8AQMAQMgQoRyHTbNAmofSpNYC4sLrjRsTExT71T2E0yj4haMX7n7rpr16+7kZER2WWXENmVIdAYCKBVx/9CjngkyyLNvsKaAy09qazJlyC5juackFDmlaIMkldH5dww5+Ra5Vb6icum0dxMoE0wsqvTgkB2bHdUvmuqsDsqk5bOEDAEziMCYQdeZv3Rn6u5R6bgzjSaw9zYhOkorA6iWOIFgyPkXSYLFZFl2CBv7R3tYkajF2fVtbW1ow5JjjuFbbcJM1icqNvbCSKCVX0JtV0ZAoaAIWAIGAKGwMkjcHKTCWHOBSjsZmdn3eRkzg671KTHydfYSjAEDAFDwBAwBM4HAuyJfW+8s7OLXe7P3YcffuCePHmCHXbb8SJjzjPQcYfdzZs33Ruvv+Fu374j59NLhP0xBBoVgUDY5CJ3Lg6bmp5yy9hhp0q63LPsGrU+xpchcEYQyByCdEZqZdUwBAyBGiCg2qagh69aqeXlGa7KkVUMFJSZNGKN57sUsMOusAtzFXQIl917+7tcJFRfF/EYssH6cFddZ0dntMOOayo8Bc+z24bykXbzN7egsEMd1Pl5Op55E4fohfhaRhKfRGucYhYVlxCkrrSAOJXEhs8hBj+VTm80vd5rPtlwjaevNGGYXh8UpzTqaxmaRn2NNz/9HA2P04nAQe06G5e9Z42zYdn7ylDRQZ61rcpwM+rTjEC2r9F71knfJ/U1zt/r+0LK+J2hsMLOG/93sbJ/ZXXFLS4tplY9M53kFAg2TB/mxzzPs1M8DZPz3Aqs7oaAIWAIlI+A9hucNNBee3dv1y0uLrlnE89wjt0sLPhEcwzIlotsm2AcpwXHVlzAzrrbt265S5fSJjF930weNMegv2do0I+TypwhcFIIsK3xR/mxWWRGtkrfLrnQnXNOG1goTtlTHNumTiZ5sVSCNSho0rKA3qfxSYv/itQaBeu7oH4xtYUYAsdBQL+r8Tf9OJmVmbZaZdkOuzIBNzJDwBAohUDY4ZaiOfnwWOjAvvwm7s2Ho4BRgJKLii46FUrkpmH+eGGJ7PDD3t7e4bo6obCD0q4dO+zELCbiyPsmdgouLy+7tbW1RHiSeoTKOgko6091xSK2g8ZoC8WVb1S+ijm1EEPAEDAEDIHTikB1+xqVayAAoHdtEtPenCCcnHyeOsOOEyx6xgiRq9Yg8bQ+BePbEDAEDAFDwBCoJgLau+/t7snuowmc8fUcO+04JueiWiormtBX02Qmx+4XL15yL7xwz129ehVj+/ZqsmJ5GQLVQYATQTIZFLVueNrOWcA6lHVzCwtQUC/6M5PZxlMU1WHDcjEEaoUAx1WnzdkOu9P2xIxfQ6BuCGgXXosPnZZ1SGVFsIhWkZOtzEeYQjPPfIlXBjE7HgzdRMG6FvU4mH/faVBh5+macH4dzWjQFCaVde2tbbHde5LswQzH5sam28JvF9cpR4IYNq2bD4iDgwRKEa+GyiMK6P2lplLi7D2psmERD6JE9XH7tFsKp5OKWn8fln00WpYkqcKfbH7KbxWyzmTB56t1zETZrSFwLhCw9n8uHvM5rmTSf+ggsLjNJzQJU否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否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否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否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否9daVqPpy52ukzyrk3Vf5al0K91XabPlNQRag0DR91S/4UVxraFCSyU/01rJT0gJeaJQlDA7BxkGygpM4nHnkLjAh5hWvw9apvmGQC8hwP7Nn5jaO/TXW1DQeXPtZ7JIjNYQaEqS7w2Vd1NQgE1CRkUm2Xl3iNX9x/hxPMCz3bja/wS77mhGkwvRODY4RTh3sp6GNFykpnV7n6jq24pLeQcpj/PHW7/LyC+4w6nOA960/QBMxw9B5h0MO+UGYb2hH2HcNccwns3HhW40G0+5mBP3u3zXYVZ+F8rGDZi7pKKOu+u2trYKz6cjZfr1SC55EZHr40lnCMx6ks3+GAJ5BPw4Jx+avee7cuOGtwrDd4XvQNEZa9lcdqevJzHjwuJNnD+5urIMhfxeyteZiB82/KjM58LXBzie4unTp8DcLzzm96eR52SIGwLNRkD7nu9/2qNRCy71TmdN2M+pZN7Y2JBFJ56v+nkl0sVuTkdLSCIFw5ewXOdO0vnkF/yrVF0wmyXvKALsK3Sm76h+DKVV2DXrYTWrnGromhtSi07tvEW11cpTlNbCDIH2InAZVss8tZlsvVgOxPdxMD0Pp48HEPz2S4m1i20JLBRu1AkNrB9hMRkc7NAspj9HTlN7n/n1/fbfgFBANlm486XGZSfJIjo0jOnEZGTIoEn8zjIHBWJ/EJ5Ig5PVwW+99ZasBr7/8IGbuXvb/fh//Mh99eVTTEYMinnJOdjk/xuY8jnE6ug+TGj0owzuyhsbH5M2c0HwTZlA0否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否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否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否OgnlSJjJywi+a9GOUFpckK7jIQ6B51CchmoZ77rzzsdjIWOVuJDSkkWyH7gjsH6ApF+e++N1v3X/9r/+3+/GP/xoTk8uyYpQmU1jH2++85T7++GP34YcfuluTU7JiKq5ISlZSQgTLzAXFWaquzwLxMmaRWM2d0l2V6VIB9crTOi9VsGUyBAwBQ8AQaCMCypOr5Wh+y/mtj7/p1d9+H8LVvz4d//Ja/9EyAE147+zsyI8yDHcg8Mdrzccm87qaDsaYMwQMAUPAEOg1BKiAkR92Tt24Ae6RDmBK11Tha4HPkTjeU6F0DP7W19/vRkfGcDb5cMYkJvlZzONK16gOE5TOd2BMHYkahwdHbhNn23NH/kD/gOsTc6l+Bz5NYlJZd+fOXTOJ2eHnZ9U3hkDazyOJmnIyfuuwxPTV06fu9dffcFOYHxoLx6WotC2vBW50vip9TShd02nKxmhJ06clNZrT0hkCnUDAdth1AnWr0xAwBOoiUDRhpWFk7jSHyckvrsThhBcdgiMpQIJK90cGLQmdXvTgOXYUyNmezA47pPMiiGZQ/yr否jHMrEHMy5XGMrwJF85l8hnQ1Nen//ip+9GPf+w+/+fP3X5l3w1hxSXd0NCAe/8777s//rM/dd/69rdkQCcRoUlsZ17c0vuiOMl76T+N4Nj8Wi9NrmU0BAwBQ8AQ6BAC5Be1nXIK5VdMqZOTKrMwjKai92DuJ+bxcTzTqNP8em++IWAIGAKGQO8iIGfY9fEcO52A7o62nsKeMy3dcNc4F6D4HXYwiYnz+Mw1ioBXwskoWCYyfD7Ob3Dh69LiktvdwfleoTjKDZOTE+4Rjpe4e/ceFHZ+hx3DaVa1xPreRgGxdNcIAc4frcMc5pdffumePXsm5jFjefo8KPS9yKbTeZ7i2GxauzMEyo+AKezK/4zOpbDWoP/cjJbAEGgqAhdhsRevmMMY/uNkFg9ipoKrUqkkCruLl9jGHDmZIZmQC5BxMq+yW3FHPAsAaVVm96o1JgoJE0n8fKzPT5GUmpZfAEm+HNIuuwIwfLiJAdoJBmxfY2XUj378I/eTn/zEvXj5AoO3Q7Tl0PHcHjZ9eGTEvf32O+7f/JvvudeevO4GYSJTHAvPV+BjLvA3B27dnEwb/+omtkhDwBAwBAwBQ6ABBMCtwcuEpeHCy+WeuZFf7uztgC+mJryVjzZQsCUxBAwBQ8AQ6EEEyAd0vHeDyhb8yq5xieecaGVlb3cXxx/sCu2TU5OiTOrrN4Vdw92VfYBb6+DHw2Eq7LgQdnFpEfMd23IEBcvkefBUjD54iDPs7mXPsOOziZ9PwzRYQkOgDQio1Ym4KsrHmxsb7jlMYs7Ozsq8nr4IPn2c2l+LnF0dHIXEb1IUnLm8yNxRJqPdGAJtR8AUdk2C3AtdFLza9wEwxtykh2fFFCCg7LARpsfsjabTtHF6VaCkcfo+MUQdTYawGsbJDrvtvMKuFmvXEjrk1/gkxEI1z+SjcE4FJE1nUanEdnqUFKvg66xgneakObySM0wjIofGFFwxir/wOMQyi9KucaFOCSYdcByoffbZZ+7//e9/6X6FHXb7+3sYuGHXHRR5ff033dTUhHvjzTfcm2+97e7de4Dz7AbYuoyilZ/NzKczri/QE6qOvOoIgUbI0rhsQb5fxXUxXew0n4bl7/PhGh/7msZ8Q8AQMAQMgbIjcDFZWr/1uVZ5dig8VMujT35O8900e723t5eMEYQXoQj65gwBQ8AQMASuHwLy/edYD7yCO9S4CJIKmTI65WsxbSc8Tx5nle9gHEi6p6an3MTkpLQlTmfXEA2AD38Zh2cvsgAXt+ZkAR6Xsbi46BYWFmSBssb7vtKPsfW0m56eljG1lmnShCJhfqcQ0H6e7+uc91En16G/y4I2fD8W0c+XlpYwD7aPd0FT0g838BjOn9YRp6p9zfz86XwQfd6bMwS6BwFT2DXpWeU/TE0q1ooxBDqEQBmYWU6wDUhQaOVKdSqKaIqDzF4cSBZm3iHEGq1WhHOROLzQwXxsAyf1eI7d0fER2pHHvxgLX2e9uDxV+XLz8V6kqQ5FCLOCLn7r+jGoPAPNh4eHMGHwtfv000/k9/z5C3d8eOz否SD9LQwm/uAP/sD9h//w5+473/nAjY6OSNH572V+DNM8WUrb631fL/G6CGZCcu7PVfPnirNbQ8AQMAQMga5FwLNs5Teet5Ovcdf8BlYP07QVdyKovMKU1Xy+a5tvhBsChoAhYAhcAgHyAR1XcWyVHx9dosiWZcmP1WRBChaa8giEm9hV5xV2EzmFXTPGXC1rUmkLruxV3ML8vOw62sYOO3SMDK08125ifAIKu2CxBrEyv9C8AXSmPrsxBBpBQOVa9bN5RPKVuSQNZ7qjwyO3IuZfF8LCtjCvx0Rn2X6few20mAv4qZx+gUyW1BDoKAL9Ha29gcrjF77MQgybUnb6GoDbkhgCEQIXYWqaVhmr+hoeFVtTmGRazad+nM9f85twCJOLXK1+CIWdfiOkJs6a8VdiR3qTb0VoJsP2Dw/c5taWrFSk/f9+MSkSEtSGI7SUCS7abl+o4se9eOrSKw0hrFgTBRmqr8/Hckfgy5cvcW7dP7rf/Pa3bg02yLnacngYO+hACq9v355xP/zhD92f/dm/gynM1yRc259gEKqgEMZ8iYuvJbAqIEmaXhSlicO4wlFTx+EappEaV+te08e+pmWY5o/jm3Ud15MvM65X02mY3jeSR9否Xt5rfg3L32se8w0BQ8AQaD0CCe9KP+qtrzSpIf8d5MIb8vbsWOAY5q6psFtdWXW70Q67pBi7MAQMAUPAEOhJBJQ1ZcY2BS3leIg77NLxkY6HKGcrrynI2K4gNiA0gm3S9pBeLtzkIto+mPMcGh9zI6OjkcIuHXNpnnaRXLZ6VF7J0AUw5emqz0gAxafOeQ6axFxeXsL5t5VkCMbdSYxnnxkcGHLDg8PY3XjTneIfB+mF9bBcc4ZAmxAo7IPs6AWfMvZjpj8+PnHbW9tYjL8jZz1TEc1vBqK9g6+XaTF6pTHaQA3nfRwXh2ta8w2B8iNgO+zK/4yMQkOgRQiUjXHFTDVi0mx9wrHDbrTKntvc3JRddrpi3YMUb7pvEWxNKDZP5SmkEprDpHC+trYmAyBWI82OYImVatVkRAmrI3Mh1WkpGGVdHOAFqtPTE8hbPpz4//KTX7q/+qu/cr/67FduC8pGfU5SFqoYxcDtnXfecR9++KG7d/+eHIjN51VdV7bm8t7FmJSXymrKqp93dZp6IVfNX69sizMEDAFDoLsR4IQD+Vp+ooKTmdxdNzc3JzwykVd84u5utFFvCBgChoAhcGUEbkLZ1d/fL4quGzxTIDPJfOXim1pAPA7lDjtau+GP4z/u+BqH0o6mPc2dj0AsL3CUxTG/+PhbwQKfeeywm5udd7s4AiROy5Kp6BgaGsKZgZPiM0zSdOswlQ0w19MIqNjLI1fo2IfVUTbe3tlxyyvLbhv9XY+HiZJo0gb8tNw0sb0YKRZ21W0IlF5hx5dZf1cBl0wsz+yuUp7lNQS6FwEyrfMYlxcZs23Mh+l9LcbIOjRNXFI+LH9PJu7Tk7mzDLkNYWTqXG1GBRGVXMkEWKJKiuvq/HXR94stisOpBKN5T55zs7Gx6Q6xKj9xaDfxYHoBIvf4fJyP0r9J3nBBiwL+hwuPZj6J3Iswxb4hB9jBC1UyFz6gci7dWVjBt7K+6v7p81+7Tz75BAOKOalgAANOPg8qINnG/r5+N4jVfwODQzIQvclzCAvql3rlWQsZ0R9tLOpGKH9xMo3VDPl7DU99TVFUSr24tARPQZxf44rya9xl/DxW+fu4TK07DtNreXp6A79eWk12mTRKX74+LdN8Q8AQMASah0DMQ5tX6nklxd+59FvneZgq7dIyOOnAHXYryytudyc1iZmmsCtDwBAwBAyBXkSg5thG2UhoNG+5Q43WVWSsV0YwChrD8R6trdDiDceII8PD+I1IW7QJ8WhCm51yTk11fX0/P+lHtyLTcJyM/4c4HmMLO44oP+zvHwi+ghIBhWNaKuxohnR8bNz3nYLxtU9tfw2BziMQz8VTASFLE6KPAef0eI7d4tIivim76bw900Tp0pb4CJ3jSsPjq/gLFIfbdRkQ0D5hOpr6T6P0Crv65FusIWAIdD8CQfqs0RAqs2LHAQJX81G5tYPVOH4VTkghSbPp47yluU4ED08rz4PbQ1u4Cn9hYR7nAVRSQaUDROcR5DiNKya5ApSDs4WlBff5r3/t/vnzz+VAbJoy4OCB+ahs5MBzZmbGPXnyurt165acecdm8FlS4Se2NS/Qrjw9vixfQALlBcqzpIaAIWAIGAKGQPMQUC6lvi+ZO+wWFxbdixcvZOItXWDUvJqtJEPAEDAEDIHuRUB32PlddjKNjcZwdJPlJ51qIamQidUcAeRnnGTnj25sbMyN4axyjgG9U/rVD8HmZRDgGNu7dDHvCbClqVHOdYjyQp否Soy5aX70wf0H7v79+6IozSWxW0Og3AhEEzj8lmxvb7unT5+6L7/6Ela0thqeB4uKKXd7jTpD4JIIKEe9ZPY0m2pI05ByXcmqFW4XMWcIGAINICDieS5dPkzvE0kzSs93rbH3zX870r1T+pr6xXxU7vh6tDSm3z/Yx1lvO66yH+2wEzKwswu+po0I6thl4bdRmoQ/QrMfCHHX4DJWFq3AHAAn+dTdQHv9P5+c10nGcBWHKPKCQbgRTJBIzRD4skMuTQPfY48LWa7k6eJuOf7jN5RnLGxBoKKy7tNPPpWJyDOc20PHNMcnx7LDbmx83L373nvu/W9/WxR3HIxSUUflKn8UzIgLssiPbbyBeKr8SE6RuwGa/M+3I5CNpBpOullmWm5ROdVhaUnVcc0KybdK61S/qJ4ATlFUEqZptJzY10SaRu81jYbrPf2iMIYXOc2ncZqXvrleRqDwm9bLDba2GQIJAv47l//6+Wj/7fNLV3zIERawrK6uusXFxQIT3kmhdmEIGAKGgCFwXRBQcTm09wbGSFwU2Yezy+l7aySdl6U5JpRxIcZVMhbk+AqO40E6juUOD4/kx7EYd3zxlzGJqWMypNdmd75lQn4J/2AkDXCItToukqUFHmJNl8bcwJnxQ+72ndvyG8buRn0umlf9YnlFY803BNqDQPI9QXXsx/pj7ZxD4rzeixfP3fPnz+U6eg1kbkj6NwrhGNTPTTEnvzx+t57cyJ/0LfGxvf8GdNu4XOnlM9Vf+vzsKo9A0xR2WjAfgDlDwBAwBC6KQPWnwwuuIr1KYekAobJXcZswHbm3uwchNvrmRJcXrb+96ZVQ7/O7uQNF2KvZl7LLjopIxUN8TR6IzN22hHTWW1T36sqq+/k//Nz9/U9/6uZhCpPKODksHVRQecf7O3fvuO99/3vuoz/4yD18+ADxVNipMi5QL4Ur6Y21yPcAnye+1lKu5je/xCw9zS6/McyyNNidIWAIGAKGQGsQwMAzVzAVdjyblmfR0CKAjZFyANmtIWAIGALXHAFOWHLspOMp+jI5XRJcONrQn0yuBrrIzw4PD2QnGFMMQYE0DJOY/f0DCeXG8xIo6l5wSKzD4hgz7rKj7CDnBLKEkIjKD54T/wBjbP5GsLPRnCHQDQjotyRdqo9ujZvtrR33FXbYffXlV25jcxNd3Sup48Xmmrcb2mk0GgLNQqC/WQWVSbBoVpusnNYjEAsl3dqHtA3dSn/xU46nncgem+my5aW4VU92cQCgah6lgEqhQ9hzr+BQ2gOsPFPhVeNL6yukaH4imIcw9iEq6ZaXl2U1/jEm+SiMqzvj7rLoXsOL/BjdWMhhWqJJ58tCgahX+28SEVck8WFVHw9CR3aesfclhKkvvvxSzBfIABODSykZRXJl5d1799y3v/Nt+d29e08Gb3zOg4ODoRquFfGrpBigfYC4pFTquijf8DM0htHaJqblRkAhyifRAJ+RUcn0qRQsIWlYuE28ojRxWJLwEhf5cvL3cZHamDis6Pqq6Yry58NiOuvFFdFnYfUQ0PdO+369tGWLazfNihVxaHfdZcPe6Ok0AuE7mPscsl/eEOaUjeCqeJ5dxwm34+Nj67+dfnxWvyFgCBgCbUJAh1N+bFO7UloZ4XhM+AiVdfjhRsJq52p9TDwCYG3C3fgn0EbZ7OjoGL9DkH/qeI45d9jRL3Ixd8yXXZT+OoURUu0v7Acq99IqDU0Fbm1tucnJSXczJGIa7qq7PYMddvgNDWV32MVlGNbXqSeVua3s5KBPP4j02Z8ZhhkemtZdxOK2ublXWJAfnWHno8OcT0iOMMnGuEs7LaH73xC+77HT70c+PE7Tyeuy0tVJTOrV3fQddvUqszhDwBAwBKoRIJPJMhp否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否aGUgEBAICAQECgGBMiGrMdSrFAF3LJTdUj+inwZhTBF56IKKKvA+4i34hjHE4J6wg5CusYGnLCDHbuUSsz1Kl90ldxbgoztMk4pml7kEEFsJ6EekGYnzsA2fBM20FZF7whPMra0tEo7NsHSNnyKr83JKXgEBIoYAfsg8CGwTxkaGpIXL57LcQjsuP7kBHZFTH8gLSDgIbBTfF9y3t7LPNzmIrBTgOfmvL7PfpS5PjUhJCCQRYCjqv04xcy6fH6Ms55/Nj2fkX80eHMHIm26TU9N46RdWmDnyWPyZVIcfgZVRA1ptl9CoKssT73NQuA1gRNrVP9JxNgfUIRlDJKmibBJ0qfvMkWmA/kUNYXtaNqIn6rGrr2jFNi1OYGdJgdNa2DUaFic9ufoFmGLbgTGsSmwI0Px3nvfk5/97c/k3IVzuvuS9SgvP6IGtD/7/HP568cfS3f3C2H7IghH/pgL68lr4piu0D6R8chAKhOMXasUAuruT1zpbxiyvvpLionKdchthJ/SGqWzeLwyb0dnpgJ+GXrvh/PeftmI5m+lIDy+tRuLk027wXOeJJZbKpV58rqpY6a+y1OIH3xA7wt9Vwt9nw8oTKFaBxSBg/1eZ/q4PF0cx1OqpJqawgYVnCyn4wIaVUe7zlu9wp9tIBAQ3AZoIUlAICCwrwgYr0EifN7BPYNfwemplWXHA1VC4FUFEwS+Ssx9JT5bOCqTGQU1Bk8IruJHO+SNsKfGX2pxPRorC2IlsmUe4GebQqQwNU+v3hTYTUxOYFPsgNrHNQEvo/Cd4QbZEydOwAxFw7oCO/899LIOt1tAICsMzfec9dtC9ocqqr3m8buPzkHnytHC39LSsvS/7JfHTx9DcDeYbGC3TsSuQE03tSMjyzMXyHwh+fxyUwafgMB2EdD3mR3vK7ogsCsAQFt8sGsBSV45ipVl11fOMGQQENh1BDjwmfPvzc+/bhbOuO58lZ2yoqoN2oKhTmtfYEfm5yB+J4vYbTk6Nqa7i1hf3aVv+iodPD6gOfccHuyXE+h72AzexpNI4JRiKhGHaja4g68ZevKpssUmpFRdWYFTcpX40ZFO6tifmJrUBcqbN2/K3/7t30jXpS4V6pEJra+twym6crTljDx5+kTu3vtWd1BNT0xh0lUm5WUQ/mXalW8M27nQtta4oIX08HQmzLnLKurIxVT+c9WOKm111xrk+aOFkwD3M2zsqiksTpQ8FRb5JRdG3qbbjNbtZltIvhvGeYU6bZPmUk5W6HtcynUMtB8+BOy9tuvBRCB/X2dzFY45c/NzENbN6/hjsaHo+WDCsUe12nD42SMaQjEBgYBAQGC7CKzHF3C8pMpD8hg8WUehl/FY2y1rd9JxNLMRLSmB9C9D4EgTCdSQ0lBfrz/WI3E2QjreNPEPdykEDGK7RoEqsMMm3oGBAawNjMoyhBl0xJ48ODfU8hRSQ0N9LOzdqcXiiIRDf1nvm1zP/9ADtkUA2EPoxvRoskeB3UD/gDx+DIHd8JBnIsa995o94oa54RaBDtFLDgF/JC054neaYFtgCB3vTiMb8isuBHIn28lwly8sS/3eDI0sxaeGp6V4wm4KgiDahLHvNUtd6T6ncV2EvT6qlnz+/Lm0NLeowd18mzQsFeRc67oslgosPZFhWcxBUowVYc4bLz/2iZWwYdfY1AyhXYvUwiYd/fQHISKFcJoN/NguM1DlOTDQB3uDM2DaGuXixSty9tx5qa6rgTBv否7LMnr6euU///M/pbqqWr7//R/IzRs3VdBHe4VHkDdVfbCcI8yb9CF/Lojy6gRvDMMUL6IHUfRe1cmQLnrQIVwv+jfPH8UD/khg75ZdCQb/Rf81LwvLXkkHXXTRe00Y3UUIx0+5Ny6984+pz42W8WH7m9mDTFBBj+uVRH+fooIyC5ECAgGBgMChQMD1nNbvs8pU40N11ly8NMdYboQ1n3ANCAQEAgIBgcOBgM2i15tpu82OxIJ23yrA4xTtCbu4CvGN8kwc73iynCfJKTRqbIRKzJTALn9Lb45M/nQH1dd4WcdbJhjzBOPY6ChswPdCi82ImpYwDHgis/1Yu5w8eUpVkfrzEYsTrruDgK0B7E7uhyFX9gDJe6530SNPkfb19cnDhw/lJQTVK5hbp52L6MySeCF+JmEFwwMm3JYqAkFgF7XcRh3ufgx8+1Fmqb7Ege79RCAaVdclId9U3PzSiSynJNR8EI9CGuw+pA272ZlZZQrsm6VghIa6EaW43ab0pSMswR5cf3+fPHv+TM6dOyfNsPEWO9Y5fsh/k84tTxxE8PMw/OzqhykDgSwqKyulHrYJqHKDAjEKzshUUoUn79kIbBeqAnv+7BkmWr1yseuSns47e/aMXOg4D6HrnMyjbmVQi9kEfeR8/tOf/oyJ2KoKJS9fuaxhC/OLeiKP5ZSVO2q44MldhvytQoBLWo8gH57Wo/FtCvVih7B1F0hT8SKk1I/0q2gO2fAeF80S6kgpFIwRY4AWYBFwdY5lEi/NFX/8ojSNRvPotIR5r3niacbpyGvpQtKB6z1Z1lF+SV2T98KEwIyyuTDQMvQLzPrlId6PfkDuCxm/C4lzQOAI1QgIHBwEMn2t9mjo5py6S9GxaXJyUviLBXboXG2+kowBBweSvarJ4Rg99grNUE5AICBQTAhwjOCPPBVteGeYh+IgNeqEdWbvdcike5kCO/BtNGtAEwo1tbWeHT5wT9QQgzTJWOh4DeMSvOyKo677TAXfAZqccIwoYMUaCOcVg4NDMjE+gecVpZBTkgrw5s3YUNsKnrqmxrNhh0Dia5ijmYJ7BQQMx42yKCTORukPV1j0frq31L2n7CPcCooK6MZHx6Svp1dGIayO59QASePY+xxd/em5BSV46peQPIa7gEAJIRAEdiXUWIHUgMBhQICDrE7gdbTlHzd0U0hDu2i06zYHwd0qjHI7RyZHU0TPB+NCNZjd3d3y8MEDuX71mnScP+8qtstV9Sc8hqQVyd2Szc3NuouPJ+3G0RY6OVUmEyfdIDxjaw2PDMuHH34k7UfbYXwcTATs3r1x50151v1Mbdw9vP9I1afU4aQeTxKOT07Jl19/Jf/rd/9L2o+3y9XL16W1pU2qq6ucMBC7qqjWUp2VdaQCJZGndaf7eOpyEfaCZqdnoTLVCXZp/28p2pFFu3lU1ULGhvWg8JFXvYeKT6r8rIKx9yr4k1FivlZvlqv3KFvrawG48lbpsNieH9ORQdLo+oc+xenykZfPrzipD1QFBAICAYH9Q0A3aaCzn8UJ8qGhIV1c4GkDc2ERx5AI14BAQCAgEBDIIkA+wvES5GuwkJ2PGcsmKpZnEE5ejnwYebVaCOv4U00nSmNUOzIVvPVc5tELOeS3ZB49xzWQ8fFxaK/pV3MZtMvuXJlU0SxFQ6UT2IGvpnYadcqABk4uAmrHL9lvVFss0247XugBytDeTL9bcGtKrpLsSxaxwXsYc+qR4WGdX2sIMDZRHzG3fA4QNKEqAYEUAkFgF8GR7XRTKOV5MOZ7q+nyZBW8AgJ7jIA/tNmE0K6FkJIvrp8n88g+r5+v8STZOQ5LoR+vzI2T1TkshlHVFAUy9g0yjjthx5gl5Awij2xiYTgsLS7DuPSgCu0oGOMpNjpOUpwI02Vg8S07guVluQEgUayoAeITVFFG1i7MjWXbCbMGnK47e/asnMOP+sWnpmGrDmlqsauPQjGetBuD7b0PP/wQ6jAb5Nq1G3L8+HF54/YbUJU5Iy/7BqXnWY/uFFzFjszKyiNSVVclo+Oj8ut/+3eczpuRf/qv/yS/+MUvIFwr11OV82hvqkQl86eCNQrVIFCj43tAxoWn+sjIPHwA1QnQef70yVNVGzIB/9W1Fez2rIYAsFppauJORNjjo5Hu1tY2OXbsmJw8dVLa8FzR1Kg7XFOn9VgOfqaKkxCx749/eI4dIro28VuBnvhvWMctFIEdJ3bvMh9dHl6AfgX+c3Ift13iteldXDJufEo1oQXmBGyaLSL4iSwjS+c/+/EsPFx3CgHrH5lfmKfsFKohn80QOJjvWr5+K/KzPh1X7njnHIU7gTkG+gI71y/6+WyGZAgPCAQEAgIBgYOBQHq+a+OkP0/jpJ8nqsyPo4XFKxoMovFOa8P7iFEhT7oE9XXk1cgnceMjebVEYIcapCHQKtHr0I+KPgAAxNqfANFkhcFGPnwGGobGcOJomnwtbR5GjptMybPSXAU3o/qoWnqLG67bR8C+R7YR7ykYNdW13EOuawTMPvoutl/S4Ujpv+tWYxXE8ZvgtxC9/fPYwD6B06UTk1gL01OnjIW1sOjb4VVvDXc+RH1Vsj5iuVlJhVyjAjRq+ksy2u2dKCS3ECcgsF0EgsBuu8iFdAGBgMCOIuDN/dP5cozEmEmB3XR0ws7ZsHOTVTdo+oNqOnlpPpWpMGxo0AnsuPjH+u+Fi+Y4WpTNfXTixFkPZkWNjU1y9epVeQqVlxSGTUyMIy7VuJRBDUqtzlMnJyalv29Avvr6G1Xpefv2bWlqaJI7b9yRr7/8Wr747AuoNp2RBezGrK6phl28RuycmpPe7l75wx/+IJcuXpYf//jHqoufbU9VKvxH20BUCUI95mQOdUcnJnIU4nZ390hvb688fvQIO7FGZAB6zydxcm8KQsLF5QVQSEZ4FafsKsHU1OlORJ4ApLDu1OlTEEKeg8HuE3ICP7W/0NCgTGcVTvmRAarCKTzallB1NQCJk7Q0Vm4yVwaM3Nto7yRxi1ou8so87kWzrluG0cIIHsUa35754MfTwPAnIBAQCAgcYgTi8THCgHMRjkUvX76UYewGTgvsGCn0oof4dQlVDwgEBAIC6yLA8cPfFKjmAGImA4vT2QFn3Zx2OYA0ZWghbRzvyJuTV6U6TG6QNGHGRhSFUTEXHWtr8mD8ESO+G8SWG4KMD2ZKC1cevBqaYmBv3sdd1WoyYnA7jgC/Uf7YPmvLXKOx1tjxog5dhn6/YO8913g6L3RJbXWtVNU4wTTFcER9vxy/1SC02y/0D0+5QWC3zbbeqY8zHpTjSdk2CQrJAgK7jMDW3lUbajcYRrNB3CLDZLG/y8M+DU5W52DvjAtiTmBn4U54YvTtMgw7nz3rG8GV8ECw1YYTZapeC0I7CsCWdTLISTsiI43hkiUohs8LILTqWACT89kyiMqOYsQXH08VTqFQqidoxgm0a9evqeHrp0+fyLOnT1V4RrrKoaaSk9cKqOdYgHqU0dER+fqbbzT+jes3pKW5Rb73ve/JEBYz/+M//kOePHqMOOPSerRF6mvrZXYS9u1m56W/t0+eP32uQr6qmipl/Dgp4wm9Z8+eyCMI5Z6i3H4YIR4ZGVU7eMNDwzIzNa3lt7Q0S+elLtDaokK22blZtafXjxN4fRAk9vb3Sw8mfhTAcRdoE+rEE3ctUPfJX0Njo7S1talNAJ4mPHHyhFy40KHCvJaWppzJGXc+ckJJWHkS0Qn1HJQKuTWsYo1Yhr01fPLSx/gjUnRvDWRXeMdBduMly97a9rL4JXARmJul9u9TyS2C52leVqUkE4++2NMS+mHmF667icBOzVF2k8aQd0CgmBHIGQO183M9YNz/oQL2rbkd8DOwMTOoAjuqtY7dAekCrf9nvQ5IleImCjcBgYBAQGB3ELCec+Nek5sKOe5wTKFd7tjpgLNx2jjuHtxYbeKiQBo1rizML+jJL56w002Ofh0sUaYaxg75Y2qc72G4yeCRqrKHmUWbmpoELzytfDfjmj85OgrruCGVPHhwu4AAwPbPafEbLQfWq1ivwbZy3RTMU5F0cbvYC66esa/GCX/yIeAwYh9ofQLXV7gJjmtOXZ1dcubUGamqhaYlJI+NlyCZtQ39vU8nKsR8+Pjq7WDz/ijzcAkI7CoCQWC3q/CGzAMChwUBG/z8AXGbdWdWebLhgM1TWFMQysyDKfAX01iSG9yNjm2WvZfJNiSVALgIM7DJNga99Twp1t5+VOudVddIsjfMLlMvxs0DscayfPSKP8SV803iTaFpfX29dHZ2ypUrV/R0GoVTaxDWrUA9B1V0lB+BwA6TWKajoPGTTz6Ro+3t8KuUS5cuyq1bt1SNAVWnTOG03PNnz2V1GcI+qMZkXjw59+TxY/ngz++rio/L166o3TzmzZOGX331tfzxj3+UTz79BCfq+tSuYUUlbNGh3BrsumIZ586fk7fffUe6ui6qkJC2D+89uC8PHj6Ue3fvSg9O8k0A00mcEJycnpLhsVGRJ0+kHDRT4MgTdTxJ2Ao7fRcvXlJh3RtvviGXr1yWjgsXsHsUNgIgmKP9u/Jy/EA3PJKfgWuzTXuOEPb3g6WCEG5JNGq+P9kE+eIU6Mc2TrV3Jp2FZbzDY0AgIBAQOEQIWE+YdL7x/ANBHB/p6EctAAPYSEI7dimBnYsS/gYEAgIBgYBAQCAHAY4fnP/bQjB5ETspZQvROYmKxIP0LcHEAce85ZVl3QxJgZ3RXyRkljYZEXO4tLQiM9i8nG9+wXemATx6fV0dNsMm85XSrnjxUJ/7HTqVmPqeR98uNQBxrSS4nUOAfSMFdg+xWfvKlWvS1npUmtaa0Fnqfy3IZuk7Vypz2p1cd5bGkNthQGBPBXbG4NpkpBQBLpU6lAqdpfgOHEyabWKXHpzsPdrNOluJyUTI0ULFgrTW5v46YdEcTkpNQ8CyuJjsXNf0yuhYTrtJ7V7lndSFBnfHx8cwWRmSM6dPKx4qIAIpGotwJdFjAq1FzSMbhc+ME8eLbuI2zyaIMqKaE/6oOpLCO+vPecJuASpRlpYXcRpwiauXKpD77NNPdSGzgjr2kccVqNN88/U35OXAS3nx9AXUV45C6DaH+DOaphx0PHz4QP71l/8qs4tzUl5dLhfOXwDzMSYff/yx/PKX/5/8GcI8qkZgmS2tLWpT7+qVq3Ieai0pSGyHmsvOri45DVWXTRC88bQfbdV1dnTJrdduqeBvDrYAhkeG5UVfrzx//lyePXkqYzilR6HwGmiZgN3A/v4+PcV3H8K+R48fIb/TcubsGVWZyZN4Z8+cgzCvU07iBF59Q50K+wgjoaM9I5644GouBYHEyb3L8IK3wcsrF3wtHQPMD0EMdRf9a4GeV8G3zJXOz88r1wWm/loKUpSkiu7ii4uVxE1iJrVMZRseAgIBgYBASSPA/pxzFuu7WRmOnbQxw7GNJ76XFjEOZpz1jkl/mYlQpI82znNcpys1+osU1kBWQCAgcIgRyI4H1q/SJhmFL9QCUoZxJOGP02Bl06dDd+cp5hG97OlHgd0cNmLSdIHb+AgTApGtcS9quC0QAde25BrpkjeAWM/P0RTEjJqH4DtjOFdgA2kDzDk0Q8sMNcqwLYLbQQQyHxz5fKocrYUK2Aqsh9DMBzdtpYSpnDNxohjcpgjofDrCKppqahoKQCmwe4KN1VyXuX71mn4RtqYSJqSbQhsilDgCeyqwK3GsdoX8mAneldxDpgGBnUNga+9qAZOTmDPxaUx72lSVMXjCjioxeVqKjEHMNGBUt1R+TqV278/nshOVkZERefHiOQR2Z6QFah4rqxJVKVZ7HyvWnZj4raD3kQfDrAy/XKYzF7c30mh8D2WGAW9EEgAAQABJREFU1dXV4xRao6qUVBWlmFDNz89BSAVBFdR3Uji3guvQ4BBs1n0uDdjxV1NVKXX1dXLmzFmdcH3vParHHJL79+/LKOpYU4WTcuVHZHh0SD7+bE6q62pUXWZvb4/09fbLp59+ht/nUGtJRmQFAsM6uXnzpnz/B9+Xn/7kp9LZ0SnHcJqP4iWqqqiBYLEKZfLEXGdHh5yDsG1p+XZs2J1GjJ8AV6rt/PTjz+TBvQfS0/Nc5uZnMeGe15OD07NTMvNsWnp7eoA77N/V1WqdT508Jbdvvy7vQMXn67DRd/bMGVWtSfw4uaSwjrjpzjuewIuABpwOSQZaC+k9nx3SvIvD3EP0l6nhNP5mcV3U2CqzSxR5pi9RrrGn/xyTZtmZhx9Jw3wPi2RZ+mHml41j/uEaEAgIBAT2H4F4DIxIieccfEZ/Hi8WeOFzs9gIgoWFMWww4TyFeaTSRXFL9RJ67VJtuUB3QCAgsF8IrDsG2NQ46lj1EWMG+QYV2EFzCIV3zg7Z/ve+fj38e+K6BOEQBRW8ctyj8IjaVqgRRjcvMpIxnryPXB4vCzocV3sHWFtr4sjPDzIwiPsceG1qnKHQrgZ2645Uk+MWfWfIl9PEQx14bpoQ4dpJ1lm+Vlw2PDznQYCg8ReBxnZQ4TS+z2po46GQlGtUi5j3pQR2zOrQv+QEoRCHWbXOmRk3eTuJ9djYONZnemXw5aCq3rXcuNKic/Ukegy3NVkS1+7CNSBQWggEgV1ptVegNiBw4BHQMReDMwdoJ/JwVaaaDepttwkoBTL5BvaDBhAn29wp9+DBA+nASTMKvkRqkmp6k5TE89XvMGdS59qD8003kbKcyQycw4m2ro4OeYxdTzzFNk+bPdHxsSOezTSeVvv0k09lGRNZCtreeeddOYXTav/4i3/UnWm//vWvcGruT7KAye4sfozDnWpUYfnLX/5KT8nRPt3U5LSqqnzjjTeVGeGJw7fefkvehLrKGxDcNTc1q90E0sj3h5NrLqoq44v3RaTayNdrU3OTNOGk3PGjx6TjzHl5cadbXg6+hPrRKQgSB2UCE8QXOH030D+ABdjRWM0F37ue7h49fce2uXfvrp7uOwd7dzzd1w6hIdWX1mHHHRlvQkmG1ZhWMrCm1jTB15HmwZaidT8foldhP0kIZQcEAgIBgeJCgGMMnPvrxpyZ2RlVhzkJOzMcg9j/51swK66KBGoCAgGBgEBAYD8QsPHDyuZ8OxbYVTg+l/xYNNxYtH28Zil2Yx81q9BcBc0alMFEQAU2SlbCXAFPf3EjZ26qfaxCCRTt4+Xfc0MoBXWD2Bg0Cr70GHjOquoqrVE53hcKjo4da5dGnLSbxQaiMP/YocZmI/DjNIYYzDrbYhlrNDU11XL06FHdUD4Ocxsy5cp0a1R+6+0QLYcsG+I8iXWk3u5uXY+Zhbat2Hnwsmm8xzhKuAkIlDoCeyqwUwl4iSNWKnXYTzp1oRztvJ80lPhrtg/k5x/idrMNTSAUV5YMSTQTcqeTHE1ObMedTFC3wd1LEAypQwbceYgZUxExMnFttnFjs0AmTdqDExXuKKIqgIGXL6GC8UKct6ZAVMZW4SY8yjyJT5xLnDVvnC8VetEZ5tFj7neLaPymNQtiHjVcNRiErs4Oee3WTT2R9ux5d7xAyVzZhhSU1cLeWzmYNgrCPvwQevfB0JHJ+Md//Ee5dv0G4pSBqZiSwaEBefTwsQrl6qBeov1omwrwPvzgQ6dyoqpazp+7IG/d+Y7ceO01OQ8cjh9vl1OnTuJkW5Oe+KNwjqcalFhHMG5pG88JyGhnj8JA0keGmBHrYaD7wrlzcvL4cfnOd+5g8r0KoeG89EHtAk81fvLJx/Lg7n25++23Mjo2ooJE4sFThRTmUZD3KdR+tjTD3l0XbPTdfE3uIJ/br99WYaS+oywRaVZgmJqoV+quWdco2iLEWEP0hp0nYpljClLq/NyTekQ+zFujxM/uyf1N4kd5xh7pcIb6pVoezs9KR1kWKcpnLUVrOpXFpQx3fZfJcP2IIaRIEQhjfpE2zAEla/feN+uLDLgNOi4G4ce+Nx5L8cCd1dMz2NWOvp5j5ZEyLriid19zNk02yNEKLcprjLlHXanWxatCuA0IBAQCAvuGQL4+1Pxo+oC/vFPsiGKLu28VMDow9vG00RLMIqwsr2JMpMAOJ+wqqyI+HXwXB0v+gksjkAcSm4nYuOsnoB8FFsPQSjM+PgFzD60x80a+tgmCuqNtrTDTUC/lw7AL7ye2+7gA8wjXghDQtnL8sDUb53ZUXcsNumyIaqxV6DoJIth6Sb52LKi8gxZpk/eOOGHJS52PGdfB6KaxcXtsbFTn2OrBP8Q5enBtwr/Oxz1HgRte0jlYVJ8Ga0sLC9eAwF4isKcCu72sWCgrIBAQ2GsEbMDbbrlM7w+v3jO8F8EIUGCnAhkrYiNOxuKU2NUJbhKil5eWpb+vT+7fuye93+2BnbjXk8Ct3kXwGrKJGGbzjBzUro05iamHSsxbt1+TyclxUZWd3b1RJsgVArJVLFiS0yyDiks6qokgg/H5F5/J7MK07rgcHBqS02dOy/d/+ANVW/n73/9RPvrwr7IIYRiZvzlcZ7BDkG1O9SonYZPuzlt35O9//g9yAgI2Mif8cSK1ggkdBXK6MIp6xvQqyfgDP9LN3Ya8Ijsww+XYmVitu+NEGh39SMjwc2dOyaXOTjlz6rQ8vfFE7t64Li8hMO2FzbtBXKn2jCc+WXf+SAMFeGwrqvmkwO/ipYty/MRxGAFvUKaKE/oqMLGGO8vhT50S7PDV58jbZqJ8LyydC0fcjSVhLt/1/jJ/ZoGLFbVe1N3z9+q7e4WEnAMCAYGAwM4jgD6bJ6WtD6eQjgtpWXVI7Nqtm995IkKOAYGAQEAgIHAQECD/skbeSWi/rlxPqVGbTLGfkuIYuBIJ7NSGOca8CggxqquqdOMmT90Ft3UEYv7MWCUwaxReTE1OqS0v8tC0q27CBKogpV132phvxkZWvjt5XVHwf3kpKzlPzu3KcIqU7zs1D1HTz+T4pK5ZLa145ltQM68ZS66e+0Uw322bY3Oj9ChUwQ5h/YVrghUVlU5bUfyh7BeVodyAwO4iEAR2u4vvoczdJg6HsvIlUemdWp63qcf2Km1U+Ln4A7Pzd385WC9BcEXVAzZwcwVM42+v+OJMhepSKBPXEVSuQBUoVSxQWMQrcTDnC/cMT4bFmNqNHxiFWpBdLUpStoVwMupydQuPPCm2qnbcOjo6VY/+R3/9WJkyxoKsTievq2UUoMHu4Oyc7rBkvY7gNN00VIY9fPxQRiHo+9Nf3pdfQC3mP/zDL+S//dM/S2Ndk0yMjMtdqJgcGhnFybxyaTt2FCfw5mQa6jDXIPw7BgEY1U3SLp3vykHcETC4q6vOXoKbRMMPBDn6QRvS0w4d62in7rL9las1bPThlF/58WM4JVctly92yne/e0dmIDB++vyZfPXFl/LXDz+UR1BTOoITdnaqbx527168eAEh44x8+dWXcvzkCbl8+bJcv3Zd3njjDZyOPA+BXZOSTRqII21TOMGjAsfX2rW/VoBRHUUUq2WFdlRTqi66WBuqX+oBPtlnRqIf0jqRap4oXpr41shh+k1crjwxzgUpLSPfb5MMQ3BRIpD9hoqSyEDUgUFg9963jfoi66/SMHJzivXDtCszB4EdBXfm2M+zKz8ILoUABlV7TuYMB6GWoQ4BgYBAQGBnEbAxK9tXxn1oVJzjC1akAoI7CgHKwbOY+vydpWjnc1tddRsiacOcvAVVYVZiQZ2nBNUdkHFwp5Ez/pT52lzBsX94O/Q//9g8gld3wo6aaiYnJ1RQajSRl6zHxtDmZtiwq3UmGSzMv+p7h/YITeKjsvl9Muth3AQ9CqkpQKrCiVKqJJ2empIxrtfMpAXVxD1JtXl5By5GAZXnOofv/D6TITSbwk3So8C3ubFJtTjxO3Gp9M3WNR93B/90dlHWcWj0bJHS/tZv+/SE+4DAfiAQBHb7gXooMyAQEMhBgMOkDZkaGM1iOdhywKZNM566Sg3m+UfinLxLzsMDg0JKSL70RNfg4JBMYSLoO5XZEDibZ/iBO3hvTAXbgkKmKgi+ynFM7fyFDrVl14LdfLRVR1L0lBvoYVwKGMlEVEHARhWZ3IU5NDAqg/2jcu/rB0o4T7Fdv3IDxrNrVR8/hWB6Mg950G9hflF3FY4j/ydPH8nAwE05feoshHZQtxIRRoUJpIuvBMR0KBNXFdYlU2wyvmUQ6tH5cDENmU17t5BMGeU6qPPkbw02Ajo6O1kheeP11+Xapcty+uRJ+fabb+U5bNkN4DTdxMQ42mZSXg69lL6BPi2DwsFz587LrdduydDgoNy6dUsFeHVQw1lbVyvV0HtPVRrG0BI750gdnujhE6qBeT1dsvA3IBAQCAgEBPYMAR1/MHYsLS9C1fOIjoEc48y5xYakZzf/UrvmDEOlVoFAb0AgIBAQKEIE/NGB44XTFOJMCugJEmgCcYyAH7PIKkK6Me4tL9JOOYQUeKYGE/JAqgWF5GIQgXdwW0Qg4gbjV8CdsJuUPmhzGQRfuQBBkTny2C0trXrCjjba2QbB7RICUcPwm6W2nVEIkc6ePStdXV2wH9go9x/c1827yseDBH8OFbfpLpF2kLMdwwm7R48fy9OnT6Wro8MJ7KIKW/fCq4/3QcYj1O1wIBAEdoejnUMtDywCNiTZMFVIRbeTJl++2TIt33xxfb8onSU3SRCiWA4WRIkFJ/icEHFSWuxqQfxabv2eFY1S2dXLZAGntygQmpye0mgUilEUxX+M7rBzQidTnpg94eRBrZgyFQVdmhZ/eLXTAgkxJAIhEU1coNTTadGuyarKamlrbVMVlbRTt4zTBcuwYaCZsUC0HdtPhWl4XIXaShOOQZklTqndl//7v/93CMBOy9y0UyfW2dUhZ8+fwanCQel+0a3CMKYffjkk//arf5fFuUX5+7/7uVy7dg1CrxoSCGZxBfYNwSiiDAoTqZ4in0NwjiOZ2AyKpPznnAkC+eTfN0DlxaWLF/UE3p07d2B7bwhqPr+QD//ygdq5m5udV+Gi1nlxDcLFfrXrQKHe+3/+swrtrt+4Ka/BDt+Zs6elsTFSxanFuvZjefqDn70K1i5r2qjON6bWImkeJDj68TkOsxurIQOd4ztAF78vFiX2YDbmGcWNHvPmmo7q0eAHWEqXX/gbEAgIBAR2HgHrcwrpbzaKy/QW7qjkuEHHvn4eu35pT2Z6ekYXXF2Ihia3JXxnY6COa1otajhwFVK1oLhlWHABgYBAQCAgkEYg2zeWRX2lKcmw2PR2fSz5LNqwgw1U3XhIf4tVfFeSxpPlixDWKf+HsZLCIxM4sp6OUy0+2vebovXaldyg/icf5rU9349ZmIrgKaNxbBRVAWlUCWqRaYANu1bYsKMJBvLq+ZxmxwF8vcLzJTpkfj5yBr9+xwzgzzxxOwXbaiNDw9KBDbqdnR3SdrRNhkeHpbu7G6GIzgUGJPD7gUwWGi/8UZjSMEQHdHWxCiGT2LjdDU1Gvb09uu5kka1ttN3QNti+rUH6bJHW8UnP7b2GTc35ff9UhuEhILDrCORf0dz1YkMBAYGAwP4jsNPTBQ5muUPjdurJXHQuifyWoRKSeqtnZmac6sEow50paTvU7U8anlSjDThOAIevXIFdNJzSqqnVSWOW6TMKN28RF4N/bU5kabNXy4u4HyETGTUAmbKui13yve+9KwtLi/L4yROZXppBOBhOMA+rtMkApkCFrbinsI6uvKoCgrUjMvCyX3p6+qA6pQrGshvl9OnTcu78Odh8a1bh3iCEdDXVNSpQW4Ytuw//8pFMjk5I5RGcTEMZFy9d0lNqnBCTwYmFWCwERSnTw/vYOcKNfvPeWO0MM+Jkm2peyuTo0aPS1tamSakG4yra4yh2NVI9w+MnjyHEG1SGisLJuTmoyUSbsd2I1WeffyZ3vvNdbcs33nwDwj+3Gw9nAtVmRVkFWsLN7Y00vZLe3eCtXGukitrSA9F81Ty2VGCIHBAICAQE9gUB9nTRwKe9nrtfxQn4iQmcrh54iVN2Y6l5yr6QuYeFGhp2DWPBHoIfigoIBAQOFALklXThGR0p7ZFVVMCWXbQ5stgruoQTdtxYu4jT5uT7yO9UwYYdf+RvHC8WRogtt6M/7UBivh/TWA+hmYyRyJaX5UkBb3NLixyDjfemHBt2HKUj/CMGOLSGIbeFq4EWwantgRN2TsPPqpq+WMBaxb1793QdhOHGv/tJt1BiiBohQMjHoIHpITZ6P370COZKLqWw0T3GNhlNhRT6YC1UaPwQLyCwNwgEgd3e4BxKCQjsEgLbGVy2kyYf+TYqbjW/KJ0l97JWRoXPnN1EMxwuhlFgN4cd7DRcbXF0vsk/djzIy6cUb/MJYyj0svpSDcY4VAFQ8PNyYEAqTp9xAjviGDcBxVV8cB4mvHK7GyPfKG6cBLH9pkjucZc8eJBars6LRsVpo40qS8ewaDmARUueMuDGvkowbEsQ0K2s4Ed1lRF12nZ4Bl+nk1yqgKFwawJxSdf0zLTuEKxvqJfbr99ShpW7NycnsSiKU3c8Zfjxp5/CZl2F6ok/e+6cnIKgrw725szINnFbZbnIlzXnBjdDhUI32lewkwGsidKUt74aqhEYJxulBif8LnZ2SdUvfqGqLweHB3Wi/pf3P5Avv/wSgrsZ5o4f6EGdX0KFyaegfQp1+PLrL5HmNXn99m25evmqHIVtPu6qpSPDS9mm2yEJzOOC+U4wBrCKT8C5wHgHaz4JrmaAhPGLFqVxoDDDPA5xonCNrX8iDysDfhZmwXERlqMG4CFVFj1THha7qK72/dkJk6IiLhCzbQTYrqFNtw1fCSW0PiZvJ5Spx+Zxrdfy+wWOMdxhPYwd1lQLrWqskTP7waTfzhRVYo9WX0PIyNfnqMPPhlmccA0IBAQCAocZAY4Drq/0UYj4NYZFnScvtqmRvAyFXuRVit1xfCB/w9NetLvOZ/Iy1HRSUVmh975t12KvT7HQl3Db7gXhX2JL9aMTsOFFG3Y0FeKcw7wW9tdbmlugKrAu5idz6oOMCpkR5aQ7RB4O8Q0qrBjy4yXnvaq21ZawXkUbgvX44BvqG5DYoazfv5ehd7tBAYc7yPgzm3sSDfpNY+2kp6dH+np7o/WVNE6WLu5U4zc9iWfvPoO0LeIGsRDzsCv902FGV1xekn24CwjsOAJBYLfjkIYMAwKHDQEOYjaobb/uHPz8AVCHRvzhybIZCICmsSC2BP34seMM6DA4D94hLAg+uH8fArJr0ooTXa2tLQ4Bg2KDZmCQRfNhy+en4esGuNRsKzINlbBNd76jQ6ph6+3Rkyfy/l/+okIpFYpBaserE5lRCEXBGf1wj0XOpSXYwquulPoGMBZgSmmrbnhkWIaGh6S6pxoqL6/KW2+/JSdOnkDYglB3efuxdjApU/IC6hBmfj+nZb1286bcun1Ljh8/oSomOYFaW0W5rDQEX1omC43emTUywPiPKAjGVJt0YdLNdBSQKX+MML7WFCH67ybvyZgyHR3TVFVXqZrMi5cuavxvv70ry6jbQP9LfXd50pDMKung+/wC6hz6oCLzgw8/kA+7OuVvfvxT+bv/4+/l5s0barCaQkCbBK6tQbBI2rUs0OOK1ef1/zCSS5OOE1Uq7Vn4U55ss6VsSF5O8TkehdOyBzGtP9qDokIR+4AA29e+s30oPhS5Lwhspc9Jx/UXz7Sfi94fnh6fgDrMfqg/ph07ngQ3x/66sD7bUhTnNduvR0OpEpsNK84aBKoCAgGBgMD+IeCPJph5KCF+P0oP5S/As5DzoLCO/BX5h1JwHAeXMPYtg8dhPfSEHXij6irY6kZdgsBu+61o81SfJ6F2F9q0X1xccBlzICYPix83u1JwZxtAsyW7ty/rG54LRiA16XEPXBeYw8ZlnjJtgLag9qPH9Dc2Poo1A/dNFJx/iJggkMK6TOYXFnCydAT2G7G+gpOmsWO8aK0k9ivgxu+XC4iOKOkUgY8sDLUQ69UQCAK7V8Nvw9Q2sNpAu2HkELguAgHHdaEp8gCOnoVPC9nO+QY+7tbjKSUOzJyg+s7eDd/voN6rDTcIsh5BDQAFPlTDaC5GGTe2QKh+UUAcbglwTRYfnafNifLF9ZKlbinMIjNJpoCGrs+eOacqLXt6ujFvQigFW5nVSubPPpGMapnq5me8FQjPnCDM7BSS8aN6sefPn6t6SRZMRqQd6iipenIYahGov//zLz7X3VZff/ONHDt2XM6fPw+hV6ucOXVa1Va2QD1IBdVMapmudiybCJC25bUVVde5AiaTO1tN0MgTbmQw+c5xB6NeQROvFLqRKSWNKzoRh4pP4EDVL7Wwccedj03NjdJ5sRNEwyA13t9pqMxgmsXVBRX40eYRf/fv3oPxB6g4QfiDh/elq7NTLl++DBzPKNNl4wfbx74PnZNyVx+/GVYl+ht/bqiffn3WqBrH/jgM7Mm/WlOtH4P5utB8cay4OCy6MX8ti345Hj4Vdp+KZJ7h+goI2Ldo79QrZJWT1N7NnIDgERAoaQRch+V6I+v9UCF2lnYaGuPGBHa683Q5x6xkx3tJVzxNvBs0Xb3TIbv2ZONIGAl2DeKQcUAgILAXCGQ6Mc7cdWShv3V0uOU8itpIKLIjT8ETauSvdmPOtt1q2zwym96dsANvBMEd4/CEYHV1tf4osCtb4kmkDBDZTMLzOgi4eQjfA8OfeNOWHbUPkW/2hXPkRWtqoHEmmqO4TDPYuyzXKS94bwcBaoPi5q2mxmY5f+GC3HztJtZsHkpfX28qO64z0Nn3YLx3KlJ4yIsAsaLQjnNt2o1mf0n1wXReVxrNVeFjnpFGIm7ONrfm3ZtfuAYEihGBILDbpVaxAXWXsg/ZBgSKAIHM5C9F0UZhUUSLkoydqRz4wO+IQhEKMyanJrFzKdpJhjBOVrNCp5wMStzD70d4zxNmT589k26oA8jZWZQHR0Kc3ZtpsK8HTRxuM0jkG+NsgfTDj/YVjEaeSjtx8ph85803cRpyEqo7X8jo2Lis4SSdMpvIT7NEHsxPhU4QZnHn2fwc46C9ceStEsK1FT31toITC31aZzJ9tTjBR/t21yCopNCuAjtPe/r7Ibzslq+++lpPNdTApl9nJwVel+S9995TwVdXV5fUQYhGGhzTi4IwTeapOgrQ5ubnVJXFHBgfnowgbXoSToV0CzIP/wVMDom3+yEewnjac2ZmVlWhTWOXI/NhHVhWJXaVUgHosZPtAnGg9INOnkacW5mTNdjeq6qt1PIXYfNvcWFRvr37rap3+OijD+Xa1Wvy05/+VH74wx9qXSorq0APBY6kWknXcnhLWuls0k/VHJGX8+ejtZldNST3jzV3bsjGPtlsicGmjnGyCTdNtPcR3Puy9+WWWonWB5QS3aFtS6m1doLWQjqc/J1XtLQad1nsb5lbEntNpnDqewCqqkdHRnXO4ihGSo0UjX07UY19ysPqWgiK+0RiKDYgEBAICBQlAvn6zdgvvsG0GBNxmoHgZsdyVSeJE3Ypoct+V88fyxLCHd1UiQmOBz8+cxGd9sepMYQCOx0wkyT7XZGSKN/NNACdTiQwCnuMGjGeh+YZ2s8lj1oLnNUhLgW9Tlhalb+eNqDnDw2+BSJg7WPROQ/s6enF5uETculSFwRLEKauLmFuiDUAfNd0bEuf/2A7BpeLQD5cfD+aXhmCDUcKSJubm9G94KV2E27tfwzVMuIb+WspulG80A/Aj2c5Olr9NsylPvgEBHYWgQMvsOPHHT4q99JYRxfw2NmPKOS2uwhQ0EGbZhTa8XSTvcdcMctOlnaXkv3NnXMO7t4axKLgAARAU8BjO47zFn9+aNOR9FRk45wZl5OjMkjp9JQcnslUdnR0yA9//EM1iD0GYd3Q8KieWKMtu2UI7lZwao1p4zbEPfjSSPjq8qCwrBxEMi6FYosLTr0YGRC+C3VgSjhRm8aptSEY3R6ETTgaIbZTDeM43fby5QB2Xo3L2bNn5MyZMxCi1St9FRWVOh6QhlWcnqO9hUUI6fhe0QYf83D1KtP7JQjU6M/wWexi5E7Gubl5pYNx52bnHC1oi3kI7OiqsauxEca+W9paVJ3NDMOQzp3M44m8FdSPu8G4DBydKlxYkWFMPNmm48BtEcwX7RW+/fbbcv7cBTUgXgcVJzzNqO1ljYUH137urwOXOW/DRVn4Ke370omwBVjmeeJblJ2/WmFW+M6XEHIMCAQEAgIFIYBuyO+JeDKbqqkGBzgW8YSdW5gpKK8SiWT19ccC9srO30JLpDKBzIBAQCAgsM8IaP/pdZ3kixxfQm0fENiBb+JJKRV27TOtVrzjHaNe36Od4eRtyBctmw078Ct2wo78W3CFI5Dl03WgxQtj63d8V/ijoI4beWnfvQKbQSthL5CO/DhP2NlmVZ/nLpyKEHMzBNhOdO67EJ0H9vb2yZWrV+UKNhdzs+3Tp0/idtP1BXL+UQLjsV0u4W8+BAzj1KQbEam56CXWgPogDC07Ug7NS41J8mRymvjZHfst/qK2M+/dvlqb2ze82+WF/BMEDgL2h2IEZUPt9Qey1+Ulr2X+O3tZ84cWt2+xYVncaBULddsfCbPjKNVrzOIkU44Nu2Kp6h7QAaWT2oetQH0jJ+ijEFDxtwTmqKIc3XjUxyn/hNkNBUF02TlL3Cq40X4xjoUbl0R9Un/gb5PKZLHO+djzKuhirDMQkNHQ9TBs7X38ycciz+ANespBI/ZeQtDlVGRCdkdvMKVRsSiDJ/SUbpbHE3b4kd6ychILZhDvwTD0ls/NfamCML4XFIItL61IFQRx9Si3kswtTv1R+PW73/1OMSOzS+bFfuxP7IfsUa6WAjwcMdbfEB8yzWSgec9do2RIaXtPw6JwMqcMU38QWo6JI08ekhaWSQaWdFIw6NLiNOFydO4RZfJObeqBLp624ymNP/zxj/LlV1/JXz/+WH70gx/JD3/wQ+mArbvGpkatH+kCJBCG4r1wh+9YFXUgKwI18mD14JI2dM9E23dsj9gxDzovramRsHfLC3RR/fTqgzIz+ai3+UVxwmXvELB3e6dL3K18d5rOkF9AYMcQwPikx5fRcbI/pp1dbhyZm59NFcE+MO4HUyEl9hBXwnX0fnfP+93o1ncjzxJDfVvk2lgeN9m2cgmJAgIBgR1DwDrMqFOzORNPf/j9HMcSapUhr0DergoaNsjDxPGjj5s8yH45K5o0+XTwZCA3QdK8AE8IHgEvRPve3MSodTCGYr8IL7Fy2dSGtY6wmSYn9tTsQrvvI+B566BhpqKiwfHNSFyNza2NEGKQD1WeMcksbrdMliWG0D6Qm/mOuZCh7QSzFnTcWN7f14dTjxPS0Ngg56vPS3t7eyJ0R3p+N9Tw4387li3zCG1CFHznMOaLra9wBNAsNkz39vfJi+5uaahrkKaGBi8RgfYeiWqk/pJ5WBDbjs77NJzHDv/123qHsw7ZbQEBtoONpVtIVhRRD4XAriiQDkQEBAIChSGgo6kNp6LMC3evcwcZhVWH0gEOn63j6bFeTAoHIRhrg722GjBF5ohcoRM+xkuQthxyr5vFIzNA4V1lZTXsxh2Vjs5OOQP1lfdgn42n5FZ4ug4/K4t1YTNnJ0mcxB6JwhiXwrQjscAOuwl5Eg4/c2QCz546K+fPnpOTp0+qKsrFlSXd5UajxJw8U9ir9uhMwBftACU9VIFJYZqpqrB8d/8K4SAcZYUUulGgydOKFAiyfvMjI2DCRrCDDOo4553azJv9N4HrBagdPSX19fVSUYXhG+ldWxPMiGoyENGtXuif8vADw31AICAQEAgIbBUB21zCdOy3Z+ZmcEJ6CmOa64hLlSlcFweMITqy2DizbsQQUHQI+ON/aL+ia55A0OFDwD7J7OdIQd1SxJNUY9OfO2FXvEt1KqxAJTjukY8iT7UEoR15OW5cpDrM2tpa3Wi4Hh+yHhaH763Yeo3nofFlaGgIawFDcuxou9o9Zy6cfxB3qgqsq6uFrTtn587CcpjvrRcdUhgC3kfMNYde2KvjpmGeKj0K8x20R9/c1Kwaerj5musRNk+0LMJ16wjM4oRdD4R1z54+kVPHT2Id6OzWMwkpAgIlgkDxzgJ2CMADxzRvE5eAwzaBC8m2iIA3c9GUNhXfOBubvPDqdrwwH5c2a8POj7txrgcgNILPF9ax/pOTE7Bl91RVLRzpuiQnjh/LywvFrYE0zuW2h4XkoIWGYJiWnYmUzgVCNUie9JQY4nM334kTJ+Ttd96BDbsxuX//PlR2jOsklcIpnkpYUx3iXonIUElEOTBjB4elwaiQVexcc2Hu3aBgi5UlY8sdhDdef03+5ic/kddu3dKTd2tISPWUM1PTEPAuqgpLnoJbhP1D2qDjzjdOqqdnp+XlwEtM9p7hOqDGu7krNLsTUamJiLF3j/0pyVN88Cci2dGMZ/rzj99u9Mo6HtJgnFXQ53LRlMpsMS5VnXz014/AAPRALeYxuXH9uvz0Jz+V733vPWk/dkyTMAWNLpO2cp4kNODgT9yUZkaK8DRgY9rgH4v4XPGI7Fzsbx642liibeL5p5KmHhAp++ylK+zWMrBKMJX5FZZDiBUQ2A4C9s0zrb3728lnP9OUVh3sG9+P7ztbZkQLOjvtL/XRjYvWj7JduUBJddXzC/PazDo+YJxCz66nxfez7XeibFY7UsaMGrHnBU74T3/+OObqNTsoICy4PUSAjaDO3dhUIB7rvdfbxvY4zJKGa0AgILDjCNinmXyC5sOiEl/yH7StTc0eXPCvqqrEhj7wPJ7j+OKP6V7Qrt1aX8ICrJunn93Tn7STX6dWEd4fwYbKGgiNKLRbTyUmUTAkEhSY2+F2Pq4+Emx3C+M9ed0RnLDjKbs58LiGId+Rukhg11DfkNqYqu8PMt3rd8ivR6neZ99VagOKX2BUahabbKklh6oaF2BfsOV8i3Re6JSLXRe1Dbj5nBp5zPnfVewXNaK1pfkf9ivfe/+dnQfWfT098uL5c7l5/UakLclQooYmdlD875D0sd5rbPnNreesThvFWS9t8C8MgYOA7YEX2BXWlCFWQCAgUIwIcCBbAvPCnes8YUeVgYfR2YTD6k4B2IOHD+U+fjzRdvLEcQva1SsnOelph6PMBHGcGnFeQhUQ733/PdUxznYbh1FgDpiV2HFJZY7LYEZtkkKCLU83IaPQD3746TMmxLzSUTDIk2U8ebcwu6C7T5tbmuT46RPSBQPPDQ3Nanh7eRmn1GiTbmkV10UsqC4qLVNTk8rYcPcbJ9Q8ZceTeJMQ4qmgLjrhZkbTWSbpphCSTuOAGNJBP2rRpB+dMtVRRVSlpxLPOmwwNUR8IqiTfuRhkwrmz3va0BscGpSR0RFleJ88eaL1OgIbeK/dek1q6+v0tJ2qAqXanAhJbRUU6xdtGCuxu/DHr+Vul7UL5IcsAwIBgYBAQQho/4b+2fp22jKlaqoV3XgRZcFIfqdYUM5FHAn1ZZUoroyGtiImNpCWQgBt5xwnBamQ8BAQCAgUEQLkJ7gBhKfVqEHEbNgZb1BEpKZJwaBAG+MU2JFv4YbKyspK5cdq6+qUfyn6OqRrVPRPnH+Qzx0aHMIpu0HYSseGoah/J9bUxHK0rU1NKUyC9+UG1uB2BgEOqcZf25U58/vlxuDh4SHdHFxTXSMXL3bJnTt3VEPUN3e/0e97Z6g4PLnwtdZ1jajKnNFw3k31oy+ev8Aa07i9+nplfJv1REmSy7oBSZT9uOP3HPrI/UC+NMoMArvSaKeSoNIWL0KHs3fNVXKY+wNlZuGAkx7+4rWFCEYugtE+DG3Y+eoQ9w7lIinJ8MJ1EgKwJxDWPX7wQG5cvRZj5uB1Ed0kkoDyv/5xFYGXi7FevaL0UbDfZNHZAmTgfHEezcVCo9GHduDWYM+uvb1NvnPnO1BNOS2fffqZPACdbNzqmipMVmELboECO698/x4Z+QInP4j3ahsBAiu1iQCP7ufd8tEHH0l5WaV0dHRKa0szhHPTOlleXFyGSkx38mEaQt952BfiCbtuqFG4d++ePMfOLOr+XwDTQ0ehm/umklL57P8Yj7Q74R2ueOA7y1NuEQgO36iCDDMXedkjK5pyLIf9p9rv40lEhPKv2skDA9/b2yu/+c1vMUHtlhs3b8jlK5fk1q3bcvnyFVW9wcyYB2mj2g3mRcEi/2WdFU3yjETzi5pXk/AwZ否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否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否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否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否Td7NxQZI6LMAUMOBa9JK6sdtzbon42Vmw2nyiJypUkkfYiulaxJ1aF718bbNddxkaaC7AXy537nMtwixNt26lK7JKscyA6IHB4EGDfqnN1XDmk0A4c+914Il8UUKR6HlBko4QTIhmJ7JPIN5APVF7EAsJ1WwisN+5SMESzD2Njo8pj+/FUJWYksBscHNRxwg/fFiEhUUEIsF146osCu5HhEV1fYEJ+F80tzfLG66+rkHVuflZ6+3rBL6cFdgUVcogj+e8x+8xRzMu5fkTNShRixw59adrx2e/D0qHhKSBQbAjsmsDO/4iUiS22mpcIPQHH4mmovXqPrc1fpTzLg+i9Sj4bo+8PdtnBcOOUujXMH0AxeSFjEs/5kR3tjXECOo0dYcsQ3DjHeBTeIIJK7njdpKxSDs7UzYcsX7U4OXw5+FJVE7Zi4ebMmdM4yRTZsluHXbL3gyfXnHNslS/qyjJaFlMPaHmEaNsokRQiugAymjxJVQZB0SD0uP/2NyOyOM8TCuPyk5/+SM6cPsuXVE5APcTPoBpzBXX47W//U/760V9lAScZ1sDs0VDzyiqEtrCNEJPplZtza4XjneJ7pY9GdDYy/GmzTk/WQSnkIt414liIc4uXVDkZqZ2MBGBMa+VyEkkhoLNtB1RBjP995pRjnxXo0tvoOfb26uHdxtm4qkd2+FC2uSS9S8Wzl0dICy否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否VTp8+rYuij9gTtZ1DO4Z0796GiKmdSCOVDMUkr4vpwnTLOZu8QiM/Y+QlyaKeo/aVO97u6nLqu1oE5p4BduXd0om023fuCKc71g4mJNyb1Ok78EA4O6NTa5xcYxcfT+6Lw21K38Sf4+HlDTWNBhzrnzGwLhxVHH7akEkE9ciyrAjIl8dpO2qTALQx7viV0zEt+kztqOh9cdNU8C8eUQlZECgIFATaIJD6GevsrCM3f5PabGD9tS64H9WGIu/f8FYZgubdVO5WeerOF/JhY6s949hm5MvSxq00tni+uzMXq4kqKhIlYKUwK2Oc7t+zZ084c/psOHzosGkFqDwpKJuxGLsb6mPlobwUBAoCy4FAQ/9pHW5NBf0sBt488t1R7X7to5vf6n6qol8bGXkvZnEQAErDE6j17nWJ+R9Cn8iX7LAuf5M2cGy3O+wGmgR2i0NLiaUJAa/mdTOQJoYRzeEfa/6eb1SN4bjT/eDBg+HAgQN2rQTadtgUVcyLIUDf+Vgbv69du6bN1ofCzqEdtgG7js0LqLYpbwWBbkegCOy6vYRWAH3OKCw2M9apeFcApKuaRB8qnZdJ/GZDnin7canEtNM4j3W6SCeSiskQqBh0RxE3dmdKwDL9LDy4LxUM1xGI3QnHjh2rdxhlKzYsw0WBy+yd9FlKc756G3UqvGxjoGjL4h8qMY8cPxreee+dcO3m9XDj1g3tAnxqak8vS0A3rlMKX3z5pe5D69VkY9TuR0MlZG68f/E0tfxkqhztXhZ9oN4RgaW752H93dxEz9reHqm47A/bB7dLKLfTFrnOnD2r+/T22443cEQF5paBzWFw+2Dox68mPFu2bNHpun5否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否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否rtzl0wxBXtIY5ooHTDCUmT8jxtiDreytvHAH+8x/1VaI5Vno04Rt7649YXD7YDh65FiYVP0YGxsLo/o9kpDunu48uHn9Zris+wQvX7lsd+Pd1u40JhmozkRwHuuO0IrJWFkhSOck40adctwxNGgnG3fv2W3CuaEdQ/a9VapYBnfssJN1O3SfIvZMIL0uenwxk+SsnXGXRIAHsKfbNftp/saz21nA8qcgUBDoGgSa2/FSEOb9gacd06R/outd2xN7YvrbSY0ZoyOj2sTAQor7oxvGb3M8/u0+u+wp8rzv5Qn5rShmzDVNByLfFwC7LCerkhxqo/MAsALUv7qOBdMasFun1Q8fOhx2SINCjzY1NRt4AhbL4Z/Y+NRg9J3H1+BWPgoCBYEOIOBtsKmnjY2dBh87YcYT9bu0z8jzw3fPbt8dIHBhUYpeBAv0TWYYQ6pxMFr5BkOexSweAg6zP4mZ8fmx5nMP7j8wQYWf0mRe3buut1ElZh6QscBJy+3drjwXhIC1hwxH2sQT3WHHlRQP7z+0NQaYL28rNHs21ew/sC+ce+1suHvvjl1h8Wy6XgPz+b0T4mH9+5V+VpU38kzgP6WN2MPakMzmY/jXtWy8lj+6IbAkiGNY9V/WARPH/E1zucw/ZGd9ditdnc31wmL38ifUQvBaiN+FUdTedxHYtcemuBQECgIdQoCB0gdEGzSzb5JEdeG4VE5NSGjnJvr3UG5bnjAaUfUkSGKEkV6x54TUZe3o+vq7b8LJ0/H+uPUSZs1auIkBF/43FaQJDV否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否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否tbp/jcKKfIq8cXDEpW7pJI0G7OtyiT6cp7H3SpnC+FlrljkHTWLg9sHbfcYC0puECghsEMd5h7dnbZjcCis10kvTjJMWZ4kqFL4tWvlS3akYXTbCwtUikl/JiamxBjr1JnubXsoATDhWxm8D2zeEs5ot/pbEtrBINsC2LT8Kx7q57PqJFyKP0UEnW6IB0MdN1vCGqh86Rf/m5/6j/mUk4euXfzNGc5EgpUjcdnJurW9UvXwOPzql78O/89//+/hk09+HW7evKF7CvrtDrrTEj4ePXrUVFvukOosTiki7FyfTs9xsnDTpo122pH4MKTnY6yyYEZn+JSk0xqfdhJDrpZHwkWv1TN+NuUr/0wBPA2K7/nGI8g9u93zQy+GDy8P4nKcFiPeEsfCEOh27L2edDudC0O90bfnEdtuyGdNQ94n5H2F0x/d674nfnsf5PbetxGKvDLpf/L4iZ045oS27ZnwKPX09HNcMudleXWaSLyiC0iUZfa8RHf17nqP+feeflnILYm2QMDKTYXDoj0bnXbu3GUbnUzFZfJv/I2EepzD8fqLE+XbK20F3M9rd9gl/3m9SFblURAoCHQYAR+ZWo1K7ZKmPa8Rf0777yZDH8KvGlfaEGcjCqS3Id+xcGf/bhNdsW5CANwiZnpLG1kZD1hHuatTRWwyYtzgHvJtOl2H0C7fJPu88mtKrnwuAAHKJj9tCtbjWpe4d/eercVM6vRjbSjF2KbcbodUZp4791r4y1++Ct+d/y7clhadVuZVK0PnX1rl2/slGCFbgxTm8O7Xrl8LFy5eDAcOHFA7GIgwCnJTKuGdU9X5pFUZFaCvvTQtbrQqhmI3DwS87Obh9YW85HWi02m9EIEvGGhFCuxeMK8lWEGgINDlCNDRsivsoU5FoRLzKRKmZHDLO2K3L8+FIYAQ69Kly+HPX3xhqjEQ2pnATtEY418xLPF7YbE3+mZ+qWJrYZ5Jn/466dPfHFDPSPo+sPZoF/haLTyh9im6rUvhjfW197iQmhYWsZbhi/pBWIR+6O9H/cGF8xdM9UR1HyJEwXnpP0LDg/sPhI8/+ii8LqaYyQyGyQ6qK9b0aJKsu/RIz1L3sObrOX/yfPt7ovU5IVs7pzhiG6gjmpqe否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是mJlitWlgKtAhkrqQoDLM2BDu/C56OztTkVLh981a4fOmSVJHeq/h4iwLovRF5IcnBw7ZJplh3MQI1L9zFRC6QtK4V2NWNh4GJXq6YgkBBYOUiEDkVb8n+JD+8+54iFpDGJsaN0eSEXS6wM79ZX5D3EbgVkxCAy3CA/ZlxHtNSM3pXu7u++fqrcPHChfBo+JECHGyAj2B2kCsLhwePzjmZJmfjcCq75JnvKhyRYGSJUAxhHao58rtVJiVIe6oF0kePhsOTEd1jmKlFjemyEMUvqq2kzlB/UF0Zz9DFJDjBt2FDn93Rxik+DGlu2TIQZjZO2wlO7tBjpzp3uvXIjZ3oMzPTcTKkutajODlZ5/WTOKoqWGUU25q587xCV23ctrYhROQRo7+5xjnc8LVGAcyfRUcKTy2P+/btM0xQ8cldNa3islRUqLoBMFKmd7NTXK2oyyldnvecqohRjfJcFLnfufwUt1cdAR8/WrWV+WLjceD/ZeKZb3qvpr9W/cBiIJHitQd/vN/I05Ot+tzxsbHw4P4DG5PY0GHGxiDvwxeDng7HQfaUNc8lqVX117OcO3aYnBL9bARyldRxUK4Lhv5lq1R2nzh5Ipw4cULvW22jEpuXnmmDE/yO8ymxGAkLvxD5GtSOw8/kJvIf5i23Lu8FgYJAlyHApsRq7tFVtLUYNCC0mVh5q8abWfTX/dwsp2LREgH6boM4deIGueqIhgONB5oZStjzZOSJ3ZM2rI1Grq1one6D37hxU3UNBpEzhphJj/hR/i4eAhqH2VCsCJm1c9rrkU55DUuT1NSMBKmp+uftA3+M+bb+0LNGd9Zutw25XG3BxjG05TSsjSwesasjJtoBuEbQ7UTd2PiotC3dlAavhxJgRz7eoMevrYdkDUDYe3AHJHN1q/S0WDK79j4zT+W1ILAgBGo9ZAsKVjwXBAoCBYGFItA8qMXwuS2858T4hJ2uQ20ATGfjQmgZCOeNunEb9icxhEz4UAcZY3j44FG4euVauHL5iuFdLUSmBPJyaZemTRIyjy1LR5aepsWTeVqvyQOqOXKBXdw9htqIUQnVRirGKtJAZHpLPxjc/EdCzvRyR95O3c936PAhUw1pqjA1MeFk2VbtTj906HA4fOSICexcVWSPBHacTouMcmSWeyqBXZbRDBCYwsgYiijUKqR/5sV31SWCazcygMFmbgMt9pO36l12BoHyy0IcdwEiiFzbs06qT2bClASeM2o78TRdjB/qlXX9JLRTnhTZrLQpJ374jT+Y1pR+osNdYqwL/VvHXBXiQqMo/gsCBYFVjID1bB3OH/2QTHrUH9ECCtwwnjzR5iF2RDPRn0o71XF/pj7Wxxt1j3Sp9vOw3fb0/n0WwrMsuo3yV4ce5xG8XnnO4VEGBrbYfbT79u4V37TBnOCXTGhnhRt9M2ZjbCxXpezpkepx8VrO20Rf6W9e2RscykdBoCDQFQg4780AswKMUUnnkxn/ev6MJwtUXudEII4RcfAGc7tKgrmdDG6jo0+k/u+B5ve1ZgDc+rjPVKet3Vg8iqaUjSPSgacKKM2mY9lofYMTkOPaoG4C06qBUAqse6k8Nea72dwvddhHjoST2rAzOLi9wc39lGcTArFpVHw+XdKTUW2+e/ggDI8MU+ETk+RerJD8I0YmK2OneM7LeEHOy3PxVBCYNwJ1bzDvIEvrkQXKYgoCBYFOILB8A0uecv7OCDqpy2FHn4yGaZ10YqBk57CvgvkcoHkxoxPorNQ4K6YbYB1ce0YLx25KO4xu3LwRvvzyi3D+wgUxMqORMUkZ96ALwaGhu24TAfS5E6pefPLgi0xMOjDsHhuT6ohJ6d7HQLcL88zC7CK/VUWooPiZ0Q7DvvUbwsGDh8LZc+fCfl0wzASFOB49fGSM80mpkXzttdekFnNQ1SumySPuZOXOCKnFlIW58Cd68aTn+XxOoJSu1+/mSJ0u7E1YlzyAHzHD0PPrSc+oAlM0Q3f6pSD2IFwlYMwduur9OZhVtM7XXxWgvBQEOoJA3k47kkCJVAj4qDEfMNyvP+cRJjEXzb0K40kU2N3TCTvtiE47c4mR7tu7cOdN5pFS8VIQsPGbupbXt3rMjrbOqwEXd9BxJ+2unbvFM22Ip+VS9YZXIaxtHJJfwvkPe/gYNkdFVdnE1mgW0EoaA5avgkBBoKMIeJ9gfYUPNh1NceGRN/RhCg6Z3hcRm/Vj9EkLGsMXTserGIK+O/95FUEINCbNAPeldnFY2mr8RBEY9WksYTzxuTaDkKlsbALQ6pzs8vJt8lI+2yGQD6rpnbZsRt9skH6sa1/iWtdMHK/9pKN5qlEnHJuM33rrrfDRRx/bZmOPql3yr7x9jn8GBrijLQNVsePjY2FGm5sxdT/rK1FEkPgoe8PX80ybRJ8XrLgXBOaBwJIK7HwCMQ+6qsYzH7/FT3cgsJDy7Q6KX1Uq4kC0FLmHqbBfSux5KbM4hroAdh25QSBRMTqyNObfHcuzJQJVW2wCPGKXmAo9bkmn92effRb+8pe/2G6vPLKaXaxt45TL/9b2+VtkJFPCmqR5HZjtJ9pwjx1qHlBXSTn7rjLUQ3Fp9s1bt8KITjigKjPWBYYtqMuFUvpmxVQ/VFpOyS+LU5s395vu973akc4EBTMuISDpoFbq7Nmz1W41w8wY5khDJawj3mQsVzGZapJkh+hkZyov8lmqwsD6NeBoEbh9dKvS8UTSM6/zlZPorulkRyVCRZ2aJBX9B0PUevbq6fGmJI1e4tF+/KDpgf3jjF0Dffo2JjW5c2LQfskeNwvQEIiPBov0Da3xJ4tkamoi否RLaepRQ13bR3dPI396nM3P3A/vxbwMAt6XxL7jZWJanLBOz8vGRvvi9zLxeRwvS0sJPxcC3mfM5aeVG+HamRSnR506ntgjx78ekvoxikpM7VR/PPy4WvhS1bH6w9MN0dR9mNsu77Oqoxmh3kMuL2Ul9eZy8LKKRRUrFnbwLkM7d4bde/aEfngl8cRuLIzGe+00spEUteDWp4mX4YnBD3wBar95j3b2sDDxrfwtCBQEOo1Ac5ufT3rWYtVuY8udT4il8gNNLahKfUxOhfdJZhe7pczZB+LMqrwuCAFDMOvzbYYH76JTdmw4Yk7shtPWfbqOYp2ugjCjIsR/brxU/Zm7lffnINAIpSFrvKGCxZoeNx+P67TXsHhKBKvcL48fNt/oryVgbUaWaHLo39Sv9Ypz4f333w+HdV89V4BUJmtvxg/QV2R2lb/V9AJEz6ucth7T2EdxEODe3bvSLnU13Lt3T+0iqbhXXGggAjbr01QWFf6UQVOZNkIZS7XRrnwVBBYXgZrrX9x4S2wFgYJAQaBGoHlgTYMf1jljMaPd66jEnBibsLAMmM1B60jL2/MQMHzbeALba9evhU8//X3402d/0k68B5XPqGKp+mx8yRiX7LXBT16mDQ7pI+cluVR5584hnXTbbgtTXt7sCIS+3/3ud+ELnQLkHqF4Gg+VlbHe5MJcGF5O5SH0RV8/a1rEtU0XbB/SSTtO2/X2rjMmjHvzTp48ZQK7bdty9RKJ0Y1cm0Irhwit5mma5HXOnVeh5yqPytM8XnL8cu8WP3/mME5DuzjmC否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否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否d/IUtospp08Z+WzQ6dOigLRyxIYkf/ihL+4kpqO+yg5XAvmYpYlsm8hpt7NjYxII6T0VQO5a3gkBBYNkQqJsivXdtrG9I7Z7xaDn78obNAInEhmFQpCOwM5WYbExMmYJuNks+nePOzMZc1/kvby+AgLp1xhPGBw0cNn/0WCZVDtzLTnmwuZWxgPJqVLBcl7TXy4Zy9sjKc94IGI6pHdeLAnWtRxj/WGsVnIwbGxtVsW1tiHuNTtaZb/3hQqvVEQAAQABJREFUzcpDf+xkl8p6qwRMJ0+fCucvnA8DWzar7H1s92edVkPEr8pHU/b5dGSA4PHj4XBVwrqrWuvasmUgbNseBXb4iUGjb3CvoCVgMvFUXR4jDk2JuufyLAi8IAJdJ7AjHzAldYfzgjkrwQoCBYHuQQDmIlHjw1o+8CGwGxWjYru/5JOzTezQI4yH657MrCJKANcLJGUrql6JHzD2ly9fCp9++qnushsIZ8+cDQNSk9m8+OSI1KXsNi/3fPpsxnZmosqSS7KPnzgRtg8Nhl1790jAOxEeD6PK4J7GDE0KdUeCZoxhg/KzTnfErVuvSaNO4OkYnqm/RKjnk0d0xk/ZBPhpWCsMqGuocXF1UUw8fXK8Xulu6t8UTklF1RtvvBl+9IMfhDOnT4d92onVp/vxGKvwz0k81FRpWm2ZJk5Nmao6bLbRKdnOAxvlxbRgzMPrwr00FXxDBBWhDbbloyDQrQh4e+1W+gpd3YuA151n2hWhbtsMPAk7b+/euaNx5rGNHd2bg0bK6L3n6t0bfZevZUEgDbE231VpwePAR3AfzZ49e+zeYARrT58m9dxagKVuou4b07Z82xQ+fEpvL6dfuMNuXQMPR1yJHIu7/CkIFASWFgEfdzzVal6R5iLMXWj7Pla5v2V9at7l/Qb0IgRaLxWLCILiHVzIjJ7aNQ否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否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否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否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否WbahMEQNhJ1/kXK5qR/1lZpUjMLfRXvSjTQYhn7U/6hNi9vdcZAXbRQbHvjRXFYG5ubrmtbZywyyzYxa5qe09qA否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否amZSlqGgf7ni3cF0RdVRFd1YGlDLrVYcN1plOfxkSWxaUxcXRyqNIXhwxyh/qFCxfcSy+9JJNe/f3RtQIRCJ6n9chL6abVMXJV/WtRDUnMlI1AQmNeiqHzux9pHhqGODtM1nkFD+pHdUkfP9QgODXNEDAEMggUtpKkMaZOSQNixbbIRa/JqUm5w45XRlDyyBYWU9pVcbGfUpkoBpiL/jwFtAoxnjyt5WlFOK0e否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否WNjNnCLocAiyKtO/4tLh2tJ6NjI272zKybmJyUxTofXnCvlbAkEg25xAIGWvrBTRmnaqy6+koSnRj4ODQpyaQc7JmHfe7mxNNL8XYDEG8HiStctE75LwakgWlsRek2M0PAEFAEdPwgLUebD9sR36URqkuvc/GLd8GNjo+5haUFnLB7Bkk0L7DpOnvCLgkq671hX+yb5E+avk+/poH3102C7o1gkZ/3na2srrh1nK7TfkwSpY7xkcwhRWYNS7gFfCQEWHY81cn5Q56YHEBfMDw8hA0dvYeGl9R5K99DsTrMgWCJNhdDqSfsdpPNZL4/Di006rOzoWtf7sPiRoHA2+DlFLt/dS5tnV/+Hjs1pk7Rt5QeMDU1LXN4O6RLcOrpg19MjN13wzvLiI/i50/YLbpnz56KiP2zs7MZGJJyUA/BluVRvYo91+azNI44rFLbRpjoWOf/Z+89u+TKkSxBkKEFQ1JrzcwSWbXds9Vdu90958yn2S9zzv7V3ZnursqqzEpRKUgmRSa11iK01kHOvQYYHt7z5yrCPcI9AiA9gAcYDIDBYNCGeuDeTTjjxfVGr23XNmWSBYHNU8A89cJNjynoO7916xY27H4wN27ckG8O9Pa2IBL+25MQe2QyduHCRfN//uEP5tz5c3i3APHdTE6FCcmQXdRodNLE/DUeBfJ4yOu4n52R6+O8tkk1rew2PmLRQHmx8UoTc7TlFKC8c7KLacvAEN88/ccTTHdu3zZPHj8xM9AXHvlmy2snJhgpECkQKdAUFGBXosa73biXamuWoPppEQeI1Ni+xkOqd7QjBRIKYKWG6uCO4lmB4yeO45ZdfxIGV3ZMQm4KfyngKj9CzpSxczZ+Zh0m85lAI0D267CSTp7ncwldeLOG71HFhZWETNEVKbBhCoSNTwUAkHF+zC5IvND2+rBRNzQ0KGrn+BbSu3fvzejIKN6CS2/YbTgfG4zIPLII+gvRdGJzfwgL+FSJyfdfx3GYcgEHx3XuTxmo7jBedDcKBQprlXWW3LBbwuENe/M6VInZKLnfbfngGtkH3OL6sAaVmGyYZUwgbgohudMk0icTpN60xVhHFw5IHzp8yBw9dtT04mBBHB9YmRiQSN4WHB8fN69evxYtYqoS09KxfH1ZuHJ/fcWUA4zhO5gCccOuZOUWSLES0FlY7RRha1Dg5f2KYdQ4DEc8OWWBRQZ2rBSafOT36pUr5j///d/NLz/flIGTgDINmD149XIvfvzX29NrjmOCee7cWcPd/472dguEv3Zo5QZYLq4PzHVoITSQGY1mN1KAfb/+LI+Cj8irQgwuBliq8Mo4b9gtL+FBahjybwtUCNFUMP4QuPhnZ1NAJnlgBmzfyj/PPI5HGE5VquMTk+bRw4fym4He8PUP0LlOGPljF6Ao8xJj5ZXyqfJkEp6I57ywEK6x3SxzWG7mNs+vsUsRcxcpECnQ7BQI5ZCVv9WVqJTcUnxpGP2SlBVEEg1C4K/jDZ4mX8EhEJ1cs3/ZaqN9nqbLcVFcEFFqbI/NsYOvBxws+yhvAqR5Y2Bw0Jw5c8acxC1/qoxKjDKeZTRbvwxVHgzCfST18x4pB5PXXyqAH4qWNtEERj/taMqNqRCu/hIX80OOs7go242FuS7cnNExULY9MAkate1X/BspEClQkgIq00WHmu1/2AYpY7g+w3cwuenf1dMpN5zGR8ftO++Y61AWbaVh29f2T0mh8oub+/gvhvnm+lF/3z65hbUwN28msFjNd2BVuPAwudeiw1gssBc8gib+aQgKJJXCuubNOt6wW8I6DW/WtaGe9e0yza7vG9UD否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否Im2EitNKg6m9h+WWHZHC5Ruo30jw63vqwPzgoIdMo5UtPNezBDv26efzoifnL538xly9fxnXbOfsOHeJRdYKcbHJpaaIcWPX1YcPu7Blz7tw5GRDq1EwWJTw8M2zz4LPnc8NTGcyb/nxA4JeX9xAuuutDgbw6qUVKleBVTimEZWcvv6CTtyox8Ybd2LhZwFXxXHYPsq5YaUezyyngmEF4CseT/GASZLFuyyXksefPn5mrVy7Lm3ZzkJFquGlHmSdyTzwRR/mUzKjsrBGa0naEys17qbDcCFV5pmlbVdQIHCkQKRApUAUFKKxDky/bpL9IDTQUjjZu2OFm9vzcvKjqplpl7VesLLP9RZjKZtzplAsx1bv70fQLU44+pSjAeRznPzpvIiw37A4cPGDOnj0r707xlkloLN/Zc+tc7LILXgqRrWnLiwwtrKMQ否GzmPQ7hLEh9MkzHr87uk0+1xPyybjIqrrjJiQ37Xj7Jwwj3o+uUJqDvLSiX6RApEAeBShR8C9opGxngwP98lTJ4uKSeff+vbthZ1U0C5ZtamzSfSJtyj/NMuVBR0enyIfWllYcJMctdWjPWcYByUKTznhhn1wYI/psHQVYl/ZXmCbXFdfwdA7HSaw33rDjgf/CHieJKxcr01WeBEZX5RQIiRzQk062SR2rKsIARL1ybG3BGmQTydYZdBNhPRkYEcxnmKw2否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否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否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否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否uI5jMWTBBiLT3SNvsvX398kBp8nJCYyvLSZZcwacxvX4d4FDKOPGYqQZ1cryiSCuiYrcD+RmKfqwKqSOcqs41zNFXa0v1hP7mD3sn1w0bgAfPnTYfPbb38pG68+/3MSbdu9lLWx4+ADGwgMYZwJeVDmk0NqPFJ/khFflVb4slaGrFZ7KUtsuqFIrlNuVp6ZKVxuG31WloHLMToafx871L3iH4OqVy+YtrqHqhl2xQlbEdkGDYePjBJPXkm3D5CIEJ3a2oRb0xsUSjv6RAtVSgJ1P0AFRJea7t+/MKCYlVKNhO33HrLSKMHeAotocRPhIgZIU4DTasl0gNBGDajEfPXqMCfWQ+eMf/29z6dInXi2myHIFz/CsDnqYqIAoXJgL9Qvjql8I17TusGBNW4hdnfGCccsOoIaWiUXx47EKyqXxiscJ+V0bcp5fBYmVBFGcmkZJ4IYPDPv1UG7ajG+ujBpbKVZADACo5GcY3+ChusE12PamUnrDjngUZwGu6LGDKGBrmYvONLJ5izkUTxx/9tlvzcVLlzCX6s0tLxdYVFYQwKLgPCsE14URhx8Lnh8+cHfOnpTnjRWGLGHj+CkOcd7GMwm3frllXrx6ZX7+5Weow3xmpqEyTw0PZFptLHb4HPJoMXkV5lHxRDtSIFKgxhRAw2db1h87kLB9dnR2QBXmfqhC229WllbNc9yafY6N+VmsCalpzLbqSsTyOPkVN+i0xprXZq3SJDyqLp0lWwge+OeGHTcbqJlgI2oYbUrxbzEKkPKpcYNWBf3dT71acJC4vQMqMfHGLjfSP2LXzN6KBKQC0ZFCWCxl5YJ0uKIRGyD5UEkcJtXd3SXqw/v7++W2HUMpEbnRT7nWmLItKUNdXCQM/7u64MEr3q4bhWa9eTyLVS1NwnrwdaQO0jsEyCmQhmt+CML64TMwf/jDH+R9Papdf/70hdk/dNAM9A2Z3//+d3jLuV/eU2Z+Kfu5l8F/lu+QAU03Ly/OLwhyOdNIORmNXhVRIG7YeTKFzBSymvXXxQdtAD6ad9g4VlCRnxlvjzxyfxsPPN64fk3UsNkTK5SzYRqJ3PXPnGNHnBCark+GDnq6AN4KoUrMTvcGk8SBQJcJqc1CKmr4Yf否fbL5CeGiezdTIOTTsI0kPKvUWYCe+3E8rsoTJVSfkT2Jl+2scnlbkUU7UqBGFAg52KK0hxq4qfwWG8y8/fzkyVNz8eJ5OfxAXfHkTcr65CRaYWakNQRNQvk58MpI+UIcjecT5p65C6lXKqzxShJzFCnQmBQI25G2r9BPc61+CqP+ams4v4vBKGx5O5zUlYcOITRtzY/aIYy6FVa/FTbrr+GV2hrf4aPwVoEMFFx44i2BcAySLS9jKpZKU60Url54K00/wqUp4OsePMIFjP37h8258+ehvu6EacdCO+tLOVNiOubQeZLOnRSP5ys3YOCtODXcLCY2Hq7kAjg3jd+9f2v+/t135i9/+au59tM1Mz0zbWZnZ4VHOW4m6zItvVVHXJ6HXMY0j/Ip8DaOphvtSIFIgfpRgO1O1lmYBBqstFdcSOLiJm+hDAz0YcNu0HR3dZvp6Wnz7Mkzb否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否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否UUI4xKuygrLqBG1fHlhClPMLhVHwyz+RpF9ccOuWF2W8GdV+mvibGD0AHNSVcrq6opcJ+UAjTprlXkJoqxVArU0gLxw4pGJn5v08TorHzjndWQKdDXyAOWHpGGofyk7ERiloGJYOQpoXZeDa4bwRNba7QjK24R/4QKA8DQEOjfqXr9+Le/YhTfsLEAzlDbmcddQIGFiX+Sw3XKC8vDhA/PVV3+Tk1B/+MMfsGF3zMMmjkJEoQ/bxs7mf5ZWSgk7LDkLHk2kQDNTYCfxs7bRaupDevZMhM3QJJQVGbQVfZaKr/kKysnBCgcwsPlmBFXSz8/Ny2lxO3FMJFdFyUegpqSAY4FU3skae7HhxgWmQ3ogp6tbYITrASCT85Cd2L/xP8JEawQuyHExc3Fx0cyAt/7+97+bL/76V/P555/LIc1VHNJcx2IGFQ6RDzlX1PkiEwon/+HYI5XR+BEpECnQUBSgPAkNRII3dPPtOh7wO378uNl/8IBpgXr9t1CHy6dRXr18hQ27/Bt2HkkDOCiP/EadK2CUUQ1QMXXNgjJ2wtHS12GjbhX9GBfm+R32W3XNTkQuFOBbtx3QosZ1Xt0Q2wxpWLta0wmeQh+GqS/j6E/8+Ace9OOGYjtVYrZ3+NtiPiLCd6OR8SE175NOjlakAw8LcrNuZGREDoJzo5MmAJFvbvTZlVX5xB9SulqTYC0aOxPQgrrs7ek1586cw++sOXjgIMay41KvvGn37bffcCiLtbBuc+r0adPT0yOb+dyso8Y/7jvolnKSepJvm1y2bEl4Y7gyRGmMTKVy0QQbdqx+ms0SMxtfvxW/TaX4X4Wz8fI6L55MmoAKhCdPnuQO0HTAh75vQ8ZOJG0+qBKTqjA5SGzlA+aKHJg1p0yEaWl6CcwGM7ChXMdIjUcB5ZB8PtBQybfnKzISfMBMXuwCkF+85v0eE5MJqBQMH1VVvvPlV8T5yXqw6IgUqCcFlA1lBIKEwgkpN+yeP38ufmfPnjPnceroGN63oQnfc7Cy1DKyyFgCKF/7BOi5k40WeOvKqHWV9GVbl3ZMqZkooLyZbYzhN2HC72z58nDk+WXjVfOdhy+brzyYatKoFFbTqRS+GNxG8JSqh8J0CC2p+PGJgwkGHQzn2xELfPB9ARt2WHhSIzJ7I9lUBNFuOAqErBCwQUE++ZTAwOCAOXHihCywt+GEeNr4Ea7MnWQRBnKCp91bPu41M9PQKDE6Yu7du2devnhpvv72a3Pj+g2ohX/lxxJ8O8Syl/J1ZLY0jeNXpECTUUCFihc0QZtGM+cthZMnT5rz586aoaEBs/5x1bx5+9o8xg0FLnrq2FVLrbFVQvg+TQG2y9aMbVf6Md26UkDZuHQiVt0z39xax+0gmjjnKk2x6kKxcubamdrhmgJx8QZbO9Z421vbC1RialpeFKlHjWxmjfJIZBKuf8loxiUmt8HglmeZeGkE4ylV78hBD8sjcfkHRstnv3bmX62HsKxCO1fc9bV1Uc3PZ1fmMR8ZHhpKCKGAQnShXBKWcjmCOj9NS9NOgSqM2OEWIBPh3IkVBXz2v/jxD/cTTp48ZX7961+b6Zlp0RQ4Pb0o4Q+gReKLL7+ERoph89nvfmeOnzgu6lrX+M4l1s32YNOPeSFaKYr8tah5GxTeO8xoiSxNt6JwTbBhp2QgcepNmPL4k0ailaX546nLdagGfA+VKLf否8ePcAIzOVFlGdjCClNrUorGfWcHdYqdcSjQFZxubtTxdAMnkmrktpx+pGyNGXjmeAWh0VklBZpvQEMGUEa0hc1lCeU7BuI0hfCoAOL0MOLPQDfze5wcmcRmdbgoJqgVfS7iKgkcwSMFakEB8mKWH8mn4HMu1c3PL5inT5+Zu3fvmH/8x3/Ept0FkbFUS7GnJYyYuBMX0CjP1yKvEUekQKRAHSkgDT+DX1uzNuQ8mEyUmn9uNE3Ns2Yo+63+1dpZmoTxNQ2FCcPy/MLwjbuJWVOWmWIOKi44zWOzbhHaLnThiWC8ReDj5sSrxGuz8StJI8LUngJ8c+XUiVPm0sVL5vDBQ8liGBiKMyw/jmcF48fFFjmsg409jm+fPntqrl69av78589xG/8hDmg+xu2ZZdOKW3tW/aXVwKL8keW0YnO82pc0YowUiBSoGwUwX/Cr0WjsfOuHasUuXbpkent75fYtNc+8efNW1oa4wM0f27+uIzFvKifqls+IOFKgSgqQP7meyQPY9hD2R7lRxZte7AujqS0FKEqsSUa1XNftwNti1KK2B3RXQwhZqQiFiAZWbZeXPjZrTNHCWhturIe0YsOuLdywIwzlG/4lZao6U00bIewSUEmsKBHwVIk5gw2w8fExUY0ZalxI9QAksSV4xTQgG+wpuuiUj0xZx2XPJ8lLQBcvXTT/9m//Jht2P137yUxP26yMT4ybv/z1c7OMt1j/34UF8z8O/g+5/WlVvSMTwqPEaNNkP+dTh4NFs38svtr89SnUBl2DY0l2eho8o42bPWFDZG+PTOxGRkbNnbt3zdMnT4Wxa5nvkDXthh0eJIXaBb6p4CeZA否jJyIUWvNXy5xEXLuaAiLtLX9x8MYbdrzqvQAh7vmQBIqst6vZpKELH/Kmc1PNsBqqduVNuxs3rssNO56cHRwckkGoDESE/VXG2ljpL8UU7UiBSIFIgUiBelKAIjxP/nKxieMSvWGnmyWh+K9nviLuxqAAx6Va96LW59Qp8+mnn5ojR4740+HcrONCky5Iqhoqnh6nOrv379+al69eQgXmd+batRvm8pXLZmx0DAd85qSQLbJCxQ0/Wa7y6SlfRp5rDF6IuYgU2DAFSqxCd3V1mdMnTuIwwEkc8GsxY1icnZya9KowZd6ADbs4L94w9WPEulKAPZX2UrYf1A07qr2zG857pV/TvrSu2dnVyPk2GDbsOrhhF6icJE3cOGMz5KlmTKJcYcdHSBXrfzrebsEmDd+wCy+NiJyTDSTG1NibyW3zxeV4k1oWPnAsCHpRRfo0dr7GRkdx024ON9K2b9O7VN1zP+HM2TNQ4blm7j24Z27fvuWJv7S0jHnUorl89bI5cvSo+eTSp+bCxfO4Wd7l65/Pgn3kwXYfKzpqSYFdtGGnLKQdkpIx+63+tIuHyZ4FIdCRkTvZmfFECvW+UgXC69dvzOoKHmXEPyveRM6FyMXNcBqFkY+if5gfmycKBE4k+dOJJaPJG3bi4B/KduJP4omn+2Mx8SNx7VYBG9KlHm4d4Nj6qEcK1eAM61vjWT8NUb6UUM/sCNVm5KKx45lDBzQ5MSkdEzsp8iQXPbTMFoemE+1Ige2lgLJzwVU4nhx0/E3efUF1V199bYYGB6FLvgNqbqwaA8vXaCG+LbhW4zxCvtf2nvj5SGkihN7aCNMQNf/ydADmpCyVJBNmlvBblGHJZzbtSvK7/TBJ/ZPWzVmGfCpqWbaOBwrzoWlrXkKIbJh+E0bh1U+/NX72O4yjMJu1K0kjC5P9DvOlZdlIvkK8ikftMKwc7mpgy+GCdNEsAFSbjvfzjiDQoWSb+9/svfd3XEeSJpokCEcSBAjQgk4kRSdSomyrpZ7uVvfMnumd3Z92z9t/c/fMzJ6d6bc7b6eNWlKrpZanSFH03oFwBEH3vi8yv3vz3rpVKAAFoArIJFHpIiMjIyPNTRPJOTnfGqO2C7plavAoItkdywGJAvtX1q/6XO/PN+y4sH4AG3a8CbNr9y7Ht2Jk+D33BLcyOf/NVWXineaxMfe//vVf3P/+3//H1F9O4O26h3iThDLF7y/mxVPT/jsul3+68rl0c195oiXZiQOJA+3DAd1yYVtX3yLq2Mf4N+x2uG3D29zszB否W7qDRdpx3Lr1444NY/jEUD+ltMlOHFhODmh0iqZVlr0fMz0llFG+0zo7yxt2T/0NcxtXlXo5KV79eeV9gq8V1gVvr/X28Y04vmGX851xuS/vTxheND6AGNVf1cL4FDZHydJ7Gso0EZIgL3ghxIPYtyw1vXHDLr99yf7R410rv1lxxRfO+rhxtY46yF7YG3a82HAX+wOTeE9bh8LIH7GdqiS9IZLMo0DYWS4W1sw6AncfaLI5qAkAwoB+Xagki0MQITnGDW7Z4g4fPoQ58j68VbfR6tiDeprujt13f/jwD64f79n9+le/du+//54b2DKAOn9uY10XZNVoQ16SA+VlGRtF5R9Ppw+tKns9eMEqvfxl+Mb+jM55Jfd55m1rXokbE1Qndg1t2NXhwIKDJSCwg5Mfb9xFv3HjhqkJZEOgGrVnL/KFAmU336plFhSqkJWhYSdeuF1noWwsyoVp4hR5eHKtDAdYH810tCtDXeNcq+h+/ASnLqBuakYPakP2KJcc1JPsNeZnil1hDpS6RltOC2GUXd4afYobGrxdd/jwUXfkCFRj4gSSmaiPVSlK6CqnXIJNduJA4kDiQOLA0nLA3rDDqVBu2D2FasNk1h4HynPuzZs3uVGcEOYbdgMDAxlDuFnHbycuPnFBhTczHz4cs3nA1998Yyow//D7P7g70KJCwwUqHk7rxsIaVWXmG8LlmUCWRXIkDiQOrEIO8Nt4ExYwd+3ahfcxt+Jm3RhU5V6wvoMqdc2gW+A3hj4dUi+xCgVhFRWJ4yZv2FHdM8dDynjVGtAqKnJbFaUbt3S5Yce/9dCilhv0IOpE8sB5u1rR/1AeOF/iPCjJRkUVBCbzjTdu2N3BW6aTkxOFDTs0s8jQ04LKjTCWnYXs4kgQwvkvDVU6j0L7xH68U3fhxx/d7BOqhffj2MzUIzwXcwYH3Ta4AcDxlt0mzKl5WWh9eMfOkNTNyGLb5qfI/7Yhq5KQDt2wk0C3QiKEi/wp44vjPP8k0LwiTnh2UhzYaGafzJqe2snJSR+G5IznP8EQLsYahzNORvlkwoQsLEefVQDjABqpw1Ris+NcGBD7C0hCnMJkG5L00yIOdM5gFsuJ/8CgRCjU5FIyj4hpvPc1A53G8YkRz7KiHEmeGZfJtAdMv4kDK8sBCTepCGLL9sqPFU6wvsdju2fOnDEVWtu3b7eT9/Glf7XtXOJjhL5oHAMi9D4w/s0Tx6FL6o7b5PwyWgFi50dg20FLRtqOsAUTJBlvJAuCUSaCVXjZTziFKU0ju4xHsDEOwSiu7Gd4VZjgO80ul0W8iMMVVq9sPCHrYWI1wfWglzq8qp/SaVTNJeq1L75bx/kJ//INFc1B5uJD45KJo4vD0jiPFLtwDlAmJMean3KTbXhkxO3cucNt2TJo32dUTvTs6XOoKXqGAzleawlVqJ45872pxP7444+xQPEdNKf8a否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否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否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否iQFPxHJKTyBXptcB0Q2zYZqjtzInSfO8syBKl02F0Iyqkp5POtVdvOA0yTUqD2G/E/jkWo+VP0I2gHoZ/ugau8nmE/xTd6nWFC2j8CgzpAbctwApypZTu43Ql3MRiwac/GYJ/34rszAwGbX091bmKep/bFs/r/KRJaoXJVFaPtAiVSzdTL/AuW8mn/aJUwRyGKPWVWDbBs87cu5MTchJAOLoYj5MNuq/BaDN6VNHFgsByjf7G/70B/u2LHTHYHKbb4v8vbbb2OT4Tg2616GCqMB6zsJ9yxuExJsEOGfXOAGuD8U4enKAfp6+4B/h9sFNUibNm/K+lnr65k+jE2LLU9KnziwvBxAz65BNMgwvdbfhw5/8+bN7ig27F5++QjmKE/dV1986X44e87mLJ5WjjO16z6pTSxvTabcEgc6jwP5zJJjKd/JHBoagr0Z3zx+XWCpy5SP8nPnZN9SAYz9m32r4bbYi3jDjkVay6ZqLmRjiR9QyDd+73LTahLr93y3sBvftl4ScnloNQsXgtnGwnCoi/RIVvhNvgWqMUfxnt1VXOa4d/++fcer6LLzMviyy89yf/fdt4bj4MGD7s0337KDYOQN/zLNEUiQ8wXuIhqLE87mbGLrTLNKNuyaqYBSLWfVnIfnLl+ZhlWBZvt8qAKBaih56+gpOqrMlMioFdgMsnlHjDPQwgYk3BTsqckpd+/ePdzemDCVJULuk+bLUkrD9As3YkgzGJTRfNI0gzfBtI4DqpsgLebNZYb5UMYo63ws9FbYdCjmT3jhKcYkX+LAqudA6FizFoCmxPagE2c2+UCny00J9tOfffaZ++H8OXfzxjXblNiKyfkW6Ky3hS/AacNu3Xqqq1IfShtYs0zKXPURWjwrx9Kfq6qMkdCtPAgVu+lfmNFDxtX4GuURx8V0KjwOI21lf316Y0hhqw8dx8QpGR5SxwNpAFGQryd82gRQ3z/CA7h8wukTZQcjgDenC3GM9iAID7AMlhAYsGIEagDmYQrL15Laj6c9y8TT18WbFPxfulEB4GC0EEWZFp6cjowe5oZ4zokez+BAEzbkpqemsEk35cbGHrjxhw8xqcc8BQeLHjx44B4+HLc5C08ZzkA1yBTgOK+amXlsm3bZqU3SBkb6N5l6TGXM1qGtbmhwyB06fMi99NJLuE1y3O3btx+bd9vtw0eMV9vjx4XaRs7/vHx0ZWwtARRrReUHUWZoKywEZZbCBZtFtMwh+WoZwgzR0tGcZdGko6qMpK54nreIjHVpG3aq1BDt22ERtlmfarNZ+ASXOLBUHGBfto6HfCiU6HN5iGHXrl32Jsivf/1r9+67PzFVQ924FbcBp/R5C5njjMk/bPZpbENsWwwjGvoZwDDeZmaY9e3wE7qnt8e+L7dBpWY/1CCpP2WyZBIHOpEDHB5s+uOF34pAmWe4l2+2AocbLwNudM+oG9q61V24eMF9+eXX7vz5C9mBbcJLJZwlwE8+L1JIshMHEgcSB8QB63SCx/cz7HOofncA/U3/xn4oC/EqMa0vQSfD72rC+P6G6ekXvlpbUfrGtEE9AvO5+vE9D85DFeZnCPIV7ec4lPkMByy1bpHFZplnIWvDwcqpNHk465MbdhNYs+d3Lzdoi8bXbTGsNT5VS45NdPmYmPzyHM+PaYT32m224Ibdnr173I5LO9zE5GS4ZefHzxx/lQsHx55BOyAO6166dAF/l9z9e/dxk32TiSjfspMp0CBSQ6RkP6ZZ6WrtHGdtHENi5IKNw6pTLWfoKtmwWwjLvNDNmZL1BlBaqkLeqmNDm4B6s8ePZyMUATgKWWqn3bDDAtidO3ewADYGVSd7F5BlewnlAgqQkrSQA+z8JOtEy4VTngS5fu2au//An6IodKItzDuhShxYLRywSW7UtXKyw9否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否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否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否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否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否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否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否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否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否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否DwFOIFFO12vYWRMl6RRP5CONJrw9CMzR0+zhL/zWLBbwlQuclRs9Fhh1qxZI39yao4ZpznN3OTCdzhBSfWWd1vu4K/F1Rw44JqammSnxfi4L44y4UGrZL5346WSx48fd508ndfb7S5fvoRdEeOL/GXG3hB4lRFga+vrXyUUWFe5O+qdd951v//9P7i33n7brYcAJnUYgZ5N+h0v5CQLRLBwAZ/7nEhDI50X7zRZgZMBEOBGRkdc28OHaB/uymlapRNi+5kZAgQV+BJh7mLjCeh6nFSmg07mekY+D5KedWb8jcoQMAQMAUOgBAH2V/x78mRMBl3Xr99wN2/edh980ANZd5P4zb9PY49K49tv3gnGzTE81c62ngOrIajAZB969uxZ98OPP7iTJ3+SXcRUOV3DNGKgi1000i97Xsot3xtoTEpjT0NgMRHQYWO+FM4/Ri3H+pw/R+NgCBgChoAhYAgYAuUQ0NmFcn5041zxtq3b3KFDh9z2Hdv9HEUl4gVwF9kCAsCEaKFg/Ni0BvtDzDedOHFC7q7rwhwz3VLD7zC5IUVk+di5+b8Bc1v84ybEF86gUHI8yO3zPL3WjQ2WXVg05qlPXTh+ft+UL/VMD9M4hr/xF7COPOcFO20OU1DVrVwWT+WX0a9I7r5QRXUyqw4SPwXsB/ZprJwkWL16tePCmqrEpD9XquUZ2DP8IFaQb9++JfdpbNq4QSYxyJGq9VggfCpJmaWX92MdO3bUDQ8Pu2YcW75x47oU6CzVZjMEDIH5IKC1Le5Okgqe1vI8d+5mWb16ldu/b59759135G877pmk0Y5mBU/NgTHVDshFvzKZCTUE+EfOGidf5B4d3K/W0d7hOjra3SCOhqtBMG2C1MmeQIC40ABeb/Rd3uCId+5io174lSsbJF/kPkElD/SaE5pvwdsehoAhYAgYAuUQiI1u3pNtqKqbpA+1Qdy4ft01N192R48ccU3YSVwV1MbTX5vg7LyzutBXG3baM1r/xl8u1PFuAT297tUec7B3+9Yt98MPP7i//vWv7gwW7drb22UigvI5JyTybb2PM/0kuuRjz2I1myGwZAhodQiFUV6DW1pep0oPy7qV56kQMj9DwBAwBAwBQ2ABWn2X1QAAQABJREFUEeB8D9ix687Lmz4OLpht3brFHT500O3Yvj13ws5T+E4/k40pA4fO3xOQs7elzsFJJeZJbAanWYHJLdH6FLS5cc6ZsnrH43Z3/vx5d+vmTVmQ0A11WZr1OyJjH6f9LiICmqF5zIt5Iwt2DViwwx/tlAm9XMhwyiNLZnQhHZz5p2TRT8tUFmxWNuEpbDNbngFj8mM3ecoEJ7SL4YRdDxfsOrvc8MhwtnAc2Ci3PK8FfFMAkvhYB7T+cdzIeVou2rHeRBPoY/D4rrVVOcQQiSWGStwWx+qPjywO7wJX/aiC83N6ZaXRisOn3mHHU3a0q2G+8c+n3qsK6u3tc1evXHGXL12SE3NKy5MfvO9OqaXicecw/pHnFjTsx14/BtWYB9wGLPSZMQQMgeeFwAq3dt069+6777l//Md/cu+/975rguovtgVUszUh9ZZtBGqvTA5S5aVfPGI9X8FjdjAqEPFkHS8ybcGp2wsXLsgdO0+fjmUfxw7NzMwR0A4TuFGIoY5vbqbg5bx8zxvD否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否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否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否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否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否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否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否fHAFHwBEYTwTsPfoWvPCsieQtyqGsm4dz3zat3yDvwwTm2jVrVVnHlZHkNZ894076wJsYfxnSG2Dj1WmhNin/izmPq7o556lVjkULpQ07y3lPnoTzodV/vGrs5ToCjoAj4Ag4Ao6AIzBZETDOz/jIuB0mr6Lch4J7WkfbAlnXsqVLVZGSxcVidPCV5MnIZ96GKUAeB/QjNirs/mG37rLjeV5cNEZ5Ei3/9CeLjLM8/M4RiKRV+J4h/aksPVUWge9XQqU+wSh3bFGLx4nVwOSiNl46YJXs6NGjcvrkad1lOrY1fLnSiCvgVsz12yppKDdZ1dc36B+PfGKcyeheSWFnHcyGWydnIMQkkfmGOwt73ahZvixlqLzN3+KG57gDw2AKWQT/LI0RBEuorqqWWRA+z545Sy5fvpweKG8f4SEtB2WihLMimTjPGZZhp1y86ha5WTpkyA99Ksx4nlwpJpenWLW7BgenTofikLuLKPxmXlTgUQAe6mCFWTuS/koaYxp+hjJ+OHQ1QRAePKxxxcoVMqN5hmzfth3lc3v4T/Lrr7/KeZwR9vjRQ6QM6wn4AopfQoaF1WCoa1xPa6/V1po/VNrJ6G/ttbYS7cwvaTE8iIE+JX01qK1ZBhpkdBT60hDkBBHy5DjljromKOjWrVsnq1etwt876ON6nIs4W5qwq64W/U1lnaVWO93IXWuDbIbrUwuztOn6BGtcmmvUEosMr7Q5Sfw0KE0f0jHeM5jl5G45CsVKsSO0ZEqJXLl1RT7/4nP523/+p577GA4FhtIxWTkVlaq3Vt98f39+OQRCN7HXuMhAL0lGgf6KoTSdgYN3Fy1eIrOwg4Lk0Aezu+xLhtGxP7mjuKOjQx4/eYx+LdZ3End5hlySLP3iCDgCjsAbgEDMP3Ouzt6jw8+3k63pxocUqjcXEnEBUVtbm57TvGvXLlm/fj3mi+b0vOWwsjmkZl7Z/J1yCphLgm/KMuAmCyWekYsD1DvfIyd2lBBTVxo13JDLZ8n6lCTjPfuWfzU1tTjLbpm2iQKhjo7bwl137hwBR8ARcAQcAUfAEXAEXhwB45/Jb3FxuTn622448oUU3C9evFi2btsmS8GLVWDHE51Z9+Hiqh5sjKAVMco3d+/eLXv27IHi7gx21j3WxVcUbJBvG46XtfL9+uYgkFFV3jcEmkhaSGkQREgS5LcAlbvcWDNtWgMsbVSCFvXrQEFR+tFH/vAbhd8OcSn0j5812aCfLMcQxBSWKj8sxOD3SHKXXFmXp3pMkGAzRzDzyjFw+NBhOQkTsFxAH9N7fB9yGqdfa6A1WKsRPPlr3lwE2tTUKNNgMa68vAwhIU7uRhQmtgx5j/TsyMQZZvY8HtdXUtiNR4XHokzrsvSqN4kAOt3oKlKJAdjW2ipLlyzBC7xXLl25jFMaB9cw7vTBoXk+oI9MCGFh4WVQhMnkGVZ08Gy5ixcuyN49eyHILsYAK5PVMF85HUoY0hsVblplEptRmV0tS1z5gmFZJEl9uWgqTanJWG/udikuKZYGrAquh1Jn4YJ2nD/Vrgc4luKMkfLKcrl2/Zrcv3tfTddR+B52ZFG4nhF7VKzfjgAB9oL98e1PJPU56cdhaSqJz2LYx+w/bvGfCtOp1dgxSeVue3u7fPS7j+Rd7Kgj/dKsKv+KuFMzj1ZsImIltCZ2TfJnOWPtqODhrk7WjQwZnZ5bd+aMfA1TmLt3/yBcDUXsiBuVQO7GAoEgnATsKa3wzhwnTppa5S67CkycPCOztw+769BLxVC40vH98QQHOj9+/Fif+SbRPuYuXmbszhFwBByBNxwBm4bflldeGVY/z5s7T7bC/OUO7qzDgqKZs2cpX8L5njyP8SLG/zxLTGzzczdwSNn8YHcMGSsX6pHMfSiU9bXy+aG4DEKi21iI0nm/E4tS9qUKO8az否1VXb0cR8ARcAQcAUfAEXAEJiUCxlyh8rw1nm+otlRVVcg8nF3HXXZcQEVHoT3TkVMjD3b79m05dOiQfPPN11DW7ZVT2GX36OEjlVeUQ+HX09PjO+sUubfv53n0FcgxyNVJUbQKQrl5I6zgVVZWpbLKfOQiMs4PGrVn+760Akj79g3Sj2否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否h9obWtz8+bOlTp89HjYPRpGvwHiGt+OhkeGIYi9Iyp172LjgfUD8nqg4vQ5OXlNXBICCYGEQGkE4p6sNIV+WHxYuW9K/vuIDkxI8VMuSpnUZoY3ymvlsr67VMZ59/OihQvctm3b3I9+9KZ7ddcuUYPJbzuR4XeDm3Nk/OEZymn88B2XEGGtI4vMbelZDdJt4wzLm9Koy75RhWHGpdzT4jLcl7mAgaVo8aPc8LsInPjHjX5LcNq8CxtZTpw64W7f7nODA0MWKeQ7eCRLQiAhkBBICCQEEgIJgRcYgfxaAEdYMsrCOMyGYhzbcR1sxdLlbt2adaIJjZulaCiYOHz4SIF2poGBB4jM8ZmOGbkewXRsLGt800KEQPhC/+TbH1sfhbwc+Vt7IUCzZtVBUDcXba9VTtuxbWUGDp4AhQeXVm0u4ltyRjaGje2Sf+TDP5rQVtWZ8UaAzk4yahESskDI3BC0XF3HXY7nz59397y2QM9ihjwKS14PIV0L1sU516JMwozMweCw+iiuT6OsrGdWginJF8G0JjUlCU4okcKqz7PKykEp+roN67DYfNv13uh1hw4fcvew4FzOWCMpF/60/hQG3ceJNe4a4Sm/B/cfuMEHg5CiV2FH7zLXgHzW8K6oogywLFz6sPqx59g5UVb68oeOCvmYhZ0tdbOasUCvJ+4okFu7GrtccH/Vxo0b5bgt7xtjx3Dt2jW5w4OnBHmXBwWP93EnH1Xk8WRVcfczdr4K2xnKE5fZv53FOCClojfX1zPiZzWeSz/iHaIHSzE29sEvbF90ZT48BVmPU4uNjU2yQ6C5uUk6HVnkgZpL6kTegvteNkBgtwa7hxrnQD9vlA/mUG8Ik1vCJMMsQcky0NOS5lOI8pR05/0CsfDnj6bAjxZYgjwfIzSxEKPYwg8K+bAueC8jefBUIQdQbB+HDx92H/75Q/fHP/7RncAukPsYaBXXW4m0i5NKPpOEQFE9gy/rkYuwVAvAHUa8U5GXP5NW6FG/YvftdnBoCBfdXnbdZ87IoIEnDtTEjdN7pUdCICGQEEgIVBwC1lvnM8bxCdW1885nqk//xQcfiPYFbuzid2IEY72REexuxqKKjCNkEAEuZRjy21EmKJ/0M3LLVyzwZl4KfSzIQjTHepe0jmnWr1/n9rz/vhsaGnR79+0V1fbc9cqNdfx+lhrXGNf0TAgkBBICCYGEQEIgIfAiIyDjJIwveUdVDcZOXDvgssL8eW3utd2vu3ff+albu24tNn1XuxtYo/3hh2/d7373B6jB/AiHCE6Kikzixzg6SuO1RtM7unyR63PGlj1qMlVoa80tzdCENl9OeNVCk9QjzG84rWEbmx7jM8hMIBcyF8H7wnX2Qay3X8b9dWfPnXO916+HuQfnbTNxHsJ3nafrFuIOQdYBtbOZmT78LQdP93yGAruo5YbmSb9S/qUyP166UnHH46dVFqdisfJ+7MTNWMOVhk5ChHGxgUcuOzs73eIli2WBWuktonKMBSrGx/iO76n8CuLSK5dh7h65i7sxDh86LAK7O7dvu/fe+5nbtn2bq86dumIBtHxgJPdNeWaSlKZH/pZmXAYro+Gjy+/g6KPJoosvGOM11DfIkfQ5c7DrZfkKt2XLK+427rW7jg/oFQjt2FmcghDvEtQEXYQg7+7dO1DvSQVLWfqeXcGDvDW7KnKUEshpMIkZaIMqUOms1DtfHitnRuI7WKaBDqCksQIjkPE0/WCBH4Rm6lkyeuxp+aGAqg0nklasXAlVgqtdZ2enWwk7VWC2t7fJKTuG895E3gUTQI+YoTsWl6+OKARWyQ+ePOLEh5WBuIUwCWEoLeERWdQ//gVpuJuOfMBXHvjRpLgMZSbOGf0ZglbExSpYR0Zwkw10iXNnRA3++iGsO/DDD+4vf/mLnKw7d/6c639wP8uvsfXPkBJ4ZWnmiJJzggho/xHwzV4c4cuPJO/3XIa7ilogvA/vIELZ5CSeb3sj6Ldu3byFU7cqwLd3UTMYUphgflP0hEBCICEwGgLl+xr7TlrfxJEH/ei27w0XDdixGU15bqPlofLD7OvN8kkZBYPy+aZq9oUdHW4HTta99dZb4WSdfRMopJKhhufDcZMY/30g1mZseJf5WEjsY8ibn7mN1udbnBZmtBlNsU1pLEbIFjzUD+H6P0S1tiBlIBFZ4I/3ufJuhRs3bsgdvOfOnsPtfdQmgMm9jBuJQUgpTirwTpaEQEIgIZAQSAgkBBICzzsCHA7KOIpjKNi45KBjRd0gXI31WI6dGNbcMs9t2PSS24K1z0WLF0GLwS3317/91f0ewrp9+/a5HggmZCBGpsIoG20xDRvTM6VkEgKjIRDG+H4+SBkB705rEm1nKiySkTzaWZhHShsejWtxmM0GLKo9SckmTMPmnJnMwWDJJyzii7kq8/JwaFgOzvCwTw/W4ilDKGWMk+WhFM3U+uVzRLfljncIznatWCNvgeCUh4gMd80jafmO6xxL+hRFJXCY2rKMndozFNiNnfjzQBG/pB0LO6DepsutXLFS7pAbwIWNsarHSSuvtUcy1DaXtVGfCFUIUj0m1VBe82on2YGs37hBTm1RIGQvtZWBzNh8fZsN2Y2TC54FFntp1FMbfkZA/vyj4IUq8VrxZ2mQ98DAoLvWe92dv3gBJ6ZOuHO47PLUyVPuOj6m13uuiQrEAQhmeOfVQ+wCGHo4jB3JQxDmQK0edtIIfyYHZsUf2Cz3oZy+V7OFtiyniG89nvfMnBmfmF5AzIgQX0OFjzgsHjtGnm5UAS+xYB1wx/kss8PN00j1OAnZjoWtpdB9vHbtWrcBJxJ5R+IKnJJs5tFeDEhiw2R4Eo3/WCZJxwCOCZ/EXlilTxJT69ZW1IpikrFhYoHqtpBHENRxdxPdunCFOxrv9btjuKfuz3/+s/v973/vzp7VC1GJM8tcjX+0F9d/cWqWanpOHAHrQ4xTvmapEnfTpo1ux6s73FII7XhfnRjWFRsujH1Eee/n1ctXRVVub29v6DuZhicV+vSTEEgIJAQqAgF+pGjwNLWN5i76zEnA8/FjxY5Lk+/7LYz996zaWW79mrVuO+55/odf/NJt3roFwqqlQvLoMU9SZ5oftK8v5sYhRal0LZ1Ke7IExflVH84LOFGU0+crlrs3oRr00qWL+P5dcVcxYaZqTJs76Ki8GI9KK2/KT0IgIZAQSAgkBBICCYFnjUAYW/lN5xxtUcsYdRPUYS1tzuxGt6qz061bu0YW7K9gHfTzzz9z/+tf/7f761/+CuHdHRy2wMoRhRYYXHLNydYZOOZKJiEwfgR0tC/rlpjwcH2Xh3gWYB23ublFDvUEXiAV6klsYsaqeJZgIZY63aCSh1FXuQeQVRw8eNB99tlnoqGPa3FmbJ3O3JX/1DJyI+hcHBLguuMCaHGpq8fBlmDyuISAirZMkcBOmieAMJCsoYyGjdGORjPRMMvX6HyyTpwl4I4OTLX97l/uprYF59qaWtfR3uFWr1mNnRs9EJhdhDAKepG9mWjDz8cX4Qx4lxZTaKI8Gn7h4kUc/f6Tu3P3rtsDNUTc3dza2ioE5DmCxQNykfvtCk6RaT3poryvDwODsTVA+Igz2AotjGkYhSbgSRjGu9bacVqMH9k2XMy5EUKqGzu245TgPbnP6g5O2vVAcNeHk4LXrl6VXTE9PdfdHdz5MfAAQlHWRZwv4V3cxvL45d0+SyUfZP8k9MVM9OQYpfxNTc0ygJg/H6fl0KHzpBxPIbFO5s1rxb1/s11TM2jQ0VsYn9ypUVNQP5pKVj8myBOxK6vUExQ8co7ce+adOV/PqPSDyfCPcUQfMp+5tiGCtIip4kkBo9IqOXbYI2AEqi/ZbinMrMH7daP3hjsCfeMcZH399TfuIk5iFpysY93IoE2FfPpLDCxnyFAyzwgBYqyGdWrVTjsNBXY8IUqhcwfaeA1ODkifA6GsqDtFBGsWwxDE9+GdprDufv99UW9BHsaL9mQSAgmBhMCzRiD+fpX87ltH558yLgwfXM2d8SgZ/1kX4Bnyt6Kjt9dURumgiQFVxi9C3//qzlfdWz96y23Hsw1jHzO6wchc9szG1eZT+LSvhn5owlfEPkcMDiTmaRwsgO58mNEUPkvFMByEg2djdAIJHIE7A+CQO4zpCTfLbYZqQTkuv3Xrlvviyy/ckSNHJUJtFb6X+EftTIXj/JCSsUjPhEBCICGQEEgIJAQSAs8vAn78xDGWrvFwYzfHUlXYxA9VmLAtXLjIbdq4ye3avdu14v6ws+e63dfffCMbvr//9ntZNyRAtimKLGNjfGO/ZH8xEWB74h/biLWTseZ2ddCa0Yq13Fas2/IamNiQh/GL/ce2MxZNyIU6Iz+bk2gKUbBZEVXnSrBk0w9oshsUdfyHDh7CxsFLcjDGosRPSzn2m077aHNrCuK5/tje3o6DQvMK7rBjnq0ODatSZbN5JdOxPiGLN/UlnyKBHQtGOKzBTX1Bi1MsVT3FVKV8WHEmsKt+nO0MplR9xYoV7vXXX3f99/tl8p0J7KzsT59uqbyoX/nXn43rHu6E+wHS8wsUdOBuuHpImrdu3YqTdo1iV1WP4IH/LJs10vLpTSDEL+4QBfngUu0PViPqcTlnBxZxFuDlktIgkEKbe1i4v9d/z13EScEbOHHXfeaMuwgBJPXs9ly77vr6+vz9dziBh9N3FALweDw/xLKLmfyRmNpROjjCH4shbrHwZ1RjLyqfBX/oGETYCX8OHBgmT9h5qpHqHKkaUAV12HWBxSt2IktwRH/J0mVuCU7Ssd0shD8vKJ0FASYFG4gqebe0mDkVrqKqmCbctuhjdWa4kpY40sQtzvw0ZHJ/mU5Z/nGAZEhyGmUgI2C5tewjuMtw0B05isuB//AH95//+Se04YvSHlgo0vDJNituz817R7yT9VkjwHeMxp60s902Q/DMU6IcPLfPx7uNdss2LANlH4d0NA+h/pT3gN6GYJ79aw3+8T0SWqFIPwmBhEBC4NkiYP3ReFKRCQP6L/ZgFk/8xhN5BtL4rpqlHVfueen6amzY2Ibx5vs/3+O286Q1NAXQcGMOx56iCpMe8ceD7oI0/MdC/Cvsx75juWzFORa0xENGKzxPG否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否sWLjAQeUCowAAQABJREFUbdiwAapAN6PMr7hly5aJBLy6Vk9iSTmlfEQR/wiWAmZs5RlDoB5ZnuyUVqDxFsVba8mwl8oBg6zuNJkQF04rW1QDSsR4xKKgB84jFkiLLEGnsNWtL6ekHfCny3jaM84d2Zrbwu3pk7RgPHVXtve3h4V7dyhnKBcJyBN/8GOZKXRRHwppEUNOhHIXt9YZabiT+/y58+67776TU3XdaAfHjx9zvb16itSSj3Mb6kXSspIrRUxncdPzGSFgbcJA925pEmwD8PfNNdf+NT+8t9I+lO3z23A3J+6n4z06iFTt1QWQNd+dkAS+pffv90Od7iV3BcL+ftjNFL5j5pueCYGEwLNCIOuL2Q/7t9Re1nEnKm/5uKkT4dQiIH04qkj6dfbx8FA/7eOtujk+4sk6Cu否eDfpT997z/3jb/7RbcR4jqe7aOTkNHnYmEQY0x/MxZ+JCSke9pHxbqPN+WuoxbEcxb55u9EYf3Pn6eg2mjjM6C3M3DHN09sD1xJs7Ttn+HlIQmIhLnw4zhVYWQYfQJw5n1i0cLF79+13Xdu8否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否RATaJUpgo5CWP7xiHUvhHWHDh3CHXV73SfYuXH02FEInXvBnwtJj/Fb5Wpxtw3Vwj6yDOVLFDKWD0juSkGA75e1+3yempqa3MaNm/Ae73IrV6yUwQlpeGBSVGH694Qn6+LTdWwfw8PD7s6du6IydWj4oaTBtmkf33xayZ0QSAhMPQL8qsR/VNnNb0L4yEx9llKKT4AA605NZjMfPzwITlqo7aC6ug6ntma7HTu2ud/85tfu7bffdrMxPnuEPrsKMwbeWZcZfsT5Z30304Gb4y76+m8ArFNstLz8dg1jc9EwvjF8MjvcaMLxd5a3Etg8VW5Z4sk3CqGO5oiq5lbHq5iaA2pOzqtFFc+GjRtcV2cnNFfUuxFslOJmQFOBM56ckfezKcV4Uk80CYGEQEIgIZAQSAgkBBICCYFKQSAbGctyJp3ecKzdAe1SVMU4FxpJbA2L8wsZpUe0Fmfyn37U7udbTHtoaAhX0wxj094ZdwZ/169f95v2kCtecyQFmWGjfSkf8mzFDfNO5xowR6XAbjkEdqoSMwPewzLqskVGPfm1M1kck8Buwkhqy9HpszIzH2NtC97aaHRnLCXvK1eulMWD7rPnoDbyjAjsSFPPeycw2eYCw6gtzBJ4xk/mP5TPvyiWZLbowawWBsYqEGmnoIcqFAehQ/fOnTuu+2y3YMBLIpfhJeNpHb5slI7LPSm5N4jcmQS97QW0fNgzzk85P1vysHB7SkcricDH0g5FUovyLwoUFoGUrgJH5jSBrURgZ0Pphp2y83FyUZWUDC0AT1k4jfCO8WAeFSNd5CmRHeGp0VFqssaPPJFINfIku+2xeIclPLirRVDHnRqXcd/YSahypYDuyOGj0I18Bm33lLt965ZX68pFMaTrIbIsM8F83eTbi2Qq/UwtAnEF+ZTtXbdnQYbY0LThQK3vHLcWmw62btsqu4uou1vY4YfVL4c1Ybf2xZN3VM9Fw0XEu3fvuv7+e9L26GftI7ULopFMQuAZIsD3WIy+j+HbEiUZv/+eykcpcEUxkrViEMhVLzthGffAn0G+C5c+V3aBwo+bcdgHr+xc4bahT9+zZ4/bDDXmnISSRu604y3l3hT109amSjYPzZCMsRA/a1tGbBk27nxqmNEaZUyRt9s3RGKjkJy0cqzJnaU9PddEUNfV1QnVNUsL否YZHopOnut43YLseIlBZyUqLnuek0HLgZXEyn5QZ9weRQ0I1a6mrgbzh3q3ffs2KTvLdQAaEHjSjmNw1iMx0vGepf8EWU6kCYGEQEIgIZAQSAgkBBICCYHnBAEbhZcaFdMv9re5D8fSzc3NOFk337XOa5UxODfKcZnLlnVtLK9P/lpKYwFnMZUuS99sng/yEHNlnqpqqtwAVOKfxcm6b7/91p09d85xY3ylmxgZK2XZPIPgsQeZZZ4zpxHXbc2HsK5V7vbmRsbYWJ2F2ackRiZKxUecfhy3UuwVKLCrdMhyVecrO+dbxmllw9IJGtpsXBC/fPkK9xIWRfbv3+dOQRAyDKFWLU6YyZFWcHki9mVSnQzvrLEXc8tKpWHFtOhAPBEX7G9BuHMXwp/jJ07IXX4U1G3YsNG98spmt3Pnbrd69epwpLUWd/5VcZEBfzTSGVmCmtyov6QvaxgWAPYWe2QrOFlnOBqvsokUBkRsEWCJFdKUcmm8kNmw4BZZEI04s7y+zFHRxQomrBtbNGPywlF+SEGLClWqeDIOHx4e56ag9fbtPuzS6EY73e8++fRTLAIdcL3Xe0X4+nCIHwJdMGL9Mo0R/PEzYmmNWg+Incw0ICD1Xj5dqbusgSgh6pWG9dnWNt+twru6Bqp9eXJ2BPcacdGXi4q6iMgWhTMAaEtsE1xQfPRoGMK6QZzGvOFu3LyJexAz1aloLr5BShLpJyGQEJhsBOydt2+DuUdJx0jk6d//UchTUAUioHWIX/1fkEP25eybqcaYqhR3vrrT/erXv3Y/evNHrnXuPOmWGYEn6+Jmozz55VdfL0pi909q/oxifOyxyCIOJLXxxGj8+a3hOGRg4AGEdNchrDrrjkJl96lTJ2WTye7duyGsgwrJtlZsDNM7lKNkJmh9ggKVSamIg/fQ8rPkQEHqkfMIQ5/M8J3lL8q+BLt9f/GLX6A+57rf/vbfRPsBN1zV4GThCNTljOC0ZGysLmO/ZE8IJAQSAgmBhEBCICGQEEgIPK8IcGwdxt3eYlNdHRvnRshwcizdiE3rS5Yuce24M2327NkZPCIs4kY6NXzKhsmM4qlshblQjjoDwLotA/Hnl8plQ/yBgz+4z7/43J05fVquMdJEdf4gxE+Vi+mMVIgAy8s5EO8N5EEfnqyrq9ereajtjXNBho/X5LiPN9qU0VWgwG7Kyj7lCVkHwIStDVH94zqcUvn5z34G9ZH97vCRo+7SpUsyIZcFbkSyk2o2Nc8WLaa8CCUTzDdyK5uVV9360oiKxGGctsOCAS+/7Ovrczdv3oIQr89dvXoVOwLOiRCzEyr25rXOdctw+o6d4Xz8zcJLKad2crlg+nwxaSxtAmjpZ10xaYCiJ5JHeJdp4V++NFlsBKoxxiGuBUzd08qgWdFyMXXioFiwlbBD1/LSTwRw+JConwpAhQZA6M5rv3se7ChYuQ1Vrdd5Tx0WvLhTgyfpjuDy0gMHDspudao1pKnGDm+21UL8bPlOSNLPDETA3ql81mdhoNKCUxer16x2nV2d+EjO1QVfCOu4q6WaC7v+3cjanX446e6D8Jd93PXrPW5wcDCwL5deIEiWhEBCYMoRCF9E/dhMefopwadHgAfiZFTDb3qoSPihg7YxBDdZ8F66zs7lOFm3zb2Pk3U7oa6cF6jbiSzmoGC4M2pbUEr9jRJ9+mKMGdPKQkKeqjtz5gw2F30rYxaerDuPu5SpHYCaGxh25ky3e+2117BRbIN8v7Lv1ZhJVQABR3DE1WrEnswav7NOBJON2HG6c+dOjOVG5O/777/HncPdbmjwvh8jVkBRUhYSAgmBhEBCICGQEEgIJAQSAtOAwNPMUhrq62WO1NXZhTXqDlFJL1kHMxmd8/SXn1g8Df9SMMQjfVurFt74kcMSOGRRBU1XTPbevbu4t+64++6777Dedhnh8YmzycpRqVxOrR/ndHOxBsmDP5QZUE5Aw7Xt581UiMDOgI2bo/kR8th/YlVg6wz+PXpiZuEIpun88xxyTvENufaBcYmsTFykpkrIDz74hex8HRgcchcuXJT4cpcbTzmBxhazRdiCUDbGQn4+I1P8YL6snIZptnjCfCOv/PFGaI0Qfgzjoj0XU3jq5jAEQty1QH3AxGUn7rrjosrmzZvlFE/D7AZ0Rpoi4wr/CAviQ09NU9MVP8sAaVEf5CH/LPMWbnkj4zIGHCTE+BqpRWVgPnYWhqWWgsAoA3a8N6SbhcUYsu0YBoHUWxSTTDjHnruGKpOQpizcUKACt+7CQF7ko4LISMp4Uu/xXdwvduLESbf/u2/dp5995o4fP+5uQHhH/4EHA2BGQR3LgrTAU+8oY7k9Nr7AWlb6RoWOrPn8J/f0IWD1H3JQpp5a5rbIPZSbN7/sFuMjqcJa7dHkvZK2pO+23mGIxgJevBuRAr2+WzfRx53D/TpX3eBQEtgFvJMlIfDMEfDfFP8Rsm8Yky3oo2N3mX7gmWc1JfDECGQjBrXxM0yb1G1Uj/TnfXX8fg9jA1Vzc5N748033K/+4VcYc70K1SJtBcI6y0g8DtEULCR7mr8+9dfaVtbeNDNZljJbnlOeUsOL6S1vvKvhL3/5q/sf/+P/lQ1gLD21BFCQR3MMl69znHkL3yHeZ7dp00YR5KlwEtTFrDXJ8GsEVtIQ8ISWMvG9dxxqKbIsYkKgjifFiUrlk3XLyTnvDp7bOk/uyV7YsdD94T9+5/6/f/2f7jw2yqkmhCfMbiJPCCQEEgIJgYRAQiAhkBBICLxoCHBw7ScIvDONVzitWbPGtUElZna3t43RQSvk2eid0elZPMcwGiHwqNIvcyOW9+cswOz65Jo219Z0c57G4WGLc+fOY53tOjT3DYf5nM2TArMKslipmCXFKsCN8jFUy8aZjmHAk42LFy9yS5ctcU2YxxpkcTl1bZNxwcOvswuhyGbiVJly5ZoKEdhVLkCTmjM0OGlyviXaCzansdHNhpBq9+7XsCP4tEjFe/GyjeAOuxEKQxDPT8UlO9ZQJzVvE2BmzT3rWkoxy6iEzt5GkLJ8FNrxj/eN8OXqwembc+fPOeJAYdFp4NLe0eEW4I8Cg7k4+sq7Vag/eBbu/PNnw/AOgjv+yFNwwlNAj7LkSSIfUPrsmdDCnkYkwcITPkzCAp74aZENj4wB8yUmBJmH0ViAPfPhRpc9tW8iEjxFx1ZU66qxUEchnvWIXNzhgtYdnKi7DfwvXLggAtRDBw+5H7Ar+9vvv5N6oJpW6wSlPZqwD8kF/1CILA/J9nwh0IrF3K04ibELwvSlPIWBU8I0fGf4x4VPefv4vvgXi22OYfTnO30SwuCLaGci/H2+4EmlSQgkBBIC04+AHx5wtBCPb6R/xriSdyTXQ33IsmXL3auv7nDv//zn7o033pCditaXT38hSueA+aPh96UfmimuXLki45b79+9jknrO/e1vH8u4kVoCShmesvv444/lW0XVmZs2bZLxZCnaSvTT+YDWq3xrkUn6CSr4ITwU2DWgfhsxod28ZbMbGHrg+u/3u2++2Yu7h0+LZovCXbeVWNKUp4RAQiAhkBBICCQEEgIJgYTAs0Fg/Kuqmv4crNnzSphNGze5hQsXYuO6roMx1E9Ps否1UUMOOteJOXGkz3G/hfGKLV4f9Rhrb3fv3nXXoFWEGjXOXzgv12xZXHKaeJ6M29Q8Q+kN1ChZzgObmppcZ2eXXKXFq3lsbmhkMiuSQudKnnMafSU/K0RgF6rkmWNVos6fKM0gnA2xNO/mH9pAXCTYuWASwnxcktRQiOIz1YFdsK9CFREFJt9BSELp+ONhi6Un2WxyHpKvIIstCsUl1aJFYIBI6PjDQDwtNJQUfg9wius67h95cP+BLLx89dVXrmHObBxBXuxWLF/pXn7pJTl5t2b1GteKu0hq5RiwcSJr2n2nJskgHY8z/dWqXR4rRvIkWGouMtoMYN7nRhOlknOLU/0yoszTbBKWEdCmLv01HJQRfsWDQkj66LFm2tlJm9GwjCdPZ+IMnMTlohbLU101S0455cvGe8T6+m65g4cOuSNQybp37z538eIFd/XyFXfzxk3Xj7tPqpCsxSM/QS5LXrJh4XSYoMZnHpsasrx51CVO+qkABHzVVGkjQob43pQ2rONW6IrmnZMbMVDhPXb2/si9dQhnXLKSy3ehh43s7RQe2wXV4FJF2dWr10RIn7WV0mkm34RAQmCyECh8s1NfPFm4Tj2f+HsbUmf1ssP1O5gotLH+mTTcOMGxEndcPoLAbi7uqHv33XfcL3/5S7dt6za5A4ACvbyxPtrSNDfpMj+LpW0sbmlGYx+WOEx4SKY1fhaW2bRQxr/wyRN1FL79+c9/hprl6zphvXbNUXhXzlAdO1XF3MQ9qhwrUe06N4CNz2RjmfHRj5dKy8vdsnkTfPwYVMJBHr+/ZucElXjLEJA0EM421De4V3fsdPPmtrmVKzrdb3/3b+4ANmT13+sXzR7kx3g0xkcc/LHENXvBO1kSAgmBhEBCICGQEEgIJAQSAs8DAmEZzBdGVrRs7MvxtPendg5eG9DZ2Rk0kjDI5jrkw7hGL+NyIfAM8MjSskF2FpbZjIP5KK2O1zWM5y9kBF9T5W5Cc8jevXvdF59/4S5dvCTaNhjKzXmWN7or3Rg2MTJhamSBKERjY5NbtnQ55AKdrqU5m8OVLSsgI2rlVzkrF5kKEdiNBlBcXaPRzZQwe/lQLkyquchNw8Y1f36b271rlyw0cJJNFZF9fbpDmLH46s8ENOxdQpHE6BOvBwKs9AwQAQFhEId2Z7IQBCKe5hp4NOgGcOruBhZVLly8KBjNhkrMJUuWuotY8O/uPoP7/9bh0s+losO2EScVuTBBtZk8eccjy7W4b0vZl0JO/SRflmlD2DLqhWLaURgPewrrJ/oxtvlI9CdXzQZwMkJ5anrEURdz6Kk0gQ6xGc52ZAtuijvjKDOG0c6FrHsQwvX334Ng9L67jN3pV69ecd9AUHcQizgHDx7Eabs7qAPefcI77zxfTUAEMcYTQWKsc7Qn228yzw8CrFf+Nbc0u65VXW7turXYVbRAVIo否53wgiQV2EVl9nGkKfj2TNpe9Gtnurvlzsr4DrsoZrImBBICCYGEwNMi4Ptb+fR7OwcI/J5zbEU1IvMgrOMp6fff3+PeeuvHonLcxg5Pm+yzjIfsy7iI4xZqYhgYGJDycDPXhx9+6D766CPx49iE5cyPUeK8MZwn83jSbt++fW7dunUisKMAk6cOafywKY5WUXarVo4FxXCQysGaf2IkiBBVk8PNv9RMsemljaCBRguctqOA8ujR4zideDXgRj7ZGC5LQfinn4RAQiAhkBBICCQEEgIJgYTAi4RAbjhMzXhLFi+R65vmzGkI84Vs/OznEByTPzMjA36Z61i61Dby9Vdfy981bFyM50Gx/ZllaRIZG3R8YkaHX8oJ+NC1bFmTxFp/V1eXqCYtOGEHQjkoYvVmzBg/MvQ2ksi7Iq0VIrAzuGJEY3slYTd6vmRRwWfXJvz00z/GZUPzL7I0PgYywmM3u3G2W9HZKafsrly57I6fOOZVRQ7JAoRSCQtaLZrYDUFxVMiPlV87CeTQgGD+ECh5Jhbe7W1Z7iWApN6CkP5+CJguXXKD2CF94sQJOe0zFxdNtkJFJo8lL1+2zHWu7HLr1q8T1U5NeJmtI8sYZzZCb8I42nnPFjPENCVdnzZP16HmpMay2CTVvEVZjIO9nZwja46YHCQfnkQ4Go0EZvHVhoU3CVehHaMRWn1SsKJ2+43LP4Q7wy5B+HnqxCl39NhRCOsuue5zZ0UwfAm43sCJuts3+xzvsTNj+eGddTR2Ekv8PZHVkT29d3gYTsEjWSoHAatIX7905uux4MMINQDt0NlNYTijSPuSp7ZDYQc3VbBS3MsFYt6ZwzOfVPN7G5sQeq72yCJh5YCQcpIQSAgkBCofAZmy8FOMjjb+rtJLBhIMkk6cHmpMJbGoWUcf3LG0w+3Z84F7f8/7uB/4ZdfY1CgnzYw+e0pv7tkiBRuXZATBZuOO+ISY5IkUyibQZhalKB2sYcZX2GD3B8cpP/zwgztw4ICoVz569ChUyZ8SAVz+u5WlU9o2PDwscXlCr6Fhtoy9qcVhekxAS5IvdCmE5mfVwG+vfH89gPQ3LNlOKLTjgUltJ4/lrr61a9dAKPmP2Pi2DCcT/yoCy/PnL2Aj1wNJl6p9iKPcTUxunqG0O1DEbW56cEqpJgQSAgmBhEBCICGQEEgIJAQmBwGOr2U8jSfHwBz62libKch4G09q5Zjb0oJNjvPkYIjGAT2Iw3g80oDGuGHkrAvO6hV+mZKN7oMn99aJkXwgWPfieVrx5Jo0x+ojyFMV7ul+iOukrrvTZ05jbfdyuLc74zhzbIaGFJPZpgU/VZjQEOMRHhSAmQPNe7zDbvHihW5O4xypA/rbfCWASM/IeBTFh2kJ+yi8Eq0VIrAzaGIIze/5e3IBIu4EHBYhuBOWJ1RmzZrlVnZ2ul27d+GyyKuubla9O3b8RKbeR1YvsqY1UxpaqEXLsD1DQDkLXjspM8IBGndG38PO6PsQ2F28fFnCuCu8DvfYUa3RaggTNm7Y4C5eugDBXaecvuP9gNRzy062rq7O1dbUilqoGggcbEc5X24mw2zpj+bHTrRZx6m+PgyeUhNZdcTBkT0iiKwRQdRZsNNnCH4CrX4EpAOSDFLjFbtpZNW74zCebuPC3BDupePddFyU4j0t3FF+FW3q6OEj7sihI27f/m9xcvGCu957XXBlBygfKWSgBoyJO9OQ3ep8wt8nF54hiwhPZoYjYI1JG2BBYfiRXLh4MRY0d7pt27bjPsl2aR8kYjvRP22ybENsINVyTyLaDfSpsm3VPKqRO3QoFL5z9460NYsrcQpSTI6EQEIgIZAQKEIg/ghHH2BaQxAcIUg8cXstxjv10EDQhs1NvKvuN7/5FZ5vyoST409+5208VJRm4Fwc8qx87HNE/swbxzI9PT24g+0bUYH5+eefi8CO2gJs7PKkeSHfq1evilaBVatWyUm76RPYlc99qMtyJKxj3wDk8w27tgV8mymxQ/0OP4IaVIwNGzEWXrduPVTJNGK+UYsJ7xz39dffQGPFWWhe6JcU/FJFudSSf0IgIZAQSAgkBBICCYGEQELguUHA5lClx9xVskbfhmuYli9fjithWrGurFryCgCw8XiB51gOP4CPyGIf2jO32rjuK+u0mMfcuXPXXcKaODcwclMj131pJLzEmp4EzrQfD4IcrEHeuabfhit6FizokPkMyzqCsnKtWg6YPFU9VC4oFSawI1CsETP26pi7Ep+WX59XcyKrlnt7V+IFCJaE/pwYiz8Wt/ny0TQ3Nbsd23a4kSEIXQYfunO4045qDDnxZiMUAQojMyL/48lTT0y6she+pcBSxoJqVp8yHUsWh+WjYRlNuq4+Tk8iDgxCxeMDWdQ5fvy4a8MpIN6x1dTcJKfv5uPFXopdxW24mHLhokVyXwsl8sQvVJZnKJjSPzKEXEHWeot3kkdkOavmWuuFZVGeJvqiy1JhDVoZzVfC8SP54X2HgTqXTOTk/TR9t/vcVag6uoR76LggReHv9d4e3AvYI3qNe65dl86dghMuhOWN3NdnbcoCkQ/mj3nwpZLcCC5Gk54zDgFr5tZ3WH3Ke4HS0J/9zsIFC9z27dvdli2vyLvFxV2l1VZpbVee9oMn6R6ODLkHEBpTHeadu7fDYIK8LZ0ZB1zKcEIgIZAQeEYIsF/U/lV7Vuun+dGlD8cG3BRBu1CA2EYQMl4Qf4Tgv6i3Rj+8dm2Xe+ftt917773nXsI9wI0y/mE/7sehiF否mErOmJckijB7ejILzsWakJPjOqq+/Nvf/ua++OILueeZk1KqVOZ4eKLGvn1FhZko4wnEz8EqnLQ9RExLgC13G4fIJOAf5g60+TuzKbxduHCRe/31N1wLdgq3ty/ASbv9IrjswYX1PBXPbz5jEhuys7YFazIJgYRAQiAhkBBICCQEEgIJgecCAR3nalFsXSqbG6g/T3QtX74Md0GvwCm7eWGTYxhyR4N0+skQnZO3yL88WIGLTqsYjcT44Rp/4CV+9ISPeDqotse1Rl9/5b7dv1+u07J829PStHnkuLJjkabhyXKzaLa6KDjAbfM9rity/X4+1vmpIcbqi2owrYwF2fYHbERNpudt4cbb3JX6rECBXQyVVVnsV9n2J6t4UmMajIdIg+HCWRRXXYv77Do6cMpuN168XpywO4oL4g9iwXsQC916CsoaZ2iZ4+4QphG/UcCx8vCZ72DGs1DA4j/EjoLrOA7Mu/+O41QiX2guTDSgg12C00H8W79uA1QBLcZJvDVuERYsqEaTLztP3s3CXw3iVENqb8LRahwz5qKY9IkmbPPH7Xw/CUDNVqqAWh7tQIzOnlldqI92TZlvoY2LKAEbNJrHWKjiTgLxRzsaHBpy96EylOW/eOmi3M9CweXJk6fcedz51wuBHQW/FOix06Oawsf4q61iefXDIP7kyUY5pmGudaFvXORj8ksElYGA1j3bGtVaso+hatlVXV1Qn7YZl7uukBOqeuIS4SD3MeRpbZRtgouEtXifBoceybvZ3X1GBOq2+4flNfrKKHvKRUIgIZAQmB4EbBxkqXPcwH6UX1oZHWh3K8GktRFD6EO1I5YIEo7xzKz6WpyYr3VLFi52b//4bffP//LPbtvWbTI24lgg1jRg6ZZ9MiNTYjAGwdiEG7P4reD45Y9//KP713/9V/l+6PglGg9NIE8c+zVhDDh79hzs2KzwKdEY5WT1SBMIFs4v6EBLgapL2dgnY79HclffSmihaGttw9h4qaiQB+pu/74B7Na9rcJg0PLe2dC+xkg/BScEEgIJgYRAQiAhkBBICCQEZhoC否UqlW/Ox1pamkUTx/oN6+XQB9eZaTjKjk1YE+UaGg0fozEXojI/XHPGf7/0LETZuniVXGHUfa7bffb3v2Pj3V53E1qsShnmfyYanedmJeZ8hHdwd3Z1uuUrlouWECuXzovNpc8Y9scA0YR2DI3DCmNVnquCZ6fWsgxOc1caiJqvglz6r否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否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否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否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否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否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否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否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否jxZIlSwpU3y6W+jY2sKEqnns4r2nRnBOfCNhPHD/Rpr6f8giTSiOs5TtCBIbYgxcSqHUuEqHK6PYReMebtkCbWKk5FKd/n9Di9xKdZjHBo+6rGzNmrNqLxvrjxlhaCDuMKyrxMm2S4oebFtjhLyb+0Jv6diPG1AoyUnWHxTsCc4LzmurTVZ3WHH4DwaELY8gH93Ht2b23WK8TvYy7v968yU5dX71y1e4wNyGfoMR5K2jGqaASD8rHaApS8pUfKPqDDwzu/nU/Pb2crfR否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否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否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否IU+VOnDgpdXd3m7nbNWseNM3VezQOskln5v4RQitf2oDRbmUgtb35L9nilK9583u9yvVoI8HABGBtjWeOU2Td17QVKF5AH2MMfZf34yeOp+063Mj9NuvWvWkC4tOnz9jBrMvaAHStuizEEn5rfsJrdJB5zoscm1jkRAyX32CU0cMLriaekLKs8MaTOCxfGON1iGHGzBmq2660WPezLtDdpksWL9Ehg5m22Yf2At+tjFesm9Fk4CAE/EZ4ehneCx0Y65z1IqcmkJ/Z468tfxvaa2Z6Xc/RikjaBlcV7B7/dYqCrhJLSyKKwAZ6qjwDUwE4AG9F4gDSNIMGT6vwHgGKaSKzuX9F/ChZ+0Fox0EW5iquX8DiyKVL0mTRXIjpzEOHONSyz7RaOHgRpqW5Bw+zmqbVIiFa8IsywmevO556lxB9hNqekaW6NRgicj03kVsVDZyxFqnwF+Ul7pA0bE6++64JvWbKhDDz7G+0Wf2I5txZMjdMmx2ptut4lHcW9lsmTpDnB1FRz9UHvgAiv0wr7w30RnxDaJTf1z0xHjM2Mhcg1MACwIMPPGjajcyNZ//6s5vpFnL6FvTGIVInsPPb4cCN4UA9Dnv+0RWqvqFg7uKdPXtWelz3paFtf4+EdRzmwNkhLvUp2jX9WN3A1gkMVDZW2FxiHdBgIz/mlkuX1GdGMoLR1w+lv8oM5l///FezmIHwHpxhgSrGCMtUPxWdEXAzP509VoLmchKINjGmMGfrQACadU8+9bTGmW7xp0gouB5pq/GLSDDVroxqZmaAUn+4HnNMjeYa+CL3a4D6GqO8ZQV2MQleY/7epOjpRDdvox5OTG8c7jJlYm3zAMXgVk8Q8L4xJR+TXIg6ViePY0KKcgZk6xrzRS+CnLNnL9mJ8E8+/UQbT3fbidIZd02vTGPG4tbxQmRgJAfHE3n2eGbQoKXC0QOQNWwjVAuQtkFBUSsATsiEsxz4KRM0MjlAB/1kXV7tGw0aS/8SmqBO9cEHBPXCxdl7Edb97RUz1YP2JCeD/UPYN1zKole5VHVahXQ8TRygfXobVS+tmMjSQg1KlT5+7Hh9qE61zepY9DWh6Lx2ONDhQIcD4gBjhj409Y8N9cOHj9gG8IfShuYE9yx9pKJpx8YoY0013AySd5He5r+Mg7Gsx4fWIPG3SsaGO1r7L7/8sm3GYjqSE/Vsxg3EQSOmMX/66bBpVR07dtRw+Ie7rz+a100DwT9Q2JJnzKsI0fjjsAZP7vDDhBaCPDRxuNcOTUrMZ/KHv7+O9R0bnwhAwYsWFZuHCAcxy8m9XvA5XD0vRcgwe0ZDzGTZaw6L9R+bKNbgbTOG+tXGq否qfDx06JCZkP12yzemWff5ps12B1GsUcv2XdbTMONCv8mpeFJU7OhRo81sW3dXt91PN199jE1aNC3vk9mi0AAhCUIn+MAmLbgMn95Zn+BHaCKPwZyQQOobaWVwZyQmXr+T5hhmVknPOARvm6rPykHfszUvbwFgFZFrA0Is/Be7h2yKtK4Qyk2RdhX36i1ceL/RvlAaqrO0IUd7DxfoECo2Ifd1lwE6lOVGWYHMWTvVFNEDyjaBBqdKbuUDjR1qQ3NFm4TqdhaO8ADtIAQKHBr45ptv06f6Tvryy6/M9F+0OzPbqLQlPqMEYjIt9qwLRJY3wAUxZN1MjOJytPFP0fYKWAalHWEdhc1ReDpSd9ggHL5DWnOYSZwy9Q7V8QQzG3z3TN1DqHmMMWvWrNnyz063a1wsx6tWDKC5eIby5Lxz9hmcdqwoB6zC3NMImSPtEW0gCpmbRAnS3l+izQkzq9qnuQ4xAylDA7sybS3T57JG36mKUfHAPZQfn/c4uOoccfO22pfQXBjtKXBAA/0M82pdXa65hib6Af39qLH9qNYHhzXPIxxnrXD61Bnrf2cQjMuUpgnBAxlPFQAt3cpJINXS5coKGhvbjqegfUT4Ja03TumPOedHCRePSQPwjMaCPdKc565YtNvnaH6fqoNDlFNJnRf0DxWSfLy9FbRFBWRaKjpJPABXt+OcyPD+YoIM1glo4GPlBSHeuPFjbTzftm27zPwdblh7mdBdKBB8dFyHA9eTA7ThaMfR56r8i/bI9SKsb57UAbWHpNk1Q5rank5tNjdbwFnnWDvWS+C29h19S+GseYAbrX4xSgL2s2fP2JUxHK774P0P7DAgVpFGa03Eup7vlB60VUT+yjyZl8Y7ld20nMUz7onlzrqnn3oyrX38CbvDjrVOjPw+wMf4lZH8ylhzMxTnlhXYUTkxkNwMFTV0GstOFh0vsLaKa4YJ2FbPSB9pmt9bpelPWOBrB1vGR56tYEs44pthm99b4SjTtcZXtqeYAGLhWC1olRVxtvgVmnrBh1+hxOd/OdaIYUOHD/RxhZkGIqDEFpFtiwBOoByAD/MvdKk8uCZNmpgee/yxSmDH5GXO6KsXtRA1gi/1qhDN5fdk9hsTZxE0JG9RrsKbSxOY4ZzzjRA/sQn9emkmtYm+OroV9oiNuHgnF3fGlnjhGaBFWHNQtclQobPW4PTmdFWUZaB4LUCsHkZ5Wb/fvj395S9/SX/603+m3dpkiLtbYmMhsq+Ka8yI0M6zLQdgfM18sTxqDzOknEIaa6dKMfXDhgQfS636fVv8nYgOBzocuIU4wPxbby5xp8vOXTvTxo3rtckyRsKZF9JECexw1VgtP6NOjD1sD7dyMTIRR1pLkwFbpQCffaiVGbVCPMAw1g0Irf75n//ZhEv/43/8D5Vv44AEVmWW0IkpRO6XwKwW91qQh32Yx+RZJujTH5wquRJhfSauAIJ/BCBsoMxoAHL/DZv9p2Ria+dO3bOm+zHWr19vh6OqxH14wHevTvSjrYimyurVq1NXV5dp9CEgjNPGoPEpqaQ/yhXPPjK7WtG9ZlfTFz6jW2l8A5E1jW/AjNC9Q9QvbRMeY2aUzRU0nBAm7d69K7GRSzoTIggmcFpRhNNIyYFVnDPqapV2yHiaqHZ8Ijb6JGUPR1horz7//PNmImqOTGDOmH6X否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否G2ae3fs2GmaC1MlvPPNZMdAUht79LRpgnePavvbEM/0QZoqULNj/dIWx1Aj0K5AU4yDDWx0IWyJ8bO/uIFHuw4NIIRd3NU1U6apuPsoXBRlYNNbcDKwDO1Z8pO6g+7p06frHo7HTPuODTMEIjt27JBw5CsT4O2TsBbzW6zdSA+vHnzwwfTMM/pYloCDPzSrENyU+HtSWlVsz6hrHFJZFaCNNZFRvpbcrupLi9VLdtdaNt否tP5i5tHe1V1C69evS5i/PHjgkAnr0Miw9iME9J0SZ3QIC2uIuMYM6Af6su7K9k84G6v0ZjQ0yjgEvWxYPfzww2nFyhXpgQce0L0u98ks5jRteE80k3C+sqWw/gdf2dBi8wrcho8PAq0bbxutcJk1OndOZlqluYIpfO6se1/meLnH+rSsbeDQGquEdVRg5qU/9GseRfCfeP2ZoIfEikPgR3/HVCcaoghzurq6Za7NzYZjjcD31sJcJ8myJp36jPMg8vHsrJwKsuwsU8KBJVM5hVFe/pkjIsN5tIdjJp6NPjaqwhoC/P9ZAoBvvnFzqzy3fLcl7d+7Px2XSTyEdbQ1HJuAoLZsq8wtqvop67AKvMGeiqdNdKChCi9MCKnOyz/jdcApbKrMXd6/YL7u8Xo4zZs/L92nwwh3SjtyokxgUqdTpC01Ue3xNtW710Ek9qdxXnlEVUUs4dBFfeAnLc5oda+9t/oJWI+rMdc+ENVIwsezgsn5NeLPLSgncBC9VIkEHXERlt8tIsIakTa8VeA5tCKjSIu3GQ7wNk3OYSNBxhOv9IMqD0PCjzvqH1e2j0jnwmh/y1BV/tBhSS2vAiYSG9IMU+VBYBMtBMnRByeoPY2cJlOr4yemrtlz0lJtFiOYO6JvqmM63LJn3960T9rWO7Zu07fuDpmZ3qs1RZ4PHE3jL7Tpz8aJhgpsBGt8Y0yxQjUE0/85YLNj504bEz6SIH+VBHYI7Rifu7q7bZ52s3Gep89O3rcqDgm3Y/ffml1AwxsPKWlQaG4MGTo/AhNJamGF91/iOCzx4ou/SV1d3XaQd9OmTemjjz5Ku3RXKUJI+jz52TwKXbnclp+zrvrNWfabiw3M67zc8hyIdk0DMkGaOEI7Ixyt+jgowvfCkiVL0wsvvJAeWPNgGi+rX/QUYDkMQps1p0DzWRv2eHodcPwHH4chSTx6jGvnYe3iDX1DbNiwXuufvRLgIYQSABkULmiN/lBE3XxeygZLgm8UtigyczbfKvCC8vK98tvf/ib99qXfpiVLF0tDXpp1oBDvK5fHCQvXD1n4WOeZWR1UwLUn6sZCKhxWiwoibekinLCIi7B4L+Gb/YOBLfNqxjc83+sv4OFJX4eq68qBaPTXNdNbKjP/jBGfe2N1mzgzY6mP3wk63YXQLQR2/WNgbBRoOJTgh5Pe3367JW2ctsFM/C3Xycl7Zt1jqBhbKxOYbWjpX57DBao/A/6否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否TVedA+29icR3ZxteyKPC4LIC998H4HE3/61Gvzfja0EJwWQYri9JGzrwHWWM15syQmcdpMtFqfVNzwlnNHT8ePqSxbUf66osvNa59LXPTX9sVHxz2QBjNd3OYG+5BZ6YLGtwxpviYwbvlLRylC0HYRZmROygz14dlVQWNcLTld+/ak06fPJ2Wy4Tx9OnT0sTbZf5X/QseWR6MY3Lt6KgKW2bYbz/Uqh/St3VlBmW+fEV3b4qJ46X9PLer28bmVStX6X18unj5Yvrggw9NwHFRPLpifApuN2baOrQRpvPW4cBgOEDf4PDa4489nn7z4m/TiuUrfP0AMusuWj/gscHA1zb0JVY8BqDG6e2TeT+3VMEylx+VUP1jHcb5v//+7zoEtkkC6iNaRoRAXH1S/irNYIi/WdOITfAQbrGOu18HB19++Xfp+eefN/P9rH/gbcXjXM7MXWrDXLxbPSgkwnN0ywdpmuFqPC2TdALbcKAjsGvDmBsf3KpJtwobKKXtcPQV3tzlyLdM0yo+YMq4SBNh5XursMDBM+LxD8RFHqTpCwfxJXyZT4S3wkFcz/DKtGSJpsnPMEmWjr01HpJgeoELjxHYcZqkdr3RXEPhYzHHn32s60P/0pVLZrbq9ddfsxPfmObitCXOTq5olOeklpWMET87p5LfoDfHCbe5AjYnGdDD5F4ZVWPCmobGcN6Ia5moorJMY6SW6HJRKgwhfCsFbxkmlvTVSUWQGa4qdZbXKTDyyIWC/7AnNh6iPnhnw8acYI4dO5o+0qbWRt07sl4bL3tlzz8068pyhP+WXIhE4YfwtI+rNun5CJqgzY3Z0oBYqpPInDqmP6CZyqW95qx+YzHYBlEnuMOBDgc6HBAHbNzXNMG4z8b6/r0/SOvjy9Td1W13mKHVEHNDS4bFfNIyst0M2Ab4GgRDO5plCCUw8TgUB4/QuELIY6av8maaguulh7wVSyqP8/esTEkfPXrM0jNugw9cbEgjWDRNJgmIcMRdCwc/2ChHqMH8gQlBKIYGwt1MYBbKGAFFIdoS1AqmVVhbBDmiLHNjepFduRIqmA27TOAhOIf1BMASTgVhpmhU1jzAbNcenX7+6ssv0/vvf2BCpG+++dZMYlInqgATDlheURdCDNZrVTdVAXv1BCOcC2XfrOgiyqMdE0mMcA8HjnZ8+4TbrY+vWLFC5rUX2kG5latCuD3O05JE8Kw93BRorDaz8EuSE6NB+B1OGev/qLFjLD0C4R0S1m3QmnHDxre0ubzZNrLsrt3gK5BRLPxGe1kAAj0/DhHcJ6sC8+fNT11d3RI6L0zd3b4ZjMUB+jjtGGf0mK/4KXGTP+/eYAy+gjR6PJ61rIERSbjg/SvEiY44WgcHq1iLRT+L+53Onz+Xtm/bbhp1aNbt2rkrbZZmzL4mLUM2vHHDdf1Mufir6trZ4TTDiMIFLEHWB3M8ZmUnSth6zz1o+S5MC/SN1z2v2zTpFkkDBw3f6To40Oxss9MCMyI98ImaHOpvkS5Xq8cpqnwPmOZnD747agcr22hzwqvxXuYFvn7kVyepgesy1GE1eREWKdu91yl68/WHp83pI2c1ohwVNOi1OSyDFBBe25FUSWhn5ipYvet/8EFfuB7Pr8HUib2N5uAMVcfWVWB5KB/mSRxrijEa48ZLg4w9kBkyR7lAVxQ8Io3+Hw4cUF/fln7Yv0+ad9/r2/m4zfMZvT0g2fsQedOnFGxhJVRPP2UK60KMyRc1jvMdvnPnbvWxt9KJ4yfSJh064VDOMmkCLsr30EI/5cIEMmMU76NUFisXMbnQPKI/We4F65xIp8mrKQsfPKhmFu9WQB8rGNNGSYiHm6H1xnPPPWeHvTHLjYbxt1u2pPMXnQCyA543O6RKoqAh00hQx3U4UHIgmghhsVVG/+r否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否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否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否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否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否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否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否fRB1y+It9NPZzScSF79u0N29WW3+vs2s8//1zfHTukN4leiXhXaP+iP3vBvVDDvoKyrSrCvxYZyvm+wePIU6ueCs9s3BhWajf+LG2y4MxqAu+Atxb8i6EtrCm5uvxiONAtg50Jo11gVVfzdaGoKkvFgQ5wIB8QfZD0a3vZ24JphK8WTxSA4oRWCkN1+Gy2I5/wCQisNskykCdQf5dcqBo9elRoamoO8+fNk3JsZCxUsCiMoiEjRpW/Oa1lbHVXcaCvcaAQYIrXJL4T3oN5XQYOwugmyvXHR8iKFU+GF55/UQrh+YXgiecSc1FkYHGFIvC8I7yL589fCD/JdcnZMzLYScjyYO4w/aG6VhyoOFBxoNscYLAiMKj5wOZxllD+pOhCRVSAFTEGe/7n82G3DD/fffttGK5D4mfMmGGK5hJRG3cqvp4C5IoYYkohkrSBor1odtNhlONMJ1wU+yprPmL5w6h3u1DKOxHS6W03X20VImh9nD/XISqqr3ivexkXMzmPLJ0o3RidmoiiuyetXsaYo/nFFoukj3PyoTjbv39f+PDDD7XrcJt2ZH1li0WIx83QDbnPQVFCXuCL1cpSdHljefkF6YnQ+vg2qlhku5M38Mfn4Juqi9M6QiuPm5vnmcvI1atX2YKbGdNnaCfaCDvLTtq9oD2G7GfQH7+Ixt4LUg316HWlHE+lfrHtlOqNmNqqFiY+Fct51ABWROKr4dGP5wEVsgHGqhtXbspwdTC8996W8D9/+h8pZg/LheccneWzKrz0q1+HFXKVOGHCJLXnYClRcT8lLPrP1UmCFzn9gMQyYyypdudAMVm/qms9LzwN2uP/kjlFWrpxAjxeV6JitSlMCHRhpwguL/fs2R1ee+218OqrfwkHDx60BQPs1uC99vYElb+z3BcVs4dWjym2714GaVEkRjt2qlCvmTNnhk2b否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否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否hJRp4BzDNuq6qqJLE0a+wviMf6EDwTk1yMTT4HjhDW43DXHifrtmzZIpMpx2AdogE7wxRJkGiAe01UIjvyCo/6ISvIlmVC89bjx0/AxOk8WeBMiPPnz7qfYB2AJkW5CI0L8flj2fD8auoBtqOO+ESXU/RInuTGHAecI6/27SJ1Ds9lJdBx8T00BRNzfN+2bN7sNm3e5FavWu1GQCfm2b9q9tUmoyH7AE/P66Uv2Q5F3WFEt8oj9kfdElO/AYoTdv2mqCKhb04C0lV59NmusScNJtMavN0N35ug3tQaZusVIZ9dEINIC+SdChhXMGEFdVWlmzR5klsN05gPHt7HDqOH7uKl9AyvUihn5EIGqgZ45w02o+tNCbDiFKr6hcLsVcnkT/MO8+fPdx988IHbhfNilmCF0/Dh1YkyT2WLiiffOfpZRfXzQsOEgGSVIt8I/HnlSu5I+7yl2d3EZN2Vq5dxluPD3IGqDD3xMUogSiBKoNgloE0sr14ngM/0Afblz54+E7NG9+7elUkHmh8ibOGPO2ucFWuWd6685iQNnUxuoX0N8XSU2sKZLqHUBwqtCLQcDdaemSbHAcBWxjKRmKz0qbnTGhuJZGEGz165dPGSO3f+nDtz9oy7evWaLFK6ePGiDGZwlXjWyeRUIRNBHRKTxdD3z0ktoCBZPiRJO850wAI8lleUudlz5oi56YULF8qgD3fY0RQmByuMZRvk4bPVKxv4EIVRwpkJMtTM+JDnLG1eBAI0L1x94etzEqFJfJyl99XQHuWuYSmg1s0EmxtiiSAPvhv3sQP1MM4v/Oabb9yXX3wBM6k33bz589xOTOxs3rLJTZg0ERN02N3v5cc6T6f6h+ZDvoS3NJtEzgLsLwkc00tYSqeFpPABMhNKGul9YXpfNj4ZzWBSqDpI9xIr7a+Dz8PuW/B5CmY/nzx5orpPmI3nkUEWnJtDHgF9E2DEMXdPoNEpbRKC21DOLHu2dRMx+boZJuY2Y9X8VgwET502VUyaskw5UccqAWjhJVEf8WTZCG6rN2G4B2baMF2aUlBmLoY1E1zw0bhipKULwwomioFFJgFrK8N+sshIjOT0igTsHQ2RJS2LNlUA0TeYHvp8Gttpg7v8+bZp4qQJ7j1YmalAX/3Pf37taN7wAcxU9qZLyAjISchCpCzmQBx1rrv37krfUYaFPs3QK0vRfs5FfzkWu5dpUpCTjWKCWSzeKC/CcQHRWJMq1nGEoXbs8q9wS7CwgguJONF063a9e/S4SXASxN4lIdX3VyrH3pRI3+EyMZE/cRQS+LS2gwvuWGe447+2rtbNnDFTFhzt//139xhn1l29ehVnVp/Drrr6xHoQ01p6k7ki55XaouWahvYHn1FtsuIrZGH+JZP6Qt7tW4V1OanPWIA2EROeS5YsdZsxUVc3axbq8nzIdIaYydbJatR/CK0dC5/MJfq1BeCexgaB5pXKag+dQhqQvxuscZiJLtJHq2tFSt5rk2Vt0NvgM07YvXZxRQQDQwI5rehrsMRGNW1QTYdIm3DGvU5e1mgrifaU5oic0kwFyBoUnh/CnkTS+IS03jN9xjR8uG7GhF0DOrKf3MGH2GnEVTgcjECHFl2UwCtJwFdKqul07cmAhgxpKMqw4jIEoFT06cS8A1Y///Wvf8WZOctkUIUDxFIvBSK9JHXcB0mOotymMIV8zc3PYc/9mrt44SLMYD2Qel8ILoZFCUQJRAkUrQSsrfX9etLJ53yRKxA/ULmr/ubNekzc3ZJ2lgMj/CC15PrRnm2cwT2CGEoVg7D8eKWuYI44uuUCuoiP7Td/orv4bI0Wu3eEV8BxkbNHcccGGThdPUucLViUwXNQzp+/IGfT/f77bzD/d8I9wHkw3E3HXXei7yCNqU5EwbTyMZ/N2BPE+H7nhGYwwH8yy7NpoOeVQDecOHESzF+uw1kdO2UnOweBhg8fjniYXEIZl2KQghOYTKeToloHOGCRysILR7OQuqI1JpSUhwmDzJ9EwSOF4CN0XsygkF/ilXqYPtGnSBRGAa1cTTdO0xNW6X+GnQunzp7GZN237rvvfnA0+zV69Ci3HeYSP/vsU7d44SKegCh1RQdv0sm6hFhPF/PhT1jwmVneRl8OzUKy0pITXoC73Hh7CgSCING5VAxCGsvHwrgzkDvrvoBptb1794rek5RfgMaSyx3pLcrulnNf3Y0+5m80pVQqVSx3kTs+YzhIx52Sq7BafucO1PE1a1zd7FlyHk07th3yTyZggYxFJu0JyzHMAP50UZjmYdeQnrwy9gQanYXSpHG5mFLuLFW8RwlQAmmN6Q15pO1Td7BZHe1dGrqTc/HDmGw6p5RQqmfBg29j39L4RJAr/hmG3lbaorb2VjHVxx1UI0eOFPPG9fU33JMjTd4qAPuxzvPsbqz1Bwkn3sNJuiEv8YB2kWd20cQyTYvTrHgbrBNU8TgV7FJfDVPDXBwho0ekyRAxafKg1LCNpkvzVN3t5ctWZNMuvC5YuEAWz5w9dxZWIp6iz3qIs4exwMrza3XXcCjGgXPNKVb25SY0z+JwLKiaWVvrRgwb7i5cOC/WIziZ+hgmRJuft0g5ST1CiVBGeXKyMiC+3qpERS5+ylCsiwwpFXlw0pkL1d5/7z330UcfufGw7FQ9rBrvXBmlhp11rVj8pztMrTxUjvpk9dqLEikMSgVh4fJkURZoz0FkbjEYYCGhhnE5iAoBv7WwbB19axkP4IzihN0ALtzI2puUQPE0jN3lUiiWC7oWztR5xzNJRowYDgXoOUKGwk74E+nwaTKHA1bcaUdHk1J5Hb3ExEuUQM8lULAuoX5yNT931nFV+47tO0WJ0l0g1CX5CWAKSoF30CuyouwATOYIDTwgkcoEf834yLiEXaWnT51OTEYEYNEbJRAlECXQ/yRgbZ5vImWxBMPwzHaXZ3aeO3cGu2tmiZmlydhpnzPZ5mGN8RSdIpSPU7a1+Fk7bm0q0yQfmwQxJHZnpAEQR+AEF4MIghvzkQ+/AIwwlifzZ/xQTDxRd6Hj5JtMSNbXy2KMq9dgAvTiJdFpOGl34cIF2UlFuHzn8zP68gH6XYjIz1MtsoNkKdcynAM7BpNy3EE3u64OO9hxXgcmM2gecOqUyWICkxMYWuJEQJ8vFSuPNFJysODwngtiMYZJ75IYlzTWh1iAZmtgCpiLOI3ryOdxsWip1/JxCOoNV6nfuX8X5zgfxI6zb91XX34Fs7FX3bixY90GnAWzdcs2t3z5CjdmzFjXChPadCZTrYsSxNA8ukLyDSrvbkB5Ed0NKCCIECf8fD9osuzpk2dYcX8aZmB/xm8f2oBzumK8AArLPYwK/Rbfl/eQzZA2TijzWc22DhGdcjp20vG8Op5HwxX04/HdQ3OmuY5vBh1Tmx9PDAwzIIh3CskHT40AW2x47wBBCBL9UQL9RgJhfU7fgn5DfpEQKq1GKErQJWG4pGoIQ9QM31BMMIwfPxE7hLeKScwxNWNhmvIQzie+Ly0WzR5rv5RB2kv8ciKCahu1CDrm1YDFT0dwThojnsJc5lCYFVyAs+dqcA4Yd8dxYRf7XJoPtL6zIDmIJ9VKucISnt//1E3Yng8fPhILar7DgrMbWEzESRfs5EPfVlifK5jLgAqk/DlWx/PpICpZaEQz3lyYQ3kNFsc6ozXSc5wXgHjos3SsK6yPZeVlbhQWZc2fN9/Ng967Y9sOWRw+ew7Mz2LymU4WJcpRQcCu/xKudVS88t6p73WvijXE3X2Mr5aq+/gjZF9LIE7Y9XUJxPyLQAJhM59t9ArFZWGMhTA8TGfxvHcUHsJ038/Oms4+LalMZcOYI6GScPbqQgehMSEH5Wr48BGiYD1/9hzKV4Pb/8cf7vTp01id3qKrqZGGq6t8dkgfXZRAVxLwdT24Wf1hzcs6Dg5S+a6bWet27dzl/va3f4MSNVcGDrlqT01lpBPNml6RW/2314v5cLKuHfU2O2kn74EAYMIO5kTqb95yV65ccw8fPRIlLktXfI4SiBKIEihmCdggiPXx8uUeEuzbYAviB/7FSxcdVyxzwmbixAkWVfjOdhQxxC8/0SFUr7C87c6mlTAESdplj9XoC8mRnsDwB/qLdBEE7ACP4PJxMkAv+XL3YKO0538eOSJnrBw99qecT0ezf9Rv2OYPwaB6CRYtCa3MUzJL+SMvwg/uQh/pJ3B416fiu5pwjXAvo0T24ImTs6NGYqBi/gK3YeMGtx2mARdhdfGoUaPBN3ZaYledLKQHd4Yu8QV4rWyy5VxIKMhWnImRD0Rl6CTSLj4wJ00OYPpgA4chbJiHoeSdK6WZo8mCYc3YgXn+zFn37VffuO+/+95dxgKeEdCHaTLxXZgfW4SdddXVw6Q+lGKAR3jN48XXFyKEwmH1SR4xqCjgKcmJKBlf2KVST+NDBAzNPiPEB4WyICSfn2MglRN0+/btgznMP7HrFGfayMSl0mfY7D1OkBXMiVj71sk7aiSA8Vz6MYkPntsQyrpOs1brsXt0186dbjnOY5yG8xlLSodi4A67OKB3cueoFAoFiH9fYvAqVrtrqVjZ2F1j2aZ07Iw6hcjit9Dc9Llpuh+XC/mmnsJ3KKkzmcwMpqP4DPigeOyZLMI61Vl9eNuiK0RLGBbSrbRZ28Qna5/eNtV9l5/KJn3vlRL5否U3kRzElsAgkOH6DiEUQqNUOWYzpHQIzPXNdVXVVRifKRET5w04l5cy5nc0JyPs3dOcXu2alJkRSPrYsMLxvFcSSKrYjzRBvzp99qxM1vG4FU6KrFq1CjvkRpEJoWkI2mJNndJjdNp7wQkSIiZfdGLuHLxPnTrFDavejgXlje74iWPov24Jj2zfiaO3eJZMi+RiYi9EDuXFsuadE6ZHmxoFjGN2L3AWK9PKgivIxmRseEzW9pyNt/D+ds/KS6qSZ0LkBVkJDO4lOPOPZy7W1ta53dDztm7Z6lauXCljUGo5BDvqULdkkpl1kXXfai8Qsx7rM3Fmc04ll63vaQx8nsCOUrcHOgV3m+a6EHM2Lheyt56snmTrT2/h74943qYs4oRdf6whkeYogYwE2FyHzXcYXbApR2DQF0inPxFbwNetX4+k7ejIRqIveYmBrsty5g0/bGVChSt1oZypUhXmEv1RAp1LoGA99EnY6U2ZMkV31mGyjmaoFuPMusrKSlE2WRcB4p3H1BlCQDJa1bMkoSpIVGCJCfcnT7mb9Kr8Cp1hRLDoogSiBKIEilYCQfOW0GhtY4E4fnRxh92pk6ccd9cvW7ZcVpgmaeFhMkPB8Cwa6gDm2HbbRwtx609Tabrcz1kN0xwsD8PPD+BEL0Eg0Ol5JfhYtdXZzIsf0nTMi+3244cP3Q2suL579467fOWq7Jo+f/48Fh2dgQ5zSVYgSwJ/sQ9vu4dxOX4SwI6H92J0JjjSlkMiOJOBNcge55dZCbA/HT9+vJxVt2DRIrdo8WKc2bHELcFk3ZiamkSunNiibKnnidwL8c78wvwLwfiwtO/OByKKHNINpBPcIXwCZoF2NzzEDz74o+PCn1KZrGnD+TuN7uixI+7bb7923379Lcymnpcdp5y8fHfXblk4NG3G9GQnltVz4iG+lPaECkb1yKXkpr4eIcgAC41EBZJM7qS1AWc40hzsl1/+U86ve4Q2gK53chVUb/XiWRT6zW+7S16izrO否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否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否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否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否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否ZTjnGP3Zk6Yn7uKFi24f+u7JEydjQc04NwWWHoYNr5b+mWMKdCUJIsVDfhljk3Vi9Qn9+IgRI2TR+F2clfw7jmc5c+a04znNdF4k4ueFJKW9RhIcPW9LAigA6rVc5F0Oi008WmUYLHYtWLAQFiXedZuxU3L2rFmiE5dhog7QKHbUb1+XeKNexHKVsqTH1xeGq7M7nwCQrQQeKt66lkAqU4XNPneNIUK8qgT6zYRdrBSvWsT9M13/Km/rKopb1j3qozIshd2dKV3aF0oXKf0jB4+s3EqwpYnnqyzBjiuablqOs83OnT0L8wQnMSh4FrucbrgnbU+QLqXKVjyL+hSEF7dUBz51MhCI8qBSVVVZjQN/69yixYux8h+H/dbNgjI1G4daTxOzmKGJAtYT1o6culNAXKwzBiPVjmVv9Y9+iyyQlkEEzXFBcu7q466QxkeN2OlxWXZ/8HwburDu5aSPD1ECUQIDVAJ5rUVR80lqOeYh7Sg7XLSHHXHQnfZMmtNMe8pHLp55hMU0Vy5fdbex007MLGYkw+x1V71OGLRiAOTBgweye/4iJub4u4zf1WtX3U3sYqYpzEc4n0RWUge4JHswwdXRebwEAcFmGNEvAhT91xvwl2WC5cdBizE4D3gBzgNehAGLtZhk4g67yTCBWS6DFZqKfZo5MaUpQuUlv374amPg/eMu/Ag7Sq9/FvHhInUd8tLdcUNkYu4Ozkk8eOAAdtgdkrOUnz9/LouLuDp7y5bNbufOHdBTpmOAUFfum66aCgSZqAilXhoJFp9qKRYS3A2YL1hXCotPlq0KfBskeYLW54gCDGmlHvb8+TM5F/IA+D169Ki7cfMGBl9bZPA1SV7I4zPI5l0I9E2FUVSJuCwTTxB5Y+vSislXthvjsZqe+uWHH34IU1gb3cyZM/A9UWGp0rukB1a542IZeAi+A1lnc3lJuB8BLAAKEE9gAtwdT3fSEMZy5D1MY3FhWHfyjTCFJMB3qDt9ZKG0MSxKoFgkELYS1jJYC5LSaG0H+0rWe8bonW3hyFGjYElhsZx9+vx5s/Ql589fgBnz5pyFHim+1/Ml711IP否6WVQC4hjP9p7f6F99/ZUswtm1e5dbsnQJFm3QtLMeqcEFHNIXkgmm84tS2tFvICJt/uHnZCD1prVr18h3/wUsGH9E85/BQhZyldD2eizG1N2QAMtOf1xwpOXOQqOmM2r0aDdn3lzsppvn5mCCbiEWq82dNx8766a5UdgZyoVZUvY+H6v72WzlXWD9iC5KYIBJoOgn7MIX9FVkb43x6+J5lbxjmleTQP8sq1RBUq6z3UnfdSBZSgqVCrpOCfZDBIniw0ChHNFeHdKQgB2WV7bM+N5xgGnKlCluLCbu2trWQCmsdwdpruiHH9yf2HF3A6vxH8NEIRU1wnOFPW74cTAql2qGhy7Mz97xMJ5+IzGTNAs2KJ5z5aUsm05j8gthBAIC1JVrQ8T85UgoTTw3hCueduzc6VatWuVGjhiFuFL5tWPFHvdZsg7ZijfiMfnnDMAqCbj6UvLEiOkr8aelZ/QlsJYmwJ2gs9y4WJ46PP7a2tvczVvY+XH8OAaVL+bsHklxpxiiL0ogSmCgSUBbIWtVyJ21S8XOqbRRIJxtmbTR7CszRPekHcv2pUAqfS5xNsGk4IP7DTAtqItprE/gbiSec8e0rS9apA1tgHnLCxcvuEPoyw8dPuSOoX29iYU4zRgAYp/e1spGmH0Bz5nwJi+RnvkIvQEhlg/ZYpyVE+8BGKOL3mV5IcGFwizc4jhZMQa6EgfSdr/7rnsHZ9UtxcIYrhanzLhCnLunZFIDi6MYSFmJLGW8itLCj8AZZ319JhiPJmnGWEK7W1z2OR9Lz0K6xpfNOTF9haSsD8Iz6hRtXdN///4999vvv8MU5g+ys467PSnX0ZDdGphm//DDD9yGDRtdddUwO9LHmyD18gMm1FRhIzmnzkSJOmz0pD7lOP9NZDj5sxQKp9c0LE1nstBUyhdTE5a7JXBPkyXI7t2773799Vf3w79+cOcvXHAvMOFOx4l3OsNqdcvwSmRfX8LKyPrr6VFWybMOzHHx1/x589zu3btlwo6TrTZpzVQqFlwNgd297Pz8WwHxmUCBA2myZarkJMjkMXxKU3ckyBA6C1MojmGGlfcQJvRncfG5q/hCaQZvmL0Pg1cCxca51V+r/8VGX2/T0xG/Fs78OpKFh0FDaa0EQwQ6TG5haBuSVpJpqDMghLvQOPFVjV1MCxcuxu66Rnf37m2ZEDlz5jxMU6pZZQCnLtNmpxE99KUsSMJ26onS8IM+LKR50NCAHXG/gdZ2nFtW46ZhIfBoTOSw/6LeU8gxjjvw9N0mHoUj/MSJekTLs2fPRSc9duyY3A1PUfWLRtQAv7OcWORcbMV6yIX+o8eMcQsWLXTvbNrkNkLvXQa9d/y48dB3y3RhHxK0MQ1kY7VA6j2e06qvvrCN79vyTSkb4EUq7JmsQ/kPBr7fJo9FP2HXG8KIFag3pBhxdC0BNtDsRvpvQ20c5PFqvSMjvB86EpzyGr5jGs4OGZ0xfuXlZQTElvZq2W03bPgI2aF142a9rKi6dvWqmNJqgPkCroq2hl8SyUU7eHotT8Iwz3zYNFWHvKQgBX3GqnJWEKTvAkFcQl83CQzLJiHckCDA4vNkCfzl1dUwbzrVzcPAyaxZczBhN0N2Sy5dAoUKduZNieanAf/oAtTJc1ekhvFh+jBckBW6oC5Y3hItKwo5GANM+OfA8bWr192BPw6IyTfWseiiBKIEBq8EutWuFJF4pN/T5qxXqUraWnogFK4+5vmenADhquZSmFt80fICZi0b3A3smLv/4B7OO7kt59HevXvPXbl2zV26eMldQR/O8GfPnuX0yei5ld4ko8LkszxCkOxz4VTFEWo8pn1QPvWir4BDmgJln8lnypp3/miNYM6cOThb5h2YclrklsIiway6WlkJbyYOBQdHOgo5ZikOHoIkzz64y1s2QQf5dImnlwBCcgJSrKdXvUOXkPHctrNnzrqffvrJ/YgfV+nTBPaY0aPc8mVL3Yb1693yxUuxgruGJaBbVok/wJvYV+qM/BAecGl5B4kIA9wsK3OmX9kzItXr8YVoWbwWTSCB9Bcrf9abW/U33W+//Q7zn/tkJysnyBN48fGSYqYvpSgB6CMPKdGyM5r0/EaaeYVUcS72OJxZNwur7D+AOdjN2B25cOFCObcxlKWm9Rhybvpg72XHTKpUCJ1KKh/ao86JKBSWA9Djh66o6DHCmCBKIEqg30mg45YobCHC9sf8mlLbtJRtn0oiDTcWRPgWj4uBli9fKQs9KiqqYBbzCcyYX86Z1EpxwRd2TjkR3X8I23D2StKvQSei44KTNvxOnDwh59COHDkCC4NXu6nYZVWKyR3C0roAcShLuHqdSPpc+MkdHYM52UcLBY8fN8K60ykxjfkCC86EBoimYB8uqeOlSwmomBXMKmEmkciZcJQ1yo4/6ipceGMLwGfMmO7mzp8vJjBpqrWuttaNHztG+ntDp0v4bfrZQvXORd5J9iFNuWDdfHptBN3MJ4JFCfRcAgN+wi63c+i5gGKKKIGeSSDpOnqWrMfQlk9vdTApHsFsj0FnGL5LCJbOlz0xFSM+09k92WHl8YgyhcGqiZMmuS0jRrpVK1fBPMEjdxZmCg4ePOAnU067e3fvykpygQcyqmVEbZ29nHmW5OU9knPhS0cQIS9MKfnhTnKNdfMYT4R7287ropIteRH6ROCgOVVTcslKGCC8Phge8qLJNbXEE0TC8YRI/niODpWqaTNmuE0bN7pt27a7JVj5X4OVUKMxEMazCmVVG8uIafxfSIjRp3GMCUsjIFJiGJeWN2kUJ0mMBwv0cbgxREBAh2K3EMVFSJp3u3Xzljt18pS7jom7FpiOii5KIEpgEEnANx05bbk1MjmBxSsT6QN7mzzKxZpMeJlHA876vHTpgpsxfaobgTNJmx43yYTcwcOH3YUL5+V3+85tGQRpxOQed9SxTeVq6TKskpbzs6Q9ZpusrbLdEvJ9eWjmzJj/HtYD8Sk3JEldXB7wwj7OeCDNqrXkksn1I9SLuAKcppw46UQ/J+QWYELio48+ch9+8KEbO26CG4aBtGHVlYAhjtT0oyxCQQZaF1Q6OtlBqEBaIl+5gJgg3LYd5ZKWefLpktDsc4BPKk8C2E1PFp9PZsFAb/qYLacW+QLMXlW+ujSpxMnli9hhRssNh1A/aY6VdZGLwxZjUdG2HdvdshXLMTg0WillHiWWERGaeFR6ypnx5+ECcE9pIGuNDEEMB3mgbpTcg/KRIkEixuc6cGpBQESqaHmCcJyoa8Xk+aOHDZigPAN95qS7CSsVzc3NskpdqqAJyJeLWCxQNnOz6Ysnzxflk5Qv/OQXnwbglG9Nu1hzWLpkqduxYwfOsHlPzK1T3wxlxfrAgVsttUCOVnSeP6k3gltlyWBPRgLBSsVkVsd8hNwy6HyYYbDY7HOIoSd+w9eTNBE2SmCwSMDeD3vf+iPfWdp7wlO+ViHYPAq5SYDPAwH6GObBBUMMRZsHyzMl6AsnTJjo1q3biMXSLTBvflX6kzt37uAefCcn/crrylxps7ZcKGP7y8VL7BR9W/zo0WP362+/yq4/7sAaO2asKx+F3VZCN1OxtcYfeGEfxwkdpiVe/rF/YehQ8DccOiyP8Zg1qw7Wnsa5FvDFvpSWd6J7dQlQzvyHoH3Z5eNi3y5ljTv7d/lDIE2ycoHa5s2b3DouqFq2DAvAJ8BcOcedylxpeSlLWuCJVadzmZkUs9yJWx3ghAZmkgQiYwTaYxguiViHzBmQPYdxFhbvXUnA3umu4DqLz9ULs+XSWcrBETfgJ+wGRzFGLqME3qIEfH/G5lS9+Q0r9CX5EBcARqN3pYmb0qpSmCWqdGPdGERNc5MwgTceStSUyVPdeUwIXcOAyy2cRfIQpjLvYQV/U2OjnNchChiRZlzYSUhjj3xCasIOIEzK8DBtGEe/5WT3bPzbfg7pEL+XKTUS8YYEBQLgYI85E18oEypF/KcsysrK3OiaGlndTLvh47CDbvbs2W7tmrWySm3K5ElQ9kukXHNxkIpcJ3hzgxQoJSeNDROr5oU4AhpnhRKlydUHOYR4kFbKGPcnT57Ahv1NdxW7QJqeNPUIazaX+BwlECUQJTBQJcCBjGvYNbdnz79wll29Gz5smHvS9BTmq+thavAUdirjfDq0pWxTuWDDHPuPEvxkoQ7aYfZKYR9hcAPxnqtxgEOIheMDud2RPnF18RDImAtiRsDE9DTsXuc5sLt37YI5oM1uKXaEUU+iYx/KP8qZg1bigCYQu8hZI7LXnNyzkZnntBwzEX37GLDAukS+KQ91OoFFffG77751X3/9NVbPn9HJK8xyTsDgz2qc/7dl61a3aMkSDNqNQIEECBPOGGY4tczSpwRIPHnlHEQzTSHsBCmoZ3YEHOAUbrHLVfQY7HRl/vV4J3/95Rf3zTffuvNY7BYuPpJ3TvSnFElndKVQfetjuba1tUu9HzlymOys2759G96JnWL否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否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否ePsBIsL8uwcmwXkWW1HUGkz69BeDoBAUQUXtL8htlyl35zcWsUBNMJuHuOdwPOwC7+aTg5Nx2n6ibhTrrJei/dRF0YYXzT+CZV6lmdrA0okFupTugtLFPT8DD+BGwsBKwglJYDYB+wuDC7xgUdz5XpFK3MZ3xnnsdQ8nJn+pnTZ+T2zTtJOyT48gpIEqMnciBy4H3kgI1a9g7rYGNxGDeYfhtzgyFtMMkpKruIXz0aM3sEXFR21Uck1Uta0ZPsErgDfAKsBfD00BqYml63dq0sw8m6GZiHVVmnk9dbKOn4Mwy72ylcBI5YDLPNczbXJgJDAG/ehCybSC2h7NtKIpBhsLhsuCyi7hMzaA27louA1VUpwYfBU2Z0XTgxf/36ddn7/V5daLt67ToUna+Vb+Mgw2zatkX2fLJHFi1uF5paL3JATPmA5SVlGi0BMGUddR5Ix5EMnMkZ6fcBAPyvyKJGUjhKyeBluYwifjUThfrfg5x7FlYljsGUF+Vc3ieUCorMkbpiutK0gfKRBtZB5VdfqFYZkYx3Y527N3kmvoUNMMG2edMmWQ4zmDTJZnXI0utazsXyUBxV2iY3G2zadimTNY4Fa6urxyNJYdL8hb50DgnyeZDKftsVl2ElxHf1cKBUn6seCt+NEhunqree7vuyKy7ct8U4++7s3VM+2PfX2/w9La938KH8h+HzHZyrr0Phnobb8否YaWMbw/qPyhMetsM/YpmEU1J/+N0fcGp9nPzHn/9Tjp84Lm+wKdrJLu6OeFq0eVdndCkmI1aRprhpNrHjQoe8/uoNTCXXSTOuS+FaBmUDZjEc6tEIIFDauMHD1Y8ouXmZJ+y4obuj44IcPXJUFXZcMyGcbfLW4uOjWw6QZzy1OAsb0否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否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否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否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否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否01sYWYihs8t+TR0/kMHbmf/PPf8qFjgvysutlZFLkQOTAe8iBvB/dg1YN/Fi3hebsPMofMFRC0aTjSygcRuGH/uTmGXqabNOmjbJu7VqcMluJhfnJQvM05ojvGXbldsDk39EjR2AOKF2oN5iqe1OM8BoLbR8vUtiPOC7ec0FmxowZUK6sko8++hgX3G+TefPnJrt6qYDhX4E4Qpz8T1NIMGGpd2IB5uXLLjl//qz89NMh+eWXo3L79p2EJVom6HF0YCZQS0tvpXHCODVBOrulRaZMmSwrcWfHcphP5B0l48fDFBDoY97Xr93swSqx8ALFgo9zKVzMcM4kKAv76AF6sVSjICyScQNFUR+XA3SKEQ+ah7165TLMQX4vf/3rX9HuqSlM3ivTjgWnbVu2Ck9KNuJOFutzyhNbG/PIVDHY3QCisCEfy/gJSzbjT3sNwna/YdIkXNCrGSFXrl6So8eOCe+iu379eoJUyWE+ogEeKonHjh0rU6HEp6L58E8/CU2AUm6xhcp0zEnQDKqHPNcFV1SCJwD5HTeNa5J2mPv97LNPZfvWrVjsnO0XLMEpVhR5yDo++TZXGCJXuncJVIio+2wRInIgciByIHKgajjg5GnOD5xP6mDqev78eTqHcIY9sP8AzEPf1HlQ7xTGHELzlYVzRi8qg3mDU0chnmHSBbn9ypUrkDN/0vuVFy9eqnMzy6bVByr3XB7MQEBAWrjJiAo73sd8/OQJGYPNN3QgVf90xkP9BmuqGjVqFObiFlj3WIe7o9dKa+tst2GOBOa4apM1ckiMUZED7z0HKEP317cWFXbvffeIFagWDqQLDL2hKJxkTQTIizPcYVq5OEsb7LfViXTk0c74PJgwjjDdu561Q0hLd2UZbB6cpZG+vPTu6S6EMHwhrmxcd2FiNJhC7C4U4i4Hm4UrBxuWZ/ksjuFMXDbIBSB1Pg+CFImprOMkyEukT508KUd+PqKLuzSnEV3kQORA9XMgHZftG8e3nXoHrwL+B66SkkMQFxy44P76LU7zAqgR5i7XQkm3Dae61qxeJW1trTAXN1HN0zgc9oNedKHg5s2bcgl3XD2AWR3e31atTtuHwy4qoeYstTKsi1/ERxIVdlRK0gzm//7f/6fyYeLE8ZqJ4zNhyU7+cbFf48A/Ln6wsRnn+sFw6cRJpSuXL8q+fT9g9/JxNR9qvFE8PMHjEEFZ54ihom/K5KmyCuXv3LVTlTxz580FTaNxiqlW6YNuVcujWU465mSzZhUKhVNjUln否5nU5+NdrH86vA4zoyxsQGHY8odxBmdp9iYM/yxscHxn40J82bQwX9Yf5gvTSsWHMBl/UCybqcCB4cpzxOtudCSy7S9evCRffvElFFf/VNOn2s5Iaxo3Tk9w0aTqjBkzcd/bqASd6nqJDE77DnCyX5lcQNnANYGD8ZAeXl/pw9PMvHSuL7pkh9Fyo7d4tCwrgUVFL2Ph74cf9mLx7zBOC953mfHkIp+Dc1EcN3iHXV3DWHkB5dfpU2fkKJTS93CqkOXym3hD266BcxiCiAHwKv9QjvITdPG+SdL3GqdTuYt/Hr6v3//+D7i3bpdMb56mkEoWYMAi75Bb+cQauFiri4NCCPB0Fk+/5bd3QWKCJ8yR5srGMiV1YaphD+NSyNRncIzpDraw76Q4oi9yIHLg/eCAfePhd1+K8p7AlsLx/sQnHPEe1j7hQOJx9UlmAYv31eRwryIYJnDODbzXePfu3Xqa/smTp3Ln7j2nsMM8qPiHvVaZoYhLGbxF6YhQeYDzj59jUmJBHWVPRFB2pbn18+c65AxO91PmmDBhvN4ty8me8585WuzhHEgrEjzFxr9RIwOZBHVTmtMslnXA3o2NjdLW1qab1XhXMzcHmTOZxcKl364CSXuXBowpkQORAxVyIPxdUWGWisCiwq4iNkWgyIHIgcgBCjeDJdoMVrk9bfW+4pEXJCnxY02Li33Pnz9XE1SnTp125jBxOoPCOH8whMJ2TymO8JEDkQMDzwG/Dj7wBZcqMYcgXbjmqTIMQzSBOW2Ku7Ntz6ef6H11bW2tUl9XrwvbZt4uWTMAvlt3buvO3tu33J1VxMeR3I1upQgZ2HiliETxz1xAIH/8c7GDyocmnHzinR6//e1vZMeOHTIdp4d07CXvkJ9+ZtV6+roSP/85lCniR48fycFDh+S///u/ofz4Wcf3pHjge0uFAe7oqMNCRBPuxZs8cZLMxMm+FctX4HTjUtCxSppRfkNjgy70uB3aVIAgL/I7fZ0SpWUrXVYAaVTIwmoHyT3wZjH3IOsQBGVXwHQN53bX892Fk/CXL1+CcvZH2bd/P06m3dCa82TdFJgnMawbAABAAElEQVSMXLN2laxEe7bOmQuTTm4ne8IaNpRbc1OcSXzo6WUTsJ+EP67pt8UuFuscFM1YwKuREdpHz547i3rsk2M4ZcdTtzU4MepMwJII1Na91Izk6NGjVPn9EH39Iu7cvX37lirxR/EkLn59v33l+qorZ3Cf+o2CeLYVFXW2g3/rtq2y+5PdujDI+JBfZSnOtkk2XDZzTIwciByIHIgcqCYOpHNiaar8LKgA/H1O5RfnVMqPbW1zIXcNl7Nnz6tcfO7sOd3E9grypZ82SyOuIEXp8wK4zu3IM8wrDJmdpjGPHj0i3GRWB3q48W7kKG8VAzLLW+4OgqMUS6saFBI5D06F3D9t2nQ5d+485scXuh5hcoJmGMAH79bjHdm8h2/lipVqsrNlVotupKt4bh5AemNRkQORA33Dgaiw6xs+RiyRAwPEgUpEpgEipWwx5cSvbFpYJ0uzd9lC+iCxp+WUgi8V31sSDV8eb3qL0/IZbgtXUkYIw3yGIxtvOPm2NIMN4zycgfhg+rI8Thn3/FmnXL52Vc51nMcF0Pd0AZmwJe+uM7yGJkUcfZEDkQMDyIHCH5H8KQxXTd+lrbCHPMH4oTtz+eMfiwk0jzcFP9p37vpYtmzdjMXrZe6+iFGj+dNeF+lpAs8cTd49efoEJgCvyN07d9W8nKbllWWZBuutdXWKinKLEDQ3uXXrdvnjH/8Ic5irddGAJGv7qqKD47FXQKB91dwQeQMYbXXA8IQcTRDR3X94H3eanMa9Xr/ItWvXwWbV8miaPWqxODEFpkbbYZZoy+bNsmLZcmltwf0jWGwZDyUezZPSkfV6ok53a7NFEKG7pplaurMNR5Km+qYbBvr1RBez0fl4F7BnHr4wLvRbnvBdKr1UfEiEwdg7xJv1G0yYPwtj4TxYizOY0ni032gyTFuxtaHIss+Byt5Ll3CS8scf1SQVT6URnv2GSvD5ixbIGph1moNd4navjJXo3mhNRebGDoc7PfWZfFKe3ITKxIM2tjSLC6rmaEeCJzj8jrVzIJ7mPN+86YLscRd99arcuHFDFVvsZ9rPkd3KMNpHjhiJ1OHyGMq6J0+fSifu39WyAOBqQkgSFBDDqAF27ttk9VkJFI6K1GCxcs6cNtxNuVU2bd4ExXwz7hyq1WS2L6nmHyPCtmesZ6PCugcBFVrhvY+RgQt5YBBhXIIhRam5DSY/jyvAYILicr2VwuVmjpGRA5EDQ4YDNhbYuDJkKhZUpHQdk/mJx8y9M5+9k/nORxg2zh/8x/lEkyiT4fQcHWPGwcTyCpiQv37jOubUt9jIchF30bt0X5SDtYkEoZSeECLf78rEk3SQSCriPCjNPF+42CFjD+H+ady/vHTZUhmHf6qnA5CW4+vjZA7QDDoaG8epKWjeQ3vz5g3M5Z35hQ9ALC1cNGM+XrRwkcydO1cmYSMb44zeASAhFhE5EDkwCByICrtBYHosMnIgciByoPo4YGLt4FFGoZNCtlvYcos/XKC7jZMqe2GG6tvvvoOgfyNR2A0epbHkyIHIgZ5xwI8vgz/MdE+2//E+Agqm0ThBx4vqN23aJL/7/e9kzZrV2KFLEzm1+DVfo4sOPF2nZ311neCt3Lt3T44dP6am8y7hZBGVFtXogjWRkuRRqTIZp6B27twpv/nN72TPnj1YdGlMdhlTwaHrIjhWpeM2Arp44JFz/OYJJJ6Wq4GZy8dPHmPR47p89eUXeg/YzZu3YBrJL+ggT+3IGlXkjGtswiJJq94hsgqnrzaD/3Pn4ASWV9KxLJ58evXmlZoXVLObuiTkV1x8jbrrboQ2GL/BuiQvYkIZDvhvhjzU3eke9PHjJ6qo+/zzz+X7776HWap7msL2mjxpkp7Y3I7TmgsWLkgUsEl7hMWxn6Ev9Y8j3sJ+o+UgiouMVCZfvnIJ9yz+gj9njpvpXOCDKk99Co8H68UTdFQoc0PReSzy3X/wQB4+eKjfB/stxwN+M07OsZyD+Pa0sN1qoZib3TJbtsPk7+9+/1uY/V2jJsGMupRTurzpotNIAwveOXwNUnvvZaHRRQ5EDkQORA4MBAfyRvLs0K86vmRodko7GEXXeZQ0joWJ6A0b1mOeqVHF1+NHj+RacBdsXj0cFq9QywMI4mwNAUIoJ1g3x8LPDV1v3sAs5p17OGV3DFYafoHswbkN5uyxiYwmrN18DuIBa2XyVPm0aVP1nrgHmMf37t2LDTvp3bVB0QPibWpqUllpHTY5tbe3QxYP7vtlfQeEilhI5EDkwEBzICrsBprjsbzIgVwO5E2zxXHuRBFECQojiQvh8uItzuAsnCCo0FOYnwsO5lJygkiIPBSxuneVwHSPpe8gjD9Gl4WtBItn2NLCuMJ4W5QpbDPD9S7vbJnZcIi7+7RCOrP1svwWb2+LD8syv6UZrItPFkURnUAkHnCU8W94MsNFclH2wYP7uP/lCO6u+wV3wNwrudBlJb0NdgampRhd8R05EDnQrxywDxGF2LjSr+X1KXJPPCa4Giy+z541Sz7+eLeeNFm4YJ6ahaythRkdpHFM15NdujDAey/86TGcIjp27Kj8dPgnuYHFCLu7zoa5PiW3l8jSObt8G/GEGxUqf/zDn2TZ8uW42L7O15vm8VyN1OyRV9i5ec4tlFBGoLkjXkE3AosgdBy/v/32e/mvP/+XHMPCCU8j0jEf2UcFYfvidtmG03yrVq7GScYZMhF3BDZPn164kxjohr91959wcUUp4QOFKg1I11gSkTjftj78NmGCgymYNhQmhA/xJAgr9PR1XqMrD6/FlYMpTLMcrEyS4pmRsMhqqgDMkUDqFKt9QHmPxS8oZvndc0c7T6OdOnUKCuzjOJ12TRW9XBzjd9PY1AgzpzOhIGoVKmgNJzGzBOLgnyuJci8isfhHV2pcMWgvPiisw0av0eyjGaNIWQ4Vb1jU88xgtEtzZV3suCj7f9wvp06clKe4S9dcVolIpRf7cCPMx77GAuF5WAW4AwX+w0cPte7MRxPfpC+Vcgxb/7zDWvvqJQWxnmCxutE4MTxr5gzZuGmjnmhd0r4EyvqpuphJAOIh96mmVP4nfST9TWLtwm9ScTOjdyEdjLJyWXxRms9jr6RdCxvWJ2drZbniO3Kgbzlg/ZtYbXwoV4LBVwJbDk9M6wsO5I0TFheOQKG/L8vta7zvQls5WvLTjFMs1WQlQjJep1FNSAPFff8t7oWjAmw6FGZLYNbxF8iAR+TWrZuYEzEv6hxMJJwbnBzn5hHMlJwsUJjhVKCQTE+czq0+v8Log6sJwIHJnfmfP3su16/dkAsdF2QWzElOgBKMpjEpvw8DITZ3MSu/2yZYl6AJSp4GpFw/WAo70kIF3cyZM6W1tRVWP6boPXtpPa010pjoixyIHBgaHIgKu6HRjrEWHxQHVDzyNfZSSo/qH+bvUcYeAIeSVA+yDUnQD0mI6ot2dws9ti7zAjbjL13C7vaffpKzZ87Iixcv0l8LJfuL0dGb76Mk0pgQORA5MNQ5gB/FNDHD03VtbW2ya9cu+dOf/ihLYJaxrn4MToCNxNDkT41R8QB4t5DgGMMFgZs3b0JZd1gOHToojx49rmqOFSyABJSOwgIGTwlRWfdv//bvsnv3bigixqiygXlsAZJ+xYE3PMoLPhhHk0dcySGLuJDCk4hncAfpl3//Qr764it50dWlJdbW1kIRWK+KuXnz5+n9eL/77e9lPswkMo38fY28L7Gg8xo4a3FvGBdVFDcwIAmP0MMsSFfHNxOHoutN3Ywvefwgn8qlM0+Gl1i5c8o6l8S2pnv27BnufDmHU3Xfyfff78X9sxdVeUtl3Xgos6ZMmSwrViyXBQsWSBPMZLE/cUGNXYbKPIS0v7jGtXpCXdQdea74Cp9ZZGn9tU+BBtLV+aJTTp46CUXzN6p8NCUzC8lwQ5Vb9ThFMH7CBOmE7PIz7me8iQXJu3fv6jfBPPpd4M3SrWaMHyzHNuNi5bRpU2DydwvuqMTJujVrEG7W++yooONiqi5oeopdvVN+ZWm3fmDxWU5bfHxHDkQORA5EDgwNDoRyYVojbqzCJg9sYKFJdJ5am97cLOvXr8cGrrsqF1y6dBkbcx9qFpUFMAGbnGmynMqZKdIiX8l0TFOU2W2y7sJGovMdHfLd3u+lcVwD否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否+FCV8Oj+GL78HmAFssbLWQHrYgBXsKxVzwevWyS65evSpnzp6Rq/8/e+/BntWxpQsuIYHIOUeJnHN0JNjG4Zzb/dw7d2Z+38x0P3eevj19rsPp4wTGNmAMNsGAAWOCTc5JIgihed9Vtfauvb+gT9KnXAX6doVVq6reXWnXqloFXfNuZzs/GoxDfOchftEeEegRBNhGkZBf7k6S7LetkcIBnOSiuplp06ZDePQWTta9hrvUlqjaG6qB1AUG9Duu57H+x/VV+GqWVxDUtba+lLtQh8n+6szZs3IL6nZonDAjVRuZANaDFuszvRgkSdn8eZJt2LChWLyfjDtFVigGa9eukYkQrnBsdcISLrw49YQZgQO5uQqRDMNcDCHvIf4eEvbj587+JqdPn4YAoEbTWAP+7723W1avXo1TdXOTu7KYR1V3iQrlZHUp3uXqWEqVFC9auoqAAW7g8gk/FdTBQpkNvZx3my58nT59Uj7//HM5euxnuQ+1mFSFVQsVmDU8LYkXSnVS69atlw0bNspMqMOqq7VPUCZmCbl08Bv60NkxY/lnrIB1nkkypWAAhWqYf9SgXbMOP336FCfszkN11km5fvOGRqV/QRsImDJ+M+5rZLvhYl7Ly1TFV7l4AYtus9o7YwK8Q2gBFijffvst/L2tJ4vpp0axY88HkwDEPgDuEFclruQn937LRinzskrGszidylxJrjEgIhARiAhEBLqGgKmPT3tn119TawM1H4wePVYaGkbK+nXr5MSJ4xCgnZVHDx/r/CFRKx8MITZ3TXPlxiqObzQ6ztmQYE8NSH5oUbq72EDGuWlj43y9p4531RXy5zBYg3yOhtaNRbo2wRN2N27c8GsTyq5bfkbgBCLnTQ0NDSqsmzx5sgrw0jymqHZLBiLTiEBEoE8gYF9LfSIzMRMRgRCBdEAKfXvfbh/dfTV/7SNUbIDP+5k7nO0YZ/MzGvrTz/yNrr1nnj7kx7gMz/u1x7Pvhle7vlSbX/r+imFufvl3FuJtYUbrwszXnukCkMUlPf9IQVWYrap+ivcdcSLdBFUVzhhfe5I6tZMm6/LR4iMiEBGoIgJop/h4VYMP7j5t7Au+WCZZBHQY7EfnNTTgPq235b/80z9B5cwCqcfJOpaRxeNGVhMeWbGNHeNSXeatWzflzJlf8XdaTxXZHEFXBIy4F542RhCGsG+kP3foMp8UsFHVz/btO+QvH/1VT9uMGz9Oc0shXa0W2r1vK5eD1fmRl6YDJ1X/pfeVkEUNhB2n5IvPv0SffkXWr1+Pv3UQiC6TZcuXQyg6GvjxJaT3ZBFrF9NbnFNvOfMiBO/DR1gqurNxGG47wBlaaPLxjcL8Q37mZzT2LOVv4R19FuNnfmF+SvGthCYfN4xjaeVp4AYZ3y/rgb3zV22tKqA6fvyY/I//8f/K3r17VTVsKwTZPKHFk6uMyLvrZs+eJZs3bYHQboOMg5A84ZNJKs0LUtIQTStD045Do4XlSHkypoVo1Q5ZgQw1GPX4lZbzzt078id20d9A++amIbYZiu4tX2FUs7NMVINJdZq0U4DXl0wNBOn1ENDPnDldtmzB3ZEffoR3sllG4HQt3y/btp6oRaZTGImfC6RNrXyyHtARmOQwb+Lty6/MEk8fw95EysAoCkNSmqzNYmR9o6vnEehwO+35LParFNl/mInYGhI9+Uzxz6ba2T7H+HU2fjYX3e0qGB8zCWbLYnU1HA+MwqJxbODYaf6s01avObbOmjlLtkGzw53bt+SXX05iHoFNMq1G7cYmo7enhTpEwdt76BOelp8aPzBZOPNEHrx3+uSpkzKXaroXLpSGhgYd5y3P4ZN31/EU3vXr1+XYsWNZtZQhYRXt3EzIPFFQOANaCXjajvkm3u5piRkS5o7PiEBEYCAhEAV2A+ltxrJEBCICEYFeQcBNlytL2iaWLg5d否Xn5PoFFrsuXLiAu+sOyq8Q2HG3uhn7KKC7Iyla/PiMCEQEIgKKADoe3p8xcdIkXDq/WV7HnXXrsMN37NjReuJL+xp8GNPw4a36oWwfy/xgpirM87+d17vZjkFocf/BQ43DH+vXEo++YEFZmH8KEuogRBk3bqKedNu+fbuqqOQ9ctnO1WGQ6XsDQBQX4kPJGP8bUCw/MOZf3dA6aWycp8I6qt+bijs4xowZq8K6589faH6gERNxnUAkWdEhXi75ssgZiT3LEsfATiHA98+xmobvmOpKeYLs3v27Ok5/+eVXcuDAfrl8+bL6c1e91ZmRo0aqgIhC2sWLl8gktDkKZlWY9QqWgNal4H4tRVt0C8O6are8sYKlVZb2IVDh+lTu3LuLnf6/yY2bFNa5U3KMo/XbgCiRCZarrwnqLKvcjMCTdZs2bZA3oY526dKlMhILkeytXuKkcC0Eejwdy3bL0w1sUyk+xiU+IwIRgYhARCAi0BEE/MCpg4rT2GBjC8fVqVOnyebNW+Thgwfy5EmT3mXX2urujbVUOC5ZHPPjGM7xi/MEmzOkYeVsNZrOn7h64wLuqr0FQWFyoi8XjfnjCfQpU6eqcG8qnlQh/+jhw2RelIvSZSfTpPpLCgkpsGOanLObSecw5hOfEYGIwEBFIG35A7WEsVwRgSojoB/sVeZZKTsboLs/D5wAmSm1OlGOxsIsrrnJM+9nboYZXegXxqGdxsJJXyqOEsafTiNguBZjYPgzjHR5dxrHQvhMdl9rsAthnebJDK7gcTP+U9x3RIHd4cOHdcHsBXaqZ02YL88jSxBdEYGIQNUQC否b22rQ1l3rq1pCVWIU5DfIa545F+YboQZn06ZN8sHu92QF1EHyjgp+EPMUEbu1OkoV8NFs4607QQaVvRBUUMXfsKFDcUfVCzkFlXlfffEV7mg7oQI80ttYbX1jZbnK57JrbqrlyyxiuOIoKswf1f/s2LFT3t+9W0/WTZkyGSoMawsSVTxcLA1D8VyxwkKRN/7V1WKxH/8YhypFly5fKpOmTNSTVpMnTZapU6ZK/fBhTkiHOFTHSdMKgR/O/BF2/aOf1q8yR+TafFiye1ozxpihsVoaZjbvV8od8ulpe7H85vNQCY3FCWlL+RkOrPaOXls82z3+aExdJEN56uzo0aPyr//yryqwvnHjZiKoIjXjkM28eXNl85bNshFtjeqd9G5Cvl0ysT9Y3Qt3Xuq07JAGxj8SukxcR4I0vSWlTucdIQPQuTK5fLBcNOwXmqEK8/TJ0/LTTz/L9WvXVQDpAvW33/7wnY5Hm9+2bZv8b//9v0M16QYIzsf498S2C/WlHre02TlA+Wt+pEmgLIFGcs9dhtLxsiipK+SW+hpd+WdH6ctzi6F9HwHri5hT66f6fq47nsOBVLaB9c6szynWb5XzC8M6Xh/6Uox07uVzZfOFdABOsmvjho7ggC5FIbDBSs0OK1askiePm6CK+hI07PyKOcZ9beN2sl8Hfw9/whdueyNJorTQ3y86pCmlFKyTVAlP4eCtm7fkJv4ePXqEO5U5Xy2MwfZYD6HdJIRz0xHnz3eg/p6b9sL6nabQNRvTYzoU2DU2NkJt/YSSp/+6llKMHRGICPR1BKLArq+/oZi/iEBEICLQZxEonNRWllWbXmORFpPSNizyvsLE+eWrl3L1ylU5f/68/AEd8c9w2s4ZpmNxKkshUkUEIgLdg0B3fJx2T04LufIjmIvy48dPkFUrV0MV5lsqtJs4cYI/WYeeBl0NexwnWPB9HDxZbv6jUKsF92w+etos53HH1elTv8rFC5dwOtipzaN6H56w6Qs4Mb8Z48s2FMLGBqgBem3ba0J1mLNmz1S1hRnawEFMCIpHAxg6t7OQMAgELU/rcJFlxvQZMnvWTF10oUjOFiGpSpT41tUCKzDnH9NIRAGWEFlH0/MIePz5PrTeawVAm4D7FQTWT581y+8Xfpcffjgo+w8ckEuXLhet78NHDJcFUDO7ZfNW3F+4Su+LdIVhO/SVCO+d1UsrAB5JHXCE1f91iWmitPJvCMpndbPpSbP8+usZOX70uC7k8SRhfzQsj/VBFIxPwf0369aula045cr7I7kYyPs3X7W2yBAI63iyzhkgohj1x1LHPEcEIgIRgYhA/0HAzZU5Lx82rF64kY7aLnjq7RS07Ny9fx/jVAvGZ5YIPzpXDAcoP1kpVuCQzMUuG否497yHu+x4Xy3nNJy/c2MLn3lDP95lx9NuvPf61q3bEPI9VKFdnrar7vr6et3gNHv2bFWHyXSjiQhEBAYnAlFgNzjfeyx1RKAdBDjLKTMJKoidp83NkpIpEumMNk9Tyr8gsQ56GF9Gy6dZCSuLXy6u0XQ2jUryUW0ay3O+XMX8zc/yYG6L69x+Tc+94uxxOqcyTaOD1ke3hTk6nz1/JldwX8wPhw7KmbNn5Cl2uZczBQvR5YhjWEQgItApBNxHsouatO9OceqpSOyTfAdTJMlaLEzPmjlTVqxcCUHVW7J+wzoIq6AGEh/ivC6DMVlmW7w3FvyopxCKJ/D40d7c1CJHjvyEO7v2yOEjh+Xx48dKSkFduFBu8XviaXm2RfqCNFk+5J07hNeuWi07d+6U13FnyPTp03BvX30RckTgf5zUo2F/rfXBeWuYIo0feKlR/LD1mXeX8bAesaqFUE4x8TQuf06FEb10NzMYKC/+Oib6UL7G3Mcv/ShHaGHK3LMwvzxHl5O0VPnwnnAzb5aPrqSX5VG8PWdxCOuRE9YhJ3ifrl0ITqA16Qn4Tz75VL7/7ju5eeNGIhhKcopk+V7HjR0HFYwLVTi+YP6CnFAYAiWtYBbL5dXGdsuHhSaIZIukwUkJgrDE6gPDdqG8SYAwBrN9875G9nFPnjyR3879hh3+p+QuVGOaekvFQqldjoy/Z+88e/g3xChTPtSdWrwzLIWibK/09PCbb70l//xP/4zTjltk/ITxmlO2T7ZuLYueLOY7gcsKBWtSZ+Bn3mExk5MO8BxCHjQGjnNlfovxyBBEx6BGwOpxWLcHNSA9WPiIfTmwe6LnyqdRpiMtl9WqhoV5svyYn7krSFCj4MeiIop56YABVmzzkydPkXfeeRdzhXr52//6m3yz7xtossCmEoxVnFdyAw3Ha+MDbzX0MmNjlvppFsnchdrShM0z6EsV2MegLWAmvg2oOWPsylWanvELn7xHjvfJNTQ06H12zc1NVRXYEQP+8fQ7BYPTIRjkaT5usosmIhARGJwIRIHd4HzvsdT9FIGe/YAJZj9F8QrD/UyoKF2lniG/7oxTKe9I13kE+C7d+3ST42z9MEFdyJ8L3rz8+cDBA3rCriWjCjOsG6E95BDtEYGIQHcgwNbbv1pdqdzWyCjsUl2Ou7TeeON12bhxg8ydM0eFCC0QtLVgIaBOBU1OOJHHEt/QCRIPHt6HoO5H+frrPXL2zFlVD2j0tuhl7jRW6FNdO7NmvWyp0jPFoUOHQcXOAtm1a5e89+670tjYCDU/uYWAcgyQSrjQYaVIosDCfNRycYUrKQxoRRx4vsJgAHiVQE/bWWQ8VWiHJ8n5Z2MEedFdzLj3USyks36WkiFZLvXOptGReJafUnHCcMtzKdrO+xNnzj2p+ukpTpWeO3dW9kBQ/ffPPsOddX9IK/z5vikYskUznkIdiwWnRtSvhY0LZPaMmTIcQmGG8+SlLUpRWqTtBfzd/Ba1yxfL5rthe9IgX9SKS+wJVfU2YDC+ISKs00zn5csWuXfvHhbiruF+Pu7sD07XhXCHkfuaneUlzijPMGwwqB9Rj7vqlsj2t9+WtyC04x2SLK/dWcd761j2BGffsMgmxDi097Uix/wMHgSKtd/BU/r+WdL4zvjebADp/z2pHyLSymiDduqT2qzY5sPiez8bc2zTFraX4G7pepm/YAFIauQ8TrxxQ1xTUxPmiBy788wcU6tfxs+SKnga9Ln8cv3hzJkzEMTNlMaGRlm6ZGnmvriQDwVn06E5gnObC8jfrVs3ce/ts5Cky3bej8cT8FQhPh6qQpO765jvAvC7nFxkEBGICPRxBKLAro+/oJi9iMDAQoCTLZsx5UsWTsRK0eTjVOIul2Yl8TtCE5ahI/F6i7a6+eU80s2D+f50nzaeWIjzr0BT869WF8iwcKeLuqDiRdMnjp+Q/fsPyJ9Qh/my5WVvgRLTjQhEBAIEtN3mPnCD4H5hrcNH9oTxE2UJFq7fwKL1G2++IUuWLJERI0Zo/qnqbyikSVTTGH4Pu4VsnprDibE63FmHjQT3HtyWY8eO4ZTREajO+1Ue40SOCqgQUXf+9gIixXpyKwdfHftZ7thduHAhFu3flNdef12WrlwhI7AwkDXsmb3xFtebm2cFT0TgAop29eBhVceEcgUcSANPn1wSrOnmPZPQ0NLhHIaRB5Wd78LqRb7g5u8WvYipawu2GMaTZ4d+PCSffvYpxunvdZx+DrXVtTW1qk6WL5yqrXkgc/iw4bJo4WLZsmUrTrOucCe6EK5p8J36hTeXJtOi0VmBs9KFzFo9CquB2fksVFqVRM9YjBc9rTyscEyR9ZIqWnlf7v379+SPy5flxs2bGSF8hlkfdLh35to5+zDezcOyjR43Vra9tk0++OB97fMmTpqo70pV9iLcNVJizULpD9+681d8nJVviKH2dJQ+jHQ+ivEjt44b49rxmDHGwEIgaaMDq1ixNBGBiEAOAW3rOhHg7h133rsW12TwTuX169bI2bNb5acjR+TmrTs6JnN8Yhwb8zgK6ZiFEUp5+QmnjSa2/sCxi4Zjla5OeDr6NTc3y5U/n8oZzOev/HlFNfzYtwHDQzMM99jNnDlDFiyYj/n/TAjtLsjDhw+D/ITUHbezDJyrT8PGmun4o5YCLZexCvJtXvEZEYgIDGwEBo3Azjr2TKc3sN9tLF03IRDrUjFgbWpULCzvV5w2nIPo3C0frUvu4mlWxrKSuJXQVJZa56lsOkoOleSnFI35h/wsVxZmbqMxf06kfZjXk1Tjn/RNw2gHIf7zPhzuYr929Srugbog169dV/UXloI9re+29mf+8RkRiAhUDwFrZ9aH9Jv2lnQuwCIcTDw0I4aPkIWLFuC+uo2ybv1avcidd0K0YtMAF7dZ7iGqujGLJcvPxe3aWnZsNXop/S/Hf5FDP/yI/upisrO2JE7WH5KtdZPZJLru8mnk82CQEA4K7KjGZ+OmTbJ161aZ1zhPhhVTgwla8nGldYzDbAfiPO3PLY0Uc8Tx+dGCOUaJlZaUR8o/n3cyCdNVBmV/wkTLEYZc83EszJ7l+HQmLEwvn0YYRt5heD6sM2mncVgfQpO2efoikP/9whlPRPLd8O/KlT/k2337cLLu7xinr+GEVgsE1Twr6cRmNdwC/4p5bROqjWqcP1/WrFkrc+fOS3eII5TpsQ64euYX37ybOXCLb7S5tMnRcVWvzE9SlEILODIW0/Np5gqu9dCYg/rx40dy8eJFuXDpoqrFLKyTmaQRoxeNKxpRI9yJ4TvgYieL2oZ+awrU376+7XV57733oQp4hr6vtletqhqXmxRo9F0oDoa8Z2hp0OnDaVUYYfFeZJHmwToEz8IEs+o0P5+ui1jq1yceFq4UafQfUAhk+6MBVbQ+X5jewN762d5Iu2svJOnQusYmE9v6vYxnDzjCslSSh1I0xfyNN8N8ePbhy2fhLpAn8pcuXSq7duyUl1CJ+f2Bg8kmmnSkAm8OSG6Qx3CjI6Ibo8jVp6MJkBRuR+GTDB7cbHcXp+tv3r4l97GBePyECX6MC5mIzmXGjx+PU3bT9RQcvyM4v9ZNMDo4Bkw7aR01apSerpuIU3b1w1N19YZkJ9lWHK3/tsmKixgJIwL9CoFBI7DrV28lZjYiMCgRcJMi+94flBB0qdCcymUnll1i102R3USQS2lUO1cDtVMPcF/MOflm7zdQtXUumZB3U/KRbUQgIjAQEbCP9lzZ2McMh7BuPoQHb0Ml3Nvbt8vatWtlwoSJ7iObH/j489/7mdgUWmiPyg99bPylgOIyTt98/fXX8sUXX8j5335L+iv7wM0woKMXu2Ws12sfO3Qo7u2bNVO24bTT7nd3yzYI7CZOdidtCvKrHm5JIx1NYPMnohic+BcpW6nxO4nj4zNqYsLATEBCMcAsfbWQLl+s95Bj4w7CIbq4dffubTl16rT8/e9/lz179siff+AEPNRgsiJw4YxqrPQCSESiwGhInRMOr1q5StasXQOB3Zy0zrT7JlkZrEIUx4mhxUNC5p6HsfJBttCnzoAR1WXev3dffv75Zzn6089y9256d13Itc/Zc0C0vsT9Pmj4vPNmfkOjqr3dvv1tmYd3wBPCFNZpV6nxAEDQrsOyBdA4b9KTHA9LMt/WGVZgfLwC/+gREYgIRAQiAn0EgbBnL5Wloj18KeIO+dv8WcdnTaYNqpyHqcCOKigf4ATbud/O64Y5zk84Q7VxiE8K25LccWDiIOeNDUF8Jr4gMfqAVO+ivnz5EtI6K6NGQS3lxEkFd8cxPxMgzON9dzwFZ/fLvXjxQr8lLN3OPvnNQp6zZs3SE3YjIbyLJiIQERjcCAwagV3/27kzuCtmXy59d9elZOKS/xruJCjGj9G7O+/tZ9GmSKRMpk4aLc1b1r99npEiRaC7sXPvz95iOmVOc2C2ZGHMiBmA7LE+8p8eWIHXnTt35KeffsLddT9AxdYV3RVuPHQV3TvCepyER0tEICLQLQiEH7HdkkBXmVq/EnZ5RTLNkz6zZ8/CvXXLVHiwBDt2KawjaQtP1uHfEHRG6fhjGQsYw/ocd1Q8ePRA76w4ceKE/P7779JEVZg4zVJju2stav4ZsMoHdd5tAIBDkXIb39raOv3oXw3hycb1G2T50mUyZeoUqYVQxY3BOT7k5U9Ft7mDU8qKOIVTEotlSTNcjZXVCOAZWD2否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是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否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否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否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否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否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"); } - .agent-avatar.openclaw { background-color:#ffe9e9; border-color:#fecaca; background-image:url("data:image/png;base64,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否/7hhgpMbBfePDpXfuvqLV1vCFK否b4sPgb0Xn/3SNRX/85GTP17W18dMmG1H1PKozWT4kezcxypu2sQU84Ld/MC1lSphCK5alT196vn8k/uKhUcwkW+HF3r72avz3XC1CpvHrapHz8IE3qEaJv1GENIHse6/iIvCO03EPT+QRgoAAcI否P4fLw/+9mFf/OI/igP7s6wRsALAsi7eduZAMpzwjG2fdVert/phhQRCPubjngm5bh098nEvfxD9i0+PrN781VXHbr7ZueKKJLduB4in6E3bDldR6SXjjeqr1Ejh/ChqHllqQoDATGJsOs4Y4YoXiZZRNsV2XFUgBt+ovoxVono3QrzKEkW4kTAYHPwPrWru89X71371Cx9DxSVNtcYisCgQYPvae8EFf7SuWPywGp/Im3kx1LZx6E23gXhIjftwhJiMQ+PouTT6pcuvrNvP2LvOo/Va5epiaeA/VFS+xfnml3ZkXLReoyuucPfft+O0/Qd3v3l1Of/O4dAfzrXaHpKG8Mn8m1gu4BUH1P5K8/Mbv/GNt9v21YJwWT9YAWBZF286cyQG6p6H3tuoHvwTzETG+uNQ5eMeCYjUaMXL/92+cvmjM9mYhwTw4csue9ERkXeRVx1/B/og66DMRxSRDBFgcgFnEsaCAVyzh29YOAUA9DyESNIdmb4RAESAwDfMdNZChAwzfBczFN/hXLP96XQO7ZtFYOERiM7atiFy6p/Dc否bFGf5xfVXCwGoyy0BGPUcqjbsxqkFAKn/0KBxjL7DoO0gLO7aGw+R7ann3M/U16z/yvBnPnn7TBj049vetXakMfoH0Nf9LhIg+w9oAUCa5mg0PPzh4nHHf3Qyob8jmdZiSSNgBYAlXXwzS3x01uXD1Xzl9TjS95lku07o3es1B651rvnC6MxCTPvCRiflWhhe6kTBf3GC5gtz0CzIPAJMFCQBFB0ovVAI4DsJZKwF0L0lWLUEADrEd2oQQAzFfRm9pnp1R831Li9fffX1dGGNReBQINC84IIzcs3Gv6pyYUtUqaNKe3LoTmf9JZPHmDvqu+yxQQ2AqP/ZAnlRUEA1bzSVU6IQUVVj4xNqqLTuDizz+3tVG/mSc/1nauJolj9s/0Gp8vrIiZ7JaJ3QmdP2P8vkWe8LiIAVABYQ7JUY1WO/8fYzC9XaX4yM119SJI2LVfqa4JH6xGoA0QKQ0ZNQ0tAxL5pYABB1KO3QO+K38qpAjfsfUNdc/T9n0iNiyNZYBGaCAMVY/9Vv/m+5Ne5fRo0xUUsxHAc6NamvWQFWZt3FAoBUeWoB4JT1nZdQYkx9rdVUMFRUB4bUzVXH+dDR//r1f2O41lgE5gMBKwDMB6o2zA4Enr7kkl9fP177C+w6JluMylkDpIBGABAfhvlnvcf2sn4J34wggFnQHEk92Gh8yc+779ryrW9hhuPKNnedeebgKM52LrjuCJZwFnDyw5ATRQNYFV40yPjyHsk7lSo0WPzJqeUVsCUx4F919FUrjheOY3JYww3DA5Vms/H6739/xW/X/NSZbx0cGgw/NZhzL1J+DWdroWMuDB83DgEIQyd3j01LqMW7bARAe3w3u2vyO3biEy1YuXzb03nnQxu/8IVrYt/2ZhGYNwSsADBv0NqAswiw1/Tk689968Zi+W9w1sAmEQIcsqAEsWRvP2US75hPIL2rWBDgngUNCBBBs64GnNzPni4Pnb/pqi8+lPK+xF9+fs45m0YmGpuwp+vhaKwbMWpyGMaaNwOJja4TrQnDaA3mUujLUSNAK7WBS0q+SmChl461kTfvGK3pZ5qY否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否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否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否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否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否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否/dAfUI9iFcPvNN6u3P/c5ajWWqjbxDdVKyr/brsErt05Qm8Levy57DImqQqCe8+zQ+11A+zEB2P7MOwJWAzDvEOsI7t62bfO6auMq5TaLDnb106pBEhISLrIITVTMu+75014bkjK64JQlys3mSu48Fjudk5vpyUmc7WTMSdgMhI1fgs2EbezZo/fRsxpFL+++SlV9+ad3qUehwm9izB3r8dDLAxK4+CeBITTp+eOdfT/2zamiJW45HAFcI6mmuIuJWuo5x6jj/sf7VPnMF6vqhjVqDL29gxgCOMiJfSiOcdxrmHBWGSqrGhjF0a96mVKHHaYqv/iF8qHmFZYDDQ53YeOxwogBzACbzUDlW22Mq9E9O9XJh2PrWDAT2ac+ZhjMqmF0JtvEdzZm1v6RZkLY6yKGyY9hjLlGHr/ISPLK1l/ZoheloUvb5Dp2hXiJpZj4xrD0I3r5mKcxCib7IHr7X7v75+pJlGGTY/4cwuFcAkzok441wqEQwFZBvzo9zBfKB7cclml2K//SmS9RExvXqPFBnB8B5n8AGp9RcKRR3Kso/3HUi+qmEXXMGS9XEcqz8su7lT+BkFCuMn6NwE2PXnIC1UMtqquJA/vUszZsVsPQFpSRILZwk01JcOIdyZvcTFsDoOmJoR8i8ErkREYjO3mEc/u1d/70F4rPbL/ElFqbsu+cdt7RJ376izsesRMC57YouoZmBYCusMy95e+f+KyPrmrWT8PYPwgUiBU4LNXUmiCwYZqmohspPyWN+RqTk9an+RIAsvG3IpyrB+Yvk0cGTStui8Sd+Xwc2P否va6+Dcb7UxzmMp4Hk4X6lhOr+C9pBPHQhkxf+9fzAeBKGATmENQrCBPuclDdnnySOu3D71dPHL1G7ShGqoaVBTUu68NVB+FvokX4mFHG5wk8c8lZhHSshQCw/oQT1J5bbpFJZDwQSHr/1Dcj6Dy6d5y3EOI8WRdrwbFQXB1/5BGYD4DJh7DXwxMmfXGScTOpb9tM72m2/vsnIB1D/3qhC7XlLu29lTlxhR9hoNqLfOMjhU9hsgOD6jFsvHMtJn3egz39J7ArH5m/CMHk/MCeszGoUWPx0ohAoB9RVyggTF7+TWiTqlhhUsXcD27400T43PipgXsFgVZQ/mp4QK3bvFmtO+lEtffOu6j9RyVBGUPDxPkgHIbIQygNuV035EEX8wFclP+R69djPgAmKnLIAPVUsskEJkwPe否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否1R4KC6Hi1DNyKQGDu/GAYgjxrQrSOWxFjaVdw609UHgRf9neAH2EodJc0SEPELZ/BwJgeumnnwza9NlTJgtBaM5a/lDscyVpxYRFtH5M/6fKnm3zX8ofy3mjnWMbGZMve2IvoE5c/ytPdi7X9t94hR0uIEon+xLBM2xdXtFDzw0m/kBQhBMQOOZSAR/Oq/drfbghonFg/OGjq/8l1OFK9mztrNzMELBWaGW5dfe3dt+/S0drBEwPZnQT8wxAXNPhDbUhMu12HOl06flHxIX3oKSGdPOnP9KSY5u5GE4Xu3zQZ5jcfhB7rr5T60U/U2K2/UCN1HM+KvfmLsBMCHPfUJBxEq3udcaiMG2Un478gQIMYRjhmrKke+/I30fuvqWYFe8HHzCH2kbgZRsAuI7U+uvev3SNYYQBLsPmxLievRI5n9GjaRhymEPzWGDm1Qqwbccgsqyka4zIw5X+TLv/VUv7YaxDZ4BCdGIYfM2TZbwLPIvwhHabNsNwGITweMxag/L8j5e+z/JFuDitlDf0xGFkOiGfmi3XbgcQgd/hhHZiqMenI3qfqf6m6Y35lvg4K1HfCZwSjuy9eqnlZjOk27WQxpm1JpSm64go3Vyj8sY+Zv63dypZUDhYusTIOipad3hQHqnpoTjDLTmgv1a5o+3ADBoCe否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否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否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否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否Mn/qxc9ROIGH7B7+utcNll+XmFMGft9gIjHD08t/zkoHmf0s9qz60ADEatjw0aybfiTgCig59bTEgbwmOiPQ1pd759UjyrKqx4luDc/JY0zQ43YzKjvRSGqHEEg2HCj24a4eV8LEMupKVWKkmLcIODPeAQHs9AhH7t8QhKmt8HiB7j4lUXVw5yosxhf5B01tq2BbCOGwCHe07RG+DObRptAcR7ooIvm3WpzbVLgUW1f7wXPde0Yx/N0BxGfIYrMdeQ0cqgqhzzL4UvzY7PwEQIDfpQAIb4JBAX07XWuVXp2ov4GF8jTwVjE89U66X1CPjvpDqIj6f5PH8Lszv+PR6Kypf4kJ9nzEKjcIXpvgD557UFV3YVnIxOvIcG+/7Acw+/Pj/DbDb+sip/WfzcCgCa9gO21C8KlpVwhYYvimHOU32OKbe9kiQU+mu4+5/9e4QXt2jHTHieMuo8NQVqCtQUWAYFOBnp9XpYBWgn7ZXVtAgKO/tHWzNfCW4vR2KBGPt9thzM1AOH0hv/Bh/VJFBpWD6N0mEKpai57AyOyy74AlaI56wORvVX8dnkL49P/GiQZfcwKH+bI16/3PpTlTerPb8C0Gp9qOUOINpzSqlGpgq6evKcmhlvZRUXpoUOkCeACEVNzxEz6zwGIp9cUKe24wYq4CtiA6I1EiqiuvLkq/Kntu9gQkDhmV8ub0f1p/AXrZhic3PL9v/p7uNb2zZuAAyN6BOG1e6aAlcLBeL+rbGn+sXjS+G1ffkowFl0H+8D9PdwSclKhAcYrFh2yOeiVLV3AgU4AYTsNdNsYY14d/DnE5JPjMrWZyYmK0a+/F3vOgdE/ipaASomvIpD9rtKUJZfs382DlcAOtCkacjsYo3WIuo/U1Jg7u4+Jfw6WU2Ba5cCVM706d8eBAMPA3LiRj5nP82uPImoCOxnNTNd/r8GSY6NAGy6Y0V4NPzYP/9irNppAABAAElEQVT4xy/MS4K5VwBYIFZt3osLIL7W3X7v9TrtEXlvfj7LXI4Jp0sedn6A4XmT7XkUw8OGL3wXmpuxOyTCfhe6HeQUUfOm9xbki3XnAaiycs+f/1AOYbHD2yxEYPweZZo9wIdhQ68xsf6Gi9/DJFUMDmH6zFtbvPMfZw7SA55ocDsACBuJHA1d6sLMSEA8LAAxEysduNRJKcyeVaGLzzykwPJg53yskn0sApQW4BxxfdS/omQZo1H/9Aky2vqAsj3lKJ/g77f+1uYC否ddlr9AF7V/VEaVShQrnPGeP8svlBWUwbGSM5E3vXchTRQnSCPMoXtH4nrH/T+fq9wX7/mmKT0a4lNl42Iytim01BENNzfm09iiI65nobwooNAWMR+IiojPJMX8Oi5f2UUP3AprQUQDu9HgS5QGaHXs/V/EiuWXz53DJGYd4x+rl/h0uYFxRJhNn2/gOyBfOaUZBuPPxj7AhziU8WfLbPnlytspXUrolQanjnz+2BfixDj54/6ucOUXP9NZAnekj/kdHQtL/ho/Hi/2R95tMrB/4HaN1h8J9jx21fifCHPUbP8e9ZAypjsx8xUayY6k336qIAUnhMFBpdn/ardtS//U8qgp8NO/否T/fjTmsKzaXVNgERRIGesigNUwrmgKUEaJN1Kw9/HjKgDfCWA4leVw8jZPZQVf9jwwrqY8jVH73fupz75WAJLB7h8lre5FNPu6NB1puBJwcbj8Qrrg9xnVUcJ4uu2fChGQfdjxzKGowTmEWKQrP9P2WKyFBfgETsMqxN/SRyseimc+pwNaNvv0j5//gbYWwJUG05I5ymBME0SmigmyS1z/rSlwhVJA46MM/Xj8lqWrww+OArYq6ou7uLOTdLvdpNPBF0wI4xXm4nMU4rWZgwJcRUkgukfJ+bW94Z/MASHNsq8VgJs/8IFL57vtP7qEPeodvgiF82l7gKhlnbSU2jETBVZx4x8v/uGtf5zx8/v/lm01eO1oJmh14poCNQVqChwcBTQpcovV+CyQqwB4vIzL3hT6nNxJ+Mt2ExrkAM9Lf5jgCFaZrVql8bgal9tW6a8ERiFfSboULy6928/hFOenIspfiofcYX3gNhoAjozSyz/OJo0oUylbdyFjL+J6hfOdZnKu2/2D13z2s+4JxnEZpwjb3woACnh4vfsb3eHgng6+/+yggrz6oYP68UUoW672ddWMPtbYtfchXBXvJ7pGUMZZx4HKgwBrCKWPbRJUJoUFvGTQjHKaHfvZyFnzsLgsvTUWcoVhBBL6C3ukOWhZ2gxqXgdrYB9gdQ2f/uHwXxcXafR7O/6cAff9ed7A0IZN9RkXA9GCUV2dr/hXAy3eQsjwKOapQ64eCqhP53vbZahfyVmKy4DJNVpk3AMWxwGMD3q+ZJ+Ig8JNTg7xRUCCdWKeBaCfhreb0hi/dazM/BF7dmFj/ornpvzW80HmF69jtkLtfP9TfkiTMdCrg5S/iu8WITn6K7+WQ9K9f8/ghRXTsY6aXPcgH/awFTzAb7fV/s0i/NlC9q0A7I6avzZqNX9yNGp2rWhIflZR+9mzoXP5U4tRXg5MKOC3NzaTtbV1SnT77p83/5nQRgdPO83lQK4us6ZATYGaAlNQgHxKh914URndpgRAC+huYAprhwEdIAlrpQ95nNyymSMW2MVDqhCdkp5MrxmTKw5+50gFt1cclI4KSWgkmBWm8tP8PiLLHxSOOB32S/NrJuwDYvhKN8kmiJ1We7c/7P7GpHTTxMXq4DR5cmle97u/+3hn0HwPT3c27WefJqRpiKzqbLN474/DGRfGpwBmdAiOwUJe否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否R+4+C6bpDFZLCwHYC8bXQULD4lm93V5Mzxk9lqgIcr4a8ypAhkEGtXTYGaAjUFagqUUYCCPxT+49JRZthLglgNSG8NRD4Jfwpa/ugPf+NgMSyb9btPtgl/0k9wlEb+STZXhNv48ZrfFmz68c1/b6fV+Y7LIfyJ62VRAFjwKz/8vnfutUdvxJ7JDgkx5HsBti/iRXAgtFMBHqFL3Yp7LjqfT5t+/tjocnPWbz+Eo2flf0QGZRGW09VwdyHUQOHEaBoJcmpu1506jZn/GVudYDgbkwKfeeiXm/lqU1OgpkBNgWVRQAJoXntZeE0LN8R72jxhugFWYwc4GGjbArgzQEZwJQMUzjMC4c94tng3bDfz1zSwWjxKVjg4Lp875xTCcG4s8+Nq+FGyChmyBlw7yWi312689Ts/++lfE34HbVfXcIkYff2HP/y7FzutN+w2W0+454KBjgQ/l2MgTdkg/MktdPgVgYzSKJ0JdEpiS+OIb40C2GwEm50zFg1Ow2/5CZ8qA7XGFpaW2niD2QR53838+X1/l4/64EW/lQ4u9kXeNsI447d08NPmj4bwQqPwMOzac2dtdu3Vva5xTYGaAsugAFcLpAgIvhSA2E7lC+VMYEL+nE0GgwQVTuYn66dc4h03dm6MMgAyApsVkAddvHrYTnbhv4gDjBc67cfPdluvfdv9n/vNCtBLjY7E1FLLKgX+ey996e0ru4Pf6PaT50pwSigP8UykDBvTCA17z1+dqFOkjDOTNiz3hLgSgJP61AxguIffwR495/vYfjDFwjWUUwAo/HlxTwcNxaYc4DGKJvaaViHweRMVFYDN9VU8odiwnwFF56NJO5DwsFBCmWxU38mpirFpfX0UcQ/NvHBDGHSnS2q+p6T19Am175blcwPLk9zaIItzAyT0V30KqvLDPKFb5YRhoTumUxg3jbsqf1l8GV5xf8izoSJGOvdSjHEhZeUrfdwvFC6bzGo/ZtbyZ+2XVfCL/S9fm8ql5H1ywCr88tgUfdmZpGIcQ/YLfzzULJQz5Emmir5V+FXRv4kvqWTCtNP2k7Ly252OB+sncHZWrIkz/a48zti5fpw0eDutS0PhzdP4VjbkCNNwUkjT1OEAf3pffIkTV0sH/sv8fRzm74tXMiMFDcI5znZxVqGH+F47+dSwnbzxB+79y08xyeU0B3IRUFUFf/GLX/zq19/2lF9stLu3Yknla3hFkOsAblHf6VZsMXflg92Y5IGaAkChbxmyzuQW45kFT0uicaAGYP+FlxBR2IM否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否TIFgRM否boLw0uAABEACB5SPA+Sbnn1z4hzusNC5/4/ZdtywficnGGArABPjuqdXf17RZY+UWrEgJSMwKmICnEAkCIAACC0KACybeXM2l/HO9Zhl7V+y/XJCoVSoaUAAmkBzHbb38xv0rzpZAe4002Ql4M1WR0hzHfbbxc6qBWAjP+CcXP6cTqaqkX/zbGbX/fxhycfKzyPxmHf9hmE3SjUkfJp88liMcz8Erp/KcfxpFRdcOraq6Z9W66k133PqNSYZjWWXP4jewFKzXHPvPwkWA+v1YSxFxRBIEQAAEhiTAiqoc3A3QIgWg6Th/Ju9wHS8BKADj5dmT9qKvXrGVVqy41qXtK+MftVjgd3LKu7yr2FWveW4WyUyNtzxPK47in3ot7z+PR4+f5SXABQiUJRAMpJMBdbFrWTnjtx/+FqQ1iPv9OzTfv00FP2/7S+cNf7LtlmvG7y8kMgEoABP8Dg7VG++jdQGCpYC54JAj+DHKA66LQ0CZ4rQ4EUNMQGDSBMKxUryQGjf7swLQdCw67fdM2udllh8rlpYZw+Tift0LT/r6EW33+Q7NR3eCycihzuUabrAfgKx4pc4HjisMeaFLzptN21Tn+ReVK5J08sVe1nVY9xLOYd1LeIZdByDb/6TOrPLt69RBYkswaERT9KwoCbr48Xao8YOVxzKHTr763elkK7HSrzOhhF8nX2cu6SL2fPWFGBS86vkk+Zf1TuWr7n+hm4evm8efFU35TsrGL0uevFfjn/39iwu5hl9O3ufLhT/vpsozqPhs2vXN597+7d8UCbiOn0AyNxu//KWX2DEaf9w1Hc81aVZAsOkPIwl3C5QBQUsPCQBAAASWjkAy/wun/LHiwmOnOqbjd0z7T5cOypQjDAVgwsBP2bz5DtdofIEVgHA8QKgJc2sA729d9GDNe9BZ1P2822Nqg85pxavHXglHaf+5JSB+lhYAByBQnkDv+1XykfKShnXBeR2d3AJGJy+VLgoAr/PfWzOFbPHOqK5R/5e33rbz1mF9g7tiBIqXQMXkwdYAAvs3OO9uW06HP3xukvN82tcyGn7BTYRqM+EAEXgFAiAAAgtFIFjgJ1JIRCk2KY90Dae75tCKqjgmTgAKwMQRG8YLvvrV76zb9U/yfNcalf02jQdwqT7bDfy2aCzA/CTD/O8HP4UEr7AXs06/0f3n30r8LAebyptgh065lnMN26MTCNOOW0P57NDJzf78XfDuqdwq2jUb//D2W2+9a3S/IEFHYH5KHl1MKm7eMmvvo76tNZeLfY+2kKGWAN0AnYpHCcEDARAAgdIEggGKVOnhFgBuFQ2uQQcfPRr2uus7f1FaKBwMRQAKwFDYyjv6zc1f/BEtafnXPLqVF7gQbZc1XxwgAAIgsAwEZHYCx5XHAJhUEeIrTQIMRv63LPtD5+246f5lYFGFOEIBmGIqtNz2X7Yt64fS/88fPo45IjBHXTVzRBVBXVIC0h3UdbtBK4Bfqz108IjV9y8pjplEm7vCcEyRwLUnHX/mo9dbF9aNumGaNp3cBBaOBojP+Y5ryhw8HgE/6NB1I+jMB8ks804nX2fOo5PzjmHn8YvM8v4ndWL9POeslAlDoPVfM09e5179TiTectUNMOV55joZImuYqy78/M0n/B+zkqXzn83zvkEdPx0Tnf/DzvMXf3XrCIi9rKsufFnu5H3YfC9P6etg+TTuKZoB5d否KN+kEVFW29hn11539p3fuygtBW8mRSCZ203KF8jtEXju1hs+3XGNm536itFYXTVIB6BZATwzAM0BPUi4mRqBROE7NV/7Hs3a/7zCvx9K3I2TAO/yx6P+PbdNFSDTOPLIx5D4+q0P3Pm9z47TH8jSE4ACoGc0VhtU4fVXGke8tdXp+oebh4xWt0kacH4tcqwBgDAQAAEQmBGB/pgnajtwHMOxbaPV7Pi2sfG/X5Dd0Dmj0C6+t1AAZpDGv3r91hvbdeef+Adg0nrX4XcPJWAGSQEvQQAEpkwgXPuE+v29jtHpdo1Dnc7Fr9h9B7b7nXI6sHdQAGYAnb3c45jv7PqdwzwCwCa12KKmMBwgAAI6ApxlxU+dfZiPlQCP0YifJYUHXT6BCFoOnfM911tv1Xws+VuS47is8y8JxwwInLplywMdw/jfHq0JQAMA6D8UgBkkA7wEARCYMgHO6XgxNK78GKb1F6/dteu+KQcB3kUEoADM8FOo7z34AdKmv807AgbLBAc1G+4KCLsD5C58mmFAx+A1D7YadI5BdCERg/webgBYPFXGnzKsB5Y5C0V+oSxNln9VUWV9E9MK7+i/H0m3cP5/jR6pAWB33XY/OK04wJ80ASgAaSZTe/OkXbva61b9za7T8D0aDNOlH0VX2RhjaoGBR0QAP4eZfgZjngI407jA8wQBrvXzyb8x3u2vZTn+4Zp9zou/+91W8Bp/ZkIgTJOZeA1PhcA3n3vihXW7cWZn/ZDh+G3D4qmBQWEkBRLvnMUadPlj8Dzc8nKyXGTJl9p1lnmWPPX9sOsAZPsvTFWfomcphKKZGb11AGSmhpiLdc30TXV6Z9menpH5ab4bVxP+DEqFX6fDn+Sf9l/Mle99SP5p/5NBl+8k+bb/NKt1AIp+J7r49WMy+C7lPvq+dVxEWsq9GERX3vOEFzyz+Tsk2R2rZtDGaEbXNj75ujt3vkGxjscpE5Bf25S9hXdxAve1nLcbDfsnPu0SSMtiKAuThNtmxu3jHgRAAASqToCVCDk5rHLfcow9+1ecd1U9/MsQPigAFUjlV9x81Z5HOgffWavz3FibVACHQsVJYxkOKc6LsGQw1+cGndPCL5mPGobS/nNNNH6WFpDvoBfOWOaZ9y5f2gKaxtlLq8ACRlONUtY3oNqb1HOW//I+y1+XVjoLz1o4tsVqGU2n87bzbr/9J1lu8H56BKAATI91rk8nbvnGp2gu4BbLqhs0IIBKfTuYGsgrZfHiGYugBOQCgCEIgMDCEeB8K9juPOhaoKXPfePac3bcddHCRXROIwQFoCIJRxU+v+3WzqXCf51/MD6r1nRwt0BQ4+yFM2wZ6D3O2Y2EXq7TDr74K9dp+S+juKfln+qP2vIhz6q9ST1LTVG9Tso/Va7qrzyr9vCsIyC/HLlm2+fC32m7Ro2yMG7+a7e89Vq3fjbnddmuYDJNApyKOCpC4Jjrr7+709j4btoVy+i4rrHeXjd8N5lVc8aFAwRAAAQqQYBr9nSGg5bDEHGLpZyPOmIjTfX3afAf2XvUkX/6invu2l2JcCMQAQEoABX7EL564rM+vM9wr7M21IwNG1eNWp2a/1l9pv5OLvxZdeYrrx0QrB9QsfAjOCAAAstDIGzZohbLYFdT2uWPMiiu+XOeZdKMplZ3zfDrpnHQ6G774q5vfmR5yMxHTKEAVCydLrjgAu/wSu3Mw7RMcKu1bnTdDnUHcLHPBykBVrgBKFoCQiKl/nJtBccMCYD/DOGP3WtayJdkxtM0fObCn6f9mXRtd1vGIa+51tpknnmJQZOccFSKABqUK5Uc/cB8/cTj/uio9dZfrVLl3yHtukuzA1xKLV5Eg4+6G04P5ImDeYdunm6e20FmquKhkz/sPH7xW5Wv+i/2+td4hkQtJj3lSWwkzcO5CWKWvqr+qzbUef5iLvO4Vfdq+Lm2lHcMOw+dO474UP0P3/b/6sz7否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否PB3qYrLc51lXrtNtaL4ITWtXk04MpQaatxu/F7XBM121Vp/0r2ukT1uu/x9OvxhvHvxHXGeuipf5BYNqepedafjq3OvylOf1S4YNf1V++qzzn+duSpPHeWf5tn/brngNz2eDEPtXFb4nfNCWTxNlhVe/u644Oezazn79jr2+efsvO1TKT/xAgQmRKD/tU7IA4gFgTiBl1239VMPbbR/Zc2pf5kLfBkYyBkpb2rCV8lUubbEGXTeGZc9zH1e4T+MPLgBgSSB8Avjb10UXI/6+rkrijNfvm865uY9m+pPReGfJIenyRNAC8DkGcOHDALXPf05p9fMwx81zM5jOrTKGdf+ed0Arv2vdHjdc4NWP6MugNhXyjVAfi7aF+1ybYuOLE1XVQDifrE7XQ2xaDhY1qAjLT8MqYQjvZZ8UorOf1W+yE1KyX5S3as20QIQEhGu8eZ+NuH04cZ+16xH33HXsGkQbIN28qNmgf1rNecdp9+18xP0SZNNHCAwXQJZ+eJ0QwHflpLAc7d/4xLHsp5MI54v45UDeWBgy6bBUHR26csMFhCKkZHCP/YKtyBQYQJUyJs04yVq2QoUXCrnW6Ts7qs3Nv941X7KGXft/DgK/won4YIHjb49HCAwWwJU9TF3PPUXXvPwqvOBg/X643iA4BEtx6hz3klbnsrI/bgCIDXfuNmgWEgLwCCzvHdSo9PVgCUcebLyzNLydS0ASZ096E/O8UCVL/HKcZIwUt2r8XWDnuyEk8SD6j5hWOBBxgCU7fsX0ar/8r2IeW8WirxQrmK/5380oE/lKM/xFgBmZZGA9Wbb8Fc2BC0Aju/9+EDN+eM37NpxkeIVHkFg6gSSucnUvYeHIECFPDV/Pm3Hf1x0YHXjfztcdz6y7lhuk4YHdKklYNDBbaWS4Q4yxzsQGDcBUQRYbta3FxT49HHKHhi8GVaXWrZWj9xo+DXD6zSszzxirD4Jhf+4UwfyhiWAFoBhycHdxAh8+nnP/rUjm+2/o66B41baPi0eFI6Y5loYjxHgkdj84fL8afmAWSngg+cS8CEDCnkIAGfYas01sJTzRzJ5tQapOikrV3Wflh/q5D3/o1kA8kwxSYhAC0ACR+pB5RsvyNmy2gIg5vz98CHPwp0X65F7Nuf057Thwp7v67yEL107pLx2yKrr+MYht3O7W1859+wdd97IbnCAQFUIZNSxqhI8hGMZCfzrPfc/+Muv/9GFR973cw/ZrvVsyzdXTS74KKP1oyKfp1PxOz否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否hljQLD/zaSv/5KfDplXihgBgKLSQAKwGKmK2JVmECV6qnpQCcKt7RxJd8w0WBTJmoDoEUMezMAJhHYonx4dofV073Cmv8BNABMIkkgc44IVDv3myOQCCoIgIAQ6JW08gJXEACBChKAAlDBREGQQAAEQAAEQGDSBNAFMGnCkD/nBKAjl09A7lsHt/Lc4AIEpksAv9Lp8oZvILDwBHhcX7CiYm/q3cJHGREEgbkkgBaAuUw2BHpaBNRR5+qgddV81HClBrWN24NRAziEe91I/yFEjuAkPvIvfj+CSDgFgTklgBaAOU04BBsEQEAhoEwBVEzxCAIgoBBAC4ACBI/LTYB34EscyjoAvJFM8ihXi1Rr+AtQwU/i4KcgkrRTIN0G8wHUZpOYC3XzpZjR4FttIS/po6aLPIv5YPF4CwLLRAC/hmVKbcQVBEAABEAABCICaAHApwACOQTUGrtqtXQNVhWgPCf6yxekeSAeJ3VLA5XvuHkqeOlRWgLoFnsBpPHgzVIRQAvAUiU3IgsCsyOgFv6zCwl8BgEQYAJoAcB3AAKlCMRqkIE7VYdWzUsJXxDLzES4uImlgNUavtoCsCAAEA0QmAsC8iudi8AikCAAAiAAAiAAAuMhgBaA8XCEFBAYSEC7m91AV3gJAiAAApMngBaAyTOGDyAAAiAAAiBQOQJoAahckiBA0yTgebQ7fGKuf3InOz8+hH1AwHzaY36UI+l3Yow6bVjv9/vP56iznCcvcLw4yEENI2c2gxp/laZqHq0s0EeeWhcgKcH3s8dk+HPEtB9h3IHA+AigBWB8LCEJBEAgIoCyFZ8CCFSfAFoAqp9GCOEcE1BrsDmV4TmOJYIOAiAwjwTQAjCPqYYwgwAIgAAIgMCIBNACMCJAOAeBcRJIzJOn5oJ5bDFI9sLTgnuxSKhDKhLxJZD6rgPUWcb5vUHWchPAr2m50x+xrzKBYKlatTitcoD7YYuV+f2XuAMBEKgUAbQAVCo5EJhFJaCv2ebFnJWAnC318pzCDARAAAQyCKAFIAMMXoMACIAACIDAIhNAC8Aipy7iVp5Aal652pit6syD55lLjV91rQYob54625V1CLy5HA2gxrbAs5Z/ARmwAgIgUIiAmpsVcgRLIAACIAACIAAC800ALQDznX4I/ZwQkBYBddS7Nvi9PevnewyAOvpfG29YAAEQmDgBtABMHDE8AAEQAAEQAIHqEUALQPXSBCGqFAGdjqyaDx4TkBklpc/bNJLudWMIMuVWyCBe+5eWEAlefI0AeZd/HZF3vnCYgsBSEVB/TUsVeUQWBEBgAgSQq0wAKkSCwPgJoAVg/EwhEQSGJ6C0CPTWApyrIQDpwHq9sQwqmrRd1QaeQQAEJkMACsBkuELqPBFIFbph4IPmak0bPNtRB/apzdxlUHBzebxZXO6tuSwnk90ZgzjE+YXcyE2UHvycxp+WqfIf5M/Ad5lKyUDbeAkCC0cACsDCJSkiVIaAb1q0/vzgNmsujNXd/FTZHu17T/9jh9qLH5XckR21+PLNbswt31r0L1QC+GrRCoBcELpztBKg67mkFBEXGs/AJx9mNBAgLOSDV8Ef33cpbuHRZxPCCv5G+MSdWth7vmaFhMh938f+Xd+//jvcgcAyEYACsEypjbgOJBAfpDbQgualFE4aa2ljroEmlAe2Qu+k0IrMuNAbNYxpzyf3xqZIsWKTKKyjWn38XcAtpj2Jmcoz/sz3Yi8rBjpzcTdY7RNTXEFg8QlAAVj8NEYMcwlwPTCnLiht8JkyUiV4ps20ARf2ab/5DbVLGC4rAlTD5cM07OA6D39sv80qAAWVilifsxi+hu0Z8fDzG89Lx0u6O6Tgl2dxG08SbmlQC/y4ubgZeA3CNtAEL0FgKQhAAViKZEYkswhwg73aaJ+wG6uhJt5HD1wADX1Q4Z9eCjgoFgORueEa2tMZOJTCP2oFSIYgzY8LcG7x4IKdlQB5Zndq4a7yV82TfiWfzIHhSdrBEwgsMgEoAIucuoibloBFNWzbS9fCxaFuDIDOXOQMvHL/dVTDD837hX/wHKuh8liFeTnaZt1weooRhZtbMuJdG1FEmHrYUhC9UC7SAhDv/ojfc2HvxUp8Vhji5oq41KM/oPUlZQkvQGCBCUABWODERdT0BPaurJ4RK0P0DhQbritD2BSDwo+qezsaFNdvGnfplroA7i8scsYWf7Jh44dM37/EVqNG4eK4hH/SgUxYj6KfeJd2Ytgj8N+7UhsgEa9AAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAYFYE/j/awMMx78tUzAAAAABJRU5ErkJggg=="); } - .agent-avatar.cursor { background-color:#f3f4f6; border-color:#d1d5db; background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABuwAAAcHCAYAAAAmQlWYAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAG7KADAAQAAAABAAAHBwAAAAC+zNuXAABAAElEQVR4Aey96XobyXKumxQHSaQoiRI1q1utnpbXWn7sM9yCf/h6fCG+Df84d7D/n/3sc55tex972e2e1K15FjWRojie+CLzq8oqFEAABIjpKwmoHCIjI98aAGYgskLQJgIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiMDICc9bz/zWy3tWxCIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACMw4ATjsDmecgYYvAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiMjcGpkPatjERABERABERABERABERABERABERABERABERABERABERABERABERCBIIedTgIREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAERGCEBOexGCF9di4AIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAcdjoHREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAERGCEBOSwGyF8dS0CIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACctjpHBABERABERABERABERABERABERABERABERABERABERABERABERCBERKQw26E8NW1CIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACMhhp3NABERABERABERABERABERABERABERABERABERABERABERABERABEZIQA67EcJX1yIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgh53OAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREYIQE57EYIX12LgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgAiIgBx2OgdEQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREYIQE5LAbIXx1LQIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAIiIAJy2OkcEAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREIEREljot++///u/D//wD//Qb3O1EwEREAEROCECh4eHRU9zc3NFWomTI6BjcHKs1dPkEdD1MXnHTBaLgAiIgAiIgAiMH4H8OxWs099+rccoZyQ+rXxUIgIiIALTRmAU9/2Dg4Pwj//4j+G//bf/1hfOvh12N2/eDH/3d3/XV6dqJAIiIAIicHIERvHhdHKjm5yeeBz0h+HkHDNZejIEeG2gN10fJ8NcvYiACIiACIiACEwfgfw7FUan71WtxzhnJD6tfFQiAiIgAtNGgPf9k7zn7+/vh3/6p3/qG2XfDru+exyThqM4WGMydJkhAmNJgNckjDvJm+ggYND2cbV7XO0aBPtJ0qHjcPTRGvdr6egRSKIfAro2+qGmNiIgAiLQOwF9zvbOTC1EYJII6DvV0UfrOIx4D0Uvx9FztJWSEIHJJsBrRdfJZB/HabF+Es9DPcNuWs4+jUMEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAEREAERGAiCcxshN0kelcn8gyT0SLQJYFJvibHyXb+kgnYx8muLk8Dic04AZ2zM34CaPgiIAIiIAJDJaDP2aHilXIRGCkB/R04fPy6hw6fsXqYDgK6VqbjOGoUoyOgCLvRsVfPIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIhBmNsJuXI49fwWlXx+MyxGRHSIw2QR0L5ns4yfrRUAEBkNA368Gw1FaREAERGBQ99NB6dEREYGTIjBp56z+DjypM0P9iIAIiIAIiMBwCSjCbrh8pV0EREAEREAEREAEREAEREAEREAEZpYAHR8zC0ADFwEREAEREAEREAEREIEuCSjCrktQEhMB/KGpX63pPBABERABERCBkyOQT/LqM/jkuDf1xGOh49BEZ7hlYj9cvoPSzuMEfeNwnYybPYPiLD0iMKkEeE12uj90IzOp45fd00mA52w+uk7nOOXYrhtZttFeBERABGaFgBx2Iz7S+nAa8QHosXt8qdAx6xGaxEVABERABETghAnos3qwwDmpMlit0iYCIjAJBAZxPx2EjklgJRuni8Aoz1vNO0zXuaTRiIAIiIAIiEAvBLQkZi+0JCsCIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACIiACAyagCLsBA5W66SUwyl/YTS9VjUwERKCJAKNZdN9poqOySSEwiPNY18B4HO3jHodBnAs5iUHry3WPY/q4/MdxTMexiccfOsaJzTjZMm5sjnO81VYEpoVAN/eIbmSmhYfGMRoCg/4M7fec7bfdaKipVxEQARE4WQKKsDtZ3upNBERABERABERABERABERABESgTwKa5OsTnJqJgAiIgAiIgAiIgAiIgAiMPQFF2I39IZKBk0Jg0L9UmpRxy04REIHBE9Bk5OCZSuPJE9B5fPLMx7XHXs6Fbr5P9aJvXJnIruMR0DlwPH5qLQIiMHkE+Pmo+9/kHbtxsljnzzgdDdky6QR0X570Izi+9ivCbnyPjSwTAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQARGYAQJjEWFHjzR469ceozvreBx0DPo7BuAGhuLXH79pbaXralqPrMY1LQQGfY0OWt+0cB6ncegYje5o9MJe36dGd5ymr+e5DkM67FDXror6+mnbTmfncl47kNK10ZnVpNQO85hSt86VSTkbZKcIiIAIiIAIDJYAvwtAazffByjfjexgLZW2JgJj4bBrMkxlIjCJBHRjm8SjJptFQAREQAREQATGhYC+S43LkZgVO+rOt3oeHJrKZoWPxikCIjAuBPT5OC5HQnaIgAiIgAiIwHAJjMWSmPziwf1whyztIiACIiACIiACIiACs0xA3zln+ehr7LNFgM626Ri17l3TcRwxCh3L6TmWGokIiIAIiIAIjBsBfc8YtyPSmz1jE2GnE6m3AzcM6Wk/BtMU3jtNYxnGuTxOOqf9uhon1rJFBPohMOhrdND6+hlTv21m5bOlfoxmZdz9nheDbFdnP0jds65rFs5jjhHHOp5LdWdcfYlK1tfLebaw3jWysIt9O31dNO1TRNdOKzieD5PMZli2D0tv61FQiQiMB4FpuB+MB8nhWMHjA+26Pw2HsbSOhsC4n8+92FeX5XVbLx8N6dnrdSwi7GYPu0YsAiIgAiIgAiIgAuNHQF/Ix++YyCIREIFIoPP9qe5E68YZV2/TRDrX01SvslES6HxOjNIy9S0CIiACIlAnoHt2nYjyIjC+BHS9jvbYjE2E3WgxqPduCdDDDvlJu3gnzd5Ox2TSxjLJ502n4zALdTp2s3CUNcZpI9DvdTtpny2DOm7jOm4ex2HZR/3gOKw+BnWMpOdoAtN5DOvOssPsXK3XRUaH7oOLdXMVkTxzXEcddVEP87CBZUcfM0kcj8B0nvPHY6LWIjCrBHQ/GO8jPw3HR9+bx/sck3XDIXCca5fXzHF0DGdUk6FVEXaTcZxkpQiIgAiIgAiIgAiIgAiIgAjMMIHcMTbDGDR0ERABERABERABERABERCBqSWgCLupPbTDGVg3nnF50YfDfpK1dnPeTPL4ptl2HbtpProa27QSGNR1y89zcBqUzmllPoxxDZv5sPUPg8k46zyJ6yXvgyym6zh245DrLFONqiOl+r6zjrp0mWe7ehRdPV+2UKo9gfx8HvfzmLaOu53taatGBERABIZDgPdHaJ/me+Q0j204Z8ZgtPL8Ev/B8DxJLTpmx6OtCLvj8VNrERABERABERABERABERABERABERABERABERABERABERABERABETgWAUXYHQufGjcRkBe9iYrKREAEREAERGCyCOjzfLKOl6wdLYGTuF6a+piuXx4zUo2RbPkxRV1TOWU61VHm6H18Bl6U6y5a72idkJjM40SmPC75WFmXl/lIU0G9PtcR66p8KZ/L1XUPLs/jAY1N11W9p25k6m2UFwEREIFZIKD74ywc5dGNUefX6Nir59ESUITdaPmrdxEQAREQAREQAREQAREQAREQgbYETsaJ07b7AVbkjqIBqh2CKjrQhqBaKkVABERABERABERABERAB否SUIRdWzSqEIHxJ8A/+vWrk/E/VrJQBERABERABERABAZBIEaBRSdWNUppENrHQUc7B127ctjci4OJevI25JmX1VmwXb08b9MsUx6nXLauZxR52pPbzTLaU8+zHHu2owz3uQzS7crrcieT199OJ8NZvYiACIiACIiACIiACPROQA673pmphQiIgAiIgAiIgAiIgAiIgAiMiACdJOh+vBwhIwIyAd1OynHK7eR5lpcRNeuYb7fP5Zr0tGunchEQAREQAREQAREQARGYTQJy2E3wcVd01QQfvCGaPuvnxayPf4inllSLgAiIQE8Eerkf9yLbkxESFoGREqCDoslpwbImmW6NRttu21MOutl3Uz+Uq8uw/Kj2TTpnqYycyI/5JgaUYR1l8/J6Gf否QzmUHabn5M3ZM9lQ1ySXl7MtyrQNigA/y6BPUXyDoio9IiACIiACIiACInDyBPi9bhTf6eSwO/njPfAecQKN4uQZ+ECksGcC9ePOm0nPitRABERABERABEZEQJ9dIwKvbieWQP37X3UgdNR065ChfFVL1eHTro59tNNRbzcu+SZ7m8rq9tZlOP66HPJ12U4ynfTU20GWurnPZfL6vPw46U721W2gLMuxZxn3rDuOTWorAiIgAiIgAiIgAiIgAtNJ4NR0Dmu2RtX5j/bZYjHro9W5MOtngMYvAiIgApNHQJ9dk3fMZLEIiMC4EujXGdZvu3HlILtEQAREQAREQAREQAREYDIJKMJuMo+bWz3uE1z8xfy42znBp0Cj6bPOe9bH33hSqFAEREAERkCgl/txL7IjGIq6HFsCdDIwcgeGNpW1G0Bdtp7P9dV15H3W5VjHfd62XlbP57LHSdfH0tQPZfJ+KFevYzlkWcd93n6a02TAcXPP8nLsh4exbm6uta6UYnvuKcs8JOtlyDeV2VGJa2Em9ZRBtklfEqvsIJe3Y2XenmWU457llGU58vUy1rEN9k1lef1kpKvHYDJslpUiIAIiIAIiME0ENBc9TUdztGMZ5fc6RdiN9tirdxEQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQAREQgRknoAi7dALQA4/sKD2o03Q+9sJR/Ad75MVzsDylbToI6LoYn+PIY9HL58T4WC9LRGByCPBag8XTeb01ReU0lbU7ZnXZeh7tmsqa9HUr19T2OGX1fplnVBP2LKv3w3LK1uuRp0xe11SG+lzPUTLt6vN+mO5GL2XzPdu164v1aNNOJtfH9NGynSPrqnrKaDyWd9rnNlOuqYx12Of2HiV7VD31HiXXVF8va7IrL0NfbFMvpx3ai4AIiEBJgN97pvM7TzlOpURAB否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否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否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否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否FM966grb886llGGe5RThmX1PNtqLwLTRaDlPjVdw9否REAERGAoBPhdbxjK5bAbBlXpFAEREAEREAERmFACmJzhRM1kDcEdbWYyp5folM否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否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否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否9x451qaOOkQUXL17QSLwjmDqzqalRnXtcG6+xsUnHdFMT1sBD9F0NrpGSjclohLSmMb/AVVJkZQlKW7+UldXlXB8DgeLZ5VhOxz11FYfW3pq9+nIDX3DoxHqfBzsOapToAUTdHTl2TLoxFfErTD/8DoZ4RtLYOLSxz+tiuW15juUkeL4j4AhsVQSoC+xFAJ1WfGxCxvEZGBiU15hytxszJHB9X04x/uWXX0kHXl7h2rGNiLbT6adNMUEflasT02TcuybZ2DHgeG4sni7NEXAEHAFHwBHYfAR2ncPOfmzaj8/Nh9hrcAQcAUdgZyLgenSp82qGh0o/ko22FI/JLuUNcRQhz3JSY7xJLJbOjR85h9lNyFtuOAENrFoHM4tvWMfKOQZoDMbL1ipEjTnw03GftpWOD0a7PcOaSY8eP5Jnz57J4+4nMtA/oNFLQ4NDiKgbU1kNmE7wCKLpruBNbTrorl2/rs6QzsOdMDJ3CqcepMFo8t2EDMFIxHXnXuDT8+KZDA0PyQCmd5sYn1THHaPlJjANJqd7mxjHtJiIqqMhetEcjAZUhb0auUHPEQt9JGuOfTHB3CpbKsgEGM3SVYqulLxBYlZa3Zbms+e90rENRyzHrBELgPGccyOPbUazk25lyTENOZxOkFOtDgz0Y0rMFvnTn9rhBGnWqcHa29ulC44QRoeehMP59Omz6nzmtJlpHVaXVat+OxBZB6+jrJmxWRy+bAf5rX3kCTIjUxy5IT/2yypa0T6rVVuRNyIpnLIk5I06NDw2St5a5CzdBgPAMF9pDSyHM1ulmA1PSstY7CBWmWnYSGeZbGyCxrTpYI4LK54fiL708Nn1z+VgewfWAf1CHjx4IP/xH/8uN3/8Ud4g4ljrR1mNiMFe16VSZa9ZJV9pmy3DarUxanTfOwKOwEdGIOoRa0WmOyKB+qLAojnkox6wzcoxMncUjrru7vCSC9e4u3X7tq5t9+WXX2JGhFPQN62CecujnHyNW767YrqClbLerO5KisUqz/ZRu2WFmJEnykUwL9OImRQ7MP5q+tn4tu/e+p5jV否Xyye1Gk9pid2csmug0jPdbsBlt/Z7N5zbD93H3X4tEW9i4NfUhx55q6/vQ56jXeewW/3p8BKOgCPgCDgCjsBaEeAP3w/zgzf9ib1ca9driMh6hEppaNkDB9r74LWTxfkFWcDxzMyMTCOSbRoRSn19r+Xhg4dy585duXnrFt7CfiqvX7/W9cT4cMp1lo4eOyqt+1o1ku7M2TNyDc66ixcvyqnTp3QaN64r否eop56eZ3CSDMggnHUvXsBRB1nPQOvu7pYBrKcyAqMR6w/tCQ+/rMN+CNjeDETLGZOzvi4H6nryP9wwWU8rd0RZO//LdYZOCp57usv4YL4HU3XtAYHOXo4ZnjJ+ZjHOZ2amNTo08JEqOqZbEMl09OhRROAdk/MXLsili5fgfL6AqNEuNV7SwVffUKe8dbruXa2u1bgXFszwp6JKVUgyIIt9sbG8sT8kQn9iS3y3YQiE0bVh4ijIxsYyp4zjZmFxQWrqauRYFxzKWJ/xs88/k88+/VTaWvfJvn0tcNrdxAsOiIbGyw+cvphG+uJ429C2uzBHwBHYFggU9YDd98xpxxekHj9+jBcAHmL/RIbfDMNx9xnugeexjmaH1NbV4lOvEb/V7lXr046mCCvBuVReJX6nOQKOgCPgCDgCjoAj8HEQ2HUOu2oPhh8Hfq/VEXAEHAFHYGchsIylVE38xR4vVaY0r8TUkFg01AGXZMaZ9bQ2kt9HDx0dDqUS87YEIwwdW0YLnHtQKBRnZBLdF4v6Ry7EXmBNkr1SQ4a4XhKNNmNvR+TBo0fy5MkTrB2HdekQUff8+XPpxVpyLzA15egYoulQEacKpPPi9OkzmOrymk4tyKkvOw51IFqpWdvyCuvOPYLxh0agITjkBvoH1Vk3Ojaqzrk3Q29kBOuMvX37Vt7BOchovhVtBkTW3wqlVsJToVhFUqV6KtEqFnbih0ZAowji+ec1sXcPHXX0ifAaCU48PeYVoRGopSeT43AaUUqcUnMIU7NyurD79+9jfbtDOk0YI0UPI2r0CD4nTpxABN4pnUqzZR+i73CdxUtWu23HGuHEunkVk6gNivVaWtsX0dL2J7LYEcticU0G52PQDOyRbVrYEronJc9PdElkTcSbuJLyOy9RxChNG1IpzRAo0irxBpqdnRLgKSYVEYuH3zghoWPGxHIf+XXMUqdHpzD1N53TfGnixInj8rd/93e6JuPlS5dVf//88894uaIP69uNaePJrzJKTrZmlX2lo6ks0wmOgCOw5RAwlaE否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否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否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否iFgCKwHAsp71orvrMczWBkxAitpT+T96quv6Nq1azHBJfjKZoXdEp7JkhoChoAhYAhsUARUYOLxnJLqDHWVYkWtlFV2MzPTNDY2xh/egqallW7fuU0//3yWrvNZEs0tzTzLdThSYHHuHf5mZ7GdjTvDRo18USdL9bDA+LdB4bXHMgQMAUOgPBAowJdxFlAlby82OTlFT3m74xFezdDd0y3XDG91OMOrrvft3UdVVdVyjhDS4g9CAWJEthMLeT375VbLWy10XJHOOOtphsWuVjEbmU7UJN4j+GWBCLmuxm9M/+E/XgGDJOmqKtq3bz+d4DOl8F5UptI0wYN1mMyT4Yk8ko4zox8Q9js2Mp72bIaAIbB0BKCLCAvyX8JnmHcI74F04XBMEMFq7xY+N/sRTyKcmBgXvtLQ0EgvvPArXnlXS1XVVby9v5tYEE1W5Opgq+RwosjSawgihXNlsczCySzGEDAEDAFDwBAwBNYZARuwW2fArbjFI6AKcmQ0X3xWS2kIGAIlj0Ae7TEZBG0XBjN+FnzjirPq4MSYxoNsVMnbzxAU3EpeNYHtZ8blHCMc9H7n7h3C+TTtbe28yqKTent6aWyUt6LJuIE5IeSoSjm8Vo9X6nkX1sVrs/H5RZrIroaAIWAIGAJFQUCtjHr1lUDfkRdbs3MG01GevAED6SQPxrS2ttAvly7RF7//A33wwYe0f+v+qOq8PlsGcnC0net3YqDGRcuV/XNeCITiISLAsiSsSv40cWrxaaIwYyKJ3S4eAcDpBl3RWMjnjOWwmKMNXRB6DM5h0O7Agefpo9/8llK8NSZWYVbxFWdN4cwpZMJqPPQ/Zv2EHhjf4YSW89q3IWAIbGIERD4I8wFf8JwhYhAI8ZGM0TRPBgCfuXPnLo3yZJLOJx30+eef0+8++zfatXt3NLCmK3uh+UAeCYWAJuB2ofBphGdOQXmIhdMUUlcXBLbonF41kQ9e1GUlefMWAIILVUQLBYH505bqhAuzceVtfAs0BDYMAqXKezYMwEV4EKcbrn/BNmC3/phbiYaAIWAIlDkC8ytICz9cqGwhtaOng3JyJ9Y1p+YiheTAl2iYnIKvUHjwyczNyOzV6alpevzoEbW2t9L3331Pl9gwe+XqdVltN8sz6JEWhro0G+AyfpY9VtahfDXgoYjkTNZIsULxklprzDfmDAFDwBAwBNYfAciIPE7lgjN0cgJONjk+SR2PO2R7zLa2h7y6YYq3Iauid+kEbW1s4G0R63nyB4h54yrTFiOpyBkthAlpmZGlM8ijyQpdRYi5SCXjBFuhDBa+EALJN0C6CMiEpuKLNJNvq0iO8+QeDK1iRT1au4oH7fbs2UsnTrzLxvQpmfgzNTUjg7tTvEpTCOF9SFYmWXgy3u4NAUNg0yCg/CX5wMImIoYfxw4ODtD否SHq6uqm4aFh2ra1kY4df4cad2yjmpoakT9CkxmPSCVWTHTSYKSjeKaUZEWSzjNDKTqHeTldJq5NMX15KpfDbZNPuLT6FsvIurRaWmpDwBDYaAgY79k4LVrMtrQBu43zHtmTGAKGgCFQhghAFXWugjVL+EPFFwJSwjCwBqOZbhfDK+1S7Ieb4nPqWnmbmfv379PFixeovb2drl+/QR0dHdTf3y9p8IVzaWSgLyoxipIQURuRhIPVPKd1i1OazxAwBAwBQ6DUEPDjMlItGCnFyOmGZkSmzM649dO9fIbQST7HdJi3Rm5uaaKjbx+lN958k3bu2CmGUqxsmMEWiSxfIGOSdHOfGxJDpEduVL4QEyr5UFnzMCfXtX/h+hraamleVbd//wH67W8/5gG7Kl5dN8arYKbp0aPHcqYdUmt/A1vbWROueXNZAYZAeSOgzEWfImAa0HGwNTNRhnr7+ug0n7Gd4Z0/PvtdG3366af08uHD0aCdnLXKPEe2evY6j2opMUnlblpY4WuyWoVTLjIGlVh1oossu4w5sei26KiYMwQMAUPAEDAE5kHABuzmAceiiotAMUeyi/vkVvpaI6ADQvaOLRXppFamyoaG6z3oJsO8VsfBGoMBOITGudgnAd6Y5gfYXDqEcaQoro7CLG8rM8ZnQnR1PaVTp07RGVZ6f+bPs2fPZMZqhhXiqsq0M9bOuRV2M/MoSNGsVa4hStD3BE+jLq6rhtjVEDAEDAFDoFgI6ICaXkMWH/JwyPtUKiWrsXGWKeTG445H1Pm0k8Pm6K0336I9e/cw46+gDA/uVaRZCvil16A9hxUOcsWTQkIgTC5Zg3ouJM93PuGRLyxPVgtaAIEEjq51fJ4gDuF4JzAoi/cBE4A4hD+zVM3nR+3du5/effddGhjol3PtYETHxJ8p3k416i/GL4EvwC6GgCFgCORBQBkRWIwwnyAN3+MMbewQ8vDhQ+Y5A9T7rE9WfldWpuj555+nui11wncwaEdy1qrjW46Km8wIv1t5p4UhQELxlePAvuACtpi4cfFL+s4itqSci0ysz6YF6XWR2Us0WSRTSrR+Vq1sBLQ/ae2WjYvdGQKGw否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否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否HkdURGpunrhpVTld9ftRZDdHrVf+sstFF4785Btv15jgAZxdie3JfuYoKt80p+nvbd+yg3378CWp否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否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否UouSQEMgQYLBbbcDr9tXc6gNeHnOW1IYUZZEvoPjswmw0xdBEEMNE5RE7BvVsExEmiVJN9eWXX4Z3fvGL8L3vfS/8+tf/Gj7//IswMzNj/i2gfqyd8Hg1QUrthSOT7gmBhEBCICFwFBBgEQ+pg1YtYmLi4t5jW9xjt36hlzX39LNHBDSWyMctiorFape0G9HC65kzI1Kl2F2WCGMRxh9cPn/Ri/mxX4+QMVG0jmnIPnt172Xx8kI0WVQb3HZjURgy7Ti401JPenZMRI0AYOmXeyO/Bn+RaD2b6kt5Qu1ct1TO4YlynpyctLOlkHqJ5YmTxWBRlp48hXRPCBxNBGJbobZNfRDf1ZKk6mBonzhxUioezxrTbkXt48Liokkne9uH31pmQ1spz6Rg80HNCWlfT548GXp7e00KDWlnPl9Xl+npWBpbJVaLkD26kbTnpRgVdiuSnlsWY25YWF25ciVcvnwltEkNb4fUfrIphE0Bi8JtWkw7zrajXcGeP/LHxlUsSWN1edXOVB2UpDeYINWI6k1cmS+bJ4UtdhQlusr7syKdMWC5zdZvlmtLe2u/2/FRbFGJO5mEQEKg2REotS/NTmmi77AhQP+3Fwm7OFM+bLk+5PRSaDQKySQEEgLNiQBfpw+3K79Ut99IuZY/Msc8vL5zJoJz0t8/MXE/vP7aa+HHP/5x+Onbb8fd7Vkk7GyPk5l4tgLWxSaC9gL3ZBICCYGEQEIgIXAUEKBfo+/z8TC78ennYlenJT51pKnfq09JO45Il7S0HNOC8Uq4cfNGePTuI1Nvhv03v/lNk5bo6Gi3RL1cqlKwg+FILMeqsdTdkrpDek+K8fyadIpGrRQLatyHR06H7/zn74QunWc3MTmhsv7KVK4Djn1j9m2VlrbzMeuTAlzK5xOJQGwH2RhyXIyyVvsWHkrqiw0MMOxOnx6R1Ng54xXRN01MTdg5kKgVrtXk0eYU2534XWqjpizXJVm2shb7Ns5tg0GH2k0knGHmucSft7feVjdXAdFuqG0RvXOzc+GLTz8Pv3//w3D54tXw9NPPGOPt29/+tqlX7uruDLM6o/2zzz4z/0jWAR7aYx4fB8dH4aM/fBTGJ8bDI7339HabWk3bbGAgMwY4autkqYVtrvqcqEkIJAQSAocHgT2fYccAwwcZ+53tw96BO1aNwmu/yyPFvzUCPvA+jGXee否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否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否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否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否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否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否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否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否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否Gwxf0urMBZSLNh+ERqMH/3X78Lr/791bBXqihw42gsXSlgdssDOAwWNv0sgGNunn4NYNnwit6F3QbVwjF5E4bNgN4cDNqjjOGyYyE/Q/cC/PhHPw7/8r/+OSxcND/s3L5DcP49fCBi3a7de+ygYbTUZgDL7TscYuXg1MFafQxZDFibUcVZ3ZX2V9EvSkzMbp0dtj6yNaxb/0A4IQ7W115/Lezd8124qTuprH0WcdVSLLHUGvQqvIYAFrxMBvMQgKcCocJAf2DA5g/Ge53kodqQB8P81FsT0+6cCgQoDnzgVF+0aHFYtnyF3afCYRBEhzmzdW+LInNweFL3pfghEYdVQ2uU6Fy23IXyNzMc1DPvQ9BCgn66DrwgYC3RQeNsHTiOEZf9WUkZ7tq9yw4V43gUU2s6TpJflqcz3USJOAbYFFOAxXEtrRySs0sUwC2/etXqsGbNGqsjovmam/WIE+/UapROVI1pWcvOIE5yLiF3XmU7fvy4ER2R5CRfDomR4Nu4aVP4kdYUP/7xj02agLvqyAfa4PfAqDbpa5GoBy1bq6RykpebVBr/7Pqd4YmAdZ/FB5ZInCQAGg6oF1MZJ4IKqjGRwODpV6NCxvqK9eB1Ae6R1Dp18qQReOaq/SxXX4IYGussQsWhcqwVvmPdF22zKGsMG0PU7CPT1qDQ3S6ox/WW598kUO/GOh/Dr4cylXBeB9Z/Yn0gVUPb4o6pAwfjPYkWufqpMNBDDHgf5+12ZnVvpRCVLooIw32xXHPwwPoHjSnhlBhlziN5JmmqGze5885j9BCQvowmULw/2ds7lvKAsfWayjKjZabdjQthDWIaDLFxXcO++5bK/72k+Vvsvlbm4MOHDhlzEJJ4DJz1K5/bRkA/evSICHZjwxIxraLuH8N8yR288+bNMWbV3doHQ9iCYYLH5jKFcXgtUjd+GL+5v26rGFhapHnABw8bTyh/VwmmqrL1AoQ31hkpwi1JyXFP7OL2RWKevBI+/fTT8NVXX5n03dq1DxjMlAmcEJ8xibvqKAvqUiODyvcmFcy8fuzYUVu3tXd0GE4A65baClKIxLeZU/iM8zLjXDcKb0G6HbC7CVbhKgxUGKgwMCAY8PU5mfm6fUAyHuaZgLeKYDfMK/FO4Fcd4k4YGon+vmItL+yafTcLL9yUo3SFrhS2LkrdR5ac73Fyf1asbAjSIh5CmB246I6vTz79JLz88svhZd1b9+2339omgYNKOCLjgjtttJRG/HNAG2XkfqXyAYuePAWBE91Il4W2PiG4Ye4TAQ7Ou3UPrBcX/A/Dlq1bdBA3JeyRekP04H/0wYfa7BwO13ToyC4QVWd2oFfa8sS0SDmamo085JY7eKB78G24yMqd11PmPOBWqyLaR15PstuBcGp+HL63L2oPmzdvCitWLtfh5v1h546d4bNPPhcn5gWD2cfqFGVILGaAyeHKEcumlQONRn55uMpeYWA4Y+C2TpBZKNPeOSgaL2IN30jD+sajp32gbrwoIYl5Z4xUSM2dN08HSO2mqoq8xwkGpJgYT5j3kDS5ICnzS5LIs3mzSX8dKmNlqZjFmFkeZ8A3j6mTvHbDDgMnTBQBc+GCMG/+PEk9TDMJ9i+//NI420nX5upSBl7ucvoEw6/h3Jo7qx5iODkqA3DMAR3Eugd0iIcqLdJmXmfcdhgUWhEVM3tUHHMbP36CHWRSv2d1sHdYhFcOMpFumNU6S+q+否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否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否Blc+T4VBgOk0iDGQNRCNfN8VD9KNeZ0Se2eFLFu//4Ddt/ZKDgcZJzO51Vmjvopf7t7X7+9H8UhTLlS7Qx0lIu3DJJv48dNMKaKSVL5CUMS+ysjUioIUm0QlIjMuoP5FglDiFGHDx+y9mVpaXK7/z5dHyEi1oVLUj+sPc7ixYvC8mUi2ImZiPDMqy0zZhpzzQFJxUftOdrj68/SiCAZXOUfCGLM02gU2LxpU1iruwT9HlJgKyTsZMfEcTphuoTwfIxIgS0G6fAHkxM44K7igwcPhR0Q7CRVeFp1PG78uLBq9eowq7W1aCNkSZtgHdIilZ0ndEffzl27RLg8oflP9w5rn3RKUua0Hwi9y5Yut34Kw3KUOmTerJ9ZYp0ZdOmnVIjCq5l7ESCz9FfYLIvKWmGgwkCFgXsIA772pMid5pY+wgN5VAS7PkJmlczQxkB/daKhV+quFmRd+d19SQpubkUlZVtwWhadV935UpSBB2kF7vmCq5xDngMHD4b33/+HDUi89+vAEQmkfCAEwpiHMvGikFXn7AhKYHtxLOTBcbM05GJuWVzcR4+Jm5ObOhiEux0Owccee0xSUs+HRx7ZKu76iWHbt1+Fl19+Obzzzns6GN1vB7ZsUoC1E7x53jUoDK5+/6GA/vR7Zn2TAVXGUzbUDSbiV5ye2rghUcImDhVofar+rQnOaDEOR2o9RdujreAHfLw5CHzih0+EZ555NixavDAcPXZEkqNvhjffeEt3QJzr3E5SfOI2KH4ZHf3/LTioiBwW7vCaPGWKEew4MGND7pv+rBv1P2xVDhUGBhADdAU3zFWoxIKJg/kJ6QUOiy1IFi6P43G787b+X4rMGDdBnO/Tpk0VZ/tMqfOT2sQJ4xkM9cT7X5i3JkyYpDFxnMFz4YLUTUmiF4aXmzeTBKCIRLlxcBvlmYcbSnbWDEjSMc6i0nDR4nZTizlFhEzuwT165IgRLDkYMyaOJsBDpBuvg21UfLXq4K29vT0s1cEiXPsbNm3UHbVbww9+8Kiex8Kjjz5md+ZwSLhx48aw4aGHwjqFW71qdeho71D+szQfRQJqzI51AMNnnBN8EvYxErwzsto7fjDUFuHdLzaqGLtRMWKckg+OMpYGaZa842eEBBg9RIpWC50i否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否rI9Rbl62as9j2trbPsTkjmC9SNI0344QcfSqpySuhoX2zMP0gLGkyKSHQIn+PHjxUx8mjYvXuvzTWoGGU98s2334ZRSrdjyRIRHjeZlJ7h2OKm+pE9h9wZxhyu6l1hoMJAhYEKAxUGuosBdrP/X3cD5+E2bNgQfvGLX+ROlb0JBvIFCwuYytyLGKDe/blT+T1cvtxztzvFjf40M3+6iuGp+ttz9I2AtdfUZrXe18GWFuNarOPOxobDmtffeCP89r/+K7z26qt2UTUEmLJhCWsXeFtaceNkeXmG5Qjpu1YGDlniQjh61TZfCB1w+ApXqnYHxvmGiqsXpALzN7/5Tdj6g60olLG79X7329+FN15/U4v2c7aaJpV4h1ATAPrA2XFZTspxzrvbRggp0rMNmPCK9IEeTD7WdDvNfgpobURwRXitauIhjTg/UUNCBVzS/SW0F7gVCaeffoJGyZI8P2WDU3Jmw7Zq1SpJZT4WnnnmmdCuzdyxY8fCG2++GV57/XXZj5uanBzPHj2256xtl/MZxG8kVzgUh2ubQ+ybUknHgetVqf0ZLGP1TY0Igd6Gra0IoBy/gwVfle/IxQAHTre+v2kqnVpEAIKYDXGM+5Qw9JGetsFmUxp5MtC0L1oc1q1fZ4Qm8rD7dWzcU0fQN/E5ZIMIdUsEOji8z549o/561cZK4Bt2xgfJAvD7TP0lRLtLuo9s1qxZYf16HQZOm27EOSSpTkjC4eJFEVbF6T5//oLw8JaHw49/9Gz4yU9+YswUjz3+WHjooQ0mLbdo4WK7Y62Vu/DELc8dnUjwRZNqhJceoTirWwBTrUj6EVv8NQebj6IvIQgTxyo7eJOHzyVUHeHieGZBU9habPMnTPSu/wUgGR/76j0bf5G3/SWYrPl0ChrT9VwhYtlah/IrtuHBWhvhlJpeBoMkE2iF1hIhIrPAklFL1ZrvPpNEQfLnpO722bFzh0keELeZiXhrWPJmUbrnrkL7fVQcyi6XpOXChQti37X1RCwPidX6coQDCQ4VjP+a4QM38FBzHSBbylxYjsYhcPccjLKfh3F3wub2Rt+N0nO3Mgy4527ltBvFUxjrGDEsv7QkN7G9WgsW8Rdp2nOmfu7s2XPGzGBqVmMkj1L0qZJz4T8SLPSVeIeY1DZqfYb2kkce/YHU/K4X08YFMXzsNMIK61SW/D6WGU4iUkcCGvq1DBHH9xnjHYww16Uec86cubrHdGFo0TzE/WZXrkoVotRRo6YViWIYbjBxJEy9wepKmO8jvHu75o3xt9ktG/uxnmijc+qSlIfxjYceFVVo3zSp4/mSQEfyuPC3RBnRaWdxb8bam3teuasONZnHTx7XvXaHa4yJSpf+CHMMBEBUUNP+INiRN5LWZ7Q+OY1EvOZrvtnPkb7t+a008Yd1Fff3ztXdgcz3XDvAOscN6SUQoxNlLCZcfMzX/Ahb否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否bMEkMNqrMPHTqkeXe/9ir1TGTEM5OQHdu/O3b/bdFTYtQgj6VVZBDTsup否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否PbJ6Xj2KfFeRinr3Sryr2FXgCgMVBioMDA8MMNZVBLte1JVPFiRhk3wv0qqiVhjoPgZYEJVNI7dymMbfxVonreXslSVny6/su5xK3vZZ4EapMC2Ntehkg7JNUkb/+Z+/DX/961/t4mXc6Dt5PNL0g564EpWDry1tAV3KNcFjGxx5WRlwg9JYxIv90vNhcwE39zzpzzepqF/+Krzwwgu2GeEuvRcl+fc/f/xjePedd6XC5BSpGpw5sQ43z7MEUcPPArcNfSN8bF6AC8IIB2AcjEUcwc5IoTg1S4VKB2hepibJGm4Jw8OGnfThTpwsKQSkLjis9TsV7qY8zfLrK3dgQS0ImxvuHZqkOxRoK6gcgeuTTREY4amZ+q+ae+9sjhd7Gy6Vr9o0dQGMXHr+7//27+FnL/zMNugchO/evTt8/Omn4b133wt7dXDE/RVlc6c2UQ4/GN9sGJHKQR0O91lcvHjBVJddunh58Ii8II6HQUaGvsKGmIN5Nvq0jYpgNxitZWTmeacxllJzAIPqK+OeFoc097RAtMsPmpphx9P3dx7Ox4jaGpMxLo5zMJ60ts4Ka9bo0GrhYjvIsjSsa3AkJWKJ/nDjMXWEio2kuKmqgkAiOPcfOGDzDIezbnzq9O8h9U5g1qCtQUcR4GYfPToS7ubNW5CkvuI9Q4T0ckI8YjplPYC0Q8Qdh3nRkL6P/WmosWEneXd6Wfw61whhTMdrLbrVBevFR1ZldalkGsrq3Jt+5GCBk4QEDglpbrn3JTH5fLNtW/jHB/8waYILmhNYp9D229pm29oCBi077C9laAe7pKd5xedQagBiM/f+QIg+IpVxSIBip46MwaqUTn9+fn/7pqltvyzJrLlaI3boMBmCYl3DEAAFjlQJOX4Mtk4OEeImzv1ZnJR2f+fcLP2yu/cutQFrvPjnYXI7oJe/Y3GI6k90iemRJgQCpIm5y+7gwQNinDgcV84ao0e0MYSohLwx1o/pcbHcrOXiGok16xLNG6uNkZK7I1mvsm5iLcVcU2BKaTWugZjFvfprqM4QQ7szSW65wSjGmMidZdz5zP2poyTpCDMZ4wnjHBLEe/ZEhlXmK2vnNaRHtCbnoYDjhVI/uWHDBtMgQP/yvusMGHFcj2sMw4VgZ7+MdBktCIY/CMWTxIhKO0P7CFo6IAjCEDjdCFvjw6R0FyqDq6mNlJQd1x9cECOHMwii+n522xyNyx0i2K0J88UkBW6JAwq9xUYYy9grITXhPNYnfo0Nd83BgIwa8S+//NIeCGyNDGld+3/svfW3HcmV7xliKjHzvWIusYpLKrtsl8sut/0a3Ou9+V/mX5lZ80u/WWvadttuFzOXSlhiZmbG+X525M6Mk+dc3XNJmCHdk5kBO3bs4NgQ0kJHc5U77cYJV4RN72u9hvOrLtjHcq3AdQk+UrarKitMykGaa+bMmR9pLaEREy6mfQE4cfmXt5vcI4lU99pUpLpUnfN4mHlFDBvXeeewr1JVFKgoUFHgcaMA67OKYddNtVJNGN1EyArMAyjAQoi/dKXmfg9I1k5Qw+VV5plD14vn6uBo897uObhkQc7iGVv0RL4kScJNmzdLa+1j3ef1YdixY4eZxrB4ApLDBqDDyvIFDs5+3c984k+atwHy5bol0EFcn2wjBQwtmFnzDug/MMzUIcwrr74S3nrrLWlHvWIHnwcPHggfvv9+eO/df5gU3Zmz582MVnFYmmSs1wKdiGNtaPyqwS+JkPrzzgEDpkHQfqO03MHBwRhMtZh/kVsCxkubeuXvbNBxpGeTxWHaxImTjWkHk+68NgduWpJ4vtHhvSedl526qHF4ZJ7EYbMzWRLB3AdxUxos3EkCw4iDPMpUTp7WSA3cbvgwnLWx5tDb8x6qjeb8+fPDunVrwxvrfmaMOyTxb2tjt1ntHS3S73SoeUYamvXaXkWf6Qb0uhUEZXVHG4JZx8XuSL6eOnUqnDxx0soT26XH7Lln0iwsE28/qghrL0jcchDFHwf1HBJEhl3tZfU9h2EF+WmmQ否fHlROBC3s4Ent8PadWyYxzYEUfd/HjGZheT5JVzSv2PY9NNicMXv2rDBBh2GDxChEM4jzv/q+ydgah1fukcUk8kAJb8CwMxOEejJu+Zzck2NpgX0X3mJx6gFoSIDe13WYNlTzx9x5c42RhHbhfa1NmOPyOsgHlowwGUxmTb1mUxFvuDjH52kTvyx2Et9S52ksav5VnmV97eD5eOzmnuX2kafKwDUD1bBtK6L8jWbgnzE8zuku1o2bNpowCgevOMIwbX5Hc7NpFkgzzQ7/nW5QNEMWGopdF+shW9/Fdg0SWquofdJvmO8v6mCbNmnM5hKOtXVhaHTsx1Dy0hVJWcNygMraq6WlxUzOoj2L8zxt6uHbPPnBJQhmr4lPjPLIfh2TZtobcTy+I9zIz8N4luPz3cjP42b1z6e5RvEJKGDQfPwvpvHf2jik6aNBkLvs7G5EmbPHHN9NjQtPq6MV+xjFq/WnhHaUm7Wc3YctRgrmBFtbuftriDGXThw/YWsm9g否feGACL3NGeGz9+qRtUOjUEafSHObd7MuxpzDnWVDJcjDmAjTCnPN+F/UnviwtJNh2mCqmfEO5+S2d9E81qMF5T/4YVHC9nd6r5/r86jd8kI+MBrnaC6FOYaJfIQtnIFE04hOL/yXhzMhbd2jb/afzAdD0LSTACZxmKcviA6nrH/elLnN0SbwSFwc8wACeNeuXQ+HDx0O53X/HQ7hPIRtEZScp/3XJJnTRKO2lnp8RsT45TX71BfOfOMrXxaOH71IFZjVofUreds6TnvQI4cPG7Nu69atuYYdZUkdAiYIwiLEwn57hvAcKYZk1JSTaU0JTFFG7jzG+hBMOxiBNySgy5yDOd+JEyZq3RIZfey7rK49E2Vn6OV74Jh/GY8YvRa3WG4H1NPPct49nR/1+PDz7PlSVTlUFKgoUFEgUoA5tWLYdaE1MEn4XxfAVEkrCjRJgfKipPzdJJh2ogE1/SN6to61lB6WroQ5XLHNhwYVFqVsADdu3BT+93/8R/jggw9kpmafLkDX3Ve22KzH2/oRJ476n25+LMPyj0WLcQ1RwgU3dWYGU4f4hriCOKhsbW0Nr732WnhH94298bM3zOzR7t07zQTmX6RZt379j8LxWuitDQaOA5wIwD6b/vExgWd5U+VhAGMxzkZu8mRd6q0NDZs5zIVwAEa86GrLlXm2+SAdUu7UDXnDEGyZ3mIMsJu6APzIkcO6G0CmPrVRNCl6QUrkatuE2x0BRZkYNwuIRpPsk7aDKaqWlhZpdg0xifvjMjF3Swcv0Iu4ZZoWkLr/zegpZh204nCSzeec2bPDv//7H8NvfvMbtakWOwDH/7JMvnz+xedq79xd95PdUWDaHDlasdBJ0Wv6VR7tEb1YPWQVQ3mmt0yzzTHoHNaGlbth7BA173Q9i2iGSp6J42f1r0AYdUjuDhgokzoiKveJ0U5g7FauosDDogCHS2hJD1B75DBmoO6sgQmGJLn3l9iW057fHHaks7QRgCViHBwuLTk0m0aPHq2DsOGWrx3uaMynfyCdHadEAdA0qNHLxk4O3Bhj++jJ4RoMLhgkZmpY0DUyN4fYYxiLOwU55EOqfe68+WHK1CkSNtDYoCnUzWJzv5OVUvSMWmSxxJQ6/vGtt4wMOf3dIyl3HI8SjwZxitAIMANbeD8wTRJNr45L0hRqIxAn+6sLaOBhuOgnx8neVXrLAN/YZjCjekOmhg8dOihT4RvCDz/8YJoigLzBmkJzw6lTp8MYaX1iCg6tNOa9CAe8AZzBTZCP7TMKenFYOZVDYfWhgwcP2qE2d/hamzZM4jrIYZJ3Z13Ep+3U5Dl2zDi7fwqhob4yIc586I70BkMo+erM2gwRMmJmD0vicTz9w3umWHiu7pdi5X5txSmHezyeDwqrjZfTLfWuewdeATNpLnnM2G78UyNbFp06oN3BUOCOakzxnxGTGQbe0+jyNpcVjm9f03t5oUVfMQpgIqCxxJwxTSYFMeEMM5o6gTl+9MgRmzciKVP6l3NxyM/m0/p9qejWk/JGGa2BnDp9Uhpkg0TnyWLCTDI6x7S9bF/AfHv+wvl8rVpQnKHSaZ76YlYxzt2MRTDuEBJkDQzTKq5B5E+Y/pjj/d2f+DHn00aiIITnUypQ9sk4CLNx/IQJxlTjLnFj2oGfUDM87Vm8AzfiL0EZwYFxyf4SZt1EmfEerTlimK5+OKt+uW3rtnBa8wZMuAULFqgsA2yPxfwxaoSENbWW37NrtzRmj4mZfNfKOFmml2fNnmUCOWgwUu7oClpFOoNTFpQ88LI//cQyEJhFBGH9xf0F4fEu+Rs3rmlOOhBg1m3btt3WSp6Hp6ZuoOYt7T1YU3EPKvfZYdEAsNCF8xHSYbkEk8vQYMfOHcbkg5kX1ypcJaIvcEfaAABAAElEQVS7+sToQ4iXOqBpASOiyRsUpQD4ZbgTXuMsMPFpK14S5Ql8hZ5pXTyBRahQrihQUaCiQLsUYF6qGHbtkqmKUFHgcaFAedFV/gZPW9rp2SisXI724xZQPG62RswWigwiHEri2BAQHwm6vXv3SdPo8/D3v/8tbNNlzdj2t4WwwonjCy0WufwzT/k35Sx6bRrHDrgczpEXmnUspJ/Tof48HeC9/PLL4RdvvhnWrFljGhEHD+0PH3z4QXjvvfckOb4lnNPGHilTDvUw6+Hlclzbws3DIUlGlrqoHge8eEfLYZQOW0dq08z9EZjRuKANHBtrp1MdEDwekAlwLa3yQJqTDdZoXRqOKcxe+kazDmYd9QMc4lt7yYjnODbMtyueZGPVxUvhLPvsk7zZfLGx46J2cEfyEM0upFFxEV9hrPJFDzzja1d+HW67MJTtUN0zsGLFivCHP/xBpmKWh6HafN67f9c2Y4ePHgkff/hh+Prrr2X65ZzhyYGJt02QtTJ3A87t4tpEBNBIUbH6lx/4sjFvbW3R4c5UY4RxiMrBPq5pelnszv94+zC89OG4Gj31zX2PaDtyYEFfvSFzNLdu6o4vMUsqV1Gguyjg7a8GnjfGzJM2xx1qdiglgYPrElg5dux4FGLxfl8DoLkP+oBlleTH4Q9adUNlegvp9Ak6UMsPd9R7GR/LY6SXgSfzNFqBg8UkwdQwB2LnNfflaRqg5ukbBD1yrzgyxHGWQzHGAw5Kx6geEIRhLKPMPhCbhgDjCbTNiAuM+I/iEFAuVp1HjFcTsTZOMe5nMLMUKWSPQ94Py3Hch7M8S9kaPeQXnxErGGcXdXfpnr17dYfPJju4vIrwVZaWcMyEsY5hDkerBBPcaAjQphib0/aDn5fXNX/6KR1zPkIxe/ftC/v27pe1gWu05pp2CZwuO8HIywkmDWCyfsJ/xIjhxhSnPO7S6FASjLw89sF3UbGe7BE8G9GqGb9GcUA/9ec9/e6Z4kVaO5U9j0jcfBmYoeF18JzGYdaR3CVKW+JeRJyHOxRDv+eLkGfX3S8Ny+OZiES0X2N4q4xoGcIAQWNqxoxZ+pth62yEm44cORx27txp6yZj5AiGN9/YUZJvg/8EE83p06lnLLf3f572nhGLUOrklkzjY/oSc/4TxCBtbWkxwUk0xEZobITZhunM/QcO2L1uZVTq6tUjZPM6Qp5Tpk4Oi5csDosXL9Hf4jB37lwJ2E03Zuy0qVNNQBNBUO4Wpc4xoc24zBoFhlAfjdWMw/ZngqmeSfGk/QwQEw1TjSN1L+x4WWoZprxpUz5mGuOIJIpr5TeC0F7Yi+qZdVLmXxhSjPHDNabeEsMSje0b2vcOlKnQ4WJOMcYi8MQcgvUM5vIzEhI8dfpUxtTqJebh+DBr1iwJTc6R5ZixCcMulsXx4pmhUhTI8ATXWq+aL4VREoOjd9Z112Wy8sCBg2LWbTMrQTBba/PJ+pkAITAA3qwx7sniB/QeIeEq7tdEA4/5jvLBREUb78TJ47ZXxDwmQs6sxwYPGRRaWqKQLXRDE5N5MC+TtTcvnxcm0puyxHK7v5eu/O3+1bOiQEWBigIVBR53CjCXdoVhV4gcPu4lrfCrKPBEUyBdbLFa8+9sp2Blc7/uLajnUAu9+DLGFgtUpPb69AtXtfDcuWtneP+998MXn38eDktykwWuL9zBzkvgi14LkyeLUnOeafzKf1lIe9EtLvFKcU2SXrggbU/YyOEjTRrvZ2+8EdauXRueX7pUWjmDw34xFN99/93wJ2nWbdywyRiKZMTh0m1tbh3fuEhmMVzKKMdK2SiIRXIsT4xXju4b4bti7hBvjCTwZs6abVD2798XTunulpu3RSeQFixboHthk7xqvBKUHE+igusAbQ5bZf4TKdpr169qc7jHNki3pYVkMGBI2n1/EUjEPc2om96LplJHQ2imqhIeemoTOFqMOiQo2SSePnPKGIxXr16WRKjKpEjUzYPqoSsYU/62YOPPnVDDRwwNixYuCMuWPh+myFzkQGkv0u6vaYPOfSlbNm8SnfcHO8ykHvhLGkKv/BSvK5h2T9qkWnKAVk7VQ39tJmEGDBjQP9zJzEyyCc2dJ07aXx7WnS8OP8vPP8kCr/4yKzNYm/y+OoC4JS0QcGRDHClPrMpVFOg+CjD/5HNUAvbGjeu68/S23VszUIy0CTJpdPLEKTPtFG7GiDYW2IhQJEzH3HScKGLw5o2/aP34XLtyJRzYf8DGm+c1HjE+mjN1E8VgcJWXDT9RnkYSNXGMw+QUWsxLnl+iA6OT0pw6FC7poAghAw5wy7ikeMZMHq/fcp2gWYMmGHMfmlszWluDdAoNaVtD6C2javTLP6jfsssDk4Cyn3+XU0d4MdSPNj2uP8tpkmx6+LWtNYZj5k8a0sXL0sLRHHdaAj+5QESC+tWrV8KnMgWNqTPC33zzF3YAS/vDUgFzn8+x1r7UPvlGOKqXInleHCo/v2hJOLT/YPhRdXhSmirp3FNum50jUVZy5c/6I8KMfQN4mPeEgXFbZsrGTxgXMAuNponhm2VI0b1bZV7x4TTxJ75euJqIPfGRZlqG3wwS5Tj+7XB5ul8ZflvfnpbwctoHhUV4bdd3klZjXhyjaGMSNpLWEYwK1r9jx44zZj1tKB0niOcugeReT8QzLY8hbAUpSkM4DIR7uocLx9gOA+SoBMtgKCHMh4nMmTNmmqDJkSNHNY/cK/q30pTpH+kMtKK/8PX0u6TBqLBp+6HsjHMp+RFiOHzoSPj+++9否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否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否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否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否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否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否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否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否xrCI1NjaZkh2v/NEA7//Ja9yWiqkB5lcusKnvViTrTulna0kvePyxaKLuwziGXX21eTuumBMzHd27fDefOndOa9KKdJew8jK3L48Sk/gp/AkVj4DvzUHwya/04HrqHZt4a9u2gQtSTf2pUFGPxKrRnzx5zi3nv3n379pBmgNxj4nlkns6Mb2qaruMN4phrefgonJdF5nHtmZw/f0FrO4SNEXef5uLcpXIyAjoOWdV1SSlbxJbeeZrS2O58gibtzSudKde/31zroZQCtaJxKdT6U50CdQq8agrULexedQvUy38LKABjFAPsRfHksR1f+QA7XwITeqK5Ofztb1/LFeZf7JwGNkYIWBoR2mNhfGGRM0zAVXrwsTwqBAaQsrw80mLRsHD+wvDxRx+H999/z9wmDsO1nTY4sKx7Yhm0aSeLqD5yS8OG0cMHd8MuaWr+/NPPZl13X1quHGjeV5tIbEA6ruBbk0AFhAfCAVwqIUD7/PMv7Fye/nIXd+bcqbBj57awZcvmsF9npMBI99MmI3i4axjqbsyOKGLg9BtpYdTJ0cSHPuuCPrIWQEuOg+6//OoLMd7zxJQfCd9//52s1H41TW0yRaays62eF/dCN3nbZlC8fXlkk3XevHnhIwkaly9bImHdg7BfFoGHDhwO16/dMGEKFhG2OWICsVI6vBBi7WRmw7GPNnaHqn8tXbI4rH9/vdyGLAjDZPkBHWkr23imIgq2XLYN/mJr7JIO7N4vgd0hnSFYabO7naJ7eHRsSdw8TZwwITTZpsqIcPzkcXNjhBapL166v5U6R6rBgweFRrkMnD1jVhglNzAsZgnWnhqHtsGcgUS4EFUTelotMgR7wAUhJxvhly5dMkHotMZpJsi6LOsu3Ci+MSF2E5uNvTdgDYCiCBuTl+Xu9qUFR0AFsoGPQgaChUVy1TujaUaYPWu2NKJXmiveC2qHh7JKix/VzmPINwe3Zrjku3X7Zo2tCOM8EnHzSsU4E/hrR22itMCxKkLZYd++/cZvwHfQ73yOoVZ8K/mGoMwzQUorMzQncZ4twrrFcq3slpBsuOkzYrxFznt0niyvQY6Mtt2EaUfQeUeIwm7否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否OFyYThIwqTe0ugm5PNSe4gRXw+lFMjHDgK7p7YBgVUlShq4B2KDm4V/7ANxVmuvP5QC7iFP1ufLcMm6QdobmE8RODAnYP2NIBiL3O4ITvIUNn0XgSk43NL4u3nztlk0MKeOmzjeNpBSfPpo8zj/3iivz8vpNYXPPfM38FvkzonzTFKBXckId5qVRJZDK569Pj4207PrbEwqAdNsxOBZmDBpgoRw74X3P/xAc/E0m3sLaHEc0/9cYDpo0BCdozjQvsvGu0i7vZdoZTM2c5n+UIaB7fB+msNz5LiWB6+n4otkEVaRNEmUR3pcpWueqOSmvSmpJFGHD46hJ/Ky/dnfV6qop9GVbB0l5V3KQDAH6x88UbSiAwDn2PULkyZNDHPnzjFhWamFnSWxn3Isizedu8uauSQT44X+xdlaCO5Onjxl565i/cB3m8pa/6MG2TMAxjQ0mGtD3BuObRhrQuQSwN3wYGOD75QIQh++IWH1VfEVt27dtHm3jeBC6agzgXHbfaFSZ/C4zpSa4thRq6fpOgO/mrReLtdYh9hv1P5GS6+Xp4swfd6ibfhLx2o8vyq2gbcD8/CkSZNNaDdaHgnKz7JzTOPIKf31dz3u6iTxa4IgYwjrOrxMcLZxeV+N9ItnaRudgaGg4WlzMvQkjJAyGGeUr9NZ5Y2N0eInvvFfLzwD4NFduBrVhQyKn9z3yODVFXJ+a/TLkGVOpa/Z3CoiDpVV8IaNG8N//i//1aym4A0I0D9fv3gX19VhZuAqXpLkFd9bZAYIuaz9ZSktWj8pziVAsnwlcTxwfrb9ZR2jTYIIE7g+etLKpDhz7+O3UjvbO4PPt0KCTf3zUqHreCl6LNaxDYOHDQ7ffPN1+G//z3/TfsjR8ExzNOWTFt6Q/QgPPCMgY08Cfip+Y/xtcm2v/kmSam6Bb39K7LjHfLGAe3LNeebMOfv+Xbl81awEn0lpGIEjZ1A3NTaat4Lh2Vl2KI1cuHBR6U+ae3KE8D63FfhE2E7bWDKll2JQpK/f1SlQp0CdAnUKvI4UqFvYvcRW42POh7UeXh8KsIh5VU0WLeuemLb9JZ2L8+tvv4avv/kmHDh4wBj/AdqgxfWJC/WMbc67V2QeobQzyHnfU50iY1mwf5aNH72DYZwh114btejgb6XO5hk+YnhkpJ/i+hI3V9JqgwkWg9yvf7/QW/kQmBw7fixs3rRZGppHpAUnwY8CwiJzcwExuxja5izG0kAtltDs/w9//KPhy+HpLdImfyDrhOPHjpnFFWcO3ZbFAq69WEBFuqHRDX3K6JEVZjTx9xnefSRUapCV4UppoWJ1OHf+/HBXGq2bft4U/vrnv0pYecY2aKF1qs3rsADTti4Z8G68WJlCgr4MfVavfkeWkx+bO71johHntZyW8IdNNIItAHSln9Q6FL2uFPLQobL6WzDP2nDZsuWGJynAACGFLXZ9MCrS+rDwI557BMW49Ny5Y4c22IqzvLqhCqWId9MT49ZpRZ9tmt4Yli2R25TGRqvzRVlYndC5B2clsGOTpkjdTQh1Aiz9Z6DOaJgsTdKmxiYT9rsrTAcTXZQVC2yPt076ujZaXoluuKHPs0vPbowC7jCxZKKf0D8Q3CH0xgUdw4Q597UIsToVUbUaZNVgjCMcQjjdf9RAzcMN+v7wnXtqGtfdMVeVI5VSFOu3K3K/e/XatTBe7iOxep0o16Rj9H2wb60lpnK6sXmL0eyVKYdcPPtGK+e+Mq5bZeHwoiHFO+ISv3sO12inRAy7xxLCsWE2RALRmXLltXbdunBEZ7MeOLRf5xvJ0inbFCPPQ20iXbl6WS6rLpiLVtyFssHGO2qOOMYEllZ/YjpobEemvWtGyvZev/r4Eip3HZ3nkSgpJrmNfc5KjQIwLGMGiDebJneYi2SNgMY+G8tYuln/VNrC6iaiWysS2/wj5OgH1rcEHj4V64FNm342d3HLly+TQGWS3vN9j0pj5rGBtBrTYyeMk4Xn+8bH3b17R/3sivEBEdPO/8In0DeZI42HFQjmSjZz6dOmFGT9EypEStw3K/bDYffu3TbHjhnTYFcv3XnrfFz7i5d6pRc8r9N0BqG0V3Um34ukTfGvTfmsj+bIQnPtmrWmXLFfLk4fSQD7pgb4KYQTuHNlXnbFwVhf9dTsGxS7imjMfwZf9k1iDPbp00tnrQ2VtfgqfXvumfUdZ5kyr7cNtWmnHG4ccvljT7tJeyi4QU/+fI5jHTpQ6+QlSxaFLz6Xe+iPNsql71Bbv2Mxj/JEXKu8upr5vPx8DF6sbbuUWwRmPsWDAmsZ75coKzbJZeQnn35q/ND//X/+X+GklEuXrVwuRZR5oWFsg/EYrLdjny/cPhOH4C6uxZ/TwZ7z+vk0a5sCesdxB/DoTh1X6rt/3y3PGOPD1ElT7VvYRx6CJkrpeM6cuVanazq/mO/0WQn4du7YGSZL8YBvFx5WrM8JVpdobFjUf+oUqFOgToE6BV43CkSV99cN69cMX2fs4of7NUP+rUM3Mop+ZhoMV9tQMJNt3xHj7xOWCjjpnycpAwDzzyYCMOLG19Nwovl4+O6778yHOy5KYhoxoWJEbaNAcMET5WTra8AUfCsue2cHMudlFdsLMgAw1/O2YaLttUE6B2nWrFnhfR0+/vkXn8sV0NIwcJDO5FGAmeYfG8aOp3TaTPO5lwR4R48dD//zf/5/Eip+Hc6eO0dKC2bNZzTJng3rhDZZukqXSmSibDZYuA7T4nLO7Flhlc7QWSChHefusEhC8IRbl02bN+sA58PSOI3CQ2gWBZzaIHLaZIWUY2TRVEIJvR8M1CYAwqT/+J/+Maz/YD1yS7lOOh72ytXUUQm+7FwpbXh6eurk479S/WoZp3WOtSXXNBi9RI8+vfqEUSNGhvlz58rtzbLQOLPJ3IzskIALIRe4eyAPnYh6lPwp1tvV0z7v6vUvn/94ZgHrAaHwiuUrwsYNnwTcyLDAJXi/M6Gv2i8P+VjRQujxY7OqO3nqlG2gt0iz0vpqnvj1uLF+YzT3RWvEG0HzeFnYNTU2SRtznG0u3tTZBleuXLaFHZs15fT1GtNenW0zz1v1tawQcEHwjyuXmTNmaJwOF35ZW2fIMBZxxxT7WizJNK2NCFWX/GYmdHpCi3bowSyO0I5NOtp/iKyiB+vPvhudmGNfOQHjVG否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否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否UDK8hZtHSuXvstinMd0LbOMCnlObpTGkpVnt0SwajENl8jERgFcH7mdor+xGf/tt9+G//E//kc4LMETFmWEViQYwDZGVzeCZwsRRaYMm3VZ/dg/MaQEmGOs7dBWZvHBZsf0qY06N+wDO7Pu3XffMSbZFkYCbQcaA9dy64fFHYsvbUwS20dM8h0x2dsR2H33nTGQAp4LSnJ6eH5dM9IkMc+/ZXHUSwK3J1qEPhPDPlwazWvWvBu+krXbqpUrzPoIyNQJt3A//vh9+FGuMC9pIRhL1Gaq8ErHMZUyXLI2pM68558H7rCSGDlymIRcs20TaVpjoxZrfbXAvGbtcvDgQbMwoGzwhLZpOcDwtnC4tbjaAgZA3jgqKMdccbQPdUHQOX7CeKPTRx9tCPPk+76vaHn27BkJgveZhisWKgTZb8T9DUNYMESbCEew2fjI4i1xlT/WN9VvPBjeohVwsaqCbk2NjeHTjz5RP3w/jBozKl9QoRpttbDsKl/XAgW9U39+rA1Gzo/avRsrwVO54IpyTdbnBff4q1UywzJvyRxsZxG5AABAAElEQVTrQYMGmgsizl9Bk5m2vX7zum18IMw34XqeuvtvaBn+W2iLrrmf4ryYJXKty6YxLho9eHegLRFCMK65puOGtICvANrBvJVXaFROE2LYrLgr5QQ2xdi8GDyon1k24Q6IAM01db++IcPduhzfoWwiaNFmHlr7CJBwhcT4uK7N/xPHTxR1fcGOlPN8wsGob0hAVIqwSCuLuR9BIdYDnG3KOSu4sMViHUsOcL4tAQB9HZh8j0dJOWaV3BP/x3/8R9t4Qslk86ZNascWE4KR1kpRXiyO2fjkc47M24eg04J0Oa48VAwQMlYAEJGssU/ZmM7e2XlRWX2Jd7hsbJGHs3vYDGvU2ZQzZ4nmstZmsw2rDgLzEZYT+w/sM74AN4a4BgVXh0U6vk9eesTFsSuupCP4+/jU3i+pqm3wShArxbVXlsd7Hu8YHs+1Ulz6vop7B98OOFxZYsnJZiXnwe35fY8EWpPE031iZzpOnDheFmtY2+JhQG7aFOBVxmkzcPHiReayDmEyXgJiW1iSl/rDZu0ueWnAq8MEWQjPnTvXNsTpL/Cc+causDKBsSwR3l3zjt2vWLFcQvGFUvZp0kbnYOMpHHkNs9KQRVh08o4+6WnhSSYJ/qfyRtBLZf/5r38NJ2W9aAlE60cSjOICHkEDygIExr7x1fCqwNI/G5dqO2hKE8b4CMYy6UfVy0J+kz0nyHkSu6bp2ktDwjRdCYAXfHC4HZX9gkVYdi+Hh47LitRWjS2ZaG1Z9aP/vIvZiZRbf7UPc+6kSRNDQ4M8OUgA5XOsFZv9GIhuo2Fa0gvcU7+s08YeFmHR76gTczCCu0oh5hNtHEYJiWM89ETxilf8IRRAWNLyqMXmf6zrGCtpyGmfRiozCgMI0hvGaN0pRa6pcsuLsAuhHO5n74tveSAe5qq+5Vh否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否iESdNs1aRitxKu8a4PQlqfrQ8lWwytl1nTtyJSlEF2XVN9ELGkyXVrf8WZgiIyt7HkXuVs4RU6/LZElhdofE0t9mdR3GJMjboqWSm4vIhLGsnf9Xt+gMxJc7dSZpl8f+Lrq65vn4xwQlNOv1ZPVt0ZVox4JZacxaqPZc+ZUC9QnzmsNhnLt1auXPT9/+aVccutM51HiYWCN9tvf/cbWoZsUzxEk0M0Ku9o7ug8IF+MtpALngVT4KTGG2egnlHm9uFoGLmcyfn3w62rDxg32qrJUigVYxT8+fVrVL2vDOXNnW3BNf2c+Ofj1oWrr1q2aq6ZoPpvltNkqWY否goj1HOIKtFJFfe5FbQNy7/fhocBg88jDU4Mepj0K/LIp0BPY/bLbt1e7IVGABczQFi2dSx3Ak9PxPwZGCVOwkJu+g7IU+uijD+1zHa0pzqvxVqDL6tkLzLII5jULMG8ewED/ecUzDB82FrjSW4XF2KtvyM3Ci/YLj1Y8mye0hZWlLFC5i+CPtjalhuUUsfk8cvSIzq57u3r77XeqY8azJTDIvD/lSrnJXIapyTkRixcvqV577ZXqz3/+c9XX1+f3ppvw2yMttPXr1/uPBSz1dhuAhJGP5anjtFE否Q/0gkaBKVcEH7jBRKMa1xPPPf+rql+WdWwESHjqzMnqU7nbfE/1hqmVrjajPAhOa7Xo17r7KdTIvNBELmdUX2HqOlLugKDKQZdHxPDFKpCzCVetWmkrJzas+7SR/UxnvbGBOSl3itkHE05Nt4y4C1f3VVMm+iiWJmufWVv94Q9/8HWiGHEOtJUD7UUbqn6l/Up0dFIlg9I/aKOy6Ysvqg/f/9Cbm2TKd6FKgftwXhinnNfDeQaPy+0W9KSfHzl8WOeSnbyrwoihUKjZ77LJml0Ry845s2dLsP6UzrdaKKuPYB7TZp5TspkpTI0FvPpsriaggkzX9jSMJqCAVbJEP+GhC7w6TeOmaxmN9w/ULciWMQABm7hDS5hXp06dlpb+BbnFnC2r5Lk1gynq4ZboyPlA1fC2kUHYhYWCtenFHOWckuXLlvv5lFxUXpLV4V0NpQ0GwFQ8fTkZrjB2M4TFA66WxWTSHwEwtBnfHvIN1/eHgMBloiwFGfundSYLaTg/67IYOVgU+hwXp4wfunmOxUb0T7/NevJt80KDvpO9Lu4phO/17Nlz7KoaWu+XNR0MaPAmFWCoI24FsSZ8XNaPfAdSYEe61qAFYtAm7u7lb8cccpeKGlibIQAuqDQxMq3bIkRNAQe+G0L9Hoa5meKySUFgN37chGri+EkukKy2hhR9cdW68fPP3bdokxkzZ1Sz1Aa03Qydm/jsumdrKxWsP4HbL否A7/EP34foCzrPRwzX3Xv3VB98+J7GwTidN7bGZ7i6o7OwHazDmzi3jyj9292wWWsRAEFOv5Qezp//XoLDmabx5WFYfIwUQ1jrSfVrK9pt36G11j65gJ5tC9nutOtErnuq28d+KDkou1ke951xQ4HzIKYZvB6efxg0qjtC3lC2bNHhUZ0VyjnVs3W+1RGd5YiiizuC0uK2+LTWWihhoBjIfJx7kzulAuOROfKCrF4RyqFocvrMKStfnZaSB3Hn9C3je4ZLTd5jIcsZbj/8oHjdn5cA/sIPEvzJ5TPnXqXiXeJkSyE9oNB1RtbnCMCboVX7jB0Yk2/yythoDjnogGDlomiEVaCctGufwRma4co5VybsWaINsr9BXtqDv3KvW56A71d+QVyk4ZFgZbAREto3EYlX/sVF5WyNP5RB+vv7vT/Fk8hpCezOSAiKcIZxDL4ITb/XH8pvCD3YPxgvFQl8rI8nS1EOOMtkyff73/2+eu6557ym5b2Fv7r+OOUCwaGma1Sn+20hye3AI63p2x3inccOAYlmucZDP6aa+4UEd2pwp1H8uHFjwzuCBLL0rePf7ao2aM+9VMokV174tZRMllejJXTTFlftJUG2zLDZ7xLGKj373XPfS4lRbbl923YL7Fh3HZG15bvvvGO3mE/Mnl4tWbrEyr998/qkSCS3mNd0FqOEgAgQmwG87kkoRDH5/NPo9yrwilzoslb64P3xdv08U99qK3Dp+z5Lyiv9sjx/QgqRZ9V/UQQg7UcffeR9PmNwgoR7naEUWcZe59vec48CPQr0KNCjwMNCgZ7A7mFpqR6e95gCrKBY3pTlWm4OyiOFxzJdC83cXWSakqteiClPJjGnpeRliUre3HiwSdy5c0f1t7//XYKnT7yRYHMCI/K6/hJeMGZi40Le3GCEdRKYBcx8x2IWa6bVK1fJNeIr1Ru/eUPWViurCTobhLSBg9wnaVELg4ZAGeS3FpxKlpxIDEJpakoDjI3rvn37pfn1hbUYwZuzz5Q5NjCGED9lHZpUbLxpf89Tg7SxWZKQDtxh8rFRxhrrv/zLv8od5kuKk7a+Nlgs6MF567at1X/89/+o否kZdUXx4E+9vKFqlEoZ0J3/3r83Cy3pxuoMmF9pU/YnuT35lc6uGz1all+Cxd9xacNulMbbRx9/JIuuY1IQDHrplUPUF+ZkRGT9edmlqMg0hF/aAlqwQeb+hjQTbxWgy8IFC3VG4Qs+uw4BJHTiTKUd6mN79++tTmsjDwM42jnoBb6mna6u860K6fIu69utrpRjIuiCgO7JJ5+uXn/tjWrR4nA1SnkeVXqPEJFnsjDOQk+d7EpDsnI5r402Z/FtlvDxuCweb6qvPIyBKhFiTuFa6upY0UtMIjaWC/oW2C0KzJkTcvlzRJr+J8WETUFlJE9oJfNdvEQbtQC6ffhRaL4bLYuh+WJwPi33M/39/bYQJI37sZggmZb6uv+BstobS98MAZs8GdO4Oo68xAUM7pgr3T8U73fA5IWCu1fc1r/5ro4oN93SdqbJZ/frfOi4tkovLwYrsCPfgMekQclv+uk+xkW8bJaFwA6Ncs5vY86YNHmSGch2iSfg4Gwmfk2dKLFJ69uhwQB870VE0oC2Vd2TlJIj1Q+pYQ9zE6YFFkOH0Dze8tW9wKhrp8p2wOIPZi9zbAYLQPhWEuibEJn//GlM039vjozvN5Z3fKPTXTVZ0Lq+rj+fyd否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否vBfSCl8rKOd6F3uInsDBikaqAyAkTOOyFHtNaCus13KKjlAND/7KslS9LMeSC3F/DtEdJh7O/OGdxx7btdqXJuhEhSIsquqOMLKfgVshTnlqXgfHqvQYWAMDtcQlYzp4daQHCKY2LaTo7d5bmEQRfBNI7Cz8CyNq/9R2GVqBTFPZIn7gpfbZb4GEogFT+yBeQA6eY5XipP2C0khsO31LmD6wb586dY5qhVMMcgyIc1oLXsRTU2pNz6zjbc8aMGdVY9RHwTfiBbxRRF5blZZmBsF5nRMHJl1LBxitFJ8D2psk0SRMnbP/JIrLI9rc//uQiChAXw5qCkIALRpFO8UqUc1mLxoFoM4v3bgYUP7l37hwb/pZpbwePg7mfs+yma706Qy6IEU6fkscIzg/9UvtX2hDXxFytvKlJ1vv+gjPn/k6X0spcWaEdOHBQyrsaw5qPKRMPQZ989El1/tz56s3X36xWLl1e9fXPtZtoY0i92I8yP7Qq1qjB3b2FVqZ3ARtjgpigJda31J/vNcoqKKExX/X1zff575zfzvqePsyccFzrKoTR7Itx38t6QBQN6Kp/6wxg1a5uzGadmtg045v3zTSBZ/Pt3bjPuSHHfsLsfM74+3FNnCjr58TjftS1V0aPAg8bBXJ8/rOMzZ7A7mHroT18HzoKsEhiYgkhRSX/6Rd8ntjf//a36vPPP7drESoVmoNwqZO5FwvZWHux2GpZntQTVFk7wShgwfrM2meq38nv/gsvvODzzHCxhTY8i0DcmcCI86JNccAFjhfYBQ7rXxi7N+RuyUJFaRC/++47tq5DSxQhF5vZXIQPtTEAn0u+UpSzUicW7iwyH5FLEs5fQlD3+iuva0P4uDUgEdTgy53zHbBG/JvohisiNqsECzp91yojyyrRXgo7zgvYwADNtWXSLn/11Vd81h9lEFjos+h//733q48+/MhuJfPDY否lUF2bdWlE39Zt0sb4icbWOJTwlWcY8dcaVkidgLFugCn/61//unr22bUSPs70xvjC9xdsjbVxw0Zp4e0RnCuGR1m5KYnNF+KXu1GLFmYisfsYfQurl1/Rnq+9UfWLQULdCNF/S3/W5qsbXam/Xeuob5yVxvPuPbvsChPN2LDwoJy7i7uRu4c/g2Gb4znrM0F9c/as2aLfNGPDmRvHpBl8+NAhu0LK9PcQ1UFBm+RUxML+6D8ISrDogvmLxjP1wKLLCgckBZp/uIl2Y9y2jV31F+oFfOakkTpYHvdLWNCakaYXCKKZL7A6Zb5kHqIvxLVFXWBkqO+5QdoAIiUy6R1nGYFgI2MCeICu3bCjDswTuPLiHDv6yrRpaCvPr1asWG7rgYM6zB6L1F9agGGBhR2CosuXL0lo/JjP6eLbgQsslGPunmWh+gd9VESMdhjYGrRDno0DE5j+zBlK0c/5rgdTrC0nABWYG1FwQHAyTuMfZiK4AyPHUaT8Kb9tJQ8ZELk8dHQ1BP3AOIMa4Iewnm8Q9ccCGHdufEdznFHQN3LP/LFcS8+YEWfZpcCuNQYLIUjcC57TmNfoM7Y8hjzlj+VbbYWsuDlzZ+ssn6f0rd9l15aMCYLnUa0否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否+/vvvFdt/Oyz6rQWwtGG8hCll6ajU0ZZzlOeb+dSSGK8DAO8hO否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否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否ImgsEA/pLPrNm78Uu4Q9yERqkYLX841Y3Ht8rJepRzao6Bsq7JRI0ZZ4Ii7jBGi6ezZc6rnf/Wi6LjGAgfOHmHxe/HCJQkrd+k8l08lGPna/YQWYetDSPoNA76e888vB/kZSho2uWhgI1iE6XPtGsz2LBXAKbhlQxEa9MvluurPf/6zzi580ZtZGPv0k4sXL1fbtu7Q+XvvV/vFoEYgQACP+AFa3EcteBh6yH7WxI84b/YEmPgRYirP0dl1Tz29Vu5xlmmDPUq0VFvleNO9x4yQAhfjpl/YZsaNOLV/uKkaYaHFCQmsdu3YWR06eMh5YWZGO0fuodfgwUiZdFQlXP8kTSGGtGbH2b3tJDHssUDDMug7Cas5Iwe3fznnUJvm/b2qXQvfKAGsGW否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否BM4E6jJipAT+YnCjrMAZaRmidvGbcQ/rlb4DMxVrXMbLFFkYoXE9X4ygfrnF5BwX+hFzy50GhK+4rIRBOFnC1ifXPFn97//6f1Svvf6ax64Z8hqb+U13B1Fh+X0ajNK8Z1iZsahMCKv4TuJ6daK+EVhLqUGNdvwOrEHGD1bGwBx3GhMlgE50WUpuleo4PWb/oy5z5831PIFACKFLZxvw/cMq7V0xJPkGXly3zoLK1MgHU2gU46xVWozLUnOtI2LsJLXBr5XH5NMPAjeY8DDJnZ91GA2lZxSm+AshFooymn+0ThijNkfAzpjFFSbKJ64nNNA/s9CDGNVVWSxwZjHCut1aUzwugTypEKJzhg7KF99jBat+OGI4LrvFflffZd3zpZioWEuskhX947I2QOjJ95w/1x1CCE/WTFja7du/r/pCGvf9/X1WmKEe4EjgDKjFS5b6HMEDUnyBwdlqJSeJ+htevGEu+fLLTVYmWrhAykFLltgV17ffHbP1L7naWzvg3K9fXAvvF+OVbyD4Pbt2nS1MKB/6ZJ9LfEqT+FHV7AhJjewvHQmUmRTNWOYY/jg7iTUYZ3V9/OFHAV/9wmdW6wlrmG+/PS7Lh2Nuf7t25v1AJDpwupPHn7NF7gTfByWP6KaJ2q2shmbuxrUs525u+eorz/H0J+YL2u+KxiBzE4p1n3++sRqneQ23j+Mnjmd4G07d/3jWHMGYZ+xagSHnHc0pFozQs5h7fKENB4Z6zHe8ivmKvslcxDqw0VO7gOoS1QHxpzw2yjaYm/oWPu79K2uULVIs2C7LoMHqkiWbJmoDAvMbdMOSkXUN6xzaBwEFrpMR2HH22arVq6XUssTKSHyPEbqOUbvgChiLIohrWhmmQQ/8KcTJb3bOGazVsIa8J0N2IBZ1TNJJXUMhlHtRZnn/3feq/9SeGBfSIfRCaHW2wnqdPrlMFmoTLLiMtfIN7ZdZgwOG/fwjo0ebTqzX2evjYn/zps3VxHET1ZfHyXPDlWru7Dk6j1qWdmTSX9IOTAYGI9gW3W0OzgR8M+0aXmOI8cSfFUxlebdo0SJ7gOhbsMBrNNb8CGb7+hf4fMNRwt1BXW24jvC4KY9EfP82yT3oJa0nlup85kWLFnvdxPcbPHCLjqX5VHnXwV35p7LKHic30me11vC49zecvvtwBKzg4SfBE1q6ZFn11FNPezzQdig4IdxkPXBI33qUb3aon+DiFyUElL5Yvzh0qfLAlnw4aNLDskeBHgV6FPhnokBPYPcTWzsXWPVi/SfC62W/lxTIpUmXVYsWx35bVui5cEdwlbmMmR8yf3lTHpP5wtlDp8RgPaCF9D4tqE+dPOOFPwxD否gsXtkguM9oI2Io2tCx+UoGzcTxU6y1//vf/776tVwgzpTgyZuQBnmwMgvGeGhWC7Tg6kfwHS/AMBlvapFL8DlxusKEGj58ZLV/78HqPWmpvfX3t2W1caTeVFnrrNTJGeufUl8K+pFACupn4R8LaDGrrMU6e171q+deqFYsW63FubTh5NN+lFxg4BrltFzO7JTLIzS60USHFmzhLMhslAdsY5CN1HhHvfkbpwXq7Nkzqhdeel5uJJfrzC0dyk3bOmPUA8utAxIM7dq1S+WdZv/sdkqw9SauwHfWRlmdt0DtTEOZ/JnZo/pMEAN2nphyWNawoT9y+JC1sZtMTNKzoQmXHTftIgyXdq+88qpdLY0sfYV0aPRv+WqLXZ2gkQrzzwFEXM3YdqXwoRO/SNz9F/gZcp7LZ668p39NmTalmictyfnz5nuD1EwbtLQdZIM4LbhRh否MS0J1UzbutPsR9cfN2pDt1zlAV9VOBMNV9Zrw/eIh+jHNVGm3g4hDXWAK4QYTd61P6FyCR8QgznBVZz+khQRxtCZjgvy305bkvdNAa8FshGmC0JwAw2qaNHMnTEIzF41ZGObRfmA2fBjaq4xF5qbAudmfPLZcCd5Gf4AW2bbEMV9YYKdrM8+bexMAAEAASURBVK8R4EflQoR8R968r9P82E0SEeQJAdKgcc/aCoFjKdLREcOt7nhRB2FvN8fNeO71v5RDadDzVoG6I/AYo7kMAdQ0WTJekFIFzH2scpkvv9fm/KKEujyfP/N9NVcb9Nn6g3YOpQiUJSbhTkqupKBR0DnqDOZJhlvhc6/fNduu7gcmm9ln7l+D4cCcgQUqgs1zEm7jHhB4Y8eN8ZyENRzCDCwQBwt8mwcLwOIf7hoRpr0qa/cXX3hR7gufkqV4uLBt6wIAEriECI2js3ItT/lSqeIfKW76Oz9nzmxbm+E62RbL9BuyOWsLU+MV4AAcbZvJHMNPXVAjhto0Q6TJlK26REy2Tbh0i3yZJiAJJ5hg/FOWFE7Qx/v7+6uXXn7J7q1QwKgtLgoGzP0Ioj+T4hDrJs4sfWzyYxZE40oroDZxLaSgTXL9xGsV3Ja2VMb1JK3TxzzGuoT1GOcP4tIZOjPORo3GBa9clgs3zhCcLs31WRKCcw7kBFkh5BqOcpjVyuotEDIKYX15SS6++abjxhhaMD5ph7NisrIOE7Gq6zAPy3xKv8QyC+brSbmCJi2CKXBvayfVqVTLWvfrP/nEzNibf7oh12YrTQ9l0VifZMHfnn1rvc6gfI8pfWscVH6MsZi3iYP5iccBLCHocwjF5syd7fPyIlPMWeTz+jIjdU2cGlF3/ZY1FI4hEIYh+ISZT1+7cQNhKC6l278TfHuIZ01Rt5MxLdj60qAuwBSCLqJ79hldKchrKyVJ4QFnDY7FslGBfjic75TWQ8xF38nCbgf9WPM2Zw1ZUUJwShGU4nz8Mn6sIOeY+Im3jYi228jVFjXgoVGvAe+Gkn9App8xIqnRWaeM74JaW9Ly4EUH1I7naAtGstY0ahvWYShjcATBOLmxbbV/jBnOE961a2f10Ucf+JsyVcxzBHZA8F6FPqA/wNOH+B5xPiTzAAxzYPM3anTsx7pgXUeR394k1Oc9x2m+ch/OemXV6ZvuS0TwVxL4fUnkNDXou3iTyLRA8n1AGRKFAaz6sRRGWGQvGYmqcSy4lazQMObCFkxokGMR+vKH0JS5Eq8De/fs1rw8w3+zJWzCje8yCaT6+/urR+SiP79ZiV0oWkS50bZ6k2hwpUOYVjFOY9VReovQ8usCrEXSAND8LkaSiA8cgJfpWmvUAfhRX7U3fWiY5hLqeUzKEl9s+rza8PkGCz7Zp5p5oEKgxzGdb71923YLLhf0LwhXrULumiZKymyWgRUdQrEFEpDRN3/Q3/r1n5imfGf/+Ic/VpOnhGvMHCMmkOrOc9ahkKDtwvtmWc2XHh/C54qs665KWZb6sbbFi8QZKYkc/Pprnfl6wEo9KAKy5mW9Ol57CzwMjZWgjcCocZuIn4GHoPxmHtQe/qg8kCC0BQd/owu9mY8nC8YMCa1QnuEbx3eE9SHQPF75rugpWkg3P2MoaA/AgDkEAfahQzoiQgpBnIfN0SLgbyGeBHb7tXZg7cu+AF7A7l27q2NSGrmitiXQflqieT0SoyxrXMYcfbwuucTVzwNvss93joWBKYcWE/2r2Y9a2AwNwsOZKsfVYOPn4axVD+sHlQL/bP2N+t7p2LrTfPeq7XsCu3tF2R7ch4gCZWHQbY3Cq8745rPexyIrJoWrEtahvbZh42d2O/Fd8SvOIjKWvWz+xExMsIalH/1n8YUWIWeePPPUM15A/17WaFjexMTRRIalKwssEAgUDYrH8qyLAwsq4pi4crJGq3379p3V//zvf5Mv+PVi6lyVcE1CopqRUzLf4SUnOuDBSBktpljf3Lk692qlXJQtMkOIMyJgXsEwQ8Nvs9xB/eOtf9g1Fq4ACZy1gjuaxNuV5gV14doIlJnpJsiN2Lrnn6v+/Jc/yW3UU6YrSdGap1w0ztGC379fi1xtqp0Pxo5gmAYNuEO5Ne2VkGsTL57zHXBghC1avNgHs+Oa5OCB/dV5Lb5pTQSGLKiBQJtRHwR3nNmwWGeirFq1WsKxadUPcp15RZs5GIvHj58UvaTNr80rWvujVAfOTop2BFZ7+RFzd37pq9NnTtc5ZqvtrhNGBzhn20cpQQ1o0KRLGwZ6Af1h+CJbZiOGu5Stm7dIY/CQhb1oTuLCJNu3Lf/D8kCb0sgKpkchCMIVGA6cXbSgX8wGMXcInGd44eIFn/+Q9abXxz8nuW8/WT79Es3VKVMfU78M4Q9IoBV986YYDSRQ7awxrThvsI0x7a/29b9EO2iRT+oCraBXWLPAtGZeGiy4PPqP/hHAs73/DZaze3wbCq7LwHTtWKu2jYi6DsK5EV0DAX7Ssi4rIrMGGruKKPWgv9AfoLnPzSn5g+lwzczAS2KE/CDm9XnNCyhjoNwR3xslLshBExg2M8TgenTiZLmc/T4svQtm4FrjU+IejEtS8dYYQg8YUKdkXcd8DjOLAB1wvfyE3GmhbY2FF++yDYZaR77NkwVn8aLFtnT+t3/7N5/9MloCPITqMLwQ9kDnxLiTnjSF+4erwhhR6SWC/ut21xyXTBiY/YlnXoFfw22WVUeWuaWuWONFiWMUtodIMzBle6puTwnJXVaYgacpADC9ZF5YsLDfwlLcBJ45c7oW2FnIoQkfOpyXhfme3XutOLRx4yZbF6Ilf01tZbpkQboysnKM11eQK2ma9QAX4DMfOQk0019Y04VCAC618GgwRoxBmMusR1gnMN7myIoGSyosxxcuXmSGOyPFwpsAbLi0GQHlIvoczH8CVgXHj5+w4I6xbBS5Kj1MuGxX0gITRjRuoE+cOi4rgvPEWukp69lMj+tcPAQgjMMye67WV5PExOM7OknMy1X6Lh/99qjcKX/j8/QoIwJ4lJmZ9lI9sFzg+3rkm2Nmai6UFQvfJAQMMAb5RoEfVe4U1iXUe331UFEhjOEjh49o/bZNbTRJ+E2SgAWFrC4YqDP426CXvM6+YTrmQ1vGiIQm+WcIpfBsB0qCxrNmz66myj30Za2fEzr0+ebIN9XHH31kq47HNG9AR0IW6YfyA36BXTP2x+6BlBVu1mwo+UjTzP9jeX5J77vXOynJ2IUBjhu5w+pjFy/KJS2CdYW0sMNiHYuep558upr6eNCGfkGfQqEGF4UoIDDusPYmP0II5nPO7ELBA+t6vh2eK7S29VVzAvMOikJ0ySJfdCsbP/1QRuCav/k9yJ7VvX6B5b3/zaEEPbDwwXoKd4eHjhyuftBatjV/gX/izC2jIJ5z3LXGWtQV7PnOHj8hjwNSatijPQ/zLZ5aUFLC2u7ouudM88XaY6FggbXdcO0/ISh7O/95Hm7BpOxEJalKWRHZTk8/1VkD30j747+tug9M63fgUYKFcTqq4sOPPqzefustWV9/WZ0+K2EdiJZk5IG/8Jk80tgzh/pTrBG1llDnMZrZIMqJtT7HUXAm3Plz5+0y0R5tJLTDKwProwkSQGNpGnsQwdGcD+3cJoIYeIJCfDcS37qtatrkG7Irv2hOnfims65H+Zg+D7+EvsHZbDOl2ICF6/z585wZbwaMRbxGIITCep31gDeJrl2Mt+1bt1Ufz/nYa+RZgkF/8LeV75XKwOqS/fPatc/6u4vL2xs3OBM9vtOcsU69QL3VAi38f+67Jl60F26YD8gjAetR2om2f0rrk+8kyEMB+Hutf/HYgyCTPxSkUAjlu+T55eeu0C3LpwW6dKJb5vllvHQfbIzXX0aterV4kCjg+ftBQuge45L1/aWMrZ7A7id2mHqh8hPh9LLfGQVyQA5sh+bSKxcAJS4/inUSvdd/P2ZcyZI527FrxVI+i9pgxgyzNtPuPXurd995T5pxm6wVFXljUeiJQ2WYWaUN2nUxV7CkYtMAWixO1z27rnr99ddtYYcVS1pLkLeurxa94JxVaeLnKugFPCTTxenE/B42Mhi8ly56M7pDrjGwZrsobXACC2gYB1lGE2bcJ3EGvmnGwLziDwYPFjnjxo2unpbLxD/96U9idr7ijYOqonrjFiqmIDZzX0hg9/Zbb9sNIkI6FvZZ58TJGHRBg/KgEwt12mO8XEUsX7xcbpOek6sPbQAsCcP94MjqDJucDeurv/3n32wBSR5ClsG9SFYH7ikyr/WLckMb8I4/F9NIEPi2EMb14bgJOqds/FjB1IajaPw5f4EDHrgD4zyz+fPmVM8//7w3WlgzIcBjE3peljXnvv1efvk3i8l2WNqLwVQIXcEGAtGrmxFDuqffNOmRmZrxbIhx0cnZiitWrAxLkEzYdo36R1+lpuVZFHN/8zMCH20OpUfJOVRHjn6jv6PVD+qrj4zBAmJkdfWmGJxq266hBbbr6wclkpqDarNZ0DZ9QtrI0BAXfjC否T2Wdd9LsIJmaoafg1kKzuAzTO1jNzPTplqJYPz4hntFva/7iyuoHxoc0wG9q+/LO/oRzBPPT6Vyvo9OktX1NeJbUQmujqGooKqiVBYJmqEJk1fGIROUtIkX0W3viWjBc9lEKbRi49lxSsDWHxB+X+PSBKp7/lN/JSJHFKoruDoj0IDDXNrMS3yEFOxO1uON/5+99/yv4ujyfZucc0YC9hYi5wwOOM88c+7MnHlz75y5b87n/oPzOWnOeaITtrGNAWOyQCKIHG1yNPf3XatWd++tLSFAAvl5doF2d1dYtWpVdfWqWqHk8hIBL4WZB0OAkLLahU1jBHZYCz3R3HtbAi4PAT+vuFzstd3n46dco1Dqz1aKCew0J96UQOiOXA6a22nBgQ4oSSxoXWAWwLe1UYXrvxDolavq7Z5+wn3xmlVrsk8++SR7+523s7a2NtuEtT6kn6yPgo7CGVLao9O03DaL5sfy+A3W8h64Cp7qFOoWrKyDURml204uubwS6uKPocMfwdP8jt8oXsRwF8ELpaIRWbqW3m1vVCmN2wI6IxbcCbbZpyuWoRPGTzTr8G+mfG1p/IwQzQxnyqhdWJwzJotxmWcdlBvwhOdByM0czN/IUbK60/sDf4a7qUkaO12ndL6t5uCJ2ghcqI18vlPW7w0IZnAEr/z+0a5Hj13YhfU08OnTxHbQWTkJOZsIYU+nPDMgvGOcIlwMmpYJYWNerrJOafPuROdJExBwzhPn3mLRNVFeBaqVilnnxrhIQ6YMxu6Zg4AHXrekyX9eOODSjrBIHgEQhF+5eiUpN1n0G/uBX+sS3/rVV1/oHRyt87K2aZPWLUPKSJmiR3qHIp7x6e+LWgyzpncpvkNOY9JTxyoZxSc6x0Y449U6y6FNnzE9WyUFtI6OE9np7jNGH2cAh8my4YJZjM6eNSPbrHPREF6Ug9UFInnw++jnmqQ8T9wk/OLRro3iajKkh/7ma1R28OOi3fl47bXKMu16zVSTEH3nJfktaB50573FpSMCeoRtHR0nzWobQOTGWuX8uQsmKEEYT6As784vv9yy9xae/LDO47ysDXQseninRul8qQni+3FbOEHCZZQCsNaGn5qgOQZBCwIT3t9Z2oBHuMdc0UNZSXU5jUq4M6ZBxKNSOngRqegGRB2sURB05Iq11Jq16+xMT87t7jrVJVVVFGlY64JBLRaBZ1zBvT5YmtoDvcO5NdZ79zRvorhwTcoReBypVjXviU9t1TESLS0LzNpvmmg8wpQxfFIAVvyLOQCcQM1J5viVaVuLT6I7hE/fZOdVeEydEZ1iBYv2Bp2I9nnXeZVhgsMzCqVYO3/x+efZrl1fmcXUEwm3ygH8r8kifa9ctGI9hgvk5fLEYgotgmGzVlIcoRzfshVyJWpWeQePZseOHs6AiOXooUOHsv/+3/+bvh1Psp1aoyOwpgnwlHwj+WZFd9Eyg21t9DaV20Nd5UAaVpcAwJreFDMVB1zm8ouyAuPYEFMmkxBqoVxoE3g/UFauVqv2/eGbhMBvGPslKYA7Foj02UwpEc7QnIxgEtTw+oFAF/4Pt5gIrvi+4mL0of4RUOQRxXVnLbK4ofJTjJYCI9rL2YX71GasDxcvXmwKNViWnus+Jzru1bfxlPpNvLCEe936hvMd53xllJJGjnHrXu86r8FGqv3EuIcuKSI63VCIOMcHmhehNq2I7/9dMYZeHVb/a33zOYt2DywuNlcK5GDBH1hsm9BeBwX+1sbCX1t7mwK71/GWNOsYFArEB+mVgdcwHi8KTcyFmBtwYaGPdiUHJX/9zW5byKNdGZOGuTiEUxKnQ5xZoWgBw6IsNsI3bdqU/ed/+Zfs3XfetU1C3Hj4pk1itPQMO8MCo4aXsjawgGAxpAx5HSzwKOH1sRjo6urUZsIP2U9aWOYHVisHdQ1EoPoRqv9XtQuBHZpg7+58N/sv/++/2bkP9x/c06bcLZ3lhjapa66ijcqiAau3WzpofKK03YeLYX8g+tVsrjZAMegLPdBcHSUmfWnb4mzp4iXZvNnzs5E6K+bRk/tq3wjBeiYG9kL2pz/+OfvD739vrpW0grBmhzDEyFdHCOIaVF3bB3VleLRxoSvnEaHBjxslFvpXrl01K5cYwzlsOs/6K7O8H330UfYP//A7LbSW+wYTsLSQv3GuW334ffblrs/Vnm6qsvAktSWeueawy5F93Of0rMtTjuee8yTY2Hjrrbe1qGqzTQgrAjkhmALjsVw/zbNEtTFl8c1NJeDe864W3R0nO7KzcumBEJfA0tMsIqTZPOKZWzAQz2KPsRE0NIDlysg01ILaDYr5ekj3LIbnaMHN2RwI7NE+fSBBJVYWLMi5jzZCixqCvsb2gQMbSYv1buHWFauGCAy7XFPVO9nmuLxPrNGRm/lLgihtRCOMStldiUEPthyHTqU/5bLCirL4fCwSTWQ+miyiqKh8RzZCObvH6FflvIo8xulcGr+pfLGfq4iASSl75EdlolGK9L6jnVTg7c2rsmr9iXTADZc7Td/UcKDlVxpYjAGDBDz9GW10ZVMECC48omxtwMUf54ktXLTA5p5CMAK0ckNqy725J6dHf+qnl7DGfqzvzZM6S1wEL22L23TOy3JzO3gXgZ7mjaLDe2877yaultD85gzR//SP/5e53UIwY+eQjdB8pHkpH48CFZ/RwN77v7YV1uP2Q3x+k2fysW/dm8O2YZ7n6HlDOkNi4ANAG9Moj1UWEzqSNXAwfKDNcBNuMrdx7hOW2X4OD2eCITCJTbPAHGGl5nr92TvBmNdLYPyJYNrmqglPeNVIo/cdE3sXDNscswDa8Ao/9FDfYwRy0YdcwVnV2zjB4m20NNmnagMaq3Y21xGGKVMBMxGfcYFCjW1wkqo8IzSfq8GW12ED3//AH5wD//uy0Ok82WlWArfkPhPLQ2sf8HsJD2V5wNlu+/fty6ZqrE6XgHS6Nv+ZB6ZOnio3uBOz0SNG6XvJmBccg1XA87FWPPNu8P39UQpUzPPMGVhC/CgFNIQSbzqA32kJKb/44nPri6VLl2ljd46hRVsI9KvxBrrSB0Fvu4reJohTVh+u+tU9fUBfkMdoorJ2JVP6ThnfbTVkJljZuHVLdk7CuQePH0hg6m7lyXNb1s4IXTs7T9qZQoaXwS0NGz17XySAzUtOAboR8jQO3seN0/oTW1uePo9A3+Nmcdu2rbbhjZta3CwTmJNQJOR9uybPFpdlAYUbc5QAOUvazyz/PPv8s88kRNhvY4D5IMYe5bnnynhgTWDziuYTzpmqVquy5l2RLW1fagohM2fN1PnA40ywYWtFlQ1Ymp5srOZ40wYbTrVti3SukVK0tpw68PdYR62XoibWhijfndb5Wv7dra2LNhHsHUlJ5fvAnFwhf+dNtfbQZv3DEwUWkZflInIf8+A0zYOqf4XOdsMzyYYNG8Vft2lenG7fH+sL9QM0s5Bom57ysVczVUbeyNTHNWZ1Q1LlUhNTVQUg2ultpTXifSWMun7tenbw0EEpJHydffft9+YuEq8j1vfK5e32sYDF4Y0bN+1cuq5Tp7Itmp8ny7sM80p8P/l+DNcf313chi6RkGeOlLZGjRprwjqEWt1aQ6LsBE8+r2W+0ufqG8a58j5quMbYAzaxkUaj7B0qmkVUHijHezBa/CmuqAlYc5uSs9I4n/v48eMmVF0k3hRLyZlyRT1Kyrzz5swzV5m8h1iqs0a0elNd8BFnZHGGIGv5yuXZUvF49LHjpjGi9T6CP/g/hHUIjbFaxCWojRxri7fHEBuCP94DjhjegU7qm/KtrCKZH+aqn8bO0NmOWpdVKhUTdh48OMm+OSh8npW7Uc6zwz0t33GUAAh0q5Gw1GfEvVx46YIvV12z1AtRIN7dFyrUzNykQJMCQ44CTYHdkOuSJkIDT4FgKBJ3Eo+qKPiVgmXzmIjvCxdjesT0c7oAvtlviVlGWHdQh5bjigAmMrTTQlgXmvhsPj3DYkhAcIGJptRmWdZ9LAGNaeTOnmnYscDxBY2QpkL9icdV0JXHhCBwog2WX5liYejMsdoq6Rgac4ePHM3++Mc/mlbbL7I2iOAWTvH08lc2Sx4LH7TVccO2XFp9S2DCtZHERhba6ywmxsoVFcI5zoX4VJZ1uDiCqaYtCPp0sfu+MMFiDRqzcIGZnTF3RrZty+bsd3//O7mJWG/MOuVZ8IIXbqTQUDss7cWzZ89ZXCykqTdCcRcxz79GGese0Z9+oE4Cmp4rtek7a84sbZbfsU05/O573zpsqi9b0+A+Z926tTYupk+TOzct2gjkYwHz/XffZd/t/kbavr6h4FD8lwVUjAcrU058yfugD304SYvCZWrPylWrzSoMzWETJgVs4Uj9aaj6QK17qaAXebC04E2k7xHYfvvNt7bZhUCOgFbmGGnGTps83RYpnPMG3c6cOmMLEtwQCczQD4ajL3zZpI6NZsbvVPV1S0uLNmgmSIv0qc0lFzQ+Lsl9De5NgvbW+YPY0hiPeX01dQ2TButMcwtYWVQxzVVLVrsoZ4t2RVhZxQUMg1nX92wMcH6Uu47zRN+I9y0Z5gj7E1xC0b2lMUWSTRK6mgS0Pi8lSUqlEyyPLSB6Jo+t+RVMy1XKalWmTNZeRUQ7GewILUKgF7CCpv4sYP7f3g1AF3QjydsXQrcCZavI8VGZQAl8ACRqYZzqD1zqAnXgLq/a1qa/xdqgOW+axmQL/JxMAbkOwBt5dOoUre0dCdrAhpC7GhudxhX5dRaZBBa4NcSN1rGjR01bvXdItSloZ69ctTL75ONPdA7bu1m1UjHhE/M6f6J6Tj9K2oghMg/19KxJzHMZoPRk/RHFggQUszjGR8DQvXWanlNUPAbgtK2pxygTKa9+ZWyCUqBaxiugx/eMNrEpjYIHQowzZ89oI/yGvb6MW94hH3+U1L1oK1WM1FbSoKzX9KtesGG/envK8alkVN2va/7u9pH7+vWb2Q9ScBo9crRRccdbO4yvoQgbrDZvJfpyz/eROd0CeKstUU9sWPuZn0E5z8ovG/9sXt6XkgZW9li1sUkZfESRk/Z7eSwvsMYYrTkV12bbtr8lV33wj5mNfTY/cXP2SDwD0yRKPT1rtuz2Q13d3edMII1bsqVLl2SLJJjo6urKM9FOgtM/jx70G2u1eGfO8+nU+bacP3xNG9zti30D+KloBu2wLrgsy42fJfRkLsXqAp6FM+e4R+A6RhvYMfcx+GgLlHmWW3Bo/KlCz8Po1gOE86abQAAXc2ckiDjecSzvYzbAGavwM9flyg0em37HHRnKFza3Axf6GUzlTs/Rp3kl/aZouUcTgv0sq2ZbSF3az1KvM9uLtCfylumROq0cJfSj7+N7TUniZundYZ7CMmX37t15vsgPX4ow9ovPZeE5dpyEAYvtHOp9sm754rMvTPGQ8Uc+1oDPC9TDxjrnTp05fSY70nrY3NvOkqUfm/KcX4u7eTbnsVrDUiZwqYdtYzSRIPoz5h7GuIVIqC88gM/gx7nhlbZqtmrt6uz7fXuyMT/IfXRSXAAnR0NfqMArvRG9oVHXfZ5NkZRnHoZvtiXS3Uxno/2cXZCQEGEWllmsx3G1yDeotbXVBPxYb+FONzwVNKrXSBlDSvgZzjWI1NK0lJXcAgmCDrkR2aET34qgAe4MWZv+4fd/MIEdwqigmUEBSE4vh8t8jbtpyh08eEgCriUaM1O1/h5t62jSQYF6RisOS7b58+eam8R79++oTb5/gZIBws5Z/+1/2LdnrazS5iT36r5fgUtFb6Gx29Y+ZqqCyzCMqEw4gmbBx+q7KKHdKFmUDr/HWpwzWmVpPgYXxw9MgMZ3DxfPC3UWOkJWeK9WrYna29vtfTt86LA3WCgAFwVp73u1X7zEYbnG3Kt3EEXSqToDFw81KL4xq/NNZg2OZwT6HQtG8DNPDLrxvhJ4Iy8NGJqBsYAl3U9Snlm2ZEm2dfNWs8xFIIqQtVJBaLfQvn/sF5w6dUqKNvsVv8jmNc6xtkD/aCx5b9JdtDmeems76Y1oE+UapfUGqxnfpECTAk0KvD4KxDe2N97p9WHy8jU1BXYvT7tmyTdMgVd98YLNMM7tZdriq28ref/+Q50TcjT7Wlpxx6XRFGewgSN/sDIsDGGaYZDZjCEe93csInbs2J7953/+l2yLfPBPkh95BHws8N0dEsxv2mRJTBYbdcY+wXgJOGwpARbMNlOUjycmKRPYiXGVT0xnjI8dz77/fo9t2IIPmneUJy+wHJTDA2Z9iIkv4uO5fEUYMVFuilauWpG9/9GH2RItIrztcnUhjfMRbH6pfZflnvL//O/fZ//n//xv0+rmgGngPBJ9XGhXi4e1OVUM/WwRok0lFmssTNAO/E//+E/ZP//zP5nW2RNpz7PIGC5tvXBZc/z4sYzDu4FPqNdgr60xVdbbRZnr84Oj9YHqpQrwxMXI+x++b9ZTf/rTXzIWH/jkR8sWorOhAxzb8BN9cDdYFZPd0tKq8TDZtHhlCmC08QXadR0qfVKL/AumLY4ANBZm3vMg7NSqx4+UvkL0Y295zBWUNqp2vLUtq1YXaRGIuxOvLb9JlRbjyZqpZE8IzMq4ofV46KeD9g6dkQY9Gp7QAy3JcWM4h2BJtnnLpmzTti0Wz+bIF59+YYtxrF1CKA7+z2uD4fkGfqAHa14WnLboldED48PPUPKNGMYjbskuyBL04jmdNyG61LSnTLQBbAN49BVIZ/OoWq3apgfnshBss9Ea5fONjYSEYxkmcwzzE3HMgcxvCOUieLzjgBCdP2X0ZBtI6dGiqCDw1T2Ld99FCHAvdk3wc5gNYAUqATjaxjXvH6EEVvGsmceyOznAM5W22zSHK8qbBIWUYPSBUtxaiuDZg40dcruw12F5Dr/v7Re4uHVkHuKP+wjqCoMPnJwMkfjGr0GwApGgScRAa7Sop8staIs0yGfoygaNB80d+qZUKhVzcYbwgm9GpKWb2otoPnrs6Gy8NveXtS/J3nnr7eyT3/3OhH7A4rsBDmyS0BdOM1E4Ea/AL3DvRw/VZ6l/BsO6MRmPwoCBYm0wFNJYsQjSFBqB8/T+/tZCSNWlwrVpNiGoXqNDKQlXrNu2b5MSwiUJg55qk/GGlcfqvRYer4nw1rfTrunXMguexXmzyin9bUj/86ku2sCG36lTXdk9WWZynuxifYew6AJnV4bCckZg9WPjQt8tXOBBdfoD3iTGBgpTWLrxHHFxBTGsqTmjhg1FQOLqDYtQvu21wQngykbD7Rt4S2XmafxXqtWsrX2xzZ9skM5j01NCN6RFbGA/flS4WC7DpD4UKBD+Y/UAD9vaOl+bnFXjEWmT0UO423dZ+cu4l2EN1j04MgIIjx8/lbLSVbk179LZr21676cbre/gmrDjRPbN19/IuuC49csCWW6wUc95fNVKJVugzXssqfK5QCCtTwy2j13j4UQP2mztplKvmq41vmfalKmyWF5owjvmnJgbAk/676LGO0K7iRMnSTmp7pxLg0duH+k5fOp66QBQx+ClQbyhgvYeDUjdTtMCFPQNmsQ1pdL39o937Jn6dYy5GIT3xiqdvidEKXj1juMderH1X+/z2zvftrPrftizJzuw/0etLa6zqEjlmAPoD+tog1P/w7sNj4fQDkU8ziuDt0L5ZMbM6bYBj8LJpo1bsmWyJp07b67WA3JTyHcsjU2DafcFnkU9gX/CAXxyQgde0bqi1Kvdeftx/Qm+nK/Jt9fPkBWvbvMfNUT9tbXB/RQpfd/VlvQnBDUIN1B4uyGhOeeFj9eaauasGbKQX64jGjaY9d9iCYOwxEN4RAfHe27vvCKgSk6quorSG5sqjETHNcaMAS21JHJxZVw4bD/f7fbtX6SweFSCul3ZX/7yF1NepA0xR9V8A6BfCRjCth/379d5ZnNsjTllw3qdZ+59Ci2sISk/PEyL5vVKW0VHK8gFtYSEBPj47vPns3//93/Prsua7b/+f/9V42+G1U/dRhtAlQjiFPJeLNrjbUMphfPUwZM/FCQQot0SrpZX/D2ehXifnqidCB337d3vVmNa72GdiZtYFBkRXvs31evnu0eznmE0Lujgd0LKG1989rnWKNOzTZs3m8BYalX2TdDbYnUtkNAOYTzCLLz6uECdVuifdTbwHH+/e3O/0Kwc+DJDSM7SvIjLagn4b0oJDQEdHcyeQVu1KoHtUtvbQSH4/PlzpkC+RkdXrJJy7axZMS8VQ8LnJ2pSmv55iGt6zC/E12OWJzZvhhAFyu/pEEKriUqTAm+UAv6d6m1+e6OoPbfy2NV4bsZmhiYFmhQoUYBFD4GrOD2sYHBj8eWuL7NjEgixMIl0Y62Vh6ymaaudNhhOLKjYDNkg5hoNfhjUmWKQmVDwJc+VfGX2yDXcPMYYLd3aVZXBcBrTqTIEY7J1RbMNNy43rsldiBaUnK3HYdUsChcvwQ3fXGPWTaudRegTdsoA7EIwEybxrMCv4+cHSLOgwC0FZzuwwcPznTt3jSFG44vzRT784P2svc0FdrCJ5rpNjDx5L2iBsHcv7jkPmkYqZ6+w8fFAAphol1Vc+rGpNs23aLESxo7X4lYLr40bN2SrVq8yl1BYbt0TTqOFOxp95851mxulXbt2aWF8JYfYWz15hn7cOHVondPoqTbHhj/jHJxxtkmESwq0ulmYs7h6oMPsqReBHQsyo6kWHSxkp2gTaOOGDdl7O9/LKouqtlBhHOB6h3K/aIGF5txFLe5D6EjFvqAKTPqBdF2WYNZrl4K1magDDUXOWXxrx1tZpVK1hRC5ojz3wKBN/KWuItoCz4Yr7VW/PFPbeMatTafctR6Wa1Q2DFlgm7a60WRKtnb92uy9Dz/INssNFXWNlkuXsaPHmSuiW1rs0qfQ9hdtXLIgY9xGAH4EcHqTAYqAQmA0VsIBxoYLAByzOxLYdWvjtlubONznOL9B1KHhNL1j1aoEdtqA4Uw0C9YWtnnpV58fAt+gO+OhHIg3oSWCvhQsR+obK1fqs8hjVzKW0wxEGX4Bs6bca3iI9kZV9k6ADu0yyaanOLZOlRxb3fi9L5ztvtws0TDg90xzuke9XCMv92zaTJa18wj1Gwv8yQ3OfFIusg75wNjiG8FmIGNwNC6PpDm9QXPmOllVc15d+V3ino0hNg1tc05lewvMs8O1GTpPZwm1VarZO5rj3t25M1uxbJlcY06y+ZZ5hc3SEDZb19aRjv6JKO/T4rlcN+Oj/t0op/frXhXwOqRXp19FXjVTXlc0rgTQ2+NjmBHNM7zIrJmcZTfBNMQ7Ok5kP/30UyqlHEYsp1g+blMlQcdSFa9OszKwXu7pG/82S5CmTbJz2gA7qG8T36d5UrJCeIeyigl2Egxwx+oGTXZcG6OYFXMhWbgvP9dXjacAXKIhsDNlLeHAeEMZpT5Ql7k9U8IjCQGvyMrgqAQJh48cMY8GuMZEMI8rWDYwH+mb+DObs43ldYKSBhJ9Jjz5hl7TOV7wimyWjlPfTZ40VZZjctMp/tTPd6vH6jU8p3EBXbq7z2Vffb3Lvp87duxwLw7qE/hxLDbgx++LB6xWq9l8KXNNlAAGARvWNpUqVuKTTCiDRcZUuW6H/4TngLYIauHHTDlOczdxRqNSE4mboQ1ivEcwFz2hr8Q3ez9ndubfCY31Vgl/2hcvsToMTAlG83ZoUCDeS/qHb8q0qdOzWdNnmmUt6wzGAoG5DGvtAwcPaH02LPtZvCeWOvt/0rpKbsxRrhwlAZDeTitj40HfXwJzCsHnSLu1H+Dzx7gtB75buLtlnF+6cDnrWNJhFn3TZTE7V9ZPk+UCFxeIfON8fJZL+32Mt4S+IhnbBH5jnraIAfrx+Zx6mRuxeFouDyt4C8F6lTPE4sztviqEUkG1uOszv5HW6Us+FCXu6SgEvKownxJOnx4lQc1Fuem8YoojSyXcwKILvgCeCEvkWIMG3aygfmJ88Ew/Wl9GdULUcBWRC5x7zBZAsfFhMLQGjgCPf+jQkezPf/6TzqzbZcdWoLzB9wfFNtZJGnmR3a9Fh9q4wcJuiub7xdpTWLNmdZ6H8cq3JAJz4MqVK7PtEubc1XqUcgR4H6zdLtw7n33/w/fZitUrbK6EX8QKkbEebezZdqV4w1M1ymEE9F5E+ZI1Dt+jmxKgIqBDmMaaljYy9jmDEIElQro5s+ZkKKzOQdgrV44tLa12pIQBpx61HYUlU9LTlW9BV5fObtP3s61tsbxItJkbSPqMNK68S+3tS7Kd4uf4tlFfvQUs+aIHU0OGxMVGW6yV1N47ssTHbfWxE8dNIYczUtlPYu8DoX/nyVN2niPeZ9gr4BiUq1evZpVKxfrJ+ibG7pBoYROJJgWaFGhSoEmBvijQ+85FX6WaaU0K/KYoEJwJnJ5C4sRhzoqQmFFlJTcpVqo2S8pOpCcYgyc4aPEd12bJIVlOseHCGW52jpuYZcIwMcz4kf9VGzjDRw23w8fxqf/hhx9lH330sR2KjHuOwAkmtrwAM3wE0xhl/cKEg0MsIuHlyG+biLqi3cYBzeEy8p6Y/292f5v9Xue24TaCxcxqMfUff/JxtmXr1myMBEuj5FJMACVIcyHY01+fmDa2a0W6a042aYCNf3Q0v2/LpSUCklvaCOKQdgQmMIxcl0oTfa00u1YuX2WMumn6QQstVBDusXA7LoaT89ywkIKJvydGNGgK3RoF+sVcYuiKhvsTuRZFq+x3Ouvtd//w9xJCthktoJGfkzVCxmkPzKXTp59+qnMBvjP83SqtJ4Nu/S7Y5a4v4xHp5bjIG1cvPMyswlavXq1FYavVf+LESR1afy7vZywJWQixoIq+nKGNzY8lwP3k7z7RYmVhvjE8SoueC9KsO3LsaHZIiywWHBEoG2PH43JMIstzr4wfqGFu/epabyNP6bhvZUN89crVWoSvMGsW+rMcrP+ApLFiWKicekx/jhNj1MaqHodpc4to6r2tzQosHzlXkQX9uPR+spCconM+Vq5aJVcpq7Pp2kghCaFhy7wW016/KRc4B3XmzpGDh+2sHdxk3bmTNDlVv9VH+xLMuJbxfi33IgPvEPWz6c+ZAmj6T1b72CgkgCvnPF6UBij9fV8bDk7jugX7ACP8PJqA1yRtRIMvZybQL5RRc9SBGjfWryClOFtNB4LR//5sZeh0BZvPSFZAYSBaCMxnerd5h8lP3R64ell7jnjlyaMjayoRuXPXmLUQUq64pNxRKKIblOmLXtYuyiZc8mfgKK6MYp6W6iQt0qOOuPqrphIAgS72jwJeOPIpxgJ0I45/KG7Qf2i6T5wwycYU76h9S1S8vsmBF2UHL+QtbVhFdC+JdDGBNrGphruwZbI8qFbasnVSdlmub8BibdQwNnlf+I4B3QQfKoOCCpuHE8ZN1DfrF6OJASz9IAjEFdPSJUulMLHTXCtXq25hxKYZdZMnsI6ixfjMu0JJnivycqUJQU2eI4f3UDyTI0oV7S7HKYPGUSpdZCWWJAXvPUaIP9llAH4cvveFYOsxoSHYUXdcvWb/DvxqVlpjZLUYG6TwOGymFeWAJbztj+g0X9v4BZZCGbQeexujEe9F6goR2Y/Ae+P4qVpw0B9a69/t3q1xNDVbL0Ur3Is5AbwOcMetMZYJbKbDB2HRRippwOgr2HecOU9KU0+fQt/0/paKRXcznwV+AROeCvedCxcszNasXat3ZJbOcVpu56uhHHVIbsN6C4xB+zalMQOuN/QdRfA1UzzJNAm0lixtN/7lypXL+ftYhqcmWnhOM8tFXuie70O0n3F18eL57DPxdPCulUrFzpWbJCu6yVJ6Gqb39I6UgNisRwEOaxt9pLIxe8bYWGSe4I8zkxD2b5V3C9ymjUxWcI+tD1xQSt+NhIfXN877u0CbjWDbZNZYePxUrtnV39BR/jGkbHPb3PBi+Yj1C0LBCNal0Zi8VZHa1zUGQ15YmbmPeMrGfTlPOZ57T4s+I2Zohd7aEFiW2xZ540papEdcXCM+4JDT87OuiHIT9a3E3SUW6We6z5iLU0rEe4zr1SPHj2aXbkj5TwOeDfEQrD/ReGOc+HsQ9dIrxX1Re+93CDI4Lw8e8Mzps5pTdon/mm1nHW+WEB7LmTVr1tj6arTWlTSDs1xpRVmRIGr1q37tRj9k1EN5vuwdm+enOHxBS/wIJTiHjzNgd8i6+rYsTTmXuRDYgYCXKkOvp5Oh2TBnUaqgdeSONPDxe6ybsfCGnh1ai4Ur+uqiip13t2njpqy6uGp8EiWir5kLY33GHBDwDCroWxMca3vU+280VUZgRDA4ElrCnxAN7/VQuHSe6Mh2ffFF9h//6z/kXlcukWWZS/+RnzFgPFoCEvBIsw4ULBRmHz362c43OydBHIIaFAiK72sgyRpufLZa1lb37t7Pzp09l508cUL00LpbuIApOa9dv5r94Y9/sLp/J+8CWKXBP4EHdHBFkdQuLiZQ1NVarQj+40GDNOVHMZX3CYEi8yWCQd6RoBjZwBXru67OThPcMa6nSoA6XVaR81vmae6casI98pn+igqJilozCIrqwJr5jIRYJ7pOZhe0B4FVqrmelgU/+wTwbQgf33rrreySzp/84YcfbM+Cuo3WCRsjgOKGSgA/eoVvSgQUQ3D5yrmyfFc2atxO1TdvsearK1Jc3b37e9sjYI6CB8GaEGs7aEI/hlecAmJAVr9RoVdqfRf0YMlRGxgpzdCkQJMCTQoMfQrEd3PoY9o7hk2BXe+0aab8tVLAGN2XbVxiUmC4xcmwOMfdDeevca7BLW0EsrGHEAbG/gmbL/pDpMGGNkzjrFmzJchpNzeYH374vlmFwYxSztxIaGMghCAwol5jDRdlyNewS+KygA+nZRtkYu5grrHyAsYNCdM4bPnzzz/XYuBBtnnTpuzv/v7vsnff3Zmt37jRBAcjcAuiAL7A5gpjiJa4HoyTg1mHYUYzDWuxWxIa/azDqhEe4dLitoQtaGRjZYem2/JlK8SkT1F7xFgPc/aQdiLU+/rrr+S26GtbQIErdSK4I0Sr7aHux3HTL4tr/YcBZUH492rP9q07TBDySALBUdoQHCmacM/GzlEJuk6ePCntNPz2y6WZtVeWgmmx0YMfrau3/GiLMYuoxdQZf+EvOrEhyWbQhx98IIHhw+zzLz83V6Q/SyhFXbQDzVyNlrSpjGbvaHPdtGHTxmyprDrYZEeQGe5vTuvsn6+++kpC1x9MSBw4Ud9ABNpV2yKHykYGG9mcZbNm7Rq1S4d7SyvVDyanveRzHPjl2Z+8fPwGjUlnvA4f4Zr89yRQPa1zJhhDvsgEhkPgPULztW2xtCbnzs/GyJ0qabi/m6tNMu6xpFwoQeKClgW2edF94byfb6cxymIUy0Q2CcgbcAOn13GN/ra6rFkuQJmlsypp12xtrJpbHmVg7uD9YoORRRla176IEpREk8HDOe+hHlWgxY0WMi5nQqEAS9I85B3OKGIcJXS1eVPPLFkO5iz+9I/A3BJCNTa+sMBFE5ZrbZ8FjlZMP/GcIxAJQ+8qVK0vhVl9Vxa0aNCO1ERSuI0WC4q/Z0qIsV1Pa+LL9EYTFw19rH/v3pNGP2UdSkN6Ubav9CjU33yRv9yKIq7nHXPM2HHjzToL981jhXeLrGVa9b5jlb5MLq4Q3M2R2yS+BdDm4cPH否9QNgiO1cTcOfO1AVvJfpYFL0Lx8kYYYxpXh20S+r37zrvZO/pbIU308VJSQPjHpj/fSN7PgQ/ALPd79PTL1TQYGPYXk7xumpCa5O+4fpWIe6vFmvNa1YdYRT9EoFX3MvBocHp7WfqLzCvmCyUjwPAOXBAf8fU335ilMa66sDa3NPAVwnwncU01WwKumXrH4D3wQBB0sMzP+aEesUMKAproly49Sto7X4plY+67774315hY6s+bN0/fltm2Gf3jvh9rLE/Bt47sPoeUKr0hq70Tz07Y+cAInVpaW0wRhrP1+gqNYPeV/0XSoMXwERpLYnc58++U3Gf/eOBHncvZLWvCFdbGqeJNJsryCIUtNjqxLMQzAd+YGGu871iOwBOyiQ8/i+UJwklcZ8KLwX+Rx793vsGuAxTFehY8OsoD1UpVNF4uHuOR4REkpE7OVsLbxHpteuOWk0Ab6mlvCS/0AxR7S1Ip7nsbKS8E+G8ys383C/ohXF23fp08WVzMHj5+aG5n43tBXsbVjZs3shs/u2vfMukZZwPQwfZ9YtOdP6xdCaM7Rmtzfoqtoc5LCM/aa0FLq1ncIazG9SDWvRFivDNU7Bv96gMvQPdxLcblWL0/rHfxmsEa+Ucp1vHe1o7dPkC9cFLRhxT177Xjw9qYtQB/rEGZr7GGxgL27JluKdtes29+pSrrWwm8JmntilthnwP87PWiZXRx1BXXolXMgfwRinzg44p5xKHk2nWyM/tS6/Ivv/xSQsQTtnZGIEYf4jo61kMOqe43wTdhjuChOHzqVJfRmfkLvLkyLzB2wQdhDd+tVVKADOs5g6omDNO8Srh918+zg/fmyI5q1ZWWnEe1LDU/1k5IIByMEoowNglwimDtMF17HCiy4JKTsfD4yWOrDzoA12ike/YPsL6v6LiFqdOnZAsqC7P5uHquVORx5JzOZ5OFnngxxrJ9BzQXs6dAedYLp/U9OHTwpwwLvRk6+51+BAfS4Q9pC4ItLOSHnRMUQ9jTLWNNy4bOQ6KsIcSYOK9zqFHO4WzLtupiE9hNmDDRrAvx3DRLinm4WaUPmSewXqTd9PkoKa55Tw+d9jUxaVKgSYEmBZoU6J0CBVfXe55mSpMCv3EKJI4ssSjB+MCwwPTBpEUcMYScmclvLNr5OTF+cKNA/VmLJbScduu8DFzYxYLOFmwBVfl/FYM1RsI6NHo5zPl9uYnENQNnEoRrOZhWd12RKqUe/feavP7Qqqdugorwq6sL69Aks3NVFIuQ5+njp7YRcfKkXFDp8OHz57szNDP/73/91+y9d9/TxtKsbMLosWKccfEFLIXhWlBQtTY7EDBm2ui0egItbSY9nfhrxvkdj7QZhHABgRQuPB7pinYgG5xYc8zVhpHhqrYYQy7w0KhbmnB//tOfpR39mRh0NFPTxr/qiHyGS91PtBug+MefMn2yWddtkoBr0cJKNla+79lYJYwU3gi7zko7FuvCA+onNJ4tqPwThGXQOA/ewGIs5Al+o2RyYN0SpEpYWzr9N5KFskAiHEJ4N3/+vGzVmlXZOVtY38ouXbxMA004gztLFlq+kEIzf7yY6QVaSC3Xgmqm01556MNsjNONcy6+++47nc1yzBZyCL否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否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否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否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否zyu9ccLwQI+tRDG3b17RxvCZ82ROH7swMXCAmERcBOnTizQlu6UOQFMs6E1yKZzfIS8wj4RZCrDhNMneDbt8eT0y6TPNqCM5DQFFJh/yIJ25RZ+TCg9kRPhO/duy8+VfKadv2CTKE5wvf/++2aGZJzMVEBHw4fQZlpeIPFOC2UF34TbbuM5I5Eo6MmiBn47uFwB3YQKqw88hlDa6LoEP/iMQzMZAdBYbbwg9BibNvCpJyYIEaCh9YmZFcxAYGYLO/XzZEpvxqwZFjdl8lRtTKFx3WOT21vSOuckD/6y0L6bMnWyzHag7f+OFqpbiyU6/cQGd88jaaSdvyih4V6zcQ+TJmLiUu1EmCic8+XnAl9jCxcs8jZREqcDqQNlEnAMff7cBVvQYhax0IkizI30PO/xtjSo4I49NP3JIUoeKnKshESwhW7AhuG2d9+1E3/vvrvNNhmoS9OgDORxXPoVLsqAt8TRzyzN+hQJwDo0+KKfPdAmGT6xjhw+5L4G1V6WqsXe+PHyyzZmvPsPkJbl5MlTDD8npxr6cUJL+R6yu+o2EmswFU01AD1G5hwm4npDN4tR9csAvwhlnJ7LcSFF2rpWeYKP9AW0WldooYi2I4E+i//C8FsHmSy+2AAPzVQrlmfL0fhTlp+i63Tl7WRtIbjAE7CBMeKtaWNR3qsAh865GPmbXUucKrVRYOfQvEFj2JmhUCtLOTAzq3fn+d3n9t7f0IbAo0cyOyteOW3NSuodVwrv6sTGsxHn34+I6o3lFcdAY8XE1sQ0ganqVCXad0F8dnM5M+09RHsa2OgP9FM23q7rVDEnhzB5NXasa8nbWJAAK6zeLBBX9dKqdENulHsO/83SLc1zl7ctbqB/uEKFubqjrOqpotnJqFIGRlMOX+EaWN4cqq+8faWlVyxHpft4T+KVL5OFqmH8UKUr7Lorq6Obpu1SQYPTu43PkbwMYdf4z5wHpQ40/9HAR7Em/Hzl5UdxjVhLakfkJqfHC4QqKRnI3PU9+Ru+yVilk+UoCem1srF/JAjriydQeO/OPflpPlos6lyk02f3i/naXOWEzGxtVv4sU2HMHdjMfF1D+e1NFcDv8XX56EFgx7fETpOoA3K6oHcbDmOtrdPqR2VDI/PYo0ePyK/QGvN525/ArjllhlRJ0V7x3By6MXYwsI05G5+a4SHuRWhqxOw4muGvwzWDaVV+Y3yMRTnG6tuCIIIUb7P4ZrKu6OrqKrq7us3SxzUJXz3U6YiyUvIouXidGomhT7KuioBy4omTJ4r/+I//V9V/LpOWnRLwSKisj4PPS916CTWGL9OmTdW6eKYpYoGHuQOCgxMnTkjwsatY0d1tayUU9EKwF2XxTs7QmnrlyhUS8iw1BQdOQEn1MAnsgo/5NyNyt699ccDGw2CfAFFWOK21GacfF0tBEOsmzAHzMFGuC1as6Na6cZv5O0WYhqDPlTg0U1ejV+9IulcZTzVfZ7zF/9t+mUR9S31m1YqV8oM4Q99AWf9J/Qua5i+Ybwqxp6WEe0PfHRPYGRH+DqlLmTwwI71Mjbi45rT3dU+/N34koLs6fXZQJ/kXy8LJAinXTFu/3k6JLpK7k7c6l+rU6GxTFWJtYnOcrECo5DGugIx0oD6tAiahUVThtF2P2nyC1kz4suvu6irWq54I6vBTzfz+sNbpnHC9excXH7RnjtUfIq7kXwlT3qRMPAdhcc3xte/bHGhz4JfEgfxb0V+9BwPbH643JX3UC+zajfamdLXRWI80idAMhDs2IzGx+KOciONbgoUG/Q8hDJqTTF4XaeKJHf7NmzeZ7zoXJClzmsXYxKnc7dKEpZzDqAQKSUUGN9AcC+EGcdz7P4cwgZ9wj5UjgQtmsmBf8aNO192SPfV5Ml+2fKmcfEtjcYqEHT3aIGo1yYry8itLHjYqGsorAfLJVhnZz02tcv1At0xOaKCN+rBxRDvghPy4tC7/+OUfi5/27inuJ1OYbLhgohIt9s6lnfp7y9qGhSMLQk7SsVnGYgQNd07ZTZ02xU4yMXm1zWm1NW3MpiCLywea5GKaY5LavGO6+3jpFJ8x6ULAZx3+Xm7culE80YIWAQunAPBPM0mbi2vWvV189NGHxfsya4j/MQJwLHQmSFBFfehfFy9eNrMkV2Se5Inqgb8u8Izp8baBFd4SffOW1JhAo7VKv3xGv32qCbpomzd3dvHutneLv//7vy8+++zzYvmy5S7sgLAmoXlpUFJRQ/sQ6D9+9VR7IF515MTiPr1T3337XXH+7HmjCToRhk6cMKmYLpMpS2Tycc5MLYhEM4H2GFDwYjPQnGruc4D6c5atvM3hiczxBRAwzeIjvck1R5vfiw/wYpz4RJ9kkwOe1UOUJlAFf/Jb/Sb4KpenO44q1u5SkpdB2Smd+ArUs9Z/e+3c1wEG/0yRtDnC9QjUkSoZjbqHP9zT1zhxyonUW/L3c/HyBWm0XykmjFts43LpBw9EIE51Dbz5Na9qBeaxkVbF98+aHPeI3UNgEDuIQu2dzSsnJNHlEH7OUB+kL9ImYZqINmHj7Wf5o8Hf0i3zQenj4CCKzkBfkPgMQ+NtzojUX/xlaQTr9wk8DczpN8fQAdTLzes0dKW8KKagxoaMnNR4YftC3Edb0PdIbuiX0SGFk282J+26u7tsA5DNQjMP3ld5rzjNeUWN9E87fA81J7ilzbBx+g4zV4nq9cGWYa1BfK8f9DwoLl57qFMWp+VD7ZZO18kk7lwpoy3qLG5IsPXgyUPNSbRxn7f3sFI2vMhN4e3ZHRPUmV9UNUSz7+3wUiHstXcGc2tsaq9euUr+u1YKYKmRkLOdPpU/G8Cw/Yxsac2r0VdtR5I+f5uDRhurEmnMXfD1tHbdOjuphI9mlAljHt7vRCSQjsJrVQdXMrx585aUCg8Xc+fMl/LiVgl3ODE805TuWG9Ea/E+hXltBECY3UUpkYBA4AetrcfJ2glKefgvY31GWbHWjjUAayZOLi5fvsx8XzNuNgjWKbCxaayM9s/AOYDAjlOM4+VPcLmEp+vWrS8WyPQl4ZkUNWjViRKgrlq5QmadH8gSzFFZHfrJLC3Q4NUwpu8cDxHSLXNGrGF88cUX1lb/9E//XKybsa5g05H2lkaLjb+YYbfcIBEAAEAASURBVEcZ9pSEe5jFxN2F9wkXGnOfYY9SrPzy4SVv7qpv7t6128wRr5Ty5ErVeZL6Jv0Tf9lr1qyV0ulRuXW4IR+Aj5uW1ozGpoAjHEk7IPQ+IqWQNbJY0NkpQbjmVSu6VxQb128wP7u7Za2IuRWCu5OyCoBiS0/Pk1KpRSs1o5q5S8xfRrga7eIGyYFXMrcZJI1t8DYH2hwYOAdGRGD3IgPHi+QZeLXbkKObAzET728KFHDUJsGmKJ5ijzk2KPqqM0I5BGLfyfTH7t27zHcKE0XyPu15ZpuXmHPAhOBGmRHAFCbCnzw4Bf5rQgdo0Z9NN9NOF36DCCVNekRoZ3FpAyH6vi9imCCB6LlNfv/2t++L40ePm3mJNVrcow0WAg6ukdcQUriFoCyf+Eaa0xeTsOAZ5FL7gQcvY+Dw4PcQZdtTRPJg9/z4hj3+DH7c+UPxxR//oEnmYROwWR6Ria38lTLxgIBsy5ZtZlZljhZ9mHfCHCY4WAyaQEzP3PNX8kvF0N5sUsN3W1AoDtkRwlI2sydPkiN1NdWde3dkwui4mZEIn07j5FwbdoEDX2T43/n7//YPxcZNG01g9kjCwMdaII0d4yfLnmoyfENC1+PHj5n/EhajBFpIy1e7j5+81SIuv8Ih/iLQD6gbAjsCC2jo+Pjjj2UGc6uZ9ETASZnUv4EPgndcznNDUPYD+gOVJFY/USjRCmWf9kf7fSAtyxMnThWHDh42TXLaGh491umojlkd9j4tkRkuhKoR8jYhLoqJ9JKc3ikliN/Uc9afa+ANjwGbKteQVsWpKmUoY/M4dQsey6g8g+Lhh52gNb6GyR9Q5m+fb4gYbFV5gzHzNhRMosXwQ2lqjURQ2S4eTYql2yURVpHlmVJWwXko6ddjjBGUQcjTLCL7oa0J0aY80Z/NgJIIpO9xErbEJXjrk0JtsDZuUg718fLQ3EVwhHkeFp7zJs5RuuO1G34c1B+ryqVkOOA8TRFlPDeRFTrNNLDijJZgaAYD/EgG56aXWLXDACiIjIkX3i7wlLxeYxQFpuo08kxtxLFR8VA+eTB5RX9iPLytTQ38dnLCju9OBOOTEPGvZF6JVTdRdjDWMhLZEJFiE3DksdiB/lSZoq80K6MVNutq1KJC0xs0kZyD9K5FxORQvVER42V6WlVu5Pf4EKxn3c8TGn4jT6syIz3PFLBZWnZLu5XvrzZlCbwPvXKVfcphLJ3KKD5Hl5dc3XsfBAXfPdqNU8RRV5RNlmnTdp02xW9KYHzunJQ+9I2OkIqOx1FxNQ4Fk0QRG6M3dYoKutkUQ2Fn3NjH9k5V3BxG0qMRSppU6hhX7urRxizCxCvyq7dk8e1i6hS+yUuL85cuFJeucuK/NV2BFog+wFojGOEU+jKnHiH22VifGzGuRR8fUXJSx6VszAyfPSuzZbKawYnMCPC04jFPzmWPq1ICfmivOX4v1/Hn8fUSAy5gWj2TL2DiGrBxreOuP0e+iI989XjSIy5gIo9fX9SMMWPVdM0/8HW1TApwHZlrBB+/9J6NxgGqsfoNTzHuNiOb9ct5WQFhfczJ543y9zVn3lxXeGRhlALrja6u5eYXk7Hv8eOblsJ6A8so38k3PD7XP5Bfb4R68Ij3kPlHBO4X6nTT2rXrTDHrvOZ6t+RHNw+vG29z2kfDPfx78OChLPaclcD5lPm0W9G9Um+L3he15zPte/AZx8f4Upm0xOwrJ93LoPz+Rnk/p8/Ym6Yf5vX83dGa/ScJ+abL5CZmNVevXm1CoBh3UcZlbU0/wHpN7l7EYFRCvZ0p50VDNS9UFRMi4lB2Zn6Bm4vTOnF28+dbUl6bZfMRFIEx03rkyJHi0JFDdlItyoeUlyAn0AzrlWqiSHD0+LHi+IkT5mJhvqxETeuQIvLCxdauHbq/re8QirTM8S9JcIelIU4QI2Afq47g9YyxVG3NYKHI4GM5zPZZmyr/6OdcnxV5JYnB67wf1wnpK60O+yqeRzt9r4In7TKHnwPx7lDS69oHR0RgN/xN0S7hl8kBphD5BCDnQopvkczL+1xmG/QjgcwEE/6gbfbXv/5VJiJ22sLZNpPZPGI/WYFNzG6Zh3jvvfeK9dpAmipzijaLKcvwKY1DN/62SmEaFLWIKzmhz8yN6MqmFVqKhw4dKv4oYRWTqW2a/OLzasOG9eVCh8mvs4O6WdUaiWjxBPll2fHQAnZYoyEigu6drfpVZeDHxUuXi7/ppNa3X39jG1/4/ZotO+sLZFIDbTHsrm/e/E6x5u21Lsg0QUAgHJorwoJDMhvxhy/+q9glbVFMXdFP4DeTfj4EmJ5EYPfRRx+ZmcPH2qRjow6fbZMna/IrAeDde8Jz8ID621+Kg7L1z+k+AvXkJBGbl4MJlAsdXNGoY7HDQmihTHy8t+294rPPPjOB3Sr5I6AfWxCs975gPLxuUWrE0yj81cizRZ6izQ+jzBlyQoe6YGrk+PHjMvl0zE4vTpYwExrp27TdBgkS12/cYL5AiK+HKLYeP7zPQ1Mq1QETtTKM+qGfWFCk1zb9CtjgDYZWYbPbgU0QryeHDGQJJ8gCZ/2e53oA否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否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否61Z60S4SGAZ/54rnMI/PNnOGaPYwLKn0iAGEZ6SiVYWK/KU5CrXokUhMcXbUKfM1v9RUE+BLhaHkVQuAU5Q9MpskMHndaLF+63Bg3lxNM6H7r1m0x0zrt7hI0r2pd1RIF1IPSI1BIn2pTCQfFCj9vaSSrfPALUA4wfvM4eZoKbsQMeHmKCmbuqxRVQJW8fEtxU2ePlDyFZQ3WkWeZNL1YmpQwMI1nRpIaNLju1Gs/AABAAElEQVSvB80DTI9t3LgxMcgmlcJmy1lAaL9el2SgvxKwZ45vnVfCyh9RR1aaRAdMpNIe6Gf9l/uL5/29xZrVq4v5GscniGkKT477S5k7rd9osLNn5KTwoAztR+JwQwLw+LPuIV/CwM9O8evBN3+2Vor5SwzBRRLYbdu2rejs6tI9LJfsLkWSWbmtcLUlBEbuyMPy0bM2JI/18b6PVmY7eCMtuwtdPTYuoxG/QmNqr+od7Zame80UoDHGUkotkjuRsACCWfEbO25Ie8mZqVFv3r9pu/Lx/9aPHKuIVY9jI//oBfVx6/3ztPVhedqIF3HiO4/De4TzPlwcwkZzpA1Y8Yw09d/hzzPCxp63D4Werm44SYAdF4RIy9VXuO/qjASuHGzB5Fy9qx+T6sPf6+86svne2xvwMzH+L/b2FQck6Fkps4l79+4zyyDrdN/2Rh0yfHj/gZlCjPJB0RzcY9v/HpYwrsUOKy7UfaWtcV+pInMAcJG0vpZLADhf+2KEhNyrltMzX1dEPs3n2CnA+MJB5mvXbkjj8YzV5yOZ6Z0pay5acKQam2Am07H6g7nE77/7Vu39jI9HWoP4AS9qtzrohcadOcHnDdPq586es4MJN3QIbN3a9UrPnE+o9tDS3Fusul67ZrV4IbeLW9Km5165eue90n3ztlQfb7TvRnBoS1juYK1xXILk3bv36oqULXaom36+mPapA4eNxgfgBcxXwWs0vF8mHD4TAtNTsoTDWurZ82fFNP2bNHFysWDOgmKD+uqFCxfsgNijx480jp0T/+KoTJWulAC+Opzsc5FKlxUwLLCAV5S/JsK4Ea6gvBqccWf8QSWIdcEHhXQT2SYFmhQYMwXq+YhjTtiM2KTAO6NAtjiwFUE+n9cjZQvAKgEMh6NaePz8889m6qBFz否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否TjCJVPOuLAp7UUeQd7fCuLARwzw93Bq1dt1bm15ZZv7HDRsC1tpBaBuUJwMoo8oo+XuIJQuFSJMoezgR26qMcRsEk1NlzZ81EIvdChiNf8LUc9aKowsVDc1j4sAY7dPigaWtt2rDJ5jkOwQCDPuY0dqQchhXE8if9DJlVY65dItNN09U2w6ENm1BIXlEGhxXxeOIz1DeP8RG/5wUPElHc8NfzijQXn+vU/XQxSdfrtP2LVSuKzu7OkijezpTEK730b76MgwIZvSOV9QF1nufPn6oOLtsBvmfpwBO05s/vr6Ti+AsgAeFln/Vw8oaR5xPx8vCXzfNl040l78CTPMYSvx6XSJPDcWq7D/NCSiOPGOMYKxmflq9YZofrbN9WDxqMUtoPuvtk5c+LeFOHYgcGnkjY8rOY/ltsvb9Qd2NhPhFTmdAIF2MIWx否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否poOB34hrVL3s21pSHjxy2+78Zr1ontNrVHItkwaCjY7WEc6es7Pfv37NrQjp1f/UKHXZEkN5SI7CLGvH+TCHDJxoV68Bw1Vv4NJ8fGwWs/39shfpIyxNzL8X7FOvtQyh/Yw7ZR9ogm8X6sCigad8QLpdmDWf45JlWBr4e0FJSJ7LR7pkopg/ukk4R/SBG1/4D+41BhF8w456JOcEAhWm0tWvXmsAEswDBYGqYrdJXC9tYlsSzShGDAD7mq3x4ekz/hTmCWYj9OmV2XEIPtP5WrtDpJp1imtbim5tyACVJBb78yL3yRZEilHEiV/eL38A5vnNI+NWHR7y6Z4pWpa5PV4UMhZmHOdyhqT1OvT+xwy9bCxqQ8M8IbrGpt5KeFtO/pVdhC2wuiTfzjtev65T502Lyi0nSEEMAMbGYPWOWTHesKNZoczFfC1ZOk3ISfVAmysBQLK0SNnWPxs/xY8elZXfO7MSTL3/cVcemk7sSf6376f71X/+1WCetTk4Rot0CQ5IL3HnyB6MMK4LARJPJypbIZn5RWPlV/QbaeAAM21r6ECsBSDSoKFl61L4AW23T2rQR0YE+F2MJUxmntZm5JEYumzVwxhGXd8wEzde9f5zERNuunv5RBnCsxTNHISukeYN/+OXxhr7nsepLXY/L0NTj9MkyC6b5kDwBmeJl0S2jSMOHlbAuAqSPqivh6iWixRMfp2cZy+A7XPfzvNRKLJF+7AXGu8eyX0U1TeXhK8YYvPclMGNTB7NqtsZS2jiMfvoNjjp2EClvfbdIKILADoEuwrvhXInfkAhVadkcc0G8mWW9c8+E7FNMM8jbFON9ixielAUTyNev3zBtpVWr2jXWLpXwfKbhG22Rp1O1yoPsHZfqid8IpCG4oRup3b1sGBkFtiX+UQyrBirT/g9tW1SLhUUcNJn8fsHozwEd2DZOXr2mu9Wu2unrmdISrnHAS85w0g9Pbw2we3x0cnmdxYjo2TMDUvqGX6SJ74gQ/vFdPfMxj1ikpD6hN38hqIBhyHfrL/X3pgQEz6MsQ5UFEGu+hvsgVvxBDWtD8shTex0WxhTjVDf3gKKpzann06fPFteu3vB73JSYOkIYj6Nf21sCFqZO8cQrD6vJMfIXUaykFjHRRwdRoAlMnusyf9zZ2WVWC否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否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否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否CGJ7DJRzSCauacRPEFBe19KO5lCsEqo6e4eOhPwhXS07YRPiBkDs2YPEWV3gEZbnmEBu/WFPQD3YlvgnfhjrCbAwRs6jAr8kB9iYvK78m0KQcPEGiiLcf9VNcknMX8SH/fJdsAc28dNMoZ0/VZBz1yf8o3dhdMQW3Qda9BjzQ5z6mdrZDwBI27KVNaDBRlgpk7RWMAG7tgxD+S4KxPY/ElMdoxz2lMXqWYAKM+9Q8AIIhoExMY5tagDjeYacqxopniWS0abdHSHDRmzdUrV00zBBq1CFc2+uCAUNPNLRn6VucNaSUcx1K/DuXD+WU+oy/yx5iChu5FjSWzpNEGfRDyQDMbz1SstjadZFfdEGZOdGbEauTq+39Jv+i4qi9vg37Yg3x0hkDqZ42gOQPo2tVrxhTh7r3Tp5eZyet5c+fJxO9imbKeVSwUgwhm3QKdvEYrDyFejBtWTnK0ukxjiWXl/ZfXeLM2pG/GYNPcpy3SVpS23uEXwnXy4i+EjPgjEL51647oe0vag5ds3MZM0hnN/yd0SKS7q0thd+z0ObCvS/MOgRvjOgJSZSqYtQIt6gXTX2iTEadclyguKIITh0/+9sPf7B7AfV/sK9rb260NE0acsj7qC9Tgm1J7GgKdOkErxkucCRQ0NpwVE3WF7mqaKaF4q5hV0MPmBvJVPH5JAs6MD6wXoFe4Em54ZE/MLzIWIvj8v//3/5qg/R90ar99TbtpG9J3OXSF5iWa5ND80cCjkokHqJHgZ1k1X4dQQH1ddcjhBtbF1MW0qS54HU9bGgK26TEGCkB7rUYGJbTTfI+2ykPNqfl4lLdr75HR1vOQMWTVjDICBYKyI0QhSCSPQ1jMH/QPxkGst6A5ziFRvj91Z+1XJH2hCSEOneRt2iYM/xlCKtaRzJ9o4C9ZtFiaPct1vIU7lX8xDbudO3dIYHepOCct9i7NsbhY2zEHUZPNnmFkGf8Pi4HkuBv78OGDWnfN1x3pn+ke2TUWgkUCNM12795lB2K7ursrgZ2SK9idnvQPQHp74EVB/Mlh2aOnu8f251hX4J7aYsI8C5vRNkMCvM3FFe2Dmesfd54v5/qU3OLl7+bxBn6wCHJZa66fJEBe1S4h4uaN6uuLi3Vr1herlq8szj/t1GGqodZO3gAqrwyS9RoCu9Paky/VOuorHeILGnLP+EoJ8LZu2SrrOKe0R71vB+3oixs2HhU/bUnR0dFuOER/s4/mT5MCTQo0KdCkwCtTo否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否rMhZA+dRBQNlLNQ0PDyW3NIQyqIwEc7C89ybInrafJPD/zF0DvjrHdI8LS3/76NxGKFtuehHpmEBH6b4wrIOghIIGIYcwigfqWsa9MQk97buFpczYtI5+LlXGKX6QpzsmQd9i/6T+I1RLLzZIukMRpXV2/bDeK2C11jINaI1GZuW79OrNz0tjQZGrImIdIfjuDBVVjOyD/dBXP8uQFP+pt89qAFQEsjQkyljH/dgHrw3Mj0KH6FYIc9kRH7o4Ywe5S7yVJ1V0w+5Ks66wbVoYajCpkd8x17I4elHQc43TPrj1WZ8LZG6yeSmPIX80T1nRjzrAMArMHI5n1h3UEpg7UUn3z9TdGyHL4xn3vZfP0ZlofBMCHnLUBgCZDmpyy+Vl8+TvskAxGyntV88rkB0mms2ZljnZSuApAUmt+ug8boxVrmP5Zfvoz7lhRWpO0k1Q3EsiNSxrtbFWndXZz52azadeKmqytW7WG3pA00pD1S1aP4svoLi3GKL8FgRhMMJk9FfMIyHHGPC7sQRBl7bP8M50QiBjZWAM4892Sndo7OoMtF7MC+2bRMdVK96EgAPBZ70L5rGe+p3H2QU0e0kgwXMEwOO5696GaMAvlxkN00jBQIvZv7k9I+l67ftPsZ2HDeYWYRWEe4k5Rq7v9WsG4VXcICKS3tQ8QFjOAefnlkjX1zmY8A7/X6ofXsm16Xf0wIDwA99PWhS0WVrtoke49K4whGeYdNFugXhzHudLzCP1Zbd+nBNmY137D2R2GMu4eLRs2GjPTwrqFyca2FjEw7NB5da1pz5BJ5MzNZr8yfrn3YV6jV3dzNCcst7a3Jl/s3Ztck6336zLP4gzkrA+Vu2pW7Y/mBW1VMJDCPIotQfqP8+5KSQl3PO+QTc42u9fekFkG1M7CYDooLRicPVGLyV6VLYLW3o+maWVFSgiUECgh8ElCY否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否DEU/KFULi+OV758nBCo4EjNiV35cGfPO6Xv0KLBIvZ2IEENSawjXOwQY1NMZoUxxX0h9nUkhpTh+xh2cjVuFKIbrCwIAIv+HZIet6/QZpVucfPPdt8n30j2OrSPiQ7R4pYMLp5SMeJJWlEvDqAMHhxEdULCh93YhCMM34voa否7zy5JIw62VeoS1IgqhOg4HLj9ru77DO3nnsLGIhGVHHfvIDkEGIh1qR1copLG6chQcE5ahBA/2kpmzILwgaHEYNek0qabA1hiqFSFuYezbEbiUE7tQr9iHd4/jpRXDx/6uTJHmk42lkC6HZV6O9eO81yYBMKjxgoQThCEuAovUtpU6gHa0dwghvcE4YR32dIHD3POlFAtXGCWEOoVYef10qFd3GMFPLxDscHdFvD0jaY8///nPyZGjR40w9UxITpCLtTVSHSZEsakaU8GWtwqljpNxqBuDWE0/UT/UpZmdE11aF+gCC9EKCRouE6grywadMg9HdkoMjn4nHxo3T0hrDuCofhsQZ/d1GQ9HreG0OCtSpas86hzG0Ty7XJ/rPieE9cpkl2wSLVki6SgRDJB2wVE3ryP9yAUO+wFI512RCkT695I4Mrk8oLYVKQwItdbnShun93eTAKGTqYv+IcXiEDHlm4RN0llbkCCpEwFLCH/PiPKRToR4ZfBVfhBKRjv6El+IVaH/bczJp6IWcZ3SgCw8exmd+2R9siyicqgWNgxBykOw41K6SxykVBjJtSCplJNSiU8+ECi7ZJuxTjDp6OywX504NulH+rVJY3Kp1NCyfufOe8B9/DurmQdYPv5BzwEvYAysITBClIUbOGpKBTBH5+i5fUbPtJE8RrU39YDAg62Rvr7+ZPvW7dqr1kqKa4HZhVm1cpVJUcZSUUg2OQqjArbTBTavaNr1lIZj3FS64ncUqiQwLFy9es2YFk6K2AzidFg2TB//u4diDFgg+zcNRgDD7i1rLkQx1m0k3AAWcAHpwhp6WwgK7NexRiAlxjhDGtjqpHqF3SAqv8ortc2a4MNafhDskKA5duyYIaFQRcq5BUcaonIWQWU3ey9Eu6diPBnLkR+2UPeLw50zx3JJHjjBzjiQLGFaAa9HVDFbd1LQ0r701dYnI3IqjfuB1EEC6KwQ05yldqlsXFi7GCV5ZNSsst6z9wVHuOckn6ws5rItLxYtq5pBgvWRNGhaGJFN3d9s/Ychg2DsrYAgcwQSGcg7c1FpmV/5UkLgY4BAxVygQhrnnCcf21kHJqRHWqfSEayHzZG04tXHNb7x6E8jj3p4nOq5jIo+yuNd0xUz8ny8PsXwat/V0lTzi9N6eOxX7d3r4fHTJ4CXq7iGpH7cBW3NVD+xZqNiGSISKjGxpWpIcCUMa1i1MvO1tXro3PAtzoX4m/0NG2dIda9fs042W9ck9U0iiAo0EIw2SSvArl27TMPDoFQrOuEhhpz3LH7eu3F4+R4g4MtNBo/Ig/MVd6/jx4+atp3NnVuM0UjDPmkUgxDnSGxUc46yu4+nFcDj/szyLryAS0ErSW/vRamZ3JTsWrDT8l2qPF3zg2srKiStWPXep3+tyl7vYiHR9z0xgkK86mhrS9YJRwIT+MU+SaCd7ZJtiXtRzHy8MQbTZSOEv09FK0oY/8OKGaNN7DdIBqKBhrnDWZQJgoae9o72pK2tXfeC5VYA/R80WtzWmeupTFk0sWWlZ7/KOth8s8ZSutVAz3gWEj/293Me/gU4Ba/y7xQg4PtNOJdPIeEciFrCZA508ifaxBkj2H2i8CirPYMQYH/OzwVszr4hT1BoHFXvgSgRbEbdEeIYIgJEmKdSx1QjhDHc8DG3fFwMXF5btm9OVq9dZZzgvxzYn/wmabHHj58kbR2dyVf7vkp27t4liYxw2ADBy4VgoZDvc否bMU5EFZCGQa1USg2kNWoOLQLhB5KdTREk9fUb16Qm4VJQRSjOs60iFq5fv94Qfd5ygwR/lCY+vI63eRAGaDIXi0jhmR6IQCqO5SzEg/0ZReaSCfIS5CQcihcv9Ur64rwOcDftILZEOupRhweyHQJo3r9RJlVfrcVWf96s6OAVYlepSwgAOlG701ty1ejypOkL5rHMvRJRRxyA164mPRd7zE6ZE0pAGsKh98c//VF2D7Yni+uChI9Vh/zjelGJUfCMeyytpfWj3kmuH5cN+gtk5q+//Zb8+NNPZsMMYoJd6HUwBgjhIBW1bwyAkhfjnPgcqs1RL6ub0uvZqEvruvXr1DdNRihEqrCn56LZWNrUuilcmkJK+xuXTQ0YNxBrcVxyR8QdeEuSH6g25MJl409ihKNbb0km/yetPglCHYIHF7vzkmj5H//zf0qlyqCI7Fulzna5Ed1Qf8OcRS0U74zNe7KPBuEOYh2Sc9hRuiM/LnnVLunjV1BwVTUE4uCiOo6fLg+1vlG/Y2QdAj5St6gPwUbe8xcRgn3MvL1wr4MiWv/mZXyotye6kCHVc1HSllzmXmmdrEG9nRBSbjeR8RE7+uCGiL1nJXUI0Zc1BYeNvxoh77GFxyUPJEulI5/QdlvX9RqmRXXAMXZtfkC8TtdhxhLz/+nTx1KRKkYO+eMsB8ssLcN85/Yfxi3Eb9QGwx38jWzZoQazTtJZS2WjsL4eG4VOZLFla/YBFnd9OsxihGkcbJWTB+sUezk/EAogvGsW1IrhR/ZVhFxZrn1sx/YdUvPTGaSPyUTrnBHs9IrkaGNTQ9KgH+84ogSkrH1Oyx/WVmzyLpLdzq1bOk3dl2fsM4p9uUFSGqj0BBmGfUjaVc2x92DHjznRInsv+/7wB5Pa8/WJvvSpWrmWM4/IUdIKnKn0wee8dE9gEtqrw1+hIG1QMQw8sTsT22sp1o1kqHumLbjKss3L/rh0cvAxiKeBvOcOZgi4+yHO457LxqghBvMoFW+VqSuCyo8SAh8EAr5nhnNQXgXzt/N9UPWNrWM0HticF+GHtc/T5qmm8hbPBt7TST2VLD7quOO1h7C4/V否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否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否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否Ov37xhuBhp4mByWS8p2c3CFfBj7kxdkkTqnZ8zBJYNBHweiOOPEeGjOYKA+RjmQWw3D3UOhtWtu82+Jl8hD9WOofHxYyrT/Z1t/Onms4DCFeA2OCbAxDuyFCZlLb4bWS4GKD+yltg+qfSJBDuYIvbLxvZezblIULN3qlySobV7+l6FWq5PdSBuCAYI2v3aS8L4u0OMNseOHQ9rxSCCY/0+cvhw+PWvfx2Gh0dMrey7QvtIw8Sy55QQmAhPQEPAnd5ekyr79tSpcOrESdPWw/nMLmkM2LQJzRrjXTH8avCJ8aH0WRk/EtPCuXPndIbTJalVmUDp6NAe4ZDUNH6jMx8xJl+5XEZt1Hc8r9mOsobxkww5L7rbcyds3bQ1NG9rDt98e0pnXJKUvvJcc0pk9i0LWvcwV2WsS3bC10Z1AdbgwrfFKHlDZmVgwgIHZY6DcLd9x3Yxe20Vg7Q0NLz9aNKNqCy9ua89fPPNN7Zv8wyr8zzmR/f1Wvp7ek8AmXrb82TxxgXOHhkCGQIZAosCAlFk5QsXlQm9PAT5wnnn7L4eBNTstt2YdDklkFZsiGdvRTwYknrBYYncv5PKM5zRZUR0cMfibv2JbSn9ynKIeF2qVACba/vE9YOtlO6b3eGnH/8S7gl5M2KPKFerV6+2w+BVet4gxGOTiHZrsLOiA7kmqcJ0JzqcuJ8+SlLmg8r3VtxcA6bGAZUAHHa9FyHMNnAl0lFtrSkfxEYjTFHPWThiT5SC+5MfnJsnhBR/992vpPYjEuxAjiCq+S4e6R9USF6+dNmkqppWrjI4Uh8uYIQDtkgIDQ4+CTe6b4S//OXH8MMPP4RuIWwvJNnmDqQcQuZTcZABI6+q3y2ckuSQAeLnJ/IQ3JBoQxIOotHwyFA4c/ZM+MM//yH8+U/fG5w9/fo7bUIZKStEEnSqv5L0Hy2BRACX5UM99I/9vibZv2paAfE3XryvKiTNkMiLV9WfPM/oU311f79bHywqqqwcxP55gru32ASf58jb+p/GDoeiEDfoh6kDhhDpRtV2rs6TOviYIqzXiXt2DSBQNCWSl8+ePRVjwGPNX0MaHy9NLQgwph2yixCwIcmPHLBh7A88GTAGDQyW4+ffeObvvaS7OFDBziYMCdhDaJY6qZRgxzphfbWIb4nkn1lDw否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否w0buvb4x9PJ9wyBDIEMgeUEgWVLsGNBTxGI5dTos6kriFG86Dq2xJbJRcSJxTX+eVijgTiioij+yB7S26G+LdBtjp0W9Hujpi91MR5xC19L03N1z/h9nZAykCC46R7J5gtEuxfiCOIguEmEKzHYmePQCA67EydPhMNHDps+dUcCyQh1gRwm40BIICJev37d1AFgNw3VmkY8sxDT/0nrT8mr0jdOo6plBWM4BGNbxDiUGxt8XUJKu7oOSsouGgImr/dCjvjOITjw+NvPP9uhHByH3gZIIWKbZzqOOB8EDzZCXnbgA4YNYbCEoSdGo+niRpnja1TLiU28f/nXfwtXrl4zO0HE5QCStknTIQ5EPKRxrMwKh921G9dv2PVUyONEbREh5X24COUF9zJO8+5Q574QHNWwa7ICAXu1TyRQxjamJz0TgeSubCPdu3vHNjfeF/xeXz8gV0Cv/tOyfS/hv2whMPuKM3cwrrF9+YF5iP5Kby24aNP+yHwMQZ+NLtLRHR0dYW+iUsZLw9zhV/SrWsrHsIfN9+UHAfpU2q/U3WoWYnvlR47+8laE5Afiika1D2slKlkhllVMOxZ0Xn5Y+3CMk2fq+0gCYFOPdRLV06yV7lCZp64/ztWOhXGfSw8j2J0/H86dPWfqxFis0VZQuYKIrjIZ/CicrgZZ1vhxSPpYuAfwe/36lZUZHKVFB9aoJG9SXdzl8emQyPcMgQiBOKY1Z2kOKFXwixELO9JGCB8dsf2Ow4v1091EY7ORfzkPVtE9meTuI7RhCkU4D5NEKx/9G5n45X5loGk81MYBRvWXTUIN/OvD1bwrZ0+Zu7kCHu7vGdm8WoRG6h8GLgRBUmEQj8OejfCWVJEetruwybtWkjbVhFl89LxLGJUe+aEOAsUylPgCwwhH1k2YO69Jk8sNEXOej4nhpWhYVGN2dXbKlt3vzKYdWhtS522X+uXnxhCocKp09lHYojszd8EEdOXyRZ2jXBWhZ1g4zGqprGyTWsxDpuKb8YGLt6KRzAc0wxlPwd2ip4cvgti50dXLl8Mvf/tZ+9q7lj7qNr/59nQ4eKgrwMg9n857XVG8cVnZdxUeVaz9/Q+N2YIzqf0d+8Pe9jap8ayVtGW/Hf/GJfVFPCaqB5mDgz58/FCEuDvSzKDzF9Vrq6Tn9u/fH9r2toWN66W否jhse6O93d2798Wgfc3qnEqD0z+8ljpBUsqOY8YTC6ZVrlqHh1+1X/JbhkCGQIbAUoHAsiXYLZUGXJz1iEt/ROpAvmprwSEYal3uSdpnWIs/qixYxOudbZDY+BR/9d955wCLC6QAqQ1UMaGqkYMnLrjGcRy2bROX/MGDh8I+IRmoakodeYDsU2bUa8Kl90BqAFDvBPHIylKklcabz+cItvGIitVlW0s4fuKEXc0yaE75HMmlTBDZODA788svJsWY2gn0etaXHdS7Dv22IIR3YmaMI0KbEM9VgjuHhqmjzBzCr1yl72tXy+7PG5N6/PmvP4cf//KTEUE5gCdNpAQ3rN8gmw7rag4iwfpB+BXIkiYsRN1ecU0iQQlBFlePwJsnfaXYCMT3JfrrYPe7V5PxUl7Rk/H3VByIwK+np0d66Z96aLvTb7LLEPhSEHDp249SCaOZSvOWj3ONe+uLsT+yTiBVgK07CHZHjhyxDeLaOpuZ9UPgS9Uj57NEIJBOf5o7UeX4UAcsrJ8cPnCwt15XSrCbjzUmnYbp+3Bl9/b2mB1ZDoDXaa2EcFfmzdor/9SV31LPCZ4hhrMeXBIH/C0xTj2R5A7SheV6AFxS2EyQTr036zvc9djCfSKubA5yINghYbJVajFTKcH6uPk9QyBDIELANFVoUmBMoyKMcQnuZnaIE8mhOEaLwVpQj+KeKR5zZnhOHwLlbJrMe/i5f+Jdk2i9P9OyzcXJBxgcYS7dsFHzeFPFtFCTUH6ZFQTYK7LmnBcjysWLFyTZNVouYUaw6+oKf//3f2eq/FKCHeMlu5lBAHwlxVkc0Eas1jdwp/7+R6b6sq+v3/b84FH72tulGnZHnS3BaqDEsUPakWjHnbFU30bYtL9643o4c/Zs6Lnda2Y60KJ0/PiJ0NHZqTOe9TOr0ByGrmoT4YBmm1u3eowYdfDwIUkAdgqvXJ+gb/W1m8PCTCMpyutXo+CmdeLOHTFbXxFT/H2dL+mMTfNZi87UUEO6Q5pPHK+j3e8Q9soVMb7d1/leIg1O4ilw6vKt+9SgKHmcNgBK9soQyBBYAhBYtgQ7Q5aXQAN+vSr48q07a6Stk/GhQqgaIGwWPC6qEHO4mnRBOIMIY8SXp6PivhowNY3l4ZCiTIayEM7CJis6bczB1itxccPVbUQl+66whQrEouAiCEV7bzuEKLJp4vDt4weFs7NipPFE3NMfRD+QDYgb2IzAWWoETPK2D3P1E8FqyFvJsVkAmbw50C6CWI7Ue5uIdMePHQvHpEOcw2wiE0Zg否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否h2IGmmlm1vywyFVh+26Q1JFgO03Q5KKcn6StFZahnv374a/SHXjxYuXjJPVEtAPYYpquNec3SlKUZwJ04QQt0IELBzlR63Krt2tQk43m2pKSwFY6fugiHTYe7srBHFMaq8oOwg0dqLGVyLm7nxNDVo05pmW0KIopN35iY7DA8q4UpJ3z6SC8YLUknz//Z+F5F2XatExk3ajT+CogxHsdICHxCNE1dJB4NV32m9URo7hTLt75664iqMqTNrdCXdlHHsoEq/1XFZvwNX6N41TgIP2R40onI6MOWBXtdqyAk+u7AKCABs4l4At59ZkCEOwG5HtC5gP2Mtt37Fdhy2tNl94NXI/dkjk+5QQoLNM1mHsW9UBOZRA0g57tkjapcwujjNMmedMApB1kT34zMDAY1NV+ejRQ5uz10uFEgciNscT1MU5ZpIHYZN8eGVNuCXi2rXr18Jj5Wl4Ah9w5cDU8wTwI0gaDPuUMIbc6emVJoXH4Y0IdqvXSv21mHPWC2+pPaCzXPJPhkCGQCMI+Jgr5i3mHZivGKccVCO9gRPaV+F9ek/HY91w9ynG4o3/qQ89PsS4CaQoY8Iu1yjSNPw87+JOJexKo8ZvwMGvdD7jq7sSdEU6Hp57TJc0okYQ/+b+6R18Oe43Yth07wHc2a7AuIiVc4MN6RfO8vIX3ZHq2ikixa7WXXEun3RBSiLmxxlBAJvrqHi+K4bRAanIxAYX+0bcWrUBNsSOHj1qxFNsC7I/rlotZlX2n/iafz8TAuAvZ878bNcj2UHbIgLPiRMnwm9++1tJmnWJcO3MupzexBOctDVoFx+yjYqA2RKIgRcvXQyPRahF1eR+aeI43HUo7GndLWa/emnWRqnM3i8Z9mViNi否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否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否7XMW00QsbzyEN8ll+Xk6RrDTnqf2IEdhLUyUfIN5kIN6CDPY3zSpOT2z52SfZ5Jzkgp5I4kPpBHHtGd5ISKdqerVITJEuhGzs/msItgJ30DtKAQ7ro9K973SgtCDjTv2YUgKb2veZvboerWnPS1bWSdOnrD9THoAX5bd5rgVRrDDfhd4DIwL5fcUWPl5ziBAX4IY26M2uiHVfGu0jkO0k+l6czulseHkyVOhu/uWhYHBxPvnjAbTnJV46SYEg8Gly1dsjBw/eiwcOXQkdHV0SmPQ30u972i4/6Dfxh1NwwlMqU6zAEnRZMX4Hw8ntP30SsLuhx/+K+zatSvs1vnPLmnwOXL4mGnkuHv/rjE/+aw3PoXZ+xTDfFxCzHAQ8958fBcGpFISyTP65WoRjZkLmrdukzaREZvLxkVeQB7YOqbs165eDSePn5RduijhDWMF46qjszN0HTyoefeDaT/h2OeVTJk8UhzsFx4TkQ/V6Kb1SfWqxtoCqmQuSoZAhkCGwFeAQJaw+wpAXxpZxq1cugGsX1yLEIZApcgt4UBOol7yaMukT+pbbly/YWoS32uThYvpORomDz2SJj71ecmrxtlmr/AxJMmeLaY9wVG1d+/ecOToUSFue4ybyYOYPnTlgo2kT8IKR6Xz/MngoG3cyJdUXDKMEvHuKXudLa15/GmUD0gROsK3ikNztYhhJj1HQGG3b3WQiP72ixcvGmLERhVnm+cisbgtrlKur1MFR4uqSkeCKo2SbsLZwCKtxaZ4zbo1ZkPizp1eIco/CCGPKkXJBY5WrtKARpW1ZbB29Ro7wDN1Y6qbwV4bYhD1x7LRcU0Eu/tC8t7q4C86laJMg9LjSo/4ugx+Kxg0qCwfddHHabOnz56a6qQnA0+MUM4BwVRjq0Gq2StDYM4hUD9ymdO5zD92Y8sT5oPXYghgTmMORJ1M686dxuXuhWJuS+co98/3DIEZQ4C+58uLItMfbT2VH8Q7W3dnnOjUEazfK5itmUVw5mok7FAt+Ul2d2Bs4WBk3epCIn3qZGcVgvHoruG6Q6GLgtv41bqzQrgfXhzuoAbrwb0HZvcEJAqtAKz36yRdwrjGfnB2GQIZAhUEfMjZsCrGF37FMLOAEKDGRHhCAjjuWWIspLhiSE/Fgtf8+JhmZ+Oh/O5j/FMRyN4LT0JDsCKSh7fc9B3GwPJSSeMMUBSFQDY58ICL6ZAW+6zVq4o0vWAx0JS/zMnAAKLbqBHdnpu9UexZQXjBhhmEuVdSOwfTj0vOsXdhT4HatohbvA6v9Q5D0DvNSe9E4HNGDbR9MB8DY4iNFJHD5hFJ5bFHwcRDn5g8r2uv8t+H/nv4b//t/wgdHR0KF/Fs7v5MhcBfYGTcLE0Ba6VOk/fo0tYtvHTLuHoFi0mf6JAOQp79XX60K5pnbt68aQScjZs3hdVFYkg6Hj12NBwXIeGpmMLoN+7KPuweuqs5S1cMi/I9P0QITAQjVCS+lORVf99Ds2UHcb1ZGpAOHZWk3fWrpZYdxkuTLj8/YAxY08ovTbse3sagKsLQ5UuXw6lTp8Lvf//7sFm4UlfXQV2HwzBSXiLaayCXUb3LlB5z8JB0kbJLxv4Yc4PB4JkYnkdEuFu3bk否2dpiZ1T4P302Om7Mz0cZp1XNBhnTEhDjnjwZEk46LDXt2FbWSqKGQcJuX3t76OjoMEZhbAniaD+kKznLwe4dhFSk8HCKqu9xuPLOLq56i9/M34CafmsE5UZ+xP46zuduYJPd3EPgc+Ga22Xu2yKnODcQyAS7uYFjTmVSCDRYkECydDGpgphh2+zcuXMBTvH3Tkxi/XXbAHqyV+6s4NNwrINVUCFzttzHuCAPh6Qb/de/+U3Y33EgNGmTxBfCs+GiXGzoHgrBu3jpoiH0bNSii1tZPwJuULtplG7mQSarNXXdps0FKh426MALZxtkVYhN30txOsFBeO7sGSHED+yblwAkC9ikrvYt/dLgmcBJ4d5K/eazwn4USPILcfqfu3g+/PM//0E66s+L6wrVIuDFH4NppKeMSsPhzzfgjxpMVNxRH8pHeCTDsF11u/d2+OnHH0067LWQwsqRipc+KVQVYHk/CdYcmAJLJBxQvcGBwrBUwkDEw2Yk6n0YB9llCCxECNh0k0zuHMxBtODADcecAQftNm32UTHF+mJTQu7TC7E5F12Z4goTD4noixyCQ3yK86bmVh08zaer6caax5/qEBFJc/CiNdIcYCqk16+1NXQ+yzFl2jUF9dDgfPEZ9UlDYoRCAoWDbeyfYKsWlXBI2b3SwflHrVENk/Hk8j1DYLlDoBggti4WsEB67JmYsZ7qIPSNiEylSnkbfDMbUcVwnRTKJOvjmoC+t7NnjWDe/a/xgC72VMI/IdKxXlNK8v6kCZa9DDirMUSobhzux2cxVcofBlDU/LNH49AdfABVlqh8H5HtKhjSUBPKgTB+j2X3FsIdB+NIJCIpx76TPOr3l/Xv1AnX2F87FRUadf3UgHxvs7c9f077oJeyy7XLGEWRIkmdtQjVVmT2a5s3bjL1cavFtAjhEDikjBpp3Pw8BQQAXr3DrxgG4I3Xrl3VutksFYztZs9+nc4HcNjg+s2vfx36+/vMnMNtSWg1bncLnn8+EwKMGd9zDorYg9rK+zoLOiJC6cGDXaY1Y4vGRL+NhCITa7+iEeXF/IIjncrXvGxfi+eYxiDj8bIYh+8KZ+o4cFAE2RM636F974oxeyBG+MK/XnayZZyj4vy6zmvQlLR7z24jGL95+1r2SZ+pfrXaiL5wUSfMjjZ0B9PDE6kfffj4Udh3YL9JzW3auDkcleTkQ83Bd2SzDok6jvqYc5HKw0zKgf2EXWsEuyQ5Tza508JVC否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否1Ih2EE95FUsIBA4gyBo/xfyZU5qx5Nuoy7VWWBG3PXrtbQ0dEhxHabbWIdzC/F0comEdWebNxTGM9XNdj4PJX6GmxFgGVhg+KDVI4KgAGSKNtjdQTLPvYEoWUgW/qnjWh71JRyuRQBgSEE9vb0hsuXLoX+xKZg3Gw7mmvJ5h+HQASwvTEOOMgFmR7R4ciIJOzgWHYiLg0QbdfFtvEkuJPMeN80RH7OEJhfCGhq8KXDlhHPjYM9b否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否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否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否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是GIOZNY2VimWBdN5ZkVWiXX9Gzoxv7NHOWn7B35V87LsWGkwrJNp3DeziV4HmiT5IEqubGrUIgDcA0JZIy/RZkI67qEsOEccapQ2qxQLPZ0+4Rn4/ZkEAhfZzo3FH0JzzAiod3U1LQp/dr1xNIOOpIYg93XrmViDcY2SiP4cfuhJ4SxyDqYTqZ0CSyjbY1jL54+c9/94fdql0WLe9Yh15gNdT7+GQJub+mYGqHInEHPxi+yyQqEOArjJPvvRH8gDBsZGTE3+9d6ut3de3dNIMv1kEJ5w3GxtgjsFvU9vhl7o/F8zV291ulu9Sv+mwRfJfPdAU76l4CzI0XKiXFZ2MnD1eSEt5qjrVDW7rzaacroGRyT8Y/6rklAOSlFbtyhEssvrDH6MVJHalq6Y6H+m8nPdtKF+vDduc8e74i2KJn2OF7RM3dfhDpkKlPiOxHrEm+gMy7euRTYnTFm8fWHIRDmQbvOgSZQuVGZF+EzJAHMhIRKwaTdLurPYcxMuF5oa1Mss/MhCYHd9evX3d07d13X1S5jNrn1iojqNS0APX/+QoTeH4xogBAIKU2whXPF2hqBmrysUM0YyCFo7t275/r7+l1jfYPRQjyyKs0ji9skv+ABm1If+P3XkUW3CoFdZ4drlUtMCLKBVxLYjYzmxDr8lG8l+4a4FxGICFwkBAJTnx3D/ajCeaxzWPBn4T+dWHy8qnHm4cMHrq/vlpjDbCxQz12m7477EYGIQLEQMLpHdA6JfouyDrGOUfqyBbiyPOF6sQoW3xMROKcIpHkJ+hSWQstLy3IdrX4l3ifpbgdql+aF0nR3oNvDNiuUS7/pQHYX8oTxWqmaBUxSpzK76WvwZiRiNRFz60oqBi8MnGdt008gt0Ng52OyQ7NcER0TU/ERmJ+fl+eXabO8Qmiwr7mJRBvCk/f397s78uzT2NiYcY1Z/FJe/DduSxlvbGJcitevzOKRMQp39303+1yjhHaVUjAgDItZYyGKS7oTfYslH7bWDXO7WQY4FBqmJZh98fylxcnrlqem+w/vu9a21sw9Z7nD2L0s67+xUQns5E2kTesmWKjVN8gC95A6Fb28wtgWpPJejPAbK9WhoSGLT7fz1selw2tUh9a4WluaFZdPrj2VELeSqC/WjkODw/ImNW60oV2IfyICEYGIwCVBIArsLklDn2Y1g3AoMCK2kJrM1PgdR1NpYGDAjY+PWwBzysI9TMJpZjCU0Z6HospLYf4v9Ez6VhZl29vaLYBta3OriOksc7Mu7dKn8kX/u9/+1spzWF6QCeGXzvs09w+rN+8E22YRMt4lpggzMQf7sqRDADo7O+dWpX0UFqfzcQr1SG9Psx7pvGnGAk1pt4TycFClmFIEHm6UBinWMcMizCbGJsytHfcdljJ1PeyGeD4PgYhYHiDx8Bwj4Mdvvmk/Roaq4A4Td8dY61qsl2Q+Ym7A7cpXX31lGrN1dd7FlD1vDx812oTc4zYiEBE4SQR8Dw5zk1+gebv91gQLe/vyGJAQC4fREidZlphXROAiIgC/ta45cU782OrKquJ2Z5VZgn0c86l3OeZ7JDjk8CXqiMbnpabJjNAu6aN+lTb7/EXEMoxXOYJNBqnwS1XaYDHcdFIHWNXVy+1be2u7CXmgSdKJ+/MXusEcd9519XUK8VCdWchOPxf3Px2B8I0bPZmdhg5kCL89MzPtRuSlZ3Zu1u3uZ92u4y7xgRTBvv7qS9cppbDy8ri8dgDAD5w4CL31HlcWJG48r1N4R否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否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否z5ci9uIQEQgIhARiAicZwTS/MLe3jsJFtbkgmzKbWwitJNfRk2H0OBYhzF3pu9nn38I8TKzJvcLEPvpOXMPiYtIfkF4p33OB9qerbKyRJ6NjQ3u3r17EmZ8WbICO8pMfUih7IHS8DSEFpD9ZbsHS5ZXr17Jqu5/un/+3/9sXlHwPsNiOrxboRTwKXQtnishBPTt8g0kn3CmYFgw4QHn6dMncvW6pO8l287Eo3/46KEED3dcU3Nj5pk0/Zk5GXc+GYHQt+hLq4otODIyIo9PExLY1blHjx65G1orYu0hJMnWD7RjuHbYFmtK2hevHbgTrq2vN/eTVZVVNl7yXej1Nt4dlsfnnCfvoxJjzJLcX87K2pP95uYm19zaIoFi5VGPlcQ1YpoODQ1ZP0K5gXAFhCq4/+CBxU0tryy38dPmpWQgxhvKnKwpWXdkzREF/XJdC/NVtoXze2xJVDkWIiIQEYgIfBQCUWD3UTAV76bSI9rzqAMORTH4ckK4+okRhGBoEJitrK4YocC5vKc59fEJYsp+4R1MuH7SRVhFMNpb8lN+Qy4Jamvr7J0TY5NudHjUBIYwnSSIOCOg7Ojs/1gtUrilSwSeVdWVrqOj3TU1NJp7lXfyk44G0ao0xXAfeR5S+C484epLzLkaCSKb5ZqioqJKdVKAYfkef7v91r6p81CvWMaIQESghBCw+ShdHj83pc/YvLS27jakcRviz4Trnvn1s1S1YlwgsPv6229MEaRC2p3Z5Oed7HHciwhEBCICEYGIwPlBIJ+/hFcbGRmW547Xbl1zZLjOwifu6jlOJ3ORaavcWV4s7HmaHyVC/5NkTy41nRQL+cmqjGPje1h28PMpvFl5RblZtvfduuXa2lrF88iKwlLIOTks4iZdbTCAhsCa4907LQinJHNWH9UFESZeXAw/lXNNVos/Pnni/uf/9w/uxx9/dNtSuKyQq0zwQ5iZj2sRqxZf9akIJLSm/3LJRCeSD4XveF1t/mrglXv64rlbkBcc2tnu0m18020KcXH12lVXLwFPSHw/9AWyflemv/qVsbWztol/8hDIHxVAyyMWRhX/AJZXrxQzcGTktauQ4Of+w/vmPSMjsEvwBvOPTfZu3c7YiLIDQsFtuRcuU/uiXI0yg6X0APKxmR/jPrLPKXVyYN+TLu5IMdHKJsWBKyob3kMqE4HiMV5T1FsZEzc3NhUfcNy9ketYlP5ZAGxTuJu+m32yEryqOnihI+否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否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否GWyQLBFTV18j8ah/GITO25OxEBY/DyJwqcJCStKUhaYwla5wsTtAATxkGIoDA0MGUES3svEDZ+Xn0I1ILwKEV/595/ksX+n1xY1jdEMuH6xmEDFaKJ2iQghaDm+zxHUzcjEf9sCF6dcxahgaUKUcho8Z1GxD4CU6W8B/ALt8oEs4uWIQEQgIvDJCHi1Dz/wsOhm7mEksCM+QlAyIfNkimFw9e/SiTZpyn/x5WP34NFDaac2pcqAfqbX0fTPhQEudUvcjQhEBCICEYGIwAVGAF4EXowFcfah+Re0yPqLX/zC/df/+l/dkx9/yFo7FGC8ysp4fs/t7u1YPpn5V3mRX6kki9+uwmwSr12u5IhbRb3fvfMu/tNlZVG8VVY87e1tJsBL87OF6xPoh9Kpb+FyXuKzoYkKQJC5lGo+vo05uVAlphZCbHj6kAKtiWCnU2sZnaIzEfTmfyd8/uGX/2w4jluPQMCJbaGlkHcS/ixqXHo98tqtyFIW15AIfeirAfdCz5F7Ou8cvGlI/Wj2PcWE25aLYFxNYhmGcIkfiev8zipRnk1ZAW5sbNqYWiVhMb9QPsqVLuNZljWNEQK7OVkCjoy+djOzs1LoeGeuhlFux8tWTW1tFlfrVF44iQtN5qNMUoXKtIRmzWWV0x7/k1/mPn9H9jDuRQQiAhGBEkHg0gnsSgT30i4Gs1qhxERnE5y/Af/Ri9J6ef36tQjSyRyCtNDjH3suEE+Z+5lsbSJ1Fsi7vUPaiwpoTuy8F89fyOXESwUTXs/eLuoqzTxlLpzRTj6cRi+oLOE89W1QcNzr3dfd1c4uCUTLzc3o/MJ8EhfQA38Al1R9uBbyS52OuxGBiEBEICKQIIDAjtgSuLecnpkxJZPsXOFHUEZbc+ErVrCpqVEBzPvt19AYAphnx26yDeN58oq4iQhEBCICEYGIwKVBgHkVoRt8CBYmWDfMaoH1uz/8wT198lTu/WdtITtYQ6SBYf7dlYUKCp/7WqANifPZuTmcPfvtrhbml6SoihBmYXHBhJFX4L/0C6lK7tr6+vrMOr+/v9/41vT1cF/cnnMEjiD+6BN809OKdzil9RGsuiwh/UkSQpOe3h736NEj4/8rKyvDpSO32S/tyNsu3UWgTcGbqT94MZZsyUJ2bnZeiubLFru6TgKfWoUjof+SUl3YjsOfw/L11317kj8uGldWVk0wVqG2bGioN+vjbIuHHIu/RZEdC+FNrZsRPqayqlLK4bWHCu2KX8KDb0Rgh3vLV69eydXlGylYvjVFiNu3b2t8vWX4hnE1bOl3WD9vbG5leTM1LNetlfWHdvYtfvCd8UxEICIQEShFBKLArhRb5azLlKIubBJkZrPZTdqTIkrCRAeBghuACZmg4yIkrdHCNX4nkUI2lKVZC6htra3yD14vTcdNNywLOzSmEN6VavIoyLpOBRQq9i9NMFAvXGJe7brm2qSRicYmMZaIl7S757WE7H7+JCnTJMmx4R0uxm1EICIQEbjkCMCehX8BCpg5XGKygELMibSFnc1XYmrh+C0mzXu5+lLMBKyf26UF3dDYlIo1kh2LQ95xGxGICEQEIgIRgcuCgC2Cii8Js6HxIZpjg2AOV2wstL589UILr1MFYnFLOUZzLoINeDgEfRm+MZ/JKSaoR7wbS7s5Werg3WVMLg+3t7a91xm8zyRI4Krty8eP3b/7d//B/fyv/pV4Oylipvg3qpJPmxSzevFdx0fA8+DZ5+xbh1Y0Bt9/MIU+G77recXgwlIIK1IsvWwxQFnhNrHvVp/79ttvXX9/v1k98Qa+DdlnJd9I9p1hz68phKO4TSOQaScaIyTte89GTmPMnlxXbmq82ba2Yy0J17UI7WhLfqEdrcum8wn5hW3etR1Z1y0tLrl1WXjVSGjfrvAtDQ0NOUKx8GgxthkskpfB7+BhhG+SOrc0t7jamlyhXTHKddQ7DPPkBnOJqbXFkeEhN6V1RuLStba0mALlrb4+hY9pyoyr4Tn6JVbQxL2jnljlWXtafHLbO+r15+padgyKI8K5arhY2IjAJyBQ8QnPxEcuAwIQLWEGVH1tYghUZlJ/GJdVTf5okOEChaDhIX3O9MG78lO5XAvUSBOqTQQG1mgItZYVP48A4BBIpt2phw4+mZ+TJ8gOnj3dM9k6iRQXzYD4TuvBVmBwJih7e3u7qxNxh6uVmdkZEVarhnuhkvFoSNTZ6l0At3DPWW2z9T6rEsT3RgQiApcdARZAUJXAcg6GdUouMefn5k0jNswZfqzyR/LSpUHa6zPhNqaxsdF1a9GtRcwicVsR/LHQGFNEICIQEYgIRAQuNQIwNeI/mENxBRgSgrjXcmf2/Q/fZ0IZYFWUTvByW3IXiFXE7u5OhudhzuZ/yfAQMF2a8t+pfrMz0+7Z0yfu1s0bcmfY6VrFu1nSdcpboVjrN27ckGVdg9EY8KoomKIs9FZ1hXaA73svesQzb2lE4v5FQgBacVEWmaMS7kJ3tjW1mvIXdaQv3Lp1033zzTfmqeiHH3+0qnuBXZmT6PvA5xGpzo/7OpLumrrZI0d72BqLPGygkNcsmh73itNac0HAYx3SDz2G/cF8Ulmyyw3JTVuK4Tkny2LyYL2qs/Oq9X/ex3vPOlG+eYUCgKdBYNfZ2WlKFNtvs+5az7qMvD+ZTmwuYcwk7M60+s721qaUH666Xo2tjK/wZWGdMmwZf1dX10yZgvXBToWawdLR50lDJalgRwonU/eF++M2IhARiAicIQLRwu4MwT8vr2YCRGMSJoP9MJVBgOALnEC+5hpEzFYm6b5PSfYu3pf3PO4FGhsa5Xe8xVXK5crG1oZbXF4015FYoYX7Q9k+5d2n8QxEAr9MSvbR9goEBtt6WdihOVRVUWlBdt+8GZPrNr8wzLPAkUaU/fDL5B13IgIRgYhAROAAAmF+YM5aw8JuCgs74q5qzsqba+xhxu0kF8ZnxuaHDx/ar0WxL8LYfeBF8UREICIQEYgIRAQuAQJpnoT5MsyZoep4XcEK7UcJIt6MjuZ4YQn3vJMFBHG6ibGEBUiYq+16+gXhgTPeQkNMSUn1j3/8o8IxvLDF/1BWBHDwyiyI496wQ4K8n/30p+4//6f/7P7jf/xP7s9/9ueuu7vb4mbZvYVojzOuX3z95yOQ7gd8z/Py5vDq1UsTyiGYlvGcpYrycouj9lAxkntv9否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否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否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否csXm3HItcp2GW5eLFi/p+LxZi9sumk91qQ8/XEXAEHAFHYOsQSOk6hTSNT7EcDsESCDWoyKusYFNhAxY333jzDfnJT34iL798HZoXJWk+0JQi6h9q40ETBT6E2mGu7MuvvpSvv/5aOmHuL8XrWC6hFGCNrBDxkqnF8OOzEchsO17zR62YYfi5++7b78BrzsGP4LeqqXXsWDP8D96Us2fP6YI1c2B8mtmk5h3lf9S6w8QHc5rQOMUQ8l29ek3+9V//VU7D1OZ7f31PfapNTfH7oEUeaOpBY4jzIHXrkGhJ45G1JojLYE1uR94NT/Txvv2TqiPOoosVCD5Hh4blQVu7nAG2C9dfVuz5nFGIUUF+odTW1qrGZHd3F/pYunWeJGBJTJP3/TxCAKCuhRHvaZNgzj8K069POp/oJoW6Iw3SNN8kj588jiGkEIgCnrQxzRpLE4mjIq+UJiT5iYX5BdWuyyvIlRxshNC+FkVfq1yplLbhLFFW9mMKE0dGh9VfaWlpuVRDW43C/vHxMc3c+jLjbkd4VrqWqx25YYCmSunSoKamBuWthi+7SsmDII6BeB/KDahS8WAE7drV9QTj4hmpP3Jkw1UwGmX5WjttOIFNRDSsN/Hqpl7Z6fw2VUh/yRE4oAi4wO6ANvzTqk2CxB/NmjDMg4CPjY2pU2QS81QgU2BXjBtf2M1NHZ9FsDeVaBa8xB2qtAleCNv09AEwMjwiE+MTahqGxSOWnFx4cAQcAUfAEdhJBFL0izs1h7DI9aSrUxcpy7AwWQRzl8agGVNDOqU/HbNT43YNdkK/8sorql1Hszr9/f3pTP1OVsvzcgQcAUfAEXAE0hAgNTOKFmhXku8ijdN/OB4C31JSXKyadf/3v/yL/PhHP4JfowLQtCUVzJGvMZrILMjD9PX2wyRju/zpT3+Sjz/6SH3DUksjpEqhTtiQmV4CFim1wZFpedgYAik+PMSnoJVtQs07WnOhgOcJ/PN++NGH8ElXKqfhE20cfpzIz587d14FBnkwD7eEBuG7BTDTR3N9DPwu6PuJi91NTY3S3NSkGnfllWW6qH/v3n2ZHMfmUyychyWAaC6UmhJpu2pbc84Uiuh/E0BQYDc0OCR379yWszA/ynkjFwQ0Cv6wLelT7MSJE3Lt2jVoUUKYBAE4N/1mButTmff9OoXAuhjxAfHGb2hwGNaPllVASpOwefkwTQp/jjrWoW24PoYDoqeEcZoD7q0VoqSjcXMFpjah9QUtMOur9o6VbZ1kLNq2HU3DjhpqtYePSDHG/j5opbGcFohBkl7Y/Z06JrGhv0H6pOOvEWNTVXWV/thfGFI8Wo6utamG3f37cvHCBfUNmSozU7WUrRVST/3MEXAEHIHdRMAFdruJ/q7nHYgSaG8cjAjrpIQPMCMhozUBjQNqHXDHnsXBtIb8FaPovSg1kDwjenGyB+pE8VRgUuSfAJABorAuvyBfHbETU07M4x1aAFAnQpwCJmzT811NE0di7cERcAQcAUdgkwhgEA20Cu9HA6sOq9HYygWuYTgsp0ZAy7FjUnj0KJjWIg7g0JRTh3U6TjN30joM1TrQ8xl3y5aXlcupU6fkAhjCL7/8Mo7L+FFUnnpwBBwBR8ARcAR2HAHSoSRPkeTZwgJyMB8G3Q+YDquX69evy9tvvy0vwRxmOUwpMtD8YkiDhBMpGv2E5s/jJx3yPjSwKLBru98WW2YJMZP0V5OyV8OF/31hBIxHX4pMVdo1hWrcgLu8dB++6H4v3V09ENidU42Us2fPyrETx+UItE5yImEdeVMK6xjyKMSjIBD/GOcHb/0QpjVLYer0llBodx+/Ecyb6FtKfeIhbmzmj4wrPxabbzkjG7cx8WT7zMzNSm/fAH598EU4pZqOhCu03SE1SXu0uVkuXbosD2DulCYbTWDH/hx1vzhdP1kfgSRWutbFqNE3SbEUMZ/FWhc1y6anZyBXK4CALR8mIku1D1B4HXx2BtOx6+cUPdEMtaW1nfjuEn7ctpCLRo7LwLyfmdg2RkCZuGFxHAL4yqppKYRFqEJsMtcNi1HfZe42nmxjSTactLnroVIBz2m2mTRKfQPGqQRUl2GOdGR4VLo6u3RT5iJomAWtUwL8RHU1CoW4fJyDEx5JxdjvQvAeaEjs56N998n++qz6buadZ6Xpzw8mAi6wO5jtvmatObDEgwtiqPCIBBw7WGgTfwS28XUXXfR2LkymMH4scEqQrzUz2Kc3DbNU9UjIw66rJBk3e+UFmARR8MkJxsQUNOwihkjfxwuZwrpUun7mCDgCjoAjsOUIGOcFTmwJ5rzoT+Te3Tty4vgxLFjSbEp5zFSTkeOPTFv4wxPs3lRNu2VlFGmW5eTJk9J8tFn9QNB5PYO+o2f+xxFwBBwBR8AR2AUESO8impfkX8jz8UftESxJQ7OuRF599XvyX/7L/yM3YUKxtKRE5mfnJa+Awhv6/gHPyI0q2MxCf2b0cz4w2C+ffvoJhHXvyKeffKq8Tj61SZCmLnKnsubbqconTlM3/WwzCCTb1M5tkTEHgrRJ+J/78IOP5LPPPg9mFhvq5R9+8g/y1ls/gHWA7+kchvEY2GbUosylnUy00dLKIhbFS+XmzZvQUDmjvu3+9sknEAD+Tr78/Av1AczNqWhubGbid4Qj0kgGW1vgvfQnyVgH45yyURX7AIhZCFMHYRpzANp2XGspjPxwQW6ubXAMG8iuXp2W725/J99+920MUBAdmCAhvu0nz4GAfYcUVvO7hVQbwuclmZmawRrYPIR2hVIGgV0JxkCaYFSzrxnf9XrZsWU43PLHwP6wTKEfx07kZXnrM42x83/CN8SyiQrtZqepMbui2rb8DjkGaPk2WOedqgHNL9MiCl0P0O94PspKgR2tWUUF5iCm2JNGUQjb2dWNd4ZlAe8yEH9Wy6oWDX36jH8orHt2YOtuKOKzk/IYWYdAJg3LugJ6gfY9Ai6w2/dN/HwVVHJDqoVJBMkPJ+tzmKyMmsAOE8rUwOXE6XnQpU8IathxpyIn4wtTCzI5MYmJW+SvD3DG2Drtfx5oPa4j4Ag4AhtDgGMrfwxGwniMzrlLs7+/T9raYDbl4gXVKmBUe4UMtv54L8Fsc+ymTzvJCdMq+hs5fuy4HG0+qiaQuUkjtbmFKXpwBBwBR8ARcAR2FoGYz0hkq7QMNIz0D6uz8AVUpX5Y33zjTbl65YrU19epL1Zb5ATp02uyi6SONL/YA9OLH378kbzz7jty//49Xfi1LJRmkkZqdKOmEdGNDhbXjy+OQLKNFXs2GALnIHNLwbXF9Mw0hHcTMP83CA2iPLi9GEMb9khVVZVuVGpsbJIjaPcCmJdjGtxcygVyCjW48fTw4cNSAPOo1ECiUIMaOZ999hnSCZt7KfSjsJaadjb3YToMdkyWUx/s8z+xYCSacIb+EypNjCawJkDhA4UQtfDJVRQJHugzrQobwVpaT0gjBKyxMC/Gi7h6R4rheKGTFI5cq6HrkvKKHN3AUF1Vgz6whDWcmefOgalaylz10bGWqeAjYH/Yib6wbr/D58NvUwWREHDRstb8QuBZaC6Xa1f0770TZSQkGw0cjwYxfnV2dcno2CjoVL0KVsshXM3D+BMoDtGmViNM+87NaHyuaXJz5lNDGKpCFGu4p77gDx0BR8AR2B4EXGC3PbjukVSNAgWqREKsuyWVcKeqwIXGUexKoZmwpIYdJ5rZRrxTpd7tM07A0vGhOZFCMDd5eblCRok+BPiLJ20osuJpzZKoQnJSn7jtp46AI+AIOALPgYAtmPAVZeYyxlua+iID2N7WpqZTZsCwW6BBKAvq45Xbo0k3I588yaRKwTDS98WNl1/WMf7hw4fqM8be96Mj4Ag4Ao6AI7AbCISFzFTOkRyF5EyFBK+++pr88z//Ut588w2prqpQOkdNuvx8LhtAJwjCG8blpk5ZXpQZLGB/9c3X8u///d/l3Xf+rLwi/TRRWLOIX6CTqfz0LEkwMx755dYhkOTTk+fMgRtGqXH32RdfyHd37sjv//AHaN3RD+8N+eEPf6had/mV+Wg+fDH40ZAA5zu5EOzmQshXXVUtly5ekqrKajl39rzU1NTKu+++K48fd2DBPEeFfRTyLkTvMw0KDXT+hPz5CWjaLMwBDMl+SBzGJ8ak/UG7HId50lFIh8YAAEAASURBVCIISYrgQ4yBAnEuKlRDmEoLDkVF4T6fmVl2nnt4NgKpWXz49pJvhPYIMdgeFKB2QYhdvwiNY5iAbYKfNAqy5ufn0tZukmmsdZ451HGtzdZ++IxmZDlG6ni6VgI7ci9VSgrCJqAxnQ8hPIX5NPXP9T/6vTTh+44U6RmZcH2yt69XHj56oL72uImgDBp2tThWwDzmNDfHg6cLNQt/p2em0K7jqtHKNma/Cjp4zCyFQfyd4CR5N74fly31NL7lJ/sKARN0P2+lNvve8+bj8fc/Ai6w2/9t/Nw1JP1KBhXYYecKd6SQWCuBQwQ7JuP6+foIUGBXEPmwm4W9eprEnAKjZJM2fTMD+/VT8yeOgCPgCDgCz0LAmCsbWnmMTZzYzUQiVJIjnaNZzD5o2k3Bz6gFppWZnj3TB/YQN+ms/czp03IDC1+6A7Sz0wV2MVh+4gg4Ao6AI7BrCGTQPvJzXFyiq4NjMOX88svX5Y0334SPs/MQrnAZG+bbYJuPcSh/4womzfwvLi2oVtbdO7chqHtHvrn1jZrGpECG/A7UUVZp2u1anQ9wxmvx62xLfgb05USf6vxRs4sbS2n2bx7uMLg4f/z4CTUFWAXhXEVlpWrU2UIkj/QbdRpznUo84yZULuxzwX90dCQs8EffFuHX7yxqB06XmH/Gpxg9PSCHROWJNc2nt2Gz2AlgfvzoMamBpYYQwuQyH9pOVZVV0L6r1f4XBOeJRA4IbFtdzfh7RsJqKUOPEKDie34Cf9b5eflSUVEuJ06cUGHP4OBAVARa3Ajf9bPKFOKlYtnazwpNNmLzH/2jCbTAdiskvyL2X9a9APWmVSh+c9Swo4+7bBLYLcBn6kB/PzYIPIZfx35sQDgvlRUVUt/QILXwtbnY1y/zU+madKwDzWdyvNNxERVX6CPgjRbuVjt4vo6AI+AIZCKQtQK75OTSCGlm4f36RRFIrC5qUry2e+HIdiBBJGEbh4CJDqstJHeGhXtJcm+x9v/Rvk/7Zu0Yak4cAy4qsIMd9Lw8mA/BziX6e1ATA9yyaCEV3e740RFwBBwBR2CTCBhF4+thpF1NuchIa8BWZf5jjCk4mx+BnwMuoCxyN7ky1GTOI9M1SIxjPZl7Hvke6Sff5TlNyLS2tsq169flDnau53/0UZTJegcrBJ8naMJ60f2+I+AIOAKOgCPwDARiykLaZXFBs5KBl9QKP9naIj946y154/XXYc65WYUzNJNGP1u5fBs0ciWik6SFi9A2+eD99+Xf/uu/yQcfvC9jWORVGom45BeT/FCgs0wjmbOf7xYCybbRMuBD4WL87Oyc3L93H5uW+uSTv30izc1Nai3g1VdflddhIpVm58jPMlAjiOlQ0Evtlp/89Cdy6tQpaWk5Ib/5zW/k1q1b+j3kY9GfaVPjMs43fBCpOZWmuL//WN0z1w1Yaz7jxui79+6qYOjGjRurwOB7VTBXSw28unt1KhRNWj5a9YLfWIVA5vDD8TF8zauHpvHxMXnc8QjC6Apokl6U0vJS6e7tkkNt4Q3VfIxysLZdlSFu6KeOP8ohoADLWPehTzWOu8zV/q317lbeW6+MvB/TCWQYBO6jKqgsLy2D5mw1NO4mML4Hf9xbWabnTosFjRpxcQE+7GD9q0f90g2pb8DKqkppwphFod3o2LhqCjIPpUuoJ+vKDQkD2JwwDRpFoaRqOLIViAPGNmqtKh6ZH4tlnIbWc9fAX9jDCCT7kI3je7g6XvQ9gkDWCuz2CH4HopiL2I1CYR01wqht52FzCHBg504lOu+lDfQZLAirABQTBA+OgCPgCDgCO4DAU4bbIHQLZeDi0gg0y7t7umUYPlkqyyvUvBM5b07Y7V96ial9QMZ3RRe0SrAA2nKiBRoLx6QCZlpGkI4HR8ARcAQcAUcg2xCgIOA6Npj8/d//SF566Qp8AZVpEW2hkwvMh7BxhYvUXNAdGx9VX3UffPCBfPLJJ9BIH5BcmPznj9ojjG9BF6ztwo9ZiQDbOSxSwxQgNpSOY6Nub1+f3Lt/Tx49ehg0J2Ee7yJMYFLbqLi4SE0z5oKv5byI5lKbGhvV9xqtyHR3d6t2krp+oAlVbk7l/ItzKBySC59ZCcgOF4p4cCNvR0eHPIKQaAzzz2X6lCS+XCdg++BXV1en/iU70Ca3sRnMBXZb01A2ztmSDNtjampa+8DRkWHJK8iTSmiTUqOU5oEt2Ht2vd6Rb/C7DwE6y+Axog6hzRuJiCzCjh9TZQsCuzEIjwshzKLGWgUElqWlJZFga8eLtm6GxHAOGwxoxWR0eAQbSBaU12puPioNGIs6Oh4Dc/Bk6DcW2K5UQugFb0ftPGqrloFXY5zQJiEm8Ui9ZW/70RFwBByBnUcg6wV2yUF25+E5aDkaubZjqP8chHTjsPfMnTW03R2H9GiJiUgc42CfGKVP4KSTPNyns1uaG6Hgjt84o3JHD4O9xvPEq7z04Ag4Ao6AI/AcCDzPGErGzgIZN+527sDCSSdM4hS0npTCggJ9rGM1x23w22Hcjt5TTj84brcd6NzteQxmdGgyipteaIqFi50WkuO93UuVwu740RFwBBwBR8AR2AQCicVKfTtakVbeIzLFxkXMw4dr5PJLL8n3XntVjmKTiQXlUEDrFsGz6KZDCGZoHeS9996T//XrX8tf/vKe0kqmp76ZIJhheiFQyGAphXv2xO76MUsQQMOwbVJtB14VAqPx8Qlpb3+g2iq3YPL07LlzcunSRbl8+bIK7yhAsrUaHmld4MKFC/J//af/pKbB34cG5t27d6GNtyz5h8Ky0yJN/6V9h0GIp0hE9/fzd0KMDTNrfd5TvEfGpBd+00ZHRtW6QwGAoCYjfShzDaGpqVFu3LgunZ2PVZg0MGDmGS0lHNHnMrpdYnabiOenioB+9zjL/ObIB0xjc/UoN6xDGFQJ4Sn5APp1m8M/tpn9ngYlooV84gz43hIERBwfqa0Xt9bTktnyZ/YNsg4WWBKuUY1TYAcTrCdyTkg5NhxyA+IhbDjf9ZAqalyUCYxR5K8W5+alEmZkGxsapaGuIfYBqRFJiKL2ojC860mnuj4oLS6NBXbBV2Sc7KrvYVU7abNFbZfAMJWCn+1XBKzv7Nf6eb0CAmljY2oyu+PwZK3AzjvCTnwLRvUiYqNZ2r3Uh7oAJo27Ufij7edkSH7Iyft+vhoBftNEmibUwu5TOsK1STueOLFfDZrfcQQcAUfgBRBIp2hIaH1yp+OxZcXd4CNYMHnQ3i4dp89IfV09/DhU2mM96piuQzcXm5gwjhkZ0ufIsaNH5crVq2oujOYxuYvagyPgCDgCjoAjsFMIZPJr4XoFGiOlavLs6pWrcv78OQjujqgwIZh7jkoHukY/P/TvKguz8vBRu/z5z+/Ib3/3e+nu6lLLIQWgdQtY6E1qKSTXNzJp407V2/PZGAKZ3wffIo/K9uRGI/rz7ezskvvwsfbo4UMZhim6XPgxpBC3Gj6uOGfifCcH5jHrIcT7wQ/e0lnR5NSkdHZ1ChfVeYNWCHThHOknp2N2njGF2ljh92CstfBmNbjOQmFdP3xyUQhBfFWzFbgSt1qYHqVvyVOnvpWPP/pbouZE8KCgl6j2C57GiGUMUJF8R1On0G5sYlJKy8rRHvlSWlKqml3L0BylMHUjIdnefIXXzOPQoXBMjpUbSW+74pCvYZ3mYG6S9aYZ21z063wIKmnaNltC8munMHUS7TOJdcrDtYflMLTmjtQdgQYw/KhGgZsoWReOZ3R1QN+EvT190tx0FDHCWhzNPWuj2Et+dAQyEHD5RAYgfrkjCGTPyLsj1fVM1kOA0w2bcnDSkJw4LECrjoRwagKaASDgNljFkw9STQZLIFz53ww86IQds26dCNHJd5qWBZ5p9A1O/BxcR8ARcAQcga1BwOhdcvjlbvARLEjdgT+RU9COo/8Kri5xnLZ4JH36ixLg4lYUQ5lxlo7O6k+ePCVvvPFGzCQ+TWAX0uCbHhwBR8ARcAQcgS1EgGwIFi6pKbEEfo5CuSNH6uTtn70tv/znX8rlS5eloDBoktOzEzUtaJaPQoOCkiKZHB4Sakz97ve/lXf+9K709vaq5kkeiKLxhqG0pIyBVgbymMEQhUj+dw8gwDnJ0pK135Kan/v666/VxPd9mMukNt3Vy1ektaVVWlpapKS8TAV3VRDifQ9+72ihh4Lezz//QoW7S0vBUo9uYcVnwpTD1xKBYReW5R7A6EWLyL7DH4UJFJZMQMj5GAKFjscdiiXNEmoAJoUFhdIIc3/Hjh2DsL08zprvh7kpIqWBaoAy6gECNUbmxU641kW/jsMDQ1JSVAL8i1R7axkWkkYnYLYUx42GzJZga9hamh03mtZWxGOe6eN29IXgPstG4TFd4ZAOkAYUFRXJIfgp5Xu79SUlMTQMSKMo3O6Dtmlj81GprKmB6dh6aPiWWBQ15bzMPoINA8Mwn/ngwQPp6noiL12+FMfhSMSNKckBKZnfunUGHh4cgc0gYP0+2Q/X6pebSdvf2T8IuMBu/7TlBmvydNIT6BTjhHgcNCiwo7+1TO06y9BSPLjkanXNU7Q7PFNE6cgWk3EV1mFykdyFSiwZh7FXp8anHhwBR8ARcAReGIE1B9g1qBjicUc4zWFSg4BOyuPBORrgbbwOutMsWZQ4DmRnST+5OFpfX4dFrYty5/YdNRGVrEP0Rurd5EM/dwQcAUfAEXAEXgABWxBiEoFWBeEAmQ5uJKQG+Fvff0tee/U19Vu3tEzrH3xGU23QiMKCNDyQyRKEe48hQPjd738v//N//Fp6enpivpBWQxiSeekNpYPhzP/uQQRSExRd2A9CpSUZwmamickJaWtvk88++0za8O28efMN/Waajx+TqppqrSytEvC7op+1JXxHw0NDKgCwOVP8veA7tFnYHkTphYpMTJM/YsI1l8dPHsuDhw+gMVQLn4FBYKfCBOTGeWUlBKLV1dXwrVwQuSthY6UazNYhkLwGHuxeuON/n4ZAEqs5fL998HdWXFKiAtTGhgaZmZ2GYHVCFjE6bjogE221ZGabTmxzL8Z9MHo9eb2Ecs2g7jSBTFpBs5jFxcUqgF+MxvzN5bq5t/gNm2BDv/QIN66n0YVBd2+PnJw8jXYqFvpkLYjcGCitQ5/hu3yFvB19bNKcbJqrHytWqhvFXUpvoQOGR9F4pRfJyJaAHx0BR2A/IGDjzW7XxQV2u90CWZg/SY9NnEm4ub否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否FoY8Dsd2Mn+ZjBqfhRSCicyg2SBqKurlV27IwNCgvP/hh/IIpjG5EzQEp6q70dKepyPgCDgC+xkB0iMSJFIY8iInT52U73//dfzego+xRtWUU34EtC4XG0tIr6hR0tn9RP761/fkV7/6tdy6dSvNP1Mm/5JJA/cznl63pyPA72cai/z8DQ0Ny/22+9INk6rzC8vy6ms3YVqvRK5ffRkaLnPy8Mkj9W2nguKnJ7s/n0b90iqnfZXzx2g6SP+AxI6m/sogoGP/hXIrwooUFxWrKdtTp05JbW2tWn7QuaYl5sctRYBjHsfFqclJmUG70HQw+YEJWOBQq0n47vdNiGgGvydu3Oda4OLiovrjLsamRQrwsimwbbhWyY2V9GXHsaegsBDavPXSAD+Qi9gsPw5hHtffkrzdJNqyp7tbTT7nNjdLZSU2Y67i6LKppl4WR8AROIgI7IrAzib6e1FiuhfLnP5hcxZorJUd02PYFZ2OU/WdDllTi4phkXLv42C13LqjLdsS1bCMG9ImVpzYccfOEnbMUcuOR+sHgY3eunJ4So6AI+AIOAIbR8AoYbRGEg3J4YrjNM1Cdzx+DNNgj+Sll14KvuuwaMKxnQsrNvbrzk1NjKM9TpgEInBPJ0MuFlvod6S+vl6qsCOau3PJEDMwHQu2u5NJMCSfhTv+1xFwBBwBR8ARSCFgdExJT0RPeO8QtOdIm0jLuODPBf6//7sfy/defU2qa4JmztICPNuBFhUUFagW3YMH7fLX996Tjz74EEKXQX03lVN0BvpneRo/kyBjq6L7jb2FwFrzjiTvb23+tFpxkZ9aYt98+62Mww/WJ59+JGdOnZGz585IXv4hWfjzfMpHG+dT0DaLv6mnJbxHnz0LM2Ku88ioI9EazyDMMHbBn119/RGpqakJNcfzHGwCPgTMaJqR2nf5+QVqvQe30F+hTbRHMdrNYq81foV5fnjCtbCpyWkVYHFaX1FeoRv4FkcX4rl8ZvnXSjMzzm5dr/k9RgVmP7Sysx/zx5AHv6YF8OWdx80d+M7CN6uPtvaPZb6BVFkP/ibGJ2V8DAJUbAYoLS1XfotC1f4+8lras3SDilCTHIFuakZHR/AbliOHa5EGN2NyBS8EpkkcjNLFo1M0SEWHKPbeOiTH8r1Vci+tI3DwEICrWw+OQDoCSqRBpObn5nXHChcraZohDnuZQsWV2LkTEsXcPAjsMLlexOSbzqS528eDI+AIOAKOQHYjQIZtHA7W7925I7e/+w6O5wdjxpwLI/xlBt6ynz5Tpo8sX9C2bsSOz5fg1+X0qdNgKkvBQK6RSGaifu0IOAKOgCPgCGwAgSRdIg1jIC/CDSMXzl+Um6+/AZOYR9VX3TJ8sa5AWJeDeEtYlKWGwvt/fV/+2//7/8m77/4ZPoyG1yR0SarFHGyRUzPzPwccAQiTsKDPb44WZTqhxfLBhx/Jf/23/ybv/uU9mcZCOc04lsG/VAEEAOZb8YCDtqr61G5qh/D8a2i4dsK8aNI9CSMT30r0afblxvoGKYLmUxDQr0rKb2wSgeRcnms4fRgfqfUI8OUo/FI3NzWpNtcmk98Tr1FQSdcAM9jETz6mrKRUfcQV5OVvT/m否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否0AP3T92C0FXFiHSNzn2E0/dt3dvTBPRF8OYZcpd2tqNDKC/Om7SIsENroV+kvYp0kTUKQLudiZWl5RAT92LeqDpLysXAsQ4qYytj7HXOyfldSPjoAj4Ag4AgcVAS7ER0QmgkDpBc55LIXvusamRjl69CjOy/Te/Nycmgv7/LMv5Df/63fy/gcf6+KmmWdcxAZNLkhHVA3kLKI6JIuaB//aL8o0Cw4RuVWyu15xlDSHSqwZxUg4seC8N5iEp1l4WpuIrnFfscI9pdV8KREsD1svoIYef9R2Sv7i58RXf4ZvIjGcWpkyskmPpFcJBKLmsbKsETlxa2vbcmN5JrI/QKeZ2NjcjhDY96Bw8HuI7vF6GVaO+nv65D4E612dmQK78EEXwC9yS0uLXLt2VVpbW1SAx3c5CdVvh3/w0/PwwP8+DYGMbhHBp29w/j4KDeWe/l4I7GYgsKuUI/CVRjOROiYwlmKdPjY8LbusfRY+m7h4s6AfE9jMz039+fCfSGEY6x2+wjjatp1YsySPlhn70BzKNwxfe8PDI6oJSKEiN66UQNOO2oAcx5VIJJqG650T4xPQTB2T2Zk55c+0vVEr7S/JxrfMcLT+G7d54pmfvhgCjm3q+3oxJNPfdlzT8dgrV24Sc6+01A6Xk0RvYWFed5tQYEfmzcPmEKAfCTLADJzkeXAEsh0B9n8LxkTSTBKdm/NIATR3rnlwBPYjAvr1J/qA1pEMG+6xP9Cva/9Avy5yVldX6WMzI/Z0PPg+Y/C4DE2GYHqMDC93Rb/00hXdIDM+MYYd1E4rno6lP3UEHAFHwBHg4mMauQK否T/UQpjMO37sqGrd0Fwewyw0IzoedchXX34lf/3LB/Ldt3dkfGxCFyVp3oy8HhcvUzPAjLT3ANzJsutiK8ucvMlrexAv2sZGKPlU6bwewwX/IjARCDz4F+/rq7r4m7iHmzQgyuR1Edfy4etRsPk128zm10yb/CF/yef2jh3tGa81fRyT9yyeH/cBAolOTV9bnHO23W9T/1zTWJcJIWhmQr4sFEy0tByXa9evSsdj+FqGb0qZMhyi75Xfmd3y46YRoKlFatj1QsOutrZWmhublS8uKqKZ0v0d5rBpcRx159BWhDWByqpK1VCjLzgLeKTjYxgxw8hpz7bqmOgeaUlyPOS65ejoqMzApc8htE95eblUV1X//+y9Z3tkR3ImmvDe+wbQ3rMdyabVDMkZLXef3dVKu1/v/pn9Sau5dz/dK/NIejTDGYkcmvYGQMN7791938gT52QVgIYrAFWFyO7CcZl5Mt+TJiIjIwJCuVm/doE4Reg0zIO/VfqInJx0Y2NjrhabKOvq6lLytAtDwBAwBM4SARPYZQj9kGBWIvpwWXN6Y+D0dpxwuHziyRQzFusQ1oPmvlZgi5umU7YCgR3THLeUx6lhzqUFWGSCSS6H+OZcPazA5wKBsI3ynII57jSugNNmLvpQU5Q70WQHNildC4ZAHiLAli1Mp195S+Y8tHkKrEdHxlxPT49ramyEU/M6MTXG3fPsM7I0EizU4a7nComTTNFR7tF0XY6F1Xv377tpMJj0Z/fTjz9CYDebh6halQwBQ8AQMAQyiYCQYSEthrmnGguU3Z1d7u7Nu66trU0EeJybuGD5pz995/733/7f7rvvvpNFTZ2SSOuJwCial3SJH5NaMv9lsuCZzisqtxZWhGp6L3qXn861Zv6hp2I5d3Oa9nO43ttZRP+E90X7DrQxBZ0lxSUwuVYKnOHTqaRYzBFyE4+YFiVFgFdRv56p+Q7iTDOZtF5D3LlBlm4TNkBf+w1xmxJn5/v9HV83X37mzTzxV+qQksZH4aMdQdcqf否dj+3GKSBA7PU76DH9e/ATsr0s0yT78AjM/Y3C35YXjvCzi49F+ECk64K29g534+Ytd6GzMzZ/69seWx+EwWwIu7SFU6hq7r0ire9IF4tqQcyXYHpxemJSTETW1dVinWwLQtOyuJ67fceIAZA4fjSIo2ftSXpzoXCL7nIqKyshsCt3tfDfNzc7J+1Y6xymCc8zUsn0DKPvpK7D+Zj9ZRFlpOlObo6sQlnJp42OlmNNc07G3hKM026Tib2CAn2IT8DvXXtHOwR2tb6o6DzsP+khfNe2vj+OlF7A+IGdGAKGwCkhoGMRX6dz6ym9+kReYwK7Q8Ka8x99l4nHExCpEwwbOgV2y9hJswKBHXd3aQg7gd6z494IEC+yhyTODLu9cbIn2YkACV8uKHDxgWYleL6yvCJmJ7KzxFYqQyDTCGB+lFUxn+8aHK73979zT578gsXQVuzehKkxPOJPxnss4jHEDL5OrxpJYjKGn5C50Hfr9k04ON9yr9+8gW+hN24WC6sabN5QJOxoCBgChoAhoAjEi76YSoTPIL+BuaqhocHdu3vPPXzwwLU0t4hAaGpy3H3//b+5f/6Xf3Hf/fE78fPDRcsymNJbg1adWgLhtMS8ZFEScxenL85UOo3pu7PtuFv50nl2TuOsi/5N0qC+fMY/fCpmL70vPzUJz2deSFcEAV2xCESK4ReJi/Rl8B/GebyEQrtS/4w0s2reM63PGYvJmOc5pycCO/o438BvFd9hzVGDhYviKsBTE6XcEMSNcqTJxcwm8mA+MX2Ayug7knpJdexPFiPA76ftLr2Y/J58pt+TmkPUBBqfmJD2UYhn/DEwHjWe2tvbReuLguQkRDloRskDOzskAoo3+6PwwhCkFhUWiU/qMmjYcbxgH83XQLcA1C6kJlptTb2rqy1wE+UTO6p76k2N3UCbOfrUMhQOWE72mUpsOG5qbnKV/VXCW2mfk76FuKwTfd4Njwy7i5fgoqCjQ+oTda0ddbMbhoAhYAicJgImsMsQ2jroJ+RyhjI+jWziSY4zXTLF0iwKJzEyDjFDwPJgcrNwOARS8DtcUottCJwpAmy7XCQQLbvyilh4d6aFspcbAqeCgF8I8dNiMj9yB3xvH8yK/fyTu37jOsxZXt9RGmX0lDGUCMjOz56yHCpTKeMVY2GPC6w3kNeVK5dcI8xsDg0N+kUcJIgXZXe8xW4YAoaAIWAInFsEInaM9Bn5ULHmgTmltrbG3b57291/+FC0bai5/U//9M/ub//2b90f/vAdFv2nhK/bhFYIJ6VtLjBHeSmWcskpMO2+Pj/LI+uqYW/+isIsL8gAOBKd9VSfs+nVYp40JVoBOpe+ZWuhpVgL02jUzqiorBCT8BSClEKLToRxE否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否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否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否ZNAtLSzCzN8AtIv6xAz1oviJSq2oTrc8clFVpta0eTs1xe5Xu2G9e8zj3eV7jrv4q9XTOpPu4L2tyGwdS0jfhtTGpOZJERawm6EJVARa/WL3ReBeIJon0zAht4k4udqmjvclsjC1ftioaNKecY/05Ry1S8GHTUxOiXDb+xHz7Z2C71po2bW1tYk/O37n9SysXq4Xif2GviPpJ3Nurl7odgrBq6oq9xXY5WzdOcigDXKtcGFh3lVWVkAgVuJqYF1kFWPLIjbuqobaidaR5UDQsYpdJbol99dh9pKaj/QxXl9fL/7AaSqWmxIY2IcK0C/og49mk2fnZ8WiyRT8wcblRxxqfHM89f8kacp7/B37awgYAoZA5hHIS4Fdbkqe06ix935rjRtOSXskCK否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否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否akTcbelB0Zhe2FbXITgkr6jmMoAk1XdAYmMdnM+dM+4i0CRVZPomcsH7cr0goEBXZs+3IPtOkC+g6toCwtLWNDcdpaA/tRVGnmH3QpSe8ZJ0VFj9GjPD/ourHyAsetbqbyOW45sjm9YZTNX+fgZcvjKeLgIJzXmDpR6cQV4sAOTt82VVWVstO+CDudLRgChoAhkBEEQNDSzN/rV6/dL0+euHcDg9Cg887PuV9ACQwSd56RhIneYgjsQNhvgmguhdmKx48fu//+P/6H+/qbr92li93CXDItiWclCjNSVsvEEDhFBCik4CaZFZgEnJiYdH/+8Wf3/PlL9BeYXtkhrPMFI3+oIT6PT/yT8FKXdCik4z+hBRChq7PT/eV/+Ev5Xbl8yfcpJBeWUgkGfZEdDYETRICLWBzza2Caq7ml2TU3Nbu6au9f0b/2fC10nCDUlrUhYAgYAoZAviMQEIFra+tuHBp2tOQwNTkpGk/0A+55Lx+R2kYd7e3wI3vdNTU2gB4sEuFFvsN0KvWLyBdvGp88K4Q+pLGDb3Qq5Tjll5CHofWE5eVl0VyjJa8S/FSj7ZSLE7+OAjkKsak9xz5Ay2IM3E/JZxQwqibdxsamm5yYgJWgITc7O5sisBM2KSZN8/97xgDaiSFgCJwoAlkrsPMLtfGod6IgnHzmrEeW1YVEQfzjbn298GjwmsQaNVkouOMioob8+jZaKzsaAoZAZhHYY9zD2CJPQNmurK64STC否6MjbhYCPBLG8VipySMGBnpHUrztbWpeFLhKmMLshIDh4cOH7pPPPnGdXZ2utLxMBHoUSMQaTJmtlOVmCBweAW3LPEZB51F9FN73/cDfWcROzl74enz79q0IudM5epm70af0HyZzmc95P/7Fk73PU+NqWuwfRbZbmPOLxQTSRWjaXbx40dEBO3e/Slm1gDhqmcmcCoMaPLNTQ2AvBLTN7/U8vM+2WYQFxJLiUldRVu7qILhraKgXv3YF0EC1YAgYAoaAIWAIGAK7IADaTFd1MJWSLBSrChqTwofpmWk3ODToJiGw29zEhsmIZtQ43DjWCv/Gly9dhnnMFldoG7cVmmMdSQeFgUIs2WgqfGv4JP/OWfe19TURjFGrjcoAasXrNGsrvAver0EEiRDWicAOPYfrn2rFhM/Ik8kmMmgEglVyC/PzbmpqEoJHaKhGmnfMS2lc/cbSB/lnR5AnO+7m+w3lSfO9ntlaP22f2Vo+K9feCBTh0f/a+/HeTz788EP313/913tHyOATWVTKYH7ZkVX6CJ5+fdRSpueTek2iLQycrmQAjW5yEuM1J6fh4WH36tUr+M8ZEPVvsTkdJrZzQ8AQMAT2QUDH73jo4SDEX8S0VMDcSm1tjWuErwSeV1VVyxhEGlgEC5LQixiYRnxqQXZXWFAE+/clcIpe5err62GaYgkE9JSbgSN1hijFPqWzx4bAGSOgHSM6phPU7D9kIrlxhuaJ2rHjOd6NKhM427oPPMbnSJf8S+6HtZW5Hwk8c0kTmcUiJCFjugGmehVafnNwqs6+pQxomN7ODYFMI8A2WQYN6pbWFvGl+Ah+FRvq6t342LjQohPUBoA/003scrZgCBgChoAhYAgYAjsRIJuVEngdyShIQ5Zjg2NlZaVrga+61pZWOY/YMuHBRFABYQW1i8Zhon0Ia0LUQrKQWQRoOYaBAixP/2c2/2zLTazmgDuhQkA5aD22s6XlJUeffmcVqmB2vampyVHjj+YuqTnHdr8FwTaF257VguYdBIxF2DC2jM3GXK94AHcedFnAc+XdaEJT4pMHQ1z+s2AInDUCIQ+v63JnXabz9H7i/7vf/c79+OOPR6p21to5tMbE76mDfERhxZ94r/uMkB7XJ9pBuHFCSSizKOckLfHnxFRRXoHFk3LsgqFs1wf9NnF6fWBHQ8AQMATSEIjHiXAQCna2zczOuGfPnrlrV6/B7EqTa2trkxwKEV9GJAx3PJIIZqAfIx2DOC51dnaBKC6CFtI72WQwIkwlHUgn45kktD+GQDYioM2U0/ouU/vs7Jz7+eefXXtHu7sIf3bcLMUgvi/Qxguh/c7+oH1CHuofzTs9Y7xHmchtOouUK79owHxogvCzz79wc/MLbmR0FKaTxiPtV83YjobAySLA8VsWDLGIwwWtJrTJi5cvu9aXL90iBMgUJlswBAwBQ8AQMAQMgZ0IKAuktCEpPfouJkPFTdmk63p6eoRvWsWmMAaJw4SgA2mSur29zV2/cd11dHSIMEMiJTGTy+iM6Rli0tNf2t/3IEDetgBCKzCtiOVp+XzmX7nZipsQWcciaHGyncWbEAWBU2o/0tj9h6FQjmY6V1aWpROUV5TDVGeJW13zdCZ9h8PhHsoKiyM45f1pbA5eomlP+W7RB+Yn5DXyZvbKZ0VP/Sfm/ch3ud63oyGQaQS0XcbjP8Z0C7mLgF+hyd3y53HJD9KxDhJnd4hSiKlwsonILE4ypaVl2NFf47jzhMI7C4aAIWAIHA0BHauiYzDmTE3Oup9+/Ml9990fXE9vD3wpRLvsMDuRvhCCFwOWJz4goIBmHYV23Pm2BfOYHKe6u7rd559+5r756mv38MED19LSwoQWDIHcQiBosxRCU4OU5m否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否0J2xhZqADdu3BC2b99hi7au37yuhZHaVURDXczjIyAcU6/98Xk9Ixzo001pYS1jDwxlrENHAERhncYamfFi5vfMa9Gs0xob3ZPAjl1269dLYCeVmLnAziJy4VuKFYEdCylHRkYkuLsTuiTgY6ND/V348sjMadFSXBgVBAoCzxMCRWD31N52c9PjLUGz+0ITOM/wtnwq8U52mxxMs9lMAuZn2JneZiMnnbER8g7GPGNcaEYKfUGgILACEKiFDrGOs9ojVXd53cFKz6++/jJs3TYYNg1sCrv27A6dQZ3fZKimqHMISjVl9Y/U/q3WLjzUtuDGuXZbt24Lf/M3/1Sd7R4NALrC+XPnbUC6Wir+WL3HTHCq5oxz3aXOO9V5yjwF5V4QWHwE6u8j492i+FF2uU6fPiMh2vFw7uw5Ta6sswGin7MQOdTtM6oy70/ft3B8XwwqUX90Q4elX7lyOdzSRM01ra7mXLwr167qO7mpQetoGL03qoH0mAnzEOQRFj8E33MZ7xPMRVP8nmMEUr1fT2jMxMKKvipovgt2mdpK/vQ9oKKoT+cqs5CslWnx2bQiK24FgYJAQaAgUBBYsQi0agut+a1/UhuLwO6mnWN3/sI57XLaHTZosTYjLdpgxl30Lzf294eh4eFw/OQJW9w1pbPGS3/v8YrP84afjeFTwaRssQgQgR3zi5Qxv9s4vYK2VUmuPBfNQpz3NO5h7MOZ3j06DggBXD62imO1tHtOMRPm5q2bplb2xs0bYVP7gB3nYe9VyTaZN5lOxucarG+bOsHRu6Zx2nKvEYi4x2fmpospCDyPCBSB3fP41ufIszeNUWDXaeowOceucYddnDCfg03xKggUBAoCrRHI+1vqtLthpw/CiM8++ywcOvRS+N73vm/1Tz65S4f3ga/8VFBCr1YHjvoKtZiTGgB0SH3vGtVZh48cllDiSrh48WL44svPw/nzF7SjSIPMOsoYtaen2d0TVu4FgWWEACpYvvn2m/Db3/5WK6JvSEA9aOctULbZQcdgEcH1pKmbmbAddWOjY4EdrKwGvSxB3fXr101wd0vn4iGwG+GS0A7BHCun4XNfg9FqoKRvxAebfGuVe4VL+YgqKIrlsRCwkkSdDhfdc4PaJIR1/Zo8ZDc1ZXVmWcxDFHtBoCBQECgIFAQKAq0QYJcQ/cHjx4+HAy8cCJt1Vt2GDRtEWg+IEKSgDvPAgQOmceHChQvWl2zFr7gVBOZCgC6dj8GjSswJ29GJkIwjMFatijvunEddCt1l8e/0MhnvjI2O2kUCu7VTjj5ms4CI7+V+ygD2m1r8eOHiBfuG1q/fYF1WGyORzMbu6+InvHAsCBQEnhsEisDuqb1qr8m9OfL74ybI+c6PD9Qewu8ekvNrENatWSOBnQ5hjUbTgax6TsQ+d47fYuUgRVRuBYGCwIpCINYQtj6N+qOpwmC1GteZM9+F06dOmbCNidnV6LbXgNGNTeVWlZWYmACB22oJ6xDesdPuvh2S/s//xb/Qgekbwn/+v/9z+E//6T9r9dyYVsxBs9pomOy11XAwb5Emj7PcCwJPAwGXV/gAlyI6qRWpX335pYRrd8J77/0u7JCaIs73GtVgc1RCb1RejmtXXFR1OR6Fb/oeJqUGc0ICDujYNTcq1S/QIswb091UHIk/50qiYhP1NBgGp3xPtPnEbztUJRBs+H7NQ35N3zTkxRQEKgTmWT4oTqxs5swQmzBJ5YvvoW9tr86x2xI2qW24rskSynkxBYGCQEGgIFAQKAgkBFq0tT5vYx215E/fjkVbR48eDS8eOqTFkoeqsZAvhqEt3rx5swnsvvji8/DpZ39pCbP3V/H0PmtLwuL4XCKQlwnG8ajpn5icsDE+Yw7GHs0CsicDVFyEiNBuXJpFmOPs6GizM/Xy9KDtARXtzFNg+HZuaYfdFWktuanFjwggMTFM/MDiN+QDJPOWv1HFh2oGNj2W2wwE8ncww7M4FASeEwRcCvOcZLdk0xFobD7ctb5TQXZ0dJrArmdNT2jTYbANxjuDYuRCO3dqoCsPBYGCQEHgYQjQg029eXYBnZLA7osvv7Cdclu3bQ3tPT2RA5UMdY79ZB1iWRHCaVxpO+2mpuMgYNOmTeGtt74XWBH6pYQc33z9TdpFlHWsiRpJQ2T3sJQW/4LAU0GAT6SNAq6CyiHnnDty8sTJsEGrOllQg7AOQRwqZhg4MqhkQBkNC22STRYTVHP3Qi8/BNmrJRhnNz2LdTrapRZJ7hOTEgDCE35NKgp9IFUriSGO8iFFpMvvoyMQJ25scQVFPhnKG+eL7Ny5M2zbts0EzUVg5+iUe0GgIFAQKAgUBBaAgDp5qEg/deqkFkueCXelNt2NL2ikHd6oXXdDQ3t0jt320KPjBmiLXaDn9OVeEJgvApQdxigchcjiwDaNYRAM+5hivnwel07DKhOgSRZnxrSLsCBRBu09lZE/459VLF7UHUP6USd76dIl+4amdGQAY6YYTN9HGgtlXCp2xVIQKAgUBBaCQBHYLQStRaddPhNbPnWXp4jt4L29azVpvia0d9RFJTY+3hQBijdHeehFB6swLAgUBFYEArGeiB1zr03qjLED6Otvvg6/e/e3Ghh2aaC4LvRKYEdH2DrDIrUOsZaL0iW2HT9VcLmp0y+xQ1UrMcH7s5/9UwkhusPf/d3f6fpluCaBB2peGCiws8hXzDkb76cTXzEFgaeJQFUGrfzH3W4czD45FXfF3b5124aFqISNB7bPnVq+OwR/qHxZuza273xQrHS1Q+D1PUxMjIfR22NhWipmbXddlYiaN6th7XwHPkE7F5KPpXwwNULF9lAEKC7efcyIqX8pp3kbYd6i37hxYxgeHg67du2s1LtmQYu1IFAQKAgUBAoCBYF5IIAGBRZ7XbhwMZzTed+3bt+J58amdvlBWvTVofkgzhYfkGrMLo3LagNh7PfRTaTtLqYg8FAEVE7Y0TalQsM52+yw07A99fkeGnrxCCivdRFWWiRInOLcb81xqjzbGEckNuNJMccxGcZGLJw0gR1nfEvQ5+Xf7pqjyGdKPdxyubvAPfazl0uqSjoKAgWBVgjUUphWvsVtCRGoK/3FjaSZ7zx7TwpGSL+owFlpv0a769ZI3VZHJrAjvQ9Mt0KKy6NIj4ubn8KtIFAQWFEIeD2hesM7ij4oJJ+cvcVKzz/96aOwa/eu8Nrrr5t6S8fAqxt7dl50i9WRfpBUqLHjiM70pIQQdJz37Nkdetf8rTrhk+Hrr7/QLrvbco8HXZMGLu+8ejzlXhB42ghkY0Nrm6NAToXevh3aYZXxKf8I/B6/q1VSJYvwWqXbdsFzZ/DZKSF1tw5T71u3NqzVeWDdmnxh5ehdqcgcHb0XgjQMIrjjOzM1s1INE1e+svpVu/y0255nBqIIuqcm4tl50xp0F1MQWDACdbFtDGoFUE7cM7NeK/337NFK/x07w1dff535FGtBoCBQECgIFAQKAgtFgIWL165fMzXTaFTo6upMLKQ/gY6fTJcWebEAct26ddYHjBocGhvwvM+aGJRbQaABgVicGLPHxX4IjSljcZzR1OFrCLn0D8wD8C2gqWRacwiModww5uHPvwfGY2gEujIyYqoxGQ/xNZAD8ha/jCw/lVsiqm+yFVMQKAgUBGZHoAjsZsfmufWhoaFBRQ1Cj3a29GqHXTx8NTZCNK7NhkasmIJAQaAgsBAEYq3RWHcwCLx7967OVDgWvv3mW1v5uW3rdqno40BqVUze/013BBFuak5yFW284iq5jf0bwjvvvGNCiX/41T+Ed3/3O52TN6LQUv/b3mHCB1bI5QbONc/cp9gLAk8egaosVhbS0PBQJWq1Vqx2S3XRek2usJNurc6jXY3aGb4hCegYkKLu8tLlW+GeBHUI6xioIryjvd+kldT9/ZvChg0bbZJmrc6yxb1nTbfUY06FU6dPha+++jpcPH/BVmTzHZV+QAV/sTwmAlX9ndXvuHF23d59e8PuPbtMZftjRlOCFwQKAgWBgkBB4LlGgHHXLWlruKzzuK5fv6G+38YZZ3ihlaSvry8MDGiXnYR3JqBoMR/0XANZMr9wBDSE8QWz9PGeuFH8PtYnHYyNULXOQmIWOFLuOb+Oi++ENDK+Ysxz985dnWF3Jdy4ftPGTwzH2M9gQki4PoXszBc/8uG4zzdMoSsIFASeDgJFYPdEcPcaO59Ya+W2GImZi+8c02mquO0sOut8QUdTFHVLd3V1a6KuyxqoZtVxEM3BdTEyVHgUBAoCKwiBWENZxdGi7oi+ZHdsbDycO38+HP3227B1cDAM6uLshAZD597+a2EBtSx1mbrU6mjD777tGEIQMTQ0FP7dv/v3EmKsD0ePHQtXr14zdtYhV2qgoQPrY1BqQTiUOs5gKj9PCAHKHKbqMcxwiP6z/TIQQ8VMj3bS9UvAsVnCN1QJrtbuuMmJyXBPAvErV66Y+qOrV0fCuL41yn2bduVxZu2gzo0cGh4Oe/fu05klO8Lglq0mKFmTFvAg3Pvwj3/QwHYy3JZKGL5V1NswoC2mILAYCFDzrmbHtNXAiaO+g26VwU2aMOR8UvqmtYk1tT/7J+PPpWQ6EuVeECgIFAQKAs8VAmoQrS1NzWSrMQ39usuXL0nF30WpTO/UTro+CSekTSE1noTv1cKtbYNb1SfcEi5cvBgFG07QAlBvh0v72wKc59Spubgg3GI3GyYKwzSeT2OJJS83ioBpBI+HXXPj4xMaE42ZcK6D87x1MW56IDWZqO9kJ6Cd+a2Ak5NaYCyh3R2d/Yigz2YL7ouh+CC4s/JPBOZT/0YXc36qP+BdTEGgIFAj4ELs5fZtFIFd/Y5WmI1K2Jugh2ctTnCnLpwFi60YE3i9WpmPWkxUYc0Q2D2cdaEoCBQECgKzI9BQTekhPbPb5+SJk+Hdd9+11Zw/+clPbMfv7IwYTsbAsSbTszqjDx6g0iKuimMn3cDA5vDGm2+Ffy/B3Qapd/niiy9NDcx97a4zwZ0GD6yiywcVkXNDQmdPRvEpCDwJBNJAix1zqK9m9xtnzXI+HQe4d3R02nfD89jYaLh6LZ5Hd0eDy1sSst24cSOMjY9LALIm7Ny5S2eC7aoEe9u3bw/bdO2QsG7T5s2hX8I+VCF1aeEOKjTv6ZzJTZsGwub+zboGwkcf/SmcPfudfW+khQ5vWX39JArBsxmHzxHkdWyekzhpgwpW9WOz+QSsuK3V2crs/lyzZk0erNgLAgWBgkBBoCBQEFggAvTZEDqcluaEkydP6Ozw9dpNt1Zc6nEVLDdKJfXBgwfCsWNHdfbdqIR7cbFXHl3WZOfOxV4QaIkAO9kYLyAE42xsxuHT+nOhXctAi+iYj+yZBxgfHwv3dEQA8wDsJO3WwrDpSVIkDTwi5lthMRl3/hB037mthYu6RyMi9VOt+8pPMQWBgkBB4DERKAK7RwSQihpjEwoNPPLKOW8GGoie0ANp8TRgp+NVG/fBJbfz7PnizkRgl1bqMxE3oZUnuUkwmJOHcWxyumIvCBQECgIgYHVNVXHkNVIjPnSWWe35ySefaJfP9vDaa6+FrVu3NhKlJ6+/qqGl8acrTac5DgDaENwpOonidP7R9vAf/sP/plWig+F//z//j/Dxxx9rdZwOvkb1hcL6WWGJg4Uz3lW6WyajOBYEFgUBL88Vs2YHlWMfDCKwo43uldpLzpztkkDNDnBXYZ+amtYuutsS1l01oR1CcFS9TE1O2dkMqMrct29feEPnRL799tth7/BwWLd+Xehb22fCOQarnbpslalUw8SVpdqFp52uhw6+GAYHBkOnhOBXr17Vbtiz9nF3iI7Bd1ncU729YpmBAPUyjqphrWzH56rvaOVbbpq8SYSRg4WJ1u4uqWvv6Q1tmjiZ1s5o+OXVc/MnE0OV34JAQaAgUBAoCDxnCKhBZDzDP2Oiar4mudH2IqS4qN115y+cDwcOHMAnmqrdfWBCPBZ37dR1/ORJCewuO1XDnSAe3u8NBOXhuUWgKnsqc5Q7G3NrzKATshmx2ziD8T9j9aU2MQb7Miwq0uJn2GFnkS9jGiWrMlDbXzq2m6M0xibGTYAd8xO/L7qvhCMOseKXn2KWMQK8P4yX0WWc1JK0JUJgub77IrBbohc+P7bLtfL2dMVGBzVYHDLMaua4TVxNVarU6nxmrVntWGwFgYJAQWAeCFB/eL0Tyaljbmon0FdffaUdQDvCX/3VX4fdu/fYDh/8ZmtUjVNVHcGTekyr4bSNGLV9rKBjh8ahFw+Z+r+RayO2su/rr79Oai2mGtJLx5xBRDEFgWWJAGVbCXsgoQVqZaYlpOMPFS6j2gl3R+ovUXmJAK1DO/D6+iSQW7c27N61O+zYsTO8+uqr4c033zSB+KZN/bN+V3neO8WHq7e3J/z1T38Srt24qvPwJsKJ48d1DsqteJaDAvCNzuwr5JyK/XlEIArrZs859e1qLZ5gp6iduViRxnqYctW7tjdsGdwS1mvFP+3E/QeN549WQYqlIFAQKAgUBAoCBYGIQBwWNaBBP+2WdgkdVx/u6LdHwytHXrEdRhAx3UObzVhoo/qILx4+HI6dOBE+/eyzh/YXiaqYgsBcCCCcm5qaCkxI07ezHXZSPblqVaOmm7l4LJYfabFxk3absmDRLxYrVsa+BxYAx9JNmLt374RrWhh5995o6OrQQsdO5aZMG1SQFUtBoCDweAgUgd0j4jfbZHHzpHNk36rL0sotT0xe0z+MNg83mz3nkfOO9NYZk8Jl7raqReRMlJBPVpf0Sm0WajFvt99Oepo9XM2rTMzNhn1xLwgUBFojkOolKh56t4wMMXpmcIhhtdvlyyNS1XJaqz5fsLPsOqVTPjdeC9nAUh5M+CKggwXn0lGP8Uenu0PERIeAo3/TxvBv/pd/EzYPbAn/13/8j+GDDz+0c7kQ8MEFGkyp2wyGp/JjRSPFXBcPf+P2csw3lZynksanEal9H2qzURszMTGlMnrXVFXKYhMt7KJDSMdgkgK/VuqNBlTODx85IpVGL4SXdedsyK2D2yT4GJQgry9+GAvIDO9ms1Rm/u3f/m1ol3Dl57/4efjwwz9YnOzy47tj9SnfT/wCV6l/kZalLiCeQrqyEPDvuM6VSnP2AVNuKD/tEtphb2X8LLsBnc+IsBh1Xq2Mh87YtyIrbgWBgkBBoCBQEFjxCMS+48xsjo2OhSuXr9guuzsSQNQqCdV6pga6R/NAnCfOxSJub58ZW9HPs0usS3s7E9/iEhGw8peBwRjFL8oT4+9Yrui9LX1JyvuelN9x2y03pmOAdMSAHS8QtYt4kkkb6jv1E6amdbadwozeGws3b9wMt7VgcZU2OHR2NU2v55E4oxV6Bw83Xj/482z3RwkzG6/HcZ9veh8njhL2ySOwXMrX4+S8qUZ5HFYl7LOOAPMiWT1bZadThw+vlz7zjTrHhnNv4sGqlXexFAQKAgWBxUEgq4R8sIjQ4YJUtLz//j/ajp533nnHVGSioreVoasIGwQEEl1EEio2OfpAACHe1PSUHaCOAKN/0yZbHXfz9s3w9dffSIf9uK34s8BZmlrFV9wKAk8FARVpBr6oA5yeHteOunqQxArVbqmx3qDdRz1rem2H/O49Q9qhuju8/f3vS2h3OByU8JsJF864c8NnwnnpGG7Jas/NP/4NrZWg79VXXtOCnl6pR7qkFdon9L1esLANneQ5uTVzL8/PAwKt+pvkm0Ez9TuTJV5nN+OxXpMi+/fvCy9IdRdqXu9qJ2le3prpy3NBoCBQECgIFAQKAjMRoO1klxNzPOfOngsjI1cluJgwjSZa9WgjKdpr+pZoXOJ4AhZ5lQnumVgWl4UhgLAuqunXBoGkgpKy+DQMaaE/eU/9SVT/d/d0a7dcly32zdODgJqyz8W3c+vWzXD+/Plw5crleERBby3MfgIyxzxpxV4QKAisQARaz3iuwIwuvywxFVZPsM1M31x+Po02F437OW1jDPj6KhcmtjEWQlaevRPGnYmT3t5eu9ht5w2UBSJc06yL8/P8eUqcvtwLAgWBgkADAqkOoW5Z5Ss1EwH1y8jISPjooz+G9Tpfa//+/WHbtm3m63WP11cNPNOD14AIAI3OOtmrNfBUhzvVfevXrdcuoX+us7vWhf/yX/9L+KPiunnzpp2hxIQxh2J7XLAVi8o0VX+Ve7EsFAEHNbYY/m5iw9TYiuTvYqGxrAj6Cg4s1YOV73a10Ru0uGbP0FDYu3c47B3aGwY3b5GQe0dYp4U3O3bsMFWCrJRuUPMiTlaus28RrMCay7+x6i4/hH0cvo5b/8Cm8JrOwjt56nT44x//YINXX9zDm/X+BjyLeb4RqEtsaxwoT6xgtvJlB4E00uGPWuNdUuu6R2qST5081UiQPbG3M5qHxZoFKtaCQEGgIFAQKAg8NwjEXhoLJFFrfkOLF++Nj4W+B2ttp52dJ5uaUNpfNC6t03ipfXV7mNSf9xOb4fJ+fOn/NSNTnh0BE9hpAW37fS3QUn+PcUnz2MRpl/yusc6UjhNg7EK6OjT/yVEClPnaqFSnPipulH3Of+QsbwTeWzYPVqSz9Trd3bk2P1cMnkFLI1bzy8CjhJkf50JVEIiLQJcah3yeZCniKgK7pUB13jxjB2ne5DMIHzd8ZEhD4Y0GLqYKM3NhdQm76/r7+23Vfgz1ZD4Aj6vcCwIFgZWPQN1pq7uv6gtL3dldO8uur683fP8Hb4f9L7wQNqxfb4DUYWbiUw0WYZJEBrjx17aqTR3t+6Zys72jLbz+xuu2A2n6/pQ662Phk7/8JUxqlR9ngU0vTlU7M4HFZXYEhHn9/pzMy4U/P493oaLBIgJnFtMgoLPdSFIh2KtVnZu3DIa92nl05OWXw4svvhgOH3pRKl83h3VSiWlGuD6QKk3OumNAimCkWi0aP5MKVCZZ8okY+9ag4d1IUNeWDWLXaCff977/tujxC+FXv/qVDWCdWZmwcSTK/WEIxPKtM+zaUYlZ7wDNw7F4Y9++veEFtQWffvpp7lXsBYGCQEGgIFAQKAjMC4E4JvL+HrvVL1++LI0JF3UcSk9Y09Vt46WgM8XUtTPDIu6dO3aG7dt2hLPnz0ot9fi8YipEBYEKAQqTxguMMRAUoz6fvp+NR1os1KrCLZGFcQvfAON+tOxw3n27hHVcNvbJ4mWcY9MKuMnOAt+zZ78zLSN7dg9llAuzilX1jS0sZKEuCBQEnhYC1GFLbYrAbqkRXnL+qcWbNZ7GQsRE3QwjEqPSD922aK9X1Hd2SiWmJscR2nEAa3PD5XN2T6C8zkh6cSgIFARWDgLNjZ7XNVGwdl+q9i6Fo0ePhRcPngkdEkr0SO2f00lxfp8AAEAASURBVICC10UVInKw+qxyiHVdrAX5dWFFJFinSeCf/vRntpu4///5RfjTn/8UvjtzNgsdrc08ZxA8ow6OZfN7eCLZ8aYpgWtvx9z0w3tUA5OOJVRylsMb8ASDTkxPJWD09Hn6F7FxpIwjrOuTWiLOj9u1a5epJ9q4sT9skmpXri1btoQdO3dqJ93WsHXLZqm+7LHzwPw9stuUFHPBzy73TPfZBGw58p5fgvR0d4XdSsuEBrrHjx8LX375lQ1iGYjPVp48/GxxNSWpPK4UBObxPbDSmt3WtrI/5du/OMrLGgmnUcu1ffs2qeZKwugKHyjzklp5FEtBoCBQECgIFAQKAkKAvp+ZrE3mDK+RK1fChfPnwub+TaF7oIuV3A1N6hoEdrt3heF9w+He2N1w6fKlhn6e9+2s004EpTmOOJfflmWBMYKPE1xrx9OAis/AF/KSHs7m5vKxsacpptefgs6uu21HAVzRd+OaRdw3Fv3mPql/eJFq5kyFhy73gsDzgYB//+S2+Xt7XASc92Lzfdx0LSR8EdgtBK1lQ+s9H6/wmxsCEup+Tjsz8U7hPnSw4sRZ/OWZwo3AjknAgYGB0KPVVrmJnT0mU3GNccXQOVWxFwQKAgWBhyPgjapT5nUJdczt23fCp5/8JWzeNGD10vDwkC0icPrmu9d+3kjz7G7Ukau0y66tjfpLOvSnogoMzvcaEl8mhKG+dfNWGJVOe3baVemrmTRH+cw+O0ZPLwN否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否d3/q2aj5lxcNYQE69btAuCs0I4WNVNqw8kd3O6ci8IFAQKAg9FIKuqvPNehclU/rFYAEEOnehdOsdu+/adtnMLWuqflh020Zrx6i8+pS5xmlhWlxrvGHdc6Uc3e/qBlOvrzllkQ8PDOoOs33aHsVPqtnYPjUp9xqQOqSaG+wr8JOpAy47niXQ35as5DTkm5pdhnaCwG3TRPxLwDM4MXkyAyV36+1ldyIQ5mAxIKLVJbcQOqajbsX2HnS+4S6tvd0pdDgI8hHG0I75y0u8xrjhAI87zUn/5m9/8Nvz8v/3X8Mknf7GzAkdHx7TjcbXOumvXOQdSnZJWYQJ23KgeM+6CqjwvD7M7JsbBAISpQkWH1sHx10Ue2qX60fCRHQHu3v37w1/9+MfhjTffkBrVIyastN2HaceQ5VfMOSPR2AhX3PgzfJFT5Ca9U7Cxd2KhSJ48UnoVtUzkExOvxxQOn5iV6GBxKb7KNNPJI/eu6JIlCwmlpYO0mxEv+GPu63vhTApWmvZr99Phw0fC66+9Ho4fPx5OnDgVz4eY4Jt6dk2V76Ys2LtpcvPHCm57dyAIXhmqiaCiwzfzdj6Os9/dfSH3FmzrlChSS0NKJ3w9rlgOU0wiqvJLADGFr6ff74m6qqNqHlDXSFKnxO+F76r+GCK9fwPir3Sxo5U0sXMXgT/nLRdTECgIFAQKAgWBgsD8EGhuowk1qR12Fy9dVH/tWHj5yBEtskp9NTXXse3V2czq29G/P3DghfDZZ5+2XoQ2vyQUqoJASwTo39V9xZYki+5IfHaOvb6BDi1ERWNMR4f6o+qbuqFbDB1n2vkOVeZFL126rOuSjX9n9tv9S4t8am61zfmXe0GgIFAQcASemZGtTxJ4wp9GBe5xL5/7PCp42gbIvI14jMR3SvXQ4ODWsGv3blORMHJ1JKq1auL5pBvWpujLY0GgIPCsI+D1llVcs9VzUh8o4d1VqcVkYPn111+H19940waOcZK3FthZ+0HvWmbWqrC5Zw2hEWsiOSUhCobiMzs7BqTWkR1VqySIWbd+Q3hXO/0+0q6wixI4RbUeFuWS/Vjy0k9DG+kJtpiNwPJST4nrMeHRDIgNQNxP4eFrKi27u7Qzbp12WfeZYI47Qrg1a9fYSkLO+Nsg1Y/rOZ9Ou+k4m46dZOxCRCWkHcDdkK6ZsNy9d9cWg7z33nvhV7/6VfjDH/6onXYXKywZLNmrUPoYN1XCOV7MDP16M/k/zAXusAExe9cZDjFieSQ4uSNUQBCFSqChoaGwbetgGB4elmrPrdph90LYs2dP2CpVQgjhmk3cWVbzs5xl+OTvIb5bcs7lCeApptfC8qTwziJS5rSExDXnEDk2cjWS+f2kgJaOLF0eGPd6d+oq6z/8zc9+Zupj/+f//P/Cn/785zApYlMro4RzhkVVLp3JE7g3fDuKz9LgQFr8CUcrYzjE57jathHT+GSBKjp7UpD6bbh/vDcIuxq9qqe8KLpjM1bN+XC6Oe8544Y8zxmq0bMFj9nySsDGdAsx/i1u0Iv1tk+QtEvFLn6EoU51jQ7wcazxZ+cz9ROLB0K4gfecZq70zRmweBYECgIFgYJAQWClIJA3hpl9WgK6Ee0UOqXFVQgh2G1kRm0x7THdIRbgbdF5zHt1PjOLuWm3iykILBYCdAuta7hYDOfNZ5XNb47rTLquzu7QofnP+52czZ1rRVGfdFp90lWcWx8Z35MWEdTIct3TeHbexjuzMwLM6jGDsjjEcUTj+KKg8rwh4OPFOKZcOblf9i3rXIDP5ffsv6JWlbT3pFr5zZZjhbFgMUw13yRyXxHSHDI2jh6H36Oucs4d2q7dE6xmzich7V3QgROzUlk2I1qeCwIFgQUjQGVC9ZMqKib/MV6/UE+5/e7de6aK5ei334R9GjgiOGI3V26oo3zXm7HVc+QYfytaxVvVjbEyTBPtCB/aZEfJX1IcI1p217185OWwZ/eQraibnJ4Mt9+9Fe7o/DXOWfI0wp8sPbbJkyuGxpO76l/PFyOcGFf8JU7PbZ6ePC3g0+rq0FlsqLFEOEfdTxuwd++wzmAbDEMSSG3s32hnR3VJxSPn1HVoxa2dX9cW+SHsQ7AFFkFyK2srUsQMdnBv0049FPGg9x8VmP/9//3v4S+f/sWEsUzQx0kAVjwKef0bj+r9xdzF1kcI+Mgpz9xD7DGsiOx9Rx4NOMkJwZMBTDYkgONCtecLBw+EN996K/z4Rz8Ohw6+oNXGO62cQE+6USsU2bJ7jjIELvUZb6kQVimMyUc4EcsO71SBIg/eYtpxBE/erlwsrL07s9U/Oda4QkkY8sa3EEOSbecS/aCtYLTE4yJDYJncyZnUHCJNpJMaGSOOwvXevt7w+utvymu1hNqXwlcSso+Po+Y0/iX2NYMltM3AJmXKviPPoN+rdDhilYOB2irdEY+cPuJttAncVuEyzgu2NpTZBYdWgOqlP0rgFKaZR/OzkeU5l51/o4vuvJs2W9EcVzbHkqtPX3UBdFFQKkai8/e4WvVMnxYRbJQAfeTqVZtsQQDsJo/R3eZ9Jx4n9vwkB39077zIzMfP+T5W+jzyci8IFAQKAgWBgsA8EWjVZ6jaJPVB0WwxMjISbly/bhoRoLdLbTGNIv3gNmm9YHFen8YJRWA3T+AL2awIUL58cZb3oehXuX3WgIvswQ67CWnMYdzJODZqh/GvI0bGwks2nqaeaxjXsQ3XtIiYa0x2N/gT0nqSjSycJLt7b9AJ/TkjKdZZEfAxwawExWPFI7ASy8CyF9it+FK1TDLY2CwwCUKnrE4chZ8dEkzYDg0N2a6JXGBHa2UfiAJlwWoGxVYQKAgUBBYbgVRJ0bE+duxY+Pu//3s7Q+37Oi9r/fp1FhtCIduZpTqMOqrVADVPlkga6r7cDzsd7ljLxYnjNqnLQFCFgPDHP/5RGJ8Ytd1l7B46e+a7cOf2nYpF3ol4WDqqQAuxkHgMuGClMk4V8qw1s8IwOc45cnbGnHap9K7tVX7W2QB8o3Tw2045Dcg3bdksmo1h+7bttmtuUCtq+0RL2zBfQ3Lu632hRsQG9xLW3ZE60YuXLoRf//rX4Te//k34/e9/r3NSL2sg5OoSUyYsO9FuA58UqWOZt1nzTY/RGV5zlA1FycCMfCKw3LlzZxjUbrpdu3eGg4de0m66A+HQgQMSYm7W2Xz1GYoudIu76aLwwHaTKVL8eDlWJrxcVhlI7/EhmYhU+e9DAszqTVq8wMxKZB7zS1nOIwrrUI2JYJaD2w9IsPnDd94Jp8+cth2pN67fDBOTEylQHkN81zm3xbJ7mXF+zc/mXr2Pisot0ZvfJpqlS3FD1PEhfe85YjOovE5wj5TeKp0zLU65ePcmjJoZ31fZUKVgznwPXG3aWcdFPzNmAffV+mIktJNDFHynfqdCogpzi86KZDHBzZs3TI3uZFWmmmNc4DPpJxEPyccCuRbygkBBoCBQECgILB8E6A+oqaMNZszAgrpbt27bYrprWgizRxqW6G9w0YX1eaC1SePGBmkauazdeLOON5ZPTktKljEC9Md9gVYsbZS4qrO69ClX/AjqJiemTK1/FExHbQ8euX0jShJp9T7q6CgqMbXDThfqMdPnZEk3O9mYl5k34by4FaKCQEHg2UagCOyWzfvzynmxGyTnGzNa7RzJ8t1I4R5Mjrg93u0MO+2mGJSKr7VSf+YdtdiGxgaLBoydEpgytxFxK78FgYLA4iFAHYPJJ9hZ/ebnaqKWBbW9+3WOmNVHEvqsfqBJX9EQlB0/1FB+wcs4ZlWvx4EfxuOKe2TY8BR325mfONnOPQmgUP/4r/7Vvw67du0Omwf+W/i7//k/wonjJ8K0dlkhsHC+zs+Y66ehqq3y575ZwmKE7jHj3sA3C+bxNt/ZlUK9vlY7prdt22o75hiQb5VAjmdUINvKWQnzUAHa1dVtwj0EV+jz79JuOtqBqal4Fhug2kBdGYrRqx1hBa7y5HHTMKBqh4uVueyCOXPmTPjtb34dfvGLX4Rvvz0armpiYBKeaJIUL9tZJ2vOu85rymiW3xwYxWyPPoHAs6fF3YxX3mARRJfTEYZ8bpCaz1dfezX88Ic/DK+++krYLaz6N26SWtQu7bTsCe0dq3XO4VTMvLazE9521QXUqERVKrgRHwJL7uCD8cEp8fiVQFRCyBxXTBhFhMuMnM0Xnslpxq3yoJ0mXexxIx2JBZbUOfA8z+CBg8fZ0nN2R3jGnXaRBrxeffXVcPv2beW1Lbz//vv2zonA0pdYkbyYRP1Gy+yRPNQn5vthZHPmPwWeD42DVb0nwmYPBqWeZ0CKW9qB6fFUwbDID/cosBKtq4k19olbCuDhU7IjhHrXvtO4+oYMaMoP5VKXvkloYr0G9hI2p8JiryH9xJv9WhTNzx6v36v43KHpzjdgeVO9hDCfi+8HQ5b4LsgENHbZztPoD+2WLQNhaGhPOHv2O61wVh1iGrygJfl1OpuindfjjPCzsGsVTUyholGY5mDNz/NKTCEqCBQECgIFgYLAYyLgfYS8favasNRwTasvfvPmrXD5ymXdb4ZeLUrrUh+Oppm2FUP7u1HCOs5wvnDhfLgnwUV15l0ksV+n56GKJ/Mv1oIAZdH7gtgpM7EP9+SwoS+MYbElY3f1iPVU99asn6r+qH8/fmeR2C19I+xInZD2ECvkSjx8YtlXZmo2jHgsHo+PB3drIDSq8rMYCOR1nb+3xeBbeCw+AuVd1ZgWgV2NxTKxUXlntfmCUhUr/gUFMWKFy4POkgQmxRHUcT7PenXM2jXZiokNTc7AnCPPR81KYlFuBYGCQEFgLgSol7hGx8bC6MWL4eNPPgmHdED6Sy8dNtW9qGNcJfWMucmruEafnGp2O9VaQzh1xqkHEWR1SzXk66+9LnUYY6FNQq5PPv44nD93Vtf5cPfu3TAl4V2zmcGvmeAxnpnkRthGuthFZ3fV42t614Q+CeqiGrl+2zW3WzvHdu3cFQY16N68eSD0a3ddt/I0V6fWBE1M8icBlE32K72EMSEpaY8jlSiUEi07Z/DnzLrLI1fCb9/9bfjl3/1deO+9fxRG92xwA8B2wLfJClJDYqA3IB+RmVc7QzgRpuB5kJyjuUOm2QgElOu0U5Mz+DiX78UXXwxvfe974Y033gj79kn1qtpByh7m/gMJITWwM0GjhHWoxMSvJXYahNIRtcEoESoBlgbudiUhg3Ge30+en4eFsLggwpLif1iYx/G3+BC2yPh3cvDgQZXLrnDzxs1w+vSZcOPGDQlmOQsCisczjZiDZRS4uiA03iVE1PvBbu8p3c3P3eUWaaGLPNpwS/62WxL3ij9xMYiPKk8RxhJ+lXYWRr7x0HpR2XO781d24y5gYSR+Fod48keBMPy4Y7F74o9d9JU/sBkRN3PFpTLgCrpe9kw4h5t51II8faj655m76jZ7jgJmhOe48a5sIkN3dsjdnxa9uU3FO+8SOgsbJ2CIF1p4TE+LLgkHSSBunPnBffPAgM7JHLD6m7rL8wLOHJxjfBSmDR3vKZt2lo4Wa7DD7ujRb8OJEydMhZfl+HELFAkspiBQECgIFAQKAisRAWtbY0NpPQra1dRusiDvioR1x0+ctPHBHo0TuiS0s6WPdB5ESx9qcOvWcPDAwTAi1fanTp8Kd6YWcIbXSsS05OmREWgU2DF2iII7+n5P1BCfXa1ipS9Kvzj2t9EgMikVmqPaWYfAbmxstA6U2OhUjWIKAgWBgsCCESgCuwVDtlgBmhsdf6aXlGYgZkQ1mzuE7pfzyRm4e04bwzDnMcPIzSZ1NKFCP85XObMangnedbqYSHFjU2weRZooWs2KfeMT2zunLfeCQEGgIDAvBLxOEbHVMapbrLpKdQw9ZZtQpsecDOcX/eUvn4QDB/aHlw69GHZu3xknfEVCneaTv8ZHYfxeW4grc5cd9uamOs3o0zOVo/0hFIRInsTRo8HsG2+8qR13A+FlCQ4/++zT8Pv3fx+++fqbcO3qNUsp6cgHH/A3A59HMSkj3B6oUmcin4n/zu4um/xGXeMenTm3efOWsFN3ds+h3rFPwrtuHajNWXy9Pbok1OPiTDp20WHydJqD/cQIEUrY69A9vgs9g5Ycid/8Ew+ElUzid/V0W5hvPv8m/PY3vwm//OUvbWfdXQ10bCWi0SteCQEw1TuziMxJnjFdD0PLyk1kIkZxh05sr6IPvE2oIhrbUWSMtWJYC1IQGrz00kvh9Tdet7L0wgsvJEHChtC7prdKV2QvwY8Jb0hRLKeebvADm0iX4Qal4ufvQRIq27Pc4EIKCWvn51nolFvdkq26mzf89IeJuTOrYY0NQdIMY+QxzAy/zCGmJzpU1CkR+WvxIF5mKM74RyyEgwSbq5XX9o6usE0TPIcOvhiOHH7ZBHes4PZBrmPn/BrvymWViNruYbjTV2ESya60W4vzFRHsdEiNLSoUEUZ3IMxWecS9q7vH7rh38qzvgt2AHDhPfwe3TnjwLHu71Hu2S/iMmk++FVaZs0AAXqtNpaN2iCktJoDjW1B6SBtJNwEe6dRFZjw75u/P+CUTbdDy33x3okjVGCaWFX9/8ZWpHpPF3NJdBc3SYO8NN8qsrmkmJBC6yY4bdu6suqdMx3sUwjGxN6VtbZO6OPsDNacT4xO2SAE7wjsWMnDOx9joqAntEPzBm9X4N2/dDPe0qAGh227tUt6k8+g6swUDEQ7SgRR/ldJGTnnXwljYxrM2Vaf1ccZyPTMCppYvB+Yhd+jhq2TJ2E9V3io+ojG0I9GM7zCPInLIXaI9f4/592q+iS926lCP1++RQ/ktCBQECgIFgYLAoyFg7UlsWNXQNPZDrE1S40Uf4LrUTJ/57rRU/V0M2zSGMCN6wtMOtqlPN6Bzrvft3RdOnDwZzl+4ELT6rkWi8j7KbC1ji2DF6blCgHJlZSuVTfrL9BWfmCFe79tpHhR7fKT8xjLM9+H9YbTFML7yEn1P/dtx9Yfd2JicYCKwYVjTxGvkWFG7pdyXAIHYv18CxoXloiNQ3lUNaRHY1VgUWzMC3vLQKKmliivHgyaju8MmdcxQ/zZydaRqRJ28seFpZlqeCwIFgYLAoyJALRNrmHrCOdZPzpFJ/w8+eF+Tvf1SQtgWtgwMaiK+U5PCUd2a081WT3k9VseUonQPPboVIR3nKcHLJ4WnpiYZ99p5ZqRhz55dYc/QbhPidXd2hy+//Mp2fjCBzWo8dpTUHGVdoCFeJqsRQiBM6JKArlPCkB6paFzPzjDV1bt27ghbBznfaa+pu9wtgd0WDbo3aoddhwQLs5k4Yc+gRDnWxR95I8442Rwny5OjbgjEIi00DLIwJqyS82qtQNR0v03ms2L397//x/CLn//CVCIyyU98iPsIh513Bu8YX3MqeQtcCzPkIY28qoDWvilOBC7g1iPB5bbBbeGlwy/Zbrq3dSbioUMHrd0jEOcikjZ2GMHPdnGCiIFTs+fRB57Akqe3zpMhKT+A5caPjAWIkyFmBweczbP1TwppnlBbXnnKPVoHXRLXmOfIOuZEKVIGPDns8jz04qHws5/9zIRcn3/xWbh48YIJKBCCGUaOB2zAwATR+rIlkDEBmco+ZzC2q2xFAZ3sJkxrN6FcZZcbwjmEaZ32rXTYN4kgDmE17t0StJsAT8JqzqVEcGfqX/mmeJbwjnsHAj3d4Y3gD+Ed31G70mKCuOqunXAkmxyT9vQOIyLPxq8XWy9LvD/srjqTO9+CCfUQ4OnbQPg2bsK68TAutUCTE1GAx/cyqomMsVHthpbKLIR9tlNPTAlz6/atcE/nWSIoP3TwoPUxwbYuMY5ZozANV8oCfVIWJQwNDZlKX8EtdwlPVWaogzgTM+4CTDv8lFaLn92AWWG1MkoD02RymiavJXu0OmXJuBfGBYGCQEGgIPBcIxAbPIOANjOauv2jbb+qXXNHv/1WZzUfDAd1ZjMm9jEjNQtk2GF3SFoojh4/FujLFVMQeBQEfMxEn41da3Ec8CicHjFM/REYA9JjC8vU163HJIxl4jiQQRmfTd5Xm5gYj2phlYdOjS1r//q7esTUlWAFgYLAc4hAEdg99Zcee0e2YlhpqduJqtckV6/g3a35mUy0cpvpns1JKK4Uxld6iL3HQMgsMdVkhk2AaCX5BqlK27Jls1ZRnQ93NMHSvPIFujwu41d+CgIFgYJAQy0T6yDv6M41IVrRgKBVLl7nNUJ6W2ctHBs9Gv48+OcwtHs4vCb1lB2dG60OcwFSHqIhzlQBez1Y12FMtscqET8T1GVMPCXcERp4OOJDbeKL2um3trcvvPrKayac+ujPH4XPP/883JSQyutKm9RXeJ8Yz9jPaUUI1i0hEyost+nsuV27doYd23foeVB19Jak8nKj7ZrbqHp77dq+0LduXeiR0IHBEPl3bJsjYvcPfg9oK7y9cHASMeEjhgkFSD0cfjaBr/Po5MbOpAlN2qOy7sMPPwy/+YffSM3OiTAuwaUbuCCs8/diUeOZxWt+IkwxWlDzVhxuKhydSDyrF+NEulv6dUfYslET/gdeeCEcPnw4vPzyy2H/vn1hQDhynl9v79oqFJivUrtJWMNHPnXMkaelLrWtvFsVBdFDlfC2tMKDHUuRgZUBy3uMCt4wFgmBbWWm7ciKj3LyzEEWaR0CY1nFH/nl9NEl/lo8uYPsTpv7kXqM32tLdM9/CUdaYhJ5n2SCsFGIi51vZdv2reGH7/xAOG8Kr73+ShgZGWH7mflZniDEiBc7De3cRQlheF8IzVCvGgV32t0mN9ztLgEOgjz8fYcb7naJP+8QYRsrYxHW8S0ggGPiCX4mADT/KBDMea9SGBfMtZsAUfwsvxUyVdmsXWI2nrVfS3/9Y++U3W3xSEYJ+z2DdVG0ssNuOheGxckOVGQ+0O47LVTQTlsE9JQxu4QWwjQEe1PyR23vwKYBE6TyPqxMJ1orV5Qi4e5lzIqHiFDZPjw8HH7w9g9MQHdRuwHaOtqkplYCRAkJUUl8T7t47969E+6q38rz7dt3bNcfOwM9PSq1sje+qeZnCHKSmJYIRuU+I1DNk9DGQcSNnGqaypb41N9k5UPgaAwkvjd3yGiKtSBQECgIFAQKArMhQLtB85Xac/qq3pawyAWV5We/+y5cuXTZFtwZG9qc1N7QH9qoM5537tpuZ8nSp2plnGcrv+JWEHAEYrGKfRnr2qRy6f5Lem/qQyE45KgD+lpoO2CsQP/WynLqbsVbelDi0DbB4jPUvHf0rZPLfRtbxM6sHsmPyPPvIXbh8ozW/ES97Iynnb5vMQWBgsDSIlAEdkuL7xJzpzJfjIoy8WnJzvnXDQeTWUwOs0vj7PlzYWx8rEFgl9fdTe3eEuNR2BcECgIrEgGqoaoqUl3UXFd59aTKh841KteOHT0aPpFqTFQasjuqv3+jTdYbqSom72TCFreKRQsAvU7zJLQgieFFuEpK6l0kQTiECpsHNtt15PCRMDQ8FAYGBzSR3B7OnDodrktFJue2TWtnHoOAKiFZJAwSEEREwUISKCBU0NW3rk8D5MEwJDVy+/btNYETqhu3bd0m1cXaQaeBM2r7AJD0xHzHwbgvtJCPuZuqDuJNYEDrV5DKm9yAM8Z22iiATzqzyy7mXztv2H3DLjRoxYsw16Tb//e/f9921iG0u33ntvHxtDEIQGA3w8jJ4/CBQkVDYAzpjbaGgVByaryBRRLaoCpxUDsOX9DK4Xd++E5450fvhFdePqIJCAS9ohP+nI否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否o2CyB6Ebi0bsrnN9prSYgXNLxyWgu3lLqjYlwLukcz2ZEL14/mK4pB1558+fCxcvXjRh36gEeLXQLkXmcarush3FPJMgm+CKntV3YqjH3LhbpCi/BYGCQEGgIPA8IuBtYGMbSdvojavs8qRZof2+dfuWFuGdkSDuu3BHKp6tTc+AI9wGjbuGh4fCLp2zvQaVmcnU7X5sNb3t9LiaeXm4cn9+EaBMeLloLqNPChV22Jk2BSWAskpfyxZH5glQYaY8ez9s+v60nW2MWsx7WryVG74m+MyhICgnXxF2f4f+ra+ITJVMFASeIAJFYPcEwX52oqLZSZ01rFy5SR05Vpls1uT4sM4C+u6778LlkSt2ZoeT5sHglj87TbkXBAoCKwmBOFFKx5pqAtlDbWKdktcFVie06IXjTmcWYVB7R7vtQOvSmW/dmgBFCNPR2SUBndxl79TB50yM9vX1hXW9a8OWLZtNYLdVgpr1Oq8OlYe9UtuyTv6s/GyTWkmbz0yTmqbasE5kbbNE1I/ztlmmRK27sYjZtuDu5dWr87QdYfJsa1stgV1nqn5N3KfVhRLMWFqZaOZspogxYefT+WWQ3DhBm7+BmIJIkxKapdfeYZVoT226i874yp/0xTw8kMrONlMvierLD95/P/z6178OX3/1tdSCaHCvoO0a7CBqmpZKOTdE6dE2ptUp4p10mkllBtqcnkEBAqg1vb22mGS/zll98cWXwg9+8MPwllRg7t2318rQlOI2FiK28MqoiQDlmFhn8ZBqMIxp9PIC9nEQgo/46KbX02jwgmf6c+YIBFCth9iuIT6R+/tiQIhQVi/ccl3j0xhFqyejVfjcxFTmLtHeSDXTP3epeDQlekY5zP2b0pHzK/YFIFCB/5Aw0C3kpT6E3Xy95x1lnr6UJ89a7jXfeOdDRxH0IunpnFks3efhHEkn/HwSghA8ey3Bt7va2q32sIad3vjrj//cEJ5rQgI9dtqNaOfdDanKvHD+goR3l8IFqUi+JIHdFQnyULFJHcrOuwkEfVIzZjvwFI6d49Na0U0djMnT9VQKQ57JYi8IFAQKAgWBZxKB2HbFXzLArvDT2jnHhVrnqq2JDaDt8F4ndf5Dw8Nh9+491hd/JjNeEl0QSAiY2nP1r+hjMdYxoVxTB9J6XhoHMz5nbpQFWZxnjPpzzj22/p/4pS4aNv2lnqECzxhDFfQLAgWBgkCGQBHYZWAsXyuV+qOahYZVE2ItTxz4x8F+1dRU8w1MKtIoMQE+rI7Z0WNHwy01Skwi2CRERal0i6Fzqy2Pmp8SriBQEHgSCDR0IL2XmTqpsYqovmolJ9YzkEXhEpbGVPLIuWCJMno630bS+ETHWDsV1uj8否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否m0LexuS39gngpJ0rMtLINONhSGFxoaoaZVpTU9MyPTUDNaGz8KO0aWQzAfln31FbkWTIkZlGtZYoPyVQ59DfiH8eTD+qLaWtBTIHhyndB/uD7Fvat4GjUONDug59lww72ijZAkZ7W1srGKZ7VUp2164uvcTDOKQ/607pS0SR0/rkhZ8VOladHzuqyhy/paBO4/HjWPvw/Z7NO0tiJXg2KawtrItRfaU5XT79WL9ek9SE09+Wm8TftTp+q+rXCMCNFECYyoaNbUlr9eZUZIDh7At0ydOQ0t9hjnzih4059NA0Ihzsv5RmpuQ6pY4rMVZwHLh+4xrGmGlIPE9qX48RhZdAgUCBQIFAgUCBRSnAuSh3cqNNujt378qNGzekvb0DmpZ2KgZCuWspBdB+USW7du2Sm3tuyukzp+V2xppvIeZFMxICAwVeGAW4L+N+TDcaWLtxj6O2xFN9g2tD6y1ck1FTSl/fPexZm1RbCZdr/AUXKBAoECiwVAoEht1SKbXqcEtZptiIbkO/ZcL87ZvPLD8/3H9P4/PDvPeMGcUOCvwgTlq0MUXVmFthR+gubl2N4RBSDxI8dOE1UCBQ4OkpwL6nPXhJ3diNC3F/zYhj/dTBJLfFeFTIf1x6JstP5h9fwEN49n2qwuXhPSXpGnDTkkwTqiksg38DJOq2tG2VTqhF6drVCVuXW6UKTH07nMyiBvOjUnpMhPn1hjZVZ0mxrMjfgqx8hteE/Oxb8xyjcrEsblYeXqSflcXyYPlkkRdzPuNtMbgFYXkSMNL76Ro9ydTiQTAZQWC3KoPu9Okz8pc//1lOn4IqzElj2LEuwTjykUQZYPWaY5ntm/XPdOxnaTG8vLxEqKaRTN/XXn1NfvLTD+T48Te0XRVBQoxx1FYegNk2ySgiPv2x3UYZ0QMFTR8Jx2kxD65tk4HFYOKjekfiKiktceXQvD5WuwQTE86m3MORh8qcowTcNJhvlHwbhS0pMr3uYaNGNXWDY否NDg7ipuWgPnnbeAoSdYl9PkcNJ+VWBLt8ZVJaXqrqY1UlLA47qMySTEsytufAzCvAsww256ohCVdcVKy2/MrKylUVHqVsiKuQqmUBx+9iPOmHYmn5mLYx7KiWk9J605DgM6k/5o+Sk3yqZCDm9QlICdIuFstFm3aXr1zBzdEaZdR+//33UG+7Q2+SlqOfU31m9eYqSNY2q/o+3sQuK0OZWFdR5WutaF3gjyO60t3CrY0seGoc+AIwgfU8F0SAR0Yw69w5h4W0CS5Q4EkUS否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否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否R4YSCcElFMgaqFkTvI1G+zvFj3E7GgcvlC7gASQZeFwMBLe2KRCd2T91JrnX0RM4vPCV38Tt7JapD3zZYjDpkzHHN4XFu/tUP/9dVRdCXQkZ6EODQzIxNg5VJv0yA6YDJU7Koe5wG2xQlpWVaTs0NMYYilJZ9PgzjuNSX+d/06Vx9F7nhVqYfdfY4qai9rygGpWlXdSRPBEQX41ajOPHNX97Msy15eQZj20MJKACuadjmgiYY3MqyUYVrsXF89J/vx9ql2/LZ599Jj04BJjCTcaHwyPSc7dHrl+9Jnfv3FVGXXtHl9ora4HNOqq/fBsqFilpNzz6EPbpTslnn/xdvvzscxmC2lgyLKkicVfHLoWpAuOmqqZaGqDS5xHG4FnMZRyTZ6am0I9mk80ZCqTli2iJnAeXhwK2ZrBnGixuO6Al5z3e4Ha3uHu1Xdy5fUcGBwaVydbT1yNbYOOO9u2qKqulCjbvSmCfTiVCWSeoFKoT5lxK6bkaSORx7mVdjYyMCATzZHJiTBlyZM6yDQ0NDoIhPAOpuxLciC3VHxkg7mJE0jTT+Q7fgQJrgQJs31SHyQsHphZzLeQr5CFQIFAgUCBQYA1TwBZfuphFPu2ZJ8tk2A2DEcELVdRUsHPnDpHGBoVmVKLj2qupuUl279ktOxD+AzRYmLMLUfYdnoEC64UC8f6Fjdz6CZ6xP72xAZmcmFTpU2oMqajg3jUcxa+XOg75DBR4URQIo8SLovyCdNOrogUA8LAZwGAJk+Xnx80KNz8fzn+3cD8dCzc/g6G/+fGdt0mc/A39eduXbhOYAs1QjXD48BG5d+8+JqwJ6X8Cw84YWP5kp8he0j96aJjncJcSLWXQBV+Bg+lJ2FuiYee5ec9o7UtKk7VVrKidQ1otdt5r7KcvLgD7kvjGTMRDi8Gy2nUOSwRx0zDp5uG+/Uy493S8ONHUi9rBg6QSHRnDZID0QmUt1QBSPeYDSJt88JMP5NDhw6pu0KRgCMuycfghYyUqreLhH0cpP18K6o0UuWFxxAiTYXCxLNRhzfbLCrN4+Z758kD4bHw+XW18SmDzpbPQ3+pRabgg2NJeEJDj4XA4WGOg5gDEH+lypvBHmdHyIEhrE1FcrbJF5rTKTKyK0ZLhkx58mh896KeA8QMeAIEfwSg1SnoYbRhGR3sXjwsjRHi4PlWI9uokrhixprYa6ijn5MsvP5X/+I//kPMXLkhTU7Mybh5COu6HH76Xvr4+KQMD+sD+A2DSfaA20Do7O6RtS5sUY2w9dfYMVDZ/IX/56CM5d/Yc7PbNKEOGzEra7fvX//V/kZ/9/GcqcfUA6jX//vdP5aO//FXOQ9prePhBLrOOGU8XBF5+fWcEM1ZwGRSIm1EqzFom6/gM6qwXa4vqqkppwAHRIag2PXHihBw58ops2bIFFxBKQP9IsgiNDgpW3TferI1XQXXTdlxQqK+rV4bdLC7GUA0q1W/29fWq6szamjppa2uVms2wnZeSqvPzaXVtY4Zr+u6vD5cq0jP5tLysHvKsEriy5aaRhsuCyY2RfPlxlxMvwZD7tho4cjGu5lcynywFq6ONa/9YZ6否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否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否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否31UEfTvgsEYjsNwOpIixvqwNcFevtHhL8zALiF3lm1T3XitIv9CTanZb38IZ5jUUu+QgUXdGnmhulKmfh4vDm62+EmbrMMaDxb/HSpVI/OyfcG8TW6SRJf04LM2bOCC1irIudJglj2UgVfWK7rkHPAdF1bzcqDS+HgwcPmR28a9evhIVLFoUFDxaEG1euh5tSTQJjz1wGFORpcOmdPg6dI5kFXPStB6iGzQBnvCAcJ1k66w/OOCfZvYyRTzzl5HEqe+rUaZKQXhdelV20P/+f/yOsWbc6fPnVV+H7/T+EGxrrcdifon8aA8lCyp+xYgC8R8e3y3yZB4YGjhREKUX2jRlf4zplsugpjpXx+/dKhXd7e4dUtZ7WuPqVLjSsD69sfyV89OGHpioZ+6bxIoOoQwVTLk9oxcbMGGJ1KqrCOaQWyEs2fxRtIDBrREXO0b54DWkZHjbaMkZKZ60dKYHivL4UhuosoymnOk/xnn/yIugxfSPB+phFjzJ7TgOOvlHkAw/OtBtF8jJJiYESA08ZA2MZSWzesP5e7EueMnhl8SUGRoUB07JMStEnyynWxr5eIbhPWi5OS7PFMV2S27F9hy7GsTmOztezmEZpamo2cw4zxLDjEhuXZZ/8/O01l88SA08bA75AiyO99QuC+KOL6LIzF1CvXpVWsd5e9ZuiX8QcGXxeTN4ZKmKfdiPK8ksMlBgYBxgoGXbj4COMHgQftUefo0j5OHmLUh7V5wcMNmFppopnZHHS8TmoSQez6yTJs/vNN0OX1ICdOHFCtos6rUoOuDhAq3aV5VbHvuDvjhiaEREmT8TDoFRe3rh+LVy9ck03c/rC/AXzo/RX28rQ2tpKjtyBNf/6PCuw6HXk5efZJrTH8AVuMrxw0IXKjnliXCFRhzQPKiZZXvlBviMs0mQ87LWwCoQT4l/Dc9R4ep4aSf2TeTmAaEfNimDjRD+BT0hf4x9MxPqmOlPX9tqrO6Uusz7MnzsvHDl8OBw9elTM8avhttQNhgeSthMofnDNpqobVXwqB3UN3/7zn8rbaDclF8xfEJaJacKNSBglbK4AlfzUyQt9FgfFuit8HlI+JzwGIplEJjfIcALP+h5k1NzYEJYtWSw7nc3GUG7RGNfU3BIm3YfmJ0sCls2+1BAqz+DAXWOwIH3KRZDJU5okodYTznacDXv3fR9++OknGfo+a9J2UyQBBeP5ri6G3L0r6VivEzgyYs1p否MzzmHE5zGkNpclEbMuUr8l0Q9v9EMYP54fBtxM9R3Uz3IZYMcrO8IGMXzaZD+yTn2USxd9fVE9J4V7vlhT+TsWDPhYlOZJ8cmYbWSnn/ybWUCWw7+9mGYPJmeMW8W7RCZlcYv82PFjdqHm7sBg6DjZHjbKBuhsqUueO29emDN3jqk5NQZeViyHVYyX否dA6FCKyrXq9AOFGTxZen84aKl90Bg3lCY9T/kcCwayDz6WLOM8LSoxkYT3SwXjHNwSvBIDLwQGfMTNRwwPEPS+Py0akqdSUJKwSDDEl85TPhfkOXNPkS2vM0s8JImDMCSiKKP0lRgYCwacLtM8cQ8aQwb6B0yry2lJ2N3gYlzGsIO20VzAembSJOz/cvluptbFs2R+osXWVJSQ9oFYYvlbYmD8YQA6rtUXckg15kZ6j+t86BqmNOcvvWLYYWcdN3Ro9kE7L6n0lBgoMTDBMFAy7MbtBx86ZLOYiS6N8zBvSPW7p60O9/Q8SePxPD0PcR6OP3W10niY54nvxQYi5jfVUpqo4iINVkNMj6QPB1yoQOvSBAazrvPmzWEXa3m5KVgvud+YIVkbe3S7H5WYt252mXQito+wobN48UJJklzQYe9ALo2RfpnqL/ySo2xszROijBpFnNCX442bULd1Ewp83xS+Fy0aNIad3YjiEBepOiPoWJ3n866VLuJIhothpKQuQoovkx3ZxkRZeu8n+TOrxHJZmkgd5jVYxMwAANs5hdAiBuOqFW3GrHvrjd3h7Lkz4d8/+yz89a9/DQd/PWBMC6QAGySpB2MOxh0wItF0T1J3x44dN8lXJH+WiFn39ttvh08++ViqMl+xtt+XHbH7quuBujS4qBMg2DAweFVOBq7egYn2juSK1ENTeWaeI6VLc3oewkabJ81f+kePgcfDr/ePlEg4ZOYiQlNToxnq7usflDTc/dBY3xC46AHzC5rjr6Exqmb1g2mkogZFvwcOHgx//Y//EK3/qoMAqcnU5uja5WsiB2zd9dnGqbK否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否Y2fXknNZ6QFpwij14p0HkNgbBM0Gl18p43uqL7CZXF5sDxJ8iypx6Yx1WH+7qV72jR8pDDP9zhPLz8tozrM4UnDq8P8PS1nfPj5lun3NKgUwPrabWeOD0hLKEoMPGsMxD4NG8Fc2lGyge9h420an461Ns+kQ4ZVEOup+NWL50vLSuehaqw4mFyeK0ZfSs1KzmAfqYwITgpgzFtdV/leYuBxMOBUxa4YavP3QakKxyYzWiTmySxDvS46YbsuueoWmmXHbvmyZWHTxg3SHtRpl59qXaLz/gCcQ9YrBJauxMAzxkA1HfpY7LSax6tDmPKhDD4YdtfVLy5q7wcje4rMr5iZEXUey4PIqrmRxm6P87RZlvJRYqDEwEuBgZJh91J8xifViGc30KebFKBP3zlUWKYF2xtvvKmF3eWwb98+iYv32AGZp/OJ8Em1/EUs544YdkjXHfntSNiwYX1YLdVbGGvesfPVcPr02bBnz95wTrf7S1w9wtdllWSrrLjdwGYghoFPSQc/m43+/j4rlA26SbTZWin2H/AdF2i+gBq+/oenGD5vHlOjEOAqtkokALYioRs3RtoHmwGrV60OvX13wlQxy3tFV/1Sg9kt6UzUXPYpfEAbLRx9874OqGFgYmfS/rpuicYk7SS7W/2SUFq6dFmYM2+uGHf1oT7DIbcr6bvWf0Gt/gxbBUhWfvlTYiASRkYfGR37RsfGMhEOTGRonMPn2N/iEz8MD+gV257t7R3h4IGD4djRY8YMWbt2jdkUWy7VsE2ye3e7pyt03upUXLfRNh0XGrUyM2YNX8Tesx2W0bDo1vo+tKx4C9MTRk7u5EfCr0GMcaRa58+bH3Zu3xHef+/98Prrr4dly5eFqVOmShXtPZNevTvQYAzGc+fOhbNnz1l/hFHOxi2IJ1TJtMtrKT2PiAEbmixv7UGIb+5/jHv+jXlCGvcl+c+45sw7irLLDVKTaSpaGSsV3y/JTVRlUtZZMWFRiXmqo8MktrENiuT2AtmgnT59ulS+StqOyxGy2ch3h1kHTRpVZbRVwBHp8hGbX2abYBiAbkzFKzRVuhIDExQDPtonM7VhwsN5qY4jzMfdGK8UVYlYD5izqMpIxv48X0yVrc+zlzE8KKuYu4qMwOf1FKHR523zZyV01anL9xIDj48BaMzojZ+M4GC8YTP99OnTYdHCRca0myQ197F7xBxNzU06/1mqM42NSncm/Hbs2OMDU5ZQYmCcYSDpFnahk/MltHFg1qaluTmHlrE+mz7ysNJTYqDEwMTDQMmwy755utBNF+bPniTyZY5Dpqcte5JnLag8jcdlK6SKvB7maarzPCyceM8zirLypnge8lfnS9+LdEtlI4tDTiaxczq8bG9vJ7MdhNkhmQ7C0m9mkRPsB7tLF89fCB3CDaoKuaXTrIl+zpw5YeXKlSbNceLECVNvmG8mJxiOHtbctK8PoSc2xioAhhz4g0mFClIYdt3ZAexkHeLrKNf+rC5lyN/yVVZG19nDu0X2atmKXhB9seait9Ft0vRkYiFXy9Gmuix9Z否NTcwBtLVXaYGfw2EOmV/b8WqYLQbeW6/vDlevXQ0d2ljt/36/2f3i0BmHxA8HydgPQ7LwTv8dSY/sl4TSjXBMEne7xIx4VZIjMCRapfqNg20YnqjPtFtjnHYbfEUDcjitBn6KuDzoqXsi7mtX4/CMJg0ljJSudg0TPdTGKPUnv0xoB1CgXaiEVv0P0oChQTjMucF7g2YTjvT9GhO7urrDNam9vHH9Rjhy+Gj4VSomO3V7EYntbTu2hg2ykzpt6oxwXGPjF59/HtpPtlu/hn5RSckFdpOOUz3UiaRe/jmBx//kwUtcZNTJkziHl76GNNUfPv2DqStuWxnH5tYW3aLM+sQDqcy8dfuW2ZU8J6lyVKPgsJ1pDDsqyoq3OotXkpVuDBgAfzbuGyIdmzGMb4bzp317C2G9oe+dJbfx2T+3wopSYBjfVVpkiKPtFmiU8qAD/gZ0aHVRUndchDiu8RJ7oEuXLtEB1Yawdeu2sFaS8lNapVo4YxiT15mCwG3yFXoS7nAWgGWQFAAZ9JU/WRuTwDR5hgLFUn5MZJSuRHk6PN7+pJza3jyXoseUMSsuzV+7hqcXmjbyecJRu4UOnT/TVP7tPMxoR/RntO+B5bPEwITBQOy/2b2bqvEruxSR4cL7U9rj87FWaSr8ox8IKzBt652KkNG+FOOy50Dqjn4d+3aUxq+EsRh5vW1VCPCiymeJgbFjIOso2Yq46B8KhyaN1kV4g3cHwyVpqjnZcTKskpYB7NSZYy2jf6i7R8U9WgjWyLzHL78siOvfDCIrq2piq3odO+xljhIDTxgDcRwu5gloNJ1LiDfV5AzG1i90IVx7Vmw83urqssudKUheXlyQqyTy+Vo6LTjNVPpLDJQYeKkwUDLsxuXntNE4gczfH3dkftz8CUgV3uHKFdz5xFKRYciLT0i2bNPshh5zFm2bN28Kq9eskj27n6TPvDcu/hSfbkaGFDZBAu5J3VavDNVevnApXLt8NWDTbqpu73NDf6FUr23eusUWxzA7u3WTP8XZk6Kolx7VWljlzC01FkbdJTFHz8uWHVIRU2QHCwkaJbO1FE9o2XA93E6CtCrLGROGwxG60FhxTFHen0bMa3V6X4qb/TnaQM3cPsPU9d2W2ssjv/1m6hkGtNE63XFK0kCD4Z6Yb5NgVHIA3TBJwj/3w/XOG2Y3sVN28GA2sDHjkLmtbZWYe1IPZ2cIRSPzjd2IAD6rSO8Nz6q+sp6aGLDPYD0jj4ZOmBNw9Cn+oG1oj+C7GgOxZWfvIq9BXVpAsu62mHY3pA4TyWwY0ot1mxeJ7bfff8fUEt4RY+/UmVOmbufSxcuya3fdGChsourVn6kTSVJTCxurz2HyAz/rZ4RmZF3R3RXWIHU/MNvWrFkTPnjvg/DnP/85rFzVFmStTPCq36h8Bg7GD8pE/fPBgwdMFWafGIc4g0P6MivKpuGle2wMFGNk/JK8oz4YZhuMuojziOuCcRfHVsurbMpi9GgfUBCRGjWm2CzUwxw0izrhZl1AatKFGt7vSEK768xZ2VY8Ew4dPhRmSw3OpuObZd/luq1zkHieOm1qmCKmLrRhDGTRvEvcxZJH+h09jdB6XG0a83KUSl7a685j/F2xhTf3jTYsz5B4auV否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否XpmnyQ9we8uqddb1LxQcb5o8qJ4H2YBNUKU5370Z6wzMjUoZSgMWQrrN5ZCAXGhieSFXux/ZFrYpCfG+X3RE4vKB/clCaRNFQcarQtodwjLliw1pgj2O6bL7mS7VLSelxRir2wP9ErNAzY1DvT8KjWBYngIl0iLtGkzNn/+AoOBQ2XxMOQiZOYtf0oMVGHAN0NIZ8IM7pcEW7duJHIgdVZScselPvDbr/8Zftz/o42HSHauXt1m9ibfFVNt91u7w9wF88KhQ4fCnn/uCf/x2V9Dx8l2SRfdihshmCAQovgucT6Jhwf0Af5gxKEGq1G0v1C38He/8Wb4/R9+H3bv3m2SdqjJ4tb6oGyLkgZVmIwXMFroOzhgoqw+STWdOHFC6ooP2GULTXfm0gVnDCl/h8NAOuIa3pLhQyg2V2ttMVx5I4Xn34XvpLL9nWqMqSzPfeeIAAcRegIH39sOlLWe6e+7G1WVaf0yoAs2rbJ/1CRawa5dk1StcdFm+dJl+lsa6iQ5yhoI1WcUiFqou6LBu/VaF+mAFq3h1MEhMlXyZwfKekYpukhrMSaO7ZTRe6dbY3OvLj71SJ2a1FPJ2D0qnrn9Dg3TNi5aoPLVGJIqmLOD0pUYKDFQYuBFwgBjLyorWyXJg9Qw9r25bLZUY+zKlSvD1q1bzPbsqlWrbA1bLUHn46oGRZN6cNWSvDC3MMwzyFOPO/z+52EVT+YEC8h+9WDMtb8kIXXboJ6lthcqJTh7EkVd2NFt1Pobm3qTJs21NP7DhYtuHQafO38+nDx5MiyX5GC7nviR6rhy5YpJdrCWYp0SLw157vJZYuAxMQCZR7I1+rp8+ZLs8x62tTnSrdOmzTAaNpoWrSL9ulB7y02bt4Sjvx0zO+opBNB7Tv9pROkvMfACYSDpFmZTmgtz2GzEtvqU5lbtH3Vhzsb+rPO8QG0rQS0xUGLgyWGgZNg9OVw+pZIYznEs/tMB2/0eH1M9/u+jlDcSLMR5mfhjWrXGQPUFV9zYZNFZ8rhh0EZrclQBRQakGzZt3BT+8Ps/2GHVF59/aQw78iOdY6r8sk0MYV6f72so40V1EUcROX7wS1v8Jhu2aDo12Z+/cFHqCZeHGdN0Q1Q3SHEYQt+xY4fdZuMQ74SYKRgsL1yGdAWANv8uRfzE8cVD+gwR1uwMNwkRgX9Uzxw48KvZw否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否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否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否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否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否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否EwIfKcI6bt61veTFMkrOO0Jdp3O3bskGbDRZ8ktVi0dLTp8MnSkx+1v4GoEikFIlRgmJrYxf75510msHv1lVcqgZ0hBc2UgLftrLSheCIUklk5mSRDM2RqR4cY3pPT13/52k6ywoBm06s1heD是mRf5TFPMb4j/BDHdulkdnWpnuuRCvcS+SCXK8om2PwhHkXtDYtDL+WVcdXaSrdCcKfoV+wzngOE6Pk6fmSBGnbPz3zRGNiD9xH07fOt2VhSk/ieMm8CS2gSNDSyxK18lCWIs4k4YfNFNqcX3zxpWl0vv76G+mDD95PH374kWnJYMIIsz0sQCdMmGJaIBZf6YDTqmKP7DQwd25gghUhM6aSTpw4YSeFuYz5kfCMaya1qL8F6icvdx6WVSVAR9EzSjpYKVvBhF9UpTn+YOF5WHO8SG8kn3l5aMf+Zervo/5En2LcFz1NfCizvRw+EJW0SYMOBl+nxrF7EtJhWue6TuLjOjSmcTABYiJN6496VulrvtCHhRGhDCkimF/xQ/72qhKLrhHYTHok2iY+AkTC+JZDoKgenqZ1Tktti2TqUFqAEzQHcX/P8ePH03/M/w8zQwyjTXZ4x7dzpBVjKodAuKttkpkvuygmLKZw33zzzfSrTz9Nm17bpPlombe1xhzwyB9YZRyyMbEYj0Ea7Vk0Cp9l6/lFd7lP0XJVw1taFol4ak+0L82RaCM/07wx1YmGxxUdMoChWVSL2IrvX5RPxTU/i9Tih7yYc+bMnZvefPut9OkvP02bN25Ky5cuS7N0mKG9o93mB6gtKK5FMlZmu0NPWU/Wv7JAVcE8WvGtbEvnVBqfeQQHMrxaglWYa2JEnFbPLIOyMAEXYVV6EZK1WuU1at7y8kYdnmfhyEN/kZWypwRGB+FH9gUYrw0O4ByuIfDl/chpv8RCjacSFS/ixedzWXGQwGn/wYNm3nLHzp/Sqp6etGHdelkLWZdm6E6r27oH65gOd+2QNhmHxjDPbkIlCZEear2JZg77G1un68n4ynrb1/DqDkXjx7NVXaOX5yTBGNiSblolMCJ+Xrc8K58f9KvCYnHl0KFDptV/WAKPNatXp09/9Stp3G21qyKwiOHOa8uahfX6PR3KuyPzlQj+rkowiub45cuXTBOeJ+2D2Xq0ytkD3NUay/czLrDjcCiHZPljn8BWcUq79gBa72DJU6XOi1y/jwcMRIehLk否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否hJID5dUtm/KZM6Ui//93v0oEDB8QUO19OoIDnm408xegD0Scs4ef9E5N+UZEoT9TLspfnxYsX0s6dO6W59E164403Und3d5Ieg5eOSKoYdfPkYAB5kPN+CJRNeTGXEWzCaL6tO3y4e4fn8RPHJYy5ZYzpYECHAIdUDC9FesEgL1IfhY+oeFa0Fl5ZqL8OBaY5UoOWGdj3DW8z2JC/SQL3JGXxmMP+zWm97BfKH39Mg96/98AY1Fx2//vf/y61iYn9wYcfyMTqlkJgF4XWfYgKmzNnju6wW2FPGM990mY5JfNzFy5cMC2VKCBMhComTAcPmVASbhUacZ7sSTpVf/B8Bko7ED9QeKsSBGzEBSb8muFzmAjLYVuFB9xYfTbWia+8xrzbSfACzIQmooG+6zd1J8AlE/IyXnVJIIJp1jI1RfR0/LckGwG4z3DwVaZaRbJEihw0bqIhxd1llI+DIAifCUXYgyCndEZg+jYvfoZfmjKtUf7CGJGPH9EGdpedhK0R1i5B1AJpnXX3rNIp68V2GCCq1m8+KVBWYc0Q6eDZq3lUQJHcAE9FLApHmdhQH+49LKHdgXSj74YxmWhHys0YaAIztekdzY0cXMD09nS1PffVQYvMm7SracMJDs1i7rRbMH+BjXsLFy3099lzJKjT/S2z5uhPAjvhgTkYASJp992+ZYJptOt83pVQWvlDQqZ9HLWhnkXdrcqBh6h/hBXffDaDRFKtn5FQ69DBfSOnwaFexlCnbX4fjyPI0wTCLyOi6jrXGKgxMGwMMGYwV/jUZqYJyjRsPPGA0i9eytHeXvwrH6Hwyb8jXv18uTAwMB04zUAl3GWHWfGLFy7KckFf6sJiANYCFMYejzd4OvN1kGnd+vXp5z270wXxMswUeRGWz4/GayD2ALSroNrVGHjuGKgovCmr6BTFeo1QvO5rP3j3nq5M0MFmrkcIZ+kAW4yo9l2kYWt883DoeK3H3sBe/awxMHYwMCICu7GDjvFY0uahOYbs8VjXqk4Nco7Ku+mtwEWOovAqFn否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否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否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否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否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否wsA0MeALQ2kD9OiEjxP30blVj/EkcFXL6VGwVguPZccr4RWwyMt9nMpizIeseBZlJ76YlxMVyUkBRhy0YiOR1skQlk6Ez3YRU/fu2Ru2bt6qxV2ncXQa7dChryVl8m34RBInZ86ctQX/oeLiXMUY6SOhloIsaNJLJLRGgi5MQwjiEMdviWh+X5IGq2T4uaWlKbQuajViLRnBHLN65ijLCmFnrl25FxnJoaSACBrL4I0ILg1m9qfEkEI1GgFIiGAPCeI6ajB27d4lSZIzYfnK5SJgjYYb2rSP6o4DfzCzKO9+nSirXrCFzuWlHBCI8Cb5IqZd7BDgKIvDTfUo+UQcWt9QfwGltOfChU1S77YjrFu3TqrwxLjt7zMJtQdqI9rsriSRjNBPdvqVQzCXuCiVDaEf4+GdHUtMcqexscSkNqA9ai0C77DNwp32dWeMAH956js9wV2pjOjjYe7v74RO5ef5le4e23yybOyWBeQ56yGPqwfGQM5sh/tTp/6g2/WbN8LXmud+/7vfhyNHjph64Duy5wlukAZDGrlFUl+tLbKnpv7EfABjZ3hk2A4aXBcjC6bf8ePHLS7MqHVr14Y9u3aH1159PYzvGQ8bJLW6uK01PyhhDBz否8yHwEubIOFrY5NBiCf+1CD+25sNUAspXIhOmsxZnbPXoj/BPk9zaAMHPoClJJVIZjEeMTqXdIS21sWhV4zHMc2V2DZFEnFsdMxsnt6+LQbenRGbK4t2T5lfIX6Au4ViXrVKso4DEYs0ryNlxw/pNRhS8S5pNMHhDEQYgLxrpjYzW44SeWWHEGJbIu2LqlKLqzrV1+kghhAG7Pxw1Bm7JTDovv3mm/DhBx+G76T6GUnKG5Ke5AQ1zEFcq5j7SFPbT+ni+sC8qLxVJ1u/THRduVsBzLX3w1URgb45eCisXLo87Nvzkg6+dIlp1xgPTYzDgBSMNK/yINmYpLwb9NzYGCXqFBrGs7pgK/G+yqDO6hXGsDQ8WMMamPnFQMjfsv5SeK/+6KmqZFg9QfKdFQw4/r09pirkceLGfDxXxozNI/KmH3tO8U4si5Db7mQslTm9V/iUBb8IL+X1B2e+V4p74RcBB6mOCQMJAwkDtYYB9km48mVLntnSFkPjvnHo5pAOIA+EQUkZYcu+oVXkTcVjzdOMbvvzZcuXhS1bt5j2Bb577dCeMiHcNmWWIWtmXBXK1wYvLd0TBp4xBioHAl1Uf9ZvBQrdlYOWt/TNc/fubevz1SH0Hs0Awvl7fCtePUbRLz0nDCQMVMeAf1tV0mWqx34y38SwezK8pVTTwgCLQZr2y1FViY8MR9NAFSl9eY37Sb35xlKPkcEXiQ2U+UB26sA/hNMGSUPgFotxh1u7bk1Ysbwr9PZskH2hDVIVeVGMm/5wXfbtLkoX9lWJ13MKDXVela44IdkkVahSnLQiDDxzsvusiKd//vOflc3D8LN33w2vvf5aWLFCasVQWSG6OoRSiLPk679YpiPFa12EBD/3zwCw6F42cWM4RGOYVEskZbgCu0PoshfRddu2bWbDaUCnkgYuXZY6jVsiWFNfz5c8ass9eCCiuk4QYmsKu1n2nSFwjTDvmzobc/IU/o13oSoIK1YRoqC2tB4iu34dYnitkJq/ziWdsi+1JXz00cfhsFQFnjhxQkR7SaQoVS1iA0kbPr56enrUpiuMeUAFjemqehf7qFU8XWYIA0/bGx4jPVHptlMlYf7Tf9a7TfKL+YS565LG9cGDB8NXB7/WR/xg6BCDarXsqS2WakaekTDDrgUHCJqbWuwQAQcLYNihUhFbn5dQHSsiwIDuN/UxNCi/s5Ic7u/rD6d1/0HSez29PbLZ1mEG7ZH0XL58uUmwLVLelf2QjyzmRZsnHe4IvI3RYvziszce45g8qrk8fkSJLw0W1Zl2pEWyDeZkk+bEDHETsuMQBMwqU6UnBh62PsErDjXEpG/QvMrcCvNugeaUfPqZkBtTUWRSsSZRPlVgvbI0XIAZBqPKBSiT3hNDkOYlMNaN9WRU7TAkuyVX1BaD4fz5C2LM3ZC0+Inwo9Q+HxOz7rzWsmGdMgV+HOU1yZYfjEz+rM6qireD483wSnnMq5kjDji4eOF8+Pjjj4WzheGbbw7ZvNkpVcurV6+J7S2bfQsXNqjMB/pgHhUDVOvZApWptZfDBdTn4QLylaSz6ku9rG7cAYk+QNnck0sYmCYG6DKVLo417+fqv2LG3xExZzSp/8pRVRxm1XBIRCS146DMk6WHhIGEgYSBhIFnhAH2SLY3ysrL3ysmbQ4aoxHjR9mn3rJlS9i0cbN958cNVYyM否jVUtW+V/aI+0Xr+OKLLyxXZnn2gJ6l7cvk5+9Z0emWMFBbGKCDZoODb5cxHVzk24gD8uP6jtUnj5lCiccIAd13PVP07GKQR6+tWidoEgZqEgO2hvjiMUsQJobdLCE2ZesYmO4KMN14nu+T3GdnBfIxWtxYToRuqrKpu8IdBdndX8nLCY2eCwu0lcuFiASIHhiJjzGWw2NvHkfRSAuDp6OjU5JWu8LaNWu否AMBGhViBw58adIoEHmGx6U2jgJIL0f+ECG5R4kJBZhUggIzWCAYmbQC73Iw7VA/cVti+6g5WyamGYRtJDMgpsJ4griKxJQRc1UZYMSVttERBAjJFqZKEeZ4yWNmZVpaSWQsWACxVwRiOWCCcN2zvlfE9WUmJfPdke9s437giy9NqkbiERa3li5FiLDRBvEc6RDwPJkDL4YpxeEPRpYR7ekMSoYf+Lg//kDSIs1hx46doXfTRmN+IW3CSUVU4lkZU5QzWfkz5h+7csyuUF2Y0Kj0XLN2jangg4FnzvpFMdHkkNR5vdSXC1lPnmAehMTxPxuAToUhDyvi3f0clsp39y/cq0WRX2wnAtVOaisc/Zsn+ic/Y/7gl4UPSCrqc32QH/r6kCTkRqx/9/T0hK3btopZv1QfM02mxrFRTBaYT5lORuUjKVsdHmB+vCk1wWdOn5Wdt2M2Jx4+/J0OM1wI95Dokiph5i3KaRPjj4MQqzWPbli33lTw7mjYHmDY5Q7w9aPLoT6RB6R/dYl14V5An9cjT194iDUveOiR+KiptPHKHMlcB4b0bzbtyqPb+DfcGW8qzqPkYaARCnz6axQzjvng4UMxz7I8vHzixzRIwcU5tlgMYe78mfndn+2exSFveHU2R5Mvf8xZpSxMNSeHKlDp/M2hQ6bW+MjRo2a7AUbqyPAts1GH+skGU7UZ50BgGNOcibUSO+RAOSyWmTOcgaocFkUAoILjgMQZqVT9/e//U/3qM6n77BJhaFv46U9/Gl7a+5KYv23CgdR7kodwd1+VAQ7xMa3PwtRU6yhH8BrbRC1m7Y8HIVZ8odzSY+mpANIkj48Td5IskvdTYoAWduftUfTzsMp7ZVx/L8aLfrEXFf2zZwZwwd3XHEXfZw82Load9fVC+Iv8aC3iY76At2wIG2oK3i8yqlLdEwYSBhIG5gQDlXNwPNQlUApLKvuuIalFR4X9gBh3a9esK8FKBqbVYIGpg8cGMSrb2ZNFV8rI9oellOkpYeCZYyDbkli5lX2/Epi4h5Gvujg7Pw4XDuvAKfu9Jpk/sP1epnHF0uYJYp/3ns/ZJN851vEhhvPA+JauT4mB4t7bvzWfMsuUvMYwMNvt6itWjVU7gZMw8LxjgNXQl0jqyvP0V8jSQq40bEbJQi4SHyF3TnTFNKzhqISD0LxSqipxmzdvtlNqGzasD7v37AmHjx4xxg327W7euBGGRSwdQVWcGHmoTQsPx6GwxoIqCoQhxOQVJ7CHZhT31KlT4b333zNbamysd2zdHtaK4YJNJU6/QcAERtJSgYosYzl+JWIeIT44edbrWSo/A1HwNEpCokNq3DolEYGE1ooVy2RPqCEMyP7R2bPnwj391aqjbbGBxKbMbCKBAnZ3hrSMyJ7v9uQPQyBDBtIr4kaoahFXEMb5QeRH0m6xPl7Im/Zoa2szFBQ3GLWGE9Tx9fT02mnJdevXq//oOJm7WEV741FYmNI9KnzKxCnwGWOA1io08CSlMwPCaIKpu1ZSUBt7N5pK3E2bN+bSmJMkzb0ZHzu37wzbt2036dOjRw/bwQOk7lAle0VSXufOn7dx1KA5BMk61Mz29/eFHTt3hq1bt9qhBKSysCXarnHVJnuLizTnEn+yWlBDG3sw3vLJTJ5KQL1MMjmDMg8nWGOf4Y9gNXlHTPE00ZEP/6S3IpgrbN61EGNcIglmItATk+c+JtmrpBZVvhBT8Muyz/3Jmnmd3E11pCfIcgJ22ivUi9ElBsOt4Tu21ty6NWyEGE6N3rxxU5Lg5yVBh/rLb2Vz84TZqYOBSt64es1xJvUn/KIkF/WbzJXMbUVcWWQuoEE/bPu1trSaJDanU5Hk41AEsJh0ofIYuT0cRk4Pmzol5s1vJYnc33cyHNWhj02b否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否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否56c6VeH4yqyCTjMC14eVk9osXLzAhP4ZJ7bxdgShYwfQS/XR69WXUyXXGcBK+Z08P/vbIBDsn+GrJUT4q7LhT5vDhIzIpjweZERH+bFCGQr1KpohUnFAZbPJV6/PmiKHorTb3StKThk8nfUKrzS1OJ+dahgA2Acs5/2FDxR1coA9Nh3zYc3fXsePH3cef/MSdws4v1tHXcB4nz9jkxz8VVlSG0XQmd5a1YLcWw6hkGh0dc9euXnP37t2T3b00wfgJ+Hz66a+xe+wDKO/2yrt4+PAiFE77xdSmytvc3OJOnT3j3nzrTTFn293Z7fbgYHEqw27fueP++29/g7Px/uxmF9L3mHWGiu7jkPf8hQtu/8EDrmcvznyDEu4JlIFffPaF+/N/fCZtoebDKxV73VidzEmNYyeOi+ncvXt78cosuYePH7pr16+7H27CXPHwKJSSs7KbkO8rlXlLS9MwX9uDyZLj7vLly+78xfPuIM7KZPmpjGttaxGFI3dJ8/yQx48fwzTRXXf1+6vuGnYt8py/7GvOtKqI5HkkbPubICPbM7+LGn0DaLhIg37izV3bsSNPmmLeBSVr9y6aRDrlXoZ8o9hxx117NI0JFlAAJo/dLxhhbuDLf+p4JzU6CfKx3ptf133tThIoK7lmU8T11KcrIDdPTSKQ96SyT3atgpfnx3pKRwW8LCygwm5uVtqh3JzLs8tNsh0C2RYEqJJrdhyxQ6HZDo/XymAIGALbHAEuFmZvJ+101FjTEs19jIdvwoLCKy9dkcVahIL0VGCQlOPDzo4O14exe3d3qrDzY39SmzMEth4CtKbw9OmAe3DvPuaLnqOu44Mm14XvEH1v6A1Bsnpe0mhkLoPcQB1/5kaGwGSOUAdg5YgtzhCocQQ2oj6bwm4DK0G5Rqxc3AaKuIWyynYi6tfeJi5KubiYrtbu88qynIxaVtJp+jhM02uYp0kHqJom0EUzprqLLm2YONkRtGrKVq5UkAUNGtnhL57kJIkEs6Mu6KyDTJylxC1否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否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否c81qbtoTBKpUL9YZfQcTamzSJhK4N8fVmPdvclI2bkV6hrfRtLTS75yJU8KwICMixV4JJI0oJGdb4GnJpE4YUSTlj5fSr8EOmVNhRsVbb/+u79149iZw0mLI/jAoxJfTW5yhx0VadLGIC3zIi70a5jipHnzqnkUhIX0+t4wjrKJ07jgzTKg1Kqk0111JNXkeqOygF3imFb8+FF6yTehIYV3ybNOUlfjRnOtBq/a4xE/T8V/NVIqn7XwWE2+y6fJPr/Un8pciovWrFLxlYUn7QzItW5L3hzg4D9XVtPc9zQmJ3VBUmWcdxYVnwbfdv8E0+e4s1Cw0hoChoAhsDURiHvU3BYcgzuOWV88H3bDL4Zl1/kSFrRIy+87gGS8x112vb29sBRx0D16/EjMSdOEvLokr3Aj+eVmqinsagisHwI69uMAJv2GYeX0lZIKO9b5x48eu+fPXyS7S0UikPg5PY5/+P0mQ0f5nmO8jmV9qIRIsu3wo2WrvW+L7YDu5pdBny8l2U7P2BR2m1+3TAJDYBUI+A55FQlLJEmGoiF+NfzTgUKJTAqDmUU22wKK0gR+oOLzk4FKnE5Exw8n1GP+8EjjjSgO4KdxtssczmDimUWpixOkoZt5J2VgcSIhxkbG3b27d91d/FG5xZ0xdJx8wjS6VwQIMOmUM2MUcM9LOWLARvOhUDrwQGJO0PPMqaM4v4pnSE1i8p6Zx52gZLbKn1SiQgY0xbdr9y534fwFOUdvV/cu/7wIQPyQVezC5DXjI07baZBQM8BuqCA57UAcxDqI55ytitreBAt0SXywQpmUgNVZ0uKnjh5WccYGBsyKt/In4fxJnebDEM/Lv93kE8elKRCOHOQdZkb4W8T7Xo82UsxRIqgRZ+HxbDiemccdOlTgNaI9aGzwJjDJSxR3iAuWJpEX8sWfyICfUnkzLctU4JgWAUzj0/MqKCTEoVQJX8armctGaCzj9ywUS8omfJlZyFTZav5qHpM4JHmGSM2z+OlqartuJAJ8PvFz3si8V5JXth6tJO1aaVm/4/wXsBiJ/fbExLhMOq6V/85JH9ofFDiuczG2OwcLK6khYAgYAtsDAVnkOvTMPXn6FGek74fZ91ac3c4p0LTNb4TZ9iMYA1/EbqQBmBG8d/+em59MFXbbAwkrxbZHgN89qNZU2D0fGnL3cYYdF3hzvitxMmgEYfhGSsJL3gSmJeO3XsRW+bb是iaxOuBgLeFtB6cjachsKUQYGcU91wcxJX604Jl4zU85qNh1byqrOXyiWlUzjwZlE5p9Kq06udVXbkwpSm8cvec/BFjzZLXyHGCVP+lwT4vTqL6f2lMcqf8QoBKyolgTrowWiZsEeF98GPSeQYKu+zq81qcmJFxVVJYfzM+Pu4ePHjo7j944MZwL4XM0CQ4IJwNfQFMARsAJKniCT9itgfnZp3DWXYXL1xwPT09osjLm8Au81Qy0izv7ezqFHN37737nigMW1taChLx2fjnoyUriM54Ckpb8CZnCM2biwAx1r9cgioEFj6j4vw0/+Wed0y3HG2lYoe6BnbZOi5SZ0VP2KosGYICsRDHdin86burKXj1LqWhUm+5tinhpwzBRKVRjnr1eaU5MZy0PMujo70D53h0uQ6Y4mxpaoHlzOwwMciFfJhGdtyRgbQlhTwZvLzz0pBO5BUWWnbPtoBrSp6y1oLiSjFk5WjYVcsock7/wYdACRdekiDlldxpRvGVqTzHhMxudjgCvn7w/Vveaf1ZaR2K03nFuK/nmRyDCBzXTE5OOI4TeIZd7JZrR2La7Xafi5lAq/j6Euuz1Ot2w8HKYwgYAobAtkFAm+8wsEvGwlGfTOXFixcv3MNHj9wgFHeqvMCytWRE19TUgLObD+H787w7DAsvzc3NCUSFvbvv8zHSTOLtxhDYaASSah9lHNdILkbn/BDNYg7DxKvWeSFHYnld+G0ZpU9vtY5rSEylcXFupCsVrjzsaggYAqtFIDsTs1o+ls4Q2IIIaHe39URPBqQ1PmDMRVj79AzsShsPCzIkMrAQOvxwsMEBgv4rhILnIXHSN3BQpvDTrONcWIEeD2D84CWb4+b6EyWVyh/EGcfK+QcPH7iHUNiN45yaQkdin8Cbzkonv304JvwkgQ8XDwOAl36AtLS0Qnl22f30k5+KEq2trVVSFP2kSYqiVhQAPvv69rrXX3/dffDBh+70qdNyNlfCQ2ba1KeZ8lo7Tt/J7TnJV5uYr9fTz1MMJ+9ieGGkbVFYQljShoRwqba4p9e/kvSEtgnvG9soMSsJipAkKZL3M1x3sGmb50n0LWcefHU1Pa90KotcJcRTSD0Nu9OkDBAs07wIdakfps+awmTmWu+ZXx7DbLD6y+WtZUpkQd6SPwqs+cV5efqII261/JofhfP/vJjEwOPgc0ljk1ztZgUI8Lno3wqSFZEqj+Q5F1HUXoDKvP6SpbU5Ly+Ob8bHJ3CG3aic06s0aRumIXbNbawAiz7LrVT/7GkaAoaAIbCTENCekFfv0jvxh8Gd7DZ6PuR+uP2DfDvPZBaykJZWJQ4dpsLunDt85DAUdplFo6CR/PBTapwredqPIbAJCPD7hzXUX1lH8TWDvxGMA4dHR9zUzFQildRjoQ5BSESL6hqeEG7DGxvbbcOHGhVpuz5fM4kZPWS7NQQMgXIIJMOAQKR+etnNx07jsuF+YpmUxTE+vaaMuVV0nzAs5MDxOgfXeuUAhue70EQGV6LTv9XcJGRfeDrgnjx5DLNXE4n4er6UFkmQUDh4RVGltHIN5dZ4ieYAz7nW1haYpjwPfObc4ycP3dWr37uxMezkExcYBV86OtSA/Gv4bhL+SsGOtR7nYjU1N7pDWNlIRd2Jkyfc7j27xVQpnw1pxGXEDV6JSoqQofUJ7XdnIaC1Ia4hK0OAdW5d2wWKpmLihtU2kTaKk/AkonwZEnY5Z否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否X7mQW4Qol8+1o9lnQX/4hZFPIVjoyK/GuFNEXirHhPvuw3HDIczPU9rX2n0f8PmhDpUUq9JcdnzAyekfm52BFAOObCZjFpMmvxIXswiUJthtDwBAwBAwBQ2DLI6CdW9Qf6jiRUbRSMwJTmDdv/iCLRl++fDktMglAzG/RPXv2uBMnTrrDhw9jh52ZxExBsrtaQ4CfSOlYF7M9oR7r99bi0iLOMx53AwMDMAc75GZmZ5IiFH6+8U3x80E1PxmWlMBuDIGdgUDVFXY62Uv40gZkZ4C51Uupz86eW6knGQ/7sjQ6SsyGl0qTF54NUz95Kv84jOFZP8Oq4fLy07By/CuhKZV+ubSl4j0G0fhcMpBBDO6y4dyFRmXdxMS4KOxSabj7K/XV1l14zkFA2tuvb8SKQCjmqAB7MfzC3b9/D393XRNMSzY3tYhyjBj4DxBfMCYXFgiXOERLGP1alzC4o2M7UI8/xmubwHwP0pTlBx/KDrwOmAr505//A8rCIdndJ4d4S5qgIpTMQu0Fn6amZpyB1+POnj3jLr/0kjt6+AjCmsAfmkUvouQtP5RVbkLZMwQamiaI7zQ2yzSm2dx7xXRzpag09xTHwn4iDU85KfYM0XgN41XD9JqmLLzTNIWhpXxp/Q0UIbnkkv6EWp7lvZwspXLNhlfCpzBvfQ+FkyYPgUoprxHilFbIAq3SpJJoiDJjTGFY6vN36idlnIr+2Gn+SkRaKu3UJfFJfiTwsb4poKdcDsopuoY2JAqRW82rRHQgX2FenvOq0lbjfdZ3iwJUg18oyI681D5+vm7qM/fyZuur+v1LlNZ1tHbJe8UJGv+nD3oeu+InJmFFAH+y6z7Q+pcRPFNGmsSuhoAhYAgYAobA1kcgp3+jYoK96RK+b7nz/Pbt2+4uzvSieUx+Q/否B/aLcuQE6Phd2tOzR3bgtbeZwm7rV4rtU4J07Kdl4gAvGeThXsd4PoyzMUuo42NjY1BWj2CHXaqw82NBLur2TsahSJbhFmL1otTq12v5VEplV0PAEFg5AlVX2KkItf+xrJLa1RBYCQKlOqpyPMqlYQeXjc8LI/+4MyyXXzXiSslQDd7V5JGPSVGoDOCpfPJn2HmFXeEOu2pKtV68WFN82XAXfZSwPN9++637/e//1S3ML+Cw7AtQ3DV5GnygpKuo0oFZIqMwZDh5gn8Aj1e243KeHSblec/8aT7k1Kl+KAWbsDvumHv5lSvum2+/cddv3HD3sdNvYnzMnwtAWSW9Zyi78CBzP84E+OQnP3XvvPWO299HU5jMSKVhDnBBCcDgECLB9mMIrBwB1qCkgkl9KqxXa61hKe+Vy4YUKl4pMeLwQFsof16ucaK8eA0LDNVb5srXNOuycrBJEro4+6J02VRZruX8RczKEa8yrnJMVpnBssmkTVyWygh2JgJ4B8Lsoyr7oqGAQDI3OyfmqqemptC8LGHhTTBbDULp53cmcFZqQ8AQMAQMgZ2GQOggZfSIHXbT6BefPHriHj146EZGRkRhx76UO5HEhWFmc0uL6+3pkT+esc6FMKWcjkzjoW8pWgs3BKqJAOuuzM9wfBfqOvnrvV5pXYoKai5WlzT8LmaVZ+XFXzoVo7WZPMipUqffTitKVClzozMEdjQCVVfY2UTD1q1P9uyWe3alOiHt3DRe/eSnYeV4x/RKlw3L+pUue43zqzRNlof6mT7mF4frfV68xq3ndbmycVu/0hROUS1g0DI9NYlddjjDbn4uEVJVWZtVokSQ6CYdLEEqFMcPqLykC+HjgrvgFmAC69r337t/6e5y3d273PETJ11be5tMztEECGkED4WEebCgwhO72+hCZtoO6JVR3AHHj5lFmODEZw3MY7a4U2dOyd+ly5fcv/zh98799jduFGfbzeBjaMalZrhk5SKZwLV3tEOZeN69/94H7uKFS66BuwSx8os791g4wR5yeDFV2MInoqGeY6nfwjSlqDY7XAfSlCPGe7PlKs4/RR2PKbjkJvgrxbxUOg0vxyePxoclb3kqoAqKa8xT33RGx+H0l+bPWO/iNEqvcVm/hi9zjVmCVBXsGlzANQQyjLfFNBoSlyY否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否8niuRtQq0EEsHJHLlbJY4Xr0ZKW7/p0KKV3WsewmpBm7AVWXVrOQdCCLHcVSU0URHb2+vmJak/DxHjvbzWQrugCM9lWmkbRQDB/OiVJuVQ7T97jcf34BJvRn36PETjxXS6dl1VMJxxxzHdLTf70Xhan4eT0c6/PKsOjiGCaowx8krFYfkf4o76375S/fxRx+6A319iONZAKDBzryCdzxbXdQfyp+uBZPc+FPC+YSaPCUKjJYdfqYpttNdAdbrVrBi1IuzKkeTjcv6lZs+S/qVJi9M6Utd4zSeRkOUa2FKjS2klQqvhJmEGa9SJdeYYx5tHC+JMkQZr5AkacKN7EvNIyykzvFJUPqTME6Diu+yRFm/byeYTkVSCvV7nhqqOWT9DM8LU3peY46laP否StHGvLM0sT/mGaex+62PQN6z1WefF1eNEut4RPMhz/iefubNP4b7OPaZNPWtO+w4TlDH8UE8ptBwuxoChoAhYAgYAtsJAf2e53dq1jEuPouLJDM4+/XRwwfu9u1bcvQDj5ygkx5WPnZhOaa52fXgG/zIkcNu965dicJOvlXJk6aqJYUkZSbhhoxyBElj7c4QqB4CmarGWqjzKRrFseAcFHbPnz+Xs+w62tqxgLtDdpWSXupxqL5SdUPCpEoro6iKL1eAjZmTWE4Ki9+pCGyH+lezCrudWqms3IaAIVBdBLSh5nlr05jEoimABZztps6PPXQEoqG1deVAibsCh54NuhGYqxwcHHRjY+PyRUFlGs+q4yCrsQ5NOmh9mf1OuPpgrpIKtSUo3aanYeISirnFYOayoKTISP7xinSSL3Crg55OJv0iYkEMMpFvV1eXO3XqlPvoww/l78rLV1wbDu/mii06fQZR8mVuyX0Fo8FluFm0IVALCGyHWr3WlnI7YJDWpbWikXKyO0NgdQiENwrd5STMZA9ijDA0NCQmrZWf77utrioedjUEDAFDwBDYGQjwOzZ2OgbVHnF2dg7f1M/c7Vu33dOnT0WB19nZJV+gXmGxKAq6JpgO3L//AI6W6MWupFbpb0W5oYziTPReGKjHroZAbSAg5zbiHMZbt266bszftLe3FQpWVKeLAgrpzWcIGALrioAp7NYVXmNuCNQiAvHoVTthvVYi70poK+G3cpolrGZTx8F4PCDPSqdx3JE2PTWNiSwo7MI5b+Qhk1k1MKimHInT4iGI4VPYUcdJOJ43NzT03E3hsOCYnh8Ncs4czrCrlwJT2eaVboyjok42FYYssOk否MH8AXhLtpo3/UJPXaBX2AlNIli4oXzkhb9WfLjQrj/Pq3vvvffdyZMnxXxIPRR5saKOfCUNkoGzMJIw3oXyarElUom9p+g3FCUTrhzyYzPE5t0wBPS5MMNSz0ZpsvFZf8yDaUqlIx1dnF5pMzF1gSZqVzxF+ptw0Zs4ayULcdrmJK9VnK2mD2nUq2kYrOmUbbmrpi8oZlECpYoFSZEpDPWJNUXMKi8sjs+7T9Pk3eWlYFgsUZrOU2uchqs/5qVxcRjvK6H否1E+pfhq/Pa8al8Tt+fbs6SVlKoW6gBlQO/J/noJO9hDlZ3GTgGaw+Q5tDynV50+P/Xb1RAwBAwBQ8AQ2I4IFI+f9VuWY2v0nfyfGeZxTmB0eNQNvxjB0RCTsGjjaeV7OnT5HP+0tbbh6IceHFOxyw2Pjoglm7h/1W9bPx73CUPy7Qi1lWkrIcA6HyrjHJTUcjzKvXvuxLET7vChw1FJfC0ufo8CSebdiRLarSEgCBS0ifqBYtisCQG//WFNLCyxIWAIGAK1hUAyaA5icaA9D9ORXIHuzUmmJi9qS3JIkxkMseObh4mOBSgcm2FakufZ7d6123VhBWBLS4trgPlK+QBhQZCW4zGm8cow+BgGvzd5CTOa3FlHqpCPdqzEyO+qSyLcEnbXeYaedzJhi2CaumT+3Fn3wQcfwBTmr9xbb77pDhw44BoaGwuUdRRN+MiN/RgCOwOB8Cbpq7YzCr3hpURjZM4Q2EEI6PiGEyr8UzcDc9cvXryQv1mcTat0Gm9XQ8AQMAQMAUPAEPAIxHPJE1gQ+wQ77B49eiRzBXLWPVe3RkQtra3uMMxinjl7xu3bt9c14lvXnCFQ6whEw0QRdWp6yt1/cN/9cPMH9+z5MznyJCkDx5WJx24MAUOgFhCwnqYWnoLJYAhsKAJ5XbFOLWfjsv4NFbRkZtH4WWjSSStOUaEsWpzAgYomnls3jUEKTQHIQFy510IRKbKI7QWnmUsdMVH2hsYm14UVfYcOHhRTkxMTE25gYMA9uP9AVtMvTMM0JXbMNcNkB8s2NwsTmdhVRxx0VQYVcwtQwFFxV+QkfxEAtvj9JKBgih+Bh3zw4dKAP55Xx3PvOjs73blz59zHH3/k3n/3Pdd//KSctUd6/iFJ4sgrmTzEPePFyYOUU7c0IFwTCviVk4bpNZBGFykzUwjfKMJuNxABfV7MUp+VXsuJUY6GPMvFx3yzdLE8MV10n11uG0WVvEU28q5E7KPbJJmEkZYh/FGirJhJipQkCpLbvCSl2OmmQSZcCu9DHq2GZfPS8Lw8s7SV+ZWjUudxVpo4jmGxX9PHtNn4OE7pS12VNhuv4eSt/OOwLP329Vt7up7PNr9OSWiIkr44FgHvM06HlZD42czDNPbkxCR24POMWyxKYnqtunF6uzcEDAFDwBAwBHYAArKANdsPws9hcZ0c24AvbvjZfw49H3IPcJ7d0aNH3N6eXteIs+uEMODUDP9B7EY6ffqsKPdGRkblHHkfnTnHbgdga0WsDQTCULFouMdqn1b99I4Lup7DWtPA0wE3juNVdF5IlnrzvcA3sacOaZgBXpiisWhtFN+kqDEE4u+SGhNty4pjCrst++hMcEPAEFgOAQ41dCCjO+ympydlt5qkBYHuMFuO1/rHq6ReZtEhQD4qylqxk23fvn3utddfdyeOH5cy3b17133x+efuxvUb7u79ezLg8h8fCxAVw64wzuJF75mwvqHeNTY04g8mK7l6EI677mgydBFKvqCiS0Z5MtffkMrG9E1I399/0v3kJx+7T//mb6C4u+D27N6N8RwHdFAKggsMdYmcSd6SU94PeQdhi6LTfIuiLGCHIKB1oFw92XgoVCGWVdptpCSKzGrzrCT9xqGeJ42GrUQKTbNaVCydIVADCFRQjaVvll7WyztHKwIwl01zR+zHOXaonfFNDWBqIhgChoAhYAgYAoJAYSfL/vPB/fvu+++/c0cPHxYrNq3t7ULJL1RS06rMyZMn3ZUrV9y9e3fd7du3ZYGMjxVS+zEEahMBnRPCpMzMrLfGQLOYI6OjsqCdQsczMRxf+vkbrf0g4EsgRDElU5ozBAyB9URgQxV2+uFomtf1fKSleRN/w740Pjs7plznq4Napcn6Nxs5yqUypbJIaBIFs5JYPcfddfyjeUnvqNgiEf820SF7yiFS1EPRxVV/1AQsLInJDX4k7Oruhq3xY+4VfCj0QXn37NkzdwQfFV988YX747//u3uIA4S52m9+bl52wtVBbbYAnouyq07LizPnYNKDh2b39OxxbfgYoaJueGQY/IbcGAZuYgYTUFAathf1UNbxujC/6OaW5mHbv9ddOH/BffTRh+6dd95xp8+ccT29Pa4Bijw638YwNf6xzZFnE63WKn5UTCVp5TkQBPWGUF4Em8ifd2vtWx4qGx1WyZMqlknPpaxTTVgxSU5IXl5aefLiGKbxOexWEJS3KS/JMbkpZBjnnJDkBhamK+VLeOQQlJUv0JdNn8OzukFpwaUJBnOvgGAuaVyaZ15YTKul4VVp9apclCYbzvhycZper5pe02j41rza2HAzn5vW18I6VVizlCaVU/pn1HPfs/qFMrMzM3JOL+PopP+G0k5XT6ep7c4QMAQMAUPAENghCLB7jTpVP9bkNyqCQ/gC5ggeP37sbty47i6/9BK+bc96cCQtiepcU1MjLN0cQNwpt//A/gKTmOxvZQFfyCjKboeAbMXcLARK1TVWXa36MQ3H/FzE/vz5czcxPpFYnfL1t7gUMZ+CF0m4K32cg4bZ1RAwBKqBwIYq7KohsPFYHQJsnOlsYmZ1+O3cVOymSzkdBpSK38hw1m+VNQwacJHQMAvM1XM0Jclz7Ki8q1XHAVMdd7SJ/H6VPHfY7erqdvv27pXDgfv6+txBmMfs7d3rDh4+5OobG9wffv8HHJg9LJNzjTjXrg476OgacG1paXYd7R0wY9nlenBg9oGD+2G+sovb93BwNlZXYWfdyMiIKOu0rZDE1BvWNfjdeA5mN8HrzOkz7tNPP3W/+MUv3BGYDemGIpG77vyuOlXQ+bZGeKzkhw/MnCGwLgho5eJV24p1yciYrgABnSxZQZIqkFarDtRSH7g6WLS9t7Hh6vCrTqqV1UdVyMV564KkGSxIUscddqasUzTsaggYAoaAIbDjEMgM92Ovjn+ICRfyPqHC7tp19/DBQyx+mUqg8sct8Hu8wXV2dbljR4+6vfge1zPsOM0gUw3oyrf+qDAptt1sEwSydZL1nn8TY2NudGxEdtxpUf0ia/UVXgv50GfOEDAENgKBDVXY2e6HjXik+XkY9vm4WGg5BLKdcdbPtIXddzlu6xfn5eJgWSd//eSjyueVR7Ozs1hJNA6lXUZht1lFELFD5pyvg0uCvNf/olANONi6pbUFf62ywo/vcwPMUu7Fodcvv3zZDQ4Ount37rprV6+JPf25hUUx3bG3p8cdO37MnTp10p04edLt7zsgO+w4iTeEHXo3b91yI1DyDQ4M4uybKRnAFWSN3Xnzi/OQq87t33/AnT9/3n380Ufuvffec2ews64D59jJRwoSLeKfOhnwJbjiRuYjQyGFSErqy0u/RimzJCBvatKnTRNprnatXQSWf2bJo09qhVYKvcalW55feJuQKE6v6WJeG3MvOUMUlUbbKpZ7pVIpj1jyUjyytKXoYl6V3md5Mx3554WX4qnPnXjEmCh9LG95vjGlps5ey9GUiisVnuW9Nf02NlzuufH5l695xRy0zsTpSvFR2mIuGqJclFKv9frygHAG45tJKOtm5mY1mfTn8YRkEmE3hoAhYAgYAobATkIgdJzan8ZFZxQVdiOjY+7+/Yfu8ZOnMC8dFHYYmIrlGRBxvMSjJLhQlcdAtDS3xGwwUvDc8/IoIDSPIbBBCBSNATluDB9bs1jIPo55Mf7x2BUG18PKEz/keM96jFvvkkodQjRCwpNIpbarIVCzCOg7sZW+fzdUYVezT84EMwQMgQwCBT1xiMuGqZ/Xze2s03krv2rIN8YYWIt5PS/nHCayxnC4Lg/YTXbYcfAdEmsDngFi/bwqdICOUoosCGeQmqZcxKiJq/ha29pECZecOwcTlQ3Y/XZw/0F37uwZ7Jo7IMo8LrDnjrq+fX3upUuX3NswW/nOu2+5cxfOwQxmL1ZTjcPkxw/uT//+H+4JPkp+uH4D1ycyuZctLFfoc1fdnj273Y/f/LH727/9tXv7rbfdPuzs4+Hb3JnH4RwnDuvxj07GgSiMKO3ol9Bw4x+FhkRXpSok0NCIMLolbXmKiNhuawKBaj+zavNbH5AKa3Wm1q5zFY4RyspR7dIq/zjPteSh/JRHtfgqP7saAuUR0Bq4kpqnaWLOGrYSPj59qeYhGSvIrMqSnF03MTnh5ubnkoy5MGfDxzVJ7nZjCBgChoAhYAhsMgLaiWo3nBFHgvH5yjPc6Uax64hHTgwPe4s12KYevmdTBlxEu3vXbrd/7z43MDCAoyXmZTe7ZpXJwryGwKYgoPMxBZlrNUZlpZKa9ZxzQZwf6ujoEItJQh/oaDepyBUEWa0vwscCDIEqI2AKuyoDauwMAUOgVhDgIMKPKjhpxR12YxiIj+NvDue8xW5Thht5mYZBkNgRD/GcmOvs6MRgqkcOwebOOrrFpQUo+GjyshG73/rcyZMn3enTp+QsukOHDrqzZ8+5N15/w7354zfdhUsXofBrdUPPh9w12Of/+quv3Xfffe/u37vvRmESkxN7zKcOK6tUUUh/R2eHO37sOA7YfsV98skn7t1333P9yKcBZkHoeE6W/8SholSC8BMKoV69Mph/CZ1GlL7G5OkZZ0q/AkaaxK6GwDogwLrPd3Fjndb/UhlXEl8q7caWpDi35WQvTlEYspL0pM3ikBdWmIP5DIEsAqocE2VaNnItfu1co0ZGaij87Lu5u479OM8iyY5t1pKtpTUEDAFDwBAwBLYzAvyO9QtM/biRx2e8ePHCPXr8CEdFDLv2tnYsgm0N38aehoto92FRbH//KTcI5d7AswE3FZmj3s54Wdm2DwJcvD74bMjdu3fP9fXtl0Xhzc1NyTSNH8uizsv/9MiT7YPA+peE3wVV/yZYf7EthxpDwBR2NfZATBxDoDYQ8IPSQlmyYVl/IfXG+VSOwklXneNSOTiRxTPsJnCG3QJWw4kD0RImvDbFZQWE+DSnQWsEdLywZNzltmv3Lpiz3O927dolO94YX4+db4uLC1CU+bPl+vv73c9//kuEzbtjx47KhwTNVh4+cgQ775rcnbu33d//wz+4//k//qe7c/uuKC9f4MDhaXxkcDDBc/DoFsgPebbC/CY/Rn72s5+5X+K8uvMXLrgu2O7n4IP/mIYycl8diyKDEn72BMFJ4x0C+F8iEBLifVygYZg479eUGlp4LR9bSGu+2kCgkmdWCY2WphJapdHKRX9eGHkqjfJf+ZXvgFbxbGrNVcMltyjLbLzSlb4WpygO0dSM8ZmlNOmdUlXjuhauedgV8lNfBFyB0BpfEJjxlKLJC88Ly7Az7zZHoPI6oMq6tI2pFBr否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否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否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否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否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否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否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否9daVqPpy52ukzyrk3Vf5al0K91XabPlNQRag0DR91S/4UVxraFCSyU/01rJT0gJeaJQlDA7BxkGygpM4nHnkLjAh5hWvw9apvmGQC8hwP7Nn5jaO/TXW1DQeXPtZ7JIjNYQaEqS7w2Vd1NQgE1CRkUm2Xl3iNX9x/hxPMCz3bja/wS77mhGkwvRODY4RTh3sp6GNFykpnV7n6jq24pLeQcpj/PHW7/LyC+4w6nOA960/QBMxw9B5h0MO+UGYb2hH2HcNccwns3HhW40G0+5mBP3u3zXYVZ+F8rGDZi7pKKOu+u2trYKz6cjZfr1SC55EZHr40lnCMx6ks3+GAJ5BPw4Jx+avee7cuOGtwrDd4XvQNEZa9lcdqevJzHjwuJNnD+5urIMhfxeyteZiB82/KjM58LXBzie4unTp8DcLzzm96eR52SIGwLNRkD7nu9/2qNRCy71TmdN2M+pZN7Y2JBFJ56v+nkl0sVuTkdLSCIFw5ewXOdO0vnkF/yrVF0wmyXvKALsK3Sm76h+DKVV2DXrYTWrnGromhtSi07tvEW11cpTlNbCDIH2InAZVss8tZlsvVgOxPdxMD0Pp48HEPz2S4m1i20JLBRu1AkNrB9hMRkc7NAspj9HTlN7n/n1/fbfgFBANlm486XGZSfJIjo0jOnEZGTIoEn8zjIHBWJ/EJ5Ig5PVwW+99ZasBr7/8IGbuXvb/fh//Mh99eVTTEYMinnJOdjk/xuY8jnE6ug+TGj0owzuyhsbH5M2c0HwTZlA0否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否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否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否OgnlSJjJywi+a9GOUFpckK7jIQ6B51CchmoZ77rzzsdjIWOVuJDSkkWyH7gjsH6ApF+e++N1v3X/9r/+3+/GP/xoTk8uyYpQmU1jH2++85T7++GP34YcfuluTU7JiKq5ISlZSQgTLzAXFWaquzwLxMmaRWM2d0l2V6VIB9crTOi9VsGUyBAwBQ8AQaCMCypOr5Wh+y/mtj7/p1d9+H8LVvz4d//Ja/9EyAE147+zsyI8yDHcg8Mdrzccm87qaDsaYMwQMAUPAEOg1BKiAkR92Tt24Ae6RDmBK11Tha4HPkTjeU6F0DP7W19/vRkfGcDb5cMYkJvlZzONK16gOE5TOd2BMHYkahwdHbhNn23NH/kD/gOsTc6l+Bz5NYlJZd+fOXTOJ2eHnZ9U3hkDazyOJmnIyfuuwxPTV06fu9dffcFOYHxoLx6WotC2vBW50vip9TShd02nKxmhJ06clNZrT0hkCnUDAdth1AnWr0xAwBOoiUDRhpWFk7jSHyckvrsThhBcdgiMpQIJK90cGLQmdXvTgOXYUyNmezA47pPMiiGZQ/yr否jHMrEHMy5XGMrwJF85l8hnQ1Nen//ip+9GPf+w+/+fP3X5l3w1hxSXd0NCAe/8777s//rM/dd/69rdkQCcRoUlsZ17c0vuiOMl76T+N4Nj8Wi9NrmU0BAwBQ8AQ6BAC5Be1nXIK5VdMqZOTKrMwjKai92DuJ+bxcTzTqNP8em++IWAIGAKGQO8iIGfY9fEcO52A7o62nsKeMy3dcNc4F6D4HXYwiYnz+Mw1ioBXwskoWCYyfD7Ob3Dh69LiktvdwfleoTjKDZOTE+4Rjpe4e/ceFHZ+hx3DaVa1xPreRgGxdNcIAc4frcMc5pdffumePXsm5jFjefo8KPS9yKbTeZ7i2GxauzMEyo+AKezK/4zOpbDWoP/cjJbAEGgqAhdhsRevmMMY/uNkFg9ipoKrUqkkCruLl9jGHDmZIZmQC5BxMq+yW3FHPAsAaVVm96o1JgoJE0n8fKzPT5GUmpZfAEm+HNIuuwIwfLiJAdoJBmxfY2XUj378I/eTn/zEvXj5AoO3Q7Tl0PHcHjZ9eGTEvf32O+7f/JvvudeevO4GYSJTHAvPV+BjLvA3B27dnEwb/+omtkhDwBAwBAwBQ6ABBMCtwcuEpeHCy+WeuZFf7uztgC+mJryVjzZQsCUxBAwBQ8AQ6EEEyAd0vHeDyhb8yq5xieecaGVlb3cXxx/sCu2TU5OiTOrrN4Vdw92VfYBb6+DHw2Eq7LgQdnFpEfMd23IEBcvkefBUjD54iDPs7mXPsOOziZ9PwzRYQkOgDQio1Ym4KsrHmxsb7jlMYs7Ozsq8nr4IPn2c2l+LnF0dHIXEb1IUnLm8yNxRJqPdGAJtR8AUdk2C3AtdFLza9wEwxtykh2fFFCCg7LARpsfsjabTtHF6VaCkcfo+MUQdTYawGsbJDrvtvMKuFmvXEjrk1/gkxEI1z+SjcE4FJE1nUanEdnqUFKvg66xgneakObySM0wjIofGFFwxir/wOMQyi9KucaFOCSYdcByoffbZZ+7//e9/6X6FHXb7+3sYuGHXHRR5ff033dTUhHvjzTfcm2+97e7de4Dz7AbYuoyilZ/NzKczri/QE6qOvOoIgUbI0rhsQb5fxXUxXew0n4bl7/PhGh/7msZ8Q8AQMAQMgbIjcDFZWr/1uVZ5dig8VMujT35O8900e723t5eMEYQXoQj65gwBQ8AQMASuHwLy/edYD7yCO9S4CJIKmTI65WsxbSc8Tx5nle9gHEi6p6an3MTkpLQlTmfXEA2AD38Zh2cvsgAXt+ZkAR6Xsbi46BYWFmSBssb7vtKPsfW0m56eljG1lmnShCJhfqcQ0H6e7+uc91En16G/y4I2fD8W0c+XlpYwD7aPd0FT0g838BjOn9YRp6p9zfz86XwQfd6bMwS6BwFT2DXpWeU/TE0q1ooxBDqEQBmYWU6wDUhQaOVKdSqKaIqDzF4cSBZm3iHEGq1WhHOROLzQwXxsAyf1eI7d0fER2pHHvxgLX2e9uDxV+XLz8V6kqQ5FCLOCLn7r+jGoPAPNh4eHMGHwtfv000/k9/z5C3d8eOz否SD9LQwm/uAP/sD9h//w5+473/nAjY6OSNH572V+DNM8WUrb631fL/G6CGZCcu7PVfPnirNbQ8AQMAQMga5FwLNs5Teet5Ovcdf8BlYP07QVdyKovMKU1Xy+a5tvhBsChoAhYAhcAgHyAR1XcWyVHx9dosiWZcmP1WRBChaa8giEm9hV5xV2EzmFXTPGXC1rUmkLruxV3ML8vOw62sYOO3SMDK08125ifAIKu2CxBrEyv9C8AXSmPrsxBBpBQOVa9bN5RPKVuSQNZ7qjwyO3IuZfF8LCtjCvx0Rn2X6few20mAv4qZx+gUyW1BDoKAL9Ha29gcrjF77MQgybUnb6GoDbkhgCEQIXYWqaVhmr+hoeFVtTmGRazad+nM9f85twCJOLXK1+CIWdfiOkJs6a8VdiR3qTb0VoJsP2Dw/c5taWrFSk/f9+MSkSEtSGI7SUCS7abl+o4se9eOrSKw0hrFgTBRmqr8/Hckfgy5cvcW7dP7rf/Pa3bg02yLnacngYO+hACq9v355xP/zhD92f/dm/gynM1yRc259gEKqgEMZ8iYuvJbAqIEmaXhSlicO4wlFTx+EappEaV+te08e+pmWY5o/jm3Ud15MvM65X02mY3jeSR9否Xt5rfg3L32se8w0BQ8AQaD0CCe9KP+qtrzSpIf8d5MIb8vbsWOAY5q6psFtdWXW70Q67pBi7MAQMAUPAEOhJBJQ1ZcY2BS3leIg77NLxkY6HKGcrrynI2K4gNiA0gm3S9pBeLtzkIto+mPMcGh9zI6OjkcIuHXNpnnaRXLZ6VF7J0AUw5emqz0gAxafOeQ6axFxeXsL5t5VkCMbdSYxnnxkcGHLDg8PY3XjTneIfB+mF9bBcc4ZAmxAo7IPs6AWfMvZjpj8+PnHbW9tYjL8jZz1TEc1vBqK9g6+XaTF6pTHaQA3nfRwXh2ta8w2B8iNgO+zK/4yMQkOgRQiUjXHFTDVi0mx9wrHDbrTKntvc3JRddrpi3YMUb7pvEWxNKDZP5SmkEprDpHC+trYmAyBWI82OYImVatVkRAmrI3Mh1WkpGGVdHOAFqtPTE8hbPpz4//KTX7q/+qu/cr/67FduC8pGfU5SFqoYxcDtnXfecR9++KG7d/+eHIjN51VdV7bm8t7FmJSXymrKqp93dZp6IVfNX69sizMEDAFDoLsR4IQD+Vp+ooKTmdxdNzc3JzwykVd84u5utFFvCBgChoAhcGUEbkLZ1d/fL4quGzxTIDPJfOXim1pAPA7lDjtau+GP4z/u+BqH0o6mPc2dj0AsL3CUxTG/+PhbwQKfeeywm5udd7s4AiROy5Kp6BgaGsKZgZPiM0zSdOswlQ0w19MIqNjLI1fo2IfVUTbe3tlxyyvLbhv9XY+HiZJo0gb8tNw0sb0YKRZ21W0IlF5hx5dZf1cBl0wsz+yuUp7lNQS6FwEyrfMYlxcZs23Mh+l9LcbIOjRNXFI+LH9PJu7Tk7mzDLkNYWTqXG1GBRGVXMkEWKJKiuvq/HXR94stisOpBKN5T55zs7Gx6Q6xKj9xaDfxYHoBIvf4fJyP0r9J3nBBiwL+hwuPZj6J3Iswxb4hB9jBC1UyFz6gci7dWVjBt7K+6v7p81+7Tz75BAOKOalgAANOPg8qINnG/r5+N4jVfwODQzIQvclzCAvql3rlWQsZ0R9tLOpGKH9xMo3VDPl7DU99TVFUSr24tARPQZxf44rya9xl/DxW+fu4TK07DtNreXp6A79eWk12mTRKX74+LdN8Q8AQMASah0DMQ5tX6nklxd+59FvneZgq7dIyOOnAHXYryytudyc1iZmmsCtDwBAwBAyBXkSg5thG2UhoNG+5Q43WVWSsV0YwChrD8R6trdDiDceII8PD+I1IW7QJ8WhCm51yTk11fX0/P+lHtyLTcJyM/4c4HmMLO44oP+zvHwi+ghIBhWNaKuxohnR8bNz3nYLxtU9tfw2BziMQz8VTASFLE6KPAef0eI7d4tIivim76bw900Tp0pb4CJ3jSsPjq/gLFIfbdRkQ0D5hOpr6T6P0Crv65FusIWAIdD8CQfqs0RAqs2LHAQJX81G5tYPVOH4VTkghSbPp47yluU4ED08rz4PbQ1u4Cn9hYR7nAVRSQaUDROcR5DiNKya5ApSDs4WlBff5r3/t/vnzz+VAbJoy4OCB+ahs5MBzZmbGPXnyurt165acecdm8FlS4Se2NS/Qrjw9vixfQALlBcqzpIaAIWAIGAKGQPMQUC6lvi+ZO+wWFxbdixcvZOItXWDUvJqtJEPAEDAEDIHuRUB32PlddjKNjcZwdJPlJ51qIamQidUcAeRnnGTnj25sbMyN4axyjgG9U/rVD8HmZRDgGNu7dDHvCbClqVHOdYjyQp否Soy5aX70wf0H7v79+6IozSWxW0Og3AhEEzj8lmxvb7unT5+6L7/6Ela0thqeB4uKKXd7jTpD4JIIKEe9ZPY0m2pI05ByXcmqFW4XMWcIGAINICDieS5dPkzvE0kzSs93rbH3zX870r1T+pr6xXxU7vh6tDSm3z/Yx1lvO66yH+2wEzKwswu+po0I6thl4bdRmoQ/QrMfCHHX4DJWFq3AHAAn+dTdQHv9P5+c10nGcBWHKPKCQbgRTJBIzRD4skMuTQPfY48LWa7k6eJuOf7jN5RnLGxBoKKy7tNPPpWJyDOc20PHNMcnx7LDbmx83L373nvu/W9/WxR3HIxSUUflKn8UzIgLssiPbbyBeKr8SE6RuwGa/M+3I5CNpBpOullmWm5ROdVhaUnVcc0KybdK61S/qJ4ATlFUEqZptJzY10SaRu81jYbrPf2iMIYXOc2ncZqXvrleRqDwm9bLDba2GQIJAv47l//6+Wj/7fNLV3zIERawrK6uusXFxQIT3kmhdmEIGAKGgCFwXRBQcTm09wbGSFwU2Yezy+l7aySdl6U5JpRxIcZVMhbk+AqO40E6juUOD4/kx7EYd3zxlzGJqWMypNdmd75lQn4J/2AkDXCItToukqUFHmJNl8bcwJnxQ+72ndvyG8buRn0umlf9YnlFY803BNqDQPI9QXXsx/pj7ZxD4rzeixfP3fPnz+U6eg1kbkj6NwrhGNTPTTEnvzx+t57cyJ/0LfGxvf8GdNu4XOnlM9Vf+vzsKo9A0xR2WjAfgDlDwBAwBC6KQPWnwwuuIr1KYekAobJXcZswHbm3uwchNvrmRJcXrb+96ZVQ7/O7uQNF2KvZl7LLjopIxUN8TR6IzN22hHTWW1T36sqq+/k//Nz9/U9/6uZhCpPKODksHVRQecf7O3fvuO99/3vuoz/4yD18+ADxVNipMi5QL4Ur6Y21yPcAnye+1lKu5je/xCw9zS6/McyyNNidIWAIGAKGQGsQwMAzVzAVdjyblmfR0CKAjZFyANmtIWAIGALXHAFOWHLspOMp+jI5XRJcONrQn0yuBrrIzw4PD2QnGFMMQYE0DJOY/f0DCeXG8xIo6l5wSKzD4hgz7rKj7CDnBLKEkIjKD54T/wBjbP5GsLPRnCHQDQjotyRdqo9ujZvtrR33FXbYffXlV25jcxNd3Sup48Xmmrcb2mk0GgLNQqC/WQWVSbBoVpusnNYjEAsl3dqHtA3dSn/xU46nncgem+my5aW4VU92cQCgah6lgEqhQ9hzr+BQ2gOsPFPhVeNL6yukaH4imIcw9iEq6ZaXl2U1/jEm+SiMqzvj7rLoXsOL/BjdWMhhWqJJ58tCgahX+28SEVck8WFVHw9CR3aesfclhKkvvvxSzBfIABODSykZRXJl5d1799y3v/Nt+d29e08Gb3zOg4ODoRquFfGrpBigfYC4pFTquijf8DM0htHaJqblRkAhyifRAJ+RUcn0qRQsIWlYuE28ojRxWJLwEhf5cvL3cZHamDis6Pqq6Yry58NiOuvFFdFnYfUQ0PdO+369tGWLazfNihVxaHfdZcPe6Ok0AuE7mPscsl/eEOaUjeCqeJ5dxwm34+Nj67+dfnxWvyFgCBgCbUJAh1N+bFO7UloZ4XhM+AiVdfjhRsJq52p9TDwCYG3C3fgn0EbZ7OjoGL9DkH/qeI45d9jRL3Ixd8yXXZT+OoURUu0v7Acq99IqDU0Fbm1tucnJSXczJGIa7qq7PYMddvgNDWV32MVlGNbXqSeVua3s5KBPP4j02Z8ZhhkemtZdxOK2ublXWJAfnWHno8OcT0iOMMnGuEs7LaH73xC+77HT70c+PE7Tyeuy0tVJTOrV3fQddvUqszhDwBAwBKoRIJPJMhp否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否aGUgEBAICAQECgGBMiGrMdSrFAF3LJTdUj+inwZhTBF56IKKKvA+4i34hjHE4J6wg5CusYGnLCDHbuUSsz1Kl90ldxbgoztMk4pml7kEEFsJ6EekGYnzsA2fBM20FZF7whPMra0tEo7NsHSNnyKr83JKXgEBIoYAfsg8CGwTxkaGpIXL57LcQjsuP7kBHZFTH8gLSDgIbBTfF9y3t7LPNzmIrBTgOfmvL7PfpS5PjUhJCCQRYCjqv04xcy6fH6Ms55/Nj2fkX80eHMHIm26TU9N46RdWmDnyWPyZVIcfgZVRA1ptl9CoKssT73NQuA1gRNrVP9JxNgfUIRlDJKmibBJ0qfvMkWmA/kUNYXtaNqIn6rGrr2jFNi1OYGdJgdNa2DUaFic9ufoFmGLbgTGsSmwI0Px3nvfk5/97c/k3IVzuvuS9SgvP6IGtD/7/HP568cfS3f3C2H7IghH/pgL68lr4piu0D6R8chAKhOMXasUAuruT1zpbxiyvvpLionKdchthJ/SGqWzeLwyb0dnpgJ+GXrvh/PeftmI5m+lIDy+tRuLk027wXOeJJZbKpV58rqpY6a+y1OIH3xA7wt9Vwt9nw8oTKFaBxSBg/1eZ/q4PF0cx1OqpJqawgYVnCyn4wIaVUe7zlu9wp9tIBAQ3AZoIUlAICCwrwgYr0EifN7BPYNfwemplWXHA1VC4FUFEwS+Ssx9JT5bOCqTGQU1Bk8IruJHO+SNsKfGX2pxPRorC2IlsmUe4GebQqQwNU+v3hTYTUxOYFPsgNrHNQEvo/Cd4QbZEydOwAxFw7oCO/899LIOt1tAICsMzfec9dtC9ocqqr3m8buPzkHnytHC39LSsvS/7JfHTx9DcDeYbGC3TsSuQE03tSMjyzMXyHwh+fxyUwafgMB2EdD3mR3vK7ogsCsAQFt8sGsBSV45ipVl11fOMGQQENh1BDjwmfPvzc+/bhbOuO58lZ2yoqoN2oKhTmtfYEfm5yB+J4vYbTk6Nqa7i1hf3aVv+iodPD6gOfccHuyXE+h72AzexpNI4JRiKhGHaja4g68ZevKpssUmpFRdWYFTcpX40ZFO6tifmJrUBcqbN2/K3/7t30jXpS4V6pEJra+twym6crTljDx5+kTu3vtWd1BNT0xh0lUm5WUQ/mXalW8M27nQtta4oIX08HQmzLnLKurIxVT+c9WOKm111xrk+aOFkwD3M2zsqiksTpQ8FRb5JRdG3qbbjNbtZltIvhvGeYU6bZPmUk5W6HtcynUMtB8+BOy9tuvBRCB/X2dzFY45c/NzENbN6/hjsaHo+WDCsUe12nD42SMaQjEBgYBAQGC7CKzHF3C8pMpD8hg8WUehl/FY2y1rd9JxNLMRLSmB9C9D4EgTCdSQ0lBfrz/WI3E2QjreNPEPdykEDGK7RoEqsMMm3oGBAawNjMoyhBl0xJ48ODfU8hRSQ0N9LOzdqcXiiIRDf1nvm1zP/9ADtkUA2EPoxvRoskeB3UD/gDx+DIHd8JBnIsa995o94oa54RaBDtFLDgF/JC054neaYFtgCB3vTiMb8isuBHIn28lwly8sS/3eDI0sxaeGp6V4wm4KgiDahLHvNUtd6T6ncV2EvT6qlnz+/Lm0NLeowd18mzQsFeRc67oslgosPZFhWcxBUowVYc4bLz/2iZWwYdfY1AyhXYvUwiYd/fQHISKFcJoN/NguM1DlOTDQB3uDM2DaGuXixSty9tx5qa6rgTBv否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否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否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否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否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否Td7NxQZI6LMAUMOBa9JK6sdtzbon42Vmw2nyiJypUkkfYiulaxJ1aF718bbNddxkaaC7AXy537nMtwixNt26lK7JKscyA6IHB4EGDfqnN1XDmk0A4c+914Il8UUKR6HlBko4QTIhmJ7JPIN5APVF7EAsJ1WwisN+5SMESzD2Njo8pj+/FUJWYksBscHNRxwg/fFiEhUUEIsF146osCu5HhEV1fYEJ+F80tzfLG66+rkHVuflZ6+3rBL6cFdgUVcogj+e8x+8xRzMu5fkTNShRixw59adrx2e/D0qHhKSBQbAjsmsDO/4iUiS22mpcIPQHH4mmovXqPrc1fpTzLg+i9Sj4bo+8PdtnBcOOUujXMH0AxeSFjEs/5kR3tjXECOo0dYcsQ3DjHeBTeIIJK7njdpKxSDs7UzYcsX7U4OXw5+FJVE7Zi4ebMmdM4yRTZsluHXbL3gyfXnHNslS/qyjJaFlMPaHmEaNsokRQiugAymjxJVQZB0SD0uP/2NyOyOM8TCuPyk5/+SM6cPsuXVE5APcTPoBpzBXX47W//U/760V9lAScZ1sDs0VDzyiqEtrCNEJPplZtza4XjneJ7pY9GdDYy/GmzTk/WQSnkIt414liIc4uXVDkZqZ2MBGBMa+VyEkkhoLNtB1RBjP995pRjnxXo0tvoOfb26uHdxtm4qkd2+FC2uSS9S8Wzl0dICy否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否VTp8+rYuij9gTtZ1DO4Z0796GiKmdSCOVDMUkr4vpwnTLOZu8QiM/Y+QlyaKeo/aVO97u6nLqu1oE5p4BduXd0om023fuCKc71g4mJNyb1Ok78EA4O6NTa5xcYxcfT+6Lw21K38Sf4+HlDTWNBhzrnzGwLhxVHH7akEkE9ciyrAjIl8dpO2qTALQx7viV0zEt+kztqOh9cdNU8C8eUQlZECgIFATaIJD6GevsrCM3f5PabGD9tS64H9WGIu/f8FYZgubdVO5WeerOF/JhY6s949hm5MvSxq00tni+uzMXq4kqKhIlYKUwK2Oc7t+zZ084c/psOHzosGkFqDwpKJuxGLsb6mPlobwUBAoCy4FAQ/9pHW5NBf0sBt488t1R7X7to5vf6n6qol8bGXkvZnEQAErDE6j17nWJ+R9Cn8iX7LAuf5M2cGy3O+wGmgR2i0NLiaUJAa/mdTOQJoYRzeEfa/6eb1SN4bjT/eDBg+HAgQN2rQTadtgUVcyLIUDf+Vgbv69du6bN1ofCzqEdtgG7js0LqLYpbwWBbkegCOy6vYRWAH3OKCw2M9apeFcApKuaRB8qnZdJ/GZDnin7canEtNM4j3W6SCeSiskQqBh0RxE3dmdKwDL9LDy4LxUM1xGI3QnHjh2rdxhlKzYsw0WBy+yd9FlKc756G3UqvGxjoGjL4h8qMY8cPxreee+dcO3m9XDj1g3tAnxqak8vS0A3rlMKX3z5pe5D69VkY9TuR0MlZG68f/E0tfxkqhztXhZ9oN4RgaW752H93dxEz9reHqm47A/bB7dLKLfTFrnOnD2r+/T22443cEQF5paBzWFw+2Dox68mPFu2bNHpun5否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否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否rtzl0wxBXtIY5ooHTDCUmT8jxtiDreytvHAH+8x/1VaI5Vno04Rt7649YXD7YDh65FiYVP0YGxsLo/o9kpDunu48uHn9Zris+wQvX7lsd+Pd1u40JhmozkRwHuuO0IrJWFkhSOck40adctwxNGgnG3fv2W3CuaEdQ/a9VapYBnfssJN1O3SfIvZMIL0uenwxk+SsnXGXRIAHsKfbNftp/saz21nA8qcgUBDoGgSa2/FSEOb9gacd06R/outd2xN7YvrbSY0ZoyOj2sTAQor7oxvGb3M8/u0+u+wp8rzv5Qn5rShmzDVNByLfFwC7LCerkhxqo/MAsALUv7qOBdMasFun1Q8fOhx2SINCjzY1NRt4AhbL4Z/Y+NRg9J3H1+BWPgoCBYEOIOBtsKmnjY2dBh87YcYT9bu0z8jzw3fPbt8dIHBhUYpeBAv0TWYYQ6pxMFr5BkOexSweAg6zP4mZ8fmx5nMP7j8wQYWf0mRe3buut1ElZh6QscBJy+3drjwXhIC1hwxH2sQT3WHHlRQP7z+0NQaYL28rNHs21ew/sC+ce+1suHvvjl1h8Wy6XgPz+b0T4mH9+5V+VpU38kzgP6WN2MPakMzmY/jXtWy8lj+6IbAkiGNY9V/WARPH/E1zucw/ZGd9ditdnc31wmL38ifUQvBaiN+FUdTedxHYtcemuBQECgIdQoCB0gdEGzSzb5JEdeG4VE5NSGjnJvr3UG5bnjAaUfUkSGKEkV6x54TUZe3o+vq7b8LJ0/H+uPUSZs1auIkBF/43FaQJDV否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否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否tbp/jcKKfIq8cXDEpW7pJI0G7OtyiT6cp7H3SpnC+FlrljkHTWLg9sHbfcYC0puECghsEMd5h7dnbZjcCis10kvTjJMWZ4kqFL4tWvlS3akYXTbCwtUikl/JiamxBjr1JnubXsoATDhWxm8D2zeEs5ot/pbEtrBINsC2LT8Kx7q57PqJFyKP0UEnW6IB0MdN1vCGqh86Rf/m5/6j/mUk4euXfzNGc5EgpUjcdnJurW9UvXwOPzql78O/89//+/hk09+HW7evKF7CvrtDrrTEj4ePXrUVFvukOosTiki7FyfTs9xsnDTpo122pH4MKTnY6yyYEZn+JSk0xqfdhJDrpZHwkWv1TN+NuUr/0wBPA2K7/nGI8g9u93zQy+GDy8P4nKcFiPeEsfCEOh27L2edDudC0O90bfnEdtuyGdNQ94n5H2F0x/d674nfnsf5PbetxGKvDLpf/L4iZ045oS27ZnwKPX09HNcMudleXWaSLyiC0iUZfa8RHf17nqP+feeflnILYm2QMDKTYXDoj0bnXbu3GUbnUzFZfJv/I2EepzD8fqLE+XbK20F3M9rd9gl/3m9SFblURAoCHQYAR+ZWo1K7ZKmPa8Rf0777yZDH8KvGlfaEGcjCqS3Id+xcGf/bhNdsW5CANwiZnpLG1kZD1hHuatTRWwyYtzgHvJtOl2H0C7fJPu88mtKrnwuAAHKJj9tCtbjWpe4d/eercVM6vRjbSjF2KbcbodUZp4791r4y1++Ct+d/y7clhadVuZVK0PnX1rl2/slGCFbgxTm8O7Xrl8LFy5eDAcOHFA7GIgwCnJTKuGdU9X5pFUZFaCvvTQtbrQqhmI3DwS87Obh9YW85HWi02m9EIEvGGhFCuxeMK8lWEGgINDlCNDRsivsoU5FoRLzKRKmZHDLO2K3L8+FIYAQ69Kly+HPX3xhqjEQ2pnATtEY418xLPF7YbE3+mZ+qWJrYZ5Jn/466dPfHFDPSPo+sPZoF/haLTyh9im6rUvhjfW197iQmhYWsZbhi/pBWIR+6O9H/cGF8xdM9UR1HyJEwXnpP0LDg/sPhI8/+ii8LqaYyQyGyQ6qK9b0aJKsu/RIz1L3sObrOX/yfPt7ovU5IVs7pzhiG6gjmpqe否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是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否RLaepRQ13bR3dPI396nM3P3A/vxbwMAt6XxL7jZWJanLBOz8vGRvvi9zLxeRwvS0sJPxcC3mfM5aeVG+HamRSnR506ntgjx78ekvoxikpM7VR/PPy4WvhS1bH6w9MN0dR9mNsu77Oqoxmh3kMuL2Ul9eZy8LKKRRUrFnbwLkM7d4bde/aEfngl8cRuLIzGe+00spEUteDWp4mX4YnBD3wBar95j3b2sDDxrfwtCBQEOo1Ac5ufT3rWYtVuY8udT4il8gNNLahKfUxOhfdJZhe7pczZB+LMqrwuCAFDMOvzbYYH76JTdmw4Yk7shtPWfbqOYp2ugjCjIsR/brxU/Zm7lffnINAIpSFrvKGCxZoeNx+P67TXsHhKBKvcL48fNt/oryVgbUaWaHLo39Sv9Ypz4f333w+HdV89V4BUJmtvxg/QV2R2lb/V9AJEz6ucth7T2EdxEODe3bvSLnU13Lt3T+0iqbhXXGggAjbr01QWFf6UQVOZNkIZS7XRrnwVBBYXgZrrX9x4S2wFgYJAQaBGoHlgTYMf1jljMaPd66jEnBibsLAMmM1B60jL2/MQMHzbeALba9evhU8//X3402d/0k68B5XPqGKp+mx8yRiX7LXBT16mDQ7pI+cluVR5584hnXTbbgtTXt7sCIS+3/3ud+ELnQLkHqF4Gg+VlbHe5MJcGF5O5SH0RV8/a1rEtU0XbB/SSTtO2/X2rjMmjHvzTp48ZQK7bdty9RKJ0Y1cm0Irhwit5mma5HXOnVeh5yqPytM8XnL8cu8WP3/mME5DuzjmC否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否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否d/IUtospp08Z+WzQ6dOigLRyxIYkf/ihL+4kpqO+yg5XAvmYpYlsm8hpt7NjYxII6T0VQO5a3gkBBYNkQqJsivXdtrG9I7Z7xaDn78obNAInEhmFQpCOwM5WYbExMmYJuNks+nePOzMZc1/kvby+AgLp1xhPGBw0cNn/0WCZVDtzLTnmwuZWxgPJqVLBcl7TXy4Zy9sjKc94IGI6pHdeLAnWtRxj/WGsVnIwbGxtVsW1tiHuNTtaZb/3hQqvVEQAAQABJREFUzcpDf+xkl8p6qwRMJ0+fCucvnA8DWzar7H1s92edVkPEr8pHU/b5dGSA4PHj4XBVwrqrWuvasmUgbNseBXb4iUGjb3CvoCVgMvFUXR4jDk2JuufyLAi8IAJdJ7AjHzAldYfzgjkrwQoCBYHuQQDmIlHjw1o+8CGwGxWjYru/5JOzTezQI4yH657MrCJKANcLJGUrql6JHzD2ly9fCp9++qnushsIZ8+cDQNSk9m8+OSI1KXsNi/3fPpsxnZmosqSS7KPnzgRtg8Nhl1790jAOxEeD6PK4J7GDE0KdUeCZoxhg/KzTnfErVuvSaNO4OkYnqm/RKjnk0d0xk/ZBPhpWCsMqGuocXF1UUw8fXK8Xulu6t8UTklF1RtvvBl+9IMfhDOnT4d92onVp/vxGKvwz0k81FRpWm2ZJk5Nmao6bLbRKdnOAxvlxbRgzMPrwr00FXxDBBWhDbbloyDQrQh4e+1W+gpd3YuA151n2hWhbtsMPAk7b+/euaNx5rGNHd2bg0bK6L3n6t0bfZevZUEgDbE231VpwePAR3AfzZ49e+zeYARrT58m9dxagKVuou4b07Z82xQ+fEpvL6dfuMNuXQMPR1yJHIu7/CkIFASWFgEfdzzVal6R5iLMXWj7Pla5v2V9at7l/Qb0IgRaLxWLCILiHVzIjJ7aNQ否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否qe24zvscjFrXcoZ22cjEUhXv2SBK1sGp8DUbEGs5LdYsJt2b7zxhvvJT/6dT02+CiTMJHdxexW/ZT0py6Muaatm3mXhb3Cu81OaVt05RYbroAen5/BHuynsOvvbv/2f3NVr19wrr37mHoOZ5Q5QLtS9+upr7srVKxAj0SfjY3+pM1LBOBCBxpEfyOguK7lrLmCXui1MdWQY5zHOO53ot7rR78h7xVd1n/ev33EcFQMyyy5CQNsQs9zqPrzV8XdRsXdIVpXmcY7Tv1NnPdKHZNWLi0rdMvN0xx3o3GVLkd2yaUMIOell1m2HgGXZOCIC+f49VDWpQ1pTUtqlJjyt3+tmsWB369YddwmTaYMUfRWUuA9OqZFrYc3TuJTPSNzzRS3VUPyT+0njZBg8AcP7pQbiE3b0S0uq1Ln/tl9DwBCoGYGiZqRNVGmEBApDmWKHh9Q8nK6DOEyOmakoJjedaBejpv2k9Itp1NO/fhzENGu66a5vAPQFiwQ8tUVzSi6hCEaO7bnoaKrBCOQqnpJ11TV2SjaiRCOWD8WujoyOuiHcPciT34ep7JLQYa7NvhwCKV+auiC2m2grXKzbxKKR3p9MFwnugZAkZYqJCf/OXz5pJeCC/8zMtLuK+Yzvv7vnnmETGsNVJXMVcJ+ElfGtrjpIT6HJZIrYannQgpv2FhYWHcXiT45PYEM6Fk5xLQpVjDDvCk7MlMCLCX/KRJbY24shUBsCdVuwqy1ac20IGALdiAC7sJg5EPFTOP7Pe+wooiFR0tdZh5fg0U4vYEw4ickFrrWdbffll19CLOanWLD7Szc+flomo3pxUXKzVcxIlcQNS05fcdDLXYX9GFjyNN3ExAT08251bdX1YuAyDtERE8jDMAYvvPgdy3ty4TPDrj5Hvt5W774ktYcYMGRrG4eAZNaGgCFgCJRFgOKg+OxD3o1sJgK956Cd9+7IHS/YSCQTlJjDoqgoGdAb2S2Lp1lUjwAnSa9evep++MMfutu372DC1N9flw8hmaTLW1T9Ta7HcyKcUKeEgVFOzkLKQOaEnbEUVSNqDg2BuiCA9uibJnSd+IUZ2yXvnOKiei8W9tlH8XTd/p6/v64dmiqTTkU9JB3vp2QzAMXv8Q470q6NTYj3gyQWnrDjHXztkHaf8s7/DUWUYB5jv4WTj7yfdx08zsTEpIyDx8fHkhOdLFctYy3fzkes9TlkW1/DYt08TnaxfORULZKlZckU+nc1KWZIaUoXnNO4fv26+/Gbb7onT57IPEe6YEcXxf5h0dWKc5GPHj2Sua2J0xMQKUqR5f4uuyJgSlEsNSnyZ2aGQC0IHL6dopbQutgtB/P6dDEMmayTgeNjqrsR0K6LJ5VkAiKqEmRINsHUr0McJu2T+kIuUT12N3xtkXuZrMykhPTODyDXIWv9wYOH7osvPnePHz92OxgMxEqmjIQOsEDDU1K2rBRRxZD3vFkcau49OGU06eN3r0qdC8458ar3QrCuDWAn6NTUpLtw/ry7gAW8manpZKehTyIG0HDHR3e7pjFr+hhPWNxj/kIa4uyU9A1yaQX9qwq4FFZ6jUfd0I+aqX/V1U2sq11eVzc0z4dX7pvmproBAe2/E5rcDZlGHkvaapfku1Ozma3HKf3iZgzPj2T5VO46X4e4qC1sRiEbkvRf/Cikz61DLpu31qWjW2MWNhXVQnXtNalTvBjLR+lJTEd5wu7MzBl37doNd+7seVlIUwzhBZ7xcOc83uVbLDV0dVmqJ+kI/tLToQwPJ3f6enGH3aCctIvTkw+pkl3erX0bAobA4Qho20xcSsOW3iXDffOUU39/n4jkZ5vluHgPGw3jE3akBKoOpwrqsj66zwf7QiqffnlDWjmeoshdbkg4QJq3IIVlfR0n7NCniooT7k3st9EIoFyEnkt985FRPOkayoVjdyqWmZywi9yUjne9X/utDwJsOb79pOGxbXFBjYt1GyKy1M+x0AUl/Ci14IyD8KIIgmWbPnTjxzD0Q9HbFy5edK+8/LK7cuUqTvIP0VhUEga/WO76eOuu+RXsIjrGjHNzAe/8u3//vlvA4mlMe2kvZYEiYCkkJZQQeJqaMgTqj4CdsKs/phaiIWAI5BAoXfDxDjg5Jsf/saOFbri7kE++g8wFZ5+tRADlFLMk3I30+eefu1/9+tdg/EfdT//yp2D+s5clFyaXgZC7bIjKBsy6pUxtbxBtwGhpRhnlWvf4TcZVeS8JhUYNSeNJCrShhXWSgLC0GgKGwIlEIE/FebpONwl5O/IdvL+Cj+5uZlZ9v4CXuOM7kRhYopuBgNSXXEQxDzyCHdvnL1xw165cwe5t3F/HzU/kq1gN+ZABqZNiUJycY5r4UHoAJ9aL0sgoy5nXKTkWjCFgCOQQQOtEC+Wvn2TnHZO8b1LGwmjAe5hA3odkkERxnFJHGpGEW8sL6QqTEfyQbvDkLukZRe/u7u7JvfS87oLjfCpuIDh1yl9LELyZ1iAEZE8owj7g6gJUXGW4YLe0tCQPJc2M4XTdKE55s755x16z3wYhwMJhY4fS9sN38p9sL1wkor6Pdh+r4CU2St9pGVaPuFi+C56CJ3S5GZnSkK5fvy4Ls6kHe0sQIHZRQexs78gJu6+++lIWOzPzkZE79S88VsXCUZemGwJHR8AW7I6OXcanDXIycNhHlyKg7SCenFAoaBbfv8Hvbexi5+SYivtQt6a3EQJkUJQZIT/P72iwyMHYd99/58bef8/dunnLvfH6DyTxdMIy5kCB9ULrRswYybuGLb4q/GicDLCsytpJnUM6fPylnpIBSmQl2c0G45NMi0LlB9qnZLYNDsIAKXaa5F0NC9ykIGtEcSIkVcG32mtgsR77oXklt7G/at7rGVY18ZkbQ6D5CJS01TomgfSIqpFx1DG5HRaU0q+URvq+idkEb4Ky4UN+hDtsvVI//KK/+Nu7aOWv1qdGpCFFyYfeXjlvRI6PH2aMEXfDs76wrWs5+frW4yZwJ8oM7v6lGDLPzng33g/8IaA0LHxLYXgTZR2U3YhT7cPyJiQ1+weY5Ke3cNqPNn29fXKCh1IDTBkChkBrEWDz5GKdqh5sKOzv7ZdTahyfkHZwfKxuvMvUffqmIdRXV9qloSq9Ik3yafIpIJ3rw8lALjbyBPHBwY7b3t7CnVwbsuCY+k/poZqZXh8ElKST9kuVYmeBjkI/NRYu4Kzjjl5ZFMJC8PDwUFZMMr1JIOrD9LohEPryQnhhuLmxBTGyPGEHqVMQkalKygMFTO8ShPz4YqabuLxYD2QzGvp9nnadBq8xMzMjG6qT8KRW+C/1yzasYxM1U/edrit9ZT79HXYL7umTp7KozWtUUsUyCeCnhomZ2kQll3FlH4bAUREwkZhHRc78GQKGQM0IaGdG否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否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否WbahMEQNhJ1/kXK5qR/1lZpUjMLfRXvSjTQYhn7U/6hNi9vdcZAXbRQbHvjRXFYG5ubrmtbZywyyzYxa5qe09qA否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否amZSlqGgf7ni3cF0RdVRFd1YGlDLrVYcN1plOfxkSWxaUxcXRyqNIXhwxyh/qFCxfcSy+9JJNe/f3RtQIRCJ6n9chL6abVMXJV/WtRDUnMlI1AQmNeiqHzux9pHhqGODtM1nkFD+pHdUkfP9QgODXNEDAEMggUtpKkMaZOSQNixbbIRa/JqUm5w45XRlDyyBYWU9pVcbGfUpkoBpiL/jwFtAoxnjyt5WlFOK0e否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否WNjNnCLocAiyKtO/4tLh2tJ6NjI272zKybmJyUxTofXnCvlbAkEg25xAIGWvrBTRmnaqy6+koSnRj4ODQpyaQc7JmHfe7mxNNL8XYDEG8HiStctE75LwakgWlsRek2M0PAEFAEdPwgLUebD9sR36URqkuvc/GLd8GNjo+5haUFnLB7Bkk0L7DpOnvCLgkq671hX+yb5E+avk+/poH3102C7o1gkZ/3na2srrh1nK7TfkwSpY7xkcwhRWYNS7gFfCQEWHY81cn5Q56YHEBfMDw8hA0dvYeGl9R5K99DsTrMgWCJNhdDqSfsdpPNZL4/Di006rOzoWtf7sPiRoHA2+DlFLt/dS5tnV/+Hjs1pk7Rt5QeMDU1LXN4O6RLcOrpg19MjN13wzvLiI/i50/YLbpnz56KiP2zs7MZGJJyUA/BluVRvYo91+azNI44rFLbRpjoWOf/Z+89u+TKkSxBkKEFQ1JrzcwSWbXds9Vdu90958yn2S9zzv7V3ZnursqqzEpRKUgmRSa11iK01kHOvQYYHt7z5yrCPcI9AiA9gAcYDIDBYNCGeuDeTTjjxfVGr23XNmWSBYHNU8A89cJNjynoO7916xY27H4wN27ckG8O9Pa2IBL+25MQe2QyduHCRfN//uEP5tz5c3i3APHdTE6FCcmQXdRodNLE/DUeBfJ4yOu4n52R6+O8tkk1rew2PmLRQHmx8UoTc7TlFKC8c7KLacvAEN88/ccTTHdu3zZPHj8xM9AXHvlmy2snJhgpECkQKdAUFGBXosa73biXamuWoPppEQeI1Ni+xkOqd7QjBRIKYKWG6uCO4lmB4yeO45ZdfxIGV3ZMQm4KfyngKj9CzpSxczZ+Zh0m85lAI0D267CSTp7ncwldeLOG71HFhZWETNEVKbBhCoSNTwUAkHF+zC5IvND2+rBRNzQ0KGrn+BbSu3fvzejIKN6CS2/YbTgfG4zIPLII+gvRdGJzfwgL+FSJyfdfx3GYcgEHx3XuTxmo7jBedDcKBQprlXWW3LBbwuENe/M6VInZKLnfbfngGtkH3OL6sAaVmGyYZUwgbgohudMk0icTpN60xVhHFw5IHzp8yBw9dtT04mBBHB9YmRiQSN4WHB8fN69evxYtYqoS09KxfH1ZuHJ/fcWUA4zhO5gCccOuZOUWSLES0FlY7RRha1Dg5f2KYdQ4DEc8OWWBRQZ2rBSafOT36pUr5j///d/NLz/flIGTgDINmD149XIvfvzX29NrjmOCee7cWcPd/472dguEv3Zo5QZYLq4PzHVoITSQGY1mN1KAfb/+LI+Cj8irQgwuBliq8Mo4b9gtL+FBahjybwtUCNFUMP4QuPhnZ1NAJnlgBmzfyj/PPI5HGE5VquMTk+bRw4fym4He8PUP0LlOGPljF6Ao8xJj5ZXyqfJkEp6I57ywEK6x3SxzWG7mNs+vsUsRcxcpECnQ7BQI5ZCVv9WVqJTcUnxpGP2SlBVEEg1C4K/jDZ4mX8EhEJ1cs3/ZaqN9nqbLcVFcEFFqbI/NsYOvBxws+yhvAqR5Y2Bw0Jw5c8acxC1/qoxKjDKeZTRbvwxVHgzCfST18x4pB5PXXyqAH4qWNtEERj/taMqNqRCu/hIX80OOs7go242FuS7cnNExULY9MAkate1X/BspEClQkgIq00WHmu1/2AYpY7g+w3cwuenf1dMpN5zGR8ftO++Y61AWbaVh29f2T0mh8oub+/gvhvnm+lF/3z65hbUwN28msFjNd2BVuPAwudeiw1gssBc8gib+aQgKJJXCuubNOt6wW8I6DW/WtaGe9e0yza7vG9UD否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否Im2EitNKg6m9h+WWHZHC5Ruo30jw63vqwPzgoIdMo5UtPNezBDv26efzoifnL538xly9fxnXbOfsOHeJRdYKcbHJpaaIcWPX1YcPu7Blz7tw5GRDq1EwWJTw8M2zz4LPnc8NTGcyb/nxA4JeX9xAuuutDgbw6qUVKleBVTimEZWcvv6CTtyox8Ybd2LhZwFXxXHYPsq5YaUezyyngmEF4CseT/GASZLFuyyXksefPn5mrVy7Lm3ZzkJFquGlHmSdyTzwRR/mUzKjsrBGa0naEys17qbDcCFV5pmlbVdQIHCkQKRApUAUFKKxDky/bpL9IDTQUjjZu2OFm9vzcvKjqplpl7VesLLP9RZjKZtzplAsx1bv70fQLU44+pSjAeRznPzpvIiw37A4cPGDOnj0r707xlkloLN/Zc+tc7LILXgqRrWnLiwwtrKMQ否GzmPQ7hLEh9MkzHr87uk0+1xPyybjIqrrjJiQ37Xj7Jwwj3o+uUJqDvLSiX6RApEAeBShR8C9opGxngwP98lTJ4uKSeff+vbthZ1U0C5ZtamzSfSJtyj/NMuVBR0enyIfWllYcJMctdWjPWcYByUKTznhhn1wYI/psHQVYl/ZXmCbXFdfwdA7HSaw33rDjgf/CHieJKxcr01WeBEZX5RQIiRzQk062SR2rKsIARL1ybG3BGmQTydYZdBNhPRkYEcxnmKw2否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否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否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否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否uI5jMWTBBiLT3SNvsvX398kBp8nJCYyvLSZZcwacxvX4d4FDKOPGYqQZ1cryiSCuiYrcD+RmKfqwKqSOcqs41zNFXa0v1hP7mD3sn1w0bgAfPnTYfPbb38pG68+/3MSbdu9lLWx4+ADGwgMYZwJeVDmk0NqPFJ/khFflVb4slaGrFZ7KUtsuqFIrlNuVp6ZKVxuG31WloHLMToafx871L3iH4OqVy+YtrqHqhl2xQlbEdkGDYePjBJPXkm3D5CIEJ3a2oRb0xsUSjv6RAtVSgJ1P0AFRJea7t+/MKCYlVKNhO33HrLSKMHeAotocRPhIgZIU4DTasl0gNBGDajEfPXqMCfWQ+eMf/29z6dInXi2myHIFz/CsDnqYqIAoXJgL9Qvjql8I17TusGBNW4hdnfGCccsOoIaWiUXx47EKyqXxiscJ+V0bcp5fBYmVBFGcmkZJ4IYPDPv1UG7ajG+ujBpbKVZADACo5GcY3+ChusE12PamUnrDjngUZwGu6LGDKGBrmYvONLJ5izkUTxx/9tlvzcVLlzCX6s0tLxdYVFYQwKLgPCsE14URhx8Lnh8+cHfOnpTnjRWGLGHj+CkOcd7GMwm3frllXrx6ZX7+5Weow3xmpqEyTw0PZFptLHb4HPJoMXkV5lHxRDtSIFKgxhRAw2db1h87kLB9dnR2QBXmfqhC229WllbNc9yafY6N+VmsCalpzLbqSsTyOPkVN+i0xprXZq3SJDyqLp0lWwge+OeGHTcbqJlgI2oYbUrxbzEKkPKpcYNWBf3dT71acJC4vQMqMfHGLjfSP2LXzN6KBKQC0ZFCWCxl5YJ0uKIRGyD5UEkcJtXd3SXqw/v7++W2HUMpEbnRT7nWmLItKUNdXCQM/7u64MEr3q4bhWa9eTyLVS1NwnrwdaQO0jsEyCmQhmt+CML64TMwf/jDH+R9Papdf/70hdk/dNAM9A2Z3//+d3jLuV/eU2Z+Kfu5l8F/lu+QAU03Ly/OLwhyOdNIORmNXhVRIG7YeTKFzBSymvXXxQdtAD6ad9g4VlCRnxlvjzxyfxsPPN64fk3UsNkTK5SzYRqJ3PXPnGNHnBCark+GDnq6AN4KoUrMTvcGk8SBQJcJqc1CKmr4Yf否fbL5CeGiezdTIOTTsI0kPKvUWYCe+3E8rsoTJVSfkT2Jl+2scnlbkUU7UqBGFAg52KK0hxq4qfwWG8y8/fzkyVNz8eJ5OfxAXfHkTcr65CRaYWakNQRNQvk58MpI+UIcjecT5p65C6lXKqzxShJzFCnQmBQI25G2r9BPc61+CqP+ams4v4vBKGx5O5zUlYcOITRtzY/aIYy6FVa/FTbrr+GV2hrf4aPwVoEMFFx44i2BcAySLS9jKpZKU60Url54K00/wqUp4OsePMIFjP37h8258+ehvu6EacdCO+tLOVNiOubQeZLOnRSP5ys3YOCtODXcLCY2Hq7kAjg3jd+9f2v+/t135i9/+au59tM1Mz0zbWZnZ4VHOW4m6zItvVVHXJ6HXMY0j/Ip8DaOphvtSIFIgfpRgO1O1lmYBBqstFdcSOLiJm+hDAz0YcNu0HR3dZvp6Wnz7Mkzb否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否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否UUI4xKuygrLqBG1fHlhClPMLhVHwyz+RpF9ccOuWF2W8GdV+mvibGD0AHNSVcrq6opcJ+UAjTprlXkJoqxVArU0gLxw4pGJn5v08TorHzjndWQKdDXyAOWHpGGofyk7ERiloGJYOQpoXZeDa4bwRNba7QjK24R/4QKA8DQEOjfqXr9+Le/YhTfsLEAzlDbmcddQIGFiX+Sw3XKC8vDhA/PVV3+Tk1B/+MMfsGF3zMMmjkJEoQ/bxs7mf5ZWSgk7LDkLHk2kQDNTYCfxs7bRaupDevZMhM3QJJQVGbQVfZaKr/kKysnBCgcwsPlmBFXSz8/Ny2lxO3FMJFdFyUegpqSAY4FU3skae7HhxgWmQ3ogp6tbYITrASCT85Cd2L/xP8JEawQuyHExc3Fx0cyAt/7+97+bL/76V/P555/LIc1VHNJcx2IGFQ6RDzlX1PkiEwon/+HYI5XR+BEpECnQUBSgPAkNRII3dPPtOh7wO378uNl/8IBpgXr9t1CHy6dRXr18hQ27/Bt2HkkDOCiP/EadK2CUUQ1QMXXNgjJ2wtHS12GjbhX9GBfm+R32W3XNTkQuFOBbtx3QosZ1Xt0Q2wxpWLta0wmeQh+GqS/j6E/8+Ace9OOGYjtVYrZ3+NtiPiLCd6OR8SE175NOjlakAw8LcrNuZGREDoJzo5MmAJFvbvTZlVX5xB9SulqTYC0aOxPQgrrs7ek1586cw++sOXjgIMay41KvvGn37bffcCiLtbBuc+r0adPT0yOb+dyso8Y/7jvolnKSepJvm1y2bEl4Y7gyRGmMTKVy0QQbdqx+ms0SMxtfvxW/TaX4X4Wz8fI6L55MmoAKhCdPnuQO0HTAh75vQ8ZOJG0+qBKTqjA5SGzlA+aKHJg1p0yEaWl6CcwGM7ChXMdIjUcB5ZB8PtBQybfnKzISfMBMXuwCkF+85v0eE5MJqBQMH1VVvvPlV8T5yXqw6IgUqCcFlA1lBIKEwgkpN+yeP38ufmfPnjPnceroGN63oQnfc7Cy1DKyyFgCKF/7BOi5k40WeOvKqHWV9GVbl3ZMqZkooLyZbYzhN2HC72z58nDk+WXjVfOdhy+brzyYatKoFFbTqRS+GNxG8JSqh8J0CC2p+PGJgwkGHQzn2xELfPB9ARt2WHhSIzJ7I9lUBNFuOAqErBCwQUE++ZTAwOCAOXHihCywt+GEeNr4Ea7MnWQRBnKCp91bPu41M9PQKDE6Yu7du2devnhpvv72a3Pj+g2ohX/lxxJ8O8Syl/J1ZLY0jeNXpECTUUCFihc0QZtGM+cthZMnT5rz586aoaEBs/5x1bx5+9o8xg0FLnrq2FVLrbFVQvg+TQG2y9aMbVf6Md26UkDZuHQiVt0z39xax+0gmjjnKk2x6kKxcubamdrhmgJx8QZbO9Z421vbC1RialpeFKlHjWxmjfJIZBKuf8loxiUmt8HglmeZeGkE4ylV78hBD8sjcfkHRstnv3bmX62HsKxCO1fc9bV1Uc3PZ1fmMR8ZHhpKCKGAQnShXBKWcjmCOj9NS9NOgSqM2OEWIBPh3IkVBXz2v/jxD/cTTp48ZX7961+b6Zlp0RQ4Pb0o4Q+gReKLL7+ERoph89nvfmeOnzgu6lrX+M4l1s32YNOPeSFaKYr8tah5GxTeO8xoiSxNt6JwTbBhp2QgcepNmPL4k0ailaX546nLdagGfA+VKLf否8ePcAIzOVFlGdjCClNrUorGfWcHdYqdcSjQFZxubtTxdAMnkmrktpx+pGyNGXjmeAWh0VklBZpvQEMGUEa0hc1lCeU7BuI0hfCoAOL0MOLPQDfze5wcmcRmdbgoJqgVfS7iKgkcwSMFakEB8mKWH8mn4HMu1c3PL5inT5+Zu3fvmH/8x3/Ept0FkbFUS7GnJYyYuBMX0CjP1yKvEUekQKRAHSkgDT+DX1uzNuQ8mEyUmn9uNE3Ns2Yo+63+1dpZmoTxNQ2FCcPy/MLwjbuJWVOWmWIOKi44zWOzbhHaLnThiWC8ReDj5sSrxGuz8StJI8LUngJ8c+XUiVPm0sVL5vDBQ8liGBiKMyw/jmcF48fFFjmsg409jm+fPntqrl69av78589xG/8hDmg+xu2ZZdOKW3tW/aXVwKL8keW0YnO82pc0YowUiBSoGwUwX/Cr0WjsfOuHasUuXbpkent75fYtNc+8efNW1oa4wM0f27+uIzFvKifqls+IOFKgSgqQP7meyQPY9hD2R7lRxZte7AujqS0FKEqsSUa1XNftwNti1KK2B3RXQwhZqQiFiAZWbZeXPjZrTNHCWhturIe0YsOuLdywIwzlG/4lZao6U00bIewSUEmsKBHwVIk5gw2w8fExUY0ZalxI9QAksSV4xTQgG+wpuuiUj0xZx2XPJ8lLQBcvXTT/9m//Jht2P137yUxP26yMT4ybv/z1c7OMt1j/34UF8z8O/g+5/WlVvSMTwqPEaNNkP+dTh4NFs38svtr89SnUBl2DY0l2eho8o42bPWFDZG+PTOxGRkbNnbt3zdMnT4Wxa5nvkDXthh0eJIXaBb6p4CeZA否jJyIUWvNXy5xEXLuaAiLtLX9x8MYbdrzqvQAh7vmQBIqst6vZpKELH/Kmc1PNsBqqduVNuxs3rssNO56cHRwckkGoDESE/VXG2ljpL8UU7UiBSIFIgUiBelKAIjxP/nKxieMSvWGnmyWh+K9nviLuxqAAx6Va96LW59Qp8+mnn5ojR4740+HcrONCky5Iqhoqnh6nOrv379+al69eQgXmd+batRvm8pXLZmx0DAd85qSQLbJCxQ0/Wa7y6SlfRp5rDF6IuYgU2DAFSqxCd3V1mdMnTuIwwEkc8GsxY1icnZya9KowZd6ADbs4L94w9WPEulKAPZX2UrYf1A07qr2zG857pV/TvrSu2dnVyPk2GDbsOrhhF6icJE3cOGMz5KlmTKJcYcdHSBXrfzrebsEmDd+wCy+NiJyTDSTG1NibyW3zxeV4k1oWPnAsCHpRRfo0dr7GRkdx024ON9K2b9O7VN1zP+HM2TNQ4blm7j24Z27fvuWJv7S0jHnUorl89bI5cvSo+eTSp+bCxfO4Wd7l65/Pgn3kwXYfKzpqSYFdtGGnLKQdkpIx+63+tIuHyZ4FIdCRkTvZmfFECvW+UgXC69dvzOoKHmXEPyveRM6FyMXNcBqFkY+if5gfmycKBE4k+dOJJaPJG3bi4B/KduJP4omn+2Mx8SNx7VYBG9KlHm4d4Nj6qEcK1eAM61vjWT8NUb6UUM/sCNVm5KKx45lDBzQ5MSkdEzsp8iQXPbTMFoemE+1Ige2lgLJzwVU4nhx0/E3efUF1V199bYYGB6FLvgNqbqwaA8vXaCG+LbhW4zxCvtf2nvj5SGkihN7aCNMQNf/ydADmpCyVJBNmlvBblGHJZzbtSvK7/TBJ/ZPWzVmGfCpqWbaOBwrzoWlrXkKIbJh+E0bh1U+/NX72O4yjMJu1K0kjC5P9DvOlZdlIvkK8ikftMKwc7mpgy+GCdNEsAFSbjvfzjiDQoWSb+9/svfd3XEeSJpokCEcSBAjQgk4kRSdSomyrpZ7uVvfMnumd3Z92z9t/c/fMzJ6d6bc7b6eNWlKrpZanSFH03oFwBEH3vi8yv3vz3rpVKAAFoArIJFHpIiMjIyPNTRPJOTnfGqO2C7plavAoItkdywGJAvtX1q/6XO/PN+y4sH4AG3a8CbNr9y7Ht2Jk+D33BLcyOf/NVWXineaxMfe//vVf3P/+3//H1F9O4O26h3iThDLF7y/mxVPT/jsul3+68rl0c195oiXZiQOJA+3DAd1yYVtX3yLq2Mf4N+x2uG3D29zszB否W7qDRdpx3Lr1444NY/jEUD+ltMlOHFhODmh0iqZVlr0fMz0llFG+0zo7yxt2T/0NcxtXlXo5KV79eeV9gq8V1gVvr/X28Y04vmGX851xuS/vTxheND6AGNVf1cL4FDZHydJ7Gso0EZIgL3ghxIPYtyw1vXHDLr99yf7R410rv1lxxRfO+rhxtY46yF7YG3a82HAX+wOTeE9bh8LIH7GdqiS9IZLMo0DYWS4W1sw6AncfaLI5qAkAwoB+Xagki0MQITnGDW7Z4g4fPoQ58j68VbfR6tiDeprujt13f/jwD64f79n9+le/du+//54b2DKAOn9uY10XZNVoQ16SA+VlGRtF5R9Ppw+tKns9eMEqvfxl+Mb+jM55Jfd55m1rXokbE1Qndg1t2NXhwIKDJSCwg5Mfb9xFv3HjhqkJZEOgGrVnL/KFAmU336plFhSqkJWhYSdeuF1noWwsyoVp4hR5eHKtDAdYH810tCtDXeNcq+h+/ASnLqBuakYPakP2KJcc1JPsNeZnil1hDpS6RltOC2GUXd4afYobGrxdd/jwUXfkCFRj4gSSmaiPVSlK6CqnXIJNduJA4kDiQOLA0nLA3rDDqVBu2D2FasNk1h4HynPuzZs3uVGcEOYbdgMDAxlDuFnHbycuPnFBhTczHz4cs3nA1998Yyow//D7P7g70KJCwwUqHk7rxsIaVWXmG8LlmUCWRXIkDiQOrEIO8Nt4ExYwd+3ahfcxt+Jm3RhU5V6wvoMqdc2gW+A3hj4dUi+xCgVhFRWJ4yZv2FHdM8dDynjVGtAqKnJbFaUbt3S5Yce/9dCilhv0IOpE8sB5u1rR/1AeOF/iPCjJRkUVBCbzjTdu2N3BW6aTkxOFDTs0s8jQ04LKjTCWnYXs4kgQwvkvDVU6j0L7xH68U3fhxx/d7BOqhffj2MzUIzwXcwYH3Ta4AcDxlt0mzKl5WWh9eMfOkNTNyGLb5qfI/7Yhq5KQDt2wk0C3QiKEi/wp44vjPP8k0LwiTnh2UhzYaGafzJqe2snJSR+G5IznP8EQLsYahzNORvlkwoQsLEefVQDjABqpw1Ris+NcGBD7C0hCnMJkG5L00yIOdM5gFsuJ/8CgRCjU5FIyj4hpvPc1A53G8YkRz7KiHEmeGZfJtAdMv4kDK8sBCTepCGLL9sqPFU6wvsdju2fOnDEVWtu3b7eT9/Glf7XtXOJjhL5oHAMi9D4w/s0Tx6FL6o7b5PwyWgFi50dg20FLRtqOsAUTJBlvJAuCUSaCVXjZTziFKU0ju4xHsDEOwSiu7Gd4VZjgO80ul0W8iMMVVq9sPCHrYWI1wfWglzq8qp/SaVTNJeq1L75bx/kJ//INFc1B5uJD45KJo4vD0jiPFLtwDlAmJMean3KTbXhkxO3cucNt2TJo32dUTvTs6XOoKXqGAzleawlVqJ45872pxP7444+xQPEdNKf8a否kiIN4+Ue8+ssolcASxgLj3wwqORIHEgc6gANZP2+DDfuUWqK5aD08stXtwI3dfixifnfuLJ5G+dbduA5NS/iOKJjQP+jWQQW6AnjyJA60lAPR+ES8GiPLeXBc49MQ/OPcieMpx75kWsEBrt0W56G+WvI5C2+u9eMNu16+EYeNEBn1R/bZ0qDz0OyD6bIqF5KSbbAlXFk+gM2j6MJfFokNO2zcVG3YxfOvUnar10u+gD02P0T7EefYliawUcd34KamuT/AOMKZFfiLxDZnFH8zJnugjOkhUQhtaIUPpVwWiDOMYco8uy5JOfF58qLR6Oge98Ybb7jruIB05fJVO4TCvKhVbQZaS6hJ8LPPP3PHThzHhnKX243DKrxlTsPyE5dKoKwssupHAmqJqwDKYcKs8LJf4XPbedaNcMRxRf6LZ3PntHiIDtywixm3eAYsHIOvNFNBCQFmA+SDkmO4YRdP0PKGUsxJpShWfRGmxkfgeSWowZACEgfmxwGTOQwu7NXQidNQdnmimHLOAYgb1ByQaGyg4qCT5NT4kX46kQOUcP5Rjl9AvfE19zkmJvv373OvvfaaO/DSgahQFHRMgEKIbKZOJnEgcSBxIHFg+TgQ97/suzlPeUQNAGHDLp+XCHL5aEs5rSwHeDt+YHCL271nt9s6vNW0kxhFEAr7aMeg/QQqwB7NTLsfL/zoPvzwj3iv7o/u008/tUWLp3jigKqxezb0mFw9fR7fqgtly7/+s8ImSctYkRyJAx3OAbbmvEXzCQi+Vzc8POT27t3jRraN2KfDxUsX7f2f69iw4xgUG30b5Fji2FZpwxkAAEAASURBVOROHGgPDnBNh2s8vGEXH3ZakxsxS1wlFdMG0+bTxw07vGPHDZFWGvU96osWipt0czOxaz1UYob1wYXiWl3pyOGcu1QvOz01BTXqD+w97edhw45ltm8SgdJGUoZVyQThF2NU7wUcgdR444nuPaN73dtvv+OuXLmGg2pTtrfBbyqZKZTn+7Pfu9///vegdT1UY77vdu3caRrWYlyCT/biObCiG3Zx5c+/gnPByRtGHDYXc9RCYrhiWNxgIjm1RVym8jSvswkZ1QJeu3rN3cONjPIELc6BblEpuxxvMIiM8/cwTFFMRR7GtPl4wfEUB8oEb54qd1Xl29ow8XM582xtCdYaNtVYVm7JF4WsCwsbYRCijN+/f9/dvHnTPRz3b3kwDcH87dMMgzkYXivPRZjkSxxYSQ6YbGcNwPdZ7F9v3rjp/u3f/o+dvu/v73P7D+w3MnnSiAcyeOowG798MouPnLnsh0BlE8OsZNlT3quRA5Iylk2SprCyv9nyK10Zvoy3HE+/YKriymGCjfMrhnFMocnanveu0V/xSTwSG8p+hitMaQQb+wWjuLJNWMHILsMs1l8fr2Jiiqtye4r3Vx5RJSb+/KJTnCJ2V6WeX1gsh/G3zfyw1EKrrIxpLcW1eXVyyFzzS6r52YdDNwdeeskNDg5l/QYX3V0XTx0/d1dwOOfff/c796eP/uS++fobd+nSJXf3zh3byGOdUgU8TbzgUuBZmugW2JE8iQOdygH1u+pz1b8wPJt7YO4/gDd/jh456o4dPYb3roftcMi5cz+4r7762tTnEt7SBEbwaKtwh6BkJQ60HQc43vF2HTftuHnH+U08x2k7gjuIIPUpOckxb30sb67xxlKP3bCLbjaqQynMBuMexfc2cUhtfnnOdDWCZdo4Pd0xPJ9l6sKhBV5eWasmLrnxKmorPM79Au3n6ZPn9u4xtZJx7UiGl+C49sTxhft4bHc21hhSBmqN39eCxSnxPG2i9PTJQQQI8f+tYkkLadg9uhv1+rZplvgOGqYuXLxI4MywX/gRWieePvlXmx/v2LHTbRnYAtXQ/bWyIKJ9EXz5gAnZeEPbyhv8yarkwIpu2FVStOyBkhhm3EhiPFwmYBGd3Ly4c+eufdzdxvsG8WmUCKzgjHMtRESeqryiaDiJRX8+hmkK6bJoOBoVr4g4+RIHcg5QoGw0wcABGWKHzoWwu5D569euu7EHY4UbdkzIDr9sKoLKIMmfOLD8HJCoUrbxR9n1Is9J1HqcuH/krmIh79M/f+pePfWq+8m777qtQ1sNrrBZB8rZxQrd8hdkuXJkKWOz+ksclza5Yw6UZSGOk1vy0Qys0iR7cRwo81x+Yp27Htp9YYYliEtUj1dPocqJ79fphl0O10zqHLrKVcaQfZOWI6oSp7Al5EB1BWzBwvpLLx10hw4edkMYv2VmH8/aAsPDiYfui6+/dP/4z//kPvrwT24M71BxUYIag7qh8u4ZFl34bVc1txUus9NEt8CO5EkcWA0cKI+a7GU4Tg4NbrUNu4MHD9l7Tnehaek6vhf4XUwYOxAQ+gTrmfh9wbSrgSmpDB3IAZPCOenWDbvZ2fyGXbvPC+csVBsBNKoF8plv4/obdj32NlgzpAun7HIfU/Y3g1Mwwun9ARPo5EYdN+3SDTtxKtjgDY1ppWT/j/8z/BbBX7xHsA4AVZcTVVee7zH3FRPyaWgpXZ4mc9HBaIHAYV6OT/jr7+83tZj79+93g5g7lw37B35XXb58CRqoPnfHjx13gzgUd+jQIbdpYFOmPjdDX0ZQ9jcNWE5IvxJnpasC6viwtGG3gCq0tgf5CO0RH3XYsLt92124gEeGb92quWE35wdekzRw8Zh/ON/ZZIqVBmMjWt0NaKU5vPT5s/78Hzfq+I/mKd6GuXX7lm1S37t3ryDzrZJ3yyj9JA4sIwfYt3t51wQAU4Ewgbl566b7CO/Z7MMEhrq9R0dH7WRZRl6exAelri9jTXKsJAfKgklaKJxV4StBJ+lIjWUlON/ReUJsuLBByamSHvbbPEz3CO8tzODQBd+zW0rj5z1VlCxlrgl3mQPq1Wy2is+lF/beuMOiw6A7eviIO4GFhR14i1aLj/cfPDDVPn/80x/dH6AG87NP/+Lu3LydHULj+yyYBMy9UVcmJPkTBxIHOp4DWY9eMU3hgb2hoSF37Mgxt3/vfjczO+Nu3MQh1ocPskVZLoP6RduOZ0UqwBriAOczT57MZiox6deYuYbYsGJF7e7pdhs3bcKmXX9xnSGjSD2TZjyKKPrpU4hSyFa4UlbZglVcnIZx66kSE3OktXzDzngDZqCJeAObG1o0nIdyDKB5igNg/B7hQTAzbFN0MJrpA5zFxT/cbBDuOHy+7pBPnCxQEIJstMo+qLhpvH3HDje6Z4/rw23PGajH9d85OQbewv3hh3Pun/7pH03d53/5r//FHd502DbseGCSfUZX9PYlSWhFUXIK1o5rRTfslq7ztyYQanEhouHTqPGRTtFKdX8UWC+0Ph9uXjzARx/fOqKaQO2e53CBlMWSVZLLHP9cZZwrvoS4pd6VzLulBVkSZF6OPGrJ2JJk1CzSWEZDmlz+80gORpT5G3iQlCeRNTg1m02CSxxoNw6ovxddvn/ND0dMTE26L774qxsZGTZVFdu2bTN1FYL3My/68j7PLycjJCBXG1denIdlJiTLU2cxyZE4MA8OVAhVZWrCSdqq0ihMMERSDiv7lZHC5Y/tGJ/Cy2FlP+EU5nHnbacYLoxLaav9Mo+cjqXMcS7c4neZF/LH6cthSlsFUxVHuDhc+OKwGNdC3cJbzo/9qcc5V47cpOMJcX5UFucoShnnsVA6fTpPU+vwLY6aNZxaVYDNOt5uwZ04Y8amTZvdnt2jbu+efVCJOeieocJmsHjyw/nz7re//a377//jv7vLUIH5GLKyDgsMpoQKSSk3uewI+Srib+ubwipiTirKWuWAWjqbh5oIuxK5Gc/5/CYsqu/audttwYEAfhNfvnrFjY+PZ30GvyCUhrbwwplM4sCyc0Bzp5qMJaRBQKkimhsL/NOaZk2aFLAgDhirwee4LrQ2IIQb否QbdnjHLt7wYLwdRmKvojpToshuqp8J6WPYGpRxNjh5EMMaLYgvaxpSOO24jPSvRlPmia33hINiVlsYJxjGeSQ3vR7hZtpz+Km9QXXo14hiTJ7XCsnlQyFlTnJfwoflsGUY+OPkVtm+xn1uvrLj9FRzeWD/ATwFc8A0qk1MTuRImRSF4BrwXz77i+vBpt7p1193ezDH3rixC/3Gc7t9ySyVFe2wLWh4DEX0Ea0yWGTTP4a9aei5AEVDzIe50ixH/Ipu2C2sgFnVLyz5IlKxQenPo/FCwht212/ccmfPnsOmXe0jwzVZxg2mJrJ+QDFvvqPwwtS0ZP0Ckq7H9eSwsR8QNZNZMzD16UoxC+cAOwTfUS8cRytTsqOK+s4MNWk0aQ+R3KTm+3W8VcpN6nxBI0uSHIkDq4ADed/IR3bPfP+9o275Xbt2u1OnTtnHetwuWOD4RFXctqva1SpgUCpC23Igl925SSTsXJPeueLnzqX1EHEZmylDaylo/zbNOot51KryLz+v50s5+17esHuMD2Ru2DWlznC+mST4tuOAH499b6YPbobtwknhUydPYTFhNxZMHruvvvnanTt31n38p4+g7vrP7vLFS45jvIypeArNR+N4+7d3UT+3bXwCWNY7hLIqpeLjDwLxQTDJThxYzRxgG7CNe9hcb2FbYL9ARUd8h4g3S3iAj+9aDwwMuK++/QpvX35rT6Rk38RIptEya2vAp7DVzL9Utg7iQEkgeTmB86YZjJVc7+Eiuw6gdlCp2pTUErMrqOzt7XF8d3fjxo24YRcv1XPNEDVhA3Tco1QgiYKy8TwKm4+T6auo5hyL/SA37WLD7jKZIgc4JkxNTrkH9x+4WbSrbqwldXV31b2BLZ4Xsczla6amo8rJwKtz27Zju3vz7bfc5SuX3SeffIJ9jrO2Zs16561K9gvPnz9zDx8+dN9gTv3bf/1/TT7feutNt23bMNbLUD70JSY74Z3DsmyEoXWugq35+LgX6CBmRMJmVDfyZ9LYovIxL5yZorAG1NxFHh9/6G7duu0mJqLd53KOZTLL8XP4+bGkDyZzh5Of2cQQ6a0RBboIYwMs/AzyTWaOTALkXFDNxQdCrKkyhfx0L5IZRLFKjBYV2qU4xc6TMudvGb3gVwqqjfTyY2VifMLdv3ffPZp+lJ2wkXy2S1kSHYkDzXMg75PUJgvyjGaAs/ZQBXvbXcLi3kX88SN9M07uU31FbNjvWh/NPtjGCt/35fhKfno1oORkxCjb0N0xhLYh75aSpEb1spC4eNwW3Qor41M44RSnMPmFQ7bi5addD3YxceW0VfkSZm6jpjo35HJDVJWJYVX8FGwcp7D50L2QNPXxc/5B0wyPeVhNcGUq2NdybsxNOy44GU72y5W8sOj008kciMTYvnvse4PfaetcX3ePG9661Q0PbzWZuIrFh9//3//rPsFG3eeffeZuY0x/DvU9VAH0DPLCxfnnnPcSJwWLf3BzocybKLMQ0kmWLxLbAkzW4OAO5VRZ7P0tgoCHbE/kZT6HEVSyEwdWFweslUPWC0vQbCf4Y1vgjZd1GHx6uje4waEtbstWvPGDxcgb12+6SxcuufGH+Q07caaqx6gKE3yyEweWgwMa0ZiXhgLvxoEnjImcO3G9J02bWl0bxdaPbqVg+G5uP27X9fb0lG7Y+UE6rqtCwsjDHAhd6MfgL+YcJahw1sJyTpWHkm6tl5STN0NjOU1H+smOnCWhCKUKRSi/R6gSkzewp6am3RasH0GZaFZkcwWv5ll5rMBqQ3wM66UMU0MUAIphYQ/N2r7VF6LtG4ma4PFv88Bmd/jwYXf02DF3/sfzzp0NuWmDlh9hIc0DvNv6IdTKb9jQZZt1QxgbN0CObS6NcnZx/iwakSaXG+QEf8AsR7CzCPiVuATSYm/Ox6q8RUMc12IC6qDr0A27OqVZ4mA2IN+I/OegF/x1UKEy4+7hltH9+/dw4+2ZncDiaQO/q9yqSlXevpCkYxZX1Xl6mAsSatxLzIKEPnHAODAL3eZ37t5xfNdr5vEMPlwwqcBJinSKPQnIauJA1QIVJ1sff/Kx6Zen+/3338NC4HDUB8cTp9XEjbgsrRrXYpzJnTiQOJA40BwHbD7uvxT9Z1z+lWUIGE91TjZHhhYMM/zWCl1X6sE8S1bdL+SAdfsCiyP8DtuxbTtU+uy3t2cfcuzGO7Qffvih+8d//EfcsDvn+AYzN+v6+/vtNLCp/8pXD3L2RLKTB3a2q6pIWkRhnN0qgq3vS6m+srZXxaPOZkeiPnEg4wDlv2zYrzxDv8Jv3Y0b+93evXvcwYMvuZHtI1iPeey+++6M++KzL+wNzBfc5EgmcaBDOcA+fnb2CWR9lgOAbTZwAT+ZpecAx+De3j67YUeVu9z0aF/D9Q7/1740Li9lpU+RLHNu2E1MjONyzy33YOyB24g5pz/sHdoVE3JeVTX4ZFiWw0FNfWztz91WvNG6ZfOAu3z5svviyy9MXfwLzJeNYtAa9wgs2+d//Qz9xmPcOt+HTbsRdwCqNDlv5B8N4Vm8OF1VCGGTKXKgnXuBIqUFn6S5WOUFkMwjGKXJIho4BKu0FaCM4h9AuSgwi40z2mYgl/Z2AhteAxQeeD6/OTIOptyoY5782JRhlmrtvu3naQTT2J4vfCNsZVxlf6O0KW6lOBAPNpQndtwakPUxT7mbnp62xTDS6U9QrBTFKd/EgdZygP2rZJ02/TL8iOFCHxcEt0Edzuuvnw4bdr6tcF5Sbi95co0tAVuhsSmHsq28S2kLYM3AFBLU8VThUVidJCl4lXFAcqZ6l5/FLIcpTuGyY5ZUhSm+nF7hshUvf2xX4SV8VXgc1ghnjH+1ulV+8UT+5sub92fs6xaOpyrHuEusiY9JZbYgxHIPiQrRiOPmix0iAhSWFPDraY3785o85hmgcaKVOOdJQgInB4IMvKA8+mo2vuweHXWv4V2NTVAv9fnnn0G1zxX39VdfmWofvr1hYz0GbR6ApKmpxwhXAbFBd+aPFUmNReULNtUcac4T84LPLfgNO2xaYDMijutMLiSqEweqOWBNwQY5NJLQTuLmQtnfjIX0PaN73M6dO11PX497CC1LN65fx1MRN3CI9ZF1R0yj5qX+iWOWTCFegclOHFhGDuTSiEwjgaSMU7uS1he52ZD6/KWsmIj5yIabdH24YdcD1Zjr1+O6UzBh30Ne2EzHeYtZtf0O4zA3MigBedC8b4Kf8R5TiCxZlrQgLEUAzYOLoWvPp+8XthWtA6nd2A27Gdyww8bWFNZPefgjN/4bReOET8/Ycs2oEhrWVo7WXEpTCoY3+yYKUeuo8hmojWbciOvqWu+6erzq56EtW3Djsxu3BLnnwP7hhe11ELvKyH2J23duY479tRvF3LsX79pxjNSGnWWDDHizryiOxfLkfCyGBzKbsmL8wke6c0PcsV8x5TxjfxW80i2t3YEbdjHjlpY59bCTAgo1Tx9yY466W6emp2xhIEuTS0cWtFgHUQotG4fXLz2DD83ZwoYJhVRwzeXJEq2cEDZHY4JaDg7UkxvKuQZkqkh4+HDM8fFRO41M6Yl7xuUgNOWROLDkHGCfmI83lP/10DnOvpJqDc7/+KP7E07rn3ztVXtsd3Bg0FRqEc7aA/vrKP2Sk7voDNIYsGgWJgTz4EDetuaRqAFoq/E1yGrNR7UBr0lCgy6LfbAOtdFtH6ac4KS5yqqWXo652SIE6prvv/DvEk4Inzv7vfv2229NIwrHcC4i9EDlFBdSeAgtzWP94Tuch7ZbRL1cMAR/qCqU75Q8nsH3Jt41atjwVrV0pcKtJQ5w8z+bw0fjzXrcsOM71q+9etodOngYmxp40x3aZq7euOomH2EtiP0OFz5xeI+almg0YmrIkn8t8TOVtY05UBJIjoXPnkIlZpfXTmAHn9LcaUkrMGZvX1+v24INkk0bN2HdId+wa5aAqLuyJOx3SlXcFKoynqpEWhusilvLYWxDPABlBhXAeeY4nhK6hbHiwYMH7uk+f9kmG2PELA0SC6004Vm0LUIcVMoPu31799km3JWrV91TaC3x31U+k2zdC14ehPv9H35v2gc5f3z//Z/ZbTthkxw2I1uLLsIqQtAmG3aqPnJWVTo3l/VxVdtZzAdfDNtcntys44ceB7DZZ7O2Wz6DDxnRY0sDUc+rHJovWT060Ky54ADDhs+3Fp7iI9M2TaL8qlOLCsYunpLqPFJop3LATiWTeIiGDR5BXChulDl1xvxof4STIdRVPPMol/lyuZO0lTmS/J3GAXap+e0R32vyVp36Ty4G/wid3r/97b8i6IV7/7333Y4dO5DI3+xgm+kKJ+PUHrKFxNCP8zKAxfEnANV25UrdiIPNwDRKXy8uHivmyiOGrYcvhS89B+aqp2YpEJ64XhVWD0dVfJxe6cpwZb/gGtn10uThcnHZzRvSwj/5Q3DJYvOsbYcloI7yNi5vXpRyXclfTB+6rzzZcrlAjvIWZXHWnLtkfSwqkCrq2U/b3JwJ+Z9FQeKq9DGuZt2a9zcLn+CWhgOFegiVyzDeePn887/YActbN29BlfvdXBsKSMnkQwLhNfdwGG+pnCxNqaux2hzeyPeF0ncjocUn2YbB2gUbBuJNFdILO+E/ODjodu/e7Ua2bXPTU1Puhx/OuenbdzIchBfuAj5GJJM40KEcsJaAgcJsNCFrRfTwDx6qxBzZOuIOHnjJbRtG25jGsyh4z728DhQXv9w+fMuMIZI7cWBlOVCeP9k6I9Y5tf5TluGVpXb15a6xlCXbsKHbbtjxhhJvt7O/sP7Iih06Irg131XarF/JgQGUhVpq/QhEtsK9rVCllV2EaujjZLtO3g3TdXBkubjWZgLr6OZBsbExXPKZnDQtTapUscrSC4lVgepBTCn7FV5lCzauu2KYDpRQuiRDHlOcxpks7sJc8ADGPN4OvIN5YGzivmEGGgcvXLhgc8hXXjmFN/COuBFooyJGvYtsaUkKA1FeS88x1/4sdtE/5GmtKQeW/XGKRnEx3PK422TDbv6FjYVj/qkXmYJ1iD/ebBvDTaObN27aI5IZTRBAXB43OYxzYrJiE4hj53J7wVE7Zl5PoJqNE0RTi6kIoPGQc+FL8YkD1Ryg5IZPFQPgZEGnRLhJd/36DegzvgIVILUPawujZHDh8i5MyU4cWDkORN2qTSqev8jVD/OgxEVMSv4nb5qiL961bafjLbve/l4jmBMPPybErWnlypJyThxoDw5odFhJajgyVdNRPclfSVpT3gUO1K+6Ihg6bzvUpg07zWtYwaFjT/OTAstWhydUqo26cN+8ccM27Vjl+kajiimq+qGfG3aZqegSGLSW5ITzFr6bs3/fPnf8xAn3yqlTeKNru8117mKz8/at2zm7UmeZ8SI5VhEH0Ogl2mGoCAHsRLy6ul27d7mjx467wcEhd/nSZfc93q8bh7Yl9THWZ6yljmMVVf9aLErVVyq/cfnHMYGbd5Rtyfda5NHSljmfaZDfvJnEcbivvy/nuzqlQMhiu5eK6U5FEXO6KiLrB5HWrPOsD7aWYqhKfGJi0t25c9fGimfPo7ln9j0a12pzNdQ8D8v48rxy0fJt3Np6NPPdDJXyx44dc2+++aabxGbjfRxQYd9AOMorDW1qoaIaXb4d/iO1UH34Jzc0uNX1bOhxu0Z3uYGBTQardExKbYX8lxmo4szpyULXvKNNNuyiimqySiQg1eBV+KrClFpxRWH2of43HsyUNzfKqA7z3v17bgqnDzODJBQ2Yc3CF+GwxhMhpJ+qCUnDU1xbpz83dIcGBFcck8O0r0sNuX0pXIWUSUggNrGsl0vKdz7u3rvnbt++ZTIvuauxQ0KhLeNJ/sSBTuKA+nzJOf38gJmZeYwFrDvu66+/cR999Ce8k7PJHTx8yCba66k2mQ/Ps01xQLAu2Z+Ui8tubSRqKAYGgDxILsXkqRUiiDymyiUopSrDVIXHYUrPdHE4/XEc/cm0ngNlnrc+h2qMzeTbAEaiMY8ZeAE0pBeaahqrQ/M0uSuHZFgF3VWgeaK2dGn6l/NNhYjLp7D5FGEx6ZVfjGM+eVfDGtbaAntgZMUo5si+mh/I9gZLTIrc1ehTaIdyQFKWVS8DgjxwrM4ND9J4v8Qoj4MrIKiMKwC2r6ew+MEizVUYlJkwvDm0cdNGnIg+7N5991333nvvuWMnjuNYNDc3n1tcXGqPNuN4HJXciQMdyQEbQ9lvkHq2i9AhWP9ikbh9ikXJbdtG3Oi+Ubeht9tdvHjRnf3+nKk7y9paqc2VvyE6kjmJ6FXLgUxuoxIyjGOnvnejqORcBAfqj5jWyxjmDRu67P2vLtPmxs1Sn2EOMQcB9TOZI6GiKxAw84pgpSjYJFhEFyJWt4dtpaotsdQM56WHcVx2mJzCG3bY2Ko1YLKpd6iN8SGqgPqSoPzz70Hhykczv87L3yI+X20IA3qqRpfhe4r79++3TbuzZ8+alkHF0bb3jWHHWqi4P/H1N1+5Dd1dbmhoi/vFll/4DTugf/4MT4oBvS6DkAqWyP7wI9Ghn0ZUet9S/dbLRVQsPl/VzUIwtcmG3UJIb1WaehVUHU7x1sSLO8h3sVN+88Yt7JaPFxspkrOKq7EsjHadcGFqVrq9YQca7IZd0JPO/Ezvem1LXVimK5SK5ROfV4iENZWt77wps1HHZMLkZU3MoMxfu3bVPlAeQk+xFkJ4QgIVJrCWyn2GNDkSB9qEA/Gg+4SqMXHT7n/9y7/g1vUT95v/+Bs7mU5VFtYs0Bpe2Lwnmhq1YGDIW9tKMIUFEAUtKMxKFKGj8hSvO4roDiA2luMOIHfBJC62jS4m/dLwmC0iaxXR3MMmH4jg3IS3p+wUKANJxmKKsWDep4QrygEJSVb3XIQMFGU6qeFfo/Lhn3jAyWisngwPD7uXDh50P/vZz9zf/e3fup/85CdQZTTiHk6Mu4s4Ld2HOY0M05FpnOMkkziwqjhQIdPc4+/qwhcybubu3LHN7d27xw1DzdcDfAd/9eVX7osv/mrvEunbILWLVSURa64wlGP+aY2H8yi51xwzlrnA1ADQjRt2PXjHzgzmt6wLm8rEc91F0qVuTlOkhuiYb71OjeFC1hDJ2oiMN+18nXnW8dDTo0fTbgxPCvGW2rNsImozKTBHNUEbDJW3RWzLq4iugLxGnjwU14L9HM9nzg27PXv22Ibdn//8ZzvcxRjC8XA6N96sz0CZKA5M+wz7EufPn3fUzLBz5063/8ABt33bdoszWEtNJJ4W8s3W/QOhEjdGEyIEM9slMEuLnQSzzIsxHbhht7gC58xqgMdkh2IIGF7NNFC6+T/fSOKG2X08HHn79m03QX20qowgfJYsz7ClLubFhWJuoNgbDGHDLs9kKXPPc0mu5jgg2bDOqLkkLYTynaFHWC0XlPayKdNM2inz169dNxUgPCVSmMBJ/suIkj9xYJVwQO1XbYPFovv+/fvuy6++dP0b+zEx2WGPRY9ictPd3YOFME5kvJJknz60tjCe+KYXtz+10TjMcgpcrGqtIarSEr7KyBAomHKejdKkuPbkQKO6rBencJaoJAPyxiBZwX2gJLISJMAaGvwszzAhSkR8RvAcDqarSKMgoZ0DS4puBQfE9PnhUh9tG3ZYZLKPYtUbbfwFa36IVwhapK9Q9h2dbTZO1zCxJqCjy1lDvJpOqZjclKMhXzLeoDVsgVq/ffv2utdee80dP37cnT79ujsBdZgDAwMG34t5zKaNG20+w/bFtPzMfYHvY9+aDCz9JA50JAfUXKqIjyWct14Gtwy6Iy+/7A7gtkEfFtTHsAZ07ocfcIj1Et6ym0abqMKSwhIHOo8DnEPpFhDd+ZjReWVpF4rV15S7CT+m+thuvl/X2+d6sWlHY2Mt13jpaNIU8Nt4XZG2Cl+pA8vwZI5aAkg7/601I442LHkUST7xGSuunVIrn9pWLd+iRLWRIaQZmIrESibiK0D0DWX1GuCpfWHDhg1u566dbmR4xG5/sgwGC1xCK3TrMNfUugDLe+bMGffnTz62m+m7dlI1JuaWTBfkTW/bSY783BLYCIM/na2zfOA3U840BFdbAlbiaqh2Du3ADbtWszOqvMjJXEzYQlhVh8SbbfehDpPqASdwAlGCR9GVaBBP7KZ/MUYkMi+q+nkU3rBT3j5ezYR5KwVzbSUliylF47TqLBpDdWYs66ktyxeJCaXEy5MfhEmvaOZgYxt2Fy+78bHShl1nVkmiOnFgTg5k/Ss232jYwyqM3SrHgjs4RfTll1/aRIRt6P3333cHDx0yXfR2A5Xp2JaIoIGZq5eO59gZDRm+GHs9TPXC66VVuOwss+RYNg6U6yyui3KciKoXznjFCQ9thclWXMAXg4SgzBKokiJCcpoFCSZLJEcGoYDMzibtWUgzjvr4Gqf26WpS16W7MbbljBWv8zzLRJf9hKwpKcIEV46rF57nWOtSmtqYxYcId5lOzV0cPoa9Okw/i8lLW5ti8dQkDCvPgXK91o6NFTSWE1WArJYgzeO5acdvxxfoNLqwAdHXv9GdOv2a+/WvPnC//uUHbh82IrYObXWbN222eJafvOS6SjcWbLhww4Um4pNq0dXCo1SOxIGcA7WdQ3d3rzv00kH3xuk3cMNuHxZdp93Vq1fxdwXuSRxgrU0jfE31RwJOduLACnOA8mrqxDFP1PdukuHFVYpmrX4FgXNS9Rf+JiPf1aWhdp7+vn4ckOmOMiSsXz80l5JGEHQyj8ooDuDhQ0F0CE620tPODCLjeO8TBkDZ3IDzgyJUln4NOGKel/lArtj6aeDT49nHuOAz7qYfTRUvPBifWsNDy6+K7xn6vP5yGfSyo7SsTpWFYqPbdnxXcQQ3y7cNDbsJbMQx1dPnVO3J+aAytcQeIYOA4DwOtfz2t/+Kg1/97ueYZw5s8YfBmMbyDCTFeTKpnQkzHB5dTrmXREbNz5DIGEtVasVnBaoCWvawtGG3CJZzkZZXW+/cvuMm8ZCkBM2EVvW9CPyVSUOHyzjm9xgbKLz1pLyXKttKWlLgvDmgznDeCZc7AWRLt+dEs8kbbnTy0fmbt266mcczvqMFbZS79uralpthKb81wQGN9TjuU+hrEc72wtvWn3zySXgLZpPbjFNEu0d3Q2WA3/RmG+LmHT+A2K4W0m5EwprgdyrkynOgsmOvDOS8vMZo/FCEwSC5x1DCE7za4Faa2M7zqMgsBkzuNc2BZ/iIfIqNBT+PyeVsIX3ummZkhxY+7h3y2u/QwjQgW/2rvgFjUD/DYF/rF9R0S6Knt8cNDg6aiqN9UFP0PlRgvv/uT+2G3Wa8w0sVR7HhJh9P/G/ELbsN3Rtsw475VeUZp0vuxIFO50A8XnAhff/+A7h9+gq0aAy6mzdvukuXL2PhMt+sa9QeO50Xif61xAF+q+JWHd6aMpnmIJr6/CURALCVzDXcHLPZz1AFId/KpInnMhawIj/5LMrT64lgKL/X0lygTqWw8vAn7QbcBJ+anHKPph/V3LAr8nC5ap35sBY5SwSpti7lD6Tn9OS09PX0uR3bdzrOG6kqfezhQ/fi6RP79mfaTErYd2S7bc7dwFg5i83K/fv2u5cOHbZ3knn4y/LL0GepQclSmSyzUgbiQym4jbwrvGEXM67ZimomTS2MBGed12+JKhBMyNe8ISwmRWBcJaIg0w6GG3YT4xOmt5yqEHIDBBTUuFfLIxftskYRcM/yDTvbsNOjDESf02hOkLMuoiVqQ3PQEuHJm+EcaVhsz8CYV3MmWuUAK8uLWKCLjFYNx3XGMKWweNYn/rgA否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否iQFPxHJKTyBXptcB0Q2zYZqjtzInSfO8syBKl02F0Iyqkp5POtVdvOA0yTUqD2G/E/jkWo+VP0I2gHoZ/ugau8nmE/xTd6nWFC2j8CgzpAbctwApypZTu43Ql3MRiwac/GYJ/34rszAwGbX091bmKep/bFs/r/KRJaoXJVFaPtAiVSzdTL/AuW8mn/aJUwRyGKPWVWDbBs87cu5MTchJAOLoYj5MNuq/BaDN6VNHFgsByjf7G/70B/u2LHTHYHKbb4v8vbbb2OT4Tg2616GCqMB6zsJ9yxuExJsEOGfXOAGuD8U4enKAfp6+4B/h9sFNUibNm/K+lnr65k+jE2LLU9KnziwvBxAz65BNMgwvdbfhw5/8+bN7ig27F5++QjmKE/dV1986X44e87mLJ5WjjO16z6pTSxvTabcEgc6jwP5zJJjKd/JHBoagr0Z3zx+XWCpy5SP8nPnZN9SAYz9m32r4bbYi3jDjkVay6ZqLmRjiR9QyDd+73LTahLr93y3sBvftl4ScnloNQsXgtnGwnCoi/RIVvhNvgWqMUfxnt1VXOa4d/++fcer6LLzMviyy89yf/fdt4bj4MGD7s0337KDYOQN/zLNEUiQ8wXuIhqLE87mbGLrTLNKNuyaqYBSLWfVnIfnLl+ZhlWBZvt8qAKBaih56+gpOqrMlMioFdgMsnlHjDPQwgYk3BTsqckpd+/ePdzemDCVJULuk+bLUkrD9As3YkgzGJTRfNI0gzfBtI4DqpsgLebNZYb5UMYo63ws9FbYdCjmT3jhKcYkX+LAqudA6FizFoCmxPagE2c2+UCny00J9tOfffaZ++H8OXfzxjXblNiKyfkW6Ky3hS/AacNu3Xqqq1IfShtYs0zKXPURWjwrx9Kfq6qMkdCtPAgVu+lfmNFDxtX4GuURx8V0KjwOI21lf316Y0hhqw8dx8QpGR5SxwNpAFGQryd82gRQ3z/CA7h8wukTZQcjgDenC3GM9iAID7AMlhAYsGIEagDmYQrL15Laj6c9y8TT18WbFPxfulEB4GC0EEWZFp6cjowe5oZ4zokez+BAEzbkpqemsEk35cbGHrjxhw8xqcc8BQeLHjx44B4+HLc5C08ZzkA1yBTgOK+amXlsm3bZqU3SBkb6N5l6TGXM1qGtbmhwyB06fMi99NJLuE1y3O3btx+bd9vtw0eMV9vjx4XaRs7/vHx0ZWwtARRrReUHUWZoKywEZZbCBZtFtMwh+WoZwgzR0tGcZdGko6qMpK54nreIjHVpG3aq1BDt22ERtlmfarNZ+ASXOLBUHGBfto6HfCiU6HN5iGHXrl32Jsivf/1r9+67PzFVQ924FbcBp/R5C5njjMk/bPZpbENsWwwjGvoZwDDeZmaY9e3wE7qnt8e+L7dBpWY/1CCpP2WyZBIHOpEDHB5s+uOF34pAmWe4l2+2AocbLwNudM+oG9q61V24eMF9+eXX7vz5C9mBbcJLJZwlwE8+L1JIshMHEgcSB8QB63SCx/cz7HOofncA/U3/xn4oC/EqMa0vQSfD72rC+P6G6ekXvlpbUfrGtEE9AvO5+vE9D85DFeZnCPIV7ec4lPkMByy1bpHFZplnIWvDwcqpNHk465MbdhNYs+d3Lzdoi8bXbTGsNT5VS45NdPmYmPzyHM+PaYT32m224Ibdnr173I5LO9zE5GS4ZefHzxx/lQsHx55BOyAO6166dAF/l9z9e/dxk32TiSjfspMp0CBSQ6RkP6ZZ6WrtHGdtHENi5IKNw6pTLWfoKtmwWwjLvNDNmZL1BlBaqkLeqmNDm4B6s8ePZyMUATgKWWqn3bDDAtidO3ewADYGVSd7F5BlewnlAgqQkrSQA+z8JOtEy4VTngS5fu2au//An6IodKItzDuhShxYLRywSW7UtXKyw9否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否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否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否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否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否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否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否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否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否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否DwFOIFFO12vYWRMl6RRP5CONJrw9CMzR0+zhL/zWLBbwlQuclRs9Fhh1qxZI39yao4ZpznN3OTCdzhBSfWWd1vu4K/F1Rw44JqammSnxfi4L44y4UGrZL5346WSx48fd508ndfb7S5fvoRdEeOL/GXG3hB4lRFga+vrXyUUWFe5O+qdd951v//9P7i33n7brYcAJnUYgZ5N+h0v5CQLRLBwAZ/7nEhDI50X7zRZgZMBEOBGRkdc28OHaB/uymlapRNi+5kZAgQV+BJh7mLjCeh6nFSmg07mekY+D5KedWb8jcoQMAQMAUOgBAH2V/x78mRMBl3Xr99wN2/edh980ANZd5P4zb9PY49K49tv3gnGzTE81c62ngOrIajAZB969uxZ98OPP7iTJ3+SXcRUOV3DNGKgi1000i97Xsot3xtoTEpjT0NgMRHQYWO+FM4/Ri3H+pw/R+NgCBgChoAhYAgYAuUQ0NmFcn5041zxtq3b3KFDh9z2Hdv9HEUl4gVwF9kCAsCEaKFg/Ni0BvtDzDedOHFC7q7rwhwz3VLD7zC5IUVk+di5+b8Bc1v84ybEF86gUHI8yO3zPL3WjQ2WXVg05qlPXTh+ft+UL/VMD9M4hr/xF7COPOcFO20OU1DVrVwWT+WX0a9I7r5QRXUyqw4SPwXsB/ZprJwkWL16tePCmqrEpD9XquUZ2DP8IFaQb9++JfdpbNq4QSYxyJGq9VggfCpJmaWX92MdO3bUDQ8Pu2YcW75x47oU6CzVZjMEDIH5IKC1Le5Okgqe1vI8d+5mWb16ldu/b59759135G877pmk0Y5mBU/NgTHVDshFvzKZCTUE+EfOGidf5B4d3K/W0d7hOjra3SCOhqtBMG2C1MmeQIC40ABeb/Rd3uCId+5io174lSsbJF/kPkElD/SaE5pvwdsehoAhYAgYAuUQiI1u3pNtqKqbpA+1Qdy4ft01N192R48ccU3YSVwV1MbTX5vg7LyzutBXG3baM1r/xl8u1PFuAT297tUec7B3+9Yt98MPP7i//vWv7gwW7drb22UigvI5JyTybb2PM/0kuuRjz2I1myGwZAhodQiFUV6DW1pep0oPy7qV56kQMj9DwBAwBAwBQ2ABWn2X1QAAQABJREFUEeB8D9ix687Lmz4OLpht3brFHT500O3Yvj13ws5T+E4/k40pA4fO3xOQs7elzsFJJeZJbAanWYHJLdH6FLS5cc6ZsnrH43Z3/vx5d+vmTVmQ0A11WZr1OyJjH6f9LiICmqF5zIt5Iwt2DViwwx/tlAm9XMhwyiNLZnQhHZz5p2TRT8tUFmxWNuEpbDNbngFj8mM3ecoEJ7SL4YRdDxfsOrvc8MhwtnAc2Ci3PK8FfFMAkvhYB7T+cdzIeVou2rHeRBPoY/D4rrVVOcQQiSWGStwWx+qPjywO7wJX/aiC83N6ZaXRisOn3mHHU3a0q2G+8c+n3qsK6u3tc1evXHGXL12SE3NKy5MfvO9OqaXicecw/pHnFjTsx14/BtWYB9wGLPSZMQQMgeeFwAq3dt069+6777l//Md/cu+/975rguovtgVUszUh9ZZtBGqvTA5S5aVfPGI9X8FjdjAqEPFkHS8ybcGp2wsXLsgdO0+fjmUfxw7NzMwR0A4TuFGIoY5vbqbg5bx8zxvD否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否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否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否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否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否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否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否fHAFHwBEYTwTsPfoWvPCsieQtyqGsm4dz3zat3yDvwwTm2jVrVVnHlZHkNZ894076wJsYfxnSG2Dj1WmhNin/izmPq7o556lVjkULpQ07y3lPnoTzodV/vGrs5ToCjoAj4Ag4Ao6AIzBZETDOz/jIuB0mr6Lch4J7WkfbAlnXsqVLVZGSxcVidPCV5MnIZ96GKUAeB/QjNirs/mG37rLjeV5cNEZ5Ei3/9CeLjLM8/M4RiKRV+J4h/aksPVUWge9XQqU+wSh3bFGLx4nVwOSiNl46YJXs6NGjcvrkad1lOrY1fLnSiCvgVsz12yppKDdZ1dc36B+PfGKcyeheSWFnHcyGWydnIMQkkfmGOwt73ahZvixlqLzN3+KG57gDw2AKWQT/LI0RBEuorqqWWRA+z545Sy5fvpweKG8f4SEtB2WihLMimTjPGZZhp1y86ha5WTpkyA99Ksx4nlwpJpenWLW7BgenTofikLuLKPxmXlTgUQAe6mCFWTuS/koaYxp+hjJ+OHQ1QRAePKxxxcoVMqN5hmzfth3lc3v4T/Lrr7/KeZwR9vjRQ6QM6wn4AopfQoaF1WCoa1xPa6/V1po/VNrJ6G/ttbYS7cwvaTE8iIE+JX01qK1ZBhpkdBT60hDkBBHy5DjljromKOjWrVsnq1etwt876ON6nIs4W5qwq64W/U1lnaVWO93IXWuDbIbrUwuztOn6BGtcmmvUEosMr7Q5Sfw0KE0f0jHeM5jl5G45CsVKsSO0ZEqJXLl1RT7/4nP523/+p577GA4FhtIxWTkVlaq3Vt98f39+OQRCN7HXuMhAL0lGgf6KoTSdgYN3Fy1eIrOwg4Lk0Aezu+xLhtGxP7mjuKOjQx4/eYx+LdZ3End5hlySLP3iCDgCjsAbgEDMP3Ouzt6jw8+3k63pxocUqjcXEnEBUVtbm57TvGvXLlm/fj3mi+b0vOWwsjmkZl7Z/J1yCphLgm/KMuAmCyWekYsD1DvfIyd2lBBTVxo13JDLZ8n6lCTjPfuWfzU1tTjLbpm2iQKhjo7bwl137hwBR8ARcAQcAUfAEXAEXhwB45/Jb3FxuTn622448oUU3C9evFi2btsmS8GLVWDHE51Z9+Hiqh5sjKAVMco3d+/eLXv27IHi7gx21j3WxVcUbJBvG46XtfL9+uYgkFFV3jcEmkhaSGkQREgS5LcAlbvcWDNtWgMsbVSCFvXrQEFR+tFH/vAbhd8OcSn0j5812aCfLMcQxBSWKj8sxOD3SHKXXFmXp3pMkGAzRzDzyjFw+NBhOQkTsFxAH9N7fB9yGqdfa6A1WKsRPPlr3lwE2tTUKNNgMa68vAwhIU7uRhQmtgx5j/TsyMQZZvY8HtdXUtiNR4XHokzrsvSqN4kAOt3oKlKJAdjW2ipLlyzBC7xXLl25jFMaB9cw7vTBoXk+oI9MCGFh4WVQhMnkGVZ08Gy5ixcuyN49eyHILsYAK5PVMF85HUoY0hsVblplEptRmV0tS1z5gmFZJEl9uWgqTanJWG/udikuKZYGrAquh1Jn4YJ2nD/Vrgc4luKMkfLKcrl2/Zrcv3tfTddR+B52ZFG4nhF7VKzfjgAB9oL98e1PJPU56cdhaSqJz2LYx+w/bvGfCtOp1dgxSeVue3u7fPS7j+Rd7Kgj/dKsKv+KuFMzj1ZsImIltCZ2TfJnOWPtqODhrk7WjQwZnZ5bd+aMfA1TmLt3/yBcDUXsiBuVQO7GAoEgnATsKa3wzhwnTppa5S67CkycPCOztw+769BLxVC40vH98QQHOj9+/Fif+SbRPuYuXmbszhFwBByBNxwBm4bflldeGVY/z5s7T7bC/OUO7qzDgqKZs2cpX8L5njyP8SLG/zxLTGzzczdwSNn8YHcMGSsX6pHMfSiU9bXy+aG4DEKi21iI0nm/E4tS9qUKO8az否1VXb0cR8ARcAQcAUfAEXAEJiUCxlyh8rw1nm+otlRVVcg8nF3HXXZcQEVHoT3TkVMjD3b79m05dOiQfPPN11DW7ZVT2GX36OEjlVeUQ+HX09PjO+sUubfv53n0FcgxyNVJUbQKQrl5I6zgVVZWpbLKfOQiMs4PGrVn+760Akj79g3Sj2否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否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否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否sWLjAQeUCowAAQABJREFUbdiwAapAN6PMr7hly5aJBLy6Vk9iSTmlfEQR/wiWAmZs5RlDoB5ZnuyUVqDxFsVba8mwl8oBg6zuNJkQF04rW1QDSsR4xKKgB84jFkiLLEGnsNWtL6ekHfCny3jaM84d2Zrbwu3pk7RgPHVXtve3h4V7dyhnKBcJyBN/8GOZKXRRHwppEUNOhHIXt9YZabiT+/y58+67776TU3XdaAfHjx9zvb16itSSj3Mb6kXSspIrRUxncdPzGSFgbcJA925pEmwD8PfNNdf+NT+8t9I+lO3z23A3J+6n4z06iFTt1QWQNd+dkAS+pffv90Od7iV3BcL+ftjNFL5j5pueCYGEwLNCIOuL2Q/7t9Re1nEnKm/5uKkT4dQiIH04qkj6dfbx8FA/7eOtujk+4sk6Cu否eDfpT997z/3jb/7RbcR4jqe7aOTkNHnYmEQY0x/MxZ+JCSke9pHxbqPN+WuoxbEcxb55u9EYf3Pn6eg2mjjM6C3M3DHN09sD1xJs7Ttn+HlIQmIhLnw4zhVYWQYfQJw5n1i0cLF79+13Xdu8否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否RATaJUpgo5CWP7xiHUvhHWHDh3CHXV73SfYuXH02FEInXvBnwtJj/Fb5Wpxtw3Vwj6yDOVLFDKWD0juSkGA75e1+3yempqa3MaNm/Ae73IrV6yUwQlpeGBSVGH694Qn6+LTdWwfw8PD7s6du6IydWj4oaTBtmkf33xayZ0QSAhMPQL8qsR/VNnNb0L4yEx9llKKT4AA605NZjMfPzwITlqo7aC6ug6ntma7HTu2ud/85tfu7bffdrMxPnuEPrsKMwbeWZcZfsT5Z30304Gb4y76+m8ArFNstLz8dg1jc9EwvjF8MjvcaMLxd5a3Etg8VW5Z4sk3CqGO5oiq5lbHq5iaA2pOzqtFFc+GjRtcV2cnNFfUuxFslOJmQFOBM56ckfezKcV4Uk80CYGEQEIgIZAQSAgkBBICCYFKQSAbGctyJp3ecKzdAe1SVMU4FxpJbA2L8wsZpUe0Fmfyn37U7udbTHtoaAhX0wxj094ZdwZ/169f95v2kCtecyQFmWGjfSkf8mzFDfNO5xowR6XAbjkEdqoSMwPewzLqskVGPfm1M1kck8Buwkhqy9HpszIzH2NtC97aaHRnLCXvK1eulMWD7rPnoDbyjAjsSFPPeycw2eYCw6gtzBJ4xk/mP5TPvyiWZLbowawWBsYqEGmnoIcqFAehQ/fOnTuu+2y3YMBLIpfhJeNpHb5slI7LPSm5N4jcmQS97QW0fNgzzk85P1vysHB7SkcricDH0g5FUovyLwoUFoGUrgJH5jSBrURgZ0Pphp2y83FyUZWUDC0AT1k4jfCO8WAeFSNd5CmRHeGp0VFqssaPPJFINfIku+2xeIclPLirRVDHnRqXcd/YSahypYDuyOGj0I18Bm33lLt965ZX68pFMaTrIbIsM8F83eTbi2Qq/UwtAnEF+ZTtXbdnQYbY0LThQK3vHLcWmw62btsqu4uou1vY4YfVL4c1Ybf2xZN3VM9Fw0XEu3fvuv7+e9L26GftI7ULopFMQuAZIsD3WIy+j+HbEiUZv/+eykcpcEUxkrViEMhVLzthGffAn0G+C5c+V3aBwo+bcdgHr+xc4bahT9+zZ4/bDDXmnISSRu604y3l3hT109amSjYPzZCMsRA/a1tGbBk27nxqmNEaZUyRt9s3RGKjkJy0cqzJnaU9PddEUNfV1QnVNUsL否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否1UUMOOteJOXGkz3G/hfGKLV4f9Rhrb3fv3nXXoFWEGjXOXzgv12xZXHKaeJ6M29Q8Q+kN1ChZzgObmppcZ2eXXKXFq3lsbmhkMiuSQudKnnMafSU/K0RgF6rkmWNVos6fKM0gnA2xNO/mH9pAXCTYuWASwnxcktRQiOIz1YFdsK9CFREFJt9BSELp+ONhi6Un2WxyHpKvIIstCsUl1aJFYIBI6PjDQDwtNJQUfg9wius67h95cP+BLLx89dVXrmHObBxBXuxWLF/pXn7pJTl5t2b1GteKu0hq5RiwcSJr2n2nJskgHY8z/dWqXR4rRvIkWGouMtoMYN7nRhOlknOLU/0yoszTbBKWEdCmLv01HJQRfsWDQkj66LFm2tlJm9GwjCdPZ+IMnMTlohbLU101S0455cvGe8T6+m65g4cOuSNQybp37z538eIFd/XyFXfzxk3Xj7tPqpCsxSM/QS5LXrJh4XSYoMZnHpsasrx51CVO+qkABHzVVGkjQob43pQ2rONW6IrmnZMbMVDhPXb2/si9dQhnXLKSy3ehh43s7RQe2wXV4FJF2dWr10RIn7WV0mkm34RAQmCyECh8s1NfPFm4Tj2f+HsbUmf1ssP1O5gotLH+mTTcOMGxEndcPoLAbi7uqHv33XfcL3/5S7dt6za5A4ACvbyxPtrSNDfpMj+LpW0sbmlGYx+WOEx4SKY1fhaW2bRQxr/wyRN1FL79+c9/hprl6zphvXbNUXhXzlAdO1XF3MQ9qhwrUe06N4CNz2RjmfHRj5dKy8vdsnkTfPwYVMJBHr+/ZucElXjLEJA0EM421De4V3fsdPPmtrmVKzrdb3/3b+4ANmT13+sXzR7kx3g0xkcc/LHENXvBO1kSAgmBhEBCICGQEEgIJAQSAs8DAmEZzBdGVrRs7MvxtPendg5eG9DZ2Rk0kjDI5jrkw7hGL+NyIfAM8MjSskF2FpbZjIP5KK2O1zWM5y9kBF9T5W5Cc8jevXvdF59/4S5dvCTaNhjKzXmWN7or3Rg2MTJhamSBKERjY5NbtnQ55AKdrqU5m8OVLSsgI2rlVzkrF5kKEdiNBlBcXaPRzZQwe/lQLkyquchNw8Y1f36b271rlyw0cJJNFZF9fbpDmLH46s8ENOxdQpHE6BOvBwKs9AwQAQFhEId2Z7IQBCKe5hp4NOgGcOruBhZVLly8KBjNhkrMJUuWuotY8O/uPoP7/9bh0s+losO2EScVuTBBtZk8eccjy7W4b0vZl0JO/SRflmlD2DLqhWLaURgPewrrJ/oxtvlI9CdXzQZwMkJ5anrEURdz6Kk0gQ6xGc52ZAtuijvjKDOG0c6FrHsQwvX334Ng9L67jN3pV69ecd9AUHcQizgHDx7Eabs7qAPefcI77zxfTUAEMcYTQWKsc7Qn228yzw8CrFf+Nbc0u65VXW7turXYVbRAVIo否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否mErOmJckijB7ejILzsWakJPjOqq+/Nvf/ua++OILueeZk1KqVOZ4eKLGvn1FhZko4wnEz8EqnLQ9RExLgC13G4fIJOAf5g60+TuzKbxduHCRe/31N1wLdgq3ty/ASbv9IrjswYX1PBXPbz5jEhuys7YFazIJgYRAQiAhkBBICCQEEgIJgecCAR3nalFsXSqbG6g/T3QtX74Md0GvwCm7eWGTYxhyR4N0+skQnZO3yL88WIGLTqsYjcT44Rp/4CV+9ISPeDqotse1Rl9/5b7dv1+u07J829PStHnkuLJjkabhyXKzaLa6KDjAbfM9rity/X4+1vmpIcbqi2owrYwF2fYHbERNpudt4cbb3JX6rECBXQyVVVnsV9n2J6t4UmMajIdIg+HCWRRXXYv77Do6cMpuN168XpywO4oL4g9iwXsQC916CsoaZ2iZ4+4QphG/UcCx8vCZ72DGs1DA4j/EjoLrOA7Mu/+O41QiX2guTDSgg12C00H8W79uA1QBLcZJvDVuERYsqEaTLztP3s3CXw3iVENqb8LRahwz5qKY9IkmbPPH7Xw/CUDNVqqAWh7tQIzOnlldqI92TZlvoY2LKAEbNJrHWKjiTgLxRzsaHBpy96EylOW/eOmi3M9CweXJk6fcedz51wuBHQW/FOix06Oawsf4q61iefXDIP7kyUY5pmGudaFvXORj8ksElYGA1j3bGtVaso+hatlVXV1Qn7YZl7uukBOqeuIS4SD3MeRpbZRtgouEtXifBoceybvZ3X1GBOq2+4flNfrKKHvKRUIgIZAQmB4EbBxkqXPcwH6UX1oZHWh3K8GktRFD6EO1I5YIEo7xzKz6WpyYr3VLFi52b//4bffP//LPbtvWbTI24lgg1jRg6ZZ9MiNTYjAGwdiEG7P4reD45Y9//KP713/9V/l+6PglGg9NIE8c+zVhDDh79hzs2KzwKdEY5WT1SBMIFs4v6EBLgapL2dgnY79HclffSmihaGttw9h4qaiQB+pu/74B7Na9rcJg0PLe2dC+xkg/BScEEgIJgYRAQiAhkBBICCQEZhoC否UqlW/Ox1pamkUTx/oN6+XQB9eZaTjKjk1YE+UaGg0fozEXojI/XHPGf7/0LETZuniVXGHUfa7bffb3v2Pj3V53E1qsShnmfyYanedmJeZ8hHdwd3Z1uuUrlouWECuXzovNpc8Y9scA0YR2DI3DCmNVnquCZ6fWsgxOc1caiJqvglz6r否L6+U8IeMMFhL8GM0jISIXjcxG14SOYduOHe6/3boBSf5c9wV2GN+8eQtCpRpdbMFE+tEj1eMamIduw3KUhVSsjTjwtcll2ToXw2i0/CsNF3j4xx3X2R1sd+/edf04LnwNJ8546qwVCxSLFx+QF57HaZtxAq+5ucW1zG0RvcQtc+e5FggouPO4uaVJOoOGhgY5fsv6sUWy4vxo3Y3fv5iynA87KC5ecUc5VUDdh3rLvlu33D2UjW3iLvxuY5Hl9u07uI/ulghHqD7q4qXL7jKOSff19SF+hkmcDvdtVKP9hdwL8L4y4joJXrFnzCnZZyIC2ftVXK9U08sByvr169ymlze5pcuWinCbQjx9z/BGSMPhwqD96fssgwuoC+CufgqG+e6dgACZ93OWOtU5E7FLeU4IVCoCHEPQpMX2Sq0hzZdMMFBXnPhRxQcNBVIUVHFcxHDb0MUwrU8J0eEiO3DfiZPW+mHSsgW0zmtzG6AC8bVdr7l33nnHbVi/EWOaRqHjZh9rJ6Q3Qx4F/jY4YFJGBL9o1GC+xc84ThYbdGQauInbN1l8H6AlAGOWi7hv9/Tp0+67775zVIF58+bNSf12sJw8vdeHe1Xv3CmtbaC4QBP1YZkN0InyKkRQuBHEEknoZB/tg2mTBn/coNaMce66devku87Na12rutzXX37lzp09624BFzNsD/n2ZWH2tFLFtWph6ZkQSAgkBBICCYGEQEIgIZAQqAQEOGYtN16N50Fmb8FaPMfL69dvcK1t2Qk7GVNjPiG8ZD7mR8PlmJcqPMflzI/nIy6LD4fy9hGxZstN87y7jnOYC+cuuCO47ujixUtyJVKpUkm2fPRKf3goiEZh/QAEzkO4dr92zVrHqwyasV5vJsTLFdY04hFTRdlizJxnBQvs8iCyqc4AmEfJor13LBkblS120N+aJAUnNNzpWjur2q1bv1Z2/3Ln7xlMoCmQqYbAjrui5XQVBHZ8udWMkrinqLjHJGTZXlA+DQp7WnkHIOgahoCLgi0uivGuQC6QcXc1hXG8QJRHaxdBVebSJUvk9N1i2NsXtLv57fMh5Gt1jXOaXD1oeQIv7KrwiceLW7Hd0uczX1SrNYZldUhX5ubCHcOGcHqOgsdbEMZR8MhTdNx1fu3qNXlSOHfj5g2534+0/OOCHwUjtFOwqyqOuGCDf0jc2hz5hxN1KI/k05dLaOKMavbS7wuCAN+Trq4u9/obr8npOp5EMENhnjYTNib9Y1uicI6GYnPdewTxOdriFQiOTxw7gefVSV10lcTST0IgIVASAVtkLxmYPCsCAY4nqA2Af6wvfrOlb0VXyjB+u7WLLVb/KL0tA/nlBp3Eg6u+vl7Uq2+EVoE9ez5w7+95X+745Z0LQw+H5C7fam6osAg5JNiX58O0Z1dCG7/mohU7OaCIIwpFfjSURePGJArrjh076r6E4IhqMA8cOCCbkPgdmWzDMhKCMjBMcnIGhK+vSeYe2BVgrmlKEwEBy8tNfzSsY7Yv3mu3es0at3TpUrfllVfcgvYO96f//JM7eOiQbBQj7oqT1hvjjWYKkh+NMIUlBBICCYGEQEIgIZAQSAgkBKYBgXi8qiNcrF9FY1zODejkGLgJY2Vqpli5ciXGzRAU+YmD8iBh8RztiYtkCSJNrqFxkx3XYv1IHi78Q8AINO49uN8PYd153EHdLdpHOH8iJfMqmX7ixCsrQphpKMCSOZaN6/XrIThdTYEd68GM0VlEuOn1PJgZJLArBXdUI6WCp82vfL7YcOLGw4Ynr6FFkTxTHRDvkMAiDgRKS5Yuc7tfe82dPnMawqZqd/7cRdePE1akoZuqg2jnax24WyIFfIX5jP5hsfJF0o7U+iYtOPsqIyS+xMcEWaUA4KLFnDnQhwupfQfUkXbMb8e9Hu1uXutc/M2TE3iN2JU+GzpzuRDW0DBbhH4UaFAAaIttFKyyTmSRDYsittgmnSfypLnTjwHzxD92sOx4qXqIp+D4pKCNf4NQgzo4OABVlv0Q2N0ToV1vb6+crqPO4hu9N9w1COuowmgAtI/KfCwkXZSROdA8AEX9L3AYppY/w64UVslvBiNgFWwVPkZRKNBevXq1+9GP3sJC3hZ8GFskBtnwpD+Pl0tb8YMDYU9/vJS0ixvPh1iA5p1Dx48dc1dw6pPuZBICCYHJR8Deucnn/HxwlG8ximJPK5UJIexp/s/qyfTtRB3tNhbQfJmwjMIkjhFZq4WdNn2orBtbenwInuh3qdqbk8pVXavcurXr3K5dO92bb76J3YhrZIwj4w1qZgADTWuyS2j5HH9LlDIi7/fvPxCNAIcPH5Y76/bu3esOQWjEMQ/zPdmG+PPy+N27X5P7Wblrc6absVAvVAejdcVT8Bz/8o/qrzlWbmxsch24t/b8hfOYc5xXdepecEfc2NbYZksay4Q1hZJEyTMhkBBICCQEEgIJgYRAQiAhUDkIcKWUM6t4Pshxb+u8VhzqWConvOqw/sshbjbMzWxZSYr9ZNnMxshCWOAIPlxWwyJbxAq88J/COs6ZuJHuFDSQ/P2zz9z30EJyC4dSLL/FqWZsZqJNV651rst1da7P86Qj1yd5wo7lLjWfNRwiFH3xzccoKh+VChbYGYgGah5MC8/7T7Vb8xHEIGg0ZkLOMy9ZdrFweYLIGiLdxo0NUg0aISbTqyBF/pd//hcRKP2v//3/u6NHjzGimwUB0UjVI/cQ6eqLGiXmOVT0Y5TsRlBKEQKeJQqU0ZZnKJ1fCU7EjffB8VhxT891UY909tw5OX3HDrmWf9z9js6aAjouiNXXN4jgjgscFODZk6f1KOQgfd0sPFE/tbUquGM98t9jCNVGkJ4J5YaGHjqeABwaGpQ8DIiA7r7cR8c8UQ3mw2GemnsIwd6wGxocghtxBgZE8CH+fjEr7rAyOzHBH9KFAqzs40JAMuAEGevsS0CcvJ4nBKzf8fWff2us7TRBXWxnV5fbuHGTW7hgIRDgwjKO4bMt+xdSxhNgYHECTOTt+Q/iRMd1LLpevnzF9UP4HO9eCvTJkhBICEweAnj38u/15DGf2ZzYV+X7q6n89jFtTv44nqDhWICTLz4ZxuGf3B8q/a12tBCPgBJ16md7HEvwHzd2ORyYYv7ppjaA9VB7+RY2WVBYt3nzS3JXL8ckFLCQP+0ST1Iv/iEfNdaC1J3HrDgmfCyKWIyPUZo7EFmAlP3ChQvuk08+EfWXPFVHLQJUVVlWMBRiP52FOGzcuNH98pf/AKHmG7IR6+k4jTeWlX+89GPQRTDmOUdBygT1TiOfZP7AKfUcRWQb4tzj5Zc3Q9vEYlGP+RUusv/000+gIvO83I1h7Yd1Qvr4vSlI0/gWeGpW0m9CICGQEEgIJAQSAgmBhEBCYLoQKBqe+nGyTGSiQM7HGhsbXXtHu1wTY1oqbL4j42ophB/42vg3VzBVTY+xN3jHsjghk/QoeIJL4vv1WvqD+P+y997PdeRYmigkGpESSdFToiQaeW+okilVqUrl2s107ZuYmRexL3o25k+Z/+PtT/tiZiJeb+zOdNf0vOqqLiuVkVQl77333lASSel93wEOEpm8l7qX3gASb2YCBwfAl8BJAAc4sF5Jpp5jzvjYsWPmL198YfYfOCDHJfnkkgx5rwl5o8UVTPCDctmjempwtNVc7LRrlPGslo3FdtCp16S5jmOF3aTBeMCCaF3MErFCUmDIRAVqYH19g+naWANFT5+54CYxbt64KZM8MgnDKip1OR/HbApT6JnQCDz4sU3ZCkQHATelUYHAFdzdfd2m+1n3a8HhpAYn3KicK4Nijoo6/eMkHCeC6E+Faoko7DgJZ9PnBAfT0gk6KtyolKPwfYa0qYijAq8H/r08d44S6DWOZ5HwT+qAp2V6nFBxEyt8Ul7+o+SJ480UQoCTbjopyzpha0oCAJXMtZj4XbN2jVm5cqVpxq5T1ncqmtleqApgfMaVNfbgx90e6vSONZfyiZOut27dMg8eYfJV66ASx2tEICIwZAS0zQ2Z0RRh4L+FY1he+bLzB39JfvjNhnSVF2qvIjLdN1sGeojA3fQiu/Hd5wIimiuuq6+DsmWt6dqw0Wx7c5tZvmIZFlo0iRKG/Yxe7OS3yjpbW1SOjxUE/A6xL8Tvw+nTZ2RX3eeff272799nbty4ITu9ElyGP5dycHlHB1ZpLpTBOFOYyp8nqU94J9yhyckJ1hWe7TwD/dyDBw+Zw4cOyzmC7K/yvQ34bqRyD/87ixwjAhGBiEBEICIQEYgIRAQiAiONAOfj2R+mGcy2tjb0iWfLRhqmy5FUMmaQp6Fnh+NB4a2daDteCxdMcvzHM72PQmG3f/9+c/369aGnO444sKyp8QVA5rw7DX5WVlaKoq4Rpvt10Stpk/cwjgoyjFmZAAq7dIVNym4rcPI8Vnc2H5pLVQgxN4mfzVsqx3hguNL4MH/DOJys4Upse84IB888EP5v/o+PsQW0ynz22efm5MnTMonOnVx0VOhJJVfG4js5fnyRQoycZyjIUo2cRQeNNGSJZyPwGdCK46QVnT6H71AC+CMkSmmVEFRE2LM9umGi1J6NR6UGhQoFPK/2XiffPDd5R4yv5kx5xhwnQPSPYeokm0nm1Dt9ZTgUglZocSJPgxMhlvghLPWgtPE66RHQeuHqPiu9by9UKLNe4H9l5UyzatVq8+67O8yK5csxYTdDoJGzlLCbQz6mIJQ6jDg8VxM/Fj48o8bLvewIxa4RUdY9eCDtxWNMEs2P94w3EYGIQLEIsCnpN5BNyrfpYhlNQHotd86su+9cKGbsN5Kr9KyM0nijiRnTouzkohzmgvdc8DBN+g6QyVgG0ddHOQp/7zfd7q6zSySwcAKlQmTGp/nCjRs3wnTxW2I+fe7cVpgMaTI17jBupse+CcssMpyFpsKPV+FBLkm98dgocDZYaF77o7girkbXlaS6ujTkQSsC3333nfkf/+P/MYcOHcIg9I4o8LjbcKTeCQd5DQ31Zi3MPC9ZsmQUdtaFJR6Ze8Xag+6TsS/Pv0LceFr/QljnKEPYd+WdpeBq4q4NXaa+rt5swJWmd3bu3AkzpUekzvok8t34hPIRRP+IQEQgIhARiAhEBCICEYGIwOgiEPaLmbLO8uqVfhyD0XLJ4iWLTUdnh1hU82Mkx4A9aDvctJ1enQOzIzRysf46FqJP4rSjTGbCCVflR79kvMp0e2Cx6vad23JcwJkzpzFeeuDHSkw3zHuSxsS5s9iiJCirlAXAElu+B26AaWhqMPWNDZlxG8fL6iyOfCKH3C6ff27q8eA7ARR2ChPBTV6H+k6kq1aPQkphGz5LzEprd07xqQmTMG9vfwc7u2ZgEvy2/NGMo5xd5hKQSq4MCBAT1MQnEmDDnNfs5I9CQnggB0Q4WLByvCHgCcujmCqzolDjkif/ZPINE0zD7agCsZMoNk9MXSbwmGE4m75V7vmddC5PuEQXEciNAOtGtpq7CkP5QZvQq9esxgTw22bx4iWy0p51jWGMxuiirMNEs+y4cxN/EoZA0lGB/ByTsVevXjVnsHvizp07opCWDGXTFs/4ExGICEQERgcByjM6iipxerXeznPkLpSfMhBziXOHPBV2dNxhx53IMtgQOjdwQWanQ/FWVgaT26X2rLo6nLvW0dFp3n//ffPhB+/DHOYyWXWoVgNeoF9CZR1NelMus9hadklslH+QBTi7s47n8546dUoUdl999aUcmj4a2ampqTHr1q037733nijtuKNsKjrXBFB0bQt8Oax7/K6/lJ11zU3Ncm4Hz0DkQkH2gR9jgdr1a9fNs+5ncuZyWJ/4fhO+UxHVWOaIQEQgIhARiAhEBCICEYEJhUCO8R/HT02wMrUai9iXLV0mCyTDMmmfV69h2GDvtQ+tCidu7lArbU+fPhVrF99+8405efyEWGXTdOwMtT5NvKsOw3PlnGNfvgeeXcedjpU4kipxA8VMqCby3Rgr7BTgsIUM5KdQK73Sqv/wXrXBkKudZBga/5CfcsqWwE6Hc/hsy0htvPWj4m4aJmJmmNmzy1Fhl5l339kuZ57t3r0H5oNuyhkgYhYRzGUqiAlqxiUhh5vCp5mYaNcc+Q8nDAouTgYO8rBwISCzDMK+DxvBJ+/fTcEpvp5QefJ9uQrD9JLywZwmduNN0zC5+hyRUqK54FR6okRxPgm/FEl8mPQIqDRhQW1d8UVGNeI5i2WlZbAN3WoWL1piFi9cIofrcvJOHWsbJ5v70IF4iV0gdNTXSZWVJz5YeUXTWRcunDcnThyXXXbcQaok9ib+RgQiAkNFQFphIPTZFuls67T3k/W3qG+Z9ocEDPu9999FgPVavAKMC8dT30YYQ1PyPT3IU8hM8BelGkhtvvi9T2Sv+KEMddjxxLPX1q9bBzm92MxtpbxebObNn48ViLZbT0kvO/1FMFtFHW8JAcM0B3athX3yWIRZHey9pOMia2KSKnNhZFXozp27zO9//3vz008/ya46Rz1iFw68eeZwZ2enefPNN82OHe+Z5dhBThMrk81ZlIM67d8BSuraQVKdEegi8CJ1h8pjR0frHtXVNTgPcS3GH7Wmo73DfPnlF+Z77Iy8d++Bh471R9igUiW8fXC8iQhEBCICEYGIQEQgIhARiAiMOQK+W6w3weJzdmbZj+W4obGx0SxfttwsXLhYTMaHYyXSaH838Xcdaj/S0qJqQvrMuFk/+6y8OP/GObdp0+yiS6uwO2SosDt9+rR58fxFwgx3Pl4/vimycfpA0PnfYRKUgZa8WlvnmTU4Y3tR50IzMxi3aZlZKBvFYaildO81mdtHOnxFAX8lHa/XMVbYDQYW9xIHE3WcxGEJtCnrNVepEjql0gJMM/U4aJETDhxPc3vs99//aG7fvpOufG6w7RMjw1wJKdspfNU2mwjOAYAC5nZCQ35FOHphwVcVRBV/obfgSpALJ6k4fU8uoqXhhIf9o6CWe6UXAqf4UL/M1bOEv5YtQxIfpyACWi+0joUQsK5WVlSaefPmmfXr1xuuqK+pqbIm1GTnB2Oh1qJCcfcGOxHOR9jwe8h6yvrKXaB0PI/xzNkz5ujRI3ImkVfYSWj8iQhEBIYLAWnTYOa/K8PFeLLxcQDJ1xv3+u32AwSWV8EcRNmVn0Yt9PvL9Ck/p9lVWvIiyaukrMTukIPSZNbMKjO7djbOplthtm/fbrZjB/SiRTh/DSsNeWZuH+L39PaIzJZTRRF/Gs0Vw8mgDwsybLnt1eZxCIW1DAr6td8eKg5tOc+fv2C+wGHpf/zjH839+/cL4jFUIuI5Y0a5mLfheRTEjrvG6Ap9T0PNw7iN796LDCpYS+wLIzL2D8/cbdfc3GLmQUFcjTMTn2Kn3bFjx3HY/ROc/fzM9QnYR3DtijzHbYFjxiICEYGIQEQgIhARiAhEBCICQMB1WMN+aznOcG5uaTbtHR3Y4dUoFkxGZqCdpCrDVH10fXGOnbiAk3P9J06chHUSmsN8KPNtJbDE10dzVxPY6aiUxWZZ6Vh0dyuWvubPXyALBxfieLDKygqh6f+jwPUPsS9Y0M0VOK79JoDCbmICO+BbTxUpM6ANwoLbfux4AObiJctQm6ebu1jh2t393Py8b5959PCR3WkHf5pXevmqTybXlYEMwl1dzqSsJPGaD4HsCwGOiqEKFxHigaxIJj3yMYW/8g3ikVontiisNMjzEw/8aFzeOCLmiXH8fMsAScegqYFAWE1YYqmvUkfwicQkru6gq4d5tS1bt5pf/OIj7DxYBtO75QIQ6x2mkuVeqpmrlDwPidv0+Ud/dia4dV/rX/ezp9hhdwGdi1NiEpPKvOgiAhGBoSHg2zPYSHvMwS6ffw7SqeEF0OSbSPAAjspA/YbKt1xB02uRyFBOUp4qT3k5Ig8LZ+jzgSilMH1Jc43cOdfe3oZFFEvMnLlzTWdnp1m6ZKnp6GjHoooa6esxq9MhX+VsO5fvxJy2TV/yJyAUWbBhJL9586acVffv//7vYgrz8ePHw8h9YFZcMPLo0WMMeG+LdQqunJ1yjvWff7z4tqDPLgCPtsbYrz7rTSnO0obmWAhbWubI+bYVFRXmxx93y6H3nDx44M6pZb0jtvze0yJEdBGBiEBEICIQEYgIRAQiAhGB8YMA+rzS7XVjNHfR/HHxeS0WSbZgsVpjQ4Mpx/nX2n9WGttb1ohJHzoJz3dnaTWGjP1ICg/62TlgHtk03UwvnSYL4G/dumkOHDxgzl+8YJ7iyBmmKmNZxnOOfspT/SbKVVFkfjnu4NyinVO057DPndOCnY5LzXxsLOg/yS1ouKLmQyCf//AgFL4L5n843Rgr7MJXo8XK5ReGKQCFvBiNN7jrMGOdrluuGJzYtk7LpU/22c2Li6fNDyfDuYNlmhy42NbWgZ1220xvb68o6g4fOWLuQPtOTTUn03kWCn5cGso7eUyHJP5T6S7Xe85AloYDoFGwhg0zTeCe3CtNv1nGdcJUbnKFWhrhErygcLLNqkfw66PbmzBP2TKEYTnzGz0nJQJSM1DJeWV18p0C96xViBPNjVg9tHHjRrN12zZ0UJrkvCQFxce3TOzHFHzZmeDiAJFVCNOPK5+7ccYNzfXewjmb3LY/3B8wzVu8RgQiAhYBNs+p7MLvefYbSFxUjlEYEqth+y6SMWQoZSHlnMhZFYr6Ulzm+qUp1gfZZ0Nc/GM4zRDW1deZBQvazKZNmyCX35Cdz83NTaLEm4FzjCl7e2lmGEoRGdQh/vQSxmdJ7YDHJsnM8S+HCwHLBg8UlqXN8az5CIMuX75sqKz7l3/5FyjPHiXnmoZEI3RPXNlXvnHjhphopmkZHiZPx6Lmyu8IZWXE2WqV04SSvqL6uBrhqoUuyJH6BxKto9lvNs+3KysvlbNtG3GmdtuCDjHRyvrKVb88q5a77VkflUeSYryLCEQEIgIRgYhARCAiEE5G2tMAAEAASURBVBGICIwtAjrE8f3joONcWloipuDb29pxTMxczLljR1eeYVT/UgSM+gemfSQToM9G4YAE/5kkrVpxQdy+fT/DFP0uc+niJYyd7AJ4hqUcnjM+qeDx+pDNtS2DVdpxsWU1Nio1NzeblpYWLGYtk/EF5xvt3DihQgzBws53psrpX7D6klbvJ8Z1jBV2xYCkyPJacIspJoHRp9UiDZCyTPu4IuuA2pJbDGbOmmlWrV4tO2F6e3qlAu/DTrvup89gGqlXSHmunZiwk0ac4FdA8gPkbIoGFQOao80VxftlBe0AsOaa/Eiie44DcIhBUxIB7ZHk6hQ4mcCOCU2trVi5HObWlps5+CCWYQJO5YadgLbfQtZDqW0QQTI5zZX3cPSnVOJiAn4JOZF8995dcxsTeN3PuBKI4XZSmpN+0UUEIgIRgeFCQBULVtxB2kBIWUnEFCB5KK/kn/OHnCKNk2Y+jNT6NU3ChIWLTYq0UzoukrKDB5cSZSXEI40Hi9xkgrkcxGF5Zbmpx9l0c7B7iYOSJvzNm9cqz8uXrxAFyXzstJsxo0zSIBvKZ7tAwu2sc4o/ZHTcOPs+7PeBO66uXr1qLl26ZO7evTtmeaQZmZ07d0LxWW3ee+89YLsIA/NqqSP5XtGYZXY0Enb1hbVWK7/ioHVb2xezw/sS9Bmo7Fy5aiWUxNPljI8D+/dj9e9Bc+H8eTHVE56/OBrFiGlEBCICEYGIQEQgIhARiAhEBAZEIBwn5RialZaWyREx63BW+BJYNJk5c+CzrsM+8oDpSmCSuO1jJxmQfjhouDnHzvti8Rv+3bt3z+zZs9d8881Oc/HiRSjs7Bz/69OaoBQYhFBRR0tfPK9uQVsbzmufKwtWWSLiTYw87gmEY1Zgm6eRycgEUNjlKnguv6G9Hx2c2kowNF4FxfZF8CNliZbkwxJMg4dQ4JFT3LzHlLcMqjFVY6iMq6vFoHnlKlmtzDOjurFq+PSZM2jc96Uic6UrajUWYffJWFwH4Jaxy63Pj3ueQpcE89cUehxgZIW3n1N5TYbTwTpxRt+Cy5xmEZ/GMwLJ9z9/BWEdztRj7upowFb/VZh469rQZeZgy7nIDJD6joMvd8JA7pCmsHPeJXJeEncx9JiHjx6aa9eu4XybR2Iaiyy87PH84k1EICJQLALS5jKRonznBjcrBIkPb+VKnPhgg/gg/SJ6yYIE8RFfa5WA9HCKMRdNMSr/hD8i+jB3Q0VUyg8fWD4zDmViiex6Qx8MgwuhDcxkTofpwBkzZpgWyN1FODd03dp1pqtrA0yeLzFzobzjYorKGTijrrwCCyRKsBAC+Zlme4PMieSJchcJsn/Ac+y4aMIOHKyfLTMyMwouLeOJgHXsm965c1cGmlSYjaXjzrrvvvvOnIdiid+nf/iHfxCF3VjmaVTTdpVV6yzbh39TvEGAPts+J57sf9fGOFCmqctX0nfYsGGDrEBetnSpKJs//fOncrYdFXZ2AQ/aGup8dBGBiEBEICIQEYgIRAQiAhGB0UCAYz1x6IL6Xqj6qY8PSHJUhl1c3Fm3es0a09HZYWZwh51z6TlUjrc0pNCrTTA1XgIP9sT5j/xpWh4jOlnw2dfbZ65dv2aOHz9hzp49aziGKTrJQrM2RnRe8Yb0da6bWeHREPPaFphl3EwwZ44sENQshnHsqMWiYtElkupyvGANGuZrOk/Dx3wCKOyGr7ADcWJjSzfAgahHMcxlTKuaU9+xOUuG6c/JdZ5ptxo77WiWhitfq3btwtbZfXIYvJhMQuG0EoUNYRRLEpOKCEQExgiBpDNhJYl8xkTmWbnQ2jrPbN/+Ls6lec8smN/mZYXGYyzKDZEdlCV4Ts5HshPfODxJTGjy8Nv79x/CPNYJc/ToUdlJ4WUOhawyHSMsYrIRgYjA5ETASrdMXy7psaPQ9sEPkmi7z/6XEO/v6LIoWaWDUlEegoI/+OMtXR/+aTpkw34XlW7lMGE5CxYRZuFMumr8Vc2qlhWb3NlVVV2FlYOtONS8UxR1S6msww47KvLUSTIcvsH8ZR+UH3Y3HxZjMRHJgmTGy26Jx3zBexrLOcZyl6YSjx49Zg4fPizmKLVcY3Gl0vTJkyey049nr129es20YfXmdCg+FSbiNlVd2GQEBvFgHbeTCaxwFh97rgTPUZw5c6ZpamiUOl5VPcvs3LXTnDp5Ss4LfPLkqYcyjkM8FPEmIhARiAhEBCICEYGIQERgtBHo19G14zVmg3NWVNjRqsma1WtMZ2dnajyWzupQBwt2/KjqJR170HIFd+o8hCnMs+fOmR9++MGcOXtGzt/W9BnHxlafiXsl5jo+4FXnDSsrKnFu3XKzZcsWs3DhQnkvWko7NBkq/sptfF8nmcIufGlhSyzsJehAvTDq4aXifAqdlkArrXgiY0mlJAUbKHwwMU5/W6lfiUkabt3tgylMng3/4sUzc/LkGQyY7wgb7sZjbK56lTiamIQmPyFyeUgS4ng3YRDQj8CEyXDMaHEI5GisKkdURoR1QD/0VPi3YNXK6tVr8VFcgYm3CpEPNPmsHQjpEjCyMEAA+hEyYUwKpgtlnUznIQjiRRYKnDlz1pw6dco8uP/AySgn38JMFFfCSB0RiAjkRSD5couCBnRTqalRxlk55wBSOKT/RCFlPayYtLQWH8o1/JcBQgiu2DIQD8aRP0YoCFRS20iSp2mlphIKjXnz5otiqHNhpww85uHgbB5kzvPoKioroOyAEg/KOyr0pkHJ1wPlHNOjHLa7nnHHfl+JVdixB0jlE/t0lPXczUe5TFn8Svp5NhteC+Uew0uImX4vwvCi7/udFWA5PHjwUHa0ncPAk2cxjAdH7O7cuW1Onz5l2tvb0YdusOdUjIfMjWIedPzBJF3NxfDCthftA2i1t+GuHtqaKTktweQCFXVcOEhTrsuXLzf/8R//Yb766msoR7s9Z9YxcuYe0bDuCZP4ExGICEQEIgIRgYhARCAiEBEYBgS078p+p+3V4sZ3dNXH9WzRP6Vj37S8olzOEW9uaZZNMeH4SMgQhWOt4pymJ6lID5oc6Gt7xrxTnnZ8d+3adbMLC+B2YSPOTZy/rc5SKa36TrxriCtxVwsxGJ7Z94CFq1xQyaMhuLC1DKZKveN4F/OP+AGAFlvLz97rGMM++VgT8maSKewm5DsYQqZtw36JSR1WSu6uq51da9atX4fJoRlyKGNV1Xfmp70/Y8fLfa+o40CcFZpxWMejiwhEBKYWAuxkUAZwdwcnKjdv3oSP4VI8zxK5IEp9zqhRVuAiCwR8J4JCg742jEJEZIkL5/1dmD47dOCQOXjggKywp190EYGIQERg1BBQkQMTkvZWr7lz0F9GBfSut0+Jx3M7qTwrwaqo6SWl0u8qKy815ViNyR1xHEzMmFEhZjxoQqV8RrmZPXu2nE/XNn+B6ejsMAsXLTQdHR2mpalZFER2gMF8ISH8Z156MQhR6whUzLGzbneAQTGHZzGFqWV0JYRIt6IZ/j6IbOH0mSRj4WgS8+7dO7LjurubCpzBuUqcZUCLEjwAnu/h+fPnYg6eJmKKdTwfgYtKPv30z3JOwltvvY2B4YJi2UxJevn6o8KxlVA5zIpXWoZzGLHLjn+1dQ1oG+WmsnKWWPu4gYmGW7du4n29kHMpWOf5/ljX+7e9KQlpLHREICIQEYgIRAQiAhGBiMCII2DHW35whPS4iI8768rLy0wH5sbaoSiajXn1ZIyWZCoYISaew3RH3uxj92IDDk1gfvP1N+aH7783jx4+GqYUxh8bHQdwulDGsi6LVRjv8V0sxZERdXgXoqCTl+ZGszq4zUYcf0UcUo4micJO3xax0OmI0C/0HxJeIxDZ5jPJrc0/6x2dlsY+6a9SMxRNehrN+NCPk0nTTK2cabfG9PT0mbKScpxd99IcgWm6mzdvSeMv5fY7uF7YxJVpHLChMOI/bTC4CWWY0MefiEBEYIIg4ASHb885ss0239HeYd7Zvt1s2bRFduiSTCbS+LXEH+WQShtsQcGDkzMklHtLwLM21THNR08em4uXLpjLly/LmZoa1u/q8pkk0o8iekQEIgIFIDBQWy8g+oQnocgSJRZKotJo2M7NcgxfoZ9FhV0pVl7yEGwqI2qw6KG2djb+ak0LzFjW1dXj2iKLIWpm14jCjiY9KqFgqq6u8bRcLEEFHxV/dExCxeFL3JS8gmIQhbKyFR6455l7LKftq2kk0IGFDCjJAIykLuAqfq5fx0EOn7mTkIY01VkaxrF/9Gca4pgpuuxz6CcEkqy9y0WLECpnuKiM5kH1XDMXteALeTRAGdTR0SGHwXNgfePGdZhfPomz8S4UzEcJOTi/dOkSrFH0yNmtK1asCBR22cJrrMl31d4+Xx3rFp1+0u0T/L0HfNSTdYz/8F6s0wBjamqqxYQNJzsWLVpsDhzYZ37++WfgfRk78B9LXeRYhIuDqDiNLiIQEYgIRAQiAhGBiEBEICIwbAi4bim7sLyVPitu7JSW9n6T1DhOaW5uMosX42iCOa1mBsYZWcfRgf4pzzQNQ53jrctDMFLSUARp/9ly5PjrJRaa8szzR48emStXr0q/+d69+zK20z66Z6A3mkaQtAaNx6tmN4sJx6+yBhCZ5hFftXW1Mq6uouUZGZwmMaVcWT8p/+tBCCnC+RMdE483zCaJwk5hzbxE9Z60V1Y3rlJlue3WWU7KcAKIg+T16zZgMqlGViJXYHJp9+49srqZcLBySqPPQCYTOggLKzLpo4sIRAQmCAKZNp0v1zOrZplVsM39wYcfYYfdFlOFCTZ1+lH0cgA39p7M3QMmr1+hxyPShx9M9oYQ9qLnhbl+84Z0Mh4/fiKyJjuRrunEa0QgIhARGBYEIH4ohmiajzt9aGKysnImOvyl1mwkFi5ZGSUSzMqtEsgvUTbYxUra86FiiOcHlJaUuWuJ7JwrL1e+lYaDh+qqGqz4m20aGhuhSGrA+Z8LTFNTk5kPU5ezoazjbjDt/IdX7WdRofi8p0dkJOUo06XpchZkOk1fwpP+MoCRfhllLH3geAtHXogltNZHfPFD5Z4yII8kNOCSeA73nWQ8YVqHQdcSnM23dOkyw4EnF5AV4lgGDqCJfX19vdm48Q3T1dVlVq5cJatgDx484M6juyg4FcJTaYgrd+hdx2HuPG/18uUrhko7KgKZLv9C3DTelL26qte//EmA1FVY/eA4pBXma9ge2tuxs7SjTZTYu3fvNqdxbiBXDmu97s8v+kQEIgIRgYhARCAiEBGICEQEhh8B9u2DYZFPgOO2ZTwzDfNiixYtEmt1PrDYm1wJ5OTB+TRLzDEpF7HRKsXBQwfNzz/9hDHK9STcjQF1vJqT3QT3JBY8F3tu61yzapU1s89xIJ2Mayd4+QaT/UmksEsGjOkmGPoPBqKRjjNA/hAkzVcbMSYPQme92citrw22kwyc1OGEEle29mGiagZWeFdXVZu9aPgXLlyQSQoOqHnmCQUDNfmhY0oFy5kwYryPCEQExg4BJyKsGo1tuH8r5seObb8ZE2mdHe2YSOvAhHM9v4I+336i0gkX7Uh4flTWgZzpkISm2jgBR3NXd+7dxWTsTUyiPvUdDJEmZJ/NTvbZ5yDeRAQiAsUgoM1X+wPFxJ1QtLnkCApAUQL9l5kOwVSFxQjLcIZW18YNOJ9zrnn+7AV2+naLiUnKMv4Rr2lQyrEPxHPh6Ky5cO4Es+YuqbgpLaO5y1JR2JVRcUQ/mEvh7rgKnD1XWTHTzEJ6M2G2ow677GbNnCV9L5pUoQJuQIc8QDWIhK0gpNxlT4yxnJeUi6HcGUfHX3vHJ4awz8d7OjzjXh6lgNaXv7pogvcMtyk6WvopDwS8ApB+UKT+GpHXHE7JtP4Jf3iqP3cdbty4UXa0cTfc8ePHc3DJ7UVl6KZNm/C32axbt15Wvy5YsEDeQQ8WiPAw9qE49oE5IN6zZ7dbWbtYLFUIhCiAlmkoaYyvuP3evmRPfeXBvTj99tv6oZ62/rA/ID6piKxLTtmMSGxDc9AGu7qmi8JuGRS2u3butGORi4mSlXykPwGeGXY5oXM5KYg2J4PoGRGICEQEIgIRgYhARCAiMHkRcB1KHdPw0fcyJcyOobSvS3P7HR0dZs2aNWYBTORzDBi6cDyg/VDbIw6pctw7Yo0fxuWYi/PwSR5fmXPnz5mvv/4a45I9cqyVcmTebZHIwRWOgcGt0o7Hq5bbZ9ffpHNLHcbKFSvNhvXrRXHHecu0Q0QflzfKOaHyPnYAE8xJJjS8U9zTvuPrKV0Lx1feisiNfyVFxBnnpL5Ivjb2yzDrHxu+ChnUOD9BxDMlOFlUXVMjh8A3w1TTnJYW2W3Hsz6ouafjKu6XnCIiH1fzNWle86cu0eNPRCAiMF4QcA2Xk14DOU6gsRPyxhtviGxoamoc8GNl5YsIiH5sKR8ohzjRzWRv3bpljh0+Yk7DRNljbOVXF+WIIhGvEYGRRUD7BSObyhhzp4hTocJ7OooodIh60ffhzriFCzvNX33812btmnWm90WfVdjBDHgfBkUyMEIU9n/sbjqeCodnDAj4TFN9vC/RM+rEjCOVENz5xsTwI/850OOfVVAwhH0y+nH3nJ4vbPtWVG4wMv6Qho3HPht9yYPnJzhK4UFuVELaxVSkt85erRl0+hAI66cDQaHz9DY/4lfED7EUuV5EHJIyJ8yROuaeysfKmZVmXuU8Udrt27dPLD68eGHPM6NpSnUsJwdmVIhypSvP/+PA+e///v8077//Pkw3NwjGVKoyj/Oxq5FmMknP3XL2e6XcCruqacxPP/1U3stvf/uxWb9+NiJnB4iF8RvfVOHbKSKnWp9cdNbUkFP43rlC2NVIJPBKzhzs6OjAztP5ZmPXRuy4azPlFTNM9/Nn5sH9BzDf3xOYxWRrSPPO5tLyzvrG54hARCAiEBGICEQEIgIRgYhAfgSk7+o6sOzactwhYx5cq2FtqgP9VVoD4dgCXjldHu+ctOoZ9pnVT6/s+WqnmvP0x44dk7PrDh86LP1j7RknvWPmgH8DcVXuE+86G0dNrIeybvOWzWZe6zwZF6ZK4Yvtb1LBOR/4MlMD5ZxU49JzkijsxiW2RWZKK5w02YKbnzRV/jiXqoeomBRCM7BaoBUmmtjIX2BgTNNC32NFMg+y5FkSVkgldVhzojzjNSIQERiHCGi7DxqslR7Ia+AnOQ8+UpzYXLF8pdnx7g6zYUOXmBnT0lF+CFv8cAedZ8MbPHv+5If/loA31t3CzrpDhw7J7olHjx6q90T9Pvr8x5uIQERgfCJgpQ+n+OHwQOXL0+6nogSYid1vdbProYArlV1jlGk8B04FEuUZlVJ+9xn7TMNUTPar7J8mp5z1ajPMJ4pTeRI5be/1N1HUkc4ShhyEjmnhhukRhMEo2jQ9XjWd0E/wpQduJP1+mUioGeTpE2/hy51xv/rVr+XcuKNHj0g/9MGDBy7vNGlaAiXcfHybNmDAvFQUdnPnzoXSbjXOMagV85gBS5hcbIRJ5804w+6imLW8ffs2Bre9Iclr74nb/fv3Ef+IYVpvvvnmAHG0ZAMAMEDsiRNky6mlzJZa/PEjVw3UwuE59GK15XvlX0N5o+yUZFWtravD2XYHYCLzlLlz967pg0Kd70LObkQkLjwM+Sj78Kp11db9MCTeRwQiAhGBiEBEICIQEYgITBUE3DAJfclsidWDvVb+2WfpO+JW+p1YbNbcgg0uGAfUYrGgnretMRlFz3PmeNI6HTcy0HHVQDWVolcSIIMaVa6M5ha901LV4yePzYmTJ8wRjEcuXr4ox8wwHS4wZfp2tKccXBYm7IXlQKHgeMf+PN8HfbjDrrOz0yyGpUCeEU/crEviyLsTbw1zJA5hy4m8XRq8kL/npfTDf9UxiY5RhpoCl4/+02CYcDD98ccfDybqCMbRF6ZXJhXej2DSw8Q6acbKkPm3f7lLojHsVQWUnH/CeBASrJdcNV4Bs5jzoLhb2LkQA+fpcl7H/Qf3QYUJKwyk6bSCyUP8iQhEBMYvAhmBEH4U9CMVZp7hlAOckPzlL39lfvObv4ad7qWiwGeY3eSArxnuKTPs5005MDErY4QPd5TgH9PhM/+4Sp7Kus8++9zs3bMXE6APxEymcojXiEBEICIw3AiIZKIM4k41yDfp7EN4zcT5dXPmtorCh6Ys2ecRxRw6SSKzXEaoFKASz/XjXXw8Oz+SpWVhUgJwsmEZWUwKlYu8yi4+5E36ZSIvrfzELTiIyPVMxU88GeAGkRKHEtfSW2J9snnQcjOMadqrXHL+aOx+gRpgWUiw3/8GIDRYbvpFTjw0ur+6Gw7Cli5dggPdF8vOqzt37pju7m6JyN3fLbAEsX37dvOP//iP5m//9m/Nli1b8J1aJv5cbKZlYwTecwEKv2nV1dXm4cMHorjjoLdYR/xoGpP546pOnrfH/OR3WrL8FOMzhPnWP81hWMPT5cpV6xiL2Os/5aJcASXaD2n4JwZehYS7Rftwvh3PpuDZIByLVFbOME+ePjFcUdzzwu60k4kTV8HCnGk6eg3rgvrFa0QgIhARiAhEBCICEYGIwNRCwA19chY6CXO92sRD6GkFhMq6LVgAuOmNTWbJ4iWyyMwzc53RVA/ZPfh+Mnjaex8rdaP9Znrm4sN+8JFjR8yXX35pvv/ue3Pp4iWxHKJMcs3tadhEuSblTu6Yd+JWinEyBvIyRli5aqX58MMPZKcjx2Jp6kBfkXmPufD3cTkwkbT4433Fbzh/OJ5Up+MU+v3hD3+QRYoaVsw17rArBq1xQqvVwNc1aPJZ7XI3ZKWmwq5C/njGSnf3E/MEq9C/+upL2Mk9b+7fvY8Btp+WcfySAmtaWge1miv3hDLeRQQiAqONQK4PFPPAD4V+ODixycnQrVu3wixZl2mDWUyueKdju1Y6bduUKBLg+Mgz/CgLlC/tbsPwm5mOCXHubOHk65kzZ8w1nAf0EpOfVi5JEvEnIhARiAiMIAKQNq6Dwh1W167hTLLde8xsmAXftu0tUcSUUE5BLlGIiQxjbiDDxFHh5zs61osyj6GUjdr3kRClk1BLSxms3hJHw5iWssN1IJnI+C43QYz8t2naMJX8cQYXgpQksTANTT30A3d91GCXoOJH5SUVb4sXL4KS5gkWevTim3FadrjpgpKuri6zcuVKmKNp8N8osrHvgYwTrKn8aW/vEFObe/fuTdG7pAu+8BtGc/G7du3Czr0mHHa+Sq487FzzXzCzcUmoLyXPS/Ivr7DMKzcfzXtIE+vHhC2B75g7XtnWFi5caJ49f0sWDDY1NZujR46Zc+fOmUcPH5oeKF1JI+10coDfD4/oERGICEQEIgIRgYhARCAiMHQEgi5oDmZ23BB2J2XM5SLVYEfdcpx9vm7dOlilm4t+KRebWcces86xD5yGxsh/1d43KezZdbAq4caf3ZiXP3b0mNn57U6xGEIFnjqbvsYeai6U6+hdiTUdNxsy9xZT8ZIflo82NWZCRzG/bb7heddNOM5L5yk9ZfgCvedrbvrBxdSzOXAZlNy9ht8YBEeF3RiAnjtJW1H61amAOBumzxITDxwMv3Lbbn2YbMsFhauHbBBl5SVWILW2mqVLFpvf/8//ab777gfz9MlTaS6sq47cVlt9CPKitwzStNQvXiMCEYFRQMA1PNkmL8mxdeOf87eTXdhxImdUvoI5sTqz470d2Fn3V9L+yzO7B/RMJP2oCiPhhVbeTwbYRGgagLsSXkHxx44FFXb37t2z9rbdZBv5qDJwFFCJSUQEIgJTCAGRRBR6XHDk5FRf3ytznYqXnd+Yygooh2BSg7uwSkD3ss/uqEvO2QJYlFWI/oqL+0RuKau04KN8Fee82eei46+Xm+JD05xORqZZuNC0p+Oq3IXGU7hAb2HFcUhdmOeUh3JM+6ZI3IMqwfTKgqhJQmJBJ9jwikf+eRzoR4LAacoS4B8CAnfL8+moEGvF2QTcYadmLKnM4245fq+o3FHHVxymxmeXPRnszp5dI2fehXE0bqFXYnD16lXzr//6L+bUqZPmv/23fzS//e1vTQ3OtEhctsRJyPi+G+BlDJDxbCx91ivR0Nqn9ULrDdkSU33m1fZXbJ+AK5pXr15jmqGsW7ZsuaHC9SusLP75533Sj+DuPO6MZR+DytSsI+/oIgIRgYhARCAiEBGICEQEpjgCebqE2ge1Ywjb/+QYRfxdH5VjjjVr1so5y61z5/l+axbRMAkdDfi+rw7UdDTniZlY0lcmT3ZfqaCSc9Vf2nHX7dt35Ow6moq/dfMWB1spl6SX8p5wDyyHYpbKPMYIXKjZtWGj2bBuvamfXWst43giACKbiwhmFo3ss0DuYtowgVPJPE/e5PRMURTzkNS3YmINTBsVdgPjM+FDabJJGjxrqSjzXmEAXGpq0Aj414vJqwcPH2FA/NKcPHnK3L51C5Mnz7yM0MkPP5kz4RGJBYgITA0E7GQWZ6FfmdKyUjN/wXzZ5r8VJsZaoaxPFH32u6eygit+cjv9oPGKKTpcaEq3p7dHzsLkGUKc7Hzy5IlEDyfqcvOLvhGBiEBEYOgIWImFXy+6XokS6Nz5C+bQ4cPm0uXLZvmKFf78M3am7e5gyj7KMzeAg1xTs5XMlbIThQTI3HoohDDOAA4RLVcRv5YwiBLcDsCkyCAwLZYvFSGU1zy/Tfp+z57hwPUaWdVYj/PiykrLRGmmg9oic5ST3OpY2A8tkXMJ5GyCHJSqjLHX3CUjLx2zcaddZ2eHmM88hTPRHj1ivxa7KYt0NKd59+49c/jwESjtTgk21dVVrp4E77NIvpOaXBuKK6S+Ez7yHUlfgA8I4D8+973iDvxpYrp2wfwFpgpKWpojZaOjou7IkSMwcfrQPEOd1LpgWdi6EPrRP7qIQEQgIhARiAhEBCICEYGpi4COFjLd0v6AZAjq6uthBnOxmGAMxyV2zNI/+lB82H8lX1kcibEQFy3evXvH7N+/35w4dtzcvXPX9ZvRS0a/OezvFly+oWRwjOLyaIuGxgY5lmDturWmvr7Oj73SWeLLUyTSIZP1ifbQ/mkwhRtfZ9jxpWVfnPppi8yGD6bU4yMOB8Pyx+y44vFZIUiV1Gv7Gc6IvFgKCgCe89Le1mHmtMyR4Lv37uLAyyduNSvOByktNyXTS2A1CusARkJqsQzRRQQiAkUjIM1ZmrS2Zy8OUryqqmeZhYsWmm1vbTPvbH8H28yXy7k/bPBOFAi9SkobmU+cTrO8laGmyWfKEf49f/HCXLpyGavifzY/7t4tJjFT2/ij3FD44jUiEBEYbgQoopyY4sXuDrNKApr5nlE+Q87u5ao97twqww6uktISUdgxK/ZQcSgQcLYWlXjc08UT5sg0XAEoSeBHZCD6VZZCyPCTuJQ/ZaRSSkTLgDTilFj7cfAUMl7dn3ThfATrL3EDGqXVL0DYV9P+nsYJr5TTVEp988035v/9/e/Nn/70J3P50iX5PtRjtSnNKNMcpGTG/rjomqItnXi6PFKpaUPVfDJDXaCLHV40f5pnfU5o8sclDfGi0/Pv5syZK2enXrlyRZQ9NrT4X+70a2xsxIDR1pvKyorANIv9PhbPdSxj2LeSfhfqF+ZL8c6GuUrq3mUYqjH0zes7seOTJJSpyPsFgZziSDNA/AeS8rJyKO2qDJV3y5YuxW7JCuyyu2tuY9c+d8VqXLZvRtD6IgHxJyIQEYgIRAQiAhGBiEBEYMoikO5tWhh0TOFHK+GcFPqSDOf4YcP6Debtt7ebtWvX+nGPjwNWpPO8bDc0B87pHOgYMozL5Nl/5fiUi9PYp71z+zYswuwy//mn/4TSbh+Ud3cDqxJMTLq9rvedI9kJ5EUsXuEvcby3z2UYB6xYsQqWwH5tqGeqwZhdzqf39C4ecOvvbJijkGA/aqGn/vWPOCo+fOfxDLtRgXpiJqKVlbm3ddVWZSrgaGKGZok6OjtM+YwyIeCkFc3RXL50xTy498BOYol0QTAbGUfgIdMAFss5b3BAGW8jAhGB0UCAO2R5NsyWLVuhrNsuZ8aUl6Ot06HB2qbMNs0H24KTDoYnCybHOFEGUvyUlFh67mK4eOGC+emnn7BL96R5hpVC6uKkmiIRrxGBiMBIIkBpJH/uhyZ+KavuwkTvXsim2bWzpb/T2dkpihffn+FAgIR0uNqBFJ9VEtq+jzC3P0Ka98exQocpFcWJ17zRRjOAu8i4A43nhX333Xfmq6++wmBxp2B1HeeP8rvR29Nj2hYswM7sBWY2znZgX7Gf07L2CyjcI8G78Dghpb66cihmeSZaRUWluXXrpphX5M7BwTru7OIur8bGJil7dfVmGdSzXkSXQUAhYduj02f7ZH/di5Kd/PDhRAUdJy3Y0kqhRK+rqze1s+tMR0e7qZxVKf5sN7du3JL6+uL5c9s+Jb5NLPYxBMb4ExGICEQEIgIRgYhARGBKI8CeYc4uaC5f9EtnwrLDnJYWs2jRItPS0pxYYsEIjt1W/nGYOLwuzfA2FHY//fyTjMcuYdHkS4zREperNEnoRLvT0uiVb0tGywCZY80FbQtMG86wm1U1y0zD7kN5AalCprFLBb3mIV/deE20cRE8yUxi6usf/MscF2+lwExIaV1Rk0Gr9aBqzbpk0sk+a/g07JzDGlcnhbj6fDsm9GnDd2HHIvP999+bgwcPmlsQIs8xSKb5olLstOvlWRIYYEcXEYgIjB0CoYTTtp9t6cxdGZRzbW1t5u23tuPvHdOSPcCVk2VuIo1r3ulUcvCe4sEF81FoSW9XBnF3ynTTg4nd8+fOm5/3/mTOnj5jXmDHXXQRgYjA1EUgXJWYSy4NKzIUWBSITiha+WTlFuXXgwcPzY8//CDSbREUOvPnzzfcPUWFnkTBD5UHpY6B9IkcL13DF8rEVN5B4Ei9nMzSui6W0ElYliDFsP+DN8OpCfUnSdIugDcXanH32RdffCGKOu6wuwyToXewopOKPA4WP/vsM7Nv3z4c/j3TbNu2zfzyl780XV1dIu+zyWeT1PIKHQJVQZOOFxYGFIiUfGfCsHSs1z2RD81Xzp8/T5R3XKX6+PHjYKXq6zgk4TRRc/ToMYm7AOak16xZg7PsakDA/GVLncQb3bswH4PBbaD4GpaPrwvHRd4xyFJKaY0ugCQSIeXtw2waJdhxZ6Zjl+v0mWY9zq+ora2Ts+1+hsJ9D863u3jhopjJfIU6TMsfdGL9I8f7CHPdP02JGn8iAhGBiEBEICIQEYgIRAQmAQK+r4cOoPYBdY7LezDAEXLM0NLcZNbB/OLatWvEqkZ/GEDsGYehmgL9LIGO15Rc+8ThuIixuGvsJc6t49ndTzHWOH/+nFg7uXL1CqxWPUdWQ942TeWZpGb9J9pvWA7N+3ScVV1TM9ssWbIEZkkXQXGHs+swv+idDBCzmCgn658v1POY4DeTRGGnL22Cv40Cs+8nNrK1MxXfBeqklAuj4KKA4h/FmZ3c6oM5mjJDU0KVMJFZVVXtTeYdOnTIyIprJ23yJWm52USm1ttIgR4fIgIjjoB+yJOJ8P4tju2bJqV4bh3tQK9evdo0Q1nHbf+csKUjDWNqZ4YT2JQJtqmrjCClUPEGLpEAGu8Zzry8fu26uYBddk+ePpEzoPihtbKlf94sn/gbEYgIRASGCQErupw8I0/bz+Gkfh+UUOzDHD58yFzAmXabNnWbWbOwcg+CzprXE6mHlXwcHFAmWpkV9mmcUJTMSqgTg4k0lCD7k9PThWfFYfY5YFP0LXkpvwHywAVYx48fN5988on58ssvDc1icpc0lXV0Dx48kHPtTpw4Ic9UeLVhxeNSmCksxZl23HnG70hFRYWsRLXfCyGVH81C+K1IQnPd2cxm+eSiLMSPeWxra8eu8i1yLsRhnGHI3YTFOn4nnz3rNjyb9cyZM+bmzRvYrd6IQWSJ1B3y833xYplPSPo8lSqHN+tAP+/MC1bs7FgkAcT2G16Ksm4dBu31OFekqakJZmxLRdF+9sxZUdrxrEnpyzChpNIljOJdRCAiEBGICEQEIgIRgYjAlEEgZ/9zgNK34EiotWvXmVWYJ2N/M3S+24oOq/ZZbee2Xw83jJa+D0mROY4x7TzeNHMPVkA4vth/4IAsnNRxWJoBnlwfd1J0dT2QSSl59EJr61wxg7li+XIzC4tFvXN9fJ1z9O/EE0yNm0misMu+rElRpbOFyv/sixtKhQy5p/HtPiBAPLQADpx5xsvyZctwtl2lrETnZAd32x2GaaBbt24FcdK3AftgMiP0TdPHp4hARKB4BNhG7YfefvT1I05OOvHFjxo/fp2dC82vfvkr8+FHH5mO9nZTyq3lAZ08sOeBiS8rOdLyI/mmUjZIRE/3CsuG+jCh+YqT4Tevmxu3boiyjjxhbBe7WYKVMTah+BsRiAhEBEYMAZFXaREmaVEechfOvfsPzMnTp8xxKKLWramQPo6cZceI7KqIkMNVuy36zCDhlAS5R3/RKN5jKDeOmfLUtAdiSRqu5JQr7uUbweegDBwIcmBIJd2f//xn8+OPP+KMsHv92FIJEu6SPn36tPlf/+t/Y/XnadlNzXPtuNts65tvmrlzePbx6Mt6/TYFxfPlmDGj3KxcuVLy9RJnEnI3IZWOOtjzhAXeUMHJXYg0HcqdmdyxTvOgIbYFshpBMr758eN0lbHkSrOGCm2bmq3Z+fBjfbLmtm1Enq+9adNm2d24bt06c/DAQTF3SqXzE5y3rU7rgnBnWgjQpJUmXiMCEYGIQEQgIhARiAhEBCYhAq7Tl7P/J51DOy6yO9xwhhwWl7fMnWNWY0yzYvkKu1klJyw5epOhl+PNnidv9ZFjMfvPMhWLEBhj0Wod+8A3b940f4G1k//v00/N2XNnZfEkKTW+jeXGdOKfDVGKcXbNgY16aQnCZy4Cbcc85fa334bSrgu6CFozUWcX0lpFJ/yyDJTMX5UzPZTYBqaffIQJcTNJFXYTAvuhZzJV88IKGrK2/hrKKDJx4eJykMsBMnfXiKlLPNfgrJellctEYbdqxQozG0q8x08eY8LrHlar98k2VU7qZCdAyFLTCXMQ7yMCEYFhQCDbuFLt3/JnJ+QVJikrZ1ZgR8Qys2PH+2YzJruqa6pAYBmwkyATZxLf7TJBqE6CWk5cTWQTkIk1yAhaq1JHv27stLh957Y5gXPrbty44Xdo8PtIpV3mO6lR4zUiEBGY5AjYIcsYFDKQiZRf0k9xgoiKl5MnTpo9u/eYmqrZZvGixaYS52ZxMFVsfgNR+Foxl5arxWOiJlU0pk87KCtFO/2TdZtKDVmMvhrlNXfO/QDToP/8z/+Mc4p/FlPnCVX+Oy7UopKPCisq/WgWcseOHaYK/UIuDKnBwKqiYoZ8L1hWpmW/GeCpmQ3zmj+pIYcwfe6Aq62tNatWrYSC8rSYeafJT/2eFZsITT7zrL9du3bJri+abcl5nl+xjIeFXgEeLDONzxekL0n99Fo475SiLhNNuftkEI6qksexj8JW+Qrna88w81pbTR3e6VpMqixbtlT6N/cf3jfnzp5Dv8SecfHqVZ+vg8rUp6ke8RoRiAhEBCICEYGIQEQgIjCpEeB4gE7GI7j6MQD8aYKR45Oa2TVm3rx5Zv68+QP060GYt68qSfgfP15ziYfKOkuEXq30bY3pwXjq0uUrZveePeYwrNk9efTYsA+dHZPyeTI6vh55N8CjDIshW+e3muUrlpm5UKByHCdjhQGLng50r9ujFYZq2OBwHD5Og0vfxooKu6GgN07jhlXLZzH0lFbiQuDvg1yNpjk7Tkjwr7qm2ryCXJuJCf+Ozg5z/NgxOePk4cNHdtyNONKwOLSmEKKQwp8KSJ9+vIkIRAQGj0DQSKWZurZKhtrW2PZKp5fCHneL6drYZT788EOcA7PO1NXXgYpTXxpJmdnnsDNAHxvK3yRGrj0U3TClduTQYbNr5y5zmrsvensQxzlNSp/jNSIQEYgIjDICsgjJpdkN07379++Xp1aY/17AAdqsCisZpc/CIEhDaBF0QOWiJn0k9XBXL+b8TYZglB7D5FUJQuXaw4cPxcwKd5kdQ9/tm2++MSexwIJmMAt1VFhR2aeOu5r24jwx4sRdelSk0NxyCw5tp1n1uTBrUo1D3GmyUF2YP/Ub6SvP31u/foP53e9+Zz7//HN591Q+UoFZjGP5ae6ZeLKMPGOB5S2lstd9StntnRxO+wbDWxoPD9hrCgl2PtT3ZTR19k1IV4LVtzyMnn80Z8udjk1NzVIPL5w/jzp+xTyAaaHnPfH8XMUuXiMCEYGIQEQgIhARiAhMGQTYndROZp5C68zW3LlzzYqVK2SerBnn2Kljf177p+o34BWL5L0L08Z9Mj9HCp7PTEtX08Tqxzn0XfdCWXf65Cnz5PETsVpVIhtoZBrds2R+w3k6HzAJbogPd9e1tS8wPGO+Gf36UjyrPkEWARLH7CCrqBc0CYBCESaJwk5biDYafeZLUr/J8cLSpQjLZsusJfch6uEiSqVXP1R4uSUxDr/kPRsPGwjWptvD3OG5aNESTFTMMYs6F5n/+I9PsH33L1id/UL+KEhK5OwXgwmNPnCwKbOxJQLG+bk8xEtEICJQHALaZBmLbcs7BMiuOnrBnxNZS2HS9qOPfmE2b9lsqmRnnVWgWx7KCS1X2LCdKj8nA6xUkCQYJjHwox9QPQiWk75nz56VlUGcEH7ZV9xEqC9DvIkIRAQiAiOIAGUWz2njWXbHjh2B6ZFzcnZAbUOtlaeQb+n+vwg8LxkzgYm/is7hzLsIXORHeWoa3kMD7JXBuUi4o5A7y2jSnLvjuKvu0qVLKTOCaU6FPemOszt37oj5GJrIXLBggVmzdi1MF24yM7DbjucP6HeiEK6pT5ovp5aKHLynsEu/q9wp0OTMkiWLTUPD70TRRuUl81yswo7fPZoIZd2heVDutuvoaJcD0rnDsJC85M7hePJN41tYzvLHCd+cr5yOKfsr4pQIV51A0XS1Psh4xCdjd8yyfm3YsMG0t3XA9OkK7P78QnY/vnjx3Lx40CO8rLKYVkM0EeUcrxGBiEBEICIQEYgIRAQiApMSAXT7RFGmXU3XD2RfXbuEHJ+0t7eZTW+8YZYvxzFQs5Iz07RPn5prSwGljOnp+pi8iLc否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否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否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否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否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否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否NBu4ljQwPBF8JttKOTxCp0PC6ZRWsAxpVOPfM4eFiWesQL5yIr+0PCuilZjgOlz3xauyWHwvom6IjLmOLSzphym/P7t3Fxo1fFTPVhqeIBzFnp+0uWLCwmC8B2fgJujfehHPaTCBYsfGUjdbQxH4BuMOb5HNT5jNzNPqFB3jLDzfetHVO+9cNp+7Y2M0p3d7Fi4s9Ej7CAzh19923uqNSAgHWgVIp1KOb3ejT5lOVpnvVMtAWvnFoKPAAUEDVfuLkicXKVas0r3qhWCUtJgjwwwT/Y6eBtQg9eFetA36jdltrPgjo6NsRBqJak/XYHVKR/uabf9bJuk0mrPNNfrX5SST8gLxrZPFcVcQzvjpK45nx2oC6du1q/R7XfZ6LNTcZbzyMoHUYZZkYtJbeu+Sf/SdfJ06ZIWglXccA6IFZHjZS7eQWfvfmu72nuTfxfACxqleuqDx19wcp65FH8hT57ORWz3OE8V2PORsOODHw6jhAS9GdgShdkjY3PezNhN/TtBTwj8Fe4vCBrb1TNB9QeUdggARkghjY2Hljirmz5wqm7yTjPrtLUv1wVAK6I4eP2M7vvbt32WB0//4DxXF1EqiI4j6D69KLDk4xoM3fdapgD7mV56NTiM5uKbsk1DlAR9ecCjBgxTUnPdLbSyiH6YPmjuAypDsPTltjAdXCCWcvZ1LztKyYbHeh42L+WRliZ0GPEw8zJaRjsZBTA8ukf3npkqW6s2KhduDO0E658Xb3hKnCGaWJt+KQApMB27NX1gfLsOEBlslZCGJLr7Rb0boq7TgMP6eVhyufDo4MmlPkq4xTBsw/PFRrV+j+1D3dFGALAexS5FTd3/72t+Kgdv0xmcF0WiTAvaoSKTM4NqahQEOBhgIPOwUSnw6e29JvaXc4O8Xp66fNmFYsWLSgWLy4x/oI2LG8vZ+ErQZrDb4fdA137OGXu9WcwyuCEg03c7d+MjYXqa+A79NRhROB+2FQ/7xmzRpbGEOFIConORHWkvd+wIkgCOzek1rC0doIw2SY++xCYNcCM8tU9mlgYtxT9VVaH9eke/78+doBOr1YuXJVcfToEf2Ompo5Ttxxoo+7oxCs0veRVkt6geBP+EagyH2B4A1NVq5c2UVgFyXfCdm+/CJ8UDTCYq+7Rdj8HeFzt/w7/ANW7tftO+LgH/HSOxWwh/DxHt/4RhskFjWexRcMC8WUMarWWBj985tvFX/521+LzTrNdF2CUAR7aFaw8bnC+4JM1SQNiB6BCY0pxzD875m3IRr0qmMVlCtzo7xUuek0TsZttBavJk6Y6GoitaOaDYnntOnw0qVLdk/knW03jmuFZZanrMGXuGtsTXmy4DZBp13gAYztH1v9WLFm9Rot8C+ye8hNta+E+YQdrcUnxvaYUP1Zz5OxSgkDYmxvPEMLgnECj/pnwgKN829KaHf9xjWrS4S/ITvCO1P3t/078cyNxYcffaQTtLrjSkI7hBIG7/pNXbQghiwB3007GUU9dr50XfHJY9S+wMNqvpFI7SGjh2Xmrjw6lkyfKXs+PEi/cVYeo42jwQQTdisU/I0ODtefuUOFJ+mTbpl2RAz6hT0DleOcOVetx8BHGv52cOAgTK08eRPb51w5HL67pVEPV7cH2u7uaRsspVXSKIsU+Y535iWa5Lb2b+CSB8uGHhZej76iOa2BVYUaZvPOas7OqRV+DpkEglaFn0TVBhuuFlm/YX0xeswo2zTAIvGTa58q1kklHvcnPfLIIzaOAEeEdczT+UaNrY0pQBbkw1ToCDP/CxQ5EdhmVIZuEowSx45UdlBKE74wSfyTa1OW9vQWF599Thu2T+o07R6drP2kmPDxx3aPKtoGGEvRB4Eq9IoU6wVj5UuIGPxUIR3F5tlQ4D6mAPW+E39i3W7i5Am6K22JbfDjVBcnWjEW3tp31hfSkKIRlW2/dFAsb8vOo5gHMG4cZf3xQW3se+eddySwe7PYpw01HPLAN8aI+jSTt/6sxYb3ffkOCvF2XtSajSlS+/vYYyuKl195SScdl4nv+kbJoGeENuqWdHfX4F0RhvLJuLK+I/UyRPnR2aeTayc3wNTd6/YyqXv+oxHYDXkRURnyqjjkCTQAjb6DbHRRNHozFnMoHcqs5hTRjPhYPGLJ2FDlIC2LxTAmgaM7F9G8OfOKMwsXF4s1gTws9VKHtZv6+InjUp9w1u52OX1KgjupRbqgnWXsYr0oVRDoUmZQx4WnLELQgXhH81N0EynTkb0YzNpbRCm5fI6bvssBZkTkXYOFSxk/D9f522iQedEhMAFHrSW7QNgVjCovFhvZ5TZBg+dJkzkBOcl2682aMcuEdgz4OUXHTlsWEOsdC1gGXoFx1AXrk8Ixw4XPqorwFTFqgYbICn4MIILqTFSYCN2Q+tUTEgZv2qjFAqnC/PLL9RIMn7KBSWvS+fCj1aexNRRoKNBQoKHALSiQJiioO/rhh0N2VxbqlJfrDlRUt9jiUQoTkPK+hq6v6jMiROc3Xc4te5ToSxWQsPwYO+Rpdobe7kochGGchHv22WcLTq5tVJ+C4I3FpoEaxjbAgC5cXF7fONIVXpe+lvBOWt+Uw4Xo/DhNc+HCReF51Pp4NulwhxknWzjJduLECRNA9Dv9rogNnQe4sJjPXX+HtBv/5MlTpsqPEoyyi/FI/1PtV43pAxy1pw/i9xGz3asTnIDPW6b15W562vhGfuYtMEEHF6Bwt/Roq48nNKbeu2ev3VG3WarIPpKgZKvuEWIsHYa2YJVG7aQTRhHuvnpnGfH2AI08B51ySX1ilzrjZNriRLWZaVOn2UIz6iPn6S4p7pRCI8MOtdfTZ07pTrYhuBNS6aYiLsuwonP4hEuWqXDSmwXx0TpZO336tGKmVNbPmzvPBP8z+JbAjhM4ixYtKpb09sp/ht0zlUVv+TQa8RBe1CnfaOlj6oo3SGOF0vSpDEI11K1eK4Zr0sfdeMNHCZ9RY8yNesZJmv379xW7du8stkj95Y7vdxbbt283VaycHIq6K3Fg0vziS1aW2yCBLIQza7hlmAeMzOme/bR8GH1B0TPjwpoc5ShrpwUt3hqnguNDrHo9tmLDPaJaSA/vjqWHJVTSTLhkkVKsFkDOH4hVATcYA3nQxgxHvamzLD4zFhip/hS7z9X01jfqW8EcHO2XBLdhZ6MBC8fci0i/i0D3plS0pdxanIHgVmUa2lb9CzCMTl5MVb8jdysZududjm2JgbfFTu/WAF4SWXno08qTspABNnjgRvti/ophkwDm2NHjxdHDx4oTx06aIG+rhN8LFy20vh1+tUjCPHgBp+48PxbN4ZmKAc9nCH/xjfQspNYxvLVVOCYSOKB+PAkPDOo2p2xZU8AgYECV3AzxItYmFmjdgfEhWpYQSu7cucvGJNeuVZoLwA1jNEpvdwO/wMxTJFxjGgrcnxRI7a5qdjY0Iy/wE8Yoa1Y/XryKKsyn1ll/HmNhP1Gb+kjLvHEoYpotHrAYbzHyl5fzVG2W0SYf+m828jEf+PTTj4uPPvxIffYW2+A3XG24hUcEwPSOtllzvu+sUKvkJM7E2/KAin7uC//lL39lqjCDt1k8aFqjeRuAPhwi/XoQ3BtTUeCeE9jZQEH4RYOsUL2fvurVrG4nL3U3ZyeVe9jzfEecTn55uJ/6uxuenfDu5FbHv1OYupun2SocasUjjQszEosVC4wxmsSkctZvRZSEUjF4CsysAzCA3gG4uyAJjmOmp8HGR3jo33Zd6X6VMZpIzps7p7iqy1O5B+2KBt9M8k6dOGUqnI5qgemHwxLmHT1WHNHE74cffiiOHT9WoE6TexAYrIfqh8CrP8wyb1PRziI/A3tD1yxFs/KwbNq79eF+rW7tthw/fLvZc3e++TF5Z3GBATs/Fufma4ER9RksNOLGDpFx0oHPAIAFSDoc4vGNei1oStkZTL0DaxOSYpdDqiapjAmsX2682OVCydQ8Q8AZ7imBSCcHE9+RV0+bkMIxgWXSZHE1sePSXUok6IGubRZUWVj99LNPTR0Pqnds126kb4kEnl53I93m3T8K5GWctYj+RW5CNRRoKHCXKJDz4r447i3QSVHb2rrc/VQz7x91QuqYNkpsKnp7enXSY7X1QcHLg0cDirAGi/iyO2/Pcc3wSc4WL3O2Tzmatx68c75k/gLsC4IR0VILS7/eLDS99NJLhjP3xKEeE80AgzXc2Ybwjo1IrSYyCo4YKCT8w5q83c+fZb+cRUVd1XidDJon1VmoumZHPieuPv/8cxPiILxDcMfJO4yVRQBysD/ZE5qA23fffWun/hm/xAR5cEgF8SJ2EDHe4V4PF+55uDxM7h5heXcK424xfilJbSBa4QwLzxQYdYPApF7jZN56GMQUltd5nQLjFNMnOsHw6aefin7fFT9oDI2giZNVLAQTy8bPKV6Odf7t2OYu98Z3TinPP3hV2DrJPFTrN7Tz3wjdxzJu7DgJuWbpR否jxZIlSwpU3y6W+jY2sKEqnns4r2nRnBOfCNhPHD/Rpr6f8giTSiOs5TtCBIbYgxcSqHUuEqHK6PYReMebtkCbWKk5FKd/n9Di9xKdZjHBo+6rGzNmrNqLxvrjxlhaCDuMKyrxMm2S4oebFtjhLyb+0Jv6diPG1AoyUnWHxTsCc4LzmurTVZ3WHH4DwaELY8gH93Ht2b23WK8TvYy7v968yU5dX71y1e4wNyGfoMR5K2jGqaASD8rHaApS8pUfKPqDDwzu/nU/Pb2crfR否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否JQPnqJD+vJPi+APuxct1QGEa9Ccu954U++oGyb84q1fy/wSlZIKx2m1UZpboobST7BJiCZ3TrsxVxxjgjo2dfJD0JYEdUkIh7YWTsWxYXS07sQMQR31kTi4I6Qzu+asfMcclnR8rltysSx3d+czhHdxCg8VrQjfz+keSk6Qn5LQjpNbp06etjduCJa5pzLiIPyj7+GHG7+rOpl341o69WfrAF5Hrd2UDcuL3X3KKmDtjiBW9EaGCJFoQnWRE65dw0TQVD+oKxjaO334GeF/Wj9Ju1QGWkdRWe3Zs7vYI/spbQziBPF8qcaeMnmqlfu48ajMVXkheI/KWrZMXFIFJhE+Dbn0jdttmcg/p+6GF5N1Yniq1jVQkwnvOCL+O9E2I0/QRoANJmw9e+asrk05Z2XRKWk4agvOnQI1bg0F7hMKsJFhqjaoPff8c8Xrr/9Cc6dlxmtBP8Yszi+8LeXfHsY3QURY3CwkDCbxK9bKtn4r9em64xhtIbt37y7HD/Tb0WcT94ExJVsrP9qy5iTyAyRTde8mAlPuD1yz5nHjV6iX15DGvnO6V/wnYBvF2+A3DoOnwJAJ7Oi4MXkDGTxaD1PMqNRRySPv4R7vcOddD4sb4dRkOnnh/ZOaTkhFvsIv3p0Q7Y9fwCN+t/B5GOpqq92rcGvckp4W1MMHY4rYDJXMhIPb7FmmIUAeKt4RqDW9cG19K4wGodUE1H3p1KzdpbaHq6OpZ3LDn4E3O/7WatL76muvmUoZdrejPgNd+YQReIsbA2AGwUxMHWmEFBoc48bCB0I9HPjJRL6ofOAEzVgs8ckLk1afXDE49t2GTMBQIcIkjImZhHfppB2qMF11qE+wfGKVVGGmyZGpF9EEDBgYw8W+qodR2RFzR33b0W2bz+PhecKzLCMPWT6r6NVX6TmUHyJjpOAUrYB7FXVXTmO0GFnJO+WBGTZCd2hoEevrzV8Xf3rzT8V6LRSzszJ4si8YVCkALhVhC9jG0k6BoBO0tNN2coD2FTXb4zQuDQUaCtyLFPAemz6C9jukrVgAAx78ARXXLLrMkdojdrY8tW6d7fR3qvgiCzjAm4kJTsGvccHE2216pm6gxT25pVcZJ+z0xwEXN3ALewm3nx8sYq5TPhBosZv9r3/9q51M62f0tmAsEO7fv7/YsGG9bd5BeEcaZNSoYrSRrd7/KUQLDdog1x3y0E4Zxiiozea03dKlS034uEc7bhGQbZYKck5JonYLHH8Kw9gN1Y7//d//bWo7X375ZcPXcYnSzfOVYxn+udutvvuKE+lEmLADM9zi3Smdzn5lO1Q5hwp5yprQtrkId4aiKivKa5gWaVmg5QTi+vVfFl9I/eVXX23UKbC9GutW5UQ71KEUM6kKlUgFJnkOSs+f8CPqOO0TE+9ACf847V+Ow80T9xF2smTp0keLlatWaoPeqmLubKmO1B1102dIeDdzejFDajDRVgEd6wbhyhHdnf2hFrAYP27QaTFUxWJIF1xI08bLQs9LyKEEz4uw+DlPc/9bP33xmo15tMfVa1YVSx9dqo0OK6U+bqru2VtQTNHiEfxmosIEnXK4LW7UmbLQg9d5HnCv/KAx+WNuQv1SGOWRk0yEgeVAK4Q/bnyM/c0Waa/47PNivXgW2iz2qO5xfQHzqTA23k4CRDgVJeqlGiH87WiCU6v7vWALmhpq0AOkUtsMdHlzOBBu7eEsEK5O51tkjDSMxqLzWAnG0JrCFQZsUOAUKKcnEZBNmKDToBKojVf5c/rChGYm0JOQTsI4hNWcoEPYx6YSm28yz6T89LY0sMc81L6Zh/p81Oed8a232hN+xHOhIXGZq+Kun9Qtc/c5/vSvpREREiXklFpJ5l2GG6KPqD+AAw0Tdmo+zT2ObGpFG821WbpDcf4lzfkv2yYbhHCcmGXDTSmUk7D+3LnzJrxDqHfy1Emr08ek6vW4NtackID6vE6xIbjLTd6WDIfSk0yLD8CEy7UWqy0d6zqhrf6U8ds/SAuh1rBh3s6wR/peBLTxQnm6Wuzavcvmv5xYmy7esXSJNi08trp44smnbMPRqFGTonQMhsESBnHKtiX1KL+Uj7K4LTsJ6+RY4RORckhVDqn3CPVLo+DUq6lS4fn8Cz8r5s2fXzyvcckBjY026S56xiNcjRLwy3jpw+pcoKKchalSDJfm3VDgDlOgU6WLuhmNR/aKT8K7vB+gfsO7Z8yYrk2Bup/2ySeL2RoT0DYqU7V72jun9eh/gAicqvZXMWi6nGK3QwniH2xS2Khx41/++jcbP8ILiQsDoe8e2PilSude/epEEyjWyUAH1KU/rruBn3v2GY0h51k/Z2WksoFM0Lpu3MVLlVIo0yw/PEYZs9QqEC68a4HriTzE9hiFDhkJaGxW6YcMYgOo/xRoKnv/aTXAkPCQ4Cn/P3vv/W3VkeT5JsIbARIgJIHgXkB4JyGPvFSlUq3p7up+a735sfuvmj9geq1Zb1XP9HR1V5VKDiPvEbIIEB4BQngQHr3vJyJj7zznnnMt5iJOwj07d2ZkZGSk3RkZkQNJGmkGOQbRn/h35ZIEa3wsF3m3JEnwzc5xpDRddzws010xfMBzktbCBe6Dr6diw4SPctOms75MuOfq8PVEGfl4rH5BZC+aeEmldxvgkQYqhI9hC4sPHIvPcIGMdCAxPPbi70W8F1E0SoAYZbMn+Eiip6aULDz0aZzkFme02Jzs9alA8JVci6yLLK+pt8yvpKN1pjW0T4sIR2+zDy/ucdn0+Wd2Wuijjz7Wx9ZhO3GK0BPzqvU9BTVmscrKX4d0fL1xgHYWbThqAiE34R3X4UCHA8OdA2U/9fmiDLkq1BcIOUDx5Vdf6NT+mDTjLsxjLraNRs8nRhB/syFEQY2hQ6OoHpY0Jwox7z6GuZ/xf6COzVCEW2ykcrDniy++sI29wY6BrEUQkL322utGH6ZXENgZadAMgRXdgyC4RQFLWikHf/fff7/xBq0DhGQIDtjw/Ep3UfFxT1ljczM0eVqgvqpBCAp37txpG49o/XV1ddldXWwc/1rm7qqNRr9RQNQPcy0Nwdqt+H9BAkw0Jr7b8p2Zaty4YWP6VAJxTJkCO3KENnW0HvL1K+kC6VWtlhuIDN6oiJRVG1gcdGN9h6CLO+m61D4eemhNWq1NrhU6oIfmKNp2CBVwxk88wRfhwZ0+fVqb8j+mt3XS/D//87/Sug0bTHg9Sib9EADYN4HWOb7WsSSqFk/rb/Vvu/Aawn3Q5MIVhCxjTDiDlsmSJUvSk0+tTUuXLUn3L9DJemlCIYihIcSal/ol98irKg6h+u9fSnU78lagXwFSlrL85WEG2ov9AWMZIHiUSczzl2SR5IzxCa26dW+uS+vWrbex4WcdXECwzoYgJjPZg7I/8grCmguf3/uIbpPqBgQHobm9MPjYBqiRAj/tv97kqV8sdkQWkPlhzKw5F0IyzSWjrf1OMEEs5luxrMIdo5gxREP0DgkwJqoNM0bTzhHmTZLQmbEZ85QhTKM9uWCu1r6DXKqxnav6QxNAu/AmsPya21QVWbc7b5/6tbaUm5R9HYtLmbCW9JWBmaWB3tMpD9qonEVTP7mOnHaND3xniz9oAcZXsIMINtcj2Xh6HY6QkPpnWWNhrkP4fEjjARreP/zwQzogoTSC6Z9ksvG4hPimdad5+/IVtPEu2YEWNC/4s+/ywCvsNgaTSZOzIuqnLCogLUCrlNE/qwB5WNeYqUzGOJXLWYG5u1PpxPFTmj93m5D2bmnSP6Bx8fjJE7oTa5VptNHOGFtMG1Pjadt6byayJKD05zogCFrb4itKLQ4ZLHsvJB+n/oDZ3y79cch6//59ds/dKL7jNQZj6tnuHhT/7VC18op8jNclPR1/hwPDlAO0+9JF22Xtf9ddM9ID6qOrJTCaK2seEzSGabfPjHlVIm51lqK7aRygkzLWxl9g9/5FnCJNO561Dt9lb739lszofmYHwLBsxnzCXH5Ja81w/e36AT8cn63KENyPZ0k3c8ZircNefOHF9Njjj9s3R4wxHAxz1ypliaXjv9ocuOoCu6jUq03orwFfDEiUZXB8atXtgjPNcdGZmsMD/tf6LMs7GB40pok35hbHXOO3+aFkYwVcLzrLCQXQOjVvJFBIY2A1jRHMgp/Tn2Z6QwvLerCs8wATjjbV3MbinXvrMD914MABnbK90z52+Li5Fq5gg6FvKl6UeshZU7bqr8BmPNN75OtP/fr/PMnnk7B8/bSrSE/Yg9FlnQZIlVlBR1/ewFPhaEgQXIwnUA7JQsdOCemDjY/UESNHSGB3VvfVfZD+4z/+w+7jOXnSL+W2k8KCZ9MhXD32wL8I7TwHwgF4SP+J/sUmkGkHDARJB7bDgQ4HbggHrsmwFwN5gRyBC6fW0QZCKMUGGBvobLLHOBzJ4r2ZIflQdwz/1fqghAscEcY7ZPj8iJYIMawlCMM/dIf2w706BT579mwTKiGcjPFwINj5QEbDDtOY3OfyxBNPDCT5kGHhe/CeZwjv0OBYuHChmbnjZDt1h8YdJ/YRcFwPBz9pQ2g3ILiDBgQabFzjoLe++yoqNlpD8/u1ozjaVG2tIGio84yQoMrTsDYBBs0GX+uyCYlwlPYaG/BA/KwT0Nu/354++ugjMzP77bdb0rbt20ywZLiF8PIvtYYdaZpd0NAcfqPfo/31oEM8qZzKB89oE2gJoUFD/1uAJtqiRXav2z06Fc1G791336NNlulm3q+vdT7rlj17dqe3ZEb9lb++kj7fvNl4Sh3YmsbaGBnbCFKRU655WXOHaxwDLFFEGQ5eKC93gM1Uf79PQrp53fNSV5ffTUcZMOHJWDD59ik2ZtUIED0IZ87PciULeeCNCcjsve7ThJOEdNBm9AGjnX7oCN4jjLwiDTkT/EhQafdeCy+mV/ft3Ze+/vprM3+55bstpu2CRieanlFey4bMjBgohjpCb0JHGeSMemMgb/CeX3968SSUgKdENzuhgLejZWJy8u2T010z7rJvzxnSkuQ+zikSliB8Y5ydJOEb5iYn6J6isXoShkBu/Hjdqyj/GJmuRPuOeZN2z/cwAsBS2Nqc/Y1497bAmFxzBH6Zy4+gK7M4Xvt8GsYKB8zNSRRhbVgLBYKiXfeJsAeAayZOVB0wB8685+40d+5cExAhJEKQxxzPeobDI5gjxlLPId3hiCDvx0M/mtnNU5obEdy1c9bncqSXCc0aL4wdhBHvjDcKgo0FK1ui9HiEpvrG1V4JvDehfG6VzJ+Qc+DAQQkYZXVGd959Lo01yrZUc+kSXRUy5745OjRQax57ekOnPKsaNH9JRNny6QfuvCz2S5C/elQjiIVV0KK9uT1P1hprhNZXTz35lOjtkqBxtWvb6ZDUDxKgxh13kc7GMNERdDmvPYeyTToxnd8OB64fB8pe1JBrDIS5/0yfPi2tXbvWTGHO6+72K4JIYH1HPwGvJ0GmyECwvnF8HKG9R6dTT9C4cFlauSNHjtJemR9ux4rAK7Ii8N5772l8O2nQ9B0bO6p+XGdVBEHJTe7ajweMF2N0QOtejf0rpNzxwIMP6HDmAo2N7BXD7eArLMBvNVAxivSMPfHNSiyuSpUD8grCI5t+Y+xqCr6Kr0FNUHcVUV9jVFdNYEdFddxQODCQxlPC3qx8v0noLlldDzv9qmhP6uWM0ga6anEXERkj8UwaNrkpzvqVnnyc8LEycqTutwtYFqjZ9dX/zmqB/bVOinNqnA8jPvD5KGrljMYg1AD00kBnw4ujyDOaJVO0fzooKoeTwgfz+KhmUPdM/BcI94GwfsPnaaOMFqIf3g1nxmOAjYR6UP51TLzU+eAjvI4jvvGNkHYuMPU/RU9MgaNnjCitxtWcg8pqmygSNPKPTZXLFy6n7zn1u25d+qs2XDBfAt/ZeGCxwqm8cMFD3hvYFgCdZ68csL6pOqEvsomACaRzus/kik6adlyHAx0O3DwcuPYfBoyxProfP37CtLY+/vgj+3CcN2+ebU4Gt6pxWeDVuNyPSaU3EOLqMtaQMaXEM2gY6JP5hU11zGOycfftt9/aRn+Uub/4gEdoxwn+77/fYQeLuCMPzQmcl6O/2AYPF3STL5pJnL7n3l82KaENk1RvvvmmCdAQ4MUHPhuCtlk4+Kx7pKQ9mOBAh0LAbfON5hzMkEIPp5D7EsT0QHrVA+o21RM17b63+CIFmgV69VUv6xvffPW13W3pog6bXRSPuZ否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否IU+VOnDgpdXd3m7nbNWseNM3VezQOskln5v4RQitf2oDRbmUgtb35L9nilK9583u9yvVoI8HABGBtjWeOU2Td17QVKF5AH2MMfZf34yeOp+063Mj9NuvWvWkC4tOnz9jBrMvaAHStuizEEn5rfsJrdJB5zoscm1jkRAyX32CU0cMLriaekLKs8MaTOCxfGON1iGHGzBmq2660WPezLtDdpksWL9Ehg5m22Yf2At+tjFesm9Fk4CAE/EZ4ehneCx0Y65z1IqcmkJ/Z468tfxvaa2Z6Xc/RikjaBlcV7B7/dYqCrhJLSyKKwAZ6qjwDUwE4AG9F4gDSNIMGT6vwHgGKaSKzuX9F/ChZ+0Fox0EW5iquX8DiyKVL0mTRXIjpzEOHONSyz7RaOHgRpqW5Bw+zmqbVIiFa8IsywmevO556lxB9hNqekaW6NRgicj03kVsVDZyxFqnwF+Ul7pA0bE6++64JvWbKhDDz7G+0Wf2I5txZMjdMmx2ptut4lHcW9lsmTpDnB1FRz9UHvgAiv0wr7w30RnxDaJTf1z0xHjM2Mhcg1MACwIMPPGjajcyNZ//6s5vpFnL6FvTGIVInsPPb4cCN4UA9Dnv+0RWqvqFg7uKdPXtWelz3paFtf4+EdRzmwNkhLvUp2jX9WN3A1gkMVDZW2FxiHdBgIz/mlkuX1GdGMoLR1w+lv8oM5l///FezmIHwHpxhgSrGCMtUPxWdEXAzP509VoLmchKINjGmMGfrQACadU8+9bTGmW7xp0gouB5pq/GLSDDVroxqZmaAUn+4HnNMjeYa+CL3a4D6GqO8ZQV2MQleY/7epOjpRDdvox5OTG8c7jJlYm3zAMXgVk8Q8L4xJR+TXIg6ViePY0KKcgZk6xrzRS+CnLNnL9mJ8E8+/UQbT3fbidIZd02vTGPG4tbxQmRgJAfHE3n2eGbQoKXC0QOQNWwjVAuQtkFBUSsATsiEsxz4KRM0MjlAB/1kXV7tGw0aS/8SmqBO9cEHBPXCxdl7Edb97RUz1YP2JCeD/UPYN1zKole5VHVahXQ8TRygfXobVS+tmMjSQg1KlT5+7Hh9qE61zepY9DWh6Lx2ONDhQIcD4gBjhj409Y8N9cOHj9gG8IfShuYE9yx9pKJpx8YoY0013AySd5He5r+Mg7Gsx4fWIPG3SsaGO1r7L7/8sm3GYjqSE/Vsxg3EQSOmMX/66bBpVR07dtRw+Ie7rz+a100DwT9Q2JJnzKsI0fjjsAZP7vDDhBaCPDRxuNcOTUrMZ/KHv7+O9R0bnwhAwYsWFZuHCAcxy8m9XvA5XD0vRcgwe0ZDzGTZaw6L9R+bKNbgbTOG+tXGq否qfDx06JCZkP12yzemWff5ps12B1GsUcv2XdbTMONCv8mpeFJU7OhRo81sW3dXt91PN199jE1aNC3vk9mi0AAhCUIn+MAmLbgMn95Zn+BHaCKPwZyQQOobaWVwZyQmXr+T5hhmVknPOARvm6rPykHfszUvbwFgFZFrA0Is/Be7h2yKtK4Qyk2RdhX36i1ceL/RvlAaqrO0IUd7DxfoECo2Ifd1lwE6lOVGWYHMWTvVFNEDyjaBBqdKbuUDjR1qQ3NFm4TqdhaO8ADtIAQKHBr45ptv06f6Tvryy6/M9F+0OzPbqLQlPqMEYjIt9qwLRJY3wAUxZN1MjOJytPFP0fYKWAalHWEdhc1ReDpSd9ggHL5DWnOYSZwy9Q7V8QQzG3z3TN1DqHmMMWvWrNnyz063a1wsx6tWDKC5eIby5Lxz9hmcdqwoB6zC3NMImSPtEW0gCpmbRAnS3l+izQkzq9qnuQ4xAylDA7sybS3T57JG36mKUfHAPZQfn/c4uOoccfO22pfQXBjtKXBAA/0M82pdXa65hib6Af39qLH9qNYHhzXPIxxnrXD61Bnrf2cQjMuUpgnBAxlPFQAt3cpJINXS5coKGhvbjqegfUT4Ja03TumPOedHCRePSQPwjMaCPdKc565YtNvnaH6fqoNDlFNJnRf0DxWSfLy9FbRFBWRaKjpJPABXt+OcyPD+YoIM1glo4GPlBSHeuPFjbTzftm27zPwdblh7mdBdKBB8dFyHA9eTA7ThaMfR56r8i/bI9SKsb57UAbWHpNk1Q5rank5tNjdbwFnnWDvWS+C29h19S+GseYAbrX4xSgL2s2fP2JUxHK774P0P7DAgVpFGa03Eup7vlB60VUT+yjyZl8Y7ld20nMUz7onlzrqnn3oyrX38CbvDjrVOjPw+wMf4lZH8ylhzMxTnlhXYUTkxkNwMFTV0GstOFh0vsLaKa4YJ2FbPSB9pmt9bpelPWOBrB1vGR56tYEs44pthm99b4SjTtcZXtqeYAGLhWC1olRVxtvgVmnrBh1+hxOd/OdaIYUOHD/RxhZkGIqDEFpFtiwBOoByAD/MvdKk8uCZNmpgee/yxSmDH5GXO6KsXtRA1gi/1qhDN5fdk9hsTZxE0JG9RrsKbSxOY4ZzzjRA/sQn9emkmtYm+OroV9oiNuHgnF3fGlnjhGaBFWHNQtclQobPW4PTmdFWUZaB4LUCsHkZ5Wb/fvj395S9/SX/603+m3dpkiLtbYmMhsq+Ka8yI0M6zLQdgfM18sTxqDzOknEIaa6dKMfXDhgQfS636fVv8nYgOBzocuIU4wPxbby5xp8vOXTvTxo3rtckyRsKZF9JECexw1VgtP6NOjD1sD7dyMTIRR1pLkwFbpQCffaiVGbVCPMAw1g0Irf75n//ZhEv/43/8D5Vv44AEVmWW0IkpRO6XwKwW91qQh32Yx+RZJujTH5wquRJhfSauAIJ/BCBsoMxoAHL/DZv9p2Ria+dO3bOm+zHWr19vh6OqxH14wHevTvSjrYimyurVq1NXV5dp9CEgjNPGoPEpqaQ/yhXPPjK7WtG9ZlfTFz6jW2l8A5E1jW/AjNC9Q9QvbRMeY2aUzRU0nBAm7d69K7GRSzoTIggmcFpRhNNIyYFVnDPqapV2yHiaqHZ8Ijb6JGUPR1horz7//PNmImqOTGDOmH6X否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否G2ae3fs2GmaC1MlvPPNZMdAUht79LRpgnePavvbEM/0QZoqULNj/dIWx1Aj0K5AU4yDDWx0IWyJ8bO/uIFHuw4NIIRd3NU1U6apuPsoXBRlYNNbcDKwDO1Z8pO6g+7p06frHo7HTPuODTMEIjt27JBw5CsT4O2TsBbzW6zdSA+vHnzwwfTMM/pYloCDPzSrENyU+HtSWlVsz6hrHFJZFaCNNZFRvpbcrupLi9VLdtdaNt否tP5i5tHe1V1C69evS5i/PHjgkAnr0Miw9iME9J0SZ3QIC2uIuMYM6Af6su7K9k84G6v0ZjQ0yjgEvWxYPfzww2nFyhXpgQce0L0u98ks5jRteE80k3C+sqWw/gdf2dBi8wrcho8PAq0bbxutcJk1OndOZlqluYIpfO6se1/meLnH+rSsbeDQGquEdVRg5qU/9GseRfCfeP2ZoIfEikPgR3/HVCcaoghzurq6Za7NzYZjjcD31sJcJ8myJp36jPMg8vHsrJwKsuwsU8KBJVM5hVFe/pkjIsN5tIdjJp6NPjaqwhoC/P9ZAoBvvnFzqzy3fLcl7d+7Px2XSTyEdbQ1HJuAoLZsq8wtqvop67AKvMGeiqdNdKChCi9MCKnOyz/jdcApbKrMXd6/YL7u8Xo4zZs/L92nwwh3SjtyokxgUqdTpC01Ue3xNtW710Ek9qdxXnlEVUUs4dBFfeAnLc5oda+9t/oJWI+rMdc+ENVIwsezgsn5NeLPLSgncBC9VIkEHXERlt8tIsIakTa8VeA5tCKjSIu3GQ7wNk3OYSNBxhOv9IMqD0PCjzvqH1e2j0jnwmh/y1BV/tBhSS2vAiYSG9IMU+VBYBMtBMnRByeoPY2cJlOr4yemrtlz0lJtFiOYO6JvqmM63LJn3960T9rWO7Zu07fuDpmZ3qs1RZ4PHE3jL7Tpz8aJhgpsBGt8Y0yxQjUE0/85YLNj504bEz6SIH+VBHYI7Rifu7q7bZ52s3Gep89O3rcqDgm3Y/ffml1AwxsPKWlQaG4MGTo/AhNJamGF91/iOCzx4ou/SV1d3XaQd9OmTemjjz5Ku3RXKUJI+jz52TwKXbnclp+zrvrNWfabiw3M67zc8hyIdk0DMkGaOEI7Ixyt+jgowvfCkiVL0wsvvJAeWPNgGi+rX/QUYDkMQps1p0DzWRv2eHodcPwHH4chSTx6jGvnYe3iDX1DbNiwXuufvRLgIYQSABkULmiN/lBE3XxeygZLgm8UtigyczbfKvCC8vK98tvf/ib99qXfpiVLF0tDXpp1oBDvK5fHCQvXD1n4WOeZWR1UwLUn6sZCKhxWiwoibekinLCIi7B4L+Gb/YOBLfNqxjc83+sv4OFJX4eq68qBaPTXNdNbKjP/jBGfe2N1mzgzY6mP3wk63YXQLQR2/WNgbBRoOJTgh5Pe3367JW2ctsFM/C3Xycl7Zt1jqBhbKxOYbWjpX57DBao/A/6否DXTVZFXMNu8mvI0b2Ie5KLMFu3Ztzu9/sbr6c//9V9p8+YvbcJlYv5VLDRuBPt7yROewlvb2MjVwqnjhTIFxN1CnFTnBCaaINyD0HEdDnQ40OFAfzlwXnco8EH57nvvmpYWQpu7Zs6wj6kSB0NPNT2UEb34W6Xh+636Zuol7VCj0Dh76KGHTFAFLkzQsFYZqENQg1CTTaePP/5Egq3Zaf78bjusVH3E9xNp/fHamGCo82ar9C6wnCVtw3tsY4yNMu7FQvOO+7w+//zztE+bB0eOHLV6R8Dx0ksvpWeffdbuy4F/0OvzetA70BYQ6W7k01thzSPKJHqYU7Vhc1lCu7PcGyYBySGZQKOe//qXv6a3ZQrzyNGjvqZRsUkzUosgNlp8QzmXCVzhbiL2wA/K7mYqR6QxOoA3XptWk3Vv4RKZjVy2QveISHiLKTaEd3ZncS7nZcxc0omtvDCHCP3AJPw/6MOBAABAAElEQVQWd0X8Yt0oAbI2sc5KWMc484k2btev32AbuDu1gcv6nyQkBafhJaCtI1/wuuCLbxG0ZsbpPl/ueUK48+ijj6ZHtJl9t+5bvH3S7cJtOYjcyzJvJcGZcnQzViZitJysKPgqT34xmgwk46k3mKsye3T9S5r8Zn3I8sMyhfONwwMcIjglIfpmXQvwgXiC6VUE6tyjxTo7XOxXwRex8VfjrDwIvlU3mPMbO3a0HQ7ggMAUaUiiUYem77JlS+3gQXd3l1lgGTPaN/LgvY1N4sgvascYF2NzrxyTvd69SfabcfC41/bXb0wGWKIDbbsqbJtlb4kGRkqf0EFbSXNfiXqFbS5Uq7LksGZQ8rVuWxCgLmCOp8d57g00ZBhjdBPSSE8t2DiTcY/iDskJfndrGY7JugvafD90+HDaq7nzG2m/bpVAfceO76WBd0CCtBM69HLG1hXcRcVagb2QEkdB/oC8kG6b1SoodHDghj+uncAywq7du02Ax2EK+gmmfzlAPVpaQfQncypwsMPwKbCBV/DBIesYAOvA5gQVtHsaGUzZOWQwaswoHRRyi0mzZs9O99x7j+gaY1YKduzY6fsIxRjXhLR6LbE3k1UBdTwdDgyGA2rjCIw5PDNSa5T5djDkEc01T5gmPsI9rAPQBzXTWA4m3MudgxCf2+URLutJWONWn7uNO5CVHpO7h3WPI9ry//GnP+kA2KZ0ShYcrsb4MJgiX/c0jCPRicsxBZZpMXNFlhRYx00YN14HBBemF3/zm/Tc88+b1ah0GxzlYJXGMkvbhKAqTGN4ZFdFdzzXhAMdgd01YevVQFp2gegcERbv/cmnv7CBO3BGuubwiB/qM/CDhzzK93a4A6YVTRHWG0yruAhrl2er8EgTeQZMhMc7E4nDxCLYIfTbE7QaZH2Bm0+P1KhMw26C7gkYr4HWT+PXwppW6Iqk8mog1mRWZSLfLt2lc0kLvRkzZqQZ2iy8+967bTK8rNO5TIo6iNHgYsOk/DhrALCXZp70hOhXSC5QYIvytZ50m6Eih0gV7/WzXQoP93TtUrvQNeMSkMEFQoKp84Zj6IGJcP77e1XP4rV9BCuAxYwvSDxsxEh9OJy7lD7S/Rrr1r+Z3nj9DdPKiM2FWNSQrfmDjsiSiI7rgwP0Necfvvicgp91eDJztPfqI4iL1Pfu2Z1O6aORk9rqWH3g70R3ONDhwC3HgV6GBcZ5TNS9/c5GjSvjtLlyd5o+Y5rN6zHHMQ/g+AVVhU4ej8lTDUD9cLEG6QfooEFul+CBjXu09TFvhbBqMAI7CIAPaNr97//9v820zT/+4z8a7jCv1TDsVgypSe+rvDHn1imG5qvqSxm7FpVO+4oP3d3dJtzAZCh3zaBlh8kt7hBGMMPmHxpKCPsCRyMlUbgyNFpDGdcqLNL0FgdMxOMvcfKeXctg0tUR1ZuCmD2pQ9o6/DAtMG0UsH5k3tyjzc/PJTj58qsv0yYJMjkEw+ZodRANHAiJyL6hsnkncBg5iOQPuvQXfbiBwog3AAkipTHKvW4rV61Iq3UIaI1OmN8toe6cOXO1ETzN7m6M9gAPYQFLeHAbT4ynvl6xTa28dhwpk1C0fcyj7dy5I7337nv29/Enn0hLZb9pvVZ0Qa8hI0Qe3v0Hj4JypPGfuvwljRs1zszemkbookVp/rwFJpCeKdNKCOs42FRqwyLwCpqpzVyjlo1htzw9O/KjzOTjzhKbN5fa4uMbxPBCo/7wm5lQPbmfhTZnWmBqc4xBP0kA8IXMYn2pvw8++tDGJu63QZPG7syyMuZsI/cgozH45noz/ojkzFO7G0xhYzTvYO5yydIlOgwxPy3SHWNTZOp9lg5HUJf3qC4nT77dzGbFeFYWHAGstRnwF456KF1VRzkwoAOubA+GEjiAQBOoIhFxg3DNyav3yiOkkRfPIrypOPWnXZEkwANFI4kRmqEqhJGqhg7IOqTRVyVtDLa3Km3lcaAyl6gqxtWqjAAoTQWXPeXeRRnn9ZYzyfsbNjiBhgwyrjJNUELfjnonrBJyOYD9srcxVoKmUQj+NZbMmHan5sllOiB5xNYDmLnbI7OZaAkzlx48cNAOIARe8ohywq8I9yzqOCNVAM08tZJ5wiot4y8HSZir0JDH0g1z90NrHk4rV6yyO7hG6H55HKb8yNPnPIVBj2ee8cWbAslMsDbOE2zvGTgeGbzAkmMUIXh4iPCTJ3mO052Ss2bdq7XkbWmqNGKXSYPp9ddfT4z/3MvZ7OJ7N8Kt/Pml9Ed859nhQDsOWDvOkY39jqaaW5Me7GF2z5tnh5NWa/2Lphfmcy9f8fVM1RVyX7LRSYHWt9WbrEvo54oOjSCgQwA1evSodFnC+927d9lBnPXr3pTm/HfpqNba5J3F6RXp9Rhx87dy40dVMoaUpjJlAMYxHHdvL1u+ND3z7NMyXb5Qd6nfbvyXuLTAAp78GszK+5r1WFT7qoQ5TdR3I22B0IauKknzm0fUsAVgG++1gm2T3Q0K7gjsbhDjb1y2jd2nNR0DafytMfQ/NOjheTXzBVfgbkfN1cyvXR79DI8BUeDVYCvyyxKwocPdWQyunAgd6B0x4CUbPqZZFPNBf3rL6fTee+/Zyaz7dCpr0iRsofOxy8KaCdLpLznVH872s9RXAazk0NDR9adsFYw8+Btc9QEjuiqhXQFRJSasR+qK39Q8phbPa6PhOy060Kr781/+IiHrLmsfMU3GpGg5XF1WFETfOl5noS8azZTDFTchwAnK++6bLRNV92qxM9X6CKbtrE+JPaTrWZu3Dt86Je1woMOBAXBAA8ZJmXv8RvdI3a45fdWq1bZpyvyOKz987V0/8Sk1nId51hYIG9BC5l431ixDcWdktu+LLzanw4d/TLPvu0+mAlf2uA+nHX7WL3yg+p1efhCG8TrWV6yBSsFCOzz9DQ+8JTz1OGHCePFijrTuZprAzszu6aQ7dxJzjyEfzyGsg+Z6JulPTZOgL7hrNTMF3nhCO3Nnng+tMB7HOsU0xFQfCE92bNuqu9Q+Sm+uWy+hHVqH+0xTYrTWntzRjHaD1ZXSldjJ4WZ0tAPbwDWBpe58k/lL7gVbJIHXS7/7bXpy7RPpfglMQtBElcIvOMm/gpXePMAHkBzCLXjFhhQhbLqgwfv9ju1po+6q467jr776RncaHbI1pd07po1d+kbFW08Iuh4O4dgoCZ65G2nChEmpq2tuWrV6lYTnj6SHpU2LgBENLUcmalVGOyWvdKA14Vp1YrtEX+VeBpofHC5g0tM7hcKFT6/NYyPxYLL2gl8v3udpQ0n3YJ2Sab19ZvbyNW1cc4ff1q3bTDAMD6CS0/6kd54XWfag7CYLoALgDpv5o6XVwJgnP5p0s7SenX//Am2aPm3jNSYJOXU/Vtp0LmyQdqJSXxJfRqgt6uyg8d5r1dtzMzfgYema38u4ln6SG80tY699YJDfDzoC9否TVedA+29icR3ZxteyKPC4LIC998H4HE3/61Gvzfja0EJwWQYri9JGzrwHWWM15syQmcdpMtFqfVNzwlnNHT8ePqSxbUf66osvNa59LXPTX9sVHxz2QBjNd3OYG+5BZ6YLGtwxpviYwbvlLRylC0HYRZmROygz14dlVQWNcLTld+/ak06fPJ2Wy4Tx9OnT0sTbZf5X/QseWR6MY3Lt6KgKW2bYbz/Uqh/St3VlBmW+fEV3b4qJ46X9PLer28bmVStX6X18unj5Yvrggw9NwHFRPLpifApuN2baOrQRpvPW4cBgOEDf4PDa4489nn7z4m/TiuUrfP0AMusuWj/gscHA1zb0JVY8BqDG6e2TeT+3VMEylx+VUP1jHcb5v//+7zoEtkkC6iNaRoRAXH1S/irNYIi/WdOITfAQbrGOu18HB19++Xfp+eefN/P9rH/gbcXjXM7MXWrDXLxbPSgkwnN0ywdpmuFqPC2TdALbcKAjsGvDmBsf3KpJtwobKKXtcPQV3tzlyLdM0yo+YMq4SBNh5XursMDBM+LxD8RFHqTpCwfxJXyZT4S3wkFcz/DKtGSJpsnPMEmWjr01HpJgeoELjxHYcZqkdr3RXEPhYzHHn32s60P/0pVLZrbq9ddfsxPfmObitCXOTq5olOeklpWMET87p5LfoDfHCbe5AjYnGdDD5F4ZVWPCmobGcN6Ia5moorJMY6SW6HJRKgwhfCsFbxkmlvTVSUWQGa4qdZbXKTDyyIWC/7AnNh6iPnhnw8acYI4dO5o+0qbWRt07sl4bL3tlzz8068pyhP+WXIhE4YfwtI+rNun5CJqgzY3Z0oBYqpPInDqmP6CZyqW95qx+YzHYBlEnuMOBDgc6HBAHbNzXNMG4z8b6/r0/SOvjy9Td1W13mKHVEHNDS4bFfNIyst0M2Ab4GgRDO5plCCUw8TgUB4/QuELIY6av8maaguulh7wVSyqP8/esTEkfPXrM0jNugw9cbEgjWDRNJgmIcMRdCwc/2ChHqMH8gQlBKIYGwt1MYBbKGAFFIdoS1AqmVVhbBDmiLHNjepFduRIqmA27TOAhOIf1BMASTgVhpmhU1jzAbNcenX7+6ssv0/vvf2BCpG+++dZMYlInqgATDlheURdCDNZrVTdVAXv1BCOcC2XfrOgiyqMdE0mMcA8HjnZ8+4TbrY+vWLFC5rUX2kG5latCuD3O05JE8Kw93BRorDaz8EuSE6NB+B1OGev/qLFjLD0C4R0S1m3QmnHDxre0ubzZNrLsrt3gK5BRLPxGe1kAAj0/DhHcJ6sC8+fNT11d3RI6L0zd3b4ZjMUB+jjtGGf0mK/4KXGTP+/eYAy+gjR6PJ61rIERSbjg/SvEiY44WgcHq1iLRT+L+53Onz+Xtm/bbhp1aNbt2rkrbZZmzL4mLUM2vHHDdf1Mufir6trZ4TTDiMIFLEHWB3M8ZmUnSth6zz1o+S5MC/SN1z2v2zTpFkkDBw3f6To40Oxss9MCMyI98ImaHOpvkS5Xq8cpqnwPmOZnD747agcr22hzwqvxXuYFvn7kVyepgesy1GE1eREWKdu91yl68/WHp83pI2c1ohwVNOi1OSyDFBBe25FUSWhn5ipYvet/8EFfuB7Pr8HUib2N5uAMVcfWVWB5KB/mSRxrijEa48ZLg4w9kBkyR7lAVxQ8Io3+Hw4cUF/fln7Yv0+ad9/r2/m4zfMZvT0g2fsQedOnFGxhJVRPP2UK60KMyRc1jvMdvnPnbvWxt9KJ4yfSJh064VDOMmkCLsr30EI/5cIEMmMU76NUFisXMbnQPKI/We4F65xIp8mrKQsfPKhmFu9WQB8rGNNGSYiHm6H1xnPPPWeHvTHLjYbxt1u2pPMXnQCyA543O6RKoqAh00hQx3U4UHIgmghhsVVG/+r否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否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否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否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否uXRT/KKtv/OJIRX91WHCg4QE9m0Oh8uN07UJtuL5IZpEYNHhWam5vNKG56Cy3WwGUlbrzxlMOYROCdJKAb6G6w8VCDADoWysBwd1iGinNaFH1WRzGc1Xfn/IULbfc0MozNFyrUxkPG01SZjnHLoeo5wLgfx+u4cEQ6Dy0uRlZAz/SQFmO/8OKvVFiweQkvS+fPy+OMnjHIxHGoa23VXf5V+e8AB5BX6gLzGLoT5KQNG9ab7PHg4gdsh/h12wUunaH6hvdXdRJ1Wr0vuty0TotLWuQree66fMkW9OHN4ZVXXw0ffvCBeRexnXVKBwfZ48IdZUndF6pqe3Idkf34sZblyD+MOwP0zTIuzJ49Jzz55MqAq+xHH1lqcuB1LfRityK84i8GOHQ3wt0q927U9c6UWRns7gyf+2QpNsEmyvwDom8R6sNw/Ytf/wzV9XGN8nqc19Lz+NXjubYF2wimPn/+HPGU/M3Tclwq0aWuFM1qWJvfmODShOUwKBHGawv6OK2g5RwDlC53ItinvKpg14JOJhJWxpy01bDsuGOFaHNzc5gxQ4ela+fS/PkL9NykFSHjCwVIK3qNVfrhamyKeOEBUYiS8coDSpII6P0YOA8FnSlzTFL+9Gx8FA6b0oSGZ+et4yiuSkPZceniZXN3yYqf3Xt3S7Eld1GHDkqo+FE7sHCJKYWXjJWcMZIL8RlZGUpoj8HShT9ePba69jgH6hoifpRoRbPCKK1enDNntgzNC+VSYFzxUUYr4X+dMwkxzLKrNA91KPOk6r7iQMWBigOd4oDPS0z8+bx2Xgqcndp9MkHnwC2Rm7ulUp6w077deatByUx/HjSttRtq58O4qjWW5x+C7Wa3ROqA4gll1xs6iwIX3ri7qx9Hb4+pexDIS7jsweXxU0+tsp3UuAv0M+vAHvldz5T6547QkTHZZJS28uRwUb6pgcyLTqBJSjD5B1hrT+srmUxGpMk2UWKiXigCXXiiDQfKj5BLU9euXg979+6zdvl621fmIWD37j3hzKkz4eKFi6a0s7IoDykl0QJ5tX3EoQoQIu5cSB0auqwtS1KNBuu7UnYQ7Jw16iHesBIZY9e6tetst+W85qYwW4a7yVOnyEVmvrjM75EjU+NQhnDYU/lDEbEMrvpHErujaBeCskTDn9IG6iy8yMdo3N67d0947bXXwv/86c/hgAzbLBKLmaSsMmwQHgP9d9myx2w3Fn0bd5izZbwbM2asAJBpE6AMfci+sVwKt2Qrt2jDFAexgraMnuYlxh6lJBAZEv8FPEJZmfqJZTsBZFH5svCCs1QmBVv49vY7b9uK+i8+/8Jk7DM6M9hl6BtSiLuRklIIBV0ZHTGlL/0612ppgnb/M/eyAqOuw2R8XaI23Pj0M+GpVavl1rTJxicUnsYzobGdsKCDuakJvC9Gfqc0YFIf9a5KVFuhNaWtY9rK27n43sKbU9FWGWVf9L7KO9I7obfw9g61dwart0vOm9o4G7dFTOzLDudXh3VqY3wey/jUMBRbSUtooniycUnXfEzC1TO6CxZHjxo5OsyRcny0xlpkF1xE8z5iaLjRwL1w8R4a7obUtIpkXLSz+RJ50ITW58CB/eGVV/5shgy8v6zTrtu5OscYox3jAuUT3EU6/dk4YBVLvdwJUpylJaWHs4S4UqJjhinHJ4x11JU5jAVP7NyeLs9OQ4cMD39+5ZVosBM87jPZHYyxIFXB6Kp++icH6EW0O4G2N3kprwpJamjemenyQMD5vhs3bg7NzfPkMjyemRZz8y77XeyPTNkseuJKfs5hG6h+c2DPQS3YecN2tW7b9o0Z6/ycZPokO834p9sUdAMuI4Xf/he8KgX14hVx9myyTes6wc9JWoz1tDyE/PpXvzZ37cOTRyjOAGZvrO2+L5EaRi8rYozlxPsEmASFCKc4bjxTgSvmgMJWUW0De6a7evW5BSLyPnlXiepk4ZXBrpMMq8D7GwcYcVoPLZ2vxd3H4wOODzas/rpP29BZSXtFQmRcedITde0CdzSJ4EqJvx9lsIPGLz7/MkyfMd3cZN5///3hgcUPhlmzZ8m1y0S5dxipyXqAGe+GatcdK4WHyB0Rgh/KNXehAx79N3y0AH+1oYwBLg/Md+7Wx9J4NgD9JlgT1hEeJAywmgzDp//h7omt+BjgTtkZZqdsl9WevXulPP1BSq692vlw3GAMj0+wukI/tBOPsiEP3n5FXF16EV/d9DgHaHZaw9pF7T1s2PAwZcok+V5fHhbrvBDfbYFwyB/95urVy3auCq5KcI1pkmaPU1YhrDhQcaDiQOKATxk2j2h3heYozoxl582nOheV3fVLpdydqJ32KI58CmG86sng+DQUFsGLYAxtNZcVUPGGxQ7snMG1H38s6sG1Inl7K6AEQDaK3gcG2oILziTDKLNaO1bYWccuKpRxCAKRlN6jp1fqmfEPyuPnc3lHpcwfAg0obdxAuW6M6o54Zsilq9o1p53jF3Um6949e8LHH30i90Mfhy+//iocO3asWLBiChUp7W6hHFQfdCOK1ck7R69UsOeRGo/EIhSPtP34ceNtdThKpieffFIKp412ZhhngABDXTnvDcUmfIh9PfV+WK1Q9GOL5scTLDm1i+5J8qwJxHZVSEkCXgw37KxDpvz73/9uxu3vd+ywvhsxxTaGjmFDh5shB1eXj2in6PMvPG99uqlprsnQwLTIfT3vHopc5Gz+RQLEhazvJJJi3VI89ECip3n5Hb4W/SJioDz+wMsiqQFiKCvBL168EH7SQqhPP/0s/N//+59hy9YPzPVvpC8pnZQH+Tnvd7EdOkxNnwPM+c951hhdp8g4vPjBxTr/59nw4gsv2BmE1BP1udVfPOCbgh2WjULRVq1vGoFXcRUH+jAHujX6dKhexWsiaBuOFRGHZb1v7AzSAheU4BjtxmkBJ2PraO3CHjV6ZNiqnT94DLDzJhnbUs4OFdweUCTAIOJYGbTT7mL802Lhs3KTiS6C83YXLVokvc8U05VgVPPgKLx+XD3OYTzNn7k2htN4o/GJ8eraVe3cEU8wFOCG77RctF+6fCG8Jm8JeFhiMbMAbYzPx7e8jOr+HuKAOhX9bvyEceGxx5eFZ7XLa926dWGs5G6bt+gLhcyU11sZlUZgPkPHdu3a1XDg4IHw1ttvht///vfhS3niwP03MtEgwXCucCF7qqPSV62/Fh27uDG898pPfa2Q68aPH2uLstauWxte+vWvw8qVK203I7IeesuoO407EckPn7oc2kFQT1uXy6gydooDvziDnU8m/V3o71QrJ+D6uneGB/V5G5XfEZhG+dqO68iw4ENSR2DbGYGKoc3x+NXxt01lOSx6HmDr89Wn5c8Rd5rHagrSfJVwcVN++BKLUmHixEly5TPNXFOev3DeJjnSeiOgYCB4OxdlqCp5nN+jDDp69Kh23V3XSrED5lYCgwg7mFjtPk7buqdMmRymyl88Z/JNmjTZ4viAHaS63a5/Oocbw3lqQWUbN6zcCeHKtcsSPM/Zofc//XRKZ+ycMDcUnLlzTq4vT506FX6W+89Tp06HU3L9+ZN20p2Tq4qrEqBtlWwddmut1KD19JmA37r56zBUj93mQN4F6KNCyPtEe9BH+Rs7drR2XDwVNm3eZLvreKes/yoNgZKPlKv6WMH1Ka5JrqMEQ+nEu5gI9GL8udt0VwgqDlQc+EVywKYM+0nVt0GFH8YrdqOcl2vJL8MkFr3IhdoyMy7Esx8Yt1hZ6eNRIwZGeaJRSttxjJcsfPERz3ZrQZEtaNAHNUrkNgrlQxIZgJ11Bw8eNMW7ja9tF9ftFIwx7hoQ+QiXjhjwZs9mB/UCu7JAKM4DXlzrCngzdI1nEa/jiLOPl+XX2jJ9J0xtbIIlMnaDOHf57JMU+Ek0U5oAVSg8lrnJJjwUK+wUIyCrwA92bu3avUvG1O/N5eLevfvM7bN5SdCHv7dRYShJOA0JP3VMcfgivQdvqLqbnNhP4P3Q5Sov269etPVb0Um8pYl/XHHNvnDhovD48uXWF+Y1N4dm/eFiaKw8VmAAJ68b6WI5ia/GdyhSoD3sNuG3yDINN5QEy2/84h0im+J1ExWt0VjHjtOvtXvjnXfeiS5jtbOiXq4Ez4gRI0ze57zdRx5eqrONHjcj48yZs7TjNp5xRJkY6pxP1DmSqV/dhwHQRQx/nlZUxuINHiJT8N0Yjp否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否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否fRB1y+It9NPZzScSF79u0N29WW3+vs2s8//1zfHTukN4leiXhXaP+iP3vBvVDDvoKyrSrCvxYZyvm+wePIU6ueCs9s3BhWajf+LG2y4MxqAu+Atxb8i6EtrCm5uvxiONAtg50Jo11gVVfzdaGoKkvFgQ5wIB8QfZD0a3vZ24JphK8WTxSA4oRWCkN1+Gy2I5/wCQisNskykCdQf5dcqBo9elRoamoO8+fNk3JsZCxUsCiMoiEjRpW/Oa1lbHVXcaCvcaAQYIrXJL4T3oN5XQYOwugmyvXHR8iKFU+GF55/UQrh+YXgiecSc1FkYHGFIvC8I7yL589fCD/JdcnZMzLYScjyYO4w/aG6VhyoOFBxoNscYLAiMKj5wOZxllD+pOhCRVSAFTEGe/7n82G3DD/fffttGK5D4mfMmGGK5hJRG3cqvp4C5IoYYkohkrSBor1odtNhlONMJ1wU+yprPmL5w6h3u1DKOxHS6W03X20VImh9nD/XISqqr3ivexkXMzmPLJ0o3RidmoiiuyetXsaYo/nFFoukj3PyoTjbv39f+PDDD7XrcJt2ZH1li0WIx83QDbnPQVFCXuCL1cpSdHljefkF6YnQ+vg2qlhku5M38Mfn4Juqi9M6QiuPm5vnmcvI1atX2YKbGdNnaCfaCDvLTtq9oD2G7GfQH7+Ixt4LUg316HWlHE+lfrHtlOqNmNqqFiY+Fct51ABWROKr4dGP5wEVsgHGqhtXbspwdTC8996W8D9/+h8pZg/LheccneWzKrz0q1+HFXKVOGHCJLXnYClRcT8lLPrP1UmCFzn9gMQyYyypdudAMVm/qms9LzwN2uP/kjlFWrpxAjxeV6JitSlMCHRhpwguL/fs2R1ee+218OqrfwkHDx60BQPs1uC99vYElb+z3BcVs4dWjym2714GaVEkRjt2qlCvmTNnhk2b否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否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否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否U3kRzElsAgkOH6DiEUQqNUOWYzpHQIzPXNdVXVVRifKRET5w04l5cy5nc0JyPs3dOcXu2alJkRSPrYsMLxvFcSSKrYjzRBvzp99qxM1vG4FU6KrFq1CjvkRpEJoWkI2mJNndJjdNp7wQkSIiZfdGLuHLxPnTrFDavejgXlje74iWPov24Jj2zfiaO3eJZMi+RiYi9EDuXFsuadE6ZHmxoFjGN2L3AWK9PKgivIxmRseEzW9pyNt/D+ds/KS6qSZ0LkBVkJDO4lOPOPZy7W1ta53dDztm7Z6lauXCljUGo5BDvqULdkkpl1kXXfai8Qsx7rM3Fmc04ll63vaQx8nsCOUrcHOgV3m+a6EHM2Lheyt56snmTrT2/h74943qYs4oRdf6whkeYogYwE2FyHzXcYXbApR2DQF0inPxFbwNetX4+k7ejIRqIveYmBrsty5g0/bGVChSt1oZypUhXmEv1RAp1LoGA99EnY6U2ZMkV31mGyjmaoFuPMusrKSlE2WRcB4p3H1BlCQDJa1bMkoSpIVGCJCfcnT7mb9Kr8Cp1hRLDoogSiBKIEilYCQfOW0GhtY4E4fnRxh92pk6ccd9cvW7ZcVpgmaeFhMkPB8Cwa6gDm2HbbRwtx609Tabrcz1kN0xwsD8PPD+BEL0Eg0Ol5JfhYtdXZzIsf0nTMi+3244cP3Q2suL579467fOWq7Jo+f/48Fh2dgQ5zSVYgSwJ/sQ9vu4dxOX4SwI6H92J0JjjSlkMiOJOBNcge55dZCbA/HT9+vJxVt2DRIrdo8WKc2bHELcFk3ZiamkSunNiibKnnidwL8c78wvwLwfiwtO/OByKKHNINpBPcIXwCZoF2NzzEDz74o+PCn1KZrGnD+TuN7uixI+7bb7923379Lcymnpcdp5y8fHfXblk4NG3G9GQnltVz4iG+lPaECkb1yKXkpr4eIcgAC41EBZJM7qS1AWc40hzsl1/+U86ve4Q2gK53chVUb/XiWRT6zW+7S16izrO否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否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否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否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否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否ZTjnGP3Zk6Yn7uKFi24f+u7JEydjQc04NwWWHoYNr5b+mWMKdCUJIsVDfhljk3Vi9Qn9+IgRI2TR+F2clfw7jmc5c+a04znNdF4k4ueFJKW9RhIcPW9LAigA6rVc5F0Oi008WmUYLHYtWLAQFiXedZuxU3L2rFmiE5dhog7QKHbUb1+XeKNexHKVsqTH1xeGq7M7nwCQrQQeKt66lkAqU4XNPneNIUK8qgT6zYRdrBSvWsT9M13/Km/rKopb1j3qozIshd2dKV3aF0oXKf0jB4+s3EqwpYnnqyzBjiuablqOs83OnT0L8wQnMSh4FrucbrgnbU+QLqXKVjyL+hSEF7dUBz51MhCI8qBSVVVZjQN/69yixYux8h+H/dbNgjI1G4daTxOzmKGJAtYT1o6culNAXKwzBiPVjmVv9Y9+iyyQlkEEzXFBcu7q466QxkeN2OlxWXZ/8HwburDu5aSPD1ECUQIDVAJ5rUVR80lqOeYh7Sg7XLSHHXHQnfZMmtNMe8pHLp55hMU0Vy5fdbex007MLGYkw+x1V71OGLRiAOTBgweye/4iJub4u4zf1WtX3U3sYqYpzEc4n0RWUge4JHswwdXRebwEAcFmGNEvAhT91xvwl2WC5cdBizE4D3gBzgNehAGLtZhk4g67yTCBWS6DFZqKfZo5MaUpQuUlv374amPg/eMu/Ag7Sq9/FvHhInUd8tLdcUNkYu4Ozkk8eOAAdtgdkrOUnz9/LouLuDp7y5bNbufOHdBTpmOAUFfum66aCgSZqAilXhoJFp9qKRYS3A2YL1hXCotPlq0KfBskeYLW54gCDGmlHvb8+TM5F/IA+D169Ki7cfMGBl9bZPA1SV7I4zPI5l0I9E2FUVSJuCwTTxB5Y+vSislXthvjsZqe+uWHH34IU1gb3cyZM/A9UWGp0rukB1a542IZeAi+A1lnc3lJuB8BLAAKEE9gAtwdT3fSEMZy5D1MY3FhWHfyjTCFJMB3qDt9ZKG0MSxKoFgkELYS1jJYC5LSaG0H+0rWe8bonW3hyFGjYElhsZx9+vx5s/Ql589fgBnz5pyFHim+1/Ml711IP否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否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否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否ePsBIsL8uwcmwXkWW1HUGkz69BeDoBAUQUXtL8htlyl35zcWsUBNMJuHuOdwPOwC7+aTg5Nx2n6ibhTrrJei/dRF0YYXzT+CZV6lmdrA0okFupTugtLFPT8DD+BGwsBKwglJYDYB+wuDC7xgUdz5XpFK3MZ3xnnsdQ8nJn+pnTZ+T2zTtJOyT48gpIEqMnciBy4H3kgI1a9g7rYGNxGDeYfhtzgyFtMMkpKruIXz0aM3sEXFR21Uck1Uta0ZPsErgDfAKsBfD00BqYml63dq0sw8m6GZiHVVmnk9dbKOn4Mwy72ylcBI5YDLPNczbXJgJDAG/ehCybSC2h7NtKIpBhsLhsuCyi7hMzaA27louA1VUpwYfBU2Z0XTgxf/36ddn7/V5daLt67ToUna+Vb+Mgw2zatkX2fLJHFi1uF5paL3JATPmA5SVlGi0BMGUddR5Ix5EMnMkZ6fcBAPyvyKJGUjhKyeBluYwifjUThfrfg5x7FlYljsGUF+Vc3ieUCorMkbpiutK0gfKRBtZB5VdfqFYZkYx3Y527N3kmvoUNMMG2edMmWQ4zmDTJZnXI0utazsXyUBxV2iY3G2zadimTNY4Fa6urxyNJYdL8hb50DgnyeZDKftsVl2ElxHf1cKBUn6seCt+NEhunqree7vuyKy7ct8U4++7s3VM+2PfX2/w9La938KH8h+HzHZyrr0Phnobb8否YaWMbw/qPyhMetsM/YpmEU1J/+N0fcGp9nPzHn/9Tjp84Lm+wKdrJLu6OeFq0eVdndCkmI1aRprhpNrHjQoe8/uoNTCXXSTOuS+FaBmUDZjEc6tEIIFDauMHD1Y8ouXmZJ+y4obuj44IcPXJUFXZcMyGcbfLW4uOjWw6QZzy1OAsb0否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否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否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否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否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否01sYWYihs8t+TR0/kMHbmf/PPf8qFjgvysutlZFLkQOTAe8iBvB/dg1YN/Fi3hebsPMofMFRC0aTjSygcRuGH/uTmGXqabNOmjbJu7VqcMluJhfnJQvM05ojvGXbldsDk39EjR2AOKF2oN5iqe1OM8BoLbR8vUtiPOC7ec0FmxowZUK6sko8++hgX3G+TefPnJrt6qYDhX4E4Qpz8T1NIMGGpd2IB5uXLLjl//qz89NMh+eWXo3L79p2EJVom6HF0YCZQS0tvpXHCODVBOrulRaZMmSwrcWfHcphP5B0l48fDFBDoY97Xr93swSqx8ALFgo9zKVzMcM4kKAv76AF6sVSjICyScQNFUR+XA3SKEQ+ah7165TLMQX4vf/3rX9HuqSlM3ivTjgWnbVu2Ck9KNuJOFutzyhNbG/PIVDHY3QCisCEfy/gJSzbjT3sNwna/YdIkXNCrGSFXrl6So8eOCe+iu379eoJUyWE+ogEeKonHjh0rU6HEp6L58E8/CU2AUm6xhcp0zEnQDKqHPNcFV1SCJwD5HTeNa5J2mPv97LNPZfvWrVjsnO0XLMEpVhR5yDo++TZXGCJXuncJVIio+2wRInIgciByIHKgajjg5GnOD5xP6mDqev78eTqHcIY9sP8AzEPf1HlQ7xTGHELzlYVzRi8qg3mDU0chnmHSBbn9ypUrkDN/0vuVFy9eqnMzy6bVByr3XB7MQEBAWrjJiAo73sd8/OQJGYPNN3QgVf90xkP9BmuqGjVqFObiFlj3WIe7o9dKa+tst2GOBOa4apM1ckiMUZED7z0HKEP317cWFXbvffeIFagWDqQLDL2hKJxkTQTIizPcYVq5OEsb7LfViXTk0c74PJgwjjDdu561Q0hLd2UZbB6cpZG+vPTu6S6EMHwhrmxcd2FiNJhC7C4U4i4Hm4UrBxuWZ/ksjuFMXDbIBSB1Pg+CFImprOMkyEukT508KUd+PqKLuzSnEV3kQORA9XMgHZftG8e3nXoHrwL+B66SkkMQFxy44P76LU7zAqgR5i7XQkm3Dae61qxeJW1trTAXN1HN0zgc9oNedKHg5s2bcgl3XD2AWR3e31atTtuHwy4qoeYstTKsi1/ERxIVdlRK0gzm//7f/6fyYeLE8ZqJ4zNhyU7+cbFf48A/Ln6wsRnn+sFw6cRJpSuXL8q+fT9g9/JxNR9qvFE8PMHjEEFZ54ihom/K5KmyCuXv3LVTlTxz580FTaNxiqlW6YNuVcujWU465mSzZhUKhVNjUln否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否atVJP3bj5GacFkxQ0445vGQ8G4WO/d++qrr3WOH2irGfxN0tjYKPMgE1N52NbWpiffeadddJEDkQNDnwMfzJduA7lNJkO/aWMNhyIH0v7rfi4X19Hinejh0i0uhA7jAulEQZgWxoWwKY5QqEkkoSRfXh7DaWnZcIr73XzvitfoMyqyYYvn29KszOI0xyemG4zlIazFWb4wjXHZsMFV+q48f9q3rNxSNJcqO1sXF7ZYo8R25hGLpaUeh5tCMi1Ndb3o0sXvjgsdcgOL4C9gO554knwOHGHGqEjuY6w0H4yvyIHIgX7jgH2PyVeXePqtyP5DjIVp/jheCEXCzp27ZDPucJozd47usuXIw3Ey/HOEMM75eL/FtevX8OP+KszgPVKlg6UZ0UX8soQBeNs4bzKxFUkanZLCxcykGdANG+W3v/q1LFu6VOpwbxzzcrGFTpsYDyriiKuAJ8BF5RzdSCjceJLtORQ4VFocP3lCLl+9AiUINl7AUSHX3DxdVqxaIYsWLYICZ6Eswe7rGVjQYRoVPM5x5zMVNlTlOA6yBL13Dm8tjXXwaS7CZy35cjSWTK4MSensfZ7i6u1rWwK7wVhyNmzxfLu0FIL8UK4m/dlBB3wyr8/EWddMwvJknWXkqfiDBw7I3//+d/nl55/lKe5xo2M/4S7x5StWyIaNG9X8oiplNdEoQkBJscI0o0a6buW+Qc1idCSdl3nQR3y8OwQKeAIHLpEWfBH2XRgFpoRkPGm/jpOy/Kaf4/umQpGKeuvjAVqt35jRY6CAHqvyS8elC3rK8MljKO6D6mgeEpWNC5ENgJ9so3K+Cacdl+FOHy6irsQCZnPzzORbJ09QXaVV+YYA+aJ+T3/Cfs9pC2u+onq4TNk2cWClGGLxYS6LKyogRkQO9AsH0nGiMvQ9ha8Ma4TqXw5wXAnHmXctrS9xvSstlec3qpO5kvMAsit3gqHX4ojZxnudHxQG8gF34ahLTWNSxl66ZKm8hfkFbmqjss4UdjXYkOU2eDnZ0pmdJnKdiZKNL+XmTqPD2jGUd3knLe+i/Qkb6lpbWnAX3DycJJ+icxrzhd8s/ZRPKLNMgrWJOrUwQZkUykfA2jzn6td/T5qCnzp1qrS1tekJO5rGTOQmX2zaTv1HR8QcORA5MDgc+GAUdoPD3lhq5MD7wAEVvzyhJlj1Nd0U6Uo5pvVXuaXK7O/4sD6l6l4qvr9p6w/83deloJVLgJtFDJpGozkpLnz/uO9HOXT4EIT5B0kvyRdMwxJC/vdHfSPOyIHIAeNA+Dm/z18eT8/xZN0i7GDlibI//elPqqwbPXoU9wfrNMW6joDiSE/WGQPw5g/3+/fvyfnz5+TokSNYpIeZn4H6NR/QUc4bLkRk4UgqFREjcIfVBJgA2r5tq/zbH/9dtm7bpifrOB5zIcUt2aC+emLOLagQFzdZWJqG3eqK7qgmzziW79u3T/71zb/0xDR3Btfib8YM7LTeuEF+9ZvfyAooCmZOb5aG+gYs2GDDRtcr0FMjw0eQ3/xzkgL5yn5GZZ2+sXLCMuh3jiE6i7Wwi43P7jigXPVAKVeLcjHJ/tg4aIcunJqksu7QwYPyj3/8Q/71r3/JXZiE1N7BxS+2OUw6bdq8GX1su0znKS5v5jLsn1oqBQEUoK2LvOGiG2lBVPeOZGWqkMgP2n+APUBkXiqHebr/Ee7YOXX6tFzECYBHVLrBOTrKFK60vpEnUPRdu3pNFdXDoEF0Y8ZwjAvo3OaIJkOfJQ3EmwrxqVOmyqrVy+XTTz/RuzqprOdi4EuYs+XCKe/4YRuQb6qch5/Md6QzrsK2GIgKxTIiByIHIgfemQMclMuM8e+M//1BkE5RjidpOJhbc1jFuZRyHBV29LvNV24eH1YzTKZB1mtsHOc25Xacl9u3b8vLrpcFjEnmHCKo0HUHyjns7p07uMvuCJR1c2XN2rXS1tqq9IWyAItzs5zo5hUqySZDaUdrEI8TWaD/76cmTXa6btmyZbjbeXZyl7OxJCsbWXx8Rw5EDgwNDkSF3dBoxyFZC5uAshPokKzsgFbKBJ+erhJk81k4JL4nOA2WePhn4RDf0PFzUUMXn/LY1otqDuz30R3R1nbFcLbTPa1iadi0DwzTu42uXLkiP+z9QQ4fOqwKuxQHfWlZhrEwPYYiByIH+pUDyS9j/wVW24do9LnBt5gVSCeI3UM1Fj/Eudv2k08+ka1bt8r8+fPULORrrDroeIv6UR4xmYRx/ONCBN+8o2v/vv1y8OAhXXywAsPi01HLUgfmbazIlkYlHelz9YAZ0NZWWbdunXz26a9wCmq5jBvvLrW3e/g4RhPWZUpHYdZLFzd8BbNd4TIUHl9//bV888038uzpM2nBzua22S16gnHFylWydMUqKO9mSB2Uo+Tna+yCHka9BhVBcMTnUSdvTfBp9DsawpJBkZ+A3ma1NprZMDJQmE+Tu41zUCllWTyW3tM36cqjpxweq4vls3C5PJZmeRjOzxfGKjT6ALsB+1UNTtbZN3EfyrlvYQbzL5//RQ4eOoS72x65vgXAUVCI8z4YLjjNw3fWrO1dl3x/LD3B7Ulhn3LUuSej6UJ6NJzTwdMc9KU56EvTmDvHAYh38NGsJ++ipFluKiPpXL3zMfDbeAETmg8ePlCzoDThreML8Lkcab7Ul1N+H0ZZOWSR1t1H8Durr6tXs6RbtmzDt7hV2traoKzDHZ1wOtapj8umrLd7a5rG45Hhu8l7uZ+b5TGCkjZJIoJ2IaV0aZoLx2e1ckDnBRBnY0G10hnpihwo5kBPxxmDt3GqGOP7HsN5Tl3ZKjogk/00ZPkse5KfHsoNEOyAnEq70bDcsHjpEtmzZ4/OrydhMv327Tuak9tEOOMQHZ9ahsfJl6HNFBdA0JtNTeVdWsDgGsNV/HFzTWNDg1cqZlAgyLmyYWwD7nididPnzXL58mV5/vxZMWA/xdA8fWtrq5rE5Em7gtN12lCVcaOfyItoIwciB/qZAx+Mwi4KkP3ckyL6KuNAVkjJI88JT3kphXEmCBTGlg9ZnkroMEyV0mPwpd49KbMUjp7GlyvT0uzdG970lJ6+gDd6icto7glel59itjn1JWjhscWeJI6QDAzTu+ouXbwkB/cflFMnT2Hx7JUuBNiigME53AUIXFR8Rg5EDvQLB+yLtq8u++6XQvsLKX6IUyfEE1yzW2bLRx99rCfrZs+eJaNhKu4NTpbx9zBlSCq3TJbUxWtT5AEH77W6cOGC7P3+ezmM00U8jUPYdLxyFTBe9Vd1QrzJAkeyes7FkhTC1YnKRmfasq6uDsq69fK//tf/IRtx6m3MGJwsfPMK9cZPBdSF7Z7U2++a5ljt/msqIPgGP6HEeYMTOrzrg2ZCz5w+A8XHz7iz5B7uxpqGE4wfyzac4tu+fRvu4piMMmrdIgR5hn/MT36zXDqSbbx0MYjwaXZeycxjEppmOHvmCB8wp+LMPS2nO8SV4DOYSugNYSyf0RCmWVypd5iX/cj1Jd5rRsd27sKJtJOnTsh//vk/5fPPP5c7d2/7NgQA2nL8xAmyYOF83AfTjt3102EuFYohomU7myNetgP+uzvTaF7VtzDA2AsT6JD8JNIh0qBPd69wwc/j8H2kVFfp7Hwux48dkx+ggLzUcaFQsRiWbbTjzRNp3H3PE6VU8NmJAfKLtbA+rFlK4AjQ9ZuXC5DjoTyd09aGU3UboazbgrtxFmPn/ihYN4CshROG7mQdSXBtTXL5x+9MXci4bF0y7eEy2LNsogGVeGcLKgEWoyMHIgciB/qFAx/GGJTUErswbKhP4jxfkw0aNqQXAGDO1Xg8+Mafm4U1UudCTEOYdxbA5GSjjMCGnhf/z/8rd+64+16psNM5U2cdFliA3FPgUOenJCAFHlUYIoZyxT1sMDpz5rQsOLtA5syB6XucojMZP8zEuMbGBr0/jpt3uCmHyr7+vsuO8zTNYVJJ2NbWphvdSCPjo4sciBz4cDjwwSjsPpwmjTWNHBgMDlAAyxOZnGBWSJHF5cEbZBZf否+FCV8Oj+GL78HmAFssbLWQHrYgBXsKxVzwevWyS65evSpnzp6Rq/8/e+/BntWxpQsuIYHIOUeJnHN0JNjG4Zzb/dw7d2Z+38x0P3eevj19rsPp4wTGNmAMNsGAAWOCTc5JIgihed9Vtfauvb+gT9KnXAX6doVVq6reXWnXqloFXfNuZzs/GoxDfOchftEeEegRBNhGkZBf7k6S7LetkcIBnOSiuplp06ZDePQWTta9hrvUlqjaG6qB1AUG9Duu57H+x/VV+GqWVxDUtba+lLtQh8n+6szZs3IL6nZonDAjVRuZANaDFuszvRgkSdn8eZJt2LChWLyfjDtFVigGa9eukYkQrnBsdcISLrw49YQZgQO5uQqRDMNcDCHvIf4eEvbj587+JqdPn4YAoEbTWAP+7723W1avXo1TdXOTu7KYR1V3iQrlZHUp3uXqWEqVFC9auoqAAW7g8gk/FdTBQpkNvZx3my58nT59Uj7//HM5euxnuQ+1mFSFVQsVmDU8LYkXSnVS69atlw0bNspMqMOqq7VPUCZmCbl08Bv60NkxY/lnrIB1nkkypWAAhWqYf9SgXbMOP336FCfszkN11km5fvOGRqV/QRsImDJ+M+5rZLvhYl7Ly1TFV7l4AYtus9o7YwK8Q2gBFijffvst/L2tJ4vpp0axY88HkwDEPgDuEFclruQn937LRinzskrGszidylxJrjEgIhARiAhEBLqGgKmPT3tn119TawM1H4wePVYaGkbK+nXr5MSJ4xCgnZVHDx/r/CFRKx8MITZ3TXPlxiqObzQ6ztmQYE8NSH5oUbq72EDGuWlj43y9p4531RXy5zBYg3yOhtaNRbo2wRN2N27c8GsTyq5bfkbgBCLnTQ0NDSqsmzx5sgrw0jymqHZLBiLTiEBEoE8gYF9LfSIzMRMRgRCBdEAKfXvfbh/dfTV/7SNUbIDP+5k7nO0YZ/MzGvrTz/yNrr1nnj7kx7gMz/u1x7Pvhle7vlSbX/r+imFufvl3FuJtYUbrwszXnukCkMUlPf9IQVWYrap+ivcdcSLdBFUVzhhfe5I6tZMm6/LR4iMiEBGoIgJop/h4VYMP7j5t7Au+WCZZBHQY7EfnNTTgPq235b/80z9B5cwCqcfJOpaRxeNGVhMeWbGNHeNSXeatWzflzJlf8XdaTxXZHEFXBIy4F542RhCGsG+kP3foMp8UsFHVz/btO+QvH/1VT9uMGz9Oc0shXa0W2r1vK5eD1fmRl6YDJ1X/pfeVkEUNhB2n5IvPv0SffkXWr1+Pv3UQiC6TZcuXQyg6GvjxJaT3ZBFrF9NbnFNvOfMiBO/DR1gqurNxGG47wBlaaPLxjcL8Q37mZzT2LOVv4R19FuNnfmF+SvGthCYfN4xjaeVp4AYZ3y/rgb3zV22tKqA6fvyY/I//8f/K3r17VTVsKwTZPKHFk6uMyLvrZs+eJZs3bYHQboOMg5A84ZNJKs0LUtIQTStD045Do4XlSHkypoVo1Q5ZgQw1GPX4lZbzzt078id20d9A++amIbYZiu4tX2FUs7NMVINJdZq0U4DXl0wNBOn1ENDPnDldtmzB3ZEffoR3sllG4HQt3y/btp6oRaZTGImfC6RNrXyyHtARmOQwb+Lty6/MEk8fw95EysAoCkNSmqzNYmR9o6vnEehwO+35LParFNl/mInYGhI9+Uzxz6ba2T7H+HU2fjYX3e0qGB8zCWbLYnU1HA+MwqJxbODYaf6s01avObbOmjlLtkGzw53bt+SXX05iHoFNMq1G7cYmo7enhTpEwdt76BOelp8aPzBZOPNEHrx3+uSpkzKXaroXLpSGhgYd5y3P4ZN31/EU3vXr1+XYsWNZtZQhYRXt3EzIPFFQOANaCXjajvkm3u5piRkS5o7PiEBEYCAhEAV2A+ltxrJEBCICEYFeQcBNlytL2iaWLg5d否Xn5PoFFrsuXLiAu+sOyq8Q2HG3uhn7KKC7Iyla/PiMCEQEIgKKADoe3p8xcdIkXDq/WV7HnXXrsMN37NjReuJL+xp8GNPw4a36oWwfy/xgpirM87+d17vZjkFocf/BQ43DH+vXEo++YEFZmH8KEuogRBk3bqKedNu+fbuqqOQ9ctnO1WGQ6XsDQBQX4kPJGP8bUCw/MOZf3dA6aWycp8I6qt+bijs4xowZq8K6589faH6gERNxnUAkWdEhXi75ssgZiT3LEsfATiHA98+xmobvmOpKeYLs3v27Ok5/+eVXcuDAfrl8+bL6c1e91ZmRo0aqgIhC2sWLl8gktDkKZlWY9QqWgNal4H4tRVt0C8O6are8sYKlVZb2IVDh+lTu3LuLnf6/yY2bFNa5U3KMo/XbgCiRCZarrwnqLKvcjMCTdZs2bZA3oY526dKlMhILkeytXuKkcC0Eejwdy3bL0w1sUyk+xiU+IwIRgYhARCAi0BEE/MCpg4rT2GBjC8fVqVOnyebNW+Thgwfy5EmT3mXX2urujbVUOC5ZHPPjGM7xi/MEmzOkYeVsNZrOn7h64wLuqr0FQWFyoi8XjfnjCfQpU6eqcG8qnlQh/+jhw2RelIvSZSfTpPpLCgkpsGOanLObSecw5hOfEYGIwEBFIG35A7WEsVwRgSojoB/sVeZZKTsboLs/D5wAmSm1OlGOxsIsrrnJM+9nboYZXegXxqGdxsJJXyqOEsafTiNguBZjYPgzjHR5dxrHQvhMdl9rsAthnebJDK7gcTP+U9x3RIHd4cOHdcHsBXaqZ02YL88jSxBdEYGIQNUQC否b22rQ1l3rq1pCVWIU5DfIa545F+YboQZn06ZN8sHu92QF1EHyjgp+EPMUEbu1OkoV8NFs4607QQaVvRBUUMXfsKFDcUfVCzkFlXlfffEV7mg7oQI80ttYbX1jZbnK57JrbqrlyyxiuOIoKswf1f/s2LFT3t+9W0/WTZkyGSoMawsSVTxcLA1D8VyxwkKRN/7V1WKxH/8YhypFly5fKpOmTNSTVpMnTZapU6ZK/fBhTkiHOFTHSdMKgR/O/BF2/aOf1q8yR+TafFiye1ozxpihsVoaZjbvV8od8ulpe7H85vNQCY3FCWlL+RkOrPaOXls82z3+aExdJEN56uzo0aPyr//yryqwvnHjZiKoIjXjkM28eXNl85bNshFtjeqd9G5Cvl0ysT9Y3Qt3Xuq07JAGxj8SukxcR4I0vSWlTucdIQPQuTK5fLBcNOwXmqEK8/TJ0/LTTz/L9WvXVQDpAvW33/7wnY5Hm9+2bZv8b//9v0M16QYIzsf498S2C/WlHre02TlA+Wt+pEmgLIFGcs9dhtLxsiipK+SW+hpd+WdH6ctzi6F9HwHri5hT66f6fq47nsOBVLaB9c6szynWb5XzC8M6Xh/6Uox07uVzZfOFdABOsmvjho7ggC5FIbDBSs0OK1askiePm6CK+hI07PyKOcZ9beN2sl8Hfw9/whdueyNJorTQ3y86pCmlFKyTVAlP4eCtm7fkJv4ePXqEO5U5Xy2MwfZYD6HdJIRz0xHnz3eg/p6b9sL6nabQNRvTYzoU2DU2NkJt/YSSp/+6llKMHRGICPR1BKLArq+/oZi/iEBEICLQZxEonNRWllWbXmORFpPSNizyvsLE+eWrl3L1ylU5f/68/AEd8c9w2s4ZpmNxKkshUkUEIgLdg0B3fJx2T04LufIjmIvy48dPkFUrV0MV5lsqtJs4cYI/WYeeBl0NexwnWPB9HDxZbv6jUKsF92w+etos53HH1elTv8rFC5dwOtipzaN6H56w6Qs4Mb8Z48s2FMLGBqgBem3ba0J1mLNmz1S1hRnawEFMCIpHAxg6t7OQMAgELU/rcJFlxvQZMnvWTF10oUjOFiGpSpT41tUCKzDnH9NIRAGWEFlH0/MIePz5PrTeawVAm4D7FQTWT581y+8Xfpcffjgo+w8ckEuXLhet78NHDJcFUDO7ZfNW3F+4Su+LdIVhO/SVCO+d1UsrAB5JHXCE1f91iWmitPJvCMpndbPpSbP8+usZOX70uC7k8SRhfzQsj/VBFIxPwf0369aula045cr7I7kYyPs3X7W2yBAI63iyzhkgohj1x1LHPEcEIgIRgYhA/0HAzZU5Lx82rF64kY7aLnjq7RS07Ny9fx/jVAvGZ5YIPzpXDAcoP1kpVuCQzMUuG否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否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是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否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否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否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否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否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"); } - .agent-avatar.claude { background-color:#fff1ea; border-color:#fed7aa; background-image:url("data:image/png;base64,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"); } - .agent-avatar.unknown { background: #f5f5f5; color: #666; border-color: #ddd; } - .agent-name { font-size:13px; color:var(--ink); font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } - .agent-meta { font-size:11px; color:var(--ink-faint); font-family:var(--font-mono); white-space:nowrap; } - .agent-pill { font-size:10px; padding:2px 7px; border:1px solid var(--border); border-radius:999px; color:var(--ink-muted); background:var(--bg-subtle); } - .agent-pill.online { color:var(--green); border-color:rgba(45,106,79,.28); background:rgba(45,106,79,.08); } - .agent-avatar.claude { background-image:none !important; background-color:#fff7f2 !important; border-color:#f4c5a8 !important; } diff --git a/src/viewer/server.ts b/src/viewer/server.ts deleted file mode 100644 index c62c44cc..00000000 --- a/src/viewer/server.ts +++ /dev/null @@ -1,1734 +0,0 @@ -import { - createServer, - type Server, - type IncomingMessage, - type ServerResponse, -} from "node:http"; -import { createHash, randomUUID } from "node:crypto"; -import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; -import { basename, join, dirname, resolve, sep } from "node:path"; -import { fileURLToPath } from "node:url"; -import { homedir } from "node:os"; -import { renderViewerDocument } from "./document.js"; -import type { Action, CompressedObservation, Memory, ReviewQueueItem, Session } from "../types.js"; -import { KV, fingerprintId } from "../state/schema.js"; -import { generateTodosFromSessions, refreshTodoAction, updateChangedTodoCards } from "../functions/todo-extract.js"; -import { - DEFAULT_TODO_EXTRACT_MAX_SESSIONS, - detectEmbeddingProvider, - detectLlmProviderKind, - getTodoExtractorUserConfig, - getUserEnvPath, - isAutoCompressEnabled, - isConsolidationEnabled, - isContextInjectionEnabled, - isGraphExtractionEnabled, - writeUserEnv, - WRITABLE_TODO_EXTRACT_KEYS, -} from "../config.js"; -import { VERSION } from "../version.js"; - -// Self-host the viewer favicon at /favicon.svg instead of an inline -// data: URI so the viewer CSP can stay tight at `img-src 'self'`. -// Mirrors loadViewerTemplate() in document.ts — same candidate paths so -// it resolves both from source (vitest) and from dist/ (npm run start). -function loadViewerFavicon(): Buffer | null { - const base = dirname(fileURLToPath(import.meta.url)); - const candidates = [ - join(base, "..", "src", "viewer", "favicon.svg"), - join(base, "..", "viewer", "favicon.svg"), - join(base, "viewer", "favicon.svg"), - ]; - for (const path of candidates) { - try { - return readFileSync(path); - } catch {} - } - return null; -} - -function readViewerAsset(relativePath: string): Buffer | null { - const base = dirname(fileURLToPath(import.meta.url)); - const candidates = [ - join(base, "..", "src", "viewer", relativePath), - join(base, "..", "viewer", relativePath), - join(base, "viewer", relativePath), - ]; - for (const path of candidates) { - try { - return readFileSync(path); - } catch {} - } - return null; -} - -function readProjectDoc(relativePath: string): Buffer | null { - const safeDocs = new Set(["browser-extension-ai-site-test-cards-cn.md"]); - const name = basename(relativePath); - if (!safeDocs.has(name)) return null; - const base = dirname(fileURLToPath(import.meta.url)); - const candidates = [ - join(process.cwd(), "docs", name), - join(base, "..", "..", "docs", name), - join(base, "..", "docs", name), - ]; - for (const path of candidates) { - try { - return readFileSync(path); - } catch {} - } - return null; -} - -function readJsonIfExists(path: string): Record | null { - try { - if (!existsSync(path)) return null; - return JSON.parse(readFileSync(path, "utf8")) as Record; - } catch { - return null; - } -} - -function deliveryArtifactRoot(): string { - const base = dirname(fileURLToPath(import.meta.url)); - const candidates = [ - process.cwd(), - resolve(base, "..", ".."), - resolve(base, ".."), - resolve(base, "."), - ]; - for (const candidate of candidates) { - if (existsSync(join(candidate, "artifacts", "delivery-manifest.json"))) return candidate; - } - return process.cwd(); -} - -function readDeliveryStatus(): Record { - const root = deliveryArtifactRoot(); - const manifest = readJsonIfExists(join(root, "artifacts", "delivery-manifest.json")); - const evidence = readJsonIfExists(join(root, "artifacts", "ai-validation-evidence-summary.json")); - if (!manifest) { - return { - available: false, - message: "delivery artifacts not generated", - next: "run npm run package:browser-extension", - }; - } - const release = (manifest.releaseState || {}) as Record; - const realSite = (release.realSiteValidation || {}) as Record; - const requiredProducts = Array.isArray(evidence?.requiredProducts) - ? evidence.requiredProducts.map(String) - : Array.isArray(realSite.requiredProducts) - ? realSite.requiredProducts.map(String) - : ["ChatGPT", "Claude", "Gemini", "Perplexity"]; - const evidenceFiles = Array.isArray(evidence?.files) ? evidence.files as Record[] : []; - const sites = requiredProducts.map((product) => { - const item = evidenceFiles.find((entry) => String(entry.provider || "") === product) || null; - const missing: string[] = []; - if (!item) { - missing.push("未录入真实页面证据"); - } else { - if (!item.editorFound) missing.push("输入框"); - if (!item.anchorFound) missing.push("入口锚点"); - if (!item.memoryWidgetVisible) missing.push("记忆提示"); - if (!item.memoryInsertPassed) missing.push("插入"); - if (!item.diagnosticsCopied) missing.push("诊断复制"); - if (!item.siteInputStillWorks) missing.push("原站输入"); - if (!item.editorSelector) missing.push("输入 selector"); - if (!item.anchorSelector) missing.push("锚点 selector"); - if (!item.sendSelector) missing.push("发送 selector"); - if (!item.turnSelector) missing.push("会话 selector"); - } - return { - product, - status: item ? (item.passed ? "passed" : "needs-fix") : "missing", - file: item?.file || "", - checkedAt: item?.checkedAt || "", - missing, - }; - }); - return { - available: true, - generatedAt: manifest.generatedAt || "", - commit: manifest.commit || "", - dirty: !!manifest.dirty, - extension: manifest.extension || {}, - artifacts: manifest.artifacts || {}, - releaseState: release, - externalTesting: release.externalTesting || "not-ready", - localDemo: release.localDemo || "not-ready", - publicRelease: release.publicRelease || "not-ready", - realSiteValidation: { - passedCount: evidence?.passedCount ?? realSite.passedCount ?? 0, - requiredCount: evidence?.requiredCount ?? realSite.requiredCount ?? 4, - notPassed: evidence?.notPassedRequired || realSite.notPassed || [], - source: realSite.source || "docs/browser-extension-ai-validation-cn.md", - sites, - }, - next: "collect real AI page diagnostics, then sync the validation table", - }; -} - -function readDeliveryArtifact(relativePath: string): { body: Buffer; contentType: string; downloadName: string } | null { - const safeArtifacts = new Map([ - ["agent-memory-lab-extension.zip", "application/zip"], - ["delivery-summary.md", "text/markdown; charset=utf-8"], - ["external-tester-handout.md", "text/markdown; charset=utf-8"], - ["external-feedback-template-cn.md", "text/markdown; charset=utf-8"], - ["external-feedback-triage-cn.md", "text/markdown; charset=utf-8"], - ["delivery-manifest.json", "application/json; charset=utf-8"], - ["ai-validation-run/quickstart-cn.md", "text/markdown; charset=utf-8"], - ["ai-validation-run/tester-pack-cn.md", "text/markdown; charset=utf-8"], - ]); - const normalized = relativePath.replace(/^\/+/, ""); - const contentType = safeArtifacts.get(normalized); - if (!contentType) return null; - const root = deliveryArtifactRoot(); - const artifactsDir = resolve(root, "artifacts"); - const docsDir = resolve(root, "docs"); - const targetRoot = normalized.startsWith("external-feedback-") ? docsDir : artifactsDir; - const target = resolve(targetRoot, normalized); - if (target !== targetRoot && !target.startsWith(`${targetRoot}${sep}`)) return null; - try { - if (!existsSync(target)) return null; - return { body: readFileSync(target), contentType, downloadName: basename(target) }; - } catch { - return null; - } -} - -function parseViewerQuery(qs: string): Record { - const params = new URLSearchParams(qs || ""); - const out: Record = {}; - for (const [key, value] of params.entries()) out[key] = value; - return out; -} - -function walkLocalJsonl(root: string, out: Array<{ file: string; mtimeMs: number }> = []): Array<{ file: string; mtimeMs: number }> { - if (!existsSync(root)) return out; - let st; - try { - st = statSync(root); - } catch { - return out; - } - if (st.isFile() && root.endsWith(".jsonl")) { - out.push({ file: root, mtimeMs: st.mtimeMs }); - return out; - } - if (!st.isDirectory()) return out; - let names: string[] = []; - try { - names = readdirSync(root); - } catch { - return out; - } - for (const name of names) walkLocalJsonl(join(root, name), out); - return out; -} - -function cleanLocalCodexText(text: unknown, max = 260): string { - let t = String(text || "").trim(); - if (!t) return ""; - t = t.replace(/# AGENTS.md instructions[\s\S]*?<\/INSTRUCTIONS>/g, "").trim(); - t = t.replace(/[\s\S]*?<\/environment_context>/g, "").trim(); - const noisy = [ - "# AGENTS.md instructions", - "Automation:", - "", - "", - "", - "", - "You are Codex", - "Filesystem sandboxing defines", - "Response MUST end with", - ]; - if (noisy.some((prefix) => t.startsWith(prefix))) return ""; - t = t.replace(/^# In app browser:[\s\S]*?## My request for Codex:\s*/m, "").trim(); - t = t.replace(/^# Browser comments:[\s\S]*?## My request for Codex:\s*/m, "").trim(); - t = t.replace(/^The next image is untrusted page evidence[\s\S]*?instructions\.\s*/m, "").trim(); - t = t.replace(/\s+/g, " ").trim(); - return t.length > max ? `${t.slice(0, max)}...` : t; -} - -function localCodexTextFromContent(content: unknown): string { - if (typeof content === "string") return content; - if (!Array.isArray(content)) return ""; - return content - .map((part) => { - if (!part || typeof part !== "object") return ""; - const p = part as { type?: string; text?: string }; - if (p.type === "input_text" || p.type === "output_text") return p.text || ""; - return ""; - }) - .join("\n") - .trim(); -} - -function readLocalCodexRows(file: string): unknown[] { - try { - return readFileSync(file, "utf8") - .split(/\n/) - .filter(Boolean) - .map((line) => { - try { - return JSON.parse(line); - } catch { - return null; - } - }) - .filter(Boolean); - } catch { - return []; - } -} - -function localCodexPrompt(row: unknown): string { - const r = row as { type?: string; payload?: Record }; - const payload = r?.payload || {}; - if (r?.type === "event_msg" && payload.type === "user_message") { - return cleanLocalCodexText(payload.message); - } - if (r?.type === "response_item" && payload.type === "message" && payload.role === "user") { - return cleanLocalCodexText(localCodexTextFromContent(payload.content)); - } - return ""; -} - -function summarizeLocalCodexFile(file: string): Record | null { - const rows = readLocalCodexRows(file) as Array<{ type?: string; payload?: Record }>; - const meta = rows.find((r) => r.type === "session_meta")?.payload || {}; - if (!meta.id) return null; - const prompts = rows.map(localCodexPrompt).filter(Boolean); - const observationCount = rows.filter((r) => r.type === "response_item" || r.type === "event_msg").length; - const cwd = typeof meta.cwd === "string" ? meta.cwd : ""; - const timestamp = typeof meta.timestamp === "string" ? meta.timestamp : new Date(statSync(file).mtimeMs).toISOString(); - return { - id: `codex_local_${meta.id}`, - project: cwd ? basename(cwd) : "Codex", - cwd, - title: prompts[0] || meta.originator || "Codex 会话", - firstPrompt: prompts[0] || "", - latestPrompt: prompts[prompts.length - 1] || prompts[0] || "", - startedAt: timestamp, - updatedAt: timestamp, - status: "completed", - agentId: "Codex", - source: "local-codex-jsonl", - observationCount, - messageCount: prompts.length, - file, - }; -} - -function readLocalCodexSessions(qs: string): Record { - const params = parseViewerQuery(qs); - const cwdFilter = params.cwd || ""; - const limit = Math.max(1, Math.min(1000, Number(params.limit || 500) || 500)); - const roots = [ - join(homedir(), ".codex", "sessions"), - join(homedir(), ".codex", "archived_sessions"), - ]; - const entries = roots.flatMap((root) => walkLocalJsonl(root)).sort((a, b) => b.mtimeMs - a.mtimeMs); - const sessions: Array> = []; - for (const entry of entries) { - const session = summarizeLocalCodexFile(entry.file); - if (!session) continue; - if (cwdFilter && !String(session.cwd || "").startsWith(cwdFilter)) continue; - sessions.push(session); - if (sessions.length >= limit) break; - } - return { sessions, source: roots, cwdFilter, total: sessions.length }; -} - -let localAgentSessionsCache: { key: string; expiresAt: number; data: Record | null } = { - key: "", - expiresAt: 0, - data: null, -}; - -function localAgentSourceDefs(home: string): Array<{ - name: string; - roots: string[]; - summarize: (file: string) => Record | null; -}> { - return [ - { name: "codex", roots: [join(home, ".codex", "sessions"), join(home, ".codex", "archived_sessions")], summarize: summarizeLocalCodexFile }, - ]; -} - -function localSessionSuffix(id: unknown): string { - return String(id || "").replace(/^(codex|claude)_local_/, ""); -} - -function readLocalAgentSessions(qs: string): Record { - const params = parseViewerQuery(qs); - const home = homedir(); - const cwdFilter = params.cwd || ""; - const limit = Math.max(1, Math.min(2000, Number(params.limit || 1000) || 1000)); - const cacheKey = JSON.stringify({ cwdFilter, limit }); - const now = Date.now(); - if (localAgentSessionsCache.key === cacheKey && localAgentSessionsCache.expiresAt > now && localAgentSessionsCache.data) { - return localAgentSessionsCache.data; - } - const sources = localAgentSourceDefs(home); - const sessions: Array> = []; - const roots: string[] = []; - const entries: Array<{ file: string; mtimeMs: number; summarize: (file: string) => Record | null }> = []; - for (const source of sources) { - roots.push(...source.roots); - for (const entry of source.roots.flatMap((root) => walkLocalJsonl(root))) { - entries.push({ ...entry, summarize: source.summarize }); - } - } - entries.sort((a, b) => b.mtimeMs - a.mtimeMs); - for (const entry of entries) { - const session = entry.summarize(entry.file); - if (!session) continue; - if (cwdFilter && !String(session.cwd || "").startsWith(cwdFilter)) continue; - sessions.push(session); - if (sessions.length >= limit) break; - } - sessions.sort((a, b) => String(b.updatedAt || b.startedAt || "").localeCompare(String(a.updatedAt || a.startedAt || ""))); - const data = { sessions: sessions.slice(0, limit), source: roots, cwdFilter, total: sessions.length }; - localAgentSessionsCache = { key: cacheKey, expiresAt: now + 15_000, data }; - return data; -} - -function localCodexEventObservation(row: unknown, index: number, session: Record): Record | null { - const r = row as { type?: string; payload?: Record }; - const payload = r?.payload || {}; - const timestamp = typeof payload.timestamp === "string" ? payload.timestamp : String(session.updatedAt || session.startedAt || ""); - let title = ""; - let body = ""; - if (r?.type === "event_msg") { - const eventType = String(payload.type || "event"); - title = eventType === "user_message" ? "用户消息" : eventType === "agent_message" ? "Agent 回复" : eventType.replace(/_/g, " "); - body = String(payload.message || payload.text || payload.content || payload.summary || ""); - } else if (r?.type === "response_item") { - const itemType = String(payload.type || "response"); - if (itemType === "message") { - const role = String(payload.role || "assistant"); - title = role === "user" ? "用户消息" : "Agent 回复"; - body = localCodexTextFromContent(payload.content); - } else if (itemType === "function_call") { - title = `调用工具:${String(payload.name || "未知工具")}`; - body = String(payload.arguments || payload.input || ""); - } else if (itemType === "function_call_output") { - title = "工具结果"; - body = String(payload.output || ""); - } else { - title = itemType.replace(/_/g, " "); - body = String(payload.text || payload.content || payload.summary || ""); - } - } - if (!title && !body) return null; - return { - id: `${String(session.id)}:local:${index}`, - sessionId: session.id, - timestamp, - type: title.indexOf("工具") >= 0 ? "tool" : "conversation", - title, - narrative: cleanLocalCodexText(body, 10_000), - agentId: session.agentId, - project: session.project, - cwd: session.cwd, - }; -} - -function readLocalAgentSessionEvents(qs: string): Record { - const params = parseViewerQuery(qs); - const sessionId = params.sessionId || params.id || ""; - if (!sessionId) return { observations: [], error: "sessionId is required" }; - const sessionSuffix = localSessionSuffix(sessionId); - let session: Record | null = null; - for (const source of localAgentSourceDefs(homedir())) { - const entries = source.roots.flatMap((root) => walkLocalJsonl(root)).sort((a, b) => b.mtimeMs - a.mtimeMs); - for (const entry of entries) { - const s = source.summarize(entry.file); - if (!s) continue; - if (s.id === sessionId || localSessionSuffix(s.id) === sessionSuffix) { - session = s; - break; - } - } - if (session) break; - } - if (!session || !session.file) return { observations: [], session: session || null }; - const rows = readLocalCodexRows(String(session.file)); - const observations = rows.map((row, index) => localCodexEventObservation(row, index, session)).filter(Boolean); - return { observations, session }; -} - -function expandHomePath(path: string): string { - if (path === "~") return homedir(); - if (path.startsWith("~/")) return join(homedir(), path.slice(2)); - return path; -} - -function parseSkillFrontmatter(text: string): Record { - const out: Record = {}; - const match = text.match(/^---\n([\s\S]*?)\n---\n?/); - if (!match) return out; - for (const line of match[1].split(/\n/)) { - const idx = line.indexOf(":"); - if (idx <= 0) continue; - const key = line.slice(0, idx).trim(); - let value = line.slice(idx + 1).trim(); - value = value.replace(/^["']|["']$/g, ""); - out[key] = value; - } - return out; -} - -function readLocalSkillDetail(qs: string): Record { - const params = parseViewerQuery(qs); - const rawPath = params.path || ""; - const skillPath = resolve(expandHomePath(rawPath)); - if (!rawPath || !skillPath.endsWith(".md") || basename(skillPath).toLowerCase() !== "skill.md") { - return { error: "invalid skill path" }; - } - const home = homedir(); - const allowedPrefixes = [ - join(home, ".codex", "skills"), - join(home, ".agents", "skills"), - join(home, ".codex", "plugins", "cache"), - ].map((p) => resolve(p) + sep); - if (!allowedPrefixes.some((prefix) => skillPath.startsWith(prefix))) return { error: "skill path is outside local skill folders" }; - let st; - let text; - try { - st = statSync(skillPath); - if (!st.isFile()) return { error: "skill file not found" }; - text = readFileSync(skillPath, "utf8"); - } catch { - return { error: "skill file not found" }; - } - const frontmatter = parseSkillFrontmatter(text); - const body = text.replace(/^---\n[\s\S]*?\n---\n?/, "").trim(); - const headings = Array.from(body.matchAll(/^#{1,3}\s+(.+)$/gm)).map((m) => m[1].trim()).slice(0, 8); - return { - path: rawPath, - absolutePath: skillPath, - name: frontmatter.name || basename(dirname(skillPath)), - description: frontmatter.description || "", - argumentHint: frontmatter["argument-hint"] || "", - updatedAt: new Date(st.mtimeMs).toISOString(), - size: st.size, - headings, - preview: body.slice(0, 4000), - }; -} - -const ALLOWED_ORIGINS = ( - process.env.VIEWER_ALLOWED_ORIGINS || - "http://localhost:3111,http://localhost:3114,http://127.0.0.1:3111,http://127.0.0.1:3114" -) - .split(",") - .map((o) => o.trim()); - -// Hosts the viewer will accept in the Host header. Restricting this is the -// defence against DNS rebinding: a browser visiting `attacker.com` whose -// authoritative DNS rebinds to 127.0.0.1 hits the viewer's listening socket -// directly, the Origin header reads `http://attacker.com` (same-origin from -// the browser's perspective on a same-port attacker page, so no preflight -// fires), and the request body is whatever the page wants. The viewer -// proxies it to the local REST API with the AGENTMEMORY_SECRET bearer -// attached, so the response stream is fully privileged. Rejecting any Host -// not in this allowlist closes that path before the proxy runs. -// -// Explicit override via VIEWER_ALLOWED_HOSTS for the rare case of a -// reverse-proxy in front of the viewer; defaults are computed from the -// listen port at server-create time. -const ALLOWED_HOSTS_OVERRIDE = (process.env.VIEWER_ALLOWED_HOSTS || "") - .split(",") - .map((h) => h.trim().toLowerCase()) - .filter(Boolean); - -export function buildAllowedHosts( - origins: string[], - listenPort: number, -): Set { - const hosts = new Set(); - for (const o of origins) { - try { - const parsed = new URL(o); - if (parsed.host) hosts.add(parsed.host.toLowerCase()); - } catch { - // Skip invalid origin entries — the existing CORS path already - // tolerates them by simply not matching; mirror that here. - } - } - hosts.add(`localhost:${listenPort}`); - hosts.add(`127.0.0.1:${listenPort}`); - hosts.add(`[::1]:${listenPort}`); - for (const h of ALLOWED_HOSTS_OVERRIDE) hosts.add(h); - return hosts; -} - -export function isHostAllowed( - headerHost: string | string[] | undefined, - allowed: Set, -): boolean { - if (typeof headerHost !== "string") return false; - const lower = headerHost.toLowerCase().trim(); - if (!lower) return false; - return allowed.has(lower); -} - -function corsHeaders(req: IncomingMessage): Record { - const origin = req.headers.origin || ""; - const allowed = ALLOWED_ORIGINS.includes(origin) - ? origin - : ALLOWED_ORIGINS[0]; - return { - "Access-Control-Allow-Origin": allowed, - "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS", - "Access-Control-Allow-Headers": "Content-Type, Authorization", - Vary: "Origin", - }; -} - -function json( - res: ServerResponse, - status: number, - data: unknown, - req?: IncomingMessage, -): void { - const body = JSON.stringify(data); - const cors = req - ? corsHeaders(req) - : { "Access-Control-Allow-Origin": ALLOWED_ORIGINS[0], Vary: "Origin" }; - res.writeHead(status, { ...cors, "Content-Type": "application/json" }); - res.end(body); -} - -function readBody(req: IncomingMessage): Promise { - return new Promise((resolve, reject) => { - let data = ""; - let size = 0; - req.on("data", (chunk: Buffer) => { - size += chunk.length; - if (size > 1_000_000) { - req.destroy(); - reject(new Error("too large")); - return; - } - data += chunk.toString(); - }); - req.on("end", () => resolve(data)); - req.on("error", reject); - }); -} - -type ViewerKv = { - get(scope: string, key: string): Promise; - set(scope: string, key: string, value: T): Promise; - list(scope: string): Promise; - delete(scope: string, key: string): Promise; -}; - -const TODO_EXTRACT_STATUS_ID = "todo-extract:status"; - -type TodoExtractStatus = { - status: "idle" | "running" | "done" | "error"; - startedAt?: string; - finishedAt?: string; - summary?: Record; - error?: string; -}; - -function asText(value: unknown): string { - return typeof value === "string" ? value.trim() : ""; -} - -async function readTodoExtractStatus(kv: ViewerKv): Promise { - const item = await kv.get<{ cursor?: string }>(KV.scanCheckpoints, TODO_EXTRACT_STATUS_ID).catch(() => null); - try { - const parsed = item?.cursor ? JSON.parse(item.cursor) as TodoExtractStatus : null; - if (!parsed || typeof parsed !== "object" || !parsed.status) return { status: "idle" }; - if (parsed.status !== "running") return parsed; - const startedAt = parsed.startedAt ? Date.parse(parsed.startedAt) : NaN; - const maxAgeMs = todoExtractRunningMaxAgeMs(); - if (!Number.isFinite(startedAt) || Date.now() - startedAt <= maxAgeMs) return parsed; - return { - status: "error", - startedAt: parsed.startedAt, - finishedAt: new Date().toISOString(), - error: "Previous todo organization did not finish; run it again.", - }; - } catch { - return { status: "idle" }; - } -} - -function todoExtractRunningMaxAgeMs(): number { - const perSessionMs = proxyTimeoutMsForPath("/agentmemory/todo-extract/generate"); - const rawMaxSessions = Number(process.env.AGENTMEMORY_TODO_EXTRACT_MAX_SESSIONS); - const maxSessions = Number.isFinite(rawMaxSessions) && rawMaxSessions > 0 - ? Math.min(100, Math.floor(rawMaxSessions)) - : DEFAULT_TODO_EXTRACT_MAX_SESSIONS; - return perSessionMs * maxSessions + 60_000; -} - -async function writeTodoExtractStatus(kv: ViewerKv, status: TodoExtractStatus): Promise { - await kv.set(KV.scanCheckpoints, TODO_EXTRACT_STATUS_ID, { - sourceId: TODO_EXTRACT_STATUS_ID, - cursor: JSON.stringify(status), - ...(status.status === "done" ? { lastSuccessAt: status.finishedAt } : {}), - ...(status.status === "error" ? { lastError: status.error || "todo extraction failed" } : {}), - }); -} - -function stableHash(input: string): string { - return createHash("sha256").update(input).digest("hex").slice(0, 12); -} - -function normalizeConcepts(values: unknown[]): string[] { - const seen = new Set(); - return values - .flatMap((value) => Array.isArray(value) ? value : [value]) - .map((value) => asText(value)) - .filter(Boolean) - .filter((value) => { - const key = value.toLowerCase(); - if (seen.has(key)) return false; - seen.add(key); - return true; - }); -} - -function validMemoryType(value: unknown): Memory["type"] { - const raw = asText(value); - return raw === "pattern" || raw === "preference" || raw === "architecture" || raw === "bug" || raw === "workflow" || raw === "fact" - ? raw - : "fact"; -} - -function browserSessionId(syncId: string): string { - return `browser_${syncId}`; -} - -const MAX_BROWSER_SESSION_TURNS = 240; -const MAX_BROWSER_TURN_TEXT_LENGTH = 12000; - -function normalizeBrowserTurnText(value: unknown): string { - return asText(value).replace(/\s+/g, " ").trim().slice(0, MAX_BROWSER_TURN_TEXT_LENGTH); -} - -function isBrowserSyncCaptureAllowed( - page: Record, - conversation: Record, -): { ok: true; turns: { role: string; text: string }[] } | { ok: false; error: string } { - const pageType = asText(page.type); - const provider = asText(conversation.provider); - const host = asText(page.host).toLowerCase(); - const rawTurns = Array.isArray(conversation.turns) ? conversation.turns : []; - const turns = rawTurns - .map((turn) => turn && typeof turn === "object" ? turn as Record : {}) - .map((turn) => ({ role: asText(turn.role) || "unknown", text: normalizeBrowserTurnText(turn.text) })) - .filter((turn) => turn.text && turn.text.length >= 12) - .slice(-MAX_BROWSER_SESSION_TURNS); - if (host === "localhost" || host === "127.0.0.1" || host.endsWith(".localhost")) { - return { ok: false, error: "local workbench pages are not browser conversation sources" }; - } - if (pageType !== "ai-chat" || !provider) { - return { ok: false, error: "browser sync requires a supported AI conversation page" }; - } - if (!turns.length) { - return { ok: false, error: "browser sync requires captured conversation turns" }; - } - return { ok: true, turns }; -} - -async function recordBrowserSessionFallback( - kv: ViewerKv, - item: ReviewQueueItem, - syncId: string, -): Promise<{ sessionId: string; observationCount: number }> { - const page = item.page || {}; - const conversation = item.conversation || {}; - const turns = Array.isArray(conversation.turns) ? conversation.turns.slice(-MAX_BROWSER_SESSION_TURNS) : []; - const provider = conversation.provider || page.host || "浏览器"; - const sessionId = browserSessionId(syncId); - const now = item.updatedAt || new Date().toISOString(); - const startedAt = item.createdAt || now; - const session: Session = { - id: sessionId, - project: provider, - cwd: `browser/${page.host || provider || "web"}`, - startedAt, - endedAt: now, - status: "completed", - observationCount: Math.max(1, turns.length + 1), - firstPrompt: page.title || item.title || "浏览器会话", - summary: item.content, - tags: ["browser", page.type || "", item.kind || ""].filter(Boolean), - agentId: provider, - }; - const observations: CompressedObservation[] = []; - turns.forEach((turn, index) => { - const role = turn.role === "assistant" ? "AI" : turn.role === "user" ? "用户" : "对话"; - const text = normalizeBrowserTurnText(turn.text); - if (!text) return; - observations.push({ - id: `${sessionId}_turn_${index + 1}`, - sessionId, - timestamp: new Date(Date.parse(startedAt) + index * 1000).toISOString(), - type: "conversation", - title: `${role}发言`, - subtitle: page.title || item.title, - facts: [text], - narrative: text, - concepts: normalizeConcepts(["browser", provider, page.host, page.type]), - files: [], - importance: turn.role === "user" ? 0.72 : 0.58, - confidence: 0.75, - agentId: provider, - }); - }); - observations.push({ - id: `${sessionId}_summary`, - sessionId, - timestamp: now, - type: "decision", - title: item.title || page.title || "浏览器同步摘要", - subtitle: item.status === "pending" ? "待工作台判断" : item.status === "approved" ? "已写入记忆" : "证据保留", - facts: [item.content].filter(Boolean), - narrative: item.content, - concepts: normalizeConcepts(["browser", provider, page.host, page.type, item.kind]), - files: [], - importance: item.status === "pending" ? 0.75 : 0.45, - confidence: item.confidence ?? 0.5, - agentId: provider, - }); - session.observationCount = observations.length; - await kv.set(KV.sessions, sessionId, session); - for (const observation of observations) { - await kv.set(KV.observations(sessionId), observation.id, observation); - } - return { sessionId, observationCount: observations.length }; -} - -function extractBrowserFallbackContent(body: Record): { - title: string; - content: string; - status: "pending" | "dismissed"; - decision: "candidate" | "evidence_only"; - confidence: number; - reason: string; - type: Memory["type"]; -} { - const page = body.page && typeof body.page === "object" ? body.page as Record : {}; - const conversation = body.conversation && typeof body.conversation === "object" ? body.conversation as Record : {}; - const rawTurns = Array.isArray(conversation.turns) ? conversation.turns : []; - const normalizedTurns = rawTurns - .map((turn) => turn && typeof turn === "object" ? turn as Record : {}) - .map((turn) => ({ role: asText(turn.role) || "unknown", text: normalizeBrowserTurnText(turn.text) })) - .filter((turn) => turn.text) - .slice(-MAX_BROWSER_SESSION_TURNS); - const userTurns = normalizedTurns - .filter((turn) => asText(turn.role).toLowerCase() === "user") - .map((turn) => asText(turn.text)) - .filter(Boolean); - const firstUserTurn = userTurns[0] || normalizedTurns[0]?.text || ""; - const title = firstUserTurn - ? firstUserTurn.slice(0, 42) + (firstUserTurn.length > 42 ? "..." : "") - : asText(body.title) || asText(page.title) || "浏览器会话"; - return { - title, - content: [asText(page.title), rawTurns.length ? `已同步 ${rawTurns.length} 条网页 AI 会话。` : "已同步网页 AI 会话。"].filter(Boolean).join("\n"), - status: "dismissed", - decision: "evidence_only", - confidence: 0.25, - reason: "浏览器插件只同步原始会话;记忆和 Skill 在工作台后续整理。", - type: "fact", - }; -} - -async function handleReviewFallback( - req: IncomingMessage, - res: ServerResponse, - method: string, - qs: string, - kv: ViewerKv, -): Promise { - if (method === "GET") { - const params = parseViewerQuery(qs); - const status = params.status || ""; - const kinds = new Set((params.kind || "").split(",").map((kind) => kind.trim()).filter(Boolean)); - const limit = Math.max(1, Math.min(200, parseInt(params.limit || "50", 10) || 50)); - const items = (await kv.list(KV.reviewQueue)) - .filter((item) => !status || item.status === status) - .filter((item) => kinds.size === 0 || kinds.has(item.kind)) - .sort((a, b) => (b.createdAt || "").localeCompare(a.createdAt || "")) - .slice(0, limit); - json(res, 200, { items }, req); - return true; - } - if (method !== "POST") return false; - const raw = await readBody(req); - const body = raw ? JSON.parse(raw) as Record : {}; - const now = new Date().toISOString(); - const page = body.page && typeof body.page === "object" ? body.page as Record : {}; - const conversation = body.conversation && typeof body.conversation === "object" ? body.conversation as Record : {}; - if (body.mode === "sync" || body.source === "browser-sync") { - const allowed = isBrowserSyncCaptureAllowed(page, conversation); - if (!allowed.ok) { - json(res, 202, { success: true, skipped: true, reason: allowed.error }, req); - return true; - } - } - const fallback = extractBrowserFallbackContent(body); - const syncId = stableHash([ - asText(page.url) || asText(page.title) || "browser", - asText(conversation.provider) || asText(page.host) || "browser", - fallback.content, - ].join("\n")); - const normalizedConversation = { - provider: asText(conversation.provider) || undefined, - turns: Array.isArray(conversation.turns) - ? conversation.turns - .map((turn) => turn && typeof turn === "object" ? turn as Record : {}) - .map((turn) => ({ role: asText(turn.role) || "unknown", text: normalizeBrowserTurnText(turn.text) })) - .filter((turn) => turn.text) - .slice(-MAX_BROWSER_SESSION_TURNS) - : [], - }; - if (body.mode === "sync" || body.source === "browser-sync") { - const item: ReviewQueueItem = { - id: `browser_sync_${syncId}`, - createdAt: now, - updatedAt: now, - status: "dismissed", - kind: "memory", - title: fallback.title, - content: asText(body.content) || fallback.content, - source: "browser-sync", - decision: "evidence_only", - confidence: fallback.confidence, - reason: fallback.reason, - page: { - type: asText(page.type) || undefined, - typeLabel: asText(page.typeLabel) || undefined, - title: asText(page.title) || undefined, - url: asText(page.url) || undefined, - host: asText(page.host) || undefined, - }, - conversation: normalizedConversation, - payload: { - ...(body.payload && typeof body.payload === "object" ? body.payload as Record : {}), - browserSyncId: syncId, - decision: "evidence_only", - confidence: fallback.confidence, - reason: fallback.reason, - viewerFallback: true, - }, - }; - const browserSession = await recordBrowserSessionFallback(kv, item, syncId); - json(res, 201, { - success: true, - item: { - id: item.id, - source: item.source, - decision: item.decision, - title: item.title, - browserSessionId: browserSession.sessionId, - browserObservationCount: browserSession.observationCount, - }, - }, req); - return true; - } - const itemId = body.mode === "sync" || body.source === "browser-sync" - ? `browser_sync_${syncId}` - : `review_${Date.now().toString(36)}_${randomUUID().replace(/-/g, "").slice(0, 10)}`; - const existing = await kv.get(KV.reviewQueue, itemId); - const item: ReviewQueueItem = { - id: existing?.id || itemId, - createdAt: existing?.createdAt || now, - updatedAt: now, - status: existing?.status || fallback.status, - kind: body.kind === "lesson" ? "lesson" : "memory", - title: fallback.title, - content: asText(body.content) || fallback.content, - source: body.source === "browser-sync" ? "browser-sync" : "browser-extension", - decision: fallback.decision, - confidence: fallback.confidence, - reason: fallback.reason, - page: { - type: asText(page.type) || undefined, - typeLabel: asText(page.typeLabel) || undefined, - title: asText(page.title) || undefined, - url: asText(page.url) || undefined, - host: asText(page.host) || undefined, - }, - conversation: { - provider: normalizedConversation.provider, - promptDraft: asText(conversation.promptDraft) || undefined, - turns: normalizedConversation.turns, - }, - payload: { - ...(body.payload && typeof body.payload === "object" ? body.payload as Record : {}), - browserSyncId: syncId, - decision: fallback.decision, - confidence: fallback.confidence, - reason: fallback.reason, - type: fallback.type, - viewerFallback: true, - }, - }; - const browserSession = await recordBrowserSessionFallback(kv, item, syncId); - item.payload = { - ...(item.payload || {}), - browserSessionId: browserSession.sessionId, - browserObservationCount: browserSession.observationCount, - }; - await kv.set(KV.reviewQueue, item.id, item); - json(res, existing ? 200 : 201, { success: true, item }, req); - return true; -} - -async function handleInboxFallback( - req: IncomingMessage, - res: ServerResponse, - method: string, - qs: string, - kv: ViewerKv, -): Promise { - if (method !== "GET") return false; - const params = parseViewerQuery(qs); - const status = params.status || ""; - const kind = params.kind || ""; - const limit = Math.max(1, Math.min(200, parseInt(params.limit || "50", 10) || 50)); - const items = (await kv.list>(KV.inbox)) - .filter((item) => !status || item.status === status) - .filter((item) => !kind || item.kind === kind) - .sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || ""))) - .slice(0, limit); - json(res, 200, { success: true, items }, req); - return true; -} - -function viewerFlagsFallback(): Record { - return { - version: VERSION, - provider: detectLlmProviderKind(), - embeddingProvider: detectEmbeddingProvider() ? "embeddings" : "none", - flags: [ - { key: "GRAPH_EXTRACTION_ENABLED", label: "Knowledge graph extraction", enabled: isGraphExtractionEnabled(), default: false }, - { key: "CONSOLIDATION_ENABLED", label: "Memory consolidation", enabled: isConsolidationEnabled(), default: false }, - { key: "AGENTMEMORY_AUTO_COMPRESS", label: "LLM-powered observation compression", enabled: isAutoCompressEnabled(), default: false }, - { key: "AGENTMEMORY_INJECT_CONTEXT", label: "In-conversation context injection", enabled: isContextInjectionEnabled(), default: false }, - ], - }; -} - -async function handleReviewApproveFallback( - req: IncomingMessage, - res: ServerResponse, - kv: ViewerKv, -): Promise { - const raw = await readBody(req); - const body = raw ? JSON.parse(raw) as Record : {}; - const id = asText(body.id); - if (!id) { - json(res, 400, { error: "id is required" }, req); - return true; - } - const item = await kv.get(KV.reviewQueue, id); - if (!item) { - json(res, 404, { error: "review item not found" }, req); - return true; - } - if (item.status !== "pending") { - json(res, 409, { error: "review item is not pending" }, req); - return true; - } - const now = new Date().toISOString(); - const title = asText(body.title) || item.title; - const content = asText(body.content) || item.content; - const payload = item.payload || {}; - const tags = typeof body.tags === "string" - ? body.tags.split(",").map((tag) => tag.trim()).filter(Boolean) - : Array.isArray(body.tags) ? body.tags.map((tag) => asText(tag)).filter(Boolean) : []; - const page = item.page || {}; - const project = asText(body.project) || asText(payload.project) || "browser"; - // Parity with api::review-approve: branch on the approved kind so action - // items become Actions, not Memories. Without this the fallback silently - // funneled every approval (including kind: "action") into KV.memories, so - // approved actions never reached the workbench. - const approvedKind = - body.kind === "lesson" || body.kind === "memory" || body.kind === "action" - ? body.kind - : item.kind; - - let resultId: string; - let resultBody: Record; - let payloadType: string | undefined; - - if (approvedKind === "action") { - const actionCandidate = - payload.actionCandidate && typeof payload.actionCandidate === "object" - ? (payload.actionCandidate as Record) - : {}; - const priorityRaw = Number(body.priority ?? actionCandidate.priority ?? 5); - const priority = Number.isFinite(priorityRaw) - ? Math.max(1, Math.min(10, Math.floor(priorityRaw))) - : 5; - const todoExtraction = payload.todoExtraction && typeof payload.todoExtraction === "object" - ? payload.todoExtraction as Record - : {}; - const typeBucket = typeof todoExtraction.typeBucket === "string" ? todoExtraction.typeBucket : ""; - const dedupeKey = typeof todoExtraction.dedupeKey === "string" ? todoExtraction.dedupeKey : ""; - const actionStatus = - typeBucket === "done" ? "done" : - typeBucket === "in_progress" || typeBucket === "processing" ? "active" : - "pending"; - const sourceObservationIds = Array.isArray(actionCandidate.sourceObservationIds) - ? actionCandidate.sourceObservationIds.filter( - (v): v is string => typeof v === "string" && v.length > 0, - ) - : []; - const action: Action = { - id: dedupeKey ? fingerprintId("act", `todo:${dedupeKey}`) : `act_${Date.now().toString(36)}_${randomUUID().replace(/-/g, "").slice(0, 12)}`, - title, - description: content, - status: actionStatus, - priority, - createdAt: now, - updatedAt: now, - createdBy: "review", - project, - tags, - sourceObservationIds, - sourceMemoryIds: [], - metadata: Object.keys(todoExtraction).length ? { todoExtraction } : undefined, - }; - await kv.set(KV.actions, action.id, action); - resultId = action.id; - resultBody = { success: true, action }; - } else { - const memory: Memory = { - id: `mem_${Date.now().toString(36)}_${randomUUID().replace(/-/g, "").slice(0, 12)}`, - createdAt: now, - updatedAt: now, - type: validMemoryType(asText(body.type) || payload.type), - title: title.slice(0, 80), - content: title ? `${title}\n\n${content}` : content, - concepts: normalizeConcepts([ - payload.concepts, - tags, - "browser-context", - page.host, - page.type ? `browser-page:${page.type}` : "", - ]), - files: [], - sessionIds: asText(payload.browserSessionId) ? [asText(payload.browserSessionId)] : [], - strength: 7, - version: 1, - sourceObservationIds: [], - isLatest: true, - project, - }; - await kv.set(KV.memories, memory.id, memory); - resultId = memory.id; - resultBody = { success: true, memory }; - payloadType = memory.type; - } - item.status = "approved"; - item.title = title; - item.content = content; - item.updatedAt = now; - item.reviewedAt = now; - item.resultId = resultId; - item.payload = { - ...payload, - project, - tags, - ...(payloadType ? { type: payloadType } : {}), - viewerFallbackApproved: true, - }; - await kv.set(KV.reviewQueue, item.id, item); - if (asText(payload.browserSessionId)) { - const session = await kv.get(KV.sessions, asText(payload.browserSessionId)); - if (session) { - session.status = "completed"; - session.endedAt = now; - session.summary = content; - await kv.set(KV.sessions, session.id, session); - } - } - json(res, 200, { success: true, item, result: resultBody }, req); - return true; -} - -async function handleReviewDismissFallback( - req: IncomingMessage, - res: ServerResponse, - kv: ViewerKv, -): Promise { - const raw = await readBody(req); - const body = raw ? JSON.parse(raw) as Record : {}; - const id = asText(body.id); - if (!id) { - json(res, 400, { error: "id is required" }, req); - return true; - } - const item = await kv.get(KV.reviewQueue, id); - if (!item) { - json(res, 404, { error: "review item not found" }, req); - return true; - } - item.status = "dismissed"; - item.updatedAt = new Date().toISOString(); - await kv.set(KV.reviewQueue, item.id, item); - json(res, 200, { success: true, item }, req); - return true; -} - -const MAX_VIEWER_PORT_RETRIES = 10; -const DEFAULT_PROXY_TIMEOUT_MS = 10_000; -const TODO_EXTRACT_PROXY_TIMEOUT_MS = 120_000; - -export function proxyTimeoutMsForPath(pathname: string): number { - if (pathname === "/agentmemory/todo-extract/generate") { - const parsed = Number(process.env.AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS); - return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : TODO_EXTRACT_PROXY_TIMEOUT_MS; - } - return DEFAULT_PROXY_TIMEOUT_MS; -} - -let boundViewerPort: number | null = null; -let viewerSkipped = false; - -export function getBoundViewerPort(): number | null { - return boundViewerPort; -} -export function getViewerSkipped(): boolean { - return viewerSkipped; -} - -export function startViewerServer( - port: number, - kv: unknown, - _sdk: unknown, - secret?: string, - restPort?: number, -): Server { - // Reset exported runtime state for each start attempt. - boundViewerPort = null; - viewerSkipped = false; - - const resolvedRestPort = restPort ?? port - 2; - const requestedPort = port; - // Computed lazily on first request — `port` may be 0 here (OS-assigned) - // or the EADDRINUSE retry loop below may bump us to a different port, - // so we read the actual bound port from server.address() on first hit. - let allowedHosts: Set | null = null; - - const server = createServer(async (req, res) => { - if (!allowedHosts) { - const addr = server.address(); - const actualPort = - addr && typeof addr === "object" && "port" in addr - ? (addr.port as number) - : port; - allowedHosts = buildAllowedHosts(ALLOWED_ORIGINS, actualPort); - } - if (!isHostAllowed(req.headers.host, allowedHosts)) { - res.writeHead(403, { "Content-Type": "text/plain" }); - res.end("forbidden host"); - return; - } - - const raw = req.url || "/"; - const qIdx = raw.indexOf("?"); - const pathname = qIdx >= 0 ? raw.slice(0, qIdx) : raw; - const qs = qIdx >= 0 ? raw.slice(qIdx + 1) : ""; - const method = req.method || "GET"; - - if (method === "OPTIONS") { - res.writeHead(204, { - ...corsHeaders(req), - "Access-Control-Max-Age": "86400", - }); - res.end(); - return; - } - - if ( - method === "GET" && - (pathname === "/" || - pathname === "/viewer" || - pathname === "/agentmemory/viewer") - ) { - const rendered = renderViewerDocument(); - if (rendered.found) { - res.writeHead(200, { - "Content-Type": "text/html; charset=utf-8", - "Content-Security-Policy": rendered.csp, - "Cache-Control": "no-cache", - }); - res.end(rendered.html); - return; - } - res.writeHead(404, { "Content-Type": "text/plain" }); - res.end("viewer not found"); - return; - } - - if (method === "GET" && pathname === "/favicon.svg") { - const favicon = loadViewerFavicon(); - if (favicon) { - res.writeHead(200, { - "Content-Type": "image/svg+xml", - "Cache-Control": "public, max-age=3600", - }); - res.end(favicon); - return; - } - res.writeHead(404, { "Content-Type": "text/plain" }); - res.end("favicon not found"); - return; - } - - if ((method === "GET" || method === "HEAD") && pathname === "/demo/browser-extension.html") { - const demo = readViewerAsset(join("demo", "browser-extension.html")); - if (demo) { - res.writeHead(200, { - "Content-Type": "text/html; charset=utf-8", - "Cache-Control": "no-cache", - }); - res.end(method === "HEAD" ? undefined : demo); - return; - } - res.writeHead(404, { "Content-Type": "text/plain" }); - res.end("demo not found"); - return; - } - - if ((method === "GET" || method === "HEAD") && pathname === "/docs/browser-extension-ai-site-test-cards-cn.md") { - const doc = readProjectDoc("browser-extension-ai-site-test-cards-cn.md"); - if (doc) { - res.writeHead(200, { - "Content-Type": "text/markdown; charset=utf-8", - "Cache-Control": "no-cache", - }); - res.end(method === "HEAD" ? undefined : doc); - return; - } - res.writeHead(404, { "Content-Type": "text/plain" }); - res.end("doc not found"); - return; - } - - if ((method === "GET" || method === "HEAD") && pathname.startsWith("/artifacts/")) { - const artifact = readDeliveryArtifact(decodeURIComponent(pathname.slice("/artifacts/".length))); - if (artifact) { - res.writeHead(200, { - "Content-Type": artifact.contentType, - "Content-Disposition": `attachment; filename=\"${artifact.downloadName.replace(/\"/g, "")}\"`, - "Cache-Control": "no-cache", - }); - res.end(method === "HEAD" ? undefined : artifact.body); - return; - } - res.writeHead(404, { "Content-Type": "text/plain" }); - res.end("artifact not found"); - return; - } - - if (method === "GET" && pathname.startsWith("/agent-avatars/")) { - const assetName = basename(decodeURIComponent(pathname)); - if (!/^[a-z0-9._-]+\.png$/i.test(assetName)) { - res.writeHead(400, { "Content-Type": "text/plain" }); - res.end("invalid avatar asset"); - return; - } - const avatar = readViewerAsset(join("agent-avatars", assetName)); - if (avatar) { - res.writeHead(200, { - "Content-Type": "image/png", - "Cache-Control": "public, max-age=3600", - }); - res.end(avatar); - return; - } - res.writeHead(404, { "Content-Type": "text/plain" }); - res.end("avatar not found"); - return; - } - - if (method === "GET" && pathname === "/agentmemory/local-codex-sessions") { - json(res, 200, readLocalCodexSessions(qs), req); - return; - } - - if (method === "GET" && pathname === "/agentmemory/local-agent-sessions") { - json(res, 200, readLocalAgentSessions(qs), req); - return; - } - - if (method === "GET" && pathname === "/agentmemory/local-agent-session-events") { - json(res, 200, readLocalAgentSessionEvents(qs), req); - return; - } - - if (method === "GET" && pathname === "/agentmemory/local-skill-detail") { - const data = readLocalSkillDetail(qs); - json(res, data.error ? 400 : 200, data, req); - return; - } - - if (method === "GET" && pathname === "/agentmemory/delivery-status") { - json(res, 200, readDeliveryStatus(), req); - return; - } - - if (method === "GET" && pathname === "/agentmemory/livez") { - json(res, 200, { - status: "ok", - service: "agentmemory", - viewerPort: getBoundViewerPort() || requestedPort, - viewerSkipped: getViewerSkipped(), - proxy: true, - }, req); - return; - } - - if (method === "GET" && pathname === "/agentmemory/health") { - json(res, 200, { - status: "ok", - service: "agentmemory", - viewerPort: getBoundViewerPort() || requestedPort, - viewerSkipped: getViewerSkipped(), - proxy: true, - }, req); - return; - } - - if (method === "GET" && pathname === "/agentmemory/config/flags") { - json(res, 200, viewerFlagsFallback(), req); - return; - } - - if (pathname === "/agentmemory/review") { - try { - if (await handleReviewFallback(req, res, method, qs, kv as ViewerKv)) return; - } catch (err) { - console.error(`[viewer] review fallback error:`, err); - json(res, 500, { error: "review fallback error" }, req); - return; - } - } - - if (pathname === "/agentmemory/inbox") { - try { - if (await handleInboxFallback(req, res, method, qs, kv as ViewerKv)) return; - } catch (err) { - console.error(`[viewer] inbox fallback error:`, err); - json(res, 500, { error: "inbox fallback error" }, req); - return; - } - } - - if (pathname === "/agentmemory/review/approve" && method === "POST") { - try { - if (await handleReviewApproveFallback(req, res, kv as ViewerKv)) return; - } catch (err) { - console.error(`[viewer] review approve fallback error:`, err); - json(res, 500, { error: "review approve fallback error" }, req); - return; - } - } - - if (pathname === "/agentmemory/review/dismiss" && method === "POST") { - try { - if (await handleReviewDismissFallback(req, res, kv as ViewerKv)) return; - } catch (err) { - console.error(`[viewer] review dismiss fallback error:`, err); - json(res, 500, { error: "review dismiss fallback error" }, req); - return; - } - } - - if (method === "GET" && pathname === "/agentmemory/actions") { - const params = parseViewerQuery(qs); - const limit = Math.max(1, Math.min(200, parseInt(params.limit || "50", 10) || 50)); - let actions = await (kv as ViewerKv).list(KV.actions); - const todoExtract = await readTodoExtractStatus(kv as ViewerKv); - if (params.status) actions = actions.filter((action) => action.status === params.status); - if (params.project) actions = actions.filter((action) => action.project === params.project); - if (params.parentId) actions = actions.filter((action) => action.parentId === params.parentId); - actions.sort((a, b) => (b.updatedAt || "").localeCompare(a.updatedAt || "")); - json(res, 200, { success: true, actions: actions.slice(0, limit), todoExtract }, req); - return; - } - - if (method === "POST" && pathname === "/agentmemory/actions/update") { - const raw = await readBody(req); - const body = raw ? JSON.parse(raw) as Record : {}; - const actionId = asText(body.actionId); - const status = asText(body.status); - if (!actionId) { - json(res, 400, { error: "actionId is required" }, req); - return; - } - const validStatuses = new Set(["pending", "active", "done", "blocked", "cancelled"]); - if (status && !validStatuses.has(status)) { - json(res, 400, { error: `invalid status: ${status}` }, req); - return; - } - const action = await (kv as ViewerKv).get(KV.actions, actionId); - if (!action) { - json(res, 404, { success: false, error: "action not found" }, req); - return; - } - if (status) action.status = status as Action["status"]; - action.updatedAt = new Date().toISOString(); - await (kv as ViewerKv).set(KV.actions, action.id, action); - json(res, 200, { success: true, action }, req); - return; - } - - if (method === "POST" && pathname === "/agentmemory/todo-extract/generate") { - const raw = await readBody(req); - const body = raw ? JSON.parse(raw) as Record : {}; - const startedAt = new Date().toISOString(); - await writeTodoExtractStatus(kv as ViewerKv, { status: "running", startedAt }); - // Fire-and-forget: return 202 immediately, run extraction in background. - // This prevents HTTP/proxy timeouts from leaving status stuck at "running". - json(res, 202, { accepted: true, startedAt }, req); - generateTodosFromSessions(kv as ViewerKv, body).then(async (result) => { - await writeTodoExtractStatus(kv as ViewerKv, { - status: "done", - startedAt, - finishedAt: new Date().toISOString(), - summary: result as unknown as Record, - }); - }).catch(async (err) => { - await writeTodoExtractStatus(kv as ViewerKv, { - status: "error", - startedAt, - finishedAt: new Date().toISOString(), - error: err instanceof Error ? err.message : String(err), - }); - }); - return; - } - - if (method === "POST" && pathname === "/agentmemory/todo/action-refresh") { - const raw = await readBody(req); - const body = raw ? JSON.parse(raw) as Record : {}; - const actionId = asText(body.actionId); - if (!actionId) { - json(res, 400, { error: "actionId is required" }, req); - return; - } - const result = await refreshTodoAction(kv as ViewerKv, { actionId }); - if (!result.success && result.reason === "action-not-found") { - json(res, 404, result, req); - return; - } - if (!result.success) { - json(res, 400, result, req); - return; - } - json(res, 200, result, req); - return; - } - - if (method === "POST" && pathname === "/agentmemory/todo/update") { - const raw = await readBody(req); - const body = raw ? JSON.parse(raw) as Record : {}; - const mode = body.mode === "apply" ? "apply" : "dry-run"; - const scope = body.scope === "all" ? "all" : "changed"; - const maxCards = - typeof body.maxCards === "number" && body.maxCards > 0 ? Math.floor(body.maxCards) : undefined; - // On apply, the viewer passes back the dry-run decisions so they are applied - // verbatim instead of re-calling the (non-deterministic) LLM. - const decisions = Array.isArray(body.decisions) ? body.decisions as never : undefined; - const result = await updateChangedTodoCards(kv as ViewerKv, { mode, maxCards, scope, decisions }); - json(res, 200, result, req); - return; - } - - if (pathname === "/agentmemory/config/todo-extractor" && (method === "GET" || method === "POST")) { - if (method === "POST") { - const raw = await readBody(req); - const body = raw ? JSON.parse(raw) as Record : {}; - const updates: Record = {}; - // Single writable-keys source of truth (mirrors api::todo-extractor-config) - // so a UI field (e.g. the LLM timeout) can never silently drop on save. - WRITABLE_TODO_EXTRACT_KEYS.forEach((key) => { - const value = asText(body[key]); - if (value && !/[\r\n]/.test(value)) updates[key] = value; - }); - if (Object.keys(updates).length) writeUserEnv(updates); - } - json(res, 200, { - success: true, - envPath: getUserEnvPath(), - config: getTodoExtractorUserConfig(), - restartRequired: false, - }, req); - return; - } - - if (method === "GET" && pathname === "/agentmemory/frontier") { - const params = parseViewerQuery(qs); - const limit = Math.max(1, Math.min(200, parseInt(params.limit || "20", 10) || 20)); - const allActions = await (kv as ViewerKv).list(KV.actions); - const frontier = allActions - .filter((action) => action.status !== "done" && action.status !== "cancelled") - .filter((action) => !params.project || action.project === params.project) - .sort((a, b) => (b.updatedAt || "").localeCompare(a.updatedAt || "")) - .slice(0, limit) - .map((action, index) => ({ action, score: limit - index, blockers: [], leased: false })); - json(res, 200, { - success: true, - frontier, - totalActions: allActions.length, - totalUnblocked: frontier.length, - }, req); - return; - } - - if (method === "GET" && pathname === "/agentmemory/sessions") { - const sessions = await (kv as ViewerKv).list(KV.sessions); - sessions.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || "")); - json(res, 200, { sessions }, req); - return; - } - - if (method === "GET" && pathname === "/agentmemory/observations") { - const params = parseViewerQuery(qs); - const sessionId = params.sessionId || ""; - if (!sessionId) { - json(res, 400, { error: "sessionId required" }, req); - return; - } - const observations = await (kv as ViewerKv).list(KV.observations(sessionId)); - observations.sort((a, b) => (a.timestamp || "").localeCompare(b.timestamp || "")); - json(res, 200, { observations }, req); - return; - } - - try { - await proxyToRestApi(resolvedRestPort, pathname, qs, method, req, res, secret); - } catch (err) { - console.error(`[viewer] proxy error on ${method} ${pathname}:`, err); - json(res, 502, { error: "upstream error" }, req); - } - }); - - let attempt = 0; - let currentPort = requestedPort; - - const tryListen = (): void => { - server.listen(currentPort, "127.0.0.1"); - }; - - server.on("listening", () => { - const addr = server.address(); - boundViewerPort = - addr && typeof addr === "object" && "port" in addr - ? addr.port - : currentPort; - viewerSkipped = false; - if (currentPort === requestedPort) { - console.log(`[agentmemory] Viewer: http://localhost:${currentPort}`); - } else { - console.log( - `[agentmemory] Viewer started on http://localhost:${currentPort} (fallback from ${requestedPort})`, - ); - } - }); - - server.on("error", (err: NodeJS.ErrnoException) => { - if (err.code === "EADDRINUSE" && attempt < MAX_VIEWER_PORT_RETRIES) { - attempt++; - currentPort = requestedPort + attempt; - setImmediate(tryListen); - return; - } - if (err.code === "EADDRINUSE") { - boundViewerPort = null; - viewerSkipped = true; - console.warn( - `[agentmemory] Viewer ports ${requestedPort}-${requestedPort + MAX_VIEWER_PORT_RETRIES} all in use, skipping viewer.`, - ); - } else { - boundViewerPort = null; - viewerSkipped = true; - console.error(`[agentmemory] Viewer error:`, err.message); - } - }); - - tryListen(); - - return server; -} - -async function proxyToRestApi( - restPort: number, - pathname: string, - qs: string, - method: string, - req: IncomingMessage, - res: ServerResponse, - secret?: string, -): Promise { - const upstreamPath = pathname.startsWith("/agentmemory/") - ? pathname - : `/agentmemory${pathname.startsWith("/") ? pathname : "/" + pathname}`; - - const upstreamUrl = `http://127.0.0.1:${restPort}${upstreamPath}${qs ? "?" + qs : ""}`; - - const headers: Record = {}; - if (secret) { - headers["Authorization"] = `Bearer ${secret}`; - } - const ct = req.headers["content-type"]; - if (ct) { - headers["Content-Type"] = ct; - } - - let body: string | undefined; - if (method === "POST" || method === "PUT" || method === "DELETE" || method === "PATCH") { - body = await readBody(req); - } - - const controller = new AbortController(); - const fetchTimeout = setTimeout(() => controller.abort(), proxyTimeoutMsForPath(upstreamPath)); - let upstream: Response; - try { - upstream = await fetch(upstreamUrl, { - method, - headers, - body: body || undefined, - signal: controller.signal, - }); - clearTimeout(fetchTimeout); - } catch (err) { - clearTimeout(fetchTimeout); - if (err instanceof Error && err.name === "AbortError") { - json(res, 504, { error: "upstream timeout" }, req); - return; - } - throw err; - } - - const cors = corsHeaders(req); - const responseBody = await upstream.text(); - const responseHeaders: Record = { - ...cors, - }; - const upstreamCt = upstream.headers.get("content-type"); - if (upstreamCt) { - responseHeaders["Content-Type"] = upstreamCt; - } - - res.writeHead(upstream.status, responseHeaders); - res.end(responseBody); -} diff --git a/src/xenova.d.ts b/src/xenova.d.ts deleted file mode 100644 index 7abd4b09..00000000 --- a/src/xenova.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module "@xenova/transformers" { - export function pipeline(task: string, model: string): Promise; -} diff --git a/test/access-tracker.test.ts b/test/access-tracker.test.ts deleted file mode 100644 index 35fca781..00000000 --- a/test/access-tracker.test.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -function mockKV() { - const store = new Map>(); - return { - store, - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string) => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const m = store.get(scope); - return m ? (Array.from(m.values()) as T[]) : []; - }, - }; -} - -describe("access-tracker", () => { - it("getAccessLog returns empty log for unknown id", async () => { - const { getAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const kv = mockKV(); - const log = await getAccessLog(kv as never, "mem_xyz"); - expect(log).toEqual({ memoryId: "mem_xyz", count: 0, lastAt: "", recent: [] }); - }); - - it("recordAccess increments count and lastAt", async () => { - const { recordAccess, getAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const kv = mockKV(); - await recordAccess(kv as never, "mem_a", 1_000_000); - await recordAccess(kv as never, "mem_a", 2_000_000); - await recordAccess(kv as never, "mem_a", 3_000_000); - - const log = await getAccessLog(kv as never, "mem_a"); - expect(log.count).toBe(3); - expect(log.recent).toEqual([1_000_000, 2_000_000, 3_000_000]); - expect(log.lastAt).toBe(new Date(3_000_000).toISOString()); - }); - - it("recent[] is bounded to last 20 entries", async () => { - const { recordAccess, getAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const kv = mockKV(); - for (let i = 1; i <= 50; i++) { - await recordAccess(kv as never, "mem_b", i * 1000); - } - const log = await getAccessLog(kv as never, "mem_b"); - expect(log.count).toBe(50); - expect(log.recent.length).toBe(20); - // Should be the LAST 20: 31_000..50_000 - expect(log.recent[0]).toBe(31_000); - expect(log.recent[19]).toBe(50_000); - }); - - it("recordAccessBatch deduplicates and writes once per id", async () => { - const { recordAccessBatch, getAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const kv = mockKV(); - await recordAccessBatch( - kv as never, - ["mem_a", "mem_b", "mem_a", "mem_b", "mem_c"], - 5_000_000, - ); - expect((await getAccessLog(kv as never, "mem_a")).count).toBe(1); - expect((await getAccessLog(kv as never, "mem_b")).count).toBe(1); - expect((await getAccessLog(kv as never, "mem_c")).count).toBe(1); - }); - - it("recordAccess swallows kv.set errors (must not break reads)", async () => { - const { recordAccess } = await import( - "../src/functions/access-tracker.js" - ); - const kv = mockKV(); - kv.set = (async () => { - throw new Error("boom"); - }) as never; - await expect(recordAccess(kv as never, "mem_a")).resolves.toBeUndefined(); - }); - - it("concurrent recordAccess calls do not lose increments (keyed mutex)", async () => { - const { recordAccess, getAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const kv = mockKV(); - await Promise.all( - Array.from({ length: 25 }, (_, i) => - recordAccess(kv as never, "mem_race", i * 100), - ), - ); - const log = await getAccessLog(kv as never, "mem_race"); - expect(log.count).toBe(25); - }); - - it("recordAccessBatch: a single failing id does not block siblings", async () => { - const { recordAccessBatch, getAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const kv = mockKV(); - const realSet = kv.set.bind(kv); - kv.set = (async (scope: string, key: string, val: unknown) => { - if (key === "mem_slow") throw new Error("write failed"); - return realSet(scope, key, val); - }) as never; - - await recordAccessBatch( - kv as never, - ["mem_slow", "mem_fast_a", "mem_fast_b"], - 1_000_000, - ); - expect((await getAccessLog(kv as never, "mem_fast_a")).count).toBe(1); - expect((await getAccessLog(kv as never, "mem_fast_b")).count).toBe(1); - expect((await getAccessLog(kv as never, "mem_slow")).count).toBe(0); - }); - - it("ignores empty / falsy memory ids", async () => { - const { recordAccess, recordAccessBatch } = await import( - "../src/functions/access-tracker.js" - ); - const kv = mockKV(); - await recordAccess(kv as never, ""); - await recordAccessBatch(kv as never, ["", "mem_x", ""]); - expect(kv.store.get("mem:access")?.has("")).toBeFalsy(); - expect(kv.store.get("mem:access")?.get("mem_x")).toBeTruthy(); - }); - - it("deleteAccessLog removes the target entry and leaves siblings intact", async () => { - const { recordAccess, deleteAccessLog, getAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const kv = mockKV(); - await recordAccess(kv as never, "mem_a"); - await recordAccess(kv as never, "mem_b"); - - await deleteAccessLog(kv as never, "mem_a"); - - expect(kv.store.get("mem:access")?.has("mem_a")).toBe(false); - expect((await getAccessLog(kv as never, "mem_b")).count).toBe(1); - }); - - it("deleteAccessLog is a no-op for unknown ids and empty ids", async () => { - const { deleteAccessLog, recordAccess } = await import( - "../src/functions/access-tracker.js" - ); - const kv = mockKV(); - await recordAccess(kv as never, "mem_keep"); - - await deleteAccessLog(kv as never, ""); - await deleteAccessLog(kv as never, "mem_unknown"); - - expect(kv.store.get("mem:access")?.has("mem_keep")).toBe(true); - }); - - it("deleteAccessLog swallows kv.delete errors", async () => { - const { deleteAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const kv = mockKV(); - kv.delete = (async () => { - throw new Error("boom"); - }) as never; - - await expect( - deleteAccessLog(kv as never, "mem_x"), - ).resolves.toBeUndefined(); - }); -}); - -describe("normalizeAccessLog", () => { - it("returns a well-formed empty log for nullish / non-object input", async () => { - const { normalizeAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const log = normalizeAccessLog(null); - expect(log).toEqual({ - memoryId: "", - count: 0, - lastAt: "", - recent: [], - }); - expect(normalizeAccessLog(undefined).count).toBe(0); - expect(normalizeAccessLog("garbage").count).toBe(0); - }); - - it("coerces count to a non-negative integer", async () => { - const { normalizeAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - expect(normalizeAccessLog({ count: -5 }).count).toBe(0); - expect(normalizeAccessLog({ count: 3.7 }).count).toBe(3); - expect(normalizeAccessLog({ count: NaN }).count).toBe(0); - expect(normalizeAccessLog({ count: "123" }).count).toBe(0); - }); - - it("preserves large lifetime counts (NOT capped at ring buffer size)", async () => { - const { normalizeAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const log = normalizeAccessLog({ memoryId: "m", count: 500, recent: [1] }); - expect(log.count).toBe(500); - }); - - it("truncates recent[] to the last 20 entries and drops non-finite values", async () => { - const { normalizeAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const input = Array.from({ length: 40 }, (_, i) => i * 1000); - const withGarbage = [...input, NaN, Infinity, "bad" as unknown as number]; - const log = normalizeAccessLog({ recent: withGarbage }); - expect(log.recent.length).toBe(20); - expect(log.recent[0]).toBe(20_000); - expect(log.recent[19]).toBe(39_000); - }); - - it("count is at least recent.length when count < recent.length", async () => { - const { normalizeAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - const log = normalizeAccessLog({ - count: 2, - recent: [1, 2, 3, 4, 5], - }); - expect(log.count).toBeGreaterThanOrEqual(5); - }); - - it("fills in memoryId only when field is a string", async () => { - const { normalizeAccessLog } = await import( - "../src/functions/access-tracker.js" - ); - expect(normalizeAccessLog({ memoryId: "mem_x" }).memoryId).toBe("mem_x"); - expect(normalizeAccessLog({ memoryId: 42 }).memoryId).toBe(""); - }); -}); diff --git a/test/action-candidates.test.ts b/test/action-candidates.test.ts deleted file mode 100644 index 6ea4289d..00000000 --- a/test/action-candidates.test.ts +++ /dev/null @@ -1,582 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { registerActionCandidateFunctions } from "../src/functions/action-candidates.js"; -import { - extractActionCandidatesFromObservations, - extractActionCandidatesFromTurns, -} from "../src/functions/action-candidates.js"; -import { KV } from "../src/state/schema.js"; -import type { Action, CompressedObservation, ReviewQueueItem, Session } from "../src/types.js"; -import { mockKV, mockSdk } from "./helpers/mocks.js"; - -function obs( - id: string, - patch: Partial, -): CompressedObservation { - return { - id, - sessionId: "ses_actions", - timestamp: `2026-06-11T09:${String(Math.max(0, Number(id.replace(/\D/g, "")) || 0)).padStart(2, "0")}:00.000Z`, - type: "conversation", - title: id, - facts: [], - narrative: "", - concepts: [], - files: [], - importance: 5, - confidence: 0.6, - ...patch, - }; -} - -describe("zero-LLM action candidates", () => { - it("extracts explicit Chinese and English todo/follow-up candidates", () => { - const candidates = extractActionCandidatesFromObservations([ - obs("obs_1", { - narrative: "下一步请修复摘要按钮一直显示摘要为空的问题。", - files: ["src/functions/session-highlights.ts"], - }), - obs("obs_2", { - narrative: "TODO: 补充摘要按钮回归测试。", - files: ["test/action-candidates.test.ts"], - }), - obs("obs_3", { - narrative: "TODO: 修复 exec_command 自然语言误判。", - }), - obs("obs_4", { - narrative: "下一步请修复 apply_patch 相关的待办抽取。", - }), - ]); - - expect(candidates).toHaveLength(4); - expect(candidates[0]).toMatchObject({ - title: "修复摘要按钮一直显示摘要为空的问题", - priority: expect.any(Number), - source: "observation", - sourceObservationIds: ["obs_1"], - }); - expect(candidates[0].tags).toEqual(expect.arrayContaining(["action-candidate", "follow-up"])); - expect(candidates[1].title).toBe("补充摘要按钮回归测试"); - expect(candidates[1].sourceObservationIds).toEqual(["obs_2"]); - expect(candidates.map((c) => c.title)).toEqual(expect.arrayContaining([ - "修复 exec_command 自然语言误判", - "修复 apply_patch 相关的待办抽取", - ])); - }); - - it("filters tool-output, status reports, and git-ref fragments (STEP-08)", () => { - const candidates = extractActionCandidatesFromObservations([ - obs("obs_1", { - type: "command_run", - narrative: '⏺ Bash(pwd && echo "--- branch ---" && git branch --show-current)', - }), - obs("obs_2", { - narrative: - "服务可用: - Viewer: [http://localhost:3115](http://localhost:3115) - Health: [http://localhost:3115/agentmemory/livez]", - }), - obs("obs_3", { narrative: "origin-main-e7f5ca2`" }), - obs("obs_4", { narrative: "下一步请修复分类边界判定错误。" }), - ]); - const titles = candidates.map((c) => c.title); - expect(titles.some((t) => /⏺|Bash\(/.test(t))).toBe(false); - expect(titles.some((t) => /服务可用|Viewer:|Health:/.test(t))).toBe(false); - expect(titles.some((t) => /^origin-main/.test(t))).toBe(false); - expect(titles).toContain("修复分类边界判定错误"); - }); - - it("extracts repair candidates only when failures include a repair action", () => { - const candidates = extractActionCandidatesFromObservations([ - obs("obs_1", { - type: "command_run", - title: "Bash", - narrative: "Command failed with exit code 1: npm test failed.", - }), - obs("obs_2", { - type: "task", - narrative: "blocked: review approval path still needs sourceObservationIds.", - }), - obs("obs_3", { - type: "conversation", - narrative: "验证未通过,需要修复 action review 的 priority 映射。", - }), - obs("obs_4", { - type: "conversation", - narrative: "测试失败,需要修复 replay import 幂等性。", - }), - obs("obs_5", { - type: "conversation", - narrative: "Need to fix the failing CI and rerun tests.", - }), - ]); - - expect(candidates.some((c) => c.sourceObservationIds.includes("obs_1"))).toBe(false); - expect(candidates.map((c) => c.reason)).toEqual(expect.arrayContaining(["blocked", "follow_up"])); - expect(candidates.map((c) => c.title)).toEqual(expect.arrayContaining([ - "修复 replay import 幂等性", - "the failing CI and rerun tests", - ])); - expect(candidates.find((c) => c.sourceObservationIds.includes("obs_5"))?.tags).toContain("follow-up"); - }); - - it("does not extract from normal explanations or read-only source observations", () => { - const candidates = extractActionCandidatesFromObservations([ - obs("obs_1", { - type: "conversation", - narrative: "我会先审查当前链路,并说明实现计划。", - }), - obs("obs_2", { - type: "file_read", - title: "Read src/functions/actions.ts", - narrative: "function run() { /* TODO failed test blocked */ }", - }), - obs("obs_3", { - type: "search", - narrative: "src/file.ts: TODO: old source text", - }), - obs("obs_4", { - type: "web_fetch", - narrative: "Docs mention follow up tasks and failed validation.", - }), - ]); - - expect(candidates).toEqual([]); - }); - - it("does not treat bare polite requests as action candidates", () => { - const candidates = extractActionCandidatesFromObservations([ - obs("obs_1", { narrative: "请审查当前实现。" }), - obs("obs_2", { narrative: "需要分析为何摘要为空。" }), - obs("obs_3", { narrative: "希望你说明原因。" }), - ]); - - expect(candidates).toEqual([]); - }); - - it("filters structured tool traces without rejecting natural-language tool names", () => { - const candidates = extractActionCandidatesFromObservations([ - obs("obs_1", { - narrative: "{\"function_id\":\"x\",\"toolInput\":{\"command\":\"npm test\"}}", - }), - obs("obs_2", { - narrative: "toolOutput function_id \"command\": npm test", - }), - obs("obs_3", { - narrative: "TODO: 修复 exec_command 自然语言误判。", - }), - ]); - - expect(candidates).toHaveLength(1); - expect(candidates[0]).toMatchObject({ - title: "修复 exec_command 自然语言误判", - sourceObservationIds: ["obs_3"], - }); - }); - - it("does not turn Markdown implementation plans into action candidates", () => { - const candidates = extractActionCandidatesFromObservations([ - obs("obs_1", { - narrative: [ - "# 待办生成链路与前端展示修复计划", - "", - "## Summary", - "本轮修复待办候选生成与展示链路。", - "", - "## Key Changes", - "- 前端待办页改造:打开待办页时自动整理最近会话。", - "- 后端补齐会话待办候选生成。", - "", - "## Test Plan", - "- npm test -- --run test/action-candidates.test.ts", - ].join("\n"), - }), - obs("obs_2", { - narrative: "# 待办生成链路与前端展示修复计划 ## Summary 本轮修复待办候选生成与展示链路。 ## Key Changes - 前端待办页改造。 ## Test Plan - npm test", - }), - obs("obs_3", { - narrative: "进行修复计划的构建 审查结果 [P1] 仍会显示计划。# 待办生成链路与前端展示修复计划 ## Summary 本轮修复链路。 ## Key Changes - 前端过滤。 ## Test Plan - npm test", - }), - obs("obs_4", { - narrative: "# 待办生成链路与前端展示修复计划 ## Summary 本轮暂不处理摘要按钮,只修待办候选生成与展示链路。", - }), - ]); - - expect(candidates).toEqual([]); - }); - - it("uses only the extracted action sentence as candidate description", () => { - const candidates = extractActionCandidatesFromObservations([ - obs("obs_1", { - narrative: [ - "背景:待办页候选生成目前会混入大段上下文,这些内容不应进入描述。", - "下一步请修复待办页展示问题,并补充必要回归测试。", - "Summary / Key Changes / Test Plan 这些文档段落不应该出现在候选里。", - ].join("\n"), - }), - ]); - - expect(candidates).toHaveLength(1); - expect(candidates[0].title).toBe("修复待办页展示问题,并补充必要回归测试"); - expect(candidates[0].description).toBe("下一步请修复待办页展示问题,并补充必要回归测试。"); - expect(candidates[0].description).not.toContain("Summary"); - expect(candidates[0].description.length).toBeLessThanOrEqual(180); - }); - - it("deduplicates candidates from the same batch and skips existing pending work", () => { - const existingAction: Action = { - id: "act_existing", - title: "修复会话重点里 Agent 回复缺失的问题", - description: "old", - status: "pending", - priority: 6, - createdAt: "2026-06-11T08:00:00.000Z", - updatedAt: "2026-06-11T08:00:00.000Z", - createdBy: "test", - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - }; - const existingReview: ReviewQueueItem = { - id: "review_existing", - createdAt: "2026-06-11T08:00:00.000Z", - updatedAt: "2026-06-11T08:00:00.000Z", - status: "pending", - kind: "action", - title: "补充 action review 测试", - content: "old", - source: "api", - }; - - const candidates = extractActionCandidatesFromObservations( - [ - obs("obs_1", { narrative: "下一步请修复会话重点里 Agent 回复缺失的问题。" }), - obs("obs_2", { narrative: "下一步请修复会话重点里 Agent 回复缺失的问题。" }), - obs("obs_3", { narrative: "TODO: 补充 action review 测试。" }), - obs("obs_4", { narrative: "TODO: 新增 Viewer 行动候选展示。" }), - ], - { existingActions: [existingAction], existingReviewItems: [existingReview] }, - ); - - expect(candidates).toHaveLength(1); - expect(candidates[0].title).toBe("新增 Viewer 行动候选展示"); - }); - - it("collapses CJK rewordings of one task across a batch but keeps distinct work (STEP-08 PR4)", () => { - const candidates = extractActionCandidatesFromObservations([ - obs("obs_1", { narrative: "TODO: 克隆上游项目到子目录。" }), - obs("obs_2", { narrative: "TODO: 克隆上游项目到子目录中。" }), // insertion - obs("obs_3", { narrative: "TODO: 把上游项目克隆到子目录。" }), // word-order - obs("obs_4", { narrative: "TODO: 克隆上游项目到子目录下。" }), // insertion - obs("obs_5", { narrative: "TODO: 修复登录态失效后摘要不显示。" }), // distinct - ]); - - expect(candidates.map((c) => c.title)).toEqual([ - "克隆上游项目到子目录", - "修复登录态失效后摘要不显示", - ]); - }); - - it("keeps semantically distinct candidates that only look similar (STEP-08 PR4)", () => { - // negation flip, version bump, and a two-char substitution must NOT collapse - const candidates = extractActionCandidatesFromObservations([ - obs("obs_1", { narrative: "TODO: 支持离线缓存模式。" }), - obs("obs_2", { narrative: "TODO: 不支持离线缓存模式。" }), - obs("obs_3", { narrative: "TODO: 升级依赖到 v2。" }), - obs("obs_4", { narrative: "TODO: 升级依赖到 v3。" }), - obs("obs_5", { narrative: "TODO: 克隆上游仓库到子目录。" }), - obs("obs_6", { narrative: "TODO: 克隆上游项目到子目录。" }), - ]); - - expect(candidates).toHaveLength(6); - }); - - it("near-dup guard is seeded from open work only, never from done work (STEP-08 PR4)", () => { - const seedNearDup = (status: Action["status"]): Action => ({ - id: `act_${status}`, - title: "克隆上游项目到子目录中", - description: "old", - status, - priority: 6, - createdAt: "2026-06-11T08:00:00.000Z", - updatedAt: "2026-06-11T08:00:00.000Z", - createdBy: "test", - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - }); - const observation = obs("obs_1", { narrative: "TODO: 克隆上游项目到子目录。" }); - - // A near-dup of an open action is suppressed... - expect( - extractActionCandidatesFromObservations([observation], { - existingActions: [seedNearDup("pending")], - }), - ).toHaveLength(0); - - // ...but a near-dup of a done action may reappear (work could have regressed). - expect( - extractActionCandidatesFromObservations([observation], { - existingActions: [seedNearDup("done")], - }), - ).toHaveLength(1); - }); - - it("seeds the near-dup guard from pending action reviews but not approved/dismissed ones (STEP-08 PR4)", () => { - const seedReview = (status: ReviewQueueItem["status"]): ReviewQueueItem => ({ - id: `review_${status}`, - createdAt: "2026-06-11T08:00:00.000Z", - updatedAt: "2026-06-11T08:00:00.000Z", - status, - kind: "action", - title: "克隆上游项目到子目录中", - content: "old", - source: "viewer", - }); - const observation = obs("obs_1", { narrative: "TODO: 克隆上游项目到子目录。" }); - - expect( - extractActionCandidatesFromObservations([observation], { - existingReviewItems: [seedReview("pending")], - }), - ).toHaveLength(0); - expect( - extractActionCandidatesFromObservations([observation], { - existingReviewItems: [seedReview("dismissed")], - }), - ).toHaveLength(1); - }); - - it("collapses near-duplicate browser turns of one task into a single draft (STEP-08 PR4)", () => { - const candidates = extractActionCandidatesFromTurns([ - { role: "user", text: "下一步请修复登录页面的样式问题。" }, - { role: "user", text: "下一步请修复登录页面的样式问题啊。" }, // insertion reword - { role: "user", text: "下一步请修复 issue 123。" }, - { role: "user", text: "下一步请修复 issue 456。" }, // different issue → kept - ]); - - expect(candidates.map((c) => c.title)).toEqual([ - "修复登录页面的样式问题", - "修复 issue 123", - "修复 issue 456", - ]); - }); - - it("extracts browser conversation turns with explicit next-step language", () => { - const candidates = extractActionCandidatesFromTurns([ - { role: "assistant", text: "我会先解释这个页面。" }, - { role: "user", text: "下一步请修复 action review 的批准流程。" }, - { role: "assistant", text: "普通总结,不应生成待办。" }, - ]); - - expect(candidates).toHaveLength(1); - expect(candidates[0]).toMatchObject({ - title: "修复 action review 的批准流程", - source: "browser-review", - sourceObservationIds: [], - }); - }); - - it("only extracts browser action drafts from user turns", () => { - const candidates = extractActionCandidatesFromTurns([ - { role: "assistant", text: "下一步请修复摘要按钮一直显示摘要为空的问题。" }, - { role: "user", text: "请审查当前实现。" }, - { role: "user", text: "需要分析为何摘要为空。" }, - { role: "user", text: "希望你说明原因。" }, - { role: "user", text: "下一步请修复摘要按钮一直显示摘要为空的问题。" }, - ]); - - expect(candidates).toHaveLength(1); - expect(candidates[0].title).toBe("修复摘要按钮一直显示摘要为空的问题"); - }); -}); - -describe("mem::action-candidates-generate", () => { - function setup() { - const sdk = mockSdk(); - const kv = mockKV(); - registerActionCandidateFunctions(sdk as never, kv as never); - return { sdk, kv }; - } - - async function seedSession(kv: ReturnType) { - const session: Session = { - id: "ses_actions", - project: "agentmemory-lab", - cwd: "/repo", - startedAt: "2026-06-11T08:00:00.000Z", - endedAt: "2026-06-11T08:10:00.000Z", - status: "completed", - observationCount: 4, - }; - await kv.set(KV.sessions, session.id, session); - await kv.set(KV.observations(session.id), "obs_1", obs("obs_1", { - sessionId: session.id, - narrative: "TODO: 补充待办页候选回归测试。", - timestamp: "2026-06-11T08:01:00.000Z", - })); - await kv.set(KV.observations(session.id), "obs_2", obs("obs_2", { - sessionId: session.id, - narrative: "下一步请修复待办页不展示未批准候选的问题。", - timestamp: "2026-06-11T08:02:00.000Z", - })); - await kv.set(KV.observations(session.id), "obs_3", obs("obs_3", { - sessionId: session.id, - narrative: "测试失败,需要修复 review approve 的错误处理。", - timestamp: "2026-06-11T08:03:00.000Z", - })); - await kv.set(KV.observations(session.id), "obs_4", obs("obs_4", { - sessionId: session.id, - narrative: "请审查当前实现。", - timestamp: "2026-06-11T08:04:00.000Z", - })); - } - - it("generates pending action review items from recent session observations", async () => { - const { sdk, kv } = setup(); - await seedSession(kv); - const duplicateSession: Session = { - id: "ses_actions_duplicate", - project: "agentmemory-lab", - cwd: "/repo", - startedAt: "2026-06-11T08:20:00.000Z", - endedAt: "2026-06-11T08:30:00.000Z", - status: "completed", - observationCount: 1, - }; - await kv.set(KV.sessions, duplicateSession.id, duplicateSession); - await kv.set(KV.observations(duplicateSession.id), "obs_dup", obs("obs_dup", { - sessionId: duplicateSession.id, - narrative: "TODO: 补充待办页候选回归测试。", - timestamp: "2026-06-11T08:21:00.000Z", - })); - - const result = await sdk.trigger("mem::action-candidates-generate", { - maxSessions: 5, - maxObservationsPerSession: 20, - }) as { success: true; generated: number; items: ReviewQueueItem[]; scannedSessions: number; scannedObservations: number }; - - expect(result.success).toBe(true); - expect(result.scannedSessions).toBe(2); - expect(result.scannedObservations).toBe(5); - expect(result.generated).toBe(3); - expect(result.items.map((item) => item.kind)).toEqual(["action", "action", "action"]); - expect(result.items.map((item) => item.title)).toEqual(expect.arrayContaining([ - "补充待办页候选回归测试", - "修复待办页不展示未批准候选的问题", - "修复 review approve 的错误处理", - ])); - expect(result.items.every((item) => item.payload?.project === "agentmemory-lab")).toBe(true); - const sessionOnlyItem = result.items.find((item) => item.title === "修复待办页不展示未批准候选的问题"); - expect(sessionOnlyItem).toMatchObject({ - status: "pending", - source: "viewer", - payload: { - project: "agentmemory-lab", - sourceSessionId: "ses_actions", - sourceSessionProject: "agentmemory-lab", - sourceSessionCwd: "/repo", - actionCandidate: { - sourceObservationIds: expect.any(Array), - }, - }, - }); - }); - - it("falls back to session cwd when source session project is missing", async () => { - const { sdk, kv } = setup(); - const session: Session = { - id: "ses_cwd_project", - project: "", - cwd: "/repo/no-project", - startedAt: "2026-06-11T08:00:00.000Z", - endedAt: "2026-06-11T08:10:00.000Z", - status: "completed", - observationCount: 1, - }; - await kv.set(KV.sessions, session.id, session); - await kv.set(KV.observations(session.id), "obs_cwd", obs("obs_cwd", { - sessionId: session.id, - narrative: "TODO: 补充 cwd project fallback 测试。", - timestamp: "2026-06-11T08:01:00.000Z", - })); - - const result = await sdk.trigger("mem::action-candidates-generate", { - maxSessions: 5, - maxObservationsPerSession: 20, - }) as { success: true; generated: number; items: ReviewQueueItem[] }; - - expect(result.success).toBe(true); - expect(result.generated).toBe(1); - expect(result.items[0]).toMatchObject({ - title: "补充 cwd project fallback 测试", - payload: { - project: "/repo/no-project", - sourceSessionId: "ses_cwd_project", - sourceSessionCwd: "/repo/no-project", - }, - }); - expect(result.items[0].payload?.sourceSessionProject).toBeUndefined(); - }); - - it("collapses title-drifted candidates across sessions via the generate loop (STEP-08 PR4)", async () => { - const { sdk, kv } = setup(); - const mkSession = (id: string, ended: string): Session => ({ - id, - project: "agentmemory-lab", - cwd: "/repo", - startedAt: ended, - endedAt: ended, - status: "completed", - observationCount: 1, - }); - // Two sessions, same task reworded (insertion) — exact-key dedup misses it, - // the cross-session near-dup guard must collapse it. - await kv.set(KV.sessions, "ses_a", mkSession("ses_a", "2026-06-11T08:10:00.000Z")); - await kv.set(KV.observations("ses_a"), "obs_a", obs("obs_a", { - sessionId: "ses_a", - narrative: "TODO: 克隆上游项目到子目录。", - timestamp: "2026-06-11T08:11:00.000Z", - })); - await kv.set(KV.sessions, "ses_b", mkSession("ses_b", "2026-06-11T08:20:00.000Z")); - await kv.set(KV.observations("ses_b"), "obs_b", obs("obs_b", { - sessionId: "ses_b", - narrative: "TODO: 克隆上游项目到子目录中。", - timestamp: "2026-06-11T08:21:00.000Z", - })); - - const result = await sdk.trigger("mem::action-candidates-generate", {}) as { generated: number; items: ReviewQueueItem[] }; - - expect(result.generated).toBe(1); - expect(result.items[0].title).toMatch(/^克隆上游项目到子目录/); - }); - - it("does not regenerate duplicates after pending, dismissed, approved, or existing actions", async () => { - const { sdk, kv } = setup(); - await seedSession(kv); - - const first = await sdk.trigger("mem::action-candidates-generate", {}) as { generated: number; items: ReviewQueueItem[] }; - expect(first.generated).toBe(3); - const pendingDuplicate = await sdk.trigger("mem::action-candidates-generate", {}) as { generated: number }; - expect(pendingDuplicate.generated).toBe(0); - - const items = await kv.list(KV.reviewQueue); - await kv.set(KV.reviewQueue, items[0].id, { ...items[0], status: "dismissed", reviewedAt: "2026-06-11T08:11:00.000Z" }); - await kv.set(KV.reviewQueue, items[1].id, { ...items[1], status: "approved", reviewedAt: "2026-06-11T08:12:00.000Z" }); - await kv.delete(KV.reviewQueue, items[2].id); - const existingAction: Action = { - id: "act_existing", - title: items[2].title, - description: items[2].content, - status: "pending", - priority: 8, - createdAt: "2026-06-11T08:13:00.000Z", - updatedAt: "2026-06-11T08:13:00.000Z", - createdBy: "test", - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - }; - await kv.set(KV.actions, existingAction.id, existingAction); - - const afterTerminalStates = await sdk.trigger("mem::action-candidates-generate", {}) as { generated: number }; - expect(afterTerminalStates.generated).toBe(0); - }); -}); diff --git a/test/actions.test.ts b/test/actions.test.ts deleted file mode 100644 index 45e12cfa..00000000 --- a/test/actions.test.ts +++ /dev/null @@ -1,475 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerActionsFunction } from "../src/functions/actions.js"; -import type { Action, ActionEdge } from "../src/types.js"; -import { mockKV, mockSdk } from "./helpers/mocks.js"; - -describe("Actions Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerActionsFunction(sdk as never, kv as never); - }); - - describe("mem::action-create", () => { - it("creates an action with valid data", async () => { - const result = (await sdk.trigger("mem::action-create", { - title: "Fix login bug", - description: "Users cannot log in with SSO", - priority: 7, - createdBy: "agent-1", - project: "webapp", - tags: ["bug", "auth"], - })) as { success: boolean; action: Action; edges: ActionEdge[] }; - - expect(result.success).toBe(true); - expect(result.action.id).toMatch(/^act_/); - expect(result.action.title).toBe("Fix login bug"); - expect(result.action.description).toBe("Users cannot log in with SSO"); - expect(result.action.status).toBe("pending"); - expect(result.action.priority).toBe(7); - expect(result.action.createdBy).toBe("agent-1"); - expect(result.action.project).toBe("webapp"); - expect(result.action.tags).toEqual(["bug", "auth"]); - expect(result.action.createdAt).toBeDefined(); - expect(result.action.updatedAt).toBeDefined(); - expect(result.edges).toEqual([]); - }); - - it("returns error when title is missing", async () => { - const result = (await sdk.trigger("mem::action-create", { - description: "No title provided", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("title is required"); - }); - - it("accepts explicit status and metadata for extracted todos", async () => { - const result = (await sdk.trigger("mem::action-create", { - title: "Validate release", - status: "active", - metadata: { todoExtraction: { typeBucket: "processing" } }, - })) as { success: boolean; action: Action }; - - expect(result.success).toBe(true); - expect(result.action.status).toBe("active"); - expect(result.action.metadata).toEqual({ todoExtraction: { typeBucket: "processing" } }); - }); - - it("rejects invalid explicit status", async () => { - const result = (await sdk.trigger("mem::action-create", { - title: "Invalid status", - status: "processing", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("invalid status"); - }); - - it("clamps priority 0 to default 5 (falsy fallback)", async () => { - const result = (await sdk.trigger("mem::action-create", { - title: "Zero priority task", - priority: 0, - })) as { success: boolean; action: Action }; - - expect(result.success).toBe(true); - expect(result.action.priority).toBe(5); - }); - - it("clamps negative priority to 1", async () => { - const result = (await sdk.trigger("mem::action-create", { - title: "Negative priority task", - priority: -3, - })) as { success: boolean; action: Action }; - - expect(result.success).toBe(true); - expect(result.action.priority).toBe(1); - }); - - it("clamps priority 15 to 10", async () => { - const result = (await sdk.trigger("mem::action-create", { - title: "High priority task", - priority: 15, - })) as { success: boolean; action: Action }; - - expect(result.success).toBe(true); - expect(result.action.priority).toBe(10); - }); - - it("validates parent action exists", async () => { - const result = (await sdk.trigger("mem::action-create", { - title: "Child task", - parentId: "nonexistent_parent", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("parent action not found"); - }); - - it("creates action with valid parent", async () => { - const parentResult = (await sdk.trigger("mem::action-create", { - title: "Parent task", - })) as { success: boolean; action: Action }; - - const childResult = (await sdk.trigger("mem::action-create", { - title: "Child task", - parentId: parentResult.action.id, - })) as { success: boolean; action: Action }; - - expect(childResult.success).toBe(true); - expect(childResult.action.parentId).toBe(parentResult.action.id); - }); - - it("creates inline edges with valid types", async () => { - const targetResult = (await sdk.trigger("mem::action-create", { - title: "Target action", - })) as { success: boolean; action: Action }; - - const result = (await sdk.trigger("mem::action-create", { - title: "Source action", - edges: [ - { type: "requires", targetActionId: targetResult.action.id }, - { type: "unlocks", targetActionId: targetResult.action.id }, - ], - })) as { success: boolean; action: Action; edges: ActionEdge[] }; - - expect(result.success).toBe(true); - expect(result.edges.length).toBe(2); - expect(result.edges[0].id).toMatch(/^ae_/); - expect(result.edges[0].type).toBe("requires"); - expect(result.edges[0].sourceActionId).toBe(result.action.id); - expect(result.edges[0].targetActionId).toBe(targetResult.action.id); - expect(result.edges[1].type).toBe("unlocks"); - }); - - it("returns error for inline edge with invalid type", async () => { - const targetResult = (await sdk.trigger("mem::action-create", { - title: "Target action", - })) as { success: boolean; action: Action }; - - const result = (await sdk.trigger("mem::action-create", { - title: "Source action", - edges: [ - { type: "invalid_type", targetActionId: targetResult.action.id }, - ], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("invalid edge type"); - }); - - it("returns error for inline edge with nonexistent target", async () => { - const result = (await sdk.trigger("mem::action-create", { - title: "Source action", - edges: [{ type: "requires", targetActionId: "nonexistent_id" }], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("target action not found"); - }); - }); - - describe("mem::action-update", () => { - it("updates an action with valid data", async () => { - const createResult = (await sdk.trigger("mem::action-create", { - title: "Original title", - priority: 5, - })) as { success: boolean; action: Action }; - - const updateResult = (await sdk.trigger("mem::action-update", { - actionId: createResult.action.id, - title: "Updated title", - priority: 8, - status: "active", - assignedTo: "agent-2", - tags: ["updated"], - })) as { success: boolean; action: Action }; - - expect(updateResult.success).toBe(true); - expect(updateResult.action.title).toBe("Updated title"); - expect(updateResult.action.priority).toBe(8); - expect(updateResult.action.status).toBe("active"); - expect(updateResult.action.assignedTo).toBe("agent-2"); - expect(updateResult.action.tags).toEqual(["updated"]); - }); - - it("returns error when actionId is missing", async () => { - const result = (await sdk.trigger("mem::action-update", { - title: "no id", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("actionId is required"); - }); - - it("returns error for nonexistent action", async () => { - const result = (await sdk.trigger("mem::action-update", { - actionId: "nonexistent_id", - status: "done", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("action not found"); - }); - - it("propagates completion when status set to done", async () => { - const actionB = (await sdk.trigger("mem::action-create", { - title: "Dependency B", - })) as { success: boolean; action: Action }; - - const actionA = (await sdk.trigger("mem::action-create", { - title: "Action A depends on B", - edges: [{ type: "requires", targetActionId: actionB.action.id }], - })) as { success: boolean; action: Action }; - - await sdk.trigger("mem::action-update", { - actionId: actionA.action.id, - status: "blocked", - }); - - await sdk.trigger("mem::action-update", { - actionId: actionB.action.id, - status: "done", - }); - - const getResult = (await sdk.trigger("mem::action-get", { - actionId: actionA.action.id, - })) as { success: boolean; action: Action }; - - expect(getResult.action.status).toBe("pending"); - }); - }); - - describe("mem::action-edge-create", () => { - it("creates an edge between two actions", async () => { - const source = (await sdk.trigger("mem::action-create", { - title: "Source", - })) as { success: boolean; action: Action }; - - const target = (await sdk.trigger("mem::action-create", { - title: "Target", - })) as { success: boolean; action: Action }; - - const result = (await sdk.trigger("mem::action-edge-create", { - sourceActionId: source.action.id, - targetActionId: target.action.id, - type: "requires", - })) as { success: boolean; edge: ActionEdge }; - - expect(result.success).toBe(true); - expect(result.edge.id).toMatch(/^ae_/); - expect(result.edge.type).toBe("requires"); - expect(result.edge.sourceActionId).toBe(source.action.id); - expect(result.edge.targetActionId).toBe(target.action.id); - expect(result.edge.createdAt).toBeDefined(); - }); - - it("returns error when required fields are missing", async () => { - const result = (await sdk.trigger("mem::action-edge-create", { - sourceActionId: "some_id", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("required"); - }); - - it("returns error for invalid edge type", async () => { - const source = (await sdk.trigger("mem::action-create", { - title: "Source", - })) as { success: boolean; action: Action }; - - const target = (await sdk.trigger("mem::action-create", { - title: "Target", - })) as { success: boolean; action: Action }; - - const result = (await sdk.trigger("mem::action-edge-create", { - sourceActionId: source.action.id, - targetActionId: target.action.id, - type: "invalid_type", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("type must be one of"); - }); - - it("returns error for nonexistent source action", async () => { - const target = (await sdk.trigger("mem::action-create", { - title: "Target", - })) as { success: boolean; action: Action }; - - const result = (await sdk.trigger("mem::action-edge-create", { - sourceActionId: "nonexistent", - targetActionId: target.action.id, - type: "requires", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("source action not found"); - }); - - it("returns error for nonexistent target action", async () => { - const source = (await sdk.trigger("mem::action-create", { - title: "Source", - })) as { success: boolean; action: Action }; - - const result = (await sdk.trigger("mem::action-edge-create", { - sourceActionId: source.action.id, - targetActionId: "nonexistent", - type: "requires", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("target action not found"); - }); - }); - - describe("mem::action-list", () => { - beforeEach(async () => { - await sdk.trigger("mem::action-create", { - title: "Task A", - status: "pending", - project: "alpha", - tags: ["frontend"], - }); - await new Promise((r) => setTimeout(r, 5)); - await sdk.trigger("mem::action-create", { - title: "Task B", - project: "alpha", - tags: ["backend"], - }); - await new Promise((r) => setTimeout(r, 5)); - await sdk.trigger("mem::action-create", { - title: "Task C", - project: "beta", - tags: ["frontend", "backend"], - }); - }); - - it("returns all actions", async () => { - const result = (await sdk.trigger("mem::action-list", {})) as { - success: boolean; - actions: Action[]; - }; - - expect(result.success).toBe(true); - expect(result.actions.length).toBe(3); - }); - - it("filters by status", async () => { - const all = (await sdk.trigger("mem::action-list", {})) as { - actions: Action[]; - }; - const firstAction = all.actions[0]; - - await sdk.trigger("mem::action-update", { - actionId: firstAction.id, - status: "done", - }); - - const result = (await sdk.trigger("mem::action-list", { - status: "done", - })) as { success: boolean; actions: Action[] }; - - expect(result.success).toBe(true); - expect(result.actions.length).toBe(1); - expect(result.actions[0].status).toBe("done"); - }); - - it("filters by project", async () => { - const result = (await sdk.trigger("mem::action-list", { - project: "alpha", - })) as { success: boolean; actions: Action[] }; - - expect(result.success).toBe(true); - expect(result.actions.length).toBe(2); - expect(result.actions.every((a) => a.project === "alpha")).toBe(true); - }); - - it("filters by tags", async () => { - const result = (await sdk.trigger("mem::action-list", { - tags: ["backend"], - })) as { success: boolean; actions: Action[] }; - - expect(result.success).toBe(true); - expect(result.actions.length).toBe(2); - expect( - result.actions.every((a) => a.tags.includes("backend")), - ).toBe(true); - }); - - it("respects limit", async () => { - const result = (await sdk.trigger("mem::action-list", { - limit: 2, - })) as { success: boolean; actions: Action[] }; - - expect(result.success).toBe(true); - expect(result.actions.length).toBe(2); - }); - }); - - describe("mem::action-get", () => { - it("returns action with edges and children", async () => { - const parent = (await sdk.trigger("mem::action-create", { - title: "Parent", - })) as { success: boolean; action: Action }; - - const child = (await sdk.trigger("mem::action-create", { - title: "Child", - parentId: parent.action.id, - })) as { success: boolean; action: Action }; - - const other = (await sdk.trigger("mem::action-create", { - title: "Other", - })) as { success: boolean; action: Action }; - - await sdk.trigger("mem::action-edge-create", { - sourceActionId: parent.action.id, - targetActionId: other.action.id, - type: "unlocks", - }); - - const result = (await sdk.trigger("mem::action-get", { - actionId: parent.action.id, - })) as { - success: boolean; - action: Action; - edges: ActionEdge[]; - children: Action[]; - }; - - expect(result.success).toBe(true); - expect(result.action.id).toBe(parent.action.id); - expect(result.edges.length).toBe(1); - expect(result.edges[0].type).toBe("unlocks"); - expect(result.children.length).toBe(1); - expect(result.children[0].id).toBe(child.action.id); - }); - - it("returns error for missing actionId", async () => { - const result = (await sdk.trigger("mem::action-get", {})) as { - success: boolean; - error: string; - }; - - expect(result.success).toBe(false); - expect(result.error).toContain("actionId is required"); - }); - - it("returns error for nonexistent action", async () => { - const result = (await sdk.trigger("mem::action-get", { - actionId: "nonexistent", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("action not found"); - }); - }); -}); diff --git a/test/agent-id-scope.test.ts b/test/agent-id-scope.test.ts deleted file mode 100644 index b74e5c74..00000000 --- a/test/agent-id-scope.test.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -// AGENT_ID scope for multi-agent memory isolation. - -describe("loadAgentScope (#554)", () => { - const ORIG = process.env["AGENT_ID"]; - beforeEach(() => { - vi.resetModules(); - delete process.env["AGENT_ID"]; - }); - afterEach(() => { - if (ORIG === undefined) delete process.env["AGENT_ID"]; - else process.env["AGENT_ID"] = ORIG; - }); - - it("returns null when AGENT_ID is unset", async () => { - const { loadAgentScope, getAgentId } = await import("../src/config.js"); - expect(loadAgentScope()).toBeNull(); - expect(getAgentId()).toBeUndefined(); - }); - - it("returns the agentId + scope mode when AGENT_ID is set", async () => { - process.env["AGENT_ID"] = "architect"; - const { loadAgentScope, getAgentId } = await import("../src/config.js"); - expect(loadAgentScope()).toEqual({ agentId: "architect", mode: "shared" }); - expect(getAgentId()).toBe("architect"); - }); - - it("trims whitespace and rejects empty after trim", async () => { - process.env["AGENT_ID"] = " "; - const { loadAgentScope } = await import("../src/config.js"); - expect(loadAgentScope()).toBeNull(); - }); - - it("caps length at 128 chars to keep KV writes well-formed", async () => { - process.env["AGENT_ID"] = "x".repeat(500); - const { getAgentId } = await import("../src/config.js"); - expect(getAgentId()!.length).toBe(128); - }); -}); - -describe("mem::remember stamps agentId on the Memory (#554)", () => { - const ORIG = process.env["AGENT_ID"]; - beforeEach(() => { - vi.resetModules(); - delete process.env["AGENT_ID"]; - }); - afterEach(() => { - if (ORIG === undefined) delete process.env["AGENT_ID"]; - else process.env["AGENT_ID"] = ORIG; - }); - - function mockKV() { - const store = new Map>(); - return { - store, - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string) => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const m = store.get(scope); - return m ? (Array.from(m.values()) as T[]) : []; - }, - }; - } - - function mockSdk() { - const fns = new Map(); - return { - fns, - registerFunction: (idOrOpts: string | { id: string }, fn: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - fns.set(id, fn); - }, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = fns.get(id); - if (fn) return fn(payload); - return null; - }, - }; - } - - it("stamps env AGENT_ID on Memory when no body override", async () => { - process.env["AGENT_ID"] = "developer"; - const { registerRememberFunction } = await import( - "../src/functions/remember.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = (await sdk.trigger("mem::remember", { - content: "prefer async-std over tokio", - type: "preference", - })) as { memory: { id: string; agentId?: string } }; - - expect(result.memory.agentId).toBe("developer"); - }); - - it("body agentId overrides env AGENT_ID", async () => { - process.env["AGENT_ID"] = "architect"; - const { registerRememberFunction } = await import( - "../src/functions/remember.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = (await sdk.trigger("mem::remember", { - content: "use http-cache for github API", - agentId: "reviewer", - })) as { memory: { id: string; agentId?: string } }; - - expect(result.memory.agentId).toBe("reviewer"); - }); - - it("no env, no body → no agentId on Memory (legacy)", async () => { - const { registerRememberFunction } = await import( - "../src/functions/remember.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = (await sdk.trigger("mem::remember", { - content: "legacy unscoped memory", - })) as { memory: { id: string; agentId?: string } }; - - expect(result.memory.agentId).toBeUndefined(); - }); - - it("body agentId is trimmed of leading/trailing whitespace", async () => { - const { registerRememberFunction } = await import( - "../src/functions/remember.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = (await sdk.trigger("mem::remember", { - content: "trim me", - agentId: " architect ", - })) as { memory: { id: string; agentId?: string } }; - - expect(result.memory.agentId).toBe("architect"); - }); - - it("body agentId longer than 128 chars is truncated to 128", async () => { - const { registerRememberFunction } = await import( - "../src/functions/remember.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const long = "x".repeat(500); - const result = (await sdk.trigger("mem::remember", { - content: "cap length", - agentId: long, - })) as { memory: { id: string; agentId?: string } }; - - expect(result.memory.agentId).toBeDefined(); - expect(result.memory.agentId!.length).toBe(128); - expect(result.memory.agentId).toBe("x".repeat(128)); - }); - - it("whitespace-only body agentId falls back to env AGENT_ID", async () => { - process.env["AGENT_ID"] = "developer"; - const { registerRememberFunction } = await import( - "../src/functions/remember.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = (await sdk.trigger("mem::remember", { - content: "fall through to env", - agentId: " ", - })) as { memory: { id: string; agentId?: string } }; - - expect(result.memory.agentId).toBe("developer"); - }); - - it("empty-string body agentId with no env → no agentId stamped", async () => { - const { registerRememberFunction } = await import( - "../src/functions/remember.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = (await sdk.trigger("mem::remember", { - content: "no env no usable body", - agentId: "", - })) as { memory: { id: string; agentId?: string } }; - - expect(result.memory.agentId).toBeUndefined(); - }); -}); - -describe("AGENTMEMORY_AGENT_SCOPE mode (#554)", () => { - const ORIG_ID = process.env["AGENT_ID"]; - const ORIG_MODE = process.env["AGENTMEMORY_AGENT_SCOPE"]; - beforeEach(() => { - vi.resetModules(); - delete process.env["AGENT_ID"]; - delete process.env["AGENTMEMORY_AGENT_SCOPE"]; - }); - afterEach(() => { - if (ORIG_ID === undefined) delete process.env["AGENT_ID"]; - else process.env["AGENT_ID"] = ORIG_ID; - if (ORIG_MODE === undefined) delete process.env["AGENTMEMORY_AGENT_SCOPE"]; - else process.env["AGENTMEMORY_AGENT_SCOPE"] = ORIG_MODE; - }); - - it("defaults to shared mode when AGENT_ID set but scope unset", async () => { - process.env["AGENT_ID"] = "developer"; - const { loadAgentScope, isAgentScopeIsolated } = await import( - "../src/config.js" - ); - expect(loadAgentScope()).toEqual({ - agentId: "developer", - mode: "shared", - }); - expect(isAgentScopeIsolated()).toBe(false); - }); - - it("flips to isolated when AGENTMEMORY_AGENT_SCOPE=isolated", async () => { - process.env["AGENT_ID"] = "developer"; - process.env["AGENTMEMORY_AGENT_SCOPE"] = "isolated"; - const { loadAgentScope, isAgentScopeIsolated } = await import( - "../src/config.js" - ); - expect(loadAgentScope()).toEqual({ - agentId: "developer", - mode: "isolated", - }); - expect(isAgentScopeIsolated()).toBe(true); - }); - - it("isolated requires AGENT_ID to also be set (no scope without id)", async () => { - process.env["AGENTMEMORY_AGENT_SCOPE"] = "isolated"; - const { isAgentScopeIsolated } = await import("../src/config.js"); - expect(isAgentScopeIsolated()).toBe(false); - }); - - it("unknown scope values fall back to shared", async () => { - process.env["AGENT_ID"] = "developer"; - process.env["AGENTMEMORY_AGENT_SCOPE"] = "weird"; - const { isAgentScopeIsolated } = await import("../src/config.js"); - expect(isAgentScopeIsolated()).toBe(false); - }); -}); diff --git a/test/audit.test.ts b/test/audit.test.ts deleted file mode 100644 index 12cb6610..00000000 --- a/test/audit.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { recordAudit, queryAudit } from "../src/functions/audit.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -describe("Audit Functions", () => { - let kv: ReturnType; - - beforeEach(() => { - kv = mockKV(); - }); - - it("recordAudit creates an entry with proper fields", async () => { - const entry = await recordAudit( - kv as never, - "observe", - "mem::compress", - ["obs_1", "obs_2"], - { count: 2 }, - 0.85, - "user-1", - ); - - expect(entry.id).toMatch(/^aud_/); - expect(entry.timestamp).toBeDefined(); - expect(entry.operation).toBe("observe"); - expect(entry.functionId).toBe("mem::compress"); - expect(entry.targetIds).toEqual(["obs_1", "obs_2"]); - expect(entry.details).toEqual({ count: 2 }); - expect(entry.qualityScore).toBe(0.85); - expect(entry.userId).toBe("user-1"); - }); - - it("queryAudit returns entries sorted by timestamp desc", async () => { - await recordAudit(kv as never, "observe", "fn1", ["a"], {}); - await new Promise((r) => setTimeout(r, 10)); - await recordAudit(kv as never, "delete", "fn2", ["b"], {}); - - const entries = await queryAudit(kv as never); - expect(entries.length).toBe(2); - expect( - new Date(entries[0].timestamp).getTime(), - ).toBeGreaterThanOrEqual(new Date(entries[1].timestamp).getTime()); - }); - - it("queryAudit filters by operation", async () => { - await recordAudit(kv as never, "observe", "fn1", [], {}); - await recordAudit(kv as never, "delete", "fn2", [], {}); - await recordAudit(kv as never, "observe", "fn3", [], {}); - - const entries = await queryAudit(kv as never, { operation: "observe" }); - expect(entries.length).toBe(2); - expect(entries.every((e) => e.operation === "observe")).toBe(true); - }); - - it("queryAudit filters by dateFrom/dateTo", async () => { - const early = await recordAudit(kv as never, "observe", "fn1", [], {}); - await new Promise((r) => setTimeout(r, 20)); - const late = await recordAudit(kv as never, "delete", "fn2", [], {}); - - const entries = await queryAudit(kv as never, { - dateFrom: late.timestamp, - }); - expect(entries.length).toBe(1); - expect(entries[0].operation).toBe("delete"); - - const entriesBefore = await queryAudit(kv as never, { - dateTo: early.timestamp, - }); - expect(entriesBefore.length).toBe(1); - expect(entriesBefore[0].operation).toBe("observe"); - }); - - it("queryAudit respects limit", async () => { - for (let i = 0; i < 10; i++) { - await recordAudit(kv as never, "observe", `fn${i}`, [], {}); - } - - const entries = await queryAudit(kv as never, { limit: 3 }); - expect(entries.length).toBe(3); - }); -}); diff --git a/test/auto-compress.test.ts b/test/auto-compress.test.ts deleted file mode 100644 index fdcced8f..00000000 --- a/test/auto-compress.test.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import type { RawObservation } from "../src/types.js"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -function mockKV() { - const store = new Map>(); - return { - store, - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string) => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const m = store.get(scope); - return m ? (Array.from(m.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const fns = new Map(); - const triggered: Array<{ id: string; data: unknown }> = []; - return { - fns, - triggered, - registerFunction: ( - idOrOpts: string | { id: string }, - fn: Function, - _options?: Record, - ) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - fns.set(id, fn); - }, - trigger: async ( - idOrInput: - | string - | { function_id: string; payload: unknown; action?: unknown }, - data?: unknown, - ) => { - const id = - typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = - typeof idOrInput === "string" ? data : idOrInput.payload; - triggered.push({ id, data: payload }); - const fn = fns.get(id); - if (fn) return fn(payload); - return null; - }, - }; -} - -function validPayload(overrides: Partial> = {}) { - return { - sessionId: "ses_test", - hookType: "post_tool_use", - timestamp: new Date().toISOString(), - data: { - tool_name: "Read", - tool_input: { file_path: "src/foo.ts" }, - tool_output: "file contents here", - }, - ...overrides, - }; -} - -describe("mem::observe auto-compress gate (#138)", () => { - beforeEach(() => { - // Reset module cache so observe.js re-imports config.js with the - // fresh AGENTMEMORY_AUTO_COMPRESS env state. Without this, a later - // test that sets the env var can be undermined by cached module - // state from an earlier test (and vice versa). - vi.resetModules(); - delete process.env["AGENTMEMORY_AUTO_COMPRESS"]; - }); - afterEach(() => { - delete process.env["AGENTMEMORY_AUTO_COMPRESS"]; - }); - - it("default (AGENTMEMORY_AUTO_COMPRESS unset): does NOT fire mem::compress", async () => { - const { registerObserveFunction } = await import( - "../src/functions/observe.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - registerObserveFunction(sdk as never, kv as never); - - const result = (await sdk.trigger( - "mem::observe", - validPayload(), - )) as { observationId: string }; - - expect(result.observationId).toBeTruthy(); - const compressCalls = sdk.triggered.filter((t) => t.id === "mem::compress"); - expect(compressCalls).toHaveLength(0); - }); - - it("default: stores a synthetic CompressedObservation with the raw-derived fields", async () => { - const { registerObserveFunction } = await import( - "../src/functions/observe.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - registerObserveFunction(sdk as never, kv as never); - - const payload = validPayload(); - await sdk.trigger("mem::observe", payload); - - const scope = `mem:obs:${payload.sessionId}`; - const stored = kv.store.get(scope); - expect(stored).toBeDefined(); - expect(stored!.size).toBe(1); - const [entry] = Array.from(stored!.values()); - const obs = entry as { - type: string; - title: string; - files: string[]; - confidence: number; - }; - expect(obs.type).toBe("file_read"); - expect(obs.title).toBe("Read"); - expect(obs.files).toContain("src/foo.ts"); - expect(obs.confidence).toBe(0.3); - }); - - it("AGENTMEMORY_AUTO_COMPRESS=true: fires mem::compress exactly once", async () => { - process.env["AGENTMEMORY_AUTO_COMPRESS"] = "true"; - const { registerObserveFunction } = await import( - "../src/functions/observe.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - registerObserveFunction(sdk as never, kv as never); - - await sdk.trigger("mem::observe", validPayload()); - - const compressCalls = sdk.triggered.filter((t) => t.id === "mem::compress"); - expect(compressCalls).toHaveLength(1); - }); - - it("AGENTMEMORY_AUTO_COMPRESS=false explicitly: does NOT fire mem::compress", async () => { - process.env["AGENTMEMORY_AUTO_COMPRESS"] = "false"; - const { registerObserveFunction } = await import( - "../src/functions/observe.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - registerObserveFunction(sdk as never, kv as never); - - await sdk.trigger("mem::observe", validPayload()); - - const compressCalls = sdk.triggered.filter((t) => t.id === "mem::compress"); - expect(compressCalls).toHaveLength(0); - }); -}); - -describe("buildSyntheticCompression", () => { - it("maps common tool names to the right ObservationType", async () => { - const { buildSyntheticCompression } = await import( - "../src/functions/compress-synthetic.js" - ); - const base: RawObservation = { - id: "obs_1", - sessionId: "ses_1", - timestamp: new Date().toISOString(), - hookType: "post_tool_use", - raw: {}, - }; - const cases: Array<[string, string]> = [ - ["Read", "file_read"], - ["Write", "file_write"], - ["Edit", "file_edit"], - ["Bash", "command_run"], - ["Grep", "search"], - ["WebFetch", "web_fetch"], - ["Task", "subagent"], - ["UnknownTool", "other"], - ]; - for (const [name, expectedType] of cases) { - const synthetic = ( - await import("../src/functions/compress-synthetic.js") - ).buildSyntheticCompression({ ...base, toolName: name }); - expect(synthetic.type, `${name} -> ${expectedType}`).toBe(expectedType); - } - // silence unused warning — buildSyntheticCompression is used above - expect(typeof buildSyntheticCompression).toBe("function"); - }); - - it("extracts file paths from tool_input into the files array", async () => { - const { buildSyntheticCompression } = await import( - "../src/functions/compress-synthetic.js" - ); - const synth = buildSyntheticCompression({ - id: "obs_2", - sessionId: "ses_1", - timestamp: new Date().toISOString(), - hookType: "post_tool_use", - toolName: "Edit", - toolInput: { file_path: "/app/src/bar.ts", pattern: "foo" }, - raw: {}, - }); - expect(synth.files).toContain("/app/src/bar.ts"); - expect(synth.files).toContain("foo"); - expect(synth.type).toBe("file_edit"); - }); - - it("truncates long narratives so it can't blow up the index", async () => { - const { buildSyntheticCompression } = await import( - "../src/functions/compress-synthetic.js" - ); - const longInput = "x".repeat(2000); - const synth = buildSyntheticCompression({ - id: "obs_3", - sessionId: "ses_1", - timestamp: new Date().toISOString(), - hookType: "post_tool_use", - toolName: "Bash", - toolInput: { command: longInput }, - toolOutput: longInput, - raw: {}, - }); - expect(synth.narrative.length).toBeLessThanOrEqual(400); - }); - - it("maps post_tool_failure to the error type even with no tool name", async () => { - const { buildSyntheticCompression } = await import( - "../src/functions/compress-synthetic.js" - ); - const synth = buildSyntheticCompression({ - id: "obs_4", - sessionId: "ses_1", - timestamp: new Date().toISOString(), - hookType: "post_tool_failure", - raw: {}, - }); - expect(synth.type).toBe("error"); - }); -}); diff --git a/test/auto-forget.test.ts b/test/auto-forget.test.ts deleted file mode 100644 index 0f104eb3..00000000 --- a/test/auto-forget.test.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerAutoForgetFunction } from "../src/functions/auto-forget.js"; -import { - getSearchIndex, - setIndexPersistence, -} from "../src/functions/search.js"; -import { memoryToObservation } from "../src/state/memory-utils.js"; -import type { Memory, CompressedObservation, Session } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeMemory(overrides: Partial = {}): Memory { - return { - id: "mem_1", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "pattern", - title: "Test memory", - content: "This is a test memory with enough words for comparison", - concepts: ["test"], - files: [], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, - ...overrides, - }; -} - -describe("Auto-Forget Function", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerAutoForgetFunction(sdk as never, kv as never); - }); - - it("detects and deletes TTL-expired memories", async () => { - const expired = makeMemory({ - id: "mem_expired", - forgetAfter: "2020-01-01T00:00:00Z", - }); - await kv.set("mem:memories", "mem_expired", expired); - - const result = (await sdk.trigger("mem::auto-forget", {})) as { - ttlExpired: string[]; - }; - - expect(result.ttlExpired).toContain("mem_expired"); - const deleted = await kv.get("mem:memories", "mem_expired"); - expect(deleted).toBeNull(); - }); - - it("detects contradiction between very similar memories", async () => { - const mem1 = makeMemory({ - id: "mem_1", - content: "Use React hooks for state management in all components", - createdAt: "2026-01-01T00:00:00Z", - }); - const mem2 = makeMemory({ - id: "mem_2", - content: "Use React hooks for state management in all components", - createdAt: "2026-02-01T00:00:00Z", - }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - - const result = (await sdk.trigger("mem::auto-forget", {})) as { - contradictions: Array<{ - memoryA: string; - memoryB: string; - similarity: number; - }>; - }; - - expect(result.contradictions.length).toBe(1); - const older = await kv.get("mem:memories", "mem_1"); - expect(older!.isLatest).toBe(false); - }); - - it("evicts low-value old observations", async () => { - const session: Session = { - id: "ses_1", - project: "my-project", - cwd: "/tmp", - startedAt: "2025-01-01T00:00:00Z", - status: "completed", - observationCount: 1, - }; - await kv.set("mem:sessions", "ses_1", session); - - const oldLowObs: CompressedObservation = { - id: "obs_old", - sessionId: "ses_1", - timestamp: "2025-01-01T00:00:00Z", - type: "other", - title: "trivial event", - facts: [], - narrative: "nothing important", - concepts: [], - files: [], - importance: 1, - }; - await kv.set("mem:obs:ses_1", "obs_old", oldLowObs); - - const result = (await sdk.trigger("mem::auto-forget", {})) as { - lowValueObs: string[]; - }; - - expect(result.lowValueObs).toContain("obs_old"); - }); - - it("dryRun mode identifies but does not delete anything", async () => { - const expired = makeMemory({ - id: "mem_expired", - forgetAfter: "2020-01-01T00:00:00Z", - }); - await kv.set("mem:memories", "mem_expired", expired); - - const result = (await sdk.trigger("mem::auto-forget", { dryRun: true })) as { - ttlExpired: string[]; - dryRun: boolean; - }; - - expect(result.dryRun).toBe(true); - expect(result.ttlExpired).toContain("mem_expired"); - - const stillExists = await kv.get("mem:memories", "mem_expired"); - expect(stillExists).not.toBeNull(); - }); - - describe("search-index cleanup", () => { - beforeEach(() => { - getSearchIndex().clear(); - setIndexPersistence(null); - }); - - afterEach(() => { - setIndexPersistence(null); - }); - - it("removes TTL-expired memories from the BM25 index and flushes persistence", async () => { - const persistence = { scheduleSave: vi.fn(), save: vi.fn(async () => {}) }; - setIndexPersistence(persistence); - - const expired = makeMemory({ - id: "mem_expired", - forgetAfter: "2020-01-01T00:00:00Z", - }); - await kv.set("mem:memories", "mem_expired", expired); - getSearchIndex().add(memoryToObservation(expired)); - expect(getSearchIndex().has("mem_expired")).toBe(true); - - await sdk.trigger("mem::auto-forget", {}); - - expect(getSearchIndex().has("mem_expired")).toBe(false); - expect(persistence.save).toHaveBeenCalled(); - }); - - it("removes evicted low-value observations from the BM25 index", async () => { - const session: Session = { - id: "ses_1", - project: "my-project", - cwd: "/tmp", - startedAt: "2025-01-01T00:00:00Z", - status: "completed", - observationCount: 1, - }; - await kv.set("mem:sessions", "ses_1", session); - - const oldLowObs: CompressedObservation = { - id: "obs_old", - sessionId: "ses_1", - timestamp: "2025-01-01T00:00:00Z", - type: "other", - title: "trivial event", - facts: [], - narrative: "nothing important", - concepts: [], - files: [], - importance: 1, - }; - await kv.set("mem:obs:ses_1", "obs_old", oldLowObs); - getSearchIndex().add(oldLowObs); - expect(getSearchIndex().has("obs_old")).toBe(true); - - await sdk.trigger("mem::auto-forget", {}); - - expect(getSearchIndex().has("obs_old")).toBe(false); - }); - - it("does not flush persistence on dryRun", async () => { - const persistence = { scheduleSave: vi.fn(), save: vi.fn(async () => {}) }; - setIndexPersistence(persistence); - - const expired = makeMemory({ - id: "mem_expired", - forgetAfter: "2020-01-01T00:00:00Z", - }); - await kv.set("mem:memories", "mem_expired", expired); - getSearchIndex().add(memoryToObservation(expired)); - - await sdk.trigger("mem::auto-forget", { dryRun: true }); - - // dryRun must not mutate the index or write to disk. - expect(getSearchIndex().has("mem_expired")).toBe(true); - expect(persistence.save).not.toHaveBeenCalled(); - }); - }); - - it("does not flag non-similar memories as contradictions", async () => { - const mem1 = makeMemory({ - id: "mem_1", - content: "We use TypeScript with strict mode enabled for all backend services", - }); - const mem2 = makeMemory({ - id: "mem_2", - content: "The deployment pipeline runs integration tests before merging to main", - }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - - const result = (await sdk.trigger("mem::auto-forget", {})) as { - contradictions: unknown[]; - }; - - expect(result.contradictions.length).toBe(0); - }); -}); diff --git a/test/browser-capture-extract.test.ts b/test/browser-capture-extract.test.ts deleted file mode 100644 index 174636a3..00000000 --- a/test/browser-capture-extract.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { mockKV } from "./helpers/mocks.js"; -import { KV } from "../src/state/schema.js"; -import { buildTurnActionDrafts } from "../src/functions/action-candidates.js"; - -// PLAN-002 STEP-03: browser-capture turns -> action drafts, via the shared -// buildTurnActionDrafts used by both api::review-create and the viewer-server -// review fallback. Dedup keeps a browser session from minting duplicate todos. -const baseOpts = { - now: new Date().toISOString(), - source: "browser-sync" as const, - page: { host: "chat.example.com" }, - basePayload: { viewerFallback: true }, -}; - -describe("buildTurnActionDrafts (browser capture -> action drafts)", () => { - it("builds a pending action draft from an actionable browser turn", async () => { - const kv = mockKV(); - const drafts = await buildTurnActionDrafts(kv as never, { - ...baseOpts, - turns: [{ role: "user", text: "下一步请修复登录页的报错。" }], - }); - expect(drafts.length).toBeGreaterThanOrEqual(1); - expect(drafts[0].kind).toBe("action"); - expect(drafts[0].source).toBe("browser-sync"); - expect(drafts[0].status).toBe("pending"); - }); - - it("returns [] when there are no turns", async () => { - const kv = mockKV(); - expect(await buildTurnActionDrafts(kv as never, { ...baseOpts, turns: [] })).toEqual([]); - }); - - it("dedups identical turns within one extraction", async () => { - const kv = mockKV(); - const turn = { role: "user", text: "下一步请修复登录页的报错。" }; - const drafts = await buildTurnActionDrafts(kv as never, { ...baseOpts, turns: [turn, turn] }); - expect(drafts.length).toBe(1); - }); - - it("does not re-emit a draft whose candidate is already in the review queue", async () => { - const kv = mockKV(); - const turns = [{ role: "user", text: "下一步请修复登录页的报错。" }]; - const first = await buildTurnActionDrafts(kv as never, { ...baseOpts, turns }); - expect(first.length).toBeGreaterThanOrEqual(1); - for (const d of first) await kv.set(KV.reviewQueue, d.id, d); - const second = await buildTurnActionDrafts(kv as never, { ...baseOpts, turns }); - expect(second).toEqual([]); - }); -}); diff --git a/test/cascade.test.ts b/test/cascade.test.ts deleted file mode 100644 index 8462abc7..00000000 --- a/test/cascade.test.ts +++ /dev/null @@ -1,275 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerCascadeFunction } from "../src/functions/cascade.js"; -import type { Memory, GraphNode, GraphEdge } from "../src/types.js"; -import { mockKV, mockSdk } from "./helpers/mocks.js"; - -describe("Cascade Update Function", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - vi.clearAllMocks(); - registerCascadeFunction(sdk as never, kv as never); - }); - - it("returns error when supersededMemoryId is missing", async () => { - const result = (await sdk.trigger("mem::cascade-update", {})) as { - success: boolean; - error: string; - }; - expect(result.success).toBe(false); - expect(result.error).toBe("supersededMemoryId is required"); - }); - - it("returns error for non-existent memory", async () => { - const result = (await sdk.trigger("mem::cascade-update", { - supersededMemoryId: "mem_missing", - })) as { success: boolean; error: string }; - expect(result.success).toBe(false); - expect(result.error).toBe("superseded memory not found"); - }); - - it("flags graph nodes referencing superseded observation IDs", async () => { - const memory: Memory = { - id: "mem_old", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "fact", - title: "Old fact", - content: "Old content", - concepts: ["react"], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: false, - sourceObservationIds: ["obs_a", "obs_b"], - }; - await kv.set("mem:memories", "mem_old", memory); - - const node: GraphNode = { - id: "node_1", - type: "concept", - name: "react", - properties: {}, - sourceObservationIds: ["obs_a"], - createdAt: "2026-03-01T00:00:00Z", - }; - await kv.set("mem:graph:nodes", "node_1", node); - - const unrelatedNode: GraphNode = { - id: "node_2", - type: "file", - name: "index.ts", - properties: {}, - sourceObservationIds: ["obs_c"], - createdAt: "2026-03-01T00:00:00Z", - }; - await kv.set("mem:graph:nodes", "node_2", unrelatedNode); - - const result = (await sdk.trigger("mem::cascade-update", { - supersededMemoryId: "mem_old", - })) as { success: boolean; flagged: { nodes: number; edges: number } }; - - expect(result.success).toBe(true); - expect(result.flagged.nodes).toBe(1); - - const updated = await kv.get("mem:graph:nodes", "node_1"); - expect(updated!.stale).toBe(true); - - const unchanged = await kv.get("mem:graph:nodes", "node_2"); - expect(unchanged!.stale).toBeUndefined(); - }); - - it("flags graph edges referencing superseded observation IDs", async () => { - const memory: Memory = { - id: "mem_old2", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "pattern", - title: "Old pattern", - content: "Old pattern content", - concepts: ["testing"], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: false, - sourceObservationIds: ["obs_x"], - }; - await kv.set("mem:memories", "mem_old2", memory); - - const edge: GraphEdge = { - id: "edge_1", - type: "uses", - sourceNodeId: "node_a", - targetNodeId: "node_b", - weight: 1, - sourceObservationIds: ["obs_x", "obs_y"], - createdAt: "2026-03-01T00:00:00Z", - }; - await kv.set("mem:graph:edges", "edge_1", edge); - - const result = (await sdk.trigger("mem::cascade-update", { - supersededMemoryId: "mem_old2", - })) as { success: boolean; flagged: { edges: number } }; - - expect(result.success).toBe(true); - expect(result.flagged.edges).toBe(1); - - const updated = await kv.get("mem:graph:edges", "edge_1"); - expect(updated!.stale).toBe(true); - }); - - it("counts sibling memories sharing 2+ concepts", async () => { - const superseded: Memory = { - id: "mem_superseded", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "architecture", - title: "React architecture", - content: "Old arch", - concepts: ["react", "frontend", "typescript"], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: false, - }; - await kv.set("mem:memories", "mem_superseded", superseded); - - const sibling: Memory = { - id: "mem_sibling", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "pattern", - title: "React patterns", - content: "Sibling memory sharing concepts", - concepts: ["react", "typescript"], - files: [], - sessionIds: [], - strength: 6, - version: 1, - isLatest: true, - }; - await kv.set("mem:memories", "mem_sibling", sibling); - - const unrelated: Memory = { - id: "mem_unrelated", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "fact", - title: "Python setup", - content: "Unrelated memory", - concepts: ["python", "backend"], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: true, - }; - await kv.set("mem:memories", "mem_unrelated", unrelated); - - const result = (await sdk.trigger("mem::cascade-update", { - supersededMemoryId: "mem_superseded", - })) as { success: boolean; flagged: { siblingMemories: number }; total: number }; - - expect(result.success).toBe(true); - expect(result.flagged.siblingMemories).toBe(1); - expect(result.total).toBeGreaterThanOrEqual(1); - }); - - it("skips already stale nodes", async () => { - const memory: Memory = { - id: "mem_skip", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "fact", - title: "Skip test", - content: "Content", - concepts: [], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: false, - sourceObservationIds: ["obs_s"], - }; - await kv.set("mem:memories", "mem_skip", memory); - - const node: GraphNode = { - id: "node_stale", - type: "concept", - name: "already stale", - properties: {}, - sourceObservationIds: ["obs_s"], - createdAt: "2026-03-01T00:00:00Z", - stale: true, - }; - await kv.set("mem:graph:nodes", "node_stale", node); - - const result = (await sdk.trigger("mem::cascade-update", { - supersededMemoryId: "mem_skip", - })) as { success: boolean; flagged: { nodes: number } }; - - expect(result.success).toBe(true); - expect(result.flagged.nodes).toBe(0); - }); - - it("does not flag siblings when fewer than 2 shared concepts", async () => { - const memory: Memory = { - id: "mem_one_concept", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "fact", - title: "One concept", - content: "Content", - concepts: ["react"], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: false, - }; - await kv.set("mem:memories", "mem_one_concept", memory); - - const result = (await sdk.trigger("mem::cascade-update", { - supersededMemoryId: "mem_one_concept", - })) as { success: boolean; flagged: { siblingMemories: number } }; - - expect(result.success).toBe(true); - expect(result.flagged.siblingMemories).toBe(0); - }); - - it("returns zero counts when no sourceObservationIds and < 2 concepts", async () => { - const memory: Memory = { - id: "mem_empty", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "fact", - title: "Empty refs", - content: "No references", - concepts: [], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: false, - }; - await kv.set("mem:memories", "mem_empty", memory); - - const result = (await sdk.trigger("mem::cascade-update", { - supersededMemoryId: "mem_empty", - })) as { success: boolean; total: number }; - - expect(result.success).toBe(true); - expect(result.total).toBe(0); - }); -}); diff --git a/test/checkpoints.test.ts b/test/checkpoints.test.ts deleted file mode 100644 index 4c6af904..00000000 --- a/test/checkpoints.test.ts +++ /dev/null @@ -1,494 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerCheckpointsFunction } from "../src/functions/checkpoints.js"; -import type { Action, ActionEdge, Checkpoint } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeAction( - id: string, - status: Action["status"] = "blocked", -): Action { - return { - id, - title: `Action ${id}`, - description: `Description for ${id}`, - status, - priority: 5, - createdAt: "2026-02-01T00:00:00Z", - updatedAt: "2026-02-01T00:00:00Z", - createdBy: "agent-setup", - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - }; -} - -describe("Checkpoint Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(async () => { - sdk = mockSdk(); - kv = mockKV(); - registerCheckpointsFunction(sdk as never, kv as never); - }); - - describe("mem::checkpoint-create", () => { - it("creates a checkpoint with valid name", async () => { - const result = (await sdk.trigger("mem::checkpoint-create", { - name: "CI Build", - description: "Wait for CI to pass", - type: "ci", - })) as { success: boolean; checkpoint: Checkpoint }; - - expect(result.success).toBe(true); - expect(result.checkpoint.name).toBe("CI Build"); - expect(result.checkpoint.description).toBe("Wait for CI to pass"); - expect(result.checkpoint.status).toBe("pending"); - expect(result.checkpoint.type).toBe("ci"); - expect(result.checkpoint.id).toMatch(/^ckpt_/); - }); - - it("returns error when name is missing", async () => { - const result = (await sdk.trigger("mem::checkpoint-create", { - name: "", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("name is required"); - }); - - it("defaults type to external when not specified", async () => { - const result = (await sdk.trigger("mem::checkpoint-create", { - name: "External Gate", - })) as { success: boolean; checkpoint: Checkpoint }; - - expect(result.success).toBe(true); - expect(result.checkpoint.type).toBe("external"); - }); - - it("sets expiresAt when expiresInMs is provided", async () => { - const before = Date.now(); - const result = (await sdk.trigger("mem::checkpoint-create", { - name: "Timed Gate", - expiresInMs: 60000, - })) as { success: boolean; checkpoint: Checkpoint }; - - expect(result.success).toBe(true); - expect(result.checkpoint.expiresAt).toBeDefined(); - const expiresAt = new Date(result.checkpoint.expiresAt!).getTime(); - expect(expiresAt).toBeGreaterThanOrEqual(before + 60000); - }); - - it("creates action edges for linkedActionIds", async () => { - await kv.set("mem:actions", "act_1", makeAction("act_1")); - await kv.set("mem:actions", "act_2", makeAction("act_2")); - - const result = (await sdk.trigger("mem::checkpoint-create", { - name: "Deployment Gate", - type: "deploy", - linkedActionIds: ["act_1", "act_2"], - })) as { success: boolean; checkpoint: Checkpoint }; - - expect(result.success).toBe(true); - expect(result.checkpoint.linkedActionIds).toEqual(["act_1", "act_2"]); - - const edges = await kv.list("mem:action-edges"); - expect(edges.length).toBe(2); - expect(edges[0].type).toBe("gated_by"); - expect(edges[0].targetActionId).toBe(result.checkpoint.id); - expect(edges.map((e) => e.sourceActionId).sort()).toEqual(["act_1", "act_2"]); - }); - - it("creates no edges when linkedActionIds is empty", async () => { - await sdk.trigger("mem::checkpoint-create", { - name: "No Links", - linkedActionIds: [], - }); - - const edges = await kv.list("mem:action-edges"); - expect(edges.length).toBe(0); - }); - }); - - describe("mem::checkpoint-resolve", () => { - it("resolves a pending checkpoint to passed", async () => { - const created = (await sdk.trigger("mem::checkpoint-create", { - name: "CI Gate", - type: "ci", - })) as { success: boolean; checkpoint: Checkpoint }; - - const result = (await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: created.checkpoint.id, - status: "passed", - resolvedBy: "ci-bot", - result: { buildId: 123 }, - })) as { success: boolean; checkpoint: Checkpoint; unblockedCount: number }; - - expect(result.success).toBe(true); - expect(result.checkpoint.status).toBe("passed"); - expect(result.checkpoint.resolvedBy).toBe("ci-bot"); - expect(result.checkpoint.resolvedAt).toBeDefined(); - expect(result.checkpoint.result).toEqual({ buildId: 123 }); - }); - - it("resolves a pending checkpoint to failed", async () => { - const created = (await sdk.trigger("mem::checkpoint-create", { - name: "Approval Gate", - type: "approval", - })) as { success: boolean; checkpoint: Checkpoint }; - - const result = (await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: created.checkpoint.id, - status: "failed", - resolvedBy: "reviewer", - })) as { success: boolean; checkpoint: Checkpoint }; - - expect(result.success).toBe(true); - expect(result.checkpoint.status).toBe("failed"); - }); - - it("returns error when checkpoint is already resolved", async () => { - const created = (await sdk.trigger("mem::checkpoint-create", { - name: "Already Done", - })) as { success: boolean; checkpoint: Checkpoint }; - - await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: created.checkpoint.id, - status: "passed", - }); - - const result = (await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: created.checkpoint.id, - status: "failed", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("checkpoint already passed"); - }); - - it("returns error for nonexistent checkpoint", async () => { - const result = (await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: "ckpt_nonexistent", - status: "passed", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("checkpoint not found"); - }); - - it("returns error when checkpointId or status is missing", async () => { - const result = (await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: "", - status: "passed", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("checkpointId and status are required"); - }); - - it("unblocks gated actions when all checkpoints pass", async () => { - await kv.set("mem:actions", "act_1", makeAction("act_1", "blocked")); - - const cp1 = (await sdk.trigger("mem::checkpoint-create", { - name: "Gate 1", - type: "ci", - linkedActionIds: ["act_1"], - })) as { success: boolean; checkpoint: Checkpoint }; - - const cp2 = (await sdk.trigger("mem::checkpoint-create", { - name: "Gate 2", - type: "approval", - linkedActionIds: ["act_1"], - })) as { success: boolean; checkpoint: Checkpoint }; - - await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: cp1.checkpoint.id, - status: "passed", - }); - - const actionAfterFirst = await kv.get("mem:actions", "act_1"); - expect(actionAfterFirst!.status).toBe("blocked"); - - const result = (await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: cp2.checkpoint.id, - status: "passed", - })) as { success: boolean; unblockedCount: number }; - - expect(result.success).toBe(true); - expect(result.unblockedCount).toBe(1); - - const action = await kv.get("mem:actions", "act_1"); - expect(action!.status).toBe("pending"); - }); - - it("does not unblock actions when checkpoint fails", async () => { - await kv.set("mem:actions", "act_1", makeAction("act_1", "blocked")); - - const cp = (await sdk.trigger("mem::checkpoint-create", { - name: "Failing Gate", - linkedActionIds: ["act_1"], - })) as { success: boolean; checkpoint: Checkpoint }; - - const result = (await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: cp.checkpoint.id, - status: "failed", - })) as { success: boolean; unblockedCount: number }; - - expect(result.success).toBe(true); - expect(result.unblockedCount).toBe(0); - - const action = await kv.get("mem:actions", "act_1"); - expect(action!.status).toBe("blocked"); - }); - - it("does not unblock actions that are not in blocked status", async () => { - await kv.set("mem:actions", "act_1", makeAction("act_1", "active")); - - const cp = (await sdk.trigger("mem::checkpoint-create", { - name: "Gate for non-blocked", - linkedActionIds: ["act_1"], - })) as { success: boolean; checkpoint: Checkpoint }; - - const result = (await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: cp.checkpoint.id, - status: "passed", - })) as { success: boolean; unblockedCount: number }; - - expect(result.success).toBe(true); - expect(result.unblockedCount).toBe(0); - }); - }); - - describe("mem::checkpoint-list", () => { - beforeEach(async () => { - await sdk.trigger("mem::checkpoint-create", { - name: "CI Check", - type: "ci", - }); - await sdk.trigger("mem::checkpoint-create", { - name: "Approval Check", - type: "approval", - }); - await sdk.trigger("mem::checkpoint-create", { - name: "Deploy Check", - type: "deploy", - }); - }); - - it("lists all checkpoints when no filters applied", async () => { - const result = (await sdk.trigger("mem::checkpoint-list", {})) as { - success: boolean; - checkpoints: Checkpoint[]; - }; - - expect(result.success).toBe(true); - expect(result.checkpoints.length).toBe(3); - }); - - it("filters checkpoints by status", async () => { - const all = (await sdk.trigger("mem::checkpoint-list", {})) as { - checkpoints: Checkpoint[]; - }; - const firstId = all.checkpoints[0].id; - - await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: firstId, - status: "passed", - }); - - const pending = (await sdk.trigger("mem::checkpoint-list", { - status: "pending", - })) as { success: boolean; checkpoints: Checkpoint[] }; - - expect(pending.success).toBe(true); - expect(pending.checkpoints.length).toBe(2); - expect(pending.checkpoints.every((c) => c.status === "pending")).toBe(true); - - const passed = (await sdk.trigger("mem::checkpoint-list", { - status: "passed", - })) as { success: boolean; checkpoints: Checkpoint[] }; - - expect(passed.checkpoints.length).toBe(1); - expect(passed.checkpoints[0].status).toBe("passed"); - }); - - it("filters checkpoints by type", async () => { - const result = (await sdk.trigger("mem::checkpoint-list", { - type: "ci", - })) as { success: boolean; checkpoints: Checkpoint[] }; - - expect(result.success).toBe(true); - expect(result.checkpoints.length).toBe(1); - expect(result.checkpoints[0].type).toBe("ci"); - expect(result.checkpoints[0].name).toBe("CI Check"); - }); - - it("returns empty list when no checkpoints match filter", async () => { - const result = (await sdk.trigger("mem::checkpoint-list", { - type: "external", - })) as { success: boolean; checkpoints: Checkpoint[] }; - - expect(result.success).toBe(true); - expect(result.checkpoints.length).toBe(0); - }); - - it("sorts checkpoints by createdAt descending", async () => { - const result = (await sdk.trigger("mem::checkpoint-list", {})) as { - success: boolean; - checkpoints: Checkpoint[]; - }; - - for (let i = 0; i < result.checkpoints.length - 1; i++) { - const current = new Date(result.checkpoints[i].createdAt).getTime(); - const next = new Date(result.checkpoints[i + 1].createdAt).getTime(); - expect(current).toBeGreaterThanOrEqual(next); - } - }); - }); - - describe("mem::checkpoint-expire", () => { - it("expires pending checkpoints past their expiresAt", async () => { - const created = (await sdk.trigger("mem::checkpoint-create", { - name: "Expiring Gate", - expiresInMs: 1, - })) as { success: boolean; checkpoint: Checkpoint }; - - created.checkpoint.expiresAt = new Date(Date.now() - 60000).toISOString(); - await kv.set("mem:checkpoints", created.checkpoint.id, created.checkpoint); - - const result = (await sdk.trigger("mem::checkpoint-expire", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(1); - - const cp = await kv.get("mem:checkpoints", created.checkpoint.id); - expect(cp!.status).toBe("expired"); - expect(cp!.resolvedAt).toBeDefined(); - }); - - it("does not expire non-pending checkpoints", async () => { - const created = (await sdk.trigger("mem::checkpoint-create", { - name: "Already Passed", - expiresInMs: 1, - })) as { success: boolean; checkpoint: Checkpoint }; - - await sdk.trigger("mem::checkpoint-resolve", { - checkpointId: created.checkpoint.id, - status: "passed", - }); - - const cp = await kv.get("mem:checkpoints", created.checkpoint.id); - cp!.expiresAt = new Date(Date.now() - 60000).toISOString(); - await kv.set("mem:checkpoints", created.checkpoint.id, cp); - - const result = (await sdk.trigger("mem::checkpoint-expire", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(0); - }); - - it("does not expire checkpoints without expiresAt", async () => { - await sdk.trigger("mem::checkpoint-create", { - name: "No Expiry", - }); - - const result = (await sdk.trigger("mem::checkpoint-expire", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(0); - }); - - it("does not expire checkpoints whose expiresAt is in the future", async () => { - await sdk.trigger("mem::checkpoint-create", { - name: "Future Gate", - expiresInMs: 3600000, - }); - - const result = (await sdk.trigger("mem::checkpoint-expire", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(0); - }); - - it("handles multiple expired checkpoints", async () => { - const cp1 = (await sdk.trigger("mem::checkpoint-create", { - name: "Expired 1", - expiresInMs: 1, - })) as { success: boolean; checkpoint: Checkpoint }; - - const cp2 = (await sdk.trigger("mem::checkpoint-create", { - name: "Expired 2", - expiresInMs: 1, - })) as { success: boolean; checkpoint: Checkpoint }; - - cp1.checkpoint.expiresAt = new Date(Date.now() - 60000).toISOString(); - await kv.set("mem:checkpoints", cp1.checkpoint.id, cp1.checkpoint); - - cp2.checkpoint.expiresAt = new Date(Date.now() - 30000).toISOString(); - await kv.set("mem:checkpoints", cp2.checkpoint.id, cp2.checkpoint); - - const result = (await sdk.trigger("mem::checkpoint-expire", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(2); - }); - }); -}); diff --git a/test/circuit-breaker.test.ts b/test/circuit-breaker.test.ts deleted file mode 100644 index a4e32a30..00000000 --- a/test/circuit-breaker.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { CircuitBreaker } from "../src/providers/circuit-breaker.js"; - -describe("CircuitBreaker", () => { - beforeEach(() => { - vi.useFakeTimers(); - }); - - afterEach(() => { - vi.useRealTimers(); - }); - - it("starts in closed state", () => { - const cb = new CircuitBreaker(); - expect(cb.getState().state).toBe("closed"); - expect(cb.isAllowed).toBe(true); - }); - - it("stays closed after fewer than 3 failures", () => { - const cb = new CircuitBreaker(); - cb.recordFailure(); - cb.recordFailure(); - expect(cb.getState().state).toBe("closed"); - expect(cb.isAllowed).toBe(true); - }); - - it("opens after 3 failures within the window", () => { - const cb = new CircuitBreaker(); - cb.recordFailure(); - cb.recordFailure(); - cb.recordFailure(); - expect(cb.getState().state).toBe("open"); - expect(cb.isAllowed).toBe(false); - }); - - it("resets failure count when failures are outside the window", () => { - const cb = new CircuitBreaker(); - cb.recordFailure(); - cb.recordFailure(); - vi.advanceTimersByTime(61_000); - cb.recordFailure(); - expect(cb.getState().state).toBe("closed"); - expect(cb.getState().failures).toBe(1); - }); - - it("transitions to half-open after recovery timeout", () => { - const cb = new CircuitBreaker(); - cb.recordFailure(); - cb.recordFailure(); - cb.recordFailure(); - expect(cb.isAllowed).toBe(false); - vi.advanceTimersByTime(30_000); - expect(cb.isAllowed).toBe(true); - expect(cb.getState().state).toBe("half-open"); - }); - - it("closes on success in half-open state", () => { - const cb = new CircuitBreaker(); - cb.recordFailure(); - cb.recordFailure(); - cb.recordFailure(); - vi.advanceTimersByTime(30_000); - cb.isAllowed; - cb.recordSuccess(); - expect(cb.getState().state).toBe("closed"); - expect(cb.getState().failures).toBe(0); - expect(cb.getState().lastFailureAt).toBeNull(); - }); - - it("reopens on failure in half-open state", () => { - const cb = new CircuitBreaker(); - cb.recordFailure(); - cb.recordFailure(); - cb.recordFailure(); - vi.advanceTimersByTime(30_000); - cb.isAllowed; - cb.recordFailure(); - expect(cb.getState().state).toBe("open"); - }); - - it("records lastFailureAt timestamp", () => { - const cb = new CircuitBreaker(); - vi.setSystemTime(new Date("2026-01-15T10:00:00Z")); - cb.recordFailure(); - expect(cb.getState().lastFailureAt).toBe( - new Date("2026-01-15T10:00:00Z").getTime(), - ); - }); - - it("records openedAt timestamp", () => { - const cb = new CircuitBreaker(); - vi.setSystemTime(new Date("2026-01-15T10:00:00Z")); - cb.recordFailure(); - cb.recordFailure(); - cb.recordFailure(); - expect(cb.getState().openedAt).toBe( - new Date("2026-01-15T10:00:00Z").getTime(), - ); - }); - - it("success in closed state is a no-op", () => { - const cb = new CircuitBreaker(); - cb.recordSuccess(); - expect(cb.getState().state).toBe("closed"); - expect(cb.getState().failures).toBe(0); - }); -}); diff --git a/test/claude-bridge-path.test.ts b/test/claude-bridge-path.test.ts deleted file mode 100644 index fa3573d3..00000000 --- a/test/claude-bridge-path.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { homedir } from "node:os"; -import { join } from "node:path"; -import { loadClaudeBridgeConfig } from "../src/config.js"; - -// bridge path must match Claude Code's slug convention exactly: -// ~/.claude/projects//MEMORY.md -// where replaces every / and \ with - and KEEPS any leading -. -// The previous code stripped the leading - and added a /memory/ -// subdirectory; the bridge then wrote a file Claude Code never read. -describe("loadClaudeBridgeConfig path (#625)", () => { - const ORIG_ENV = { ...process.env }; - beforeEach(() => { - delete process.env["CLAUDE_MEMORY_BRIDGE"]; - delete process.env["CLAUDE_PROJECT_PATH"]; - delete process.env["CLAUDE_MEMORY_LINE_BUDGET"]; - }); - afterEach(() => { - process.env = { ...ORIG_ENV }; - }); - - it("preserves leading - on POSIX absolute paths", () => { - process.env["CLAUDE_MEMORY_BRIDGE"] = "true"; - process.env["CLAUDE_PROJECT_PATH"] = "/home/user/repos/my-project"; - const cfg = loadClaudeBridgeConfig(); - expect(cfg.memoryFilePath).toBe( - join(homedir(), ".claude", "projects", "-home-user-repos-my-project", "MEMORY.md"), - ); - }); - - it("writes MEMORY.md directly under the slug dir, no memory/ subdir", () => { - process.env["CLAUDE_MEMORY_BRIDGE"] = "true"; - process.env["CLAUDE_PROJECT_PATH"] = "/Users/x/agentmemory"; - const cfg = loadClaudeBridgeConfig(); - expect(cfg.memoryFilePath).not.toMatch(/[/\\]memory[/\\]MEMORY\.md$/); - expect(cfg.memoryFilePath).toMatch(/-Users-x-agentmemory[/\\]MEMORY\.md$/); - }); - - it("returns empty memoryFilePath when bridge disabled", () => { - const cfg = loadClaudeBridgeConfig(); - expect(cfg.enabled).toBe(false); - expect(cfg.memoryFilePath).toBe(""); - }); - - it("returns empty memoryFilePath when project path unset", () => { - process.env["CLAUDE_MEMORY_BRIDGE"] = "true"; - const cfg = loadClaudeBridgeConfig(); - expect(cfg.enabled).toBe(true); - expect(cfg.memoryFilePath).toBe(""); - }); - - it("handles Windows-style backslash paths by swapping to -", () => { - process.env["CLAUDE_MEMORY_BRIDGE"] = "true"; - process.env["CLAUDE_PROJECT_PATH"] = "C:\\Users\\x\\project"; - const cfg = loadClaudeBridgeConfig(); - expect(cfg.memoryFilePath).toMatch(/C:-Users-x-project[/\\]MEMORY\.md$/); - }); -}); diff --git a/test/claude-bridge.test.ts b/test/claude-bridge.test.ts deleted file mode 100644 index 42f2b027..00000000 --- a/test/claude-bridge.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("node:fs", () => ({ - existsSync: vi.fn(), - readFileSync: vi.fn(), - writeFileSync: vi.fn(), - mkdirSync: vi.fn(), -})); - -vi.mock("node:path", async () => ({ - ...(await vi.importActual("node:path")), - dirname: vi.fn().mockReturnValue("/tmp"), -})); - -import { registerClaudeBridgeFunction } from "../src/functions/claude-bridge.js"; -import { existsSync, readFileSync, writeFileSync } from "node:fs"; -import type { ClaudeBridgeConfig, Memory } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -const enabledConfig: ClaudeBridgeConfig = { - enabled: true, - projectPath: "/tmp/my-project", - memoryFilePath: "/tmp/.claude/MEMORY.md", - lineBudget: 200, -}; - -const disabledConfig: ClaudeBridgeConfig = { - enabled: false, - projectPath: "", - memoryFilePath: "", - lineBudget: 200, -}; - -describe("Claude Bridge Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - vi.clearAllMocks(); - }); - - it("claude-bridge-read returns content when file exists", async () => { - registerClaudeBridgeFunction(sdk as never, kv as never, enabledConfig); - - vi.mocked(existsSync).mockReturnValue(true); - vi.mocked(readFileSync).mockReturnValue( - "# Memory\n\n## Project Summary\nA test project\n\n## Key Memories\nSome memories", - ); - - const result = (await sdk.trigger("mem::claude-bridge-read", {})) as { - success: boolean; - content: string; - sections: Record; - }; - - expect(result.success).toBe(true); - expect(result.content).toContain("# Memory"); - expect(result.sections).toBeDefined(); - expect(result.sections["Project Summary"]).toBe("A test project"); - }); - - it("claude-bridge-read returns empty when file does not exist", async () => { - registerClaudeBridgeFunction(sdk as never, kv as never, enabledConfig); - - vi.mocked(existsSync).mockReturnValue(false); - - const result = (await sdk.trigger("mem::claude-bridge-read", {})) as { - success: boolean; - content: string; - parsed: boolean; - }; - - expect(result.success).toBe(true); - expect(result.content).toBe(""); - expect(result.parsed).toBe(false); - }); - - it("claude-bridge-sync writes MEMORY.md with memories", async () => { - registerClaudeBridgeFunction(sdk as never, kv as never, enabledConfig); - - const mem: Memory = { - id: "mem_1", - createdAt: "2026-02-01T00:00:00Z", - updatedAt: "2026-02-01T00:00:00Z", - type: "pattern", - title: "Auth pattern", - content: "Always validate tokens", - concepts: ["auth"], - files: [], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, - }; - await kv.set("mem:memories", "mem_1", mem); - - vi.mocked(existsSync).mockReturnValue(true); - - const result = (await sdk.trigger("mem::claude-bridge-sync", {})) as { - success: boolean; - path: string; - lines: number; - }; - - expect(result.success).toBe(true); - expect(result.path).toBe("/tmp/.claude/MEMORY.md"); - expect(writeFileSync).toHaveBeenCalled(); - const writtenContent = vi.mocked(writeFileSync).mock.calls[0][1] as string; - expect(writtenContent).toContain("Auth pattern"); - }); - - it("claude-bridge-sync returns error when not configured", async () => { - registerClaudeBridgeFunction(sdk as never, kv as never, disabledConfig); - - const result = (await sdk.trigger("mem::claude-bridge-sync", {})) as { - success: boolean; - error: string; - }; - - expect(result.success).toBe(false); - expect(result.error).toContain("not configured"); - }); - - it("claude-bridge-read returns error when not configured", async () => { - registerClaudeBridgeFunction(sdk as never, kv as never, disabledConfig); - - const result = (await sdk.trigger("mem::claude-bridge-read", {})) as { - success: boolean; - error: string; - }; - - expect(result.success).toBe(false); - expect(result.error).toContain("not configured"); - }); -}); diff --git a/test/claude-code-with-hooks.test.ts b/test/claude-code-with-hooks.test.ts deleted file mode 100644 index 6c36b096..00000000 --- a/test/claude-code-with-hooks.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { resolve } from "node:path"; -import { - buildMergedHooks, - findPluginRoot, - type HookManifest, -} from "../src/cli/connect/codex-hooks.js"; - -const PLUGIN_ROOT = resolve(__dirname, "..", "plugin"); - -describe("buildMergedHooks against plugin/hooks/hooks.json (Claude Code)", () => { - it("locates the same plugin root used by the codex variant", () => { - expect(findPluginRoot()).toBe(PLUGIN_ROOT); - }); - - it("rewrites ${CLAUDE_PLUGIN_ROOT} to absolute pluginRoot in every command", () => { - const merged = buildMergedHooks(null, PLUGIN_ROOT, "hooks.json"); - for (const entries of Object.values(merged.hooks)) { - for (const entry of entries) { - for (const handler of entry.hooks) { - expect(handler.command).not.toContain("${CLAUDE_PLUGIN_ROOT}"); - expect(handler.command).toContain(`${PLUGIN_ROOT}/scripts/`); - } - } - } - }); - - it("includes Claude-only events that hooks.codex.json omits", () => { - const merged = buildMergedHooks(null, PLUGIN_ROOT, "hooks.json"); - const events = Object.keys(merged.hooks); - expect(events).toContain("SessionStart"); - expect(events).toContain("Stop"); - const claudeOnly = ["SessionEnd", "SubagentStop", "Notification"]; - expect( - claudeOnly.some((e) => events.includes(e)), - `hooks.json should include at least one Claude-only event (${claudeOnly.join(", ")})`, - ).toBe(true); - }); - - it("appends to existing user hooks without dropping them", () => { - const existing: HookManifest = { - hooks: { - SessionStart: [ - { hooks: [{ type: "command", command: "echo user-custom-claude" }] }, - ], - }, - }; - const merged = buildMergedHooks(existing, PLUGIN_ROOT, "hooks.json"); - const sessionStart = merged.hooks["SessionStart"]!; - expect( - sessionStart.some((e) => - e.hooks.some((h) => h.command === "echo user-custom-claude"), - ), - ).toBe(true); - expect( - sessionStart.some((e) => - e.hooks.some((h) => - h.command.includes(`${PLUGIN_ROOT}/scripts/session-start.mjs`), - ), - ), - ).toBe(true); - }); - - it("re-install strips previous agentmemory entries (idempotent)", () => { - const first = buildMergedHooks(null, PLUGIN_ROOT, "hooks.json"); - const second = buildMergedHooks(first, PLUGIN_ROOT, "hooks.json"); - for (const event of Object.keys(first.hooks)) { - expect( - second.hooks[event]!.length, - `${event} should not double after second install`, - ).toBe(first.hooks[event]!.length); - } - }); -}); diff --git a/test/cli-connect.test.ts b/test/cli-connect.test.ts deleted file mode 100644 index f9031c86..00000000 --- a/test/cli-connect.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { - ADAPTERS, - knownAgents, - resolveAdapter, -} from "../src/cli/connect/index.js"; - -describe("agentmemory connect — dispatcher", () => { - it("resolves every known agent by lowercase name", () => { - for (const name of knownAgents()) { - const a = resolveAdapter(name); - expect(a, `expected adapter for ${name}`).not.toBeNull(); - expect(a!.name).toBe(name); - } - }); - - it("resolves case-insensitively", () => { - expect(resolveAdapter("Codex")?.name).toBe("codex"); - expect(resolveAdapter("CODEX")?.name).toBe("codex"); - }); - - it("returns null for unknown agents", () => { - expect(resolveAdapter("nonexistent-agent")).toBeNull(); - expect(resolveAdapter("")).toBeNull(); - }); - - it("ships the supported agent list (codex only after PLAN-006 P2)", () => { - expect(knownAgents().sort()).toEqual(["codex"]); - expect(ADAPTERS.length).toBe(1); - }); - - it("every adapter exposes detect() and install()", () => { - for (const a of ADAPTERS) { - expect(typeof a.detect).toBe("function"); - expect(typeof a.install).toBe("function"); - expect(typeof a.name).toBe("string"); - expect(typeof a.displayName).toBe("string"); - } - }); -}); diff --git a/test/cli-doctor-fixes.test.ts b/test/cli-doctor-fixes.test.ts deleted file mode 100644 index 23fc90ca..00000000 --- a/test/cli-doctor-fixes.test.ts +++ /dev/null @@ -1,282 +0,0 @@ -// Unit tests for the doctor v2 diagnostic catalog. -// -// We exercise the data structure (every entry has check/fix/message), -// the pure parseEnvFile / realProviderKeys helpers, and the dry-run plan -// formatting. The full interactive prompt loop lives in src/cli.ts and is -// driven by clack — exercising it would require a TTY and is out of scope. - -import { describe, it, expect } from "vitest"; -import { - buildDiagnostics, - DIAGNOSTIC_IDS, - dryRunPlan, - parseEnvFile, - placeholderProviderKeys, - realProviderKeys, - type DoctorContext, - type DoctorEffects, -} from "../src/cli/doctor-diagnostics.js"; - -function stubCtx(overrides: Partial = {}): DoctorContext { - return { - baseUrl: "http://localhost:3111", - viewerUrl: "http://localhost:3113", - envPath: "/tmp/test/.agentmemory/.env", - pidfilePath: "/tmp/test/.agentmemory/iii.pid", - enginePath: "/tmp/test/.agentmemory/engine-state.json", - pinnedVersion: "0.11.2", - ...overrides, - }; -} - -function stubEffects(overrides: Partial = {}): DoctorEffects { - return { - envFileExists: () => true, - readEnvFile: () => ({ ANTHROPIC_API_KEY: "sk-ant-real-key-value" }), - pidfileExists: () => false, - pidfilePidIsAlive: () => null, - findIiiBinary: () => "/Users/test/.local/bin/iii", - localBinIiiPath: () => "/Users/test/.local/bin/iii", - iiiBinaryVersion: () => "0.11.2", - viewerReachable: async () => true, - runInit: async () => ({ ok: true, message: "wrote .env" }), - openEditor: async () => ({ ok: true, message: "saved" }), - runIiiInstaller: async () => ({ ok: true, message: "installed" }), - runStop: async () => ({ ok: true, message: "stopped" }), - runStart: async () => ({ ok: true, message: "started" }), - clearEnginePidAndState: () => {}, - ...overrides, - }; -} - -describe("doctor v2 diagnostic catalog", () => { - it("exports a stable list of diagnostic ids", () => { - expect(DIAGNOSTIC_IDS).toContain("env-missing"); - expect(DIAGNOSTIC_IDS).toContain("no-llm-provider-key"); - expect(DIAGNOSTIC_IDS).toContain("todo-extractor-llm-not-ready"); - expect(DIAGNOSTIC_IDS).toContain("engine-version-mismatch"); - expect(DIAGNOSTIC_IDS).toContain("viewer-unreachable"); - expect(DIAGNOSTIC_IDS).toContain("stale-pidfile"); - expect(DIAGNOSTIC_IDS).toContain("env-placeholder-keys"); - expect(DIAGNOSTIC_IDS).toContain("iii-on-path-not-local-bin"); - }); - - it("every diagnostic has check, fix, message, and fixPreview", () => { - const diagnostics = buildDiagnostics(stubEffects()); - expect(diagnostics.length).toBe(DIAGNOSTIC_IDS.length); - for (const d of diagnostics) { - expect(d.id).toMatch(/^[a-z][a-z0-9-]+$/); - expect(d.message.length).toBeGreaterThan(0); - expect(d.fixPreview.length).toBeGreaterThan(0); - expect(d.moreInfo.length).toBeGreaterThan(0); - expect(typeof d.check).toBe("function"); - expect(typeof d.fix).toBe("function"); - } - }); - - it("diagnostic ids are unique", () => { - const diagnostics = buildDiagnostics(stubEffects()); - const ids = diagnostics.map((d) => d.id); - expect(new Set(ids).size).toBe(ids.length); - }); - - it("env-missing fails when env file is absent", async () => { - const diagnostics = buildDiagnostics(stubEffects({ envFileExists: () => false })); - const envCheck = diagnostics.find((d) => d.id === "env-missing")!; - const status = await envCheck.check(stubCtx()); - expect(status.ok).toBe(false); - }); - - it("env-missing passes when env file exists", async () => { - const diagnostics = buildDiagnostics(stubEffects({ envFileExists: () => true })); - const envCheck = diagnostics.find((d) => d.id === "env-missing")!; - const status = await envCheck.check(stubCtx()); - expect(status.ok).toBe(true); - }); - - it("no-llm-provider-key fails when env has only placeholders", async () => { - const diagnostics = buildDiagnostics( - stubEffects({ - envFileExists: () => true, - readEnvFile: () => ({ ANTHROPIC_API_KEY: "your-key-here" }), - }), - ); - const check = diagnostics.find((d) => d.id === "no-llm-provider-key")!; - const status = await check.check(stubCtx()); - expect(status.ok).toBe(false); - }); - - it("no-llm-provider-key passes when one real key is set", async () => { - const diagnostics = buildDiagnostics(stubEffects()); - const check = diagnostics.find((d) => d.id === "no-llm-provider-key")!; - const status = await check.check(stubCtx()); - expect(status.ok).toBe(true); - }); - - it("no-llm-provider-key is advisory (manualOnly) — must not fail the first-run gate", async () => { - // PLAN-001 STEP-07: onboarding seeds LANGEXTRACT_* (To-Do extraction), not a - // compression provider key, so the legacy memory-engine key is optional and - // its absence must not be a primary first-run failure. - const diagnostics = buildDiagnostics(stubEffects()); - const check = diagnostics.find((d) => d.id === "no-llm-provider-key")!; - expect(check.manualOnly).toBe(true); - }); - - it("todo-extractor-llm-not-ready fails when LANGEXTRACT_API_KEY is missing", async () => { - const diagnostics = buildDiagnostics( - stubEffects({ - readEnvFile: () => ({ - ANTHROPIC_API_KEY: "sk-ant-real-key-value", - LANGEXTRACT_PROVIDER: "openai", - LANGEXTRACT_BASE_URL: "https://api.novita.ai/openai/v1", - }), - }), - ); - const check = diagnostics.find((d) => d.id === "todo-extractor-llm-not-ready")!; - const status = await check.check(stubCtx()); - expect(status.ok).toBe(false); - expect(status.detail).toContain("LANGEXTRACT_API_KEY missing"); - expect(check.manualOnly).toBe(true); - }); - - it("todo-extractor-llm-not-ready passes with OpenAI-compatible LangExtract config", async () => { - const diagnostics = buildDiagnostics( - stubEffects({ - readEnvFile: () => ({ - LANGEXTRACT_API_KEY: "sk-real", - LANGEXTRACT_PROVIDER: "openai", - LANGEXTRACT_BASE_URL: "https://api.novita.ai/openai/v1", - }), - }), - ); - const check = diagnostics.find((d) => d.id === "todo-extractor-llm-not-ready")!; - const status = await check.check(stubCtx()); - expect(status.ok).toBe(true); - expect(status.detail).toBe("LANGEXTRACT_* ready"); - }); - - it("engine-version-mismatch fails when iii reports the wrong version", async () => { - const diagnostics = buildDiagnostics( - stubEffects({ iiiBinaryVersion: () => "0.99.99" }), - ); - const check = diagnostics.find((d) => d.id === "engine-version-mismatch")!; - const status = await check.check(stubCtx()); - expect(status.ok).toBe(false); - expect(status.detail).toContain("0.99.99"); - expect(status.detail).toContain("0.11.2"); - }); - - it("engine-version-mismatch passes when iii matches pinned version", async () => { - const diagnostics = buildDiagnostics(stubEffects()); - const check = diagnostics.find((d) => d.id === "engine-version-mismatch")!; - const status = await check.check(stubCtx()); - expect(status.ok).toBe(true); - }); - - it("viewer-unreachable fails when viewer probe returns false", async () => { - const diagnostics = buildDiagnostics( - stubEffects({ viewerReachable: async () => false }), - ); - const check = diagnostics.find((d) => d.id === "viewer-unreachable")!; - const status = await check.check(stubCtx()); - expect(status.ok).toBe(false); - }); - - it("stale-pidfile passes when no pidfile exists", async () => { - const diagnostics = buildDiagnostics(stubEffects({ pidfileExists: () => false })); - const check = diagnostics.find((d) => d.id === "stale-pidfile")!; - const status = await check.check(stubCtx()); - expect(status.ok).toBe(true); - }); - - it("stale-pidfile fails when pidfile points at a dead pid", async () => { - const diagnostics = buildDiagnostics( - stubEffects({ pidfileExists: () => true, pidfilePidIsAlive: () => false }), - ); - const check = diagnostics.find((d) => d.id === "stale-pidfile")!; - const status = await check.check(stubCtx()); - expect(status.ok).toBe(false); - expect(status.detail).toBe("pid is gone"); - }); - - it("env-placeholder-keys detects sk-ant-... placeholder", async () => { - const diagnostics = buildDiagnostics( - stubEffects({ - envFileExists: () => true, - readEnvFile: () => ({ ANTHROPIC_API_KEY: "sk-ant-..." }), - }), - ); - const check = diagnostics.find((d) => d.id === "env-placeholder-keys")!; - const status = await check.check(stubCtx()); - expect(status.ok).toBe(false); - expect(status.detail).toContain("ANTHROPIC_API_KEY"); - }); - - it("iii-on-path-not-local-bin warns when iii lives in another location", async () => { - const diagnostics = buildDiagnostics( - stubEffects({ - findIiiBinary: () => "/opt/homebrew/bin/iii", - localBinIiiPath: () => "/Users/test/.local/bin/iii", - }), - ); - const check = diagnostics.find((d) => d.id === "iii-on-path-not-local-bin")!; - const status = await check.check(stubCtx()); - expect(status.ok).toBe(false); - expect(check.manualOnly).toBe(true); - }); - - it("dryRunPlan lists each failing diagnostic with the fix preview", () => { - const diagnostics = buildDiagnostics(stubEffects()); - const results = diagnostics.map((d) => ({ - diagnostic: d, - status: { ok: false, detail: "stub fail" }, - })); - const lines = dryRunPlan(stubCtx(), results); - expect(lines.some((l) => l.includes("env-missing"))).toBe(true); - expect(lines.some((l) => l.includes("would fix:"))).toBe(true); - }); - - it("dryRunPlan reports all-passing state", () => { - const diagnostics = buildDiagnostics(stubEffects()); - const results = diagnostics.map((d) => ({ - diagnostic: d, - status: { ok: true }, - })); - const lines = dryRunPlan(stubCtx(), results); - expect(lines.length).toBe(1); - expect(lines[0]).toContain("All checks passing"); - }); -}); - -describe("parseEnvFile", () => { - it("strips comments and blank lines", () => { - const env = parseEnvFile("# a comment\n\nFOO=bar\nBAZ=qux\n"); - expect(env).toEqual({ FOO: "bar", BAZ: "qux" }); - }); - - it("strips surrounding quotes", () => { - const env = parseEnvFile(`A="hello"\nB='world'\nC=plain\n`); - expect(env).toEqual({ A: "hello", B: "world", C: "plain" }); - }); -}); - -describe("realProviderKeys / placeholderProviderKeys", () => { - it("returns real keys only", () => { - const env = { - ANTHROPIC_API_KEY: "sk-ant-real-value", - OPENAI_API_KEY: "sk-...", - GEMINI_API_KEY: "", - OPENROUTER_API_KEY: "your-key-here", - }; - expect(realProviderKeys(env)).toEqual(["ANTHROPIC_API_KEY"]); - expect(placeholderProviderKeys(env)).toContain("OPENAI_API_KEY"); - expect(placeholderProviderKeys(env)).toContain("OPENROUTER_API_KEY"); - expect(placeholderProviderKeys(env)).not.toContain("GEMINI_API_KEY"); - }); - - it("treats xxx-style placeholders as fake", () => { - expect(placeholderProviderKeys({ ANTHROPIC_API_KEY: "xxxx-xxxx" })).toEqual([ - "ANTHROPIC_API_KEY", - ]); - }); -}); diff --git a/test/cli-loop.test.ts b/test/cli-loop.test.ts new file mode 100644 index 00000000..dc2bd61b --- /dev/null +++ b/test/cli-loop.test.ts @@ -0,0 +1,277 @@ +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { createServer } from "node:http"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { DEFAULT_UI_PORT, main } from "../src/cli.js"; + +test("CLI scans and organize does not create rule fallback cards without LLM config", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-cli-")); + const previousHome = process.env.AI_TODO_HOME; + process.env.AI_TODO_HOME = join(dir, "home"); + + try { + const sessions = join(dir, "codex"); + mkdirSync(sessions); + writeFileSync(join(sessions, "session.jsonl"), [ + JSON.stringify({ role: "user", text: "Please add CLI list output", timestamp: new Date().toISOString() }) + ].join("\n")); + + const scanned = await capture(() => main(["scan", "codex", sessions])); + assert.equal(scanned.code, 0); + assert.match(scanned.stdout, /scanned: 1/); + assert.match(scanned.stdout, /observations: 1/); + const rescanned = await capture(() => main(["scan", "codex", sessions])); + assert.equal(rescanned.code, 0); + assert.match(rescanned.stdout, /skipped: 1/); + const organized = await capture(() => main(["organize"])); + assert.equal(organized.code, 0); + assert.match(organized.stdout, /created: 0/); + assert.match(organized.stdout, /engine: llm/); + assert.match(organized.stdout, /warnings: llm_config_missing/); + + const listed = await capture(() => main(["list"])); + assert.equal(listed.code, 0); + assert.match(listed.stdout, /No todos/); + } finally { + process.env.AI_TODO_HOME = previousHome; + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("CLI init writes local env config and doctor reports it", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-cli-init-")); + const previousHome = process.env.AI_TODO_HOME; + process.env.AI_TODO_HOME = join(dir, "home"); + + try { + const init = await capture(() => main([ + "init", + "--api-key", "dummy-llm-key-value", + "--model", "custom/model", + "--endpoint", "https://llm.example.test/v1", + "--codex-home", join(dir, "codex"), + "--claude-home", join(dir, "claude"), + "--since-days", "30", + "--max-interactions", "15" + ])); + assert.equal(init.code, 0); + const envText = readFileSync(join(process.env.AI_TODO_HOME, ".env"), "utf8"); + assert.match(envText, /AI_TODO_LLM_MODEL=custom\/model/); + assert.match(envText, /AI_TODO_LLM_API_KEY=dummy-llm-key-value/); + const doctor = await capture(() => main(["doctor"])); + assert.equal(doctor.code, 0); + assert.match(doctor.stdout, /llm key: configured/); + assert.doesNotMatch(doctor.stdout, /dummy-llm-key-value/); + } finally { + process.env.AI_TODO_HOME = previousHome; + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("CLI reports empty lists and invalid todo updates", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-cli-empty-")); + const previousHome = process.env.AI_TODO_HOME; + process.env.AI_TODO_HOME = join(dir, "home"); + + try { + const listed = await capture(() => main(["list"])); + assert.equal(listed.code, 0); + assert.match(listed.stdout, /No todos/); + + const missingId = await capture(() => main(["done"])); + assert.equal(missingId.code, 1); + assert.match(missingId.stderr, /missing todo id/); + + const unknownId = await capture(() => main(["ignore", "missing"])); + assert.equal(unknownId.code, 1); + assert.match(unknownId.stderr, /todo not found/); + + const missingScanSource = await capture(() => main(["scan"])); + assert.equal(missingScanSource.code, 1); + assert.match(missingScanSource.stderr, /usage: ai-todo scan/); + + const badSource = await capture(() => main(["scan", "browser", dir])); + assert.equal(badSource.code, 1); + assert.match(badSource.stderr, /unsupported source/); + + const missingPath = await capture(() => main(["scan", "codex", join(dir, "missing")])); + assert.equal(missingPath.code, 1); + assert.match(missingPath.stderr, /path not found/); + } finally { + process.env.AI_TODO_HOME = previousHome; + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("CLI scan uses default source paths with environment overrides", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-cli-defaults-")); + const previousHome = process.env.AI_TODO_HOME; + const previousCodex = process.env.AI_TODO_CODEX_HOME; + const previousClaude = process.env.AI_TODO_CLAUDE_HOME; + process.env.AI_TODO_HOME = join(dir, "home"); + process.env.AI_TODO_CODEX_HOME = join(dir, "codex-default"); + process.env.AI_TODO_CLAUDE_HOME = join(dir, "claude-default"); + + try { + mkdirSync(process.env.AI_TODO_CODEX_HOME); + mkdirSync(process.env.AI_TODO_CLAUDE_HOME); + writeFileSync(join(process.env.AI_TODO_CODEX_HOME, "session.jsonl"), [ + JSON.stringify({ role: "user", text: "Please scan default Codex path" }) + ].join("\n")); + writeFileSync(join(process.env.AI_TODO_CLAUDE_HOME, "session.jsonl"), [ + JSON.stringify({ role: "user", content: "Please scan default Claude path" }) + ].join("\n")); + const explicit = join(dir, "explicit-codex"); + mkdirSync(explicit); + writeFileSync(join(explicit, "session.jsonl"), [ + JSON.stringify({ role: "user", text: "Please scan explicit Codex path" }) + ].join("\n")); + + const codex = await capture(() => main(["scan", "codex"])); + assert.equal(codex.code, 0); + assert.match(codex.stdout, /scanned: 1/); + + const claude = await capture(() => main(["scan", "claude-code"])); + assert.equal(claude.code, 0); + assert.match(claude.stdout, /scanned: 1/); + + const explicitScan = await capture(() => main(["scan", "codex", explicit])); + assert.equal(explicitScan.code, 0); + assert.match(explicitScan.stdout, /scanned: 1/); + } finally { + process.env.AI_TODO_HOME = previousHome; + process.env.AI_TODO_CODEX_HOME = previousCodex; + process.env.AI_TODO_CLAUDE_HOME = previousClaude; + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("CLI open reports the fixed default port when it is occupied", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-cli-open-")); + const previousHome = process.env.AI_TODO_HOME; + process.env.AI_TODO_HOME = join(dir, "home"); + const blocker = await tryListenOnDefaultPort(); + + try { + const opened = await capture(() => main(["open"])); + assert.equal(opened.code, 1); + assert.match(opened.stderr, /3111 is already in use/); + assert.match(opened.stderr, /ai-todo open --port /); + } finally { + process.env.AI_TODO_HOME = previousHome; + if (blocker) { + await new Promise((resolve, reject) => blocker.close((error) => error ? reject(error) : resolve())); + } + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("CLI open shuts down cleanly on SIGINT", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-cli-open-stop-")); + let child: ReturnType | undefined; + + try { + child = spawn(process.execPath, ["dist/cli.js", "open", "--port", "0"], { + cwd: process.cwd(), + env: { ...process.env, AI_TODO_HOME: join(dir, "home") }, + stdio: ["ignore", "pipe", "pipe"] + }); + + const opened = await waitForOpen(child); + const health = await fetch(`http://127.0.0.1:${opened.port}/healthz`); + assert.equal(health.status, 200); + assert.deepEqual(await health.json(), { ok: true }); + + child.kill("SIGINT"); + const exit = await waitForExit(child); + assert.equal(exit.code, 0); + assert.equal(exit.signal, null); + assert.doesNotMatch(opened.output() + exit.stderr, /database is not open|ERR_INVALID_STATE/); + } finally { + if (child && child.exitCode === null) child.kill("SIGKILL"); + rmSync(dir, { recursive: true, force: true }); + } +}); + +async function tryListenOnDefaultPort() { + const blocker = createServer(); + try { + await new Promise((resolve, reject) => { + blocker.once("error", reject); + blocker.listen(DEFAULT_UI_PORT, "127.0.0.1", () => { + blocker.off("error", reject); + resolve(); + }); + }); + return blocker; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "EADDRINUSE") return undefined; + throw error; + } +} + +function waitForOpen(child: ReturnType): Promise<{ port: number; output: () => string }> { + let stdout = ""; + let stderr = ""; + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(`timeout waiting for open\n${stdout}\n${stderr}`)), 5000); + const finish = (port: number) => { + clearTimeout(timer); + resolve({ port, output: () => stdout + stderr }); + }; + child.stdout?.setEncoding("utf8"); + child.stderr?.setEncoding("utf8"); + child.stdout?.on("data", (chunk: string) => { + stdout += chunk; + const match = stdout.match(/AI-Todo UI: http:\/\/127\.0\.0\.1:(\d+)\//); + if (match) finish(Number(match[1])); + }); + child.stderr?.on("data", (chunk: string) => { + stderr += chunk; + }); + child.once("error", (error) => { + clearTimeout(timer); + reject(error); + }); + child.once("exit", (code, signal) => { + clearTimeout(timer); + reject(new Error(`open exited before ready: code=${code} signal=${signal}\n${stdout}\n${stderr}`)); + }); + }); +} + +function waitForExit(child: ReturnType): Promise<{ code: number | null; signal: NodeJS.Signals | null; stderr: string }> { + let stderr = ""; + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error("timeout waiting for open to exit")), 5000); + child.stderr?.on("data", (chunk: Buffer | string) => { + stderr += chunk.toString(); + }); + child.once("exit", (code, signal) => { + clearTimeout(timer); + resolve({ code, signal, stderr }); + }); + }); +} + +async function capture(fn: () => Promise) { + let stdout = ""; + let stderr = ""; + const originalLog = console.log; + const originalError = console.error; + console.log = (...args: unknown[]) => { + stdout += `${args.join(" ")}\n`; + }; + console.error = (...args: unknown[]) => { + stderr += `${args.join(" ")}\n`; + }; + try { + return { code: await fn(), stdout, stderr }; + } finally { + console.log = originalLog; + console.error = originalError; + } +} diff --git a/test/cli-onboarding.test.ts b/test/cli-onboarding.test.ts deleted file mode 100644 index 9cc8ba20..00000000 --- a/test/cli-onboarding.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -const prompts = vi.hoisted(() => ({ - note: vi.fn(), - multiselect: vi.fn(async () => { - throw new Error("interactive multiselect should not run in non-TTY onboarding"); - }), - select: vi.fn(async () => { - throw new Error("interactive select should not run in non-TTY onboarding"); - }), - confirm: vi.fn(async () => true), - isCancel: vi.fn(() => false), - cancel: vi.fn(), - log: { - warn: vi.fn(), - step: vi.fn(), - error: vi.fn(), - }, -})); - -vi.mock("@clack/prompts", () => prompts); -vi.mock("../src/cli/connect/index.js", () => ({ - resolveAdapter: vi.fn(), - runAdapter: vi.fn(), -})); - -const ORIGINAL_HOME = process.env["HOME"]; -const ORIGINAL_USERPROFILE = process.env["USERPROFILE"]; -const ORIGINAL_CI = process.env["CI"]; -const ORIGINAL_AGENTMEMORY_HOME = process.env["AGENTMEMORY_HOME"]; -const stdinTtyDescriptor = Object.getOwnPropertyDescriptor(process.stdin, "isTTY"); -const stdoutTtyDescriptor = Object.getOwnPropertyDescriptor(process.stdout, "isTTY"); - -let sandboxHome: string; - -function setTTY(value: boolean): void { - Object.defineProperty(process.stdin, "isTTY", { value, configurable: true }); - Object.defineProperty(process.stdout, "isTTY", { value, configurable: true }); -} - -function restoreTTY(): void { - if (stdinTtyDescriptor) Object.defineProperty(process.stdin, "isTTY", stdinTtyDescriptor); - else delete (process.stdin as NodeJS.ReadStream & { isTTY?: boolean }).isTTY; - if (stdoutTtyDescriptor) Object.defineProperty(process.stdout, "isTTY", stdoutTtyDescriptor); - else delete (process.stdout as NodeJS.WriteStream & { isTTY?: boolean }).isTTY; -} - -async function freshOnboarding() { - vi.resetModules(); - return await import("../src/cli/onboarding.js"); -} - -function activeEnvLines(env: string): string[] { - return env.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")); -} - -describe("cli onboarding", () => { - beforeEach(() => { - sandboxHome = mkdtempSync(join(tmpdir(), "agentmemory-onboarding-")); - process.env["HOME"] = sandboxHome; - process.env["USERPROFILE"] = sandboxHome; - process.env["CI"] = "0"; - delete process.env["AGENTMEMORY_HOME"]; - setTTY(false); - vi.clearAllMocks(); - }); - - afterEach(() => { - restoreTTY(); - if (ORIGINAL_HOME === undefined) delete process.env["HOME"]; - else process.env["HOME"] = ORIGINAL_HOME; - if (ORIGINAL_USERPROFILE === undefined) delete process.env["USERPROFILE"]; - else process.env["USERPROFILE"] = ORIGINAL_USERPROFILE; - if (ORIGINAL_CI === undefined) delete process.env["CI"]; - else process.env["CI"] = ORIGINAL_CI; - if (ORIGINAL_AGENTMEMORY_HOME === undefined) delete process.env["AGENTMEMORY_HOME"]; - else process.env["AGENTMEMORY_HOME"] = ORIGINAL_AGENTMEMORY_HOME; - rmSync(sandboxHome, { recursive: true, force: true }); - }); - - it("does not prompt and records default preferences when onboarding runs without a TTY", async () => { - const { runOnboarding } = await freshOnboarding(); - - const result = await runOnboarding(); - - expect(result).toEqual({ agents: [], provider: null }); - expect(prompts.multiselect).not.toHaveBeenCalled(); - expect(prompts.select).not.toHaveBeenCalled(); - expect(prompts.confirm).not.toHaveBeenCalled(); - - const preferencesPath = join(sandboxHome, ".agentmemory", "preferences.json"); - expect(existsSync(preferencesPath)).toBe(true); - const preferences = JSON.parse(readFileSync(preferencesPath, "utf-8")); - expect(preferences).toMatchObject({ - schemaVersion: 1, - lastAgent: null, - lastAgents: [], - lastProvider: null, - skipSplash: true, - }); - expect(typeof preferences.firstRunAt).toBe("string"); - }); - - it("keeps To-Do LLM extraction off unless the user opts in", async () => { - setTTY(true); - prompts.multiselect.mockResolvedValueOnce([]); - prompts.select.mockResolvedValueOnce("skip"); - const { runOnboarding } = await freshOnboarding(); - - await runOnboarding(); - - const envPath = join(sandboxHome, ".agentmemory", ".env"); - const lines = activeEnvLines(readFileSync(envPath, "utf-8")); - expect(lines).not.toContain("AGENTMEMORY_TODO_EXTRACTOR=langextract"); - expect(lines.some((line) => line.startsWith("LANGEXTRACT_API_KEY="))).toBe(false); - }); - - it("writes To-Do LangExtract defaults when the user opts in", async () => { - setTTY(true); - prompts.multiselect.mockResolvedValueOnce([]); - prompts.select.mockResolvedValueOnce("novita"); - const { runOnboarding } = await freshOnboarding(); - - await runOnboarding(); - - const envPath = join(sandboxHome, ".agentmemory", ".env"); - const lines = activeEnvLines(readFileSync(envPath, "utf-8")); - expect(lines).toContain("AGENTMEMORY_TODO_EXTRACTOR=langextract"); - expect(lines).toContain("LANGEXTRACT_PROVIDER=openai"); - expect(lines).toContain("LANGEXTRACT_MODEL=deepseek/deepseek-v4-flash"); - expect(lines).toContain("LANGEXTRACT_BASE_URL=https://api.novita.ai/openai/v1"); - expect(lines.some((line) => line.startsWith("LANGEXTRACT_API_KEY="))).toBe(false); - }); - - it("uses AGENTMEMORY_HOME for first-run preferences and env without changing Codex home", async () => { - const dataHome = join(sandboxHome, "isolated-agentmemory"); - process.env["AGENTMEMORY_HOME"] = dataHome; - setTTY(true); - prompts.multiselect.mockResolvedValueOnce([]); - prompts.select.mockResolvedValueOnce("novita"); - const { runOnboarding } = await freshOnboarding(); - - await runOnboarding(); - - const preferencesPath = join(dataHome, "preferences.json"); - const envPath = join(dataHome, ".env"); - expect(existsSync(preferencesPath)).toBe(true); - expect(existsSync(envPath)).toBe(true); - expect(existsSync(join(sandboxHome, ".agentmemory", "preferences.json"))).toBe(false); - expect(activeEnvLines(readFileSync(envPath, "utf-8"))).toContain( - "LANGEXTRACT_MODEL=deepseek/deepseek-v4-flash", - ); - }); -}); diff --git a/test/cli-remove.test.ts b/test/cli-remove.test.ts deleted file mode 100644 index ac5794e1..00000000 --- a/test/cli-remove.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -// Unit tests for the `agentmemory remove` destruction plan. -// -// The plan module is pure-fs (just inspects what's present) so we sandbox -// a fake $HOME under tmpdir() and assert which plan items come back. The -// actual file deletion is wrapped in src/cli.ts. - -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { - buildRemovePlan, - formatPlan, - type ConnectManifest, - type RemoveContext, -} from "../src/cli/remove-plan.js"; -import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -let sandbox: string; - -function ctx(overrides: Partial = {}): RemoveContext { - return { - home: sandbox, - pinnedVersion: "0.11.2", - localBinIiiVersion: null, - connectManifest: null, - ...overrides, - }; -} - -function touch(relPath: string, content = ""): void { - const full = join(sandbox, relPath); - mkdirSync(join(full, ".."), { recursive: true }); - writeFileSync(full, content); -} - -function mkdir(relPath: string): void { - mkdirSync(join(sandbox, relPath), { recursive: true }); -} - -beforeEach(() => { - sandbox = mkdtempSync(join(tmpdir(), "agentmemory-remove-")); -}); - -afterEach(() => { - rmSync(sandbox, { recursive: true, force: true }); -}); - -describe("buildRemovePlan", () => { - it("returns no applicable items on a clean system", () => { - const plan = buildRemovePlan(ctx(), { force: false, keepData: false }); - const applicable = plan.filter((p) => p.applicable); - expect(applicable.length).toBe(0); - }); - - it("includes pidfile + engine-state when both exist", () => { - touch(".agentmemory/iii.pid", "12345\n"); - touch(".agentmemory/engine-state.json", "{}"); - const plan = buildRemovePlan(ctx(), { force: false, keepData: false }); - const ids = plan.filter((p) => p.applicable).map((p) => p.id); - expect(ids).toContain("stop-engine"); - expect(ids).toContain("pidfile"); - expect(ids).toContain("engine-state"); - }); - - it("marks .env as alwaysAsk", () => { - touch(".agentmemory/.env", "ANTHROPIC_API_KEY=sk-ant-real\n"); - const plan = buildRemovePlan(ctx(), { force: false, keepData: false }); - const envItem = plan.find((p) => p.id === "env")!; - expect(envItem.applicable).toBe(true); - expect(envItem.alwaysAsk).toBe(true); - }); - - it("--keep-data hides .env, preferences, backups, and data-dir", () => { - touch(".agentmemory/.env", "x"); - touch(".agentmemory/preferences.json", "{}"); - mkdir(".agentmemory/backups"); - mkdir(".agentmemory/data"); - const plan = buildRemovePlan(ctx(), { force: false, keepData: true }); - const applicable = plan.filter((p) => p.applicable).map((p) => p.id); - expect(applicable).not.toContain("env"); - expect(applicable).not.toContain("preferences"); - expect(applicable).not.toContain("backups"); - expect(applicable).not.toContain("data-dir"); - }); - - it("data-dir is alwaysAsk even on --force", () => { - mkdir(".agentmemory/data"); - const plan = buildRemovePlan(ctx(), { force: true, keepData: false }); - const item = plan.find((p) => p.id === "data-dir")!; - expect(item.applicable).toBe(true); - expect(item.alwaysAsk).toBe(true); - }); - - it("expands connect-manifest entries into individual plan items", () => { - const manifest: ConnectManifest = { - installed: [ - { target: join(sandbox, "fake-claude-symlink"), agent: "claude-code", symlink: true }, - { target: join(sandbox, "fake-cursor-link"), agent: "cursor" }, - ], - }; - touch("fake-claude-symlink"); - touch("fake-cursor-link"); - const plan = buildRemovePlan(ctx({ connectManifest: manifest }), { - force: false, - keepData: false, - }); - const connectItems = plan.filter((p) => p.id.startsWith("connect:")); - expect(connectItems.length).toBe(2); - expect(connectItems.every((p) => p.applicable)).toBe(true); - }); - - it("local-bin/iii is alwaysAsk when version does not match", () => { - touch(".local/bin/iii", "fakebin"); - const plan = buildRemovePlan( - ctx({ localBinIiiVersion: "9.9.9" }), - { force: false, keepData: false }, - ); - const item = plan.find((p) => p.id === "local-bin-iii")!; - expect(item.applicable).toBe(true); - expect(item.alwaysAsk).toBe(true); - }); - - it("local-bin/iii is auto-fixable when version matches pinned", () => { - touch(".local/bin/iii", "fakebin"); - const plan = buildRemovePlan( - ctx({ localBinIiiVersion: "0.11.2" }), - { force: false, keepData: false }, - ); - const item = plan.find((p) => p.id === "local-bin-iii")!; - expect(item.applicable).toBe(true); - expect(item.alwaysAsk).toBe(false); - expect(item.description).toContain("matches pinned"); - }); - - it("local-bin/iii absent: no plan entry created", () => { - const plan = buildRemovePlan(ctx(), { force: false, keepData: false }); - expect(plan.find((p) => p.id === "local-bin-iii")).toBeUndefined(); - }); -}); - -describe("formatPlan", () => { - it("renders applicable items with numbers", () => { - touch(".agentmemory/iii.pid", "1"); - touch(".agentmemory/engine-state.json", "{}"); - const plan = buildRemovePlan(ctx(), { force: false, keepData: false }); - const out = formatPlan(plan); - expect(out).toMatch(/^\s+1\./m); - expect(out).toContain("pidfile"); - expect(out).toContain("engine-state.json"); - }); - - it("marks alwaysAsk items with [asks]", () => { - touch(".agentmemory/.env", "x"); - const plan = buildRemovePlan(ctx(), { force: false, keepData: false }); - const out = formatPlan(plan); - expect(out).toContain("[asks]"); - }); -}); diff --git a/test/codex-connect-hooks.test.ts b/test/codex-connect-hooks.test.ts deleted file mode 100644 index 75accbee..00000000 --- a/test/codex-connect-hooks.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { writeFileSync, readFileSync, mkdirSync, rmSync } from "node:fs"; -import { join, resolve } from "node:path"; -import { tmpdir } from "node:os"; -import { - buildMergedHooks, - findPluginRoot, - type HookManifest, -} from "../src/cli/connect/codex-hooks.js"; - -const PLUGIN_ROOT = resolve(__dirname, "..", "plugin"); - -describe("findPluginRoot", () => { - it("locates the bundled plugin/ directory from src/cli/connect/", () => { - const root = findPluginRoot(); - expect(root).toBe(PLUGIN_ROOT); - }); -}); - -describe("buildMergedHooks", () => { - it("rewrites ${CLAUDE_PLUGIN_ROOT} to absolute pluginRoot in every command", () => { - const merged = buildMergedHooks(null, PLUGIN_ROOT); - for (const entries of Object.values(merged.hooks)) { - for (const entry of entries) { - for (const handler of entry.hooks) { - expect(handler.command).not.toContain("${CLAUDE_PLUGIN_ROOT}"); - expect(handler.command).toContain(`${PLUGIN_ROOT}/scripts/`); - } - } - } - }); - - it("preserves matchers from the bundled manifest (e.g. PreToolUse)", () => { - const merged = buildMergedHooks(null, PLUGIN_ROOT); - const preToolUse = merged.hooks["PreToolUse"]; - expect(preToolUse).toBeDefined(); - expect(preToolUse!.length).toBeGreaterThan(0); - expect(preToolUse![0].matcher).toBe("Edit|Write|Read|Glob|Grep"); - }); - - it("includes all six expected lifecycle events", () => { - const merged = buildMergedHooks(null, PLUGIN_ROOT); - for (const event of [ - "SessionStart", - "UserPromptSubmit", - "PreToolUse", - "PostToolUse", - "PreCompact", - "Stop", - ]) { - expect(Object.keys(merged.hooks)).toContain(event); - } - }); - - it("appends to existing user hooks without dropping them", () => { - const existing: HookManifest = { - hooks: { - SessionStart: [ - { - hooks: [{ type: "command", command: "echo user-custom" }], - }, - ], - UserPromptSubmit: [ - { - hooks: [{ type: "command", command: "echo another-user-hook" }], - }, - ], - }, - }; - const merged = buildMergedHooks(existing, PLUGIN_ROOT); - const sessionStart = merged.hooks["SessionStart"]!; - const userHook = sessionStart.find((e) => - e.hooks.some((h) => h.command === "echo user-custom"), - ); - expect(userHook, "user's SessionStart hook should survive").toBeDefined(); - const ours = sessionStart.find((e) => - e.hooks.some((h) => h.command.includes(`${PLUGIN_ROOT}/scripts/session-start.mjs`)), - ); - expect(ours, "agentmemory SessionStart hook should be appended").toBeDefined(); - }); - - it("re-install strips previous agentmemory entries (idempotent by script path)", () => { - const first = buildMergedHooks(null, PLUGIN_ROOT); - const second = buildMergedHooks(first, PLUGIN_ROOT); - for (const event of Object.keys(first.hooks)) { - expect( - second.hooks[event]!.length, - `${event} should not double after second install`, - ).toBe(first.hooks[event]!.length); - } - }); - - it("re-install preserves unrelated user entries", () => { - const userEntry = { - hooks: [{ type: "command", command: "echo user-untouchable" }], - }; - const withUser: HookManifest = { - hooks: { - SessionStart: [userEntry], - Stop: [{ hooks: [{ type: "command", command: "echo also-user" }] }], - }, - }; - const installed = buildMergedHooks(withUser, PLUGIN_ROOT); - const reinstalled = buildMergedHooks(installed, PLUGIN_ROOT); - expect( - reinstalled.hooks["SessionStart"]!.some((e) => - e.hooks.some((h) => h.command === "echo user-untouchable"), - ), - ).toBe(true); - expect( - reinstalled.hooks["Stop"]!.some((e) => - e.hooks.some((h) => h.command === "echo also-user"), - ), - ).toBe(true); - }); - - it("handles empty existing manifest object", () => { - const merged = buildMergedHooks({ hooks: {} }, PLUGIN_ROOT); - expect(Object.keys(merged.hooks).length).toBeGreaterThan(0); - }); -}); - -describe("buildMergedHooks file round-trip", () => { - it("produces JSON that parses back to a structurally equivalent manifest", () => { - const dir = join(tmpdir(), `agentmemory-codex-hooks-${process.pid}-${Date.now()}`); - mkdirSync(dir, { recursive: true }); - const path = join(dir, "hooks.json"); - try { - const merged = buildMergedHooks(null, PLUGIN_ROOT); - writeFileSync(path, `${JSON.stringify(merged, null, 2)}\n`, "utf-8"); - const reread = JSON.parse(readFileSync(path, "utf-8")) as HookManifest; - expect(Object.keys(reread.hooks).sort()).toEqual(Object.keys(merged.hooks).sort()); - } finally { - rmSync(dir, { recursive: true, force: true }); - } - }); -}); diff --git a/test/codex-plugin.test.ts b/test/codex-plugin.test.ts deleted file mode 100644 index 474a66cf..00000000 --- a/test/codex-plugin.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { readFileSync, existsSync } from "node:fs"; -import { join, resolve } from "node:path"; - -const repoRoot = resolve(__dirname, ".."); -const pluginRoot = join(repoRoot, "plugin"); - -function readJson(path: string): T { - return JSON.parse(readFileSync(path, "utf-8")) as T; -} - -type HookHandler = { type: string; command: string }; -type HookEntry = { hooks: HookHandler[] }; - -function hookCommands(path: string): string[] { - const manifest = readJson<{ hooks: Record }>(path); - return Object.values(manifest.hooks).flatMap((entries) => - entries.flatMap((entry) => entry.hooks.map((handler) => handler.command)), - ); -} - -describe("Plugin hook manifests", () => { - it("quote plugin script paths so roots with spaces stay intact", () => { - for (const manifest of ["hooks.json", "hooks.codex.json"]) { - const commands = hookCommands(join(pluginRoot, "hooks", manifest)); - expect(commands.length, `${manifest} should contain hook commands`).toBeGreaterThan(0); - - for (const command of commands) { - expect(command).toMatch(/^node "\$\{CLAUDE_PLUGIN_ROOT\}\/scripts\/[^\s"]+\.mjs"$/); - } - } - }); -}); - -describe("Codex plugin manifest (developers.openai.com/codex/plugins)", () => { - it("ships .codex-plugin/plugin.json with kebab-case name + version + references", () => { - const manifestPath = join(pluginRoot, ".codex-plugin/plugin.json"); - expect(existsSync(manifestPath)).toBe(true); - const manifest = readJson<{ - name: string; - version: string; - description?: string; - skills?: string; - mcpServers?: string; - hooks?: string; - }>(manifestPath); - expect(manifest.name).toBe("agent-memory-lab"); - expect(manifest.name).toMatch(/^[a-z][a-z0-9-]*$/); - expect(manifest.version).toMatch(/^\d+\.\d+\.\d+/); - expect(manifest.skills).toBeDefined(); - expect(manifest.mcpServers).toBeDefined(); - expect(manifest.hooks).toBeDefined(); - }); - - it("manifest version matches main package.json", () => { - const pkgVer = readJson<{ version: string }>(join(repoRoot, "package.json")).version; - const codexVer = readJson<{ version: string }>( - join(pluginRoot, ".codex-plugin/plugin.json"), - ).version; - expect(codexVer).toBe(pkgVer); - }); - - it("all referenced manifest paths resolve to existing files / directories", () => { - const manifest = readJson<{ skills: string; mcpServers: string; hooks: string }>( - join(pluginRoot, ".codex-plugin/plugin.json"), - ); - expect(existsSync(join(pluginRoot, manifest.skills))).toBe(true); - expect(existsSync(join(pluginRoot, manifest.mcpServers))).toBe(true); - expect(existsSync(join(pluginRoot, manifest.hooks))).toBe(true); - }); - - it("plugin MCP server inherits remote agentmemory environment overrides", () => { - const mcp = readJson<{ - mcpServers: Record< - string, - { - command: string; - args: string[]; - env?: Record; - } - >; - }>(join(pluginRoot, ".mcp.json")); - - // env interpolation must include defaults so Claude Code (and - // any other MCP host that fails parse on unset ${VAR}) doesn't drop - // the server silently when the user hasn't exported the var. - expect(mcp.mcpServers.agentmemory?.env?.AGENTMEMORY_URL).toMatch( - /\$\{AGENTMEMORY_URL:-/, - ); - expect(mcp.mcpServers.agentmemory?.env?.AGENTMEMORY_SECRET).toMatch( - /\$\{AGENTMEMORY_SECRET:-/, - ); - }); - - it("hooks.codex.json contains only events Codex supports (no Subagent / SessionEnd / Notification / TaskCompleted / PostToolUseFailure)", () => { - const hooksPath = join(pluginRoot, "hooks/hooks.codex.json"); - const hooks = readJson<{ hooks: Record }>(hooksPath); - const events = Object.keys(hooks.hooks); - const codexSupported = new Set([ - "SessionStart", - "UserPromptSubmit", - "PreToolUse", - "PostToolUse", - "PermissionRequest", - "PreCompact", - "PostCompact", - "Stop", - ]); - for (const event of events) { - expect(codexSupported.has(event), `unexpected event "${event}" in hooks.codex.json`).toBe(true); - } - expect(events).toContain("SessionStart"); - expect(events).toContain("UserPromptSubmit"); - expect(events).toContain("PreToolUse"); - expect(events).toContain("PostToolUse"); - expect(events).toContain("PreCompact"); - expect(events).toContain("Stop"); - }); - - it("hook command scripts referenced in hooks.codex.json exist on disk", () => { - const hooks = readJson<{ hooks: Record }>( - join(pluginRoot, "hooks/hooks.codex.json"), - ); - const scriptRefs = new Set(); - for (const entries of Object.values(hooks.hooks)) { - for (const entry of entries) { - for (const handler of entry.hooks) { - const match = handler.command.match(/\$\{CLAUDE_PLUGIN_ROOT\}\/(scripts\/[^\s"]+)/); - if (match) scriptRefs.add(match[1]); - } - } - } - expect(scriptRefs.size).toBeGreaterThan(0); - for (const rel of scriptRefs) { - expect(existsSync(join(pluginRoot, rel)), `missing hook script: ${rel}`).toBe(true); - } - }); -}); - -describe("Codex marketplace.json (.codex-plugin/marketplace.json at repo root)", () => { - it("ships a marketplace manifest pointing at the plugin/ subdirectory", () => { - const marketplacePath = join(repoRoot, ".codex-plugin/marketplace.json"); - expect(existsSync(marketplacePath)).toBe(true); - const marketplace = readJson<{ - name: string; - plugins: Array<{ - name: string; - source: { source: string; url: string; path: string; ref?: string }; - }>; - }>(marketplacePath); - expect(marketplace.name).toBe("agent-memory-lab"); - expect(marketplace.plugins).toHaveLength(1); - const entry = marketplace.plugins[0]; - expect(entry.name).toBe("agent-memory-lab"); - expect(entry.source.source).toBe("git-subdir"); - expect(entry.source.path).toBe("./plugin"); - expect(entry.source.url).toBe("https://github.com/MaimoryLab/agentmemory-lab.git"); - expect(entry.source.ref).toBe("main"); - }); -}); diff --git a/test/compress-file.test.ts b/test/compress-file.test.ts deleted file mode 100644 index 9b6820b3..00000000 --- a/test/compress-file.test.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -const fileStore = new Map(); -const symlinkPaths = new Set(); -const openEloopPaths = new Set(); - -vi.mock("node:fs/promises", () => ({ - lstat: vi.fn(async (path: string) => { - if (symlinkPaths.has(path)) { - return { isSymbolicLink: () => true }; - } - if (!fileStore.has(path)) { - throw Object.assign(new Error(`ENOENT: no such file or directory, lstat '${path}'`), { - code: "ENOENT", - }); - } - return { isSymbolicLink: () => false }; - }), - open: vi.fn(async (path: string) => { - if (openEloopPaths.has(path)) { - throw Object.assign(new Error("ELOOP: too many levels of symbolic links"), { - code: "ELOOP", - }); - } - return { - writeFile: vi.fn(async (content: string) => { - fileStore.set(path, content); - }), - close: vi.fn(async () => {}), - }; - }), - readFile: vi.fn(async (path: string) => { - const value = fileStore.get(path); - if (value === undefined) throw new Error("ENOENT"); - return value; - }), - writeFile: vi.fn(async (path: string, content: string) => { - fileStore.set(path, content); - }), -})); - -import { registerCompressFileFunction } from "../src/functions/compress-file.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("mem::compress-file", () => { - let sdk: ReturnType; - let kv: ReturnType; - let summarize: ReturnType; - - beforeEach(() => { - fileStore.clear(); - symlinkPaths.clear(); - openEloopPaths.clear(); - sdk = mockSdk(); - kv = mockKV(); - summarize = vi.fn(); - registerCompressFileFunction( - sdk as never, - kv as never, - { name: "test-provider", summarize, compress: summarize } as never, - ); - }); - - it("rejects symlinks", async () => { - symlinkPaths.add("/tmp/notes.md"); - const result = (await sdk.trigger("mem::compress-file", { - filePath: "/tmp/notes.md", - })) as { success: boolean; error: string }; - expect(result.success).toBe(false); - expect(result.error).toContain("symlink"); - expect(summarize).not.toHaveBeenCalled(); - expect(fileStore.size).toBe(0); - }); - - it("rejects TOCTOU symlink swap at write time via O_NOFOLLOW", async () => { - const path = "/tmp/notes.md"; - fileStore.set( - path, - "# Title\n\nVisit https://example.com\n\n```ts\nconst x = 1;\n```\n\nContent.", - ); - summarize.mockResolvedValue( - "# Title\n\nVisit https://example.com\n\n```ts\nconst x = 1;\n```\n\nShort.", - ); - openEloopPaths.add(path); - - const result = (await sdk.trigger("mem::compress-file", { - filePath: path, - })) as { success: boolean; error: string }; - expect(result.success).toBe(false); - expect(result.error).toContain("symlink"); - }); - - it("rejects non-markdown paths", async () => { - const result = (await sdk.trigger("mem::compress-file", { - filePath: "/tmp/readme.txt", - })) as { success: boolean; error: string }; - expect(result.success).toBe(false); - expect(result.error).toContain(".md"); - }); - - it("returns file not found for missing paths", async () => { - const result = (await sdk.trigger("mem::compress-file", { - filePath: "/tmp/nonexistent.md", - })) as { success: boolean; error: string }; - expect(result.success).toBe(false); - expect(result.error).toContain("not found"); - }); - - it("compresses markdown and writes .original.md backup", async () => { - const path = "/tmp/notes.md"; - fileStore.set( - path, - "# Title\n\nVisit https://example.com\n\n```ts\nconst x = 1;\n```\n\nSome long explanation.", - ); - - summarize.mockResolvedValue( - "# Title\n\nVisit https://example.com\n\n```ts\nconst x = 1;\n```\n\nShort explanation.", - ); - - const result = (await sdk.trigger("mem::compress-file", { - filePath: path, - })) as { - success: boolean; - backupPath: string; - compressedChars: number; - originalChars: number; - }; - - expect(result.success).toBe(true); - expect(result.backupPath).toBe("/tmp/notes.original.md"); - expect(fileStore.get("/tmp/notes.original.md")).toContain("Some long explanation."); - expect(fileStore.get(path)).toContain("Short explanation."); - expect(result.compressedChars).toBeLessThan(result.originalChars); - }); - - it("fails validation when URLs change", async () => { - const path = "/tmp/guide.md"; - fileStore.set(path, "# Guide\n\nhttps://example.com\n"); - summarize.mockResolvedValue("# Guide\n\nhttps://different.example.com\n"); - - const result = (await sdk.trigger("mem::compress-file", { - filePath: path, - })) as { success: boolean; error: string; details: string[] }; - - expect(result.success).toBe(false); - expect(result.error).toContain("validation"); - expect(result.details.some((d) => d.includes("url"))).toBe(true); - expect(fileStore.get("/tmp/guide.original.md")).toBeUndefined(); - }); - - it("uses a distinct backup path for *.original.md inputs", async () => { - const path = "/tmp/notes.original.md"; - fileStore.set(path, "# Title\n\nLong original body."); - summarize.mockResolvedValue("# Title\n\nShort body."); - - const result = (await sdk.trigger("mem::compress-file", { - filePath: path, - })) as { success: boolean; backupPath: string }; - - expect(result.success).toBe(true); - expect(result.backupPath).toBe("/tmp/notes.original.backup.md"); - expect(fileStore.get("/tmp/notes.original.backup.md")).toBe( - "# Title\n\nLong original body.", - ); - expect(fileStore.get(path)).toBe("# Title\n\nShort body."); - }); -}); diff --git a/test/confidence.test.ts b/test/confidence.test.ts deleted file mode 100644 index 74f0a6f3..00000000 --- a/test/confidence.test.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerRelationsFunction } from "../src/functions/relations.js"; -import type { Memory, MemoryRelation } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - getFunction: (id: string) => functions.get(id), - }; -} - -function makeMemory(overrides: Partial = {}): Memory { - return { - id: "mem_1", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "pattern", - title: "Test memory", - content: "This is a test memory", - concepts: ["test"], - files: [], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, - ...overrides, - }; -} - -describe("Confidence Scoring", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerRelationsFunction(sdk as never, kv as never); - }); - - it("computes default confidence from co-occurrence and recency", async () => { - const mem1 = makeMemory({ - id: "mem_1", - sessionIds: ["ses_1", "ses_2"], - updatedAt: new Date().toISOString(), - }); - const mem2 = makeMemory({ - id: "mem_2", - sessionIds: ["ses_1", "ses_2", "ses_3"], - updatedAt: new Date().toISOString(), - }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - - const result = (await sdk.trigger("mem::relate", { - sourceId: "mem_1", - targetId: "mem_2", - type: "related", - })) as { success: boolean; relation: MemoryRelation }; - - expect(result.success).toBe(true); - expect(result.relation.confidence).toBeGreaterThan(0.5); - expect(result.relation.confidence).toBeLessThanOrEqual(1); - }); - - it("uses explicit confidence when provided", async () => { - const mem1 = makeMemory({ id: "mem_1" }); - const mem2 = makeMemory({ id: "mem_2" }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - - const result = (await sdk.trigger("mem::relate", { - sourceId: "mem_1", - targetId: "mem_2", - type: "related", - confidence: 0.95, - })) as { success: boolean; relation: MemoryRelation }; - - expect(result.relation.confidence).toBe(0.95); - }); - - it("clamps confidence to [0, 1]", async () => { - const mem1 = makeMemory({ id: "mem_1" }); - const mem2 = makeMemory({ id: "mem_2" }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - - const over = (await sdk.trigger("mem::relate", { - sourceId: "mem_1", - targetId: "mem_2", - type: "related", - confidence: 1.5, - })) as { success: boolean; relation: MemoryRelation }; - expect(over.relation.confidence).toBe(1); - - const mem3 = makeMemory({ id: "mem_3" }); - await kv.set("mem:memories", "mem_3", mem3); - - const under = (await sdk.trigger("mem::relate", { - sourceId: "mem_1", - targetId: "mem_3", - type: "related", - confidence: -0.5, - })) as { success: boolean; relation: MemoryRelation }; - expect(under.relation.confidence).toBe(0); - }); - - it("mem::get-related sorts by confidence desc", async () => { - const mem1 = makeMemory({ id: "mem_1", relatedIds: ["mem_2", "mem_3"] }); - const mem2 = makeMemory({ id: "mem_2", relatedIds: ["mem_1"] }); - const mem3 = makeMemory({ id: "mem_3", relatedIds: ["mem_1"] }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - await kv.set("mem:memories", "mem_3", mem3); - - await kv.set("mem:relations", "rel_low", { - type: "related", - sourceId: "mem_1", - targetId: "mem_2", - createdAt: new Date().toISOString(), - confidence: 0.3, - } as MemoryRelation); - await kv.set("mem:relations", "rel_high", { - type: "related", - sourceId: "mem_1", - targetId: "mem_3", - createdAt: new Date().toISOString(), - confidence: 0.9, - } as MemoryRelation); - - const result = (await sdk.trigger("mem::get-related", { - memoryId: "mem_1", - maxHops: 1, - })) as { results: Array<{ memory: Memory; hop: number; confidence: number }> }; - - expect(result.results.length).toBe(2); - expect(result.results[0].confidence).toBeGreaterThanOrEqual( - result.results[1].confidence, - ); - }); - - it("minConfidence filter works", async () => { - const mem1 = makeMemory({ id: "mem_1", relatedIds: ["mem_2", "mem_3"] }); - const mem2 = makeMemory({ id: "mem_2", relatedIds: ["mem_1"] }); - const mem3 = makeMemory({ id: "mem_3", relatedIds: ["mem_1"] }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - await kv.set("mem:memories", "mem_3", mem3); - - await kv.set("mem:relations", "rel_low", { - type: "related", - sourceId: "mem_1", - targetId: "mem_2", - createdAt: new Date().toISOString(), - confidence: 0.2, - } as MemoryRelation); - await kv.set("mem:relations", "rel_high", { - type: "related", - sourceId: "mem_1", - targetId: "mem_3", - createdAt: new Date().toISOString(), - confidence: 0.8, - } as MemoryRelation); - - const result = (await sdk.trigger("mem::get-related", { - memoryId: "mem_1", - maxHops: 1, - minConfidence: 0.5, - })) as { results: Array<{ memory: Memory; hop: number; confidence: number }> }; - - expect(result.results.length).toBe(1); - expect(result.results[0].memory.id).toBe("mem_3"); - }); - - it("mem::evolve creates supersedes relation with confidence=1.0", async () => { - const original = makeMemory({ id: "mem_old", version: 1 }); - await kv.set("mem:memories", "mem_old", original); - - await sdk.trigger("mem::evolve", { - memoryId: "mem_old", - newContent: "Updated content", - }); - - const relations = await kv.list("mem:relations"); - const supersedesRel = relations.find((r) => r.type === "supersedes"); - expect(supersedesRel).toBeDefined(); - expect(supersedesRel!.confidence).toBe(1.0); - }); - - it("old relations without confidence default to 0.5", async () => { - const mem1 = makeMemory({ id: "mem_1", relatedIds: ["mem_2"] }); - const mem2 = makeMemory({ id: "mem_2", relatedIds: ["mem_1"] }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - - await kv.set("mem:relations", "rel_old", { - type: "related", - sourceId: "mem_1", - targetId: "mem_2", - createdAt: new Date().toISOString(), - } as MemoryRelation); - - const result = (await sdk.trigger("mem::get-related", { - memoryId: "mem_1", - maxHops: 1, - })) as { results: Array<{ memory: Memory; hop: number; confidence: number }> }; - - expect(result.results.length).toBe(1); - expect(result.results[0].confidence).toBe(0.5); - }); -}); diff --git a/test/config.test.ts b/test/config.test.ts new file mode 100644 index 00000000..512bf979 --- /dev/null +++ b/test/config.test.ts @@ -0,0 +1,265 @@ +import assert from "node:assert/strict"; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { getAppPaths } from "../src/paths.js"; +import { + ensureDefaultEnv, + formatEnvFile, + loadConfig, + loadEnvConfig, + loadSecrets, + maskSecret, + parseEnvFile, + saveConfig, + saveEnvConfig, + saveSecrets +} from "../src/config.js"; +import { resolveSourcePath, resolveSourcePaths } from "../src/sources/scan.js"; + +test("config reads defaults and persists source paths", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-config-")); + try { + const paths = getAppPaths(dir); + assert.deepEqual(loadConfig(paths), { + sources: { + codex: {}, + "claude-code": {} + }, + llm: { + enabled: true, + provider: "openai", + model: "deepseek/deepseek-v4-flash", + endpoint: "https://api.novita.ai/openai/v1", + thinkingDepth: "medium", + timeoutMs: 120000 + }, + organize: { + sinceDays: 7, + maxInteractionsPerSession: 10, + maxSessions: 16, + maxObservationsPerSession: 40 + } + }); + + const config = { + sources: { + codex: { path: join(dir, "codex") }, + "claude-code": { path: join(dir, "claude") } + }, + llm: { + enabled: true, + provider: "openai" as const, + model: "custom/model", + endpoint: "https://llm.example.test/v1", + thinkingDepth: "high" as const, + timeoutMs: 30000 + }, + organize: { + sinceDays: 14, + maxInteractionsPerSession: 20, + maxSessions: 12, + maxObservationsPerSession: 30 + } + }; + saveConfig(paths, config); + assert.deepEqual(loadConfig(paths), config); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("config rejects invalid files and preserves source path precedence", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-config-invalid-")); + const previousCodex = process.env.AI_TODO_CODEX_HOME; + delete process.env.AI_TODO_CODEX_HOME; + + try { + const paths = getAppPaths(dir); + mkdirSync(paths.configDir, { recursive: true }); + writeFileSync(paths.configPath, "{"); + assert.throws(() => loadConfig(paths), /config_invalid/); + + const explicit = join(dir, "explicit"); + const env = join(dir, "env"); + const configPath = join(dir, "config-codex"); + saveConfig(paths, { + sources: { codex: { path: configPath }, "claude-code": {} }, + llm: { + enabled: true, + provider: "openai", + model: "deepseek/deepseek-v4-flash", + endpoint: "https://api.novita.ai/openai/v1", + thinkingDepth: "medium", + timeoutMs: 120000 + }, + organize: { + sinceDays: 7, + maxInteractionsPerSession: 10, + maxSessions: 8, + maxObservationsPerSession: 40 + } + }); + assert.equal(resolveSourcePath("codex", explicit, paths), explicit); + process.env.AI_TODO_CODEX_HOME = env; + assert.equal(resolveSourcePath("codex", undefined, paths), env); + delete process.env.AI_TODO_CODEX_HOME; + assert.equal(resolveSourcePath("codex", undefined, paths), configPath); + } finally { + if (previousCodex === undefined) { + delete process.env.AI_TODO_CODEX_HOME; + } else { + process.env.AI_TODO_CODEX_HOME = previousCodex; + } + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("config rejects invalid llm settings", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-config-llm-invalid-")); + try { + const paths = getAppPaths(dir); + assert.throws(() => saveConfig(paths, { + sources: { codex: {}, "claude-code": {} }, + llm: { + enabled: true, + provider: "anthropic" as any, + model: "model", + endpoint: "https://example.test/v1", + thinkingDepth: "medium", + timeoutMs: 120000 + }, + organize: { sinceDays: 7, maxInteractionsPerSession: 10, maxSessions: 8, maxObservationsPerSession: 40 } + }), /config_invalid/); + assert.throws(() => saveConfig(paths, { + sources: { codex: {}, "claude-code": {} }, + llm: { + enabled: true, + provider: "openai", + model: "", + endpoint: "https://example.test/v1", + thinkingDepth: "medium", + timeoutMs: 120000 + }, + organize: { sinceDays: 7, maxInteractionsPerSession: 10, maxSessions: 8, maxObservationsPerSession: 40 } + }), /config_invalid/); + assert.throws(() => saveConfig(paths, { + sources: { codex: {}, "claude-code": {} }, + llm: { + enabled: true, + provider: "openai", + model: "model", + endpoint: "", + thinkingDepth: "medium", + timeoutMs: 120000 + }, + organize: { sinceDays: 7, maxInteractionsPerSession: 10, maxSessions: 8, maxObservationsPerSession: 40 } + }), /config_invalid/); + assert.throws(() => saveConfig(paths, { + sources: { codex: {}, "claude-code": {} }, + llm: { + enabled: true, + provider: "openai", + model: "model", + endpoint: "https://example.test/v1", + thinkingDepth: "medium", + timeoutMs: 0 + }, + organize: { sinceDays: 7, maxInteractionsPerSession: 10, maxSessions: 8, maxObservationsPerSession: 40 } + }), /config_invalid/); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("secrets persist separately and mask api keys", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-secrets-")); + try { + const paths = getAppPaths(dir); + assert.deepEqual(loadSecrets(paths), {}); + saveSecrets(paths, { llmApiKey: "dummy-llm-key-value" }); + assert.equal(loadSecrets(paths).llmApiKey, "dummy-llm-key-value"); + assert.equal(maskSecret("dummy-llm-key-value"), "dum****alue"); + assert.ok(existsSync(paths.secretsPath)); + assert.match(readFileSync(paths.secretsPath, "utf8"), /dummy-llm-key-value/); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("env config parses comments, quotes, defaults, and masks api keys", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-env-")); + try { + const paths = getAppPaths(dir); + saveEnvConfig(paths, parseEnvFile([ + "# local config", + "AI_TODO_CODEX_HOME='/tmp/codex sessions'", + "AI_TODO_LLM_MODEL=custom/model # comment", + "AI_TODO_LLM_API_KEY=\"dummy-llm-key-value\"", + "AI_TODO_ORGANIZE_SINCE_DAYS=30" + ].join("\n"))); + const env = loadEnvConfig(paths); + assert.equal(env.AI_TODO_CODEX_HOME, "/tmp/codex sessions"); + assert.equal(env.AI_TODO_LLM_MODEL, "custom/model"); + assert.equal(loadSecrets(paths).llmApiKey, "dummy-llm-key-value"); + assert.match(formatEnvFile(env), /AI_TODO_LLM_API_KEY=dummy-llm-key-value/); + assert.throws(() => parseEnvFile("UNSUPPORTED=value"), /env_invalid/); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("env config ignores removed python setting for existing installs", () => { + const removedKey = "AI_TODO_LLM_" + "PYTHON"; + assert.deepEqual(parseEnvFile(`${removedKey}=python3\nAI_TODO_LLM_MODEL=custom/model`), { + AI_TODO_LLM_MODEL: "custom/model" + }); +}); + +test("default env generation writes necessary values without empty api key", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-env-default-")); + try { + const paths = getAppPaths(dir); + ensureDefaultEnv(paths); + const text = readFileSync(paths.envPath, "utf8"); + assert.match(text, /AI_TODO_CODEX_HOME=.*\.codex/); + assert.doesNotMatch(text, /AI_TODO_CODEX_HOME=.*\.codex\/sessions/); + assert.match(text, /AI_TODO_LLM_MODEL=deepseek\/deepseek-v4-flash/); + assert.match(text, /AI_TODO_ORGANIZE_SINCE_DAYS=7/); + assert.match(text, /AI_TODO_ORGANIZE_MAX_SESSIONS=16/); + assert.match(text, /AI_TODO_ORGANIZE_MAX_OBSERVATIONS_PER_SESSION=40/); + assert.doesNotMatch(text, /AI_TODO_LLM_API_KEY/); + assert.equal((readFileSync(paths.envPath).byteLength > 0), true); + assert.equal(statSync(paths.envPath).mode & 0o777, 0o600); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("codex home expands to sessions and archived sessions roots", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-codex-roots-")); + try { + const codexHome = join(dir, ".codex"); + mkdirSync(join(codexHome, "sessions"), { recursive: true }); + mkdirSync(join(codexHome, "archived_sessions"), { recursive: true }); + assert.deepEqual(resolveSourcePaths("codex", codexHome), [ + join(codexHome, "sessions"), + join(codexHome, "archived_sessions") + ]); + assert.equal(resolveSourcePath("codex", codexHome), join(codexHome, "sessions")); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("stale temporary source paths are ignored when loading config", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-stale-config-")); + try { + const paths = getAppPaths(dir); + saveEnvConfig(paths, parseEnvFile("AI_TODO_CODEX_HOME=/var/folders/x/ai-todo-http-deadbeef/codex")); + assert.deepEqual(loadConfig(paths).sources.codex, {}); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); diff --git a/test/consistency.test.ts b/test/consistency.test.ts deleted file mode 100644 index e0871cbf..00000000 --- a/test/consistency.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; -import { readFileSync } from "node:fs"; -import { join } from "node:path"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { getAllTools } from "../src/mcp/tools-registry.js"; -import { VERSION } from "../src/version.js"; - -const ROOT = join(import.meta.dirname, ".."); - -function readText(relativePath: string): string { - return readFileSync(join(ROOT, relativePath), "utf-8"); -} - -function countRestApiEndpoints(): number { - const src = readText("src/triggers/api.ts"); - return Array.from(src.matchAll(/api_path:\s*["`]/g)).length; -} - -describe("Consistency checks", () => { - const toolCount = getAllTools().length; - const restEndpointCount = countRestApiEndpoints(); - - it("version.ts matches package.json", () => { - const pkg = JSON.parse(readText("package.json")); - expect(VERSION).toBe(pkg.version); - }); - - it("plugin.json version matches package.json", () => { - const pkg = JSON.parse(readText("package.json")); - const plugin = JSON.parse(readText("plugin/.claude-plugin/plugin.json")); - expect(plugin.version).toBe(pkg.version); - }); - - it("export-import.ts supports current version", () => { - const src = readText("src/functions/export-import.ts"); - expect(src).toContain(`"${VERSION}"`); - }); - - it("README mentions correct MCP tool count", () => { - const readme = readText("README.md"); - const toolCountPattern = new RegExp(`${toolCount}\\s+MCP tools`); - expect(readme).toMatch(toolCountPattern); - const toolResourcePattern = new RegExp(`${toolCount}\\s+tools,\\s+6\\s+resources`); - expect(readme).toMatch(toolResourcePattern); - }); - - it("documented REST endpoint counts match registered API paths", () => { - const readme = readText("README.md"); - const agents = readText("AGENTS.md"); - const index = readText("src/index.ts"); - - expect(restEndpointCount).toBeGreaterThan(0); - expect(readme).toContain(`${restEndpointCount} endpoints on port`); - expect(agents).toContain(`${restEndpointCount} REST endpoints`); - expect(index).toContain(`REST API: ${restEndpointCount} endpoints`); - }); - - it("all tool names are unique", () => { - const tools = getAllTools(); - const names = new Set(tools.map((t) => t.name)); - expect(names.size).toBe(tools.length); - }); - - it("all tools have name, description, and inputSchema", () => { - for (const tool of getAllTools()) { - expect(tool.name).toBeTruthy(); - expect(tool.description).toBeTruthy(); - expect(tool.inputSchema).toBeDefined(); - expect(tool.inputSchema.type).toBe("object"); - } - }); - - it("every host-path bind mount in docker-compose.yml is in the published files list (#136)", () => { - // Regression guard for #136: docker-compose.yml references - // ./iii-config.docker.yaml as a read-only bind mount, but the file - // was missing from the published tarball. Docker silently creates - // missing bind sources as empty directories, so the engine crashed - // with "Is a directory (os error 21)" at /app/config.yaml. - const compose = readText("docker-compose.yml"); - const pkg = JSON.parse(readText("package.json")); - const files: string[] = pkg.files ?? []; - - // Match `./:` style bind mounts. We only care - // about files that live in the repo root (so they'd be shipped via - // the `files` field). `iii-data:/data` (a named volume) has no `./` - // prefix and is correctly skipped. - const bindRe = /^\s*-\s+\.\/([^\s:]+):[^\s]+/gm; - const sources: string[] = []; - for (const m of compose.matchAll(bindRe)) sources.push(m[1]!); - - expect(sources.length).toBeGreaterThan(0); - for (const src of sources) { - // Any nested path would need a directory entry in `files` (e.g. - // `dist/`); for top-level files, the exact name must be listed. - const topLevel = src.split("/")[0]!; - const covered = - files.includes(src) || - files.includes(topLevel) || - files.includes(`${topLevel}/`); - expect( - covered, - `docker-compose.yml mounts ./${src} but package.json "files" does not ship it — ${topLevel} would be auto-created as an empty dir on install, breaking \`npx @agentmemory/agentmemory\``, - ).toBe(true); - } - }); -}); diff --git a/test/consolidate-project-scope.test.ts b/test/consolidate-project-scope.test.ts deleted file mode 100644 index e8efc45c..00000000 --- a/test/consolidate-project-scope.test.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("../src/functions/audit.js", () => ({ - recordAudit: vi.fn(), -})); - -import { registerConsolidateFunction } from "../src/functions/consolidate.js"; -import { KV } from "../src/state/schema.js"; -import type { CompressedObservation, Memory, MemoryProvider, Session } from "../src/types.js"; - -function makeMockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function makeMockSdk() { - const functions = new Map(); - return { - registerFunction: (id: string, handler: Function) => { - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : (idOrInput as { payload: unknown }).payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function registered: ${id}`); - return fn(payload); - }, - }; -} - -function makeProvider(title = "synthesized memory title"): MemoryProvider { - return { - name: "mock", - compress: vi.fn().mockResolvedValue( - ` - pattern - ${title} - synthesized content about the concept - auth - src/auth.ts - 7 - `, - ), - embed: vi.fn().mockResolvedValue(new Float32Array(384)), - embedBatch: vi.fn().mockResolvedValue([]), - dimensions: 384, - compressionModel: "mock-model", - }; -} - -function makeSession(id: string, project: string): Session { - return { - id, - project, - cwd: `/srv/${project}`, - startedAt: new Date().toISOString(), - status: "completed", - observationCount: 5, - }; -} - -function makeObs(id: string, sessionId: string, concept: string): CompressedObservation { - return { - id, - sessionId, - timestamp: new Date().toISOString(), - type: "decision", - title: `${concept} observation ${id}`, - facts: [`fact about ${concept}`], - narrative: `detailed narrative about ${concept} pattern usage`, - concepts: [concept], - files: ["src/auth.ts"], - importance: 8, - }; -} - -function makeExistingMemory(id: string, title: string, project?: string): Memory { - return { - id, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "pattern", - title, - content: "existing content", - concepts: ["auth"], - files: ["src/auth.ts"], - sessionIds: [], - strength: 6, - version: 1, - isLatest: true, - ...(project !== undefined && { project }), - }; -} - -describe("mem::consolidate — cross-project existingMatch guard", () => { - it("does not evolve a memory from a different project even when titles match", async () => { - const sdk = makeMockSdk(); - const kv = makeMockKV(); - const provider = makeProvider("synthesized memory title"); - - // A memory scoped to "web" with the same title the provider will generate - const webMemory = makeExistingMemory("mem_web", "synthesized memory title", "web"); - await kv.set(KV.memories, webMemory.id, webMemory); - - // Session and observations for "api" project - const apiSession = makeSession("sess_api", "api"); - await kv.set(KV.sessions, apiSession.id, apiSession); - for (let i = 0; i < 3; i++) { - await kv.set( - KV.observations(apiSession.id), - `obs_${i}`, - makeObs(`obs_${i}`, apiSession.id, "auth"), - ); - } - - registerConsolidateFunction(sdk as never, kv as never, provider as never); - await sdk.trigger("mem::consolidate", { project: "api", minObservations: 1 }); - - // The web memory must remain untouched — isLatest still true - const webStored = await kv.get(KV.memories, webMemory.id); - expect(webStored?.isLatest).toBe(true); - expect(webStored?.project).toBe("web"); - - // A new "api" memory should have been created - const allMemories = await kv.list(KV.memories); - const apiMemories = allMemories.filter((m) => m.project === "api" && m.isLatest); - expect(apiMemories).toHaveLength(1); - expect(apiMemories[0].title).toBe("synthesized memory title"); - }); - - it("evolves an existing memory within the same project when titles match", async () => { - const sdk = makeMockSdk(); - const kv = makeMockKV(); - const provider = makeProvider("synthesized memory title"); - - // A memory already scoped to "api" with the same title - const apiMemory = makeExistingMemory("mem_api_old", "synthesized memory title", "api"); - await kv.set(KV.memories, apiMemory.id, apiMemory); - - const apiSession = makeSession("sess_api", "api"); - await kv.set(KV.sessions, apiSession.id, apiSession); - for (let i = 0; i < 3; i++) { - await kv.set( - KV.observations(apiSession.id), - `obs_${i}`, - makeObs(`obs_${i}`, apiSession.id, "auth"), - ); - } - - registerConsolidateFunction(sdk as never, kv as never, provider as never); - await sdk.trigger("mem::consolidate", { project: "api", minObservations: 1 }); - - // The old api memory should have been marked non-latest (evolved) - const oldMemory = await kv.get(KV.memories, apiMemory.id); - expect(oldMemory?.isLatest).toBe(false); - - // A new evolved memory should exist - const allMemories = await kv.list(KV.memories); - const latestApi = allMemories.filter((m) => m.project === "api" && m.isLatest); - expect(latestApi).toHaveLength(1); - expect(latestApi[0].id).not.toBe(apiMemory.id); - expect(latestApi[0].parentId).toBe(apiMemory.id); - }); - - it("unscoped consolidation may evolve any existing memory regardless of project (background cron behavior)", async () => { - const sdk = makeMockSdk(); - const kv = makeMockKV(); - const provider = makeProvider("synthesized memory title"); - - // A scoped memory that an unscoped consolidation run should be able to evolve - const scopedMemory = makeExistingMemory("mem_api_old", "synthesized memory title", "api"); - await kv.set(KV.memories, scopedMemory.id, scopedMemory); - - // Session with no project restriction — unscoped consolidation - const session = makeSession("sess_any", "any"); - await kv.set(KV.sessions, session.id, session); - for (let i = 0; i < 3; i++) { - await kv.set( - KV.observations(session.id), - `obs_${i}`, - makeObs(`obs_${i}`, session.id, "auth"), - ); - } - - registerConsolidateFunction(sdk as never, kv as never, provider as never); - // No project passed — unscoped consolidation - await sdk.trigger("mem::consolidate", { minObservations: 1 }); - - // The existing scoped memory should have been evolved (unscoped run is unrestricted) - const old = await kv.get(KV.memories, scopedMemory.id); - expect(old?.isLatest).toBe(false); - - // The evolved successor should be latest and carry no project (unscoped run) - const allMemories = await kv.list(KV.memories); - const successor = allMemories.find((m) => m.isLatest && m.id !== scopedMemory.id); - expect(successor).toBeDefined(); - expect(successor?.isLatest).toBe(true); - expect(successor?.project).toBeUndefined(); - }); - - it("stamps the correct project on newly created memories", async () => { - const sdk = makeMockSdk(); - const kv = makeMockKV(); - const provider = makeProvider("brand new memory"); - - const session = makeSession("sess_api", "api"); - await kv.set(KV.sessions, session.id, session); - for (let i = 0; i < 3; i++) { - await kv.set( - KV.observations(session.id), - `obs_${i}`, - makeObs(`obs_${i}`, session.id, "auth"), - ); - } - - registerConsolidateFunction(sdk as never, kv as never, provider as never); - await sdk.trigger("mem::consolidate", { project: "api", minObservations: 1 }); - - const memories = await kv.list(KV.memories); - expect(memories).toHaveLength(1); - expect(memories[0].project).toBe("api"); - }); - - it("leaves project undefined on memories when consolidate is called without a project", async () => { - const sdk = makeMockSdk(); - const kv = makeMockKV(); - const provider = makeProvider("unscoped memory"); - - const session = makeSession("sess_any", "any"); - await kv.set(KV.sessions, session.id, session); - for (let i = 0; i < 3; i++) { - await kv.set( - KV.observations(session.id), - `obs_${i}`, - makeObs(`obs_${i}`, session.id, "auth"), - ); - } - - registerConsolidateFunction(sdk as never, kv as never, provider as never); - await sdk.trigger("mem::consolidate", { minObservations: 1 }); - - const memories = await kv.list(KV.memories); - expect(memories).toHaveLength(1); - expect(memories[0].project).toBeUndefined(); - }); -}); diff --git a/test/consolidation-default.test.ts b/test/consolidation-default.test.ts deleted file mode 100644 index d7a7d5da..00000000 --- a/test/consolidation-default.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -const ENV_KEYS = [ - "CONSOLIDATION_ENABLED", - "AGENTMEMORY_PROVIDER", - "ANTHROPIC_API_KEY", - "OPENAI_API_KEY", - "OPENAI_API_KEY_FOR_LLM", - "OPENROUTER_API_KEY", - "GEMINI_API_KEY", - "GOOGLE_API_KEY", - "MINIMAX_API_KEY", - "OPENAI_BASE_URL", -]; - -const ORIGINAL_HOME = process.env["HOME"]; -const ORIGINAL_USERPROFILE = process.env["USERPROFILE"]; -const ORIGINAL: Record = {}; - -let sandboxHome: string; - -async function freshConfig() { - vi.resetModules(); - return await import("../src/config.js"); -} - -function writeEnv(contents: string) { - const dir = join(sandboxHome, ".agentmemory"); - mkdirSync(dir, { recursive: true }); - writeFileSync(join(dir, ".env"), contents); -} - -describe("isConsolidationEnabled default behavior", () => { - beforeEach(() => { - sandboxHome = mkdtempSync(join(tmpdir(), "agentmemory-consolidation-")); - process.env["HOME"] = sandboxHome; - process.env["USERPROFILE"] = sandboxHome; - for (const k of ENV_KEYS) { - ORIGINAL[k] = process.env[k]; - delete process.env[k]; - } - }); - - afterEach(() => { - if (ORIGINAL_HOME === undefined) delete process.env["HOME"]; - else process.env["HOME"] = ORIGINAL_HOME; - if (ORIGINAL_USERPROFILE === undefined) delete process.env["USERPROFILE"]; - else process.env["USERPROFILE"] = ORIGINAL_USERPROFILE; - for (const k of ENV_KEYS) { - if (ORIGINAL[k] === undefined) delete process.env[k]; - else process.env[k] = ORIGINAL[k]; - } - rmSync(sandboxHome, { recursive: true, force: true }); - }); - - it("returns false when no LLM provider configured (default BM25-only mode)", async () => { - writeEnv(""); - const cfg = await freshConfig(); - expect(cfg.isConsolidationEnabled()).toBe(false); - }); - - it("returns true by default when ANTHROPIC_API_KEY is set", async () => { - writeEnv("ANTHROPIC_API_KEY=sk-test-123"); - const cfg = await freshConfig(); - expect(cfg.isConsolidationEnabled()).toBe(true); - }); - - it("returns true by default when OPENAI_API_KEY is set", async () => { - writeEnv("OPENAI_API_KEY=sk-test-123"); - const cfg = await freshConfig(); - expect(cfg.isConsolidationEnabled()).toBe(true); - }); - - it("returns true by default when OPENAI_BASE_URL is set (local OpenAI-compatible)", async () => { - writeEnv("OPENAI_BASE_URL=http://localhost:1234/v1"); - const cfg = await freshConfig(); - expect(cfg.isConsolidationEnabled()).toBe(true); - }); - - it("returns true by default when AGENTMEMORY_PROVIDER=agent-sdk", async () => { - writeEnv("AGENTMEMORY_PROVIDER=agent-sdk"); - const cfg = await freshConfig(); - expect(cfg.isConsolidationEnabled()).toBe(true); - }); - - it("explicit CONSOLIDATION_ENABLED=false overrides provider-based default", async () => { - writeEnv("ANTHROPIC_API_KEY=sk-test-123\nCONSOLIDATION_ENABLED=false"); - const cfg = await freshConfig(); - expect(cfg.isConsolidationEnabled()).toBe(false); - }); - - it("explicit CONSOLIDATION_ENABLED=true overrides absence of provider", async () => { - writeEnv("CONSOLIDATION_ENABLED=true"); - const cfg = await freshConfig(); - expect(cfg.isConsolidationEnabled()).toBe(true); - }); - - it("AGENTMEMORY_PROVIDER=noop returns false even with API key set", async () => { - writeEnv("AGENTMEMORY_PROVIDER=noop\nANTHROPIC_API_KEY=sk-test-123"); - const cfg = await freshConfig(); - expect(cfg.isConsolidationEnabled()).toBe(false); - }); - - it("OPENAI_API_KEY_FOR_LLM=false scopes the key to embeddings only", async () => { - writeEnv("OPENAI_API_KEY=sk-test-123\nOPENAI_API_KEY_FOR_LLM=false"); - const cfg = await freshConfig(); - expect(cfg.isConsolidationEnabled()).toBe(false); - }); -}); diff --git a/test/consolidation-pipeline.test.ts b/test/consolidation-pipeline.test.ts deleted file mode 100644 index 5f72090c..00000000 --- a/test/consolidation-pipeline.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("../src/config.js", () => ({ - getConsolidationDecayDays: () => 30, - isConsolidationEnabled: vi.fn(() => true), -})); - -import { registerConsolidationPipelineFunction } from "../src/functions/consolidation-pipeline.js"; -import { isConsolidationEnabled } from "../src/config.js"; -import type { SessionSummary, Memory, SemanticMemory, ProceduralMemory } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeSummary(i: number): SessionSummary { - return { - sessionId: `ses_${i}`, - project: "test-project", - createdAt: new Date(Date.now() - i * 86400000).toISOString(), - title: `Session ${i} summary`, - narrative: `Worked on feature ${i}`, - keyDecisions: [`Decision ${i}`], - filesModified: [`src/file${i}.ts`], - concepts: ["typescript", "testing"], - observationCount: 5, - }; -} - -function makePattern(i: number): Memory { - return { - id: `mem_${i}`, - createdAt: "2026-01-01T00:00:00Z", - updatedAt: "2026-01-01T00:00:00Z", - type: "pattern", - title: `Pattern ${i}`, - content: `Always do thing ${i}`, - concepts: ["testing"], - files: [], - sessionIds: ["ses_1", "ses_2"], - strength: 5, - version: 1, - isLatest: true, - }; -} - -describe("Consolidation Pipeline", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - }); - - it("pipeline skips semantic when fewer than 5 summaries", async () => { - const provider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn(), - }; - registerConsolidationPipelineFunction(sdk as never, kv as never, provider as never); - - for (let i = 0; i < 3; i++) { - await kv.set("mem:summaries", `ses_${i}`, makeSummary(i)); - } - - const result = (await sdk.trigger("mem::consolidate-pipeline", { - tier: "semantic", - })) as { success: boolean; results: Record }; - - expect(result.success).toBe(true); - const semantic = result.results.semantic as { skipped: boolean; reason: string }; - expect(semantic.skipped).toBe(true); - expect(semantic.reason).toContain("fewer than 5"); - expect(provider.summarize).not.toHaveBeenCalled(); - }); - - it("pipeline skips procedural when fewer than 2 patterns", async () => { - const provider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn(), - }; - registerConsolidationPipelineFunction(sdk as never, kv as never, provider as never); - - const mem: Memory = { - ...makePattern(1), - sessionIds: ["ses_1", "ses_2"], - }; - await kv.set("mem:memories", "mem_1", mem); - - const result = (await sdk.trigger("mem::consolidate-pipeline", { - tier: "procedural", - })) as { success: boolean; results: Record }; - - expect(result.success).toBe(true); - const procedural = result.results.procedural as { skipped: boolean; reason: string }; - expect(procedural.skipped).toBe(true); - expect(procedural.reason).toContain("fewer than 2"); - }); - - it("with enough summaries, creates semantic memories from provider response", async () => { - const provider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn().mockResolvedValue( - `TypeScript is the primary language`, - ), - }; - registerConsolidationPipelineFunction(sdk as never, kv as never, provider as never); - - for (let i = 0; i < 6; i++) { - await kv.set("mem:summaries", `ses_${i}`, makeSummary(i)); - } - - const result = (await sdk.trigger("mem::consolidate-pipeline", { - tier: "semantic", - })) as { success: boolean; results: Record }; - - expect(result.success).toBe(true); - const semantic = result.results.semantic as { newFacts: number }; - expect(semantic.newFacts).toBe(1); - - const stored = await kv.list("mem:semantic"); - expect(stored.length).toBe(1); - expect(stored[0].fact).toBe("TypeScript is the primary language"); - expect(stored[0].confidence).toBe(0.9); - }); - - it("with enough patterns, creates procedural memories from provider response", async () => { - const provider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn().mockResolvedValue( - `Create test fileWrite assertions`, - ), - }; - registerConsolidationPipelineFunction(sdk as never, kv as never, provider as never); - - for (let i = 0; i < 3; i++) { - await kv.set("mem:memories", `mem_${i}`, makePattern(i)); - } - - const result = (await sdk.trigger("mem::consolidate-pipeline", { - tier: "procedural", - })) as { success: boolean; results: Record }; - - expect(result.success).toBe(true); - const procedural = result.results.procedural as { newProcedures: number }; - expect(procedural.newProcedures).toBe(1); - - const stored = await kv.list("mem:procedural"); - expect(stored.length).toBe(1); - expect(stored[0].name).toBe("Test Workflow"); - expect(stored[0].steps.length).toBe(2); - expect(stored[0].triggerCondition).toBe("when writing tests"); - }); - - it("consolidation records an audit entry", async () => { - const provider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn(), - }; - registerConsolidationPipelineFunction(sdk as never, kv as never, provider as never); - - await sdk.trigger("mem::consolidate-pipeline", { tier: "semantic" }); - - const audits = await kv.list("mem:audit"); - expect(audits.length).toBe(1); - }); - - it("pipeline returns early when consolidation is disabled", async () => { - vi.mocked(isConsolidationEnabled).mockReturnValue(false); - const provider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn(), - }; - registerConsolidationPipelineFunction(sdk as never, kv as never, provider as never); - - const result = (await sdk.trigger("mem::consolidate-pipeline", {})) as { - success: boolean; - skipped?: boolean; - reason?: string; - }; - - expect(result.success).toBe(false); - expect(result.skipped).toBe(true); - expect(result.reason).toContain("CONSOLIDATION_ENABLED"); - expect(provider.summarize).not.toHaveBeenCalled(); - vi.mocked(isConsolidationEnabled).mockReturnValue(true); - }); - - it("pipeline proceeds with force=true even when consolidation is disabled", async () => { - vi.mocked(isConsolidationEnabled).mockReturnValue(false); - const provider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn(), - }; - registerConsolidationPipelineFunction(sdk as never, kv as never, provider as never); - - const result = (await sdk.trigger("mem::consolidate-pipeline", { - force: true, - })) as { success: boolean; results: Record }; - - expect(result.success).toBe(true); - expect(result.results).toBeDefined(); - vi.mocked(isConsolidationEnabled).mockReturnValue(true); - }); -}); diff --git a/test/context-injection.test.ts b/test/context-injection.test.ts deleted file mode 100644 index d7689638..00000000 --- a/test/context-injection.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { spawn } from "node:child_process"; -import { join } from "node:path"; - -const HOOKS_DIR = join(import.meta.dirname, "..", "plugin", "scripts"); - -// Spawns a compiled plugin hook as a subprocess, feeds it JSON on stdin, -// and returns { stdout, stderr, exitCode, tookMs }. The test is about -// making sure the hook writes NOTHING to stdout when context injection is -// disabled — which is what Claude Code reads to decide whether to prepend -// memory context to the next tool turn. -function runHook( - scriptName: string, - stdin: string, - env: Record, -): Promise<{ - stdout: string; - stderr: string; - exitCode: number | null; - tookMs: number; -}> { - return new Promise((resolve, reject) => { - const start = Date.now(); - const child = spawn( - process.execPath, - [join(HOOKS_DIR, scriptName)], - { - env: { - // Start from a clean slate — don't leak test-runner env into - // the hook. Only pass PATH and anything explicitly set by the - // test case. - PATH: process.env["PATH"] ?? "", - ...env, - }, - stdio: ["pipe", "pipe", "pipe"], - }, - ); - - let stdout = ""; - let stderr = ""; - child.stdout.on("data", (chunk) => { - stdout += chunk.toString(); - }); - child.stderr.on("data", (chunk) => { - stderr += chunk.toString(); - }); - child.on("error", reject); - child.on("close", (exitCode) => { - resolve({ stdout, stderr, exitCode, tookMs: Date.now() - start }); - }); - - child.stdin.write(stdin); - child.stdin.end(); - }); -} - -describe("pre-tool-use hook — context injection gate (#143)", () => { - it("writes nothing to stdout when AGENTMEMORY_INJECT_CONTEXT is unset (default)", async () => { - const payload = JSON.stringify({ - session_id: "ses_test", - tool_name: "Read", - tool_input: { file_path: "src/foo.ts" }, - }); - // No AGENTMEMORY_* env vars at all — simulates a fresh Claude Pro - // install with no ~/.agentmemory/.env overrides. - const result = await runHook("pre-tool-use.mjs", payload, {}); - expect(result.stdout).toBe(""); - expect(result.exitCode).toBe(0); - }); - - it("writes nothing to stdout when AGENTMEMORY_INJECT_CONTEXT=false explicitly", async () => { - const payload = JSON.stringify({ - session_id: "ses_test", - tool_name: "Edit", - tool_input: { file_path: "src/foo.ts", old_string: "a", new_string: "b" }, - }); - const result = await runHook("pre-tool-use.mjs", payload, { - AGENTMEMORY_INJECT_CONTEXT: "false", - }); - expect(result.stdout).toBe(""); - expect(result.exitCode).toBe(0); - }); - - it("exits fast when disabled (no stdin consumption, no network fetch)", async () => { - // The disabled path must not open stdin or reach for fetch — it - // should return immediately. A 250ms budget is generous enough to - // account for Node startup on CI while still catching any accidental - // fetch round-trip or stdin buffering. - const result = await runHook("pre-tool-use.mjs", "", {}); - expect(result.tookMs).toBeLessThan(1000); - expect(result.stdout).toBe(""); - }); - - it("when AGENTMEMORY_INJECT_CONTEXT=true, hook still runs but safely errors on unreachable backend", async () => { - // Opt-in path. We point at a port that's guaranteed closed so the - // fetch fails fast; the hook must still exit cleanly (the whole - // point of the try/catch is not to break Claude Code) and must not - // echo anything to stdout when the fetch fails. - const payload = JSON.stringify({ - session_id: "ses_test", - tool_name: "Read", - tool_input: { file_path: "src/foo.ts" }, - }); - const result = await runHook("pre-tool-use.mjs", payload, { - AGENTMEMORY_INJECT_CONTEXT: "true", - AGENTMEMORY_URL: "http://127.0.0.1:1", - }); - expect(result.exitCode).toBe(0); - expect(result.stdout).toBe(""); - }); -}); - -describe("session-start hook — context injection gate (#143)", () => { - it("registers the session but writes nothing to stdout when AGENTMEMORY_INJECT_CONTEXT is unset", async () => { - // Session registration POST will fail against the unreachable URL, - // but the hook's try/catch must swallow that cleanly — Claude Code - // must never see an error at session start. - const payload = JSON.stringify({ - session_id: "ses_test", - cwd: "/tmp/fake-project", - }); - const result = await runHook("session-start.mjs", payload, { - AGENTMEMORY_URL: "http://127.0.0.1:1", - }); - expect(result.exitCode).toBe(0); - expect(result.stdout).toBe(""); - }); -}); diff --git a/test/context-lessons.test.ts b/test/context-lessons.test.ts deleted file mode 100644 index d915c939..00000000 --- a/test/context-lessons.test.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; -import { registerContextFunction } from "../src/functions/context.js"; -import { KV } from "../src/state/schema.js"; -import type { Lesson } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - if (!store.has(scope)) return []; - return Array.from(store.get(scope)!.values()) as T[]; - }, - }; -} - -type ContextHandler = (data: { - sessionId: string; - project: string; - budget?: number; -}) => Promise<{ context: string; blocks: number; tokens: number }>; - -function wireContext(kv: ReturnType, budget = 4000) { - let handler: ContextHandler | undefined; - const sdk = { - registerFunction: vi.fn((id: string, cb: ContextHandler) => { - if (id === "mem::context") handler = cb; - }), - } as unknown as import("iii-sdk").ISdk; - registerContextFunction(sdk, kv as never, budget); - if (!handler) throw new Error("mem::context not registered"); - return handler; -} - -function makeLesson(over: Partial = {}): Lesson { - const now = new Date().toISOString(); - return { - id: over.id ?? `lesson_${Math.random().toString(36).slice(2)}`, - content: over.content ?? "default lesson content", - context: over.context ?? "", - confidence: over.confidence ?? 0.7, - reinforcements: over.reinforcements ?? 1, - source: over.source ?? "manual", - sourceIds: over.sourceIds ?? [], - project: over.project, - tags: over.tags ?? [], - createdAt: over.createdAt ?? now, - updatedAt: over.updatedAt ?? now, - lastReinforcedAt: over.lastReinforcedAt, - lastDecayedAt: over.lastDecayedAt, - decayRate: over.decayRate ?? 0.05, - deleted: over.deleted, - }; -} - -async function seedLesson( - kv: ReturnType, - partial: Partial, -) { - const lesson = makeLesson(partial); - await kv.set(KV.lessons, lesson.id, lesson); - return lesson; -} - -describe("mem::context — lessons auto-injection (#457)", () => { - let kv: ReturnType; - let handler: ContextHandler; - - beforeEach(() => { - kv = mockKV(); - handler = wireContext(kv); - }); - - it("includes a 'Lessons Learned' block when KV has lessons for the project", async () => { - await seedLesson(kv, { - id: "lesson_a", - content: "always run npm test before commit", - project: "/tmp/proj", - confidence: 0.85, - }); - - const result = await handler({ - sessionId: "ses_a", - project: "/tmp/proj", - }); - - expect(result.context).toContain("Lessons Learned"); - expect(result.context).toContain("always run npm test before commit"); - expect(result.blocks).toBeGreaterThan(0); - }); - - it("omits the lessons block entirely when KV has no lessons", async () => { - const result = await handler({ - sessionId: "ses_empty", - project: "/tmp/proj", - }); - - expect(result.context).not.toContain("Lessons Learned"); - }); - - it("ranks project-scoped lessons above global lessons", async () => { - await seedLesson(kv, { - id: "lesson_global", - content: "global-lesson-marker", - project: undefined, - confidence: 0.9, - }); - await seedLesson(kv, { - id: "lesson_project", - content: "project-lesson-marker", - project: "/tmp/proj", - confidence: 0.7, - }); - - const result = await handler({ - sessionId: "ses_rank", - project: "/tmp/proj", - }); - - const projectIdx = result.context.indexOf("project-lesson-marker"); - const globalIdx = result.context.indexOf("global-lesson-marker"); - expect(projectIdx).toBeGreaterThan(-1); - expect(globalIdx).toBeGreaterThan(-1); - expect(projectIdx).toBeLessThan(globalIdx); - }); - - it("excludes lessons scoped to a different project", async () => { - await seedLesson(kv, { - id: "lesson_other", - content: "other-project-lesson", - project: "/tmp/other-project", - confidence: 0.9, - }); - - const result = await handler({ - sessionId: "ses_isolate", - project: "/tmp/proj", - }); - - expect(result.context).not.toContain("other-project-lesson"); - }); - - it("excludes deleted lessons", async () => { - await seedLesson(kv, { - id: "lesson_deleted", - content: "tombstoned-lesson", - project: "/tmp/proj", - confidence: 0.9, - deleted: true, - }); - - const result = await handler({ - sessionId: "ses_deleted", - project: "/tmp/proj", - }); - - expect(result.context).not.toContain("tombstoned-lesson"); - }); - - it("caps at the top 10 lessons by confidence", async () => { - for (let i = 0; i < 15; i++) { - await seedLesson(kv, { - id: `lesson_${i}`, - content: `lesson-marker-${i}`, - project: "/tmp/proj", - confidence: i / 100, - }); - } - - const result = await handler({ - sessionId: "ses_cap", - project: "/tmp/proj", - }); - - const matched = result.context.match(/lesson-marker-/g) ?? []; - expect(matched.length).toBe(10); - expect(result.context).toContain("lesson-marker-14"); - expect(result.context).toContain("lesson-marker-5"); - expect(result.context).not.toContain("lesson-marker-0"); - }); - - it("shows lesson confidence in the rendered line", async () => { - await seedLesson(kv, { - id: "lesson_conf", - content: "test confidence rendering", - project: "/tmp/proj", - confidence: 0.83, - }); - - const result = await handler({ - sessionId: "ses_conf", - project: "/tmp/proj", - }); - - expect(result.context).toContain("(0.83)"); - }); - - it("appends optional context string when present", async () => { - await seedLesson(kv, { - id: "lesson_ctx", - content: "use TaskCreate for >5-file work", - context: "when working on multi-file refactors", - project: "/tmp/proj", - confidence: 0.8, - }); - - const result = await handler({ - sessionId: "ses_ctx", - project: "/tmp/proj", - }); - - expect(result.context).toContain( - "use TaskCreate for >5-file work — when working on multi-file refactors", - ); - }); -}); diff --git a/test/context-slots.test.ts b/test/context-slots.test.ts deleted file mode 100644 index 808597c4..00000000 --- a/test/context-slots.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import { registerContextFunction } from "../src/functions/context.js"; -import { KV } from "../src/state/schema.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - if (!store.has(scope)) return []; - return Array.from(store.get(scope)!.values()) as T[]; - }, - }; -} - -type ContextHandler = (data: { - sessionId: string; - project: string; - budget?: number; -}) => Promise<{ context: string; blocks: number; tokens: number }>; - -function wireContext(kv: ReturnType) { - let handler: ContextHandler | undefined; - const sdk = { - registerFunction: vi.fn((id: string, cb: ContextHandler) => { - if (id === "mem::context") handler = cb; - }), - } as unknown as import("iii-sdk").ISdk; - registerContextFunction(sdk, kv as never, 2000); - if (!handler) throw new Error("mem::context not registered"); - return handler; -} - -async function seedPinnedSlot( - kv: ReturnType, - label: string, - content: string, - scope: "project" | "global" = "global", -) { - const target = scope === "global" ? KV.globalSlots : KV.slots; - await kv.set(target, label, { - label, - content, - description: "", - sizeLimit: 2000, - pinned: true, - readOnly: false, - scope, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }); -} - -describe("mem::context — pinned slot injection", () => { - const ORIGINAL_SLOTS_ENV = process.env["AGENTMEMORY_SLOTS"]; - - afterEach(() => { - if (ORIGINAL_SLOTS_ENV === undefined) { - delete process.env["AGENTMEMORY_SLOTS"]; - } else { - process.env["AGENTMEMORY_SLOTS"] = ORIGINAL_SLOTS_ENV; - } - }); - - describe("when AGENTMEMORY_SLOTS=true", () => { - let kv: ReturnType; - let handler: ContextHandler; - - beforeEach(() => { - process.env["AGENTMEMORY_SLOTS"] = "true"; - kv = mockKV(); - handler = wireContext(kv); - }); - - it("includes pinned global slot content in returned context", async () => { - await seedPinnedSlot(kv, "tool_guidelines", "rule-alpha", "global"); - - const result = await handler({ - sessionId: "ses_a", - project: "/tmp/proj", - }); - - expect(result.context).toContain("tool_guidelines"); - expect(result.context).toContain("rule-alpha"); - expect(result.blocks).toBeGreaterThan(0); - }); - - it("renders multiple pinned slots, sorted by label", async () => { - await seedPinnedSlot(kv, "user_preferences", "pref-alpha", "global"); - await seedPinnedSlot(kv, "tool_guidelines", "rule-alpha", "global"); - - const result = await handler({ - sessionId: "ses_b", - project: "/tmp/proj", - }); - - const guidelinesIdx = result.context.indexOf("tool_guidelines"); - const prefsIdx = result.context.indexOf("user_preferences"); - expect(guidelinesIdx).toBeGreaterThan(-1); - expect(prefsIdx).toBeGreaterThan(-1); - expect(guidelinesIdx).toBeLessThan(prefsIdx); - }); - - it("skips unpinned slots even when they have content", async () => { - await kv.set(KV.globalSlots, "self_notes", { - label: "self_notes", - content: "unpinned-content-alpha", - description: "", - sizeLimit: 1500, - pinned: false, - readOnly: false, - scope: "global", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }); - - const result = await handler({ - sessionId: "ses_c", - project: "/tmp/proj", - }); - - expect(result.context).not.toContain("unpinned-content-alpha"); - }); - - it("skips empty pinned slots (the seeded defaults)", async () => { - await seedPinnedSlot(kv, "persona", "", "global"); - - const result = await handler({ - sessionId: "ses_d", - project: "/tmp/proj", - }); - - expect(result.context).not.toContain("persona"); - }); - - it("project-scoped slot shadows global slot with the same label", async () => { - await seedPinnedSlot(kv, "tool_guidelines", "global-value", "global"); - await seedPinnedSlot(kv, "tool_guidelines", "project-value", "project"); - - const result = await handler({ - sessionId: "ses_e", - project: "/tmp/proj", - }); - - expect(result.context).toContain("project-value"); - expect(result.context).not.toContain("global-value"); - }); - }); - - describe("when AGENTMEMORY_SLOTS is off", () => { - it("does not include any slot content", async () => { - delete process.env["AGENTMEMORY_SLOTS"]; - const kv = mockKV(); - const handler = wireContext(kv); - - await seedPinnedSlot(kv, "tool_guidelines", "rule-alpha", "global"); - - const result = await handler({ - sessionId: "ses_f", - project: "/tmp/proj", - }); - - expect(result.context).not.toContain("tool_guidelines"); - expect(result.context).not.toContain("rule-alpha"); - }); - }); -}); diff --git a/test/contracts.test.ts b/test/contracts.test.ts new file mode 100644 index 00000000..4fb98013 --- /dev/null +++ b/test/contracts.test.ts @@ -0,0 +1,15 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { CLI_COMMANDS, HTTP_ROUTES, MCP_TOOLS } from "../src/contracts.js"; + +test("public contracts expose only ai-todo and todo tool names", () => { + const text = [ + ...CLI_COMMANDS, + ...HTTP_ROUTES, + ...MCP_TOOLS + ].join("\n"); + + assert.match(text, /ai-todo/); + assert.match(text, /todo_organize/); + assert.doesNotMatch(text, /agentmemory-lab|iii-engine|memory_/); +}); diff --git a/test/copilot-plugin.test.ts b/test/copilot-plugin.test.ts deleted file mode 100644 index 86e2c418..00000000 --- a/test/copilot-plugin.test.ts +++ /dev/null @@ -1,379 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { readFileSync, existsSync } from "node:fs"; -import { join, resolve } from "node:path"; -import { createServer } from "node:http"; -import { spawn } from "node:child_process"; - -const repoRoot = resolve(__dirname, ".."); -const pluginRoot = join(repoRoot, "plugin"); - -function readJson(path: string): T { - return JSON.parse(readFileSync(path, "utf-8")) as T; -} - -const SUPPORTED_COPILOT_EVENTS = new Set([ - "sessionStart", - "userPromptSubmitted", - "preToolUse", - "postToolUse", - "postToolUseFailure", - "preCompact", - "agentStop", - "sessionEnd", - "subagentStart", - "subagentStop", - "notification", -]); - -const REQUIRED_MINIMUM_EVENTS = [ - "sessionStart", - "userPromptSubmitted", - "preToolUse", - "postToolUse", - "agentStop", -]; - -const KNOWN_SKILL_DIRS = [ - "session-history", - "handoff", - "recap", - "commit-context", - "commit-history", - "ask-user", - "organize-todos", -]; - -describe("Copilot plugin manifest (plugin/plugin.json)", () => { - it("manifest exists with kebab-case name, version, and required fields", () => { - const manifestPath = join(pluginRoot, "plugin.json"); - expect(existsSync(manifestPath)).toBe(true); - const manifest = readJson<{ - name: string; - version: string; - description?: string; - skills?: string; - mcpServers?: string; - hooks?: string; - }>(manifestPath); - expect(manifest.name).toBe("agent-memory-lab"); - expect(manifest.name).toMatch(/^[a-z][a-z0-9-]*$/); - expect(manifest.version).toMatch(/^\d+\.\d+\.\d+/); - expect(manifest.skills).toBeDefined(); - expect(manifest.mcpServers).toBeDefined(); - expect(manifest.hooks).toBeDefined(); - }); - - it("manifest version matches main package.json", () => { - const pkgVer = readJson<{ version: string }>(join(repoRoot, "package.json")).version; - const pluginVer = readJson<{ version: string }>( - join(pluginRoot, "plugin.json"), - ).version; - expect(pluginVer).toBe(pkgVer); - }); - - it("all referenced manifest paths resolve to existing files / directories", () => { - const manifest = readJson<{ skills: string; mcpServers: string; hooks: string }>( - join(pluginRoot, "plugin.json"), - ); - const manifestDir = pluginRoot; - expect(existsSync(resolve(manifestDir, manifest.skills))).toBe(true); - expect(existsSync(resolve(manifestDir, manifest.mcpServers))).toBe(true); - expect(existsSync(resolve(manifestDir, manifest.hooks))).toBe(true); - }); - - it("skills path resolves and contains all known skill directories", () => { - const manifest = readJson<{ skills: string }>(join(pluginRoot, "plugin.json")); - const manifestDir = pluginRoot; - const skillsPath = resolve(manifestDir, manifest.skills); - for (const skill of KNOWN_SKILL_DIRS) { - expect( - existsSync(join(skillsPath, skill)), - `missing skill directory: ${skill}`, - ).toBe(true); - } - }); -}); - -describe("Copilot MCP config (.mcp.copilot.json)", () => { - it("file exists with expected shape", () => { - const mcpPath = join(pluginRoot, ".mcp.copilot.json"); - expect(existsSync(mcpPath)).toBe(true); - const config = readJson<{ - mcpServers: { - agentmemory: { - type: string; - command: string; - args: string[]; - env: Record; - tools: string[]; - }; - }; - }>(mcpPath); - const server = config.mcpServers.agentmemory; - expect(server.type).toBe("local"); - expect(server.command).toBe("npx"); - expect(server.args).toEqual(["-y", "@agentmemory/mcp"]); - expect(server.env["AGENTMEMORY_URL"]).toBe( - "${AGENTMEMORY_URL:-http://localhost:3111}", - ); - expect(server.env["AGENTMEMORY_SECRET"]).toBe("${AGENTMEMORY_SECRET:-}"); - expect(server.env["AGENTMEMORY_TOOLS"]).toBe("${AGENTMEMORY_TOOLS:-all}"); - expect(server.tools).toContain("*"); - }); -}); - -describe("Copilot hooks config (hooks/hooks.copilot.json)", () => { - type HookEntry = { - type: string; - command?: string; - bash?: string; - powershell?: string; - matcher?: string; - }; - - function loadHooks() { - return readJson<{ version: number; hooks: Record }>( - join(pluginRoot, "hooks/hooks.copilot.json"), - ); - } - - it("has top-level version === 1 and hooks object", () => { - const config = loadHooks(); - expect(config.version).toBe(1); - expect(config.hooks).toBeDefined(); - expect(typeof config.hooks).toBe("object"); - }); - - it("contains only supported Copilot event names", () => { - const config = loadHooks(); - for (const event of Object.keys(config.hooks)) { - expect( - SUPPORTED_COPILOT_EVENTS.has(event), - `unsupported event "${event}" in hooks.copilot.json`, - ).toBe(true); - } - }); - - it("contains all required minimum events", () => { - const config = loadHooks(); - const events = Object.keys(config.hooks); - for (const event of REQUIRED_MINIMUM_EVENTS) { - expect(events, `missing required event: ${event}`).toContain(event); - } - }); - - it("PreToolUse entry has the correct matcher", () => { - const config = loadHooks(); - const preToolEntries = config.hooks["preToolUse"]; - expect(preToolEntries).toBeDefined(); - const withMatcher = preToolEntries.find( - (e) => e.matcher === "edit|write|create|read|view|glob|grep", - ); - expect( - withMatcher, - "PreToolUse must have matcher edit|write|create|read|view|glob|grep", - ).toBeDefined(); - }); - - it("every handler has type === 'command' and exactly one of command/bash/powershell", () => { - const config = loadHooks(); - for (const [event, entries] of Object.entries(config.hooks)) { - for (const handler of entries) { - expect(handler.type, `${event} handler type`).toBe("command"); - const commandFields = [handler.command, handler.bash, handler.powershell].filter( - (v): v is string => typeof v === "string" && v.trim().length > 0, - ); - expect( - commandFields.length, - `${event} handler must have exactly one of command/bash/powershell`, - ).toBe(1); - } - } - }); - - it("every referenced script exists on disk", () => { - const config = loadHooks(); - const scriptRefs = new Set(); - for (const entries of Object.values(config.hooks)) { - for (const handler of entries) { - const cmd = handler.command ?? handler.bash ?? handler.powershell ?? ""; - const match = cmd.match(/\$\{(?:COPILOT_PLUGIN_ROOT|CLAUDE_PLUGIN_ROOT)\}\/(scripts\/[^\s]+)/); - if (match) scriptRefs.add(match[1]); - } - } - expect(scriptRefs.size).toBeGreaterThan(0); - for (const rel of scriptRefs) { - expect(existsSync(join(pluginRoot, rel)), `missing hook script: ${rel}`).toBe(true); - } - }); -}); - -describe("Copilot hook scripts", () => { - type ObservedRequest = { path: string; body: Record }; - - async function runHook( - script: string, - payload: Record, - env: Record = {}, - ): Promise<{ requests: ObservedRequest[]; stdout: string }> { - const requests: ObservedRequest[] = []; - const server = createServer((req, res) => { - let raw = ""; - req.on("data", (chunk) => { - raw += chunk; - }); - req.on("end", () => { - requests.push({ - path: req.url ?? "", - body: raw ? (JSON.parse(raw) as Record) : {}, - }); - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ context: "remembered context" })); - }); - }); - - await new Promise((resolveServer) => { - server.listen(0, "127.0.0.1", resolveServer); - }); - - const address = server.address(); - if (!address || typeof address === "string") { - server.close(); - throw new Error("test server did not bind to a TCP port"); - } - - try { - const child = spawn(process.execPath, [join(pluginRoot, script)], { - env: { - ...process.env, - AGENTMEMORY_URL: `http://127.0.0.1:${address.port}`, - AGENTMEMORY_SECRET: "", - ...env, - }, - stdio: ["pipe", "pipe", "pipe"], - }); - let stdout = ""; - let stderr = ""; - child.stdout.on("data", (chunk) => { - stdout += chunk; - }); - child.stderr.on("data", (chunk) => { - stderr += chunk; - }); - child.stdin.end(JSON.stringify(payload)); - - const exitCode = await new Promise((resolveExit, reject) => { - const timeout = setTimeout(() => { - child.kill(); - reject(new Error(`hook ${script} timed out`)); - }, 5000); - child.on("error", reject); - child.on("close", (code) => { - clearTimeout(timeout); - resolveExit(code); - }); - }); - - expect(exitCode, stderr).toBe(0); - return { requests, stdout }; - } finally { - await new Promise((resolveClose) => { - server.close(() => resolveClose()); - }); - } - } - - it("session-start accepts Copilot camelCase sessionId", async () => { - const result = await runHook( - "scripts/session-start.mjs", - { sessionId: "copilot-session", cwd: "C:\\repo" }, - { AGENTMEMORY_INJECT_CONTEXT: "true" }, - ); - - expect(result.stdout).toBe("remembered context"); - expect(result.requests[0]?.path).toBe("/agentmemory/session/start"); - expect(result.requests[0]?.body).toMatchObject({ - sessionId: "copilot-session", - project: "C:\\repo", - cwd: "C:\\repo", - }); - }); - - it("pre-tool-use narrows Copilot sessionId to strings", async () => { - const result = await runHook( - "scripts/pre-tool-use.mjs", - { - sessionId: 123, - toolName: "read", - toolArgs: { path: "src/index.ts" }, - }, - { AGENTMEMORY_INJECT_CONTEXT: "true" }, - ); - - expect(result.stdout).toBe("remembered context"); - expect(result.requests[0]?.path).toBe("/agentmemory/enrich"); - expect(result.requests[0]?.body).toMatchObject({ - sessionId: "unknown", - files: ["src/index.ts"], - terms: [], - toolName: "read", - }); - }); - - it("prompt-submit accepts Copilot camelCase prompt payload", async () => { - const result = await runHook("scripts/prompt-submit.mjs", { - sessionId: "copilot-session", - cwd: "C:\\repo", - userPrompt: "remember this prompt", - }); - - expect(result.requests[0]?.path).toBe("/agentmemory/observe"); - expect(result.requests[0]?.body).toMatchObject({ - hookType: "prompt_submit", - sessionId: "copilot-session", - data: { prompt: "remember this prompt" }, - }); - }); - - it("post-tool-failure accepts Copilot camelCase tool and error payloads", async () => { - const result = await runHook("scripts/post-tool-failure.mjs", { - sessionId: "copilot-session", - cwd: "C:\\repo", - toolName: "edit", - toolArgs: { filePath: "src/index.ts" }, - errorMessage: "failed", - }); - - expect(result.requests[0]?.path).toBe("/agentmemory/observe"); - expect(result.requests[0]?.body).toMatchObject({ - hookType: "post_tool_failure", - sessionId: "copilot-session", - data: { - tool_name: "edit", - tool_input: JSON.stringify({ filePath: "src/index.ts" }), - error: "failed", - }, - }); - }); - - it("notification accepts Copilot camelCase notificationType", async () => { - const result = await runHook("scripts/notification.mjs", { - sessionId: "copilot-session", - cwd: "C:\\repo", - notificationType: "permission_prompt", - title: "Tool approval", - message: "Approve edit", - }); - - expect(result.requests[0]?.path).toBe("/agentmemory/observe"); - expect(result.requests[0]?.body).toMatchObject({ - hookType: "notification", - sessionId: "copilot-session", - data: { - notification_type: "permission_prompt", - title: "Tool approval", - message: "Approve edit", - }, - }); - }); -}); diff --git a/test/cross-project-isolation.test.ts b/test/cross-project-isolation.test.ts deleted file mode 100644 index 8fc37498..00000000 --- a/test/cross-project-isolation.test.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("../src/state/keyed-mutex.js", () => ({ - withKeyedLock: (_key: string, fn: () => Promise) => fn(), -})); - -vi.mock("../src/functions/audit.js", () => ({ - recordAudit: vi.fn(), -})); - -vi.mock("../src/functions/access-tracker.js", () => ({ - recordAccessBatch: vi.fn(), - deleteAccessLog: vi.fn(), -})); - -vi.mock("../src/config.js", () => ({ - getAgentId: () => undefined, -})); - -import { registerRememberFunction } from "../src/functions/remember.js"; -import { registerSearchFunction, getSearchIndex, setIndexPersistence } from "../src/functions/search.js"; -import { registerEnrichFunction } from "../src/functions/enrich.js"; -import { KV } from "../src/state/schema.js"; -import type { Session } from "../src/types.js"; - -function makeMockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function makeMockSdk() { - const functions = new Map(); - const triggerOverrides = new Map(); - return { - registerFunction: (id: string, handler: Function) => { - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : (idOrInput as { payload: unknown }).payload; - if (triggerOverrides.has(id)) return triggerOverrides.get(id)!(payload); - const fn = functions.get(id); - if (!fn) throw new Error(`No function registered: ${id}`); - return fn(payload); - }, - overrideTrigger: (id: string, handler: Function) => { - triggerOverrides.set(id, handler); - }, - }; -} - -async function seedSessions(kv: ReturnType) { - const apiSession: Session = { - id: "sess-api", - project: "api", - cwd: "/srv/api", - startedAt: new Date().toISOString(), - status: "active", - observationCount: 0, - }; - const webSession: Session = { - id: "sess-web", - project: "web", - cwd: "/srv/web", - startedAt: new Date().toISOString(), - status: "active", - observationCount: 0, - }; - await kv.set(KV.sessions, apiSession.id, apiSession); - await kv.set(KV.sessions, webSession.id, webSession); -} - -describe("cross-project isolation — end-to-end", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(async () => { - sdk = makeMockSdk(); - kv = makeMockKV(); - - // Disable index persistence in tests so no file I/O occurs. - setIndexPersistence(null); - - // Clear the singleton BM25 index between tests. - getSearchIndex().clear(); - - // Register all three functions against the shared KV. - registerRememberFunction(sdk as never, kv as never); - registerSearchFunction(sdk as never, kv as never); - - // Enrich calls mem::search internally; wire the file-context trigger as a no-op. - registerEnrichFunction(sdk as never, kv as never); - sdk.overrideTrigger("mem::file-context", async () => ({ context: "" })); - - await seedSessions(kv); - }); - - it("bug memory scoped to api does not appear in enrich context for web project", async () => { - await sdk.trigger("mem::remember", { - content: "express-jwt throws 401 when Authorization header has extra whitespace. Call .trim() before passing to middleware.", - type: "bug", - files: ["src/middleware/auth.ts"], - project: "api", - }); - - const result = await sdk.trigger("mem::enrich", { - sessionId: "sess-web", - files: ["src/middleware/auth.ts"], - project: "web", - }) as { context: string }; - - expect(result.context).not.toContain("agentmemory-past-errors"); - expect(result.context).not.toContain("express-jwt"); - }); - - it("bug memory scoped to api appears in enrich context for api project", async () => { - await sdk.trigger("mem::remember", { - content: "express-jwt throws 401 when Authorization header has extra whitespace. Call .trim() before passing to middleware.", - type: "bug", - files: ["src/middleware/auth.ts"], - project: "api", - }); - - const result = await sdk.trigger("mem::enrich", { - sessionId: "sess-api", - files: ["src/middleware/auth.ts"], - project: "api", - }) as { context: string }; - - expect(result.context).toContain("agentmemory-past-errors"); - expect(result.context).toContain("express-jwt"); - }); - - it("bug memory scoped to api is excluded from search results for web project", async () => { - await sdk.trigger("mem::remember", { - content: "express-jwt throws 401 when Authorization header has extra whitespace", - type: "bug", - files: ["src/middleware/auth.ts"], - project: "api", - }); - - // Force index rebuild so the freshly saved memory is indexed. - getSearchIndex().clear(); - - const result = await sdk.trigger("mem::search", { - query: "express-jwt whitespace", - project: "web", - }) as { results: Array<{ observation: { title: string; narrative?: string } }> }; - - const titles = result.results.map((r) => r.observation.title); - expect(titles.join(" ")).not.toContain("express-jwt"); - }); - - it("bug memory scoped to api is included in search results for api project", async () => { - await sdk.trigger("mem::remember", { - content: "express-jwt throws 401 when Authorization header has extra whitespace", - type: "bug", - files: ["src/middleware/auth.ts"], - project: "api", - }); - - // Force index rebuild so the freshly saved memory is indexed. - getSearchIndex().clear(); - - const result = await sdk.trigger("mem::search", { - query: "express-jwt whitespace", - project: "api", - }) as { results: Array<{ observation: { title: string; narrative?: string } }> }; - - const combined = result.results - .map((r) => `${r.observation.title} ${r.observation.narrative ?? ""}`) - .join(" "); - expect(combined).toContain("express-jwt"); - }); - - it("two projects with overlapping filenames see only their own bug memories", async () => { - await sdk.trigger("mem::remember", { - content: "express-jwt Authorization header whitespace causes 401", - type: "bug", - files: ["src/middleware/auth.ts"], - project: "api", - }); - await sdk.trigger("mem::remember", { - content: "nextauth cookie domain mismatch breaks SSO on subdomains", - type: "bug", - files: ["src/middleware/auth.ts"], - project: "web", - }); - - const apiEnrich = await sdk.trigger("mem::enrich", { - sessionId: "sess-api", - files: ["src/middleware/auth.ts"], - project: "api", - }) as { context: string }; - - expect(apiEnrich.context).toContain("express-jwt"); - expect(apiEnrich.context).not.toContain("nextauth"); - - const webEnrich = await sdk.trigger("mem::enrich", { - sessionId: "sess-web", - files: ["src/middleware/auth.ts"], - project: "web", - }) as { context: string }; - - expect(webEnrich.context).toContain("nextauth"); - expect(webEnrich.context).not.toContain("express-jwt"); - }); - - it("unscoped (legacy) bug memory is visible to both projects", async () => { - await sdk.trigger("mem::remember", { - content: "generic auth middleware always validates content-type header", - type: "bug", - files: ["src/middleware/auth.ts"], - // no project — legacy / unscoped - }); - - const apiResult = await sdk.trigger("mem::enrich", { - sessionId: "sess-api", - files: ["src/middleware/auth.ts"], - project: "api", - }) as { context: string }; - - const webResult = await sdk.trigger("mem::enrich", { - sessionId: "sess-web", - files: ["src/middleware/auth.ts"], - project: "web", - }) as { context: string }; - - expect(apiResult.context).toContain("generic auth middleware"); - expect(webResult.context).toContain("generic auth middleware"); - }); - - it("memories from different projects do not supersede each other via Jaccard dedup", async () => { - const sharedContent = "jwt token must be trimmed before validation in the middleware layer"; - - await sdk.trigger("mem::remember", { - content: sharedContent, - type: "bug", - files: ["src/middleware/auth.ts"], - project: "api", - }); - - // Save nearly identical content under a different project. - const result = await sdk.trigger("mem::remember", { - content: sharedContent, - type: "bug", - files: ["src/middleware/auth.ts"], - project: "web", - }) as { memory: { id: string; project: string } }; - - // Both memories must survive as independent latest entries. - const memories = await kv.list(KV.memories) as Array<{ isLatest: boolean; project: string }>; - const latestByProject = memories.filter((m) => m.isLatest); - const projects = latestByProject.map((m) => m.project); - - expect(projects).toContain("api"); - expect(projects).toContain("web"); - expect(result.memory.project).toBe("web"); - }); -}); diff --git a/test/crystallize.test.ts b/test/crystallize.test.ts deleted file mode 100644 index cbb29f59..00000000 --- a/test/crystallize.test.ts +++ /dev/null @@ -1,522 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerCrystallizeFunction } from "../src/functions/crystallize.js"; -import type { Action, Crystal, MemoryProvider } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function mockProvider(): MemoryProvider { - return { - name: "test", - compress: vi.fn(), - summarize: vi.fn().mockResolvedValue( - '{"narrative":"test","keyOutcomes":["done"],"filesAffected":["a.ts"],"lessons":["learned"]}', - ), - }; -} - -function makeAction(overrides: Partial & { id: string }): Action { - return { - title: "Test action", - description: "A test action", - status: "done", - priority: 5, - createdAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(), - updatedAt: new Date().toISOString(), - createdBy: "agent-1", - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - ...overrides, - }; -} - -describe("Crystallize Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - let provider: MemoryProvider; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - provider = mockProvider(); - registerCrystallizeFunction(sdk as never, kv as never, provider); - }); - - describe("mem::crystallize", () => { - it("crystallizes completed actions with valid JSON response", async () => { - const action = makeAction({ id: "act_1", title: "Fix bug", status: "done" }); - await kv.set("mem:actions", action.id, action); - - const result = (await sdk.trigger("mem::crystallize", { - actionIds: ["act_1"], - project: "webapp", - sessionId: "sess_1", - })) as { success: boolean; crystal: Crystal }; - - expect(result.success).toBe(true); - expect(result.crystal.id).toMatch(/^crys_/); - expect(result.crystal.narrative).toBe("test"); - expect(result.crystal.keyOutcomes).toEqual(["done"]); - expect(result.crystal.filesAffected).toEqual(["a.ts"]); - expect(result.crystal.lessons).toEqual(["learned"]); - expect(result.crystal.sourceActionIds).toEqual(["act_1"]); - expect(result.crystal.project).toBe("webapp"); - expect(result.crystal.sessionId).toBe("sess_1"); - expect(result.crystal.createdAt).toBeDefined(); - }); - - it("marks source actions with crystallizedInto", async () => { - const action = makeAction({ id: "act_mark", status: "done" }); - await kv.set("mem:actions", action.id, action); - - const result = (await sdk.trigger("mem::crystallize", { - actionIds: ["act_mark"], - })) as { success: boolean; crystal: Crystal }; - - expect(result.success).toBe(true); - - const updated = await kv.get("mem:actions", "act_mark"); - expect(updated!.crystallizedInto).toBe(result.crystal.id); - }); - - it("falls back to raw text when provider returns non-JSON", async () => { - (provider.summarize as ReturnType).mockResolvedValue( - "Just a plain text summary with no JSON.", - ); - - const action = makeAction({ id: "act_nojson", status: "done" }); - await kv.set("mem:actions", action.id, action); - - const result = (await sdk.trigger("mem::crystallize", { - actionIds: ["act_nojson"], - })) as { success: boolean; crystal: Crystal }; - - expect(result.success).toBe(true); - expect(result.crystal.narrative).toBe( - "Just a plain text summary with no JSON.", - ); - expect(result.crystal.keyOutcomes).toEqual([]); - expect(result.crystal.filesAffected).toEqual([]); - expect(result.crystal.lessons).toEqual([]); - }); - - it("returns error for non-existent action", async () => { - const result = (await sdk.trigger("mem::crystallize", { - actionIds: ["act_ghost"], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("action not found: act_ghost"); - }); - - it("returns error for non-done action", async () => { - const action = makeAction({ id: "act_pending", status: "pending" }); - await kv.set("mem:actions", action.id, action); - - const result = (await sdk.trigger("mem::crystallize", { - actionIds: ["act_pending"], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain('status "pending"'); - }); - - it("returns error for empty actionIds", async () => { - const result = (await sdk.trigger("mem::crystallize", { - actionIds: [], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("actionIds is required"); - }); - - it("returns error when actionIds is missing", async () => { - const result = (await sdk.trigger("mem::crystallize", {})) as { - success: boolean; - error: string; - }; - - expect(result.success).toBe(false); - expect(result.error).toContain("actionIds is required"); - }); - - it("accepts cancelled actions", async () => { - const action = makeAction({ id: "act_cancel", status: "cancelled" }); - await kv.set("mem:actions", action.id, action); - - const result = (await sdk.trigger("mem::crystallize", { - actionIds: ["act_cancel"], - })) as { success: boolean; crystal: Crystal }; - - expect(result.success).toBe(true); - expect(result.crystal.sourceActionIds).toEqual(["act_cancel"]); - }); - - it("crystallizes multiple actions in one call", async () => { - const a1 = makeAction({ id: "act_m1", status: "done", title: "First" }); - const a2 = makeAction({ id: "act_m2", status: "done", title: "Second" }); - await kv.set("mem:actions", a1.id, a1); - await kv.set("mem:actions", a2.id, a2); - - const result = (await sdk.trigger("mem::crystallize", { - actionIds: ["act_m1", "act_m2"], - })) as { success: boolean; crystal: Crystal }; - - expect(result.success).toBe(true); - expect(result.crystal.sourceActionIds).toEqual(["act_m1", "act_m2"]); - }); - - it("returns failure when provider throws", async () => { - (provider.summarize as ReturnType).mockRejectedValue( - new Error("API down"), - ); - - const action = makeAction({ id: "act_fail", status: "done" }); - await kv.set("mem:actions", action.id, action); - - const result = (await sdk.trigger("mem::crystallize", { - actionIds: ["act_fail"], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("crystallization failed"); - expect(result.error).toContain("API down"); - }); - }); - - describe("mem::crystal-list", () => { - beforeEach(async () => { - const c1: Crystal = { - id: "crys_1", - narrative: "First crystal", - keyOutcomes: [], - filesAffected: [], - lessons: [], - sourceActionIds: ["act_1"], - project: "alpha", - sessionId: "sess_a", - createdAt: new Date("2025-01-01").toISOString(), - }; - const c2: Crystal = { - id: "crys_2", - narrative: "Second crystal", - keyOutcomes: [], - filesAffected: [], - lessons: [], - sourceActionIds: ["act_2"], - project: "beta", - sessionId: "sess_b", - createdAt: new Date("2025-02-01").toISOString(), - }; - const c3: Crystal = { - id: "crys_3", - narrative: "Third crystal", - keyOutcomes: [], - filesAffected: [], - lessons: [], - sourceActionIds: ["act_3"], - project: "alpha", - sessionId: "sess_a", - createdAt: new Date("2025-03-01").toISOString(), - }; - await kv.set("mem:crystals", c1.id, c1); - await kv.set("mem:crystals", c2.id, c2); - await kv.set("mem:crystals", c3.id, c3); - }); - - it("returns all crystals sorted by createdAt desc", async () => { - const result = (await sdk.trigger("mem::crystal-list", {})) as { - success: boolean; - crystals: Crystal[]; - }; - - expect(result.success).toBe(true); - expect(result.crystals.length).toBe(3); - expect(result.crystals[0].id).toBe("crys_3"); - expect(result.crystals[1].id).toBe("crys_2"); - expect(result.crystals[2].id).toBe("crys_1"); - }); - - it("filters by project", async () => { - const result = (await sdk.trigger("mem::crystal-list", { - project: "alpha", - })) as { success: boolean; crystals: Crystal[] }; - - expect(result.success).toBe(true); - expect(result.crystals.length).toBe(2); - expect(result.crystals.every((c) => c.project === "alpha")).toBe(true); - }); - - it("filters by sessionId", async () => { - const result = (await sdk.trigger("mem::crystal-list", { - sessionId: "sess_b", - })) as { success: boolean; crystals: Crystal[] }; - - expect(result.success).toBe(true); - expect(result.crystals.length).toBe(1); - expect(result.crystals[0].id).toBe("crys_2"); - }); - - it("respects limit", async () => { - const result = (await sdk.trigger("mem::crystal-list", { - limit: 1, - })) as { success: boolean; crystals: Crystal[] }; - - expect(result.success).toBe(true); - expect(result.crystals.length).toBe(1); - expect(result.crystals[0].id).toBe("crys_3"); - }); - }); - - describe("mem::crystal-get", () => { - it("returns crystal by id", async () => { - const crystal: Crystal = { - id: "crys_get_1", - narrative: "Found it", - keyOutcomes: ["yes"], - filesAffected: ["b.ts"], - lessons: ["test"], - sourceActionIds: ["act_x"], - createdAt: new Date().toISOString(), - }; - await kv.set("mem:crystals", crystal.id, crystal); - - const result = (await sdk.trigger("mem::crystal-get", { - crystalId: "crys_get_1", - })) as { success: boolean; crystal: Crystal }; - - expect(result.success).toBe(true); - expect(result.crystal.id).toBe("crys_get_1"); - expect(result.crystal.narrative).toBe("Found it"); - }); - - it("returns error for non-existent crystal", async () => { - const result = (await sdk.trigger("mem::crystal-get", { - crystalId: "crys_missing", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("crystal not found"); - }); - - it("returns error when crystalId is missing", async () => { - const result = (await sdk.trigger("mem::crystal-get", {})) as { - success: boolean; - error: string; - }; - - expect(result.success).toBe(false); - expect(result.error).toContain("crystalId is required"); - }); - }); - - describe("mem::auto-crystallize", () => { - it("returns group summaries in dryRun mode", async () => { - const action = makeAction({ - id: "act_dry", - status: "done", - project: "proj", - }); - await kv.set("mem:actions", action.id, action); - - const result = (await sdk.trigger("mem::auto-crystallize", { - dryRun: true, - })) as { - success: boolean; - dryRun: boolean; - groupCount: number; - groups: { groupKey: string; actionCount: number; actionIds: string[] }[]; - crystalIds: string[]; - }; - - expect(result.success).toBe(true); - expect(result.dryRun).toBe(true); - expect(result.groupCount).toBe(1); - expect(result.groups[0].actionIds).toContain("act_dry"); - expect(result.crystalIds).toEqual([]); - }); - - it("groups by parentId when present", async () => { - const parent = makeAction({ - id: "act_parent", - status: "done", - parentId: undefined, - }); - const child1 = makeAction({ - id: "act_child1", - status: "done", - parentId: "act_parent", - }); - const child2 = makeAction({ - id: "act_child2", - status: "done", - parentId: "act_parent", - }); - await kv.set("mem:actions", parent.id, parent); - await kv.set("mem:actions", child1.id, child1); - await kv.set("mem:actions", child2.id, child2); - - const result = (await sdk.trigger("mem::auto-crystallize", { - dryRun: true, - })) as { - success: boolean; - groups: { groupKey: string; actionCount: number; actionIds: string[] }[]; - }; - - expect(result.success).toBe(true); - const parentGroup = result.groups.find((g) => g.groupKey === "act_parent"); - expect(parentGroup).toBeDefined(); - expect(parentGroup!.actionCount).toBe(2); - }); - - it("groups by project when no parentId", async () => { - const a1 = makeAction({ id: "act_proj1", status: "done", project: "webapp" }); - const a2 = makeAction({ id: "act_proj2", status: "done", project: "webapp" }); - const a3 = makeAction({ id: "act_proj3", status: "done", project: "api" }); - await kv.set("mem:actions", a1.id, a1); - await kv.set("mem:actions", a2.id, a2); - await kv.set("mem:actions", a3.id, a3); - - const result = (await sdk.trigger("mem::auto-crystallize", { - dryRun: true, - })) as { - success: boolean; - groups: { groupKey: string; actionCount: number }[]; - }; - - expect(result.success).toBe(true); - const webGroup = result.groups.find((g) => g.groupKey === "webapp"); - const apiGroup = result.groups.find((g) => g.groupKey === "api"); - expect(webGroup).toBeDefined(); - expect(webGroup!.actionCount).toBe(2); - expect(apiGroup).toBeDefined(); - expect(apiGroup!.actionCount).toBe(1); - }); - - it("skips already-crystallized actions", async () => { - const action = makeAction({ - id: "act_already", - status: "done", - crystallizedInto: "crys_existing", - }); - await kv.set("mem:actions", action.id, action); - - const result = (await sdk.trigger("mem::auto-crystallize", { - dryRun: true, - })) as { success: boolean; groupCount: number }; - - expect(result.success).toBe(true); - expect(result.groupCount).toBe(0); - }); - - it("skips actions newer than threshold", async () => { - const recentAction = makeAction({ - id: "act_recent", - status: "done", - createdAt: new Date().toISOString(), - }); - await kv.set("mem:actions", recentAction.id, recentAction); - - const result = (await sdk.trigger("mem::auto-crystallize", { - olderThanDays: 7, - dryRun: true, - })) as { success: boolean; groupCount: number }; - - expect(result.success).toBe(true); - expect(result.groupCount).toBe(0); - }); - - it("creates crystals for each group in non-dryRun mode", async () => { - const a1 = makeAction({ id: "act_auto1", status: "done", project: "proj1" }); - const a2 = makeAction({ id: "act_auto2", status: "done", project: "proj2" }); - await kv.set("mem:actions", a1.id, a1); - await kv.set("mem:actions", a2.id, a2); - - const result = (await sdk.trigger("mem::auto-crystallize", {})) as { - success: boolean; - groupCount: number; - crystalIds: string[]; - }; - - expect(result.success).toBe(true); - expect(result.groupCount).toBe(2); - expect(result.crystalIds.length).toBe(2); - expect(result.crystalIds[0]).toMatch(/^crys_/); - expect(result.crystalIds[1]).toMatch(/^crys_/); - }); - - it("filters by project when specified", async () => { - const a1 = makeAction({ id: "act_fp1", status: "done", project: "keep" }); - const a2 = makeAction({ id: "act_fp2", status: "done", project: "skip" }); - await kv.set("mem:actions", a1.id, a1); - await kv.set("mem:actions", a2.id, a2); - - const result = (await sdk.trigger("mem::auto-crystallize", { - project: "keep", - dryRun: true, - })) as { - success: boolean; - groupCount: number; - groups: { groupKey: string; actionCount: number }[]; - }; - - expect(result.success).toBe(true); - expect(result.groupCount).toBe(1); - expect(result.groups[0].groupKey).toBe("keep"); - }); - - it("returns empty when no qualifying actions exist", async () => { - const result = (await sdk.trigger("mem::auto-crystallize", {})) as { - success: boolean; - groupCount: number; - crystalIds: string[]; - }; - - expect(result.success).toBe(true); - expect(result.groupCount).toBe(0); - expect(result.crystalIds).toEqual([]); - }); - }); -}); diff --git a/test/diagnostics.test.ts b/test/diagnostics.test.ts deleted file mode 100644 index 4951778a..00000000 --- a/test/diagnostics.test.ts +++ /dev/null @@ -1,868 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerDiagnosticsFunction } from "../src/functions/diagnostics.js"; -import type { - Action, - ActionEdge, - DiagnosticCheck, - Lease, - Sentinel, - Sketch, - Signal, - Session, - Memory, - MeshPeer, -} from "../src/types.js"; -import { KV } from "../src/state/schema.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeAction(overrides: Partial = {}): Action { - return { - id: `act_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`, - title: "Test action", - description: "", - status: "pending", - priority: 5, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - createdBy: "agent-1", - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - ...overrides, - }; -} - -function makeLease(overrides: Partial = {}): Lease { - return { - id: `lease_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`, - actionId: "act_missing", - agentId: "agent-1", - acquiredAt: new Date().toISOString(), - expiresAt: new Date(Date.now() + 60_000).toISOString(), - status: "active", - ...overrides, - }; -} - -function makeEdge(overrides: Partial = {}): ActionEdge { - return { - id: `ae_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`, - type: "requires", - sourceActionId: "src", - targetActionId: "tgt", - createdAt: new Date().toISOString(), - ...overrides, - }; -} - -function makeSentinel(overrides: Partial = {}): Sentinel { - return { - id: `sen_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`, - name: "Test sentinel", - type: "timer", - status: "watching", - config: {}, - createdAt: new Date().toISOString(), - linkedActionIds: [], - ...overrides, - }; -} - -function makeSketch(overrides: Partial = {}): Sketch { - return { - id: `sk_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`, - title: "Test sketch", - description: "", - status: "active", - actionIds: [], - createdAt: new Date().toISOString(), - expiresAt: new Date(Date.now() + 60_000).toISOString(), - ...overrides, - }; -} - -function makeSignal(overrides: Partial = {}): Signal { - return { - id: `sig_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`, - from: "agent-1", - type: "info", - content: "test", - createdAt: new Date().toISOString(), - ...overrides, - }; -} - -function makeSession(overrides: Partial = {}): Session { - return { - id: `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`, - project: "test", - cwd: "/tmp", - startedAt: new Date().toISOString(), - status: "active", - observationCount: 0, - ...overrides, - }; -} - -function makeMemory(overrides: Partial = {}): Memory { - return { - id: `mem_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "fact", - title: "Test memory", - content: "content", - concepts: [], - files: [], - sessionIds: [], - strength: 1, - version: 1, - isLatest: true, - ...overrides, - }; -} - -function makePeer(overrides: Partial = {}): MeshPeer { - return { - id: `peer_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`, - url: "http://localhost:3111", - name: "Test peer", - status: "connected", - sharedScopes: [], - ...overrides, - }; -} - -describe("Diagnostics Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerDiagnosticsFunction(sdk as never, kv as never); - }); - - describe("mem::diagnose", () => { - it("empty system passes all checks", async () => { - const result = (await sdk.trigger("mem::diagnose", {})) as { - success: boolean; - checks: DiagnosticCheck[]; - summary: { pass: number; warn: number; fail: number; fixable: number }; - }; - - expect(result.success).toBe(true); - // 15 = 8 original (actions, leases, sentinels, sketches, signals, - // sessions, memories, mesh) + 6 added in #lesson-visibility - // (lessons, summaries, semantic, procedural, crystals, insights) + - // 1 added in #memory-project-scope (memory-project-coverage). - expect(result.summary.pass).toBe(15); - expect(result.summary.warn).toBe(0); - expect(result.summary.fail).toBe(0); - expect(result.summary.fixable).toBe(0); - expect(result.checks.every((c) => c.status === "pass")).toBe(true); - }); - - it("active action with no lease produces warn", async () => { - const action = makeAction({ status: "active" }); - await kv.set(KV.actions, action.id, action); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["actions"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("active-no-lease:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("warn"); - expect(check!.fixable).toBe(false); - }); - - it("blocked action with all deps done produces fail (fixable)", async () => { - const dep = makeAction({ status: "done" }); - const blocked = makeAction({ status: "blocked" }); - const edge = makeEdge({ - sourceActionId: blocked.id, - targetActionId: dep.id, - type: "requires", - }); - await kv.set(KV.actions, dep.id, dep); - await kv.set(KV.actions, blocked.id, blocked); - await kv.set(KV.actionEdges, edge.id, edge); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["actions"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("blocked-deps-done:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("fail"); - expect(check!.fixable).toBe(true); - }); - - it("pending action with unsatisfied deps produces fail (fixable)", async () => { - const dep = makeAction({ status: "active" }); - const pending = makeAction({ status: "pending" }); - const edge = makeEdge({ - sourceActionId: pending.id, - targetActionId: dep.id, - type: "requires", - }); - await kv.set(KV.actions, dep.id, dep); - await kv.set(KV.actions, pending.id, pending); - await kv.set(KV.actionEdges, edge.id, edge); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["actions"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("pending-unsatisfied-deps:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("fail"); - expect(check!.fixable).toBe(true); - }); - - it("expired active lease produces fail (fixable)", async () => { - const action = makeAction({ status: "active" }); - const lease = makeLease({ - actionId: action.id, - status: "active", - expiresAt: new Date(Date.now() - 60_000).toISOString(), - }); - await kv.set(KV.actions, action.id, action); - await kv.set(KV.leases, lease.id, lease); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["leases"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("expired-lease:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("fail"); - expect(check!.fixable).toBe(true); - }); - - it("orphaned lease (action gone) produces fail (fixable)", async () => { - const lease = makeLease({ - actionId: "act_gone", - status: "active", - expiresAt: new Date(Date.now() + 60_000).toISOString(), - }); - await kv.set(KV.leases, lease.id, lease); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["leases"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("orphaned-lease:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("fail"); - expect(check!.fixable).toBe(true); - }); - - it("expired watching sentinel produces fail (fixable)", async () => { - const sentinel = makeSentinel({ - status: "watching", - expiresAt: new Date(Date.now() - 60_000).toISOString(), - }); - await kv.set(KV.sentinels, sentinel.id, sentinel); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["sentinels"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("expired-sentinel:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("fail"); - expect(check!.fixable).toBe(true); - }); - - it("sentinel referencing missing action produces warn", async () => { - const sentinel = makeSentinel({ - linkedActionIds: ["act_nonexistent"], - }); - await kv.set(KV.sentinels, sentinel.id, sentinel); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["sentinels"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("sentinel-missing-action:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("warn"); - expect(check!.fixable).toBe(false); - }); - - it("expired active sketch produces fail (fixable)", async () => { - const sketch = makeSketch({ - status: "active", - expiresAt: new Date(Date.now() - 60_000).toISOString(), - }); - await kv.set(KV.sketches, sketch.id, sketch); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["sketches"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("expired-sketch:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("fail"); - expect(check!.fixable).toBe(true); - }); - - it("expired signal produces fail (fixable)", async () => { - const signal = makeSignal({ - expiresAt: new Date(Date.now() - 60_000).toISOString(), - }); - await kv.set(KV.signals, signal.id, signal); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["signals"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("expired-signal:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("fail"); - expect(check!.fixable).toBe(true); - }); - - it("active session older than 24h produces warn", async () => { - const session = makeSession({ - status: "active", - startedAt: new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString(), - }); - await kv.set(KV.sessions, session.id, session); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["sessions"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("abandoned-session:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("warn"); - expect(check!.fixable).toBe(false); - }); - - it("memory with stale isLatest produces fail (fixable)", async () => { - const oldMemory = makeMemory({ isLatest: true }); - const newMemory = makeMemory({ supersedes: [oldMemory.id] }); - await kv.set(KV.memories, oldMemory.id, oldMemory); - await kv.set(KV.memories, newMemory.id, newMemory); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["memories"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("memory-stale-latest:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("fail"); - expect(check!.fixable).toBe(true); - }); - - it("memory superseding non-existent produces warn", async () => { - const memory = makeMemory({ supersedes: ["mem_gone"] }); - await kv.set(KV.memories, memory.id, memory); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["memories"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("memory-missing-supersedes:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("warn"); - expect(check!.fixable).toBe(false); - }); - - it("stale mesh peer produces warn", async () => { - const peer = makePeer({ - lastSyncAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), - }); - await kv.set(KV.mesh, peer.id, peer); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["mesh"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("stale-peer:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("warn"); - expect(check!.fixable).toBe(false); - }); - - it("error mesh peer produces warn", async () => { - const peer = makePeer({ status: "error" }); - await kv.set(KV.mesh, peer.id, peer); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["mesh"], - })) as { checks: DiagnosticCheck[] }; - - const check = result.checks.find((c) => - c.name.startsWith("error-peer:"), - ); - expect(check).toBeDefined(); - expect(check!.status).toBe("warn"); - expect(check!.fixable).toBe(false); - }); - - it("filters by categories", async () => { - const action = makeAction({ status: "active" }); - await kv.set(KV.actions, action.id, action); - - const signal = makeSignal({ - expiresAt: new Date(Date.now() - 60_000).toISOString(), - }); - await kv.set(KV.signals, signal.id, signal); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["signals"], - })) as { checks: DiagnosticCheck[] }; - - expect(result.checks.every((c) => c.category === "signals")).toBe(true); - expect( - result.checks.some((c) => c.category === "actions"), - ).toBe(false); - }); - }); - - describe("mem::heal", () => { - it("unblocks stuck blocked action", async () => { - const dep = makeAction({ status: "done" }); - const blocked = makeAction({ status: "blocked", title: "Stuck task" }); - const edge = makeEdge({ - sourceActionId: blocked.id, - targetActionId: dep.id, - type: "requires", - }); - await kv.set(KV.actions, dep.id, dep); - await kv.set(KV.actions, blocked.id, blocked); - await kv.set(KV.actionEdges, edge.id, edge); - - const result = (await sdk.trigger("mem::heal", { - categories: ["actions"], - })) as { success: boolean; fixed: number; details: string[] }; - - expect(result.success).toBe(true); - expect(result.fixed).toBe(1); - expect(result.details[0]).toContain("Unblocked"); - - const updated = await kv.get(KV.actions, blocked.id); - expect(updated!.status).toBe("pending"); - }); - - it("blocks pending action with unsatisfied deps", async () => { - const dep = makeAction({ status: "active" }); - const pending = makeAction({ - status: "pending", - title: "Should be blocked", - }); - const edge = makeEdge({ - sourceActionId: pending.id, - targetActionId: dep.id, - type: "requires", - }); - await kv.set(KV.actions, dep.id, dep); - await kv.set(KV.actions, pending.id, pending); - await kv.set(KV.actionEdges, edge.id, edge); - - const result = (await sdk.trigger("mem::heal", { - categories: ["actions"], - })) as { success: boolean; fixed: number; details: string[] }; - - expect(result.success).toBe(true); - expect(result.fixed).toBe(1); - expect(result.details[0]).toContain("Blocked"); - - const updated = await kv.get(KV.actions, pending.id); - expect(updated!.status).toBe("blocked"); - }); - - it("expires stale lease and resets action", async () => { - const action = makeAction({ - status: "active", - assignedTo: "agent-1", - }); - const lease = makeLease({ - actionId: action.id, - agentId: "agent-1", - status: "active", - expiresAt: new Date(Date.now() - 60_000).toISOString(), - }); - await kv.set(KV.actions, action.id, action); - await kv.set(KV.leases, lease.id, lease); - - const result = (await sdk.trigger("mem::heal", { - categories: ["leases"], - })) as { success: boolean; fixed: number; details: string[] }; - - expect(result.success).toBe(true); - expect(result.fixed).toBe(1); - expect(result.details[0]).toContain("Expired lease"); - - const updatedLease = await kv.get(KV.leases, lease.id); - expect(updatedLease!.status).toBe("expired"); - - const updatedAction = await kv.get(KV.actions, action.id); - expect(updatedAction!.status).toBe("pending"); - expect(updatedAction!.assignedTo).toBeUndefined(); - }); - - it("deletes orphaned lease", async () => { - const lease = makeLease({ - actionId: "act_gone", - status: "released", - expiresAt: new Date(Date.now() + 60_000).toISOString(), - }); - await kv.set(KV.leases, lease.id, lease); - - const result = (await sdk.trigger("mem::heal", { - categories: ["leases"], - })) as { success: boolean; fixed: number; details: string[] }; - - expect(result.success).toBe(true); - expect(result.fixed).toBe(1); - expect(result.details[0]).toContain("Deleted orphaned lease"); - - const deleted = await kv.get(KV.leases, lease.id); - expect(deleted).toBeNull(); - }); - - it("expires stale sentinel", async () => { - const sentinel = makeSentinel({ - status: "watching", - name: "Stale watcher", - expiresAt: new Date(Date.now() - 60_000).toISOString(), - }); - await kv.set(KV.sentinels, sentinel.id, sentinel); - - const result = (await sdk.trigger("mem::heal", { - categories: ["sentinels"], - })) as { success: boolean; fixed: number; details: string[] }; - - expect(result.success).toBe(true); - expect(result.fixed).toBe(1); - expect(result.details[0]).toContain("Expired sentinel"); - - const updated = await kv.get(KV.sentinels, sentinel.id); - expect(updated!.status).toBe("expired"); - }); - - it("dry run reports but does not fix", async () => { - const dep = makeAction({ status: "done" }); - const blocked = makeAction({ status: "blocked", title: "Stuck task" }); - const edge = makeEdge({ - sourceActionId: blocked.id, - targetActionId: dep.id, - type: "requires", - }); - await kv.set(KV.actions, dep.id, dep); - await kv.set(KV.actions, blocked.id, blocked); - await kv.set(KV.actionEdges, edge.id, edge); - - const result = (await sdk.trigger("mem::heal", { - categories: ["actions"], - dryRun: true, - })) as { success: boolean; fixed: number; details: string[] }; - - expect(result.success).toBe(true); - expect(result.fixed).toBe(1); - expect(result.details[0]).toContain("[dry-run]"); - - const unchanged = await kv.get(KV.actions, blocked.id); - expect(unchanged!.status).toBe("blocked"); - }); - }); - - describe("per-store tally categories (#lesson-visibility)", () => { - it("lessons category: passes with valid live lessons + ignores tombstoned", async () => { - await kv.set(KV.lessons, "lsn_live", { - id: "lsn_live", content: "x", context: "", confidence: 0.8, - reinforcements: 0, source: "manual", sourceIds: [], tags: [], - createdAt: "", updatedAt: "", decayRate: 0.05, - }); - await kv.set(KV.lessons, "lsn_tomb", { - id: "lsn_tomb", content: "x", context: "", confidence: 0.5, - reinforcements: 0, source: "manual", sourceIds: [], tags: [], - createdAt: "", updatedAt: "", decayRate: 0.05, deleted: true, - }); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["lessons"], - })) as { checks: DiagnosticCheck[] }; - - const ok = result.checks.find((c) => c.name === "lessons-ok"); - expect(ok?.status).toBe("pass"); - expect(ok?.message).toMatch(/All 1 lessons.*1 tombstoned/); - }); - - it("lessons category: warns on out-of-range confidence", async () => { - await kv.set(KV.lessons, "lsn_bad", { - id: "lsn_bad", content: "x", context: "", confidence: 1.5, - reinforcements: 0, source: "manual", sourceIds: [], tags: [], - createdAt: "", updatedAt: "", decayRate: 0.05, - }); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["lessons"], - })) as { checks: DiagnosticCheck[] }; - - const warn = result.checks.find((c) => c.name.startsWith("lesson-bad-confidence:")); - expect(warn?.status).toBe("warn"); - }); - - it("summaries category: warns on missing title", async () => { - await kv.set(KV.summaries, "ses_1", { - sessionId: "ses_1", project: "p", createdAt: "", title: "", - narrative: "n", keyDecisions: [], filesModified: [], concepts: [], - observationCount: 1, - }); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["summaries"], - })) as { checks: DiagnosticCheck[] }; - - const warn = result.checks.find((c) => c.name.startsWith("summary-missing-title:")); - expect(warn?.status).toBe("warn"); - }); - - it("procedural category: warns on empty steps", async () => { - await kv.set(KV.procedural, "proc_1", { - id: "proc_1", name: "noop", steps: [], triggerCondition: "x", - frequency: 1, sourceSessionIds: [], strength: 0.5, - createdAt: "", updatedAt: "", - }); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["procedural"], - })) as { checks: DiagnosticCheck[] }; - - const warn = result.checks.find((c) => c.name.startsWith("procedural-empty-steps:")); - expect(warn?.status).toBe("warn"); - }); - - it("crystals category: warns on empty narrative", async () => { - await kv.set(KV.crystals, "cry_1", { - id: "cry_1", narrative: "", keyOutcomes: [], filesAffected: [], - lessons: [], sourceActionIds: [], createdAt: "", - }); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["crystals"], - })) as { checks: DiagnosticCheck[] }; - - const warn = result.checks.find((c) => c.name.startsWith("crystal-empty-narrative:")); - expect(warn?.status).toBe("warn"); - }); - - it("insights category: warns on out-of-range confidence", async () => { - await kv.set(KV.insights, "ins_bad", { - id: "ins_bad", title: "t", content: "c", confidence: -0.1, - reinforcements: 0, sourceConceptCluster: [], sourceMemoryIds: [], - sourceLessonIds: [], sourceCrystalIds: [], tags: [], - createdAt: "", updatedAt: "", decayRate: 0.05, - }); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["insights"], - })) as { checks: DiagnosticCheck[] }; - - const warn = result.checks.find((c) => c.name.startsWith("insight-bad-confidence:")); - expect(warn?.status).toBe("warn"); - }); - - it("semantic category: warns on out-of-range confidence", async () => { - await kv.set(KV.semantic, "sem_bad", { - id: "sem_bad", fact: "f", confidence: 2.0, sourceSessionIds: [], - sourceMemoryIds: [], accessCount: 0, lastAccessedAt: "", - strength: 0, createdAt: "", updatedAt: "", - }); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["semantic"], - })) as { checks: DiagnosticCheck[] }; - - const warn = result.checks.find((c) => c.name.startsWith("semantic-bad-confidence:")); - expect(warn?.status).toBe("warn"); - }); - - it("categories filter accepts new categories and skips others", async () => { - const result = (await sdk.trigger("mem::diagnose", { - categories: ["lessons", "summaries"], - })) as { checks: DiagnosticCheck[] }; - - expect(result.checks.every((c) => c.category === "lessons" || c.category === "summaries")).toBe(true); - expect(result.checks.some((c) => c.category === "lessons")).toBe(true); - expect(result.checks.some((c) => c.category === "summaries")).toBe(true); - }); - - describe("defensive row-shape handling (regression #473)", () => { - it("NaN/Infinity confidence on a lesson is flagged as warn, not silently passed", async () => { - await kv.set(KV.lessons, "lsn_nan", { - id: "lsn_nan", content: "x", context: "", confidence: NaN, - reinforcements: 0, source: "manual", sourceIds: [], tags: [], - createdAt: "", updatedAt: "", decayRate: 0.05, - }); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["lessons"], - })) as { checks: DiagnosticCheck[] }; - - const warn = result.checks.find((c) => c.name.startsWith("lesson-bad-confidence:")); - expect(warn?.status).toBe("warn"); - }); - - it("non-string summary title doesn't throw — surfaces as warn", async () => { - await kv.set(KV.summaries, "ses_bad_title", { - sessionId: "ses_bad_title", - project: "p", - createdAt: "", - title: null as unknown as string, // simulate corrupted row - narrative: "n", - keyDecisions: [], - filesModified: [], - concepts: [], - observationCount: 1, - }); - - // The bug to guard against: the old code called .trim() unconditionally, - // which throws on null/number, which aborts the whole diagnose run and - // any later category check never executes. Verify diagnose completes - // AND surfaces the bad row. - const result = (await sdk.trigger("mem::diagnose", { - categories: ["summaries", "lessons"], - })) as { checks: DiagnosticCheck[]; success?: boolean }; - - expect(result.success).toBe(true); - const warn = result.checks.find((c) => c.name.startsWith("summary-missing-title:")); - expect(warn?.status).toBe("warn"); - // Later category still ran: - expect(result.checks.some((c) => c.category === "lessons")).toBe(true); - }); - - it("non-string crystal narrative doesn't throw — surfaces as warn", async () => { - await kv.set(KV.crystals, "cry_bad", { - id: "cry_bad", - narrative: undefined as unknown as string, - keyOutcomes: [], - filesAffected: [], - lessons: [], - sourceActionIds: [], - createdAt: "", - }); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["crystals"], - })) as { checks: DiagnosticCheck[]; success?: boolean }; - - expect(result.success).toBe(true); - const warn = result.checks.find((c) => c.name.startsWith("crystal-empty-narrative:")); - expect(warn?.status).toBe("warn"); - }); - - it("Infinity confidence on insight + semantic both flagged", async () => { - await kv.set(KV.insights, "ins_inf", { - id: "ins_inf", - title: "t", - content: "c", - confidence: Infinity, - reinforcements: 0, - sourceConceptCluster: [], - sourceMemoryIds: [], - sourceLessonIds: [], - sourceCrystalIds: [], - tags: [], - createdAt: "", - updatedAt: "", - decayRate: 0.05, - }); - await kv.set(KV.semantic, "sem_nan", { - id: "sem_nan", - fact: "f", - confidence: NaN, - sourceSessionIds: [], - sourceMemoryIds: [], - accessCount: 0, - lastAccessedAt: "", - strength: 0, - createdAt: "", - updatedAt: "", - }); - - const result = (await sdk.trigger("mem::diagnose", { - categories: ["insights", "semantic"], - })) as { checks: DiagnosticCheck[] }; - - expect(result.checks.find((c) => c.name === "insight-bad-confidence:ins_inf")?.status).toBe("warn"); - expect(result.checks.find((c) => c.name === "semantic-bad-confidence:sem_nan")?.status).toBe("warn"); - }); - }); - }); -}); diff --git a/test/embedding-provider.test.ts b/test/embedding-provider.test.ts deleted file mode 100644 index 6c2d263e..00000000 --- a/test/embedding-provider.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { - createEmbeddingProvider, - withDimensionGuard, -} from "../src/providers/embedding/index.js"; -import { GeminiEmbeddingProvider } from "../src/providers/embedding/gemini.js"; -import { OpenAIEmbeddingProvider } from "../src/providers/embedding/openai.js"; -import type { EmbeddingProvider } from "../src/types.js"; - -describe("createEmbeddingProvider", () => { - const originalEnv = { ...process.env }; - - beforeEach(() => { - process.env = { ...originalEnv }; - delete process.env["GEMINI_API_KEY"]; - delete process.env["OPENAI_API_KEY"]; - delete process.env["VOYAGE_API_KEY"]; - delete process.env["COHERE_API_KEY"]; - delete process.env["OPENROUTER_API_KEY"]; - delete process.env["EMBEDDING_PROVIDER"]; - }); - - afterEach(() => { - process.env = originalEnv; - }); - - it("returns null when no API keys are set", () => { - const provider = createEmbeddingProvider(); - expect(provider).toBeNull(); - }); - - it("returns GeminiEmbeddingProvider when GEMINI_API_KEY is set", () => { - process.env["GEMINI_API_KEY"] = "test-key-123"; - const provider = createEmbeddingProvider(); - expect(provider).toBeInstanceOf(GeminiEmbeddingProvider); - expect(provider!.name).toBe("gemini"); - }); - - it("returns OpenAIEmbeddingProvider when OPENAI_API_KEY is set", () => { - process.env["OPENAI_API_KEY"] = "test-key-456"; - const provider = createEmbeddingProvider(); - expect(provider).toBeInstanceOf(OpenAIEmbeddingProvider); - expect(provider!.name).toBe("openai"); - }); - - it("EMBEDDING_PROVIDER override takes precedence", () => { - process.env["GEMINI_API_KEY"] = "test-key-123"; - process.env["OPENAI_API_KEY"] = "test-key-456"; - process.env["EMBEDDING_PROVIDER"] = "openai"; - const provider = createEmbeddingProvider(); - expect(provider).toBeInstanceOf(OpenAIEmbeddingProvider); - }); -}); - -describe("OpenAIEmbeddingProvider", () => { - const originalEnv = { ...process.env }; - - beforeEach(() => { - process.env = { ...originalEnv }; - delete process.env["OPENAI_BASE_URL"]; - delete process.env["OPENAI_EMBEDDING_BASE_URL"]; - delete process.env["OPENAI_EMBEDDING_API_KEY"]; - delete process.env["OPENAI_EMBEDDING_MODEL"]; - delete process.env["OPENAI_EMBEDDING_DIMENSIONS"]; - }); - - afterEach(() => { - process.env = originalEnv; - }); - - it("uses default base URL and model when env vars are not set", () => { - const provider = new OpenAIEmbeddingProvider("test-key"); - expect(provider.name).toBe("openai"); - expect(provider.dimensions).toBe(1536); - }); - - it("throws when no API key is provided", () => { - delete process.env["OPENAI_API_KEY"]; - delete process.env["OPENAI_EMBEDDING_API_KEY"]; - expect(() => new OpenAIEmbeddingProvider()).toThrow(/API key is required.*OPENAI_EMBEDDING_API_KEY.*OPENAI_API_KEY/); - }); - - it("respects OPENAI_BASE_URL env var", async () => { - process.env["OPENAI_BASE_URL"] = "https://my-proxy.example.com"; - const provider = new OpenAIEmbeddingProvider("test-key"); - - const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue( - new Response(JSON.stringify({ data: [{ embedding: [0.1, 0.2, 0.3] }] }), { status: 200 }), - ); - - await provider.embed("hello"); - expect(fetchSpy).toHaveBeenCalledWith( - "https://my-proxy.example.com/v1/embeddings", - expect.any(Object), - ); - - fetchSpy.mockRestore(); - }); - - it("respects OPENAI_EMBEDDING_MODEL env var", async () => { - process.env["OPENAI_EMBEDDING_MODEL"] = "text-embedding-3-large"; - const provider = new OpenAIEmbeddingProvider("test-key"); - - const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue( - new Response(JSON.stringify({ data: [{ embedding: [0.1, 0.2, 0.3] }] }), { status: 200 }), - ); - - await provider.embed("hello"); - const body = JSON.parse((fetchSpy.mock.calls[0][1] as RequestInit).body as string); - expect(body.model).toBe("text-embedding-3-large"); - - fetchSpy.mockRestore(); - }); - - it("derives dimensions from model in the known-models table", () => { - process.env["OPENAI_EMBEDDING_MODEL"] = "text-embedding-3-large"; - const large = new OpenAIEmbeddingProvider("test-key"); - expect(large.dimensions).toBe(3072); - - process.env["OPENAI_EMBEDDING_MODEL"] = "text-embedding-ada-002"; - const ada = new OpenAIEmbeddingProvider("test-key"); - expect(ada.dimensions).toBe(1536); - - process.env["OPENAI_EMBEDDING_MODEL"] = "text-embedding-3-small"; - const small = new OpenAIEmbeddingProvider("test-key"); - expect(small.dimensions).toBe(1536); - }); - - it("OPENAI_EMBEDDING_DIMENSIONS overrides the model-derived dimensions", () => { - process.env["OPENAI_EMBEDDING_MODEL"] = "text-embedding-3-large"; - process.env["OPENAI_EMBEDDING_DIMENSIONS"] = "768"; - const provider = new OpenAIEmbeddingProvider("test-key"); - expect(provider.dimensions).toBe(768); - }); - - it("falls back to 1536 for unknown custom models", () => { - process.env["OPENAI_EMBEDDING_MODEL"] = "mystery-self-hosted-model"; - const provider = new OpenAIEmbeddingProvider("test-key"); - expect(provider.dimensions).toBe(1536); - }); - - it("rejects invalid OPENAI_EMBEDDING_DIMENSIONS values", () => { - process.env["OPENAI_EMBEDDING_DIMENSIONS"] = "not-a-number"; - expect(() => new OpenAIEmbeddingProvider("test-key")).toThrow( - /OPENAI_EMBEDDING_DIMENSIONS must be a positive integer/, - ); - - process.env["OPENAI_EMBEDDING_DIMENSIONS"] = "-5"; - expect(() => new OpenAIEmbeddingProvider("test-key")).toThrow( - /OPENAI_EMBEDDING_DIMENSIONS must be a positive integer/, - ); - - process.env["OPENAI_EMBEDDING_DIMENSIONS"] = "0"; - expect(() => new OpenAIEmbeddingProvider("test-key")).toThrow( - /OPENAI_EMBEDDING_DIMENSIONS must be a positive integer/, - ); - }); -}); - -describe("withDimensionGuard", () => { - function fakeProvider(opts: { - dimensions: number; - embed: () => Float32Array; - batch?: () => Float32Array[]; - image?: () => Float32Array; - }): EmbeddingProvider { - const provider: EmbeddingProvider = { - name: "fake", - dimensions: opts.dimensions, - embed: async () => opts.embed(), - embedBatch: async () => opts.batch?.() ?? [opts.embed()], - }; - if (opts.image) provider.embedImage = async () => opts.image!(); - return provider; - } - - it("preserves the wrapped provider's prototype so instanceof keeps working", async () => { - class FakeProvider implements EmbeddingProvider { - readonly name = "fake-class"; - readonly dimensions = 4; - async embed(): Promise { - return new Float32Array([1, 2, 3, 4]); - } - async embedBatch(): Promise { - return [new Float32Array([1, 2, 3, 4])]; - } - } - const guarded = withDimensionGuard(new FakeProvider()); - expect(guarded).toBeInstanceOf(FakeProvider); - expect(guarded.name).toBe("fake-class"); - expect(guarded.dimensions).toBe(4); - }); - - it("passes through vectors that match the declared dimensions", async () => { - const guarded = withDimensionGuard( - fakeProvider({ - dimensions: 4, - embed: () => new Float32Array([1, 2, 3, 4]), - batch: () => [new Float32Array([1, 2, 3, 4]), new Float32Array([5, 6, 7, 8])], - }), - ); - await expect(guarded.embed("x")).resolves.toEqual(new Float32Array([1, 2, 3, 4])); - await expect(guarded.embedBatch(["a", "b"])).resolves.toHaveLength(2); - }); - - it("throws when embed() returns the wrong dimension", async () => { - const guarded = withDimensionGuard( - fakeProvider({ - dimensions: 4, - embed: () => new Float32Array([1, 2, 3]), - }), - ); - await expect(guarded.embed("x")).rejects.toThrow( - /dimension mismatch in fake\.embed: expected 4, got 3/, - ); - }); - - it("throws when any vector in embedBatch() returns the wrong dimension", async () => { - const guarded = withDimensionGuard( - fakeProvider({ - dimensions: 4, - embed: () => new Float32Array([1, 2, 3, 4]), - batch: () => [new Float32Array([1, 2, 3, 4]), new Float32Array([1, 2])], - }), - ); - await expect(guarded.embedBatch(["a", "b"])).rejects.toThrow( - /dimension mismatch in fake\.embedBatch\[1\]: expected 4, got 2/, - ); - }); - - it("guards embedImage when present and omits it when absent", async () => { - const withImage = withDimensionGuard( - fakeProvider({ - dimensions: 4, - embed: () => new Float32Array([1, 2, 3, 4]), - image: () => new Float32Array([1, 2]), - }), - ); - expect(withImage.embedImage).toBeDefined(); - await expect(withImage.embedImage!("/tmp/x")).rejects.toThrow( - /dimension mismatch in fake\.embedImage: expected 4, got 2/, - ); - - const withoutImage = withDimensionGuard( - fakeProvider({ - dimensions: 4, - embed: () => new Float32Array([1, 2, 3, 4]), - }), - ); - expect(withoutImage.embedImage).toBeUndefined(); - }); -}); diff --git a/test/enrich-project-isolation.test.ts b/test/enrich-project-isolation.test.ts deleted file mode 100644 index 40016f13..00000000 --- a/test/enrich-project-isolation.test.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerEnrichFunction } from "../src/functions/enrich.js"; -import type { Memory } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - const triggerOverrides = new Map(); - return { - registerFunction: (id: string, handler: Function) => { - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : (idOrInput as { payload: unknown }).payload; - if (triggerOverrides.has(id)) return triggerOverrides.get(id)!(payload); - const fn = functions.get(id); - if (!fn) throw new Error(`No function registered: ${id}`); - return fn(payload); - }, - overrideTrigger: (id: string, handler: Function) => { - triggerOverrides.set(id, handler); - }, - }; -} - -function makeBugMemory(overrides: Partial = {}): Memory { - return { - id: "mem_bug_1", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "bug", - title: "express-jwt whitespace bug", - content: "express-jwt throws 401 when Authorization header has extra whitespace after Bearer", - concepts: ["auth", "jwt"], - files: ["src/middleware/auth.ts"], - sessionIds: ["sess-api-001"], - strength: 8, - version: 1, - isLatest: true, - ...overrides, - }; -} - -describe("mem::enrich — project isolation for bug memories", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerEnrichFunction(sdk as never, kv as never); - sdk.overrideTrigger("mem::file-context", async () => ({ context: "" })); - sdk.overrideTrigger("mem::search", async () => ({ results: [] })); - }); - - it("does not surface a scoped bug memory when caller project differs", async () => { - await kv.set("mem:memories", "mem_bug_1", makeBugMemory({ project: "api" })); - - const result = await sdk.trigger("mem::enrich", { - sessionId: "sess-web-001", - files: ["src/middleware/auth.ts"], - project: "web", - }) as { context: string }; - - expect(result.context).not.toContain("agentmemory-past-errors"); - expect(result.context).not.toContain("express-jwt"); - }); - - it("surfaces a scoped bug memory when caller project matches", async () => { - await kv.set("mem:memories", "mem_bug_1", makeBugMemory({ project: "api" })); - - const result = await sdk.trigger("mem::enrich", { - sessionId: "sess-api-001", - files: ["src/middleware/auth.ts"], - project: "api", - }) as { context: string }; - - expect(result.context).toContain("agentmemory-past-errors"); - expect(result.context).toContain("express-jwt"); - }); - - it("surfaces an unscoped (legacy) bug memory regardless of caller project", async () => { - await kv.set("mem:memories", "mem_bug_1", makeBugMemory({ project: undefined })); - - const result = await sdk.trigger("mem::enrich", { - sessionId: "sess-web-001", - files: ["src/middleware/auth.ts"], - project: "web", - }) as { context: string }; - - // Unscoped memories remain visible everywhere for backward-compat - expect(result.context).toContain("agentmemory-past-errors"); - expect(result.context).toContain("express-jwt"); - }); - - it("surfaces an unscoped bug memory when caller provides no project", async () => { - await kv.set("mem:memories", "mem_bug_1", makeBugMemory({ project: undefined })); - - const result = await sdk.trigger("mem::enrich", { - sessionId: "sess-api-001", - files: ["src/middleware/auth.ts"], - }) as { context: string }; - - expect(result.context).toContain("agentmemory-past-errors"); - }); - - it("surfaces a scoped bug memory when caller provides no project", async () => { - await kv.set("mem:memories", "mem_bug_1", makeBugMemory({ project: "api" })); - - // No project on the caller — guard does not engage, memory is visible - const result = await sdk.trigger("mem::enrich", { - sessionId: "sess-api-001", - files: ["src/middleware/auth.ts"], - }) as { context: string }; - - expect(result.context).toContain("agentmemory-past-errors"); - }); - - it("isolates multiple memories from different projects correctly", async () => { - await kv.set("mem:memories", "mem_api", makeBugMemory({ - id: "mem_api", - project: "api", - title: "express-jwt whitespace", - content: "express-jwt whitespace issue", - files: ["src/middleware/auth.ts"], - })); - await kv.set("mem:memories", "mem_web", makeBugMemory({ - id: "mem_web", - project: "web", - title: "nextauth cookie", - content: "nextauth cookie domain mismatch", - files: ["src/middleware/auth.ts"], - })); - - const apiResult = await sdk.trigger("mem::enrich", { - sessionId: "sess-api-001", - files: ["src/middleware/auth.ts"], - project: "api", - }) as { context: string }; - - expect(apiResult.context).toContain("express-jwt"); - expect(apiResult.context).not.toContain("nextauth"); - - const webResult = await sdk.trigger("mem::enrich", { - sessionId: "sess-web-001", - files: ["src/middleware/auth.ts"], - project: "web", - }) as { context: string }; - - expect(webResult.context).toContain("nextauth"); - expect(webResult.context).not.toContain("express-jwt"); - }); - - it("only includes latest bug memories, respecting project scope", async () => { - await kv.set("mem:memories", "mem_old", makeBugMemory({ - id: "mem_old", - project: "api", - title: "old express bug", - content: "old express auth bug now fixed", - files: ["src/middleware/auth.ts"], - isLatest: false, - })); - await kv.set("mem:memories", "mem_new", makeBugMemory({ - id: "mem_new", - project: "api", - title: "new express bug", - content: "new express auth edge case", - files: ["src/middleware/auth.ts"], - isLatest: true, - })); - - const result = await sdk.trigger("mem::enrich", { - sessionId: "sess-api-001", - files: ["src/middleware/auth.ts"], - project: "api", - }) as { context: string }; - - expect(result.context).toContain("new express bug"); - expect(result.context).not.toContain("old express auth bug"); - }); -}); - -describe("mem::enrich — project forwarded to mem::search", () => { - it("passes project to the search trigger when provided", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerEnrichFunction(sdk as never, kv as never); - - let capturedSearchPayload: Record = {}; - sdk.overrideTrigger("mem::file-context", async () => ({ context: "" })); - sdk.overrideTrigger("mem::search", async (payload: unknown) => { - capturedSearchPayload = payload as Record; - return { results: [] }; - }); - - await sdk.trigger("mem::enrich", { - sessionId: "sess-api-001", - files: ["src/middleware/auth.ts"], - project: "api", - }); - - expect(capturedSearchPayload.project).toBe("api"); - }); - - it("does not pass project to search when caller provides none", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerEnrichFunction(sdk as never, kv as never); - - let capturedSearchPayload: Record = {}; - sdk.overrideTrigger("mem::file-context", async () => ({ context: "" })); - sdk.overrideTrigger("mem::search", async (payload: unknown) => { - capturedSearchPayload = payload as Record; - return { results: [] }; - }); - - await sdk.trigger("mem::enrich", { - sessionId: "sess-api-001", - files: ["src/middleware/auth.ts"], - }); - - expect(capturedSearchPayload.project).toBeUndefined(); - }); -}); diff --git a/test/enrich.test.ts b/test/enrich.test.ts deleted file mode 100644 index adfb31f8..00000000 --- a/test/enrich.test.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerEnrichFunction } from "../src/functions/enrich.js"; -import type { Memory } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function makeMemory(overrides: Partial = {}): Memory { - return { - id: "mem_1", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "bug", - title: "Known bug", - content: "Null pointer in handler", - concepts: ["bug"], - files: ["src/handler.ts"], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, - ...overrides, - }; -} - -function mockSdk() { - const functions = new Map(); - const triggerOverrides = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - if (triggerOverrides.has(id)) { - return triggerOverrides.get(id)!(payload); - } - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - overrideTrigger: (id: string, handler: Function) => { - triggerOverrides.set(id, handler); - }, - getFunction: (id: string) => functions.get(id), - }; -} - -describe("Enrich Function", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerEnrichFunction(sdk as never, kv as never); - }); - - it("returns file context and relevant memories", async () => { - sdk.overrideTrigger( - "mem::file-context", - async () => ({ context: "File was edited in session ses_1" }), - ); - sdk.overrideTrigger("mem::search", async () => ({ - results: [ - { observation: { narrative: "User fixed a bug in handler" } }, - ], - })); - - const bugMem = makeMemory({ - id: "bug_1", - files: ["src/handler.ts"], - type: "bug", - }); - await kv.set("mem:memories", "bug_1", bugMem); - - const result = (await sdk.trigger("mem::enrich", { - sessionId: "ses_1", - files: ["src/handler.ts"], - })) as { context: string; truncated: boolean }; - - expect(result.context).toContain("File was edited in session ses_1"); - expect(result.context).toContain("agentmemory-relevant-context"); - expect(result.context).toContain("agentmemory-past-errors"); - expect(result.truncated).toBe(false); - }); - - it("extracts terms from Grep/Glob pattern for search", async () => { - let capturedQuery = ""; - sdk.overrideTrigger("mem::file-context", async () => ({ context: "" })); - sdk.overrideTrigger("mem::search", async (data: any) => { - capturedQuery = data.query; - return { results: [] }; - }); - - await sdk.trigger("mem::enrich", { - sessionId: "ses_1", - files: ["src/utils.ts"], - terms: ["handleError"], - toolName: "Grep", - }); - - expect(capturedQuery).toContain("handleError"); - }); - - it("truncates context at 4000 chars", async () => { - const longContext = "x".repeat(5000); - sdk.overrideTrigger( - "mem::file-context", - async () => ({ context: longContext }), - ); - sdk.overrideTrigger("mem::search", async () => ({ results: [] })); - - const result = (await sdk.trigger("mem::enrich", { - sessionId: "ses_1", - files: ["src/big.ts"], - })) as { context: string; truncated: boolean }; - - expect(result.context.length).toBe(4000); - expect(result.truncated).toBe(true); - }); - - it("returns empty context when no data found", async () => { - sdk.overrideTrigger("mem::file-context", async () => ({ context: "" })); - sdk.overrideTrigger("mem::search", async () => ({ results: [] })); - - const result = (await sdk.trigger("mem::enrich", { - sessionId: "ses_1", - files: ["src/new-file.ts"], - })) as { context: string; truncated: boolean }; - - expect(result.context).toBe(""); - expect(result.truncated).toBe(false); - }); - - it("handles failed triggers without crashing", async () => { - sdk.overrideTrigger("mem::file-context", async () => { - throw new Error("file-context failed"); - }); - sdk.overrideTrigger("mem::search", async () => { - throw new Error("search failed"); - }); - - const result = (await sdk.trigger("mem::enrich", { - sessionId: "ses_1", - files: ["src/handler.ts"], - })) as { context: string; truncated: boolean }; - - expect(result.context).toBeDefined(); - expect(result.truncated).toBe(false); - }); - - it("includes bug memories that overlap with requested files", async () => { - sdk.overrideTrigger("mem::file-context", async () => ({ context: "" })); - sdk.overrideTrigger("mem::search", async () => ({ results: [] })); - - const bugMem = makeMemory({ - id: "bug_match", - type: "bug", - title: "Race condition", - content: "Race condition in worker pool", - files: ["src/worker.ts"], - isLatest: true, - }); - const nonBugMem = makeMemory({ - id: "pattern_1", - type: "pattern", - title: "Code pattern", - content: "Singleton pattern used", - files: ["src/worker.ts"], - isLatest: true, - }); - await kv.set("mem:memories", "bug_match", bugMem); - await kv.set("mem:memories", "pattern_1", nonBugMem); - - const result = (await sdk.trigger("mem::enrich", { - sessionId: "ses_1", - files: ["src/worker.ts"], - })) as { context: string; truncated: boolean }; - - expect(result.context).toContain("Race condition"); - expect(result.context).not.toContain("Singleton pattern"); - }); -}); diff --git a/test/env-loader.test.ts b/test/env-loader.test.ts deleted file mode 100644 index 3c037648..00000000 --- a/test/env-loader.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -const ORIGINAL_HOME = process.env["HOME"]; -const ORIGINAL_USERPROFILE = process.env["USERPROFILE"]; -const ORIGINAL_AGENTMEMORY_HOME = process.env["AGENTMEMORY_HOME"]; - -let sandboxHome: string; - -async function freshConfig() { - vi.resetModules(); - return await import("../src/config.js"); -} - -function writeEnv(contents: string) { - const dir = join(sandboxHome, ".agentmemory"); - mkdirSync(dir, { recursive: true }); - writeFileSync(join(dir, ".env"), contents); -} - -describe("loadEnvFile", () => { - beforeEach(() => { - sandboxHome = mkdtempSync(join(tmpdir(), "agentmemory-env-")); - process.env["HOME"] = sandboxHome; - process.env["USERPROFILE"] = sandboxHome; - delete process.env["AGENTMEMORY_AUTO_COMPRESS"]; - delete process.env["AGENTMEMORY_DROP_STALE_INDEX"]; - delete process.env["CONSOLIDATION_ENABLED"]; - delete process.env["GRAPH_EXTRACTION_ENABLED"]; - delete process.env["TOKEN"]; - delete process.env["HASHVAL"]; - delete process.env["AGENTMEMORY_HOME"]; - }); - - afterEach(() => { - if (ORIGINAL_HOME === undefined) delete process.env["HOME"]; - else process.env["HOME"] = ORIGINAL_HOME; - if (ORIGINAL_USERPROFILE === undefined) delete process.env["USERPROFILE"]; - else process.env["USERPROFILE"] = ORIGINAL_USERPROFILE; - if (ORIGINAL_AGENTMEMORY_HOME === undefined) delete process.env["AGENTMEMORY_HOME"]; - else process.env["AGENTMEMORY_HOME"] = ORIGINAL_AGENTMEMORY_HOME; - rmSync(sandboxHome, { recursive: true, force: true }); - }); - - it("honors AGENTMEMORY_HOME without changing the user's Codex home", async () => { - const dataHome = join(sandboxHome, "isolated-agentmemory"); - process.env["AGENTMEMORY_HOME"] = dataHome; - mkdirSync(dataHome, { recursive: true }); - writeFileSync(join(dataHome, ".env"), "TOKEN=from-data-home"); - - const cfg = await freshConfig(); - expect(cfg.getUserEnvPath()).toBe(join(dataHome, ".env")); - expect(cfg.getEnvVar("TOKEN")).toBe("from-data-home"); - }); - - it("strips trailing inline # comments on unquoted values", async () => { - writeEnv( - [ - "AGENTMEMORY_AUTO_COMPRESS=true # opt in to LLM compression", - "CONSOLIDATION_ENABLED=true # daily summarization", - "GRAPH_EXTRACTION_ENABLED=true # entity graph", - ].join("\n"), - ); - const cfg = await freshConfig(); - expect(cfg.isAutoCompressEnabled()).toBe(true); - expect(cfg.isConsolidationEnabled()).toBe(true); - expect(cfg.isGraphExtractionEnabled()).toBe(true); - }); - - it("preserves # inside double-quoted values", async () => { - writeEnv('TOKEN="abc#def"'); - const cfg = await freshConfig(); - expect(cfg.getEnvVar("TOKEN")).toBe("abc#def"); - }); - - it("preserves # inside single-quoted values", async () => { - writeEnv("TOKEN='abc#def'"); - const cfg = await freshConfig(); - expect(cfg.getEnvVar("TOKEN")).toBe("abc#def"); - }); - - it("treats hash without leading space as part of value", async () => { - writeEnv("HASHVAL=abc#def"); - const cfg = await freshConfig(); - expect(cfg.getEnvVar("HASHVAL")).toBe("abc#def"); - }); - - it("strips inline comment after a quoted value and unwraps quotes", async () => { - writeEnv('TOKEN="abc" # trailing comment'); - const cfg = await freshConfig(); - expect(cfg.getEnvVar("TOKEN")).toBe("abc"); - }); - - it("strips inline comment after a single-quoted value and unwraps quotes", async () => { - writeEnv("TOKEN='abc' # trailing comment"); - const cfg = await freshConfig(); - expect(cfg.getEnvVar("TOKEN")).toBe("abc"); - }); - - it("reads AGENTMEMORY_DROP_STALE_INDEX from the env file", async () => { - writeEnv("AGENTMEMORY_DROP_STALE_INDEX=true"); - const cfg = await freshConfig(); - expect(cfg.isDropStaleIndexEnabled()).toBe(true); - }); -}); diff --git a/test/eval.test.ts b/test/eval.test.ts deleted file mode 100644 index f1070e1a..00000000 --- a/test/eval.test.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { - ObserveInputSchema, - CompressOutputSchema, - SummaryOutputSchema, - SearchInputSchema, - ContextInputSchema, - RememberInputSchema, -} from "../src/eval/schemas.js"; -import { validateInput, validateOutput } from "../src/eval/validator.js"; -import { - scoreCompression, - scoreSummary, - scoreContextRelevance, -} from "../src/eval/quality.js"; - -describe("Zod Schemas", () => { - describe("ObserveInputSchema", () => { - it("accepts valid input", () => { - const result = ObserveInputSchema.safeParse({ - hookType: "post_tool_use", - sessionId: "ses_abc", - project: "my-project", - cwd: "/home/user", - timestamp: "2026-01-01T00:00:00Z", - data: { tool_name: "Read" }, - }); - expect(result.success).toBe(true); - }); - - it("rejects missing sessionId", () => { - const result = ObserveInputSchema.safeParse({ - hookType: "post_tool_use", - project: "my-project", - cwd: "/home/user", - timestamp: "2026-01-01T00:00:00Z", - data: {}, - }); - expect(result.success).toBe(false); - }); - - it("rejects invalid hookType", () => { - const result = ObserveInputSchema.safeParse({ - hookType: "invalid_hook", - sessionId: "ses_abc", - project: "my-project", - cwd: "/home/user", - timestamp: "2026-01-01T00:00:00Z", - data: {}, - }); - expect(result.success).toBe(false); - }); - }); - - describe("CompressOutputSchema", () => { - it("accepts valid output", () => { - const result = CompressOutputSchema.safeParse({ - type: "file_edit", - title: "Edit auth module", - facts: ["Added JWT validation"], - narrative: "Modified the auth middleware to validate tokens", - concepts: ["auth"], - files: ["src/auth.ts"], - importance: 7, - }); - expect(result.success).toBe(true); - }); - - it("rejects empty facts array", () => { - const result = CompressOutputSchema.safeParse({ - type: "file_edit", - title: "Edit auth module", - facts: [], - narrative: "Modified the auth middleware to validate tokens", - concepts: [], - files: [], - importance: 5, - }); - expect(result.success).toBe(false); - }); - - it("rejects title over 120 chars", () => { - const result = CompressOutputSchema.safeParse({ - type: "file_edit", - title: "x".repeat(121), - facts: ["fact"], - narrative: "A narrative that is long enough", - concepts: [], - files: [], - importance: 5, - }); - expect(result.success).toBe(false); - }); - - it("rejects importance outside 1-10", () => { - const result = CompressOutputSchema.safeParse({ - type: "file_edit", - title: "Test", - facts: ["fact"], - narrative: "A valid narrative here", - concepts: [], - files: [], - importance: 11, - }); - expect(result.success).toBe(false); - }); - - it("rejects narrative under 10 chars", () => { - const result = CompressOutputSchema.safeParse({ - type: "file_edit", - title: "Test", - facts: ["fact"], - narrative: "short", - concepts: [], - files: [], - importance: 5, - }); - expect(result.success).toBe(false); - }); - }); - - describe("SummaryOutputSchema", () => { - it("accepts valid summary", () => { - const result = SummaryOutputSchema.safeParse({ - title: "Session Summary", - narrative: "This session focused on implementing authentication features and fixing bugs", - keyDecisions: ["Use JWT"], - filesModified: ["auth.ts"], - concepts: ["auth"], - }); - expect(result.success).toBe(true); - }); - - it("rejects short narrative", () => { - const result = SummaryOutputSchema.safeParse({ - title: "Summary", - narrative: "Too short", - keyDecisions: [], - filesModified: [], - concepts: [], - }); - expect(result.success).toBe(false); - }); - }); - - describe("SearchInputSchema", () => { - it("accepts valid search", () => { - expect(SearchInputSchema.safeParse({ query: "auth" }).success).toBe(true); - }); - - it("accepts search with limit", () => { - expect( - SearchInputSchema.safeParse({ query: "auth", limit: 10 }).success, - ).toBe(true); - }); - - it("rejects empty query", () => { - expect(SearchInputSchema.safeParse({ query: "" }).success).toBe(false); - }); - }); - - describe("ContextInputSchema", () => { - it("accepts valid input", () => { - expect( - ContextInputSchema.safeParse({ - sessionId: "ses_1", - project: "proj", - }).success, - ).toBe(true); - }); - }); - - describe("RememberInputSchema", () => { - it("accepts valid input", () => { - expect( - RememberInputSchema.safeParse({ - content: "Always use TypeScript", - type: "preference", - }).success, - ).toBe(true); - }); - - it("rejects empty content", () => { - expect( - RememberInputSchema.safeParse({ content: "" }).success, - ).toBe(false); - }); - }); -}); - -describe("Validator", () => { - it("returns valid with correct data", () => { - const result = validateInput(SearchInputSchema, { query: "test" }, "search"); - expect(result.valid).toBe(true); - if (result.valid) { - expect(result.data.query).toBe("test"); - } - }); - - it("returns invalid with error details", () => { - const result = validateInput(SearchInputSchema, { query: "" }, "search"); - expect(result.valid).toBe(false); - if (!result.valid) { - expect(result.result.functionId).toBe("search"); - expect(result.result.errors.length).toBeGreaterThan(0); - } - }); - - it("validateOutput works same as validateInput", () => { - const result = validateOutput( - CompressOutputSchema, - { - type: "file_edit", - title: "Test", - facts: ["a"], - narrative: "A long enough narrative", - concepts: [], - files: [], - importance: 5, - }, - "compress", - ); - expect(result.valid).toBe(true); - }); -}); - -describe("Quality Scoring", () => { - describe("scoreCompression", () => { - it("returns 0 for empty object", () => { - expect(scoreCompression({})).toBe(0); - }); - - it("returns 100 for perfect observation", () => { - const score = scoreCompression({ - type: "file_edit", - title: "A good title", - facts: ["fact 1", "fact 2", "fact 3"], - narrative: "A narrative that is definitely more than fifty characters long and provides good context", - concepts: ["auth", "jwt"], - importance: 7, - }); - expect(score).toBe(100); - }); - - it("scores partial observations between 0 and 100", () => { - const score = scoreCompression({ - title: "Test", - facts: ["one"], - narrative: "Short but valid narrative", - }); - expect(score).toBeGreaterThan(0); - expect(score).toBeLessThan(100); - }); - }); - - describe("scoreSummary", () => { - it("returns 0 for empty object", () => { - expect(scoreSummary({})).toBe(0); - }); - - it("returns high score for complete summary", () => { - const score = scoreSummary({ - title: "Session Summary Title", - narrative: - "This is a detailed narrative about what happened during the session with enough content to be meaningful and complete for review purposes", - keyDecisions: ["Used JWT for auth", "Chose PostgreSQL"], - filesModified: ["src/auth.ts", "src/db.ts"], - concepts: ["authentication", "database"], - }); - expect(score).toBeGreaterThanOrEqual(90); - }); - }); - - describe("scoreContextRelevance", () => { - it("returns 0 for empty context", () => { - expect(scoreContextRelevance("", "proj")).toBe(0); - }); - - it("scores higher when project is mentioned", () => { - const withProject = scoreContextRelevance( - "This is for my-project with details", - "my-project", - ); - const without = scoreContextRelevance( - "Some generic context details", - "my-project", - ); - expect(withProject).toBeGreaterThan(without); - }); - - it("scores higher with more XML sections", () => { - const multi = scoreContextRelevance( - "ABCD", - "test", - ); - const single = scoreContextRelevance("A", "test"); - expect(multi).toBeGreaterThan(single); - }); - }); -}); diff --git a/test/evict.test.ts b/test/evict.test.ts deleted file mode 100644 index d1aedac2..00000000 --- a/test/evict.test.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; -import type { - CompressedObservation, - RawObservation, - Session, -} from "../src/types.js"; -import { registerEvictFunction } from "../src/functions/evict.js"; -import { KV } from "../src/state/schema.js"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -type Store = Map>; -type Handler = (payload: unknown) => unknown | Promise; - -function daysAgo(days: number): string { - return new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); -} - -function makeSession(id: string): Session { - return { - id, - project: "agentmemory", - cwd: "/repo/agentmemory", - startedAt: daysAgo(31), - status: "active", - observationCount: 1, - }; -} - -function makeObservation(sessionId: string): CompressedObservation { - return { - id: "obs_1", - sessionId, - timestamp: daysAgo(31), - type: "decision", - title: "Chose sqlite storage", - facts: ["Use sqlite for local state"], - narrative: "The session chose sqlite for local state.", - concepts: ["sqlite"], - files: ["src/state/kv.ts"], - importance: 8, - }; -} - -function makeRawObservation(sessionId: string): RawObservation { - return { - id: "raw_1", - sessionId, - timestamp: daysAgo(31), - hookType: "post_tool_use", - toolName: "Edit", - raw: { file_path: "src/state/kv.ts" }, - }; -} - -function mockKV(store: Store, listFailures: Set = new Set()) { - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - if (listFailures.has(scope)) { - throw new Error(`list failed for ${scope}`); - } - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const handlers = new Map(); - const calls: Array<{ function_id: string; payload: unknown }> = []; - return { - calls, - sdk: { - registerFunction: (functionId: string, handler: Handler) => { - handlers.set(functionId, handler); - }, - trigger: async (input: { function_id: string; payload: unknown }) => { - calls.push(input); - const handler = handlers.get(input.function_id); - if (!handler) throw new Error(`missing handler: ${input.function_id}`); - return handler(input.payload); - }, - }, - }; -} - -function storeForObservations( - sessionId: string, - observations: Array, -): Store { - const session = makeSession(sessionId); - return new Map([ - [KV.sessions, new Map([[session.id, session]])], - [KV.summaries, new Map()], - [ - KV.observations(session.id), - new Map(observations.map((observation) => [observation.id, observation])), - ], - [KV.config, new Map()], - [KV.audit, new Map()], - ]); -} - -function storeForObservedSession(sessionId: string): Store { - return storeForObservations(sessionId, [makeObservation(sessionId)]); -} - -describe("mem::evict stale sessions", () => { - it("runs session recovery before deleting a stale observed session", async () => { - const sessionId = "ses_stale"; - const store = storeForObservedSession(sessionId); - const kv = mockKV(store); - const { sdk, calls } = mockSdk(); - - registerEvictFunction(sdk as never, kv as never); - sdk.registerFunction("event::session::stopped", async (payload) => { - expect(payload).toEqual({ sessionId }); - expect(await kv.get(KV.sessions, sessionId)).toMatchObject({ - id: sessionId, - }); - return { success: true }; - }); - sdk.registerFunction("mem::consolidate-pipeline", () => ({ - success: true, - })); - - const result = (await sdk.trigger({ - function_id: "mem::evict", - payload: {}, - })) as { staleSessions: number }; - - expect(result.staleSessions).toBe(1); - expect(await kv.get(KV.sessions, sessionId)).toBeNull(); - const audits = await kv.list<{ - details: { reason: string }; - }>(KV.audit); - expect(audits[0].details.reason).toBe( - "stale_session_recovered_then_evicted", - ); - expect(calls.map((call) => call.function_id)).toContain( - "event::session::stopped", - ); - expect(calls.map((call) => call.function_id)).toContain( - "mem::consolidate-pipeline", - ); - }); - - it("keeps a stale observed session when recovery fails", async () => { - const sessionId = "ses_unrecovered"; - const store = storeForObservedSession(sessionId); - const kv = mockKV(store); - const { sdk, calls } = mockSdk(); - - registerEvictFunction(sdk as never, kv as never); - sdk.registerFunction("event::session::stopped", () => ({ - success: false, - error: "no_provider", - })); - - const result = (await sdk.trigger({ - function_id: "mem::evict", - payload: {}, - })) as { staleSessions: number }; - - expect(result.staleSessions).toBe(0); - expect(await kv.get(KV.sessions, sessionId)).toMatchObject({ - id: sessionId, - }); - expect(calls.map((call) => call.function_id)).toContain( - "event::session::stopped", - ); - expect(calls.map((call) => call.function_id)).not.toContain( - "mem::consolidate-pipeline", - ); - }); - - it("keeps a stale session when observation scanning fails", async () => { - const sessionId = "ses_scan_failed"; - const store = storeForObservedSession(sessionId); - const kv = mockKV(store, new Set([KV.observations(sessionId)])); - const { sdk, calls } = mockSdk(); - - registerEvictFunction(sdk as never, kv as never); - sdk.registerFunction("event::session::stopped", () => ({ - success: true, - })); - - const result = (await sdk.trigger({ - function_id: "mem::evict", - payload: {}, - })) as { staleSessions: number }; - - expect(result.staleSessions).toBe(0); - expect(await kv.get(KV.sessions, sessionId)).toMatchObject({ - id: sessionId, - }); - expect(calls.map((call) => call.function_id)).not.toContain( - "event::session::stopped", - ); - }); - - it("keeps a stale session that only has raw observations", async () => { - const sessionId = "ses_raw_only"; - const store = storeForObservations(sessionId, [ - makeRawObservation(sessionId), - ]); - const kv = mockKV(store); - const { sdk, calls } = mockSdk(); - - registerEvictFunction(sdk as never, kv as never); - sdk.registerFunction("event::session::stopped", () => ({ - success: true, - })); - - const result = (await sdk.trigger({ - function_id: "mem::evict", - payload: {}, - })) as { staleSessions: number }; - - expect(result.staleSessions).toBe(0); - expect(await kv.get(KV.sessions, sessionId)).toMatchObject({ - id: sessionId, - }); - expect(calls.map((call) => call.function_id)).not.toContain( - "event::session::stopped", - ); - }); -}); diff --git a/test/export-import.test.ts b/test/export-import.test.ts deleted file mode 100644 index 0b4c0e30..00000000 --- a/test/export-import.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerExportImportFunction } from "../src/functions/export-import.js"; -import type { - Session, - CompressedObservation, - Memory, - SessionSummary, - ExportData, -} from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -const testSession: Session = { - id: "ses_1", - project: "my-project", - cwd: "/tmp", - startedAt: "2026-02-01T00:00:00Z", - status: "completed", - observationCount: 1, -}; - -const testObs: CompressedObservation = { - id: "obs_1", - sessionId: "ses_1", - timestamp: "2026-02-01T10:00:00Z", - type: "file_edit", - title: "Edit auth", - facts: ["Added check"], - narrative: "Auth changes", - concepts: ["auth"], - files: ["src/auth.ts"], - importance: 7, -}; - -const testMemory: Memory = { - id: "mem_1", - createdAt: "2026-02-01T00:00:00Z", - updatedAt: "2026-02-01T00:00:00Z", - type: "pattern", - title: "Auth pattern", - content: "Always validate tokens", - concepts: ["auth"], - files: [], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, -}; - -const testSummary: SessionSummary = { - sessionId: "ses_1", - project: "my-project", - createdAt: "2026-02-01T00:00:00Z", - title: "Auth work", - narrative: "Worked on auth", - keyDecisions: ["Use JWT"], - filesModified: ["src/auth.ts"], - concepts: ["auth"], - observationCount: 1, -}; - -describe("Export/Import Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(async () => { - sdk = mockSdk(); - kv = mockKV(); - registerExportImportFunction(sdk as never, kv as never); - - await kv.set("mem:sessions", "ses_1", testSession); - await kv.set("mem:obs:ses_1", "obs_1", testObs); - await kv.set("mem:memories", "mem_1", testMemory); - await kv.set("mem:summaries", "ses_1", testSummary); - }); - - it("export produces valid ExportData structure", async () => { - const result = (await sdk.trigger("mem::export", {})) as ExportData; - - expect(result.version).toBe("0.9.24"); - expect(result.exportedAt).toBeDefined(); - expect(result.sessions.length).toBe(1); - expect(result.sessions[0].id).toBe("ses_1"); - expect(result.observations["ses_1"].length).toBe(1); - expect(result.memories.length).toBe(1); - expect(result.summaries.length).toBe(1); - }); - - it("import with merge strategy adds data", async () => { - const exportData: ExportData = { - version: "0.3.0", - exportedAt: new Date().toISOString(), - sessions: [{ ...testSession, id: "ses_2", observationCount: 0 }], - observations: {}, - memories: [{ ...testMemory, id: "mem_2", title: "New pattern" }], - summaries: [], - }; - - const result = (await sdk.trigger("mem::import", { - exportData, - strategy: "merge", - })) as { success: boolean; sessions: number; memories: number }; - - expect(result.success).toBe(true); - expect(result.sessions).toBe(1); - expect(result.memories).toBe(1); - - const allSessions = await kv.list("mem:sessions"); - expect(allSessions.length).toBe(2); - }); - - it("import with skip strategy does not overwrite existing", async () => { - const exportData: ExportData = { - version: "0.3.0", - exportedAt: new Date().toISOString(), - sessions: [testSession], - observations: { ses_1: [testObs] }, - memories: [testMemory], - summaries: [testSummary], - }; - - const result = (await sdk.trigger("mem::import", { - exportData, - strategy: "skip", - })) as { success: boolean; skipped: number; sessions: number }; - - expect(result.success).toBe(true); - expect(result.skipped).toBeGreaterThan(0); - expect(result.sessions).toBe(0); - }); - - it("import with replace strategy clears existing data first", async () => { - const newSession: Session = { - id: "ses_new", - project: "new-project", - cwd: "/tmp/new", - startedAt: "2026-03-01T00:00:00Z", - status: "active", - observationCount: 0, - }; - const exportData: ExportData = { - version: "0.3.0", - exportedAt: new Date().toISOString(), - sessions: [newSession], - observations: {}, - memories: [], - summaries: [], - }; - - const result = (await sdk.trigger("mem::import", { - exportData, - strategy: "replace", - })) as { success: boolean; sessions: number }; - - expect(result.success).toBe(true); - expect(result.sessions).toBe(1); - - const oldSession = await kv.get("mem:sessions", "ses_1"); - expect(oldSession).toBeNull(); - }); - - it("export then import round-trip preserves data", async () => { - const exported = (await sdk.trigger("mem::export", {})) as ExportData; - - const freshKv = mockKV(); - const freshSdk = mockSdk(); - registerExportImportFunction(freshSdk as never, freshKv as never); - - const importResult = (await freshSdk.trigger("mem::import", { - exportData: exported, - strategy: "merge", - })) as { - success: boolean; - sessions: number; - observations: number; - memories: number; - }; - - expect(importResult.success).toBe(true); - expect(importResult.sessions).toBe(1); - expect(importResult.observations).toBe(1); - expect(importResult.memories).toBe(1); - - const reExported = (await freshSdk.trigger( - "mem::export", - {}, - )) as ExportData; - expect(reExported.sessions.length).toBe(exported.sessions.length); - expect(reExported.memories.length).toBe(exported.memories.length); - }); - - it("import rejects unsupported version", async () => { - const exportData = { - version: "1.0.0", - exportedAt: new Date().toISOString(), - sessions: [], - observations: {}, - memories: [], - summaries: [], - } as unknown as ExportData; - - const result = (await sdk.trigger("mem::import", { - exportData, - strategy: "merge", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("Unsupported export version"); - }); -}); diff --git a/test/facets.test.ts b/test/facets.test.ts deleted file mode 100644 index f37d756e..00000000 --- a/test/facets.test.ts +++ /dev/null @@ -1,449 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerFacetsFunction } from "../src/functions/facets.js"; -import type { Facet } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("Facets Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerFacetsFunction(sdk as never, kv as never); - }); - - describe("mem::facet-tag", () => { - it("tags a target with a facet", async () => { - const result = (await sdk.trigger("mem::facet-tag", { - targetId: "act_123", - targetType: "action", - dimension: "priority", - value: "high", - })) as { success: boolean; facet: Facet }; - - expect(result.success).toBe(true); - expect(result.facet.id).toMatch(/^fct_/); - expect(result.facet.targetId).toBe("act_123"); - expect(result.facet.targetType).toBe("action"); - expect(result.facet.dimension).toBe("priority"); - expect(result.facet.value).toBe("high"); - expect(result.facet.createdAt).toBeDefined(); - }); - - it("returns error when dimension is empty", async () => { - const result = (await sdk.trigger("mem::facet-tag", { - targetId: "act_123", - targetType: "action", - dimension: " ", - value: "high", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("dimension is required"); - }); - - it("returns error when value is empty", async () => { - const result = (await sdk.trigger("mem::facet-tag", { - targetId: "act_123", - targetType: "action", - dimension: "priority", - value: "", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("value is required"); - }); - - it("returns error for invalid targetType", async () => { - const result = (await sdk.trigger("mem::facet-tag", { - targetId: "act_123", - targetType: "invalid", - dimension: "priority", - value: "high", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("targetType must be one of"); - }); - - it("skips duplicate tag", async () => { - await sdk.trigger("mem::facet-tag", { - targetId: "act_123", - targetType: "action", - dimension: "priority", - value: "high", - }); - - const result = (await sdk.trigger("mem::facet-tag", { - targetId: "act_123", - targetType: "action", - dimension: "priority", - value: "high", - })) as { success: boolean; facet: Facet; skipped: boolean }; - - expect(result.success).toBe(true); - expect(result.skipped).toBe(true); - }); - }); - - describe("mem::facet-untag", () => { - it("removes a specific value from a dimension", async () => { - await sdk.trigger("mem::facet-tag", { - targetId: "act_123", - targetType: "action", - dimension: "priority", - value: "high", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "act_123", - targetType: "action", - dimension: "priority", - value: "urgent", - }); - - const result = (await sdk.trigger("mem::facet-untag", { - targetId: "act_123", - dimension: "priority", - value: "high", - })) as { success: boolean; removed: number }; - - expect(result.success).toBe(true); - expect(result.removed).toBe(1); - - const remaining = (await sdk.trigger("mem::facet-get", { - targetId: "act_123", - })) as { success: boolean; dimensions: Array<{ dimension: string; values: string[] }> }; - - expect(remaining.dimensions[0].values).toEqual(["urgent"]); - }); - - it("removes all values in a dimension when value is omitted", async () => { - await sdk.trigger("mem::facet-tag", { - targetId: "act_123", - targetType: "action", - dimension: "priority", - value: "high", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "act_123", - targetType: "action", - dimension: "priority", - value: "urgent", - }); - - const result = (await sdk.trigger("mem::facet-untag", { - targetId: "act_123", - dimension: "priority", - })) as { success: boolean; removed: number }; - - expect(result.success).toBe(true); - expect(result.removed).toBe(2); - - const remaining = (await sdk.trigger("mem::facet-get", { - targetId: "act_123", - })) as { success: boolean; dimensions: Array<{ dimension: string; values: string[] }> }; - - expect(remaining.dimensions).toEqual([]); - }); - }); - - describe("mem::facet-query", () => { - beforeEach(async () => { - await sdk.trigger("mem::facet-tag", { - targetId: "act_1", - targetType: "action", - dimension: "priority", - value: "high", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "act_1", - targetType: "action", - dimension: "status", - value: "active", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "act_2", - targetType: "action", - dimension: "priority", - value: "low", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "act_2", - targetType: "action", - dimension: "status", - value: "active", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "mem_1", - targetType: "memory", - dimension: "priority", - value: "high", - }); - }); - - it("queries with matchAll (AND logic)", async () => { - const result = (await sdk.trigger("mem::facet-query", { - matchAll: ["priority:high", "status:active"], - })) as { success: boolean; results: Array<{ targetId: string }> }; - - expect(result.success).toBe(true); - expect(result.results.length).toBe(1); - expect(result.results[0].targetId).toBe("act_1"); - }); - - it("queries with matchAny (OR logic)", async () => { - const result = (await sdk.trigger("mem::facet-query", { - matchAny: ["priority:high", "priority:low"], - })) as { success: boolean; results: Array<{ targetId: string }> }; - - expect(result.success).toBe(true); - expect(result.results.length).toBe(3); - }); - - it("queries with both matchAll and matchAny", async () => { - const result = (await sdk.trigger("mem::facet-query", { - matchAll: ["status:active"], - matchAny: ["priority:high"], - })) as { success: boolean; results: Array<{ targetId: string; matchedFacets: string[] }> }; - - expect(result.success).toBe(true); - expect(result.results.length).toBe(1); - expect(result.results[0].targetId).toBe("act_1"); - expect(result.results[0].matchedFacets).toContain("status:active"); - expect(result.results[0].matchedFacets).toContain("priority:high"); - }); - - it("returns error when neither matchAll nor matchAny provided", async () => { - const result = (await sdk.trigger("mem::facet-query", {})) as { - success: boolean; - error: string; - }; - - expect(result.success).toBe(false); - expect(result.error).toContain("at least one of matchAll or matchAny is required"); - }); - - it("filters by targetType", async () => { - const result = (await sdk.trigger("mem::facet-query", { - matchAny: ["priority:high"], - targetType: "memory", - })) as { success: boolean; results: Array<{ targetId: string; targetType: string }> }; - - expect(result.success).toBe(true); - expect(result.results.length).toBe(1); - expect(result.results[0].targetId).toBe("mem_1"); - expect(result.results[0].targetType).toBe("memory"); - }); - - it("respects limit", async () => { - const result = (await sdk.trigger("mem::facet-query", { - matchAny: ["priority:high", "priority:low"], - limit: 1, - })) as { success: boolean; results: Array<{ targetId: string }> }; - - expect(result.success).toBe(true); - expect(result.results.length).toBe(1); - }); - }); - - describe("mem::facet-get", () => { - it("returns facets grouped by dimension", async () => { - await sdk.trigger("mem::facet-tag", { - targetId: "act_1", - targetType: "action", - dimension: "priority", - value: "high", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "act_1", - targetType: "action", - dimension: "priority", - value: "urgent", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "act_1", - targetType: "action", - dimension: "status", - value: "active", - }); - - const result = (await sdk.trigger("mem::facet-get", { - targetId: "act_1", - })) as { success: boolean; dimensions: Array<{ dimension: string; values: string[] }> }; - - expect(result.success).toBe(true); - expect(result.dimensions.length).toBe(2); - - const priorityDim = result.dimensions.find((d) => d.dimension === "priority"); - expect(priorityDim).toBeDefined(); - expect(priorityDim!.values).toEqual(["high", "urgent"]); - - const statusDim = result.dimensions.find((d) => d.dimension === "status"); - expect(statusDim).toBeDefined(); - expect(statusDim!.values).toEqual(["active"]); - }); - - it("returns empty dimensions for target with no facets", async () => { - const result = (await sdk.trigger("mem::facet-get", { - targetId: "nonexistent", - })) as { success: boolean; dimensions: Array<{ dimension: string; values: string[] }> }; - - expect(result.success).toBe(true); - expect(result.dimensions).toEqual([]); - }); - }); - - describe("mem::facet-stats", () => { - beforeEach(async () => { - await sdk.trigger("mem::facet-tag", { - targetId: "act_1", - targetType: "action", - dimension: "priority", - value: "high", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "act_2", - targetType: "action", - dimension: "priority", - value: "high", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "act_3", - targetType: "action", - dimension: "priority", - value: "low", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "mem_1", - targetType: "memory", - dimension: "category", - value: "bugfix", - }); - }); - - it("returns dimensions with value counts", async () => { - const result = (await sdk.trigger("mem::facet-stats", {})) as { - success: boolean; - dimensions: Array<{ - dimension: string; - values: Array<{ value: string; count: number }>; - }>; - totalFacets: number; - }; - - expect(result.success).toBe(true); - expect(result.totalFacets).toBe(4); - expect(result.dimensions.length).toBe(2); - - const priorityDim = result.dimensions.find((d) => d.dimension === "priority"); - expect(priorityDim).toBeDefined(); - const highVal = priorityDim!.values.find((v) => v.value === "high"); - expect(highVal!.count).toBe(2); - const lowVal = priorityDim!.values.find((v) => v.value === "low"); - expect(lowVal!.count).toBe(1); - }); - - it("filters by targetType", async () => { - const result = (await sdk.trigger("mem::facet-stats", { - targetType: "memory", - })) as { - success: boolean; - dimensions: Array<{ - dimension: string; - values: Array<{ value: string; count: number }>; - }>; - totalFacets: number; - }; - - expect(result.success).toBe(true); - expect(result.totalFacets).toBe(1); - expect(result.dimensions.length).toBe(1); - expect(result.dimensions[0].dimension).toBe("category"); - expect(result.dimensions[0].values[0].value).toBe("bugfix"); - expect(result.dimensions[0].values[0].count).toBe(1); - }); - }); - - describe("mem::facet-dimensions", () => { - it("returns unique dimension names with counts", async () => { - await sdk.trigger("mem::facet-tag", { - targetId: "act_1", - targetType: "action", - dimension: "priority", - value: "high", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "act_2", - targetType: "action", - dimension: "priority", - value: "low", - }); - await sdk.trigger("mem::facet-tag", { - targetId: "act_1", - targetType: "action", - dimension: "status", - value: "active", - }); - - const result = (await sdk.trigger("mem::facet-dimensions", {})) as { - success: boolean; - dimensions: Array<{ dimension: string; count: number }>; - }; - - expect(result.success).toBe(true); - expect(result.dimensions.length).toBe(2); - - const priorityDim = result.dimensions.find((d) => d.dimension === "priority"); - expect(priorityDim).toBeDefined(); - expect(priorityDim!.count).toBe(2); - - const statusDim = result.dimensions.find((d) => d.dimension === "status"); - expect(statusDim).toBeDefined(); - expect(statusDim!.count).toBe(1); - }); - }); -}); diff --git a/test/fallback-chain.test.ts b/test/fallback-chain.test.ts deleted file mode 100644 index 5113b699..00000000 --- a/test/fallback-chain.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { FallbackChainProvider } from "../src/providers/fallback-chain.js"; -import type { MemoryProvider } from "../src/types.js"; - -function makeProvider( - name: string, - impl?: Partial, -): MemoryProvider { - return { - name, - compress: impl?.compress ?? (async () => `compressed by ${name}`), - summarize: impl?.summarize ?? (async () => `summarized by ${name}`), - }; -} - -describe("FallbackChainProvider", () => { - it("returns result from first provider when it succeeds", async () => { - const chain = new FallbackChainProvider([ - makeProvider("primary"), - makeProvider("secondary"), - ]); - const result = await chain.compress("sys", "user"); - expect(result).toBe("compressed by primary"); - }); - - it("falls back to second provider when first fails", async () => { - const failing: MemoryProvider = { - name: "failing", - compress: async () => { - throw new Error("primary down"); - }, - summarize: async () => { - throw new Error("primary down"); - }, - }; - const chain = new FallbackChainProvider([ - failing, - makeProvider("backup"), - ]); - const result = await chain.compress("sys", "user"); - expect(result).toBe("compressed by backup"); - }); - - it("throws the last error when all providers fail", async () => { - const failing1: MemoryProvider = { - name: "fail1", - compress: async () => { - throw new Error("fail1 error"); - }, - summarize: async () => { - throw new Error("fail1 error"); - }, - }; - const failing2: MemoryProvider = { - name: "fail2", - compress: async () => { - throw new Error("fail2 error"); - }, - summarize: async () => { - throw new Error("fail2 error"); - }, - }; - const chain = new FallbackChainProvider([failing1, failing2]); - await expect(chain.compress("sys", "user")).rejects.toThrow("fail2 error"); - }); - - it("formats the name correctly", () => { - const chain = new FallbackChainProvider([ - makeProvider("anthropic"), - makeProvider("gemini"), - makeProvider("openrouter"), - ]); - expect(chain.name).toBe("fallback(anthropic -> gemini -> openrouter)"); - }); - - it("summarize also uses fallback chain", async () => { - const failing: MemoryProvider = { - name: "failing", - compress: async () => { - throw new Error("down"); - }, - summarize: async () => { - throw new Error("down"); - }, - }; - const chain = new FallbackChainProvider([ - failing, - makeProvider("backup"), - ]); - const result = await chain.summarize("sys", "user"); - expect(result).toBe("summarized by backup"); - }); -}); diff --git a/test/fetch-timeout.test.ts b/test/fetch-timeout.test.ts deleted file mode 100644 index d6e1fe9e..00000000 --- a/test/fetch-timeout.test.ts +++ /dev/null @@ -1,346 +0,0 @@ -import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; -import { fetchWithTimeout } from "../src/providers/_fetch.js"; -import { MinimaxProvider } from "../src/providers/minimax.js"; -import { OpenRouterProvider } from "../src/providers/openrouter.js"; -import { OpenAIProvider } from "../src/providers/openai.js"; -import { GeminiEmbeddingProvider } from "../src/providers/embedding/gemini.js"; -import { OpenAIEmbeddingProvider } from "../src/providers/embedding/openai.js"; -import { CohereEmbeddingProvider } from "../src/providers/embedding/cohere.js"; -import { VoyageEmbeddingProvider } from "../src/providers/embedding/voyage.js"; -import { OpenRouterEmbeddingProvider } from "../src/providers/embedding/openrouter.js"; - -// A fetch mock that never resolves — simulates a hung upstream. -function hangingFetch(_url: string, _init?: RequestInit): Promise { - // honour AbortSignal so the timeout actually cancels us - const init = _init ?? {}; - return new Promise((_resolve, reject) => { - if (init.signal) { - if (init.signal.aborted) { - reject(new DOMException("AbortError", "AbortError")); - return; - } - init.signal.addEventListener("abort", () => { - reject(new DOMException("AbortError", "AbortError")); - }); - } - }); -} - -// ───────────────────────────────────────────────────────────── -// fetchWithTimeout unit tests -// ───────────────────────────────────────────────────────────── -describe("fetchWithTimeout", () => { - beforeEach(() => { - vi.spyOn(globalThis, "fetch").mockImplementation(hangingFetch as typeof fetch); - }); - - afterEach(() => { - vi.restoreAllMocks(); - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - }); - - it("resolves normally when fetch completes within the timeout", async () => { - vi.restoreAllMocks(); - vi.spyOn(globalThis, "fetch").mockResolvedValue( - new Response(JSON.stringify({ ok: true }), { status: 200 }), - ); - const res = await fetchWithTimeout("https://example.com", {}, 1000); - expect(res.status).toBe(200); - }); - - it("aborts with an AbortError when fetch hangs beyond the configured timeout", async () => { - await expect( - fetchWithTimeout("https://example.com", {}, 50), - ).rejects.toThrow(); - }); - - it("reads AGENTMEMORY_LLM_TIMEOUT_MS as the default timeout when no explicit ms is given", async () => { - process.env["AGENTMEMORY_LLM_TIMEOUT_MS"] = "50"; - // no explicit third arg — must pick up the env var - await expect( - fetchWithTimeout("https://example.com", {}), - ).rejects.toThrow(); - }); - - it("falls back to 60 000 ms when AGENTMEMORY_LLM_TIMEOUT_MS is not set (type check only)", () => { - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - vi.restoreAllMocks(); - vi.spyOn(globalThis, "fetch").mockResolvedValue( - new Response(null, { status: 204 }), - ); - const p = fetchWithTimeout("https://example.com", {}); - expect(p).toBeInstanceOf(Promise); - return p; - }); -}); - -// ───────────────────────────────────────────────────────────── -// Provider hang regression tests -// Each provider must call fetchWithTimeout, which honours the -// AbortSignal when the explicit timeoutMs is tiny (50 ms). -// ───────────────────────────────────────────────────────────── - -describe("Provider hang regression — MinimaxProvider", () => { - beforeEach(() => { - vi.spyOn(globalThis, "fetch").mockImplementation(hangingFetch as typeof fetch); - process.env["AGENTMEMORY_LLM_TIMEOUT_MS"] = "50"; - }); - afterEach(() => { - vi.restoreAllMocks(); - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - }); - - it("compress() aborts after timeout when upstream hangs", async () => { - const provider = new MinimaxProvider("test-key", "MiniMax-M2.7", 800); - await expect(provider.compress("system", "user")).rejects.toThrow(); - }); -}); - -describe("Provider hang regression — OpenRouterProvider (covers Gemini LLM path)", () => { - beforeEach(() => { - vi.spyOn(globalThis, "fetch").mockImplementation(hangingFetch as typeof fetch); - process.env["AGENTMEMORY_LLM_TIMEOUT_MS"] = "50"; - }); - afterEach(() => { - vi.restoreAllMocks(); - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - }); - - it("compress() aborts after timeout when upstream hangs", async () => { - const provider = new OpenRouterProvider( - "test-key", - "gemini-2.5-flash", - 1024, - "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", - ); - await expect(provider.compress("system", "user")).rejects.toThrow(); - }); -}); - -describe("Provider hang regression — GeminiEmbeddingProvider", () => { - beforeEach(() => { - vi.spyOn(globalThis, "fetch").mockImplementation(hangingFetch as typeof fetch); - process.env["AGENTMEMORY_LLM_TIMEOUT_MS"] = "50"; - }); - afterEach(() => { - vi.restoreAllMocks(); - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - }); - - it("embedBatch() aborts after timeout when upstream hangs", async () => { - const provider = new GeminiEmbeddingProvider("test-key"); - await expect(provider.embedBatch(["hello"])).rejects.toThrow(); - }); -}); - -describe("Provider hang regression — OpenAIEmbeddingProvider", () => { - beforeEach(() => { - vi.spyOn(globalThis, "fetch").mockImplementation(hangingFetch as typeof fetch); - process.env["AGENTMEMORY_LLM_TIMEOUT_MS"] = "50"; - }); - afterEach(() => { - vi.restoreAllMocks(); - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - }); - - it("embedBatch() aborts after timeout when upstream hangs", async () => { - const provider = new OpenAIEmbeddingProvider("test-key"); - await expect(provider.embedBatch(["hello"])).rejects.toThrow(); - }); -}); - -describe("Provider hang regression — CohereEmbeddingProvider", () => { - beforeEach(() => { - vi.spyOn(globalThis, "fetch").mockImplementation(hangingFetch as typeof fetch); - process.env["AGENTMEMORY_LLM_TIMEOUT_MS"] = "50"; - }); - afterEach(() => { - vi.restoreAllMocks(); - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - }); - - it("embedBatch() aborts after timeout when upstream hangs", async () => { - const provider = new CohereEmbeddingProvider("test-key"); - await expect(provider.embedBatch(["hello"])).rejects.toThrow(); - }); -}); - -describe("Provider hang regression — VoyageEmbeddingProvider", () => { - beforeEach(() => { - vi.spyOn(globalThis, "fetch").mockImplementation(hangingFetch as typeof fetch); - process.env["AGENTMEMORY_LLM_TIMEOUT_MS"] = "50"; - }); - afterEach(() => { - vi.restoreAllMocks(); - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - }); - - it("embedBatch() aborts after timeout when upstream hangs", async () => { - const provider = new VoyageEmbeddingProvider("test-key"); - await expect(provider.embedBatch(["hello"])).rejects.toThrow(); - }); -}); - -describe("Provider hang regression — OpenRouterEmbeddingProvider", () => { - beforeEach(() => { - vi.spyOn(globalThis, "fetch").mockImplementation(hangingFetch as typeof fetch); - process.env["AGENTMEMORY_LLM_TIMEOUT_MS"] = "50"; - }); - afterEach(() => { - vi.restoreAllMocks(); - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - }); - - it("embedBatch() aborts after timeout when upstream hangs", async () => { - const provider = new OpenRouterEmbeddingProvider("test-key"); - await expect(provider.embedBatch(["hello"])).rejects.toThrow(); - }); -}); - -// ───────────────────────────────────────────────────────────── -// #446 — OpenAI LLM provider env-var precedence -// -// v0.9.17 shipped OPENAI_TIMEOUT_MS (OpenAI-scoped). PR #379 then -// shipped AGENTMEMORY_LLM_TIMEOUT_MS (shared). The provider now -// honours both: OPENAI_TIMEOUT_MS wins for back-compat, with -// AGENTMEMORY_LLM_TIMEOUT_MS as the global fall-back. -// ───────────────────────────────────────────────────────────── -describe("OpenAIProvider timeout env precedence (#446)", () => { - beforeEach(() => { - delete process.env["OPENAI_TIMEOUT_MS"]; - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - vi.spyOn(globalThis, "fetch").mockImplementation(hangingFetch as typeof fetch); - }); - afterEach(() => { - vi.restoreAllMocks(); - delete process.env["OPENAI_TIMEOUT_MS"]; - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - }); - - it("OPENAI_TIMEOUT_MS alone aborts the OpenAI LLM call", async () => { - process.env["OPENAI_TIMEOUT_MS"] = "30"; - const provider = new OpenAIProvider("test-key", "gpt-4o-mini", 1024); - await expect(provider.compress("system", "user")).rejects.toThrow( - /timed out after 30ms/, - ); - }); - - it("AGENTMEMORY_LLM_TIMEOUT_MS alone aborts the OpenAI LLM call", async () => { - process.env["AGENTMEMORY_LLM_TIMEOUT_MS"] = "30"; - const provider = new OpenAIProvider("test-key", "gpt-4o-mini", 1024); - await expect(provider.compress("system", "user")).rejects.toThrow( - /timed out after 30ms/, - ); - }); - - it("OPENAI_TIMEOUT_MS wins when both are set (back-compat)", async () => { - process.env["OPENAI_TIMEOUT_MS"] = "30"; - // Set the global to a much larger value — if precedence is wrong, - // we'd time out at 5000ms and the test would hang past the 5s - // vitest default. We assert the message ms to lock the precedence. - process.env["AGENTMEMORY_LLM_TIMEOUT_MS"] = "5000"; - const provider = new OpenAIProvider("test-key", "gpt-4o-mini", 1024); - await expect(provider.compress("system", "user")).rejects.toThrow( - /timed out after 30ms/, - ); - }); - - it("falls back to the 60 000 ms default when neither is set", () => { - // We don't actually wait 60s — the provider stores timeoutMs at - // construction. Construct, then assert the bound via the error - // message after the hang aborts at a tiny pre-set value. - const provider = new OpenAIProvider("test-key", "gpt-4o-mini", 1024); - // Access the resolved timeout via the constructed field name. The - // class keeps `timeoutMs` private; reaching in via the index - // access keeps the test on the public observed behaviour: the ms - // value reported in the timeout error message must be 60000. - const ms = (provider as unknown as { timeoutMs: number }).timeoutMs; - expect(ms).toBe(60_000); - }); - - it("rejects malformed env values like '30ms' or '1_000'", () => { - // parseInt would have silently returned 30 / 1 for these typos — - // strict parse now rejects them and the provider falls back to - // the 60 000 ms default so a malformed env doesn't masquerade as - // an aggressive bound. - // Whitespace-only padding (" 30 ") is legitimate env-var handling — we - // trim before validating. The cases below are real typos parseInt would - // silently swallow. - for (const bad of ["30ms", "1_000", "60s", "30abc", "-30", "0"]) { - process.env["OPENAI_TIMEOUT_MS"] = bad; - const provider = new OpenAIProvider("test-key", "gpt-4o-mini", 1024); - const ms = (provider as unknown as { timeoutMs: number }).timeoutMs; - expect(ms).toBe(60_000); - delete process.env["OPENAI_TIMEOUT_MS"]; - } - }); -}); - -// ───────────────────────────────────────────────────────────── -// #627 — OpenAI provider must read message.reasoning_content -// DeepSeek V4 / Qwen3 / GLM / Kimi return reasoning_content (with -// underscore); only checking `reasoning` left thinking-model output -// dropped on the floor and tripped the compress circuit breaker. -// ───────────────────────────────────────────────────────────── -describe("OpenAIProvider thinking-model fallback (#627)", () => { - beforeEach(() => { - delete process.env["OPENAI_TIMEOUT_MS"]; - delete process.env["AGENTMEMORY_LLM_TIMEOUT_MS"]; - }); - afterEach(() => { - vi.restoreAllMocks(); - }); - - function mockOpenAIResponse(body: object): void { - vi.spyOn(globalThis, "fetch").mockImplementation( - (async () => - new Response(JSON.stringify(body), { - status: 200, - headers: { "content-type": "application/json" }, - })) as typeof fetch, - ); - } - - it("returns reasoning_content when content is empty (DeepSeek V4 / Qwen3 shape)", async () => { - mockOpenAIResponse({ - choices: [ - { - message: { - content: "", - reasoning_content: "thinking-mode output", - }, - }, - ], - }); - const provider = new OpenAIProvider("test-key", "gpt-4o-mini", 1024); - const out = await provider.compress("system", "user"); - expect(out).toBe("thinking-mode output"); - }); - - it("still returns reasoning (no underscore) for older o-series shape", async () => { - mockOpenAIResponse({ - choices: [{ message: { content: "", reasoning: "older shape" } }], - }); - const provider = new OpenAIProvider("test-key", "gpt-4o-mini", 1024); - const out = await provider.compress("system", "user"); - expect(out).toBe("older shape"); - }); - - it("content wins over both reasoning fields when present", async () => { - mockOpenAIResponse({ - choices: [ - { - message: { - content: "real content", - reasoning: "ignore", - reasoning_content: "also ignore", - }, - }, - ], - }); - const provider = new OpenAIProvider("test-key", "gpt-4o-mini", 1024); - const out = await provider.compress("system", "user"); - expect(out).toBe("real content"); - }); -}); - diff --git a/test/fixtures/jsonl/basic.jsonl b/test/fixtures/jsonl/basic.jsonl deleted file mode 100644 index 18f9a2a9..00000000 --- a/test/fixtures/jsonl/basic.jsonl +++ /dev/null @@ -1,2 +0,0 @@ -{"type":"user","uuid":"u1","sessionId":"sess-basic","timestamp":"2026-04-17T10:00:00.000Z","cwd":"/Users/alice/project","message":{"role":"user","content":[{"type":"text","text":"Fix the login bug"}]}} -{"type":"assistant","uuid":"a1","sessionId":"sess-basic","timestamp":"2026-04-17T10:00:05.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Looking into it now."}]}} diff --git a/test/fixtures/jsonl/codex-session.jsonl b/test/fixtures/jsonl/codex-session.jsonl deleted file mode 100644 index 964dee0c..00000000 --- a/test/fixtures/jsonl/codex-session.jsonl +++ /dev/null @@ -1,5 +0,0 @@ -{"type":"session_meta","payload":{"id":"codex-real-session","cwd":"/Users/alice/codex-project","timestamp":"2026-06-11T08:00:00.000Z","originator":"codex"}} -{"type":"event_msg","payload":{"type":"user_message","timestamp":"2026-06-11T08:00:01.000Z","message":"生成摘要按钮点击必定失败,请修复"}} -{"type":"response_item","payload":{"type":"message","role":"assistant","timestamp":"2026-06-11T08:00:02.000Z","content":[{"type":"text","text":"我会先复现并定位摘要链路。"}]}} -{"type":"response_item","payload":{"type":"function_call","name":"exec_command","timestamp":"2026-06-11T08:00:03.000Z","arguments":"{\"cmd\":\"rg summarize src/viewer/index.html\"}"}} -{"type":"response_item","payload":{"type":"function_call_output","timestamp":"2026-06-11T08:00:04.000Z","output":"src/viewer/index.html:6866 summarizeSession"}} diff --git a/test/fixtures/jsonl/errors.jsonl b/test/fixtures/jsonl/errors.jsonl deleted file mode 100644 index 63c98a80..00000000 --- a/test/fixtures/jsonl/errors.jsonl +++ /dev/null @@ -1,4 +0,0 @@ -{"type":"user","uuid":"u1","sessionId":"sess-err","timestamp":"2026-04-17T12:00:00.000Z","cwd":"/tmp/x","message":{"role":"user","content":[{"type":"text","text":"Run tests"}]}} -not-valid-json-line -{"type":"assistant","uuid":"a1","sessionId":"sess-err","timestamp":"2026-04-17T12:00:01.000Z","message":{"role":"assistant","content":[{"type":"tool_use","id":"t1","name":"Bash","input":{"command":"npm test"}}]}} -{"type":"user","uuid":"u2","sessionId":"sess-err","timestamp":"2026-04-17T12:00:02.000Z","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"t1","content":"exit 1","is_error":true}]}} diff --git a/test/fixtures/jsonl/tool-use.jsonl b/test/fixtures/jsonl/tool-use.jsonl deleted file mode 100644 index f96f5bed..00000000 --- a/test/fixtures/jsonl/tool-use.jsonl +++ /dev/null @@ -1,4 +0,0 @@ -{"type":"user","uuid":"u1","sessionId":"sess-tool","timestamp":"2026-04-17T11:00:00.000Z","cwd":"/Users/bob/repo","message":{"role":"user","content":[{"type":"text","text":"List the files"}]}} -{"type":"assistant","uuid":"a1","sessionId":"sess-tool","timestamp":"2026-04-17T11:00:02.000Z","message":{"role":"assistant","content":[{"type":"tool_use","id":"toolu_1","name":"Bash","input":{"command":"ls"}}]}} -{"type":"user","uuid":"u2","sessionId":"sess-tool","timestamp":"2026-04-17T11:00:03.000Z","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_1","content":"README.md\nsrc\n"}]}} -{"type":"assistant","uuid":"a2","sessionId":"sess-tool","timestamp":"2026-04-17T11:00:04.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Two entries."}]}} diff --git a/test/frontier.test.ts b/test/frontier.test.ts deleted file mode 100644 index 2be7f5ce..00000000 --- a/test/frontier.test.ts +++ /dev/null @@ -1,492 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerFrontierFunction } from "../src/functions/frontier.js"; -import { registerActionsFunction } from "../src/functions/actions.js"; -import type { Action, ActionEdge, Checkpoint, Lease } from "../src/types.js"; -import type { FrontierItem } from "../src/functions/frontier.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeAction(overrides: Partial): Action { - const now = new Date().toISOString(); - return { - id: overrides.id || `act_${Math.random().toString(36).slice(2, 10)}`, - title: overrides.title || "Test action", - description: overrides.description || "", - status: overrides.status || "pending", - priority: overrides.priority || 5, - createdAt: overrides.createdAt || now, - updatedAt: overrides.updatedAt || now, - createdBy: overrides.createdBy || "agent-1", - assignedTo: overrides.assignedTo, - project: overrides.project, - tags: overrides.tags || [], - sourceObservationIds: overrides.sourceObservationIds || [], - sourceMemoryIds: overrides.sourceMemoryIds || [], - result: overrides.result, - parentId: overrides.parentId, - metadata: overrides.metadata, - }; -} - -describe("Frontier Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerActionsFunction(sdk as never, kv as never); - registerFrontierFunction(sdk as never, kv as never); - }); - - describe("mem::frontier", () => { - it("returns empty frontier when no actions exist", async () => { - const result = (await sdk.trigger("mem::frontier", {})) as { - success: boolean; - frontier: FrontierItem[]; - totalActions: number; - totalUnblocked: number; - }; - - expect(result.success).toBe(true); - expect(result.frontier).toEqual([]); - expect(result.totalActions).toBe(0); - expect(result.totalUnblocked).toBe(0); - }); - - it("returns pending actions sorted by score", async () => { - const lowPriority = makeAction({ - id: "act_low", - title: "Low priority", - priority: 2, - }); - const highPriority = makeAction({ - id: "act_high", - title: "High priority", - priority: 9, - }); - - await kv.set("mem:actions", lowPriority.id, lowPriority); - await kv.set("mem:actions", highPriority.id, highPriority); - - const result = (await sdk.trigger("mem::frontier", {})) as { - success: boolean; - frontier: FrontierItem[]; - }; - - expect(result.success).toBe(true); - expect(result.frontier.length).toBe(2); - expect(result.frontier[0].action.id).toBe("act_high"); - expect(result.frontier[1].action.id).toBe("act_low"); - expect(result.frontier[0].score).toBeGreaterThan( - result.frontier[1].score, - ); - }); - - it("excludes done and cancelled actions", async () => { - const pending = makeAction({ - id: "act_pending", - title: "Pending", - status: "pending", - }); - const done = makeAction({ - id: "act_done", - title: "Done", - status: "done", - }); - const cancelled = makeAction({ - id: "act_cancelled", - title: "Cancelled", - status: "cancelled", - }); - - await kv.set("mem:actions", pending.id, pending); - await kv.set("mem:actions", done.id, done); - await kv.set("mem:actions", cancelled.id, cancelled); - - const result = (await sdk.trigger("mem::frontier", {})) as { - success: boolean; - frontier: FrontierItem[]; - totalActions: number; - }; - - expect(result.success).toBe(true); - expect(result.frontier.length).toBe(1); - expect(result.frontier[0].action.id).toBe("act_pending"); - expect(result.totalActions).toBe(3); - }); - - it("excludes blocked actions with unsatisfied requires edge", async () => { - const dependency = makeAction({ - id: "act_dep", - title: "Dependency", - status: "pending", - }); - const blocked = makeAction({ - id: "act_blocked", - title: "Blocked", - status: "blocked", - }); - - await kv.set("mem:actions", dependency.id, dependency); - await kv.set("mem:actions", blocked.id, blocked); - - const edge: ActionEdge = { - id: "ae_1", - type: "requires", - sourceActionId: blocked.id, - targetActionId: dependency.id, - createdAt: new Date().toISOString(), - }; - await kv.set("mem:action-edges", edge.id, edge); - - const result = (await sdk.trigger("mem::frontier", {})) as { - success: boolean; - frontier: FrontierItem[]; - }; - - expect(result.success).toBe(true); - const ids = result.frontier.map((f) => f.action.id); - expect(ids).toContain("act_dep"); - expect(ids).not.toContain("act_blocked"); - }); - - it("respects project filter", async () => { - const alphaAction = makeAction({ - id: "act_alpha", - title: "Alpha task", - project: "alpha", - }); - const betaAction = makeAction({ - id: "act_beta", - title: "Beta task", - project: "beta", - }); - - await kv.set("mem:actions", alphaAction.id, alphaAction); - await kv.set("mem:actions", betaAction.id, betaAction); - - const result = (await sdk.trigger("mem::frontier", { - project: "alpha", - })) as { success: boolean; frontier: FrontierItem[] }; - - expect(result.success).toBe(true); - expect(result.frontier.length).toBe(1); - expect(result.frontier[0].action.project).toBe("alpha"); - }); - - it("higher priority scores higher", async () => { - const low = makeAction({ - id: "act_low", - title: "Low", - priority: 1, - createdAt: new Date().toISOString(), - }); - const high = makeAction({ - id: "act_high", - title: "High", - priority: 10, - createdAt: new Date().toISOString(), - }); - - await kv.set("mem:actions", low.id, low); - await kv.set("mem:actions", high.id, high); - - const result = (await sdk.trigger("mem::frontier", {})) as { - success: boolean; - frontier: FrontierItem[]; - }; - - expect(result.frontier[0].action.id).toBe("act_high"); - expect(result.frontier[0].score).toBeGreaterThan( - result.frontier[1].score, - ); - }); - - it("excludes actions gated by pending checkpoint", async () => { - const gatedAction = makeAction({ - id: "act_gated", - title: "Gated action", - status: "pending", - }); - - const checkpoint: Checkpoint = { - id: "ckpt_1", - name: "CI check", - description: "Waiting for CI", - status: "pending", - type: "ci", - createdAt: new Date().toISOString(), - linkedActionIds: ["act_gated"], - }; - - await kv.set("mem:actions", gatedAction.id, gatedAction); - await kv.set("mem:checkpoints", checkpoint.id, checkpoint); - - const gateEdge: ActionEdge = { - id: "ae_gate", - type: "gated_by", - sourceActionId: gatedAction.id, - targetActionId: checkpoint.id, - createdAt: new Date().toISOString(), - }; - await kv.set("mem:action-edges", gateEdge.id, gateEdge); - - const result = (await sdk.trigger("mem::frontier", {})) as { - success: boolean; - frontier: FrontierItem[]; - }; - - expect(result.frontier.length).toBe(0); - }); - - it("excludes actions conflicting with active actions", async () => { - const activeAction = makeAction({ - id: "act_active", - title: "Active task", - status: "active", - }); - const conflictAction = makeAction({ - id: "act_conflict", - title: "Conflicting task", - status: "pending", - }); - - await kv.set("mem:actions", activeAction.id, activeAction); - await kv.set("mem:actions", conflictAction.id, conflictAction); - - const conflictEdge: ActionEdge = { - id: "ae_conflict", - type: "conflicts_with", - sourceActionId: conflictAction.id, - targetActionId: activeAction.id, - createdAt: new Date().toISOString(), - }; - await kv.set("mem:action-edges", conflictEdge.id, conflictEdge); - - const result = (await sdk.trigger("mem::frontier", {})) as { - success: boolean; - frontier: FrontierItem[]; - }; - - const ids = result.frontier.map((f) => f.action.id); - expect(ids).toContain("act_active"); - expect(ids).not.toContain("act_conflict"); - }); - - it("active actions get score bonus", async () => { - const pendingAction = makeAction({ - id: "act_pending", - title: "Pending", - status: "pending", - priority: 5, - createdAt: new Date().toISOString(), - }); - const activeAction = makeAction({ - id: "act_active", - title: "Active", - status: "active", - priority: 5, - createdAt: new Date().toISOString(), - }); - - await kv.set("mem:actions", pendingAction.id, pendingAction); - await kv.set("mem:actions", activeAction.id, activeAction); - - const result = (await sdk.trigger("mem::frontier", {})) as { - success: boolean; - frontier: FrontierItem[]; - }; - - const activeItem = result.frontier.find( - (f) => f.action.id === "act_active", - )!; - const pendingItem = result.frontier.find( - (f) => f.action.id === "act_pending", - )!; - - expect(activeItem.score).toBeGreaterThan(pendingItem.score); - }); - }); - - describe("mem::next", () => { - it("returns top suggestion when actions exist", async () => { - const action = makeAction({ - id: "act_1", - title: "Top task", - priority: 8, - tags: ["urgent"], - }); - await kv.set("mem:actions", action.id, action); - - const result = (await sdk.trigger("mem::next", {})) as { - success: boolean; - suggestion: { - actionId: string; - title: string; - description: string; - priority: number; - score: number; - tags: string[]; - } | null; - message: string; - totalActions: number; - }; - - expect(result.success).toBe(true); - expect(result.suggestion).not.toBeNull(); - expect(result.suggestion!.actionId).toBe("act_1"); - expect(result.suggestion!.title).toBe("Top task"); - expect(result.suggestion!.priority).toBe(8); - expect(result.suggestion!.tags).toEqual(["urgent"]); - expect(result.message).toContain("Top task"); - expect(result.totalActions).toBe(1); - }); - - it("returns null suggestion when no actions exist", async () => { - const result = (await sdk.trigger("mem::next", {})) as { - success: boolean; - suggestion: null; - message: string; - totalActions: number; - }; - - expect(result.success).toBe(true); - expect(result.suggestion).toBeNull(); - expect(result.message).toContain("No actionable work"); - expect(result.totalActions).toBe(0); - }); - - it("returns null when all actions are done", async () => { - const doneAction = makeAction({ - id: "act_done", - title: "Completed", - status: "done", - }); - await kv.set("mem:actions", doneAction.id, doneAction); - - const result = (await sdk.trigger("mem::next", {})) as { - success: boolean; - suggestion: null; - message: string; - totalActions: number; - }; - - expect(result.success).toBe(true); - expect(result.suggestion).toBeNull(); - expect(result.totalActions).toBe(1); - }); - - it("propagates failure when frontier fails", async () => { - const originalFunctions = new Map(); - - const failSdk = { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - originalFunctions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - if (id === "mem::frontier") { - return { success: false, error: "internal failure" }; - } - const fn = originalFunctions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; - - const failKv = mockKV(); - registerFrontierFunction(failSdk as never, failKv as never); - - const nextFn = originalFunctions.get("mem::next")!; - const result = (await nextFn({})) as { - success: boolean; - suggestion: null; - message: string; - totalActions: number; - }; - - expect(result.success).toBe(false); - expect(result.suggestion).toBeNull(); - expect(result.message).toContain("Failed to compute frontier"); - expect(result.totalActions).toBe(0); - }); - - it("respects project filter", async () => { - const alphaAction = makeAction({ - id: "act_alpha", - title: "Alpha task", - project: "alpha", - priority: 5, - }); - const betaAction = makeAction({ - id: "act_beta", - title: "Beta task", - project: "beta", - priority: 10, - }); - - await kv.set("mem:actions", alphaAction.id, alphaAction); - await kv.set("mem:actions", betaAction.id, betaAction); - - const result = (await sdk.trigger("mem::next", { - project: "alpha", - })) as { - success: boolean; - suggestion: { actionId: string; title: string } | null; - }; - - expect(result.success).toBe(true); - expect(result.suggestion).not.toBeNull(); - expect(result.suggestion!.actionId).toBe("act_alpha"); - }); - }); -}); diff --git a/test/governance.test.ts b/test/governance.test.ts deleted file mode 100644 index 6390bbee..00000000 --- a/test/governance.test.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerGovernanceFunction } from "../src/functions/governance.js"; -import { - getSearchIndex, - setIndexPersistence, -} from "../src/functions/search.js"; -import { memoryToObservation } from "../src/state/memory-utils.js"; -import type { Memory, AuditEntry } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeMemory(id: string, type: Memory["type"] = "pattern"): Memory { - return { - id, - createdAt: "2026-02-01T00:00:00Z", - updatedAt: "2026-02-01T00:00:00Z", - type, - title: `Memory ${id}`, - content: `Content for ${id}`, - concepts: ["test"], - files: [], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, - }; -} - -describe("Governance Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(async () => { - sdk = mockSdk(); - kv = mockKV(); - registerGovernanceFunction(sdk as never, kv as never); - - await kv.set("mem:memories", "mem_1", makeMemory("mem_1", "pattern")); - await kv.set("mem:memories", "mem_2", makeMemory("mem_2", "bug")); - await kv.set("mem:memories", "mem_3", makeMemory("mem_3", "pattern")); - }); - - it("governance-delete removes specified memories", async () => { - const result = (await sdk.trigger("mem::governance-delete", { - memoryIds: ["mem_1"], - reason: "outdated", - })) as { success: boolean; deleted: number; total: number }; - - expect(result.success).toBe(true); - expect(result.deleted).toBe(1); - expect(result.total).toBe(1); - - const remaining = await kv.list("mem:memories"); - expect(remaining.length).toBe(2); - }); - - it("governance-delete handles non-existent IDs gracefully", async () => { - const result = (await sdk.trigger("mem::governance-delete", { - memoryIds: ["nonexistent_1", "nonexistent_2"], - })) as { success: boolean; deleted: number; total: number }; - - expect(result.success).toBe(true); - expect(result.deleted).toBe(0); - expect(result.total).toBe(2); - - const remaining = await kv.list("mem:memories"); - expect(remaining.length).toBe(3); - }); - - it("governance-bulk deletes by type filter", async () => { - const result = (await sdk.trigger("mem::governance-bulk", { - type: ["pattern"], - })) as { success: boolean; deleted: number }; - - expect(result.success).toBe(true); - expect(result.deleted).toBe(2); - - const remaining = await kv.list("mem:memories"); - expect(remaining.length).toBe(1); - expect(remaining[0].type).toBe("bug"); - }); - - it("governance-bulk respects dryRun", async () => { - const result = (await sdk.trigger("mem::governance-bulk", { - type: ["pattern"], - dryRun: true, - })) as { success: boolean; dryRun: boolean; wouldDelete: number; ids: string[] }; - - expect(result.success).toBe(true); - expect(result.dryRun).toBe(true); - expect(result.wouldDelete).toBe(2); - expect(result.ids).toContain("mem_1"); - expect(result.ids).toContain("mem_3"); - - const remaining = await kv.list("mem:memories"); - expect(remaining.length).toBe(3); - }); - - // Delete paths must tear down the BM25 index entry and trigger an - // IndexPersistence save so a hard process exit can't restore a stale - // snapshot at next boot. - describe("search index cleanup on delete", () => { - function indexedObs(id: string, title: string) { - return memoryToObservation({ - id, - createdAt: "2026-02-01T00:00:00Z", - updatedAt: "2026-02-01T00:00:00Z", - type: "fact", - title, - content: title, - concepts: [], - files: [], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, - }); - } - - function mockPersistence() { - return { - scheduleSave: vi.fn(), - save: vi.fn(async () => {}), - }; - } - - beforeEach(() => { - // SearchIndex is a module-level singleton — wipe it so cases - // don't bleed into each other. - getSearchIndex().clear(); - setIndexPersistence(null); - }); - - // The persistence singleton is module-scoped; without this reset - // the last test's mock would leak into sibling tests in the outer - // suite and trigger unexpected spy invocations. - afterEach(() => { - setIndexPersistence(null); - }); - - it("governance-delete removes the memory from the search index", async () => { - getSearchIndex().add(indexedObs("mem_1", "alpha")); - getSearchIndex().add(indexedObs("mem_2", "beta")); - expect(getSearchIndex().has("mem_1")).toBe(true); - - await sdk.trigger("mem::governance-delete", { - memoryIds: ["mem_1"], - }); - - expect(getSearchIndex().has("mem_1")).toBe(false); - expect(getSearchIndex().has("mem_2")).toBe(true); - }); - - it("governance-delete flushes persistence immediately", async () => { - const persistence = mockPersistence(); - setIndexPersistence(persistence); - getSearchIndex().add(indexedObs("mem_1", "alpha")); - - await sdk.trigger("mem::governance-delete", { memoryIds: ["mem_1"] }); - - // Delete paths must use the synchronous save (not the debounced - // scheduleSave) so a process exit immediately after delete can't - // resurrect the entry on next boot. - expect(persistence.save).toHaveBeenCalled(); - }); - - it("governance-delete skips persistence flush when nothing was deleted", async () => { - const persistence = mockPersistence(); - setIndexPersistence(persistence); - - await sdk.trigger("mem::governance-delete", { - memoryIds: ["nonexistent_999"], - }); - - expect(persistence.save).not.toHaveBeenCalled(); - }); - - it("governance-bulk removes deleted memories from the search index", async () => { - getSearchIndex().add(indexedObs("mem_1", "alpha")); - getSearchIndex().add(indexedObs("mem_2", "beta")); - getSearchIndex().add(indexedObs("mem_3", "gamma")); - - await sdk.trigger("mem::governance-bulk", { type: ["pattern"] }); - - // mem_1 and mem_3 are type "pattern" per the outer beforeEach. - expect(getSearchIndex().has("mem_1")).toBe(false); - expect(getSearchIndex().has("mem_3")).toBe(false); - expect(getSearchIndex().has("mem_2")).toBe(true); - }); - - it("governance-bulk flushes persistence immediately", async () => { - const persistence = mockPersistence(); - setIndexPersistence(persistence); - getSearchIndex().add(indexedObs("mem_1", "alpha")); - - await sdk.trigger("mem::governance-bulk", { type: ["pattern"] }); - - expect(persistence.save).toHaveBeenCalled(); - }); - }); - - it("audit-query returns audit entries", async () => { - await sdk.trigger("mem::governance-delete", { - memoryIds: ["mem_1"], - reason: "cleanup", - }); - - const entries = (await sdk.trigger("mem::audit-query", {})) as AuditEntry[]; - - expect(entries.length).toBe(1); - expect(entries[0].operation).toBe("delete"); - expect(entries[0].functionId).toBe("mem::governance-delete"); - }); -}); diff --git a/test/graph-retrieval.test.ts b/test/graph-retrieval.test.ts deleted file mode 100644 index 2eb3f923..00000000 --- a/test/graph-retrieval.test.ts +++ /dev/null @@ -1,298 +0,0 @@ -import { describe, it, expect, beforeEach } from "vitest"; -import { GraphRetrieval } from "../src/functions/graph-retrieval.js"; -import type { GraphNode, GraphEdge } from "../src/types.js"; - -function mockKV( - nodes: GraphNode[] = [], - edges: GraphEdge[] = [], -) { - const store = new Map>(); - const nodesMap = new Map(); - for (const n of nodes) nodesMap.set(n.id, n); - store.set("mem:graph:nodes", nodesMap); - - const edgesMap = new Map(); - for (const e of edges) edgesMap.set(e.id, e); - store.set("mem:graph:edges", edgesMap); - - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function makeNode( - id: string, - name: string, - type: GraphNode["type"] = "concept", - obsIds: string[] = ["obs_1"], -): GraphNode { - return { - id, - type, - name, - properties: {}, - sourceObservationIds: obsIds, - createdAt: new Date().toISOString(), - }; -} - -function makeEdge( - id: string, - sourceNodeId: string, - targetNodeId: string, - type: GraphEdge["type"] = "related_to", - weight = 0.8, -): GraphEdge { - return { - id, - type, - sourceNodeId, - targetNodeId, - weight, - sourceObservationIds: ["obs_1"], - createdAt: new Date().toISOString(), - tcommit: new Date().toISOString(), - isLatest: true, - }; -} - -describe("GraphRetrieval", () => { - it("finds entities by name", async () => { - const nodes = [ - makeNode("n1", "React", "library", ["obs_1"]), - makeNode("n2", "Vue", "library", ["obs_2"]), - ]; - const kv = mockKV(nodes, []); - const retrieval = new GraphRetrieval(kv as never); - - const results = await retrieval.searchByEntities(["React"]); - expect(results.length).toBeGreaterThan(0); - expect(results[0].obsId).toBe("obs_1"); - }); - - it("finds entities by partial name match", async () => { - const nodes = [makeNode("n1", "auth-middleware", "function", ["obs_1"])]; - const kv = mockKV(nodes, []); - const retrieval = new GraphRetrieval(kv as never); - - const results = await retrieval.searchByEntities(["auth"]); - expect(results.length).toBeGreaterThan(0); - }); - - it("traverses graph edges to find related observations", async () => { - const nodes = [ - makeNode("n1", "React", "library", ["obs_1"]), - makeNode("n2", "Component", "concept", ["obs_2"]), - ]; - const edges = [makeEdge("e1", "n1", "n2", "uses")]; - const kv = mockKV(nodes, edges); - const retrieval = new GraphRetrieval(kv as never); - - const results = await retrieval.searchByEntities(["React"], 2); - const obsIds = results.map((r) => r.obsId); - expect(obsIds).toContain("obs_1"); - expect(obsIds).toContain("obs_2"); - }); - - it("returns empty for no matches", async () => { - const kv = mockKV([], []); - const retrieval = new GraphRetrieval(kv as never); - const results = await retrieval.searchByEntities(["nonexistent"]); - expect(results).toEqual([]); - }); - - it("expands from existing chunks", async () => { - const nodes = [ - makeNode("n1", "auth.ts", "file", ["obs_1"]), - makeNode("n2", "jwt", "concept", ["obs_2"]), - ]; - const edges = [makeEdge("e1", "n1", "n2", "uses")]; - const kv = mockKV(nodes, edges); - const retrieval = new GraphRetrieval(kv as never); - - const results = await retrieval.expandFromChunks(["obs_1"]); - const obsIds = results.map((r) => r.obsId); - expect(obsIds).toContain("obs_2"); - }); - - it("does not duplicate already-seen observations in expansion", async () => { - const nodes = [makeNode("n1", "file.ts", "file", ["obs_1", "obs_2"])]; - const kv = mockKV(nodes, []); - const retrieval = new GraphRetrieval(kv as never); - - const results = await retrieval.expandFromChunks(["obs_1"]); - const obsIds = results.map((r) => r.obsId); - expect(obsIds).not.toContain("obs_1"); - }); - - it("performs temporal query - current state", async () => { - const nodes = [makeNode("n1", "Alice", "person", ["obs_1"])]; - const edges = [ - makeEdge("e1", "n1", "n1", "located_in" as any, 0.9), - { - ...makeEdge("e2", "n1", "n1", "located_in" as any, 0.9), - tvalid: "2024-06-01", - isLatest: true, - }, - ]; - const kv = mockKV(nodes, edges); - const retrieval = new GraphRetrieval(kv as never); - - const result = await retrieval.temporalQuery("Alice"); - expect(result.entity).toBeDefined(); - expect(result.entity!.name).toBe("Alice"); - expect(result.currentState.length).toBeGreaterThan(0); - }); - - it("returns null entity for unknown name", async () => { - const kv = mockKV([], []); - const retrieval = new GraphRetrieval(kv as never); - const result = await retrieval.temporalQuery("Unknown"); - expect(result.entity).toBeNull(); - }); - - it("scores closer paths higher", async () => { - const nodes = [ - makeNode("n1", "React", "library", ["obs_1"]), - makeNode("n2", "Hook", "concept", ["obs_2"]), - makeNode("n3", "State", "concept", ["obs_3"]), - ]; - const edges = [ - makeEdge("e1", "n1", "n2", "uses", 0.9), - makeEdge("e2", "n2", "n3", "related_to", 0.8), - ]; - const kv = mockKV(nodes, edges); - const retrieval = new GraphRetrieval(kv as never); - - const results = await retrieval.searchByEntities(["React"], 3); - const directScore = results.find((r) => r.obsId === "obs_1")?.score ?? 0; - const indirectScore = results.find((r) => r.obsId === "obs_3")?.score ?? 0; - expect(directScore).toBeGreaterThan(indirectScore); - }); - - // Dijkstra path selection (#328). The BFS implementation this - // replaced visited a node via its first-discovered path regardless - // of edge weight. Dijkstra picks the highest-weight (lowest - // 1/weight cost) path, so a one-hop weak edge no longer beats a - // two-hop chain of strong edges to the same node. - it("picks the weight-optimal path under Dijkstra, not the edge-count-shortest one (#328)", async () => { - const nodes = [ - makeNode("n1", "Start", "concept", ["obs_start"]), - makeNode("n2", "Mid", "concept", ["obs_mid"]), - makeNode("n3", "End", "concept", ["obs_end"]), - ]; - const edges = [ - // Direct n1 → n3 path with a weak edge. BFS would prefer this. - makeEdge("e_direct", "n1", "n3", "related_to", 0.15), - // Two-hop chain n1 → n2 → n3 with strong edges. Total cost - // (1/0.9) + (1/0.9) ≈ 2.22, vs direct 1/0.15 ≈ 6.67. - // Dijkstra picks the chain. - makeEdge("e_strong_a", "n1", "n2", "related_to", 0.9), - makeEdge("e_strong_b", "n2", "n3", "related_to", 0.9), - ]; - const kv = mockKV(nodes, edges); - const retrieval = new GraphRetrieval(kv as never); - - const results = await retrieval.searchByEntities(["Start"], 3); - const endResult = results.find((r) => r.obsId === "obs_end"); - expect(endResult).toBeDefined(); - // Path is [Start → Mid → End] (length 3) — Dijkstra picked the - // chain of two strong edges over the direct weak one. - expect(endResult!.pathLength).toBe(3); - expect(endResult!.graphContext).toContain("Mid"); - }); - - it("handles disconnected nodes without crashing", async () => { - const nodes = [ - makeNode("n1", "A", "concept", ["obs_a"]), - makeNode("n2", "B", "concept", ["obs_b"]), - // n3 is unreachable from the matched node. - makeNode("n3", "Lonely", "concept", ["obs_lonely"]), - ]; - const edges = [makeEdge("e1", "n1", "n2", "related_to", 0.7)]; - const kv = mockKV(nodes, edges); - const retrieval = new GraphRetrieval(kv as never); - - const results = await retrieval.searchByEntities(["A"], 5); - expect(results.find((r) => r.obsId === "obs_a")).toBeDefined(); - expect(results.find((r) => r.obsId === "obs_b")).toBeDefined(); - expect(results.find((r) => r.obsId === "obs_lonely")).toBeUndefined(); - }); - - it("clamps near-zero edge weights without dividing by zero", async () => { - const nodes = [ - makeNode("n1", "Anchor", "concept", ["obs_anchor"]), - makeNode("n2", "Weak", "concept", ["obs_weak"]), - ]; - // weight: 0 is malformed but we shouldn't crash on it; the clamp - // floor at 0.01 means traversal completes with a very high cost - // rather than throwing or producing Infinity. - const edges = [makeEdge("e1", "n1", "n2", "related_to", 0)]; - const kv = mockKV(nodes, edges); - const retrieval = new GraphRetrieval(kv as never); - - const results = await retrieval.searchByEntities(["Anchor"], 2); - const weak = results.find((r) => r.obsId === "obs_weak"); - expect(weak).toBeDefined(); - expect(Number.isFinite(weak!.score)).toBe(true); - }); - - it("scores startNode observations at 1.0 via the fallback path, not 0.5 via the path-scoring loop (#328 review)", async () => { - // Regression for a bug surfaced by inline review on #463: if the - // traversal includes a length-1 path for the startNode itself, - // the generic path-scoring loop in searchByEntities computes - // avgWeight=0.5 (empty edgeWeights → fallback) and pathLength=1, - // yielding score=0.5, then marks the obs as visited. The - // dedicated score=1.0 fallback loop for startNode obs is then - // skipped via the visitedObs guard — dead code. - const nodes = [ - makeNode("n1", "React", "library", ["obs_root"]), - makeNode("n2", "Hook", "concept", ["obs_neighbor"]), - ]; - const edges = [makeEdge("e1", "n1", "n2", "uses", 0.8)]; - const kv = mockKV(nodes, edges); - const retrieval = new GraphRetrieval(kv as never); - - const results = await retrieval.searchByEntities(["React"], 2); - const root = results.find((r) => r.obsId === "obs_root"); - expect(root).toBeDefined(); - expect(root!.score).toBe(1.0); - expect(root!.pathLength).toBe(0); - }); - - it("respects maxDepth bound (Dijkstra stops at edge-count depth)", async () => { - // Chain n1 -> n2 -> n3 -> n4. With maxDepth=2 we should reach n3 - // but not n4 — edge-count semantics preserved from the old BFS. - const nodes = [ - makeNode("n1", "Start", "concept", ["obs_1"]), - makeNode("n2", "Hop1", "concept", ["obs_2"]), - makeNode("n3", "Hop2", "concept", ["obs_3"]), - makeNode("n4", "Hop3", "concept", ["obs_4"]), - ]; - const edges = [ - makeEdge("e1", "n1", "n2", "related_to", 0.8), - makeEdge("e2", "n2", "n3", "related_to", 0.8), - makeEdge("e3", "n3", "n4", "related_to", 0.8), - ]; - const kv = mockKV(nodes, edges); - const retrieval = new GraphRetrieval(kv as never); - - const results = await retrieval.searchByEntities(["Start"], 2); - expect(results.find((r) => r.obsId === "obs_3")).toBeDefined(); - expect(results.find((r) => r.obsId === "obs_4")).toBeUndefined(); - }); -}); diff --git a/test/graph.test.ts b/test/graph.test.ts deleted file mode 100644 index ca50264c..00000000 --- a/test/graph.test.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerGraphFunction } from "../src/functions/graph.js"; -import type { - CompressedObservation, - GraphNode, - GraphEdge, - GraphQueryResult, -} from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -const mockProvider = { - name: "test", - compress: vi.fn().mockResolvedValue(` -src/index.ts -typescript - - - -`), - summarize: vi.fn(), -}; - -const testObs: CompressedObservation = { - id: "obs_1", - sessionId: "ses_1", - timestamp: "2026-02-01T10:00:00Z", - type: "file_edit", - title: "Edit index file", - facts: ["Modified main function"], - narrative: "Updated index.ts with main function", - concepts: ["typescript", "entry-point"], - files: ["src/index.ts"], - importance: 7, -}; - -describe("Graph Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - vi.clearAllMocks(); - registerGraphFunction(sdk as never, kv as never, mockProvider as never); - }); - - it("graph-extract creates nodes and edges from XML response", async () => { - const result = (await sdk.trigger("mem::graph-extract", { - observations: [testObs], - })) as { success: boolean; nodesAdded: number; edgesAdded: number }; - - expect(result.success).toBe(true); - expect(result.nodesAdded).toBe(2); - expect(result.edgesAdded).toBe(1); - - const nodes = await kv.list("mem:graph:nodes"); - expect(nodes.length).toBe(2); - expect(nodes.find((n) => n.name === "src/index.ts")).toBeDefined(); - expect(nodes.find((n) => n.name === "main")).toBeDefined(); - - const edges = await kv.list("mem:graph:edges"); - expect(edges.length).toBe(1); - expect(edges[0].type).toBe("uses"); - }); - - it("graph-extract accepts self-closing entity tags", async () => { - mockProvider.compress.mockResolvedValueOnce(` - -typescript - - - -`); - - const result = (await sdk.trigger("mem::graph-extract", { - observations: [testObs], - })) as { success: boolean; nodesAdded: number; edgesAdded: number }; - - expect(result.success).toBe(true); - expect(result.nodesAdded).toBe(2); - expect(result.edgesAdded).toBe(1); - - const nodes = await kv.list("mem:graph:nodes"); - expect(nodes.some((n) => n.name === "src/index.ts")).toBe(true); - expect(nodes.some((n) => n.name === "main")).toBe(true); - - const edges = await kv.list("mem:graph:edges"); - expect(edges).toHaveLength(1); - expect(edges[0].type).toBe("uses"); - }); - - it("graph-extract tolerates reordered attributes (#635)", async () => { - // Codex CLI's LLM tends to emit attribute order name→type and - // source→target→type rather than the hard-coded type-first / - // type/source/target/weight sequence the old parser required. - mockProvider.compress.mockResolvedValueOnce(` - -typescript - - - -`); - - const result = (await sdk.trigger("mem::graph-extract", { - observations: [testObs], - })) as { success: boolean; nodesAdded: number; edgesAdded: number }; - - expect(result.success).toBe(true); - expect(result.nodesAdded).toBe(2); - expect(result.edgesAdded).toBe(1); - - const nodes = await kv.list("mem:graph:nodes"); - expect(nodes.find((n) => n.name === "src/index.ts")?.type).toBe("file"); - expect(nodes.find((n) => n.name === "main")?.type).toBe("function"); - - const edges = await kv.list("mem:graph:edges"); - expect(edges).toHaveLength(1); - expect(edges[0].type).toBe("uses"); - expect(edges[0].weight).toBeCloseTo(0.9, 5); - }); - - it("graph-query with search returns matching nodes", async () => { - await sdk.trigger("mem::graph-extract", { observations: [testObs] }); - - const result = (await sdk.trigger("mem::graph-query", { - query: "index", - })) as GraphQueryResult; - - expect(result.nodes.length).toBeGreaterThanOrEqual(1); - expect(result.nodes.some((n) => n.name.includes("index"))).toBe(true); - }); - - it("graph-query with startNodeId does BFS traversal", async () => { - await sdk.trigger("mem::graph-extract", { observations: [testObs] }); - - const nodes = await kv.list("mem:graph:nodes"); - const fileNode = nodes.find((n) => n.name === "src/index.ts")!; - - const result = (await sdk.trigger("mem::graph-query", { - startNodeId: fileNode.id, - maxDepth: 2, - })) as GraphQueryResult; - - expect(result.nodes.length).toBeGreaterThanOrEqual(1); - expect(result.edges.length).toBeGreaterThanOrEqual(1); - expect(result.depth).toBe(2); - }); - - it("graph-stats returns counts by type", async () => { - await sdk.trigger("mem::graph-extract", { observations: [testObs] }); - - const result = (await sdk.trigger("mem::graph-stats", {})) as { - totalNodes: number; - totalEdges: number; - nodesByType: Record; - edgesByType: Record; - }; - - expect(result.totalNodes).toBe(2); - expect(result.totalEdges).toBe(1); - expect(result.nodesByType.file).toBe(1); - expect(result.nodesByType.function).toBe(1); - expect(result.edgesByType.uses).toBe(1); - }); - - it("graph-extract returns error for empty observations", async () => { - const result = (await sdk.trigger("mem::graph-extract", { - observations: [], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("No observations"); - }); -}); diff --git a/test/health-thresholds.test.ts b/test/health-thresholds.test.ts deleted file mode 100644 index 6f918a91..00000000 --- a/test/health-thresholds.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { evaluateHealth } from "../src/health/thresholds.js"; -import type { HealthSnapshot } from "../src/types.js"; - -function snap(over: Partial = {}): HealthSnapshot { - return { - connectionState: "connected", - workers: [], - memory: { heapUsed: 0, heapTotal: 1, rss: 0, external: 0 }, - cpu: { userMicros: 0, systemMicros: 0, percent: 0 }, - eventLoopLagMs: 0, - uptimeSeconds: 1, - kvConnectivity: { status: "ok", latencyMs: 1 }, - status: "healthy", - alerts: [], - ...over, - }; -} - -describe("evaluateHealth memory severity", () => { - it("stays healthy when heap fills a tiny steady-state process (issue #158)", () => { - const s = snap({ - memory: { - heapUsed: 45 * 1024 * 1024, - heapTotal: 46 * 1024 * 1024, - rss: 120 * 1024 * 1024, - external: 0, - }, - }); - const { status, alerts, notes } = evaluateHealth(s); - expect(status).toBe("healthy"); - expect(alerts.find((a) => a.startsWith("memory_critical_"))).toBeUndefined(); - expect(alerts.find((a) => a.startsWith("memory_warn_"))).toBeUndefined(); - expect(alerts.find((a) => a.startsWith("memory_heap_tight_"))).toBeUndefined(); - expect(notes.find((n) => n.startsWith("memory_heap_tight_"))).toBeDefined(); - }); - - it("goes critical when heap ratio is high AND RSS is above the floor", () => { - const s = snap({ - memory: { - heapUsed: 970 * 1024 * 1024, - heapTotal: 1000 * 1024 * 1024, - rss: 1100 * 1024 * 1024, - external: 0, - }, - }); - const { status, alerts } = evaluateHealth(s); - expect(status).toBe("critical"); - expect(alerts.some((a) => a.startsWith("memory_critical_"))).toBe(true); - }); - - it("records heap_tight in the warn band when RSS is below the floor", () => { - const s = snap({ - memory: { - heapUsed: 85 * 1024 * 1024, - heapTotal: 100 * 1024 * 1024, - rss: 50 * 1024 * 1024, - external: 0, - }, - }); - const { status, alerts, notes } = evaluateHealth(s); - expect(status).toBe("healthy"); - expect(notes.some((n) => n.startsWith("memory_heap_tight_"))).toBe(true); - expect(alerts.some((a) => a.startsWith("memory_heap_tight_"))).toBe(false); - expect(alerts.some((a) => a.startsWith("memory_warn_"))).toBe(false); - expect(alerts.some((a) => a.startsWith("memory_critical_"))).toBe(false); - }); - - it("goes degraded when heap is above warn AND RSS is above the floor", () => { - const s = snap({ - memory: { - heapUsed: 850 * 1024 * 1024, - heapTotal: 1000 * 1024 * 1024, - rss: 900 * 1024 * 1024, - external: 0, - }, - }); - const { status, alerts } = evaluateHealth(s, { memoryRssFloorBytes: 800 * 1024 * 1024 }); - expect(status).toBe("degraded"); - expect(alerts.some((a) => a.startsWith("memory_warn_"))).toBe(true); - }); - - it("respects caller-supplied memoryRssFloorBytes", () => { - const s = snap({ - memory: { - heapUsed: 98, - heapTotal: 100, - rss: 50 * 1024 * 1024, - external: 0, - }, - }); - const loose = evaluateHealth(s, { memoryRssFloorBytes: 10 * 1024 * 1024 }); - expect(loose.status).toBe("critical"); - const strict = evaluateHealth(s, { memoryRssFloorBytes: 1024 * 1024 * 1024 }); - expect(strict.status).toBe("healthy"); - }); -}); diff --git a/test/helpers/mocks.ts b/test/helpers/mocks.ts deleted file mode 100644 index a382e2ee..00000000 --- a/test/helpers/mocks.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { vi } from "vitest"; - -type Handler = (data: unknown) => Promise; - -export function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -export function mockSdk() { - const functions = new Map(); - return { - registerFunction: ( - idOrOpts: string | { id: string }, - handler: Handler, - _options?: Record, - ) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: vi.fn(), - trigger: async ( - idOrInput: - | string - | { function_id: string; payload: unknown; action?: unknown }, - data?: unknown, - ) => { - const id = - typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = - typeof idOrInput === "string" ? data : (idOrInput.payload as unknown); - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} diff --git a/test/hook-project.test.ts b/test/hook-project.test.ts deleted file mode 100644 index 1445b600..00000000 --- a/test/hook-project.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { mkdtempSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { basename, join } from "node:path"; -import { execSync } from "node:child_process"; -import { resolveProject } from "../src/hooks/_project.js"; - -describe("resolveProject — hook project basename resolver", () => { - const originalEnv = process.env.AGENTMEMORY_PROJECT_NAME; - const repoBasename = basename( - execSync("git rev-parse --show-toplevel", { - cwd: process.cwd(), - stdio: ["ignore", "pipe", "ignore"], - }) - .toString() - .trim(), - ); - - beforeEach(() => { - delete process.env.AGENTMEMORY_PROJECT_NAME; - }); - - afterEach(() => { - if (originalEnv === undefined) { - delete process.env.AGENTMEMORY_PROJECT_NAME; - } else { - process.env.AGENTMEMORY_PROJECT_NAME = originalEnv; - } - }); - - it("AGENTMEMORY_PROJECT_NAME env wins over everything", () => { - process.env.AGENTMEMORY_PROJECT_NAME = "my-override"; - expect(resolveProject("/var/log")).toBe("my-override"); - expect(resolveProject(process.cwd())).toBe("my-override"); - }); - - it("trims whitespace on env override", () => { - process.env.AGENTMEMORY_PROJECT_NAME = " spaced "; - expect(resolveProject("/var/log")).toBe("spaced"); - }); - - it("ignores empty env override", () => { - process.env.AGENTMEMORY_PROJECT_NAME = " "; - expect(resolveProject(process.cwd())).toBe(repoBasename); - }); - - it("returns git toplevel basename when cwd is inside a repo", () => { - const top = resolveProject(process.cwd()); - expect(top).toBe(repoBasename); - }); - - it("returns git toplevel basename from a nested subdir", () => { - const nested = join(process.cwd(), "src", "hooks"); - expect(resolveProject(nested)).toBe(repoBasename); - }); - - it("falls back to basename(cwd) when not in a git repo", () => { - const dir = mkdtempSync(join(tmpdir(), "amem-noproj-")); - try { - expect(resolveProject(dir)).toBe(dir.split("/").pop()); - } finally { - rmSync(dir, { recursive: true, force: true }); - } - }); - - it("defaults to process.cwd() when no cwd argument given", () => { - expect(resolveProject()).toBe(repoBasename); - }); - - it("defaults to process.cwd() when cwd argument is empty", () => { - expect(resolveProject("")).toBe(repoBasename); - expect(resolveProject(" ")).toBe(repoBasename); - }); -}); diff --git a/test/http-api.test.ts b/test/http-api.test.ts new file mode 100644 index 00000000..2fe893f1 --- /dev/null +++ b/test/http-api.test.ts @@ -0,0 +1,465 @@ +import assert from "node:assert/strict"; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { openDatabase, type Database } from "../src/db/index.js"; +import { getAppPaths } from "../src/paths.js"; +import { createAppServer, createStartupScanner } from "../src/server/index.js"; +import { scanSource } from "../src/sources/scan.js"; + +test("HTTP API scans sources, lists sessions, observations, runs, and updates todos", async () => { + const fixture = createFixture(); + const paths = getAppPaths(join(fixture.root, "home")); + const db = openDatabase(paths); + const server = await startServer(db, paths, { + llmExtractor: async (observations: Array<{ id: string; role: string; text: string }>) => { + const observation = observations.find((item) => item.role === "user"); + assert.ok(observation); + return { + ok: true, + todos: [{ + title: "Add HTTP API routes", + description: "Add HTTP API routes for the local todo service.", + confidence: 0.9, + sourceObservationId: observation.id, + quote: observation.text, + dedupeKey: "http-api-routes" + }] + }; + } + }); + + try { + const scan = await postJson(server.url("/sources/scan"), { + source: "codex", + path: fixture.codex + }); + assert.equal(scan.status, 200); + assert.deepEqual(await scan.json(), { + source: "codex", + scanned: 1, + observations: 1, + skipped: 0 + }); + + const sources = await getJson(server.url("/sources")); + assert.equal(sources.status, 200); + const sourceBody = await sources.json(); + assert.equal(sourceBody.find((source: any) => source.source === "codex").sessions, 1); + + const sessions = await getJson(server.url("/sessions")); + assert.equal(sessions.status, 200); + const sessionBody = await sessions.json(); + assert.equal(sessionBody.length, 1); + + const observations = await getJson(server.url(`/sessions/${sessionBody[0].id}/observations`)); + assert.equal(observations.status, 200); + assert.equal((await observations.json())[0].text, "Please add HTTP API routes"); + + const organize = await postJson(server.url("/todos/organize"), {}); + const organizeBody = await organize.json(); + assert.equal(organize.status, 200); + assert.equal(organizeBody.created, 1); + + const run = await getJson(server.url(`/organize-runs/${organizeBody.runId}`)); + assert.equal(run.status, 200); + assert.equal((await run.json()).runId, organizeBody.runId); + + const todos = await getJson(server.url("/todos")); + const todo = (await todos.json())[0]; + const patch = await patchJson(server.url(`/todos/${todo.id}`), { status: "done" }); + assert.equal(patch.status, 200); + assert.equal((await patch.json()).status, "done"); + + const evidence = await getJson(server.url(`/todos/${todo.id}/evidence`)); + assert.equal(evidence.status, 200); + assert.equal((await evidence.json())[0].text, "Please add HTTP API routes"); + + const updated = await getJson(server.url("/todos")); + assert.equal((await updated.json())[0].status, "done"); + } finally { + await server.close(); + db.close(); + rmSync(fixture.root, { recursive: true, force: true }); + } +}); + +test("HTTP API returns small explicit errors", async () => { + const fixture = createFixture(); + const paths = getAppPaths(join(fixture.root, "home")); + const db = openDatabase(paths); + const server = await startServer(db, paths); + + try { + assert.equal((await postJson(server.url("/sources/scan"), { source: "browser", path: fixture.codex })).status, 400); + assert.equal((await postJson(server.url("/sources/scan"), { source: "codex", path: join(fixture.root, "missing") })).status, 400); + assert.equal((await getJson(server.url("/sessions/missing/observations"))).status, 404); + assert.equal((await patchJson(server.url("/todos/missing"), { status: "done" })).status, 404); + assert.equal((await patchJson(server.url("/todos/missing"), { status: "todo" })).status, 400); + assert.equal((await getJson(server.url("/todos/missing/evidence"))).status, 404); + assert.equal((await getJson(server.url("/organize-runs/missing"))).status, 404); + } finally { + await server.close(); + db.close(); + rmSync(fixture.root, { recursive: true, force: true }); + } +}); + +test("HTTP API reports missing database", async () => { + const server = createAppServer(); + await new Promise((resolve) => server.listen(0, resolve)); + const address = server.address(); + assert.ok(address && typeof address !== "string"); + const url = (path: string) => `http://127.0.0.1:${address.port}${path}`; + + try { + assert.equal((await getJson(url("/sources"))).status, 503); + } finally { + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + } +}); + +test("HTTP API reports invalid JSON", async () => { + const fixture = createFixture(); + const paths = getAppPaths(join(fixture.root, "home")); + const db = openDatabase(paths); + const server = await startServer(db, paths); + + try { + const badJson = await fetch(server.url("/sources/scan"), { + method: "POST", + body: "{" + }); + assert.equal(badJson.status, 400); + assert.equal((await badJson.json()).error, "invalid_json"); + } finally { + await server.close(); + db.close(); + rmSync(fixture.root, { recursive: true, force: true }); + } +}); + +test("HTTP startup scan status exposes automatic scan results", async () => { + const fixture = createFixture(); + const previousCodex = process.env.AI_TODO_CODEX_HOME; + const previousClaude = process.env.AI_TODO_CLAUDE_HOME; + process.env.AI_TODO_CODEX_HOME = fixture.codex; + process.env.AI_TODO_CLAUDE_HOME = join(fixture.root, "missing-claude"); + const paths = getAppPaths(join(fixture.root, "home")); + const db = openDatabase(paths); + const scanner = createStartupScanner(db, paths); + const server = createAppServer({ db, paths, startupScan: scanner.status }); + await new Promise((resolve) => server.listen(0, resolve)); + const address = server.address(); + assert.ok(address && typeof address !== "string"); + const url = (path: string) => `http://127.0.0.1:${address.port}${path}`; + + try { + scanner.start(); + await waitFor(async () => ((await (await getJson(url("/startup/scan"))).json()) as any).status !== "indexing"); + const response = await getJson(url("/startup/scan")); + const body = await response.json(); + assert.equal(response.status, 200); + assert.equal(body.status, "failed"); + assert.equal(body.sources.find((source: any) => source.source === "codex").result.scanned, 1); + assert.ok(body.warnings.includes("claude-code_path_not_found")); + } finally { + if (previousCodex === undefined) delete process.env.AI_TODO_CODEX_HOME; + else process.env.AI_TODO_CODEX_HOME = previousCodex; + if (previousClaude === undefined) delete process.env.AI_TODO_CLAUDE_HOME; + else process.env.AI_TODO_CLAUDE_HOME = previousClaude; + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + db.close(); + rmSync(fixture.root, { recursive: true, force: true }); + } +}); + +test("HTTP source scan uses default paths with environment overrides", async () => { + const fixture = createFixture(); + const previousCodex = process.env.AI_TODO_CODEX_HOME; + process.env.AI_TODO_CODEX_HOME = fixture.codex; + const paths = getAppPaths(join(fixture.root, "home")); + const db = openDatabase(paths); + const server = await startServer(db, paths); + + try { + const response = await postJson(server.url("/sources/scan"), { source: "codex" }); + assert.equal(response.status, 200); + assert.equal((await response.json()).scanned, 1); + } finally { + if (previousCodex === undefined) { + delete process.env.AI_TODO_CODEX_HOME; + } else { + process.env.AI_TODO_CODEX_HOME = previousCodex; + } + await server.close(); + db.close(); + rmSync(fixture.root, { recursive: true, force: true }); + } +}); + +test("HTTP settings persist source paths and scan uses config path", async () => { + const fixture = createFixture(); + const paths = getAppPaths(join(fixture.root, "home")); + const db = openDatabase(paths); + const server = await startServer(db, paths); + + try { + const initial = await getJson(server.url("/settings")); + assert.equal(initial.status, 200); + const initialBody = await initial.json(); + assert.equal(initialBody.llm.model, "deepseek/deepseek-v4-flash"); + assert.equal(initialBody.llm.apiKeyConfigured, false); + assert.equal(initialBody.llm.apiKeyMasked, ""); + assert.equal(initialBody.organize.sinceDays, 7); + assert.equal(initialBody.organize.maxSessions, 16); + + const saved = await putJson(server.url("/settings"), { + sources: { codex: { path: fixture.codex }, "claude-code": {} }, + llm: { + enabled: true, + provider: "openai", + model: "custom/model", + endpoint: "https://llm.example.test/v1", + thinkingDepth: "high", + timeoutMs: 30000, + apiKey: "dummy-llm-key-value" + }, + organize: { + sinceDays: 30, + maxInteractionsPerSession: 15 + } + }); + assert.equal(saved.status, 200); + const savedBody = await saved.json(); + assert.equal(savedBody.sources.codex.path, fixture.codex); + assert.equal(savedBody.llm.model, "custom/model"); + assert.equal(savedBody.llm.apiKeyConfigured, true); + assert.equal(savedBody.llm.apiKeyMasked, "dum****alue"); + assert.equal(savedBody.llm.apiKey, undefined); + assert.equal(savedBody.organize.sinceDays, 30); + assert.match(readFileSync(paths.envPath, "utf8"), /AI_TODO_LLM_API_KEY=dummy-llm-key-value/); + + const scan = await postJson(server.url("/sources/scan"), { source: "codex" }); + assert.equal(scan.status, 200); + assert.equal((await scan.json()).scanned, 1); + } finally { + await server.close(); + db.close(); + rmSync(fixture.root, { recursive: true, force: true }); + } +}); + +test("HTTP settings rejects invalid config", async () => { + const fixture = createFixture(); + const paths = getAppPaths(join(fixture.root, "home")); + const db = openDatabase(paths); + const server = await startServer(db, paths); + + try { + assert.equal((await putJson(server.url("/settings"), { sources: { codex: {}, browser: {} } })).status, 400); + assert.equal((await putJson(server.url("/settings"), { sources: { codex: { path: "" }, "claude-code": {} } })).status, 400); + assert.equal((await putJson(server.url("/settings"), { sources: { codex: { path: 1 }, "claude-code": {} } })).status, 400); + assert.equal((await putJson(server.url("/settings"), { + sources: { codex: {}, "claude-code": {} }, + llm: { + enabled: true, + provider: "openai", + model: "", + endpoint: "https://example.test/v1", + thinkingDepth: "medium", + timeoutMs: 120000 + } + })).status, 400); + const badJson = await fetch(server.url("/settings"), { method: "PUT", body: "{" }); + assert.equal(badJson.status, 400); + } finally { + await server.close(); + db.close(); + rmSync(fixture.root, { recursive: true, force: true }); + } +}); + +test("HTTP settings clears llm api key when requested", async () => { + const fixture = createFixture(); + const paths = getAppPaths(join(fixture.root, "home")); + const db = openDatabase(paths); + const server = await startServer(db, paths); + + try { + const withKey = await putJson(server.url("/settings"), { + sources: { codex: {}, "claude-code": {} }, + llm: { + enabled: true, + provider: "openai", + model: "deepseek/deepseek-v4-flash", + endpoint: "https://api.novita.ai/openai/v1", + thinkingDepth: "medium", + timeoutMs: 120000, + apiKey: "dummy-llm-key-value" + }, + organize: { + sinceDays: 7, + maxInteractionsPerSession: 10 + } + }); + assert.equal(withKey.status, 200); + assert.ok(existsSync(paths.envPath)); + + const cleared = await putJson(server.url("/settings"), { + sources: { codex: {}, "claude-code": {} }, + llm: { + enabled: true, + provider: "openai", + model: "deepseek/deepseek-v4-flash", + endpoint: "https://api.novita.ai/openai/v1", + thinkingDepth: "medium", + timeoutMs: 120000, + apiKey: "" + }, + organize: { + sinceDays: 7, + maxInteractionsPerSession: 10 + } + }); + assert.equal(cleared.status, 200); + assert.equal((await cleared.json()).llm.apiKeyConfigured, false); + assert.doesNotMatch(readFileSync(paths.envPath, "utf8"), /dummy-llm-key-value/); + } finally { + await server.close(); + db.close(); + rmSync(fixture.root, { recursive: true, force: true }); + } +}); + +test("HTTP browser ingest validates input", async () => { + const fixture = createFixture(); + const paths = getAppPaths(join(fixture.root, "home")); + const db = openDatabase(paths); + const server = await startServer(db, paths); + + try { + assert.equal((await postJson(server.url("/browser/sessions"), {})).status, 400); + assert.equal((await postJson(server.url("/browser/sessions"), { messages: [] })).status, 400); + assert.equal((await postJson(server.url("/browser/sessions"), { messages: [{ text: "" }] })).status, 400); + assert.equal((await postJson(server.url("/browser/sessions"), { messages: [{ text: 1 }] })).status, 400); + assert.equal((await postJson(server.url("/browser/sessions"), { messages: [{ text: "x", createdAt: "nope" }] })).status, 400); + assert.equal((await postJson(server.url("/browser/sessions"), { messages: [{ role: "user", text: "Valid browser todo" }] })).status, 200); + } finally { + await server.close(); + db.close(); + rmSync(fixture.root, { recursive: true, force: true }); + } +}); + +test("HTTP organize returns structured failure", async () => { + const fixture = createFixture(); + const paths = getAppPaths(join(fixture.root, "home")); + const db = openDatabase(paths); + const scan = scanSource(db, "codex", fixture.codex, paths); + assert.equal(scan.ok, true); + const server = createAppServer({ + db, + paths, + organizeOptions: { + llmExtractor: async () => { + throw new Error("boom"); + } + } + }); + await new Promise((resolve) => server.listen(0, resolve)); + const address = server.address(); + assert.ok(address && typeof address !== "string"); + + try { + const response = await fetch(`http://127.0.0.1:${address.port}/todos/organize`, { method: "POST" }); + const body = await response.json(); + assert.equal(response.status, 500); + assert.equal(body.error, "organize_failed"); + assert.deepEqual(body.warnings, ["organize_failed"]); + } finally { + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + db.close(); + rmSync(fixture.root, { recursive: true, force: true }); + } +}); + +test("HTTP server serves the minimal UI assets", async () => { + const fixture = createFixture(); + const db = openDatabase(getAppPaths(join(fixture.root, "home"))); + const server = await startServer(db); + + try { + const index = await getJson(server.url("/")); + assert.equal(index.status, 200); + assert.match(await index.text(), /Source Dashboard/); + + const css = await getJson(server.url("/app.css")); + assert.equal(css.status, 200); + assert.match(css.headers.get("content-type") ?? "", /text\/css/); + + const js = await getJson(server.url("/app.js")); + assert.equal(js.status, 200); + assert.match(await js.text(), /organize/); + } finally { + await server.close(); + db.close(); + rmSync(fixture.root, { recursive: true, force: true }); + } +}); + +function createFixture() { + const root = mkdtempSync(join(tmpdir(), "ai-todo-http-")); + const codex = join(root, "codex"); + mkdirSync(codex); + writeFileSync(join(codex, "session.jsonl"), [ + JSON.stringify({ role: "user", text: "Please add HTTP API routes", timestamp: new Date().toISOString() }) + ].join("\n")); + return { root, codex }; +} + +async function startServer(db: Database, paths = getAppPaths(), organizeOptions = {}) { + const server = createAppServer({ db, paths, organizeOptions }); + await new Promise((resolve) => server.listen(0, resolve)); + const address = server.address(); + assert.ok(address && typeof address !== "string"); + return { + url: (path: string) => `http://127.0.0.1:${address.port}${path}`, + close: () => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())) + }; +} + +function getJson(url: string) { + return fetch(url); +} + +function postJson(url: string, body: unknown) { + return fetch(url, { + method: "POST", + body: JSON.stringify(body) + }); +} + +function patchJson(url: string, body: unknown) { + return fetch(url, { + method: "PATCH", + body: JSON.stringify(body) + }); +} + +function putJson(url: string, body: unknown) { + return fetch(url, { + method: "PUT", + body: JSON.stringify(body) + }); +} + + +async function waitFor(predicate: () => Promise) { + for (let attempt = 0; attempt < 50; attempt++) { + if (await predicate()) return; + await new Promise((resolve) => setTimeout(resolve, 10)); + } + throw new Error("timed out"); +} diff --git a/test/hybrid-search.test.ts b/test/hybrid-search.test.ts deleted file mode 100644 index 811247ac..00000000 --- a/test/hybrid-search.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { describe, it, expect, beforeEach } from "vitest"; -import { HybridSearch } from "../src/state/hybrid-search.js"; -import { SearchIndex } from "../src/state/search-index.js"; -import type { CompressedObservation, EmbeddingProvider } from "../src/types.js"; - -function makeObs( - overrides: Partial = {}, -): CompressedObservation { - return { - id: "obs_1", - sessionId: "ses_1", - timestamp: new Date().toISOString(), - type: "file_edit", - title: "Edit auth middleware", - subtitle: "JWT validation", - facts: ["Added token check"], - narrative: "Modified the auth middleware to validate JWT tokens", - concepts: ["authentication", "jwt"], - files: ["src/middleware/auth.ts"], - importance: 7, - ...overrides, - }; -} - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -describe("HybridSearch", () => { - let bm25: SearchIndex; - let kv: ReturnType; - - beforeEach(() => { - bm25 = new SearchIndex(); - kv = mockKV(); - }); - - it("returns BM25-only results when no vector index is provided", async () => { - const obs = makeObs({ id: "obs_1", sessionId: "ses_1" }); - bm25.add(obs); - await kv.set("mem:obs:ses_1", "obs_1", obs); - - const hybrid = new HybridSearch(bm25, null, null, kv as never); - const results = await hybrid.search("auth"); - - expect(results.length).toBe(1); - expect(results[0].observation.id).toBe("obs_1"); - expect(results[0].vectorScore).toBe(0); - expect(results[0].bm25Score).toBeGreaterThan(0); - }); - - it("returns empty results for no-match query", async () => { - const obs = makeObs({ id: "obs_1", sessionId: "ses_1" }); - bm25.add(obs); - await kv.set("mem:obs:ses_1", "obs_1", obs); - - const hybrid = new HybridSearch(bm25, null, null, kv as never); - const results = await hybrid.search("database"); - expect(results).toEqual([]); - }); - - it("combinedScore is derived from bm25Score when no vector index", async () => { - const obs = makeObs({ id: "obs_1", sessionId: "ses_1" }); - bm25.add(obs); - await kv.set("mem:obs:ses_1", "obs_1", obs); - - const hybrid = new HybridSearch(bm25, null, null, kv as never); - const results = await hybrid.search("auth"); - - expect(results[0].combinedScore).toBeGreaterThan(0); - expect(results[0].vectorScore).toBe(0); - expect(results[0].graphScore).toBe(0); - }); - - it("results are sorted by combinedScore descending", async () => { - const obs1 = makeObs({ - id: "obs_1", - sessionId: "ses_1", - title: "auth handler", - narrative: "auth auth auth module", - concepts: ["auth"], - }); - const obs2 = makeObs({ - id: "obs_2", - sessionId: "ses_1", - title: "database setup", - narrative: "auth connection config", - concepts: ["database"], - }); - bm25.add(obs1); - bm25.add(obs2); - await kv.set("mem:obs:ses_1", "obs_1", obs1); - await kv.set("mem:obs:ses_1", "obs_2", obs2); - - const hybrid = new HybridSearch(bm25, null, null, kv as never); - const results = await hybrid.search("auth"); - - expect(results.length).toBe(2); - expect(results[0].combinedScore).toBeGreaterThanOrEqual( - results[1].combinedScore, - ); - }); - - it("respects limit parameter", async () => { - for (let i = 0; i < 10; i++) { - const obs = makeObs({ - id: `obs_${i}`, - sessionId: "ses_1", - title: `auth feature ${i}`, - }); - bm25.add(obs); - await kv.set("mem:obs:ses_1", `obs_${i}`, obs); - } - - const hybrid = new HybridSearch(bm25, null, null, kv as never); - const results = await hybrid.search("auth", 3); - expect(results.length).toBe(3); - }); - - it("skips observations not found in KV", async () => { - const obs = makeObs({ id: "obs_missing", sessionId: "ses_1" }); - bm25.add(obs); - - const hybrid = new HybridSearch(bm25, null, null, kv as never); - const results = await hybrid.search("auth"); - expect(results).toEqual([]); - }); - - it("falls back to KV.memories when an indexed entry is a saved memory (#265)", async () => { - // mem::remember writes to KV.memories under the synthetic sessionId - // "memory" — the BM25 index sees that synthetic sessionId, but - // KV.observations("memory") never has anything. - const indexable = makeObs({ - id: "mem_abc", - sessionId: "memory", - title: "Test memory for search", - narrative: "Test memory for search", - concepts: ["test", "search"], - }); - bm25.add(indexable); - - const memory = { - id: "mem_abc", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "fact", - title: "Test memory for search", - content: "Test memory for search", - concepts: ["test", "search"], - files: [], - sessionIds: [], - strength: 7, - version: 1, - isLatest: true, - }; - await kv.set("mem:memories", "mem_abc", memory); - - const hybrid = new HybridSearch(bm25, null, null, kv as never); - const results = await hybrid.search("test memory search"); - - expect(results.length).toBe(1); - expect(results[0].observation.id).toBe("mem_abc"); - expect(results[0].observation.narrative).toBe("Test memory for search"); - expect(results[0].observation.concepts).toEqual(["test", "search"]); - }); -}); diff --git a/test/inbox-deliver.test.ts b/test/inbox-deliver.test.ts deleted file mode 100644 index 69186d58..00000000 --- a/test/inbox-deliver.test.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -// Config gate is toggled per-test via these mocks (env-free, deterministic). -const mockGate = { - enabled: false, - replyLoop: false, - config: null as null | { userId: string; urgentQuestion: boolean }, -}; -vi.mock("../src/config.js", () => ({ - isLarkDeliveryEnabled: () => mockGate.enabled, - isLarkReplyLoopEnabled: () => mockGate.replyLoop, - getLarkConfig: () => mockGate.config, -})); - -import { registerInboxDeliverFunction } from "../src/functions/inbox-deliver.js"; -import { KV } from "../src/state/schema.js"; -import { PENDING_REPLY_KEY } from "../src/functions/lark-reply-consumer.js"; -import type { DeliveryRecord, InboxItem } from "../src/types.js"; -import type { DeliveryOutcome } from "../src/functions/lark-adapter.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - call: async (id: string, payload?: unknown) => { - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeItem(over: Partial = {}): InboxItem { - return { - id: "inbox_test_1", - kind: "question", - body: "要不要加鉴权?", - status: "awaiting", - createdAt: "2026-06-15T10:00:00Z", - ...over, - }; -} - -describe("Inbox delivery primitive (Line D / STEP-D1)", () => { - let sdk: ReturnType; - let kv: ReturnType; - let deliver: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - deliver = vi.fn(async (): Promise => ({ ok: true, messageId: "om_x", urgent: true })); - mockGate.enabled = false; - mockGate.replyLoop = false; - mockGate.config = null; - registerInboxDeliverFunction(sdk as never, kv as never, deliver as never); - }); - - it("rejects payload without item id", async () => { - const r = await sdk.call("mem::inbox-deliver", {}); - expect(r.success).toBe(false); - }); - - it("config gate: delivery disabled → skipped, adapter never called", async () => { - mockGate.enabled = false; - const r = await sdk.call("mem::inbox-deliver", { item: makeItem() }); - expect(r.success).toBe(true); - expect(r.skipped).toBe(true); - expect(deliver).not.toHaveBeenCalled(); - const rec = await kv.get(KV.delivery, "inbox_test_1"); - expect(rec?.status).toBe("skipped"); - }); - - it("config gate: enabled but no lark config → skipped", async () => { - mockGate.enabled = true; - mockGate.config = null; - const r = await sdk.call("mem::inbox-deliver", { item: makeItem() }); - expect(r.skipped).toBe(true); - expect(deliver).not.toHaveBeenCalled(); - }); - - it("enabled + config: calls adapter, writes sent record + audit", async () => { - mockGate.enabled = true; - mockGate.config = { userId: "ou_abc", urgentQuestion: true }; - const r = await sdk.call("mem::inbox-deliver", { item: makeItem() }); - expect(r.success).toBe(true); - expect(deliver).toHaveBeenCalledTimes(1); - const rec = await kv.get(KV.delivery, "inbox_test_1"); - expect(rec?.status).toBe("sent"); - expect(rec?.messageId).toBe("om_x"); - expect(rec?.urgent).toBe(true); - expect(rec?.attempts).toBe(1); - expect(rec?.deliveredAt).toBeTruthy(); - // audit row written - const audits = await kv.list<{ operation: string }>(KV.audit); - expect(audits.some((a) => a.operation === "inbox_delivered")).toBe(true); - }); - - it("dedup: a sent item is not pushed again", async () => { - mockGate.enabled = true; - mockGate.config = { userId: "ou_abc", urgentQuestion: true }; - await sdk.call("mem::inbox-deliver", { item: makeItem() }); - expect(deliver).toHaveBeenCalledTimes(1); - // second call for same id → skip, adapter not called again - const r2 = await sdk.call("mem::inbox-deliver", { item: makeItem() }); - expect(r2.skipped).toBe(true); - expect(deliver).toHaveBeenCalledTimes(1); - }); - - it("failure: records failed + audit, allows retry (attempts increments)", async () => { - mockGate.enabled = true; - mockGate.config = { userId: "ou_abc", urgentQuestion: true }; - deliver.mockResolvedValueOnce({ ok: false, error: "missing_scope" }); - const r = await sdk.call("mem::inbox-deliver", { item: makeItem() }); - expect(r.success).toBe(false); - expect(r.error).toBe("missing_scope"); - let rec = await kv.get(KV.delivery, "inbox_test_1"); - expect(rec?.status).toBe("failed"); - expect(rec?.attempts).toBe(1); - const audits = await kv.list<{ operation: string }>(KV.audit); - expect(audits.some((a) => a.operation === "delivery_failed")).toBe(true); - - // retry: failed row is not deduped; adapter called again, now succeeds - deliver.mockResolvedValueOnce({ ok: true, messageId: "om_y" }); - const r2 = await sdk.call("mem::inbox-deliver", { item: makeItem() }); - expect(r2.success).toBe(true); - expect(deliver).toHaveBeenCalledTimes(2); - rec = await kv.get(KV.delivery, "inbox_test_1"); - expect(rec?.status).toBe("sent"); - expect(rec?.attempts).toBe(2); - }); - - it("briefing delivery records kind in audit", async () => { - mockGate.enabled = true; - mockGate.config = { userId: "ou_abc", urgentQuestion: false }; - deliver.mockResolvedValueOnce({ ok: true, messageId: "om_b", urgent: false }); - await sdk.call("mem::inbox-deliver", { item: makeItem({ id: "inbox_b", kind: "briefing", body: "今天完成了 3 件" }) }); - const rec = await kv.get(KV.delivery, "inbox_b"); - expect(rec?.status).toBe("sent"); - expect(rec?.urgent).toBe(false); - }); - - // --- STEP-D5a: reply-loop pending pointer is set on question delivery --- - - it("reply loop ON: sent question sets the pending-reply pointer", async () => { - mockGate.enabled = true; - mockGate.replyLoop = true; - mockGate.config = { userId: "ou_abc", urgentQuestion: true }; - await sdk.call("mem::inbox-deliver", { item: makeItem({ id: "inbox_q", kind: "question" }) }); - const ptr = await kv.get<{ itemId: string }>(KV.delivery, PENDING_REPLY_KEY); - expect(ptr?.itemId).toBe("inbox_q"); - }); - - it("reply loop ON: sent briefing does NOT set the pointer", async () => { - mockGate.enabled = true; - mockGate.replyLoop = true; - mockGate.config = { userId: "ou_abc", urgentQuestion: false }; - deliver.mockResolvedValueOnce({ ok: true, messageId: "om_b", urgent: false }); - await sdk.call("mem::inbox-deliver", { item: makeItem({ id: "inbox_b2", kind: "briefing" }) }); - expect(await kv.get(KV.delivery, PENDING_REPLY_KEY)).toBeNull(); - }); - - it("reply loop OFF: sent question does NOT set the pointer", async () => { - mockGate.enabled = true; - mockGate.replyLoop = false; - mockGate.config = { userId: "ou_abc", urgentQuestion: true }; - await sdk.call("mem::inbox-deliver", { item: makeItem({ id: "inbox_q3", kind: "question" }) }); - expect(await kv.get(KV.delivery, PENDING_REPLY_KEY)).toBeNull(); - }); - - it("failed question delivery does NOT set the pointer", async () => { - mockGate.enabled = true; - mockGate.replyLoop = true; - mockGate.config = { userId: "ou_abc", urgentQuestion: true }; - deliver.mockResolvedValueOnce({ ok: false, error: "boom" }); - await sdk.call("mem::inbox-deliver", { item: makeItem({ id: "inbox_q4", kind: "question" }) }); - expect(await kv.get(KV.delivery, PENDING_REPLY_KEY)).toBeNull(); - }); -}); diff --git a/test/inbox-skill-trigger.test.ts b/test/inbox-skill-trigger.test.ts deleted file mode 100644 index 897bbe1e..00000000 --- a/test/inbox-skill-trigger.test.ts +++ /dev/null @@ -1,276 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerInboxFunction } from "../src/functions/inbox.js"; -import { registerMcpEndpoints } from "../src/mcp/server.js"; -import { registerApiTriggers } from "../src/triggers/api.js"; -import type { InboxItem } from "../src/types.js"; - -// End-to-end scenario harness for STEP-C1.5. The two Agent skills -// (ask-user / organize-todos) never touch the mem::inbox-* functions -// directly: they call the MCP tool memory_inbox_ask / memory_inbox_notify, -// and fall back to POST /agentmemory/inbox/{ask,notify} when MCP is down. -// inbox.test.ts only exercises the innermost functions, so this file proves -// the two real skill entry points actually land an item in the inbox and -// that a user opening their workbench (inbox-list) sees it. - -type RegisteredHandler = (input: unknown) => unknown; - -interface TriggerRoute { - method: string; - path: string; - functionId: string; -} - -function makeHarness() { - const store = new Map>(); - const kv = { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; - - const functions = new Map(); - const routes: TriggerRoute[] = []; - const sdk = { - registerFunction: (idOrOpts: string | { id: string }, handler: RegisteredHandler) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: (t: { - type: string; - function_id: string; - config?: { api_path?: string; http_method?: string }; - }) => { - if (t.type === "http" && t.config?.api_path) { - routes.push({ - method: (t.config.http_method ?? "GET").toUpperCase(), - path: t.config.api_path, - functionId: t.function_id, - }); - } - }, - // The handlers call sdk.trigger({ function_id, payload }) to reach the - // underlying mem::inbox-* function; route it through the same registry. - trigger: async ({ function_id, payload }: { function_id: string; payload?: unknown }) => { - const fn = functions.get(function_id); - if (!fn) throw new Error(`No function: ${function_id}`); - return fn(payload); - }, - }; - - registerInboxFunction(sdk as never, kv as never); - registerMcpEndpoints(sdk as never, kv as never); - registerApiTriggers(sdk as never, kv as never); - - return { sdk, kv, functions, routes }; -} - -describe("STEP-C1.5 skill → inbox end-to-end", () => { - let h: ReturnType; - - beforeEach(() => { - h = makeHarness(); - }); - - // --- Primary path: the MCP tool the skills call first. --- - - function callMcpTool(name: string, args: Record) { - const handler = h.functions.get("mcp::tools::call"); - if (!handler) throw new Error("mcp::tools::call not registered"); - return handler({ body: { name, arguments: args } }) as Promise<{ - status_code: number; - body: { content?: { type: string; text: string }[]; error?: string }; - }>; - } - - function parseToolItem(res: { - body: { content?: { type: string; text: string }[] }; - }): { success: boolean; item: InboxItem } { - const text = res.body.content?.[0]?.text; - if (!text) throw new Error("tool returned no content"); - return JSON.parse(text); - } - - it("ask-user via memory_inbox_ask lands an awaiting question the user can see", async () => { - // Scenario: Agent is blocked on a destructive decision, user is away. - const res = await callMcpTool("memory_inbox_ask", { - body: "`/admin/*` 路由要不要也加鉴权?我改完了 `/api/*`。", - fromAgent: "auth-refactor", - project: "/repo/agentmemory", - sourceObservationIds: ["obs_123", "obs_456"], - }); - expect(res.status_code).toBe(200); - const { success, item } = parseToolItem(res); - expect(success).toBe(true); - expect(item.kind).toBe("question"); - expect(item.status).toBe("awaiting"); - expect(item.fromAgent).toBe("auth-refactor"); - expect(item.project).toBe("/repo/agentmemory"); - expect(item.sourceObservationIds).toEqual(["obs_123", "obs_456"]); - - // User opens their workbench → the question is there. - const list = await h.functions.get("mem::inbox-list")!({ status: "awaiting", kind: "question" }); - expect((list as { items: InboxItem[] }).items.some((i) => i.id === item.id)).toBe(true); - }); - - it("organize-todos via memory_inbox_notify lands an awaiting briefing the user can see", async () => { - // Scenario: a multi-step task reached a natural stopping point. - const res = await callMcpTool("memory_inbox_notify", { - body: "今天跟进了 C1.5:✅ 两个 skill 已开 PR;⏳ C2 未动。", - fromAgent: "line-c-c1.5", - project: "/repo/agentmemory", - }); - expect(res.status_code).toBe(200); - const { success, item } = parseToolItem(res); - expect(success).toBe(true); - expect(item.kind).toBe("briefing"); - expect(item.status).toBe("awaiting"); - - const list = await h.functions.get("mem::inbox-list")!({ kind: "briefing" }); - expect((list as { items: InboxItem[] }).items.some((i) => i.id === item.id)).toBe(true); - }); - - it("memory_inbox_ask rejects an empty body (skill must supply a real question)", async () => { - const res = await callMcpTool("memory_inbox_ask", { body: " " }); - expect(res.status_code).toBe(400); - expect(res.body.error).toMatch(/body is required/); - const list = await h.functions.get("mem::inbox-list")!({}); - expect((list as { items: InboxItem[] }).items.length).toBe(0); - }); - - // --- Fallback path: skills POST to the REST endpoint when MCP is down. --- - - function callRest(method: string, path: string, body?: unknown, query?: Record) { - const route = h.routes.find((r) => r.path === path && r.method === method.toUpperCase()); - if (!route) throw new Error(`no route for ${method} ${path}`); - const handler = h.functions.get(route.functionId); - if (!handler) throw new Error(`no handler for ${route.functionId}`); - return handler({ body, query_params: query ?? {}, headers: {} }) as Promise<{ - status_code: number; - body: { success?: boolean; item?: InboxItem; items?: InboxItem[]; error?: string }; - }>; - } - - it("ask-user fallback POST /agentmemory/inbox/ask lands the question", async () => { - const res = await callRest("POST", "/agentmemory/inbox/ask", { - body: "删还是修这两个失败测试?", - fromAgent: "ci-triage", - }); - expect(res.status_code).toBe(201); - expect(res.body.success).toBe(true); - expect(res.body.item?.kind).toBe("question"); - - const list = await callRest("GET", "/agentmemory/inbox", undefined, { status: "awaiting" }); - expect(list.body.items?.some((i) => i.id === res.body.item?.id)).toBe(true); - }); - - it("organize-todos fallback POST /agentmemory/inbox/notify lands the briefing", async () => { - const res = await callRest("POST", "/agentmemory/inbox/notify", { - body: "批量修复完成,3 处已改。", - fromAgent: "batch-fix", - }); - expect(res.status_code).toBe(201); - expect(res.body.item?.kind).toBe("briefing"); - - const list = await callRest("GET", "/agentmemory/inbox", undefined, { kind: "briefing" }); - expect(list.body.items?.some((i) => i.id === res.body.item?.id)).toBe(true); - }); - - it("fallback POST rejects an empty body before reaching the inbox", async () => { - const res = await callRest("POST", "/agentmemory/inbox/ask", { body: "" }); - expect(res.status_code).toBe(400); - const list = await callRest("GET", "/agentmemory/inbox", undefined, {}); - expect(list.body.items?.length).toBe(0); - }); - - it("fallback POST rejects a whitespace-only body with 400, not 201", async () => { - // Regression: the API layer used to only check presence (!req.body.body), - // so " " passed the 201 path while the function returned {success:false}. - const ask = await callRest("POST", "/agentmemory/inbox/ask", { body: " " }); - expect(ask.status_code).toBe(400); - const notify = await callRest("POST", "/agentmemory/inbox/notify", { body: "\n\t " }); - expect(notify.status_code).toBe(400); - const list = await callRest("GET", "/agentmemory/inbox", undefined, {}); - expect(list.body.items?.length).toBe(0); - }); - - it("write endpoints whitelist fields — unknown keys never reach the item", async () => { - const res = await callRest("POST", "/agentmemory/inbox/ask", { - body: "real question", - fromAgent: "agent-x", - status: "answered", - id: "attacker-controlled", - answer: "injected", - } as Record); - expect(res.status_code).toBe(201); - const item = res.body.item as InboxItem & { answer?: string }; - expect(item.fromAgent).toBe("agent-x"); - // Whitelisted away: forged status/id/answer must not survive. - expect(item.status).toBe("awaiting"); - expect(item.id).not.toBe("attacker-controlled"); - expect(item.answer).toBeUndefined(); - }); - - // --- Both entry points feed the same single-user inbox. --- - - it("MCP and REST writes converge in one inbox, newest first", async () => { - await callMcpTool("memory_inbox_ask", { body: "first via MCP" }); - await new Promise((r) => setTimeout(r, 2)); - await callRest("POST", "/agentmemory/inbox/notify", { body: "second via REST" }); - - const list = await callRest("GET", "/agentmemory/inbox", undefined, {}); - const items = list.body.items ?? []; - expect(items.length).toBe(2); - // inbox-list sorts by createdAt desc; the REST write is newest. - expect(items[0]?.body).toBe("second via REST"); - expect(items[1]?.body).toBe("first via MCP"); - }); - - // --- STEP-D4: GET /inbox joins the mem:delivery ledger onto each item. --- - - it("inbox-list joins delivery status onto the matching item (read-only)", async () => { - const res = await callRest("POST", "/agentmemory/inbox/ask", { body: "要不要加鉴权?" }); - const id = res.body.item!.id; - // Simulate the delivery primitive having recorded a sent push. - await h.kv.set("mem:delivery", id, { - id, channel: "lark", status: "sent", messageId: "om_abc", - urgent: false, attempts: 1, createdAt: "2026-06-15T10:00:00Z", - deliveredAt: "2026-06-15T10:00:05Z", - }); - - const list = await callRest("GET", "/agentmemory/inbox", undefined, {}); - const item = (list.body.items ?? []).find((i) => i.id === id) as - InboxItem & { delivery?: { channel: string; status: string; messageId?: string } }; - expect(item.delivery).toBeTruthy(); - expect(item.delivery!.status).toBe("sent"); - expect(item.delivery!.channel).toBe("lark"); - expect(item.delivery!.messageId).toBe("om_abc"); - // The persisted inbox item itself is untouched (no delivery field in KV). - const stored = await h.kv.get("mem:inbox", id); - expect(stored!.delivery).toBeUndefined(); - }); - - it("items without a delivery record have no delivery field", async () => { - const res = await callRest("POST", "/agentmemory/inbox/ask", { body: "没投递的问题" }); - const id = res.body.item!.id; - const list = await callRest("GET", "/agentmemory/inbox", undefined, {}); - const item = (list.body.items ?? []).find((i) => i.id === id) as - InboxItem & { delivery?: unknown }; - expect(item.delivery).toBeUndefined(); - }); -}); diff --git a/test/inbox.test.ts b/test/inbox.test.ts deleted file mode 100644 index 752e71d5..00000000 --- a/test/inbox.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -// Delivery config gate (Line D / STEP-D3). Default OFF so the Line C inbox -// behavior — and the 9 tests below — are unaffected. Per-test toggle via -// mockGate.enabled. Only isLarkDeliveryEnabled is imported by inbox.ts. -const mockGate = { enabled: false }; -vi.mock("../src/config.js", () => ({ - isLarkDeliveryEnabled: () => mockGate.enabled, -})); - -import { registerInboxFunction } from "../src/functions/inbox.js"; -import type { InboxItem } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - // Real iii-sdk has no triggerVoid; fire-and-forget is - // trigger({ function_id, payload, action }). Mock trigger and let tests - // assert against it. Returns a resolved promise so the no-await dispatch - // and its .catch() are exercised. - const trigger = vi.fn(async () => ({ ok: true })); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger, - call: async (id: string, payload?: unknown) => { - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("Inbox Functions (Line C)", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - mockGate.enabled = false; - registerInboxFunction(sdk as never, kv as never); - }); - - it("inbox-ask creates an awaiting question", async () => { - const r = await sdk.call("mem::inbox-ask", { body: "删还是修这两个测试?" }); - expect(r.success).toBe(true); - expect(r.item.kind).toBe("question"); - expect(r.item.status).toBe("awaiting"); - expect(r.item.id).toMatch(/^inbox_/); - }); - - it("inbox-ask rejects empty body", async () => { - const r = await sdk.call("mem::inbox-ask", { body: " " }); - expect(r.success).toBe(false); - }); - - it("inbox-notify creates an awaiting briefing", async () => { - const r = await sdk.call("mem::inbox-notify", { body: "今天跟进了 3 件" }); - expect(r.success).toBe(true); - expect(r.item.kind).toBe("briefing"); - expect(r.item.status).toBe("awaiting"); - }); - - it("inbox-list returns items WITHOUT requiring agentId, filters by status/kind", async () => { - await sdk.call("mem::inbox-ask", { body: "q1" }); - await sdk.call("mem::inbox-notify", { body: "b1" }); - const all = await sdk.call("mem::inbox-list", {}); - expect(all.success).toBe(true); - expect(all.items.length).toBe(2); - const onlyQ = await sdk.call("mem::inbox-list", { kind: "question" }); - expect(onlyQ.items.length).toBe(1); - expect(onlyQ.items[0].kind).toBe("question"); - const awaiting = await sdk.call("mem::inbox-list", { status: "awaiting" }); - expect(awaiting.items.length).toBe(2); - }); - - it("inbox-answer flips status to answered and stores answer", async () => { - const asked = await sdk.call("mem::inbox-ask", { body: "q" }); - const r = await sdk.call("mem::inbox-answer", { id: asked.item.id, answer: "改" }); - expect(r.success).toBe(true); - expect(r.item.status).toBe("answered"); - expect(r.item.answer).toBe("改"); - expect(r.item.answeredAt).toBeTruthy(); - // no longer in awaiting list - const awaiting = await sdk.call("mem::inbox-list", { status: "awaiting" }); - expect(awaiting.items.find((i: InboxItem) => i.id === asked.item.id)).toBeUndefined(); - }); - - it("inbox-answer with empty answer = ack (briefing read)", async () => { - const b = await sdk.call("mem::inbox-notify", { body: "b" }); - const r = await sdk.call("mem::inbox-answer", { id: b.item.id }); - expect(r.item.status).toBe("answered"); - expect(r.item.answer).toBeUndefined(); - }); - - it("inbox-dismiss flips status to dismissed", async () => { - const asked = await sdk.call("mem::inbox-ask", { body: "q" }); - const r = await sdk.call("mem::inbox-dismiss", { id: asked.item.id }); - expect(r.success).toBe(true); - expect(r.item.status).toBe("dismissed"); - }); - - it("answer/dismiss on missing id returns error", async () => { - expect((await sdk.call("mem::inbox-answer", { id: "nope" })).success).toBe(false); - expect((await sdk.call("mem::inbox-dismiss", { id: "nope" })).success).toBe(false); - }); - - it("expired items are filtered from list", async () => { - await sdk.call("mem::inbox-ask", { body: "expired", expiresInMs: -1000 }); - const all = await sdk.call("mem::inbox-list", {}); - expect(all.items.length).toBe(0); - }); - - // Line D / STEP-D3 — fire-and-forget delivery dispatch on the write path. - describe("delivery dispatch (STEP-D3)", () => { - it("delivery OFF (default): ask/notify do NOT trigger deliver", async () => { - await sdk.call("mem::inbox-ask", { body: "q" }); - await sdk.call("mem::inbox-notify", { body: "b" }); - expect(sdk.trigger).not.toHaveBeenCalled(); - }); - - it("delivery ON: ask triggers mem::inbox-deliver (fire-and-forget, with item)", async () => { - mockGate.enabled = true; - const r = await sdk.call("mem::inbox-ask", { body: "要不要加鉴权?" }); - expect(r.success).toBe(true); - expect(sdk.trigger).toHaveBeenCalledTimes(1); - const arg = sdk.trigger.mock.calls[0][0]; - expect(arg.function_id).toBe("mem::inbox-deliver"); - expect(arg.payload).toEqual({ item: r.item }); - // fire-and-forget form: an action is supplied (TriggerAction.Void()). - expect(arg.action).toBeTruthy(); - }); - - it("delivery ON: notify triggers mem::inbox-deliver (fire-and-forget, with item)", async () => { - mockGate.enabled = true; - const r = await sdk.call("mem::inbox-notify", { body: "今天完成了 3 件" }); - expect(r.success).toBe(true); - expect(sdk.trigger).toHaveBeenCalledTimes(1); - const arg = sdk.trigger.mock.calls[0][0]; - expect(arg.function_id).toBe("mem::inbox-deliver"); - expect(arg.payload).toEqual({ item: r.item }); - expect(arg.action).toBeTruthy(); - }); - - it("trigger throwing (sync) does NOT break the inbox write (still success)", async () => { - mockGate.enabled = true; - sdk.trigger.mockImplementationOnce(() => { - throw new Error("dispatch boom"); - }); - const r = await sdk.call("mem::inbox-ask", { body: "q" }); - expect(r.success).toBe(true); - expect(r.item.status).toBe("awaiting"); - const stored = await kv.get("mem:inbox", r.item.id); - expect(stored?.id).toBe(r.item.id); - }); - - it("trigger rejecting (async) does NOT break the inbox write (still success)", async () => { - mockGate.enabled = true; - sdk.trigger.mockRejectedValueOnce(new Error("async dispatch boom")); - const r = await sdk.call("mem::inbox-ask", { body: "q" }); - expect(r.success).toBe(true); - expect(r.item.status).toBe("awaiting"); - // allow the swallowed rejection's microtask to flush without surfacing - await Promise.resolve(); - }); - }); -}); diff --git a/test/index-persistence.test.ts b/test/index-persistence.test.ts deleted file mode 100644 index fd60ab24..00000000 --- a/test/index-persistence.test.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import { IndexPersistence } from "../src/state/index-persistence.js"; -import { SearchIndex } from "../src/state/search-index.js"; -import { VectorIndex } from "../src/state/vector-index.js"; -import type { CompressedObservation } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function makeObs( - overrides: Partial = {}, -): CompressedObservation { - return { - id: "obs_1", - sessionId: "ses_1", - timestamp: new Date().toISOString(), - type: "file_edit", - title: "Edit auth middleware", - subtitle: "JWT validation", - facts: ["Added token check"], - narrative: "Modified the auth middleware to validate JWT tokens", - concepts: ["authentication", "jwt"], - files: ["src/middleware/auth.ts"], - importance: 7, - ...overrides, - }; -} - -describe("IndexPersistence", () => { - let kv: ReturnType; - - beforeEach(() => { - vi.useFakeTimers(); - kv = mockKV(); - }); - - afterEach(() => { - vi.useRealTimers(); - }); - - it("saves and loads BM25 index round-trip", async () => { - const bm25 = new SearchIndex(); - bm25.add(makeObs({ id: "obs_1", title: "auth handler" })); - - const persistence = new IndexPersistence(kv as never, bm25, null); - await persistence.save(); - - const loaded = await persistence.load(); - expect(loaded.bm25).not.toBeNull(); - expect(loaded.bm25!.size).toBe(1); - const results = loaded.bm25!.search("auth"); - expect(results.length).toBe(1); - }); - - it("saves and loads vector index round-trip", async () => { - const bm25 = new SearchIndex(); - const vector = new VectorIndex(); - vector.add("obs_1", "ses_1", new Float32Array([0.1, 0.2, 0.3])); - - const persistence = new IndexPersistence(kv as never, bm25, vector); - await persistence.save(); - - const loaded = await persistence.load(); - expect(loaded.vector).not.toBeNull(); - expect(loaded.vector!.size).toBe(1); - }); - - it("scheduleSave debounces multiple calls", async () => { - const bm25 = new SearchIndex(); - const persistence = new IndexPersistence(kv as never, bm25, null); - - persistence.scheduleSave(); - persistence.scheduleSave(); - persistence.scheduleSave(); - - await expect(kv.get("mem:index:bm25", "data")).resolves.toBeNull(); - - vi.advanceTimersByTime(5000); - await vi.runAllTimersAsync(); - - const saved = await kv.get("mem:index:bm25", "data"); - expect(saved).not.toBeNull(); - }); - - it("stop clears the pending timer", async () => { - const bm25 = new SearchIndex(); - bm25.add(makeObs({ id: "obs_1", title: "auth handler" })); - const persistence = new IndexPersistence(kv as never, bm25, null); - - persistence.scheduleSave(); - persistence.stop(); - - vi.advanceTimersByTime(10000); - const saved = await kv.get("mem:index:bm25", "data"); - expect(saved).toBeNull(); - }); - - it("returns null indexes when nothing has been saved", async () => { - const bm25 = new SearchIndex(); - const persistence = new IndexPersistence(kv as never, bm25, null); - - const loaded = await persistence.load(); - expect(loaded.bm25).toBeNull(); - expect(loaded.vector).toBeNull(); - }); - - it("scheduled save swallows kv.set rejection without unhandledRejection (#204)", async () => { - const failingKv = { - ...mockKV(), - set: vi.fn(async () => { - const err = new Error( - "TIMEOUT: invocation timed out after 30000ms", - ) as Error & { code?: string; function_id?: string }; - err.code = "TIMEOUT"; - err.function_id = "state::set"; - throw err; - }), - }; - const bm25 = new SearchIndex(); - bm25.add(makeObs({ id: "obs_1", title: "auth handler" })); - const persistence = new IndexPersistence(failingKv as never, bm25, null); - - let unhandled = false; - const onUnhandled = () => { - unhandled = true; - }; - process.on("unhandledRejection", onUnhandled); - - try { - persistence.scheduleSave(); - vi.advanceTimersByTime(5000); - await vi.runAllTimersAsync(); - // give microtasks a chance to flush - await Promise.resolve(); - expect(failingKv.set).toHaveBeenCalled(); - expect(unhandled).toBe(false); - } finally { - process.off("unhandledRejection", onUnhandled); - } - }); - - it("save() does not throw when kv.set rejects (#204)", async () => { - const failingKv = { - ...mockKV(), - set: vi.fn(async () => { - throw new Error("TIMEOUT"); - }), - }; - const bm25 = new SearchIndex(); - bm25.add(makeObs({ id: "obs_1", title: "auth handler" })); - const persistence = new IndexPersistence(failingKv as never, bm25, null); - - await expect(persistence.save()).resolves.toBeUndefined(); - }); -}); diff --git a/test/infer-memory-projects.test.ts b/test/infer-memory-projects.test.ts deleted file mode 100644 index ddd5e491..00000000 --- a/test/infer-memory-projects.test.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { inferMemoryProjects } from "../src/functions/migrate.js"; -import { KV } from "../src/state/schema.js"; -import type { Memory, Session } from "../src/types.js"; - -function makeMockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function makeMemory(id: string, sessionIds: string[], project?: string): Memory { - return { - id, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "bug", - title: `Memory ${id}`, - content: `Content for ${id}`, - concepts: [], - files: [], - sessionIds, - strength: 5, - version: 1, - isLatest: true, - ...(project !== undefined && { project }), - }; -} - -function makeSession(id: string, project: string): Session { - return { - id, - project, - cwd: `/srv/${project}`, - startedAt: new Date().toISOString(), - status: "completed", - observationCount: 0, - }; -} - -describe("inferMemoryProjects", () => { - it("skips memories that already have a project", async () => { - const kv = makeMockKV(); - await kv.set(KV.memories, "mem_a", makeMemory("mem_a", [], "api")); - - const result = await inferMemoryProjects(kv); - - expect(result.skipped).toBe(1); - expect(result.updated).toBe(0); - expect(result.ambiguous).toBe(0); - - const stored = await kv.get(KV.memories, "mem_a"); - expect(stored?.project).toBe("api"); - }); - - it("marks a memory ambiguous when it has no sessionIds", async () => { - const kv = makeMockKV(); - await kv.set(KV.memories, "mem_a", makeMemory("mem_a", [])); - - const result = await inferMemoryProjects(kv); - - expect(result.ambiguous).toBe(1); - expect(result.updated).toBe(0); - - const stored = await kv.get(KV.memories, "mem_a"); - expect(stored?.project).toBeUndefined(); - }); - - it("marks a memory ambiguous when none of its sessions have a project", async () => { - const kv = makeMockKV(); - const session: Session = { - id: "sess_a", - project: "", - cwd: "/tmp", - startedAt: new Date().toISOString(), - status: "completed", - observationCount: 0, - }; - await kv.set(KV.sessions, "sess_a", session); - await kv.set(KV.memories, "mem_a", makeMemory("mem_a", ["sess_a"])); - - const result = await inferMemoryProjects(kv); - - expect(result.ambiguous).toBe(1); - expect(result.updated).toBe(0); - }); - - it("marks a memory ambiguous when all its sessions are missing from KV", async () => { - const kv = makeMockKV(); - // Memory references sessions that don't exist (e.g. deleted) - await kv.set(KV.memories, "mem_a", makeMemory("mem_a", ["ghost_sess_1", "ghost_sess_2"])); - - const result = await inferMemoryProjects(kv); - - expect(result.ambiguous).toBe(1); - expect(result.updated).toBe(0); - }); - - it("infers project when all sessions belong to the same project", async () => { - const kv = makeMockKV(); - await kv.set(KV.sessions, "sess_1", makeSession("sess_1", "api")); - await kv.set(KV.sessions, "sess_2", makeSession("sess_2", "api")); - await kv.set(KV.memories, "mem_a", makeMemory("mem_a", ["sess_1", "sess_2"])); - - const result = await inferMemoryProjects(kv); - - expect(result.updated).toBe(1); - expect(result.skipped).toBe(0); - expect(result.ambiguous).toBe(0); - - const stored = await kv.get(KV.memories, "mem_a"); - expect(stored?.project).toBe("api"); - }); - - it("infers the majority project when sessions span multiple projects", async () => { - const kv = makeMockKV(); - await kv.set(KV.sessions, "sess_1", makeSession("sess_1", "api")); - await kv.set(KV.sessions, "sess_2", makeSession("sess_2", "api")); - await kv.set(KV.sessions, "sess_3", makeSession("sess_3", "web")); - // api appears 2 times, web 1 time — api wins strict majority - await kv.set(KV.memories, "mem_a", makeMemory("mem_a", ["sess_1", "sess_2", "sess_3"])); - - const result = await inferMemoryProjects(kv); - - expect(result.updated).toBe(1); - const stored = await kv.get(KV.memories, "mem_a"); - expect(stored?.project).toBe("api"); - }); - - it("marks a memory ambiguous when sessions tie across two projects", async () => { - const kv = makeMockKV(); - await kv.set(KV.sessions, "sess_1", makeSession("sess_1", "api")); - await kv.set(KV.sessions, "sess_2", makeSession("sess_2", "web")); - // exact 1-1 tie — no strict majority - await kv.set(KV.memories, "mem_a", makeMemory("mem_a", ["sess_1", "sess_2"])); - - const result = await inferMemoryProjects(kv); - - expect(result.ambiguous).toBe(1); - expect(result.updated).toBe(0); - - const stored = await kv.get(KV.memories, "mem_a"); - expect(stored?.project).toBeUndefined(); - }); - - it("counts correctly but does not write to KV in dry-run mode", async () => { - const kv = makeMockKV(); - await kv.set(KV.sessions, "sess_1", makeSession("sess_1", "api")); - await kv.set(KV.memories, "mem_a", makeMemory("mem_a", ["sess_1"])); - - const result = await inferMemoryProjects(kv, true); - - expect(result.updated).toBe(1); - - // KV must not have been written - const stored = await kv.get(KV.memories, "mem_a"); - expect(stored?.project).toBeUndefined(); - }); - - it("handles a mix of already-scoped, updatable, and ambiguous memories in one pass", async () => { - const kv = makeMockKV(); - await kv.set(KV.sessions, "sess_api", makeSession("sess_api", "api")); - await kv.set(KV.sessions, "sess_web", makeSession("sess_web", "web")); - - // Already scoped - await kv.set(KV.memories, "mem_scoped", makeMemory("mem_scoped", [], "existing")); - // Will be updated - await kv.set(KV.memories, "mem_update", makeMemory("mem_update", ["sess_api"])); - // No sessionIds — ambiguous - await kv.set(KV.memories, "mem_no_sess", makeMemory("mem_no_sess", [])); - // Tie — ambiguous - await kv.set(KV.memories, "mem_tie", makeMemory("mem_tie", ["sess_api", "sess_web"])); - - const result = await inferMemoryProjects(kv); - - expect(result.skipped).toBe(1); - expect(result.updated).toBe(1); - expect(result.ambiguous).toBe(2); - - const updated = await kv.get(KV.memories, "mem_update"); - expect(updated?.project).toBe("api"); - - const scoped = await kv.get(KV.memories, "mem_scoped"); - expect(scoped?.project).toBe("existing"); - - const noSess = await kv.get(KV.memories, "mem_no_sess"); - expect(noSess?.project).toBeUndefined(); - - const tie = await kv.get(KV.memories, "mem_tie"); - expect(tie?.project).toBeUndefined(); - }); - - it("ignores missing sessions when voting but still infers if remainder has majority", async () => { - const kv = makeMockKV(); - await kv.set(KV.sessions, "sess_real", makeSession("sess_real", "api")); - // ghost_sess does not exist in KV — should be silently skipped in voting - await kv.set(KV.memories, "mem_a", makeMemory("mem_a", ["sess_real", "ghost_sess"])); - - const result = await inferMemoryProjects(kv); - - // Only one vote collected (api), which is a strict majority of 1 project out of 1 - expect(result.updated).toBe(1); - const stored = await kv.get(KV.memories, "mem_a"); - expect(stored?.project).toBe("api"); - }); - - it("is idempotent when run twice in succession", async () => { - const kv = makeMockKV(); - await kv.set(KV.sessions, "sess_1", makeSession("sess_1", "api")); - await kv.set(KV.memories, "mem_a", makeMemory("mem_a", ["sess_1"])); - - const first = await inferMemoryProjects(kv); - expect(first.updated).toBe(1); - - const second = await inferMemoryProjects(kv); - expect(second.updated).toBe(0); - expect(second.skipped).toBe(1); - - const stored = await kv.get(KV.memories, "mem_a"); - expect(stored?.project).toBe("api"); - }); -}); diff --git a/test/integration.test.ts b/test/integration.test.ts deleted file mode 100644 index 45a0cf49..00000000 --- a/test/integration.test.ts +++ /dev/null @@ -1,301 +0,0 @@ -import { describe, it, expect, beforeAll, afterAll } from "vitest"; - -const BASE_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111"; -const SECRET = process.env["AGENTMEMORY_SECRET"] || ""; - -const SESSION_ID = `test_${Date.now()}`; -const PROJECT = "/tmp/test-project"; - -function url(path: string): string { - return `${BASE_URL}${path}`; -} - -function authHeaders(): Record { - const headers: Record = { - "Content-Type": "application/json", - }; - if (SECRET) { - headers["Authorization"] = `Bearer ${SECRET}`; - } - return headers; -} - -async function json(res: Response): Promise { - const text = await res.text(); - try { - return JSON.parse(text); - } catch { - return text; - } -} - -describe("agentmemory integration", () => { - beforeAll(async () => { - const res = await fetch(url("/agentmemory/health")).catch(() => null); - if (!res || !res.ok) { - throw new Error( - `agentmemory is not running at ${BASE_URL}. Start it with: npm run build && npm run start:local-memory`, - ); - } - }); - - describe("health", () => { - it("returns ok", async () => { - const res = await fetch(url("/agentmemory/health")); - expect(res.status).toBe(200); - const body = (await json(res)) as { status: string; service: string }; - expect(["ok", "healthy"]).toContain(body.status); - expect(body.service).toBe("agentmemory"); - }); - }); - - describe("session lifecycle", () => { - it("starts a session", async () => { - const res = await fetch(url("/agentmemory/session/start"), { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - sessionId: SESSION_ID, - project: PROJECT, - cwd: PROJECT, - }), - }); - expect(res.status).toBe(200); - const body = (await json(res)) as { - session: { id: string; status: string }; - context: string; - }; - expect(body.session.id).toBe(SESSION_ID); - expect(body.session.status).toBe("active"); - expect(typeof body.context).toBe("string"); - }); - - it("lists sessions including the new one", async () => { - const res = await fetch(url("/agentmemory/sessions")); - expect(res.status).toBe(200); - const body = (await json(res)) as { - sessions: Array<{ id: string }>; - }; - expect(Array.isArray(body.sessions)).toBe(true); - const found = body.sessions.find((s) => s.id === SESSION_ID); - expect(found).toBeDefined(); - }); - - it("ends the session", async () => { - const res = await fetch(url("/agentmemory/session/end"), { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ sessionId: SESSION_ID }), - }); - expect(res.status).toBe(200); - const body = (await json(res)) as { success: boolean }; - expect(body.success).toBe(true); - }); - - it("session is marked completed", async () => { - const res = await fetch(url("/agentmemory/sessions")); - const body = (await json(res)) as { - sessions: Array<{ id: string; status: string; endedAt?: string }>; - }; - const session = body.sessions.find((s) => s.id === SESSION_ID); - expect(session).toBeDefined(); - expect(session!.status).toBe("completed"); - expect(session!.endedAt).toBeDefined(); - }); - }); - - describe("observations", () => { - const OBS_SESSION = `test_obs_${Date.now()}`; - - beforeAll(async () => { - await fetch(url("/agentmemory/session/start"), { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - sessionId: OBS_SESSION, - project: PROJECT, - cwd: PROJECT, - }), - }); - }); - - afterAll(async () => { - await fetch(url("/agentmemory/session/end"), { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ sessionId: OBS_SESSION }), - }); - }); - - it("captures an observation", async () => { - const res = await fetch(url("/agentmemory/observe"), { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "post_tool_use", - sessionId: OBS_SESSION, - project: PROJECT, - cwd: PROJECT, - timestamp: new Date().toISOString(), - data: { - tool: "Edit", - file: "src/auth.ts", - content: "Added JWT token validation middleware", - }, - }), - }); - expect(res.status).toBe(201); - }); - - it("captures a second observation", async () => { - const res = await fetch(url("/agentmemory/observe"), { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - hookType: "post_tool_use", - sessionId: OBS_SESSION, - project: PROJECT, - cwd: PROJECT, - timestamp: new Date().toISOString(), - data: { - tool: "Bash", - command: "npm test", - output: "Tests: 12 passed, 0 failed", - }, - }), - }); - expect(res.status).toBe(201); - }); - - it("lists observations for the session", async () => { - const res = await fetch( - url(`/agentmemory/observations?sessionId=${OBS_SESSION}`), - ); - expect(res.status).toBe(200); - const body = (await json(res)) as { - observations: Array<{ id: string; sessionId: string }>; - }; - expect(Array.isArray(body.observations)).toBe(true); - }); - - it("returns 400 without sessionId", async () => { - const res = await fetch(url("/agentmemory/observations")); - expect(res.status).toBe(400); - const body = (await json(res)) as { error: string }; - expect(body.error).toBe("sessionId required"); - }); - }); - - describe("search", () => { - it("searches observations", async () => { - const res = await fetch(url("/agentmemory/search"), { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ query: "auth", limit: 5 }), - }); - expect(res.status).toBe(200); - const body = await json(res); - expect(body).toBeDefined(); - }); - - it("returns results for empty limit", async () => { - const res = await fetch(url("/agentmemory/search"), { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ query: "test" }), - }); - expect(res.status).toBe(200); - }); - }); - - describe("context", () => { - it("generates context for a project", async () => { - const res = await fetch(url("/agentmemory/context"), { - method: "POST", - headers: authHeaders(), - body: JSON.stringify({ - sessionId: "ctx-test", - project: PROJECT, - }), - }); - expect(res.status).toBe(200); - const body = (await json(res)) as { context: string }; - expect(typeof body.context).toBe("string"); - }); - }); - - describe("viewer", () => { - it("serves the viewer HTML", async () => { - const res = await fetch(url("/agentmemory/viewer"), { - headers: SECRET ? authHeaders() : undefined, - }); - expect(res.status).toBe(200); - const body = await res.text(); - expect(body).toContain("html"); - }); - }); - - describe("dashboard list endpoints", () => { - it("GET /semantic returns { semantic: [...] }", async () => { - const res = await fetch(url("/agentmemory/semantic"), { - headers: SECRET ? authHeaders() : undefined, - }); - expect(res.status).toBe(200); - const body = (await json(res)) as { semantic: unknown[] }; - expect(Array.isArray(body.semantic)).toBe(true); - }); - - it("GET /procedural returns { procedural: [...] }", async () => { - const res = await fetch(url("/agentmemory/procedural"), { - headers: SECRET ? authHeaders() : undefined, - }); - expect(res.status).toBe(200); - const body = (await json(res)) as { procedural: unknown[] }; - expect(Array.isArray(body.procedural)).toBe(true); - }); - - it("GET /relations returns { relations: [...] }", async () => { - const res = await fetch(url("/agentmemory/relations"), { - headers: SECRET ? authHeaders() : undefined, - }); - expect(res.status).toBe(200); - const body = (await json(res)) as { relations: unknown[] }; - expect(Array.isArray(body.relations)).toBe(true); - }); - }); - - describe("auth", () => { - it("health endpoint is always public", async () => { - const res = await fetch(url("/agentmemory/health")); - expect(res.status).toBe(200); - }); - - if (SECRET) { - it("rejects unauthenticated requests", async () => { - const res = await fetch(url("/agentmemory/search"), { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ query: "test" }), - }); - expect(res.status).toBe(401); - }); - - it("rejects wrong bearer token", async () => { - const res = await fetch(url("/agentmemory/search"), { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer wrong-token", - }, - body: JSON.stringify({ query: "test" }), - }); - expect(res.status).toBe(401); - }); - - it("rejects unauthenticated viewer requests on the API port", async () => { - const res = await fetch(url("/agentmemory/viewer")); - expect(res.status).toBe(401); - }); - } - }); -}); diff --git a/test/lark-adapter.test.ts b/test/lark-adapter.test.ts deleted file mode 100644 index faa603ca..00000000 --- a/test/lark-adapter.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { makeLarkAdapter, _internal, type RunResult } from "../src/functions/lark-adapter.js"; -import type { InboxItem } from "../src/types.js"; -import type { LarkConfig } from "../src/config.js"; - -function q(over: Partial = {}): InboxItem { - return { id: "inbox_q1", kind: "question", body: "要不要给 `/admin/*` 加鉴权?", status: "awaiting", fromAgent: "auth", createdAt: "2026-06-15T10:00:00Z", ...over }; -} -function b(over: Partial = {}): InboxItem { - return { id: "inbox_b1", kind: "briefing", body: "今天完成了 3 件", status: "awaiting", fromAgent: "line-d", createdAt: "2026-06-15T10:00:00Z", ...over }; -} -const cfg: LarkConfig = { userId: "ou_target", urgentQuestion: true }; - -const okSend: RunResult = { code: 0, stdout: JSON.stringify({ data: { message_id: "om_123" } }), stderr: "" }; -const okUrgent: RunResult = { code: 0, stdout: JSON.stringify({ data: {} }), stderr: "" }; - -describe("lark adapter (STEP-D2)", () => { - it("question → interactive card argv, then urgent_app via stdin", async () => { - const calls: { args: string[]; input?: string }[] = []; - const runner = vi.fn(async (args: string[], input?: string): Promise => { - calls.push({ args, input }); - return args.includes("urgent_app") ? okUrgent : okSend; - }); - const deliver = makeLarkAdapter(runner); - const out = await deliver(q(), cfg); - - expect(out.ok).toBe(true); - expect(out.messageId).toBe("om_123"); - expect(out.urgent).toBe(true); - - // send call: interactive, --content is a JSON string arg (NOT stdin), no `-` - const send = calls[0]; - expect(send.args).toContain("+messages-send"); - expect(send.args).toContain("--msg-type"); - expect(send.args).toContain("interactive"); - const ci = send.args.indexOf("--content"); - expect(ci).toBeGreaterThan(-1); - expect(send.args[ci + 1]).not.toBe("-"); // content is inline JSON, not stdin - expect(() => JSON.parse(send.args[ci + 1])).not.toThrow(); - expect(send.args).toContain("--idempotency-key"); - expect(send.input).toBeUndefined(); // send does not use stdin - - // urgent call: uses --data - + stdin body with user_id_list - const urgent = calls[1]; - expect(urgent.args).toContain("urgent_app"); - expect(urgent.args).toContain("--data"); - expect(urgent.args).toContain("-"); - expect(JSON.parse(urgent.input!)).toEqual({ user_id_list: ["ou_target"] }); - }); - - it("briefing → --markdown (CLI wraps as post), no urgent call", async () => { - const calls: { args: string[] }[] = []; - const runner = vi.fn(async (args: string[]): Promise => { - calls.push({ args }); - return okSend; - }); - const deliver = makeLarkAdapter(runner); - const out = await deliver(b(), cfg); - - expect(out.ok).toBe(true); - expect(out.urgent).toBe(false); - expect(calls).toHaveLength(1); // no urgent for briefing - expect(calls[0].args).toContain("--markdown"); - expect(calls[0].args).not.toContain("interactive"); - }); - - it("urgent failure degrades to sent-without-urgency (still ok)", async () => { - const runner = vi.fn(async (args: string[]): Promise => { - if (args.includes("urgent_app")) return { code: 1, stdout: "", stderr: JSON.stringify({ error: { message: "missing scope im:message.urgent" } }) }; - return okSend; - }); - const out = await makeLarkAdapter(runner)(q(), cfg); - expect(out.ok).toBe(true); // delivery still succeeded - expect(out.urgent).toBe(false); // just not urgent - expect(out.messageId).toBe("om_123"); - }); - - it("urgentQuestion=false → no urgent call", async () => { - const calls: string[][] = []; - const runner = vi.fn(async (args: string[]): Promise => { calls.push(args); return okSend; }); - const out = await makeLarkAdapter(runner)(q(), { userId: "ou_target", urgentQuestion: false }); - expect(out.ok).toBe(true); - expect(out.urgent).toBe(false); - expect(calls).toHaveLength(1); - }); - - it("send non-zero exit → ok:false with error summary", async () => { - const runner = vi.fn(async (): Promise => ({ code: 1, stdout: "", stderr: JSON.stringify({ error: { type: "validation", message: "bad open_id" } }) })); - const out = await makeLarkAdapter(runner)(q(), cfg); - expect(out.ok).toBe(false); - expect(out.error).toContain("bad open_id"); - }); - - it("send ok but no message_id → ok:false", async () => { - const runner = vi.fn(async (): Promise => ({ code: 0, stdout: JSON.stringify({ data: {} }), stderr: "" })); - const out = await makeLarkAdapter(runner)(q(), cfg); - expect(out.ok).toBe(false); - expect(out.error).toMatch(/no message_id/); - }); - - it("runner throws → ok:false, never propagates", async () => { - const runner = vi.fn(async (): Promise => { throw new Error("ENOENT lark-cli"); }); - const out = await makeLarkAdapter(runner)(q(), cfg); - expect(out.ok).toBe(false); - expect(out.error).toContain("ENOENT"); - }); - - it("errorSummary prefers structured envelope, falls back to stderr text", () => { - expect(_internal.errorSummary({ code: 1, stdout: "", stderr: JSON.stringify({ error: { message: "X" } }) })).toBe("X"); - expect(_internal.errorSummary({ code: 2, stdout: "", stderr: "plain boom" })).toBe("plain boom"); - expect(_internal.errorSummary({ code: 3, stdout: "", stderr: "" })).toContain("code 3"); - }); - - it("question card is valid JSON with title + workbench button url", () => { - const card = JSON.parse(_internal.questionCard(q())); - expect(card.header.title.content).toContain("Agent 在等你回"); - const flat = JSON.stringify(card); - expect(flat).toContain("去工作台回应"); - expect(flat).toContain("#actions"); - }); -}); diff --git a/test/lark-reply-consumer.test.ts b/test/lark-reply-consumer.test.ts deleted file mode 100644 index 6076d753..00000000 --- a/test/lark-reply-consumer.test.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { - parseEventLine, - makeReplyHandler, - setPendingReplyTarget, - getPendingReplyTarget, - clearPendingReplyTarget, - PENDING_REPLY_KEY, -} from "../src/functions/lark-reply-consumer.js"; -import { KV } from "../src/state/schema.js"; - -// STEP-D5a — the reply-loop mapping kernel, with NO real subprocess. We feed -// NDJSON lines straight into makeReplyHandler().handleLine and assert the -// filter → dedup → method-A pointer mapping → inbox-answer pipeline. D5b wires -// the real `lark-cli event consume` stdout into this same handler. - -const USER = "ou_target_user"; - -function mockKV() { - const store = new Map>(); - return { - // Mirror the real iii-engine state::get: a MISSING key resolves to - // `undefined`, not `null`. (The earlier `?? null` mock hid the - // duplicate_event bug where `!== null` dropped every first-seen event.) - get: async (scope: string, key: string): Promise => { - const bucket = store.get(scope); - return bucket && bucket.has(key) ? (bucket.get(key) as T) : (undefined as unknown as T); - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -// p2p text-message event from the target user. Overridable per test. -function evt(over: Partial<{ event_id: string; sender_id: string; chat_type: string; content: string }> = {}) { - return JSON.stringify({ - event_id: over.event_id ?? "evt_1", - sender_id: over.sender_id ?? USER, - chat_type: over.chat_type ?? "p2p", - content: over.content ?? JSON.stringify({ text: "改,和 /api/* 一致" }), - }); -} - -describe("D5a lark reply-consumer kernel", () => { - let kv: ReturnType; - let trigger: ReturnType; - let sdk: { trigger: ReturnType }; - - beforeEach(() => { - kv = mockKV(); - trigger = vi.fn(async () => ({ success: true, item: { id: "x", status: "answered" } })); - sdk = { trigger }; - }); - - function handler() { - return makeReplyHandler({ kv: kv as never, sdk: sdk as never, userId: USER }); - } - - // Seed an awaiting question so the consumer's stale-pointer guard passes. - async function seedQuestion(id: string) { - await kv.set(KV.inbox, id, { - id, kind: "question", body: "q", status: "awaiting", createdAt: "2026-06-15T10:00:00Z", - }); - } - - // --- NDJSON parsing --- - - it("parseEventLine returns null for blank/malformed lines", () => { - expect(parseEventLine("")).toBeNull(); - expect(parseEventLine(" ")).toBeNull(); - expect(parseEventLine("{ not json")).toBeNull(); - expect(parseEventLine("null")).toBeNull(); - expect(parseEventLine("42")).toBeNull(); - }); - - it("parseEventLine reads flat and nested envelopes; unwraps {text}", () => { - const flat = parseEventLine(evt({ content: JSON.stringify({ text: "hi" }) })); - expect(flat).toEqual({ eventId: "evt_1", senderId: USER, chatType: "p2p", content: "hi" }); - - const nested = parseEventLine(JSON.stringify({ - header: { event_id: "evt_n" }, - event: { sender: { sender_id: { open_id: USER } }, message: { chat_type: "p2p", content: JSON.stringify({ text: "yo" }) } }, - })); - expect(nested?.eventId).toBe("evt_n"); - expect(nested?.senderId).toBe(USER); - expect(nested?.content).toBe("yo"); - }); - - it("parseEventLine returns null when a required field is missing", () => { - expect(parseEventLine(JSON.stringify({ event_id: "e", sender_id: USER }))).toBeNull(); // no content - expect(parseEventLine(JSON.stringify({ sender_id: USER, content: "x" }))).toBeNull(); // no event_id - }); - - // --- pointer helpers --- - - it("set/get/clear pending reply target round-trips via mem:delivery reserved key", async () => { - expect(await getPendingReplyTarget(kv as never)).toBeNull(); - await setPendingReplyTarget(kv as never, "inbox_q1"); - expect(await getPendingReplyTarget(kv as never)).toBe("inbox_q1"); - // stored under the reserved key, not as a DeliveryRecord id - expect(await kv.get(KV.delivery, PENDING_REPLY_KEY)).toBeTruthy(); - await clearPendingReplyTarget(kv as never); - expect(await getPendingReplyTarget(kv as never)).toBeNull(); - }); - - it("latest question overwrites the pointer (method A: newest wins)", async () => { - await setPendingReplyTarget(kv as never, "inbox_q1"); - await setPendingReplyTarget(kv as never, "inbox_q2"); - expect(await getPendingReplyTarget(kv as never)).toBe("inbox_q2"); - }); - - // --- reply mapping pipeline --- - - it("reply hitting the pointer answers the question and clears the pointer", async () => { - await seedQuestion("inbox_q1"); - await setPendingReplyTarget(kv as never, "inbox_q1"); - const out = await handler().handleLine(evt({ content: JSON.stringify({ text: "改" }) })); - expect(out.action).toBe("answered"); - expect(out.itemId).toBe("inbox_q1"); - expect(trigger).toHaveBeenCalledTimes(1); - expect(trigger.mock.calls[0][0]).toMatchObject({ - function_id: "mem::inbox-answer", - payload: { id: "inbox_q1", answer: "改" }, - }); - expect(await getPendingReplyTarget(kv as never)).toBeNull(); - }); - - it("first-seen event (missing dedup key → undefined) is NOT treated as duplicate", async () => { - // Regression: real iii-engine state::get returns undefined for a missing - // key. A `!== null` dedup check wrongly flagged every first event as - // duplicate_event and dropped the reply. A brand-new event must answer. - await seedQuestion("inbox_fresh"); - await setPendingReplyTarget(kv as never, "inbox_fresh"); - const out = await handler().handleLine(evt({ event_id: "evt_never_seen" })); - expect(out.action).toBe("answered"); - expect(out.reason).toBeUndefined(); - expect(trigger).toHaveBeenCalledTimes(1); - }); - - it("reply with no pending pointer is ignored (no answer)", async () => { - const out = await handler().handleLine(evt()); - expect(out.action).toBe("ignored"); - expect(out.reason).toBe("no_pending_target"); - expect(trigger).not.toHaveBeenCalled(); - }); - - it("duplicate event_id does not answer twice", async () => { - await seedQuestion("inbox_q1"); - await setPendingReplyTarget(kv as never, "inbox_q1"); - const line = evt({ event_id: "evt_dup" }); - const first = await handler().handleLine(line); - expect(first.action).toBe("answered"); - // pointer cleared, but even with a fresh pointer the dup event must not act - await setPendingReplyTarget(kv as never, "inbox_q2"); - const second = await handler().handleLine(line); - expect(second.action).toBe("ignored"); - expect(second.reason).toBe("duplicate_event"); - expect(trigger).toHaveBeenCalledTimes(1); - expect(await getPendingReplyTarget(kv as never)).toBe("inbox_q2"); // untouched - }); - - it("reply whose pointer target is no longer awaiting clears the stale pointer", async () => { - // user answered the question in the workbench → item is 'answered' - await kv.set(KV.inbox, "inbox_done", { - id: "inbox_done", kind: "question", body: "q", status: "answered", createdAt: "2026-06-15T10:00:00Z", - }); - await setPendingReplyTarget(kv as never, "inbox_done"); - const out = await handler().handleLine(evt({ event_id: "evt_stale" })); - expect(out.action).toBe("ignored"); - expect(out.reason).toBe("target_not_awaiting"); - expect(trigger).not.toHaveBeenCalled(); - expect(await getPendingReplyTarget(kv as never)).toBeNull(); // stale pointer cleared - }); - - it("reply from a non-target user is ignored", async () => { - await setPendingReplyTarget(kv as never, "inbox_q1"); - const out = await handler().handleLine(evt({ sender_id: "ou_someone_else" })); - expect(out.action).toBe("ignored"); - expect(out.reason).toBe("not_target_user"); - expect(trigger).not.toHaveBeenCalled(); - }); - - it("non-p2p (group) message is ignored", async () => { - await setPendingReplyTarget(kv as never, "inbox_q1"); - const out = await handler().handleLine(evt({ chat_type: "group" })); - expect(out.action).toBe("ignored"); - expect(out.reason).toBe("not_p2p"); - expect(trigger).not.toHaveBeenCalled(); - }); - - it("empty/whitespace body is ignored (unparseable)", async () => { - await setPendingReplyTarget(kv as never, "inbox_q1"); - const out = await handler().handleLine(evt({ content: JSON.stringify({ text: " " }) })); - expect(out.action).toBe("ignored"); - expect(out.reason).toBe("unparseable"); - expect(trigger).not.toHaveBeenCalled(); - }); - - it("malformed NDJSON line is ignored, never throws", async () => { - await setPendingReplyTarget(kv as never, "inbox_q1"); - const out = await handler().handleLine("{ broken json "); - expect(out.action).toBe("ignored"); - expect(out.reason).toBe("unparseable"); - expect(trigger).not.toHaveBeenCalled(); - // pointer survives a junk line - expect(await getPendingReplyTarget(kv as never)).toBe("inbox_q1"); - }); - - it("inbox-answer failure: event still marked, pointer kept, no throw", async () => { - await seedQuestion("inbox_gone"); - await setPendingReplyTarget(kv as never, "inbox_gone"); - trigger.mockResolvedValueOnce({ success: false, error: "inbox item not found" }); - const out = await handler().handleLine(evt({ event_id: "evt_fail" })); - expect(out.action).toBe("ignored"); - expect(out.reason).toBe("answer_failed"); - // pointer NOT cleared (answer didn't take) - expect(await getPendingReplyTarget(kv as never)).toBe("inbox_gone"); - }); - - it("sdk.trigger throwing is contained (line skipped, loop survives)", async () => { - await seedQuestion("inbox_q1"); - await setPendingReplyTarget(kv as never, "inbox_q1"); - trigger.mockRejectedValueOnce(new Error("engine down")); - const out = await handler().handleLine(evt({ event_id: "evt_throw" })); - expect(out.action).toBe("ignored"); - expect(out.reason).toBe("handler_error"); - }); -}); diff --git a/test/lark-reply-loop.test.ts b/test/lark-reply-loop.test.ts deleted file mode 100644 index 35b12d2d..00000000 --- a/test/lark-reply-loop.test.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; -import { EventEmitter } from "node:events"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { startLarkReplyConsumer, type SpawnFn } from "../src/functions/lark-reply-loop.js"; -import { setPendingReplyTarget } from "../src/functions/lark-reply-consumer.js"; -import { KV } from "../src/state/schema.js"; - -// STEP-D5b — process lifecycle + NDJSON line framing only. The mapping -// semantics are D5a's and are NOT re-tested here. We inject a fake child -// process so no real lark-cli ever starts. - -const USER = "ou_target_user"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -// A minimal stand-in for ChildProcess with controllable stdout/stderr streams -// and a recording stdin + kill. -function makeFakeChild() { - const stdout = new EventEmitter() as EventEmitter & { setEncoding: (e: string) => void }; - stdout.setEncoding = () => {}; - const stderr = new EventEmitter() as EventEmitter & { setEncoding: (e: string) => void }; - stderr.setEncoding = () => {}; - const child = new EventEmitter() as EventEmitter & { - stdout: typeof stdout; - stderr: typeof stderr; - stdin: { end: ReturnType }; - kill: ReturnType; - }; - child.stdout = stdout; - child.stderr = stderr; - child.stdin = { end: vi.fn() }; - child.kill = vi.fn(); - return child; -} - -function evt(over: Partial<{ event_id: string; sender_id: string; chat_type: string; content: string }> = {}) { - return JSON.stringify({ - event_id: over.event_id ?? "evt_1", - sender_id: over.sender_id ?? USER, - chat_type: over.chat_type ?? "p2p", - content: over.content ?? JSON.stringify({ text: "改" }), - }); -} - -const flush = () => new Promise((r) => setTimeout(r, 0)); - -describe("D5b lark reply loop (process lifecycle + framing)", () => { - let kv: ReturnType; - let trigger: ReturnType; - let sdk: { trigger: ReturnType }; - let child: ReturnType; - let spawnArgs: { command: string; args: string[] } | null; - let spawnFn: SpawnFn; - - beforeEach(() => { - kv = mockKV(); - trigger = vi.fn(async () => ({ success: true, item: { id: "x", status: "answered" } })); - sdk = { trigger }; - child = makeFakeChild(); - spawnArgs = null; - spawnFn = ((command: string, args: string[]) => { - spawnArgs = { command, args }; - return child as never; - }) as SpawnFn; - }); - - async function seedAwaiting(id: string) { - await kv.set(KV.inbox, id, { - id, kind: "question", body: "q", status: "awaiting", createdAt: "2026-06-15T10:00:00Z", - }); - } - - function start() { - return startLarkReplyConsumer({ kv: kv as never, sdk: sdk as never, userId: USER, spawnFn }); - } - - it("spawns lark-cli event consume with the expected argv", () => { - start(); - expect(spawnArgs?.command).toBe("lark-cli"); - expect(spawnArgs?.args).toEqual([ - "event", "consume", "im.message.receive_v1", "--as", "bot", "--quiet", - ]); - }); - - it("feeds each complete NDJSON line to the kernel (handleLine called per line)", async () => { - await seedAwaiting("inbox_q1"); - await setPendingReplyTarget(kv as never, "inbox_q1"); - start(); - // two events across one chunk; second has its own pointer set after first clears - child.stdout.emit("data", evt({ event_id: "e1" }) + "\n"); - await flush(); - await seedAwaiting("inbox_q2"); - await setPendingReplyTarget(kv as never, "inbox_q2"); - child.stdout.emit("data", evt({ event_id: "e2" }) + "\n"); - await flush(); - expect(trigger).toHaveBeenCalledTimes(2); - }); - - it("reassembles a JSON object split across two chunks", async () => { - await seedAwaiting("inbox_q1"); - await setPendingReplyTarget(kv as never, "inbox_q1"); - start(); - const line = evt({ event_id: "split" }); - const half = Math.floor(line.length / 2); - child.stdout.emit("data", line.slice(0, half)); - await flush(); - expect(trigger).not.toHaveBeenCalled(); // no newline yet - child.stdout.emit("data", line.slice(half) + "\n"); - await flush(); - expect(trigger).toHaveBeenCalledTimes(1); - }); - - it("holds a trailing partial line until its newline arrives", async () => { - await seedAwaiting("inbox_q1"); - await setPendingReplyTarget(kv as never, "inbox_q1"); - start(); - // first full line + start of a second - child.stdout.emit("data", evt({ event_id: "a" }) + "\n" + evt({ event_id: "b" }).slice(0, 10)); - await flush(); - expect(trigger).toHaveBeenCalledTimes(1); // only the complete line acted - }); - - it("malformed line does not stop the loop; a later good line still processes", async () => { - await seedAwaiting("inbox_q1"); - await setPendingReplyTarget(kv as never, "inbox_q1"); - start(); - child.stdout.emit("data", "{ broken json \n"); - await flush(); - expect(trigger).not.toHaveBeenCalled(); - child.stdout.emit("data", evt({ event_id: "good" }) + "\n"); - await flush(); - expect(trigger).toHaveBeenCalledTimes(1); - }); - - it("non-zero child exit only warns (no throw, no restart)", () => { - start(); - // emitting exit must not throw - expect(() => child.emit("exit", 1, null)).not.toThrow(); - // spawn was called exactly once — no auto-restart - expect(spawnArgs).toBeTruthy(); - }); - - it("stop() ends stdin and sends SIGTERM (never kill -9), and is idempotent", () => { - const loop = start(); - loop.stop(); - expect(child.stdin.end).toHaveBeenCalledTimes(1); - expect(child.kill).toHaveBeenCalledWith("SIGTERM"); - expect(child.kill).not.toHaveBeenCalledWith("SIGKILL"); - // idempotent: second stop is a no-op - loop.stop(); - expect(child.stdin.end).toHaveBeenCalledTimes(1); - expect(child.kill).toHaveBeenCalledTimes(1); - }); - - it("exit after stop() is treated as expected (still no throw)", () => { - const loop = start(); - loop.stop(); - expect(() => child.emit("exit", null, "SIGTERM")).not.toThrow(); - }); - - it("stderr data does not throw and is summarized (no event body leak path tested)", () => { - start(); - expect(() => child.stderr.emit("data", "[event] ready\n")).not.toThrow(); - }); -}); diff --git a/test/leases.test.ts b/test/leases.test.ts deleted file mode 100644 index 95f74b5b..00000000 --- a/test/leases.test.ts +++ /dev/null @@ -1,400 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerLeasesFunction } from "../src/functions/leases.js"; -import type { Action, Lease } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeAction( - id: string, - status: Action["status"] = "pending", -): Action { - return { - id, - title: `Action ${id}`, - description: `Description for ${id}`, - status, - priority: 5, - createdAt: "2026-02-01T00:00:00Z", - updatedAt: "2026-02-01T00:00:00Z", - createdBy: "agent-setup", - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - }; -} - -describe("Lease Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(async () => { - sdk = mockSdk(); - kv = mockKV(); - registerLeasesFunction(sdk as never, kv as never); - - await kv.set("mem:actions", "act_1", makeAction("act_1", "pending")); - await kv.set("mem:actions", "act_2", makeAction("act_2", "done")); - await kv.set("mem:actions", "act_3", makeAction("act_3", "cancelled")); - await kv.set("mem:actions", "act_4", makeAction("act_4", "pending")); - }); - - describe("mem::lease-acquire", () => { - it("acquires a lease for a valid action", async () => { - const result = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; lease: Lease; renewed: boolean }; - - expect(result.success).toBe(true); - expect(result.lease.actionId).toBe("act_1"); - expect(result.lease.agentId).toBe("agent-a"); - expect(result.lease.status).toBe("active"); - expect(result.renewed).toBe(false); - expect(result.lease.id).toMatch(/^lse_/); - }); - - it("returns error when actionId or agentId is missing", async () => { - const r1 = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "", - })) as { success: boolean; error: string }; - expect(r1.success).toBe(false); - expect(r1.error).toBe("actionId and agentId are required"); - - const r2 = (await sdk.trigger("mem::lease-acquire", { - actionId: "", - agentId: "agent-a", - })) as { success: boolean; error: string }; - expect(r2.success).toBe(false); - expect(r2.error).toBe("actionId and agentId are required"); - }); - - it("returns error for nonexistent action", async () => { - const result = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_nonexistent", - agentId: "agent-a", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("action not found"); - }); - - it("returns error for done action", async () => { - const result = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_2", - agentId: "agent-a", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("action already completed"); - }); - - it("returns error for cancelled action", async () => { - const result = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_3", - agentId: "agent-a", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("action already completed"); - }); - - it("returns existing lease when same agent already holds it", async () => { - const first = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; lease: Lease }; - - const second = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; lease: Lease; renewed: boolean; message: string }; - - expect(second.success).toBe(true); - expect(second.lease.id).toBe(first.lease.id); - expect(second.renewed).toBe(false); - expect(second.message).toBe("Already holding this lease"); - }); - - it("returns conflict error when different agent holds the lease", async () => { - await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - }); - - const result = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-b", - })) as { success: boolean; error: string; heldBy: string; expiresAt: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("action already leased"); - expect(result.heldBy).toBe("agent-a"); - expect(result.expiresAt).toBeDefined(); - }); - - it("sets action status to active after acquire", async () => { - await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - }); - - const action = await kv.get("mem:actions", "act_1"); - expect(action!.status).toBe("active"); - expect(action!.assignedTo).toBe("agent-a"); - }); - }); - - describe("mem::lease-release", () => { - it("releases an active lease", async () => { - await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - }); - - const result = (await sdk.trigger("mem::lease-release", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; released: boolean }; - - expect(result.success).toBe(true); - expect(result.released).toBe(true); - }); - - it("sets action to done when result is provided", async () => { - await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - }); - - await sdk.trigger("mem::lease-release", { - actionId: "act_1", - agentId: "agent-a", - result: "completed successfully", - }); - - const action = await kv.get("mem:actions", "act_1"); - expect(action!.status).toBe("done"); - expect(action!.result).toBe("completed successfully"); - expect(action!.assignedTo).toBeUndefined(); - }); - - it("sets action to pending when no result is provided", async () => { - await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - }); - - await sdk.trigger("mem::lease-release", { - actionId: "act_1", - agentId: "agent-a", - }); - - const action = await kv.get("mem:actions", "act_1"); - expect(action!.status).toBe("pending"); - expect(action!.assignedTo).toBeUndefined(); - }); - - it("returns error when no active lease exists for agent", async () => { - const result = (await sdk.trigger("mem::lease-release", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("no active lease found for this agent"); - }); - - it("returns error when actionId or agentId is missing", async () => { - const result = (await sdk.trigger("mem::lease-release", { - actionId: "", - agentId: "agent-a", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("actionId and agentId are required"); - }); - }); - - describe("mem::lease-renew", () => { - it("renews an active non-expired lease", async () => { - const acquired = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; lease: Lease }; - - const originalExpiry = acquired.lease.expiresAt; - - const result = (await sdk.trigger("mem::lease-renew", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; lease: Lease }; - - expect(result.success).toBe(true); - expect(result.lease.renewedAt).toBeDefined(); - expect( - new Date(result.lease.expiresAt).getTime(), - ).toBeGreaterThanOrEqual(new Date(originalExpiry).getTime()); - }); - - it("returns error when lease is expired", async () => { - const acquired = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; lease: Lease }; - - acquired.lease.expiresAt = new Date(Date.now() - 60000).toISOString(); - await kv.set("mem:leases", acquired.lease.id, acquired.lease); - - const result = (await sdk.trigger("mem::lease-renew", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("no active (non-expired) lease to renew"); - }); - - it("returns error when actionId or agentId is missing", async () => { - const result = (await sdk.trigger("mem::lease-renew", { - actionId: "", - agentId: "agent-a", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("actionId and agentId are required"); - }); - }); - - describe("mem::lease-cleanup", () => { - it("expires active leases past their expiresAt and resets actions to pending", async () => { - const acquired = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; lease: Lease }; - - acquired.lease.expiresAt = new Date(Date.now() - 60000).toISOString(); - await kv.set("mem:leases", acquired.lease.id, acquired.lease); - - const result = (await sdk.trigger("mem::lease-cleanup", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(1); - - const lease = await kv.get("mem:leases", acquired.lease.id); - expect(lease!.status).toBe("expired"); - - const action = await kv.get("mem:actions", "act_1"); - expect(action!.status).toBe("pending"); - expect(action!.assignedTo).toBeUndefined(); - }); - - it("does not expire non-expired active leases", async () => { - await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - }); - - const result = (await sdk.trigger("mem::lease-cleanup", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(0); - - const action = await kv.get("mem:actions", "act_1"); - expect(action!.status).toBe("active"); - }); - - it("handles multiple expired leases across different actions", async () => { - const a1 = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; lease: Lease }; - - const a4 = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_4", - agentId: "agent-b", - })) as { success: boolean; lease: Lease }; - - a1.lease.expiresAt = new Date(Date.now() - 60000).toISOString(); - await kv.set("mem:leases", a1.lease.id, a1.lease); - - a4.lease.expiresAt = new Date(Date.now() - 30000).toISOString(); - await kv.set("mem:leases", a4.lease.id, a4.lease); - - const result = (await sdk.trigger("mem::lease-cleanup", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(2); - }); - - it("does not reset action when action is no longer active", async () => { - const acquired = (await sdk.trigger("mem::lease-acquire", { - actionId: "act_1", - agentId: "agent-a", - })) as { success: boolean; lease: Lease }; - - acquired.lease.expiresAt = new Date(Date.now() - 60000).toISOString(); - await kv.set("mem:leases", acquired.lease.id, acquired.lease); - - const action = await kv.get("mem:actions", "act_1"); - action!.status = "done"; - await kv.set("mem:actions", "act_1", action); - - await sdk.trigger("mem::lease-cleanup", {}); - - const updatedAction = await kv.get("mem:actions", "act_1"); - expect(updatedAction!.status).toBe("done"); - }); - }); -}); diff --git a/test/lessons.test.ts b/test/lessons.test.ts deleted file mode 100644 index 4a55003e..00000000 --- a/test/lessons.test.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerLessonsFunctions } from "../src/functions/lessons.js"; -import type { Lesson } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("Lessons", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerLessonsFunctions(sdk as never, kv as never); - }); - - describe("mem::lesson-save", () => { - it("creates a lesson with default confidence 0.5", async () => { - const result = (await sdk.trigger("mem::lesson-save", { - content: "Always use execFile instead of exec", - context: "Security best practice", - project: "/test", - tags: ["security"], - })) as { success: boolean; action: string; lesson: Lesson }; - - expect(result.success).toBe(true); - expect(result.action).toBe("created"); - expect(result.lesson.confidence).toBe(0.5); - expect(result.lesson.content).toBe("Always use execFile instead of exec"); - expect(result.lesson.source).toBe("manual"); - expect(result.lesson.reinforcements).toBe(0); - }); - - it("accepts custom confidence", async () => { - const result = (await sdk.trigger("mem::lesson-save", { - content: "Test lesson", - confidence: 0.8, - })) as { lesson: Lesson }; - - expect(result.lesson.confidence).toBe(0.8); - }); - - it("clamps invalid confidence to default", async () => { - const result = (await sdk.trigger("mem::lesson-save", { - content: "Bad confidence", - confidence: 5.0, - })) as { lesson: Lesson }; - - expect(result.lesson.confidence).toBe(0.5); - }); - - it("strengthens existing lesson on duplicate content", async () => { - const first = (await sdk.trigger("mem::lesson-save", { - content: "Duplicate lesson", - })) as { action: string; lesson: Lesson }; - - expect(first.action).toBe("created"); - const originalId = first.lesson.id; - - const second = (await sdk.trigger("mem::lesson-save", { - content: "Duplicate lesson", - })) as { action: string; lesson: Lesson }; - - expect(second.action).toBe("strengthened"); - expect(second.lesson.id).toBe(originalId); - expect(second.lesson.reinforcements).toBe(1); - expect(second.lesson.confidence).toBeGreaterThan(0.5); - }); - - it("rejects empty content", async () => { - const result = (await sdk.trigger("mem::lesson-save", { - content: "", - })) as { success: boolean }; - - expect(result.success).toBe(false); - }); - - it("sets crystal source and sourceIds when provided", async () => { - const result = (await sdk.trigger("mem::lesson-save", { - content: "Crystal-derived lesson", - source: "crystal", - sourceIds: ["crys_123"], - confidence: 0.6, - })) as { lesson: Lesson }; - - expect(result.lesson.source).toBe("crystal"); - expect(result.lesson.sourceIds).toEqual(["crys_123"]); - expect(result.lesson.confidence).toBe(0.6); - }); - }); - - describe("mem::lesson-recall", () => { - beforeEach(async () => { - await sdk.trigger("mem::lesson-save", { - content: "Database indexing improves query performance", - project: "/app", - tags: ["database"], - confidence: 0.9, - }); - await sdk.trigger("mem::lesson-save", { - content: "Always validate user input at boundaries", - project: "/app", - tags: ["security"], - confidence: 0.3, - }); - await sdk.trigger("mem::lesson-save", { - content: "Use TypeScript strict mode for type safety", - project: "/other", - tags: ["typescript"], - }); - }); - - it("finds lessons matching query", async () => { - const result = (await sdk.trigger("mem::lesson-recall", { - query: "database performance", - })) as { success: boolean; lessons: Array }; - - expect(result.success).toBe(true); - expect(result.lessons.length).toBeGreaterThan(0); - expect(result.lessons[0].content).toContain("Database indexing"); - }); - - it("filters by project", async () => { - const result = (await sdk.trigger("mem::lesson-recall", { - query: "type safety typescript", - project: "/other", - })) as { lessons: Lesson[] }; - - expect(result.lessons.length).toBe(1); - expect(result.lessons[0].project).toBe("/other"); - }); - - it("filters by minConfidence", async () => { - const result = (await sdk.trigger("mem::lesson-recall", { - query: "validate input", - minConfidence: 0.5, - })) as { lessons: Lesson[] }; - - expect(result.lessons.length).toBe(0); - }); - - it("returns empty for no matches", async () => { - const result = (await sdk.trigger("mem::lesson-recall", { - query: "xyznonexistent", - })) as { lessons: Lesson[] }; - - expect(result.lessons.length).toBe(0); - }); - - it("rejects empty query", async () => { - const result = (await sdk.trigger("mem::lesson-recall", { - query: "", - })) as { success: boolean }; - - expect(result.success).toBe(false); - }); - }); - - describe("mem::lesson-list", () => { - beforeEach(async () => { - await sdk.trigger("mem::lesson-save", { content: "Lesson A", confidence: 0.9, project: "/app" }); - await sdk.trigger("mem::lesson-save", { content: "Lesson B", confidence: 0.3, project: "/app" }); - await sdk.trigger("mem::lesson-save", { content: "Lesson C", confidence: 0.7, source: "crystal" }); - }); - - it("lists all lessons sorted by confidence", async () => { - const result = (await sdk.trigger("mem::lesson-list", {})) as { lessons: Lesson[] }; - - expect(result.lessons.length).toBe(3); - expect(result.lessons[0].confidence).toBe(0.9); - expect(result.lessons[2].confidence).toBe(0.3); - }); - - it("filters by project", async () => { - const result = (await sdk.trigger("mem::lesson-list", { project: "/app" })) as { lessons: Lesson[] }; - expect(result.lessons.length).toBe(2); - }); - - it("filters by source", async () => { - const result = (await sdk.trigger("mem::lesson-list", { source: "crystal" })) as { lessons: Lesson[] }; - expect(result.lessons.length).toBe(1); - }); - - it("filters by minConfidence", async () => { - const result = (await sdk.trigger("mem::lesson-list", { minConfidence: 0.5 })) as { lessons: Lesson[] }; - expect(result.lessons.length).toBe(2); - }); - - it("respects limit", async () => { - const result = (await sdk.trigger("mem::lesson-list", { limit: 1 })) as { lessons: Lesson[] }; - expect(result.lessons.length).toBe(1); - }); - }); - - describe("mem::lesson-strengthen", () => { - it("increases confidence with diminishing returns", async () => { - const saved = (await sdk.trigger("mem::lesson-save", { - content: "Strengthen me", - confidence: 0.5, - })) as { lesson: Lesson }; - - const result = (await sdk.trigger("mem::lesson-strengthen", { - lessonId: saved.lesson.id, - })) as { success: boolean; lesson: Lesson }; - - expect(result.success).toBe(true); - expect(result.lesson.reinforcements).toBe(1); - expect(result.lesson.confidence).toBeCloseTo(0.55, 2); - expect(result.lesson.lastReinforcedAt).toBeDefined(); - }); - - it("caps confidence at 1.0", async () => { - const saved = (await sdk.trigger("mem::lesson-save", { - content: "High confidence", - confidence: 0.95, - })) as { lesson: Lesson }; - - const result = (await sdk.trigger("mem::lesson-strengthen", { - lessonId: saved.lesson.id, - })) as { lesson: Lesson }; - - expect(result.lesson.confidence).toBeLessThanOrEqual(1.0); - }); - - it("fails for missing lessonId", async () => { - const result = (await sdk.trigger("mem::lesson-strengthen", { - lessonId: "nonexistent", - })) as { success: boolean }; - - expect(result.success).toBe(false); - }); - }); - - describe("mem::lesson-decay-sweep", () => { - it("decays old lessons incrementally", async () => { - const saved = (await sdk.trigger("mem::lesson-save", { - content: "Old lesson", - confidence: 0.8, - })) as { lesson: Lesson }; - - const lessons = await kv.list("mem:lessons"); - const lesson = lessons[0]; - lesson.createdAt = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(); - await kv.set("mem:lessons", lesson.id, lesson); - - const result = (await sdk.trigger("mem::lesson-decay-sweep", {})) as { - decayed: number; - softDeleted: number; - }; - - expect(result.decayed).toBe(1); - - const after = await kv.get("mem:lessons", lesson.id); - expect(after!.confidence).toBeLessThan(0.8); - expect(after!.lastDecayedAt).toBeDefined(); - }); - - it("does not decay lessons less than 1 week old", async () => { - await sdk.trigger("mem::lesson-save", { - content: "Recent lesson", - confidence: 0.5, - }); - - const result = (await sdk.trigger("mem::lesson-decay-sweep", {})) as { - decayed: number; - }; - - expect(result.decayed).toBe(0); - }); - - it("soft-deletes low-confidence unreinforced lessons", async () => { - const saved = (await sdk.trigger("mem::lesson-save", { - content: "Weak lesson", - confidence: 0.12, - })) as { lesson: Lesson }; - - const lesson = await kv.get("mem:lessons", saved.lesson.id); - lesson!.createdAt = new Date(Date.now() - 21 * 24 * 60 * 60 * 1000).toISOString(); - await kv.set("mem:lessons", lesson!.id, lesson!); - - const result = (await sdk.trigger("mem::lesson-decay-sweep", {})) as { - softDeleted: number; - }; - - expect(result.softDeleted).toBe(1); - - const after = await kv.get("mem:lessons", saved.lesson.id); - expect(after!.deleted).toBe(true); - }); - - it("uses lastDecayedAt for incremental delta (not full age)", async () => { - const saved = (await sdk.trigger("mem::lesson-save", { - content: "Incremental decay", - confidence: 0.8, - })) as { lesson: Lesson }; - - const lesson = await kv.get("mem:lessons", saved.lesson.id); - lesson!.createdAt = new Date(Date.now() - 28 * 24 * 60 * 60 * 1000).toISOString(); - lesson!.lastDecayedAt = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); - lesson!.confidence = 0.6; - await kv.set("mem:lessons", lesson!.id, lesson!); - - await sdk.trigger("mem::lesson-decay-sweep", {}); - - const after = await kv.get("mem:lessons", saved.lesson.id); - expect(after!.confidence).toBeCloseTo(0.55, 2); - expect(after!.confidence).toBeGreaterThan(0.4); - }); - }); -}); diff --git a/test/llm-extract.test.ts b/test/llm-extract.test.ts new file mode 100644 index 00000000..77b2f98b --- /dev/null +++ b/test/llm-extract.test.ts @@ -0,0 +1,208 @@ +import assert from "node:assert/strict"; +import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; +import test from "node:test"; +import { defaultConfig } from "../src/config.js"; +import { createLlmRunner } from "../src/extract/llm-runner.js"; + +const observation = { + id: "obs-1", + sessionId: "session-1", + source: "browser" as const, + role: "user", + text: "Please add LLM settings UI", + createdAt: "2026-01-01T00:00:00.000Z" +}; + +const assistantObservation = { + id: "obs-2", + sessionId: "session-1", + source: "browser" as const, + role: "assistant", + text: "The settings UI is added; the remaining work is wiring the save action.", + createdAt: "2026-01-01T00:01:00.000Z" +}; + +test("LLM runner reports missing api key before provider call", async () => { + const runner = createLlmRunner(defaultConfig().llm, {}); + assert.deepEqual(await runner([observation]), { ok: false, warning: "llm_config_missing" }); +}); + +test("LLM runner sends OpenAI-compatible request and parses grounded todos", async () => { + const server = await startMockProvider(async (request) => { + assert.equal(request.url, "/v1/chat/completions"); + assert.equal(request.headers.authorization, "Bearer dummy-llm-key-value"); + const payload = await readJson(request); + assert.equal(payload.model, "test/model"); + assert.equal(payload.temperature, 0); + assert.equal(payload.reasoning_effort, "medium"); + assert.deepEqual(payload.response_format, { type: "json_object" }); + const userMessage = payload.messages.find((message: any) => message.role === "user"); + const userPayload = JSON.parse(userMessage.content); + assert.equal(userPayload.blocks[0].sourceObservationId, "obs-1"); + assert.equal(userPayload.taskChains[0].latestAssistantReply, assistantObservation.text); + assert.equal(userPayload.taskChains[0].completionState, "in_progress"); + return jsonResponse({ + choices: [{ + message: { + content: JSON.stringify({ + todos: [{ + title: "Add LLM settings UI", + description: "Wire the save action after the LLM settings UI is added.", + confidence: 0.91, + sourceObservationId: "obs-1", + quote: "Please add LLM settings UI", + dedupeKey: "add-llm-settings-ui" + }] + }) + } + }] + }); + }); + + try { + const runner = createLlmRunner( + { ...defaultConfig().llm, model: "test/model", endpoint: server.url("/v1") }, + { llmApiKey: "dummy-llm-key-value" } + ); + const result = await runner([observation, assistantObservation]); + assert.equal(result.ok, true); + assert.equal(result.ok && result.todos[0].title, "Add LLM settings UI"); + } finally { + await server.close(); + } +}); + +test("LLM runner merges low-information continuation turns into previous task chain", async () => { + const server = await startMockProvider(async (request) => { + const payload = await readJson(request); + const userMessage = payload.messages.find((message: any) => message.role === "user"); + const userPayload = JSON.parse(userMessage.content); + assert.equal(userPayload.taskChains.length, 1); + assert.ok(userPayload.taskChains[0].observationIds.includes("obs-3")); + return jsonResponse({ todos: [] }); + }); + + try { + const runner = createLlmRunner( + { ...defaultConfig().llm, endpoint: server.url("") }, + { llmApiKey: "dummy-llm-key-value" } + ); + const result = await runner([ + observation, + assistantObservation, + { + id: "obs-3", + sessionId: "session-1", + source: "browser" as const, + role: "user", + text: "continue", + createdAt: "2026-01-01T00:02:00.000Z" + }, + { + id: "obs-4", + sessionId: "session-1", + source: "browser" as const, + role: "assistant", + text: "Still remaining: wire the save action.", + createdAt: "2026-01-01T00:03:00.000Z" + } + ]); + assert.deepEqual(result, { ok: true, todos: [] }); + } finally { + await server.close(); + } +}); + +test("LLM runner maps invalid model output to output warning", async () => { + const server = await startMockProvider(async () => jsonResponse({ + choices: [{ message: { content: "not json" } }] + })); + + try { + const runner = createLlmRunner( + { ...defaultConfig().llm, endpoint: server.url("") }, + { llmApiKey: "dummy-llm-key-value" } + ); + assert.deepEqual(await runner([observation]), { + ok: false, + warning: "llm_output_invalid", + reason: "invalid_json", + retryable: true + }); + } finally { + await server.close(); + } +}); + +test("LLM runner preserves HTTP provider failure reasons", async () => { + const server = await startMockProvider(async () => ({ status: 401, body: { error: "bad key" } })); + + try { + const runner = createLlmRunner( + { ...defaultConfig().llm, endpoint: server.url("") }, + { llmApiKey: "dummy-llm-key-value" } + ); + assert.deepEqual(await runner([observation]), { + ok: false, + warning: "llm_provider_failed", + reason: "http_401", + retryable: true + }); + } finally { + await server.close(); + } +}); + +test("LLM runner maps slow provider calls to timeout", async () => { + const server = await startMockProvider(async () => { + await new Promise((resolve) => setTimeout(resolve, 80)); + return jsonResponse({ todos: [] }); + }); + + try { + const runner = createLlmRunner( + { ...defaultConfig().llm, endpoint: server.url(""), timeoutMs: 10 }, + { llmApiKey: "dummy-llm-key-value" } + ); + assert.deepEqual(await runner([observation]), { + ok: false, + warning: "llm_timeout", + reason: "timeout", + retryable: true + }); + } finally { + await server.close(); + } +}); + +function jsonResponse(body: unknown, status = 200) { + return { status, body }; +} + +async function startMockProvider( + handler: (request: IncomingMessage) => Promise<{ status: number; body: unknown }> +) { + const server = createServer(async (request: IncomingMessage, response: ServerResponse) => { + try { + const result = await handler(request); + response.writeHead(result.status, { "content-type": "application/json" }); + response.end(JSON.stringify(result.body)); + } catch (error) { + response.writeHead(500, { "content-type": "application/json" }); + response.end(JSON.stringify({ error: (error as Error).message })); + } + }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + const address = server.address(); + assert.ok(address && typeof address !== "string"); + return { + url: (path: string) => `http://127.0.0.1:${address.port}${path}`, + close: () => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())) + }; +} + +async function readJson(request: IncomingMessage): Promise { + let body = ""; + for await (const chunk of request) body += chunk; + return JSON.parse(body); +} diff --git a/test/mcp-env-placeholder.test.ts b/test/mcp-env-placeholder.test.ts deleted file mode 100644 index 2481cf97..00000000 --- a/test/mcp-env-placeholder.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, it, expect, afterEach, beforeEach } from "vitest"; -import { resolveEnvOrEmpty } from "../src/mcp/rest-proxy.js"; - -const VAR = "AGENTMEMORY_TEST_URL"; - -describe("resolveEnvOrEmpty — guards against literal ${VAR} placeholders", () => { - let original: string | undefined; - - beforeEach(() => { - original = process.env[VAR]; - delete process.env[VAR]; - }); - - afterEach(() => { - if (original === undefined) delete process.env[VAR]; - else process.env[VAR] = original; - }); - - it("returns '' when env var is unset", () => { - expect(resolveEnvOrEmpty(VAR)).toBe(""); - }); - - it("returns '' when env var is empty string", () => { - process.env[VAR] = ""; - expect(resolveEnvOrEmpty(VAR)).toBe(""); - }); - - it("returns '' when env var is literal ${VAR} placeholder", () => { - process.env[VAR] = "${AGENTMEMORY_TEST_URL}"; - expect(resolveEnvOrEmpty(VAR)).toBe(""); - }); - - it("returns '' when env var is a different literal placeholder", () => { - process.env[VAR] = "${SOME_OTHER_VAR}"; - expect(resolveEnvOrEmpty(VAR)).toBe(""); - }); - - it("preserves a real URL value", () => { - process.env[VAR] = "https://memory.prod.example/api"; - expect(resolveEnvOrEmpty(VAR)).toBe("https://memory.prod.example/api"); - }); - - it("preserves a real secret value that happens to contain a $ char", () => { - process.env[VAR] = "secret-with-$dollar"; - expect(resolveEnvOrEmpty(VAR)).toBe("secret-with-$dollar"); - }); - - it("does not treat ${ at start without matching } as placeholder", () => { - process.env[VAR] = "${unclosed"; - expect(resolveEnvOrEmpty(VAR)).toBe("${unclosed"); - }); - - it("does not treat $VAR (no braces) as placeholder", () => { - process.env[VAR] = "$AGENTMEMORY_URL"; - expect(resolveEnvOrEmpty(VAR)).toBe("$AGENTMEMORY_URL"); - }); -}); diff --git a/test/mcp-prompts.test.ts b/test/mcp-prompts.test.ts deleted file mode 100644 index 67ebe71c..00000000 --- a/test/mcp-prompts.test.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerMcpEndpoints } from "../src/mcp/server.js"; -import type { Session, SessionSummary, Memory } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - const triggerOverrides = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - if (triggerOverrides.has(id)) { - return triggerOverrides.get(id)!(payload); - } - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - overrideTrigger: (id: string, handler: Function) => { - triggerOverrides.set(id, handler); - }, - getFunction: (id: string) => functions.get(id), - }; -} - -function makeReq(body?: unknown, headers?: Record) { - return { - body, - headers: headers || {}, - query_params: {}, - }; -} - -describe("MCP Prompts", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerMcpEndpoints(sdk as never, kv as never); - }); - - it("lists 3 prompts", async () => { - const fn = sdk.getFunction("mcp::prompts::list")!; - const result = (await fn(makeReq())) as { - status_code: number; - body: { prompts: unknown[] }; - }; - - expect(result.status_code).toBe(200); - expect(result.body.prompts).toHaveLength(3); - }); - - it("recall_context returns messages with search results", async () => { - sdk.overrideTrigger("mem::search", async () => ({ - results: [{ observation: { title: "Found something" } }], - })); - - const mem: Memory = { - id: "mem_1", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "pattern", - title: "Auth pattern", - content: "Always use JWT", - concepts: ["auth"], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: true, - }; - await kv.set("mem:memories", "mem_1", mem); - - const fn = sdk.getFunction("mcp::prompts::get")!; - const result = (await fn( - makeReq({ - name: "recall_context", - arguments: { task_description: "implement auth" }, - }), - )) as { - status_code: number; - body: { messages: Array<{ role: string; content: { text: string } }> }; - }; - - expect(result.status_code).toBe(200); - expect(result.body.messages).toHaveLength(1); - expect(result.body.messages[0].role).toBe("user"); - expect(result.body.messages[0].content.text).toContain("implement auth"); - }); - - it("session_handoff returns session data", async () => { - const session: Session = { - id: "ses_1", - project: "/test", - cwd: "/test", - startedAt: "2026-02-01T00:00:00Z", - status: "completed", - observationCount: 10, - }; - await kv.set("mem:sessions", "ses_1", session); - - const summary: SessionSummary = { - sessionId: "ses_1", - project: "/test", - createdAt: "2026-02-01T00:00:00Z", - title: "Auth implementation", - narrative: "Implemented JWT auth", - keyDecisions: ["Used JWT"], - filesModified: ["src/auth.ts"], - concepts: ["auth"], - observationCount: 10, - }; - await kv.set("mem:summaries", "ses_1", summary); - - const fn = sdk.getFunction("mcp::prompts::get")!; - const result = (await fn( - makeReq({ - name: "session_handoff", - arguments: { session_id: "ses_1" }, - }), - )) as { - status_code: number; - body: { messages: Array<{ role: string; content: { text: string } }> }; - }; - - expect(result.status_code).toBe(200); - expect(result.body.messages[0].content.text).toContain("Session Handoff"); - expect(result.body.messages[0].content.text).toContain("ses_1"); - }); - - it("detect_patterns returns analysis", async () => { - sdk.overrideTrigger("mem::patterns", async () => ({ - fileCoOccurrence: [{ files: ["a.ts", "b.ts"], count: 5 }], - })); - - const fn = sdk.getFunction("mcp::prompts::get")!; - const result = (await fn( - makeReq({ - name: "detect_patterns", - arguments: { project: "/myapp" }, - }), - )) as { - status_code: number; - body: { messages: Array<{ role: string; content: { text: string } }> }; - }; - - expect(result.status_code).toBe(200); - expect(result.body.messages[0].content.text).toContain("Pattern Analysis"); - }); - - it("returns 400 for missing required arg", async () => { - const fn = sdk.getFunction("mcp::prompts::get")!; - const result = (await fn( - makeReq({ - name: "recall_context", - arguments: {}, - }), - )) as { status_code: number }; - - expect(result.status_code).toBe(400); - }); - - it("returns 400 for unknown prompt name", async () => { - const fn = sdk.getFunction("mcp::prompts::get")!; - const result = (await fn( - makeReq({ - name: "nonexistent_prompt", - arguments: {}, - }), - )) as { status_code: number }; - - expect(result.status_code).toBe(400); - }); - - it("returns 400 for non-string argument value", async () => { - const fn = sdk.getFunction("mcp::prompts::get")!; - const result = (await fn( - makeReq({ - name: "recall_context", - arguments: { task_description: 42 }, - }), - )) as { status_code: number }; - - expect(result.status_code).toBe(400); - }); -}); diff --git a/test/mcp-resources.test.ts b/test/mcp-resources.test.ts deleted file mode 100644 index 87922e35..00000000 --- a/test/mcp-resources.test.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerMcpEndpoints } from "../src/mcp/server.js"; -import type { Session, SessionSummary, Memory } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - const triggerOverrides = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - if (triggerOverrides.has(id)) { - return triggerOverrides.get(id)!(payload); - } - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - overrideTrigger: (id: string, handler: Function) => { - triggerOverrides.set(id, handler); - }, - getFunction: (id: string) => functions.get(id), - }; -} - -function makeReq(body?: unknown, headers?: Record) { - return { - body, - headers: headers || {}, - query_params: {}, - }; -} - -describe("MCP Resources", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerMcpEndpoints(sdk as never, kv as never); - }); - - it("lists 6 resources", async () => { - const fn = sdk.getFunction("mcp::resources::list")!; - const result = (await fn(makeReq())) as { - status_code: number; - body: { resources: unknown[] }; - }; - - expect(result.status_code).toBe(200); - expect(result.body.resources).toHaveLength(6); - }); - - it("reads agentmemory://status", async () => { - const session: Session = { - id: "ses_1", - project: "/test", - cwd: "/test", - startedAt: new Date().toISOString(), - status: "active", - observationCount: 5, - }; - await kv.set("mem:sessions", "ses_1", session); - - const fn = sdk.getFunction("mcp::resources::read")!; - const result = (await fn(makeReq({ uri: "agentmemory://status" }))) as { - status_code: number; - body: { contents: Array<{ text: string }> }; - }; - - expect(result.status_code).toBe(200); - const data = JSON.parse(result.body.contents[0].text); - expect(data.sessionCount).toBe(1); - }); - - it("reads agentmemory://project/{name}/profile", async () => { - sdk.overrideTrigger("mem::profile", async () => ({ - project: "/myapp", - topConcepts: [{ concept: "auth", frequency: 5 }], - })); - - const fn = sdk.getFunction("mcp::resources::read")!; - const result = (await fn( - makeReq({ uri: "agentmemory://project/myapp/profile" }), - )) as { - status_code: number; - body: { contents: Array<{ text: string }> }; - }; - - expect(result.status_code).toBe(200); - const data = JSON.parse(result.body.contents[0].text); - expect(data.project).toBe("/myapp"); - }); - - it("reads agentmemory://project/{name}/recent with sorted summaries", async () => { - const summaries: SessionSummary[] = [ - { - sessionId: "ses_1", - project: "myapp", - createdAt: "2026-01-01T00:00:00Z", - title: "Old session", - narrative: "old", - keyDecisions: [], - filesModified: [], - concepts: [], - observationCount: 1, - }, - { - sessionId: "ses_2", - project: "myapp", - createdAt: "2026-02-01T00:00:00Z", - title: "New session", - narrative: "new", - keyDecisions: [], - filesModified: [], - concepts: [], - observationCount: 2, - }, - { - sessionId: "ses_3", - project: "other", - createdAt: "2026-02-15T00:00:00Z", - title: "Other project", - narrative: "other", - keyDecisions: [], - filesModified: [], - concepts: [], - observationCount: 3, - }, - ]; - for (const s of summaries) { - await kv.set("mem:summaries", s.sessionId, s); - } - - const fn = sdk.getFunction("mcp::resources::read")!; - const result = (await fn( - makeReq({ uri: "agentmemory://project/myapp/recent" }), - )) as { - status_code: number; - body: { contents: Array<{ text: string }> }; - }; - - expect(result.status_code).toBe(200); - const data = JSON.parse(result.body.contents[0].text); - expect(data).toHaveLength(2); - expect(data[0].sessionId).toBe("ses_2"); - }); - - it("reads agentmemory://memories/latest", async () => { - const memories: Memory[] = [ - { - id: "mem_1", - createdAt: "2026-01-01T00:00:00Z", - updatedAt: "2026-02-01T00:00:00Z", - type: "pattern", - title: "Latest pattern", - content: "content", - concepts: [], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: true, - }, - { - id: "mem_2", - createdAt: "2026-01-01T00:00:00Z", - updatedAt: "2026-01-15T00:00:00Z", - type: "bug", - title: "Old bug", - content: "content", - concepts: [], - files: [], - sessionIds: [], - strength: 3, - version: 2, - isLatest: false, - }, - ]; - for (const m of memories) { - await kv.set("mem:memories", m.id, m); - } - - const fn = sdk.getFunction("mcp::resources::read")!; - const result = (await fn( - makeReq({ uri: "agentmemory://memories/latest" }), - )) as { - status_code: number; - body: { contents: Array<{ text: string }> }; - }; - - expect(result.status_code).toBe(200); - const data = JSON.parse(result.body.contents[0].text); - expect(data).toHaveLength(1); - expect(data[0].id).toBe("mem_1"); - expect(data[0].title).toBe("Latest pattern"); - }); - - it("returns 404 for unknown URI", async () => { - const fn = sdk.getFunction("mcp::resources::read")!; - const result = (await fn( - makeReq({ uri: "agentmemory://nonexistent" }), - )) as { status_code: number }; - - expect(result.status_code).toBe(404); - }); - - it("returns 401 when auth fails", async () => { - const authedSdk = mockSdk(); - const authedKv = mockKV(); - registerMcpEndpoints(authedSdk as never, authedKv as never, "test-secret"); - - const fn = authedSdk.getFunction("mcp::resources::list")!; - const result = (await fn(makeReq())) as { status_code: number }; - expect(result.status_code).toBe(401); - - const authedResult = (await fn( - makeReq(undefined, { authorization: "Bearer test-secret" }), - )) as { status_code: number }; - expect(authedResult.status_code).toBe(200); - }); - - it("handles URI with special characters via decodeURIComponent", async () => { - sdk.overrideTrigger("mem::profile", async (data: any) => ({ - project: data.project, - topConcepts: [], - })); - - const fn = sdk.getFunction("mcp::resources::read")!; - const result = (await fn( - makeReq({ - uri: "agentmemory://project/my%20app%2Fsubdir/profile", - }), - )) as { - status_code: number; - body: { contents: Array<{ text: string }> }; - }; - - expect(result.status_code).toBe(200); - const data = JSON.parse(result.body.contents[0].text); - expect(data.project).toBe("my app/subdir"); - }); - - it("returns 400 for malformed percent-encoding in URI", async () => { - const fn = sdk.getFunction("mcp::resources::read")!; - const result = (await fn( - makeReq({ - uri: "agentmemory://project/bad%E0encoding/profile", - }), - )) as { status_code: number; body: { error: string } }; - - expect(result.status_code).toBe(400); - expect(result.body.error).toContain("percent-encoding"); - }); -}); diff --git a/test/mcp-standalone-proxy.test.ts b/test/mcp-standalone-proxy.test.ts deleted file mode 100644 index dc08a024..00000000 --- a/test/mcp-standalone-proxy.test.ts +++ /dev/null @@ -1,386 +0,0 @@ -import { describe, expect, it, beforeEach, afterEach, vi } from "vitest"; -import { handleToolCall } from "../src/mcp/standalone.js"; -import { resetHandleForTests } from "../src/mcp/rest-proxy.js"; -import { InMemoryKV } from "../src/mcp/in-memory-kv.js"; - -type FetchMock = ReturnType; - -function installFetch(handler: (url: string, init?: RequestInit) => Response): FetchMock { - const fn = vi.fn(async (url: string | URL, init?: RequestInit) => - handler(url.toString(), init), - ); - (globalThis as { fetch: typeof fetch }).fetch = fn as unknown as typeof fetch; - return fn; -} - -const BASE = "http://localhost:3111"; - -describe("@agentmemory/mcp standalone — server proxy (issue #159)", () => { - const originalFetch = globalThis.fetch; - - beforeEach(() => { - resetHandleForTests(); - process.env["AGENTMEMORY_URL"] = BASE; - delete process.env["AGENTMEMORY_SECRET"]; - }); - - afterEach(() => { - resetHandleForTests(); - globalThis.fetch = originalFetch; - delete process.env["AGENTMEMORY_URL"]; - }); - - it("proxies memory_sessions to GET /agentmemory/sessions when server is up", async () => { - const calls: Array<{ url: string; method: string }> = []; - installFetch((url, init) => { - calls.push({ url, method: init?.method || "GET" }); - if (url.endsWith("/agentmemory/livez")) { - return new Response("ok", { status: 200 }); - } - if (url.includes("/agentmemory/sessions")) { - return new Response( - JSON.stringify({ sessions: [{ id: "sess-1", observations: 69 }] }), - { status: 200, headers: { "content-type": "application/json" } }, - ); - } - return new Response("not found", { status: 404 }); - }); - - const res = await handleToolCall("memory_sessions", { limit: 5 }); - const body = JSON.parse(res.content[0].text); - expect(body.sessions).toHaveLength(1); - expect(body.sessions[0].id).toBe("sess-1"); - expect(calls.find((c) => c.url.includes("/sessions"))).toBeDefined(); - }); - - it("proxies memory_smart_search to POST /agentmemory/smart-search", async () => { - installFetch((url, init) => { - if (url.endsWith("/agentmemory/livez")) return new Response("ok", { status: 200 }); - if (url.endsWith("/agentmemory/smart-search")) { - const body = JSON.parse((init?.body as string) || "{}"); - return new Response( - JSON.stringify({ - mode: "compact", - query: body.query, - results: [{ id: "m1", score: 0.9 }], - }), - { status: 200 }, - ); - } - return new Response("", { status: 404 }); - }); - const res = await handleToolCall("memory_smart_search", { query: "auth bug", limit: 5 }); - const body = JSON.parse(res.content[0].text); - expect(body.query).toBe("auth bug"); - expect(body.results[0].id).toBe("m1"); - }); - - it("proxies memory_recall to POST /agentmemory/search and forwards format/token_budget (#507)", async () => { - const calls: Array<{ url: string; body?: unknown }> = []; - installFetch((url, init) => { - if (url.endsWith("/agentmemory/livez")) return new Response("ok", { status: 200 }); - const body = init?.body ? JSON.parse(init.body as string) : undefined; - calls.push({ url, body }); - if (url.endsWith("/agentmemory/search")) { - return new Response( - JSON.stringify({ - mode: "full", - facts: [{ id: "m1" }], - narrative: "n", - concepts: ["c"], - files: ["f"], - }), - { status: 200, headers: { "content-type": "application/json" } }, - ); - } - return new Response("not found", { status: 404 }); - }); - const res = await handleToolCall("memory_recall", { - query: "auth bug", - limit: 5, - format: "full", - token_budget: 800, - }); - const body = JSON.parse(res.content[0].text); - expect(body.mode).toBe("full"); - expect(body.facts[0].id).toBe("m1"); - const searchCall = calls.find((c) => c.url.endsWith("/agentmemory/search")); - expect(searchCall).toBeDefined(); - expect(searchCall?.body).toEqual({ - query: "auth bug", - limit: 5, - format: "full", - token_budget: 800, - }); - expect(calls.find((c) => c.url.endsWith("/agentmemory/smart-search"))).toBeUndefined(); - }); - - it("memory_recall defaults format to 'full' when omitted (#507)", async () => { - let recallBody: Record | undefined; - installFetch((url, init) => { - if (url.endsWith("/agentmemory/livez")) return new Response("ok", { status: 200 }); - if (url.endsWith("/agentmemory/search")) { - recallBody = init?.body ? JSON.parse(init.body as string) : undefined; - return new Response(JSON.stringify({ mode: "full", facts: [] }), { status: 200 }); - } - return new Response("not found", { status: 404 }); - }); - await handleToolCall("memory_recall", { query: "x" }); - expect(recallBody?.["format"]).toBe("full"); - expect(recallBody).not.toHaveProperty("token_budget"); - }); - - it("proxies memory_governance_delete to the DELETE REST endpoint", async () => { - const calls: Array<{ url: string; method: string; body?: unknown }> = []; - installFetch((url, init) => { - const method = init?.method || "GET"; - if (url.endsWith("/agentmemory/livez")) return new Response("ok", { status: 200 }); - calls.push({ - url, - method, - body: init?.body ? JSON.parse(init.body as string) : undefined, - }); - if (url.endsWith("/agentmemory/governance/memories") && method === "DELETE") { - return new Response(JSON.stringify({ success: true, deleted: 2 }), { - status: 200, - headers: { "content-type": "application/json" }, - }); - } - return new Response("method not allowed", { status: 405, statusText: "Method Not Allowed" }); - }); - - const res = await handleToolCall("memory_governance_delete", { - memoryIds: "mem_1, mem_2", - reason: "cleanup stale test data", - }); - - expect(JSON.parse(res.content[0].text)).toEqual({ success: true, deleted: 2 }); - expect(calls).toEqual([ - { - url: `${BASE}/agentmemory/governance/memories`, - method: "DELETE", - body: { - memoryIds: ["mem_1", "mem_2"], - reason: "cleanup stale test data", - }, - }, - ]); - }); - - it("local fallback returns the same shape as proxy for memory_smart_search", async () => { - installFetch(() => { - throw new Error("ECONNREFUSED"); - }); - const localKv = new InMemoryKV(undefined); - await handleToolCall("memory_save", { content: "shape-check entry" }, localKv); - const res = await handleToolCall("memory_smart_search", { query: "shape" }, localKv); - const body = JSON.parse(res.content[0].text); - expect(body).toHaveProperty("mode", "compact"); - expect(Array.isArray(body.results)).toBe(true); - expect(body.results[0].content).toBe("shape-check entry"); - }); - - it("attaches Bearer token on the proxied tool request, not just the probe", async () => { - process.env["AGENTMEMORY_SECRET"] = "s3cret"; - const authByPath = new Map(); - installFetch((url, init) => { - const auth = (init?.headers as Record | undefined)?.[ - "authorization" - ]; - const u = new URL(url); - authByPath.set(u.pathname, auth); - if (url.endsWith("/agentmemory/livez")) return new Response("ok", { status: 200 }); - return new Response(JSON.stringify({ sessions: [] }), { status: 200 }); - }); - await handleToolCall("memory_sessions", {}); - expect(authByPath.get("/agentmemory/livez")).toBe("Bearer s3cret"); - expect(authByPath.get("/agentmemory/sessions")).toBe("Bearer s3cret"); - }); - - it("falls back to local InMemoryKV when server is unreachable", async () => { - installFetch(() => { - throw new Error("ECONNREFUSED"); - }); - const localKv = new InMemoryKV(undefined); - await handleToolCall("memory_save", { content: "local only" }, localKv); - const recall = await handleToolCall("memory_recall", { query: "local" }, localKv); - const out = JSON.parse(recall.content[0].text); - expect(out.mode).toBe("compact"); - expect(out.results).toHaveLength(1); - expect(out.results[0].content).toBe("local only"); - }); - - it("invalidates the handle on proxy failure, so the next call re-probes", async () => { - let probeCount = 0; - let serverUp = true; - installFetch((url) => { - if (url.endsWith("/agentmemory/livez")) { - probeCount++; - return serverUp ? new Response("ok", { status: 200 }) : new Response("", { status: 500 }); - } - return new Response("boom", { status: 500, statusText: "Internal Server Error" }); - }); - const localKv = new InMemoryKV(undefined); - await handleToolCall("memory_save", { content: "first fallback" }, localKv); - expect(probeCount).toBe(1); - serverUp = false; - await handleToolCall("memory_save", { content: "second fallback" }, localKv); - expect(probeCount).toBe(2); - }); - - it("forwards non-essential tools to /agentmemory/mcp/call (#234)", async () => { - const calls: Array<{ url: string; body?: unknown }> = []; - installFetch((url, init) => { - if (url.endsWith("/agentmemory/livez")) { - return new Response("ok", { status: 200 }); - } - if (url.endsWith("/agentmemory/mcp/call")) { - const body = init?.body ? JSON.parse(init.body as string) : null; - calls.push({ url, body }); - return new Response( - JSON.stringify({ - content: [ - { - type: "text", - text: JSON.stringify({ saved: "lesson_xyz" }), - }, - ], - }), - { status: 200, headers: { "content-type": "application/json" } }, - ); - } - return new Response("not found", { status: 404 }); - }); - - const res = await handleToolCall("memory_lesson_save", { - title: "Always pin lockfiles", - content: "...", - }); - const body = JSON.parse(res.content[0].text); - expect(body.saved).toBe("lesson_xyz"); - expect(calls).toHaveLength(1); - expect(calls[0].body).toEqual({ - name: "memory_lesson_save", - arguments: { title: "Always pin lockfiles", content: "..." }, - }); - }); - - it("rejects non-essential tools when no server is reachable (#234)", async () => { - installFetch(() => { - throw new Error("ECONNREFUSED"); - }); - const localKv = new InMemoryKV(undefined); - await expect( - handleToolCall("memory_lesson_save", { title: "x" }, localKv), - ).rejects.toThrow(/Unknown tool: memory_lesson_save/); - }); - - it("does not retry local after a validation error", async () => { - const fetchFn = installFetch((url) => { - if (url.endsWith("/agentmemory/livez")) return new Response("ok", { status: 200 }); - return new Response("{}", { status: 200 }); - }); - const localKv = new InMemoryKV(undefined); - await expect( - handleToolCall("memory_save", { content: "" }, localKv), - ).rejects.toThrow("content is required"); - const remembersCalled = fetchFn.mock.calls.some(([url]) => - String(url).endsWith("/agentmemory/remember"), - ); - expect(remembersCalled).toBe(false); - }); - - it("AGENTMEMORY_FORCE_PROXY=1 skips livez probe and trusts the server", async () => { - process.env["AGENTMEMORY_FORCE_PROXY"] = "1"; - const calls: string[] = []; - installFetch((url, init) => { - calls.push(url); - if (url.endsWith("/agentmemory/livez")) { - throw new Error("probe should be skipped"); - } - if (url.endsWith("/agentmemory/remember")) { - return new Response(JSON.stringify({ id: "m-1", action: "created" }), { - status: 200, - headers: { "content-type": "application/json" }, - }); - } - return new Response("not found", { status: 404 }); - }); - try { - await handleToolCall("memory_save", { content: "force-proxy" }); - expect(calls.some((u) => u.endsWith("/agentmemory/livez"))).toBe(false); - expect(calls.some((u) => u.endsWith("/agentmemory/remember"))).toBe(true); - } finally { - delete process.env["AGENTMEMORY_FORCE_PROXY"]; - } - }); - - it("logs probe failure to stderr so sandboxed clients can diagnose silently dropped tools", async () => { - installFetch((url) => { - if (url.endsWith("/agentmemory/livez")) { - throw new Error("ECONNREFUSED 127.0.0.1:3111"); - } - return new Response("not found", { status: 404 }); - }); - const writes: string[] = []; - const origWrite = process.stderr.write.bind(process.stderr); - process.stderr.write = ((chunk: string | Uint8Array) => { - writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8")); - return true; - }) as typeof process.stderr.write; - try { - const localKv = new InMemoryKV(undefined); - await handleToolCall("memory_save", { content: "diag" }, localKv); - } finally { - process.stderr.write = origWrite; - } - const joined = writes.join(""); - expect(joined).toMatch(/livez probe .* failed/); - expect(joined).toMatch(/AGENTMEMORY_FORCE_PROXY/); - }); - - it("local fallback tools/list returns all 7 IMPLEMENTED_TOOLS regardless of AGENTMEMORY_TOOLS env (#234)", async () => { - const { handleToolsList } = await import("../src/mcp/standalone.js"); - installFetch(() => { - throw new Error("ECONNREFUSED"); - }); - delete process.env["AGENTMEMORY_TOOLS"]; - const before = await handleToolsList(); - const beforeTools = before.tools as Array<{ name: string }>; - expect(beforeTools.map((t) => t.name).sort()).toEqual([ - "memory_audit", - "memory_export", - "memory_governance_delete", - "memory_recall", - "memory_save", - "memory_sessions", - "memory_smart_search", - ]); - expect(beforeTools).toHaveLength(7); - - resetHandleForTests(); - process.env["AGENTMEMORY_TOOLS"] = "core"; - const core = await handleToolsList(); - expect((core.tools as unknown[]).length).toBe(7); - delete process.env["AGENTMEMORY_TOOLS"]; - }); - - it("AGENTMEMORY_PROBE_TIMEOUT_MS overrides the default probe timeout", async () => { - process.env["AGENTMEMORY_PROBE_TIMEOUT_MS"] = "50"; - let probeStarted = 0; - installFetch((url) => { - if (url.endsWith("/agentmemory/livez")) { - probeStarted++; - return new Response("ok", { status: 200 }); - } - return new Response("not found", { status: 404 }); - }); - try { - const localKv = new InMemoryKV(undefined); - await handleToolCall("memory_save", { content: "timeout-knob" }, localKv); - expect(probeStarted).toBe(1); - } finally { - delete process.env["AGENTMEMORY_PROBE_TIMEOUT_MS"]; - } - }); -}); diff --git a/test/mcp-standalone.test.ts b/test/mcp-standalone.test.ts deleted file mode 100644 index b48eade9..00000000 --- a/test/mcp-standalone.test.ts +++ /dev/null @@ -1,452 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("node:fs", () => ({ - existsSync: vi.fn().mockReturnValue(false), - readFileSync: vi.fn(), - writeFileSync: vi.fn(), - mkdirSync: vi.fn(), -})); - -vi.mock("../src/mcp/transport.js", () => ({ - createStdioTransport: vi.fn(() => ({ start: vi.fn(), stop: vi.fn() })), -})); - -vi.mock("../src/config.js", () => ({ - getStandalonePersistPath: vi.fn(() => "/tmp/test-standalone.json"), -})); - -import { - getAllTools, - CORE_TOOLS, - V040_TOOLS, -} from "../src/mcp/tools-registry.js"; -import { InMemoryKV } from "../src/mcp/in-memory-kv.js"; -import { handleToolCall } from "../src/mcp/standalone.js"; -import { - resetHandleForTests, - setLivezProbe, -} from "../src/mcp/rest-proxy.js"; -import { writeFileSync } from "node:fs"; - -// Issue #449: hard-coded fetch() against :3111 in the livez probe was racing -// with vitest's mock setup, making this file the "10-11 pre-existing failures" -// referenced in the last 5 release notes. Stub the probe with an instant -// ok:false response so the shim takes the deterministic InMemoryKV fallback -// path on every test. Guard the real network with a fetch trap so any -// regression that bypasses the DI seam fails loudly instead of timing out. -const instantLocalFallbackProbe = vi.fn(async () => ({ - ok: false, - status: 0, - statusText: "stubbed: forced local fallback", -})); - -const fetchTrap = vi.fn(async (url: unknown) => { - throw new Error( - `unexpected real fetch() call in mcp-standalone.test.ts: ${String(url)} — the livez probe DI stub should have absorbed this`, - ); -}); - -describe("Tools Registry", () => { - it("getAllTools returns all tools with unique names", () => { - const tools = getAllTools(); - expect(tools.length).toBeGreaterThanOrEqual(41); - const names = new Set(tools.map((t) => t.name)); - expect(names.size).toBe(tools.length); - for (const required of [ - "memory_verify", - "memory_lesson_save", - "memory_lesson_recall", - "memory_obsidian_export", - "memory_save", - "memory_recall", - ]) { - expect(tools.some((t) => t.name === required)).toBe(true); - } - }); - - it("CORE_TOOLS has 14 items", () => { - expect(CORE_TOOLS.length).toBe(14); - }); - - it("V040_TOOLS has 8 items", () => { - expect(V040_TOOLS.length).toBe(8); - }); - - it("all tools have required name, description, inputSchema fields", () => { - const tools = getAllTools(); - for (const tool of tools) { - expect(tool.name).toBeDefined(); - expect(typeof tool.name).toBe("string"); - expect(tool.name.length).toBeGreaterThan(0); - expect(tool.description).toBeDefined(); - expect(typeof tool.description).toBe("string"); - expect(tool.inputSchema).toBeDefined(); - expect(tool.inputSchema.type).toBe("object"); - expect(tool.inputSchema.properties).toBeDefined(); - } - }); -}); - -describe("InMemoryKV", () => { - let kv: InMemoryKV; - - beforeEach(() => { - kv = new InMemoryKV(); - }); - - it("get/set/list/delete operations work", async () => { - await kv.set("scope1", "key1", { value: "hello" }); - const result = await kv.get<{ value: string }>("scope1", "key1"); - expect(result).toEqual({ value: "hello" }); - - const list = await kv.list("scope1"); - expect(list.length).toBe(1); - - await kv.delete("scope1", "key1"); - const afterDelete = await kv.get("scope1", "key1"); - expect(afterDelete).toBeNull(); - }); - - it("list returns empty array for unknown scope", async () => { - const result = await kv.list("nonexistent"); - expect(result).toEqual([]); - }); - - it("persist writes JSON", async () => { - const kvWithPersist = new InMemoryKV("/tmp/test-kv.json"); - await kvWithPersist.set("scope1", "key1", { data: "test" }); - kvWithPersist.persist(); - - expect(writeFileSync).toHaveBeenCalledWith( - "/tmp/test-kv.json", - expect.any(String), - "utf-8", - ); - const written = vi.mocked(writeFileSync).mock.calls[0][1] as string; - const parsed = JSON.parse(written); - expect(parsed.scope1.key1).toEqual({ data: "test" }); - }); - - it("set overwrites existing values", async () => { - await kv.set("scope1", "key1", "first"); - await kv.set("scope1", "key1", "second"); - const result = await kv.get("scope1", "key1"); - expect(result).toBe("second"); - const list = await kv.list("scope1"); - expect(list.length).toBe(1); - }); -}); - -describe("handleToolCall", () => { - const originalFetch = globalThis.fetch; - - beforeEach(() => { - vi.mocked(writeFileSync).mockClear(); - instantLocalFallbackProbe.mockClear(); - fetchTrap.mockClear(); - // Order matters: resetHandleForTests() restores the default probe and - // clears the cached handle. Install the stub AFTER the reset so the - // shim's next resolveHandle() call hits the stubbed instant-fail path - // instead of the real 2s AbortController fetch. - resetHandleForTests(); - setLivezProbe(instantLocalFallbackProbe); - (globalThis as { fetch: typeof fetch }).fetch = fetchTrap as unknown as typeof fetch; - }); - - afterEach(() => { - (globalThis as { fetch: typeof fetch }).fetch = originalFetch; - resetHandleForTests(); - }); - - it("livez probe stub is invoked instead of the real fetch (issue #449)", async () => { - const kv = new InMemoryKV(); - await handleToolCall("memory_save", { content: "regression guard" }, kv); - expect(instantLocalFallbackProbe).toHaveBeenCalledTimes(1); - expect(fetchTrap).not.toHaveBeenCalled(); - }); - - it("memory_save persists to disk immediately after saving", async () => { - const kv = new InMemoryKV("/tmp/test-handle.json"); - const result = await handleToolCall( - "memory_save", - { content: "Test memory content" }, - kv, - ); - const parsed = JSON.parse(result.content[0].text); - expect(parsed.saved).toMatch(/^mem_/); - expect(writeFileSync).toHaveBeenCalledWith( - "/tmp/test-handle.json", - expect.any(String), - "utf-8", - ); - }); - - it("memory_save without persist path does not call writeFileSync", async () => { - const kv = new InMemoryKV(); - await handleToolCall("memory_save", { content: "No persist path" }, kv); - expect(writeFileSync).not.toHaveBeenCalled(); - }); - - it("memory_save throws when content is missing", async () => { - const kv = new InMemoryKV(); - await expect( - handleToolCall("memory_save", {}, kv), - ).rejects.toThrow("content is required"); - }); - - it("memory_save rejects non-string content safely (no runtime TypeError)", async () => { - const kv = new InMemoryKV(); - // These would have crashed on .trim() before the type-guard fix. - for (const bogus of [42, {}, [], null, undefined, true]) { - await expect( - handleToolCall("memory_save", { content: bogus }, kv), - ).rejects.toThrow("content is required"); - } - }); - - it("memory_recall returns matching memories", async () => { - const kv = new InMemoryKV(); - await handleToolCall("memory_save", { content: "TypeScript is great" }, kv); - await handleToolCall("memory_save", { content: "Python is also great" }, kv); - const result = await handleToolCall( - "memory_recall", - { query: "typescript" }, - kv, - ); - const parsed = JSON.parse(result.content[0].text); - expect(parsed.results).toHaveLength(1); - expect(parsed.results[0].content).toBe("TypeScript is great"); - }); - - it("memory_save accepts concepts/files as arrays (plugin skill format, #139)", async () => { - const kv = new InMemoryKV(); - const result = await handleToolCall( - "memory_save", - { - content: "Use HMAC for API auth", - concepts: ["hmac", "api-auth", "security"], - files: ["src/auth.ts", "src/middleware.ts"], - }, - kv, - ); - const saved = JSON.parse(result.content[0].text); - const mem = await kv.get<{ concepts: string[]; files: string[] }>( - "mem:memories", - saved.saved, - ); - expect(mem?.concepts).toEqual(["hmac", "api-auth", "security"]); - expect(mem?.files).toEqual(["src/auth.ts", "src/middleware.ts"]); - }); - - it("memory_save still accepts concepts/files as comma-separated strings (legacy)", async () => { - const kv = new InMemoryKV(); - const result = await handleToolCall( - "memory_save", - { - content: "JWT refresh rotation", - concepts: "jwt, refresh, rotation", - files: "src/auth.ts", - }, - kv, - ); - const saved = JSON.parse(result.content[0].text); - const mem = await kv.get<{ concepts: string[]; files: string[] }>( - "mem:memories", - saved.saved, - ); - expect(mem?.concepts).toEqual(["jwt", "refresh", "rotation"]); - expect(mem?.files).toEqual(["src/auth.ts"]); - }); - - it("memory_smart_search falls back to substring match in the standalone shim (#139)", async () => { - const kv = new InMemoryKV(); - await handleToolCall( - "memory_save", - { content: "Use bcrypt for password hashing" }, - kv, - ); - await handleToolCall( - "memory_save", - { content: "Use argon2id for new projects" }, - kv, - ); - const result = await handleToolCall( - "memory_smart_search", - { query: "bcrypt", limit: 5 }, - kv, - ); - const parsed = JSON.parse(result.content[0].text); - expect(parsed.results).toHaveLength(1); - expect(parsed.results[0].content).toBe("Use bcrypt for password hashing"); - }); - - it("memory_smart_search rejects empty query to prevent match-all in forget flow (#139)", async () => { - const kv = new InMemoryKV(); - await handleToolCall("memory_save", { content: "anything" }, kv); - await expect( - handleToolCall("memory_smart_search", {}, kv), - ).rejects.toThrow("query is required"); - await expect( - handleToolCall("memory_smart_search", { query: "" }, kv), - ).rejects.toThrow("query is required"); - await expect( - handleToolCall("memory_smart_search", { query: " " }, kv), - ).rejects.toThrow("query is required"); - }); - - it("memory_smart_search searches files and concepts, not just title/content (#139)", async () => { - const kv = new InMemoryKV(); - await handleToolCall( - "memory_save", - { - content: "generic note", - concepts: ["oauth", "token-rotation"], - files: ["src/auth/refresh.ts"], - }, - kv, - ); - await handleToolCall("memory_save", { content: "unrelated" }, kv); - - // Find by file path - const byFile = JSON.parse( - ( - await handleToolCall( - "memory_smart_search", - { query: "src/auth/refresh.ts" }, - kv, - ) - ).content[0].text, - ); - expect(byFile.results).toHaveLength(1); - expect(byFile.results[0].files).toContain("src/auth/refresh.ts"); - - // Find by concept - const byConcept = JSON.parse( - ( - await handleToolCall( - "memory_smart_search", - { query: "token-rotation" }, - kv, - ) - ).content[0].text, - ); - expect(byConcept.results).toHaveLength(1); - }); - - it("memory_sessions honours the limit arg (#139)", async () => { - const kv = new InMemoryKV(); - for (let i = 0; i < 5; i++) { - await kv.set("mem:sessions", `ses_${i}`, { - id: `ses_${i}`, - project: "demo", - }); - } - const result = await handleToolCall( - "memory_sessions", - { limit: 2 }, - kv, - ); - const parsed = JSON.parse(result.content[0].text); - expect(parsed.sessions).toHaveLength(2); - }); - - it("parseLimit clamps bad/malicious limit values to a safe range", async () => { - const kv = new InMemoryKV(); - for (let i = 0; i < 150; i++) { - await handleToolCall("memory_save", { content: `mem ${i}` }, kv); - } - - // Negative / NaN / Infinity / string / object — all should fall back - // to the default (10) for memory_smart_search. - for (const bogus of [-1, NaN, Infinity, "abc", {}, true]) { - const r = await handleToolCall( - "memory_smart_search", - { query: "mem", limit: bogus }, - kv, - ); - expect(JSON.parse(r.content[0].text).results).toHaveLength(10); - } - - // An absurdly large limit gets clamped to MAX_LIMIT (100). - const huge = await handleToolCall( - "memory_smart_search", - { query: "mem", limit: 99999 }, - kv, - ); - expect(JSON.parse(huge.content[0].text).results).toHaveLength(100); - }); - - it("memory_governance_delete removes memories by id array (#139)", async () => { - const kv = new InMemoryKV(); - const a = JSON.parse( - (await handleToolCall("memory_save", { content: "one" }, kv)).content[0] - .text, - ); - const b = JSON.parse( - (await handleToolCall("memory_save", { content: "two" }, kv)).content[0] - .text, - ); - const c = JSON.parse( - (await handleToolCall("memory_save", { content: "three" }, kv)).content[0] - .text, - ); - const result = await handleToolCall( - "memory_governance_delete", - { memoryIds: [a.saved, c.saved] }, - kv, - ); - const parsed = JSON.parse(result.content[0].text); - expect(parsed.deleted).toBe(2); - expect(parsed.requested).toBe(2); - - const remaining = await kv.list>("mem:memories"); - expect(remaining).toHaveLength(1); - expect((remaining[0] as { id: string }).id).toBe(b.saved); - }); - - it("memory_governance_delete accepts CSV-string memoryIds too", async () => { - const kv = new InMemoryKV(); - const saved = JSON.parse( - (await handleToolCall("memory_save", { content: "x" }, kv)).content[0] - .text, - ); - const result = await handleToolCall( - "memory_governance_delete", - { memoryIds: saved.saved, reason: "test csv" }, - kv, - ); - const parsed = JSON.parse(result.content[0].text); - expect(parsed.deleted).toBe(1); - expect(parsed.reason).toBe("test csv"); - }); - - it("memory_governance_delete throws when memoryIds is missing or empty", async () => { - const kv = new InMemoryKV(); - await expect( - handleToolCall("memory_governance_delete", {}, kv), - ).rejects.toThrow("memoryIds is required"); - await expect( - handleToolCall("memory_governance_delete", { memoryIds: [] }, kv), - ).rejects.toThrow("memoryIds is required"); - }); - - it("memory_governance_delete silently skips unknown ids", async () => { - const kv = new InMemoryKV(); - const saved = JSON.parse( - (await handleToolCall("memory_save", { content: "real" }, kv)).content[0] - .text, - ); - const result = await handleToolCall( - "memory_governance_delete", - { memoryIds: [saved.saved, "mem_does_not_exist"] }, - kv, - ); - const parsed = JSON.parse(result.content[0].text); - expect(parsed.deleted).toBe(1); - expect(parsed.requested).toBe(2); - }); -}); diff --git a/test/mcp-surface-default.test.ts b/test/mcp-surface-default.test.ts deleted file mode 100644 index 07fa235b..00000000 --- a/test/mcp-surface-default.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { readFileSync } from "node:fs"; -import { - getAllTools, - getVisibleTools, -} from "../src/mcp/tools-registry.js"; - -// plugin manifests and README advertise 51 MCP tools. The old -// default was AGENTMEMORY_TOOLS=core which silently capped the surface -// at 8 essentials with no indication the other 43 existed. Default -// flipped to "all"; the lean set is still accessible via -// AGENTMEMORY_TOOLS=core. -describe("MCP tool surface default (#553)", () => { - const ORIG = process.env["AGENTMEMORY_TOOLS"]; - beforeEach(() => { - delete process.env["AGENTMEMORY_TOOLS"]; - }); - afterEach(() => { - if (ORIG === undefined) delete process.env["AGENTMEMORY_TOOLS"]; - else process.env["AGENTMEMORY_TOOLS"] = ORIG; - }); - - it("default returns the full 51-tool surface, matching plugin advertising", () => { - const visible = getVisibleTools(); - const all = getAllTools(); - expect(visible.length).toBe(all.length); - expect(visible.length).toBeGreaterThanOrEqual(48); - }); - - it("AGENTMEMORY_TOOLS=all returns the same full set", () => { - process.env["AGENTMEMORY_TOOLS"] = "all"; - expect(getVisibleTools().length).toBe(getAllTools().length); - }); - - it("AGENTMEMORY_TOOLS=core returns the 8 essential tools", () => { - process.env["AGENTMEMORY_TOOLS"] = "core"; - const names = new Set(getVisibleTools().map((t) => t.name)); - expect(names.size).toBe(8); - for (const t of [ - "memory_save", - "memory_recall", - "memory_consolidate", - "memory_smart_search", - "memory_sessions", - "memory_diagnose", - "memory_lesson_save", - "memory_reflect", - ]) { - expect(names.has(t)).toBe(true); - } - }); - - it("plugin .mcp.json provides default env interpolation so CC parse never fails (#510)", () => { - const raw = readFileSync("plugin/.mcp.json", "utf-8"); - const cfg = JSON.parse(raw) as { - mcpServers: { agentmemory: { env: Record } }; - }; - const env = cfg.mcpServers.agentmemory.env; - // Per Claude Code MCP docs: ${VAR} without a default fails config - // parse when VAR is unset, silently dropping the server. ${VAR:-x} - // form is what unblocks fresh installs that haven't exported - // AGENTMEMORY_URL. - expect(env["AGENTMEMORY_URL"]).toMatch(/\$\{AGENTMEMORY_URL:-/); - expect(env["AGENTMEMORY_SECRET"]).toMatch(/\$\{AGENTMEMORY_SECRET:-/); - expect(env["AGENTMEMORY_TOOLS"]).toMatch(/\$\{AGENTMEMORY_TOOLS:-all\}/); - }); -}); diff --git a/test/mcp-transport.test.ts b/test/mcp-transport.test.ts deleted file mode 100644 index 006ecc9e..00000000 --- a/test/mcp-transport.test.ts +++ /dev/null @@ -1,275 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; -import { - createMessageParser, - formatResponse, - processLine, - type JsonRpcResponse, - type RequestHandler, -} from "../src/mcp/transport.js"; - -function collector() { - const out: JsonRpcResponse[] = []; - const err: string[] = []; - return { - out, - err, - writeOut: (r: JsonRpcResponse) => out.push(r), - writeErr: (m: string) => err.push(m), - }; -} - -const okHandler: RequestHandler = async (method) => ({ method }); - -describe("processLine — request path", () => { - it("emits a response for a request with id", async () => { - const c = collector(); - await processLine( - JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize" }), - okHandler, - c.writeOut, - c.writeErr, - ); - expect(c.out).toHaveLength(1); - expect(c.out[0]).toEqual({ - jsonrpc: "2.0", - id: 1, - result: { method: "initialize" }, - }); - }); - - it("emits an error response when the handler throws on a request", async () => { - const c = collector(); - const throwingHandler: RequestHandler = async () => { - throw new Error("boom"); - }; - await processLine( - JSON.stringify({ jsonrpc: "2.0", id: 7, method: "tools/list" }), - throwingHandler, - c.writeOut, - c.writeErr, - ); - expect(c.out).toHaveLength(1); - expect(c.out[0].id).toBe(7); - expect(c.out[0].error?.code).toBe(-32603); - expect(c.out[0].error?.message).toBe("boom"); - }); -}); - -describe("processLine — notification path (#129)", () => { - it("does NOT emit a response for a notification (no id field)", async () => { - const c = collector(); - const handlerCalled = vi.fn(async () => ({ shouldNotEscape: true })); - await processLine( - JSON.stringify({ - jsonrpc: "2.0", - method: "notifications/initialized", - }), - handlerCalled, - c.writeOut, - c.writeErr, - ); - expect(handlerCalled).toHaveBeenCalledOnce(); - expect(c.out).toHaveLength(0); - expect(c.err).toHaveLength(0); - }); - - it("does NOT emit a response for a notification with id: null", async () => { - const c = collector(); - await processLine( - JSON.stringify({ - jsonrpc: "2.0", - id: null, - method: "notifications/cancelled", - }), - okHandler, - c.writeOut, - c.writeErr, - ); - expect(c.out).toHaveLength(0); - }); - - it("logs to stderr but does NOT emit a response when a notification handler throws", async () => { - const c = collector(); - const throwingHandler: RequestHandler = async () => { - throw new Error("notification crash"); - }; - await processLine( - JSON.stringify({ - jsonrpc: "2.0", - method: "notifications/initialized", - }), - throwingHandler, - c.writeOut, - c.writeErr, - ); - expect(c.out).toHaveLength(0); - expect(c.err).toHaveLength(1); - expect(c.err[0]).toContain("notification handler error"); - expect(c.err[0]).toContain("notification crash"); - }); -}); - -describe("processLine — malformed input", () => { - it("emits a parse error with id: null for invalid JSON", async () => { - const c = collector(); - await processLine("not-json", okHandler, c.writeOut, c.writeErr); - expect(c.out).toHaveLength(1); - expect(c.out[0].id).toBeNull(); - expect(c.out[0].error?.code).toBe(-32700); - expect(c.out[0].error?.message).toBe("Parse error"); - }); - - it("ignores empty / whitespace-only lines", async () => { - const c = collector(); - await processLine("", okHandler, c.writeOut, c.writeErr); - await processLine(" \t ", okHandler, c.writeOut, c.writeErr); - expect(c.out).toHaveLength(0); - expect(c.err).toHaveLength(0); - }); - - it("emits an Invalid Request error when a request has an id but no jsonrpc", async () => { - const c = collector(); - await processLine( - JSON.stringify({ id: 1, method: "tools/list" }), - okHandler, - c.writeOut, - c.writeErr, - ); - expect(c.out).toHaveLength(1); - expect(c.out[0].id).toBe(1); - expect(c.out[0].error?.code).toBe(-32600); - }); - - it("silently drops a malformed message that has no id (treated as notification)", async () => { - const c = collector(); - await processLine( - JSON.stringify({ method: "broken" }), - okHandler, - c.writeOut, - c.writeErr, - ); - // No jsonrpc field, no id — drop without responding. - expect(c.out).toHaveLength(0); - }); - - it("silently drops a malformed message with a non-primitive id (can't safely echo)", async () => { - const c = collector(); - await processLine( - JSON.stringify({ id: { nested: true }, method: "broken" }), - okHandler, - c.writeOut, - c.writeErr, - ); - // Malformed shape + non-primitive id — can't echo id back, drop silently. - expect(c.out).toHaveLength(0); - }); -}); - -describe("processLine — id type validation (JSON-RPC §4)", () => { - it("rejects a request whose id is an object with -32600 and id: null", async () => { - const c = collector(); - const handlerCalled = vi.fn(okHandler); - await processLine( - JSON.stringify({ - jsonrpc: "2.0", - id: { bogus: true }, - method: "tools/list", - }), - handlerCalled, - c.writeOut, - c.writeErr, - ); - expect(handlerCalled).not.toHaveBeenCalled(); - expect(c.out).toHaveLength(1); - expect(c.out[0].id).toBeNull(); - expect(c.out[0].error?.code).toBe(-32600); - expect(c.out[0].error?.message).toContain("id must be"); - }); - - it("rejects a request whose id is an array", async () => { - const c = collector(); - const handlerCalled = vi.fn(okHandler); - await processLine( - JSON.stringify({ jsonrpc: "2.0", id: [1, 2], method: "tools/list" }), - handlerCalled, - c.writeOut, - c.writeErr, - ); - expect(handlerCalled).not.toHaveBeenCalled(); - expect(c.out).toHaveLength(1); - expect(c.out[0].id).toBeNull(); - expect(c.out[0].error?.code).toBe(-32600); - }); - - it("rejects a request whose id is a boolean", async () => { - const c = collector(); - const handlerCalled = vi.fn(okHandler); - await processLine( - JSON.stringify({ jsonrpc: "2.0", id: true, method: "tools/list" }), - handlerCalled, - c.writeOut, - c.writeErr, - ); - expect(handlerCalled).not.toHaveBeenCalled(); - expect(c.out).toHaveLength(1); - expect(c.out[0].id).toBeNull(); - expect(c.out[0].error?.code).toBe(-32600); - }); - - it("accepts a request with string id", async () => { - const c = collector(); - await processLine( - JSON.stringify({ jsonrpc: "2.0", id: "abc-123", method: "ping" }), - okHandler, - c.writeOut, - c.writeErr, - ); - expect(c.out).toHaveLength(1); - expect(c.out[0].id).toBe("abc-123"); - expect(c.out[0].result).toEqual({ method: "ping" }); - }); -}); - -describe("stdio framing", () => { - it("parses Content-Length framed MCP messages split across chunks", () => { - const messages: string[] = []; - const parser = createMessageParser((message) => messages.push(message)); - const body = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize" }); - const framed = `Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`; - - parser.push(framed.slice(0, 12)); - parser.push(framed.slice(12)); - - expect(messages).toEqual([body]); - expect(parser.isFramed()).toBe(true); - }); - - it("parses newline-delimited JSON for existing clients", () => { - const messages: string[] = []; - const parser = createMessageParser((message) => messages.push(message)); - const first = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }); - const second = JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }); - - parser.push(`${first}\n${second}\n`); - - expect(messages).toEqual([first, second]); - expect(parser.isFramed()).toBe(false); - }); - - it("formats responses with Content-Length framing when requested", () => { - const response: JsonRpcResponse = { - jsonrpc: "2.0", - id: 1, - result: { ok: true }, - }; - const formatted = formatResponse(response, true); - - expect(Array.isArray(formatted)).toBe(true); - if (!Array.isArray(formatted)) throw new Error("expected framed response"); - const header = formatted[0].toString("ascii"); - const body = formatted[1].toString("utf8"); - - expect(header).toBe(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n`); - expect(JSON.parse(body)).toEqual(response); - }); -}); diff --git a/test/mcp.test.ts b/test/mcp.test.ts new file mode 100644 index 00000000..86e20d7a --- /dev/null +++ b/test/mcp.test.ts @@ -0,0 +1,131 @@ +import assert from "node:assert/strict"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { openDatabase } from "../src/db/index.js"; +import { getAppPaths } from "../src/paths.js"; +import { callMcpTool, listMcpTools } from "../src/mcp/index.js"; +import { handleJsonRpcLine } from "../src/mcp/stdio.js"; + +test("MCP exposes the minimal todo tools", () => { + assert.deepEqual(listMcpTools().map((tool) => tool.name), [ + "todo_scan", + "todo_organize", + "todo_list", + "todo_update", + "todo_open" + ]); +}); + +test("MCP tools scan, organize, list, update, and open", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-mcp-")); + try { + const sessions = join(dir, "codex"); + mkdirSync(sessions); + writeFileSync(join(sessions, "session.jsonl"), [ + JSON.stringify({ role: "user", text: "Please add MCP tool support", timestamp: new Date().toISOString() }) + ].join("\n")); + + const paths = getAppPaths(join(dir, "home")); + const db = openDatabase(paths); + const scan = await callMcpTool(db, "todo_scan", { source: "codex", path: sessions }, paths); + assert.equal(scan.source, "codex"); + assert.equal(scan.scanned, 1); + + const organize = await callMcpTool(db, "todo_organize", {}, paths); + assert.equal(organize.created, 0); + assert.equal(organize.engine, "llm"); + assert.deepEqual(organize.warnings, ["llm_config_missing"]); + + const listed = await callMcpTool(db, "todo_list", {}, paths); + assert.equal(listed.length, 0); + + const open = await callMcpTool(db, "todo_open", {}, paths); + db.close(); + assert.deepEqual(open, { opened: false, message: "run ai-todo open to start the local UI" }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("MCP organize can use configured llm extraction", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-mcp-llm-")); + try { + const paths = getAppPaths(join(dir, "home")); + const db = openDatabase(paths); + const sessions = join(dir, "codex"); + mkdirSync(sessions); + writeFileSync(join(sessions, "session.jsonl"), JSON.stringify({ role: "user", text: "Please add MCP LLM cards", timestamp: new Date().toISOString() })); + await callMcpTool(db, "todo_scan", { source: "codex", path: sessions }, paths); + const observationId = String((db.prepare("SELECT id FROM observations LIMIT 1").get() as any).id); + + const organize = await callMcpTool(db, "todo_organize", {}, paths, { + organizeOptions: { + llmExtractor: async () => ({ + ok: true, + todos: [{ + title: "Add MCP LLM cards", + description: "Use the configured LLM path from MCP.", + confidence: 0.9, + sourceObservationId: observationId, + quote: "Please add MCP LLM cards", + dedupeKey: "mcp-llm-cards" + }] + }) + } + }); + + assert.equal(organize.engine, "llm"); + assert.equal(organize.created, 1); + const listed = await callMcpTool(db, "todo_list", {}, paths); + assert.equal(listed.length, 1); + const updated = await callMcpTool(db, "todo_update", { id: listed[0].id, status: "done" }, paths); + assert.equal(updated.status, "done"); + db.close(); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("MCP tools return small explicit errors", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-mcp-errors-")); + try { + const paths = getAppPaths(join(dir, "home")); + const db = openDatabase(paths); + await assert.rejects(() => callMcpTool(db, "missing", {}, paths), /unknown tool/); + await assert.rejects(() => callMcpTool(db, "todo_scan", { source: "browser" }, paths), /unsupported source/); + await assert.rejects(() => callMcpTool(db, "todo_update", { id: "missing", status: "todo" }, paths), /invalid status/); + await assert.rejects(() => callMcpTool(db, "todo_update", { id: "missing", status: "done" }, paths), /todo not found/); + db.close(); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("MCP JSON-RPC handles list, call, and errors", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-mcp-rpc-")); + try { + const paths = getAppPaths(join(dir, "home")); + const db = openDatabase(paths); + const list = await handleJsonRpcLine(db, JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }), paths) as any; + assert.equal(list.result.tools.length, 5); + + const call = await handleJsonRpcLine(db, JSON.stringify({ + jsonrpc: "2.0", + id: 2, + method: "tools/call", + params: { name: "todo_open", arguments: {} } + }), paths) as any; + assert.equal(JSON.parse(call.result.content[0].text).opened, false); + + const unknown = await handleJsonRpcLine(db, JSON.stringify({ jsonrpc: "2.0", id: 3, method: "missing" }), paths) as any; + assert.equal(unknown.error.code, -32601); + + const bad = await handleJsonRpcLine(db, "{", paths) as any; + db.close(); + assert.equal(bad.error.code, -32700); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); diff --git a/test/memories-pagination.test.ts b/test/memories-pagination.test.ts deleted file mode 100644 index c3275df6..00000000 --- a/test/memories-pagination.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; - -// /memories and /export must support count + pagination so the -// viewer and `agentmemory status` work on large corpora (8K+ memories) -// without timing out at the iii engine boundary. -describe("memories + export pagination (#544)", () => { - const api = readFileSync("src/triggers/api.ts", "utf-8"); - - it("api::memories accepts count=true and returns total + latestCount", () => { - expect(api).toMatch(/req\.query_params\?\.\["count"\]\s*===\s*"true"/); - // count must report the SAME scope as the list path (#554 follow-up). - expect(api).toMatch(/total:\s*filtered\.length/); - expect(api).toMatch(/latestCount:\s*filtered\.filter/); - }); - - it("api::memories accepts limit + offset query params", () => { - expect(api).toMatch(/query_params\?\.\["limit"\]/); - expect(api).toMatch(/query_params\?\.\["offset"\]/); - expect(api).toMatch(/filtered\.slice\(offset/); - expect(api).toMatch(/total:\s*filtered\.length/); - }); - - it("api::memories caps limit at 5000 to bound response size", () => { - expect(api).toMatch(/Math\.min\(parsedLimit,\s*5000\)/); - }); - - it("api::export passes through maxSessions + offset query params", () => { - expect(api).toMatch(/query_params\?\.\["maxSessions"\]/); - expect(api).toMatch(/query_params\?\.\["offset"\]/); - // The payload object is named `payload` in our handler; assert it is - // forwarded to mem::export rather than the previous empty object. - expect(api).toMatch( - /sdk\.trigger\(\{\s*function_id:\s*"mem::export",\s*payload,/m, - ); - }); - - it("viewer dashboard caps memories?latest fetch with limit", () => { - const viewer = readFileSync("src/viewer/index.html", "utf-8"); - expect(viewer).toMatch(/memories\?latest=true&limit=500/); - expect(viewer).toMatch(/memories\?latest=true&limit=2000/); - }); -}); diff --git a/test/mesh.test.ts b/test/mesh.test.ts deleted file mode 100644 index e08db078..00000000 --- a/test/mesh.test.ts +++ /dev/null @@ -1,755 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerMeshFunction } from "../src/functions/mesh.js"; -import type { - MeshPeer, - Memory, - Action, - SemanticMemory, - ProceduralMemory, - MemoryRelation, - GraphNode, - GraphEdge, -} from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("Mesh Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - vi.clearAllMocks(); - registerMeshFunction(sdk as never, kv as never); - }); - - describe("mesh-register", () => { - it("registers a valid peer", async () => { - const result = (await sdk.trigger("mem::mesh-register", { - url: "https://peer1.example.com", - name: "peer-1", - sharedScopes: ["memories"], - })) as { success: boolean; peer: MeshPeer }; - - expect(result.success).toBe(true); - expect(result.peer.url).toBe("https://peer1.example.com"); - expect(result.peer.name).toBe("peer-1"); - expect(result.peer.status).toBe("disconnected"); - expect(result.peer.sharedScopes).toEqual(["memories"]); - expect(result.peer.id).toMatch(/^peer_/); - - const peers = await kv.list("mem:mesh"); - expect(peers.length).toBe(1); - }); - - it("uses expanded default sharedScopes when not provided", async () => { - const result = (await sdk.trigger("mem::mesh-register", { - url: "https://peer2.example.com", - name: "peer-2", - })) as { success: boolean; peer: MeshPeer }; - - expect(result.success).toBe(true); - expect(result.peer.sharedScopes).toEqual([ - "memories", - "actions", - "semantic", - "procedural", - "relations", - "graph:nodes", - "graph:edges", - ]); - }); - - it("stores syncFilter when provided", async () => { - const result = (await sdk.trigger("mem::mesh-register", { - url: "https://peer3.example.com", - name: "peer-3", - syncFilter: { project: "/my/project" }, - })) as { success: boolean; peer: MeshPeer }; - - expect(result.success).toBe(true); - expect(result.peer.syncFilter).toEqual({ project: "/my/project" }); - }); - - it("returns error when url is missing", async () => { - const result = (await sdk.trigger("mem::mesh-register", { - name: "peer-1", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("url and name are required"); - }); - - it("returns error when name is missing", async () => { - const result = (await sdk.trigger("mem::mesh-register", { - url: "https://peer1.example.com", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("url and name are required"); - }); - - it("returns error for duplicate url", async () => { - await sdk.trigger("mem::mesh-register", { - url: "https://peer1.example.com", - name: "peer-1", - }); - - const result = (await sdk.trigger("mem::mesh-register", { - url: "https://peer1.example.com", - name: "peer-1-duplicate", - })) as { success: boolean; error: string; peerId: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("peer already registered"); - expect(result.peerId).toBeDefined(); - }); - }); - - describe("mesh-list", () => { - it("returns empty list when no peers registered", async () => { - const result = (await sdk.trigger("mem::mesh-list", {})) as { - success: boolean; - peers: MeshPeer[]; - }; - - expect(result.success).toBe(true); - expect(result.peers).toEqual([]); - }); - - it("returns all registered peers", async () => { - await sdk.trigger("mem::mesh-register", { - url: "https://peer1.example.com", - name: "peer-1", - }); - await sdk.trigger("mem::mesh-register", { - url: "https://peer2.example.com", - name: "peer-2", - }); - - const result = (await sdk.trigger("mem::mesh-list", {})) as { - success: boolean; - peers: MeshPeer[]; - }; - - expect(result.success).toBe(true); - expect(result.peers.length).toBe(2); - expect(result.peers.map((p) => p.name).sort()).toEqual(["peer-1", "peer-2"]); - }); - }); - - describe("mesh-sync", () => { - it("requires a configured shared secret", async () => { - const regResult = (await sdk.trigger("mem::mesh-register", { - url: "https://peer1.example.com", - name: "peer-1", - })) as { success: boolean; peer: MeshPeer }; - - const result = (await sdk.trigger("mem::mesh-sync", { - peerId: regResult.peer.id, - direction: "push", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("AGENTMEMORY_SECRET"); - }); - - it("sends authorization headers to peers when syncing", async () => { - const authedSdk = mockSdk(); - const authedKv = mockKV(); - registerMeshFunction(authedSdk as never, authedKv as never, "mesh-secret"); - - const fetchMock = vi.fn(async () => - new Response(JSON.stringify({ accepted: 0 }), { - status: 200, - headers: { "Content-Type": "application/json" }, - }), - ); - vi.stubGlobal("fetch", fetchMock); - - const regResult = (await authedSdk.trigger("mem::mesh-register", { - url: "https://peer2.example.com", - name: "peer-2", - })) as { success: boolean; peer: MeshPeer }; - - const result = (await authedSdk.trigger("mem::mesh-sync", { - peerId: regResult.peer.id, - direction: "push", - })) as { success: boolean; results: Array<{ errors: string[] }> }; - - expect(result.success).toBe(true); - expect(result.results[0].errors).toEqual([]); - expect(fetchMock).toHaveBeenCalledWith( - "https://peer2.example.com/agentmemory/mesh/receive", - expect.objectContaining({ - headers: expect.objectContaining({ - Authorization: "Bearer mesh-secret", - }), - }), - ); - - vi.unstubAllGlobals(); - }); - }); - - describe("mesh-receive", () => { - it("accepts new memories", async () => { - const mem: Memory = { - id: "mem_1", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "pattern", - title: "Test memory", - content: "Test content", - concepts: ["test"], - files: [], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - memories: [mem], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(1); - - const stored = await kv.get("mem:memories", "mem_1"); - expect(stored).toBeDefined(); - expect(stored!.title).toBe("Test memory"); - }); - - it("accepts newer memory over existing (last-write-wins)", async () => { - const older: Memory = { - id: "mem_1", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "pattern", - title: "Old title", - content: "Old content", - concepts: [], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: true, - }; - await kv.set("mem:memories", "mem_1", older); - - const newer: Memory = { - ...older, - updatedAt: "2026-03-02T00:00:00Z", - title: "New title", - content: "New content", - version: 2, - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - memories: [newer], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(1); - - const stored = await kv.get("mem:memories", "mem_1"); - expect(stored!.title).toBe("New title"); - }); - - it("rejects older memory than existing", async () => { - const existing: Memory = { - id: "mem_1", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-02T00:00:00Z", - type: "pattern", - title: "Existing title", - content: "Existing content", - concepts: [], - files: [], - sessionIds: [], - strength: 5, - version: 2, - isLatest: true, - }; - await kv.set("mem:memories", "mem_1", existing); - - const older: Memory = { - ...existing, - updatedAt: "2026-03-01T00:00:00Z", - title: "Old title", - version: 1, - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - memories: [older], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(0); - - const stored = await kv.get("mem:memories", "mem_1"); - expect(stored!.title).toBe("Existing title"); - }); - - it("skips memory entries with missing id", async () => { - const result = (await sdk.trigger("mem::mesh-receive", { - memories: [ - { updatedAt: "2026-03-01T00:00:00Z", title: "No ID" } as unknown as Memory, - ], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(0); - }); - - it("skips memory entries with invalid date", async () => { - const result = (await sdk.trigger("mem::mesh-receive", { - memories: [ - { - id: "mem_bad_date", - updatedAt: "not-a-date", - title: "Bad date", - } as unknown as Memory, - ], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(0); - }); - - it("accepts new actions", async () => { - const action: Action = { - id: "act_1", - title: "Fix bug", - description: "Fix the login bug", - status: "pending", - priority: 1, - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - createdBy: "agent-1", - tags: ["bug"], - sourceObservationIds: [], - sourceMemoryIds: [], - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - actions: [action], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(1); - - const stored = await kv.get("mem:actions", "act_1"); - expect(stored).toBeDefined(); - expect(stored!.title).toBe("Fix bug"); - }); - - it("accepts newer action over existing (last-write-wins)", async () => { - const older: Action = { - id: "act_1", - title: "Old action", - description: "Old desc", - status: "pending", - priority: 1, - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - createdBy: "agent-1", - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - }; - await kv.set("mem:actions", "act_1", older); - - const newer: Action = { - ...older, - updatedAt: "2026-03-02T00:00:00Z", - title: "Updated action", - status: "done", - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - actions: [newer], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(1); - - const stored = await kv.get("mem:actions", "act_1"); - expect(stored!.title).toBe("Updated action"); - expect(stored!.status).toBe("done"); - }); - - it("rejects older action than existing", async () => { - const existing: Action = { - id: "act_1", - title: "Current action", - description: "Current desc", - status: "active", - priority: 1, - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-02T00:00:00Z", - createdBy: "agent-1", - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - }; - await kv.set("mem:actions", "act_1", existing); - - const older: Action = { - ...existing, - updatedAt: "2026-03-01T00:00:00Z", - title: "Stale action", - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - actions: [older], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(0); - - const stored = await kv.get("mem:actions", "act_1"); - expect(stored!.title).toBe("Current action"); - }); - - it("skips action entries with missing id", async () => { - const result = (await sdk.trigger("mem::mesh-receive", { - actions: [ - { updatedAt: "2026-03-01T00:00:00Z", title: "No ID" } as unknown as Action, - ], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(0); - }); - - it("skips action entries with invalid date", async () => { - const result = (await sdk.trigger("mem::mesh-receive", { - actions: [ - { - id: "act_bad_date", - updatedAt: "invalid-date-string", - title: "Bad date", - } as unknown as Action, - ], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(0); - }); - - it("accepts both memories and actions in one call", async () => { - const mem: Memory = { - id: "mem_combo", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "fact", - title: "Combo memory", - content: "Content", - concepts: [], - files: [], - sessionIds: [], - strength: 3, - version: 1, - isLatest: true, - }; - const action: Action = { - id: "act_combo", - title: "Combo action", - description: "Desc", - status: "pending", - priority: 2, - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - createdBy: "agent-1", - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - memories: [mem], - actions: [action], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(2); - }); - - it("returns zero accepted for empty arrays", async () => { - const result = (await sdk.trigger("mem::mesh-receive", { - memories: [], - actions: [], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(0); - }); - }); - - describe("mesh-remove", () => { - it("removes a registered peer", async () => { - const regResult = (await sdk.trigger("mem::mesh-register", { - url: "https://peer1.example.com", - name: "peer-1", - })) as { success: boolean; peer: MeshPeer }; - - const result = (await sdk.trigger("mem::mesh-remove", { - peerId: regResult.peer.id, - })) as { success: boolean }; - - expect(result.success).toBe(true); - - const peers = await kv.list("mem:mesh"); - expect(peers.length).toBe(0); - }); - - it("returns error when peerId is missing", async () => { - const result = (await sdk.trigger("mem::mesh-remove", {})) as { - success: boolean; - error: string; - }; - - expect(result.success).toBe(false); - expect(result.error).toContain("peerId is required"); - }); - - it("succeeds silently for non-existent peerId", async () => { - const result = (await sdk.trigger("mem::mesh-remove", { - peerId: "peer_nonexistent", - })) as { success: boolean }; - - expect(result.success).toBe(true); - }); - }); - - describe("mesh-receive expanded scopes", () => { - it("accepts semantic memories", async () => { - const sem: SemanticMemory = { - id: "sem_1", - fact: "React uses JSX", - confidence: 0.9, - sourceSessionIds: ["ses_1"], - sourceMemoryIds: ["mem_1"], - accessCount: 1, - lastAccessedAt: "2026-03-01T00:00:00Z", - strength: 7, - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - semantic: [sem], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(1); - const stored = await kv.get("mem:semantic", "sem_1"); - expect(stored).toBeDefined(); - expect(stored!.fact).toBe("React uses JSX"); - }); - - it("accepts procedural memories", async () => { - const proc: ProceduralMemory = { - id: "proc_1", - name: "Deploy to prod", - steps: ["build", "test", "deploy"], - triggerCondition: "on merge to main", - frequency: 5, - sourceSessionIds: ["ses_1"], - strength: 8, - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - procedural: [proc], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(1); - const stored = await kv.get("mem:procedural", "proc_1"); - expect(stored!.name).toBe("Deploy to prod"); - }); - - it("accepts graph nodes", async () => { - const node: GraphNode = { - id: "gn_1", - type: "concept", - name: "typescript", - properties: {}, - sourceObservationIds: ["obs_1"], - createdAt: "2026-03-01T00:00:00Z", - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - graphNodes: [node], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(1); - const stored = await kv.get("mem:graph:nodes", "gn_1"); - expect(stored!.name).toBe("typescript"); - }); - - it("accepts graph edges", async () => { - const edge: GraphEdge = { - id: "ge_1", - type: "uses", - sourceNodeId: "gn_1", - targetNodeId: "gn_2", - weight: 1, - sourceObservationIds: ["obs_1"], - createdAt: "2026-03-01T00:00:00Z", - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - graphEdges: [edge], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(1); - const stored = await kv.get("mem:graph:edges", "ge_1"); - expect(stored!.type).toBe("uses"); - }); - - it("accepts relations", async () => { - const rel: MemoryRelation = { - type: "supersedes", - sourceId: "mem_2", - targetId: "mem_1", - createdAt: "2026-03-01T00:00:00Z", - confidence: 0.95, - }; - const relWithId = { ...rel, id: "rel_1" } as MemoryRelation & { id: string }; - - const result = (await sdk.trigger("mem::mesh-receive", { - relations: [relWithId], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(1); - }); - - it("accepts all scope types in one call", async () => { - const mem: Memory = { - id: "mem_all", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "fact", - title: "All scopes test", - content: "Content", - concepts: [], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: true, - }; - const sem: SemanticMemory = { - id: "sem_all", - fact: "Test", - confidence: 0.5, - sourceSessionIds: [], - sourceMemoryIds: [], - accessCount: 0, - lastAccessedAt: "2026-03-01T00:00:00Z", - strength: 5, - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - }; - const node: GraphNode = { - id: "gn_all", - type: "file", - name: "test.ts", - properties: {}, - sourceObservationIds: [], - createdAt: "2026-03-01T00:00:00Z", - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - memories: [mem], - semantic: [sem], - graphNodes: [node], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(3); - }); - - it("applies LWW for semantic memories", async () => { - const older: SemanticMemory = { - id: "sem_lww", - fact: "Old fact", - confidence: 0.5, - sourceSessionIds: [], - sourceMemoryIds: [], - accessCount: 1, - lastAccessedAt: "2026-03-01T00:00:00Z", - strength: 5, - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - }; - await kv.set("mem:semantic", "sem_lww", older); - - const newer: SemanticMemory = { - ...older, - fact: "New fact", - updatedAt: "2026-03-02T00:00:00Z", - }; - - const result = (await sdk.trigger("mem::mesh-receive", { - semantic: [newer], - })) as { success: boolean; accepted: number }; - - expect(result.success).toBe(true); - expect(result.accepted).toBe(1); - const stored = await kv.get("mem:semantic", "sem_lww"); - expect(stored!.fact).toBe("New fact"); - }); - }); -}); diff --git a/test/minimax-provider.test.ts b/test/minimax-provider.test.ts deleted file mode 100644 index 4167d4b3..00000000 --- a/test/minimax-provider.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { describe, expect, it, afterEach } from "vitest"; -import { MinimaxProvider } from "../src/providers/minimax.js"; - -describe("MinimaxProvider — base URL resolution (#285)", () => { - const originalEnv = process.env["MINIMAX_BASE_URL"]; - - afterEach(() => { - if (originalEnv === undefined) { - delete process.env["MINIMAX_BASE_URL"]; - } else { - process.env["MINIMAX_BASE_URL"] = originalEnv; - } - }); - - it("defaults to https://api.minimax.io/anthropic (not the legacy minimaxi.com host)", () => { - delete process.env["MINIMAX_BASE_URL"]; - const provider = new MinimaxProvider("test-key", "MiniMax-M2.7", 800); - expect((provider as unknown as { baseUrl: string }).baseUrl).toBe( - "https://api.minimax.io/anthropic", - ); - }); - - it("honors MINIMAX_BASE_URL via getEnvVar (merged ~/.agentmemory/.env + process.env)", () => { - process.env["MINIMAX_BASE_URL"] = "https://custom.example.com/anthropic"; - const provider = new MinimaxProvider("test-key", "MiniMax-M2.7", 800); - expect((provider as unknown as { baseUrl: string }).baseUrl).toBe( - "https://custom.example.com/anthropic", - ); - }); -}); diff --git a/test/multimodal.test.ts b/test/multimodal.test.ts deleted file mode 100644 index 24e71f3f..00000000 --- a/test/multimodal.test.ts +++ /dev/null @@ -1,377 +0,0 @@ -import { describe, it, expect, vi, afterAll, beforeEach } from "vitest"; -import { existsSync, rmSync } from "node:fs"; - -vi.mock("iii-sdk", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - getContext: () => ({ - logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn() }, - }), - }; -}); - -vi.mock("../src/functions/search.js", () => ({ - getSearchIndex: () => ({ - add: vi.fn(), - }), - vectorIndexAddGuarded: vi.fn().mockResolvedValue(false), -})); - -const mockTriggerVoid = vi.fn(); -const mockTrigger = vi.fn().mockResolvedValue(undefined); -const mockSdk = { triggerVoid: mockTriggerVoid, trigger: mockTrigger } as any; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - if (!store.has(scope)) return []; - return Array.from(store.get(scope)!.values()) as T[]; - }, - getStore: () => store, - }; -} - -const kv = mockKV() as any; - -import { registerObserveFunction } from "../src/functions/observe.js"; -import { registerCompressFunction } from "../src/functions/compress.js"; -import type { RawObservation, CompressedObservation, MemoryProvider } from "../src/types.js"; - -const VALID_COMPRESS_XML = `image -Screenshot of Red Dot -Test image observation -Image shows a single red pixel on white background -A vision model described a screenshot showing a red dot on a white background -testingscreenshot - -5`; - -describe("End-to-End Multimodal Flow", () => { - let savedImagePath: string | undefined; - - afterAll(() => { - if (savedImagePath && existsSync(savedImagePath)) { - rmSync(savedImagePath); - console.log(`Cleanup: Removed test image at ${savedImagePath}`); - } - }); - - beforeEach(() => { - mockTrigger.mockClear(); - }); - - it("Step 1: Agent image should be successfully saved to hard drive", async () => { - let observeCallback: any = null; - const sdkMocker = { ...mockSdk, registerFunction: vi.fn((id, cb) => { if (id === "mem::observe") observeCallback = cb; }) }; - registerObserveFunction(sdkMocker, kv); - - const fakeIncomingData = { - hookType: "post_tool_use", - sessionId: "test-session", - timestamp: new Date().toISOString(), - data: { - tool_name: "screenshot", - tool_output: { - image_data: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==" - } - } - }; - - const res = await observeCallback(fakeIncomingData); - expect(res.observationId).toBeDefined(); - - const obsList = await kv.list("mem:obs:test-session"); - expect(obsList.length).toBe(1); - - const raw = obsList[0] as RawObservation; - expect(raw.modality).toBe("mixed"); - - expect(raw.imageData).toBeDefined(); - expect(typeof raw.imageData).toBe("string"); - expect(existsSync(raw.imageData!)).toBe(true); - - savedImagePath = raw.imageData; - - const deltaCalls = mockTrigger.mock.calls.filter( - (c: any[]) => c[0]?.function_id === "mem::disk-size-delta" - ); - expect(deltaCalls.length).toBe(1); - expect(deltaCalls[0][0].payload.deltaBytes).toBeGreaterThan(0); - }); - - it("Step 2 & 3: mem::compress should call the vision model and store compressed observation in KV", async () => { - const mockProvider: MemoryProvider = { - name: "mock-vision", - compress: async (_systemPrompt, userPrompt) => { - expect(userPrompt).toContain("TEST_VISION_RESULT: I see a red dot"); - return VALID_COMPRESS_XML; - }, - summarize: async () => "", - describeImage: async (_base64, _mimeType, _prompt) => { - return "TEST_VISION_RESULT: I see a red dot"; - }, - }; - - let compressCallback: any = null; - const sdkMocker = { - ...mockSdk, - registerFunction: vi.fn((id, cb) => { - if (id === "mem::compress") compressCallback = cb; - }), - }; - registerCompressFunction(sdkMocker, kv, mockProvider); - - expect(compressCallback).not.toBeNull(); - - const rawObsList = await kv.list("mem:obs:test-session"); - const raw = rawObsList[0] as RawObservation; - - expect(raw.modality).toBeDefined(); - expect(raw.imageData).toBe(savedImagePath); - - const result = await compressCallback({ - observationId: raw.id, - sessionId: raw.sessionId, - raw, - }); - - expect(result.success).toBe(true); - expect(result.compressed).toBeDefined(); - - const compressed = result.compressed as CompressedObservation; - expect(compressed.imageDescription).toBe("TEST_VISION_RESULT: I see a red dot"); - expect(compressed.imageRef).toBe(savedImagePath); - expect(compressed.modality).toBe("mixed"); - expect(compressed.title).toBe("Screenshot of Red Dot"); - expect(compressed.narrative).toContain("red dot"); - - const stored = await kv.get("mem:obs:test-session", raw.id!) as CompressedObservation | null; - expect(stored).not.toBeNull(); - expect(stored!.imageDescription).toBe("TEST_VISION_RESULT: I see a red dot"); - expect(stored!.imageRef).toBe(savedImagePath); - }); -}); - -describe("Disk Size Manager", () => { - it("should increment disk size and trigger cleanup when over quota", async () => { - const localKv = mockKV() as any; - const localTrigger = vi.fn().mockResolvedValue(undefined); - const localSdk = { trigger: localTrigger } as any; - - let managerCallback: any = null; - const sdkMocker = { - ...localSdk, - registerFunction: vi.fn((id: string, cb: any) => { - if (id === "mem::disk-size-delta") managerCallback = cb; - }), - }; - - const { registerDiskSizeManager } = await import("../src/functions/disk-size-manager.js"); - registerDiskSizeManager(sdkMocker, localKv); - - expect(managerCallback).not.toBeNull(); - - const res1 = await managerCallback({ deltaBytes: 1000 }); - expect(res1.success).toBe(true); - expect(res1.currentTotal).toBe(1000); - - const res2 = await managerCallback({ deltaBytes: 2000 }); - expect(res2.success).toBe(true); - expect(res2.currentTotal).toBe(3000); - - expect(localTrigger).not.toHaveBeenCalled(); - }); - - it("should trigger cleanup when total exceeds max bytes", async () => { - const originalEnv = process.env.AGENTMEMORY_IMAGE_STORE_MAX_BYTES; - try { - process.env.AGENTMEMORY_IMAGE_STORE_MAX_BYTES = "5000"; - - const localKv = mockKV() as any; - const localTrigger = vi.fn().mockResolvedValue(undefined); - const localSdk = { trigger: localTrigger } as any; - - let managerCallback: any = null; - const sdkMocker = { - ...localSdk, - registerFunction: vi.fn((id: string, cb: any) => { - if (id === "mem::disk-size-delta") managerCallback = cb; - }), - }; - - const { registerDiskSizeManager } = await import("../src/functions/disk-size-manager.js"); - registerDiskSizeManager(sdkMocker, localKv); - - await managerCallback({ deltaBytes: 3000 }); - expect(localTrigger).not.toHaveBeenCalled(); - - await managerCallback({ deltaBytes: 3000 }); - expect(localTrigger).toHaveBeenCalledWith({ function_id: "mem::image-quota-cleanup", payload: {}, action: expect.anything() }); - } finally { - if (originalEnv === undefined) { - delete process.env.AGENTMEMORY_IMAGE_STORE_MAX_BYTES; - } else { - process.env.AGENTMEMORY_IMAGE_STORE_MAX_BYTES = originalEnv; - } - } - }); - - it("should clamp negative totals to zero", async () => { - const localKv = mockKV() as any; - const localSdk = { trigger: vi.fn().mockResolvedValue(undefined) } as any; - - let managerCallback: any = null; - const sdkMocker = { - ...localSdk, - registerFunction: vi.fn((id: string, cb: any) => { - if (id === "mem::disk-size-delta") managerCallback = cb; - }), - }; - - const { registerDiskSizeManager } = await import("../src/functions/disk-size-manager.js"); - registerDiskSizeManager(sdkMocker, localKv); - - const res = await managerCallback({ deltaBytes: -99999 }); - expect(res.currentTotal).toBe(0); - }); - - it("should reject invalid deltaBytes", async () => { - const localKv = mockKV() as any; - const localSdk = { trigger: vi.fn().mockResolvedValue(undefined) } as any; - - let managerCallback: any = null; - const sdkMocker = { - ...localSdk, - registerFunction: vi.fn((id: string, cb: any) => { - if (id === "mem::disk-size-delta") managerCallback = cb; - }), - }; - - const { registerDiskSizeManager } = await import("../src/functions/disk-size-manager.js"); - registerDiskSizeManager(sdkMocker, localKv); - - const res = await managerCallback({ deltaBytes: "not-a-number" }); - expect(res.success).toBe(false); - }); -}); - -describe("Image Refs", () => { - it("should increment and decrement ref counts correctly with deletion parity", async () => { - const localKv = mockKV() as any; - const localTrigger = vi.fn().mockResolvedValue(undefined); - const localSdk = { trigger: localTrigger } as any; - - const { saveImageToDisk } = await import("../src/utils/image-store.js"); - const { incrementImageRef, decrementImageRef, getImageRefCount } = await import("../src/functions/image-refs.js"); - - const result = await saveImageToDisk( - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==" - ); - const testFile = result.filePath; - - expect(existsSync(testFile)).toBe(true); - - // Initial state - expect(await getImageRefCount(localKv, testFile)).toBe(0); - - // Increment to 1 - await incrementImageRef(localKv, testFile); - expect(await getImageRefCount(localKv, testFile)).toBe(1); - - // Increment to 2 (shared image) - await incrementImageRef(localKv, testFile); - expect(await getImageRefCount(localKv, testFile)).toBe(2); - - // Decrement from 2 to 1 - await decrementImageRef(localKv, localSdk, testFile); - expect(await getImageRefCount(localKv, testFile)).toBe(1); - - // (c) shared image with refcount >= 2 is NOT deleted when one decrements - expect(existsSync(testFile)).toBe(true); - expect(localTrigger).not.toHaveBeenCalled(); - - // (a) decrementing to zero triggers image file deletion and negative delta - await decrementImageRef(localKv, localSdk, testFile); - expect(await getImageRefCount(localKv, testFile)).toBe(0); - expect(existsSync(testFile)).toBe(false); - - const deltaCalls = localTrigger.mock.calls.filter( - (c: any[]) => c[0]?.function_id === "mem::disk-size-delta" - ); - expect(deltaCalls.length).toBe(1); - expect(deltaCalls[0][0].payload.deltaBytes).toBeLessThan(0); - - // (b) decrementing an already-zero/unknown ref is a no-op - localTrigger.mockClear(); - await decrementImageRef(localKv, localSdk, "/fake/unknown/path.png"); - expect(await getImageRefCount(localKv, "/fake/unknown/path.png")).toBe(0); - const noOpDeltaCalls = localTrigger.mock.calls.filter( - (c: any[]) => c[0]?.function_id === "mem::disk-size-delta" - ); - expect(noOpDeltaCalls.length).toBe(0); - }); -}); - -describe("Image Store", () => { - let testFilePath: string | undefined; - - afterAll(() => { - if (testFilePath && existsSync(testFilePath)) { - rmSync(testFilePath); - } - }); - - it("saveImageToDisk should return filePath and bytesWritten", async () => { - const { saveImageToDisk } = await import("../src/utils/image-store.js"); - const result = await saveImageToDisk( - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==" - ); - - expect(result.filePath).toBeDefined(); - expect(typeof result.filePath).toBe("string"); - expect(result.filePath.length).toBeGreaterThan(0); - expect(result.bytesWritten).toBeGreaterThan(0); - expect(existsSync(result.filePath)).toBe(true); - - testFilePath = result.filePath; - }); - - it("deleteImage should return deletedBytes after successful deletion", async () => { - const { deleteImage } = await import("../src/utils/image-store.js"); - - expect(testFilePath).toBeDefined(); - expect(existsSync(testFilePath!)).toBe(true); - - const result = await deleteImage(testFilePath!); - expect(result.deletedBytes).toBeGreaterThan(0); - expect(existsSync(testFilePath!)).toBe(false); - - testFilePath = undefined; - }); - - it("deleteImage should return 0 for non-existent files", async () => { - const { deleteImage } = await import("../src/utils/image-store.js"); - const result = await deleteImage("/non/existent/file.png"); - expect(result.deletedBytes).toBe(0); - }); - - it("saveImageToDisk should return empty for empty input", async () => { - const { saveImageToDisk } = await import("../src/utils/image-store.js"); - const result = await saveImageToDisk(""); - expect(result.filePath).toBe(""); - expect(result.bytesWritten).toBe(0); - }); -}); diff --git a/test/observe-implicit-session.test.ts b/test/observe-implicit-session.test.ts deleted file mode 100644 index b0272bc7..00000000 --- a/test/observe-implicit-session.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -function mockKV() { - const store = new Map>(); - return { - store, - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - update: async (scope: string, key: string, updates: Array<{ path: string; value: unknown }>) => { - const m = store.get(scope); - if (!m) return; - const v = (m.get(key) as Record) ?? {}; - for (const u of updates) v[u.path] = u.value; - m.set(key, v); - }, - delete: async (scope: string, key: string) => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const m = store.get(scope); - return m ? (Array.from(m.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const fns = new Map(); - return { - fns, - registerFunction: ( - idOrOpts: string | { id: string }, - fn: Function, - ) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - fns.set(id, fn); - }, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown; action?: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = fns.get(id); - if (fn) return fn(payload); - return null; - }, - }; -} - -describe("observe implicit session create (#638)", () => { - beforeEach(() => { - vi.resetModules(); - }); - - it("creates the session on first observe when project+cwd present and session record missing", async () => { - const { registerObserveFunction } = await import("../src/functions/observe.js"); - const sdk = mockSdk(); - const kv = mockKV(); - registerObserveFunction(sdk as never, kv as never); - - const result = (await sdk.trigger("mem::observe", { - sessionId: "ses_opencode_abc", - project: "/home/user/myrepo", - cwd: "/home/user/myrepo", - hookType: "prompt_submit", - timestamp: new Date().toISOString(), - data: { prompt: "ship the helm chart" }, - })) as { observationId: string }; - - expect(result.observationId).toBeTruthy(); - - const sessionScope = kv.store.get("mem:sessions"); - expect(sessionScope).toBeTruthy(); - const session = sessionScope!.get("ses_opencode_abc") as Record; - expect(session).toBeTruthy(); - expect(session.id).toBe("ses_opencode_abc"); - expect(session.project).toBe("/home/user/myrepo"); - expect(session.cwd).toBe("/home/user/myrepo"); - expect(session.status).toBe("active"); - expect(session.observationCount).toBe(1); - expect(session.firstPrompt).toBe("ship the helm chart"); - }); - - it("does not implicit-create when project+cwd missing (test-payload back-compat)", async () => { - const { registerObserveFunction } = await import("../src/functions/observe.js"); - const sdk = mockSdk(); - const kv = mockKV(); - registerObserveFunction(sdk as never, kv as never); - - await sdk.trigger("mem::observe", { - sessionId: "ses_no_project", - hookType: "post_tool_use", - timestamp: new Date().toISOString(), - data: { tool_name: "Read", tool_input: { file_path: "x.ts" } }, - }); - - const sessionScope = kv.store.get("mem:sessions"); - // Either no scope at all, or no entry for this session - expect(sessionScope?.get("ses_no_project")).toBeUndefined(); - }); - - it("does not overwrite an existing session when one already exists", async () => { - const { registerObserveFunction } = await import("../src/functions/observe.js"); - const sdk = mockSdk(); - const kv = mockKV(); - registerObserveFunction(sdk as never, kv as never); - - await kv.set("mem:sessions", "ses_existing", { - id: "ses_existing", - project: "/orig/project", - cwd: "/orig/cwd", - startedAt: "2026-01-01T00:00:00Z", - status: "active", - observationCount: 7, - firstPrompt: "original first prompt", - }); - - await sdk.trigger("mem::observe", { - sessionId: "ses_existing", - project: "/different/project", - cwd: "/different/cwd", - hookType: "post_tool_use", - timestamp: new Date().toISOString(), - data: { tool_name: "Read" }, - }); - - const session = kv.store.get("mem:sessions")!.get("ses_existing") as Record; - // Original project + firstPrompt preserved - expect(session.project).toBe("/orig/project"); - expect(session.firstPrompt).toBe("original first prompt"); - // Counter bumped, updatedAt refreshed - expect(session.observationCount).toBe(8); - expect(session.updatedAt).toBeTruthy(); - }); -}); diff --git a/test/obsidian-export.test.ts b/test/obsidian-export.test.ts deleted file mode 100644 index a02cc8a4..00000000 --- a/test/obsidian-export.test.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -const writtenFiles = new Map(); -const createdDirs = new Set(); - -vi.mock("node:fs/promises", () => ({ - mkdir: vi.fn(async (dir: string) => { - createdDirs.add(dir); - }), - writeFile: vi.fn(async (path: string, content: string) => { - writtenFiles.set(path, content); - }), -})); - -import { registerObsidianExportFunction } from "../src/functions/obsidian-export.js"; -import type { Memory, Lesson, Crystal, Session } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeMemory(id: string): Memory { - return { - id, - createdAt: "2026-04-01T00:00:00Z", - updatedAt: "2026-04-01T00:00:00Z", - type: "pattern", - title: `Memory ${id}`, - content: `Content for ${id}`, - concepts: ["testing"], - files: ["src/test.ts"], - sessionIds: ["ses_1"], - strength: 7, - version: 1, - isLatest: true, - }; -} - -function makeLesson(id: string): Lesson { - return { - id, - content: `Lesson ${id}`, - context: "Test context", - confidence: 0.8, - reinforcements: 2, - source: "manual", - sourceIds: [], - project: "/test", - tags: ["testing"], - createdAt: "2026-04-01T00:00:00Z", - updatedAt: "2026-04-01T00:00:00Z", - decayRate: 0.05, - }; -} - -function makeCrystal(id: string): Crystal { - return { - id, - narrative: `Crystal narrative ${id}`, - keyOutcomes: ["Outcome 1"], - filesAffected: ["src/test.ts"], - lessons: ["Learned something"], - sourceActionIds: ["act_1"], - sessionId: "ses_1", - project: "/test", - createdAt: "2026-04-01T00:00:00Z", - }; -} - -function makeSession(id: string): Session { - return { - id, - project: "/test", - cwd: "/Users/test/project", - startedAt: "2026-04-01T00:00:00Z", - endedAt: "2026-04-01T01:00:00Z", - status: "completed", - observationCount: 15, - }; -} - -describe("Obsidian Export", () => { - let sdk: ReturnType; - let kv: ReturnType; - const exportRoot = "/tmp/agentmemory-export-root"; - - beforeEach(() => { - process.env.AGENTMEMORY_EXPORT_ROOT = exportRoot; - sdk = mockSdk(); - kv = mockKV(); - writtenFiles.clear(); - createdDirs.clear(); - registerObsidianExportFunction(sdk as never, kv as never); - }); - - it("creates directories and MOC.md with empty data", async () => { - const result = (await sdk.trigger("mem::obsidian-export", {})) as { - success: boolean; - exported: Record; - vaultDir: string; - }; - - expect(result.success).toBe(true); - expect(result.exported.memories).toBe(0); - expect(result.exported.lessons).toBe(0); - expect(result.exported.crystals).toBe(0); - expect(result.exported.sessions).toBe(0); - expect(createdDirs.size).toBe(4); - - const mocPath = [...writtenFiles.keys()].find((k) => k.endsWith("MOC.md")); - expect(mocPath).toBeDefined(); - const moc = writtenFiles.get(mocPath!); - expect(moc).toContain("# agentmemory vault"); - expect(moc).toContain("## Memories (0)"); - }); - - it("exports memories with YAML frontmatter", async () => { - const mem = makeMemory("mem_001"); - await kv.set("mem:memories", mem.id, mem); - - const result = (await sdk.trigger("mem::obsidian-export", {})) as { - exported: Record; - }; - - expect(result.exported.memories).toBe(1); - - const memFile = [...writtenFiles.entries()].find(([k]) => - k.includes("memories/mem_001.md"), - ); - expect(memFile).toBeDefined(); - const content = memFile![1]; - expect(content).toContain("---"); - expect(content).toContain('id: "mem_001"'); - expect(content).toContain('type: "pattern"'); - expect(content).toContain("# Memory mem_001"); - expect(content).toContain("#testing"); - }); - - it("exports lessons with confidence and source", async () => { - const lesson = makeLesson("lsn_001"); - await kv.set("mem:lessons", lesson.id, lesson); - - const result = (await sdk.trigger("mem::obsidian-export", {})) as { - exported: Record; - }; - - expect(result.exported.lessons).toBe(1); - - const lsnFile = [...writtenFiles.entries()].find(([k]) => - k.includes("lessons/lsn_001.md"), - ); - expect(lsnFile).toBeDefined(); - const content = lsnFile![1]; - expect(content).toContain("confidence: 0.8"); - expect(content).toContain("reinforcements: 2"); - expect(content).toContain('source: "manual"'); - }); - - it("exports crystals with wikilinks to source actions", async () => { - const crystal = makeCrystal("crys_001"); - await kv.set("mem:crystals", crystal.id, crystal); - - await sdk.trigger("mem::obsidian-export", {}); - - const crysFile = [...writtenFiles.entries()].find(([k]) => - k.includes("crystals/crys_001.md"), - ); - expect(crysFile).toBeDefined(); - expect(crysFile![1]).toContain("[[act_1]]"); - expect(crysFile![1]).toContain("Outcome 1"); - }); - - it("respects types filter", async () => { - await kv.set("mem:memories", "mem_001", makeMemory("mem_001")); - await kv.set("mem:lessons", "lsn_001", makeLesson("lsn_001")); - - const result = (await sdk.trigger("mem::obsidian-export", { - types: ["lessons"], - })) as { exported: Record }; - - expect(result.exported.lessons).toBe(1); - expect(result.exported.memories).toBe(0); - }); - - it("respects custom vaultDir", async () => { - await sdk.trigger("mem::obsidian-export", { - vaultDir: "/tmp/agentmemory-export-root/test-vault", - }); - - const hasCustomPath = [...createdDirs].some((d) => - d.startsWith("/tmp/agentmemory-export-root/test-vault"), - ); - expect(hasCustomPath).toBe(true); - }); - - it("rejects vaultDir outside the export root", async () => { - const result = (await sdk.trigger("mem::obsidian-export", { - vaultDir: "/tmp/outside-root", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain(exportRoot); - }); - - it("skips deleted lessons", async () => { - const lesson = makeLesson("lsn_deleted"); - (lesson as any).deleted = true; - await kv.set("mem:lessons", lesson.id, lesson); - - const result = (await sdk.trigger("mem::obsidian-export", {})) as { - exported: Record; - }; - - expect(result.exported.lessons).toBe(0); - }); - - it("skips non-latest memories", async () => { - const mem = makeMemory("mem_old"); - mem.isLatest = false; - await kv.set("mem:memories", mem.id, mem); - - const result = (await sdk.trigger("mem::obsidian-export", {})) as { - exported: Record; - }; - - expect(result.exported.memories).toBe(0); - }); - - it("MOC groups entries by type", async () => { - await kv.set("mem:memories", "mem_001", makeMemory("mem_001")); - await kv.set("mem:lessons", "lsn_001", makeLesson("lsn_001")); - await kv.set("mem:crystals", "crys_001", makeCrystal("crys_001")); - await kv.set("mem:sessions", "ses_001", makeSession("ses_001")); - - await sdk.trigger("mem::obsidian-export", {}); - - const mocPath = [...writtenFiles.keys()].find((k) => k.endsWith("MOC.md")); - const moc = writtenFiles.get(mocPath!)!; - - expect(moc).toContain("## Memories (1)"); - expect(moc).toContain("## Lessons (1)"); - expect(moc).toContain("## Crystals (1)"); - expect(moc).toContain("## Sessions (1)"); - expect(moc).toContain("[[memories/mem_001|"); - expect(moc).toContain("[[lessons/lsn_001|"); - expect(moc).toContain("[[crystals/crys_001|"); - expect(moc).toContain("[[sessions/ses_001|"); - }); - - it("returns undefined errors on success", async () => { - await kv.set("mem:memories", "mem_001", makeMemory("mem_001")); - - const result = (await sdk.trigger("mem::obsidian-export", {})) as { - success: boolean; - errors?: unknown[]; - }; - - expect(result.success).toBe(true); - expect(result.errors).toBeUndefined(); - }); -}); diff --git a/test/onboarding.test.ts b/test/onboarding.test.ts deleted file mode 100644 index f0629f26..00000000 --- a/test/onboarding.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { readFileSync } from "node:fs"; -import { describe, expect, it } from "vitest"; - -import { buildAgentOptions, getInitialAgentValues } from "../src/cli/onboarding.js"; - -describe("first-run onboarding", () => { - it("offers Codex as a native setup target", () => { - const options = buildAgentOptions(); - expect(options).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - value: "codex", - label: expect.stringContaining("Codex"), - hint: "native plugin", - }), - ]), - ); - }); - - it("selects Codex by default (the only wireable agent after PLAN-006 P2)", () => { - expect(getInitialAgentValues({ COPILOT_CLI: "1" })).toEqual(["codex"]); - expect(getInitialAgentValues({})).toEqual(["codex"]); - }); - - it("frames the first-run model selection as To-Do extraction (not memory compression)", () => { - const source = readFileSync("src/cli/onboarding.ts", "utf-8"); - expect(source).toContain("extract To-Dos"); - expect(source).not.toContain("memory compression/consolidation"); - expect(source).not.toContain("Not To-Do extraction"); - }); -}); diff --git a/test/openai-shared.test.ts b/test/openai-shared.test.ts deleted file mode 100644 index 135e213b..00000000 --- a/test/openai-shared.test.ts +++ /dev/null @@ -1,304 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { - buildAuthHeaders, - buildChatUrl, - buildEmbeddingUrl, - detectAzure, - normalizeBaseUrl, -} from "../src/providers/_openai-shared.js"; -import { OpenAIEmbeddingProvider } from "../src/providers/embedding/openai.js"; - -describe("_openai-shared — detectAzure", () => { - it("detects standard Azure resource hostname", () => { - expect( - detectAzure( - "https://myresource.openai.azure.com/openai/deployments/mydeploy", - ), - ).toBe(true); - }); - - it("does not flag api.openai.com", () => { - expect(detectAzure("https://api.openai.com")).toBe(false); - }); - - it("does not flag DeepSeek / SiliconFlow / Ollama / vLLM", () => { - expect(detectAzure("https://api.deepseek.com/v1")).toBe(false); - expect(detectAzure("https://api.siliconflow.cn")).toBe(false); - expect(detectAzure("http://localhost:11434/v1")).toBe(false); - expect(detectAzure("http://localhost:8000/v1")).toBe(false); - }); - - it("returns false for malformed URLs", () => { - expect(detectAzure("not-a-url")).toBe(false); - expect(detectAzure("")).toBe(false); - }); -}); - -describe("_openai-shared — buildChatUrl", () => { - it("appends /v1/chat/completions for standard OpenAI", () => { - expect(buildChatUrl("https://api.openai.com", false, "2024-08-01-preview")).toBe( - "https://api.openai.com/v1/chat/completions", - ); - }); - - it("appends /chat/completions + api-version for Azure", () => { - const url = buildChatUrl( - "https://myresource.openai.azure.com/openai/deployments/mydeploy", - true, - "2024-08-01-preview", - ); - expect(url).toBe( - "https://myresource.openai.azure.com/openai/deployments/mydeploy/chat/completions?api-version=2024-08-01-preview", - ); - }); - - it("URL-encodes the api-version", () => { - const url = buildChatUrl( - "https://r.openai.azure.com/openai/deployments/d", - true, - "preview/with/slashes", - ); - expect(url).toContain("api-version=preview%2Fwith%2Fslashes"); - }); - - it("preserves pre-existing query params on the base URL", () => { - // A corporate proxy or diagnostics endpoint might already carry - // query parameters on the base URL. String-concat would have - // interpolated the route path into the query string; URL-API - // composition keeps the query intact and adds api-version - // alongside. - const url = buildChatUrl( - "https://proxy.example.com/openai/deployments/d?tenant=acme", - true, - "2024-08-01-preview", - ); - const parsed = new URL(url); - expect(parsed.pathname).toBe("/openai/deployments/d/chat/completions"); - expect(parsed.searchParams.get("tenant")).toBe("acme"); - expect(parsed.searchParams.get("api-version")).toBe("2024-08-01-preview"); - }); - - it("strips trailing slashes from base path before joining route", () => { - const url = buildChatUrl( - "https://r.openai.azure.com/openai/deployments/d/", - true, - "2024-08-01-preview", - ); - expect(new URL(url).pathname).toBe("/openai/deployments/d/chat/completions"); - }); - - it("routes through /openai/v1 when the base URL has no /deployments/ segment (Azure v1 GA)", () => { - // Azure shipped a v1 URL pattern that mirrors the OpenAI shape: - // /openai/v1/chat/completions, deployment passed in the body as - // `model`. No api-version query param. - const url = buildChatUrl( - "https://r.openai.azure.com", - true, - "2024-08-01-preview", // ignored on v1 - ); - const parsed = new URL(url); - expect(parsed.pathname).toBe("/openai/v1/chat/completions"); - expect(parsed.searchParams.get("api-version")).toBeNull(); - }); - - it("strips a trailing /openai or /openai/v1 prefix when composing v1 URLs", () => { - // Users may pre-configure OPENAI_BASE_URL with the /openai/v1 - // suffix already present. We should not double it. - const fromOpenai = buildChatUrl( - "https://r.openai.azure.com/openai", - true, - "ignored", - ); - expect(new URL(fromOpenai).pathname).toBe("/openai/v1/chat/completions"); - - const fromV1 = buildChatUrl( - "https://r.openai.azure.com/openai/v1", - true, - "ignored", - ); - expect(new URL(fromV1).pathname).toBe("/openai/v1/chat/completions"); - }); -}); - -describe("_openai-shared — buildEmbeddingUrl", () => { - it("appends /v1/embeddings for standard OpenAI", () => { - expect( - buildEmbeddingUrl("https://api.openai.com", false, "2024-08-01-preview"), - ).toBe("https://api.openai.com/v1/embeddings"); - }); - - it("appends /embeddings + api-version for Azure legacy (no /v1/ prefix)", () => { - const url = buildEmbeddingUrl( - "https://r.openai.azure.com/openai/deployments/embed-deploy", - true, - "2024-08-01-preview", - ); - expect(url).toBe( - "https://r.openai.azure.com/openai/deployments/embed-deploy/embeddings?api-version=2024-08-01-preview", - ); - }); - - it("routes through /openai/v1/embeddings on Azure v1 (no api-version)", () => { - const url = buildEmbeddingUrl( - "https://r.openai.azure.com", - true, - "2024-08-01-preview", // ignored on v1 - ); - const parsed = new URL(url); - expect(parsed.pathname).toBe("/openai/v1/embeddings"); - expect(parsed.searchParams.get("api-version")).toBeNull(); - }); -}); - -describe("_openai-shared — non-OpenAI base URLs (#628, #646)", () => { - it("does not double /v1 when base URL already ends with /v1 (DeepSeek shape, #628)", () => { - expect( - buildChatUrl("https://api.deepseek.com/v1", false, "2024-08-01-preview"), - ).toBe("https://api.deepseek.com/v1/chat/completions"); - expect( - buildEmbeddingUrl("https://api.deepseek.com/v1", false, "2024-08-01-preview"), - ).toBe("https://api.deepseek.com/v1/embeddings"); - }); - - it("does not inject /v1 when provider uses non-OpenAI version segment (Zhipu /api/paas/v4, #646)", () => { - expect( - buildChatUrl( - "https://open.bigmodel.cn/api/paas/v4", - false, - "2024-08-01-preview", - ), - ).toBe("https://open.bigmodel.cn/api/paas/v4/chat/completions"); - expect( - buildEmbeddingUrl( - "https://open.bigmodel.cn/api/paas/v4", - false, - "2024-08-01-preview", - ), - ).toBe("https://open.bigmodel.cn/api/paas/v4/embeddings"); - }); - - it("tolerates trailing slash on already-versioned base", () => { - expect( - buildChatUrl("https://api.deepseek.com/v1/", false, "2024-08-01-preview"), - ).toBe("https://api.deepseek.com/v1/chat/completions"); - }); - - it("handles localhost OpenAI-compatible servers with explicit /v1", () => { - expect( - buildChatUrl("http://localhost:11434/v1", false, "2024-08-01-preview"), - ).toBe("http://localhost:11434/v1/chat/completions"); - expect( - buildChatUrl("http://localhost:8000/v1", false, "2024-08-01-preview"), - ).toBe("http://localhost:8000/v1/chat/completions"); - }); -}); - -describe("_openai-shared — buildAuthHeaders", () => { - it("emits Authorization: Bearer for standard OpenAI", () => { - expect(buildAuthHeaders("sk-test", false)).toEqual({ - "Content-Type": "application/json", - Authorization: "Bearer sk-test", - }); - }); - - it("emits api-key header for Azure", () => { - expect(buildAuthHeaders("azure-key", true)).toEqual({ - "Content-Type": "application/json", - "api-key": "azure-key", - }); - }); -}); - -describe("_openai-shared — normalizeBaseUrl", () => { - it("returns default when no value passed", () => { - expect(normalizeBaseUrl(undefined)).toBe("https://api.openai.com"); - expect(normalizeBaseUrl("")).toBe("https://api.openai.com"); - }); - - it("strips trailing slashes", () => { - expect(normalizeBaseUrl("https://api.deepseek.com/v1///")).toBe( - "https://api.deepseek.com/v1", - ); - }); - - it("returns explicit values unchanged otherwise", () => { - expect(normalizeBaseUrl("https://api.deepseek.com/v1")).toBe( - "https://api.deepseek.com/v1", - ); - }); -}); - -// ───────────────────────────────────────────────────────────── -// OpenAIEmbeddingProvider — Azure transport (#371) -// Verifies the embedding path now uses the shared Azure helpers: -// hits /embeddings (not /v1/embeddings), includes api-version, uses -// api-key header instead of Authorization: Bearer. -// ───────────────────────────────────────────────────────────── -describe("OpenAIEmbeddingProvider — Azure auto-detection (#371)", () => { - const ORIGINAL_BASE = process.env["OPENAI_BASE_URL"]; - const ORIGINAL_VERSION = process.env["OPENAI_API_VERSION"]; - - beforeEach(() => { - vi.restoreAllMocks(); - }); - - afterEach(() => { - if (ORIGINAL_BASE === undefined) delete process.env["OPENAI_BASE_URL"]; - else process.env["OPENAI_BASE_URL"] = ORIGINAL_BASE; - if (ORIGINAL_VERSION === undefined) delete process.env["OPENAI_API_VERSION"]; - else process.env["OPENAI_API_VERSION"] = ORIGINAL_VERSION; - vi.restoreAllMocks(); - }); - - it("uses Azure shape when OPENAI_BASE_URL points at *.openai.azure.com", async () => { - process.env["OPENAI_BASE_URL"] = - "https://myres.openai.azure.com/openai/deployments/embed-d"; - process.env["OPENAI_API_VERSION"] = "2024-08-01-preview"; - - let capturedUrl = ""; - let capturedHeaders = new Headers(); - vi.spyOn(globalThis, "fetch").mockImplementation( - async (url: string | URL | Request, init?: RequestInit) => { - capturedUrl = String(url); - capturedHeaders = new Headers(init?.headers); - return new Response( - JSON.stringify({ data: [{ embedding: [0.1, 0.2, 0.3] }] }), - { status: 200 }, - ); - }, - ); - - const provider = new OpenAIEmbeddingProvider("azure-key"); - await provider.embedBatch(["hello"]); - - expect(capturedUrl).toBe( - "https://myres.openai.azure.com/openai/deployments/embed-d/embeddings?api-version=2024-08-01-preview", - ); - expect(capturedHeaders.get("api-key")).toBe("azure-key"); - expect(capturedHeaders.get("Authorization")).toBeNull(); - }); - - it("uses standard shape when OPENAI_BASE_URL points at api.openai.com", async () => { - process.env["OPENAI_BASE_URL"] = "https://api.openai.com"; - - let capturedUrl = ""; - let capturedHeaders = new Headers(); - vi.spyOn(globalThis, "fetch").mockImplementation( - async (url: string | URL | Request, init?: RequestInit) => { - capturedUrl = String(url); - capturedHeaders = new Headers(init?.headers); - return new Response( - JSON.stringify({ data: [{ embedding: [0.4, 0.5, 0.6] }] }), - { status: 200 }, - ); - }, - ); - - const provider = new OpenAIEmbeddingProvider("sk-test"); - await provider.embedBatch(["hello"]); - - expect(capturedUrl).toBe("https://api.openai.com/v1/embeddings"); - expect(capturedHeaders.get("Authorization")).toBe("Bearer sk-test"); - expect(capturedHeaders.get("api-key")).toBeNull(); - }); -}); diff --git a/test/organize.test.ts b/test/organize.test.ts new file mode 100644 index 00000000..621a6d19 --- /dev/null +++ b/test/organize.test.ts @@ -0,0 +1,669 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { openDatabase } from "../src/db/index.js"; +import { getAppPaths } from "../src/paths.js"; +import { createAppServer } from "../src/server/index.js"; +import { ingestBrowserSession } from "../src/sources/browser.js"; +import { listTodos, organizeTodos, scopeObservations } from "../src/todos/service.js"; + +test("organize without an LLM extractor creates no rule fallback cards", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-no-llm-")); + try { + const db = openDatabase(getAppPaths(dir)); + ingestBrowserSession(db, { + id: "browser-1", + messages: [ + { role: "user", text: "Please add a CLI doctor command" }, + { role: "assistant", text: "Implemented the doctor command" } + ] + }); + + const result = await organizeTodos(db); + const todos = listTodos(db); + db.close(); + + assert.equal(result.engine, "llm"); + assert.deepEqual(result.warnings, ["llm_config_missing"]); + assert.equal(result.scanned, 2); + assert.equal(result.created, 0); + assert.equal(result.updated, 0); + assert.equal(todos.length, 0); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("organize endpoint returns zero cards when LLM config is missing", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-http-")); + const paths = getAppPaths(dir); + const db = openDatabase(paths); + ingestBrowserSession(db, { + id: "browser-1", + messages: [{ role: "user", text: "Need update settings persistence" }] + }); + const server = createAppServer({ db, paths }); + + await new Promise((resolve) => server.listen(0, resolve)); + try { + const address = server.address(); + assert.ok(address && typeof address !== "string"); + const response = await fetch(`http://127.0.0.1:${address.port}/todos/organize`, { method: "POST" }); + const result = await response.json(); + assert.equal(response.status, 200); + assert.equal(result.engine, "llm"); + assert.equal(result.created, 0); + assert.equal(result.ignored, 0); + assert.deepEqual(result.warnings, ["llm_config_missing"]); + } finally { + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + db.close(); + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("organize endpoint can use configured llm extraction", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-http-llm-")); + const paths = getAppPaths(dir); + const db = openDatabase(paths); + ingestBrowserSession(db, { + id: "browser-1", + messages: [{ role: "user", text: "Please add HTTP LLM cards" }] + }); + const observationId = String((db.prepare("SELECT id FROM observations LIMIT 1").get() as any).id); + const server = createAppServer({ + db, + paths, + organizeOptions: { + llmExtractor: async () => ({ + ok: true, + todos: [{ + title: "Add HTTP LLM cards", + description: "Use the configured LLM path from the HTTP endpoint.", + confidence: 0.9, + sourceObservationId: observationId, + quote: "Please add HTTP LLM cards", + dedupeKey: "http-llm-cards" + }] + }) + } + }); + + await new Promise((resolve) => server.listen(0, resolve)); + try { + const address = server.address(); + assert.ok(address && typeof address !== "string"); + const response = await fetch(`http://127.0.0.1:${address.port}/todos/organize`, { method: "POST" }); + const result = await response.json(); + assert.equal(response.status, 200); + assert.equal(result.engine, "llm"); + assert.equal(result.created, 1); + assert.deepEqual(result.warnings, []); + } finally { + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + db.close(); + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("llm organize creates grounded cards and dedupes by model key", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-llm-")); + try { + const db = openDatabase(getAppPaths(dir)); + ingestBrowserSession(db, { + id: "browser-1", + messages: [ + { role: "user", text: "Please add LLM settings UI" }, + { role: "assistant", text: "I will add LLM settings UI controls." } + ] + }); + const observationId = String((db.prepare("SELECT id FROM observations WHERE role = 'user' LIMIT 1").get() as any).id); + + const extractor = async () => ({ + ok: true as const, + todos: [{ + title: "Add LLM settings UI", + description: "Add settings controls for the LLM provider.", + confidence: 0.91, + sourceObservationId: observationId, + quote: "Please add LLM settings UI", + dedupeKey: "add-llm-settings-ui" + }] + }); + const result = await organizeTodos(db, { llmExtractor: extractor }); + const second = await organizeTodos(db, { llmExtractor: extractor }); + const todos = listTodos(db); + db.close(); + + assert.equal(result.engine, "llm"); + assert.deepEqual(result.warnings, []); + assert.equal(result.created, 1); + assert.equal(second.created, 0); + assert.equal(second.updated, 1); + assert.equal(todos.length, 1); + assert.equal(todos[0].title, "Add LLM settings UI"); + assert.equal(todos[0].evidenceIds.length, 1); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("llm organize batches input without rules fallback for failed batches", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-llm-batch-")); + try { + const db = openDatabase(getAppPaths(dir)); + ingestBrowserSession(db, { + id: "browser-1", + messages: [ + { role: "user", text: "Please add batched card one" }, + { role: "user", text: "Please add batched card two" }, + { role: "user", text: "Please add batched card three" } + ] + }); + let calls = 0; + const result = await organizeTodos(db, { + limits: { llmBatchSize: 2 }, + llmExtractor: async (observations) => { + calls++; + if (calls === 2) return { ok: false, warning: "llm_timeout" }; + const observation = observations.find((item) => item.role === "user")!; + return { + ok: true, + todos: [{ + title: "LLM batched card one", + description: "Create the first batched card through LLM.", + confidence: 0.9, + sourceObservationId: observation.id, + quote: observation.text, + dedupeKey: "batched-card-one" + }] + }; + } + }); + const todos = listTodos(db); + db.close(); + + assert.equal(calls, 2); + assert.equal(result.engine, "llm"); + assert.deepEqual(result.warnings.sort(), ["llm_batch_failed", "llm_timeout"].sort()); + assert.equal(result.created, 1); + assert.equal(todos.length, 1); + assert.equal(todos[0].title, "LLM batched card one"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("llm organize batches by session instead of mixing sessions", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-session-batch-")); + try { + const db = openDatabase(getAppPaths(dir)); + ingestBrowserSession(db, { + id: "browser-1", + messages: [ + { role: "user", text: "Please add session one card" }, + { role: "assistant", text: "I will add session one card." } + ] + }); + ingestBrowserSession(db, { + id: "browser-2", + messages: [ + { role: "user", text: "Please add session two card" }, + { role: "assistant", text: "I will add session two card." } + ] + }); + const calls: string[][] = []; + const result = await organizeTodos(db, { + limits: { llmBatchSize: 20 }, + llmExtractor: async (observations) => { + calls.push(Array.from(new Set(observations.map((item) => item.sessionId)))); + const observation = observations.find((item) => item.role === "user")!; + return { + ok: true, + todos: [{ + title: observation.text.replace("Please add", "Add"), + description: "Create the session scoped todo card.", + confidence: 0.9, + sourceObservationId: observation.id, + quote: observation.text, + dedupeKey: observation.sessionId + }] + }; + } + }); + db.close(); + + assert.equal(result.created, 2); + assert.deepEqual(calls, [["browser-1"], ["browser-2"]]); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("organize applies batch payload budget per LLM request instead of globally", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-batch-budget-")); + try { + const db = openDatabase(getAppPaths(dir)); + for (const sessionId of ["budget-1", "budget-2", "budget-3"]) { + ingestBrowserSession(db, { + id: sessionId, + messages: [{ role: "user", text: `Please add ${sessionId} card` }] + }); + } + + const sessionsSeen: string[] = []; + const result = await organizeTodos(db, { + limits: { + maxTotalTextChars: 1000, + maxBatchPayloadChars: 40, + llmBatchSize: 20 + }, + llmExtractor: async (observations) => { + const observation = observations.find((item) => item.role === "user")!; + sessionsSeen.push(observation.sessionId); + return { + ok: true, + todos: [{ + title: `Add ${observation.sessionId} card`, + description: "Create the card from a separate LLM request.", + confidence: 0.9, + sourceObservationId: observation.id, + quote: observation.text, + dedupeKey: observation.sessionId + }] + }; + } + }); + db.close(); + + assert.equal(result.created, 3); + assert.deepEqual(sessionsSeen.sort(), ["budget-1", "budget-2", "budget-3"]); + assert.ok(!result.warnings.includes("organize_scope_truncated")); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("llm organize extracts batches with bounded concurrency", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-concurrency-")); + try { + const db = openDatabase(getAppPaths(dir)); + for (const sessionId of ["concurrent-1", "concurrent-2", "concurrent-3", "concurrent-4"]) { + ingestBrowserSession(db, { + id: sessionId, + messages: [{ role: "user", text: `Please add ${sessionId} card` }] + }); + } + + let active = 0; + let maxActive = 0; + const result = await organizeTodos(db, { + limits: { llmBatchSize: 20, llmConcurrency: 2 }, + llmExtractor: async (observations) => { + active++; + maxActive = Math.max(maxActive, active); + await new Promise((resolve) => setTimeout(resolve, 20)); + active--; + const observation = observations.find((item) => item.role === "user")!; + return { + ok: true, + todos: [{ + title: `Add ${observation.sessionId} card`, + description: "Create the card from a concurrent LLM request.", + confidence: 0.9, + sourceObservationId: observation.id, + quote: observation.text, + dedupeKey: observation.sessionId + }] + }; + } + }); + db.close(); + + assert.equal(result.created, 4); + assert.equal(maxActive, 2); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("organize limits scoped observations before LLM extraction", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-limits-")); + try { + const db = openDatabase(getAppPaths(dir)); + ingestBrowserSession(db, { + id: "browser-1", + messages: [ + { role: "user", text: `Please add ${"x".repeat(30)} one` }, + { role: "user", text: "Please add limited block two" }, + { role: "user", text: "Please add limited block three" } + ] + }); + const result = await organizeTodos(db, { + limits: { + maxUserBlocks: 2, + maxTotalTextChars: 80, + maxBlockTextChars: 20, + llmBatchSize: 20 + }, + llmExtractor: async (observations) => { + assert.equal(observations.filter((item) => item.role === "user").length, 2); + assert.ok(observations.every((item) => item.text.length <= 20)); + return { ok: true, todos: [] }; + } + }); + db.close(); + + assert.equal(result.engine, "llm"); + assert.ok(result.warnings.includes("llm_input_truncated")); + assert.ok(result.warnings.includes("organize_scope_truncated")); + assert.ok(result.warnings.includes("llm_no_valid_candidates")); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("organize limits newest sessions before per-session extraction", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-max-sessions-")); + try { + const db = openDatabase(getAppPaths(dir)); + insertObservation(db, "old-obs", "old-session", "browser", "user", "Please add old card", new Date(Date.now() - 60_000).toISOString()); + insertObservation(db, "new-obs", "new-session", "browser", "user", "Please add new card", new Date(Date.now()).toISOString()); + + const calls: string[] = []; + const result = await organizeTodos(db, { + scope: { sinceDays: 7, maxInteractionsPerSession: 10, maxSessions: 1, maxObservationsPerSession: 40 }, + llmExtractor: async (observations) => { + const observation = observations.find((item) => item.role === "user")!; + calls.push(observation.sessionId); + return { + ok: true, + todos: [{ + title: "Add new card", + description: "Create the newest scoped todo card.", + confidence: 0.9, + sourceObservationId: observation.id, + quote: observation.text, + dedupeKey: "new-card" + }] + }; + } + }); + db.close(); + + assert.deepEqual(calls, ["new-session"]); + assert.equal(result.created, 1); + assert.ok(result.warnings.includes("organize_scope_truncated")); + assert.equal(result.details?.scope?.sessionsDropped, 1); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("organize trims final LLM payload for user and assistant text with details", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-payload-budget-")); + try { + const db = openDatabase(getAppPaths(dir)); + ingestBrowserSession(db, { + id: "browser-1", + messages: [ + { role: "user", text: `Please add ${"user ".repeat(100)}card` }, + { role: "assistant", text: `Assistant status ${"assistant ".repeat(100)}remaining work` } + ] + }); + const result = await organizeTodos(db, { + limits: { + maxUserBlocks: 120, + maxTotalTextChars: 80000, + maxBlockTextChars: 3000, + llmBatchSize: 20, + maxObservationTextChars: 60, + maxSessionPayloadChars: 160, + maxBatchPayloadChars: 220 + }, + llmExtractor: async (observations) => { + assert.ok(observations.some((item) => item.role === "assistant" && item.text.length <= 60)); + assert.ok(observations.every((item) => item.text.length <= 60)); + return { ok: true, todos: [] }; + } + }); + db.close(); + + assert.ok(result.warnings.includes("llm_input_truncated")); + assert.ok(result.details?.truncations?.some((item) => item.sessionId === "browser-1" && item.role === "assistant")); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("organize records provider failure details while other sessions succeed", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-failure-details-")); + try { + const db = openDatabase(getAppPaths(dir)); + ingestBrowserSession(db, { id: "failed-session", messages: [{ role: "user", text: "Please add failed card" }] }); + ingestBrowserSession(db, { id: "ok-session", messages: [{ role: "user", text: "Please add ok card" }] }); + + const result = await organizeTodos(db, { + llmExtractor: async (observations) => { + const observation = observations.find((item) => item.role === "user")!; + if (observation.sessionId === "failed-session") { + return { ok: false, warning: "llm_provider_failed", reason: "http_401", retryable: true }; + } + return { + ok: true, + todos: [{ + title: "Add ok card", + description: "Create the card from the successful session.", + confidence: 0.9, + sourceObservationId: observation.id, + quote: observation.text, + dedupeKey: "ok-card" + }] + }; + } + }); + const todos = listTodos(db); + db.close(); + + assert.equal(result.created, 1); + assert.equal(todos.length, 1); + assert.ok(result.warnings.includes("llm_provider_failed")); + assert.ok(result.warnings.includes("llm_batch_failed")); + assert.deepEqual(result.details?.batchFailures?.map((item) => ({ + sessionId: item.sessionId, + warning: item.warning, + reason: item.reason, + retryable: item.retryable + })), [{ + sessionId: "failed-session", + warning: "llm_provider_failed", + reason: "http_401", + retryable: true + }]); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("llm organize rejects process, status, and tool-polluted candidates", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-quality-")); + try { + const db = openDatabase(getAppPaths(dir)); + ingestBrowserSession(db, { + id: "browser-1", + messages: [ + { role: "user", text: "我会做最后一次状态确认,确保工作区干净。服务可用,健康检查已完成。" }, + { role: "assistant", text: "Bash(git status) process exited 0" } + ] + }); + const observationId = String((db.prepare("SELECT id FROM observations WHERE role = 'user' LIMIT 1").get() as any).id); + const result = await organizeTodos(db, { + llmExtractor: async () => ({ + ok: true, + todos: [{ + title: "做最后一次状态确认", + description: "确认工作区干净并检查服务可用。", + confidence: 0.95, + sourceObservationId: observationId, + quote: "做最后一次状态确认", + dedupeKey: "final-status-check" + }] + }) + }); + const todos = listTodos(db); + db.close(); + + assert.equal(result.created, 0); + assert.deepEqual(result.warnings, ["llm_no_valid_candidates"]); + assert.equal(todos.length, 0); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("llm organize suppresses near duplicate cards with different model keys", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-near-dupe-")); + try { + const db = openDatabase(getAppPaths(dir)); + ingestBrowserSession(db, { + id: "browser-1", + messages: [ + { role: "user", text: "后续需要修复 CI 失败,并重新跑测试。" }, + { role: "user", text: "需要重新跑测试并修复 CI 失败。" } + ] + }); + const rows = db.prepare("SELECT id, text FROM observations WHERE role = 'user' ORDER BY id").all() as Array<{ id: string; text: string }>; + const result = await organizeTodos(db, { + llmExtractor: async () => ({ + ok: true, + todos: [ + { + title: "修复 CI 失败并重新跑测试", + description: "后续需要修复 CI 失败,并重新跑测试。", + confidence: 0.9, + sourceObservationId: rows[0].id, + quote: rows[0].text, + dedupeKey: "fix-ci-rerun-tests" + }, + { + title: "重新跑测试并修复 CI 失败", + description: "需要重新跑测试并修复 CI 失败。", + confidence: 0.88, + sourceObservationId: rows[1].id, + quote: rows[1].text, + dedupeKey: "rerun-tests-fix-ci" + } + ] + }) + }); + const todos = listTodos(db); + db.close(); + + assert.equal(result.created, 1); + assert.equal(todos.length, 1); + assert.equal(todos[0].title, "修复 CI 失败并重新跑测试"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("llm unavailable returns warnings without creating rules cards", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-llm-unavailable-")); + try { + const db = openDatabase(getAppPaths(dir)); + ingestBrowserSession(db, { + id: "browser-1", + messages: [{ role: "user", text: "Please add fallback warnings" }] + }); + + const result = await organizeTodos(db, { + llmExtractor: async () => ({ ok: false, warning: "llm_config_missing" }) + }); + const todos = listTodos(db); + db.close(); + + assert.equal(result.engine, "llm"); + assert.deepEqual(result.warnings, ["llm_config_missing"]); + assert.equal(result.created, 0); + assert.equal(todos.length, 0); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("llm organize rejects ungrounded model output without rules fallback", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-organize-llm-invalid-")); + try { + const db = openDatabase(getAppPaths(dir)); + ingestBrowserSession(db, { + id: "browser-1", + messages: [{ role: "user", text: "Please add grounded evidence checks" }] + }); + const observationId = String((db.prepare("SELECT id FROM observations LIMIT 1").get() as any).id); + + const result = await organizeTodos(db, { + llmExtractor: async () => ({ + ok: true, + todos: [{ + title: "Add unrelated todo", + description: "This quote is not grounded.", + confidence: 0.9, + sourceObservationId: observationId, + quote: "missing quote", + dedupeKey: "bad" + }] + }) + }); + const todos = listTodos(db); + db.close(); + + assert.equal(result.engine, "llm"); + assert.deepEqual(result.warnings, ["llm_no_valid_candidates"]); + assert.equal(result.created, 0); + assert.equal(todos.length, 0); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("organize scope filters old observations and keeps recent interactions per session", () => { + const now = Date.now(); + const observations = [ + observation("old", "s1", "user", "old", new Date(now - 10 * 86400000).toISOString()), + observation("u1", "s1", "user", "one", new Date(now - 3000).toISOString()), + observation("a1", "s1", "assistant", "reply one", new Date(now - 2500).toISOString()), + observation("u2", "s1", "user", "two", new Date(now - 2000).toISOString()), + observation("a2", "s1", "assistant", "reply two", new Date(now - 1500).toISOString()), + observation("u3", "s1", "user", "three", new Date(now - 1000).toISOString()) + ]; + assert.deepEqual( + scopeObservations(observations, { sinceDays: 7, maxInteractionsPerSession: 2 }).map((item) => item.id), + ["u2", "a2", "u3"] + ); +}); + +function observation(id: string, sessionId: string, role: string, text: string, createdAt: string) { + return { id, sessionId, source: "browser" as const, role, text, createdAt }; +} + +function insertObservation( + db: ReturnType, + id: string, + sessionId: string, + source: string, + role: string, + text: string, + createdAt: string +) { + db.prepare("INSERT OR REPLACE INTO sessions (id, source, path, updated_at) VALUES (?, ?, ?, ?)").run(sessionId, source, sessionId, createdAt); + db.prepare("INSERT INTO observations (id, session_id, source, role, text, created_at) VALUES (?, ?, ?, ?, ?, ?)").run( + id, + sessionId, + source, + role, + text, + createdAt + ); +} diff --git a/test/package-scripts.test.ts b/test/package-scripts.test.ts deleted file mode 100644 index c4d7bc22..00000000 --- a/test/package-scripts.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { readFileSync } from "node:fs"; -import { join } from "node:path"; -import { describe, expect, it } from "vitest"; - -const ROOT = join(import.meta.dirname, ".."); - -describe("package scripts", () => { - it("installs LangExtract Python dependencies during npm install", () => { - const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8")) as { - files?: string[]; - scripts?: Record; - }; - - expect(pkg.scripts?.postinstall).toBe("node scripts/install-langextract.mjs"); - expect(pkg.files).toContain("scripts/install-langextract.mjs"); - expect(readFileSync(join(ROOT, "scripts/install-langextract.mjs"), "utf-8")).toContain(".agentmemory-python"); - }); -}); diff --git a/test/preferences.test.ts b/test/preferences.test.ts deleted file mode 100644 index 9989b833..00000000 --- a/test/preferences.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -const ORIGINAL_HOME = process.env["HOME"]; -const ORIGINAL_USERPROFILE = process.env["USERPROFILE"]; - -let sandboxHome: string; - -async function freshPrefs() { - vi.resetModules(); - return await import("../src/cli/preferences.js"); -} - -describe("cli preferences", () => { - beforeEach(() => { - sandboxHome = mkdtempSync(join(tmpdir(), "agentmemory-prefs-")); - process.env["HOME"] = sandboxHome; - process.env["USERPROFILE"] = sandboxHome; - }); - - afterEach(() => { - if (ORIGINAL_HOME === undefined) delete process.env["HOME"]; - else process.env["HOME"] = ORIGINAL_HOME; - if (ORIGINAL_USERPROFILE === undefined) delete process.env["USERPROFILE"]; - else process.env["USERPROFILE"] = ORIGINAL_USERPROFILE; - rmSync(sandboxHome, { recursive: true, force: true }); - }); - - it("returns defaults when no preferences file exists", async () => { - const { readPrefs } = await freshPrefs(); - const p = readPrefs(); - expect(p.schemaVersion).toBe(1); - expect(p.lastAgent).toBeNull(); - expect(p.lastAgents).toEqual([]); - expect(p.lastProvider).toBeNull(); - expect(p.skipSplash).toBe(false); - expect(p.skipNpxHint).toBe(false); - expect(p.firstRunAt).toBeNull(); - }); - - it("isFirstRun is true when no preferences file exists", async () => { - const { isFirstRun } = await freshPrefs(); - expect(isFirstRun()).toBe(true); - }); - - it("writePrefs persists values and merges with existing keys", async () => { - const { writePrefs, readPrefs, prefsPath } = await freshPrefs(); - writePrefs({ lastAgent: "claude-code", lastAgents: ["claude-code", "cursor"] }); - let p = readPrefs(); - expect(p.lastAgent).toBe("claude-code"); - expect(p.lastAgents).toEqual(["claude-code", "cursor"]); - expect(p.lastProvider).toBeNull(); - - writePrefs({ lastProvider: "anthropic", skipSplash: true }); - p = readPrefs(); - expect(p.lastAgent).toBe("claude-code"); - expect(p.lastProvider).toBe("anthropic"); - expect(p.skipSplash).toBe(true); - - const raw = JSON.parse(readFileSync(prefsPath(), "utf-8")); - expect(raw.schemaVersion).toBe(1); - expect(raw.lastAgents).toEqual(["claude-code", "cursor"]); - }); - - it("isFirstRun flips to false after firstRunAt is recorded", async () => { - const { writePrefs, isFirstRun } = await freshPrefs(); - writePrefs({ firstRunAt: new Date().toISOString() }); - expect(isFirstRun()).toBe(false); - }); - - it("readPrefs falls back to defaults when the file is corrupt", async () => { - const { readPrefs, prefsDir, prefsPath } = await freshPrefs(); - mkdirSync(prefsDir(), { recursive: true }); - writeFileSync(prefsPath(), "{not json", "utf-8"); - const p = readPrefs(); - expect(p.lastAgent).toBeNull(); - expect(p.schemaVersion).toBe(1); - }); - - it("readPrefs forces schemaVersion to 1 even when the file lies", async () => { - const { readPrefs, prefsDir, prefsPath } = await freshPrefs(); - mkdirSync(prefsDir(), { recursive: true }); - writeFileSync( - prefsPath(), - JSON.stringify({ schemaVersion: 99, lastAgent: "cursor" }), - "utf-8", - ); - const p = readPrefs(); - expect(p.schemaVersion).toBe(1); - expect(p.lastAgent).toBe("cursor"); - }); - - it("resetPrefs removes the file", async () => { - const { writePrefs, resetPrefs, isFirstRun, prefsPath } = await freshPrefs(); - writePrefs({ firstRunAt: new Date().toISOString() }); - expect(isFirstRun()).toBe(false); - resetPrefs(); - expect(() => readFileSync(prefsPath())).toThrow(); - expect(isFirstRun()).toBe(true); - }); -}); diff --git a/test/privacy.test.ts b/test/privacy.test.ts deleted file mode 100644 index 450e2091..00000000 --- a/test/privacy.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { stripPrivateData } from "../src/functions/privacy.js"; - -describe("stripPrivateData", () => { - it("strips private tags", () => { - expect(stripPrivateData("hello secret world")).toBe( - "hello [REDACTED] world", - ); - }); - - it("strips private tags case-insensitive", () => { - expect(stripPrivateData("data")).toBe("[REDACTED]"); - }); - - it("strips API keys", () => { - expect(stripPrivateData("api_key=sk-ant-1234567890abcdefghij")).toBe( - "[REDACTED_SECRET]", - ); - }); - - it("strips GitHub PATs", () => { - expect( - stripPrivateData("token: ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"), - ).toBe("[REDACTED_SECRET]"); - }); - - it("strips standalone GitHub PATs", () => { - expect( - stripPrivateData("found ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij here"), - ).toBe("found [REDACTED_SECRET] here"); - }); - - it("strips Slack tokens", () => { - expect(stripPrivateData("xoxb-123456-789012-abcdef")).toBe( - "[REDACTED_SECRET]", - ); - }); - - it("strips AWS access keys", () => { - expect(stripPrivateData("key=AKIAIOSFODNN7EXAMPLE")).toBe( - "key=[REDACTED_SECRET]", - ); - }); - - it("strips JWT tokens", () => { - expect( - stripPrivateData( - "eyJhbGciOiJIUzI1.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.SflKxwRJSMeKKF2QT4fwpM", - ), - ).toBe("[REDACTED_SECRET]"); - }); - - it("strips sk- prefixed keys", () => { - expect(stripPrivateData("sk-1234567890abcdefghijklmnopqr")).toBe( - "[REDACTED_SECRET]", - ); - }); - - it("strips OpenAI project keys", () => { - expect( - stripPrivateData("sk-proj-1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJ"), - ).toBe("[REDACTED_SECRET]"); - }); - - it("strips GitHub fine-grained service tokens", () => { - expect( - stripPrivateData("ghs_1234567890abcdefghijklmnopqrstuvwxyzAB"), - ).toBe("[REDACTED_SECRET]"); - }); - - it("strips bearer tokens", () => { - expect( - stripPrivateData( - "Authorization: Bearer sk-proj-1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJ", - ), - ).toBe("Authorization: [REDACTED_SECRET]"); - }); - - it("handles multiple secrets in one string", () => { - const input = - "sk-abcdefghijklmnopqrstuv and ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij"; - const result = stripPrivateData(input); - expect(result).not.toContain("sk-"); - expect(result).not.toContain("ghp_"); - }); - - it("does not strip short strings", () => { - expect(stripPrivateData("api_key=short")).toBe("api_key=short"); - }); - - it("returns empty string unchanged", () => { - expect(stripPrivateData("")).toBe(""); - }); - - it("handles no secrets gracefully", () => { - expect(stripPrivateData("normal text without secrets")).toBe( - "normal text without secrets", - ); - }); - - it("works correctly on consecutive calls (no regex statefulness)", () => { - const input = "sk-ABCDEFGHIJKLMNOPQRSTUVWXYZabc"; - expect(stripPrivateData(input)).toBe("[REDACTED_SECRET]"); - expect(stripPrivateData(input)).toBe("[REDACTED_SECRET]"); - expect(stripPrivateData(input)).toBe("[REDACTED_SECRET]"); - }); -}); diff --git a/test/profile.test.ts b/test/profile.test.ts deleted file mode 100644 index fbc7b017..00000000 --- a/test/profile.test.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerProfileFunction } from "../src/functions/profile.js"; -import type { - CompressedObservation, - Session, - ProjectProfile, -} from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("Profile Function", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(async () => { - sdk = mockSdk(); - kv = mockKV(); - registerProfileFunction(sdk as never, kv as never); - - const session: Session = { - id: "ses_1", - project: "my-project", - cwd: "/tmp/my-project", - startedAt: "2026-02-01T00:00:00Z", - status: "completed", - observationCount: 3, - }; - await kv.set("mem:sessions", "ses_1", session); - - const obs1: CompressedObservation = { - id: "obs_1", - sessionId: "ses_1", - timestamp: "2026-02-01T10:00:00Z", - type: "file_edit", - title: "Edit auth module", - facts: [], - narrative: "Auth changes", - concepts: ["typescript", "authentication"], - files: ["/project/src/auth.ts", "/project/src/middleware.ts"], - importance: 8, - }; - const obs2: CompressedObservation = { - id: "obs_2", - sessionId: "ses_1", - timestamp: "2026-02-01T11:00:00Z", - type: "file_edit", - title: "Update database", - facts: [], - narrative: "DB changes", - concepts: ["typescript", "database"], - files: ["/project/src/db.ts"], - importance: 6, - }; - const obs3: CompressedObservation = { - id: "obs_3", - sessionId: "ses_1", - timestamp: "2026-02-01T12:00:00Z", - type: "error", - title: "Connection timeout", - facts: [], - narrative: "Error occurred", - concepts: ["error"], - files: ["/project/src/db.ts"], - importance: 4, - }; - - await kv.set("mem:obs:ses_1", "obs_1", obs1); - await kv.set("mem:obs:ses_1", "obs_2", obs2); - await kv.set("mem:obs:ses_1", "obs_3", obs3); - }); - - it("generates profile with topConcepts sorted by frequency", async () => { - const result = (await sdk.trigger("mem::profile", { - project: "my-project", - })) as { profile: ProjectProfile; cached: boolean }; - - expect(result.cached).toBe(false); - expect(result.profile.topConcepts[0].concept).toBe("typescript"); - expect(result.profile.topConcepts[0].frequency).toBe(2); - }); - - it("generates profile with topFiles sorted by frequency", async () => { - const result = (await sdk.trigger("mem::profile", { - project: "my-project", - })) as { profile: ProjectProfile }; - - expect(result.profile.topFiles[0].file).toBe("/project/src/db.ts"); - expect(result.profile.topFiles[0].frequency).toBe(2); - }); - - it("extracts conventions from file patterns", async () => { - const result = (await sdk.trigger("mem::profile", { - project: "my-project", - })) as { profile: ProjectProfile }; - - expect(result.profile.conventions).toContain("TypeScript project"); - expect(result.profile.conventions).toContain( - "Standard src/ directory structure", - ); - }); - - it("returns cached profile if fresh", async () => { - await sdk.trigger("mem::profile", { project: "my-project" }); - - const result = (await sdk.trigger("mem::profile", { - project: "my-project", - })) as { profile: ProjectProfile; cached: boolean }; - - expect(result.cached).toBe(true); - }); - - it("returns null profile for unknown project", async () => { - const result = (await sdk.trigger("mem::profile", { - project: "nonexistent", - })) as { profile: null; reason: string }; - - expect(result.profile).toBeNull(); - expect(result.reason).toBe("no_sessions"); - }); -}); diff --git a/test/query-expansion.test.ts b/test/query-expansion.test.ts deleted file mode 100644 index 73a5fb17..00000000 --- a/test/query-expansion.test.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; -import type { MemoryProvider } from "../src/types.js"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, fn: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, fn); - }, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (fn) return fn(payload); - return null; - }, - }; -} - -describe("QueryExpansion", () => { - it("imports without errors", async () => { - const mod = await import("../src/functions/query-expansion.js"); - expect(mod.registerQueryExpansionFunction).toBeDefined(); - expect(mod.extractEntitiesFromQuery).toBeDefined(); - }); - - it("extracts entities from capitalized words", async () => { - const { extractEntitiesFromQuery } = await import( - "../src/functions/query-expansion.js" - ); - const entities = extractEntitiesFromQuery( - 'What happened with React and the Vue migration?', - ); - expect(entities).toContain("React"); - expect(entities).toContain("Vue"); - expect(entities).not.toContain("What"); - }); - - it("extracts quoted entities", async () => { - const { extractEntitiesFromQuery } = await import( - "../src/functions/query-expansion.js" - ); - const entities = extractEntitiesFromQuery( - 'Find memories about "auth middleware" changes', - ); - expect(entities).toContain("auth middleware"); - }); - - it("expands queries via LLM", async () => { - const { registerQueryExpansionFunction } = await import( - "../src/functions/query-expansion.js" - ); - - const response = ` - - Authentication middleware modifications - JWT token validation changes - Security layer updates - - - Auth changes in the past 7 days - - - auth middleware - JWT - -`; - - const provider: MemoryProvider = { - name: "test", - compress: vi.fn().mockResolvedValue(response), - summarize: vi.fn().mockResolvedValue(response), - }; - - const sdk = mockSdk(); - registerQueryExpansionFunction(sdk as never, provider); - - const result = (await sdk.trigger("mem::expand-query", { - query: "What changed in auth?", - })) as { success: boolean; expansion: any }; - - expect(result.success).toBe(true); - expect(result.expansion.original).toBe("What changed in auth?"); - expect(result.expansion.reformulations.length).toBe(3); - expect(result.expansion.entityExtractions).toContain("auth middleware"); - expect(result.expansion.temporalConcretizations.length).toBe(1); - }); - - it("returns empty expansion on LLM failure", async () => { - const { registerQueryExpansionFunction } = await import( - "../src/functions/query-expansion.js" - ); - - const provider: MemoryProvider = { - name: "test", - compress: vi.fn().mockRejectedValue(new Error("LLM down")), - summarize: vi.fn().mockRejectedValue(new Error("LLM down")), - }; - - const sdk = mockSdk(); - registerQueryExpansionFunction(sdk as never, provider); - - const result = (await sdk.trigger("mem::expand-query", { - query: "test query", - })) as { success: boolean; expansion: any }; - - expect(result.success).toBe(true); - expect(result.expansion.original).toBe("test query"); - expect(result.expansion.reformulations).toEqual([]); - }); - - it("respects maxReformulations limit", async () => { - const { registerQueryExpansionFunction } = await import( - "../src/functions/query-expansion.js" - ); - - const response = ` - - Query A - Query B - Query C - Query D - Query E - Query F - - - -`; - - const provider: MemoryProvider = { - name: "test", - compress: vi.fn().mockResolvedValue(response), - summarize: vi.fn().mockResolvedValue(response), - }; - - const sdk = mockSdk(); - registerQueryExpansionFunction(sdk as never, provider); - - const result = (await sdk.trigger("mem::expand-query", { - query: "test", - maxReformulations: 3, - })) as { success: boolean; expansion: any }; - - expect(result.expansion.reformulations.length).toBe(3); - }); -}); diff --git a/test/reflect.test.ts b/test/reflect.test.ts deleted file mode 100644 index 7ea163d5..00000000 --- a/test/reflect.test.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerReflectFunctions } from "../src/functions/reflect.js"; -import type { Insight, GraphNode, GraphEdge, SemanticMemory, Lesson, Crystal } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeConceptNode(name: string): GraphNode { - return { - id: `node_${name}`, - type: "concept", - name, - properties: {}, - sourceObservationIds: [], - createdAt: "2026-04-01T00:00:00Z", - }; -} - -function makeEdge(src: string, tgt: string): GraphEdge { - return { - id: `edge_${src}_${tgt}`, - type: "related_to", - sourceNodeId: `node_${src}`, - targetNodeId: `node_${tgt}`, - weight: 1, - sourceObservationIds: [], - createdAt: "2026-04-01T00:00:00Z", - }; -} - -function makeSemantic(fact: string, id?: string): SemanticMemory { - return { - id: id || `sem_${fact.slice(0, 8)}`, - fact, - confidence: 0.8, - sourceSessionIds: [], - sourceMemoryIds: [], - accessCount: 1, - lastAccessedAt: "2026-04-01T00:00:00Z", - strength: 0.8, - createdAt: "2026-04-01T00:00:00Z", - updatedAt: "2026-04-01T00:00:00Z", - }; -} - -function makeLesson(content: string, tags: string[]): Lesson { - return { - id: `lsn_${content.slice(0, 8)}`, - content, - context: "", - confidence: 0.7, - reinforcements: 0, - source: "manual", - sourceIds: [], - tags, - createdAt: "2026-04-01T00:00:00Z", - updatedAt: "2026-04-01T00:00:00Z", - decayRate: 0.05, - }; -} - -function makeCrystal(narrative: string, lessons: string[]): Crystal { - return { - id: `crys_${narrative.slice(0, 8)}`, - narrative, - keyOutcomes: [], - filesAffected: [], - lessons, - sourceActionIds: [], - createdAt: "2026-04-01T00:00:00Z", - }; -} - -const XML_RESPONSE = ` - -Security requires layered protection: input validation, safe APIs, and deny-lists together. - - -Focus test effort on system boundaries where trust transitions occur. - -`; - -describe("Reflect", () => { - let sdk: ReturnType; - let kv: ReturnType; - let provider: { name: string; compress: ReturnType; summarize: ReturnType }; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - provider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn().mockResolvedValue(XML_RESPONSE), - }; - registerReflectFunctions(sdk as never, kv as never, provider as never); - }); - - describe("mem::reflect", () => { - it("returns empty when no graph nodes or memories exist", async () => { - const result = (await sdk.trigger("mem::reflect", {})) as { - success: boolean; - newInsights: number; - clustersProcessed: number; - }; - - expect(result.success).toBe(true); - expect(result.newInsights).toBe(0); - expect(result.clustersProcessed).toBe(0); - }); - - it("synthesizes insights from graph concept clusters", async () => { - await kv.set("mem:graph:nodes", "node_security", makeConceptNode("security")); - await kv.set("mem:graph:nodes", "node_validation", makeConceptNode("validation")); - await kv.set("mem:graph:nodes", "node_testing", makeConceptNode("testing")); - await kv.set("mem:graph:edges", "edge_1", makeEdge("security", "validation")); - await kv.set("mem:graph:edges", "edge_2", makeEdge("security", "testing")); - - await kv.set("mem:semantic", "sem_1", makeSemantic("Always validate security inputs")); - await kv.set("mem:semantic", "sem_2", makeSemantic("Testing improves security coverage")); - await kv.set("mem:semantic", "sem_3", makeSemantic("Validation prevents injection attacks")); - await kv.set("mem:lessons", "lsn_1", makeLesson("Use execFile for security", ["security"])); - - const result = (await sdk.trigger("mem::reflect", {})) as { - success: boolean; - newInsights: number; - }; - - expect(result.success).toBe(true); - expect(result.newInsights).toBe(2); - expect(provider.summarize).toHaveBeenCalled(); - - const insights = await kv.list("mem:insights"); - expect(insights.length).toBe(2); - expect(insights[0].title).toBeTruthy(); - expect(insights[0].sourceConceptCluster.length).toBeGreaterThan(0); - }); - - it("skips clusters with fewer than 3 supporting items", async () => { - await kv.set("mem:graph:nodes", "node_sparse", makeConceptNode("sparse")); - await kv.set("mem:graph:nodes", "node_topic", makeConceptNode("topic")); - await kv.set("mem:graph:edges", "edge_1", makeEdge("sparse", "topic")); - await kv.set("mem:semantic", "sem_1", makeSemantic("One sparse fact")); - - const result = (await sdk.trigger("mem::reflect", {})) as { - clustersSkipped: number; - newInsights: number; - }; - - expect(result.clustersSkipped).toBe(1); - expect(result.newInsights).toBe(0); - expect(provider.summarize).not.toHaveBeenCalled(); - }); - - it("deduplicates insights by fingerprint", async () => { - await kv.set("mem:graph:nodes", "node_security", makeConceptNode("security")); - await kv.set("mem:graph:nodes", "node_validation", makeConceptNode("validation")); - await kv.set("mem:graph:edges", "edge_1", makeEdge("security", "validation")); - await kv.set("mem:semantic", "sem_1", makeSemantic("Always validate security inputs")); - await kv.set("mem:semantic", "sem_2", makeSemantic("Testing improves security coverage")); - await kv.set("mem:semantic", "sem_3", makeSemantic("Validation prevents injection")); - - await sdk.trigger("mem::reflect", {}); - const first = await kv.list("mem:insights"); - expect(first.length).toBe(2); - - const result = (await sdk.trigger("mem::reflect", {})) as { - reinforced: number; - newInsights: number; - }; - - expect(result.reinforced).toBe(2); - expect(result.newInsights).toBe(0); - - const after = await kv.list("mem:insights"); - expect(after.length).toBe(2); - expect(after[0].reinforcements).toBe(1); - }); - - it("falls back to Jaccard grouping when graph is empty", async () => { - await kv.set("mem:semantic", "sem_1", makeSemantic("security validation is important")); - await kv.set("mem:semantic", "sem_2", makeSemantic("security testing prevents bugs")); - await kv.set("mem:semantic", "sem_3", makeSemantic("validation testing framework")); - await kv.set("mem:lessons", "lsn_1", makeLesson("Use security headers", ["security", "validation"])); - - const result = (await sdk.trigger("mem::reflect", {})) as { - success: boolean; - usedFallback: boolean; - }; - - expect(result.success).toBe(true); - expect(result.usedFallback).toBe(true); - }); - - it("handles LLM failure gracefully", async () => { - provider.summarize.mockRejectedValue(new Error("LLM timeout")); - - await kv.set("mem:graph:nodes", "node_a", makeConceptNode("concept_a")); - await kv.set("mem:graph:nodes", "node_b", makeConceptNode("concept_b")); - await kv.set("mem:graph:edges", "edge_1", makeEdge("concept_a", "concept_b")); - await kv.set("mem:semantic", "sem_1", makeSemantic("fact about concept_a")); - await kv.set("mem:semantic", "sem_2", makeSemantic("fact about concept_b")); - await kv.set("mem:semantic", "sem_3", makeSemantic("concept_a and concept_b together")); - - const result = (await sdk.trigger("mem::reflect", {})) as { - success: boolean; - newInsights: number; - }; - - expect(result.success).toBe(true); - expect(result.newInsights).toBe(0); - }); - }); - - describe("mem::insight-list", () => { - beforeEach(async () => { - const now = new Date().toISOString(); - await kv.set("mem:insights", "ins_1", { - id: "ins_1", title: "Insight A", content: "Content A", confidence: 0.9, - reinforcements: 2, sourceConceptCluster: ["security"], sourceMemoryIds: [], - sourceLessonIds: [], sourceCrystalIds: [], project: "/app", - tags: ["security"], createdAt: now, updatedAt: now, decayRate: 0.05, - }); - await kv.set("mem:insights", "ins_2", { - id: "ins_2", title: "Insight B", content: "Content B", confidence: 0.4, - reinforcements: 0, sourceConceptCluster: ["testing"], sourceMemoryIds: [], - sourceLessonIds: [], sourceCrystalIds: [], project: "/other", - tags: ["testing"], createdAt: now, updatedAt: now, decayRate: 0.05, - }); - }); - - it("lists all non-deleted insights sorted by confidence", async () => { - const result = (await sdk.trigger("mem::insight-list", {})) as { insights: Insight[] }; - expect(result.insights.length).toBe(2); - expect(result.insights[0].confidence).toBe(0.9); - }); - - it("filters by project", async () => { - const result = (await sdk.trigger("mem::insight-list", { project: "/app" })) as { insights: Insight[] }; - expect(result.insights.length).toBe(1); - }); - - it("filters by minConfidence", async () => { - const result = (await sdk.trigger("mem::insight-list", { minConfidence: 0.5 })) as { insights: Insight[] }; - expect(result.insights.length).toBe(1); - }); - }); - - describe("mem::insight-search", () => { - beforeEach(async () => { - const now = new Date().toISOString(); - await kv.set("mem:insights", "ins_1", { - id: "ins_1", title: "Defense in Depth", content: "Security requires layered protection", - confidence: 0.85, reinforcements: 1, sourceConceptCluster: ["security"], - sourceMemoryIds: [], sourceLessonIds: [], sourceCrystalIds: [], - tags: ["security"], createdAt: now, updatedAt: now, decayRate: 0.05, - }); - }); - - it("finds insights matching query", async () => { - const result = (await sdk.trigger("mem::insight-search", { - query: "security layered protection", - })) as { insights: Array }; - - expect(result.insights.length).toBe(1); - expect(result.insights[0].title).toBe("Defense in Depth"); - }); - - it("rejects empty query", async () => { - const result = (await sdk.trigger("mem::insight-search", { query: "" })) as { success: boolean }; - expect(result.success).toBe(false); - }); - }); - - describe("mem::insight-decay-sweep", () => { - it("decays old insights incrementally", async () => { - await kv.set("mem:insights", "ins_old", { - id: "ins_old", title: "Old", content: "Old insight", confidence: 0.8, - reinforcements: 1, sourceConceptCluster: [], sourceMemoryIds: [], - sourceLessonIds: [], sourceCrystalIds: [], tags: [], - createdAt: new Date(Date.now() - 21 * 86400000).toISOString(), - updatedAt: new Date(Date.now() - 21 * 86400000).toISOString(), - decayRate: 0.05, - }); - - const result = (await sdk.trigger("mem::insight-decay-sweep", {})) as { decayed: number }; - expect(result.decayed).toBe(1); - - const after = await kv.get("mem:insights", "ins_old"); - expect(after!.confidence).toBeLessThan(0.8); - expect(after!.lastDecayedAt).toBeDefined(); - }); - - it("soft-deletes low-confidence unreinforced insights", async () => { - await kv.set("mem:insights", "ins_weak", { - id: "ins_weak", title: "Weak", content: "Weak insight", confidence: 0.12, - reinforcements: 0, sourceConceptCluster: [], sourceMemoryIds: [], - sourceLessonIds: [], sourceCrystalIds: [], tags: [], - createdAt: new Date(Date.now() - 21 * 86400000).toISOString(), - updatedAt: new Date(Date.now() - 21 * 86400000).toISOString(), - decayRate: 0.05, - }); - - const result = (await sdk.trigger("mem::insight-decay-sweep", {})) as { softDeleted: number }; - expect(result.softDeleted).toBe(1); - - const after = await kv.get("mem:insights", "ins_weak"); - expect(after!.deleted).toBe(true); - }); - }); -}); diff --git a/test/relations.test.ts b/test/relations.test.ts deleted file mode 100644 index bf6025e7..00000000 --- a/test/relations.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerRelationsFunction } from "../src/functions/relations.js"; -import type { Memory } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - getFunction: (id: string) => functions.get(id), - }; -} - -function makeMemory(overrides: Partial = {}): Memory { - return { - id: "mem_1", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "pattern", - title: "Test memory", - content: "This is a test memory", - concepts: ["test"], - files: [], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, - ...overrides, - }; -} - -describe("Relations Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerRelationsFunction(sdk as never, kv as never); - }); - - describe("mem::relate", () => { - it("creates a relation between two memories", async () => { - const mem1 = makeMemory({ id: "mem_1" }); - const mem2 = makeMemory({ id: "mem_2" }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - - const result = await sdk.trigger("mem::relate", { - sourceId: "mem_1", - targetId: "mem_2", - type: "related", - }); - - expect((result as { success: boolean }).success).toBe(true); - - const updated1 = await kv.get("mem:memories", "mem_1"); - const updated2 = await kv.get("mem:memories", "mem_2"); - expect(updated1!.relatedIds).toContain("mem_2"); - expect(updated2!.relatedIds).toContain("mem_1"); - }); - - it("returns error when source memory not found", async () => { - const mem2 = makeMemory({ id: "mem_2" }); - await kv.set("mem:memories", "mem_2", mem2); - - const result = await sdk.trigger("mem::relate", { - sourceId: "mem_missing", - targetId: "mem_2", - type: "related", - }); - - expect((result as { success: boolean }).success).toBe(false); - }); - - it("does not duplicate relatedIds on repeated calls", async () => { - const mem1 = makeMemory({ id: "mem_1", relatedIds: ["mem_2"] }); - const mem2 = makeMemory({ id: "mem_2", relatedIds: ["mem_1"] }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - - await sdk.trigger("mem::relate", { - sourceId: "mem_1", - targetId: "mem_2", - type: "related", - }); - - const updated1 = await kv.get("mem:memories", "mem_1"); - expect(updated1!.relatedIds!.filter((id) => id === "mem_2").length).toBe(1); - }); - }); - - describe("mem::evolve", () => { - it("marks old memory as not latest and creates new version", async () => { - const original = makeMemory({ id: "mem_old", version: 1 }); - await kv.set("mem:memories", "mem_old", original); - - const result = (await sdk.trigger("mem::evolve", { - memoryId: "mem_old", - newContent: "Updated content", - newTitle: "Updated title", - })) as { success: boolean; memory: Memory; previousId: string }; - - expect(result.success).toBe(true); - expect(result.memory.version).toBe(2); - expect(result.memory.content).toBe("Updated content"); - expect(result.memory.title).toBe("Updated title"); - expect(result.memory.parentId).toBe("mem_old"); - expect(result.memory.isLatest).toBe(true); - - const old = await kv.get("mem:memories", "mem_old"); - expect(old!.isLatest).toBe(false); - }); - - it("returns error when memory not found", async () => { - const result = await sdk.trigger("mem::evolve", { - memoryId: "mem_missing", - newContent: "Updated content", - }); - - expect((result as { success: boolean }).success).toBe(false); - }); - }); - - describe("mem::get-related", () => { - it("retrieves related memories within 1 hop", async () => { - const mem1 = makeMemory({ id: "mem_1", relatedIds: ["mem_2"] }); - const mem2 = makeMemory({ id: "mem_2", relatedIds: ["mem_1", "mem_3"] }); - const mem3 = makeMemory({ id: "mem_3", relatedIds: ["mem_2"] }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - await kv.set("mem:memories", "mem_3", mem3); - - const result = (await sdk.trigger("mem::get-related", { - memoryId: "mem_1", - maxHops: 1, - })) as { results: Array<{ memory: Memory; hop: number }> }; - - expect(result.results.length).toBe(1); - expect(result.results[0].memory.id).toBe("mem_2"); - expect(result.results[0].hop).toBe(1); - }); - - it("retrieves related memories within 2 hops", async () => { - const mem1 = makeMemory({ id: "mem_1", relatedIds: ["mem_2"] }); - const mem2 = makeMemory({ id: "mem_2", relatedIds: ["mem_1", "mem_3"] }); - const mem3 = makeMemory({ id: "mem_3", relatedIds: ["mem_2"] }); - await kv.set("mem:memories", "mem_1", mem1); - await kv.set("mem:memories", "mem_2", mem2); - await kv.set("mem:memories", "mem_3", mem3); - - const result = (await sdk.trigger("mem::get-related", { - memoryId: "mem_1", - maxHops: 2, - })) as { results: Array<{ memory: Memory; hop: number }> }; - - expect(result.results.length).toBe(2); - const ids = result.results.map((r) => r.memory.id); - expect(ids).toContain("mem_2"); - expect(ids).toContain("mem_3"); - }); - }); -}); diff --git a/test/remember-bm25-index.test.ts b/test/remember-bm25-index.test.ts deleted file mode 100644 index 77ee8d12..00000000 --- a/test/remember-bm25-index.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { SearchIndex } from "../src/state/search-index.js"; -import type { CompressedObservation, Memory } from "../src/types.js"; - -// Mirrors the helper used by remember.ts and rebuildIndex(). Kept inline -// here rather than exporting from src/ so the test asserts the contract, -// not the implementation. -function memoryAsIndexable(memory: Memory): CompressedObservation { - return { - id: memory.id, - sessionId: memory.sessionIds[0] ?? "memory", - timestamp: memory.createdAt, - type: "decision", - title: memory.title, - facts: [memory.content], - narrative: memory.content, - concepts: memory.concepts, - files: memory.files, - importance: memory.strength, - }; -} - -function makeMemory(overrides: Partial = {}): Memory { - return { - id: "mem_test_001", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "fact", - title: "BM25 test memory", - content: "BM25 search returns this memory by keyword match", - concepts: ["bm25", "search", "test"], - files: [], - sessionIds: [], - strength: 7, - version: 1, - isLatest: true, - ...overrides, - }; -} - -describe("SearchIndex.has()", () => { - it("returns false for unknown ids", () => { - expect(new SearchIndex().has("mem_unknown")).toBe(false); - }); - - it("returns true after add()", () => { - const idx = new SearchIndex(); - idx.add(memoryAsIndexable(makeMemory())); - expect(idx.has("mem_test_001")).toBe(true); - }); -}); - -describe("memory indexing into SearchIndex (closes #257)", () => { - it("makes a saved memory findable by keyword search", () => { - const idx = new SearchIndex(); - idx.add(memoryAsIndexable(makeMemory({ - id: "mem_user_001", - title: "JWT middleware uses jose for Edge compatibility", - content: "Chose jose over jsonwebtoken because Cloudflare Workers don't ship Node crypto", - concepts: ["auth", "jose", "edge"], - }))); - - const hits = idx.search("jose middleware", 5); - expect(hits).toHaveLength(1); - expect(hits[0].obsId).toBe("mem_user_001"); - }); - - it("returns the memory when the issue's reproduction query is run", () => { - // From issue #257: user saved a memory containing 'BM25 test' - // keywords and the search returned empty — recall failure. - const idx = new SearchIndex(); - idx.add(memoryAsIndexable(makeMemory({ - id: "mem_moy3u6ua_8c6962b668e7", - title: "BM25 test", - content: "Confirmed BM25 indexing works for memories saved via memory_save", - concepts: [], - }))); - - const hits = idx.search("BM25 test", 5); - expect(hits.length).toBeGreaterThan(0); - expect(hits[0].obsId).toBe("mem_moy3u6ua_8c6962b668e7"); - }); - - it("matches concepts as well as title and content", () => { - const idx = new SearchIndex(); - idx.add(memoryAsIndexable(makeMemory({ - id: "mem_concept_001", - title: "Generic title", - content: "Generic content", - concepts: ["unique-concept-marker"], - }))); - - const hits = idx.search("unique-concept-marker", 5); - expect(hits.length).toBeGreaterThan(0); - expect(hits[0].obsId).toBe("mem_concept_001"); - }); -}); diff --git a/test/remember-forget-audit.test.ts b/test/remember-forget-audit.test.ts deleted file mode 100644 index 7d17b543..00000000 --- a/test/remember-forget-audit.test.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("../src/state/keyed-mutex.js", () => ({ - withKeyedLock: (_key: string, fn: () => Promise) => fn(), -})); - -import { registerRememberFunction } from "../src/functions/remember.js"; -import { - getSearchIndex, - setIndexPersistence, -} from "../src/functions/search.js"; -import { memoryToObservation } from "../src/state/memory-utils.js"; -import type { Memory } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (id: string, handler: Function) => { - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (input: { function_id: string; payload: unknown }) => { - const fn = functions.get(input.function_id); - if (!fn) throw new Error(`unknown fn ${input.function_id}`); - return fn(input.payload); - }, - }; -} - -describe("mem::forget audit coverage (issue #125)", () => { - it("emits a single audit row when a memory is forgotten", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - await kv.set("mem:memories", "mem_a", { id: "mem_a", content: "x" }); - - const result = await sdk.trigger({ - function_id: "mem::forget", - payload: { memoryId: "mem_a" }, - }); - expect((result as { deleted: number }).deleted).toBe(1); - - const auditRows = await kv.list<{ - operation: string; - functionId: string; - targetIds: string[]; - details: Record; - }>("mem:audit"); - expect(auditRows).toHaveLength(1); - const [row] = auditRows; - expect(row.operation).toBe("forget"); - expect(row.functionId).toBe("mem::forget"); - expect(row.targetIds).toEqual(["mem_a"]); - expect(row.details.memoriesDeleted).toBe(1); - expect(row.details.observationsDeleted).toBe(0); - expect(row.details.sessionDeleted).toBe(false); - }); - - it("emits one batched audit row when an entire session is forgotten", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - await kv.set("mem:sessions", "sess_1", { id: "sess_1" }); - await kv.set("mem:summaries", "sess_1", { id: "sess_1" }); - await kv.set("mem:obs:sess_1", "obs_a", { id: "obs_a" }); - await kv.set("mem:obs:sess_1", "obs_b", { id: "obs_b" }); - - await sdk.trigger({ - function_id: "mem::forget", - payload: { sessionId: "sess_1" }, - }); - - const auditRows = await kv.list<{ - targetIds: string[]; - details: Record; - }>("mem:audit"); - expect(auditRows).toHaveLength(1); - const [row] = auditRows; - expect([...row.targetIds].sort()).toEqual(["obs_a", "obs_b"]); - expect(row.details.memoriesDeleted).toBe(0); - expect(row.details.observationsDeleted).toBe(2); - expect(row.details.sessionDeleted).toBe(true); - expect(row.details.deleted).toBe(4); - }); - - it("does not emit an audit row when nothing is deleted", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - await sdk.trigger({ - function_id: "mem::forget", - payload: { sessionId: undefined, memoryId: undefined }, - }); - - const auditRows = await kv.list("mem:audit"); - expect(auditRows).toHaveLength(0); - }); -}); - -// Delete paths must tear down the BM25 index entry and synchronously -// flush the persisted snapshot. Without this, a deleted memory keeps -// occupying limit-capped search result slots, and an in-memory remove -// would be lost if the process exits before the debounced save fires. -describe("mem::forget search-index cleanup", () => { - function makeMemory(id: string): Memory { - return { - id, - createdAt: "2026-02-01T00:00:00Z", - updatedAt: "2026-02-01T00:00:00Z", - type: "fact", - title: `title ${id}`, - content: `content ${id}`, - concepts: [], - files: [], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, - }; - } - - beforeEach(() => { - getSearchIndex().clear(); - setIndexPersistence(null); - }); - - afterEach(() => { - setIndexPersistence(null); - }); - - it("removes a forgotten memory from the BM25 index", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const mem = makeMemory("mem_a"); - await kv.set("mem:memories", mem.id, mem); - getSearchIndex().add(memoryToObservation(mem)); - expect(getSearchIndex().has("mem_a")).toBe(true); - - await sdk.trigger({ - function_id: "mem::forget", - payload: { memoryId: "mem_a" }, - }); - - expect(getSearchIndex().has("mem_a")).toBe(false); - }); - - it("removes forgotten observations from the BM25 index", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - await kv.set("mem:obs:ses_1", "obs_a", { id: "obs_a" }); - await kv.set("mem:obs:ses_1", "obs_b", { id: "obs_b" }); - getSearchIndex().add(memoryToObservation(makeMemory("obs_a"))); - getSearchIndex().add(memoryToObservation(makeMemory("obs_b"))); - - await sdk.trigger({ - function_id: "mem::forget", - payload: { sessionId: "ses_1", observationIds: ["obs_a"] }, - }); - - expect(getSearchIndex().has("obs_a")).toBe(false); - expect(getSearchIndex().has("obs_b")).toBe(true); - }); - - it("flushes persistence immediately when a memory is forgotten", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - const persistence = { scheduleSave: vi.fn(), save: vi.fn(async () => {}) }; - setIndexPersistence(persistence); - - await kv.set("mem:memories", "mem_a", makeMemory("mem_a")); - - await sdk.trigger({ - function_id: "mem::forget", - payload: { memoryId: "mem_a" }, - }); - - expect(persistence.save).toHaveBeenCalled(); - }); -}); diff --git a/test/remember-project-scope.test.ts b/test/remember-project-scope.test.ts deleted file mode 100644 index bda699f5..00000000 --- a/test/remember-project-scope.test.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("../src/state/keyed-mutex.js", () => ({ - withKeyedLock: (_key: string, fn: () => Promise) => fn(), -})); - -vi.mock("iii-sdk", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - TriggerAction: { - ...actual.TriggerAction, - Void: vi.fn(() => ({ type: "void" })), - }, - }; -}); - -import { vi } from "vitest"; -import { registerRememberFunction } from "../src/functions/remember.js"; -import { getSearchIndex, setIndexPersistence } from "../src/functions/search.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (id: string, handler: Function) => { - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (input: { function_id: string; payload: unknown; action?: unknown }) => { - const fn = functions.get(input.function_id); - if (!fn) return {}; - return fn(input.payload); - }, - }; -} - -describe("mem::remember — project field stamping", () => { - beforeEach(() => { - getSearchIndex().clear(); - setIndexPersistence(null); - }); - - afterEach(() => { - setIndexPersistence(null); - }); - - it("persists project on the saved memory when provided", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = await sdk.trigger({ - function_id: "mem::remember", - payload: { - content: "express-jwt requires trimmed Bearer token", - type: "bug", - files: ["src/middleware/auth.ts"], - project: "api", - }, - }) as { success: boolean; memory: { id: string; project?: string } }; - - expect(result.success).toBe(true); - expect(result.memory.project).toBe("api"); - - const stored = await kv.get<{ project?: string }>("mem:memories", result.memory.id); - expect(stored?.project).toBe("api"); - }); - - it("leaves project undefined when not provided (backward-compat)", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = await sdk.trigger({ - function_id: "mem::remember", - payload: { content: "some unscoped memory" }, - }) as { success: boolean; memory: { id: string; project?: string } }; - - expect(result.success).toBe(true); - expect(result.memory.project).toBeUndefined(); - }); - - it("trims whitespace from the project value", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = await sdk.trigger({ - function_id: "mem::remember", - payload: { content: "padded project name", project: " api " }, - }) as { success: boolean; memory: { project?: string } }; - - expect(result.memory.project).toBe("api"); - }); - - it("treats a blank project string the same as no project", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = await sdk.trigger({ - function_id: "mem::remember", - payload: { content: "blank project string", project: " " }, - }) as { success: boolean; memory: { project?: string } }; - - expect(result.memory.project).toBeUndefined(); - }); -}); - -describe("mem::remember — cross-project dedup isolation", () => { - beforeEach(() => { - getSearchIndex().clear(); - setIndexPersistence(null); - }); - - afterEach(() => { - setIndexPersistence(null); - }); - - it("does not supersede a memory from a different project even when content is similar", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - // Save a memory in project "api" - const first = await sdk.trigger({ - function_id: "mem::remember", - payload: { - content: "always use express-jwt middleware for token validation in this project", - type: "pattern", - project: "api", - }, - }) as { memory: { id: string; isLatest: boolean; project?: string } }; - - // Save a near-identical memory in project "web" — should NOT supersede the api one - const second = await sdk.trigger({ - function_id: "mem::remember", - payload: { - content: "always use express-jwt middleware for token validation in this project", - type: "pattern", - project: "web", - }, - }) as { memory: { id: string; supersedes: string[]; project?: string } }; - - expect(second.memory.project).toBe("web"); - expect(second.memory.supersedes).toHaveLength(0); - - // The api memory must still be isLatest - const apiMemory = await kv.get<{ isLatest: boolean }>("mem:memories", first.memory.id); - expect(apiMemory?.isLatest).toBe(true); - }); - - it("still supersedes within the same project when content is similar", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const first = await sdk.trigger({ - function_id: "mem::remember", - payload: { - content: "always use express-jwt middleware for token validation in this project", - type: "pattern", - project: "api", - }, - }) as { memory: { id: string } }; - - const second = await sdk.trigger({ - function_id: "mem::remember", - payload: { - content: "always use express-jwt middleware for token validation in this project", - type: "pattern", - project: "api", - }, - }) as { memory: { supersedes: string[] } }; - - expect(second.memory.supersedes).toContain(first.memory.id); - - const original = await kv.get<{ isLatest: boolean }>("mem:memories", first.memory.id); - expect(original?.isLatest).toBe(false); - }); - - it("allows an unscoped memory to be superseded by a scoped one (legacy compat)", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - // Existing legacy memory with no project - const legacy = await sdk.trigger({ - function_id: "mem::remember", - payload: { - content: "always use express-jwt middleware for token validation in this project", - type: "pattern", - }, - }) as { memory: { id: string } }; - - // New scoped memory — should supersede the legacy unscoped one - const scoped = await sdk.trigger({ - function_id: "mem::remember", - payload: { - content: "always use express-jwt middleware for token validation in this project", - type: "pattern", - project: "api", - }, - }) as { memory: { supersedes: string[] } }; - - expect(scoped.memory.supersedes).toContain(legacy.memory.id); - }); - - it("allows a scoped memory to be superseded by an unscoped one (legacy compat)", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const scoped = await sdk.trigger({ - function_id: "mem::remember", - payload: { - content: "always use express-jwt middleware for token validation in this project", - type: "pattern", - project: "api", - }, - }) as { memory: { id: string } }; - - // Unscoped write — should still supersede since one side has no project - const unscoped = await sdk.trigger({ - function_id: "mem::remember", - payload: { - content: "always use express-jwt middleware for token validation in this project", - type: "pattern", - }, - }) as { memory: { supersedes: string[] } }; - - expect(unscoped.memory.supersedes).toContain(scoped.memory.id); - }); -}); diff --git a/test/replay-sensitive.test.ts b/test/replay-sensitive.test.ts deleted file mode 100644 index 6550dc7b..00000000 --- a/test/replay-sensitive.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { isSensitive } from "../src/functions/replay.js"; - -describe("isSensitive path guard", () => { - it("blocks .env and common secret filenames", () => { - expect(isSensitive("/Users/x/project/.env")).toBe(true); - expect(isSensitive("/Users/x/project/.env.local")).toBe(true); - expect(isSensitive("/tmp/credentials.json")).toBe(true); - expect(isSensitive("/home/alice/.ssh/id_rsa")).toBe(true); - expect(isSensitive("/srv/app/secret.key")).toBe(true); - expect(isSensitive("/srv/app/access_token.txt")).toBe(true); - expect(isSensitive("/srv/app/private_key.pem")).toBe(true); - }); - - it("does not false-positive on project names containing substrings", () => { - expect(isSensitive("/Users/dev/jsonwebtoken-demo/transcript.jsonl")).toBe(false); - expect(isSensitive("/repos/secrethandshake-lib/a.jsonl")).toBe(false); - expect(isSensitive("/opt/tokeniser/out.jsonl")).toBe(false); - expect(isSensitive("/Users/alice/.claude/projects/myapp/abc.jsonl")).toBe(false); - }); -}); diff --git a/test/replay.test.ts b/test/replay.test.ts deleted file mode 100644 index 95b4229b..00000000 --- a/test/replay.test.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { describe, expect, it, beforeEach, vi } from "vitest"; -import { readFileSync } from "node:fs"; -import { join } from "node:path"; -import { parseJsonlText } from "../src/replay/jsonl-parser.js"; -import { projectTimeline } from "../src/replay/timeline.js"; -import { ingestJsonlFile, registerReplayFunctions } from "../src/functions/replay.js"; -import { getSearchIndex } from "../src/functions/search.js"; -import { buildSyntheticCompression } from "../src/functions/compress-synthetic.js"; -import type { RawObservation, Session } from "../src/types.js"; -import { KV } from "../src/state/schema.js"; -import { mockKV, mockSdk } from "./helpers/mocks.js"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("../src/functions/audit.js", () => ({ - safeAudit: vi.fn(), -})); - -const fx = (name: string) => - readFileSync(join(__dirname, "fixtures/jsonl", name), "utf-8"); - -const fxPath = (name: string) => join(__dirname, "fixtures/jsonl", name); - -beforeEach(() => { - getSearchIndex().clear(); -}); - -describe("parseJsonlText", () => { - it("parses basic user/assistant exchange", () => { - const out = parseJsonlText(fx("basic.jsonl")); - expect(out.sessionId).toBe("sess-basic"); - expect(out.project).toBe("project"); - expect(out.cwd).toBe("/Users/alice/project"); - expect(out.observations).toHaveLength(2); - expect(out.observations[0].hookType).toBe("prompt_submit"); - expect(out.observations[0].userPrompt).toBe("Fix the login bug"); - expect(out.observations[1].hookType).toBe("stop"); - expect(out.observations[1].assistantResponse).toBe("Looking into it now."); - }); - - it("parses tool_use + tool_result pairs", () => { - const out = parseJsonlText(fx("tool-use.jsonl")); - expect(out.sessionId).toBe("sess-tool"); - const kinds = out.observations.map((o) => o.hookType); - expect(kinds).toEqual([ - "prompt_submit", - "pre_tool_use", - "post_tool_use", - "stop", - ]); - const toolCall = out.observations[1]; - expect(toolCall.toolName).toBe("Bash"); - expect((toolCall.toolInput as { command: string }).command).toBe("ls"); - const toolResult = out.observations[2]; - expect(toolResult.toolOutput).toBe("README.md\nsrc\n"); - }); - - it("tolerates malformed lines and marks tool errors", () => { - const out = parseJsonlText(fx("errors.jsonl")); - const errObs = out.observations.find((o) => o.hookType === "post_tool_failure"); - expect(errObs).toBeDefined(); - expect(errObs?.toolOutput).toBe("exit 1"); - }); - - it("falls back to generated sessionId when missing", () => { - const text = JSON.stringify({ - type: "user", - timestamp: "2026-01-01T00:00:00.000Z", - message: { role: "user", content: [{ type: "text", text: "hi" }] }, - }); - const out = parseJsonlText(text); - expect(out.sessionId).toMatch(/^sess_/); - }); - - it("returns empty observations for blank input", () => { - const out = parseJsonlText(""); - expect(out.observations).toHaveLength(0); - }); - - it("prefers the file's sessionId over the fallback", () => { - const text = [ - JSON.stringify({ - type: "user", - sessionId: "real-session-from-file", - timestamp: "2026-01-01T00:00:00.000Z", - message: { role: "user", content: [{ type: "text", text: "hi" }] }, - }), - ].join("\n"); - const out = parseJsonlText(text, "fallback-should-be-ignored"); - expect(out.sessionId).toBe("real-session-from-file"); - for (const obs of out.observations) { - expect(obs.sessionId).toBe("real-session-from-file"); - } - }); - - it("returns the same sessionId across repeated parses of one file", () => { - const text = JSON.stringify({ - type: "user", - sessionId: "stable-id", - timestamp: "2026-01-01T00:00:00.000Z", - message: { role: "user", content: [{ type: "text", text: "hi" }] }, - }); - const a = parseJsonlText(text, "fb-1"); - const b = parseJsonlText(text, "fb-2"); - expect(a.sessionId).toBe("stable-id"); - expect(b.sessionId).toBe("stable-id"); - }); - - it("uses the fallback only when the file has no sessionId", () => { - const text = JSON.stringify({ - type: "user", - timestamp: "2026-01-01T00:00:00.000Z", - message: { role: "user", content: [{ type: "text", text: "hi" }] }, - }); - const out = parseJsonlText(text, "fb-used"); - expect(out.sessionId).toBe("fb-used"); - }); - - it("parses Codex session_meta/event_msg/response_item transcripts", () => { - const out = parseJsonlText(fx("codex-session.jsonl"), "fallback-ignored"); - - expect(out.sessionId).toBe("codex-real-session"); - expect(out.project).toBe("codex-project"); - expect(out.cwd).toBe("/Users/alice/codex-project"); - expect(out.startedAt).toBe("2026-06-11T08:00:00.000Z"); - expect(out.endedAt).toBe("2026-06-11T08:00:04.000Z"); - expect(out.observations.map((o) => o.hookType)).toEqual([ - "prompt_submit", - "stop", - "pre_tool_use", - "post_tool_use", - ]); - expect(out.observations[0].userPrompt).toBe("生成摘要按钮点击必定失败,请修复"); - expect(out.observations[1].assistantResponse).toBe("我会先复现并定位摘要链路。"); - expect(out.observations[2].toolName).toBe("exec_command"); - expect(out.observations[2].toolInput).toEqual({ - cmd: "rg summarize src/viewer/index.html", - }); - expect(out.observations[3].toolOutput).toBe("src/viewer/index.html:6866 summarizeSession"); - expect(out.observations.every((o) => o.sessionId === "codex-real-session")).toBe(true); - }); - - it("generates stable observation ids across repeated parses of one JSONL", () => { - const first = parseJsonlText(fx("codex-session.jsonl"), "fallback-ignored"); - const second = parseJsonlText(fx("codex-session.jsonl"), "fallback-ignored"); - - expect(first.observations.map((o) => o.id)).toEqual(second.observations.map((o) => o.id)); - }); - - it("derives 'unknown' project when the cwd field is shell-command pollution (STEP-08 PR5)", () => { - const mkEntry = (cwd: string) => - JSON.stringify({ - type: "user", - sessionId: "sess-cwd", - cwd, - timestamp: "2026-01-01T00:00:00.000Z", - message: { role: "user", content: [{ type: "text", text: "hi" }] }, - }); - - // a shell command captured in the cwd field must NOT become a project name - expect(parseJsonlText(mkEntry('pwd && echo "--- branch ---" && git branch --show-current')).project).toBe("unknown"); - expect(parseJsonlText(mkEntry("git branch --show-current")).project).toBe("unknown"); - expect(parseJsonlText(mkEntry("npm run build")).project).toBe("unknown"); // not absolute - expect(parseJsonlText(mkEntry("/tmp/x && rm -rf /")).project).toBe("unknown"); - - // ...and the polluted cwd itself must not be persisted into the session - const polluted = parseJsonlText(mkEntry('pwd && echo "x" && git branch')); - expect(polluted.cwd).not.toContain("&&"); - expect(polluted.cwd).not.toContain("echo"); - expect(parseJsonlText(mkEntry("/tmp/x && rm -rf /")).project).toBe("unknown"); - - // clean absolute paths resolve to their last segment, incl. names with bare - // punctuation (route groups, q&a) and spaces — these are NOT pollution - expect(parseJsonlText(mkEntry("/Users/alice/agentmemory-lab")).project).toBe("agentmemory-lab"); - expect(parseJsonlText(mkEntry("/repo")).project).toBe("repo"); - expect(parseJsonlText(mkEntry("/app/(marketing)")).project).toBe("(marketing)"); - expect(parseJsonlText(mkEntry("/data/q&a-service")).project).toBe("q&a-service"); - expect(parseJsonlText(mkEntry("/Users/bob/My Project")).project).toBe("My Project"); - expect(parseJsonlText(mkEntry("/Users/bob/项目-todo")).project).toBe("项目-todo"); - expect(parseJsonlText(mkEntry("~/dev/site")).project).toBe("site"); - expect(parseJsonlText(mkEntry("C:\\Users\\bob\\proj")).project).toBe("proj"); - }); -}); - -describe("projectTimeline", () => { - it("preserves ordering and computes offsets from real timestamps", () => { - const parsed = parseJsonlText(fx("tool-use.jsonl")); - const tl = projectTimeline(parsed.observations); - expect(tl.eventCount).toBe(4); - expect(tl.events[0].kind).toBe("prompt"); - expect(tl.events[1].kind).toBe("tool_call"); - expect(tl.events[2].kind).toBe("tool_result"); - expect(tl.events[3].kind).toBe("response"); - expect(tl.events[0].offsetMs).toBe(0); - expect(tl.events[3].offsetMs).toBeGreaterThan(0); - }); - - it("synthesizes pacing when all timestamps identical", () => { - const parsed = parseJsonlText(fx("basic.jsonl")); - for (const obs of parsed.observations) obs.timestamp = "2026-04-17T10:00:00.000Z"; - const tl = projectTimeline(parsed.observations); - expect(tl.events[0].offsetMs).toBe(0); - expect(tl.events[1].offsetMs).toBeGreaterThanOrEqual(300); - }); - - it("returns empty timeline for no observations", () => { - const tl = projectTimeline([]); - expect(tl.eventCount).toBe(0); - expect(tl.totalDurationMs).toBe(0); - expect(tl.events).toHaveLength(0); - }); - - it("marks errored tool results as tool_error kind", () => { - const parsed = parseJsonlText(fx("errors.jsonl")); - const tl = projectTimeline(parsed.observations); - expect(tl.events.some((e) => e.kind === "tool_error")).toBe(true); - }); - - it("uses one shared fallback timestamp when metadata missing", () => { - const text = JSON.stringify({ - type: "user", - message: { role: "user", content: [{ type: "text", text: "hi" }] }, - }); - const out = parseJsonlText(text); - expect(out.startedAt).toBe(out.endedAt); - }); -}); - -describe("buildSyntheticCompression replay coverage", () => { - it("preserves assistant responses in the zero-LLM narrative", () => { - const parsed = parseJsonlText(fx("basic.jsonl")); - const response = parsed.observations.find((o) => o.assistantResponse); - expect(response).toBeDefined(); - - const compressed = buildSyntheticCompression(response!); - - expect(compressed.type).toBe("conversation"); - expect(compressed.narrative).toContain("Looking into it now."); - }); -}); - -describe("ingestJsonlFile", () => { - it("ingests one file and is idempotent on re-ingest (the scanner's reuse path)", async () => { - const kv = mockKV(); - const first = await ingestJsonlFile(kv as never, fxPath("codex-session.jsonl")); - expect(first).toEqual({ sessionId: "codex-real-session", newObservations: 4 }); - const second = await ingestJsonlFile(kv as never, fxPath("codex-session.jsonl")); - expect(second).toEqual({ sessionId: "codex-real-session", newObservations: 0 }); - const stored = await kv.list(KV.observations("codex-real-session")); - expect(stored).toHaveLength(4); - }); - - it("returns null for an unreadable file (skip, no throw)", async () => { - const kv = mockKV(); - const result = await ingestJsonlFile(kv as never, fxPath("does-not-exist.jsonl")); - expect(result).toBeNull(); - }); -}); - -describe("mem::replay::import-jsonl", () => { - it("is idempotent when importing the same Codex JSONL repeatedly", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerReplayFunctions(sdk as never, kv as never); - - const first = await sdk.trigger("mem::replay::import-jsonl", { - path: fxPath("codex-session.jsonl"), - maxFiles: 1, - }) as { success: boolean; sessionIds: string[]; observations: number }; - const second = await sdk.trigger("mem::replay::import-jsonl", { - path: fxPath("codex-session.jsonl"), - maxFiles: 1, - }) as { success: boolean; sessionIds: string[]; observations: number }; - - expect(first.success).toBe(true); - expect(second.success).toBe(true); - expect(first.sessionIds).toEqual(["codex-real-session"]); - expect(second.sessionIds).toEqual(["codex-real-session"]); - expect(first.observations).toBe(4); - expect(second.observations).toBe(0); - - const stored = await kv.list(KV.observations("codex-real-session")); - expect(stored).toHaveLength(4); - const session = await kv.get(KV.sessions, "codex-real-session"); - expect(session?.observationCount).toBe(4); - }); -}); diff --git a/test/reranker.test.ts b/test/reranker.test.ts deleted file mode 100644 index 0694e6d5..00000000 --- a/test/reranker.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; - -vi.mock("@xenova/transformers", () => { - throw new Error("not installed"); -}); - -import { rerank, isRerankerAvailable } from "../src/state/reranker.js"; - -describe("reranker", () => { - it("returns results unchanged when @xenova/transformers is unavailable", async () => { - const results = [ - { - observation: { - id: "o1", - title: "First", - narrative: "First result", - }, - bm25Score: 0.5, - vectorScore: 0.6, - graphScore: 0, - combinedScore: 0.8, - sessionId: "s1", - }, - { - observation: { - id: "o2", - title: "Second", - narrative: "Second result", - }, - bm25Score: 0.3, - vectorScore: 0.4, - graphScore: 0, - combinedScore: 0.5, - sessionId: "s1", - }, - ] as any; - - const reranked = await rerank("test query", results); - expect(reranked).toEqual(results); - }); - - it("isRerankerAvailable returns false when not loaded", () => { - expect(isRerankerAvailable()).toBe(false); - }); - - it("handles single result gracefully", async () => { - const results = [ - { - observation: { id: "o1", title: "Only" }, - combinedScore: 1.0, - }, - ] as any; - - const reranked = await rerank("query", results); - expect(reranked).toHaveLength(1); - }); - - it("handles empty results", async () => { - const reranked = await rerank("query", []); - expect(reranked).toHaveLength(0); - }); -}); diff --git a/test/retention-access.test.ts b/test/retention-access.test.ts deleted file mode 100644 index f6e28f71..00000000 --- a/test/retention-access.test.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; -import type { Memory, SemanticMemory } from "../src/types.js"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -function mockKV( - memories: Memory[] = [], - semanticMems: SemanticMemory[] = [], -) { - const store = new Map>(); - const memMap = new Map(); - for (const m of memories) memMap.set(m.id, m); - store.set("mem:memories", memMap); - const semMap = new Map(); - for (const s of semanticMems) semMap.set(s.id, s); - store.set("mem:semantic", semMap); - store.set("mem:retention", new Map()); - store.set("mem:access", new Map()); - - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string) => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const m = store.get(scope); - return m ? (Array.from(m.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const fns = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, fn: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - fns.set(id, fn); - }, - trigger: async ( - input: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const functionId = - typeof input === "string" ? input : input.function_id; - const payload = typeof input === "string" ? data : input.payload; - const fn = fns.get(functionId); - if (fn) return fn(payload); - return null; - }, - }; -} - -function makeMemory(id: string, daysOld = 30): Memory { - const created = new Date( - Date.now() - daysOld * 86_400_000, - ).toISOString(); - return { - id, - createdAt: created, - updatedAt: created, - type: "fact", - title: `Memory ${id}`, - content: `Content ${id}`, - concepts: [], - files: [], - sessionIds: ["ses_1"], - strength: 1, - version: 1, - isLatest: true, - }; -} - -function makeSemantic( - id: string, - daysOld: number, - accessCount = 0, -): SemanticMemory { - const created = new Date( - Date.now() - daysOld * 86_400_000, - ).toISOString(); - return { - id, - fact: `Fact ${id}`, - confidence: 0.8, - sourceSessionIds: ["ses_1"], - sourceMemoryIds: [], - accessCount, - lastAccessedAt: created, - strength: 0.8, - createdAt: created, - updatedAt: created, - }; -} - -describe("RetentionScoring with access log (issue #119)", () => { - it("episodic memories with recorded reads get higher reinforcementBoost than untouched ones", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - const { recordAccess } = await import( - "../src/functions/access-tracker.js" - ); - - const memories = [ - makeMemory("mem_hot", 30), - makeMemory("mem_cold", 30), - ]; - const sdk = mockSdk(); - const kv = mockKV(memories); - registerRetentionFunctions(sdk as never, kv as never); - - // Simulate 5 agent reads of mem_hot in the past 24h - const now = Date.now(); - for (let i = 0; i < 5; i++) { - await recordAccess(kv as never, "mem_hot", now - i * 60_000); - } - - const result = (await sdk.trigger({ function_id: "mem::retention-score", payload: {} })) as any; - - const hot = result.scores.find((s: any) => s.memoryId === "mem_hot"); - const cold = result.scores.find((s: any) => s.memoryId === "mem_cold"); - - expect(hot.accessCount).toBe(5); - expect(cold.accessCount).toBe(0); - expect(hot.reinforcementBoost).toBeGreaterThan(0); - expect(cold.reinforcementBoost).toBe(0); - expect(hot.score).toBeGreaterThan(cold.score); - }); - - it("recent reads contribute more to reinforcement than ancient reads", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - const { recordAccess } = await import( - "../src/functions/access-tracker.js" - ); - - const memories = [ - makeMemory("mem_recent_read", 60), - makeMemory("mem_old_read", 60), - ]; - const sdk = mockSdk(); - const kv = mockKV(memories); - registerRetentionFunctions(sdk as never, kv as never); - - const now = Date.now(); - // mem_recent_read: 1 access yesterday - await recordAccess(kv as never, "mem_recent_read", now - 86_400_000); - // mem_old_read: 1 access 60 days ago - await recordAccess(kv as never, "mem_old_read", now - 60 * 86_400_000); - - const result = (await sdk.trigger({ function_id: "mem::retention-score", payload: {} })) as any; - const recent = result.scores.find( - (s: any) => s.memoryId === "mem_recent_read", - ); - const old = result.scores.find( - (s: any) => s.memoryId === "mem_old_read", - ); - - expect(recent.reinforcementBoost).toBeGreaterThan(old.reinforcementBoost); - }); - - it("backwards-compat: semantic memories with only legacy lastAccessedAt still score", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - // Pre-0.8.3 data: semantic memory has lastAccessedAt set by the - // consolidation pipeline, but no entry in mem:access. The merge in - // retention.ts must inject lastAccessedAt into accessTimestamps so - // the boost is non-zero. Compare against an identical sem with NO - // lastAccessedAt to prove the merge actually contributes. - const semWith = makeSemantic("sem_with_legacy", 30, 3); - semWith.lastAccessedAt = new Date(Date.now() - 86_400_000).toISOString(); - const semWithout = makeSemantic("sem_without_legacy", 30, 3); - semWithout.lastAccessedAt = ""; - - const sdk = mockSdk(); - const kv = mockKV([], [semWith, semWithout]); - registerRetentionFunctions(sdk as never, kv as never); - - const result = (await sdk.trigger({ function_id: "mem::retention-score", payload: {} })) as any; - const withEntry = result.scores.find( - (s: any) => s.memoryId === "sem_with_legacy", - ); - const withoutEntry = result.scores.find( - (s: any) => s.memoryId === "sem_without_legacy", - ); - - expect(withEntry.accessCount).toBe(3); - expect(withEntry.reinforcementBoost).toBeGreaterThan(0); - expect(withoutEntry.reinforcementBoost).toBe(0); - // The merged legacy timestamp must produce a meaningful delta. - expect(withEntry.reinforcementBoost).toBeGreaterThan( - withoutEntry.reinforcementBoost + 0.1, - ); - }); - - it("corrupted lastAccessedAt does not propagate NaN into the score", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - const sem = makeSemantic("sem_corrupt", 30, 1); - sem.lastAccessedAt = ""; - - const sdk = mockSdk(); - const kv = mockKV([], [sem]); - registerRetentionFunctions(sdk as never, kv as never); - - const result = (await sdk.trigger({ function_id: "mem::retention-score", payload: {} })) as any; - const entry = result.scores.find((s: any) => s.memoryId === "sem_corrupt"); - - expect(Number.isFinite(entry.score)).toBe(true); - expect(Number.isFinite(entry.reinforcementBoost)).toBe(true); - }); - - it("retention scoring normalizes malformed mem:access rows", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - const memories = [ - makeMemory("mem_corrupt", 10), - makeMemory("mem_clean", 10), - ]; - const sdk = mockSdk(); - const kv = mockKV(memories); - - // Seed the access namespace directly with garbage rows. - await kv.set("mem:access", "mem_corrupt", { - memoryId: "mem_corrupt", - count: "not-a-number" as unknown as number, - lastAt: 42 as unknown as string, - recent: [NaN, "bad" as unknown as number, 5_000, Infinity, -1_000], - }); - await kv.set("mem:access", "mem_clean", { - memoryId: "mem_clean", - count: -7, - lastAt: "", - recent: Array.from({ length: 50 }, (_, i) => i * 1000), - }); - - registerRetentionFunctions(sdk as never, kv as never); - - const result = (await sdk.trigger({ function_id: "mem::retention-score", payload: {} })) as any; - const corrupt = result.scores.find( - (s: any) => s.memoryId === "mem_corrupt", - ); - const clean = result.scores.find((s: any) => s.memoryId === "mem_clean"); - - expect(Number.isFinite(corrupt.score)).toBe(true); - expect(Number.isFinite(corrupt.reinforcementBoost)).toBe(true); - expect(corrupt.accessCount).toBeGreaterThanOrEqual(0); - expect(Number.isFinite(clean.score)).toBe(true); - // recent[] was 50 entries; normalization should have capped at 20. - expect(clean.accessCount).toBeGreaterThanOrEqual(20); - }); - - it("retention scoring survives kv.list(mem:access) failures", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - const memories = [makeMemory("mem_resilient", 10)]; - const sdk = mockSdk(); - const kv = mockKV(memories); - const realList = kv.list.bind(kv); - kv.list = (async (scope: string) => { - if (scope === "mem:access") throw new Error("namespace missing"); - return realList(scope); - }) as never; - - registerRetentionFunctions(sdk as never, kv as never); - - const result = (await sdk.trigger({ function_id: "mem::retention-score", payload: {} })) as any; - expect(result.success).toBe(true); - const entry = result.scores.find( - (s: any) => s.memoryId === "mem_resilient", - ); - expect(entry.accessCount).toBe(0); - }); - - it("fresh access log overrides legacy single-sample for semantic memories", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - const { recordAccess } = await import( - "../src/functions/access-tracker.js" - ); - - const sem = makeSemantic("sem_active", 30, 1); - const sdk = mockSdk(); - const kv = mockKV([], [sem]); - registerRetentionFunctions(sdk as never, kv as never); - - const now = Date.now(); - for (let i = 0; i < 10; i++) { - await recordAccess(kv as never, "sem_active", now - i * 30_000); - } - - const result = (await sdk.trigger({ function_id: "mem::retention-score", payload: {} })) as any; - const entry = result.scores.find( - (s: any) => s.memoryId === "sem_active", - ); - - // effectiveCount = max(log=10, sem.accessCount=1) = 10 - expect(entry.accessCount).toBe(10); - }); -}); diff --git a/test/retention.test.ts b/test/retention.test.ts deleted file mode 100644 index fb92b102..00000000 --- a/test/retention.test.ts +++ /dev/null @@ -1,566 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import { - getSearchIndex, - setIndexPersistence, -} from "../src/functions/search.js"; -import { memoryToObservation } from "../src/state/memory-utils.js"; -import type { Memory, SemanticMemory } from "../src/types.js"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -function mockKV( - memories: Memory[] = [], - semanticMems: SemanticMemory[] = [], -) { - const store = new Map>(); - - const memMap = new Map(); - for (const m of memories) memMap.set(m.id, m); - store.set("mem:memories", memMap); - - const semMap = new Map(); - for (const s of semanticMems) semMap.set(s.id, s); - store.set("mem:semantic", semMap); - - store.set("mem:retention", new Map()); - - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string, fn: Function) => { - if (typeof idOrOpts !== "string") { - throw new Error("registerFunction expects string function id"); - } - functions.set(idOrOpts, fn); - }, - trigger: async (input: { function_id: string; payload: unknown }) => { - if (typeof input === "string") { - throw new Error("legacy trigger signature is not supported in tests"); - } - const fn = functions.get(input.function_id); - if (fn) return fn(input.payload); - return null; - }, - }; -} - -function makeMemory( - id: string, - type: Memory["type"], - daysOld: number, -): Memory { - const created = new Date( - Date.now() - daysOld * 24 * 60 * 60 * 1000, - ).toISOString(); - return { - id, - createdAt: created, - updatedAt: created, - type, - title: `Memory ${id}`, - content: `Content of memory ${id}`, - concepts: [], - files: [], - sessionIds: ["ses_1"], - strength: 1, - version: 1, - isLatest: true, - }; -} - -function makeSemanticMemory( - id: string, - daysOld: number, - accessCount = 0, -): SemanticMemory { - const created = new Date( - Date.now() - daysOld * 24 * 60 * 60 * 1000, - ).toISOString(); - return { - id, - fact: `Fact ${id}`, - confidence: 0.8, - sourceSessionIds: ["ses_1"], - sourceMemoryIds: [], - accessCount, - lastAccessedAt: created, - strength: 0.8, - createdAt: created, - updatedAt: created, - }; -} - -describe("RetentionScoring", () => { - it("imports without errors", async () => { - const mod = await import("../src/functions/retention.js"); - expect(mod.registerRetentionFunctions).toBeDefined(); - }); - - it("computes retention scores for all memories", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - const memories = [ - makeMemory("mem_recent", "architecture", 1), - makeMemory("mem_old", "fact", 365), - ]; - - const sdk = mockSdk(); - const kv = mockKV(memories); - registerRetentionFunctions(sdk as never, kv as never); - - const result = (await sdk.trigger({ - function_id: "mem::retention-score", - payload: {}, - })) as { - success: boolean; - total: number; - tiers: any; - scores: any[]; - }; - - expect(result.success).toBe(true); - expect(result.total).toBe(2); - expect(result.scores.length).toBe(2); - - const recentScore = result.scores.find( - (s: any) => s.memoryId === "mem_recent", - ); - const oldScore = result.scores.find( - (s: any) => s.memoryId === "mem_old", - ); - - expect(recentScore!.score).toBeGreaterThan(oldScore!.score); - }); - - it("higher-type memories get higher salience", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - const memories = [ - makeMemory("mem_arch", "architecture", 30), - makeMemory("mem_fact", "fact", 30), - ]; - - const sdk = mockSdk(); - const kv = mockKV(memories); - registerRetentionFunctions(sdk as never, kv as never); - - const result = (await sdk.trigger({ - function_id: "mem::retention-score", - payload: {}, - })) as any; - - const archScore = result.scores.find( - (s: any) => s.memoryId === "mem_arch", - ); - const factScore = result.scores.find( - (s: any) => s.memoryId === "mem_fact", - ); - - expect(archScore.salience).toBeGreaterThan(factScore.salience); - }); - - it("classifies memories into tiers", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - const memories = [ - makeMemory("hot1", "architecture", 1), - makeMemory("hot2", "preference", 3), - makeMemory("warm1", "pattern", 60), - makeMemory("cold1", "fact", 300), - ]; - - const sdk = mockSdk(); - const kv = mockKV(memories); - registerRetentionFunctions(sdk as never, kv as never); - - const result = (await sdk.trigger({ - function_id: "mem::retention-score", - payload: {}, - })) as any; - expect(result.tiers.hot + result.tiers.warm + result.tiers.cold + result.tiers.evictable).toBe(4); - }); - - it("dry-run eviction shows candidates without deleting", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - const memories = [ - makeMemory("mem_keep", "architecture", 1), - makeMemory("mem_evict", "fact", 500), - ]; - - const sdk = mockSdk(); - const kv = mockKV(memories); - registerRetentionFunctions(sdk as never, kv as never); - - await sdk.trigger({ function_id: "mem::retention-score", payload: {} }); - - const dryResult = (await sdk.trigger({ - function_id: "mem::retention-evict", - payload: { - threshold: 0.5, - dryRun: true, - }, - })) as any; - - expect(dryResult.dryRun).toBe(true); - expect(dryResult.wouldEvict).toBeGreaterThanOrEqual(0); - - const remaining = await kv.list("mem:memories"); - expect(remaining.length).toBe(2); - }); - - it("includes semantic memories in scoring", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - const semanticMems = [ - makeSemanticMemory("sem_1", 10, 5), - makeSemanticMemory("sem_2", 200, 0), - ]; - - const sdk = mockSdk(); - const kv = mockKV([], semanticMems); - registerRetentionFunctions(sdk as never, kv as never); - - const result = (await sdk.trigger({ - function_id: "mem::retention-score", - payload: {}, - })) as any; - - expect(result.total).toBe(2); - const sem1 = result.scores.find((s: any) => s.memoryId === "sem_1"); - const sem2 = result.scores.find((s: any) => s.memoryId === "sem_2"); - expect(sem1.score).toBeGreaterThan(sem2.score); - }); - - it("scores tag rows with their source scope (#124)", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - const sdk = mockSdk(); - const kv = mockKV( - [makeMemory("mem_ep", "fact", 10)], - [makeSemanticMemory("sem_sem", 10, 2)], - ); - registerRetentionFunctions(sdk as never, kv as never); - - const result = (await sdk.trigger({ - function_id: "mem::retention-score", - payload: {}, - })) as any; - const ep = result.scores.find((s: any) => s.memoryId === "mem_ep"); - const sem = result.scores.find((s: any) => s.memoryId === "sem_sem"); - expect(ep.source).toBe("episodic"); - expect(sem.source).toBe("semantic"); - - // Also assert the source discriminator is persisted to mem:retention, - // not just present in the transient response payload — the eviction - // loop reads back from stored rows, so a regression in kv.set or - // serialization would still pass the in-memory check above. - const [epStored, semStored] = await Promise.all([ - kv.get("mem:retention", "mem_ep"), - kv.get("mem:retention", "sem_sem"), - ]); - expect(epStored).toMatchObject({ source: "episodic" }); - expect(semStored).toMatchObject({ source: "semantic" }); - }); - - it("mem::retention-evict deletes semantic memories from mem:semantic, not mem:memories (#124)", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - // Both are 500 days old with zero access → both will score below - // the default cold threshold. Before #124 the loop silently called - // kv.delete(mem:memories, ) which was a no-op, leaving - // the semantic row in mem:semantic forever. - const sdk = mockSdk(); - const kv = mockKV( - [makeMemory("mem_evict", "fact", 500)], - [makeSemanticMemory("sem_evict", 500, 0)], - ); - registerRetentionFunctions(sdk as never, kv as never); - - await sdk.trigger({ function_id: "mem::retention-score", payload: {} }); - const result = (await sdk.trigger({ - function_id: "mem::retention-evict", - payload: { threshold: 0.9 }, - })) as any; - - expect(result.evicted).toBe(2); - expect(result.evictedEpisodic).toBe(1); - expect(result.evictedSemantic).toBe(1); - - const remainingEp = await kv.list("mem:memories"); - const remainingSem = await kv.list("mem:semantic"); - expect(remainingEp).toHaveLength(0); - expect(remainingSem).toHaveLength(0); - - // Retention score rows also cleaned up for both. - const remainingScores = await kv.list("mem:retention"); - expect(remainingScores).toHaveLength(0); - }); - - it("mem::retention-evict emits a single batched audit record on success (#124, audit policy)", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - const sdk = mockSdk(); - const kv = mockKV( - [makeMemory("mem_a", "fact", 500), makeMemory("mem_b", "fact", 500)], - [makeSemanticMemory("sem_c", 500, 0)], - ); - registerRetentionFunctions(sdk as never, kv as never); - - await sdk.trigger({ function_id: "mem::retention-score", payload: {} }); - await sdk.trigger({ - function_id: "mem::retention-evict", - payload: { threshold: 0.9 }, - }); - - // Retention-score ALSO emits an audit row (one per rescore, also - // required by the repo audit-coverage policy), so filter the audit - // log down to just the retention-evict entry we're asserting on. - const allEntries = await kv.list<{ - operation: string; - functionId: string; - targetIds: string[]; - details: Record; - }>("mem:audit"); - const evictEntries = allEntries.filter( - (e) => e.functionId === "mem::retention-evict", - ); - expect(evictEntries).toHaveLength(1); - const [entry] = evictEntries; - expect(entry.operation).toBe("delete"); - expect([...entry.targetIds].sort()).toEqual(["mem_a", "mem_b", "sem_c"]); - expect(entry.details.evicted).toBe(3); - expect(entry.details.evictedEpisodic).toBe(2); - expect(entry.details.evictedSemantic).toBe(1); - }); - - it("mem::retention-evict skips audit when evicted=0 (no spurious audit rows)", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - const sdk = mockSdk(); - // Memory is 1 day old → score will be high → nothing falls below - // the strict 0.99 threshold → evict=0 → no evict audit row. - // Retention-score itself still writes one audit row per sweep, - // which is the expected behavior (zero-eviction != zero-rescore), - // so we filter the audit log down to just the evict entries. - const kv = mockKV([makeMemory("mem_keep", "architecture", 1)]); - registerRetentionFunctions(sdk as never, kv as never); - - await sdk.trigger({ function_id: "mem::retention-score", payload: {} }); - await sdk.trigger({ - function_id: "mem::retention-evict", - payload: { threshold: 0.0001 }, - }); - - const allEntries = await kv.list<{ functionId: string }>("mem:audit"); - const evictEntries = allEntries.filter( - (e) => e.functionId === "mem::retention-evict", - ); - expect(evictEntries).toHaveLength(0); - }); - - it("mem::retention-score emits a batched audit row per rescore (#124, audit policy)", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - const sdk = mockSdk(); - const kv = mockKV( - [makeMemory("mem_a", "fact", 10), makeMemory("mem_b", "fact", 10)], - [makeSemanticMemory("sem_c", 10, 2)], - ); - registerRetentionFunctions(sdk as never, kv as never); - - await sdk.trigger({ function_id: "mem::retention-score", payload: {} }); - - const allEntries = await kv.list<{ - operation: string; - functionId: string; - targetIds: string[]; - details: Record; - }>("mem:audit"); - const scoreEntries = allEntries.filter( - (e) => e.functionId === "mem::retention-score", - ); - expect(scoreEntries).toHaveLength(1); - const [entry] = scoreEntries; - expect(entry.operation).toBe("retention_score"); - // targetIds is intentionally empty — a mature store can have 1000+ - // memory ids per rescore and flooding the audit log would be worse - // than recording just the summary counts. - expect(entry.targetIds).toEqual([]); - expect(entry.details.total).toBe(3); - expect(entry.details.episodic).toBe(2); - expect(entry.details.semantic).toBe(1); - }); - - it("mem::retention-evict probes namespaces for legacy semantic rows (backwards-compat, #124)", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - // The actual nasty case from a prior review: a pre-0.8.10 - // store that had a semantic memory scored by the old code path. - // The retention row has NO source field and the memory lives in - // mem:semantic. If the eviction path blindly defaults missing - // source to episodic, it no-ops the delete and strands the - // semantic row forever — which is the exact bug #124 is about. - const sdk = mockSdk(); - const kv = mockKV([], [makeSemanticMemory("sem_legacy", 500, 0)]); - registerRetentionFunctions(sdk as never, kv as never); - - await kv.set("mem:retention", "sem_legacy", { - memoryId: "sem_legacy", - // No `source` field — simulates a row written by 0.8.9 or earlier. - score: 0.01, - salience: 0, - temporalDecay: 0, - reinforcementBoost: 0, - lastAccessed: new Date().toISOString(), - accessCount: 0, - }); - - const result = (await sdk.trigger({ - function_id: "mem::retention-evict", - payload: { threshold: 0.5 }, - })) as any; - expect(result.evicted).toBe(1); - expect(result.evictedSemantic).toBe(1); - expect(result.evictedEpisodic).toBe(0); - - // Most important assertion: the semantic row is GONE from - // mem:semantic. Before the probe fix, this assertion failed - // because the delete targeted mem:memories. - const remainingSem = await kv.list("mem:semantic"); - expect(remainingSem).toHaveLength(0); - }); - - it("mem::retention-evict routes pre-0.8.10 episodic rows with missing source to mem:memories (#124)", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - - // Simulate a store that was scored on 0.8.9 or earlier: retention - // rows exist but they have no `source` field. The new eviction - // loop must still route those to mem:memories so users don't get - // stuck with un-evictable episodic rows after upgrading. - const sdk = mockSdk(); - const kv = mockKV([makeMemory("mem_old", "fact", 500)]); - registerRetentionFunctions(sdk as never, kv as never); - - // Directly plant a legacy-shape retention score (no `source` key). - await kv.set("mem:retention", "mem_old", { - memoryId: "mem_old", - score: 0.01, - salience: 0, - temporalDecay: 0, - reinforcementBoost: 0, - lastAccessed: new Date().toISOString(), - accessCount: 0, - }); - - const result = (await sdk.trigger({ - function_id: "mem::retention-evict", - payload: { threshold: 0.5 }, - })) as any; - expect(result.evicted).toBe(1); - expect(result.evictedEpisodic).toBe(1); - expect(result.evictedSemantic).toBe(0); - const remaining = await kv.list("mem:memories"); - expect(remaining).toHaveLength(0); - }); - - describe("search-index cleanup on eviction", () => { - beforeEach(() => { - getSearchIndex().clear(); - setIndexPersistence(null); - }); - - afterEach(() => { - setIndexPersistence(null); - }); - - it("removes evicted memories from the BM25 index and flushes persistence", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - const persistence = { scheduleSave: vi.fn(), save: vi.fn(async () => {}) }; - setIndexPersistence(persistence); - - const evictee = makeMemory("mem_evict", "fact", 500); - const sdk = mockSdk(); - const kv = mockKV([evictee]); - registerRetentionFunctions(sdk as never, kv as never); - getSearchIndex().add(memoryToObservation(evictee)); - expect(getSearchIndex().has("mem_evict")).toBe(true); - - await sdk.trigger({ function_id: "mem::retention-score", payload: {} }); - await sdk.trigger({ - function_id: "mem::retention-evict", - payload: { threshold: 0.9 }, - }); - - expect(getSearchIndex().has("mem_evict")).toBe(false); - expect(persistence.save).toHaveBeenCalled(); - }); - - it("does not flush persistence when nothing is evicted", async () => { - const { registerRetentionFunctions } = await import( - "../src/functions/retention.js" - ); - const persistence = { scheduleSave: vi.fn(), save: vi.fn(async () => {}) }; - setIndexPersistence(persistence); - - const sdk = mockSdk(); - const kv = mockKV([makeMemory("mem_fresh", "architecture", 1)]); - registerRetentionFunctions(sdk as never, kv as never); - - await sdk.trigger({ function_id: "mem::retention-score", payload: {} }); - await sdk.trigger({ - function_id: "mem::retention-evict", - payload: { threshold: 0.01 }, - }); - - expect(persistence.save).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/test/review-action.test.ts b/test/review-action.test.ts deleted file mode 100644 index e6965dd8..00000000 --- a/test/review-action.test.ts +++ /dev/null @@ -1,535 +0,0 @@ -import { describe, expect, it, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerActionsFunction } from "../src/functions/actions.js"; -import { registerActionCandidateFunctions } from "../src/functions/action-candidates.js"; -import { registerApiTriggers } from "../src/triggers/api.js"; -import type { Action, CompressedObservation, ReviewQueueItem, Session } from "../src/types.js"; -import { KV, fingerprintId } from "../src/state/schema.js"; -import { mockKV, mockSdk } from "./helpers/mocks.js"; - -function req(body: Record = {}, query_params: Record = {}) { - return { body, query_params, headers: {} }; -} - -function compressedObs(id: string, patch: Partial): CompressedObservation { - return { - id, - sessionId: "ses_actions", - timestamp: "2026-06-11T08:00:00.000Z", - type: "conversation", - title: id, - narrative: "", - facts: [], - concepts: [], - files: [], - importance: 5, - ...patch, - }; -} - -describe("review action candidates", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerActionsFunction(sdk as never, kv as never); - registerActionCandidateFunctions(sdk as never, kv as never); - sdk.registerFunction("mem::todo-extract-generate", async (payload) => ({ success: true, ...payload })); - sdk.registerFunction("mem::todo-refresh-action", async (payload) => ({ success: true, keptOld: false, reason: "replaced", scannedObservations: 1, ...payload })); - sdk.registerFunction("api::session::start", async () => ({ success: true })); - sdk.registerFunction("mem::observe", async () => ({ success: true })); - sdk.registerFunction("mem::remember", async () => ({ success: true, memory: { id: "mem_1" } })); - sdk.registerFunction("mem::lesson-save", async () => ({ success: true, lesson: { id: "les_1" } })); - registerApiTriggers(sdk as never, kv as never); - }); - - it("generates action review drafts from recent session observations through the API", async () => { - const session: Session = { - id: "ses_actions", - project: "agentmemory-lab", - cwd: "/repo", - startedAt: "2026-06-11T08:00:00.000Z", - endedAt: "2026-06-11T08:10:00.000Z", - status: "completed", - observationCount: 2, - }; - await kv.set(KV.sessions, session.id, session); - await kv.set(KV.observations(session.id), "obs_1", compressedObs("obs_1", { - sessionId: session.id, - narrative: "下一步请修复待办页不展示未批准候选的问题。", - })); - await kv.set(KV.observations(session.id), "obs_2", compressedObs("obs_2", { - sessionId: session.id, - narrative: "请审查当前实现。", - })); - - const response = await sdk.trigger("api::review-actions-generate", req({ - maxSessions: 10, - maxObservationsPerSession: 20, - })) as { status_code: number; body: { success: boolean; generated: number; items: ReviewQueueItem[]; scannedSessions: number; scannedObservations: number } }; - - expect(response.status_code).toBe(200); - expect(response.body.success).toBe(true); - expect(response.body.generated).toBe(1); - expect(response.body.scannedSessions).toBe(1); - expect(response.body.scannedObservations).toBe(2); - expect(response.body.items[0]).toMatchObject({ - kind: "action", - status: "pending", - source: "viewer", - title: "修复待办页不展示未批准候选的问题", - payload: { - project: "agentmemory-lab", - sourceSessionId: "ses_actions", - sourceSessionProject: "agentmemory-lab", - sourceSessionCwd: "/repo", - }, - }); - - const second = await sdk.trigger("api::review-actions-generate", req({ - maxSessions: 10, - maxObservationsPerSession: 20, - })) as { body: { generated: number; items: ReviewQueueItem[] } }; - expect(second.body.generated).toBe(0); - expect(second.body.items).toEqual([]); - }); - - it("manually triggers todo extraction through the API", async () => { - const response = await sdk.trigger("api::todo-extract-generate", req({ - maxSessions: 3, - maxObservationsPerSession: 20, - project: "agentmemory-lab", - force: true, - cleanup: "dry-run", - })) as { status_code: number; body: Record }; - - expect(response.status_code).toBe(200); - expect(response.body).toMatchObject({ - success: true, - maxSessions: 3, - maxObservationsPerSession: 20, - project: "agentmemory-lab", - force: true, - cleanup: "dry-run", - }); - }); - - it("refreshes a single todo action through a whitelisted API payload", async () => { - const response = await sdk.trigger("api::todo-refresh-action", req({ - actionId: "act_1", - extra: "ignored", - })) as { status_code: number; body: Record }; - - expect(response.status_code).toBe(200); - expect(response.body).toMatchObject({ - success: true, - actionId: "act_1", - keptOld: false, - reason: "replaced", - scannedObservations: 1, - }); - expect(response.body.extra).toBeUndefined(); - }); - - it("validates single todo action refresh requests", async () => { - const missing = await sdk.trigger("api::todo-refresh-action", req({})) as { status_code: number; body: Record }; - expect(missing.status_code).toBe(400); - expect(missing.body).toMatchObject({ error: "actionId is required" }); - - sdk.registerFunction("mem::todo-refresh-action", async () => ({ - success: false, - keptOld: true, - reason: "action-not-found", - error: "action not found", - scannedObservations: 0, - })); - const notFound = await sdk.trigger("api::todo-refresh-action", req({ actionId: "missing" })) as { status_code: number; body: Record }; - expect(notFound.status_code).toBe(404); - expect(notFound.body).toMatchObject({ error: "action not found" }); - }); - - it("exposes todo extractor config without returning the API key", async () => { - const oldModel = process.env.LANGEXTRACT_MODEL; - const oldKey = process.env.LANGEXTRACT_API_KEY; - process.env.LANGEXTRACT_MODEL = "deepseek/deepseek-v4-flash"; - process.env.LANGEXTRACT_API_KEY = "secret"; - const response = await sdk.trigger("api::todo-extractor-config", req()) as { status_code: number; body: { success: boolean; config: Record; envPath: string; restartRequired: boolean } }; - if (oldModel === undefined) delete process.env.LANGEXTRACT_MODEL; - else process.env.LANGEXTRACT_MODEL = oldModel; - if (oldKey === undefined) delete process.env.LANGEXTRACT_API_KEY; - else process.env.LANGEXTRACT_API_KEY = oldKey; - - expect(response.status_code).toBe(200); - expect(response.body.success).toBe(true); - expect(response.body.envPath).toContain(".agentmemory"); - expect(response.body.config.LANGEXTRACT_MODEL).toBe("deepseek/deepseek-v4-flash"); - expect(response.body.config.LANGEXTRACT_API_KEY).toBeUndefined(); - expect(response.body.config.LANGEXTRACT_API_KEY_CONFIGURED).toBe(true); - expect(response.body.restartRequired).toBe(false); - }); - - it("rejects invalid todo extraction limits through the API", async () => { - const response = await sdk.trigger("api::todo-extract-generate", req({ - maxSessions: 0, - })) as { status_code: number; body: Record }; - - expect(response.status_code).toBe(400); - expect(response.body).toMatchObject({ error: "maxSessions must be a positive integer" }); - }); - - it("approves generated session action reviews into the source session project when request project is omitted", async () => { - const session: Session = { - id: "ses_actions", - project: "agentmemory-lab", - cwd: "/repo", - startedAt: "2026-06-11T08:00:00.000Z", - endedAt: "2026-06-11T08:10:00.000Z", - status: "completed", - observationCount: 1, - }; - await kv.set(KV.sessions, session.id, session); - await kv.set(KV.observations(session.id), "obs_1", compressedObs("obs_1", { - sessionId: session.id, - narrative: "下一步请修复待办页项目归属。", - })); - - const generated = await sdk.trigger("api::review-actions-generate", req({ - maxSessions: 10, - maxObservationsPerSession: 20, - })) as { status_code: number; body: { generated: number; items: ReviewQueueItem[] } }; - expect(generated.status_code).toBe(200); - expect(generated.body.generated).toBe(1); - expect(generated.body.items[0].payload?.project).toBe("agentmemory-lab"); - - const approveResponse = await sdk.trigger("api::review-approve", req({ - id: generated.body.items[0].id, - kind: "action", - title: generated.body.items[0].title, - content: generated.body.items[0].content, - })) as { status_code: number; body: { success: boolean; item: ReviewQueueItem; result: { success: boolean; action: Action } } }; - - expect(approveResponse.status_code).toBe(200); - expect(approveResponse.body.result.action).toMatchObject({ - title: "修复待办页项目归属", - project: "agentmemory-lab", - }); - }); - - it("accepts explicit action review items", async () => { - const response = await sdk.trigger("api::review-create", req({ - kind: "action", - title: "修复 action review 批准流程", - content: "下一步请修复 action review 的批准流程。", - source: "api", - payload: { - actionCandidate: { - priority: 8, - sourceObservationIds: ["obs_1"], - reason: "follow_up", - confidence: 0.8, - }, - project: "agentmemory-lab", - tags: ["action-candidate", "follow-up"], - }, - })) as { status_code: number; body: { success: boolean; item: ReviewQueueItem } }; - - expect(response.status_code).toBe(201); - expect(response.body.item.kind).toBe("action"); - expect(response.body.item.payload?.actionCandidate).toMatchObject({ - priority: 8, - sourceObservationIds: ["obs_1"], - }); - }); - - it("rejects non-displayable review candidates including raw action items", async () => { - const planResponse = await sdk.trigger("api::review-create", req({ - kind: "memory", - title: "{", - content: "{\"plan\":[{\"status\":\"completed\",\"step\":\"npm test -- --run test/viewer-session-id.test.ts\"}]}", - source: "api", - })) as { status_code: number; body: { error?: string } }; - - expect(planResponse.status_code).toBe(400); - expect(planResponse.body.error).toBe("review_content_not_displayable"); - - const toolResponse = await sdk.trigger("api::review-create", req({ - kind: "lesson", - title: "工具输出", - content: "{\"function_id\":\"x\",\"toolInput\":{\"command\":\"npm test\"}}", - source: "api", - })) as { status_code: number; body: { error?: string } }; - - expect(toolResponse.status_code).toBe(400); - expect(toolResponse.body.error).toBe("review_content_not_displayable"); - - const pathResponse = await sdk.trigger("api::review-create", req({ - kind: "memory", - title: "源码路径", - content: "src/functions/action-candidates.ts\nnpm test -- --run test/action-candidates.test.ts", - source: "api", - })) as { status_code: number; body: { error?: string } }; - - expect(pathResponse.status_code).toBe(400); - expect(pathResponse.body.error).toBe("review_content_not_displayable"); - - const actionJsonResponse = await sdk.trigger("api::review-create", req({ - kind: "action", - title: "{", - content: "{\"plan\":[{\"status\":\"completed\",\"step\":\"npm test\"}]}", - source: "api", - })) as { status_code: number; body: { error?: string } }; - - expect(actionJsonResponse.status_code).toBe(400); - expect(actionJsonResponse.body.error).toBe("review_content_not_displayable"); - - const actionMarkdownPlanResponse = await sdk.trigger("api::review-create", req({ - kind: "action", - title: "待办生成链路与前端展示修复计划", - content: [ - "# 待办生成链路与前端展示修复计划", - "## Summary", - "本轮修复待办候选生成与展示链路。", - "## Key Changes", - "- 前端待办页改造。", - "## Test Plan", - "- npm test", - ].join("\n"), - source: "api", - })) as { status_code: number; body: { error?: string } }; - - expect(actionMarkdownPlanResponse.status_code).toBe(400); - expect(actionMarkdownPlanResponse.body.error).toBe("review_content_not_displayable"); - - const compactMarkdownPlanResponse = await sdk.trigger("api::review-create", req({ - kind: "action", - title: "待办生成链路与前端展示修复计划", - content: "# 待办生成链路与前端展示修复计划 ## Summary 本轮修复待办候选生成与展示链路。 ## Key Changes - 前端待办页改造。 ## Test Plan - npm test", - source: "api", - })) as { status_code: number; body: { error?: string } }; - - expect(compactMarkdownPlanResponse.status_code).toBe(400); - expect(compactMarkdownPlanResponse.body.error).toBe("review_content_not_displayable"); - - const embeddedCompactMarkdownPlanResponse = await sdk.trigger("api::review-create", req({ - kind: "action", - title: "进行修复计划的构建 审查结果", - content: "进行修复计划的构建 审查结果 [P1] 仍会显示计划。# 待办生成链路与前端展示修复计划 ## Summary 本轮修复链路。 ## Key Changes - 前端过滤。 ## Test Plan - npm test", - source: "api", - })) as { status_code: number; body: { error?: string } }; - - expect(embeddedCompactMarkdownPlanResponse.status_code).toBe(400); - expect(embeddedCompactMarkdownPlanResponse.body.error).toBe("review_content_not_displayable"); - - const summaryOnlyMarkdownPlanResponse = await sdk.trigger("api::review-create", req({ - kind: "action", - title: "待办生成链路与前端展示修复计划", - content: "# 待办生成链路与前端展示修复计划 ## Summary 本轮暂不处理摘要按钮,只修待办候选生成与展示链路。", - source: "api", - })) as { status_code: number; body: { error?: string } }; - - expect(summaryOnlyMarkdownPlanResponse.status_code).toBe(400); - expect(summaryOnlyMarkdownPlanResponse.body.error).toBe("review_content_not_displayable"); - - const reviewFindingResponse = await sdk.trigger("api::review-create", req({ - kind: "action", - title: "进行修复计划的构建 审查结果", - content: "进行修复计划的构建 审查结果 [P1] #memories/#actions 仍会把完整 Markdown 修复计划当成待审阅行动。根因在 src/functions/action-candidates.ts (line 57)。", - source: "api", - })) as { status_code: number; body: { error?: string } }; - - expect(reviewFindingResponse.status_code).toBe(400); - expect(reviewFindingResponse.body.error).toBe("review_content_not_displayable"); - }); - - it("keeps browser review candidates but no longer spawns rule action drafts (STEP-14)", async () => { - const normal = await sdk.trigger("api::review-create", req({ - title: "ChatGPT page", - content: "用户希望待审阅内容保持可读。", - source: "browser-extension", - page: { title: "ChatGPT", host: "chatgpt.com", url: "https://chatgpt.com/c/normal" }, - })) as { status_code: number; body: { success: boolean; item: ReviewQueueItem } }; - - expect(normal.status_code).toBe(201); - expect(normal.body.item.title).toBe("ChatGPT page"); - - const withTurns = await sdk.trigger("api::review-create", req({ - title: "ChatGPT page", - content: "普通记忆候选", - source: "browser-extension", - page: { title: "ChatGPT", host: "chatgpt.com", url: "https://chatgpt.com/c/action" }, - conversation: { - provider: "ChatGPT", - turns: [ - { role: "user", text: "下一步请修复待审阅污染过滤。" }, - ], - }, - })) as { status_code: number; body: { success: boolean; item: ReviewQueueItem; actionDrafts?: ReviewQueueItem[] } }; - - expect(withTurns.status_code).toBe(201); - // STEP-14: browser turns no longer spawn rule-based action drafts — they flow - // through the todo-extract LLM pipeline. The review item is still recorded. - expect(withTurns.body.actionDrafts).toBeUndefined(); - expect(withTurns.body.item.title).toBe("ChatGPT page"); - }); - - it("approves action review items into pending Actions", async () => { - const createResponse = await sdk.trigger("api::review-create", req({ - kind: "action", - title: "修复 action review 批准流程", - content: "下一步请修复 action review 的批准流程。", - source: "api", - payload: { - actionCandidate: { - priority: 8, - sourceObservationIds: ["obs_1"], - reason: "follow_up", - confidence: 0.8, - }, - todoExtraction: { - dedupeKey: "fix-review-action-approve", - typeBucket: "follow_up", - }, - project: "agentmemory-lab", - tags: ["action-candidate", "follow-up"], - }, - })) as { body: { item: ReviewQueueItem } }; - - const approveResponse = await sdk.trigger("api::review-approve", req({ - id: createResponse.body.item.id, - kind: "action", - title: "修复批准流程", - content: "确保 review action approve 调用 mem::action-create。", - project: "agentmemory-lab", - tags: "action-candidate,reviewed", - priority: 9, - })) as { status_code: number; body: { success: boolean; item: ReviewQueueItem; result: { success: boolean; action: Action } } }; - - expect(approveResponse.status_code).toBe(200); - expect(approveResponse.body.item.status).toBe("approved"); - expect(approveResponse.body.item.kind).toBe("action"); - expect(approveResponse.body.result.action).toMatchObject({ - title: "修复批准流程", - description: "确保 review action approve 调用 mem::action-create。", - status: "pending", - priority: 9, - project: "agentmemory-lab", - tags: ["action-candidate", "reviewed"], - sourceObservationIds: ["obs_1"], - }); - expect(approveResponse.body.result.action.id).toBe(fingerprintId("act", "todo:fix-review-action-approve")); - expect(approveResponse.body.result.action.metadata?.todoExtraction).toMatchObject({ - dedupeKey: "fix-review-action-approve", - }); - expect(approveResponse.body.item.resultId).toBe(approveResponse.body.result.action.id); - - const storedActions = await kv.list(KV.actions); - expect(storedActions).toHaveLength(1); - }); - - it("keeps action review items pending when action creation fails", async () => { - sdk.registerFunction("mem::action-create", async () => ({ - success: false, - error: "action backend unavailable", - })); - const item: ReviewQueueItem = { - id: "review_action_failure", - createdAt: "2026-06-11T08:00:00.000Z", - updatedAt: "2026-06-11T08:00:00.000Z", - status: "pending", - kind: "action", - title: "修复审批失败保护", - content: "下一步请修复审批失败保护。", - source: "viewer", - payload: { - actionCandidate: { priority: 8, sourceObservationIds: ["obs_1"] }, - tags: ["action-candidate"], - }, - }; - await kv.set(KV.reviewQueue, item.id, item); - - const response = await sdk.trigger("api::review-approve", req({ - id: item.id, - kind: "action", - priority: "not-a-number", - })) as { status_code: number; body: { success?: boolean; error?: string } }; - - expect(response.status_code).toBe(502); - expect(response.body).toMatchObject({ - success: false, - error: "action backend unavailable", - }); - const stored = await kv.get(KV.reviewQueue, item.id); - expect(stored?.status).toBe("pending"); - expect(stored?.resultId).toBeUndefined(); - }); - - it("dismisses action review items without creating Actions", async () => { - const createResponse = await sdk.trigger("api::review-create", req({ - kind: "action", - title: "忽略这个 action", - content: "TODO: ignore me", - source: "api", - })) as { body: { item: ReviewQueueItem } }; - - const dismissResponse = await sdk.trigger("api::review-dismiss", req({ - id: createResponse.body.item.id, - })) as { status_code: number; body: { success: boolean; item: ReviewQueueItem } }; - - expect(dismissResponse.status_code).toBe(200); - expect(dismissResponse.body.item.status).toBe("dismissed"); - expect(await kv.list(KV.actions)).toEqual([]); - }); - - it("records the browser session but creates no action drafts — browser flows through todo-extract (STEP-14)", async () => { - const response = await sdk.trigger("api::review-create", req({ - title: "ChatGPT page", - content: "普通记忆候选", - source: "browser-extension", - page: { title: "ChatGPT", host: "chatgpt.com", url: "https://chatgpt.com/c/1" }, - conversation: { - provider: "ChatGPT", - turns: [ - { role: "assistant", text: "这里是页面说明。" }, - { role: "user", text: "下一步请修复 Viewer 待审阅里的行动候选展示。" }, - ], - }, - })) as { status_code: number; body: { success: boolean; item: ReviewQueueItem; actionDrafts?: ReviewQueueItem[] } }; - - expect(response.status_code).toBe(201); - expect(response.body.item.kind).toBe("memory"); - // No rule-based action drafts; only the memory review item itself. - expect(response.body.actionDrafts).toBeUndefined(); - const pending = await kv.list(KV.reviewQueue); - expect(pending.filter((item) => item.kind === "action")).toEqual([]); - }); - - it("does not create browser action drafts from ordinary requests or assistant plans", async () => { - const response = await sdk.trigger("api::review-create", req({ - title: "ChatGPT page", - content: "普通记忆候选", - source: "browser-extension", - page: { title: "ChatGPT", host: "chatgpt.com", url: "https://chatgpt.com/c/2" }, - conversation: { - provider: "ChatGPT", - turns: [ - { role: "user", text: "请审查当前实现。" }, - { role: "user", text: "需要分析为何摘要为空。" }, - { role: "user", text: "希望你说明原因。" }, - { role: "assistant", text: "下一步请修复摘要按钮一直显示摘要为空的问题。" }, - ], - }, - })) as { status_code: number; body: { success: boolean; item: ReviewQueueItem; actionDrafts?: ReviewQueueItem[] } }; - - expect(response.status_code).toBe(201); - expect(response.body.actionDrafts ?? []).toEqual([]); - - const pending = await kv.list(KV.reviewQueue); - expect(pending.filter((item) => item.kind === "action")).toEqual([]); - }); -}); diff --git a/test/routines.test.ts b/test/routines.test.ts deleted file mode 100644 index f5a66a71..00000000 --- a/test/routines.test.ts +++ /dev/null @@ -1,498 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerRoutinesFunction } from "../src/functions/routines.js"; -import type { Action, Routine, RoutineRun } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("Routines Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerRoutinesFunction(sdk as never, kv as never); - }); - - describe("mem::routine-create", () => { - it("creates a routine with valid data", async () => { - const result = (await sdk.trigger("mem::routine-create", { - name: "Deploy Pipeline", - description: "Standard deploy steps", - steps: [ - { title: "Build", description: "Run build", actionTemplate: {}, dependsOn: [] }, - { title: "Test", description: "Run tests", actionTemplate: {}, dependsOn: [0] }, - ], - tags: ["deploy", "ci"], - })) as { success: boolean; routine: Routine }; - - expect(result.success).toBe(true); - expect(result.routine.id).toMatch(/^rtn_/); - expect(result.routine.name).toBe("Deploy Pipeline"); - expect(result.routine.description).toBe("Standard deploy steps"); - expect(result.routine.steps.length).toBe(2); - expect(result.routine.tags).toEqual(["deploy", "ci"]); - expect(result.routine.createdAt).toBeDefined(); - expect(result.routine.updatedAt).toBeDefined(); - expect(result.routine.frozen).toBe(true); - }); - - it("returns error when name is missing", async () => { - const result = (await sdk.trigger("mem::routine-create", { - name: "", - steps: [{ title: "Step 1", description: "", actionTemplate: {}, dependsOn: [] }], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("name and steps are required"); - }); - - it("returns error when steps array is empty", async () => { - const result = (await sdk.trigger("mem::routine-create", { - name: "Empty Routine", - steps: [], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("name and steps are required"); - }); - - it("returns error when a step has no title", async () => { - const result = (await sdk.trigger("mem::routine-create", { - name: "Bad Steps", - steps: [ - { title: "Good Step", description: "ok", actionTemplate: {}, dependsOn: [] }, - { title: " ", description: "no title", actionTemplate: {}, dependsOn: [] }, - ], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("step 1 must have a title"); - }); - - it("assigns correct order to steps", async () => { - const result = (await sdk.trigger("mem::routine-create", { - name: "Ordered Routine", - steps: [ - { title: "First", description: "", actionTemplate: {}, dependsOn: [] }, - { title: "Second", description: "", actionTemplate: {}, dependsOn: [] }, - { title: "Third", description: "", actionTemplate: {}, dependsOn: [] }, - ], - })) as { success: boolean; routine: Routine }; - - expect(result.success).toBe(true); - expect(result.routine.steps[0].order).toBe(0); - expect(result.routine.steps[1].order).toBe(1); - expect(result.routine.steps[2].order).toBe(2); - }); - - it("preserves explicit order values", async () => { - const result = (await sdk.trigger("mem::routine-create", { - name: "Custom Order", - steps: [ - { order: 10, title: "First", description: "", actionTemplate: {}, dependsOn: [] }, - { order: 20, title: "Second", description: "", actionTemplate: {}, dependsOn: [] }, - ], - })) as { success: boolean; routine: Routine }; - - expect(result.success).toBe(true); - expect(result.routine.steps[0].order).toBe(10); - expect(result.routine.steps[1].order).toBe(20); - }); - - it("defaults frozen to true when not specified", async () => { - const result = (await sdk.trigger("mem::routine-create", { - name: "Default Frozen", - steps: [{ title: "Step", description: "", actionTemplate: {}, dependsOn: [] }], - })) as { success: boolean; routine: Routine }; - - expect(result.success).toBe(true); - expect(result.routine.frozen).toBe(true); - }); - - it("respects frozen=false when explicitly set", async () => { - const result = (await sdk.trigger("mem::routine-create", { - name: "Unfrozen", - steps: [{ title: "Step", description: "", actionTemplate: {}, dependsOn: [] }], - frozen: false, - })) as { success: boolean; routine: Routine }; - - expect(result.success).toBe(true); - expect(result.routine.frozen).toBe(false); - }); - }); - - describe("mem::routine-list", () => { - beforeEach(async () => { - await sdk.trigger("mem::routine-create", { - name: "Routine A", - steps: [{ title: "S1", description: "", actionTemplate: {}, dependsOn: [] }], - tags: ["deploy"], - frozen: true, - }); - await sdk.trigger("mem::routine-create", { - name: "Routine B", - steps: [{ title: "S1", description: "", actionTemplate: {}, dependsOn: [] }], - tags: ["test", "ci"], - frozen: false, - }); - await sdk.trigger("mem::routine-create", { - name: "Routine C", - steps: [{ title: "S1", description: "", actionTemplate: {}, dependsOn: [] }], - tags: ["deploy", "ci"], - frozen: true, - }); - }); - - it("lists all routines", async () => { - const result = (await sdk.trigger("mem::routine-list", {})) as { - success: boolean; - routines: Routine[]; - }; - - expect(result.success).toBe(true); - expect(result.routines.length).toBe(3); - }); - - it("filters by frozen=true", async () => { - const result = (await sdk.trigger("mem::routine-list", { - frozen: true, - })) as { success: boolean; routines: Routine[] }; - - expect(result.success).toBe(true); - expect(result.routines.length).toBe(2); - expect(result.routines.every((r) => r.frozen === true)).toBe(true); - }); - - it("filters by frozen=false", async () => { - const result = (await sdk.trigger("mem::routine-list", { - frozen: false, - })) as { success: boolean; routines: Routine[] }; - - expect(result.success).toBe(true); - expect(result.routines.length).toBe(1); - expect(result.routines[0].name).toBe("Routine B"); - }); - - it("filters by tags", async () => { - const result = (await sdk.trigger("mem::routine-list", { - tags: ["deploy"], - })) as { success: boolean; routines: Routine[] }; - - expect(result.success).toBe(true); - expect(result.routines.length).toBe(2); - const names = result.routines.map((r) => r.name); - expect(names).toContain("Routine A"); - expect(names).toContain("Routine C"); - }); - - it("filters by tags with multiple matches", async () => { - const result = (await sdk.trigger("mem::routine-list", { - tags: ["ci"], - })) as { success: boolean; routines: Routine[] }; - - expect(result.success).toBe(true); - expect(result.routines.length).toBe(2); - const names = result.routines.map((r) => r.name); - expect(names).toContain("Routine B"); - expect(names).toContain("Routine C"); - }); - }); - - describe("mem::routine-run", () => { - let routineId: string; - - beforeEach(async () => { - const result = (await sdk.trigger("mem::routine-create", { - name: "Test Pipeline", - steps: [ - { order: 0, title: "Build", description: "Build step", actionTemplate: { priority: 3 }, dependsOn: [] }, - { order: 1, title: "Test", description: "Test step", actionTemplate: { priority: 5 }, dependsOn: [0] }, - { order: 2, title: "Deploy", description: "Deploy step", actionTemplate: {}, dependsOn: [0, 1] }, - ], - })) as { success: boolean; routine: Routine }; - routineId = result.routine.id; - }); - - it("creates actions for each step", async () => { - const result = (await sdk.trigger("mem::routine-run", { - routineId, - initiatedBy: "user-1", - })) as { success: boolean; run: RoutineRun; actionsCreated: number }; - - expect(result.success).toBe(true); - expect(result.actionsCreated).toBe(3); - expect(result.run.actionIds.length).toBe(3); - expect(result.run.status).toBe("running"); - expect(result.run.initiatedBy).toBe("user-1"); - - const actions = await kv.list("mem:actions"); - expect(actions.length).toBe(3); - const titles = actions.map((a) => a.title); - expect(titles).toContain("Build"); - expect(titles).toContain("Test"); - expect(titles).toContain("Deploy"); - }); - - it("creates dependency edges between steps", async () => { - const result = (await sdk.trigger("mem::routine-run", { - routineId, - })) as { success: boolean; run: RoutineRun; actionsCreated: number }; - - expect(result.success).toBe(true); - - const edges = await kv.list<{ - id: string; - type: string; - sourceActionId: string; - targetActionId: string; - }>("mem:action-edges"); - - expect(edges.length).toBe(3); - expect(edges.every((e) => e.type === "requires")).toBe(true); - }); - - it("creates routine run tracking object", async () => { - const result = (await sdk.trigger("mem::routine-run", { - routineId, - initiatedBy: "agent-x", - })) as { success: boolean; run: RoutineRun }; - - expect(result.run.id).toMatch(/^run_/); - expect(result.run.routineId).toBe(routineId); - expect(result.run.status).toBe("running"); - expect(result.run.startedAt).toBeDefined(); - expect(result.run.actionIds.length).toBe(3); - expect(result.run.initiatedBy).toBe("agent-x"); - - const stored = await kv.get("mem:routine-runs", result.run.id); - expect(stored).not.toBeNull(); - expect(stored!.routineId).toBe(routineId); - }); - - it("preserves priority 0 via nullish coalescing", async () => { - const createResult = (await sdk.trigger("mem::routine-create", { - name: "Zero Priority", - steps: [ - { order: 0, title: "Step Zero", description: "", actionTemplate: { priority: 0 }, dependsOn: [] }, - ], - })) as { success: boolean; routine: Routine }; - - const runResult = (await sdk.trigger("mem::routine-run", { - routineId: createResult.routine.id, - })) as { success: boolean; run: RoutineRun }; - - const actionId = runResult.run.actionIds[0]; - const action = await kv.get("mem:actions", actionId); - expect(action!.priority).toBe(0); - }); - - it("tags actions with routine id", async () => { - const result = (await sdk.trigger("mem::routine-run", { - routineId, - })) as { success: boolean; run: RoutineRun }; - - for (const actionId of result.run.actionIds) { - const action = await kv.get("mem:actions", actionId); - expect(action!.tags).toContain(`routine:${routineId}`); - } - }); - - it("returns error when routineId is missing", async () => { - const result = (await sdk.trigger("mem::routine-run", { - routineId: "", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("routineId is required"); - }); - - it("returns error when routine is not found", async () => { - const result = (await sdk.trigger("mem::routine-run", { - routineId: "rtn_nonexistent", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("routine not found"); - }); - }); - - describe("mem::routine-status", () => { - let runId: string; - let actionIds: string[]; - - beforeEach(async () => { - const createResult = (await sdk.trigger("mem::routine-create", { - name: "Status Test", - steps: [ - { order: 0, title: "Step A", description: "", actionTemplate: {}, dependsOn: [] }, - { order: 1, title: "Step B", description: "", actionTemplate: {}, dependsOn: [] }, - { order: 2, title: "Step C", description: "", actionTemplate: {}, dependsOn: [] }, - ], - })) as { success: boolean; routine: Routine }; - - const runResult = (await sdk.trigger("mem::routine-run", { - routineId: createResult.routine.id, - })) as { success: boolean; run: RoutineRun }; - - runId = runResult.run.id; - actionIds = runResult.run.actionIds; - }); - - it("reports running status when actions are in progress", async () => { - const result = (await sdk.trigger("mem::routine-status", { - runId, - })) as { success: boolean; run: RoutineRun; progress: { total: number; pending: number } }; - - expect(result.success).toBe(true); - expect(result.run.status).toBe("running"); - expect(result.progress.total).toBe(3); - expect(result.progress.pending).toBe(3); - }); - - it("marks run completed when all actions are done", async () => { - for (const actionId of actionIds) { - const action = await kv.get("mem:actions", actionId); - action!.status = "done"; - await kv.set("mem:actions", actionId, action); - } - - const result = (await sdk.trigger("mem::routine-status", { - runId, - })) as { success: boolean; run: RoutineRun; progress: { done: number; total: number } }; - - expect(result.success).toBe(true); - expect(result.run.status).toBe("completed"); - expect(result.run.completedAt).toBeDefined(); - expect(result.progress.done).toBe(3); - }); - - it("marks run failed when any action is cancelled", async () => { - const action = await kv.get("mem:actions", actionIds[0]); - action!.status = "cancelled"; - await kv.set("mem:actions", actionIds[0], action); - - const result = (await sdk.trigger("mem::routine-status", { - runId, - })) as { success: boolean; run: RoutineRun }; - - expect(result.success).toBe(true); - expect(result.run.status).toBe("failed"); - }); - - it("marks run failed when any action is cancelled (mixed statuses)", async () => { - const action = await kv.get("mem:actions", actionIds[1]); - (action as Action).status = "done"; - await kv.set("mem:actions", actionIds[1], action); - - const action2 = await kv.get("mem:actions", actionIds[2]); - (action2 as Action).status = "cancelled"; - await kv.set("mem:actions", actionIds[2], action2); - - const result = (await sdk.trigger("mem::routine-status", { - runId, - })) as { success: boolean; run: RoutineRun }; - - expect(result.success).toBe(true); - expect(result.run.status).toBe("failed"); - }); - - it("returns error when runId is missing", async () => { - const result = (await sdk.trigger("mem::routine-status", { - runId: "", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("runId is required"); - }); - - it("returns error when run is not found", async () => { - const result = (await sdk.trigger("mem::routine-status", { - runId: "run_nonexistent", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("run not found"); - }); - }); - - describe("mem::routine-freeze", () => { - it("freezes a routine", async () => { - const createResult = (await sdk.trigger("mem::routine-create", { - name: "Unfreeze Me", - steps: [{ title: "Step", description: "", actionTemplate: {}, dependsOn: [] }], - frozen: false, - })) as { success: boolean; routine: Routine }; - - expect(createResult.routine.frozen).toBe(false); - - const result = (await sdk.trigger("mem::routine-freeze", { - routineId: createResult.routine.id, - })) as { success: boolean; routine: Routine }; - - expect(result.success).toBe(true); - expect(result.routine.frozen).toBe(true); - expect(result.routine.updatedAt).toBeDefined(); - }); - - it("returns error when routineId is missing", async () => { - const result = (await sdk.trigger("mem::routine-freeze", { - routineId: "", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("routineId is required"); - }); - - it("returns error when routine is not found", async () => { - const result = (await sdk.trigger("mem::routine-freeze", { - routineId: "rtn_nonexistent", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("routine not found"); - }); - }); -}); diff --git a/test/rules.test.ts b/test/rules.test.ts new file mode 100644 index 00000000..fb1de3f5 --- /dev/null +++ b/test/rules.test.ts @@ -0,0 +1,34 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { extractRuleCandidate } from "../src/extract/rules.js"; + +test("rules extract concise titles and preserve descriptions", () => { + const candidate = extractRuleCandidate("Please add CLI list output so users can inspect pending work."); + + assert.ok(candidate); + assert.equal(candidate.title, "Add CLI list output"); + assert.equal(candidate.description, "Please add CLI list output so users can inspect pending work."); +}); + +test("rules ignore polite chatter and completed status updates", () => { + assert.equal(extractRuleCandidate("Please take a look when you have time."), null); + assert.equal(extractRuleCandidate("Implemented the doctor command."), null); + assert.equal(extractRuleCandidate("Thanks, this is done now."), null); +}); + +test("rules produce stable merge keys for similar requests", () => { + const first = extractRuleCandidate("Please add CLI list output"); + const second = extractRuleCandidate("Need to add cli list output."); + + assert.ok(first); + assert.ok(second); + assert.equal(first.mergeKey, second.mergeKey); + assert.equal(first.title, "Add CLI list output"); +}); + +test("rules clean todo prefixes and support Chinese action requests", () => { + assert.equal(extractRuleCandidate("todo: fix login redirect.")?.title, "Fix login redirect"); + assert.equal(extractRuleCandidate("需要修复设置保存")?.title, "需要修复设置保存"); + assert.equal(extractRuleCandidate("请添加导出按钮")?.title, "请添加导出按钮"); + assert.equal(extractRuleCandidate("请看看这个,谢谢"), null); +}); diff --git a/test/runtime.test.ts b/test/runtime.test.ts new file mode 100644 index 00000000..7c2807e8 --- /dev/null +++ b/test/runtime.test.ts @@ -0,0 +1,129 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { main } from "../src/cli.js"; +import { openDatabase } from "../src/db/index.js"; +import { getAppPaths } from "../src/paths.js"; +import { createAppServer } from "../src/server/index.js"; + +test("doctor creates config, data, and database paths", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-")); + const previous = process.env.AI_TODO_HOME; + process.env.AI_TODO_HOME = dir; + + try { + const doctor = await capture(() => main(["doctor"])); + assert.equal(doctor.code, 0); + assert.match(doctor.stdout, /llm enabled: true/); + assert.match(doctor.stdout, /env status: missing; run ai-todo init/); + assert.match(doctor.stdout, /llm key: missing/); + assert.match(doctor.stdout, /llm model: deepseek\/deepseek-v4-flash/); + assert.match(doctor.stdout, /llm endpoint: https:\/\/api\.novita\.ai\/openai\/v1/); + assert.doesNotMatch(doctor.stdout, /llm python|llm runtime/); + const db = openDatabase(getAppPaths(dir)); + const rows = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all(); + db.close(); + assert.ok(rows.some((row) => row.name === "sessions")); + } finally { + process.env.AI_TODO_HOME = previous; + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("healthz returns ok", async () => { + const server = createAppServer(); + + await new Promise((resolve) => server.listen(0, resolve)); + try { + const address = server.address(); + assert.ok(address && typeof address !== "string"); + const response = await fetch(`http://127.0.0.1:${address.port}/healthz`); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { ok: true }); + } finally { + await new Promise((resolve, reject) => { + server.close((error) => error ? reject(error) : resolve()); + }); + } +}); + +test("sessions endpoint omits zero-observation sessions and returns preview metadata", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-session-list-")); + const db = openDatabase(getAppPaths(dir)); + const server = createAppServer({ db }); + + db.prepare("INSERT INTO sessions (id, source, path, updated_at) VALUES ('empty', 'claude-code', 'empty.jsonl', '2026-01-01T00:00:00.000Z')").run(); + db.prepare("INSERT INTO sessions (id, source, path, updated_at) VALUES ('visible', 'codex', 'visible.jsonl', '2026-01-02T00:00:00.000Z')").run(); + db.prepare("INSERT INTO observations (id, session_id, source, role, text, created_at) VALUES ('obs1', 'visible', 'codex', 'user', 'Please show this preview', '2026-01-02T00:00:00.000Z')").run(); + + await new Promise((resolve) => server.listen(0, resolve)); + try { + const address = server.address(); + assert.ok(address && typeof address !== "string"); + const response = await fetch(`http://127.0.0.1:${address.port}/sessions`); + assert.equal(response.status, 200); + const sessions = await response.json(); + assert.equal(sessions.length, 1); + assert.equal(sessions[0].id, "visible"); + assert.equal(sessions[0].observationCount, 1); + assert.equal(sessions[0].preview, "Please show this preview"); + } finally { + await new Promise((resolve, reject) => { + server.close((error) => error ? reject(error) : resolve()); + }); + db.close(); + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("database migration clears noisy pre-clean transcript data once", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-clean-migration-")); + const paths = getAppPaths(dir); + try { + let db = openDatabase(paths); + db.prepare("INSERT INTO sessions (id, source, path, updated_at) VALUES ('s1', 'codex', 'old.jsonl', '2026-01-01T00:00:00.000Z')").run(); + db.prepare("INSERT INTO observations (id, session_id, source, role, text, created_at) VALUES ('o1', 's1', 'codex', 'user', 'old noisy text', '2026-01-01T00:00:00.000Z')").run(); + db.prepare("INSERT INTO scan_checkpoints (source, path, mtime_ms, size) VALUES ('codex', 'old.jsonl', 1, 1)").run(); + db.prepare("INSERT INTO todos (id, title, description, status, updated_at) VALUES ('t1', 'Old', 'Old noisy todo', 'done', '2026-01-01T00:00:00.000Z')").run(); + db.prepare("INSERT INTO todos (id, title, description, status, updated_at) VALUES ('t-ignore', 'Ignored', 'Ignored noisy todo', 'ignored', '2026-01-01T00:00:00.000Z')").run(); + db.prepare("INSERT INTO evidence (id, todo_id, observation_id, text) VALUES ('e1', 't1', 'o1', 'old evidence')").run(); + db.prepare("INSERT INTO organize_runs (id, result_json, created_at) VALUES ('r1', '{}', '2026-01-01T00:00:00.000Z')").run(); + db.prepare("UPDATE schema_meta SET value = '0' WHERE key = 'clean_transcript_version'").run(); + db.close(); + + db = openDatabase(paths); + for (const table of ["sessions", "observations", "scan_checkpoints", "evidence", "organize_runs"]) { + const row = db.prepare(`SELECT COUNT(*) as count FROM ${table}`).get() as { count: number }; + assert.equal(row.count, 0, table); + } + const keptTodo = db.prepare("SELECT title, description, status FROM todos WHERE id = 't1'").get() as { title: string; description: string; status: string }; + assert.equal(keptTodo.title, "Old"); + assert.equal(keptTodo.description, "Old noisy todo"); + assert.equal(keptTodo.status, "done"); + assert.equal((db.prepare("SELECT status FROM todos WHERE id = 't-ignore'").get() as { status: string }).status, "ignored"); + db.prepare("INSERT INTO todos (id, title, description, status, updated_at) VALUES ('t2', 'Fresh', 'Fresh LLM todo', 'todo', '2026-01-02T00:00:00.000Z')").run(); + db.close(); + + db = openDatabase(paths); + const row = db.prepare("SELECT COUNT(*) as count FROM todos").get() as { count: number }; + db.close(); + assert.equal(row.count, 3); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +async function capture(fn: () => Promise) { + let stdout = ""; + const originalLog = console.log; + console.log = (...args: unknown[]) => { + stdout += `${args.join(" ")}\n`; + }; + try { + return { code: await fn(), stdout }; + } finally { + console.log = originalLog; + } +} diff --git a/test/schema-fingerprint.test.ts b/test/schema-fingerprint.test.ts deleted file mode 100644 index f56af2c1..00000000 --- a/test/schema-fingerprint.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { fingerprintId, KV } from "../src/state/schema.js"; - -describe("fingerprintId", () => { - it("returns string with correct prefix", () => { - const id = fingerprintId("mem", "some content"); - expect(id).toMatch(/^mem_/); - }); - - it("same content produces same ID (deterministic)", () => { - const id1 = fingerprintId("obs", "identical content here"); - const id2 = fingerprintId("obs", "identical content here"); - expect(id1).toBe(id2); - }); - - it("different content produces different IDs", () => { - const id1 = fingerprintId("obs", "content alpha"); - const id2 = fingerprintId("obs", "content beta"); - expect(id1).not.toBe(id2); - }); - - it("different prefixes produce different IDs", () => { - const id1 = fingerprintId("mem", "same content"); - const id2 = fingerprintId("obs", "same content"); - expect(id1).not.toBe(id2); - }); - - it("ID has sufficient length (prefix + underscore + 16 hex chars)", () => { - const id = fingerprintId("mem", "test"); - const parts = id.split("_"); - expect(parts.length).toBe(2); - expect(parts[0]).toBe("mem"); - expect(parts[1]).toHaveLength(16); - expect(parts[1]).toMatch(/^[0-9a-f]{16}$/); - }); - - it("handles empty content", () => { - const id = fingerprintId("x", ""); - expect(id).toMatch(/^x_[0-9a-f]{16}$/); - }); - - it("handles long content", () => { - const longContent = "a".repeat(10000); - const id = fingerprintId("long", longContent); - expect(id).toMatch(/^long_[0-9a-f]{16}$/); - }); -}); - -describe("KV scopes", () => { - it("has actions scope", () => { - expect(KV.actions).toBe("mem:actions"); - }); - - it("has actionEdges scope", () => { - expect(KV.actionEdges).toBe("mem:action-edges"); - }); - - it("has leases scope", () => { - expect(KV.leases).toBe("mem:leases"); - }); - - it("has routines scope", () => { - expect(KV.routines).toBe("mem:routines"); - }); - - it("has routineRuns scope", () => { - expect(KV.routineRuns).toBe("mem:routine-runs"); - }); - - it("has signals scope", () => { - expect(KV.signals).toBe("mem:signals"); - }); - - it("has checkpoints scope", () => { - expect(KV.checkpoints).toBe("mem:checkpoints"); - }); - - it("has mesh scope", () => { - expect(KV.mesh).toBe("mem:mesh"); - }); -}); diff --git a/test/schema.test.ts b/test/schema.test.ts deleted file mode 100644 index 5f09df23..00000000 --- a/test/schema.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { KV, STREAM, generateId, charBigramSimilarity, nearDuplicateTitle } from '../src/state/schema.js' - -describe('KV', () => { - it('has correct session scope', () => { - expect(KV.sessions).toBe('mem:sessions') - }) - - it('generates observation scope with session ID', () => { - expect(KV.observations('ses_123')).toBe('mem:obs:ses_123') - }) - - it('has correct summaries scope', () => { - expect(KV.summaries).toBe('mem:summaries') - }) -}) - -describe('STREAM', () => { - it('has correct name', () => { - expect(STREAM.name).toBe('mem-live') - }) - - it('group returns session ID', () => { - expect(STREAM.group('ses_123')).toBe('ses_123') - }) -}) - -describe('generateId', () => { - it('includes prefix', () => { - expect(generateId('obs')).toMatch(/^obs_/) - }) - - it('generates unique IDs', () => { - const ids = new Set(Array.from({ length: 100 }, () => generateId('test'))) - expect(ids.size).toBe(100) - }) - - it('has sufficient length', () => { - const id = generateId('obs') - expect(id.length).toBeGreaterThan(15) - }) -}) - -describe('charBigramSimilarity', () => { - it('treats identical and empty strings as fully similar', () => { - expect(charBigramSimilarity('克隆上游项目到子目录', '克隆上游项目到子目录')).toBe(1) - expect(charBigramSimilarity('', '')).toBe(1) - }) - - it('scores CJK rewordings high where whitespace jaccard cannot', () => { - // insertion and word-order drift of the same task - expect(charBigramSimilarity('克隆上游项目到子目录', '克隆上游项目到子目录中')).toBeGreaterThanOrEqual(0.7) - expect(charBigramSimilarity('克隆上游项目到子目录', '把上游项目克隆到子目录')).toBeGreaterThanOrEqual(0.7) - }) - - it('keeps unrelated CJK titles well below the dedup bar', () => { - expect(charBigramSimilarity('修复登录态失效后摘要不显示', '克隆上游项目到子目录')).toBeLessThan(0.3) - expect(charBigramSimilarity('修复摘要按钮', '克隆上游项目到子目录')).toBeLessThan(0.3) - }) - - it('ignores punctuation, whitespace, and case', () => { - expect(charBigramSimilarity('Fix the CI failure', 'fix the ci, failure!')).toBe(1) - }) -}) - -describe('nearDuplicateTitle', () => { - it('collapses CJK rewordings of the same task (insertion, word-order)', () => { - expect(nearDuplicateTitle('克隆上游项目到子目录', '克隆上游项目到子目录中')).toBe(true) - expect(nearDuplicateTitle('克隆上游项目到子目录', '克隆上游项目到子目录下')).toBe(true) - expect(nearDuplicateTitle('克隆上游项目到子目录', '把上游项目克隆到子目录')).toBe(true) - }) - - it('keeps two-char CJK substitutions apart (possibly distinct tasks)', () => { - // identical surface shape, but 项目/仓库 and 失效/超时 may be different work — - // both sit at/below ~0.667, under the 0.70 bar, so neither is collapsed. - expect(nearDuplicateTitle('克隆上游项目到子目录', '克隆上游仓库到子目录')).toBe(false) - expect(nearDuplicateTitle('修复登录态失效问题', '修复登录态超时问题')).toBe(false) - }) - - it('never collapses a polarity flip even at very high surface similarity', () => { - // sim 0.77–0.93 but the negation makes them opposite tasks - expect(charBigramSimilarity('支持离线缓存模式', '不支持离线缓存模式')).toBeGreaterThanOrEqual(0.7) - expect(nearDuplicateTitle('支持离线缓存模式', '不支持离线缓存模式')).toBe(false) - expect(nearDuplicateTitle('启用实验性缓存', '禁用实验性缓存')).toBe(false) - expect(nearDuplicateTitle('登录后显示摘要', '登录后不显示摘要')).toBe(false) - expect(nearDuplicateTitle('enable offline cache mode', 'disable offline cache mode')).toBe(false) - }) - - it('never collapses differing numbers, versions, or ordinals', () => { - expect(charBigramSimilarity('升级依赖到 v2', '升级依赖到 v3')).toBeGreaterThanOrEqual(0.7) - expect(nearDuplicateTitle('升级依赖到 v2', '升级依赖到 v3')).toBe(false) - expect(nearDuplicateTitle('迁移到 node 18', '迁移到 node 20')).toBe(false) - expect(nearDuplicateTitle('把超时改成30秒', '把超时改成60秒')).toBe(false) - expect(nearDuplicateTitle('实现第一阶段迁移', '实现第二阶段迁移')).toBe(false) - }) - - it('never collapses English titles that differ by one content word', () => { - expect(charBigramSimilarity('fix the login bug', 'fix the logout bug')).toBeGreaterThanOrEqual(0.7) - expect(nearDuplicateTitle('fix the login bug', 'fix the logout bug')).toBe(false) - expect(nearDuplicateTitle('update the user docs', 'update the user code')).toBe(false) - expect(nearDuplicateTitle('refactor the auth module', 'refactor the auth method')).toBe(false) - }) - - it('does not fuzzy-match titles shorter than the 6-char floor', () => { - // '克隆上游项' (5) vs '克隆上游项目' is bigram-similar but too short to trust - expect(charBigramSimilarity('克隆上游项', '克隆上游项目')).toBeGreaterThanOrEqual(0.7) - expect(nearDuplicateTitle('克隆上游项', '克隆上游项目')).toBe(false) - expect(nearDuplicateTitle('修复CI', '修复CD')).toBe(false) - }) - - it('measures the floor on the same normalization the scorer uses (no whitespace inflation)', () => { - // raw 'fix ci' is 6 chars but its canonical form 'fixci' is 5 — it must - // fall below the floor, not collapse with the distinct task 'fix cd'. - expect(nearDuplicateTitle('fix ci', 'fix cd')).toBe(false) - expect(nearDuplicateTitle('use ci', 'use cd')).toBe(false) - }) -}) diff --git a/test/search-index.test.ts b/test/search-index.test.ts deleted file mode 100644 index 805faec4..00000000 --- a/test/search-index.test.ts +++ /dev/null @@ -1,293 +0,0 @@ -import { describe, it, expect, beforeEach } from "vitest"; -import { SearchIndex } from "../src/state/search-index.js"; -import { segmentCjk } from "../src/state/cjk-segmenter.js"; -import type { CompressedObservation } from "../src/types.js"; - -function makeObs( - overrides: Partial = {}, -): CompressedObservation { - return { - id: "obs_1", - sessionId: "ses_1", - timestamp: new Date().toISOString(), - type: "file_edit", - title: "Edit auth middleware", - subtitle: "JWT validation", - facts: ["Added token check"], - narrative: "Modified the auth middleware to validate JWT tokens", - concepts: ["authentication", "jwt"], - files: ["src/middleware/auth.ts"], - importance: 7, - ...overrides, - }; -} - -describe("SearchIndex", () => { - let index: SearchIndex; - - beforeEach(() => { - index = new SearchIndex(); - }); - - it("starts empty", () => { - expect(index.size).toBe(0); - }); - - it("adds and finds observations", () => { - index.add(makeObs()); - expect(index.size).toBe(1); - const results = index.search("auth"); - expect(results.length).toBe(1); - expect(results[0].obsId).toBe("obs_1"); - }); - - it("returns empty for no matches", () => { - index.add(makeObs()); - expect(index.search("database")).toEqual([]); - }); - - it("scores exact matches higher than prefix matches", () => { - index.add( - makeObs({ - id: "obs_exact", - title: "redis cache", - narrative: "Set up redis caching layer", - concepts: ["redis"], - facts: ["Added redis"], - files: ["src/redis.ts"], - }), - ); - index.add( - makeObs({ - id: "obs_prefix", - title: "redistool handler", - narrative: "Set up redistool for ops", - concepts: ["redistool"], - facts: ["Added redistool"], - files: ["src/redistool.ts"], - }), - ); - const results = index.search("redis"); - const exact = results.find((r) => r.obsId === "obs_exact"); - const prefix = results.find((r) => r.obsId === "obs_prefix"); - expect(exact).toBeDefined(); - expect(prefix).toBeDefined(); - expect(exact!.score).toBeGreaterThanOrEqual(prefix!.score); - }); - - it("respects limit", () => { - for (let i = 0; i < 30; i++) { - index.add(makeObs({ id: `obs_${i}`, title: `auth feature ${i}` })); - } - expect(index.search("auth", 5).length).toBe(5); - }); - - it("clears the index", () => { - index.add(makeObs()); - index.clear(); - expect(index.size).toBe(0); - expect(index.search("auth")).toEqual([]); - }); - - // Regression coverage: deleted docs must not keep occupying result - // slots after remove(). Without remove(), a limit-capped search can - // return fewer live results than requested because the slot is held - // by a doc that no longer exists in storage. - describe("remove", () => { - it("drops a removed doc from search results", () => { - index.add(makeObs({ id: "obs_a", title: "jose for JWT" })); - index.add( - makeObs({ id: "obs_b", title: "JWT tokens expire after 30 minutes" }), - ); - expect(index.search("jose jwt", 1)[0].obsId).toBe("obs_a"); - - index.remove("obs_a"); - - // With limit=1, the survivor must now surface instead of getting - // masked by the deleted doc's slot. - const after = index.search("jose jwt", 1); - expect(after).toHaveLength(1); - expect(after[0].obsId).toBe("obs_b"); - expect(index.size).toBe(1); - }); - - it("is a no-op for unknown ids", () => { - index.add(makeObs({ id: "obs_a" })); - expect(() => index.remove("does_not_exist")).not.toThrow(); - expect(index.size).toBe(1); - expect(index.search("auth")).toHaveLength(1); - }); - - it("cleans up empty posting lists and the prefix cache", () => { - // unique_concept_xyz appears only in obs_a. After removing obs_a, - // a prefix search for "unique_" must NOT surface it from the - // sortedTerms cache. - index.add( - makeObs({ - id: "obs_a", - concepts: ["unique_concept_xyz"], - }), - ); - index.add(makeObs({ id: "obs_b", title: "completely different" })); - // Prime the sortedTerms cache. - expect(index.search("unique_")).toHaveLength(1); - - index.remove("obs_a"); - - expect(index.search("unique_concept_xyz")).toEqual([]); - expect(index.search("unique_")).toEqual([]); - }); - - it("keeps scoring consistent after add/remove/add cycles", () => { - // totalDocLength bookkeeping desync would skew BM25 normalization - // (docLen / avgDocLen). Round-trip add → remove → re-add and - // confirm size + retrievability hold. - const obs = makeObs({ id: "obs_a" }); - index.add(obs); - expect(index.size).toBe(1); - index.remove("obs_a"); - expect(index.size).toBe(0); - index.add(obs); - expect(index.size).toBe(1); - expect(index.search("auth")[0].obsId).toBe("obs_a"); - }); - - it("survives serialize/deserialize after removes", () => { - index.add(makeObs({ id: "obs_a", title: "alpha doc" })); - index.add(makeObs({ id: "obs_b", title: "beta doc" })); - index.remove("obs_a"); - - const restored = SearchIndex.deserialize(index.serialize()); - expect(restored.size).toBe(1); - expect(restored.search("alpha")).toEqual([]); - expect(restored.search("beta")).toHaveLength(1); - }); - }); - - it("returns empty for empty query", () => { - index.add(makeObs()); - expect(index.search("")).toEqual([]); - }); - - it("searches across multiple fields", () => { - index.add( - makeObs({ id: "obs_file", title: "something", files: ["auth.ts"] }), - ); - expect(index.search("auth").length).toBe(1); - }); - - it("handles multiple query terms", () => { - index.add( - makeObs({ - id: "obs_both", - title: "redis cache", - narrative: "Set up redis and cache layer", - concepts: ["redis", "cache"], - facts: ["Added caching"], - files: ["src/cache.ts"], - }), - ); - index.add( - makeObs({ - id: "obs_one", - title: "redis only", - narrative: "Set up redis connection", - concepts: ["redis"], - facts: ["Added redis"], - files: ["src/redis.ts"], - }), - ); - const results = index.search("redis cache"); - expect(results[0].obsId).toBe("obs_both"); - expect(results[0].score).toBeGreaterThan(results[1].score); - }); - - it("indexes and finds non-ASCII (Greek) text", () => { - index.add( - makeObs({ - id: "obs_greek", - title: "Προβολή μνήμης", - narrative: "Δοκιμάζουμε αναζήτηση σε ελληνικά", - concepts: ["δοκιμή", "μνήμη"], - }), - ); - const results = index.search("μνήμη"); - expect(results.length).toBe(1); - expect(results[0].obsId).toBe("obs_greek"); - }); - - it("tokenizes mixed ASCII and non-ASCII (Greek) queries", () => { - index.add( - makeObs({ - id: "obs_mixed", - title: "JWT middleware ρύθμιση", - narrative: "Configured JWT with ελληνικά σχόλια", - concepts: ["auth", "jwt", "ρύθμιση"], - }), - ); - const results = index.search("JWT ρύθμιση"); - expect(results.length).toBe(1); - expect(results[0].obsId).toBe("obs_mixed"); - }); - - it("segments Chinese (Han) text into words", () => { - index.add( - makeObs({ - id: "obs_zh", - title: "项目记忆存储", - narrative: "我们正在测试中文分词", - concepts: ["项目", "记忆"], - }), - ); - const results = index.search("项目"); - expect(results.length).toBeGreaterThan(0); - const hit = results.find((r) => r.obsId === "obs_zh"); - expect(hit).toBeDefined(); - expect(hit!.score).toBeGreaterThan(0); - }); - - it("segments Japanese (kana + kanji) text into words", () => { - index.add( - makeObs({ - id: "obs_ja", - title: "プロジェクト記憶", - narrative: "日本語の分かち書きをテストしています", - concepts: ["プロジェクト", "記憶"], - }), - ); - const results = index.search("プロジェクト"); - expect(results.length).toBeGreaterThan(0); - const hit = results.find((r) => r.obsId === "obs_ja"); - expect(hit).toBeDefined(); - expect(hit!.score).toBeGreaterThan(0); - }); - - it("segments Korean (Hangul) syllable blocks into words", () => { - index.add( - makeObs({ - id: "obs_ko", - title: "프로젝트 메모리 저장소", - narrative: "한국어 검색을 테스트합니다", - concepts: ["프로젝트", "메모리"], - }), - ); - const results = index.search("메모리"); - expect(results.length).toBeGreaterThan(0); - const hit = results.find((r) => r.obsId === "obs_ko"); - expect(hit).toBeDefined(); - expect(hit!.score).toBeGreaterThan(0); - }); - - it("preserves source order across mixed CJK and non-CJK runs", () => { - expect(segmentCjk("hello 项目 world")).toEqual(["hello", "项目", "world"]); - expect(segmentCjk("abc 메모리 def 项目 ghi")).toEqual([ - "abc", - "메모리", - "def", - "项目", - "ghi", - ]); - expect(segmentCjk("leading 项目")).toEqual(["leading", "项目"]); - expect(segmentCjk("项目 trailing")).toEqual(["项目", "trailing"]); - }); -}); diff --git a/test/search.test.ts b/test/search.test.ts deleted file mode 100644 index 7bc635ce..00000000 --- a/test/search.test.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerSearchFunction, getSearchIndex, rebuildIndex, setVectorIndex, setEmbeddingProvider, getVectorIndex } from "../src/functions/search.js"; -import { VectorIndex } from "../src/state/vector-index.js"; -import { KV } from "../src/state/schema.js"; -import type { CompressedObservation, Session } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("mem::search", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(async () => { - sdk = mockSdk(); - kv = mockKV(); - registerSearchFunction(sdk as never, kv as never); - - const session: Session = { - id: "ses_1", - project: "demo", - cwd: "/tmp/demo", - startedAt: "2026-01-01T00:00:00Z", - status: "completed", - observationCount: 2, - }; - await kv.set(KV.sessions, session.id, session); - - const obsA: CompressedObservation = { - id: "obs_a", - sessionId: "ses_1", - timestamp: "2026-01-01T00:00:00Z", - type: "decision", - title: "Auth middleware decision", - subtitle: "JWT strategy", - facts: ["Use rotating refresh tokens"], - narrative: "Implemented auth middleware with JWT refresh rotation.", - concepts: ["auth", "jwt"], - files: ["src/auth.ts"], - importance: 8, - }; - const obsB: CompressedObservation = { - id: "obs_b", - sessionId: "ses_1", - timestamp: "2026-01-02T00:00:00Z", - type: "file_edit", - title: "UI button styling", - facts: ["Updated primary button color"], - narrative: "Adjusted button styles in the settings page.", - concepts: ["ui", "css"], - files: ["src/ui/button.tsx"], - importance: 4, - }; - - await kv.set(KV.observations("ses_1"), obsA.id, obsA); - await kv.set(KV.observations("ses_1"), obsB.id, obsB); - - // Module-level SearchIndex singleton would leak across tests; reset. - getSearchIndex().clear(); - }); - - it("returns full format by default", async () => { - const result = (await sdk.trigger("mem::search", { - query: "auth middleware", - })) as { format: string; results: Array<{ observation: CompressedObservation }> }; - - expect(result.format).toBe("full"); - expect(result.results).toHaveLength(1); - expect(result.results[0]?.observation.id).toBe("obs_a"); - }); - - it("returns compact format when requested", async () => { - const result = (await sdk.trigger("mem::search", { - query: "auth", - format: "compact", - })) as { format: string; results: Array<{ obsId: string; title: string }> }; - - expect(result.format).toBe("compact"); - expect(result.results[0]?.obsId).toBe("obs_a"); - expect(result.results[0]?.title).toBe("Auth middleware decision"); - }); - - it("returns narrative text and respects token budget", async () => { - const result = (await sdk.trigger("mem::search", { - query: "auth ui", - format: "narrative", - token_budget: 20, - })) as { - format: string; - results: Array<{ obsId: string }>; - text: string; - tokens_used: number; - tokens_budget: number; - truncated: boolean; - }; - - expect(result.format).toBe("narrative"); - expect(result.tokens_budget).toBe(20); - expect(result.tokens_used).toBeLessThanOrEqual(20); - expect(typeof result.text).toBe("string"); - expect(result.results.length).toBeLessThanOrEqual(2); - expect(result.truncated).toBe(true); - }); - - it("rejects invalid format values", async () => { - await expect( - sdk.trigger("mem::search", { query: "auth", format: "verbose" }), - ).rejects.toThrow("format must be one of"); - }); - - it("surfaces saved memories from KV.memories (#265)", async () => { - // mem::remember persists to KV.memories under a synthetic sessionId - // ("memory") that has no corresponding KV.observations entry. mem::search - // must fall back to KV.memories or memory_recall returns empty. - await kv.set(KV.memories, "mem_x1", { - id: "mem_x1", - createdAt: "2026-02-01T00:00:00Z", - updatedAt: "2026-02-01T00:00:00Z", - type: "fact", - title: "Pineapple belongs on pizza", - content: "Pineapple belongs on pizza for testing fallback path.", - concepts: ["pineapple", "pizza"], - files: [], - sessionIds: [], - strength: 7, - version: 1, - isLatest: true, - }); - // Force the rebuild to pick up the new memory (mem::search only - // rebuilds on first call when idx.size === 0). - await rebuildIndex(kv as never); - - const result = (await sdk.trigger("mem::search", { - query: "pineapple pizza", - format: "compact", - })) as { results: Array<{ obsId: string; title: string }> }; - - const hit = result.results.find((r) => r.obsId === "mem_x1"); - expect(hit).toBeDefined(); - expect(hit?.title).toBe("Pineapple belongs on pizza"); - }); - - it("rebuildIndex populates the vector index", async () => { - const mockEmbedder = { - name: "test", - dimensions: 3, - embed: async (_text: string) => new Float32Array([0.1, 0.2, 0.3]), - embedBatch: async (_texts: string[]) => - _texts.map(() => new Float32Array([0.1, 0.2, 0.3])), - }; - setEmbeddingProvider(mockEmbedder); - setVectorIndex(new VectorIndex()); - - await rebuildIndex(kv as never); - - const vi = getVectorIndex(); - expect(vi).not.toBeNull(); - expect(vi!.size).toBeGreaterThan(0); - - // Cleanup - setVectorIndex(null); - setEmbeddingProvider(null); - }); -}); diff --git a/test/sentinels.test.ts b/test/sentinels.test.ts deleted file mode 100644 index 39c19a77..00000000 --- a/test/sentinels.test.ts +++ /dev/null @@ -1,627 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerSentinelsFunction } from "../src/functions/sentinels.js"; -import { registerActionsFunction } from "../src/functions/actions.js"; -import type { Action, ActionEdge, Sentinel } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("Sentinels Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerSentinelsFunction(sdk as never, kv as never); - registerActionsFunction(sdk as never, kv as never); - }); - - describe("mem::sentinel-create", () => { - it("creates a webhook sentinel with valid config", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "deploy-hook", - type: "webhook", - config: { path: "/hooks/deploy" }, - })) as { success: boolean; sentinel: Sentinel }; - - expect(result.success).toBe(true); - expect(result.sentinel.id).toMatch(/^snl_/); - expect(result.sentinel.name).toBe("deploy-hook"); - expect(result.sentinel.type).toBe("webhook"); - expect(result.sentinel.status).toBe("watching"); - expect(result.sentinel.config).toEqual({ path: "/hooks/deploy" }); - expect(result.sentinel.linkedActionIds).toEqual([]); - expect(result.sentinel.createdAt).toBeDefined(); - }); - - it("creates a timer sentinel with valid config", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "timeout-check", - type: "timer", - config: { durationMs: 5000 }, - })) as { success: boolean; sentinel: Sentinel }; - - expect(result.success).toBe(true); - expect(result.sentinel.type).toBe("timer"); - expect(result.sentinel.status).toBe("watching"); - }); - - it("creates a threshold sentinel with valid config", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "high-calls", - type: "threshold", - config: { metric: "api_calls", operator: "gt", value: 100 }, - })) as { success: boolean; sentinel: Sentinel }; - - expect(result.success).toBe(true); - expect(result.sentinel.type).toBe("threshold"); - }); - - it("creates a pattern sentinel with valid config", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "error-watcher", - type: "pattern", - config: { pattern: "error|fail" }, - })) as { success: boolean; sentinel: Sentinel }; - - expect(result.success).toBe(true); - expect(result.sentinel.type).toBe("pattern"); - }); - - it("creates an approval sentinel without config", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "needs-approval", - type: "approval", - })) as { success: boolean; sentinel: Sentinel }; - - expect(result.success).toBe(true); - expect(result.sentinel.type).toBe("approval"); - expect(result.sentinel.config).toEqual({}); - }); - - it("creates a custom sentinel without config", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "custom-gate", - type: "custom", - })) as { success: boolean; sentinel: Sentinel }; - - expect(result.success).toBe(true); - expect(result.sentinel.type).toBe("custom"); - }); - - it("returns error when name is missing", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - type: "webhook", - config: { path: "/hooks/deploy" }, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("name is required"); - }); - - it("returns error for invalid type", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "bad-type", - type: "invalid_type", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("type must be one of"); - }); - - it("returns error for timer config missing durationMs", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "bad-timer", - type: "timer", - config: {}, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("positive durationMs"); - }); - - it("returns error for timer config with negative durationMs", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "neg-timer", - type: "timer", - config: { durationMs: -100 }, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("positive durationMs"); - }); - - it("returns error for threshold config missing metric", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "bad-threshold", - type: "threshold", - config: { operator: "gt", value: 10 }, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("threshold config requires"); - }); - - it("returns error for threshold config with invalid operator", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "bad-op", - type: "threshold", - config: { metric: "calls", operator: "gte", value: 10 }, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("threshold config requires"); - }); - - it("returns error for threshold config missing value", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "no-val", - type: "threshold", - config: { metric: "calls", operator: "gt" }, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("threshold config requires"); - }); - - it("returns error for pattern config missing pattern", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "bad-pattern", - type: "pattern", - config: {}, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("pattern config requires"); - }); - - it("returns error for webhook config missing path", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "bad-webhook", - type: "webhook", - config: {}, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("webhook config requires"); - }); - - it("creates gated_by edges for linkedActionIds", async () => { - const action = (await sdk.trigger("mem::action-create", { - title: "Gated task", - })) as { success: boolean; action: Action }; - - const result = (await sdk.trigger("mem::sentinel-create", { - name: "gate-sentinel", - type: "approval", - linkedActionIds: [action.action.id], - })) as { success: boolean; sentinel: Sentinel }; - - expect(result.success).toBe(true); - expect(result.sentinel.linkedActionIds).toEqual([action.action.id]); - - const edges = await kv.list("mem:action-edges"); - const gatedEdges = edges.filter( - (e) => - e.type === "gated_by" && - e.sourceActionId === action.action.id && - e.targetActionId === result.sentinel.id, - ); - expect(gatedEdges.length).toBe(1); - }); - - it("returns error for non-existent linkedActionId", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "bad-link", - type: "approval", - linkedActionIds: ["nonexistent_action"], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("linked action not found"); - }); - - it("sets expiresAt when expiresInMs is provided", async () => { - const result = (await sdk.trigger("mem::sentinel-create", { - name: "expiring", - type: "custom", - expiresInMs: 60000, - })) as { success: boolean; sentinel: Sentinel }; - - expect(result.success).toBe(true); - expect(result.sentinel.expiresAt).toBeDefined(); - const created = new Date(result.sentinel.createdAt).getTime(); - const expires = new Date(result.sentinel.expiresAt!).getTime(); - expect(expires - created).toBeCloseTo(60000, -2); - }); - }); - - describe("mem::sentinel-trigger", () => { - it("triggers a watching sentinel", async () => { - const sentinel = (await sdk.trigger("mem::sentinel-create", { - name: "trigger-me", - type: "approval", - })) as { success: boolean; sentinel: Sentinel }; - - const result = (await sdk.trigger("mem::sentinel-trigger", { - sentinelId: sentinel.sentinel.id, - result: { approvedBy: "admin" }, - })) as { success: boolean; sentinel: Sentinel; unblockedCount: number }; - - expect(result.success).toBe(true); - expect(result.sentinel.status).toBe("triggered"); - expect(result.sentinel.triggeredAt).toBeDefined(); - expect(result.sentinel.result).toEqual({ approvedBy: "admin" }); - expect(result.unblockedCount).toBe(0); - }); - - it("returns error when triggering already-triggered sentinel", async () => { - const sentinel = (await sdk.trigger("mem::sentinel-create", { - name: "already-fired", - type: "custom", - })) as { success: boolean; sentinel: Sentinel }; - - await sdk.trigger("mem::sentinel-trigger", { - sentinelId: sentinel.sentinel.id, - }); - - const result = (await sdk.trigger("mem::sentinel-trigger", { - sentinelId: sentinel.sentinel.id, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("already triggered"); - }); - - it("returns error for non-existent sentinel", async () => { - const result = (await sdk.trigger("mem::sentinel-trigger", { - sentinelId: "nonexistent_sentinel", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("sentinel not found"); - }); - - it("returns error when sentinelId is missing", async () => { - const result = (await sdk.trigger("mem::sentinel-trigger", {})) as { - success: boolean; - error: string; - }; - - expect(result.success).toBe(false); - expect(result.error).toContain("sentinelId is required"); - }); - - it("unblocks gated actions when triggered", async () => { - const action = (await sdk.trigger("mem::action-create", { - title: "Blocked task", - })) as { success: boolean; action: Action }; - - const sentinel = (await sdk.trigger("mem::sentinel-create", { - name: "gate", - type: "approval", - linkedActionIds: [action.action.id], - })) as { success: boolean; sentinel: Sentinel }; - - await sdk.trigger("mem::action-update", { - actionId: action.action.id, - status: "blocked", - }); - - const result = (await sdk.trigger("mem::sentinel-trigger", { - sentinelId: sentinel.sentinel.id, - })) as { success: boolean; sentinel: Sentinel; unblockedCount: number }; - - expect(result.success).toBe(true); - expect(result.unblockedCount).toBe(1); - - const updated = (await sdk.trigger("mem::action-get", { - actionId: action.action.id, - })) as { success: boolean; action: Action }; - - expect(updated.action.status).toBe("pending"); - }); - }); - - describe("mem::sentinel-cancel", () => { - it("cancels a watching sentinel", async () => { - const sentinel = (await sdk.trigger("mem::sentinel-create", { - name: "cancel-me", - type: "custom", - })) as { success: boolean; sentinel: Sentinel }; - - const result = (await sdk.trigger("mem::sentinel-cancel", { - sentinelId: sentinel.sentinel.id, - })) as { success: boolean; sentinel: Sentinel }; - - expect(result.success).toBe(true); - expect(result.sentinel.status).toBe("cancelled"); - }); - - it("returns error when cancelling non-watching sentinel", async () => { - const sentinel = (await sdk.trigger("mem::sentinel-create", { - name: "already-triggered", - type: "custom", - })) as { success: boolean; sentinel: Sentinel }; - - await sdk.trigger("mem::sentinel-trigger", { - sentinelId: sentinel.sentinel.id, - }); - - const result = (await sdk.trigger("mem::sentinel-cancel", { - sentinelId: sentinel.sentinel.id, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("cannot cancel sentinel with status"); - }); - - it("returns error for non-existent sentinel", async () => { - const result = (await sdk.trigger("mem::sentinel-cancel", { - sentinelId: "nonexistent_sentinel", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("sentinel not found"); - }); - - it("returns error when sentinelId is missing", async () => { - const result = (await sdk.trigger("mem::sentinel-cancel", {})) as { - success: boolean; - error: string; - }; - - expect(result.success).toBe(false); - expect(result.error).toContain("sentinelId is required"); - }); - }); - - describe("mem::sentinel-list", () => { - beforeEach(async () => { - await sdk.trigger("mem::sentinel-create", { - name: "webhook-1", - type: "webhook", - config: { path: "/a" }, - }); - await sdk.trigger("mem::sentinel-create", { - name: "timer-1", - type: "timer", - config: { durationMs: 1000 }, - }); - await sdk.trigger("mem::sentinel-create", { - name: "approval-1", - type: "approval", - }); - }); - - it("returns all sentinels", async () => { - const result = (await sdk.trigger("mem::sentinel-list", {})) as { - success: boolean; - sentinels: Sentinel[]; - }; - - expect(result.success).toBe(true); - expect(result.sentinels.length).toBe(3); - }); - - it("filters by status", async () => { - const all = (await sdk.trigger("mem::sentinel-list", {})) as { - sentinels: Sentinel[]; - }; - await sdk.trigger("mem::sentinel-trigger", { - sentinelId: all.sentinels[0].id, - }); - - const result = (await sdk.trigger("mem::sentinel-list", { - status: "triggered", - })) as { success: boolean; sentinels: Sentinel[] }; - - expect(result.success).toBe(true); - expect(result.sentinels.length).toBe(1); - expect(result.sentinels[0].status).toBe("triggered"); - }); - - it("filters by type", async () => { - const result = (await sdk.trigger("mem::sentinel-list", { - type: "webhook", - })) as { success: boolean; sentinels: Sentinel[] }; - - expect(result.success).toBe(true); - expect(result.sentinels.length).toBe(1); - expect(result.sentinels[0].type).toBe("webhook"); - }); - - it("filters by both status and type", async () => { - const result = (await sdk.trigger("mem::sentinel-list", { - status: "watching", - type: "approval", - })) as { success: boolean; sentinels: Sentinel[] }; - - expect(result.success).toBe(true); - expect(result.sentinels.length).toBe(1); - expect(result.sentinels[0].type).toBe("approval"); - expect(result.sentinels[0].status).toBe("watching"); - }); - }); - - describe("mem::sentinel-expire", () => { - it("expires sentinels past their expiresAt", async () => { - await sdk.trigger("mem::sentinel-create", { - name: "will-expire", - type: "custom", - expiresInMs: 1, - }); - - await new Promise((r) => setTimeout(r, 10)); - - const result = (await sdk.trigger("mem::sentinel-expire", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(1); - - const list = (await sdk.trigger("mem::sentinel-list", { - status: "expired", - })) as { sentinels: Sentinel[] }; - expect(list.sentinels.length).toBe(1); - }); - - it("skips sentinels that have not expired", async () => { - await sdk.trigger("mem::sentinel-create", { - name: "not-expired", - type: "custom", - expiresInMs: 600000, - }); - - const result = (await sdk.trigger("mem::sentinel-expire", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(0); - }); - - it("skips sentinels without expiresAt", async () => { - await sdk.trigger("mem::sentinel-create", { - name: "no-expiry", - type: "custom", - }); - - const result = (await sdk.trigger("mem::sentinel-expire", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(0); - }); - - it("skips non-watching sentinels even if expired", async () => { - const sentinel = (await sdk.trigger("mem::sentinel-create", { - name: "already-cancelled", - type: "custom", - expiresInMs: 1, - })) as { success: boolean; sentinel: Sentinel }; - - await sdk.trigger("mem::sentinel-cancel", { - sentinelId: sentinel.sentinel.id, - }); - - await new Promise((r) => setTimeout(r, 10)); - - const result = (await sdk.trigger("mem::sentinel-expire", {})) as { - success: boolean; - expired: number; - }; - - expect(result.success).toBe(true); - expect(result.expired).toBe(0); - }); - }); - - describe("mem::sentinel-check", () => { - it("triggers threshold sentinel when condition is met", async () => { - await kv.set("mem:metrics", "api_calls", { - totalCalls: 150, - errorCount: 0, - avgDurationMs: 50, - }); - - await sdk.trigger("mem::sentinel-create", { - name: "high-traffic", - type: "threshold", - config: { metric: "api_calls", operator: "gt", value: 100 }, - }); - - const result = (await sdk.trigger("mem::sentinel-check", {})) as { - success: boolean; - triggered: string[]; - checkedCount: number; - }; - - expect(result.success).toBe(true); - expect(result.triggered.length).toBe(1); - expect(result.checkedCount).toBe(1); - }); - - it("does not trigger threshold sentinel when condition is not met", async () => { - await kv.set("mem:metrics", "api_calls", { - totalCalls: 50, - errorCount: 0, - avgDurationMs: 50, - }); - - await sdk.trigger("mem::sentinel-create", { - name: "low-traffic", - type: "threshold", - config: { metric: "api_calls", operator: "gt", value: 100 }, - }); - - const result = (await sdk.trigger("mem::sentinel-check", {})) as { - success: boolean; - triggered: string[]; - checkedCount: number; - }; - - expect(result.success).toBe(true); - expect(result.triggered.length).toBe(0); - }); - - it("returns empty triggered list when no active sentinels", async () => { - const result = (await sdk.trigger("mem::sentinel-check", {})) as { - success: boolean; - triggered: string[]; - checkedCount: number; - }; - - expect(result.success).toBe(true); - expect(result.triggered).toEqual([]); - expect(result.checkedCount).toBe(0); - }); - }); -}); diff --git a/test/session-end-triggers-graph.test.ts b/test/session-end-triggers-graph.test.ts deleted file mode 100644 index 8fc2749a..00000000 --- a/test/session-end-triggers-graph.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; - -// #666: api::session::end must publish the session-stopped lifecycle so -// summarize + slot-reflect + graph extraction actually fire. Before this -// fix the `event::session::stopped` handler in events.ts was a dead -// subscriber — no code published `agentmemory.session.stopped`, so graph -// nodes / lessons / crystals never materialized despite the handler -// existing. Fire-and-forget trigger keeps the HTTP response fast (kv.update -// runs synchronously, downstream pipeline fan-outs without blocking). -describe("api::session::end → event::session::stopped (#666)", () => { - const api = readFileSync("src/triggers/api.ts", "utf-8"); - - it("api::session::end calls sdk.trigger('event::session::stopped') after kv.update", () => { - expect(api).toMatch( - /api::session::end[\s\S]*?kv\.update\(KV\.sessions[\s\S]*?trigger\(\{\s*function_id:\s*"event::session::stopped"/, - ); - }); - - it("session::stopped trigger payload includes sessionId", () => { - expect(api).toMatch( - /trigger\(\{\s*function_id:\s*"event::session::stopped",\s*payload:\s*\{\s*sessionId\s*\}/, - ); - }); -}); - -// #666: viewer's "Build Graph" button used to POST /agentmemory/graph/build -// which returned 404 because the endpoint was never registered. Backfill -// the knowledge graph from existing compressed observations across every -// session in batches. -describe("api::graph-build endpoint (#666)", () => { - const api = readFileSync("src/triggers/api.ts", "utf-8"); - - it("registers api::graph-build function", () => { - expect(api).toMatch(/registerFunction\("api::graph-build"/); - }); - - it("registers HTTP trigger at /agentmemory/graph/build", () => { - expect(api).toMatch( - /api_path:\s*"\/agentmemory\/graph\/build",\s*http_method:\s*"POST"/, - ); - }); - - it("iterates sessions and calls mem::graph-extract", () => { - expect(api).toMatch(/kv\.list\(KV\.sessions\)/); - expect(api).toMatch(/kv\.list\(KV\.observations\(sid\)\)/); - expect(api).toMatch( - /sdk\.trigger\(\{\s*function_id:\s*"mem::graph-extract"/, - ); - }); - - it("filters observations that have a title (compressed only)", () => { - expect(api).toMatch(/typeof o\.title === "string" && o\.title\.length > 0/); - }); - - it("respects batchSize override with a 100-item upper bound", () => { - expect(api).toMatch(/Math\.min\(100,\s*Number\(.*batchSize/); - }); - - it("response shape matches what the viewer expects (success + nodes)", () => { - expect(api).toMatch(/success:\s*true,\s*sessions:[\s\S]*?nodes:\s*totalNodes/); - }); -}); - -// #666: `agentmemory status` showed Memories/Observations as 0 because it -// fetched /agentmemory/export which times out on iii-engine's file-based -// KV under concurrent kv.list() pressure. Switch to /memories for the -// memory count and derive observation count from sessions[].observationCount. -describe("agentmemory status no longer depends on /export (#666)", () => { - const cli = readFileSync("src/cli.ts", "utf-8"); - - it("status uses count-only memories endpoint instead of export", () => { - expect(cli).toMatch(/apiFetch\(base,\s*"memories\?count=true"\)/); - expect(cli).not.toMatch(/apiFetch\(base,\s*"export"\)/); - }); - - it("status derives obsCount from sessions[].observationCount", () => { - expect(cli).toMatch( - /sessionList\.reduce\([\s\S]*?observationCount/, - ); - }); - - it("status reads memCount from memoriesRes.latestCount (count endpoint)", () => { - expect(cli).toMatch(/memoriesRes\?\.latestCount\s*\?\?\s*memoriesRes\?\.total/); - }); -}); diff --git a/test/session-highlights-api.test.ts b/test/session-highlights-api.test.ts deleted file mode 100644 index bb11fabc..00000000 --- a/test/session-highlights-api.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { readFileSync } from "node:fs"; -import { join } from "node:path"; -import { describe, expect, it } from "vitest"; - -const ROOT = join(import.meta.dirname, ".."); - -function readText(relativePath: string): string { - return readFileSync(join(ROOT, relativePath), "utf-8"); -} - -describe("session highlights REST API wiring", () => { - it("registers a REST-only session highlights endpoint", () => { - const api = readText("src/triggers/api.ts"); - const mcpRegistry = readText("src/mcp/tools-registry.ts"); - const mcpServer = readText("src/mcp/server.ts"); - - expect(api).toContain('"api::session::highlights"'); - expect(api).toContain('api_path: "/agentmemory/session/highlights"'); - expect(api).toContain('function_id: "mem::session-highlights"'); - expect(api).toMatch(/maxItems must be a positive integer no greater than 200/); - - expect(mcpRegistry).not.toContain("memory_session_highlights"); - expect(mcpServer).not.toContain('case "memory_session_highlights"'); - }); - - it("updates documented REST endpoint counts", () => { - const api = readText("src/triggers/api.ts"); - const endpointCount = Array.from(api.matchAll(/api_path:\s*["`]/g)).length; - const readme = readText("README.md"); - const agents = readText("AGENTS.md"); - const index = readText("src/index.ts"); - - expect(endpointCount).toBe(142); - expect(readme).toContain("142 endpoints on port"); - expect(agents).toContain("142 REST endpoints"); - expect(index).toContain("REST API: 142 endpoints"); - }); -}); diff --git a/test/session-highlights.test.ts b/test/session-highlights.test.ts deleted file mode 100644 index 7b6aa059..00000000 --- a/test/session-highlights.test.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { describe, expect, it, beforeEach } from "vitest"; -import { registerSessionHighlightsFunction } from "../src/functions/session-highlights.js"; -import type { - CompressedObservation, - RawObservation, - Session, - SessionHighlightsView, -} from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - functions, - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - trigger: async ( - idOrInput: string | { function_id: string; payload: unknown }, - data?: unknown, - ) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeObs( - id: string, - timestamp: string, - patch: Partial, -): CompressedObservation { - return { - id, - sessionId: "ses_highlights", - timestamp, - type: "other", - title: id, - facts: [], - narrative: "", - concepts: [], - files: [], - importance: 5, - ...patch, - }; -} - -function makeRawObs( - id: string, - timestamp: string, - patch: Partial, -): RawObservation { - return { - id, - sessionId: "ses_highlights", - timestamp, - hookType: "post_tool_use", - toolName: "Bash", - raw: {}, - ...patch, - }; -} - -describe("mem::session-highlights", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(async () => { - sdk = mockSdk(); - kv = mockKV(); - registerSessionHighlightsFunction(sdk as never, kv as never); - - const session: Session = { - id: "ses_highlights", - project: "agentmemory-lab", - cwd: "/repo/agentmemory-lab", - startedAt: "2026-06-11T01:00:00.000Z", - endedAt: "2026-06-11T01:30:00.000Z", - status: "completed", - observationCount: 7, - firstPrompt: - "Build a zero-LLM session highlights backend and do not modify frontend design.", - }; - await kv.set("mem:sessions", session.id, session); - - const observations = [ - makeObs("obs_user", "2026-06-11T01:00:01.000Z", { - type: "conversation", - title: "prompt_submit", - narrative: - "Build a zero-LLM session highlights backend and exclude LLM modules.", - importance: 8, - }), - makeObs("obs_agent", "2026-06-11T01:03:00.000Z", { - type: "conversation", - title: "assistant response", - narrative: - "Agent output: Proposed a REST-only highlights API and explained the implementation plan.", - importance: 7, - }), - makeObs("obs_mcp", "2026-06-11T01:05:00.000Z", { - type: "other", - title: "memory_smart_search", - narrative: "Called MCP memory_smart_search for prior session context.", - importance: 6, - }), - makeObs("obs_cmd", "2026-06-11T01:10:00.000Z", { - type: "command_run", - title: "Bash", - subtitle: '{"command":"npm test -- --run test/session-highlights.test.ts"}', - narrative: - "Ran command npm test -- --run test/session-highlights.test.ts. Tests: 12 passed, 0 failed.", - importance: 8, - }), - makeObs("obs_fail", "2026-06-11T01:15:00.000Z", { - type: "error", - title: "Bash error", - narrative: "Command failed with exit 1 because sessionId was missing.", - importance: 9, - }), - makeObs("obs_file", "2026-06-11T01:20:00.000Z", { - type: "file_edit", - title: "Edit src/functions/session-highlights.ts", - narrative: - "Generated important backend file and updated the API endpoint registration.", - files: ["src/functions/session-highlights.ts", "src/triggers/api.ts"], - importance: 8, - }), - makeObs("obs_skill", "2026-06-11T01:25:00.000Z", { - type: "task", - title: "Skill follow-up", - narrative: - "TODO: promote this heuristic into a Skill if repeated across sessions.", - importance: 7, - }), - makeRawObs("raw_prompt", "2026-06-11T01:26:00.000Z", { - hookType: "prompt_submit", - userPrompt: "把会话重点限制为用户和 Agent 在对话框里真实说过的话。", - }), - makeRawObs("raw_agent", "2026-06-11T01:27:00.000Z", { - hookType: "stop", - assistantResponse: - "我会把 highlights 改成 conversation-only,并保留现有 REST 响应结构。", - }), - makeRawObs("raw_tool_pollution", "2026-06-11T01:28:00.000Z", { - hookType: "post_tool_use", - toolName: "exec_command", - toolInput: { - command: "npm test -- --run test/session-highlights.test.ts", - }, - toolOutput: { - stdout: "failed error apply_patch function_id toolInput toolOutput", - }, - raw: { - function_id: "mem::session-highlights", - payload: { command: "apply_patch" }, - }, - }), - ]; - for (const obs of observations) { - await kv.set("mem:obs:ses_highlights", obs.id, obs); - } - }); - - it("builds a conversation-only highlights view without an LLM provider", async () => { - const result = (await sdk.trigger("mem::session-highlights", { - sessionId: "ses_highlights", - maxItems: 20, - })) as SessionHighlightsView; - - expect(result.success).toBe(true); - expect(result.session.id).toBe("ses_highlights"); - expect(result.coverage.llmUsed).toBe(false); - expect(result.coverage.hasAssistantOutputs).toBe(true); - expect(result.stats.observationCount).toBe(10); - - const categories = result.highlights.map((h) => h.category); - expect(new Set(categories)).toEqual(new Set(["goal", "agent_output"])); - expect(categories).not.toContain("mcp"); - expect(categories).not.toContain("command"); - expect(categories).not.toContain("validation"); - expect(categories).not.toContain("failure"); - expect(categories).not.toContain("file"); - expect(categories).not.toContain("skill"); - expect(categories).not.toContain("todo"); - expect(result.stats.fileCount).toBe(0); - expect(result.stats.toolCount).toBe(0); - - const summaries = result.highlights.map((h) => h.summary).join("\n"); - expect(summaries).toContain("Build a zero-LLM session highlights backend"); - expect(summaries).toContain("把会话重点限制为用户和 Agent"); - expect(summaries).toContain("conversation-only"); - expect(summaries).not.toContain("npm test"); - expect(summaries).not.toContain("apply_patch"); - expect(summaries).not.toContain("function_id"); - expect(summaries).not.toContain("toolInput"); - }); - - it("returns a stable empty view when a session has no observations", async () => { - await kv.set("mem:sessions", "empty", { - id: "empty", - project: "agentmemory-lab", - cwd: "/repo/agentmemory-lab", - startedAt: "2026-06-11T02:00:00.000Z", - status: "active", - observationCount: 0, - } satisfies Session); - - const result = (await sdk.trigger("mem::session-highlights", { - sessionId: "empty", - })) as SessionHighlightsView; - - expect(result.success).toBe(true); - expect(result.highlights).toEqual([]); - expect(result.coverage.hasAssistantOutputs).toBe(false); - expect(result.coverage.missingAssistantOutputs).toBe(true); - }); - - it("rejects missing sessionId and reports missing sessions", async () => { - const invalid = await sdk.trigger("mem::session-highlights", {}); - expect(invalid).toMatchObject({ success: false, error: "sessionId is required" }); - - const missing = await sdk.trigger("mem::session-highlights", { - sessionId: "missing", - }); - expect(missing).toMatchObject({ success: false, error: "session_not_found" }); - }); - - it("ignores source reads, command JSON, tool IO, and operational error words", async () => { - await kv.set("mem:obs:ses_highlights", "obs_source_read", makeObs( - "obs_source_read", - "2026-06-11T01:28:00.000Z", - { - type: "file_read", - title: "Read src/functions/session-highlights.ts", - narrative: - "function isValidationText(text) { return text.includes('failed') || text.includes('error') || text.includes('test'); }", - files: ["src/functions/session-highlights.ts"], - importance: 6, - }, - )); - - await kv.set("mem:obs:ses_highlights", "obs_readonly_shell", makeObs( - "obs_readonly_shell", - "2026-06-11T01:29:00.000Z", - { - type: "command_run", - title: "Bash", - subtitle: '{"command":"nl -ba src/functions/session-highlights.ts | sed -n \'1,80p\'"}', - narrative: - "1 function isValidationText(text) { return text.includes('failed') || text.includes('error') || text.includes('test'); }", - importance: 5, - }, - )); - - const result = (await sdk.trigger("mem::session-highlights", { - sessionId: "ses_highlights", - maxItems: 50, - })) as SessionHighlightsView; - - expect(result.highlights.map((h) => h.sourceObservationId)).not.toContain("obs_source_read"); - expect(result.highlights.map((h) => h.sourceObservationId)).not.toContain("obs_readonly_shell"); - expect(result.highlights.map((h) => h.sourceObservationId)).not.toContain("raw_tool_pollution"); - expect(new Set(result.highlights.map((h) => h.category))).toEqual(new Set(["goal", "agent_output"])); - }); - - it("filters JSON-shaped and tool-trace conversation text before adding highlights", async () => { - await kv.set("mem:obs:ses_highlights", "obs_json_conversation", makeObs( - "obs_json_conversation", - "2026-06-11T01:31:00.000Z", - { - type: "conversation", - title: "conversation", - narrative: '{"command":"npm test","toolInput":{"file":"src/index.ts"}}', - }, - )); - await kv.set("mem:obs:ses_highlights", "raw_tool_trace_prompt", makeRawObs( - "raw_tool_trace_prompt", - "2026-06-11T01:32:00.000Z", - { - hookType: "prompt_submit", - userPrompt: "exec_command apply_patch toolOutput function_id", - }, - )); - - const result = (await sdk.trigger("mem::session-highlights", { - sessionId: "ses_highlights", - maxItems: 50, - })) as SessionHighlightsView; - - expect(result.highlights.map((h) => h.sourceObservationId)).not.toContain("obs_json_conversation"); - expect(result.highlights.map((h) => h.sourceObservationId)).not.toContain("raw_tool_trace_prompt"); - expect(result.highlights.map((h) => h.summary).join("\n")).not.toContain('"command"'); - }); -}); diff --git a/test/sessions-delete-api.test.ts b/test/sessions-delete-api.test.ts deleted file mode 100644 index 4a113ab9..00000000 --- a/test/sessions-delete-api.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; - -// DELETE /agentmemory/sessions?project=... is what `demo` prints as its -// cleanup command; before this route existed it 405'd and left seeded -// sessions in the local store. Guard the wiring + the by-project filter. -describe("sessions delete-by-project REST API", () => { - const api = readFileSync("src/triggers/api.ts", "utf-8"); - - it("registers a DELETE route for /agentmemory/sessions", () => { - expect(api).toContain('"api::sessions::delete"'); - expect(api).toMatch( - /function_id:\s*"api::sessions::delete",\s*config:\s*\{\s*api_path:\s*"\/agentmemory\/sessions",\s*http_method:\s*"DELETE"/, - ); - }); - - it("requires project, filters by it, and deletes matching sessions", () => { - expect(api).toContain("project is required"); - expect(api).toMatch(/s\.project === project/); - expect(api).toMatch(/kv\.delete\(KV\.sessions, s\.id\)/); - expect(api).toMatch(/deleted:\s*targets\.length/); - }); -}); diff --git a/test/signals.test.ts b/test/signals.test.ts deleted file mode 100644 index 3554a7f2..00000000 --- a/test/signals.test.ts +++ /dev/null @@ -1,411 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerSignalsFunction } from "../src/functions/signals.js"; -import type { Signal } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("Signals Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerSignalsFunction(sdk as never, kv as never); - }); - - describe("mem::signal-send", () => { - it("sends a signal with valid data", async () => { - const result = (await sdk.trigger("mem::signal-send", { - from: "agent-a", - to: "agent-b", - content: "Hello there", - type: "info", - })) as { success: boolean; signal: Signal }; - - expect(result.success).toBe(true); - expect(result.signal.id).toMatch(/^sig_/); - expect(result.signal.from).toBe("agent-a"); - expect(result.signal.to).toBe("agent-b"); - expect(result.signal.content).toBe("Hello there"); - expect(result.signal.type).toBe("info"); - expect(result.signal.threadId).toMatch(/^thr_/); - expect(result.signal.createdAt).toBeDefined(); - }); - - it("returns error when from is missing", async () => { - const result = (await sdk.trigger("mem::signal-send", { - from: "", - content: "Hello", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("from and non-empty content are required"); - }); - - it("returns error when content is whitespace only", async () => { - const result = (await sdk.trigger("mem::signal-send", { - from: "agent-a", - content: " ", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("from and non-empty content are required"); - }); - - it("returns error when content is empty string", async () => { - const result = (await sdk.trigger("mem::signal-send", { - from: "agent-a", - content: "", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("from and non-empty content are required"); - }); - - it("auto-threads replies from parent signal", async () => { - const parent = (await sdk.trigger("mem::signal-send", { - from: "agent-a", - to: "agent-b", - content: "Initial message", - })) as { success: boolean; signal: Signal }; - - const reply = (await sdk.trigger("mem::signal-send", { - from: "agent-b", - to: "agent-a", - content: "Reply message", - replyTo: parent.signal.id, - })) as { success: boolean; signal: Signal }; - - expect(reply.success).toBe(true); - expect(reply.signal.threadId).toBe(parent.signal.threadId); - expect(reply.signal.replyTo).toBe(parent.signal.id); - }); - - it("sets expiresAt when expiresInMs is provided", async () => { - const result = (await sdk.trigger("mem::signal-send", { - from: "agent-a", - content: "Temporary message", - expiresInMs: 60000, - })) as { success: boolean; signal: Signal }; - - expect(result.success).toBe(true); - expect(result.signal.expiresAt).toBeDefined(); - const expiresAt = new Date(result.signal.expiresAt!).getTime(); - const createdAt = new Date(result.signal.createdAt).getTime(); - expect(expiresAt - createdAt).toBeCloseTo(60000, -2); - }); - - it("defaults type to info when not specified", async () => { - const result = (await sdk.trigger("mem::signal-send", { - from: "agent-a", - content: "No type specified", - })) as { success: boolean; signal: Signal }; - - expect(result.success).toBe(true); - expect(result.signal.type).toBe("info"); - }); - - it("trims content whitespace", async () => { - const result = (await sdk.trigger("mem::signal-send", { - from: "agent-a", - content: " padded content ", - })) as { success: boolean; signal: Signal }; - - expect(result.success).toBe(true); - expect(result.signal.content).toBe("padded content"); - }); - }); - - describe("mem::signal-read", () => { - beforeEach(async () => { - await sdk.trigger("mem::signal-send", { - from: "agent-a", - to: "agent-b", - content: "Message 1", - type: "info", - }); - await sdk.trigger("mem::signal-send", { - from: "agent-c", - to: "agent-b", - content: "Message 2", - type: "request", - }); - await sdk.trigger("mem::signal-send", { - from: "agent-b", - to: "agent-a", - content: "Message 3", - type: "response", - }); - }); - - it("reads signals for an agent", async () => { - const result = (await sdk.trigger("mem::signal-read", { - agentId: "agent-b", - })) as { success: boolean; signals: Signal[] }; - - expect(result.success).toBe(true); - expect(result.signals.length).toBeGreaterThanOrEqual(2); - }); - - it("marks signals as read", async () => { - await sdk.trigger("mem::signal-read", { - agentId: "agent-b", - }); - - const signals = await kv.list("mem:signals"); - const toAgentB = signals.filter((s) => s.to === "agent-b"); - expect(toAgentB.every((s) => s.readAt !== undefined)).toBe(true); - }); - - it("filters by unreadOnly", async () => { - await sdk.trigger("mem::signal-read", { - agentId: "agent-b", - }); - - const result = (await sdk.trigger("mem::signal-read", { - agentId: "agent-b", - unreadOnly: true, - })) as { success: boolean; signals: Signal[] }; - - expect(result.success).toBe(true); - expect(result.signals.length).toBe(0); - }); - - it("filters by threadId", async () => { - const sent = (await sdk.trigger("mem::signal-send", { - from: "agent-x", - to: "agent-b", - content: "Thread-specific message", - threadId: "thr_specific", - })) as { success: boolean; signal: Signal }; - - const result = (await sdk.trigger("mem::signal-read", { - agentId: "agent-b", - threadId: "thr_specific", - })) as { success: boolean; signals: Signal[] }; - - expect(result.success).toBe(true); - expect(result.signals.length).toBe(1); - expect(result.signals[0].threadId).toBe("thr_specific"); - }); - - it("filters by type", async () => { - const result = (await sdk.trigger("mem::signal-read", { - agentId: "agent-b", - type: "request", - })) as { success: boolean; signals: Signal[] }; - - expect(result.success).toBe(true); - expect(result.signals.every((s) => s.type === "request")).toBe(true); - }); - - it("returns error when agentId is missing", async () => { - const result = (await sdk.trigger("mem::signal-read", { - agentId: "", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("agentId is required"); - }); - }); - - describe("mem::signal-threads", () => { - it("groups signals by thread", async () => { - const first = (await sdk.trigger("mem::signal-send", { - from: "agent-a", - to: "agent-b", - content: "Thread 1 message 1", - })) as { success: boolean; signal: Signal }; - - await sdk.trigger("mem::signal-send", { - from: "agent-b", - to: "agent-a", - content: "Thread 1 message 2", - replyTo: first.signal.id, - }); - - await sdk.trigger("mem::signal-send", { - from: "agent-a", - to: "agent-b", - content: "Different thread", - }); - - const result = (await sdk.trigger("mem::signal-threads", { - agentId: "agent-a", - })) as { - success: boolean; - threads: Array<{ - threadId: string; - messages: number; - participants: string[]; - }>; - }; - - expect(result.success).toBe(true); - expect(result.threads.length).toBe(2); - - const firstThread = result.threads.find( - (t) => t.threadId === first.signal.threadId, - ); - expect(firstThread).toBeDefined(); - expect(firstThread!.messages).toBe(2); - expect(firstThread!.participants).toContain("agent-a"); - expect(firstThread!.participants).toContain("agent-b"); - }); - - it("returns error when agentId is missing", async () => { - const result = (await sdk.trigger("mem::signal-threads", { - agentId: "", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("agentId is required"); - }); - }); - - describe("mem::signal-cleanup", () => { - it("removes expired signals", async () => { - const now = Date.now(); - const expiredSignal: Signal = { - id: "sig_expired", - from: "agent-a", - to: "agent-b", - content: "Expired", - type: "info", - threadId: "thr_1", - createdAt: new Date(now - 120000).toISOString(), - expiresAt: new Date(now - 60000).toISOString(), - }; - await kv.set("mem:signals", expiredSignal.id, expiredSignal); - - const validSignal: Signal = { - id: "sig_valid", - from: "agent-a", - to: "agent-b", - content: "Still valid", - type: "info", - threadId: "thr_2", - createdAt: new Date(now).toISOString(), - expiresAt: new Date(now + 60000).toISOString(), - }; - await kv.set("mem:signals", validSignal.id, validSignal); - - const result = (await sdk.trigger("mem::signal-cleanup", {})) as { - success: boolean; - removed: number; - }; - - expect(result.success).toBe(true); - expect(result.removed).toBe(1); - - const remaining = await kv.list("mem:signals"); - expect(remaining.length).toBe(1); - expect(remaining[0].id).toBe("sig_valid"); - }); - - it("keeps signals without expiration", async () => { - const noExpiry: Signal = { - id: "sig_noexpiry", - from: "agent-a", - content: "No expiration", - type: "info", - threadId: "thr_3", - createdAt: new Date().toISOString(), - }; - await kv.set("mem:signals", noExpiry.id, noExpiry); - - const result = (await sdk.trigger("mem::signal-cleanup", {})) as { - success: boolean; - removed: number; - }; - - expect(result.success).toBe(true); - expect(result.removed).toBe(0); - - const remaining = await kv.list("mem:signals"); - expect(remaining.length).toBe(1); - }); - - it("removes multiple expired signals at once", async () => { - const now = Date.now(); - - for (let i = 0; i < 5; i++) { - const sig: Signal = { - id: `sig_exp_${i}`, - from: "agent-a", - content: `Expired ${i}`, - type: "info", - threadId: `thr_${i}`, - createdAt: new Date(now - 200000).toISOString(), - expiresAt: new Date(now - 100000).toISOString(), - }; - await kv.set("mem:signals", sig.id, sig); - } - - const keepSig: Signal = { - id: "sig_keep", - from: "agent-b", - content: "Keep me", - type: "alert", - threadId: "thr_keep", - createdAt: new Date(now).toISOString(), - }; - await kv.set("mem:signals", keepSig.id, keepSig); - - const result = (await sdk.trigger("mem::signal-cleanup", {})) as { - success: boolean; - removed: number; - }; - - expect(result.success).toBe(true); - expect(result.removed).toBe(5); - - const remaining = await kv.list("mem:signals"); - expect(remaining.length).toBe(1); - expect(remaining[0].id).toBe("sig_keep"); - }); - }); -}); diff --git a/test/sketches.test.ts b/test/sketches.test.ts deleted file mode 100644 index ce72b628..00000000 --- a/test/sketches.test.ts +++ /dev/null @@ -1,550 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerSketchesFunction } from "../src/functions/sketches.js"; -import type { Action, ActionEdge, Sketch } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("Sketches Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - registerSketchesFunction(sdk as never, kv as never); - }); - - describe("mem::sketch-create", () => { - it("creates a sketch with valid title", async () => { - const result = (await sdk.trigger("mem::sketch-create", { - title: "Refactor auth module", - })) as { success: boolean; sketch: Sketch }; - - expect(result.success).toBe(true); - expect(result.sketch.id).toMatch(/^sk_/); - expect(result.sketch.title).toBe("Refactor auth module"); - expect(result.sketch.status).toBe("active"); - expect(result.sketch.actionIds).toEqual([]); - expect(result.sketch.createdAt).toBeDefined(); - expect(result.sketch.expiresAt).toBeDefined(); - }); - - it("returns error when title is empty", async () => { - const result = (await sdk.trigger("mem::sketch-create", { - title: "", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("title is required"); - }); - - it("returns error when title is missing", async () => { - const result = (await sdk.trigger("mem::sketch-create", {})) as { - success: boolean; - error: string; - }; - - expect(result.success).toBe(false); - expect(result.error).toContain("title is required"); - }); - - it("creates a sketch with custom TTL", async () => { - const result = (await sdk.trigger("mem::sketch-create", { - title: "Short-lived sketch", - expiresInMs: 60000, - })) as { success: boolean; sketch: Sketch }; - - expect(result.success).toBe(true); - const created = new Date(result.sketch.createdAt).getTime(); - const expires = new Date(result.sketch.expiresAt).getTime(); - expect(expires - created).toBe(60000); - }); - - it("defaults TTL to one hour", async () => { - const result = (await sdk.trigger("mem::sketch-create", { - title: "Default TTL", - })) as { success: boolean; sketch: Sketch }; - - expect(result.success).toBe(true); - const created = new Date(result.sketch.createdAt).getTime(); - const expires = new Date(result.sketch.expiresAt).getTime(); - expect(expires - created).toBe(3600000); - }); - - it("stores project on sketch", async () => { - const result = (await sdk.trigger("mem::sketch-create", { - title: "Project sketch", - project: "webapp", - })) as { success: boolean; sketch: Sketch }; - - expect(result.success).toBe(true); - expect(result.sketch.project).toBe("webapp"); - }); - }); - - describe("mem::sketch-add", () => { - let sketchId: string; - - beforeEach(async () => { - const result = (await sdk.trigger("mem::sketch-create", { - title: "Test sketch", - project: "myproject", - })) as { success: boolean; sketch: Sketch }; - sketchId = result.sketch.id; - }); - - it("adds an action to the sketch", async () => { - const result = (await sdk.trigger("mem::sketch-add", { - sketchId, - title: "Implement login", - description: "Add SSO support", - priority: 8, - })) as { success: boolean; action: Action; edges: ActionEdge[] }; - - expect(result.success).toBe(true); - expect(result.action.id).toMatch(/^act_/); - expect(result.action.title).toBe("Implement login"); - expect(result.action.description).toBe("Add SSO support"); - expect(result.action.priority).toBe(8); - expect(result.action.sketchId).toBe(sketchId); - expect(result.action.project).toBe("myproject"); - expect(result.action.createdBy).toBe("sketch"); - expect(result.edges).toEqual([]); - }); - - it("returns error for non-existent sketch", async () => { - const result = (await sdk.trigger("mem::sketch-add", { - sketchId: "nonexistent", - title: "Some action", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("sketch not found"); - }); - - it("returns error for non-active sketch", async () => { - await sdk.trigger("mem::sketch-promote", { sketchId }); - - const result = (await sdk.trigger("mem::sketch-add", { - sketchId, - title: "Late action", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("sketch is not active"); - }); - - it("adds action with dependsOn within sketch", async () => { - const first = (await sdk.trigger("mem::sketch-add", { - sketchId, - title: "First step", - })) as { success: boolean; action: Action }; - - const second = (await sdk.trigger("mem::sketch-add", { - sketchId, - title: "Second step", - dependsOn: [first.action.id], - })) as { success: boolean; action: Action; edges: ActionEdge[] }; - - expect(second.success).toBe(true); - expect(second.edges.length).toBe(1); - expect(second.edges[0].type).toBe("requires"); - expect(second.edges[0].sourceActionId).toBe(second.action.id); - expect(second.edges[0].targetActionId).toBe(first.action.id); - }); - - it("returns error for dependsOn referencing action outside sketch", async () => { - const result = (await sdk.trigger("mem::sketch-add", { - sketchId, - title: "Depends on external", - dependsOn: ["act_outside"], - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("not found in this sketch"); - }); - - it("returns error when sketchId is missing", async () => { - const result = (await sdk.trigger("mem::sketch-add", { - title: "No sketch", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("sketchId is required"); - }); - - it("returns error when title is missing", async () => { - const result = (await sdk.trigger("mem::sketch-add", { - sketchId, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("title is required"); - }); - }); - - describe("mem::sketch-promote", () => { - it("promotes sketch and removes sketchId from actions", async () => { - const sketch = (await sdk.trigger("mem::sketch-create", { - title: "Promotable sketch", - project: "alpha", - })) as { success: boolean; sketch: Sketch }; - - const a1 = (await sdk.trigger("mem::sketch-add", { - sketchId: sketch.sketch.id, - title: "Action 1", - })) as { success: boolean; action: Action }; - - const a2 = (await sdk.trigger("mem::sketch-add", { - sketchId: sketch.sketch.id, - title: "Action 2", - })) as { success: boolean; action: Action }; - - const result = (await sdk.trigger("mem::sketch-promote", { - sketchId: sketch.sketch.id, - })) as { success: boolean; promotedIds: string[] }; - - expect(result.success).toBe(true); - expect(result.promotedIds).toContain(a1.action.id); - expect(result.promotedIds).toContain(a2.action.id); - expect(result.promotedIds.length).toBe(2); - - const stored = await kv.get("mem:actions", a1.action.id); - expect(stored!.sketchId).toBeUndefined(); - expect(stored!.project).toBe("alpha"); - }); - - it("promotes sketch with project override", async () => { - const sketch = (await sdk.trigger("mem::sketch-create", { - title: "Override project", - project: "original", - })) as { success: boolean; sketch: Sketch }; - - const a = (await sdk.trigger("mem::sketch-add", { - sketchId: sketch.sketch.id, - title: "Task", - })) as { success: boolean; action: Action }; - - const result = (await sdk.trigger("mem::sketch-promote", { - sketchId: sketch.sketch.id, - project: "newproject", - })) as { success: boolean; promotedIds: string[] }; - - expect(result.success).toBe(true); - - const stored = await kv.get("mem:actions", a.action.id); - expect(stored!.project).toBe("newproject"); - expect(stored!.sketchId).toBeUndefined(); - }); - - it("returns error for non-active sketch", async () => { - const sketch = (await sdk.trigger("mem::sketch-create", { - title: "To be discarded", - })) as { success: boolean; sketch: Sketch }; - - await sdk.trigger("mem::sketch-discard", { - sketchId: sketch.sketch.id, - }); - - const result = (await sdk.trigger("mem::sketch-promote", { - sketchId: sketch.sketch.id, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("sketch is not active"); - }); - - it("returns error for non-existent sketch", async () => { - const result = (await sdk.trigger("mem::sketch-promote", { - sketchId: "nonexistent", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("sketch not found"); - }); - - it("sets sketch status to promoted", async () => { - const sketch = (await sdk.trigger("mem::sketch-create", { - title: "Promote me", - })) as { success: boolean; sketch: Sketch }; - - await sdk.trigger("mem::sketch-promote", { - sketchId: sketch.sketch.id, - }); - - const stored = await kv.get("mem:sketches", sketch.sketch.id); - expect(stored!.status).toBe("promoted"); - expect(stored!.promotedAt).toBeDefined(); - }); - }); - - describe("mem::sketch-discard", () => { - it("discards sketch and deletes actions and edges", async () => { - const sketch = (await sdk.trigger("mem::sketch-create", { - title: "Discard me", - })) as { success: boolean; sketch: Sketch }; - - const a1 = (await sdk.trigger("mem::sketch-add", { - sketchId: sketch.sketch.id, - title: "Action 1", - })) as { success: boolean; action: Action }; - - const a2 = (await sdk.trigger("mem::sketch-add", { - sketchId: sketch.sketch.id, - title: "Action 2", - dependsOn: [a1.action.id], - })) as { success: boolean; action: Action; edges: ActionEdge[] }; - - const result = (await sdk.trigger("mem::sketch-discard", { - sketchId: sketch.sketch.id, - })) as { success: boolean; discardedCount: number }; - - expect(result.success).toBe(true); - expect(result.discardedCount).toBe(2); - - const storedA1 = await kv.get("mem:actions", a1.action.id); - expect(storedA1).toBeNull(); - - const storedA2 = await kv.get("mem:actions", a2.action.id); - expect(storedA2).toBeNull(); - - const storedEdge = await kv.get( - "mem:action-edges", - a2.edges[0].id, - ); - expect(storedEdge).toBeNull(); - - const storedSketch = await kv.get( - "mem:sketches", - sketch.sketch.id, - ); - expect(storedSketch!.status).toBe("discarded"); - expect(storedSketch!.discardedAt).toBeDefined(); - }); - - it("returns error for non-active sketch", async () => { - const sketch = (await sdk.trigger("mem::sketch-create", { - title: "Promote first", - })) as { success: boolean; sketch: Sketch }; - - await sdk.trigger("mem::sketch-promote", { - sketchId: sketch.sketch.id, - }); - - const result = (await sdk.trigger("mem::sketch-discard", { - sketchId: sketch.sketch.id, - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("sketch is not active"); - }); - - it("returns error for non-existent sketch", async () => { - const result = (await sdk.trigger("mem::sketch-discard", { - sketchId: "nonexistent", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("sketch not found"); - }); - }); - - describe("mem::sketch-list", () => { - beforeEach(async () => { - await sdk.trigger("mem::sketch-create", { - title: "Active alpha", - project: "alpha", - }); - await new Promise((r) => setTimeout(r, 5)); - await sdk.trigger("mem::sketch-create", { - title: "Active beta", - project: "beta", - }); - await new Promise((r) => setTimeout(r, 5)); - const toPromote = (await sdk.trigger("mem::sketch-create", { - title: "Promoted alpha", - project: "alpha", - })) as { success: boolean; sketch: Sketch }; - await sdk.trigger("mem::sketch-promote", { - sketchId: toPromote.sketch.id, - }); - }); - - it("returns all sketches", async () => { - const result = (await sdk.trigger("mem::sketch-list", {})) as { - success: boolean; - sketches: (Sketch & { actionCount: number })[]; - }; - - expect(result.success).toBe(true); - expect(result.sketches.length).toBe(3); - }); - - it("filters by status", async () => { - const result = (await sdk.trigger("mem::sketch-list", { - status: "active", - })) as { - success: boolean; - sketches: (Sketch & { actionCount: number })[]; - }; - - expect(result.success).toBe(true); - expect(result.sketches.length).toBe(2); - expect(result.sketches.every((s) => s.status === "active")).toBe(true); - }); - - it("filters by project", async () => { - const result = (await sdk.trigger("mem::sketch-list", { - project: "alpha", - })) as { - success: boolean; - sketches: (Sketch & { actionCount: number })[]; - }; - - expect(result.success).toBe(true); - expect(result.sketches.length).toBe(2); - expect(result.sketches.every((s) => s.project === "alpha")).toBe(true); - }); - - it("includes actionCount in results", async () => { - const result = (await sdk.trigger("mem::sketch-list", {})) as { - success: boolean; - sketches: (Sketch & { actionCount: number })[]; - }; - - expect(result.success).toBe(true); - expect(result.sketches[0].actionCount).toBe(0); - }); - }); - - describe("mem::sketch-gc", () => { - it("collects expired active sketches", async () => { - const sketch = (await sdk.trigger("mem::sketch-create", { - title: "Expired sketch", - expiresInMs: 1, - })) as { success: boolean; sketch: Sketch }; - - await sdk.trigger("mem::sketch-add", { - sketchId: sketch.sketch.id, - title: "Doomed action", - }); - - await new Promise((r) => setTimeout(r, 10)); - - const result = (await sdk.trigger("mem::sketch-gc", {})) as { - success: boolean; - collected: number; - }; - - expect(result.success).toBe(true); - expect(result.collected).toBe(1); - - const stored = await kv.get("mem:sketches", sketch.sketch.id); - expect(stored!.status).toBe("discarded"); - expect(stored!.discardedAt).toBeDefined(); - }); - - it("skips non-expired sketches", async () => { - await sdk.trigger("mem::sketch-create", { - title: "Still alive", - expiresInMs: 3600000, - }); - - const result = (await sdk.trigger("mem::sketch-gc", {})) as { - success: boolean; - collected: number; - }; - - expect(result.success).toBe(true); - expect(result.collected).toBe(0); - }); - - it("skips non-active sketches", async () => { - const sketch = (await sdk.trigger("mem::sketch-create", { - title: "Already promoted", - expiresInMs: 1, - })) as { success: boolean; sketch: Sketch }; - - await sdk.trigger("mem::sketch-promote", { - sketchId: sketch.sketch.id, - }); - - await new Promise((r) => setTimeout(r, 10)); - - const result = (await sdk.trigger("mem::sketch-gc", {})) as { - success: boolean; - collected: number; - }; - - expect(result.success).toBe(true); - expect(result.collected).toBe(0); - }); - - it("deletes actions and edges of expired sketches", async () => { - const sketch = (await sdk.trigger("mem::sketch-create", { - title: "GC with cleanup", - expiresInMs: 1, - })) as { success: boolean; sketch: Sketch }; - - const a1 = (await sdk.trigger("mem::sketch-add", { - sketchId: sketch.sketch.id, - title: "Step 1", - })) as { success: boolean; action: Action }; - - await sdk.trigger("mem::sketch-add", { - sketchId: sketch.sketch.id, - title: "Step 2", - dependsOn: [a1.action.id], - }); - - await new Promise((r) => setTimeout(r, 10)); - - await sdk.trigger("mem::sketch-gc", {}); - - const storedAction = await kv.get("mem:actions", a1.action.id); - expect(storedAction).toBeNull(); - }); - }); -}); diff --git a/test/skill-extract.test.ts b/test/skill-extract.test.ts deleted file mode 100644 index b28fce63..00000000 --- a/test/skill-extract.test.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; - -const mockKv = { - get: vi.fn(), - set: vi.fn(), - list: vi.fn(), - delete: vi.fn(), -}; - -const mockSdk = { - registerFunction: vi.fn(), -}; - -const mockProvider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn(), -}; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("../src/functions/audit.js", () => ({ - recordAudit: vi.fn(), -})); - -import { registerSkillExtractFunctions } from "../src/functions/skill-extract.js"; - -describe("skill-extract", () => { - let handlers: Record; - - beforeEach(() => { - vi.clearAllMocks(); - mockKv.get.mockResolvedValue(null); - mockKv.set.mockResolvedValue(undefined); - mockKv.list.mockResolvedValue([]); - - handlers = {}; - mockSdk.registerFunction.mockImplementation((idOrMeta: any, handler: any) => { - const id = typeof idOrMeta === "string" ? idOrMeta : idOrMeta.id; - handlers[id] = handler; - }); - - registerSkillExtractFunctions(mockSdk as any, mockKv as any, mockProvider); - }); - - it("registers all skill functions", () => { - expect(handlers["mem::skill-extract"]).toBeDefined(); - expect(handlers["mem::skill-list"]).toBeDefined(); - expect(handlers["mem::skill-match"]).toBeDefined(); - }); - - it("skill-extract requires sessionId", async () => { - const result = await handlers["mem::skill-extract"]({}); - expect(result.success).toBe(false); - }); - - it("skill-extract returns error for missing session", async () => { - mockKv.get.mockReturnValue(Promise.resolve(null)); - const result = await handlers["mem::skill-extract"]({ - sessionId: "nonexistent", - }); - expect(result.success).toBe(false); - expect(result.error).toContain("session not found"); - }); - - it("skill-extract parses LLM response into ProceduralMemory", async () => { - mockKv.get.mockImplementation((scope: string, key: string) => { - if (scope === "mem:sessions") - return Promise.resolve({ id: "s1", project: "test", status: "completed" }); - if (scope === "mem:summaries") - return Promise.resolve({ - sessionId: "s1", - project: "test", - title: "Fix auth bug", - narrative: "Debugged and fixed JWT expiration", - keyDecisions: ["Switch to RS256"], - filesModified: ["auth.ts"], - concepts: ["authentication", "JWT"], - createdAt: new Date().toISOString(), - observationCount: 10, - }); - return Promise.resolve(null); - }); - - mockKv.list.mockReturnValue( - Promise.resolve(Array.from({ length: 5 }, (_, i) => ({ - id: `obs${i}`, - sessionId: "s1", - timestamp: new Date().toISOString(), - type: "file_edit", - title: `Edit auth.ts step ${i}`, - narrative: `Modified JWT validation logic step ${i}`, - importance: 7, - concepts: ["JWT"], - files: ["auth.ts"], - facts: [], - }))), - ); - - mockProvider.summarize.mockResolvedValue(` - -When the agent encounters JWT authentication failures or token expiration issues -Fix JWT Token Expiration - -Check the JWT library configuration for clock skew tolerance -Verify the signing algorithm matches between issuer and verifier -Update token expiration to use RS256 with proper key rotation - -JWT auth works reliably with proper expiration handling -jwt,authentication,security - - `); - - const result = await handlers["mem::skill-extract"]({ sessionId: "s1" }); - expect(result.success).toBe(true); - expect(result.extracted).toBe(true); - expect(result.skill.name).toBe("Fix JWT Token Expiration"); - expect(result.skill.steps).toHaveLength(3); - expect(result.skill.triggerCondition).toContain("JWT"); - expect(mockKv.set).toHaveBeenCalled(); - }); - - it("skill-extract returns no-skill for exploratory sessions", async () => { - mockKv.get.mockImplementation((scope: string) => { - if (scope === "mem:sessions") return Promise.resolve({ id: "s1", project: "test", status: "completed" }); - if (scope === "mem:summaries") - return Promise.resolve({ - sessionId: "s1", - project: "test", - title: "Explore codebase", - narrative: "Browsed files", - keyDecisions: [], - filesModified: [], - concepts: [], - createdAt: new Date().toISOString(), - observationCount: 3, - }); - return Promise.resolve(null); - }); - mockKv.list.mockReturnValue( - Promise.resolve(Array.from({ length: 4 }, (_, i) => ({ - id: `obs${i}`, - sessionId: "s1", - timestamp: new Date().toISOString(), - type: "file_read", - title: `Read file ${i}`, - importance: 5, - concepts: [], - files: [], - facts: [], - narrative: "", - }))), - ); - mockProvider.summarize.mockResolvedValue(""); - - const result = await handlers["mem::skill-extract"]({ sessionId: "s1" }); - expect(result.success).toBe(true); - expect(result.extracted).toBe(false); - }); - - it("skill-list returns sorted by strength", async () => { - mockKv.list.mockResolvedValue([ - { id: "s1", name: "Low", strength: 0.3 }, - { id: "s2", name: "High", strength: 0.9 }, - ]); - const result = await handlers["mem::skill-list"]({}); - expect(result.skills[0].name).toBe("High"); - }); - - it("skill-match finds relevant skills", async () => { - mockKv.list.mockResolvedValue([ - { - id: "s1", - name: "Fix JWT Auth", - triggerCondition: "JWT failures", - tags: ["jwt", "auth"], - steps: ["check config", "update key"], - strength: 0.8, - }, - { - id: "s2", - name: "Deploy Docker", - triggerCondition: "container deployment", - tags: ["docker", "deploy"], - steps: ["build image", "push"], - strength: 0.7, - }, - ]); - - const result = await handlers["mem::skill-match"]({ - query: "JWT authentication token expired", - }); - expect(result.matches.length).toBe(1); - expect(result.matches[0].skill.name).toBe("Fix JWT Auth"); - }); -}); diff --git a/test/sliding-window.test.ts b/test/sliding-window.test.ts deleted file mode 100644 index 9f3a75ed..00000000 --- a/test/sliding-window.test.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; -import type { CompressedObservation, MemoryProvider } from "../src/types.js"; - -function makeObs( - id: string, - title: string, - narrative: string, - overrides: Partial = {}, -): CompressedObservation { - return { - id, - sessionId: "ses_1", - timestamp: new Date().toISOString(), - type: "file_edit", - title, - subtitle: "", - facts: [], - narrative, - concepts: [], - files: [], - importance: 5, - ...overrides, - }; -} - -function mockKV(observations: CompressedObservation[] = []) { - const store = new Map>(); - const obsMap = new Map(); - for (const obs of observations) { - obsMap.set(obs.id, obs); - } - store.set("mem:obs:ses_1", obsMap); - - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, fn: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, fn); - }, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (fn) return fn(payload); - return null; - }, - triggerVoid: () => {}, - }; -} - -function mockProvider(response: string): MemoryProvider { - return { - name: "test", - compress: vi.fn().mockResolvedValue(response), - summarize: vi.fn().mockResolvedValue(response), - }; -} - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -describe("SlidingWindow", () => { - it("imports without errors", async () => { - const mod = await import("../src/functions/sliding-window.js"); - expect(mod.registerSlidingWindowFunction).toBeDefined(); - }); - - it("registers both functions", async () => { - const { registerSlidingWindowFunction } = await import( - "../src/functions/sliding-window.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - const provider = mockProvider(""); - registerSlidingWindowFunction(sdk as never, kv as never, provider); - - expect(sdk.trigger).toBeDefined(); - }); - - it("enriches observation with sliding window context", async () => { - const { registerSlidingWindowFunction } = await import( - "../src/functions/sliding-window.js" - ); - - const obs1 = makeObs( - "obs_1", - "User discussed React framework", - "The user mentioned they are working with React for their frontend.", - { timestamp: "2024-01-01T00:00:00Z" }, - ); - const obs2 = makeObs( - "obs_2", - "Framework frustration", - "The user said they hate that framework and find it hard to debug.", - { timestamp: "2024-01-01T00:01:00Z" }, - ); - const obs3 = makeObs( - "obs_3", - "Switching to Vue", - "The user decided to switch to Vue for the project.", - { timestamp: "2024-01-01T00:02:00Z" }, - ); - - const kv = mockKV([obs1, obs2, obs3]); - const sdk = mockSdk(); - - const enrichedXml = ` - The user (working with React for frontend) expressed strong frustration with React framework, finding it difficult to debug. - - - - - User dislikes React due to debugging difficulty - - - User was working with React before expressing frustration - -`; - - const provider = mockProvider(enrichedXml); - registerSlidingWindowFunction(sdk as never, kv as never, provider); - - const result = (await sdk.trigger("mem::enrich-window", { - observationId: "obs_2", - sessionId: "ses_1", - lookback: 1, - lookahead: 1, - })) as { success: boolean; enriched: any }; - - expect(result.success).toBe(true); - expect(result.enriched).toBeDefined(); - expect(result.enriched.resolvedEntities["that framework"]).toBe("React"); - expect(result.enriched.preferences).toContain( - "User dislikes React due to debugging difficulty", - ); - expect(result.enriched.contextBridges.length).toBeGreaterThan(0); - }); - - it("returns null enrichment when no adjacent observations", async () => { - const { registerSlidingWindowFunction } = await import( - "../src/functions/sliding-window.js" - ); - const obs = makeObs("obs_solo", "Solo observation", "Just one."); - const kv = mockKV([obs]); - const sdk = mockSdk(); - const provider = mockProvider(""); - registerSlidingWindowFunction(sdk as never, kv as never, provider); - - const result = (await sdk.trigger("mem::enrich-window", { - observationId: "obs_solo", - sessionId: "ses_1", - })) as { success: boolean; enriched: any; reason: string }; - - expect(result.success).toBe(true); - expect(result.enriched).toBeNull(); - }); - - it("returns error for missing observation", async () => { - const { registerSlidingWindowFunction } = await import( - "../src/functions/sliding-window.js" - ); - const kv = mockKV([]); - const sdk = mockSdk(); - const provider = mockProvider(""); - registerSlidingWindowFunction(sdk as never, kv as never, provider); - - const result = (await sdk.trigger("mem::enrich-window", { - observationId: "nonexistent", - sessionId: "ses_1", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toBe("Observation not found"); - }); -}); diff --git a/test/slots-flag-gate.test.ts b/test/slots-flag-gate.test.ts deleted file mode 100644 index 287bcbd8..00000000 --- a/test/slots-flag-gate.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -// Regression tests for #678: -// - isSlotsEnabled / isReflectEnabled must read from ~/.agentmemory/.env -// (not only process.env), so users who set AGENTMEMORY_SLOTS in the -// dotfile see the flag take effect. -// - HTTP triggers must return 503 with enableHow when the flag is off, -// not 500. - -describe("isSlotsEnabled — reads merged env (#678)", () => { - let home: string; - let ORIG_HOME: string | undefined; - let ORIG_FLAG: string | undefined; - - beforeEach(() => { - home = mkdtempSync(join(tmpdir(), "am-slots-flag-")); - mkdirSync(join(home, ".agentmemory"), { recursive: true }); - ORIG_HOME = process.env["HOME"]; - ORIG_FLAG = process.env["AGENTMEMORY_SLOTS"]; - process.env["HOME"] = home; - delete process.env["AGENTMEMORY_SLOTS"]; - vi.resetModules(); - }); - - afterEach(() => { - if (ORIG_HOME !== undefined) process.env["HOME"] = ORIG_HOME; - if (ORIG_FLAG !== undefined) process.env["AGENTMEMORY_SLOTS"] = ORIG_FLAG; - else delete process.env["AGENTMEMORY_SLOTS"]; - rmSync(home, { recursive: true, force: true }); - }); - - it("returns false when neither process.env nor .env sets the flag", async () => { - const { isSlotsEnabled } = await import("../src/functions/slots.js"); - expect(isSlotsEnabled()).toBe(false); - }); - - it("returns true when AGENTMEMORY_SLOTS=true lives only in ~/.agentmemory/.env", async () => { - writeFileSync( - join(home, ".agentmemory", ".env"), - "AGENTMEMORY_SLOTS=true\n", - ); - const { isSlotsEnabled } = await import("../src/functions/slots.js"); - expect(isSlotsEnabled()).toBe(true); - }); - - it("returns true when process.env wins over .env (existing behaviour preserved)", async () => { - writeFileSync( - join(home, ".agentmemory", ".env"), - "AGENTMEMORY_SLOTS=false\n", - ); - process.env["AGENTMEMORY_SLOTS"] = "true"; - const { isSlotsEnabled } = await import("../src/functions/slots.js"); - expect(isSlotsEnabled()).toBe(true); - }); -}); - -describe("isReflectEnabled — reads merged env (#678)", () => { - let home: string; - let ORIG_HOME: string | undefined; - let ORIG_FLAG: string | undefined; - - beforeEach(() => { - home = mkdtempSync(join(tmpdir(), "am-reflect-flag-")); - mkdirSync(join(home, ".agentmemory"), { recursive: true }); - ORIG_HOME = process.env["HOME"]; - ORIG_FLAG = process.env["AGENTMEMORY_REFLECT"]; - process.env["HOME"] = home; - delete process.env["AGENTMEMORY_REFLECT"]; - vi.resetModules(); - }); - - afterEach(() => { - if (ORIG_HOME !== undefined) process.env["HOME"] = ORIG_HOME; - if (ORIG_FLAG !== undefined) process.env["AGENTMEMORY_REFLECT"] = ORIG_FLAG; - else delete process.env["AGENTMEMORY_REFLECT"]; - rmSync(home, { recursive: true, force: true }); - }); - - it("returns true when AGENTMEMORY_REFLECT=true is only in ~/.agentmemory/.env", async () => { - writeFileSync( - join(home, ".agentmemory", ".env"), - "AGENTMEMORY_REFLECT=true\n", - ); - const { isReflectEnabled } = await import("../src/functions/slots.js"); - expect(isReflectEnabled()).toBe(true); - }); -}); diff --git a/test/slots.test.ts b/test/slots.test.ts deleted file mode 100644 index 70da2aed..00000000 --- a/test/slots.test.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; -import { registerSlotsFunctions, DEFAULT_SLOTS, listPinnedSlots, renderPinnedContext } from "../src/functions/slots.js"; -import { KV } from "../src/state/schema.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - if (!store.has(scope)) return []; - return Array.from(store.get(scope)!.values()) as T[]; - }, - }; -} - -function wire() { - const kv = mockKV(); - const handlers: Record) => Promise>> = {}; - const sdk = { - registerFunction: vi.fn((id: string, cb) => { - handlers[id] = cb; - }), - } as unknown as import("iii-sdk").ISdk; - registerSlotsFunctions(sdk, kv as never); - return { kv, handlers }; -} - -async function waitForSeed(kv: ReturnType) { - for (let i = 0; i < 20; i++) { - const p = await kv.list(KV.slots); - const g = await kv.list(KV.globalSlots); - if (p.length + g.length >= DEFAULT_SLOTS.length) return; - await new Promise((r) => setTimeout(r, 5)); - } -} - -describe("slots — primitive", () => { - let kv: ReturnType; - let handlers: Record) => Promise>>; - - beforeEach(async () => { - ({ kv, handlers } = wire()); - await waitForSeed(kv); - }); - - it("seeds default slots into the right scopes on first run", async () => { - const global = (await kv.list(KV.globalSlots)) as Array<{ label: string }>; - const project = (await kv.list(KV.slots)) as Array<{ label: string }>; - expect(global.map((s) => s.label).sort()).toEqual( - ["persona", "tool_guidelines", "user_preferences"].sort(), - ); - expect(project.map((s) => s.label).sort().length).toBeGreaterThanOrEqual(5); - }); - - it("rejects labels with bad shape", async () => { - const res = (await handlers["mem::slot-create"]({ label: "Bad Label!" })) as { success: boolean; error: string }; - expect(res.success).toBe(false); - expect(res.error).toMatch(/label required/); - }); - - it("create then get round-trips a new slot", async () => { - const created = (await handlers["mem::slot-create"]({ - label: "notes_todo", - content: "hello", - description: "scratchpad", - })) as { success: boolean; slot: { label: string; content: string } }; - expect(created.success).toBe(true); - expect(created.slot.content).toBe("hello"); - - const fetched = (await handlers["mem::slot-get"]({ label: "notes_todo" })) as { - success: boolean; - slot: { content: string }; - }; - expect(fetched.success).toBe(true); - expect(fetched.slot.content).toBe("hello"); - }); - - it("rejects duplicate create", async () => { - await handlers["mem::slot-create"]({ label: "scratch", content: "a" }); - const dup = (await handlers["mem::slot-create"]({ label: "scratch", content: "b" })) as { - success: boolean; - error: string; - }; - expect(dup.success).toBe(false); - expect(dup.error).toMatch(/already exists/); - }); - - it("append refuses writes that would blow the sizeLimit", async () => { - await handlers["mem::slot-create"]({ label: "tight", content: "", sizeLimit: 10 }); - const ok = (await handlers["mem::slot-append"]({ label: "tight", text: "short" })) as { success: boolean }; - expect(ok.success).toBe(true); - const tooBig = (await handlers["mem::slot-append"]({ label: "tight", text: "way too long for this slot" })) as { - success: boolean; - error: string; - }; - expect(tooBig.success).toBe(false); - expect(tooBig.error).toMatch(/exceed sizeLimit/); - }); - - it("replace refuses content above sizeLimit", async () => { - await handlers["mem::slot-create"]({ label: "tiny", content: "", sizeLimit: 5 }); - const res = (await handlers["mem::slot-replace"]({ label: "tiny", content: "exceeds" })) as { - success: boolean; - error: string; - }; - expect(res.success).toBe(false); - expect(res.error).toMatch(/exceeds/); - }); - - it("delete removes the slot", async () => { - await handlers["mem::slot-create"]({ label: "throwaway", content: "bye" }); - const del = (await handlers["mem::slot-delete"]({ label: "throwaway" })) as { success: boolean }; - expect(del.success).toBe(true); - const get = (await handlers["mem::slot-get"]({ label: "throwaway" })) as { success: boolean }; - expect(get.success).toBe(false); - }); - - it("project slot shadows global slot of the same label", async () => { - // Default seed already created a global `persona`. Populate it through - // the public handler, then create a project-scoped override through the - // same handler so scope validation + shadowing logic is exercised end - // to end (no direct kv.set). - await handlers["mem::slot-replace"]({ label: "persona", content: "global-persona" }); - const createRes = (await handlers["mem::slot-create"]({ - label: "persona", - content: "project-override", - scope: "project", - })) as { success: boolean }; - expect(createRes.success).toBe(true); - - const res = (await handlers["mem::slot-get"]({ label: "persona" })) as { - slot: { content: string }; - scope: string; - }; - expect(res.slot.content).toBe("project-override"); - expect(res.scope).toBe("project"); - }); - - it("rejects invalid sizeLimit instead of silently defaulting", async () => { - const tooBig = (await handlers["mem::slot-create"]({ - label: "oversize", - sizeLimit: 99999, - })) as { success: boolean; error: string }; - expect(tooBig.success).toBe(false); - expect(tooBig.error).toMatch(/sizeLimit must be/); - - const negative = (await handlers["mem::slot-create"]({ - label: "negative", - sizeLimit: -1, - })) as { success: boolean; error: string }; - expect(negative.success).toBe(false); - - const nonInteger = (await handlers["mem::slot-create"]({ - label: "fractional", - sizeLimit: 1.5, - })) as { success: boolean; error: string }; - expect(nonInteger.success).toBe(false); - }); - - it("rejects unknown scope values", async () => { - const res = (await handlers["mem::slot-create"]({ - label: "bad_scope", - scope: "wrong" as unknown as "project", - })) as { success: boolean; error: string }; - expect(res.success).toBe(false); - expect(res.error).toMatch(/scope must be/); - }); - - it("listPinnedSlots returns only pinned slots with content", async () => { - await handlers["mem::slot-append"]({ label: "persona", text: "helpful senior engineer" }); - const pinned = await listPinnedSlots(kv as never); - expect(pinned.some((s) => s.label === "persona")).toBe(true); - expect(pinned.every((s) => s.pinned && s.content.trim().length > 0)).toBe(true); - }); - - it("renderPinnedContext serialises slots into markdown", async () => { - await handlers["mem::slot-append"]({ label: "persona", text: "senior eng" }); - const pinned = await listPinnedSlots(kv as never); - const rendered = renderPinnedContext(pinned); - expect(rendered).toContain("## persona"); - expect(rendered).toContain("senior eng"); - }); -}); - -describe("slots — reflect", () => { - let kv: ReturnType; - let handlers: Record) => Promise>>; - - beforeEach(async () => { - ({ kv, handlers } = wire()); - await waitForSeed(kv); - }); - - it("no-ops when the session has no observations", async () => { - const res = (await handlers["mem::slot-reflect"]({ sessionId: "empty-session" })) as { - success: boolean; - applied: number; - }; - expect(res.success).toBe(true); - expect(res.applied).toBe(0); - }); - - it("moves TODO-flavoured observations into pending_items and counts patterns", async () => { - const sessionId = "sess_reflect"; - const obsKey = KV.observations(sessionId); - const base = { - id: "obs1", - sessionId, - timestamp: new Date().toISOString(), - type: "error" as const, - title: "TODO: wire up retries", - subtitle: "", - facts: [], - narrative: "agent left a TODO for retries", - concepts: [], - files: ["src/retry.ts"], - importance: 5, - }; - await kv.set(obsKey, "obs1", base); - await kv.set(obsKey, "obs2", { - ...base, - id: "obs2", - title: "compile error", - narrative: "tsc failed", - files: ["src/other.ts"], - type: "error", - }); - const res = (await handlers["mem::slot-reflect"]({ sessionId })) as { - success: boolean; - applied: number; - observationsReviewed: number; - }; - expect(res.success).toBe(true); - expect(res.observationsReviewed).toBe(2); - expect(res.applied).toBeGreaterThan(0); - - const pending = (await handlers["mem::slot-get"]({ label: "pending_items" })) as { - slot: { content: string }; - }; - expect(pending.slot.content).toContain("TODO: wire up retries"); - - const patterns = (await handlers["mem::slot-get"]({ label: "session_patterns" })) as { - slot: { content: string }; - }; - expect(patterns.slot.content).toMatch(/errors: 2/); - }); -}); diff --git a/test/smart-search.test.ts b/test/smart-search.test.ts deleted file mode 100644 index 9d0c94e0..00000000 --- a/test/smart-search.test.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerSmartSearchFunction } from "../src/functions/smart-search.js"; -import type { - CompressedObservation, - HybridSearchResult, - CompactSearchResult, - Session, -} from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeObs( - overrides: Partial = {}, -): CompressedObservation { - return { - id: "obs_1", - sessionId: "ses_1", - timestamp: "2026-02-01T10:00:00Z", - type: "file_edit", - title: "Edit auth handler", - facts: [], - narrative: "Modified auth", - concepts: ["auth"], - files: ["src/auth.ts"], - importance: 7, - ...overrides, - }; -} - -describe("Smart Search Function", () => { - let sdk: ReturnType; - let kv: ReturnType; - let searchResults: HybridSearchResult[]; - - beforeEach(async () => { - sdk = mockSdk(); - kv = mockKV(); - - const obs1 = makeObs({ id: "obs_1", sessionId: "ses_1", title: "Auth handler" }); - const obs2 = makeObs({ id: "obs_2", sessionId: "ses_1", title: "Database setup" }); - - searchResults = [ - { - observation: obs1, - bm25Score: 0.8, - vectorScore: 0, - combinedScore: 0.8, - sessionId: "ses_1", - }, - { - observation: obs2, - bm25Score: 0.3, - vectorScore: 0, - combinedScore: 0.3, - sessionId: "ses_1", - }, - ]; - - const session: Session = { - id: "ses_1", - project: "my-project", - cwd: "/tmp", - startedAt: "2026-02-01T00:00:00Z", - status: "completed", - observationCount: 2, - }; - await kv.set("mem:sessions", "ses_1", session); - await kv.set("mem:obs:ses_1", "obs_1", obs1); - await kv.set("mem:obs:ses_1", "obs_2", obs2); - - const searchFn = async (_query: string, _limit: number) => searchResults; - registerSmartSearchFunction(sdk as never, kv as never, searchFn); - }); - - it("compact mode returns CompactSearchResult array", async () => { - const result = (await sdk.trigger("mem::smart-search", { - query: "auth", - })) as { mode: string; results: CompactSearchResult[] }; - - expect(result.mode).toBe("compact"); - expect(result.results.length).toBe(2); - expect(result.results[0]).toHaveProperty("obsId"); - expect(result.results[0]).toHaveProperty("title"); - expect(result.results[0]).toHaveProperty("type"); - expect(result.results[0]).toHaveProperty("score"); - expect(result.results[0]).toHaveProperty("timestamp"); - expect(result.results[0]).not.toHaveProperty("narrative"); - }); - - it("expand mode returns full observations for given IDs", async () => { - const result = (await sdk.trigger("mem::smart-search", { - expandIds: ["obs_1"], - })) as { mode: string; results: Array<{ obsId: string; observation: CompressedObservation }> }; - - expect(result.mode).toBe("expanded"); - expect(result.results.length).toBe(1); - expect(result.results[0].observation.title).toBe("Auth handler"); - }); - - it("returns error when query is missing and no expandIds", async () => { - const result = (await sdk.trigger("mem::smart-search", {})) as { - mode: string; - error: string; - }; - - expect(result.mode).toBe("compact"); - expect(result.error).toBe("query is required"); - expect((result as { results: unknown[] }).results).toEqual([]); - }); - - it("respects limit parameter in compact mode", async () => { - const result = (await sdk.trigger("mem::smart-search", { - query: "auth", - limit: 1, - })) as { mode: string; results: CompactSearchResult[] }; - - expect(result.results.length).toBeLessThanOrEqual(2); - }); - - it("expand returns empty for nonexistent observation IDs", async () => { - const result = (await sdk.trigger("mem::smart-search", { - expandIds: ["obs_nonexistent_ses_xxx"], - })) as { mode: string; results: unknown[] }; - - expect(result.mode).toBe("expanded"); - expect(result.results.length).toBe(0); - }); - - it("compact mode records access for every returned observation id (#119)", async () => { - await sdk.trigger("mem::smart-search", { query: "auth" }); - // recordAccessBatch is fire-and-forget — let the microtask queue drain. - await new Promise((r) => setImmediate(r)); - - const log1 = (await kv.get("mem:access", "obs_1")) as { - count: number; - } | null; - const log2 = (await kv.get("mem:access", "obs_2")) as { - count: number; - } | null; - - expect(log1?.count).toBe(1); - expect(log2?.count).toBe(1); - }); - - it("expand mode records access for expanded observation ids (#119)", async () => { - await sdk.trigger("mem::smart-search", { expandIds: ["obs_1"] }); - await new Promise((r) => setImmediate(r)); - - const log = (await kv.get("mem:access", "obs_1")) as { - count: number; - } | null; - expect(log?.count).toBe(1); - }); - - describe("lesson inclusion (#lesson-visibility)", () => { - it("compact mode returns lessons array alongside observation results", async () => { - sdk.registerFunction("mem::lesson-recall", async (payload: any) => ({ - success: true, - lessons: [ - { id: "lsn_a", content: "always rebase before push", confidence: 0.9, createdAt: "2026-04-01T00:00:00Z", project: "p", tags: ["git"], score: 0.81 }, - { id: "lsn_b", content: "never force-push to main", confidence: 0.95, createdAt: "2026-04-02T00:00:00Z", project: "p", tags: ["git"], score: 0.76 }, - ], - })); - - const result = (await sdk.trigger("mem::smart-search", { - query: "rebase", - })) as { mode: string; results: CompactSearchResult[]; lessons?: any[] }; - - expect(result.mode).toBe("compact"); - expect(result.results.length).toBe(2); // observations unchanged - expect(result.lessons).toBeDefined(); - expect(result.lessons!.length).toBe(2); - expect(result.lessons![0]).toMatchObject({ - lessonId: "lsn_a", - confidence: 0.9, - score: 0.81, - }); - expect(result.lessons![0].tags).toEqual(["git"]); - }); - - it("compact mode truncates long lesson content for preview", async () => { - const long = "x".repeat(500); - sdk.registerFunction("mem::lesson-recall", async () => ({ - success: true, - lessons: [{ id: "lsn_long", content: long, confidence: 0.5, createdAt: "", tags: [], score: 0.4 }], - })); - - const result = (await sdk.trigger("mem::smart-search", { - query: "x", - })) as { lessons: any[] }; - - expect(result.lessons[0].content.length).toBeLessThan(long.length); - expect(result.lessons[0].content).toMatch(/…$/); - }); - - it("includeLessons:false omits the lessons array entirely", async () => { - // No lesson-recall handler registered — would throw if invoked. - const result = (await sdk.trigger("mem::smart-search", { - query: "auth", - includeLessons: false, - })) as { mode: string; results: CompactSearchResult[]; lessons?: unknown }; - - expect(result.results.length).toBe(2); - expect(result.lessons).toBeUndefined(); - }); - - it("forwards project filter to mem::lesson-recall", async () => { - let receivedPayload: any = null; - sdk.registerFunction("mem::lesson-recall", async (payload: any) => { - receivedPayload = payload; - return { success: true, lessons: [] }; - }); - - await sdk.trigger("mem::smart-search", { - query: "rebase", - project: "gitops-assistant", - }); - - expect(receivedPayload).toMatchObject({ - query: "rebase", - project: "gitops-assistant", - }); - }); - - it("tolerates mem::lesson-recall failure: returns empty lessons, observations unchanged", async () => { - sdk.registerFunction("mem::lesson-recall", async () => { - throw new Error("lessons store unavailable"); - }); - - const result = (await sdk.trigger("mem::smart-search", { - query: "auth", - })) as { results: CompactSearchResult[]; lessons: any[] }; - - expect(result.results.length).toBe(2); - expect(result.lessons).toEqual([]); - }); - - it("tolerates non-success lesson-recall response shape", async () => { - sdk.registerFunction("mem::lesson-recall", async () => ({ - success: false, - error: "query is required", - })); - - const result = (await sdk.trigger("mem::smart-search", { - query: "auth", - })) as { results: CompactSearchResult[]; lessons: any[] }; - - expect(result.results.length).toBe(2); - expect(result.lessons).toEqual([]); - }); - }); -}); diff --git a/test/snapshot.test.ts b/test/snapshot.test.ts deleted file mode 100644 index 198774d2..00000000 --- a/test/snapshot.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("node:child_process", () => ({ - execFile: vi.fn( - (_cmd: string, _args: string[], _opts: unknown, cb: Function) => { - cb(null, { stdout: "abc1234\n", stderr: "" }); - }, - ), -})); - -vi.mock("node:util", async () => { - const actual = (await vi.importActual("node:util")) as Record< - string, - unknown - >; - return { - ...actual, - promisify: () => async () => ({ stdout: "abc1234\n", stderr: "" }), - }; -}); - -vi.mock("node:fs", () => ({ - existsSync: vi.fn().mockReturnValue(true), - mkdirSync: vi.fn(), - writeFileSync: vi.fn(), - readFileSync: vi - .fn() - .mockReturnValue('{"version":"0.4.0","sessions":[],"memories":[]}'), -})); - -import { registerSnapshotFunction } from "../src/functions/snapshot.js"; -import type { Session, Memory, SnapshotMeta } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -describe("Snapshot Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - const snapshotDir = "/tmp/agentmemory-snapshots"; - - beforeEach(async () => { - sdk = mockSdk(); - kv = mockKV(); - vi.clearAllMocks(); - registerSnapshotFunction(sdk as never, kv as never, snapshotDir); - - const session: Session = { - id: "ses_1", - project: "test", - cwd: "/tmp", - startedAt: "2026-02-01T00:00:00Z", - status: "completed", - observationCount: 1, - }; - await kv.set("mem:sessions", "ses_1", session); - - const mem: Memory = { - id: "mem_1", - createdAt: "2026-02-01T00:00:00Z", - updatedAt: "2026-02-01T00:00:00Z", - type: "pattern", - title: "Test pattern", - content: "Always test", - concepts: [], - files: [], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, - }; - await kv.set("mem:memories", "mem_1", mem); - }); - - it("snapshot-create serializes state and returns meta", async () => { - const result = (await sdk.trigger("mem::snapshot-create", { - message: "Test snapshot", - })) as { success: boolean; snapshot: SnapshotMeta }; - - expect(result.success).toBe(true); - expect(result.snapshot).toBeDefined(); - expect(result.snapshot.commitHash).toBe("abc1234"); - expect(result.snapshot.message).toBe("Test snapshot"); - expect(result.snapshot.stats.sessions).toBe(1); - expect(result.snapshot.stats.memories).toBe(1); - }); - - it("snapshot-list returns snapshots from git log", async () => { - const result = (await sdk.trigger("mem::snapshot-list", {})) as { - snapshots: Array<{ - commitHash: string; - createdAt: string; - message: string; - }>; - }; - - expect(result.snapshots).toBeDefined(); - expect(Array.isArray(result.snapshots)).toBe(true); - }); - - it("snapshot-restore requires commitHash", async () => { - const result = (await sdk.trigger("mem::snapshot-restore", {})) as { - success: boolean; - error: string; - }; - - expect(result.success).toBe(false); - expect(result.error).toContain("commitHash"); - }); - - it("snapshot-restore loads state from commit", async () => { - const result = (await sdk.trigger("mem::snapshot-restore", { - commitHash: "abc1234", - })) as { success: boolean; commitHash: string }; - - expect(result.success).toBe(true); - expect(result.commitHash).toBe("abc1234"); - }); - - it("snapshot-create records an audit entry", async () => { - await sdk.trigger("mem::snapshot-create", { message: "Audit test" }); - - const audits = await kv.list("mem:audit"); - expect(audits.length).toBe(1); - }); -}); diff --git a/test/source-scan-codex.test.ts b/test/source-scan-codex.test.ts deleted file mode 100644 index b8e3957b..00000000 --- a/test/source-scan-codex.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import { mkdtemp, writeFile, rm } from "node:fs/promises"; -import { readFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { mockKV, mockSdk } from "./helpers/mocks.js"; -import { KV } from "../src/state/schema.js"; -import type { ScanCheckpoint } from "../src/types.js"; -import { getSearchIndex } from "../src/functions/search.js"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); -vi.mock("../src/functions/audit.js", () => ({ safeAudit: vi.fn() })); - -// Wrap ingestJsonlFile so we can assert reads / inject failures, keeping -// everything else in replay.ts (isSensitive/isSymlink/findJsonlFiles) real. -const { ingestSpy } = vi.hoisted(() => ({ ingestSpy: vi.fn() })); -vi.mock("../src/functions/replay.js", async (importOriginal) => { - const actual = await importOriginal(); - ingestSpy.mockImplementation(actual.ingestJsonlFile); - return { ...actual, ingestJsonlFile: ingestSpy }; -}); - -import { - registerSourceScanFunctions, - scanCodexSource, - type CodexScanResult, -} from "../src/functions/source-scan-codex.js"; - -const fixture = readFileSync(join(__dirname, "fixtures/jsonl", "codex-session.jsonl"), "utf-8"); - -let dir: string; -beforeEach(async () => { - getSearchIndex().clear(); - ingestSpy.mockClear(); - dir = await mkdtemp(join(tmpdir(), "codex-scan-")); -}); -afterEach(async () => { - await rm(dir, { recursive: true, force: true }); -}); - -describe("scanCodexSource", () => { - it("ingests new files, then skips unchanged files WITHOUT re-reading them", async () => { - const kv = mockKV(); - const file = join(dir, "rollout-1.jsonl"); - await writeFile(file, fixture); - - const first = await scanCodexSource(kv as never, { path: dir }); - expect(first.imported).toBe(1); - expect(first.sessionIds).toEqual(["codex-real-session"]); - expect(ingestSpy).toHaveBeenCalledTimes(1); - - const cp = await kv.get(KV.scanCheckpoints, "codex"); - expect(cp?.lastSuccessAt).toBeTruthy(); - expect(JSON.parse(cp!.cursor)[file]).toMatchObject({ size: expect.any(Number) }); - - ingestSpy.mockClear(); - const second = await scanCodexSource(kv as never, { path: dir }); - expect(second.imported).toBe(0); - expect(second.skipped).toBe(1); - expect(ingestSpy).not.toHaveBeenCalled(); // unchanged file was never read - }); - - it("re-reads a file after it changes (grows)", async () => { - const kv = mockKV(); - const file = join(dir, "rollout-1.jsonl"); - await writeFile(file, fixture); - await scanCodexSource(kv as never, { path: dir }); - - ingestSpy.mockClear(); - await writeFile(file, `${fixture}\n${fixture.split("\n")[0]}`); // grow -> size changes - const rescan = await scanCodexSource(kv as never, { path: dir }); - expect(ingestSpy).toHaveBeenCalledTimes(1); - expect(rescan.skipped).toBe(0); - }); - - it("records a failed file, keeps the batch going, and retries it next scan", async () => { - const kv = mockKV(); - await writeFile(join(dir, "a.jsonl"), fixture); - await writeFile(join(dir, "b.jsonl"), fixture); - - ingestSpy.mockImplementationOnce(() => { - throw new Error("boom"); - }); // first file processed throws; the batch must continue - - const scan = await scanCodexSource(kv as never, { path: dir }); - expect(scan.errors).toBe(1); - expect(scan.scanned).toBe(2); - - const cp = await kv.get(KV.scanCheckpoints, "codex"); - expect(cp?.lastError).toContain("boom"); - expect(Object.keys(JSON.parse(cp!.cursor))).toHaveLength(1); // only the file that succeeded - - ingestSpy.mockClear(); - await scanCodexSource(kv as never, { path: dir }); - expect(ingestSpy).toHaveBeenCalledTimes(1); // the previously-failed file is retried - }); - - it("runs via the registered mem::source-scan::codex trigger (what auto-scan invokes)", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerSourceScanFunctions(sdk as never, kv as never); - await writeFile(join(dir, "rollout-1.jsonl"), fixture); - - const res = (await sdk.trigger("mem::source-scan::codex", { path: dir })) as CodexScanResult; - expect(res.success).toBe(true); - expect(res.imported).toBe(1); - expect(res.sessionIds).toEqual(["codex-real-session"]); - }); -}); diff --git a/test/sources.test.ts b/test/sources.test.ts new file mode 100644 index 00000000..56925e93 --- /dev/null +++ b/test/sources.test.ts @@ -0,0 +1,260 @@ +import assert from "node:assert/strict"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { openDatabase } from "../src/db/index.js"; +import { getAppPaths } from "../src/paths.js"; +import { parseJsonl } from "../src/parser/jsonl.js"; +import { createAppServer } from "../src/server/index.js"; +import { scanClaudeCodeSessions } from "../src/sources/claude-code.js"; +import { scanCodexSessions } from "../src/sources/codex.js"; +import { observationFromRecord } from "../src/sources/jsonl-source.js"; + +test("parseJsonl reads non-empty JSON object lines", () => { + const records = parseJsonl("{\"text\":\"one\"}\n\n{\"text\":\"two\"}\n"); + assert.deepEqual(records.map((record) => record.value.text), ["one", "two"]); +}); + +test("codex and claude scanners write clean visible transcript and skip unchanged files", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-source-")); + try { + mkdirSync(join(dir, "codex")); + mkdirSync(join(dir, "claude")); + writeFileSync(join(dir, "codex", "session.jsonl"), [ + JSON.stringify({ type: "session_meta", payload: { id: "codex-session", cwd: "/tmp/project", timestamp: "2026-01-01T00:00:00.000Z" } }), + JSON.stringify({ type: "event_msg", payload: { type: "user_message", message: "Please add a CLI doctor command", timestamp: "2026-01-01T00:00:01.000Z" } }), + JSON.stringify({ type: "response_item", payload: { type: "function_call", name: "exec_command", arguments: "{\"cmd\":\"git status\"}" } }), + JSON.stringify({ type: "response_item", payload: { type: "function_call_output", output: "tool output should not be stored" } }), + JSON.stringify({ type: "response_item", payload: { type: "reasoning", text: "hidden reasoning should not be stored" } }), + JSON.stringify({ type: "response_item", payload: { type: "message", role: "assistant", content: [{ type: "output_text", text: "Implemented doctor" }, { type: "tool_use", text: "tool text" }] } }) + ].join("\n")); + writeFileSync(join(dir, "claude", "session.jsonl"), [ + JSON.stringify({ + type: "user", + message: { + role: "user", + content: [ + { type: "text", text: "Fix the scanner checkpoint" }, + { type: "tool_result", content: "tool result should not be stored" } + ] + } + }), + JSON.stringify({ + type: "assistant", + message: { + role: "assistant", + content: [ + { type: "thinking", text: "private chain of thought" }, + { type: "text", text: "The checkpoint now skips unchanged files." }, + { type: "tool_use", name: "Bash", input: { command: "npm test" } } + ] + } + }) + ].join("\n")); + + const db = openDatabase(getAppPaths(join(dir, "home"))); + assert.deepEqual(scanCodexSessions(db, join(dir, "codex")), { + source: "codex", + scanned: 1, + observations: 2, + skipped: 0 + }); + assert.deepEqual(scanCodexSessions(db, join(dir, "codex")), { + source: "codex", + scanned: 0, + observations: 0, + skipped: 1 + }); + assert.equal(scanClaudeCodeSessions(db, join(dir, "claude")).observations, 2); + const rows = db.prepare("SELECT source, role, text FROM observations ORDER BY source, text").all(); + db.close(); + assert.equal(rows.length, 4); + assert.ok(rows.some((row) => row.source === "claude-code" && row.text === "Fix the scanner checkpoint")); + assert.ok(rows.some((row) => row.source === "claude-code" && row.role === "assistant" && row.text === "The checkpoint now skips unchanged files.")); + assert.ok(rows.some((row) => row.source === "codex" && row.role === "assistant" && row.text === "Implemented doctor")); + assert.ok(!rows.some((row) => String(row.text).includes("tool"))); + assert.ok(!rows.some((row) => String(row.text).includes("reasoning"))); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("codex scanner dedupes mirrored event and response messages", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-source-dedupe-")); + try { + mkdirSync(join(dir, "codex")); + writeFileSync(join(dir, "codex", "session.jsonl"), [ + JSON.stringify({ type: "event_msg", payload: { type: "user_message", message: "Please add clean transcript tests" } }), + JSON.stringify({ type: "response_item", payload: { type: "message", role: "user", content: [{ type: "input_text", text: "Please add clean transcript tests" }] } }), + JSON.stringify({ type: "event_msg", payload: { type: "agent_message", message: "Added clean transcript tests." } }), + JSON.stringify({ type: "response_item", payload: { type: "message", role: "assistant", content: [{ type: "output_text", text: "Added clean transcript tests." }] } }) + ].join("\n")); + + const db = openDatabase(getAppPaths(join(dir, "home"))); + assert.equal(scanCodexSessions(db, join(dir, "codex")).observations, 2); + const rows = db.prepare("SELECT role, text FROM observations").all(); + db.close(); + assert.equal(rows.length, 2); + assert.ok(rows.some((row) => row.role === "user" && row.text === "Please add clean transcript tests")); + assert.ok(rows.some((row) => row.role === "assistant" && row.text === "Added clean transcript tests.")); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("clean transcript preserves meaningful newlines and does not drop user JSON examples", () => { + const multiline = observationFromRecord("codex", "session", "/tmp/session.jsonl", { + line: 1, + value: { + type: "event_msg", + payload: { + type: "user_message", + message: "Please keep this list:\n- first item\n- second item\n\n```json\n{\"exec_command\":\"example, not tool output\"}\n```" + } + } + }); + assert.ok(multiline); + assert.match(multiline.text, /first item\n- second item/); + assert.match(multiline.text, /exec_command/); +}); + +test("codex scanner removes injected instruction noise before storing observations", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-source-noise-")); + try { + mkdirSync(join(dir, "codex")); + writeFileSync(join(dir, "codex", "session.jsonl"), [ + JSON.stringify({ + type: "event_msg", + payload: { + type: "user_message", + message: [ + "# AGENTS.md instructions", + "do not store this", + "secret local context", + "stale cwd reminder", + "## My request for Codex:", + "Please keep only this visible request" + ].join("\n"), + timestamp: "2026-01-01T00:00:01.000Z" + } + }) + ].join("\n")); + + const db = openDatabase(getAppPaths(join(dir, "home"))); + assert.equal(scanCodexSessions(db, join(dir, "codex")).observations, 1); + const row = db.prepare("SELECT text FROM observations").get() as { text: string }; + db.close(); + assert.equal(row.text, "Please keep only this visible request"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("scanner checkpoints but does not store sessions with no visible observations", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-source-empty-session-")); + try { + mkdirSync(join(dir, "claude")); + writeFileSync(join(dir, "claude", "session.jsonl"), [ + JSON.stringify({ + type: "user", + message: { + role: "user", + content: [ + { + type: "text", + text: "/effort\neffort\nultracode" + } + ] + } + }), + JSON.stringify({ + type: "assistant", + isMeta: true, + message: { role: "assistant", content: [{ type: "text", text: "metadata should not be stored" }] } + }) + ].join("\n")); + + const db = openDatabase(getAppPaths(join(dir, "home"))); + assert.deepEqual(scanClaudeCodeSessions(db, join(dir, "claude")), { + source: "claude-code", + scanned: 1, + observations: 0, + skipped: 0 + }); + assert.equal((db.prepare("SELECT COUNT(*) as count FROM sessions").get() as { count: number }).count, 0); + assert.equal((db.prepare("SELECT COUNT(*) as count FROM scan_checkpoints").get() as { count: number }).count, 1); + db.close(); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("claude scanner keeps visible user and assistant text after filtering metadata", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-source-claude-visible-")); + try { + mkdirSync(join(dir, "claude")); + writeFileSync(join(dir, "claude", "session.jsonl"), [ + JSON.stringify({ type: "user", isSidechain: true, message: { role: "user", content: [{ type: "text", text: "sidechain should be skipped" }] } }), + JSON.stringify({ type: "user", message: { role: "user", content: [{ type: "text", text: "Please clean Claude visible transcript" }] } }), + JSON.stringify({ type: "assistant", message: { role: "assistant", content: [{ type: "text", text: "I will keep only readable transcript text." }] } }) + ].join("\n")); + + const db = openDatabase(getAppPaths(join(dir, "home"))); + assert.equal(scanClaudeCodeSessions(db, join(dir, "claude")).observations, 2); + const rows = db.prepare("SELECT role, text FROM observations ORDER BY role DESC").all(); + db.close(); + assert.ok(rows.some((row) => row.role === "user" && row.text === "Please clean Claude visible transcript")); + assert.ok(rows.some((row) => row.role === "assistant" && row.text === "I will keep only readable transcript text.")); + assert.ok(!rows.some((row) => String(row.text).includes("sidechain"))); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("checkpoint rescans when jsonl file mtime and size change", () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-checkpoint-")); + try { + mkdirSync(join(dir, "codex")); + const file = join(dir, "codex", "session.jsonl"); + writeFileSync(file, JSON.stringify({ role: "user", text: "Please scan once" })); + + const db = openDatabase(getAppPaths(join(dir, "home"))); + assert.equal(scanCodexSessions(db, join(dir, "codex")).observations, 1); + writeFileSync(file, [ + JSON.stringify({ role: "user", text: "Please scan once" }), + JSON.stringify({ role: "user", text: "Please scan twice now" }) + ].join("\n")); + assert.equal(scanCodexSessions(db, join(dir, "codex")).observations, 2); + const row = db.prepare("SELECT COUNT(*) as count FROM observations").get(); + db.close(); + assert.ok(row); + assert.equal(row.count, 2); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +test("browser sessions endpoint ingests observations", async () => { + const dir = mkdtempSync(join(tmpdir(), "ai-todo-browser-")); + const db = openDatabase(getAppPaths(dir)); + const server = createAppServer({ db }); + + await new Promise((resolve) => server.listen(0, resolve)); + try { + const address = server.address(); + assert.ok(address && typeof address !== "string"); + const response = await fetch(`http://127.0.0.1:${address.port}/browser/sessions`, { + method: "POST", + body: JSON.stringify({ id: "browser-1", messages: [{ role: "user", text: "Track this browser todo" }] }) + }); + assert.equal(response.status, 200); + const row = db.prepare("SELECT COUNT(*) as count FROM observations WHERE source = 'browser'").get(); + assert.ok(row); + assert.equal(row.count, 1); + } finally { + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + db.close(); + rmSync(dir, { recursive: true, force: true }); + } +}); diff --git a/test/stop-hook-recursion-guard.test.ts b/test/stop-hook-recursion-guard.test.ts deleted file mode 100644 index 55d0f911..00000000 --- a/test/stop-hook-recursion-guard.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from "vitest"; -import { isSdkChildContext } from "../src/hooks/sdk-guard.js"; -import { NoopProvider } from "../src/providers/noop.js"; - -describe("isSdkChildContext — Stop hook recursion guard", () => { - const originalEnv = process.env.AGENTMEMORY_SDK_CHILD; - - beforeEach(() => { - delete process.env.AGENTMEMORY_SDK_CHILD; - }); - - afterEach(() => { - if (originalEnv === undefined) { - delete process.env.AGENTMEMORY_SDK_CHILD; - } else { - process.env.AGENTMEMORY_SDK_CHILD = originalEnv; - } - }); - - it("returns true when AGENTMEMORY_SDK_CHILD=1 is in env", () => { - process.env.AGENTMEMORY_SDK_CHILD = "1"; - expect(isSdkChildContext({})).toBe(true); - }); - - it("returns true when payload.entrypoint === 'sdk-ts'", () => { - expect(isSdkChildContext({ entrypoint: "sdk-ts" })).toBe(true); - }); - - it("returns false for a normal CC payload", () => { - expect(isSdkChildContext({ entrypoint: "cli", session_id: "s1" })).toBe(false); - }); - - it("returns false when payload is null / undefined / non-object", () => { - expect(isSdkChildContext(null)).toBe(false); - expect(isSdkChildContext(undefined)).toBe(false); - expect(isSdkChildContext("not-an-object")).toBe(false); - expect(isSdkChildContext(42)).toBe(false); - }); - - it("env marker wins over payload shape", () => { - process.env.AGENTMEMORY_SDK_CHILD = "1"; - expect(isSdkChildContext({ entrypoint: "cli" })).toBe(true); - }); -}); - -describe("NoopProvider — no-op fallback when no LLM key present", () => { - it("reports name 'noop' so callers can detect it and short-circuit", () => { - const p = new NoopProvider(); - expect(p.name).toBe("noop"); - }); - - it("returns empty string for compress and summarize", async () => { - const p = new NoopProvider(); - await expect(p.compress()).resolves.toBe(""); - await expect(p.summarize()).resolves.toBe(""); - }); -}); diff --git a/test/stop-worker-pidfile.test.ts b/test/stop-worker-pidfile.test.ts deleted file mode 100644 index f4550c69..00000000 --- a/test/stop-worker-pidfile.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; - -// #640 + #474: stop must also kill the worker process, not just the -// iii engine. We expose the worker pidfile from src/index.ts and read it -// from src/cli.ts. Static check that both files agree on the path -// (~/.agentmemory/worker.pid) and that stop reads it. -describe("stop reaps the worker process (#640, #474)", () => { - it("src/index.ts writes worker.pid alongside iii.pid", () => { - const source = readFileSync("src/index.ts", "utf-8"); - expect(source).toMatch(/workerPidfilePath\(\)/); - expect(source).toMatch(/"worker\.pid"/); - expect(source).toMatch(/writeWorkerPidfile\(\)/); - expect(source).toMatch(/clearWorkerPidfile\(\)/); - }); - - it("src/cli.ts reads worker.pid in runStop and signals it on stop", () => { - const source = readFileSync("src/cli.ts", "utf-8"); - expect(source).toMatch(/workerPidfilePath\(\)/); - expect(source).toMatch(/"worker\.pid"/); - expect(source).toMatch(/readWorkerPidfile\(\)/); - expect(source).toMatch(/clearWorkerPidfile\(\)/); - // Verify stop wiring: workerCandidates set is built from the pidfile - // and signaled alongside the engine pids. - expect(source).toMatch(/workerCandidates/); - expect(source).toMatch(/Stopping agentmemory worker/); - }); - - it("both files agree on the pidfile path: ~/.agentmemory/worker.pid", () => { - const indexSrc = readFileSync("src/index.ts", "utf-8"); - const cliSrc = readFileSync("src/cli.ts", "utf-8"); - expect(indexSrc).toMatch(/\.agentmemory["'].*worker\.pid|"worker\.pid"/); - expect(cliSrc).toMatch(/\.agentmemory["'].*worker\.pid|"worker\.pid"/); - }); - - it("centralizes runtime data under AGENTMEMORY_HOME when set", () => { - const configSrc = readFileSync("src/config.ts", "utf-8"); - const indexSrc = readFileSync("src/index.ts", "utf-8"); - const cliSrc = readFileSync("src/cli.ts", "utf-8"); - expect(configSrc).toMatch(/AGENTMEMORY_HOME/); - expect(indexSrc).toMatch(/getAgentMemoryDataDir\(\)/); - expect(cliSrc).toMatch(/getAgentMemoryDataDir\(\)/); - }); -}); - -describe("new user startup guardrails", () => { - it("keeps npm install usable with the current peer dependency set", () => { - expect(readFileSync(".npmrc", "utf-8")).toContain("legacy-peer-deps=true"); - }); - - it("starts the pinned iii engine without background update checks", () => { - const source = readFileSync("src/cli.ts", "utf-8"); - expect(source).toContain('["--no-update-check", "--config", configPath]'); - }); - - it("installs SOCKS support for OpenAI-compatible LangExtract behind proxies", () => { - expect(readFileSync("requirements-langextract.txt", "utf-8")).toMatch(/socksio|httpx\[socks\]/); - }); -}); diff --git a/test/summarize.test.ts b/test/summarize.test.ts deleted file mode 100644 index 2eca206e..00000000 --- a/test/summarize.test.ts +++ /dev/null @@ -1,425 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("../src/state/schema.js", () => ({ - KV: { - sessions: "sessions", - summaries: "summaries", - observations: (sessionId: string) => `obs:${sessionId}`, - audit: "audit", - }, -})); - -vi.mock("../src/eval/schemas.js", () => ({ - SummaryOutputSchema: {}, -})); - -vi.mock("../src/eval/validator.js", () => ({ - validateOutput: () => ({ valid: true, result: { errors: [] } }), -})); - -vi.mock("../src/eval/quality.js", () => ({ - scoreSummary: () => 100, -})); - -vi.mock("../src/functions/audit.js", () => ({ - safeAudit: vi.fn(), -})); - -import { registerSummarizeFunction } from "../src/functions/summarize.js"; -import type { - CompressedObservation, - Session, - MemoryProvider, -} from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - store, - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - functions, - registerFunction: (id: string, handler: Function) => { - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async () => ({}), - }; -} - -function makeObs(i: number, sessionId: string): CompressedObservation { - return { - id: `obs_${i}`, - sessionId, - timestamp: new Date().toISOString(), - type: "conversation", - title: `obs ${i}`, - facts: [`fact ${i}`], - narrative: `narrative for obs ${i}`, - concepts: [], - files: [`src/file_${i}.ts`], - importance: 5, - }; -} - -function makeProvider(responses: string[]): MemoryProvider & { - calls: Array<{ system: string; user: string }>; -} { - const calls: Array<{ system: string; user: string }> = []; - let i = 0; - return { - name: "test", - calls, - compress: async () => "", - summarize: async (system: string, user: string) => { - calls.push({ system, user }); - const r = responses[i] ?? responses[responses.length - 1]; - i += 1; - return r; - }, - }; -} - -function summaryXml(opts: { - title: string; - narrative?: string; - decisions?: string[]; - files?: string[]; - concepts?: string[]; -}): string { - const d = (opts.decisions ?? []).map((x) => `${x}`).join(""); - const f = (opts.files ?? []).map((x) => `${x}`).join(""); - const c = (opts.concepts ?? []).map((x) => `${x}`).join(""); - return ` -${opts.title} -${opts.narrative ?? "narrative"} -${d} -${f} -${c} -`; -} - -async function setupHandler(opts: { - sessionId: string; - obsCount: number; - provider: MemoryProvider; -}) { - const sdk = mockSdk(); - const kv = mockKV(); - const session: Session = { - id: opts.sessionId, - project: "test-project", - cwd: "/tmp", - startedAt: new Date().toISOString(), - status: "completed", - observationCount: opts.obsCount, - }; - await kv.set("sessions", opts.sessionId, session); - for (let i = 0; i < opts.obsCount; i++) { - const o = makeObs(i, opts.sessionId); - await kv.set(`obs:${opts.sessionId}`, o.id, o); - } - registerSummarizeFunction(sdk as any, kv as any, opts.provider); - const handler = sdk.functions.get("mem::summarize")!; - return { handler, kv }; -} - -describe("mem::summarize chunking", () => { - const ORIGINAL_ENV = { ...process.env }; - - beforeEach(() => { - delete process.env.SUMMARIZE_CHUNK_SIZE; - delete process.env.SUMMARIZE_CHUNK_CONCURRENCY; - }); - - afterEach(() => { - process.env = { ...ORIGINAL_ENV }; - }); - - it("small session takes the single-call path (no chunking, no reduce)", async () => { - const provider = makeProvider([ - summaryXml({ - title: "Small session", - decisions: ["decision A"], - files: ["src/a.ts"], - concepts: ["concept-a"], - }), - ]); - const { handler, kv } = await setupHandler({ - sessionId: "ses_small", - obsCount: 10, - provider, - }); - - const result: any = await handler({ sessionId: "ses_small" }); - - expect(result.success).toBe(true); - expect(provider.calls).toHaveLength(1); - expect(provider.calls[0].user).toContain("Session observations (10 total)"); - const stored: any = await kv.get("summaries", "ses_small"); - expect(stored?.title).toBe("Small session"); - }); - - it("large session map-reduces: N chunk calls + 1 reduce call", async () => { - process.env.SUMMARIZE_CHUNK_SIZE = "100"; - process.env.SUMMARIZE_CHUNK_CONCURRENCY = "1"; // serial keeps call ordering deterministic - const provider = makeProvider([ - summaryXml({ title: "Chunk 1", decisions: ["dA"], files: ["src/a.ts"], concepts: ["ca"] }), - summaryXml({ title: "Chunk 2", decisions: ["dB"], files: ["src/b.ts"], concepts: ["cb"] }), - summaryXml({ title: "Chunk 3", decisions: ["dC"], files: ["src/c.ts"], concepts: ["cc"] }), - summaryXml({ - title: "Merged", - decisions: ["dA", "dB", "dC"], - files: ["src/a.ts", "src/b.ts", "src/c.ts"], - concepts: ["ca", "cb", "cc"], - }), - ]); - const { handler, kv } = await setupHandler({ - sessionId: "ses_large", - obsCount: 250, - provider, - }); - - const result: any = await handler({ sessionId: "ses_large" }); - - expect(result.success).toBe(true); - expect(provider.calls).toHaveLength(4); - // First three are chunk calls (use the summary system prompt). - expect(provider.calls[0].system).toContain("session summarizer"); - expect(provider.calls[2].system).toContain("session summarizer"); - // Last is the reduce call (uses the merge system prompt). - expect(provider.calls[3].system).toContain("merging multiple partial summaries"); - expect(provider.calls[3].user).toContain("Chunk 1 of 3"); - expect(provider.calls[3].user).toContain("Chunk 3 of 3"); - - const stored: any = await kv.get("summaries", "ses_large"); - expect(stored?.title).toBe("Merged"); - // observationCount on the persisted summary should reflect the full session, - // not just the final chunk. - expect(stored?.observationCount).toBe(250); - expect(stored?.keyDecisions).toEqual(["dA", "dB", "dC"]); - }); - - it("SUMMARIZE_CHUNK_SIZE env override is respected", async () => { - process.env.SUMMARIZE_CHUNK_SIZE = "50"; - process.env.SUMMARIZE_CHUNK_CONCURRENCY = "1"; - const provider = makeProvider([ - summaryXml({ title: "chunk" }), - summaryXml({ title: "chunk" }), - summaryXml({ title: "chunk" }), - summaryXml({ title: "chunk" }), - summaryXml({ title: "merged" }), - ]); - const { handler } = await setupHandler({ - sessionId: "ses_env", - obsCount: 175, - provider, - }); - - const result: any = await handler({ sessionId: "ses_env" }); - - expect(result.success).toBe(true); - // 175 obs ÷ 50 = 4 chunks (last chunk has 25) + 1 reduce = 5 calls. - expect(provider.calls).toHaveLength(5); - }); - - it("flaky chunk: parse fails once, retried, then succeeds — no skip", async () => { - process.env.SUMMARIZE_CHUNK_SIZE = "100"; - process.env.SUMMARIZE_CHUNK_CONCURRENCY = "1"; - const provider = makeProvider([ - summaryXml({ title: "ok1" }), - "", // chunk 2 attempt 1: parse-fail - summaryXml({ title: "ok2" }), // chunk 2 attempt 2 (retry): success - summaryXml({ title: "ok3" }), - summaryXml({ title: "merged" }), - ]); - const { handler, kv } = await setupHandler({ - sessionId: "ses_flaky", - obsCount: 250, - provider, - }); - - const result: any = await handler({ sessionId: "ses_flaky" }); - - expect(result.success).toBe(true); - // 3 chunks × 1 attempt + 1 retry on chunk 2 + 1 reduce = 5 calls. - expect(provider.calls).toHaveLength(5); - const stored: any = await kv.get("summaries", "ses_flaky"); - expect(stored?.title).toBe("merged"); - }); - - it("persistently-broken chunk is skipped, reduce still runs on remaining partials", async () => { - process.env.SUMMARIZE_CHUNK_SIZE = "100"; - process.env.SUMMARIZE_CHUNK_CONCURRENCY = "1"; - const provider = makeProvider([ - summaryXml({ title: "ok1" }), - "", "", // chunk 2: both attempts parse-fail - summaryXml({ title: "ok3" }), - summaryXml({ title: "merged-with-skip" }), - ]); - const { handler, kv } = await setupHandler({ - sessionId: "ses_skip", - obsCount: 250, - provider, - }); - - const result: any = await handler({ sessionId: "ses_skip" }); - - expect(result.success).toBe(true); - // 1 ok + (1 + 1 retry skip) + 1 ok + 1 reduce = 5 calls. - expect(provider.calls).toHaveLength(5); - // Reduce input should mention only 2 of 3 chunks (chunk 2 skipped) — - // but the chunk indices in the reduce labels should reflect chunk 1 and 3, - // preserving chronological boundaries. - const reduceCall = provider.calls[4]; - expect(reduceCall.user).toContain("Chunk 1 of 2"); - expect(reduceCall.user).toContain("Chunk 2 of 2"); - expect(reduceCall.user).toContain("obs 1-100"); // first surviving chunk - expect(reduceCall.user).toContain("obs 201-250"); // third surviving chunk (was idx 2, range 201-250) - const stored: any = await kv.get("summaries", "ses_skip"); - expect(stored?.title).toBe("merged-with-skip"); - }); - - it("too many skipped chunks falls back to a local summary", async () => { - process.env.SUMMARIZE_CHUNK_SIZE = "100"; - process.env.SUMMARIZE_CHUNK_CONCURRENCY = "1"; - // 3 chunks, 2 fully broken → >50% skipped → bail. - const provider = makeProvider([ - summaryXml({ title: "ok1" }), - "", "", - "", "", - ]); - const { handler, kv } = await setupHandler({ - sessionId: "ses_too_broken", - obsCount: 250, - provider, - }); - - const result: any = await handler({ sessionId: "ses_too_broken" }); - - expect(result.success).toBe(true); - expect(result.fallback).toBe(true); - expect(result.reason).toMatch(/too_many_chunks_skipped: 2\/3/); - const stored: any = await kv.get("summaries", "ses_too_broken"); - expect(stored?.sessionId).toBe("ses_too_broken"); - expect(stored?.narrative).toContain("本地规则兜底"); - }); - - it("provider error on one chunk after retry is skipped, not propagated", async () => { - process.env.SUMMARIZE_CHUNK_SIZE = "100"; - process.env.SUMMARIZE_CHUNK_CONCURRENCY = "1"; - let i = 0; - const provider: MemoryProvider & { calls: any[] } = { - name: "test", - calls: [], - compress: async () => "", - summarize: async (system: string, user: string) => { - (provider as any).calls.push({ system, user }); - i += 1; - if (i === 1) return summaryXml({ title: "ok1" }); - // chunk 2: both attempts throw (e.g. provider 400) - if (i === 2 || i === 3) throw new Error("OpenAI API error (400): content rejected"); - if (i === 4) return summaryXml({ title: "ok3" }); - return summaryXml({ title: "merged-with-skip" }); - }, - }; - const { handler, kv } = await setupHandler({ - sessionId: "ses_net", - obsCount: 250, - provider, - }); - - const result: any = await handler({ sessionId: "ses_net" }); - - expect(result.success).toBe(true); - // 1 ok + 2 fail + 1 ok + 1 reduce = 5 calls. - expect((provider as any).calls.length).toBe(5); - const stored: any = await kv.get("summaries", "ses_net"); - expect(stored?.title).toBe("merged-with-skip"); - }); - - it("every chunk failing on provider error falls back to a local summary", async () => { - process.env.SUMMARIZE_CHUNK_SIZE = "100"; - process.env.SUMMARIZE_CHUNK_CONCURRENCY = "1"; - // 3 chunks, all chunk calls throw → 3/3 skipped → bail. - const provider: MemoryProvider & { calls: any[] } = { - name: "test", - calls: [], - compress: async () => "", - summarize: async (system: string, user: string) => { - (provider as any).calls.push({ system, user }); - throw new Error("OpenAI API error (400): invalid request"); - }, - }; - const { handler, kv } = await setupHandler({ - sessionId: "ses_all_400", - obsCount: 250, - provider, - }); - - const result: any = await handler({ sessionId: "ses_all_400" }); - - expect(result.success).toBe(true); - expect(result.fallback).toBe(true); - expect(result.reason).toMatch(/too_many_chunks_skipped: 3\/3/); - const stored: any = await kv.get("summaries", "ses_all_400"); - expect(stored?.sessionId).toBe("ses_all_400"); - expect(stored?.narrative).toContain("本地规则兜底"); - }); - - it("chunks run in parallel batches according to SUMMARIZE_CHUNK_CONCURRENCY", async () => { - process.env.SUMMARIZE_CHUNK_SIZE = "100"; - process.env.SUMMARIZE_CHUNK_CONCURRENCY = "2"; - let inflight = 0; - let maxInflight = 0; - const provider: MemoryProvider & { calls: any[] } = { - name: "test", - calls: [], - compress: async () => "", - summarize: async (system: string, user: string) => { - (provider as any).calls.push({ system, user }); - inflight += 1; - maxInflight = Math.max(maxInflight, inflight); - // Yield to event loop so siblings can also enter before we resolve. - await new Promise((r) => setTimeout(r, 5)); - inflight -= 1; - if (system.includes("merging")) return summaryXml({ title: "merged" }); - return summaryXml({ title: "ok" }); - }, - }; - const { handler } = await setupHandler({ - sessionId: "ses_par", - obsCount: 400, // 4 chunks at chunkSize=100 - provider, - }); - - const result: any = await handler({ sessionId: "ses_par" }); - - expect(result.success).toBe(true); - // 4 chunks at concurrency 2 → max 2 in flight at once during the chunk phase. - // Reduce is a single call so doesn't bump it. - expect(maxInflight).toBe(2); - }); -}); diff --git a/test/team.test.ts b/test/team.test.ts deleted file mode 100644 index b2aa1c69..00000000 --- a/test/team.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerTeamFunction } from "../src/functions/team.js"; -import type { Memory, TeamConfig, TeamSharedItem, TeamProfile } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -const teamConfig: TeamConfig = { - teamId: "test-team", - userId: "user-1", - mode: "shared" as const, -}; - -const testMemory: Memory = { - id: "mem_1", - createdAt: "2026-02-01T00:00:00Z", - updatedAt: "2026-02-01T00:00:00Z", - type: "pattern", - title: "Auth pattern", - content: "Always validate tokens", - concepts: ["auth", "security"], - files: ["src/auth.ts"], - sessionIds: ["ses_1"], - strength: 5, - version: 1, - isLatest: true, -}; - -describe("Team Functions", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(async () => { - sdk = mockSdk(); - kv = mockKV(); - registerTeamFunction(sdk as never, kv as never, teamConfig); - await kv.set("mem:memories", "mem_1", testMemory); - }); - - it("team-share stores item in team namespace", async () => { - const result = (await sdk.trigger("mem::team-share", { - itemId: "mem_1", - itemType: "memory", - })) as { success: boolean; sharedItem: TeamSharedItem }; - - expect(result.success).toBe(true); - expect(result.sharedItem.sharedBy).toBe("user-1"); - expect(result.sharedItem.type).toBe("memory"); - expect(result.sharedItem.visibility).toBe("shared"); - - const items = await kv.list("mem:team:test-team:shared"); - expect(items.length).toBe(1); - }); - - it("team-share fails for missing itemId", async () => { - const result = (await sdk.trigger("mem::team-share", { - itemType: "memory", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("required"); - }); - - it("team-feed returns shared items sorted by date", async () => { - await sdk.trigger("mem::team-share", { - itemId: "mem_1", - itemType: "memory", - }); - - const mem2: Memory = { ...testMemory, id: "mem_2", title: "Second pattern" }; - await kv.set("mem:memories", "mem_2", mem2); - - await new Promise((r) => setTimeout(r, 10)); - await sdk.trigger("mem::team-share", { - itemId: "mem_2", - itemType: "memory", - }); - - const result = (await sdk.trigger("mem::team-feed", {})) as { - items: TeamSharedItem[]; - total: number; - }; - - expect(result.items.length).toBe(2); - expect(result.total).toBe(2); - expect( - new Date(result.items[0].sharedAt).getTime(), - ).toBeGreaterThanOrEqual(new Date(result.items[1].sharedAt).getTime()); - }); - - it("team-profile aggregates concepts and files", async () => { - await sdk.trigger("mem::team-share", { - itemId: "mem_1", - itemType: "pattern", - }); - - const result = (await sdk.trigger("mem::team-profile", {})) as TeamProfile; - - expect(result.teamId).toBe("test-team"); - expect(result.members).toContain("user-1"); - expect(result.totalSharedItems).toBe(1); - expect(result.topConcepts.length).toBeGreaterThan(0); - expect(result.topConcepts.some((c) => c.concept === "auth")).toBe(true); - expect(result.topFiles.some((f) => f.file === "src/auth.ts")).toBe(true); - }); - - it("team-share fails when item not found in KV", async () => { - const result = (await sdk.trigger("mem::team-share", { - itemId: "nonexistent", - itemType: "memory", - })) as { success: boolean; error: string }; - - expect(result.success).toBe(false); - expect(result.error).toContain("not found"); - }); -}); diff --git a/test/temporal-graph.test.ts b/test/temporal-graph.test.ts deleted file mode 100644 index 55ef301d..00000000 --- a/test/temporal-graph.test.ts +++ /dev/null @@ -1,375 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; -import type { GraphNode, GraphEdge, MemoryProvider } from "../src/types.js"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -function mockKV( - nodes: GraphNode[] = [], - edges: GraphEdge[] = [], -) { - const store = new Map>(); - const nodesMap = new Map(); - for (const n of nodes) nodesMap.set(n.id, n); - store.set("mem:graph:nodes", nodesMap); - - const edgesMap = new Map(); - for (const e of edges) edgesMap.set(e.id, e); - store.set("mem:graph:edges", edgesMap); - - store.set("mem:graph:edge-history", new Map()); - - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, fn: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, fn); - }, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (fn) return fn(payload); - return null; - }, - }; -} - -describe("TemporalGraph", () => { - it("imports without errors", async () => { - const mod = await import("../src/functions/temporal-graph.js"); - expect(mod.registerTemporalGraphFunctions).toBeDefined(); - }); - - it("registers all three functions", async () => { - const { registerTemporalGraphFunctions } = await import( - "../src/functions/temporal-graph.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - const provider: MemoryProvider = { - name: "test", - compress: vi.fn().mockResolvedValue(""), - summarize: vi.fn().mockResolvedValue(""), - }; - - registerTemporalGraphFunctions(sdk as never, kv as never, provider); - - const fns = Array.from((sdk as any).trigger ? [] : []); - expect(sdk.trigger).toBeDefined(); - }); - - it("extracts temporal graph with context metadata", async () => { - const { registerTemporalGraphFunctions } = await import( - "../src/functions/temporal-graph.js" - ); - - const response = ` - - - engineer - - - tech - - - - - Alice joined Acme Corp as an engineer - positive - - -`; - - const provider: MemoryProvider = { - name: "test", - compress: vi.fn().mockResolvedValue(response), - summarize: vi.fn().mockResolvedValue(response), - }; - - const sdk = mockSdk(); - const kv = mockKV(); - registerTemporalGraphFunctions(sdk as never, kv as never, provider); - - const result = (await sdk.trigger("mem::temporal-graph-extract", { - observations: [ - { - id: "obs_1", - title: "Alice at Acme", - narrative: "Alice works at Acme Corp as an engineer", - concepts: ["career"], - files: [], - type: "conversation", - timestamp: "2024-01-01T00:00:00Z", - }, - ], - })) as { success: boolean; nodesAdded: number; edgesAdded: number }; - - expect(result.success).toBe(true); - expect(result.nodesAdded).toBe(2); - expect(result.edgesAdded).toBe(1); - - const storedEdges = await kv.list("mem:graph:edges"); - expect(storedEdges.length).toBe(1); - expect(storedEdges[0].tcommit).toBeDefined(); - expect(storedEdges[0].tvalid).toBe("2024-01-01"); - expect(storedEdges[0].context?.reasoning).toBe( - "Alice joined Acme Corp as an engineer", - ); - expect(storedEdges[0].context?.sentiment).toBe("positive"); - expect(storedEdges[0].isLatest).toBe(true); - expect(storedEdges[0].version).toBe(1); - }); - - it("appends new edge version instead of overwriting", async () => { - const { registerTemporalGraphFunctions } = await import( - "../src/functions/temporal-graph.js" - ); - - const existingNode: GraphNode = { - id: "gn_existing_alice", - type: "person", - name: "Alice", - properties: { role: "engineer" }, - sourceObservationIds: ["obs_0"], - createdAt: "2024-01-01T00:00:00Z", - }; - const existingNode2: GraphNode = { - id: "gn_existing_acme", - type: "organization", - name: "Acme Corp", - properties: {}, - sourceObservationIds: ["obs_0"], - createdAt: "2024-01-01T00:00:00Z", - }; - const existingEdge: GraphEdge = { - id: "ge_old", - type: "works_at" as any, - sourceNodeId: "gn_existing_alice", - targetNodeId: "gn_existing_acme", - weight: 0.9, - sourceObservationIds: ["obs_0"], - createdAt: "2024-01-01T00:00:00Z", - tcommit: "2024-01-01T00:00:00Z", - tvalid: "2024-01-01", - version: 1, - isLatest: true, - }; - - const response = ` - - - senior engineer - - - - - - - Alice was promoted to senior engineer - positive - - -`; - - const provider: MemoryProvider = { - name: "test", - compress: vi.fn().mockResolvedValue(response), - summarize: vi.fn().mockResolvedValue(response), - }; - - const sdk = mockSdk(); - const kv = mockKV([existingNode, existingNode2], [existingEdge]); - registerTemporalGraphFunctions(sdk as never, kv as never, provider); - - const result = (await sdk.trigger("mem::temporal-graph-extract", { - observations: [ - { - id: "obs_1", - title: "Alice promotion", - narrative: "Alice was promoted to senior engineer at Acme Corp", - concepts: [], - files: [], - type: "conversation", - timestamp: "2025-01-01T00:00:00Z", - }, - ], - })) as { success: boolean; nodesAdded: number; edgesAdded: number }; - - expect(result.success).toBe(true); - - const allEdges = await kv.list("mem:graph:edges"); - expect(allEdges.length).toBe(2); - - const oldEdge = allEdges.find((e) => e.id === "ge_old"); - expect(oldEdge?.isLatest).toBe(false); - expect(oldEdge?.tvalidEnd).toBeDefined(); - - const newEdge = allEdges.find((e) => e.id !== "ge_old"); - expect(newEdge?.isLatest).toBe(true); - expect(newEdge?.version).toBe(2); - expect(newEdge?.tvalid).toBe("2025-01-01"); - }); - - it("temporal query returns current state", async () => { - const { registerTemporalGraphFunctions } = await import( - "../src/functions/temporal-graph.js" - ); - - const node: GraphNode = { - id: "gn_1", - type: "person", - name: "Bob", - properties: {}, - sourceObservationIds: ["obs_1"], - createdAt: "2024-01-01T00:00:00Z", - }; - const edge1: GraphEdge = { - id: "ge_1", - type: "located_in" as any, - sourceNodeId: "gn_1", - targetNodeId: "gn_2", - weight: 0.9, - sourceObservationIds: ["obs_1"], - createdAt: "2023-01-01T00:00:00Z", - tcommit: "2023-01-01T00:00:00Z", - tvalid: "2023-01-01", - tvalidEnd: "2024-06-01", - version: 1, - isLatest: false, - }; - const edge2: GraphEdge = { - id: "ge_2", - type: "located_in" as any, - sourceNodeId: "gn_1", - targetNodeId: "gn_3", - weight: 0.9, - sourceObservationIds: ["obs_2"], - createdAt: "2024-06-01T00:00:00Z", - tcommit: "2024-06-01T00:00:00Z", - tvalid: "2024-06-01", - version: 2, - isLatest: true, - }; - - const sdk = mockSdk(); - const kv = mockKV([node], [edge1, edge2]); - const provider: MemoryProvider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn(), - }; - registerTemporalGraphFunctions(sdk as never, kv as never, provider); - - const result = (await sdk.trigger("mem::temporal-query", { - entityName: "Bob", - })) as any; - - expect(result.entity).toBeDefined(); - expect(result.entity.name).toBe("Bob"); - expect(result.currentEdges.length).toBe(1); - expect(result.currentEdges[0].id).toBe("ge_2"); - }); - - it("temporal query with asOf returns historical state", async () => { - const { registerTemporalGraphFunctions } = await import( - "../src/functions/temporal-graph.js" - ); - - const node: GraphNode = { - id: "gn_1", - type: "person", - name: "Charlie", - properties: {}, - sourceObservationIds: ["obs_1"], - createdAt: "2023-01-01T00:00:00Z", - }; - const edge1: GraphEdge = { - id: "ge_1", - type: "located_in" as any, - sourceNodeId: "gn_1", - targetNodeId: "gn_nyc", - weight: 0.9, - sourceObservationIds: ["obs_1"], - createdAt: "2023-01-01T00:00:00Z", - tcommit: "2023-01-01T00:00:00Z", - tvalid: "2023-01-01", - tvalidEnd: "2024-06-01", - version: 1, - isLatest: false, - }; - const edge2: GraphEdge = { - id: "ge_2", - type: "located_in" as any, - sourceNodeId: "gn_1", - targetNodeId: "gn_london", - weight: 0.9, - sourceObservationIds: ["obs_2"], - createdAt: "2024-06-01T00:00:00Z", - tcommit: "2024-06-01T00:00:00Z", - tvalid: "2024-06-01", - version: 2, - isLatest: true, - }; - - const sdk = mockSdk(); - const kv = mockKV([node], [edge1, edge2]); - const provider: MemoryProvider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn(), - }; - registerTemporalGraphFunctions(sdk as never, kv as never, provider); - - const result = (await sdk.trigger("mem::temporal-query", { - entityName: "Charlie", - asOf: "2023-06-01T00:00:00Z", - })) as any; - - expect(result.entity.name).toBe("Charlie"); - expect(result.currentEdges.length).toBe(1); - expect(result.currentEdges[0].targetNodeId).toBe("gn_nyc"); - }); - - it("handles empty observations gracefully", async () => { - const { registerTemporalGraphFunctions } = await import( - "../src/functions/temporal-graph.js" - ); - const sdk = mockSdk(); - const kv = mockKV(); - const provider: MemoryProvider = { - name: "test", - compress: vi.fn(), - summarize: vi.fn(), - }; - registerTemporalGraphFunctions(sdk as never, kv as never, provider); - - const result = (await sdk.trigger("mem::temporal-graph-extract", { - observations: [], - })) as any; - - expect(result.success).toBe(false); - expect(result.error).toBe("No observations provided"); - }); -}); diff --git a/test/timeline.test.ts b/test/timeline.test.ts deleted file mode 100644 index 22273bc2..00000000 --- a/test/timeline.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerTimelineFunction } from "../src/functions/timeline.js"; -import type { CompressedObservation, Session, TimelineEntry } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => { - return (store.get(scope)?.get(key) as T) ?? null; - }, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (idOrOpts: string | { id: string }, handler: Function) => { - const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id; - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => { - const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id; - const payload = typeof idOrInput === "string" ? data : idOrInput.payload; - const fn = functions.get(id); - if (!fn) throw new Error(`No function: ${id}`); - return fn(payload); - }, - }; -} - -function makeObs( - id: string, - timestamp: string, - title: string, -): CompressedObservation { - return { - id, - sessionId: "ses_1", - timestamp, - type: "file_edit", - title, - facts: [], - narrative: title, - concepts: [], - files: [], - importance: 5, - }; -} - -describe("Timeline Function", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(async () => { - sdk = mockSdk(); - kv = mockKV(); - registerTimelineFunction(sdk as never, kv as never); - - const session: Session = { - id: "ses_1", - project: "my-project", - cwd: "/tmp", - startedAt: "2026-02-01T00:00:00Z", - status: "completed", - observationCount: 5, - }; - await kv.set("mem:sessions", "ses_1", session); - - await kv.set("mem:obs:ses_1", "obs_1", makeObs("obs_1", "2026-02-01T10:00:00Z", "First edit")); - await kv.set("mem:obs:ses_1", "obs_2", makeObs("obs_2", "2026-02-01T11:00:00Z", "Second edit")); - await kv.set("mem:obs:ses_1", "obs_3", makeObs("obs_3", "2026-02-01T12:00:00Z", "Third edit")); - await kv.set("mem:obs:ses_1", "obs_4", makeObs("obs_4", "2026-02-01T13:00:00Z", "Fourth edit")); - await kv.set("mem:obs:ses_1", "obs_5", makeObs("obs_5", "2026-02-01T14:00:00Z", "Fifth edit")); - }); - - it("anchors by ISO date and returns surrounding observations", async () => { - const result = (await sdk.trigger("mem::timeline", { - anchor: "2026-02-01T12:00:00Z", - before: 2, - after: 2, - })) as { entries: TimelineEntry[] }; - - expect(result.entries.length).toBe(5); - expect(result.entries[0].observation.id).toBe("obs_1"); - expect(result.entries[4].observation.id).toBe("obs_5"); - }); - - it("relativePosition is correct relative to anchor", async () => { - const result = (await sdk.trigger("mem::timeline", { - anchor: "2026-02-01T12:00:00Z", - before: 2, - after: 2, - })) as { entries: TimelineEntry[] }; - - const positions = result.entries.map((e) => e.relativePosition); - expect(positions).toEqual([-2, -1, 0, 1, 2]); - }); - - it("respects before and after limits", async () => { - const result = (await sdk.trigger("mem::timeline", { - anchor: "2026-02-01T12:00:00Z", - before: 1, - after: 1, - })) as { entries: TimelineEntry[] }; - - expect(result.entries.length).toBe(3); - expect(result.entries[0].observation.id).toBe("obs_2"); - expect(result.entries[2].observation.id).toBe("obs_4"); - }); - - it("returns empty entries when no sessions exist for project", async () => { - const result = (await sdk.trigger("mem::timeline", { - anchor: "2026-02-01T12:00:00Z", - project: "nonexistent-project", - })) as { entries: TimelineEntry[] }; - - expect(result.entries.length).toBe(0); - }); - - it("handles keyword anchor by finding matching observation", async () => { - const result = (await sdk.trigger("mem::timeline", { - anchor: "Third", - before: 1, - after: 1, - })) as { entries: TimelineEntry[] }; - - expect(result.entries.length).toBe(3); - const titles = result.entries.map((e) => e.observation.title); - expect(titles).toContain("Third edit"); - }); -}); diff --git a/test/todo-extract.test.ts b/test/todo-extract.test.ts deleted file mode 100644 index d7bd96c6..00000000 --- a/test/todo-extract.test.ts +++ /dev/null @@ -1,1549 +0,0 @@ -import { readFileSync } from "node:fs"; -import { join } from "node:path"; -import { describe, expect, it, beforeEach, afterEach, vi } from "vitest"; - -vi.mock("../src/config.js", () => ({ - DEFAULT_LANGEXTRACT_BASE_URL: "https://api.novita.ai/openai/v1", - DEFAULT_TODO_EXTRACT_TIMEOUT_MS: 120_000, - DEFAULT_TODO_EXTRACT_SINCE_DAYS: 7, - DEFAULT_TODO_EXTRACT_MAX_INTERACTIONS: 10, - DEFAULT_TODO_EXTRACT_MAX_SESSIONS: 8, - getEnvVar: (key: string) => { - const values: Record = { - AGENTMEMORY_TODO_EXTRACTOR: "rules", - AGENTMEMORY_TODO_DIRECT_CONFIDENCE: "0.6", - AGENTMEMORY_TODO_REVIEW_CONFIDENCE: "0.55", - // Wide default window so existing time-agnostic tests are never excluded by - // the STEP-11 sinceDays filter; scope tests set process.env explicitly. - AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS: "3650", - }; - return process.env[key] ?? values[key]; - }, - normalizeTodoExtractorModel: (value?: string) => value || "deepseek/deepseek-v4-flash", - normalizeTodoExtractorProvider: (value?: string) => (value || "openai").toLowerCase(), -})); - -import { cleanPollutedTodoCards, updateChangedTodoCards, cleanTodoTitle, generateTodosFromSessions, refreshTodoAction, validateTodoEvidence, runLangExtractSidecar, type ExtractedTodo } from "../src/functions/todo-extract.js"; -import type { Action, CompressedObservation, ReviewQueueItem, ScanCheckpoint, Session } from "../src/types.js"; -import { KV } from "../src/state/schema.js"; -import { mockKV } from "./helpers/mocks.js"; - -const ROOT = join(import.meta.dirname, ".."); - -function session(patch: Partial = {}): Session { - return { - id: "ses_1", - project: "agentmemory-lab", - cwd: "/repo", - startedAt: "2026-06-17T08:00:00.000Z", - endedAt: "2026-06-17T09:00:00.000Z", - status: "completed", - observationCount: 1, - ...patch, - }; -} - -function obs(patch: Partial = {}): CompressedObservation { - return { - id: "obs_1", - sessionId: "ses_1", - timestamp: "2026-06-17T08:10:00.000Z", - type: "conversation", - title: "assistant", - subtitle: "", - facts: [], - narrative: "下一步请修复 CI 失败,并重新跑测试。", - concepts: [], - files: [], - importance: 5, - ...patch, - }; -} - -function generatedAction(patch: Partial = {}): Action { - return { - id: "act_old", - title: "旧卡片标题", - description: "旧卡片说明", - status: "pending", - priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", - updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", - tags: ["todo-extracted", "type:follow_up"], - sourceObservationIds: ["obs_1"], - sourceMemoryIds: [], - metadata: { - todoExtraction: { - title: "旧卡片标题", - description: "旧卡片说明", - confidence: 0.72, - timeBucket: "current", - typeBucket: "follow_up", - sourceSessionId: "ses_1", - sourceCheckpoint: "2026-06-17T09:00:00.000Z:1", - evidence: { - sourceObservationId: "obs_1", - quote: "旧卡片说明", - }, - dedupeKey: "old-card-title", - }, - }, - ...patch, - }; -} - -function jsonText(value: unknown): string { - return JSON.stringify(value).replace(/\s+/g, " "); -} - -describe("todo extraction", () => { - let kv: ReturnType; - - beforeEach(() => { - kv = mockKV(); - }); - - it("falls back to rules and directly creates high-confidence todos with evidence metadata", async () => { - await kv.set(KV.sessions, "ses_1", session({ status: "active" })); - await kv.set(KV.observations("ses_1"), "obs_1", obs()); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - - expect(result.llmFallback).toBeUndefined(); - expect(result.directCreated).toBe(1); - expect(result.reviewCreated).toBe(0); - const actions = await kv.list(KV.actions); - expect(actions).toHaveLength(1); - expect(actions[0]).toMatchObject({ - status: "pending", - project: "agentmemory-lab", - tags: expect.arrayContaining(["todo-extracted", "time:current", "type:follow_up"]), - sourceObservationIds: ["obs_1"], - }); - expect(actions[0].metadata?.todoExtraction).toMatchObject({ - sourceSessionId: "ses_1", - sourceCheckpoint: "2026-06-17T09:00:00.000Z:1", - evidence: { sourceObservationId: "obs_1" }, - }); - }); - - it("extracts English unresolved todos but ignores completed English summaries", async () => { - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 2 })); - await kv.set(KV.observations("ses_1"), "obs_done", obs({ - id: "obs_done", - narrative: "Tests passed and the PR was merged. No action needed.", - })); - await kv.set(KV.observations("ses_1"), "obs_real", obs({ - id: "obs_real", - narrative: "Need to fix the failing CI and rerun tests.", - })); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - - expect(result.scannedObservations).toBe(1); - expect(result.directCreated).toBe(1); - expect((await kv.list(KV.actions))[0]).toMatchObject({ - sourceObservationIds: ["obs_real"], - tags: expect.arrayContaining(["type:follow_up"]), - }); - }); - - it("does not turn bare failure reports into rule todos", async () => { - await kv.set(KV.sessions, "ses_1", session({ status: "active" })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ - narrative: "Command failed with exit code 1.", - })); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - - expect(result.scannedObservations).toBe(0); - expect(result.directCreated).toBe(0); - expect(await kv.list(KV.actions)).toHaveLength(0); - }); - - it("skips LangExtract when session prefilter finds no candidate blocks", async () => { - process.env.AGENTMEMORY_TODO_EXTRACTOR = "langextract"; - await kv.set(KV.sessions, "ses_1", session({ status: "active" })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ - narrative: "Tests passed and the PR was merged. No action needed.", - })); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - - expect(result.scannedObservations).toBe(0); - expect(result.directCreated).toBe(0); - expect(result.llmFallback).toBeUndefined(); - delete process.env.AGENTMEMORY_TODO_EXTRACTOR; - }); - - it("uses scan checkpoints to skip unchanged sessions", async () => { - process.env.AGENTMEMORY_TODO_EXTRACTOR = "langextract"; - await kv.set(KV.sessions, "ses_1", session()); - await kv.set(KV.observations("ses_1"), "obs_1", obs()); - - await generateTodosFromSessions(kv as never, { - force: true, - scanSources: false, - runLangExtractSidecar: async () => [{ - title: "修复 CI 失败并重新跑测试", - description: "下一步请修复 CI 失败,并重新跑测试。", - confidence: 0.95, - timeBucket: "current", - typeBucket: "follow_up", - sourceSessionId: "ses_1", - evidence: { sourceObservationId: "obs_1", quote: "下一步请修复 CI 失败,并重新跑测试。" }, - dedupeKey: "fix-ci", - }], - }); - const second = await generateTodosFromSessions(kv as never, { scanSources: false }); - delete process.env.AGENTMEMORY_TODO_EXTRACTOR; - - expect(second.scannedObservations).toBe(0); - expect(second.directCreated).toBe(0); - expect(await kv.list(KV.actions)).toHaveLength(1); - }); - - it("does not write scan checkpoints when LangExtract falls back to rules", async () => { - process.env.AGENTMEMORY_TODO_EXTRACTOR = "langextract"; - await kv.set(KV.sessions, "ses_1", session()); - await kv.set(KV.observations("ses_1"), "obs_1", obs()); - - const first = await generateTodosFromSessions(kv as never, { - force: true, - scanSources: false, - runLangExtractSidecar: async () => { - throw new Error("langextract unavailable: No module named 'langextract'"); - }, - }); - expect(await kv.get(KV.scanCheckpoints, "todo-extract:all")).toBeNull(); - const second = await generateTodosFromSessions(kv as never, { - scanSources: false, - runLangExtractSidecar: async () => [], - }); - delete process.env.AGENTMEMORY_TODO_EXTRACTOR; - - expect(first).toMatchObject({ engine: "rules", llmFallback: true }); - expect(second.scannedObservations).toBe(1); - }); - - it("ignores legacy untagged scan checkpoints before LangExtract runs", async () => { - process.env.AGENTMEMORY_TODO_EXTRACTOR = "langextract"; - await kv.set(KV.sessions, "ses_1", session()); - await kv.set(KV.observations("ses_1"), "obs_1", obs()); - await kv.set(KV.scanCheckpoints, "todo-extract:all", { - sourceId: "todo-extract:all", - cursor: JSON.stringify({ ses_1: "2026-06-17T09:00:00.000Z:1" }), - }); - - const result = await generateTodosFromSessions(kv as never, { - scanSources: false, - runLangExtractSidecar: async () => [], - }); - delete process.env.AGENTMEMORY_TODO_EXTRACTOR; - - expect(result.scannedObservations).toBe(1); - }); - - it("discards medium-confidence rule todos instead of sending them to review", async () => { - await kv.set(KV.sessions, "ses_1", session()); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ narrative: "下一步请修复 CI 失败,并重新跑测试。" })); - - process.env.AGENTMEMORY_TODO_DIRECT_CONFIDENCE = "0.8"; - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - delete process.env.AGENTMEMORY_TODO_DIRECT_CONFIDENCE; - - expect(result.directCreated).toBe(0); - expect(result.reviewCreated).toBe(0); - expect(result.discarded).toBe(1); - expect(await kv.list(KV.reviewQueue)).toHaveLength(0); - }); - - it("discards vague process-like rule todos instead of creating review candidates", async () => { - await kv.set(KV.sessions, "ses_1", session()); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ - narrative: "TODO: 重启 Codex desktop app 后再测一次。", - })); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - - expect(result.directCreated).toBe(0); - expect(result.reviewCreated).toBe(0); - expect(result.discarded).toBe(1); - expect(await kv.list(KV.actions)).toHaveLength(0); - expect(await kv.list(KV.reviewQueue)).toHaveLength(0); - }); - - it("does not persist pure agent process narration as todos", async () => { - await kv.set(KV.sessions, "ses_1", session({ status: "active" })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ - narrative: "TODO: 做最后一次状态确认,确保工作区干净、当前分支和 PR 链接明确。", - })); - await kv.set(KV.observations("ses_1"), "obs_2", obs({ - id: "obs_2", - narrative: "TODO: 启动后做健康检查,确认 Viewer 和 Health 都正常。", - })); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - - expect(result.scannedObservations).toBe(0); - expect(result.directCreated).toBe(0); - expect(result.reviewCreated).toBe(0); - expect(await kv.list(KV.actions)).toHaveLength(0); - expect(await kv.list(KV.reviewQueue)).toHaveLength(0); - }); - - it("uses session task chains so continue/retry updates the prior user task instead of creating a new card", async () => { - process.env.AGENTMEMORY_TODO_EXTRACTOR = "langextract"; - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 4 })); - await kv.set(KV.observations("ses_1"), "u1", obs({ - id: "u1", - title: "prompt_submit", - narrative: "帮我把全量更新和单卡更新按钮的问题修掉。", - timestamp: "2026-06-17T08:00:00.000Z", - })); - await kv.set(KV.observations("ses_1"), "a1", obs({ - id: "a1", - title: "stop", - narrative: "全量更新会秒变已更新,单卡更新会长时间后提示没有更好卡片,后续需要调试按钮状态链路。", - timestamp: "2026-06-17T08:01:00.000Z", - })); - await kv.set(KV.observations("ses_1"), "u2", obs({ - id: "u2", - title: "prompt_submit", - narrative: "继续", - timestamp: "2026-06-17T08:02:00.000Z", - })); - await kv.set(KV.observations("ses_1"), "a2", obs({ - id: "a2", - title: "stop", - narrative: "已定位到全量更新直接返回、单卡刷新没有走 LLM;下一步需要修 sidecar 空负例。", - timestamp: "2026-06-17T08:03:00.000Z", - })); - - const seen: any[] = []; - const result = await generateTodosFromSessions(kv as never, { - force: true, - scanSources: false, - cleanup: "none", - runLangExtractSidecar: async (input: any) => { - seen.push(input); - return [{ - title: "修复全量更新和单卡更新按钮问题", - description: "下一步需要修 sidecar 空负例。", - confidence: 0.9, - timeBucket: "current", - typeBucket: "follow_up", - sourceSessionId: "ses_1", - evidence: { sourceObservationId: "a2", quote: "下一步需要修 sidecar 空负例" }, - dedupeKey: "fix-update-buttons", - }]; - }, - } as never); - delete process.env.AGENTMEMORY_TODO_EXTRACTOR; - - expect(result.directCreated).toBe(1); - expect(seen[0].taskChains).toHaveLength(1); - expect(seen[0].taskChains[0]).toMatchObject({ - userObservationId: "u1", - latestStatusObservationId: "a2", - continuedByObservationIds: ["u2"], - }); - const action = (await kv.list(KV.actions))[0]; - expect(action.title).toBe("修复全量更新和单卡更新按钮问题"); - expect(action.metadata?.todoChain).toMatchObject({ - sourceSessionId: "ses_1", - userObservationId: "u1", - latestStatusObservationId: "a2", - completionState: "in_progress", - }); - }); - - it("skips completed task chains with no next step", async () => { - process.env.AGENTMEMORY_TODO_EXTRACTOR = "langextract"; - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 2 })); - await kv.set(KV.observations("ses_1"), "u1", obs({ - id: "u1", - title: "prompt_submit", - narrative: "把修改提交 PR。", - timestamp: "2026-06-17T08:00:00.000Z", - })); - await kv.set(KV.observations("ses_1"), "a1", obs({ - id: "a1", - title: "stop", - narrative: "PR 已创建,CI 已通过,无需后续处理。", - timestamp: "2026-06-17T08:01:00.000Z", - })); - - const result = await generateTodosFromSessions(kv as never, { - force: true, - scanSources: false, - cleanup: "none", - runLangExtractSidecar: async () => [{ - title: "提交 PR", - description: "PR 已创建,CI 已通过。", - confidence: 0.95, - timeBucket: "current", - typeBucket: "done", - sourceSessionId: "ses_1", - evidence: { sourceObservationId: "a1", quote: "PR 已创建,CI 已通过" }, - dedupeKey: "submit-pr", - }], - } as never); - delete process.env.AGENTMEMORY_TODO_EXTRACTOR; - - expect(result.directCreated).toBe(0); - expect(result.discarded).toBe(1); - expect(await kv.list(KV.actions)).toHaveLength(0); - }); - - it("keeps history todos hidden instead of writing them to actions", async () => { - await kv.set(KV.sessions, "ses_1", session({ - startedAt: "2026-05-01T08:00:00.000Z", - endedAt: "2026-05-01T09:00:00.000Z", - })); - await kv.set(KV.observations("ses_1"), "obs_1", obs()); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - - expect(result.hiddenHistory).toBe(1); - expect(await kv.list(KV.actions)).toHaveLength(0); - const reviews = await kv.list(KV.reviewQueue); - expect(reviews[0]).toMatchObject({ - status: "dismissed", - payload: { - hiddenHistory: true, - todoExtraction: expect.objectContaining({ timeBucket: "history" }), - }, - }); - }); - - it("marks extracted actions for recheck when the source session changes", async () => { - await kv.set(KV.sessions, "ses_1", session({ observationCount: 2 })); - await kv.set(KV.actions, "act_1", { - id: "act_1", - title: "整理待办", - description: "整理待办", - status: "pending", - priority: 5, - createdAt: "2026-06-17T09:00:00.000Z", - updatedAt: "2026-06-17T09:00:00.000Z", - createdBy: "todo-extract", - tags: ["todo-extracted", "time:current", "type:to_start"], - sourceObservationIds: ["obs_1"], - sourceMemoryIds: [], - metadata: { - todoExtraction: { - sourceSessionId: "ses_1", - sourceCheckpoint: "2026-06-17T09:00:00.000Z:1", - }, - }, - }); - - const result = await generateTodosFromSessions(kv as never, { scanSources: false }); - - expect(result.recheckMarked).toBe(1); - const actions = await kv.list(KV.actions); - expect(actions[0].tags).toContain("todo-recheck"); - expect(actions[0].metadata?.todoExtraction).toMatchObject({ - needsRecheck: true, - latestSourceCheckpoint: "2026-06-17T09:00:00.000Z:2", - }); - }); - - it("suppresses todos that near-duplicate an existing open action but not a done one (STEP-08 PR4)", async () => { - const seedExisting = async (status: Action["status"]) => { - await kv.set(KV.actions, "act_seed", { - id: "act_seed", - title: "克隆上游项目到子目录中", - description: "克隆上游项目到子目录中", - status, - priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", - updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "test", - tags: [], - sourceObservationIds: [], - sourceMemoryIds: [], - }); - }; - - // Open action present → the near-dup todo is suppressed. - await seedExisting("pending"); - await kv.set(KV.sessions, "ses_1", session({ status: "active" })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ narrative: "TODO: 克隆上游项目到子目录。" })); - const suppressed = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - expect(suppressed.directCreated).toBe(0); - expect((await kv.list(KV.actions)).map((a) => a.id)).toEqual(["act_seed"]); - - // Same title but the existing action is done → the work may have regressed, - // so the todo is created again. - kv = mockKV(); - await seedExisting("done"); - await kv.set(KV.sessions, "ses_1", session({ status: "active" })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ narrative: "TODO: 克隆上游项目到子目录。" })); - const created = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - expect(created.directCreated).toBe(1); - }); - - it("rejects extracted todos when evidence quote is not grounded", () => { - expect(validateTodoEvidence({ - title: "修复不存在的问题", - description: "修复不存在的问题", - confidence: 0.95, - timeBucket: "current", - typeBucket: "pending", - sourceSessionId: "ses_1", - evidence: { sourceObservationId: "obs_1", quote: "不存在的 quote" }, - dedupeKey: "bad-evidence", - }, new Map([["obs_1", { text: "普通总结,没有行动。" }]]))).toBe(false); - }); - - it("requires the evidence quote to be grounded IN the observation, not merely to contain it (STEP-08)", () => { - const base = { - title: "修复 CI 失败", - description: "修复 CI 失败", - confidence: 0.95, - timeBucket: "current" as const, - typeBucket: "pending" as const, - sourceSessionId: "ses_1", - dedupeKey: "k", - }; - const blocks = new Map([["obs_1", { text: "后续需要修复 CI 失败。" }]]); - // grounded: the quote is a substring of the observation - expect(validateTodoEvidence({ ...base, evidence: { sourceObservationId: "obs_1", quote: "修复 CI 失败" } }, blocks)).toBe(true); - // ungrounded: the quote wraps the whole observation + hallucinated extra — - // it CONTAINS the block text but is not grounded in it, so must be rejected - expect(validateTodoEvidence({ - ...base, - evidence: { sourceObservationId: "obs_1", quote: "后续需要修复 CI 失败。还要重写整个部署流水线。" }, - }, blocks)).toBe(false); - }); - - it("cleans bad tool-log titles before writing todos", async () => { - const cleaned = cleanTodoTitle( - "langextract-demo/...`", - "因为用户明确要截图,我会读取截图专项说明,然后保存到 `/tmp/ai-todo-langextract-demo/...`。", - ); - expect(cleaned).toContain("读取截图专项说明"); - expect(cleaned).not.toContain("langextract-demo"); - expect(cleaned).not.toContain("保存到"); - expect(cleanTodoTitle( - "{\"cmd\":\"gh pr list --json number,title\"}", - "{\"cmd\":\"gh pr list --json number,title\"}", - )).toBeNull(); - }); - - it("compacts long assistant-progress sentences into readable card titles", () => { - const cleaned = cleanTodoTitle( - "我会再等一轮;若仍未完成,我会中断这次安装,转用仓库结构和脚本级检查继续评估,不让验证步骤卡住整体结论", - "npm install 长时间无输出;计划再等待一轮,如仍未完成则中断安装,改用仓库结构和脚本级检查继续评估。", - ); - expect(cleaned).toBe("再等一轮"); - }); - - it("rejects process-check titles that are not durable user tasks", () => { - expect(cleanTodoTitle( - "做最后一次状态确认", - "我会做最后一次状态确认,确保工作区干净、当前分支和 PR 链接明确。", - )).toBeNull(); - expect(cleanTodoTitle( - "启动后做健康检查", - "启动后我会做健康检查,确认 Viewer 和 Health 都正常。", - )).toBeNull(); - expect(cleanTodoTitle( - "修复深色模式按钮对比度", - "修复深色模式按钮对比度,避免主操作在暗色背景下不可读。", - )).toBe("修复深色模式按钮对比度"); - }); - - it("anti-truncation: skips fragment titles and trims on a boundary (STEP-08)", () => { - // HTTP-status cut "返回 4" is a truncation fragment → fall through to the - // clean description rather than emitting it as a card title. - expect(cleanTodoTitle("返回 4", "修复老路由返回 404 的问题。")).toBe( - "修复老路由返回 404 的问题", - ); - // a list cut to "…、/he" is a fragment → fall through. - expect(cleanTodoTitle("老的 /actions、/sessions、/he", "修复老路由 404。")).toBe( - "修复老路由 404", - ); - // a long no-comma title trims to <=42 chars without a dangling boundary char - // and without splitting a word. - const t = cleanTodoTitle( - "investigate the failing classification boundary detector here now please", - "", - ); - expect(t).toBeTruthy(); - expect(Array.from(t!).length).toBeLessThanOrEqual(42); - expect(t).not.toMatch(/[,,;;、\s]$/u); - }); - - it("sidecar failures are explicit so auto mode can fall back", async () => { - process.env.LANGEXTRACT_PYTHON = "__missing_python__"; - await expect(runLangExtractSidecar({ blocks: [{ text: "后续需要修复 CI。", sourceObservationId: "obs_1" }] }, { timeoutMs: 500 })) - .rejects.toBeTruthy(); - delete process.env.LANGEXTRACT_PYTHON; - }); - - it("sidecar examples avoid empty extraction groups that break LangExtract alignment", () => { - const sidecar = readFileSync(join(ROOT, "src/functions/todo-extract-langextract.py"), "utf-8"); - expect(sidecar).not.toContain("extractions=[],"); - expect(sidecar).toContain("DO NOT extract completed work"); - }); - - it("reports when auto mode fell back from LangExtract to rules", async () => { - process.env.AGENTMEMORY_TODO_EXTRACTOR = "auto"; - process.env.LANGEXTRACT_PYTHON = "__missing_python__"; - await kv.set(KV.sessions, "ses_1", session()); - await kv.set(KV.observations("ses_1"), "obs_1", obs()); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - - expect(result.engine).toBe("rules"); - expect(result.llmFallback).toBe(true); - expect(result.fallbackReason).toBeTruthy(); - delete process.env.AGENTMEMORY_TODO_EXTRACTOR; - delete process.env.LANGEXTRACT_PYTHON; - }); - - it("refreshes one generated todo card from nearby source context", async () => { - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 3 })); - await kv.set(KV.observations("ses_1"), "obs_0", obs({ - id: "obs_0", - title: "prompt_submit", - narrative: "请检查单卡刷新。", - timestamp: "2026-06-17T08:00:00.000Z", - })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ - id: "obs_1", - narrative: "这里只是旧卡片来源,没有明确新待办。", - timestamp: "2026-06-17T08:05:00.000Z", - })); - await kv.set(KV.observations("ses_1"), "obs_2", obs({ - id: "obs_2", - narrative: "下一步请修复单卡刷新按钮状态,并重新跑 viewer 测试。", - timestamp: "2026-06-17T08:06:00.000Z", - })); - await kv.set(KV.actions, "act_old", generatedAction()); - - const result = await refreshTodoAction(kv as never, { actionId: "act_old" }); - - expect(result).toMatchObject({ - success: true, - keptOld: false, - reason: "replaced", - engine: "rules", - scannedObservations: 1, - }); - expect(result.action).toMatchObject({ - id: "act_old", - createdAt: "2026-06-17T08:00:00.000Z", - title: expect.stringContaining("单卡刷新按钮"), - sourceObservationIds: ["obs_2"], - }); - const stored = await kv.get(KV.actions, "act_old"); - expect(stored?.title).toContain("单卡刷新按钮"); - expect(stored?.metadata?.refresh).toMatchObject({ - previousTitle: "旧卡片标题", - reason: "replaced", - }); - }); - - it("refreshes from recent interaction context when the source observation is missing", async () => { - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 2 })); - await kv.set(KV.observations("ses_1"), "obs_recent", obs({ - id: "obs_recent", - narrative: "下一步请修复刷新回退路径,并补充回归测试。", - timestamp: "2026-06-17T08:30:00.000Z", - })); - await kv.set(KV.actions, "act_old", generatedAction({ - sourceObservationIds: ["obs_missing"], - metadata: { - todoExtraction: { - sourceSessionId: "ses_1", - evidence: { sourceObservationId: "obs_missing", quote: "旧来源" }, - dedupeKey: "old-card-title", - }, - }, - })); - - const result = await refreshTodoAction(kv as never, { actionId: "act_old" }); - - expect(result).toMatchObject({ success: true, keptOld: false, reason: "replaced" }); - expect(result.action?.sourceObservationIds).toEqual(["obs_recent"]); - expect(result.action?.title).toContain("刷新回退路径"); - }); - - it("keeps the old card for medium-confidence single-card refresh results", async () => { - process.env.AGENTMEMORY_TODO_DIRECT_CONFIDENCE = "0.8"; - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 1 })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ - narrative: "下一步请修复单卡刷新低置信度路径。", - })); - await kv.set(KV.actions, "act_old", generatedAction()); - - const result = await refreshTodoAction(kv as never, { actionId: "act_old" }); - delete process.env.AGENTMEMORY_TODO_DIRECT_CONFIDENCE; - - expect(result).toMatchObject({ - success: true, - keptOld: true, - reason: "low-confidence", - }); - expect((await kv.get(KV.actions, "act_old"))?.title).toBe("旧卡片标题"); - expect(await kv.list(KV.reviewQueue)).toHaveLength(0); - }); - - it("keeps the old card when single-card refresh finds no valid todo", async () => { - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 1 })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ - narrative: "Tests passed and the PR was merged. No action needed.", - })); - await kv.set(KV.actions, "act_old", generatedAction()); - - const result = await refreshTodoAction(kv as never, { actionId: "act_old" }); - - expect(result).toMatchObject({ - success: true, - keptOld: true, - reason: "no-valid-todo", - scannedObservations: 0, - }); - expect((await kv.get(KV.actions, "act_old"))?.title).toBe("旧卡片标题"); - }); - - it("keeps strong multi-step delivery titles that remain readable", async () => { - await kv.set(KV.sessions, "ses_1", session({ status: "active" })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ - narrative: "下一步需要修正目录显示文字(去掉重复编号)并更新页码缓存后重渲染。", - })); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - - expect(result.directCreated).toBe(1); - expect((await kv.list(KV.actions))[0]?.title).toBe("修正目录显示文字(去掉重复编号)并更新页码缓存后重渲染"); - }); - - it("compacts long technical identifiers out of generated card titles", async () => { - const quote = "下一步需要推送 codex/todo-cleanup-flash-model 分支到远程仓库。"; - await kv.set(KV.sessions, "ses_1", session({ status: "active" })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ narrative: quote })); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - - expect(result.directCreated).toBe(1); - const action = (await kv.list(KV.actions))[0]; - expect(action.title).toBe("推送当前工作分支到远程仓库"); - expect(action.title).not.toContain("codex/todo-cleanup-flash-model"); - expect(action.description).toContain("codex/todo-cleanup-flash-model"); - expect(jsonText(action.metadata?.todoQuality)).toContain("titleCompacted"); - }); - - it("rejects incomplete dangling titles from single-card refresh with a specific reason", async () => { - process.env.AGENTMEMORY_TODO_EXTRACTOR = "langextract"; - const quote = "准备推送分支 codex/todo-cleanup-flash-model 到"; - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 1 })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ narrative: quote })); - await kv.set(KV.actions, "act_old", generatedAction({ - title: quote, - description: quote, - metadata: { - todoExtraction: { - sourceSessionId: "ses_1", - evidence: { sourceObservationId: "obs_1", quote }, - dedupeKey: "push-branch-fragment", - }, - }, - })); - - const result = await (refreshTodoAction as unknown as ( - kvArg: typeof kv, - data: { actionId: string }, - deps: { runLangExtractSidecar: (input: Record) => Promise }, - ) => ReturnType)(kv, { actionId: "act_old" }, { - runLangExtractSidecar: async () => [{ - title: quote, - description: quote, - confidence: 0.99, - timeBucket: "current", - typeBucket: "to_start", - sourceSessionId: "ses_1", - evidence: { sourceObservationId: "obs_1", quote }, - dedupeKey: "push-branch-fragment", - }], - }); - delete process.env.AGENTMEMORY_TODO_EXTRACTOR; - - expect(result).toMatchObject({ - success: true, - keptOld: true, - reason: "incomplete-title", - engine: "langextract", - }); - expect((await kv.get(KV.actions, "act_old"))?.title).toBe(quote); - }); - - it("repairs a dangling single-card title from existing evidence when LangExtract returns no todo", async () => { - process.env.AGENTMEMORY_TODO_EXTRACTOR = "langextract"; - const quote = "网络和代理现在可用。准备推送 `codex/todo-cleanup-flash-model` 到 origin。"; - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 1 })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ narrative: quote })); - await kv.set(KV.actions, "act_old", generatedAction({ - title: "准备推送分支 codex/todo-cleanup-flash-model 到", - description: quote, - status: "active", - metadata: { - todoExtraction: { - sourceSessionId: "ses_1", - timeBucket: "current", - typeBucket: "in_progress", - evidence: { sourceObservationId: "obs_1", quote }, - dedupeKey: "push-branch-fragment", - }, - }, - })); - - const result = await (refreshTodoAction as unknown as ( - kvArg: typeof kv, - data: { actionId: string }, - deps: { runLangExtractSidecar: (input: Record) => Promise }, - ) => ReturnType)(kv, { actionId: "act_old" }, { - runLangExtractSidecar: async () => { - throw new Error("LangExtract should not run for locally repairable dangling cards"); - }, - }); - delete process.env.AGENTMEMORY_TODO_EXTRACTOR; - - expect(result).toMatchObject({ - success: true, - keptOld: false, - reason: "replaced-from-existing-evidence", - engine: "rules", - }); - const stored = await kv.get(KV.actions, "act_old"); - expect(stored?.title).toBe("推送当前工作分支到 origin"); - expect(stored?.description).toContain("codex/todo-cleanup-flash-model"); - expect(jsonText(stored?.metadata?.todoQuality)).toContain("titleCompacted"); - }); - - it("rejects single-card refresh for manual actions", async () => { - await kv.set(KV.actions, "act_manual", generatedAction({ - id: "act_manual", - createdBy: "manual", - tags: [], - metadata: {}, - })); - - const result = await refreshTodoAction(kv as never, { actionId: "act_manual" }); - - expect(result).toMatchObject({ - success: false, - keptOld: true, - reason: "not-generated", - scannedObservations: 0, - }); - }); - - it("passes the old card and cleanup metadata into LangExtract single-card refresh", async () => { - process.env.AGENTMEMORY_TODO_EXTRACTOR = "langextract"; - let sidecarInput: Record | null = null; - const quote = "内层仓库当前基线等于 origin/main,建 codex/ 分支、提交并 push,push 前跑 secrets diff 检查。"; - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 1 })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ narrative: quote })); - await kv.set(KV.actions, "act_old", generatedAction({ - title: "创建 codex 分支、提交并 push AI-Todo 仓库修改", - description: quote, - metadata: { - todoExtraction: { - sourceSessionId: "ses_1", - evidence: { sourceObservationId: "obs_1", quote }, - dedupeKey: "old-card-title", - }, - cleanup: { - decision: "rewrite", - title: "提交 AI-Todo 清理分支", - reason: "Title is process-like; make it a concrete user-facing action.", - previousTitle: "创建 codex 分支、提交并 push AI-Todo 仓库修改", - }, - }, - })); - - const result = await (refreshTodoAction as unknown as ( - kvArg: typeof kv, - data: { actionId: string }, - deps: { runLangExtractSidecar: (input: Record) => Promise }, - ) => ReturnType)(kv, { actionId: "act_old" }, { - runLangExtractSidecar: async (input) => { - sidecarInput = input; - return [{ - title: "提交 AI-Todo 清理分支", - description: "提交 AI-Todo 清理分支,并在 push 前跑 secrets diff 检查。", - confidence: 0.95, - timeBucket: "current", - typeBucket: "to_start", - sourceSessionId: "ses_1", - evidence: { sourceObservationId: "obs_1", quote }, - dedupeKey: "submit-aitodo-cleanup-branch", - }]; - }, - }); - delete process.env.AGENTMEMORY_TODO_EXTRACTOR; - - expect(result).toMatchObject({ success: true, keptOld: false, reason: "replaced", engine: "langextract" }); - expect(sidecarInput?.refreshAction).toMatchObject({ - id: "act_old", - title: "创建 codex 分支、提交并 push AI-Todo 仓库修改", - cleanup: { - title: "提交 AI-Todo 清理分支", - reason: "Title is process-like; make it a concrete user-facing action.", - }, - }); - expect((await kv.get(KV.actions, "act_old"))?.title).toBe("提交 AI-Todo 清理分支"); - }); - - it("reports LangExtract refresh failures instead of calling them no better card", async () => { - process.env.AGENTMEMORY_TODO_EXTRACTOR = "langextract"; - process.env.LANGEXTRACT_PYTHON = "__missing_python__"; - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 1 })); - await kv.set(KV.observations("ses_1"), "obs_1", obs({ narrative: "Tests passed and the PR was merged. No action needed." })); - await kv.set(KV.actions, "act_old", generatedAction()); - - const result = await refreshTodoAction(kv as never, { actionId: "act_old" }); - delete process.env.AGENTMEMORY_TODO_EXTRACTOR; - delete process.env.LANGEXTRACT_PYTHON; - - expect(result).toMatchObject({ - success: false, - keptOld: true, - reason: "llm-refresh-failed", - engine: "rules", - }); - expect(result.fallbackReason).toBeTruthy(); - }); - - it("cleans generated command-log cards from actions and review queue", async () => { - await kv.set(KV.actions, "act_bad", { - id: "act_bad", - title: "json nameWithOwner", - description: "gh pr list --json number,title --limit 20", - status: "pending", - priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", - updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", - tags: ["todo-extracted"], - sourceObservationIds: [], - sourceMemoryIds: [], - }); - await kv.set(KV.actions, "act_good", { - id: "act_good", - title: "整理首版功能文档", - description: "整理首版功能文档并给 Steve review。", - status: "pending", - priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", - updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", - tags: ["todo-extracted"], - sourceObservationIds: [], - sourceMemoryIds: [], - }); - await kv.set(KV.reviewQueue, "review_bad", { - id: "review_bad", - createdAt: "2026-06-17T08:00:00.000Z", - updatedAt: "2026-06-17T08:00:00.000Z", - status: "pending", - kind: "action", - title: "limit 20", - content: "{\"cmd\":\"gh pr list --json number\"}", - source: "viewer", - payload: { tags: ["todo-extracted"], actionCandidate: { reason: "todo" } }, - }); - - const result = await cleanPollutedTodoCards(kv as never); - - expect(result).toMatchObject({ cleanedActions: 1, cleanedReviews: 1, completedActions: 0, completedReviews: 0 }); - const actions = await kv.list(KV.actions); - expect(actions.find((a) => a.id === "act_bad")).toMatchObject({ - status: "cancelled", - metadata: { cleanup: expect.objectContaining({ decision: "garbage", previousStatus: "pending" }) }, - }); - expect(actions.find((a) => a.id === "act_good")).toMatchObject({ status: "pending" }); - expect((await kv.list(KV.reviewQueue))[0]).toMatchObject({ - status: "dismissed", - payload: { cleanup: expect.objectContaining({ decision: "garbage" }) }, - }); - }); - - it("cleans completed-work narration cards but keeps genuine repairs (STEP-08 Layer 2)", async () => { - await kv.set(KV.actions, "act_done", { - id: "act_done", - title: "三个抽取标签都能显示", - description: "三个抽取标签都能显示,同时保持卡片不会被无关标签撑开。", - status: "pending", - priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", - updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", - tags: ["todo-extracted"], - sourceObservationIds: [], - sourceMemoryIds: [], - }); - await kv.set(KV.actions, "act_keep", { - id: "act_keep", - title: "修复登录态失效后摘要不显示的问题", - description: "修复登录态失效后摘要不显示的问题。", - status: "pending", - priority: 6, - createdAt: "2026-06-17T08:00:00.000Z", - updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", - tags: ["todo-extracted"], - sourceObservationIds: [], - sourceMemoryIds: [], - }); - - const result = await cleanPollutedTodoCards(kv as never); - - expect(result.completedActions).toBe(1); - const actions = await kv.list(KV.actions); - expect(actions.find((a) => a.id === "act_done")).toMatchObject({ - status: "done", - metadata: { cleanup: expect.objectContaining({ decision: "done", previousStatus: "pending" }) }, - }); - expect(actions.find((a) => a.id === "act_keep")).toMatchObject({ status: "pending" }); - }); - - it("cleans status-report cards but keeps repairs that mention a status phrase (STEP-08)", async () => { - await kv.set(KV.actions, "act_status", { - id: "act_status", - title: "服务可用", - description: "服务可用:Viewer 状态正常,无需处理。", - status: "pending", - priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", - updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", - tags: ["todo-extracted"], - sourceObservationIds: [], - sourceMemoryIds: [], - }); - // contains "服务可用" but is a real repair — must NOT be filtered (regression - // guard for the previously-unconditional 服务可用 pollution rule) - await kv.set(KV.actions, "act_repair", { - id: "act_repair", - title: "修复服务可用性回归", - description: "修复服务可用性回归,排查压测下偶发 5xx。", - status: "pending", - priority: 6, - createdAt: "2026-06-17T08:00:00.000Z", - updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", - tags: ["todo-extracted"], - sourceObservationIds: [], - sourceMemoryIds: [], - }); - - const result = await cleanPollutedTodoCards(kv as never); - - expect(result.cleanedActions).toBe(1); - const actions = await kv.list(KV.actions); - expect(actions.find((a) => a.id === "act_status")).toMatchObject({ status: "cancelled" }); - expect(actions.find((a) => a.id === "act_repair")).toMatchObject({ status: "pending" }); - }); - - it("dry-runs cleanup without mutating cards", async () => { - await kv.set(KV.actions, "act_noise", { - id: "act_noise", - title: "继续检查关键入口和 GitHub 状态并输出报告", - description: "我会继续看关键入口文件和 GitHub 侧的分支、PR、issue、release、CI 配置。", - status: "pending", - priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", - updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", - tags: ["todo-extracted"], - sourceObservationIds: [], - sourceMemoryIds: [], - }); - - const result = await cleanPollutedTodoCards(kv as never, "dry-run"); - - expect(result.cleanedActions).toBe(1); - expect((await kv.list(KV.actions))[0]).toMatchObject({ id: "act_noise", status: "pending" }); - }); - - it("todo generation does not cleanup existing cards unless requested", async () => { - await kv.set(KV.actions, "act_noise", { - id: "act_noise", - title: "继续检查关键入口和 GitHub 状态并输出报告", - description: "我会继续看关键入口文件和 GitHub 侧的分支、PR、issue、release、CI 配置。", - status: "pending", - priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", - updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", - tags: ["todo-extracted"], - sourceObservationIds: [], - sourceMemoryIds: [], - }); - await kv.set(KV.sessions, "ses_1", session({ status: "active" })); - await kv.set(KV.observations("ses_1"), "obs_1", obs()); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false }); - - expect(result.cleanedActions).toBe(0); - expect((await kv.list(KV.actions)).find((a) => a.id === "act_noise")).toMatchObject({ status: "pending" }); - }); - - it("update applies KEEP/DROP/DONE/REWRITE/MERGE on changed-session cards with audit (STEP-12)", async () => { - await kv.set(KV.sessions, "ses_x", session({ id: "ses_x", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 9 })); - const mkAction = (id: string, title: string) => - kv.set(KV.actions, id, { - id, title, description: title + " — details", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted", "todo-recheck"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_x", sourceCheckpoint: "2026-06-17T09:00:00.000Z:1" } }, - }); - await mkAction("a_keep", "Fix the login bug"); - await mkAction("a_drop", "npm test output"); - await mkAction("a_done", "shipped the dashboard"); - await mkAction("a_rw", "fix"); - await mkAction("a_merge", "duplicate fix"); - const decide = async () => [ - { id: "a:a_keep", decision: "KEEP" as const }, - { id: "a:a_drop", decision: "DROP" as const, reason: "tool log" }, - { id: "a:a_done", decision: "DONE" as const, reason: "shipped" }, - { id: "a:a_rw", decision: "REWRITE" as const, newTitle: "Fix the dashboard N+1", newDescription: "fix the slow query" }, - { id: "a:a_merge", decision: "MERGE" as const, mergeIntoId: "a:a_rw" }, - ]; - const result = await updateChangedTodoCards(kv as never, { mode: "apply", decide }); - expect(result).toMatchObject({ engine: "llm", scanned: 5, kept: 1, dropped: 1, completed: 1, rewritten: 1, merged: 1 }); - const actions = await kv.list(KV.actions); - const byId = (id: string) => actions.find((a) => a.id === id); - expect(byId("a_keep")).toMatchObject({ status: "pending" }); - expect(byId("a_drop")).toMatchObject({ status: "cancelled", metadata: { cleanup: expect.objectContaining({ decision: "drop", llm: true }) } }); - expect(byId("a_done")).toMatchObject({ status: "done", metadata: { cleanup: expect.objectContaining({ decision: "done" }) } }); - expect(byId("a_rw")).toMatchObject({ title: "Fix the dashboard N+1", status: "pending", metadata: { cleanup: expect.objectContaining({ decision: "rewrite", previousTitle: "fix" }) } }); - expect(byId("a_merge")).toMatchObject({ status: "cancelled", metadata: { cleanup: expect.objectContaining({ decision: "merge", mergeIntoId: "a:a_rw" }) } }); - // every processed card advances its checkpoint and drops the recheck tag, so - // it won't be reprocessed until its session changes again. - expect(byId("a_keep")?.metadata?.todoExtraction).toMatchObject({ sourceCheckpoint: "2026-06-18T09:00:00.000Z:9", needsRecheck: false }); - expect(byId("a_keep")?.tags).not.toContain("todo-recheck"); - }); - - it("update only touches cards whose source session changed (STEP-12)", async () => { - // unchanged: stored checkpoint == current session fingerprint - await kv.set(KV.sessions, "ses_same", session({ id: "ses_same", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 3 })); - await kv.set(KV.actions, "a_same", { - id: "a_same", title: "Fix the login bug", description: "x", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_same", sourceCheckpoint: "2026-06-18T09:00:00.000Z:3" } }, - }); - let called = false; - const decide = async () => { called = true; return []; }; - const result = await updateChangedTodoCards(kv as never, { mode: "apply", decide }); - expect(result.scanned).toBe(0); - expect(called).toBe(false); // decide never runs when nothing changed - expect((await kv.list(KV.actions)).find((a) => a.id === "a_same")).toMatchObject({ status: "pending" }); - }); - - it("update scope=all re-judges unchanged generated cards so existing vague titles can be rewritten (STEP-10)", async () => { - await kv.set(KV.sessions, "ses_same", session({ id: "ses_same", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 3 })); - await kv.set(KV.actions, "a_vague", { - id: "a_vague", title: "克隆仓库并全面了解其状况", description: "需要克隆 MaimoryLab/AI-Todo 并了解项目现状。", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_same", sourceCheckpoint: "2026-06-18T09:00:00.000Z:3" } }, - }); - let captured: Array<{ titleQualityHint?: string }> = []; - const decide = async (cards: Array<{ titleQualityHint?: string }>) => { - captured = cards; - return [{ - id: "a:a_vague", - decision: "REWRITE" as const, - newTitle: "克隆 AI-Todo 仓库", - newDescription: "克隆 MaimoryLab/AI-Todo 仓库并确认本地可用。", - }]; - }; - - const result = await updateChangedTodoCards(kv as never, { mode: "apply", scope: "all", decide }); - - expect(captured[0]?.titleQualityHint).toContain("全面了解"); - expect(result).toMatchObject({ engine: "llm", scanned: 1, rewritten: 1 }); - expect((await kv.list(KV.actions)).find((a) => a.id === "a_vague")).toMatchObject({ - title: "克隆 AI-Todo 仓库", - status: "pending", - metadata: { cleanup: expect.objectContaining({ decision: "rewrite", previousTitle: "克隆仓库并全面了解其状况" }) }, - }); - }); - - it("flags agent-process titles for LLM rewrite during full maintenance", async () => { - await kv.set(KV.sessions, "ses_same", session({ id: "ses_same", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 3 })); - await kv.set(KV.actions, "a_process", { - id: "a_process", title: "重启 Codex desktop app 后再测一次", description: "建议处理顺序:2. 重启 Codex desktop app 后再测一次。", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_same", sourceCheckpoint: "2026-06-18T09:00:00.000Z:3" } }, - }); - let captured: Array<{ title: string; titleQualityHint?: string }> = []; - const decide = async (cards: Array<{ title: string; titleQualityHint?: string }>) => { - captured = cards; - return [{ - id: "a:a_process", - decision: "REWRITE" as const, - newTitle: "验证重启后的 Codex 桌面端", - newDescription: "重启 Codex desktop app 后再验证问题是否仍存在。", - }]; - }; - - const result = await updateChangedTodoCards(kv as never, { mode: "apply", scope: "all", decide }); - - expect(captured[0]?.titleQualityHint).toContain("process or status-check"); - expect(result).toMatchObject({ engine: "llm", scanned: 1, rewritten: 1 }); - expect((await kv.list(KV.actions)).find((a) => a.id === "a_process")).toMatchObject({ - title: "验证重启后的 Codex 桌面端", - status: "pending", - metadata: { cleanup: expect.objectContaining({ decision: "rewrite", previousTitle: "重启 Codex desktop app 后再测一次" }) }, - }); - }); - - it("update sorts before maxCards so near-duplicate titles can enter the same LLM batch (STEP-10)", async () => { - await kv.set(KV.sessions, "ses_same", session({ id: "ses_same", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 3 })); - const mkAction = (id: string, title: string) => - kv.set(KV.actions, id, { - id, title, description: title, status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_same", sourceCheckpoint: "2026-06-18T09:00:00.000Z:3" } }, - }); - await mkAction("z_first", "整理 Zeta 发布记录"); - await mkAction("clone_a", "克隆 AI-Todo 仓库并全面了解"); - await mkAction("clone_b", "克隆AI-Todo仓库并了解现状"); - let capturedIds: string[] = []; - const decide = async (cards: Array<{ id: string }>) => { - capturedIds = cards.map((card) => card.id); - return [{ id: "a:clone_b", decision: "MERGE" as const, mergeIntoId: "a:clone_a" }]; - }; - - const result = await updateChangedTodoCards(kv as never, { mode: "dry-run", scope: "all", maxCards: 2, decide }); - - expect(capturedIds).toEqual(["a:clone_a", "a:clone_b"]); - expect(result).toMatchObject({ scanned: 2, merged: 1 }); - }); - - it("update passes the session delta to the LLM (STEP-12)", async () => { - await kv.set(KV.sessions, "ses_x", session({ id: "ses_x", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 5 })); - await kv.set(KV.observations("ses_x"), "o1", obs({ id: "o1", sessionId: "ses_x", narrative: "刚刚已经修复并合并了登录超时。" })); - await kv.set(KV.actions, "a1", { - id: "a1", title: "Fix login timeout", description: "x", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_x", sourceCheckpoint: "2026-06-17T09:00:00.000Z:1" } }, - }); - let captured: Array<{ sessionDelta?: string }> = []; - const decide = async (cards: Array<{ sessionDelta?: string }>) => { captured = cards; return [{ id: "a:a1", decision: "DONE" as const }]; }; - const result = await updateChangedTodoCards(kv as never, { mode: "apply", decide }); - expect(result.completed).toBe(1); - expect(captured[0]?.sessionDelta).toContain("登录超时"); - expect((await kv.list(KV.actions)).find((a) => a.id === "a1")).toMatchObject({ status: "done" }); - }); - - it("update rule-filters tool dumps out of the session delta (STEP-12)", async () => { - await kv.set(KV.sessions, "ses_x", session({ id: "ses_x", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 5 })); - // a tool-trace observation (must be filtered) + a human one (must survive) - await kv.set(KV.observations("ses_x"), "o_tool", obs({ id: "o_tool", sessionId: "ses_x", timestamp: "2026-06-18T08:00:00.000Z", narrative: '{"cmd":"gh pr list --json number"}' })); - await kv.set(KV.observations("ses_x"), "o_human", obs({ id: "o_human", sessionId: "ses_x", timestamp: "2026-06-18T08:01:00.000Z", narrative: "刚刚已经修复并合并了登录超时。" })); - await kv.set(KV.actions, "a1", { - id: "a1", title: "Fix login timeout", description: "x", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_x", sourceCheckpoint: "2026-06-17T09:00:00.000Z:1" } }, - }); - let captured: Array<{ sessionDelta?: string }> = []; - const decide = async (cards: Array<{ sessionDelta?: string }>) => { captured = cards; return [{ id: "a:a1", decision: "KEEP" as const }]; }; - await updateChangedTodoCards(kv as never, { mode: "dry-run", decide }); - expect(captured[0]?.sessionDelta).toContain("登录超时"); // human text kept - expect(captured[0]?.sessionDelta).not.toContain("gh pr list"); // tool dump dropped - }); - - it("update dry-run previews without mutating (STEP-12)", async () => { - await kv.set(KV.sessions, "ses_x", session({ id: "ses_x", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 5 })); - await kv.set(KV.actions, "a_drop", { - id: "a_drop", title: "npm test output", description: "x", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_x", sourceCheckpoint: "2026-06-17T09:00:00.000Z:1" } }, - }); - const decide = async () => [{ id: "a:a_drop", decision: "DROP" as const, reason: "noise" }]; - const result = await updateChangedTodoCards(kv as never, { mode: "dry-run", decide }); - expect(result).toMatchObject({ engine: "llm", dropped: 1 }); - expect(result.preview).toHaveLength(1); - expect((await kv.list(KV.actions)).find((a) => a.id === "a_drop")).toMatchObject({ status: "pending" }); - }); - - it("update leaves cards untouched when the LLM fails — no rule fallback (STEP-12)", async () => { - await kv.set(KV.sessions, "ses_x", session({ id: "ses_x", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 5 })); - await kv.set(KV.actions, "a_bad", { - id: "a_bad", title: "gh pr list --json number", description: "{\"cmd\":\"gh pr list\"}", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_x", sourceCheckpoint: "2026-06-17T09:00:00.000Z:1" } }, - }); - const decide = async (): Promise => { throw new Error("LLM down"); }; - const result = await updateChangedTodoCards(kv as never, { mode: "apply", decide }); - // update is LLM-only: failure is signalled by fallbackReason, not engine. - expect(result.fallbackReason).toContain("LLM down"); - expect(result.dropped).toBe(0); - // update has no rule equivalent, so the card stays exactly as it was - expect((await kv.list(KV.actions)).find((a) => a.id === "a_bad")).toMatchObject({ status: "pending" }); - }); - - it("update also re-judges changed-session review cards (STEP-12)", async () => { - await kv.set(KV.sessions, "ses_x", session({ id: "ses_x", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 5 })); - await kv.set(KV.reviewQueue, "rev1", { - id: "rev1", createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - status: "pending", kind: "action", title: "maybe fix the thing", content: "x", source: "viewer", - payload: { tags: ["todo-extracted"], todoExtraction: { sourceSessionId: "ses_x", sourceCheckpoint: "2026-06-17T09:00:00.000Z:1" } }, - } as never); - const decide = async () => [{ id: "r:rev1", decision: "DROP" as const, reason: "noise" }]; - const result = await updateChangedTodoCards(kv as never, { mode: "apply", decide }); - expect(result.scanned).toBe(1); - expect(result.dropped).toBe(1); - expect((await kv.list(KV.reviewQueue)).find((r) => r.id === "rev1")).toMatchObject({ status: "dismissed" }); - }); - - it("update downgrades a MERGE whose target is not in the batch to KEEP (STEP-12)", async () => { - await kv.set(KV.sessions, "ses_x", session({ id: "ses_x", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 5 })); - await kv.set(KV.actions, "a_src", { - id: "a_src", title: "real todo", description: "x", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_x", sourceCheckpoint: "2026-06-17T09:00:00.000Z:1" } }, - }); - const decide = async () => [{ id: "a:a_src", decision: "MERGE" as const, mergeIntoId: "a:ghost" }]; - const result = await updateChangedTodoCards(kv as never, { mode: "apply", decide }); - expect(result.merged).toBe(0); - expect(result.kept).toBe(1); - // dangling MERGE downgraded → source preserved, not cancelled - expect((await kv.list(KV.actions)).find((a) => a.id === "a_src")).toMatchObject({ status: "pending" }); - }); - - it("update apply reuses the dry-run decisions verbatim — no LLM re-call (STEP-12)", async () => { - await kv.set(KV.sessions, "ses_x", session({ id: "ses_x", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 5 })); - await kv.set(KV.actions, "a_drop", { - id: "a_drop", title: "noise", description: "x", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_x", sourceCheckpoint: "2026-06-17T09:00:00.000Z:1" } }, - }); - let decideCalled = false; - const decide = async () => { decideCalled = true; return [{ id: "a:a_drop", decision: "KEEP" as const }]; }; - // The dry-run decided DROP; apply must honour that, not re-ask the LLM - // (which here would say KEEP). - const result = await updateChangedTodoCards(kv as never, { - mode: "apply", decide, - decisions: [{ id: "a:a_drop", decision: "DROP", reason: "preview said drop" }], - }); - expect(decideCalled).toBe(false); - expect(result.dropped).toBe(1); - expect((await kv.list(KV.actions)).find((a) => a.id === "a_drop")).toMatchObject({ status: "cancelled" }); - }); - - it("update apply replays every supplied dry-run decision without default maxCards truncation (STEP-10)", async () => { - await kv.set(KV.sessions, "ses_x", session({ id: "ses_x", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 5 })); - const decisions = []; - for (let i = 0; i < 61; i++) { - const id = `a_${i}`; - await kv.set(KV.actions, id, { - id, title: `noise ${i}`, description: "x", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_x", sourceCheckpoint: "2026-06-17T09:00:00.000Z:1" } }, - }); - decisions.push({ id: `a:${id}`, decision: "DROP" as const, reason: "preview said drop" }); - } - - const result = await updateChangedTodoCards(kv as never, { mode: "apply", maxCards: 10, decisions }); - - expect(result.dropped).toBe(61); - expect((await kv.list(KV.actions)).filter((a) => a.status === "cancelled")).toHaveLength(61); - }); - - it("update apply replays decisions for unchanged scope=all cards even when scope is omitted (STEP-10)", async () => { - await kv.set(KV.sessions, "ses_same", session({ id: "ses_same", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 3 })); - await kv.set(KV.actions, "a_same", { - id: "a_same", title: "克隆仓库并全面了解其状况", description: "x", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_same", sourceCheckpoint: "2026-06-18T09:00:00.000Z:3" } }, - }); - - const result = await updateChangedTodoCards(kv as never, { - mode: "apply", - decisions: [{ - id: "a:a_same", - decision: "REWRITE", - newTitle: "克隆 AI-Todo 仓库", - newDescription: "x", - }], - }); - - expect(result.rewritten).toBe(1); - expect((await kv.list(KV.actions)).find((a) => a.id === "a_same")).toMatchObject({ - title: "克隆 AI-Todo 仓库", - }); - }); - - it("update dry-run returns the decisions so apply can replay them (STEP-12)", async () => { - await kv.set(KV.sessions, "ses_x", session({ id: "ses_x", endedAt: "2026-06-18T09:00:00.000Z", observationCount: 5 })); - await kv.set(KV.actions, "a1", { - id: "a1", title: "real todo", description: "x", status: "pending", priority: 5, - createdAt: "2026-06-17T08:00:00.000Z", updatedAt: "2026-06-17T08:00:00.000Z", - createdBy: "todo-extract", tags: ["todo-extracted"], sourceObservationIds: [], sourceMemoryIds: [], - metadata: { todoExtraction: { sourceSessionId: "ses_x", sourceCheckpoint: "2026-06-17T09:00:00.000Z:1" } }, - }); - const decide = async () => [{ id: "a:a1", decision: "DONE" as const, reason: "done now" }]; - const dry = await updateChangedTodoCards(kv as never, { mode: "dry-run", decide }); - expect(dry.decisions).toEqual([{ id: "a:a1", decision: "DONE", reason: "done now" }]); - expect((await kv.list(KV.actions)).find((a) => a.id === "a1")).toMatchObject({ status: "pending" }); // dry-run didn't mutate - }); - - it("extracts todos from a browser-captured session (STEP-14)", async () => { - // Browser sessions are recorded (recordBrowserSessionFallback) as - // type:conversation observations titled 用户发言/AI发言 — todo-extract must - // consume them like any other session (no separate action-candidate leg). - await kv.set(KV.sessions, "browser_sync_x", session({ - id: "browser_sync_x", project: "ChatGPT", cwd: "browser/chatgpt.com", - status: "completed", observationCount: 2, tags: ["browser"], agentId: "ChatGPT", - })); - await kv.set(KV.observations("browser_sync_x"), "t_ai", obs({ - id: "t_ai", sessionId: "browser_sync_x", title: "AI发言", type: "conversation", - narrative: "这里是页面说明。", - })); - await kv.set(KV.observations("browser_sync_x"), "t_user", obs({ - id: "t_user", sessionId: "browser_sync_x", title: "用户发言", type: "conversation", - narrative: "后续需要修复登录接口的超时问题。", - })); - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false, cleanup: "none" }); - expect(result.directCreated).toBe(1); - expect((await kv.list(KV.actions))[0]).toMatchObject({ project: "ChatGPT", sourceObservationIds: ["t_user"] }); - }); - - it("filters agent progress observations before rules extraction", async () => { - await kv.set(KV.sessions, "ses_1", session({ status: "active", observationCount: 2 })); - await kv.set(KV.observations("ses_1"), "obs_noise", obs({ - id: "obs_noise", - narrative: "我会继续看关键入口文件和 GitHub 侧的分支、PR、issue、release、CI 配置。", - })); - await kv.set(KV.observations("ses_1"), "obs_real", obs({ - id: "obs_real", - narrative: "后续需要修复 CI 失败,并重新跑测试。", - })); - - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false, cleanup: "none" }); - - expect(result.scannedObservations).toBe(1); - expect(result.directCreated).toBe(1); - expect((await kv.list(KV.actions))[0].sourceObservationIds).toEqual(["obs_real"]); - }); -}); - -describe("todo extraction scope — sinceDays + interaction window (STEP-11)", () => { - let kv: ReturnType; - const daysAgo = (n: number) => new Date(Date.now() - n * 86_400_000).toISOString(); - - beforeEach(() => { - kv = mockKV(); - }); - afterEach(() => { - delete process.env.AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS; - delete process.env.AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION; - }); - - // Two completed sessions, both inside the 14d "recent" bucket (so neither is - // hidden as history), but one older than a tight sinceDays window. - async function seedTwoSessions() { - await kv.set(KV.sessions, "ses_recent", session({ - id: "ses_recent", status: "completed", startedAt: daysAgo(1), endedAt: daysAgo(1), observationCount: 1, - })); - await kv.set(KV.sessions, "ses_mid", session({ - id: "ses_mid", status: "completed", startedAt: daysAgo(5), endedAt: daysAgo(5), observationCount: 1, - })); - await kv.set(KV.observations("ses_recent"), "o_recent", obs({ - id: "o_recent", sessionId: "ses_recent", timestamp: daysAgo(1), - narrative: "后续需要修复登录接口的超时问题。", - })); - await kv.set(KV.observations("ses_mid"), "o_mid", obs({ - id: "o_mid", sessionId: "ses_mid", timestamp: daysAgo(5), - narrative: "后续需要更新数据库驱动到 v5。", - })); - } - - it("control: extracts both sessions when the window covers both", async () => { - process.env.AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS = "30"; - await seedTwoSessions(); - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false, cleanup: "none" }); - expect(result.directCreated).toBe(2); - expect(await kv.list(KV.actions)).toHaveLength(2); - }); - - it("skips sessions whose endedAt is older than the sinceDays window", async () => { - process.env.AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS = "3"; - await seedTwoSessions(); - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false, cleanup: "none" }); - const actions = await kv.list(KV.actions); - expect(actions).toHaveLength(1); - expect(actions[0].metadata?.todoExtraction).toMatchObject({ sourceSessionId: "ses_recent" }); - }); - - it("lets a body sinceDays override the env window", async () => { - process.env.AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS = "30"; - await seedTwoSessions(); - const result = await generateTodosFromSessions(kv as never, { - force: true, scanSources: false, cleanup: "none", sinceDays: 3, - }); - expect(result.directCreated).toBe(1); - expect((await kv.list(KV.actions))[0].metadata?.todoExtraction).toMatchObject({ sourceSessionId: "ses_recent" }); - }); - - // A user message in synthetic-compression form: type "conversation", title - // === raw hookType "prompt_submit" (the interaction-boundary signal). - function promptObs(id: string, narrative: string, timestamp: string): CompressedObservation { - return obs({ id, sessionId: "ses_turns", type: "conversation", title: "prompt_submit", narrative, timestamp }); - } - async function seedThreeInteractions() { - await kv.set(KV.sessions, "ses_turns", session({ id: "ses_turns", status: "active", observationCount: 3 })); - await kv.set(KV.observations("ses_turns"), "t1", promptObs("t1", "后续需要修复登录接口的超时问题。", daysAgo(3))); - await kv.set(KV.observations("ses_turns"), "t2", promptObs("t2", "后续需要补充新用户的上手文档。", daysAgo(2))); - await kv.set(KV.observations("ses_turns"), "t3", promptObs("t3", "后续需要更新数据库驱动到 v5。", daysAgo(1))); - } - - it("control: extracts every interaction when the window covers them all", async () => { - process.env.AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS = "30"; - await seedThreeInteractions(); - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false, cleanup: "none" }); - expect(result.directCreated).toBe(3); - }); - - it("keeps only the most recent N interaction records per session", async () => { - process.env.AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS = "30"; - process.env.AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION = "1"; - await seedThreeInteractions(); - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false, cleanup: "none" }); - expect(result.directCreated).toBe(1); - expect((await kv.list(KV.actions))[0].sourceObservationIds).toEqual(["t3"]); - }); - - it("treats a session with no user-message boundary as a single interaction", async () => { - process.env.AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS = "30"; - process.env.AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION = "1"; - await kv.set(KV.sessions, "ses_noturn", session({ id: "ses_noturn", status: "active", observationCount: 1 })); - await kv.set(KV.observations("ses_noturn"), "n1", obs({ - id: "n1", sessionId: "ses_noturn", title: "assistant", narrative: "后续需要修复登录接口的超时问题。", - })); - const result = await generateTodosFromSessions(kv as never, { force: true, scanSources: false, cleanup: "none" }); - expect(result.directCreated).toBe(1); - }); -}); diff --git a/test/todo-extractor-config.test.ts b/test/todo-extractor-config.test.ts deleted file mode 100644 index e0a3cccb..00000000 --- a/test/todo-extractor-config.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs"; -import { spawnSync } from "node:child_process"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -const ORIGINAL_HOME = process.env["HOME"]; -const ORIGINAL_USERPROFILE = process.env["USERPROFILE"]; - -let sandboxHome: string; - -async function freshConfig() { - vi.resetModules(); - return await import("../src/config.js"); -} - -describe("todo extractor user config", () => { - beforeEach(() => { - sandboxHome = mkdtempSync(join(tmpdir(), "agentmemory-todo-config-")); - process.env["HOME"] = sandboxHome; - process.env["USERPROFILE"] = sandboxHome; - delete process.env.LANGEXTRACT_API_KEY; - delete process.env.LANGEXTRACT_MODEL; - delete process.env.LANGEXTRACT_PROVIDER; - delete process.env.LANGEXTRACT_BASE_URL; - delete process.env.AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS; - delete process.env.AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS; - delete process.env.AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION; - }); - - afterEach(() => { - if (ORIGINAL_HOME === undefined) delete process.env["HOME"]; - else process.env["HOME"] = ORIGINAL_HOME; - if (ORIGINAL_USERPROFILE === undefined) delete process.env["USERPROFILE"]; - else process.env["USERPROFILE"] = ORIGINAL_USERPROFILE; - rmSync(sandboxHome, { recursive: true, force: true }); - }); - - it("writes only allowed LangExtract keys and never exposes the API key", async () => { - const { getTodoExtractorUserConfig, getUserEnvPath, writeUserEnv } = await freshConfig(); - writeUserEnv({ - LANGEXTRACT_MODEL: "deepseek/deepseek-v4-flash", - LANGEXTRACT_API_KEY: "secret", - NOT_ALLOWED: "ignored", - }); - - const raw = readFileSync(getUserEnvPath(), "utf-8"); - expect(raw).toContain("LANGEXTRACT_MODEL=deepseek/deepseek-v4-flash"); - expect(raw).toContain("LANGEXTRACT_API_KEY=secret"); - expect(raw).not.toContain("NOT_ALLOWED"); - - const cfg = getTodoExtractorUserConfig(); - expect(cfg.LANGEXTRACT_MODEL).toBe("deepseek/deepseek-v4-flash"); - expect(cfg.LANGEXTRACT_API_KEY_CONFIGURED).toBe(true); - expect(cfg.LANGEXTRACT_API_KEY_MASKED).toBe("se****et"); - expect(cfg).not.toHaveProperty("LANGEXTRACT_API_KEY"); - }); - - it("treats the old bundled model as unset and returns the current default", async () => { - const { getTodoExtractorUserConfig, writeUserEnv } = await freshConfig(); - writeUserEnv({ LANGEXTRACT_MODEL: "pa/gpt-5.5" }); - - expect(getTodoExtractorUserConfig().LANGEXTRACT_MODEL).toBe("deepseek/deepseek-v4-flash"); - }); - - it("defaults LangExtract to Novita OpenAI-compatible routing", async () => { - const { getTodoExtractorUserConfig, writeUserEnv } = await freshConfig(); - writeUserEnv({ LANGEXTRACT_PROVIDER: "novita" }); - - const cfg = getTodoExtractorUserConfig(); - expect(cfg.LANGEXTRACT_PROVIDER).toBe("openai"); - expect(cfg.LANGEXTRACT_BASE_URL).toBe("https://api.novita.ai/openai/v1"); - }); - - it("exposes whether the LangExtract runtime has the Python dependency", async () => { - const { getTodoExtractorUserConfig } = await freshConfig(); - - const cfg = getTodoExtractorUserConfig(); - - expect(cfg.LANGEXTRACT_RUNTIME_READY).toBeTypeOf("boolean"); - expect(cfg.LANGEXTRACT_RUNTIME_ERROR).toBeTypeOf("string"); - }); - - it("checks LangExtract runtime readiness without importing the heavy package", async () => { - const { detectLangExtractRuntimeProbe } = await freshConfig(); - expect(detectLangExtractRuntimeProbe()).toContain("find_spec('langextract')"); - expect(detectLangExtractRuntimeProbe()).not.toContain("import langextract"); - expect(spawnSync("python3", ["-c", detectLangExtractRuntimeProbe()], { encoding: "utf8" }).status).toBeTypeOf("number"); - }); - - it("defaults LangExtract Python to the project-managed venv when it exists", async () => { - const originalCwd = process.cwd(); - const projectRoot = mkdtempSync(join(tmpdir(), "agentmemory-langextract-project-")); - const venvPython = join(projectRoot, ".agentmemory-python", "bin", "python"); - mkdirSync(join(projectRoot, ".agentmemory-python", "bin"), { recursive: true }); - writeFileSync(venvPython, "#!/bin/sh\n"); - const expectedPython = join(realpathSync(projectRoot), ".agentmemory-python", "bin", "python"); - process.chdir(projectRoot); - try { - const { resolveLangExtractPython } = await freshConfig(); - expect(resolveLangExtractPython({})).toBe(expectedPython); - expect(resolveLangExtractPython({ LANGEXTRACT_PYTHON: "__custom_python__" })).toBe("__custom_python__"); - } finally { - process.chdir(originalCwd); - rmSync(projectRoot, { recursive: true, force: true }); - } - }); - - it("round-trips the LLM extract timeout: defaults when unset, persists + reads back when set", async () => { - const { getTodoExtractorUserConfig, getUserEnvPath, writeUserEnv } = await freshConfig(); - - // read-back returns the default when unset (UI shows a real value, not blank) - expect(getTodoExtractorUserConfig().AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS).toBe("120000"); - - // it is an allowed key: write persists it, and the GET path returns it - writeUserEnv({ AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS: "45000" }); - expect(readFileSync(getUserEnvPath(), "utf-8")).toContain("AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS=45000"); - expect(getTodoExtractorUserConfig().AGENTMEMORY_TODO_EXTRACT_TIMEOUT_MS).toBe("45000"); - }); - - it("round-trips the STEP-11 scope settings (sinceDays + max interactions): defaults when unset, persists when set", async () => { - const { getTodoExtractorUserConfig, getUserEnvPath, writeUserEnv } = await freshConfig(); - - // defaults surface in the GET path so the UI shows real values, not blanks - const defaults = getTodoExtractorUserConfig(); - expect(defaults.AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS).toBe("7"); - expect(defaults.AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION).toBe("10"); - - // both are allowed keys: writes persist and read back - writeUserEnv({ - AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS: "14", - AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION: "5", - }); - const raw = readFileSync(getUserEnvPath(), "utf-8"); - expect(raw).toContain("AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS=14"); - expect(raw).toContain("AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION=5"); - const cfg = getTodoExtractorUserConfig(); - expect(cfg.AGENTMEMORY_TODO_EXTRACT_SINCE_DAYS).toBe("14"); - expect(cfg.AGENTMEMORY_TODO_EXTRACT_MAX_INTERACTIONS_PER_SESSION).toBe("5"); - }); -}); diff --git a/test/ui-static.test.ts b/test/ui-static.test.ts new file mode 100644 index 00000000..efafe996 --- /dev/null +++ b/test/ui-static.test.ts @@ -0,0 +1,17 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import test from "node:test"; + +test("organize button shows busy state and estimated duration copy", () => { + const js = readFileSync("public/app.js", "utf8"); + const css = readFileSync("public/app.css", "utf8"); + + assert.match(js, /button\.classList\.add\("is-busy"\)/); + assert.match(js, /button\.setAttribute\("aria-busy", "true"\)/); + assert.match(js, /button\.textContent = "Organizing\.\.\."/); + assert.match(js, /This may take about/); + assert.match(js, /only the newest/); + assert.match(js, /organize\.maxSessions \?\? 16/); + assert.match(css, /button\.primary\.is-busy/); + assert.match(css, /@keyframes spin/); +}); diff --git a/test/vector-index-dimensions.test.ts b/test/vector-index-dimensions.test.ts deleted file mode 100644 index 140d7f8a..00000000 --- a/test/vector-index-dimensions.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { describe, it, expect, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { VectorIndex } from "../src/state/vector-index.js"; -import { migrateVectorIndex } from "../src/functions/migrate-vector-index.js"; -import type { EmbeddingProvider } from "../src/types.js"; - -describe("VectorIndex.validateDimensions", () => { - it("reports no mismatches and an empty dim set on an empty index", () => { - const result = new VectorIndex().validateDimensions(384); - expect(result.mismatches).toEqual([]); - expect(Array.from(result.seenDimensions)).toEqual([]); - }); - - it("reports no mismatches when every vector matches the expected dimension", () => { - const idx = new VectorIndex(); - idx.add("o1", "s1", new Float32Array(384)); - idx.add("o2", "s1", new Float32Array(384)); - const result = idx.validateDimensions(384); - expect(result.mismatches).toEqual([]); - expect(Array.from(result.seenDimensions)).toEqual([384]); - }); - - it("reports every wrong-dimension vector, not just the first", () => { - const idx = new VectorIndex(); - idx.add("good1", "s1", new Float32Array(384)); - idx.add("bad1", "s1", new Float32Array(1536)); - idx.add("good2", "s1", new Float32Array(384)); - idx.add("bad2", "s1", new Float32Array(768)); - const result = idx.validateDimensions(384); - expect(result.mismatches).toHaveLength(2); - expect(result.mismatches.map((m) => m.obsId).sort()).toEqual(["bad1", "bad2"]); - expect(Array.from(result.seenDimensions).sort((a, b) => a - b)).toEqual([ - 384, 768, 1536, - ]); - }); - - it("flags every entry when the entire index has the wrong dimension", () => { - const idx = new VectorIndex(); - idx.add("o1", "s1", new Float32Array(384)); - idx.add("o2", "s1", new Float32Array(384)); - const result = idx.validateDimensions(1536); - expect(result.mismatches).toHaveLength(2); - expect(Array.from(result.seenDimensions)).toEqual([384]); - }); -}); - -describe("migrateVectorIndex", () => { - const newProvider: EmbeddingProvider = { - name: "test-4d", - dimensions: 4, - embed: async (_text: string) => new Float32Array([0.1, 0.2, 0.3, 0.4]), - embedBatch: async (_texts: string[]) => - _texts.map(() => new Float32Array([0.1, 0.2, 0.3, 0.4])), - }; - - function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (_scope: string, _key: string): Promise => {}, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; - } - - it("re-embeds observations with new provider dimensions", async () => { - const kv = mockKV(); - await kv.set("mem:sessions", "ses_1", { id: "ses_1" }); - await kv.set("mem:obs:ses_1", "obs_1", { - id: "obs_1", - sessionId: "ses_1", - timestamp: new Date().toISOString(), - type: "decision", - title: "migration test", - facts: ["test"], - narrative: "Testing migration", - concepts: ["test"], - files: [], - importance: 5, - }); - - const result = await migrateVectorIndex(kv as never, newProvider); - expect(result.success).toBe(true); - expect(result.totalProcessed).toBe(1); - expect(result.vectorSize).toBe(1); - expect(result.failed).toBe(0); - }); - - it("re-embeds memories with new provider dimensions", async () => { - const kv = mockKV(); - await kv.set("mem:memories", "mem_1", { - id: "mem_1", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - type: "fact", - title: "migration memory", - content: "This memory will be re-embedded", - concepts: ["test"], - files: [], - sessionIds: ["ses_1"], - strength: 7, - version: 1, - isLatest: true, - }); - - const result = await migrateVectorIndex(kv as never, newProvider); - expect(result.totalProcessed).toBe(1); - expect(result.vectorSize).toBe(1); - }); - - it("handles empty KV gracefully", async () => { - const kv = mockKV(); - - const result = await migrateVectorIndex(kv as never, newProvider); - expect(result.success).toBe(true); - expect(result.totalProcessed).toBe(0); - expect(result.vectorSize).toBe(0); - expect(result.failed).toBe(0); - }); -}); diff --git a/test/vector-index-populate.test.ts b/test/vector-index-populate.test.ts deleted file mode 100644 index 936232a7..00000000 --- a/test/vector-index-populate.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -vi.mock("../src/state/keyed-mutex.js", () => ({ - withKeyedLock: (_key: string, fn: () => Promise) => fn(), -})); - -import { registerRememberFunction } from "../src/functions/remember.js"; -import { setVectorIndex, setEmbeddingProvider, getVectorIndex } from "../src/functions/search.js"; -import { VectorIndex } from "../src/state/vector-index.js"; -import type { EmbeddingProvider } from "../src/types.js"; - -function mockKV() { - const store = new Map>(); - return { - get: async (scope: string, key: string): Promise => - (store.get(scope)?.get(key) as T) ?? null, - set: async (scope: string, key: string, data: T): Promise => { - if (!store.has(scope)) store.set(scope, new Map()); - store.get(scope)!.set(key, data); - return data; - }, - delete: async (scope: string, key: string): Promise => { - store.get(scope)?.delete(key); - }, - list: async (scope: string): Promise => { - const entries = store.get(scope); - return entries ? (Array.from(entries.values()) as T[]) : []; - }, - }; -} - -function mockSdk() { - const functions = new Map(); - return { - registerFunction: (id: string, handler: Function) => { - functions.set(id, handler); - }, - registerTrigger: () => {}, - trigger: async (input: { function_id: string; payload: unknown }) => { - const fn = functions.get(input.function_id); - if (!fn) throw new Error(`unknown fn ${input.function_id}`); - return fn(input.payload); - }, - }; -} - -describe("vector index population on remember", () => { - const mockEmbedder: EmbeddingProvider = { - name: "test", - dimensions: 3, - embed: async (_text: string) => new Float32Array([0.1, 0.2, 0.3]), - embedBatch: async (_texts: string[]) => - _texts.map(() => new Float32Array([0.1, 0.2, 0.3])), - }; - - let vectorIndex: VectorIndex; - - beforeEach(() => { - vectorIndex = new VectorIndex(); - setVectorIndex(vectorIndex); - setEmbeddingProvider(mockEmbedder); - }); - - afterEach(() => { - setVectorIndex(null); - setEmbeddingProvider(null); - }); - - it("calls vectorIndex.add() when remember saves a memory", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = await sdk.trigger({ - function_id: "mem::remember", - payload: { content: "Test memory for vector indexing", type: "fact" }, - }); - - expect((result as { success: boolean }).success).toBe(true); - expect(vectorIndex.size).toBe(1); - }); - - it("calls vectorIndex.add() with short content (0% similarity dedup)", async () => { - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - await sdk.trigger({ - function_id: "mem::remember", - payload: { content: "First unique memory", type: "fact" }, - }); - await sdk.trigger({ - function_id: "mem::remember", - payload: { content: "Second completely different memory", type: "fact" }, - }); - - expect(vectorIndex.size).toBe(2); - }); - - it("handles missing embedder gracefully (vectorIndex stays null)", async () => { - // Override beforeEach setup: this case wants null state explicitly. - setVectorIndex(null); - setEmbeddingProvider(null); - - const sdk = mockSdk(); - const kv = mockKV(); - registerRememberFunction(sdk as never, kv as never); - - const result = await sdk.trigger({ - function_id: "mem::remember", - payload: { content: "This should work without vector index", type: "fact" }, - }); - - expect((result as { success: boolean }).success).toBe(true); - expect(getVectorIndex()).toBeNull(); - }); -}); diff --git a/test/vector-index.test.ts b/test/vector-index.test.ts deleted file mode 100644 index 317f4f03..00000000 --- a/test/vector-index.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { describe, it, expect, beforeEach } from "vitest"; -import { VectorIndex } from "../src/state/vector-index.js"; - -describe("VectorIndex", () => { - let index: VectorIndex; - - beforeEach(() => { - index = new VectorIndex(); - }); - - it("starts empty", () => { - expect(index.size).toBe(0); - }); - - it("adds and retrieves vectors", () => { - index.add("obs_1", "ses_1", new Float32Array([0.1, 0.2, 0.3])); - expect(index.size).toBe(1); - }); - - it("removes a vector", () => { - index.add("obs_1", "ses_1", new Float32Array([0.1, 0.2, 0.3])); - index.remove("obs_1"); - expect(index.size).toBe(0); - }); - - it("returns empty array when searching empty index", () => { - const results = index.search(new Float32Array([0.1, 0.2, 0.3])); - expect(results).toEqual([]); - }); - - it("returns results sorted by cosine similarity", () => { - index.add("obs_close", "ses_1", new Float32Array([1, 0, 0])); - index.add("obs_far", "ses_1", new Float32Array([0, 1, 0])); - index.add("obs_medium", "ses_1", new Float32Array([0.7, 0.7, 0])); - - const results = index.search(new Float32Array([1, 0, 0])); - expect(results[0].obsId).toBe("obs_close"); - expect(results[0].score).toBeCloseTo(1.0, 5); - expect(results[1].obsId).toBe("obs_medium"); - expect(results[2].obsId).toBe("obs_far"); - expect(results[2].score).toBeCloseTo(0.0, 5); - }); - - it("respects the limit parameter", () => { - for (let i = 0; i < 10; i++) { - index.add(`obs_${i}`, "ses_1", new Float32Array([i * 0.1, 0.5, 0.5])); - } - const results = index.search(new Float32Array([0.9, 0.5, 0.5]), 3); - expect(results.length).toBe(3); - }); - - it("clears all vectors", () => { - index.add("obs_1", "ses_1", new Float32Array([0.1, 0.2, 0.3])); - index.add("obs_2", "ses_1", new Float32Array([0.4, 0.5, 0.6])); - index.clear(); - expect(index.size).toBe(0); - expect(index.search(new Float32Array([0.1, 0.2, 0.3]))).toEqual([]); - }); - - it("serialize and deserialize round-trip preserves data", () => { - index.add("obs_1", "ses_1", new Float32Array([0.1, 0.2, 0.3])); - index.add("obs_2", "ses_2", new Float32Array([0.4, 0.5, 0.6])); - - const json = index.serialize(); - const restored = VectorIndex.deserialize(json); - - expect(restored.size).toBe(2); - const results = restored.search(new Float32Array([0.1, 0.2, 0.3]), 2); - expect(results.length).toBe(2); - expect(results[0].obsId).toBe("obs_1"); - expect(results[0].sessionId).toBe("ses_1"); - }); - - it("handles zero vectors without error", () => { - index.add("obs_zero", "ses_1", new Float32Array([0, 0, 0])); - const results = index.search(new Float32Array([1, 0, 0])); - expect(results[0].score).toBe(0); - }); - - it("round-trip preserves dim + identity for pooled-Buffer sizes (#587)", () => { - // 384-dim floats = 1536 bytes, comfortably inside Node's 8KB Buffer - // pool. Without explicit byteOffset/byteLength in the base64 round-trip, - // deserialise reads pool offset 0 and reports the entire pool as a - // 2048-element view, which the live index then rejects with - // "dimensions seen on disk: 2048". - const DIM = 384; - const vecs = Array.from({ length: 5 }, (_, n) => { - const v = new Float32Array(DIM); - for (let i = 0; i < DIM; i++) v[i] = n * 1000 + i; - return v; - }); - vecs.forEach((v, n) => index.add(`obs_${n}`, "ses_1", v)); - - const restored = VectorIndex.deserialize(index.serialize()); - expect(restored.size).toBe(5); - const { mismatches } = restored.validateDimensions(DIM); - expect(mismatches).toEqual([]); - for (let n = 0; n < 5; n++) { - const results = restored.search(vecs[n], 1); - expect(results[0].obsId).toBe(`obs_${n}`); - expect(results[0].score).toBeCloseTo(1.0, 4); - } - }); - - it("preserves bytes when source Float32Array is itself a sliced view (#587)", () => { - // The encode side has the same risk: passing arr.buffer drops the - // slice metadata if arr is a sub-view (subarray / typedArray.set). - const backing = new Float32Array(8); - for (let i = 0; i < 8; i++) backing[i] = i; - const slice = backing.subarray(2, 6); // values 2, 3, 4, 5 - - index.add("obs_slice", "ses_1", slice); - const restored = VectorIndex.deserialize(index.serialize()); - const results = restored.search(new Float32Array([2, 3, 4, 5]), 1); - expect(results[0].obsId).toBe("obs_slice"); - expect(results[0].score).toBeCloseTo(1.0, 4); - }); -}); diff --git a/test/verify.test.ts b/test/verify.test.ts deleted file mode 100644 index 9af433c7..00000000 --- a/test/verify.test.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; - -vi.mock("../src/logger.js", () => ({ - logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, -})); - -import { registerVerifyFunction } from "../src/functions/verify.js"; -import type { Memory, CompressedObservation, Session } from "../src/types.js"; -import { mockKV, mockSdk } from "./helpers/mocks.js"; - -describe("Verify Function", () => { - let sdk: ReturnType; - let kv: ReturnType; - - beforeEach(() => { - sdk = mockSdk(); - kv = mockKV(); - vi.clearAllMocks(); - registerVerifyFunction(sdk as never, kv as never); - }); - - it("returns error when id is missing", async () => { - const result = (await sdk.trigger("mem::verify", {})) as { - success: boolean; - error: string; - }; - expect(result.success).toBe(false); - expect(result.error).toBe("id is required"); - }); - - it("returns not found for unknown id", async () => { - const result = (await sdk.trigger("mem::verify", { - id: "unknown_123", - })) as { success: boolean; error: string }; - expect(result.success).toBe(false); - expect(result.error).toBe("not found"); - }); - - it("verifies a memory with citation chain", async () => { - const session: Session = { - id: "ses_1", - project: "/test/project", - cwd: "/test", - startedAt: "2026-03-01T00:00:00Z", - status: "completed", - observationCount: 2, - }; - await kv.set("mem:sessions", "ses_1", session); - - const obs: CompressedObservation = { - id: "obs_1", - sessionId: "ses_1", - timestamp: "2026-03-01T00:01:00Z", - type: "decision", - title: "Chose React over Vue", - facts: ["React chosen for ecosystem"], - narrative: "Team decided on React", - concepts: ["react", "frontend"], - files: ["src/App.tsx"], - importance: 8, - confidence: 0.85, - }; - await kv.set("mem:obs:ses_1", "obs_1", obs); - - const memory: Memory = { - id: "mem_1", - createdAt: "2026-03-01T00:02:00Z", - updatedAt: "2026-03-01T00:02:00Z", - type: "architecture", - title: "Using React for frontend", - content: "The team uses React for the frontend framework", - concepts: ["react", "frontend"], - files: ["src/App.tsx"], - sessionIds: ["ses_1"], - strength: 8, - version: 1, - isLatest: true, - sourceObservationIds: ["obs_1"], - }; - await kv.set("mem:memories", "mem_1", memory); - - const result = (await sdk.trigger("mem::verify", { id: "mem_1" })) as { - success: boolean; - type: string; - citations: Array<{ - observationId: string; - confidence: number; - sessionProject: string; - }>; - citationCount: number; - }; - - expect(result.success).toBe(true); - expect(result.type).toBe("memory"); - expect(result.citationCount).toBe(1); - expect(result.citations[0].observationId).toBe("obs_1"); - expect(result.citations[0].confidence).toBe(0.85); - expect(result.citations[0].sessionProject).toBe("/test/project"); - }); - - it("verifies a memory with no source observations", async () => { - const memory: Memory = { - id: "mem_2", - createdAt: "2026-03-01T00:00:00Z", - updatedAt: "2026-03-01T00:00:00Z", - type: "fact", - title: "API uses REST", - content: "The API follows REST conventions", - concepts: ["api", "rest"], - files: [], - sessionIds: [], - strength: 5, - version: 1, - isLatest: true, - }; - await kv.set("mem:memories", "mem_2", memory); - - const result = (await sdk.trigger("mem::verify", { id: "mem_2" })) as { - success: boolean; - type: string; - citationCount: number; - citations: unknown[]; - }; - - expect(result.success).toBe(true); - expect(result.type).toBe("memory"); - expect(result.citationCount).toBe(0); - expect(result.citations).toEqual([]); - }); - - it("verifies an observation directly", async () => { - const session: Session = { - id: "ses_2", - project: "/my/project", - cwd: "/my", - startedAt: "2026-03-01T00:00:00Z", - status: "active", - observationCount: 1, - }; - await kv.set("mem:sessions", "ses_2", session); - - const obs: CompressedObservation = { - id: "obs_direct", - sessionId: "ses_2", - timestamp: "2026-03-01T00:01:00Z", - type: "file_write", - title: "Created index.ts", - facts: ["Created file"], - narrative: "Agent created the index file", - concepts: ["typescript"], - files: ["index.ts"], - importance: 6, - confidence: 0.72, - }; - await kv.set("mem:obs:ses_2", "obs_direct", obs); - - const result = (await sdk.trigger("mem::verify", { - id: "obs_direct", - })) as { - success: boolean; - type: string; - observation: { id: string; confidence: number }; - session: { project: string }; - }; - - expect(result.success).toBe(true); - expect(result.type).toBe("observation"); - expect(result.observation.id).toBe("obs_direct"); - expect(result.observation.confidence).toBe(0.72); - expect(result.session.project).toBe("/my/project"); - }); - - it("returns memory info with supersede chain", async () => { - const memory: Memory = { - id: "mem_v2", - createdAt: "2026-03-02T00:00:00Z", - updatedAt: "2026-03-02T00:00:00Z", - type: "pattern", - title: "Updated pattern", - content: "Updated pattern content", - concepts: ["testing"], - files: [], - sessionIds: [], - strength: 9, - version: 2, - parentId: "mem_v1", - supersedes: ["mem_v1"], - isLatest: true, - }; - await kv.set("mem:memories", "mem_v2", memory); - - const result = (await sdk.trigger("mem::verify", { id: "mem_v2" })) as { - success: boolean; - memory: { - id: string; - version: number; - parentId: string; - supersedes: string[]; - }; - }; - - expect(result.success).toBe(true); - expect(result.memory.version).toBe(2); - expect(result.memory.parentId).toBe("mem_v1"); - expect(result.memory.supersedes).toEqual(["mem_v1"]); - }); -}); diff --git a/test/viewer-app-utils.test.ts b/test/viewer-app-utils.test.ts deleted file mode 100644 index a430298d..00000000 --- a/test/viewer-app-utils.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { readFileSync } from "node:fs"; -import { join } from "node:path"; - -// First frontend unit tests (PLAN-007 STEP-03). The viewer SPA is one shared- -// scope tag`).not.toMatch(/<\/script>/); - // a bare "', - ); - } - }); -}); - -describe("viewer i18n + transport extraction (PLAN-007 STEP-04)", () => { - it("isolates the i18n catalog + t() into app/05-i18n.js", () => { - const i18n = readFileSync(join(PARTS_DIR, "app", "05-i18n.js"), "utf-8"); - expect(i18n).toContain("I18N_MESSAGES"); - expect(i18n).toMatch(/function t\(/); - }); - - it("isolates the REST transport (api) into app/45-transport.js", () => { - const transport = readFileSync(join(PARTS_DIR, "app", "45-transport.js"), "utf-8"); - expect(transport).toMatch(/function api\(/); - }); -}); - -describe("viewer render/dispatch fragmentation (PLAN-007 STEP-05)", () => { - it("splits the render layer into per-view app fragments + a bootstrap tail", () => { - const m = manifest(); - expect(m).not.toContain("app/90-rest.js"); - expect(m.some((n) => /^app\/.*(actions|todo)/.test(n)), "no To-Do render fragment").toBe(true); - expect(m.some((n) => /^app\/.*bootstrap/.test(n)), "no bootstrap fragment").toBe(true); - }); - - it("isolates the To-Do render (renderActions) into app/60-actions-todo.js", () => { - const todo = readFileSync(join(PARTS_DIR, "app", "60-actions-todo.js"), "utf-8"); - expect(todo).toMatch(/function renderActions\(/); - }); -}); - - - - diff --git a/test/viewer-done-section.test.ts b/test/viewer-done-section.test.ts deleted file mode 100644 index 7b38c908..00000000 --- a/test/viewer-done-section.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -import * as vm from "node:vm"; -import { describe, expect, it } from "vitest"; -import { renderViewerDocument } from "../src/viewer/document.js"; - -// STEP-C4「已完成」折叠区:只读 action.status==='done' 且当天 updatedAt 的项, -// 默认折叠,纯前端筛现有数据。锁定:① isUpdatedToday 当天判定 ② 折叠区只收当天 done -// ③ 展开/折叠由 state.actions.doneExpanded 驱动 ④ 无当天 done 不渲染 ⑤ 不动后端。 - -function htmlEscape(value: string): string { - return value - .replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); -} - -function loadViewerSandbox() { - const rendered = renderViewerDocument(); - if (!rendered.found) throw new Error("viewer document not found"); - const scriptMatch = rendered.html.match(/'); - expect(out).not.toContain(" { - const render = loadRenderer(); - const out = render(''); - // 整段被转义为纯文本展示(无活动 标签、无未转义引号),onerror 仅作为字面文字存在 - expect(out).not.toContain(" { - const render = loadRenderer(); - const out = render("[click](javascript:alert(1))"); - // 非 http(s) → 不放行为 ;原文被转义保留,无可执行链接 - expect(out).not.toContain('href="javascript:'); - expect(out).not.toContain(" { - const render = loadRenderer(); - const out = render("```\n\n```"); - expect(out).toContain("x"); - }); -}); - -describe("renderMarkdownSafe — correct rendering (STEP-04)", () => { - it("renders headings as bounded

-

(not h1/h2)", () => { - const render = loadRenderer(); - expect(render("# Title")).toContain(" { - const render = loadRenderer(); - expect(render("```\nconst a = 1;\n```")).toContain("npm test'); - }); - - it("renders unordered lists", () => { - const render = loadRenderer(); - const out = render("- one\n- two"); - expect(out).toContain("/g) || []).length).toBe(2); - }); - - it("renders bold, italic, and safe http links", () => { - const render = loadRenderer(); - expect(render("**bold**")).toContain("bold"); - expect(render("a *word* b")).toContain("word"); - const link = render("[docs](https://example.com/x)"); - expect(link).toContain('href="https://example.com/x"'); - expect(link).toContain('rel="noopener noreferrer"'); - }); - - it("returns empty string for blank input", () => { - const render = loadRenderer(); - expect(render("")).toBe(""); - expect(render(" ")).toBe(""); - }); - - // 回归(评测发现):两个含 * 的行内码之间的文本被 emphasis 跨 span 误配。 - it("does not emphasize text between two inline-code spans containing *", () => { - const render = loadRenderer(); - const out = render("我改完了 `/api/*`,但 `/admin/*` 你之前没提"); - // 两个路径都成代码片段,星号原样保留在 内 - expect(out).toContain("/api/*"); - expect(out).toContain("/admin/*"); - // 关键:不该出现 (此前 `*`,但 `/admin/*` 之间被错误斜体) - expect(out).not.toContain(""); - }); - - it("inline code with * does not leak emphasis into surrounding bold", () => { - const render = loadRenderer(); - const out = render("**加**:与 `/api/*` 一致"); - expect(out).toContain(""); - expect(out).toContain("/api/*"); - expect(out).not.toContain(""); - }); -}); \ No newline at end of file diff --git a/test/viewer-memories-sort.test.ts b/test/viewer-memories-sort.test.ts deleted file mode 100644 index 93c2917c..00000000 --- a/test/viewer-memories-sort.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { readFileSync } from "node:fs"; - -// Viewer Memories tab used to render in KV-insertion order, hiding new -// entries at the bottom of long lists (#674). loadMemories() now sorts -// the response newest-first on `createdAt` (fallback `updatedAt`) before -// renderMemories sees it. Matches the pattern used by Sessions, which -// sorts by the canonical session record time desc via localeCompare. -describe("viewer Memories tab sorts newest first (#674)", () => { - const viewer = readFileSync("src/viewer/index.html", "utf-8"); - - it("loadMemories sorts items by createdAt desc before storing in state", () => { - expect(viewer).toMatch( - /loadMemories[\s\S]*?items\.sort\(function\(a,\s*b\)\s*\{[\s\S]*?bc\.localeCompare\(ac\)/, - ); - }); - - it("sort falls back to updatedAt when createdAt is missing", () => { - expect(viewer).toMatch( - /\(a && a\.createdAt\) \|\| \(a && a\.updatedAt\)/, - ); - expect(viewer).toMatch( - /\(b && b\.createdAt\) \|\| \(b && b\.updatedAt\)/, - ); - }); - - it("Memories sort mirrors the Sessions localeCompare descending pattern", () => { - expect(viewer).toMatch( - /items = state\.sessions\.items\.slice\(\)\.sort\(function\(a, b\) \{\s*return \(sessionRecordTime\(b\) \|\| ''\)\.localeCompare\(sessionRecordTime\(a\) \|\| ''\);\s*\}\)/, - ); - }); -}); diff --git a/test/viewer-security.test.ts b/test/viewer-security.test.ts deleted file mode 100644 index a881a4fd..00000000 --- a/test/viewer-security.test.ts +++ /dev/null @@ -1,438 +0,0 @@ -import { describe, it, expect, afterAll } from "vitest"; -import type { AddressInfo } from "node:net"; -import { request as httpRequest } from "node:http"; -import { renderViewerDocument } from "../src/viewer/document.js"; -import { KV } from "../src/state/schema.js"; -import type { Action, CompressedObservation, Session } from "../src/types.js"; -import { - buildAllowedHosts, - isHostAllowed, - proxyTimeoutMsForPath, - startViewerServer, -} from "../src/viewer/server.js"; - -describe("viewer document security", () => { - it("serves a nonce-backed CSP without unsafe-inline script execution", () => { - const rendered = renderViewerDocument(); - expect(rendered.found).toBe(true); - if (!rendered.found) return; - - expect(rendered.csp).toContain("script-src 'nonce-"); - expect(rendered.csp).toContain("script-src-attr 'none'"); - expect(rendered.csp).toContain("img-src 'self'"); - expect(rendered.csp).not.toContain("script-src 'unsafe-inline'"); - expect(rendered.html).toContain("