dsh-agentone 0.5.38 → 0.5.40
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/README.md +14 -7
- package/lib/client.js +5 -1
- package/lib/defaults-sync.js +257 -0
- package/lib/http.js +6 -3
- package/lib/index.js +195 -297
- package/lib/page.js +71 -9
- package/lib/platform-config.js +56 -0
- package/lib/proc.js +6 -4
- package/lib/profile-plugins.js +143 -0
- package/lib/skill-state.js +102 -0
- package/lib/skill.js +182 -104
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -33,14 +33,17 @@ import {
|
|
|
33
33
|
import { applyPlatformModel, removePlatformModel } from './model.js';
|
|
34
34
|
import { installPlatformSkill, listPlatformSkills, removePlatformSkill } from './skill.js';
|
|
35
35
|
import { applyPlatformSkillhub, removePlatformSkillhub } from './skillhub.js';
|
|
36
|
+
import { createProfilePlugins, validatePackage, PROTECTED_PACKAGES } from './profile-plugins.js';
|
|
37
|
+
import { createDefaultsSync } from './defaults-sync.js';
|
|
38
|
+
import { canonicalSkillSlug, localSkillState } from './skill-state.js';
|
|
36
39
|
import { ensureLanguageDefault } from './language.js';
|
|
37
40
|
import { bootstrapInstanceCredentials, INSTANCE_SOURCE, isInstanceManaged, resolvePlatformUrl } from './instance.js';
|
|
38
41
|
import { beginCliAuth, cliAuthQrcode, completeCliAuth, parseCliAuthBeginOutput } from './cliauth.mjs';
|
|
39
42
|
import { platformFetch } from './http.js';
|
|
40
43
|
import { donePageHtml, statusPageHtml } from './page.js';
|
|
41
|
-
import { compareSemver,
|
|
44
|
+
import { compareSemver, firstErrorLines, hasChineseText, installCliTool, isTerminalCliAuthFailure, isUnexpectedStoreError, larkCliInitActiveUrl, larkCliInitStatus, probeCliDetail, probePythonEnv, pnpmViewLatest, runCliTool, startLarkCliInit, stopLarkCliInit, unexpectedStoreGuidance, upgradeCliTool } from './proc.js';
|
|
42
45
|
|
|
43
|
-
import { clearCredentials, loadCredentials,
|
|
46
|
+
import { clearCredentials, loadCredentials, saveCredentials, updateCredentials } from './store.js';
|
|
44
47
|
|
|
45
48
|
const require = createRequire(import.meta.url);
|
|
46
49
|
|
|
@@ -58,6 +61,7 @@ export const Config = z.object({
|
|
|
58
61
|
.default('https://ccpg.one.agentone.work')
|
|
59
62
|
.description('AgentOne 平台地址(本地开发在 profile cordis.patch.yml 覆盖为 http://127.0.0.1:8558)'),
|
|
60
63
|
storeDir: z.string().default('').description('凭据存储目录,空则用 $DSH_HOME/agentone'),
|
|
64
|
+
profileName: z.string().default('').description('当前 Web profile 名称;Desktop 自动读取当前 profile,旧版 Web 宿主请明确填写'),
|
|
61
65
|
});
|
|
62
66
|
|
|
63
67
|
/** 本次进程内的登录 CSRF state(/callback 比对后即失效)。 */
|
|
@@ -214,13 +218,6 @@ async function ensureFreshToken(config, log) {
|
|
|
214
218
|
return tokenRefreshInFlight;
|
|
215
219
|
}
|
|
216
220
|
|
|
217
|
-
/**
|
|
218
|
-
* 插件管理目标 profile:普通 dsh 固定 web(控制台只跑 web profile);
|
|
219
|
-
* DSH Desktop 用 desktopProfiles.current(apply 时探测填充)。
|
|
220
|
-
*/
|
|
221
|
-
let PLUGIN_PROFILE = 'web';
|
|
222
|
-
/** desktop 受管插件操作通道(desktopPnpm.runPlugin 包装),非 desktop 为 null。 */
|
|
223
|
-
let desktopPluginCli = null;
|
|
224
221
|
/**
|
|
225
222
|
* dsh ≥ 0.1.7 的官方 settings 服务(apply 时可选获取):用来把平台模型写进
|
|
226
223
|
* profile patch 活配置层,同步即时生效。旧宿主没有该服务,取到 null 即回退
|
|
@@ -228,10 +225,6 @@ let desktopPluginCli = null;
|
|
|
228
225
|
*/
|
|
229
226
|
let settingsService = null;
|
|
230
227
|
|
|
231
|
-
function pluginProfileDir(config) {
|
|
232
|
-
return join(resolveDshHome(config), 'profiles', PLUGIN_PROFILE);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
228
|
/** 模型同步失败自愈重试调度(模块级单例:apply 重入先清旧定时器)。
|
|
236
229
|
* 平台侧瞬时故障(如 AirRouter 短暂不可达)会让启动同步失败,错误一直
|
|
237
230
|
* 挂在状态页直到下次 dsh 重启——这里由 5s 状态轮询驱动:发现 model_error
|
|
@@ -267,14 +260,6 @@ function scheduleModelRetry(config, log) {
|
|
|
267
260
|
modelRetryTimer.unref?.();
|
|
268
261
|
}
|
|
269
262
|
|
|
270
|
-
/** 转发 dsh plugin 子命令:desktop 走受管 pnpm,普通 dsh 走官方 CLI。 */
|
|
271
|
-
async function runPluginCli(args) {
|
|
272
|
-
if (desktopPluginCli) {
|
|
273
|
-
return desktopPluginCli(args);
|
|
274
|
-
}
|
|
275
|
-
return dshPluginCli(args, PLUGIN_PROFILE);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
263
|
/** 宿主 DesktopPnpmService 的并发锁占用报错(同一时刻只允许一个 pnpm
|
|
279
264
|
* 操作——设计如此,不是故障)。识别后应排队等待而非把错误抛给用户。 */
|
|
280
265
|
const DESKTOP_PNPM_BUSY_RE = /another desktop pnpm operation is already running/i;
|
|
@@ -300,13 +285,14 @@ const sleep = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); });
|
|
|
300
285
|
*/
|
|
301
286
|
export function desktopRunPlugin(service, invokingDir) {
|
|
302
287
|
let queue = Promise.resolve();
|
|
303
|
-
const run = async (args) => {
|
|
288
|
+
const run = async (args, options = {}) => {
|
|
304
289
|
// 宿主 lock 是同步抛错(runPlugin 调用即抛),先探再跑 + 失败退避都做
|
|
305
290
|
let attempt = 0;
|
|
306
291
|
for (;;) {
|
|
292
|
+
options.signal?.throwIfAborted();
|
|
307
293
|
let handle;
|
|
308
294
|
try {
|
|
309
|
-
handle = service.runPlugin([...args], invokingDir);
|
|
295
|
+
handle = service.runPlugin([...args], invokingDir, options.signal);
|
|
310
296
|
} catch (error) {
|
|
311
297
|
const message = error?.message || String(error);
|
|
312
298
|
if (DESKTOP_PNPM_BUSY_RE.test(message) && attempt < 5) {
|
|
@@ -316,6 +302,9 @@ export function desktopRunPlugin(service, invokingDir) {
|
|
|
316
302
|
}
|
|
317
303
|
throw error;
|
|
318
304
|
}
|
|
305
|
+
const cancel = () => handle.cancel();
|
|
306
|
+
options.signal?.addEventListener('abort', cancel, { once: true });
|
|
307
|
+
if (options.signal?.aborted) cancel();
|
|
319
308
|
let stdout = '';
|
|
320
309
|
let stderr = '';
|
|
321
310
|
const append = (acc, chunk) => (acc + String(chunk)).slice(-64 * 1024);
|
|
@@ -329,6 +318,7 @@ export function desktopRunPlugin(service, invokingDir) {
|
|
|
329
318
|
}, 300_000);
|
|
330
319
|
try {
|
|
331
320
|
const outcome = await handle.done;
|
|
321
|
+
options.signal?.throwIfAborted();
|
|
332
322
|
if (timedOut) throw new Error('命令执行超时,请稍后重试');
|
|
333
323
|
if (outcome.exitCode !== 0 || outcome.signal != null) {
|
|
334
324
|
const message = firstErrorLines(`${stderr}\n${stdout}`);
|
|
@@ -350,35 +340,18 @@ export function desktopRunPlugin(service, invokingDir) {
|
|
|
350
340
|
return { stdout, stderr };
|
|
351
341
|
} finally {
|
|
352
342
|
clearTimeout(timer);
|
|
343
|
+
options.signal?.removeEventListener('abort', cancel);
|
|
353
344
|
}
|
|
354
345
|
}
|
|
355
346
|
};
|
|
356
|
-
return (args) => {
|
|
357
|
-
const next = queue.then(() => run(args));
|
|
347
|
+
return (args, options = {}) => {
|
|
348
|
+
const next = queue.then(() => run(args, options));
|
|
358
349
|
// 队列尾吞错,避免一次失败卡死后续排队操作
|
|
359
350
|
queue = next.catch(() => undefined);
|
|
360
351
|
return next;
|
|
361
352
|
};
|
|
362
353
|
}
|
|
363
354
|
|
|
364
|
-
/**
|
|
365
|
-
* npm 包名白名单(scoped 与非 scoped)。用户输入会进入 CLI 参数,必须先
|
|
366
|
-
* 收敛到 npm 官方命名规则,杜绝以 `-` 开头的值被 dsh/pnpm 当成 flag。
|
|
367
|
-
*/
|
|
368
|
-
const NPM_NAME_RE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
369
|
-
/** 版本白名单:语义化版本、^ ~ 前缀与 latest 标签。 */
|
|
370
|
-
const VERSION_RE = /^(?:\^|~)?\d+(?:\.\d+){0,3}(?:[-+][a-z0-9._-]+)?$|^latest$/;
|
|
371
|
-
|
|
372
|
-
/** 校验用户输入的包名/版本;非法值直接拒绝(中文报错)。 */
|
|
373
|
-
function assertValidPackage(name, version) {
|
|
374
|
-
if (!NPM_NAME_RE.test(name)) {
|
|
375
|
-
throw new Error('插件名不合法:仅允许小写字母、数字和 . _ -(scoped 名如 @scope/pkg)');
|
|
376
|
-
}
|
|
377
|
-
if (version !== undefined && version !== '' && !VERSION_RE.test(version)) {
|
|
378
|
-
throw new Error('版本号不合法:仅允许语义化版本(如 1.2.3、^1.2.0)或 latest');
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
355
|
async function readJsonFile(path) {
|
|
383
356
|
try {
|
|
384
357
|
return JSON.parse(await readFile(path, 'utf8'));
|
|
@@ -393,19 +366,6 @@ async function installedVersion(profile, name) {
|
|
|
393
366
|
return typeof manifest?.version === 'string' ? manifest.version : null;
|
|
394
367
|
}
|
|
395
368
|
|
|
396
|
-
/** 包是否带 dsh.bundle 声明(决定它在层列表里是否被激活)。 */
|
|
397
|
-
async function isBundlePackage(profile, name) {
|
|
398
|
-
const manifest = await readJsonFile(join(profile, 'node_modules', name, 'package.json'));
|
|
399
|
-
return Boolean(manifest?.dsh?.bundle);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/** link:/file: 等本地路径依赖展示短标签(没有版本号可读)。 */
|
|
403
|
-
function specLabel(spec) {
|
|
404
|
-
if (typeof spec !== 'string') return '';
|
|
405
|
-
if (spec.startsWith('link:') || spec.startsWith('file:')) return '本地链接';
|
|
406
|
-
return '';
|
|
407
|
-
}
|
|
408
|
-
|
|
409
369
|
/** dsh 插件与 CLI 操作白名单:用户输入不得进入 execFile 的 tool 位。 */
|
|
410
370
|
const CLI_TOOLS = new Set(['lark-cli', 'ccpg-cli']);
|
|
411
371
|
/** 除 CLI 外可通过 /agentone/api/cli/install 自助安装的工具(python 无
|
|
@@ -448,7 +408,7 @@ const INBOX_BUNDLES = new Set([
|
|
|
448
408
|
* 装入 /opt/prebuilt/dsh-home,init.sh 首启物化到持久卷)。用户可以卸载,
|
|
449
409
|
* 但它们承载控制台核心体验(侧栏宿主/Workflow One/插件市场/IM 桥),所以
|
|
450
410
|
* 每次 dsh 启动时检测:缺失则静默重装(常驻保证),已装则不碰(尊重用户
|
|
451
|
-
*
|
|
411
|
+
* 的版本选择)。安装走 profile-plugins 受管通道,与平台默认能力同一队列。
|
|
452
412
|
*/
|
|
453
413
|
const BUILTIN_PLUGINS = [
|
|
454
414
|
'dsh-better-sidebar',
|
|
@@ -479,203 +439,15 @@ const BUILTIN_PLUGIN_DESCRIPTIONS = {
|
|
|
479
439
|
'AgentOne 平台集成:IDOne 登录、平台模型、SkillHub 技能与 CLI 授权(本插件)',
|
|
480
440
|
};
|
|
481
441
|
|
|
482
|
-
/**
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const manifest = await readJsonFile(join(dir, 'package.json'));
|
|
486
|
-
const dependencies = manifest?.dependencies ?? {};
|
|
487
|
-
const bundles = Array.isArray(manifest?.dsh?.profile?.bundles)
|
|
488
|
-
? manifest.dsh.profile.bundles
|
|
489
|
-
: [];
|
|
490
|
-
const plugins = [];
|
|
491
|
-
const seen = new Set();
|
|
492
|
-
for (const [name, spec] of Object.entries(dependencies)) {
|
|
493
|
-
if (INBOX_BUNDLES.has(name)) continue;
|
|
494
|
-
seen.add(name);
|
|
495
|
-
const version = await installedVersion(dir, name);
|
|
496
|
-
const bundle = version !== null && (await isBundlePackage(dir, name));
|
|
497
|
-
plugins.push({
|
|
498
|
-
name,
|
|
499
|
-
spec,
|
|
500
|
-
version,
|
|
501
|
-
source: specLabel(spec) || 'npm',
|
|
502
|
-
bundled: bundle,
|
|
503
|
-
// 激活 = 已装 + 带 bundle 声明 + 出现在 dsh.profile.bundles
|
|
504
|
-
active: bundle && bundles.includes(name),
|
|
505
|
-
builtin: BUILTIN_PLUGINS.includes(name),
|
|
506
|
-
// 自身随平台镜像升级:前端据此隐藏卸载/升级按钮(与后端拒绝逻辑同源)
|
|
507
|
-
self_managed: name === PLUGIN_PACKAGE_NAME && isSelfManaged(),
|
|
508
|
-
description: BUILTIN_PLUGIN_DESCRIPTIONS[name] || null,
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
// 内置插件被卸载后不在 dependencies 里,但仍要出现在列表中
|
|
512
|
-
// (状态「未安装」+「安装」按钮),否则用户无从感知与恢复。
|
|
513
|
-
for (const name of BUILTIN_PLUGINS) {
|
|
514
|
-
if (seen.has(name)) continue;
|
|
515
|
-
plugins.push({
|
|
516
|
-
name,
|
|
517
|
-
spec: null,
|
|
518
|
-
version: null,
|
|
519
|
-
source: 'npm',
|
|
520
|
-
bundled: false,
|
|
521
|
-
active: false,
|
|
522
|
-
builtin: true,
|
|
523
|
-
self_managed: false,
|
|
524
|
-
description: BUILTIN_PLUGIN_DESCRIPTIONS[name] || null,
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
return {
|
|
528
|
-
profile: PLUGIN_PROFILE,
|
|
529
|
-
bundles: bundles.filter((name) => !INBOX_BUNDLES.has(name)),
|
|
530
|
-
plugins,
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/** dsh plugin add 的构建脚本放行参数(与镜像 Dockerfile 预装命令同源)。
|
|
535
|
-
* pnpm 11 默认拦截依赖构建脚本,node-pty(sidebar/harness-one 的终端依赖)
|
|
536
|
-
* 被拦会导致 add 非零退出、bundles reconcile 不执行——包装上了却永远
|
|
537
|
-
* 「未激活」。用 = 连接形式,避免空格分隔被 pnpm 拆成两个参数。
|
|
538
|
-
*/
|
|
442
|
+
/** 内置插件安装期构建授权(与镜像 Dockerfile 预装命令同源):pnpm 默认
|
|
443
|
+
* 拦截依赖构建脚本,node-pty(侧栏/工作台的终端依赖)被拦会导致插件
|
|
444
|
+
* 装上却无法激活。平台默认能力清单未声明时按此兜底合并。 */
|
|
539
445
|
const BUILTIN_PLUGIN_ALLOW_BUILD = {
|
|
540
|
-
'dsh-better-sidebar': ['
|
|
541
|
-
'dsh-harness-one': ['
|
|
542
|
-
dshmarket: ['
|
|
543
|
-
'dsh-im': [],
|
|
544
|
-
[PLUGIN_PACKAGE_NAME]: [],
|
|
446
|
+
'dsh-better-sidebar': ['node-pty'],
|
|
447
|
+
'dsh-harness-one': ['node-pty'],
|
|
448
|
+
dshmarket: ['node-pty'],
|
|
545
449
|
};
|
|
546
450
|
|
|
547
|
-
/** 安装:白名单校验后转发(desktop 受管 pnpm / 官方 CLI;参数数组,无 shell)。
|
|
548
|
-
* pnpm 11 在 profile 存在被忽略的构建脚本时 add 以 ERR_PNPM_IGNORED_BUILDS
|
|
549
|
-
* 非零退出,但包本身已装上(先装后报)——以落盘为准,装上即成功。 */
|
|
550
|
-
async function installProfilePlugin(config, name, version) {
|
|
551
|
-
assertValidPackage(name, version);
|
|
552
|
-
const target = version ? `${name}@${version}` : name;
|
|
553
|
-
try {
|
|
554
|
-
await runPluginCli(['add', target, ...(BUILTIN_PLUGIN_ALLOW_BUILD[name] || [])]);
|
|
555
|
-
} catch (error) {
|
|
556
|
-
if ((await installedVersion(pluginProfileDir(config), name)) === null) throw error;
|
|
557
|
-
}
|
|
558
|
-
return listProfilePlugins(config);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/** 卸载:自身与内置插件拒绝(无卸载按钮 + 后端兜底);白名单校验后转发官方 CLI。 */
|
|
562
|
-
async function removeProfilePlugin(config, name) {
|
|
563
|
-
assertValidPackage(name);
|
|
564
|
-
if (INBOX_BUNDLES.has(name)) {
|
|
565
|
-
throw new Error('dsh 内置组件不可卸载');
|
|
566
|
-
}
|
|
567
|
-
if (BUILTIN_PLUGINS.includes(name)) {
|
|
568
|
-
throw new Error('内置插件承载 dsh 核心体验,不可卸载');
|
|
569
|
-
}
|
|
570
|
-
if (name === PLUGIN_PACKAGE_NAME) {
|
|
571
|
-
throw new Error('AgentOne 插件是登录与模型配置的载体,不能卸载自己');
|
|
572
|
-
}
|
|
573
|
-
await runPluginCli(['remove', name]);
|
|
574
|
-
return listProfilePlugins(config);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
/** 升级到 registry 最新版:view 查实时 latest → 显式版本号 add(会重写
|
|
578
|
-
* package.json spec)→ 校验落盘版本。不能用 `dsh plugin update`(即
|
|
579
|
-
* `pnpm update`):它只在 spec range 内升级,`dsh plugin add` 写入的精确
|
|
580
|
-
* pin(如 "0.19.1")下永远是 no-op,却返回成功——历史上「点升级提示已
|
|
581
|
-
* 升级、版本永不变」的根因。 */
|
|
582
|
-
async function upgradeProfilePlugin(config, name) {
|
|
583
|
-
assertValidPackage(name);
|
|
584
|
-
if (INBOX_BUNDLES.has(name)) {
|
|
585
|
-
throw new Error('dsh 内置组件随 dsh 一同升级,请等待平台更新 dsh');
|
|
586
|
-
}
|
|
587
|
-
if (name === PLUGIN_PACKAGE_NAME && isSelfManaged()) {
|
|
588
|
-
throw new Error('AgentOne 插件随平台镜像一同升级,无需手动操作');
|
|
589
|
-
}
|
|
590
|
-
const dir = pluginProfileDir(config);
|
|
591
|
-
const [latest, current] = await Promise.all([
|
|
592
|
-
pnpmViewLatest(name, dir).catch(() => null),
|
|
593
|
-
installedVersion(dir, name),
|
|
594
|
-
]);
|
|
595
|
-
if (!latest) {
|
|
596
|
-
throw new Error('查询插件最新版本失败,请稍后重试');
|
|
597
|
-
}
|
|
598
|
-
// latest dist-tag 可能指向旧版(作者误指/回退),不比已装高就没必要升
|
|
599
|
-
if (latest === current || (current && compareSemver(latest, current) <= 0)) {
|
|
600
|
-
throw new Error(`已是最新版本(v${current ?? latest})`);
|
|
601
|
-
}
|
|
602
|
-
try {
|
|
603
|
-
await runPluginCli(['add', `${name}@${latest}`, ...(BUILTIN_PLUGIN_ALLOW_BUILD[name] || [])]);
|
|
604
|
-
} catch (error) {
|
|
605
|
-
// 同 install:ERR_PNPM_IGNORED_BUILDS 非零退出但包已装上,不阻断升级
|
|
606
|
-
if ((await installedVersion(dir, name)) !== latest) throw error;
|
|
607
|
-
}
|
|
608
|
-
const after = await installedVersion(dir, name);
|
|
609
|
-
if (after !== latest) {
|
|
610
|
-
// 兑现「插件已升级」提示的前置校验:没升上去就明说,不再假报成功
|
|
611
|
-
throw new Error(`升级未生效(当前 v${after ?? '未安装'},目标 v${latest}),请稍后重试`);
|
|
612
|
-
}
|
|
613
|
-
return listProfilePlugins(config);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* 内置插件常驻检测:镜像预装插件缺失时逐个静默重装。单个失败不阻断
|
|
618
|
-
* (网络抖动等),下一个启动周期再试;全部已装时零开销。
|
|
619
|
-
*/
|
|
620
|
-
async function ensureBuiltinPlugins(config, log) {
|
|
621
|
-
const state = await listProfilePlugins(config);
|
|
622
|
-
const installed = new Set(state.plugins.map((plugin) => plugin.name));
|
|
623
|
-
const missing = BUILTIN_PLUGINS.filter((name) => !installed.has(name));
|
|
624
|
-
if (!missing.length) return [];
|
|
625
|
-
const reinstalled = [];
|
|
626
|
-
for (const name of missing) {
|
|
627
|
-
try {
|
|
628
|
-
await installProfilePlugin(config, name);
|
|
629
|
-
reinstalled.push(name);
|
|
630
|
-
log.info(`[dsh-agentone] builtin plugin reinstalled: ${name}`);
|
|
631
|
-
} catch (error) {
|
|
632
|
-
log.warn(`[dsh-agentone] builtin plugin reinstall failed (${name}): ${error?.message || error}`);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return reinstalled;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* 套餐技能自动同步:平台上 source=plan 且未安装的技能静默装齐(与平台托管
|
|
640
|
-
* 实例的套餐技能预装语义对齐);单个失败不阻断其余,结果记 credentials。
|
|
641
|
-
*/
|
|
642
|
-
async function syncPlanSkills(config, log, credentials) {
|
|
643
|
-
const token = credentials?.access_token;
|
|
644
|
-
if (!token) return null;
|
|
645
|
-
let listing;
|
|
646
|
-
try {
|
|
647
|
-
listing = await listPlatformSkills(config.platformUrl, token);
|
|
648
|
-
} catch (error) {
|
|
649
|
-
log.warn(`[dsh-agentone] skills list failed: ${error?.message || error}`);
|
|
650
|
-
await updateCredentials(config, { skills_error: String(error?.message || error) }).catch(
|
|
651
|
-
() => {},
|
|
652
|
-
);
|
|
653
|
-
return null;
|
|
654
|
-
}
|
|
655
|
-
const pending = (listing.skills || []).filter((skill) => skill.source === 'plan' && !skill.installed);
|
|
656
|
-
let installed = 0;
|
|
657
|
-
const failures = [];
|
|
658
|
-
for (const skill of pending) {
|
|
659
|
-
try {
|
|
660
|
-
await installPlatformSkill(config.platformUrl, token, skill.canonical_slug, {
|
|
661
|
-
config,
|
|
662
|
-
source: 'plan',
|
|
663
|
-
});
|
|
664
|
-
installed += 1;
|
|
665
|
-
log.info(`[dsh-agentone] plan skill installed: ${skill.canonical_slug}`);
|
|
666
|
-
} catch (error) {
|
|
667
|
-
failures.push(`${skill.canonical_slug}: ${error?.message || error}`);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
await updateCredentials(config, {
|
|
671
|
-
skills_synced_at: new Date().toISOString(),
|
|
672
|
-
skills_error: failures.length ? failures.join(';') : null,
|
|
673
|
-
}).catch(() => {});
|
|
674
|
-
if (failures.length) log.warn(`[dsh-agentone] plan skill failures: ${failures.join(';')}`);
|
|
675
|
-
return { installed, failed: failures.length };
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
|
|
679
451
|
/**
|
|
680
452
|
* status 内缓存:skills/catalog 内容几乎不变,却被 5s 轮询每次全量重拉。
|
|
681
453
|
* 缓存 45s;模型同步结果本就落盘在 credentials,不受缓存影响。
|
|
@@ -727,7 +499,7 @@ async function cachedPlatformStatus(config, credentials) {
|
|
|
727
499
|
* token + state 比对),收完第一个请求即关停;5 分钟无回调自动超时关闭。
|
|
728
500
|
* 返回外部浏览器可达的回调地址。
|
|
729
501
|
*/
|
|
730
|
-
function openCallbackServer(config, log) {
|
|
502
|
+
function openCallbackServer(config, log, onLogin = () => {}, isCurrent = () => true) {
|
|
731
503
|
return new Promise((resolve, reject) => {
|
|
732
504
|
const server = createServer(async (req, res) => {
|
|
733
505
|
const close = () => server.close();
|
|
@@ -758,6 +530,7 @@ button{font:inherit;padding:9px 22px;border-radius:10px;border:none;cursor:point
|
|
|
758
530
|
finish(false);
|
|
759
531
|
return;
|
|
760
532
|
}
|
|
533
|
+
if (!isCurrent()) { finish(false); return; }
|
|
761
534
|
const expiresIn = Number(result.expires_in) || 0;
|
|
762
535
|
await saveCredentials(config, {
|
|
763
536
|
schema_version: 2,
|
|
@@ -774,10 +547,8 @@ button{font:inherit;padding:9px 22px;border-radius:10px;border:none;cursor:point
|
|
|
774
547
|
...(await loadCredentials(config)),
|
|
775
548
|
access_token: result.access_token,
|
|
776
549
|
}).catch(() => {});
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
access_token: result.access_token,
|
|
780
|
-
}).catch(() => {});
|
|
550
|
+
if (!isCurrent()) { finish(false); return; }
|
|
551
|
+
onLogin();
|
|
781
552
|
finish(true);
|
|
782
553
|
} catch (error) {
|
|
783
554
|
log.warn(`[dsh-agentone] desktop exchange failed: ${error?.message || error}`);
|
|
@@ -795,12 +566,13 @@ button{font:inherit;padding:9px 22px;border-radius:10px;border:none;cursor:point
|
|
|
795
566
|
}
|
|
796
567
|
|
|
797
568
|
/**
|
|
798
|
-
*
|
|
799
|
-
*
|
|
569
|
+
* 是否运行在桌面壳内:宿主进程是 Electron(execPath 指向桌面壳可执行文件)。
|
|
570
|
+
* 壳品牌:DSH Desktop / DSH NEXT / DeepSeek HARNESS(NEXT 的公开发布名)。
|
|
571
|
+
* 与页面侧标记(page.js 传 desktop:true)互为兜底——iframe 内
|
|
800
572
|
* location.search 不含宿主 query,页面自检在设置 iframe 场景会漏。
|
|
801
573
|
*/
|
|
802
574
|
function isDesktopHost() {
|
|
803
|
-
return /DSH Desktop\.(?:app|exe)/i.test(process.execPath);
|
|
575
|
+
return /(?:DSH Desktop|DSH NEXT|DeepSeek HARNESS)\.(?:app|exe)/i.test(process.execPath);
|
|
804
576
|
}
|
|
805
577
|
|
|
806
578
|
export function apply(ctx, config) {
|
|
@@ -821,14 +593,96 @@ export function apply(ctx, config) {
|
|
|
821
593
|
// patch 活配置层;取不到(旧宿主)就回退「只写 settings.yaml + 下次启动导入」。
|
|
822
594
|
// 不能写进 inject:那会让插件在旧宿主上一直 PENDING。
|
|
823
595
|
settingsService = ctx.get?.('settings') ?? null;
|
|
824
|
-
if (desktopProfiles?.current?.name) {
|
|
825
|
-
PLUGIN_PROFILE = desktopProfiles.current.name;
|
|
826
|
-
ctx.inject(['desktopPnpm'], (desktopCtx) => {
|
|
827
|
-
desktopPluginCli = desktopRunPlugin(desktopCtx.desktopPnpm, pluginProfileDir(config));
|
|
828
|
-
log.info(`[dsh-agentone] desktop profile: ${PLUGIN_PROFILE}`);
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
596
|
ctx.effect(() => {
|
|
597
|
+
let disposed = false;
|
|
598
|
+
const profiles = createProfilePlugins({ ctx, config, desktopRun: desktopRunPlugin });
|
|
599
|
+
const defaults = createDefaultsSync({ config, profiles,
|
|
600
|
+
credentials: () => ensureFreshToken(config, log), log });
|
|
601
|
+
const listProfilePlugins = async () => {
|
|
602
|
+
const state = await profiles.list();
|
|
603
|
+
const seen = new Set();
|
|
604
|
+
state.plugins.forEach((plugin) => {
|
|
605
|
+
seen.add(plugin.name);
|
|
606
|
+
plugin.self_managed = plugin.name === PLUGIN_PACKAGE_NAME && isSelfManaged();
|
|
607
|
+
plugin.builtin = plugin.builtin || BUILTIN_PLUGINS.includes(plugin.name);
|
|
608
|
+
plugin.description = plugin.description || BUILTIN_PLUGIN_DESCRIPTIONS[plugin.name] || null;
|
|
609
|
+
});
|
|
610
|
+
// 内置插件被卸载后不在 dependencies 里,仍以「未安装」占位出现在列表,
|
|
611
|
+
// 否则用户无从感知与恢复(常驻检测会在下次启动时自动重装)。
|
|
612
|
+
for (const name of BUILTIN_PLUGINS) {
|
|
613
|
+
if (seen.has(name)) continue;
|
|
614
|
+
state.plugins.push({ name, spec: null, version: null, bundled: false, registered: false,
|
|
615
|
+
active: false, source: 'npm', builtin: true,
|
|
616
|
+
self_managed: name === PLUGIN_PACKAGE_NAME && isSelfManaged(),
|
|
617
|
+
description: BUILTIN_PLUGIN_DESCRIPTIONS[name] || null, target_version: null,
|
|
618
|
+
state: 'missing', loaded: null });
|
|
619
|
+
}
|
|
620
|
+
return state;
|
|
621
|
+
};
|
|
622
|
+
const installProfilePlugin = async (_config, name, version = 'latest') => {
|
|
623
|
+
validatePackage(name, version);
|
|
624
|
+
// 内置插件(镜像控制台四件套/自身)是本机概念,不依赖平台登录:
|
|
625
|
+
// 直接走受管安装队列,构建授权用镜像预装命令同源的 node-pty 兜底。
|
|
626
|
+
if (BUILTIN_PLUGINS.includes(name)) {
|
|
627
|
+
await profiles.install(name, 'latest', { allowBuild: BUILTIN_PLUGIN_ALLOW_BUILD[name] || [] });
|
|
628
|
+
return listProfilePlugins();
|
|
629
|
+
}
|
|
630
|
+
await defaults.manualOperation(async (_owner, options) => {
|
|
631
|
+
const policy = options.document.plugins.find((item) => item.name === name);
|
|
632
|
+
// 平台清单声明的构建授权与内置插件已知的 node-pty 兜底合并
|
|
633
|
+
const allowBuild = [...new Set([...(policy?.allow_build || []), ...(BUILTIN_PLUGIN_ALLOW_BUILD[name] || [])])];
|
|
634
|
+
return profiles.install(name, 'latest', { ...options, allowBuild });
|
|
635
|
+
});
|
|
636
|
+
return listProfilePlugins();
|
|
637
|
+
};
|
|
638
|
+
const removeProfilePlugin = async (_config, name) => {
|
|
639
|
+
validatePackage(name);
|
|
640
|
+
if (PROTECTED_PACKAGES.has(name)) throw new Error('AgentOne 自身和 DSH 基础组件不可卸载');
|
|
641
|
+
if (BUILTIN_PLUGINS.includes(name)) throw new Error('内置插件承载 dsh 控制台核心体验,不可卸载');
|
|
642
|
+
return defaults.manualOperation((_owner, options) => profiles.remove(name, options));
|
|
643
|
+
};
|
|
644
|
+
const upgradeProfilePlugin = async (_config, name) => {
|
|
645
|
+
if (INBOX_BUNDLES.has(name) || (name === PLUGIN_PACKAGE_NAME && isSelfManaged())) {
|
|
646
|
+
throw new Error('此组件随宿主或平台更新,请等待平台更新');
|
|
647
|
+
}
|
|
648
|
+
await defaults.manualOperation(async (_owner, options) => {
|
|
649
|
+
const managed = options.document.plugins.find((item) => item.name === name);
|
|
650
|
+
if (managed) return profiles.install(name, managed.version, { ...options, allowBuild: managed.allow_build });
|
|
651
|
+
const latest = await pnpmViewLatest(name, profiles.target().dir).catch(() => null);
|
|
652
|
+
if (!latest) throw new Error('查询插件最新版本失败,请检查 npm 连接后重试');
|
|
653
|
+
const installed = (await profiles.list()).plugins.find((item) => item.name === name);
|
|
654
|
+
if (installed?.version && compareSemver(latest, installed.version) <= 0) {
|
|
655
|
+
throw new Error(`已是最新版本(v${installed.version})`);
|
|
656
|
+
}
|
|
657
|
+
return profiles.install(name, latest, options);
|
|
658
|
+
});
|
|
659
|
+
return listProfilePlugins();
|
|
660
|
+
};
|
|
661
|
+
const syncPlanSkills = async () => {
|
|
662
|
+
const result = await defaults.sync({ manual: true });
|
|
663
|
+
if (result?.error) throw new Error(result.error);
|
|
664
|
+
invalidateStatusCache();
|
|
665
|
+
return { installed: result?.skills.filter((item) => item.state === 'current').length || 0,
|
|
666
|
+
failed: result?.skills.filter((item) => ['failed', 'conflict', 'report_pending'].includes(item.state)).length || 0 };
|
|
667
|
+
};
|
|
668
|
+
const onLogin = () => {
|
|
669
|
+
defaults.reset();
|
|
670
|
+
void defaults.sync().then(invalidateStatusCache).catch(() => {});
|
|
671
|
+
};
|
|
672
|
+
// 内置插件常驻检测:缺失时静默重装(不依赖登录态);单个失败不阻断,
|
|
673
|
+
// 下个启动周期再试。已装不碰——版本升级交给平台默认能力同步。
|
|
674
|
+
const ensureBuiltinPlugins = async () => {
|
|
675
|
+
const state = await profiles.list();
|
|
676
|
+
const installed = new Set(state.plugins.filter((plugin) => plugin.version).map((plugin) => plugin.name));
|
|
677
|
+
for (const name of BUILTIN_PLUGINS.filter((entry) => !installed.has(entry))) {
|
|
678
|
+
try {
|
|
679
|
+
await profiles.install(name, 'latest', { allowBuild: BUILTIN_PLUGIN_ALLOW_BUILD[name] || [] });
|
|
680
|
+
log.info(`[dsh-agentone] builtin plugin reinstalled: ${name}`);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
log.warn(`[dsh-agentone] builtin plugin reinstall failed (${name}): ${error?.message || error}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
};
|
|
832
686
|
// 模块级重试定时器跨 apply 存活:重入先清,避免旧定时器引用旧 config
|
|
833
687
|
clearModelRetry();
|
|
834
688
|
modelRetryAttempts = 0;
|
|
@@ -905,6 +759,18 @@ export function apply(ctx, config) {
|
|
|
905
759
|
modelRetryAttempts = 0;
|
|
906
760
|
}
|
|
907
761
|
}
|
|
762
|
+
if (skills?.skills && defaults.snapshot().user_id) {
|
|
763
|
+
const identity = defaults.snapshot();
|
|
764
|
+
skills = { ...skills, skills: await Promise.all(skills.skills.map(async (item) => {
|
|
765
|
+
const local = await localSkillState(config.platformUrl, item.canonical_slug, {
|
|
766
|
+
config: { ...config, dshHome: identity.home }, userId: identity.user_id, registry: identity.registry,
|
|
767
|
+
});
|
|
768
|
+
return { ...item, installed: ['current', 'report_pending'].includes(local.state),
|
|
769
|
+
local_state: local.state, installed_version: local.record?.version || '',
|
|
770
|
+
upgrade_available: local.state === 'current' && Boolean(item.latest_version && item.latest_fingerprint)
|
|
771
|
+
&& (local.record.version !== item.latest_version || local.record.fingerprint !== item.latest_fingerprint) };
|
|
772
|
+
})) };
|
|
773
|
+
}
|
|
908
774
|
// 登录入口判定:UI 登录卡文案需要;托管实例不显示登录入口,跳过查询
|
|
909
775
|
const loginProvider = isInstanceManaged() ? 'feishu' : await resolveLoginProvider(config.platformUrl, log);
|
|
910
776
|
sendJson(response, 200, {
|
|
@@ -918,6 +784,7 @@ export function apply(ctx, config) {
|
|
|
918
784
|
instance_id: credentials?.instance_id ?? null,
|
|
919
785
|
token_expires_at: credentials?.expires_at ?? null,
|
|
920
786
|
plugin_version: PLUGIN_VERSION,
|
|
787
|
+
defaults: defaults.snapshot(),
|
|
921
788
|
capabilities,
|
|
922
789
|
skills,
|
|
923
790
|
skill_display_names: displayNames,
|
|
@@ -960,7 +827,7 @@ export function apply(ctx, config) {
|
|
|
960
827
|
// /agentone/callback 会 403。desktop 环境(服务端自检 execPath 或页面
|
|
961
828
|
// 传 desktop 标记)改由插件在本进程起一次性本地服务收回调。
|
|
962
829
|
const callbackUrl = body.desktop || isDesktopRuntime
|
|
963
|
-
? await openCallbackServer(config, log)
|
|
830
|
+
? await openCallbackServer(config, log, onLogin, () => !disposed)
|
|
964
831
|
: String(body.callback_url || '');
|
|
965
832
|
if (!/^https?:\/\/|^\/portal\//.test(callbackUrl)) {
|
|
966
833
|
sendJson(response, 400, { error: '回调地址不合法' });
|
|
@@ -1008,6 +875,7 @@ export function apply(ctx, config) {
|
|
|
1008
875
|
redirect(false);
|
|
1009
876
|
return;
|
|
1010
877
|
}
|
|
878
|
+
if (disposed) { redirect(false); return; }
|
|
1011
879
|
const expiresIn = Number(result.expires_in) || 0;
|
|
1012
880
|
await saveCredentials(config, {
|
|
1013
881
|
schema_version: 2,
|
|
@@ -1024,10 +892,8 @@ export function apply(ctx, config) {
|
|
|
1024
892
|
...(await loadCredentials(config)),
|
|
1025
893
|
access_token: result.access_token,
|
|
1026
894
|
}).catch(() => {});
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
access_token: result.access_token,
|
|
1030
|
-
}).catch(() => {});
|
|
895
|
+
if (disposed) { redirect(false); return; }
|
|
896
|
+
onLogin();
|
|
1031
897
|
redirect(true);
|
|
1032
898
|
} catch (error) {
|
|
1033
899
|
log.warn(`[dsh-agentone] exchange failed: ${error?.message || error}`);
|
|
@@ -1035,6 +901,33 @@ export function apply(ctx, config) {
|
|
|
1035
901
|
}
|
|
1036
902
|
});
|
|
1037
903
|
|
|
904
|
+
register('/agentone/api/defaults/status', (request, response) => {
|
|
905
|
+
if (request.method !== 'GET') { sendJson(response, 405, { error: '请求方法不正确' }); return; }
|
|
906
|
+
sendJson(response, 200, defaults.snapshot());
|
|
907
|
+
});
|
|
908
|
+
for (const action of ['sync', 'retry', 'apply']) {
|
|
909
|
+
register(`/agentone/api/defaults/${action}`, async (request, response) => {
|
|
910
|
+
if (request.method !== 'POST') { sendJson(response, 405, { error: '请求方法不正确' }); return; }
|
|
911
|
+
try {
|
|
912
|
+
const body = await readJsonBody(request);
|
|
913
|
+
const credentials = await ensureFreshToken(config, log);
|
|
914
|
+
if (!credentials) { sendJson(response, 401, { error: '请先登录 AgentOne 平台' }); return; }
|
|
915
|
+
let item = null;
|
|
916
|
+
if (action !== 'sync') {
|
|
917
|
+
if (!['skill', 'plugin'].includes(body.kind) || typeof body.name !== 'string') {
|
|
918
|
+
sendJson(response, 400, { error: '请选择有效的技能或插件' }); return;
|
|
919
|
+
}
|
|
920
|
+
if (action === 'apply' && body.confirm !== true) {
|
|
921
|
+
sendJson(response, 400, { error: '应用差异将替换当前版本,请确认后继续' }); return;
|
|
922
|
+
}
|
|
923
|
+
item = { kind: body.kind, name: body.name, apply: action === 'apply' };
|
|
924
|
+
}
|
|
925
|
+
void defaults.sync({ manual: true, item }).then(invalidateStatusCache).catch(() => {});
|
|
926
|
+
sendJson(response, 202, { ok: true, running: true });
|
|
927
|
+
} catch (error) { sendJson(response, 400, { error: error?.message || '默认能力同步失败,请重试' }); }
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
1038
931
|
register('/agentone/api/model/sync', async (request, response) => {
|
|
1039
932
|
if (request.method !== 'POST') {
|
|
1040
933
|
response.writeHead(405, { allow: 'POST' });
|
|
@@ -1086,17 +979,12 @@ export function apply(ctx, config) {
|
|
|
1086
979
|
return;
|
|
1087
980
|
}
|
|
1088
981
|
const body = await readJsonBody(request);
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
config.platformUrl,
|
|
1096
|
-
credentials.access_token,
|
|
1097
|
-
slug,
|
|
1098
|
-
{ config },
|
|
1099
|
-
);
|
|
982
|
+
let slug;
|
|
983
|
+
try { slug = canonicalSkillSlug(body.slug); }
|
|
984
|
+
catch (error) { sendJson(response, 400, { error: error.message }); return; }
|
|
985
|
+
const result = await defaults.manualOperation((owner, options) => installPlatformSkill(
|
|
986
|
+
owner.platform, owner.token, slug, options,
|
|
987
|
+
));
|
|
1100
988
|
invalidateStatusCache();
|
|
1101
989
|
sendJson(response, 200, { ok: true, ...result });
|
|
1102
990
|
} catch (error) {
|
|
@@ -1153,7 +1041,15 @@ export function apply(ctx, config) {
|
|
|
1153
1041
|
config.platformUrl,
|
|
1154
1042
|
credentials.access_token,
|
|
1155
1043
|
).catch(() => null);
|
|
1156
|
-
const
|
|
1044
|
+
const identity = defaults.snapshot();
|
|
1045
|
+
const installed = [];
|
|
1046
|
+
if (identity.user_id) {
|
|
1047
|
+
for (const skill of listing?.skills || []) {
|
|
1048
|
+
const local = await localSkillState(config.platformUrl, skill.canonical_slug, {
|
|
1049
|
+
config: { ...config, dshHome: identity.home }, userId: identity.user_id, registry: identity.registry });
|
|
1050
|
+
if (['current', 'report_pending'].includes(local.state)) installed.push({ ...skill, installed: true, installed_version: local.record.version });
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1157
1053
|
sendJson(response, 200, { ...data, installed });
|
|
1158
1054
|
} catch (error) {
|
|
1159
1055
|
sendJson(response, 502, { error: '技能市场暂时不可用,请稍后再试' });
|
|
@@ -1173,12 +1069,10 @@ export function apply(ctx, config) {
|
|
|
1173
1069
|
return;
|
|
1174
1070
|
}
|
|
1175
1071
|
const body = await readJsonBody(request);
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
}
|
|
1181
|
-
await removePlatformSkill(config.platformUrl, credentials.access_token, slug, { config });
|
|
1072
|
+
let slug;
|
|
1073
|
+
try { slug = canonicalSkillSlug(body.slug); }
|
|
1074
|
+
catch (error) { sendJson(response, 400, { error: error.message }); return; }
|
|
1075
|
+
await defaults.manualOperation((owner, options) => removePlatformSkill(owner.platform, owner.token, slug, options));
|
|
1182
1076
|
invalidateStatusCache();
|
|
1183
1077
|
sendJson(response, 200, { ok: true, canonical_slug: slug });
|
|
1184
1078
|
} catch (error) {
|
|
@@ -1198,7 +1092,7 @@ export function apply(ctx, config) {
|
|
|
1198
1092
|
// 不用 pnpm outdated——它受元数据缓存与 minimumReleaseAge 冷却期
|
|
1199
1093
|
// 影响,新发布的版本一段时间内查不到,升级按钮追着发版延迟出现。
|
|
1200
1094
|
// 只查已装且来自 npm 的包;未安装/本地链接没有版本可查。
|
|
1201
|
-
const dir =
|
|
1095
|
+
const dir = profiles.target().dir;
|
|
1202
1096
|
await Promise.all(
|
|
1203
1097
|
state.plugins
|
|
1204
1098
|
.filter((item) => item.version !== null && item.source === 'npm')
|
|
@@ -1273,7 +1167,7 @@ export function apply(ctx, config) {
|
|
|
1273
1167
|
// 中版本。升级成功后落盘先变、运行代码要重启宿主才换——两者差值即
|
|
1274
1168
|
// 前端「已安装待重启」提示的依据。
|
|
1275
1169
|
try {
|
|
1276
|
-
const dir =
|
|
1170
|
+
const dir = profiles.target().dir;
|
|
1277
1171
|
const [version, latest] = await Promise.all([
|
|
1278
1172
|
installedVersion(dir, PLUGIN_PACKAGE_NAME),
|
|
1279
1173
|
pnpmViewLatest(PLUGIN_PACKAGE_NAME, dir).catch(() => null),
|
|
@@ -1674,6 +1568,7 @@ export function apply(ctx, config) {
|
|
|
1674
1568
|
try {
|
|
1675
1569
|
// 先吊销平台侧 refresh token(尽力而为),再清本地;凭据读取失败
|
|
1676
1570
|
// (文件损坏等)也要走到清凭据,不能把用户卡在登录态。
|
|
1571
|
+
defaults.reset();
|
|
1677
1572
|
const credentials = await loadCredentials(config);
|
|
1678
1573
|
if (credentials?.refresh_token) {
|
|
1679
1574
|
await revokePlatformToken(config.platformUrl, credentials.refresh_token);
|
|
@@ -1693,9 +1588,9 @@ export function apply(ctx, config) {
|
|
|
1693
1588
|
}
|
|
1694
1589
|
});
|
|
1695
1590
|
|
|
1696
|
-
//
|
|
1697
|
-
//
|
|
1698
|
-
ensureBuiltinPlugins(
|
|
1591
|
+
// 默认能力属于已登录平台;无凭据不安装兼容默认。协调器独立于页面存活。
|
|
1592
|
+
// 内置插件常驻检测与登录无关,独立先行。
|
|
1593
|
+
ensureBuiltinPlugins().catch((error) => {
|
|
1699
1594
|
log.warn(`[dsh-agentone] builtin plugin check failed: ${error?.message || error}`);
|
|
1700
1595
|
});
|
|
1701
1596
|
ensureLanguageDefault(config, log).catch((error) => {
|
|
@@ -1707,17 +1602,20 @@ export function apply(ctx, config) {
|
|
|
1707
1602
|
log.warn(`[dsh-agentone] instance credential bootstrap failed: ${error?.message || error}`);
|
|
1708
1603
|
});
|
|
1709
1604
|
const credentials = await ensureFreshToken(config, log);
|
|
1710
|
-
if (!credentials) return;
|
|
1605
|
+
if (!credentials || disposed) return;
|
|
1606
|
+
void defaults.sync().then(invalidateStatusCache).catch(() => {});
|
|
1711
1607
|
await syncPlatformModel(config, log).catch(() => {});
|
|
1712
1608
|
// SkillHub 连接在托管容器里由 Hub 直接注入 env(CLAWHUB_* / SKILLHUB_*),
|
|
1713
1609
|
// 插件不再重复写 skillhub.env / clawhub login,避免两个写入方互相盖。
|
|
1714
1610
|
if (!isInstanceManaged()) {
|
|
1715
1611
|
await syncPlatformSkillhub(config, log, credentials).catch(() => {});
|
|
1716
1612
|
}
|
|
1717
|
-
await syncPlanSkills(config, log, credentials).catch(() => {});
|
|
1718
1613
|
})();
|
|
1719
1614
|
|
|
1720
|
-
return () => {
|
|
1615
|
+
return async () => {
|
|
1616
|
+
disposed = true;
|
|
1617
|
+
await defaults.dispose();
|
|
1618
|
+
await profiles.dispose();
|
|
1721
1619
|
pendingStates.clear();
|
|
1722
1620
|
pendingCliAuth.clear();
|
|
1723
1621
|
clearModelRetry();
|