Compare commits
145 Commits
fix/1539
...
mimo/code/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cc2ff812a | ||
|
|
ae78394624 | ||
| e02506b688 | |||
| c4201ae27d | |||
| 2d73c816d5 | |||
| 3c367f1ca7 | |||
|
|
eb50b39c0f | ||
| de82be0621 | |||
| fc117f6e7c | |||
| eafe213c66 | |||
| e7f6655a10 | |||
| 675e352351 | |||
| 01339952fe | |||
| c3fc2e4c29 | |||
| 2c8844a478 | |||
| 1cadc33882 | |||
| 4e2a353ba3 | |||
| c3d0400918 | |||
| 1fb98ff769 | |||
| a3570df3b2 | |||
| a62d39470f | |||
| 4fb292ca43 | |||
| 4b0e375697 | |||
|
|
17be3c8804 | ||
| 6bbd1c2baf | |||
| 604f73a1b8 | |||
| 825a2c8a94 | |||
|
|
b2f4bd0448 | ||
| 40502cf91c | |||
| 2c2181cbaf | |||
| 3cb45008f6 | |||
| 7d475151ea | |||
| 181d4ce933 | |||
| ecbd104d03 | |||
| 6e3ea2637c | |||
| 779a65cd83 | |||
| bc48abd970 | |||
| a3f1688cb7 | |||
| a80d749f69 | |||
| e7ab9fbe17 | |||
| c61c8bb030 | |||
| 8fd5d57864 | |||
| 3b5c62fa76 | |||
| a4f76705df | |||
| dc74a84192 | |||
| 48f85da0c0 | |||
| a0443a7003 | |||
| 428a9da3bd | |||
| 3361100830 | |||
|
|
6da8d627b6 | ||
|
|
ec2a427a7a | ||
|
|
d19f62476c | ||
|
|
b178b4ad98 | ||
|
|
a96dac0d8a | ||
|
|
76298f9255 | ||
|
|
4215ef786f | ||
|
|
9ce8c0b5a7 | ||
|
|
e23ba71cf3 | ||
|
|
9d1040265a | ||
| 6878f206ee | |||
|
|
8faa930baf | ||
| b9de0d7003 | |||
|
|
c5ce9cd7aa | ||
|
|
60eea86c93 | ||
|
|
23deb761dc | ||
|
|
2872b04ca9 | ||
|
|
9f90392a93 | ||
|
|
d15a82ff1e | ||
|
|
c3b455bd9c | ||
| 61c24c390b | |||
| 0dd12b5560 | |||
| e4b265cdfe | |||
| 7dcebe4cb4 | |||
| 05abd170ab | |||
| 2ce333ee1a | |||
| b6938b40b4 | |||
| 98cff9b2ce | |||
| 00a8b2b265 | |||
| a4203a3d58 | |||
| ed505b3e7c | |||
| a85cd96a71 | |||
| 4abf39b874 | |||
| 6b9ae9b9f0 | |||
| 6d80f98ac8 | |||
| 46fcad445b | |||
| 484cc1f97b | |||
| 8d7e666d10 | |||
| b44d9d7b41 | |||
| 7b62b16503 | |||
| 4251d61c44 | |||
| e158f752d2 | |||
| bbdec73003 | |||
| 7c48449c31 | |||
| 8a66158996 | |||
| 8b7a2efa83 | |||
| 29aaaf31ef | |||
| f53462b101 | |||
| 35c2af1ad2 | |||
| 2a1bf1e213 | |||
| 72cd0f3030 | |||
| 4ebfb035e3 | |||
| d883f062d2 | |||
| 46d8893ec8 | |||
|
|
557713501c | ||
|
|
970a810e52 | ||
|
|
2500366821 | ||
|
|
35bb12e53d | ||
|
|
61e10ef022 | ||
|
|
37b6b8239e | ||
|
|
3b3d602926 | ||
|
|
b2570554d5 | ||
|
|
0bf9c6766a | ||
|
|
631d0cd192 | ||
| ee09247af3 | |||
| 1154460919 | |||
|
|
29ad855662 | ||
|
|
4bcf014076 | ||
|
|
3b77a3aa77 | ||
|
|
f72e79d378 | ||
|
|
6b55eb1b99 | ||
|
|
a643955ebc | ||
|
|
4f560dd08a | ||
|
|
20711a8692 | ||
|
|
2dfd3013b6 | ||
|
|
7cc68f0d04 | ||
|
|
0f504ef665 | ||
|
|
091089e53e | ||
|
|
0348138bd9 | ||
|
|
6f9b2cd299 | ||
|
|
4a1b37f0fa | ||
|
|
ca68286eb1 | ||
| 3f877e2019 | |||
| fdb906cd95 | |||
| c5fef11788 | |||
| 10b76472f9 | |||
|
|
b83af291c7 | ||
|
|
59f36fc40f | ||
| 981ab55a95 | |||
| 0a90c861b6 | |||
| b9fed5ee88 | |||
| 8b34ec207a | |||
|
|
cc1264140c | ||
| 33e10f2aac | |||
| 8c28e97aa9 | |||
|
|
9c3d9952d7 |
@@ -285,49 +285,6 @@ class AgentMemory:
|
|||||||
logger.warning(f"Failed to store memory: {e}")
|
logger.warning(f"Failed to store memory: {e}")
|
||||||
return None
|
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(
|
def write_diary(
|
||||||
self,
|
self,
|
||||||
summary: Optional[str] = None,
|
summary: Optional[str] = None,
|
||||||
|
|||||||
152
app.js
152
app.js
@@ -83,7 +83,6 @@ let workshopPanelCanvas = null;
|
|||||||
let workshopScanMat = null;
|
let workshopScanMat = null;
|
||||||
let workshopPanelRefreshTimer = 0;
|
let workshopPanelRefreshTimer = 0;
|
||||||
let lastFocusedPortal = null;
|
let lastFocusedPortal = null;
|
||||||
let portalHealthTimer = null;
|
|
||||||
|
|
||||||
// ═══ VISITOR / OPERATOR MODE ═══
|
// ═══ VISITOR / OPERATOR MODE ═══
|
||||||
let uiMode = 'visitor'; // 'visitor' | 'operator'
|
let uiMode = 'visitor'; // 'visitor' | 'operator'
|
||||||
@@ -171,6 +170,8 @@ class AgentFSM {
|
|||||||
this.agentId = agentId;
|
this.agentId = agentId;
|
||||||
this.state = initialState;
|
this.state = initialState;
|
||||||
this.transitions = {};
|
this.transitions = {};
|
||||||
|
this._transitionLog = [];
|
||||||
|
this._onTransition = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
addTransition(fromState, toState, condition) {
|
addTransition(fromState, toState, condition) {
|
||||||
@@ -178,17 +179,34 @@ class AgentFSM {
|
|||||||
this.transitions[fromState].push({ toState, condition });
|
this.transitions[fromState].push({ toState, condition });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTransition(callback) {
|
||||||
|
this._onTransition = callback;
|
||||||
|
}
|
||||||
|
|
||||||
update(facts) {
|
update(facts) {
|
||||||
const possibleTransitions = this.transitions[this.state] || [];
|
const possibleTransitions = this.transitions[this.state] || [];
|
||||||
for (const transition of possibleTransitions) {
|
for (const transition of possibleTransitions) {
|
||||||
if (transition.condition(facts)) {
|
if (transition.condition(facts)) {
|
||||||
console.log(`[FSM] Agent ${this.agentId} transitioning: ${this.state} -> ${transition.toState}`);
|
const from = this.state;
|
||||||
this.state = transition.toState;
|
this.state = transition.toState;
|
||||||
|
const entry = {
|
||||||
|
agent: this.agentId,
|
||||||
|
from,
|
||||||
|
to: this.state,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
facts: Object.fromEntries(facts),
|
||||||
|
};
|
||||||
|
this._transitionLog.push(entry);
|
||||||
|
if (this._transitionLog.length > 50) this._transitionLog.shift();
|
||||||
|
if (this._onTransition) this._onTransition(entry);
|
||||||
|
console.log(`[FSM] Agent ${this.agentId}: ${from} -> ${this.state}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTransitionLog() { return this._transitionLog; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class KnowledgeGraph {
|
class KnowledgeGraph {
|
||||||
@@ -648,6 +666,15 @@ function setupGOFAI() {
|
|||||||
// Setup FSM
|
// Setup FSM
|
||||||
agentFSMs['timmy'] = new AgentFSM('timmy', 'IDLE');
|
agentFSMs['timmy'] = new AgentFSM('timmy', 'IDLE');
|
||||||
agentFSMs['timmy'].addTransition('IDLE', 'ANALYZING', (facts) => facts.get('activePortals') > 0);
|
agentFSMs['timmy'].addTransition('IDLE', 'ANALYZING', (facts) => facts.get('activePortals') > 0);
|
||||||
|
agentFSMs['timmy'].addTransition('ANALYZING', 'REACTING', (facts) => facts.get('CRITICAL_DRAIN_PATTERN') || facts.get('UNSTABLE_OSCILLATION'));
|
||||||
|
agentFSMs['timmy'].addTransition('REACTING', 'IDLE', (facts) => !facts.get('CRITICAL_DRAIN_PATTERN') && !facts.get('UNSTABLE_OSCILLATION') && !(facts.get('activePortals') > 0));
|
||||||
|
|
||||||
|
// Wire FSM transitions to trajectory logging (issue #674)
|
||||||
|
agentFSMs['timmy'].onTransition((entry) => {
|
||||||
|
if (window._nexusTrajectoryHook) {
|
||||||
|
window._nexusTrajectoryHook('fsm_transition', entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
symbolicEngine.addRule((facts) => facts.get('UNSTABLE_OSCILLATION'), () => 'STABILIZE MATRIX', 'Unstable oscillation demands stabilization', ['UNSTABLE_OSCILLATION']);
|
symbolicEngine.addRule((facts) => facts.get('UNSTABLE_OSCILLATION'), () => 'STABILIZE MATRIX', 'Unstable oscillation demands stabilization', ['UNSTABLE_OSCILLATION']);
|
||||||
symbolicEngine.addRule((facts) => facts.get('CRITICAL_DRAIN_PATTERN'), () => 'SHED PORTAL LOAD', 'Critical drain demands portal shedding', ['CRITICAL_DRAIN_PATTERN']);
|
symbolicEngine.addRule((facts) => facts.get('CRITICAL_DRAIN_PATTERN'), () => 'SHED PORTAL LOAD', 'Critical drain demands portal shedding', ['CRITICAL_DRAIN_PATTERN']);
|
||||||
@@ -1537,7 +1564,6 @@ function createPortals(data) {
|
|||||||
const portal = createPortal(config);
|
const portal = createPortal(config);
|
||||||
portals.push(portal);
|
portals.push(portal);
|
||||||
});
|
});
|
||||||
startPortalHealthChecks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPortal(config) {
|
function createPortal(config) {
|
||||||
@@ -1676,19 +1702,6 @@ function createPortal(config) {
|
|||||||
swirl,
|
swirl,
|
||||||
pSystem,
|
pSystem,
|
||||||
light,
|
light,
|
||||||
labelCanvas,
|
|
||||||
labelContext: lctx,
|
|
||||||
labelTexture: labelTex,
|
|
||||||
labelMesh,
|
|
||||||
baseState: window.PortalHealth
|
|
||||||
? window.PortalHealth.captureBaseState(config)
|
|
||||||
: {
|
|
||||||
status: config.status || 'online',
|
|
||||||
blocked_reason: config.blocked_reason ?? null,
|
|
||||||
interaction_ready: config.interaction_ready !== false,
|
|
||||||
action_label: config.destination?.action_label || 'ENTER',
|
|
||||||
},
|
|
||||||
health: null,
|
|
||||||
customElements: {}
|
customElements: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1769,86 +1782,6 @@ function createPortal(config) {
|
|||||||
return portalObj;
|
return portalObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePortalLabelTexture(portal, runtimeState) {
|
|
||||||
if (!portal?.labelContext || !portal?.labelCanvas || !portal?.labelTexture) return;
|
|
||||||
|
|
||||||
const ctx = portal.labelContext;
|
|
||||||
const canvas = portal.labelCanvas;
|
|
||||||
const portalColor = new THREE.Color(portal.config.color);
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.font = 'bold 32px "Orbitron", sans-serif';
|
|
||||||
ctx.fillStyle = '#' + portalColor.getHexString();
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.fillText(`◈ ${portal.config.name.toUpperCase()}`, 256, 36);
|
|
||||||
|
|
||||||
ctx.font = 'bold 18px "Orbitron", sans-serif';
|
|
||||||
if (runtimeState && runtimeState.online === false) {
|
|
||||||
ctx.fillStyle = '#ff4466';
|
|
||||||
ctx.fillText('OFFLINE', 256, 68);
|
|
||||||
} else if (portal.config.role) {
|
|
||||||
const roleColors = { timmy: '#4af0c0', reflex: '#ff4466', pilot: '#ffd700', creative: '#ff00ff' };
|
|
||||||
ctx.fillStyle = roleColors[portal.config.role] || '#888888';
|
|
||||||
ctx.fillText(portal.config.role.toUpperCase(), 256, 68);
|
|
||||||
}
|
|
||||||
|
|
||||||
portal.labelTexture.needsUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setPortalVisualIntensity(portal, intensity) {
|
|
||||||
const factor = Math.max(0.12, Math.min(1, intensity));
|
|
||||||
portal.group.traverse(obj => {
|
|
||||||
if (obj.isPointLight) {
|
|
||||||
if (obj.userData.baseIntensity == null) obj.userData.baseIntensity = obj.intensity;
|
|
||||||
obj.intensity = obj.userData.baseIntensity * factor;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const materials = obj.material ? (Array.isArray(obj.material) ? obj.material : [obj.material]) : [];
|
|
||||||
materials.forEach(mat => {
|
|
||||||
if (mat.opacity != null) {
|
|
||||||
if (mat.userData.baseOpacity == null) mat.userData.baseOpacity = mat.opacity;
|
|
||||||
mat.opacity = mat.userData.baseOpacity * factor;
|
|
||||||
}
|
|
||||||
if (mat.emissiveIntensity != null) {
|
|
||||||
if (mat.userData.baseEmissiveIntensity == null) mat.userData.baseEmissiveIntensity = mat.emissiveIntensity;
|
|
||||||
mat.emissiveIntensity = mat.userData.baseEmissiveIntensity * factor;
|
|
||||||
}
|
|
||||||
if (mat.transparent != null && factor < 1) mat.transparent = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setPortalRuntimeState(portal, runtimeState) {
|
|
||||||
portal.health = runtimeState;
|
|
||||||
portal.config.status = runtimeState.status;
|
|
||||||
portal.config.blocked_reason = runtimeState.blocked_reason;
|
|
||||||
portal.config.interaction_ready = runtimeState.interaction_ready;
|
|
||||||
if (portal.config.destination) {
|
|
||||||
portal.config.destination.action_label = runtimeState.actionLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
setPortalVisualIntensity(portal, runtimeState.online === false ? 0.2 : 1);
|
|
||||||
updatePortalLabelTexture(portal, runtimeState);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runPortalHealthChecks() {
|
|
||||||
if (!window.PortalHealth || portals.length === 0) return;
|
|
||||||
|
|
||||||
await Promise.all(portals.map(async (portal) => {
|
|
||||||
const probe = await window.PortalHealth.probePortalHealth(portal.config);
|
|
||||||
const runtimeState = window.PortalHealth.computePortalRuntimeState(portal.config, portal.baseState, probe);
|
|
||||||
setPortalRuntimeState(portal, runtimeState);
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (atlasOverlayActive) populateAtlas();
|
|
||||||
if (activePortal) checkPortalProximity();
|
|
||||||
}
|
|
||||||
|
|
||||||
function startPortalHealthChecks() {
|
|
||||||
if (!window.PortalHealth || portalHealthTimer) return;
|
|
||||||
runPortalHealthChecks();
|
|
||||||
portalHealthTimer = setInterval(runPortalHealthChecks, window.PortalHealth.DEFAULT_HEALTH_INTERVAL_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══ PARTICLES ═══
|
// ═══ PARTICLES ═══
|
||||||
function createParticles() {
|
function createParticles() {
|
||||||
const count = particleCount(1500);
|
const count = particleCount(1500);
|
||||||
@@ -2110,10 +2043,12 @@ function setupControls() {
|
|||||||
);
|
);
|
||||||
const raycaster = new THREE.Raycaster();
|
const raycaster = new THREE.Raycaster();
|
||||||
raycaster.setFromCamera(mouse, camera);
|
raycaster.setFromCamera(mouse, camera);
|
||||||
const intersects = raycaster.intersectObjects(portals.map(p => p.ring));
|
// Raycast against both ring and swirl for a larger click target
|
||||||
|
const portalMeshes = portals.flatMap(p => [p.ring, p.swirl]);
|
||||||
|
const intersects = raycaster.intersectObjects(portalMeshes);
|
||||||
if (intersects.length > 0) {
|
if (intersects.length > 0) {
|
||||||
const clickedRing = intersects[0].object;
|
const hitObj = intersects[0].object;
|
||||||
const portal = portals.find(p => p.ring === clickedRing);
|
const portal = portals.find(p => p.ring === hitObj || p.swirl === hitObj);
|
||||||
if (portal) activatePortal(portal);
|
if (portal) activatePortal(portal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3143,14 +3078,8 @@ function checkPortalProximity() {
|
|||||||
|
|
||||||
activePortal = closest;
|
activePortal = closest;
|
||||||
const hint = document.getElementById('portal-hint');
|
const hint = document.getElementById('portal-hint');
|
||||||
const hintLabel = document.getElementById('portal-hint-label');
|
|
||||||
if (activePortal) {
|
if (activePortal) {
|
||||||
const runtimeState = activePortal.health || {
|
|
||||||
online: activePortal.config.status !== 'offline',
|
|
||||||
tooltipText: `Enter ${activePortal.config.name}`,
|
|
||||||
};
|
|
||||||
document.getElementById('portal-hint-name').textContent = activePortal.config.name;
|
document.getElementById('portal-hint-name').textContent = activePortal.config.name;
|
||||||
if (hintLabel) hintLabel.textContent = runtimeState.online ? 'Enter' : 'Offline';
|
|
||||||
hint.style.display = 'flex';
|
hint.style.display = 'flex';
|
||||||
} else {
|
} else {
|
||||||
hint.style.display = 'none';
|
hint.style.display = 'none';
|
||||||
@@ -3164,13 +3093,8 @@ function activatePortal(portal) {
|
|||||||
const descDisplay = document.getElementById('portal-desc-display');
|
const descDisplay = document.getElementById('portal-desc-display');
|
||||||
const redirectBox = document.getElementById('portal-redirect-box');
|
const redirectBox = document.getElementById('portal-redirect-box');
|
||||||
const errorBox = document.getElementById('portal-error-box');
|
const errorBox = document.getElementById('portal-error-box');
|
||||||
const errorMsg = document.querySelector('#portal-error-box .portal-error-msg');
|
|
||||||
const timerDisplay = document.getElementById('portal-timer');
|
const timerDisplay = document.getElementById('portal-timer');
|
||||||
const statusDot = document.getElementById('portal-status-dot');
|
const statusDot = document.getElementById('portal-status-dot');
|
||||||
const runtimeState = portal.health || {
|
|
||||||
online: portal.config.status !== 'offline',
|
|
||||||
blocked_reason: portal.config.blocked_reason,
|
|
||||||
};
|
|
||||||
|
|
||||||
nameDisplay.textContent = portal.config.name.toUpperCase();
|
nameDisplay.textContent = portal.config.name.toUpperCase();
|
||||||
descDisplay.textContent = portal.config.description;
|
descDisplay.textContent = portal.config.description;
|
||||||
@@ -3179,13 +3103,6 @@ function activatePortal(portal) {
|
|||||||
|
|
||||||
overlay.style.display = 'flex';
|
overlay.style.display = 'flex';
|
||||||
|
|
||||||
if (!runtimeState.online) {
|
|
||||||
redirectBox.style.display = 'none';
|
|
||||||
errorBox.style.display = 'block';
|
|
||||||
if (errorMsg) errorMsg.textContent = runtimeState.blocked_reason || 'OFFLINE';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (portal.config.destination && portal.config.destination.url) {
|
if (portal.config.destination && portal.config.destination.url) {
|
||||||
redirectBox.style.display = 'block';
|
redirectBox.style.display = 'block';
|
||||||
errorBox.style.display = 'none';
|
errorBox.style.display = 'none';
|
||||||
@@ -3204,7 +3121,6 @@ function activatePortal(portal) {
|
|||||||
} else {
|
} else {
|
||||||
redirectBox.style.display = 'none';
|
redirectBox.style.display = 'none';
|
||||||
errorBox.style.display = 'block';
|
errorBox.style.display = 'block';
|
||||||
if (errorMsg) errorMsg.textContent = 'DESTINATION NOT YET LINKED';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -236,7 +236,7 @@
|
|||||||
<!-- Portal Hint -->
|
<!-- Portal Hint -->
|
||||||
<div id="portal-hint" class="portal-hint" style="display:none;">
|
<div id="portal-hint" class="portal-hint" style="display:none;">
|
||||||
<div class="portal-hint-key">F</div>
|
<div class="portal-hint-key">F</div>
|
||||||
<div class="portal-hint-text"><span id="portal-hint-label">Enter</span> <span id="portal-hint-name"></span></div>
|
<div class="portal-hint-text">Enter <span id="portal-hint-name"></span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Vision Hint -->
|
<!-- Vision Hint -->
|
||||||
@@ -394,7 +394,6 @@
|
|||||||
<div id="memory-inspect-panel" class="memory-inspect-panel" style="display:none;" aria-label="Memory Inspect Panel"></div>
|
<div id="memory-inspect-panel" class="memory-inspect-panel" style="display:none;" aria-label="Memory Inspect Panel"></div>
|
||||||
<div id="memory-connections-panel" class="memory-connections-panel" style="display:none;" aria-label="Memory Connections Panel"></div>
|
<div id="memory-connections-panel" class="memory-connections-panel" style="display:none;" aria-label="Memory Connections Panel"></div>
|
||||||
|
|
||||||
<script src="./portal-health.js"></script>
|
|
||||||
<script src="./boot.js"></script>
|
<script src="./boot.js"></script>
|
||||||
<script src="./avatar-customization.js"></script>
|
<script src="./avatar-customization.js"></script>
|
||||||
<script src="./lod-system.js"></script>
|
<script src="./lod-system.js"></script>
|
||||||
|
|||||||
@@ -62,15 +62,6 @@ core_rooms:
|
|||||||
- proof-of-concept code snippets
|
- proof-of-concept code snippets
|
||||||
- benchmark data
|
- 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:
|
optional_rooms:
|
||||||
- key: evennia
|
- key: evennia
|
||||||
label: Evennia
|
label: Evennia
|
||||||
@@ -107,6 +98,15 @@ optional_rooms:
|
|||||||
purpose: Catch-all for artefacts not yet assigned to a named room
|
purpose: Catch-all for artefacts not yet assigned to a named room
|
||||||
wizards: ["*"]
|
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
|
# Tunnel routing table
|
||||||
# Defines which room pairs are connected across wizard wings.
|
# Defines which room pairs are connected across wizard wings.
|
||||||
# A tunnel lets `recall <query> --fleet` search both wings at once.
|
# A tunnel lets `recall <query> --fleet` search both wings at once.
|
||||||
|
|||||||
@@ -13,12 +13,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from nexus.mempalace.config import MEMPALACE_PATH, FLEET_WING
|
from nexus.mempalace.config import MEMPALACE_PATH, FLEET_WING
|
||||||
from nexus.mempalace.searcher import search_memories, add_memory, MemPalaceResult
|
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__ = [
|
__all__ = [
|
||||||
"MEMPALACE_PATH",
|
"MEMPALACE_PATH",
|
||||||
@@ -26,8 +20,4 @@ __all__ = [
|
|||||||
"search_memories",
|
"search_memories",
|
||||||
"add_memory",
|
"add_memory",
|
||||||
"MemPalaceResult",
|
"MemPalaceResult",
|
||||||
"ConversationArtifact",
|
|
||||||
"build_request_response_artifact",
|
|
||||||
"extract_alexander_request_pairs",
|
|
||||||
"normalize_speaker",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ CORE_ROOMS: list[str] = [
|
|||||||
"nexus", # reports, docs, KT
|
"nexus", # reports, docs, KT
|
||||||
"issues", # tickets, backlog
|
"issues", # tickets, backlog
|
||||||
"experiments", # prototypes, spikes
|
"experiments", # prototypes, spikes
|
||||||
"sovereign", # Alexander request/response artifacts
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# ── ChromaDB collection name ──────────────────────────────────────────────────
|
# ── ChromaDB collection name ──────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -125,6 +125,51 @@ class TrajectoryLogger:
|
|||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def log_tactical(
|
||||||
|
self,
|
||||||
|
agent: str,
|
||||||
|
from_state: str,
|
||||||
|
to_state: str,
|
||||||
|
facts_snapshot: Optional[dict] = None,
|
||||||
|
):
|
||||||
|
"""Log an FSM state transition as a tactical training signal.
|
||||||
|
|
||||||
|
Captures reflex-layer decisions (IDLE->ANALYZING->REACTING->IDLE)
|
||||||
|
as separate training samples so the LoRA learns tactical patterns
|
||||||
|
alongside thought/action cycles.
|
||||||
|
"""
|
||||||
|
perception = f"[Tactical] Agent {agent} state change: {from_state} -> {to_state}"
|
||||||
|
if facts_snapshot:
|
||||||
|
perception += f'\nWorld state: {json.dumps(facts_snapshot, default=str)[:500]}'
|
||||||
|
|
||||||
|
thought = f"Reflex transition triggered: conditions met for {from_state} -> {to_state}"
|
||||||
|
|
||||||
|
cycle = {
|
||||||
|
"id": f"{self.session_id}_tactical_{len(self.cycles)}",
|
||||||
|
"model": "nexus-embodied-tactical",
|
||||||
|
"started_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||||
|
"cycle_ms": 0,
|
||||||
|
"conversations": [
|
||||||
|
{"from": "system", "value": self.system_prompt},
|
||||||
|
{"from": "human", "value": perception},
|
||||||
|
{"from": "gpt", "value": thought},
|
||||||
|
],
|
||||||
|
"message_count": 3,
|
||||||
|
"metadata": {
|
||||||
|
"type": "tactical",
|
||||||
|
"agent": agent,
|
||||||
|
"from_state": from_state,
|
||||||
|
"to_state": to_state,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.cycles.append(cycle)
|
||||||
|
|
||||||
|
with open(self.log_file, "a") as f:
|
||||||
|
f.write(json.dumps(cycle) + "\n")
|
||||||
|
|
||||||
|
return cycle["id"]
|
||||||
|
|
||||||
def list_trajectory_files(self) -> list[dict]:
|
def list_trajectory_files(self) -> list[dict]:
|
||||||
"""List all trajectory files with stats."""
|
"""List all trajectory files with stats."""
|
||||||
files = []
|
files = []
|
||||||
|
|||||||
135
portal-health.js
135
portal-health.js
@@ -1,135 +0,0 @@
|
|||||||
(function (global) {
|
|
||||||
const DEFAULT_HEALTH_INTERVAL_MS = 5 * 60 * 1000;
|
|
||||||
const DEFAULT_TIMEOUT_MS = 3000;
|
|
||||||
|
|
||||||
function derivePortalHealthTarget(config = {}) {
|
|
||||||
if (typeof config.health_check_url === 'string' && config.health_check_url.trim()) {
|
|
||||||
return config.health_check_url.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
const destinationUrl = config?.destination?.url;
|
|
||||||
if (typeof destinationUrl === 'string' && destinationUrl.trim()) {
|
|
||||||
return destinationUrl.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldProbePortalHealth(config = {}) {
|
|
||||||
const target = derivePortalHealthTarget(config);
|
|
||||||
return typeof target === 'string' && /^https?:\/\//i.test(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
function captureBaseState(config = {}) {
|
|
||||||
return {
|
|
||||||
status: config.status || 'online',
|
|
||||||
blocked_reason: config.blocked_reason ?? null,
|
|
||||||
interaction_ready: config.interaction_ready !== false,
|
|
||||||
action_label: config?.destination?.action_label || 'ENTER',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function computePortalRuntimeState(config = {}, baseState = captureBaseState(config), probe = {}) {
|
|
||||||
const tracked = shouldProbePortalHealth(config);
|
|
||||||
const portalName = config.name || 'Portal';
|
|
||||||
|
|
||||||
if (!tracked) {
|
|
||||||
return {
|
|
||||||
tracked: false,
|
|
||||||
online: true,
|
|
||||||
status: baseState.status,
|
|
||||||
blocked_reason: baseState.blocked_reason,
|
|
||||||
interaction_ready: baseState.interaction_ready,
|
|
||||||
actionLabel: baseState.action_label,
|
|
||||||
tooltipText: `Enter ${portalName}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const online = probe.online !== false;
|
|
||||||
if (!online) {
|
|
||||||
const reason = probe.reason || 'Offline';
|
|
||||||
return {
|
|
||||||
tracked: true,
|
|
||||||
online: false,
|
|
||||||
status: 'offline',
|
|
||||||
blocked_reason: reason,
|
|
||||||
interaction_ready: false,
|
|
||||||
actionLabel: 'OFFLINE',
|
|
||||||
tooltipText: 'Offline',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
tracked: true,
|
|
||||||
online: true,
|
|
||||||
status: baseState.status,
|
|
||||||
blocked_reason: baseState.blocked_reason,
|
|
||||||
interaction_ready: baseState.interaction_ready,
|
|
||||||
actionLabel: baseState.action_label,
|
|
||||||
tooltipText: `Enter ${portalName}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function describeProbeFailure(error) {
|
|
||||||
if (!error) return 'Offline';
|
|
||||||
if (error.name === 'AbortError') return 'Offline';
|
|
||||||
return error.message || 'Offline';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function probePortalHealth(config = {}, { fetchImpl = global.fetch, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
|
|
||||||
const target = derivePortalHealthTarget(config);
|
|
||||||
if (!shouldProbePortalHealth(config)) {
|
|
||||||
return { online: true, skipped: true, target };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof fetchImpl !== 'function') {
|
|
||||||
return { online: false, reason: 'Offline', target };
|
|
||||||
}
|
|
||||||
|
|
||||||
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
||||||
const timeout = controller
|
|
||||||
? setTimeout(() => controller.abort(), timeoutMs)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetchImpl(target, {
|
|
||||||
method: 'HEAD',
|
|
||||||
mode: 'no-cors',
|
|
||||||
cache: 'no-store',
|
|
||||||
signal: controller ? controller.signal : undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (timeout) clearTimeout(timeout);
|
|
||||||
|
|
||||||
const online = !response || response.type === 'opaque' || response.ok || response.status < 500;
|
|
||||||
return {
|
|
||||||
online,
|
|
||||||
reason: online ? null : `Offline (${response.status || 'unreachable'})`,
|
|
||||||
target,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
if (timeout) clearTimeout(timeout);
|
|
||||||
return {
|
|
||||||
online: false,
|
|
||||||
reason: describeProbeFailure(error),
|
|
||||||
target,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const api = {
|
|
||||||
DEFAULT_HEALTH_INTERVAL_MS,
|
|
||||||
DEFAULT_TIMEOUT_MS,
|
|
||||||
derivePortalHealthTarget,
|
|
||||||
shouldProbePortalHealth,
|
|
||||||
captureBaseState,
|
|
||||||
computePortalRuntimeState,
|
|
||||||
probePortalHealth,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
module.exports = api;
|
|
||||||
}
|
|
||||||
|
|
||||||
global.PortalHealth = api;
|
|
||||||
})(typeof window !== 'undefined' ? window : globalThis);
|
|
||||||
67
portals.json
67
portals.json
@@ -129,22 +129,13 @@
|
|||||||
"type": "harness",
|
"type": "harness",
|
||||||
"params": {
|
"params": {
|
||||||
"mode": "creative"
|
"mode": "creative"
|
||||||
},
|
}
|
||||||
"action_label": "Enter Workshop"
|
|
||||||
},
|
},
|
||||||
"agents_present": [
|
"agents_present": [
|
||||||
"timmy",
|
"timmy",
|
||||||
"kimi"
|
"kimi"
|
||||||
],
|
],
|
||||||
"interaction_ready": true,
|
"interaction_ready": true
|
||||||
"portal_type": "creative-harness",
|
|
||||||
"world_category": "creative",
|
|
||||||
"environment": "production",
|
|
||||||
"access_mode": "visitor",
|
|
||||||
"readiness_state": "online",
|
|
||||||
"blocked_reason": null,
|
|
||||||
"telemetry_source": "workshop",
|
|
||||||
"owner": "Timmy"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "archive",
|
"id": "archive",
|
||||||
@@ -166,21 +157,12 @@
|
|||||||
"type": "harness",
|
"type": "harness",
|
||||||
"params": {
|
"params": {
|
||||||
"mode": "read"
|
"mode": "read"
|
||||||
},
|
}
|
||||||
"action_label": "Enter Archive"
|
|
||||||
},
|
},
|
||||||
"agents_present": [
|
"agents_present": [
|
||||||
"claude"
|
"claude"
|
||||||
],
|
],
|
||||||
"interaction_ready": true,
|
"interaction_ready": true
|
||||||
"portal_type": "knowledge-harness",
|
|
||||||
"world_category": "archive",
|
|
||||||
"environment": "production",
|
|
||||||
"access_mode": "visitor",
|
|
||||||
"readiness_state": "online",
|
|
||||||
"blocked_reason": null,
|
|
||||||
"telemetry_source": "archive",
|
|
||||||
"owner": "Timmy"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "chapel",
|
"id": "chapel",
|
||||||
@@ -202,19 +184,10 @@
|
|||||||
"type": "harness",
|
"type": "harness",
|
||||||
"params": {
|
"params": {
|
||||||
"mode": "meditation"
|
"mode": "meditation"
|
||||||
},
|
}
|
||||||
"action_label": "Enter Chapel"
|
|
||||||
},
|
},
|
||||||
"agents_present": [],
|
"agents_present": [],
|
||||||
"interaction_ready": true,
|
"interaction_ready": true
|
||||||
"portal_type": "sanctuary-harness",
|
|
||||||
"world_category": "sanctuary",
|
|
||||||
"environment": "production",
|
|
||||||
"access_mode": "visitor",
|
|
||||||
"readiness_state": "online",
|
|
||||||
"blocked_reason": null,
|
|
||||||
"telemetry_source": "chapel",
|
|
||||||
"owner": "Timmy"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "courtyard",
|
"id": "courtyard",
|
||||||
@@ -236,22 +209,13 @@
|
|||||||
"type": "harness",
|
"type": "harness",
|
||||||
"params": {
|
"params": {
|
||||||
"mode": "social"
|
"mode": "social"
|
||||||
},
|
}
|
||||||
"action_label": "Enter Courtyard"
|
|
||||||
},
|
},
|
||||||
"agents_present": [
|
"agents_present": [
|
||||||
"timmy",
|
"timmy",
|
||||||
"perplexity"
|
"perplexity"
|
||||||
],
|
],
|
||||||
"interaction_ready": true,
|
"interaction_ready": true
|
||||||
"portal_type": "social-harness",
|
|
||||||
"world_category": "social",
|
|
||||||
"environment": "production",
|
|
||||||
"access_mode": "visitor",
|
|
||||||
"readiness_state": "online",
|
|
||||||
"blocked_reason": null,
|
|
||||||
"telemetry_source": "courtyard",
|
|
||||||
"owner": "Reflex"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "gate",
|
"id": "gate",
|
||||||
@@ -273,19 +237,10 @@
|
|||||||
"type": "harness",
|
"type": "harness",
|
||||||
"params": {
|
"params": {
|
||||||
"mode": "transit"
|
"mode": "transit"
|
||||||
},
|
}
|
||||||
"action_label": "Enter Gate"
|
|
||||||
},
|
},
|
||||||
"agents_present": [],
|
"agents_present": [],
|
||||||
"interaction_ready": false,
|
"interaction_ready": false
|
||||||
"portal_type": "transit-harness",
|
|
||||||
"world_category": "transit",
|
|
||||||
"environment": "production",
|
|
||||||
"access_mode": "operator",
|
|
||||||
"readiness_state": "standby",
|
|
||||||
"blocked_reason": null,
|
|
||||||
"telemetry_source": "gate",
|
|
||||||
"owner": "Reflex"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "playground",
|
"id": "playground",
|
||||||
@@ -337,4 +292,4 @@
|
|||||||
"agents_present": [],
|
"agents_present": [],
|
||||||
"interaction_ready": true
|
"interaction_ready": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
const { test } = require('node:test');
|
|
||||||
const assert = require('node:assert/strict');
|
|
||||||
const fs = require('node:fs');
|
|
||||||
const path = require('node:path');
|
|
||||||
|
|
||||||
const ROOT = path.resolve(__dirname, '..');
|
|
||||||
|
|
||||||
test('index bootstraps portal-health helper before app startup', () => {
|
|
||||||
const html = fs.readFileSync(path.join(ROOT, 'index.html'), 'utf8');
|
|
||||||
assert.match(html, /portal-health\.js/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('app wires recurring portal health checks and offline tooltip behavior', () => {
|
|
||||||
const source = fs.readFileSync(path.join(ROOT, 'app.js'), 'utf8');
|
|
||||||
assert.match(source, /startPortalHealthChecks\(/);
|
|
||||||
assert.match(source, /runPortalHealthChecks\(/);
|
|
||||||
assert.match(source, /PortalHealth\.DEFAULT_HEALTH_INTERVAL_MS/);
|
|
||||||
assert.match(source, /tooltipText/);
|
|
||||||
assert.match(source, /Offline/);
|
|
||||||
assert.match(source, /setPortalRuntimeState\(/);
|
|
||||||
});
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
const { test } = require('node:test');
|
|
||||||
const assert = require('node:assert/strict');
|
|
||||||
|
|
||||||
const {
|
|
||||||
DEFAULT_HEALTH_INTERVAL_MS,
|
|
||||||
derivePortalHealthTarget,
|
|
||||||
shouldProbePortalHealth,
|
|
||||||
computePortalRuntimeState,
|
|
||||||
} = require('../portal-health.js');
|
|
||||||
|
|
||||||
test('portal health checks default to five minutes', () => {
|
|
||||||
assert.equal(DEFAULT_HEALTH_INTERVAL_MS, 5 * 60 * 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('portal health target prefers explicit health URL then destination URL', () => {
|
|
||||||
assert.equal(
|
|
||||||
derivePortalHealthTarget({
|
|
||||||
health_check_url: 'https://status.timmy.foundation/health',
|
|
||||||
destination: { url: 'https://portal.timmy.foundation' },
|
|
||||||
}),
|
|
||||||
'https://status.timmy.foundation/health'
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
derivePortalHealthTarget({ destination: { url: 'https://portal.timmy.foundation' } }),
|
|
||||||
'https://portal.timmy.foundation'
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
derivePortalHealthTarget({ destination: { url: null } }),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('portal health probe eligibility only tracks portals with external URLs', () => {
|
|
||||||
assert.equal(shouldProbePortalHealth({ destination: { url: 'https://portal.timmy.foundation' } }), true);
|
|
||||||
assert.equal(shouldProbePortalHealth({ destination: { url: './local.html' } }), false);
|
|
||||||
assert.equal(shouldProbePortalHealth({ destination: { url: null } }), false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runtime state marks offline portals unavailable and restores base state when healthy', () => {
|
|
||||||
const config = {
|
|
||||||
name: 'Workshop',
|
|
||||||
status: 'online',
|
|
||||||
blocked_reason: null,
|
|
||||||
interaction_ready: true,
|
|
||||||
destination: { url: 'https://workshop.timmy.foundation', action_label: 'Enter Workshop' },
|
|
||||||
};
|
|
||||||
const baseState = {
|
|
||||||
status: 'online',
|
|
||||||
blocked_reason: null,
|
|
||||||
interaction_ready: true,
|
|
||||||
action_label: 'Enter Workshop',
|
|
||||||
};
|
|
||||||
|
|
||||||
const offline = computePortalRuntimeState(config, baseState, { online: false, reason: 'Offline' });
|
|
||||||
assert.equal(offline.status, 'offline');
|
|
||||||
assert.equal(offline.interaction_ready, false);
|
|
||||||
assert.equal(offline.blocked_reason, 'Offline');
|
|
||||||
assert.equal(offline.tooltipText, 'Offline');
|
|
||||||
assert.equal(offline.actionLabel, 'OFFLINE');
|
|
||||||
|
|
||||||
const restored = computePortalRuntimeState(config, baseState, { online: true });
|
|
||||||
assert.equal(restored.status, 'online');
|
|
||||||
assert.equal(restored.interaction_ready, true);
|
|
||||||
assert.equal(restored.blocked_reason, null);
|
|
||||||
assert.equal(restored.tooltipText, 'Enter Workshop');
|
|
||||||
assert.equal(restored.actionLabel, 'Enter Workshop');
|
|
||||||
});
|
|
||||||
@@ -20,7 +20,6 @@ from agent.memory import (
|
|||||||
SessionTranscript,
|
SessionTranscript,
|
||||||
create_agent_memory,
|
create_agent_memory,
|
||||||
)
|
)
|
||||||
from nexus.mempalace.conversation_artifacts import ConversationArtifact
|
|
||||||
from agent.memory_hooks import MemoryHooks
|
from agent.memory_hooks import MemoryHooks
|
||||||
|
|
||||||
|
|
||||||
@@ -185,24 +184,6 @@ class TestAgentMemory:
|
|||||||
doc_id = mem.write_diary()
|
doc_id = mem.write_diary()
|
||||||
assert doc_id is None # MemPalace unavailable
|
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
|
# MemoryHooks tests
|
||||||
|
|||||||
@@ -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"
|
|
||||||
Reference in New Issue
Block a user