@getmegabrain/cli 0.1.7 → 0.1.8

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.
@@ -28,17 +28,25 @@ export class AgentSession {
28
28
  agentContext;
29
29
  cellCounter = 0;
30
30
  tools;
31
- constructor(model, tools, emit, agentContext = '') {
31
+ messages;
32
+ constructor(model, tools, emit, agentContext = '', history = []) {
32
33
  this.model = model;
33
34
  this.emit = emit;
34
35
  this.agentContext = agentContext;
35
36
  this.tools = new Map(tools.map(t => [t.definition.name, t]));
37
+ this.messages = [...history];
38
+ }
39
+ /** The full model-visible message history (prior turns + this run) — persist to give the
40
+ * session memory across sends. */
41
+ getMessages() {
42
+ return this.messages;
36
43
  }
37
44
  async run(prompt) {
38
45
  const system = this.agentContext
39
46
  ? `${SYSTEM_PROMPT}\n\nProject context: ${this.agentContext}`
40
47
  : SYSTEM_PROMPT;
41
- const messages = [{ role: 'user', content: [{ type: 'text', text: prompt }] }];
48
+ const messages = this.messages;
49
+ messages.push({ role: 'user', content: [{ type: 'text', text: prompt }] });
42
50
  const toolDefs = [...this.tools.values()].map(t => t.definition);
43
51
  try {
44
52
  for (let turn = 0; turn < MAX_TURNS; turn += 1) {
@@ -224,11 +224,14 @@ export async function startScienceServer(host = '127.0.0.1') {
224
224
  if (seg[0] === 'api' &&
225
225
  seg[1] === 'sessions' &&
226
226
  seg[2] &&
227
- (seg[3] === 'events' || seg[3] === 'agent' || seg[3] === 'kernel')) {
227
+ (seg[3] === 'events' || seg[3] === 'agent' || seg[3] === 'kernel' || seg[3] === 'transcript')) {
228
228
  const creds = readCredentials();
229
229
  if (!creds)
230
230
  return json(res, { error: 'signed-out' }, 401);
231
231
  const sid = seg[2];
232
+ if (seg[3] === 'transcript' && method === 'GET') {
233
+ return json(res, store.getMessages(sid));
234
+ }
232
235
  if (seg[3] === 'events' && method === 'GET') {
233
236
  res.writeHead(200, {
234
237
  'content-type': 'text/event-stream',
@@ -261,8 +264,10 @@ export async function startScienceServer(host = '127.0.0.1') {
261
264
  const project = session ? store.getProject(session.projectId) : null;
262
265
  store.touchSession(sid, session && !session.summary ? { summary: prompt } : undefined);
263
266
  fanout(sid, 'agent', { type: 'user', text: prompt });
264
- const agent = new AgentSession(new GatewayModel({ apiKey: creds.token }), [pythonTool(kernels.get(sid))], evt => fanout(sid, 'agent', evt), project?.agentContext ?? '');
265
- void agent.run(prompt);
267
+ const agent = new AgentSession(new GatewayModel({ apiKey: creds.token }), [pythonTool(kernels.get(sid))], evt => fanout(sid, 'agent', evt), project?.agentContext ?? '', store.getMessages(sid));
268
+ // Persist the full history when the turn ends, so the session remembers it and the
269
+ // transcript survives a refresh.
270
+ void agent.run(prompt).finally(() => store.setMessages(sid, agent.getMessages()));
266
271
  return json(res, { ok: true });
267
272
  }
268
273
  }
@@ -70,6 +70,21 @@
70
70
  sendBtn.textContent = on ? 'Running…' : 'Send';
71
71
  }
72
72
 
73
+ // Replay a persisted conversation (model messages) into the transcript on load.
74
+ function renderHistory(messages) {
75
+ messages.forEach(function (m) {
76
+ (m.content || []).forEach(function (b) {
77
+ if (m.role === 'user') {
78
+ if (b.type === 'text') addUser(b.text);
79
+ else if (b.type === 'tool_result') addToolResult(b.content, '');
80
+ } else {
81
+ if (b.type === 'text') addAgentText(b.text);
82
+ else if (b.type === 'tool_use') addToolCall(b.name, (b.input && b.input.code) || '');
83
+ }
84
+ });
85
+ });
86
+ }
87
+
73
88
  // ── Sidebar: this project's sessions ────────────────────────────────
74
89
  function loadSidebar() {
75
90
  if (!pid) return;
@@ -150,16 +165,27 @@
150
165
  });
151
166
  });
152
167
 
153
- fetch('/api/sessions/' + sid)
168
+ fetch('/api/sessions/' + sid + '/transcript')
154
169
  .then(function (r) {
155
170
  return r.json();
156
171
  })
157
- .then(function (s) {
158
- if (s && s.title) $('session-title').textContent = s.title;
159
- // Prefill the composer with the seeded first task so the user can just hit Send.
160
- if (s && (s.summary || s.title) && !composer.value) composer.value = s.summary || s.title;
172
+ .then(function (msgs) {
173
+ var has = Array.isArray(msgs) && msgs.length > 0;
174
+ if (has) renderHistory(msgs);
175
+ return fetch('/api/sessions/' + sid)
176
+ .then(function (r) {
177
+ return r.json();
178
+ })
179
+ .then(function (s) {
180
+ if (s && s.title) $('session-title').textContent = s.title;
181
+ // Prefill the composer with the seeded first task only for a fresh session.
182
+ if (!has && s && (s.summary || s.title) && !composer.value)
183
+ composer.value = s.summary || s.title;
184
+ });
185
+ })
186
+ .catch(function () {})
187
+ .then(function () {
188
+ loadSidebar();
189
+ connect();
161
190
  });
162
-
163
- loadSidebar();
164
- connect();
165
191
  })();
@@ -28,6 +28,8 @@ function defaultSettings() {
28
28
  profile: '',
29
29
  };
30
30
  }
31
+ const MAX_MESSAGES = 60;
32
+ const MAX_TOOL_RESULT_CHARS = 4000;
31
33
  const STORE_FILE = path.join(MEGABRAIN_CONFIG_DIR, 'science-workspace.json');
32
34
  function id(prefix) {
33
35
  return `${prefix}_${randomBytes(8).toString('hex')}`;
@@ -48,6 +50,7 @@ export class WorkspaceStore {
48
50
  projects: parsed.projects ?? [],
49
51
  sessions: parsed.sessions ?? [],
50
52
  settings: parsed.settings,
53
+ transcripts: parsed.transcripts ?? {},
51
54
  };
52
55
  }
53
56
  catch {
@@ -157,4 +160,19 @@ export class WorkspaceStore {
157
160
  this.save();
158
161
  return next;
159
162
  }
163
+ // ── Per-session conversation history (agent memory + transcript replay) ──
164
+ getMessages(sessionId) {
165
+ return this.data.transcripts?.[sessionId] ?? [];
166
+ }
167
+ setMessages(sessionId, messages) {
168
+ // Cap history length and truncate large tool outputs so the on-disk workspace stays small.
169
+ const capped = messages.slice(-MAX_MESSAGES).map(m => ({
170
+ role: m.role,
171
+ content: m.content.map(b => b.type === 'tool_result' && b.content.length > MAX_TOOL_RESULT_CHARS
172
+ ? { ...b, content: `${b.content.slice(0, MAX_TOOL_RESULT_CHARS)}\n…(truncated)` }
173
+ : b),
174
+ }));
175
+ (this.data.transcripts ??= {})[sessionId] = capped;
176
+ this.save();
177
+ }
160
178
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmegabrain/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "MegaBrain CLI — sign in once, then code from your terminal via OpenCode wired to the MegaBrain Gateway, or run `megabrain science` for the local research workbench.",
5
5
  "license": "MIT",
6
6
  "type": "module",