@happycastle/oh-my-openclaw 0.12.3 → 0.12.5
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/commands/persona-commands.js +21 -18
- package/dist/hooks/context-injector.js +13 -12
- package/dist/hooks/persona-injector.d.ts +0 -5
- package/dist/hooks/persona-injector.js +42 -49
- package/dist/index.js +37 -13
- package/dist/types.d.ts +18 -0
- package/package.json +1 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { getActivePersona, setActivePersona, resetPersonaState } from '../utils/persona-state.js';
|
|
2
2
|
import { resolvePersonaId, listPersonas, DEFAULT_PERSONA_ID } from '../agents/persona-prompts.js';
|
|
3
|
-
|
|
3
|
+
function getDisplayName(personaId) {
|
|
4
|
+
const persona = listPersonas().find((p) => p.id === personaId);
|
|
5
|
+
return persona ? `${persona.emoji} ${persona.displayName}` : personaId;
|
|
6
|
+
}
|
|
4
7
|
export function registerPersonaCommands(api) {
|
|
5
8
|
api.registerCommand({
|
|
6
9
|
name: 'omoc',
|
|
@@ -11,23 +14,23 @@ export function registerPersonaCommands(api) {
|
|
|
11
14
|
const args = (ctx.args ?? '').trim().toLowerCase();
|
|
12
15
|
api.logger.info(`[omoc] Parsed args: "${args}" (length: ${args.length})`);
|
|
13
16
|
if (!args) {
|
|
17
|
+
const previousId = getActivePersona();
|
|
14
18
|
setActivePersona(DEFAULT_PERSONA_ID);
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
: DEFAULT_PERSONA_ID;
|
|
19
|
+
const name = getDisplayName(DEFAULT_PERSONA_ID);
|
|
20
|
+
const switchNote = previousId && previousId !== DEFAULT_PERSONA_ID
|
|
21
|
+
? `\n\nSwitched from **${getDisplayName(previousId)}**.`
|
|
22
|
+
: '';
|
|
20
23
|
return {
|
|
21
|
-
text: `# OmOC Mode: ON\n\nActive persona: **${name}
|
|
24
|
+
text: `# OmOC Mode: ON\n\nActive persona: **${name}**${switchNote}\n\nApplied immediately — your next message will use this persona.\n\nUse \`/omoc list\` to see available personas, or \`/omoc <name>\` to switch.`,
|
|
22
25
|
};
|
|
23
26
|
}
|
|
24
27
|
if (args === 'off') {
|
|
25
28
|
const wasActive = getActivePersona();
|
|
29
|
+
const wasName = wasActive ? getDisplayName(wasActive) : null;
|
|
26
30
|
resetPersonaState();
|
|
27
|
-
resetPersonaInjectorState();
|
|
28
31
|
return {
|
|
29
|
-
text:
|
|
30
|
-
? `# OmOC Mode: OFF\n\nPersona **${
|
|
32
|
+
text: wasName
|
|
33
|
+
? `# OmOC Mode: OFF\n\nPersona **${wasName}** deactivated. Applied immediately — your next message will use default behavior.`
|
|
31
34
|
: '# OmOC Mode: OFF\n\nNo persona was active.',
|
|
32
35
|
};
|
|
33
36
|
}
|
|
@@ -42,7 +45,7 @@ export function registerPersonaCommands(api) {
|
|
|
42
45
|
text: [
|
|
43
46
|
'# OmOC Personas',
|
|
44
47
|
'',
|
|
45
|
-
`Active: ${activeId ? `**${activeId}**` : '_none_'}`,
|
|
48
|
+
`Active: ${activeId ? `**${getDisplayName(activeId)}**` : '_none_'}`,
|
|
46
49
|
'',
|
|
47
50
|
'| | Command | Name | Role |',
|
|
48
51
|
'|---|---------|------|------|',
|
|
@@ -60,15 +63,15 @@ export function registerPersonaCommands(api) {
|
|
|
60
63
|
text: `# Unknown Persona: "${args}"\n\nAvailable personas: ${available}\n\nUse \`/omoc list\` for details.`,
|
|
61
64
|
};
|
|
62
65
|
}
|
|
63
|
-
|
|
66
|
+
const previousId = getActivePersona();
|
|
64
67
|
setActivePersona(resolvedId);
|
|
65
|
-
const
|
|
66
|
-
const switched =
|
|
67
|
-
const
|
|
68
|
-
?
|
|
69
|
-
:
|
|
68
|
+
const displayName = getDisplayName(resolvedId);
|
|
69
|
+
const switched = listPersonas().find((p) => p.id === resolvedId);
|
|
70
|
+
const switchNote = previousId && previousId !== resolvedId
|
|
71
|
+
? `\n\nSwitched from **${getDisplayName(previousId)}**.`
|
|
72
|
+
: '';
|
|
70
73
|
return {
|
|
71
|
-
text: `# Persona Switched\n\nActive persona: **${displayName}
|
|
74
|
+
text: `# Persona Switched\n\nActive persona: **${displayName}**${switchNote}\n\nApplied immediately — your next message will use the ${switched?.theme ?? 'persona'} prompt.`,
|
|
72
75
|
};
|
|
73
76
|
},
|
|
74
77
|
});
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { contextCollector } from '../features/context-collector.js';
|
|
2
2
|
export function registerContextInjector(api) {
|
|
3
|
-
api.
|
|
4
|
-
|
|
3
|
+
// Use the typed hook system (api.on) instead of api.registerHook.
|
|
4
|
+
// api.registerHook registers into the internal hook system which does NOT
|
|
5
|
+
// trigger before_prompt_build — only hookRunner (typed hooks) does.
|
|
6
|
+
api.on('before_prompt_build', (_event, ctx) => {
|
|
7
|
+
const sessionKey = ctx.agentId || 'default';
|
|
5
8
|
if (!contextCollector.hasEntries(sessionKey)) {
|
|
6
|
-
return
|
|
9
|
+
return;
|
|
7
10
|
}
|
|
8
11
|
const entryCount = contextCollector.getEntries(sessionKey).length;
|
|
9
12
|
const collectedContext = contextCollector.collectAsString(sessionKey);
|
|
10
13
|
if (!collectedContext) {
|
|
11
|
-
return
|
|
14
|
+
return;
|
|
12
15
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}, {
|
|
18
|
-
|
|
19
|
-
description: 'Unified context injection from ContextCollector into prependContext',
|
|
20
|
-
});
|
|
16
|
+
api.logger.info(`[omoc] Context injected via before_prompt_build: ${entryCount} entries for ${sessionKey}`);
|
|
17
|
+
return {
|
|
18
|
+
prependContext: collectedContext,
|
|
19
|
+
};
|
|
20
|
+
}, { priority: 50 } // Lower priority than persona (100) — persona goes first
|
|
21
|
+
);
|
|
21
22
|
}
|
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
import { OmocPluginApi } from '../types.js';
|
|
2
|
-
export declare function resetPersonaContextEntries(): void;
|
|
3
|
-
export declare function resetPersonaInjectorState(): void;
|
|
4
|
-
export declare function getPersonaInjectorState(): {
|
|
5
|
-
lastInjectedPersonaId: string | null;
|
|
6
|
-
};
|
|
7
2
|
export declare function registerPersonaInjector(api: OmocPluginApi): void;
|
|
@@ -1,61 +1,54 @@
|
|
|
1
1
|
import { getActivePersona } from '../utils/persona-state.js';
|
|
2
|
-
import { readPersonaPromptSync } from '../agents/persona-prompts.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
import { readPersonaPromptSync, resolvePersonaId } from '../agents/persona-prompts.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the effective persona ID.
|
|
5
|
+
*
|
|
6
|
+
* Priority:
|
|
7
|
+
* 1. Manually set persona via /omoc command (getActivePersona())
|
|
8
|
+
* 2. agentId from the hook context (set by OpenClaw core)
|
|
9
|
+
* 3. null — no persona to inject
|
|
10
|
+
*/
|
|
11
|
+
function resolveEffectivePersona(ctx) {
|
|
12
|
+
const manual = getActivePersona();
|
|
13
|
+
if (manual) {
|
|
14
|
+
const resolved = resolvePersonaId(manual);
|
|
15
|
+
if (resolved)
|
|
16
|
+
return { personaId: resolved, source: 'manual' };
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return { lastInjectedPersonaId };
|
|
18
|
+
const agentId = ctx.agentId;
|
|
19
|
+
if (!agentId)
|
|
20
|
+
return null;
|
|
21
|
+
const resolved = resolvePersonaId(agentId);
|
|
22
|
+
if (!resolved)
|
|
23
|
+
return null;
|
|
24
|
+
return { personaId: resolved, source: 'auto' };
|
|
23
25
|
}
|
|
24
26
|
export function registerPersonaInjector(api) {
|
|
25
|
-
api.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (lastInjectedPersonaId === personaId) {
|
|
27
|
+
// Use the typed hook system (api.on) for before_prompt_build.
|
|
28
|
+
// This directly injects into the system prompt via prependContext,
|
|
29
|
+
// which is more reliable than bootstrapFiles via agent:bootstrap.
|
|
30
|
+
//
|
|
31
|
+
// api.registerHook('before_prompt_build', ...) registers into the internal
|
|
32
|
+
// hook system which does NOT trigger before_prompt_build — only hookRunner
|
|
33
|
+
// (typed hooks via api.on) does.
|
|
34
|
+
api.on('before_prompt_build', (_event, ctx) => {
|
|
35
|
+
const result = resolveEffectivePersona(ctx);
|
|
36
|
+
if (!result) {
|
|
37
|
+
api.logger.info(`[omoc] Persona injector: no persona resolved (agentId=${ctx.agentId ?? 'none'}, manual=${getActivePersona() ?? 'none'})`);
|
|
37
38
|
return;
|
|
38
39
|
}
|
|
40
|
+
const { personaId, source } = result;
|
|
39
41
|
try {
|
|
40
|
-
if (lastInjectedPersonaId) {
|
|
41
|
-
contextCollector.unregister(sessionKey, `persona/${lastInjectedPersonaId}`);
|
|
42
|
-
}
|
|
43
42
|
const content = readPersonaPromptSync(personaId);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
content,
|
|
47
|
-
|
|
48
|
-
source: 'persona',
|
|
49
|
-
});
|
|
50
|
-
personaSessionKeys.add(sessionKey);
|
|
51
|
-
lastInjectedPersonaId = personaId;
|
|
52
|
-
api.logger.info(`[omoc] Persona context registered: ${personaId}`);
|
|
43
|
+
api.logger.info(`[omoc] Persona injected via before_prompt_build: ${personaId} (${source}, agentId=${ctx.agentId ?? 'none'})`);
|
|
44
|
+
return {
|
|
45
|
+
prependContext: content,
|
|
46
|
+
};
|
|
53
47
|
}
|
|
54
48
|
catch (err) {
|
|
55
|
-
api.logger.error(`[omoc] Failed to
|
|
49
|
+
api.logger.error(`[omoc] Failed to inject persona ${personaId}:`, err);
|
|
50
|
+
return;
|
|
56
51
|
}
|
|
57
|
-
}, {
|
|
58
|
-
|
|
59
|
-
description: 'Injects active persona prompt once per persona change',
|
|
60
|
-
});
|
|
52
|
+
}, { priority: 100 } // High priority — persona prompt should be prepended first
|
|
53
|
+
);
|
|
61
54
|
}
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,17 @@ import { registerPersonaCommands } from './commands/persona-commands.js';
|
|
|
16
16
|
import { registerPersonaInjector } from './hooks/persona-injector.js';
|
|
17
17
|
import { registerContextInjector } from './hooks/context-injector.js';
|
|
18
18
|
import { registerSetupCli } from './cli/setup.js';
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Generation counter for multi-registration handling.
|
|
21
|
+
*
|
|
22
|
+
* OpenClaw may call register() multiple times with different api instances.
|
|
23
|
+
* Only the LAST call's api connects to the live hook dispatcher.
|
|
24
|
+
* We track a generation number so that hooks from stale registrations
|
|
25
|
+
* become no-ops, solving both:
|
|
26
|
+
* - hooks not firing (stale api from early call)
|
|
27
|
+
* - triple firing (all 3 registrations' hooks executing)
|
|
28
|
+
*/
|
|
29
|
+
let generation = 0;
|
|
20
30
|
const registry = {
|
|
21
31
|
hooks: [],
|
|
22
32
|
services: [],
|
|
@@ -24,16 +34,30 @@ const registry = {
|
|
|
24
34
|
commands: [],
|
|
25
35
|
cli: [],
|
|
26
36
|
};
|
|
37
|
+
function guardedApi(api, gen) {
|
|
38
|
+
return {
|
|
39
|
+
...api,
|
|
40
|
+
registerHook: (event, handler, meta) => {
|
|
41
|
+
api.registerHook(event, (evt) => {
|
|
42
|
+
if (gen !== generation)
|
|
43
|
+
return evt;
|
|
44
|
+
return handler(evt);
|
|
45
|
+
}, meta);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
27
49
|
export default function register(api) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
const gen = ++generation;
|
|
51
|
+
registry.hooks = [];
|
|
52
|
+
registry.services = [];
|
|
53
|
+
registry.tools = [];
|
|
54
|
+
registry.commands = [];
|
|
55
|
+
registry.cli = [];
|
|
33
56
|
const config = getConfig(api);
|
|
34
|
-
api.logger.info(`[${PLUGIN_ID}] Initializing plugin v${VERSION}`);
|
|
57
|
+
api.logger.info(`[${PLUGIN_ID}] Initializing plugin v${VERSION} (register call #${gen})`);
|
|
58
|
+
const guarded = guardedApi(api, gen);
|
|
35
59
|
try {
|
|
36
|
-
registerTodoEnforcer(
|
|
60
|
+
registerTodoEnforcer(guarded);
|
|
37
61
|
registry.hooks.push('todo-enforcer');
|
|
38
62
|
api.logger.info(`[${PLUGIN_ID}] Todo Enforcer hook registered (enabled: ${config.todo_enforcer_enabled})`);
|
|
39
63
|
}
|
|
@@ -41,7 +65,7 @@ export default function register(api) {
|
|
|
41
65
|
api.logger.error(`[${PLUGIN_ID}] Failed to register Todo Enforcer:`, err);
|
|
42
66
|
}
|
|
43
67
|
try {
|
|
44
|
-
registerCommentChecker(
|
|
68
|
+
registerCommentChecker(guarded);
|
|
45
69
|
registry.hooks.push('comment-checker');
|
|
46
70
|
api.logger.info(`[${PLUGIN_ID}] Comment Checker hook registered (enabled: ${config.comment_checker_enabled})`);
|
|
47
71
|
}
|
|
@@ -49,7 +73,7 @@ export default function register(api) {
|
|
|
49
73
|
api.logger.error(`[${PLUGIN_ID}] Failed to register Comment Checker:`, err);
|
|
50
74
|
}
|
|
51
75
|
try {
|
|
52
|
-
registerMessageMonitor(
|
|
76
|
+
registerMessageMonitor(guarded);
|
|
53
77
|
registry.hooks.push('message-monitor', 'message-received-monitor');
|
|
54
78
|
api.logger.info(`[${PLUGIN_ID}] Message Monitor hook registered`);
|
|
55
79
|
}
|
|
@@ -57,7 +81,7 @@ export default function register(api) {
|
|
|
57
81
|
api.logger.error(`[${PLUGIN_ID}] Failed to register Message Monitor:`, err);
|
|
58
82
|
}
|
|
59
83
|
try {
|
|
60
|
-
registerStartupHook(
|
|
84
|
+
registerStartupHook(guarded);
|
|
61
85
|
registry.hooks.push('gateway-startup');
|
|
62
86
|
api.logger.info(`[${PLUGIN_ID}] Gateway startup hook registered`);
|
|
63
87
|
}
|
|
@@ -65,7 +89,7 @@ export default function register(api) {
|
|
|
65
89
|
api.logger.error(`[${PLUGIN_ID}] Failed to register startup hook:`, err);
|
|
66
90
|
}
|
|
67
91
|
try {
|
|
68
|
-
registerPersonaInjector(
|
|
92
|
+
registerPersonaInjector(guarded);
|
|
69
93
|
registry.hooks.push('persona-injector');
|
|
70
94
|
api.logger.info(`[${PLUGIN_ID}] Persona injector hook registered`);
|
|
71
95
|
}
|
|
@@ -73,7 +97,7 @@ export default function register(api) {
|
|
|
73
97
|
api.logger.error(`[${PLUGIN_ID}] Failed to register Persona Injector:`, err);
|
|
74
98
|
}
|
|
75
99
|
try {
|
|
76
|
-
registerContextInjector(
|
|
100
|
+
registerContextInjector(guarded);
|
|
77
101
|
registry.hooks.push('context-injector');
|
|
78
102
|
api.logger.info(`[${PLUGIN_ID}] Context injector hook registered (before_prompt_build)`);
|
|
79
103
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -122,4 +122,22 @@ export interface OmocPluginApi {
|
|
|
122
122
|
}) => void | Promise<void>, opts?: {
|
|
123
123
|
commands?: string[];
|
|
124
124
|
}) => void;
|
|
125
|
+
on: <TEvent = unknown, TResult = unknown>(hookName: string, handler: (event: TEvent, ctx: TypedHookContext) => TResult | Promise<TResult> | void, opts?: {
|
|
126
|
+
priority?: number;
|
|
127
|
+
}) => void;
|
|
128
|
+
}
|
|
129
|
+
export interface TypedHookContext {
|
|
130
|
+
agentId?: string;
|
|
131
|
+
sessionKey?: string;
|
|
132
|
+
sessionId?: string;
|
|
133
|
+
workspaceDir?: string;
|
|
134
|
+
messageProvider?: unknown;
|
|
135
|
+
}
|
|
136
|
+
export interface BeforePromptBuildResult {
|
|
137
|
+
systemPrompt?: string;
|
|
138
|
+
prependContext?: string;
|
|
139
|
+
}
|
|
140
|
+
export interface BeforePromptBuildEvent {
|
|
141
|
+
prompt?: string;
|
|
142
|
+
messages?: unknown[];
|
|
125
143
|
}
|
package/package.json
CHANGED