@femtomc/mu-server 26.2.89 → 26.2.91

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.
@@ -1,11 +1,6 @@
1
1
  import { ControlPlaneCommandPipeline, ControlPlaneOutbox, ControlPlaneRuntime, getControlPlanePaths, TelegramControlPlaneAdapterSpec, } from "@femtomc/mu-control-plane";
2
2
  import { DEFAULT_MU_CONFIG } from "./config.js";
3
- import { DEFAULT_INTER_ROOT_QUEUE_POLICY, normalizeInterRootQueuePolicy, } from "./control_plane_contract.js";
4
- import { ControlPlaneRunSupervisor, } from "./run_supervisor.js";
5
- import { DurableRunQueue, queueStatesForRunStatusFilter, runSnapshotFromQueueSnapshot } from "./run_queue.js";
6
3
  import { buildMessagingOperatorRuntime, createOutboxDrainLoop } from "./control_plane_bootstrap_helpers.js";
7
- import { ControlPlaneRunQueueCoordinator } from "./control_plane_run_queue_coordinator.js";
8
- import { enqueueRunEventOutbox } from "./control_plane_run_outbox.js";
9
4
  import { buildWakeOutboundEnvelope, resolveWakeFanoutCapability, wakeDeliveryMetadataFromOutboxRecord, wakeDispatchReasonCode, wakeFanoutDedupeKey, } from "./control_plane_wake_delivery.js";
10
5
  import { createStaticAdaptersFromDetected, detectAdapters, } from "./control_plane_adapter_registry.js";
11
6
  import { OutboundDeliveryRouter } from "./outbound_delivery_router.js";
@@ -27,17 +22,8 @@ function emptyNotifyOperatorsResult() {
27
22
  decisions: [],
28
23
  };
29
24
  }
30
- function normalizeIssueId(value) {
31
- if (!value) {
32
- return null;
33
- }
34
- const trimmed = value.trim();
35
- if (!/^mu-[a-z0-9][a-z0-9-]*$/i.test(trimmed)) {
36
- return null;
37
- }
38
- return trimmed.toLowerCase();
39
- }
40
25
  export { detectAdapters };
26
+ const TELEGRAM_CAPTION_MAX_LEN = 1_024;
41
27
  /**
42
28
  * Telegram supports a markdown dialect that uses single markers for emphasis.
43
29
  * Normalize the most common LLM/GitHub-style markers (`**bold**`, `__italic__`, headings)
@@ -100,6 +86,287 @@ async function postTelegramMessage(botToken, payload) {
100
86
  body: JSON.stringify(payload),
101
87
  });
102
88
  }
89
+ async function postTelegramApiJson(botToken, method, payload) {
90
+ return await fetch(`https://api.telegram.org/bot${botToken}/${method}`, {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify(payload),
94
+ });
95
+ }
96
+ async function postTelegramApiMultipart(botToken, method, form) {
97
+ return await fetch(`https://api.telegram.org/bot${botToken}/${method}`, {
98
+ method: "POST",
99
+ body: form,
100
+ });
101
+ }
102
+ function truncateTelegramCaption(text) {
103
+ const normalized = text.trim();
104
+ if (normalized.length <= TELEGRAM_CAPTION_MAX_LEN) {
105
+ return normalized;
106
+ }
107
+ if (TELEGRAM_CAPTION_MAX_LEN <= 16) {
108
+ return normalized.slice(0, TELEGRAM_CAPTION_MAX_LEN);
109
+ }
110
+ const suffix = "…(truncated)";
111
+ const headLen = Math.max(0, TELEGRAM_CAPTION_MAX_LEN - suffix.length);
112
+ return `${normalized.slice(0, headLen)}${suffix}`;
113
+ }
114
+ function chooseTelegramMediaMethod(attachment) {
115
+ const mime = attachment.mime_type?.toLowerCase() ?? "";
116
+ const filename = attachment.filename?.toLowerCase() ?? "";
117
+ const declaredType = attachment.type.toLowerCase();
118
+ const isSvg = mime === "image/svg+xml" || filename.endsWith(".svg");
119
+ const isImageMime = mime.startsWith("image/");
120
+ if ((declaredType === "image" || isImageMime) && !isSvg) {
121
+ return "sendPhoto";
122
+ }
123
+ return "sendDocument";
124
+ }
125
+ function parseRetryDelayMs(res) {
126
+ const retryAfter = res.headers.get("retry-after");
127
+ if (!retryAfter) {
128
+ return undefined;
129
+ }
130
+ const parsed = Number.parseInt(retryAfter, 10);
131
+ if (!Number.isFinite(parsed) || parsed < 0) {
132
+ return undefined;
133
+ }
134
+ return parsed * 1000;
135
+ }
136
+ export async function deliverTelegramOutboxRecord(opts) {
137
+ const { botToken, record } = opts;
138
+ const fallbackMessagePayload = buildTelegramSendMessagePayload({
139
+ chatId: record.envelope.channel_conversation_id,
140
+ text: record.envelope.body,
141
+ richFormatting: true,
142
+ });
143
+ const firstAttachment = record.envelope.attachments?.[0] ?? null;
144
+ if (!firstAttachment) {
145
+ let res = await postTelegramMessage(botToken, fallbackMessagePayload);
146
+ if (!res.ok && res.status === 400 && fallbackMessagePayload.parse_mode) {
147
+ res = await postTelegramMessage(botToken, buildTelegramSendMessagePayload({
148
+ chatId: record.envelope.channel_conversation_id,
149
+ text: record.envelope.body,
150
+ richFormatting: false,
151
+ }));
152
+ }
153
+ if (res.ok) {
154
+ return { kind: "delivered" };
155
+ }
156
+ const responseBody = await res.text().catch(() => "");
157
+ if (res.status === 429 || res.status >= 500) {
158
+ return {
159
+ kind: "retry",
160
+ error: `telegram sendMessage ${res.status}: ${responseBody}`,
161
+ retryDelayMs: parseRetryDelayMs(res),
162
+ };
163
+ }
164
+ return {
165
+ kind: "retry",
166
+ error: `telegram sendMessage ${res.status}: ${responseBody}`,
167
+ };
168
+ }
169
+ const mediaMethod = chooseTelegramMediaMethod(firstAttachment);
170
+ const mediaField = mediaMethod === "sendPhoto" ? "photo" : "document";
171
+ const mediaReference = firstAttachment.reference.file_id ?? firstAttachment.reference.url ?? null;
172
+ if (!mediaReference) {
173
+ return { kind: "retry", error: "telegram media attachment missing reference" };
174
+ }
175
+ const mediaCaption = truncateTelegramCaption(record.envelope.body);
176
+ let mediaResponse;
177
+ if (firstAttachment.reference.file_id) {
178
+ mediaResponse = await postTelegramApiJson(botToken, mediaMethod, mediaMethod === "sendPhoto"
179
+ ? {
180
+ chat_id: record.envelope.channel_conversation_id,
181
+ photo: firstAttachment.reference.file_id,
182
+ caption: mediaCaption,
183
+ }
184
+ : {
185
+ chat_id: record.envelope.channel_conversation_id,
186
+ document: firstAttachment.reference.file_id,
187
+ caption: mediaCaption,
188
+ });
189
+ }
190
+ else {
191
+ const sourceUrl = firstAttachment.reference.url;
192
+ const sourceRes = await fetch(sourceUrl);
193
+ if (!sourceRes.ok) {
194
+ const sourceErr = await sourceRes.text().catch(() => "");
195
+ return {
196
+ kind: "retry",
197
+ error: `telegram attachment fetch ${sourceRes.status}: ${sourceErr}`,
198
+ retryDelayMs: sourceRes.status === 429 || sourceRes.status >= 500 ? parseRetryDelayMs(sourceRes) : undefined,
199
+ };
200
+ }
201
+ const body = await sourceRes.arrayBuffer();
202
+ const contentType = firstAttachment.mime_type ?? sourceRes.headers.get("content-type") ?? "application/octet-stream";
203
+ const filename = firstAttachment.filename ?? `${firstAttachment.type || "attachment"}.bin`;
204
+ const form = new FormData();
205
+ form.append("chat_id", record.envelope.channel_conversation_id);
206
+ if (mediaCaption.length > 0) {
207
+ form.append("caption", mediaCaption);
208
+ }
209
+ form.append(mediaField, new Blob([body], { type: contentType }), filename);
210
+ mediaResponse = await postTelegramApiMultipart(botToken, mediaMethod, form);
211
+ }
212
+ if (mediaResponse.ok) {
213
+ return { kind: "delivered" };
214
+ }
215
+ const mediaBody = await mediaResponse.text().catch(() => "");
216
+ if (mediaResponse.status === 429 || mediaResponse.status >= 500) {
217
+ return {
218
+ kind: "retry",
219
+ error: `telegram ${mediaMethod} ${mediaResponse.status}: ${mediaBody}`,
220
+ retryDelayMs: parseRetryDelayMs(mediaResponse),
221
+ };
222
+ }
223
+ const fallbackPlainPayload = buildTelegramSendMessagePayload({
224
+ chatId: record.envelope.channel_conversation_id,
225
+ text: record.envelope.body,
226
+ richFormatting: false,
227
+ });
228
+ const fallbackRes = await postTelegramMessage(botToken, fallbackPlainPayload);
229
+ if (fallbackRes.ok) {
230
+ return { kind: "delivered" };
231
+ }
232
+ const fallbackBody = await fallbackRes.text().catch(() => "");
233
+ if (fallbackRes.status === 429 || fallbackRes.status >= 500) {
234
+ return {
235
+ kind: "retry",
236
+ error: `telegram media fallback sendMessage ${fallbackRes.status}: ${fallbackBody}`,
237
+ retryDelayMs: parseRetryDelayMs(fallbackRes),
238
+ };
239
+ }
240
+ return {
241
+ kind: "retry",
242
+ error: `telegram media fallback sendMessage ${fallbackRes.status}: ${fallbackBody} (media_error=${mediaMethod} ${mediaResponse.status}: ${mediaBody})`,
243
+ };
244
+ }
245
+ async function postSlackJson(opts) {
246
+ const response = await fetch(`https://slack.com/api/${opts.method}`, {
247
+ method: "POST",
248
+ headers: {
249
+ Authorization: `Bearer ${opts.botToken}`,
250
+ "Content-Type": "application/json",
251
+ },
252
+ body: JSON.stringify(opts.payload),
253
+ });
254
+ const payload = (await response.json().catch(() => null));
255
+ return { response, payload };
256
+ }
257
+ export async function deliverSlackOutboxRecord(opts) {
258
+ const { botToken, record } = opts;
259
+ const attachments = record.envelope.attachments ?? [];
260
+ if (attachments.length === 0) {
261
+ const delivered = await postSlackJson({
262
+ botToken,
263
+ method: "chat.postMessage",
264
+ payload: {
265
+ channel: record.envelope.channel_conversation_id,
266
+ text: record.envelope.body,
267
+ unfurl_links: false,
268
+ unfurl_media: false,
269
+ },
270
+ });
271
+ if (delivered.response.ok && delivered.payload?.ok) {
272
+ return { kind: "delivered" };
273
+ }
274
+ const status = delivered.response.status;
275
+ const err = delivered.payload?.error ?? "unknown_error";
276
+ if (status === 429 || status >= 500) {
277
+ return {
278
+ kind: "retry",
279
+ error: `slack chat.postMessage ${status}: ${err}`,
280
+ retryDelayMs: parseRetryDelayMs(delivered.response),
281
+ };
282
+ }
283
+ return { kind: "retry", error: `slack chat.postMessage ${status}: ${err}` };
284
+ }
285
+ let firstError = null;
286
+ for (const [index, attachment] of attachments.entries()) {
287
+ const referenceUrl = attachment.reference.url;
288
+ if (!referenceUrl) {
289
+ return {
290
+ kind: "retry",
291
+ error: `slack attachment ${index + 1} missing reference.url`,
292
+ };
293
+ }
294
+ const source = await fetch(referenceUrl);
295
+ if (!source.ok) {
296
+ const sourceErr = await source.text().catch(() => "");
297
+ if (source.status === 429 || source.status >= 500) {
298
+ return {
299
+ kind: "retry",
300
+ error: `slack attachment fetch ${source.status}: ${sourceErr}`,
301
+ retryDelayMs: parseRetryDelayMs(source),
302
+ };
303
+ }
304
+ return { kind: "retry", error: `slack attachment fetch ${source.status}: ${sourceErr}` };
305
+ }
306
+ const bytes = await source.arrayBuffer();
307
+ const contentType = attachment.mime_type ?? source.headers.get("content-type") ?? "application/octet-stream";
308
+ const filename = attachment.filename ?? `attachment-${index + 1}`;
309
+ const form = new FormData();
310
+ form.set("channels", record.envelope.channel_conversation_id);
311
+ form.set("filename", filename);
312
+ form.set("title", filename);
313
+ if (index === 0 && record.envelope.body.trim().length > 0) {
314
+ form.set("initial_comment", record.envelope.body);
315
+ }
316
+ form.set("file", new Blob([bytes], { type: contentType }), filename);
317
+ const uploaded = await fetch("https://slack.com/api/files.upload", {
318
+ method: "POST",
319
+ headers: {
320
+ Authorization: `Bearer ${botToken}`,
321
+ },
322
+ body: form,
323
+ });
324
+ const uploadPayload = (await uploaded.json().catch(() => null));
325
+ if (!(uploaded.ok && uploadPayload?.ok)) {
326
+ const status = uploaded.status;
327
+ const err = uploadPayload?.error ?? "unknown_error";
328
+ if (status === 429 || status >= 500) {
329
+ return {
330
+ kind: "retry",
331
+ error: `slack files.upload ${status}: ${err}`,
332
+ retryDelayMs: parseRetryDelayMs(uploaded),
333
+ };
334
+ }
335
+ if (!firstError) {
336
+ firstError = `slack files.upload ${status}: ${err}`;
337
+ }
338
+ }
339
+ }
340
+ if (firstError) {
341
+ const fallback = await postSlackJson({
342
+ botToken,
343
+ method: "chat.postMessage",
344
+ payload: {
345
+ channel: record.envelope.channel_conversation_id,
346
+ text: record.envelope.body,
347
+ unfurl_links: false,
348
+ unfurl_media: false,
349
+ },
350
+ });
351
+ if (fallback.response.ok && fallback.payload?.ok) {
352
+ return { kind: "delivered" };
353
+ }
354
+ const status = fallback.response.status;
355
+ const err = fallback.payload?.error ?? "unknown_error";
356
+ if (status === 429 || status >= 500) {
357
+ return {
358
+ kind: "retry",
359
+ error: `slack chat.postMessage fallback ${status}: ${err} (upload_error=${firstError})`,
360
+ retryDelayMs: parseRetryDelayMs(fallback.response),
361
+ };
362
+ }
363
+ return {
364
+ kind: "retry",
365
+ error: `slack chat.postMessage fallback ${status}: ${err} (upload_error=${firstError})`,
366
+ };
367
+ }
368
+ return { kind: "delivered" };
369
+ }
103
370
  export async function bootstrapControlPlane(opts) {
104
371
  const controlPlaneConfig = opts.config ?? DEFAULT_MU_CONFIG.control_plane;
105
372
  const detected = detectAdapters(controlPlaneConfig);
@@ -124,7 +391,6 @@ export async function bootstrapControlPlane(opts) {
124
391
  const paths = getControlPlanePaths(opts.repoRoot);
125
392
  const runtime = new ControlPlaneRuntime({ repoRoot: opts.repoRoot });
126
393
  let pipeline = null;
127
- let runSupervisor = null;
128
394
  let outboxDrainLoop = null;
129
395
  let wakeDeliveryObserver = opts.wakeDeliveryObserver ?? null;
130
396
  const outboundDeliveryChannels = new Set();
@@ -143,29 +409,6 @@ export async function bootstrapControlPlane(opts) {
143
409
  });
144
410
  await outbox.load();
145
411
  let scheduleOutboxDrainRef = null;
146
- const runQueue = new DurableRunQueue({ repoRoot: opts.repoRoot });
147
- const interRootQueuePolicy = normalizeInterRootQueuePolicy(opts.interRootQueuePolicy ?? DEFAULT_INTER_ROOT_QUEUE_POLICY);
148
- const runQueueCoordinator = new ControlPlaneRunQueueCoordinator({
149
- runQueue,
150
- interRootQueuePolicy,
151
- getRunSupervisor: () => runSupervisor,
152
- });
153
- runSupervisor = new ControlPlaneRunSupervisor({
154
- repoRoot: opts.repoRoot,
155
- spawnProcess: opts.runSupervisorSpawnProcess,
156
- onEvent: async (event) => {
157
- await runQueueCoordinator.onRunEvent(event);
158
- const outboxRecord = await enqueueRunEventOutbox({
159
- outbox,
160
- event,
161
- nowMs: Math.trunc(Date.now()),
162
- });
163
- if (outboxRecord) {
164
- scheduleOutboxDrainRef?.();
165
- }
166
- },
167
- });
168
- await runQueueCoordinator.scheduleReconcile("bootstrap");
169
412
  pipeline = new ControlPlaneCommandPipeline({
170
413
  runtime,
171
414
  operator,
@@ -177,7 +420,6 @@ export async function bootstrapControlPlane(opts) {
177
420
  errorCode: "cli_validation_failed",
178
421
  trace: {
179
422
  cliCommandKind: record.target_type,
180
- runRootId: null,
181
423
  },
182
424
  mutatingEvents: [
183
425
  {
@@ -201,7 +443,6 @@ export async function bootstrapControlPlane(opts) {
201
443
  errorCode: "session_lifecycle_failed",
202
444
  trace: {
203
445
  cliCommandKind: action,
204
- runRootId: null,
205
446
  },
206
447
  mutatingEvents: [
207
448
  {
@@ -225,7 +466,6 @@ export async function bootstrapControlPlane(opts) {
225
466
  },
226
467
  trace: {
227
468
  cliCommandKind: action,
228
- runRootId: null,
229
469
  },
230
470
  mutatingEvents: [
231
471
  {
@@ -245,7 +485,6 @@ export async function bootstrapControlPlane(opts) {
245
485
  errorCode: err instanceof Error && err.message ? err.message : "session_lifecycle_failed",
246
486
  trace: {
247
487
  cliCommandKind: action,
248
- runRootId: null,
249
488
  },
250
489
  mutatingEvents: [
251
490
  {
@@ -259,96 +498,6 @@ export async function bootstrapControlPlane(opts) {
259
498
  };
260
499
  }
261
500
  }
262
- if (record.target_type === "run start" || record.target_type === "run resume") {
263
- try {
264
- const launched = await runQueueCoordinator.launchQueuedRunFromCommand(record);
265
- return {
266
- terminalState: "completed",
267
- result: {
268
- ok: true,
269
- async_run: true,
270
- run_job_id: launched.job_id,
271
- run_root_id: launched.root_issue_id,
272
- run_status: launched.status,
273
- run_mode: launched.mode,
274
- run_source: launched.source,
275
- },
276
- trace: {
277
- cliCommandKind: launched.mode,
278
- runRootId: launched.root_issue_id,
279
- },
280
- mutatingEvents: [
281
- {
282
- eventType: "run.supervisor.start",
283
- payload: {
284
- run_job_id: launched.job_id,
285
- run_mode: launched.mode,
286
- run_root_id: launched.root_issue_id,
287
- run_source: launched.source,
288
- queue_id: launched.queue_id ?? null,
289
- queue_state: launched.queue_state ?? null,
290
- },
291
- },
292
- ],
293
- };
294
- }
295
- catch (err) {
296
- return {
297
- terminalState: "failed",
298
- errorCode: err instanceof Error && err.message ? err.message : "run_queue_start_failed",
299
- trace: {
300
- cliCommandKind: record.target_type.replaceAll(" ", "_"),
301
- runRootId: record.target_id,
302
- },
303
- };
304
- }
305
- }
306
- if (record.target_type === "run interrupt") {
307
- const result = await runQueueCoordinator.interruptQueuedRun({
308
- rootIssueId: record.target_id,
309
- });
310
- if (!result.ok) {
311
- return {
312
- terminalState: "failed",
313
- errorCode: result.reason ?? "run_interrupt_failed",
314
- trace: {
315
- cliCommandKind: "run_interrupt",
316
- runRootId: result.run?.root_issue_id ?? record.target_id,
317
- },
318
- mutatingEvents: [
319
- {
320
- eventType: "run.supervisor.interrupt.failed",
321
- payload: {
322
- reason: result.reason,
323
- target: record.target_id,
324
- },
325
- },
326
- ],
327
- };
328
- }
329
- return {
330
- terminalState: "completed",
331
- result: {
332
- ok: true,
333
- async_run: true,
334
- interrupted: true,
335
- run: result.run,
336
- },
337
- trace: {
338
- cliCommandKind: "run_interrupt",
339
- runRootId: result.run?.root_issue_id ?? record.target_id,
340
- },
341
- mutatingEvents: [
342
- {
343
- eventType: "run.supervisor.interrupt",
344
- payload: {
345
- target: record.target_id,
346
- run: result.run,
347
- },
348
- },
349
- ],
350
- };
351
- }
352
501
  return null;
353
502
  },
354
503
  });
@@ -366,6 +515,7 @@ export async function bootstrapControlPlane(opts) {
366
515
  await telegramManager.initialize();
367
516
  for (const adapter of createStaticAdaptersFromDetected({
368
517
  detected,
518
+ config: controlPlaneConfig,
369
519
  pipeline,
370
520
  outbox,
371
521
  })) {
@@ -416,6 +566,19 @@ export async function bootstrapControlPlane(opts) {
416
566
  isActive: () => telegramManager.hasActiveGeneration(),
417
567
  });
418
568
  const deliveryRouter = new OutboundDeliveryRouter([
569
+ {
570
+ channel: "slack",
571
+ deliver: async (record) => {
572
+ const slackBotToken = controlPlaneConfig.adapters.slack.bot_token;
573
+ if (!slackBotToken) {
574
+ return { kind: "retry", error: "slack bot token not configured in mu workspace config" };
575
+ }
576
+ return await deliverSlackOutboxRecord({
577
+ botToken: slackBotToken,
578
+ record,
579
+ });
580
+ },
581
+ },
419
582
  {
420
583
  channel: "telegram",
421
584
  deliver: async (record) => {
@@ -423,44 +586,15 @@ export async function bootstrapControlPlane(opts) {
423
586
  if (!telegramBotToken) {
424
587
  return { kind: "retry", error: "telegram bot token not configured in mu workspace config" };
425
588
  }
426
- const richPayload = buildTelegramSendMessagePayload({
427
- chatId: record.envelope.channel_conversation_id,
428
- text: record.envelope.body,
429
- richFormatting: true,
589
+ return await deliverTelegramOutboxRecord({
590
+ botToken: telegramBotToken,
591
+ record,
430
592
  });
431
- let res = await postTelegramMessage(telegramBotToken, richPayload);
432
- // Fallback: if Telegram rejects markdown entities, retry as plain text.
433
- if (!res.ok && res.status === 400 && richPayload.parse_mode) {
434
- const plainPayload = buildTelegramSendMessagePayload({
435
- chatId: record.envelope.channel_conversation_id,
436
- text: record.envelope.body,
437
- richFormatting: false,
438
- });
439
- res = await postTelegramMessage(telegramBotToken, plainPayload);
440
- }
441
- if (res.ok) {
442
- return { kind: "delivered" };
443
- }
444
- const responseBody = await res.text().catch(() => "");
445
- if (res.status === 429 || res.status >= 500) {
446
- const retryAfter = res.headers.get("retry-after");
447
- const retryDelayMs = retryAfter ? Number.parseInt(retryAfter, 10) * 1000 : undefined;
448
- return {
449
- kind: "retry",
450
- error: `telegram sendMessage ${res.status}: ${responseBody}`,
451
- retryDelayMs: retryDelayMs && Number.isFinite(retryDelayMs) ? retryDelayMs : undefined,
452
- };
453
- }
454
- return {
455
- kind: "retry",
456
- error: `telegram sendMessage ${res.status}: ${responseBody}`,
457
- };
458
593
  },
459
594
  },
460
595
  ]);
461
- for (const channel of deliveryRouter.supportedChannels()) {
462
- outboundDeliveryChannels.add(channel);
463
- }
596
+ outboundDeliveryChannels.add("slack");
597
+ outboundDeliveryChannels.add("telegram");
464
598
  const notifyOperators = async (notifyOpts) => {
465
599
  if (!pipeline) {
466
600
  return emptyNotifyOperatorsResult();
@@ -491,6 +625,7 @@ export async function bootstrapControlPlane(opts) {
491
625
  sourceTsMs: wakeSourceTsMs,
492
626
  };
493
627
  const nowMs = Math.trunc(Date.now());
628
+ const slackBotToken = controlPlaneConfig.adapters.slack.bot_token;
494
629
  const telegramBotToken = telegramManager.activeBotToken();
495
630
  const bindings = pipeline.identities
496
631
  .listBindings({ includeInactive: false })
@@ -505,6 +640,7 @@ export async function bootstrapControlPlane(opts) {
505
640
  const capability = resolveWakeFanoutCapability({
506
641
  binding,
507
642
  isChannelDeliverySupported: (channel) => outboundDeliveryChannels.has(channel),
643
+ slackBotToken,
508
644
  telegramBotToken,
509
645
  });
510
646
  if (!capability.ok) {
@@ -624,76 +760,6 @@ export async function bootstrapControlPlane(opts) {
624
760
  }
625
761
  return result;
626
762
  },
627
- async listRuns(opts = {}) {
628
- const limit = Math.max(1, Math.min(500, Math.trunc(opts.limit ?? 100)));
629
- const fallbackStatusFilter = queueStatesForRunStatusFilter(opts.status);
630
- if (Array.isArray(fallbackStatusFilter) && fallbackStatusFilter.length === 0) {
631
- return [];
632
- }
633
- const queued = await runQueue.listRunSnapshots({
634
- status: opts.status,
635
- limit,
636
- runtimeByJobId: runQueueCoordinator.runtimeSnapshotsByJobId(),
637
- });
638
- const seen = new Set(queued.map((run) => run.job_id));
639
- const fallbackRuns = runSupervisor?.list({ limit: 500 }) ?? [];
640
- for (const run of fallbackRuns) {
641
- if (seen.has(run.job_id)) {
642
- continue;
643
- }
644
- if (fallbackStatusFilter && fallbackStatusFilter.length > 0) {
645
- const mapped = run.status === "completed"
646
- ? "done"
647
- : run.status === "failed"
648
- ? "failed"
649
- : run.status === "cancelled"
650
- ? "cancelled"
651
- : "active";
652
- if (!fallbackStatusFilter.includes(mapped)) {
653
- continue;
654
- }
655
- }
656
- queued.push(run);
657
- seen.add(run.job_id);
658
- }
659
- return queued.slice(0, limit);
660
- },
661
- async getRun(idOrRoot) {
662
- const queued = await runQueue.get(idOrRoot);
663
- if (queued) {
664
- const runtime = queued.job_id ? (runSupervisor?.get(queued.job_id) ?? null) : null;
665
- return runSnapshotFromQueueSnapshot(queued, runtime);
666
- }
667
- return runSupervisor?.get(idOrRoot) ?? null;
668
- },
669
- async startRun(startOpts) {
670
- return await runQueueCoordinator.launchQueuedRun({
671
- mode: "run_start",
672
- prompt: startOpts.prompt,
673
- maxSteps: startOpts.maxSteps,
674
- source: "api",
675
- dedupeKey: `api:run_start:${crypto.randomUUID()}`,
676
- });
677
- },
678
- async resumeRun(resumeOpts) {
679
- const rootIssueId = normalizeIssueId(resumeOpts.rootIssueId);
680
- if (!rootIssueId) {
681
- throw new Error("run_resume_invalid_root_issue_id");
682
- }
683
- return await runQueueCoordinator.launchQueuedRun({
684
- mode: "run_resume",
685
- rootIssueId,
686
- maxSteps: resumeOpts.maxSteps,
687
- source: "api",
688
- dedupeKey: `api:run_resume:${rootIssueId}:${crypto.randomUUID()}`,
689
- });
690
- },
691
- async interruptRun(interruptOpts) {
692
- return await runQueueCoordinator.interruptQueuedRun(interruptOpts);
693
- },
694
- async traceRun(traceOpts) {
695
- return (await runSupervisor?.trace(traceOpts.idOrRoot, { limit: traceOpts.limit })) ?? null;
696
- },
697
763
  async submitTerminalCommand(terminalOpts) {
698
764
  if (!pipeline) {
699
765
  throw new Error("control_plane_pipeline_unavailable");
@@ -702,7 +768,6 @@ export async function bootstrapControlPlane(opts) {
702
768
  },
703
769
  async stop() {
704
770
  wakeDeliveryObserver = null;
705
- runQueueCoordinator.stop();
706
771
  if (outboxDrainLoop) {
707
772
  outboxDrainLoop.stop();
708
773
  outboxDrainLoop = null;
@@ -715,7 +780,6 @@ export async function bootstrapControlPlane(opts) {
715
780
  // Best effort adapter cleanup.
716
781
  }
717
782
  }
718
- await runSupervisor?.stop();
719
783
  try {
720
784
  await pipeline?.stop();
721
785
  }
@@ -739,12 +803,6 @@ export async function bootstrapControlPlane(opts) {
739
803
  // Best effort cleanup.
740
804
  }
741
805
  }
742
- try {
743
- await runSupervisor?.stop();
744
- }
745
- catch {
746
- // Best effort cleanup.
747
- }
748
806
  try {
749
807
  await pipeline?.stop();
750
808
  }
@@ -14,6 +14,7 @@ export type DetectedAdapter = DetectedStaticAdapter | DetectedTelegramAdapter;
14
14
  export declare function detectAdapters(config: ControlPlaneConfig): DetectedAdapter[];
15
15
  export declare function createStaticAdaptersFromDetected(opts: {
16
16
  detected: readonly DetectedAdapter[];
17
+ config: ControlPlaneConfig;
17
18
  pipeline: ControlPlaneCommandPipeline;
18
19
  outbox: ControlPlaneOutbox;
19
20
  }): ControlPlaneAdapter[];