@femtomc/mu-server 26.2.106 → 26.2.107
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 +5 -5
- package/dist/api/control_plane.js +64 -1
- package/dist/control_plane.d.ts +20 -2
- package/dist/control_plane.js +338 -43
- package/dist/control_plane_adapter_registry.d.ts +2 -1
- package/dist/control_plane_adapter_registry.js +4 -0
- package/dist/control_plane_telegram_generation.d.ts +2 -1
- package/dist/control_plane_telegram_generation.js +3 -0
- package/dist/server_routing.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@ HTTP API server for mu control-plane infrastructure.
|
|
|
4
4
|
Powers `mu serve`, messaging frontend transport routes, and
|
|
5
5
|
control-plane/session coordination endpoints.
|
|
6
6
|
|
|
7
|
-
> Scope note: server-routed
|
|
8
|
-
>
|
|
9
|
-
>
|
|
7
|
+
> Scope note: server-routed endpoints focus on runtime coordination only.
|
|
8
|
+
> Business reads/writes are CLI-first, while long-lived runtime coordination
|
|
9
|
+
> (runs/heartbeats/cron) stays server-owned.
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
@@ -44,7 +44,7 @@ Use `mu store paths --pretty` to resolve `<store>` for the active repo/workspace
|
|
|
44
44
|
|
|
45
45
|
### Health Check
|
|
46
46
|
|
|
47
|
-
- `GET /healthz`
|
|
47
|
+
- `GET /healthz` - Returns 200 OK
|
|
48
48
|
|
|
49
49
|
### Status
|
|
50
50
|
|
|
@@ -256,6 +256,6 @@ The server uses:
|
|
|
256
256
|
- Filesystem-backed JSONL event storage (FsJsonlStore)
|
|
257
257
|
- Bun's built-in HTTP server
|
|
258
258
|
- Control-plane adapter/webhook transport + session coordination routes
|
|
259
|
-
- Generation-supervised control-plane hot reload lifecycle
|
|
259
|
+
- Generation-supervised control-plane hot reload lifecycle
|
|
260
260
|
|
|
261
261
|
Control-plane/coordination data is persisted under `<store>/` (for example `<store>/events.jsonl` and `<store>/control-plane/*`). Use `mu store paths` to resolve `<store>` for your repo.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CONTROL_PLANE_CHANNEL_ADAPTER_SPECS, ingressModeForValue, summarizeInboundAttachmentPolicy, } from "@femtomc/mu-control-plane";
|
|
2
|
+
import { UI_ACTIONS_UNSUPPORTED_REASON, UI_COMPONENT_SUPPORT } from "../control_plane.js";
|
|
2
3
|
import { configRoutes } from "./config.js";
|
|
3
4
|
import { eventRoutes } from "./events.js";
|
|
4
5
|
import { identityRoutes } from "./identities.js";
|
|
@@ -92,9 +93,70 @@ function mediaInboundAttachmentCapability(config, channel) {
|
|
|
92
93
|
reason: hasToken ? null : "telegram_bot_token_missing",
|
|
93
94
|
};
|
|
94
95
|
}
|
|
96
|
+
const TEXT_ONLY_UI_COMPONENT_SUPPORT = {
|
|
97
|
+
text: true,
|
|
98
|
+
list: false,
|
|
99
|
+
key_value: false,
|
|
100
|
+
divider: false,
|
|
101
|
+
};
|
|
102
|
+
const CHANNEL_UI_CAPABILITIES = {
|
|
103
|
+
slack: {
|
|
104
|
+
supported: true,
|
|
105
|
+
reason: null,
|
|
106
|
+
components: UI_COMPONENT_SUPPORT,
|
|
107
|
+
actions: {
|
|
108
|
+
supported: true,
|
|
109
|
+
reason: null,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
discord: {
|
|
113
|
+
supported: true,
|
|
114
|
+
reason: null,
|
|
115
|
+
components: TEXT_ONLY_UI_COMPONENT_SUPPORT,
|
|
116
|
+
actions: {
|
|
117
|
+
supported: true,
|
|
118
|
+
reason: null,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
telegram: {
|
|
122
|
+
supported: true,
|
|
123
|
+
reason: null,
|
|
124
|
+
components: TEXT_ONLY_UI_COMPONENT_SUPPORT,
|
|
125
|
+
actions: {
|
|
126
|
+
supported: true,
|
|
127
|
+
reason: null,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
neovim: {
|
|
131
|
+
supported: true,
|
|
132
|
+
reason: null,
|
|
133
|
+
components: TEXT_ONLY_UI_COMPONENT_SUPPORT,
|
|
134
|
+
actions: {
|
|
135
|
+
supported: true,
|
|
136
|
+
reason: null,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
terminal: {
|
|
140
|
+
supported: true,
|
|
141
|
+
reason: null,
|
|
142
|
+
components: TEXT_ONLY_UI_COMPONENT_SUPPORT,
|
|
143
|
+
actions: {
|
|
144
|
+
supported: false,
|
|
145
|
+
reason: UI_ACTIONS_UNSUPPORTED_REASON,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
function uiCapabilityForChannel(channel) {
|
|
150
|
+
return CHANNEL_UI_CAPABILITIES[channel] ?? {
|
|
151
|
+
supported: true,
|
|
152
|
+
reason: null,
|
|
153
|
+
components: TEXT_ONLY_UI_COMPONENT_SUPPORT,
|
|
154
|
+
actions: { supported: false, reason: UI_ACTIONS_UNSUPPORTED_REASON },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
95
157
|
export async function controlPlaneRoutes(request, url, deps, headers) {
|
|
96
158
|
const path = url.pathname;
|
|
97
|
-
if (path === "/api/control-plane
|
|
159
|
+
if (path === "/api/control-plane/status") {
|
|
98
160
|
if (request.method !== "GET") {
|
|
99
161
|
return Response.json({ error: "Method Not Allowed" }, { status: 405, headers });
|
|
100
162
|
}
|
|
@@ -164,6 +226,7 @@ export async function controlPlaneRoutes(request, url, deps, headers) {
|
|
|
164
226
|
outbound_delivery: mediaOutboundCapability(config, spec.channel),
|
|
165
227
|
inbound_attachment_download: mediaInboundAttachmentCapability(config, spec.channel),
|
|
166
228
|
},
|
|
229
|
+
ui: uiCapabilityForChannel(spec.channel),
|
|
167
230
|
}));
|
|
168
231
|
return Response.json({
|
|
169
232
|
ok: true,
|
package/dist/control_plane.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { MessagingOperatorBackend, MessagingOperatorRuntime } from "@femtomc/mu-agent";
|
|
2
|
-
import { type
|
|
2
|
+
import { type UiDoc, type UiEvent } from "@femtomc/mu-core";
|
|
3
|
+
import { type GenerationTelemetryRecorder, type OutboxDeliveryHandlerResult, type OutboxRecord, UiCallbackTokenStore } from "@femtomc/mu-control-plane";
|
|
3
4
|
import { type ControlPlaneConfig, type ControlPlaneGenerationContext, type ControlPlaneHandle, type ControlPlaneSessionLifecycle, type TelegramGenerationSwapHooks, type WakeDeliveryObserver } from "./control_plane_contract.js";
|
|
4
5
|
import { detectAdapters } from "./control_plane_adapter_registry.js";
|
|
5
6
|
export type { ActiveAdapter, ControlPlaneConfig, ControlPlaneGenerationContext, ControlPlaneHandle, ControlPlaneSessionLifecycle, ControlPlaneSessionMutationAction, ControlPlaneSessionMutationResult, NotifyOperatorsOpts, NotifyOperatorsResult, TelegramGenerationReloadResult, TelegramGenerationRollbackTrigger, TelegramGenerationSwapHooks, WakeDeliveryEvent, WakeDeliveryObserver, WakeNotifyContext, WakeNotifyDecision, } from "./control_plane_contract.js";
|
|
@@ -52,8 +53,22 @@ export declare function buildTelegramSendMessagePayload(opts: {
|
|
|
52
53
|
richFormatting: boolean;
|
|
53
54
|
}): TelegramSendMessagePayload;
|
|
54
55
|
export declare function splitTelegramMessageText(text: string, maxLen?: number): string[];
|
|
56
|
+
export declare const UI_COMPONENT_SUPPORT: {
|
|
57
|
+
readonly text: true;
|
|
58
|
+
readonly list: true;
|
|
59
|
+
readonly key_value: true;
|
|
60
|
+
readonly divider: true;
|
|
61
|
+
};
|
|
62
|
+
export declare const UI_ACTIONS_UNSUPPORTED_REASON = "ui_actions_not_implemented";
|
|
63
|
+
export declare function uiDocTextLines(doc: UiDoc, opts?: {
|
|
64
|
+
includeActions?: boolean;
|
|
65
|
+
}): string[];
|
|
66
|
+
export declare function uiDocsTextFallback(uiDocs: readonly UiDoc[]): string;
|
|
55
67
|
export declare function splitSlackMessageText(text: string, maxLen?: number): string[];
|
|
56
|
-
type TelegramCallbackDataEncoder = (commandText: string
|
|
68
|
+
type TelegramCallbackDataEncoder = (commandText: string, opts: {
|
|
69
|
+
record: OutboxRecord;
|
|
70
|
+
uiEvent?: UiEvent;
|
|
71
|
+
}) => Promise<string>;
|
|
57
72
|
export declare function deliverTelegramOutboxRecord(opts: {
|
|
58
73
|
botToken: string;
|
|
59
74
|
record: OutboxRecord;
|
|
@@ -62,6 +77,9 @@ export declare function deliverTelegramOutboxRecord(opts: {
|
|
|
62
77
|
export declare function deliverSlackOutboxRecord(opts: {
|
|
63
78
|
botToken: string;
|
|
64
79
|
record: OutboxRecord;
|
|
80
|
+
uiCallbackTokenStore?: UiCallbackTokenStore;
|
|
81
|
+
nowMs?: () => number;
|
|
82
|
+
fetchImpl?: typeof fetch;
|
|
65
83
|
}): Promise<OutboxDeliveryHandlerResult>;
|
|
66
84
|
export type BootstrapControlPlaneOpts = {
|
|
67
85
|
repoRoot: string;
|
package/dist/control_plane.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { applyHudStylePreset, normalizeHudDocs } from "@femtomc/mu-core";
|
|
2
|
-
import { ControlPlaneCommandPipeline, ControlPlaneOutbox, ControlPlaneRuntime, buildSlackHudActionId, getControlPlanePaths, TelegramControlPlaneAdapterSpec, } from "@femtomc/mu-control-plane";
|
|
1
|
+
import { applyHudStylePreset, normalizeHudDocs, normalizeUiDocs, } from "@femtomc/mu-core";
|
|
2
|
+
import { ControlPlaneCommandPipeline, ControlPlaneOutbox, ControlPlaneRuntime, buildSanitizedUiEventForAction, buildSlackHudActionId, getControlPlanePaths, issueUiDocActionPayloads, TelegramControlPlaneAdapterSpec, uiActionPayloadContextFromOutboxRecord, uiDocActionPayloadKey, UiCallbackTokenStore, } from "@femtomc/mu-control-plane";
|
|
3
3
|
import { DEFAULT_MU_CONFIG } from "./config.js";
|
|
4
4
|
import { buildMessagingOperatorRuntime, createOutboxDrainLoop } from "./control_plane_bootstrap_helpers.js";
|
|
5
5
|
import { buildWakeOutboundEnvelope, resolveWakeFanoutCapability, wakeDeliveryMetadataFromOutboxRecord, wakeDispatchReasonCode, wakeFanoutDedupeKey, } from "./control_plane_wake_delivery.js";
|
|
@@ -146,8 +146,19 @@ const SLACK_BLOCK_TEXT_MAX_LEN = 3_000;
|
|
|
146
146
|
const SLACK_BLOCKS_MAX = 50;
|
|
147
147
|
const SLACK_ACTIONS_MAX_PER_BLOCK = 5;
|
|
148
148
|
const SLACK_ACTIONS_MAX_TOTAL = 20;
|
|
149
|
+
const SLACK_ACTION_VALUE_MAX_CHARS = 2_000;
|
|
150
|
+
const SLACK_UI_EVENT_ACTION_ID = buildSlackHudActionId("ui_event");
|
|
149
151
|
const SLACK_DOCS_MAX = 3;
|
|
150
152
|
const SLACK_SECTION_LINE_MAX = 8;
|
|
153
|
+
const UI_DOCS_MAX = 3;
|
|
154
|
+
const UI_CALLBACK_TOKEN_TTL_MS = 15 * 60_000;
|
|
155
|
+
export const UI_COMPONENT_SUPPORT = {
|
|
156
|
+
text: true,
|
|
157
|
+
list: true,
|
|
158
|
+
key_value: true,
|
|
159
|
+
divider: true,
|
|
160
|
+
};
|
|
161
|
+
export const UI_ACTIONS_UNSUPPORTED_REASON = "ui_actions_not_implemented";
|
|
151
162
|
function hudDocsForPresentation(input, maxDocs) {
|
|
152
163
|
return normalizeHudDocs(input, { maxDocs }).map((doc) => applyHudStylePreset(doc) ?? doc);
|
|
153
164
|
}
|
|
@@ -257,6 +268,88 @@ function hudDocSectionLines(doc) {
|
|
|
257
268
|
}
|
|
258
269
|
return lines;
|
|
259
270
|
}
|
|
271
|
+
function uiDocComponentLines(doc) {
|
|
272
|
+
const lines = [];
|
|
273
|
+
const components = [...doc.components].sort((a, b) => a.id.localeCompare(b.id));
|
|
274
|
+
for (const component of components) {
|
|
275
|
+
switch (component.kind) {
|
|
276
|
+
case "text": {
|
|
277
|
+
lines.push(component.text);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case "list": {
|
|
281
|
+
if (component.title) {
|
|
282
|
+
lines.push(component.title);
|
|
283
|
+
}
|
|
284
|
+
for (const item of component.items) {
|
|
285
|
+
lines.push(`• ${item.label}${item.detail ? ` · ${item.detail}` : ""}`);
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case "key_value": {
|
|
290
|
+
if (component.title) {
|
|
291
|
+
lines.push(component.title);
|
|
292
|
+
}
|
|
293
|
+
for (const row of component.rows) {
|
|
294
|
+
lines.push(`• ${row.key}: ${row.value}`);
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
case "divider": {
|
|
299
|
+
lines.push("────────");
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return lines;
|
|
305
|
+
}
|
|
306
|
+
function uiDocActionLines(doc) {
|
|
307
|
+
const actions = [...doc.actions].sort((a, b) => a.id.localeCompare(b.id));
|
|
308
|
+
return actions.map((action) => {
|
|
309
|
+
const parts = [`• ${action.label}`];
|
|
310
|
+
if (action.description) {
|
|
311
|
+
parts.push(action.description);
|
|
312
|
+
}
|
|
313
|
+
parts.push(`(id=${action.id})`);
|
|
314
|
+
return parts.join(" ");
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
export function uiDocTextLines(doc, opts = {}) {
|
|
318
|
+
const lines = [`UI · ${doc.title}`];
|
|
319
|
+
if (doc.summary) {
|
|
320
|
+
lines.push(doc.summary);
|
|
321
|
+
}
|
|
322
|
+
const componentLines = uiDocComponentLines(doc);
|
|
323
|
+
if (componentLines.length > 0) {
|
|
324
|
+
lines.push(...componentLines);
|
|
325
|
+
}
|
|
326
|
+
if (opts.includeActions !== false) {
|
|
327
|
+
const actionLines = uiDocActionLines(doc);
|
|
328
|
+
if (actionLines.length > 0) {
|
|
329
|
+
lines.push("Actions:");
|
|
330
|
+
lines.push(...actionLines);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return lines;
|
|
334
|
+
}
|
|
335
|
+
export function uiDocsTextFallback(uiDocs) {
|
|
336
|
+
if (uiDocs.length === 0) {
|
|
337
|
+
return "";
|
|
338
|
+
}
|
|
339
|
+
const sections = uiDocs.map((doc) => uiDocTextLines(doc).join("\n"));
|
|
340
|
+
return sections.join("\n\n");
|
|
341
|
+
}
|
|
342
|
+
function appendUiDocText(body, uiDocs) {
|
|
343
|
+
const fallback = uiDocsTextFallback(uiDocs);
|
|
344
|
+
if (!fallback) {
|
|
345
|
+
return body;
|
|
346
|
+
}
|
|
347
|
+
const trimmed = body.trim();
|
|
348
|
+
if (trimmed.length === 0) {
|
|
349
|
+
return fallback;
|
|
350
|
+
}
|
|
351
|
+
return `${trimmed}\n\n${fallback}`;
|
|
352
|
+
}
|
|
260
353
|
function hudActionButtons(doc) {
|
|
261
354
|
return doc.actions.slice(0, SLACK_ACTIONS_MAX_TOTAL).map((action) => ({
|
|
262
355
|
type: "button",
|
|
@@ -264,10 +357,44 @@ function hudActionButtons(doc) {
|
|
|
264
357
|
type: "plain_text",
|
|
265
358
|
text: truncateSlackText(action.label, 75),
|
|
266
359
|
},
|
|
267
|
-
value: truncateSlackText(action.command_text,
|
|
360
|
+
value: truncateSlackText(action.command_text, SLACK_ACTION_VALUE_MAX_CHARS),
|
|
268
361
|
action_id: buildSlackHudActionId(action.id),
|
|
269
362
|
}));
|
|
270
363
|
}
|
|
364
|
+
function uiDocActionTextLine(action, opts = {}) {
|
|
365
|
+
const parts = [`• ${action.label}`];
|
|
366
|
+
if (action.description) {
|
|
367
|
+
parts.push(action.description);
|
|
368
|
+
}
|
|
369
|
+
parts.push(`(id=${action.id}${opts.suffix ? `; ${opts.suffix}` : ""})`);
|
|
370
|
+
return parts.join(" ");
|
|
371
|
+
}
|
|
372
|
+
function uiDocActionButtons(doc, actionPayloadsByKey) {
|
|
373
|
+
const buttons = [];
|
|
374
|
+
const fallbackLines = [];
|
|
375
|
+
const actions = [...doc.actions].sort((a, b) => a.id.localeCompare(b.id)).slice(0, SLACK_ACTIONS_MAX_TOTAL);
|
|
376
|
+
for (const action of actions) {
|
|
377
|
+
const payload = actionPayloadsByKey.get(uiDocActionPayloadKey(doc.ui_id, action.id));
|
|
378
|
+
if (!payload) {
|
|
379
|
+
fallbackLines.push(uiDocActionTextLine(action, { suffix: "interactive unavailable" }));
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (payload.length > SLACK_ACTION_VALUE_MAX_CHARS) {
|
|
383
|
+
fallbackLines.push(uiDocActionTextLine(action, { suffix: "interactive payload too large" }));
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
buttons.push({
|
|
387
|
+
type: "button",
|
|
388
|
+
text: {
|
|
389
|
+
type: "plain_text",
|
|
390
|
+
text: truncateSlackText(action.label, 75),
|
|
391
|
+
},
|
|
392
|
+
value: payload,
|
|
393
|
+
action_id: SLACK_UI_EVENT_ACTION_ID,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return { buttons, fallbackLines };
|
|
397
|
+
}
|
|
271
398
|
export function splitSlackMessageText(text, maxLen = SLACK_MESSAGE_MAX_LEN) {
|
|
272
399
|
if (text.length <= maxLen) {
|
|
273
400
|
return [text];
|
|
@@ -288,15 +415,17 @@ export function splitSlackMessageText(text, maxLen = SLACK_MESSAGE_MAX_LEN) {
|
|
|
288
415
|
}
|
|
289
416
|
return chunks;
|
|
290
417
|
}
|
|
291
|
-
function slackBlocksForOutboxRecord(record, body) {
|
|
418
|
+
function slackBlocksForOutboxRecord(record, body, uiDocs, opts = {}) {
|
|
292
419
|
const hudDocs = hudDocsForPresentation(record.envelope.metadata?.hud_docs, SLACK_DOCS_MAX);
|
|
293
|
-
if (hudDocs.length === 0) {
|
|
420
|
+
if (hudDocs.length === 0 && uiDocs.length === 0) {
|
|
294
421
|
return undefined;
|
|
295
422
|
}
|
|
423
|
+
const uiDocActionPayloadsByKey = opts.uiDocActionPayloadsByKey ?? new Map();
|
|
296
424
|
const blocks = [];
|
|
425
|
+
const headerText = body.trim().length > 0 ? body : "Update";
|
|
297
426
|
blocks.push({
|
|
298
427
|
type: "section",
|
|
299
|
-
text: { type: "mrkdwn", text: truncateSlackText(
|
|
428
|
+
text: { type: "mrkdwn", text: truncateSlackText(headerText) },
|
|
300
429
|
});
|
|
301
430
|
for (const doc of hudDocs) {
|
|
302
431
|
if (blocks.length >= SLACK_BLOCKS_MAX) {
|
|
@@ -327,16 +456,53 @@ function slackBlocksForOutboxRecord(record, body) {
|
|
|
327
456
|
});
|
|
328
457
|
}
|
|
329
458
|
}
|
|
459
|
+
for (const doc of uiDocs) {
|
|
460
|
+
if (blocks.length >= SLACK_BLOCKS_MAX) {
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
const lines = uiDocTextLines(doc, { includeActions: false });
|
|
464
|
+
blocks.push({
|
|
465
|
+
type: "context",
|
|
466
|
+
elements: [{ type: "mrkdwn", text: truncateSlackText(lines[0]) }],
|
|
467
|
+
});
|
|
468
|
+
for (const line of lines.slice(1)) {
|
|
469
|
+
if (blocks.length >= SLACK_BLOCKS_MAX) {
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
blocks.push({
|
|
473
|
+
type: "section",
|
|
474
|
+
text: { type: "mrkdwn", text: truncateSlackText(line) },
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
const actionRender = uiDocActionButtons(doc, uiDocActionPayloadsByKey);
|
|
478
|
+
for (let idx = 0; idx < actionRender.buttons.length; idx += SLACK_ACTIONS_MAX_PER_BLOCK) {
|
|
479
|
+
if (blocks.length >= SLACK_BLOCKS_MAX) {
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
blocks.push({
|
|
483
|
+
type: "actions",
|
|
484
|
+
elements: actionRender.buttons.slice(idx, idx + SLACK_ACTIONS_MAX_PER_BLOCK),
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
if (actionRender.fallbackLines.length > 0 && blocks.length < SLACK_BLOCKS_MAX) {
|
|
488
|
+
blocks.push({
|
|
489
|
+
type: "section",
|
|
490
|
+
text: {
|
|
491
|
+
type: "mrkdwn",
|
|
492
|
+
text: truncateSlackText(`Actions:\n${actionRender.fallbackLines.join("\n")}`),
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
330
497
|
return blocks.slice(0, SLACK_BLOCKS_MAX);
|
|
331
498
|
}
|
|
332
499
|
function slackThreadTsFromMetadata(metadata) {
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
return value.trim();
|
|
337
|
-
}
|
|
500
|
+
const value = metadata?.slack_thread_ts;
|
|
501
|
+
if (typeof value !== "string") {
|
|
502
|
+
return undefined;
|
|
338
503
|
}
|
|
339
|
-
|
|
504
|
+
const trimmed = value.trim();
|
|
505
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
340
506
|
}
|
|
341
507
|
function slackStatusMessageTsFromMetadata(metadata) {
|
|
342
508
|
const value = metadata?.slack_status_message_ts;
|
|
@@ -353,57 +519,135 @@ const TELEGRAM_DOCS_MAX = 2;
|
|
|
353
519
|
function utf8ByteLength(value) {
|
|
354
520
|
return new TextEncoder().encode(value).length;
|
|
355
521
|
}
|
|
356
|
-
function telegramTextForOutboxRecord(record, body) {
|
|
522
|
+
function telegramTextForOutboxRecord(record, body, uiDocs) {
|
|
357
523
|
const hudDocs = hudDocsForPresentation(record.envelope.metadata?.hud_docs, TELEGRAM_DOCS_MAX);
|
|
358
|
-
if (hudDocs.length === 0) {
|
|
359
|
-
return body;
|
|
360
|
-
}
|
|
361
524
|
const lines = [body.trim()];
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
525
|
+
if (hudDocs.length > 0) {
|
|
526
|
+
for (const doc of hudDocs) {
|
|
527
|
+
lines.push("", `HUD · ${doc.title}`);
|
|
528
|
+
for (const sectionLine of hudDocSectionLines(doc)) {
|
|
529
|
+
lines.push(stripSlackMrkdwn(sectionLine));
|
|
530
|
+
}
|
|
366
531
|
}
|
|
367
532
|
}
|
|
533
|
+
const uiFallback = uiDocsTextFallback(uiDocs);
|
|
534
|
+
if (uiFallback) {
|
|
535
|
+
lines.push("", uiFallback);
|
|
536
|
+
}
|
|
368
537
|
return lines.join("\n").trim();
|
|
369
538
|
}
|
|
539
|
+
function telegramReplyMarkupFromButtons(buttons) {
|
|
540
|
+
if (buttons.length === 0) {
|
|
541
|
+
return undefined;
|
|
542
|
+
}
|
|
543
|
+
const inline_keyboard = [];
|
|
544
|
+
for (let idx = 0; idx < buttons.length; idx += TELEGRAM_ACTIONS_PER_ROW) {
|
|
545
|
+
inline_keyboard.push(buttons.slice(idx, idx + TELEGRAM_ACTIONS_PER_ROW));
|
|
546
|
+
}
|
|
547
|
+
return { inline_keyboard };
|
|
548
|
+
}
|
|
549
|
+
function telegramOverflowText(lines) {
|
|
550
|
+
if (lines.length === 0) {
|
|
551
|
+
return "";
|
|
552
|
+
}
|
|
553
|
+
return `\n\nActions:\n${lines.join("\n")}`;
|
|
554
|
+
}
|
|
370
555
|
async function compileTelegramHudActions(opts) {
|
|
371
556
|
const hudDocs = hudDocsForPresentation(opts.record.envelope.metadata?.hud_docs, TELEGRAM_DOCS_MAX);
|
|
372
557
|
if (hudDocs.length === 0) {
|
|
373
|
-
return {
|
|
558
|
+
return { buttonEntries: [], overflowLines: [] };
|
|
374
559
|
}
|
|
375
|
-
const
|
|
560
|
+
const buttonEntries = [];
|
|
376
561
|
const overflowLines = [];
|
|
377
562
|
const actions = hudDocs.flatMap((doc) => doc.actions).slice(0, TELEGRAM_ACTIONS_MAX_TOTAL);
|
|
378
563
|
for (const action of actions) {
|
|
564
|
+
const fallbackLine = `• ${action.label}: ${action.command_text}`;
|
|
379
565
|
let callbackData = action.command_text;
|
|
380
566
|
if (opts.encodeCallbackData) {
|
|
381
567
|
try {
|
|
382
|
-
callbackData = await opts.encodeCallbackData(action.command_text);
|
|
568
|
+
callbackData = await opts.encodeCallbackData(action.command_text, { record: opts.record });
|
|
383
569
|
}
|
|
384
570
|
catch {
|
|
385
|
-
overflowLines.push(
|
|
571
|
+
overflowLines.push(fallbackLine);
|
|
386
572
|
continue;
|
|
387
573
|
}
|
|
388
574
|
}
|
|
389
575
|
if (utf8ByteLength(callbackData) > TELEGRAM_CALLBACK_DATA_MAX_BYTES) {
|
|
390
|
-
overflowLines.push(
|
|
576
|
+
overflowLines.push(fallbackLine);
|
|
391
577
|
continue;
|
|
392
578
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
579
|
+
buttonEntries.push({
|
|
580
|
+
button: {
|
|
581
|
+
text: action.label.slice(0, 64),
|
|
582
|
+
callback_data: callbackData,
|
|
583
|
+
},
|
|
584
|
+
fallbackLine,
|
|
396
585
|
});
|
|
397
586
|
}
|
|
398
|
-
const inline_keyboard = [];
|
|
399
|
-
for (let idx = 0; idx < buttons.length; idx += TELEGRAM_ACTIONS_PER_ROW) {
|
|
400
|
-
inline_keyboard.push(buttons.slice(idx, idx + TELEGRAM_ACTIONS_PER_ROW));
|
|
401
|
-
}
|
|
402
587
|
return {
|
|
403
|
-
|
|
404
|
-
|
|
588
|
+
buttonEntries,
|
|
589
|
+
overflowLines,
|
|
405
590
|
};
|
|
406
591
|
}
|
|
592
|
+
function commandTextForUiDocAction(action) {
|
|
593
|
+
const fromMetadata = typeof action.metadata.command_text === "string" ? action.metadata.command_text.trim() : "";
|
|
594
|
+
if (fromMetadata.length === 0) {
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
return fromMetadata;
|
|
598
|
+
}
|
|
599
|
+
async function compileTelegramUiDocActions(opts) {
|
|
600
|
+
if (opts.uiDocs.length === 0) {
|
|
601
|
+
return { buttonEntries: [], overflowLines: [] };
|
|
602
|
+
}
|
|
603
|
+
const buttonEntries = [];
|
|
604
|
+
const overflowLines = [];
|
|
605
|
+
for (const doc of opts.uiDocs) {
|
|
606
|
+
const actions = [...doc.actions].sort((a, b) => a.id.localeCompare(b.id));
|
|
607
|
+
for (const action of actions) {
|
|
608
|
+
const uiEvent = buildSanitizedUiEventForAction({
|
|
609
|
+
doc,
|
|
610
|
+
action,
|
|
611
|
+
createdAtMs: opts.nowMs,
|
|
612
|
+
});
|
|
613
|
+
const commandText = commandTextForUiDocAction(action);
|
|
614
|
+
const fallbackLine = commandText
|
|
615
|
+
? `• ${action.label}: ${commandText}`
|
|
616
|
+
: `• ${action.label}: interactive unavailable (missing command_text)`;
|
|
617
|
+
if (!commandText || !uiEvent) {
|
|
618
|
+
overflowLines.push(fallbackLine);
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
if (!opts.encodeCallbackData) {
|
|
622
|
+
overflowLines.push(fallbackLine);
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
let callbackData;
|
|
626
|
+
try {
|
|
627
|
+
callbackData = await opts.encodeCallbackData(commandText, {
|
|
628
|
+
record: opts.record,
|
|
629
|
+
uiEvent,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
overflowLines.push(fallbackLine);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (utf8ByteLength(callbackData) > TELEGRAM_CALLBACK_DATA_MAX_BYTES) {
|
|
637
|
+
overflowLines.push(fallbackLine);
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
buttonEntries.push({
|
|
641
|
+
button: {
|
|
642
|
+
text: action.label.slice(0, 64),
|
|
643
|
+
callback_data: callbackData,
|
|
644
|
+
},
|
|
645
|
+
fallbackLine,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return { buttonEntries, overflowLines };
|
|
650
|
+
}
|
|
407
651
|
async function postTelegramMessage(botToken, payload) {
|
|
408
652
|
return await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
409
653
|
method: "POST",
|
|
@@ -492,13 +736,28 @@ async function sendTelegramTextChunks(opts) {
|
|
|
492
736
|
}
|
|
493
737
|
export async function deliverTelegramOutboxRecord(opts) {
|
|
494
738
|
const { botToken, record } = opts;
|
|
739
|
+
const uiDocs = normalizeUiDocs(record.envelope.metadata?.ui_docs, { maxDocs: TELEGRAM_DOCS_MAX });
|
|
740
|
+
const nowMs = Math.trunc(Date.now());
|
|
495
741
|
const hudActions = await compileTelegramHudActions({
|
|
496
742
|
record,
|
|
497
743
|
encodeCallbackData: opts.encodeCallbackData,
|
|
498
744
|
});
|
|
499
|
-
const
|
|
745
|
+
const uiActions = await compileTelegramUiDocActions({
|
|
746
|
+
record,
|
|
747
|
+
uiDocs,
|
|
748
|
+
nowMs,
|
|
749
|
+
encodeCallbackData: opts.encodeCallbackData,
|
|
750
|
+
});
|
|
751
|
+
const combinedEntries = [...hudActions.buttonEntries, ...uiActions.buttonEntries];
|
|
752
|
+
const visibleEntries = combinedEntries.slice(0, TELEGRAM_ACTIONS_MAX_TOTAL);
|
|
753
|
+
const overflowLines = [
|
|
754
|
+
...hudActions.overflowLines,
|
|
755
|
+
...uiActions.overflowLines,
|
|
756
|
+
...combinedEntries.slice(TELEGRAM_ACTIONS_MAX_TOTAL).map((entry) => entry.fallbackLine),
|
|
757
|
+
];
|
|
758
|
+
const replyMarkup = telegramReplyMarkupFromButtons(visibleEntries.map((entry) => entry.button));
|
|
500
759
|
const replyToMessageId = maybeParseTelegramMessageId(record.envelope.metadata?.telegram_reply_to_message_id);
|
|
501
|
-
const telegramText = `${telegramTextForOutboxRecord(record, record.envelope.body)}${
|
|
760
|
+
const telegramText = `${telegramTextForOutboxRecord(record, record.envelope.body, uiDocs)}${telegramOverflowText(overflowLines)}`.trim();
|
|
502
761
|
const fallbackMessagePayload = {
|
|
503
762
|
...buildTelegramSendMessagePayload({
|
|
504
763
|
chatId: record.envelope.channel_conversation_id,
|
|
@@ -640,7 +899,8 @@ export async function deliverTelegramOutboxRecord(opts) {
|
|
|
640
899
|
};
|
|
641
900
|
}
|
|
642
901
|
async function postSlackJson(opts) {
|
|
643
|
-
const
|
|
902
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
903
|
+
const response = await fetchImpl(`https://slack.com/api/${opts.method}`, {
|
|
644
904
|
method: "POST",
|
|
645
905
|
headers: {
|
|
646
906
|
Authorization: `Bearer ${opts.botToken}`,
|
|
@@ -653,10 +913,31 @@ async function postSlackJson(opts) {
|
|
|
653
913
|
}
|
|
654
914
|
export async function deliverSlackOutboxRecord(opts) {
|
|
655
915
|
const { botToken, record } = opts;
|
|
916
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
656
917
|
const attachments = record.envelope.attachments ?? [];
|
|
657
|
-
const
|
|
918
|
+
const uiDocs = normalizeUiDocs(record.envelope.metadata?.ui_docs, { maxDocs: UI_DOCS_MAX });
|
|
919
|
+
let uiDocActionPayloadsByKey = new Map();
|
|
920
|
+
if (opts.uiCallbackTokenStore && uiDocs.some((doc) => doc.actions.length > 0)) {
|
|
921
|
+
const issued = await issueUiDocActionPayloads({
|
|
922
|
+
uiDocs,
|
|
923
|
+
tokenStore: opts.uiCallbackTokenStore,
|
|
924
|
+
context: uiActionPayloadContextFromOutboxRecord(record),
|
|
925
|
+
ttlMs: UI_CALLBACK_TOKEN_TTL_MS,
|
|
926
|
+
nowMs: Math.trunc((opts.nowMs ?? Date.now)()),
|
|
927
|
+
});
|
|
928
|
+
uiDocActionPayloadsByKey = new Map(issued.map((entry) => [entry.key, entry.payload_json]));
|
|
929
|
+
}
|
|
930
|
+
const renderedBodyForBlocks = renderSlackMarkdown(record.envelope.body);
|
|
931
|
+
const blocks = slackBlocksForOutboxRecord(record, renderedBodyForBlocks, uiDocs, {
|
|
932
|
+
uiDocActionPayloadsByKey,
|
|
933
|
+
});
|
|
934
|
+
const bodyForText = blocks
|
|
935
|
+
? record.envelope.body.trim().length > 0
|
|
936
|
+
? record.envelope.body
|
|
937
|
+
: "Update"
|
|
938
|
+
: appendUiDocText(record.envelope.body, uiDocs);
|
|
939
|
+
const renderedBody = renderSlackMarkdown(bodyForText);
|
|
658
940
|
const textChunks = splitSlackMessageText(renderedBody);
|
|
659
|
-
const blocks = slackBlocksForOutboxRecord(record, renderedBody);
|
|
660
941
|
const threadTs = slackThreadTsFromMetadata(record.envelope.metadata);
|
|
661
942
|
const statusMessageTs = slackStatusMessageTsFromMetadata(record.envelope.metadata);
|
|
662
943
|
if (attachments.length === 0) {
|
|
@@ -673,6 +954,7 @@ export async function deliverSlackOutboxRecord(opts) {
|
|
|
673
954
|
unfurl_media: false,
|
|
674
955
|
...(blocks ? { blocks } : {}),
|
|
675
956
|
},
|
|
957
|
+
fetchImpl,
|
|
676
958
|
});
|
|
677
959
|
if (updated.response.ok && updated.payload?.ok) {
|
|
678
960
|
chunkStartIndex = 1;
|
|
@@ -707,6 +989,7 @@ export async function deliverSlackOutboxRecord(opts) {
|
|
|
707
989
|
...(index === 0 && blocks ? { blocks } : {}),
|
|
708
990
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
709
991
|
},
|
|
992
|
+
fetchImpl,
|
|
710
993
|
});
|
|
711
994
|
if (delivered.response.ok && delivered.payload?.ok) {
|
|
712
995
|
continue;
|
|
@@ -733,7 +1016,7 @@ export async function deliverSlackOutboxRecord(opts) {
|
|
|
733
1016
|
error: `slack attachment ${index + 1} missing reference.url`,
|
|
734
1017
|
};
|
|
735
1018
|
}
|
|
736
|
-
const source = await
|
|
1019
|
+
const source = await fetchImpl(referenceUrl);
|
|
737
1020
|
if (!source.ok) {
|
|
738
1021
|
const sourceErr = await source.text().catch(() => "");
|
|
739
1022
|
if (source.status === 429 || source.status >= 500) {
|
|
@@ -759,7 +1042,7 @@ export async function deliverSlackOutboxRecord(opts) {
|
|
|
759
1042
|
form.set("thread_ts", threadTs);
|
|
760
1043
|
}
|
|
761
1044
|
form.set("file", new Blob([bytes], { type: contentType }), filename);
|
|
762
|
-
const uploaded = await
|
|
1045
|
+
const uploaded = await fetchImpl("https://slack.com/api/files.upload", {
|
|
763
1046
|
method: "POST",
|
|
764
1047
|
headers: {
|
|
765
1048
|
Authorization: `Bearer ${botToken}`,
|
|
@@ -797,6 +1080,7 @@ export async function deliverSlackOutboxRecord(opts) {
|
|
|
797
1080
|
unfurl_media: false,
|
|
798
1081
|
...(threadTs ? { thread_ts: threadTs } : {}),
|
|
799
1082
|
},
|
|
1083
|
+
fetchImpl,
|
|
800
1084
|
});
|
|
801
1085
|
if (fallback.response.ok && fallback.payload?.ok) {
|
|
802
1086
|
continue;
|
|
@@ -840,6 +1124,7 @@ export async function bootstrapControlPlane(opts) {
|
|
|
840
1124
|
return null;
|
|
841
1125
|
}
|
|
842
1126
|
const paths = getControlPlanePaths(opts.repoRoot);
|
|
1127
|
+
const uiCallbackTokenStore = new UiCallbackTokenStore(paths.uiCallbackTokenPath);
|
|
843
1128
|
const runtime = new ControlPlaneRuntime({ repoRoot: opts.repoRoot });
|
|
844
1129
|
let pipeline = null;
|
|
845
1130
|
let outboxDrainLoop = null;
|
|
@@ -847,6 +1132,7 @@ export async function bootstrapControlPlane(opts) {
|
|
|
847
1132
|
const outboundDeliveryChannels = new Set();
|
|
848
1133
|
const adapterMap = new Map();
|
|
849
1134
|
try {
|
|
1135
|
+
await uiCallbackTokenStore.load();
|
|
850
1136
|
await runtime.start();
|
|
851
1137
|
const operator = opts.operatorRuntime !== undefined
|
|
852
1138
|
? opts.operatorRuntime
|
|
@@ -868,6 +1154,7 @@ export async function bootstrapControlPlane(opts) {
|
|
|
868
1154
|
const telegramManager = new TelegramAdapterGenerationManager({
|
|
869
1155
|
pipeline,
|
|
870
1156
|
outbox,
|
|
1157
|
+
uiCallbackTokenStore,
|
|
871
1158
|
initialConfig: controlPlaneConfig,
|
|
872
1159
|
onOutboxEnqueued: () => {
|
|
873
1160
|
scheduleOutboxDrainRef?.();
|
|
@@ -881,6 +1168,7 @@ export async function bootstrapControlPlane(opts) {
|
|
|
881
1168
|
config: controlPlaneConfig,
|
|
882
1169
|
pipeline,
|
|
883
1170
|
outbox,
|
|
1171
|
+
uiCallbackTokenStore,
|
|
884
1172
|
})) {
|
|
885
1173
|
const route = adapter.spec.route;
|
|
886
1174
|
if (adapterMap.has(route)) {
|
|
@@ -939,6 +1227,7 @@ export async function bootstrapControlPlane(opts) {
|
|
|
939
1227
|
return await deliverSlackOutboxRecord({
|
|
940
1228
|
botToken: slackBotToken,
|
|
941
1229
|
record,
|
|
1230
|
+
uiCallbackTokenStore,
|
|
942
1231
|
});
|
|
943
1232
|
},
|
|
944
1233
|
},
|
|
@@ -952,12 +1241,18 @@ export async function bootstrapControlPlane(opts) {
|
|
|
952
1241
|
return await deliverTelegramOutboxRecord({
|
|
953
1242
|
botToken: telegramBotToken,
|
|
954
1243
|
record,
|
|
955
|
-
encodeCallbackData: async (commandText) => {
|
|
1244
|
+
encodeCallbackData: async (commandText, encodeOpts) => {
|
|
956
1245
|
const active = telegramManager.activeAdapter();
|
|
957
1246
|
if (!active) {
|
|
958
1247
|
return commandText;
|
|
959
1248
|
}
|
|
960
|
-
return await active.issueCallbackToken({
|
|
1249
|
+
return await active.issueCallbackToken({
|
|
1250
|
+
commandText,
|
|
1251
|
+
actorId: encodeOpts.record.envelope.correlation.actor_id,
|
|
1252
|
+
actorBindingId: encodeOpts.record.envelope.correlation.actor_binding_id,
|
|
1253
|
+
conversationId: encodeOpts.record.envelope.channel_conversation_id,
|
|
1254
|
+
uiEvent: encodeOpts.uiEvent,
|
|
1255
|
+
});
|
|
961
1256
|
},
|
|
962
1257
|
});
|
|
963
1258
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ControlPlaneAdapter, type ControlPlaneCommandPipeline, type ControlPlaneOutbox } from "@femtomc/mu-control-plane";
|
|
1
|
+
import { type ControlPlaneAdapter, type ControlPlaneCommandPipeline, type ControlPlaneOutbox, UiCallbackTokenStore } from "@femtomc/mu-control-plane";
|
|
2
2
|
import type { ControlPlaneConfig } from "./control_plane_contract.js";
|
|
3
3
|
export type DetectedStaticAdapter = {
|
|
4
4
|
name: "slack" | "discord" | "neovim";
|
|
@@ -16,4 +16,5 @@ export declare function createStaticAdaptersFromDetected(opts: {
|
|
|
16
16
|
config: ControlPlaneConfig;
|
|
17
17
|
pipeline: ControlPlaneCommandPipeline;
|
|
18
18
|
outbox: ControlPlaneOutbox;
|
|
19
|
+
uiCallbackTokenStore: UiCallbackTokenStore;
|
|
19
20
|
}): ControlPlaneAdapter[];
|
|
@@ -8,6 +8,7 @@ const STATIC_ADAPTER_MODULES = [
|
|
|
8
8
|
outbox: opts.outbox,
|
|
9
9
|
signingSecret: opts.secret,
|
|
10
10
|
botToken: opts.config.adapters.slack.bot_token,
|
|
11
|
+
uiCallbackTokenStore: opts.uiCallbackTokenStore,
|
|
11
12
|
}),
|
|
12
13
|
},
|
|
13
14
|
{
|
|
@@ -17,6 +18,7 @@ const STATIC_ADAPTER_MODULES = [
|
|
|
17
18
|
pipeline: opts.pipeline,
|
|
18
19
|
outbox: opts.outbox,
|
|
19
20
|
signingSecret: opts.secret,
|
|
21
|
+
uiCallbackTokenStore: opts.uiCallbackTokenStore,
|
|
20
22
|
}),
|
|
21
23
|
},
|
|
22
24
|
{
|
|
@@ -25,6 +27,7 @@ const STATIC_ADAPTER_MODULES = [
|
|
|
25
27
|
create: (opts) => new NeovimControlPlaneAdapter({
|
|
26
28
|
pipeline: opts.pipeline,
|
|
27
29
|
sharedSecret: opts.secret,
|
|
30
|
+
uiCallbackTokenStore: opts.uiCallbackTokenStore,
|
|
28
31
|
}),
|
|
29
32
|
},
|
|
30
33
|
];
|
|
@@ -69,6 +72,7 @@ export function createStaticAdaptersFromDetected(opts) {
|
|
|
69
72
|
outbox: opts.outbox,
|
|
70
73
|
secret: detected.secret,
|
|
71
74
|
config: opts.config,
|
|
75
|
+
uiCallbackTokenStore: opts.uiCallbackTokenStore,
|
|
72
76
|
}));
|
|
73
77
|
}
|
|
74
78
|
return adapters;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { ControlPlaneCommandPipeline, ControlPlaneOutbox, type ControlPlaneSignalObserver, type ReloadableGenerationIdentity, TelegramControlPlaneAdapter } from "@femtomc/mu-control-plane";
|
|
1
|
+
import { ControlPlaneCommandPipeline, ControlPlaneOutbox, type ControlPlaneSignalObserver, type ReloadableGenerationIdentity, TelegramControlPlaneAdapter, UiCallbackTokenStore } from "@femtomc/mu-control-plane";
|
|
2
2
|
import type { ControlPlaneConfig, TelegramGenerationReloadResult, TelegramGenerationSwapHooks } from "./control_plane_contract.js";
|
|
3
3
|
export declare class TelegramAdapterGenerationManager {
|
|
4
4
|
#private;
|
|
5
5
|
constructor(opts: {
|
|
6
6
|
pipeline: ControlPlaneCommandPipeline;
|
|
7
7
|
outbox: ControlPlaneOutbox;
|
|
8
|
+
uiCallbackTokenStore: UiCallbackTokenStore;
|
|
8
9
|
initialConfig: ControlPlaneConfig;
|
|
9
10
|
nowMs?: () => number;
|
|
10
11
|
onOutboxEnqueued?: () => void;
|
|
@@ -79,6 +79,7 @@ async function runWithTimeout(opts) {
|
|
|
79
79
|
export class TelegramAdapterGenerationManager {
|
|
80
80
|
#pipeline;
|
|
81
81
|
#outbox;
|
|
82
|
+
#uiCallbackTokenStore;
|
|
82
83
|
#nowMs;
|
|
83
84
|
#onOutboxEnqueued;
|
|
84
85
|
#signalObserver;
|
|
@@ -90,6 +91,7 @@ export class TelegramAdapterGenerationManager {
|
|
|
90
91
|
constructor(opts) {
|
|
91
92
|
this.#pipeline = opts.pipeline;
|
|
92
93
|
this.#outbox = opts.outbox;
|
|
94
|
+
this.#uiCallbackTokenStore = opts.uiCallbackTokenStore;
|
|
93
95
|
this.#nowMs = opts.nowMs ?? Date.now;
|
|
94
96
|
this.#onOutboxEnqueued = opts.onOutboxEnqueued ?? null;
|
|
95
97
|
this.#signalObserver = opts.signalObserver ?? null;
|
|
@@ -115,6 +117,7 @@ export class TelegramAdapterGenerationManager {
|
|
|
115
117
|
acceptIngress: opts.acceptIngress,
|
|
116
118
|
ingressDrainEnabled: opts.ingressDrainEnabled,
|
|
117
119
|
nowMs: this.#nowMs,
|
|
120
|
+
uiCallbackTokenStore: this.#uiCallbackTokenStore,
|
|
118
121
|
});
|
|
119
122
|
}
|
|
120
123
|
async initialize() {
|
package/dist/server_routing.js
CHANGED
|
@@ -13,7 +13,7 @@ export function createServerRequestHandler(deps) {
|
|
|
13
13
|
if (request.method === "OPTIONS") {
|
|
14
14
|
return new Response(null, { status: 204, headers });
|
|
15
15
|
}
|
|
16
|
-
if (path === "/healthz"
|
|
16
|
+
if (path === "/healthz") {
|
|
17
17
|
return new Response("ok", { status: 200, headers });
|
|
18
18
|
}
|
|
19
19
|
if (path === "/api/server/shutdown") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-server",
|
|
3
|
-
"version": "26.2.
|
|
3
|
+
"version": "26.2.107",
|
|
4
4
|
"description": "HTTP API server for mu control-plane transport/session plus run/activity scheduling coordination.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mu",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"start": "bun run dist/cli.js"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@femtomc/mu-agent": "26.2.
|
|
34
|
-
"@femtomc/mu-control-plane": "26.2.
|
|
35
|
-
"@femtomc/mu-core": "26.2.
|
|
33
|
+
"@femtomc/mu-agent": "26.2.107",
|
|
34
|
+
"@femtomc/mu-control-plane": "26.2.107",
|
|
35
|
+
"@femtomc/mu-core": "26.2.107"
|
|
36
36
|
}
|
|
37
37
|
}
|