feat: port MCP retrieval and write hardening

- align os MCP/runtime/docs with ra-h contract
- add safe direct node lookup and context-optional flows
- gate edge and context writes behind confirmation
This commit is contained in:
“BeeRad”
2026-04-14 20:57:07 +10:00
parent d825e7a783
commit 929423cb21
35 changed files with 1172 additions and 534 deletions
+4 -30
View File
@@ -432,32 +432,6 @@ export default function FocusPanel({
}
};
const createEdgeAuto = async (targetNodeId: number) => {
if (activeTab === null) return;
try {
const response = await fetch('/api/edges', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
from_node_id: activeTab,
to_node_id: targetNodeId,
source: 'user',
explanation: '',
}),
});
if (!response.ok) {
throw new Error('Failed to create edge');
}
await fetchEdgesData(activeTab);
} catch (error) {
console.error('Error creating edge:', error);
window.alert('Failed to create connection. Please try again.');
throw error;
}
};
const createEdgeWithExplanation = async (targetNodeId: number, explanation: string) => {
if (activeTab === null) return;
try {
@@ -1433,11 +1407,11 @@ export default function FocusPanel({
onClose={() => setEdgeSearchOpen(false)}
excludeNodeId={activeTab}
onEdgeCreate={async (nodeId, explanation) => {
if (explanation && explanation.trim()) {
await createEdgeWithExplanation(nodeId, explanation.trim());
} else {
await createEdgeAuto(nodeId);
if (!explanation || !explanation.trim()) {
window.alert('Add a short explanation for the relationship before creating the edge.');
return;
}
await createEdgeWithExplanation(nodeId, explanation.trim());
}}
/>
@@ -315,12 +315,12 @@ export default function NodeSearchModal({
value={explanation}
onChange={(e) => setExplanation(e.target.value.slice(0, 500))}
onKeyDown={(e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter' && explanation.trim()) {
e.preventDefault();
void handleCreate(selectedNode, explanation);
}
}}
placeholder="Describe this connection... (optional, leave blank to auto-infer)"
placeholder="Describe this connection in one clear sentence"
rows={3}
style={{
width: '100%',
@@ -351,7 +351,7 @@ export default function NodeSearchModal({
<button
type="button"
onClick={() => { void handleCreate(selectedNode, explanation); }}
disabled={submitting}
disabled={submitting || !explanation.trim()}
style={{
width: '100%',
display: 'flex',
@@ -368,7 +368,7 @@ export default function NodeSearchModal({
opacity: submitting ? 0.6 : 1,
}}
>
{submitting ? 'Creating…' : explanation.trim() ? 'Create connection' : 'Create connection (auto-infer)'}
{submitting ? 'Creating…' : 'Create connection'}
</button>
</div>
)}