@femtomc/mu-agent 26.2.55 → 26.2.57
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 +10 -10
- package/dist/extensions/branding.d.ts.map +1 -1
- package/dist/extensions/branding.js +6 -3
- package/dist/extensions/cron.d.ts +4 -0
- package/dist/extensions/cron.d.ts.map +1 -0
- package/dist/extensions/cron.js +187 -0
- package/dist/extensions/event-log.d.ts +2 -2
- package/dist/extensions/event-log.d.ts.map +1 -1
- package/dist/extensions/event-log.js +7 -4
- package/dist/extensions/index.d.ts +1 -0
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +2 -0
- package/dist/extensions/messaging-setup.d.ts +1 -1
- package/dist/extensions/messaging-setup.d.ts.map +1 -1
- package/dist/extensions/messaging-setup.js +111 -31
- package/dist/extensions/mu-command-dispatcher.d.ts +11 -0
- package/dist/extensions/mu-command-dispatcher.d.ts.map +1 -0
- package/dist/extensions/mu-command-dispatcher.js +143 -0
- package/dist/extensions/server-tools.d.ts.map +1 -1
- package/dist/extensions/server-tools.js +39 -20
- package/dist/extensions/shared.d.ts +38 -5
- package/dist/extensions/shared.d.ts.map +1 -1
- package/dist/extensions/shared.js +23 -1
- package/package.json +2 -2
- package/prompts/roles/operator.md +1 -0
package/README.md
CHANGED
|
@@ -54,16 +54,16 @@ Current stack:
|
|
|
54
54
|
|
|
55
55
|
## Slash commands (operator-facing)
|
|
56
56
|
|
|
57
|
-
- `/mu
|
|
58
|
-
- `/mu
|
|
59
|
-
- `/mu
|
|
60
|
-
- `/mu
|
|
61
|
-
- `/mu
|
|
62
|
-
- `/mu
|
|
63
|
-
- `/mu
|
|
64
|
-
- `/mu
|
|
65
|
-
- `/mu
|
|
66
|
-
- `/mu
|
|
57
|
+
- `/mu status` — concise server status
|
|
58
|
+
- `/mu control` — active control-plane adapters and webhook routes
|
|
59
|
+
- `/mu setup` — adapter preflight
|
|
60
|
+
- `/mu setup plan <adapter>` — actionable wiring plan
|
|
61
|
+
- `/mu setup apply <adapter>` — guided config apply + control-plane reload
|
|
62
|
+
- `/mu setup verify [adapter]` — runtime verification for mounted routes
|
|
63
|
+
- `/mu setup <adapter>` — sends adapter setup brief to mu agent (`--no-agent` prints local guide)
|
|
64
|
+
- `/mu events [n]` / `/mu events tail [n]` — event log tail
|
|
65
|
+
- `/mu events watch on|off` — toggle event watch widget
|
|
66
|
+
- `/mu brand on|off|toggle` — enable/disable UI branding
|
|
67
67
|
|
|
68
68
|
## Tools (agent/operator-facing)
|
|
69
69
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branding.d.ts","sourceRoot":"","sources":["../../src/extensions/branding.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"branding.d.ts","sourceRoot":"","sources":["../../src/extensions/branding.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AAgDpF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,YAAY,QA8NjD;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - Lightweight periodic status refresh (open/ready/control-plane)
|
|
9
9
|
*/
|
|
10
10
|
import { basename } from "node:path";
|
|
11
|
+
import { registerMuSubcommand } from "./mu-command-dispatcher.js";
|
|
11
12
|
import { fetchMuStatus, muServerUrl } from "./shared.js";
|
|
12
13
|
const EMPTY_SNAPSHOT = {
|
|
13
14
|
openCount: 0,
|
|
@@ -61,7 +62,7 @@ export function brandingExtension(pi) {
|
|
|
61
62
|
: snapshot.controlPlaneActive
|
|
62
63
|
? theme.fg("success", `cp ${snapshot.adapters.join(",") || "on"}`)
|
|
63
64
|
: theme.fg("muted", "cp off");
|
|
64
|
-
const line1 = `${theme.fg("accent", "μ")}${theme.fg("dim", " quick actions")}: ${theme.fg("muted", "/mu
|
|
65
|
+
const line1 = `${theme.fg("accent", "μ")}${theme.fg("dim", " quick actions")}: ${theme.fg("muted", "/mu status /mu control /mu setup /mu events")}`;
|
|
65
66
|
const line2 = `${theme.fg("dim", `open ${snapshot.openCount} · ready ${snapshot.readyCount}`)} · ${cpState}`;
|
|
66
67
|
return [truncateToWidth(line1, width), truncateToWidth(line2, width)];
|
|
67
68
|
},
|
|
@@ -220,8 +221,10 @@ export function brandingExtension(pi) {
|
|
|
220
221
|
footerRequestRender = null;
|
|
221
222
|
activeCtx = null;
|
|
222
223
|
});
|
|
223
|
-
pi
|
|
224
|
-
|
|
224
|
+
registerMuSubcommand(pi, {
|
|
225
|
+
subcommand: "brand",
|
|
226
|
+
summary: "Toggle mu TUI branding",
|
|
227
|
+
usage: "/mu brand [on|off|toggle]",
|
|
225
228
|
handler: async (args, ctx) => {
|
|
226
229
|
const mode = args.trim().toLowerCase();
|
|
227
230
|
if (mode === "on") {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron.d.ts","sourceRoot":"","sources":["../../src/extensions/cron.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAiBlE,wBAAgB,aAAa,CAAC,EAAE,EAAE,YAAY,QAoK7C;AAED,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { clampInt, fetchMuJson, textResult, toJsonText } from "./shared.js";
|
|
4
|
+
function trimOrNull(value) {
|
|
5
|
+
if (value == null)
|
|
6
|
+
return null;
|
|
7
|
+
const trimmed = value.trim();
|
|
8
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
9
|
+
}
|
|
10
|
+
function normalizedNumber(value) {
|
|
11
|
+
if (value == null || !Number.isFinite(value)) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return Math.trunc(value);
|
|
15
|
+
}
|
|
16
|
+
export function cronExtension(pi) {
|
|
17
|
+
const Params = Type.Object({
|
|
18
|
+
action: StringEnum(["status", "list", "get", "create", "update", "delete", "trigger", "enable", "disable"]),
|
|
19
|
+
program_id: Type.Optional(Type.String({ description: "Cron program ID" })),
|
|
20
|
+
title: Type.Optional(Type.String({ description: "Program title" })),
|
|
21
|
+
target_kind: Type.Optional(Type.String({ description: "Target kind (run|activity)" })),
|
|
22
|
+
run_job_id: Type.Optional(Type.String({ description: "Run job ID target" })),
|
|
23
|
+
run_root_issue_id: Type.Optional(Type.String({ description: "Run root issue ID target" })),
|
|
24
|
+
activity_id: Type.Optional(Type.String({ description: "Activity ID target" })),
|
|
25
|
+
schedule_kind: Type.Optional(Type.String({ description: "Schedule kind (at|every|cron)" })),
|
|
26
|
+
at_ms: Type.Optional(Type.Number({ description: "One-shot timestamp in epoch ms" })),
|
|
27
|
+
at: Type.Optional(Type.String({ description: "One-shot timestamp (ISO-8601)" })),
|
|
28
|
+
every_ms: Type.Optional(Type.Number({ description: "Fixed interval in ms" })),
|
|
29
|
+
anchor_ms: Type.Optional(Type.Number({ description: "Anchor timestamp for every schedules" })),
|
|
30
|
+
expr: Type.Optional(Type.String({ description: "Cron expression (5-field)" })),
|
|
31
|
+
tz: Type.Optional(Type.String({ description: "Optional IANA timezone for cron expressions" })),
|
|
32
|
+
reason: Type.Optional(Type.String({ description: "Execution reason" })),
|
|
33
|
+
enabled: Type.Optional(Type.Boolean({ description: "Enabled state" })),
|
|
34
|
+
schedule_filter: Type.Optional(Type.String({ description: "Filter list by schedule kind" })),
|
|
35
|
+
limit: Type.Optional(Type.Number({ description: "Max returned items for list" })),
|
|
36
|
+
});
|
|
37
|
+
pi.registerTool({
|
|
38
|
+
name: "mu_cron",
|
|
39
|
+
label: "Cron",
|
|
40
|
+
description: "Manage persistent cron programs. Actions: status, list, get, create, update, delete, trigger, enable, disable.",
|
|
41
|
+
parameters: Params,
|
|
42
|
+
async execute(_toolCallId, params) {
|
|
43
|
+
switch (params.action) {
|
|
44
|
+
case "status": {
|
|
45
|
+
const payload = await fetchMuJson("/api/cron/status");
|
|
46
|
+
return textResult(toJsonText(payload), { action: "status" });
|
|
47
|
+
}
|
|
48
|
+
case "list": {
|
|
49
|
+
const query = new URLSearchParams();
|
|
50
|
+
const targetKind = trimOrNull(params.target_kind);
|
|
51
|
+
if (targetKind) {
|
|
52
|
+
query.set("target_kind", targetKind);
|
|
53
|
+
}
|
|
54
|
+
if (typeof params.enabled === "boolean") {
|
|
55
|
+
query.set("enabled", params.enabled ? "true" : "false");
|
|
56
|
+
}
|
|
57
|
+
const scheduleFilter = trimOrNull(params.schedule_filter) ?? trimOrNull(params.schedule_kind);
|
|
58
|
+
if (scheduleFilter) {
|
|
59
|
+
query.set("schedule_kind", scheduleFilter);
|
|
60
|
+
}
|
|
61
|
+
query.set("limit", String(clampInt(params.limit, 50, 1, 500)));
|
|
62
|
+
const payload = await fetchMuJson(`/api/cron?${query.toString()}`);
|
|
63
|
+
return textResult(toJsonText(payload), {
|
|
64
|
+
action: "list",
|
|
65
|
+
targetKind,
|
|
66
|
+
enabled: params.enabled,
|
|
67
|
+
scheduleFilter,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
case "get": {
|
|
71
|
+
const programId = trimOrNull(params.program_id);
|
|
72
|
+
if (!programId)
|
|
73
|
+
return textResult("get requires program_id");
|
|
74
|
+
const payload = await fetchMuJson(`/api/cron/${encodeURIComponent(programId)}`);
|
|
75
|
+
return textResult(toJsonText(payload), { action: "get", programId });
|
|
76
|
+
}
|
|
77
|
+
case "create": {
|
|
78
|
+
const title = trimOrNull(params.title);
|
|
79
|
+
const targetKind = trimOrNull(params.target_kind);
|
|
80
|
+
const scheduleKind = trimOrNull(params.schedule_kind);
|
|
81
|
+
if (!title)
|
|
82
|
+
return textResult("create requires title");
|
|
83
|
+
if (!targetKind)
|
|
84
|
+
return textResult("create requires target_kind (run|activity)");
|
|
85
|
+
if (!scheduleKind)
|
|
86
|
+
return textResult("create requires schedule_kind (at|every|cron)");
|
|
87
|
+
const payload = await fetchMuJson("/api/cron/create", {
|
|
88
|
+
method: "POST",
|
|
89
|
+
body: {
|
|
90
|
+
title,
|
|
91
|
+
target_kind: targetKind,
|
|
92
|
+
run_job_id: trimOrNull(params.run_job_id),
|
|
93
|
+
run_root_issue_id: trimOrNull(params.run_root_issue_id),
|
|
94
|
+
activity_id: trimOrNull(params.activity_id),
|
|
95
|
+
schedule_kind: scheduleKind,
|
|
96
|
+
at_ms: normalizedNumber(params.at_ms),
|
|
97
|
+
at: trimOrNull(params.at),
|
|
98
|
+
every_ms: normalizedNumber(params.every_ms),
|
|
99
|
+
anchor_ms: normalizedNumber(params.anchor_ms),
|
|
100
|
+
expr: trimOrNull(params.expr),
|
|
101
|
+
tz: trimOrNull(params.tz),
|
|
102
|
+
reason: trimOrNull(params.reason),
|
|
103
|
+
enabled: typeof params.enabled === "boolean" ? params.enabled : undefined,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
return textResult(toJsonText(payload), {
|
|
107
|
+
action: "create",
|
|
108
|
+
title,
|
|
109
|
+
targetKind,
|
|
110
|
+
scheduleKind,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
case "update": {
|
|
114
|
+
const programId = trimOrNull(params.program_id);
|
|
115
|
+
if (!programId)
|
|
116
|
+
return textResult("update requires program_id");
|
|
117
|
+
const payload = await fetchMuJson("/api/cron/update", {
|
|
118
|
+
method: "POST",
|
|
119
|
+
body: {
|
|
120
|
+
program_id: programId,
|
|
121
|
+
title: trimOrNull(params.title),
|
|
122
|
+
target_kind: trimOrNull(params.target_kind),
|
|
123
|
+
run_job_id: trimOrNull(params.run_job_id),
|
|
124
|
+
run_root_issue_id: trimOrNull(params.run_root_issue_id),
|
|
125
|
+
activity_id: trimOrNull(params.activity_id),
|
|
126
|
+
schedule_kind: trimOrNull(params.schedule_kind),
|
|
127
|
+
at_ms: normalizedNumber(params.at_ms),
|
|
128
|
+
at: trimOrNull(params.at),
|
|
129
|
+
every_ms: normalizedNumber(params.every_ms),
|
|
130
|
+
anchor_ms: normalizedNumber(params.anchor_ms),
|
|
131
|
+
expr: trimOrNull(params.expr),
|
|
132
|
+
tz: trimOrNull(params.tz),
|
|
133
|
+
reason: trimOrNull(params.reason),
|
|
134
|
+
enabled: typeof params.enabled === "boolean" ? params.enabled : undefined,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
return textResult(toJsonText(payload), { action: "update", programId });
|
|
138
|
+
}
|
|
139
|
+
case "delete": {
|
|
140
|
+
const programId = trimOrNull(params.program_id);
|
|
141
|
+
if (!programId)
|
|
142
|
+
return textResult("delete requires program_id");
|
|
143
|
+
const payload = await fetchMuJson("/api/cron/delete", {
|
|
144
|
+
method: "POST",
|
|
145
|
+
body: {
|
|
146
|
+
program_id: programId,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
return textResult(toJsonText(payload), { action: "delete", programId });
|
|
150
|
+
}
|
|
151
|
+
case "trigger": {
|
|
152
|
+
const programId = trimOrNull(params.program_id);
|
|
153
|
+
if (!programId)
|
|
154
|
+
return textResult("trigger requires program_id");
|
|
155
|
+
const payload = await fetchMuJson("/api/cron/trigger", {
|
|
156
|
+
method: "POST",
|
|
157
|
+
body: {
|
|
158
|
+
program_id: programId,
|
|
159
|
+
reason: trimOrNull(params.reason),
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
return textResult(toJsonText(payload), { action: "trigger", programId });
|
|
163
|
+
}
|
|
164
|
+
case "enable":
|
|
165
|
+
case "disable": {
|
|
166
|
+
const programId = trimOrNull(params.program_id);
|
|
167
|
+
if (!programId)
|
|
168
|
+
return textResult(`${params.action} requires program_id`);
|
|
169
|
+
const payload = await fetchMuJson("/api/cron/update", {
|
|
170
|
+
method: "POST",
|
|
171
|
+
body: {
|
|
172
|
+
program_id: programId,
|
|
173
|
+
enabled: params.action === "enable",
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
return textResult(toJsonText(payload), {
|
|
177
|
+
action: params.action,
|
|
178
|
+
programId,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
default:
|
|
182
|
+
return textResult(`unknown action: ${params.action}`);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
export default cronExtension;
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* mu-event-log — Event stream helper for mu serve.
|
|
3
3
|
*
|
|
4
4
|
* - Status line with last event type and tail count
|
|
5
|
-
* - Optional watch widget below editor (`/mu
|
|
6
|
-
* - Command for quick tail inspection (`/mu
|
|
5
|
+
* - Optional watch widget below editor (`/mu events watch on|off`)
|
|
6
|
+
* - Command for quick tail inspection (`/mu events [n]` or `/mu events tail [n]`)
|
|
7
7
|
*/
|
|
8
8
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
9
9
|
export declare function eventLogExtension(pi: ExtensionAPI): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-log.d.ts","sourceRoot":"","sources":["../../src/extensions/event-log.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"event-log.d.ts","sourceRoot":"","sources":["../../src/extensions/event-log.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,+BAA+B,CAAC;AAiCpF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,YAAY,QAkHjD;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* mu-event-log — Event stream helper for mu serve.
|
|
3
3
|
*
|
|
4
4
|
* - Status line with last event type and tail count
|
|
5
|
-
* - Optional watch widget below editor (`/mu
|
|
6
|
-
* - Command for quick tail inspection (`/mu
|
|
5
|
+
* - Optional watch widget below editor (`/mu events watch on|off`)
|
|
6
|
+
* - Command for quick tail inspection (`/mu events [n]` or `/mu events tail [n]`)
|
|
7
7
|
*/
|
|
8
|
+
import { registerMuSubcommand } from "./mu-command-dispatcher.js";
|
|
8
9
|
import { clampInt, fetchMuJson, muServerUrl } from "./shared.js";
|
|
9
10
|
function eventTime(tsMs) {
|
|
10
11
|
return new Date(tsMs).toLocaleTimeString();
|
|
@@ -96,8 +97,10 @@ export function eventLogExtension(pi) {
|
|
|
96
97
|
stopPolling();
|
|
97
98
|
activeCtx = null;
|
|
98
99
|
});
|
|
99
|
-
pi
|
|
100
|
-
|
|
100
|
+
registerMuSubcommand(pi, {
|
|
101
|
+
subcommand: "events",
|
|
102
|
+
summary: "Inspect event tails and toggle the watch widget",
|
|
103
|
+
usage: "/mu events [n] | /mu events tail [n] | /mu events watch on|off",
|
|
101
104
|
handler: async (args, ctx) => {
|
|
102
105
|
const tokens = args
|
|
103
106
|
.trim()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { activitiesExtension } from "./activities.js";
|
|
2
2
|
export { brandingExtension } from "./branding.js";
|
|
3
|
+
export { cronExtension } from "./cron.js";
|
|
3
4
|
export { eventLogExtension } from "./event-log.js";
|
|
4
5
|
export { heartbeatsExtension } from "./heartbeats.js";
|
|
5
6
|
export { messagingSetupExtension } from "./messaging-setup.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,kCAAkC,EAAE,MAAM,kCAAkC,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,kCAAkC,EAAE,MAAM,kCAAkC,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AA2B1E;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,UAE/B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,UAElC,CAAC"}
|
package/dist/extensions/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { activitiesExtension } from "./activities.js";
|
|
2
2
|
export { brandingExtension } from "./branding.js";
|
|
3
|
+
export { cronExtension } from "./cron.js";
|
|
3
4
|
export { eventLogExtension } from "./event-log.js";
|
|
4
5
|
export { heartbeatsExtension } from "./heartbeats.js";
|
|
5
6
|
export { messagingSetupExtension } from "./messaging-setup.js";
|
|
@@ -16,6 +17,7 @@ const SERVE_EXTENSION_MODULE_BASENAMES = [
|
|
|
16
17
|
"orchestration-runs",
|
|
17
18
|
"activities",
|
|
18
19
|
"heartbeats",
|
|
20
|
+
"cron",
|
|
19
21
|
];
|
|
20
22
|
const OPERATOR_EXTENSION_MODULE_BASENAMES = [
|
|
21
23
|
"branding",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* mu-messaging-setup — Adapter configuration diagnostics + guided setup.
|
|
3
3
|
*
|
|
4
4
|
* Goals:
|
|
5
|
-
* - Make `/mu
|
|
5
|
+
* - Make `/mu setup <adapter>` hand setup context to the active mu agent.
|
|
6
6
|
* - Keep configuration in `.mu/config.json` (no process.env mutations).
|
|
7
7
|
* - Support plan/apply/verify workflow with in-process control-plane reload.
|
|
8
8
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messaging-setup.d.ts","sourceRoot":"","sources":["../../src/extensions/messaging-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,YAAY,EAA6C,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"messaging-setup.d.ts","sourceRoot":"","sources":["../../src/extensions/messaging-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,YAAY,EAA6C,MAAM,+BAA+B,CAAC;AAypC7G,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,YAAY,QAyPvD;AAED,eAAe,uBAAuB,CAAC"}
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
* mu-messaging-setup — Adapter configuration diagnostics + guided setup.
|
|
3
3
|
*
|
|
4
4
|
* Goals:
|
|
5
|
-
* - Make `/mu
|
|
5
|
+
* - Make `/mu setup <adapter>` hand setup context to the active mu agent.
|
|
6
6
|
* - Keep configuration in `.mu/config.json` (no process.env mutations).
|
|
7
7
|
* - Support plan/apply/verify workflow with in-process control-plane reload.
|
|
8
8
|
*/
|
|
9
9
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
10
10
|
import { Type } from "@sinclair/typebox";
|
|
11
11
|
import { loadBundledPrompt } from "../default_prompts.js";
|
|
12
|
+
import { registerMuSubcommand } from "./mu-command-dispatcher.js";
|
|
12
13
|
import { fetchMuJson, fetchMuStatus, muServerUrl, textResult, toJsonText } from "./shared.js";
|
|
13
14
|
const MESSAGING_SETUP_BRIEF_TEMPLATE = loadBundledPrompt("skills/messaging-setup-brief.md");
|
|
14
15
|
function interpolateTemplate(template, vars) {
|
|
@@ -31,7 +32,7 @@ const ADAPTERS = [
|
|
|
31
32
|
"Copy Signing Secret into .mu/config.json → control_plane.adapters.slack.signing_secret.",
|
|
32
33
|
"Create a Slash Command (e.g. /mu) with Request URL <public-base-url>/webhooks/slack.",
|
|
33
34
|
"Install/reinstall app after command changes.",
|
|
34
|
-
"Run /mu in Slack, then /mu
|
|
35
|
+
"Run /mu in Slack, then /mu setup verify slack.",
|
|
35
36
|
],
|
|
36
37
|
},
|
|
37
38
|
{
|
|
@@ -49,7 +50,7 @@ const ADAPTERS = [
|
|
|
49
50
|
"Create/open app in Discord Developer Portal.",
|
|
50
51
|
"Copy Interaction Public Key into .mu/config.json → control_plane.adapters.discord.signing_secret.",
|
|
51
52
|
"Set Interactions Endpoint URL to <public-base-url>/webhooks/discord.",
|
|
52
|
-
"Run a Discord command interaction, then /mu
|
|
53
|
+
"Run a Discord command interaction, then /mu setup verify discord.",
|
|
53
54
|
],
|
|
54
55
|
},
|
|
55
56
|
{
|
|
@@ -79,7 +80,7 @@ const ADAPTERS = [
|
|
|
79
80
|
"Call Telegram setWebhook using URL <public-base-url>/webhooks/telegram and matching secret_token.",
|
|
80
81
|
"Link your Telegram identity to control-plane policy (mu control link --channel telegram --actor-id <telegram-user-id> --tenant-id telegram-bot --role <viewer|contributor|operator>).",
|
|
81
82
|
"Optionally set control_plane.adapters.telegram.bot_username.",
|
|
82
|
-
"Send /mu in Telegram chat, then /mu
|
|
83
|
+
"Send /mu in Telegram chat, then /mu setup verify telegram.",
|
|
83
84
|
],
|
|
84
85
|
},
|
|
85
86
|
{
|
|
@@ -251,7 +252,7 @@ function nextStepForState(opts) {
|
|
|
251
252
|
case "active":
|
|
252
253
|
return "No action needed. Adapter is mounted and receiving webhooks.";
|
|
253
254
|
case "configured_not_active":
|
|
254
|
-
return "Run `/mu
|
|
255
|
+
return "Run `/mu setup apply <adapter>` to trigger in-process control-plane reload.";
|
|
255
256
|
case "missing_config":
|
|
256
257
|
return `Set required config fields: ${opts.missing.join(", ")}.`;
|
|
257
258
|
case "planned":
|
|
@@ -383,7 +384,7 @@ function setupGuide(checks, adapterId) {
|
|
|
383
384
|
return [
|
|
384
385
|
"# Messaging Integration Setup",
|
|
385
386
|
"",
|
|
386
|
-
"Use `/mu
|
|
387
|
+
"Use `/mu setup <adapter>` to hand setup context to mu agent.",
|
|
387
388
|
"Config source of truth is `.mu/config.json`.",
|
|
388
389
|
"",
|
|
389
390
|
...sections,
|
|
@@ -402,15 +403,15 @@ function buildPlan(check, publicBaseUrl) {
|
|
|
402
403
|
else {
|
|
403
404
|
if (check.missing.length > 0) {
|
|
404
405
|
steps.push(`Set required config fields: ${check.missing.join(", ")}.`);
|
|
405
|
-
steps.push(`Run /mu
|
|
406
|
+
steps.push(`Run /mu setup apply ${check.id} to write config and reload control-plane.`);
|
|
406
407
|
}
|
|
407
408
|
if (check.state === "configured_not_active") {
|
|
408
|
-
steps.push(`Run /mu
|
|
409
|
+
steps.push(`Run /mu setup apply ${check.id} to trigger control-plane reload.`);
|
|
409
410
|
}
|
|
410
411
|
if (webhookUrl) {
|
|
411
412
|
steps.push(`Configure provider webhook/inbound URL to: ${webhookUrl}`);
|
|
412
413
|
}
|
|
413
|
-
steps.push(`Run verification: /mu
|
|
414
|
+
steps.push(`Run verification: /mu setup verify ${check.id}${normalizedBase ? ` --public-base-url ${normalizedBase}` : ""}`);
|
|
414
415
|
}
|
|
415
416
|
return {
|
|
416
417
|
id: check.id,
|
|
@@ -423,8 +424,8 @@ function buildPlan(check, publicBaseUrl) {
|
|
|
423
424
|
missing_required_fields: check.missing,
|
|
424
425
|
steps,
|
|
425
426
|
commands: {
|
|
426
|
-
apply: `/mu
|
|
427
|
-
verify: `/mu
|
|
427
|
+
apply: `/mu setup apply ${check.id}`,
|
|
428
|
+
verify: `/mu setup verify ${check.id}`,
|
|
428
429
|
},
|
|
429
430
|
};
|
|
430
431
|
}
|
|
@@ -452,6 +453,62 @@ function planText(plan) {
|
|
|
452
453
|
function planSummary(plans) {
|
|
453
454
|
return plans.map((plan) => planText(plan)).join("\n\n");
|
|
454
455
|
}
|
|
456
|
+
function isRecord(value) {
|
|
457
|
+
return typeof value === "object" && value !== null;
|
|
458
|
+
}
|
|
459
|
+
function isControlPlaneGenerationIdentity(value) {
|
|
460
|
+
if (!isRecord(value))
|
|
461
|
+
return false;
|
|
462
|
+
return typeof value.generation_id === "string" && typeof value.generation_seq === "number";
|
|
463
|
+
}
|
|
464
|
+
function isControlPlaneReloadGenerationSummary(value) {
|
|
465
|
+
if (!isRecord(value))
|
|
466
|
+
return false;
|
|
467
|
+
if (typeof value.attempt_id !== "string")
|
|
468
|
+
return false;
|
|
469
|
+
if (typeof value.coalesced !== "boolean")
|
|
470
|
+
return false;
|
|
471
|
+
if (value.from_generation !== null && !isControlPlaneGenerationIdentity(value.from_generation))
|
|
472
|
+
return false;
|
|
473
|
+
if (!isControlPlaneGenerationIdentity(value.to_generation))
|
|
474
|
+
return false;
|
|
475
|
+
if (value.active_generation !== null && !isControlPlaneGenerationIdentity(value.active_generation))
|
|
476
|
+
return false;
|
|
477
|
+
return value.outcome === "success" || value.outcome === "failure";
|
|
478
|
+
}
|
|
479
|
+
function parseControlPlaneReloadApiResponse(raw) {
|
|
480
|
+
let parsed;
|
|
481
|
+
try {
|
|
482
|
+
parsed = JSON.parse(raw);
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
return {
|
|
486
|
+
response: null,
|
|
487
|
+
error: "control-plane reload returned invalid JSON response",
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
if (!isRecord(parsed)) {
|
|
491
|
+
return {
|
|
492
|
+
response: null,
|
|
493
|
+
error: "control-plane reload returned non-object payload",
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
if (!isControlPlaneReloadGenerationSummary(parsed.generation)) {
|
|
497
|
+
return {
|
|
498
|
+
response: null,
|
|
499
|
+
error: "control-plane reload response missing generation metadata (expected generation-scoped contract)",
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
const parsedRecord = parsed;
|
|
503
|
+
const response = {
|
|
504
|
+
...parsed,
|
|
505
|
+
telegram_generation: parsedRecord.telegram_generation ?? null,
|
|
506
|
+
};
|
|
507
|
+
return {
|
|
508
|
+
response,
|
|
509
|
+
error: null,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
455
512
|
async function reloadControlPlaneInProcess(reason) {
|
|
456
513
|
const base = muServerUrl();
|
|
457
514
|
if (!base) {
|
|
@@ -468,18 +525,27 @@ async function reloadControlPlaneInProcess(reason) {
|
|
|
468
525
|
body: JSON.stringify({ reason }),
|
|
469
526
|
});
|
|
470
527
|
const raw = await response.text();
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
528
|
+
const parsedResult = parseControlPlaneReloadApiResponse(raw);
|
|
529
|
+
const parsed = parsedResult.response;
|
|
530
|
+
if (parsedResult.error) {
|
|
531
|
+
return {
|
|
532
|
+
ok: false,
|
|
533
|
+
response: null,
|
|
534
|
+
error: parsedResult.error,
|
|
535
|
+
};
|
|
474
536
|
}
|
|
475
|
-
|
|
476
|
-
|
|
537
|
+
if (!parsed) {
|
|
538
|
+
return {
|
|
539
|
+
ok: false,
|
|
540
|
+
response: null,
|
|
541
|
+
error: "control-plane reload response missing payload",
|
|
542
|
+
};
|
|
477
543
|
}
|
|
478
|
-
if (!response.ok || !parsed
|
|
544
|
+
if (!response.ok || !parsed.ok) {
|
|
479
545
|
return {
|
|
480
546
|
ok: false,
|
|
481
547
|
response: parsed,
|
|
482
|
-
error: parsed
|
|
548
|
+
error: parsed.error ?? `control-plane reload failed (${response.status})`,
|
|
483
549
|
};
|
|
484
550
|
}
|
|
485
551
|
return {
|
|
@@ -496,6 +562,22 @@ async function reloadControlPlaneInProcess(reason) {
|
|
|
496
562
|
};
|
|
497
563
|
}
|
|
498
564
|
}
|
|
565
|
+
function reloadOutcomeSummary(reload) {
|
|
566
|
+
if (!reload.ok) {
|
|
567
|
+
return `Control-plane reload failed: ${reload.error ?? "unknown error"}.`;
|
|
568
|
+
}
|
|
569
|
+
const response = reload.response;
|
|
570
|
+
if (!response) {
|
|
571
|
+
return "Control-plane reload failed: missing reload response payload.";
|
|
572
|
+
}
|
|
573
|
+
const adapters = response.control_plane?.adapters.join(", ") || "(none)";
|
|
574
|
+
const generationSummary = `${response.generation.outcome} (${response.generation.active_generation?.generation_id ?? response.generation.to_generation.generation_id})`;
|
|
575
|
+
const telegramRollbackTrigger = response.telegram_generation?.rollback.trigger;
|
|
576
|
+
const telegramNote = response.telegram_generation?.handled && telegramRollbackTrigger
|
|
577
|
+
? ` rollback_trigger=${telegramRollbackTrigger}`
|
|
578
|
+
: "";
|
|
579
|
+
return `Control-plane reloaded in-process. Active adapters: ${adapters}. Generation: ${generationSummary}.${telegramNote}`;
|
|
580
|
+
}
|
|
499
581
|
function patchForAdapterValues(adapterId, values) {
|
|
500
582
|
switch (adapterId) {
|
|
501
583
|
case "slack":
|
|
@@ -621,7 +703,7 @@ function verifyText(result) {
|
|
|
621
703
|
lines.push(` next: ${check.next_step}`);
|
|
622
704
|
}
|
|
623
705
|
if (!result.ok) {
|
|
624
|
-
lines.push("", "Tip: run `/mu
|
|
706
|
+
lines.push("", "Tip: run `/mu setup plan <adapter>` for exact remediation steps.");
|
|
625
707
|
}
|
|
626
708
|
return lines.join("\n");
|
|
627
709
|
}
|
|
@@ -772,7 +854,7 @@ function buildAgentSetupPrompt(opts) {
|
|
|
772
854
|
missing_fields: opts.check.missing.join(", ") || "(none)",
|
|
773
855
|
provider_steps: adapter.providerSetupSteps.map((step, index) => `${index + 1}. ${step}`).join("\n"),
|
|
774
856
|
field_status: adapterFieldStatusLines(adapter, opts.check).join("\n"),
|
|
775
|
-
verify_command: `/mu
|
|
857
|
+
verify_command: `/mu setup verify ${adapter.id}${verifyFlag}`,
|
|
776
858
|
});
|
|
777
859
|
}
|
|
778
860
|
function dispatchSetupPromptToAgent(pi, ctx, prompt) {
|
|
@@ -846,9 +928,7 @@ async function runInteractiveApply(ctx, adapterId) {
|
|
|
846
928
|
const lines = [
|
|
847
929
|
`Updated config fields: ${outcome.updated_fields.join(", ") || "(none)"}`,
|
|
848
930
|
`Config path: ${outcome.config_path ?? runtime.configPath ?? "(unknown)"}`,
|
|
849
|
-
outcome.reload
|
|
850
|
-
? `Control-plane reloaded in-process. Active adapters: ${outcome.reload.response?.control_plane?.adapters.join(", ") || "(none)"}.`
|
|
851
|
-
: `Control-plane reload failed: ${outcome.reload.error ?? "unknown error"}.`,
|
|
931
|
+
reloadOutcomeSummary(outcome.reload),
|
|
852
932
|
"",
|
|
853
933
|
verifyText(verify),
|
|
854
934
|
];
|
|
@@ -944,7 +1024,7 @@ export function messagingSetupExtension(pi) {
|
|
|
944
1024
|
const overrides = params.fields ?? {};
|
|
945
1025
|
const stillMissing = check.missing.filter((field) => !(field in overrides));
|
|
946
1026
|
if (stillMissing.length > 0) {
|
|
947
|
-
return textResult(`Cannot apply ${adapterId}: missing required config fields (${stillMissing.join(", ")}). Pass them via the fields parameter or use /mu
|
|
1027
|
+
return textResult(`Cannot apply ${adapterId}: missing required config fields (${stillMissing.join(", ")}). Pass them via the fields parameter or use /mu setup apply ${adapterId} for guided input.`, { adapter: adapterId, missing_required_fields: stillMissing });
|
|
948
1028
|
}
|
|
949
1029
|
const outcome = await applyAdapterConfig({
|
|
950
1030
|
adapterId,
|
|
@@ -959,9 +1039,7 @@ export function messagingSetupExtension(pi) {
|
|
|
959
1039
|
const lines = [
|
|
960
1040
|
`Updated config fields: ${outcome.updated_fields.join(", ") || "(none)"}`,
|
|
961
1041
|
`Config path: ${outcome.config_path ?? runtime.configPath ?? "(unknown)"}`,
|
|
962
|
-
outcome.reload
|
|
963
|
-
? `Control-plane reloaded in-process. Active adapters: ${outcome.reload.response?.control_plane?.adapters.join(", ") || "(none)"}.`
|
|
964
|
-
: `Control-plane reload failed: ${outcome.reload.error ?? "unknown error"}.`,
|
|
1042
|
+
reloadOutcomeSummary(outcome.reload),
|
|
965
1043
|
"",
|
|
966
1044
|
verifyText(verify),
|
|
967
1045
|
];
|
|
@@ -980,12 +1058,14 @@ export function messagingSetupExtension(pi) {
|
|
|
980
1058
|
}
|
|
981
1059
|
},
|
|
982
1060
|
});
|
|
983
|
-
pi
|
|
984
|
-
|
|
1061
|
+
registerMuSubcommand(pi, {
|
|
1062
|
+
subcommand: "setup",
|
|
1063
|
+
summary: "Messaging adapter setup workflow (preflight/guide/plan/apply/verify)",
|
|
1064
|
+
usage: "/mu setup [preflight|guide|plan|apply|verify] [adapter] [--public-base-url URL] [--agent|--no-agent]",
|
|
985
1065
|
handler: async (args, ctx) => {
|
|
986
1066
|
const parsed = parseSetupCommandArgs(args);
|
|
987
1067
|
if (parsed.error) {
|
|
988
|
-
ctx.ui.notify(`${parsed.error}. Usage: /mu
|
|
1068
|
+
ctx.ui.notify(`${parsed.error}. Usage: /mu setup [preflight|guide|plan|apply|verify] [adapter] [--public-base-url URL] [--agent|--no-agent]`, "error");
|
|
989
1069
|
return;
|
|
990
1070
|
}
|
|
991
1071
|
switch (parsed.action) {
|
|
@@ -1044,7 +1124,7 @@ export function messagingSetupExtension(pi) {
|
|
|
1044
1124
|
}
|
|
1045
1125
|
case "apply": {
|
|
1046
1126
|
if (!parsed.adapterId) {
|
|
1047
|
-
ctx.ui.notify("apply requires adapter. Example: /mu
|
|
1127
|
+
ctx.ui.notify("apply requires adapter. Example: /mu setup apply slack", "error");
|
|
1048
1128
|
return;
|
|
1049
1129
|
}
|
|
1050
1130
|
const text = await runInteractiveApply(ctx, parsed.adapterId);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
export type MuSubcommandHandler = (args: string, ctx: ExtensionCommandContext) => Promise<void> | void;
|
|
3
|
+
export type MuSubcommandRegistration = {
|
|
4
|
+
subcommand: string;
|
|
5
|
+
summary: string;
|
|
6
|
+
usage: string;
|
|
7
|
+
aliases?: string[];
|
|
8
|
+
handler: MuSubcommandHandler;
|
|
9
|
+
};
|
|
10
|
+
export declare function registerMuSubcommand(pi: ExtensionAPI, registration: MuSubcommandRegistration): void;
|
|
11
|
+
//# sourceMappingURL=mu-command-dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mu-command-dispatcher.d.ts","sourceRoot":"","sources":["../../src/extensions/mu-command-dispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAE3F,MAAM,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEvG,MAAM,MAAM,wBAAwB,GAAG;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,mBAAmB,CAAC;CAC7B,CAAC;AAiIF,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,wBAAwB,GAAG,IAAI,CAsDnG"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const DISPATCHER_STATES = new WeakMap();
|
|
2
|
+
const RESERVED_SUBCOMMANDS = new Set(["help", "?"]);
|
|
3
|
+
function normalizeSubcommand(value) {
|
|
4
|
+
return value.trim().toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
function isValidSubcommandToken(value) {
|
|
7
|
+
return /^[a-z][a-z0-9_-]*$/.test(value);
|
|
8
|
+
}
|
|
9
|
+
function subcommandUsageSummary(entry) {
|
|
10
|
+
const aliasSuffix = entry.aliases && entry.aliases.length > 0 ? ` (aliases: ${entry.aliases.join(", ")})` : "";
|
|
11
|
+
return `- ${entry.usage} — ${entry.summary}${aliasSuffix}`;
|
|
12
|
+
}
|
|
13
|
+
function renderSubcommandCatalog(state) {
|
|
14
|
+
if (state.entries.size === 0) {
|
|
15
|
+
return ["No /mu subcommands are currently registered.", "", "Try again after extensions finish loading."].join("\n");
|
|
16
|
+
}
|
|
17
|
+
const entries = [...state.entries.values()].sort((left, right) => left.normalizedSubcommand.localeCompare(right.normalizedSubcommand));
|
|
18
|
+
const lines = ["Usage: /mu <subcommand> [args]", "", "Subcommands:"];
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
lines.push(subcommandUsageSummary(entry));
|
|
21
|
+
}
|
|
22
|
+
lines.push("", "Run `/mu help <subcommand>` for focused usage.");
|
|
23
|
+
return lines.join("\n");
|
|
24
|
+
}
|
|
25
|
+
function renderSubcommandHelp(entry) {
|
|
26
|
+
const lines = [entry.summary, "", `Usage: ${entry.usage}`];
|
|
27
|
+
if (entry.aliases && entry.aliases.length > 0) {
|
|
28
|
+
lines.push(`Aliases: ${entry.aliases.map((alias) => `/mu ${alias}`).join(", ")}`);
|
|
29
|
+
}
|
|
30
|
+
return lines.join("\n");
|
|
31
|
+
}
|
|
32
|
+
function parseInvocation(args) {
|
|
33
|
+
const trimmed = args.trim();
|
|
34
|
+
if (trimmed.length === 0) {
|
|
35
|
+
return { subcommand: "", remainder: "" };
|
|
36
|
+
}
|
|
37
|
+
const boundary = trimmed.search(/\s/);
|
|
38
|
+
if (boundary === -1) {
|
|
39
|
+
return { subcommand: trimmed, remainder: "" };
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
subcommand: trimmed.slice(0, boundary),
|
|
43
|
+
remainder: trimmed.slice(boundary + 1).trim(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function resolveEntry(state, token) {
|
|
47
|
+
const normalized = normalizeSubcommand(token);
|
|
48
|
+
if (!normalized)
|
|
49
|
+
return null;
|
|
50
|
+
const canonical = state.aliases.get(normalized) ?? normalized;
|
|
51
|
+
return state.entries.get(canonical) ?? null;
|
|
52
|
+
}
|
|
53
|
+
function ensureDispatcher(pi) {
|
|
54
|
+
const existing = DISPATCHER_STATES.get(pi);
|
|
55
|
+
if (existing) {
|
|
56
|
+
return existing;
|
|
57
|
+
}
|
|
58
|
+
const state = {
|
|
59
|
+
entries: new Map(),
|
|
60
|
+
aliases: new Map(),
|
|
61
|
+
};
|
|
62
|
+
DISPATCHER_STATES.set(pi, state);
|
|
63
|
+
pi.registerCommand("mu", {
|
|
64
|
+
description: "mu command dispatcher (`/mu <subcommand> ...`)",
|
|
65
|
+
handler: async (args, ctx) => {
|
|
66
|
+
const parsed = parseInvocation(args);
|
|
67
|
+
if (!parsed.subcommand) {
|
|
68
|
+
ctx.ui.notify(renderSubcommandCatalog(state), "info");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const normalized = normalizeSubcommand(parsed.subcommand);
|
|
72
|
+
if (normalized === "help" || normalized === "?") {
|
|
73
|
+
if (!parsed.remainder) {
|
|
74
|
+
ctx.ui.notify(renderSubcommandCatalog(state), "info");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const detail = resolveEntry(state, parsed.remainder.split(/\s+/)[0] ?? "");
|
|
78
|
+
if (!detail) {
|
|
79
|
+
ctx.ui.notify(`Unknown mu subcommand: ${parsed.remainder}\n\n${renderSubcommandCatalog(state)}`, "error");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
ctx.ui.notify(renderSubcommandHelp(detail), "info");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const entry = resolveEntry(state, parsed.subcommand);
|
|
86
|
+
if (!entry) {
|
|
87
|
+
ctx.ui.notify(`Unknown mu subcommand: ${parsed.subcommand}\n\n${renderSubcommandCatalog(state)}`, "error");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
await entry.handler(parsed.remainder, ctx);
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
export function registerMuSubcommand(pi, registration) {
|
|
96
|
+
const state = ensureDispatcher(pi);
|
|
97
|
+
const normalizedSubcommand = normalizeSubcommand(registration.subcommand);
|
|
98
|
+
if (!isValidSubcommandToken(normalizedSubcommand)) {
|
|
99
|
+
throw new Error(`Invalid mu subcommand: ${registration.subcommand}`);
|
|
100
|
+
}
|
|
101
|
+
if (RESERVED_SUBCOMMANDS.has(normalizedSubcommand)) {
|
|
102
|
+
throw new Error(`Reserved mu subcommand: ${registration.subcommand}`);
|
|
103
|
+
}
|
|
104
|
+
if (!registration.usage.startsWith("/mu ")) {
|
|
105
|
+
throw new Error(`mu subcommand usage must start with '/mu ': ${registration.usage}`);
|
|
106
|
+
}
|
|
107
|
+
const normalizedAliases = (registration.aliases ?? [])
|
|
108
|
+
.map((alias) => normalizeSubcommand(alias))
|
|
109
|
+
.filter((alias) => alias.length > 0 && alias !== normalizedSubcommand);
|
|
110
|
+
for (const alias of normalizedAliases) {
|
|
111
|
+
if (!isValidSubcommandToken(alias)) {
|
|
112
|
+
throw new Error(`Invalid mu subcommand alias: ${alias}`);
|
|
113
|
+
}
|
|
114
|
+
if (RESERVED_SUBCOMMANDS.has(alias)) {
|
|
115
|
+
throw new Error(`Reserved mu subcommand alias: ${alias}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const existing = state.entries.get(normalizedSubcommand);
|
|
119
|
+
if (existing) {
|
|
120
|
+
for (const alias of existing.normalizedAliases) {
|
|
121
|
+
state.aliases.delete(alias);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const alias of normalizedAliases) {
|
|
125
|
+
const occupiedBy = state.aliases.get(alias);
|
|
126
|
+
if (occupiedBy && occupiedBy !== normalizedSubcommand) {
|
|
127
|
+
throw new Error(`mu subcommand alias '${alias}' is already registered by '${occupiedBy}'`);
|
|
128
|
+
}
|
|
129
|
+
if (state.entries.has(alias) && alias !== normalizedSubcommand) {
|
|
130
|
+
throw new Error(`mu subcommand alias '${alias}' conflicts with existing subcommand`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const entry = {
|
|
134
|
+
...registration,
|
|
135
|
+
normalizedSubcommand,
|
|
136
|
+
normalizedAliases,
|
|
137
|
+
};
|
|
138
|
+
state.entries.set(normalizedSubcommand, entry);
|
|
139
|
+
state.aliases.set(normalizedSubcommand, normalizedSubcommand);
|
|
140
|
+
for (const alias of normalizedAliases) {
|
|
141
|
+
state.aliases.set(alias, normalizedSubcommand);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-tools.d.ts","sourceRoot":"","sources":["../../src/extensions/server-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"server-tools.d.ts","sourceRoot":"","sources":["../../src/extensions/server-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAqFlE,MAAM,MAAM,wBAAwB,GAAG;IACtC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;CAClC,CAAC;AA2XF,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,GAAE,wBAA6B,QAQzF;AAED,wBAAgB,4BAA4B,CAAC,EAAE,EAAE,YAAY,QAS5D;AAED,eAAe,oBAAoB,CAAC"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
7
7
|
import { Type } from "@sinclair/typebox";
|
|
8
|
+
import { registerMuSubcommand } from "./mu-command-dispatcher.js";
|
|
8
9
|
import { clampInt, fetchMuJson, fetchMuStatus, muServerUrl, textResult, toJsonText, } from "./shared.js";
|
|
9
10
|
function trimOrNull(value) {
|
|
10
11
|
if (value == null)
|
|
@@ -21,17 +22,33 @@ function cpRoutesFromStatus(routes, adapters) {
|
|
|
21
22
|
route: `/webhooks/${name}`,
|
|
22
23
|
}));
|
|
23
24
|
}
|
|
25
|
+
function generationSummary(generation) {
|
|
26
|
+
const active = generation.active_generation?.generation_id ?? "(none)";
|
|
27
|
+
const pending = generation.pending_reload
|
|
28
|
+
? `${generation.pending_reload.attempt_id}:${generation.pending_reload.state}`
|
|
29
|
+
: "(none)";
|
|
30
|
+
const last = generation.last_reload
|
|
31
|
+
? `${generation.last_reload.attempt_id}:${generation.last_reload.state}`
|
|
32
|
+
: "(none)";
|
|
33
|
+
return `generation: active=${active} pending=${pending} last=${last}`;
|
|
34
|
+
}
|
|
35
|
+
function observabilitySummary(counters) {
|
|
36
|
+
return `observability: reload_success=${counters.reload_success_total} reload_failure=${counters.reload_failure_total} duplicate=${counters.duplicate_signal_total} drop=${counters.drop_signal_total}`;
|
|
37
|
+
}
|
|
24
38
|
function summarizeStatus(status) {
|
|
25
|
-
const cp = status.control_plane
|
|
39
|
+
const cp = status.control_plane;
|
|
26
40
|
const routes = cpRoutesFromStatus(cp.routes, cp.adapters);
|
|
27
41
|
const routeText = routes.length > 0 ? routes.map((entry) => `${entry.name}:${entry.route}`).join(", ") : "(none)";
|
|
28
|
-
|
|
42
|
+
const lines = [
|
|
29
43
|
`repo: ${status.repo_root}`,
|
|
30
44
|
`issues: open=${status.open_count} ready=${status.ready_count}`,
|
|
31
45
|
`control_plane: ${cp.active ? "active" : "inactive"}`,
|
|
32
46
|
`adapters: ${cp.adapters.length > 0 ? cp.adapters.join(", ") : "(none)"}`,
|
|
33
47
|
`routes: ${routeText}`,
|
|
34
|
-
|
|
48
|
+
generationSummary(cp.generation),
|
|
49
|
+
observabilitySummary(cp.observability.counters),
|
|
50
|
+
];
|
|
51
|
+
return lines.join("\n");
|
|
35
52
|
}
|
|
36
53
|
function sliceWithLimit(items, limitRaw, fallback = 50) {
|
|
37
54
|
const limit = clampInt(limitRaw, fallback, 1, 200);
|
|
@@ -68,7 +85,7 @@ function registerServerTools(pi, opts) {
|
|
|
68
85
|
ctx.ui.setStatus("mu-server", ctx.ui.theme.fg("dim", `μ server ${url}`));
|
|
69
86
|
try {
|
|
70
87
|
const status = await fetchMuStatus(4_000);
|
|
71
|
-
ctx.ui.setStatus("mu-status", ctx.ui.theme.fg("dim", `open ${status.open_count} · ready ${status.ready_count} · cp ${status.control_plane
|
|
88
|
+
ctx.ui.setStatus("mu-status", ctx.ui.theme.fg("dim", `open ${status.open_count} · ready ${status.ready_count} · cp ${status.control_plane.active ? "on" : "off"}`));
|
|
72
89
|
}
|
|
73
90
|
catch {
|
|
74
91
|
ctx.ui.setStatus("mu-status", ctx.ui.theme.fg("warning", "μ status unavailable"));
|
|
@@ -96,19 +113,19 @@ function registerServerTools(pi, opts) {
|
|
|
96
113
|
parameters: ControlPlaneParams,
|
|
97
114
|
async execute(_toolCallId, params) {
|
|
98
115
|
const status = await fetchMuStatus();
|
|
99
|
-
const cp = status.control_plane
|
|
100
|
-
active: false,
|
|
101
|
-
adapters: [],
|
|
102
|
-
routes: [],
|
|
103
|
-
};
|
|
116
|
+
const cp = status.control_plane;
|
|
104
117
|
const routes = cpRoutesFromStatus(cp.routes, cp.adapters);
|
|
118
|
+
const generation = cp.generation;
|
|
119
|
+
const observability = cp.observability.counters;
|
|
105
120
|
switch (params.action) {
|
|
106
121
|
case "status":
|
|
107
122
|
return textResult(toJsonText({
|
|
108
123
|
active: cp.active,
|
|
109
124
|
adapters: cp.adapters,
|
|
110
125
|
routes,
|
|
111
|
-
|
|
126
|
+
generation,
|
|
127
|
+
observability,
|
|
128
|
+
}), { control_plane: cp, routes, generation, observability });
|
|
112
129
|
case "adapters":
|
|
113
130
|
return textResult(toJsonText(cp.adapters), { adapters: cp.adapters });
|
|
114
131
|
case "routes":
|
|
@@ -360,8 +377,10 @@ function registerServerTools(pi, opts) {
|
|
|
360
377
|
}
|
|
361
378
|
},
|
|
362
379
|
});
|
|
363
|
-
pi
|
|
364
|
-
|
|
380
|
+
registerMuSubcommand(pi, {
|
|
381
|
+
subcommand: "status",
|
|
382
|
+
summary: "Show concise mu server status",
|
|
383
|
+
usage: "/mu status",
|
|
365
384
|
handler: async (_args, ctx) => {
|
|
366
385
|
try {
|
|
367
386
|
const status = await fetchMuStatus();
|
|
@@ -372,21 +391,21 @@ function registerServerTools(pi, opts) {
|
|
|
372
391
|
}
|
|
373
392
|
},
|
|
374
393
|
});
|
|
375
|
-
pi
|
|
376
|
-
|
|
394
|
+
registerMuSubcommand(pi, {
|
|
395
|
+
subcommand: "control",
|
|
396
|
+
summary: "Show control-plane adapter/runtime status",
|
|
397
|
+
usage: "/mu control",
|
|
377
398
|
handler: async (_args, ctx) => {
|
|
378
399
|
try {
|
|
379
400
|
const status = await fetchMuStatus();
|
|
380
|
-
const cp = status.control_plane
|
|
381
|
-
active: false,
|
|
382
|
-
adapters: [],
|
|
383
|
-
routes: [],
|
|
384
|
-
};
|
|
401
|
+
const cp = status.control_plane;
|
|
385
402
|
const routes = cpRoutesFromStatus(cp.routes, cp.adapters);
|
|
386
403
|
const lines = [
|
|
387
404
|
`control_plane: ${cp.active ? "active" : "inactive"}`,
|
|
388
405
|
`adapters: ${cp.adapters.length > 0 ? cp.adapters.join(", ") : "(none)"}`,
|
|
389
406
|
`routes: ${routes.length > 0 ? routes.map((entry) => `${entry.name}:${entry.route}`).join(", ") : "(none)"}`,
|
|
407
|
+
generationSummary(cp.generation),
|
|
408
|
+
observabilitySummary(cp.observability.counters),
|
|
390
409
|
];
|
|
391
410
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
392
411
|
}
|
|
@@ -400,7 +419,7 @@ export function serverToolsExtension(pi, opts = {}) {
|
|
|
400
419
|
registerServerTools(pi, {
|
|
401
420
|
allowForumPost: opts.allowForumPost ?? true,
|
|
402
421
|
toolIntroLine: opts.toolIntroLine ??
|
|
403
|
-
"Tools: mu_status, mu_control_plane, mu_issues, mu_forum, mu_events, mu_runs, mu_activities, mu_heartbeats, mu_identity.",
|
|
422
|
+
"Tools: mu_status, mu_control_plane, mu_issues, mu_forum, mu_events, mu_runs, mu_activities, mu_heartbeats, mu_cron, mu_identity.",
|
|
404
423
|
extraSystemPromptLines: opts.extraSystemPromptLines ?? [],
|
|
405
424
|
});
|
|
406
425
|
}
|
|
@@ -2,15 +2,48 @@ export type MuControlPlaneRoute = {
|
|
|
2
2
|
name: string;
|
|
3
3
|
route: string;
|
|
4
4
|
};
|
|
5
|
+
export type MuGenerationIdentity = {
|
|
6
|
+
generation_id: string;
|
|
7
|
+
generation_seq: number;
|
|
8
|
+
};
|
|
9
|
+
export type MuGenerationReloadAttempt = {
|
|
10
|
+
attempt_id: string;
|
|
11
|
+
reason: string;
|
|
12
|
+
state: "planned" | "swapped" | "completed" | "failed";
|
|
13
|
+
requested_at_ms: number;
|
|
14
|
+
swapped_at_ms: number | null;
|
|
15
|
+
finished_at_ms: number | null;
|
|
16
|
+
from_generation: MuGenerationIdentity | null;
|
|
17
|
+
to_generation: MuGenerationIdentity;
|
|
18
|
+
};
|
|
19
|
+
export type MuGenerationSupervisorSnapshot = {
|
|
20
|
+
supervisor_id: string;
|
|
21
|
+
active_generation: MuGenerationIdentity | null;
|
|
22
|
+
pending_reload: MuGenerationReloadAttempt | null;
|
|
23
|
+
last_reload: MuGenerationReloadAttempt | null;
|
|
24
|
+
};
|
|
25
|
+
export type MuGenerationObservabilityCounters = {
|
|
26
|
+
reload_success_total: number;
|
|
27
|
+
reload_failure_total: number;
|
|
28
|
+
reload_drain_duration_ms_total: number;
|
|
29
|
+
reload_drain_duration_samples_total: number;
|
|
30
|
+
duplicate_signal_total: number;
|
|
31
|
+
drop_signal_total: number;
|
|
32
|
+
};
|
|
33
|
+
export type MuControlPlaneStatus = {
|
|
34
|
+
active: boolean;
|
|
35
|
+
adapters: string[];
|
|
36
|
+
routes?: MuControlPlaneRoute[];
|
|
37
|
+
generation: MuGenerationSupervisorSnapshot;
|
|
38
|
+
observability: {
|
|
39
|
+
counters: MuGenerationObservabilityCounters;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
5
42
|
export type MuStatusResponse = {
|
|
6
43
|
repo_root: string;
|
|
7
44
|
open_count: number;
|
|
8
45
|
ready_count: number;
|
|
9
|
-
control_plane
|
|
10
|
-
active: boolean;
|
|
11
|
-
adapters: string[];
|
|
12
|
-
routes?: MuControlPlaneRoute[];
|
|
13
|
-
};
|
|
46
|
+
control_plane: MuControlPlaneStatus;
|
|
14
47
|
};
|
|
15
48
|
export declare function muServerUrl(): string | null;
|
|
16
49
|
export declare function clampInt(value: number | undefined, fallback: number, min: number, max: number): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/extensions/shared.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/extensions/shared.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtD,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,eAAe,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC7C,aAAa,EAAE,oBAAoB,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC/C,cAAc,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACjD,WAAW,EAAE,yBAAyB,GAAG,IAAI,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC/C,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,8BAA8B,EAAE,MAAM,CAAC;IACvC,mCAAmC,EAAE,MAAM,CAAC;IAC5C,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAClC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAC/B,UAAU,EAAE,8BAA8B,CAAC;IAC3C,aAAa,EAAE;QACd,QAAQ,EAAE,iCAAiC,CAAC;KAC5C,CAAC;CACF,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,oBAAoB,CAAC;CACpC,CAAC;AAEF,wBAAgB,WAAW,IAAI,MAAM,GAAG,IAAI,CAG3C;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAKtG;AAED,wBAAsB,WAAW,CAAC,CAAC,EAClC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAChE,OAAO,CAAC,CAAC,CAAC,CA4BZ;AAiCD,wBAAsB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAGjF;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;;;;;;EAK7E;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEjD"}
|
|
@@ -39,8 +39,30 @@ export async function fetchMuJson(path, opts = {}) {
|
|
|
39
39
|
clearTimeout(timeout);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
function ensureGenerationScopedStatus(status) {
|
|
43
|
+
const controlPlane = status.control_plane;
|
|
44
|
+
if (!controlPlane || typeof controlPlane !== "object") {
|
|
45
|
+
throw new Error("mu server /api/status missing control_plane payload (expected generation-scoped contract)");
|
|
46
|
+
}
|
|
47
|
+
const controlPlaneRecord = controlPlane;
|
|
48
|
+
if (!("generation" in controlPlaneRecord) || !controlPlaneRecord.generation) {
|
|
49
|
+
throw new Error("mu server /api/status missing control_plane.generation (expected generation-scoped contract)");
|
|
50
|
+
}
|
|
51
|
+
if (!("observability" in controlPlaneRecord) || !controlPlaneRecord.observability) {
|
|
52
|
+
throw new Error("mu server /api/status missing control_plane.observability (expected generation-scoped contract)");
|
|
53
|
+
}
|
|
54
|
+
const observability = controlPlaneRecord.observability;
|
|
55
|
+
if (typeof observability !== "object" ||
|
|
56
|
+
observability == null ||
|
|
57
|
+
!("counters" in observability) ||
|
|
58
|
+
!observability.counters) {
|
|
59
|
+
throw new Error("mu server /api/status missing control_plane.observability.counters (expected generation-scoped contract)");
|
|
60
|
+
}
|
|
61
|
+
return status;
|
|
62
|
+
}
|
|
42
63
|
export async function fetchMuStatus(timeoutMs) {
|
|
43
|
-
|
|
64
|
+
const status = await fetchMuJson("/api/status", { timeoutMs });
|
|
65
|
+
return ensureGenerationScopedStatus(status);
|
|
44
66
|
}
|
|
45
67
|
export function textResult(text, details = {}) {
|
|
46
68
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-agent",
|
|
3
|
-
"version": "26.2.
|
|
3
|
+
"version": "26.2.57",
|
|
4
4
|
"description": "Shared agent runtime for mu chat, orchestration roles, and serve extensions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mu",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"prompts/**"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@femtomc/mu-core": "
|
|
26
|
+
"@femtomc/mu-core": "workspace:*",
|
|
27
27
|
"@mariozechner/pi-agent-core": "^0.52.12",
|
|
28
28
|
"@mariozechner/pi-ai": "^0.52.12",
|
|
29
29
|
"@mariozechner/pi-coding-agent": "^0.52.12",
|