@workclaw/openclaw-workclaw 1.0.335 → 1.0.337

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.
@@ -18,6 +18,9 @@ async function readRequestBody(req) {
18
18
  }
19
19
  async function loadRuntimeConfig(api) {
20
20
  const runtimeConfig = api.runtime?.config;
21
+ if (runtimeConfig?.current) {
22
+ return runtimeConfig.current();
23
+ }
21
24
  if (runtimeConfig?.loadConfig) {
22
25
  return runtimeConfig.loadConfig();
23
26
  }
@@ -25,6 +28,25 @@ async function loadRuntimeConfig(api) {
25
28
  }
26
29
  async function writeRuntimeConfig(api, config) {
27
30
  const runtimeConfig = api.runtime?.config;
31
+ const afterWrite = { mode: "none", reason: "workclaw-accounts-api" };
32
+ if (runtimeConfig?.mutateConfigFile) {
33
+ await runtimeConfig.mutateConfigFile({
34
+ afterWrite,
35
+ mutate(draft) {
36
+ for (const key of Object.keys(draft)) {
37
+ if (!(key in config)) {
38
+ delete draft[key];
39
+ }
40
+ }
41
+ Object.assign(draft, JSON.parse(JSON.stringify(config)));
42
+ }
43
+ });
44
+ return;
45
+ }
46
+ if (runtimeConfig?.replaceConfigFile) {
47
+ await runtimeConfig.replaceConfigFile(config, afterWrite);
48
+ return;
49
+ }
28
50
  if (runtimeConfig?.writeConfigFile) {
29
51
  await runtimeConfig.writeConfigFile(config);
30
52
  return;
@@ -27,16 +27,94 @@ async function writeConfigFile(newConfig, cfg, log) {
27
27
  try {
28
28
  const runtime = getWorkclawRuntime();
29
29
  const configApi = runtime.config;
30
+ const afterWrite = {
31
+ mode: "auto",
32
+ reason: "workclaw-full-config-write"
33
+ };
30
34
  logger.info(`[智小途-配置] 准备写入配置 (channels/openclaw-workclaw/accounts): ${JSON.stringify(newConfig?.channels?.["openclaw-workclaw"]?.accounts, null, 2)}`);
31
35
  logger.info(`[智小途-配置] 准备写入配置 (models): ${JSON.stringify(newConfig?.models, null, 2)}`);
32
- await configApi.writeConfigFile(newConfig);
33
- logger.info(`[智小途-配置] 配置写入成功`);
34
- Object.assign(cfg, newConfig);
36
+ if (configApi.mutateConfigFile) {
37
+ const result = await configApi.mutateConfigFile({
38
+ mutate: (draft) => {
39
+ replaceObjectContents(draft, newConfig);
40
+ },
41
+ afterWrite
42
+ });
43
+ logger.info("[智小途-配置] 配置写入成功(使用 mutateConfigFile)");
44
+ logConfigWriteResult(result, log);
45
+ } else if (configApi.replaceConfigFile) {
46
+ const result = await configApi.replaceConfigFile(newConfig, afterWrite);
47
+ logger.info("[智小途-配置] 配置写入成功(使用 replaceConfigFile)");
48
+ logConfigWriteResult(result, log);
49
+ } else if (configApi.writeConfigFile) {
50
+ await configApi.writeConfigFile(newConfig);
51
+ logger.info("[智小途-配置] 配置写入成功(使用 writeConfigFile - 兼容模式)");
52
+ } else {
53
+ throw new Error("No config write API available");
54
+ }
55
+ replaceObjectContents(cfg, newConfig);
35
56
  } catch (err) {
36
57
  logger.error(`[智小途-配置] 写入配置失败: ${String(err)}`);
37
58
  throw err;
38
59
  }
39
60
  }
61
+ async function mutateConfigPatch(cfg, mutate, log, reason = "workclaw-config-patch") {
62
+ const runtime = getWorkclawRuntime();
63
+ const configApi = runtime.config;
64
+ const afterWrite = {
65
+ mode: "none",
66
+ reason
67
+ };
68
+ if (configApi.mutateConfigFile) {
69
+ const result = await configApi.mutateConfigFile({
70
+ mutate,
71
+ afterWrite
72
+ });
73
+ logConfigWriteResult(result, log);
74
+ } else {
75
+ const nextConfig = cloneConfig(cfg);
76
+ mutate(nextConfig);
77
+ if (configApi.replaceConfigFile) {
78
+ const result = await configApi.replaceConfigFile(nextConfig, afterWrite);
79
+ logConfigWriteResult(result, log);
80
+ } else if (configApi.writeConfigFile) {
81
+ await configApi.writeConfigFile(nextConfig);
82
+ } else {
83
+ throw new Error("No config write API available");
84
+ }
85
+ }
86
+ mutate(cfg);
87
+ }
88
+ function replaceObjectContents(target, source) {
89
+ if (target == null || typeof target !== "object")
90
+ return;
91
+ if (source == null || typeof source !== "object")
92
+ return;
93
+ for (const key of Object.keys(target)) {
94
+ if (!(key in source)) {
95
+ delete target[key];
96
+ }
97
+ }
98
+ const clonedSource = cloneConfig(source);
99
+ Object.assign(target, clonedSource);
100
+ }
101
+ function cloneConfig(value) {
102
+ return JSON.parse(JSON.stringify(value));
103
+ }
104
+ function logConfigWriteResult(result, log) {
105
+ if (!result)
106
+ return;
107
+ const serialized = JSON.stringify(result);
108
+ log?.info?.(`[智小途-配置] 写入结果: ${serialized}`);
109
+ const mode = result.followUp?.mode ?? result.afterWrite?.mode;
110
+ if (mode === "restart") {
111
+ log?.info?.("[智小途-配置] 框架要求重启后生效");
112
+ } else if (mode === "auto") {
113
+ log?.info?.("[智小途-配置] 框架将按自动策略评估 reload/restart");
114
+ } else if (mode === "none") {
115
+ log?.info?.("[智小途-配置] 配置按 none 策略写入,不请求自动重启");
116
+ }
117
+ }
40
118
  function loadOpenConversationId(accountId, userId) {
41
119
  const statePath = getStateFilePath();
42
120
  const key = `${accountId}:${userId}`;
@@ -109,35 +187,55 @@ async function initWorkclawAgent(params, cfg, log) {
109
187
  const workclawCfg = cfg?.channels?.[PLUGIN_ID];
110
188
  if (!workclawCfg)
111
189
  return;
190
+ const accountId = params.accountId || "default";
191
+ const existingApiKey = cfg.models?.providers?.[DEFAULT_PROVIDER]?.apiKey;
192
+ const shouldWriteApiKey = Boolean(
193
+ params.apiKey && !(existingApiKey && typeof existingApiKey === "object" && existingApiKey.type)
194
+ );
112
195
  if (params.apiKey) {
113
- const existingApiKey = cfg.models?.providers?.[DEFAULT_PROVIDER]?.apiKey;
114
196
  if (existingApiKey && typeof existingApiKey === "object" && existingApiKey.type) {
115
197
  logger.info(`[智小途-智能体] apiKey 是 SecretRef (type=${existingApiKey.type}),跳过覆盖`);
116
198
  } else {
117
- if (!cfg.models)
118
- cfg.models = {};
119
- if (!cfg.models.providers)
120
- cfg.models.providers = {};
121
- if (!cfg.models.providers[DEFAULT_PROVIDER])
122
- cfg.models.providers[DEFAULT_PROVIDER] = {};
123
- cfg.models.providers[DEFAULT_PROVIDER].apiKey = params.apiKey;
124
199
  logger.info(`[智小途-智能体] apiKey 已设置(明文值)`);
125
200
  }
126
201
  }
127
202
  if (params.agentId) {
128
- const accountId = params.accountId || "default";
129
- const accounts = workclawCfg.accounts ?? {};
130
- workclawCfg.accounts = accounts;
131
- if (!accounts[accountId])
132
- accounts[accountId] = {};
133
- accounts[accountId].agentId = params.agentId;
134
203
  logger.info(`[智小途-智能体] agentId=${params.agentId} 已设置 accountId=${accountId}`);
135
204
  }
136
205
  if (params.userId) {
137
- workclawCfg.userId = params.userId;
138
206
  logger.info(`[智小途-智能体] userId=${params.userId} 已设置`);
139
207
  }
140
- await writeConfigFile(cfg, cfg, log);
208
+ await mutateConfigPatch(
209
+ cfg,
210
+ (draft) => {
211
+ if (!draft.channels)
212
+ draft.channels = {};
213
+ if (!draft.channels[PLUGIN_ID])
214
+ draft.channels[PLUGIN_ID] = {};
215
+ const draftWorkclawCfg = draft.channels[PLUGIN_ID];
216
+ if (shouldWriteApiKey) {
217
+ if (!draft.models)
218
+ draft.models = {};
219
+ if (!draft.models.providers)
220
+ draft.models.providers = {};
221
+ if (!draft.models.providers[DEFAULT_PROVIDER])
222
+ draft.models.providers[DEFAULT_PROVIDER] = {};
223
+ draft.models.providers[DEFAULT_PROVIDER].apiKey = params.apiKey;
224
+ }
225
+ if (params.agentId) {
226
+ const accounts = draftWorkclawCfg.accounts ?? {};
227
+ draftWorkclawCfg.accounts = accounts;
228
+ if (!accounts[accountId])
229
+ accounts[accountId] = {};
230
+ accounts[accountId].agentId = params.agentId;
231
+ }
232
+ if (params.userId) {
233
+ draftWorkclawCfg.userId = params.userId;
234
+ }
235
+ },
236
+ log,
237
+ "workclaw-init-agent"
238
+ );
141
239
  logger.info(`[智小途-智能体] 所有配置已持久化`);
142
240
  } catch (err) {
143
241
  logger.error(`[智小途-智能体] 初始化失败: ${String(err)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"accounts-api.d.ts","sourceRoot":"","sources":["../../../src/api/accounts-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAgE5D,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,iBAAiB,IAC/C,KAAK,GAAG,EAAE,KAAK,GAAG,mBA2FjC"}
1
+ {"version":3,"file":"accounts-api.d.ts","sourceRoot":"","sources":["../../../src/api/accounts-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AA0F5D,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,iBAAiB,IAC/C,KAAK,GAAG,EAAE,KAAK,GAAG,mBA2FjC"}
@@ -13,10 +13,11 @@ interface ConfigLogger {
13
13
  /**
14
14
  * 将新配置写入 openclaw.json 文件。
15
15
  *
16
- * 注意:
17
- * 1. newConfig 已经是完整的配置对象(调用方已通过 { ...cfg, ... } 构建)
18
- * 2. 直接使用 writeConfigFile 而不是 mutateConfigFile,因为后者的 mutate 回调有嵌套属性更新问题
19
- * 3. writeConfigFile 虽然标记为 deprecated,但仍是官方插件使用的兼容方法,仅打印警告不影响功能
16
+ * 正确的实现方式:
17
+ * 1. 优先使用 mutateConfigFile(官方推荐)
18
+ * 2. 必须指定 afterWrite 策略
19
+ * 3. 处理返回值中的 followUp 意图
20
+ * 4. 使用深合并策略处理嵌套属性
20
21
  */
21
22
  export declare function writeConfigFile(newConfig: any, cfg: any, log?: ConfigLogger): Promise<void>;
22
23
  /**
@@ -67,11 +68,9 @@ export declare function saveWorkClawUserId(accountId: string, userId: string | n
67
68
  */
68
69
  export declare function saveWorkClawAgentId(accountId: string, agentId: string | number, cfg: any, log?: ConfigLogger): Promise<void>;
69
70
  /**
70
- * 仅将 apiKey 保存到配置内存。
71
+ * apiKey 保存到模型配置并持久化到 openclaw.json。
71
72
  *
72
- * 不写入 openclaw.json 以避免触发 openclaw 的配置监听器,
73
- * 否则会重新解析环境变量(如 ${MODEL_API_KEY})
74
- * 并可能覆盖运行时配置中已解析的值。
73
+ * 使用局部 mutate,避免把整份运行时快照回写到配置文件。
75
74
  */
76
75
  export declare function saveWorkClawApiKey(apiKey: string, cfg: any, log?: ConfigLogger): Promise<void>;
77
76
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"config-writer.d.ts","sourceRoot":"","sources":["../../../src/gateway/config-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmBH,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B;AAsCD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,GAAG,EACd,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAavF;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAmBf;AA4DD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE;IACN,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,EACD,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CA+Cf;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAmCf;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAkBf"}
1
+ {"version":3,"file":"config-writer.d.ts","sourceRoot":"","sources":["../../../src/gateway/config-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmBH,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B;AA2ED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,GAAG,EACd,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAyCf;AAoHD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAavF;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAmBf;AA4DD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE;IACN,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,EACD,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAuEf;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CA0Cf;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,GAAG,EACR,GAAG,CAAC,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAuBf"}
@@ -35,9 +35,21 @@
35
35
  "description": "智小途企业通讯平台",
36
36
  "schema": {
37
37
  "type": "object",
38
- "additionalProperties": true,
38
+ "additionalProperties": false,
39
39
  "required": ["appKey", "appSecret"],
40
40
  "properties": {
41
+ "enabled": {
42
+ "type": "boolean",
43
+ "title": "是否启用"
44
+ },
45
+ "baseUrl": {
46
+ "type": "string",
47
+ "title": "接口地址"
48
+ },
49
+ "websocketUrl": {
50
+ "type": "string",
51
+ "title": "WebSocket 地址"
52
+ },
41
53
  "appKey": {
42
54
  "type": "string",
43
55
  "description": "智小途应用 Key",
@@ -47,6 +59,168 @@
47
59
  "type": "string",
48
60
  "description": "智小途应用 Secret",
49
61
  "title": "应用 Secret"
62
+ },
63
+ "agentId": {
64
+ "title": "默认智能体 ID",
65
+ "anyOf": [
66
+ {
67
+ "type": "string"
68
+ },
69
+ {
70
+ "type": "number"
71
+ }
72
+ ]
73
+ },
74
+ "userId": {
75
+ "title": "默认用户 ID",
76
+ "anyOf": [
77
+ {
78
+ "type": "string"
79
+ },
80
+ {
81
+ "type": "number"
82
+ }
83
+ ]
84
+ },
85
+ "localIp": {
86
+ "type": "string",
87
+ "title": "本地 IP"
88
+ },
89
+ "requestTimeout": {
90
+ "type": "integer",
91
+ "minimum": 1,
92
+ "title": "请求超时毫秒"
93
+ },
94
+ "allowInsecureTls": {
95
+ "type": "boolean",
96
+ "title": "允许不安全 TLS"
97
+ },
98
+ "allowRawJsonPayload": {
99
+ "type": "boolean",
100
+ "title": "允许原始 JSON 负载"
101
+ },
102
+ "uploadUrl": {
103
+ "type": "string",
104
+ "title": "上传地址"
105
+ },
106
+ "uploadFieldName": {
107
+ "type": "string",
108
+ "title": "上传字段名"
109
+ },
110
+ "uploadHeaders": {
111
+ "type": "object",
112
+ "additionalProperties": {
113
+ "type": "string"
114
+ },
115
+ "title": "上传请求头"
116
+ },
117
+ "uploadFormFields": {
118
+ "type": "object",
119
+ "additionalProperties": {
120
+ "anyOf": [
121
+ {
122
+ "type": "string"
123
+ },
124
+ {
125
+ "type": "number"
126
+ },
127
+ {
128
+ "type": "boolean"
129
+ }
130
+ ]
131
+ },
132
+ "title": "上传表单字段"
133
+ },
134
+ "uploadResponseUrlPath": {
135
+ "type": "string",
136
+ "title": "上传响应 URL 路径"
137
+ },
138
+ "dmPolicy": {
139
+ "type": "string",
140
+ "enum": ["open"],
141
+ "title": "私聊策略"
142
+ },
143
+ "allowFrom": {
144
+ "type": "array",
145
+ "title": "允许发送者",
146
+ "items": {
147
+ "anyOf": [
148
+ {
149
+ "type": "string"
150
+ },
151
+ {
152
+ "type": "number"
153
+ }
154
+ ]
155
+ }
156
+ },
157
+ "mediaMaxMb": {
158
+ "type": "number",
159
+ "exclusiveMinimum": 0,
160
+ "title": "媒体大小上限"
161
+ },
162
+ "accounts": {
163
+ "type": "object",
164
+ "title": "账户配置",
165
+ "additionalProperties": {
166
+ "type": "object",
167
+ "additionalProperties": false,
168
+ "properties": {
169
+ "enabled": {
170
+ "type": "boolean"
171
+ },
172
+ "name": {
173
+ "type": "string"
174
+ },
175
+ "agentId": {
176
+ "anyOf": [
177
+ {
178
+ "type": "string"
179
+ },
180
+ {
181
+ "type": "number"
182
+ }
183
+ ]
184
+ },
185
+ "userId": {
186
+ "anyOf": [
187
+ {
188
+ "type": "string"
189
+ },
190
+ {
191
+ "type": "number"
192
+ }
193
+ ]
194
+ },
195
+ "openConversationId": {
196
+ "type": "string"
197
+ },
198
+ "dmPolicy": {
199
+ "type": "string",
200
+ "enum": ["open"]
201
+ },
202
+ "allowFrom": {
203
+ "type": "array",
204
+ "items": {
205
+ "anyOf": [
206
+ {
207
+ "type": "string"
208
+ },
209
+ {
210
+ "type": "number"
211
+ }
212
+ ]
213
+ }
214
+ },
215
+ "mediaMaxMb": {
216
+ "type": "number",
217
+ "exclusiveMinimum": 0
218
+ },
219
+ "allowInsecureTls": {
220
+ "type": "boolean"
221
+ }
222
+ }
223
+ }
50
224
  }
51
225
  }
52
226
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@workclaw/openclaw-workclaw",
3
3
  "type": "module",
4
- "version": "1.0.335",
4
+ "version": "1.0.337",
5
5
  "description": "智小途企业通讯平台 OpenClaw 渠道插件",
6
6
  "license": "MIT",
7
7
  "keywords": [