@coclaw/openclaw-coclaw 0.25.1 → 0.26.0

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
@@ -810,11 +810,14 @@ const plugin = {
810
810
  loadSdk: () => import('openclaw/plugin-sdk/provider-auth'),
811
811
  loadConfigMutation: () => import('openclaw/plugin-sdk/config-mutation'),
812
812
  // provider-catalog-runtime 供通用 device-code 扫码登录(B1)拿 resolvePluginProviders,
813
- // 驱动 provider 自带的 device_code 登录方法(codex/copilot 及以后任意 device_code provider
813
+ // 驱动 provider 自带的 device_code 登录方法(codex/copilot 及以后任意 device_code provider);
814
+ // 同时供 providerAuth.catalog 拿 setup 全集(mode:'setup')
814
815
  loadProviderCatalogRuntime: () => import('openclaw/plugin-sdk/provider-catalog-runtime'),
816
+ // agent-runtime 供 providerAuth.catalog 的 hasCred 别名归一基座 id(resolveProviderIdForAuth)
817
+ loadAgentRuntime: () => import('openclaw/plugin-sdk/agent-runtime'),
815
818
  });
816
819
 
817
- // 模型默认配置 RPC(coclaw.model.set / list / listUsable)。三个 SDK 子入口的字面量
820
+ // 模型默认配置 RPC(coclaw.model.set / list / listAvailable + listUsable 过渡别名)。三个 SDK 子入口的字面量
818
821
  // dynamic import 必须留在本入口源码——OpenClaw plugin loader 只扫入口源码
819
822
  // 命中 `openclaw/plugin-sdk/*` 字面量并触发 jiti 重写;藏在子模块的字面量
820
823
  // loader 看不到 → 原生 Node 解析必败。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coclaw/openclaw-coclaw",
3
- "version": "0.25.1",
3
+ "version": "0.26.0",
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.",
@@ -55,6 +55,10 @@
55
55
  "verify": "pnpm check && pnpm test",
56
56
  "link": "bash ./scripts/link.sh",
57
57
  "unlink": "bash ./scripts/unlink.sh",
58
+ "wt:up": "bash ./scripts/worktree-gateway.sh up",
59
+ "wt:reload": "bash ./scripts/worktree-gateway.sh reload",
60
+ "wt:call": "bash ./scripts/worktree-gateway.sh call",
61
+ "wt:down": "bash ./scripts/worktree-gateway.sh down",
58
62
  "install:npm": "bash ./scripts/install-npm.sh",
59
63
  "uninstall:npm": "bash ./scripts/uninstall-npm.sh",
60
64
  "release:pre": "bash ./scripts/prerelease.sh",
@@ -1,18 +1,19 @@
1
1
  /**
2
- * model-default/handlers.js —— coclaw.model.set / list / listUsable 三个 RPC 的纯函数实现
2
+ * model-default/handlers.js —— coclaw.model.set / list / listAvailable 三个 RPC 的纯函数实现
3
+ * (listAvailable 即原 listUsable 改名;index.js 把 listUsable 留作过渡别名映到同一 handler)
3
4
  *
4
5
  * 设计要点(详见 docs/model-config-api.md § 3):
5
6
  * - DI 注入 sdk(mutateConfigFile / loadModelCatalog / provider-auth 凭据探针 / resolveProviderIdForAuth)
6
7
  * + loadConfig + resolveAgentDir,便于单测;产线注入在 ./index.js
7
8
  * - **出参不加 status wrap**(gateway-method-design skill 约定):set → {};list → { default, agents };
8
- * listUsable → { byProvider, configuredProviders }
9
+ * listAvailable → { byProvider }(configuredProviders 已迁出,UI 加 provider 排除改吃 catalog.hasCred)
9
10
  * - 错误码只用 INVALID_ARGS / IO_FAILED,参考 provider-auth/handlers.js
10
11
  * 既有 plugin 的 respondError 用 INTERNAL_ERROR 与本节契约不一致,所以本模块自带局部 helper
11
12
  * - set 校验 fail-fast 顺序:params shape → 拒未知字段 → agentId → primary 类型 → primary 形态
12
13
  * (纯字符串:含 '/'、'/' 不在端点;不依赖 cfg)→ loadConfig → 凭据门 → 存在性
13
14
  * 形态校验**前置在 loadConfig 之前**,cfg 不可读时非法形态仍是 INVALID_ARGS 而非 IO_FAILED
14
15
  * - 凭据门 + 选模型器枚举 + list 信号全部走统一别名感知原语(resolve.js),杜绝跨界面口径分叉(§ 3.2.1)
15
- * - set 存在性 + listUsable 枚举走同一目录源 loadModelCatalog({readOnly:false}):选得到 ⇒ 设得上(红线天然成立)。
16
+ * - set 存在性 + listAvailable 枚举走同一目录源 loadModelCatalog({readOnly:false}):选得到 ⇒ 设得上(红线天然成立)。
16
17
  * 用 readOnly:false(含 manifest 合并)才带进 openai-codex/* 等 manifest-only provider;readOnly:true 只读落盘,
17
18
  * 这类从不落盘的 provider 缺失(oauth 已授权却选不出,本次回归根因)。
18
19
  */
@@ -21,7 +22,7 @@ import { listAllPrimariesWithCredentials, computeProviderUsable, enumerateUsable
21
22
  import { writePrimary } from './persist.js';
22
23
 
23
24
  const SET_ALLOWED_KEYS = new Set(['agentId', 'primary']);
24
- const LISTUSABLE_ALLOWED_KEYS = new Set(['agentId']);
25
+ const LISTAVAILABLE_ALLOWED_KEYS = new Set(['agentId']);
25
26
 
26
27
  function respondInvalid(respond, message) {
27
28
  respond(false, undefined, { code: 'INVALID_ARGS', message });
@@ -52,7 +53,7 @@ function parseProviderModel(primary) {
52
53
  }
53
54
 
54
55
  /**
55
- * 构造 set / listUsable 用的统一别名感知凭据 deps。
56
+ * 构造 set / listAvailable 用的统一别名感知凭据 deps。
56
57
  * @param {object} sdk
57
58
  * @param {string} agentDir
58
59
  * @returns {object}
@@ -95,19 +96,19 @@ async function validateProviderCredAndCatalog({ provider, model, primary, cfg, s
95
96
  }
96
97
 
97
98
  /**
98
- * 构造 set / list / listUsable 三个 handler。
99
+ * 构造 set / list / listAvailable 三个 handler。
99
100
  *
100
101
  * @param {object} opts
101
102
  * @param {object} opts.sdk
102
103
  * @param {Function} opts.sdk.mutateConfigFile - openclaw/plugin-sdk/config-mutation(set 写盘)
103
- * @param {Function} opts.sdk.loadModelCatalog - openclaw/plugin-sdk/agent-runtime(set 存在性 + listUsable 枚举的目录源)
104
+ * @param {Function} opts.sdk.loadModelCatalog - openclaw/plugin-sdk/agent-runtime(set 存在性 + listAvailable 枚举的目录源)
104
105
  * @param {Function} opts.sdk.isProviderApiKeyConfigured - openclaw/plugin-sdk/provider-auth(env+账本凭据信号,别名感知)
105
106
  * @param {Function} opts.sdk.hasConfiguredSecretInput - openclaw/plugin-sdk/provider-auth(内联 key 判定)
106
- * @param {Function} opts.sdk.ensureAuthProfileStore - openclaw/plugin-sdk/provider-auth(账本非空 / configuredProviders)
107
+ * @param {Function} opts.sdk.ensureAuthProfileStore - openclaw/plugin-sdk/provider-auth(账本非空判定)
107
108
  * @param {Function} opts.sdk.resolveProviderIdForAuth - openclaw/plugin-sdk/agent-runtime(别名归一)
108
109
  * @param {Function} opts.loadConfig - 返回当前 cfg snapshot;缺失时返回 null
109
110
  * @param {Function} opts.resolveAgentDir - 返回 agent /agent 子目录全路径(默认 main agent;agentId 贯穿)
110
- * @returns {{ set: Function, list: Function, listUsable: Function }}
111
+ * @returns {{ set: Function, list: Function, listAvailable: Function }}
111
112
  */
112
113
  export function buildModelDefaultHandlers({ sdk, loadConfig, resolveAgentDir }) {
113
114
  async function set({ params, respond }) {
@@ -207,14 +208,14 @@ export function buildModelDefaultHandlers({ sdk, loadConfig, resolveAgentDir })
207
208
  }
208
209
  }
209
210
 
210
- async function listUsable({ params, respond }) {
211
+ async function listAvailable({ params, respond }) {
211
212
  try {
212
213
  if (!params || typeof params !== 'object' || Array.isArray(params)) {
213
214
  respondInvalid(respond, 'params must be an object');
214
215
  return;
215
216
  }
216
217
  for (const key of Object.keys(params)) {
217
- if (!LISTUSABLE_ALLOWED_KEYS.has(key)) {
218
+ if (!LISTAVAILABLE_ALLOWED_KEYS.has(key)) {
218
219
  respondInvalid(respond, `unknown field: ${key}`);
219
220
  return;
220
221
  }
@@ -238,15 +239,10 @@ export function buildModelDefaultHandlers({ sdk, loadConfig, resolveAgentDir })
238
239
 
239
240
  // 目录源 loadModelCatalog({readOnly:false}):含 manifest 合并,才有 openai-codex/* 这类 manifest-only provider
240
241
  // (readOnly:true 只读落盘缺它们 → oauth 已授权却选不出,本次回归根因)。
241
- // 整体抛错(罕见,如 runtime config 取不到)→ 兜空 entries:byProvider 退化为空,
242
- // configuredProviders 不依赖目录仍可算 → 「不空白」,UI 加 provider 排除照常工作(§ 3.2.1 降级)。
243
- let entries;
244
- try {
245
- entries = await sdk.loadModelCatalog({ readOnly: false });
246
- }
247
- catch {
248
- entries = [];
249
- }
242
+ // 抛错(罕见,如 runtime config 取不到)→ 走外层 catch 映射 IO_FAILED,不吞成空 entries:
243
+ // byProvider 同时驱动前端 primary 有效性(计算属性),把"清单没加载出来"伪装成"权威空清单"会让前端
244
+ // 误报主模型失效。如实暴露失败 → UI 维持 available=null="先不下结论",与"真空(无凭据)"区分开。
245
+ const entries = await sdk.loadModelCatalog({ readOnly: false });
250
246
 
251
247
  respond(true, enumerateUsableModels(entries, cfg, deps));
252
248
  }
@@ -255,5 +251,5 @@ export function buildModelDefaultHandlers({ sdk, loadConfig, resolveAgentDir })
255
251
  }
256
252
  }
257
253
 
258
- return { set, list, listUsable };
254
+ return { set, list, listAvailable };
259
255
  }
@@ -1,5 +1,6 @@
1
1
  /**
2
- * model-default 注册入口 —— 把 coclaw.model.set / list / listUsable 接到 gateway
2
+ * model-default 注册入口 —— 把 coclaw.model.set / list / listAvailable 接到 gateway
3
+ *(listAvailable 即原 listUsable 改名;listUsable 作过渡别名继续注册、映到同一 handler)。
3
4
  *
4
5
  * 设计(同 provider-auth/index.js):
5
6
  * - 三个 SDK 子入口(config-mutation / provider-auth / agent-runtime)懒加载,
@@ -34,7 +35,7 @@ function defaultLoadProviderAuth() {
34
35
  }
35
36
  function defaultLoadAgentRuntime() {
36
37
  // agent-runtime barrel 同时给:resolveProviderIdForAuth(别名归一)+ loadModelCatalog(干净目录,
37
- // set 存在性 / listUsable 选模型器枚举同源)。barrel re-export provider-auth-aliases.js + model-catalog.js。
38
+ // set 存在性 / listAvailable 选模型器枚举同源)。barrel re-export provider-auth-aliases.js + model-catalog.js。
38
39
  _agentRuntimeP ??= import('openclaw/plugin-sdk/agent-runtime');
39
40
  return _agentRuntimeP;
40
41
  }
@@ -49,7 +50,8 @@ export function __resetSdkCaches() {
49
50
  }
50
51
 
51
52
  /**
52
- * 在 gateway api 上注册 `coclaw.model.set` / `coclaw.model.list` / `coclaw.model.listUsable`。
53
+ * 在 gateway api 上注册 `coclaw.model.set` / `coclaw.model.list` / `coclaw.model.listAvailable`
54
+ *(外加过渡别名 `coclaw.model.listUsable`,与 listAvailable 映同一 handler)。
53
55
  *
54
56
  * 仅 `register(api)` 的 `if (api.registrationMode === 'full')` 分支调;
55
57
  * 其它 mode 注册副作用违规(参 plugins/openclaw/CLAUDE.md "Service / register 副作用边界")。
@@ -80,13 +82,13 @@ export function registerModelDefaultHandlers(api, opts = {}) {
80
82
  ]);
81
83
  const sdk = {
82
84
  mutateConfigFile: configMutation.mutateConfigFile,
83
- // 干净目录(set 存在性 + listUsable 枚举同源):agent-runtime barrel re-export model-catalog.js
85
+ // 干净目录(set 存在性 + listAvailable 枚举同源):agent-runtime barrel re-export model-catalog.js
84
86
  loadModelCatalog: agentRuntime.loadModelCatalog,
85
- // 凭据信号(providerUsable / hasAnyUsableCredential / 凭据门 / configuredProviders)
87
+ // 凭据信号(providerUsable / hasAnyUsableCredential / 凭据门)
86
88
  isProviderApiKeyConfigured: providerAuth.isProviderApiKeyConfigured,
87
89
  hasConfiguredSecretInput: providerAuth.hasConfiguredSecretInput,
88
90
  ensureAuthProfileStore: providerAuth.ensureAuthProfileStore,
89
- // 别名归一(内联凭据信号 + 选模型器枚举 + configuredProviders)
91
+ // 别名归一(内联凭据信号 + 选模型器枚举)
90
92
  resolveProviderIdForAuth: agentRuntime.resolveProviderIdForAuth,
91
93
  };
92
94
  return buildModelDefaultHandlers({ sdk, loadConfig, resolveAgentDir });
@@ -114,5 +116,7 @@ export function registerModelDefaultHandlers(api, opts = {}) {
114
116
 
115
117
  api.registerGatewayMethod('coclaw.model.set', wrap('set'));
116
118
  api.registerGatewayMethod('coclaw.model.list', wrap('list'));
117
- api.registerGatewayMethod('coclaw.model.listUsable', wrap('listUsable'));
119
+ api.registerGatewayMethod('coclaw.model.listAvailable', wrap('listAvailable'));
120
+ // 过渡别名:旧 UI 仍调 listUsable,映到同一 handler(决策1:纯过渡,不加任何额外兜底)
121
+ api.registerGatewayMethod('coclaw.model.listUsable', wrap('listAvailable'));
118
122
  }
@@ -285,7 +285,7 @@ export function computeConfiguredProviders(cfg, deps) {
285
285
  * @param {object[]} catalogEntries - loadModelCatalog({readOnly:false}) 的结果(ModelCatalogEntry[])
286
286
  * @param {object} cfg
287
287
  * @param {object} deps - { agentDir, isProviderApiKeyConfigured, hasConfiguredSecretInput, resolveProviderIdForAuth, ensureAuthProfileStore }
288
- * @returns {{ byProvider: Record<string, string[]>, configuredProviders: string[] }}
288
+ * @returns {{ byProvider: Record<string, string[]> }}
289
289
  */
290
290
  export function enumerateUsableModels(catalogEntries, cfg, deps) {
291
291
  const grouped = new Map(); // provider -> Set<modelId>
@@ -307,7 +307,9 @@ export function enumerateUsableModels(catalogEntries, cfg, deps) {
307
307
  byProvider[provider] = [...ids].sort();
308
308
  }
309
309
  }
310
- return { byProvider, configuredProviders: computeConfiguredProviders(cfg, deps) };
310
+ // 只返回 byProviderconfiguredProviders 已迁出(catalog 经 computeConfiguredProviders 单独算 hasCred,
311
+ // UI 加 provider 排除改吃 catalog.hasCred)。computeConfiguredProviders 仍具名导出供 catalog 复用。
312
+ return { byProvider };
311
313
  }
312
314
 
313
315
  /**
@@ -38,7 +38,7 @@ import { PORTAL_PROVIDER_ID, CONFIG_DEFAULT_BASE_URL, VALID_REGIONS } from './mi
38
38
  import { getPortalModels } from './portal-model-catalog.js';
39
39
  import { remoteLog } from '../remote-log.js';
40
40
  import { getClawConfig } from '../claw-config.js';
41
- import { listAllPrimaries, providerSegmentOf } from '../model-default/resolve.js';
41
+ import { listAllPrimaries, providerSegmentOf, computeConfiguredProviders } from '../model-default/resolve.js';
42
42
  import { deepMergeInto } from '../utils/deep-merge.js';
43
43
  import {
44
44
  isVerificationNote,
@@ -50,6 +50,14 @@ import {
50
50
  const VALID_CRED_TYPES = new Set(['api_key', 'oauth', 'token']);
51
51
  const PORTAL_PROFILE_ID = `${PORTAL_PROVIDER_ID}:default`;
52
52
 
53
+ // catalog 出参的 authMethods 映射(一条规则零特判):只露这三 kind,token/custom 不进出参。
54
+ // kind 五值见上游 types.ts(oauth|api_key|token|device_code|custom)。
55
+ const KIND_TO_AUTH_METHOD = {
56
+ device_code: 'oauth-device-code',
57
+ oauth: 'oauth-login',
58
+ api_key: 'api-key',
59
+ };
60
+
53
61
  function respondInvalid(respond, message) {
54
62
  respond(false, undefined, { code: 'INVALID_ARGS', message });
55
63
  }
@@ -84,7 +92,9 @@ function isNonEmptyString(v) {
84
92
  * @param {Function} [opts.logRemote] - (text) → void,OAuth 终态诊断推送;默认模块级 remoteLog(测试注入 spy)
85
93
  * @param {Function} [opts.resolveConfig] - () → OpenClaw runtime config 快照;通用 device-code 登录(B1)拿 config 用,默认 getClawConfig
86
94
  * @param {Function} [opts.resolveProviders] - ({ config, providerRefs }) → ProviderPlugin[];B1 经它拿 provider 的 auth 方法(生产由入口注入,内部 activate:false),默认抛错
87
- * @returns {{ setApiKey, list, remove, loginOauth, cancelOauth }}
95
+ * @param {Function} [opts.resolveSetupProviders] - ({ config }) → ProviderPlugin[];catalog 经它拿 setup 全集(mode:'setup', activate:false, cache:true,生产由入口注入),默认抛错
96
+ * @param {Function} [opts.loadProviderIdResolver] - () → Promise<resolveProviderIdForAuth>;catalog 算 hasCred 时别名归一基座 id(生产由入口惰性加载 agent-runtime),默认抛错
97
+ * @returns {{ setApiKey, list, remove, loginOauth, cancelOauth, catalog }}
88
98
  */
89
99
  export function buildProviderAuthHandlers({
90
100
  sdk,
@@ -96,6 +106,8 @@ export function buildProviderAuthHandlers({
96
106
  logRemote = remoteLog,
97
107
  resolveConfig = getClawConfig,
98
108
  resolveProviders = () => { throw new Error('provider catalog runtime not injected'); },
109
+ resolveSetupProviders = () => { throw new Error('provider catalog runtime not injected'); },
110
+ loadProviderIdResolver = () => { throw new Error('agent runtime not injected'); },
99
111
  }) {
100
112
  // TODO: 将来若要支持"设默认模型 / 多账号顺序"等需要写 cfg 的操作,会撞上
101
113
  // gateway 重启窗口的 UX 问题——参 docs/model-config-api.md § 3 / § 5(占位章节)。
@@ -553,7 +565,79 @@ export function buildProviderAuthHandlers({
553
565
  }
554
566
  }
555
567
 
556
- return { setApiKey, list, remove, loginOauth, cancelOauth };
568
+ // --- provider 目录(能力 1):枚举全集 provider + 各自认证方式 + 是否已配凭据 ---
569
+
570
+ // 无参;出参 { providers: [{ provider, authMethods, hasCred }] }(命名对象、不 wrap、不 undefined)。
571
+ // 数据源 = resolvePluginProviders(setup) 全集(含未配 provider);authMethods 一条规则零特判
572
+ // (device_code/oauth/api_key 三 kind 露出,token/custom 不露,authMethods 空则该 provider 不进出参);
573
+ // hasCred 复用 computeConfiguredProviders 三源(账本/内联/env)别名归一基座 id。
574
+ // 错误码:未知字段 → INVALID_ARGS(params:{} / undefined / null 都放行);解析/凭据探针/store 读抛错 → IO_FAILED。
575
+ async function catalog({ params, respond }) {
576
+ try {
577
+ // 无参方法:params 缺省(undefined / null)或空对象都放行;带任何字段即未知字段。
578
+ if (params !== undefined && params !== null) {
579
+ if (typeof params !== 'object' || Array.isArray(params)) {
580
+ respondInvalid(respond, 'params must be an object');
581
+ return;
582
+ }
583
+ const keys = Object.keys(params);
584
+ if (keys.length > 0) {
585
+ respondInvalid(respond, `unknown field: ${keys[0]}`);
586
+ return;
587
+ }
588
+ }
589
+
590
+ const config = resolveConfig() ?? {};
591
+ // setup 全集(含未配):activate:false 零副作用、cache:true 复用进程内发现缓存。生产由入口注入。
592
+ const providers = await resolveSetupProviders({ config });
593
+ // 别名归一基座 id(hasCred 计算用):agent-runtime 惰性加载,失败走 IO_FAILED。
594
+ const resolveProviderIdForAuth = await loadProviderIdResolver();
595
+ // 三源 hasCred(账本/内联/env),全过 resolveProviderIdForAuth 归一基座 id。
596
+ const configuredSet = new Set(computeConfiguredProviders(config, {
597
+ agentDir: resolveAgentDir(),
598
+ isProviderApiKeyConfigured: sdk.isProviderApiKeyConfigured,
599
+ hasConfiguredSecretInput: sdk.hasConfiguredSecretInput,
600
+ ensureAuthProfileStore: sdk.ensureAuthProfileStore,
601
+ resolveProviderIdForAuth,
602
+ }));
603
+
604
+ const out = [];
605
+ for (const p of providers ?? []) {
606
+ const provider = p?.id;
607
+ if (typeof provider !== 'string' || provider.length === 0) continue;
608
+ const authMethods = mapAuthMethods(p.auth);
609
+ if (authMethods.length === 0) continue; // custom-only / token-only / 空 auth[] 自然排除
610
+ out.push({ provider, authMethods, hasCred: configuredSet.has(provider) });
611
+ }
612
+ respond(true, { providers: out });
613
+ }
614
+ catch (err) {
615
+ respondIoFailed(respond, err);
616
+ }
617
+ }
618
+
619
+ return { setApiKey, list, remove, loginOauth, cancelOauth, catalog };
620
+ }
621
+
622
+ /**
623
+ * 把 provider 的 auth[] 映射成对外的 authMethods(catalog 用,一条规则零特判):
624
+ * device_code→oauth-device-code、oauth→oauth-login、api_key→api-key;token/custom 不露。
625
+ * 保留 auth[] 出现顺序,按方法名去重(一 provider 可多入口、同 kind 多条只算一次)。
626
+ * @param {Array<{kind?:string}>} authArr - resolvePluginProviders 返回项的 auth 数组
627
+ * @returns {string[]}
628
+ */
629
+ function mapAuthMethods(authArr) {
630
+ const out = [];
631
+ const seen = new Set();
632
+ if (!Array.isArray(authArr)) return out;
633
+ for (const a of authArr) {
634
+ const method = KIND_TO_AUTH_METHOD[a?.kind];
635
+ if (method && !seen.has(method)) {
636
+ seen.add(method);
637
+ out.push(method);
638
+ }
639
+ }
640
+ return out;
557
641
  }
558
642
 
559
643
  /**
@@ -25,6 +25,7 @@ import { mainAgentDir } from '../claw-paths.js';
25
25
  let _sdkPromise;
26
26
  let _configMutationPromise;
27
27
  let _catalogRuntimePromise;
28
+ let _agentRuntimePromise;
28
29
 
29
30
  // 默认 loader 仅作 fallback:生产路径必须由入口(plugins/openclaw/index.js)注入,
30
31
  // 因为 OpenClaw plugin loader 只扫入口源码识别 `openclaw/plugin-sdk/*` 字面量并触发 jiti 重写;
@@ -45,6 +46,12 @@ function defaultLoadProviderCatalogRuntime() {
45
46
  return _catalogRuntimePromise;
46
47
  }
47
48
 
49
+ function defaultLoadAgentRuntime() {
50
+ // agent-runtime barrel 提供 resolveProviderIdForAuth(别名归一基座 id)——catalog 算 hasCred 用。
51
+ _agentRuntimePromise ??= import('openclaw/plugin-sdk/agent-runtime');
52
+ return _agentRuntimePromise;
53
+ }
54
+
48
55
  /**
49
56
  * 测试辅助:清掉懒加载 SDK 缓存。
50
57
  */
@@ -52,6 +59,7 @@ export function __resetSdkCache() {
52
59
  _sdkPromise = undefined;
53
60
  _configMutationPromise = undefined;
54
61
  _catalogRuntimePromise = undefined;
62
+ _agentRuntimePromise = undefined;
55
63
  }
56
64
 
57
65
  /**
@@ -65,7 +73,8 @@ export function __resetSdkCache() {
65
73
  * @param {Function} [opts.resolveAgentDir] - 覆盖 agentDir 解析(默认 mainAgentDir)
66
74
  * @param {Function} [opts.loadSdk] - 必传(生产由入口注入字面量 dynamic import);缺省回退仅为测试兜底
67
75
  * @param {Function} [opts.loadConfigMutation] - 必传(同上,OAuth 写 cfg 用)
68
- * @param {Function} [opts.loadProviderCatalogRuntime] - 必传(同上,通用 device-code 登录 B1 resolvePluginProviders 用)
76
+ * @param {Function} [opts.loadProviderCatalogRuntime] - 必传(同上,通用 device-code 登录 B1 + catalog setup 全集拿 resolvePluginProviders 用)
77
+ * @param {Function} [opts.loadAgentRuntime] - 必传(同上,catalog 算 hasCred 拿 resolveProviderIdForAuth 用)
69
78
  * @param {object} [opts.registry] - 覆盖 oauth-registry(默认模块级单例)
70
79
  */
71
80
  export function registerProviderAuthHandlers(api, opts = {}) {
@@ -73,9 +82,10 @@ export function registerProviderAuthHandlers(api, opts = {}) {
73
82
  const loadSdk = opts.loadSdk ?? defaultLoadSdk;
74
83
  const loadConfigMutation = opts.loadConfigMutation ?? defaultLoadConfigMutation;
75
84
  const loadProviderCatalogRuntime = opts.loadProviderCatalogRuntime ?? defaultLoadProviderCatalogRuntime;
85
+ const loadAgentRuntime = opts.loadAgentRuntime ?? defaultLoadAgentRuntime;
76
86
  const registry = opts.registry ?? { registerLogin, getLogin, removeLogin };
77
87
 
78
- // catalog-runtime 仅通用 device-code 登录(B1)才需要,独立惰性加载——不耦合进 getHandlers
88
+ // catalog-runtime 仅通用 device-code 登录(B1)与 catalog 才需要,独立惰性加载——不耦合进 getHandlers
79
89
  // 的 Promise.all,避免 setApiKey / list / remove / minimax-oauth 因这个 SDK 子入口缺失而连带失败。
80
90
  let catalogRuntimePromise;
81
91
  const resolveProviders = async ({ config, providerRefs }) => {
@@ -89,6 +99,25 @@ export function registerProviderAuthHandlers(api, opts = {}) {
89
99
  mode: 'runtime',
90
100
  });
91
101
  };
102
+ // catalog 用的 setup 全集解析(独立于上面的 runtime 闭包——B1 登录仍走 runtime):
103
+ // mode:'setup' 无条件返回全部 provider(含未配过的),activate:false 零副作用、cache:true 复用发现缓存。
104
+ const resolveSetupProviders = async ({ config }) => {
105
+ catalogRuntimePromise ??= loadProviderCatalogRuntime();
106
+ const catalogRuntime = await catalogRuntimePromise;
107
+ return catalogRuntime.resolvePluginProviders({
108
+ config,
109
+ activate: false,
110
+ cache: true,
111
+ mode: 'setup',
112
+ });
113
+ };
114
+ // agent-runtime 同样独立惰性加载(仅 catalog 的 hasCred 别名归一才需要),不耦合进 getHandlers。
115
+ let agentRuntimePromise;
116
+ const loadProviderIdResolver = async () => {
117
+ agentRuntimePromise ??= loadAgentRuntime();
118
+ const agentRuntime = await agentRuntimePromise;
119
+ return agentRuntime.resolveProviderIdForAuth;
120
+ };
92
121
 
93
122
  let handlersPromise;
94
123
  async function getHandlers() {
@@ -107,7 +136,15 @@ export function registerProviderAuthHandlers(api, opts = {}) {
107
136
  generatePkce: providerAuthSdk.generatePkceVerifierChallenge,
108
137
  toForm: providerAuthSdk.toFormUrlEncoded,
109
138
  });
110
- return buildProviderAuthHandlers({ sdk, resolveAgentDir, oauth, registry, resolveProviders });
139
+ return buildProviderAuthHandlers({
140
+ sdk,
141
+ resolveAgentDir,
142
+ oauth,
143
+ registry,
144
+ resolveProviders,
145
+ resolveSetupProviders,
146
+ loadProviderIdResolver,
147
+ });
111
148
  })();
112
149
  }
113
150
  return handlersPromise;
@@ -136,4 +173,5 @@ export function registerProviderAuthHandlers(api, opts = {}) {
136
173
  api.registerGatewayMethod('coclaw.providerAuth.remove', wrap('remove'));
137
174
  api.registerGatewayMethod('coclaw.providerAuth.loginOauth', wrap('loginOauth'));
138
175
  api.registerGatewayMethod('coclaw.providerAuth.cancelOauth', wrap('cancelOauth'));
176
+ api.registerGatewayMethod('coclaw.providerAuth.catalog', wrap('catalog'));
139
177
  }