@getmegabrain/cli 0.1.12 → 0.1.13

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.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A per-key serial task queue. Tasks enqueued under the same key run strictly one after
3
+ * another (in enqueue order); tasks under different keys run independently.
4
+ *
5
+ * The Science daemon uses this to serialize agent turns per session: without it, sending
6
+ * several messages in quick succession spawns overlapping AgentSessions that share one
7
+ * kernel and race on the persisted transcript — so turns clobber each other and messages
8
+ * appear "unseen". Serializing means each turn starts only after the previous finished and
9
+ * therefore reads the prior turn's saved history.
10
+ */
11
+ export function createKeyedQueue() {
12
+ const tails = new Map();
13
+ return function enqueue(key, task) {
14
+ const prev = tails.get(key) ?? Promise.resolve();
15
+ // Run `task` after the previous one settles — on both fulfill and reject, so a failed
16
+ // turn never blocks the queue.
17
+ const result = prev.then(task, task);
18
+ // The tail we chain the *next* task onto must never reject (that would skip the catch
19
+ // handler on subsequent tasks); swallow it here. Callers still see `result`'s outcome.
20
+ tails.set(key, result.catch(() => undefined));
21
+ return result;
22
+ };
23
+ }
@@ -14,6 +14,7 @@ import { OpenAlexClient } from './agent/openalex.js';
14
14
  import { PubMedClient } from './agent/pubmed.js';
15
15
  import { GatewayModel } from './agent/model.js';
16
16
  import { DEFAULT_MODEL, fetchPickerModels } from './agent/models.js';
17
+ import { createKeyedQueue } from './queue.js';
17
18
  /**
18
19
  * The `megabrain science` local daemon's HTTP server. Serves the sign-in hand-off on
19
20
  * localhost (docs/megabrain-science/reference/claude-science-flow.md §1.1) and, once signed
@@ -34,6 +35,9 @@ export async function startScienceServer(host = '127.0.0.1') {
34
35
  // flow to every browser watching a session, tagged with a `channel`.
35
36
  const sseClients = new Map();
36
37
  const kernelHooked = new Set();
38
+ // Serialize agent turns per session so rapid messages are handled in order (each seeing the
39
+ // previous turn's history) instead of racing on the shared kernel and transcript.
40
+ const agentQueue = createKeyedQueue();
37
41
  const fanout = (sid, channel, payload) => {
38
42
  const set = sseClients.get(sid);
39
43
  if (!set)
@@ -273,17 +277,28 @@ export async function startScienceServer(host = '127.0.0.1') {
273
277
  if (!prompt)
274
278
  return json(res, { error: 'prompt-required' }, 400);
275
279
  const model = str(b.model)?.trim() || DEFAULT_MODEL;
276
- const session = store.getSession(sid);
277
- const project = session ? store.getProject(session.projectId) : null;
278
- store.touchSession(sid, session && !session.summary ? { summary: prompt } : undefined);
280
+ const token = creds.token;
281
+ // Echo the user's message immediately so it shows up in order, then run the turn on the
282
+ // per-session queue. The turn reads history *at run time* so it includes any turn that
283
+ // was still in flight when this message arrived.
279
284
  fanout(sid, 'agent', { type: 'user', text: prompt });
280
- const agent = new AgentSession(new GatewayModel({ apiKey: creds.token, model }), [
281
- pythonTool(kernels.get(sid)),
282
- literatureTool(new MultiSourceClient([new ArxivClient(), new OpenAlexClient(), new PubMedClient()])),
283
- ], evt => fanout(sid, 'agent', evt), project?.agentContext ?? '', store.getMessages(sid));
284
- // Persist the full history when the turn ends, so the session remembers it and the
285
- // transcript survives a refresh.
286
- void agent.run(prompt).finally(() => store.setMessages(sid, agent.getMessages()));
285
+ void agentQueue(sid, async () => {
286
+ const session = store.getSession(sid);
287
+ const project = session ? store.getProject(session.projectId) : null;
288
+ store.touchSession(sid, session && !session.summary ? { summary: prompt } : undefined);
289
+ const agent = new AgentSession(new GatewayModel({ apiKey: token, model }), [
290
+ pythonTool(kernels.get(sid)),
291
+ literatureTool(new MultiSourceClient([new ArxivClient(), new OpenAlexClient(), new PubMedClient()])),
292
+ ], evt => fanout(sid, 'agent', evt), project?.agentContext ?? '', store.getMessages(sid));
293
+ try {
294
+ await agent.run(prompt);
295
+ }
296
+ finally {
297
+ // Persist the full history so the session remembers it and the transcript survives
298
+ // a refresh — and so the next queued turn reads it as its starting point.
299
+ store.setMessages(sid, agent.getMessages());
300
+ }
301
+ });
287
302
  return json(res, { ok: true });
288
303
  }
289
304
  }
@@ -79,9 +79,13 @@
79
79
  scroll();
80
80
  }
81
81
 
82
+ // Track how many turns are in flight. The send button stays enabled so you can queue
83
+ // follow-up messages while the agent works — the server runs them in order per session.
84
+ var pending = 0;
82
85
  function setRunning(on) {
83
- sendBtn.disabled = on;
84
- sendBtn.textContent = on ? 'Running…' : 'Send';
86
+ pending += on ? 1 : -1;
87
+ if (pending < 0) pending = 0;
88
+ sendBtn.textContent = pending > 0 ? 'Working…' : 'Send';
85
89
  }
86
90
 
87
91
  // Replay a persisted conversation (model messages) into the transcript on load.
@@ -183,7 +187,7 @@
183
187
 
184
188
  function send() {
185
189
  var text = composer.value.trim();
186
- if (!text || sendBtn.disabled) return;
190
+ if (!text) return;
187
191
  composer.value = '';
188
192
  setRunning(true);
189
193
  fetch('/api/sessions/' + sid + '/agent', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmegabrain/cli",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
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",