@cleocode/core 2026.3.71 → 2026.3.73
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/dist/cleo.d.ts.map +1 -1
- package/dist/hooks/handlers/agent-hooks.d.ts +48 -0
- package/dist/hooks/handlers/agent-hooks.d.ts.map +1 -0
- package/dist/hooks/handlers/context-hooks.d.ts +53 -0
- package/dist/hooks/handlers/context-hooks.d.ts.map +1 -0
- package/dist/hooks/handlers/error-hooks.d.ts +4 -4
- package/dist/hooks/handlers/error-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/file-hooks.d.ts +3 -3
- package/dist/hooks/handlers/file-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/index.d.ts +8 -1
- package/dist/hooks/handlers/index.d.ts.map +1 -1
- package/dist/hooks/handlers/mcp-hooks.d.ts +29 -7
- package/dist/hooks/handlers/mcp-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/session-hooks.d.ts +5 -5
- package/dist/hooks/handlers/session-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/task-hooks.d.ts +5 -5
- package/dist/hooks/handlers/task-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/work-capture-hooks.d.ts +7 -7
- package/dist/hooks/handlers/work-capture-hooks.d.ts.map +1 -1
- package/dist/hooks/payload-schemas.d.ts +177 -11
- package/dist/hooks/payload-schemas.d.ts.map +1 -1
- package/dist/hooks/provider-hooks.d.ts +33 -7
- package/dist/hooks/provider-hooks.d.ts.map +1 -1
- package/dist/hooks/registry.d.ts +26 -6
- package/dist/hooks/registry.d.ts.map +1 -1
- package/dist/hooks/types.d.ts +132 -38
- package/dist/hooks/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +497 -59
- package/dist/index.js.map +4 -4
- package/dist/sessions/snapshot.d.ts +125 -0
- package/dist/sessions/snapshot.d.ts.map +1 -0
- package/package.json +6 -6
- package/src/cleo.ts +12 -0
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.ts +634 -0
- package/src/hooks/handlers/agent-hooks.ts +148 -0
- package/src/hooks/handlers/context-hooks.ts +156 -0
- package/src/hooks/handlers/error-hooks.ts +8 -5
- package/src/hooks/handlers/file-hooks.ts +6 -4
- package/src/hooks/handlers/index.ts +12 -1
- package/src/hooks/handlers/mcp-hooks.ts +74 -9
- package/src/hooks/handlers/session-hooks.ts +7 -7
- package/src/hooks/handlers/task-hooks.ts +7 -7
- package/src/hooks/handlers/work-capture-hooks.ts +12 -12
- package/src/hooks/payload-schemas.ts +96 -26
- package/src/hooks/provider-hooks.ts +50 -9
- package/src/hooks/registry.ts +86 -23
- package/src/hooks/types.ts +175 -39
- package/src/index.ts +10 -0
- package/src/sessions/index.ts +4 -4
- package/src/sessions/snapshot.ts +343 -0
- package/src/store/json.ts +2 -2
- package/src/task-work/index.ts +4 -4
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { hooks } from '../registry.js';
|
|
20
|
-
import type {
|
|
20
|
+
import type { PromptSubmitPayload, ResponseCompletePayload } from '../types.js';
|
|
21
21
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
23
23
|
// Shared helpers
|
|
@@ -60,9 +60,9 @@ async function isWorkCaptureEnabled(projectRoot: string): Promise<boolean> {
|
|
|
60
60
|
* Mutations that represent novel work not already captured by lifecycle hooks.
|
|
61
61
|
*
|
|
62
62
|
* Excluded (already captured elsewhere):
|
|
63
|
-
* - tasks.complete → task-hooks.ts (
|
|
64
|
-
* - session.start → session-hooks.ts (
|
|
65
|
-
* - session.end → session-hooks.ts (
|
|
63
|
+
* - tasks.complete → task-hooks.ts (PostToolUse)
|
|
64
|
+
* - session.start → session-hooks.ts (SessionStart)
|
|
65
|
+
* - session.end → session-hooks.ts (SessionEnd)
|
|
66
66
|
* - memory.brain.observe → observeBrain itself writes to brain; self-loop
|
|
67
67
|
*
|
|
68
68
|
* All query gateway operations are excluded by the gateway check before
|
|
@@ -100,17 +100,17 @@ function shouldCapture(gateway: string, domain: string, operation: string): bool
|
|
|
100
100
|
// ---------------------------------------------------------------------------
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
|
-
* Handle
|
|
103
|
+
* Handle PromptSubmit — log incoming mutation intents to BRAIN.
|
|
104
104
|
*
|
|
105
105
|
* Only fires for mutate operations in CAPTURE_OPERATIONS.
|
|
106
106
|
* Gated behind brain.captureWork config (or CLEO_BRAIN_CAPTURE_WORK env).
|
|
107
107
|
*
|
|
108
108
|
* @param projectRoot - Absolute path to the project root
|
|
109
|
-
* @param payload -
|
|
109
|
+
* @param payload - PromptSubmit event payload
|
|
110
110
|
*/
|
|
111
111
|
export async function handleWorkPromptSubmit(
|
|
112
112
|
projectRoot: string,
|
|
113
|
-
payload:
|
|
113
|
+
payload: PromptSubmitPayload,
|
|
114
114
|
): Promise<void> {
|
|
115
115
|
if (!shouldCapture(payload.gateway, payload.domain, payload.operation)) return;
|
|
116
116
|
if (!(await isWorkCaptureEnabled(projectRoot))) return;
|
|
@@ -130,18 +130,18 @@ export async function handleWorkPromptSubmit(
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
|
-
* Handle
|
|
133
|
+
* Handle ResponseComplete — capture completed mutations to BRAIN.
|
|
134
134
|
*
|
|
135
135
|
* Only fires for successful mutate operations in CAPTURE_OPERATIONS.
|
|
136
136
|
* Failures are skipped — the intent was already captured by handleWorkPromptSubmit.
|
|
137
137
|
* Gated behind brain.captureWork config (or CLEO_BRAIN_CAPTURE_WORK env).
|
|
138
138
|
*
|
|
139
139
|
* @param projectRoot - Absolute path to the project root
|
|
140
|
-
* @param payload -
|
|
140
|
+
* @param payload - ResponseComplete event payload
|
|
141
141
|
*/
|
|
142
142
|
export async function handleWorkResponseComplete(
|
|
143
143
|
projectRoot: string,
|
|
144
|
-
payload:
|
|
144
|
+
payload: ResponseCompletePayload,
|
|
145
145
|
): Promise<void> {
|
|
146
146
|
if (!shouldCapture(payload.gateway, payload.domain, payload.operation)) return;
|
|
147
147
|
// Only capture successful completions — failures are noise
|
|
@@ -171,14 +171,14 @@ export async function handleWorkResponseComplete(
|
|
|
171
171
|
// handlers always run first.
|
|
172
172
|
hooks.register({
|
|
173
173
|
id: 'work-capture-prompt-submit',
|
|
174
|
-
event: '
|
|
174
|
+
event: 'PromptSubmit',
|
|
175
175
|
handler: handleWorkPromptSubmit,
|
|
176
176
|
priority: 90,
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
hooks.register({
|
|
180
180
|
id: 'work-capture-response-complete',
|
|
181
|
-
event: '
|
|
181
|
+
event: 'ResponseComplete',
|
|
182
182
|
handler: handleWorkResponseComplete,
|
|
183
183
|
priority: 90,
|
|
184
184
|
});
|
|
@@ -28,44 +28,63 @@ export const HookPayloadSchema = z.object({
|
|
|
28
28
|
// CAAMP-mapped Event Payload Schemas
|
|
29
29
|
// ============================================================================
|
|
30
30
|
|
|
31
|
-
/** Zod schema for {@link
|
|
32
|
-
export const
|
|
31
|
+
/** Zod schema for {@link SessionStartPayload}. */
|
|
32
|
+
export const SessionStartPayloadSchema = HookPayloadSchema.extend({
|
|
33
33
|
sessionId: z.string(),
|
|
34
34
|
name: z.string(),
|
|
35
35
|
scope: z.string(),
|
|
36
36
|
agent: z.string().optional(),
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
export const
|
|
39
|
+
/** @deprecated Use {@link SessionStartPayloadSchema}. Kept for backward compatibility. */
|
|
40
|
+
export const OnSessionStartPayloadSchema = SessionStartPayloadSchema;
|
|
41
|
+
|
|
42
|
+
/** Zod schema for {@link SessionEndPayload}. */
|
|
43
|
+
export const SessionEndPayloadSchema = HookPayloadSchema.extend({
|
|
41
44
|
sessionId: z.string(),
|
|
42
45
|
duration: z.number(),
|
|
43
46
|
tasksCompleted: z.array(z.string()),
|
|
44
47
|
});
|
|
45
48
|
|
|
46
|
-
/**
|
|
47
|
-
export const
|
|
49
|
+
/** @deprecated Use {@link SessionEndPayloadSchema}. Kept for backward compatibility. */
|
|
50
|
+
export const OnSessionEndPayloadSchema = SessionEndPayloadSchema;
|
|
51
|
+
|
|
52
|
+
/** Zod schema for {@link PreToolUsePayload}. */
|
|
53
|
+
export const PreToolUsePayloadSchema = HookPayloadSchema.extend({
|
|
48
54
|
taskId: z.string(),
|
|
49
55
|
taskTitle: z.string(),
|
|
50
56
|
previousTask: z.string().optional(),
|
|
57
|
+
toolName: z.string().optional(),
|
|
58
|
+
toolInput: z.record(z.string(), z.unknown()).optional(),
|
|
51
59
|
});
|
|
52
60
|
|
|
53
|
-
/**
|
|
54
|
-
export const
|
|
61
|
+
/** @deprecated Use {@link PreToolUsePayloadSchema}. Kept for backward compatibility. */
|
|
62
|
+
export const OnToolStartPayloadSchema = PreToolUsePayloadSchema;
|
|
63
|
+
|
|
64
|
+
/** Zod schema for {@link PostToolUsePayload}. */
|
|
65
|
+
export const PostToolUsePayloadSchema = HookPayloadSchema.extend({
|
|
55
66
|
taskId: z.string(),
|
|
56
67
|
taskTitle: z.string(),
|
|
57
68
|
status: z.enum(['done', 'archived', 'cancelled']),
|
|
69
|
+
toolResult: z.record(z.string(), z.unknown()).optional(),
|
|
58
70
|
});
|
|
59
71
|
|
|
60
|
-
/**
|
|
61
|
-
export const
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
/** @deprecated Use {@link PostToolUsePayloadSchema}. Kept for backward compatibility. */
|
|
73
|
+
export const OnToolCompletePayloadSchema = PostToolUsePayloadSchema;
|
|
74
|
+
|
|
75
|
+
/** Zod schema for {@link NotificationPayload}. */
|
|
76
|
+
export const NotificationPayloadSchema = HookPayloadSchema.extend({
|
|
77
|
+
filePath: z.string().optional(),
|
|
78
|
+
changeType: z.enum(['write', 'create', 'delete']).optional(),
|
|
64
79
|
sizeBytes: z.number().optional(),
|
|
80
|
+
message: z.string().optional(),
|
|
65
81
|
});
|
|
66
82
|
|
|
67
|
-
/**
|
|
68
|
-
export const
|
|
83
|
+
/** @deprecated Use {@link NotificationPayloadSchema}. Kept for backward compatibility. */
|
|
84
|
+
export const OnFileChangePayloadSchema = NotificationPayloadSchema;
|
|
85
|
+
|
|
86
|
+
/** Zod schema for {@link PostToolUseFailurePayload}. */
|
|
87
|
+
export const PostToolUseFailurePayloadSchema = HookPayloadSchema.extend({
|
|
69
88
|
errorCode: z.union([z.number(), z.string()]),
|
|
70
89
|
message: z.string(),
|
|
71
90
|
domain: z.string().optional(),
|
|
@@ -74,16 +93,22 @@ export const OnErrorPayloadSchema = HookPayloadSchema.extend({
|
|
|
74
93
|
stack: z.string().optional(),
|
|
75
94
|
});
|
|
76
95
|
|
|
77
|
-
/**
|
|
78
|
-
export const
|
|
96
|
+
/** @deprecated Use {@link PostToolUseFailurePayloadSchema}. Kept for backward compatibility. */
|
|
97
|
+
export const OnErrorPayloadSchema = PostToolUseFailurePayloadSchema;
|
|
98
|
+
|
|
99
|
+
/** Zod schema for {@link PromptSubmitPayload}. */
|
|
100
|
+
export const PromptSubmitPayloadSchema = HookPayloadSchema.extend({
|
|
79
101
|
gateway: z.string(),
|
|
80
102
|
domain: z.string(),
|
|
81
103
|
operation: z.string(),
|
|
82
104
|
source: z.string().optional(),
|
|
83
105
|
});
|
|
84
106
|
|
|
85
|
-
/**
|
|
86
|
-
export const
|
|
107
|
+
/** @deprecated Use {@link PromptSubmitPayloadSchema}. Kept for backward compatibility. */
|
|
108
|
+
export const OnPromptSubmitPayloadSchema = PromptSubmitPayloadSchema;
|
|
109
|
+
|
|
110
|
+
/** Zod schema for {@link ResponseCompletePayload}. */
|
|
111
|
+
export const ResponseCompletePayloadSchema = HookPayloadSchema.extend({
|
|
87
112
|
gateway: z.string(),
|
|
88
113
|
domain: z.string(),
|
|
89
114
|
operation: z.string(),
|
|
@@ -92,6 +117,44 @@ export const OnResponseCompletePayloadSchema = HookPayloadSchema.extend({
|
|
|
92
117
|
errorCode: z.string().optional(),
|
|
93
118
|
});
|
|
94
119
|
|
|
120
|
+
/** @deprecated Use {@link ResponseCompletePayloadSchema}. Kept for backward compatibility. */
|
|
121
|
+
export const OnResponseCompletePayloadSchema = ResponseCompletePayloadSchema;
|
|
122
|
+
|
|
123
|
+
/** Zod schema for {@link SubagentStartPayload}. */
|
|
124
|
+
export const SubagentStartPayloadSchema = HookPayloadSchema.extend({
|
|
125
|
+
agentId: z.string(),
|
|
126
|
+
role: z.string().optional(),
|
|
127
|
+
taskId: z.string().optional(),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/** Zod schema for {@link SubagentStopPayload}. */
|
|
131
|
+
export const SubagentStopPayloadSchema = HookPayloadSchema.extend({
|
|
132
|
+
agentId: z.string(),
|
|
133
|
+
status: z.enum(['complete', 'partial', 'blocked', 'failed']).optional(),
|
|
134
|
+
taskId: z.string().optional(),
|
|
135
|
+
summary: z.string().optional(),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
/** Zod schema for {@link PreCompactPayload}. */
|
|
139
|
+
export const PreCompactPayloadSchema = HookPayloadSchema.extend({
|
|
140
|
+
tokensBefore: z.number().optional(),
|
|
141
|
+
reason: z.string().optional(),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
/** Zod schema for {@link PostCompactPayload}. */
|
|
145
|
+
export const PostCompactPayloadSchema = HookPayloadSchema.extend({
|
|
146
|
+
tokensBefore: z.number().optional(),
|
|
147
|
+
tokensAfter: z.number().optional(),
|
|
148
|
+
success: z.boolean(),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/** Zod schema for {@link ConfigChangePayload}. */
|
|
152
|
+
export const ConfigChangePayloadSchema = HookPayloadSchema.extend({
|
|
153
|
+
key: z.string(),
|
|
154
|
+
previousValue: z.unknown().optional(),
|
|
155
|
+
newValue: z.unknown().optional(),
|
|
156
|
+
});
|
|
157
|
+
|
|
95
158
|
// ============================================================================
|
|
96
159
|
// CLEO Internal Event Payload Schemas
|
|
97
160
|
// ============================================================================
|
|
@@ -147,14 +210,21 @@ export const OnPatrolPayloadSchema = HookPayloadSchema.extend({
|
|
|
147
210
|
* this map fall back to the base {@link HookPayloadSchema}.
|
|
148
211
|
*/
|
|
149
212
|
const EVENT_SCHEMA_MAP: Partial<Record<HookEvent, z.ZodType>> = {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
213
|
+
// CAAMP canonical events (16)
|
|
214
|
+
SessionStart: SessionStartPayloadSchema,
|
|
215
|
+
SessionEnd: SessionEndPayloadSchema,
|
|
216
|
+
PreToolUse: PreToolUsePayloadSchema,
|
|
217
|
+
PostToolUse: PostToolUsePayloadSchema,
|
|
218
|
+
Notification: NotificationPayloadSchema,
|
|
219
|
+
PostToolUseFailure: PostToolUseFailurePayloadSchema,
|
|
220
|
+
PromptSubmit: PromptSubmitPayloadSchema,
|
|
221
|
+
ResponseComplete: ResponseCompletePayloadSchema,
|
|
222
|
+
SubagentStart: SubagentStartPayloadSchema,
|
|
223
|
+
SubagentStop: SubagentStopPayloadSchema,
|
|
224
|
+
PreCompact: PreCompactPayloadSchema,
|
|
225
|
+
PostCompact: PostCompactPayloadSchema,
|
|
226
|
+
ConfigChange: ConfigChangePayloadSchema,
|
|
227
|
+
// CLEO internal coordination events (5)
|
|
158
228
|
onWorkAvailable: OnWorkAvailablePayloadSchema,
|
|
159
229
|
onAgentSpawn: OnAgentSpawnPayloadSchema,
|
|
160
230
|
onAgentComplete: OnAgentCompletePayloadSchema,
|
|
@@ -2,29 +2,34 @@
|
|
|
2
2
|
* Provider Hook Capabilities - Phase 2C of T5237
|
|
3
3
|
*
|
|
4
4
|
* Helper functions for querying which providers support which hook events.
|
|
5
|
-
* Wraps CAAMP's provider discovery functions with CLEO-specific helpers.
|
|
5
|
+
* Wraps CAAMP's provider discovery and normalizer functions with CLEO-specific helpers.
|
|
6
|
+
*
|
|
7
|
+
* Updated for CAAMP 1.9.1: uses CanonicalHookEvent and toNative/toCanonical normalizers.
|
|
6
8
|
*
|
|
7
9
|
* @module @cleocode/cleo/hooks/provider-hooks
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import {
|
|
11
|
-
type
|
|
13
|
+
type CanonicalHookEvent,
|
|
12
14
|
getCommonHookEvents,
|
|
13
15
|
getProvidersByHookEvent,
|
|
16
|
+
supportsHook,
|
|
17
|
+
toCanonical,
|
|
18
|
+
toNative,
|
|
14
19
|
} from '@cleocode/caamp';
|
|
15
20
|
import { type HookEvent, isProviderHookEvent } from './types.js';
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
23
|
* Get all providers that support a specific hook event
|
|
19
24
|
*
|
|
20
|
-
* @param event - The hook event to query
|
|
25
|
+
* @param event - The hook event to query (canonical or internal)
|
|
21
26
|
* @returns Array of provider IDs that support this event
|
|
22
27
|
*/
|
|
23
28
|
export function getHookCapableProviders(event: HookEvent): string[] {
|
|
24
29
|
if (!isProviderHookEvent(event)) {
|
|
25
30
|
return [];
|
|
26
31
|
}
|
|
27
|
-
const providers = getProvidersByHookEvent(event);
|
|
32
|
+
const providers = getProvidersByHookEvent(event as CanonicalHookEvent);
|
|
28
33
|
return providers.map((p) => p.id);
|
|
29
34
|
}
|
|
30
35
|
|
|
@@ -32,11 +37,47 @@ export function getHookCapableProviders(event: HookEvent): string[] {
|
|
|
32
37
|
* Get hook events supported by all specified providers
|
|
33
38
|
*
|
|
34
39
|
* @param providerIds - Optional array of provider IDs (uses all active providers if omitted)
|
|
35
|
-
* @returns Array of hook events supported by all specified providers
|
|
40
|
+
* @returns Array of canonical hook events supported by all specified providers
|
|
41
|
+
*/
|
|
42
|
+
export function getSharedHookEvents(providerIds?: string[]): CanonicalHookEvent[] {
|
|
43
|
+
return getCommonHookEvents(providerIds) as CanonicalHookEvent[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Translate a canonical hook event to a provider-native event name.
|
|
48
|
+
*
|
|
49
|
+
* @param event - The canonical CAAMP event name
|
|
50
|
+
* @param providerId - The provider to translate for
|
|
51
|
+
* @returns The native event name, or null if the provider doesn't support it
|
|
52
|
+
*/
|
|
53
|
+
export function toNativeHookEvent(event: CanonicalHookEvent, providerId: string): string | null {
|
|
54
|
+
return toNative(event, providerId);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Translate a provider-native event name back to its canonical equivalent.
|
|
59
|
+
*
|
|
60
|
+
* @param nativeEvent - The provider-specific event name
|
|
61
|
+
* @param providerId - The provider this event belongs to
|
|
62
|
+
* @returns The canonical event name, or null if no mapping exists
|
|
63
|
+
*/
|
|
64
|
+
export function toCanonicalHookEvent(
|
|
65
|
+
nativeEvent: string,
|
|
66
|
+
providerId: string,
|
|
67
|
+
): CanonicalHookEvent | null {
|
|
68
|
+
return toCanonical(nativeEvent, providerId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if a provider supports a specific canonical hook event.
|
|
73
|
+
*
|
|
74
|
+
* @param event - The canonical event name
|
|
75
|
+
* @param providerId - The provider to check
|
|
76
|
+
* @returns True if the provider supports this event
|
|
36
77
|
*/
|
|
37
|
-
export function
|
|
38
|
-
return
|
|
78
|
+
export function providerSupportsHookEvent(event: CanonicalHookEvent, providerId: string): boolean {
|
|
79
|
+
return supportsHook(event, providerId);
|
|
39
80
|
}
|
|
40
81
|
|
|
41
|
-
export type {
|
|
42
|
-
export { getCommonHookEvents, getProvidersByHookEvent };
|
|
82
|
+
export type { CanonicalHookEvent as ProviderHookEvent, HookEvent };
|
|
83
|
+
export { getCommonHookEvents, getProvidersByHookEvent, supportsHook, toCanonical, toNative };
|
package/src/hooks/registry.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Hook Registry System - Phase 2C of T5237
|
|
3
3
|
*
|
|
4
4
|
* Central registry for managing hook handlers with priority-based execution,
|
|
5
|
-
* async dispatch, and best-effort error handling. Integrates with CAAMP 1.
|
|
6
|
-
* event definitions while providing CLEO's execution engine.
|
|
5
|
+
* async dispatch, and best-effort error handling. Integrates with CAAMP 1.9.1
|
|
6
|
+
* canonical event definitions while providing CLEO's execution engine.
|
|
7
7
|
*
|
|
8
8
|
* @module @cleocode/cleo/hooks/registry
|
|
9
9
|
*/
|
|
@@ -11,6 +11,18 @@
|
|
|
11
11
|
import { getLogger } from '../logger.js';
|
|
12
12
|
import type { HookConfig, HookEvent, HookPayload, HookRegistration } from './types.js';
|
|
13
13
|
|
|
14
|
+
/** Map from old `on`-prefix event names to new canonical names. Used for backward compat. */
|
|
15
|
+
const LEGACY_EVENT_MAP: Record<string, HookEvent> = {
|
|
16
|
+
onSessionStart: 'SessionStart',
|
|
17
|
+
onSessionEnd: 'SessionEnd',
|
|
18
|
+
onToolStart: 'PreToolUse',
|
|
19
|
+
onToolComplete: 'PostToolUse',
|
|
20
|
+
onFileChange: 'Notification',
|
|
21
|
+
onError: 'PostToolUseFailure',
|
|
22
|
+
onPromptSubmit: 'PromptSubmit',
|
|
23
|
+
onResponseComplete: 'ResponseComplete',
|
|
24
|
+
};
|
|
25
|
+
|
|
14
26
|
/**
|
|
15
27
|
* Default configuration for the hook system.
|
|
16
28
|
* All events are enabled by default.
|
|
@@ -18,14 +30,24 @@ import type { HookConfig, HookEvent, HookPayload, HookRegistration } from './typ
|
|
|
18
30
|
const DEFAULT_HOOK_CONFIG: HookConfig = {
|
|
19
31
|
enabled: true,
|
|
20
32
|
events: {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
// CAAMP canonical events (16)
|
|
34
|
+
SessionStart: true,
|
|
35
|
+
SessionEnd: true,
|
|
36
|
+
PromptSubmit: true,
|
|
37
|
+
ResponseComplete: true,
|
|
38
|
+
PreToolUse: true,
|
|
39
|
+
PostToolUse: true,
|
|
40
|
+
PostToolUseFailure: true,
|
|
41
|
+
PermissionRequest: true,
|
|
42
|
+
SubagentStart: true,
|
|
43
|
+
SubagentStop: true,
|
|
44
|
+
PreModel: true,
|
|
45
|
+
PostModel: true,
|
|
46
|
+
PreCompact: true,
|
|
47
|
+
PostCompact: true,
|
|
48
|
+
Notification: true,
|
|
49
|
+
ConfigChange: true,
|
|
50
|
+
// CLEO internal coordination events (5)
|
|
29
51
|
onWorkAvailable: true,
|
|
30
52
|
onAgentSpawn: true,
|
|
31
53
|
onAgentComplete: true,
|
|
@@ -40,17 +62,43 @@ const DEFAULT_HOOK_CONFIG: HookConfig = {
|
|
|
40
62
|
* Manages registration, priority-based ordering, and async dispatch
|
|
41
63
|
* of hook handlers. Provides best-effort execution where errors in
|
|
42
64
|
* one handler do not block others.
|
|
65
|
+
*
|
|
66
|
+
* Backward compatibility: handlers registered with legacy `on`-prefix
|
|
67
|
+
* event names (e.g. `onSessionStart`) are automatically remapped to their
|
|
68
|
+
* canonical equivalents (e.g. `SessionStart`) with a deprecation warning.
|
|
43
69
|
*/
|
|
44
70
|
export class HookRegistry {
|
|
45
71
|
private handlers: Map<HookEvent, HookRegistration[]> = new Map();
|
|
46
72
|
private config: HookConfig = DEFAULT_HOOK_CONFIG;
|
|
47
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Resolve a potentially-legacy event name to its canonical equivalent.
|
|
76
|
+
*
|
|
77
|
+
* If the event name matches a known legacy `on`-prefix name, it is
|
|
78
|
+
* remapped and a deprecation warning is logged. Unknown names pass through
|
|
79
|
+
* unchanged so callers using the new canonical names are unaffected.
|
|
80
|
+
*/
|
|
81
|
+
private resolveEvent(event: string): HookEvent {
|
|
82
|
+
const canonical = LEGACY_EVENT_MAP[event];
|
|
83
|
+
if (canonical) {
|
|
84
|
+
getLogger('hooks').warn(
|
|
85
|
+
{ legacyEvent: event, canonicalEvent: canonical },
|
|
86
|
+
`[DEPRECATED] Hook event '${event}' has been renamed to '${canonical}'. Update your handler registration.`,
|
|
87
|
+
);
|
|
88
|
+
return canonical;
|
|
89
|
+
}
|
|
90
|
+
return event as HookEvent;
|
|
91
|
+
}
|
|
92
|
+
|
|
48
93
|
/**
|
|
49
94
|
* Register a hook handler for a specific event.
|
|
50
95
|
*
|
|
51
96
|
* Handlers are sorted by priority (highest first) and executed
|
|
52
97
|
* in parallel when the event is dispatched.
|
|
53
98
|
*
|
|
99
|
+
* Backward compatibility: legacy `on`-prefix event names are automatically
|
|
100
|
+
* remapped to their canonical equivalents.
|
|
101
|
+
*
|
|
54
102
|
* @param registration - The hook registration containing event, handler, priority, and ID
|
|
55
103
|
* @returns A function to unregister the handler
|
|
56
104
|
*
|
|
@@ -58,7 +106,7 @@ export class HookRegistry {
|
|
|
58
106
|
* ```typescript
|
|
59
107
|
* const unregister = hooks.register({
|
|
60
108
|
* id: 'my-handler',
|
|
61
|
-
* event: '
|
|
109
|
+
* event: 'SessionStart',
|
|
62
110
|
* handler: async (root, payload) => { console.log('Session started'); },
|
|
63
111
|
* priority: 100
|
|
64
112
|
* });
|
|
@@ -67,15 +115,18 @@ export class HookRegistry {
|
|
|
67
115
|
* ```
|
|
68
116
|
*/
|
|
69
117
|
register<T extends HookPayload>(registration: HookRegistration<T>): () => void {
|
|
70
|
-
const
|
|
71
|
-
|
|
118
|
+
const resolvedEvent = this.resolveEvent(registration.event as string);
|
|
119
|
+
const resolvedRegistration = { ...registration, event: resolvedEvent } as HookRegistration;
|
|
120
|
+
|
|
121
|
+
const list = this.handlers.get(resolvedEvent) || [];
|
|
122
|
+
list.push(resolvedRegistration);
|
|
72
123
|
// Sort by priority (highest first)
|
|
73
124
|
list.sort((a, b) => b.priority - a.priority);
|
|
74
|
-
this.handlers.set(
|
|
125
|
+
this.handlers.set(resolvedEvent, list);
|
|
75
126
|
|
|
76
127
|
// Return unregister function
|
|
77
128
|
return () => {
|
|
78
|
-
const handlers = this.handlers.get(
|
|
129
|
+
const handlers = this.handlers.get(resolvedEvent);
|
|
79
130
|
if (handlers) {
|
|
80
131
|
const idx = handlers.findIndex((h) => h.id === registration.id);
|
|
81
132
|
if (idx !== -1) handlers.splice(idx, 1);
|
|
@@ -90,14 +141,17 @@ export class HookRegistry {
|
|
|
90
141
|
* execution. Errors in individual handlers are logged but do not block
|
|
91
142
|
* other handlers or propagate to the caller.
|
|
92
143
|
*
|
|
93
|
-
*
|
|
144
|
+
* Backward compatibility: legacy `on`-prefix event names are automatically
|
|
145
|
+
* remapped to their canonical equivalents.
|
|
146
|
+
*
|
|
147
|
+
* @param event - The CAAMP canonical hook event to dispatch
|
|
94
148
|
* @param projectRoot - The project root directory path
|
|
95
149
|
* @param payload - The event payload (typed by event)
|
|
96
150
|
* @returns Promise that resolves when all handlers have completed
|
|
97
151
|
*
|
|
98
152
|
* @example
|
|
99
153
|
* ```typescript
|
|
100
|
-
* await hooks.dispatch('
|
|
154
|
+
* await hooks.dispatch('SessionStart', '/project', {
|
|
101
155
|
* timestamp: new Date().toISOString(),
|
|
102
156
|
* sessionId: 'sess-123',
|
|
103
157
|
* name: 'My Session',
|
|
@@ -113,10 +167,12 @@ export class HookRegistry {
|
|
|
113
167
|
// Check if hooks enabled globally
|
|
114
168
|
if (!this.config.enabled) return;
|
|
115
169
|
|
|
170
|
+
const resolvedEvent = this.resolveEvent(event as string);
|
|
171
|
+
|
|
116
172
|
// Check if this event enabled
|
|
117
|
-
if (!this.config.events[
|
|
173
|
+
if (!this.config.events[resolvedEvent]) return;
|
|
118
174
|
|
|
119
|
-
const handlers = this.handlers.get(
|
|
175
|
+
const handlers = this.handlers.get(resolvedEvent);
|
|
120
176
|
if (!handlers || handlers.length === 0) return;
|
|
121
177
|
|
|
122
178
|
// Execute all handlers in parallel (best-effort)
|
|
@@ -126,7 +182,10 @@ export class HookRegistry {
|
|
|
126
182
|
await reg.handler(projectRoot, payload);
|
|
127
183
|
} catch (error) {
|
|
128
184
|
// Hooks are best-effort - log but don't throw
|
|
129
|
-
getLogger('hooks').warn(
|
|
185
|
+
getLogger('hooks').warn(
|
|
186
|
+
{ err: error, hookId: reg.id, event: resolvedEvent },
|
|
187
|
+
'Hook handler failed',
|
|
188
|
+
);
|
|
130
189
|
}
|
|
131
190
|
}),
|
|
132
191
|
);
|
|
@@ -136,12 +195,14 @@ export class HookRegistry {
|
|
|
136
195
|
* Check if a specific event is currently enabled.
|
|
137
196
|
*
|
|
138
197
|
* Both the global enabled flag and the per-event flag must be true.
|
|
198
|
+
* Automatically resolves legacy `on`-prefix event names.
|
|
139
199
|
*
|
|
140
200
|
* @param event - The CAAMP hook event to check
|
|
141
201
|
* @returns True if the event is enabled
|
|
142
202
|
*/
|
|
143
203
|
isEnabled(event: HookEvent): boolean {
|
|
144
|
-
|
|
204
|
+
const resolvedEvent = this.resolveEvent(event as string);
|
|
205
|
+
return this.config.enabled && this.config.events[resolvedEvent];
|
|
145
206
|
}
|
|
146
207
|
|
|
147
208
|
/**
|
|
@@ -154,7 +215,7 @@ export class HookRegistry {
|
|
|
154
215
|
* @example
|
|
155
216
|
* ```typescript
|
|
156
217
|
* hooks.setConfig({ enabled: false }); // Disable all hooks
|
|
157
|
-
* hooks.setConfig({ events: {
|
|
218
|
+
* hooks.setConfig({ events: { PostToolUseFailure: false } }); // Disable specific event
|
|
158
219
|
* ```
|
|
159
220
|
*/
|
|
160
221
|
setConfig(config: Partial<HookConfig>): void {
|
|
@@ -174,12 +235,14 @@ export class HookRegistry {
|
|
|
174
235
|
* List all registered handlers for a specific event.
|
|
175
236
|
*
|
|
176
237
|
* Returns handlers in priority order (highest first).
|
|
238
|
+
* Automatically resolves legacy `on`-prefix event names.
|
|
177
239
|
*
|
|
178
240
|
* @param event - The CAAMP hook event
|
|
179
241
|
* @returns Array of hook registrations
|
|
180
242
|
*/
|
|
181
243
|
listHandlers(event: HookEvent): HookRegistration[] {
|
|
182
|
-
|
|
244
|
+
const resolvedEvent = this.resolveEvent(event as string);
|
|
245
|
+
return [...(this.handlers.get(resolvedEvent) || [])];
|
|
183
246
|
}
|
|
184
247
|
}
|
|
185
248
|
|