@femtomc/mu-server 26.2.101 → 26.2.103

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
@@ -126,6 +126,9 @@ Use `mu store paths --pretty` to resolve `<store>` for the active repo/workspace
126
126
  "source": "neovim"
127
127
  }
128
128
  ```
129
+ - `session_kind` is optional. When omitted, the server auto-resolves `session_id` across
130
+ `<store>/operator/sessions` and `<store>/control-plane/operator-sessions`.
131
+ If the same id exists in both stores, pass `session_kind` (or `session_dir`) to disambiguate.
129
132
  - Optional overrides: `session_file`, `session_dir`, `provider`, `model`, `thinking`, `extension_profile`
130
133
  - Response includes: `turn.reply`, `turn.context_entry_id`, `turn.session_file`
131
134
 
@@ -186,7 +189,9 @@ Operational fallbacks:
186
189
  - Missing Slack/Telegram bot tokens surface capability reason codes (`*_bot_token_missing`) and retry behavior.
187
190
 
188
191
  Server channel renderers consume canonical `hud_docs` metadata (`HudDoc`) for Slack/Telegram HUD
189
- rendering + actions. New features should extend the shared HUD contract path instead of bespoke
192
+ rendering + actions. Optional HUD presentation hints (`title_style`, `snapshot_style`, chip/item styles)
193
+ and metadata presets (`metadata.style_preset`) may be used by richer renderers and safely ignored by
194
+ plain-text channels. New features should extend the shared HUD contract path instead of bespoke
190
195
  channel-specific HUD payload formats.
191
196
 
192
197
  ## Running the Server
package/dist/config.d.ts CHANGED
@@ -12,7 +12,6 @@ export type MuConfig = {
12
12
  telegram: {
13
13
  webhook_secret: string | null;
14
14
  bot_token: string | null;
15
- bot_username: string | null;
16
15
  };
17
16
  neovim: {
18
17
  shared_secret: string | null;
@@ -44,7 +43,6 @@ export type MuConfigPatch = {
44
43
  telegram?: {
45
44
  webhook_secret?: string | null;
46
45
  bot_token?: string | null;
47
- bot_username?: string | null;
48
46
  };
49
47
  neovim?: {
50
48
  shared_secret?: string | null;
@@ -76,7 +74,6 @@ export type MuConfigPresence = {
76
74
  telegram: {
77
75
  webhook_secret: boolean;
78
76
  bot_token: boolean;
79
- bot_username: boolean;
80
77
  };
81
78
  neovim: {
82
79
  shared_secret: boolean;
package/dist/config.js CHANGED
@@ -15,7 +15,6 @@ export const DEFAULT_MU_CONFIG = {
15
15
  telegram: {
16
16
  webhook_secret: null,
17
17
  bot_token: null,
18
- bot_username: null,
19
18
  },
20
19
  neovim: {
21
20
  shared_secret: null,
@@ -107,9 +106,6 @@ export function normalizeMuConfig(input) {
107
106
  if ("bot_token" in telegram) {
108
107
  next.control_plane.adapters.telegram.bot_token = normalizeNullableString(telegram.bot_token);
109
108
  }
110
- if ("bot_username" in telegram) {
111
- next.control_plane.adapters.telegram.bot_username = normalizeNullableString(telegram.bot_username);
112
- }
113
109
  }
114
110
  const neovim = asRecord(adapters.neovim);
115
111
  if (neovim && "shared_secret" in neovim) {
@@ -185,9 +181,6 @@ function normalizeMuConfigPatch(input) {
185
181
  if ("bot_token" in telegram) {
186
182
  telegramPatch.bot_token = normalizeNullableString(telegram.bot_token);
187
183
  }
188
- if ("bot_username" in telegram) {
189
- telegramPatch.bot_username = normalizeNullableString(telegram.bot_username);
190
- }
191
184
  if (Object.keys(telegramPatch).length > 0) {
192
185
  patch.control_plane.adapters.telegram = telegramPatch;
193
186
  }
@@ -273,9 +266,6 @@ export function applyMuConfigPatch(base, patchInput) {
273
266
  if ("bot_token" in adapters.telegram) {
274
267
  next.control_plane.adapters.telegram.bot_token = adapters.telegram.bot_token ?? null;
275
268
  }
276
- if ("bot_username" in adapters.telegram) {
277
- next.control_plane.adapters.telegram.bot_username = adapters.telegram.bot_username ?? null;
278
- }
279
269
  }
280
270
  if (adapters.neovim && "shared_secret" in adapters.neovim) {
281
271
  next.control_plane.adapters.neovim.shared_secret = adapters.neovim.shared_secret ?? null;
@@ -372,7 +362,6 @@ export function muConfigPresence(config) {
372
362
  telegram: {
373
363
  webhook_secret: isPresent(config.control_plane.adapters.telegram.webhook_secret),
374
364
  bot_token: isPresent(config.control_plane.adapters.telegram.bot_token),
375
- bot_username: isPresent(config.control_plane.adapters.telegram.bot_username),
376
365
  },
377
366
  neovim: {
378
367
  shared_secret: isPresent(config.control_plane.adapters.neovim.shared_secret),
@@ -1,4 +1,4 @@
1
- import { normalizeHudDocs } from "@femtomc/mu-core";
1
+ import { applyHudStylePreset, normalizeHudDocs } from "@femtomc/mu-core";
2
2
  import { ControlPlaneCommandPipeline, ControlPlaneOutbox, ControlPlaneRuntime, buildSlackHudActionId, getControlPlanePaths, TelegramControlPlaneAdapterSpec, } from "@femtomc/mu-control-plane";
3
3
  import { DEFAULT_MU_CONFIG } from "./config.js";
4
4
  import { buildMessagingOperatorRuntime, createOutboxDrainLoop } from "./control_plane_bootstrap_helpers.js";
@@ -148,6 +148,9 @@ const SLACK_ACTIONS_MAX_PER_BLOCK = 5;
148
148
  const SLACK_ACTIONS_MAX_TOTAL = 20;
149
149
  const SLACK_DOCS_MAX = 3;
150
150
  const SLACK_SECTION_LINE_MAX = 8;
151
+ function hudDocsForPresentation(input, maxDocs) {
152
+ return normalizeHudDocs(input, { maxDocs }).map((doc) => applyHudStylePreset(doc) ?? doc);
153
+ }
151
154
  function truncateSlackText(text, maxLen = SLACK_BLOCK_TEXT_MAX_LEN) {
152
155
  if (text.length <= maxLen) {
153
156
  return text;
@@ -176,11 +179,33 @@ function hudTonePrefix(tone) {
176
179
  return "ℹ️";
177
180
  }
178
181
  }
182
+ function applySlackHudTextStyle(text, style, opts = {}) {
183
+ const weight = style?.weight ?? opts.defaultWeight;
184
+ const italic = style?.italic ?? opts.defaultItalic ?? false;
185
+ const code = style?.code ?? opts.defaultCode ?? false;
186
+ let out = text;
187
+ if (code) {
188
+ out = `\`${out}\``;
189
+ }
190
+ if (italic) {
191
+ out = `_${out}_`;
192
+ }
193
+ if (weight === "strong") {
194
+ out = `*${out}*`;
195
+ }
196
+ return out;
197
+ }
198
+ function stripSlackMrkdwn(text) {
199
+ return text.replace(/[*_`~]/g, "");
200
+ }
179
201
  function hudDocSectionLines(doc) {
180
202
  const lines = [];
181
203
  const chipsLine = doc.chips
182
204
  .slice(0, 8)
183
- .map((chip) => `${hudTonePrefix(chip.tone)} *${chip.label}*`)
205
+ .map((chip) => {
206
+ const label = applySlackHudTextStyle(chip.label, chip.style, { defaultWeight: "strong" });
207
+ return `${hudTonePrefix(chip.tone)} ${label}`;
208
+ })
184
209
  .join(" · ");
185
210
  if (chipsLine.length > 0) {
186
211
  lines.push(chipsLine);
@@ -188,25 +213,28 @@ function hudDocSectionLines(doc) {
188
213
  for (const section of doc.sections) {
189
214
  switch (section.kind) {
190
215
  case "kv": {
191
- const title = section.title ? `*${section.title}*` : "*Details*";
192
- const items = section.items.slice(0, SLACK_SECTION_LINE_MAX).map((item) => `• *${item.label}:* ${item.value}`);
216
+ const title = applySlackHudTextStyle(section.title ?? "Details", section.title_style, { defaultWeight: "strong" });
217
+ const items = section.items.slice(0, SLACK_SECTION_LINE_MAX).map((item) => {
218
+ const value = applySlackHudTextStyle(item.value, item.value_style);
219
+ return `• *${item.label}:* ${value}`;
220
+ });
193
221
  if (items.length > 0) {
194
222
  lines.push([title, ...items].join("\n"));
195
223
  }
196
224
  break;
197
225
  }
198
226
  case "checklist": {
199
- const title = section.title ? `*${section.title}*` : "*Checklist*";
227
+ const title = applySlackHudTextStyle(section.title ?? "Checklist", section.title_style, { defaultWeight: "strong" });
200
228
  const items = section.items
201
229
  .slice(0, SLACK_SECTION_LINE_MAX)
202
- .map((item) => `${item.done ? "✅" : "⬜"} ${item.label}`);
230
+ .map((item) => `${item.done ? "✅" : "⬜"} ${applySlackHudTextStyle(item.label, item.style)}`);
203
231
  if (items.length > 0) {
204
232
  lines.push([title, ...items].join("\n"));
205
233
  }
206
234
  break;
207
235
  }
208
236
  case "activity": {
209
- const title = section.title ? `*${section.title}*` : "*Activity*";
237
+ const title = applySlackHudTextStyle(section.title ?? "Activity", section.title_style, { defaultWeight: "strong" });
210
238
  const items = section.lines.slice(0, SLACK_SECTION_LINE_MAX).map((line) => `• ${line}`);
211
239
  if (items.length > 0) {
212
240
  lines.push([title, ...items].join("\n"));
@@ -214,14 +242,18 @@ function hudDocSectionLines(doc) {
214
242
  break;
215
243
  }
216
244
  case "text": {
217
- const title = section.title ? `*${section.title}*\n` : "";
218
- lines.push(`${title}${section.text}`);
245
+ const title = section.title
246
+ ? `${applySlackHudTextStyle(section.title, section.title_style, { defaultWeight: "strong" })}\n`
247
+ : "";
248
+ const text = applySlackHudTextStyle(section.text, section.style);
249
+ lines.push(`${title}${text}`);
219
250
  break;
220
251
  }
221
252
  }
222
253
  }
223
254
  if (lines.length === 0) {
224
- lines.push(`*Snapshot*\n${doc.snapshot_compact}`);
255
+ const snapshot = applySlackHudTextStyle(doc.snapshot_compact, doc.snapshot_style);
256
+ lines.push(`${applySlackHudTextStyle("Snapshot", undefined, { defaultWeight: "strong" })}\n${snapshot}`);
225
257
  }
226
258
  return lines;
227
259
  }
@@ -257,7 +289,7 @@ export function splitSlackMessageText(text, maxLen = SLACK_MESSAGE_MAX_LEN) {
257
289
  return chunks;
258
290
  }
259
291
  function slackBlocksForOutboxRecord(record, body) {
260
- const hudDocs = normalizeHudDocs(record.envelope.metadata?.hud_docs, { maxDocs: SLACK_DOCS_MAX });
292
+ const hudDocs = hudDocsForPresentation(record.envelope.metadata?.hud_docs, SLACK_DOCS_MAX);
261
293
  if (hudDocs.length === 0) {
262
294
  return undefined;
263
295
  }
@@ -270,9 +302,10 @@ function slackBlocksForOutboxRecord(record, body) {
270
302
  if (blocks.length >= SLACK_BLOCKS_MAX) {
271
303
  break;
272
304
  }
305
+ const styledTitle = applySlackHudTextStyle(doc.title, doc.title_style, { defaultWeight: "strong" });
273
306
  blocks.push({
274
307
  type: "context",
275
- elements: [{ type: "mrkdwn", text: truncateSlackText(`*HUD · ${doc.title}*`) }],
308
+ elements: [{ type: "mrkdwn", text: truncateSlackText(`*HUD* · ${styledTitle}`) }],
276
309
  });
277
310
  for (const line of hudDocSectionLines(doc)) {
278
311
  if (blocks.length >= SLACK_BLOCKS_MAX) {
@@ -321,7 +354,7 @@ function utf8ByteLength(value) {
321
354
  return new TextEncoder().encode(value).length;
322
355
  }
323
356
  function telegramTextForOutboxRecord(record, body) {
324
- const hudDocs = normalizeHudDocs(record.envelope.metadata?.hud_docs, { maxDocs: TELEGRAM_DOCS_MAX });
357
+ const hudDocs = hudDocsForPresentation(record.envelope.metadata?.hud_docs, TELEGRAM_DOCS_MAX);
325
358
  if (hudDocs.length === 0) {
326
359
  return body;
327
360
  }
@@ -329,13 +362,13 @@ function telegramTextForOutboxRecord(record, body) {
329
362
  for (const doc of hudDocs) {
330
363
  lines.push("", `HUD · ${doc.title}`);
331
364
  for (const sectionLine of hudDocSectionLines(doc)) {
332
- lines.push(sectionLine.replace(/\*/g, ""));
365
+ lines.push(stripSlackMrkdwn(sectionLine));
333
366
  }
334
367
  }
335
368
  return lines.join("\n").trim();
336
369
  }
337
370
  async function compileTelegramHudActions(opts) {
338
- const hudDocs = normalizeHudDocs(opts.record.envelope.metadata?.hud_docs, { maxDocs: TELEGRAM_DOCS_MAX });
371
+ const hudDocs = hudDocsForPresentation(opts.record.envelope.metadata?.hud_docs, TELEGRAM_DOCS_MAX);
339
372
  if (hudDocs.length === 0) {
340
373
  return { overflowText: "" };
341
374
  }
@@ -8,7 +8,6 @@ export type DetectedTelegramAdapter = {
8
8
  name: "telegram";
9
9
  webhookSecret: string;
10
10
  botToken: string | null;
11
- botUsername: string | null;
12
11
  };
13
12
  export type DetectedAdapter = DetectedStaticAdapter | DetectedTelegramAdapter;
14
13
  export declare function detectAdapters(config: ControlPlaneConfig): DetectedAdapter[];
@@ -50,7 +50,6 @@ export function detectAdapters(config) {
50
50
  name: "telegram",
51
51
  webhookSecret: telegramSecret,
52
52
  botToken: config.adapters.telegram.bot_token,
53
- botUsername: config.adapters.telegram.bot_username,
54
53
  });
55
54
  }
56
55
  return detected;
@@ -23,21 +23,18 @@ function telegramAdapterConfigFromControlPlane(config) {
23
23
  return {
24
24
  webhookSecret,
25
25
  botToken: config.adapters.telegram.bot_token,
26
- botUsername: config.adapters.telegram.bot_username,
27
26
  };
28
27
  }
29
28
  function applyTelegramAdapterConfig(base, telegram) {
30
29
  const next = cloneControlPlaneConfig(base);
31
30
  next.adapters.telegram.webhook_secret = telegram?.webhookSecret ?? null;
32
31
  next.adapters.telegram.bot_token = telegram?.botToken ?? null;
33
- next.adapters.telegram.bot_username = telegram?.botUsername ?? null;
34
32
  return next;
35
33
  }
36
34
  function cloneTelegramAdapterConfig(config) {
37
35
  return {
38
36
  webhookSecret: config.webhookSecret,
39
37
  botToken: config.botToken,
40
- botUsername: config.botUsername,
41
38
  };
42
39
  }
43
40
  function describeError(err) {
@@ -112,7 +109,6 @@ export class TelegramAdapterGenerationManager {
112
109
  outbox: this.#outbox,
113
110
  webhookSecret: config.webhookSecret,
114
111
  botToken: config.botToken,
115
- botUsername: config.botUsername,
116
112
  deferredIngress: true,
117
113
  onOutboxEnqueued: this.#onOutboxEnqueued ?? undefined,
118
114
  signalObserver: this.#signalObserver ?? undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@femtomc/mu-server",
3
- "version": "26.2.101",
3
+ "version": "26.2.103",
4
4
  "description": "HTTP API server for mu control-plane transport/session plus run/activity scheduling coordination.",
5
5
  "keywords": [
6
6
  "mu",
@@ -30,8 +30,8 @@
30
30
  "start": "bun run dist/cli.js"
31
31
  },
32
32
  "dependencies": {
33
- "@femtomc/mu-agent": "26.2.101",
34
- "@femtomc/mu-control-plane": "26.2.101",
35
- "@femtomc/mu-core": "26.2.101"
33
+ "@femtomc/mu-agent": "26.2.103",
34
+ "@femtomc/mu-control-plane": "26.2.103",
35
+ "@femtomc/mu-core": "26.2.103"
36
36
  }
37
37
  }