mixdog 0.9.83 → 0.9.85

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.
Files changed (30) hide show
  1. package/package.json +1 -1
  2. package/scripts/channel-daemon-stub.mjs +2 -1
  3. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +30 -3
  4. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +38 -4
  5. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +48 -32
  6. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +25 -14
  7. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +8 -2
  8. package/src/runtime/agent/orchestrator/session/store/listing.mjs +38 -1
  9. package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
  10. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +44 -73
  11. package/src/runtime/channels/lib/owned-runtime.mjs +6 -8
  12. package/src/runtime/channels/lib/parent-bridge.mjs +27 -15
  13. package/src/runtime/channels/lib/worker-ipc.mjs +17 -4
  14. package/src/runtime/memory/index.mjs +14 -5
  15. package/src/runtime/memory/lib/agent-ipc.mjs +11 -12
  16. package/src/runtime/shared/atomic-file.mjs +51 -24
  17. package/src/runtime/shared/err-text.mjs +1 -0
  18. package/src/runtime/shared/safe-ipc-send.mjs +34 -0
  19. package/src/session-runtime/lifecycle-api.mjs +9 -3
  20. package/src/session-runtime/runtime-core.mjs +5 -1
  21. package/src/standalone/channel-daemon.mjs +2 -1
  22. package/src/standalone/channel-worker.mjs +21 -19
  23. package/src/standalone/memory-runtime-proxy.mjs +98 -23
  24. package/src/tui/app/slash-dispatch.mjs +23 -13
  25. package/src/tui/app/theme-effort-pickers.mjs +3 -1
  26. package/src/tui/dist/index.mjs +115 -57
  27. package/src/tui/engine/live-share.mjs +2 -2
  28. package/src/tui/engine/session-flow.mjs +20 -10
  29. package/src/tui/engine/turn.mjs +28 -16
  30. package/src/tui/engine.mjs +17 -1
@@ -43,8 +43,12 @@ export function createSessionFlow(bag) {
43
43
  const mode = options.mode || 'prompt';
44
44
  const priority = options.priority || defaultQueuePriority(mode);
45
45
  const displayText = promptDisplayText(text, options);
46
+ const submittedAt = Number(options.submittedAt);
46
47
  return {
47
48
  id: options.id || nextId(),
49
+ submittedAt: Number.isFinite(submittedAt) && submittedAt > 0
50
+ ? Math.round(submittedAt)
51
+ : Date.now(),
48
52
  text: displayText,
49
53
  content: text,
50
54
  pastedImages: options.pastedImages && typeof options.pastedImages === 'object' ? options.pastedImages : null,
@@ -283,6 +287,8 @@ export function createSessionFlow(bag) {
283
287
  set({ queued: [...getState().queued, entry] });
284
288
  if (isQueuedEntryEditable(entry)) flushEmitImmediate?.();
285
289
  }
290
+ const queueAgeMs = Math.max(0, Date.now() - Number(entry.submittedAt || Date.now()));
291
+ tuiDebug(`prompt-queued id=${String(entry.id)} busy=${getState().busy ? 1 : 0} ageMs=${queueAgeMs}`);
286
292
  if (getState().busy) tuiDebug(`busy-queue enqueue mode=${entry.mode} pending=${pending.length}`);
287
293
  void drain();
288
294
  return true;
@@ -309,24 +315,28 @@ export function createSessionFlow(bag) {
309
315
  if (batch.length === 0) break;
310
316
  for (const entry of batch) {
311
317
  const content = entry.content;
318
+ const steeringMeta = {
319
+ id: entry.id,
320
+ submittedAt: entry.submittedAt,
321
+ ...(Array.isArray(entry.images) && entry.images.length ? { images: entry.images } : {}),
322
+ };
312
323
  if (entry.suppressDisplay) {
313
324
  // Model-visible twin of an already-rendered live completion: deliver
314
325
  // content to the model but flag onSteerMessage to skip the duplicate
315
326
  // transcript card (no fall-back to content-derived display text).
316
327
  if (Array.isArray(content) ? content.length > 0 : String(content ?? '').trim().length > 0) {
317
- out.push({ text: STEERING_SUPPRESSED_DISPLAY, content });
328
+ out.push({ ...steeringMeta, text: STEERING_SUPPRESSED_DISPLAY, content });
318
329
  }
319
330
  continue;
320
331
  }
321
- const value = typeof content === 'string'
322
- ? content.trim()
323
- : { text: String(entry.text || '').trim(), content };
324
- if (typeof value === 'string') {
325
- if (value.length > 0) out.push(value);
326
- } else if (Array.isArray(value.content)) {
327
- if (value.content.length > 0) out.push(value);
328
- } else if (String(value.content ?? '').trim().length > 0) {
329
- out.push(value);
332
+ const displayText = String(entry.text || '').trim();
333
+ if (typeof content === 'string') {
334
+ const value = content.trim();
335
+ if (value.length > 0) out.push({ ...steeringMeta, text: displayText || value, content: value });
336
+ } else if (Array.isArray(content)) {
337
+ if (content.length > 0) out.push({ ...steeringMeta, text: displayText, content });
338
+ } else if (String(content ?? '').trim().length > 0) {
339
+ out.push({ ...steeringMeta, text: displayText, content });
330
340
  }
331
341
  }
332
342
  commitSteeringQueueEntries(batch);
@@ -17,8 +17,11 @@ export function createRunTurn(bag) {
17
17
  } = bag;
18
18
  // Small fallbacks keep isolated createRunTurn harnesses source-compatible;
19
19
  // the real engine supplies atomic implementations that also maintain revision.
20
- const updateStreamingTail = updateStreamingTailFromStore || ((id, patch = {}) => {
21
- set({ streamingTail: { ...(getState().streamingTail || {}), ...patch, kind: 'assistant', id, streaming: true } });
20
+ const updateStreamingTail = updateStreamingTailFromStore || ((id, patch = {}, extra = {}) => {
21
+ set({
22
+ streamingTail: { ...(getState().streamingTail || {}), ...patch, kind: 'assistant', id, streaming: true },
23
+ ...extra,
24
+ });
22
25
  return true;
23
26
  });
24
27
  const settleStreamingTail = settleStreamingTailFromStore || ((id, patch = {}) => {
@@ -671,7 +674,12 @@ export function createRunTurn(bag) {
671
674
  patch.spinner = { ...getState().spinner, responseLength: responseLengthVal, thinking: false, thinkingLastEndedAt: _pendingThinkingLastEndedAt || getState().spinner.thinkingLastEndedAt, mode: compactingActive ? 'compacting' : 'responding' };
672
675
  _publishedThinkingActive = false;
673
676
  }
674
- if (Object.keys(patch).length > 0) set(patch);
677
+ if (patch.streamingTail) {
678
+ const { streamingTail, ...extra } = patch;
679
+ updateStreamingTail(streamingTail.id, streamingTail, extra);
680
+ } else if (Object.keys(patch).length > 0) {
681
+ set(patch);
682
+ }
675
683
  _pendingThinkingLastEndedAt = 0;
676
684
  }
677
685
  if (_pendingThinkFlush) {
@@ -800,7 +808,7 @@ export function createRunTurn(bag) {
800
808
  onStreamDelta: () => {
801
809
  markTurnProgress('stream-delta');
802
810
  },
803
- onSteerMessage: (text) => {
811
+ onSteerMessage: (text, steeringMeta = {}) => {
804
812
  if (!markTurnProgress('steer-message')) return;
805
813
  // A suppressed live-completion twin is model-visible only; its
806
814
  // Response card was already pushed at delivery time. Skip the
@@ -826,7 +834,17 @@ export function createRunTurn(bag) {
826
834
  // same-category tool call opens a fresh card instead of reusing
827
835
  // one whose count would then change ABOVE this steered user item.
828
836
  clearAggregateContinuation();
829
- pushUserOrSyntheticItem(value, undefined, 'injected');
837
+ const steeringIds = Array.isArray(steeringMeta?.ids)
838
+ ? steeringMeta.ids.filter((id) => id !== undefined && id !== null)
839
+ : [];
840
+ pushUserOrSyntheticItem(
841
+ value,
842
+ steeringIds[0],
843
+ 'injected',
844
+ Array.isArray(steeringMeta?.images) && steeringMeta.images.length
845
+ ? { images: steeringMeta.images }
846
+ : null,
847
+ );
830
848
  }
831
849
  },
832
850
  onToolCall: async (_iter, calls) => {
@@ -1091,17 +1109,11 @@ export function createRunTurn(bag) {
1091
1109
  _emittedVisibleText = '';
1092
1110
  if (currentAssistantId) {
1093
1111
  if (currentAssistantText) {
1094
- set({
1095
- streamingTail: {
1096
- ...(getState().streamingTail || {}),
1097
- kind: 'assistant',
1098
- id: currentAssistantId,
1099
- text: currentAssistantText,
1100
- streaming: true,
1101
- at: getState().streamingTail?.at || Date.now(),
1102
- ...turnRouteMeta,
1103
- },
1104
- });
1112
+ updateStreamingTail(currentAssistantId, {
1113
+ text: currentAssistantText,
1114
+ at: getState().streamingTail?.at || Date.now(),
1115
+ ...turnRouteMeta,
1116
+ }, {}, { resetText: true });
1105
1117
  } else {
1106
1118
  clearStreamingTail(currentAssistantId);
1107
1119
  currentAssistantId = null;
@@ -537,12 +537,28 @@ export async function createEngineSession({
537
537
  ...extra,
538
538
  });
539
539
  };
540
- const updateStreamingTail = (id, patch = {}, extra = {}) => {
540
+ // Non-enumerable so the marker never enters persisted transcript items,
541
+ // live-share JSON, or renderer snapshots. The desktop host reads the shared
542
+ // Symbol.for key before cloning and uses it to prove that a growing text is
543
+ // an append, avoiding an O(total text) startsWith check on every frame.
544
+ const streamingTailTextEpochKey = Symbol.for('mixdog.streaming-tail-text-epoch');
545
+ let streamingTailTextEpoch = 0;
546
+ const updateStreamingTail = (id, patch = {}, extra = {}, { resetText = false } = {}) => {
541
547
  if (id == null) return false;
542
548
  const current = state.streamingTail?.id === id
543
549
  ? state.streamingTail
544
550
  : { kind: 'assistant', id, text: '', streaming: true };
545
551
  const next = { ...current, ...patch, kind: 'assistant', id, streaming: true };
552
+ const currentTextEpoch = current[streamingTailTextEpochKey];
553
+ const textEpoch = !resetText && Number.isSafeInteger(currentTextEpoch)
554
+ ? currentTextEpoch
555
+ : ++streamingTailTextEpoch;
556
+ Object.defineProperty(next, streamingTailTextEpochKey, {
557
+ value: textEpoch,
558
+ enumerable: false,
559
+ configurable: false,
560
+ writable: false,
561
+ });
546
562
  let changed = state.streamingTail !== current;
547
563
  if (!changed) {
548
564
  for (const [key, value] of Object.entries(next)) {