@jmylchreest/aide-plugin 0.0.56 → 0.0.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -2
- package/src/cli/codex-config.ts +428 -0
- package/src/cli/hook.ts +85 -0
- package/src/cli/index.ts +49 -12
- package/src/cli/install.ts +52 -25
- package/src/cli/status.ts +50 -17
- package/src/cli/uninstall.ts +29 -8
- package/src/core/mcp-sync.ts +208 -8
- package/src/core/types.ts +2 -2
- package/src/hooks/agent-cleanup.ts +91 -0
- package/src/hooks/comment-checker.ts +115 -0
- package/src/hooks/context-guard.ts +115 -0
- package/src/hooks/context-pruning.ts +216 -0
- package/src/hooks/hud-updater.ts +180 -0
- package/src/hooks/permission-handler.ts +173 -0
- package/src/hooks/persistence.ts +93 -0
- package/src/hooks/pre-compact.ts +127 -0
- package/src/hooks/pre-tool-enforcer.ts +120 -0
- package/src/hooks/session-end.ts +148 -0
- package/src/hooks/session-start.ts +488 -0
- package/src/hooks/session-summary.ts +147 -0
- package/src/hooks/skill-injector.ts +235 -0
- package/src/hooks/subagent-tracker.ts +525 -0
- package/src/hooks/task-completed.ts +445 -0
- package/src/hooks/tool-tracker.ts +89 -0
- package/src/hooks/write-guard.ts +95 -0
- package/src/lib/hook-utils.ts +53 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Agent Cleanup Hook (Stop)
|
|
4
|
+
*
|
|
5
|
+
* Cleans up agent-specific state when an agent stops.
|
|
6
|
+
* This prevents stale state from polluting future agents with the same ID.
|
|
7
|
+
*
|
|
8
|
+
* Runs after persistence hook to clean up when agent is allowed to stop.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, appendFileSync } from "fs";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import { readStdin } from "../lib/hook-utils.js";
|
|
14
|
+
import { findAideBinary } from "../core/aide-client.js";
|
|
15
|
+
import { cleanupAgent } from "../core/cleanup.js";
|
|
16
|
+
import { debug } from "../lib/logger.js";
|
|
17
|
+
|
|
18
|
+
const SOURCE = "agent-cleanup";
|
|
19
|
+
|
|
20
|
+
interface HookInput {
|
|
21
|
+
hook_event_name: string;
|
|
22
|
+
session_id: string;
|
|
23
|
+
cwd: string;
|
|
24
|
+
agent_id?: string;
|
|
25
|
+
transcript_path?: string;
|
|
26
|
+
permission_mode?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function main(): Promise<void> {
|
|
30
|
+
try {
|
|
31
|
+
const input = await readStdin();
|
|
32
|
+
if (!input.trim()) {
|
|
33
|
+
console.log(JSON.stringify({ continue: true }));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const data: HookInput = JSON.parse(input);
|
|
38
|
+
const cwd = data.cwd || process.cwd();
|
|
39
|
+
const agentId = data.agent_id || data.session_id;
|
|
40
|
+
|
|
41
|
+
// Clean up agent-specific state — delegates to core
|
|
42
|
+
if (agentId) {
|
|
43
|
+
const binary = findAideBinary({
|
|
44
|
+
cwd,
|
|
45
|
+
pluginRoot:
|
|
46
|
+
process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
|
|
47
|
+
});
|
|
48
|
+
if (binary) {
|
|
49
|
+
const cleared = cleanupAgent(binary, cwd, agentId);
|
|
50
|
+
if (cleared) {
|
|
51
|
+
const logDir = join(cwd, ".aide", "_logs");
|
|
52
|
+
if (existsSync(logDir)) {
|
|
53
|
+
const logPath = join(logDir, "agent-cleanup.log");
|
|
54
|
+
const timestamp = new Date().toISOString();
|
|
55
|
+
appendFileSync(
|
|
56
|
+
logPath,
|
|
57
|
+
`${timestamp} Cleaned up state for agent: ${agentId}\n`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Always continue - cleanup is best-effort
|
|
65
|
+
console.log(JSON.stringify({ continue: true }));
|
|
66
|
+
} catch (error) {
|
|
67
|
+
debug(SOURCE, `Hook error: ${error}`);
|
|
68
|
+
console.log(JSON.stringify({ continue: true }));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
process.on("uncaughtException", (err) => {
|
|
73
|
+
debug(SOURCE, `UNCAUGHT EXCEPTION: ${err}`);
|
|
74
|
+
try {
|
|
75
|
+
console.log(JSON.stringify({ continue: true }));
|
|
76
|
+
} catch {
|
|
77
|
+
console.log('{"continue":true}');
|
|
78
|
+
}
|
|
79
|
+
process.exit(0);
|
|
80
|
+
});
|
|
81
|
+
process.on("unhandledRejection", (reason) => {
|
|
82
|
+
debug(SOURCE, `UNHANDLED REJECTION: ${reason}`);
|
|
83
|
+
try {
|
|
84
|
+
console.log(JSON.stringify({ continue: true }));
|
|
85
|
+
} catch {
|
|
86
|
+
console.log('{"continue":true}');
|
|
87
|
+
}
|
|
88
|
+
process.exit(0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
main();
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Comment Checker Hook (PostToolUse)
|
|
4
|
+
*
|
|
5
|
+
* Detects excessive or obvious comments in code written by AI agents.
|
|
6
|
+
* Injects a warning via additionalContext to nudge the agent toward
|
|
7
|
+
* cleaner code without blocking the tool call.
|
|
8
|
+
*
|
|
9
|
+
* Core logic is in src/core/comment-checker.ts for cross-platform reuse.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readStdin } from "../lib/hook-utils.js";
|
|
13
|
+
import { debug } from "../lib/logger.js";
|
|
14
|
+
import {
|
|
15
|
+
checkComments,
|
|
16
|
+
getCheckableFilePath,
|
|
17
|
+
getContentToCheck,
|
|
18
|
+
} from "../core/comment-checker.js";
|
|
19
|
+
|
|
20
|
+
const SOURCE = "comment-checker";
|
|
21
|
+
|
|
22
|
+
interface HookInput {
|
|
23
|
+
hook_event_name: string;
|
|
24
|
+
session_id: string;
|
|
25
|
+
cwd: string;
|
|
26
|
+
tool_name?: string;
|
|
27
|
+
agent_id?: string;
|
|
28
|
+
tool_input?: Record<string, unknown>;
|
|
29
|
+
tool_result?: {
|
|
30
|
+
success: boolean;
|
|
31
|
+
duration?: number;
|
|
32
|
+
};
|
|
33
|
+
transcript_path?: string;
|
|
34
|
+
permission_mode?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface HookOutput {
|
|
38
|
+
continue: true;
|
|
39
|
+
hookSpecificOutput?: {
|
|
40
|
+
hookEventName: string;
|
|
41
|
+
additionalContext?: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main(): Promise<void> {
|
|
46
|
+
try {
|
|
47
|
+
const input = await readStdin();
|
|
48
|
+
if (!input.trim()) {
|
|
49
|
+
console.log(JSON.stringify({ continue: true }));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const data: HookInput = JSON.parse(input);
|
|
54
|
+
const toolName = data.tool_name || "";
|
|
55
|
+
const toolInput = data.tool_input || {};
|
|
56
|
+
|
|
57
|
+
// Only check Write/Edit/MultiEdit tool calls
|
|
58
|
+
const filePath = getCheckableFilePath(toolName, toolInput);
|
|
59
|
+
if (!filePath) {
|
|
60
|
+
console.log(JSON.stringify({ continue: true }));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Get the content to analyze
|
|
65
|
+
const contentResult = getContentToCheck(toolName, toolInput);
|
|
66
|
+
if (!contentResult) {
|
|
67
|
+
console.log(JSON.stringify({ continue: true }));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const [content, isNewContent] = contentResult;
|
|
72
|
+
const result = checkComments(filePath, content, isNewContent);
|
|
73
|
+
|
|
74
|
+
if (result.hasExcessiveComments) {
|
|
75
|
+
debug(
|
|
76
|
+
SOURCE,
|
|
77
|
+
`Detected ${result.suspiciousCount} suspicious comments in ${filePath}`,
|
|
78
|
+
);
|
|
79
|
+
const output: HookOutput = {
|
|
80
|
+
continue: true,
|
|
81
|
+
hookSpecificOutput: {
|
|
82
|
+
hookEventName: "PostToolUse",
|
|
83
|
+
additionalContext: result.warning,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
console.log(JSON.stringify(output));
|
|
87
|
+
} else {
|
|
88
|
+
console.log(JSON.stringify({ continue: true }));
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
debug(SOURCE, `Hook error: ${error}`);
|
|
92
|
+
console.log(JSON.stringify({ continue: true }));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
process.on("uncaughtException", (err) => {
|
|
97
|
+
debug(SOURCE, `UNCAUGHT EXCEPTION: ${err}`);
|
|
98
|
+
try {
|
|
99
|
+
console.log(JSON.stringify({ continue: true }));
|
|
100
|
+
} catch {
|
|
101
|
+
console.log('{"continue":true}');
|
|
102
|
+
}
|
|
103
|
+
process.exit(0);
|
|
104
|
+
});
|
|
105
|
+
process.on("unhandledRejection", (reason) => {
|
|
106
|
+
debug(SOURCE, `UNHANDLED REJECTION: ${reason}`);
|
|
107
|
+
try {
|
|
108
|
+
console.log(JSON.stringify({ continue: true }));
|
|
109
|
+
} catch {
|
|
110
|
+
console.log('{"continue":true}');
|
|
111
|
+
}
|
|
112
|
+
process.exit(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
main();
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Context Guard Hook (PreToolUse)
|
|
4
|
+
*
|
|
5
|
+
* Monitors Read tool calls and advises agents to use code_outline
|
|
6
|
+
* before reading large files. Also tracks code_outline/code_symbols
|
|
7
|
+
* calls so it knows which files have been outlined.
|
|
8
|
+
*
|
|
9
|
+
* This is a soft warning — it never blocks, only injects advisory context.
|
|
10
|
+
*
|
|
11
|
+
* Core logic is in src/core/context-guard.ts for cross-platform reuse.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readStdin } from "../lib/hook-utils.js";
|
|
15
|
+
import { debug } from "../lib/logger.js";
|
|
16
|
+
import { checkContextGuard, checkSmartReadHint } from "../core/context-guard.js";
|
|
17
|
+
import { findAideBinary } from "../core/aide-client.js";
|
|
18
|
+
|
|
19
|
+
const SOURCE = "context-guard";
|
|
20
|
+
|
|
21
|
+
interface HookInput {
|
|
22
|
+
hook_event_name: string;
|
|
23
|
+
session_id: string;
|
|
24
|
+
cwd: string;
|
|
25
|
+
tool_name?: string;
|
|
26
|
+
agent_name?: string;
|
|
27
|
+
agent_id?: string;
|
|
28
|
+
tool_input?: Record<string, unknown>;
|
|
29
|
+
transcript_path?: string;
|
|
30
|
+
permission_mode?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface HookOutput {
|
|
34
|
+
continue: boolean;
|
|
35
|
+
message?: string;
|
|
36
|
+
hookSpecificOutput?: {
|
|
37
|
+
hookEventName: string;
|
|
38
|
+
additionalContext?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function main(): Promise<void> {
|
|
43
|
+
try {
|
|
44
|
+
const input = await readStdin();
|
|
45
|
+
if (!input.trim()) {
|
|
46
|
+
console.log(JSON.stringify({ continue: true }));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const data: HookInput = JSON.parse(input);
|
|
51
|
+
const toolName = data.tool_name || "";
|
|
52
|
+
const toolInput = data.tool_input || {};
|
|
53
|
+
const cwd = data.cwd || process.cwd();
|
|
54
|
+
const sessionId = data.session_id || "unknown";
|
|
55
|
+
|
|
56
|
+
const result = checkContextGuard(toolName, toolInput, cwd, sessionId);
|
|
57
|
+
|
|
58
|
+
if (result.shouldAdvise && result.advisory) {
|
|
59
|
+
debug(SOURCE, `Advising on large file read`);
|
|
60
|
+
const output: HookOutput = {
|
|
61
|
+
continue: true,
|
|
62
|
+
hookSpecificOutput: {
|
|
63
|
+
hookEventName: "PreToolUse",
|
|
64
|
+
additionalContext: result.advisory,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
console.log(JSON.stringify(output));
|
|
68
|
+
} else {
|
|
69
|
+
// Smart read hint: suggest code index for re-reads of unchanged files
|
|
70
|
+
const binary = findAideBinary({
|
|
71
|
+
cwd,
|
|
72
|
+
pluginRoot:
|
|
73
|
+
process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
|
|
74
|
+
});
|
|
75
|
+
const hintResult = checkSmartReadHint(toolName, toolInput, cwd, binary);
|
|
76
|
+
if (hintResult.shouldHint && hintResult.hint) {
|
|
77
|
+
debug(SOURCE, `Smart read hint triggered`);
|
|
78
|
+
const output: HookOutput = {
|
|
79
|
+
continue: true,
|
|
80
|
+
hookSpecificOutput: {
|
|
81
|
+
hookEventName: "PreToolUse",
|
|
82
|
+
additionalContext: hintResult.hint,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
console.log(JSON.stringify(output));
|
|
86
|
+
} else {
|
|
87
|
+
console.log(JSON.stringify({ continue: true }));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
debug(SOURCE, `Hook error: ${error}`);
|
|
92
|
+
console.log(JSON.stringify({ continue: true }));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
process.on("uncaughtException", (err) => {
|
|
97
|
+
debug(SOURCE, `UNCAUGHT EXCEPTION: ${err}`);
|
|
98
|
+
try {
|
|
99
|
+
console.log(JSON.stringify({ continue: true }));
|
|
100
|
+
} catch {
|
|
101
|
+
console.log('{"continue":true}');
|
|
102
|
+
}
|
|
103
|
+
process.exit(0);
|
|
104
|
+
});
|
|
105
|
+
process.on("unhandledRejection", (reason) => {
|
|
106
|
+
debug(SOURCE, `UNHANDLED REJECTION: ${reason}`);
|
|
107
|
+
try {
|
|
108
|
+
console.log(JSON.stringify({ continue: true }));
|
|
109
|
+
} catch {
|
|
110
|
+
console.log('{"continue":true}');
|
|
111
|
+
}
|
|
112
|
+
process.exit(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
main();
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Context Pruning Hook (PostToolUse)
|
|
4
|
+
*
|
|
5
|
+
* Reduces context usage by deduplicating repeated tool outputs,
|
|
6
|
+
* annotating superseded reads, and purging large error outputs.
|
|
7
|
+
*
|
|
8
|
+
* For MCP tools: uses `updatedMCPToolOutput` to replace the output.
|
|
9
|
+
* For built-in tools: uses `additionalContext` to add dedup notes.
|
|
10
|
+
*
|
|
11
|
+
* Tracker state is persisted to a temp file per session so it survives
|
|
12
|
+
* across separate hook process invocations.
|
|
13
|
+
*
|
|
14
|
+
* Core logic is in src/core/context-pruning/ for cross-platform reuse.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readStdin } from "../lib/hook-utils.js";
|
|
18
|
+
import { debug } from "../lib/logger.js";
|
|
19
|
+
import { ContextPruningTracker } from "../core/context-pruning/index.js";
|
|
20
|
+
import type { ToolRecord } from "../core/context-pruning/types.js";
|
|
21
|
+
import { tmpdir } from "os";
|
|
22
|
+
import { join } from "path";
|
|
23
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
24
|
+
|
|
25
|
+
const SOURCE = "context-pruning";
|
|
26
|
+
|
|
27
|
+
interface HookInput {
|
|
28
|
+
hook_event_name: string;
|
|
29
|
+
session_id: string;
|
|
30
|
+
cwd: string;
|
|
31
|
+
tool_name?: string;
|
|
32
|
+
tool_input?: Record<string, unknown>;
|
|
33
|
+
tool_output?: string;
|
|
34
|
+
mcp_server_name?: string;
|
|
35
|
+
transcript_path?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface HookOutput {
|
|
39
|
+
continue: boolean;
|
|
40
|
+
hookSpecificOutput?: {
|
|
41
|
+
hookEventName: string;
|
|
42
|
+
additionalContext?: string;
|
|
43
|
+
updatedMCPToolOutput?: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Path to the persisted tracker history for a session. */
|
|
48
|
+
function historyPath(sessionId: string): string {
|
|
49
|
+
return join(tmpdir(), `aide-context-pruning-${sessionId}.json`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Load tracker history from disk. */
|
|
53
|
+
function loadHistory(sessionId: string): {
|
|
54
|
+
history: ToolRecord[];
|
|
55
|
+
hasExplainedPruning: boolean;
|
|
56
|
+
} {
|
|
57
|
+
const path = historyPath(sessionId);
|
|
58
|
+
try {
|
|
59
|
+
if (existsSync(path)) {
|
|
60
|
+
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
61
|
+
return {
|
|
62
|
+
history: data.history || [],
|
|
63
|
+
hasExplainedPruning: data.hasExplainedPruning || false,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Corrupt file — start fresh
|
|
68
|
+
}
|
|
69
|
+
return { history: [], hasExplainedPruning: false };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Save tracker history to disk. */
|
|
73
|
+
function saveHistory(
|
|
74
|
+
sessionId: string,
|
|
75
|
+
history: ToolRecord[],
|
|
76
|
+
hasExplainedPruning: boolean,
|
|
77
|
+
): void {
|
|
78
|
+
const path = historyPath(sessionId);
|
|
79
|
+
try {
|
|
80
|
+
// Keep only last 200 entries to prevent unbounded growth
|
|
81
|
+
const trimmed = history.length > 200 ? history.slice(-200) : history;
|
|
82
|
+
writeFileSync(
|
|
83
|
+
path,
|
|
84
|
+
JSON.stringify({ history: trimmed, hasExplainedPruning }),
|
|
85
|
+
"utf-8",
|
|
86
|
+
);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
debug(SOURCE, `Failed to save history: ${err}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Check if a tool is an MCP tool (aide or other MCP server). */
|
|
93
|
+
function isMCPTool(toolName: string, mcpServerName?: string): boolean {
|
|
94
|
+
if (mcpServerName) return true;
|
|
95
|
+
// Convention: MCP tools often have mcp__ prefix or are aide tools
|
|
96
|
+
if (toolName.startsWith("mcp__")) return true;
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function main(): Promise<void> {
|
|
101
|
+
try {
|
|
102
|
+
const input = await readStdin();
|
|
103
|
+
if (!input.trim()) {
|
|
104
|
+
console.log(JSON.stringify({ continue: true }));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const data: HookInput = JSON.parse(input);
|
|
109
|
+
const toolName = data.tool_name || "";
|
|
110
|
+
const toolInput = data.tool_input || {};
|
|
111
|
+
const toolOutput = data.tool_output || "";
|
|
112
|
+
const cwd = data.cwd || process.cwd();
|
|
113
|
+
const sessionId = data.session_id || "unknown";
|
|
114
|
+
|
|
115
|
+
// Skip if no tool output to prune
|
|
116
|
+
if (!toolOutput || toolOutput.length < 50) {
|
|
117
|
+
console.log(JSON.stringify({ continue: true }));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Create tracker with loaded history
|
|
122
|
+
const tracker = new ContextPruningTracker(cwd);
|
|
123
|
+
const { history: priorHistory, hasExplainedPruning } =
|
|
124
|
+
loadHistory(sessionId);
|
|
125
|
+
tracker.loadHistory(priorHistory);
|
|
126
|
+
|
|
127
|
+
// Use a synthetic callId since CC doesn't provide one
|
|
128
|
+
const callId = `cc-${sessionId}-${Date.now()}`;
|
|
129
|
+
|
|
130
|
+
// Process through pruning strategies
|
|
131
|
+
const result = tracker.process(callId, toolName, toolInput, toolOutput);
|
|
132
|
+
|
|
133
|
+
// Track whether we've explained pruning tags to the model
|
|
134
|
+
let explained = hasExplainedPruning;
|
|
135
|
+
|
|
136
|
+
// Save updated history
|
|
137
|
+
saveHistory(sessionId, tracker.getHistory(), explained);
|
|
138
|
+
|
|
139
|
+
if (result.modified) {
|
|
140
|
+
debug(
|
|
141
|
+
SOURCE,
|
|
142
|
+
`Pruned [${result.strategy}]: saved ${result.bytesSaved} bytes for ${toolName}`,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const output: HookOutput = {
|
|
146
|
+
continue: true,
|
|
147
|
+
hookSpecificOutput: {
|
|
148
|
+
hookEventName: "PostToolUse",
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (isMCPTool(toolName, data.mcp_server_name)) {
|
|
153
|
+
// For MCP tools, replace the output entirely
|
|
154
|
+
output.hookSpecificOutput!.updatedMCPToolOutput = result.output;
|
|
155
|
+
} else {
|
|
156
|
+
// For built-in tools, add context note about the dedup
|
|
157
|
+
output.hookSpecificOutput!.additionalContext = result.output.includes(
|
|
158
|
+
"[aide:dedup]",
|
|
159
|
+
)
|
|
160
|
+
? `Note: This tool output is identical to a previous call. The full content was already provided earlier.`
|
|
161
|
+
: result.output.includes("[aide:purge]")
|
|
162
|
+
? `Note: Error output was truncated. Re-run the command to see full output.`
|
|
163
|
+
: undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// On first prune, inject explanation of pruning tags via additionalContext
|
|
167
|
+
if (!explained) {
|
|
168
|
+
const pruningNotes = [
|
|
169
|
+
"<aide-context-pruning>",
|
|
170
|
+
"Tool outputs may contain these tags from aide's context optimization:",
|
|
171
|
+
"- [aide:dedup] — This output is identical to a previous call. Refer to the earlier result.",
|
|
172
|
+
"- [aide:supersede] — A prior Read of this file is now stale after a Write/Edit.",
|
|
173
|
+
"- [aide:purge] — Large error output was trimmed. Re-run the command for full output.",
|
|
174
|
+
"</aide-context-pruning>",
|
|
175
|
+
].join("\n");
|
|
176
|
+
|
|
177
|
+
const existing = output.hookSpecificOutput!.additionalContext || "";
|
|
178
|
+
output.hookSpecificOutput!.additionalContext = existing
|
|
179
|
+
? `${existing}\n\n${pruningNotes}`
|
|
180
|
+
: pruningNotes;
|
|
181
|
+
|
|
182
|
+
explained = true;
|
|
183
|
+
// Persist the flag
|
|
184
|
+
saveHistory(sessionId, tracker.getHistory(), explained);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log(JSON.stringify(output));
|
|
188
|
+
} else {
|
|
189
|
+
console.log(JSON.stringify({ continue: true }));
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
debug(SOURCE, `Hook error: ${error}`);
|
|
193
|
+
console.log(JSON.stringify({ continue: true }));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
process.on("uncaughtException", (err) => {
|
|
198
|
+
debug(SOURCE, `UNCAUGHT EXCEPTION: ${err}`);
|
|
199
|
+
try {
|
|
200
|
+
console.log(JSON.stringify({ continue: true }));
|
|
201
|
+
} catch {
|
|
202
|
+
console.log('{"continue":true}');
|
|
203
|
+
}
|
|
204
|
+
process.exit(0);
|
|
205
|
+
});
|
|
206
|
+
process.on("unhandledRejection", (reason) => {
|
|
207
|
+
debug(SOURCE, `UNHANDLED REJECTION: ${reason}`);
|
|
208
|
+
try {
|
|
209
|
+
console.log(JSON.stringify({ continue: true }));
|
|
210
|
+
} catch {
|
|
211
|
+
console.log('{"continue":true}');
|
|
212
|
+
}
|
|
213
|
+
process.exit(0);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
main();
|