@happycastle/oh-my-openclaw 0.10.0 → 0.11.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.
- package/dist/features/context-collector.d.ts +30 -0
- package/dist/features/context-collector.js +85 -0
- package/dist/hooks/context-injector.d.ts +2 -0
- package/dist/hooks/context-injector.js +20 -0
- package/dist/hooks/persona-injector.d.ts +1 -0
- package/dist/hooks/persona-injector.js +25 -11
- package/dist/hooks/todo-enforcer.js +8 -11
- package/dist/index.js +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type ContextPriority = 'critical' | 'high' | 'normal' | 'low';
|
|
2
|
+
export type ContextSourceType = 'persona' | 'todo-enforcer' | 'system' | 'plugin';
|
|
3
|
+
export interface ContextEntry {
|
|
4
|
+
id: string;
|
|
5
|
+
content: string;
|
|
6
|
+
priority: ContextPriority;
|
|
7
|
+
source: ContextSourceType;
|
|
8
|
+
oneShot?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface RegisterContextOptions {
|
|
11
|
+
id: string;
|
|
12
|
+
content: string;
|
|
13
|
+
priority?: ContextPriority;
|
|
14
|
+
source?: ContextSourceType;
|
|
15
|
+
oneShot?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare class ContextCollector {
|
|
18
|
+
private sessions;
|
|
19
|
+
register(sessionKey: string, options: RegisterContextOptions): void;
|
|
20
|
+
unregister(sessionKey: string, entryId: string): void;
|
|
21
|
+
collect(sessionKey: string): ContextEntry[];
|
|
22
|
+
collectAsString(sessionKey: string, separator?: string): string;
|
|
23
|
+
clear(sessionKey: string): void;
|
|
24
|
+
clearAll(): void;
|
|
25
|
+
getEntries(sessionKey: string): ContextEntry[];
|
|
26
|
+
hasEntries(sessionKey: string): boolean;
|
|
27
|
+
private getOrCreateSession;
|
|
28
|
+
private sortEntries;
|
|
29
|
+
}
|
|
30
|
+
export declare const contextCollector: ContextCollector;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const PRIORITY_ORDER = {
|
|
2
|
+
critical: 0,
|
|
3
|
+
high: 1,
|
|
4
|
+
normal: 2,
|
|
5
|
+
low: 3,
|
|
6
|
+
};
|
|
7
|
+
export class ContextCollector {
|
|
8
|
+
sessions = new Map();
|
|
9
|
+
register(sessionKey, options) {
|
|
10
|
+
const sessionEntries = this.getOrCreateSession(sessionKey);
|
|
11
|
+
const entry = {
|
|
12
|
+
id: options.id,
|
|
13
|
+
content: options.content,
|
|
14
|
+
priority: options.priority ?? 'normal',
|
|
15
|
+
source: options.source ?? 'plugin',
|
|
16
|
+
oneShot: options.oneShot ?? false,
|
|
17
|
+
};
|
|
18
|
+
sessionEntries.set(options.id, entry);
|
|
19
|
+
}
|
|
20
|
+
unregister(sessionKey, entryId) {
|
|
21
|
+
const sessionEntries = this.sessions.get(sessionKey);
|
|
22
|
+
if (!sessionEntries) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
sessionEntries.delete(entryId);
|
|
26
|
+
if (sessionEntries.size === 0) {
|
|
27
|
+
this.sessions.delete(sessionKey);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
collect(sessionKey) {
|
|
31
|
+
const sessionEntries = this.sessions.get(sessionKey);
|
|
32
|
+
if (!sessionEntries) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const entries = this.sortEntries([...sessionEntries.values()]);
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
if (entry.oneShot) {
|
|
38
|
+
sessionEntries.delete(entry.id);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (sessionEntries.size === 0) {
|
|
42
|
+
this.sessions.delete(sessionKey);
|
|
43
|
+
}
|
|
44
|
+
return entries;
|
|
45
|
+
}
|
|
46
|
+
collectAsString(sessionKey, separator = '\n\n') {
|
|
47
|
+
const entries = this.collect(sessionKey);
|
|
48
|
+
return entries.map((entry) => entry.content).join(separator);
|
|
49
|
+
}
|
|
50
|
+
clear(sessionKey) {
|
|
51
|
+
this.sessions.delete(sessionKey);
|
|
52
|
+
}
|
|
53
|
+
clearAll() {
|
|
54
|
+
this.sessions.clear();
|
|
55
|
+
}
|
|
56
|
+
getEntries(sessionKey) {
|
|
57
|
+
const sessionEntries = this.sessions.get(sessionKey);
|
|
58
|
+
if (!sessionEntries) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
return this.sortEntries([...sessionEntries.values()]);
|
|
62
|
+
}
|
|
63
|
+
hasEntries(sessionKey) {
|
|
64
|
+
const sessionEntries = this.sessions.get(sessionKey);
|
|
65
|
+
return Boolean(sessionEntries && sessionEntries.size > 0);
|
|
66
|
+
}
|
|
67
|
+
getOrCreateSession(sessionKey) {
|
|
68
|
+
let sessionEntries = this.sessions.get(sessionKey);
|
|
69
|
+
if (!sessionEntries) {
|
|
70
|
+
sessionEntries = new Map();
|
|
71
|
+
this.sessions.set(sessionKey, sessionEntries);
|
|
72
|
+
}
|
|
73
|
+
return sessionEntries;
|
|
74
|
+
}
|
|
75
|
+
sortEntries(entries) {
|
|
76
|
+
return entries.sort((a, b) => {
|
|
77
|
+
const priorityDiff = PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority];
|
|
78
|
+
if (priorityDiff !== 0) {
|
|
79
|
+
return priorityDiff;
|
|
80
|
+
}
|
|
81
|
+
return a.id.localeCompare(b.id);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export const contextCollector = new ContextCollector();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { contextCollector } from '../features/context-collector.js';
|
|
2
|
+
export function registerContextInjector(api) {
|
|
3
|
+
api.registerHook('before_prompt_build', (event) => {
|
|
4
|
+
const sessionKey = event.agentId || 'default';
|
|
5
|
+
if (!contextCollector.hasEntries(sessionKey)) {
|
|
6
|
+
return event;
|
|
7
|
+
}
|
|
8
|
+
const entryCount = contextCollector.getEntries(sessionKey).length;
|
|
9
|
+
const collectedContext = contextCollector.collectAsString(sessionKey);
|
|
10
|
+
if (!collectedContext) {
|
|
11
|
+
return event;
|
|
12
|
+
}
|
|
13
|
+
event.prependContext = collectedContext;
|
|
14
|
+
api.logger.info(`[omoc] Context injected: ${entryCount} entries for ${sessionKey}`);
|
|
15
|
+
return event;
|
|
16
|
+
}, {
|
|
17
|
+
name: 'oh-my-openclaw.context-injector',
|
|
18
|
+
description: 'Unified context injection from ContextCollector into prependContext',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import { getActivePersona } from '../utils/persona-state.js';
|
|
2
2
|
import { readPersonaPromptSync } from '../agents/persona-prompts.js';
|
|
3
|
+
import { contextCollector } from '../features/context-collector.js';
|
|
3
4
|
let lastInjectedPersonaId = null;
|
|
5
|
+
const personaSessionKeys = new Set();
|
|
6
|
+
export function resetPersonaContextEntries() {
|
|
7
|
+
for (const sessionKey of personaSessionKeys) {
|
|
8
|
+
const entries = contextCollector.getEntries(sessionKey);
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
if (entry.source === 'persona') {
|
|
11
|
+
contextCollector.unregister(sessionKey, entry.id);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
personaSessionKeys.clear();
|
|
16
|
+
}
|
|
4
17
|
export function resetPersonaInjectorState() {
|
|
5
18
|
lastInjectedPersonaId = null;
|
|
19
|
+
resetPersonaContextEntries();
|
|
6
20
|
}
|
|
7
21
|
export function getPersonaInjectorState() {
|
|
8
22
|
return { lastInjectedPersonaId };
|
|
@@ -10,30 +24,30 @@ export function getPersonaInjectorState() {
|
|
|
10
24
|
export function registerPersonaInjector(api) {
|
|
11
25
|
api.registerHook('agent:bootstrap', (event) => {
|
|
12
26
|
const personaId = getActivePersona();
|
|
27
|
+
const sessionKey = event.context.agentId || 'default';
|
|
13
28
|
if (!personaId) {
|
|
14
29
|
return;
|
|
15
30
|
}
|
|
16
|
-
if (!event.context.bootstrapFiles) {
|
|
17
|
-
event.context.bootstrapFiles = [];
|
|
18
|
-
}
|
|
19
|
-
const alreadyInFiles = event.context.bootstrapFiles.some((f) => f.path.startsWith('omoc://persona/'));
|
|
20
|
-
if (alreadyInFiles) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
31
|
if (lastInjectedPersonaId === personaId) {
|
|
24
32
|
return;
|
|
25
33
|
}
|
|
26
34
|
try {
|
|
35
|
+
if (lastInjectedPersonaId) {
|
|
36
|
+
contextCollector.unregister(sessionKey, `persona/${lastInjectedPersonaId}`);
|
|
37
|
+
}
|
|
27
38
|
const content = readPersonaPromptSync(personaId);
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
contextCollector.register(sessionKey, {
|
|
40
|
+
id: `persona/${personaId}`,
|
|
30
41
|
content,
|
|
42
|
+
priority: 'high',
|
|
43
|
+
source: 'persona',
|
|
31
44
|
});
|
|
45
|
+
personaSessionKeys.add(sessionKey);
|
|
32
46
|
lastInjectedPersonaId = personaId;
|
|
33
|
-
api.logger.info(`[omoc] Persona
|
|
47
|
+
api.logger.info(`[omoc] Persona context registered: ${personaId}`);
|
|
34
48
|
}
|
|
35
49
|
catch (err) {
|
|
36
|
-
api.logger.error(`[omoc] Failed to
|
|
50
|
+
api.logger.error(`[omoc] Failed to register persona context ${personaId}:`, err);
|
|
37
51
|
}
|
|
38
52
|
}, {
|
|
39
53
|
name: 'oh-my-openclaw.persona-injector',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getConfig } from '../utils/config.js';
|
|
2
|
+
import { contextCollector } from '../features/context-collector.js';
|
|
2
3
|
const ORCHESTRATOR_IDS = new Set([
|
|
3
4
|
'omoc_prometheus',
|
|
4
5
|
'omoc_atlas',
|
|
@@ -50,25 +51,21 @@ export function registerTodoEnforcer(api) {
|
|
|
50
51
|
}
|
|
51
52
|
const role = classifyAgentRole(event.context.agentId);
|
|
52
53
|
const directive = DIRECTIVES[role];
|
|
54
|
+
const sessionKey = event.context.agentId || 'default';
|
|
53
55
|
if (!directive) {
|
|
54
56
|
return;
|
|
55
57
|
}
|
|
56
|
-
if (!event.context.bootstrapFiles) {
|
|
57
|
-
event.context.bootstrapFiles = [];
|
|
58
|
-
}
|
|
59
|
-
const alreadyInjected = event.context.bootstrapFiles.some((f) => f.path === 'omoc://todo-enforcer');
|
|
60
|
-
if (alreadyInjected) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
58
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
contextCollector.register(sessionKey, {
|
|
60
|
+
id: 'todo-enforcer',
|
|
66
61
|
content: directive,
|
|
62
|
+
priority: 'normal',
|
|
63
|
+
source: 'todo-enforcer',
|
|
67
64
|
});
|
|
68
|
-
api.logger.info(`[omoc] Todo enforcer
|
|
65
|
+
api.logger.info(`[omoc] Todo enforcer context registered (role: ${role})`);
|
|
69
66
|
}
|
|
70
67
|
catch (err) {
|
|
71
|
-
api.logger.error('[omoc] Todo enforcer
|
|
68
|
+
api.logger.error('[omoc] Todo enforcer context registration failed:', err);
|
|
72
69
|
}
|
|
73
70
|
}, {
|
|
74
71
|
name: 'oh-my-openclaw.todo-enforcer',
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { registerRalphCommands } from './commands/ralph-commands.js';
|
|
|
14
14
|
import { registerStatusCommands } from './commands/status-commands.js';
|
|
15
15
|
import { registerPersonaCommands } from './commands/persona-commands.js';
|
|
16
16
|
import { registerPersonaInjector } from './hooks/persona-injector.js';
|
|
17
|
+
import { registerContextInjector } from './hooks/context-injector.js';
|
|
17
18
|
import { registerSetupCli } from './cli/setup.js';
|
|
18
19
|
const registry = {
|
|
19
20
|
hooks: [],
|
|
@@ -65,6 +66,14 @@ export default function register(api) {
|
|
|
65
66
|
catch (err) {
|
|
66
67
|
api.logger.error(`[${PLUGIN_ID}] Failed to register Persona Injector:`, err);
|
|
67
68
|
}
|
|
69
|
+
try {
|
|
70
|
+
registerContextInjector(api);
|
|
71
|
+
registry.hooks.push('context-injector');
|
|
72
|
+
api.logger.info(`[${PLUGIN_ID}] Context injector hook registered (before_prompt_build)`);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
api.logger.error(`[${PLUGIN_ID}] Failed to register Context Injector:`, err);
|
|
76
|
+
}
|
|
68
77
|
try {
|
|
69
78
|
registerRalphLoop(api);
|
|
70
79
|
registry.services.push('ralph-loop');
|
package/package.json
CHANGED