@femtomc/mu-agent 26.2.69 → 26.2.70

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.
Files changed (52) hide show
  1. package/README.md +20 -1
  2. package/dist/backend.d.ts +1 -0
  3. package/dist/backend.d.ts.map +1 -1
  4. package/dist/backend.js +11 -2
  5. package/dist/extensions/activities.d.ts +4 -1
  6. package/dist/extensions/activities.d.ts.map +1 -1
  7. package/dist/extensions/activities.js +127 -16
  8. package/dist/extensions/branding.d.ts +2 -3
  9. package/dist/extensions/branding.d.ts.map +1 -1
  10. package/dist/extensions/branding.js +73 -58
  11. package/dist/extensions/cron.d.ts +4 -1
  12. package/dist/extensions/cron.d.ts.map +1 -1
  13. package/dist/extensions/cron.js +74 -14
  14. package/dist/extensions/heartbeats.d.ts +4 -1
  15. package/dist/extensions/heartbeats.d.ts.map +1 -1
  16. package/dist/extensions/heartbeats.js +60 -17
  17. package/dist/extensions/index.d.ts +13 -3
  18. package/dist/extensions/index.d.ts.map +1 -1
  19. package/dist/extensions/index.js +13 -3
  20. package/dist/extensions/messaging-setup.d.ts +4 -1
  21. package/dist/extensions/messaging-setup.d.ts.map +1 -1
  22. package/dist/extensions/messaging-setup.js +47 -10
  23. package/dist/extensions/mu-full-tools.d.ts +10 -0
  24. package/dist/extensions/mu-full-tools.d.ts.map +1 -0
  25. package/dist/extensions/mu-full-tools.js +25 -0
  26. package/dist/extensions/mu-operator.d.ts.map +1 -1
  27. package/dist/extensions/mu-operator.js +2 -6
  28. package/dist/extensions/mu-query-tools.d.ts +10 -0
  29. package/dist/extensions/mu-query-tools.d.ts.map +1 -0
  30. package/dist/extensions/mu-query-tools.js +11 -0
  31. package/dist/extensions/operator-command.d.ts.map +1 -1
  32. package/dist/extensions/operator-command.js +106 -6
  33. package/dist/extensions/orchestration-runs-readonly.d.ts.map +1 -1
  34. package/dist/extensions/orchestration-runs-readonly.js +180 -10
  35. package/dist/extensions/orchestration-runs.d.ts.map +1 -1
  36. package/dist/extensions/orchestration-runs.js +206 -14
  37. package/dist/extensions/server-tools.d.ts +10 -0
  38. package/dist/extensions/server-tools.d.ts.map +1 -1
  39. package/dist/extensions/server-tools.js +688 -290
  40. package/dist/extensions/shared.d.ts +11 -0
  41. package/dist/extensions/shared.d.ts.map +1 -1
  42. package/dist/extensions/shared.js +81 -0
  43. package/dist/session_factory.d.ts.map +1 -1
  44. package/dist/session_factory.js +3 -1
  45. package/dist/ui_defaults.d.ts +4 -0
  46. package/dist/ui_defaults.d.ts.map +1 -0
  47. package/dist/ui_defaults.js +18 -0
  48. package/package.json +4 -3
  49. package/prompts/roles/operator.md +5 -0
  50. package/prompts/roles/orchestrator.md +1 -0
  51. package/prompts/roles/worker.md +10 -4
  52. package/themes/mu-gruvbox-dark.json +90 -0
package/README.md CHANGED
@@ -45,13 +45,15 @@ not anonymous inline factories).
45
45
 
46
46
  Current stack:
47
47
 
48
- - `brandingExtension` — mu header/footer/widgets
48
+ - `brandingExtension` — mu compact header/footer branding + default theme
49
49
  - `serverToolsExtension` — status + issues/forum/events/control-plane tools
50
50
  - `eventLogExtension` — event tail + watch widget
51
51
  - `messagingSetupExtension` — adapter diagnostics and setup guidance
52
52
 
53
53
  `mu serve` sets `MU_SERVER_URL` automatically for these extensions.
54
54
 
55
+ Default operator UI theme is `mu-gruvbox-dark`.
56
+
55
57
  ## Slash commands (operator-facing)
56
58
 
57
59
  - `/mu status` — concise server status
@@ -81,6 +83,23 @@ Current stack:
81
83
  - `action`: `check | preflight | guide | plan | apply | verify`
82
84
  - `adapter`: `slack | discord | telegram | gmail`
83
85
 
86
+ ### Query contract (context-safe by default)
87
+
88
+ Read-heavy `mu_*` tools are designed to be summary-first, with explicit narrowing:
89
+
90
+ - `limit` controls result size (default is usually `20` for list/query/read actions).
91
+ - `contains` performs case-insensitive content filtering where relevant.
92
+ - `fields` (comma-separated paths) supports precise retrieval on targeted actions (`get`, `status`, `trace`, etc.).
93
+
94
+ Recommended flow:
95
+
96
+ 1. Discover with bounded list/query (`limit` + filters).
97
+ 2. Select a concrete ID.
98
+ 3. Retrieve only required fields via `fields`.
99
+
100
+ Tool `details` may still include richer payloads for diagnostics, but `content` is
101
+ kept compact to reduce context pollution.
102
+
84
103
  ## Messaging setup notes
85
104
 
86
105
  - Runtime setup state comes from `GET /api/config` and `.mu/config.json`.
package/dist/backend.d.ts CHANGED
@@ -40,6 +40,7 @@ export type CreateMuResourceLoaderOpts = {
40
40
  settingsManager?: SettingsManager;
41
41
  additionalExtensionPaths?: string[];
42
42
  additionalSkillPaths?: string[];
43
+ additionalThemePaths?: string[];
43
44
  };
44
45
  export declare function createMuResourceLoader(opts: CreateMuResourceLoaderOpts): DefaultResourceLoader;
45
46
  //# sourceMappingURL=backend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,EACN,WAAW,EAOX,qBAAqB,EAErB,eAAe,EACf,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,MAAM,cAAc,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,aAAa;IAC7B,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3C;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA4BpD;AAGD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC3B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,WAAW,EACxB,kBAAkB,CAAC,EAAE,MAAM,GACzB,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CA6BxB;AAED;;;;GAIG;AACH,qBAAa,UAAW,YAAW,aAAa;IACzC,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;CAiGhD;AAGD,MAAM,MAAM,0BAA0B,GAAG;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,0BAA0B,GAAG,qBAAqB,CAuB9F"}
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,EACN,WAAW,EAOX,qBAAqB,EAErB,eAAe,EACf,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAI5C,MAAM,MAAM,cAAc,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,aAAa;IAC7B,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3C;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA4BpD;AAGD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC3B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,WAAW,EACxB,kBAAkB,CAAC,EAAE,MAAM,GACzB,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CA6BxB;AAED;;;;GAIG;AACH,qBAAa,UAAW,YAAW,aAAa;IACzC,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;CAmGhD;AAGD,MAAM,MAAM,0BAA0B,GAAG;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,wBAAwB,CAAC,EAAE,MAAM,EAAE,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,0BAA0B,GAAG,qBAAqB,CA6B9F"}
package/dist/backend.js CHANGED
@@ -3,6 +3,8 @@ import { mkdir, open } from "node:fs/promises";
3
3
  import { basename, dirname, join } from "node:path";
4
4
  import { getModels, getProviders } from "@mariozechner/pi-ai";
5
5
  import { AuthStorage, createAgentSession, createBashTool, createEditTool, createReadTool, createWriteTool, DefaultResourceLoader, SessionManager, SettingsManager, } from "@mariozechner/pi-coding-agent";
6
+ import { orchestratorToolExtensionPaths, workerToolExtensionPaths } from "./extensions/index.js";
7
+ import { MU_DEFAULT_THEME_NAME, MU_DEFAULT_THEME_PATH } from "./ui_defaults.js";
6
8
  export function streamHasError(line) {
7
9
  let event;
8
10
  try {
@@ -75,11 +77,13 @@ export class SdkBackend {
75
77
  const scope = opts.provider ? ` in provider "${opts.provider}"` : "";
76
78
  throw new Error(`Model "${opts.model}" not found${scope} in pi-ai registry.`);
77
79
  }
78
- const settingsManager = SettingsManager.inMemory();
80
+ const settingsManager = SettingsManager.inMemory({ theme: MU_DEFAULT_THEME_NAME, quietStartup: true });
81
+ const roleExtensionPaths = opts.role === "worker" ? workerToolExtensionPaths : orchestratorToolExtensionPaths;
79
82
  const resourceLoader = createMuResourceLoader({
80
83
  cwd: opts.cwd,
81
84
  systemPrompt: opts.systemPrompt,
82
85
  settingsManager,
86
+ additionalExtensionPaths: roleExtensionPaths,
83
87
  });
84
88
  await resourceLoader.reload();
85
89
  const tools = [
@@ -159,6 +163,10 @@ export function createMuResourceLoader(opts) {
159
163
  for (const p of opts.additionalSkillPaths ?? []) {
160
164
  skillPaths.add(p);
161
165
  }
166
+ const themePaths = new Set([MU_DEFAULT_THEME_PATH]);
167
+ for (const p of opts.additionalThemePaths ?? []) {
168
+ themePaths.add(p);
169
+ }
162
170
  // If a repo has a top-level `skills/` dir (like workshop/), load it.
163
171
  const repoSkills = join(opts.cwd, "skills");
164
172
  if (existsSync(repoSkills)) {
@@ -167,9 +175,10 @@ export function createMuResourceLoader(opts) {
167
175
  return new DefaultResourceLoader({
168
176
  cwd: opts.cwd,
169
177
  agentDir: opts.agentDir,
170
- settingsManager: opts.settingsManager ?? SettingsManager.inMemory(),
178
+ settingsManager: opts.settingsManager ?? SettingsManager.inMemory({ theme: MU_DEFAULT_THEME_NAME, quietStartup: true }),
171
179
  additionalExtensionPaths: opts.additionalExtensionPaths,
172
180
  additionalSkillPaths: [...skillPaths],
181
+ additionalThemePaths: [...themePaths],
173
182
  systemPromptOverride: (_base) => opts.systemPrompt,
174
183
  agentsFilesOverride: (base) => ({
175
184
  agentsFiles: base.agentsFiles.filter((f) => basename(f.path) === "AGENTS.md"),
@@ -1,4 +1,7 @@
1
1
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- export declare function activitiesExtension(pi: ExtensionAPI): void;
2
+ export type ActivitiesExtensionOpts = {
3
+ allowMutations?: boolean;
4
+ };
5
+ export declare function activitiesExtension(pi: ExtensionAPI, opts?: ActivitiesExtensionOpts): void;
3
6
  export default activitiesExtension;
4
7
  //# sourceMappingURL=activities.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"activities.d.ts","sourceRoot":"","sources":["../../src/extensions/activities.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAUlE,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,QAmHnD;AAED,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"activities.d.ts","sourceRoot":"","sources":["../../src/extensions/activities.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAsDlE,MAAM,MAAM,uBAAuB,GAAG;IACrC,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,GAAE,uBAA4B,QAqMvF;AAED,eAAe,mBAAmB,CAAC"}
@@ -1,28 +1,65 @@
1
1
  import { StringEnum } from "@mariozechner/pi-ai";
2
2
  import { Type } from "@sinclair/typebox";
3
- import { clampInt, fetchMuJson, textResult, toJsonText } from "./shared.js";
3
+ import { asArray, asNumber, asRecord, asString, clampInt, fetchMuJson, parseFieldPaths, previewText, selectFields, textResult, toJsonText, } from "./shared.js";
4
4
  function trimOrNull(value) {
5
5
  if (value == null)
6
6
  return null;
7
7
  const trimmed = value.trim();
8
8
  return trimmed.length > 0 ? trimmed : null;
9
9
  }
10
- export function activitiesExtension(pi) {
10
+ function summarizeActivity(activity) {
11
+ return {
12
+ activity_id: asString(activity.activity_id),
13
+ title: previewText(activity.title, 140),
14
+ kind: asString(activity.kind),
15
+ status: asString(activity.status),
16
+ message_preview: previewText(activity.message, 180),
17
+ started_at_ms: asNumber(activity.started_at_ms),
18
+ updated_at_ms: asNumber(activity.updated_at_ms),
19
+ finished_at_ms: asNumber(activity.finished_at_ms),
20
+ };
21
+ }
22
+ function summarizeActivityEvent(event) {
23
+ return {
24
+ ts_ms: asNumber(event.ts_ms),
25
+ status: asString(event.status),
26
+ message_preview: previewText(event.message, 220),
27
+ reason: asString(event.reason),
28
+ source: asString(event.source),
29
+ };
30
+ }
31
+ function summarizeActivityMutation(payload) {
32
+ const activity = asRecord(payload.activity) ?? asRecord(payload.target_activity) ?? asRecord(payload.result);
33
+ return {
34
+ ok: payload.ok ?? null,
35
+ reason: asString(payload.reason),
36
+ activity: activity ? summarizeActivity(activity) : null,
37
+ };
38
+ }
39
+ export function activitiesExtension(pi, opts = {}) {
40
+ const allowMutations = opts.allowMutations ?? true;
41
+ const activityActions = allowMutations
42
+ ? ["list", "get", "start", "progress", "heartbeat", "complete", "fail", "cancel", "events"]
43
+ : ["list", "get", "events"];
11
44
  const ActivitiesParams = Type.Object({
12
- action: StringEnum(["list", "get", "start", "progress", "heartbeat", "complete", "fail", "cancel", "events"]),
45
+ action: StringEnum(activityActions),
13
46
  activity_id: Type.Optional(Type.String({ description: "Activity ID" })),
14
47
  title: Type.Optional(Type.String({ description: "Title for start" })),
15
48
  kind: Type.Optional(Type.String({ description: "Activity kind for start/list filtering" })),
16
49
  heartbeat_every_ms: Type.Optional(Type.Number({ description: "Heartbeat interval in ms for start" })),
17
50
  status: Type.Optional(Type.String({ description: "Status filter for list" })),
51
+ contains: Type.Optional(Type.String({ description: "Case-insensitive search text for list/event messages" })),
18
52
  message: Type.Optional(Type.String({ description: "Progress/final message" })),
19
53
  reason: Type.Optional(Type.String({ description: "Heartbeat reason" })),
20
- limit: Type.Optional(Type.Number({ description: "Optional limit for list/events" })),
54
+ fields: Type.Optional(Type.String({ description: "Comma-separated fields for get/events selection" })),
55
+ limit: Type.Optional(Type.Number({ description: "Optional limit for list/events (default: 20)" })),
21
56
  });
22
57
  pi.registerTool({
23
58
  name: "mu_activities",
24
59
  label: "Activities",
25
- description: "Manage generic long-running activities. Actions: list, get, start, progress, heartbeat, complete, fail, cancel, events.",
60
+ description: allowMutations
61
+ ? "Manage generic long-running activities. Actions: list, get, start, progress, heartbeat, complete, fail, cancel, events. List/events are summary-first; use fields for precise retrieval."
62
+ : "Read-only activity inspection. Actions: list, get, events. Mutation actions are disabled in query-only mode.",
26
63
  parameters: ActivitiesParams,
27
64
  async execute(_toolCallId, params) {
28
65
  switch (params.action) {
@@ -30,31 +67,72 @@ export function activitiesExtension(pi) {
30
67
  const query = new URLSearchParams();
31
68
  const status = trimOrNull(params.status);
32
69
  const kind = trimOrNull(params.kind);
33
- const limit = clampInt(params.limit, 50, 1, 500);
70
+ const contains = trimOrNull(params.contains);
71
+ const limit = clampInt(params.limit, 20, 1, 500);
34
72
  if (status)
35
73
  query.set("status", status);
36
74
  if (kind)
37
75
  query.set("kind", kind);
38
- query.set("limit", String(limit));
76
+ query.set("limit", String(Math.max(limit, 100)));
39
77
  const payload = await fetchMuJson(`/api/activities?${query.toString()}`);
40
- return textResult(toJsonText(payload), { action: "list", status, kind, limit });
78
+ const activities = asArray(payload.activities)
79
+ .map((activity) => asRecord(activity))
80
+ .filter((activity) => activity != null)
81
+ .filter((activity) => {
82
+ if (!contains)
83
+ return true;
84
+ const haystack = `${previewText(activity.title, 500)}\n${previewText(activity.message, 500)}`.toLowerCase();
85
+ return haystack.includes(contains.toLowerCase());
86
+ });
87
+ const sliced = activities.slice(0, limit);
88
+ return textResult(toJsonText({
89
+ total: activities.length,
90
+ returned: sliced.length,
91
+ truncated: sliced.length < activities.length,
92
+ activities: sliced.map((activity) => summarizeActivity(activity)),
93
+ }), { action: "list", status, kind, contains, limit, payload });
41
94
  }
42
95
  case "get": {
43
96
  const activityId = trimOrNull(params.activity_id);
44
97
  if (!activityId)
45
98
  return textResult("get requires activity_id");
46
99
  const payload = await fetchMuJson(`/api/activities/${encodeURIComponent(activityId)}`);
47
- return textResult(toJsonText(payload), { action: "get", activityId });
100
+ const fields = parseFieldPaths(trimOrNull(params.fields) ?? undefined);
101
+ const content = fields.length > 0
102
+ ? { activity_id: activityId, selected: selectFields(payload, fields) }
103
+ : { activity: summarizeActivity(payload) };
104
+ return textResult(toJsonText(content), { action: "get", activityId, fields, payload });
48
105
  }
49
106
  case "events": {
50
107
  const activityId = trimOrNull(params.activity_id);
51
108
  if (!activityId)
52
109
  return textResult("events requires activity_id");
53
- const limit = clampInt(params.limit, 200, 1, 2_000);
54
- const payload = await fetchMuJson(`/api/activities/${encodeURIComponent(activityId)}/events?limit=${limit}`);
55
- return textResult(toJsonText(payload), { action: "events", activityId, limit });
110
+ const contains = trimOrNull(params.contains);
111
+ const limit = clampInt(params.limit, 20, 1, 500);
112
+ const payload = await fetchMuJson(`/api/activities/${encodeURIComponent(activityId)}/events?limit=${Math.max(limit, 100)}`);
113
+ const fields = parseFieldPaths(trimOrNull(params.fields) ?? undefined);
114
+ const records = asArray(payload.events)
115
+ .map((event) => asRecord(event))
116
+ .filter((event) => event != null)
117
+ .filter((event) => {
118
+ if (!contains)
119
+ return true;
120
+ const haystack = `${previewText(event.message, 500)}\n${previewText(event.reason, 500)}\n${previewText(event.status, 500)}`.toLowerCase();
121
+ return haystack.includes(contains.toLowerCase());
122
+ });
123
+ const sliced = records.slice(-limit);
124
+ const content = fields.length > 0
125
+ ? { activity_id: activityId, selected: sliced.map((event) => selectFields(event, fields)) }
126
+ : { activity_id: activityId, events: sliced.map((event) => summarizeActivityEvent(event)) };
127
+ return textResult(toJsonText(content), { action: "events", activityId, contains, limit, fields, payload });
56
128
  }
57
129
  case "start": {
130
+ if (!allowMutations) {
131
+ return textResult("activity mutations are disabled in query-only mode.", {
132
+ blocked: true,
133
+ reason: "activities_query_only_mode",
134
+ });
135
+ }
58
136
  const title = trimOrNull(params.title);
59
137
  if (!title)
60
138
  return textResult("start requires title");
@@ -70,9 +148,21 @@ export function activitiesExtension(pi) {
70
148
  heartbeat_every_ms: heartbeatEveryMs,
71
149
  },
72
150
  });
73
- return textResult(toJsonText(payload), { action: "start", title, kind, heartbeatEveryMs });
151
+ return textResult(toJsonText(summarizeActivityMutation(payload)), {
152
+ action: "start",
153
+ title,
154
+ kind,
155
+ heartbeatEveryMs,
156
+ payload,
157
+ });
74
158
  }
75
159
  case "progress": {
160
+ if (!allowMutations) {
161
+ return textResult("activity mutations are disabled in query-only mode.", {
162
+ blocked: true,
163
+ reason: "activities_query_only_mode",
164
+ });
165
+ }
76
166
  const activityId = trimOrNull(params.activity_id);
77
167
  if (!activityId)
78
168
  return textResult("progress requires activity_id");
@@ -84,9 +174,20 @@ export function activitiesExtension(pi) {
84
174
  message,
85
175
  },
86
176
  });
87
- return textResult(toJsonText(payload), { action: "progress", activityId, message });
177
+ return textResult(toJsonText(summarizeActivityMutation(payload)), {
178
+ action: "progress",
179
+ activityId,
180
+ message,
181
+ payload,
182
+ });
88
183
  }
89
184
  case "heartbeat": {
185
+ if (!allowMutations) {
186
+ return textResult("activity mutations are disabled in query-only mode.", {
187
+ blocked: true,
188
+ reason: "activities_query_only_mode",
189
+ });
190
+ }
90
191
  const activityId = trimOrNull(params.activity_id);
91
192
  if (!activityId)
92
193
  return textResult("heartbeat requires activity_id");
@@ -98,7 +199,12 @@ export function activitiesExtension(pi) {
98
199
  reason,
99
200
  },
100
201
  });
101
- return textResult(toJsonText(payload), { action: "heartbeat", activityId, reason });
202
+ return textResult(toJsonText(summarizeActivityMutation(payload)), {
203
+ action: "heartbeat",
204
+ activityId,
205
+ reason,
206
+ payload,
207
+ });
102
208
  }
103
209
  case "complete":
104
210
  case "fail":
@@ -114,7 +220,12 @@ export function activitiesExtension(pi) {
114
220
  message,
115
221
  },
116
222
  });
117
- return textResult(toJsonText(payload), { action: params.action, activityId, message });
223
+ return textResult(toJsonText(summarizeActivityMutation(payload)), {
224
+ action: params.action,
225
+ activityId,
226
+ message,
227
+ payload,
228
+ });
118
229
  }
119
230
  default:
120
231
  return textResult(`unknown action: ${params.action}`);
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * mu-branding — Custom serve-mode TUI chrome for mu.
3
3
  *
4
- * Adds:
5
- * - Custom header + footer
6
- * - Quick command widget above the editor
4
+ * Defaults to a minimal, information-dense layout:
5
+ * - Compact header + footer
7
6
  * - Terminal title and working message branding
8
7
  * - Lightweight periodic status refresh (open/ready/control-plane)
9
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"branding.d.ts","sourceRoot":"","sources":["../../src/extensions/branding.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AAgDpF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,YAAY,QA8NjD;AAED,eAAe,iBAAiB,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,QAmOjD;AAED,eAAe,iBAAiB,CAAC"}
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * mu-branding — Custom serve-mode TUI chrome for mu.
3
3
  *
4
- * Adds:
5
- * - Custom header + footer
6
- * - Quick command widget above the editor
4
+ * Defaults to a minimal, information-dense layout:
5
+ * - Compact header + footer
7
6
  * - Terminal title and working message branding
8
7
  * - Lightweight periodic status refresh (open/ready/control-plane)
9
8
  */
10
9
  import { basename } from "node:path";
10
+ import { MU_DEFAULT_THEME_NAME, MU_VERSION } from "../ui_defaults.js";
11
11
  import { registerMuSubcommand } from "./mu-command-dispatcher.js";
12
12
  import { fetchMuStatus, muServerUrl } from "./shared.js";
13
13
  const EMPTY_SNAPSHOT = {
@@ -17,7 +17,7 @@ const EMPTY_SNAPSHOT = {
17
17
  adapters: [],
18
18
  error: null,
19
19
  };
20
- const ANSI_RE = /\u001b\[[0-9;]*m/g;
20
+ const ANSI_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
21
21
  function visibleWidth(text) {
22
22
  return text.replace(ANSI_RE, "").length;
23
23
  }
@@ -31,61 +31,75 @@ function truncateToWidth(text, width) {
31
31
  return plain.slice(0, 1);
32
32
  return `${plain.slice(0, width - 1)}…`;
33
33
  }
34
+ function centerLine(text, width) {
35
+ const vw = visibleWidth(text);
36
+ if (vw >= width)
37
+ return truncateToWidth(text, width);
38
+ const pad = Math.floor((width - vw) / 2);
39
+ return " ".repeat(pad) + text;
40
+ }
34
41
  function routesFromStatus(adapters, routes) {
35
42
  if (routes && routes.length > 0)
36
43
  return routes;
37
44
  return adapters.map((name) => ({ name, route: `/webhooks/${name}` }));
38
45
  }
39
- function modelLabelFromContext(ctx) {
46
+ function shortModelLabel(ctx) {
40
47
  if (!ctx.model)
41
- return "model:unknown";
42
- return `${ctx.model.provider}/${ctx.model.id}`;
48
+ return "?";
49
+ return ctx.model.id;
50
+ }
51
+ function shortModelLabelFromEvent(model) {
52
+ return model.id;
43
53
  }
44
- function modelLabelFromEventModel(model) {
45
- return `${model.provider}/${model.id}`;
54
+ const BAR_CHARS = ["░", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"];
55
+ function contextBar(percent, barWidth) {
56
+ const clamped = Math.max(0, Math.min(100, percent));
57
+ const filled = (clamped / 100) * barWidth;
58
+ const full = Math.floor(filled);
59
+ const frac = filled - full;
60
+ const fracIdx = Math.round(frac * (BAR_CHARS.length - 1));
61
+ const empty = barWidth - full - (fracIdx > 0 ? 1 : 0);
62
+ return (BAR_CHARS[BAR_CHARS.length - 1].repeat(full) +
63
+ (fracIdx > 0 ? BAR_CHARS[fracIdx] : "") +
64
+ BAR_CHARS[0].repeat(Math.max(0, empty)));
46
65
  }
47
66
  export function brandingExtension(pi) {
48
67
  let enabled = true;
49
68
  let repoName = "mu";
50
- let currentModel = "model:unknown";
69
+ let currentModelLabel = "?";
51
70
  let snapshot = { ...EMPTY_SNAPSHOT };
52
71
  let activeCtx = null;
53
72
  let pollTimer = null;
54
73
  let footerRequestRender = null;
55
- function refreshWidget(ctx) {
74
+ function applyDefaultTheme(ctx) {
56
75
  if (!ctx.hasUI)
57
76
  return;
58
- ctx.ui.setWidget("mu-quick-actions", (_tui, theme) => ({
59
- render(width) {
60
- const cpState = snapshot.error
61
- ? theme.fg("warning", "cp unavailable")
62
- : snapshot.controlPlaneActive
63
- ? theme.fg("success", `cp ${snapshot.adapters.join(",") || "on"}`)
64
- : theme.fg("muted", "cp off");
65
- const line1 = `${theme.fg("accent", "μ")}${theme.fg("dim", " quick actions")}: ${theme.fg("muted", "/mu status /mu control /mu setup /mu events")}`;
66
- const line2 = `${theme.fg("dim", `open ${snapshot.openCount} · ready ${snapshot.readyCount}`)} · ${cpState}`;
67
- return [truncateToWidth(line1, width), truncateToWidth(line2, width)];
68
- },
69
- invalidate() { },
70
- }));
77
+ const result = ctx.ui.setTheme(MU_DEFAULT_THEME_NAME);
78
+ if (!result.success) {
79
+ ctx.ui.notify(`failed to apply ${MU_DEFAULT_THEME_NAME}: ${result.error ?? "unknown error"}`, "warning");
80
+ }
71
81
  }
72
82
  function applyChrome(ctx) {
73
83
  if (!ctx.hasUI || !enabled)
74
84
  return;
75
- ctx.ui.setTitle(`mu ${repoName}`);
76
- ctx.ui.setWorkingMessage("μ working...");
85
+ ctx.ui.setTitle(`mu · ${repoName}`);
86
+ ctx.ui.setWorkingMessage("working");
87
+ ctx.ui.setWidget("mu-quick-actions", undefined);
77
88
  ctx.ui.setHeader((_tui, theme) => ({
78
89
  render(width) {
79
- const line1 = `${theme.fg("accent", theme.bold("μ"))} ${theme.bold("mu")} ${theme.fg("muted", "serve console")}`;
80
- const line2 = theme.fg("dim", `repo: ${repoName}`);
81
- const line3 = theme.fg("muted", "status via: mu_status · mu_control_plane · mu_messaging_setup");
82
- return [
83
- "",
84
- truncateToWidth(line1, width),
85
- truncateToWidth(line2, width),
86
- truncateToWidth(line3, width),
87
- "",
88
- ];
90
+ const cpPart = snapshot.error
91
+ ? ""
92
+ : snapshot.controlPlaneActive
93
+ ? ` ${theme.fg("muted", "·")} ${theme.fg("success", `cp:${snapshot.adapters.join(",") || "on"}`)}`
94
+ : "";
95
+ const line = [
96
+ theme.fg("accent", theme.bold("μ")),
97
+ theme.fg("muted", "·"),
98
+ theme.fg("dim", `v${MU_VERSION}`),
99
+ theme.fg("muted", "·"),
100
+ theme.fg("dim", repoName),
101
+ ].join(" ") + cpPart;
102
+ return [centerLine(line, width)];
89
103
  },
90
104
  invalidate() { },
91
105
  }));
@@ -102,27 +116,24 @@ export function brandingExtension(pi) {
102
116
  },
103
117
  invalidate() { },
104
118
  render(width) {
105
- const cpLabel = snapshot.error
106
- ? theme.fg("warning", "cp:unavailable")
107
- : snapshot.controlPlaneActive
108
- ? theme.fg("success", `cp:${snapshot.adapters.join(",") || "on"}`)
109
- : theme.fg("muted", "cp:off");
110
- const left = [
111
- theme.fg("accent", "μ"),
112
- theme.fg("dim", repoName),
113
- theme.fg("muted", "|"),
114
- theme.fg("dim", `open ${snapshot.openCount} ready ${snapshot.readyCount}`),
115
- theme.fg("muted", "|"),
116
- cpLabel,
117
- ].join(" ");
119
+ const parts = [theme.fg("dim", currentModelLabel)];
118
120
  const branch = footerData.getGitBranch();
119
- const right = theme.fg("dim", branch ? `${currentModel} · branch:${branch}` : currentModel);
120
- const pad = " ".repeat(Math.max(1, width - visibleWidth(left) - visibleWidth(right)));
121
- return [truncateToWidth(`${left}${pad}${right}`, width)];
121
+ if (branch) {
122
+ parts.push(theme.fg("muted", "·"), theme.fg("dim", branch));
123
+ }
124
+ const usage = activeCtx?.getContextUsage();
125
+ if (usage && usage.percent != null) {
126
+ const pct = Math.round(usage.percent);
127
+ const barColor = pct >= 80 ? "warning" : pct >= 60 ? "muted" : "dim";
128
+ parts.push(theme.fg("muted", "·"), theme.fg(barColor, `ctx ${pct}%`), theme.fg(barColor, contextBar(pct, 10)));
129
+ }
130
+ if (snapshot.openCount > 0 || snapshot.readyCount > 0) {
131
+ parts.push(theme.fg("muted", "·"), theme.fg("dim", `open ${snapshot.openCount} ready ${snapshot.readyCount}`));
132
+ }
133
+ return [centerLine(parts.join(" "), width)];
122
134
  },
123
135
  };
124
136
  });
125
- refreshWidget(ctx);
126
137
  }
127
138
  function clearChrome(ctx) {
128
139
  if (!ctx.hasUI)
@@ -142,7 +153,6 @@ export function brandingExtension(pi) {
142
153
  error: "MU_SERVER_URL not set",
143
154
  };
144
155
  ctx.ui.setStatus("mu-overview", ctx.ui.theme.fg("warning", "μ server unavailable"));
145
- refreshWidget(ctx);
146
156
  footerRequestRender?.();
147
157
  return;
148
158
  }
@@ -169,7 +179,6 @@ export function brandingExtension(pi) {
169
179
  };
170
180
  ctx.ui.setStatus("mu-overview", ctx.ui.theme.fg("warning", "μ status refresh failed"));
171
181
  }
172
- refreshWidget(ctx);
173
182
  footerRequestRender?.();
174
183
  }
175
184
  function ensurePolling() {
@@ -190,10 +199,11 @@ export function brandingExtension(pi) {
190
199
  async function initialize(ctx) {
191
200
  activeCtx = ctx;
192
201
  repoName = basename(ctx.cwd);
193
- currentModel = modelLabelFromContext(ctx);
202
+ currentModelLabel = shortModelLabel(ctx);
194
203
  if (!ctx.hasUI)
195
204
  return;
196
205
  if (enabled) {
206
+ applyDefaultTheme(ctx);
197
207
  applyChrome(ctx);
198
208
  await refreshStatus(ctx);
199
209
  ensurePolling();
@@ -210,10 +220,14 @@ export function brandingExtension(pi) {
210
220
  await initialize(ctx);
211
221
  });
212
222
  pi.on("model_select", async (event, ctx) => {
213
- currentModel = modelLabelFromEventModel(event.model);
223
+ currentModelLabel = shortModelLabelFromEvent(event.model);
224
+ if (!ctx.hasUI || !enabled)
225
+ return;
226
+ footerRequestRender?.();
227
+ });
228
+ pi.on("turn_end", async (_event, ctx) => {
214
229
  if (!ctx.hasUI || !enabled)
215
230
  return;
216
- ctx.ui.setStatus("mu-model", ctx.ui.theme.fg("dim", currentModel));
217
231
  footerRequestRender?.();
218
232
  });
219
233
  pi.on("session_shutdown", async () => {
@@ -241,6 +255,7 @@ export function brandingExtension(pi) {
241
255
  return;
242
256
  }
243
257
  if (enabled) {
258
+ applyDefaultTheme(ctx);
244
259
  applyChrome(ctx);
245
260
  await refreshStatus(ctx);
246
261
  ensurePolling();
@@ -1,4 +1,7 @@
1
1
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- export declare function cronExtension(pi: ExtensionAPI): void;
2
+ export type CronExtensionOpts = {
3
+ allowMutations?: boolean;
4
+ };
5
+ export declare function cronExtension(pi: ExtensionAPI, opts?: CronExtensionOpts): void;
3
6
  export default cronExtension;
4
7
  //# sourceMappingURL=cron.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cron.d.ts","sourceRoot":"","sources":["../../src/extensions/cron.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAiBlE,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,QAoK7C;AAED,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"cron.d.ts","sourceRoot":"","sources":["../../src/extensions/cron.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA0DlE,MAAM,MAAM,iBAAiB,GAAG;IAC/B,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,GAAE,iBAAsB,QAqM3E;AAED,eAAe,aAAa,CAAC"}