@femtomc/mu-agent 26.2.108 → 26.2.109
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 +4 -19
- package/assets/mu-tui-logo.png +0 -0
- package/dist/extensions/ui.d.ts.map +1 -1
- package/dist/extensions/ui.js +72 -20
- package/package.json +2 -2
- package/prompts/skills/core/SKILL.md +7 -4
- package/prompts/skills/core/mu/SKILL.md +2 -0
- package/prompts/skills/core/programmable-ui/SKILL.md +299 -0
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ They are organized as category meta-skills plus subskills:
|
|
|
27
27
|
|
|
28
28
|
- `core`
|
|
29
29
|
- `mu`
|
|
30
|
+
- `programmable-ui`
|
|
30
31
|
- `memory`
|
|
31
32
|
- `tmux`
|
|
32
33
|
- `code-mode`
|
|
@@ -48,6 +49,7 @@ They are organized as category meta-skills plus subskills:
|
|
|
48
49
|
|
|
49
50
|
Starter skills are version-synced by CLI bootstrap. Initial bootstrap seeds missing
|
|
50
51
|
skills; bundled-version changes refresh installed starter skill files.
|
|
52
|
+
Operator-facing workflow detail lives in the skill docs (for example, `core/programmable-ui`).
|
|
51
53
|
|
|
52
54
|
## Install
|
|
53
55
|
|
|
@@ -75,7 +77,7 @@ Current stack:
|
|
|
75
77
|
|
|
76
78
|
- `brandingExtension` — mu compact header/footer branding + default theme
|
|
77
79
|
- `eventLogExtension` — event tail + watch widget
|
|
78
|
-
- `uiExtension` — programmable `UiDoc` surface (`/mu ui ...`, `mu_ui`)
|
|
80
|
+
- `uiExtension` — programmable `UiDoc` surface (`/mu ui ...`, `mu_ui`)
|
|
79
81
|
|
|
80
82
|
Default operator UI theme is `mu-gruvbox-dark`.
|
|
81
83
|
|
|
@@ -86,24 +88,7 @@ Default operator UI theme is `mu-gruvbox-dark`.
|
|
|
86
88
|
- `/mu brand on|off|toggle` — enable/disable UI branding
|
|
87
89
|
- `/mu ui ...` — inspect interactive `UiDoc`s (`status`/`snapshot`)
|
|
88
90
|
- `/mu help` — dispatcher catalog of registered `/mu` subcommands
|
|
89
|
-
- `ctrl+shift+u` — reopen local programmable-UI interaction flow
|
|
90
|
-
|
|
91
|
-
## Programmable UI documents
|
|
92
|
-
|
|
93
|
-
Skills can publish interactive UI state via the `mu_ui` tool. Rendered `UiDoc`s survive session reconnects
|
|
94
|
-
(30 minute retention per session ID), respect revision/version bumps, and route action clicks/taps back to
|
|
95
|
-
plain command text via `metadata.command_text` (the `/answer` flow is the reference pattern).
|
|
96
|
-
|
|
97
|
-
Actions without `metadata.command_text` are treated as non-interactive and rendered as deterministic fallback rows.
|
|
98
|
-
|
|
99
|
-
Current runtime behavior is channel-specific:
|
|
100
|
-
|
|
101
|
-
- Slack renders rich blocks + interactive action buttons.
|
|
102
|
-
- Discord/Telegram/Neovim render text-first docs; interactive actions are tokenized, while status-profile actions deterministically degrade to command-text fallback.
|
|
103
|
-
- Terminal operator UI (`mu serve`) renders docs in-widget, auto-prompts when agent publishes new runnable actions, shows `awaiting` UI status/widget state until resolved, and supports manual reopen via `ctrl+shift+u` (in-TUI picker overlay + prompt composition).
|
|
104
|
-
- When interactive controls cannot be rendered, adapters append deterministic text fallback.
|
|
105
|
-
|
|
106
|
-
See the [Programmable UI substrate guide](../../docs/mu-ui.md) for the full support matrix and workflow.
|
|
91
|
+
- `ctrl+shift+u` — reopen local programmable-UI interaction flow
|
|
107
92
|
|
|
108
93
|
## Tooling model (CLI-first)
|
|
109
94
|
|
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;AAsiCpF,wBAAgB,WAAW,CAAC,EAAE,EAAE,YAAY,QA4N3C;AAED,eAAe,WAAW,CAAC"}
|
package/dist/extensions/ui.js
CHANGED
|
@@ -4,12 +4,21 @@ import { registerMuSubcommand } from "./mu-command-dispatcher.js";
|
|
|
4
4
|
const UI_DISPLAY_DOCS_MAX = 16;
|
|
5
5
|
const UI_WIDGET_COMPONENTS_MAX = 6;
|
|
6
6
|
const UI_WIDGET_ACTIONS_MAX = 4;
|
|
7
|
+
const UI_WIDGET_COMPONENT_DETAILS_MAX = 3;
|
|
7
8
|
const UI_PICKER_COMPONENTS_MAX = 8;
|
|
8
9
|
const UI_PICKER_LIST_ITEMS_MAX = 4;
|
|
9
10
|
const UI_PICKER_KEYVALUE_ROWS_MAX = 4;
|
|
10
11
|
const UI_SESSION_KEY_FALLBACK = "__mu_ui_active_session__";
|
|
11
12
|
const UI_PROMPT_PREVIEW_MAX = 160;
|
|
12
13
|
const UI_INTERACT_SHORTCUT = "ctrl+shift+u";
|
|
14
|
+
const UI_INTERACT_OVERLAY_OPTIONS = {
|
|
15
|
+
anchor: "top-left",
|
|
16
|
+
row: 0,
|
|
17
|
+
col: 0,
|
|
18
|
+
width: "100%",
|
|
19
|
+
maxHeight: "100%",
|
|
20
|
+
margin: 0,
|
|
21
|
+
};
|
|
13
22
|
const STATE_BY_SESSION = new Map();
|
|
14
23
|
const UI_STATE_TTL_MS = 30 * 60 * 1000; // keep session state for 30 minutes after last access
|
|
15
24
|
function createState() {
|
|
@@ -18,6 +27,7 @@ function createState() {
|
|
|
18
27
|
pendingPrompt: null,
|
|
19
28
|
promptedRevisionKeys: new Set(),
|
|
20
29
|
awaitingUiIds: new Set(),
|
|
30
|
+
interactionDepth: 0,
|
|
21
31
|
};
|
|
22
32
|
}
|
|
23
33
|
function pruneStaleStates(nowMs) {
|
|
@@ -122,6 +132,23 @@ function preferredDocForState(state, candidate) {
|
|
|
122
132
|
function awaitingDocs(state, docs) {
|
|
123
133
|
return docs.filter((doc) => state.awaitingUiIds.has(doc.ui_id) && runnableActions(doc).length > 0);
|
|
124
134
|
}
|
|
135
|
+
function beginUiInteraction(ctx, state) {
|
|
136
|
+
state.interactionDepth += 1;
|
|
137
|
+
refreshUi(ctx);
|
|
138
|
+
}
|
|
139
|
+
function endUiInteraction(ctx, state) {
|
|
140
|
+
state.interactionDepth = Math.max(0, state.interactionDepth - 1);
|
|
141
|
+
refreshUi(ctx);
|
|
142
|
+
}
|
|
143
|
+
async function withUiInteraction(ctx, state, run) {
|
|
144
|
+
beginUiInteraction(ctx, state);
|
|
145
|
+
try {
|
|
146
|
+
return await run();
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
endUiInteraction(ctx, state);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
125
152
|
function short(text, max = 64) {
|
|
126
153
|
const normalized = text.replace(/\s+/g, " ").trim();
|
|
127
154
|
if (normalized.length <= max) {
|
|
@@ -636,12 +663,7 @@ async function pickUiActionInteractively(opts) {
|
|
|
636
663
|
initialActionId: opts.actionId,
|
|
637
664
|
}), {
|
|
638
665
|
overlay: true,
|
|
639
|
-
overlayOptions:
|
|
640
|
-
anchor: "center",
|
|
641
|
-
width: "78%",
|
|
642
|
-
maxHeight: "70%",
|
|
643
|
-
margin: 1,
|
|
644
|
-
},
|
|
666
|
+
overlayOptions: UI_INTERACT_OVERLAY_OPTIONS,
|
|
645
667
|
});
|
|
646
668
|
return selected ?? null;
|
|
647
669
|
}
|
|
@@ -780,7 +802,11 @@ function renderDocPreview(theme, doc, opts) {
|
|
|
780
802
|
if (components.length > 0) {
|
|
781
803
|
lines.push(theme.fg("dim", "Components:"));
|
|
782
804
|
for (const component of components) {
|
|
783
|
-
|
|
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
|
+
}
|
|
784
810
|
}
|
|
785
811
|
}
|
|
786
812
|
const interactiveActions = runnableActions(doc);
|
|
@@ -804,19 +830,38 @@ function renderDocPreview(theme, doc, opts) {
|
|
|
804
830
|
}
|
|
805
831
|
return lines;
|
|
806
832
|
}
|
|
807
|
-
function
|
|
833
|
+
function componentPreviewLines(component) {
|
|
808
834
|
const { kind } = component;
|
|
809
835
|
switch (kind) {
|
|
810
836
|
case "text":
|
|
811
|
-
return `text · ${short(component.text, 80)}
|
|
812
|
-
case "list":
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
+
}
|
|
816
861
|
case "divider":
|
|
817
|
-
return "divider";
|
|
862
|
+
return ["divider"];
|
|
818
863
|
}
|
|
819
|
-
return kind;
|
|
864
|
+
return [kind];
|
|
820
865
|
}
|
|
821
866
|
function refreshUi(ctx) {
|
|
822
867
|
const key = sessionKey(ctx);
|
|
@@ -833,17 +878,24 @@ function refreshUi(ctx) {
|
|
|
833
878
|
}
|
|
834
879
|
const awaiting = awaitingDocs(state, docs);
|
|
835
880
|
const labels = docs.map((doc) => doc.ui_id).join(", ");
|
|
881
|
+
const readiness = state.interactionDepth > 0
|
|
882
|
+
? ctx.ui.theme.fg("accent", "prompting")
|
|
883
|
+
: awaiting.length > 0
|
|
884
|
+
? ctx.ui.theme.fg("accent", `awaiting ${awaiting.length}`)
|
|
885
|
+
: ctx.ui.theme.fg("dim", "ready");
|
|
836
886
|
ctx.ui.setStatus("mu-ui", [
|
|
837
887
|
ctx.ui.theme.fg("dim", "ui"),
|
|
838
888
|
ctx.ui.theme.fg("muted", "·"),
|
|
839
889
|
ctx.ui.theme.fg("accent", `${docs.length}`),
|
|
840
890
|
ctx.ui.theme.fg("muted", "·"),
|
|
841
|
-
|
|
842
|
-
? ctx.ui.theme.fg("accent", `awaiting ${awaiting.length}`)
|
|
843
|
-
: ctx.ui.theme.fg("dim", "ready"),
|
|
891
|
+
readiness,
|
|
844
892
|
ctx.ui.theme.fg("muted", "·"),
|
|
845
893
|
ctx.ui.theme.fg("text", labels),
|
|
846
894
|
].join(" "));
|
|
895
|
+
if (state.interactionDepth > 0) {
|
|
896
|
+
ctx.ui.setWidget("mu-ui", undefined);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
847
899
|
const primaryDoc = awaiting[0] ?? docs[0];
|
|
848
900
|
ctx.ui.setWidget("mu-ui", renderDocPreview(ctx.ui.theme, primaryDoc, {
|
|
849
901
|
awaitingResponse: state.awaitingUiIds.has(primaryDoc.ui_id),
|
|
@@ -853,7 +905,7 @@ function refreshUi(ctx) {
|
|
|
853
905
|
export function uiExtension(pi) {
|
|
854
906
|
const commandUsage = "/mu ui status|snapshot [compact|multiline]|interact [ui_id [action_id]]";
|
|
855
907
|
const usage = `Usage: ${commandUsage}`;
|
|
856
|
-
const runUiActionFromDoc = async (ctx, state, uiId, actionId) => {
|
|
908
|
+
const runUiActionFromDoc = async (ctx, state, uiId, actionId) => withUiInteraction(ctx, state, async () => {
|
|
857
909
|
const docs = activeDocs(state, UI_DISPLAY_DOCS_MAX);
|
|
858
910
|
if (docs.length === 0) {
|
|
859
911
|
ctx.ui.notify("No UI docs are currently available.", "info");
|
|
@@ -943,7 +995,7 @@ export function uiExtension(pi) {
|
|
|
943
995
|
}
|
|
944
996
|
retainAwaitingUiIdsForActiveDocs(state);
|
|
945
997
|
ctx.ui.notify(`Submitted prompt from ${selectedDoc.ui_id}/${selectedAction.id}.`, "info");
|
|
946
|
-
};
|
|
998
|
+
});
|
|
947
999
|
registerMuSubcommand(pi, {
|
|
948
1000
|
subcommand: "ui",
|
|
949
1001
|
summary: "Inspect and manage interactive UI docs",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-agent",
|
|
3
|
-
"version": "26.2.
|
|
3
|
+
"version": "26.2.109",
|
|
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.109",
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: core
|
|
3
|
-
description: "Meta-skill for core mu operating primitives. Routes to mu, memory, tmux, and code-mode based on task shape."
|
|
3
|
+
description: "Meta-skill for core mu operating primitives. Routes to mu, programmable-ui, memory, tmux, and code-mode based on task shape."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# core
|
|
@@ -10,6 +10,7 @@ Use this meta-skill when the user asks for general `mu` operation guidance and y
|
|
|
10
10
|
## Subskills
|
|
11
11
|
|
|
12
12
|
- `mu` — default CLI-first operating workflow (inspect, mutate, verify, handoff).
|
|
13
|
+
- `programmable-ui` — canonical `mu_ui`/`UiDoc` workflow for publishing, inspecting, and debugging interactive UI docs.
|
|
13
14
|
- `memory` — prior-context retrieval, timeline reconstruction, and memory-index maintenance.
|
|
14
15
|
- `tmux` — persistent terminal/session substrate for bounded command execution and fan-out.
|
|
15
16
|
- `code-mode` — tmux-backed REPL loops for iterative execution and context compression.
|
|
@@ -17,12 +18,14 @@ Use this meta-skill when the user asks for general `mu` operation guidance and y
|
|
|
17
18
|
## Selection guide
|
|
18
19
|
|
|
19
20
|
1. Start with `mu` for most day-to-day operator work.
|
|
20
|
-
2.
|
|
21
|
-
3. Add `
|
|
22
|
-
4. Add `
|
|
21
|
+
2. Route to `programmable-ui` when the user asks about `mu_ui`, `/mu ui ...`, `UiDoc` payloads, action wiring, or interactive prompt behavior.
|
|
22
|
+
3. Add `memory` when prior context or timeline anchors are required.
|
|
23
|
+
4. Add `tmux` when durable shell state or parallel worker shells are needed.
|
|
24
|
+
5. Add `code-mode` when solving by live execution is cheaper than chat-only reasoning.
|
|
23
25
|
|
|
24
26
|
## Common patterns
|
|
25
27
|
|
|
26
28
|
- **Bounded investigation**: Use `mu` commands (`get`, `read`, `health`) to inspect current state, then use `memory` to find "when did this last work?" before attempting a fix.
|
|
29
|
+
- **Programmable UI scaffolding**: Route to `programmable-ui` to emit schema-valid `UiDoc` templates quickly, verify state with `/mu ui status|snapshot`, and close docs with `mu_ui remove|clear`.
|
|
27
30
|
- **Context compression**: If a user asks for complex debugging that involves running code and printing huge errors, route to `code-mode`. The agent can spin up a REPL, iterate on a fix offline, and return only the root cause to the chat.
|
|
28
31
|
- **Parallel fan-out**: If a command takes a long time, or needs to run across multiple directories, route to `tmux` to spawn parallel worker shells, keep them running in the background, and periodically read their output.
|
|
@@ -224,6 +224,7 @@ Keep operator↔human communication mu_ui-first across these skills:
|
|
|
224
224
|
- one non-interactive status doc per active profile (`metadata.profile.variant: "status"`)
|
|
225
225
|
- separate interactive prompt docs for decisions (`metadata.command_text` actions)
|
|
226
226
|
- explicit `mu_ui remove` teardown for resolved prompts and completed passes
|
|
227
|
+
For focused `UiDoc` schema templates/action wiring/status diagnostics, use `programmable-ui`.
|
|
227
228
|
For REPL-driven exploration and context compression, use `code-mode`.
|
|
228
229
|
For persistent terminal sessions and worker fan-out mechanics, use `tmux`.
|
|
229
230
|
For recurring bounded automation loops, use `heartbeats`.
|
|
@@ -247,6 +248,7 @@ For wall-clock schedules (one-shot, interval, cron-expression), use `crons`.
|
|
|
247
248
|
|
|
248
249
|
- Prior-context retrieval and index maintenance: **`memory`**
|
|
249
250
|
- Planning/decomposition and DAG review: **`planning`**
|
|
251
|
+
- Programmable UI schema/templates/action-routing diagnostics: **`programmable-ui`**
|
|
250
252
|
- mu_ui-first status/prompt communication patterns for DAG work: **`planning`**, **`execution`**, **`control-flow`**, **`model-routing`**
|
|
251
253
|
- Shared DAG semantics for planning + execution: **`protocol`**
|
|
252
254
|
- Loop/termination policy overlays (review gates, retries, escalation): **`control-flow`**
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: programmable-ui
|
|
3
|
+
description: "Builds and debugs mu_ui UiDocs with schema-valid payloads, interaction wiring, and status/snapshot verification."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# programmable-ui
|
|
7
|
+
|
|
8
|
+
Use this skill when the task involves `mu_ui`, `UiDoc` payloads, interactive actions, or `/mu ui ...` inspection commands.
|
|
9
|
+
|
|
10
|
+
## Contents
|
|
11
|
+
|
|
12
|
+
- [Core contract](#core-contract)
|
|
13
|
+
- [60-second quickstart](#60-second-quickstart)
|
|
14
|
+
- [UiDoc schema cheat sheet](#uidoc-schema-cheat-sheet)
|
|
15
|
+
- [mu_ui action semantics](#mu_ui-action-semantics)
|
|
16
|
+
- [Canonical templates](#canonical-templates)
|
|
17
|
+
- [Status-profile rules](#status-profile-rules)
|
|
18
|
+
- [Debugging playbook](#debugging-playbook)
|
|
19
|
+
- [Verify and teardown checklist](#verify-and-teardown-checklist)
|
|
20
|
+
- [Evaluation scenarios](#evaluation-scenarios)
|
|
21
|
+
|
|
22
|
+
## Core contract
|
|
23
|
+
|
|
24
|
+
1. **Publish schema-valid docs only**
|
|
25
|
+
- `mu_ui` accepts `doc: object`, but runtime validation is strict (`UiDoc` schema).
|
|
26
|
+
- Invalid payloads fail with `Invalid UiDoc.`.
|
|
27
|
+
|
|
28
|
+
2. **Keep interaction command-driven**
|
|
29
|
+
- Interactive actions must set `action.metadata.command_text` (for example `/answer yes`).
|
|
30
|
+
- User clicks/taps are translated back into normal command turns.
|
|
31
|
+
|
|
32
|
+
3. **Separate status and decisions**
|
|
33
|
+
- Keep one non-interactive status doc per active profile (`metadata.profile.variant: "status"`).
|
|
34
|
+
- Use separate interactive docs for user decisions.
|
|
35
|
+
|
|
36
|
+
4. **Use monotonic revisions**
|
|
37
|
+
- Increment `revision.version` on each update for the same `ui_id`.
|
|
38
|
+
- Replays/reconnects keep the highest revision deterministically.
|
|
39
|
+
|
|
40
|
+
5. **Read -> act -> verify**
|
|
41
|
+
- After each `set|update|replace|remove|clear`, check `/mu ui status` and `/mu ui snapshot`.
|
|
42
|
+
|
|
43
|
+
6. **Close docs explicitly**
|
|
44
|
+
- Resolve prompts with `mu_ui remove` (preferred) or `mu_ui clear`.
|
|
45
|
+
|
|
46
|
+
## 60-second quickstart
|
|
47
|
+
|
|
48
|
+
1. Publish one interactive doc:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"action": "set",
|
|
53
|
+
"doc": {
|
|
54
|
+
"v": 1,
|
|
55
|
+
"ui_id": "ui:demo",
|
|
56
|
+
"title": "Demo",
|
|
57
|
+
"summary": "Minimal interactive panel",
|
|
58
|
+
"components": [
|
|
59
|
+
{ "kind": "text", "id": "intro", "text": "Choose an option", "metadata": {} }
|
|
60
|
+
],
|
|
61
|
+
"actions": [
|
|
62
|
+
{
|
|
63
|
+
"id": "ack",
|
|
64
|
+
"label": "Acknowledge",
|
|
65
|
+
"kind": "primary",
|
|
66
|
+
"payload": { "choice": "ack" },
|
|
67
|
+
"metadata": { "command_text": "/answer ack" }
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"revision": { "id": "rev:demo:1", "version": 1 },
|
|
71
|
+
"updated_at_ms": 1,
|
|
72
|
+
"metadata": {}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
2. Verify live state:
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
/mu ui status
|
|
81
|
+
/mu ui snapshot compact
|
|
82
|
+
/mu ui snapshot multiline
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
3. Handle command (`/answer ack`) in normal skill logic.
|
|
86
|
+
|
|
87
|
+
4. Remove prompt doc:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{ "action": "remove", "ui_id": "ui:demo" }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## UiDoc schema cheat sheet
|
|
94
|
+
|
|
95
|
+
Required top-level fields for each doc:
|
|
96
|
+
|
|
97
|
+
- `v`: `1`
|
|
98
|
+
- `ui_id`: non-empty string (max 64)
|
|
99
|
+
- `title`: non-empty string
|
|
100
|
+
- `components`: non-empty array (`text|list|key_value|divider`)
|
|
101
|
+
- `revision`: `{ id: string, version: nonnegative int }`
|
|
102
|
+
- `updated_at_ms`: nonnegative integer
|
|
103
|
+
|
|
104
|
+
Common optional fields (recommended):
|
|
105
|
+
|
|
106
|
+
- `summary`: deterministic fallback summary
|
|
107
|
+
- `actions`: interactive options (empty for pure status)
|
|
108
|
+
- `metadata`: profile/snapshot metadata and custom annotations
|
|
109
|
+
|
|
110
|
+
Component minimums:
|
|
111
|
+
|
|
112
|
+
- `text`: `id`, `text`
|
|
113
|
+
- `list`: `id`, `items[]` (`id`, `label`, optional `detail`, optional `tone`)
|
|
114
|
+
- `key_value`: `id`, `rows[]` (`key`, `value`, optional `tone`)
|
|
115
|
+
- `divider`: `id`
|
|
116
|
+
|
|
117
|
+
Action minimums:
|
|
118
|
+
|
|
119
|
+
- `id`, `label`
|
|
120
|
+
- `metadata.command_text` required for interactive routing
|
|
121
|
+
- optional: `kind`, `description`, `payload`, `component_id`, `callback_token`
|
|
122
|
+
|
|
123
|
+
## mu_ui action semantics
|
|
124
|
+
|
|
125
|
+
- `status`
|
|
126
|
+
- Returns count, `ui_id` list, status-profile counts/warnings, and awaiting counts.
|
|
127
|
+
- `snapshot`
|
|
128
|
+
- `snapshot_format`: `compact|multiline` (defaults to `compact`).
|
|
129
|
+
- `set`
|
|
130
|
+
- Upsert one doc by `ui_id`.
|
|
131
|
+
- `update`
|
|
132
|
+
- Same behavior as `set` (single-doc upsert).
|
|
133
|
+
- `replace`
|
|
134
|
+
- Replace entire active doc set with `docs[]`.
|
|
135
|
+
- `remove`
|
|
136
|
+
- Remove one doc by `ui_id`.
|
|
137
|
+
- `clear`
|
|
138
|
+
- Remove all docs for the session.
|
|
139
|
+
|
|
140
|
+
## Canonical templates
|
|
141
|
+
|
|
142
|
+
### 1) Interactive `/answer` prompt
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"action": "set",
|
|
147
|
+
"doc": {
|
|
148
|
+
"v": 1,
|
|
149
|
+
"ui_id": "ui:answer",
|
|
150
|
+
"title": "Answer",
|
|
151
|
+
"summary": "Please choose yes or no",
|
|
152
|
+
"components": [
|
|
153
|
+
{ "kind": "text", "id": "prompt", "text": "Choose an answer", "metadata": {} }
|
|
154
|
+
],
|
|
155
|
+
"actions": [
|
|
156
|
+
{
|
|
157
|
+
"id": "answer_yes",
|
|
158
|
+
"label": "Yes",
|
|
159
|
+
"kind": "primary",
|
|
160
|
+
"payload": { "choice": "yes" },
|
|
161
|
+
"metadata": { "command_text": "/answer yes" }
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"id": "answer_no",
|
|
165
|
+
"label": "No",
|
|
166
|
+
"kind": "secondary",
|
|
167
|
+
"payload": { "choice": "no" },
|
|
168
|
+
"metadata": { "command_text": "/answer no" }
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
"revision": { "id": "rev:answer:1", "version": 1 },
|
|
172
|
+
"updated_at_ms": 1,
|
|
173
|
+
"metadata": {}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Handler contract:
|
|
179
|
+
|
|
180
|
+
1. Parse `/answer <choice>`.
|
|
181
|
+
2. Validate choice.
|
|
182
|
+
3. `mu_ui remove` (or `clear`) for `ui:answer`.
|
|
183
|
+
4. Emit normal response.
|
|
184
|
+
|
|
185
|
+
### 2) Status-profile doc (non-interactive)
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"action": "set",
|
|
190
|
+
"doc": {
|
|
191
|
+
"v": 1,
|
|
192
|
+
"ui_id": "ui:planning",
|
|
193
|
+
"title": "Planning status",
|
|
194
|
+
"summary": "Drafting issue DAG",
|
|
195
|
+
"components": [
|
|
196
|
+
{
|
|
197
|
+
"kind": "key_value",
|
|
198
|
+
"id": "kv",
|
|
199
|
+
"rows": [
|
|
200
|
+
{ "key": "phase", "value": "decomposition" },
|
|
201
|
+
{ "key": "next", "value": "approval prompt" }
|
|
202
|
+
],
|
|
203
|
+
"metadata": {}
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"kind": "list",
|
|
207
|
+
"id": "milestones",
|
|
208
|
+
"items": [
|
|
209
|
+
{ "id": "m1", "label": "Root issue captured" },
|
|
210
|
+
{ "id": "m2", "label": "Leaf tasks drafted" }
|
|
211
|
+
],
|
|
212
|
+
"metadata": {}
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
"actions": [],
|
|
216
|
+
"revision": { "id": "rev:planning:12", "version": 12 },
|
|
217
|
+
"updated_at_ms": 1730000000000,
|
|
218
|
+
"metadata": {
|
|
219
|
+
"profile": {
|
|
220
|
+
"id": "planning",
|
|
221
|
+
"variant": "status",
|
|
222
|
+
"snapshot": {
|
|
223
|
+
"compact": "planning: DAG draft ready",
|
|
224
|
+
"multiline": "phase: decomposition\nnext: approval prompt"
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### 3) Parameterized command text with payload defaults
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"id": "approve",
|
|
237
|
+
"label": "Approve",
|
|
238
|
+
"payload": { "choice": "approve", "note": "looks good" },
|
|
239
|
+
"metadata": { "command_text": "/answer choice={{choice}} note={{note}}" }
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
In terminal UI interaction flow, placeholders are auto-filled from `payload` when possible, and unresolved fields are prompted.
|
|
244
|
+
|
|
245
|
+
## Status-profile rules
|
|
246
|
+
|
|
247
|
+
When `metadata.profile.id` is one of `planning|subagents|control-flow|model-routing` and variant is `status`:
|
|
248
|
+
|
|
249
|
+
- expected `ui_id` values:
|
|
250
|
+
- `planning` -> `ui:planning`
|
|
251
|
+
- `subagents` -> `ui:subagents`
|
|
252
|
+
- `control-flow` -> `ui:control-flow`
|
|
253
|
+
- `model-routing` -> `ui:model-routing`
|
|
254
|
+
- keep `actions: []` (status docs are non-interactive)
|
|
255
|
+
- include `summary` plus `metadata.profile.snapshot.compact`
|
|
256
|
+
- preferred components by profile:
|
|
257
|
+
- `planning`: `key_value` + `list`
|
|
258
|
+
- `subagents`: `key_value` + `list`
|
|
259
|
+
- `control-flow`: `key_value`
|
|
260
|
+
- `model-routing`: `key_value` + `list`
|
|
261
|
+
|
|
262
|
+
Use `/mu ui status` to catch profile warnings early.
|
|
263
|
+
|
|
264
|
+
## Debugging playbook
|
|
265
|
+
|
|
266
|
+
- **`doc is required`**
|
|
267
|
+
- Missing `doc` parameter for `set|update`.
|
|
268
|
+
- **`Invalid UiDoc.`**
|
|
269
|
+
- Schema mismatch. Re-check required fields and component/action shapes.
|
|
270
|
+
- **`docs must be an array` / `docs[i]: invalid UiDoc`**
|
|
271
|
+
- `replace` payload malformed.
|
|
272
|
+
- **Action appears but cannot run**
|
|
273
|
+
- Missing/empty `metadata.command_text`, or status-profile doc (actions intentionally non-runnable).
|
|
274
|
+
- **`awaiting` stays non-zero**
|
|
275
|
+
- Prompt doc still active. Remove with `mu_ui remove` once handled.
|
|
276
|
+
|
|
277
|
+
## Verify and teardown checklist
|
|
278
|
+
|
|
279
|
+
After each change:
|
|
280
|
+
|
|
281
|
+
1. `/mu ui status` shows expected doc count and ids.
|
|
282
|
+
2. `/mu ui snapshot compact` shows deterministic summary.
|
|
283
|
+
3. `/mu ui snapshot multiline` shows readable panel/action projection.
|
|
284
|
+
4. Prompt resolved? Remove/clear doc explicitly.
|
|
285
|
+
5. Keep issue/forum truth in sync; `mu_ui` is communication state, not source-of-truth task state.
|
|
286
|
+
|
|
287
|
+
## Evaluation scenarios
|
|
288
|
+
|
|
289
|
+
1. **First-time interactive prompt**
|
|
290
|
+
- Publish `ui:answer`, click action, confirm `/answer ...` reaches normal handler, remove prompt doc.
|
|
291
|
+
|
|
292
|
+
2. **Status + decision split**
|
|
293
|
+
- Keep `ui:planning` status-profile doc active; open separate interactive approval doc; verify status remains non-interactive.
|
|
294
|
+
|
|
295
|
+
3. **Replay/reconnect safety**
|
|
296
|
+
- Publish revisions 1 then 2; replay stale rev 1; verify highest revision remains active.
|
|
297
|
+
|
|
298
|
+
4. **Channel degrade resilience**
|
|
299
|
+
- Ensure every actionable row has deterministic `command_text` fallback so manual command entry always works.
|