@femtomc/mu-agent 26.2.70 → 26.2.72
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 +1 -2
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +1 -1
- package/dist/default_prompts.d.ts +1 -0
- package/dist/default_prompts.d.ts.map +1 -1
- package/dist/default_prompts.js +2 -0
- package/dist/extensions/messaging-setup/actions.d.ts +22 -0
- package/dist/extensions/messaging-setup/actions.d.ts.map +1 -0
- package/dist/extensions/messaging-setup/actions.js +229 -0
- package/dist/extensions/messaging-setup/adapters.d.ts +24 -0
- package/dist/extensions/messaging-setup/adapters.d.ts.map +1 -0
- package/dist/extensions/messaging-setup/adapters.js +170 -0
- package/dist/extensions/messaging-setup/index.d.ts +17 -0
- package/dist/extensions/messaging-setup/index.d.ts.map +1 -0
- package/dist/extensions/messaging-setup/index.js +261 -0
- package/dist/extensions/messaging-setup/parser.d.ts +33 -0
- package/dist/extensions/messaging-setup/parser.d.ts.map +1 -0
- package/dist/extensions/messaging-setup/parser.js +240 -0
- package/dist/extensions/messaging-setup/runtime.d.ts +16 -0
- package/dist/extensions/messaging-setup/runtime.d.ts.map +1 -0
- package/dist/extensions/messaging-setup/runtime.js +110 -0
- package/dist/extensions/messaging-setup/types.d.ts +157 -0
- package/dist/extensions/messaging-setup/types.d.ts.map +1 -0
- package/dist/extensions/messaging-setup/types.js +4 -0
- package/dist/extensions/messaging-setup/ui.d.ts +15 -0
- package/dist/extensions/messaging-setup/ui.d.ts.map +1 -0
- package/dist/extensions/messaging-setup/ui.js +173 -0
- package/dist/extensions/messaging-setup.d.ts +2 -14
- package/dist/extensions/messaging-setup.d.ts.map +1 -1
- package/dist/extensions/messaging-setup.js +2 -1190
- package/dist/mu_roles.d.ts +3 -3
- package/dist/mu_roles.d.ts.map +1 -1
- package/dist/mu_roles.js +11 -3
- package/package.json +2 -2
- package/prompts/roles/reviewer.md +34 -0
- package/prompts/roles/soul.md +3 -4
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mu-messaging-setup — Adapter configuration diagnostics + guided setup.
|
|
3
|
+
*
|
|
4
|
+
* Goals:
|
|
5
|
+
* - Make `/mu setup <adapter>` hand setup context to the active mu agent.
|
|
6
|
+
* - Keep configuration in `.mu/config.json` (no process.env mutations).
|
|
7
|
+
* - Support plan/apply/verify workflow with in-process control-plane reload.
|
|
8
|
+
*/
|
|
9
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
10
|
+
import { Type } from "@sinclair/typebox";
|
|
11
|
+
import { registerMuSubcommand } from "../mu-command-dispatcher.js";
|
|
12
|
+
import { textResult, toJsonText } from "../shared.js";
|
|
13
|
+
import { ADAPTERS, normalizeAdapterId } from "./adapters.js";
|
|
14
|
+
import { applyAdapterConfig, buildVerifyOutcome, reloadOutcomeSummary } from "./actions.js";
|
|
15
|
+
import { buildAgentSetupPrompt, checksForAdapter, findCheckByAdapter, maybeDispatchAgentSetupBrief, parseSetupCommandArgs, runInteractiveApply, } from "./parser.js";
|
|
16
|
+
import { collectChecksCached, refreshMessagingStatus } from "./runtime.js";
|
|
17
|
+
import { buildPlan, planSummary, preflightSummary, setupGuide, summarizeChecks, verifyText } from "./ui.js";
|
|
18
|
+
export function messagingSetupExtension(pi, opts = {}) {
|
|
19
|
+
const allowApply = opts.allowApply ?? true;
|
|
20
|
+
pi.on("before_agent_start", async (event) => {
|
|
21
|
+
const { checks } = await collectChecksCached();
|
|
22
|
+
const summary = summarizeChecks(checks);
|
|
23
|
+
const lines = [
|
|
24
|
+
"",
|
|
25
|
+
"[MU MESSAGING]",
|
|
26
|
+
summary.length > 0 ? summary : "no adapter status available",
|
|
27
|
+
allowApply
|
|
28
|
+
? "Use mu_messaging_setup(action=preflight|plan|apply|verify|guide) for operator workflow."
|
|
29
|
+
: "Use mu_messaging_setup(action=preflight|plan|verify|guide) for query workflow.",
|
|
30
|
+
];
|
|
31
|
+
return {
|
|
32
|
+
systemPrompt: `${event.systemPrompt}${lines.join("\n")}`,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
36
|
+
await refreshMessagingStatus(ctx);
|
|
37
|
+
});
|
|
38
|
+
const setupActions = allowApply
|
|
39
|
+
? ["check", "preflight", "guide", "plan", "apply", "verify"]
|
|
40
|
+
: ["check", "preflight", "guide", "plan", "verify"];
|
|
41
|
+
const SetupParams = Type.Object({
|
|
42
|
+
action: StringEnum(setupActions),
|
|
43
|
+
adapter: Type.Optional(Type.String({ description: "Adapter name: slack, discord, telegram" })),
|
|
44
|
+
public_base_url: Type.Optional(Type.String({
|
|
45
|
+
description: "Optional public base URL used to compute expected webhook endpoints (e.g. https://example.ngrok.app)",
|
|
46
|
+
})),
|
|
47
|
+
fields: Type.Optional(Type.Record(Type.String(), Type.String(), {
|
|
48
|
+
description: "Config field overrides for apply action. Keys are field names (e.g. bot_token, webhook_secret), values are the secrets/tokens to write.",
|
|
49
|
+
})),
|
|
50
|
+
});
|
|
51
|
+
pi.registerTool({
|
|
52
|
+
name: "mu_messaging_setup",
|
|
53
|
+
label: "Messaging Setup",
|
|
54
|
+
description: allowApply
|
|
55
|
+
? "Messaging setup workflow. Actions: check/preflight/guide/plan/apply/verify. For apply, pass field values via the fields parameter (e.g. fields={bot_token:'...', webhook_secret:'...'})."
|
|
56
|
+
: "Messaging setup query workflow. Actions: check/preflight/guide/plan/verify. Apply is disabled in query-only mode.",
|
|
57
|
+
parameters: SetupParams,
|
|
58
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
59
|
+
const adapterId = params.adapter ? normalizeAdapterId(params.adapter) : null;
|
|
60
|
+
if (params.adapter && !adapterId) {
|
|
61
|
+
return textResult(`Unknown adapter: ${params.adapter}. Available: ${ADAPTERS.map((adapter) => adapter.id).join(", ")}`);
|
|
62
|
+
}
|
|
63
|
+
switch (params.action) {
|
|
64
|
+
case "check": {
|
|
65
|
+
const { checks, runtime } = await collectChecksCached();
|
|
66
|
+
const filteredChecks = checksForAdapter(checks, adapterId);
|
|
67
|
+
return textResult(toJsonText({
|
|
68
|
+
checks: filteredChecks,
|
|
69
|
+
runtime: { ...runtime, routesByAdapter: Object.fromEntries(runtime.routesByAdapter) },
|
|
70
|
+
}), {
|
|
71
|
+
checks: filteredChecks,
|
|
72
|
+
runtime,
|
|
73
|
+
adapter: adapterId,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
case "preflight": {
|
|
77
|
+
const { checks, runtime } = await collectChecksCached();
|
|
78
|
+
const filteredChecks = checksForAdapter(checks, adapterId);
|
|
79
|
+
return textResult(preflightSummary(filteredChecks, runtime), {
|
|
80
|
+
checks: filteredChecks,
|
|
81
|
+
runtime,
|
|
82
|
+
adapter: adapterId,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
case "guide":
|
|
86
|
+
case "plan": {
|
|
87
|
+
const { checks, runtime } = await collectChecksCached();
|
|
88
|
+
if (adapterId) {
|
|
89
|
+
const check = findCheckByAdapter(checks, adapterId);
|
|
90
|
+
if (!check) {
|
|
91
|
+
return textResult(`Unknown adapter: ${adapterId}`);
|
|
92
|
+
}
|
|
93
|
+
const plan = buildPlan(check, params.public_base_url);
|
|
94
|
+
const brief = buildAgentSetupPrompt({
|
|
95
|
+
check,
|
|
96
|
+
plan,
|
|
97
|
+
configPath: runtime.configPath,
|
|
98
|
+
publicBaseUrl: params.public_base_url,
|
|
99
|
+
});
|
|
100
|
+
return textResult(brief, { checks, runtime, adapter: adapterId, plan });
|
|
101
|
+
}
|
|
102
|
+
if (params.action === "guide") {
|
|
103
|
+
return textResult(setupGuide(checks), { checks, runtime, adapter: null });
|
|
104
|
+
}
|
|
105
|
+
const plans = checks.map((check) => buildPlan(check, params.public_base_url));
|
|
106
|
+
return textResult(planSummary(plans), { plans, runtime, adapter: null });
|
|
107
|
+
}
|
|
108
|
+
case "apply": {
|
|
109
|
+
if (!allowApply) {
|
|
110
|
+
return textResult("apply is disabled in query-only mode. Use plan/guide/verify actions.", {
|
|
111
|
+
blocked: true,
|
|
112
|
+
reason: "messaging_setup_query_only_mode",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (!adapterId) {
|
|
116
|
+
return textResult("apply requires adapter (slack|discord|telegram)");
|
|
117
|
+
}
|
|
118
|
+
const { runtime, checks } = await collectChecksCached(0);
|
|
119
|
+
if (!runtime.configPresence) {
|
|
120
|
+
return textResult(`Cannot read config presence: ${runtime.fetchError ?? "unknown error"}`);
|
|
121
|
+
}
|
|
122
|
+
const check = findCheckByAdapter(checks, adapterId);
|
|
123
|
+
if (!check) {
|
|
124
|
+
return textResult(`Unknown adapter: ${adapterId}`);
|
|
125
|
+
}
|
|
126
|
+
const overrides = params.fields ?? {};
|
|
127
|
+
const stillMissing = check.missing.filter((field) => !(field in overrides));
|
|
128
|
+
if (stillMissing.length > 0) {
|
|
129
|
+
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 });
|
|
130
|
+
}
|
|
131
|
+
const outcome = await applyAdapterConfig({
|
|
132
|
+
adapterId,
|
|
133
|
+
overrides,
|
|
134
|
+
presence: runtime.configPresence,
|
|
135
|
+
});
|
|
136
|
+
if (!outcome.ok) {
|
|
137
|
+
return textResult(`Apply failed: ${outcome.reason} (${outcome.missing_required_fields.join(", ")}).`, outcome);
|
|
138
|
+
}
|
|
139
|
+
const { checks: refreshed } = await collectChecksCached(0);
|
|
140
|
+
const verify = buildVerifyOutcome(refreshed, { adapterId, publicBaseUrl: params.public_base_url });
|
|
141
|
+
const lines = [
|
|
142
|
+
`Updated config fields: ${outcome.updated_fields.join(", ") || "(none)"}`,
|
|
143
|
+
`Config path: ${outcome.config_path ?? runtime.configPath ?? "(unknown)"}`,
|
|
144
|
+
reloadOutcomeSummary(outcome.reload),
|
|
145
|
+
"",
|
|
146
|
+
verifyText(verify),
|
|
147
|
+
];
|
|
148
|
+
return textResult(lines.join("\n"), { outcome, verify });
|
|
149
|
+
}
|
|
150
|
+
case "verify": {
|
|
151
|
+
const { checks } = await collectChecksCached(0);
|
|
152
|
+
const verify = buildVerifyOutcome(checks, {
|
|
153
|
+
adapterId: adapterId ?? undefined,
|
|
154
|
+
publicBaseUrl: params.public_base_url,
|
|
155
|
+
});
|
|
156
|
+
return textResult(verifyText(verify), { verify });
|
|
157
|
+
}
|
|
158
|
+
default:
|
|
159
|
+
return textResult(`Unknown action: ${params.action}`);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
registerMuSubcommand(pi, {
|
|
164
|
+
subcommand: "setup",
|
|
165
|
+
summary: "Messaging adapter setup workflow (preflight/guide/plan/apply/verify)",
|
|
166
|
+
usage: "/mu setup [preflight|guide|plan|apply|verify] [adapter] [--public-base-url URL] [--agent|--no-agent]",
|
|
167
|
+
handler: async (args, ctx) => {
|
|
168
|
+
const parsed = parseSetupCommandArgs(args);
|
|
169
|
+
if (parsed.error) {
|
|
170
|
+
ctx.ui.notify(`${parsed.error}. Usage: /mu setup [preflight|guide|plan|apply|verify] [adapter] [--public-base-url URL] [--agent|--no-agent]`, "error");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
switch (parsed.action) {
|
|
174
|
+
case "check": {
|
|
175
|
+
const { checks, runtime } = await collectChecksCached(0);
|
|
176
|
+
const filteredChecks = checksForAdapter(checks, parsed.adapterId);
|
|
177
|
+
ctx.ui.notify(toJsonText({
|
|
178
|
+
checks: filteredChecks,
|
|
179
|
+
runtime: { ...runtime, routesByAdapter: Object.fromEntries(runtime.routesByAdapter) },
|
|
180
|
+
}), "info");
|
|
181
|
+
await refreshMessagingStatus(ctx);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
case "preflight": {
|
|
185
|
+
const { checks, runtime } = await collectChecksCached(0);
|
|
186
|
+
if (await maybeDispatchAgentSetupBrief({ pi, ctx, parsed, checks, runtime })) {
|
|
187
|
+
if (runtime.fetchError) {
|
|
188
|
+
ctx.ui.notify(`runtime note: ${runtime.fetchError}`, "warning");
|
|
189
|
+
}
|
|
190
|
+
await refreshMessagingStatus(ctx);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const filteredChecks = checksForAdapter(checks, parsed.adapterId);
|
|
194
|
+
ctx.ui.notify(preflightSummary(filteredChecks, runtime), "info");
|
|
195
|
+
await refreshMessagingStatus(ctx);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
case "guide": {
|
|
199
|
+
const { checks, runtime } = await collectChecksCached(0);
|
|
200
|
+
if (await maybeDispatchAgentSetupBrief({ pi, ctx, parsed, checks, runtime })) {
|
|
201
|
+
if (runtime.fetchError) {
|
|
202
|
+
ctx.ui.notify(`runtime note: ${runtime.fetchError}`, "warning");
|
|
203
|
+
}
|
|
204
|
+
await refreshMessagingStatus(ctx);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
ctx.ui.notify(setupGuide(checks, parsed.adapterId ?? undefined), "info");
|
|
208
|
+
if (runtime.fetchError) {
|
|
209
|
+
ctx.ui.notify(`runtime note: ${runtime.fetchError}`, "warning");
|
|
210
|
+
}
|
|
211
|
+
await refreshMessagingStatus(ctx);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
case "plan": {
|
|
215
|
+
const { checks, runtime } = await collectChecksCached(0);
|
|
216
|
+
if (await maybeDispatchAgentSetupBrief({ pi, ctx, parsed, checks, runtime })) {
|
|
217
|
+
await refreshMessagingStatus(ctx);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const plans = parsed.adapterId
|
|
221
|
+
? checks
|
|
222
|
+
.filter((check) => check.id === parsed.adapterId)
|
|
223
|
+
.map((check) => buildPlan(check, parsed.publicBaseUrl))
|
|
224
|
+
: checks.map((check) => buildPlan(check, parsed.publicBaseUrl));
|
|
225
|
+
ctx.ui.notify(planSummary(plans), "info");
|
|
226
|
+
await refreshMessagingStatus(ctx);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
case "apply": {
|
|
230
|
+
if (!allowApply) {
|
|
231
|
+
ctx.ui.notify("apply is disabled in query-only mode. Use /mu setup plan and /mu setup verify.", "warning");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (!parsed.adapterId) {
|
|
235
|
+
ctx.ui.notify("apply requires adapter. Example: /mu setup apply slack", "error");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const text = await runInteractiveApply(ctx, parsed.adapterId);
|
|
239
|
+
ctx.ui.notify(text, "info");
|
|
240
|
+
await refreshMessagingStatus(ctx);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
case "verify": {
|
|
244
|
+
const { checks, runtime } = await collectChecksCached(0);
|
|
245
|
+
if (await maybeDispatchAgentSetupBrief({ pi, ctx, parsed, checks, runtime })) {
|
|
246
|
+
await refreshMessagingStatus(ctx);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const verify = buildVerifyOutcome(checks, {
|
|
250
|
+
adapterId: parsed.adapterId ?? undefined,
|
|
251
|
+
publicBaseUrl: parsed.publicBaseUrl,
|
|
252
|
+
});
|
|
253
|
+
ctx.ui.notify(verifyText(verify), verify.ok ? "info" : "warning");
|
|
254
|
+
await refreshMessagingStatus(ctx);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
export default messagingSetupExtension;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command argument parsing and agent dispatch for mu-messaging-setup.
|
|
3
|
+
*/
|
|
4
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
5
|
+
import type { AdapterCheck, AdapterConfig, AdapterId, AdapterPlan, RuntimeState, SetupAction } from "./types.js";
|
|
6
|
+
export type ParsedSetupCommand = {
|
|
7
|
+
action: SetupAction;
|
|
8
|
+
adapterId: AdapterId | null;
|
|
9
|
+
publicBaseUrl: string | undefined;
|
|
10
|
+
dispatchToAgent: "auto" | "force" | "off";
|
|
11
|
+
error: string | null;
|
|
12
|
+
};
|
|
13
|
+
export declare function parseSetupCommandArgs(args: string): ParsedSetupCommand;
|
|
14
|
+
export declare function shouldDispatchSetupToAgent(parsed: ParsedSetupCommand): boolean;
|
|
15
|
+
export declare function adapterFieldStatusLines(adapter: AdapterConfig, check: AdapterCheck): string[];
|
|
16
|
+
export declare function buildAgentSetupPrompt(opts: {
|
|
17
|
+
check: AdapterCheck;
|
|
18
|
+
plan: AdapterPlan;
|
|
19
|
+
configPath: string | null;
|
|
20
|
+
publicBaseUrl?: string;
|
|
21
|
+
}): string;
|
|
22
|
+
export declare function dispatchSetupPromptToAgent(pi: ExtensionAPI, ctx: ExtensionCommandContext, prompt: string): void;
|
|
23
|
+
export declare function findCheckByAdapter(checks: AdapterCheck[], adapterId: AdapterId): AdapterCheck | null;
|
|
24
|
+
export declare function checksForAdapter(checks: AdapterCheck[], adapterId: AdapterId | null): AdapterCheck[];
|
|
25
|
+
export declare function maybeDispatchAgentSetupBrief(opts: {
|
|
26
|
+
pi: ExtensionAPI;
|
|
27
|
+
ctx: ExtensionCommandContext;
|
|
28
|
+
parsed: ParsedSetupCommand;
|
|
29
|
+
checks: AdapterCheck[];
|
|
30
|
+
runtime: RuntimeState;
|
|
31
|
+
}): Promise<boolean>;
|
|
32
|
+
export declare function runInteractiveApply(ctx: ExtensionCommandContext, adapterId: AdapterId): Promise<string>;
|
|
33
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../src/extensions/messaging-setup/parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAW3F,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAQjH,MAAM,MAAM,kBAAkB,GAAG;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,eAAe,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;IAC1C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAmHtE;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAM9E;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,EAAE,CAK7F;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC3C,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,MAAM,CAiBT;AAED,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAM/G;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,SAAS,EAAE,SAAS,GAAG,YAAY,GAAG,IAAI,CAEpG;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,YAAY,EAAE,CAMpG;AAED,wBAAsB,4BAA4B,CAAC,IAAI,EAAE;IACxD,EAAE,EAAE,YAAY,CAAC;IACjB,GAAG,EAAE,uBAAuB,CAAC;IAC7B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,OAAO,EAAE,YAAY,CAAC;CACtB,GAAG,OAAO,CAAC,OAAO,CAAC,CAenB;AAED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,uBAAuB,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAyD7G"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command argument parsing and agent dispatch for mu-messaging-setup.
|
|
3
|
+
*/
|
|
4
|
+
import { loadBundledPrompt } from "../../default_prompts.js";
|
|
5
|
+
import { adapterById, isSetupAction, normalizeAdapterId, normalizePublicBaseUrl, } from "./adapters.js";
|
|
6
|
+
import { applyAdapterConfig, buildVerifyOutcome, reloadOutcomeSummary } from "./actions.js";
|
|
7
|
+
import { collectChecksCached } from "./runtime.js";
|
|
8
|
+
import { buildPlan, verifyText } from "./ui.js";
|
|
9
|
+
const MESSAGING_SETUP_BRIEF_TEMPLATE = loadBundledPrompt("skills/messaging-setup-brief.md");
|
|
10
|
+
function interpolateTemplate(template, vars) {
|
|
11
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => vars[key] ?? `{{${key}}}`);
|
|
12
|
+
}
|
|
13
|
+
export function parseSetupCommandArgs(args) {
|
|
14
|
+
const tokens = args
|
|
15
|
+
.trim()
|
|
16
|
+
.split(/\s+/)
|
|
17
|
+
.filter((token) => token.length > 0);
|
|
18
|
+
const positional = [];
|
|
19
|
+
let publicBaseUrl;
|
|
20
|
+
let dispatchToAgent = "auto";
|
|
21
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
22
|
+
const token = tokens[i];
|
|
23
|
+
if (token === "--agent") {
|
|
24
|
+
dispatchToAgent = "force";
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (token === "--no-agent") {
|
|
28
|
+
dispatchToAgent = "off";
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (token === "--public-base-url") {
|
|
32
|
+
const next = tokens[i + 1];
|
|
33
|
+
if (!next) {
|
|
34
|
+
return {
|
|
35
|
+
action: "preflight",
|
|
36
|
+
adapterId: null,
|
|
37
|
+
publicBaseUrl: undefined,
|
|
38
|
+
dispatchToAgent,
|
|
39
|
+
error: "Missing value for --public-base-url",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
publicBaseUrl = next;
|
|
43
|
+
i += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (token.startsWith("--public-base-url=")) {
|
|
47
|
+
publicBaseUrl = token.slice("--public-base-url=".length);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (token.startsWith("--")) {
|
|
51
|
+
return {
|
|
52
|
+
action: "preflight",
|
|
53
|
+
adapterId: null,
|
|
54
|
+
publicBaseUrl: undefined,
|
|
55
|
+
dispatchToAgent,
|
|
56
|
+
error: `Unknown option: ${token}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
positional.push(token);
|
|
60
|
+
}
|
|
61
|
+
if (positional.length === 0) {
|
|
62
|
+
return { action: "preflight", adapterId: null, publicBaseUrl, dispatchToAgent, error: null };
|
|
63
|
+
}
|
|
64
|
+
const first = positional[0].toLowerCase();
|
|
65
|
+
if (isSetupAction(first)) {
|
|
66
|
+
const action = first;
|
|
67
|
+
const adapterId = positional[1] ? normalizeAdapterId(positional[1]) : null;
|
|
68
|
+
if (positional[1] && !adapterId) {
|
|
69
|
+
return {
|
|
70
|
+
action,
|
|
71
|
+
adapterId: null,
|
|
72
|
+
publicBaseUrl,
|
|
73
|
+
dispatchToAgent,
|
|
74
|
+
error: `Unknown adapter: ${positional[1]}`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (positional.length > 2) {
|
|
78
|
+
return {
|
|
79
|
+
action,
|
|
80
|
+
adapterId,
|
|
81
|
+
publicBaseUrl,
|
|
82
|
+
dispatchToAgent,
|
|
83
|
+
error: `Unexpected extra arguments: ${positional.slice(2).join(" ")}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return { action, adapterId, publicBaseUrl, dispatchToAgent, error: null };
|
|
87
|
+
}
|
|
88
|
+
const adapterId = normalizeAdapterId(first);
|
|
89
|
+
if (!adapterId) {
|
|
90
|
+
return {
|
|
91
|
+
action: "preflight",
|
|
92
|
+
adapterId: null,
|
|
93
|
+
publicBaseUrl,
|
|
94
|
+
dispatchToAgent,
|
|
95
|
+
error: `Unknown adapter or action: ${positional[0]}`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (positional.length === 1) {
|
|
99
|
+
return { action: "guide", adapterId, publicBaseUrl, dispatchToAgent, error: null };
|
|
100
|
+
}
|
|
101
|
+
const second = positional[1].toLowerCase();
|
|
102
|
+
if (!isSetupAction(second)) {
|
|
103
|
+
return {
|
|
104
|
+
action: "guide",
|
|
105
|
+
adapterId,
|
|
106
|
+
publicBaseUrl,
|
|
107
|
+
dispatchToAgent,
|
|
108
|
+
error: `Unknown action: ${positional[1]}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (positional.length > 2) {
|
|
112
|
+
return {
|
|
113
|
+
action: second,
|
|
114
|
+
adapterId,
|
|
115
|
+
publicBaseUrl,
|
|
116
|
+
dispatchToAgent,
|
|
117
|
+
error: `Unexpected extra arguments: ${positional.slice(2).join(" ")}`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return { action: second, adapterId, publicBaseUrl, dispatchToAgent, error: null };
|
|
121
|
+
}
|
|
122
|
+
export function shouldDispatchSetupToAgent(parsed) {
|
|
123
|
+
if (parsed.dispatchToAgent === "force")
|
|
124
|
+
return true;
|
|
125
|
+
if (parsed.dispatchToAgent === "off")
|
|
126
|
+
return false;
|
|
127
|
+
if (!parsed.adapterId)
|
|
128
|
+
return false;
|
|
129
|
+
if (parsed.action === "apply" || parsed.action === "check")
|
|
130
|
+
return false;
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
export function adapterFieldStatusLines(adapter, check) {
|
|
134
|
+
return adapter.fields.map((field) => {
|
|
135
|
+
const status = check.missing.includes(field.key) ? "MISSING" : "SET";
|
|
136
|
+
return `- ${field.key}: ${status} (${field.required ? "required" : "optional"})`;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
export function buildAgentSetupPrompt(opts) {
|
|
140
|
+
const adapter = adapterById(opts.check.id);
|
|
141
|
+
const normalizedBase = normalizePublicBaseUrl(opts.publicBaseUrl);
|
|
142
|
+
const webhookUrl = normalizedBase ? `${normalizedBase}${opts.plan.route}` : opts.plan.webhook_url;
|
|
143
|
+
const verifyFlag = normalizedBase ? ` --public-base-url ${normalizedBase}` : "";
|
|
144
|
+
return interpolateTemplate(MESSAGING_SETUP_BRIEF_TEMPLATE, {
|
|
145
|
+
adapter_name: adapter.name,
|
|
146
|
+
adapter_id: adapter.id,
|
|
147
|
+
state: opts.check.state,
|
|
148
|
+
config_path: opts.configPath ?? ".mu/config.json",
|
|
149
|
+
route: opts.plan.route,
|
|
150
|
+
webhook_url: webhookUrl ?? "(need public base URL)",
|
|
151
|
+
missing_fields: opts.check.missing.join(", ") || "(none)",
|
|
152
|
+
provider_steps: adapter.providerSetupSteps.map((step, index) => `${index + 1}. ${step}`).join("\n"),
|
|
153
|
+
field_status: adapterFieldStatusLines(adapter, opts.check).join("\n"),
|
|
154
|
+
verify_command: `/mu setup verify ${adapter.id}${verifyFlag}`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
export function dispatchSetupPromptToAgent(pi, ctx, prompt) {
|
|
158
|
+
if (ctx.isIdle()) {
|
|
159
|
+
pi.sendUserMessage(prompt);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
163
|
+
}
|
|
164
|
+
export function findCheckByAdapter(checks, adapterId) {
|
|
165
|
+
return checks.find((check) => check.id === adapterId) ?? null;
|
|
166
|
+
}
|
|
167
|
+
export function checksForAdapter(checks, adapterId) {
|
|
168
|
+
if (!adapterId) {
|
|
169
|
+
return checks;
|
|
170
|
+
}
|
|
171
|
+
const match = checks.find((check) => check.id === adapterId);
|
|
172
|
+
return match ? [match] : [];
|
|
173
|
+
}
|
|
174
|
+
export async function maybeDispatchAgentSetupBrief(opts) {
|
|
175
|
+
if (!opts.parsed.adapterId)
|
|
176
|
+
return false;
|
|
177
|
+
if (!shouldDispatchSetupToAgent(opts.parsed))
|
|
178
|
+
return false;
|
|
179
|
+
const check = findCheckByAdapter(opts.checks, opts.parsed.adapterId);
|
|
180
|
+
if (!check)
|
|
181
|
+
return false;
|
|
182
|
+
const plan = buildPlan(check, opts.parsed.publicBaseUrl);
|
|
183
|
+
const prompt = buildAgentSetupPrompt({
|
|
184
|
+
check,
|
|
185
|
+
plan,
|
|
186
|
+
configPath: opts.runtime.configPath,
|
|
187
|
+
publicBaseUrl: opts.parsed.publicBaseUrl,
|
|
188
|
+
});
|
|
189
|
+
dispatchSetupPromptToAgent(opts.pi, opts.ctx, prompt);
|
|
190
|
+
opts.ctx.ui.notify(`Sent ${check.name} setup brief to mu agent.`, "info");
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
export async function runInteractiveApply(ctx, adapterId) {
|
|
194
|
+
const adapter = adapterById(adapterId);
|
|
195
|
+
if (adapter.support === "planned") {
|
|
196
|
+
return `${adapter.name} is currently planned and not runtime-available.`;
|
|
197
|
+
}
|
|
198
|
+
const { checks, runtime } = await collectChecksCached(0);
|
|
199
|
+
if (!runtime.configPresence) {
|
|
200
|
+
return `Cannot read config presence: ${runtime.fetchError ?? "unknown error"}`;
|
|
201
|
+
}
|
|
202
|
+
const check = findCheckByAdapter(checks, adapterId);
|
|
203
|
+
if (!check) {
|
|
204
|
+
return `Unknown adapter: ${adapterId}`;
|
|
205
|
+
}
|
|
206
|
+
const overrides = {};
|
|
207
|
+
for (const key of check.missing) {
|
|
208
|
+
const entered = await ctx.ui.input(`${adapter.name}: enter value for ${key}`);
|
|
209
|
+
if (entered == null) {
|
|
210
|
+
return "Cancelled apply flow.";
|
|
211
|
+
}
|
|
212
|
+
const value = entered.trim();
|
|
213
|
+
if (value.length === 0) {
|
|
214
|
+
return `Cancelled: empty value for ${key}.`;
|
|
215
|
+
}
|
|
216
|
+
overrides[key] = value;
|
|
217
|
+
}
|
|
218
|
+
const applyConfirmed = await ctx.ui.confirm(`Apply ${adapter.name} configuration?`, `This writes to .mu/config.json and triggers in-process control-plane reload.`);
|
|
219
|
+
if (!applyConfirmed) {
|
|
220
|
+
return "Apply cancelled.";
|
|
221
|
+
}
|
|
222
|
+
const outcome = await applyAdapterConfig({
|
|
223
|
+
adapterId,
|
|
224
|
+
overrides,
|
|
225
|
+
presence: runtime.configPresence,
|
|
226
|
+
});
|
|
227
|
+
if (!outcome.ok) {
|
|
228
|
+
return `Apply failed: ${outcome.reason} (${outcome.missing_required_fields.join(", ")}).`;
|
|
229
|
+
}
|
|
230
|
+
const { checks: refreshedChecks } = await collectChecksCached(0);
|
|
231
|
+
const verify = buildVerifyOutcome(refreshedChecks, { adapterId });
|
|
232
|
+
const lines = [
|
|
233
|
+
`Updated config fields: ${outcome.updated_fields.join(", ") || "(none)"}`,
|
|
234
|
+
`Config path: ${outcome.config_path ?? runtime.configPath ?? "(unknown)"}`,
|
|
235
|
+
reloadOutcomeSummary(outcome.reload),
|
|
236
|
+
"",
|
|
237
|
+
verifyText(verify),
|
|
238
|
+
];
|
|
239
|
+
return lines.join("\n");
|
|
240
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime state fetching and adapter check collection for mu-messaging-setup.
|
|
3
|
+
*/
|
|
4
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
5
|
+
import type { AdapterCheck, RuntimeState } from "./types.js";
|
|
6
|
+
import type { ConfigReadResponse } from "./types.js";
|
|
7
|
+
export declare function fetchConfigPresence(): Promise<ConfigReadResponse>;
|
|
8
|
+
export declare function fetchRuntimeState(): Promise<RuntimeState>;
|
|
9
|
+
export declare function collectChecks(): Promise<{
|
|
10
|
+
checks: AdapterCheck[];
|
|
11
|
+
runtime: RuntimeState;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function collectChecksCached(ttlMs?: number): Promise<Awaited<ReturnType<typeof collectChecks>>>;
|
|
14
|
+
export declare function resetChecksCache(): void;
|
|
15
|
+
export declare function refreshMessagingStatus(ctx: ExtensionContext): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/extensions/messaging-setup/runtime.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEtE,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEvE;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,YAAY,CAAC,CA8C/D;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC;IAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC,CA6BhG;AAID,wBAAsB,mBAAmB,CAAC,KAAK,GAAE,MAAc,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAcnH;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime state fetching and adapter check collection for mu-messaging-setup.
|
|
3
|
+
*/
|
|
4
|
+
import { ADAPTERS, configured, deriveState, missingRequiredFields, nextStepForState } from "./adapters.js";
|
|
5
|
+
import { summarizeChecks } from "./ui.js";
|
|
6
|
+
import { fetchMuJson, fetchMuStatus, muServerUrl } from "../shared.js";
|
|
7
|
+
export async function fetchConfigPresence() {
|
|
8
|
+
return await fetchMuJson("/api/config", { timeoutMs: 4_000 });
|
|
9
|
+
}
|
|
10
|
+
export async function fetchRuntimeState() {
|
|
11
|
+
if (!muServerUrl()) {
|
|
12
|
+
return {
|
|
13
|
+
repoRoot: null,
|
|
14
|
+
configPath: null,
|
|
15
|
+
runtimeActive: false,
|
|
16
|
+
routesByAdapter: new Map(),
|
|
17
|
+
configPresence: null,
|
|
18
|
+
fetchError: "MU_SERVER_URL not set",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const [status, config] = await Promise.all([fetchMuStatus(2_000), fetchConfigPresence()]);
|
|
23
|
+
const cp = status.control_plane ?? {
|
|
24
|
+
active: false,
|
|
25
|
+
adapters: [],
|
|
26
|
+
routes: [],
|
|
27
|
+
};
|
|
28
|
+
const routesByAdapter = new Map();
|
|
29
|
+
for (const route of cp.routes ?? []) {
|
|
30
|
+
routesByAdapter.set(route.name, route.route);
|
|
31
|
+
}
|
|
32
|
+
for (const adapter of cp.adapters) {
|
|
33
|
+
if (!routesByAdapter.has(adapter)) {
|
|
34
|
+
routesByAdapter.set(adapter, `/webhooks/${adapter}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
repoRoot: status.repo_root,
|
|
39
|
+
configPath: config.config_path,
|
|
40
|
+
runtimeActive: cp.active,
|
|
41
|
+
routesByAdapter,
|
|
42
|
+
configPresence: config.presence,
|
|
43
|
+
fetchError: null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return {
|
|
48
|
+
repoRoot: null,
|
|
49
|
+
configPath: null,
|
|
50
|
+
runtimeActive: false,
|
|
51
|
+
routesByAdapter: new Map(),
|
|
52
|
+
configPresence: null,
|
|
53
|
+
fetchError: err instanceof Error ? err.message : String(err),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export async function collectChecks() {
|
|
58
|
+
const runtime = await fetchRuntimeState();
|
|
59
|
+
const checks = ADAPTERS.map((adapter) => {
|
|
60
|
+
const missing = missingRequiredFields(adapter, runtime.configPresence);
|
|
61
|
+
const isConfigured = configured(adapter, runtime.configPresence);
|
|
62
|
+
const active = runtime.routesByAdapter.has(adapter.id);
|
|
63
|
+
const route = runtime.routesByAdapter.get(adapter.id) ?? null;
|
|
64
|
+
const state = deriveState({ adapter, configured: isConfigured, active });
|
|
65
|
+
const notes = [...(adapter.notes ?? [])];
|
|
66
|
+
if (runtime.fetchError && runtime.fetchError !== "MU_SERVER_URL not set") {
|
|
67
|
+
notes.push(`Runtime/config status unavailable: ${runtime.fetchError}`);
|
|
68
|
+
}
|
|
69
|
+
if (runtime.configPath) {
|
|
70
|
+
notes.push(`Config path: ${runtime.configPath}`);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
id: adapter.id,
|
|
74
|
+
name: adapter.name,
|
|
75
|
+
support: adapter.support,
|
|
76
|
+
configured: isConfigured,
|
|
77
|
+
missing,
|
|
78
|
+
active,
|
|
79
|
+
route,
|
|
80
|
+
state,
|
|
81
|
+
next_step: nextStepForState({ state, missing }),
|
|
82
|
+
notes,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
return { checks, runtime };
|
|
86
|
+
}
|
|
87
|
+
let checksCache = null;
|
|
88
|
+
export async function collectChecksCached(ttlMs = 4_000) {
|
|
89
|
+
if (ttlMs <= 0) {
|
|
90
|
+
const value = await collectChecks();
|
|
91
|
+
checksCache = { tsMs: Date.now(), value };
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
if (checksCache && now - checksCache.tsMs <= ttlMs) {
|
|
96
|
+
return checksCache.value;
|
|
97
|
+
}
|
|
98
|
+
const value = await collectChecks();
|
|
99
|
+
checksCache = { tsMs: now, value };
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
export function resetChecksCache() {
|
|
103
|
+
checksCache = null;
|
|
104
|
+
}
|
|
105
|
+
export async function refreshMessagingStatus(ctx) {
|
|
106
|
+
if (!ctx.hasUI)
|
|
107
|
+
return;
|
|
108
|
+
const { checks } = await collectChecksCached();
|
|
109
|
+
ctx.ui.setStatus("mu-messaging", ctx.ui.theme.fg("dim", summarizeChecks(checks)));
|
|
110
|
+
}
|