@femtomc/mu-agent 26.3.3 → 26.3.4

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/README.md CHANGED
@@ -77,7 +77,7 @@ Current stack:
77
77
 
78
78
  - `brandingExtension` — mu compact header/footer branding + default theme
79
79
  - `eventLogExtension` — event tail + watch widget
80
- - `uiExtension` — programmable `UiDoc` surface (`/mu ui ...`, `mu_ui`)
80
+ - `uiExtension` — programmable `UiDoc` surface (`/mu ui ...`, `mu_ui`) with session-resume persistence via `custom` entries (`mu-ui-state/v1`)
81
81
 
82
82
  Default operator UI theme is `mu-gruvbox-dark`.
83
83
 
@@ -1 +1 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/extensions/ui.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AAq/CpF,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,QAkO3C;AAED,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/extensions/ui.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AA0qDpF,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,QA4P3C;AAED,eAAe,WAAW,CAAC"}
@@ -1,4 +1,4 @@
1
- import { normalizeUiDocs, parseUiDoc, resolveUiStatusProfileName, uiStatusProfileWarnings, } from "@femtomc/mu-core";
1
+ import { normalizeUiDocs, parseUiDoc, resolveUiStatusProfileName, stableSerializeJson, uiStatusProfileWarnings, } from "@femtomc/mu-core";
2
2
  import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
3
3
  import { registerMuSubcommand } from "./mu-command-dispatcher.js";
4
4
  const UI_DISPLAY_DOCS_MAX = 16;
@@ -6,6 +6,8 @@ const UI_PICKER_COMPONENTS_MAX = 8;
6
6
  const UI_PICKER_LIST_ITEMS_MAX = 4;
7
7
  const UI_PICKER_KEYVALUE_ROWS_MAX = 4;
8
8
  const UI_SESSION_KEY_FALLBACK = "__mu_ui_active_session__";
9
+ const UI_STATE_CUSTOM_TYPE = "mu-ui-state/v1";
10
+ const UI_STATE_SCHEMA_VERSION = 1;
9
11
  const UI_PROMPT_PREVIEW_MAX = 160;
10
12
  const UI_INTERACT_SHORTCUT_PRIMARY = "ctrl+shift+u";
11
13
  const UI_INTERACT_SHORTCUT_FALLBACK = "alt+u";
@@ -46,6 +48,7 @@ function createState() {
46
48
  interactionDepth: 0,
47
49
  lastStatusText: null,
48
50
  lastWidgetSignature: null,
51
+ lastPersistSignature: null,
49
52
  };
50
53
  }
51
54
  function pruneStaleStates(nowMs) {
@@ -389,6 +392,164 @@ function parseDocListInput(value) {
389
392
  }
390
393
  return { ok: true, docs: normalizeUiDocs(docs, { maxDocs: UI_DISPLAY_DOCS_MAX }) };
391
394
  }
395
+ function normalizeStringArray(value) {
396
+ if (!Array.isArray(value)) {
397
+ return [];
398
+ }
399
+ const out = [];
400
+ const seen = new Set();
401
+ for (const entry of value) {
402
+ if (typeof entry !== "string") {
403
+ continue;
404
+ }
405
+ const trimmed = entry.trim();
406
+ if (!trimmed || seen.has(trimmed)) {
407
+ continue;
408
+ }
409
+ seen.add(trimmed);
410
+ out.push(trimmed);
411
+ }
412
+ return out;
413
+ }
414
+ function parsePersistedPendingPrompt(value) {
415
+ if (!isPlainObject(value)) {
416
+ return null;
417
+ }
418
+ const kind = value.kind === "action" || value.kind === "review" ? value.kind : null;
419
+ if (!kind) {
420
+ return null;
421
+ }
422
+ const uiIdRaw = typeof value.ui_id === "string" ? value.ui_id.trim() : "";
423
+ if (!uiIdRaw) {
424
+ return null;
425
+ }
426
+ const actionIdRaw = typeof value.action_id === "string" ? value.action_id.trim() : "";
427
+ if (kind === "review") {
428
+ return { kind, ui_id: uiIdRaw };
429
+ }
430
+ if (actionIdRaw) {
431
+ return { kind, ui_id: uiIdRaw, action_id: actionIdRaw };
432
+ }
433
+ return { kind, ui_id: uiIdRaw };
434
+ }
435
+ function parsePersistedUiState(value) {
436
+ if (!isPlainObject(value)) {
437
+ return null;
438
+ }
439
+ if (value.v !== UI_STATE_SCHEMA_VERSION) {
440
+ return null;
441
+ }
442
+ const parsedDocs = parseDocListInput(value.docs);
443
+ if (!parsedDocs.ok) {
444
+ return null;
445
+ }
446
+ const pendingPrompts = [];
447
+ const pendingRaw = Array.isArray(value.pending_prompts) ? value.pending_prompts : [];
448
+ for (const pending of pendingRaw) {
449
+ const parsed = parsePersistedPendingPrompt(pending);
450
+ if (parsed) {
451
+ pendingPrompts.push(parsed);
452
+ }
453
+ }
454
+ return {
455
+ v: 1,
456
+ docs: parsedDocs.docs,
457
+ pending_prompts: pendingPrompts,
458
+ prompted_revision_keys: normalizeStringArray(value.prompted_revision_keys),
459
+ awaiting_ui_ids: normalizeStringArray(value.awaiting_ui_ids),
460
+ };
461
+ }
462
+ function serializeUiState(state) {
463
+ retainPromptedRevisionKeysForActiveDocs(state);
464
+ retainAwaitingUiIdsForActiveDocs(state);
465
+ retainPendingPromptsForActiveDocs(state);
466
+ const docs = activeDocs(state, UI_DISPLAY_DOCS_MAX);
467
+ return {
468
+ v: 1,
469
+ docs,
470
+ pending_prompts: state.pendingPrompts.map((pending) => ({
471
+ kind: pending.kind,
472
+ ui_id: pending.uiId,
473
+ ...(pending.actionId ? { action_id: pending.actionId } : {}),
474
+ })),
475
+ prompted_revision_keys: [...state.promptedRevisionKeys].sort((left, right) => left.localeCompare(right)),
476
+ awaiting_ui_ids: [...state.awaitingUiIds].sort((left, right) => left.localeCompare(right)),
477
+ };
478
+ }
479
+ function uiStatePersistSignature(value) {
480
+ return stableSerializeJson(value);
481
+ }
482
+ function persistUiStateSnapshot(pi, state) {
483
+ const snapshot = serializeUiState(state);
484
+ const signature = uiStatePersistSignature(snapshot);
485
+ if (state.lastPersistSignature === signature) {
486
+ return;
487
+ }
488
+ pi.appendEntry(UI_STATE_CUSTOM_TYPE, snapshot);
489
+ state.lastPersistSignature = signature;
490
+ }
491
+ function applyPersistedUiState(state, persisted) {
492
+ state.docsById.clear();
493
+ state.pendingPrompts = [];
494
+ state.promptedRevisionKeys.clear();
495
+ state.awaitingUiIds.clear();
496
+ state.interactionDepth = 0;
497
+ state.lastStatusText = null;
498
+ state.lastWidgetSignature = null;
499
+ for (const doc of persisted.docs) {
500
+ state.docsById.set(doc.ui_id, doc);
501
+ }
502
+ for (const revisionKey of persisted.prompted_revision_keys) {
503
+ state.promptedRevisionKeys.add(revisionKey);
504
+ }
505
+ for (const uiId of persisted.awaiting_ui_ids) {
506
+ state.awaitingUiIds.add(uiId);
507
+ }
508
+ for (const pending of persisted.pending_prompts) {
509
+ enqueuePendingPrompt(state, {
510
+ kind: pending.kind,
511
+ uiId: pending.ui_id,
512
+ actionId: pending.action_id,
513
+ });
514
+ }
515
+ retainPromptedRevisionKeysForActiveDocs(state);
516
+ retainAwaitingUiIdsForActiveDocs(state);
517
+ retainPendingPromptsForActiveDocs(state);
518
+ state.lastPersistSignature = uiStatePersistSignature(serializeUiState(state));
519
+ }
520
+ function latestPersistedUiStateFromBranch(entries) {
521
+ let latest = null;
522
+ for (const entry of entries) {
523
+ if (!isPlainObject(entry)) {
524
+ continue;
525
+ }
526
+ if (entry.type !== "custom" || entry.customType !== UI_STATE_CUSTOM_TYPE) {
527
+ continue;
528
+ }
529
+ const parsed = parsePersistedUiState(entry.data);
530
+ if (!parsed) {
531
+ continue;
532
+ }
533
+ latest = parsed;
534
+ }
535
+ return latest;
536
+ }
537
+ function restoreStateFromSessionBranch(ctx, key) {
538
+ const manager = ctx.sessionManager;
539
+ const branch = typeof manager.getBranch === "function" ? manager.getBranch() : [];
540
+ const restored = createState();
541
+ const persisted = latestPersistedUiStateFromBranch(branch);
542
+ if (persisted) {
543
+ applyPersistedUiState(restored, persisted);
544
+ }
545
+ const nowMs = Date.now();
546
+ pruneStaleStates(nowMs);
547
+ STATE_BY_SESSION.set(key, {
548
+ state: restored,
549
+ lastAccessMs: nowMs,
550
+ });
551
+ return restored;
552
+ }
392
553
  function statusProfileWarningsExtraForDoc(doc) {
393
554
  const warnings = uiStatusProfileWarnings(doc);
394
555
  if (warnings.length === 0) {
@@ -1355,6 +1516,7 @@ export function uiExtension(pi) {
1355
1516
  removePendingPromptsForUiId(state, selectedDoc.ui_id);
1356
1517
  retainAwaitingUiIdsForActiveDocs(state);
1357
1518
  retainPendingPromptsForActiveDocs(state);
1519
+ persistUiStateSnapshot(pi, state);
1358
1520
  ctx.ui.notify(`Submitted prompt from ${selectedDoc.ui_id}/${selectedAction.id}.`, "info");
1359
1521
  });
1360
1522
  registerMuSubcommand(pi, {
@@ -1432,15 +1594,35 @@ export function uiExtension(pi) {
1432
1594
  if (ctx.hasUI && result.ok && result.changedUiIds && result.changedUiIds.length > 0) {
1433
1595
  armAutoPromptForUiDocs(state, result.changedUiIds);
1434
1596
  }
1597
+ const shouldPersistMutation = result.ok &&
1598
+ (result.action === "set" ||
1599
+ result.action === "update" ||
1600
+ result.action === "replace" ||
1601
+ result.action === "remove" ||
1602
+ result.action === "clear");
1603
+ if (shouldPersistMutation) {
1604
+ persistUiStateSnapshot(pi, state);
1605
+ }
1435
1606
  refreshUi(ctx);
1436
1607
  return buildToolResult({ state, ...result });
1437
1608
  },
1438
1609
  });
1439
- pi.on("session_start", (_event, ctx) => {
1610
+ const restoreAndRefresh = (ctx) => {
1611
+ const key = sessionKey(ctx);
1612
+ restoreStateFromSessionBranch(ctx, key);
1440
1613
  refreshUi(ctx);
1614
+ };
1615
+ pi.on("session_start", (_event, ctx) => {
1616
+ restoreAndRefresh(ctx);
1441
1617
  });
1442
1618
  pi.on("session_switch", (_event, ctx) => {
1443
- refreshUi(ctx);
1619
+ restoreAndRefresh(ctx);
1620
+ });
1621
+ pi.on("session_fork", (_event, ctx) => {
1622
+ restoreAndRefresh(ctx);
1623
+ });
1624
+ pi.on("session_tree", (_event, ctx) => {
1625
+ restoreAndRefresh(ctx);
1444
1626
  });
1445
1627
  pi.on("agent_end", async (_event, ctx) => {
1446
1628
  if (!ctx.hasUI) {
@@ -1453,6 +1635,7 @@ export function uiExtension(pi) {
1453
1635
  if (!pending) {
1454
1636
  return;
1455
1637
  }
1638
+ persistUiStateSnapshot(pi, state);
1456
1639
  if (pending.kind === "action") {
1457
1640
  ctx.ui.notify(`Agent requested input via ${pending.uiId}. Submit now or press ${UI_INTERACT_SHORTCUT_HINT} later.`, "info");
1458
1641
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@femtomc/mu-agent",
3
- "version": "26.3.3",
3
+ "version": "26.3.4",
4
4
  "description": "Shared operator runtime for mu assistant sessions and serve extensions.",
5
5
  "keywords": [
6
6
  "mu",
@@ -24,7 +24,7 @@
24
24
  "themes/**"
25
25
  ],
26
26
  "dependencies": {
27
- "@femtomc/mu-core": "26.3.3",
27
+ "@femtomc/mu-core": "26.3.4",
28
28
  "@mariozechner/pi-agent-core": "^0.54.2",
29
29
  "@mariozechner/pi-ai": "^0.54.2",
30
30
  "@mariozechner/pi-coding-agent": "^0.54.2",