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 picommand with the same--project-dir/-pand--global/-gflags as the other agents. - Registers a
PIagent definition:AGENTS.mdinstructions, 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_dironAgentDef, plus_install_extension/_remove_extension), deploying a self-containedmemanto-sync.tsinto~/.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
shelloption; 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).
- New
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-sectionindex.mdfiles drop their non-conformingtype/title/timestampfrontmatter. - Memory frontmatter replaces the bare
timestampfield with a structuredgenerated: {by, at}block, normalizingbyinto a qualified source (human:/process:prefix or a namespaceda/bform). - Context documents copied into the bundle without frontmatter now get a YAML-serialized
type: Context Document/titleheader instead of being copied verbatim; the metrics overview gainstype: Metrics Overviewfrontmatter. - Import (
map_okf) readsextra.generated.atfirst and falls back to the legacy top-leveltimestamp, so v0.1 bundles still import with correctcreated_at.
- Root index now declares
Bug Fixes
- CLI crashed with
UnicodeEncodeErrorwhen 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 — affectingrecall,status,forgetand the other 23console.status()sites. - Redirected streams are now switched to UTF-8 once at CLI package import, before any Rich
Consoleis constructed, so every call site is covered.isatty()is deliberately not used as the signal: on WindowsNULreports itself as a character device, socommand > NULlooks like a tty while still encoding as cp1252. Streams already reporting UTF-8 are left untouched, and a failedreconfigure()can never stop the CLI from starting.
- 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:
- 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(created0o600) and reloaded on startup, so a restarted profile no longer starts unauthenticated. remember,recallandanswerroute 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.RLockto prevent deadlock when a refresh re-entersensure_session(), and the profile directory name is sanitized via_sanitize_agent_id.
- Session tokens are now persisted per profile in
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_accesspreviously 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 itssession_token.- Loopback trust now additionally requires a loopback
Hostheader and a non-cross-site browser request: theOriginmust resolve tolocalhost/a loopback address (any port), and absent anOrigin,Sec-Fetch-Site: cross-site/same-siteis rejected. - The
Hostcheck closes DNS rebinding, where an attacker-controlled hostname resolving to127.0.0.1made a remote-origin request look local. - UI management endpoints apply the same
_is_cross_site_browser_requestguard in_require_local, returning403instead of serving the request.
Tests
tests/test_cli_stream_encoding.py— new suite covering the cp1252→UTF-8 switch, theNUL-as-tty case, UTF-8 alias spellings being left alone, streams lackingreconfigure,reconfigurefailures never breaking startup, and Rich glyphs surviving a cp1252 stream.tests/test_api.py— cross-site loopback cannot create or activate an agent (and nosession_tokenleaks into the response), loopback and cross-port loopback origins keep local management access, DNS-rebindingHostheaders are rejected.tests/test_ui_auth.py— loopback cross-siteOriginandSec-Fetch-Siterequests 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 sharesAGENTS.mdwith 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.pynow uses alocalhost:8000base URL so requests satisfy the new Host check.
Full Changelog
Full Changelog: v0.2.17...v0.2.18