Showing preview only (5,398K chars total). Download the full file or copy to clipboard to get everything.
Repository: mem9-ai/mem9
Branch: main
Commit: 6b2249f21285
Files: 592
Total size: 4.9 MB
Directory structure:
gitextract_odup76ae/
├── .agents/
│ └── plugins/
│ └── marketplace.json
├── .claude-plugin/
│ └── marketplace.json
├── .github/
│ └── workflows/
│ ├── deploy-dev.yml
│ ├── server-plugin-checks.yml
│ └── sync-claude-plugin.yml
├── .gitignore
├── AGENTS.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── benchmark/
│ ├── BASELINE.md
│ ├── MR-NIAH/
│ │ ├── AGENTS.md
│ │ ├── README.md
│ │ ├── USAGE.md
│ │ ├── fetch_data.py
│ │ ├── mr-niah-transcript.py
│ │ ├── run_batch.py
│ │ ├── run_mem_compare.sh
│ │ └── score.py
│ ├── README.md
│ ├── locomo/
│ │ ├── README.md
│ │ ├── USAGE.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── cli.ts
│ │ │ ├── evaluation.ts
│ │ │ ├── ingest.ts
│ │ │ ├── llm.ts
│ │ │ ├── mem9.ts
│ │ │ ├── retrieve.ts
│ │ │ ├── stats.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── prompts/
│ │ └── example.yaml
│ ├── results/
│ │ └── .gitkeep
│ ├── scripts/
│ │ ├── benchmark.sh
│ │ ├── drive-session.py
│ │ └── report.py
│ └── workspace/
│ ├── IDENTITY.md
│ ├── SOUL.md
│ └── USER.md
├── claude-plugin/
│ ├── .claude-plugin/
│ │ └── plugin.json
│ ├── AGENTS.md
│ ├── README.md
│ ├── hooks/
│ │ ├── common.sh
│ │ ├── hooks.json
│ │ ├── lib/
│ │ │ ├── hook-json.mjs
│ │ │ ├── memories-formatter.mjs
│ │ │ └── transcript-parser.mjs
│ │ ├── pre-compact.sh
│ │ ├── session-end.sh
│ │ ├── session-start.sh
│ │ ├── stop.sh
│ │ └── user-prompt-submit.sh
│ ├── skills/
│ │ ├── recall/
│ │ │ └── SKILL.md
│ │ ├── setup/
│ │ │ └── SKILL.md
│ │ └── store/
│ │ └── SKILL.md
│ └── tsconfig.json
├── cli/
│ ├── AGENTS.md
│ ├── README.md
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── codex-plugin/
│ ├── .codex-plugin/
│ │ └── plugin.json
│ ├── .gitignore
│ ├── AGENTS.md
│ ├── README.md
│ ├── bootstrap-hooks/
│ │ ├── session-start.mjs
│ │ ├── shared/
│ │ │ └── bootstrap.mjs
│ │ ├── stop.mjs
│ │ └── user-prompt-submit.mjs
│ ├── hooks/
│ │ ├── session-start.mjs
│ │ ├── shared/
│ │ │ ├── debug.mjs
│ │ │ ├── format.mjs
│ │ │ └── transcript.mjs
│ │ ├── stop.mjs
│ │ └── user-prompt-submit.mjs
│ ├── lib/
│ │ ├── config.mjs
│ │ ├── http.mjs
│ │ ├── project-root.mjs
│ │ ├── skill-runtime.mjs
│ │ └── update-check.mjs
│ ├── package.json
│ ├── skills/
│ │ ├── cleanup/
│ │ │ ├── SKILL.md
│ │ │ └── scripts/
│ │ │ └── cleanup.mjs
│ │ ├── recall/
│ │ │ ├── SKILL.md
│ │ │ ├── agents/
│ │ │ │ └── openai.yaml
│ │ │ └── scripts/
│ │ │ └── recall.mjs
│ │ ├── setup/
│ │ │ ├── SKILL.md
│ │ │ └── scripts/
│ │ │ └── setup.mjs
│ │ └── store/
│ │ ├── SKILL.md
│ │ ├── agents/
│ │ │ └── openai.yaml
│ │ └── scripts/
│ │ └── store.mjs
│ ├── templates/
│ │ └── hooks.json
│ ├── tests/
│ │ ├── bootstrap-hooks.test.mjs
│ │ ├── cleanup.test.mjs
│ │ ├── debug.test.mjs
│ │ ├── plugin-files.test.mjs
│ │ ├── recall.test.mjs
│ │ ├── runtime-config.test.mjs
│ │ ├── session-start.test.mjs
│ │ ├── setup.test.mjs
│ │ ├── smoke.test.mjs
│ │ ├── stop.test.mjs
│ │ ├── store.test.mjs
│ │ ├── test-temp.mjs
│ │ ├── update-check.test.mjs
│ │ └── user-prompt-submit.test.mjs
│ └── tsconfig.json
├── dashboard/
│ ├── README.md
│ ├── app/
│ │ ├── .gitignore
│ │ ├── AGENTS.md
│ │ ├── README.md
│ │ ├── components.json
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── public/
│ │ │ └── _redirects
│ │ ├── scripts/
│ │ │ └── sentry-sourcemaps.mjs
│ │ ├── src/
│ │ │ ├── api/
│ │ │ │ ├── analysis-cache.ts
│ │ │ │ ├── analysis-client.test.ts
│ │ │ │ ├── analysis-client.ts
│ │ │ │ ├── analysis-helpers.test.ts
│ │ │ │ ├── analysis-helpers.ts
│ │ │ │ ├── analysis-matcher.test.ts
│ │ │ │ ├── analysis-matcher.ts
│ │ │ │ ├── analysis-queries.test.ts
│ │ │ │ ├── analysis-queries.ts
│ │ │ │ ├── client.ts
│ │ │ │ ├── deep-analysis-queries.test.tsx
│ │ │ │ ├── deep-analysis-queries.ts
│ │ │ │ ├── local-cache.ts
│ │ │ │ ├── mock-data.ts
│ │ │ │ ├── provider-http.test.ts
│ │ │ │ ├── provider-http.ts
│ │ │ │ ├── provider-mock.test.ts
│ │ │ │ ├── provider-mock.ts
│ │ │ │ ├── provider.ts
│ │ │ │ ├── queries.test.ts
│ │ │ │ ├── queries.ts
│ │ │ │ ├── source-memories.test.ts
│ │ │ │ └── source-memories.ts
│ │ │ ├── assets/
│ │ │ │ ├── ark-pixel-font-10px-monospaced-otf-v2026.02.27/
│ │ │ │ │ ├── OFL.txt
│ │ │ │ │ ├── ark-pixel-10px-monospaced-ja.otf
│ │ │ │ │ ├── ark-pixel-10px-monospaced-ko.otf
│ │ │ │ │ ├── ark-pixel-10px-monospaced-latin.otf
│ │ │ │ │ ├── ark-pixel-10px-monospaced-zh_cn.otf
│ │ │ │ │ ├── ark-pixel-10px-monospaced-zh_hk.otf
│ │ │ │ │ ├── ark-pixel-10px-monospaced-zh_tr.otf
│ │ │ │ │ └── ark-pixel-10px-monospaced-zh_tw.otf
│ │ │ │ └── audio/
│ │ │ │ └── bgm10-full-loop.opus
│ │ │ ├── components/
│ │ │ │ ├── lang-toggle.tsx
│ │ │ │ ├── pixel-farm/
│ │ │ │ │ ├── actor-preview-panel.tsx
│ │ │ │ │ ├── feedback-dialog.test.tsx
│ │ │ │ │ ├── feedback-dialog.tsx
│ │ │ │ │ ├── front-target-panel.tsx
│ │ │ │ │ ├── phaser-stage.tsx
│ │ │ │ │ ├── pointer-coordinates-panel.tsx
│ │ │ │ │ ├── world-state-panel.test.tsx
│ │ │ │ │ └── world-state-panel.tsx
│ │ │ │ ├── space/
│ │ │ │ │ ├── add-dialog.tsx
│ │ │ │ │ ├── analysis-panel.test.tsx
│ │ │ │ │ ├── analysis-panel.tsx
│ │ │ │ │ ├── deep-analysis-overlay.tsx
│ │ │ │ │ ├── deep-analysis-tab.test.tsx
│ │ │ │ │ ├── deep-analysis-tab.tsx
│ │ │ │ │ ├── delete-dialog.tsx
│ │ │ │ │ ├── detail-panel.tsx
│ │ │ │ │ ├── edit-dialog.tsx
│ │ │ │ │ ├── empty-state.tsx
│ │ │ │ │ ├── export-dialog.tsx
│ │ │ │ │ ├── import-dialog.tsx
│ │ │ │ │ ├── import-status.tsx
│ │ │ │ │ ├── memory-card.test.tsx
│ │ │ │ │ ├── memory-card.tsx
│ │ │ │ │ ├── memory-composition-chart.test.tsx
│ │ │ │ │ ├── memory-composition-chart.tsx
│ │ │ │ │ ├── memory-farm-preparation-dialog.tsx
│ │ │ │ │ ├── memory-farm-promo-card.test.tsx
│ │ │ │ │ ├── memory-farm-promo-card.tsx
│ │ │ │ │ ├── memory-insight-layout.test.ts
│ │ │ │ │ ├── memory-insight-layout.ts
│ │ │ │ │ ├── memory-insight-overview.test.tsx
│ │ │ │ │ ├── memory-insight-overview.tsx
│ │ │ │ │ ├── memory-insight-relations.tsx
│ │ │ │ │ ├── memory-insight-workspace.test.tsx
│ │ │ │ │ ├── memory-insight-workspace.tsx
│ │ │ │ │ ├── memory-overview-tabs.test.tsx
│ │ │ │ │ ├── memory-overview-tabs.tsx
│ │ │ │ │ ├── memory-pulse-overview.test.tsx
│ │ │ │ │ ├── memory-pulse-overview.tsx
│ │ │ │ │ ├── memory-rhythm-chart.tsx
│ │ │ │ │ ├── memory-signal-stack.tsx
│ │ │ │ │ ├── mobile-analysis-sheet.tsx
│ │ │ │ │ ├── mobile-detail-sheet.test.tsx
│ │ │ │ │ ├── mobile-detail-sheet.tsx
│ │ │ │ │ ├── mobile-panel-shell.tsx
│ │ │ │ │ ├── session-preview.tsx
│ │ │ │ │ ├── space-page-layout.tsx
│ │ │ │ │ ├── space-selectors.test.ts
│ │ │ │ │ ├── space-selectors.ts
│ │ │ │ │ ├── space-tools.tsx
│ │ │ │ │ ├── space-view-utils.ts
│ │ │ │ │ ├── tag-strip.test.tsx
│ │ │ │ │ ├── tag-strip.tsx
│ │ │ │ │ ├── time-range.tsx
│ │ │ │ │ ├── topic-strip.tsx
│ │ │ │ │ ├── use-memory-farm-entry-state.test.ts
│ │ │ │ │ ├── use-memory-farm-entry-state.ts
│ │ │ │ │ ├── use-space-data-model.test.tsx
│ │ │ │ │ ├── use-space-data-model.ts
│ │ │ │ │ └── use-space-route-state.ts
│ │ │ │ ├── theme-toggle.tsx
│ │ │ │ └── ui/
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── button-group.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── dialog.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ ├── input.tsx
│ │ │ │ ├── progress.tsx
│ │ │ │ ├── select.tsx
│ │ │ │ ├── switch.tsx
│ │ │ │ └── tabs.tsx
│ │ │ ├── config/
│ │ │ │ └── features.ts
│ │ │ ├── i18n/
│ │ │ │ ├── index.ts
│ │ │ │ └── locales/
│ │ │ │ ├── en.json
│ │ │ │ └── zh-CN.json
│ │ │ ├── index.css
│ │ │ ├── lib/
│ │ │ │ ├── connect-bootstrap-init.ts
│ │ │ │ ├── connect-bootstrap.test.ts
│ │ │ │ ├── connect-bootstrap.ts
│ │ │ │ ├── ga4.ts
│ │ │ │ ├── memory-derived-signals.test.ts
│ │ │ │ ├── memory-derived-signals.ts
│ │ │ │ ├── memory-filters.test.ts
│ │ │ │ ├── memory-filters.ts
│ │ │ │ ├── memory-insight-background.test.ts
│ │ │ │ ├── memory-insight-background.ts
│ │ │ │ ├── memory-insight-background.worker.ts
│ │ │ │ ├── memory-insight-entities.ts
│ │ │ │ ├── memory-insight-relations.test.ts
│ │ │ │ ├── memory-insight-relations.ts
│ │ │ │ ├── memory-insight.test.ts
│ │ │ │ ├── memory-insight.ts
│ │ │ │ ├── memory-pulse.test.ts
│ │ │ │ ├── memory-pulse.ts
│ │ │ │ ├── mixpanel-auto-click.ts
│ │ │ │ ├── mixpanel.ts
│ │ │ │ ├── pixel-farm/
│ │ │ │ │ ├── baby-cow.ts
│ │ │ │ │ ├── character.ts
│ │ │ │ │ ├── chicken.ts
│ │ │ │ │ ├── collision-layer.ts
│ │ │ │ │ ├── cow.ts
│ │ │ │ │ ├── create-game.ts
│ │ │ │ │ ├── data/
│ │ │ │ │ │ ├── memory-store.ts
│ │ │ │ │ │ ├── memory-to-world.test.ts
│ │ │ │ │ │ ├── memory-to-world.ts
│ │ │ │ │ │ ├── source.ts
│ │ │ │ │ │ ├── types.ts
│ │ │ │ │ │ └── use-pixel-farm-world.ts
│ │ │ │ │ ├── depth.ts
│ │ │ │ │ ├── dialog-interaction.test.ts
│ │ │ │ │ ├── dialog-interaction.ts
│ │ │ │ │ ├── dialog-state.test.ts
│ │ │ │ │ ├── dialog-state.ts
│ │ │ │ │ ├── field-layout.test.ts
│ │ │ │ │ ├── field-layout.ts
│ │ │ │ │ ├── generated-mask-data.ts
│ │ │ │ │ ├── generated-mask-source.ts
│ │ │ │ │ ├── island-mask.ts
│ │ │ │ │ ├── npc-dialog-content.test.ts
│ │ │ │ │ ├── npc-dialog-content.ts
│ │ │ │ │ ├── npc-tips.test.ts
│ │ │ │ │ ├── npc-tips.ts
│ │ │ │ │ ├── palette.ts
│ │ │ │ │ ├── plant-dialog-content.test.ts
│ │ │ │ │ ├── plant-dialog-content.ts
│ │ │ │ │ ├── plant-placement.test.ts
│ │ │ │ │ ├── plant-placement.ts
│ │ │ │ │ ├── runtime-assets.ts
│ │ │ │ │ ├── tileset-config.ts
│ │ │ │ │ ├── ui-dialog-layout.test.ts
│ │ │ │ │ ├── ui-dialog-layout.ts
│ │ │ │ │ ├── ui-dialog-pagination.test.ts
│ │ │ │ │ ├── ui-dialog-pagination.ts
│ │ │ │ │ ├── ui-dialog.ts
│ │ │ │ │ ├── ui-scene.ts
│ │ │ │ │ ├── use-pixel-farm-npc-dialog-content.test.tsx
│ │ │ │ │ ├── use-pixel-farm-npc-dialog-content.ts
│ │ │ │ │ └── world-render.ts
│ │ │ │ ├── session.test.ts
│ │ │ │ ├── session.ts
│ │ │ │ ├── tag-signals.test.ts
│ │ │ │ ├── tag-signals.ts
│ │ │ │ ├── theme.ts
│ │ │ │ ├── time.ts
│ │ │ │ └── utils.ts
│ │ │ ├── main.tsx
│ │ │ ├── pages/
│ │ │ │ ├── connect-loader.ts
│ │ │ │ ├── connect.test.tsx
│ │ │ │ ├── connect.tsx
│ │ │ │ ├── pixel-farm-editor.tsx
│ │ │ │ ├── pixel-farm.test.tsx
│ │ │ │ ├── pixel-farm.tsx
│ │ │ │ ├── space.test.tsx
│ │ │ │ └── space.tsx
│ │ │ ├── router.tsx
│ │ │ ├── test/
│ │ │ │ └── setup.ts
│ │ │ ├── types/
│ │ │ │ ├── analysis.ts
│ │ │ │ ├── import.ts
│ │ │ │ ├── memory.ts
│ │ │ │ └── time-range.ts
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.app.json
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ ├── tsconfig.test.json
│ │ └── vite.config.ts
│ └── docs/
│ ├── dashboard-mvp-spec.md
│ ├── data-contract.md
│ ├── dev-tasks.md
│ ├── information-architecture.md
│ ├── memory-card-session-preview-demo-plan.md
│ └── ui-first-mock-plan.md
├── docs/
│ ├── BENCHMARK.md
│ ├── DESIGN.md
│ ├── api/
│ │ └── openapi.json
│ ├── design/
│ │ ├── auto-increase-spend-limit.md
│ │ ├── crdt-memory-proposal.md
│ │ ├── crdt-vector-clock-logic.md
│ │ ├── fts-hybrid-search-proposal.md
│ │ ├── issue-110-session-messages-api-proposal.md
│ │ ├── issue-115-reconcile-tags-proposal.md
│ │ ├── issue-149-recall-improvements-proposal.md
│ │ ├── issue-294-active-tenants-metric-proposal.md
│ │ ├── issue-305-active-memory-metrics-proposal.md
│ │ ├── issue-311-space-chain-e2e-proposal.md
│ │ ├── mem9-runtime-usage-client-proposal.md
│ │ ├── middleware-cluster-blacklist-proposal.md
│ │ ├── multi-database-backend-architecture-proposal.md
│ │ ├── multi-tenant-provisioning-proposal.md
│ │ ├── raw-session-storage-proposal.md
│ │ ├── smart-memory-pipeline-proposal.md
│ │ ├── space-chain-console-plan.md
│ │ ├── space-chain-prd.md
│ │ ├── tidbcloud-pool-api-migration-design.md
│ │ ├── time-aware-recall-proposal.md
│ │ └── utm-skill-onboarding-proposal.md
│ ├── harness/
│ │ ├── benchmark_answering_temporal_assist_accepted_20260412T184441.md
│ │ ├── context_selection_and_answer_canonicalization_20260422T194809.md
│ │ ├── enumeration_adjacent_turn_success_20260425T2230.md
│ │ └── failure_server_recall_migration_20260423T000347.md
│ └── superpowers/
│ └── specs/
│ ├── 2026-03-26-pixel-farm-interaction-performance-design.md
│ └── 2026-03-27-pixel-farm-ui-scene-dialog-design.md
├── e2e/
│ ├── AGENTS.md
│ ├── README.md
│ ├── api-smoke-test-existing-tenant.sh
│ ├── api-smoke-test-round2-v1alpha2.sh
│ ├── api-smoke-test-round2.sh
│ ├── api-smoke-test-sessions.sh
│ ├── api-smoke-test-space-chain.sh
│ ├── api-smoke-test-utm.sh
│ ├── api-smoke-test-v1alpha2.sh
│ ├── api-smoke-test.sh
│ ├── concurrent-real-doc-test.py
│ ├── crdt-e2e-tests.sh
│ ├── crdt-server-merge-e2e.py
│ └── plugin-crdt-e2e.py
├── openclaw-plugin/
│ ├── .gitignore
│ ├── AGENTS.md
│ ├── README.md
│ ├── backend.ts
│ ├── hooks.ts
│ ├── index.test.ts
│ ├── index.ts
│ ├── openclaw.plugin.json
│ ├── package.json
│ ├── publish.sh
│ ├── server-backend.test.ts
│ ├── server-backend.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── types.ts
├── opencode-plugin/
│ ├── .gitignore
│ ├── AGENTS.md
│ ├── README.md
│ ├── package.json
│ ├── scripts/
│ │ ├── publish.mjs
│ │ ├── publish.test.mjs
│ │ └── test.mjs
│ ├── src/
│ │ ├── index.ts
│ │ ├── server/
│ │ │ ├── backend.ts
│ │ │ ├── config.ts
│ │ │ ├── debug.ts
│ │ │ ├── hooks.ts
│ │ │ ├── index.ts
│ │ │ ├── ingest/
│ │ │ │ ├── select.ts
│ │ │ │ └── submit.ts
│ │ │ ├── recall/
│ │ │ │ ├── format.ts
│ │ │ │ └── query.ts
│ │ │ ├── server-backend.ts
│ │ │ ├── session-transcript.ts
│ │ │ ├── setup-flow.ts
│ │ │ └── tools.ts
│ │ ├── shared/
│ │ │ ├── credentials-store.ts
│ │ │ ├── defaults.ts
│ │ │ ├── platform-paths.ts
│ │ │ ├── plugin-meta.ts
│ │ │ ├── setup-files.ts
│ │ │ └── types.ts
│ │ └── tui/
│ │ └── index.ts
│ ├── tests/
│ │ ├── config.test.ts
│ │ ├── credentials-store.test.ts
│ │ ├── ingest.test.ts
│ │ ├── recall.test.ts
│ │ ├── server-backend.test.ts
│ │ ├── session-transcript.test.ts
│ │ ├── setup-files.test.ts
│ │ ├── source-imports.test.ts
│ │ └── tui-setup.test.ts
│ ├── tsconfig.json
│ └── tsconfig.test.json
├── server/
│ ├── AGENTS.md
│ ├── Dockerfile
│ ├── cmd/
│ │ └── mnemo-server/
│ │ ├── main.go
│ │ └── main_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── internal/
│ │ ├── config/
│ │ │ ├── config.go
│ │ │ └── config_test.go
│ │ ├── domain/
│ │ │ ├── errors.go
│ │ │ ├── tokengen.go
│ │ │ ├── types.go
│ │ │ └── upload.go
│ │ ├── embed/
│ │ │ └── embedder.go
│ │ ├── encrypt/
│ │ │ ├── encryptor.go
│ │ │ ├── factory.go
│ │ │ ├── factory_test.go
│ │ │ ├── kms.go
│ │ │ ├── md5.go
│ │ │ ├── md5_test.go
│ │ │ ├── plain.go
│ │ │ └── plain_test.go
│ │ ├── handler/
│ │ │ ├── AGENTS.md
│ │ │ ├── chain_runtime.go
│ │ │ ├── handler.go
│ │ │ ├── memory.go
│ │ │ ├── memory_batch_delete_test.go
│ │ │ ├── memory_test.go
│ │ │ ├── metering.go
│ │ │ ├── recall.go
│ │ │ ├── recall_test.go
│ │ │ ├── runtime_usage.go
│ │ │ ├── space_chain.go
│ │ │ ├── task.go
│ │ │ ├── tenant.go
│ │ │ ├── tenant_test.go
│ │ │ └── version_test.go
│ │ ├── llm/
│ │ │ ├── client.go
│ │ │ └── client_test.go
│ │ ├── metering/
│ │ │ ├── AGENTS.md
│ │ │ ├── config.go
│ │ │ ├── console_writer.go
│ │ │ ├── gzip.go
│ │ │ ├── gzip_test.go
│ │ │ ├── path.go
│ │ │ ├── path_test.go
│ │ │ ├── s3_client.go
│ │ │ ├── s3_writer.go
│ │ │ ├── transport_writer.go
│ │ │ ├── webhook_writer.go
│ │ │ ├── writer.go
│ │ │ └── writer_test.go
│ │ ├── metrics/
│ │ │ ├── metrics.go
│ │ │ └── metrics_test.go
│ │ ├── middleware/
│ │ │ ├── auth.go
│ │ │ ├── auth_test.go
│ │ │ ├── cooldown.go
│ │ │ ├── ratelimit.go
│ │ │ └── ratelimit_test.go
│ │ ├── repository/
│ │ │ ├── db9/
│ │ │ │ ├── db9.go
│ │ │ │ ├── memory.go
│ │ │ │ ├── space_chain.go
│ │ │ │ ├── tenant.go
│ │ │ │ ├── upload_task.go
│ │ │ │ └── upload_task_test.go
│ │ │ ├── factory.go
│ │ │ ├── postgres/
│ │ │ │ ├── memory.go
│ │ │ │ ├── postgres.go
│ │ │ │ ├── space_chain.go
│ │ │ │ ├── tenant.go
│ │ │ │ └── upload_task.go
│ │ │ ├── repository.go
│ │ │ └── tidb/
│ │ │ ├── AGENTS.md
│ │ │ ├── fts_test.go
│ │ │ ├── memory.go
│ │ │ ├── memory_integration_test.go
│ │ │ ├── sessions.go
│ │ │ ├── sessions_test.go
│ │ │ ├── space_chain.go
│ │ │ ├── tenant.go
│ │ │ ├── tenant_integration_test.go
│ │ │ ├── testutil_test.go
│ │ │ ├── tidb.go
│ │ │ ├── upload_task.go
│ │ │ └── utm.go
│ │ ├── reqid/
│ │ │ └── reqid.go
│ │ ├── runtimeusage/
│ │ │ ├── client.go
│ │ │ ├── client_test.go
│ │ │ ├── manager.go
│ │ │ ├── manager_test.go
│ │ │ ├── outbox.go
│ │ │ ├── outbox_test.go
│ │ │ ├── types.go
│ │ │ ├── worker.go
│ │ │ └── worker_test.go
│ │ ├── service/
│ │ │ ├── AGENTS.md
│ │ │ ├── activity.go
│ │ │ ├── activity_test.go
│ │ │ ├── ingest.go
│ │ │ ├── ingest_test.go
│ │ │ ├── memory.go
│ │ │ ├── memory_bulk_delete_test.go
│ │ │ ├── memory_test.go
│ │ │ ├── recall.go
│ │ │ ├── search_source_turns.go
│ │ │ ├── search_source_turns_test.go
│ │ │ ├── session.go
│ │ │ ├── session_test.go
│ │ │ ├── source_provenance.go
│ │ │ ├── space_chain.go
│ │ │ ├── space_chain_test.go
│ │ │ ├── temporal_fact.go
│ │ │ ├── tenant.go
│ │ │ ├── tenant_test.go
│ │ │ ├── upload.go
│ │ │ └── upload_test.go
│ │ └── tenant/
│ │ ├── AGENTS.md
│ │ ├── pool.go
│ │ ├── pool_test.go
│ │ ├── provisioner.go
│ │ ├── provisioner_test.go
│ │ ├── schema.go
│ │ ├── schema_compat.go
│ │ ├── schema_compat_test.go
│ │ ├── starter.go
│ │ ├── starter_test.go
│ │ ├── util.go
│ │ ├── zero.go
│ │ └── zero_test.go
│ ├── schema.sql
│ ├── schema_db9.sql
│ └── schema_pg.sql
├── site/
│ ├── AGENTS.md
│ ├── astro.config.mjs
│ ├── netlify.toml
│ ├── package.json
│ ├── public/
│ │ ├── SETUP.md
│ │ ├── SKILL.md
│ │ ├── TROUBLESHOOTING.md
│ │ ├── UNINSTALL.md
│ │ └── _meta.json
│ ├── scripts/
│ │ └── netlify-build.sh
│ ├── src/
│ │ ├── components/
│ │ │ ├── Agents.astro
│ │ │ ├── AnimatedLogo.astro
│ │ │ ├── ApiReference.astro
│ │ │ ├── Benchmark.astro
│ │ │ ├── BridgeBanner.astro
│ │ │ ├── ContactModal.astro
│ │ │ ├── DocsPage.astro
│ │ │ ├── FAQ.astro
│ │ │ ├── FeatureCard.astro
│ │ │ ├── Features.astro
│ │ │ ├── Footer.astro
│ │ │ ├── Hero.astro
│ │ │ ├── Navbar.astro
│ │ │ ├── Pricing.astro
│ │ │ └── SecuritySection.astro
│ │ ├── content/
│ │ │ ├── docs.ts
│ │ │ └── site.ts
│ │ ├── layouts/
│ │ │ └── Layout.astro
│ │ ├── pages/
│ │ │ ├── api.astro
│ │ │ ├── docs.astro
│ │ │ ├── index.astro
│ │ │ ├── openclaw-memory.astro
│ │ │ └── pricing.astro
│ │ ├── scripts/
│ │ │ └── site-ui.ts
│ │ └── styles/
│ │ └── global.css
│ └── tsconfig.json
├── skills/
│ └── mnemos-setup/
│ └── SKILL.md
└── test-results/
└── .last-run.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .agents/plugins/marketplace.json
================================================
{
"name": "mem9-ai",
"interface": {
"displayName": "mem9"
},
"plugins": [
{
"name": "mem9",
"source": {
"source": "local",
"path": "./codex-plugin"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_USE"
},
"category": "Productivity"
}
]
}
================================================
FILE: .claude-plugin/marketplace.json
================================================
{
"name": "mem9",
"owner": {
"name": "mem9-ai"
},
"metadata": {
"description": "Official mem9 plugins for Claude Code"
},
"plugins": [
{
"name": "mem9",
"source": "./claude-plugin",
"description": "Persistent cloud memory for Claude Code with automatic recall, transcript ingest, and on-demand setup, recall, and store skills.",
"version": "0.3.1",
"homepage": "https://mem9.ai",
"repository": "https://github.com/mem9-ai/mem9",
"license": "Apache-2.0"
}
]
}
================================================
FILE: .github/workflows/deploy-dev.yml
================================================
name: Deploy server to dev
on:
push:
branches: [main]
paths:
- 'server/**'
env:
AWS_REGION: ap-southeast-1
ECR_REGISTRY: 401696231252.dkr.ecr.ap-southeast-1.amazonaws.com
ECR_REPOSITORY: mnemo-server
EKS_CLUSTER: dev-mem9-eks-ap-southeast-1
DEPLOY_NAMESPACE: mnemos
K8S_DEPLOYMENT: mnemos-server
jobs:
deploy:
name: Build and deploy to dev
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: server/go.mod
cache-dependency-path: server/go.sum
- name: Vet
run: make vet
- name: Test
run: make test
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::401696231252:role/dev-mem9-eks-ap-southeast-1-github-deploy
aws-region: ${{ env.AWS_REGION }}
- name: Log in to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push image
run: |
REF_NAME=${{ github.ref_name }}
REF_NAME=${REF_NAME/\//-}
TAG="${REF_NAME}-${GITHUB_SHA::7}"
REGISTRY=${{ env.ECR_REGISTRY }} COMMIT=$TAG make docker
docker push ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:$TAG
echo "IMAGE_TAG=$TAG" >> "$GITHUB_ENV"
- name: Update kubeconfig
run: |
aws eks update-kubeconfig \
--name ${{ env.EKS_CLUSTER }} \
--region ${{ env.AWS_REGION }}
- name: Deploy
run: |
kubectl set image deployment/${{ env.K8S_DEPLOYMENT }} \
${{ env.K8S_DEPLOYMENT }}=${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }} \
-n ${{ env.DEPLOY_NAMESPACE }}
kubectl rollout status deployment/${{ env.K8S_DEPLOYMENT }} \
-n ${{ env.DEPLOY_NAMESPACE }} \
--timeout=120s
- name: Rollback on failed deploy
if: failure()
run: |
kubectl rollout undo deployment/${{ env.K8S_DEPLOYMENT }} \
-n ${{ env.DEPLOY_NAMESPACE }}
================================================
FILE: .github/workflows/server-plugin-checks.yml
================================================
name: Server and plugin checks
on:
pull_request:
branches: [main]
paths:
- ".github/workflows/server-plugin-checks.yml"
- "Makefile"
- "server/**"
- "openclaw-plugin/**"
- "opencode-plugin/**"
- "claude-plugin/**"
push:
branches: [main]
paths:
- ".github/workflows/server-plugin-checks.yml"
- "Makefile"
- "server/**"
- "openclaw-plugin/**"
- "opencode-plugin/**"
- "claude-plugin/**"
workflow_dispatch:
permissions:
contents: read
jobs:
server-test:
name: Server vet and tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: server/go.mod
cache-dependency-path: server/go.sum
- name: Vet
run: make vet
- name: Test with coverage
run: make test-cover
- name: Print coverage summary
if: always()
run: |
if [ -f server/coverage/coverage.txt ]; then
tail -n 1 server/coverage/coverage.txt
fi
- name: Upload server coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: server-coverage
path: |
server/coverage/coverage.out
server/coverage/coverage.txt
if-no-files-found: warn
retention-days: 14
plugin-typecheck:
name: Plugin typecheck
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Typecheck OpenClaw plugin
working-directory: openclaw-plugin
run: |
npm install --no-audit --no-fund
npm run typecheck
- name: Typecheck OpenCode plugin
working-directory: opencode-plugin
run: |
npm install --no-audit --no-fund
npm run typecheck
- name: Check Claude plugin hooks
working-directory: claude-plugin
run: |
bash -n hooks/common.sh hooks/pre-compact.sh hooks/session-end.sh hooks/session-start.sh hooks/stop.sh hooks/user-prompt-submit.sh
node --check hooks/lib/hook-json.mjs
node --check hooks/lib/memories-formatter.mjs
node --check hooks/lib/transcript-parser.mjs
================================================
FILE: .github/workflows/sync-claude-plugin.yml
================================================
name: Sync claude-plugin to standalone repo
on:
push:
branches: [main]
paths:
- 'claude-plugin/**'
jobs:
sync:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout monorepo
if: ${{ env.HAS_DEPLOY_KEY == 'true' }}
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Push claude-plugin/ to mem9-claude-plugin
if: ${{ env.HAS_DEPLOY_KEY == 'true' }}
uses: cpina/github-action-push-to-another-repository@v1.7.2
env:
SSH_DEPLOY_KEY: ${{ secrets.CLAUDE_PLUGIN_DEPLOY_KEY }}
with:
source-directory: 'claude-plugin'
destination-github-username: 'mem9-ai'
destination-repository-name: 'mem9-claude-plugin'
target-branch: 'main'
commit-message: 'sync from mem9-ai/mem9@${{ github.sha }}'
env:
HAS_DEPLOY_KEY: ${{ secrets.CLAUDE_PLUGIN_DEPLOY_KEY != '' }}
================================================
FILE: .gitignore
================================================
# OS
.DS_Store
Thumbs.db
# Editors
.idea/
.vscode/
*.swp
*.swo
*~
# Environment and secrets
.env
.env.local
.publish.env
.npmrc
# JavaScript package outputs
node_modules/
dist/
.astro/
*.tgz
.netlify/
# Python cache
__pycache__/
# Go build outputs
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out
vendor/
# Server outputs
server/bin
server/coverage/
server/.tmp
# Benchmark results
benchmark/results/*
!benchmark/results/.gitkeep
benchmark/MR-NIAH/origin/
benchmark/MR-NIAH/output/
benchmark/MR-NIAH/results*/
benchmark/MR-NIAH/logs/
benchmark/MR-NIAH/results-logs/
benchmark/MR-NIAH/.cache/
# Claude Code and experiment outputs
.claude/
================================================
FILE: AGENTS.md
================================================
---
title: mnemos — Agent context
---
## What this repo is
mnemos is shared, cloud-persistent memory for coding agents. The core system is a Go
REST server backed by TiDB/MySQL, plus four agent integrations, a standalone CLI,
and a small Astro site.
## Cross-repo relationship: `mem9` and `mem9-node`
- `mem9-node` is a sibling repository at `../mem9-node`. It is not a directory inside this repo.
- `dashboard/app` in this repo is the frontend half of the dashboard product. In day-to-day discussion, "the dashboard backend" usually refers to code in `mem9-node`, especially `apps/api` and `apps/worker`.
- `dashboard/app/src/api/analysis-client.ts` calls `mem9-node` endpoints for `v1/analysis-jobs`, `v1/deep-analysis/*`, and taxonomy/deep-analysis workflows.
- `mem9-node/apps/api/src/mem9-source.service.ts` depends on this repo's Go API as the mem9 source of truth. Its `MEM9_SOURCE_API_BASE_URL` defaults to `http://127.0.0.1:8080/v1alpha2/mem9s`.
- `dashboard/app/src/api/provider-http.ts` still sends the dashboard's standard `/your-memory/api/...` data requests to this repo's Go server (`/v1alpha2/mem9s/...`) using `X-API-Key` and `X-Mnemo-Agent-Id`.
- When a task touches dashboard UI and backend behavior together, inspect both repos before assuming the implementation belongs only under `server/` in this repo.
## High-level modules
| Path | Role |
| -------------------- | ------------------------------------------------------------ |
| `server/` | Go API server, business logic, TiDB SQL, tenant provisioning, runtime usage |
| `cli/` | Standalone Go CLI for exercising mnemo-server endpoints |
| `dashboard/app/` | React dashboard SPA; frontend half of the dashboard product |
| `openclaw-plugin/` | OpenClaw memory plugin (`kind: "memory"`) |
| `opencode-plugin/` | OpenCode plugin (`@mem9/opencode`) |
| `claude-plugin/` | Claude Code plugin (hooks + skills) |
| `codex-plugin/` | Codex plugin (hooks + `$mem9:*` skills) |
| `docs/design/` | Architecture/proposal notes and design drafts |
| `site/` | Astro static site — deployed to Netlify from `main` branch |
| `e2e/` | Live end-to-end scripts against a running server |
| `benchmark/MR-NIAH/` | Benchmark harness for OpenClaw memory evaluation |
## Commands
```bash
# Go server build / verify
make build
make vet
make test
make test-integration
# Single Go test
cd server && go test -race -count=1 -run TestFunctionName ./internal/service/
# TypeScript plugin verification
cd openclaw-plugin && npm test
cd openclaw-plugin && npm run typecheck
cd opencode-plugin && pnpm test
cd opencode-plugin && pnpm run typecheck
pnpm --dir codex-plugin test
pnpm --dir codex-plugin typecheck
# Site dev/build
cd site && npm run dev
cd site && npm run build
# CLI build
cd cli && go build -o mnemo .
# Run server locally
cd server && MNEMO_DSN="user:pass@tcp(host:4000)/db?parseTime=true" go run ./cmd/mnemo-server
```
## Global conventions
- Architecture is strict `handler -> service -> repository`; plugins always call the HTTP API.
- No ORM. Server SQL is raw `database/sql` with parameter placeholders only.
- `embed.New()` and `llm.New()` may return `nil`; callers must branch correctly.
- Vector and keyword search each fetch `limit * 3` before RRF merge.
- `INSERT ... ON DUPLICATE KEY UPDATE` is the expected upsert pattern.
- Atomic version bump happens in SQL: `SET version = version + 1`.
- `X-Mnemo-Agent-Id` is the per-agent identity header for memory requests.
- Legacy API metering uses `MNEMO_METERING_*`; runtime usage quota and console metering use `MNEMO_RUNTIME_USAGE_*` and do not use `MNEMO_METERING_URL`.
- Always use `make` targets for building and Docker image operations — never construct raw `go build` or `docker build` commands from scratch. Use `make build-linux` for the server binary and `REGISTRY=<ecr> COMMIT=<tag> make docker` for images.
## Go style
- Format with `gofmt` only.
- Imports use three groups: stdlib, external, internal.
- Use `PascalCase` for exported names, `camelCase` for unexported names.
- Acronyms stay all-caps inside identifiers: `tenantID`, `agentID`.
- Sentinel errors live in `server/internal/domain/errors.go`; compare with `errors.Is()`.
- Wrap errors with `fmt.Errorf("context: %w", err)`.
- Validation errors use `&domain.ValidationError{Field: ..., Message: ...}`.
- HTTP/domain error mapping stays centralized in `server/internal/handler/handler.go`.
## TypeScript style
- ESM only: `"type": "module"`, `module: "NodeNext"` or local package equivalent.
- Always use `.js` on local imports when the package uses NodeNext.
- Use `import type` for type-only imports.
- Formatting is consistent: double quotes, semicolons, trailing commas in multi-line literals.
- Public methods use explicit return types.
- Nullable is `T | null`; optional is `field?: T`.
- No `any`.
- Tool/error strings use `err instanceof Error ? err.message : String(err)`.
## Bash and hooks
- Hook scripts start with `set -euo pipefail`.
- Use Python for JSON/url-encoding helpers instead of `jq` in hook logic.
- `curl` calls use explicit timeouts.
## SQL / storage rules
- Tags are JSON arrays; store `[]`, never `NULL`.
- Filter tags with `JSON_CONTAINS`.
- Every vector search must include `embedding IS NOT NULL`.
- `VEC_COSINE_DISTANCE(...)` must match in `SELECT` and `ORDER BY` byte-for-byte.
- When `autoModel != ""`, do not write the `embedding` column; it is generated.
- `MNEMO_EMBED_AUTO_MODEL` and `MNEMO_EMBED_API_KEY` represent different embedding modes.
## Where to look
| Task | File |
| -------------------- | ------------------------------------------- |
| Add/change route | `server/internal/handler/handler.go` |
| Memory CRUD / search | `server/internal/service/memory.go` |
| Ingest pipeline | `server/internal/service/ingest.go` |
| TiDB SQL | `server/internal/repository/tidb/memory.go` |
| Tenant provisioning | `server/internal/service/tenant.go` |
| Runtime usage quota | `server/internal/runtimeusage/` |
| Metering writer | `server/internal/metering/` |
| CLI command wiring | `cli/main.go` |
| Dashboard frontend | `dashboard/app/` |
| Dashboard backend (sibling repo) | `../mem9-node/apps/api/` |
| Dashboard worker (sibling repo) | `../mem9-node/apps/worker/` |
| Claude hooks | `claude-plugin/hooks/` |
| Codex hooks and skills | `codex-plugin/` |
| Architecture notes | `docs/design/` |
| OpenCode wiring | `opencode-plugin/src/index.ts` |
| OpenClaw wiring | `openclaw-plugin/index.ts` |
| Site copy/content | `site/src/content/site.ts` |
| Production SKILL.md | `site/public/SKILL.md` |
## site/ — Netlify deployment
`/site/` is the deployment directory for the mem9.ai static website.
It is hosted on Netlify and **automatically deployed from the `main` branch**.
| File | Purpose |
|---|---|
| `site/public/SKILL.md` | **Production** SKILL.md — served at `https://mem9.ai/SKILL.md` |
When updating the SKILL.md that agents fetch, edit **only** these two files:
- `site/public/SKILL.md` — production, changes go live within seconds after merging to `main`
Do **not** edit any other copy (e.g. `clawhub-skill/mem9/SKILL.md` has been removed).
Do **not** manually sync to clawhub — Netlify handles publishing automatically.
## Hierarchical AGENTS.md files
Use the local file when you work in these areas:
- `server/AGENTS.md`
- `server/internal/handler/AGENTS.md`
- `server/internal/metering/AGENTS.md`
- `server/internal/service/AGENTS.md`
- `server/internal/repository/tidb/AGENTS.md`
- `server/internal/tenant/AGENTS.md`
- `cli/AGENTS.md`
- `openclaw-plugin/AGENTS.md`
- `opencode-plugin/AGENTS.md`
- `claude-plugin/AGENTS.md`
- `codex-plugin/AGENTS.md`
- `site/AGENTS.md`
- `dashboard/app/AGENTS.md`
- `e2e/AGENTS.md`
- `benchmark/MR-NIAH/AGENTS.md`
Validate this map after editing:
```bash
python3 -c 'from pathlib import Path; import re, subprocess; text = Path("AGENTS.md").read_text(); paths = re.findall(r"`([^`]+/AGENTS\.md)`", text); tracked = set(subprocess.check_output(["git", "ls-files", "*AGENTS.md"], text=True).splitlines()); missing = [p for p in paths if p not in tracked]; print("\n".join(missing)); raise SystemExit(1 if missing else 0)'
```
## GitHub access
Prefer `gh` CLI to read GitHub content (issues, PRs, file contents, comments). Fall back
to `curl` or `webfetch` only when `gh` is unavailable or does not work. Examples:
```bash
# View a PR
gh pr view <number>
# Read a file from a specific ref
gh api repos/{owner}/{repo}/contents/{path}?ref={branch} --jq '.content' | base64 -d
# List issues or PR comments
gh issue view <number> --comments
gh pr view <number> --comments
```
### Review loop approval policy
When the user names a specific PR and says `run the review loop`, `use the loop
to resolve review comments`, or equivalent wording, treat that as approval for a
bounded review-comment resolution loop on that PR.
This approval covers only actions required by the loop:
1. Commit changes that directly address review feedback.
2. Push those commits to the PR branch.
3. Post GitHub review-thread replies.
4. Resolve fixed GitHub review threads.
5. Post the configured GitHub reviewer trigger comment, such as `@codex review`.
6. Repeat the same sequence until the loop reaches its configured stop condition.
This approval does not cover force-pushes, rebases, merges, creating new PRs,
deployments, deleting files outside the working tree, or unrelated code changes.
Stop and ask before those actions. Stop and ask if a review comment requires a
product or architecture decision that is not clearly implied by the PR.
## Explicitly absent
- No `.cursor/rules/`, `.cursorrules`, or `.github/copilot-instructions.md` were found.
- No repo-wide TypeScript test runner is configured; plugin tests are package-local.
- No repo-wide TypeScript lint config exists, and the plugin packages do not expose `lint` scripts.
================================================
FILE: CLAUDE.md
================================================
@AGENTS.md
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to mnemos
Thanks for your interest in contributing!
## Development Setup
### Server (Go)
```bash
# Prerequisites: Go 1.22+, a MySQL-compatible database (TiDB or MySQL 8.0+)
# Clone and build
git clone https://github.com/mem9-ai/mem9.git
cd mem9/server
go mod download
go build ./cmd/mnemo-server
# Apply schema
mysql -h <host> -P <port> -u <user> -p < schema.sql
# Run
export MNEMO_DSN="user:pass@tcp(host:port)/mnemos?parseTime=true"
go run ./cmd/mnemo-server
```
### Claude Code Plugin
The claude-plugin is pure bash + curl with zero dependencies. To test locally:
```bash
export MNEMO_API_URL="http://localhost:8080"
export MNEMO_API_TOKEN="mnemo_xxx"
# Test a hook script directly
echo '{}' | ./claude-plugin/hooks/session-start.sh
```
### Agent Plugins (OpenClaw)
```bash
cd openclaw-plugin
npm install
```
This section is for the OpenClaw integration specifically; mnemos supports multiple agent platforms.
## Making Changes
1. Fork the repo and create a feature branch
2. Make your changes
3. Run `cd server && go vet ./...` to check for issues
4. Submit a pull request
## Code Style
- **Go**: `gofmt` is the standard. No additional linters required.
- **Shell**: Follow the patterns in `claude-plugin/hooks/common.sh`. Use `set -euo pipefail`.
- **TypeScript**: Follow existing patterns in agent plugin packages (`openclaw-plugin/`, `opencode-plugin/`).
## Architecture
The codebase follows a clean layered architecture:
```
HTTP Request → Handler → Service → Repository → Database
```
- **Domain types** (`internal/domain/`) are imported by all layers
- **Repository interfaces** (`internal/repository/repository.go`) define the contract
- **TiDB implementations** (`internal/repository/tidb/`) are the only SQL-aware code
- **Services** contain business logic (upsert, conflict resolution, validation)
- **Handlers** map HTTP ↔ service calls, nothing more
When adding a new feature, start from the domain types and work outward.
## Reporting Issues
Please use [GitHub Issues](https://github.com/mem9-ai/mem9/issues) with:
- Steps to reproduce
- Expected vs actual behavior
- Server version and database type
================================================
FILE: LICENSE
================================================
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 2025 mnemos contributors
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.
================================================
FILE: Makefile
================================================
MAKEFILE_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
IMG ?= $(REGISTRY)/mnemo-server:$(COMMIT)
.PHONY: build vet clean run test test-cover test-integration docker
build:
mkdir -p $(MAKEFILE_DIR)/server/bin
cd server && CGO_ENABLED=0 go build -o ./bin/mnemo-server ./cmd/mnemo-server
build-linux:
mkdir -p $(MAKEFILE_DIR)/server/bin
cd server && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/mnemo-server ./cmd/mnemo-server
vet:
cd server && go vet ./...
test:
cd server && go test -race -count=1 ./...
test-cover:
cd server && mkdir -p coverage
cd server && go test -race -count=1 -covermode=atomic -coverprofile=coverage/coverage.out ./...
cd server && go tool cover -func=coverage/coverage.out > coverage/coverage.txt
test-integration:
cd server && go test -tags=integration -race -count=1 -v ./internal/repository/tidb/
clean:
rm -f server/bin/mnemo-server
run: build
cd server && MNEMO_DSN="$(MNEMO_DSN)" ./bin/mnemo-server
docker: build-linux
docker build --platform=linux/amd64 -q -f ./server/Dockerfile -t $(IMG) .
================================================
FILE: README.md
================================================
<p align="center">
<img src="site/public/mem9-wordmark-square.svg" alt="mem9" width="180" />
</p>
<p align="center">
<strong>Persistent Memory for AI Agents.</strong><br/>
Your agents forget everything between sessions. mem9 fixes that with persistent memory across sessions and machines, shared memory for multi-agent workflows, and hybrid recall with a visual dashboard.
</p>
<p align="center">
For OpenClaw and ClawHub installs, start here: <a href="https://mem9.ai/openclaw-memory">mem9.ai/openclaw-memory</a>
<br/>
Hermes Agent, Claude Code, OpenCode, Codex, and Dify guides are below.
</p>
<p align="center">
<a href="https://tidbcloud.com"><img src="https://img.shields.io/badge/Powered%20by-TiDB%20Cloud%20Starter-E60C0C?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIj48cGF0aCBkPSJNMTEuOTk4NCAxLjk5OTAyTDMuNzE4NzUgNy40OTkwMkwzLjcxODc1IDE3TDExLjk5NjQgMjIuNUwyMC4yODE0IDE3VjcuNDk5MDJMMTEuOTk4NCAxLjk5OTAyWiIgZmlsbD0id2hpdGUiLz48L3N2Zz4=" alt="Powered by TiDB Cloud Starter"></a>
<a href="https://goreportcard.com/report/github.com/mem9-ai/mem9/server"><img src="https://goreportcard.com/badge/github.com/mem9-ai/mem9/server" alt="Go Report Card"></a>
<a href="https://github.com/mem9-ai/mem9/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="License"></a>
<a href="https://github.com/mem9-ai/mem9"><img src="https://img.shields.io/github/stars/mem9-ai/mem9?style=social" alt="Stars"></a>
</p>
---
## Quick Start
1. Choose your mem9 endpoint.
- Hosted API: `https://api.mem9.ai`
- Self-hosted: apply the matching control-plane schema, then start `mnemo-server`:
```bash
cd server
MNEMO_DSN="user:pass@tcp(host:4000)/mnemos?parseTime=true" go run ./cmd/mnemo-server
```
See [Self-Hosting](#self-hosting) for backend-specific setup details, and [API Reference](#api-reference) for provisioning.
2. Pick your integration guide.
- [OpenClaw / ClawHub](https://mem9.ai/openclaw-memory)
- [Hermes Agent](https://github.com/mem9-ai/mem9-hermes-plugin#readme)
- [Claude Code](claude-plugin/README.md)
- [OpenCode](opencode-plugin/README.md)
- [Codex](codex-plugin/README.md)
- [Dify](https://github.com/mem9-ai/mem9-dify-plugin#readme)
- [Any HTTP client / custom runtime](#api-reference)
3. Set your credentials.
```bash
# Hosted API
export MEM9_API_URL="https://api.mem9.ai"
export MEM9_API_KEY="<mem9-api-key>"
# Self-hosted
export MEM9_API_URL="http://localhost:8080"
export MEM9_API_KEY="<mem9-api-key>"
```
For self-hosted deployments, use your server URL and the mem9 API key returned or configured by your provisioning flow.
## Why mem9
mem9 gives coding agents one shared memory layer instead of separate local notebooks and one-off prompt files.
| What mem9 gives you | Why it matters |
|---|---|
| Persistent memory across sessions and machines | Your context survives restarts, laptop switches, and long-running projects |
| Shared memory across agents and workflow platforms | OpenClaw, Hermes Agent, Claude Code, OpenCode, Codex, Dify apps, and custom clients can recall the same facts |
| Stateless integrations | Runtime plugins stay thin because storage, search, ingest, and policy live in the server |
| Hybrid recall and a visual dashboard | Semantic search, keyword search, and inspection workflows stay in one system |
## Supported Platforms and Agent Runtimes
| Platform | Integration shape | Install / docs |
|---|---|---|
| OpenClaw | `kind: "memory"` plugin for server-backed shared memory | [OpenClaw / ClawHub install guide](https://mem9.ai/openclaw-memory) |
| Hermes Agent | Memory provider plugin with setup and activation flow | [mem9-hermes-plugin README](https://github.com/mem9-ai/mem9-hermes-plugin#readme) |
| Claude Code | Marketplace plugin with hooks and skills | [`claude-plugin/README.md`](claude-plugin/README.md) |
| OpenCode | Plugin SDK integration loaded from `opencode.json` | [`opencode-plugin/README.md`](opencode-plugin/README.md) |
| Codex | Marketplace plugin with managed hooks and project overrides | [`codex-plugin/README.md`](codex-plugin/README.md) |
| Dify | Tool plugin for Dify Agent apps and Workflow apps, with single-space and multi-space authorization | [mem9-dify-plugin README](https://github.com/mem9-ai/mem9-dify-plugin#readme) |
| Any HTTP client / custom runtime | Direct REST API integration | [API Reference](#api-reference) |
All supported runtimes and platform integrations expose the same core memory flow: store, search, get, update, and delete against the mem9 server API.
## Why the Hosted API
The hosted mem9 API is the fastest way to put persistent memory behind an agent fleet while keeping the option to self-host later.
| Hosted API capability | Why teams start here |
|---|---|
| Hosted mem9 API with instant space provisioning | You can install an agent integration first and skip standing up infrastructure on day one |
| Shared memory across runtimes and platforms | One space can serve OpenClaw, Hermes Agent, Claude Code, OpenCode, Codex, Dify apps, and custom clients together |
| Managed search and storage | Hybrid recall works out of the box without a separate vector stack or sync layer |
| TiDB Cloud Starter foundation | The hosted path benefits from instant provisioning, native vector search, full-text search, server-side auto-embedding, hybrid search, and MySQL-compatible operational semantics |
| Same API contract as self-hosted mem9 | Moving to your own deployment is a base-URL and credential change, not a plugin rewrite |
| Visual dashboard and product onboarding | Teams can inspect and manage memory without building internal tooling first |
Under the hood, the hosted mem9 API runs the same mem9 server model surfaced in this repository, with TiDB Cloud Starter providing managed provisioning, native vector search, full-text search, server-side auto-embedding, hybrid search, and MySQL-compatible storage semantics.
## API Reference
Set `X-Mnemo-Agent-Id` on authenticated memory, import, and session-message requests when you want the server to distinguish which runtime or agent instance is writing and recalling memories inside the same mem9 space. This works on both the tenant-path `v1alpha1` routes and the `v1alpha2` API-key routes.
### Provisioning
Use this endpoint when you want mem9 to auto-provision a new TiDB-backed space.
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/v1alpha1/mem9s` | TiDB auto-provision endpoint when a provisioner is configured. TiDB Zero enables this path by default on `tidb`; TiDB Cloud Pool uses `MNEMO_TIDB_ZERO_ENABLED=false` with `MNEMO_TIDBCLOUD_API_KEY` and `MNEMO_TIDBCLOUD_API_SECRET`. Manual-bootstrap deployments use pre-existing tenants instead of this path. Returns `{ "id" }`. Accepts optional `utm_*` query params for attribution logging |
Prefer `v1alpha2` for all new integrations. It uses `X-API-Key` and is the primary API surface for current runtimes.
### Preferred API (`v1alpha2`)
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/v1alpha2/mem9s/memories` | Preferred unified write endpoint. Requires `X-API-Key` header |
| `GET` | `/v1alpha2/mem9s/memories` | Preferred search endpoint. Requires `X-API-Key` header |
| `GET` | `/v1alpha2/mem9s/memories/{id}` | Preferred get-by-id endpoint. Requires `X-API-Key` header |
| `PUT` | `/v1alpha2/mem9s/memories/{id}` | Preferred update endpoint. Requires `X-API-Key` header |
| `DELETE` | `/v1alpha2/mem9s/memories/{id}` | Preferred delete endpoint. Requires `X-API-Key` header |
### Legacy Tenant-Path API (`v1alpha1`)
Use these endpoints only when you need compatibility with older tenant-ID-in-path clients.
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/v1alpha1/mem9s/{tenantID}/memories` | Legacy unified write endpoint. Tenant key travels in the URL path |
| `GET` | `/v1alpha1/mem9s/{tenantID}/memories` | Legacy search endpoint for `tenantID`-configured clients |
| `GET` | `/v1alpha1/mem9s/{tenantID}/memories/{id}` | Legacy get-by-id endpoint |
| `PUT` | `/v1alpha1/mem9s/{tenantID}/memories/{id}` | Legacy update endpoint. Optional `If-Match` for version check |
| `DELETE` | `/v1alpha1/mem9s/{tenantID}/memories/{id}` | Legacy delete endpoint |
## Self-Hosting
Before first start, apply the control-plane schema that matches your backend: `server/schema.sql`, `server/schema_pg.sql`, or `server/schema_db9.sql`.
mem9 server supports multiple storage backends. Set `MNEMO_DB_BACKEND` to `tidb`, `postgres`, or `db9`, point `MNEMO_DSN` at that backend, and the rest of the runtime contract stays the same for your agents. TiDB supports three tenant flows: TiDB Zero auto-provisioning is enabled by default on `tidb`; TiDB Cloud Pool auto-provisioning uses `MNEMO_TIDB_ZERO_ENABLED=false` with `MNEMO_TIDBCLOUD_API_KEY` and `MNEMO_TIDBCLOUD_API_SECRET`; manual bootstrap uses pre-existing tenants mode. `postgres` and `db9` use the advanced manual-bootstrap path, which requires an active tenant row in the control-plane DB plus a live tenant database and schema behind it. In `v1alpha2`, `X-API-Key` resolves tenants by ID lookup.
### Build & Run
```bash
make build
cd server
MNEMO_DSN="user:pass@tcp(host:4000)/mnemos?parseTime=true" ./bin/mnemo-server
```
For PostgreSQL or db9 deployments, export `MNEMO_DB_BACKEND=postgres` or `MNEMO_DB_BACKEND=db9` before launching the server.
### Docker
`make docker` tags the image as `${REGISTRY}/mnemo-server:${COMMIT}`. This local example builds `local/mnemo-server:dev`:
```bash
make docker REGISTRY=local COMMIT=dev
docker run -e MNEMO_DSN="..." -e MNEMO_DB_BACKEND="tidb" -p 8080:8080 local/mnemo-server:dev
```
### Environment Variables
Minimal runtime config is `MNEMO_DSN`. Everything else is optional or only applies to specific deployment modes.
#### Core Server
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `MNEMO_DSN` | Yes | — | Database connection string |
| `MNEMO_PORT` | No | `8080` | HTTP listen port |
| `MNEMO_DB_BACKEND` | No | `tidb` | Database backend: `tidb`, `postgres`, or `db9` |
| `MNEMO_RATE_LIMIT` | No | `100` | Requests/sec per IP |
| `MNEMO_RATE_BURST` | No | `200` | Burst size |
| `MNEMO_UPLOAD_DIR` | No | `./uploads` | Directory used for uploaded file storage |
| `MNEMO_WORKER_CONCURRENCY` | No | `5` | Parallelism for async upload ingest workers |
| `MNEMO_UTM_ENABLED` | No | `false` | Enable UTM campaign tracking. When enabled, `utm_*` query params on provisioning requests are stored in the control-plane DB. Requires the `tenant_utm` table to exist |
#### Embedding And Ingest
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `MNEMO_EMBED_AUTO_MODEL` | No | — | TiDB/db9 `EMBED_TEXT()` model name. When set, it takes precedence over client-side embeddings |
| `MNEMO_EMBED_AUTO_DIMS` | No | `1024` | Vector dimensions for `MNEMO_EMBED_AUTO_MODEL` |
| `MNEMO_EMBED_API_KEY` | No | — | Client-side embedding provider API key. Optional for local OpenAI-compatible endpoints when `MNEMO_EMBED_BASE_URL` is set |
| `MNEMO_EMBED_BASE_URL` | No | `https://api.openai.com/v1` when client-side embeddings are enabled | Custom OpenAI-compatible embedding endpoint |
| `MNEMO_EMBED_MODEL` | No | `text-embedding-3-small` | Client-side embedding model name |
| `MNEMO_EMBED_DIMS` | No | `1536` | Client-side embedding vector dimensions |
| `MNEMO_LLM_API_KEY` | No | — | LLM provider API key. If unset, smart ingest falls back to raw ingest behavior |
| `MNEMO_LLM_BASE_URL` | No | `https://api.openai.com/v1` when LLM ingest is enabled | Custom OpenAI-compatible chat endpoint |
| `MNEMO_LLM_MODEL` | No | `gpt-4o-mini` | LLM model for smart ingest |
| `MNEMO_LLM_TEMPERATURE` | No | `0.1` | LLM temperature for smart ingest |
| `MNEMO_INGEST_MODE` | No | `smart` | Ingest mode: `smart` or `raw` |
| `MNEMO_FTS_ENABLED` | No | `false` | Enable TiDB full-text search path. Only set this on clusters that support TiDB FTS |
#### Search Source Turns
The `MEM9_SOURCE_TURN_*` variables control how many source turn conversations are attached to search results as contextual decorations.
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `MEM9_SOURCE_TURN_MIN_SCORE` | No | `2` | Minimum term-frequency relevance score for a source turn to be included in search result decorations |
| `MEM9_SOURCE_TURN_PER_MEMORY_LIMIT` | No | `2` | Maximum source turns attached to a single memory in search results |
| `MEM9_SOURCE_TURN_TOTAL_LIMIT` | No | `12` | Maximum total source turns across all memories in a single search response |
#### Space Chain Recall
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `MNEMO_CHAIN_RECALL_STOP_SCORE` | No | `0.5` | Stop querying later Space Chain nodes once a node result score reaches this threshold. Must be between `0` and `1` |
#### Provisioning And Pooling
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `MNEMO_TIDB_ZERO_ENABLED` | No | `true` | Enable TiDB Zero auto-provisioning for `tidb` backend. When enabled, it takes precedence over TiDB Cloud Pool provisioning |
| `MNEMO_TIDB_ZERO_API_URL` | No | `https://zero.tidbapi.com/v1alpha1` | TiDB Zero API base URL |
| `MNEMO_TIDBCLOUD_API_URL` | No | `https://serverless.tidbapi.com` | TiDB Cloud Pool API base URL |
| `MNEMO_TIDBCLOUD_POOL_ID` | No | `2` | TiDB Cloud Pool ID used for cluster takeover |
| `MNEMO_TIDBCLOUD_API_KEY` | No | — | TiDB Cloud Pool API key. Used only when `MNEMO_TIDB_ZERO_ENABLED=false`, `MNEMO_DB_BACKEND=tidb`, and pool takeover is desired |
| `MNEMO_TIDBCLOUD_API_SECRET` | No | — | TiDB Cloud Pool API secret for digest auth. Same conditions as `MNEMO_TIDBCLOUD_API_KEY` |
| `MNEMO_TENANT_POOL_MAX_IDLE` | No | `5` | Max idle tenant database connections kept in the in-process tenant pool |
| `MNEMO_TENANT_POOL_MAX_OPEN` | No | `10` | Max open connections per tenant database handle |
| `MNEMO_TENANT_POOL_CONNECT_TIMEOUT` | No | `3s` | Timeout for tenant pool cold-connect ping/open attempts |
| `MNEMO_TENANT_POOL_IDLE_TIMEOUT` | No | `10m` | Idle timeout for tenant database handles |
| `MNEMO_TENANT_POOL_TOTAL_LIMIT` | No | `200` | Total tenant database handles allowed across the process |
| `MNEMO_CLUSTER_BLACKLIST` | No | — | Comma-separated TiDB cluster IDs whose spend-limit errors should be translated to HTTP 429 instead of 503 |
#### Auto Spend Limit
These variables control automatic spend-limit increases for TiDB Cloud clusters that hit their cap. The feature progressively raises the limit up to `MNEMO_AUTO_SPEND_LIMIT_MAX` with a configurable cooldown between increments.
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `MNEMO_AUTO_SPEND_LIMIT_ENABLED` | No | `false` | Enable automatic spend-limit increases for TiDB Cloud clusters. Requires valid `MNEMO_TIDBCLOUD_API_KEY` and `MNEMO_TIDBCLOUD_API_SECRET` |
| `MNEMO_AUTO_SPEND_LIMIT_INCREMENT` | No | `500` | Amount to increase the spend limit by each step (in USD cents: 500 = $5.00) |
| `MNEMO_AUTO_SPEND_LIMIT_MAX` | No | `10000` | Maximum spend limit allowed (in USD cents: 10000 = $100.00). Must be greater than the increment |
| `MNEMO_AUTO_SPEND_LIMIT_COOLDOWN` | No | `1h` | Minimum time between consecutive spend-limit increases for the same cluster |
#### Metering
These variables configure the legacy server-side API metering writer. It emits `mem9-api` events for successful recall and ingest operations and is separate from runtime usage quota metering.
Metering location is configured as a single destination URL. Supported schemes are:
- `s3://<bucket>/<prefix>/` for compressed JSON batches in S3
- `http://...` or `https://...` for JSON batch webhooks
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `MNEMO_METERING_ENABLED` | No | `false` | Enable the metering writer. When `false`, the writer is a no-op |
| `MNEMO_METERING_URL` | No | — | Metering destination URL. Supported forms: `s3://<bucket>/<prefix>/`, `http://...`, or `https://...`. If empty, the writer stays disabled even when `MNEMO_METERING_ENABLED=true` |
| `MNEMO_METERING_FLUSH_INTERVAL` | No | `10s` | In-memory batch flush interval for the metering writer |
#### Runtime Usage Quota And Metering
Runtime usage is disabled by default. When enabled, the server reserves quota before memory recall/write operations, releases reservations after failed operations, commits reservations after successful operations, and sends console metering events to the runtime usage service. This path uses `MNEMO_RUNTIME_USAGE_BASE_URL` and does not use `MNEMO_METERING_URL`.
The runtime usage outbox uses the control-plane `runtime_usage_outbox` table for pending reservation finalization and metering delivery. It is enabled by default when runtime usage is enabled.
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `MNEMO_RUNTIME_USAGE_ENABLED` | No | `false` | Enable runtime usage quota gating and console metering for memory recall/write operations |
| `MNEMO_RUNTIME_USAGE_BASE_URL` | Yes when enabled | — | Runtime usage service base URL. Must be `http` or `https`; query and fragment are rejected |
| `MNEMO_RUNTIME_USAGE_INTERNAL_SECRET` | Yes when enabled | — | Bearer token for internal runtime usage service calls |
| `MNEMO_RUNTIME_USAGE_TIMEOUT` | No | `3s` | Timeout for quota reservation and finalization requests |
| `MNEMO_RUNTIME_USAGE_METERING_TIMEOUT` | No | `5s` | Timeout for console metering event delivery requests |
| `MNEMO_RUNTIME_USAGE_RESERVATION_TTL` | No | `30m` | Parsed into server config, but currently not sent to reservation requests; changing it does not alter reservation lifetimes |
| `MNEMO_RUNTIME_USAGE_OPERATION_TTL` | No | `30m` | Parsed into server config, but currently not used to expire runtime usage outbox rows; changing it does not alter outbox lifetimes |
| `MNEMO_RUNTIME_USAGE_FAIL_OPEN` | No | `false` | Allow operations when quota reservation fails with a retryable runtime usage service error. Quota denials and operation conflicts still fail closed |
| `MNEMO_RUNTIME_USAGE_OUTBOX_ENABLED` | No | same as `MNEMO_RUNTIME_USAGE_ENABLED` | Persist pending reservation and metering steps for retry. If explicitly set to `false` while runtime usage is enabled, `MNEMO_RUNTIME_USAGE_FAIL_OPEN` must be `true` |
#### Security And Debugging
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `MNEMO_ENCRYPT_TYPE` | No | `plain` | Encryption type for tenant DB passwords: `plain`, `md5`, or `kms`. One-time deployment decision. |
| `MNEMO_ENCRYPT_KEY` | No | — | Encryption key for `md5` or KMS key ID for `kms`. Required when `MNEMO_ENCRYPT_TYPE` is not `plain` |
| `MNEMO_DEBUG_LLM` | No | `false` | Log raw LLM responses for debugging parse errors. Use only in dev/test because responses may contain user data |
#### AWS KMS Environment
These are only relevant when `MNEMO_ENCRYPT_TYPE=kms`. The server uses the AWS SDK default config chain; the common environment-based inputs referenced in code are:
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `AWS_ACCESS_KEY_ID` | No | — | AWS access key ID for KMS auth when using environment-based AWS credentials |
| `AWS_SECRET_ACCESS_KEY` | No | — | AWS secret access key for KMS auth when using environment-based AWS credentials |
| `AWS_REGION` | No | — | AWS region used to create the KMS client |
#### Test-Only
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `MNEMO_TEST_DSN` | No | Falls back to `MNEMO_DSN` | Integration-test DSN used by server repository tests |
## Repository Map
| Path | Role |
|---|---|
| [`server/`](server/) | Core Go REST API and source of truth for spaces, memories, search, ingest, and tenant provisioning |
| [`cli/`](cli/) | Standalone Go CLI for exercising mem9 API and ingest flows |
| [`openclaw-plugin/`](openclaw-plugin/) | OpenClaw memory plugin |
| [`opencode-plugin/`](opencode-plugin/) | OpenCode plugin |
| [`claude-plugin/`](claude-plugin/) | Claude Code hooks and skills integration |
| [`codex-plugin/`](codex-plugin/) | Codex marketplace plugin and managed hooks |
| [`site/`](site/) | Public mem9.ai site and published onboarding assets |
| [`dashboard/`](dashboard/) | Dashboard product frontend and supporting product docs |
| [`benchmark/`](benchmark/) | Benchmark harnesses and datasets for mem9 evaluation |
| [`e2e/`](e2e/) | Live end-to-end scripts against a running mem9 server |
| [`docs/`](docs/) | Architecture notes, design docs, and feature specs |
## Related Repositories
| Repository | What it owns | When to look there |
|---|---|---|
| [`mem9`](.) | Core Go API server, agent plugins, CLI, site, dashboard frontend, benchmark harnesses, and docs | You are working on the shared memory server, plugin integrations, or the main product docs |
| [`mem9-node`](https://github.com/mem9-ai/mem9-node) | Dashboard analysis backend, async jobs, and worker flows | A dashboard feature depends on backend APIs, background jobs, or analysis pipelines |
| [`mem9-hermes-plugin`](https://github.com/mem9-ai/mem9-hermes-plugin) | Hermes Agent plugin packaging, setup flow, and Hermes-specific docs | You are changing Hermes installation, activation, or runtime-specific behavior |
| [`mem9-dify-plugin`](https://github.com/mem9-ai/mem9-dify-plugin) | Dify tool plugin, memory tools, authorization modes, and Dify-specific docs | You are changing Dify Agent app, Workflow app, or multi-space plugin behavior |
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
## License
[Apache-2.0](LICENSE)
---
<p align="center">
<a href="https://tidbcloud.com">
<img src="assets/tidb-logo.png" alt="TiDB Cloud Starter" height="28" />
</a>
<br/>
<sub>Built on <a href="https://tidbcloud.com">TiDB Cloud Starter</a> for shared memory, vector search, and managed cloud provisioning.</sub>
</p>
================================================
FILE: benchmark/BASELINE.md
================================================
export MNEMO_LLM_MODEL="qwen3.6-flash"
export OPENAI_JUDGE_MODEL="qwen3.6-plus"
export OPENAI_CHAT_MODEL="qwen3.6-plus"
── Results ──────────────────────────────────
Overall F1 (micro): 62.30% (n=1985)
Overall F1 (macro): 54.02%
Overall LLM (micro): 66.28% (n=1539)
Overall LLM (macro): 56.98%
Overall Evidence Recall: 68.19%
Cat 1 (multi-hop ): F1=26.81% LLM=37.01% ER=43.1% (n=281 llm_n=281)
Cat 2 (single-hop ): F1=67.62% LLM=80.37% ER=84.9% (n=321 llm_n=321)
Cat 3 (temporal ): F1=21.12% LLM=36.46% ER=41.5% (n=96 llm_n=96)
Cat 4 (open-domain ): F1=59.46% LLM=74.08% ER=75.6% (n=841 llm_n=841)
Cat 5 (adversarial ): F1=95.07% LLM=N/A ER=63.5% (n=446)
──────────────────────────────────────────────
================================================
FILE: benchmark/MR-NIAH/AGENTS.md
================================================
---
title: benchmark/MR-NIAH — Benchmark harness
---
## Overview
MR-NIAH is a bridge from the MiniMax benchmark corpus to OpenClaw sessions and mem9 comparison runs.
## Files and workflow
| File | Role |
| ----------------------- | ----------------------------------------------------- |
| `fetch_data.py` | Mirror/update upstream dataset into `origin/` |
| `mr-niah-transcript.py` | Convert raw turns into OpenClaw session JSON |
| `run_batch.py` | Replay generated sessions through an OpenClaw profile |
| `run_mem_compare.sh` | Compare baseline vs mem9-enabled profile |
| `score.py` | Apply MR-NIAH scoring rubric to predictions |
| `USAGE.md` | Full prerequisites and end-to-end usage |
## Where to look
- Dataset cache and raw source: `origin/`
- Generated sessions and index: `output/`
- Latest run outputs: `results/`
- Preserved comparison outputs: `results-*/`
- Helper state: `.cache/`
## Commands
```bash
cd benchmark/MR-NIAH && python3 fetch_data.py
cd benchmark/MR-NIAH && python3 mr-niah-transcript.py
cd benchmark/MR-NIAH && python3 run_batch.py --profile mrniah_local --agent main --limit 30
cd benchmark/MR-NIAH && SAMPLE_LIMIT=30 bash run_mem_compare.sh
cd benchmark/MR-NIAH && python3 score.py results/predictions.jsonl
```
## Local conventions
- Treat this as pipeline code, not product code; scripts are orchestrators around local files and external tools.
- Keep generated artifacts out of the source files under review; `origin/`, `output/`, and `results*/` are working directories.
- `run_mem_compare.sh` depends on the rest of the pipeline being reproducible; avoid hidden local assumptions.
- Preserve benchmark comparability: do not change the scoring rubric casually.
## Gotchas
- `run_mem_compare.sh` expects Python 3.10+.
- `run_mem_compare.sh` expects the mem9 API endpoint to be reachable; by default it uses `https://api.mem9.ai`.
- If mem9 space provisioning is rate-limited, wait briefly and rerun, or point `MEM9_BASE_URL` at another mem9-compatible endpoint.
## Anti-patterns
- Do NOT hardcode one-off local result paths into reusable scripts.
- Do NOT mix transcript generation and scoring logic in the same script.
- Do NOT overwrite canonical benchmark data in `origin/` with transformed output.
## Outstanding follow-ups
- Persist comparison scores to files instead of only printing to stdout.
- Add a `--model` flag to `run_mem_compare.sh`.
- Add an explicit flag for forced memory hacks / compaction behavior.
================================================
FILE: benchmark/MR-NIAH/README.md
================================================
# MR-NIAH (OpenClaw) Benchmark Harness
MR-NIAH is our proving ground for turning legacy multi-turn chatbot benchmarks into first-class OpenClaw sessions. The harness bridges [MiniMax’s MR-NIAH](https://github.com/MiniMax-AI/MiniMax-01/tree/main/evaluation/MR-NIAH) corpus into OpenClaw-compatible transcripts, replays them through baseline and memory-enabled profiles, and reports MR-NIAH scores so existing datasets can drive current memory experiments without hand-curated prompts.
## Background: Bridging Stored Benchmarks to OpenClaw
Research teams have produced numerous memory benchmarks for chatbots, but their formats (JSONL dumps, bespoke timelines, ad-hoc scoring) do not slot into OpenClaw’s profile + session model. This repo demonstrates how to wrap one of those datasets—MR-NIAH—so that every sample can be ingested, replayed, and scored inside OpenClaw without manual transcription. The same pattern applies to other dormant benchmarks: swap in a new transcript conversion script and the rest of the automation stays identical. The value is multiplicative: OpenClaw gains immediate access to a large body of historical evaluation data, and benchmark owners do not need to redesign tasks from scratch.
## What the Harness Provides
- **Dataset mirroring (`fetch_data.py`)** – keeps a local `origin/` mirror of MiniMax’s MR-NIAH dumps.
- **Transcript bridge (`mr-niah-transcript.py`)** – rewrites raw turns into OpenClaw `session` JSON plus an `index`.
- **Batch execution (`run_batch.py`)** – rehydrates sessions into a profile, calls `openclaw agent`, and stores `results/`.
- **Comparison runner (`run_mem_compare.sh`)** – clones the baseline profile, installs the mem9 plugin, enables OpenClaw 4.23+ conversation access for mem9, provisions a fresh mem9 space on the hosted mem9 API (or another configured mem9 endpoint), runs both profiles, prints accuracy deltas, and (on successful full comparisons) writes a tar.gz archive containing results + logs. Supports `--model` / `--compact`, plus managed profiles (template + `.env`) to avoid baseline/mem drift; defaults to `benchmark/MR-NIAH/config/openclaw/`.
- **Scoring (`score.py`)** – invokes the MR-NIAH exact-match rubric so downstream results remain comparable to prior papers.
Directory layout, helper scripts, and agent responsibilities are summarized below:
- `origin/`, `output/`, `results/`, `results-*/`, `.cache/` – see **Directory layout** and `AGENTS.md` for details.
- [`USAGE.md`](USAGE.md) – prerequisites, dependencies, and end-to-end commands for the full pipeline.
- [`AGENTS.md`](AGENTS.md) – step-by-step agent responsibilities plus outstanding TODOs.
## Directory layout
- `origin/` – upstream dataset dumps mirroring `data/<lang>/<tokens>_tokens.jsonl`.
- `output/` – regenerated sessions plus `output/index.jsonl` for the next run.
- `results/` – latest batch predictions (`results/predictions.jsonl` + raw logs).
- `results-<profile>/` – preserved outputs from comparison runs (baseline vs mem).
- `.cache/` – helper state for benchmark runs.
## Pipeline Overview
The pipeline is unchanged from prior revisions, but documentation now lives in dedicated files:
1. **Fetch** – mirror MR-NIAH data into `origin/` (`fetch_data.py`).
2. **Transcribe** – convert each sample into OpenClaw sessions and `output/index.jsonl` (`mr-niah-transcript.py`).
3. **Replay** – run batches against an OpenClaw profile to populate `results/` (`run_batch.py`).
4. **Compare (optional)** – run baseline vs mem9 via `run_mem_compare.sh`, which depends on `run_batch.py` and `score.py`.
5. **Score** – compute MR-NIAH accuracy for any predictions file (`score.py`).
See [`USAGE.md`](USAGE.md) for flags, environment variables, and troubleshooting guidance.
## Why This Approach Helps
- **Parallelism by design** – transcript generation decouples dataset preparation from OpenClaw execution, so multiple profiles or agents can replay the same `output/` concurrently without re-downloading data.
- **scales to other datasets** – once the transcript conversion is adapted, the remaining steps (batching, comparison, scoring) stay identical, enabling “drop-in” baselines for any legacy memory benchmark.
- **Fast baseline coverage** – by replaying large corpora automatically, teams can collect baseline accuracy for new models or plugins in hours instead of curating bespoke prompts.
MR-NIAH remains the first bridge: it proves the tooling can translate a stored benchmark into OpenClaw runs, execute them in parallel, and surface MR-NIAH scores. The same architecture can now be reused for the broader set of historical datasets while we design the next generation of native OpenClaw memory evaluations.
================================================
FILE: benchmark/MR-NIAH/USAGE.md
================================================
# MR-NIAH Usage Guide
This document explains how to prepare an OpenClaw profile, set up the required dependencies, and run the MR-NIAH benchmark pipeline end-to-end.
## Prerequisites
### OpenClaw profiles
There are two ways to run:
1) **Full baseline-vs-mem comparison (recommended)**: `run_mem_compare.sh` defaults to managed profiles and recreates fresh OpenClaw profiles per run from `benchmark/MR-NIAH/config/openclaw/`. You do not need to manually initialize `~/.openclaw-<profile>` beforehand.
2) **Single-profile batch runs** (e.g. calling `run_batch.py` directly): initialize your profile with the OpenClaw CLI so that `~/.openclaw-<profile>/openclaw.json` exists.
#### (Optional) Managed profiles (template + .env)
If you do not want to manually maintain two profiles (baseline + mem) and risk configuration drift (e.g. compaction settings), `run_mem_compare.sh` can recreate profiles from a template directory each run.
For full baseline-vs-mem comparisons, managed profiles are enabled by default to avoid accidental reuse of existing profiles. If you do not pass `--base-profile/--mem-profile`, the runner appends a `_yyyymmddhhmmss` suffix automatically.
Requirements:
- A template directory that contains at least an `openclaw.json` (it can also include `agents/`, `workspace/`, etc).
- An `.env` file that contains your secret API keys and any other required environment variables.
- The runner treats it as opaque and never prints it.
- `.env` is gitignored.
Default locations (in this repo):
- Template dir: `benchmark/MR-NIAH/config/openclaw/` (must contain `openclaw.json`)
- Env file: `benchmark/MR-NIAH/config/openclaw/.env`
Setup:
1) Copy `example.env` to `.env`:
```
cp benchmark/MR-NIAH/config/openclaw/example.env benchmark/MR-NIAH/config/openclaw/.env
```
2) Edit `benchmark/MR-NIAH/config/openclaw/.env` to set your keys.
3) Ensure `benchmark/MR-NIAH/config/openclaw/openclaw.json` references the same variable names (it typically uses `${ENV_VAR}` placeholders).
Example (recreate baseline + mem from a local template, set model + compaction preset):
```
./run_mem_compare.sh \
--model "dashscope/qwen3.5-plus" \
--compact "safeguard-20k"
```
Compaction presets live under `benchmark/MR-NIAH/openclaw/compact/` (a default `safeguard-20k` preset is included).
### Software and infrastructure
| Requirement | Why you need it | Notes |
| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Python 3.10+ & pip | Runs `fetch_data.py`, `mr-niah-transcript.py`, `run_batch.py`, and `score.py`. | Install dependencies with `python3 -m pip install -r requirements.txt` from the repo root if available, or install `requests`, `click`, and `rich` manually. |
| Git + network access to MiniMax’s MR-NIAH repo | `fetch_data.py` mirrors upstream datasets via GitHub. | Works with anonymous HTTPS; provide a token if your network requires it. |
| OpenClaw CLI (latest) | Executes agents for each regenerated session. | Verify `openclaw --version` works and that the CLI can run your chosen profile interactively. |
| Access to the hosted mem9 API (or another mem9-compatible endpoint) | Stores mem9 state whenever you run the comparison flow. | By default the script uses `https://api.mem9.ai`; pass `--mem9-base-url` if you want a different endpoint. |
## Pipeline
### 1. Fetch MR-NIAH datasets
```
cd benchmark/MR-NIAH
python3 fetch_data.py [--lang LANG] [--tokens BUCKET ...] [--paths FILE ...]
```
- Without flags the script mirrors every published bucket (both languages) into `origin/`.
- Use `--lang {chinese|english|all|none}` and `--tokens` to narrow the dump, or `--paths` for explicit files such as `data/chinese/10240_tokens.jsonl`.
- `--dest` overrides the target directory, `--revision` pins to a GitHub ref, and `--dry-run` previews the plan.
### 2. Generate OpenClaw transcripts
```
python3 mr-niah-transcript.py [--lang LANG] [--tokens BUCKET ...] [--input FILE ...] [--limit N]
```
- The script wipes `output/`, converts each dataset entry so that the final user turn becomes the question, and emits:
- `output/sessions/<uuid>.jsonl` – session history ready for OpenClaw.
- `output/index.jsonl` – metadata that downstream steps consume.
- The defaults read all files in `origin/` if present; pass explicit files with `--input` or disable auto-selection via `--lang none`.
- If `benchmark/MR-NIAH/output/` is not writable (or you want to keep it immutable), write transcripts somewhere else:
```
python3 mr-niah-transcript.py --output-dir /tmp/mrniah-output
```
### 3. Run OpenClaw batches
```
python3 run_batch.py --profile mrniah_local --agent main --limit 30
```
- The script copies each transcript into `<profile>/agents/<agent>/sessions/`, registers it in `sessions.json`, calls `openclaw agent --session-id ... --message "<question>" --json`, and stores both structured JSON and raw logs under `results/`.
- Key flags:
- `--profile` – target OpenClaw profile (must already exist as described above).
- `--agent` – agent directory name inside the profile. Defaults to `main`.
- `--limit` – cap the number of MR-NIAH samples processed.
- `--output-dir` – where to read `index.jsonl` and `sessions/*.jsonl` from (default: `output/`).
- `--import-sessions` – uploads the session transcript to mem9 via `/imports` before each agent turn. Requires mem9 tenant details via `--mem9-api-url/--mem9-tenant-id` (or env vars / profile config).
- Artifacts land in `results/predictions.jsonl` plus `results/raw/*.stdout.json` / `.stderr.txt`.
### 4. (Optional) Baseline vs mem9 comparison
```
./run_mem_compare.sh --limit 30
```
If you generated transcripts into a non-default output directory, pass the same location to the runner:
```
./run_mem_compare.sh --output-dir /tmp/mrniah-output --limit 10
```
To rerun only one side (useful when baseline already exists and you just want to retry the mem9 run):
```
./run_mem_compare.sh --profile mrniah_mem --limit 30
```
To resume a failed single-profile run from a specific sample id (keeps `benchmark/MR-NIAH/results-<profile>/` and appends to `predictions.jsonl`):
```
./run_mem_compare.sh --profile mrniah_mem --resume 91
```
To re-run a single case (useful for patching up failures after the batch finishes):
```
./run_mem_compare.sh --profile mrniah_mem --case 91
```
By default, the runner continues on per-case failures and records them into `predictions.jsonl`.
To stop immediately on the first failure, add `--fail-fast`.
To compare existing runs without re-running (e.g. baseline succeeded earlier, mem was re-run later):
```
./run_mem_compare.sh --compare
```
#### Common options
- `--model <provider/model>`: sets `agents.defaults.model.primary` for both baseline + mem profiles.
- `--compact <preset|path.json>`: applies a compaction preset to both baseline + mem profiles (`agents.defaults.contextTokens` + `agents.defaults.compaction`).
- `--model-context-window <n>`: best-effort patch of the selected model catalog entry in `openclaw.json` (`models.providers.*.models[].contextWindow`). This is only applied when the profile `openclaw.json` contains a matching model entry.
- `--mem9-base-url <url>`: overrides the default mem9 base URL for this run.
#### Post-processing (archive)
When you run a full baseline-vs-mem comparison (not `--profile`, not `--compare`, not `--case`, not `--resume`) and the script completes successfully, it automatically creates a tarball in `results-logs/` containing:
- both `results-<profile>/` directories
- the main compare log file
1. Verifies `output/index.jsonl` exists (generate it if missing).
2. Creates `~/.openclaw-<mem-profile>` by cloning `~/.openclaw-<base-profile>` when the mem profile is missing, or when you pass `--reset-mem-profile`.
3. Uses the hosted mem9 API by default (`https://api.mem9.ai`), or the endpoint you provide via `--mem9-base-url`.
4. Chooses a mem9 isolation strategy via `--mem9-isolation`:
- `tenant` (default): provisions a fresh mem9 space per case (strong isolation; recommended).
- `clear`: provisions one mem9 space for the run and clears memories before/after each case.
5. Chooses a mem9 history load strategy via `--mem9-load-method`:
- `line-write` (default): replays the transcript by posting each JSONL message line to `v1alpha2 /memories` sequentially.
- `import-session`: uploads the full transcript via `v1alpha1 /imports` (`file_type=session`) and polls the task.
6. Installs the `openclaw-plugin` into the memory profile, adds `plugins.allow=["mem9"]`, writes the tenant credentials into `plugins.entries.mem9.config`, and enables `plugins.entries.mem9.hooks.allowConversationAccess=true` on OpenClaw 4.23+ / 2026.4.22+.
7. Calls `run_batch.py` twice (baseline vs mem), writing into `results-${profile}` for baseline and `results-${mem_profile}` for the mem run.
8. Prints accuracy for both runs and the delta.
Key flags for reproducibility:
- `--base-profile` / `--mem-profile` / `--agent` / `--limit`
- `--mem9-base-url` / `--mem9-isolation` / `--mem9-load-method`
- `--mem9-line-write-*` and `--mem9-import-*` (depending on load method)
- `--mem9-trace-*`
- `--parallel` / `--sequential`
- `--openclaw-timeout`
- `--reset-mem-profile`
Workspace note:
- The scripts configure each OpenClaw profile to use a benchmark workspace under `~/.openclaw-<profile>/workspace` (not under `~/.openclaw/`).
### 5. Score predictions
```
python3 score.py [results/predictions.jsonl] [--max-errors 5]
```
- Splits each ground-truth answer into key phrases and checks whether each phrase appears as a substring in the model prediction (case-sensitive). The per-sample score is the fraction of matched phrases. Refusal responses are scored as 0.
- Use `--max-errors` to print mismatched samples for manual inspection.
- Point the script at the comparison artifacts (`results-mrniah_local/predictions.jsonl`, `results-mrniah_mem/predictions.jsonl`) to evaluate each run independently.
### Troubleshooting tips
- Regenerating transcripts is safe—`mr-niah-transcript.py` deletes and recreates `output/` on every run.
- If OpenClaw logs include ANSI escape sequences, `run_batch.py` strips them before parsing JSON. Check `results/raw/*.stderr.txt` when a session fails.
- If the hosted mem9 API rejects provisioning or rate-limits requests, wait a bit and rerun, or point `--mem9-base-url` to another mem9-compatible endpoint.
================================================
FILE: benchmark/MR-NIAH/fetch_data.py
================================================
#!/usr/bin/env python3
"""Fetch MR-NIAH dataset files from MiniMax's GitHub mirror.
Examples:
# Full mirror (both languages, all buckets)
python3 fetch_data.py
# Only download the Chinese 10,240-token subset
python3 fetch_data.py --lang chinese --tokens 10240
# Fetch explicit files regardless of language selection
python3 fetch_data.py --lang none --paths data/chinese/2048_tokens.jsonl english/10240_tokens.jsonl
# Preview what would be downloaded
python3 fetch_data.py --lang english --tokens 2048 10240 --dry-run
"""
from __future__ import annotations
import argparse
import fnmatch
import hashlib
import json
import os
import sys
import tempfile
from pathlib import Path
from typing import Iterable, List, Sequence
from urllib.error import HTTPError, URLError
from urllib.request import urlopen
ROOT = Path(__file__).resolve().parent
DEFAULT_DEST = ROOT / "origin"
DEFAULT_REVISION = "main"
TOKEN_BUCKETS = [
"2048",
"10240",
"20480",
"30720",
"40960",
"51200",
"61440",
"71680",
"81920",
"92160",
"102400",
"112640",
"122880",
"131072",
"204800",
"307200",
"409600",
"512000",
"614400",
"716800",
"819200",
"921600",
"1024000",
]
LANGUAGES = ("chinese", "english")
FILE_TEMPLATE = [f"data/{{lang}}/{token}_tokens.jsonl" for token in TOKEN_BUCKETS]
MANIFEST = {lang: [entry.format(lang=lang) for entry in FILE_TEMPLATE] for lang in LANGUAGES}
ALL_TOKENS = TOKEN_BUCKETS
def manifest_paths(langs: Sequence[str]) -> List[str]:
selected: List[str] = []
seen: set[str] = set()
for lang in langs:
for path in MANIFEST.get(lang, []):
if path not in seen:
selected.append(path)
seen.add(path)
return selected
def normalize_dataset_path(path: str) -> str:
norm = path.strip()
if not norm:
return ""
rel = Path(norm.lstrip("/"))
if any(part == ".." for part in rel.parts):
raise SystemExit(f"Refusing to traverse '..' in dataset path: {path}")
if rel.parts and rel.parts[0] != "data":
rel = Path("data") / rel
return rel.as_posix()
def dedupe(paths: Iterable[str]) -> List[str]:
seen: set[str] = set()
result: List[str] = []
for path in paths:
if path and path not in seen:
result.append(path)
seen.add(path)
return result
def collect_paths(langs: Sequence[str], extra: Sequence[str], allowed_tokens: set[str] | None) -> List[str]:
base = filter_by_tokens(manifest_paths(langs), allowed_tokens)
normalized_extra: List[str] = []
for raw in extra:
norm = normalize_dataset_path(raw)
if norm:
normalized_extra.append(norm)
return dedupe(base + normalized_extra)
def apply_include_filter(paths: Iterable[str], include: Sequence[str]) -> List[str]:
if not include:
return list(paths)
filtered = []
for path in paths:
if any(fnmatch.fnmatch(path, pattern) for pattern in include):
filtered.append(path)
return filtered
def relative_dest(path: str) -> Path:
rel = Path(path)
if rel.parts and rel.parts[0] == "data":
rel = rel.relative_to("data")
return rel
def display_path(dest_root: Path, target: Path) -> str:
try:
return target.relative_to(dest_root).as_posix()
except ValueError:
return str(target)
def token_from_path(path: str) -> str:
return Path(path).name.split("_", 1)[0]
def filter_by_tokens(paths: Iterable[str], allowed: set[str] | None) -> List[str]:
if allowed is None:
return list(paths)
return [path for path in paths if token_from_path(path) in allowed]
def parse_tokens(values: Sequence[str]) -> set[str] | None:
if not values:
return None
normalized: set[str] = set()
for value in values:
token = value.strip().lower()
if not token:
continue
if token == "all":
return None
if token not in ALL_TOKENS:
raise SystemExit(
f"Unsupported token bucket '{value}'. Valid choices: {', '.join(ALL_TOKENS)} or 'all'."
)
normalized.add(token)
return normalized if normalized else None
def github_url(path: str, revision: str) -> str:
rel = Path("evaluation") / "MR-NIAH" / path
return f"https://raw.githubusercontent.com/MiniMax-AI/MiniMax-01/{revision}/{rel.as_posix()}"
def sha256_for_file(path: Path) -> str:
hasher = hashlib.sha256()
with path.open("rb") as fh:
for chunk in iter(lambda: fh.read(8192), b""):
hasher.update(chunk)
return hasher.hexdigest()
def download_file(url: str, dest: Path, force: bool, label: str) -> bool:
if dest.exists() and not force:
print(f" - Skipping {label} (already exists)")
return False
dest.parent.mkdir(parents=True, exist_ok=True)
print(f" - Downloading {url} → {label}")
try:
with urlopen(url) as resp, dest.open("wb") as fh:
while True:
chunk = resp.read(65536)
if not chunk:
break
fh.write(chunk)
except HTTPError as exc:
dest.unlink(missing_ok=True)
raise RuntimeError(f"HTTP error {exc.code} for {url}") from exc
except URLError as exc:
dest.unlink(missing_ok=True)
raise RuntimeError(f"Network error for {url}: {exc.reason}") from exc
return True
def list_manifest() -> None:
data = {lang: paths for lang, paths in MANIFEST.items()}
print(json.dumps(data, indent=2))
def invalid_jsonl_lines(path: Path) -> List[int]:
invalid: List[int] = []
with path.open("rb") as fh:
for line_number, raw_line in enumerate(fh, start=1):
try:
line = raw_line.decode("utf-8")
except UnicodeDecodeError:
invalid.append(line_number)
continue
try:
json.loads(line)
except Exception:
invalid.append(line_number)
return invalid
def sanitize_jsonl_file(path: Path) -> List[int]:
invalid_lines = invalid_jsonl_lines(path)
if not invalid_lines:
return []
invalid_lookup = set(invalid_lines)
tmp_path: Path | None = None
try:
with tempfile.NamedTemporaryFile(
"wb",
delete=False,
dir=path.parent,
prefix=f".{path.name}.",
suffix=".tmp",
) as tmp:
tmp_path = Path(tmp.name)
with path.open("rb") as src, tmp_path.open("wb") as dst:
for line_number, raw_line in enumerate(src, start=1):
if line_number in invalid_lookup:
continue
dst.write(raw_line)
os.replace(tmp_path, path)
finally:
if tmp_path is not None:
tmp_path.unlink(missing_ok=True)
return invalid_lines
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Download MR-NIAH dataset files")
parser.add_argument("--dest", type=Path, default=DEFAULT_DEST, help="Destination directory (default: origin/)")
parser.add_argument(
"--lang",
choices=["chinese", "english", "all", "none"],
default="all",
help="Which language subset to include from the built-in manifest",
)
parser.add_argument("--paths", nargs="*", default=[], help="Additional dataset-relative paths (e.g. data/chinese/10240_tokens.jsonl)")
parser.add_argument(
"--include",
nargs="*",
default=[],
help="Glob filters applied to the resulting path list (e.g. '*10240*')",
)
parser.add_argument("--tokens", nargs="+", default=["all"], help="Token buckets to fetch (e.g. 10240 or 'all')")
parser.add_argument("--revision", default=DEFAULT_REVISION, help="Git revision/branch (default: main)")
parser.add_argument("--force", action="store_true", help="Redownload files even if they already exist")
parser.add_argument("--dry-run", action="store_true", help="Print actions without downloading")
parser.add_argument("--list", action="store_true", help="Print the built-in manifest and exit")
parser.add_argument("--checksum", action="store_true", help="After download, print SHA-256 digests")
parser.add_argument(
"--sanitize-jsonl",
action=argparse.BooleanOptionalAction,
default=True,
help="After download, remove invalid JSON lines from .jsonl files (default: on)",
)
parser.add_argument(
"--sanitize-existing",
action="store_true",
help="Sanitize even when a file is skipped because it already exists",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
if args.list:
list_manifest()
return 0
if args.lang == "all":
langs = list(MANIFEST.keys())
elif args.lang == "none":
langs = []
else:
langs = [args.lang]
allowed_tokens = parse_tokens(args.tokens)
paths = collect_paths(langs, args.paths, allowed_tokens)
paths = apply_include_filter(paths, args.include)
if not paths:
print("No files matched the current selection.", file=sys.stderr)
return 1
dest_root = args.dest.resolve()
print(f"Destination: {dest_root}")
print(f"Source: github (revision {args.revision})")
print(f"Files: {len(paths)}")
if args.dry_run:
for path in paths:
url = github_url(path, args.revision)
target = dest_root / relative_dest(path)
label = display_path(dest_root, target)
print(f"DRY-RUN {url} → {label}")
return 0
dest_root.mkdir(parents=True, exist_ok=True)
for path in paths:
url = github_url(path, args.revision)
target = dest_root / relative_dest(path)
label = display_path(dest_root, target)
try:
downloaded = download_file(url, target, args.force, label)
except RuntimeError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 2
if (
args.sanitize_jsonl
and target.suffix == ".jsonl"
and target.exists()
and (downloaded or args.sanitize_existing)
):
invalid_lines = sanitize_jsonl_file(target)
for line_number in invalid_lines:
print(f" removed invalid json: {label}:{line_number}")
if args.checksum:
digest = sha256_for_file(target)
print(f" sha256({label}) = {digest}")
print("All requested files downloaded.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
================================================
FILE: benchmark/MR-NIAH/mr-niah-transcript.py
================================================
#!/usr/bin/env python3
"""Generate OpenClaw session transcripts from MR-NIAH JSON/JSONL data.
Process:
1. Read one or more MR-NIAH dumps (JSON array/object or JSONL). By default we
look for `origin/<lang>/10240_tokens.jsonl` for both languages.
2. For each sample:
- The last `user` turn becomes the **question**.
- All turns before that last user message become the fixed **history**.
- The sample's `label` is treated as the expected **answer**.
3. Emit one OpenClaw transcript per sample (history only) and an index file
describing each session/question/answer tuple.
Outputs live under `output/`:
- `output/sessions/<session-uuid>.jsonl`
- `output/index.jsonl` (one JSON object per sample)
Usage:
cd benchmark/MR-NIAH
# Convert both languages' 10,240-token dumps (default behaviour)
python3 mr-niah-transcript.py
# Only process Chinese rows from a specific token bucket
python3 mr-niah-transcript.py --lang chinese --tokens 2048
# Point to explicit files (absolute, relative, or dataset paths)
python3 mr-niah-transcript.py origin/chinese/10240_tokens.jsonl --limit 50
python3 mr-niah-transcript.py --lang none --input data/english/20480_tokens.jsonl
"""
from __future__ import annotations
import argparse
import datetime as _dt
import json
import shutil
import sys
import uuid
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
try: # optional dependency; falls back to zero-token counts when missing
import tiktoken # type: ignore
except Exception: # pragma: no cover - defensive import
tiktoken = None
HERE = Path(__file__).resolve().parent
ORIGIN = HERE / "origin"
OUTPUT = HERE / "output"
SESS_DIR = OUTPUT / "sessions"
INDEX_PATH = OUTPUT / "index.jsonl"
DEFAULT_PROVIDER = "import"
DEFAULT_API = "import"
DEFAULT_MODEL = "import"
# Match pi-ai StopReason semantics; "import" is not a stop reason.
DEFAULT_STOP_REASON = "stop"
LANG_CHOICES = ("chinese", "english")
TOKEN_BUCKETS = [
"2048",
"10240",
"20480",
"30720",
"40960",
"51200",
"61440",
"71680",
"81920",
"92160",
"102400",
"112640",
"122880",
"131072",
"204800",
"307200",
"409600",
"512000",
"614400",
"716800",
"819200",
"921600",
"1024000",
]
def canonical_tokens(selection: set[str] | None) -> List[str]:
if selection is None:
return list(TOKEN_BUCKETS)
return [token for token in TOKEN_BUCKETS if token in selection]
def parse_tokens(values: Sequence[str]) -> set[str] | None:
if not values:
return {"10240"}
normalized: set[str] = set()
for value in values:
token = value.strip().lower()
if not token:
continue
if token == "all":
return None
if token not in TOKEN_BUCKETS:
raise SystemExit(
f"Unsupported token bucket '{value}'. Valid choices: {', '.join(TOKEN_BUCKETS)} or 'all'."
)
normalized.add(token)
return normalized if normalized else {"10240"}
def parse_langs(choice: str) -> List[str]:
if choice == "all":
return list(LANG_CHOICES)
if choice == "none":
return []
if choice in LANG_CHOICES:
return [choice]
raise SystemExit(f"Unsupported language choice: {choice}")
def normalize_dataset_relative(value: str) -> Path:
rel = Path(value.strip().lstrip("/"))
if rel.parts and rel.parts[0] == "data":
rel = rel.relative_to("data")
return rel
def describe_path(path: Path) -> str:
try:
return path.relative_to(HERE).as_posix()
except ValueError:
return str(path)
def dedupe_paths(paths: Iterable[Path]) -> List[Path]:
seen: set[Path] = set()
result: List[Path] = []
for p in paths:
if p not in seen:
result.append(p)
seen.add(p)
return result
def resolve_input_path(value: str) -> Path:
candidate = Path(value).expanduser()
if candidate.is_file():
return candidate
rel = normalize_dataset_relative(value)
if rel:
dataset_path = ORIGIN / rel
if dataset_path.is_file():
return dataset_path
raise SystemExit(f"Input not found: {value}")
def build_auto_inputs(langs: Sequence[str], tokens: Sequence[str]) -> Tuple[List[Path], List[Path]]:
selected: List[Path] = []
missing: List[Path] = []
for lang in langs:
for token in tokens:
rel = Path(lang) / f"{token}_tokens.jsonl"
target = ORIGIN / rel
fallback = ORIGIN / f"{token}_tokens.jsonl"
if target.is_file():
selected.append(target)
elif fallback.is_file():
selected.append(fallback)
else:
missing.append(target)
return dedupe_paths(selected), missing
def gather_inputs(positionals: Sequence[str], extras: Sequence[str], lang_choice: str, token_args: Sequence[str]) -> List[Path]:
explicit = [resolve_input_path(p) for p in list(positionals) + list(extras)]
if explicit:
return dedupe_paths(explicit)
langs = parse_langs(lang_choice)
if not langs:
raise SystemExit("No input files specified. Provide --input/positional files or choose --lang/--tokens.")
token_selection = parse_tokens(token_args)
tokens = canonical_tokens(token_selection)
selected, missing = build_auto_inputs(langs, tokens)
if not selected:
message = "No matching files found under origin/. Run fetch_data.py first or adjust --lang/--tokens."
if missing:
missing_lines = "\n".join(f" - {describe_path(p)}" for p in missing)
message += f"\nMissing expected files:\n{missing_lines}"
raise SystemExit(message)
if missing:
print("WARNING: skipping missing files:", file=sys.stderr)
for miss in missing:
print(f" - {describe_path(miss)}", file=sys.stderr)
return selected
def _build_token_counter():
def _heuristic(text: str) -> int:
# pi-coding-agent uses a conservative chars/4 heuristic for compaction.
# Use it as a fallback when tiktoken isn't available.
return (len(text) + 3) // 4
if tiktoken is None:
print(
"WARNING: tiktoken not available; falling back to chars/4 token estimates. "
"Install tiktoken for more accurate counts.",
file=sys.stderr,
)
return _heuristic
encoding = None
try:
encoding = tiktoken.get_encoding("cl100k_base")
except Exception:
try:
encoding = tiktoken.encoding_for_model("gpt-4o-mini") # pragma: no cover - fallback
except Exception:
encoding = None
if encoding is None:
print(
"WARNING: failed to initialize tiktoken; falling back to chars/4 token estimates.",
file=sys.stderr,
)
return _heuristic
def _count(text: str) -> int:
try:
return len(encoding.encode(text))
except Exception:
return _heuristic(text)
return _count
count_tokens = _build_token_counter()
def make_usage_snapshot(*, input_tokens: int, output_tokens: int) -> Dict[str, Any]:
usage = {
"input": int(max(0, input_tokens)),
"output": int(max(0, output_tokens)),
"cacheRead": 0,
"cacheWrite": 0,
"totalTokens": int(max(0, input_tokens) + max(0, output_tokens)),
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0,
"total": 0,
},
}
return usage
def isoformat_utc(dt: _dt.datetime) -> str:
return dt.isoformat(timespec="milliseconds").replace("+00:00", "Z")
def utc_now_iso() -> str:
return isoformat_utc(_dt.datetime.now(tz=_dt.timezone.utc))
def short_hex(counter: int, seed: str) -> str:
"""Deterministic 8-hex id for parentId chaining."""
# uuid4().hex is 32 chars; take first 8 but mix in order to stay stable.
return uuid.uuid5(uuid.NAMESPACE_URL, f"{seed}:{counter}").hex[:8]
def make_session_header(session_id: str, ts: str) -> Dict[str, Any]:
return {"type": "session", "version": 3, "id": session_id, "timestamp": ts, "cwd": "/"}
def make_message_entry(
entry_id: str,
parent_id: Optional[str],
ts_iso: str,
ts_ms: int,
role: str,
text: str,
*,
usage_mode: str,
prompt_tokens: int,
) -> Dict[str, Any]:
token_count = count_tokens(text)
base: Dict[str, Any] = {
"type": "message",
"id": entry_id,
"parentId": parent_id,
"timestamp": ts_iso,
}
if role == "user":
# pi-ai user messages support either a plain string or a [{type:"text"}] list.
# Keep the block form for better compatibility with transcript consumers.
base["message"] = {
"role": "user",
"content": [{"type": "text", "text": text}],
"timestamp": int(ts_ms),
}
# Real OpenClaw transcripts generally only have usage snapshots on assistant messages
# (LLM calls), so per-call mode leaves user entries without usage.
if usage_mode == "per-message":
base["usage"] = make_usage_snapshot(input_tokens=token_count, output_tokens=0)
return base
if role == "assistant":
if usage_mode == "per-call":
usage = make_usage_snapshot(input_tokens=prompt_tokens, output_tokens=token_count)
elif usage_mode == "per-message":
usage = make_usage_snapshot(input_tokens=0, output_tokens=token_count)
else:
raise ValueError(f"unsupported usage_mode: {usage_mode}")
base["message"] = {
"role": "assistant",
"content": [{"type": "text", "text": text}],
"api": DEFAULT_API,
"provider": DEFAULT_PROVIDER,
"model": DEFAULT_MODEL,
"usage": usage,
"stopReason": DEFAULT_STOP_REASON,
"timestamp": int(ts_ms),
}
return base
raise ValueError(f"unsupported role: {role}")
def read_transcript(path: Path) -> List[Dict[str, Any]]:
entries: List[Dict[str, Any]] = []
with path.open("r", encoding="utf-8") as fh:
for line_no, line in enumerate(fh, start=1):
stripped = line.strip()
if not stripped:
continue
try:
entries.append(json.loads(stripped))
except json.JSONDecodeError as exc:
raise ValueError(f"invalid JSON in {path} at line {line_no}") from exc
return entries
def clean_output() -> None:
"""Remove everything under output/ so we always start fresh."""
if OUTPUT.exists():
shutil.rmtree(OUTPUT)
SESS_DIR.mkdir(parents=True, exist_ok=True)
def iter_samples(path: Path) -> Iterable[Tuple[int, Dict[str, Any]]]:
"""Yield (line_no, sample) supporting JSON array/object or JSONL.
Smoke test (JSONL):
$ cd benchmark/MR-NIAH
$ python3 mr-niah-transcript.py --limit 2
# Expect 2 session files and 2 index rows.
"""
text = path.read_text(encoding="utf-8")
stripped = text.strip()
if not stripped:
return
nonempty_lines = [
(line_no, line.strip())
for line_no, line in enumerate(text.splitlines(), start=1)
if line.strip()
]
jsonl_rows: Optional[List[Tuple[int, Dict[str, Any]]]] = None
jsonl_failure: Optional[Tuple[int, json.JSONDecodeError]] = None
if len(nonempty_lines) > 1:
jsonl_rows = []
for line_no, line in nonempty_lines:
try:
obj = json.loads(line)
except json.JSONDecodeError as exc:
jsonl_failure = (line_no, exc)
jsonl_rows = None
break
jsonl_rows.append((line_no, obj)) # type: ignore[arg-type]
if jsonl_rows is not None:
for row in jsonl_rows:
yield row
return
try:
obj = json.loads(stripped)
except json.JSONDecodeError as exc:
if jsonl_failure:
line_no, _ = jsonl_failure
raise ValueError(f"invalid JSON in {path} at line {line_no}") from exc
raise ValueError(f"invalid JSON in {path}") from exc
if isinstance(obj, list):
for idx, item in enumerate(obj, start=1):
if isinstance(item, dict):
yield idx, item
else:
yield idx, {"value": item}
return
if isinstance(obj, dict):
data = obj.get("data")
if isinstance(data, list):
for idx, item in enumerate(data, start=1):
if isinstance(item, dict):
yield idx, item
else:
yield idx, {"value": item}
return
yield 1, obj
return
def normalize_turn(m: Dict[str, Any]) -> Optional[Tuple[str, str]]:
role = m.get("role") or m.get("from")
content = m.get("content") or m.get("value") or m.get("text")
if role == "human":
role = "user"
if role in ("gpt", "bot"):
role = "assistant"
if role not in ("user", "assistant"):
return None
if isinstance(content, list):
content = json.dumps(content, ensure_ascii=False)
if not isinstance(content, str):
return None
return role, content
def split_history_question(messages: List[Dict[str, Any]]) -> Tuple[List[Tuple[str, str]], str]:
turns: List[Tuple[str, str]] = []
for m in messages:
if isinstance(m, dict):
t = normalize_turn(m)
if t:
turns.append(t)
if not turns:
raise ValueError("no valid turns")
# find last user index
last_user_idx = None
for i in range(len(turns) - 1, -1, -1):
if turns[i][0] == "user":
last_user_idx = i
break
if last_user_idx is None:
raise ValueError("no user turn found")
question = turns[last_user_idx][1]
history = turns[:last_user_idx] # EXCLUDES final user question
return history, question
def validate_transcript(lines: List[Dict[str, Any]]) -> None:
if not lines or lines[0].get("type") != "session":
raise ValueError("first line must be session header")
prev = None
seen = set()
for i, entry in enumerate(lines[1:], start=2):
if entry.get("type") != "message":
raise ValueError(f"bad entry type at line {i}: {entry.get('type')}")
eid = entry.get("id")
if not isinstance(eid, str) or len(eid) != 8:
raise ValueError(f"bad entry id at line {i}: {eid!r}")
if eid in seen:
raise ValueError(f"duplicate entry id at line {i}: {eid}")
seen.add(eid)
pid = entry.get("parentId")
if prev is None:
if pid is not None:
raise ValueError("first message parentId must be null")
else:
if pid != prev:
raise ValueError(f"parentId chain broken at line {i}: {pid} != {prev}")
msg = entry.get("message")
if not isinstance(msg, dict):
raise ValueError(f"missing message at line {i}")
role = msg.get("role")
if role not in ("user", "assistant"):
raise ValueError(f"bad role at line {i}: {role!r}")
ts_ms = msg.get("timestamp")
if not isinstance(ts_ms, int):
raise ValueError(f"message.timestamp must be int(ms) at line {i}")
if role == "user":
# pi-ai user message: { role, content: string, timestamp:number }
content = msg.get("content")
if isinstance(content, str):
if not content:
raise ValueError(f"user content missing/invalid at line {i}")
elif isinstance(content, list):
if not content:
raise ValueError(f"user content missing/invalid at line {i}")
for chunk in content:
if not isinstance(chunk, dict):
raise ValueError(f"user content chunk invalid at line {i}")
if chunk.get("type") != "text" or not isinstance(chunk.get("text"), str):
raise ValueError(f"user content chunk missing text at line {i}")
else:
raise ValueError(f"user content missing/invalid at line {i}")
# Optional token snapshot may be stored on the entry (not on the user message).
usage = entry.get("usage")
if usage is not None:
if not isinstance(usage, dict):
raise ValueError(f"entry.usage invalid at line {i}")
if "totalTokens" not in usage or not isinstance(usage["totalTokens"], int):
raise ValueError(f"entry.usage.totalTokens missing/invalid at line {i}")
prev = eid
continue
# assistant
if not isinstance(msg.get("content"), list) or not msg["content"]:
raise ValueError(f"assistant content missing/invalid at line {i}")
for chunk in msg["content"]:
if not isinstance(chunk, dict):
raise ValueError(f"assistant content chunk invalid at line {i}")
if chunk.get("type") != "text" or not isinstance(chunk.get("text"), str):
raise ValueError(f"assistant content chunk missing text at line {i}")
for key in ("api", "provider", "model", "stopReason"):
if not isinstance(msg.get(key), str):
raise ValueError(f"missing assistant {key} at line {i}")
usage = msg.get("usage")
if not isinstance(usage, dict):
raise ValueError(f"assistant usage missing/invalid at line {i}")
for field in ("input", "output", "cacheRead", "cacheWrite"):
if field not in usage:
raise ValueError(f"assistant usage missing {field} at line {i}")
if "totalTokens" not in usage or not isinstance(usage["totalTokens"], int):
raise ValueError(f"assistant usage.totalTokens missing/invalid at line {i}")
cost = usage.get("cost")
if not isinstance(cost, dict):
raise ValueError(f"assistant usage.cost missing/invalid at line {i}")
for field in ("input", "output", "cacheRead", "cacheWrite", "total"):
if field not in cost:
raise ValueError(f"assistant usage.cost missing {field} at line {i}")
prev = eid
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument(
"inputs",
nargs="*",
metavar="INPUT",
help="Explicit MR-NIAH JSON/JSONL files (repeatable).",
)
ap.add_argument(
"--input",
dest="extra_inputs",
action="append",
default=[],
help="Additional input file path (repeatable). Accepts absolute paths or dataset-relative values such as data/chinese/10240_tokens.jsonl.",
)
ap.add_argument(
"--lang",
choices=["chinese", "english", "all", "none"],
default="all",
help="Auto-select files from origin/<lang> when no explicit inputs are provided (default: all).",
)
ap.add_argument(
"--tokens",
nargs="+",
default=["all"],
help="Token buckets to load (e.g. 10240 20480 or 'all').",
)
ap.add_argument("--limit", type=int, default=0, help="Stop after N samples (0 = all)")
ap.add_argument(
"--output-dir",
default="",
help="Write outputs under this directory (expects <dir>/sessions/ and <dir>/index.jsonl). Default: benchmark/MR-NIAH/output/",
)
ap.add_argument(
"--usage-mode",
choices=["per-call", "per-message"],
default="per-call",
help="Usage snapshot style: per-call mimics real OpenClaw (assistant usage grows with context); per-message is legacy/import style.",
)
ap.add_argument(
"--base-prompt-tokens",
type=int,
default=0,
help="Fixed prompt token baseline added to every assistant call usage.input (approx system prompt + tooling).",
)
ap.add_argument(
"--message-overhead-tokens",
type=int,
default=0,
help="Extra tokens to add per message when estimating call prompt size (approx chat serialization overhead).",
)
args = ap.parse_args()
output_dir = (args.output_dir or "").strip()
if output_dir:
p = Path(output_dir).expanduser()
if not p.is_absolute():
p = (HERE / p)
out_path = p.resolve()
global OUTPUT, SESS_DIR, INDEX_PATH
OUTPUT = out_path
SESS_DIR = OUTPUT / "sessions"
INDEX_PATH = OUTPUT / "index.jsonl"
inputs = gather_inputs(args.inputs, args.extra_inputs, args.lang, args.tokens)
clean_output()
limit = args.limit if args.limit and args.limit > 0 else None
count = 0
INDEX_PATH.parent.mkdir(parents=True, exist_ok=True)
with INDEX_PATH.open("w", encoding="utf-8") as index_file:
for inp in inputs:
source_label = describe_path(inp)
print(f"[input] {source_label}")
for line_no, obj in iter_samples(inp):
if not isinstance(obj, dict):
continue
messages = obj.get("messages")
if not isinstance(messages, list):
continue
label = obj.get("label")
if not isinstance(label, str):
label = json.dumps(label, ensure_ascii=False)
history, question = split_history_question(messages)
session_id = str(uuid.uuid4())
salt = session_id
base_dt = _dt.datetime.now(tz=_dt.timezone.utc)
entries: List[Dict[str, Any]] = [make_session_header(session_id, isoformat_utc(base_dt))]
parent = None
current_dt = base_dt
# Best-effort: approximate prompt growth by summing tokenized message text.
# This yields monotonically increasing assistant usage.totalTokens until compaction.
base_prompt_tokens = max(0, int(args.base_prompt_tokens))
overhead_tokens = max(0, int(args.message_overhead_tokens))
context_tokens = 0
for idx, (role, text_value) in enumerate(history, start=1):
current_dt = current_dt + _dt.timedelta(seconds=1)
eid = short_hex(idx, salt)
ts_iso = isoformat_utc(current_dt)
ts_ms = int(current_dt.timestamp() * 1000)
prompt_tokens = base_prompt_tokens + context_tokens
entries.append(
make_message_entry(
eid,
parent,
ts_iso,
ts_ms,
role,
text_value,
usage_mode=str(args.usage_mode),
prompt_tokens=prompt_tokens,
)
)
parent = eid
context_tokens += count_tokens(text_value) + overhead_tokens
validate_transcript(entries)
out_path = SESS_DIR / f"{session_id}.jsonl"
with out_path.open("w", encoding="utf-8") as f:
for e in entries:
f.write(json.dumps(e, ensure_ascii=False) + "\n")
written_entries = read_transcript(out_path)
validate_transcript(written_entries)
index_file.write(
json.dumps(
{
"id": count,
"line": line_no,
"sourceFile": source_label,
"sourceLine": line_no,
"session": session_id,
"sessionFile": f"sessions/{session_id}.jsonl",
"question": question,
"answer": label,
},
ensure_ascii=False,
)
+ "\n"
)
count += 1
if limit is not None and count >= limit:
break
if limit is not None and count >= limit:
break
if count == 0:
print("WARNING: no sessions generated (check input format)", file=sys.stderr)
print(f"Generated {count} sessions -> {SESS_DIR}")
print(f"Index -> {INDEX_PATH}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
================================================
FILE: benchmark/MR-NIAH/run_batch.py
================================================
#!/usr/bin/env python3
"""MR-NIAH batch runner.
Design goal:
- For each generated session transcript in output/sessions/<sessionId>.jsonl:
1) Copy transcript into the target OpenClaw profile's sessions dir.
2) Register the sessionId into that profile's sessions.json store with a unique key
(so the store can be searched by sessionId).
3) Run `openclaw agent --session-id <sessionId> --message <question> --json`.
4) Save raw stdout/stderr + extracted prediction.
Why registration is needed:
- OpenClaw's session store (sessions.json) is keyed by sessionKey (usually derived from --to).
- If we don't use --to, we must add store entries ourselves so resolveSessionKeyForRequest()
can find a key by sessionId.
Usage:
cd benchmark/MR-NIAH
python3 run_batch.py --profile mrniah_local --agent main --limit 30
Outputs:
- results/predictions.jsonl
- results/raw/<id>-<sessionId>.stdout.json
- results/raw/<id>-<sessionId>.stderr.txt
"""
from __future__ import annotations
import argparse
import atexit
import http.client
import json
import os
import random
import re
import shutil
import socket
import subprocess
import sys
import time
import urllib.parse
import urllib.request
import urllib.error
import uuid
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, List, Optional
HERE = Path(__file__).resolve().parent
OUTPUT = HERE / "output"
INDEX = OUTPUT / "index.jsonl"
SESS_OUT = OUTPUT / "sessions"
META_SUFFIX = ".meta.json"
PROFILE_MEMORY_DIR = "memory"
OPENCLAW_DEFAULT_WORKSPACE_DIRNAME = ".openclaw"
_openclaw_conversation_access_support: Optional[bool] = None
_openclaw_conversation_access_skip_logged = False
def now_ms() -> int:
return int(time.time() * 1000)
def _parse_openclaw_version_text(text: str) -> Optional[tuple[int, int, int]]:
match = re.search(r"(\d+)\.(\d+)(?:\.(\d+))?", text)
if not match:
return None
return (
int(match.group(1)),
int(match.group(2)),
int(match.group(3) or 0),
)
def openclaw_supports_conversation_access() -> bool:
global _openclaw_conversation_access_support
if _openclaw_conversation_access_support is not None:
return _openclaw_conversation_access_support
proc = subprocess.run(
["openclaw", "--version"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=False,
)
version = _parse_openclaw_version_text(f"{proc.stdout}\n{proc.stderr}")
if version is None:
_openclaw_conversation_access_support = False
return False
major, minor, patch = version
if major >= 2026:
_openclaw_conversation_access_support = (major, minor, patch) >= (2026, 4, 22)
else:
_openclaw_conversation_access_support = (major, minor, patch) >= (4, 23, 0)
return _openclaw_conversation_access_support
def ensure_mem9_conversation_access(profile: str) -> None:
global _openclaw_conversation_access_skip_logged
if not openclaw_supports_conversation_access():
if not _openclaw_conversation_access_skip_logged:
print(
"[mem9] OpenClaw version does not support hooks.allowConversationAccess; "
"automatic conversation upload requires OpenClaw 4.23+ / 2026.4.22+",
file=sys.stderr,
flush=True,
)
_openclaw_conversation_access_skip_logged = True
return
subprocess.run(
[
"openclaw",
"--profile",
profile,
"config",
"set",
"plugins.entries.mem9.hooks.allowConversationAccess",
"true",
],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
def extract_last_compaction_event(session_file: Path) -> Optional[Dict[str, Any]]:
"""Return the last compaction event line (if any) from a session JSONL transcript.
Transcripts can be very large, so scan from the end (best-effort).
"""
try:
with session_file.open("rb") as fh:
fh.seek(0, 2)
pos = fh.tell()
if pos <= 0:
return None
block_size = 1024 * 1024 # 1MB
buf = b""
max_scan_bytes = 64 * 1024 * 1024 # 64MB
scanned = 0
while pos > 0 and scanned < max_scan_bytes:
read_size = block_size if pos >= block_size else pos
pos -= read_size
fh.seek(pos)
chunk = fh.read(read_size)
scanned += len(chunk)
buf = chunk + buf
if b"\n" not in buf and pos > 0:
continue
lines = buf.split(b"\n")
buf = lines[0] # keep incomplete head for next iteration
for raw in reversed(lines[1:]):
raw = raw.strip()
if not raw:
continue
# Fast substring check before JSON parse.
if b'"type"' not in raw or b"compaction" not in raw:
continue
try:
obj = json.loads(raw.decode("utf-8"))
except Exception:
continue
if isinstance(obj, dict) and obj.get("type") == "compaction":
return obj
except FileNotFoundError:
return None
return None
def coerce_str(value: Any) -> Optional[str]:
if isinstance(value, str):
v = value.strip()
return v if v else None
return None
def preview_text(value: Any, max_chars: int) -> Optional[str]:
if max_chars <= 0:
return None
if not isinstance(value, str):
return None
s = value.replace("\n", " ").strip()
if not s:
return None
if len(s) <= max_chars:
return s
return s[:max_chars] + "..."
def truncate_text(value: str, max_chars: int) -> str:
if max_chars <= 0:
return value
if len(value) <= max_chars:
return value
return value[:max_chars]
def maybe_truncate(text: str, max_chars: int) -> tuple[str, bool]:
if max_chars <= 0:
return text, False
if len(text) <= max_chars:
return text, False
return text[:max_chars], True
def compaction_event_key(event: Dict[str, Any]) -> tuple[Optional[str], Optional[str]]:
"""Best-effort stable identifier for comparing compaction events."""
return (coerce_str(event.get("id")), coerce_str(event.get("timestamp")))
def maybe_add_agent_arg(cmd: List[str], agent: str) -> None:
if agent and agent != "main":
cmd.extend(["--agent", agent])
def load_index(path: Path) -> List[Dict[str, Any]]:
lines = [ln for ln in path.read_text(encoding="utf-8").splitlines() if ln.strip()]
return [json.loads(ln) for ln in lines]
def read_json(path: Path) -> Dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def write_json(path: Path, obj: Any) -> None:
path.write_text(json.dumps(obj, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def append_jsonl(path: Path, obj: Any) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(obj, ensure_ascii=False) + "\n")
def safe_extract_text(payload_obj: Any) -> str:
"""Extract assistant text from OpenClaw CLI --json output.
Expected shapes we've seen:
- embedded: {payloads:[{text:...}], meta:{...}}
- gateway: {runId, status, result:{payloads:[{text:...}]}}
If payloads empty, return "".
"""
def flatten(v: Any) -> Optional[str]:
if isinstance(v, str):
return v
if isinstance(v, dict):
for k in ("text", "content", "value", "output"):
if k in v:
out = flatten(v[k])
if out:
return out
return None
if isinstance(v, list):
parts = [flatten(x) for x in v]
parts = [p for p in parts if p]
if parts:
return "\n".join(parts)
return None
if isinstance(payload_obj, dict):
# embedded style
if isinstance(payload_obj.get("payloads"), list) and payload_obj["payloads"]:
texts = []
for p in payload_obj["payloads"]:
if isinstance(p, dict):
t = flatten(p.get("text"))
if t:
texts.append(t)
if texts:
return "\n".join(texts).strip()
# gateway style
result = payload_obj.get("result")
if isinstance(result, dict):
payloads = result.get("payloads")
if isinstance(payloads, list) and payloads:
texts = []
for p in payloads:
if isinstance(p, dict):
t = flatten(p.get("text"))
if t:
texts.append(t)
if texts:
return "\n".join(texts).strip()
return ""
ANSI_RE = re.compile(r"\x1B\[[0-9;]*[A-Za-z]")
JSON_DECODER = json.JSONDecoder()
def strip_ansi(text: str) -> str:
return ANSI_RE.sub("", text)
def parse_json_stdout(stdout: str) -> Optional[Any]:
if not stdout:
return None
cleaned = strip_ansi(stdout).strip()
if not cleaned:
return None
def try_decode(text: str) -> Optional[Any]:
if not text:
return None
try:
obj, _ = JSON_DECODER.raw_decode(text)
return obj
except json.JSONDecodeError:
return None
obj = try_decode(cleaned)
if obj is not None:
return obj
brace_idx = cleaned.find("{")
# NOTE: bracket_idx is only searched when no '{' exists anywhere in the
# output, so array-shaped JSON responses are never tried if any '{' is
# present. This works for current OpenClaw output shapes but may need a
# fix if array-only responses become possible.
bracket_idx = cleaned.find("[") if brace_idx == -1 else -1
start = -1
if brace_idx != -1:
start = brace_idx
elif bracket_idx != -1:
start = bracket_idx
if start == -1:
return None
snippet = cleaned[start:].lstrip()
return try_decode(snippet)
def find_first_str_by_key(obj: Any, keys: set[str]) -> Optional[str]:
"""Best-effort deep search for the first string value for any key in keys."""
queue: List[Any] = [obj]
seen = 0
while queue and seen < 2000:
cur = queue.pop(0)
seen += 1
if isinstance(cur, dict):
for k, v in cur.items():
if k in keys and isinstance(v, str):
out = v.strip()
if out:
return out
queue.append(v)
elif isinstance(cur, list):
queue.extend(cur)
return None
def extract_effective_session_id(payload_obj: Any) -> Optional[str]:
# Common candidates across embedded/gateway outputs.
keys = {
"sessionId",
"sessionID",
"session_id",
"session",
"effectiveSessionId",
"newSessionId",
}
return find_first_str_by_key(payload_obj, keys)
def extract_run_id(payload_obj: Any) -> Optional[str]:
keys = {"runId", "runID", "run_id"}
return find_first_str_by_key(payload_obj, keys)
def build_multipart_form(
*, fields: Dict[str, str], file_field: str, filename: str, file_bytes: bytes, content_type: str
) -> tuple[bytes, str]:
boundary = "---------------------------" + uuid.uuid4().hex
lines: List[bytes] = []
def add_line(s: str) -> None:
lines.append(s.encode("utf-8"))
for k, v in fields.items():
add_line(f"--{boundary}\r\n")
add_line(f'Content-Disposition: form-data; name="{k}"\r\n\r\n')
add_line(f"{v}\r\n")
add_line(f"--{boundary}\r\n")
add_line(
f'Content-Disposition: form-data; name="{file_field}"; filename="{filename}"\r\n'
)
add_line(f"Content-Type: {content_type}\r\n\r\n")
lines.append(file_bytes)
add_line("\r\n")
add_line(f"--{boundary}--\r\n")
body = b"".join(lines)
return body, f"multipart/form-data; boundary={boundary}"
def http_json(
*,
method: str,
url: str,
headers: Dict[str, str],
body: Optional[bytes] = None,
timeout_s: int = 30,
max_attempts: int = 1,
retry_base_sleep_s: float = 1.0,
retry_max_sleep_s: float = 10.0,
) -> Any:
retryable_http = {408, 425, 429, 500, 502, 503, 504}
last_exc: Optional[BaseException] = None
attempts = max(1, int(max_attempts))
for attempt in range(1, attempts + 1):
req = urllib.request.Request(url, data=body, method=method.upper())
for k, v in headers.items():
req.add_header(k, v)
try:
with urllib.request.urlopen(req, timeout=timeout_s) as resp:
data = resp.read()
if not data:
return None
return json.loads(data.decode("utf-8"))
except urllib.error.HTTPError as e:
body_bytes = b""
try:
body_bytes = e.read() or b""
except Exception:
body_bytes = b""
body_text = body_bytes.decode("utf-8", errors="replace")
last_exc = RuntimeError(f"HTTP {e.code} {method} {url}: {body_text[:2000]}")
if attempt < attempts and int(getattr(e, "code", 0) or 0) in retryable_http:
base = max(0.0, float(retry_base_sleep_s))
cap = max(base, float(retry_max_sleep_s))
sleep_s = min(cap, base * (2 ** (attempt - 1)))
sleep_s = sleep_s * (0.7 + random.random() * 0.6) # jitter
time.sleep(sleep_s)
continue
raise last_exc from e
except (
urllib.error.URLError,
socket.timeout,
TimeoutError,
ConnectionResetError,
http.client.HTTPException,
) as e:
last_exc = RuntimeError(f"Network error {method} {url}: {e}")
if attempt < attempts:
base = max(0.0, float(retry_base_sleep_s))
cap = max(base, float(retry_max_sleep_s))
sleep_s = min(cap, base * (2 ** (attempt - 1)))
sleep_s = sleep_s * (0.7 + random.random() * 0.6) # jitter
time.sleep(sleep_s)
continue
raise last_exc from e
if last_exc is not None:
raise last_exc
raise RuntimeError(f"Unexpected error {method} {url}")
def mem9_import_session(
*,
api_url: str,
tenant_id: str,
agent_id: str,
session_id: str,
import_file: Path,
timeout_s: int,
poll_interval_s: float,
) -> Dict[str, Any]:
"""Upload a session file via /imports and wait for completion.
Note: This uploads the OpenClaw session JSONL transcript directly. The server supports
OpenClaw's nested JSONL format and will extract {role, content} for ingest.
"""
start = time.time()
import_bytes = import_file.read_bytes()
body, ct = build_multipart_form(
fields={"agent_id": agent_id, "file_type": "session", "session_id": session_id},
file_field="file",
filename=f"{session_id}.jsonl",
file_bytes=import_bytes,
content_type="application/octet-stream",
)
headers = {"Content-Type": ct, "X-Mnemo-Agent-Id": agent_id}
create_url = f"{api_url}/v1alpha1/mem9s/{tenant_id}/imports"
created = http_json(
method="POST",
url=create_url,
headers=headers,
body=body,
timeout_s=60,
max_attempts=2,
retry_base_sleep_s=1.0,
retry_max_sleep_s=10.0,
)
task_id = created.get("id") if isinstance(created, dict) else None
if not isinstance(task_id, str) or not task_id:
raise RuntimeError(f"mem9 import did not return task id: {created!r}")
print(
f"[mem9] import created session={session_id} task={task_id}",
flush=True,
)
detail_url = f"{api_url}/v1alpha1/mem9s/{tenant_id}/imports/{task_id}"
last_detail: Any = None
last_status: Any = None
last_print_s = 0.0
transient_errors = 0
while True:
elapsed = time.time() - start
if elapsed > timeout_s:
raise TimeoutError(f"mem9 import task timed out after {timeout_s}s (task={task_id})")
try:
detail = http_json(
method="GET",
url=detail_url,
headers={"X-Mnemo-Agent-Id": agent_id},
timeout_s=60,
max_attempts=6,
retry_base_sleep_s=1.0,
retry_max_sleep_s=10.0,
)
except Exception as e:
transient_errors += 1
# Treat polling failures as transient; keep waiting until the overall task timeout.
print(
f"[mem9] import poll transient_error={transient_errors} task={task_id} err={e}",
flush=True,
)
time.sleep(poll_interval_s)
continue
last_detail = detail
status = detail.get("status") if isinstance(detail, dict) else None
total = detail.get("total") if isinstance(detail, dict) else None
done = detail.get("done") if isinstance(detail, dict) else None
now_s = time.time()
should_print = False
if status != last_status:
should_print = True
if status in ("done", "failed"):
should_print = True
if (now_s - last_print_s) >= 5.0:
should_print = True
if should_print:
last_status = status
last_print_s = now_s
print(
f"[mem9] import poll task={task_id} status={status} done={done} total={total}",
flush=True,
)
if status in ("done", "failed"):
break
time.sleep(poll_interval_s)
total_chunks = last_detail.get("total") if isinstance(last_detail, dict) else None
done_chunks = last_detail.get("done") if isinstance(last_detail, dict) else None
error_msg = last_detail.get("error") if isinstance(last_detail, dict) else None
status_final = (last_detail.get("status") if isinstance(last_detail, dict) else None)
verified = (
status_final == "done"
and not (isinstance(error_msg, str) and error_msg.strip())
and (
(isinstance(total_chunks, int) and isinstance(done_chunks, int) and done_chunks >= total_chunks)
or (not isinstance(total_chunks, int) or not isinstance(done_chunks, int))
)
)
return {
"create": created,
"taskId": task_id,
"status": status_final,
"detail": last_detail,
"verified": verified,
"totalChunks": total_chunks if isinstance(total_chunks, int) else None,
"doneChunks": done_chunks if isinstance(done_chunks, int) else None,
"durationMs": int((time.time() - start) * 1000),
"fileBytes": len(import_bytes),
"filePath": str(import_file),
"transientPollErrors": transient_errors,
}
def mem9_list_memories(
*,
api_url: str,
tenant_id: str,
agent_id: str,
limit: int = 200,
offset: int = 0,
) -> Dict[str, Any]:
url = f"{api_url}/v1alpha1/mem9s/{tenant_id}/memories?limit={int(limit)}&offset={int(offset)}"
data = http_json(
method="GET",
url=url,
headers={"X-Mnemo-Agent-Id": agent_id},
timeout_s=30,
max_attempts=6,
retry_base_sleep_s=1.0,
retry_max_sleep_s=10.0,
)
if not isinstance(data, dict):
raise RuntimeError(f"mem9 list memories returned non-object: {data!r}")
return data
def mem9_search_memories(
*,
api_url: str,
tenant_id: str,
agent_id: str,
query: str,
limit: int = 10,
offset: int = 0,
) -> Dict[str, Any]:
params = urllib.parse.urlencode(
{
"q": query,
"limit": int(limit),
"offset": int(offset),
}
)
url = f"{api_url}/v1alpha1/mem9s/{tenant_id}/memories?{params}"
data = http_json(
method="GET",
url=url,
headers={"X-Mnemo-Agent-Id": agent_id},
timeout_s=30,
max_attempts=6,
retry_base_sleep_s=1.0,
retry_max_sleep_s=10.0,
)
if not isinstance(data, dict):
raise RuntimeError(f"mem9 search memories returned non-object: {data!r}")
return data
def mem9v2_create_messages(
*,
api_url: str,
api_key: str,
agent_id: str,
session_id: str,
messages: List[Dict[str, str]],
) -> Dict[str, Any]:
url = f"{api_url}/v1alpha2/mem9s/memories"
body = {
"agent_id": agent_id,
"session_id": session_id,
"messages": messages,
}
data = http_json(
method="POST",
url=url,
headers={
"X-Mnemo-Agent-Id": agent_id,
"X-API-Key": api_key,
"Content-Type": "application/json",
},
body=json.dumps(body, ensure_ascii=False).encode("utf-8"),
timeout_s=30,
max_attempts=6,
retry_base_sleep_s=1.0,
retry_max_sleep_s=10.0,
)
if not isinstance(data, dict):
raise RuntimeError(f"mem9 v1alpha2 create returned non-object: {data!r}")
return data
def mem9v2_search_memories(
*,
api_url: str,
api_key: str,
agent_id: str,
query: str,
limit: int = 10,
offset: int = 0,
memory_type: str = "",
session_id: str = "",
) -> Dict[str, Any]:
params: Dict[str, Any] = {
"q": query,
"limit": int(limit),
"offset": int(offset),
}
if memory_type:
params["memory_type"] = memory_type
if session_id:
params["session_id"] = session_id
qs = urllib.parse.urlencode(params)
url = f"{api_url}/v1alpha2/mem9s/memories?{qs}"
data = http_json(
method="GET",
url=url,
headers={
"X-Mnemo-Agent-Id": agent_id,
"X-API-Key": api_key,
},
timeout_s=30,
max_attempts=6,
retry_base_sleep_s=1.0,
retry_max_sleep_s=10.0,
)
if not isinstance(data, dict):
raise RuntimeError(f"mem9 v1alpha2 search returned non-object: {data!r}")
return data
def mem9_clear_memories(
*,
api_url: str,
tenant_id: str,
agent_id: str,
max_to_delete: int = 50_000,
stable_empty_checks: int = 3,
stable_empty_interval_s: float = 1.0,
max_duration_s: float = 90.0,
) -> Dict[str, Any]:
"""Delete all tenant memories (best-effort).
mem9 may add new memories asynchronously (e.g. smart ingest finishing late). To avoid
flakiness, we require the list endpoint to be empty for N consecutive checks.
"""
start = time.time()
deleted = 0
transient_errors = 0
empty_streak = 0
# Keep fetching from offset=0 while deleting, because totals/offsets shift.
for _ in range(2_000):
if (time.time() - start) > float(max_duration_s):
break
if deleted >= max_to_delete:
raise RuntimeError(
f"mem9 clear exceeded max_to_delete={max_to_delete} (tenant={tenant_id})"
)
try:
page = mem9_list_memories(api_url=api_url, tenant_id=tenant_id, agent_id=agent_id)
except Exception as e:
transient_errors += 1
print(f"[mem9] clear list transient_error={transient_errors} err={e}", flush=True)
time.sleep(1.0)
continue
memories = page.get("memories")
if not isinstance(memories, list):
raise RuntimeError(f"mem9 list memories missing .memories: {page!r}")
if len(memories) == 0:
empty_streak += 1
if empty_streak >= max(1, int(stable_empty_checks)):
break
time.sleep(float(stable_empty_interval_s))
continue
empty_streak = 0
for m in memories:
if not isinstance(m, dict):
continue
mid = m.get("id")
if not isinstance(mid, str) or not mid:
continue
del_url = f"{api_url}/v1alpha1/mem9s/{tenant_id}/memories/{mid}"
try:
http_json(
method="DELETE",
url=del_url,
headers={"X-Mnemo-Agent-Id": agent_id},
timeout_s=30,
max_attempts=6,
retry_base_sleep_s=1.0,
retry_max_sleep_s=10.0,
)
deleted += 1
except Exception as e:
transient_errors += 1
print(
f"[mem9] clear delete transient_error={transient_errors} id={mid} err={e}",
flush=True,
)
time.sleep(1.0)
final_page = mem9_list_memories(api_url=api_url, tenant_id=tenant_id, agent_id=agent_id)
final_memories = final_page.get("memories")
remaining = len(final_memories) if isinstance(final_memories, list) else None
verified = remaining == 0
remaining_sample: Optional[List[Dict[str, Any]]] = None
if isinstance(final_memories, list) and final_memories:
sample: List[Dict[str, Any]] = []
for m in final_memories[:10]:
if not isinstance(m, dict):
continue
sample.append(
{
"id": m.get("id"),
"agent_id": m.get("agent_id"),
"session_id": m.get("session_id"),
"memory_type": m.get("memory_type"),
"state": m.get("state"),
"created_at": m.get("created_at"),
"updated_at": m.get("updated_at"),
}
)
remaining_sample = sample
return {
"deleted": deleted,
"remaining": remaining,
"verified": verified,
"durationMs": int((time.time() - start) * 1000),
"transientErrors": transient_errors,
"remainingSample": remaining_sample,
}
def mem9_provision_tenant(*, api_url: str) -> str:
"""Provision a new mem9 tenant/space via POST /v1alpha1/mem9s."""
created = http_json(
method="POST",
url=f"{api_url}/v1alpha1/mem9s",
headers={},
timeout_s=30,
max_attempts=6,
retry_base_sleep_s=1.0,
retry_max_sleep_s=10.0,
)
tenant_id = created.get("id") if isinstance(created, dict) else None
if not isinstance(tenant_id, str) or not tenant_id.strip():
raise RuntimeError(f"mem9 provision did not return .id: {created!r}")
return tenant_id.strip()
def _http_probe_ok(*, url: str, timeout_s: int = 5) -> bool:
req = urllib.request.Request(url, method="GET")
try:
with urllib.request.urlopen(req, timeout=timeout_s) as resp:
code = getattr(resp, "status", None) or resp.getcode()
return 200 <= int(code) < 300
except Exception:
return False
def _wait_gateway_healthy(*, port: int, timeout_s: int = 60) -> None:
deadline = time.time() + float(timeout_s)
url = f"http://localhost:{int(port)}/health"
while time.time() < deadline:
if _http_probe_ok(url=url, timeout_s=5):
return
time.sleep(0.5)
raise RuntimeError(f"gateway not healthy at {url}")
def _start_gateway(*, profile: str, log_path: Path) -> subprocess.Popen[str]:
log_path.parent.mkdir(parents=True, exist_ok=True)
fh = log_path.open("a", encoding="utf-8")
# NOTE: We intentionally do not pass port/token here; those are read from the profile config.
proc = subprocess.Popen(
["openclaw", "--profile", profile, "gateway"],
stdout=fh,
stderr=subprocess.STDOUT,
text=True,
)
# Close our copy of the file handle; the child keeps its own fd.
try:
fh.close()
except Exception:
pass
return proc
def _stop_process(proc: Optional[subprocess.Popen[str]]) -> None:
if proc is None:
return
if proc.poll() is not None:
return
try:
proc.terminate()
except Exception:
return
try:
proc.wait(timeout=10)
except Exception:
try:
proc.kill()
except Exception:
pass
def _summarize_memories_page(page: Dict[str, Any]) -> Dict[str, Any]:
memories_raw = page.get("memories")
memories: List[Dict[str, Any]] = memories_raw if isinstance(memories_raw, list) else []
sample: List[Dict[str, Any]] = []
for m in memories[:10]:
if not isinstance(m, dict):
continue
content_preview = preview_text(m.get("content"), 220)
sample.append(
{
"id": m.get("id"),
"agent_id": m.get("agent_id"),
"session_id": m.get("session_id"),
"memory_type": m.get("memory_type"),
"state": m.get("state"),
"tags": m.get("tags"),
"source": m.get("source"),
"score": m.get("score"),
"content_preview": content_preview,
"created_at": m.get("created_at"),
"updated_at": m.get("updated_at"),
}
)
total = page.get("total")
return {
"count": len(memories),
"total": int(total) if isinstance(total, int) else None,
"sample": sample if sample else None,
}
def summarize_memories_page(page: Dict[str, Any], content_preview_chars: int) -> Dict[str, Any]:
base = _summarize_memories_page(page)
sample = base.get("sample")
if not isinstance(sample, list) or content_preview_chars <= 0:
return base
for rec in sample:
if not isinstance(rec, dict):
continue
if "content_preview" in rec:
rec["content_preview"] = preview_text(rec.get("content_preview"), int(content_preview_chars))
return base
@dataclass
class StorePaths:
profile: str
agent: str
profile_dir: Path
sessions_dir: Path
store_path: Path
def resolve_store_paths(profile: str, agent: str) -> StorePaths:
profile_dir = Path.home() / f".openclaw-{profile}"
sessions_dir = profile_dir / "agents" / agent / "sessions"
store_path = sessions_dir / "sessions.json"
return StorePaths(
profile=profile,
agent=agent,
profile_dir=profile_dir,
sessions_dir=sessions_dir,
store_path=store_path,
)
def resolve_default_openclaw_workspace_dir(profile: str) -> Path:
"""Best-effort match for OpenClaw's default workspace dir derivation.
OpenClaw workspaces are stored under ~/.openclaw/workspace[-<profile>].
Note: the workspace dir is *not* under the profile's state dir.
"""
base = Path.home() / OPENCLAW_DEFAULT_WORKSPACE_DIRNAME
prof = (profile or "").strip()
if prof and prof.lower() != "default":
return base / f"workspace-{prof}"
return base / "workspace"
def resolve_profile_workspace_dir(*, profile: str, agent: str, profile_dir: Path) -> Path:
"""Resolve the effective agent workspace dir for this profile (best-effort).
OpenClaw chooses workspaces in this order:
1) agents.list[].workspace for the selected agent id
2) agents.defaults.workspace (for the default agent)
3) fallback to ~/.openclaw/workspace[-<profile>]
We mirror that here so injected transcripts have a `cwd` that matches the
gateway's embedded agent workspace.
"""
cfg_path = profile_dir / "openclaw.json"
try:
cfg = json.loads(cfg_path.read_text(encoding="utf-8"))
except Exception:
return resolve_default_openclaw_workspace_dir(profile)
agents_cfg = cfg.get("agents")
if isinstance(agents_cfg, dict):
agent_norm = (agent or "").strip().lower()
raw_list = agents_cfg.get("list")
if agent_norm and isinstance(raw_list, list):
for entry in raw_list:
if not isinstance(entry, dict):
continue
entry_id = entry.get("id")
if not isinstance(entry_id, str) or entry_id.strip().lower() != agent_norm:
continue
ws = entry.get("workspace")
if isinstance(ws, str) and ws.strip():
return Path(ws).expanduser()
defaults = agents_cfg.get("defaults")
if isinstance(defaults, dict):
ws = defaults.get("workspace")
if isinstance(ws, str) and ws.strip():
return Path(ws).expanduser()
return resolve_default_openclaw_workspace_dir(profile)
def rewrite_session_header_cwd(*, session_file: Path, cwd: Path) -> None:
"""Rewrite only the first JSONL header line to update `cwd`.
This keeps SessionManager.open() from inheriting an unrelated cwd (often "/")
from imported transcripts, which can leak into tool/file behaviors.
"""
tmp = session_file.with_suffix(session_file.suffix + ".tmp")
with session_file.open("rb") as src, tmp.open("wb") as dst:
first = src.readline()
if not first:
raise ValueError(f"empty session file: {session_file}")
try:
header = json.loads(first.decode("utf-8"))
except Exception as e:
raise ValueError(f"invalid session header JSON: {session_file}") from e
if not (isinstance(header, dict) and header.get("type") == "session"):
raise ValueError(f"first line is not session header: {session_file}")
header["cwd"] = str(cwd)
dst.write((json.dumps(header, ensure_ascii=False) + "\n").encode("utf-8"))
shutil.copyfileobj(src, dst)
tmp.replace(session_file)
def _extract_text_blocks(value: Any) -> str:
if isinstance(value, str):
return value
if isinstance(value, list):
chunks: List[str] = []
for item in value:
if isinstance(item, str):
chunks.append(item)
continue
if isinstance(item, dict):
if item.get("type") == "text" and isinstance(item.get("text"), str):
chunks.append(item["text"])
continue
for k in ("text", "content", "value", "output"):
if k in item and isinstance(item.get(k), str):
chunks.append(item[k])
break
return "".join(chunks)
if isinstance(value, dict):
for k in ("text", "content", "value", "output"):
if k in value:
return _extract_text_blocks(value[k])
return ""
def extract_openclaw_session_messages(session_file: Path) -> List[Dict[str, Any]]:
"""Extract {role, content, line} from an OpenClaw session transcript JSONL.
Supports both "simple" {role, content} lines and OpenClaw nested lines:
{"type":"message","message":{"role":"...","content":[{"type":"text","text":"..."}]}}
"""
messages: List[Dict[str, Any]] = []
with session_file.open("rb") as fh:
for line_number, raw in enumerate(fh, start=1):
raw = raw.strip()
if not raw:
continue
try:
obj = json.loads(raw.decode("utf-8"))
except Exception:
continue
if not isinstance(obj, dict):
continue
role: Optional[str] = None
content: str = ""
if isinstance(obj.get("role"), str):
role = obj.get("role")
content = _extract_text_blocks(obj.get("content"))
elif obj.get("type") == "message" and isinstance(obj.get("message"), dict):
msg = obj["message"]
if isinstance(msg.get("role"), str):
role = msg.get("role")
content = _extract_text_blocks(msg.get("content"))
if not role:
continue
role = role.strip()
if role in ("system",):
continue
content = (content or "").strip()
if not content:
continue
messages.append({"role": role, "content": content, "line": line_number})
return messages
def ensure_store_initialized(paths: StorePaths) -> None:
"""Ensure sessions dir & store file exist."""
paths.sessions_dir.mkdir(parents=True, exist_ok=True)
if not paths.store_path.exists():
write_json(paths.store_path, {})
def load_store(paths: StorePaths) -> Dict[str, Any]:
if not paths.store_path.exists():
return {}
return read_json(paths.store_path)
def pick_template_entry(store: Dict[str, Any]) -> Dict[str, Any]:
"""Pick an existing entry to clone optional fields from."""
# Prefer agent:main:main if present
for k in ("agent:main:main",):
v = store.get(k)
if isinstance(v, dict):
return v
# else first dict entry
for v in store.values():
if isinstance(v, dict):
return v
return {}
def _coerce_int(value: Any) -> Optional[int]:
if isinstance(value, bool):
return None
if isinstance(value, int):
return value
if isinstance(value, float) and value.is_integer():
return int(value)
if isinstance(value, str):
v = value.strip()
if not v:
return None
try:
return int(v, 10)
except ValueError:
return None
return None
def load_processed_sample_ids(pred_path: Path) -> set[int]:
"""Load completed sample IDs from an existing predictions.jsonl (best-effort).
Records written by this script include `ok` / `error`. When resuming, we
treat failed records as *not processed* so they can be retried automatically.
"""
if not pred_path.exists():
return set()
ids: set[int] = set()
try:
for ln in pred_path.read_text(encoding="utf-8").splitlines():
ln = ln.strip()
if not ln:
continue
try:
obj = json.loads(ln)
except Exception:
continue
if not isinstance(obj, dict):
continue
sid = _coerce_int(obj.get("id"))
if sid is None:
continue
ok = obj.get("ok")
if ok is False:
continue
err = obj.get("error")
if isinstance(err, str) and err.strip():
continue
if sid is not None:
ids.add(sid)
except Exception:
return ids
return ids
def build_session_entry(
*,
session_id: str,
session_file: Path,
bench_key: str,
) -> Dict[str, Any]:
# Keep this lightweight; updatedAt is primarily used for display/sorting in OpenClaw UIs.
return {
"sessionId": session_id,
"updatedAt": now_ms(),
"sessionFile": str(session_file),
# Use SessionEntry.label/displayName (string fields) to tag benchmark sessions.
# SessionEntry.origin is an object in OpenClaw; do not store a string there.
"label": "bench:mrniah",
"displayName": bench_key,
}
def upsert_store_entry(*, paths: StorePaths, key: str, entry: Dict[str, Any]) -> None:
store = load_store(paths)
store[key] = entry
write_json(paths.store_path, store)
def find_store_entry(
*, store: Dict[str, Any], session_id: str, preferred_key: str
) -> tuple[str, Dict[str, Any]]:
preferred = store.get(preferred_key)
if isinstance(preferred, dict) and preferred.get("sessionId") == session_id:
return preferred_key, preferred
for k, v in store.items():
if isinstance(v, dict) and v.get("sessionId") == session_id:
return k, v
return preferred_key, {}
def extract_compaction_metrics(
*, entry_before: Dict[str, Any], entry_after: Dict[str, Any]
) -> Dict[str, Any]:
before = _coerce_int(entry_before.get("compactionCount")) or 0
after = _coerce_int(entry_after.get("compactionCount")) or 0
delta = max(0, after - before)
total_tokens_fresh = entry_after.get("totalTokensFresh")
return {
"compactionCountBefore": before,
"compactionCountAfter": after,
"compactionCountDelta": delta,
"compactionTriggered": bool(delta),
"totalTokens": _coerce_int(entry_after.get("totalTokens")),
"totalTokensFresh": total_tokens_fresh if isinstance(total_tokens_fresh, bool) else None,
"inputTokens": _coerce_int(entry_after.get("inputTokens")),
"outputTokens": _coerce_int(entry_after.get("outputTokens")),
"contextTokens": _coerce_int(entry_after.get("contextTokens")),
"cacheRead": _coerce_int(entry_after.get("cacheRead")),
"cacheWrite": _coerce_int(entry_after.get("cacheWrite")),
}
def wipe_profile_local_memory(paths: StorePaths) -> Dict[str, Any]:
"""Wipe the OpenClaw profile's local memory store (best-effort).
This prevents cross-case contamination when a profile uses local persistent memory.
OpenClaw stores this under ~/.openclaw-<profile>/memory/ (e.g. main.sqlite).
"""
start = time.time()
memory_dir = paths.profile_dir / PROFILE_MEMORY_DIR
existed = memory_dir.exists()
deleted = 0
err: Optional[str] = None
if existed:
try:
for p in memory_dir.rglob("*"):
if p.is_file():
deleted += 1
shutil.rmtree(memory_dir)
except Exception as e:
err = str(e)
try:
memory_dir.mkdir(parents=True, exist_ok=True)
except Exception as e:
if err:
err = err + "; " + str(e)
else:
err = str(e)
return {
"ok": err is None,
"existed": existed,
"deletedFiles": deleted,
"error": err,
"durationMs": int((time.time() - start) * 1000),
"path": str(memory_dir),
}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument(
"--output-dir",
default="",
help="Directory containing MR-NIAH generated transcripts (expects <dir>/index.jsonl and <dir>/sessions/*.jsonl). Default: benchmark/MR-NIAH/output/",
)
ap.add_argument("--profile", default="mrniah_local")
ap.add_argument("--agent", default="main")
ap.add_argument("--limit", type=int, default=30)
ap.add_argument(
"--resume",
type=int,
default=-1,
help="Resume from sample id (inclusive) by appending to results/predictions.jsonl instead of overwriting it.",
)
group = ap.add_mutually_exclusive_group()
group.add_argument("--reset", action="store_true", help="Prefix each question with /reset")
group.add_argument(
"--new",
action="store_true",
help="Prefix each question with /new",
)
ap.add_argument(
"--compaction-summary-max-chars",
type=int,
default=20000,
help="Truncate compaction summary in predictions.jsonl (0 = no truncation).",
)
ap.add_argument(
"--openclaw-timeout",
type=int,
default=0,
help="Pass --timeout to `openclaw agent` (0 = let OpenClaw decide).",
)
ap.add_argument(
"--results-dir",
default="",
help="Write outputs into this directory (default: benchmark/MR-NIAH/results).",
)
ap.add_argument(
"--case-id",
type=int,
default=-1,
help="Run a single sample id (useful for re-running failures).",
)
ap.set_defaults(continue_on_error=True)
ap.add_argument(
"--continue-on-error",
dest="continue_on_error",
action="store_true",
help="Continue running when a case fails (default).",
)
ap.add_argument(
"--fail-fast",
dest="continue_on_error",
action="store_false",
help="Abort the batch on the first failed case.",
)
ap.add_argument(
"--wipe-local-memory",
action="store_true",
help="Wipe the OpenClaw profile's local persistent memory before each case (~/.openclaw-<profile>/memory/*).",
)
ap.add_argument(
"--import-sessions",
action="store_true",
help="If the profile has a mem9 plugin configured, import the session transcript into mem9 before each agent turn.",
)
ap.add_argument(
"--mem9-provision-per-case",
action="store_true",
help="When --import-sessions is set, provision a fresh mem9 tenant for each case (one tenant per session-id).",
)
ap.add_argument(
"--mem9-clear-memories",
action="store_true",
help="When --import-sessions is set, clear all mem9 memories before and after each case (to keep cases independent).",
)
ap.add_argument(
"--mem9-api-url",
default="",
help="mem9 API base URL (required for --import-sessions unless the profile openclaw.json has a mem9 plugin config).",
)
ap.add_argument(
"--mem9-tenant-id",
default="",
help="mem9 tenant ID (required for --import-sessions unless the profile openclaw.json has a mem9 plugin config).",
)
ap.add_argument(
"--mem9-load-method",
choices=["import-session", "line-write"],
default="line-write",
help="How to load session history into mem9 when --import-sessions is set (default: line-write).",
)
ap.add_argument(
"--mem9-line-write-sleep-ms",
type=int,
default=0,
help="Sleep N ms after each v1alpha2 /memories write when --mem9-load-method=line-write (default 0).",
)
ap.add_argument(
"--mem9-line-write-verify-timeout",
type=float,
default=20.0,
help="Seconds to wait for v1alpha2 recall to observe the written session lines (default 20).",
)
ap.add_argument(
"--mem9-line-write-verify-interval",
type=float,
default=0.5,
help="Polling interval seconds for write verification when --mem9-load-method=line-write (default 0.5).",
)
ap.add_argument(
"--gateway-port",
type=int,
default=0,
help="Gateway port for --profile (required for --mem9-provision-per-case because the gateway is restarted per case).",
)
ap.add_argument(
"--gateway-log",
default="",
help="Path to append gateway logs when --mem9-provision-per-case is enabled.",
)
ap.add_argument(
"--mem9-import-timeout",
type=int,
default=3600,
help="Timeout (seconds) for each mem9 /imports task (only when --import-sessions is set).",
)
ap.add_argument(
"--mem9-import-poll-interval",
type=float,
default=1.0,
help="Polling interval (seconds) for each mem9 /imports task.",
)
ap.add_argument(
"--mem9-trace-limit",
type=int,
default=5,
help="Max memories to print per trace section (default 5).",
)
ap.add_argument(
"--mem9-trace-chars",
type=int,
default=220,
help="Max chars per memory content preview (default 220).",
)
ap.add_argument(
"--mem9-trace-query-chars",
type=int,
default=800,
help="Max chars from question to use for recall preview query (default 800).",
)
args = ap.parse_args()
output_dir = (args.output_dir or "").strip()
if output_dir:
p = Path(output_dir).expanduser()
if not p.is_absolute():
p = (HERE / p)
output_path = p.resolve()
else:
output_path = OUTPUT
index_path = output_path / "index.jsonl"
sess_out = output_path / "sessions"
if not index_path.exists():
raise SystemExit(f"Missing {index_path}. Run mr-niah-transcript.py first (or pass --output-dir).")
results_dir = (
Path(args.results_dir).expanduser()
if (args.results_dir or "").strip()
else (HERE / "results")
)
raw_dir = results_dir / "raw"
results_dir.mkdir(parents=True, exist_ok=True)
raw_dir.mkdir(parents=True, exist_ok=True)
paths = resolve_store_paths(args.profile, args.agent)
ensure_store_initialized(paths)
mem9_cfg: Optional[tuple[str, str]] = None
if args.import_sessions:
api_url = (
(args.mem9_api_url or "").strip()
or os.environ.get("MEM9_BASE_URL", "").strip()
or os.environ.get("MEM9_API_URL", "").strip()
or os.environ.get("MNEMO_API_URL", "").strip()
)
tenant_id = (
(args.mem9_tenant_id or "").strip()
or os.environ.get("MEM9_TENANT_ID", "").strip()
or os.environ.get("MNEMO_TENANT_ID", "").strip()
)
api_url = api_url.rstrip("/") if api_url else ""
if not api_url:
raise SystemExit(
"ERROR: --import-sessions requires mem9 apiUrl.\n"
"Provide --mem9-api-url, or set MEM9_BASE_URL."
)
if args.mem9_provision_per_case:
mem9_cfg = (api_url, "")
else:
if api_url and tenant_id:
mem9_cfg = (api_url, tenant_id)
if mem9_cfg is None:
raise SystemExit(
"ERROR: --import-sessions requires a mem9 apiUrl + tenantID (unless --mem9-provision-per-case is set).\n"
"Provide --mem9-api-url/--mem9-tenant-id, or set MEM9_BASE_URL/MEM9_TENANT_ID."
)
if args.mem9_provision_per_case:
if not args.import_sessions:
raise SystemExit("ERROR: --mem9-provision-per-case requires --import-sessions")
if not args.gateway_port or args.gateway_port <= 0:
raise SystemExit("ERROR: --mem9-provision-per-case requires --gateway-port")
if not (args.gateway_log or "").strip():
raise SystemExit("ERROR: --mem9-provision-per-case requires --gateway-log")
if args.mem9_clear_memories:
raise SystemExit("ERROR: --mem9-provision-per-case and --mem9-clear-memories are mutually exclusive")
if args.case_id is not None and int(args.case_id) >= 0:
# Single-case runs should ignore --limit so any id can be rerun.
index_entries = load_index(index_path)
wanted = int(args.case_id)
index_entries = [e for e in index_entries if _coerce_int(e.get("id")) == wanted]
if not index_entries:
raise SystemExit(f"ERROR: --case-id={wanted} not found in {index_path}.")
else:
index_entries = load_index(index_path)[: args.limit]
pred_path = results_dir / "predictions.jsonl"
resume_from = int(args.resume) if args.resume is not None else -1
processed_ids: set[int] = set()
if resume_from >= 0 and args.case_id < 0:
processed_ids = load_processed_sample_ids(pred_path)
print(
f"[resume] from={resume_from} already_done={len(processed_ids)} pred_path={pred_path}",
flush=True,
)
elif args.case_id < 0:
pred_path.write_text("", encoding="utf-8")
gateway_proc: Optional[subprocess.Popen[str]] = None
gateway_log_path = Path(args.gateway_log).expanduser() if (args.gateway_log or "").strip() else None
if args.mem9_provision_per_case:
atexit.register(lambda: _stop_process(gateway_proc))
failures: List[Dict[str, Any]] = []
for entry in index_entries:
stage = "init"
sample_id_int: Optional[int] = None
session_id: Optional[str] = None
question: Optional[str] = None
answer: str = ""
raw_meta: Optional[Path] = None
try:
sample_id = entry["id"]
sample_id_int = _coerce_int(sample_id)
if sample_id_int is None:
raise RuntimeError(f"index entry missing int-like id: {entry!r}")
if args.case_id < 0:
if resume_from >= 0 and sample_id_int < resume_from:
continue
if resume_from >= 0 and sample_id_int in processed_ids:
print(f"[{sample_id_int}] skipping=already_done", flush=True)
continue
session_id = entry["session"]
question = entry["question"]
answer = entry.get("answer", "")
if not isinstance(session_id, str) or not session_id:
raise RuntimeError(f"index entry missing session: {entry!r}")
if not isinstance(question, str) or not question:
raise RuntimeError(f"index entry missing question: {entry!r}")
raw_meta = raw_dir / f"{sample_id_int}-{session_id}{META_SUFFIX}"
print(f"[{sample_id_int}] session={session_id} running=prepare", flush=True)
stage = "wipe_local_memory"
local_memory_wipe: Optional[Dict[str, Any]] = None
if args.wipe_local_memory:
# If a gateway is running from a previous case (mem9 per-case mode),
# stop it before deleting the profile's SQLite files.
_stop_process(gateway_proc)
gateway_proc = None
print(
f"[{sample_id_int}] session={session_id} running=wipe_local_memory",
flush=True,
)
local_memory_wipe = wipe_profile_local_memory(paths)
if local_memory_wipe.get("ok") is not True:
raise RuntimeError(f"wipe local memory failed: {local_memory_wipe!r}")
stage = "prepare_session"
src = sess_out / f"{session_id}.jsonl"
if not src.exists():
raise FileNotFoundError(f"Missing generated session: {src}")
dst = paths.sessions_dir / src.name
shutil.copy2(src, dst)
# Register into sessions.json under a unique bench key
bench_key = f"bench:mrniah:{sample_id_int:04d}"
try:
rewrite_session_header_cwd(
session_file=dst,
cwd=resolve_profile_workspace_dir(
profile=args.profile,
agent=args.agent,
profile_dir=paths.profile_dir,
),
)
except Exception as e:
# Best-effort: still allow the run, but the session cwd may be less faithful.
print(
f"[{sample_id_int}] session={session_id} WARNING: rewrite cwd failed: {e}",
file=sys.stderr,
flush=True,
)
bench_entry = build_session_entry(session_id=session_id, session_file=dst, bench_key=bench_key)
upsert_store_entry(paths=paths, key=bench_key, entry=bench_entry)
stage = "mem9"
mem9_import: Optional[Dict[str, Any]] = None
mem9_load_method: str = ""
mem9_line_write: Optional[Dict[str, Any]] = None
mem9_clear_pre: Optional[Dict[str, Any]] = None
mem9_clear_post: Optional[Dict[str, Any]] = None
mem9_recall_preview: Optional[Dict[str, Any]] = None
mem9_tenant_id: Optional[str] = None
if mem9_cfg is not None:
api_url, tenant_id = mem9_cfg
if args.mem9_provision_per_case:
stage = "mem9_provision"
print(f"[{sample_id_int}] session={session_id} running=mem9_provision", flush=True)
mem9_tenant_id = mem9_provision_tenant(api_url=api_url)
print(
f"[mem9] provisioned tenant={mem9_tenant_id} session={session_id}",
flush=True,
)
# Update OpenClaw profile config so the gateway uses this tenant for this case.
try:
# The OpenClaw mem9 plugin treats apiKey as the primary v1alpha2 credential.
# Keep tenantID in sync for backward compatibility / debugging, but always
# set apiKey so the plugin does not keep using a stale placeholder.
ensure_mem9_conversation_access(args.profile)
for key in (
"plugins.entries.mem9.config.apiKey",
"plugins.entries.mem9.config.tenantID",
):
subprocess.run(
[
"openclaw",
"--profile",
args.profile,
"config",
"set",
key,
mem9_tenant_id,
],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
except subprocess.CalledProcessError as e:
msg = (e.stderr or e.stdout or "").strip()
raise RuntimeError(f"openclaw config set mem9 profile config failed: {msg}") from e
# Restart gateway per case to ensure it picks up the new tenant config.
_stop_process(gateway_proc)
gateway_proc = None
assert gateway_log_path is not None
gateway_proc = _start_gateway(profile=args.profile, log_path=gateway_log_path)
_wait_gateway_healthy(port=int(args.gateway_port), timeout_s=60)
tenant_id = mem9_tenant_id
if args.mem9_clear_memories:
stage = "mem9_clear_pre"
print(f"[{sample_id_int}] session={session_id} running=mem9_clear_pre", flush=True)
mem9_clear_pre = mem9_clear_memories(
api_url=api_url,
tenant_id=tenant_id,
agent_id=args.agent,
)
print(
f"[mem9] clear(pre) deleted={mem9_clear_pre.get('deleted')} remaining={mem9_clear_pre.get('remaining')} verified={mem9_clear_pre.get('verified')}",
flush=True,
)
if mem9_clear_pre.get("verified") is not True:
raise RuntimeError(f"mem9 clear(pre) did not verify empty: {mem9_clear_pre!r}")
mem9_load_method = (args.mem9_load_method or "").strip() or "line-write"
if mem9_load_method == "import-session":
stage = "mem9_import"
import_path = raw_dir / f"{sample_id_int}-{session_id}.import.session.jsonl"
shutil.copy2(dst, import_path)
print(f"[{sample_id_int}] session={session_id} running=mem9_import", flush=True)
mem9_import = mem9_import_session(
api_url=api_url,
tenant_id=tenant_id,
agent_id=args.agent,
session_id=session_id,
import_file=import_path,
timeout_s=int(args.mem9_import_timeout),
poll_interval_s=float(args.mem9_import_poll_interval),
)
if mem9_import.get("status") != "done" or mem9_import.get("verified") is not True:
raise RuntimeError(f"mem9 import did not complete successfully: {mem9_import!r}")
elif mem9_load_method == "line-write":
stage = "mem9_line_write"
start_s = time.time()
extracted = extract_openclaw_session_messages(dst)
total_lines = len(extracted)
posted = 0
failed = 0
first_errors: List[Dict[str, Any]] = []
sleep_ms = max(0, int(args.mem9_line_write_sleep_ms))
print(
f"[{sample_id_int}] session={session_id} running=mem9_line_write lines={total_lines}",
flush=True,
)
for rec in extracted:
role = rec.get("role")
content = rec.get("content")
line_no = rec.get("line")
if not isinstance(role, str) or not isinstance(content, str):
continue
try:
mem9v2_create_messages(
api_url=api_url,
api_key=tenant_id,
agent_id=args.agent,
session_id=session_id,
messages=[{"role": role, "content": content}],
)
posted += 1
except Exception as e:
failed += 1
if len(first_errors) < 5:
first_errors.append(
{
"line": line_no,
"role": role,
"error": str(e),
}
)
if sleep_ms > 0:
time.sleep(sleep_ms / 1000.0)
mem9_line_write = {
"linesExtracted": total_lines,
"posted": posted,
"failed": failed,
"sleepMs": sleep_ms,
"durationMs": int((time.time() - start_s) * 1000),
"firstErrors": first_errors if first_errors else None,
}
# Best-effort verification: poll until session-scoped recall sees at least one hit.
stage = "mem9_line_write_verify"
verify_start_s = time.time()
attempts = 0
ok = False
preview_query = truncate_text(question, int(args.mem9_trace_query_chars))
timeout_s = max(0.0, float(args.mem9_line_write_verify_timeout))
interval_s = max(0.05, float(args.mem9_line_write_verify_interval))
last_summary: Optional[Dict[str, Any]] = None
while (time.time() - verify_start_s) < timeout_s:
attempts += 1
try:
page = mem9v2_search_memories(
api_url=api_url,
api_key=tenant_id,
agent_id=args.agent,
query=preview_query,
limit=1,
memory_type="session",
session_id=session_id,
)
last_summary = summarize_memories_page(page, int(args.mem9_trace_chars))
if int(last_summary.get("count") or 0) > 0:
ok = True
break
except Exception:
last_summary = None
time.sleep(interval_s)
mem9_line_write["verify"] = {
"ok": ok,
"attempts": attempts,
"durationMs": int((time.time() - verify_start_s) * 1000),
"summary": last_summary,
}
else:
raise RuntimeError(f"unsupported mem9 load method: {mem9_load_method!r}")
# Snapshot "writes" (best-effort): list memories after load (insights/pinned/session search behavior depends on q).
try:
memories_page = mem9v2_search_memories(
api_url=api_url,
api_key=tenant_id,
agent_id=args.agent,
query="",
limit=200,
)
summary = summarize_memories_page(memories_page, int(args.mem9_trace_chars))
if mem9_import is not None:
mem9_import["memoriesAfterImport"] = summary
if mem9_line_write is not None:
mem9_line_write["memoriesAfterWrite"] = summary
print(
f"[mem9] memories after load count={summary.get('count')} total={summary.get('total')}",
flush=True,
)
limit = max(0, int(args.mem9_trace_limit))
chars = max(0, int(args.mem9_trace_chars))
sample = summary.get("sample")
if isinstance(sample, list) and sample and limit > 0:
print(f"[mem9] wrote(after load) sample={min(limit, len(sample))}", flush=True)
for rec in sample[:limit]:
if not isinstance(rec, dict):
continue
cid = rec.get("id")
ctype = rec.get("memory_type")
score = rec.get("score")
content_preview = preview_text(rec.get("content_preview"), chars) or ""
print(
f"[mem9] wrote id={cid} type={ctype} score={score} content={content_preview}",
flush=True,
)
except Exception as e:
print(f"[mem9] WARNING: list memories after load failed: {e}", flush=True)
if mem9_import is not None:
mem9_import["memoriesAfterImportError"] = str(e)
if mem9_line_write is not None:
mem9_line_write["memoriesAfterWriteError"] = str(e)
# Recall preview (v1alpha2): what a typical prompt query would retrieve.
try:
preview_query = truncate_text(question, int(args.mem9_trace_query_chars))
limit = max(1, int(args.mem9_trace_limit))
recall_page = mem9v2_search_memories(
api_url=api_url,
api_key=tenant_id,
agent_id=args.agent,
query=preview_query,
limit=limit,
)
mem9_recall_preview = {
"query": preview_query,
"queryTruncated": bool(len(preview_query) != len(question)),
"page": summarize_memories_page(recall_page, int(args.mem9_trace_chars)),
}
page_summary = mem9_recall_preview["page"]
print(
f"[mem9] recall(pre) q_len={len(preview_query)} count={page_summary.get('count')} total={page_summary.get('total')}",
flush=True,
)
sample = page_summary.get("sample")
chars = max(0, int(args.mem9_trace_chars))
if isinstance(sample, list) and sample and limit > 0:
for rec in sample[:limit]:
if not isinstance(rec, dict):
continue
cid = rec.get("id")
ctype = rec.get("memory_type")
score = rec.get("score")
content_preview = preview_text(rec.get("content_preview"), chars) or ""
print(
f"[mem9] recall id={cid} type={ctype} score={score} content={content_preview}",
flush=True,
)
except Exception as e:
print(f"[mem9] WARNING: recall preview failed: {e}", flush=True)
mem9_recall_preview = {
"query": truncate_text(question, int(args.mem9_trace_query_chars)),
"queryTruncated": True,
"error": str(e),
}
stage = "openclaw"
sent_message = f"/reset {question}" if args.reset else f"/new {question}" if args.new else question
cmd = [
"openclaw",
"--profile",
args.profile,
"agent",
]
maybe_add_agent_arg(cmd, args.agent)
cmd.extend(["--session-id", session_id, "--message", sent_message, "--json"])
if args.openclaw_timeout and args.openclaw_timeout > 0:
cmd.extend(["--timeout", str(int(args.openclaw_timeout))])
print(f"[{sample_id_int}] session={session_id} running=openclaw", flush=True)
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
raw_out = raw_dir / f"{sample_id_int}-{session_id}.stdout.json"
raw_err = raw_dir / f"{sample_id_int}-{session_id}.stderr.txt"
raw_out.write_text(proc.stdout, encoding="utf-8")
raw_err.write_text(proc.stderr, encoding="utf-8")
parsed_obj: Optional[Any] = None
if proc.returncode == 0:
parsed_obj = parse_json_stdout(proc.stdout)
if parsed_obj is None:
parsed_obj = parse_json_stdout(proc.stderr)
store_after = load_store(paths)
effective_session_id = extract_effective_session_id(parsed_obj) if parsed_obj is not None else None
if not effective_session_id:
effective_session_id = session_id
resolved_key, entry_after = find_store_entry(
store=store_after, session_id=effective_session_id, preferred_key=bench_key
)
compaction = extract_compaction_metrics(entry_before=bench_entry, entry_after=entry_after)
effective_session_file = dst
session_file_raw = entry_after.get("sessionFile") if isinstance(entry_after, dict) else None
if isinstance(session_file_raw, str) and session_file_raw.strip():
effective_session_file = Path(session_file_raw).expanduser()
compaction_event: Optional[Dict[str, Any]] = None
if compaction["compactionCountDelta"] > 0:
compaction_event = extract_last_compaction_event(effective_session_file)
compaction_occurred = isinstance(compaction_event, dict)
compaction["compactionTriggered"] = bool(compaction_occurred) or bool(compaction["compactionCountDelta"])
event_first_kept = (
coerce_str(compaction_event.get("firstKeptEntryId"))
if isinstance(compaction_event, dict)
else None
)
event_summary = compaction_event.get("summary") if isinstance(compaction_event, dict) else None
if not isinstance(event_summary, str):
event_summary = None
summary_truncated = False
if event_summary is not None:
event_summary, summary_truncated = maybe_truncate(
event_summary, int(args.compaction_summary_max_chars)
)
write_json(
raw_meta,
{
"id": sample_id_int,
"session": session_id,
"sessionEffective": effective_session_id,
"sessionEffectiveChanged": bool(effective_session_id and effective_session_id != session_id),
"sessionFileEffective": str(effective_session_file),
"profile": args.profile,
"agent": args.agent,
"returncode": proc.returncode,
"runId": extract_run_id(parsed_obj) if parsed_obj is not None else None,
"storeKey": resolved_key,
"storePath": str(paths.store_path),
"mem9TenantId": mem9_tenant_id,
"mem9LoadMethod": mem9_load_method or None,
"mem9Import": mem9_import,
"mem9LineWrite": mem9_line_write,
"mem9RecallPreview": mem9_recall_preview,
"mem9Clear": {
"pre": mem9_clear_pre,
"post": mem9_clear_post,
}
if mem9_cfg is not None and args.mem9_clear_memories
else None,
"localMemoryWipe": local_memory_wipe,
"compaction": compaction,
"compactionEvent": {
"occurred": compaction_occurred,
"firstKeptEntryId": event_first_kept,
"summaryChars": len(event_summary) if event_summary is not None else None,
"summaryTruncated": summary_truncated if event_summary is not None else None,
"tokensBefore": compaction_event.get("tokensBefore")
if isinstance(compaction_event, dict)
else None,
},
},
)
prediction = ""
ok = proc.returncode == 0
error: Optional[str] = None
error_stage: Optional[str] = None
if proc.returncode == 0:
if parsed_obj is not None:
prediction = safe_extract_text(parsed_obj)
else:
error_stage = "openclaw"
error = f"openclaw agent exited with code {proc.returncode}"
append_jsonl(
pred_path,
{
"id": sample_id_int,
"session": session_id,
"sessionEffective": effective_session_id,
"sessionEffectiveChanged": bool(effective_session_id and effective_session_id != session_id),
"question": question,
"message": sent_message,
"reset": bool(args.reset),
"new": bool(args.new),
"prediction": prediction,
"answer": answer,
"profile": args.profile,
"agent": args.agent,
"ok": ok,
"error": error,
"errorStage": error_stage,
"stdoutPath": str(raw_out),
"stderrPath": str(raw_err),
"metaPath": str(raw_meta),
"mem9TenantId": mem9_tenant_id,
"mem9LoadMethod": mem9_load_method or None,
"mem9ImportTaskId": mem9_import.get("taskId") if isinstance(mem9_import, dict) else None,
"mem9ImportStatus": mem9_import.get("status") if isinstance(mem9_import, dict) else None,
"mem9ImportVerified": mem9_import.get("verified") if isinstance(mem9_import, dict) else None,
"mem9ImportTotalChunks": mem9_import.get("totalChunks") if isinstance(mem9_import, dict) else None,
"mem9ImportDoneChunks": mem9_import.get("doneChunks") if isinstance(mem9_import, dict) else None,
"mem9LineWritePosted": mem9_line_write.get("posted") if isinstance(mem9_line_write, dict) else None,
"mem9LineWriteFailed": mem9_line_write.get("failed") if isinstance(mem9_line_write, dict) else None,
"compactionTriggered": compaction["compactionTriggered"],
"compactionCountDelta": compaction["compactionCountDelta"],
"compactionCountAfter": compaction["compactionCountAfter"],
"totalTokens": compaction["totalTokens"],
"totalTokensFresh": compaction["totalTokensFresh"],
"firstKeptEntryId": event_first_kept,
"compactionSummary": event_summary,
"compactionSummaryTruncated": summary_truncated if event_summary is not None else None,
},
)
comp = "yes" if compaction["compactionTriggered"] else "no"
status = "ok" if ok else "failed"
print(
f"[{sample_id_int}] session={session_id} status={status} pred_len={len(prediction)} compaction={comp}",
flush=True,
)
# Post-clear is best-effort: do not let it abort the batch.
if mem9_cfg is not None and args.mem9_clear_memories:
try:
api_url, tenant_id = mem9_cfg
stage = "mem9_clear_post"
print(f"[{sample_id_int}] session={session_id} running=mem9_clear_post", flush=True)
mem9_clear_post = mem9_clear_memories(
api_url=api_url,
tenant_id=tenant_id,
agent_id=args.agent,
)
print(
f"[mem9] clear(post) deleted={mem9_clear_post.get('deleted')} remaining={mem9_clear_post.get('remaining')} verified={mem9_clear_post.get('verified')}",
flush=True,
)
if mem9_clear_post.get("verified") is not True:
print(
f"[mem9] WARNING: clear(post) did not verify empty (will rely on next pre-clear): {mem9_clear_post!r}",
flush=True,
)
try:
meta_obj = json.loads(raw_meta.read_text(encoding="utf-8"))
if isinstance(meta_obj, dict) and isinstance(meta_obj.get("mem9Clear"), dict):
meta_obj["mem9Clear"]["post"] = mem9_clear_post
raw_meta.write_text(
json.dumps(meta_obj, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
except Exception:
pass
except Exception as e:
print(f"[mem9] WARNING: clear(post) failed: {e}", flush=True)
if not ok:
failures.append({"id": sample_id_int, "session": session_id, "stage": error_stage, "error": error})
except Exception as e:
if sample_id_int is not None and session_id:
if raw_meta is None:
raw_meta = raw_dir / f"{sample_id_int}-{session_id}{META_SUFFIX}"
try:
write_json(
raw_meta,
{
"id": sample_id_int,
"session": session_id,
"profile": args.profile,
"agent": args.agent,
"errorStage": stage,
"error": str(e),
},
)
except Exception:
pass
try:
append_jsonl(
pred_path,
{
"id": sample_id_int,
"session": session_id,
"question": question,
"message": None,
"reset": bool(args.reset),
"new": bool(args.new),
"prediction": "",
"answer": answer,
"profile": args.profile,
"agent": args.agent,
"ok": False,
"error": str(e),
"errorStage": stage,
"metaPath": str(raw_meta),
},
)
except Exception:
pass
failures.append({"id": sample_id_int, "session": session_id, "stage": stage, "error": str(e)})
print(f"[{sample_id_int}] session={session_id} status=failed stage={stage} err={e}", flush=True)
if not args.continue_on_error:
raise
if failures:
ids = sorted({f["id"] for f in failures if isinstance(f.get("id"), int)})
preview = ids[:30]
print(
f"[summary] failed_cases={len(ids)} ids={preview}{'...' if len(ids) > len(preview) else ''}",
flush=True,
)
print(f"Wrote predictions -> {pred_path}", flush=True)
print(f"Raw outputs -> {raw_dir}", flush=True)
print(f"Store -> {paths.store_path}", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
================================================
FILE: benchmark/MR-NIAH/run_mem_compare.sh
================================================
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
MRNIAH_DIR="$ROOT/benchmark/MR-NIAH"
OUTPUT_DIR="$MRNIAH_DIR/output"
INDEX_FILE="$OUTPUT_DIR/index.jsonl"
# Defaults (prefer CLI flags over environment variables for reproducibility).
BASE_PROFILE="mrniah_local"
MEM_PROFILE="mrniah_mem"
AGENT_NAME="main"
SAMPLE_LIMIT="300"
MEM9_BASE_URL="https://api.mem9.ai"
MEM9_SPACE_ID=""
BASE_CMDS=(openclaw python3 jq curl tee tar)
# Profile config overrides (optional).
MODEL_PRIMARY=""
MODEL_CONTEXT_WINDOW=0
COMPACT_SPEC=""
# OpenClaw plugin wiring settings for the mem profile.
OPENCLAW_PLUGIN_DIR="$ROOT/openclaw-plugin"
# NOTE: We primarily wire the plugin via:
# - plugins.load.paths = ["$OPENCLAW_PLUGIN_DIR"]
# - plugins.allow = ["mem9"]
#
# This flag is kept for backward compatibility / metadata only.
OPENCLAW_PLUGIN_INSTALL_MODE="link" # copy|link (legacy)
# Managed profiles mode (optional): recreate baseline from a template dir, then clone mem profile.
PROFILES_TEMPLATE_DIR=""
PROFILES_ENV_FILE=""
RECREATE_PROFILES_MODE="auto" # auto|1|0
RECREATE_PROFILES=0
RUN_TAG=""
BASE_PROFILE_EXPLICIT=0
MEM_PROFILE_EXPLICIT=0
# --reset / --new flags passed through to run_batch.py (mutually exclusive).
RESET_MODE=0
NEW_MODE=0
# Optional: run only a single OpenClaw profile (skip the compare).
RUN_ONLY_PROFILE=""
# Compare existing results without running OpenClaw.
COMPARE_ONLY=0
# Resume mode (single profile only): resume from a sample id without deleting partial results.
RESUME_FROM=""
RUN_ONLY_CASE=""
CONTINUE_ON_ERROR=1
# Pass-through OpenClaw agent timeout (seconds) to avoid runaway runs.
# 0 = let OpenClaw decide (may be profile-config dependent).
OPENCLAW_TIMEOUT="0"
# Isolation toggles.
CLEAN_SESSIONS="1"
WIPE_AGENT_SESSIONS="1"
WIPE_LOCAL_MEMORY="1"
# Speed vs stability:
# - 0: run baseline then mem sequentially (more stable; lower API pressure).
# - 1 (default): run baseline and mem in parallel (faster; higher API/QPS pressure).
PARALLEL_RUNS="1"
# run_batch.py prints mem9 debug info (writes + recall preview) during the mem run.
MEM9_TRACE_LIMIT="5"
MEM9_TRACE_CHARS="220"
MEM9_TRACE_QUERY_CHARS="800"
# mem9 isolation strategy for the mem-enabled profile:
# - "clear": reuse one tenant and clear memories pre/post each case
# - "tenant": provision a fresh tenant per case (strong isolation; recommended)
MEM9_ISOLATION="tenant"
# How to load session history into mem9 for the mem profile:
# - import-session: v1alpha1 /imports (file_type=session) with task polling
# - line-write: v1alpha2 /memories, write each JSONL message line sequentially
MEM9_LOAD_METHOD="line-write"
MEM9_LINE_WRITE_SLEEP_MS="0"
MEM9_LINE_WRITE_VERIFY_TIMEOUT="20"
MEM9_LINE_WRITE_VERIFY_INTERVAL="0.5"
MEM9_IMPORT_TIMEOUT="3600"
MEM9_IMPORT_POLL_INTERVAL="1.0"
# If set to 1, the mem profile will be regenerated from the base profile before running.
RESET_MEM_PROFILE="0"
# Gateways (required; --local mode does not support /reset or /new properly).
BASE_GATEWAY_PORT_PREFERRED="19011"
MEM_GATEWAY_PORT_PREFERRED="19012"
GATEWAY_TOKEN="mrniah-bench-token"
GATEWAY_TOKEN_EXPLICIT=0
BASE_GATEWAY_PORT=""
MEM_GATEWAY_PORT=""
BASE_GATEWAY_PID=""
MEM_GATEWAY_PID=""
LOG_DIR="$MRNIAH_DIR/results-logs"
LOG_FILE=""
RUN_ID=""
SESSION_DUMP_ROOT=""
ARCHIVE_PATH=""
log() {
echo "[$(date '+%H:%M:%S')] $*" >&2
}
openclaw_supports_conversation_access() {
local version
version="$(openclaw --version 2>/dev/null | head -n 1 || true)"
python3 - "$version" <<'PY'
import re
import sys
match = re.search(r"(\d+)\.(\d+)(?:\.(\d+))?", sys.argv[1])
if not match:
raise SystemExit(1)
major = int(match.group(1))
minor = int(match.group(2))
patch = int(match.group(3) or 0)
if major >= 2026:
raise SystemExit(0 if (major, minor, patch) >= (2026, 4, 22) else 1)
raise SystemExit(0 if (major, minor, patch) >= (4, 23, 0) else 1)
PY
}
usage() {
cat >&2 <<EOF
Usage: $(basename "$0") [options]
Notes:
- --reset/--new are mutually exclusive and, when enabled, prefix each question
with "/reset " or "/new " during run_batch.py.
- --profile runs only that OpenClaw profile (skips baseline-vs-mem comparison).
- --case <id> runs a single sample id (single-profile only; appends into results-\$profile).
- By default, continues on per-case failure and records it. Use --fail-fast to stop immediately.
- --compare skips runs and compares existing results-* directories for BASE_PROFILE/MEM_PROFILE.
- --resume <id> resumes a single-profile run from sample id (requires --profile; keeps benchmark/MR-NIAH/results-<profile>).
- --model <provider/model> sets agents.defaults.model.primary for both profiles.
- --compact <preset|path.json> applies a compaction preset for both profiles (agents.defaults.contextTokens + agents.defaults.compaction).
- --model-context-window <n> (best-effort) updates the selected model's models.providers.*.models[].contextWindow in openclaw.json for both profiles.
- Full compare runs default to managed profiles (equivalent to --recreate-profiles) to avoid accidental reuse of existing profiles.
- In managed profiles mode, if you do not pass --base-profile/--mem-profile, the script appends _<yyyymmddhhmmss> to both profile names.
- By default, uses hosted mem9 at https://api.mem9.ai (override via --mem9-base-url).
- This script starts two OpenClaw gateways (baseline + mem) on separate ports.
Options:
--profile <name> Run only one profile (no compare)
--base-profile <name> Baseline OpenClaw profile name (default: ${BASE_PROFILE})
--mem-profile <name> Mem OpenClaw profile name (default: ${MEM_PROFILE})
--agent <name> OpenClaw agent id (default: ${AGENT_NAME})
--limit <n> Sample limit (default: ${SAMPLE_LIMIT})
--output-dir <dir> Transcript output dir (default: ${OUTPUT_DIR})
--compare Compare existing results without running
--mem9-base-url <url> mem9 API base URL (default: ${MEM9_BASE_URL})
--reset [true|false] Prefix each question with /reset
--new [true|false] Prefix each question with /new
--case <id> Run a single sample id (single-profile only)
--resume <id>
gitextract_odup76ae/
├── .agents/
│ └── plugins/
│ └── marketplace.json
├── .claude-plugin/
│ └── marketplace.json
├── .github/
│ └── workflows/
│ ├── deploy-dev.yml
│ ├── server-plugin-checks.yml
│ └── sync-claude-plugin.yml
├── .gitignore
├── AGENTS.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── benchmark/
│ ├── BASELINE.md
│ ├── MR-NIAH/
│ │ ├── AGENTS.md
│ │ ├── README.md
│ │ ├── USAGE.md
│ │ ├── fetch_data.py
│ │ ├── mr-niah-transcript.py
│ │ ├── run_batch.py
│ │ ├── run_mem_compare.sh
│ │ └── score.py
│ ├── README.md
│ ├── locomo/
│ │ ├── README.md
│ │ ├── USAGE.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── cli.ts
│ │ │ ├── evaluation.ts
│ │ │ ├── ingest.ts
│ │ │ ├── llm.ts
│ │ │ ├── mem9.ts
│ │ │ ├── retrieve.ts
│ │ │ ├── stats.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── prompts/
│ │ └── example.yaml
│ ├── results/
│ │ └── .gitkeep
│ ├── scripts/
│ │ ├── benchmark.sh
│ │ ├── drive-session.py
│ │ └── report.py
│ └── workspace/
│ ├── IDENTITY.md
│ ├── SOUL.md
│ └── USER.md
├── claude-plugin/
│ ├── .claude-plugin/
│ │ └── plugin.json
│ ├── AGENTS.md
│ ├── README.md
│ ├── hooks/
│ │ ├── common.sh
│ │ ├── hooks.json
│ │ ├── lib/
│ │ │ ├── hook-json.mjs
│ │ │ ├── memories-formatter.mjs
│ │ │ └── transcript-parser.mjs
│ │ ├── pre-compact.sh
│ │ ├── session-end.sh
│ │ ├── session-start.sh
│ │ ├── stop.sh
│ │ └── user-prompt-submit.sh
│ ├── skills/
│ │ ├── recall/
│ │ │ └── SKILL.md
│ │ ├── setup/
│ │ │ └── SKILL.md
│ │ └── store/
│ │ └── SKILL.md
│ └── tsconfig.json
├── cli/
│ ├── AGENTS.md
│ ├── README.md
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── codex-plugin/
│ ├── .codex-plugin/
│ │ └── plugin.json
│ ├── .gitignore
│ ├── AGENTS.md
│ ├── README.md
│ ├── bootstrap-hooks/
│ │ ├── session-start.mjs
│ │ ├── shared/
│ │ │ └── bootstrap.mjs
│ │ ├── stop.mjs
│ │ └── user-prompt-submit.mjs
│ ├── hooks/
│ │ ├── session-start.mjs
│ │ ├── shared/
│ │ │ ├── debug.mjs
│ │ │ ├── format.mjs
│ │ │ └── transcript.mjs
│ │ ├── stop.mjs
│ │ └── user-prompt-submit.mjs
│ ├── lib/
│ │ ├── config.mjs
│ │ ├── http.mjs
│ │ ├── project-root.mjs
│ │ ├── skill-runtime.mjs
│ │ └── update-check.mjs
│ ├── package.json
│ ├── skills/
│ │ ├── cleanup/
│ │ │ ├── SKILL.md
│ │ │ └── scripts/
│ │ │ └── cleanup.mjs
│ │ ├── recall/
│ │ │ ├── SKILL.md
│ │ │ ├── agents/
│ │ │ │ └── openai.yaml
│ │ │ └── scripts/
│ │ │ └── recall.mjs
│ │ ├── setup/
│ │ │ ├── SKILL.md
│ │ │ └── scripts/
│ │ │ └── setup.mjs
│ │ └── store/
│ │ ├── SKILL.md
│ │ ├── agents/
│ │ │ └── openai.yaml
│ │ └── scripts/
│ │ └── store.mjs
│ ├── templates/
│ │ └── hooks.json
│ ├── tests/
│ │ ├── bootstrap-hooks.test.mjs
│ │ ├── cleanup.test.mjs
│ │ ├── debug.test.mjs
│ │ ├── plugin-files.test.mjs
│ │ ├── recall.test.mjs
│ │ ├── runtime-config.test.mjs
│ │ ├── session-start.test.mjs
│ │ ├── setup.test.mjs
│ │ ├── smoke.test.mjs
│ │ ├── stop.test.mjs
│ │ ├── store.test.mjs
│ │ ├── test-temp.mjs
│ │ ├── update-check.test.mjs
│ │ └── user-prompt-submit.test.mjs
│ └── tsconfig.json
├── dashboard/
│ ├── README.md
│ ├── app/
│ │ ├── .gitignore
│ │ ├── AGENTS.md
│ │ ├── README.md
│ │ ├── components.json
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── public/
│ │ │ └── _redirects
│ │ ├── scripts/
│ │ │ └── sentry-sourcemaps.mjs
│ │ ├── src/
│ │ │ ├── api/
│ │ │ │ ├── analysis-cache.ts
│ │ │ │ ├── analysis-client.test.ts
│ │ │ │ ├── analysis-client.ts
│ │ │ │ ├── analysis-helpers.test.ts
│ │ │ │ ├── analysis-helpers.ts
│ │ │ │ ├── analysis-matcher.test.ts
│ │ │ │ ├── analysis-matcher.ts
│ │ │ │ ├── analysis-queries.test.ts
│ │ │ │ ├── analysis-queries.ts
│ │ │ │ ├── client.ts
│ │ │ │ ├── deep-analysis-queries.test.tsx
│ │ │ │ ├── deep-analysis-queries.ts
│ │ │ │ ├── local-cache.ts
│ │ │ │ ├── mock-data.ts
│ │ │ │ ├── provider-http.test.ts
│ │ │ │ ├── provider-http.ts
│ │ │ │ ├── provider-mock.test.ts
│ │ │ │ ├── provider-mock.ts
│ │ │ │ ├── provider.ts
│ │ │ │ ├── queries.test.ts
│ │ │ │ ├── queries.ts
│ │ │ │ ├── source-memories.test.ts
│ │ │ │ └── source-memories.ts
│ │ │ ├── assets/
│ │ │ │ ├── ark-pixel-font-10px-monospaced-otf-v2026.02.27/
│ │ │ │ │ ├── OFL.txt
│ │ │ │ │ ├── ark-pixel-10px-monospaced-ja.otf
│ │ │ │ │ ├── ark-pixel-10px-monospaced-ko.otf
│ │ │ │ │ ├── ark-pixel-10px-monospaced-latin.otf
│ │ │ │ │ ├── ark-pixel-10px-monospaced-zh_cn.otf
│ │ │ │ │ ├── ark-pixel-10px-monospaced-zh_hk.otf
│ │ │ │ │ ├── ark-pixel-10px-monospaced-zh_tr.otf
│ │ │ │ │ └── ark-pixel-10px-monospaced-zh_tw.otf
│ │ │ │ └── audio/
│ │ │ │ └── bgm10-full-loop.opus
│ │ │ ├── components/
│ │ │ │ ├── lang-toggle.tsx
│ │ │ │ ├── pixel-farm/
│ │ │ │ │ ├── actor-preview-panel.tsx
│ │ │ │ │ ├── feedback-dialog.test.tsx
│ │ │ │ │ ├── feedback-dialog.tsx
│ │ │ │ │ ├── front-target-panel.tsx
│ │ │ │ │ ├── phaser-stage.tsx
│ │ │ │ │ ├── pointer-coordinates-panel.tsx
│ │ │ │ │ ├── world-state-panel.test.tsx
│ │ │ │ │ └── world-state-panel.tsx
│ │ │ │ ├── space/
│ │ │ │ │ ├── add-dialog.tsx
│ │ │ │ │ ├── analysis-panel.test.tsx
│ │ │ │ │ ├── analysis-panel.tsx
│ │ │ │ │ ├── deep-analysis-overlay.tsx
│ │ │ │ │ ├── deep-analysis-tab.test.tsx
│ │ │ │ │ ├── deep-analysis-tab.tsx
│ │ │ │ │ ├── delete-dialog.tsx
│ │ │ │ │ ├── detail-panel.tsx
│ │ │ │ │ ├── edit-dialog.tsx
│ │ │ │ │ ├── empty-state.tsx
│ │ │ │ │ ├── export-dialog.tsx
│ │ │ │ │ ├── import-dialog.tsx
│ │ │ │ │ ├── import-status.tsx
│ │ │ │ │ ├── memory-card.test.tsx
│ │ │ │ │ ├── memory-card.tsx
│ │ │ │ │ ├── memory-composition-chart.test.tsx
│ │ │ │ │ ├── memory-composition-chart.tsx
│ │ │ │ │ ├── memory-farm-preparation-dialog.tsx
│ │ │ │ │ ├── memory-farm-promo-card.test.tsx
│ │ │ │ │ ├── memory-farm-promo-card.tsx
│ │ │ │ │ ├── memory-insight-layout.test.ts
│ │ │ │ │ ├── memory-insight-layout.ts
│ │ │ │ │ ├── memory-insight-overview.test.tsx
│ │ │ │ │ ├── memory-insight-overview.tsx
│ │ │ │ │ ├── memory-insight-relations.tsx
│ │ │ │ │ ├── memory-insight-workspace.test.tsx
│ │ │ │ │ ├── memory-insight-workspace.tsx
│ │ │ │ │ ├── memory-overview-tabs.test.tsx
│ │ │ │ │ ├── memory-overview-tabs.tsx
│ │ │ │ │ ├── memory-pulse-overview.test.tsx
│ │ │ │ │ ├── memory-pulse-overview.tsx
│ │ │ │ │ ├── memory-rhythm-chart.tsx
│ │ │ │ │ ├── memory-signal-stack.tsx
│ │ │ │ │ ├── mobile-analysis-sheet.tsx
│ │ │ │ │ ├── mobile-detail-sheet.test.tsx
│ │ │ │ │ ├── mobile-detail-sheet.tsx
│ │ │ │ │ ├── mobile-panel-shell.tsx
│ │ │ │ │ ├── session-preview.tsx
│ │ │ │ │ ├── space-page-layout.tsx
│ │ │ │ │ ├── space-selectors.test.ts
│ │ │ │ │ ├── space-selectors.ts
│ │ │ │ │ ├── space-tools.tsx
│ │ │ │ │ ├── space-view-utils.ts
│ │ │ │ │ ├── tag-strip.test.tsx
│ │ │ │ │ ├── tag-strip.tsx
│ │ │ │ │ ├── time-range.tsx
│ │ │ │ │ ├── topic-strip.tsx
│ │ │ │ │ ├── use-memory-farm-entry-state.test.ts
│ │ │ │ │ ├── use-memory-farm-entry-state.ts
│ │ │ │ │ ├── use-space-data-model.test.tsx
│ │ │ │ │ ├── use-space-data-model.ts
│ │ │ │ │ └── use-space-route-state.ts
│ │ │ │ ├── theme-toggle.tsx
│ │ │ │ └── ui/
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── button-group.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── dialog.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ ├── input.tsx
│ │ │ │ ├── progress.tsx
│ │ │ │ ├── select.tsx
│ │ │ │ ├── switch.tsx
│ │ │ │ └── tabs.tsx
│ │ │ ├── config/
│ │ │ │ └── features.ts
│ │ │ ├── i18n/
│ │ │ │ ├── index.ts
│ │ │ │ └── locales/
│ │ │ │ ├── en.json
│ │ │ │ └── zh-CN.json
│ │ │ ├── index.css
│ │ │ ├── lib/
│ │ │ │ ├── connect-bootstrap-init.ts
│ │ │ │ ├── connect-bootstrap.test.ts
│ │ │ │ ├── connect-bootstrap.ts
│ │ │ │ ├── ga4.ts
│ │ │ │ ├── memory-derived-signals.test.ts
│ │ │ │ ├── memory-derived-signals.ts
│ │ │ │ ├── memory-filters.test.ts
│ │ │ │ ├── memory-filters.ts
│ │ │ │ ├── memory-insight-background.test.ts
│ │ │ │ ├── memory-insight-background.ts
│ │ │ │ ├── memory-insight-background.worker.ts
│ │ │ │ ├── memory-insight-entities.ts
│ │ │ │ ├── memory-insight-relations.test.ts
│ │ │ │ ├── memory-insight-relations.ts
│ │ │ │ ├── memory-insight.test.ts
│ │ │ │ ├── memory-insight.ts
│ │ │ │ ├── memory-pulse.test.ts
│ │ │ │ ├── memory-pulse.ts
│ │ │ │ ├── mixpanel-auto-click.ts
│ │ │ │ ├── mixpanel.ts
│ │ │ │ ├── pixel-farm/
│ │ │ │ │ ├── baby-cow.ts
│ │ │ │ │ ├── character.ts
│ │ │ │ │ ├── chicken.ts
│ │ │ │ │ ├── collision-layer.ts
│ │ │ │ │ ├── cow.ts
│ │ │ │ │ ├── create-game.ts
│ │ │ │ │ ├── data/
│ │ │ │ │ │ ├── memory-store.ts
│ │ │ │ │ │ ├── memory-to-world.test.ts
│ │ │ │ │ │ ├── memory-to-world.ts
│ │ │ │ │ │ ├── source.ts
│ │ │ │ │ │ ├── types.ts
│ │ │ │ │ │ └── use-pixel-farm-world.ts
│ │ │ │ │ ├── depth.ts
│ │ │ │ │ ├── dialog-interaction.test.ts
│ │ │ │ │ ├── dialog-interaction.ts
│ │ │ │ │ ├── dialog-state.test.ts
│ │ │ │ │ ├── dialog-state.ts
│ │ │ │ │ ├── field-layout.test.ts
│ │ │ │ │ ├── field-layout.ts
│ │ │ │ │ ├── generated-mask-data.ts
│ │ │ │ │ ├── generated-mask-source.ts
│ │ │ │ │ ├── island-mask.ts
│ │ │ │ │ ├── npc-dialog-content.test.ts
│ │ │ │ │ ├── npc-dialog-content.ts
│ │ │ │ │ ├── npc-tips.test.ts
│ │ │ │ │ ├── npc-tips.ts
│ │ │ │ │ ├── palette.ts
│ │ │ │ │ ├── plant-dialog-content.test.ts
│ │ │ │ │ ├── plant-dialog-content.ts
│ │ │ │ │ ├── plant-placement.test.ts
│ │ │ │ │ ├── plant-placement.ts
│ │ │ │ │ ├── runtime-assets.ts
│ │ │ │ │ ├── tileset-config.ts
│ │ │ │ │ ├── ui-dialog-layout.test.ts
│ │ │ │ │ ├── ui-dialog-layout.ts
│ │ │ │ │ ├── ui-dialog-pagination.test.ts
│ │ │ │ │ ├── ui-dialog-pagination.ts
│ │ │ │ │ ├── ui-dialog.ts
│ │ │ │ │ ├── ui-scene.ts
│ │ │ │ │ ├── use-pixel-farm-npc-dialog-content.test.tsx
│ │ │ │ │ ├── use-pixel-farm-npc-dialog-content.ts
│ │ │ │ │ └── world-render.ts
│ │ │ │ ├── session.test.ts
│ │ │ │ ├── session.ts
│ │ │ │ ├── tag-signals.test.ts
│ │ │ │ ├── tag-signals.ts
│ │ │ │ ├── theme.ts
│ │ │ │ ├── time.ts
│ │ │ │ └── utils.ts
│ │ │ ├── main.tsx
│ │ │ ├── pages/
│ │ │ │ ├── connect-loader.ts
│ │ │ │ ├── connect.test.tsx
│ │ │ │ ├── connect.tsx
│ │ │ │ ├── pixel-farm-editor.tsx
│ │ │ │ ├── pixel-farm.test.tsx
│ │ │ │ ├── pixel-farm.tsx
│ │ │ │ ├── space.test.tsx
│ │ │ │ └── space.tsx
│ │ │ ├── router.tsx
│ │ │ ├── test/
│ │ │ │ └── setup.ts
│ │ │ ├── types/
│ │ │ │ ├── analysis.ts
│ │ │ │ ├── import.ts
│ │ │ │ ├── memory.ts
│ │ │ │ └── time-range.ts
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.app.json
│ │ ├── tsconfig.json
│ │ ├── tsconfig.node.json
│ │ ├── tsconfig.test.json
│ │ └── vite.config.ts
│ └── docs/
│ ├── dashboard-mvp-spec.md
│ ├── data-contract.md
│ ├── dev-tasks.md
│ ├── information-architecture.md
│ ├── memory-card-session-preview-demo-plan.md
│ └── ui-first-mock-plan.md
├── docs/
│ ├── BENCHMARK.md
│ ├── DESIGN.md
│ ├── api/
│ │ └── openapi.json
│ ├── design/
│ │ ├── auto-increase-spend-limit.md
│ │ ├── crdt-memory-proposal.md
│ │ ├── crdt-vector-clock-logic.md
│ │ ├── fts-hybrid-search-proposal.md
│ │ ├── issue-110-session-messages-api-proposal.md
│ │ ├── issue-115-reconcile-tags-proposal.md
│ │ ├── issue-149-recall-improvements-proposal.md
│ │ ├── issue-294-active-tenants-metric-proposal.md
│ │ ├── issue-305-active-memory-metrics-proposal.md
│ │ ├── issue-311-space-chain-e2e-proposal.md
│ │ ├── mem9-runtime-usage-client-proposal.md
│ │ ├── middleware-cluster-blacklist-proposal.md
│ │ ├── multi-database-backend-architecture-proposal.md
│ │ ├── multi-tenant-provisioning-proposal.md
│ │ ├── raw-session-storage-proposal.md
│ │ ├── smart-memory-pipeline-proposal.md
│ │ ├── space-chain-console-plan.md
│ │ ├── space-chain-prd.md
│ │ ├── tidbcloud-pool-api-migration-design.md
│ │ ├── time-aware-recall-proposal.md
│ │ └── utm-skill-onboarding-proposal.md
│ ├── harness/
│ │ ├── benchmark_answering_temporal_assist_accepted_20260412T184441.md
│ │ ├── context_selection_and_answer_canonicalization_20260422T194809.md
│ │ ├── enumeration_adjacent_turn_success_20260425T2230.md
│ │ └── failure_server_recall_migration_20260423T000347.md
│ └── superpowers/
│ └── specs/
│ ├── 2026-03-26-pixel-farm-interaction-performance-design.md
│ └── 2026-03-27-pixel-farm-ui-scene-dialog-design.md
├── e2e/
│ ├── AGENTS.md
│ ├── README.md
│ ├── api-smoke-test-existing-tenant.sh
│ ├── api-smoke-test-round2-v1alpha2.sh
│ ├── api-smoke-test-round2.sh
│ ├── api-smoke-test-sessions.sh
│ ├── api-smoke-test-space-chain.sh
│ ├── api-smoke-test-utm.sh
│ ├── api-smoke-test-v1alpha2.sh
│ ├── api-smoke-test.sh
│ ├── concurrent-real-doc-test.py
│ ├── crdt-e2e-tests.sh
│ ├── crdt-server-merge-e2e.py
│ └── plugin-crdt-e2e.py
├── openclaw-plugin/
│ ├── .gitignore
│ ├── AGENTS.md
│ ├── README.md
│ ├── backend.ts
│ ├── hooks.ts
│ ├── index.test.ts
│ ├── index.ts
│ ├── openclaw.plugin.json
│ ├── package.json
│ ├── publish.sh
│ ├── server-backend.test.ts
│ ├── server-backend.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ ├── tsconfig.test.json
│ └── types.ts
├── opencode-plugin/
│ ├── .gitignore
│ ├── AGENTS.md
│ ├── README.md
│ ├── package.json
│ ├── scripts/
│ │ ├── publish.mjs
│ │ ├── publish.test.mjs
│ │ └── test.mjs
│ ├── src/
│ │ ├── index.ts
│ │ ├── server/
│ │ │ ├── backend.ts
│ │ │ ├── config.ts
│ │ │ ├── debug.ts
│ │ │ ├── hooks.ts
│ │ │ ├── index.ts
│ │ │ ├── ingest/
│ │ │ │ ├── select.ts
│ │ │ │ └── submit.ts
│ │ │ ├── recall/
│ │ │ │ ├── format.ts
│ │ │ │ └── query.ts
│ │ │ ├── server-backend.ts
│ │ │ ├── session-transcript.ts
│ │ │ ├── setup-flow.ts
│ │ │ └── tools.ts
│ │ ├── shared/
│ │ │ ├── credentials-store.ts
│ │ │ ├── defaults.ts
│ │ │ ├── platform-paths.ts
│ │ │ ├── plugin-meta.ts
│ │ │ ├── setup-files.ts
│ │ │ └── types.ts
│ │ └── tui/
│ │ └── index.ts
│ ├── tests/
│ │ ├── config.test.ts
│ │ ├── credentials-store.test.ts
│ │ ├── ingest.test.ts
│ │ ├── recall.test.ts
│ │ ├── server-backend.test.ts
│ │ ├── session-transcript.test.ts
│ │ ├── setup-files.test.ts
│ │ ├── source-imports.test.ts
│ │ └── tui-setup.test.ts
│ ├── tsconfig.json
│ └── tsconfig.test.json
├── server/
│ ├── AGENTS.md
│ ├── Dockerfile
│ ├── cmd/
│ │ └── mnemo-server/
│ │ ├── main.go
│ │ └── main_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── internal/
│ │ ├── config/
│ │ │ ├── config.go
│ │ │ └── config_test.go
│ │ ├── domain/
│ │ │ ├── errors.go
│ │ │ ├── tokengen.go
│ │ │ ├── types.go
│ │ │ └── upload.go
│ │ ├── embed/
│ │ │ └── embedder.go
│ │ ├── encrypt/
│ │ │ ├── encryptor.go
│ │ │ ├── factory.go
│ │ │ ├── factory_test.go
│ │ │ ├── kms.go
│ │ │ ├── md5.go
│ │ │ ├── md5_test.go
│ │ │ ├── plain.go
│ │ │ └── plain_test.go
│ │ ├── handler/
│ │ │ ├── AGENTS.md
│ │ │ ├── chain_runtime.go
│ │ │ ├── handler.go
│ │ │ ├── memory.go
│ │ │ ├── memory_batch_delete_test.go
│ │ │ ├── memory_test.go
│ │ │ ├── metering.go
│ │ │ ├── recall.go
│ │ │ ├── recall_test.go
│ │ │ ├── runtime_usage.go
│ │ │ ├── space_chain.go
│ │ │ ├── task.go
│ │ │ ├── tenant.go
│ │ │ ├── tenant_test.go
│ │ │ └── version_test.go
│ │ ├── llm/
│ │ │ ├── client.go
│ │ │ └── client_test.go
│ │ ├── metering/
│ │ │ ├── AGENTS.md
│ │ │ ├── config.go
│ │ │ ├── console_writer.go
│ │ │ ├── gzip.go
│ │ │ ├── gzip_test.go
│ │ │ ├── path.go
│ │ │ ├── path_test.go
│ │ │ ├── s3_client.go
│ │ │ ├── s3_writer.go
│ │ │ ├── transport_writer.go
│ │ │ ├── webhook_writer.go
│ │ │ ├── writer.go
│ │ │ └── writer_test.go
│ │ ├── metrics/
│ │ │ ├── metrics.go
│ │ │ └── metrics_test.go
│ │ ├── middleware/
│ │ │ ├── auth.go
│ │ │ ├── auth_test.go
│ │ │ ├── cooldown.go
│ │ │ ├── ratelimit.go
│ │ │ └── ratelimit_test.go
│ │ ├── repository/
│ │ │ ├── db9/
│ │ │ │ ├── db9.go
│ │ │ │ ├── memory.go
│ │ │ │ ├── space_chain.go
│ │ │ │ ├── tenant.go
│ │ │ │ ├── upload_task.go
│ │ │ │ └── upload_task_test.go
│ │ │ ├── factory.go
│ │ │ ├── postgres/
│ │ │ │ ├── memory.go
│ │ │ │ ├── postgres.go
│ │ │ │ ├── space_chain.go
│ │ │ │ ├── tenant.go
│ │ │ │ └── upload_task.go
│ │ │ ├── repository.go
│ │ │ └── tidb/
│ │ │ ├── AGENTS.md
│ │ │ ├── fts_test.go
│ │ │ ├── memory.go
│ │ │ ├── memory_integration_test.go
│ │ │ ├── sessions.go
│ │ │ ├── sessions_test.go
│ │ │ ├── space_chain.go
│ │ │ ├── tenant.go
│ │ │ ├── tenant_integration_test.go
│ │ │ ├── testutil_test.go
│ │ │ ├── tidb.go
│ │ │ ├── upload_task.go
│ │ │ └── utm.go
│ │ ├── reqid/
│ │ │ └── reqid.go
│ │ ├── runtimeusage/
│ │ │ ├── client.go
│ │ │ ├── client_test.go
│ │ │ ├── manager.go
│ │ │ ├── manager_test.go
│ │ │ ├── outbox.go
│ │ │ ├── outbox_test.go
│ │ │ ├── types.go
│ │ │ ├── worker.go
│ │ │ └── worker_test.go
│ │ ├── service/
│ │ │ ├── AGENTS.md
│ │ │ ├── activity.go
│ │ │ ├── activity_test.go
│ │ │ ├── ingest.go
│ │ │ ├── ingest_test.go
│ │ │ ├── memory.go
│ │ │ ├── memory_bulk_delete_test.go
│ │ │ ├── memory_test.go
│ │ │ ├── recall.go
│ │ │ ├── search_source_turns.go
│ │ │ ├── search_source_turns_test.go
│ │ │ ├── session.go
│ │ │ ├── session_test.go
│ │ │ ├── source_provenance.go
│ │ │ ├── space_chain.go
│ │ │ ├── space_chain_test.go
│ │ │ ├── temporal_fact.go
│ │ │ ├── tenant.go
│ │ │ ├── tenant_test.go
│ │ │ ├── upload.go
│ │ │ └── upload_test.go
│ │ └── tenant/
│ │ ├── AGENTS.md
│ │ ├── pool.go
│ │ ├── pool_test.go
│ │ ├── provisioner.go
│ │ ├── provisioner_test.go
│ │ ├── schema.go
│ │ ├── schema_compat.go
│ │ ├── schema_compat_test.go
│ │ ├── starter.go
│ │ ├── starter_test.go
│ │ ├── util.go
│ │ ├── zero.go
│ │ └── zero_test.go
│ ├── schema.sql
│ ├── schema_db9.sql
│ └── schema_pg.sql
├── site/
│ ├── AGENTS.md
│ ├── astro.config.mjs
│ ├── netlify.toml
│ ├── package.json
│ ├── public/
│ │ ├── SETUP.md
│ │ ├── SKILL.md
│ │ ├── TROUBLESHOOTING.md
│ │ ├── UNINSTALL.md
│ │ └── _meta.json
│ ├── scripts/
│ │ └── netlify-build.sh
│ ├── src/
│ │ ├── components/
│ │ │ ├── Agents.astro
│ │ │ ├── AnimatedLogo.astro
│ │ │ ├── ApiReference.astro
│ │ │ ├── Benchmark.astro
│ │ │ ├── BridgeBanner.astro
│ │ │ ├── ContactModal.astro
│ │ │ ├── DocsPage.astro
│ │ │ ├── FAQ.astro
│ │ │ ├── FeatureCard.astro
│ │ │ ├── Features.astro
│ │ │ ├── Footer.astro
│ │ │ ├── Hero.astro
│ │ │ ├── Navbar.astro
│ │ │ ├── Pricing.astro
│ │ │ └── SecuritySection.astro
│ │ ├── content/
│ │ │ ├── docs.ts
│ │ │ └── site.ts
│ │ ├── layouts/
│ │ │ └── Layout.astro
│ │ ├── pages/
│ │ │ ├── api.astro
│ │ │ ├── docs.astro
│ │ │ ├── index.astro
│ │ │ ├── openclaw-memory.astro
│ │ │ └── pricing.astro
│ │ ├── scripts/
│ │ │ └── site-ui.ts
│ │ └── styles/
│ │ └── global.css
│ └── tsconfig.json
├── skills/
│ └── mnemos-setup/
│ └── SKILL.md
└── test-results/
└── .last-run.json
Showing preview only (474K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (4896 symbols across 366 files)
FILE: benchmark/MR-NIAH/fetch_data.py
function manifest_paths (line 67) | def manifest_paths(langs: Sequence[str]) -> List[str]:
function normalize_dataset_path (line 78) | def normalize_dataset_path(path: str) -> str:
function dedupe (line 90) | def dedupe(paths: Iterable[str]) -> List[str]:
function collect_paths (line 100) | def collect_paths(langs: Sequence[str], extra: Sequence[str], allowed_to...
function apply_include_filter (line 110) | def apply_include_filter(paths: Iterable[str], include: Sequence[str]) -...
function relative_dest (line 120) | def relative_dest(path: str) -> Path:
function display_path (line 127) | def display_path(dest_root: Path, target: Path) -> str:
function token_from_path (line 134) | def token_from_path(path: str) -> str:
function filter_by_tokens (line 138) | def filter_by_tokens(paths: Iterable[str], allowed: set[str] | None) -> ...
function parse_tokens (line 144) | def parse_tokens(values: Sequence[str]) -> set[str] | None:
function github_url (line 162) | def github_url(path: str, revision: str) -> str:
function sha256_for_file (line 167) | def sha256_for_file(path: Path) -> str:
function download_file (line 175) | def download_file(url: str, dest: Path, force: bool, label: str) -> bool:
function list_manifest (line 198) | def list_manifest() -> None:
function invalid_jsonl_lines (line 203) | def invalid_jsonl_lines(path: Path) -> List[int]:
function sanitize_jsonl_file (line 220) | def sanitize_jsonl_file(path: Path) -> List[int]:
function parse_args (line 250) | def parse_args() -> argparse.Namespace:
function main (line 286) | def main() -> int:
FILE: benchmark/MR-NIAH/mr-niah-transcript.py
function canonical_tokens (line 84) | def canonical_tokens(selection: set[str] | None) -> List[str]:
function parse_tokens (line 90) | def parse_tokens(values: Sequence[str]) -> set[str] | None:
function parse_langs (line 108) | def parse_langs(choice: str) -> List[str]:
function normalize_dataset_relative (line 118) | def normalize_dataset_relative(value: str) -> Path:
function describe_path (line 125) | def describe_path(path: Path) -> str:
function dedupe_paths (line 132) | def dedupe_paths(paths: Iterable[Path]) -> List[Path]:
function resolve_input_path (line 142) | def resolve_input_path(value: str) -> Path:
function build_auto_inputs (line 154) | def build_auto_inputs(langs: Sequence[str], tokens: Sequence[str]) -> Tu...
function gather_inputs (line 171) | def gather_inputs(positionals: Sequence[str], extras: Sequence[str], lan...
function _build_token_counter (line 197) | def _build_token_counter():
function make_usage_snapshot (line 239) | def make_usage_snapshot(*, input_tokens: int, output_tokens: int) -> Dic...
function isoformat_utc (line 257) | def isoformat_utc(dt: _dt.datetime) -> str:
function utc_now_iso (line 261) | def utc_now_iso() -> str:
function short_hex (line 265) | def short_hex(counter: int, seed: str) -> str:
function make_session_header (line 271) | def make_session_header(session_id: str, ts: str) -> Dict[str, Any]:
function make_message_entry (line 275) | def make_message_entry(
function read_transcript (line 332) | def read_transcript(path: Path) -> List[Dict[str, Any]]:
function clean_output (line 346) | def clean_output() -> None:
function iter_samples (line 353) | def iter_samples(path: Path) -> Iterable[Tuple[int, Dict[str, Any]]]:
function normalize_turn (line 420) | def normalize_turn(m: Dict[str, Any]) -> Optional[Tuple[str, str]]:
function split_history_question (line 440) | def split_history_question(messages: List[Dict[str, Any]]) -> Tuple[List...
function validate_transcript (line 465) | def validate_transcript(lines: List[Dict[str, Any]]) -> None:
function main (line 559) | def main() -> int:
FILE: benchmark/MR-NIAH/run_batch.py
function now_ms (line 60) | def now_ms() -> int:
function _parse_openclaw_version_text (line 64) | def _parse_openclaw_version_text(text: str) -> Optional[tuple[int, int, ...
function openclaw_supports_conversation_access (line 75) | def openclaw_supports_conversation_access() -> bool:
function ensure_mem9_conversation_access (line 100) | def ensure_mem9_conversation_access(profile: str) -> None:
function extract_last_compaction_event (line 130) | def extract_last_compaction_event(session_file: Path) -> Optional[Dict[s...
function coerce_str (line 179) | def coerce_str(value: Any) -> Optional[str]:
function preview_text (line 186) | def preview_text(value: Any, max_chars: int) -> Optional[str]:
function truncate_text (line 199) | def truncate_text(value: str, max_chars: int) -> str:
function maybe_truncate (line 207) | def maybe_truncate(text: str, max_chars: int) -> tuple[str, bool]:
function compaction_event_key (line 215) | def compaction_event_key(event: Dict[str, Any]) -> tuple[Optional[str], ...
function maybe_add_agent_arg (line 220) | def maybe_add_agent_arg(cmd: List[str], agent: str) -> None:
function load_index (line 225) | def load_index(path: Path) -> List[Dict[str, Any]]:
function read_json (line 230) | def read_json(path: Path) -> Dict[str, Any]:
function write_json (line 234) | def write_json(path: Path, obj: Any) -> None:
function append_jsonl (line 237) | def append_jsonl(path: Path, obj: Any) -> None:
function safe_extract_text (line 243) | def safe_extract_text(payload_obj: Any) -> str:
function strip_ansi (line 303) | def strip_ansi(text: str) -> str:
function parse_json_stdout (line 307) | def parse_json_stdout(stdout: str) -> Optional[Any]:
function find_first_str_by_key (line 347) | def find_first_str_by_key(obj: Any, keys: set[str]) -> Optional[str]:
function extract_effective_session_id (line 366) | def extract_effective_session_id(payload_obj: Any) -> Optional[str]:
function extract_run_id (line 379) | def extract_run_id(payload_obj: Any) -> Optional[str]:
function build_multipart_form (line 383) | def build_multipart_form(
function http_json (line 410) | def http_json(
function mem9_import_session (line 473) | def mem9_import_session(
function mem9_list_memories (line 595) | def mem9_list_memories(
function mem9_search_memories (line 618) | def mem9_search_memories(
function mem9v2_create_messages (line 649) | def mem9v2_create_messages(
function mem9v2_search_memories (line 682) | def mem9v2_search_memories(
function mem9_clear_memories (line 721) | def mem9_clear_memories(
function mem9_provision_tenant (line 829) | def mem9_provision_tenant(*, api_url: str) -> str:
function _http_probe_ok (line 846) | def _http_probe_ok(*, url: str, timeout_s: int = 5) -> bool:
function _wait_gateway_healthy (line 856) | def _wait_gateway_healthy(*, port: int, timeout_s: int = 60) -> None:
function _start_gateway (line 866) | def _start_gateway(*, profile: str, log_path: Path) -> subprocess.Popen[...
function _stop_process (line 884) | def _stop_process(proc: Optional[subprocess.Popen[str]]) -> None:
function _summarize_memories_page (line 902) | def _summarize_memories_page(page: Dict[str, Any]) -> Dict[str, Any]:
function summarize_memories_page (line 933) | def summarize_memories_page(page: Dict[str, Any], content_preview_chars:...
class StorePaths (line 947) | class StorePaths:
function resolve_store_paths (line 955) | def resolve_store_paths(profile: str, agent: str) -> StorePaths:
function resolve_default_openclaw_workspace_dir (line 967) | def resolve_default_openclaw_workspace_dir(profile: str) -> Path:
function resolve_profile_workspace_dir (line 979) | def resolve_profile_workspace_dir(*, profile: str, agent: str, profile_d...
function rewrite_session_header_cwd (line 1019) | def rewrite_session_header_cwd(*, session_file: Path, cwd: Path) -> None:
function _extract_text_blocks (line 1042) | def _extract_text_blocks(value: Any) -> str:
function extract_openclaw_session_messages (line 1067) | def extract_openclaw_session_messages(session_file: Path) -> List[Dict[s...
function ensure_store_initialized (line 1109) | def ensure_store_initialized(paths: StorePaths) -> None:
function load_store (line 1116) | def load_store(paths: StorePaths) -> Dict[str, Any]:
function pick_template_entry (line 1122) | def pick_template_entry(store: Dict[str, Any]) -> Dict[str, Any]:
function _coerce_int (line 1136) | def _coerce_int(value: Any) -> Optional[int]:
function load_processed_sample_ids (line 1154) | def load_processed_sample_ids(pred_path: Path) -> set[int]:
function build_session_entry (line 1192) | def build_session_entry(
function upsert_store_entry (line 1209) | def upsert_store_entry(*, paths: StorePaths, key: str, entry: Dict[str, ...
function find_store_entry (line 1215) | def find_store_entry(
function extract_compaction_metrics (line 1227) | def extract_compaction_metrics(
function wipe_profile_local_memory (line 1248) | def wipe_profile_local_memory(paths: StorePaths) -> Dict[str, Any]:
function main (line 1286) | def main() -> int:
FILE: benchmark/MR-NIAH/score.py
function detect_language (line 23) | def detect_language(answer: str) -> str:
function modify_gt (line 30) | def modify_gt(gt):
function score_response (line 77) | def score_response(response: str, gt_label: str, language: str) -> float:
function load_predictions (line 87) | def load_predictions(path: Path) -> List[Dict[str, Any]]:
function _coerce_bool (line 135) | def _coerce_bool(value: Any) -> Optional[bool]:
function _coerce_int (line 149) | def _coerce_int(value: Any) -> Optional[int]:
function resolve_compaction_tag (line 167) | def resolve_compaction_tag(rec: Dict[str, Any]) -> Tuple[Optional[bool],...
function summarize_group (line 181) | def summarize_group(rows: Sequence[Dict[str, Any]]) -> Dict[str, Any]:
function _strip_ansi (line 204) | def _strip_ansi(text: str) -> str:
function _parse_json_from_mixed_stdout (line 208) | def _parse_json_from_mixed_stdout(stdout: str) -> Optional[Any]:
function _safe_read_json (line 256) | def _safe_read_json(path: Path) -> Optional[Any]:
function _extract_openclaw_meta (line 270) | def _extract_openclaw_meta(stdout_obj: Any) -> Optional[Dict[str, Any]]:
function _classify_failure (line 282) | def _classify_failure(rec: Dict[str, Any]) -> Tuple[Optional[str], Dict[...
function main (line 337) | def main() -> int:
FILE: benchmark/locomo/src/cli.ts
type Args (line 20) | interface Args {
FILE: benchmark/locomo/src/evaluation.ts
constant ARTICLES (line 3) | const ARTICLES = new Set(['a', 'an', 'and', 'the'])
FILE: benchmark/locomo/src/ingest.ts
type OrderedSession (line 9) | interface OrderedSession { dateLabel: string | null, turns: DialogTurn[]...
FILE: benchmark/locomo/src/llm.ts
constant SYSTEM_PROMPT (line 5) | const SYSTEM_PROMPT = 'You are a helpful assistant answering questions a...
type ChatMessage (line 23) | interface ChatMessage { role: 'system' | 'user' | 'assistant', content: ...
FILE: benchmark/locomo/src/stats.ts
constant CATEGORIES (line 4) | const CATEGORIES: QACategory[] = [1, 2, 3, 4, 5]
constant CATEGORY_NAMES (line 5) | const CATEGORY_NAMES: Record<QACategory, string> = {
FILE: benchmark/locomo/src/types.ts
type BenchmarkOutput (line 1) | interface BenchmarkOutput {
type BenchmarkStats (line 13) | interface BenchmarkStats {
type DialogTurn (line 22) | interface DialogTurn {
type LoCoMoSample (line 32) | interface LoCoMoSample {
type QACategory (line 38) | type QACategory = 1 | 2 | 3 | 4 | 5
type QAPair (line 40) | interface QAPair {
type QAResult (line 48) | interface QAResult {
type MnemoMemory (line 60) | interface MnemoMemory {
FILE: benchmark/scripts/drive-session.py
function send_prompt (line 26) | def send_prompt(profile: str, message: str, timeout: int, session_id: st...
function parse_response (line 77) | def parse_response(raw: dict) -> str:
function write_transcript (line 122) | def write_transcript(scenario: dict, turns: list, results_dir: str):
function write_results_json (line 168) | def write_results_json(scenario: dict, turns: list, results_dir: str):
function main (line 182) | def main():
FILE: benchmark/scripts/report.py
function classify_turn (line 18) | def classify_turn(prompt):
function render_markdown (line 35) | def render_markdown(text):
function generate_html (line 78) | def generate_html(data):
function _render_response (line 453) | def _render_response(resp):
function _preview (line 466) | def _preview(resp):
function _status_indicator (line 483) | def _status_indicator(resp):
function main (line 492) | def main():
FILE: claude-plugin/hooks/lib/hook-json.mjs
function parseJsonObject (line 12) | function parseJsonObject(raw) {
function readStdinText (line 27) | function readStdinText() {
function readStdinJson (line 34) | function readStdinJson() {
function getString (line 43) | function getString(input, key) {
function makeAdditionalContextOutput (line 53) | function makeAdditionalContextOutput(eventName, additionalContext) {
function printJson (line 66) | function printJson(value) {
function main (line 74) | function main(argv) {
FILE: claude-plugin/hooks/lib/memories-formatter.mjs
function escapeForPrompt (line 22) | function escapeForPrompt(text) {
function formatMemoryLine (line 35) | function formatMemoryLine(memory, index, maxContentLength) {
function formatMemoriesBlock (line 56) | function formatMemoriesBlock(memories, options = {}) {
function parseMemories (line 90) | function parseMemories(raw) {
function main (line 108) | function main() {
FILE: claude-plugin/hooks/lib/transcript-parser.mjs
constant START_TAG (line 8) | const START_TAG = "<relevant-memories>";
constant END_TAG (line 9) | const END_TAG = "</relevant-memories>";
function stripInjectedMemories (line 30) | function stripInjectedMemories(text) {
function blockText (line 48) | function blockText(block) {
function entryRole (line 67) | function entryRole(entry) {
function entryContent (line 93) | function entryContent(entry) {
constant ASSISTANT_NOISE_PREFIXES (line 117) | const ASSISTANT_NOISE_PREFIXES = [
function isSystemNoise (line 131) | function isSystemNoise(role, content) {
function normalizeEntry (line 144) | function normalizeEntry(entry) {
function parseTranscriptText (line 174) | function parseTranscriptText(raw) {
function selectLastTurn (line 201) | function selectLastTurn(messages) {
function applyMessageCap (line 226) | function applyMessageCap(messages, maxMessages) {
function applyByteBudget (line 238) | function applyByteBudget(messages, maxBytes) {
function selectWindow (line 267) | function selectWindow(messages, options) {
function parseTranscriptFile (line 291) | function parseTranscriptFile(filePathOrUrl, options) {
function parseArgs (line 300) | function parseArgs(argv) {
function main (line 331) | function main(argv) {
FILE: cli/main.go
type ProvisionResponse (line 32) | type ProvisionResponse struct
type Memory (line 36) | type Memory struct
type ListResponse (line 53) | type ListResponse struct
type BootstrapResponse (line 60) | type BootstrapResponse struct
type BulkResponse (line 65) | type BulkResponse struct
type IngestResponse (line 70) | type IngestResponse struct
type TaskResponse (line 78) | type TaskResponse struct
type TaskDetail (line 83) | type TaskDetail struct
type TaskListResponse (line 92) | type TaskListResponse struct
type TenantInfo (line 97) | type TenantInfo struct
type ErrorResponse (line 102) | type ErrorResponse struct
type Client (line 108) | type Client struct
method tenantPath (line 126) | func (c *Client) tenantPath(path string) string {
method doRequest (line 164) | func (c *Client) doRequest(method, path string, body interface{}) ([]b...
method doMultipart (line 226) | func (c *Client) doMultipart(path string, fields map[string]string, fi...
method Provision (line 304) | func (c *Client) Provision() (*ProvisionResponse, error) {
method CreateMemory (line 319) | func (c *Client) CreateMemory(content string, tags []string, metadata ...
method SearchMemories (line 344) | func (c *Client) SearchMemories(query, tags, source, state, memoryType...
method GetMemory (line 393) | func (c *Client) GetMemory(id string) (*Memory, error) {
method UpdateMemory (line 408) | func (c *Client) UpdateMemory(id, content string, tags []string, metad...
method DeleteMemory (line 434) | func (c *Client) DeleteMemory(id string) error {
method BulkCreate (line 445) | func (c *Client) BulkCreate(memories []map[string]interface{}) (*BulkR...
method Ingest (line 463) | func (c *Client) Ingest(messages []map[string]string, sessionID, agent...
method Bootstrap (line 490) | func (c *Client) Bootstrap(limit int) (*BootstrapResponse, error) {
method GetTenantInfo (line 509) | func (c *Client) GetTenantInfo() (*TenantInfo, error) {
method CreateTask (line 515) | func (c *Client) CreateTask(filePath, agentIDOverride, sessionID, file...
method ListTasks (line 546) | func (c *Client) ListTasks() (*TaskListResponse, error) {
method GetTask (line 561) | func (c *Client) GetTask(id string) (*TaskDetail, error) {
function NewClient (line 116) | func NewClient(baseURL, tenantID, agentID string, timeout time.Duration,...
function buildCurlCommand (line 130) | func buildCurlCommand(method, url string, headers http.Header, body inte...
function parseError (line 576) | func parseError(body []byte, status int) error {
function printJSON (line 586) | func printJSON(v interface{}) {
function main (line 593) | func main() {
function getEnvOrDefault (line 618) | func getEnvOrDefault(key, defaultVal string) string {
function getClient (line 625) | func getClient() *Client {
function requireTenantID (line 629) | func requireTenantID() error {
function provisionCmd (line 638) | func provisionCmd() *cobra.Command {
function memoryCmd (line 657) | func memoryCmd() *cobra.Command {
function memoryCreateCmd (line 675) | func memoryCreateCmd() *cobra.Command {
function memorySearchCmd (line 711) | func memorySearchCmd() *cobra.Command {
function memoryGetCmd (line 746) | func memoryGetCmd() *cobra.Command {
function memoryUpdateCmd (line 767) | func memoryUpdateCmd() *cobra.Command {
function memoryDeleteCmd (line 805) | func memoryDeleteCmd() *cobra.Command {
function memoryBulkCmd (line 825) | func memoryBulkCmd() *cobra.Command {
function memoryIngestCmd (line 863) | func memoryIngestCmd() *cobra.Command {
function memoryBootstrapCmd (line 913) | func memoryBootstrapCmd() *cobra.Command {
function taskCmd (line 941) | func taskCmd() *cobra.Command {
function taskCreateCmd (line 954) | func taskCreateCmd() *cobra.Command {
function taskListCmd (line 994) | func taskListCmd() *cobra.Command {
function taskGetCmd (line 1014) | func taskGetCmd() *cobra.Command {
function tenantCmd (line 1037) | func tenantCmd() *cobra.Command {
function tenantInfoCmd (line 1048) | func tenantInfoCmd() *cobra.Command {
FILE: codex-plugin/bootstrap-hooks/shared/bootstrap.mjs
constant DEFAULT_PLUGIN_VERSION (line 14) | const DEFAULT_PLUGIN_VERSION = "local";
constant PLUGINS_CACHE_DIR (line 15) | const PLUGINS_CACHE_DIR = path.join("plugins", "cache");
constant DEFAULT_INSTALL_METADATA (line 16) | const DEFAULT_INSTALL_METADATA = {
function isRecord (line 25) | function isRecord(value) {
function normalizeString (line 33) | function normalizeString(value) {
function replacePathToken (line 43) | function replacePathToken(text, from, to) {
function sanitizeDebugText (line 59) | function sanitizeDebugText(value, context = {}) {
function hookAdditionalContext (line 82) | function hookAdditionalContext(eventName, text) {
function resolveCodexHome (line 97) | function resolveCodexHome(inputCodexHome, env = process.env, homeDir = o...
function debugEnabled (line 110) | function debugEnabled(env = process.env) {
function resolveDebugLogFile (line 122) | function resolveDebugLogFile(input) {
function scriptHookName (line 135) | function scriptHookName(scriptName) {
function appendShimDebugError (line 161) | function appendShimDebugError(input) {
function readJsonFile (line 207) | function readJsonFile(filePath) {
function isValidPluginVersionSegment (line 223) | function isValidPluginVersionSegment(pluginVersion) {
function readInstallMetadata (line 239) | function readInstallMetadata(input = {}) {
function resolveInstallMetadataForShim (line 271) | function resolveInstallMetadataForShim(input = {}) {
function buildPluginMissingSessionStartOutput (line 290) | function buildPluginMissingSessionStartOutput() {
function handleMissingPluginHook (line 301) | function handleMissingPluginHook(scriptName) {
function resolveActivePluginVersion (line 323) | function resolveActivePluginVersion(input) {
function resolveActivePluginRoot (line 360) | function resolveActivePluginRoot(input) {
function runHookShim (line 389) | async function runHookShim(scriptName, input = {}) {
FILE: codex-plugin/hooks/session-start.mjs
function buildSessionStartMessage (line 31) | function buildSessionStartMessage(
function appendUpgradeNotice (line 103) | function appendUpgradeNotice(message, upgradeNotice) {
function runSessionStart (line 122) | async function runSessionStart(input = {}) {
function readStdinText (line 136) | function readStdinText() {
function main (line 140) | async function main() {
FILE: codex-plugin/hooks/shared/debug.mjs
function normalizeString (line 21) | function normalizeString(value) {
function replacePathToken (line 31) | function replacePathToken(text, from, to) {
function sanitizeDebugText (line 49) | function sanitizeDebugText(value, context = {}) {
function debugEnabled (line 70) | function debugEnabled(env = process.env) {
function resolveDebugLogFile (line 82) | function resolveDebugLogFile(input = {}) {
function appendDebugLog (line 112) | function appendDebugLog(input) {
function appendDebugError (line 178) | function appendDebugError(input) {
FILE: codex-plugin/hooks/shared/format.mjs
constant START_TAG (line 3) | const START_TAG = "<relevant-memories>";
constant END_TAG (line 4) | const END_TAG = "</relevant-memories>";
function escapeForPrompt (line 18) | function escapeForPrompt(text) {
function stripInjectedMemories (line 29) | function stripInjectedMemories(text) {
function memoryContent (line 47) | function memoryContent(memory) {
function formatMemoriesBlock (line 59) | function formatMemoriesBlock(memories) {
function hookAdditionalContext (line 92) | function hookAdditionalContext(eventName, text) {
FILE: codex-plugin/hooks/shared/transcript.mjs
function blockText (line 16) | function blockText(block) {
function normalizeVisibleMessage (line 41) | function normalizeVisibleMessage(role, content) {
function appendIfDistinct (line 58) | function appendIfDistinct(messages, message) {
function extractEventMessage (line 79) | function extractEventMessage(lineValue) {
function extractResponseCandidates (line 110) | function extractResponseCandidates(lineValue) {
function normalizeTranscriptItem (line 138) | function normalizeTranscriptItem(candidate) {
function parseTranscriptText (line 161) | function parseTranscriptText(raw) {
function selectStopWindow (line 233) | function selectStopWindow(
FILE: codex-plugin/hooks/stop.mjs
constant STOP_MAX_MESSAGES (line 11) | const STOP_MAX_MESSAGES = 20;
constant STOP_MAX_BYTES (line 12) | const STOP_MAX_BYTES = 200_000;
function buildIngestUrl (line 37) | function buildIngestUrl(baseUrl) {
function runStop (line 51) | async function runStop(input) {
function readStdinText (line 107) | function readStdinText() {
function main (line 111) | async function main() {
FILE: codex-plugin/hooks/user-prompt-submit.mjs
constant RECALL_LIMIT (line 11) | const RECALL_LIMIT = 10;
function buildRecallUrl (line 37) | function buildRecallUrl(baseUrl, prompt, limit = RECALL_LIMIT) {
function extractMemories (line 48) | function extractMemories(payload) {
function runUserPromptSubmit (line 75) | async function runUserPromptSubmit(input) {
function readStdinText (line 121) | function readStdinText() {
function main (line 125) | async function main() {
FILE: codex-plugin/lib/config.mjs
constant DEFAULT_AGENT_ID (line 13) | const DEFAULT_AGENT_ID = "codex";
constant DEFAULT_REQUEST_TIMEOUT_MS (line 14) | const DEFAULT_REQUEST_TIMEOUT_MS = 8_000;
constant DEFAULT_SEARCH_TIMEOUT_MS (line 15) | const DEFAULT_SEARCH_TIMEOUT_MS = 15_000;
constant DEFAULT_API_URL (line 17) | const DEFAULT_API_URL = "https://api.mem9.ai";
constant DEFAULT_PLUGIN_ID (line 18) | const DEFAULT_PLUGIN_ID = "mem9@mem9-ai";
constant DEFAULT_PLUGIN_INSTALL_IDENTITY (line 19) | const DEFAULT_PLUGIN_INSTALL_IDENTITY = {
constant DEFAULT_PLUGIN_VERSION (line 23) | const DEFAULT_PLUGIN_VERSION = "local";
constant PLUGINS_CACHE_DIR (line 24) | const PLUGINS_CACHE_DIR = path.join("plugins", "cache");
function isRecord (line 177) | function isRecord(value) {
function readJsonFile (line 181) | function readJsonFile(filePath) {
function readTextFile (line 185) | function readTextFile(filePath) {
function readDirNamesFromDisk (line 189) | function readDirNamesFromDisk(dirPath) {
function envOverride (line 195) | function envOverride(env, key) {
function normalizeTimeoutMs (line 200) | function normalizeTimeoutMs(value, fallback) {
function resolveHomePath (line 208) | function resolveHomePath(inputPath, envPath, fallbackPath) {
function normalizeString (line 220) | function normalizeString(value) {
function normalizeProfileId (line 224) | function normalizeProfileId(value) {
function isValidPluginVersionSegment (line 228) | function isValidPluginVersionSegment(pluginVersion) {
function resolveInstalledPluginCacheVersion (line 237) | function resolveInstalledPluginCacheVersion({
function runtimePaths (line 264) | function runtimePaths(projectRoot, codexHome, mem9Home) {
function asScopeConfig (line 280) | function asScopeConfig(value) {
function asCredentialsFile (line 284) | function asCredentialsFile(value) {
function resolveScopedProfileId (line 288) | function resolveScopedProfileId(globalConfig, projectConfig) {
function resolveScopedTimeout (line 293) | function resolveScopedTimeout(projectValue, globalValue, fallback) {
function resolveScopedEnabled (line 313) | function resolveScopedEnabled(globalConfig, projectConfig) {
function buildEffectiveScopeConfig (line 321) | function buildEffectiveScopeConfig(globalConfig, projectConfig) {
function resolveConfigSource (line 344) | function resolveConfigSource(globalLoad, projectLoad) {
function resolveScopeFromConfigSource (line 352) | function resolveScopeFromConfigSource(configSource) {
function loadScopeConfigFile (line 356) | function loadScopeConfigFile(filePath, exists, readJson) {
function stripTomlLineComment (line 380) | function stripTomlLineComment(line) {
function parseTomlTableHeader (line 416) | function parseTomlTableHeader(line) {
function parsePluginEnabledState (line 421) | function parsePluginEnabledState(configTomlText, pluginId = DEFAULT_PLUG...
function loadPluginState (line 458) | function loadPluginState({
function resolveLegacyPausedState (line 534) | function resolveLegacyPausedState(globalConfig, projectConfig) {
function resolveConfigIssue (line 558) | function resolveConfigIssue(globalLoad, projectLoad) {
function resolveWarnings (line 587) | function resolveWarnings(globalLoad, projectLoad) {
function resolveCodexHome (line 604) | function resolveCodexHome(inputCodexHome, env, homeDir = os.homedir()) {
function resolveMem9Home (line 612) | function resolveMem9Home(inputMem9Home, env, homeDir = os.homedir()) {
function resolveRuntimeConfig (line 620) | function resolveRuntimeConfig(input) {
function loadRuntimeStateFromDisk (line 658) | function loadRuntimeStateFromDisk(input = {}) {
function loadRuntimeFromDisk (line 783) | function loadRuntimeFromDisk(input = {}) {
FILE: codex-plugin/lib/http.mjs
function mem9FetchJson (line 14) | async function mem9FetchJson(url, options = {}) {
function mem9Headers (line 41) | function mem9Headers(apiKey, agentId) {
function buildMem9Url (line 49) | function buildMem9Url(baseUrl, relativePath) {
FILE: codex-plugin/lib/project-root.mjs
function resolveProjectRoot (line 6) | function resolveProjectRoot(input = {}) {
FILE: codex-plugin/lib/skill-runtime.mjs
function legacyPausedSource (line 5) | function legacyPausedSource(state) {
function buildRuntimeIssueMessage (line 17) | function buildRuntimeIssueMessage(state) {
function loadReadyRuntimeState (line 66) | function loadReadyRuntimeState(options = {}) {
FILE: codex-plugin/lib/update-check.mjs
constant DEFAULT_UPDATE_CHECK (line 11) | const DEFAULT_UPDATE_CHECK = Object.freeze({
constant DEFAULT_INSTALL_IDENTITY (line 16) | const DEFAULT_INSTALL_IDENTITY = Object.freeze({
constant DEFAULT_REMOTE_UPGRADE_COMMAND (line 20) | const DEFAULT_REMOTE_UPGRADE_COMMAND =
constant REMOTE_UPDATE_MANIFEST_URL (line 22) | const REMOTE_UPDATE_MANIFEST_URL =
constant REMOTE_UPDATE_TIMEOUT_MS (line 24) | const REMOTE_UPDATE_TIMEOUT_MS = 2_000;
function isRecord (line 137) | function isRecord(value) {
function normalizeString (line 145) | function normalizeString(value) {
function normalizePositiveInteger (line 154) | function normalizePositiveInteger(value, fallback) {
function normalizeDate (line 166) | function normalizeDate(value) {
function normalizeTimestamp (line 176) | function normalizeTimestamp(value) {
function normalizeVersionText (line 194) | function normalizeVersionText(value) {
function normalizeInstallIdentity (line 202) | function normalizeInstallIdentity(value) {
function buildMarketplaceUpgradeCommand (line 221) | function buildMarketplaceUpgradeCommand(marketplaceName) {
function normalizeUpdateState (line 231) | function normalizeUpdateState(value) {
function readJsonFile (line 249) | function readJsonFile(filePath) {
function compareNumbers (line 258) | function compareNumbers(left, right) {
function parseComparableVersion (line 270) | function parseComparableVersion(version) {
function comparePrereleaseIdentifier (line 300) | function comparePrereleaseIdentifier(left, right) {
function compareParsedVersions (line 328) | function compareParsedVersions(left, right) {
function normalizeRemoteManifest (line 383) | function normalizeRemoteManifest(value, fallbackUpgradeCommand) {
function isRemoteCheckDue (line 411) | function isRemoteCheckDue(lastCheckedAt, intervalHours, now) {
function fetchRemoteManifest (line 424) | async function fetchRemoteManifest(input = {}) {
function resolveInstallIdentity (line 467) | function resolveInstallIdentity(input) {
function maybeResolveRemoteUpdateNotice (line 497) | async function maybeResolveRemoteUpdateNotice(input) {
function normalizeUpdateCheckConfig (line 567) | function normalizeUpdateCheckConfig(value) {
function comparePluginVersions (line 584) | function comparePluginVersions(left, right) {
function resolveUpdateStatePath (line 599) | function resolveUpdateStatePath(codexHome) {
function readUpdateStateFile (line 607) | function readUpdateStateFile(input = {}) {
function writeUpdateStateFile (line 630) | function writeUpdateStateFile(statePath, state, input = {}) {
function resolveUpgradeNotice (line 658) | async function resolveUpgradeNotice(input) {
FILE: codex-plugin/skills/cleanup/scripts/cleanup.mjs
constant MEM9_EVENTS (line 18) | const MEM9_EVENTS = ["SessionStart", "UserPromptSubmit", "Stop"];
constant MEM9_MANAGED_HOOKS (line 19) | const MEM9_MANAGED_HOOKS = {
function isRecord (line 34) | function isRecord(value) {
function normalizeString (line 38) | function normalizeString(value) {
function isHelpToken (line 42) | function isHelpToken(token) {
function detectCleanupHelpRequest (line 47) | function detectCleanupHelpRequest(argv = process.argv.slice(2)) {
function buildCleanupHelpText (line 68) | function buildCleanupHelpText(command = "") {
function maybeWriteCleanupHelp (line 133) | function maybeWriteCleanupHelp(argv, stdout) {
function sanitizeRelativePath (line 147) | function sanitizeRelativePath(filePath, basePath, options = {}) {
function sanitizeDisplayPath (line 174) | function sanitizeDisplayPath(filePath, { cwd, codexHome, mem9Home }) {
function sanitizeProjectConfigPath (line 207) | function sanitizeProjectConfigPath(filePath, context) {
function sanitizeProjectRootPath (line 212) | function sanitizeProjectRootPath(filePath, context) {
function sanitizeOptionalPath (line 219) | function sanitizeOptionalPath(filePath, context) {
function parseArgs (line 225) | function parseArgs(argv = process.argv.slice(2)) {
function isWritablePath (line 257) | function isWritablePath(targetPath, fsOps = {}) {
function resolveContext (line 279) | function resolveContext(args = {}, options = {}) {
function normalizeHookCommand (line 331) | function normalizeHookCommand(command) {
function managedHookCommandFragments (line 335) | function managedHookCommandFragments(scriptName) {
function isMem9ManagedHook (line 342) | function isMem9ManagedHook(eventName, hook) {
function readJsonFile (line 357) | function readJsonFile(filePath, fsOps = {}) {
function writeJsonFile (line 362) | function writeJsonFile(filePath, value, fsOps = {}) {
function buildCleanupSnapshot (line 367) | function buildCleanupSnapshot(context) {
function inspectManagedHooks (line 501) | function inspectManagedHooks(filePath, context) {
function removeManagedHooks (line 541) | function removeManagedHooks(existingHooks) {
function inspectCleanup (line 575) | function inspectCleanup(argv = process.argv.slice(2), options = {}) {
function ensureWritableCleanupTargets (line 600) | function ensureWritableCleanupTargets(context, includeProject) {
function runCleanup (line 616) | function runCleanup(argv = process.argv.slice(2), options = {}) {
function main (line 709) | function main(argv = process.argv.slice(2), options = {}) {
FILE: codex-plugin/skills/recall/scripts/recall.mjs
constant DEFAULT_LIMIT (line 11) | const DEFAULT_LIMIT = 10;
function normalizeString (line 13) | function normalizeString(value) {
function isHelpToken (line 17) | function isHelpToken(token) {
function shouldWriteRecallHelp (line 22) | function shouldWriteRecallHelp(argv = process.argv.slice(2)) {
function buildRecallHelpText (line 29) | function buildRecallHelpText() {
function parseIntegerArg (line 59) | function parseIntegerArg(flag, value) {
function parseArgs (line 68) | function parseArgs(argv = process.argv.slice(2)) {
function buildRecallUrl (line 100) | function buildRecallUrl(baseUrl, query, limit = DEFAULT_LIMIT) {
function extractMemories (line 107) | function extractMemories(payload) {
function normalizeMemorySummary (line 124) | function normalizeMemorySummary(memory) {
function readStdinText (line 137) | function readStdinText() {
function runRecall (line 141) | async function runRecall(argv = process.argv.slice(2), options = {}) {
function main (line 197) | async function main(argv = process.argv.slice(2), options = {}) {
FILE: codex-plugin/skills/setup/scripts/setup.mjs
constant SCRIPT_DIR (line 31) | const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
constant PACKAGE_ROOT (line 32) | const PACKAGE_ROOT = path.resolve(SCRIPT_DIR, "../../..");
constant PLUGIN_MANIFEST_PATH (line 33) | const PLUGIN_MANIFEST_PATH = path.join(PACKAGE_ROOT, ".codex-plugin", "p...
constant HOOK_SHIM_SOURCE_DIR (line 34) | const HOOK_SHIM_SOURCE_DIR = path.join(PACKAGE_ROOT, "bootstrap-hooks");
constant HOOK_TEMPLATE_PATH (line 35) | const HOOK_TEMPLATE_PATH = path.join(PACKAGE_ROOT, "templates", "hooks.j...
constant DEFAULT_BASE_URL (line 36) | const DEFAULT_BASE_URL = "https://api.mem9.ai";
constant DEFAULT_UPDATE_CHECK (line 37) | const DEFAULT_UPDATE_CHECK = Object.freeze({
constant DEFAULT_INSTALL_METADATA (line 41) | const DEFAULT_INSTALL_METADATA = {
constant CODEX_HOOKS_CANONICAL_RENAME_VERSION (line 47) | const CODEX_HOOKS_CANONICAL_RENAME_VERSION = {
constant HOOKS_FEATURE_KEY (line 52) | const HOOKS_FEATURE_KEY = "hooks";
constant LEGACY_HOOKS_FEATURE_KEY (line 53) | const LEGACY_HOOKS_FEATURE_KEY = "codex_hooks";
constant HOOKS_FEATURE_KEYS (line 54) | const HOOKS_FEATURE_KEYS = [HOOKS_FEATURE_KEY, LEGACY_HOOKS_FEATURE_KEY];
constant MEM9_EVENTS (line 55) | const MEM9_EVENTS = ["SessionStart", "UserPromptSubmit", "Stop"];
constant HOOK_TEMPLATE_KEYS (line 56) | const HOOK_TEMPLATE_KEYS = {
constant MEM9_MANAGED_HOOKS (line 61) | const MEM9_MANAGED_HOOKS = {
function isRecord (line 76) | function isRecord(value) {
function normalizeString (line 80) | function normalizeString(value) {
function normalizeBaseUrl (line 84) | function normalizeBaseUrl(value) {
function normalizeTimeoutMs (line 89) | function normalizeTimeoutMs(value, fallback) {
function parseIntegerArg (line 97) | function parseIntegerArg(flag, value) {
function parseSemver (line 106) | function parseSemver(value) {
function compareSemver (line 119) | function compareSemver(left, right) {
function detectCodexVersion (line 136) | function detectCodexVersion(options = {}) {
function selectHooksFeatureKey (line 174) | function selectHooksFeatureKey(options = {}) {
function parseUpdateCheckMode (line 189) | function parseUpdateCheckMode(value) {
function readTextFile (line 198) | function readTextFile(filePath, readFile = readFileSync) {
function isHelpToken (line 202) | function isHelpToken(token) {
function detectSetupHelpRequest (line 207) | function detectSetupHelpRequest(argv = process.argv.slice(2)) {
function buildSetupHelpText (line 246) | function buildSetupHelpText(command = "", subcommand = "") {
function maybeWriteSetupHelp (line 430) | function maybeWriteSetupHelp(argv, stdout) {
function getProfiles (line 446) | function getProfiles(credentials) {
function hasApiKey (line 450) | function hasApiKey(profile) {
function buildDefaultProfileId (line 454) | function buildDefaultProfileId(profiles) {
function normalizeProfileRecord (line 463) | function normalizeProfileRecord(profileId, profile) {
function readJsonFileOrDefault (line 473) | function readJsonFileOrDefault(filePath, fallback, fsOps = {}, options =...
function readTextFileOrDefault (line 498) | function readTextFileOrDefault(filePath, fallback, fsOps = {}) {
function writeJsonFile (line 509) | function writeJsonFile(filePath, value, fsOps = {}) {
function writeTextFile (line 517) | function writeTextFile(filePath, text, fsOps = {}) {
function buildBackupPath (line 525) | function buildBackupPath(filePath, fsOps = {}) {
function backupFiles (line 538) | function backupFiles(filePaths, fsOps = {}) {
function sanitizeDisplayPath (line 559) | function sanitizeDisplayPath(filePath, { cwd, codexHome, mem9Home }) {
function sanitizeRelativePath (line 592) | function sanitizeRelativePath(filePath, basePath, options = {}) {
function sanitizeProjectPath (line 619) | function sanitizeProjectPath(filePath, context) {
function sanitizeBackupsForOutput (line 624) | function sanitizeBackupsForOutput(backups, context) {
function sanitizeOptionalPath (line 631) | function sanitizeOptionalPath(filePath, context) {
function requireString (line 637) | function requireString(flag, value) {
function summarizeApiKeyPreview (line 646) | function summarizeApiKeyPreview(apiKey) {
function summarizeProfileDisplayName (line 663) | function summarizeProfileDisplayName(profileId, label) {
function summarizeProfileDisplaySummary (line 668) | function summarizeProfileDisplaySummary(profile) {
function resolveInstallIdentityFromMetadata (line 678) | function resolveInstallIdentityFromMetadata(installPath, fsOps = {}) {
function summarizeInstalledSetupScriptPath (line 693) | function summarizeInstalledSetupScriptPath(context) {
function summarizeShellPath (line 727) | function summarizeShellPath(displayPath) {
function buildManualSaveKeyShellCommand (line 747) | function buildManualSaveKeyShellCommand(profileId, label, baseUrl, conte...
function summarizeProfiles (line 766) | function summarizeProfiles(profiles, context) {
function normalizeUpdateCheckConfig (line 794) | function normalizeUpdateCheckConfig(value) {
function stripTomlLineComment (line 806) | function stripTomlLineComment(line) {
function parseFeaturesHooksState (line 842) | function parseFeaturesHooksState(configTomlText = "") {
function inspectJsonFile (line 877) | function inspectJsonFile(filePath, fallback, fsOps = {}) {
function summarizeScopeConfigState (line 913) | function summarizeScopeConfigState(config, options = {}) {
function summarizeScopeFile (line 938) | function summarizeScopeFile(filePath, context, fsOps = {}, options = {}) {
function inspectInstallMetadata (line 954) | function inspectInstallMetadata(filePath, context, fsOps = {}) {
function listRelativeFiles (line 968) | function listRelativeFiles(dirPath, fsOps = {}, prefix = "") {
function detectHookShimsInstalled (line 985) | function detectHookShimsInstalled(sourceDir, targetDir, fsOps = {}) {
function detectManagedHooksInstalled (line 996) | function detectManagedHooksInstalled(existingHooks) {
function parseArgs (line 1014) | function parseArgs(argv = process.argv.slice(2)) {
function assertNodeVersion (line 1169) | function assertNodeVersion(nodeVersion = process.versions.node) {
function isWritablePath (line 1178) | function isWritablePath(targetPath, fsOps = {}) {
function resolveGlobalPaths (line 1201) | function resolveGlobalPaths(codexHome) {
function resolveInstalledPluginIdentity (line 1213) | function resolveInstalledPluginIdentity(codexHome, packageRoot = PACKAGE...
function buildInstallMetadata (line 1233) | function buildInstallMetadata(codexHome, packageRoot = PACKAGE_ROOT) {
function shellQuote (line 1240) | function shellQuote(value, platform = process.platform) {
function buildNodeCommand (line 1251) | function buildNodeCommand(scriptPath, platform = process.platform) {
function buildHookCommands (line 1256) | function buildHookCommands(hooksDir) {
function renderHooksTemplate (line 1279) | function renderHooksTemplate({
function normalizeHookCommand (line 1297) | function normalizeHookCommand(command) {
function managedHookCommandFragments (line 1301) | function managedHookCommandFragments(scriptName) {
function isMem9ManagedHook (line 1308) | function isMem9ManagedHook(eventName, hook) {
function removeManagedHooks (line 1323) | function removeManagedHooks(existingHooks) {
function mergeMem9Hooks (line 1353) | function mergeMem9Hooks(existingHooks, mem9Hooks) {
function applyCodexHooksPatch (line 1374) | function applyCodexHooksPatch(sourceText = "", options = {}) {
function upsertCredentialsProfile (line 1450) | function upsertCredentialsProfile(credentials, profile) {
function buildManualProfileGuidance (line 1470) | function buildManualProfileGuidance(profileId, label, baseUrl = DEFAULT_...
function provisionApiKey (line 1492) | async function provisionApiKey({
function buildScopeConfig (line 1543) | function buildScopeConfig(profileId, options = {}) {
function installHookShims (line 1575) | function installHookShims(sourceDir, targetDir, fsOps = {}) {
function resolveCommandContext (line 1595) | function resolveCommandContext(args = {}, options = {}) {
function resolveHooksFeature (line 1642) | function resolveHooksFeature(options = {}) {
function emitMachineSummary (line 1654) | function emitMachineSummary(summary, context, stdout) {
function sanitizeScopeResultForOutput (line 1658) | function sanitizeScopeResultForOutput(result, context) {
function sanitizeProfileResultForOutput (line 1680) | function sanitizeProfileResultForOutput(result, context) {
function prepareManagedRuntimeRepair (line 1693) | function prepareManagedRuntimeRepair(context, noteInvalidJson, options =...
function applyManagedRuntimeRepair (line 1746) | function applyManagedRuntimeRepair(context, prepared, options = {}) {
function loadCredentialsForWrite (line 1791) | function loadCredentialsForWrite(context, noteInvalidJson) {
function resolveWritableFlags (line 1806) | function resolveWritableFlags(context, options = {}) {
function resolveProfileForWrite (line 1824) | function resolveProfileForWrite(args, profiles) {
function inspectSetup (line 1837) | function inspectSetup(argv = process.argv.slice(2), options = {}) {
function runProfileCreate (line 1993) | async function runProfileCreate(args, options = {}) {
function runProfileSaveKey (line 2043) | async function runProfileSaveKey(args, options = {}) {
function readValidatedCredentials (line 2097) | function readValidatedCredentials(context) {
function resolveConfigWriteFallback (line 2118) | function resolveConfigWriteFallback(scope, targetConfig, globalConfig) {
function runScopeApply (line 2129) | async function runScopeApply(args, options = {}) {
function runScopeClear (line 2230) | async function runScopeClear(args, options = {}) {
function runSetup (line 2295) | async function runSetup(argv = process.argv.slice(2), options = {}) {
function main (line 2327) | async function main(argv = process.argv.slice(2), options = {}) {
FILE: codex-plugin/skills/store/scripts/store.mjs
function normalizeString (line 11) | function normalizeString(value) {
function isHelpToken (line 15) | function isHelpToken(token) {
function shouldWriteStoreHelp (line 20) | function shouldWriteStoreHelp(argv = process.argv.slice(2)) {
function buildStoreHelpText (line 27) | function buildStoreHelpText() {
function parseArgs (line 56) | function parseArgs(argv = process.argv.slice(2)) {
function readStdinText (line 83) | function readStdinText() {
function runStore (line 87) | async function runStore(argv = process.argv.slice(2), options = {}) {
function main (line 138) | async function main(argv = process.argv.slice(2), options = {}) {
FILE: codex-plugin/tests/bootstrap-hooks.test.mjs
function writeJson (line 25) | function writeJson(filePath, value) {
FILE: codex-plugin/tests/cleanup.test.mjs
function writeJson (line 21) | function writeJson(filePath, value) {
function readJson (line 26) | function readJson(filePath) {
function createCleanupFixture (line 30) | function createCleanupFixture() {
function createStdoutCapture (line 123) | function createStdoutCapture() {
FILE: codex-plugin/tests/recall.test.mjs
method write (line 38) | write(/** @type {string} */ chunk) {
method write (line 97) | write(/** @type {string} */ chunk) {
method write (line 153) | write() {}
FILE: codex-plugin/tests/runtime-config.test.mjs
constant REPO_ROOT (line 18) | const REPO_ROOT = "/workspace/app";
constant PROJECT_CWD (line 19) | const PROJECT_CWD = "/workspace/app/packages/web";
constant OUTSIDE_CWD (line 20) | const OUTSIDE_CWD = "/workspace/scratch";
constant CODEX_HOME (line 21) | const CODEX_HOME = "/CODEX_HOME";
constant MEM9_HOME (line 22) | const MEM9_HOME = "/MEM9_HOME";
constant GLOBAL_CONFIG_PATH (line 23) | const GLOBAL_CONFIG_PATH = `${CODEX_HOME}/mem9/config.json`;
constant PROJECT_CONFIG_PATH (line 24) | const PROJECT_CONFIG_PATH = `${REPO_ROOT}/.codex/mem9/config.json`;
constant CREDENTIALS_PATH (line 25) | const CREDENTIALS_PATH = `${MEM9_HOME}/.credentials.json`;
constant CONFIG_TOML_PATH (line 26) | const CONFIG_TOML_PATH = `${CODEX_HOME}/config.toml`;
constant INSTALL_PATH (line 27) | const INSTALL_PATH = `${CODEX_HOME}/mem9/install.json`;
constant DEFAULT_INSTALL (line 29) | const DEFAULT_INSTALL = {
constant DEFAULT_CREDENTIALS (line 36) | const DEFAULT_CREDENTIALS = {
function normalizeFixtureString (line 52) | function normalizeFixtureString(value) {
function createRuntimeDisk (line 56) | function createRuntimeDisk(options = {}) {
method exists (line 148) | exists(filePath) {
method exists (line 159) | exists(filePath) {
method exists (line 170) | exists(filePath) {
FILE: codex-plugin/tests/session-start.test.mjs
constant SESSION_START_ENTRY (line 14) | const SESSION_START_ENTRY = path.resolve("./hooks/session-start.mjs");
function writeJson (line 20) | function writeJson(filePath, value) {
function runNodeHook (line 29) | async function runNodeHook(scriptPath, input) {
FILE: codex-plugin/tests/setup.test.mjs
function writeJson (line 30) | function writeJson(filePath, value) {
function readJson (line 35) | function readJson(filePath) {
function installActivePlugin (line 39) | function installActivePlugin(codexHome) {
method write (line 183) | write(chunk) {
method write (line 209) | write(chunk) {
method execFileSync (line 507) | execFileSync() {
method write (line 758) | write(chunk) {
method json (line 816) | async json() {
method write (line 824) | write(chunk) {
method write (line 883) | write(chunk) {
method execFileSync (line 1091) | execFileSync() {
method write (line 1095) | write(chunk) {
method execFileSync (line 1262) | execFileSync() {
method write (line 1520) | write(chunk) {
FILE: codex-plugin/tests/stop.test.mjs
constant STOP_ENTRY (line 19) | const STOP_ENTRY = path.resolve("./hooks/stop.mjs");
constant NON_READY_ISSUE_CODES (line 21) | const NON_READY_ISSUE_CODES = ["plugin_disabled", "plugin_missing", "leg...
function writeJson (line 34) | function writeJson(filePath, value) {
function createCountingServer (line 39) | async function createCountingServer() {
function runNodeHook (line 84) | async function runNodeHook(scriptPath, input) {
function createRuntimeLayout (line 119) | function createRuntimeLayout(tempRoot, issueCode, baseUrl) {
method post (line 447) | async post(_url, body, options) {
method debug (line 452) | debug(stage, fields) {
FILE: codex-plugin/tests/store.test.mjs
method write (line 18) | write(/** @type {string} */ chunk) {
method write (line 66) | write(/** @type {string} */ chunk) {
method write (line 114) | write() {}
method write (line 142) | write() {}
FILE: codex-plugin/tests/test-temp.mjs
constant TESTS_DIR (line 5) | const TESTS_DIR = path.dirname(fileURLToPath(import.meta.url));
constant PACKAGE_ROOT (line 6) | const PACKAGE_ROOT = path.resolve(TESTS_DIR, "..");
constant TMP_ROOT (line 7) | const TMP_ROOT = path.join(PACKAGE_ROOT, ".tmp");
constant RUN_ROOT (line 8) | const RUN_ROOT = path.join(TMP_ROOT, `run-${process.pid}`);
function registerTmpCleanup (line 12) | function registerTmpCleanup() {
function createTempRoot (line 26) | function createTempRoot(scope = "tests") {
FILE: codex-plugin/tests/update-check.test.mjs
method exists (line 209) | exists(filePath) {
method readJson (line 212) | readJson(filePath) {
FILE: codex-plugin/tests/user-prompt-submit.test.mjs
constant USER_PROMPT_SUBMIT_ENTRY (line 19) | const USER_PROMPT_SUBMIT_ENTRY = path.resolve("./hooks/user-prompt-submi...
constant NON_READY_ISSUE_CODES (line 21) | const NON_READY_ISSUE_CODES = ["plugin_disabled", "plugin_missing", "leg...
function writeJson (line 27) | function writeJson(filePath, value) {
function createCountingServer (line 32) | async function createCountingServer() {
function runNodeHook (line 77) | async function runNodeHook(scriptPath, input) {
function createRuntimeLayout (line 112) | function createRuntimeLayout(tempRoot, issueCode, baseUrl) {
method search (line 207) | async search(url, options) {
method debug (line 216) | debug(stage, fields) {
method search (line 249) | async search() {
method debug (line 253) | debug(stage, fields) {
FILE: dashboard/app/scripts/sentry-sourcemaps.mjs
function runSentryCli (line 25) | function runSentryCli(command, extraArgs = []) {
FILE: dashboard/app/src/api/analysis-cache.ts
type AnalysisCacheEntry (line 9) | interface AnalysisCacheEntry {
function readAnalysisCache (line 17) | function readAnalysisCache(
function writeAnalysisCache (line 24) | function writeAnalysisCache(
function clearAnalysisCache (line 32) | function clearAnalysisCache(
FILE: dashboard/app/src/api/analysis-client.ts
constant ANALYSIS_API_BASE (line 19) | const ANALYSIS_API_BASE =
class AnalysisApiError (line 22) | class AnalysisApiError extends Error {
method constructor (line 28) | public constructor(
function readJson (line 42) | async function readJson<T>(response: Response): Promise<T | null> {
function request (line 51) | async function request<T>(
function requestResponse (line 67) | async function requestResponse(
method createJob (line 97) | createJob(
method uploadBatch (line 107) | uploadBatch(
method finalizeJob (line 119) | finalizeJob(
method getSnapshot (line 128) | getSnapshot(
method getUpdates (line 135) | getUpdates(
method getTaxonomy (line 144) | getTaxonomy(spaceId: string, version?: string): Promise<TaxonomyResponse> {
method createDeepAnalysisReport (line 151) | createDeepAnalysisReport(
method listDeepAnalysisReports (line 161) | listDeepAnalysisReports(
method getDeepAnalysisReport (line 173) | getDeepAnalysisReport(
method downloadDeepAnalysisDuplicatesCsv (line 180) | async downloadDeepAnalysisDuplicatesCsv(
method deleteDeepAnalysisDuplicates (line 191) | deleteDeepAnalysisDuplicates(
method deleteDeepAnalysisReport (line 200) | deleteDeepAnalysisReport(
FILE: dashboard/app/src/api/analysis-helpers.test.ts
function createMemory (line 20) | function createMemory(overrides: Partial<Memory> = {}): Memory {
function createSnapshot (line 39) | function createSnapshot(): AnalysisJobSnapshotResponse {
FILE: dashboard/app/src/api/analysis-helpers.ts
constant TERMINAL_JOB_STATUSES (line 16) | const TERMINAL_JOB_STATUSES: JobStatus[] = [
constant DEFAULT_TAXONOMY_VERSION (line 24) | const DEFAULT_TAXONOMY_VERSION = "v3";
constant MAX_ANALYSIS_FACETS (line 25) | const MAX_ANALYSIS_FACETS = 50;
constant EMPTY_AGGREGATE (line 27) | const EMPTY_AGGREGATE: AggregateSnapshot = {
function getAnalysisBatchSize (line 35) | function getAnalysisBatchSize(): number {
function getDefaultPollMs (line 40) | function getDefaultPollMs(): number {
function isTerminalJobStatus (line 45) | function isTerminalJobStatus(status: JobStatus): boolean {
function toAnalysisMemoryInput (line 49) | function toAnalysisMemoryInput(memory: Memory): AnalysisMemoryInput {
function chunkAnalysisMemories (line 58) | function chunkAnalysisMemories<T>(items: T[], size: number): T[][] {
function dateRangeFromMemories (line 67) | function dateRangeFromMemories(
function buildCreateJobRequest (line 87) | function buildCreateJobRequest(
function makeBatchSummaries (line 113) | function makeBatchSummaries(
function createPendingSnapshot (line 127) | function createPendingSnapshot(
function applyUploadedBatch (line 163) | function applyUploadedBatch(
function toAggregateCards (line 186) | function toAggregateCards(
function compareFacetValues (line 202) | function compareFacetValues(left: string, right: string): number {
function normalizeFacetStats (line 206) | function normalizeFacetStats(stats: AnalysisFacetStat[]): AnalysisFacetS...
function buildFacetStats (line 216) | function buildFacetStats(
function toFacetStatsFromLegacyValues (line 230) | function toFacetStatsFromLegacyValues(
function toFacetValues (line 248) | function toFacetValues(stats: AnalysisFacetStat[]): string[] {
function getMoreRecentProgress (line 252) | function getMoreRecentProgress(
function getMoreRecentAggregate (line 261) | function getMoreRecentAggregate(
function mergeBatchSummaries (line 270) | function mergeBatchSummaries(
function mergeSnapshotWithUpdates (line 284) | function mergeSnapshotWithUpdates(
function sha256Hex (line 319) | async function sha256Hex(input: string): Promise<string> {
function createMemoryFingerprint (line 327) | async function createMemoryFingerprint(
function createBatchHash (line 341) | async function createBatchHash(
function isDegradedAnalysisError (line 357) | function isDegradedAnalysisError(error: unknown): boolean {
FILE: dashboard/app/src/api/analysis-matcher.test.ts
function createMemory (line 10) | function createMemory(
FILE: dashboard/app/src/api/analysis-matcher.ts
function normalizeText (line 10) | function normalizeText(memory: Memory): string {
function matchesRule (line 15) | function matchesRule(text: string, rule: TaxonomyRuleDefinition): boolean {
function matchMemoriesToTaxonomy (line 30) | function matchMemoriesToTaxonomy(
function buildAnalysisCardsFromMatches (line 63) | function buildAnalysisCardsFromMatches(
function createAnalysisMatchMap (line 85) | function createAnalysisMatchMap(
FILE: dashboard/app/src/api/analysis-queries.test.ts
function createSnapshot (line 14) | function createSnapshot(
function createBatchSummaries (line 81) | function createBatchSummaries(
FILE: dashboard/app/src/api/analysis-queries.ts
constant TERMINAL_BATCH_STATUSES (line 41) | const TERMINAL_BATCH_STATUSES = new Set(["SUCCEEDED", "FAILED", "DLQ"]);
constant ANALYSIS_AUTO_REFRESH_WINDOW_MS (line 42) | const ANALYSIS_AUTO_REFRESH_WINDOW_MS = 3 * 24 * 60 * 60 * 1000;
constant MAX_STALLED_POLL_ATTEMPTS (line 43) | const MAX_STALLED_POLL_ATTEMPTS = 4;
type PollProgressState (line 45) | interface PollProgressState {
type AnalysisStartupResult (line 55) | interface AnalysisStartupResult {
constant INITIAL_STATE (line 63) | const INITIAL_STATE: SpaceAnalysisState = {
function shouldStopPollingSnapshot (line 76) | function shouldStopPollingSnapshot(
function isAnalysisCacheFresh (line 96) | function isAnalysisCacheFresh(
function trimEvents (line 109) | function trimEvents<T>(items: T[], limit: number): T[] {
function persistAnalysisSnapshot (line 113) | async function persistAnalysisSnapshot(
function createAnalysisRunKey (line 133) | function createAnalysisRunKey(
function getSnapshotPhase (line 141) | function getSnapshotPhase(
function shouldRestartIncompleteCachedSnapshot (line 153) | function shouldRestartIncompleteCachedSnapshot(
function createTerminalBatchSignature (line 162) | function createTerminalBatchSignature(
function createPollProgressState (line 171) | function createPollProgressState(
function hasPollingProgress (line 186) | function hasPollingProgress(
function getNextPollProgressState (line 200) | function getNextPollProgressState(
function shouldTreatPollAsStalled (line 219) | function shouldTreatPollAsStalled(
function shouldUseCachedAnalysisMatches (line 225) | function shouldUseCachedAnalysisMatches({
function startAnalysisStartup (line 244) | async function startAnalysisStartup(
function useSpaceAnalysis (line 337) | function useSpaceAnalysis(input: {
FILE: dashboard/app/src/api/client.ts
function createHybridProvider (line 6) | function createHybridProvider(): DashboardProvider {
FILE: dashboard/app/src/api/deep-analysis-queries.test.tsx
function createWrapper (line 22) | function createWrapper() {
FILE: dashboard/app/src/api/deep-analysis-queries.ts
constant TERMINAL_REPORT_STATUSES (line 12) | const TERMINAL_REPORT_STATUSES = new Set(["COMPLETED", "FAILED"]);
constant TERMINAL_DUPLICATE_CLEANUP_STATUSES (line 13) | const TERMINAL_DUPLICATE_CLEANUP_STATUSES = new Set(["COMPLETED", "FAILE...
function hasPendingDuplicateCleanup (line 15) | function hasPendingDuplicateCleanup(
function getDeepAnalysisReportsQueryKey (line 21) | function getDeepAnalysisReportsQueryKey(spaceId: string): string[] {
function getDeepAnalysisReportDetailQueryKey (line 25) | function getDeepAnalysisReportDetailQueryKey(
function shouldPollReports (line 32) | function shouldPollReports(reports: DeepAnalysisReportListItem[]): boole...
function useDeepAnalysisReports (line 40) | function useDeepAnalysisReports(spaceId: string, active: boolean) {
FILE: dashboard/app/src/api/local-cache.ts
constant DB_NAME (line 9) | const DB_NAME = "mem9-dashboard-cache";
constant DB_VERSION (line 10) | const DB_VERSION = 1;
constant MEMORIES_STORE (line 12) | const MEMORIES_STORE = "memories";
constant ANALYSIS_RESULTS_STORE (line 13) | const ANALYSIS_RESULTS_STORE = "analysis_results";
constant ANALYSIS_MATCHES_STORE (line 14) | const ANALYSIS_MATCHES_STORE = "analysis_matches";
constant SYNC_STATE_STORE (line 15) | const SYNC_STATE_STORE = "sync_state";
type CachedMemoryRecord (line 17) | interface CachedMemoryRecord {
type CachedAnalysisResultRecord (line 26) | interface CachedAnalysisResultRecord {
type CachedAnalysisMatchRecord (line 37) | interface CachedAnalysisMatchRecord {
type SyncStateRecord (line 47) | interface SyncStateRecord {
type CachedAnalysisResultEntry (line 55) | interface CachedAnalysisResultEntry {
function createMemoryKey (line 68) | function createMemoryKey(spaceId: string, memoryId: string): string {
function createRangeKey (line 72) | function createRangeKey(spaceId: string, range: TimeRangePreset): string {
function createMatchKey (line 76) | function createMatchKey(
function supportsIndexedDb (line 84) | function supportsIndexedDb(): boolean {
function requestToPromise (line 88) | function requestToPromise<T>(request: IDBRequest<T>): Promise<T> {
function transactionDone (line 95) | function transactionDone(transaction: IDBTransaction): Promise<void> {
function openDatabase (line 107) | function openDatabase(): Promise<IDBDatabase | null> {
function putRecords (line 153) | async function putRecords<T extends { key?: string; spaceId?: string }>(
function deleteRecords (line 169) | async function deleteRecords(storeName: string, keys: string[]): Promise...
function getRecord (line 182) | async function getRecord<T>(storeName: string, key: string): Promise<T |...
function getAllByIndex (line 193) | async function getAllByIndex<T>(
function toMatchRecord (line 209) | function toMatchRecord(
function fromMatchRecord (line 225) | function fromMatchRecord(record: CachedAnalysisMatchRecord): MemoryAnaly...
function readCachedMemories (line 233) | async function readCachedMemories(spaceId: string): Promise<Memory[]> {
function upsertCachedMemories (line 253) | async function upsertCachedMemories(
function clearCachedMemoriesForSpace (line 276) | async function clearCachedMemoriesForSpace(
function removeCachedMemory (line 297) | async function removeCachedMemory(
function readSyncState (line 309) | async function readSyncState(
function patchSyncState (line 319) | async function patchSyncState(
function readCachedAnalysisResult (line 356) | async function readCachedAnalysisResult(
function writeCachedAnalysisResult (line 387) | async function writeCachedAnalysisResult(
function clearCachedAnalysisResult (line 411) | async function clearCachedAnalysisResult(
function readCachedAnalysisMatches (line 423) | async function readCachedAnalysisMatches(
function writeCachedAnalysisMatches (line 441) | async function writeCachedAnalysisMatches(
function clearCachedAnalysisMatches (line 464) | async function clearCachedAnalysisMatches(
FILE: dashboard/app/src/api/mock-data.ts
constant MOCK_SPACE_ID (line 3) | const MOCK_SPACE_ID = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
FILE: dashboard/app/src/api/provider-http.ts
constant API_BASE (line 23) | const API_BASE = (import.meta.env.VITE_API_BASE || "/your-memory/api").r...
constant AGENT_ID (line 27) | const AGENT_ID = "mem9-dashboard";
constant EMPTY_TIMESTAMP (line 28) | const EMPTY_TIMESTAMP = new Date(0).toISOString();
function normalizeTags (line 30) | function normalizeTags(tags: unknown): string[] {
function buildHeaders (line 35) | function buildHeaders(
function normalizeMemory (line 49) | function normalizeMemory(memory: Partial<Memory>): Memory {
function hasValidMemoryShape (line 68) | function hasValidMemoryShape(memory: Partial<Memory>): boolean {
function normalizeMemoryListResponse (line 76) | function normalizeMemoryListResponse(
function normalizeSessionMessage (line 89) | function normalizeSessionMessage(
function normalizeSessionMessageListResponse (line 108) | function normalizeSessionMessageListResponse(
function request (line 118) | async function request<T>(
function requestRaw (line 136) | async function requestRaw(
method verifySpace (line 154) | async verifySpace(apiKey: string): Promise<SpaceInfo> {
method listMemories (line 167) | async listMemories(
method listSessionMessages (line 188) | async listSessionMessages(
method getStats (line 229) | async getStats(
method getMemory (line 254) | async getMemory(apiKey: string, memoryId: string): Promise<Memory> {
method createMemory (line 264) | async createMemory(
method updateMemory (line 284) | async updateMemory(
method deleteMemory (line 306) | async deleteMemory(apiKey: string, memoryId: string): Promise<void> {
method exportMemories (line 313) | async exportMemories(apiKey: string): Promise<MemoryExportFile> {
method importMemories (line 346) | async importMemories(apiKey: string, file: File): Promise<ImportTask> {
method getImportTask (line 359) | async getImportTask(
method listImportTasks (line 366) | async listImportTasks(apiKey: string): Promise<ImportTaskList> {
method getTopicSummary (line 385) | async getTopicSummary(
FILE: dashboard/app/src/api/provider-mock.ts
constant AGENT_ID (line 33) | const AGENT_ID = "mem9-dashboard";
function delay (line 35) | function delay(ms: number): Promise<void> {
function applyTimeFilter (line 96) | function applyTimeFilter(memories: Memory[], params?: TimeRangeParams): ...
function mockList (line 108) | function mockList(params: MemoryListParams): MemoryListResponse {
function mockListSessionMessages (line 164) | function mockListSessionMessages(
function mockStats (line 189) | function mockStats(params?: TimeRangeParams): MemoryStats {
function mockTopicSummary (line 198) | function mockTopicSummary(params?: TimeRangeParams): TopicSummary {
method verifySpace (line 219) | async verifySpace(apiKey: string): Promise<SpaceInfo> {
method listMemories (line 230) | async listMemories(
method listSessionMessages (line 238) | async listSessionMessages(
method getStats (line 246) | async getStats(
method getMemory (line 254) | async getMemory(_apiKey: string, memoryId: string): Promise<Memory> {
method createMemory (line 261) | async createMemory(
method updateMemory (line 286) | async updateMemory(
method deleteMemory (line 312) | async deleteMemory(apiKey: string, memoryId: string): Promise<void> {
method exportMemories (line 318) | async exportMemories(apiKey: string): Promise<MemoryExportFile> {
method importMemories (line 337) | async importMemories(
method getImportTask (line 368) | async getImportTask(
method listImportTasks (line 390) | async listImportTasks(_apiKey: string): Promise<ImportTaskList> {
method getTopicSummary (line 412) | async getTopicSummary(
FILE: dashboard/app/src/api/provider.ts
type DashboardProvider (line 17) | interface DashboardProvider {
FILE: dashboard/app/src/api/queries.test.ts
function createMemory (line 26) | function createMemory(sessionID = ""): Memory {
function createMessage (line 46) | function createMessage(
function importQueriesModule (line 67) | async function importQueriesModule() {
FILE: dashboard/app/src/api/queries.ts
constant PAGE_SIZE (line 21) | const PAGE_SIZE = 50;
function getLinkedSessionID (line 23) | function getLinkedSessionID(
function compareSessionMessages (line 29) | function compareSessionMessages(
function useStats (line 50) | function useStats(
function useMemories (line 64) | function useMemories(
function sortSessionMessages (line 97) | function sortSessionMessages(
function useSelectedSessionMessages (line 103) | function useSelectedSessionMessages(
function useMemory (line 124) | function useMemory(spaceId: string, memoryId: string | null) {
function useTopicSummary (line 132) | function useTopicSummary(
function useImportTasks (line 146) | function useImportTasks(spaceId: string, enabled = true) {
function useImportTask (line 159) | function useImportTask(
function useCreateMemory (line 177) | function useCreateMemory(spaceId: string) {
function useDeleteMemory (line 191) | function useDeleteMemory(spaceId: string) {
function useUpdateMemory (line 204) | function useUpdateMemory(spaceId: string) {
function useExportMemories (line 226) | function useExportMemories(spaceId: string) {
function useImportMemories (line 236) | function useImportMemories(spaceId: string) {
FILE: dashboard/app/src/api/source-memories.test.ts
function createMemory (line 4) | function createMemory(id: string): Memory {
function importModules (line 37) | async function importModules() {
FILE: dashboard/app/src/api/source-memories.ts
constant PAGE_SIZE (line 13) | const PAGE_SIZE = 200;
function getSourceMemoriesQueryKey (line 16) | function getSourceMemoriesQueryKey(spaceId: string): string[] {
function syncAllMemories (line 20) | async function syncAllMemories(spaceId: string): Promise<Memory[]> {
function loadSourceMemories (line 63) | async function loadSourceMemories(spaceId: string): Promise<Memory[]> {
function useSourceMemories (line 76) | function useSourceMemories(
FILE: dashboard/app/src/components/lang-toggle.tsx
constant LANGS (line 11) | const LANGS = [
function LangToggle (line 16) | function LangToggle() {
FILE: dashboard/app/src/components/pixel-farm/actor-preview-panel.tsx
type PixelFarmActorPreviewPanelProps (line 28) | interface PixelFarmActorPreviewPanelProps {
constant DIRECTION_OPTIONS (line 37) | const DIRECTION_OPTIONS = ["left", "right"] as const;
function humanizeLabel (line 39) | function humanizeLabel(value: string): string {
function radioOptions (line 46) | function radioOptions(values: readonly string[]): Array<{ label: string;...
function defaultStateForType (line 53) | function defaultStateForType(
function variantOptionsForType (line 67) | function variantOptionsForType(type: PixelFarmDebugActorType): readonly ...
function stateOptionsForType (line 80) | function stateOptionsForType(type: PixelFarmDebugActorType): readonly st...
function directionOptionsForType (line 93) | function directionOptionsForType(type: PixelFarmDebugActorType): readonl...
function PixelFarmActorPreviewPanel (line 97) | function PixelFarmActorPreviewPanel({
type RadioChipGroupProps (line 258) | interface RadioChipGroupProps {
function RadioChipGroup (line 266) | function RadioChipGroup({ label, name, onChange, options, value }: Radio...
FILE: dashboard/app/src/components/pixel-farm/feedback-dialog.tsx
type FeedbackType (line 6) | type FeedbackType = "bug" | "suggestion" | "other";
function PixelFarmFeedbackDialog (line 8) | function PixelFarmFeedbackDialog() {
FILE: dashboard/app/src/components/pixel-farm/front-target-panel.tsx
type PixelFarmFrontTargetPanelProps (line 5) | interface PixelFarmFrontTargetPanelProps {
function formatTile (line 9) | function formatTile(
function PixelFarmFrontTargetPanel (line 19) | function PixelFarmFrontTargetPanel({
FILE: dashboard/app/src/components/pixel-farm/phaser-stage.tsx
type PhaserStageProps (line 31) | interface PhaserStageProps {
function createFallbackNpcDialogContent (line 44) | function createFallbackNpcDialogContent(): PixelFarmNpcDialogContentState {
function playBubbleAppearSound (line 56) | function playBubbleAppearSound(
function PhaserStage (line 88) | function PhaserStage({
FILE: dashboard/app/src/components/pixel-farm/pointer-coordinates-panel.tsx
type PixelFarmPointerCoordinatesPanelProps (line 3) | interface PixelFarmPointerCoordinatesPanelProps {
function formatTile (line 7) | function formatTile(
function PixelFarmPointerCoordinatesPanel (line 17) | function PixelFarmPointerCoordinatesPanel({
FILE: dashboard/app/src/components/pixel-farm/world-state-panel.tsx
type PixelFarmWorldStatePanelProps (line 5) | interface PixelFarmWorldStatePanelProps {
function formatBuckets (line 10) | function formatBuckets(count: number): string {
function formatPlantCount (line 14) | function formatPlantCount(count: number): string {
function PixelFarmWorldStatePanel (line 18) | function PixelFarmWorldStatePanel({
FILE: dashboard/app/src/components/space/add-dialog.tsx
function AddMemoryDialog (line 15) | function AddMemoryDialog({
FILE: dashboard/app/src/components/space/analysis-panel.test.tsx
function createFacetStats (line 22) | function createFacetStats(
function createSnapshot (line 31) | function createSnapshot(
function createState (line 103) | function createState(
FILE: dashboard/app/src/components/space/analysis-panel.tsx
constant TERMINAL_SNAPSHOT_STATUSES (line 24) | const TERMINAL_SNAPSHOT_STATUSES = new Set([
constant COLLAPSED_CARD_LIMIT (line 31) | const COLLAPSED_CARD_LIMIT = 5;
constant COLLAPSED_FACET_LIMIT (line 32) | const COLLAPSED_FACET_LIMIT = 8;
function humanizeCategory (line 34) | function humanizeCategory(category: AnalysisCategory): string {
function formatCategoryLabel (line 42) | function formatCategoryLabel(t: TFunction, category: AnalysisCategory): ...
function formatPhaseLabel (line 48) | function formatPhaseLabel(t: TFunction, phase: SpaceAnalysisState["phase...
function getFacetStats (line 52) | function getFacetStats(
function getTagStatsFromState (line 89) | function getTagStatsFromState(
function getDisplayedBatchProgress (line 95) | function getDisplayedBatchProgress(
function formatBatchSummary (line 145) | function formatBatchSummary(
function AnalysisPanel (line 172) | function AnalysisPanel({
function AnalysisPanelBody (line 241) | function AnalysisPanelBody({
function MetricCard (line 534) | function MetricCard({ label, value }: { label: string; value: string }) {
function FacetSection (line 545) | function FacetSection({
function InlineCollapsibleSection (line 638) | function InlineCollapsibleSection({
FILE: dashboard/app/src/components/space/deep-analysis-overlay.tsx
function DeepAnalysisOverlay (line 8) | function DeepAnalysisOverlay({ active }: { active: boolean }) {
FILE: dashboard/app/src/components/space/deep-analysis-tab.tsx
constant TERMINAL_REPORT_STATUSES (line 44) | const TERMINAL_REPORT_STATUSES = new Set(["COMPLETED", "FAILED"]);
constant ACTIVE_DUPLICATE_CLEANUP_STATUSES (line 45) | const ACTIVE_DUPLICATE_CLEANUP_STATUSES = new Set(["QUEUED", "RUNNING"]);
function formatDateTime (line 47) | function formatDateTime(value: string, locale: string): string {
function isRecord (line 56) | function isRecord(value: unknown): value is Record<string, unknown> {
function isFiniteNumber (line 60) | function isFiniteNumber(value: unknown): value is number {
function toStringOrNull (line 64) | function toStringOrNull(value: unknown): string | null {
function toStringArray (line 68) | function toStringArray(value: unknown): string[] {
function normalizeEntityGroups (line 74) | function normalizeEntityGroups(value: unknown): DeepAnalysisEntityGroup[] {
function normalizeThemeHighlights (line 98) | function normalizeThemeHighlights(value: unknown): DeepAnalysisThemeItem...
function getOverviewTimeSpan (line 123) | function getOverviewTimeSpan(report: DeepAnalysisReportDetail): {
function getDuplicateRatio (line 137) | function getDuplicateRatio(report: DeepAnalysisReportDetail): number {
function getNoisyMemoryCount (line 152) | function getNoisyMemoryCount(report: DeepAnalysisReportDetail): number {
function countDuplicateMemories (line 163) | function countDuplicateMemories(
function getDuplicateCleanupStatus (line 182) | function getDuplicateCleanupStatus(
function isDuplicateCleanupPending (line 188) | function isDuplicateCleanupPending(
function getDuplicateCleanupFeedback (line 194) | function getDuplicateCleanupFeedback(
function triggerBlobDownload (line 236) | function triggerBlobDownload(blob: Blob, filename: string) {
function ReportSection (line 245) | function ReportSection({
constant WORD_CLOUD_COLORS (line 267) | const WORD_CLOUD_COLORS = [
function seededRandom (line 285) | function seededRandom(seed: number): () => number {
function EntityWordCloud (line 293) | function EntityWordCloud({
function RelationshipList (line 373) | function RelationshipList({
constant PERSONA_SECTION_COLORS (line 418) | const PERSONA_SECTION_COLORS: Record<string, string> = {
function PersonaList (line 428) | function PersonaList({
function EvidenceList (line 458) | function EvidenceList({
function DiscoveryCardList (line 496) | function DiscoveryCardList({
function ReportDetail (line 522) | function ReportDetail({
function DeepAnalysisTab (line 806) | function DeepAnalysisTab({
FILE: dashboard/app/src/components/space/delete-dialog.tsx
function DeleteDialog (line 14) | function DeleteDialog({
FILE: dashboard/app/src/components/space/edit-dialog.tsx
function EditMemoryDialog (line 16) | function EditMemoryDialog({
FILE: dashboard/app/src/components/space/empty-state.tsx
function EmptyState (line 5) | function EmptyState({
FILE: dashboard/app/src/components/space/export-dialog.tsx
function ExportDialog (line 14) | function ExportDialog({
FILE: dashboard/app/src/components/space/import-dialog.tsx
constant MAX_FILE_SIZE (line 13) | const MAX_FILE_SIZE = 50 * 1024 * 1024;
function ImportDialog (line 15) | function ImportDialog({
FILE: dashboard/app/src/components/space/import-status.tsx
constant STATUS_CONFIG (line 19) | const STATUS_CONFIG: Record<
function ImportStatusDialog (line 29) | function ImportStatusDialog({
function ImportTaskRow (line 79) | function ImportTaskRow({
FILE: dashboard/app/src/components/space/memory-card.test.tsx
function createMemory (line 8) | function createMemory(sessionID = ""): Memory {
FILE: dashboard/app/src/components/space/memory-composition-chart.tsx
type RingSegment (line 12) | interface RingSegment extends PulseCompositionSegment {
function humanizeLabelToken (line 19) | function humanizeLabelToken(value: string): string {
function buildCompositionLabels (line 27) | function buildCompositionLabels(
function polarToCartesian (line 56) | function polarToCartesian(
function describeArc (line 69) | function describeArc(
function buildRingSegments (line 94) | function buildRingSegments(
function MemoryCompositionChart (line 141) | function MemoryCompositionChart({
FILE: dashboard/app/src/components/space/memory-farm-preparation-dialog.tsx
function MemoryFarmPreparationDialog (line 15) | function MemoryFarmPreparationDialog({
FILE: dashboard/app/src/components/space/memory-farm-promo-card.tsx
function MemoryFarmPromoCard (line 5) | function MemoryFarmPromoCard({
FILE: dashboard/app/src/components/space/memory-insight-layout.test.ts
function rectsOverlap (line 11) | function rectsOverlap(
FILE: dashboard/app/src/components/space/memory-insight-layout.ts
type InsightPoint (line 1) | type InsightPoint = {
type InsightCircleItem (line 6) | type InsightCircleItem = {
type InsightRectItem (line 13) | type InsightRectItem = {
type PackedRootBubbles (line 19) | type PackedRootBubbles = {
type PackedLaneColumn (line 24) | type PackedLaneColumn = {
type PackedLaneAnchors (line 29) | type PackedLaneAnchors = {
type CanvasRect (line 35) | type CanvasRect = {
type CanvasBounds (line 42) | type CanvasBounds = {
type PlacedCircle (line 47) | type PlacedCircle = InsightCircleItem & InsightPoint & {
type PlacedRect (line 51) | type PlacedRect = InsightRectItem & InsightPoint;
constant ROOT_PADDING (line 53) | const ROOT_PADDING = 24;
constant ROOT_GUTTER (line 54) | const ROOT_GUTTER = 18;
constant ROOT_SEARCH_STEP (line 55) | const ROOT_SEARCH_STEP = 14;
constant ROOT_MIN_HEIGHT (line 56) | const ROOT_MIN_HEIGHT = 240;
constant COLUMN_PADDING (line 58) | const COLUMN_PADDING = 12;
constant COLUMN_GAP (line 59) | const COLUMN_GAP = 12;
constant COLUMN_SEARCH_STEP (line 60) | const COLUMN_SEARCH_STEP = 10;
constant COLUMN_MIN_HEIGHT (line 61) | const COLUMN_MIN_HEIGHT = 96;
constant LANE_MIN_HEIGHT (line 62) | const LANE_MIN_HEIGHT = 180;
constant CANVAS_PADDING (line 63) | const CANVAS_PADDING = 40;
function clamp (line 65) | function clamp(value: number, min: number, max: number): number {
function hashKey (line 69) | function hashKey(value: string): number {
function circleIntersects (line 78) | function circleIntersects(
function rectIntersects (line 91) | function rectIntersects(
function generateSearchOffsets (line 104) | function generateSearchOffsets(step: number, maxDistance: number): Insig...
constant ROOT_OFFSETS (line 122) | const ROOT_OFFSETS = generateSearchOffsets(ROOT_SEARCH_STEP, 960);
constant COLUMN_OFFSETS (line 123) | const COLUMN_OFFSETS = generateSearchOffsets(COLUMN_SEARCH_STEP, 720);
function buildInterleavedOrder (line 125) | function buildInterleavedOrder(size: number): number[] {
function buildRootScatterPlan (line 148) | function buildRootScatterPlan({
function resolveCirclePosition (line 266) | function resolveCirclePosition({
function resolveRectPosition (line 327) | function resolveRectPosition({
function packRootBubbles (line 380) | function packRootBubbles({
function resolveRootBubbleDrop (line 445) | function resolveRootBubbleDrop({
function layoutLaneColumn (line 483) | function layoutLaneColumn({
function resolveLaneNodeDrop (line 550) | function resolveLaneNodeDrop({
function layoutLaneAnchors (line 581) | function layoutLaneAnchors({
function computeCanvasBounds (line 615) | function computeCanvasBounds({
FILE: dashboard/app/src/components/space/memory-insight-overview.test.tsx
function createMemory (line 108) | function createMemory(id: string, content: string, tags: string[]): Memo...
function renderInsight (line 126) | function renderInsight({
function createDenseRootRelationFixture (line 153) | function createDenseRootRelationFixture(): {
FILE: dashboard/app/src/components/space/memory-insight-overview.tsx
type InsightRenderableKind (line 35) | type InsightRenderableKind = MemoryInsightNodeKind | "more";
type LanePath (line 37) | type LanePath = {
type LaneRenderableItem (line 42) | type LaneRenderableItem = {
type DragState (line 61) | type DragState = {
type PanState (line 76) | type PanState = {
type PositionedNode (line 85) | type PositionedNode = LaneRenderableItem & {
type InsightPerformanceMode (line 90) | type InsightPerformanceMode = "full" | "reduced";
type RootBubbleRelationEdge (line 92) | type RootBubbleRelationEdge = {
type RootRelationRenderableEdge (line 101) | type RootRelationRenderableEdge = RootBubbleRelationEdge & {
type SampledPathPoint (line 117) | type SampledPathPoint = InsightPoint & {
type SampledRootRelationEdge (line 121) | type SampledRootRelationEdge = RootRelationRenderableEdge & {
constant DRIFT_SEEDS (line 129) | const DRIFT_SEEDS = [
constant BUBBLE_COLOR_PALETTE (line 138) | const BUBBLE_COLOR_PALETTE = [
constant ROOT_BUBBLE_RANGE (line 149) | const ROOT_BUBBLE_RANGE = {
constant ROOT_BUBBLE_EXPONENT (line 154) | const ROOT_BUBBLE_EXPONENT = {
constant BRANCH_LIMITS (line 159) | const BRANCH_LIMITS = {
constant CANVAS_GAP (line 165) | const CANVAS_GAP = {
constant LANE_COLUMN_WIDTHS (line 170) | const LANE_COLUMN_WIDTHS = {
constant LANE_GAP (line 177) | const LANE_GAP = {
constant ROOT_RELATION_ANIMATION_BUDGET (line 182) | const ROOT_RELATION_ANIMATION_BUDGET = {
constant ROOT_RELATION_MEDIUM_EDGE_THRESHOLD (line 187) | const ROOT_RELATION_MEDIUM_EDGE_THRESHOLD = 32;
constant ROOT_RELATION_DENSE_EDGE_THRESHOLD (line 188) | const ROOT_RELATION_DENSE_EDGE_THRESHOLD = 60;
constant ROOT_RELATION_BASE_DPR_CAP (line 189) | const ROOT_RELATION_BASE_DPR_CAP = 1.5;
constant ROOT_RELATION_DENSE_DPR_CAP (line 190) | const ROOT_RELATION_DENSE_DPR_CAP = 1.25;
constant ROOT_RELATION_HIGHLIGHT_LENGTH_RATIO (line 191) | const ROOT_RELATION_HIGHLIGHT_LENGTH_RATIO = 0.14;
constant ROOT_RELATION_HIGHLIGHT_LENGTH_MIN (line 192) | const ROOT_RELATION_HIGHLIGHT_LENGTH_MIN = 24;
constant ROOT_RELATION_HIGHLIGHT_LENGTH_MAX (line 193) | const ROOT_RELATION_HIGHLIGHT_LENGTH_MAX = 72;
constant ROOT_RELATION_CYCLE_DURATION_MS (line 194) | const ROOT_RELATION_CYCLE_DURATION_MS = {
constant REDUCED_MOTION_MEDIA_QUERY (line 198) | const REDUCED_MOTION_MEDIA_QUERY = "(prefers-reduced-motion: reduce)";
function previewMemoryContent (line 200) | function previewMemoryContent(memory: Memory): string {
function normalizeInlineText (line 207) | function normalizeInlineText(value: string): string {
function hashString (line 211) | function hashString(value: string): number {
function seededUnitInterval (line 220) | function seededUnitInterval(value: string): number {
function seededRange (line 224) | function seededRange(value: string, min: number, max: number): number {
function roundSeed (line 228) | function roundSeed(value: number, digits = 2): number {
function bubbleDiameter (line 232) | function bubbleDiameter(count: number, maxCount: number, compact: boolea...
function nodeDimensions (line 241) | function nodeDimensions(
function createBubbleMotionStyle (line 283) | function createBubbleMotionStyle(id: string): CSSProperties {
function bubbleToneColor (line 307) | function bubbleToneColor(category: string): string {
function mixHexColors (line 313) | function mixHexColors(left: string, right: string, ratio = 0.5): string {
function bubbleSizeTier (line 329) | function bubbleSizeTier(diameter?: number): "small" | "medium" | "large"...
function clamp (line 345) | function clamp(value: number, min: number, max: number): number {
function hexToRgba (line 349) | function hexToRgba(hex: string, alpha: number): string {
function getRootRelationAnimationBudget (line 360) | function getRootRelationAnimationBudget(edgeCount: number, prefersReduce...
function getRootRelationEffectiveDpr (line 376) | function getRootRelationEffectiveDpr(
function getRootRelationHighlightLength (line 386) | function getRootRelationHighlightLength(pathLength: number): number {
function quadraticBezierPoint (line 394) | function quadraticBezierPoint(
function sampleBezierPath (line 410) | function sampleBezierPath(
function pointAtDistance (line 445) | function pointAtDistance(
function collectPathSegmentPoints (line 483) | function collectPathSegmentPoints(
function strokePolyline (line 509) | function strokePolyline(
function configureCanvasContext (line 525) | function configureCanvasContext(
function drawBaseEdges (line 553) | function drawBaseEdges(
function drawAnimatedEdges (line 592) | function drawAnimatedEdges(
function rootSpreadWidth (line 672) | function rootSpreadWidth(viewportWidth: number, compact: boolean, canvas...
function getBranchLimit (line 679) | function getBranchLimit(kind: keyof typeof BRANCH_LIMITS, compact: boole...
function sortMemoryNodes (line 683) | function sortMemoryNodes(memoryNodes: MemoryInsightMemoryNode[]): Memory...
function buildRootBubbleRelationEdges (line 692) | function buildRootBubbleRelationEdges(input: {
function omitKeys (line 791) | function omitKeys<T extends Record<string, unknown>>(record: T, keys: st...
function useElementWidth (line 803) | function useElementWidth<T extends HTMLElement>(): [React.RefObject<T | ...
function usePrefersReducedMotion (line 827) | function usePrefersReducedMotion(): boolean {
function InsightNodeButton (line 855) | function InsightNodeButton({
function MemoryInsightCanvas (line 1027) | function MemoryInsightCanvas({
function draftLaneKey (line 2423) | function draftLaneKey(
function MemoryInsightOverview (line 2448) | function MemoryInsightOverview(props: {
FILE: dashboard/app/src/components/space/memory-insight-relations.tsx
type InsightPoint (line 43) | type InsightPoint = {
type DragState (line 48) | type DragState = {
type PanState (line 61) | type PanState = {
type DisplayNode (line 70) | type DisplayNode = {
type StrengthPreset (line 78) | type StrengthPreset = "all" | "medium" | "strong";
constant ENTITY_LIMIT (line 80) | const ENTITY_LIMIT = 30;
constant EDGE_LIMIT (line 81) | const EDGE_LIMIT = 80;
constant RING_RADII (line 82) | const RING_RADII = [0.18, 0.3, 0.42] as const;
constant RELATION_COLORS (line 83) | const RELATION_COLORS: Record<MemoryInsightRelationType, string> = {
constant BUBBLE_COLOR_PALETTE (line 91) | const BUBBLE_COLOR_PALETTE = [
constant DRIFT_SEEDS (line 101) | const DRIFT_SEEDS = [
constant DESKTOP_MEDIA_QUERY (line 109) | const DESKTOP_MEDIA_QUERY = "(min-width: 1200px)";
function clamp (line 111) | function clamp(value: number, min: number, max: number): number {
function hashString (line 115) | function hashString(value: string): number {
function useElementWidth (line 124) | function useElementWidth<T extends HTMLElement>(): [React.RefObject<T | ...
function useIsDesktopViewport (line 148) | function useIsDesktopViewport(): boolean {
function seededUnitInterval (line 168) | function seededUnitInterval(value: string): number {
function seededRange (line 172) | function seededRange(value: string, min: number, max: number): number {
function roundSeed (line 176) | function roundSeed(value: number, digits = 2): number {
function bubbleToneColor (line 180) | function bubbleToneColor(label: string): string {
function entityDiameter (line 184) | function entityDiameter(count: number, maxCount: number): number {
function entityNodeDimensions (line 189) | function entityNodeDimensions(count: number, maxCount: number): { diamet...
function createBubbleMotionStyle (line 195) | function createBubbleMotionStyle(id: string): CSSProperties {
function bubbleSizeTier (line 219) | function bubbleSizeTier(diameter: number): "small" | "medium" | "large" {
function previewMemoryContent (line 225) | function previewMemoryContent(memory: Memory): string {
function strengthThreshold (line 231) | function strengthThreshold(preset: StrengthPreset): number {
function computeDateLabel (line 242) | function computeDateLabel(value: string, locale: string): string {
function computeGlobalLayout (line 249) | function computeGlobalLayout(
function computeFocusedLayout (line 286) | function computeFocusedLayout(
function buildDisplayGraph (line 369) | function buildDisplayGraph(
function DetailSection (line 415) | function DetailSection({
function SummaryRow (line 432) | function SummaryRow({
function RelationshipTypeBadge (line 447) | function RelationshipTypeBadge({
function RelationDetailPanel (line 467) | function RelationDetailPanel({
function MemoryInsightRelations (line 854) | function MemoryInsightRelations({
FILE: dashboard/app/src/components/space/memory-insight-workspace.test.tsx
function createMemory (line 9) | function createMemory(
function createMatch (line 32) | function createMatch(memoryId: string, categories: string[]): MemoryAnal...
function setViewport (line 40) | function setViewport(desktop: boolean): void {
function createBaseData (line 70) | function createBaseData() {
FILE: dashboard/app/src/components/space/memory-insight-workspace.tsx
function MemoryInsightWorkspace (line 12) | function MemoryInsightWorkspace({
FILE: dashboard/app/src/components/space/memory-overview-tabs.test.tsx
constant ORIGINAL_INNER_WIDTH (line 23) | const ORIGINAL_INNER_WIDTH = window.innerWidth;
function setViewportWidth (line 25) | function setViewportWidth(width: number): void {
function createMemory (line 37) | function createMemory(id: string): Memory {
FILE: dashboard/app/src/components/space/memory-overview-tabs.tsx
type OverviewMemorySelectionSource (line 20) | type OverviewMemorySelectionSource = "list" | "insight";
constant TAB_VALUES (line 22) | const TAB_VALUES = ["pulse", "insight", "analysis"] as const;
function MemoryOverviewTabs (line 24) | function MemoryOverviewTabs({
function DesktopOverviewTabsList (line 158) | function DesktopOverviewTabsList() {
function MobileOverviewTabsList (line 185) | function MobileOverviewTabsList() {
function MemoryInsightDesktopOnlyHint (line 214) | function MemoryInsightDesktopOnlyHint() {
FILE: dashboard/app/src/components/space/memory-pulse-overview.tsx
function PulseOverviewSkeleton (line 12) | function PulseOverviewSkeleton() {
function MemoryPulseOverview (line 49) | function MemoryPulseOverview({
FILE: dashboard/app/src/components/space/memory-rhythm-chart.tsx
function formatBucketLabel (line 6) | function formatBucketLabel(
function MemoryRhythmChart (line 47) | function MemoryRhythmChart({
FILE: dashboard/app/src/components/space/memory-signal-stack.tsx
function MemorySignalStack (line 5) | function MemorySignalStack({
FILE: dashboard/app/src/components/space/mobile-analysis-sheet.tsx
function MobileAnalysisSheet (line 12) | function MobileAnalysisSheet({
FILE: dashboard/app/src/components/space/mobile-detail-sheet.test.tsx
function createMemory (line 13) | function createMemory(): Memory {
function createSessionMessage (line 31) | function createSessionMessage(): SessionMessage {
function createToolResultMessage (line 67) | function createToolResultMessage(): SessionMessage {
method get (line 215) | get() {
FILE: dashboard/app/src/components/space/mobile-panel-shell.tsx
type MobilePanelVariant (line 18) | type MobilePanelVariant = "side-drawer" | "responsive-sheet";
constant SIDE_DRAWER_CLASSNAME (line 20) | const SIDE_DRAWER_CLASSNAME = cn(
constant RESPONSIVE_SHEET_CLASSNAME (line 27) | const RESPONSIVE_SHEET_CLASSNAME = cn(
function MobilePanelShell (line 52) | function MobilePanelShell({
FILE: dashboard/app/src/components/space/session-preview.tsx
type SessionContentBlock (line 10) | type SessionContentBlock =
constant UNTRUSTED_METADATA_PATTERN (line 22) | const UNTRUSTED_METADATA_PATTERN = /^(.+?\(untrusted metadata\)):\s*$/;
constant FENCED_CODE_BLOCK_PATTERN (line 23) | const FENCED_CODE_BLOCK_PATTERN = /^```[\w-]*\s*$/;
FILE: dashboard/app/src/components/space/space-page-layout.tsx
type SpacePageLayoutProps (line 47) | interface SpacePageLayoutProps {
FILE: dashboard/app/src/components/space/space-selectors.test.ts
function createMemory (line 5) | function createMemory(id: string): Memory {
FILE: dashboard/app/src/components/space/space-selectors.ts
function formatAnalysisCategoryLabel (line 18) | function formatAnalysisCategoryLabel(
function buildStats (line 25) | function buildStats(memories: Memory[]): MemoryStats {
function createTagResolver (line 33) | function createTagResolver(
type TagSummary (line 39) | interface TagSummary {
function buildTagOptions (line 45) | function buildTagOptions(
function formatTimelineLabel (line 81) | function formatTimelineLabel(
function shouldCompactMemoryOverview (line 117) | function shouldCompactMemoryOverview(
function resolveSelectedDetailMode (line 125) | function resolveSelectedDetailMode(
function getActiveFilterCount (line 132) | function getActiveFilterCount(input: {
function getPageShellClass (line 150) | function getPageShellClass(
function selectDisplayedMemories (line 159) | function selectDisplayedMemories(input: {
FILE: dashboard/app/src/components/space/space-tools.tsx
function SpaceTools (line 11) | function SpaceTools({
FILE: dashboard/app/src/components/space/space-view-utils.ts
constant DESKTOP_BREAKPOINT (line 19) | const DESKTOP_BREAKPOINT = 1280;
constant LARGE_BREAKPOINT (line 20) | const LARGE_BREAKPOINT = 1024;
function getIsDesktopViewport (line 22) | function getIsDesktopViewport(): boolean {
function getIsLargeViewport (line 27) | function getIsLargeViewport(): boolean {
function useViewportFlag (line 32) | function useViewportFlag(getter: () => boolean): boolean {
function useIsDesktopViewport (line 47) | function useIsDesktopViewport(): boolean {
function useIsLargeViewport (line 51) | function useIsLargeViewport(): boolean {
function scrollToMemoryList (line 55) | function scrollToMemoryList(): void {
function navigateAndScrollToMemoryList (line 64) | function navigateAndScrollToMemoryList(action: () => void): void {
FILE: dashboard/app/src/components/space/tag-strip.tsx
type TagSummary (line 4) | interface TagSummary {
function TagStrip (line 10) | function TagStrip({
FILE: dashboard/app/src/components/space/time-range.tsx
constant PRESETS (line 4) | const PRESETS: TimeRangePreset[] = ["7d", "30d", "90d", "all"];
function TimeRangeSelector (line 6) | function TimeRangeSelector({
FILE: dashboard/app/src/components/space/topic-strip.tsx
constant FACET_STYLES (line 4) | const FACET_STYLES: Record<MemoryFacet, string> = {
constant FACET_ACTIVE (line 15) | const FACET_ACTIVE: Record<MemoryFacet, string> = {
function TopicStrip (line 26) | function TopicStrip({
function FacetBadge (line 77) | function FacetBadge({
FILE: dashboard/app/src/components/space/use-memory-farm-entry-state.test.ts
function importModules (line 8) | async function importModules() {
FILE: dashboard/app/src/components/space/use-memory-farm-entry-state.ts
type MemoryFarmEntryStatus (line 6) | type MemoryFarmEntryStatus = "ready" | "preparing" | "unavailable";
function resolveMemoryFarmEntryStatus (line 8) | async function resolveMemoryFarmEntryStatus(input: {
function useMemoryFarmEntryState (line 53) | function useMemoryFarmEntryState(
FILE: dashboard/app/src/components/space/use-space-data-model.test.tsx
function createMemory (line 24) | function createMemory(id: string): Memory {
constant SOURCE_MEMORIES (line 42) | const SOURCE_MEMORIES = [createMemory("mem-1"), createMemory("mem-2")];
constant EMPTY_SIGNAL_INDEX (line 43) | const EMPTY_SIGNAL_INDEX: LocalDerivedSignalIndex = {
function primeMocks (line 81) | function primeMocks(): void {
FILE: dashboard/app/src/components/space/use-space-data-model.ts
type SpaceDataModel (line 42) | interface SpaceDataModel {
function useSpaceDataModel (line 78) | function useSpaceDataModel(input: {
FILE: dashboard/app/src/components/space/use-space-route-state.ts
type SpaceSearch (line 26) | type SpaceSearch = ReturnType<typeof route.useSearch>;
type SpaceRouteState (line 28) | interface SpaceRouteState {
function useSpaceRouteState (line 72) | function useSpaceRouteState(spaceId: string): SpaceRouteState {
FILE: dashboard/app/src/components/theme-toggle.tsx
constant OPTIONS (line 12) | const OPTIONS: { value: Theme; icon: typeof Sun; label: string }[] = [
function ThemeToggle (line 18) | function ThemeToggle() {
FILE: dashboard/app/src/components/ui/badge.tsx
function Badge (line 29) | function Badge({
FILE: dashboard/app/src/components/ui/button-group.tsx
function ButtonGroup (line 4) | function ButtonGroup({
function ButtonGroupSeparator (line 26) | function ButtonGroupSeparator({
FILE: dashboard/app/src/components/ui/button.tsx
function Button (line 41) | function Button({
FILE: dashboard/app/src/components/ui/dialog.tsx
function Dialog (line 8) | function Dialog({
function DialogTrigger (line 14) | function DialogTrigger({
function DialogPortal (line 20) | function DialogPortal({
function DialogClose (line 26) | function DialogClose({
function DialogOverlay (line 32) | function DialogOverlay({
function DialogContent (line 48) | function DialogContent({
function DialogHeader (line 84) | function DialogHeader({ className, ...props }: React.ComponentProps<"div...
function DialogFooter (line 94) | function DialogFooter({
function DialogTitle (line 121) | function DialogTitle({
function DialogDescription (line 134) | function DialogDescription({
FILE: dashboard/app/src/components/ui/dropdown-menu.tsx
function DropdownMenu (line 7) | function DropdownMenu({
function DropdownMenuPortal (line 13) | function DropdownMenuPortal({
function DropdownMenuTrigger (line 21) | function DropdownMenuTrigger({
function DropdownMenuContent (line 32) | function DropdownMenuContent({
function DropdownMenuGroup (line 52) | function DropdownMenuGroup({
function DropdownMenuItem (line 60) | function DropdownMenuItem({
function DropdownMenuCheckboxItem (line 83) | function DropdownMenuCheckboxItem({
function DropdownMenuRadioGroup (line 109) | function DropdownMenuRadioGroup({
function DropdownMenuRadioItem (line 120) | function DropdownMenuRadioItem({
function DropdownMenuLabel (line 144) | function DropdownMenuLabel({
function DropdownMenuSeparator (line 164) | function DropdownMenuSeparator({
function DropdownMenuShortcut (line 177) | function DropdownMenuShortcut({
function DropdownMenuSub (line 193) | function DropdownMenuSub({
function DropdownMenuSubTrigger (line 199) | function DropdownMenuSubTrigger({
function DropdownMenuSubContent (line 223) | function DropdownMenuSubContent({
FILE: dashboard/app/src/components/ui/input.tsx
function Input (line 5) | function Input({ className, type, ...props }: React.ComponentProps<"inpu...
FILE: dashboard/app/src/components/ui/progress.tsx
function Progress (line 8) | function Progress({
FILE: dashboard/app/src/components/ui/select.tsx
function Select (line 7) | function Select({
function SelectGroup (line 13) | function SelectGroup({
function SelectValue (line 19) | function SelectValue({
function SelectTrigger (line 25) | function SelectTrigger({
function SelectContent (line 51) | function SelectContent({
function SelectLabel (line 88) | function SelectLabel({
function SelectItem (line 101) | function SelectItem({
function SelectSeparator (line 128) | function SelectSeparator({
function SelectScrollUpButton (line 141) | function SelectScrollUpButton({
function SelectScrollDownButton (line 159) | function SelectScrollDownButton({
FILE: dashboard/app/src/components/ui/switch.tsx
type SwitchProps (line 3) | interface SwitchProps {
function Switch (line 10) | function Switch({ checked, className, disabled = false, onCheckedChange ...
FILE: dashboard/app/src/components/ui/tabs.tsx
function Tabs (line 9) | function Tabs({
function TabsList (line 43) | function TabsList({
function TabsTrigger (line 59) | function TabsTrigger({
function TabsContent (line 78) | function TabsContent({
FILE: dashboard/app/src/i18n/index.ts
constant STORAGE_KEY (line 6) | const STORAGE_KEY = "mem9-locale";
function detectLocale (line 8) | function detectLocale(): string {
FILE: dashboard/app/src/lib/connect-bootstrap.ts
type ConnectBootstrapState (line 1) | interface ConnectBootstrapState {
type ConnectBootstrapParseResult (line 7) | interface ConnectBootstrapParseResult {
type ConnectBootstrapInitOptions (line 13) | interface ConnectBootstrapInitOptions {
constant EMPTY_CONNECT_BOOTSTRAP_STATE (line 18) | const EMPTY_CONNECT_BOOTSTRAP_STATE: ConnectBootstrapState = {
function normalizeBootstrapParam (line 27) | function normalizeBootstrapParam(value: string | null): string | null {
function buildRelativeURL (line 36) | function buildRelativeURL(url: URL): string {
function parseConnectBootstrapFromLocation (line 40) | function parseConnectBootstrapFromLocation(
function initializeConnectBootstrapFromLocation (line 62) | function initializeConnectBootstrapFromLocation(
function consumeConnectBootstrap (line 89) | function consumeConnectBootstrap(): ConnectBootstrapState {
function resetConnectBootstrapForTests (line 95) | function resetConnectBootstrapForTests(): void {
FILE: dashboard/app/src/lib/ga4.ts
constant GA4_MEASUREMENT_ID (line 1) | const GA4_MEASUREMENT_ID = import.meta.env.VITE_GA4_MEASUREMENT_ID?.trim...
constant GA4_SCRIPT_ID (line 2) | const GA4_SCRIPT_ID = "mem9-ga4-script";
type Window (line 8) | interface Window {
function gtag (line 14) | function gtag(...args: unknown[]): void {
function initGa4 (line 19) | function initGa4(): void {
function trackGa4PageView (line 43) | function trackGa4PageView(pathname: string, search = ""): void {
FILE: dashboard/app/src/lib/memory-derived-signals.test.ts
function createMemory (line 11) | function createMemory(id: string, overrides: Partial<Memory> = {}): Memo...
function createMatch (line 30) | function createMatch(memoryId: string, categories: string[]): MemoryAnal...
FILE: dashboard/app/src/lib/memory-derived-signals.ts
type DerivedTagOrigin (line 10) | type DerivedTagOrigin = "raw" | "derived" | "mixed";
type DerivedTagSource (line 11) | type DerivedTagSource = "structured" | "named_term" | "segmented";
type DerivedSignalMemory (line 12) | type DerivedSignalMemory = Pick<Memory, "id" | "content" | "tags">;
type MemoryDerivedTagCandidate (line 14) | interface MemoryDerivedTagCandidate {
type MemoryDerivedAnalysis (line 20) | interface MemoryDerivedAnalysis {
type LocalDerivedTagStat (line 26) | interface LocalDerivedTagStat {
type LocalDerivedSignalIndex (line 33) | interface LocalDerivedSignalIndex {
type BuildLocalDerivedSignalIndexInput (line 40) | interface BuildLocalDerivedSignalIndexInput {
type CandidateAggregate (line 46) | interface CandidateAggregate {
type TagAggregate (line 53) | interface TagAggregate {
constant MAX_DERIVED_TAGS_PER_MEMORY (line 61) | const MAX_DERIVED_TAGS_PER_MEMORY = 2;
constant SOURCE_PRIORITY (line 62) | const SOURCE_PRIORITY: Record<DerivedTagSource, number> = {
constant FILE_EXTENSION_TOKENS (line 67) | const FILE_EXTENSION_TOKENS = new Set([
constant DERIVED_TAG_STOPWORDS (line 88) | const DERIVED_TAG_STOPWORDS = new Set([
function normalizeDerivedTagValue (line 236) | function normalizeDerivedTagValue(value: string): string {
function cleanDisplayValue (line 245) | function cleanDisplayValue(value: string): string {
function normalizeCategories (line 252) | function normalizeCategories(
function containsCJK (line 259) | function containsCJK(value: string): boolean {
function isNumericLike (line 263) | function isNumericLike(value: string): boolean {
function isDateOrTimeLike (line 268) | function isDateOrTimeLike(value: string): boolean {
function looksLikeFileExtension (line 273) | function looksLikeFileExtension(value: string): boolean {
function isPersonLikeValue (line 277) | function isPersonLikeValue(value: string, personLikeLabels: Set<string>)...
function isMeaningfulSegment (line 286) | function isMeaningfulSegment(value: string): boolean {
type SegmenterLike (line 307) | type SegmenterLike = {
function getSegmenter (line 314) | function getSegmenter(): SegmenterLike | null {
function extractSegmentCandidates (line 329) | function extractSegmentCandidates(content: string): string[] {
function extractStructuredCandidates (line 347) | function extractStructuredCandidates(content: string): string[] {
function addCandidate (line 393) | function addCandidate(
function collectMemoryCandidates (line 419) | function collectMemoryCandidates(memory: Pick<Memory, "content">): Memor...
function createMemoryDerivedAnalysis (line 445) | function createMemoryDerivedAnalysis(memory: DerivedSignalMemory): Memor...
function incrementCount (line 453) | function incrementCount(map: Map<string, number>, key: string): void {
function getTopDisplayLabel (line 457) | function getTopDisplayLabel(displayCounts: Map<string, number>, fallback...
function getMaxCategoryCount (line 465) | function getMaxCategoryCount(categoryCounts: Map<string, number>): number {
function buildTagStats (line 469) | function buildTagStats(
function mergeRawAndDerivedTags (line 542) | function mergeRawAndDerivedTags(rawTags: string[], derivedTags: string[]...
function buildLocalDerivedSignalIndex (line 559) | function buildLocalDerivedSignalIndex(
function getCombinedTagsForMemory (line 657) | function getCombinedTagsForMemory(
function getDerivedTagsForMemory (line 665) | function getDerivedTagsForMemory(
function getDerivedTagOrigin (line 672) | function getDerivedTagOrigin(
FILE: dashboard/app/src/lib/memory-filters.test.ts
constant FIXED_NOW (line 10) | const FIXED_NOW = new Date("2026-03-21T12:00:00Z");
function createMemory (line 12) | function createMemory(overrides: Partial<Memory> = {}): Memory {
FILE: dashboard/app/src/lib/memory-filters.ts
type MemoryTagResolver (line 11) | type MemoryTagResolver = (memory: Memory) => string[];
function parseTimestamp (line 13) | function parseTimestamp(value: string): number | null {
function sortMemoriesByCreatedAtDesc (line 18) | function sortMemoriesByCreatedAtDesc(memories: Memory[]): Memory[] {
function memoryMatchesRange (line 26) | function memoryMatchesRange(
function memoryMatchesTimeline (line 38) | function memoryMatchesTimeline(
function resolveMemoryTags (line 54) | function resolveMemoryTags(
function memoryMatchesQuery (line 61) | function memoryMatchesQuery(
function memoryMatchesTag (line 78) | function memoryMatchesTag(
function memoryMatchesType (line 92) | function memoryMatchesType(
function memoryMatchesFacet (line 100) | function memoryMatchesFacet(
function filterMemoriesForView (line 108) | function filterMemoriesForView(
FILE: dashboard/app/src/lib/memory-insight-background.test.ts
function createMemory (line 8) | function createMemory(): Memory {
FILE: dashboard/app/src/lib/memory-insight-background.ts
type InsightWorkerMemory (line 21) | interface InsightWorkerMemory {
type WorkerRequest (line 29) | type WorkerRequest =
type WorkerResult (line 61) | type WorkerResult =
type WorkerResponse (line 66) | type WorkerResponse =
constant EMPTY_LOCAL_DERIVED_SIGNAL_INDEX (line 78) | const EMPTY_LOCAL_DERIVED_SIGNAL_INDEX: LocalDerivedSignalIndex = {
constant EMPTY_MEMORY_INSIGHT_GRAPH (line 85) | const EMPTY_MEMORY_INSIGHT_GRAPH: MemoryInsightGraph = {
constant EMPTY_MEMORY_INSIGHT_RELATION_GRAPH (line 94) | const EMPTY_MEMORY_INSIGHT_RELATION_GRAPH: MemoryInsightRelationGraph = {
constant DEFAULT_DERIVED_SIGNALS_MINIMUM_MEMORY_COUNT (line 107) | const DEFAULT_DERIVED_SIGNALS_MINIMUM_MEMORY_COUNT = 80;
function shouldUseBackgroundWorker (line 119) | function shouldUseBackgroundWorker(): boolean {
function projectInsightWorkerMemory (line 125) | function projectInsightWorkerMemory(memory: Memory): InsightWorkerMemory {
function shouldUseDerivedSignalsWorker (line 135) | function shouldUseDerivedSignalsWorker(input: {
function getWorker (line 151) | function getWorker(): Worker {
function runWorkerTask (line 187) | function runWorkerTask<T extends WorkerResult>(
function useBackgroundComputation (line 203) | function useBackgroundComputation<T extends WorkerResult>({
function useBackgroundDerivedSignals (line 274) | function useBackgroundDerivedSignals({
function useBackgroundMemoryInsightGraph (line 315) | function useBackgroundMemoryInsightGraph({
function useBackgroundMemoryInsightRelationGraph (line 348) | function useBackgroundMemoryInsightRelationGraph({
FILE: dashboard/app/src/lib/memory-insight-background.worker.ts
type InsightWorkerMemory (line 24) | interface InsightWorkerMemory {
type WorkerRequest (line 32) | type WorkerRequest =
type WorkerResult (line 64) | type WorkerResult =
type WorkerResponse (line 69) | type WorkerResponse =
constant MAX_MEMORY_ANALYSIS_CACHE (line 81) | const MAX_MEMORY_ANALYSIS_CACHE = 2048;
constant MAX_RESULT_CACHE (line 82) | const MAX_RESULT_CACHE = 128;
function stableHash (line 89) | function stableHash(value: string): string {
function setBoundedCache (line 100) | function setBoundedCache<T>(cache: Map<string, T>, key: string, value: T...
function createMemoryAnalysisKey (line 116) | function createMemoryAnalysisKey(
function createMatchesKey (line 132) | function createMatchesKey(matches: MemoryAnalysisMatch[]): string {
function createCardsKey (line 143) | function createCardsKey(cards: AnalysisCategoryCard[]): string {
function createMemorySetKey (line 152) | function createMemorySetKey(
function buildSignalKey (line 166) | function buildSignalKey(memories: InsightWorkerMemory[], matches: Memory...
function buildInsightKey (line 170) | function buildInsightKey(
function buildRelationKey (line 178) | function buildRelationKey(
function getMemoryAnalyses (line 200) | function getMemoryAnalyses(memories: InsightWorkerMemory[]): MemoryDeriv...
function getOrBuildDerivedSignals (line 217) | function getOrBuildDerivedSignals(
function getOrBuildInsightGraph (line 236) | function getOrBuildInsightGraph(
function getOrBuildRelationGraph (line 258) | function getOrBuildRelationGraph(
FILE: dashboard/app/src/lib/memory-insight-entities.ts
type MemoryInsightEntityKind (line 3) | type MemoryInsightEntityKind =
type MemoryInsightEntityHit (line 9) | interface MemoryInsightEntityHit {
constant ENTITY_KIND_ORDER (line 16) | const ENTITY_KIND_ORDER: Record<MemoryInsightEntityKind, number> = {
function normalizeLabel (line 23) | function normalizeLabel(value: string): string {
function addEntityHit (line 27) | function addEntityHit(
function extractMemoryInsightEntities (line 54) | function extractMemoryInsightEntities(
FILE: dashboard/app/src/lib/memory-insight-relations.test.ts
function createMemory (line 6) | function createMemory(
function createCard (line 29) | function createCard(category: string, count: number): AnalysisCategoryCa...
function createMatch (line 37) | function createMatch(memoryId: string, categories: string[]): MemoryAnal...
FILE: dashboard/app/src/lib/memory-insight-relations.ts
type MemoryInsightRelationType (line 13) | type MemoryInsightRelationType =
type MemoryInsightRelationEntity (line 21) | interface MemoryInsightRelationEntity {
type MemoryInsightRelationEdge (line 39) | interface MemoryInsightRelationEdge {
type MemoryInsightRelationCluster (line 57) | interface MemoryInsightRelationCluster {
type MemoryInsightRelationGraph (line 64) | interface MemoryInsightRelationGraph {
type BuildInput (line 77) | interface BuildInput {
type EntityAggregate (line 88) | interface EntityAggregate {
type EdgeAggregate (line 101) | interface EdgeAggregate {
constant SIGNIFICANT_ENTITY_MIN_COUNT (line 113) | const SIGNIFICANT_ENTITY_MIN_COUNT = 2;
constant TOP_ENTITY_LIMIT (line 114) | const TOP_ENTITY_LIMIT = 30;
constant TOP_EDGE_LIMIT (line 115) | const TOP_EDGE_LIMIT = 80;
constant RELATION_PRIORITY (line 116) | const RELATION_PRIORITY: MemoryInsightRelationType[] = [
constant DEPENDS_ON_PATTERN (line 124) | const DEPENDS_ON_PATTERN = /\bdepends on\b|\brely on\b|依赖/iu;
constant USED_WITH_PATTERN (line 125) | const USED_WITH_PATTERN = /\bwith\b|\busing\b|\bvia\b|配合|结合/iu;
constant DEPLOYED_TO_PATTERN (line 126) | const DEPLOYED_TO_PATTERN = /\bdeploy(?:ed)? to\b|部署到|发布到/iu;
constant SCHEDULED_WITH_PATTERN (line 127) | const SCHEDULED_WITH_PATTERN = /\bevery\b|\bdaily\b|\bweekly\b|\bcron\b|...
constant POINTS_TO_PATTERN (line 128) | const POINTS_TO_PATTERN = /\bto\b|\bat\b|\bpath\b|\bconfig\b|指向/iu;
function normalizeLabel (line 130) | function normalizeLabel(value: string): string {
function isEligibleEntity (line 134) | function isEligibleEntity(label: string, kind: MemoryInsightEntityKind):...
function getEntityID (line 151) | function getEntityID(kind: MemoryInsightEntityKind, normalizedLabel: str...
function incrementCount (line 155) | function incrementCount(map: Map<string, number>, key: string): void {
function sortedKeysByCount (line 159) | function sortedKeysByCount(map: Map<string, number>): string[] {
function pickDominantCategory (line 168) | function pickDominantCategory(categoryCounts: Map<string, number>): stri...
function computeRangeBounds (line 176) | function computeRangeBounds(memories: Memory[]): { min: number; midpoint...
function computeRecencyScore (line 191) | function computeRecencyScore(timestamp: number, bounds: { min: number; s...
function looksLikePathOrLocation (line 197) | function looksLikePathOrLocation(value: string): boolean {
function chooseRelationType (line 204) | function chooseRelationType(
function pickRelationType (line 243) | function pickRelationType(counts: Map<MemoryInsightRelationType, number>...
function sortMemoryIDs (line 252) | function sortMemoryIDs(memoryIDs: Iterable<string>, memoriesById: Map<st...
function collectCluster (line 268) | function collectCluster(
function buildMemoryInsightRelationGraph (line 312) | function buildMemoryInsightRelationGraph(input: BuildInput): MemoryInsig...
FILE: dashboard/app/src/lib/memory-insight.test.ts
function createMemory (line 14) | function createMemory(id: string, overrides: Partial<Memory> = {}): Memo...
function createCard (line 33) | function createCard(category: string, count: number): AnalysisCategoryCa...
function createMatch (line 41) | function createMatch(
FILE: dashboard/app/src/lib/memory-insight.ts
type MemoryInsightTab (line 14) | type MemoryInsightTab = "pulse" | "insight" | "analysis";
type MemoryInsightViewMode (line 15) | type MemoryInsightViewMode = "browse" | "relations";
type MemoryInsightNodeKind (line 16) | type MemoryInsightNodeKind = "card" | "tag" | "entity" | "memory";
constant MEMORY_INSIGHT_UNTAGGED_TAG (line 20) | const MEMORY_INSIGHT_UNTAGGED_TAG = "__untagged__";
type MemoryInsightSelection (line 22) | type MemoryInsightSelection =
type MemoryInsightEntityFilter (line 44) | interface MemoryInsightEntityFilter {
type MemoryInsightCardNode (line 51) | interface MemoryInsightCardNode {
type MemoryInsightTagNode (line 64) | interface MemoryInsightTagNode {
type MemoryInsightEntityNode (line 79) | interface MemoryInsightEntityNode {
type MemoryInsightMemoryNode (line 94) | interface MemoryInsightMemoryNode {
type MemoryInsightNode (line 114) | type MemoryInsightNode =
type MemoryInsightEdge (line 120) | interface MemoryInsightEdge {
type MemoryInsightGraph (line 128) | interface MemoryInsightGraph {
type BuildMemoryInsightGraphInput (line 137) | interface BuildMemoryInsightGraphInput {
type TagBucket (line 145) | interface TagBucket {
type EntityBucket (line 152) | interface EntityBucket {
constant ENTITY_KIND_ORDER (line 159) | const ENTITY_KIND_ORDER: Record<MemoryInsightEntityKind, number> = {
constant CATEGORY_PREFIXES (line 166) | const CATEGORY_PREFIXES = [
constant CATEGORY_PREFIX_PATTERN (line 172) | const CATEGORY_PREFIX_PATTERN = /^analysis\.category\./i;
function createMatchLookup (line 174) | function createMatchLookup(
function slugify (line 185) | function slugify(value: string): string {
function capitalizeToken (line 195) | function capitalizeToken(value: string): string {
function stripInsightCategoryPrefix (line 203) | function stripInsightCategoryPrefix(value: string): string {
function humanizeInsightCategoryLabel (line 215) | function humanizeInsightCategoryLabel(value: string): string {
function normalizeLabel (line 229) | function normalizeLabel(value: string): string {
function stableInsightIdSuffix (line 233) | function stableInsightIdSuffix(value: string): string {
function buildStableInsightSegment (line 245) | function buildStableInsightSegment(value: string): string {
function buildInsightTagSegment (line 250) | function buildInsightTagSegment(tagValue: string): string {
function normalizeInsightCategoryKey (line 256) | function normalizeInsightCategoryKey(value: string): string {
function humanizeInsightLabel (line 260) | function humanizeInsightLabel(value: string): string {
function formatInsightCategoryLabel (line 264) | function formatInsightCategoryLabel(
function truncatePreview (line 284) | function truncatePreview(value: string, limit: number): string {
function buildSize (line 293) | function buildSize(base: number, count: number, scale: number): number {
function memoryMatchesInsightEntity (line 297) | function memoryMatchesInsightEntity(
function buildTagBuckets (line 316) | function buildTagBuckets(
function buildEntityBuckets (line 358) | function buildEntityBuckets(memories: Memory[]): EntityBucket[] {
function getCardMemories (line 394) | function getCardMemories(
function createCardNode (line 404) | function createCardNode(
function createTagNode (line 425) | function createTagNode(
function buildInsightTagNodeId (line 450) | function buildInsightTagNodeId(category: string, tagValue: string): stri...
function buildInsightEntityNodeId (line 454) | function buildInsightEntityNodeId(
function createEntityNode (line 464) | function createEntityNode(
function buildInsightMemoryNodeId (line 489) | function buildInsightMemoryNodeId(
function createMemoryNode (line 500) | function createMemoryNode(
function buildMemoryInsightGraph (line 541) | function buildMemoryInsightGraph(
FILE: dashboard/app/src/lib/memory-pulse.test.ts
constant FIXED_NOW (line 6) | const FIXED_NOW = new Date("2026-03-21T12:00:00Z");
function createMemory (line 8) | function createMemory(overrides: Partial<Memory> = {}): Memory {
function createStats (line 27) | function createStats(overrides: Partial<MemoryStats> = {}): MemoryStats {
function createSnapshot (line 35) | function createSnapshot(): AnalysisJobSnapshotResponse {
FILE: dashboard/app/src/lib/memory-pulse.ts
constant DAY_IN_MS (line 15) | const DAY_IN_MS = 24 * 60 * 60 * 1000;
constant TREND_BUCKETS (line 16) | const TREND_BUCKETS: Record<TimeRangePreset, number> = {
constant RANGE_DAYS (line 22) | const RANGE_DAYS: Record<Exclude<TimeRangePreset, "all">, number> = {
constant FACET_COLOR_TOKENS (line 27) | const FACET_COLOR_TOKENS: Record<MemoryFacet, string> = {
constant CATEGORY_COLOR_TOKENS (line 37) | const CATEGORY_COLOR_TOKENS: Record<AnalysisCategory, string> = {
constant DEFAULT_CATEGORY_COLOR_TOKEN (line 44) | const DEFAULT_CATEGORY_COLOR_TOKEN = "--facet-other";
type PulseTrendBucket (line 46) | interface PulseTrendBucket {
type PulseCompositionSegment (line 52) | interface PulseCompositionSegment {
type PulseSignalItem (line 61) | interface PulseSignalItem {
type MemoryPulseData (line 67) | interface MemoryPulseData {
function parseTimestamp (line 84) | function parseTimestamp(value: string): number | null {
function getWindow (line 89) | function getWindow(range: TimeRangePreset, memories: Memory[]): {
function buildPulseTrend (line 116) | function buildPulseTrend(
function normalizeSegments (line 156) | function normalizeSegments<T extends { key: string; labelKey: string; va...
function buildOuterSegments (line 167) | function buildOuterSegments(stats: MemoryStats): PulseCompositionSegment...
function isMemoryFacet (line 186) | function isMemoryFacet(value: unknown): value is MemoryFacet {
function buildFacetSegments (line 190) | function buildFacetSegments(memories: Memory[]): PulseCompositionSegment...
function buildAnalysisSegments (line 214) | function buildAnalysisSegments(
function buildPulseComposition (line 230) | function buildPulseComposition(
function buildSignalItemsFromStats (line 245) | function buildSignalItemsFromStats(
function buildSignalItemsFromMemories (line 265) | function buildSignalItemsFromMemories(
function getSnapshotTagStats (line 293) | function getSnapshotTagStats(
function buildPulseSignals (line 310) | function buildPulseSignals(
function buildMemoryPulseData (line 337) | function buildMemoryPulseData(input: {
FILE: dashboard/app/src/lib/mixpanel-auto-click.ts
function normalizeDatasetKey (line 5) | function normalizeDatasetKey(key: string): string | null {
function getEventProperties (line 16) | function getEventProperties(dataset: DOMStringMap): Record<string, strin...
function enableMixpanelAutoClickTracking (line 28) | function enableMixpanelAutoClickTracking(): void {
FILE: dashboard/app/src/lib/mixpanel.ts
constant MIXPANEL_TOKEN (line 3) | const MIXPANEL_TOKEN = import.meta.env.VITE_MIXPANEL_TOKEN?.trim() ?? "";
constant PAGE_NAME_BY_PATH (line 8) | const PAGE_NAME_BY_PATH: Record<string, string> = {
function resolvePageName (line 14) | function resolvePageName(pathname: string): string {
function initMixpanelOnLogin (line 18) | function initMixpanelOnLogin(): void {
function trackMixpanelEvent (line 31) | function trackMixpanelEvent(
function trackMixpanelPageView (line 42) | function trackMixpanelPageView(pathname: string): void {
FILE: dashboard/app/src/lib/pixel-farm/baby-cow.ts
constant BABY_COW_SPRITE_ORIGIN_X (line 10) | const BABY_COW_SPRITE_ORIGIN_X = 0.5;
constant BABY_COW_SPRITE_ORIGIN_Y (line 11) | const BABY_COW_SPRITE_ORIGIN_Y = 0.8;
constant BABY_COW_BODY_WIDTH (line 12) | const BABY_COW_BODY_WIDTH = 14;
constant BABY_COW_BODY_HEIGHT (line 13) | const BABY_COW_BODY_HEIGHT = 8;
constant BABY_COW_BODY_OFFSET_X (line 14) | const BABY_COW_BODY_OFFSET_X = 9;
constant BABY_COW_BODY_OFFSET_Y (line 15) | const BABY_COW_BODY_OFFSET_Y = 20;
constant BABY_COW_RUN_SPEED (line 16) | const BABY_COW_RUN_SPEED = 40;
constant BABY_COW_LOVE_COOLDOWN_MS (line 17) | const BABY_COW_LOVE_COOLDOWN_MS = 2600;
constant BABY_COW_AI_THINK_INTERVAL_MS (line 18) | const BABY_COW_AI_THINK_INTERVAL_MS = 180;
constant BABY_COW_ROAMING_COLLISION_COOLDOWN_MS (line 19) | const BABY_COW_ROAMING_COLLISION_COOLDOWN_MS = 350;
constant BABY_COW_ROAMING_COLLISION_IDLE_MS (line 20) | const BABY_COW_ROAMING_COLLISION_IDLE_MS = 350;
constant BABY_COW_ROAMING_COLLISION_RETREAT_MS (line 21) | const BABY_COW_ROAMING_COLLISION_RETREAT_MS = 550;
constant BABY_COW_ROAMING_COLLISION_FACE_THRESHOLD (line 22) | const BABY_COW_ROAMING_COLLISION_FACE_THRESHOLD = 2;
constant BABY_COW_BLOCKED_IDLE_MS (line 23) | const BABY_COW_BLOCKED_IDLE_MS = 240;
constant PIXEL_FARM_BABY_COW_COLORS (line 25) | const PIXEL_FARM_BABY_COW_COLORS = [
constant BABY_COW_STATES (line 33) | const BABY_COW_STATES = {
type PixelFarmBabyCowColor (line 45) | type PixelFarmBabyCowColor = (typeof PIXEL_FARM_BABY_COW_COLORS)[number];
type PixelFarmBabyCowState (line 46) | type PixelFarmBabyCowState = keyof typeof BABY_COW_STATES;
constant PIXEL_FARM_BABY_COW_STATE_OPTIONS (line 47) | const PIXEL_FARM_BABY_COW_STATE_OPTIONS = Object.keys(
type PixelFarmBabyCowConfig (line 51) | interface PixelFarmBabyCowConfig {
function animationKey (line 67) | function animationKey(color: PixelFarmBabyCowColor, state: PixelFarmBaby...
function babyCowTextureKey (line 71) | function babyCowTextureKey(color: PixelFarmBabyCowColor): string {
function randomRange (line 75) | function randomRange(min: number, max: number): number {
function registerPixelFarmBabyCowAnimations (line 79) | function registerPixelFarmBabyCowAnimations(scene: Phaser.Scene): void {
function measurePixelFarmBabyCowBodyAt (line 103) | function measurePixelFarmBabyCowBodyAt(
class PixelFarmBabyCow (line 120) | class PixelFarmBabyCow extends Phaser.Physics.Arcade.Sprite {
method constructor (line 134) | constructor(config: PixelFarmBabyCowConfig) {
method destroy (line 158) | override destroy(fromScene?: boolean): void {
method update (line 163) | update(deltaMs: number): void {
method setInteractionHeld (line 207) | setInteractionHeld(held: boolean): void {
method triggerLove (line 220) | triggerLove(sourceX: number): void {
method applyDebugPose (line 234) | applyDebugPose(state: PixelFarmBabyCowState, flipX: boolean, playing: ...
method handleRoamingCollision (line 250) | handleRoamingCollision(otherX: number, _otherY: number): void {
method updateRun (line 282) | private updateRun(deltaMs: number): void {
method handleBlockedRun (line 324) | private handleBlockedRun(): void {
method chooseNextState (line 330) | private chooseNextState(): void {
method startRun (line 360) | private startRun(): boolean {
method startCollisionRetreat (line 385) | private startCollisionRetreat(otherX: number): boolean {
method handleAnimationComplete (line 420) | private handleAnimationComplete(): void {
method cueOneShotState (line 445) | private cueOneShotState(state: Extract<PixelFarmBabyCowState, "hop" | ...
method enterTimedState (line 453) | private enterTimedState(
method canOccupyAt (line 464) | private canOccupyAt(x: number, y: number, moveX = 0, moveY = 0): boole...
method playState (line 477) | private playState(state: PixelFarmBabyCowState, ignoreIfPlaying = true...
FILE: dashboard/app/src/lib/pixel-farm/character.ts
constant CHARACTER_SPRITE_ORIGIN_X (line 9) | const CHARACTER_SPRITE_ORIGIN_X = 0.5;
constant CHARACTER_SPRITE_ORIGIN_Y (line 10) | const CHARACTER_SPRITE_ORIGIN_Y = 0.9;
constant CHARACTER_BODY_WIDTH (line 11) | const CHARACTER_BODY_WIDTH = 10;
constant CHARACTER_BODY_HEIGHT (line 12) | const CHARACTER_BODY_HEIGHT = 6;
constant CHARACTER_BODY_OFFSET_X (line 13) | const CHARACTER_BODY_OFFSET_X = 19;
constant CHARACTER_BODY_OFFSET_Y (line 14) | const CHARACTER_BODY_OFFSET_Y = 24;
constant CHARACTER_WALK_SPEED (line 15) | const CHARACTER_WALK_SPEED = 72;
constant CHARACTER_RUN_SPEED (line 16) | const CHARACTER_RUN_SPEED = 112;
constant CHARACTER_FRAMES_PER_ROW (line 17) | const CHARACTER_FRAMES_PER_ROW = 8;
constant CHARACTER_ACTION_ROWS (line 19) | const CHARACTER_ACTION_ROWS = {
constant CHARACTER_ANIMATION_FPS (line 28) | const CHARACTER_ANIMATION_FPS = {
constant PIXEL_FARM_CHARACTER_DIRECTIONS (line 37) | const PIXEL_FARM_CHARACTER_DIRECTIONS = ["down", "up", "right", "left"] ...
constant CHARACTER_TOOL_ACTIONS (line 38) | const CHARACTER_TOOL_ACTIONS = ["hoe", "axe", "water"] as const;
type PixelFarmCharacterDirection (line 40) | type PixelFarmCharacterDirection = (typeof PIXEL_FARM_CHARACTER_DIRECTIO...
type PixelFarmCharacterToolAction (line 41) | type PixelFarmCharacterToolAction = (typeof CHARACTER_TOOL_ACTIONS)[numb...
type PixelFarmCharacterAction (line 42) | type PixelFarmCharacterAction =
constant PIXEL_FARM_CHARACTER_ACTION_OPTIONS (line 47) | const PIXEL_FARM_CHARACTER_ACTION_OPTIONS = Object.keys(
type PixelFarmCharacterInput (line 51) | interface PixelFarmCharacterInput {
type PixelFarmCharacterConfig (line 58) | interface PixelFarmCharacterConfig {
type PixelFarmBodyRect (line 73) | interface PixelFarmBodyRect {
function animationKey (line 80) | function animationKey(
function directionForVector (line 87) | function directionForVector(
function registerPixelFarmCharacterAnimations (line 103) | function registerPixelFarmCharacterAnimations(scene: Phaser.Scene): void {
function measurePixelFarmCharacterBodyAt (line 129) | function measurePixelFarmCharacterBodyAt(x: number, y: number): PixelFar...
class PixelFarmCharacter (line 141) | class PixelFarmCharacter extends Phaser.Physics.Arcade.Sprite {
method constructor (line 149) | constructor(config: PixelFarmCharacterConfig) {
method destroy (line 171) | override destroy(fromScene?: boolean): void {
method update (line 176) | update(deltaMs: number, input: PixelFarmCharacterInput): void {
method getFacingDirection (line 199) | getFacingDirection(): PixelFarmCharacterDirection {
method handleAnimationComplete (line 203) | private handleAnimationComplete(): void {
method applyDebugPose (line 217) | applyDebugPose(
method updateLocomotion (line 237) | private updateLocomotion(deltaMs: number, input: PixelFarmCharacterInp...
method canOccupyAt (line 272) | private canOccupyAt(x: number, y: number, moveX = 0, moveY = 0): boole...
method setLocomotionAction (line 285) | private setLocomotionAction(action: "idle" | "walk" | "run"): void {
method startToolAction (line 295) | private startToolAction(action: PixelFarmCharacterToolAction): void {
method playAnimation (line 301) | private playAnimation(action: PixelFarmCharacterAction, ignoreIfPlayin...
FILE: dashboard/app/src/lib/pixel-farm/chicken.ts
constant CHICKEN_BODY_WIDTH (line 6) | const CHICKEN_BODY_WIDTH = 8;
constant CHICKEN_BODY_HEIGHT (line 7) | const CHICKEN_BODY_HEIGHT = 5;
constant CHICKEN_WALK_SPEED (line 8) | const CHICKEN_WALK_SPEED = 36;
constant CHICKEN_AI_THINK_INTERVAL_MS (line 9) | const CHICKEN_AI_THINK_INTERVAL_MS = 160;
constant CHICKEN_TOP_FRAME_WIDTH (line 10) | const CHICKEN_TOP_FRAME_WIDTH = 16;
constant CHICKEN_TOP_FRAME_HEIGHT (line 11) | const CHICKEN_TOP_FRAME_HEIGHT = 16;
constant CHICKEN_TOP_FRAMES_PER_ROW (line 12) | const CHICKEN_TOP_FRAMES_PER_ROW = 8;
constant CHICKEN_TOP_ROW_COUNT (line 13) | const CHICKEN_TOP_ROW_COUNT = 26;
constant CHICKEN_BODY_BOTTOM_MARGIN (line 14) | const CHICKEN_BODY_BOTTOM_MARGIN = 1;
constant CHICKEN_ROAMING_COLLISION_COOLDOWN_MS (line 15) | const CHICKEN_ROAMING_COLLISION_COOLDOWN_MS = 400;
constant CHICKEN_ROAMING_COLLISION_IDLE_MS (line 16) | const CHICKEN_ROAMING_COLLISION_IDLE_MS = 400;
constant CHICKEN_ROAMING_COLLISION_RETREAT_MS (line 17) | const CHICKEN_ROAMING_COLLISION_RETREAT_MS = 220;
constant CHICKEN_ROAMING_COLLISION_FACE_THRESHOLD (line 18) | const CHICKEN_ROAMING_COLLISION_FACE_THRESHOLD = 2;
constant CHICKEN_BLOCKED_IDLE_MS (line 19) | const CHICKEN_BLOCKED_IDLE_MS = 280;
constant CHICKEN_STANDARD_ANCHOR_X (line 20) | const CHICKEN_STANDARD_ANCHOR_X = CHICKEN_TOP_FRAME_WIDTH * 0.5;
constant PIXEL_FARM_CHICKEN_COLORS (line 22) | const PIXEL_FARM_CHICKEN_COLORS = [
type ChickenAnimationConfig (line 30) | type ChickenAnimationConfig = {
function topRowFrames (line 36) | function topRowFrames(row: number, count: number): number[] {
function chickenFrameName (line 40) | function chickenFrameName(index: number): string {
function chickenAnchorX (line 44) | function chickenAnchorX(_state: PixelFarmChickenState): number {
function chickenFrameHeight (line 48) | function chickenFrameHeight(state: PixelFarmChickenState): number {
constant CHICKEN_STATES (line 53) | const CHICKEN_STATES = {
type PixelFarmChickenColor (line 69) | type PixelFarmChickenColor = (typeof PIXEL_FARM_CHICKEN_COLORS)[number];
type PixelFarmChickenState (line 70) | type PixelFarmChickenState = keyof typeof CHICKEN_STATES;
constant PIXEL_FARM_CHICKEN_STATE_OPTIONS (line 71) | const PIXEL_FARM_CHICKEN_STATE_OPTIONS = Object.keys(
type PixelFarmChickenConfig (line 75) | interface PixelFarmChickenConfig {
function animationKey (line 92) | function animationKey(color: PixelFarmChickenColor, state: PixelFarmChic...
function chickenTextureKey (line 96) | function chickenTextureKey(color: PixelFarmChickenColor): string {
function randomRange (line 100) | function randomRange(min: number, max: number): number {
function addChickenFrame (line 104) | function addChickenFrame(
function registerChickenFramesForColor (line 115) | function registerChickenFramesForColor(scene: Phaser.Scene, color: Pixel...
function registerPixelFarmChickenAnimations (line 136) | function registerPixelFarmChickenAnimations(scene: Phaser.Scene): void {
function measurePixelFarmChickenBodyAt (line 161) | function measurePixelFarmChickenBodyAt(
class PixelFarmChicken (line 180) | class PixelFarmChicken extends Phaser.Physics.Arcade.Sprite {
method constructor (line 194) | constructor(config: PixelFarmChickenConfig) {
method destroy (line 225) | override destroy(fromScene?: boolean): void {
method update (line 230) | update(deltaMs: number): void {
method setInteractionHeld (line 272) | setInteractionHeld(held: boolean): void {
method triggerLove (line 285) | triggerLove(sourceX: number): void {
method applyDebugPose (line 289) | applyDebugPose(state: PixelFarmChickenState, flipX: boolean, playing: ...
method handleRoamingCollision (line 307) | handleRoamingCollision(otherX: number, _otherY: number): void {
method updateWalk (line 337) | private updateWalk(deltaMs: number): void {
method handleBlockedWalk (line 379) | private handleBlockedWalk(): void {
method chooseNextState (line 385) | private chooseNextState(): void {
method startWalk (line 419) | private startWalk(): boolean {
method startCollisionRetreat (line 453) | private startCollisionRetreat(otherX: number): boolean {
method handleAnimationComplete (line 488) | private handleAnimationComplete(): void {
method enterTimedState (line 503) | private enterTimedState(
method canOccupyAt (line 517) | private canOccupyAt(x: number, y: number, moveX = 0, moveY = 0): boole...
method syncBody (line 535) | private syncBody(): void {
method playState (line 554) | private playState(state: PixelFarmChickenState, ignoreIfPlaying = true...
FILE: dashboard/app/src/lib/pixel-farm/collision-layer.ts
constant QUARTER_TILE (line 5) | const QUARTER_TILE = 0.5;
constant EPSILON (line 6) | const EPSILON = 0.0001;
type PixelFarmCollisionRect (line 8) | interface PixelFarmCollisionRect {
type PixelFarmCompiledCollisionCell (line 15) | interface PixelFarmCompiledCollisionCell {
type PixelFarmCollisionIndex (line 22) | interface PixelFarmCollisionIndex {
type PixelFarmStaticCollisionBodyConfig (line 27) | interface PixelFarmStaticCollisionBodyConfig {
function halfCellKey (line 35) | function halfCellKey(halfRow: number, halfColumn: number): string {
function collisionRectForSegment (line 39) | function collisionRectForSegment(segment: PixelFarmCollisionCell): Pixel...
function occupiedHalfCells (line 51) | function occupiedHalfCells(segment: PixelFarmCollisionCell): Array<[numb...
function rectsIntersect (line 67) | function rectsIntersect(left: PixelFarmCollisionRect, right: PixelFarmCo...
function buildPixelFarmCollisionIndex (line 76) | function buildPixelFarmCollisionIndex(
function createPixelFarmStaticCollisionBodies (line 105) | function createPixelFarmStaticCollisionBodies(
function intersectsPixelFarmCollision (line 128) | function intersectsPixelFarmCollision(
FILE: dashboard/app/src/lib/pixel-farm/cow.ts
constant COW_SPRITE_ORIGIN_X (line 10) | const COW_SPRITE_ORIGIN_X = 0.5;
constant COW_SPRITE_ORIGIN_Y (line 11) | const COW_SPRITE_ORIGIN_Y = 0.8;
constant COW_BODY_WIDTH (line 12) | const COW_BODY_WIDTH = 18;
constant COW_BODY_HEIGHT (line 13) | const COW_BODY_HEIGHT = 10;
constant COW_BODY_OFFSET_X (line 14) | const COW_BODY_OFFSET_X = 7;
constant COW_BODY_OFFSET_Y (line 15) | const COW_BODY_OFFSET_Y = 18;
constant COW_WALK_SPEED (line 16) | const COW_WALK_SPEED = 28;
constant COW_LOVE_COOLDOWN_MS (line 17) | const COW_LOVE_COOLDOWN_MS = 2600;
constant COW_AI_THINK_INTERVAL_MS (line 18) | const COW_AI_THINK_INTERVAL_MS = 180;
constant COW_ROAMING_COLLISION_COOLDOWN_MS (line 19) | const COW_ROAMING_COLLISION_COOLDOWN_MS = 500;
constant COW_ROAMING_COLLISION_IDLE_MS (line 20) | const COW_ROAMING_COLLISION_IDLE_MS = 500;
constant COW_ROAMING_COLLISION_RETREAT_MS (line 21) | const COW_ROAMING_COLLISION_RETREAT_MS = 700;
constant COW_ROAMING_COLLISION_FACE_THRESHOLD (line 22) | const COW_ROAMING_COLLISION_FACE_THRESHOLD = 2;
constant COW_BLOCKED_IDLE_MS (line 23) | const COW_BLOCKED_IDLE_MS = 320;
constant PIXEL_FARM_COW_COLORS (line 25) | const PIXEL_FARM_COW_COLORS = [
constant COW_STATES (line 33) | const COW_STATES = {
type PixelFarmCowColor (line 44) | type PixelFarmCowColor = (typeof PIXEL_FARM_COW_COLORS)[number];
type PixelFarmCowState (line 45) | type PixelFarmCowState = keyof typeof COW_STATES;
constant PIXEL_FARM_COW_STATE_OPTIONS (line 46) | const PIXEL_FARM_COW_STATE_OPTIONS = Object.keys(COW_STATES) as PixelFar...
type PixelFarmCowConfig (line 48) | interface PixelFarmCowConfig {
function animationKey (line 64) | function animationKey(color: PixelFarmCowColor, state: PixelFarmCowState...
function cowTextureKey (line 68) | function cowTextureKey(color: PixelFarmCowColor): string {
function randomRange (line 72) | function randomRange(min: number, max: number): number {
function registerPixelFarmCowAnimations (line 76) | function registerPixelFarmCowAnimations(scene: Phaser.Scene): void {
function measurePixelFarmCowBodyAt (line 100) | function measurePixelFarmCowBodyAt(
class PixelFarmCow (line 115) | class PixelFarmCow extends Phaser.Physics.Arcade.Sprite {
method constructor (line 129) | constructor(config: PixelFarmCowConfig) {
method destroy (line 153) | override destroy(fromScene?: boolean): void {
method update (line 158) | update(deltaMs: number): void {
method setInteractionHeld (line 197) | setInteractionHeld(held: boolean): void {
method triggerLove (line 210) | triggerLove(sourceX: number): void {
method applyDebugPose (line 224) | applyDebugPose(state: PixelFarmCowState, flipX: boolean, playing: bool...
method handleRoamingCollision (line 240) | handleRoamingCollision(otherX: number, _otherY: number): void {
method updateWalk (line 271) | private updateWalk(deltaMs: number): void {
method handleBlockedWalk (line 313) | private handleBlockedWalk(): void {
method chooseNextState (line 319) | private chooseNextState(): void {
method startWalk (line 343) | private startWalk(): boolean {
method startCollisionRetreat (line 368) | private startCollisionRetreat(otherX: number): boolean {
method handleAnimationComplete (line 403) | private handleAnimationComplete(): void {
method enterTimedState (line 423) | private enterTimedState(state: Extract<PixelFarmCowState, "idle" | "si...
method canOccupyAt (line 431) | private canOccupyAt(x: number, y: number, moveX = 0, moveY = 0): boole...
method playState (line 437) | private playState(state: PixelFarmCowState, ignoreIfPlaying = true): v...
FILE: dashboard/app/src/lib/pixel-farm/create-game.ts
constant WATER_FRAME_DELAY (line 67) | const WATER_FRAME_DELAY = 180;
constant BACKGROUND_COLOR (line 68) | const BACKGROUND_COLOR = 0x0d141b;
constant WORLD_COLUMNS (line 69) | const WORLD_COLUMNS = 128;
constant WORLD_ROWS (line 70) | const WORLD_ROWS = 96;
constant ISLAND_COLUMNS (line 71) | const ISLAND_COLUMNS = PIXEL_FARM_MASK_COLUMNS;
constant ISLAND_ROWS (line 72) | const ISLAND_ROWS = PIXEL_FARM_MASK_ROWS;
constant CAMERA_MAX_ZOOM (line 73) | const CAMERA_MAX_ZOOM = 3;
constant CAMERA_TARGET_FILL (line 74) | const CAMERA_TARGET_FILL = 0.92;
constant CAMERA_FOLLOW_LERP (line 75) | const CAMERA_FOLLOW_LERP = 0.12;
constant CAMERA_DEADZONE_TILES_X (line 76) | const CAMERA_DEADZONE_TILES_X = 5;
constant CAMERA_DEADZONE_TILES_Y (line 77) | const CAMERA_DEADZONE_TILES_Y = 3;
constant ACTOR_LAYER_DEPTH (line 78) | const ACTOR_LAYER_DEPTH = 15;
constant STATIC_OBJECT_LAYER_DEPTH (line 79) | const STATIC_OBJECT_LAYER_DEPTH = 15;
constant INTERACTION_BUBBLE_OFFSET_Y (line 80) | const INTERACTION_BUBBLE_OFFSET_Y = PIXEL_FARM_TILE_SIZE * 0.8;
constant INTERACTION_FOCUS_FALLBACK_MS (line 81) | const INTERACTION_FOCUS_FALLBACK_MS = 180;
constant PIXEL_FARM_BGM_FADE_IN_MS (line 82) | const PIXEL_FARM_BGM_FADE_IN_MS = 500;
constant PIXEL_FARM_BGM_MAX_VOLUME (line 83) | const PIXEL_FARM_BGM_MAX_VOLUME = 0.2;
constant INTERACTION_ORIGIN_MARKER_RADIUS (line 84) | const INTERACTION_ORIGIN_MARKER_RADIUS = 4;
constant INTERACTION_ANCHOR_MARKER_RADIUS (line 85) | const INTERACTION_ANCHOR_MARKER_RADIUS = 3;
constant WATER_FRAME_COUNT (line 86) | const WATER_FRAME_COUNT = PIXEL_FARM_WATER_TEXTURE_KEYS.length;
constant ARCADE_DEBUG_ENABLED (line 87) | const ARCADE_DEBUG_ENABLED = false//import.meta.env.DEV;
constant PIXEL_FARM_COLLISION_INDEX (line 88) | const PIXEL_FARM_COLLISION_INDEX = buildPixelFarmCollisionIndex(PIXEL_FA...
constant WORLD_PIXEL_WIDTH (line 89) | const WORLD_PIXEL_WIDTH = WORLD_COLUMNS * PIXEL_FARM_TILE_SIZE;
constant WORLD_PIXEL_HEIGHT (line 90) | const WORLD_PIXEL_HEIGHT = WORLD_ROWS * PIXEL_FARM_TILE_SIZE;
constant ISLAND_PIXEL_WIDTH (line 91) | const ISLAND_PIXEL_WIDTH = PIXEL_FARM_MASK_BOUNDS.width * PIXEL_FARM_TIL...
constant ISLAND_PIXEL_HEIGHT (line 92) | const ISLAND_PIXEL_HEIGHT = PIXEL_FARM_MASK_BOUNDS.height * PIXEL_FARM_T...
constant ISLAND_START_COLUMN (line 93) | const ISLAND_START_COLUMN = Math.floor((WORLD_COLUMNS - ISLAND_COLUMNS) ...
constant ISLAND_START_ROW (line 94) | const ISLAND_START_ROW = Math.floor((WORLD_ROWS - ISLAND_ROWS) / 2);
constant ISLAND_CENTER_X (line 95) | const ISLAND_CENTER_X =
constant ISLAND_CENTER_Y (line 100) | const ISLAND_CENTER_Y =
type WaterTile (line 106) | interface WaterTile {
type DragState (line 111) | interface DragState {
type CharacterKeyboardControls (line 118) | interface CharacterKeyboardControls {
type PixelFarmCell (line 134) | interface PixelFarmCell {
type PixelFarmObjectRenderGroup (line 139) | interface PixelFarmObjectRenderGroup {
constant PIXEL_FARM_DEBUG_ACTOR_TYPES (line 145) | const PIXEL_FARM_DEBUG_ACTOR_TYPES = [
type PixelFarmDebugActorType (line 152) | type PixelFarmDebugActorType = (typeof PIXEL_FARM_DEBUG_ACTOR_TYPES)[num...
type PixelFarmDebugActorVariant (line 153) | type PixelFarmDebugActorVariant =
type PixelFarmDebugActorState (line 158) | type PixelFarmDebugActorState =
type PixelFarmDebugState (line 164) | interface PixelFarmDebugState {
type PixelFarmGameOptions (line 174) | interface PixelFarmGameOptions {
type PixelFarmPointerTile (line 185) | interface PixelFarmPointerTile {
type PixelFarmPointerDebugInfo (line 190) | interface PixelFarmPointerDebugInfo {
type PixelFarmInteractionTargetDebugInfo (line 195) | interface PixelFarmInteractionTargetDebugInfo {
type PixelFarmInteractionDebugInfo (line 211) | interface PixelFarmInteractionDebugInfo {
function createDefaultPixelFarmDebugState (line 219) | function createDefaultPixelFarmDebugState(
function localCellKey (line 266) | function localCellKey(row: number, column: number): string {
function asVolumeSound (line 270) | function asVolumeSound(sound: Phaser.Sound.BaseSound): Phaser.Sound.Base...
type PixelFarmInteractionCandidate (line 274) | interface PixelFarmInteractionCandidate {
type PixelFarmInteractionSelectionState (line 280) | interface PixelFarmInteractionSelectionState {
type PixelFarmRecentInteractionFocus (line 289) | interface PixelFarmRecentInteractionFocus {
function normalizeFacingVector (line 294) | function normalizeFacingVector(
function groupPixelFarmObjectsForDepth (line 308) | function groupPixelFarmObjectsForDepth(): PixelFarmObjectRenderGroup[] {
type PixelFarmPreviewActor (line 342) | type PixelFarmPreviewActor =
class PixelFarmSandboxScene (line 348) | class PixelFarmSandboxScene extends Phaser.Scene {
method constructor (line 384) | constructor(private readonly options: PixelFarmGameOptions = {}) {
method preload (line 388) | preload(): void {
method create (line 392) | create(): void {
method layoutLayers (line 438) | private layoutLayers(): void {
method handleResize (line 445) | private handleResize(): void {
method handleShutdown (line 452) | private handleShutdown(): void {
method rebuildWorld (line 491) | private rebuildWorld(): void {
method rebuildOcean (line 497) | private rebuildOcean(): void {
method advanceWaterFrame (line 522) | private advanceWaterFrame(): void {
method rebuildIsland (line 531) | private rebuildIsland(): void {
method update (line 581) | update(_time: number, delta: number): void {
method updateBgmState (line 597) | private updateBgmState(input: PixelFarmCharacterInput): void {
method startBgmPlayback (line 630) | private startBgmPlayback(): void {
method stopBgmCycle (line 663) | private stopBgmCycle(): void {
method bindCharacterControls (line 671) | private bindCharacterControls(): void {
method readCharacterInput (line 696) | private readCharacterInput(): PixelFarmCharacterInput {
method readCharacterAction (line 720) | private readCharacterAction(
method bindActorPhysics (line 738) | private bindActorPhysics(): void {
method asRenderedAnimal (line 784) | private asRenderedAnimal(
method createCharacter (line 817) | private createCharacter(spawnCell: PixelFarmCell): void {
method applyDebugActorState (line 830) | private applyDebugActorState(): void {
method applyWorldState (line 863) | private applyWorldState(): void {
method updateWorldDebugOverlay (line 891) | private updateWorldDebugOverlay(): void {
method drawSpatialDebugOverlay (line 908) | private drawSpatialDebugOverlay(): void {
method drawInteractionDebugOverlay (line 923) | private drawInteractionDebugOverlay(): void {
method collectRenderedInteractionDebugTiles (line 951) | private collectRenderedInteractionDebugTiles(): Array<{
method updateInteractionDebug (line 986) | private updateInteractionDebug(): void {
method updatePointerDebug (line 1028) | private updatePointerDebug(): void {
method readInteractionDebugInfo (line 1044) | private readInteractionDebugInfo(): PixelFarmInteractionDebugInfo {
method readInteractionDebugBasis (line 1100) | private readInteractionDebugBasis(): {
method readInteractionSelectionState (line 1137) | private readInteractionSelectionState(): PixelFarmInteractionSelection...
method computeInteractionSelectionState (line 1148) | private computeInteractionSelectionState(): PixelFarmInteractionSelect...
method resolveFocusedTargetForInteraction (line 1180) | private resolveFocusedTargetForInteraction(
method interactionOriginForCharacter (line 1200) | private interactionOriginForCharacter(
method collectInteractionCandidates (line 1214) | private collectInteractionCandidates(
method clearInteractionSelectionCache (line 1270) | private clearInteractionSelectionCache(): void {
method worldPointToLocalCell (line 1275) | private worldPointToLocalCell(worldPoint: { x: number; y: number }): P...
method facingVector (line 1288) | private facingVector(direction: PixelFarmCharacterDirection): { x: num...
method worldToScreenPoint (line 1302) | private worldToScreenPoint(worldX: number, worldY: number): { x: numbe...
method readPointerDebugInfo (line 1311) | private readPointerDebugInfo(): PixelFarmPointerDebugInfo {
method drawPhysicsDebugSprite (line 1362) | private drawPhysicsDebugSprite(
method drawSortMarker (line 1388) | private drawSortMarker(x: number, y: number, color: number): void {
method drawCollisionBlockerDebug (line 1400) | private drawCollisionBlockerDebug(color: number): void {
method drawInteractionOrigin (line 1420) | private drawInteractionOrigin(x: number, y: number, color: number): vo...
method drawFacingRay (line 1432) | private drawFacingRay(
method drawInteractionFrontTile (line 1452) | private drawInteractionFrontTile(
method drawInteractionTileOutline (line 1469) | private drawInteractionTileOutline(
method drawInteractionCandidate (line 1485) | private drawInteractionCandidate(
method createDebugActor (line 1501) | private createDebugActor(debugState: PixelFarmDebugState): PixelFarmPr...
method applyDebugPoseToActor (line 1531) | private applyDebugPoseToActor(
method findCharacterSpawnCell (line 1575) | private findCharacterSpawnCell(): PixelFarmCell {
method cellToWorldPosition (line 1606) | private cellToWorldPosition(cell: PixelFarmCell): { x: number; y: numb...
method cellToWorldOrigin (line 1613) | private cellToWorldOrigin(cell: PixelFarmCell): { x: number; y: number...
method rebuildCollisionBodies (line 1620) | private rebuildCollisionBodies(): void {
method worldRectToLocalRect (line 1630) | private worldRectToLocalRect(
method canCharacterStandAtCell (line 1644) | private canCharacterStandAtCell(row: number, column: number): boolean {
method isSpawnableLocalCell (line 1654) | private isSpawnableLocalCell(row: number, column: number): boolean {
method bindCameraControls (line 1701) | private bindCameraControls(): void {
method unbindCameraControls (line 1708) | private unbindCameraControls(): void {
method handlePointerDown (line 1715) | private handlePointerDown(pointer: Phaser.Input.Pointer): void {
method handlePointerMove (line 1723) | private handlePointerMove(pointer: Phaser.Input.Pointer): void {
method handlePointerUp (line 1732) | private handlePointerUp(pointer: Phaser.Input.Pointer): void {
method fitCameraToIsland (line 1741) | private fitCameraToIsland(): void {
method startCameraFollow (line 1752) | private startCameraFollow(): void {
method clampZoom (line 1772) | private clampZoom(zoom: number): number {
method minZoom (line 1776) | private minZoom(): number {
method clampCamera (line 1783) | private clampCamera(camera: Phaser.Cameras.Scene2D.Camera): void {
function createPixelFarmGame (line 1801) | function createPixelFarmGame(
FILE: dashboard/app/src/lib/pixel-farm/data/memory-store.ts
type PixelFarmMemoryStoreSnapshot (line 7) | interface PixelFarmMemoryStoreSnapshot {
type PixelFarmMemoryStore (line 13) | interface PixelFarmMemoryStore {
function compareByUpdatedAtDesc (line 19) | function compareByUpdatedAtDesc(left: Memory, right: Memory): number {
function createPixelFarmMemoryStore (line 26) | function createPixelFarmMemoryStore(): PixelFarmMemoryStore {
FILE: dashboard/app/src/lib/pixel-farm/data/memory-to-world.test.ts
function createMemory (line 5) | function createMemory(
FILE: dashboard/app/src/lib/pixel-farm/data/memory-to-world.ts
constant MAX_BUCKET_PLANT_COUNT (line 25) | const MAX_BUCKET_PLANT_COUNT = 6;
constant MAX_BUCKET_PLANT_GROUP_SIZE (line 26) | const MAX_BUCKET_PLANT_GROUP_SIZE = 10;
type BuildPixelFarmWorldStateInput (line 28) | interface BuildPixelFarmWorldStateInput {
type TagStat (line 37) | interface TagStat {
function stageForFillRatio (line 43) | function stageForFillRatio(fillRatio: number): PixelFarmCropStage {
function collectCandidateTags (line 56) | function collectCandidateTags(memories: Memory[]): TagStat[] {
function createTagStatFromSeedTag (line 98) | function createTagStatFromSeedTag(seedTag: PixelFarmSeedTag): TagStat | ...
function selectTopRankedTags (line 112) | function selectTopRankedTags(
function compareMemoryRecency (line 145) | function compareMemoryRecency(left: Memory, right: Memory): number {
function roundPlantCapacity (line 152) | function roundPlantCapacity(value: number): number {
function sliceBucketIntoPlants (line 156) | function sliceBucketIntoPlants(
function buildMemoryBuckets (line 185) | function buildMemoryBuckets(
function buildDefaultNpcs (line 233) | function buildDefaultNpcs(): PixelFarmNpcState[] {
function buildPixelFarmWorldState (line 278) | function buildPixelFarmWorldState({
FILE: dashboard/app/src/lib/pixel-farm/data/source.ts
constant ANALYSIS_RANGE (line 14) | const ANALYSIS_RANGE = "all" as const;
function cloneMemory (line 16) | function cloneMemory(memory: Memory): Memory {
function compareMemoryRecency (line 24) | function compareMemoryRecency(
function sortByRecencyDesc (line 34) | function sortByRecencyDesc<T extends Pick<Memory, "created_at" | "id" | ...
function normalizeSeedTagLabel (line 42) | function normalizeSeedTagLabel(label: string): { key: string; label: str...
function sortSeedTags (line 55) | function sortSeedTags(seedTags: PixelFarmSeedTag[]): PixelFarmSeedTag[] {
function buildSeedTagsFromFacetStats (line 65) | function buildSeedTagsFromFacetStats(
function buildSeedTagsFromTagCounts (line 92) | function buildSeedTagsFromTagCounts(
function buildSeedTags (line 102) | function buildSeedTags(
function loadCachedSeedMemories (line 116) | async function loadCachedSeedMemories(spaceId: string): Promise<Memory[]> {
function loadInitialSnapshot (line 124) | async function loadInitialSnapshot(
function pollDelta (line 141) | async function pollDelta(
FILE: dashboard/app/src/lib/pixel-farm/data/types.ts
type PixelFarmSeedTag (line 10) | interface PixelFarmSeedTag {
type PixelFarmInitialSnapshot (line 16) | interface PixelFarmInitialSnapshot {
type PixelFarmDeltaEvent (line 23) | interface PixelFarmDeltaEvent {
type PixelFarmDeltaBatch (line 33) | interface PixelFarmDeltaBatch {
type PixelFarmPlantState (line 39) | interface PixelFarmPlantState {
type PixelFarmMemoryBucketState (line 49) | interface PixelFarmMemoryBucketState {
type PixelFarmNpcState (line 62) | interface PixelFarmNpcState {
type PixelFarmWorldState (line 68) | interface PixelFarmWorldState {
type PixelFarmWorldQueryState (line 81) | interface PixelFarmWorldQueryState {
FILE: dashboard/app/src/lib/pixel-farm/data/use-pixel-farm-world.ts
function indexMemoriesById (line 14) | function indexMemoriesById(memories: readonly Memory[]): Record<string, ...
function cloneMemory (line 18) | function cloneMemory(memory: Memory): Memory {
function sortMemoriesByUpdatedAtDesc (line 26) | function sortMemoriesByUpdatedAtDesc(memories: readonly Memory[]): Memor...
function filterInteractionMemoriesByTag (line 35) | function filterInteractionMemoriesByTag(
function usePixelFarmWorld (line 53) | function usePixelFarmWorld(spaceId: string): PixelFarmWorldQueryState {
FILE: dashboard/app/src/lib/pixel-farm/depth.ts
function pixelFarmDepthForY (line 3) | function pixelFarmDepthForY(baseDepth: number, y: number): number {
function pixelFarmDepthForSpriteBody (line 7) | function pixelFarmDepthForSpriteBody(
FILE: dashboard/app/src/lib/pixel-farm/dialog-interaction.ts
function shouldIgnoreRepeatedDialogInteraction (line 3) | function shouldIgnoreRepeatedDialogInteraction(input: {
FILE: dashboard/app/src/lib/pixel-farm/dialog-state.test.ts
function createEntry (line 7) | function createEntry(id: string, memoryOffset: number) {
function createIntroEntry (line 16) | function createIntroEntry() {
FILE: dashboard/app/src/lib/pixel-farm/dialog-state.ts
type PixelFarmDialogIntroEntry (line 1) | interface PixelFarmDialogIntroEntry {
type PixelFarmDialogMemoryEntry (line 7) | interface PixelFarmDialogMemoryEntry {
type PixelFarmDialogNpcEntry (line 14) | interface PixelFarmDialogNpcEntry {
type PixelFarmDialogEntry (line 20) | type PixelFarmDialogEntry =
function isPixelFarmMemoryDialogEntry (line 25) | function isPixelFarmMemoryDialogEntry(
type PixelFarmDialogTargetSnapshot (line 31) | interface PixelFarmDialogTargetSnapshot {
type PixelFarmDialogInteractionInput (line 43) | interface PixelFarmDialogInteractionInput {
type PixelFarmOpenBubbleState (line 48) | interface PixelFarmOpenBubbleState {
function createPixelFarmOpenBubbleState (line 62) | function createPixelFarmOpenBubbleState(
function formatPixelFarmDialogCounter (line 121) | function formatPixelFarmDialogCounter(input: {
FILE: dashboard/app/src/lib/pixel-farm/field-layout.ts
constant PIXEL_FARM_TILLED_SOURCE_IDS (line 3) | const PIXEL_FARM_TILLED_SOURCE_IDS = new Set(["tilledDirtWide", "tiledDi...
type PixelFarmFieldCell (line 5) | interface PixelFarmFieldCell {
type PixelFarmFieldBounds (line 10) | interface PixelFarmFieldBounds {
type PixelFarmFieldLayout (line 17) | interface PixelFarmFieldLayout {
type PixelFarmFieldLayouts (line 23) | interface PixelFarmFieldLayouts {
function pixelFarmFieldCellKey (line 28) | function pixelFarmFieldCellKey(cell: PixelFarmFieldCell): string {
function comparePixelFarmFieldCells (line 32) | function comparePixelFarmFieldCells(
function measurePixelFarmFieldBounds (line 39) | function measurePixelFarmFieldBounds(
function collectConnectedComponents (line 58) | function collectConnectedComponents(
function collectPixelFarmTilledCells (line 106) | function collectPixelFarmTilledCells(): PixelFarmFieldCell[] {
function derivePixelFarmFieldLayouts (line 122) | function derivePixelFarmFieldLayouts(
FILE: dashboard/app/src/lib/pixel-farm/generated-mask-data.ts
constant PIXEL_FARM_GENERATED_LAYERS (line 1) | const PIXEL_FARM_GENERATED_LAYERS = [
constant PIXEL_FARM_GENERATED_OBJECTS (line 848) | const PIXEL_FARM_GENERATED_OBJECTS = [
constant PIXEL_FARM_GENERATED_OBJECT_GROUPS (line 2900) | const PIXEL_FARM_GENERATED_OBJECT_GROUPS = [
constant PIXEL_FARM_GENERATED_COLLISIONS (line 2928) | const PIXEL_FARM_GENERATED_COLLISIONS = [
FILE: dashboard/app/src/lib/pixel-farm/generated-mask-source.ts
type PixelFarmGeneratedTileOverride (line 6) | interface PixelFarmGeneratedTileOverride extends PixelFarmAssetTileSelec...
type PixelFarmGeneratedLayerPayload (line 10) | interface PixelFarmGeneratedLayerPayload {
type PixelFarmGeneratedMaskPayload (line 18) | interface PixelFarmGeneratedMaskPayload {
type PixelFarmGeneratedObjectPlacement (line 25) | interface PixelFarmGeneratedObjectPlacement {
type PixelFarmGeneratedObjectGroup (line 35) | interface PixelFarmGeneratedObjectGroup {
type PixelFarmGeneratedCollisionCell (line 41) | interface PixelFarmGeneratedCollisionCell {
function quote (line 47) | function quote(value: string): string {
function buildTile (line 51) | function buildTile(tile: PixelFarmGeneratedTileOverride): string {
function buildOverrides (line 59) | function buildOverrides(overrides: Record<string, PixelFarmGeneratedTile...
function buildLayer (line 72) | function buildLayer(layer: PixelFarmGeneratedLayerPayload): string {
function buildObject (line 88) | function buildObject(object: PixelFarmGeneratedObjectPlacement): string {
function buildObjectGroup (line 107) | function buildObjectGroup(group: PixelFarmGeneratedObjectGroup): string {
function buildCollision (line 117) | function buildCollision(cell: PixelFarmGeneratedCollisionCell): string {
function buildPixelFarmGeneratedMaskSource (line 127) | function buildPixelFarmGeneratedMaskSource(
FILE: dashboard/app/src/lib/pixel-farm/island-mask.ts
type PixelFarmTileOverride (line 12) | interface PixelFarmTileOverride extends PixelFarmAssetTileSelection {
type PixelFarmTileOverrideMap (line 15) | type PixelFarmTileOverrideMap = Record<string, PixelFarmTileOverride>;
type PixelFarmLayer (line 17) | interface PixelFarmLayer {
type PixelFarmObjectPlacement (line 25) | interface PixelFarmObjectPlacement {
type PixelFarmObjectGroup (line 35) | interface PixelFarmObjectGroup {
type PixelFarmCollisionCell (line 41) | interface PixelFarmCollisionCell {
type PixelFarmMaskBounds (line 47) | interface PixelFarmMaskBounds {
function validateMask (line 56) | function validateMask(mask: readonly string[], expectedColumns?: number,...
function normalizeLayers (line 75) | function normalizeLayers(): PixelFarmLayer[] {
function measureMask (line 108) | function measureMask(mask: readonly string[]): PixelFarmMaskBounds {
function normalizeObjectGroups (line 141) | function normalizeObjectGroups(): PixelFarmObjectGroup[] {
function normalizeObjects (line 172) | function normalizeObjects(
function normalizeCollisions (line 216) | function normalizeCollisions(): PixelFarmCollisionCell[] {
constant PIXEL_FARM_LAYERS (line 247) | const PIXEL_FARM_LAYERS = normalizeLayers();
type PixelFarmLayerId (line 248) | type PixelFarmLayerId = string;
constant PIXEL_FARM_LAYER_IDS (line 249) | const PIXEL_FARM_LAYER_IDS = PIXEL_FARM_LAYERS.map((layer) => layer.id);
constant PIXEL_FARM_ROOT_LAYER (line 250) | const PIXEL_FARM_ROOT_LAYER = PIXEL_FARM_LAYERS[0]!;
constant PIXEL_FARM_MASK_COLUMNS (line 251) | const PIXEL_FARM_MASK_COLUMNS = PIXEL_FARM_ROOT_LAYER.mask[0]?.length ?? 0;
constant PIXEL_FARM_MASK_ROWS (line 252) | const PIXEL_FARM_MASK_ROWS = PIXEL_FARM_ROOT_LAYER.mask.length;
constant PIXEL_FARM_MASK_BOUNDS (line 253) | const PIXEL_FARM_MASK_BOUNDS = measureMask(PIXEL_FARM_ROOT_LAYER.mask);
constant PIXEL_FARM_OBJECT_GROUPS (line 254) | const PIXEL_FARM_OBJECT_GROUPS = normalizeObjectGroups();
constant PIXEL_FARM_OBJECTS (line 255) | const PIXEL_FARM_OBJECTS = normalizeObjects(PIXEL_FARM_LAYER_IDS, PIXEL_...
constant PIXEL_FARM_COLLISIONS (line 256) | const PIXEL_FARM_COLLISIONS = normalizeCollisions();
function maskHasTile (line 258) | function maskHasTile(mask: readonly string[], row: number, column: numbe...
function tileOverrideKey (line 262) | function tileOverrideKey(row: number, column: number): string {
function tileOverrideAt (line 266) | function tileOverrideAt(
FILE: dashboard/app/src/lib/pixel-farm/npc-dialog-content.test.ts
function translate (line 11) | function translate(key: string, vars?: Record<string, string | number>):...
function createLightSnapshot (line 15) | function createLightSnapshot(): AnalysisJobSnapshotResponse {
function createDeepReport (line 50) | function createDeepReport(): DeepAnalysisReportDetail {
FILE: dashboard/app/src/lib/pixel-farm/npc-dialog-content.ts
type PixelFarmNpcDialogSource (line 10) | type PixelFarmNpcDialogSource =
type PixelFarmNpcDialogCandidate (line 15) | interface PixelFarmNpcDialogCandidate {
type PixelFarmNpcDialogCatalog (line 22) | interface PixelFarmNpcDialogCatalog {
type PixelFarmNpcDialogRotationState (line 28) | interface PixelFarmNpcDialogRotationState {
type Translate (line 35) | type Translate = (key: string, vars?: Record<string, string | number>) =...
function buildDeepCandidates (line 37) | function buildDeepCandidates(
function buildLightCandidates (line 86) | function buildLightCandidates(
function buildTipCandidates (line 134) | function buildTipCandidates(t: Translate): PixelFarmNpcDialogCandidate[] {
function buildPixelFarmNpcDialogCatalog (line 146) | function buildPixelFarmNpcDialogCatalog(input: {
function resolveActivePool (line 158) | function resolveActivePool(
function buildPoolSignature (line 168) | function buildPoolSignature(pool: readonly PixelFarmNpcDialogCandidate[]...
function shuffleCandidates (line 172) | function shuffleCandidates<T>(
function moveFirstAwayFromPrevious (line 188) | function moveFirstAwayFromPrevious(
function rebuildQueue (line 226) | function rebuildQueue(input: {
function pickNextPixelFarmNpcDialogEntry (line 241) | function pickNextPixelFarmNpcDialogEntry(input: {
FILE: dashboard/app/src/lib/pixel-farm/npc-tips.ts
constant PIXEL_FARM_NPC_TIP_IDS (line 4) | const PIXEL_FARM_NPC_TIP_IDS = [
type PixelFarmNpcTipId (line 13) | type PixelFarmNpcTipId = (typeof PIXEL_FARM_NPC_TIP_IDS)[number];
function pickRandomPixelFarmNpcTipId (line 15) | function pickRandomPixelFarmNpcTipId(
function getPixelFarmNpcDialogTitle (line 27) | function getPixelFarmNpcDialogTitle(): string {
function buildPixelFarmNpcDialogEntry (line 31) | function buildPixelFarmNpcDialogEntry(
FILE: dashboard/app/src/lib/pixel-farm/palette.ts
constant FARMING_PLANTS_COLUMNS (line 6) | const FARMING_PLANTS_COLUMNS = 5;
constant MUSHROOMS_FLOWERS_STONES_COLUMNS (line 7) | const MUSHROOMS_FLOWERS_STONES_COLUMNS = 12;
constant PIXEL_FARM_TOP_CROP_TAG_COUNT (line 9) | const PIXEL_FARM_TOP_CROP_TAG_COUNT = 13;
type PixelFarmCropStage (line 11) | type PixelFarmCropStage = "seed" | "sprout" | "growing" | "mature";
type PixelFarmBucketAnimalTier (line 12) | type PixelFarmBucketAnimalTier = "chicken" | "baby-cow" | "cow";
type PixelFarmCropFamilyPalette (line 14) | interface PixelFarmCropFamilyPalette {
type PixelFarmDecorationPalette (line 19) | interface PixelFarmDecorationPalette {
type PixelFarmBucketAnimalPalette (line 24) | interface PixelFarmBucketAnimalPalette {
function frameAt (line 30) | function frameAt(columns: number, row: number, column: number): number {
function farmingPlant (line 34) | function farmingPlant(row: number, column: number): PixelFarmAssetTileSe...
function otherDecoration (line 41) | function otherDecoration(row: number, column: number): PixelFarmAssetTil...
function singleTileCropPalette (line 48) | function singleTileCropPalette(row: number, family: string): PixelFarmCr...
constant PIXEL_FARM_CROP_BUCKET_PALETTES (line 67) | const PIXEL_FARM_CROP_BUCKET_PALETTES: readonly PixelFarmCropFamilyPalet...
constant PIXEL_FARM_SPECIAL_TALL_CROP_CANDIDATE (line 83) | const PIXEL_FARM_SPECIAL_TALL_CROP_CANDIDATE: PixelFarmCropFamilyPalette...
constant PIXEL_FARM_OTHER_ZONE_DECORATIONS (line 101) | const PIXEL_FARM_OTHER_ZONE_DECORATIONS = {
constant PIXEL_FARM_BUCKET_ANIMAL_PALETTES (line 136) | const PIXEL_FARM_BUCKET_ANIMAL_PALETTES: readonly PixelFarmBucketAnimalP...
FILE: dashboard/app/src/lib/pixel-farm/plant-dialog-content.test.ts
function createMemory (line 5) | function createMemory(id: string, content: string): Memory {
FILE: dashboard/app/src/lib/pixel-farm/plant-dialog-content.ts
type Translate (line 5) | type Translate = (key: string, vars?: Record<string, string | number>) =...
function buildPixelFarmPlantDialogEntries (line 7) | function buildPixelFarmPlantDialogEntries(input: {
FILE: dashboard/app/src/lib/pixel-farm/plant-placement.ts
type PixelFarmPlantPlacement (line 7) | interface PixelFarmPlantPlacement {
function cellKey (line 14) | function cellKey(cell: PixelFarmFieldCell): string {
function compareCells (line 18) | function compareCells(left: PixelFarmFieldCell, right: PixelFarmFieldCel...
function measureBounds (line 22) | function measureBounds(
function cellDistance (line 46) | function cellDistance(left: PixelFarmFieldCell, right: PixelFarmFieldCel...
function lerp (line 50) | function lerp(min: number, max: number, ratio: number): number {
function buildFieldInteriorDepthIndex (line 54) | function buildFieldInteriorDepthIndex(
function isInteriorCell (line 106) | function isInteriorCell(
function pickDistributedCells (line 113) | function pickDistributedCells(
function takeNearestCells (line 176) | function takeNearestCells(
function buildPixelFarmPlantPlacements (line 206) | function buildPixelFarmPlantPlacements(input: {
FILE: dashboard/app/src/lib/pixel-farm/runtime-assets.ts
constant PIXEL_FARM_BUBBLE_APPEAR_SOUND_KEY (line 32) | const PIXEL_FARM_BUBBLE_APPEAR_SOUND_KEY = "pixel-farm-bubble-appear";
constant PIXEL_FARM_BUBBLE_APPEAR_SOUND_DURATION_MS (line 33) | const PIXEL_FARM_BUBBLE_APPEAR_SOUND_DURATION_MS = 500;
constant PIXEL_FARM_BGM_TEXTURE_KEY (line 34) | const PIXEL_FARM_BGM_TEXTURE_KEY = "pixel-farm-bgm";
constant PIXEL_FARM_DIALOG_TEXTURE_KEY (line 35) | const PIXEL_FARM_DIALOG_TEXTURE_KEY = "pixel-farm-dialog-box";
constant PIXEL_FARM_MOUSE_CURSOR_TEXTURE_KEY (line 36) | const PIXEL_FARM_MOUSE_CURSOR_TEXTURE_KEY = "pixel-farm-mouse-cursor";
constant PIXEL_FARM_CHARACTER_TEXTURE_KEY (line 38) | const PIXEL_FARM_CHARACTER_TEXTURE_KEY = "pixel-farm-character-premium";
constant PIXEL_FARM_CHARACTER_FRAME_WIDTH (line 39) | const PIXEL_FARM_CHARACTER_FRAME_WIDTH = 48;
constant PIXEL_FARM_CHARACTER_FRAME_HEIGHT (line 40) | const PIXEL_FARM_CHARACTER_FRAME_HEIGHT = 48;
constant PIXEL_FARM_COW_FRAME_WIDTH (line 41) | const PIXEL_FARM_COW_FRAME_WIDTH = 32;
constant PIXEL_FARM_COW_FRAME_HEIGHT (line 42) | const PIXEL_FARM_COW_FRAME_HEIGHT = 32;
constant PIXEL_FARM_BABY_COW_FRAME_WIDTH (line 43) | const PIXEL_FARM_BABY_COW_FRAME_WIDTH = 32;
constant PIXEL_FARM_BABY_COW_FRAME_HEIGHT (line 44) | const PIXEL_FARM_BABY_COW_FRAME_HEIGHT = 32;
constant PIXEL_FARM_COW_TEXTURE_KEYS (line 46) | const PIXEL_FARM_COW_TEXTURE_KEYS = {
constant PIXEL_FARM_BABY_COW_TEXTURE_KEYS (line 54) | const PIXEL_FARM_BABY_COW_TEXTURE_KEYS = {
constant PIXEL_FARM_CHICKEN_TEXTURE_KEYS (line 62) | const PIXEL_FARM_CHICKEN_TEXTURE_KEYS = {
constant PIXEL_FARM_WATER_TEXTURE_KEYS (line 70) | const PIXEL_FARM_WATER_TEXTURE_KEYS = [
constant PIXEL_FARM_WATER_TEXTURE_URLS (line 77) | const PIXEL_FARM_WATER_TEXTURE_URLS = [
constant PIXEL_FARM_COW_TEXTURE_URLS (line 84) | const PIXEL_FARM_COW_TEXTURE_URLS: Record<keyof typeof PIXEL_FARM_COW_TE...
constant PIXEL_FARM_BABY_COW_TEXTURE_URLS (line 92) | const PIXEL_FARM_BABY_COW_TEXTURE_URLS: Record<
constant PIXEL_FARM_CHICKEN_TEXTURE_URLS (line 103) | const PIXEL_FARM_CHICKEN_TEXTURE_URLS: Record<
function preloadPixelFarmRuntimeAssets (line 114) | function preloadPixelFarmRuntimeAssets(scene: Phaser.Scene): void {
function preloadPixelFarmDialogAsset (line 161) | function preloadPixelFarmDialogAsset(scene: Phaser.Scene): void {
function pixelFarmWaterTextureKey (line 166) | function pixelFarmWaterTextureKey(
FILE: dashboard/app/src/lib/pixel-farm/tileset-config.ts
constant PIXEL_FARM_TILE_SIZE (line 25) | const PIXEL_FARM_TILE_SIZE = 16;
constant PIXEL_FARM_BASE_DEFAULT_FRAME (line 26) | const PIXEL_FARM_BASE_DEFAULT_FRAME = 12;
constant PIXEL_FARM_ASSET_SOURCE_IDS (line 28) | const PIXEL_FARM_ASSET_SOURCE_IDS = [
type PixelFarmAssetSourceId (line 54) | type PixelFarmAssetSourceId = (typeof PIXEL_FARM_ASSET_SOURCE_IDS)[number];
type PixelFarmAssetTileSelection (line 56) | interface PixelFarmAssetTileSelection {
type PixelFarmAssetSourceConfig (line 61) | interface PixelFarmAssetSourceConfig {
function defineAssetSource (line 70) | function defineAssetSource(
constant PIXEL_FARM_ASSET_SOURCE_CONFIG (line 87) | const PIXEL_FARM_ASSET_SOURCE_CONFIG: Record<
type PixelFarmTilesetConfig (line 149) | type PixelFarmTilesetConfig = PixelFarmAssetSourceConfig;
constant PIXEL_FARM_TILESET_CONFIG (line 150) | const PIXEL_FARM_TILESET_CONFIG = PIXEL_FARM_ASSET_SOURCE_CONFIG;
FILE: dashboard/app/src/lib/pixel-farm/ui-dialog-layout.test.ts
function createInput (line 7) | function createInput(
FILE: dashboard/app/src/lib/pixel-farm/ui-dialog-layout.ts
type PixelFarmDialogTail (line 1) | type PixelFarmDialogTail = "bottom-left" | "bottom-right";
type PixelFarmDialogPlacementInput (line 3) | interface PixelFarmDialogPlacementInput {
type PixelFarmDialogPlacement (line 16) | interface PixelFarmDialogPlacement {
function clamp (line 23) | function clamp(value: number, min: number, max: number): number {
function isWithinBounds (line 27) | function isWithinBounds(
function chooseTail (line 35) | function chooseTail(anchorX: number, centerX: number): PixelFarmDialogTa...
function chooseSafeAreaX (line 39) | function chooseSafeAreaX(input: PixelFarmDialogPlacementInput): number {
function computePixelFarmDialogPlacement (line 52) | function computePixelFarmDialogPlacement(
FILE: dashboard/app/src/lib/pixel-farm/ui-dialog-pagination.test.ts
function normalize (line 4) | function normalize(value: string): string {
FILE: dashboard/app/src/lib/pixel-farm/ui-dialog-pagination.ts
type PixelFarmDialogPaginationInput (line 1) | interface PixelFarmDialogPaginationInput {
function normalizeDialogText (line 7) | function normalizeDialogText(text: string): string {
function splitIntoSentenceUnits (line 11) | function splitIntoSentenceUnits(text: string): string[] {
function pushWordGroup (line 16) | function pushWordGroup(
function paginatePixelFarmDialogText (line 40) | function paginatePixelFarmDialogText(
FILE: dashboard/app/src/lib/pixel-farm/ui-dialog.ts
constant DIALOG_TILE_SIZE (line 16) | const DIALOG_TILE_SIZE = 16;
constant DIALOG_WIDTH (line 17) | const DIALOG_WIDTH = 420;
constant DIALOG_MIN_HEIGHT (line 18) | const DIALOG_MIN_HEIGHT = 160;
constant DIALOG_MAX_HEIGHT (line 19) | const DIALOG_MAX_HEIGHT = 240;
constant DIALOG_MARGIN_X (line 20) | const DIALOG_MARGIN_X = 12;
constant DIALOG_MARGIN_TOP (line 21) | const DIALOG_MARGIN_TOP = 12;
constant DIALOG_MARGIN_BOTTOM (line 22) | const DIALOG_MARGIN_BOTTOM = 24;
constant DIALOG_OFFSET_ABOVE_ANCHOR (line 23) | const DIALOG_OFFSET_ABOVE_ANCHOR = 14;
constant DIALOG_PADDING_X (line 24) | const DIALOG_PADDING_X = 24;
constant DIALOG_PADDING_TOP (line 25) | const DIALOG_PADDING_TOP = 24;
constant DIALOG_PADDING_BOTTOM (line 26) | const DIALOG_PADDING_BOTTOM = 24;
constant DIALOG_TEXT_WIDTH (line 27) | const DIALOG_TEXT_WIDTH = DIALOG_WIDTH - DIALOG_PADDING_X * 2;
constant DIALOG_MAX_TEXT_LINES (line 28) | const DIALOG_MAX_TEXT_LINES = 5;
constant DIALOG_HEADER_HEIGHT (line 29) | const DIALOG_HEADER_HEIGHT = 18;
constant DIALOG_LINE_HEIGHT (line 30) | const DIALOG_LINE_HEIGHT = 18;
constant TYPING_DELAY_MS (line 31) | const TYPING_DELAY_MS = 14;
type PixelFarmDialogPayload (line 33) | interface PixelFarmDialogPayload {
type PixelFarmDialogRuntimeState (line 48) | interface PixelFarmDialogRuntimeState {
type PixelFarmDialogSpriteSet (line 57) | interface PixelFarmDialogSpriteSet {
type PixelFarmDialogKeyBinding (line 69) | interface PixelFarmDialogKeyBinding {
function ensureDialogTexture (line 74) | function ensureDialogTexture(scene: Phaser.Scene): void {
function createDialogImage (line 143) | function createDialogImage(scene: Phaser.Scene, frame: string): Phaser.G...
function createDialogTileSprite (line 150) | function createDialogTileSprite(
function createSpriteSet (line 160) | function createSpriteSet(scene: Phaser.Scene): PixelFarmDialogSpriteSet {
function destroySprites (line 174) | function destroySprites(sprites: PixelFarmDialogSpriteSet | null): void {
function normalizeText (line 184) | function normalizeText(text: string): string {
function hasMorePages (line 188) | function hasMorePages(pageIndex: number, pages: readonly string[]): bool...
class PixelFarmUIDialog (line 192) | class PixelFarmUIDialog {
method constructor (line 217) | constructor(private readonly scene: Phaser.Scene) {
method destroy (line 271) | destroy(): void {
method open (line 285) | open(payload: PixelFarmDialogPayload): void {
method close (line 339) | close(): void {
method refreshAnchor (line 350) | refreshAnchor(anchorScreenX: number, anchorScreenY: number): void {
method advancePrimary (line 364) | advancePrimary(): void {
method goPrevious (line 384) | goPrevious(): void {
method goNext (line 409) | goNext(): void {
method goPreviousMemory (line 434) | private goPreviousMemory(): void {
method goNextMemory (line 452) | private goNextMemory(): void {
method setMemoryIndex (line 470) | private setMemoryIndex(nextIndex: number, nextPageIndex: number | "las...
method buildPages (line 498) | private buildPages(content: string): string[] {
method getCurrentEntry (line 510) | private getCurrentEntry(): PixelFarmDialogEntry | null {
method findPreviousEntryIndex (line 514) | private findPreviousEntryIndex(fromIndex: number): number | null {
method findNextEntryIndex (line 522) | private findNextEntryIndex(fromIndex: number): number | null {
method computePlacement (line 530) | private computePlacement(payload: PixelFarmDialogPayload): PixelFarmDi...
method measureDialogHeight (line 549) | private measureDialogHeight(pageText: string): number {
method measureWrappedLineCount (line 560) | private measureWrappedLineCount(text: string): number {
method layoutBody (line 570) | private layoutBody(width: number, height: number): void {
method applyPlacement (line 594) | private applyPlacement(): void {
method renderPage (line 615) | private renderPage(): void {
method updateTextMeta (line 640) | private updateTextMeta(): void {
method createHeaderStyle (line 667) | private createHeaderStyle(): Phaser.Types.GameObjects.Text.TextStyle {
method createContentStyle (line 675) | private createContentStyle(): Phaser.Types.GameObjects.Text.TextStyle {
method createCounterStyle (line 685) | private createCounterStyle(): Phaser.Types.GameObjects.Text.TextStyle {
method createButtonStyle (line 693) | private createButtonStyle(): Phaser.Types.GameObjects.Text.TextStyle {
method startTyping (line 701) | private startTyping(): void {
method finishTyping (line 727) | private finishTyping(): void {
method stopTyping (line 733) | private stopTyping(): void {
method bindKeyboard (line 738) | private bindKeyboard(): void {
method unbindKeyboard (line 749) | private unbindKeyboard(): void {
method isTyping (line 760) | private isTyping(): boolean {
FILE: dashboard/app/src/lib/pixel-farm/ui-scene.ts
class PixelFarmUIScene (line 8) | class PixelFarmUIScene extends Phaser.Scene {
method constructor (line 14) | constructor() {
method preload (line 18) | preload(): void {
method create (line 22) | create(): void {
method update (line 52) | update(): void {
method openDialog (line 80) | openDialog(payload: PixelFarmDialogPayload): void {
method closeDialog (line 85) | closeDialog(): void {
method refreshDialogAnchor (line 90) | refreshDialogAnchor(anchorScreenX: number, anchorScreenY: number): void {
method handleResize (line 103) | private handleResize(): void {
method syncCanvasHoverState (line 109) | private syncCanvasHoverState(): void {
method updateCanvasHoverState (line 113) | private updateCanvasHoverState(isPointerOverCanvas: boolean): void {
method handleShutdown (line 130) | private handleShutdown(): void {
FILE: dashboard/app/src/lib/pixel-farm/use-pixel-farm-npc-dialog-content.test.tsx
function createWrapper (line 34) | function createWrapper() {
FILE: dashboard/app/src/lib/pixel-farm/use-pixel-farm-npc-dialog-content.ts
function pickLatestCompletedReport (line 16) | function pickLatestCompletedReport(
type PixelFarmNpcDialogContentState (line 24) | interface PixelFarmNpcDialogContentState {
function usePixelFarmNpcDialogContent (line 30) | function usePixelFarmNpcDialogContent(
FILE: dashboard/app/src/lib/pixel-farm/world-render.ts
constant DATA_ENTITY_DEPTH (line 42) | const DATA_ENTITY_DEPTH = 15;
constant CHICKEN_ROAM_TARGET_MIN_DISTANCE (line 43) | const CHICKEN_ROAM_TARGET_MIN_DISTANCE = 4;
constant CHICKEN_ROAM_TARGET_MAX_ATTEMPTS (line 44) | const CHICKEN_ROAM_TARGET_MAX_ATTEMPTS = 10;
constant CHICKEN_RENDER_COLORS (line 46) | const CHICKEN_RENDER_COLORS = ["default", "brown"] as const satisfies re...
constant COW_RENDER_COLORS (line 47) | const COW_RENDER_COLORS = ["brown", "light"] as const satisfies readonly...
constant PIXEL_FARM_COLLISION_INDEX (line 48) | const PIXEL_FARM_COLLISION_INDEX = buildPixelFarmCollisionIndex(PIXEL_FA...
constant COW_SPAWN_BOUNDS (line 50) | const COW_SPAWN_BOUNDS: PixelFarmCellBounds = {
constant CHICKEN_SPAWN_BOUNDS (line 57) | const CHICKEN_SPAWN_BOUNDS: PixelFarmCellBounds = {
type PixelFarmGridCell (line 64) | interface PixelFarmGridCell {
type PixelFarmCellBounds (line 69) | interface PixelFarmCellBounds {
type PixelFarmAnimalPenLayout (line 76) | interface PixelFarmAnimalPenLayout {
type PixelFarmWorldBounds (line 83) | interface PixelFarmWorldBounds {
type PixelFarmWorldRendererConfig (line 90) | interface PixelFarmWorldRendererConfig {
type PixelFarmRenderedAnimal (line 96) | type PixelFarmRenderedAnimal = PixelFarmBabyCow | PixelFarmChicken | Pix...
type PixelFarmInteractablePoint (line 98) | interface PixelFarmInteractablePoint {
type PixelFarmInteractableTarget (line 104) | interface PixelFarmInteractableTarget {
function gridCellKey (line 120) | function gridCellKey(row: number, column: number): string {
function compareGridCells (line 124) | function compareGridCells(left: PixelFarmGridCell, right: PixelFarmGridC...
function measureCellBounds (line 132) | function measureCellBounds(cells: readonly PixelFarmGridCell[]): PixelFa...
function cellDistance (line 153) | function cellDistance(left: PixelFarmGridCell, right: PixelFarmGridCell)...
function canSpawnFromWalkableSet (line 157) | function canSpawnFromWalkableSet(
function pickRandomCells (line 175) | function pickRandomCells(
function collisionRectForCell (line 188) | function collisionRectForCell(cell: PixelFarmGridCell): PixelFarmCollisi...
function collectWalkableCells (line 197) | function collectWalkableCells(bounds: PixelFarmCellBounds): PixelFarmGri...
function findCropTile (line 218) | function findCropTile(
constant ISLAND_WALKABLE_CELLS (line 232) | const ISLAND_WALKABLE_CELLS = collectWalkableCells({
constant COW_SPAWN_CELLS (line 238) | const COW_SPAWN_CELLS = collectWalkableCells(COW_SPAWN_BOUNDS);
constant CHICKEN_SPAWN_CELLS (line 239) | const CHICKEN_SPAWN_CELLS = collectWalkableCells(CHICKEN_SPAWN_BOUNDS);
constant COW_PEN_LAYOUT (line 240) | const COW_PEN_LAYOUT = createAnimalPenLayoutFromCells(
constant CHICKEN_PEN_LAYOUT (line 245) | const CHICKEN_PEN_LAYOUT = createAnimalPenLayoutFromCells(
class PixelFarmWorldRenderer (line 251) | class PixelFarmWorldRenderer {
method constructor (line 265) | constructor(config: PixelFarmWorldRendererConfig) {
method destroy (line 273) | destroy(): void {
method update (line 278) | update(deltaMs: number): void {
method setPausedAnimalInstanceId (line 292) | setPausedAnimalInstanceId(animalInstanceId: string | null): void {
method getAnimalGroup (line 296) | getAnimalGroup(): Phaser.Physics.Arcade.Group {
method getAnimals (line 300) | getAnimals(): readonly PixelFarmRenderedAnimal[] {
method getCropObjects (line 304) | getCropObjects(): readonly Phaser.GameObjects.Image[] {
method getInteractableTargets (line 308) | getInteractableTargets(): readonly PixelFarmInteractableTarget[] {
method getInteractableStructureVersion (line 312) | getInteractableStructureVersion(): number {
method render (line 316) | render(worldState: PixelFarmWorldState | null): void {
method clear (line 329) | private clear(): void {
method updateInteractableStructureVersion (line 345) | private updateInteractableStructureVersion(): void {
method renderMemoryPlants (line 359) | private renderMemoryPlants(
method renderNpcs (line 402) | private renderNpcs(npcs: readonly PixelFarmNpcState[]): void {
method renderAnimalPen (line 410) | private renderAnimalPen(
method addCropTile (line 444) | private addCropTile(
method penWorldBounds (line 458) | private penWorldBounds(layout: PixelFarmAnimalPenLayout): PixelFarmWor...
method worldPointToGridCell (line 477) | private worldPointToGridCell(worldX: number, worldY: number): PixelFar...
method animalCanOccupy (line 484) | private animalCanOccupy(layout: PixelFarmAnimalPenLayout) {
method pickChickenWalkTarget (line 529) | private pickChickenWalkTarget(currentX: number, currentY: number): Pha...
method worldRectToLocalRect (line 546) | private worldRectToLocalRect(
method createAnimal (line 560) | private createAnimal(
method createNpcInteractableTarget (line 629) | private createNpcInteractableTarget(
function createAnimalPenLayoutFromCells (line 663) | function createAnimalPenLayoutFromCells(
FILE: dashboard/app/src/lib/session.test.ts
constant API_KEY_KEY (line 14) | const API_KEY_KEY = "mem9-api-key";
constant SPACE_ID_KEY (line 15) | const SPACE_ID_KEY = "mem9-space-id";
constant LAST_ACTIVE_KEY (line 16) | const LAST_ACTIVE_KEY = "mem9-last-active";
constant REMEMBERED_API_KEY (line 17) | const REMEMBERED_API_KEY = "mem9-remembered-api-key";
constant REMEMBERED_SPACE_KEY (line 18) | const REMEMBERED_SPACE_KEY = "mem9-remembered-space";
FILE: dashboard/app/src/lib/session.ts
constant API_KEY_KEY (line 1) | const API_KEY_KEY = "mem9-api-key";
constant SPACE_ID_KEY (line 2) | const SPACE_ID_KEY = "mem9-space-id";
constant LAST_ACTIVE_KEY (line 3) | const LAST_ACTIVE_KEY = "mem9-last-active";
constant REMEMBERED_API_KEY (line 4) | const REMEMBERED_API_KEY = "mem9-remembered-api-key";
constant REMEMBERED_SPACE_KEY (line 5) | const REMEMBERED_SPACE_KEY = "mem9-remembered-space";
constant IDLE_TIMEOUT_MS (line 6) | const IDLE_TIMEOUT_MS = 30 * 60 * 1000;
constant REMEMBER_ME_TTL_MS (line 7) | const REMEMBER_ME_TTL_MS = 15 * 24 * 60 * 60 * 1000;
constant MEM9_CONNECT_READY_EVENT (line 8) | const MEM9_CONNECT_READY_EVENT = "mem9-connect-ready";
constant MEM9_SPACE_HANDOFF_EVENT (line 9) | const MEM9_SPACE_HANDOFF_EVENT = "mem9-space-handoff";
type RememberedApiKey (line 11) | interface RememberedApiKey {
function writeSessionKey (line 16) | function writeSessionKey(storage: Storage, apiKey: string): void {
function removeLegacySessionState (line 21) | function removeLegacySessionState(storage: Storage): void {
function removeLegacyRememberedState (line 25) | function removeLegacyRememberedState(storage: Storage): void {
function writeSessionState (line 29) | function writeSessionState(storage: Storage, apiKey: string): void {
function migrateLegacySessionState (line 34) | function migrateLegacySessionState(storage: Storage): string | null {
function readRawRememberedApiKey (line 44) | function readRawRememberedApiKey(
function writeRememberedApiKey (line 78) | function writeRememberedApiKey(
function readRememberedApiKey (line 91) | function readRememberedApiKey(): RememberedApiKey | null {
function getApiKey (line 119) | function getApiKey(): string | null {
function setApiKey (line 129) | function setApiKey(apiKey: string, remember = false): void {
function getSpaceId (line 141) | function getSpaceId(): string | null {
function setSpaceId (line 145) | function setSpaceId(spaceId: string, remember = false): void {
function clearSpace (line 149) | function clearSpace(): void {
function touchActivity (line 157) | function touchActivity(): void {
function isSessionExpired (line 161) | function isSessionExpired(): boolean {
function restoreRememberedApiKey (line 167) | function restoreRememberedApiKey(): string | null {
function getActiveApiKey (line 175) | function getActiveApiKey(): string | null {
function restoreRememberedSpace (line 179) | function restoreRememberedSpace(): string | null {
function getActiveSpaceId (line 183) | function getActiveSpaceId(): string | null {
function isRememberedApiKey (line 187) | function isRememberedApiKey(apiKey: string): boolean {
function isRememberedSpace (line 195) | function isRememberedSpace(spaceId: string): boolean {
function maskSpaceId (line 199) | function maskSpaceId(id: string): string {
FILE: dashboard/app/src/lib/tag-signals.ts
constant LOW_SIGNAL_AGGREGATION_TAGS (line 1) | const LOW_SIGNAL_AGGREGATION_TAGS = new Set([
function normalizeTagSignal (line 10) | function normalizeTagSignal(value: string): string {
function isLowSignalAggregationTag (line 14) | function isLowSignalAggregationTag(value: string): boolean {
function filterLowSignalAggregationTags (line 18) | function filterLowSignalAggregationTags(tags: string[]): string[] {
FILE: dashboard/app/src/lib/theme.ts
type Theme (line 1) | type Theme = "light" | "dark" | "system";
constant STORAGE_KEY (line 3) | const STORAGE_KEY = "mem9-theme";
function getStoredTheme (line 5) | function getStoredTheme(): Theme {
function setStoredTheme (line 11) | function setStoredTheme(theme: Theme): void {
function applyTheme (line 16) | function applyTheme(theme: Theme): void {
function initTheme (line 24) | function initTheme(): void {
FILE: dashboard/app/src/lib/time.ts
function formatRelativeTime (line 3) | function formatRelativeTime(t: TFunction, isoDate: string): string {
FILE: dashboard/app/src/lib/utils.ts
function cn (line 4) | function cn(...inputs: ClassValue[]): string {
FILE: dashboard/app/src/pages/connect-loader.ts
type ConnectRouteLoaderData (line 10) | interface ConnectRouteLoaderData {
constant EMPTY_CONNECT_ROUTE_LOADER_DATA (line 16) | const EMPTY_CONNECT_ROUTE_LOADER_DATA: ConnectRouteLoaderData = {
function getInvalidConnectErrorMessage (line 22) | function getInvalidConnectErrorMessage(): string {
function loadConnectRouteData (line 26) | async function loadConnectRouteData(): Promise<ConnectRouteLoaderData> {
FILE: dashboard/app/src/pages/connect.test.tsx
function setLoaderData (line 58) | function setLoaderData(next: ConnectRouteLoaderData): void {
function currentURL (line 62) | function currentURL(): string {
function getByExactText (line 66) | function getByExactText(text: string): HTMLElement {
function getInlineCodeTokens (line 70) | function getInlineCodeTokens(): HTMLElement[] {
FILE: dashboard/app/src/pages/connect.tsx
function getOpenerOrigin (line 20) | function getOpenerOrigin(): string | null {
function InlineToken (line 32) | function InlineToken({ children }: { children?: ReactNode }) {
function ConnectPage (line 40) | function ConnectPage() {
FILE: dashboard/app/src/pages/pixel-farm-editor.tsx
type LayerState (line 43) | type LayerState = Omit<PixelFarmLayer, "mask"> & { mask: string[] };
type ObjectState (line 44) | type ObjectState = PixelFarmObjectPlacement;
type ObjectGroupState (line 45) | type ObjectGroupState = PixelFarmObjectGroup;
type CollisionState (line 46) | type CollisionState = PixelFarmCollisionCell;
type TerrainTool (line 47) | type TerrainTool = "paint" | "erase" | "fill" | "rectangle";
type ObjectTool (line 48) | type ObjectTool = "place" | "erase";
type CollisionTool (line 49) | type CollisionTool = "paint" | "erase";
type CollisionBrushSize (line 50) | type CollisionBrushSize = 1 | 2;
type EditorMode (line 51) | type EditorMode = "terrain" | "objects" | "collision";
type ObjectPaletteSelection (line 53) | interface ObjectPaletteSelection {
type ContentState (line 58) | interface ContentState {
type HistoryState (line 65) | interface HistoryState {
type DragState (line 71) | interface DragState {
type EditorState (line 91) | interface EditorState {
type HoveredCell (line 104) | interface HoveredCell {
type HoveredCollision (line 109) | interface HoveredCollision {
type ObjectStampTile (line 114) | interface ObjectStampTile {
constant CELL_SIZE_MIN (line 121) | const CELL_SIZE_MIN = 12;
constant CELL_SIZE_MAX (line 122) | const CELL_SIZE_MAX = 64;
constant CELL_SIZE_STEP (line 123) | const CELL_SIZE_STEP = 2;
constant INITIAL_CELL_SIZE (line 124) | const INITIAL_CELL_SIZE = 32;
constant PALETTE_CELL_SIZE (line 125) | const PALETTE_CELL_SIZE = 28;
constant MAX_HISTORY (line 126) | const MAX_HISTORY = 100;
constant DRAFT_STORAGE_KEY (line 127) | const DRAFT_STORAGE_KEY = "pixel-farm-mask-editor-draft-v11";
constant EXPORT_ENDPOINT (line 128) | const EXPORT_ENDPOINT = "/your-memory/__pixel-farm/export-generated-mask...
constant OBJECT_LAYER_ID (line 129) | const OBJECT_LAYER_ID = "objects";
constant DEFAULT_SELECTED_TILE (line 130) | const DEFAULT_SELECTED_TILE: PixelFarmAssetTileSelection = {
constant COPY (line 134) | const COPY = {
function cloneLayers (line 192) | function cloneLayers(): LayerState[] {
function cloneObjects (line 204) | function cloneObjects(): ObjectState[] {
function cloneObjectGroups (line 208) | function cloneObjectGroups(): ObjectGroupState[] {
function cloneCollisions (line 212) | function cloneCollisions(): CollisionState[] {
function cloneContent (line 216) | function cloneContent(): ContentState {
function sameContent (line 225) | function sameContent(left: ContentState, right: ContentState): boolean {
function appendPast (line 249) | function appendPast(past: ContentState[], snapshot: ContentState): Conte...
function buildEmptyMask (line 257) | function buildEmptyMask(rows: number, columns: number): string[] {
function defaultObjectLayer (line 261) | function defaultObjectLayer(): LayerState {
function ensureObjectLayer (line 282) | function ensureObjectLayer(layers: readonly LayerState[]): LayerState[] {
function findObjectAtCell (line 288) | function findObjectAtCell(
function nextObjectID (line 304) | function nextObjectID(objects: readonly ObjectState[]): string {
function nextObjectGroupID (line 316) | function nextObjectGroupID(groups: readonly ObjectGroupState[]): string {
function paletteFrameCell (line 328) | function paletteFrameCell(sourceId: PixelFarmAssetSourceId, frame: numbe...
function buildObjectStampTiles (line 337) | function buildObjectStampTiles(selection: ObjectPaletteSelection): Objec...
function defaultGroupSortMarker (line 364) | function defaultGroupSortMarker(
function nextCollisionID (line 386) | function nextCollisionID(collisions: readonly CollisionState[]): string {
function collisionPlacementKey (line 398) | function collisionPlacementKey(halfTileRow: number, halfTileColumn: numb...
function collisionCellKey (line 402) | function collisionCellKey(segment: Pick<CollisionState, "halfTileRow" | ...
function findCollisionIndex (line 406) | function findCollisionIndex(
function collisionStyle (line 416) | function collisionStyle(
function collectCollisionBrushCells (line 431) | function collectCollisionBrushCells(
function layerIndexById (line 450) | function layerIndexById(layers: readonly LayerState[], layerId: string):...
function setTileOverride (line 454) | function setTileOverride(
function updateMaskCell (line 486) | function updateMaskCell(mask: string[], row: number, column: number, fil...
function collectMaskArea (line 503) | function collectMaskArea(mask: readonly string[], row: number, column: n...
function collectMaskRect (line 537) | function collectMaskRect(
function sameTileSelection (line 568) | function sameTileSelection(
function normalizeOverrideTile (line 575) | function normalizeOverrideTile(
function mutateLayerCells (line 586) | function mutateLayerCells(
function sanitizeAssetTileSelection (line 627) | function sanitizeAssetTileSelection(input: unknown): PixelFarmAssetTileS...
function sanitizeTileOverride (line 651) | function sanitizeTileOverride(input: unknown): PixelFarmTileOverride | n...
function pruneOverrideMap (line 665) | function pruneOverrideMap(
function sanitizeMaskRows (line 694) | function sanitizeMaskRows(input: unknown, fallback: readonly string[]): ...
function sanitizeLayerList (line 708) | function sanitizeLayerList(input: unknown, fallback: readonly LayerState...
function sanitizeObjectList (line 757) | function sanitizeObjectList(input: unknown, layers: readonly LayerState[...
function sanitizeObjectGroupList (line 811) | function sanitizeObjectGroupList(input: unknown): ObjectGroupState[] {
function sanitizeCollisionList (line 854) | function sanitizeCollisionList(input: unknown): CollisionState[] {
function frameStyle (line 955) | function frameStyle(sourceId: PixelFarmAssetSourceId, frame: number, siz...
function sourceColor (line 969) | function sourceColor(sourceId: PixelFarmAssetSourceId): string {
function previewTile (line 984) | function previewTile(layer: LayerState, row: number, column: number): Pi...
function previewTilesForLayers (line 992) | function previewTilesForLayers(
function backgroundColor (line 1019) | function backgroundColor(layers: readonly LayerState[], row: number, col...
function loadDraftState (line 1030) | function loadDraftState(): EditorState {
function nextLayerID (line 1159) | function nextLayerID(layers: readonly LayerState[]): string {
function nextLayerLabel (line 1171) | function nextLayerLabel(layers: readonly LayerState[]): string {
function PixelFarmEditorPage (line 1175) | function PixelFarmEditorPage() {
FILE: dashboard/app/src/pages/pixel-farm.tsx
function PixelFarmPage (line 21) | function PixelFarmPage() {
FILE: dashboard/app/src/pages/space.test.tsx
constant FIXED_NOW (line 24) | const FIXED_NOW = new Date("2026-03-21T12:00:00Z");
function getAnalysisCategoryButton (line 56) | function getAnalysisCategoryButton(category: string): HTMLButtonElement {
function getTimelineBucket (line 68) | function getTimelineBucket(index: number): Element {
function getFarmCta (line 78) | function getFarmCta(): HTMLElement {
function createMemory (line 90) | function createMemory(
function renderSpacePage (line 116) | function renderSpacePage() {
FILE: dashboard/app/src/pages/space.tsx
function SpacePage (line 17) | function SpacePage() {
FILE: dashboard/app/src/router.tsx
function PixelFarmRoutePage (line 26) | function PixelFarmRoutePage() {
function RootLayout (line 42) | function RootLay
Condensed preview — 592 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,630K chars).
[
{
"path": ".agents/plugins/marketplace.json",
"chars": 345,
"preview": "{\n \"name\": \"mem9-ai\",\n \"interface\": {\n \"displayName\": \"mem9\"\n },\n \"plugins\": [\n {\n \"name\": \"mem9\",\n "
},
{
"path": ".claude-plugin/marketplace.json",
"chars": 531,
"preview": "{\n \"name\": \"mem9\",\n \"owner\": {\n \"name\": \"mem9-ai\"\n },\n \"metadata\": {\n \"description\": \"Official mem9 plugins fo"
},
{
"path": ".github/workflows/deploy-dev.yml",
"chars": 2239,
"preview": "name: Deploy server to dev\n\non:\n push:\n branches: [main]\n paths:\n - 'server/**'\n\nenv:\n AWS_REGION: ap-south"
},
{
"path": ".github/workflows/server-plugin-checks.yml",
"chars": 2430,
"preview": "name: Server and plugin checks\n\non:\n pull_request:\n branches: [main]\n paths:\n - \".github/workflows/server-pl"
},
{
"path": ".github/workflows/sync-claude-plugin.yml",
"chars": 957,
"preview": "name: Sync claude-plugin to standalone repo\n\non:\n push:\n branches: [main]\n paths:\n - 'claude-plugin/**'\n\njob"
},
{
"path": ".gitignore",
"chars": 642,
"preview": "# OS\n.DS_Store\nThumbs.db\n\n# Editors\n.idea/\n.vscode/\n*.swp\n*.swo\n*~\n\n# Environment and secrets\n.env\n.env.local\n.publish.e"
},
{
"path": "AGENTS.md",
"chars": 10505,
"preview": "---\ntitle: mnemos — Agent context\n---\n\n## What this repo is\n\nmnemos is shared, cloud-persistent memory for coding agents"
},
{
"path": "CLAUDE.md",
"chars": 11,
"preview": "@AGENTS.md\n"
},
{
"path": "CONTRIBUTING.md",
"chars": 2158,
"preview": "# Contributing to mnemos\n\nThanks for your interest in contributing!\n\n## Development Setup\n\n### Server (Go)\n\n```bash\n# Pr"
},
{
"path": "LICENSE",
"chars": 10770,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Makefile",
"chars": 1075,
"preview": "MAKEFILE_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))\nIMG ?= $(REGISTRY)/mnemo-server:$(COMMIT)\n\n.PHO"
},
{
"path": "README.md",
"chars": 22429,
"preview": "<p align=\"center\">\n <img src=\"site/public/mem9-wordmark-square.svg\" alt=\"mem9\" width=\"180\" />\n</p>\n<p align=\"center\">\n "
},
{
"path": "benchmark/BASELINE.md",
"chars": 746,
"preview": "export MNEMO_LLM_MODEL=\"qwen3.6-flash\"\nexport OPENAI_JUDGE_MODEL=\"qwen3.6-plus\"\nexport OPENAI_CHAT_MODEL=\"qwen3.6-plus\"\n"
},
{
"path": "benchmark/MR-NIAH/AGENTS.md",
"chars": 2638,
"preview": "---\ntitle: benchmark/MR-NIAH — Benchmark harness\n---\n\n## Overview\n\nMR-NIAH is a bridge from the MiniMax benchmark corpus"
},
{
"path": "benchmark/MR-NIAH/README.md",
"chars": 4688,
"preview": "# MR-NIAH (OpenClaw) Benchmark Harness\n\nMR-NIAH is our proving ground for turning legacy multi-turn chatbot benchmarks i"
},
{
"path": "benchmark/MR-NIAH/USAGE.md",
"chars": 11442,
"preview": "# MR-NIAH Usage Guide\n\nThis document explains how to prepare an OpenClaw profile, set up the required dependencies, and "
},
{
"path": "benchmark/MR-NIAH/fetch_data.py",
"chars": 10767,
"preview": "#!/usr/bin/env python3\n\"\"\"Fetch MR-NIAH dataset files from MiniMax's GitHub mirror.\n\nExamples:\n # Full mirror (both lan"
},
{
"path": "benchmark/MR-NIAH/mr-niah-transcript.py",
"chars": 24874,
"preview": "#!/usr/bin/env python3\n\"\"\"Generate OpenClaw session transcripts from MR-NIAH JSON/JSONL data.\n\nProcess:\n1. Read one or m"
},
{
"path": "benchmark/MR-NIAH/run_batch.py",
"chars": 81876,
"preview": "#!/usr/bin/env python3\n\"\"\"MR-NIAH batch runner.\n\nDesign goal:\n- For each generated session transcript in output/sessions"
},
{
"path": "benchmark/MR-NIAH/run_mem_compare.sh",
"chars": 62731,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nROOT=\"$(cd \"$(dirname \"$0\")/../..\" && pwd)\"\nMRNIAH_DIR=\"$ROOT/benchmark/MR-NIAH\"\n"
},
{
"path": "benchmark/MR-NIAH/score.py",
"chars": 18110,
"preview": "#!/usr/bin/env python3\n\"\"\"MR-NIAH scoring helper (mirrors MiniMax scoring logic).\n\nReads `results/predictions.jsonl` (fr"
},
{
"path": "benchmark/README.md",
"chars": 2338,
"preview": "# mem9 Benchmark Harnesses\n\nThis directory contains benchmark helpers and datasets for comparing OpenClaw's built-in fil"
},
{
"path": "benchmark/locomo/README.md",
"chars": 1917,
"preview": "# LoCoMo Benchmark for mem9\n\nThis harness evaluates `mem9` against the [LoCoMo](https://github.com/snap-research/locomo)"
},
{
"path": "benchmark/locomo/USAGE.md",
"chars": 3801,
"preview": "# LoCoMo Benchmark Usage\n\nThis guide shows how to run the `mem9` LoCoMo benchmark end-to-end.\n\n## Prerequisites\n\nYou nee"
},
{
"path": "benchmark/locomo/package.json",
"chars": 359,
"preview": "{\n \"name\": \"mem9-benchmark-locomo\",\n \"private\": true,\n \"version\": \"0.0.1\",\n \"type\": \"module\",\n \"scripts\": {\n \"st"
},
{
"path": "benchmark/locomo/src/cli.ts",
"chars": 6585,
"preview": "import type { BenchmarkOutput, LoCoMoSample, QAResult } from './types.js'\n\nimport { mkdir, readFile, writeFile } from 'n"
},
{
"path": "benchmark/locomo/src/evaluation.ts",
"chars": 2239,
"preview": "import type { QACategory } from './types.js'\n\nconst ARTICLES = new Set(['a', 'an', 'and', 'the'])\n\nconst normalizeAnswer"
},
{
"path": "benchmark/locomo/src/ingest.ts",
"chars": 4496,
"preview": "import type { DialogTurn, LoCoMoSample } from './types.js'\n\nimport { readFile, writeFile } from 'node:fs/promises'\n\nimpo"
},
{
"path": "benchmark/locomo/src/llm.ts",
"chars": 3125,
"preview": "import type { QACategory } from './types.js'\n\nimport { env } from 'node:process'\n\nconst SYSTEM_PROMPT = 'You are a helpf"
},
{
"path": "benchmark/locomo/src/mem9.ts",
"chars": 2703,
"preview": "import type { MnemoMemory } from './types.js'\n\nimport { env } from 'node:process'\n\nconst getEnv = (name: string, fallbac"
},
{
"path": "benchmark/locomo/src/retrieve.ts",
"chars": 556,
"preview": "import { getAgentId, getRetrievalLimit, searchMemories } from './mem9.js'\n\nexport const getContext = async (sessionId: s"
},
{
"path": "benchmark/locomo/src/stats.ts",
"chars": 2055,
"preview": "/* eslint-disable no-console */\nimport type { BenchmarkStats, QACategory, QAResult } from './types.js'\n\nconst CATEGORIES"
},
{
"path": "benchmark/locomo/src/types.ts",
"chars": 1370,
"preview": "export interface BenchmarkOutput {\n meta: {\n base_url: string\n data_file: string\n model: string\n tenant_id:"
},
{
"path": "benchmark/locomo/tsconfig.json",
"chars": 321,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"NodeNext\",\n \"moduleResolution\": \"NodeNext\",\n \"stri"
},
{
"path": "benchmark/prompts/example.yaml",
"chars": 426,
"preview": "name: simple-recall-smoke\ndescription: Minimal A/B recall scenario for file memory vs mem9.\nprompts:\n - \"[store] Rememb"
},
{
"path": "benchmark/results/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "benchmark/scripts/benchmark.sh",
"chars": 11468,
"preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nROOT=\"$(cd \"$(dirname \"$0\")/../..\" && pwd)\"\n\n# ----------------------------------"
},
{
"path": "benchmark/scripts/drive-session.py",
"chars": 8732,
"preview": "#!/usr/bin/env python3\n\"\"\"\ndrive-session.py — Parallel prompt driver for mem9 Layer 2b benchmarks.\n\nSends identical prom"
},
{
"path": "benchmark/scripts/report.py",
"chars": 13770,
"preview": "#!/usr/bin/env python3\n\"\"\"\nreport.py — Generate an HTML report from benchmark-results.json.\n\nRenders A/B test results as"
},
{
"path": "benchmark/workspace/IDENTITY.md",
"chars": 75,
"preview": "# IDENTITY\n\nName: Nine\nRole: Collaborator\nStyle: Precise, factual, minimal\n"
},
{
"path": "benchmark/workspace/SOUL.md",
"chars": 257,
"preview": "# SOUL\n\nYou are a helpful AI assistant participating in my daily life.\nBe concise, accurate, and direct in your response"
},
{
"path": "benchmark/workspace/USER.md",
"chars": 35,
"preview": "# USER\n\nName: Master\nTimezone: UTC\n"
},
{
"path": "claude-plugin/.claude-plugin/plugin.json",
"chars": 399,
"preview": "{\n \"name\": \"mem9\",\n \"version\": \"0.3.1\",\n \"description\": \"Persistent cloud memory for Claude Code. Requires Node.js 18"
},
{
"path": "claude-plugin/AGENTS.md",
"chars": 1613,
"preview": "---\ntitle: claude-plugin — Claude Code hooks and skills\n---\n\n## Overview\n\nClaude Code integration uses bash hooks plus J"
},
{
"path": "claude-plugin/README.md",
"chars": 3197,
"preview": "# Mem9 Claude Code Plugin\n\nPersistent cloud memory for Claude Code.\n\n## Install\n\nInstall from your terminal with the Cla"
},
{
"path": "claude-plugin/hooks/common.sh",
"chars": 9087,
"preview": "#!/usr/bin/env bash\n# common.sh — Shared helpers for mem9 hooks.\n\nset -euo pipefail\n\nMEM9_SCRIPT_DIR=\"$(cd \"$(dirname \"$"
},
{
"path": "claude-plugin/hooks/hooks.json",
"chars": 1135,
"preview": "{\n \"description\": \"Mem9 memory hooks — automatic bootstrap, recall, and ingest\",\n \"hooks\": {\n \"SessionStart\": [\n "
},
{
"path": "claude-plugin/hooks/lib/hook-json.mjs",
"chars": 2254,
"preview": "#!/usr/bin/env node\n// @ts-check\n\nimport path from \"node:path\";\nimport { readFileSync } from \"node:fs\";\nimport { fileURL"
},
{
"path": "claude-plugin/hooks/lib/memories-formatter.mjs",
"chars": 2852,
"preview": "#!/usr/bin/env node\n// @ts-check\n\nimport path from \"node:path\";\nimport { readFileSync } from \"node:fs\";\nimport { fileURL"
},
{
"path": "claude-plugin/hooks/lib/transcript-parser.mjs",
"chars": 7834,
"preview": "#!/usr/bin/env node\n// @ts-check\n\nimport path from \"node:path\";\nimport { readFileSync } from \"node:fs\";\nimport { fileURL"
},
{
"path": "claude-plugin/hooks/pre-compact.sh",
"chars": 722,
"preview": "#!/usr/bin/env bash\n# pre-compact.sh — Upload a larger recent window before compaction.\n\nset -euo pipefail\n\nSCRIPT_DIR=\""
},
{
"path": "claude-plugin/hooks/session-end.sh",
"chars": 711,
"preview": "#!/usr/bin/env bash\n# session-end.sh — Best-effort light flush on session end.\n\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(d"
},
{
"path": "claude-plugin/hooks/session-start.sh",
"chars": 2627,
"preview": "#!/usr/bin/env bash\n# session-start.sh — Check Node, auto-provision an API key if missing, and emit a short status.\n\nset"
},
{
"path": "claude-plugin/hooks/stop.sh",
"chars": 683,
"preview": "#!/usr/bin/env bash\n# stop.sh — Upload the last completed turn as structured messages.\n\nset -euo pipefail\n\nSCRIPT_DIR=\"$"
},
{
"path": "claude-plugin/hooks/user-prompt-submit.sh",
"chars": 2306,
"preview": "#!/usr/bin/env bash\n# user-prompt-submit.sh — Recall relevant memories on each user turn.\n\nset -euo pipefail\n\nSCRIPT_DIR"
},
{
"path": "claude-plugin/skills/recall/SKILL.md",
"chars": 1591,
"preview": "---\ndescription: Use when the current request needs relevant memories from Mem9.\ncontext: fork\nallowed-tools:\n - Bash\n "
},
{
"path": "claude-plugin/skills/setup/SKILL.md",
"chars": 1910,
"preview": "---\ndescription: Use when Mem9 needs to be initialized, repaired, or checked in this Claude Code environment.\ncontext: f"
},
{
"path": "claude-plugin/skills/store/SKILL.md",
"chars": 1438,
"preview": "---\ndescription: Use when the user explicitly asks Claude to remember one fact, preference, or instruction in Mem9.\ncont"
},
{
"path": "claude-plugin/tsconfig.json",
"chars": 239,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"NodeNext\",\n \"moduleResolution\": \"NodeNext\",\n \"allo"
},
{
"path": "cli/AGENTS.md",
"chars": 1086,
"preview": "---\ntitle: cli — Standalone Go CLI\n---\n\n## Overview\n\n`cli/` is a separate Go module (`github.com/qiffang/mnemos/cli`) us"
},
{
"path": "cli/README.md",
"chars": 2993,
"preview": "# mnemo CLI\n\nCommand-line tool for testing mnemo-server REST API endpoints.\n\n## Installation\n\n```bash\ncd cli\ngo build -o"
},
{
"path": "cli/go.mod",
"chars": 198,
"preview": "module github.com/qiffang/mnemos/cli\n\ngo 1.23\n\nrequire github.com/spf13/cobra v1.8.1\n\nrequire (\n\tgithub.com/inconshrevea"
},
{
"path": "cli/go.sum",
"chars": 896,
"preview": "github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/inconshreveabl"
},
{
"path": "cli/main.go",
"chars": 26567,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/file"
},
{
"path": "codex-plugin/.codex-plugin/plugin.json",
"chars": 356,
"preview": "{\n \"name\": \"mem9\",\n \"version\": \"0.2.2\",\n \"description\": \"Persistent memory for Codex. Run $mem9:setup once, then mem9"
},
{
"path": "codex-plugin/.gitignore",
"chars": 14,
"preview": ".tmp/\n.tmp-*/\n"
},
{
"path": "codex-plugin/AGENTS.md",
"chars": 1716,
"preview": "---\ntitle: codex-plugin — Codex hooks and skills\n---\n\n## Purpose\n\nCodex plugin package for mem9. It installs managed Cod"
},
{
"path": "codex-plugin/README.md",
"chars": 9105,
"preview": "# Codex Plugin for mem9\n\nPersistent memory for [Codex](https://developers.openai.com/codex).\n\nAfter setup, it does two t"
},
{
"path": "codex-plugin/bootstrap-hooks/session-start.mjs",
"chars": 119,
"preview": "// @ts-check\n\nimport { runHookShim } from \"./shared/bootstrap.mjs\";\n\nrunHookShim(\"session-start.mjs\").catch(() => {});\n"
},
{
"path": "codex-plugin/bootstrap-hooks/shared/bootstrap.mjs",
"chars": 10341,
"preview": "// @ts-check\n\nimport {\n appendFileSync,\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n} from \"node:fs\";\nimp"
},
{
"path": "codex-plugin/bootstrap-hooks/stop.mjs",
"chars": 110,
"preview": "// @ts-check\n\nimport { runHookShim } from \"./shared/bootstrap.mjs\";\n\nrunHookShim(\"stop.mjs\").catch(() => {});\n"
},
{
"path": "codex-plugin/bootstrap-hooks/user-prompt-submit.mjs",
"chars": 124,
"preview": "// @ts-check\n\nimport { runHookShim } from \"./shared/bootstrap.mjs\";\n\nrunHookShim(\"user-prompt-submit.mjs\").catch(() => {"
},
{
"path": "codex-plugin/hooks/session-start.mjs",
"chars": 8163,
"preview": "// @ts-check\n\nimport { readFileSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { loadRuntimeSta"
},
{
"path": "codex-plugin/hooks/shared/debug.mjs",
"chars": 4042,
"preview": "// @ts-check\n\nimport { appendFileSync, mkdirSync } from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path"
},
{
"path": "codex-plugin/hooks/shared/format.mjs",
"chars": 2042,
"preview": "// @ts-check\n\nconst START_TAG = \"<relevant-memories>\";\nconst END_TAG = \"</relevant-memories>\";\n\n/**\n * @typedef {{\n * "
},
{
"path": "codex-plugin/hooks/shared/transcript.mjs",
"chars": 6771,
"preview": "// @ts-check\n\nimport { stripInjectedMemories } from \"./format.mjs\";\n\n/**\n * @typedef {{\n * role: \"user\" | \"assistant\","
},
{
"path": "codex-plugin/hooks/stop.mjs",
"chars": 5925,
"preview": "// @ts-check\n\nimport { readFileSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { loadRuntimeSta"
},
{
"path": "codex-plugin/hooks/user-prompt-submit.mjs",
"chars": 5535,
"preview": "// @ts-check\n\nimport { readFileSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { loadRuntimeSta"
},
{
"path": "codex-plugin/lib/config.mjs",
"chars": 20293,
"preview": "// @ts-nocheck\n\nimport { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport os from \"node:os\";\nimport path f"
},
{
"path": "codex-plugin/lib/http.mjs",
"chars": 1164,
"preview": "// @ts-nocheck\n\nimport { DEFAULT_REQUEST_TIMEOUT_MS } from \"./config.mjs\";\n\n/**\n * @typedef {{\n * method?: string,\n * "
},
{
"path": "codex-plugin/lib/project-root.mjs",
"chars": 617,
"preview": "// @ts-nocheck\n\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\nexport function resolveProjectRoot("
},
{
"path": "codex-plugin/lib/skill-runtime.mjs",
"chars": 2673,
"preview": "// @ts-nocheck\n\nimport { loadRuntimeStateFromDisk } from \"./config.mjs\";\n\nfunction legacyPausedSource(state) {\n if (sta"
},
{
"path": "codex-plugin/lib/update-check.mjs",
"chars": 17837,
"preview": "// @ts-check\n\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport path from \"n"
},
{
"path": "codex-plugin/package.json",
"chars": 936,
"preview": "{\n \"name\": \"@mem9/codex-plugin\",\n \"version\": \"0.2.2\",\n \"description\": \"Persistent memory for Codex with setup-driven "
},
{
"path": "codex-plugin/skills/cleanup/SKILL.md",
"chars": 1463,
"preview": "---\ndescription: Remove mem9-managed Codex files before reinstalling, resetting, or uninstalling mem9.\ncontext: fork\nall"
},
{
"path": "codex-plugin/skills/cleanup/scripts/cleanup.mjs",
"chars": 21468,
"preview": "#!/usr/bin/env node\n// @ts-nocheck\n\nimport {\n accessSync,\n constants,\n existsSync,\n readFileSync,\n rmSync,\n writeF"
},
{
"path": "codex-plugin/skills/recall/SKILL.md",
"chars": 868,
"preview": "---\ndescription: Recall mem9 memories when the user explicitly asks for stored context.\ncontext: fork\nallowed-tools:\n -"
},
{
"path": "codex-plugin/skills/recall/agents/openai.yaml",
"chars": 43,
"preview": "policy:\n allow_implicit_invocation: false\n"
},
{
"path": "codex-plugin/skills/recall/scripts/recall.mjs",
"chars": 5946,
"preview": "#!/usr/bin/env node\n// @ts-nocheck\n\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { pathT"
},
{
"path": "codex-plugin/skills/setup/SKILL.md",
"chars": 6114,
"preview": "---\ndescription: Inspect and configure mem9 for Codex through the single setup entrypoint.\ncontext: fork\nallowed-tools:\n"
},
{
"path": "codex-plugin/skills/setup/scripts/setup.mjs",
"chars": 69442,
"preview": "#!/usr/bin/env node\n// @ts-nocheck\n\nimport {\n execFileSync,\n} from \"node:child_process\";\nimport {\n accessSync,\n const"
},
{
"path": "codex-plugin/skills/store/SKILL.md",
"chars": 843,
"preview": "---\ndescription: Store one user-approved fact, preference, or instruction in mem9.\ncontext: fork\nallowed-tools:\n - Bash"
},
{
"path": "codex-plugin/skills/store/agents/openai.yaml",
"chars": 43,
"preview": "policy:\n allow_implicit_invocation: false\n"
},
{
"path": "codex-plugin/skills/store/scripts/store.mjs",
"chars": 4364,
"preview": "#!/usr/bin/env node\n// @ts-nocheck\n\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { pathT"
},
{
"path": "codex-plugin/templates/hooks.json",
"chars": 791,
"preview": "{\n \"hooks\": {\n \"SessionStart\": [\n {\n \"hooks\": [\n {\n \"type\": \"command\",\n \""
},
{
"path": "codex-plugin/tests/bootstrap-hooks.test.mjs",
"chars": 11813,
"preview": "import assert from \"node:assert/strict\";\nimport { spawnSync } from \"node:child_process\";\nimport {\n existsSync,\n mkdirS"
},
{
"path": "codex-plugin/tests/cleanup.test.mjs",
"chars": 14604,
"preview": "// @ts-nocheck\n\nimport assert from \"node:assert/strict\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n "
},
{
"path": "codex-plugin/tests/debug.test.mjs",
"chars": 2209,
"preview": "import assert from \"node:assert/strict\";\nimport {\n existsSync,\n readFileSync,\n rmSync,\n} from \"node:fs\";\nimport path "
},
{
"path": "codex-plugin/tests/plugin-files.test.mjs",
"chars": 7999,
"preview": "import assert from \"node:assert/strict\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport test from \"node:test"
},
{
"path": "codex-plugin/tests/recall.test.mjs",
"chars": 6252,
"preview": "import assert from \"node:assert/strict\";\nimport { mkdirSync, rmSync } from \"node:fs\";\nimport path from \"node:path\";\nimpo"
},
{
"path": "codex-plugin/tests/runtime-config.test.mjs",
"chars": 23198,
"preview": "// @ts-nocheck\n\nimport assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport { buildSessionStartMessag"
},
{
"path": "codex-plugin/tests/session-start.test.mjs",
"chars": 7353,
"preview": "import assert from \"node:assert/strict\";\nimport { spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, rmS"
},
{
"path": "codex-plugin/tests/setup.test.mjs",
"chars": 44709,
"preview": "// @ts-nocheck\n\nimport assert from \"node:assert/strict\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n "
},
{
"path": "codex-plugin/tests/smoke.test.mjs",
"chars": 403,
"preview": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n DEFAULT_AGENT_ID,\n DEFAULT_REQUEST_T"
},
{
"path": "codex-plugin/tests/stop.test.mjs",
"chars": 13615,
"preview": "import assert from \"node:assert/strict\";\nimport { spawn } from \"node:child_process\";\nimport { createServer } from \"node:"
},
{
"path": "codex-plugin/tests/store.test.mjs",
"chars": 5943,
"preview": "import assert from \"node:assert/strict\";\nimport { mkdirSync, rmSync } from \"node:fs\";\nimport path from \"node:path\";\nimpo"
},
{
"path": "codex-plugin/tests/test-temp.mjs",
"chars": 866,
"preview": "import { mkdtempSync, mkdirSync, rmSync, rmdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPat"
},
{
"path": "codex-plugin/tests/update-check.test.mjs",
"chars": 6722,
"preview": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n DEFAULT_UPDATE_CHECK,\n comparePlugin"
},
{
"path": "codex-plugin/tests/user-prompt-submit.test.mjs",
"chars": 8356,
"preview": "import assert from \"node:assert/strict\";\nimport { spawn } from \"node:child_process\";\nimport { createServer } from \"node:"
},
{
"path": "codex-plugin/tsconfig.json",
"chars": 436,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"NodeNext\",\n \"moduleResolution\": \"NodeNext\",\n \"allo"
},
{
"path": "dashboard/README.md",
"chars": 574,
"preview": "# Dashboard\n\nThis directory is the dedicated home for mem9 dashboard work.\n\n## Structure\n\n- `docs/` — product specs, UX "
},
{
"path": "dashboard/app/.gitignore",
"chars": 117,
"preview": "*.tsbuildinfo\n.vite/\n\n# Override root .env exclusion — this .env only has non-secret defaults\npackage-lock.json\n.env\n"
},
{
"path": "dashboard/app/AGENTS.md",
"chars": 10702,
"preview": "---\ntitle: dashboard/app — mem9 Dashboard SPA\n---\n\n## Overview\n\nReact SPA for the mem9 dashboard. Deployed at `mem9.ai/y"
},
{
"path": "dashboard/app/README.md",
"chars": 4849,
"preview": "# mem9 Dashboard App\n\n## Setup\n\n```bash\ncd dashboard/app\npnpm install\ncp .env.local.example .env.local\npnpm dev\n```\n\n## "
},
{
"path": "dashboard/app/components.json",
"chars": 391,
"preview": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"new-york\",\n \"rsc\": false,\n \"tsx\": true,\n \"tailwind\": "
},
{
"path": "dashboard/app/index.html",
"chars": 898,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "dashboard/app/package.json",
"chars": 1666,
"preview": "{\n \"name\": \"mem9-dashboard\",\n \"private\": true,\n \"version\": \"0.1.0\",\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vi"
},
{
"path": "dashboard/app/public/_redirects",
"chars": 273,
"preview": "# API proxy — server-side rewrite (no CORS)\n/your-memory/api/* https://api.mem9.ai/v1alpha2/mem9s/:splat 200\n/your-mem"
},
{
"path": "dashboard/app/scripts/sentry-sourcemaps.mjs",
"chars": 1366,
"preview": "import { readdirSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { spawnSync } from \"node:chi"
},
{
"path": "dashboard/app/src/api/analysis-cache.ts",
"chars": 944,
"preview": "import {\n clearCachedAnalysisResult,\n readCachedAnalysisResult,\n writeCachedAnalysisResult,\n} from \"./local-cache\";\ni"
},
{
"path": "dashboard/app/src/api/analysis-client.test.ts",
"chars": 5504,
"preview": "import { afterEach, describe, expect, it, vi } from \"vitest\";\n\nimport { analysisApi } from \"./analysis-client\";\n\ndescrib"
},
{
"path": "dashboard/app/src/api/analysis-client.ts",
"chars": 5347,
"preview": "import type {\n AnalysisApiErrorPayload,\n AnalysisJobSnapshotResponse,\n AnalysisJobUpdatesResponse,\n CreateDeepAnalys"
},
{
"path": "dashboard/app/src/api/analysis-helpers.test.ts",
"chars": 8370,
"preview": "import { describe, expect, it } from \"vitest\";\nimport { AnalysisApiError } from \"./analysis-client\";\nimport {\n buildFac"
},
{
"path": "dashboard/app/src/api/analysis-helpers.ts",
"chars": 10143,
"preview": "import type {\n AggregateSnapshot,\n AnalysisCategory,\n AnalysisFacetStat,\n AnalysisJobSnapshotResponse,\n AnalysisJob"
},
{
"path": "dashboard/app/src/api/analysis-matcher.test.ts",
"chars": 2998,
"preview": "import { describe, expect, it } from \"vitest\";\nimport {\n buildAnalysisCardsFromMatches,\n createAnalysisMatchMap,\n mat"
},
{
"path": "dashboard/app/src/api/analysis-matcher.ts",
"chars": 2580,
"preview": "import type {\n AnalysisCategory,\n AnalysisCategoryCard,\n MemoryAnalysisMatch,\n TaxonomyResponse,\n TaxonomyRuleDefin"
},
{
"path": "dashboard/app/src/api/analysis-queries.test.ts",
"chars": 9392,
"preview": "import { describe, expect, it } from \"vitest\";\nimport {\n ANALYSIS_AUTO_REFRESH_WINDOW_MS,\n createPollProgressState,\n "
},
{
"path": "dashboard/app/src/api/analysis-queries.ts",
"chars": 24213,
"preview": "import { startTransition, useEffect, useMemo, useRef, useState } from \"react\";\nimport { useQuery } from \"@tanstack/react"
},
{
"path": "dashboard/app/src/api/client.ts",
"chars": 655,
"preview": "import { features } from \"@/config/features\";\nimport type { DashboardProvider } from \"./provider\";\nimport { mockProvider"
},
{
"path": "dashboard/app/src/api/deep-analysis-queries.test.tsx",
"chars": 4278,
"preview": "import type { ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport"
},
{
"path": "dashboard/app/src/api/deep-analysis-queries.ts",
"chars": 6011,
"preview": "import { useEffect, useMemo, useState } from \"react\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/r"
},
{
"path": "dashboard/app/src/api/local-cache.ts",
"chars": 13296,
"preview": "import type {\n AnalysisCategory,\n AnalysisJobSnapshotResponse,\n MemoryAnalysisMatch,\n} from \"@/types/analysis\";\nimpor"
},
{
"path": "dashboard/app/src/api/mock-data.ts",
"chars": 12582,
"preview": "import type { Memory, SessionMessage, SpaceInfo } from \"@/types/memory\";\n\nexport const MOCK_SPACE_ID = \"a1b2c3d4-e5f6-78"
},
{
"path": "dashboard/app/src/api/provider-http.test.ts",
"chars": 6815,
"preview": "import { afterEach, describe, expect, it, vi } from \"vitest\";\n\nimport { upsertCachedMemories } from \"./local-cache\";\nimp"
},
{
"path": "dashboard/app/src/api/provider-http.ts",
"chars": 11208,
"preview": "import type { DashboardProvider } from \"./provider\";\nimport type {\n Memory,\n MemoryListParams,\n MemoryListResponse,\n "
},
{
"path": "dashboard/app/src/api/provider-mock.test.ts",
"chars": 375,
"preview": "import { describe, expect, it } from \"vitest\";\n\nimport { mockProvider } from \"./provider-mock\";\n\ndescribe(\"mockProvider\""
},
{
"path": "dashboard/app/src/api/provider-mock.ts",
"chars": 11354,
"preview": "import type { DashboardProvider } from \"./provider\";\nimport type {\n Memory,\n MemoryListParams,\n MemoryListResponse,\n "
},
{
"path": "dashboard/app/src/api/provider.ts",
"chars": 1461,
"preview": "import type {\n Memory,\n MemoryListParams,\n MemoryListResponse,\n MemoryCreateInput,\n MemoryUpdateInput,\n MemoryStat"
},
{
"path": "dashboard/app/src/api/queries.test.ts",
"chars": 4303,
"preview": "import { afterEach, describe, expect, it, vi } from \"vitest\";\nimport type { Memory, SessionMessage } from \"@/types/memor"
},
{
"path": "dashboard/app/src/api/queries.ts",
"chars": 6846,
"preview": "import {\n useQuery,\n useInfiniteQuery,\n useMutation,\n useQueryClient,\n keepPreviousData,\n} from \"@tanstack/react-qu"
},
{
"path": "dashboard/app/src/api/source-memories.test.ts",
"chars": 3912,
"preview": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport type { Memory } from \"@/types/memory\";\n"
},
{
"path": "dashboard/app/src/api/source-memories.ts",
"chars": 2148,
"preview": "import { useQuery } from \"@tanstack/react-query\";\nimport { api } from \"./client\";\nimport {\n clearCachedMemoriesForSpace"
},
{
"path": "dashboard/app/src/assets/ark-pixel-font-10px-monospaced-otf-v2026.02.27/OFL.txt",
"chars": 4485,
"preview": "Copyright (c) 2021, TakWolf (https://takwolf.com),\r\nwith Reserved Font Name \"Ark Pixel\".\r\n\r\nThis Font Software is licens"
},
{
"path": "dashboard/app/src/components/lang-toggle.tsx",
"chars": 1364,
"preview": "import { Globe, Check } from \"lucide-react\";\nimport { useTranslation } from \"react-i18next\";\nimport { Button } from \"@/c"
},
{
"path": "dashboard/app/src/components/pixel-farm/actor-preview-panel.tsx",
"chars": 9342,
"preview": "import { useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n PIXEL_FARM_BABY_COW_COLORS"
},
{
"path": "dashboard/app/src/components/pixel-farm/feedback-dialog.test.tsx",
"chars": 784,
"preview": "import \"@/i18n\";\nimport { fireEvent, render, screen } from \"@testing-library/react\";\nimport { describe, expect, it } fro"
},
{
"path": "dashboard/app/src/components/pixel-farm/feedback-dialog.tsx",
"chars": 6452,
"preview": "import { useState } from \"react\";\nimport { MessageSquareWarning, Check } from \"lucide-react\";\nimport { useTranslation } "
},
{
"path": "dashboard/app/src/components/pixel-farm/front-target-panel.tsx",
"chars": 1265,
"preview": "import type {\n PixelFarmInteractionDebugInfo,\n} from \"@/lib/pixel-farm/create-game\";\n\ninterface PixelFarmFrontTargetPan"
},
{
"path": "dashboard/app/src/components/pixel-farm/phaser-stage.tsx",
"chars": 12584,
"preview": "import { useEffect, useRef, useState } from \"react\";\nimport type Phaser from \"phaser\";\nimport i18n from \"@/i18n\";\nimport"
},
{
"path": "dashboard/app/src/components/pixel-farm/pointer-coordinates-panel.tsx",
"chars": 1028,
"preview": "import type { PixelFarmPointerDebugInfo } from \"@/lib/pixel-farm/create-game\";\n\ninterface PixelFarmPointerCoordinatesPan"
},
{
"path": "dashboard/app/src/components/pixel-farm/world-state-panel.test.tsx",
"chars": 2286,
"preview": "import { fireEvent, render, screen } from \"@testing-library/react\";\nimport { describe, expect, it } from \"vitest\";\nimpor"
},
{
"path": "dashboard/app/src/components/pixel-farm/world-state-panel.tsx",
"chars": 3871,
"preview": "import { useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport type { PixelFarmWorldQueryStat"
},
{
"path": "dashboard/app/src/components/space/add-dialog.tsx",
"chars": 2916,
"preview": "import { useState } from \"react\";\nimport type { TFunction } from \"i18next\";\nimport { Loader2 } from \"lucide-react\";\nimpo"
},
{
"path": "dashboard/app/src/components/space/analysis-panel.test.tsx",
"chars": 17443,
"preview": "import { fireEvent, render, screen, within } from \"@testing-library/react\";\nimport type { TFunction } from \"i18next\";\nim"
},
{
"path": "dashboard/app/src/components/space/analysis-panel.tsx",
"chars": 21381,
"preview": "import { useEffect, useMemo, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { TFunction } "
},
{
"path": "dashboard/app/src/components/space/deep-analysis-overlay.tsx",
"chars": 1194,
"preview": "import { useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\n/**\n * Full-viewport overlay wit"
},
{
"path": "dashboard/app/src/components/space/deep-analysis-tab.test.tsx",
"chars": 16718,
"preview": "import { fireEvent, render, screen, waitFor, within } from \"@testing-library/react\";\nimport { describe, expect, it, vi }"
},
{
"path": "dashboard/app/src/components/space/deep-analysis-tab.tsx",
"chars": 42401,
"preview": "import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport type { TFunction } from \"i18next\";\nimport { "
},
{
"path": "dashboard/app/src/components/space/delete-dialog.tsx",
"chars": 2479,
"preview": "import type { TFunction } from \"i18next\";\nimport { Loader2 } from \"lucide-react\";\nimport { Button } from \"@/components/u"
},
{
"path": "dashboard/app/src/components/space/detail-panel.tsx",
"chars": 11494,
"preview": "import { useEffect, useRef } from \"react\";\nimport type { TFunction } from \"i18next\";\nimport { toast } from \"sonner\";\nimp"
},
{
"path": "dashboard/app/src/components/space/edit-dialog.tsx",
"chars": 2871,
"preview": "import { useState, useEffect } from \"react\";\nimport type { TFunction } from \"i18next\";\nimport { Loader2 } from \"lucide-r"
},
{
"path": "dashboard/app/src/components/space/empty-state.tsx",
"chars": 1049,
"preview": "import type { TFunction } from \"i18next\";\nimport { Plus } from \"lucide-react\";\nimport { Button } from \"@/components/ui/b"
},
{
"path": "dashboard/app/src/components/space/export-dialog.tsx",
"chars": 3005,
"preview": "import { useState } from \"react\";\nimport type { TFunction } from \"i18next\";\nimport { Download, Loader2, CheckCircle2 } f"
},
{
"path": "dashboard/app/src/components/space/import-dialog.tsx",
"chars": 5138,
"preview": "import { useRef, useState } from \"react\";\nimport type { TFunction } from \"i18next\";\nimport { Upload, FileJson, Loader2, "
},
{
"path": "dashboard/app/src/components/space/import-status.tsx",
"chars": 3296,
"preview": "import type { TFunction } from \"i18next\";\nimport {\n CheckCircle2,\n Loader2,\n AlertCircle,\n Clock,\n FileJson,\n X,\n}"
},
{
"path": "dashboard/app/src/components/space/memory-card.test.tsx",
"chars": 1520,
"preview": "import \"@/i18n\";\nimport { render, screen } from \"@testing-library/react\";\nimport { describe, expect, it, vi } from \"vite"
},
{
"path": "dashboard/app/src/components/space/memory-card.tsx",
"chars": 5762,
"preview": "import type { TFunction } from \"i18next\";\nimport { toast } from \"sonner\";\nimport { Bookmark, Copy, MessageSquareQuote, T"
},
{
"path": "dashboard/app/src/components/space/memory-composition-chart.test.tsx",
"chars": 1406,
"preview": "import \"@/i18n\";\nimport { fireEvent, render, screen } from \"@testing-library/react\";\nimport { describe, expect, it, vi }"
},
{
"path": "dashboard/app/src/components/space/memory-composition-chart.tsx",
"chars": 10298,
"preview": "import { useMemo, useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport type { PulseCompositio"
},
{
"path": "dashboard/app/src/components/space/memory-farm-preparation-dialog.tsx",
"chars": 4171,
"preview": "import { useTranslation } from \"react-i18next\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n} fro"
},
{
"path": "dashboard/app/src/components/space/memory-farm-promo-card.test.tsx",
"chars": 954,
"preview": "import \"@/i18n\";\nimport { fireEvent, render, screen } from \"@testing-library/react\";\nimport { describe, expect, it, vi }"
},
{
"path": "dashboard/app/src/components/space/memory-farm-promo-card.tsx",
"chars": 3763,
"preview": "import type { MemoryFarmEntryStatus } from \"./use-memory-farm-entry-state\";\nimport { Loader2 } from \"lucide-react\";\nimpo"
},
{
"path": "dashboard/app/src/components/space/memory-insight-layout.test.ts",
"chars": 5833,
"preview": "import { describe, expect, it } from \"vitest\";\nimport {\n computeCanvasBounds,\n layoutLaneAnchors,\n layoutLaneColumn,\n"
},
{
"path": "dashboard/app/src/components/space/memory-insight-layout.ts",
"chars": 15946,
"preview": "export type InsightPoint = {\n x: number;\n y: number;\n};\n\nexport type InsightCircleItem = {\n id: string;\n diameter: n"
},
{
"path": "dashboard/app/src/components/space/memory-insight-overview.test.tsx",
"chars": 29216,
"preview": "import { fireEvent, render, screen, waitFor, within } from \"@testing-library/react\";\nimport { afterEach, describe, expec"
},
{
"path": "dashboard/app/src/components/space/memory-insight-overview.tsx",
"chars": 85791,
"preview": "import {\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type PointerEvent as ReactPointerEvent,\n}"
},
{
"path": "dashboard/app/src/components/space/memory-insight-relations.tsx",
"chars": 62374,
"preview": "import {\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n type CSSProperties,\n type PointerEvent as Re"
},
{
"path": "dashboard/app/src/components/space/memory-insight-workspace.test.tsx",
"chars": 7848,
"preview": "import { fireEvent, render, screen, waitFor } from \"@testing-library/react\";\nimport { describe, expect, it, vi } from \"v"
},
{
"path": "dashboard/app/src/components/space/memory-insight-workspace.tsx",
"chars": 3296,
"preview": "import { useState } from \"react\";\nimport { Network, Sparkles } from \"lucide-react\";\nimport { useTranslation } from \"reac"
},
{
"path": "dashboard/app/src/components/space/memory-overview-tabs.test.tsx",
"chars": 12054,
"preview": "import { fireEvent, render, screen } from \"@testing-library/react\";\nimport { afterEach, describe, expect, it, vi } from "
},
{
"path": "dashboard/app/src/components/space/memory-overview-tabs.tsx",
"chars": 8369,
"preview": "import { useState } from \"react\";\nimport { Monitor, Network } from \"lucide-react\";\nimport { useTranslation } from \"react"
},
{
"path": "dashboard/app/src/components/space/memory-pulse-overview.test.tsx",
"chars": 2079,
"preview": "import { render, screen } from \"@testing-library/react\";\nimport { describe, expect, it } from \"vitest\";\nimport { MemoryP"
},
{
"path": "dashboard/app/src/components/space/memory-pulse-overview.tsx",
"chars": 7114,
"preview": "import { useMemo } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { MemoryCompositionChart } from "
},
{
"path": "dashboard/app/src/components/space/memory-rhythm-chart.tsx",
"chars": 11393,
"preview": "import { useEffect, useId, useMemo, useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport type"
},
{
"path": "dashboard/app/src/components/space/memory-signal-stack.tsx",
"chars": 2631,
"preview": "import { useTranslation } from \"react-i18next\";\nimport { cn } from \"@/lib/utils\";\nimport type { PulseSignalItem } from \""
},
{
"path": "dashboard/app/src/components/space/mobile-analysis-sheet.tsx",
"chars": 2018,
"preview": "import type { TFunction } from \"i18next\";\nimport { AnalysisPanelBody } from \"@/components/space/analysis-panel\";\nimport "
},
{
"path": "dashboard/app/src/components/space/mobile-detail-sheet.test.tsx",
"chars": 7832,
"preview": "import \"@/i18n\";\nimport { fireEvent, render, screen, waitFor } from \"@testing-library/react\";\nimport { describe, expect,"
},
{
"path": "dashboard/app/src/components/space/mobile-detail-sheet.tsx",
"chars": 1401,
"preview": "import type { TFunction } from \"i18next\";\nimport { DetailPanelContent } from \"@/components/space/detail-panel\";\nimport {"
},
{
"path": "dashboard/app/src/components/space/mobile-panel-shell.tsx",
"chars": 6132,
"preview": "import { useEffect, useState, type ReactNode } from \"react\";\nimport { X } from \"lucide-react\";\nimport { Button } from \"@"
},
{
"path": "dashboard/app/src/components/space/session-preview.tsx",
"chars": 18424,
"preview": "import { useMemo, useState } from \"react\";\nimport type { TFunction } from \"i18next\";\nimport { Loader2, User, MessageSqua"
},
{
"path": "dashboard/app/src/components/space/space-page-layout.tsx",
"chars": 29946,
"preview": "import type { Dispatch, SetStateAction } from \"react\";\nimport {\n Search,\n BarChart3,\n Plus,\n LogOut,\n Download,\n U"
},
{
"path": "dashboard/app/src/components/space/space-selectors.test.ts",
"chars": 1838,
"preview": "import { describe, expect, it } from \"vitest\";\nimport type { Memory } from \"@/types/memory\";\nimport { selectDisplayedMem"
},
{
"path": "dashboard/app/src/components/space/space-selectors.ts",
"chars": 5440,
"preview": "import { formatInsightCategoryLabel } from \"@/lib/memory-insight\";\nimport {\n getCombinedTagsForMemory,\n type DerivedTa"
},
{
"path": "dashboard/app/src/components/space/space-tools.tsx",
"chars": 1463,
"preview": "import type { TFunction } from \"i18next\";\nimport { Settings2, Download, Upload, ClipboardList } from \"lucide-react\";\nimp"
},
{
"path": "dashboard/app/src/components/space/space-view-utils.ts",
"chars": 2436,
"preview": "import { useEffect, useState } from \"react\";\n\n// We deliberately keep two breakpoints rather than a single \"is mobile\" f"
},
{
"path": "dashboard/app/src/components/space/tag-strip.test.tsx",
"chars": 1206,
"preview": "import { fireEvent, render, screen } from \"@testing-library/react\";\nimport type { TFunction } from \"i18next\";\nimport { d"
},
{
"path": "dashboard/app/src/components/space/tag-strip.tsx",
"chars": 2143,
"preview": "import type { TFunction } from \"i18next\";\nimport type { DerivedTagOrigin } from \"@/lib/memory-derived-signals\";\n\nexport "
},
{
"path": "dashboard/app/src/components/space/time-range.tsx",
"chars": 1002,
"preview": "import type { TFunction } from \"i18next\";\nimport type { TimeRangePreset } from \"@/types/time-range\";\n\nconst PRESETS: Tim"
},
{
"path": "dashboard/app/src/components/space/topic-strip.tsx",
"chars": 3092,
"preview": "import type { TFunction } from \"i18next\";\nimport type { MemoryFacet, TopicSummary } from \"@/types/memory\";\n\nconst FACET_"
},
{
"path": "dashboard/app/src/components/space/use-memory-farm-entry-state.test.ts",
"chars": 3509,
"preview": "import { describe, expect, it, vi } from \"vitest\";\n\nvi.mock(\"@/api/local-cache\", () => ({\n readSyncState: vi.fn(),\n re"
}
]
// ... and 392 more files (download for full content)
About this extraction
This page contains the full source code of the mem9-ai/mem9 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 592 files (4.9 MB), approximately 1.3M tokens, and a symbol index with 4896 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.