@coclaw/openclaw-coclaw 0.26.0 → 0.26.2

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/index.js CHANGED
@@ -208,10 +208,10 @@ const plugin = {
208
208
  // 防"写配置触发重启"时的反复重启)。升级补了新模型靠这条让老用户重启后自动同步。
209
209
  // config-mutation 字面量 specifier 必须出现在本入口源码里(loader 只扫入口识别 jiti alias)。
210
210
  const portalSyncP = import('openclaw/plugin-sdk/config-mutation')
211
- .then(({ mutateConfigFile }) => reconcilePortalModels({ getConfig: getClawConfig, mutateConfigFile }))
212
- .then((r) => { if (r.changed) logger.info?.('[coclaw] minimax-portal model list synced from plugin catalog'); })
211
+ .then(({ mutateConfigFile }) => reconcilePortalModels({ getConfig: getClawConfig, mutateConfigFile, logger, remoteLog }))
213
212
  .catch((err) => {
214
213
  logger.warn?.(`[coclaw] minimax-portal model reconcile failed: ${String(err?.message ?? err)}`);
214
+ remoteLog(`providerAuth.portalReconcile.failed reason=${String(err?.message ?? err)}`);
215
215
  });
216
216
  __pluginInitDone = Promise.all([topicLoadP, chatHistoryLoadP, portalSyncP]);
217
217
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coclaw/openclaw-coclaw",
3
- "version": "0.26.0",
3
+ "version": "0.26.2",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "OpenClaw plugin for remote chat over WebRTC. Run `openclaw coclaw enroll` after install.",
@@ -71,6 +71,14 @@
71
71
  "werift": "^0.19.0",
72
72
  "ws": "^8.19.0"
73
73
  },
74
+ "peerDependencies": {
75
+ "openclaw": ">=2026.3.22"
76
+ },
77
+ "peerDependenciesMeta": {
78
+ "openclaw": {
79
+ "optional": true
80
+ }
81
+ },
74
82
  "devDependencies": {
75
83
  "c8": "^10.1.3",
76
84
  "eslint": "^9.39.2"
@@ -15,15 +15,18 @@
15
15
  * 返回的 proper-case,name 用展示名。
16
16
  * - 只维护**最必须的运行元数据**:`reasoning`(是否推理模型——缺省会被当成 false,导致推理
17
17
  * 模型被按普通模型处理、思考模式出错)、`contextWindow`、`maxTokens`。**不写 `cost`**:
18
- * portal 走 token plan、不按量计费,价格无意义;`input` 也不写(系统默认即 `['text']`)。
18
+ * portal 走 token plan、不按量计费,价格无意义;`input` 默认不写(系统默认即 `['text']`),
19
+ * 除非该型号多模态(如 M3 支持 `['text', 'image']`)才显式写出。
19
20
  * 这几个值与上游 `model-definitions.ts`(DEFAULT_MINIMAX_CONTEXT_WINDOW=204800 /
20
- * DEFAULT_MINIMAX_MAX_TOKENS=131072)+ `provider-models.ts`(reasoning 标记)对齐。
21
+ * MINIMAX_M3_CONTEXT_WINDOW=1_000_000 / DEFAULT_MINIMAX_MAX_TOKENS=131072)+
22
+ * `provider-models.ts`(reasoning / input 标记)对齐。
21
23
  */
22
24
 
23
25
  export const PORTAL_MODEL_CATALOG = {
24
- // 与 openclaw-repo/extensions/minimax/ 的 provider-models.ts(reasoning) +
25
- // model-definitions.ts(contextWindow/maxTokens) 对齐
26
+ // 与 openclaw-repo/extensions/minimax/ 的 provider-models.ts(reasoning/input) +
27
+ // model-definitions.ts(contextWindow/maxTokens) 对齐;M3 起为上游默认型号、放首位
26
28
  'minimax-portal': [
29
+ { id: 'MiniMax-M3', name: 'MiniMax M3', reasoning: true, input: ['text', 'image'], contextWindow: 1000000, maxTokens: 131072 },
27
30
  { id: 'MiniMax-M2.7', name: 'MiniMax M2.7', reasoning: true, contextWindow: 204800, maxTokens: 131072 },
28
31
  { id: 'MiniMax-M2.7-highspeed', name: 'MiniMax M2.7 Highspeed', reasoning: true, contextWindow: 204800, maxTokens: 131072 },
29
32
  ],
@@ -31,15 +34,16 @@ export const PORTAL_MODEL_CATALOG = {
31
34
 
32
35
  /**
33
36
  * 取某 provider 的静态清单。返回**深拷贝**,避免调用方改到共享常量。
34
- * 未知 provider → 空数组。条目字段为扁平基本类型,`{ ...m }` 即为完整深拷贝。
37
+ * 未知 provider → 空数组。用 `structuredClone` 而非 `{ ...m }`:条目含嵌套字段(如 M3 的
38
+ * `input` 数组),浅拷贝会让返回值与共享常量复用同一个数组引用,调用方 push/splice 即污染原表。
35
39
  *
36
40
  * @param {string} providerId
37
- * @returns {{id:string, name:string, reasoning?:boolean, contextWindow?:number, maxTokens?:number}[]}
41
+ * @returns {{id:string, name:string, reasoning?:boolean, input?:string[], contextWindow?:number, maxTokens?:number}[]}
38
42
  */
39
43
  export function getPortalModels(providerId) {
40
44
  const list = PORTAL_MODEL_CATALOG[providerId];
41
45
  if (!Array.isArray(list)) return [];
42
- return list.map((m) => ({ ...m }));
46
+ return list.map((m) => structuredClone(m));
43
47
  }
44
48
 
45
49
  /**
@@ -21,25 +21,35 @@ import { getPortalModels, portalModelsCoveredById } from './portal-model-catalog
21
21
  * @param {Function} opts.getConfig - () → 当前 cfg 快照(getClawConfig);null/缺时跳过
22
22
  * @param {Function} opts.mutateConfigFile - openclaw/plugin-sdk/config-mutation 的写盘入口
23
23
  * @param {string} [opts.providerId] - 默认 minimax-portal
24
+ * @param {object} [opts.logger] - gateway 注入的 pino 风格 logger(本地诊断)
25
+ * @param {Function} [opts.remoteLog] - remoteLog(text)(远程诊断;启动一次、低频)
24
26
  * @returns {Promise<{changed:boolean, reason:string}>} reason: no-config|not-bound|no-catalog|in-sync|updated
25
27
  */
26
- export async function reconcilePortalModels({ getConfig, mutateConfigFile, providerId = PORTAL_PROVIDER_ID }) {
28
+ export async function reconcilePortalModels({ getConfig, mutateConfigFile, providerId = PORTAL_PROVIDER_ID, logger, remoteLog }) {
27
29
  const cfg = getConfig?.();
28
- // runtime 未注入 / config 不可读:跳过,下次启动再对
30
+ // runtime 未注入 / config 不可读:跳过,下次启动再对(非事件,不记诊断)
29
31
  if (!cfg || typeof cfg !== 'object') return { changed: false, reason: 'no-config' };
30
32
  const node = cfg.models?.providers?.[providerId];
31
- // 未绑定(无 provider 节点)→ 不碰。登录成功时已写过节点 + 清单,绑定后才谈得上对账
33
+ // 未绑定(无 provider 节点)→ 不碰。登录成功时已写过节点 + 清单,绑定后才谈得上对账。
34
+ // 同属非事件(未用该 provider 的网关每次启动都会到这)→ 不记诊断,避免刷屏
32
35
  if (!node || typeof node !== 'object' || Array.isArray(node)) return { changed: false, reason: 'not-bound' };
36
+ // 到这里 provider 已绑定,"模型清单是否需要注入"才成立——后续每个结果都记诊断(本地 log + remoteLog)。
37
+ // remoteLog 一次启动一条、低频,符合约定。
33
38
  const target = getPortalModels(providerId);
39
+ const finish = (changed, reason, count) => {
40
+ logger?.info?.(`[coclaw] ${providerId} model reconcile: ${changed ? 'synced' : 'no write'} (reason=${reason}, models=${count})`);
41
+ remoteLog?.(`providerAuth.portalReconcile reason=${reason} changed=${changed} provider=${providerId} models=${count}`);
42
+ return { changed, reason };
43
+ };
34
44
  // 表里没这个 provider(理论不该发生)→ 不动用户已有清单
35
- if (target.length === 0) return { changed: false, reason: 'no-catalog' };
45
+ if (target.length === 0) return finish(false, 'no-catalog', 0);
36
46
  // 只按 id 判"已覆盖":目标里每个 model id 都已在配置现有清单出现 → 视为已同步、零写入。
37
47
  // 比"全等"宽容——配置是我们的超集(别的来源,如官方 MiniMax 插件,多写了几个模型)时也判已覆盖、
38
48
  // 不去动它,避免和它来回覆盖、反复重启。仅当配置缺了我们某个 id(升级新增模型 / 老配置不全)才写。
39
49
  // 顺带说清读/写不对称:getConfig 读「解析后」配置(config.current()),mutateConfigFile 默认写
40
50
  // 「源」配置。即便上游将来在解析期给第三方 portal 注入额外模型,那也只是让配置成超集、我们的 id 仍在
41
51
  // → 判已覆盖 → 不写,不会触发"永远判不一致、每次启动都写"的循环。
42
- if (portalModelsCoveredById(node.models, target)) return { changed: false, reason: 'in-sync' };
52
+ if (portalModelsCoveredById(node.models, target)) return finish(false, 'in-sync', target.length);
43
53
 
44
54
  await mutateConfigFile({
45
55
  afterWrite: { mode: 'auto' },
@@ -50,5 +60,5 @@ export async function reconcilePortalModels({ getConfig, mutateConfigFile, provi
50
60
  p.models = getPortalModels(providerId);
51
61
  },
52
62
  });
53
- return { changed: true, reason: 'updated' };
63
+ return finish(true, 'updated', target.length);
54
64
  }