feat(util-agent): extend AgentOptions with fork / cleanup (Phase 2a)
CI / check (pull_request) Successful in 3m20s

Add AgentForkFn and AgentCleanupFn type aliases. Extend AgentOptions
with fork: AgentForkFn | null and cleanup: AgentCleanupFn | null
fields. Add getAskSessionId / setAskSessionId session-cache helpers
using <stepHash>:ask key shape (coexists with exec sessions in the
same per-agent cache file). All four adapters pass fork: null,
cleanup: null — real wiring lands in Phase 2b. Resolves #145.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 08:14:09 +00:00
parent afc0287094
commit d666516ce6
11 changed files with 363 additions and 1 deletions
+9 -1
View File
@@ -14,12 +14,20 @@ export type { FrontmatterFastPathResult } from "./frontmatter.js";
export { tryFrontmatterFastPath } from "./frontmatter.js";
export { buildFrontmatterRetryPrompt } from "./frontmatter-retry-prompt.js";
export { createAgent, parseArgv } from "./run.js";
export { getCachedSessionId, getCachePath, setCachedSessionId } from "./session-cache.js";
export {
getAskSessionId,
getCachedSessionId,
getCachePath,
setAskSessionId,
setCachedSessionId,
} from "./session-cache.js";
export { getConfigPath, getEnvPath, loadWorkflowConfig, resolveStorageRoot } from "./storage.js";
export type {
AdapterOutput,
AgentCleanupFn,
AgentContext,
AgentContinueFn,
AgentForkFn,
AgentOptions,
AgentRunFn,
AgentRunResult,
+34
View File
@@ -14,6 +14,10 @@ function cacheKey(threadId: ThreadId, role: string): string {
return `${threadId}:${role}`;
}
function askCacheKey(stepHash: string): string {
return `${stepHash}:ask`;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
@@ -86,3 +90,33 @@ export async function setCachedSessionId(
cache[cacheKey(threadId, role)] = sessionId;
await writeCache(agentName, storageRoot, cache);
}
/**
* Read the cached ask-session ID for a stepHash.
*
* Ask sessions are forked side conversations spawned by `step ask` from a
* specific completed step. They share the per-agent cache file with exec
* sessions but use the `<stepHash>:ask` key shape so the two namespaces
* never collide.
*/
export async function getAskSessionId(
agentName: string,
stepHash: string,
storageRoot: string,
): Promise<string | null> {
const cache = await readCache(agentName, storageRoot);
const sessionId = cache[askCacheKey(stepHash)];
return sessionId ?? null;
}
/** Write the ask-session ID for a stepHash into the cache. */
export async function setAskSessionId(
agentName: string,
stepHash: string,
sessionId: string,
storageRoot: string,
): Promise<void> {
const cache = await readCache(agentName, storageRoot);
cache[askCacheKey(stepHash)] = sessionId;
await writeCache(agentName, storageRoot, cache);
}
+25
View File
@@ -50,6 +50,21 @@ export type AgentContinueFn = (
export type AgentRunFn = (ctx: AgentContext) => Promise<AgentRunResult>;
/**
* Fork an existing agent session, returning a new session ID that branches
* from the source session's state. Used by `step ask` (Phase 2a infrastructure)
* to spawn a side conversation from a completed step's session without
* polluting the original session's history.
*/
export type AgentForkFn = (sessionId: string, store: AgentContext["store"]) => Promise<string>;
/**
* Clean up adapter-level resources (e.g. close ACP client, kill subprocesses).
* Invoked by the agent CLI factory after the run completes — regardless of
* success or failure — so adapters can release I/O handles deterministically.
*/
export type AgentCleanupFn = () => Promise<void>;
export type AdapterOutput = {
stepHash: string;
detailHash: string;
@@ -65,4 +80,14 @@ export type AgentOptions = {
name: string;
run: AgentRunFn;
continue: AgentContinueFn;
/**
* Optional session-fork hook. null means the adapter does not yet support
* `step ask` (Phase 2a placeholder — wired up in Phase 2b).
*/
fork: AgentForkFn | null;
/**
* Optional cleanup hook invoked after the agent CLI completes. null means
* the adapter has no resources to release.
*/
cleanup: AgentCleanupFn | null;
};