Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
529 changes: 291 additions & 238 deletions packages/agent-sdk/src/managers/aiManager.ts

Large diffs are not rendered by default.

95 changes: 92 additions & 3 deletions packages/agent-sdk/src/managers/hookManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,16 @@ export class HookManager {
messageManager.addErrorBlock(errorMessage);
return { shouldBlock: false };

case "PreCompact":
// Non-blocking for compaction, show error in error block
messageManager.addErrorBlock(errorMessage);
return { shouldBlock: false };

case "PostCompact":
// Non-blocking for compaction, show error in error block
messageManager.addErrorBlock(errorMessage);
return { shouldBlock: false };

default:
return { shouldBlock: false };
}
Expand Down Expand Up @@ -606,7 +616,9 @@ export class HookManager {
event === "WorktreeCreate" ||
event === "WorktreeRemove" ||
event === "SessionStart" ||
event === "SessionEnd") &&
event === "SessionEnd" ||
event === "PreCompact" ||
event === "PostCompact") &&
context.toolName !== undefined
) {
logger?.warn(
Expand Down Expand Up @@ -689,7 +701,9 @@ export class HookManager {
event === "WorktreeRemove" ||
event === "CwdChanged" ||
event === "SessionStart" ||
event === "SessionEnd"
event === "SessionEnd" ||
event === "PreCompact" ||
event === "PostCompact"
) {
return true;
}
Expand Down Expand Up @@ -752,7 +766,9 @@ export class HookManager {
event === "WorktreeCreate" ||
event === "WorktreeRemove" ||
event === "SessionStart" ||
event === "SessionEnd") &&
event === "SessionEnd" ||
event === "PreCompact" ||
event === "PostCompact") &&
config.matcher
) {
errors.push(`${prefix}: Event ${event} should not have a matcher`);
Expand Down Expand Up @@ -796,6 +812,8 @@ export class HookManager {
CwdChanged: 0,
SessionStart: 0,
SessionEnd: 0,
PreCompact: 0,
PostCompact: 0,
},
};
}
Expand All @@ -812,6 +830,8 @@ export class HookManager {
CwdChanged: 0,
SessionStart: 0,
SessionEnd: 0,
PreCompact: 0,
PostCompact: 0,
};

let totalConfigs = 0;
Expand Down Expand Up @@ -977,4 +997,73 @@ export class HookManager {

return results;
}

/**
* Execute PreCompact hooks before compaction.
* Returns custom instructions from hook stdout.
*/
async executePreCompactHooks(
sessionId: string,
transcriptPath: string,
customInstructions?: string,
): Promise<{
results: HookExecutionResult[];
additionalInstructions?: string;
}> {
const context: ExtendedHookExecutionContext = {
event: "PreCompact",
projectDir: this.workdir,
timestamp: new Date(),
sessionId,
transcriptPath,
cwd: this.workdir,
compactInstructions: customInstructions,
env: Object.fromEntries(
Object.entries(process.env).filter((e) => e[1] !== undefined),
) as Record<string, string>,
};

const results = await this.executeHooks("PreCompact", context);

let additionalInstructions: string | undefined;
for (const result of results) {
if (result.success && result.stdout?.trim()) {
const trimmed = result.stdout.trim();
additionalInstructions = additionalInstructions
? additionalInstructions + "\n" + trimmed
: trimmed;
}
}

return { results, additionalInstructions };
}

/**
* Execute PostCompact hooks after compaction.
* Receives the compact summary text.
*/
async executePostCompactHooks(
sessionId: string,
transcriptPath: string,
compactSummary: string,
): Promise<HookExecutionResult[]> {
const context: ExtendedHookExecutionContext = {
event: "PostCompact",
projectDir: this.workdir,
timestamp: new Date(),
sessionId,
transcriptPath,
cwd: this.workdir,
compactSummary,
env:
this.container.get<Record<string, string>>("MergedEnv") ||
(process.env as Record<string, string>),
};

const results = await this.executeHooks("PostCompact", context);
if (results.length > 0) {
this.processHookResults("PostCompact", results);
}
return results;
}
}
17 changes: 17 additions & 0 deletions packages/agent-sdk/src/managers/slashCommandManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,23 @@ export class SlashCommandManager {
}
},
});

// Register built-in compact command
this.registerCommand({
id: "compact",
name: "compact",
description: "Compact conversation history to reduce context usage",
handler: async (args?: string, signal?: AbortSignal) => {
this.aiManager.abortAIMessage();

const customInstructions = args?.trim() || undefined;

await this.aiManager.compactConversation({
customInstructions,
abortSignal: signal,
});
},
});
}

/**
Expand Down
5 changes: 4 additions & 1 deletion packages/agent-sdk/src/services/aiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,7 @@ export interface CompactMessagesOptions {
messages: ChatCompletionMessageParam[];
abortSignal?: AbortSignal;
model?: string;
customInstructions?: string;
}

export interface CompactMessagesResult {
Expand Down Expand Up @@ -835,7 +836,9 @@ export async function compactMessages(
...cleanedMessages,
{
role: "user",
content: `Please create a detailed summary of the conversation so far.`,
content: options.customInstructions
? `Please create a detailed summary of the conversation so far. Pay special attention to these instructions: ${options.customInstructions}`
: `Please create a detailed summary of the conversation so far.`,
},
],
},
Expand Down
10 changes: 9 additions & 1 deletion packages/agent-sdk/src/types/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export type HookEvent =
| "WorktreeRemove"
| "CwdChanged"
| "SessionStart"
| "SessionEnd";
| "SessionEnd"
| "PreCompact"
| "PostCompact";

// Individual hook command configuration
export interface HookCommand {
Expand Down Expand Up @@ -117,6 +119,8 @@ export function isValidHookEvent(event: string): event is HookEvent {
"CwdChanged",
"SessionStart",
"SessionEnd",
"PreCompact",
"PostCompact",
].includes(event);
}

Expand Down Expand Up @@ -187,6 +191,8 @@ export interface HookJsonInput {
source?: SessionStartSource; // Present for SessionStart events
agent_type?: string; // Present for SessionStart events
end_source?: SessionEndSource; // Present for SessionEnd events
compact_instructions?: string; // Present for PreCompact events
compact_summary?: string; // Present for PostCompact events
}

// Extended context interface for passing additional data to hook executor
Expand All @@ -205,6 +211,8 @@ export interface ExtendedHookExecutionContext extends HookExecutionContext {
source?: SessionStartSource; // Session start source (SessionStart only)
agentType?: string; // Agent type identifier (SessionStart only)
endSource?: SessionEndSource; // Session end source (SessionEnd only)
compactInstructions?: string; // Custom instructions for PreCompact
compactSummary?: string; // Summary text for PostCompact
}

// Environment variables injected into hook processes
Expand Down
Loading