@workclaw/openclaw-workclaw 1.0.1 → 1.0.11

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/package.json CHANGED
@@ -1,43 +1,43 @@
1
- {
2
- "name": "@workclaw/openclaw-workclaw",
3
- "type": "module",
4
- "version": "1.0.1",
5
- "description": "OpenClaw WorkClaw channel plugin",
6
- "license": "MIT",
7
- "main": "./src/index.ts",
8
- "publishConfig": {
9
- "access": "public"
10
- },
11
- "peerDependencies": {
12
- "openclaw": "^2026.3.13",
13
- "typescript": "^5.6.3"
14
- },
15
- "dependencies": {
16
- "undici": "^7.16.0",
17
- "ws": "^8.19.0",
18
- "zod": "^4.3.6"
19
- },
20
- "openclaw": {
21
- "extensions": [
22
- "./src/index.ts"
23
- ],
24
- "channel": {
25
- "id": "openclaw-workclaw",
26
- "label": "openclaw-workclaw",
27
- "selectionLabel": "智小途",
28
- "docsPath": "/channels/openclaw-workclaw",
29
- "docsLabel": "openclaw-workclaw",
30
- "blurb": "智小途 企业通讯平台集成",
31
- "aliases": [
32
- "openclaw-workclaw"
33
- ],
34
- "order": 35,
35
- "quickstartAllowFrom": true
36
- },
37
- "install": {
38
- "npmSpec": "@openclaw/openclaw-workclaw",
39
- "localPath": "extensions/openclaw-workclaw",
40
- "defaultChoice": "npm"
41
- }
42
- }
43
- }
1
+ {
2
+ "name": "@workclaw/openclaw-workclaw",
3
+ "type": "module",
4
+ "version": "1.0.11",
5
+ "description": "OpenClaw WorkClaw channel plugin",
6
+ "license": "MIT",
7
+ "main": "./src/index.ts",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "peerDependencies": {
12
+ "openclaw": "^2026.3.13",
13
+ "typescript": "^5.6.3"
14
+ },
15
+ "dependencies": {
16
+ "undici": "^7.16.0",
17
+ "ws": "^8.19.0",
18
+ "zod": "^4.3.6"
19
+ },
20
+ "openclaw": {
21
+ "extensions": [
22
+ "./src/index.ts"
23
+ ],
24
+ "channel": {
25
+ "id": "openclaw-workclaw",
26
+ "label": "openclaw-workclaw",
27
+ "selectionLabel": "智小途",
28
+ "docsPath": "/channels/openclaw-workclaw",
29
+ "docsLabel": "openclaw-workclaw",
30
+ "blurb": "智小途 企业通讯平台集成",
31
+ "aliases": [
32
+ "openclaw-workclaw"
33
+ ],
34
+ "order": 35,
35
+ "quickstartAllowFrom": true
36
+ },
37
+ "install": {
38
+ "npmSpec": "@openclaw/openclaw-workclaw",
39
+ "localPath": "extensions/openclaw-workclaw",
40
+ "defaultChoice": "npm"
41
+ }
42
+ }
43
+ }
@@ -1,4 +1,4 @@
1
- import { z } from 'zod'
1
+ import { z } from 'zod'
2
2
 
3
3
  export { z }
4
4
 
@@ -1,162 +1,162 @@
1
- /**
2
- * Tools List 事件处理器
3
- * 处理云端下发的 TOOLS_LIST EVENT 消息
4
- * 从 OpenClaw 获取 tools 列表并通过 HTTP 回调返回
5
- */
6
-
7
- import { fetch } from 'undici'
8
-
9
- export interface ToolsListEvent {
10
- agentId: string | number
11
- userId: string | number
12
- }
13
-
14
- export interface ToolItem {
15
- typeStr: string
16
- name: string
17
- description: string
18
- }
19
-
20
- export interface ToolsListCallbackPayload {
21
- userId: number
22
- agentId: number
23
- dataList: ToolItem[]
24
- }
25
-
26
- /**
27
- * 处理 TOOLS_LIST 事件
28
- * @param event - 事件数据
29
- * @param baseUrl - 回调基础 URL
30
- * @param token - 鉴权 token
31
- * @param log - 日志对象(可选)
32
- */
33
- export async function handleToolsListEvent(
34
- event: ToolsListEvent,
35
- baseUrl: string,
36
- token: string,
37
- log?: {
38
- info?: (msg: string) => void
39
- error?: (msg: string) => void
40
- },
41
- ): Promise<void> {
42
- const agentId = Number(event.agentId)
43
- const userId = Number(event.userId)
44
-
45
- log?.info?.(`ToolsList: Handling TOOLS_LIST event for agentId: ${agentId}, userId: ${userId}`)
46
-
47
- try {
48
- // 从 OpenClaw 获取 tools 列表
49
- const tools = await fetchToolsFromOpenClaw(agentId, userId, log)
50
-
51
- // 构建回调 payload
52
- const callbackPayload: ToolsListCallbackPayload = {
53
- userId,
54
- agentId,
55
- dataList: tools,
56
- }
57
-
58
- // 发送回调
59
- const callbackUrl = `${baseUrl.replace(/\/$/, '')}/open-apis/v1/claw/push/tools`
60
- await sendToolsListCallback(callbackUrl, callbackPayload, token, log)
61
-
62
- log?.info?.(`ToolsList: Successfully processed TOOLS_LIST event, sent ${tools.length} tools`)
63
- }
64
- catch (error) {
65
- log?.error?.(`ToolsList: Failed to handle TOOLS_LIST event: ${String(error)}`)
66
- throw error
67
- }
68
- }
69
-
70
- /**
71
- * 从 OpenClaw 获取 tools 列表
72
- * 根据 agent 的 tools.allow 配置决定返回哪些工具
73
- */
74
- async function fetchToolsFromOpenClaw(
75
- agentId: number,
76
- _userId: number,
77
- log?: {
78
- info?: (msg: string) => void
79
- error?: (msg: string) => void
80
- },
81
- ): Promise<ToolItem[]> {
82
- log?.info?.(`ToolsList: Fetching tools from OpenClaw for agentId: ${agentId}`)
83
-
84
- // OpenClaw 所有内置工具列表
85
- const allTools: ToolItem[] = [
86
- { typeStr: 'function', name: 'web_search', description: 'Search the web for information using Brave Search API.' },
87
- { typeStr: 'function', name: 'web_fetch', description: 'Fetch and read content from a URL.' },
88
- { typeStr: 'function', name: 'sessions_list', description: 'List all active agent sessions.' },
89
- { typeStr: 'function', name: 'sessions_history', description: 'Get the history of a specific session.' },
90
- { typeStr: 'function', name: 'memory_search', description: 'Search memory for past conversations and information.' },
91
- { typeStr: 'function', name: 'memory_get', description: 'Get specific memory by ID.' },
92
- { typeStr: 'function', name: 'gateway', description: 'Gateway control operations.' },
93
- { typeStr: 'function', name: 'exec', description: 'Execute shell commands.' },
94
- ]
95
-
96
- try {
97
- // 读取 OpenClaw 配置文件
98
- const { readFile } = await import('node:fs/promises')
99
- const { homedir } = await import('node:os')
100
- const path = await import('node:path')
101
-
102
- const configPath = path.join(homedir(), '.openclaw', 'openclaw.json')
103
- const configContent = await readFile(configPath, 'utf-8')
104
- const config = JSON.parse(configContent)
105
-
106
- // 查找对应的 agent 配置
107
- const agentConfig = config.agents?.list?.find((agent: any) =>
108
- String(agent.id) === String(agentId)
109
- || agent.id === `openclaw-workclaw-${agentId}`,
110
- )
111
-
112
- // 获取 tools.allow 配置
113
- const allowTools = agentConfig?.tools?.allow
114
-
115
- // 如果没有 allow 配置,或者 allow 是 ["*"],返回所有工具
116
- if (!allowTools || allowTools.length === 0 || allowTools.includes('*')) {
117
- log?.info?.(`ToolsList: Agent has no allow restriction or allow=[*], returning all ${allTools.length} tools`)
118
- return allTools
119
- }
120
-
121
- // 如果有具体的 allow 列表,只返回列表中的工具
122
- const filteredTools = allTools.filter(tool => allowTools.includes(tool.name))
123
- log?.info?.(`ToolsList: Agent allow=${JSON.stringify(allowTools)}, returning ${filteredTools.length} tools`)
124
- return filteredTools
125
- }
126
- catch (error) {
127
- log?.error?.(`ToolsList: Error reading agent config: ${String(error)}, returning all tools`)
128
- // 如果读取配置失败,返回所有工具
129
- return allTools
130
- }
131
- }
132
-
133
- /**
134
- * 发送 tools 列表回调
135
- */
136
- async function sendToolsListCallback(
137
- callbackUrl: string,
138
- payload: ToolsListCallbackPayload,
139
- token: string,
140
- log?: {
141
- info?: (msg: string) => void
142
- error?: (msg: string) => void
143
- },
144
- ): Promise<void> {
145
- log?.info?.(`ToolsList: Sending callback to ${callbackUrl}`)
146
-
147
- const response = await fetch(callbackUrl, {
148
- method: 'POST',
149
- headers: {
150
- 'Content-Type': 'application/json',
151
- 'Authorization': `Bearer ${token}`,
152
- },
153
- body: JSON.stringify(payload),
154
- })
155
-
156
- if (!response.ok) {
157
- const errorText = await response.text()
158
- throw new Error(`Callback failed: ${response.status} ${response.statusText} - ${errorText}`)
159
- }
160
-
161
- log?.info?.(`ToolsList: Callback sent successfully: ${response.status}`)
162
- }
1
+ /**
2
+ * Tools List 事件处理器
3
+ * 处理云端下发的 TOOLS_LIST EVENT 消息
4
+ * 从 OpenClaw 获取 tools 列表并通过 HTTP 回调返回
5
+ */
6
+
7
+ import { fetch } from 'undici'
8
+
9
+ export interface ToolsListEvent {
10
+ agentId: string | number
11
+ userId: string | number
12
+ }
13
+
14
+ export interface ToolItem {
15
+ typeStr: string
16
+ name: string
17
+ description: string
18
+ }
19
+
20
+ export interface ToolsListCallbackPayload {
21
+ userId: number
22
+ agentId: number
23
+ dataList: ToolItem[]
24
+ }
25
+
26
+ /**
27
+ * 处理 TOOLS_LIST 事件
28
+ * @param event - 事件数据
29
+ * @param baseUrl - 回调基础 URL
30
+ * @param token - 鉴权 token
31
+ * @param log - 日志对象(可选)
32
+ */
33
+ export async function handleToolsListEvent(
34
+ event: ToolsListEvent,
35
+ baseUrl: string,
36
+ token: string,
37
+ log?: {
38
+ info?: (msg: string) => void
39
+ error?: (msg: string) => void
40
+ },
41
+ ): Promise<void> {
42
+ const agentId = Number(event.agentId)
43
+ const userId = Number(event.userId)
44
+
45
+ log?.info?.(`ToolsList: Handling TOOLS_LIST event for agentId: ${agentId}, userId: ${userId}`)
46
+
47
+ try {
48
+ // 从 OpenClaw 获取 tools 列表
49
+ const tools = await fetchToolsFromOpenClaw(agentId, userId, log)
50
+
51
+ // 构建回调 payload
52
+ const callbackPayload: ToolsListCallbackPayload = {
53
+ userId,
54
+ agentId,
55
+ dataList: tools,
56
+ }
57
+
58
+ // 发送回调
59
+ const callbackUrl = `${baseUrl.replace(/\/$/, '')}/open-apis/v1/claw/push/tools`
60
+ await sendToolsListCallback(callbackUrl, callbackPayload, token, log)
61
+
62
+ log?.info?.(`ToolsList: Successfully processed TOOLS_LIST event, sent ${tools.length} tools`)
63
+ }
64
+ catch (error) {
65
+ log?.error?.(`ToolsList: Failed to handle TOOLS_LIST event: ${String(error)}`)
66
+ throw error
67
+ }
68
+ }
69
+
70
+ /**
71
+ * 从 OpenClaw 获取 tools 列表
72
+ * 根据 agent 的 tools.allow 配置决定返回哪些工具
73
+ */
74
+ async function fetchToolsFromOpenClaw(
75
+ agentId: number,
76
+ _userId: number,
77
+ log?: {
78
+ info?: (msg: string) => void
79
+ error?: (msg: string) => void
80
+ },
81
+ ): Promise<ToolItem[]> {
82
+ log?.info?.(`ToolsList: Fetching tools from OpenClaw for agentId: ${agentId}`)
83
+
84
+ // OpenClaw 所有内置工具列表
85
+ const allTools: ToolItem[] = [
86
+ { typeStr: 'function', name: 'web_search', description: 'Search the web for information using Brave Search API.' },
87
+ { typeStr: 'function', name: 'web_fetch', description: 'Fetch and read content from a URL.' },
88
+ { typeStr: 'function', name: 'sessions_list', description: 'List all active agent sessions.' },
89
+ { typeStr: 'function', name: 'sessions_history', description: 'Get the history of a specific session.' },
90
+ { typeStr: 'function', name: 'memory_search', description: 'Search memory for past conversations and information.' },
91
+ { typeStr: 'function', name: 'memory_get', description: 'Get specific memory by ID.' },
92
+ { typeStr: 'function', name: 'gateway', description: 'Gateway control operations.' },
93
+ { typeStr: 'function', name: 'exec', description: 'Execute shell commands.' },
94
+ ]
95
+
96
+ try {
97
+ // 读取 OpenClaw 配置文件
98
+ const { readFile } = await import('node:fs/promises')
99
+ const { homedir } = await import('node:os')
100
+ const path = await import('node:path')
101
+
102
+ const configPath = path.join(homedir(), '.openclaw', 'openclaw.json')
103
+ const configContent = await readFile(configPath, 'utf-8')
104
+ const config = JSON.parse(configContent)
105
+
106
+ // 查找对应的 agent 配置
107
+ const agentConfig = config.agents?.list?.find((agent: any) =>
108
+ String(agent.id) === String(agentId)
109
+ || agent.id === `openclaw-workclaw-${agentId}`,
110
+ )
111
+
112
+ // 获取 tools.allow 配置
113
+ const allowTools = agentConfig?.tools?.allow
114
+
115
+ // 如果没有 allow 配置,或者 allow 是 ["*"],返回所有工具
116
+ if (!allowTools || allowTools.length === 0 || allowTools.includes('*')) {
117
+ log?.info?.(`ToolsList: Agent has no allow restriction or allow=[*], returning all ${allTools.length} tools`)
118
+ return allTools
119
+ }
120
+
121
+ // 如果有具体的 allow 列表,只返回列表中的工具
122
+ const filteredTools = allTools.filter(tool => allowTools.includes(tool.name))
123
+ log?.info?.(`ToolsList: Agent allow=${JSON.stringify(allowTools)}, returning ${filteredTools.length} tools`)
124
+ return filteredTools
125
+ }
126
+ catch (error) {
127
+ log?.error?.(`ToolsList: Error reading agent config: ${String(error)}, returning all tools`)
128
+ // 如果读取配置失败,返回所有工具
129
+ return allTools
130
+ }
131
+ }
132
+
133
+ /**
134
+ * 发送 tools 列表回调
135
+ */
136
+ async function sendToolsListCallback(
137
+ callbackUrl: string,
138
+ payload: ToolsListCallbackPayload,
139
+ token: string,
140
+ log?: {
141
+ info?: (msg: string) => void
142
+ error?: (msg: string) => void
143
+ },
144
+ ): Promise<void> {
145
+ log?.info?.(`ToolsList: Sending callback to ${callbackUrl}`)
146
+
147
+ const response = await fetch(callbackUrl, {
148
+ method: 'POST',
149
+ headers: {
150
+ 'Content-Type': 'application/json',
151
+ 'Authorization': `Bearer ${token}`,
152
+ },
153
+ body: JSON.stringify(payload),
154
+ })
155
+
156
+ if (!response.ok) {
157
+ const errorText = await response.text()
158
+ throw new Error(`Callback failed: ${response.status} ${response.statusText} - ${errorText}`)
159
+ }
160
+
161
+ log?.info?.(`ToolsList: Callback sent successfully: ${response.status}`)
162
+ }
@@ -2,7 +2,7 @@ import { readFile, stat } from 'node:fs/promises'
2
2
  import os from 'node:os'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
-
5
+
6
6
  export function isLocalMediaSource(value: string): boolean {
7
7
  const trimmed = value.trim()
8
8
  return (
@@ -28,12 +28,12 @@ function readResponseUrlPath(
28
28
  data: Record<string, unknown>,
29
29
  pathValue?: string,
30
30
  ): string | undefined {
31
- if (!pathValue)
31
+ if (!pathValue)
32
32
  return undefined
33
33
  const parts = pathValue.split('.').filter(Boolean)
34
34
  let current: unknown = data
35
35
  for (const part of parts) {
36
- if (!current || typeof current !== 'object')
36
+ if (!current || typeof current !== 'object')
37
37
  return undefined
38
38
  current = (current as Record<string, unknown>)[part]
39
39
  }
@@ -46,22 +46,22 @@ function extractUploadedUrl(
46
46
  pathValue?: string,
47
47
  ): string | undefined {
48
48
  const fromPath = readResponseUrlPath(responseData, pathValue)
49
- if (fromPath)
49
+ if (fromPath)
50
50
  return fromPath
51
51
 
52
52
  const direct
53
53
  = (responseData.url as string | undefined)
54
54
  ?? (responseData.mediaUrl as string | undefined)
55
- if (typeof direct === 'string' && direct.trim())
55
+ if (typeof direct === 'string' && direct.trim())
56
56
  return direct
57
57
 
58
58
  const dataObj = responseData.data
59
59
  if (dataObj && typeof dataObj === 'object') {
60
60
  const dataUrl = (dataObj as Record<string, unknown>).url
61
61
  const dataMedia = (dataObj as Record<string, unknown>).mediaUrl
62
- if (typeof dataUrl === 'string' && dataUrl.trim())
62
+ if (typeof dataUrl === 'string' && dataUrl.trim())
63
63
  return dataUrl
64
- if (typeof dataMedia === 'string' && dataMedia.trim())
64
+ if (typeof dataMedia === 'string' && dataMedia.trim())
65
65
  return dataMedia
66
66
  }
67
67
 
@@ -69,9 +69,9 @@ function extractUploadedUrl(
69
69
  if (resultObj && typeof resultObj === 'object') {
70
70
  const resultUrl = (resultObj as Record<string, unknown>).url
71
71
  const resultMedia = (resultObj as Record<string, unknown>).mediaUrl
72
- if (typeof resultUrl === 'string' && resultUrl.trim())
72
+ if (typeof resultUrl === 'string' && resultUrl.trim())
73
73
  return resultUrl
74
- if (typeof resultMedia === 'string' && resultMedia.trim())
74
+ if (typeof resultMedia === 'string' && resultMedia.trim())
75
75
  return resultMedia
76
76
  }
77
77
 
@@ -121,7 +121,7 @@ export async function uploadLocalMedia(params: UploadLocalMediaParams): Promise<
121
121
  const { Agent, fetch: undiciFetchFn } = await import('undici')
122
122
  dispatcher = new Agent({ connect: { rejectUnauthorized: false } })
123
123
  doFetch = undiciFetchFn as any
124
- }
124
+ }
125
125
  catch (error) {
126
126
  const message = error instanceof Error ? error.message : String(error)
127
127
  throw new Error(`allowInsecureTls requires undici. ${message}`)
@@ -146,7 +146,7 @@ export async function uploadLocalMedia(params: UploadLocalMediaParams): Promise<
146
146
  if (responseText) {
147
147
  try {
148
148
  data = JSON.parse(responseText) as Record<string, unknown>
149
- }
149
+ }
150
150
  catch {
151
151
  data = {}
152
152
  }
@@ -161,7 +161,7 @@ export async function uploadLocalMedia(params: UploadLocalMediaParams): Promise<
161
161
  throw new Error('Upload response missing file URL')
162
162
  }
163
163
  return uploadedUrl
164
- }
164
+ }
165
165
  finally {
166
166
  clearTimeout(timeoutId)
167
167
  }