@femtomc/mu-server 26.2.39 → 26.2.40
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/dist/cli.js +13 -1
- package/dist/control_plane.js +130 -99
- package/package.json +13 -7
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,19 @@ catch {
|
|
|
13
13
|
console.log(`Starting mu-server on port ${port}...`);
|
|
14
14
|
console.log(`Repository root: ${repoRoot}`);
|
|
15
15
|
const { serverConfig, controlPlane } = await createServerAsync({ repoRoot, port });
|
|
16
|
-
|
|
16
|
+
let server;
|
|
17
|
+
try {
|
|
18
|
+
server = Bun.serve(serverConfig);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
try {
|
|
22
|
+
await controlPlane?.stop();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Best effort cleanup. Preserve the startup error.
|
|
26
|
+
}
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
17
29
|
console.log(`Server running at http://localhost:${port}`);
|
|
18
30
|
if (controlPlane && controlPlane.activeAdapters.length > 0) {
|
|
19
31
|
console.log("Control plane: active");
|
package/dist/control_plane.js
CHANGED
|
@@ -49,115 +49,146 @@ export async function bootstrapControlPlane(opts) {
|
|
|
49
49
|
}
|
|
50
50
|
const paths = getControlPlanePaths(opts.repoRoot);
|
|
51
51
|
const runtime = new ControlPlaneRuntime({ repoRoot: opts.repoRoot });
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const outbox = new ControlPlaneOutbox(paths.outboxPath);
|
|
63
|
-
await outbox.load();
|
|
64
|
-
let telegramBotToken = null;
|
|
65
|
-
const adapterMap = new Map();
|
|
66
|
-
for (const d of detected) {
|
|
67
|
-
let adapter;
|
|
68
|
-
switch (d.name) {
|
|
69
|
-
case "slack":
|
|
70
|
-
adapter = new SlackControlPlaneAdapter({
|
|
71
|
-
pipeline,
|
|
72
|
-
outbox,
|
|
73
|
-
signingSecret: d.signingSecret,
|
|
74
|
-
});
|
|
75
|
-
break;
|
|
76
|
-
case "discord":
|
|
77
|
-
adapter = new DiscordControlPlaneAdapter({
|
|
78
|
-
pipeline,
|
|
79
|
-
outbox,
|
|
80
|
-
signingSecret: d.signingSecret,
|
|
81
|
-
});
|
|
82
|
-
break;
|
|
83
|
-
case "telegram":
|
|
84
|
-
adapter = new TelegramControlPlaneAdapter({
|
|
85
|
-
pipeline,
|
|
86
|
-
outbox,
|
|
87
|
-
webhookSecret: d.webhookSecret,
|
|
88
|
-
botUsername: d.botUsername ?? undefined,
|
|
89
|
-
});
|
|
90
|
-
if (d.botToken) {
|
|
91
|
-
telegramBotToken = d.botToken;
|
|
92
|
-
}
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
const route = adapter.spec.route;
|
|
96
|
-
if (adapterMap.has(route)) {
|
|
97
|
-
throw new Error(`duplicate control-plane webhook route: ${route}`);
|
|
98
|
-
}
|
|
99
|
-
adapterMap.set(route, {
|
|
100
|
-
adapter,
|
|
101
|
-
info: {
|
|
102
|
-
name: adapter.spec.channel,
|
|
103
|
-
route,
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
const deliver = async (record) => {
|
|
108
|
-
const { envelope } = record;
|
|
109
|
-
if (envelope.channel === "telegram") {
|
|
110
|
-
if (!telegramBotToken) {
|
|
111
|
-
return { kind: "retry", error: "telegram bot token not configured in .mu/config.json" };
|
|
112
|
-
}
|
|
113
|
-
const res = await fetch(`https://api.telegram.org/bot${telegramBotToken}/sendMessage`, {
|
|
114
|
-
method: "POST",
|
|
115
|
-
headers: { "Content-Type": "application/json" },
|
|
116
|
-
body: JSON.stringify({
|
|
117
|
-
chat_id: envelope.channel_conversation_id,
|
|
118
|
-
text: envelope.body,
|
|
119
|
-
}),
|
|
52
|
+
let pipeline = null;
|
|
53
|
+
let drainInterval = null;
|
|
54
|
+
try {
|
|
55
|
+
await runtime.start();
|
|
56
|
+
const operator = opts.operatorRuntime !== undefined
|
|
57
|
+
? opts.operatorRuntime
|
|
58
|
+
: buildMessagingOperatorRuntime({
|
|
59
|
+
repoRoot: opts.repoRoot,
|
|
60
|
+
config: controlPlaneConfig,
|
|
61
|
+
backend: opts.operatorBackend,
|
|
120
62
|
});
|
|
121
|
-
|
|
122
|
-
|
|
63
|
+
pipeline = new ControlPlaneCommandPipeline({ runtime, operator });
|
|
64
|
+
await pipeline.start();
|
|
65
|
+
const outbox = new ControlPlaneOutbox(paths.outboxPath);
|
|
66
|
+
await outbox.load();
|
|
67
|
+
let telegramBotToken = null;
|
|
68
|
+
const adapterMap = new Map();
|
|
69
|
+
for (const d of detected) {
|
|
70
|
+
let adapter;
|
|
71
|
+
switch (d.name) {
|
|
72
|
+
case "slack":
|
|
73
|
+
adapter = new SlackControlPlaneAdapter({
|
|
74
|
+
pipeline,
|
|
75
|
+
outbox,
|
|
76
|
+
signingSecret: d.signingSecret,
|
|
77
|
+
});
|
|
78
|
+
break;
|
|
79
|
+
case "discord":
|
|
80
|
+
adapter = new DiscordControlPlaneAdapter({
|
|
81
|
+
pipeline,
|
|
82
|
+
outbox,
|
|
83
|
+
signingSecret: d.signingSecret,
|
|
84
|
+
});
|
|
85
|
+
break;
|
|
86
|
+
case "telegram":
|
|
87
|
+
adapter = new TelegramControlPlaneAdapter({
|
|
88
|
+
pipeline,
|
|
89
|
+
outbox,
|
|
90
|
+
webhookSecret: d.webhookSecret,
|
|
91
|
+
botUsername: d.botUsername ?? undefined,
|
|
92
|
+
});
|
|
93
|
+
if (d.botToken) {
|
|
94
|
+
telegramBotToken = d.botToken;
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
123
97
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
98
|
+
const route = adapter.spec.route;
|
|
99
|
+
if (adapterMap.has(route)) {
|
|
100
|
+
throw new Error(`duplicate control-plane webhook route: ${route}`);
|
|
101
|
+
}
|
|
102
|
+
adapterMap.set(route, {
|
|
103
|
+
adapter,
|
|
104
|
+
info: {
|
|
105
|
+
name: adapter.spec.channel,
|
|
106
|
+
route,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const deliver = async (record) => {
|
|
111
|
+
const { envelope } = record;
|
|
112
|
+
if (envelope.channel === "telegram") {
|
|
113
|
+
if (!telegramBotToken) {
|
|
114
|
+
return { kind: "retry", error: "telegram bot token not configured in .mu/config.json" };
|
|
115
|
+
}
|
|
116
|
+
const res = await fetch(`https://api.telegram.org/bot${telegramBotToken}/sendMessage`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: { "Content-Type": "application/json" },
|
|
119
|
+
body: JSON.stringify({
|
|
120
|
+
chat_id: envelope.channel_conversation_id,
|
|
121
|
+
text: envelope.body,
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
if (res.ok) {
|
|
125
|
+
return { kind: "delivered" };
|
|
126
|
+
}
|
|
127
|
+
if (res.status === 429 || res.status >= 500) {
|
|
128
|
+
const retryAfter = res.headers.get("retry-after");
|
|
129
|
+
const retryDelayMs = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : undefined;
|
|
130
|
+
return {
|
|
131
|
+
kind: "retry",
|
|
132
|
+
error: `telegram sendMessage ${res.status}: ${await res.text().catch(() => "")}`,
|
|
133
|
+
retryDelayMs: retryDelayMs && Number.isFinite(retryDelayMs) ? retryDelayMs : undefined,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
127
136
|
return {
|
|
128
137
|
kind: "retry",
|
|
129
138
|
error: `telegram sendMessage ${res.status}: ${await res.text().catch(() => "")}`,
|
|
130
|
-
retryDelayMs: retryDelayMs && Number.isFinite(retryDelayMs) ? retryDelayMs : undefined,
|
|
131
139
|
};
|
|
132
140
|
}
|
|
133
|
-
return
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
return undefined;
|
|
142
|
+
};
|
|
143
|
+
const dispatcher = new ControlPlaneOutboxDispatcher({ outbox, deliver });
|
|
144
|
+
drainInterval = setInterval(async () => {
|
|
145
|
+
try {
|
|
146
|
+
await dispatcher.drainDue();
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Swallow errors — the dispatcher already handles retries internally.
|
|
150
|
+
}
|
|
151
|
+
}, 2_000);
|
|
152
|
+
return {
|
|
153
|
+
activeAdapters: [...adapterMap.values()].map((v) => v.info),
|
|
154
|
+
async handleWebhook(path, req) {
|
|
155
|
+
const entry = adapterMap.get(path);
|
|
156
|
+
if (!entry)
|
|
157
|
+
return null;
|
|
158
|
+
const result = await entry.adapter.ingest(req);
|
|
159
|
+
return result.response;
|
|
160
|
+
},
|
|
161
|
+
async stop() {
|
|
162
|
+
if (drainInterval) {
|
|
163
|
+
clearInterval(drainInterval);
|
|
164
|
+
drainInterval = null;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
await pipeline?.stop();
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
await runtime.stop();
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
if (drainInterval) {
|
|
177
|
+
clearInterval(drainInterval);
|
|
178
|
+
drainInterval = null;
|
|
137
179
|
}
|
|
138
|
-
return undefined;
|
|
139
|
-
};
|
|
140
|
-
const dispatcher = new ControlPlaneOutboxDispatcher({ outbox, deliver });
|
|
141
|
-
const drainInterval = setInterval(async () => {
|
|
142
180
|
try {
|
|
143
|
-
await
|
|
181
|
+
await pipeline?.stop();
|
|
144
182
|
}
|
|
145
183
|
catch {
|
|
146
|
-
//
|
|
184
|
+
// Best effort cleanup.
|
|
147
185
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return result.response;
|
|
157
|
-
},
|
|
158
|
-
async stop() {
|
|
159
|
-
clearInterval(drainInterval);
|
|
160
|
-
await pipeline.stop();
|
|
161
|
-
},
|
|
162
|
-
};
|
|
186
|
+
try {
|
|
187
|
+
await runtime.stop();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// Best effort cleanup.
|
|
191
|
+
}
|
|
192
|
+
throw err;
|
|
193
|
+
}
|
|
163
194
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-server",
|
|
3
|
-
"version": "26.2.
|
|
3
|
+
"version": "26.2.40",
|
|
4
4
|
"description": "HTTP API server for mu status, work items, messaging setup, and web UI.",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mu",
|
|
7
|
+
"server",
|
|
8
|
+
"api",
|
|
9
|
+
"web",
|
|
10
|
+
"automation"
|
|
11
|
+
],
|
|
6
12
|
"type": "module",
|
|
7
13
|
"main": "./dist/index.js",
|
|
8
14
|
"types": "./dist/index.d.ts",
|
|
@@ -25,10 +31,10 @@
|
|
|
25
31
|
"start": "bun run dist/cli.js"
|
|
26
32
|
},
|
|
27
33
|
"dependencies": {
|
|
28
|
-
"@femtomc/mu-agent": "26.2.
|
|
29
|
-
"@femtomc/mu-control-plane": "26.2.
|
|
30
|
-
"@femtomc/mu-core": "26.2.
|
|
31
|
-
"@femtomc/mu-forum": "26.2.
|
|
32
|
-
"@femtomc/mu-issue": "26.2.
|
|
34
|
+
"@femtomc/mu-agent": "26.2.40",
|
|
35
|
+
"@femtomc/mu-control-plane": "26.2.40",
|
|
36
|
+
"@femtomc/mu-core": "26.2.40",
|
|
37
|
+
"@femtomc/mu-forum": "26.2.40",
|
|
38
|
+
"@femtomc/mu-issue": "26.2.40"
|
|
33
39
|
}
|
|
34
40
|
}
|