@workclaw/openclaw-workclaw 1.0.19 → 1.0.23
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/README.md +363 -364
- package/api.ts +3 -0
- package/dist/index.js +2 -1
- package/dist/src/api/workspace.js +2 -0
- package/dist/src/channel.js +1 -0
- package/dist/src/gateway/agent-handlers.js +87 -87
- package/dist/src/gateway/config-writer.js +4 -14
- package/dist/src/gateway/cron-tasks-handler.d.ts +19 -0
- package/dist/src/gateway/cron-tasks-handler.js +188 -0
- package/dist/src/gateway/message-context.d.ts +8 -4
- package/dist/src/gateway/message-context.js +148 -138
- package/dist/src/gateway/message-dispatcher.d.ts +0 -1
- package/dist/src/gateway/message-dispatcher.js +383 -323
- package/dist/src/gateway/reconnect.js +4 -0
- package/dist/src/gateway/skills-handler.js +187 -148
- package/dist/src/gateway/workclaw-gateway.d.ts +1 -1
- package/dist/src/gateway/workclaw-gateway.js +47 -33
- package/dist/src/tools/openclaw-workclaw-cron/src/update/params.js +17 -17
- package/index.ts +325 -0
- package/package.json +45 -62
- package/setup-entry.ts +13 -0
- package/src/accounts.ts +360 -0
- package/src/api/accounts-api.ts +156 -0
- package/src/api/prompts-api.ts +122 -0
- package/src/api/session-api.ts +246 -0
- package/src/api/skills-api.ts +74 -0
- package/src/api/workspace.ts +45 -0
- package/src/channel.ts +226 -0
- package/src/config-schema.ts +60 -0
- package/src/connection/workclaw-client.ts +618 -0
- package/src/gateway/agent-handlers.ts +551 -0
- package/src/gateway/config-writer.ts +378 -0
- package/src/gateway/cron-tasks-handler.ts +230 -0
- package/src/gateway/message-context.ts +645 -0
- package/src/gateway/message-dispatcher.ts +688 -0
- package/src/gateway/reconnect.ts +260 -0
- package/src/gateway/skills-handler.ts +805 -0
- package/src/gateway/skills-list-handler.ts +332 -0
- package/src/gateway/tools-list-handler.ts +161 -0
- package/src/gateway/workclaw-gateway.ts +298 -0
- package/src/media/upload.ts +168 -0
- package/src/outbound/index.ts +191 -0
- package/src/outbound/workclaw-sender.ts +161 -0
- package/src/runtime.ts +520 -0
- package/src/secret-contract-api.ts +4 -0
- package/src/send.ts +1 -0
- package/src/setup-api.ts +3 -0
- package/src/setup-core.ts +25 -0
- package/src/setup-surface.ts +498 -0
- package/src/tools/openclaw-workclaw-cron/api/index.ts +326 -0
- package/src/tools/openclaw-workclaw-cron/index.ts +39 -0
- package/src/tools/openclaw-workclaw-cron/src/add/params.ts +177 -0
- package/src/tools/openclaw-workclaw-cron/src/add/sync.ts +188 -0
- package/src/tools/openclaw-workclaw-cron/src/disable/params.ts +100 -0
- package/src/tools/openclaw-workclaw-cron/src/disable/sync.ts +127 -0
- package/src/tools/openclaw-workclaw-cron/src/enable/params.ts +100 -0
- package/src/tools/openclaw-workclaw-cron/src/enable/sync.ts +127 -0
- package/src/tools/openclaw-workclaw-cron/src/notify/sync.ts +148 -0
- package/src/tools/openclaw-workclaw-cron/src/remove/params.ts +109 -0
- package/src/tools/openclaw-workclaw-cron/src/remove/sync.ts +127 -0
- package/src/tools/openclaw-workclaw-cron/src/update/params.ts +195 -0
- package/src/tools/openclaw-workclaw-cron/src/update/sync.ts +161 -0
- package/src/tools/openclaw-workclaw-cron/types/index.ts +55 -0
- package/src/tools/openclaw-workclaw-cron/utils/index.ts +141 -0
- package/src/tools/openclaw-workclaw-system/index.ts +17 -0
- package/src/tools/openclaw-workclaw-system/src/get/index.ts +77 -0
- package/src/tools/openclaw-workclaw-system/src/token/index.ts +93 -0
- package/src/types.ts +50 -0
- package/src/utils/content.ts +40 -0
- package/tsconfig.json +34 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { createReconnectScheduler } from
|
|
1
|
+
import { buildAccountMap, resolveAccountByUserIdAndAgentId } from '../accounts.js';
|
|
2
|
+
import { clearReconnectScheduler, clearWorkclawConnectionConfig, clearWorkclawWsConnection, createLogger, finishConnecting, getAllDispatchersByAppKey, getAppKeyByAccountId, getDispatcherByAppKeyAndAccountId, getReconnectScheduler, getWorkclawWsConnection, registerAccountContext, setReconnectScheduler, setWorkclawConnectionConfig, setWorkclawLoggerFromContext, tryStartConnecting, unregisterAccountContext, } from '../runtime.js';
|
|
3
|
+
import { parseWorkClawMessage } from './message-context.js';
|
|
4
|
+
import { dispatchWorkclawMessage } from './message-dispatcher.js';
|
|
5
|
+
import { createReconnectScheduler } from './reconnect.js';
|
|
6
6
|
export async function startWorkclawGateway(options) {
|
|
7
7
|
const { accountId, account, cfg, log } = options;
|
|
8
|
-
const logger = createLogger(
|
|
8
|
+
const logger = createLogger('', log);
|
|
9
9
|
setWorkclawLoggerFromContext(logger);
|
|
10
10
|
const rawConfig = account.config;
|
|
11
|
-
const connectionMode = rawConfig.connectionMode ||
|
|
12
|
-
if (connectionMode !==
|
|
11
|
+
const connectionMode = rawConfig.connectionMode || 'websocket';
|
|
12
|
+
if (connectionMode !== 'websocket') {
|
|
13
13
|
throw new Error(`Unsupported connectionMode: ${connectionMode}`);
|
|
14
14
|
}
|
|
15
15
|
await startSharedWebSocket(options);
|
|
@@ -19,7 +19,7 @@ export async function startWorkclawGateway(options) {
|
|
|
19
19
|
*/
|
|
20
20
|
async function startSharedWebSocket(options) {
|
|
21
21
|
const { accountId, account, cfg, log } = options;
|
|
22
|
-
const logger = createLogger(
|
|
22
|
+
const logger = createLogger('', log);
|
|
23
23
|
const rawConfig = account.config;
|
|
24
24
|
const baseConfig = {
|
|
25
25
|
baseUrl: rawConfig.baseUrl,
|
|
@@ -36,9 +36,9 @@ async function startSharedWebSocket(options) {
|
|
|
36
36
|
openConversationId: rawConfig.openConversationId,
|
|
37
37
|
};
|
|
38
38
|
const appKey = baseConfig.appKey || accountId;
|
|
39
|
-
const appKeyRaw = baseConfig.appKey ??
|
|
39
|
+
const appKeyRaw = baseConfig.appKey ?? '';
|
|
40
40
|
buildAccountMap(cfg);
|
|
41
|
-
logger.info(`Gateway start accountId=${accountId} appKey=${appKeyRaw ? `${appKeyRaw.slice(0, 8)}...` :
|
|
41
|
+
logger.info(`Gateway start accountId=${accountId} appKey=${appKeyRaw ? `${appKeyRaw.slice(0, 8)}...` : 'missing'} baseUrl=${baseConfig.baseUrl ?? ''} websocketUrl=${baseConfig.websocketUrl ?? ''}`);
|
|
42
42
|
// 注册账号上下文
|
|
43
43
|
const ctx = {
|
|
44
44
|
accountId,
|
|
@@ -105,8 +105,8 @@ async function startSharedWebSocket(options) {
|
|
|
105
105
|
// Cache the connection config to prevent cfg mutation issues during message sends
|
|
106
106
|
// 每个账户都有自己的配置缓存
|
|
107
107
|
setWorkclawConnectionConfig(accountId, {
|
|
108
|
-
appKey: baseConfig.appKey ??
|
|
109
|
-
appSecret: baseConfig.appSecret ??
|
|
108
|
+
appKey: baseConfig.appKey ?? '',
|
|
109
|
+
appSecret: baseConfig.appSecret ?? '',
|
|
110
110
|
baseUrl: baseConfig.baseUrl,
|
|
111
111
|
websocketUrl: baseConfig.websocketUrl,
|
|
112
112
|
localIp: baseConfig.localIp,
|
|
@@ -124,33 +124,33 @@ function handleSharedMessage(appKey, data, cfg, logger) {
|
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
126
|
// ping/pong/disconnect 无需路由
|
|
127
|
-
if (parsed.type ===
|
|
127
|
+
if (parsed.type === 'ping') {
|
|
128
128
|
const ws = getWorkclawWsConnection(appKey);
|
|
129
129
|
if (ws && ws.readyState === 1) { // OPEN
|
|
130
130
|
const pongResponse = {
|
|
131
131
|
code: 200,
|
|
132
|
-
message:
|
|
133
|
-
metadata: { contentType:
|
|
132
|
+
message: 'pong',
|
|
133
|
+
metadata: { contentType: 'application/json' },
|
|
134
134
|
data: JSON.stringify(parsed.pongData),
|
|
135
135
|
};
|
|
136
136
|
ws.send(JSON.stringify(pongResponse));
|
|
137
137
|
}
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
|
-
if (parsed.type ===
|
|
140
|
+
if (parsed.type === 'disconnect') {
|
|
141
141
|
const ws = getWorkclawWsConnection(appKey);
|
|
142
142
|
ws?.close();
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
145
|
// logger.info?.(`Gateway received message: ${data}`);
|
|
146
146
|
// 根据消息类型确定 userId 和 agentId
|
|
147
|
-
let userId =
|
|
148
|
-
let agentId =
|
|
149
|
-
if (parsed.type ===
|
|
150
|
-
userId = String(parsed.message.userId ||
|
|
151
|
-
agentId = String(parsed.message.agentId ||
|
|
147
|
+
let userId = '';
|
|
148
|
+
let agentId = '';
|
|
149
|
+
if (parsed.type === 'agent_message' && parsed.message) {
|
|
150
|
+
userId = String(parsed.message.userId || '');
|
|
151
|
+
agentId = String(parsed.message.agentId || '');
|
|
152
152
|
}
|
|
153
|
-
else if (parsed.type ===
|
|
153
|
+
else if (parsed.type === 'agent_created') {
|
|
154
154
|
// agent_created 触发新账号创建
|
|
155
155
|
const allDispatchers = getAllDispatchersByAppKey(appKey);
|
|
156
156
|
if (allDispatchers && allDispatchers.size > 0) {
|
|
@@ -161,21 +161,21 @@ function handleSharedMessage(appKey, data, cfg, logger) {
|
|
|
161
161
|
}
|
|
162
162
|
return;
|
|
163
163
|
}
|
|
164
|
-
else if (parsed.type ===
|
|
165
|
-
userId = String(parsed.eventData?.futureId || parsed.eventData?.userId ||
|
|
166
|
-
agentId = String(parsed.eventData?.id || parsed.eventData?.agentId ||
|
|
164
|
+
else if (parsed.type === 'agent_updated' || parsed.type === 'agent_deleted') {
|
|
165
|
+
userId = String(parsed.eventData?.futureId || parsed.eventData?.userId || '');
|
|
166
|
+
agentId = String(parsed.eventData?.id || parsed.eventData?.agentId || '');
|
|
167
167
|
}
|
|
168
|
-
else if (parsed.type ===
|
|
169
|
-
userId = String(parsed.eventData?.userId ||
|
|
170
|
-
agentId = String(parsed.eventData?.agentId ||
|
|
168
|
+
else if (parsed.type === 'tools_list' || parsed.type === 'skills_list' || parsed.type === 'skills_event') {
|
|
169
|
+
userId = String(parsed.eventData?.userId || '');
|
|
170
|
+
agentId = String(parsed.eventData?.agentId || '');
|
|
171
171
|
}
|
|
172
|
-
else if (parsed.type ===
|
|
173
|
-
userId = String(parsed.eventData?.futureId || parsed.eventData?.userId ||
|
|
174
|
-
agentId = String(parsed.eventData?.id || parsed.eventData?.agentId ||
|
|
172
|
+
else if (parsed.type === 'init_agent') {
|
|
173
|
+
userId = String(parsed.eventData?.futureId || parsed.eventData?.userId || '');
|
|
174
|
+
agentId = String(parsed.eventData?.id || parsed.eventData?.agentId || '');
|
|
175
175
|
/**
|
|
176
176
|
* 在 init_agent 中,配置文件的agentId 为空,所有无法通过 resolveAccountByUserIdAndAgentId 查找账户
|
|
177
177
|
*/
|
|
178
|
-
const accountId =
|
|
178
|
+
const accountId = 'default';
|
|
179
179
|
const dispatcher = getDispatcherByAppKeyAndAccountId(appKey, accountId);
|
|
180
180
|
if (dispatcher) {
|
|
181
181
|
dispatchWorkclawMessage(data, dispatcher.ctx).catch((err) => {
|
|
@@ -187,6 +187,20 @@ function handleSharedMessage(appKey, data, cfg, logger) {
|
|
|
187
187
|
}
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
|
+
else if (parsed.type === 'cron_task_event') {
|
|
191
|
+
// 任务事件处理,使用默认账户
|
|
192
|
+
const accountId = 'default';
|
|
193
|
+
const dispatcher = getDispatcherByAppKeyAndAccountId(appKey, accountId);
|
|
194
|
+
if (dispatcher) {
|
|
195
|
+
dispatchWorkclawMessage(data, dispatcher.ctx).catch((err) => {
|
|
196
|
+
logger.error?.(`Dispatch error: ${String(err)}`);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
logger.warn?.(`Gateway (per-appKey) no dispatcher for accountId=${accountId}`);
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
190
204
|
else {
|
|
191
205
|
// 未知消息类型,忽略
|
|
192
206
|
return;
|
|
@@ -44,23 +44,23 @@ function getOpenclawWorkclawCronUpdateParamsTool(api) {
|
|
|
44
44
|
return {
|
|
45
45
|
name: 'openclaw-workclaw-cron-update-params',
|
|
46
46
|
label: '智小途生成修改提醒参数',
|
|
47
|
-
description: `【核心工具】生成修改提醒的 cronParams 参数。
|
|
48
|
-
|
|
49
|
-
📋 工作流程(必须按顺序执行):
|
|
50
|
-
1. 调用此工具获取 cronParams 参数
|
|
51
|
-
2. 使用返回的 cronParams 调用 OpenClaw 的 cron 工具
|
|
52
|
-
3. 必须调用 openclaw-workclaw-cron-update-sync 同步到后端
|
|
53
|
-
|
|
54
|
-
⏰ 时间格式(核心!):
|
|
55
|
-
- time 参数支持相对时间(如 "10m"、"1h"、"2d")和 cron 表达式
|
|
56
|
-
- 工具会自动将所有时间统一转换为 cron 表达式
|
|
57
|
-
- 严禁使用 --at 参数或其他绝对时间格式
|
|
58
|
-
- 最终 CLI 命令必须使用 --cron 参数
|
|
59
|
-
|
|
60
|
-
🔒 强制规则:
|
|
61
|
-
- schedule.kind 必须是 "cron"
|
|
62
|
-
- 必须使用 --cron 参数传递 cron 表达式
|
|
63
|
-
|
|
47
|
+
description: `【核心工具】生成修改提醒的 cronParams 参数。
|
|
48
|
+
|
|
49
|
+
📋 工作流程(必须按顺序执行):
|
|
50
|
+
1. 调用此工具获取 cronParams 参数
|
|
51
|
+
2. 使用返回的 cronParams 调用 OpenClaw 的 cron 工具
|
|
52
|
+
3. 必须调用 openclaw-workclaw-cron-update-sync 同步到后端
|
|
53
|
+
|
|
54
|
+
⏰ 时间格式(核心!):
|
|
55
|
+
- time 参数支持相对时间(如 "10m"、"1h"、"2d")和 cron 表达式
|
|
56
|
+
- 工具会自动将所有时间统一转换为 cron 表达式
|
|
57
|
+
- 严禁使用 --at 参数或其他绝对时间格式
|
|
58
|
+
- 最终 CLI 命令必须使用 --cron 参数
|
|
59
|
+
|
|
60
|
+
🔒 强制规则:
|
|
61
|
+
- schedule.kind 必须是 "cron"
|
|
62
|
+
- 必须使用 --cron 参数传递 cron 表达式
|
|
63
|
+
|
|
64
64
|
⚠️ 必须从 cron 工具返回值中确认更新成功`,
|
|
65
65
|
parameters: UpdateParamsSchema,
|
|
66
66
|
execute: async (_toolCallId, params) => {
|
package/index.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import process from 'node:process'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk'
|
|
7
|
+
import { defineChannelPluginEntry } from 'openclaw/plugin-sdk/core'
|
|
8
|
+
|
|
9
|
+
import { createAccountsApiHandler } from './src/api/accounts-api.js'
|
|
10
|
+
import { createPromptsApiHandler } from './src/api/prompts-api.js'
|
|
11
|
+
import { createSessionApiHandler } from './src/api/session-api.js'
|
|
12
|
+
import { createSkillsApiHandler } from './src/api/skills-api.js'
|
|
13
|
+
import { isDefaultWorkspace, resolveWorkspaceDir } from './src/api/workspace.js'
|
|
14
|
+
import { workclawPlugin } from './src/channel.js'
|
|
15
|
+
import { sendMessageWorkclaw } from './src/outbound/index.js'
|
|
16
|
+
import {
|
|
17
|
+
deleteToolContext,
|
|
18
|
+
getToolContext,
|
|
19
|
+
getToolResultHint,
|
|
20
|
+
getWorkclawLogger,
|
|
21
|
+
getWorkclawRuntime,
|
|
22
|
+
setToolCallIdToRunIdMapping,
|
|
23
|
+
setWorkclawRuntime,
|
|
24
|
+
} from './src/runtime.js'
|
|
25
|
+
|
|
26
|
+
import { registerAllOpenclawWorkclawCronTools } from './src/tools/openclaw-workclaw-cron/index.js'
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Tool output formatting helpers
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
function formatToolParams(toolName: string | undefined, params: unknown): string {
|
|
33
|
+
if (!params || typeof params !== 'object') {
|
|
34
|
+
return ''
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const normalizedToolName = (toolName || '').toLowerCase().trim()
|
|
38
|
+
const p = params as Record<string, unknown>
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
if (normalizedToolName === 'exec' || normalizedToolName === 'bash' || normalizedToolName === 'shell') {
|
|
42
|
+
const cmd = p.command || p.cmd
|
|
43
|
+
if (cmd)
|
|
44
|
+
return `command: ${String(cmd).slice(0, 200)}`
|
|
45
|
+
}
|
|
46
|
+
else if (
|
|
47
|
+
normalizedToolName === 'read'
|
|
48
|
+
|| normalizedToolName === 'file_read'
|
|
49
|
+
|| normalizedToolName === 'view'
|
|
50
|
+
) {
|
|
51
|
+
const p2 = p.path
|
|
52
|
+
if (p2)
|
|
53
|
+
return `path: ${p2}`
|
|
54
|
+
}
|
|
55
|
+
else if (
|
|
56
|
+
normalizedToolName === 'write'
|
|
57
|
+
|| normalizedToolName === 'file_write'
|
|
58
|
+
|| normalizedToolName === 'edit'
|
|
59
|
+
|| normalizedToolName === 'patch'
|
|
60
|
+
) {
|
|
61
|
+
const p2 = p.path
|
|
62
|
+
if (p2)
|
|
63
|
+
return `path: ${p2}`
|
|
64
|
+
}
|
|
65
|
+
else if (
|
|
66
|
+
normalizedToolName === 'web_fetch'
|
|
67
|
+
|| normalizedToolName === 'fetch'
|
|
68
|
+
|| normalizedToolName === 'http_get'
|
|
69
|
+
|| normalizedToolName === 'http_request'
|
|
70
|
+
|| normalizedToolName === 'open_url'
|
|
71
|
+
|| normalizedToolName === 'browser_open'
|
|
72
|
+
|| normalizedToolName === 'open'
|
|
73
|
+
) {
|
|
74
|
+
const u = p.url
|
|
75
|
+
if (u)
|
|
76
|
+
return `url: ${u}`
|
|
77
|
+
}
|
|
78
|
+
else if (
|
|
79
|
+
normalizedToolName === 'search'
|
|
80
|
+
|| normalizedToolName === 'web_search'
|
|
81
|
+
|| normalizedToolName === 'google_search'
|
|
82
|
+
|| normalizedToolName === 'browser_search'
|
|
83
|
+
|| normalizedToolName === 'browser.search'
|
|
84
|
+
) {
|
|
85
|
+
const q = p.query || p.q || p.search
|
|
86
|
+
if (q)
|
|
87
|
+
return `query: ${q}`
|
|
88
|
+
}
|
|
89
|
+
else if (
|
|
90
|
+
normalizedToolName === 'image_generate'
|
|
91
|
+
|| normalizedToolName === 'generate_image'
|
|
92
|
+
|| normalizedToolName === 'dalle'
|
|
93
|
+
|| normalizedToolName === 'text_to_image'
|
|
94
|
+
) {
|
|
95
|
+
const prompt = p.prompt
|
|
96
|
+
if (prompt)
|
|
97
|
+
return `prompt: ${String(prompt).slice(0, 200)}`
|
|
98
|
+
}
|
|
99
|
+
else if (
|
|
100
|
+
normalizedToolName === 'code_interpreter'
|
|
101
|
+
|| normalizedToolName === 'python'
|
|
102
|
+
|| normalizedToolName === 'jupyter'
|
|
103
|
+
) {
|
|
104
|
+
const code = p.code
|
|
105
|
+
if (code)
|
|
106
|
+
return `code: ${String(code).slice(0, 200)}`
|
|
107
|
+
}
|
|
108
|
+
else if (normalizedToolName === 'mcp_deliver' || normalizedToolName === 'deliver') {
|
|
109
|
+
const target = p.target || p.to
|
|
110
|
+
if (target)
|
|
111
|
+
return `target: ${target}`
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const entries = Object.entries(p)
|
|
115
|
+
if (entries.length > 0) {
|
|
116
|
+
return entries
|
|
117
|
+
.map(([k, v]) => {
|
|
118
|
+
const vStr = typeof v === 'string' ? v : JSON.stringify(v)
|
|
119
|
+
return vStr.length > 100 ? `${k}: ${vStr.slice(0, 100)}...` : `${k}: ${vStr}`
|
|
120
|
+
})
|
|
121
|
+
.join('\n')
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return String(params)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return ''
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function formatToolOutput(toolName: string | undefined, params: unknown, _result: unknown): string {
|
|
133
|
+
return formatToolParams(toolName, params)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Tool hooks
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
function registerToolHooks(api: OpenClawPluginApi): void {
|
|
141
|
+
api.on('agent_end', (event: any, ctx: any) => {
|
|
142
|
+
const { runId } = ctx || {}
|
|
143
|
+
if (runId) {
|
|
144
|
+
deleteToolContext(runId)
|
|
145
|
+
api.logger?.info?.(`[ToolHook] agent_end runId=${runId} cleaned up`)
|
|
146
|
+
}
|
|
147
|
+
return event
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
api.on('before_tool_call', (event: any, _ctx: any) => {
|
|
151
|
+
api.logger?.info?.(`[ToolHook] before_tool_call event=${JSON.stringify(event)}`)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
api.on('after_tool_call', (event: any, _ctx: any) => {
|
|
155
|
+
api.logger?.info?.(`[ToolHook] after_tool_call event=${JSON.stringify(event)}`)
|
|
156
|
+
const { runId, toolCallId, toolName, params, result } = event || {}
|
|
157
|
+
const toolCtx = runId ? getToolContext(runId) : undefined
|
|
158
|
+
|
|
159
|
+
if (toolCtx && result) {
|
|
160
|
+
const content = formatToolOutput(toolName, params, result)
|
|
161
|
+
if (content) {
|
|
162
|
+
try {
|
|
163
|
+
const cfg = getWorkclawRuntime().config.loadConfig()
|
|
164
|
+
const formattedToolOutput = {
|
|
165
|
+
name: getToolResultHint(toolName),
|
|
166
|
+
toolName,
|
|
167
|
+
content,
|
|
168
|
+
state: 'result',
|
|
169
|
+
}
|
|
170
|
+
sendMessageWorkclaw({
|
|
171
|
+
cfg,
|
|
172
|
+
to: toolCtx.target,
|
|
173
|
+
text: JSON.stringify(formattedToolOutput),
|
|
174
|
+
msgType: '26',
|
|
175
|
+
accountId: toolCtx.accountId,
|
|
176
|
+
openConversationId: toolCtx.openConversationId,
|
|
177
|
+
agentId: toolCtx.agentId,
|
|
178
|
+
replyToMessageId: toolCtx.replyToMessageId,
|
|
179
|
+
last: false,
|
|
180
|
+
source: 'after_tool_call',
|
|
181
|
+
}).catch((err) => {
|
|
182
|
+
api.logger?.error?.(`[ToolHook] after_tool_call push failed: ${err}`)
|
|
183
|
+
})
|
|
184
|
+
api.logger?.info?.(`[ToolHook] after_tool_call pushed for toolName=${toolName}`)
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
api.logger?.error?.(`[ToolHook] after_tool_call push failed: ${err}`)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (runId && toolCallId) {
|
|
193
|
+
setToolCallIdToRunIdMapping(toolCallId, runId)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return event
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
api.on('tool_result_persist', (event: any, _ctx: any) => {
|
|
200
|
+
return { message: event.message }
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
api.logger?.info?.(`[ToolHook] Tool execution hooks registered`)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============================================================================
|
|
207
|
+
// Templates
|
|
208
|
+
// ============================================================================
|
|
209
|
+
|
|
210
|
+
const templateNames = ['SOUL.md', 'IDENTITY.md'] as const
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 获取插件所在目录,兼容 Windows 下的 file:// URL 问题。
|
|
214
|
+
* 4.2 的插件加载器在 Windows 上可能传入不带协议前缀的路径。
|
|
215
|
+
*/
|
|
216
|
+
function getPluginDir(): string {
|
|
217
|
+
try {
|
|
218
|
+
const url = import.meta.url
|
|
219
|
+
if (!url.startsWith('file://')) {
|
|
220
|
+
// Windows 路径被直接传入(不带 file:// 前缀),直接取目录
|
|
221
|
+
return path.dirname(url)
|
|
222
|
+
}
|
|
223
|
+
return path.dirname(fileURLToPath(url))
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return process.cwd()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function writeFileIfMissing(filePath: string, content: string): Promise<void> {
|
|
231
|
+
try {
|
|
232
|
+
await writeFile(filePath, content, { encoding: 'utf-8', flag: 'wx' })
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (
|
|
236
|
+
error
|
|
237
|
+
&& typeof error === 'object'
|
|
238
|
+
&& 'code' in error
|
|
239
|
+
&& (error as NodeJS.ErrnoException).code === 'EEXIST'
|
|
240
|
+
) {
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
throw error
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function releaseTemplates(api: OpenClawPluginApi): Promise<void> {
|
|
248
|
+
const workspaceDir = resolveWorkspaceDir(api)
|
|
249
|
+
getWorkclawLogger().info(`workspaceDir: ${workspaceDir}`)
|
|
250
|
+
// 模板文件在根目录的 templates/ 中,而不是 dist/templates/
|
|
251
|
+
const currentDir = getPluginDir()
|
|
252
|
+
const templatesDir = path.join(
|
|
253
|
+
currentDir.endsWith('dist') ? path.dirname(currentDir) : currentDir,
|
|
254
|
+
'templates',
|
|
255
|
+
)
|
|
256
|
+
const shouldOverwrite = isDefaultWorkspace(api)
|
|
257
|
+
|
|
258
|
+
await mkdir(workspaceDir, { recursive: true })
|
|
259
|
+
|
|
260
|
+
await Promise.all(
|
|
261
|
+
templateNames.map(async (name) => {
|
|
262
|
+
const sourcePath = path.join(templatesDir, name)
|
|
263
|
+
const targetPath = path.join(workspaceDir, name)
|
|
264
|
+
const content = await readFile(sourcePath, 'utf-8')
|
|
265
|
+
if (shouldOverwrite) {
|
|
266
|
+
await writeFile(targetPath, content, { encoding: 'utf-8' })
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
await writeFileIfMissing(targetPath, content)
|
|
270
|
+
}
|
|
271
|
+
}),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
api.logger.info(`openclaw-workclaw templates ready: ${workspaceDir}`)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ============================================================================
|
|
278
|
+
// Plugin entry point
|
|
279
|
+
// ============================================================================
|
|
280
|
+
|
|
281
|
+
export default defineChannelPluginEntry({
|
|
282
|
+
id: 'openclaw-workclaw',
|
|
283
|
+
name: 'openclaw-workclaw',
|
|
284
|
+
description: '智小途 channel plugin',
|
|
285
|
+
configSchema: emptyPluginConfigSchema() as any,
|
|
286
|
+
plugin: workclawPlugin,
|
|
287
|
+
registerFull(api) {
|
|
288
|
+
setWorkclawRuntime(api.runtime)
|
|
289
|
+
|
|
290
|
+
registerAllOpenclawWorkclawCronTools(api)
|
|
291
|
+
registerToolHooks(api)
|
|
292
|
+
|
|
293
|
+
api.registerService({
|
|
294
|
+
id: 'openclaw-workclaw-templates',
|
|
295
|
+
start: async () => {
|
|
296
|
+
await releaseTemplates(api)
|
|
297
|
+
},
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
api.registerHttpRoute({
|
|
301
|
+
path: '/openclaw-workclaw/workspace/prompts',
|
|
302
|
+
handler: createPromptsApiHandler(api),
|
|
303
|
+
auth: 'plugin',
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
api.registerHttpRoute({
|
|
307
|
+
path: '/openclaw-workclaw/accounts',
|
|
308
|
+
handler: createAccountsApiHandler(api),
|
|
309
|
+
auth: 'plugin',
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
api.registerHttpRoute({
|
|
313
|
+
path: '/openclaw-workclaw/skills',
|
|
314
|
+
handler: createSkillsApiHandler(api),
|
|
315
|
+
auth: 'plugin',
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
api.registerHttpRoute({
|
|
319
|
+
path: '/openclaw-workclaw/sessions',
|
|
320
|
+
handler: createSessionApiHandler(api),
|
|
321
|
+
auth: 'plugin',
|
|
322
|
+
match: 'prefix',
|
|
323
|
+
})
|
|
324
|
+
},
|
|
325
|
+
})
|
package/package.json
CHANGED
|
@@ -1,62 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@workclaw/openclaw-workclaw",
|
|
3
|
-
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
5
|
-
"description": "openclaw-workclaw channel plugin",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"aliases": [
|
|
47
|
-
"openclaw-workclaw"
|
|
48
|
-
],
|
|
49
|
-
"order": 35,
|
|
50
|
-
"quickstartAllowFrom": true
|
|
51
|
-
},
|
|
52
|
-
"compat": {
|
|
53
|
-
"pluginApi": "2026.4.14",
|
|
54
|
-
"minGatewayVersion": "2026.4.14"
|
|
55
|
-
},
|
|
56
|
-
"install": {
|
|
57
|
-
"npmSpec": "@workclaw/openclaw-workclaw",
|
|
58
|
-
"localPath": "extensions/openclaw-workclaw",
|
|
59
|
-
"defaultChoice": "npm"
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@workclaw/openclaw-workclaw",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.23",
|
|
5
|
+
"description": "openclaw-workclaw channel plugin",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"undici": "^7.16.0",
|
|
14
|
+
"ws": "^8.19.0",
|
|
15
|
+
"zod": "^4.3.6"
|
|
16
|
+
},
|
|
17
|
+
"openclaw": {
|
|
18
|
+
"extensions": [
|
|
19
|
+
"./index.ts"
|
|
20
|
+
],
|
|
21
|
+
"setupEntry": "./src/setup-entry.ts",
|
|
22
|
+
"channel": {
|
|
23
|
+
"id": "openclaw-workclaw",
|
|
24
|
+
"label": "openclaw-workclaw",
|
|
25
|
+
"selectionLabel": "智小途",
|
|
26
|
+
"docsPath": "/channels/openclaw-workclaw",
|
|
27
|
+
"docsLabel": "openclaw-workclaw",
|
|
28
|
+
"blurb": "智小途 企业通讯平台集成",
|
|
29
|
+
"aliases": [
|
|
30
|
+
"openclaw-workclaw"
|
|
31
|
+
],
|
|
32
|
+
"order": 35,
|
|
33
|
+
"quickstartAllowFrom": true
|
|
34
|
+
},
|
|
35
|
+
"compat": {
|
|
36
|
+
"pluginApi": "2026.4.14",
|
|
37
|
+
"minGatewayVersion": "2026.4.14"
|
|
38
|
+
},
|
|
39
|
+
"install": {
|
|
40
|
+
"npmSpec": "@workclaw/openclaw-workclaw",
|
|
41
|
+
"localPath": "extensions/openclaw-workclaw",
|
|
42
|
+
"defaultChoice": "npm"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
package/setup-entry.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineBundledChannelSetupEntry } from 'openclaw/plugin-sdk/channel-entry-contract'
|
|
2
|
+
|
|
3
|
+
export default defineBundledChannelSetupEntry({
|
|
4
|
+
importMetaUrl: import.meta.url,
|
|
5
|
+
plugin: {
|
|
6
|
+
specifier: './api.js',
|
|
7
|
+
exportName: 'workclawPlugin',
|
|
8
|
+
},
|
|
9
|
+
secrets: {
|
|
10
|
+
specifier: './src/secret-contract-api.js',
|
|
11
|
+
exportName: 'channelSecrets',
|
|
12
|
+
},
|
|
13
|
+
})
|