Merge pull request #55863 from NousResearch/bb/journey-edit-delete-followup

fix(journey): atomic memory writes + desktop lint fixups (follow-up to #55859)
This commit is contained in:
brooklyn! 2026-06-30 15:21:28 -05:00 committed by GitHub
commit d1af7e16cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 32 additions and 18 deletions

View file

@ -20,7 +20,6 @@ from __future__ import annotations
from pathlib import Path
from typing import Any
_MEMORY_DELIM = "\n§\n"
_MEMORY_FILES = {"memory": "MEMORY.md", "profile": "USER.md"}
@ -63,20 +62,18 @@ def _memory_local_index(source: str, global_index: int) -> int:
return global_index - sum(1 for c in cards if c.get("source") == "memory")
def _read_chunks(path: Path) -> list[str]:
"""Raw ``§``-delimited chunks, preserving formatting; empties dropped to
match ``_memory_cards`` indexing."""
text = path.read_text(encoding="utf-8")
return [c for c in text.split(_MEMORY_DELIM) if c.strip()]
def _locate_memory(source: str, gidx: int) -> tuple[Path, list[str], int]:
"""Resolve a memory card to its file, all chunks, and local index."""
"""Resolve a memory card to its file, all §-delimited entries, and local index.
Entries come from ``MemoryStore._read_file`` the same parser the memory
tool uses so journey indices stay aligned with what the graph renders.
"""
from tools.memory_tool import MemoryStore
path = _memories_dir() / _MEMORY_FILES[source]
if not path.exists():
raise ValueError(f"{path.name} not found")
chunks = _read_chunks(path)
chunks = MemoryStore._read_file(path)
local = _memory_local_index(source, gidx)
if not 0 <= local < len(chunks):
raise ValueError("memory node id is stale — refresh the graph")
@ -149,7 +146,7 @@ def _delete_memory(node_id: str) -> dict[str, Any]:
path, chunks, local = _locate_memory(source, gidx)
del chunks[local]
_write_chunks(path, chunks)
_write_memory(path, chunks)
return {"ok": True, "message": f"deleted memory from {path.name}"}
@ -184,7 +181,7 @@ def _edit_memory(node_id: str, content: str) -> dict[str, Any]:
path, chunks, local = _locate_memory(source, gidx)
chunks[local] = body
_write_chunks(path, chunks)
_write_memory(path, chunks)
return {"ok": True, "message": f"updated memory in {path.name}"}
@ -192,9 +189,12 @@ def _edit_memory(node_id: str, content: str) -> dict[str, Any]:
# ── Helpers ─────────────────────────────────────────────────────────────────
def _write_chunks(path: Path, chunks: list[str]) -> None:
body = _MEMORY_DELIM.join(c.strip() for c in chunks)
path.write_text(f"{body}\n" if body else "", encoding="utf-8")
def _write_memory(path: Path, chunks: list[str]) -> None:
"""Atomic temp-file + rename via the memory tool, so a concurrent reader
never sees a half-written file (and the §-join stays single-sourced)."""
from tools.memory_tool import MemoryStore
MemoryStore._write_file(path, [c.strip() for c in chunks if c.strip()])
def _clear_skill_cache() -> None:

View file

@ -10,13 +10,13 @@ import type { StarmapGraph } from '@/types/hermes'
import { computePalette, memoryInkFor, resolveRgb, rgba } from './color'
import { RING_OUTER, TILT, ZOOM_MAX, ZOOM_MIN } from './constants'
import { clamp, distToSegmentSq, fitScale, fitViewport, nodeRadius } from './geometry'
import { NodeContextMenu, type NodeMenuTarget } from './node-context-menu'
import { drawScene, drawScramble } from './render'
import { decodeShareCode, encodeShareCode, ShareCodeError } from './share-code'
import { ShareControls } from './share-controls'
import { buildSimulation } from './simulation'
import { formatDate } from './text'
import { buildTimeAxis, dateAtReveal, type TimeAxis } from './time-axis'
import { NodeContextMenu, type NodeMenuTarget } from './node-context-menu'
import { Timeline } from './timeline'
import type { FadeBuckets, MemoryCard, Palette, Ring, RingLabelRect, SimLink, SimNode, Viewport } from './types'
@ -918,10 +918,10 @@ export function StarMap({
<div className="relative min-h-0 flex-1 overflow-hidden" ref={wrapRef}>
<canvas
className="block touch-none select-none text-foreground"
onContextMenu={onContextMenu}
onDoubleClick={resetView}
onMouseDown={onMouseDown}
onMouseLeave={onMouseLeave}
onContextMenu={onContextMenu}
onMouseMove={onMouseMove}
onMouseUp={endDrag}
onWheel={onWheel}

View file

@ -112,3 +112,17 @@ def test_edit_skill_rewrites_and_validates(home):
def test_missing_skill_detail(home):
assert not lm.node_detail("nonexistent-skill")["ok"]
def test_memory_writes_match_memory_tool_format(home):
"""A journey mutation must leave the file byte-identical to what the memory
tool itself writes same §-join, no trailing-newline drift so the two
surfaces never fight over format and indices stay aligned."""
from tools.memory_tool import ENTRY_DELIMITER, MemoryStore
assert lm.edit_node("memory:memory:0", "alpha rewritten")["ok"]
path = home / "memories" / "MEMORY.md"
entries = MemoryStore._read_file(path)
assert entries == ["alpha rewritten", "beta note"]
assert path.read_text(encoding="utf-8") == ENTRY_DELIMITER.join(entries)