mop-agent 0.1.15 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- /** Brain dashboard — project list with status + links into each Project Brain. */
3
- import { useEffect, useState } from "react";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
4
 
5
5
  type Project = {
6
6
  id: string;
@@ -10,160 +10,162 @@ type Project = {
10
10
  artifactCount: number;
11
11
  mopFlowVersion?: string;
12
12
  };
13
-
14
13
  type SemanticNote = { id: string; title: string; body: string; sourceProjects: string[]; confidence: number };
15
14
  type Action = { id: string; projectId: string; tool: string; summary: string; status: string; error?: string };
16
15
 
17
16
  export default function BrainPage() {
18
17
  const [projects, setProjects] = useState<Project[]>([]);
19
- const [code, setCode] = useState<string>("");
18
+ const [code, setCode] = useState("");
20
19
  const [notes, setNotes] = useState<SemanticNote[]>([]);
21
20
  const [actions, setActions] = useState<Action[]>([]);
22
21
  const [consolidating, setConsolidating] = useState(false);
23
22
  const [consolidateMsg, setConsolidateMsg] = useState("");
24
23
 
24
+ const memoryTotal = useMemo(() => projects.reduce((total, project) => total + project.memoryCount, 0), [projects]);
25
+ const onlineTotal = useMemo(() => projects.filter((project) => project.status === "online").length, [projects]);
26
+
25
27
  function loadNotes() {
26
- fetch("/api/semantic").then((r) => r.json()).then((d) => setNotes(d.notes ?? [])).catch(() => {});
28
+ fetch("/api/semantic").then((response) => response.json()).then((result) => setNotes(result.notes ?? [])).catch(() => {});
27
29
  }
28
30
  function loadActions() {
29
- fetch("/api/actions").then((r) => r.json()).then((d) => setActions(d.actions ?? [])).catch(() => {});
31
+ fetch("/api/actions").then((response) => response.json()).then((result) => setActions(result.actions ?? [])).catch(() => {});
30
32
  }
31
33
 
32
34
  useEffect(() => {
33
35
  const load = () => {
34
36
  fetch("/api/projects")
35
- .then((r) => r.json())
36
- .then((d: { projects: Project[] }) => setProjects(d.projects))
37
+ .then((response) => response.json())
38
+ .then((result: { projects: Project[] }) => setProjects(result.projects ?? []))
37
39
  .catch(() => {});
38
40
  loadActions();
39
41
  };
40
42
  load();
41
43
  loadNotes();
42
- const t = setInterval(load, 4000);
43
- return () => clearInterval(t);
44
+ const timer = setInterval(load, 4000);
45
+ return () => clearInterval(timer);
44
46
  }, []);
45
47
 
46
- async function decide(id: string, what: "approve" | "deny") {
47
- await fetch(`/api/actions/${id}/${what}`, { method: "POST" });
48
+ async function decide(id: string, decision: "approve" | "deny") {
49
+ await fetch(`/api/actions/${id}/${decision}`, { method: "POST" });
48
50
  loadActions();
49
51
  }
50
52
 
51
- async function genCode() {
52
- const r = await fetch("/api/link/code", { method: "POST" });
53
- const d = await r.json();
54
- setCode(r.ok ? d.code : `error: ${d.error}`);
53
+ async function generateCode() {
54
+ const response = await fetch("/api/link/code", { method: "POST" });
55
+ const result = await response.json();
56
+ setCode(response.ok ? result.code : `error: ${result.error}`);
55
57
  }
56
58
 
57
- async function runConsolidate() {
59
+ async function consolidate() {
58
60
  setConsolidating(true);
59
- setConsolidateMsg("…");
60
- const r = await fetch("/api/consolidate", { method: "POST" });
61
- const d = await r.json();
62
- setConsolidateMsg(
63
- r.ok ? `scanned ${d.scanned} memories ${d.notesCreated} pattern(s) promoted` : `error: ${d.error}`,
64
- );
61
+ setConsolidateMsg("Consolidating project memory…");
62
+ const response = await fetch("/api/consolidate", { method: "POST" });
63
+ const result = await response.json();
64
+ setConsolidateMsg(response.ok
65
+ ? `${result.scanned} memories scanned · ${result.notesCreated} patterns promoted`
66
+ : `Unable to consolidate: ${result.error}`);
65
67
  setConsolidating(false);
66
68
  loadNotes();
67
69
  }
68
70
 
69
71
  return (
70
- <main className="mop-page">
71
- <header className="mop-page-heading">
72
+ <main className="mop-page mop-brain-page">
73
+ <header className="mop-page-heading mop-brain-heading">
72
74
  <div>
73
- <p className="mop-page-kicker">PERSISTENT MEMORY</p>
75
+ <p className="mop-page-kicker">MEMORY WORKSPACE</p>
74
76
  <h1>Brain</h1>
75
- <p>Main Brain and every linked project memory in one place.</p>
77
+ <p>Main Brain is the shared memory core. Projects feed it context, patterns and skills.</p>
76
78
  </div>
77
- <a href="/brain/graph" style={{ ...btn, textDecoration: "none" }}>KNOWLEDGE GRAPH →</a>
79
+ <a href="/brain/graph" className="mop-primary-link">GRAPH VIEW <span>↗</span></a>
78
80
  </header>
79
81
 
80
- <div style={{ margin: "20px 0", display: "flex", gap: 12, alignItems: "center" }}>
81
- <button onClick={genCode} style={btn}>+ Link project</button>
82
- {code && (
83
- <code style={{ background: "#fffdf2", padding: "6px 10px", borderRadius: 6 }}>
84
- mop-flow-dev link --url {typeof window !== "undefined" ? window.location.origin : ""} --code {code} --project &lt;id&gt;
85
- </code>
86
- )}
87
- </div>
82
+ <section className="mop-main-brain mop-panel">
83
+ <div className="mop-main-brain-orbit" aria-hidden="true">
84
+ <span className="mop-main-brain-core">M</span>
85
+ <i /><i /><i />
86
+ </div>
87
+ <div className="mop-main-brain-copy">
88
+ <p className="mop-page-kicker">PRIMARY KNOWLEDGE LAYER</p>
89
+ <h2>Main Brain</h2>
90
+ <p>Semantic memory shared across every conversation and linked project. Consolidation promotes recurring episodic knowledge into this core.</p>
91
+ <div className="mop-main-brain-actions">
92
+ <button type="button" onClick={consolidate} disabled={consolidating}>{consolidating ? "CONSOLIDATING…" : "⟳ CONSOLIDATE MEMORY"}</button>
93
+ <a href="/brain/graph">Explore connections →</a>
94
+ </div>
95
+ {consolidateMsg && <p className="mop-brain-message">{consolidateMsg}</p>}
96
+ </div>
97
+ <div className="mop-main-brain-stats">
98
+ <div><strong>{notes.length}</strong><span>Semantic patterns</span></div>
99
+ <div><strong>{memoryTotal}</strong><span>Project memories</span></div>
100
+ <div><strong>{onlineTotal}/{projects.length}</strong><span>Projects online</span></div>
101
+ </div>
102
+ </section>
88
103
 
89
- <section className="mop-panel" style={{ margin: "8px 0 24px", padding: 16 }}>
90
- <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
91
- <h2 style={{ fontSize: 16, margin: 0, opacity: 0.85 }}>🌐 Main Brain ({notes.length})</h2>
92
- <button onClick={runConsolidate} disabled={consolidating} style={{ ...btn, background: "#2d4a3e", borderColor: "#2d4a3e" }}>
93
- {consolidating ? "consolidating…" : "⟳ Consolidate"}
94
- </button>
95
- {consolidateMsg && <span style={{ opacity: 0.65, fontSize: 13 }}>{consolidateMsg}</span>}
104
+ <section className="mop-brain-linker mop-panel">
105
+ <div>
106
+ <strong>Connect another project</strong>
107
+ <span>Generate a one-time pairing code for MOP-FLOW.</span>
96
108
  </div>
97
- <p style={{ opacity: 0.55, fontSize: 13, marginBottom: 8 }}>
98
- Recurring patterns promoted from project memory (episodic → semantic).
99
- </p>
100
- {notes.length === 0 ? (
101
- <p style={{ opacity: 0.5, fontSize: 13 }}>No patterns yet — link projects, then Consolidate.</p>
102
- ) : (
103
- <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
104
- {notes.map((n) => (
105
- <li key={n.id} style={{ padding: "8px 0", borderTop: "1px solid #14202e" }}>
106
- <strong>{n.title}</strong>{" "}
107
- <span style={{ opacity: 0.5, fontSize: 12 }}>
108
- · {n.confidence}% · {n.sourceProjects.length} project(s)
109
- </span>
110
- <div style={{ opacity: 0.65, fontSize: 13, marginTop: 2 }}>{n.body}</div>
111
- </li>
112
- ))}
113
- </ul>
109
+ <button type="button" onClick={generateCode}>+ LINK PROJECT</button>
110
+ {code && (
111
+ <code>mop-flow-dev link --url {typeof window !== "undefined" ? window.location.origin : ""} --code {code} --project &lt;id&gt;</code>
114
112
  )}
115
113
  </section>
116
114
 
115
+ <div className="mop-brain-columns">
116
+ <section className="mop-panel mop-brain-section">
117
+ <header>
118
+ <div><p className="mop-page-kicker">SEMANTIC MEMORY</p><h2>Knowledge patterns</h2></div>
119
+ <span>{notes.length}</span>
120
+ </header>
121
+ {notes.length === 0 ? (
122
+ <div className="mop-brain-empty"><strong>No patterns yet</strong><p>Link projects and consolidate memory to grow Main Brain.</p></div>
123
+ ) : (
124
+ <ul className="mop-brain-note-list">
125
+ {notes.map((note) => (
126
+ <li key={note.id}>
127
+ <span className="mop-brain-note-dot" />
128
+ <div><strong>{note.title}</strong><p>{note.body}</p><small>{note.confidence}% confidence · {note.sourceProjects.length} sources</small></div>
129
+ </li>
130
+ ))}
131
+ </ul>
132
+ )}
133
+ </section>
134
+
135
+ <section className="mop-panel mop-brain-section">
136
+ <header>
137
+ <div><p className="mop-page-kicker">CONNECTED CONTEXT</p><h2>Projects</h2></div>
138
+ <span>{projects.length}</span>
139
+ </header>
140
+ {projects.length === 0 ? (
141
+ <div className="mop-brain-empty"><strong>No linked projects</strong><p>Use Link Project above to connect the first memory source.</p></div>
142
+ ) : (
143
+ <ul className="mop-brain-project-list">
144
+ {projects.map((project) => (
145
+ <li key={project.id}>
146
+ <span className={`mop-project-status is-${project.status}`} />
147
+ <div><a href={`/brain/${project.id}`}>{project.name}</a><small>{project.memoryCount} memories · {project.artifactCount} artifacts</small></div>
148
+ <a href={`/chat/${project.id}`}>CHAT →</a>
149
+ </li>
150
+ ))}
151
+ </ul>
152
+ )}
153
+ </section>
154
+ </div>
155
+
117
156
  {actions.length > 0 && (
118
- <section style={{ margin: "8px 0 24px", border: "1px solid #3a2f1f", borderRadius: 8, padding: 16 }}>
119
- <h2 style={{ fontSize: 16, margin: "0 0 8px", opacity: 0.85 }}>⚠️ Approvals</h2>
120
- <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
121
- {actions.map((a) => (
122
- <li key={a.id} style={{ padding: "8px 0", borderTop: "1px solid #2a2415" }}>
123
- <code style={{ opacity: 0.7 }}>{a.tool}</code> · {a.projectId} {a.summary}{" "}
124
- <span style={{ opacity: 0.5, fontSize: 12 }}>[{a.status}{a.error ? `: ${a.error}` : ""}]</span>
125
- {a.status === "pending" && (
126
- <span style={{ float: "right" }}>
127
- <button onClick={() => decide(a.id, "approve")} style={{ ...btn, padding: "4px 10px", background: "#2d4a3e", borderColor: "#2d4a3e" }}>Approve</button>{" "}
128
- <button onClick={() => decide(a.id, "deny")} style={{ ...btn, padding: "4px 10px", background: "#742220", borderColor: "#742220" }}>Deny</button>
129
- </span>
130
- )}
157
+ <section className="mop-panel mop-brain-approvals">
158
+ <header><div><p className="mop-page-kicker">HUMAN GATE</p><h2>Pending actions</h2></div><span>{actions.filter((action) => action.status === "pending").length} pending</span></header>
159
+ <ul>
160
+ {actions.map((action) => (
161
+ <li key={action.id}>
162
+ <div><code>{action.tool}</code><strong>{action.summary}</strong><small>{action.projectId} · {action.status}{action.error ? ` · ${action.error}` : ""}</small></div>
163
+ {action.status === "pending" && <div><button type="button" onClick={() => decide(action.id, "approve")}>APPROVE</button><button type="button" onClick={() => decide(action.id, "deny")}>DENY</button></div>}
131
164
  </li>
132
165
  ))}
133
166
  </ul>
134
167
  </section>
135
168
  )}
136
-
137
- <h2 style={{ fontSize: 16, opacity: 0.8 }}>Projects ({projects.length})</h2>
138
- {projects.length === 0 && <p style={{ opacity: 0.5 }}>No projects linked yet.</p>}
139
- <ul style={{ listStyle: "none", padding: 0 }}>
140
- {projects.map((p) => (
141
- <li key={p.id} style={card}>
142
- <a href={`/brain/${p.id}`} style={{ color: "#742220", textDecoration: "none", fontWeight: 600 }}>
143
- {p.name}
144
- </a>
145
- <span style={{ opacity: 0.6, marginLeft: 8 }}>
146
- {p.status === "online" ? "🟢 online" : "⚪ offline"} · {p.memoryCount} memories · {p.artifactCount} artifacts
147
- </span>
148
- <a href={`/chat/${p.id}`} style={{ float: "right", color: "#742220" }}>chat →</a>
149
- </li>
150
- ))}
151
- </ul>
152
169
  </main>
153
170
  );
154
171
  }
155
-
156
- const card: React.CSSProperties = {
157
- border: "1px solid rgba(45,74,62,.28)",
158
- borderRadius: 8,
159
- padding: "12px 16px",
160
- marginBottom: 8,
161
- };
162
- const btn: React.CSSProperties = {
163
- padding: "8px 14px",
164
- borderRadius: 8,
165
- border: "1px solid #742220",
166
- background: "#742220",
167
- color: "#fef9e1",
168
- cursor: "pointer",
169
- };