@happycastle/oh-my-openclaw 0.20.1 → 0.21.1
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/todo-commands.d.ts +2 -0
- package/dist/commands/todo-commands.js +57 -0
- package/dist/hooks/subagent-tracker.js +16 -7
- package/dist/hooks/todo-reminder.js +6 -1
- package/dist/index.js +6 -0
- package/dist/services/webhook-bridge.js +30 -11
- package/dist/tools/todo/todo-update.js +6 -1
- package/dist/utils/webhook-client.d.ts +5 -1
- package/dist/utils/webhook-client.js +6 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { _sessions } from '../tools/todo/store.js';
|
|
2
|
+
const STATUS_EMOJI = {
|
|
3
|
+
pending: '⏳',
|
|
4
|
+
in_progress: '🔄',
|
|
5
|
+
completed: '✅',
|
|
6
|
+
cancelled: '❌',
|
|
7
|
+
};
|
|
8
|
+
function formatTodoList(todos) {
|
|
9
|
+
if (todos.length === 0) {
|
|
10
|
+
return '📋 No todos found.';
|
|
11
|
+
}
|
|
12
|
+
const lines = ['# 📋 Todo List\n'];
|
|
13
|
+
// Status summary
|
|
14
|
+
const statusOrder = ['in_progress', 'pending', 'completed', 'cancelled'];
|
|
15
|
+
const counts = {};
|
|
16
|
+
for (const todo of todos) {
|
|
17
|
+
counts[todo.status] = (counts[todo.status] ?? 0) + 1;
|
|
18
|
+
}
|
|
19
|
+
const summaryParts = [];
|
|
20
|
+
for (const status of statusOrder) {
|
|
21
|
+
const count = counts[status];
|
|
22
|
+
if (count && count > 0) {
|
|
23
|
+
summaryParts.push(`${STATUS_EMOJI[status]} ${status}: ${count}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
lines.push(summaryParts.join(' | '));
|
|
27
|
+
lines.push('');
|
|
28
|
+
// List each todo
|
|
29
|
+
for (const todo of todos) {
|
|
30
|
+
const emoji = STATUS_EMOJI[todo.status] ?? '❓';
|
|
31
|
+
const priority = todo.priority !== 'medium' ? ` [${todo.priority}]` : '';
|
|
32
|
+
lines.push(`${emoji}${priority} **${todo.id}**: ${todo.content}`);
|
|
33
|
+
}
|
|
34
|
+
return lines.join('\n');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Collect todos from ALL session stores.
|
|
38
|
+
* Command handlers don't receive session context, so we aggregate
|
|
39
|
+
* across every store to give the user a complete view.
|
|
40
|
+
*/
|
|
41
|
+
function collectAllTodos() {
|
|
42
|
+
const all = [];
|
|
43
|
+
for (const store of _sessions.values()) {
|
|
44
|
+
all.push(...store.todos);
|
|
45
|
+
}
|
|
46
|
+
return all;
|
|
47
|
+
}
|
|
48
|
+
export function registerTodoCommands(api) {
|
|
49
|
+
api.registerCommand({
|
|
50
|
+
name: 'todos',
|
|
51
|
+
description: 'Show current todo list (auto-reply, no AI invocation)',
|
|
52
|
+
handler: () => {
|
|
53
|
+
const todos = collectAllTodos();
|
|
54
|
+
return { text: formatTodoList(todos) };
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -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 {
|
|
3
|
+
import { callHooksAgent } 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,12 +105,17 @@ 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
|
|
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);
|
|
109
114
|
if (result.ok) {
|
|
110
|
-
api.logger.info(`${LOG_PREFIX}
|
|
115
|
+
api.logger.info(`${LOG_PREFIX} Agent turn triggered from subagent_ended: runId=${runId}`);
|
|
111
116
|
}
|
|
112
117
|
else {
|
|
113
|
-
api.logger.warn(`${LOG_PREFIX}
|
|
118
|
+
api.logger.warn(`${LOG_PREFIX} Agent turn from subagent_ended failed: ${result.error ?? `status ${result.status}`}`);
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
}, { priority: 120 });
|
|
@@ -130,12 +135,16 @@ export function registerSubagentTracker(api) {
|
|
|
130
135
|
// Send wake to ensure the main agent processes the announce and continues work
|
|
131
136
|
const config = getConfig(api);
|
|
132
137
|
if (config.webhook_bridge_enabled && config.gateway_url && config.hooks_token) {
|
|
133
|
-
void
|
|
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) => {
|
|
134
143
|
if (result.ok) {
|
|
135
|
-
api.logger.info(`${LOG_PREFIX}
|
|
144
|
+
api.logger.info(`${LOG_PREFIX} Agent turn triggered after sub-agent announce: runId=${matchedRunId}`);
|
|
136
145
|
}
|
|
137
146
|
else {
|
|
138
|
-
api.logger.warn(`${LOG_PREFIX}
|
|
147
|
+
api.logger.warn(`${LOG_PREFIX} Agent turn after announce failed: ${result.error ?? `status ${result.status}`}`);
|
|
139
148
|
}
|
|
140
149
|
});
|
|
141
150
|
}
|
|
@@ -63,7 +63,12 @@ export function registerAgentEndReminder(api) {
|
|
|
63
63
|
}
|
|
64
64
|
const config = getConfig(api);
|
|
65
65
|
if (config.webhook_bridge_enabled && config.hooks_token) {
|
|
66
|
-
|
|
66
|
+
if (!sessionKey) {
|
|
67
|
+
api.logger.warn(`${LOG_PREFIX} No sessionKey available for wake after agent_end — skipping to avoid new session creation`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
callHooksWake(`⚠️ Agent ended with ${incomplete.length} incomplete todo(s). Resume work.`, { gateway_url: config.gateway_url, hooks_token: config.hooks_token }, api.logger, { sessionKey }).catch(() => { });
|
|
71
|
+
}
|
|
67
72
|
}
|
|
68
73
|
api.logger.warn(`${LOG_PREFIX} Agent ended with ${incomplete.length} incomplete todo(s)`);
|
|
69
74
|
}
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { registerWebSearchTool } from './tools/web-search.js';
|
|
|
17
17
|
import { registerRalphCommands } from './commands/ralph-commands.js';
|
|
18
18
|
import { registerStatusCommands } from './commands/status-commands.js';
|
|
19
19
|
import { registerPersonaCommands } from './commands/persona-commands.js';
|
|
20
|
+
import { registerTodoCommands } from './commands/todo-commands.js';
|
|
20
21
|
import { registerContextInjector } from './hooks/context-injector.js';
|
|
21
22
|
import { registerGuardrailInjector } from './hooks/guardrail-injector.js';
|
|
22
23
|
import { registerSessionSync } from './hooks/session-sync.js';
|
|
@@ -181,6 +182,11 @@ export default function register(api) {
|
|
|
181
182
|
registry.commands.push('omoc_health', 'omoc_config');
|
|
182
183
|
api.logger.info(`[${PLUGIN_ID}] Status commands registered (omoc_health, omoc_config)`);
|
|
183
184
|
});
|
|
185
|
+
safeRegister(api, 'todo-commands', 'command', () => {
|
|
186
|
+
registerTodoCommands(api);
|
|
187
|
+
registry.commands.push('todos');
|
|
188
|
+
api.logger.info(`[${PLUGIN_ID}] Todo commands registered (/todos)`);
|
|
189
|
+
});
|
|
184
190
|
initPersonaState(api);
|
|
185
191
|
safeRegister(api, 'persona-commands', 'command', () => {
|
|
186
192
|
registerPersonaCommands(api);
|
|
@@ -84,17 +84,36 @@ async function checkStaleSubagents(api) {
|
|
|
84
84
|
}
|
|
85
85
|
if (stale.length === 0)
|
|
86
86
|
return;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
87
|
+
// Group stale entries by callerSessionKey and send separate wakes per group
|
|
88
|
+
const grouped = new Map();
|
|
89
|
+
for (const s of stale) {
|
|
90
|
+
const key = s.callerSessionKey ?? '__default__';
|
|
91
|
+
const group = grouped.get(key) ?? [];
|
|
92
|
+
group.push(s);
|
|
93
|
+
grouped.set(key, group);
|
|
94
|
+
}
|
|
95
|
+
for (const [sessionKey, entries] of grouped) {
|
|
96
|
+
const details = entries
|
|
97
|
+
.map((s) => ` - runId=${s.runId} task="${s.task.substring(0, 80)}" (${Math.round((now - s.spawnedAt) / 60000)}m ago)`)
|
|
98
|
+
.join('\n');
|
|
99
|
+
const message = `[OmOC Sub-agent Alert] ${entries.length} sub-agent(s) may have completed without announce:\n${details}\n\n` +
|
|
100
|
+
`Check sub-agent status with \`/subagents list\` or \`/subagents info <id>\`. ` +
|
|
101
|
+
`If completed, collect results and proceed. If still running, wait.`;
|
|
102
|
+
const targetSession = sessionKey !== '__default__' ? sessionKey : undefined;
|
|
103
|
+
if (!targetSession) {
|
|
104
|
+
api.logger.warn(`${LOG_PREFIX} No sessionKey available for stale sub-agent alert (${entries.length} stale) — cleaning up without wake to avoid new session creation`);
|
|
105
|
+
// Still clean up tracking to prevent infinite reprocessing
|
|
106
|
+
for (const s of entries) {
|
|
107
|
+
trackedSubagents.delete(s.runId);
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const result = await callHooksWake(message, webhookConfig, api.logger, { sessionKey: targetSession });
|
|
112
|
+
if (result.ok) {
|
|
113
|
+
api.logger.info(`${LOG_PREFIX} Stale sub-agent alert sent via hooks/wake (${entries.length} stale, target=${targetSession})`);
|
|
114
|
+
for (const s of entries) {
|
|
115
|
+
trackedSubagents.delete(s.runId);
|
|
116
|
+
}
|
|
98
117
|
}
|
|
99
118
|
}
|
|
100
119
|
}
|
|
@@ -39,7 +39,12 @@ export function registerTodoUpdateTool(api) {
|
|
|
39
39
|
}, sessionKey);
|
|
40
40
|
if (!updated)
|
|
41
41
|
return toolResponse(JSON.stringify({ error: 'todo_not_found', id }));
|
|
42
|
-
|
|
42
|
+
// Include completion notice when status changes to completed
|
|
43
|
+
const result = { todo: updated };
|
|
44
|
+
if (params.status === 'completed') {
|
|
45
|
+
result.notice = `✅ Todo ${updated.id} completed: ${updated.content}`;
|
|
46
|
+
}
|
|
47
|
+
return toolResponse(JSON.stringify(result, null, 2));
|
|
43
48
|
},
|
|
44
49
|
});
|
|
45
50
|
}
|
|
@@ -8,6 +8,10 @@ export interface HooksAgentOptions {
|
|
|
8
8
|
sessionKey?: string;
|
|
9
9
|
deliver?: boolean;
|
|
10
10
|
}
|
|
11
|
+
export interface HooksWakeOptions {
|
|
12
|
+
/** Target a specific session instead of creating a new one */
|
|
13
|
+
sessionKey?: string;
|
|
14
|
+
}
|
|
11
15
|
export interface WebhookResult {
|
|
12
16
|
ok: boolean;
|
|
13
17
|
status?: number;
|
|
@@ -15,7 +19,7 @@ export interface WebhookResult {
|
|
|
15
19
|
}
|
|
16
20
|
export declare function callHooksWake(text: string, config: WebhookConfig, logger?: {
|
|
17
21
|
warn: (...args: unknown[]) => void;
|
|
18
|
-
}): Promise<WebhookResult>;
|
|
22
|
+
}, options?: HooksWakeOptions): Promise<WebhookResult>;
|
|
19
23
|
export declare function callHooksAgent(message: string, config: WebhookConfig, options?: HooksAgentOptions, logger?: {
|
|
20
24
|
warn: (...args: unknown[]) => void;
|
|
21
25
|
}): Promise<WebhookResult>;
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { LOG_PREFIX } from '../constants.js';
|
|
2
|
-
export async function callHooksWake(text, config, logger) {
|
|
2
|
+
export async function callHooksWake(text, config, logger, options) {
|
|
3
3
|
if (!config.hooks_token) {
|
|
4
4
|
return { ok: false, error: 'hooks_token not configured' };
|
|
5
5
|
}
|
|
6
6
|
try {
|
|
7
|
+
const payload = { text, mode: 'now' };
|
|
8
|
+
if (options?.sessionKey) {
|
|
9
|
+
payload.sessionKey = options.sessionKey;
|
|
10
|
+
}
|
|
7
11
|
const res = await fetch(`${config.gateway_url}/hooks/wake`, {
|
|
8
12
|
method: 'POST',
|
|
9
13
|
headers: {
|
|
10
14
|
'Authorization': `Bearer ${config.hooks_token}`,
|
|
11
15
|
'Content-Type': 'application/json',
|
|
12
16
|
},
|
|
13
|
-
body: JSON.stringify(
|
|
17
|
+
body: JSON.stringify(payload),
|
|
14
18
|
});
|
|
15
19
|
return { ok: res.ok, status: res.status };
|
|
16
20
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "0.21.1",
|
|
6
6
|
"skills": [
|
|
7
7
|
"skills"
|
|
8
8
|
],
|
package/package.json
CHANGED