@femtomc/mu-server 26.2.96 → 26.2.97
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 +4 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +11 -0
- package/dist/control_plane.d.ts +5 -0
- package/dist/control_plane.js +77 -4
- package/dist/control_plane_bootstrap_helpers.js +1 -0
- package/package.json +4 -4
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,
|
package/dist/control_plane.d.ts
CHANGED
|
@@ -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;
|
package/dist/control_plane.js
CHANGED
|
@@ -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:
|
|
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
|
|
469
|
-
const
|
|
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.
|
|
3
|
+
"version": "26.2.97",
|
|
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.
|
|
34
|
-
"@femtomc/mu-control-plane": "26.2.
|
|
35
|
-
"@femtomc/mu-core": "26.2.
|
|
33
|
+
"@femtomc/mu-agent": "26.2.97",
|
|
34
|
+
"@femtomc/mu-control-plane": "26.2.97",
|
|
35
|
+
"@femtomc/mu-core": "26.2.97"
|
|
36
36
|
}
|
|
37
37
|
}
|