Files
openclaw/src/plugin-sdk/extension-shared.ts
2026-03-18 22:58:40 -05:00

136 lines
3.6 KiB
TypeScript

import type { z } from "zod";
import { runPassiveAccountLifecycle } from "./channel-runtime.js";
import { createLoggerBackedRuntime } from "./runtime.js";
type PassiveChannelStatusSnapshot = {
configured?: boolean;
running?: boolean;
lastStartAt?: number | null;
lastStopAt?: number | null;
lastError?: string | null;
probe?: unknown;
lastProbeAt?: number | null;
};
type TrafficStatusSnapshot = {
lastInboundAt?: number | null;
lastOutboundAt?: number | null;
};
type StoppableMonitor = {
stop: () => void;
};
type RequireOpenAllowFromFn = (params: {
policy?: string;
allowFrom?: Array<string | number>;
ctx: z.RefinementCtx;
path: Array<string | number>;
message: string;
}) => void;
export function buildPassiveChannelStatusSummary<TExtra extends object>(
snapshot: PassiveChannelStatusSnapshot,
extra?: TExtra,
) {
return {
configured: snapshot.configured ?? false,
...(extra ?? ({} as TExtra)),
running: snapshot.running ?? false,
lastStartAt: snapshot.lastStartAt ?? null,
lastStopAt: snapshot.lastStopAt ?? null,
lastError: snapshot.lastError ?? null,
};
}
export function buildPassiveProbedChannelStatusSummary<TExtra extends object>(
snapshot: PassiveChannelStatusSnapshot,
extra?: TExtra,
) {
return {
...buildPassiveChannelStatusSummary(snapshot, extra),
probe: snapshot.probe,
lastProbeAt: snapshot.lastProbeAt ?? null,
};
}
export function buildTrafficStatusSummary<TSnapshot extends TrafficStatusSnapshot>(
snapshot?: TSnapshot | null,
) {
return {
lastInboundAt: snapshot?.lastInboundAt ?? null,
lastOutboundAt: snapshot?.lastOutboundAt ?? null,
};
}
export async function runStoppablePassiveMonitor<TMonitor extends StoppableMonitor>(params: {
abortSignal: AbortSignal;
start: () => Promise<TMonitor>;
}): Promise<void> {
await runPassiveAccountLifecycle({
abortSignal: params.abortSignal,
start: params.start,
stop: async (monitor) => {
monitor.stop();
},
});
}
export function resolveLoggerBackedRuntime<TRuntime>(
runtime: TRuntime | undefined,
logger: Parameters<typeof createLoggerBackedRuntime>[0]["logger"],
): TRuntime {
return (
runtime ??
(createLoggerBackedRuntime({
logger,
exitError: () => new Error("Runtime exit not available"),
}) as TRuntime)
);
}
export function requireChannelOpenAllowFrom(params: {
channel: string;
policy?: string;
allowFrom?: Array<string | number>;
ctx: z.RefinementCtx;
requireOpenAllowFrom: RequireOpenAllowFromFn;
}) {
params.requireOpenAllowFrom({
policy: params.policy,
allowFrom: params.allowFrom,
ctx: params.ctx,
path: ["allowFrom"],
message: `channels.${params.channel}.dmPolicy="open" requires channels.${params.channel}.allowFrom to include "*"`,
});
}
export function readStatusIssueFields<TField extends string>(
value: unknown,
fields: readonly TField[],
): Record<TField, unknown> | null {
if (!value || typeof value !== "object") {
return null;
}
const record = value as Record<string, unknown>;
const result = {} as Record<TField, unknown>;
for (const field of fields) {
result[field] = record[field];
}
return result;
}
export function coerceStatusIssueAccountId(value: unknown): string | undefined {
return typeof value === "string" ? value : typeof value === "number" ? String(value) : undefined;
}
export function createDeferred<T>() {
let resolve!: (value: T | PromiseLike<T>) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<T>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}