@femtomc/mu-agent 26.2.109 → 26.2.110
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/assets/mu-tui-logo.png +0 -0
- package/dist/extensions/ui.d.ts.map +1 -1
- package/dist/extensions/ui.js +83 -129
- package/package.json +2 -2
package/assets/mu-tui-logo.png
CHANGED
|
Binary file
|
|
@@ -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;
|
|
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;AAs/BpF,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,QA6N3C;AAED,eAAe,WAAW,CAAC"}
|
package/dist/extensions/ui.js
CHANGED
|
@@ -2,9 +2,6 @@ import { normalizeUiDocs, parseUiDoc, resolveUiStatusProfileName, uiStatusProfil
|
|
|
2
2
|
import { matchesKey } from "@mariozechner/pi-tui";
|
|
3
3
|
import { registerMuSubcommand } from "./mu-command-dispatcher.js";
|
|
4
4
|
const UI_DISPLAY_DOCS_MAX = 16;
|
|
5
|
-
const UI_WIDGET_COMPONENTS_MAX = 6;
|
|
6
|
-
const UI_WIDGET_ACTIONS_MAX = 4;
|
|
7
|
-
const UI_WIDGET_COMPONENT_DETAILS_MAX = 3;
|
|
8
5
|
const UI_PICKER_COMPONENTS_MAX = 8;
|
|
9
6
|
const UI_PICKER_LIST_ITEMS_MAX = 4;
|
|
10
7
|
const UI_PICKER_KEYVALUE_ROWS_MAX = 4;
|
|
@@ -24,7 +21,7 @@ const UI_STATE_TTL_MS = 30 * 60 * 1000; // keep session state for 30 minutes aft
|
|
|
24
21
|
function createState() {
|
|
25
22
|
return {
|
|
26
23
|
docsById: new Map(),
|
|
27
|
-
|
|
24
|
+
pendingPrompts: [],
|
|
28
25
|
promptedRevisionKeys: new Set(),
|
|
29
26
|
awaitingUiIds: new Set(),
|
|
30
27
|
interactionDepth: 0,
|
|
@@ -88,6 +85,38 @@ function retainAwaitingUiIdsForActiveDocs(state) {
|
|
|
88
85
|
}
|
|
89
86
|
}
|
|
90
87
|
}
|
|
88
|
+
function retainPendingPromptsForActiveDocs(state) {
|
|
89
|
+
state.pendingPrompts = state.pendingPrompts.filter((pending) => {
|
|
90
|
+
const doc = state.docsById.get(pending.uiId);
|
|
91
|
+
if (!doc) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (pending.kind === "review") {
|
|
95
|
+
return isStatusProfileStatusVariant(doc);
|
|
96
|
+
}
|
|
97
|
+
const actions = runnableActions(doc);
|
|
98
|
+
if (actions.length === 0) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (!pending.actionId) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
return actions.some((action) => action.id === pending.actionId);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function removePendingPromptsForUiId(state, uiId) {
|
|
108
|
+
state.pendingPrompts = state.pendingPrompts.filter((pending) => pending.uiId !== uiId);
|
|
109
|
+
}
|
|
110
|
+
function enqueuePendingPrompt(state, pending) {
|
|
111
|
+
const duplicate = state.pendingPrompts.some((existing) => {
|
|
112
|
+
return (existing.kind === pending.kind &&
|
|
113
|
+
existing.uiId === pending.uiId &&
|
|
114
|
+
existing.actionId === pending.actionId);
|
|
115
|
+
});
|
|
116
|
+
if (!duplicate) {
|
|
117
|
+
state.pendingPrompts.push(pending);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
91
120
|
function armAutoPromptForUiDocs(state, changedUiIds) {
|
|
92
121
|
if (changedUiIds.length === 0) {
|
|
93
122
|
return;
|
|
@@ -99,26 +128,30 @@ function armAutoPromptForUiDocs(state, changedUiIds) {
|
|
|
99
128
|
changedDocs.push(doc);
|
|
100
129
|
}
|
|
101
130
|
}
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
return !state.promptedRevisionKeys.has(docRevisionKey(doc));
|
|
107
|
-
});
|
|
108
|
-
if (candidates.length === 0) {
|
|
131
|
+
const unpromptedDocs = changedDocs.filter((doc) => !state.promptedRevisionKeys.has(docRevisionKey(doc)));
|
|
132
|
+
if (unpromptedDocs.length === 0) {
|
|
109
133
|
return;
|
|
110
134
|
}
|
|
111
|
-
|
|
135
|
+
const byMostRecentRevision = (left, right) => {
|
|
112
136
|
if (left.updated_at_ms !== right.updated_at_ms) {
|
|
113
137
|
return right.updated_at_ms - left.updated_at_ms;
|
|
114
138
|
}
|
|
115
139
|
return left.ui_id.localeCompare(right.ui_id);
|
|
116
|
-
}
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
140
|
+
};
|
|
141
|
+
const runnableCandidates = unpromptedDocs.filter((doc) => runnableActions(doc).length > 0).sort(byMostRecentRevision);
|
|
142
|
+
if (runnableCandidates.length > 0) {
|
|
143
|
+
const doc = runnableCandidates[0];
|
|
144
|
+
const actions = runnableActions(doc);
|
|
145
|
+
const actionId = actions.length === 1 ? actions[0].id : undefined;
|
|
146
|
+
enqueuePendingPrompt(state, { kind: "action", uiId: doc.ui_id, actionId });
|
|
147
|
+
state.promptedRevisionKeys.add(docRevisionKey(doc));
|
|
148
|
+
}
|
|
149
|
+
const statusCandidates = unpromptedDocs.filter((doc) => isStatusProfileStatusVariant(doc)).sort(byMostRecentRevision);
|
|
150
|
+
if (statusCandidates.length > 0) {
|
|
151
|
+
const doc = statusCandidates[0];
|
|
152
|
+
enqueuePendingPrompt(state, { kind: "review", uiId: doc.ui_id });
|
|
153
|
+
state.promptedRevisionKeys.add(docRevisionKey(doc));
|
|
154
|
+
}
|
|
122
155
|
}
|
|
123
156
|
function preferredDocForState(state, candidate) {
|
|
124
157
|
const existing = state.docsById.get(candidate.ui_id);
|
|
@@ -457,9 +490,11 @@ function boundedIndex(index, length) {
|
|
|
457
490
|
function pickerComponentLines(component) {
|
|
458
491
|
switch (component.kind) {
|
|
459
492
|
case "text":
|
|
460
|
-
return [
|
|
493
|
+
return [component.text];
|
|
461
494
|
case "list": {
|
|
462
|
-
const
|
|
495
|
+
const title = component.title?.trim();
|
|
496
|
+
const prefix = title && title.length > 0 ? `${title} · ` : "";
|
|
497
|
+
const lines = [`${prefix}${component.items.length} item(s)`];
|
|
463
498
|
const visible = component.items.slice(0, UI_PICKER_LIST_ITEMS_MAX);
|
|
464
499
|
for (const item of visible) {
|
|
465
500
|
const detail = item.detail ? ` — ${item.detail}` : "";
|
|
@@ -471,7 +506,9 @@ function pickerComponentLines(component) {
|
|
|
471
506
|
return lines;
|
|
472
507
|
}
|
|
473
508
|
case "key_value": {
|
|
474
|
-
const
|
|
509
|
+
const title = component.title?.trim();
|
|
510
|
+
const prefix = title && title.length > 0 ? `${title} · ` : "";
|
|
511
|
+
const lines = [`${prefix}${component.rows.length} row(s)`];
|
|
475
512
|
const visible = component.rows.slice(0, UI_PICKER_KEYVALUE_ROWS_MAX);
|
|
476
513
|
for (const row of visible) {
|
|
477
514
|
lines.push(`${row.key}: ${row.value}`);
|
|
@@ -482,7 +519,7 @@ function pickerComponentLines(component) {
|
|
|
482
519
|
return lines;
|
|
483
520
|
}
|
|
484
521
|
case "divider":
|
|
485
|
-
return ["
|
|
522
|
+
return ["—"];
|
|
486
523
|
default:
|
|
487
524
|
return ["component"];
|
|
488
525
|
}
|
|
@@ -670,6 +707,7 @@ async function pickUiActionInteractively(opts) {
|
|
|
670
707
|
function applyUiAction(params, state) {
|
|
671
708
|
retainPromptedRevisionKeysForActiveDocs(state);
|
|
672
709
|
retainAwaitingUiIdsForActiveDocs(state);
|
|
710
|
+
retainPendingPromptsForActiveDocs(state);
|
|
673
711
|
const docs = activeDocs(state);
|
|
674
712
|
const awaitingCount = awaitingDocs(state, docs).length;
|
|
675
713
|
switch (params.action) {
|
|
@@ -712,6 +750,7 @@ function applyUiAction(params, state) {
|
|
|
712
750
|
}
|
|
713
751
|
retainPromptedRevisionKeysForActiveDocs(state);
|
|
714
752
|
retainAwaitingUiIdsForActiveDocs(state);
|
|
753
|
+
retainPendingPromptsForActiveDocs(state);
|
|
715
754
|
return {
|
|
716
755
|
ok: true,
|
|
717
756
|
action: params.action,
|
|
@@ -735,6 +774,7 @@ function applyUiAction(params, state) {
|
|
|
735
774
|
}
|
|
736
775
|
retainPromptedRevisionKeysForActiveDocs(state);
|
|
737
776
|
retainAwaitingUiIdsForActiveDocs(state);
|
|
777
|
+
retainPendingPromptsForActiveDocs(state);
|
|
738
778
|
return {
|
|
739
779
|
ok: true,
|
|
740
780
|
action: "replace",
|
|
@@ -754,17 +794,16 @@ function applyUiAction(params, state) {
|
|
|
754
794
|
if (!state.docsById.delete(uiId)) {
|
|
755
795
|
return { ok: false, action: "remove", message: `UI doc not found: ${uiId}` };
|
|
756
796
|
}
|
|
757
|
-
|
|
758
|
-
state.pendingPrompt = null;
|
|
759
|
-
}
|
|
797
|
+
removePendingPromptsForUiId(state, uiId);
|
|
760
798
|
state.awaitingUiIds.delete(uiId);
|
|
761
799
|
retainPromptedRevisionKeysForActiveDocs(state);
|
|
762
800
|
retainAwaitingUiIdsForActiveDocs(state);
|
|
801
|
+
retainPendingPromptsForActiveDocs(state);
|
|
763
802
|
return { ok: true, action: "remove", message: `UI doc removed: ${uiId}` };
|
|
764
803
|
}
|
|
765
804
|
case "clear":
|
|
766
805
|
state.docsById.clear();
|
|
767
|
-
state.
|
|
806
|
+
state.pendingPrompts = [];
|
|
768
807
|
state.promptedRevisionKeys.clear();
|
|
769
808
|
state.awaitingUiIds.clear();
|
|
770
809
|
return { ok: true, action: "clear", message: "UI docs cleared." };
|
|
@@ -785,84 +824,6 @@ function buildToolResult(opts) {
|
|
|
785
824
|
};
|
|
786
825
|
return result;
|
|
787
826
|
}
|
|
788
|
-
function renderDocPreview(theme, doc, opts) {
|
|
789
|
-
const lines = [];
|
|
790
|
-
const headerParts = [theme.fg("accent", doc.title), theme.fg("muted", `[${doc.ui_id}]`)];
|
|
791
|
-
if (opts.awaitingResponse) {
|
|
792
|
-
headerParts.push(theme.fg("accent", "awaiting-response"));
|
|
793
|
-
}
|
|
794
|
-
lines.push(headerParts.join(" "));
|
|
795
|
-
if (doc.summary) {
|
|
796
|
-
lines.push(theme.fg("muted", short(doc.summary, 80)));
|
|
797
|
-
}
|
|
798
|
-
if (opts.awaitingCount > 0) {
|
|
799
|
-
lines.push(theme.fg("accent", `Awaiting user response for ${opts.awaitingCount} UI doc(s).`));
|
|
800
|
-
}
|
|
801
|
-
const components = doc.components.slice(0, UI_WIDGET_COMPONENTS_MAX);
|
|
802
|
-
if (components.length > 0) {
|
|
803
|
-
lines.push(theme.fg("dim", "Components:"));
|
|
804
|
-
for (const component of components) {
|
|
805
|
-
const previewLines = componentPreviewLines(component);
|
|
806
|
-
for (let idx = 0; idx < previewLines.length; idx += 1) {
|
|
807
|
-
const line = previewLines[idx];
|
|
808
|
-
lines.push(`${idx === 0 ? " " : " "}${line}`);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
const interactiveActions = runnableActions(doc);
|
|
813
|
-
if (interactiveActions.length > 0) {
|
|
814
|
-
lines.push(theme.fg("muted", "Actions:"));
|
|
815
|
-
const visibleActions = interactiveActions.slice(0, UI_WIDGET_ACTIONS_MAX);
|
|
816
|
-
for (let idx = 0; idx < visibleActions.length; idx += 1) {
|
|
817
|
-
const action = visibleActions[idx];
|
|
818
|
-
lines.push(` ${idx + 1}. ${action.label}`);
|
|
819
|
-
}
|
|
820
|
-
if (interactiveActions.length > visibleActions.length) {
|
|
821
|
-
lines.push(` ... (+${interactiveActions.length - visibleActions.length} more actions)`);
|
|
822
|
-
}
|
|
823
|
-
if (opts.awaitingResponse) {
|
|
824
|
-
lines.push(theme.fg("accent", "Awaiting your response. Select an action to continue."));
|
|
825
|
-
}
|
|
826
|
-
lines.push(theme.fg("dim", `Press ${UI_INTERACT_SHORTCUT} to compose and submit a prompt from actions.`));
|
|
827
|
-
}
|
|
828
|
-
else {
|
|
829
|
-
lines.push(theme.fg("dim", "No interactive actions."));
|
|
830
|
-
}
|
|
831
|
-
return lines;
|
|
832
|
-
}
|
|
833
|
-
function componentPreviewLines(component) {
|
|
834
|
-
const { kind } = component;
|
|
835
|
-
switch (kind) {
|
|
836
|
-
case "text":
|
|
837
|
-
return [`text · ${short(component.text, 80)}`];
|
|
838
|
-
case "list": {
|
|
839
|
-
const lines = [`list · ${component.title ?? kind} · ${component.items.length} item(s)`];
|
|
840
|
-
const visible = component.items.slice(0, UI_WIDGET_COMPONENT_DETAILS_MAX);
|
|
841
|
-
for (const item of visible) {
|
|
842
|
-
const detail = item.detail ? ` — ${short(item.detail, 28)}` : "";
|
|
843
|
-
lines.push(`• ${short(item.label, 72)}${detail}`);
|
|
844
|
-
}
|
|
845
|
-
if (component.items.length > visible.length) {
|
|
846
|
-
lines.push(`... (+${component.items.length - visible.length} more items)`);
|
|
847
|
-
}
|
|
848
|
-
return lines;
|
|
849
|
-
}
|
|
850
|
-
case "key_value": {
|
|
851
|
-
const lines = [`key_value · ${component.title ?? kind} · ${component.rows.length} row(s)`];
|
|
852
|
-
const visible = component.rows.slice(0, UI_WIDGET_COMPONENT_DETAILS_MAX);
|
|
853
|
-
for (const row of visible) {
|
|
854
|
-
lines.push(`${short(row.key, 20)}: ${short(row.value, 52)}`);
|
|
855
|
-
}
|
|
856
|
-
if (component.rows.length > visible.length) {
|
|
857
|
-
lines.push(`... (+${component.rows.length - visible.length} more rows)`);
|
|
858
|
-
}
|
|
859
|
-
return lines;
|
|
860
|
-
}
|
|
861
|
-
case "divider":
|
|
862
|
-
return ["divider"];
|
|
863
|
-
}
|
|
864
|
-
return [kind];
|
|
865
|
-
}
|
|
866
827
|
function refreshUi(ctx) {
|
|
867
828
|
const key = sessionKey(ctx);
|
|
868
829
|
const state = ensureState(key);
|
|
@@ -892,15 +853,7 @@ function refreshUi(ctx) {
|
|
|
892
853
|
ctx.ui.theme.fg("muted", "·"),
|
|
893
854
|
ctx.ui.theme.fg("text", labels),
|
|
894
855
|
].join(" "));
|
|
895
|
-
|
|
896
|
-
ctx.ui.setWidget("mu-ui", undefined);
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
const primaryDoc = awaiting[0] ?? docs[0];
|
|
900
|
-
ctx.ui.setWidget("mu-ui", renderDocPreview(ctx.ui.theme, primaryDoc, {
|
|
901
|
-
awaitingResponse: state.awaitingUiIds.has(primaryDoc.ui_id),
|
|
902
|
-
awaitingCount: awaiting.length,
|
|
903
|
-
}), { placement: "belowEditor" });
|
|
856
|
+
ctx.ui.setWidget("mu-ui", undefined);
|
|
904
857
|
}
|
|
905
858
|
export function uiExtension(pi) {
|
|
906
859
|
const commandUsage = "/mu ui status|snapshot [compact|multiline]|interact [ui_id [action_id]]";
|
|
@@ -911,13 +864,8 @@ export function uiExtension(pi) {
|
|
|
911
864
|
ctx.ui.notify("No UI docs are currently available.", "info");
|
|
912
865
|
return;
|
|
913
866
|
}
|
|
914
|
-
const entries = docs
|
|
915
|
-
|
|
916
|
-
.filter((entry) => entry.actions.length > 0);
|
|
917
|
-
if (entries.length === 0) {
|
|
918
|
-
ctx.ui.notify("No runnable UI actions are currently available.", "error");
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
867
|
+
const entries = docs.map((doc) => ({ doc, actions: runnableActions(doc) }));
|
|
868
|
+
const runnableEntries = entries.filter((entry) => entry.actions.length > 0);
|
|
921
869
|
let selectedDoc = null;
|
|
922
870
|
let selectedAction = null;
|
|
923
871
|
const normalizedUiId = uiId?.trim() ?? "";
|
|
@@ -944,14 +892,19 @@ export function uiExtension(pi) {
|
|
|
944
892
|
actionId: normalizedActionId.length > 0 ? normalizedActionId : undefined,
|
|
945
893
|
});
|
|
946
894
|
if (!picked) {
|
|
947
|
-
|
|
895
|
+
if (runnableEntries.length > 0) {
|
|
896
|
+
ctx.ui.notify("UI interaction cancelled.", "info");
|
|
897
|
+
}
|
|
948
898
|
return;
|
|
949
899
|
}
|
|
950
900
|
selectedDoc = picked.doc;
|
|
951
901
|
selectedAction = picked.action;
|
|
952
902
|
}
|
|
953
903
|
if (!selectedDoc || !selectedAction) {
|
|
954
|
-
|
|
904
|
+
if (runnableEntries.length === 0) {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
ctx.ui.notify("No UI action was selected.", "info");
|
|
955
908
|
return;
|
|
956
909
|
}
|
|
957
910
|
const commandText = actionCommandText(selectedAction);
|
|
@@ -990,10 +943,9 @@ export function uiExtension(pi) {
|
|
|
990
943
|
}
|
|
991
944
|
pi.sendUserMessage(finalPrompt);
|
|
992
945
|
state.awaitingUiIds.delete(selectedDoc.ui_id);
|
|
993
|
-
|
|
994
|
-
state.pendingPrompt = null;
|
|
995
|
-
}
|
|
946
|
+
removePendingPromptsForUiId(state, selectedDoc.ui_id);
|
|
996
947
|
retainAwaitingUiIdsForActiveDocs(state);
|
|
948
|
+
retainPendingPromptsForActiveDocs(state);
|
|
997
949
|
ctx.ui.notify(`Submitted prompt from ${selectedDoc.ui_id}/${selectedAction.id}.`, "info");
|
|
998
950
|
});
|
|
999
951
|
registerMuSubcommand(pi, {
|
|
@@ -1034,7 +986,7 @@ export function uiExtension(pi) {
|
|
|
1034
986
|
},
|
|
1035
987
|
});
|
|
1036
988
|
pi.registerShortcut(UI_INTERACT_SHORTCUT, {
|
|
1037
|
-
description: "
|
|
989
|
+
description: "Open programmable UI modal and optionally submit prompt",
|
|
1038
990
|
handler: async (ctx) => {
|
|
1039
991
|
const key = sessionKey(ctx);
|
|
1040
992
|
const state = ensureState(key);
|
|
@@ -1085,12 +1037,14 @@ export function uiExtension(pi) {
|
|
|
1085
1037
|
}
|
|
1086
1038
|
const key = sessionKey(ctx);
|
|
1087
1039
|
const state = ensureState(key);
|
|
1088
|
-
|
|
1040
|
+
retainPendingPromptsForActiveDocs(state);
|
|
1041
|
+
const pending = state.pendingPrompts.shift();
|
|
1089
1042
|
if (!pending) {
|
|
1090
1043
|
return;
|
|
1091
1044
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1045
|
+
if (pending.kind === "action") {
|
|
1046
|
+
ctx.ui.notify(`Agent requested input via ${pending.uiId}. Submit now or press ${UI_INTERACT_SHORTCUT} later.`, "info");
|
|
1047
|
+
}
|
|
1094
1048
|
await runUiActionFromDoc(ctx, state, pending.uiId, pending.actionId);
|
|
1095
1049
|
refreshUi(ctx);
|
|
1096
1050
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-agent",
|
|
3
|
-
"version": "26.2.
|
|
3
|
+
"version": "26.2.110",
|
|
4
4
|
"description": "Shared operator runtime for mu assistant sessions and serve extensions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mu",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"themes/**"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@femtomc/mu-core": "26.2.
|
|
28
|
+
"@femtomc/mu-core": "26.2.110",
|
|
29
29
|
"@mariozechner/pi-agent-core": "^0.54.2",
|
|
30
30
|
"@mariozechner/pi-ai": "^0.54.2",
|
|
31
31
|
"@mariozechner/pi-coding-agent": "^0.54.2",
|