zouroboros-core 2.0.0

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.
@@ -0,0 +1,81 @@
1
+ /**
2
+ * ECC-001: Lifecycle Hook System
3
+ *
4
+ * Event-driven hooks that fire at structured points in the conversation lifecycle.
5
+ * Hooks can be registered programmatically or loaded from hook definition files.
6
+ */
7
+ export type LifecycleEvent = 'conversation.start' | 'conversation.end' | 'task.start' | 'task.complete' | 'task.fail' | 'tool.call' | 'tool.result' | 'memory.store' | 'memory.search' | 'memory.threshold' | 'persona.switch' | 'context.warning' | 'context.critical' | 'context.emergency' | 'swarm.wave.start' | 'swarm.wave.end' | 'command.execute' | 'session.branch' | 'session.compact' | 'instinct.fired' | 'error.recovery';
8
+ export interface HookPayload {
9
+ event: LifecycleEvent;
10
+ timestamp: string;
11
+ data: Record<string, unknown>;
12
+ source?: string;
13
+ }
14
+ export type HookHandler = (payload: HookPayload) => void | Promise<void>;
15
+ export interface HookRegistration {
16
+ id: string;
17
+ event: LifecycleEvent | LifecycleEvent[];
18
+ handler: HookHandler;
19
+ priority: number;
20
+ once: boolean;
21
+ enabled: boolean;
22
+ description?: string;
23
+ }
24
+ export type HookDefinitionAction = 'log' | 'memory_capture' | 'checkpoint' | 'notify' | 'custom';
25
+ export interface HookDefinition {
26
+ id: string;
27
+ event: LifecycleEvent | LifecycleEvent[];
28
+ action: HookDefinitionAction;
29
+ priority?: number;
30
+ once?: boolean;
31
+ config?: Record<string, unknown>;
32
+ description?: string;
33
+ }
34
+ /** Handler factory for pluggable definition actions */
35
+ export type DefinitionActionHandler = (def: HookDefinition) => HookHandler;
36
+ export interface HookStats {
37
+ totalRegistered: number;
38
+ totalFired: number;
39
+ byEvent: Record<string, {
40
+ registered: number;
41
+ fired: number;
42
+ avgLatencyMs: number;
43
+ }>;
44
+ errors: number;
45
+ lastFired?: string;
46
+ }
47
+ export declare class HookSystem {
48
+ private hooks;
49
+ private eventIndex;
50
+ private actionHandlers;
51
+ private stats;
52
+ constructor();
53
+ /** Register a pluggable action handler for HookDefinition actions */
54
+ registerAction(action: string, factory: DefinitionActionHandler): void;
55
+ register(registration: HookRegistration): string;
56
+ on(event: LifecycleEvent | LifecycleEvent[], handler: HookHandler, options?: {
57
+ priority?: number;
58
+ description?: string;
59
+ }): string;
60
+ once(event: LifecycleEvent | LifecycleEvent[], handler: HookHandler, options?: {
61
+ priority?: number;
62
+ }): string;
63
+ off(hookId: string): boolean;
64
+ enable(hookId: string): boolean;
65
+ disable(hookId: string): boolean;
66
+ emit(event: LifecycleEvent, data?: Record<string, unknown>, source?: string): Promise<void>;
67
+ emitSync(event: LifecycleEvent, data?: Record<string, unknown>, source?: string): void;
68
+ loadDefinitions(definitions: HookDefinition[]): string[];
69
+ getStats(): HookStats;
70
+ listHooks(): Array<{
71
+ id: string;
72
+ events: LifecycleEvent[];
73
+ enabled: boolean;
74
+ priority: number;
75
+ description?: string;
76
+ }>;
77
+ clear(): void;
78
+ }
79
+ export declare function getHookSystem(): HookSystem;
80
+ export declare function createHookSystem(): HookSystem;
81
+ export declare function resetGlobalHooks(): void;
package/dist/hooks.js ADDED
@@ -0,0 +1,231 @@
1
+ /**
2
+ * ECC-001: Lifecycle Hook System
3
+ *
4
+ * Event-driven hooks that fire at structured points in the conversation lifecycle.
5
+ * Hooks can be registered programmatically or loaded from hook definition files.
6
+ */
7
+ export class HookSystem {
8
+ hooks = new Map();
9
+ eventIndex = new Map();
10
+ actionHandlers = new Map();
11
+ stats = { totalFired: 0, byEvent: {}, errors: 0 };
12
+ constructor() {
13
+ // Register built-in action handlers
14
+ this.registerAction('log', (def) => (payload) => {
15
+ console.log(`[hook:${def.id}] ${payload.event}`, JSON.stringify(payload.data).slice(0, 200));
16
+ });
17
+ this.registerAction('notify', (def) => (payload) => {
18
+ console.log(`[hook:${def.id}] Notification: ${payload.event}`, payload.data);
19
+ });
20
+ // memory_capture, checkpoint, custom — default to log until wired by integration layer
21
+ this.registerAction('memory_capture', (def) => (payload) => {
22
+ console.log(`[hook:${def.id}] Memory capture triggered by ${payload.event}`);
23
+ });
24
+ this.registerAction('checkpoint', (def) => (payload) => {
25
+ console.log(`[hook:${def.id}] Checkpoint at ${payload.event}`);
26
+ });
27
+ this.registerAction('custom', (def) => (payload) => {
28
+ console.log(`[hook:${def.id}] Custom action for ${payload.event}`);
29
+ });
30
+ }
31
+ /** Register a pluggable action handler for HookDefinition actions */
32
+ registerAction(action, factory) {
33
+ this.actionHandlers.set(action, factory);
34
+ }
35
+ register(registration) {
36
+ this.hooks.set(registration.id, registration);
37
+ const events = Array.isArray(registration.event) ? registration.event : [registration.event];
38
+ for (const event of events) {
39
+ if (!this.eventIndex.has(event)) {
40
+ this.eventIndex.set(event, new Set());
41
+ }
42
+ this.eventIndex.get(event).add(registration.id);
43
+ }
44
+ return registration.id;
45
+ }
46
+ on(event, handler, options) {
47
+ const id = `hook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
48
+ this.register({
49
+ id,
50
+ event,
51
+ handler,
52
+ priority: options?.priority ?? 100,
53
+ once: false,
54
+ enabled: true,
55
+ description: options?.description,
56
+ });
57
+ return id;
58
+ }
59
+ once(event, handler, options) {
60
+ const id = `hook-once-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
61
+ this.register({
62
+ id,
63
+ event,
64
+ handler,
65
+ priority: options?.priority ?? 100,
66
+ once: true,
67
+ enabled: true,
68
+ });
69
+ return id;
70
+ }
71
+ off(hookId) {
72
+ const hook = this.hooks.get(hookId);
73
+ if (!hook)
74
+ return false;
75
+ this.hooks.delete(hookId);
76
+ const events = Array.isArray(hook.event) ? hook.event : [hook.event];
77
+ for (const event of events) {
78
+ this.eventIndex.get(event)?.delete(hookId);
79
+ }
80
+ return true;
81
+ }
82
+ enable(hookId) {
83
+ const hook = this.hooks.get(hookId);
84
+ if (!hook)
85
+ return false;
86
+ hook.enabled = true;
87
+ return true;
88
+ }
89
+ disable(hookId) {
90
+ const hook = this.hooks.get(hookId);
91
+ if (!hook)
92
+ return false;
93
+ hook.enabled = false;
94
+ return true;
95
+ }
96
+ async emit(event, data = {}, source) {
97
+ const hookIds = this.eventIndex.get(event);
98
+ if (!hookIds || hookIds.size === 0)
99
+ return;
100
+ const payload = {
101
+ event,
102
+ timestamp: new Date().toISOString(),
103
+ data,
104
+ source,
105
+ };
106
+ // Sort by priority
107
+ const hooks = [...hookIds]
108
+ .map(id => this.hooks.get(id))
109
+ .filter(h => h && h.enabled)
110
+ .sort((a, b) => a.priority - b.priority);
111
+ const start = Date.now();
112
+ for (const hook of hooks) {
113
+ try {
114
+ await hook.handler(payload);
115
+ }
116
+ catch {
117
+ this.stats.errors++;
118
+ }
119
+ if (hook.once) {
120
+ this.off(hook.id);
121
+ }
122
+ }
123
+ const latency = Date.now() - start;
124
+ this.stats.totalFired++;
125
+ this.stats.lastFired = payload.timestamp;
126
+ if (!this.stats.byEvent[event]) {
127
+ this.stats.byEvent[event] = { fired: 0, totalLatency: 0 };
128
+ }
129
+ this.stats.byEvent[event].fired++;
130
+ this.stats.byEvent[event].totalLatency += latency;
131
+ }
132
+ emitSync(event, data = {}, source) {
133
+ const hookIds = this.eventIndex.get(event);
134
+ if (!hookIds || hookIds.size === 0)
135
+ return;
136
+ const payload = {
137
+ event,
138
+ timestamp: new Date().toISOString(),
139
+ data,
140
+ source,
141
+ };
142
+ const hooks = [...hookIds]
143
+ .map(id => this.hooks.get(id))
144
+ .filter(h => h && h.enabled)
145
+ .sort((a, b) => a.priority - b.priority);
146
+ for (const hook of hooks) {
147
+ try {
148
+ const result = hook.handler(payload);
149
+ // If handler returns a promise, catch errors without blocking
150
+ if (result && typeof result.catch === 'function') {
151
+ result.catch(() => { this.stats.errors++; });
152
+ }
153
+ }
154
+ catch {
155
+ this.stats.errors++;
156
+ }
157
+ if (hook.once) {
158
+ this.off(hook.id);
159
+ }
160
+ }
161
+ this.stats.totalFired++;
162
+ this.stats.lastFired = payload.timestamp;
163
+ if (!this.stats.byEvent[event]) {
164
+ this.stats.byEvent[event] = { fired: 0, totalLatency: 0 };
165
+ }
166
+ this.stats.byEvent[event].fired++;
167
+ }
168
+ loadDefinitions(definitions) {
169
+ const ids = [];
170
+ for (const def of definitions) {
171
+ const factory = this.actionHandlers.get(def.action);
172
+ const handler = factory ? factory(def) : (() => { });
173
+ const id = this.register({
174
+ id: def.id,
175
+ event: def.event,
176
+ handler,
177
+ priority: def.priority ?? 100,
178
+ once: def.once ?? false,
179
+ enabled: true,
180
+ description: def.description,
181
+ });
182
+ ids.push(id);
183
+ }
184
+ return ids;
185
+ }
186
+ getStats() {
187
+ const byEvent = {};
188
+ for (const [event, hookIds] of this.eventIndex) {
189
+ const eventStats = this.stats.byEvent[event] || { fired: 0, totalLatency: 0 };
190
+ byEvent[event] = {
191
+ registered: hookIds.size,
192
+ fired: eventStats.fired,
193
+ avgLatencyMs: eventStats.fired > 0 ? eventStats.totalLatency / eventStats.fired : 0,
194
+ };
195
+ }
196
+ return {
197
+ totalRegistered: this.hooks.size,
198
+ totalFired: this.stats.totalFired,
199
+ byEvent,
200
+ errors: this.stats.errors,
201
+ lastFired: this.stats.lastFired,
202
+ };
203
+ }
204
+ listHooks() {
205
+ return [...this.hooks.values()].map(h => ({
206
+ id: h.id,
207
+ events: Array.isArray(h.event) ? h.event : [h.event],
208
+ enabled: h.enabled,
209
+ priority: h.priority,
210
+ description: h.description,
211
+ }));
212
+ }
213
+ clear() {
214
+ this.hooks.clear();
215
+ this.eventIndex.clear();
216
+ }
217
+ }
218
+ // Singleton for ecosystem-wide hook system
219
+ let _globalHooks = null;
220
+ export function getHookSystem() {
221
+ if (!_globalHooks) {
222
+ _globalHooks = new HookSystem();
223
+ }
224
+ return _globalHooks;
225
+ }
226
+ export function createHookSystem() {
227
+ return new HookSystem();
228
+ }
229
+ export function resetGlobalHooks() {
230
+ _globalHooks = null;
231
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Zouroboros Core
3
+ *
4
+ * Core types, constants, and utilities shared across all Zouroboros packages.
5
+ *
6
+ * @module zouroboros-core
7
+ */
8
+ export * from './types.js';
9
+ export * from './constants.js';
10
+ export * from './config/loader.js';
11
+ export * from './config/schema.js';
12
+ export * from './backup.js';
13
+ export * from './errors.js';
14
+ export * from './migrations.js';
15
+ export * from './hooks.js';
16
+ export * from './token-budget.js';
17
+ export * from './commands.js';
18
+ export * from './sessions.js';
19
+ export * from './instincts.js';
20
+ export declare const VERSION = "2.0.0";
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Zouroboros Core
3
+ *
4
+ * Core types, constants, and utilities shared across all Zouroboros packages.
5
+ *
6
+ * @module zouroboros-core
7
+ */
8
+ export * from './types.js';
9
+ export * from './constants.js';
10
+ export * from './config/loader.js';
11
+ export * from './config/schema.js';
12
+ export * from './backup.js';
13
+ export * from './errors.js';
14
+ export * from './migrations.js';
15
+ export * from './hooks.js';
16
+ export * from './token-budget.js';
17
+ export * from './commands.js';
18
+ export * from './sessions.js';
19
+ export * from './instincts.js';
20
+ // Version
21
+ export const VERSION = '2.0.0';
@@ -0,0 +1,117 @@
1
+ /**
2
+ * ECC-004: Instincts — Pattern Auto-Extraction
3
+ *
4
+ * Automatic extraction of behavioral patterns from sessions.
5
+ * Detects recurring patterns, scores confidence, and extracts
6
+ * hot-loadable instinct files for future sessions.
7
+ * Persists to disk (JSON file) matching selfheal persistence pattern.
8
+ */
9
+ import type { HookSystem, LifecycleEvent } from './hooks.js';
10
+ export interface Instinct {
11
+ id: string;
12
+ name: string;
13
+ description: string;
14
+ confidence: number;
15
+ pattern: PatternSpec;
16
+ trigger: TriggerSpec;
17
+ resolution: string;
18
+ evidenceCount: number;
19
+ lastSeen: string;
20
+ createdAt: string;
21
+ status: InstinctStatus;
22
+ tags: string[];
23
+ }
24
+ export type InstinctStatus = 'candidate' | 'active' | 'suspended' | 'retired';
25
+ export interface PatternSpec {
26
+ type: 'error_repeat' | 'tool_sequence' | 'context_drift' | 'custom';
27
+ signature: string;
28
+ frequency: number;
29
+ windowSize: number;
30
+ }
31
+ export interface TriggerSpec {
32
+ event: LifecycleEvent | '*';
33
+ condition: string;
34
+ cooldownMs: number;
35
+ }
36
+ export interface PatternEvidence {
37
+ timestamp: string;
38
+ sessionId?: string;
39
+ context: string;
40
+ matchStrength: number;
41
+ }
42
+ export interface InstinctMatch {
43
+ instinct: Instinct;
44
+ confidence: number;
45
+ evidence: PatternEvidence[];
46
+ }
47
+ export interface ExtractionResult {
48
+ patternsDetected: number;
49
+ instinctsCreated: number;
50
+ instinctsUpdated: number;
51
+ details: Array<{
52
+ id: string;
53
+ action: 'created' | 'updated' | 'skipped';
54
+ reason?: string;
55
+ }>;
56
+ }
57
+ export declare class InstinctEngine {
58
+ private instincts;
59
+ private evidence;
60
+ private lastFired;
61
+ private dataFile;
62
+ private hooks;
63
+ private pendingObservations;
64
+ constructor(dataDir?: string);
65
+ wireHooks(hooks: HookSystem): void;
66
+ /** Record a single observation for later batch extraction */
67
+ recordObservation(obs: {
68
+ context: string;
69
+ outcome: string;
70
+ timestamp: string;
71
+ sessionId?: string;
72
+ }): void;
73
+ register(instinct: Instinct): void;
74
+ get(id: string): Instinct | null;
75
+ list(filter?: {
76
+ status?: InstinctStatus;
77
+ minConfidence?: number;
78
+ tag?: string;
79
+ }): Instinct[];
80
+ addEvidence(instinctId: string, ev: PatternEvidence): boolean;
81
+ /** Record a negative match — the instinct fired but was wrong */
82
+ rejectMatch(instinctId: string): boolean;
83
+ match(context: string, event?: string): InstinctMatch[];
84
+ fire(instinctId: string): boolean;
85
+ extract(observations: Array<{
86
+ context: string;
87
+ outcome: string;
88
+ timestamp: string;
89
+ sessionId?: string;
90
+ }>): ExtractionResult;
91
+ suspend(id: string): boolean;
92
+ activate(id: string): boolean;
93
+ retire(id: string): boolean;
94
+ remove(id: string): boolean;
95
+ getStats(): {
96
+ total: number;
97
+ byStatus: Record<InstinctStatus, number>;
98
+ avgConfidence: number;
99
+ totalEvidence: number;
100
+ };
101
+ exportInstinct(id: string): object | null;
102
+ importInstinct(data: Instinct & {
103
+ evidence?: PatternEvidence[];
104
+ }): void;
105
+ private calculateConfidence;
106
+ private computeMatch;
107
+ /**
108
+ * Group observations by context similarity using Jaccard index on word sets.
109
+ */
110
+ private groupByContextSimilarity;
111
+ private jaccardSimilarity;
112
+ private generateSignature;
113
+ private findBySignature;
114
+ private load;
115
+ private save;
116
+ }
117
+ export declare function createInstinctEngine(dataDir?: string): InstinctEngine;