Files
openclaw/extensions/imap/index.ts
Peter Steinberger 87b56458a1 feat(plugins): inbound IMAP email trigger (#130230)
* feat(plugin-sdk): add in-process hook agent dispatch seam

* feat(plugins): add imap inbound email trigger plugin

* fix(imap): coalesce sweep wakeups and drop test-only gate exports

* docs(imap): fix agents bindings command invocation

* fix(gateway): guard optional bound context in plugin hook dispatch

Post-rebase drift made resolveBoundGatewayContext possibly-undefined; the
plugin hook dispatch path invoked it unconditionally. Use the optional-call
guard consistent with sibling runtime accessors.

* fix(imap): bump mailparser to 3.9.16 off vulnerable deepmerge-ts

mailparser 3.9.15 pinned html-to-text@10.0.0 -> deepmerge-ts@7.1.6 (GHSA
HIGH, stack exhaustion, <8.0.0). 3.9.16 pins html-to-text@10.0.1 ->
deepmerge-ts@8.0.2, clearing the production audit gate.

* refactor(gateway): extract plugin hooks runtime to keep server-plugins under max-lines

The dispatchHookAgentTurn seam pushed server-plugins.ts to 702 effective lines
(cap 700). Extract createGatewayHooksRuntime into the sibling runtime-helpers
module, mirroring createGatewayNodesRuntime/createGatewaySubagentRuntime; no
suppression added.

* docs(secrets): register imap plugin password in secretref credential matrix

The bundled imap plugin declares a secretInput (accounts.*.password); regenerate
the user-supplied credentials matrix and surface list so the target-registry docs
sync test stays green.
2026-08-26 12:24:02 -07:00

70 lines
2.3 KiB
TypeScript

import {
definePluginEntry,
type OpenClawPluginApi,
type OpenClawPluginServiceContext,
} from "openclaw/plugin-sdk/plugin-entry";
import { resolveImapConfig } from "./src/config.js";
import { createImapState } from "./src/state.js";
import { ImapAccountWatcher } from "./src/watcher.js";
const imapConfigSchema = { parse: resolveImapConfig };
export default definePluginEntry({
id: "imap",
name: "IMAP email trigger",
description: "Dispatch authenticated incoming IMAP email to isolated agent sessions.",
configSchema: imapConfigSchema,
register(api: OpenClawPluginApi) {
if (api.registrationMode !== "full") {
return;
}
let generation = 0;
let watchers: ImapAccountWatcher[] = [];
api.registerService({
id: "imap-watch",
start(context: OpenClawPluginServiceContext) {
const previous = watchers;
const activeGeneration = ++generation;
watchers = [];
const start = async () => {
await Promise.all(previous.map((watcher) => watcher.stop()));
if (activeGeneration !== generation) {
return;
}
const config = imapConfigSchema.parse(api.pluginConfig, (accountId) => {
context.logger.warn(
`imap: account=${accountId} unavailable; resolve its IMAP password and reload configuration`,
);
});
const accounts = Object.entries(config.accounts);
if (!accounts.length) {
context.logger.warn(
"imap: no accounts configured; add plugins.entries.imap.config.accounts",
);
return;
}
const state = createImapState(api.runtime);
watchers = accounts.map(
([accountId, account]) =>
new ImapAccountWatcher({ accountId, account, runtime: api.runtime, state, context }),
);
for (const watcher of watchers) {
watcher.start();
}
};
void start().catch((error: unknown) => {
if (activeGeneration === generation) {
context.serviceHealth?.reportFailure(error);
}
});
},
async stop() {
generation++;
const active = watchers;
watchers = [];
await Promise.all(active.map((watcher) => watcher.stop()));
},
});
},
});