@femtomc/mu-server 26.2.41 → 26.2.43
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 +2 -2
- package/dist/activity_supervisor.d.ts +81 -0
- package/dist/activity_supervisor.js +306 -0
- package/dist/control_plane.d.ts +30 -0
- package/dist/control_plane.js +282 -11
- package/dist/heartbeat_programs.d.ts +93 -0
- package/dist/heartbeat_programs.js +415 -0
- package/dist/heartbeat_scheduler.d.ts +38 -0
- package/dist/heartbeat_scheduler.js +238 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/run_supervisor.d.ts +101 -0
- package/dist/run_supervisor.js +480 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +628 -3
- package/package.json +6 -6
package/dist/control_plane.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ApprovedCommandBroker, CommandContextResolver, MessagingOperatorRuntime, PiMessagingOperatorBackend,
|
|
2
|
-
import { ControlPlaneCommandPipeline, ControlPlaneOutbox, ControlPlaneOutboxDispatcher, ControlPlaneRuntime, DiscordControlPlaneAdapter, getControlPlanePaths, SlackControlPlaneAdapter, TelegramControlPlaneAdapter, } from "@femtomc/mu-control-plane";
|
|
1
|
+
import { ApprovedCommandBroker, CommandContextResolver, MessagingOperatorRuntime, PiMessagingOperatorBackend, operatorExtensionPaths, } from "@femtomc/mu-agent";
|
|
2
|
+
import { ControlPlaneCommandPipeline, ControlPlaneOutbox, ControlPlaneOutboxDispatcher, correlationFromCommandRecord, ControlPlaneRuntime, DiscordControlPlaneAdapter, getControlPlanePaths, SlackControlPlaneAdapter, TelegramControlPlaneAdapter, } from "@femtomc/mu-control-plane";
|
|
3
3
|
import { DEFAULT_MU_CONFIG } from "./config.js";
|
|
4
|
+
import { ControlPlaneRunSupervisor, } from "./run_supervisor.js";
|
|
4
5
|
export function detectAdapters(config) {
|
|
5
6
|
const adapters = [];
|
|
6
7
|
const slackSecret = config.adapters.slack.signing_secret;
|
|
@@ -22,6 +23,57 @@ export function detectAdapters(config) {
|
|
|
22
23
|
}
|
|
23
24
|
return adapters;
|
|
24
25
|
}
|
|
26
|
+
function sha256Hex(input) {
|
|
27
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
28
|
+
hasher.update(input);
|
|
29
|
+
return hasher.digest("hex");
|
|
30
|
+
}
|
|
31
|
+
function outboxKindForRunEvent(kind) {
|
|
32
|
+
switch (kind) {
|
|
33
|
+
case "run_completed":
|
|
34
|
+
return "result";
|
|
35
|
+
case "run_failed":
|
|
36
|
+
return "error";
|
|
37
|
+
default:
|
|
38
|
+
return "lifecycle";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function enqueueRunEventOutbox(opts) {
|
|
42
|
+
const command = opts.event.command;
|
|
43
|
+
if (!command) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const baseCorrelation = correlationFromCommandRecord(command);
|
|
47
|
+
const correlation = {
|
|
48
|
+
...baseCorrelation,
|
|
49
|
+
run_root_id: opts.event.run.root_issue_id ?? baseCorrelation.run_root_id,
|
|
50
|
+
};
|
|
51
|
+
const envelope = {
|
|
52
|
+
v: 1,
|
|
53
|
+
ts_ms: opts.nowMs,
|
|
54
|
+
channel: command.channel,
|
|
55
|
+
channel_tenant_id: command.channel_tenant_id,
|
|
56
|
+
channel_conversation_id: command.channel_conversation_id,
|
|
57
|
+
request_id: command.request_id,
|
|
58
|
+
response_id: `resp-${sha256Hex(`run-event:${opts.event.run.job_id}:${opts.event.seq}:${opts.nowMs}`).slice(0, 20)}`,
|
|
59
|
+
kind: outboxKindForRunEvent(opts.event.kind),
|
|
60
|
+
body: opts.event.message,
|
|
61
|
+
correlation,
|
|
62
|
+
metadata: {
|
|
63
|
+
async_run: true,
|
|
64
|
+
run_event_kind: opts.event.kind,
|
|
65
|
+
run_event_seq: opts.event.seq,
|
|
66
|
+
run: opts.event.run,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
const decision = await opts.outbox.enqueue({
|
|
70
|
+
dedupeKey: `run-event:${opts.event.run.job_id}:${opts.event.seq}`,
|
|
71
|
+
envelope,
|
|
72
|
+
nowMs: opts.nowMs,
|
|
73
|
+
maxAttempts: 6,
|
|
74
|
+
});
|
|
75
|
+
return decision.record;
|
|
76
|
+
}
|
|
25
77
|
/**
|
|
26
78
|
* Telegram supports a markdown dialect that uses single markers for emphasis.
|
|
27
79
|
* Normalize the most common LLM/GitHub-style markers (`**bold**`, `__italic__`, headings)
|
|
@@ -51,8 +103,20 @@ export function renderTelegramMarkdown(text) {
|
|
|
51
103
|
}
|
|
52
104
|
return out.join("\n");
|
|
53
105
|
}
|
|
106
|
+
const TELEGRAM_MATH_PATTERNS = [
|
|
107
|
+
/\$\$[\s\S]+?\$\$/m,
|
|
108
|
+
/(^|[^\\])\$[^$\n]+\$/m,
|
|
109
|
+
/\\\([\s\S]+?\\\)/m,
|
|
110
|
+
/\\\[[\s\S]+?\\\]/m,
|
|
111
|
+
];
|
|
112
|
+
export function containsTelegramMathNotation(text) {
|
|
113
|
+
if (text.trim().length === 0) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return TELEGRAM_MATH_PATTERNS.some((pattern) => pattern.test(text));
|
|
117
|
+
}
|
|
54
118
|
export function buildTelegramSendMessagePayload(opts) {
|
|
55
|
-
if (!opts.richFormatting) {
|
|
119
|
+
if (!opts.richFormatting || containsTelegramMathNotation(opts.text)) {
|
|
56
120
|
return {
|
|
57
121
|
chat_id: opts.chatId,
|
|
58
122
|
text: opts.text,
|
|
@@ -72,6 +136,7 @@ async function postTelegramMessage(botToken, payload) {
|
|
|
72
136
|
body: JSON.stringify(payload),
|
|
73
137
|
});
|
|
74
138
|
}
|
|
139
|
+
const OUTBOX_DRAIN_INTERVAL_MS = 500;
|
|
75
140
|
function buildMessagingOperatorRuntime(opts) {
|
|
76
141
|
if (!opts.config.operator.enabled) {
|
|
77
142
|
return null;
|
|
@@ -80,7 +145,7 @@ function buildMessagingOperatorRuntime(opts) {
|
|
|
80
145
|
new PiMessagingOperatorBackend({
|
|
81
146
|
provider: opts.config.operator.provider ?? undefined,
|
|
82
147
|
model: opts.config.operator.model ?? undefined,
|
|
83
|
-
extensionPaths:
|
|
148
|
+
extensionPaths: operatorExtensionPaths,
|
|
84
149
|
});
|
|
85
150
|
return new MessagingOperatorRuntime({
|
|
86
151
|
backend,
|
|
@@ -100,7 +165,9 @@ export async function bootstrapControlPlane(opts) {
|
|
|
100
165
|
const paths = getControlPlanePaths(opts.repoRoot);
|
|
101
166
|
const runtime = new ControlPlaneRuntime({ repoRoot: opts.repoRoot });
|
|
102
167
|
let pipeline = null;
|
|
168
|
+
let runSupervisor = null;
|
|
103
169
|
let drainInterval = null;
|
|
170
|
+
const adapterMap = new Map();
|
|
104
171
|
try {
|
|
105
172
|
await runtime.start();
|
|
106
173
|
const operator = opts.operatorRuntime !== undefined
|
|
@@ -110,12 +177,123 @@ export async function bootstrapControlPlane(opts) {
|
|
|
110
177
|
config: controlPlaneConfig,
|
|
111
178
|
backend: opts.operatorBackend,
|
|
112
179
|
});
|
|
113
|
-
pipeline = new ControlPlaneCommandPipeline({ runtime, operator });
|
|
114
|
-
await pipeline.start();
|
|
115
180
|
const outbox = new ControlPlaneOutbox(paths.outboxPath);
|
|
116
181
|
await outbox.load();
|
|
182
|
+
let scheduleOutboxDrainRef = null;
|
|
183
|
+
runSupervisor = new ControlPlaneRunSupervisor({
|
|
184
|
+
repoRoot: opts.repoRoot,
|
|
185
|
+
heartbeatScheduler: opts.heartbeatScheduler,
|
|
186
|
+
onEvent: async (event) => {
|
|
187
|
+
const outboxRecord = await enqueueRunEventOutbox({
|
|
188
|
+
outbox,
|
|
189
|
+
event,
|
|
190
|
+
nowMs: Math.trunc(Date.now()),
|
|
191
|
+
});
|
|
192
|
+
if (outboxRecord) {
|
|
193
|
+
scheduleOutboxDrainRef?.();
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
pipeline = new ControlPlaneCommandPipeline({
|
|
198
|
+
runtime,
|
|
199
|
+
operator,
|
|
200
|
+
mutationExecutor: async (record) => {
|
|
201
|
+
if (record.target_type === "run start" || record.target_type === "run resume") {
|
|
202
|
+
try {
|
|
203
|
+
const launched = await runSupervisor?.startFromCommand(record);
|
|
204
|
+
if (!launched) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
terminalState: "completed",
|
|
209
|
+
result: {
|
|
210
|
+
ok: true,
|
|
211
|
+
async_run: true,
|
|
212
|
+
run_job_id: launched.job_id,
|
|
213
|
+
run_root_id: launched.root_issue_id,
|
|
214
|
+
run_status: launched.status,
|
|
215
|
+
run_mode: launched.mode,
|
|
216
|
+
run_source: launched.source,
|
|
217
|
+
},
|
|
218
|
+
trace: {
|
|
219
|
+
cliCommandKind: launched.mode,
|
|
220
|
+
runRootId: launched.root_issue_id,
|
|
221
|
+
},
|
|
222
|
+
mutatingEvents: [
|
|
223
|
+
{
|
|
224
|
+
eventType: "run.supervisor.start",
|
|
225
|
+
payload: {
|
|
226
|
+
run_job_id: launched.job_id,
|
|
227
|
+
run_mode: launched.mode,
|
|
228
|
+
run_root_id: launched.root_issue_id,
|
|
229
|
+
run_source: launched.source,
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
return {
|
|
237
|
+
terminalState: "failed",
|
|
238
|
+
errorCode: err instanceof Error && err.message ? err.message : "run_supervisor_start_failed",
|
|
239
|
+
trace: {
|
|
240
|
+
cliCommandKind: record.target_type.replaceAll(" ", "_"),
|
|
241
|
+
runRootId: record.target_id,
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (record.target_type === "run interrupt") {
|
|
247
|
+
const result = runSupervisor?.interrupt({
|
|
248
|
+
rootIssueId: record.target_id,
|
|
249
|
+
}) ?? { ok: false, reason: "not_found", run: null };
|
|
250
|
+
if (!result.ok) {
|
|
251
|
+
return {
|
|
252
|
+
terminalState: "failed",
|
|
253
|
+
errorCode: result.reason ?? "run_interrupt_failed",
|
|
254
|
+
trace: {
|
|
255
|
+
cliCommandKind: "run_interrupt",
|
|
256
|
+
runRootId: result.run?.root_issue_id ?? record.target_id,
|
|
257
|
+
},
|
|
258
|
+
mutatingEvents: [
|
|
259
|
+
{
|
|
260
|
+
eventType: "run.supervisor.interrupt.failed",
|
|
261
|
+
payload: {
|
|
262
|
+
reason: result.reason,
|
|
263
|
+
target: record.target_id,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
terminalState: "completed",
|
|
271
|
+
result: {
|
|
272
|
+
ok: true,
|
|
273
|
+
async_run: true,
|
|
274
|
+
interrupted: true,
|
|
275
|
+
run: result.run,
|
|
276
|
+
},
|
|
277
|
+
trace: {
|
|
278
|
+
cliCommandKind: "run_interrupt",
|
|
279
|
+
runRootId: result.run?.root_issue_id ?? record.target_id,
|
|
280
|
+
},
|
|
281
|
+
mutatingEvents: [
|
|
282
|
+
{
|
|
283
|
+
eventType: "run.supervisor.interrupt",
|
|
284
|
+
payload: {
|
|
285
|
+
target: record.target_id,
|
|
286
|
+
run: result.run,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
await pipeline.start();
|
|
117
296
|
let telegramBotToken = null;
|
|
118
|
-
const adapterMap = new Map();
|
|
119
297
|
for (const d of detected) {
|
|
120
298
|
let adapter;
|
|
121
299
|
switch (d.name) {
|
|
@@ -139,6 +317,10 @@ export async function bootstrapControlPlane(opts) {
|
|
|
139
317
|
outbox,
|
|
140
318
|
webhookSecret: d.webhookSecret,
|
|
141
319
|
botUsername: d.botUsername ?? undefined,
|
|
320
|
+
deferredIngress: true,
|
|
321
|
+
onOutboxEnqueued: () => {
|
|
322
|
+
scheduleOutboxDrainRef?.();
|
|
323
|
+
},
|
|
142
324
|
});
|
|
143
325
|
if (d.botToken) {
|
|
144
326
|
telegramBotToken = d.botToken;
|
|
@@ -199,14 +381,37 @@ export async function bootstrapControlPlane(opts) {
|
|
|
199
381
|
return undefined;
|
|
200
382
|
};
|
|
201
383
|
const dispatcher = new ControlPlaneOutboxDispatcher({ outbox, deliver });
|
|
202
|
-
|
|
384
|
+
let drainingOutbox = false;
|
|
385
|
+
let drainRequested = false;
|
|
386
|
+
const drainOutboxNow = async () => {
|
|
387
|
+
if (drainingOutbox) {
|
|
388
|
+
drainRequested = true;
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
drainingOutbox = true;
|
|
203
392
|
try {
|
|
204
|
-
|
|
393
|
+
do {
|
|
394
|
+
drainRequested = false;
|
|
395
|
+
await dispatcher.drainDue();
|
|
396
|
+
} while (drainRequested);
|
|
205
397
|
}
|
|
206
398
|
catch {
|
|
207
|
-
// Swallow errors — the dispatcher
|
|
399
|
+
// Swallow errors — the dispatcher handles retries internally.
|
|
400
|
+
}
|
|
401
|
+
finally {
|
|
402
|
+
drainingOutbox = false;
|
|
208
403
|
}
|
|
209
|
-
}
|
|
404
|
+
};
|
|
405
|
+
const scheduleOutboxDrain = () => {
|
|
406
|
+
queueMicrotask(() => {
|
|
407
|
+
void drainOutboxNow();
|
|
408
|
+
});
|
|
409
|
+
};
|
|
410
|
+
scheduleOutboxDrainRef = scheduleOutboxDrain;
|
|
411
|
+
drainInterval = setInterval(() => {
|
|
412
|
+
scheduleOutboxDrain();
|
|
413
|
+
}, OUTBOX_DRAIN_INTERVAL_MS);
|
|
414
|
+
scheduleOutboxDrain();
|
|
210
415
|
return {
|
|
211
416
|
activeAdapters: [...adapterMap.values()].map((v) => v.info),
|
|
212
417
|
async handleWebhook(path, req) {
|
|
@@ -214,13 +419,65 @@ export async function bootstrapControlPlane(opts) {
|
|
|
214
419
|
if (!entry)
|
|
215
420
|
return null;
|
|
216
421
|
const result = await entry.adapter.ingest(req);
|
|
422
|
+
if (result.outboxRecord) {
|
|
423
|
+
scheduleOutboxDrain();
|
|
424
|
+
}
|
|
217
425
|
return result.response;
|
|
218
426
|
},
|
|
427
|
+
async listRuns(opts = {}) {
|
|
428
|
+
return (runSupervisor?.list({
|
|
429
|
+
status: opts.status,
|
|
430
|
+
limit: opts.limit,
|
|
431
|
+
}) ?? []);
|
|
432
|
+
},
|
|
433
|
+
async getRun(idOrRoot) {
|
|
434
|
+
return runSupervisor?.get(idOrRoot) ?? null;
|
|
435
|
+
},
|
|
436
|
+
async startRun(startOpts) {
|
|
437
|
+
const run = await runSupervisor?.launchStart({
|
|
438
|
+
prompt: startOpts.prompt,
|
|
439
|
+
maxSteps: startOpts.maxSteps,
|
|
440
|
+
source: "api",
|
|
441
|
+
});
|
|
442
|
+
if (!run) {
|
|
443
|
+
throw new Error("run_supervisor_unavailable");
|
|
444
|
+
}
|
|
445
|
+
return run;
|
|
446
|
+
},
|
|
447
|
+
async resumeRun(resumeOpts) {
|
|
448
|
+
const run = await runSupervisor?.launchResume({
|
|
449
|
+
rootIssueId: resumeOpts.rootIssueId,
|
|
450
|
+
maxSteps: resumeOpts.maxSteps,
|
|
451
|
+
source: "api",
|
|
452
|
+
});
|
|
453
|
+
if (!run) {
|
|
454
|
+
throw new Error("run_supervisor_unavailable");
|
|
455
|
+
}
|
|
456
|
+
return run;
|
|
457
|
+
},
|
|
458
|
+
async interruptRun(interruptOpts) {
|
|
459
|
+
return runSupervisor?.interrupt(interruptOpts) ?? { ok: false, reason: "not_found", run: null };
|
|
460
|
+
},
|
|
461
|
+
async heartbeatRun(heartbeatOpts) {
|
|
462
|
+
return runSupervisor?.heartbeat(heartbeatOpts) ?? { ok: false, reason: "not_found", run: null };
|
|
463
|
+
},
|
|
464
|
+
async traceRun(traceOpts) {
|
|
465
|
+
return (await runSupervisor?.trace(traceOpts.idOrRoot, { limit: traceOpts.limit })) ?? null;
|
|
466
|
+
},
|
|
219
467
|
async stop() {
|
|
220
468
|
if (drainInterval) {
|
|
221
469
|
clearInterval(drainInterval);
|
|
222
470
|
drainInterval = null;
|
|
223
471
|
}
|
|
472
|
+
for (const { adapter } of adapterMap.values()) {
|
|
473
|
+
try {
|
|
474
|
+
await adapter.stop?.();
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
// Best effort adapter cleanup.
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
await runSupervisor?.stop();
|
|
224
481
|
try {
|
|
225
482
|
await pipeline?.stop();
|
|
226
483
|
}
|
|
@@ -235,6 +492,20 @@ export async function bootstrapControlPlane(opts) {
|
|
|
235
492
|
clearInterval(drainInterval);
|
|
236
493
|
drainInterval = null;
|
|
237
494
|
}
|
|
495
|
+
for (const { adapter } of adapterMap.values()) {
|
|
496
|
+
try {
|
|
497
|
+
await adapter.stop?.();
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
// Best effort cleanup.
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
await runSupervisor?.stop();
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
// Best effort cleanup.
|
|
508
|
+
}
|
|
238
509
|
try {
|
|
239
510
|
await pipeline?.stop();
|
|
240
511
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { JsonlStore } from "@femtomc/mu-core";
|
|
2
|
+
import { ActivityHeartbeatScheduler } from "./heartbeat_scheduler.js";
|
|
3
|
+
export type HeartbeatProgramTarget = {
|
|
4
|
+
kind: "run";
|
|
5
|
+
job_id: string | null;
|
|
6
|
+
root_issue_id: string | null;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "activity";
|
|
9
|
+
activity_id: string;
|
|
10
|
+
};
|
|
11
|
+
export type HeartbeatProgramSnapshot = {
|
|
12
|
+
v: 1;
|
|
13
|
+
program_id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
every_ms: number;
|
|
17
|
+
reason: string;
|
|
18
|
+
target: HeartbeatProgramTarget;
|
|
19
|
+
metadata: Record<string, unknown>;
|
|
20
|
+
created_at_ms: number;
|
|
21
|
+
updated_at_ms: number;
|
|
22
|
+
last_triggered_at_ms: number | null;
|
|
23
|
+
last_result: "ok" | "not_found" | "not_running" | "failed" | null;
|
|
24
|
+
last_error: string | null;
|
|
25
|
+
};
|
|
26
|
+
export type HeartbeatProgramOperationResult = {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
reason: "not_found" | "missing_target" | "invalid_target" | "not_running" | "failed" | null;
|
|
29
|
+
program: HeartbeatProgramSnapshot | null;
|
|
30
|
+
};
|
|
31
|
+
export type HeartbeatProgramTickEvent = {
|
|
32
|
+
ts_ms: number;
|
|
33
|
+
program_id: string;
|
|
34
|
+
message: string;
|
|
35
|
+
status: "ok" | "not_found" | "not_running" | "failed";
|
|
36
|
+
reason: string | null;
|
|
37
|
+
program: HeartbeatProgramSnapshot;
|
|
38
|
+
};
|
|
39
|
+
export type HeartbeatProgramRegistryOpts = {
|
|
40
|
+
repoRoot: string;
|
|
41
|
+
heartbeatScheduler: ActivityHeartbeatScheduler;
|
|
42
|
+
nowMs?: () => number;
|
|
43
|
+
store?: JsonlStore<HeartbeatProgramSnapshot>;
|
|
44
|
+
runHeartbeat: (opts: {
|
|
45
|
+
jobId?: string | null;
|
|
46
|
+
rootIssueId?: string | null;
|
|
47
|
+
reason?: string | null;
|
|
48
|
+
}) => Promise<{
|
|
49
|
+
ok: boolean;
|
|
50
|
+
reason: "not_found" | "not_running" | "missing_target" | null;
|
|
51
|
+
}>;
|
|
52
|
+
activityHeartbeat: (opts: {
|
|
53
|
+
activityId?: string | null;
|
|
54
|
+
reason?: string | null;
|
|
55
|
+
}) => Promise<{
|
|
56
|
+
ok: boolean;
|
|
57
|
+
reason: "not_found" | "not_running" | "missing_target" | null;
|
|
58
|
+
}>;
|
|
59
|
+
onTickEvent?: (event: HeartbeatProgramTickEvent) => void | Promise<void>;
|
|
60
|
+
};
|
|
61
|
+
export declare class HeartbeatProgramRegistry {
|
|
62
|
+
#private;
|
|
63
|
+
constructor(opts: HeartbeatProgramRegistryOpts);
|
|
64
|
+
list(opts?: {
|
|
65
|
+
enabled?: boolean;
|
|
66
|
+
targetKind?: "run" | "activity";
|
|
67
|
+
limit?: number;
|
|
68
|
+
}): Promise<HeartbeatProgramSnapshot[]>;
|
|
69
|
+
get(programId: string): Promise<HeartbeatProgramSnapshot | null>;
|
|
70
|
+
create(opts: {
|
|
71
|
+
title: string;
|
|
72
|
+
target: HeartbeatProgramTarget;
|
|
73
|
+
everyMs?: number;
|
|
74
|
+
reason?: string;
|
|
75
|
+
enabled?: boolean;
|
|
76
|
+
metadata?: Record<string, unknown>;
|
|
77
|
+
}): Promise<HeartbeatProgramSnapshot>;
|
|
78
|
+
update(opts: {
|
|
79
|
+
programId: string;
|
|
80
|
+
title?: string;
|
|
81
|
+
everyMs?: number;
|
|
82
|
+
reason?: string;
|
|
83
|
+
enabled?: boolean;
|
|
84
|
+
target?: HeartbeatProgramTarget;
|
|
85
|
+
metadata?: Record<string, unknown>;
|
|
86
|
+
}): Promise<HeartbeatProgramOperationResult>;
|
|
87
|
+
remove(programId: string): Promise<HeartbeatProgramOperationResult>;
|
|
88
|
+
trigger(opts: {
|
|
89
|
+
programId?: string | null;
|
|
90
|
+
reason?: string | null;
|
|
91
|
+
}): Promise<HeartbeatProgramOperationResult>;
|
|
92
|
+
stop(): void;
|
|
93
|
+
}
|