@happycastle/oh-my-openclaw 0.21.1 → 0.21.3

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,6 +1,6 @@
1
1
  import { LOG_PREFIX } from '../constants.js';
2
2
  import { trackSubagentSpawn, clearSubagentTracking, getCallerSessionKey, getTrackedSubagents } from '../services/webhook-bridge.js';
3
- import { callHooksAgent } from '../utils/webhook-client.js';
3
+ import { callHooksWake } from '../utils/webhook-client.js';
4
4
  import { getConfig } from '../utils/config.js';
5
5
  const SPAWN_TOOL_NAME = 'sessions_spawn';
6
6
  function extractSpawnResult(content) {
@@ -105,17 +105,15 @@ export function registerSubagentTracker(api) {
105
105
  const wakeMessage = requesterSessionKey
106
106
  ? `[System] Sub-agent completed (runId=${runId}, requester=${requesterSessionKey}). Process the result and continue pending work.`
107
107
  : `[System] Sub-agent completed (runId=${runId}). Process the result and continue pending work.`;
108
- const targetSession = requesterSessionKey ?? callerSession;
109
- const result = await callHooksAgent(wakeMessage, { gateway_url: config.gateway_url, hooks_token: config.hooks_token }, {
110
- name: 'OmOC-SubagentComplete',
111
- deliver: false,
112
- ...(targetSession ? { sessionKey: targetSession } : {}),
113
- }, api.logger);
108
+ // Use /hooks/wake to directly inject system event into the main session
109
+ // and trigger an immediate heartbeat. /hooks/agent creates a NEW isolated
110
+ // session which is not what we want — we need the main agent to wake up.
111
+ const result = await callHooksWake(wakeMessage, { gateway_url: config.gateway_url, hooks_token: config.hooks_token }, api.logger);
114
112
  if (result.ok) {
115
- api.logger.info(`${LOG_PREFIX} Agent turn triggered from subagent_ended: runId=${runId}`);
113
+ api.logger.info(`${LOG_PREFIX} Wake triggered from subagent_ended: runId=${runId}`);
116
114
  }
117
115
  else {
118
- api.logger.warn(`${LOG_PREFIX} Agent turn from subagent_ended failed: ${result.error ?? `status ${result.status}`}`);
116
+ api.logger.warn(`${LOG_PREFIX} Wake from subagent_ended failed: ${result.error ?? `status ${result.status}`}`);
119
117
  }
120
118
  }
121
119
  }, { priority: 120 });
@@ -132,19 +130,15 @@ export function registerSubagentTracker(api) {
132
130
  const callerSession = getCallerSessionKey(matchedRunId);
133
131
  clearSubagentTracking(matchedRunId);
134
132
  api.logger.info(`${LOG_PREFIX} Sub-agent announce detected: runId=${matchedRunId} (callerSession=${callerSession ?? 'unknown'})`);
135
- // Send wake to ensure the main agent processes the announce and continues work
133
+ // Use /hooks/wake to directly inject into main session and trigger heartbeat
136
134
  const config = getConfig(api);
137
135
  if (config.webhook_bridge_enabled && config.gateway_url && config.hooks_token) {
138
- void callHooksAgent(`[System] Sub-agent completed (runId=${matchedRunId}). Process the announce result and continue any pending work.`, { gateway_url: config.gateway_url, hooks_token: config.hooks_token }, {
139
- name: 'OmOC-SubagentAnnounce',
140
- deliver: false,
141
- ...(callerSession ? { sessionKey: callerSession } : {}),
142
- }, api.logger).then((result) => {
136
+ void callHooksWake(`[System] Sub-agent completed (runId=${matchedRunId}). Process the announce result and continue any pending work.`, { gateway_url: config.gateway_url, hooks_token: config.hooks_token }, api.logger).then((result) => {
143
137
  if (result.ok) {
144
- api.logger.info(`${LOG_PREFIX} Agent turn triggered after sub-agent announce: runId=${matchedRunId}`);
138
+ api.logger.info(`${LOG_PREFIX} Wake triggered after sub-agent announce: runId=${matchedRunId}`);
145
139
  }
146
140
  else {
147
- api.logger.warn(`${LOG_PREFIX} Agent turn after announce failed: ${result.error ?? `status ${result.status}`}`);
141
+ api.logger.warn(`${LOG_PREFIX} Wake after announce failed: ${result.error ?? `status ${result.status}`}`);
148
142
  }
149
143
  });
150
144
  }
@@ -1,5 +1,6 @@
1
1
  import { TOOL_PREFIX, LOG_PREFIX } from '../constants.js';
2
2
  import { getIncompleteTodos, resetStore } from '../tools/todo/store.js';
3
+ import { setCurrentSessionKey } from '../tools/todo/session-key.js';
3
4
  import { getConfig } from '../utils/config.js';
4
5
  import { callHooksWake } from '../utils/webhook-client.js';
5
6
  const TODO_TOOL_NAMES = new Set([
@@ -89,12 +90,14 @@ export function registerSessionCleanup(api) {
89
90
  const sessionKey = ctx.sessionKey ?? ctx.sessionId ?? event.sessionId;
90
91
  if (!sessionKey)
91
92
  return;
93
+ setCurrentSessionKey(sessionKey);
92
94
  clearSession(sessionKey, api, 'new session');
93
95
  }, { priority: 190 });
94
96
  api.on('session_end', async (event, ctx) => {
95
97
  const sessionKey = ctx.sessionId ?? event.sessionId;
96
98
  if (!sessionKey)
97
99
  return;
100
+ setCurrentSessionKey(undefined);
98
101
  clearSession(sessionKey, api, 'session_end');
99
102
  }, { priority: 50 });
100
103
  }
@@ -1 +1,4 @@
1
+ export declare function setCurrentSessionKey(key: string | undefined): void;
2
+ export declare function getCurrentSessionKey(): string | undefined;
1
3
  export declare function extractSessionKey(options?: unknown): string | undefined;
4
+ export declare function resolveSessionKey(options?: unknown): string | undefined;
@@ -1,3 +1,11 @@
1
+ // Module-level state to capture session key from session_start hook
2
+ let currentSessionKey;
3
+ export function setCurrentSessionKey(key) {
4
+ currentSessionKey = key;
5
+ }
6
+ export function getCurrentSessionKey() {
7
+ return currentSessionKey;
8
+ }
1
9
  export function extractSessionKey(options) {
2
10
  if (typeof options === 'object' && options !== null) {
3
11
  const opts = options;
@@ -8,3 +16,11 @@ export function extractSessionKey(options) {
8
16
  }
9
17
  return undefined;
10
18
  }
19
+ export function resolveSessionKey(options) {
20
+ // First try extracting from options (for future OpenClaw API changes)
21
+ const extracted = extractSessionKey(options);
22
+ if (extracted !== undefined)
23
+ return extracted;
24
+ // Fall back to captured session key from session_start hook
25
+ return getCurrentSessionKey();
26
+ }
@@ -2,7 +2,7 @@ import { Type } from '@sinclair/typebox';
2
2
  import { toolResponse, toolError } from '../../utils/helpers.js';
3
3
  import { TOOL_PREFIX } from '../../constants.js';
4
4
  import { createTodo } from './store.js';
5
- import { extractSessionKey } from './session-key.js';
5
+ import { resolveSessionKey } from './session-key.js';
6
6
  const TodoCreateParamsSchema = Type.Object({
7
7
  content: Type.String({ description: 'What needs to be done' }),
8
8
  priority: Type.Optional(Type.Unsafe({
@@ -27,7 +27,7 @@ export function registerTodoCreateTool(api) {
27
27
  const content = params.content?.trim();
28
28
  if (!content)
29
29
  return toolError('content is required');
30
- const sessionKey = extractSessionKey(options);
30
+ const sessionKey = resolveSessionKey(options);
31
31
  const item = createTodo(content, params.priority, params.status, sessionKey);
32
32
  return toolResponse(JSON.stringify({ todo: item }, null, 2));
33
33
  },
@@ -2,7 +2,7 @@ import { Type } from '@sinclair/typebox';
2
2
  import { toolResponse } from '../../utils/helpers.js';
3
3
  import { TOOL_PREFIX } from '../../constants.js';
4
4
  import { listTodos } from './store.js';
5
- import { extractSessionKey } from './session-key.js';
5
+ import { resolveSessionKey } from './session-key.js';
6
6
  const TodoListParamsSchema = Type.Object({
7
7
  status: Type.Optional(Type.Unsafe({
8
8
  type: 'string',
@@ -18,7 +18,7 @@ export function registerTodoListTool(api) {
18
18
  parameters: TodoListParamsSchema,
19
19
  optional: true,
20
20
  execute: async (_callId, params, options) => {
21
- const sessionKey = extractSessionKey(options);
21
+ const sessionKey = resolveSessionKey(options);
22
22
  const items = listTodos(params.status, sessionKey);
23
23
  return toolResponse(JSON.stringify({ todos: items, count: items.length }, null, 2));
24
24
  },
@@ -2,7 +2,7 @@ import { Type } from '@sinclair/typebox';
2
2
  import { toolResponse, toolError } from '../../utils/helpers.js';
3
3
  import { TOOL_PREFIX } from '../../constants.js';
4
4
  import { updateTodo } from './store.js';
5
- import { extractSessionKey } from './session-key.js';
5
+ import { resolveSessionKey } from './session-key.js';
6
6
  const TodoUpdateParamsSchema = Type.Object({
7
7
  id: Type.String({ description: 'Todo ID (e.g. todo-1)' }),
8
8
  status: Type.Optional(Type.Unsafe({
@@ -31,7 +31,7 @@ export function registerTodoUpdateTool(api) {
31
31
  if (!params.status && !params.priority && !params.content) {
32
32
  return toolError('At least one of status, priority, or content must be provided');
33
33
  }
34
- const sessionKey = extractSessionKey(options);
34
+ const sessionKey = resolveSessionKey(options);
35
35
  const updated = updateTodo(id, {
36
36
  status: params.status,
37
37
  priority: params.priority,
@@ -2,7 +2,7 @@
2
2
  "id": "oh-my-openclaw",
3
3
  "name": "Oh-My-OpenClaw",
4
4
  "description": "Multi-agent orchestration plugin — 11 agents, category-based model routing, todo enforcer, ralph loop, agent setup CLI, and custom tools",
5
- "version": "0.21.1",
5
+ "version": "0.21.3",
6
6
  "skills": [
7
7
  "skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happycastle/oh-my-openclaw",
3
- "version": "0.21.1",
3
+ "version": "0.21.3",
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",