akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
59 lines (58 loc) • 2.57 kB
JavaScript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
/** Signal -> {exit code, reason, ack message}. */
export const SIGNAL_TABLE = {
SIGTERM: { code: 143, reason: "SIGTERM", ack: true },
SIGINT: { code: 130, reason: "SIGINT", ack: true },
SIGHUP: { code: 129, reason: "SIGHUP", ack: false },
};
/**
* Drive the improve-run lifecycle: register signal handlers, await `runWork`,
* and on clean completion remove the handlers and resolve with the result. The
* caller (improve-cli.ts) owns success-path result persistence + the final
* `exit(0)`; this function returns the work result so that choreography stays
* in the CLI exactly as before.
*
* Abnormal paths:
* - A registered signal fires -> persist terminated row (watchdog-guarded) ->
* optional ack -> `exit(code)`.
* - `runWork` rejects -> handlers are removed and the rejection propagates so
* the CLI's existing catch can persist `"exception"` and rethrow.
*/
export async function runImproveSession(opts, deps) {
const { signalSource, exit, onTerminate, ack } = deps;
const makeSignalHandler = (sig) => () => {
const { code, reason, ack: shouldAck } = SIGNAL_TABLE[sig];
// Hard-exit fallback: if the synchronous persist ever hangs (e.g. a stuck
// sqlite lock under contention), the watchdog still exits with the correct
// code instead of leaving a zombie process. .unref() keeps the timer from
// holding the loop open on the normal (fast) path.
const watchdog = setTimeout(() => exit(code), 2000);
if (typeof watchdog.unref === "function")
watchdog.unref();
try {
onTerminate(reason);
}
finally {
clearTimeout(watchdog);
}
if (shouldAck)
ack?.(`received ${sig}`);
exit(code);
};
const sigtermHandler = makeSignalHandler("SIGTERM");
const sigintHandler = makeSignalHandler("SIGINT");
const sighupHandler = makeSignalHandler("SIGHUP");
signalSource.once("SIGTERM", sigtermHandler);
signalSource.once("SIGINT", sigintHandler);
signalSource.once("SIGHUP", sighupHandler);
try {
return await opts.runWork();
}
finally {
signalSource.removeListener("SIGTERM", sigtermHandler);
signalSource.removeListener("SIGINT", sigintHandler);
signalSource.removeListener("SIGHUP", sighupHandler);
}
}