Compare commits

..

1 Commits

Author SHA1 Message Date
Timmy
9397d88657 fix(#1601): Restore MemPalace Fleet API polling, remove mock MCP
Some checks failed
CI / test (pull_request) Failing after 57s
CI / validate (pull_request) Failing after 58s
Review Approval Gate / verify-review (pull_request) Failing after 10s
BURN mode PR replaced the functional connectMemPalace() (which
fetches real stats from Fleet API on port 7771) with a mock
MCP server that returns hardcoded data and zeroes out stats.

The mock definition (line 3907) overwrote the real one (line
2772) because it was defined later in the same scope.

Fix: removed the mock connectMemPalace() definition. Now only
the Fleet API version exists — fetches /health and /wings from
port 7771, counts docs, computes compression ratio, updates UI.

JS syntax verified. One connectMemPalace definition remains.

Refs #1601
2026-04-15 12:34:11 -04:00
12 changed files with 25 additions and 685 deletions

View File

@@ -285,49 +285,6 @@ class AgentMemory:
logger.warning(f"Failed to store memory: {e}")
return None
def remember_alexander_request_response(
self,
*,
request_text: str,
response_text: str,
requester: str = "Alexander Whitestone",
source: str = "",
metadata: Optional[dict] = None,
) -> Optional[str]:
"""Store an Alexander request + wizard response artifact in the sovereign room."""
if not self._check_available():
logger.warning("Cannot store Alexander artifact — MemPalace unavailable")
return None
try:
from nexus.mempalace.searcher import add_memory
from nexus.mempalace.conversation_artifacts import build_request_response_artifact
artifact = build_request_response_artifact(
requester=requester,
responder=self.agent_name,
request_text=request_text,
response_text=response_text,
source=source,
)
extra_metadata = dict(artifact.metadata)
if metadata:
extra_metadata.update(metadata)
doc_id = add_memory(
text=artifact.text,
room=artifact.room,
wing=self.wing,
palace_path=self.palace_path,
source_file=source,
extra_metadata=extra_metadata,
)
logger.debug("Stored Alexander request/response artifact in sovereign room")
return doc_id
except Exception as e:
logger.warning(f"Failed to store Alexander artifact: {e}")
return None
def write_diary(
self,
summary: Optional[str] = None,

164
app.js
View File

@@ -10,10 +10,6 @@ import { MemoryOptimizer } from './nexus/components/memory-optimizer.js';
import { MemoryInspect } from './nexus/components/memory-inspect.js';
import { MemoryPulse } from './nexus/components/memory-pulse.js';
import { ReasoningTrace } from './nexus/components/reasoning-trace.js';
import {
createPortalRegistryWatcher,
fetchPortalRegistry,
} from './portal-registry.mjs';
// ═══════════════════════════════════════════
// NEXUS v1.1 — Portal System Update
@@ -87,7 +83,6 @@ let workshopPanelCanvas = null;
let workshopScanMat = null;
let workshopPanelRefreshTimer = 0;
let lastFocusedPortal = null;
let portalRegistryWatcher = null;
// ═══ VISITOR / OPERATOR MODE ═══
let uiMode = 'visitor'; // 'visitor' | 'operator'
@@ -736,25 +731,9 @@ async function init() {
// Load Portals from Registry
try {
const portalData = await fetchPortalRegistry({
registryUrl: './portals.json',
cacheBustParam: '_registry_ts',
});
applyPortalRegistry(portalData);
portalRegistryWatcher?.stop?.();
portalRegistryWatcher = createPortalRegistryWatcher({
loadRegistry: () => fetchPortalRegistry({
registryUrl: './portals.json',
cacheBustParam: '_registry_ts',
}),
applyRegistry: (nextRegistry) => applyPortalRegistry(nextRegistry, { announce: true }),
onError: (error) => {
console.error('Failed to hot-reload portals.json:', error);
},
});
await portalRegistryWatcher.prime(portalData);
portalRegistryWatcher.start();
const response = await fetch('./portals.json');
const portalData = await response.json();
createPortals(portalData);
} catch (e) {
console.error('Failed to load portals.json:', e);
addChatMessage('error', 'Portal registry offline. Check logs.');
@@ -1105,7 +1084,7 @@ function refreshWorkshopPanel() {
ctx.fillText(`HERMES STATUS: ${wsConnected ? 'ONLINE' : 'OFFLINE'}`, 40, 120);
ctx.fillStyle = '#7b5cff';
const contextName = activePortal ? activePortal.config.name.toUpperCase() : 'NEXUS CORE';
const contextName = activePortal ? activePortal.name.toUpperCase() : 'NEXUS CORE';
ctx.fillText(`CONTEXT: ${contextName}`, 40, 160);
ctx.fillStyle = '#a0b8d0';
@@ -1552,83 +1531,6 @@ function createVisionPoint(config) {
}
// ═══ PORTAL SYSTEM ═══
function disposeMaterial(material) {
if (!material) return;
if (Array.isArray(material)) {
material.forEach(disposeMaterial);
return;
}
[
'map',
'alphaMap',
'aoMap',
'bumpMap',
'displacementMap',
'emissiveMap',
'envMap',
'lightMap',
'metalnessMap',
'normalMap',
'roughnessMap',
'specularMap',
].forEach((key) => {
material[key]?.dispose?.();
});
if (material.uniforms) {
Object.values(material.uniforms).forEach((uniform) => {
uniform?.value?.dispose?.();
});
}
material.dispose?.();
}
function disposeObject3D(root) {
if (!root?.traverse) return;
root.traverse((node) => {
node.geometry?.dispose?.();
disposeMaterial(node.material);
});
}
function clearPortals() {
portals.forEach((portal) => {
scene?.remove?.(portal.group);
disposeObject3D(portal.group);
portal.group?.clear?.();
});
portals = [];
activePortal = null;
lastFocusedPortal = null;
}
function applyPortalRegistry(data, { announce = false } = {}) {
const previousActivePortalId = activePortal?.config?.id || null;
clearPortals();
createPortals(data);
if (previousActivePortalId) {
activePortal = portals.find((portal) => portal.config.id === previousActivePortalId) || null;
if (!activePortal && portalOverlayActive) {
closePortalOverlay();
}
}
if (atlasOverlayActive) populateAtlas();
checkPortalProximity();
refreshWorkshopPanel();
if (announce) {
addChatMessage('system', `Portal registry hot-reloaded (${data.length} portals).`, false);
}
}
function createPortals(data) {
data.forEach(config => {
const portal = createPortal(config);
@@ -4001,65 +3903,9 @@ init().then(() => {
navigator.serviceWorker.register('/service-worker.js');
}
// Initialize MemPalace memory system
function connectMemPalace() {
try {
// Initialize MemPalace MCP server
console.log('Initializing MemPalace memory system...');
// Actual MCP server connection
const statusEl = document.getElementById('mem-palace-status');
if (statusEl) {
statusEl.textContent = 'MemPalace ACTIVE';
statusEl.style.color = '#4af0c0';
statusEl.style.textShadow = '0 0 10px #4af0c0';
}
// Initialize MCP server connection
if (window.Claude && window.Claude.mcp) {
window.Claude.mcp.add('mempalace', {
init: () => {
return { status: 'active', version: '3.0.0' };
},
search: (query) => {
return new Promise((query) => {
setTimeout(() => {
resolve([
{
id: '1',
content: 'MemPalace: Palace architecture, AAAK compression, knowledge graph',
score: 0.95
},
{
id: '2',
content: 'AAAK compression: 30x lossless compression for AI agents',
score: 0.88
}
]);
}, 500);
});
}
});
}
// Initialize memory stats tracking
document.getElementById('compression-ratio').textContent = '0x';
document.getElementById('docs-mined').textContent = '0';
document.getElementById('aaak-size').textContent = '0B';
} catch (err) {
console.error('Failed to initialize MemPalace:', err);
const statusEl = document.getElementById('mem-palace-status');
if (statusEl) {
statusEl.textContent = 'MemPalace ERROR';
statusEl.style.color = '#ff4466';
statusEl.style.textShadow = '0 0 10px #ff4466';
}
}
}
// Initialize MemPalace — connects to Fleet API (see connectMemPalace at line 2772)
// Initialize MemPalace
const mempalace = {
status: { compression: 0, docs: 0, aak: '0B' },
mineChat: () => {
try {
const messages = Array.from(document.querySelectorAll('.chat-msg')).map(m => m.innerText);

View File

@@ -62,15 +62,6 @@ core_rooms:
- proof-of-concept code snippets
- benchmark data
- key: sovereign
label: Sovereign
purpose: Artifacts of Alexander Whitestone's requests, directives, and wizard responses
examples:
- dated request/response artifacts
- conversation summaries with speaker tags
- directive ledgers
- response follow-through notes
optional_rooms:
- key: evennia
label: Evennia
@@ -107,6 +98,15 @@ optional_rooms:
purpose: Catch-all for artefacts not yet assigned to a named room
wizards: ["*"]
- key: sovereign
label: Sovereign
purpose: Artifacts of Alexander Whitestone's requests, directives, and conversation history
wizards: ["*"]
conventions:
naming: "YYYY-MM-DD_HHMMSS_<topic>.md"
index: "INDEX.md"
description: "Each artifact is a dated record of a request from Alexander and the wizard's response. The running INDEX.md provides a chronological catalog."
# Tunnel routing table
# Defines which room pairs are connected across wizard wings.
# A tunnel lets `recall <query> --fleet` search both wings at once.

View File

@@ -13,12 +13,6 @@ from __future__ import annotations
from nexus.mempalace.config import MEMPALACE_PATH, FLEET_WING
from nexus.mempalace.searcher import search_memories, add_memory, MemPalaceResult
from nexus.mempalace.conversation_artifacts import (
ConversationArtifact,
build_request_response_artifact,
extract_alexander_request_pairs,
normalize_speaker,
)
__all__ = [
"MEMPALACE_PATH",
@@ -26,8 +20,4 @@ __all__ = [
"search_memories",
"add_memory",
"MemPalaceResult",
"ConversationArtifact",
"build_request_response_artifact",
"extract_alexander_request_pairs",
"normalize_speaker",
]

View File

@@ -40,7 +40,6 @@ CORE_ROOMS: list[str] = [
"nexus", # reports, docs, KT
"issues", # tickets, backlog
"experiments", # prototypes, spikes
"sovereign", # Alexander request/response artifacts
]
# ── ChromaDB collection name ──────────────────────────────────────────────────

View File

@@ -1,122 +0,0 @@
"""Helpers for preserving Alexander request/response artifacts in MemPalace.
This module provides a small, typed bridge between raw conversation turns and
MemPalace drawers stored in the shared `sovereign` room. The goal is not to
solve all future speaker-tagging needs at once; it gives the Nexus one
canonical artifact shape that other miners and bridges can reuse.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Iterable
_ALEXANDER_ALIASES = {
"alexander",
"alexander whitestone",
"rockachopa",
"triptimmy",
}
@dataclass(frozen=True)
class ConversationArtifact:
requester: str
responder: str
request_text: str
response_text: str
room: str = "sovereign"
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"))
metadata: dict = field(default_factory=dict)
@property
def text(self) -> str:
return (
f"# Conversation Artifact\n\n"
f"## Alexander Request\n{self.request_text.strip()}\n\n"
f"## Wizard Response\n{self.response_text.strip()}\n"
)
def normalize_speaker(name: str | None) -> str:
cleaned = " ".join((name or "").strip().lower().split())
if cleaned in _ALEXANDER_ALIASES:
return "alexander"
return cleaned.replace(" ", "_") or "unknown"
def build_request_response_artifact(
*,
requester: str,
responder: str,
request_text: str,
response_text: str,
source: str = "",
timestamp: str | None = None,
request_timestamp: str | None = None,
response_timestamp: str | None = None,
) -> ConversationArtifact:
requester_slug = normalize_speaker(requester)
responder_slug = normalize_speaker(responder)
ts = timestamp or datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
metadata = {
"artifact_type": "alexander_request_response",
"requester": requester_slug,
"responder": responder_slug,
"speaker_tags": [f"speaker:{requester_slug}", f"speaker:{responder_slug}"],
"source": source,
"timestamp": ts,
}
if request_timestamp:
metadata["request_timestamp"] = request_timestamp
if response_timestamp:
metadata["response_timestamp"] = response_timestamp
return ConversationArtifact(
requester=requester_slug,
responder=responder_slug,
request_text=request_text,
response_text=response_text,
timestamp=ts,
metadata=metadata,
)
def extract_alexander_request_pairs(
turns: Iterable[dict],
*,
responder: str,
source: str = "",
) -> list[ConversationArtifact]:
responder_slug = normalize_speaker(responder)
pending_request: dict | None = None
artifacts: list[ConversationArtifact] = []
for turn in turns:
speaker = normalize_speaker(
turn.get("speaker") or turn.get("username") or turn.get("author") or turn.get("name")
)
text = (turn.get("text") or turn.get("content") or "").strip()
if not text:
continue
if speaker == "alexander":
pending_request = turn
continue
if speaker == responder_slug and pending_request is not None:
artifacts.append(
build_request_response_artifact(
requester="alexander",
responder=responder_slug,
request_text=(pending_request.get("text") or pending_request.get("content") or "").strip(),
response_text=text,
source=source,
request_timestamp=pending_request.get("timestamp"),
response_timestamp=turn.get("timestamp"),
timestamp=turn.get("timestamp") or pending_request.get("timestamp"),
)
)
pending_request = None
return artifacts

View File

@@ -1,123 +0,0 @@
export const DEFAULT_PORTAL_REGISTRY_POLL_MS = 5000;
export const DEFAULT_PORTAL_REGISTRY_CACHE_BUST_PARAM = '_registry_ts';
export function getPortalRegistrySignature(registry) {
return JSON.stringify(registry);
}
export function buildPortalRegistryRequestUrl(
registryUrl = './portals.json',
cacheBustValue = Date.now(),
baseHref = typeof window !== 'undefined' && window.location ? window.location.href : 'http://localhost/',
cacheBustParam = DEFAULT_PORTAL_REGISTRY_CACHE_BUST_PARAM,
) {
const url = new URL(registryUrl, baseHref);
if (cacheBustValue !== null && cacheBustValue !== undefined) {
url.searchParams.set(cacheBustParam, String(cacheBustValue));
}
return url.toString();
}
export async function fetchPortalRegistry({
fetchImpl = fetch,
registryUrl = './portals.json',
baseHref = typeof window !== 'undefined' && window.location ? window.location.href : 'http://localhost/',
cacheBustValue = Date.now(),
cacheBustParam = DEFAULT_PORTAL_REGISTRY_CACHE_BUST_PARAM,
} = {}) {
const requestUrl = buildPortalRegistryRequestUrl(
registryUrl,
cacheBustValue,
baseHref,
cacheBustParam,
);
const response = await fetchImpl(requestUrl, {
cache: 'no-store',
headers: { 'Cache-Control': 'no-cache' },
});
if (!response.ok) {
throw new Error(`Failed to load ${registryUrl}: ${response.status}`);
}
const registry = await response.json();
if (!Array.isArray(registry)) {
throw new Error(`${registryUrl} must be a JSON array`);
}
return registry;
}
export function createPortalRegistryWatcher({
loadRegistry,
applyRegistry,
onError = (error) => console.error('Portal registry watch failed:', error),
intervalMs = DEFAULT_PORTAL_REGISTRY_POLL_MS,
setIntervalImpl = setInterval,
clearIntervalImpl = clearInterval,
signatureFn = getPortalRegistrySignature,
} = {}) {
if (typeof loadRegistry !== 'function') {
throw new TypeError('loadRegistry must be a function');
}
if (typeof applyRegistry !== 'function') {
throw new TypeError('applyRegistry must be a function');
}
let intervalId = null;
let lastSignature = null;
async function refresh() {
try {
const registry = await loadRegistry();
const nextSignature = signatureFn(registry);
if (nextSignature === lastSignature) {
return {
changed: false,
registry,
previousSignature: lastSignature,
nextSignature,
};
}
const previousSignature = lastSignature;
lastSignature = nextSignature;
applyRegistry(registry, { previousSignature, nextSignature });
return {
changed: true,
registry,
previousSignature,
nextSignature,
};
} catch (error) {
onError(error);
return { changed: false, error };
}
}
return {
async prime(registry) {
lastSignature = signatureFn(registry);
return lastSignature;
},
getLastSignature() {
return lastSignature;
},
async refresh() {
return refresh();
},
start() {
if (intervalId !== null) {
return intervalId;
}
intervalId = setIntervalImpl(() => {
void refresh();
}, intervalMs);
return intervalId;
},
stop() {
if (intervalId !== null) {
clearIntervalImpl(intervalId);
intervalId = null;
}
},
};
}

View File

@@ -129,22 +129,13 @@
"type": "harness",
"params": {
"mode": "creative"
},
"action_label": "Enter Workshop"
}
},
"agents_present": [
"timmy",
"kimi"
],
"interaction_ready": true,
"portal_type": "harness",
"world_category": "creative",
"environment": "production",
"access_mode": "visitor",
"readiness_state": "online",
"telemetry_source": "workshop.timmy.foundation",
"owner": "Timmy",
"blocked_reason": null
"interaction_ready": true
},
{
"id": "archive",
@@ -166,21 +157,12 @@
"type": "harness",
"params": {
"mode": "read"
},
"action_label": "Enter Archive"
}
},
"agents_present": [
"claude"
],
"interaction_ready": true,
"portal_type": "harness",
"world_category": "knowledge",
"environment": "production",
"access_mode": "visitor",
"readiness_state": "online",
"telemetry_source": "archive.timmy.foundation",
"owner": "Timmy",
"blocked_reason": null
"interaction_ready": true
},
{
"id": "chapel",
@@ -202,19 +184,10 @@
"type": "harness",
"params": {
"mode": "meditation"
},
"action_label": "Enter Chapel"
}
},
"agents_present": [],
"interaction_ready": true,
"portal_type": "harness",
"world_category": "reflection",
"environment": "production",
"access_mode": "visitor",
"readiness_state": "online",
"telemetry_source": "chapel.timmy.foundation",
"owner": "Timmy",
"blocked_reason": null
"interaction_ready": true
},
{
"id": "courtyard",
@@ -236,22 +209,13 @@
"type": "harness",
"params": {
"mode": "social"
},
"action_label": "Enter Courtyard"
}
},
"agents_present": [
"timmy",
"perplexity"
],
"interaction_ready": true,
"portal_type": "harness",
"world_category": "social",
"environment": "production",
"access_mode": "visitor",
"readiness_state": "online",
"telemetry_source": "courtyard.timmy.foundation",
"owner": "Timmy",
"blocked_reason": null
"interaction_ready": true
},
{
"id": "gate",
@@ -273,19 +237,10 @@
"type": "harness",
"params": {
"mode": "transit"
},
"action_label": "Open Gate"
}
},
"agents_present": [],
"interaction_ready": false,
"portal_type": "harness",
"world_category": "transit",
"environment": "production",
"access_mode": "visitor",
"readiness_state": "standby",
"telemetry_source": "gate.timmy.foundation",
"owner": "Timmy",
"blocked_reason": "Transit gate staged but not interaction ready."
"interaction_ready": false
},
{
"id": "playground",
@@ -337,4 +292,4 @@
"agents_present": [],
"interaction_ready": true
}
]
]

View File

@@ -1,71 +0,0 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
buildPortalRegistryRequestUrl,
createPortalRegistryWatcher,
getPortalRegistrySignature,
} from '../portal-registry.mjs';
test('buildPortalRegistryRequestUrl appends a cache-busting query without dropping existing params', () => {
const url = buildPortalRegistryRequestUrl('./portals.json?mode=atlas', 42, 'https://nexus.test/world/index.html');
assert.equal(
url,
'https://nexus.test/world/portals.json?mode=atlas&_registry_ts=42'
);
});
test('portal registry watcher only reapplies the world when portals.json actually changes', async () => {
const applied = [];
const snapshots = [
[{ id: 'forge', status: 'online' }],
[{ id: 'forge', status: 'online' }, { id: 'archive', status: 'online' }],
];
let tick;
const started = [];
const cleared = [];
const watcher = createPortalRegistryWatcher({
intervalMs: 2500,
loadRegistry: async () => {
const next = snapshots.shift();
if (!next) throw new Error('no more snapshots');
return next;
},
applyRegistry: (registry, meta) => {
applied.push({ ids: registry.map((portal) => portal.id), meta });
},
onError: (error) => {
throw error;
},
setIntervalImpl: (fn, ms) => {
tick = fn;
started.push(ms);
return 99;
},
clearIntervalImpl: (id) => {
cleared.push(id);
},
});
await watcher.prime([{ id: 'forge', status: 'online' }]);
assert.equal(
watcher.getLastSignature(),
getPortalRegistrySignature([{ id: 'forge', status: 'online' }])
);
watcher.start();
assert.deepEqual(started, [2500]);
await tick();
assert.equal(applied.length, 0, 'same registry should not trigger a rebuild');
await tick();
assert.equal(applied.length, 1, 'changed registry should trigger one rebuild');
assert.deepEqual(applied[0].ids, ['forge', 'archive']);
assert.match(applied[0].meta.previousSignature, /forge/);
assert.match(applied[0].meta.nextSignature, /archive/);
watcher.stop();
assert.deepEqual(cleared, [99]);
});

View File

@@ -20,7 +20,6 @@ from agent.memory import (
SessionTranscript,
create_agent_memory,
)
from nexus.mempalace.conversation_artifacts import ConversationArtifact
from agent.memory_hooks import MemoryHooks
@@ -185,24 +184,6 @@ class TestAgentMemory:
doc_id = mem.write_diary()
assert doc_id is None # MemPalace unavailable
def test_remember_alexander_request_response_uses_sovereign_room(self):
mem = AgentMemory(agent_name="allegro")
mem._available = True
with patch("nexus.mempalace.searcher.add_memory", return_value="doc-123") as add_memory:
doc_id = mem.remember_alexander_request_response(
request_text="Catalog my requests.",
response_text="I will preserve them as artifacts.",
requester="Alexander Whitestone",
source="telegram:timmy-time",
)
assert doc_id == "doc-123"
kwargs = add_memory.call_args.kwargs
assert kwargs["room"] == "sovereign"
assert kwargs["wing"] == mem.wing
assert kwargs["extra_metadata"]["artifact_type"] == "alexander_request_response"
assert kwargs["extra_metadata"]["speaker_tags"] == ["speaker:alexander", "speaker:allegro"]
# ---------------------------------------------------------------------------
# MemoryHooks tests

View File

@@ -1,58 +0,0 @@
from pathlib import Path
import yaml
from nexus.mempalace.config import CORE_ROOMS
from nexus.mempalace.conversation_artifacts import (
ConversationArtifact,
build_request_response_artifact,
extract_alexander_request_pairs,
normalize_speaker,
)
def test_sovereign_room_is_core_room() -> None:
assert "sovereign" in CORE_ROOMS
rooms_yaml = yaml.safe_load(Path("mempalace/rooms.yaml").read_text())
assert any(room["key"] == "sovereign" for room in rooms_yaml["core_rooms"])
def test_normalize_speaker_maps_alexander_variants() -> None:
assert normalize_speaker("Alexander Whitestone") == "alexander"
assert normalize_speaker("Rockachopa") == "alexander"
assert normalize_speaker(" ALEXANDER ") == "alexander"
assert normalize_speaker("Bezalel") == "bezalel"
def test_build_request_response_artifact_creates_sovereign_metadata() -> None:
artifact = build_request_response_artifact(
requester="Alexander Whitestone",
responder="Allegro",
request_text="Please organize my conversation artifacts.",
response_text="I will catalog them under a sovereign room.",
source="telegram:timmy-time",
timestamp="2026-04-16T01:30:00Z",
)
assert isinstance(artifact, ConversationArtifact)
assert artifact.room == "sovereign"
assert artifact.metadata["speaker_tags"] == ["speaker:alexander", "speaker:allegro"]
assert artifact.metadata["artifact_type"] == "alexander_request_response"
assert artifact.metadata["responder"] == "allegro"
assert "## Alexander Request" in artifact.text
assert "## Wizard Response" in artifact.text
def test_extract_alexander_request_pairs_finds_following_wizard_response() -> None:
turns = [
{"speaker": "Alexander Whitestone", "text": "Catalog my requests as artifacts.", "timestamp": "2026-04-16T01:00:00Z"},
{"speaker": "Allegro", "text": "I'll build a sovereign room contract.", "timestamp": "2026-04-16T01:01:00Z"},
{"speaker": "Alexander", "text": "Make sure my words are easy to recall.", "timestamp": "2026-04-16T01:02:00Z"},
{"speaker": "Allegro", "text": "I will tag them with speaker metadata.", "timestamp": "2026-04-16T01:03:00Z"},
]
artifacts = extract_alexander_request_pairs(turns, responder="Allegro", source="telegram")
assert len(artifacts) == 2
assert artifacts[0].metadata["request_timestamp"] == "2026-04-16T01:00:00Z"
assert artifacts[1].metadata["response_timestamp"] == "2026-04-16T01:03:00Z"

View File

@@ -1,14 +0,0 @@
from pathlib import Path
APP_JS = Path("app.js")
def test_app_wires_portal_registry_hot_reload_loop() -> None:
source = APP_JS.read_text()
assert "createPortalRegistryWatcher" in source
assert "fetchPortalRegistry" in source
assert "applyPortalRegistry(" in source
assert "portalRegistryWatcher.start()" in source
assert "_registry_ts" in source