@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 +1 -1
- package/src/commands/auto.mjs +53 -11
- package/src/opencode-client.mjs +11 -1
package/package.json
CHANGED
package/src/commands/auto.mjs
CHANGED
|
@@ -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 (
|
|
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
|
|
458
|
-
|
|
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() {
|
package/src/opencode-client.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|