@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.
Files changed (70) hide show
  1. package/README.md +363 -364
  2. package/api.ts +3 -0
  3. package/dist/index.js +2 -1
  4. package/dist/src/api/workspace.js +2 -0
  5. package/dist/src/channel.js +1 -0
  6. package/dist/src/gateway/agent-handlers.js +87 -87
  7. package/dist/src/gateway/config-writer.js +4 -14
  8. package/dist/src/gateway/cron-tasks-handler.d.ts +19 -0
  9. package/dist/src/gateway/cron-tasks-handler.js +188 -0
  10. package/dist/src/gateway/message-context.d.ts +8 -4
  11. package/dist/src/gateway/message-context.js +148 -138
  12. package/dist/src/gateway/message-dispatcher.d.ts +0 -1
  13. package/dist/src/gateway/message-dispatcher.js +383 -323
  14. package/dist/src/gateway/reconnect.js +4 -0
  15. package/dist/src/gateway/skills-handler.js +187 -148
  16. package/dist/src/gateway/workclaw-gateway.d.ts +1 -1
  17. package/dist/src/gateway/workclaw-gateway.js +47 -33
  18. package/dist/src/tools/openclaw-workclaw-cron/src/update/params.js +17 -17
  19. package/index.ts +325 -0
  20. package/package.json +45 -62
  21. package/setup-entry.ts +13 -0
  22. package/src/accounts.ts +360 -0
  23. package/src/api/accounts-api.ts +156 -0
  24. package/src/api/prompts-api.ts +122 -0
  25. package/src/api/session-api.ts +246 -0
  26. package/src/api/skills-api.ts +74 -0
  27. package/src/api/workspace.ts +45 -0
  28. package/src/channel.ts +226 -0
  29. package/src/config-schema.ts +60 -0
  30. package/src/connection/workclaw-client.ts +618 -0
  31. package/src/gateway/agent-handlers.ts +551 -0
  32. package/src/gateway/config-writer.ts +378 -0
  33. package/src/gateway/cron-tasks-handler.ts +230 -0
  34. package/src/gateway/message-context.ts +645 -0
  35. package/src/gateway/message-dispatcher.ts +688 -0
  36. package/src/gateway/reconnect.ts +260 -0
  37. package/src/gateway/skills-handler.ts +805 -0
  38. package/src/gateway/skills-list-handler.ts +332 -0
  39. package/src/gateway/tools-list-handler.ts +161 -0
  40. package/src/gateway/workclaw-gateway.ts +298 -0
  41. package/src/media/upload.ts +168 -0
  42. package/src/outbound/index.ts +191 -0
  43. package/src/outbound/workclaw-sender.ts +161 -0
  44. package/src/runtime.ts +520 -0
  45. package/src/secret-contract-api.ts +4 -0
  46. package/src/send.ts +1 -0
  47. package/src/setup-api.ts +3 -0
  48. package/src/setup-core.ts +25 -0
  49. package/src/setup-surface.ts +498 -0
  50. package/src/tools/openclaw-workclaw-cron/api/index.ts +326 -0
  51. package/src/tools/openclaw-workclaw-cron/index.ts +39 -0
  52. package/src/tools/openclaw-workclaw-cron/src/add/params.ts +177 -0
  53. package/src/tools/openclaw-workclaw-cron/src/add/sync.ts +188 -0
  54. package/src/tools/openclaw-workclaw-cron/src/disable/params.ts +100 -0
  55. package/src/tools/openclaw-workclaw-cron/src/disable/sync.ts +127 -0
  56. package/src/tools/openclaw-workclaw-cron/src/enable/params.ts +100 -0
  57. package/src/tools/openclaw-workclaw-cron/src/enable/sync.ts +127 -0
  58. package/src/tools/openclaw-workclaw-cron/src/notify/sync.ts +148 -0
  59. package/src/tools/openclaw-workclaw-cron/src/remove/params.ts +109 -0
  60. package/src/tools/openclaw-workclaw-cron/src/remove/sync.ts +127 -0
  61. package/src/tools/openclaw-workclaw-cron/src/update/params.ts +195 -0
  62. package/src/tools/openclaw-workclaw-cron/src/update/sync.ts +161 -0
  63. package/src/tools/openclaw-workclaw-cron/types/index.ts +55 -0
  64. package/src/tools/openclaw-workclaw-cron/utils/index.ts +141 -0
  65. package/src/tools/openclaw-workclaw-system/index.ts +17 -0
  66. package/src/tools/openclaw-workclaw-system/src/get/index.ts +77 -0
  67. package/src/tools/openclaw-workclaw-system/src/token/index.ts +93 -0
  68. package/src/types.ts +50 -0
  69. package/src/utils/content.ts +40 -0
  70. package/tsconfig.json +34 -0
@@ -1,15 +1,15 @@
1
- import { getWorkclawWsConnection, clearWorkclawWsConnection, createLogger, setWorkclawLoggerFromContext, registerAccountContext, unregisterAccountContext, getAppKeyByAccountId, getAllDispatchersByAppKey, getDispatcherByAppKeyAndAccountId, getReconnectScheduler, setReconnectScheduler, clearReconnectScheduler, tryStartConnecting, finishConnecting, setWorkclawConnectionConfig, clearWorkclawConnectionConfig, } from "../runtime.js";
2
- import { buildAccountMap, resolveAccountByUserIdAndAgentId } from "../accounts.js";
3
- import { dispatchWorkclawMessage } from "./message-dispatcher.js";
4
- import { parseWorkClawMessage } from "./message-context.js";
5
- import { createReconnectScheduler } from "./reconnect.js";
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("", log);
8
+ const logger = createLogger('', log);
9
9
  setWorkclawLoggerFromContext(logger);
10
10
  const rawConfig = account.config;
11
- const connectionMode = rawConfig.connectionMode || "websocket";
12
- if (connectionMode !== "websocket") {
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("", log);
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)}...` : "missing"} baseUrl=${baseConfig.baseUrl ?? ""} websocketUrl=${baseConfig.websocketUrl ?? ""}`);
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 === "ping") {
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: "pong",
133
- metadata: { contentType: "application/json" },
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 === "disconnect") {
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 === "agent_message" && parsed.message) {
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 === "agent_created") {
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 === "agent_updated" || parsed.type === "agent_deleted") {
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 === "tools_list" || parsed.type === "skills_list" || parsed.type === "skills_event") {
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 === "init_agent") {
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 = "default";
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.19",
5
- "description": "openclaw-workclaw channel plugin",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "publishConfig": {
9
- "access": "public"
10
- },
11
- "files": [
12
- "dist",
13
- "!dist/tests",
14
- "!dist/**/*.test.js",
15
- "!dist/**/*.test.d.ts",
16
- "!dist/vitest.config.js",
17
- "!dist/vitest.config.d.ts",
18
- "templates",
19
- "skills",
20
- "openclaw.plugin.json"
21
- ],
22
- "scripts": {
23
- "build": "tsc"
24
- },
25
- "peerDependencies": {
26
- "openclaw": "2026.4.14",
27
- "typescript": "^5.6.3"
28
- },
29
- "dependencies": {
30
- "undici": "^7.16.0",
31
- "ws": "^8.19.0",
32
- "zod": "^4.3.6"
33
- },
34
- "openclaw": {
35
- "extensions": [
36
- "./dist/index.js"
37
- ],
38
- "setupEntry": "./dist/setup-entry.js",
39
- "channel": {
40
- "id": "openclaw-workclaw",
41
- "label": "openclaw-workclaw",
42
- "selectionLabel": "智小途",
43
- "docsPath": "/channels/openclaw-workclaw",
44
- "docsLabel": "openclaw-workclaw",
45
- "blurb": "智小途 企业通讯平台集成",
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
+ })