@femtomc/mu-server 26.2.96 → 26.2.98

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
@@ -91,6 +91,9 @@ Use `mu store paths --pretty` to resolve `<store>` for the active repo/workspace
91
91
  "memory_index": {
92
92
  "enabled": true,
93
93
  "every_ms": 300000
94
+ },
95
+ "operator": {
96
+ "timeout_ms": 600000
94
97
  }
95
98
  }
96
99
  }
@@ -153,6 +156,7 @@ mu control status --pretty
153
156
  - Discord: `control_plane.adapters.discord.signing_secret`
154
157
  - Telegram: `control_plane.adapters.telegram.webhook_secret`, `bot_token`, `bot_username`
155
158
  - Neovim: `control_plane.adapters.neovim.shared_secret`
159
+ - Optional operator tuning: `control_plane.operator.timeout_ms` (max wall-time per messaging turn, default `600000`).
156
160
 
157
161
  3) Reload live control-plane runtime:
158
162
 
package/dist/config.d.ts CHANGED
@@ -23,6 +23,7 @@ export type MuConfig = {
23
23
  provider: string | null;
24
24
  model: string | null;
25
25
  thinking: string | null;
26
+ timeout_ms: number;
26
27
  };
27
28
  memory_index: {
28
29
  enabled: boolean;
@@ -54,6 +55,7 @@ export type MuConfigPatch = {
54
55
  provider?: string | null;
55
56
  model?: string | null;
56
57
  thinking?: string | null;
58
+ timeout_ms?: number;
57
59
  };
58
60
  memory_index?: {
59
61
  enabled?: boolean;
@@ -85,6 +87,7 @@ export type MuConfigPresence = {
85
87
  provider: boolean;
86
88
  model: boolean;
87
89
  thinking: boolean;
90
+ timeout_ms: number;
88
91
  };
89
92
  memory_index: {
90
93
  enabled: boolean;
package/dist/config.js CHANGED
@@ -26,6 +26,7 @@ export const DEFAULT_MU_CONFIG = {
26
26
  provider: null,
27
27
  model: null,
28
28
  thinking: null,
29
+ timeout_ms: 600_000,
29
30
  },
30
31
  memory_index: {
31
32
  enabled: true,
@@ -129,6 +130,9 @@ export function normalizeMuConfig(input) {
129
130
  if ("thinking" in operator) {
130
131
  next.control_plane.operator.thinking = normalizeNullableString(operator.thinking);
131
132
  }
133
+ if ("timeout_ms" in operator) {
134
+ next.control_plane.operator.timeout_ms = normalizeInteger(operator.timeout_ms, next.control_plane.operator.timeout_ms, { min: 1_000, max: 7_200_000 });
135
+ }
132
136
  }
133
137
  const memoryIndex = asRecord(controlPlane.memory_index);
134
138
  if (memoryIndex) {
@@ -214,6 +218,9 @@ function normalizeMuConfigPatch(input) {
214
218
  if ("thinking" in operator) {
215
219
  patch.control_plane.operator.thinking = normalizeNullableString(operator.thinking);
216
220
  }
221
+ if ("timeout_ms" in operator) {
222
+ patch.control_plane.operator.timeout_ms = normalizeInteger(operator.timeout_ms, DEFAULT_MU_CONFIG.control_plane.operator.timeout_ms, { min: 1_000, max: 7_200_000 });
223
+ }
217
224
  if (Object.keys(patch.control_plane.operator).length === 0) {
218
225
  delete patch.control_plane.operator;
219
226
  }
@@ -288,6 +295,9 @@ export function applyMuConfigPatch(base, patchInput) {
288
295
  if ("thinking" in operator) {
289
296
  next.control_plane.operator.thinking = operator.thinking ?? null;
290
297
  }
298
+ if ("timeout_ms" in operator && typeof operator.timeout_ms === "number" && Number.isFinite(operator.timeout_ms)) {
299
+ next.control_plane.operator.timeout_ms = normalizeInteger(operator.timeout_ms, next.control_plane.operator.timeout_ms, { min: 1_000, max: 7_200_000 });
300
+ }
291
301
  }
292
302
  const memoryIndex = patch.control_plane.memory_index;
293
303
  if (memoryIndex) {
@@ -373,6 +383,7 @@ export function muConfigPresence(config) {
373
383
  provider: isPresent(config.control_plane.operator.provider),
374
384
  model: isPresent(config.control_plane.operator.model),
375
385
  thinking: isPresent(config.control_plane.operator.thinking),
386
+ timeout_ms: config.control_plane.operator.timeout_ms,
376
387
  },
377
388
  memory_index: {
378
389
  enabled: config.control_plane.memory_index.enabled,
@@ -40,6 +40,11 @@ export type TelegramSendDocumentPayload = {
40
40
  * while preserving fenced code blocks verbatim.
41
41
  */
42
42
  export declare function renderTelegramMarkdown(text: string): string;
43
+ /**
44
+ * Slack mrkdwn does not support Markdown headings.
45
+ * Normalize common heading markers while preserving fenced blocks.
46
+ */
47
+ export declare function renderSlackMarkdown(text: string): string;
43
48
  export declare function containsTelegramMathNotation(text: string): boolean;
44
49
  export declare function buildTelegramSendMessagePayload(opts: {
45
50
  chatId: string;
@@ -53,6 +53,34 @@ export function renderTelegramMarkdown(text) {
53
53
  }
54
54
  return out.join("\n");
55
55
  }
56
+ /**
57
+ * Slack mrkdwn does not support Markdown headings.
58
+ * Normalize common heading markers while preserving fenced blocks.
59
+ */
60
+ export function renderSlackMarkdown(text) {
61
+ const normalized = text.replaceAll("\r\n", "\n");
62
+ const lines = normalized.split("\n");
63
+ const out = [];
64
+ let inFence = false;
65
+ for (const line of lines) {
66
+ const trimmed = line.trimStart();
67
+ if (trimmed.startsWith("```")) {
68
+ inFence = !inFence;
69
+ out.push(line);
70
+ continue;
71
+ }
72
+ if (inFence) {
73
+ out.push(line);
74
+ continue;
75
+ }
76
+ let next = line;
77
+ next = next.replace(/^#{1,6}\s+(.+)$/, "*$1*");
78
+ next = next.replace(/\*\*(.+?)\*\*/g, "*$1*");
79
+ next = next.replace(/__(.+?)__/g, "_$1_");
80
+ out.push(next);
81
+ }
82
+ return out.join("\n");
83
+ }
56
84
  const TELEGRAM_MATH_PATTERNS = [
57
85
  /\$\$[\s\S]+?\$\$/m,
58
86
  /(^|[^\\])\$[^$\n]+\$/m,
@@ -133,7 +161,7 @@ export function splitSlackMessageText(text, maxLen = SLACK_MESSAGE_MAX_LEN) {
133
161
  }
134
162
  return chunks;
135
163
  }
136
- function slackBlocksForOutboxRecord(record) {
164
+ function slackBlocksForOutboxRecord(record, body) {
137
165
  const interactionMessage = record.envelope.metadata?.interaction_message;
138
166
  if (!interactionMessage || typeof interactionMessage !== "object") {
139
167
  return undefined;
@@ -176,7 +204,7 @@ function slackBlocksForOutboxRecord(record) {
176
204
  return undefined;
177
205
  }
178
206
  return [
179
- { type: "section", text: { type: "mrkdwn", text: record.envelope.body } },
207
+ { type: "section", text: { type: "mrkdwn", text: body } },
180
208
  { type: "actions", elements: buttons },
181
209
  ];
182
210
  }
@@ -189,6 +217,14 @@ function slackThreadTsFromMetadata(metadata) {
189
217
  }
190
218
  return undefined;
191
219
  }
220
+ function slackStatusMessageTsFromMetadata(metadata) {
221
+ const value = metadata?.slack_status_message_ts;
222
+ if (typeof value !== "string") {
223
+ return undefined;
224
+ }
225
+ const trimmed = value.trim();
226
+ return trimmed.length > 0 ? trimmed : undefined;
227
+ }
192
228
  function telegramReplyMarkupForOutboxRecord(record) {
193
229
  const interactionMessage = record.envelope.metadata?.interaction_message;
194
230
  if (!interactionMessage || typeof interactionMessage !== "object") {
@@ -465,11 +501,48 @@ async function postSlackJson(opts) {
465
501
  export async function deliverSlackOutboxRecord(opts) {
466
502
  const { botToken, record } = opts;
467
503
  const attachments = record.envelope.attachments ?? [];
468
- const textChunks = splitSlackMessageText(record.envelope.body);
469
- const blocks = slackBlocksForOutboxRecord(record);
504
+ const renderedBody = renderSlackMarkdown(record.envelope.body);
505
+ const textChunks = splitSlackMessageText(renderedBody);
506
+ const blocks = slackBlocksForOutboxRecord(record, renderedBody);
470
507
  const threadTs = slackThreadTsFromMetadata(record.envelope.metadata);
508
+ const statusMessageTs = slackStatusMessageTsFromMetadata(record.envelope.metadata);
471
509
  if (attachments.length === 0) {
510
+ let chunkStartIndex = 0;
511
+ if (statusMessageTs && textChunks.length > 0) {
512
+ const updated = await postSlackJson({
513
+ botToken,
514
+ method: "chat.update",
515
+ payload: {
516
+ channel: record.envelope.channel_conversation_id,
517
+ ts: statusMessageTs,
518
+ text: textChunks[0],
519
+ unfurl_links: false,
520
+ unfurl_media: false,
521
+ ...(blocks ? { blocks } : {}),
522
+ },
523
+ });
524
+ if (updated.response.ok && updated.payload?.ok) {
525
+ chunkStartIndex = 1;
526
+ }
527
+ else {
528
+ const status = updated.response.status;
529
+ const err = updated.payload?.error ?? "unknown_error";
530
+ if (status === 429 || status >= 500) {
531
+ return {
532
+ kind: "retry",
533
+ error: `slack chat.update ${status}: ${err}`,
534
+ retryDelayMs: parseRetryDelayMs(updated.response),
535
+ };
536
+ }
537
+ if (err !== "message_not_found" && err !== "cant_update_message") {
538
+ return { kind: "retry", error: `slack chat.update ${status}: ${err}` };
539
+ }
540
+ }
541
+ }
472
542
  for (const [index, chunk] of textChunks.entries()) {
543
+ if (index < chunkStartIndex) {
544
+ continue;
545
+ }
473
546
  const delivered = await postSlackJson({
474
547
  botToken,
475
548
  method: "chat.postMessage",
@@ -11,6 +11,7 @@ export function buildMessagingOperatorRuntime(opts) {
11
11
  provider: opts.config.operator.provider ?? undefined,
12
12
  model: opts.config.operator.model ?? undefined,
13
13
  thinking: opts.config.operator.thinking ?? undefined,
14
+ timeoutMs: opts.config.operator.timeout_ms,
14
15
  extensionPaths: operatorExtensionPaths,
15
16
  });
16
17
  const conversationSessionStore = new JsonFileConversationSessionStore(join(getControlPlanePaths(opts.repoRoot).controlPlaneDir, "operator_conversations.json"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@femtomc/mu-server",
3
- "version": "26.2.96",
3
+ "version": "26.2.98",
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.96",
34
- "@femtomc/mu-control-plane": "26.2.96",
35
- "@femtomc/mu-core": "26.2.96"
33
+ "@femtomc/mu-agent": "26.2.98",
34
+ "@femtomc/mu-control-plane": "26.2.98",
35
+ "@femtomc/mu-core": "26.2.98"
36
36
  }
37
37
  }