@xqli02/mneme 0.1.10 → 0.1.11

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xqli02/mneme",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Three-layer memory architecture for AI coding agents (Ledger + Beads + OpenCode)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -396,6 +396,7 @@ function createEventDisplay(client) {
396
396
  // Track incremental text and tool display state
397
397
  const printedTextLengths = new Map();
398
398
  const displayedToolStates = new Map();
399
+ const deltaParts = new Set(); // parts that received delta events
399
400
 
400
401
  async function start() {
401
402
  running = true;
@@ -430,32 +431,61 @@ function createEventDisplay(client) {
430
431
  const props = event.properties || {};
431
432
 
432
433
  switch (type) {
434
+ // ── Incremental text deltas (streaming) ──
435
+ case "message.part.delta": {
436
+ const partId = props.partID || props.partId;
437
+ if (partId) deltaParts.add(partId);
438
+ if (props.field === "text" && props.delta) {
439
+ process.stdout.write(props.delta);
440
+ lastOutputTime = Date.now();
441
+ }
442
+ break;
443
+ }
444
+
445
+ // ── Part snapshots (full text at end, tool states) ──
433
446
  case "message.part.updated": {
434
447
  if (!props.part) break;
435
448
  const part = props.part;
436
449
  const partId = part.id || `${props.messageID}-${props.index}`;
437
450
 
438
- if (part.type === "text" && part.text) {
439
- const prev = printedTextLengths.get(partId) || 0;
440
- const newText = part.text.slice(prev);
441
- if (newText) {
442
- process.stdout.write(newText);
443
- printedTextLengths.set(partId, part.text.length);
444
- lastOutputTime = Date.now();
445
- }
446
- } else if (
451
+ if (
447
452
  part.type === "tool-invocation" ||
448
453
  part.type === "tool-result"
449
454
  ) {
450
455
  displayToolPart(part, partId);
451
456
  lastOutputTime = Date.now();
452
457
  }
458
+ // For text parts: we rely on message.part.delta for streaming,
459
+ // so only use updated as a fallback if we missed the deltas
460
+ if (part.type === "text" && part.text) {
461
+ const prev = printedTextLengths.get(partId) || 0;
462
+ if (prev === 0 && !deltaParts.has(partId)) {
463
+ // We never saw deltas for this part — print the full text
464
+ process.stdout.write(part.text);
465
+ lastOutputTime = Date.now();
466
+ }
467
+ printedTextLengths.set(partId, part.text.length);
468
+ }
453
469
  break;
454
470
  }
455
471
 
472
+ // ── Session status (busy → idle/completed) ──
473
+ case "session.status": {
474
+ const status = props.status?.type || props.status;
475
+ if (status && status !== "busy" && status !== "pending") {
476
+ if (turnResolve) {
477
+ turnResolve(status);
478
+ turnResolve = null;
479
+ }
480
+ }
481
+ break;
482
+ }
483
+
484
+ // ── Session updated (metadata; also check for status) ──
456
485
  case "session.updated": {
457
- const status = props.session?.status || props.status;
458
- if (status && status !== "running" && status !== "pending") {
486
+ const info = props.info || props.session || {};
487
+ const status = info.status?.type || info.status;
488
+ if (status && status !== "busy" && status !== "running" && status !== "pending") {
459
489
  if (turnResolve) {
460
490
  turnResolve(status);
461
491
  turnResolve = null;
@@ -464,6 +494,17 @@ function createEventDisplay(client) {
464
494
  break;
465
495
  }
466
496
 
497
+ // ── Message-level finish detection ──
498
+ case "message.updated": {
499
+ const info = props.info || {};
500
+ if (info.finish && info.finish !== "pending") {
501
+ // Model finished generating — mark as turn end candidate
502
+ // (session.status should follow, but use this as backup)
503
+ lastOutputTime = Date.now();
504
+ }
505
+ break;
506
+ }
507
+
467
508
  default:
468
509
  break;
469
510
  }
@@ -533,6 +574,7 @@ function createEventDisplay(client) {
533
574
  currentRole = role;
534
575
  printedTextLengths.clear();
535
576
  displayedToolStates.clear();
577
+ deltaParts.clear();
536
578
  }
537
579
 
538
580
  function stop() {
@@ -65,7 +65,17 @@ export function createClient(baseUrl) {
65
65
  } else if (line.startsWith("data:")) {
66
66
  const data = line.slice(5).trim();
67
67
  try {
68
- currentEvent.properties = JSON.parse(data);
68
+ const parsed = JSON.parse(data);
69
+ // opencode sends all fields in data JSON: {type, properties, ...}
70
+ // Merge into currentEvent, but prefer explicit event: line if present
71
+ if (parsed && typeof parsed === "object") {
72
+ if (!currentEvent.type && parsed.type) {
73
+ currentEvent.type = parsed.type;
74
+ }
75
+ currentEvent.properties = parsed.properties || parsed;
76
+ } else {
77
+ currentEvent.properties = parsed;
78
+ }
69
79
  } catch {
70
80
  currentEvent.properties = data;
71
81
  }