v0.2.18

moorcheh-ai/memantov0.2.18Aug 28, 2026by het0814

AI Summary

Adds Pi coding-agent integration, hardens local management endpoints against cross-site token theft and DNS rebinding, updates OKF export to v0.2 spec, and fixes a Windows CLI UnicodeEncodeError.

Key Highlights

  • Pi (coding agent) integration via `memanto connect pi`
  • Security hardening: Loopback trust now requires Host header and non-cross-site Origin
  • OKF export conforms to the v0.2 spec with new frontmatter structure
  • Fix for Windows CLI `UnicodeEncodeError` when output is redirected

New Features

  • Pi coding agent integration
  • OKF export v0.2 spec compliance
  • Windows encoding fix
  • Hermes plugin 403 fix on profile startup

Full Release Notes

# Release Notes for v0.2.18

This release adds a Pi coding-agent integration to `memanto connect`, hardens local management endpoints against cross-site token theft and DNS rebinding, brings the OKF export bundle up to the v0.2 spec, and fixes a Windows `UnicodeEncodeError` that killed the CLI whenever its output was redirected or captured by an agent harness.

## New Features
- **Pi (coding agent) integration** (`memanto/cli/commands/connect.py`, `memanto/cli/connect/agent_registry.py`, `memanto/cli/connect/engine.py`, `memanto/cli/connect/templates.py`)
  - New `memanto connect pi` command with the same `--project-dir/-p` and `--global/-g` flags as the other agents.
  - Registers a `PI` agent definition: `AGENTS.md` instructions, skills at `.pi/skills` (project) or `~/.pi/agent/skills` (global).
  - Adds a new **code extension** artifact type to the connect engine (`extension_file` / `extension_global_dir` / `extension_local_dir` on `AgentDef`, plus `_install_extension` / `_remove_extension`), deploying a self-contained `memanto-sync.ts` into `~/.pi/agent/extensions/` or `.pi/extensions/`.
  - The extension runs `memanto memory sync --project-dir <cwd>` fire-and-forget only on a fresh session start (`reason === "startup"`, not `/resume`, `/fork`, or `/reload`), swallowing errors so it can never block Pi startup; it is a no-op on session shutdown.
  - Success notification fires only on exit code `0`, so a failed sync no longer reports "memory synced".
  - Extension spawn drops the platform-dependent `shell` option; extension removal is guarded at the call site the same way hooks and permissions already are.
  - Pi appears in the Connections UI with its official mark (`memanto/app/ui/static/index.html`).

## Improvements
- **OKF export conforms to the v0.2 spec** (`memanto/app/services/okf_export_service.py`, `memanto/cli/migrate/mappers.py`)
  - Root index now declares `okf_version: "0.2"`; per-section `index.md` files drop their non-conforming `type`/`title`/`timestamp` frontmatter.
  - Memory frontmatter replaces the bare `timestamp` field with a structured `generated: {by, at}` block, normalizing `by` into a qualified source (`human:`/`process:` prefix or a namespaced `a/b` form).
  - Context documents copied into the bundle without frontmatter now get a YAML-serialized `type: Context Document` / `title` header instead of being copied verbatim; the metrics overview gains `type: Metrics Overview` frontmatter.
  - Import (`map_okf`) reads `extra.generated.at` first and falls back to the legacy top-level `timestamp`, so v0.1 bundles still import with correct `created_at`.

## Bug Fixes
- **CLI crashed with `UnicodeEncodeError` when output was redirected on Windows** (`memanto/cli/__init__.py`)
  - Rich emits box-drawing characters, bullets and braille spinner frames. With stdout captured on Windows, Python encoded them with the locale code page (cp1252), killing commands mid-run: `UnicodeEncodeError: 'charmap' codec can't encode character '⠹'`. This hit shell pipelines, `> file`, CI logs, and agent harnesses that always pipe stdout — affecting `recall`, `status`, `forget` and the other 23 `console.status()` sites.
  - Redirected streams are now switched to UTF-8 once at CLI package import, before any Rich `Console` is constructed, so every call site is covered. `isatty()` is deliberately not used as the signal: on Windows `NUL` reports itself as a character device, so `command > NUL` looks like a tty while still encoding as cp1252. Streams already reporting UTF-8 are left untouched, and a failed `reconfigure()` can never stop the CLI from starting.
- **Hermes plugin returned 403 on profile startup** (`integrations/hermes-agents/hermes_memanto/provider.py`)
  - Session tokens are now persisted per profile in `<hermes_home>/profiles/<identity>/.memanto_session_token` (created `0o600`) and reloaded on startup, so a restarted profile no longer starts unauthenticated.
  - `remember`, `recall` and `answer` route through `_call_with_auth_retry`, which detects refreshable auth failures (`AuthenticationError`, `AuthorizationError`, `SessionExpiredError`, `InvalidSessionTokenError`), re-activates the agent, and retries once; refreshes are throttled to one every 5 seconds.
  - The client lock became a `threading.RLock` to prevent deadlock when a refresh re-enters `ensure_session()`, and the profile directory name is sanitized via `_sanitize_agent_id`.

## Security
- **Cross-site requests can no longer inherit loopback trust** (`memanto/app/routes/auth_deps.py`, `memanto/app/ui/routes/ui_router.py`)
  - `require_management_access` previously granted the server key to any request from a loopback client address. A page on any website could therefore drive the local management API from the user's browser — including activating an agent and reading back its `session_token`.
  - Loopback trust now additionally requires a loopback `Host` header and a non-cross-site browser request: the `Origin` must resolve to `localhost`/a loopback address (any port), and absent an `Origin`, `Sec-Fetch-Site: cross-site`/`same-site` is rejected.
  - The `Host` check closes DNS rebinding, where an attacker-controlled hostname resolving to `127.0.0.1` made a remote-origin request look local.
  - UI management endpoints apply the same `_is_cross_site_browser_request` guard in `_require_local`, returning `403` instead of serving the request.

## Tests
- `tests/test_cli_stream_encoding.py` — new suite covering the cp1252→UTF-8 switch, the `NUL`-as-tty case, UTF-8 alias spellings being left alone, streams lacking `reconfigure`, `reconfigure` failures never breaking startup, and Rich glyphs surviving a cp1252 stream.
- `tests/test_api.py` — cross-site loopback cannot create or activate an agent (and no `session_token` leaks into the response), loopback and cross-port loopback origins keep local management access, DNS-rebinding `Host` headers are rejected.
- `tests/test_ui_auth.py` — loopback cross-site `Origin` and `Sec-Fetch-Site` requests rejected; loopback origin accepted; remote origin rejected.
- `tests/test_connect_engine.py` / `tests/test_connect_detection.py` — Pi install deploys instructions, skill and extension; install is idempotent; remove deletes the extension; global paths match the `~/.pi/agent/` layout; agents without an extension deploy none; Pi detection keys off its own skill dir since it shares `AGENTS.md` with codex/opencode.
- `integrations/hermes-agents/tests/test_provider.py` — token persistence across restarts and auto-refresh on session expiration.
- `tests/test_unit.py` — Pi instruction-file path resolution (global and project-local); `tests/test_e2e.py` now uses a `localhost:8000` base URL so requests satisfy the new Host check.

## Full Changelog
Full Changelog: https://github.com/moorcheh-ai/memanto/compare/v0.2.17...v0.2.18