dsh-agentone 0.5.1 → 0.5.3

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/lib/index.js CHANGED
@@ -13,6 +13,7 @@
13
13
  // + agent-default-model)与 .credentials.yaml(refs.AGENTONE_API_KEY,0600)。
14
14
  // dsh 侧模型选择器随即出现「平台模型」,对话走平台代理,真实密钥不出平台。
15
15
  import z from '@deepseek-ai/schemastery';
16
+ import { createServer } from 'node:http';
16
17
  import { createRequire } from 'node:module';
17
18
  import { readFile } from 'node:fs/promises';
18
19
  import { join } from 'node:path';
@@ -456,6 +457,84 @@ async function cachedPlatformStatus(config, credentials) {
456
457
  return { ...statusCache, capabilities };
457
458
  }
458
459
 
460
+ /**
461
+ * 一次性本地回调服务(DSH Desktop 专用):desktop 宿主 webserver 只信
462
+ * Electron 内部请求,外部浏览器打 /agentone/callback 会 403。这里在插件
463
+ * 进程内起 127.0.0.1 随机端口的 http server,路径与宿主回调一致(code 换
464
+ * token + state 比对),收完第一个请求即关停;5 分钟无回调自动超时关闭。
465
+ * 返回外部浏览器可达的回调地址。
466
+ */
467
+ function openCallbackServer(config, log) {
468
+ return new Promise((resolve, reject) => {
469
+ const server = createServer(async (req, res) => {
470
+ const close = () => server.close();
471
+ const params = new URL(req.url, 'http://localhost').searchParams;
472
+ const code = params.get('code') || '';
473
+ const finish = (ok) => {
474
+ // 内联完成页:外部浏览器里没有 dsh 可返回,给出明确结果与关闭指引
475
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
476
+ res.end(`<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><title>AgentOne</title>
477
+ <style>body{font-family:system-ui,"PingFang SC",sans-serif;background:#f6f8fa;color:#1f2328;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0}
478
+ .card{background:#fff;border:1px solid #d8dee4;border-radius:16px;padding:32px 36px;text-align:center;max-width:380px}
479
+ .big{font-size:17px;font-weight:600;margin:0 0 8px}p{color:#57606a;font-size:14px}
480
+ button{font:inherit;padding:9px 22px;border-radius:10px;border:none;cursor:pointer;background:#2f6feb;color:#fff;margin-top:8px}</style></head>
481
+ <body><div class="card"><p class="big">${ok ? '✓ 已连接 AgentOne 平台' : '授权未完成'}</p>
482
+ <p>${ok ? '模型与技能正在自动配置,返回 dsh 的 AgentOne 页面即可看到已登录。' : '可以关闭本页,回到 dsh 的 AgentOne 页面重新发起登录。'}</p>
483
+ <button onclick="window.close()">关闭本页</button></div></body></html>`);
484
+ close();
485
+ };
486
+ if (!code) {
487
+ log.warn('[dsh-agentone] desktop callback rejected: missing code');
488
+ finish(false);
489
+ return;
490
+ }
491
+ try {
492
+ const result = await exchangeCode(config.platformUrl, code);
493
+ if (!pendingStates.delete(String(result.state || ''))) {
494
+ log.warn('[dsh-agentone] desktop callback rejected: unknown state');
495
+ finish(false);
496
+ return;
497
+ }
498
+ const expiresIn = Number(result.expires_in) || 0;
499
+ await saveCredentials(config, {
500
+ schema_version: 2,
501
+ access_token: result.access_token,
502
+ refresh_token: result.refresh_token || null,
503
+ platform_url: config.platformUrl,
504
+ saved_at: new Date().toISOString(),
505
+ expires_at: expiresIn ? new Date(Date.now() + expiresIn * 1000).toISOString() : null,
506
+ });
507
+ log.info('[dsh-agentone] platform login stored (desktop callback server)');
508
+ await syncPlatformModel(config, log).catch(() => {});
509
+ await syncPlanSkills(config, log, {
510
+ ...(await loadCredentials(config)),
511
+ access_token: result.access_token,
512
+ }).catch(() => {});
513
+ finish(true);
514
+ } catch (error) {
515
+ log.warn(`[dsh-agentone] desktop exchange failed: ${error?.message || error}`);
516
+ finish(false);
517
+ }
518
+ });
519
+ server.on('error', reject);
520
+ server.listen(0, '127.0.0.1', () => {
521
+ const { port } = server.address();
522
+ // 5 分钟兜底:授权窗一直没回来就关掉,不泄漏端口
523
+ setTimeout(() => server.close(), 5 * 60 * 1000).unref();
524
+ resolve(`http://127.0.0.1:${port}/agentone/callback`);
525
+ });
526
+ });
527
+ }
528
+
529
+ /**
530
+ * 是否运行在 DSH Desktop 内:宿主进程是 Electron(execPath 指向 DSH Desktop
531
+ * 可执行文件)。Desktop 的 webserver 只信 Electron 内部请求,外部浏览器回调
532
+ * 必须走一次性本地服务,与页面侧的自检(page.js/client.js)互为兜底。
533
+ */
534
+ function isDesktopHost() {
535
+ return /DSH Desktop\.app/i.test(process.execPath);
536
+ }
537
+
459
538
  export function apply(ctx, config) {
460
539
  const log = ctx.logger || console;
461
540
  ctx.effect(() => {
@@ -537,7 +616,12 @@ export function apply(ctx, config) {
537
616
  }
538
617
  try {
539
618
  const body = await readJsonBody(request);
540
- const callbackUrl = String(body.callback_url || '');
619
+ // DSH Desktop:宿主 webserver 只信 Electron 内部请求,外部浏览器访问
620
+ // /agentone/callback 会 403。desktop 环境(服务端自检 execPath 或页面
621
+ // 传 desktop 标记)改由插件在本进程起一次性本地服务收回调。
622
+ const callbackUrl = body.desktop || isDesktopHost()
623
+ ? await openCallbackServer(config, log)
624
+ : String(body.callback_url || '');
541
625
  if (!/^https?:\/\/|^\/portal\//.test(callbackUrl)) {
542
626
  sendJson(response, 400, { error: '回调地址不合法' });
543
627
  return;
package/lib/page.js CHANGED
@@ -630,13 +630,18 @@ const refresh = async () => {
630
630
  $('login-btn').onclick = async () => {
631
631
  const btn = $('login-btn');
632
632
  const base = location.pathname.replace(/\\/agentone\\/?$/, '');
633
- const callbackUrl = location.pathname.startsWith('/portal/')
634
- ? base + '/agentone/callback'
635
- : location.origin + base + '/agentone/callback';
633
+ // DSH Desktop:页面 URL 带 dsh-desktop-mode 参数,且宿主对外请求 403——
634
+ // desktop 标记让后端起一次性本地回调服务
635
+ const isDesktop = new URLSearchParams(location.search).has('dsh-desktop-mode');
636
+ const callbackUrl = isDesktop
637
+ ? null
638
+ : location.pathname.startsWith('/portal/')
639
+ ? base + '/agentone/callback'
640
+ : location.origin + base + '/agentone/callback';
636
641
  const resp = await fetch('api/login', {
637
642
  method: 'POST',
638
643
  headers: { 'Content-Type': 'application/json' },
639
- body: JSON.stringify({ callback_url: callbackUrl }),
644
+ body: JSON.stringify(isDesktop ? { desktop: true } : { callback_url: callbackUrl }),
640
645
  });
641
646
  const data = await resp.json();
642
647
  if (!resp.ok) { notify(data.error || '发起登录失败', 'err'); return; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agentone",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "AgentOne 平台集成插件:飞书登录、平台模型、SkillHub 技能、套餐申请、插件管理、lark-cli / ccpg-cli 探测(请求超时与错误码透传、令牌轮换 single-flight、凭据损坏容错、状态页 UX 增强)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",