@femtomc/mu-server 26.2.35 → 26.2.37
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 +29 -1
- package/dist/api/events.d.ts +2 -0
- package/dist/api/events.js +45 -0
- package/dist/api/forum.js +2 -2
- package/dist/api/issues.js +5 -5
- package/dist/cli.js +3 -3
- package/dist/config.d.ts +99 -0
- package/dist/config.js +360 -0
- package/dist/control_plane.d.ts +5 -28
- package/dist/control_plane.js +33 -97
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3 -2
- package/dist/server.d.ts +19 -5
- package/dist/server.js +207 -50
- package/package.json +6 -6
- package/public/assets/index-CxkevQNh.js +100 -0
- package/public/index.html +1 -1
- package/public/assets/index-0FGFtKeu.js +0 -85
package/README.md
CHANGED
|
@@ -39,10 +39,38 @@ Bun.serve(server);
|
|
|
39
39
|
{
|
|
40
40
|
"repo_root": "/path/to/repo",
|
|
41
41
|
"open_count": 10,
|
|
42
|
-
"ready_count": 3
|
|
42
|
+
"ready_count": 3,
|
|
43
|
+
"control_plane": {
|
|
44
|
+
"active": true,
|
|
45
|
+
"adapters": ["slack"],
|
|
46
|
+
"routes": [{ "name": "slack", "route": "/webhooks/slack" }]
|
|
47
|
+
}
|
|
43
48
|
}
|
|
44
49
|
```
|
|
45
50
|
|
|
51
|
+
### Config + Control Plane Admin
|
|
52
|
+
|
|
53
|
+
- `GET /api/config` - Read redacted `.mu/config.json` plus presence booleans
|
|
54
|
+
- `POST /api/config` - Apply a partial patch to `.mu/config.json`
|
|
55
|
+
- Body:
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"patch": {
|
|
59
|
+
"control_plane": {
|
|
60
|
+
"adapters": {
|
|
61
|
+
"slack": { "signing_secret": "..." }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
- `POST /api/control-plane/reload` - Re-bootstrap control-plane adapters in-process
|
|
68
|
+
- Re-reads current config from `.mu/config.json` and re-mounts adapters without restarting
|
|
69
|
+
- Body (optional):
|
|
70
|
+
```json
|
|
71
|
+
{ "reason": "mu_setup_apply" }
|
|
72
|
+
```
|
|
73
|
+
|
|
46
74
|
### Issues
|
|
47
75
|
|
|
48
76
|
- `GET /api/issues` - List issues
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export async function eventRoutes(request, context) {
|
|
2
|
+
const url = new URL(request.url);
|
|
3
|
+
const path = url.pathname.replace("/api/events", "") || "/";
|
|
4
|
+
const method = request.method;
|
|
5
|
+
if (method !== "GET") {
|
|
6
|
+
return new Response("Method Not Allowed", { status: 405 });
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const allEvents = await context.eventsStore.read();
|
|
10
|
+
// Tail - GET /api/events/tail?n=50
|
|
11
|
+
if (path === "/tail") {
|
|
12
|
+
const n = Math.min(Math.max(1, parseInt(url.searchParams.get("n") ?? "50", 10) || 50), 500);
|
|
13
|
+
return Response.json(allEvents.slice(-n));
|
|
14
|
+
}
|
|
15
|
+
// Query - GET /api/events?type=...&source=...&since=...&limit=50
|
|
16
|
+
if (path === "/") {
|
|
17
|
+
const typeFilter = url.searchParams.get("type");
|
|
18
|
+
const sourceFilter = url.searchParams.get("source");
|
|
19
|
+
const sinceRaw = url.searchParams.get("since");
|
|
20
|
+
const limit = Math.min(Math.max(1, parseInt(url.searchParams.get("limit") ?? "50", 10) || 50), 500);
|
|
21
|
+
let filtered = allEvents;
|
|
22
|
+
if (typeFilter) {
|
|
23
|
+
filtered = filtered.filter((e) => e.type === typeFilter);
|
|
24
|
+
}
|
|
25
|
+
if (sourceFilter) {
|
|
26
|
+
filtered = filtered.filter((e) => e.source === sourceFilter);
|
|
27
|
+
}
|
|
28
|
+
if (sinceRaw) {
|
|
29
|
+
const sinceMs = parseInt(sinceRaw, 10);
|
|
30
|
+
if (!Number.isNaN(sinceMs)) {
|
|
31
|
+
filtered = filtered.filter((e) => e.ts_ms >= sinceMs);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return Response.json(filtered.slice(-limit));
|
|
35
|
+
}
|
|
36
|
+
return new Response("Not Found", { status: 404 });
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error("Events API error:", error);
|
|
40
|
+
return new Response(JSON.stringify({ error: error instanceof Error ? error.message : "Internal server error" }), {
|
|
41
|
+
status: 500,
|
|
42
|
+
headers: { "Content-Type": "application/json" },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/api/forum.js
CHANGED
|
@@ -21,7 +21,7 @@ export async function forumRoutes(request, context) {
|
|
|
21
21
|
}
|
|
22
22
|
// Post message - POST /api/forum/post
|
|
23
23
|
if (path === "/post" && method === "POST") {
|
|
24
|
-
const body = await request.json();
|
|
24
|
+
const body = (await request.json());
|
|
25
25
|
const { topic, body: messageBody, author } = body;
|
|
26
26
|
if (!topic || !messageBody) {
|
|
27
27
|
return new Response("Topic and body are required", { status: 400 });
|
|
@@ -35,7 +35,7 @@ export async function forumRoutes(request, context) {
|
|
|
35
35
|
console.error("Forum API error:", error);
|
|
36
36
|
return new Response(JSON.stringify({ error: error instanceof Error ? error.message : "Internal server error" }), {
|
|
37
37
|
status: 500,
|
|
38
|
-
headers: { "Content-Type": "application/json" }
|
|
38
|
+
headers: { "Content-Type": "application/json" },
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
}
|
package/dist/api/issues.js
CHANGED
|
@@ -9,7 +9,7 @@ export async function issueRoutes(request, context) {
|
|
|
9
9
|
const tag = url.searchParams.get("tag");
|
|
10
10
|
const issues = await context.issueStore.list({
|
|
11
11
|
status: status,
|
|
12
|
-
tag: tag || undefined
|
|
12
|
+
tag: tag || undefined,
|
|
13
13
|
});
|
|
14
14
|
return Response.json(issues);
|
|
15
15
|
}
|
|
@@ -32,7 +32,7 @@ export async function issueRoutes(request, context) {
|
|
|
32
32
|
}
|
|
33
33
|
// Create issue - POST /api/issues
|
|
34
34
|
if (path === "/" && method === "POST") {
|
|
35
|
-
const body = await request.json();
|
|
35
|
+
const body = (await request.json());
|
|
36
36
|
const { title, body: issueBody, tags, priority } = body;
|
|
37
37
|
if (!title) {
|
|
38
38
|
return new Response("Title is required", { status: 400 });
|
|
@@ -48,7 +48,7 @@ export async function issueRoutes(request, context) {
|
|
|
48
48
|
if (path.startsWith("/") && method === "PATCH") {
|
|
49
49
|
const id = path.slice(1);
|
|
50
50
|
if (id) {
|
|
51
|
-
const body = await request.json();
|
|
51
|
+
const body = (await request.json());
|
|
52
52
|
const issue = await context.issueStore.update(id, body);
|
|
53
53
|
return Response.json(issue);
|
|
54
54
|
}
|
|
@@ -56,7 +56,7 @@ export async function issueRoutes(request, context) {
|
|
|
56
56
|
// Close issue - POST /api/issues/:id/close
|
|
57
57
|
if (path.endsWith("/close") && method === "POST") {
|
|
58
58
|
const id = path.slice(1, -6); // Remove leading / and trailing /close
|
|
59
|
-
const body = await request.json();
|
|
59
|
+
const body = (await request.json());
|
|
60
60
|
const { outcome } = body;
|
|
61
61
|
if (!outcome) {
|
|
62
62
|
return new Response("Outcome is required", { status: 400 });
|
|
@@ -80,7 +80,7 @@ export async function issueRoutes(request, context) {
|
|
|
80
80
|
console.error("Issue API error:", error);
|
|
81
81
|
return new Response(JSON.stringify({ error: error instanceof Error ? error.message : "Internal server error" }), {
|
|
82
82
|
status: 500,
|
|
83
|
-
headers: { "Content-Type": "application/json" }
|
|
83
|
+
headers: { "Content-Type": "application/json" },
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { createServerAsync } from "./server.js";
|
|
3
2
|
import { findRepoRoot } from "@femtomc/mu-core/node";
|
|
4
|
-
|
|
3
|
+
import { createServerAsync } from "./server.js";
|
|
4
|
+
const port = parseInt(Bun.env.PORT || "3000", 10);
|
|
5
5
|
let repoRoot;
|
|
6
6
|
try {
|
|
7
7
|
repoRoot = findRepoRoot();
|
|
8
8
|
}
|
|
9
9
|
catch {
|
|
10
|
-
console.error("Error: Could not find .mu directory. Run 'mu
|
|
10
|
+
console.error("Error: Could not find .mu directory. Run 'mu serve' or 'mu run' once to initialize it.");
|
|
11
11
|
process.exit(1);
|
|
12
12
|
}
|
|
13
13
|
console.log(`Starting mu-server on port ${port}...`);
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export type MuConfig = {
|
|
2
|
+
version: 1;
|
|
3
|
+
control_plane: {
|
|
4
|
+
adapters: {
|
|
5
|
+
slack: {
|
|
6
|
+
signing_secret: string | null;
|
|
7
|
+
};
|
|
8
|
+
discord: {
|
|
9
|
+
signing_secret: string | null;
|
|
10
|
+
};
|
|
11
|
+
telegram: {
|
|
12
|
+
webhook_secret: string | null;
|
|
13
|
+
bot_token: string | null;
|
|
14
|
+
bot_username: string | null;
|
|
15
|
+
};
|
|
16
|
+
gmail: {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
webhook_secret: string | null;
|
|
19
|
+
client_id: string | null;
|
|
20
|
+
client_secret: string | null;
|
|
21
|
+
refresh_token: string | null;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
meta_agent: {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
run_triggers_enabled: boolean;
|
|
27
|
+
provider: string | null;
|
|
28
|
+
model: string | null;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export type MuConfigPatch = {
|
|
33
|
+
control_plane?: {
|
|
34
|
+
adapters?: {
|
|
35
|
+
slack?: {
|
|
36
|
+
signing_secret?: string | null;
|
|
37
|
+
};
|
|
38
|
+
discord?: {
|
|
39
|
+
signing_secret?: string | null;
|
|
40
|
+
};
|
|
41
|
+
telegram?: {
|
|
42
|
+
webhook_secret?: string | null;
|
|
43
|
+
bot_token?: string | null;
|
|
44
|
+
bot_username?: string | null;
|
|
45
|
+
};
|
|
46
|
+
gmail?: {
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
webhook_secret?: string | null;
|
|
49
|
+
client_id?: string | null;
|
|
50
|
+
client_secret?: string | null;
|
|
51
|
+
refresh_token?: string | null;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
meta_agent?: {
|
|
55
|
+
enabled?: boolean;
|
|
56
|
+
run_triggers_enabled?: boolean;
|
|
57
|
+
provider?: string | null;
|
|
58
|
+
model?: string | null;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
export type MuConfigPresence = {
|
|
63
|
+
control_plane: {
|
|
64
|
+
adapters: {
|
|
65
|
+
slack: {
|
|
66
|
+
signing_secret: boolean;
|
|
67
|
+
};
|
|
68
|
+
discord: {
|
|
69
|
+
signing_secret: boolean;
|
|
70
|
+
};
|
|
71
|
+
telegram: {
|
|
72
|
+
webhook_secret: boolean;
|
|
73
|
+
bot_token: boolean;
|
|
74
|
+
bot_username: boolean;
|
|
75
|
+
};
|
|
76
|
+
gmail: {
|
|
77
|
+
enabled: boolean;
|
|
78
|
+
webhook_secret: boolean;
|
|
79
|
+
client_id: boolean;
|
|
80
|
+
client_secret: boolean;
|
|
81
|
+
refresh_token: boolean;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
meta_agent: {
|
|
85
|
+
enabled: boolean;
|
|
86
|
+
run_triggers_enabled: boolean;
|
|
87
|
+
provider: boolean;
|
|
88
|
+
model: boolean;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
export declare const DEFAULT_MU_CONFIG: MuConfig;
|
|
93
|
+
export declare function normalizeMuConfig(input: unknown): MuConfig;
|
|
94
|
+
export declare function applyMuConfigPatch(base: MuConfig, patchInput: unknown): MuConfig;
|
|
95
|
+
export declare function getMuConfigPath(repoRoot: string): string;
|
|
96
|
+
export declare function readMuConfigFile(repoRoot: string): Promise<MuConfig>;
|
|
97
|
+
export declare function writeMuConfigFile(repoRoot: string, config: MuConfig): Promise<string>;
|
|
98
|
+
export declare function redactMuConfigSecrets(config: MuConfig): MuConfig;
|
|
99
|
+
export declare function muConfigPresence(config: MuConfig): MuConfigPresence;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { chmod, mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
export const DEFAULT_MU_CONFIG = {
|
|
4
|
+
version: 1,
|
|
5
|
+
control_plane: {
|
|
6
|
+
adapters: {
|
|
7
|
+
slack: {
|
|
8
|
+
signing_secret: null,
|
|
9
|
+
},
|
|
10
|
+
discord: {
|
|
11
|
+
signing_secret: null,
|
|
12
|
+
},
|
|
13
|
+
telegram: {
|
|
14
|
+
webhook_secret: null,
|
|
15
|
+
bot_token: null,
|
|
16
|
+
bot_username: null,
|
|
17
|
+
},
|
|
18
|
+
gmail: {
|
|
19
|
+
enabled: false,
|
|
20
|
+
webhook_secret: null,
|
|
21
|
+
client_id: null,
|
|
22
|
+
client_secret: null,
|
|
23
|
+
refresh_token: null,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
meta_agent: {
|
|
27
|
+
enabled: true,
|
|
28
|
+
run_triggers_enabled: true,
|
|
29
|
+
provider: null,
|
|
30
|
+
model: null,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
function cloneDefault() {
|
|
35
|
+
return JSON.parse(JSON.stringify(DEFAULT_MU_CONFIG));
|
|
36
|
+
}
|
|
37
|
+
function asRecord(value) {
|
|
38
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function normalizeNullableString(value) {
|
|
44
|
+
if (value == null)
|
|
45
|
+
return null;
|
|
46
|
+
if (typeof value !== "string")
|
|
47
|
+
return null;
|
|
48
|
+
const trimmed = value.trim();
|
|
49
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
50
|
+
}
|
|
51
|
+
function normalizeBoolean(value, fallback) {
|
|
52
|
+
if (typeof value === "boolean")
|
|
53
|
+
return value;
|
|
54
|
+
if (typeof value === "string") {
|
|
55
|
+
const normalized = value.trim().toLowerCase();
|
|
56
|
+
if (["1", "true", "yes", "on", "enabled"].includes(normalized))
|
|
57
|
+
return true;
|
|
58
|
+
if (["0", "false", "no", "off", "disabled"].includes(normalized))
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return fallback;
|
|
62
|
+
}
|
|
63
|
+
export function normalizeMuConfig(input) {
|
|
64
|
+
const next = cloneDefault();
|
|
65
|
+
const root = asRecord(input);
|
|
66
|
+
if (!root)
|
|
67
|
+
return next;
|
|
68
|
+
const controlPlane = asRecord(root.control_plane);
|
|
69
|
+
if (!controlPlane)
|
|
70
|
+
return next;
|
|
71
|
+
const adapters = asRecord(controlPlane.adapters);
|
|
72
|
+
if (adapters) {
|
|
73
|
+
const slack = asRecord(adapters.slack);
|
|
74
|
+
if (slack && "signing_secret" in slack) {
|
|
75
|
+
next.control_plane.adapters.slack.signing_secret = normalizeNullableString(slack.signing_secret);
|
|
76
|
+
}
|
|
77
|
+
const discord = asRecord(adapters.discord);
|
|
78
|
+
if (discord && "signing_secret" in discord) {
|
|
79
|
+
next.control_plane.adapters.discord.signing_secret = normalizeNullableString(discord.signing_secret);
|
|
80
|
+
}
|
|
81
|
+
const telegram = asRecord(adapters.telegram);
|
|
82
|
+
if (telegram) {
|
|
83
|
+
if ("webhook_secret" in telegram) {
|
|
84
|
+
next.control_plane.adapters.telegram.webhook_secret = normalizeNullableString(telegram.webhook_secret);
|
|
85
|
+
}
|
|
86
|
+
if ("bot_token" in telegram) {
|
|
87
|
+
next.control_plane.adapters.telegram.bot_token = normalizeNullableString(telegram.bot_token);
|
|
88
|
+
}
|
|
89
|
+
if ("bot_username" in telegram) {
|
|
90
|
+
next.control_plane.adapters.telegram.bot_username = normalizeNullableString(telegram.bot_username);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const gmail = asRecord(adapters.gmail);
|
|
94
|
+
if (gmail) {
|
|
95
|
+
if ("enabled" in gmail) {
|
|
96
|
+
next.control_plane.adapters.gmail.enabled = normalizeBoolean(gmail.enabled, next.control_plane.adapters.gmail.enabled);
|
|
97
|
+
}
|
|
98
|
+
if ("webhook_secret" in gmail) {
|
|
99
|
+
next.control_plane.adapters.gmail.webhook_secret = normalizeNullableString(gmail.webhook_secret);
|
|
100
|
+
}
|
|
101
|
+
if ("client_id" in gmail) {
|
|
102
|
+
next.control_plane.adapters.gmail.client_id = normalizeNullableString(gmail.client_id);
|
|
103
|
+
}
|
|
104
|
+
if ("client_secret" in gmail) {
|
|
105
|
+
next.control_plane.adapters.gmail.client_secret = normalizeNullableString(gmail.client_secret);
|
|
106
|
+
}
|
|
107
|
+
if ("refresh_token" in gmail) {
|
|
108
|
+
next.control_plane.adapters.gmail.refresh_token = normalizeNullableString(gmail.refresh_token);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const metaAgent = asRecord(controlPlane.meta_agent);
|
|
113
|
+
if (metaAgent) {
|
|
114
|
+
if ("enabled" in metaAgent) {
|
|
115
|
+
next.control_plane.meta_agent.enabled = normalizeBoolean(metaAgent.enabled, next.control_plane.meta_agent.enabled);
|
|
116
|
+
}
|
|
117
|
+
if ("run_triggers_enabled" in metaAgent) {
|
|
118
|
+
next.control_plane.meta_agent.run_triggers_enabled = normalizeBoolean(metaAgent.run_triggers_enabled, next.control_plane.meta_agent.run_triggers_enabled);
|
|
119
|
+
}
|
|
120
|
+
if ("provider" in metaAgent) {
|
|
121
|
+
next.control_plane.meta_agent.provider = normalizeNullableString(metaAgent.provider);
|
|
122
|
+
}
|
|
123
|
+
if ("model" in metaAgent) {
|
|
124
|
+
next.control_plane.meta_agent.model = normalizeNullableString(metaAgent.model);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return next;
|
|
128
|
+
}
|
|
129
|
+
function normalizeMuConfigPatch(input) {
|
|
130
|
+
const root = asRecord(input);
|
|
131
|
+
if (!root)
|
|
132
|
+
return {};
|
|
133
|
+
const patch = {};
|
|
134
|
+
const controlPlane = asRecord(root.control_plane);
|
|
135
|
+
if (!controlPlane)
|
|
136
|
+
return patch;
|
|
137
|
+
patch.control_plane = {};
|
|
138
|
+
const adapters = asRecord(controlPlane.adapters);
|
|
139
|
+
if (adapters) {
|
|
140
|
+
patch.control_plane.adapters = {};
|
|
141
|
+
const slack = asRecord(adapters.slack);
|
|
142
|
+
if (slack && "signing_secret" in slack) {
|
|
143
|
+
patch.control_plane.adapters.slack = {
|
|
144
|
+
signing_secret: normalizeNullableString(slack.signing_secret),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const discord = asRecord(adapters.discord);
|
|
148
|
+
if (discord && "signing_secret" in discord) {
|
|
149
|
+
patch.control_plane.adapters.discord = {
|
|
150
|
+
signing_secret: normalizeNullableString(discord.signing_secret),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const telegram = asRecord(adapters.telegram);
|
|
154
|
+
if (telegram) {
|
|
155
|
+
const telegramPatch = {};
|
|
156
|
+
if ("webhook_secret" in telegram) {
|
|
157
|
+
telegramPatch.webhook_secret = normalizeNullableString(telegram.webhook_secret);
|
|
158
|
+
}
|
|
159
|
+
if ("bot_token" in telegram) {
|
|
160
|
+
telegramPatch.bot_token = normalizeNullableString(telegram.bot_token);
|
|
161
|
+
}
|
|
162
|
+
if ("bot_username" in telegram) {
|
|
163
|
+
telegramPatch.bot_username = normalizeNullableString(telegram.bot_username);
|
|
164
|
+
}
|
|
165
|
+
if (Object.keys(telegramPatch).length > 0) {
|
|
166
|
+
patch.control_plane.adapters.telegram = telegramPatch;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const gmail = asRecord(adapters.gmail);
|
|
170
|
+
if (gmail) {
|
|
171
|
+
const gmailPatch = {};
|
|
172
|
+
if ("enabled" in gmail) {
|
|
173
|
+
gmailPatch.enabled = normalizeBoolean(gmail.enabled, DEFAULT_MU_CONFIG.control_plane.adapters.gmail.enabled);
|
|
174
|
+
}
|
|
175
|
+
if ("webhook_secret" in gmail) {
|
|
176
|
+
gmailPatch.webhook_secret = normalizeNullableString(gmail.webhook_secret);
|
|
177
|
+
}
|
|
178
|
+
if ("client_id" in gmail) {
|
|
179
|
+
gmailPatch.client_id = normalizeNullableString(gmail.client_id);
|
|
180
|
+
}
|
|
181
|
+
if ("client_secret" in gmail) {
|
|
182
|
+
gmailPatch.client_secret = normalizeNullableString(gmail.client_secret);
|
|
183
|
+
}
|
|
184
|
+
if ("refresh_token" in gmail) {
|
|
185
|
+
gmailPatch.refresh_token = normalizeNullableString(gmail.refresh_token);
|
|
186
|
+
}
|
|
187
|
+
if (Object.keys(gmailPatch).length > 0) {
|
|
188
|
+
patch.control_plane.adapters.gmail = gmailPatch;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const metaAgent = asRecord(controlPlane.meta_agent);
|
|
193
|
+
if (metaAgent) {
|
|
194
|
+
patch.control_plane.meta_agent = {};
|
|
195
|
+
if ("enabled" in metaAgent) {
|
|
196
|
+
patch.control_plane.meta_agent.enabled = normalizeBoolean(metaAgent.enabled, DEFAULT_MU_CONFIG.control_plane.meta_agent.enabled);
|
|
197
|
+
}
|
|
198
|
+
if ("run_triggers_enabled" in metaAgent) {
|
|
199
|
+
patch.control_plane.meta_agent.run_triggers_enabled = normalizeBoolean(metaAgent.run_triggers_enabled, DEFAULT_MU_CONFIG.control_plane.meta_agent.run_triggers_enabled);
|
|
200
|
+
}
|
|
201
|
+
if ("provider" in metaAgent) {
|
|
202
|
+
patch.control_plane.meta_agent.provider = normalizeNullableString(metaAgent.provider);
|
|
203
|
+
}
|
|
204
|
+
if ("model" in metaAgent) {
|
|
205
|
+
patch.control_plane.meta_agent.model = normalizeNullableString(metaAgent.model);
|
|
206
|
+
}
|
|
207
|
+
if (Object.keys(patch.control_plane.meta_agent).length === 0) {
|
|
208
|
+
delete patch.control_plane.meta_agent;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (patch.control_plane.adapters && Object.keys(patch.control_plane.adapters).length === 0) {
|
|
212
|
+
delete patch.control_plane.adapters;
|
|
213
|
+
}
|
|
214
|
+
if (Object.keys(patch.control_plane).length === 0) {
|
|
215
|
+
delete patch.control_plane;
|
|
216
|
+
}
|
|
217
|
+
return patch;
|
|
218
|
+
}
|
|
219
|
+
export function applyMuConfigPatch(base, patchInput) {
|
|
220
|
+
const patch = normalizeMuConfigPatch(patchInput);
|
|
221
|
+
const next = normalizeMuConfig(base);
|
|
222
|
+
if (!patch.control_plane) {
|
|
223
|
+
return next;
|
|
224
|
+
}
|
|
225
|
+
const adapters = patch.control_plane.adapters;
|
|
226
|
+
if (adapters) {
|
|
227
|
+
if (adapters.slack && "signing_secret" in adapters.slack) {
|
|
228
|
+
next.control_plane.adapters.slack.signing_secret = adapters.slack.signing_secret ?? null;
|
|
229
|
+
}
|
|
230
|
+
if (adapters.discord && "signing_secret" in adapters.discord) {
|
|
231
|
+
next.control_plane.adapters.discord.signing_secret = adapters.discord.signing_secret ?? null;
|
|
232
|
+
}
|
|
233
|
+
if (adapters.telegram) {
|
|
234
|
+
if ("webhook_secret" in adapters.telegram) {
|
|
235
|
+
next.control_plane.adapters.telegram.webhook_secret = adapters.telegram.webhook_secret ?? null;
|
|
236
|
+
}
|
|
237
|
+
if ("bot_token" in adapters.telegram) {
|
|
238
|
+
next.control_plane.adapters.telegram.bot_token = adapters.telegram.bot_token ?? null;
|
|
239
|
+
}
|
|
240
|
+
if ("bot_username" in adapters.telegram) {
|
|
241
|
+
next.control_plane.adapters.telegram.bot_username = adapters.telegram.bot_username ?? null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (adapters.gmail) {
|
|
245
|
+
if ("enabled" in adapters.gmail && typeof adapters.gmail.enabled === "boolean") {
|
|
246
|
+
next.control_plane.adapters.gmail.enabled = adapters.gmail.enabled;
|
|
247
|
+
}
|
|
248
|
+
if ("webhook_secret" in adapters.gmail) {
|
|
249
|
+
next.control_plane.adapters.gmail.webhook_secret = adapters.gmail.webhook_secret ?? null;
|
|
250
|
+
}
|
|
251
|
+
if ("client_id" in adapters.gmail) {
|
|
252
|
+
next.control_plane.adapters.gmail.client_id = adapters.gmail.client_id ?? null;
|
|
253
|
+
}
|
|
254
|
+
if ("client_secret" in adapters.gmail) {
|
|
255
|
+
next.control_plane.adapters.gmail.client_secret = adapters.gmail.client_secret ?? null;
|
|
256
|
+
}
|
|
257
|
+
if ("refresh_token" in adapters.gmail) {
|
|
258
|
+
next.control_plane.adapters.gmail.refresh_token = adapters.gmail.refresh_token ?? null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const metaAgent = patch.control_plane.meta_agent;
|
|
263
|
+
if (metaAgent) {
|
|
264
|
+
if ("enabled" in metaAgent && typeof metaAgent.enabled === "boolean") {
|
|
265
|
+
next.control_plane.meta_agent.enabled = metaAgent.enabled;
|
|
266
|
+
}
|
|
267
|
+
if ("run_triggers_enabled" in metaAgent && typeof metaAgent.run_triggers_enabled === "boolean") {
|
|
268
|
+
next.control_plane.meta_agent.run_triggers_enabled = metaAgent.run_triggers_enabled;
|
|
269
|
+
}
|
|
270
|
+
if ("provider" in metaAgent) {
|
|
271
|
+
next.control_plane.meta_agent.provider = metaAgent.provider ?? null;
|
|
272
|
+
}
|
|
273
|
+
if ("model" in metaAgent) {
|
|
274
|
+
next.control_plane.meta_agent.model = metaAgent.model ?? null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return next;
|
|
278
|
+
}
|
|
279
|
+
export function getMuConfigPath(repoRoot) {
|
|
280
|
+
return join(repoRoot, ".mu", "config.json");
|
|
281
|
+
}
|
|
282
|
+
export async function readMuConfigFile(repoRoot) {
|
|
283
|
+
const path = getMuConfigPath(repoRoot);
|
|
284
|
+
try {
|
|
285
|
+
const raw = await Bun.file(path).text();
|
|
286
|
+
const parsed = JSON.parse(raw);
|
|
287
|
+
return normalizeMuConfig(parsed);
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
const code = err?.code;
|
|
291
|
+
if (code === "ENOENT") {
|
|
292
|
+
return cloneDefault();
|
|
293
|
+
}
|
|
294
|
+
throw err;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
export async function writeMuConfigFile(repoRoot, config) {
|
|
298
|
+
const path = getMuConfigPath(repoRoot);
|
|
299
|
+
await mkdir(dirname(path), { recursive: true });
|
|
300
|
+
await Bun.write(path, `${JSON.stringify(normalizeMuConfig(config), null, 2)}\n`);
|
|
301
|
+
try {
|
|
302
|
+
await chmod(path, 0o600);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
// Best effort only.
|
|
306
|
+
}
|
|
307
|
+
return path;
|
|
308
|
+
}
|
|
309
|
+
function redacted(value) {
|
|
310
|
+
if (!value)
|
|
311
|
+
return null;
|
|
312
|
+
return "***";
|
|
313
|
+
}
|
|
314
|
+
export function redactMuConfigSecrets(config) {
|
|
315
|
+
const next = normalizeMuConfig(config);
|
|
316
|
+
next.control_plane.adapters.slack.signing_secret = redacted(next.control_plane.adapters.slack.signing_secret);
|
|
317
|
+
next.control_plane.adapters.discord.signing_secret = redacted(next.control_plane.adapters.discord.signing_secret);
|
|
318
|
+
next.control_plane.adapters.telegram.webhook_secret = redacted(next.control_plane.adapters.telegram.webhook_secret);
|
|
319
|
+
next.control_plane.adapters.telegram.bot_token = redacted(next.control_plane.adapters.telegram.bot_token);
|
|
320
|
+
next.control_plane.adapters.gmail.webhook_secret = redacted(next.control_plane.adapters.gmail.webhook_secret);
|
|
321
|
+
next.control_plane.adapters.gmail.client_id = redacted(next.control_plane.adapters.gmail.client_id);
|
|
322
|
+
next.control_plane.adapters.gmail.client_secret = redacted(next.control_plane.adapters.gmail.client_secret);
|
|
323
|
+
next.control_plane.adapters.gmail.refresh_token = redacted(next.control_plane.adapters.gmail.refresh_token);
|
|
324
|
+
return next;
|
|
325
|
+
}
|
|
326
|
+
function isPresent(value) {
|
|
327
|
+
return typeof value === "string" && value.length > 0;
|
|
328
|
+
}
|
|
329
|
+
export function muConfigPresence(config) {
|
|
330
|
+
return {
|
|
331
|
+
control_plane: {
|
|
332
|
+
adapters: {
|
|
333
|
+
slack: {
|
|
334
|
+
signing_secret: isPresent(config.control_plane.adapters.slack.signing_secret),
|
|
335
|
+
},
|
|
336
|
+
discord: {
|
|
337
|
+
signing_secret: isPresent(config.control_plane.adapters.discord.signing_secret),
|
|
338
|
+
},
|
|
339
|
+
telegram: {
|
|
340
|
+
webhook_secret: isPresent(config.control_plane.adapters.telegram.webhook_secret),
|
|
341
|
+
bot_token: isPresent(config.control_plane.adapters.telegram.bot_token),
|
|
342
|
+
bot_username: isPresent(config.control_plane.adapters.telegram.bot_username),
|
|
343
|
+
},
|
|
344
|
+
gmail: {
|
|
345
|
+
enabled: config.control_plane.adapters.gmail.enabled,
|
|
346
|
+
webhook_secret: isPresent(config.control_plane.adapters.gmail.webhook_secret),
|
|
347
|
+
client_id: isPresent(config.control_plane.adapters.gmail.client_id),
|
|
348
|
+
client_secret: isPresent(config.control_plane.adapters.gmail.client_secret),
|
|
349
|
+
refresh_token: isPresent(config.control_plane.adapters.gmail.refresh_token),
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
meta_agent: {
|
|
353
|
+
enabled: config.control_plane.meta_agent.enabled,
|
|
354
|
+
run_triggers_enabled: config.control_plane.meta_agent.run_triggers_enabled,
|
|
355
|
+
provider: isPresent(config.control_plane.meta_agent.provider),
|
|
356
|
+
model: isPresent(config.control_plane.meta_agent.model),
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
}
|
package/dist/control_plane.d.ts
CHANGED
|
@@ -1,29 +1,6 @@
|
|
|
1
|
-
import { type Channel } from "@femtomc/mu-control-plane";
|
|
2
1
|
import { type MessagingMetaAgentBackend, MessagingMetaAgentRuntime } from "@femtomc/mu-agent";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
readonly signingSecret: "MU_SLACK_SIGNING_SECRET";
|
|
6
|
-
};
|
|
7
|
-
readonly discord: {
|
|
8
|
-
readonly signingSecret: "MU_DISCORD_SIGNING_SECRET";
|
|
9
|
-
};
|
|
10
|
-
readonly telegram: {
|
|
11
|
-
readonly webhookSecret: "MU_TELEGRAM_WEBHOOK_SECRET";
|
|
12
|
-
readonly botToken: "MU_TELEGRAM_BOT_TOKEN";
|
|
13
|
-
readonly botUsername: "MU_TELEGRAM_BOT_USERNAME";
|
|
14
|
-
readonly tenantId: "MU_TELEGRAM_TENANT_ID";
|
|
15
|
-
};
|
|
16
|
-
readonly metaAgent: {
|
|
17
|
-
readonly enabled: "MU_META_AGENT_ENABLED";
|
|
18
|
-
readonly enabledChannels: "MU_META_AGENT_ENABLED_CHANNELS";
|
|
19
|
-
readonly runTriggersEnabled: "MU_META_AGENT_RUN_TRIGGERS_ENABLED";
|
|
20
|
-
readonly provider: "MU_META_AGENT_PROVIDER";
|
|
21
|
-
readonly model: "MU_META_AGENT_MODEL";
|
|
22
|
-
readonly thinking: "MU_META_AGENT_THINKING";
|
|
23
|
-
readonly systemPrompt: "MU_META_AGENT_SYSTEM_PROMPT";
|
|
24
|
-
readonly timeoutMs: "MU_META_AGENT_TIMEOUT_MS";
|
|
25
|
-
};
|
|
26
|
-
};
|
|
2
|
+
import { type Channel } from "@femtomc/mu-control-plane";
|
|
3
|
+
import { type MuConfig } from "./config.js";
|
|
27
4
|
export type ActiveAdapter = {
|
|
28
5
|
name: Channel;
|
|
29
6
|
route: string;
|
|
@@ -33,6 +10,7 @@ export type ControlPlaneHandle = {
|
|
|
33
10
|
handleWebhook(path: string, req: Request): Promise<Response | null>;
|
|
34
11
|
stop(): Promise<void>;
|
|
35
12
|
};
|
|
13
|
+
export type ControlPlaneConfig = MuConfig["control_plane"];
|
|
36
14
|
type DetectedAdapter = {
|
|
37
15
|
name: "slack";
|
|
38
16
|
signingSecret: string;
|
|
@@ -44,12 +22,11 @@ type DetectedAdapter = {
|
|
|
44
22
|
webhookSecret: string;
|
|
45
23
|
botToken: string | null;
|
|
46
24
|
botUsername: string | null;
|
|
47
|
-
tenantId: string | null;
|
|
48
25
|
};
|
|
49
|
-
export declare function detectAdapters(
|
|
26
|
+
export declare function detectAdapters(config: ControlPlaneConfig): DetectedAdapter[];
|
|
50
27
|
export type BootstrapControlPlaneOpts = {
|
|
51
28
|
repoRoot: string;
|
|
52
|
-
|
|
29
|
+
config?: ControlPlaneConfig;
|
|
53
30
|
metaAgentRuntime?: MessagingMetaAgentRuntime | null;
|
|
54
31
|
metaAgentBackend?: MessagingMetaAgentBackend;
|
|
55
32
|
};
|