@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 +1 -1
- package/dist/extensions/ui.d.ts.map +1 -1
- package/dist/extensions/ui.js +186 -3
- package/package.json +2 -2
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":"
|
|
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"}
|
package/dist/extensions/ui.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
+
"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.
|
|
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",
|