feat(coding-agent): add session_directory extension event

Adds a session_directory extension event that fires before session manager
creation, allowing extensions to customize the session directory path based
on cwd, git branch, or other context.

- Extensions can return a custom sessionDir in the event handler
- CLI --session-dir flag takes precedence over extension-provided paths
- If multiple extensions return a sessionDir, the last one wins
- Enables implementing branch-based sessions as an extension instead of core feature

This provides the extension point needed to implement git branch-based
session directories without adding the complexity to core.

Closes #1729
This commit is contained in:
Helmut Januschka
2026-03-03 01:08:51 +01:00
committed by Mario Zechner
parent ade6a35e75
commit 7df89066d9
5 changed files with 129 additions and 11 deletions

View File

@@ -114,6 +114,8 @@ export type {
SessionBeforeTreeEvent,
SessionBeforeTreeResult,
SessionCompactEvent,
SessionDirectoryEvent,
SessionDirectoryResult,
SessionEvent,
SessionForkEvent,
SessionShutdownEvent,

View File

@@ -851,6 +851,40 @@ export class ExtensionRunner {
return { skillPaths, promptPaths, themePaths };
}
/** Emit session_directory event. Returns custom session directory from extensions (last one wins). */
async emitSessionDirectory(cwd: string, cliSessionDir: string | undefined): Promise<string | undefined> {
const ctx = this.createContext();
let customSessionDir: string | undefined;
for (const ext of this.extensions) {
const handlers = ext.handlers.get("session_directory");
if (!handlers || handlers.length === 0) continue;
for (const handler of handlers) {
try {
const event = { type: "session_directory" as const, cwd, cliSessionDir };
const handlerResult = await handler(event, ctx);
const result = handlerResult as { sessionDir?: string } | undefined;
if (result?.sessionDir) {
customSessionDir = result.sessionDir;
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const stack = err instanceof Error ? err.stack : undefined;
this.emitError({
extensionPath: ext.path,
event: "session_directory",
error: message,
stack,
});
}
}
}
return customSessionDir;
}
/** Emit input event. Transforms chain, "handled" short-circuits. */
async emitInput(text: string, images: ImageContent[] | undefined, source: InputSource): Promise<InputEventResult> {
const ctx = this.createContext();

View File

@@ -388,6 +388,14 @@ export interface ResourcesDiscoverResult {
// Session Events
// ============================================================================
/** Fired before session manager creation to allow custom session directory resolution */
export interface SessionDirectoryEvent {
type: "session_directory";
cwd: string;
/** CLI-provided session directory (if any) */
cliSessionDir: string | undefined;
}
/** Fired on initial session load */
export interface SessionStartEvent {
type: "session_start";
@@ -472,6 +480,7 @@ export interface SessionTreeEvent {
}
export type SessionEvent =
| SessionDirectoryEvent
| SessionStartEvent
| SessionBeforeSwitchEvent
| SessionSwitchEvent
@@ -866,6 +875,11 @@ export interface BeforeAgentStartEventResult {
systemPrompt?: string;
}
export interface SessionDirectoryResult {
/** Custom session directory path. If multiple extensions return this, the last one wins. */
sessionDir?: string;
}
export interface SessionBeforeSwitchResult {
cancel?: boolean;
}
@@ -936,6 +950,7 @@ export interface ExtensionAPI {
// =========================================================================
on(event: "resources_discover", handler: ExtensionHandler<ResourcesDiscoverEvent, ResourcesDiscoverResult>): void;
on(event: "session_directory", handler: ExtensionHandler<SessionDirectoryEvent, SessionDirectoryResult>): void;
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
on(
event: "session_before_switch",