@femtomc/mu-server 26.2.69 → 26.2.70

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.
@@ -0,0 +1,15 @@
1
+ import { type MessagingOperatorBackend, MessagingOperatorRuntime } from "@femtomc/mu-agent";
2
+ import { ControlPlaneOutbox, type OutboxDeliveryHandlerResult, type OutboxRecord } from "@femtomc/mu-control-plane";
3
+ import type { ControlPlaneConfig } from "./control_plane_contract.js";
4
+ export declare function buildMessagingOperatorRuntime(opts: {
5
+ repoRoot: string;
6
+ config: ControlPlaneConfig;
7
+ backend?: MessagingOperatorBackend;
8
+ }): MessagingOperatorRuntime | null;
9
+ export declare function createOutboxDrainLoop(opts: {
10
+ outbox: ControlPlaneOutbox;
11
+ deliver: (record: OutboxRecord) => Promise<undefined | OutboxDeliveryHandlerResult>;
12
+ }): {
13
+ scheduleOutboxDrain: () => void;
14
+ stop: () => void;
15
+ };
@@ -0,0 +1,75 @@
1
+ import { ApprovedCommandBroker, CommandContextResolver, MessagingOperatorRuntime, operatorExtensionPaths, PiMessagingOperatorBackend, } from "@femtomc/mu-agent";
2
+ import { ControlPlaneOutboxDispatcher, } from "@femtomc/mu-control-plane";
3
+ const OUTBOX_DRAIN_INTERVAL_MS = 500;
4
+ export function buildMessagingOperatorRuntime(opts) {
5
+ if (!opts.config.operator.enabled) {
6
+ return null;
7
+ }
8
+ const backend = opts.backend ??
9
+ new PiMessagingOperatorBackend({
10
+ provider: opts.config.operator.provider ?? undefined,
11
+ model: opts.config.operator.model ?? undefined,
12
+ extensionPaths: operatorExtensionPaths,
13
+ });
14
+ return new MessagingOperatorRuntime({
15
+ backend,
16
+ broker: new ApprovedCommandBroker({
17
+ runTriggersEnabled: opts.config.operator.run_triggers_enabled,
18
+ contextResolver: new CommandContextResolver({ allowedRepoRoots: [opts.repoRoot] }),
19
+ }),
20
+ enabled: true,
21
+ });
22
+ }
23
+ export function createOutboxDrainLoop(opts) {
24
+ const dispatcher = new ControlPlaneOutboxDispatcher({
25
+ outbox: opts.outbox,
26
+ deliver: opts.deliver,
27
+ });
28
+ let drainingOutbox = false;
29
+ let drainRequested = false;
30
+ let stopped = false;
31
+ const drainOutboxNow = async () => {
32
+ if (stopped) {
33
+ return;
34
+ }
35
+ if (drainingOutbox) {
36
+ drainRequested = true;
37
+ return;
38
+ }
39
+ drainingOutbox = true;
40
+ try {
41
+ do {
42
+ drainRequested = false;
43
+ await dispatcher.drainDue();
44
+ } while (drainRequested && !stopped);
45
+ }
46
+ catch {
47
+ // Swallow errors — dispatcher handles retry progression internally.
48
+ }
49
+ finally {
50
+ drainingOutbox = false;
51
+ }
52
+ };
53
+ const scheduleOutboxDrain = () => {
54
+ if (stopped) {
55
+ return;
56
+ }
57
+ queueMicrotask(() => {
58
+ void drainOutboxNow();
59
+ });
60
+ };
61
+ const interval = setInterval(() => {
62
+ scheduleOutboxDrain();
63
+ }, OUTBOX_DRAIN_INTERVAL_MS);
64
+ scheduleOutboxDrain();
65
+ return {
66
+ scheduleOutboxDrain,
67
+ stop: () => {
68
+ if (stopped) {
69
+ return;
70
+ }
71
+ stopped = true;
72
+ clearInterval(interval);
73
+ },
74
+ };
75
+ }
@@ -0,0 +1,119 @@
1
+ import type { Channel, CommandPipelineResult, ReloadableGenerationIdentity } from "@femtomc/mu-control-plane";
2
+ import type { ControlPlaneRunHeartbeatResult, ControlPlaneRunInterruptResult, ControlPlaneRunSnapshot, ControlPlaneRunTrace } from "./run_supervisor.js";
3
+ import type { MuConfig } from "./config.js";
4
+ /**
5
+ * Boundary contracts for server/control-plane composition.
6
+ *
7
+ * Dependency direction:
8
+ * - Domain/application code should depend on these contracts.
9
+ * - Interface adapters in `control_plane.ts` implement these seams.
10
+ */
11
+ export type ControlPlaneConfig = MuConfig["control_plane"];
12
+ export type ActiveAdapter = {
13
+ name: Channel;
14
+ route: string;
15
+ };
16
+ export type TelegramGenerationRollbackTrigger = "manual" | "warmup_failed" | "health_gate_failed" | "cutover_failed" | "post_cutover_health_failed" | "rollback_unavailable" | "rollback_failed";
17
+ export type TelegramGenerationReloadResult = {
18
+ handled: boolean;
19
+ ok: boolean;
20
+ reason: string;
21
+ route: string;
22
+ from_generation: ReloadableGenerationIdentity | null;
23
+ to_generation: ReloadableGenerationIdentity | null;
24
+ active_generation: ReloadableGenerationIdentity | null;
25
+ warmup: {
26
+ ok: boolean;
27
+ elapsed_ms: number;
28
+ error?: string;
29
+ } | null;
30
+ cutover: {
31
+ ok: boolean;
32
+ elapsed_ms: number;
33
+ error?: string;
34
+ } | null;
35
+ drain: {
36
+ ok: boolean;
37
+ elapsed_ms: number;
38
+ timed_out: boolean;
39
+ forced_stop: boolean;
40
+ error?: string;
41
+ } | null;
42
+ rollback: {
43
+ requested: boolean;
44
+ trigger: TelegramGenerationRollbackTrigger | null;
45
+ attempted: boolean;
46
+ ok: boolean;
47
+ error?: string;
48
+ };
49
+ error?: string;
50
+ };
51
+ export type ControlPlaneGenerationContext = ReloadableGenerationIdentity;
52
+ export type TelegramGenerationSwapHooks = {
53
+ onWarmup?: (ctx: {
54
+ generation: ReloadableGenerationIdentity;
55
+ reason: string;
56
+ }) => void | Promise<void>;
57
+ onCutover?: (ctx: {
58
+ from_generation: ReloadableGenerationIdentity | null;
59
+ to_generation: ReloadableGenerationIdentity;
60
+ reason: string;
61
+ }) => void | Promise<void>;
62
+ onDrain?: (ctx: {
63
+ generation: ReloadableGenerationIdentity;
64
+ reason: string;
65
+ timeout_ms: number;
66
+ }) => void | Promise<void>;
67
+ };
68
+ export type ControlPlaneSessionMutationAction = "reload" | "update";
69
+ export type ControlPlaneSessionMutationResult = {
70
+ ok: boolean;
71
+ action: ControlPlaneSessionMutationAction;
72
+ message: string;
73
+ details?: Record<string, unknown>;
74
+ };
75
+ export type ControlPlaneSessionLifecycle = {
76
+ reload: () => Promise<ControlPlaneSessionMutationResult>;
77
+ update: () => Promise<ControlPlaneSessionMutationResult>;
78
+ };
79
+ export type ControlPlaneHandle = {
80
+ activeAdapters: ActiveAdapter[];
81
+ handleWebhook(path: string, req: Request): Promise<Response | null>;
82
+ reloadTelegramGeneration?(opts: {
83
+ config: ControlPlaneConfig;
84
+ reason: string;
85
+ }): Promise<TelegramGenerationReloadResult>;
86
+ listRuns?(opts?: {
87
+ status?: string;
88
+ limit?: number;
89
+ }): Promise<ControlPlaneRunSnapshot[]>;
90
+ getRun?(idOrRoot: string): Promise<ControlPlaneRunSnapshot | null>;
91
+ startRun?(opts: {
92
+ prompt: string;
93
+ maxSteps?: number;
94
+ }): Promise<ControlPlaneRunSnapshot>;
95
+ resumeRun?(opts: {
96
+ rootIssueId: string;
97
+ maxSteps?: number;
98
+ }): Promise<ControlPlaneRunSnapshot>;
99
+ interruptRun?(opts: {
100
+ jobId?: string | null;
101
+ rootIssueId?: string | null;
102
+ }): Promise<ControlPlaneRunInterruptResult>;
103
+ heartbeatRun?(opts: {
104
+ jobId?: string | null;
105
+ rootIssueId?: string | null;
106
+ reason?: string | null;
107
+ wakeMode?: string | null;
108
+ }): Promise<ControlPlaneRunHeartbeatResult>;
109
+ traceRun?(opts: {
110
+ idOrRoot: string;
111
+ limit?: number;
112
+ }): Promise<ControlPlaneRunTrace | null>;
113
+ submitTerminalCommand?(opts: {
114
+ commandText: string;
115
+ repoRoot: string;
116
+ requestId?: string;
117
+ }): Promise<CommandPipelineResult>;
118
+ stop(): Promise<void>;
119
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import { type ControlPlaneOutbox, type OutboxRecord } from "@femtomc/mu-control-plane";
2
+ import type { ControlPlaneRunEvent } from "./run_supervisor.js";
3
+ export declare function enqueueRunEventOutbox(opts: {
4
+ outbox: ControlPlaneOutbox;
5
+ event: ControlPlaneRunEvent;
6
+ nowMs: number;
7
+ }): Promise<OutboxRecord | null>;
@@ -0,0 +1,52 @@
1
+ import { correlationFromCommandRecord, } from "@femtomc/mu-control-plane";
2
+ function sha256Hex(input) {
3
+ const hasher = new Bun.CryptoHasher("sha256");
4
+ hasher.update(input);
5
+ return hasher.digest("hex");
6
+ }
7
+ function outboxKindForRunEvent(kind) {
8
+ switch (kind) {
9
+ case "run_completed":
10
+ return "result";
11
+ case "run_failed":
12
+ return "error";
13
+ default:
14
+ return "lifecycle";
15
+ }
16
+ }
17
+ export async function enqueueRunEventOutbox(opts) {
18
+ const command = opts.event.command;
19
+ if (!command) {
20
+ return null;
21
+ }
22
+ const baseCorrelation = correlationFromCommandRecord(command);
23
+ const correlation = {
24
+ ...baseCorrelation,
25
+ run_root_id: opts.event.run.root_issue_id ?? baseCorrelation.run_root_id,
26
+ };
27
+ const envelope = {
28
+ v: 1,
29
+ ts_ms: opts.nowMs,
30
+ channel: command.channel,
31
+ channel_tenant_id: command.channel_tenant_id,
32
+ channel_conversation_id: command.channel_conversation_id,
33
+ request_id: command.request_id,
34
+ response_id: `resp-${sha256Hex(`run-event:${opts.event.run.job_id}:${opts.event.seq}:${opts.nowMs}`).slice(0, 20)}`,
35
+ kind: outboxKindForRunEvent(opts.event.kind),
36
+ body: opts.event.message,
37
+ correlation,
38
+ metadata: {
39
+ async_run: true,
40
+ run_event_kind: opts.event.kind,
41
+ run_event_seq: opts.event.seq,
42
+ run: opts.event.run,
43
+ },
44
+ };
45
+ const decision = await opts.outbox.enqueue({
46
+ dedupeKey: `run-event:${opts.event.run.job_id}:${opts.event.seq}`,
47
+ envelope,
48
+ nowMs: opts.nowMs,
49
+ maxAttempts: 6,
50
+ });
51
+ return decision.record;
52
+ }
@@ -0,0 +1,27 @@
1
+ import { ControlPlaneCommandPipeline, ControlPlaneOutbox, type ControlPlaneSignalObserver, type ReloadableGenerationIdentity, TelegramControlPlaneAdapter } from "@femtomc/mu-control-plane";
2
+ import type { ControlPlaneConfig, TelegramGenerationReloadResult, TelegramGenerationSwapHooks } from "./control_plane_contract.js";
3
+ export declare class TelegramAdapterGenerationManager {
4
+ #private;
5
+ constructor(opts: {
6
+ pipeline: ControlPlaneCommandPipeline;
7
+ outbox: ControlPlaneOutbox;
8
+ initialConfig: ControlPlaneConfig;
9
+ nowMs?: () => number;
10
+ onOutboxEnqueued?: () => void;
11
+ signalObserver?: ControlPlaneSignalObserver;
12
+ hooks?: TelegramGenerationSwapHooks;
13
+ });
14
+ initialize(): Promise<void>;
15
+ hasActiveGeneration(): boolean;
16
+ activeGeneration(): ReloadableGenerationIdentity | null;
17
+ activeBotToken(): string | null;
18
+ activeAdapter(): TelegramControlPlaneAdapter | null;
19
+ canHandleConfig(nextConfig: ControlPlaneConfig, reason: string): boolean;
20
+ reload(opts: {
21
+ config: ControlPlaneConfig;
22
+ reason: string;
23
+ warmupTimeoutMs?: number;
24
+ drainTimeoutMs?: number;
25
+ }): Promise<TelegramGenerationReloadResult>;
26
+ stop(): Promise<void>;
27
+ }