@femtomc/mu-agent 26.2.95 → 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.
@@ -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;AAu5BpF,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,YAAY,QAkoBpD;AAED,eAAe,oBAAoB,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 fetchRecentActivity(opts) {
412
- if (!muServerUrl()) {
413
- return { lines: [], error: null };
414
- }
415
- let events;
416
- try {
417
- events = await fetchMuJson(`/api/control-plane/events?limit=${ACTIVITY_EVENT_LIMIT}`, {
418
- timeoutMs: 4_000,
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
- return { lines: [], error: `activity refresh failed: ${truncateOneLine(message, 60)}` };
427
- }
428
- if (!Array.isArray(events) || events.length === 0) {
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
- if (tracked.size > 0 && !tracked.has(rendered.issueId)) {
445
- continue;
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
- if (seenIssueIds.has(rendered.issueId)) {
448
- continue;
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
- seenIssueIds.add(rendered.issueId);
451
- lines.push(`${eventAgeLabel(event.ts_ms)} ${rendered.sentence}`);
452
- if (lines.length >= ACTIVITY_LINE_LIMIT) {
453
- break;
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
- return { lines, error: null };
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") {
@@ -658,8 +756,11 @@ function renderSubagentsUi(ctx, state) {
658
756
  lines.push(ctx.ui.theme.fg("warning", truncateOneLine(state.activityError, WIDGET_ERROR_MAX)));
659
757
  }
660
758
  else if (activityLines.length === 0) {
661
- if (state.activeIssues.length > 0) {
662
- lines.push(ctx.ui.theme.fg("muted", "(no recent subagent updates yet)"));
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
+ }
663
764
  }
664
765
  else {
665
766
  lines.push(ctx.ui.theme.fg("muted", "(no active operators)"));
@@ -740,7 +841,7 @@ export function subagentsUiExtension(pi) {
740
841
  state.readyIssues = issues.ready;
741
842
  state.activeIssues = issues.active;
742
843
  state.issueError = issues.error;
743
- const trackedIssueIds = (state.activeIssues.length > 0 ? state.activeIssues : state.readyIssues)
844
+ const trackedIssueIds = [...state.activeIssues, ...state.readyIssues]
744
845
  .slice(0, 8)
745
846
  .map((issue) => issue.id);
746
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.95",
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.95",
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",