@happycastle/oh-my-openclaw 0.12.2 → 0.12.4

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.
@@ -1,29 +1,39 @@
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
+ import { resetPersonaInjectorState } from '../hooks/persona-injector.js';
4
+ function getDisplayName(personaId) {
5
+ const persona = listPersonas().find((p) => p.id === personaId);
6
+ return persona ? `${persona.emoji} ${persona.displayName}` : personaId;
7
+ }
3
8
  export function registerPersonaCommands(api) {
4
9
  api.registerCommand({
5
10
  name: 'omoc',
6
11
  description: 'OmOC mode — activate, switch, or list personas',
7
12
  acceptsArgs: true,
8
13
  handler: async (ctx) => {
14
+ api.logger.info(`[omoc] /omoc command received — raw ctx.args: ${JSON.stringify(ctx.args)}, ctx keys: ${JSON.stringify(Object.keys(ctx))}`);
9
15
  const args = (ctx.args ?? '').trim().toLowerCase();
16
+ api.logger.info(`[omoc] Parsed args: "${args}" (length: ${args.length})`);
10
17
  if (!args) {
18
+ const previousId = getActivePersona();
19
+ resetPersonaInjectorState();
11
20
  setActivePersona(DEFAULT_PERSONA_ID);
12
- const personas = listPersonas();
13
- const defaultPersona = personas.find((p) => p.id === DEFAULT_PERSONA_ID);
14
- const name = defaultPersona
15
- ? `${defaultPersona.emoji} ${defaultPersona.displayName}`
16
- : DEFAULT_PERSONA_ID;
21
+ const name = getDisplayName(DEFAULT_PERSONA_ID);
22
+ const switchNote = previousId && previousId !== DEFAULT_PERSONA_ID
23
+ ? `\n\nSwitched from **${getDisplayName(previousId)}**.`
24
+ : '';
17
25
  return {
18
- text: `# OmOC Mode: ON\n\nActive persona: **${name}**\n\nThe persona prompt will be injected into all new agent sessions.\n\nUse \`/omoc list\` to see available personas, or \`/omoc <name>\` to switch.`,
26
+ 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.`,
19
27
  };
20
28
  }
21
29
  if (args === 'off') {
22
30
  const wasActive = getActivePersona();
31
+ const wasName = wasActive ? getDisplayName(wasActive) : null;
23
32
  resetPersonaState();
33
+ resetPersonaInjectorState();
24
34
  return {
25
- text: wasActive
26
- ? `# OmOC Mode: OFF\n\nPersona **${wasActive}** deactivated. Agent sessions will use default behavior.`
35
+ text: wasName
36
+ ? `# OmOC Mode: OFF\n\nPersona **${wasName}** deactivated. Applied immediately — your next message will use default behavior.`
27
37
  : '# OmOC Mode: OFF\n\nNo persona was active.',
28
38
  };
29
39
  }
@@ -38,7 +48,7 @@ export function registerPersonaCommands(api) {
38
48
  text: [
39
49
  '# OmOC Personas',
40
50
  '',
41
- `Active: ${activeId ? `**${activeId}**` : '_none_'}`,
51
+ `Active: ${activeId ? `**${getDisplayName(activeId)}**` : '_none_'}`,
42
52
  '',
43
53
  '| | Command | Name | Role |',
44
54
  '|---|---------|------|------|',
@@ -56,14 +66,16 @@ export function registerPersonaCommands(api) {
56
66
  text: `# Unknown Persona: "${args}"\n\nAvailable personas: ${available}\n\nUse \`/omoc list\` for details.`,
57
67
  };
58
68
  }
69
+ const previousId = getActivePersona();
70
+ resetPersonaInjectorState();
59
71
  setActivePersona(resolvedId);
60
- const personas = listPersonas();
61
- const switched = personas.find((p) => p.id === resolvedId);
62
- const displayName = switched
63
- ? `${switched.emoji} ${switched.displayName}`
64
- : resolvedId;
72
+ const displayName = getDisplayName(resolvedId);
73
+ const switched = listPersonas().find((p) => p.id === resolvedId);
74
+ const switchNote = previousId && previousId !== resolvedId
75
+ ? `\n\nSwitched from **${getDisplayName(previousId)}**.`
76
+ : '';
65
77
  return {
66
- text: `# Persona Switched\n\nActive persona: **${displayName}**\n\nThe ${switched?.theme ?? 'persona'} prompt will be injected into all new agent sessions.`,
78
+ text: `# Persona Switched\n\nActive persona: **${displayName}**${switchNote}\n\nApplied immediately — your next message will use the ${switched?.theme ?? 'persona'} prompt.`,
67
79
  };
68
80
  },
69
81
  });
@@ -26,6 +26,11 @@ export function registerPersonaInjector(api) {
26
26
  const personaId = getActivePersona();
27
27
  const sessionKey = event.context.agentId || 'default';
28
28
  if (!personaId) {
29
+ if (lastInjectedPersonaId) {
30
+ contextCollector.unregister(sessionKey, `persona/${lastInjectedPersonaId}`);
31
+ lastInjectedPersonaId = null;
32
+ api.logger.info(`[omoc] Persona context cleared for ${sessionKey}`);
33
+ }
29
34
  return;
30
35
  }
31
36
  if (lastInjectedPersonaId === personaId) {
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
- let initialized = false;
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
- if (initialized) {
29
- api.logger.warn(`[${PLUGIN_ID}] Plugin already initialized — skipping duplicate register() call`);
30
- return;
31
- }
32
- initialized = true;
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(api);
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(api);
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(api);
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(api);
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(api);
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(api);
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happycastle/oh-my-openclaw",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
4
4
  "description": "Oh-My-OpenClaw plugin — multi-agent orchestration, todo enforcer, ralph loop, and custom tools for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",