@femtomc/mu-agent 26.2.94 → 26.2.96
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
|
@@ -46,7 +46,7 @@ Current stack:
|
|
|
46
46
|
- `brandingExtension` — mu compact header/footer branding + default theme
|
|
47
47
|
- `eventLogExtension` — event tail + watch widget
|
|
48
48
|
- `planningUiExtension` — planning mode: compact HUD for next-step/approval flow in widget/status surfaces while keeping the branding footer unchanged (`/mu plan ...`)
|
|
49
|
-
- `subagentsUiExtension` — subagents mode: compact HUD with activity sentences from issue/forum events
|
|
49
|
+
- `subagentsUiExtension` — subagents mode: compact HUD with activity sentences from issue/forum events, with state kept in widget/status surfaces while branding footer stays unchanged (`/mu subagents ...`)
|
|
50
50
|
|
|
51
51
|
Default operator UI theme is `mu-gruvbox-dark`.
|
|
52
52
|
|
|
@@ -56,7 +56,7 @@ Default operator UI theme is `mu-gruvbox-dark`.
|
|
|
56
56
|
- `/mu events watch on|off` — toggle event watch widget
|
|
57
57
|
- `/mu brand on|off|toggle` — enable/disable UI branding
|
|
58
58
|
- `/mu plan ...` — planning HUD (phases, checklist editing, communication state, snapshots); does not inject planning metadata into branding footer
|
|
59
|
-
- `/mu subagents ...` — tmux + issue queue monitor/spawner (profiles, spawn pause, stale/refresh controls, snapshots)
|
|
59
|
+
- `/mu subagents ...` — tmux + issue queue monitor/spawner (profiles, spawn pause, stale/refresh controls, snapshots); does not inject subagents metadata into branding footer
|
|
60
60
|
- `/mu help` — dispatcher catalog of registered `/mu` subcommands
|
|
61
61
|
|
|
62
62
|
## Tooling model (CLI-first)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branding.d.ts","sourceRoot":"","sources":["../../src/extensions/branding.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"branding.d.ts","sourceRoot":"","sources":["../../src/extensions/branding.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AAwEpF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,YAAY,QAoOjD;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { basename } from "node:path";
|
|
10
10
|
import { MU_DEFAULT_THEME_NAME, MU_VERSION } from "../ui_defaults.js";
|
|
11
|
-
import { getActiveHudMode } from "./hud-mode.js";
|
|
12
11
|
import { registerMuSubcommand } from "./mu-command-dispatcher.js";
|
|
13
12
|
import { fetchMuStatus, muServerUrl } from "./shared.js";
|
|
14
13
|
const EMPTY_SNAPSHOT = {
|
|
@@ -128,15 +127,6 @@ export function brandingExtension(pi) {
|
|
|
128
127
|
const barColor = pct >= 80 ? "warning" : pct >= 60 ? "muted" : "dim";
|
|
129
128
|
parts.push(theme.fg("muted", "·"), theme.fg(barColor, `ctx ${pct}%`), theme.fg(barColor, contextBar(pct, 10)));
|
|
130
129
|
}
|
|
131
|
-
const activeHudMode = getActiveHudMode();
|
|
132
|
-
if (activeHudMode === "subagents") {
|
|
133
|
-
const extensionStatuses = footerData.getExtensionStatuses();
|
|
134
|
-
const modeMeta = extensionStatuses.get("mu-subagents-meta") ?? "";
|
|
135
|
-
parts.push(theme.fg("muted", "·"), theme.fg("accent", "hud:subagents"));
|
|
136
|
-
if (modeMeta.length > 0) {
|
|
137
|
-
parts.push(theme.fg("muted", "·"), theme.fg("dim", truncateToWidth(modeMeta, 42)));
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
130
|
if (snapshot.openCount > 0 || snapshot.readyCount > 0) {
|
|
141
131
|
parts.push(theme.fg("muted", "·"), theme.fg("dim", `open ${snapshot.openCount} ready ${snapshot.readyCount}`));
|
|
142
132
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagents-ui.d.ts","sourceRoot":"","sources":["../../src/extensions/subagents-ui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"subagents-ui.d.ts","sourceRoot":"","sources":["../../src/extensions/subagents-ui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AA2gCpF,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,YAAY,QAkoBpD;AAED,eAAe,oBAAoB,CAAC"}
|
|
@@ -18,6 +18,7 @@ const WIDGET_SUMMARY_MAX = 76;
|
|
|
18
18
|
const WIDGET_ERROR_MAX = 72;
|
|
19
19
|
const ACTIVITY_EVENT_LIMIT = 180;
|
|
20
20
|
const ACTIVITY_LINE_LIMIT = 4;
|
|
21
|
+
const FORUM_ACTIVITY_ISSUE_LIMIT = 8;
|
|
21
22
|
function shellQuote(value) {
|
|
22
23
|
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
23
24
|
}
|
|
@@ -177,6 +178,46 @@ function parseIssueArray(label, jsonText) {
|
|
|
177
178
|
});
|
|
178
179
|
return { issues, error: null };
|
|
179
180
|
}
|
|
181
|
+
function parseForumReadLatest(label, jsonText) {
|
|
182
|
+
const trimmed = jsonText.trim();
|
|
183
|
+
if (trimmed.length === 0) {
|
|
184
|
+
return { message: null, error: null };
|
|
185
|
+
}
|
|
186
|
+
let parsed = null;
|
|
187
|
+
try {
|
|
188
|
+
parsed = JSON.parse(trimmed);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return { message: null, error: `${label}: invalid JSON output from mu forum read` };
|
|
192
|
+
}
|
|
193
|
+
if (!Array.isArray(parsed)) {
|
|
194
|
+
return { message: null, error: `${label}: expected JSON array output from mu forum read` };
|
|
195
|
+
}
|
|
196
|
+
const first = parsed[0];
|
|
197
|
+
if (!first || typeof first !== "object" || Array.isArray(first)) {
|
|
198
|
+
return { message: null, error: null };
|
|
199
|
+
}
|
|
200
|
+
const row = first;
|
|
201
|
+
const body = typeof row.body === "string" ? row.body.trim() : "";
|
|
202
|
+
if (body.length === 0) {
|
|
203
|
+
return { message: null, error: null };
|
|
204
|
+
}
|
|
205
|
+
const authorRaw = typeof row.author === "string" ? row.author.trim() : "";
|
|
206
|
+
const createdAtRaw = row.created_at;
|
|
207
|
+
const createdAtSec = typeof createdAtRaw === "number" && Number.isFinite(createdAtRaw)
|
|
208
|
+
? Math.trunc(createdAtRaw)
|
|
209
|
+
: typeof createdAtRaw === "string" && /^\d+$/.test(createdAtRaw.trim())
|
|
210
|
+
? Number.parseInt(createdAtRaw.trim(), 10)
|
|
211
|
+
: null;
|
|
212
|
+
return {
|
|
213
|
+
message: {
|
|
214
|
+
author: authorRaw.length > 0 ? authorRaw : "operator",
|
|
215
|
+
body,
|
|
216
|
+
createdAtMs: createdAtSec != null && Number.isFinite(createdAtSec) ? createdAtSec * 1_000 : null,
|
|
217
|
+
},
|
|
218
|
+
error: null,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
180
221
|
async function runMuCli(args) {
|
|
181
222
|
let proc = null;
|
|
182
223
|
try {
|
|
@@ -408,52 +449,85 @@ function isActivityEndpointUnavailable(errorMessage) {
|
|
|
408
449
|
const normalized = errorMessage.toLowerCase();
|
|
409
450
|
return normalized.includes("mu server 404") && normalized.includes("not found");
|
|
410
451
|
}
|
|
411
|
-
async function
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
catch (err) {
|
|
422
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
423
|
-
if (isActivityEndpointUnavailable(message)) {
|
|
424
|
-
return { lines: [], error: null };
|
|
452
|
+
async function fetchRecentForumActivity(issueIds) {
|
|
453
|
+
const uniqueIssueIds = Array.from(new Set(issueIds.map((issueId) => issueId.trim()).filter((issueId) => issueId.length > 0))).slice(0, FORUM_ACTIVITY_ISSUE_LIMIT);
|
|
454
|
+
if (uniqueIssueIds.length === 0) {
|
|
455
|
+
return [];
|
|
456
|
+
}
|
|
457
|
+
const reads = await Promise.all(uniqueIssueIds.map(async (issueId) => {
|
|
458
|
+
const outcome = await runMuCli(["forum", "read", `issue:${issueId}`, "--limit", "1", "--json"]);
|
|
459
|
+
if (outcome.exitCode !== 0 || outcome.error || outcome.timedOut) {
|
|
460
|
+
return null;
|
|
425
461
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
return { lines: [], error: null };
|
|
430
|
-
}
|
|
431
|
-
const tracked = new Set(opts.issueIds.map((issueId) => issueId.trim()).filter((issueId) => issueId.length > 0));
|
|
432
|
-
const seenIssueIds = new Set();
|
|
433
|
-
const lines = [];
|
|
434
|
-
const sorted = [...events].sort((left, right) => {
|
|
435
|
-
const leftTs = typeof left.ts_ms === "number" ? left.ts_ms : 0;
|
|
436
|
-
const rightTs = typeof right.ts_ms === "number" ? right.ts_ms : 0;
|
|
437
|
-
return rightTs - leftTs;
|
|
438
|
-
});
|
|
439
|
-
for (const event of sorted) {
|
|
440
|
-
const rendered = renderActivitySentence(event);
|
|
441
|
-
if (!rendered) {
|
|
442
|
-
continue;
|
|
462
|
+
const parsed = parseForumReadLatest(`forum:${issueId}`, outcome.stdout);
|
|
463
|
+
if (parsed.error || !parsed.message) {
|
|
464
|
+
return null;
|
|
443
465
|
}
|
|
444
|
-
|
|
445
|
-
|
|
466
|
+
return {
|
|
467
|
+
issueId,
|
|
468
|
+
tsMs: parsed.message.createdAtMs ?? 0,
|
|
469
|
+
line: `${eventAgeLabel(parsed.message.createdAtMs ?? undefined)} ${issueId} ${parsed.message.author}: ${truncateOneLine(parsed.message.body, 54)}`,
|
|
470
|
+
};
|
|
471
|
+
}));
|
|
472
|
+
return reads
|
|
473
|
+
.filter((row) => row != null)
|
|
474
|
+
.sort((left, right) => right.tsMs - left.tsMs)
|
|
475
|
+
.slice(0, ACTIVITY_LINE_LIMIT)
|
|
476
|
+
.map((row) => row.line);
|
|
477
|
+
}
|
|
478
|
+
async function fetchRecentActivity(opts) {
|
|
479
|
+
let endpointError = null;
|
|
480
|
+
const hasServerUrl = Boolean(muServerUrl());
|
|
481
|
+
if (hasServerUrl) {
|
|
482
|
+
let events;
|
|
483
|
+
try {
|
|
484
|
+
events = await fetchMuJson(`/api/control-plane/events?limit=${ACTIVITY_EVENT_LIMIT}`, {
|
|
485
|
+
timeoutMs: 4_000,
|
|
486
|
+
});
|
|
446
487
|
}
|
|
447
|
-
|
|
448
|
-
|
|
488
|
+
catch (err) {
|
|
489
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
490
|
+
if (!isActivityEndpointUnavailable(message)) {
|
|
491
|
+
endpointError = `activity refresh failed: ${truncateOneLine(message, 60)}`;
|
|
492
|
+
}
|
|
493
|
+
events = [];
|
|
449
494
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
495
|
+
if (Array.isArray(events) && events.length > 0) {
|
|
496
|
+
const tracked = new Set(opts.issueIds.map((issueId) => issueId.trim()).filter((issueId) => issueId.length > 0));
|
|
497
|
+
const seenIssueIds = new Set();
|
|
498
|
+
const lines = [];
|
|
499
|
+
const sorted = [...events].sort((left, right) => {
|
|
500
|
+
const leftTs = typeof left.ts_ms === "number" ? left.ts_ms : 0;
|
|
501
|
+
const rightTs = typeof right.ts_ms === "number" ? right.ts_ms : 0;
|
|
502
|
+
return rightTs - leftTs;
|
|
503
|
+
});
|
|
504
|
+
for (const event of sorted) {
|
|
505
|
+
const rendered = renderActivitySentence(event);
|
|
506
|
+
if (!rendered) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (tracked.size > 0 && !tracked.has(rendered.issueId)) {
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (seenIssueIds.has(rendered.issueId)) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
seenIssueIds.add(rendered.issueId);
|
|
516
|
+
lines.push(`${eventAgeLabel(event.ts_ms)} ${rendered.sentence}`);
|
|
517
|
+
if (lines.length >= ACTIVITY_LINE_LIMIT) {
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (lines.length > 0) {
|
|
522
|
+
return { lines, error: null };
|
|
523
|
+
}
|
|
454
524
|
}
|
|
455
525
|
}
|
|
456
|
-
|
|
526
|
+
const forumLines = await fetchRecentForumActivity(opts.issueIds);
|
|
527
|
+
if (forumLines.length > 0) {
|
|
528
|
+
return { lines: forumLines, error: null };
|
|
529
|
+
}
|
|
530
|
+
return { lines: [], error: endpointError };
|
|
457
531
|
}
|
|
458
532
|
function computeQueueDrift(sessions, activeIssues) {
|
|
459
533
|
const activeWithoutSessionIds = activeIssues
|
|
@@ -465,6 +539,30 @@ function computeQueueDrift(sessions, activeIssues) {
|
|
|
465
539
|
orphanSessions,
|
|
466
540
|
};
|
|
467
541
|
}
|
|
542
|
+
function queueFallbackActivityLines(state) {
|
|
543
|
+
const lines = [];
|
|
544
|
+
for (const issue of state.activeIssues) {
|
|
545
|
+
if (lines.length >= ACTIVITY_LINE_LIMIT) {
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
lines.push(`active ${issue.id}: ${truncateOneLine(issue.title, 52)}`);
|
|
549
|
+
}
|
|
550
|
+
for (const issue of state.readyIssues) {
|
|
551
|
+
if (lines.length >= ACTIVITY_LINE_LIMIT) {
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
lines.push(`ready ${issue.id}: ${truncateOneLine(issue.title, 52)}`);
|
|
555
|
+
}
|
|
556
|
+
if (lines.length === 0 && state.sessions.length > 0) {
|
|
557
|
+
const sessionPreview = state.sessions
|
|
558
|
+
.slice(0, 2)
|
|
559
|
+
.map((sessionName) => truncateOneLine(sessionName, 28))
|
|
560
|
+
.join(", ");
|
|
561
|
+
const suffix = state.sessions.length > 2 ? ` +${state.sessions.length - 2} more` : "";
|
|
562
|
+
lines.push(`tmux active: ${sessionPreview}${suffix}`);
|
|
563
|
+
}
|
|
564
|
+
return lines;
|
|
565
|
+
}
|
|
468
566
|
function normalizeIssueTag(raw) {
|
|
469
567
|
const trimmed = raw.trim();
|
|
470
568
|
if (!trimmed || trimmed.toLowerCase() === "clear") {
|
|
@@ -597,17 +695,7 @@ function renderSubagentsUi(ctx, state) {
|
|
|
597
695
|
statusParts.push(ctx.ui.theme.fg("muted", truncateOneLine(issueScope, 18)));
|
|
598
696
|
}
|
|
599
697
|
ctx.ui.setStatus("mu-subagents", statusParts.join(` ${ctx.ui.theme.fg("muted", "·")} `));
|
|
600
|
-
|
|
601
|
-
if (staleCount > 0) {
|
|
602
|
-
footerMetaParts.push(`drift:${staleCount}`);
|
|
603
|
-
}
|
|
604
|
-
if (refreshStale) {
|
|
605
|
-
footerMetaParts.push("refresh:stale");
|
|
606
|
-
}
|
|
607
|
-
if (state.issueError || state.sessionError || state.activityError) {
|
|
608
|
-
footerMetaParts.push("err");
|
|
609
|
-
}
|
|
610
|
-
ctx.ui.setStatus("mu-subagents-meta", footerMetaParts.join(" "));
|
|
698
|
+
ctx.ui.setStatus("mu-subagents-meta", undefined);
|
|
611
699
|
const titleParts = [
|
|
612
700
|
ctx.ui.theme.fg("accent", ctx.ui.theme.bold("Subagents")),
|
|
613
701
|
ctx.ui.theme.fg("muted", "·"),
|
|
@@ -668,8 +756,11 @@ function renderSubagentsUi(ctx, state) {
|
|
|
668
756
|
lines.push(ctx.ui.theme.fg("warning", truncateOneLine(state.activityError, WIDGET_ERROR_MAX)));
|
|
669
757
|
}
|
|
670
758
|
else if (activityLines.length === 0) {
|
|
671
|
-
|
|
672
|
-
|
|
759
|
+
const fallbackLines = queueFallbackActivityLines(state);
|
|
760
|
+
if (fallbackLines.length > 0) {
|
|
761
|
+
for (const line of fallbackLines) {
|
|
762
|
+
lines.push(`${ctx.ui.theme.fg("muted", "•")} ${ctx.ui.theme.fg("text", truncateOneLine(line, WIDGET_SUMMARY_MAX))}`);
|
|
763
|
+
}
|
|
673
764
|
}
|
|
674
765
|
else {
|
|
675
766
|
lines.push(ctx.ui.theme.fg("muted", "(no active operators)"));
|
|
@@ -750,7 +841,7 @@ export function subagentsUiExtension(pi) {
|
|
|
750
841
|
state.readyIssues = issues.ready;
|
|
751
842
|
state.activeIssues = issues.active;
|
|
752
843
|
state.issueError = issues.error;
|
|
753
|
-
const trackedIssueIds =
|
|
844
|
+
const trackedIssueIds = [...state.activeIssues, ...state.readyIssues]
|
|
754
845
|
.slice(0, 8)
|
|
755
846
|
.map((issue) => issue.id);
|
|
756
847
|
const activity = await fetchRecentActivity({ issueIds: trackedIssueIds });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-agent",
|
|
3
|
-
"version": "26.2.
|
|
3
|
+
"version": "26.2.96",
|
|
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.2.
|
|
27
|
+
"@femtomc/mu-core": "26.2.96",
|
|
28
28
|
"@mariozechner/pi-agent-core": "^0.53.0",
|
|
29
29
|
"@mariozechner/pi-ai": "^0.53.0",
|
|
30
30
|
"@mariozechner/pi-coding-agent": "^0.53.0",
|