weaver-work-cli 0.1.39 → 0.1.42
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 +4 -2
- package/dist/chunks/{archive-ENZYBBME.js → archive-J4I6MCKF.js} +2 -2
- package/dist/chunks/{archive-oa-YWLEC5V4.js → archive-oa-BRZKKTO4.js} +2 -2
- package/dist/chunks/{asset-OOV6DWPA.js → asset-L7D4FAOZ.js} +2 -2
- package/dist/chunks/{asset-idle-RE632MY5.js → asset-idle-DADLLV75.js} +2 -2
- package/dist/chunks/{attachment-content-RWN2FCLL.js → attachment-content-K4C7VLWQ.js} +3 -3
- package/dist/chunks/{blog-PEPOJAL7.js → blog-BVGQ7NZL.js} +2 -2
- package/dist/chunks/{caizhilian-WYIQDVPJ.js → caizhilian-SAADNFD6.js} +2 -2
- package/dist/chunks/{calendar-CIARBAOX.js → calendar-Q7NQY5Z2.js} +61 -23
- package/dist/chunks/{chunk-F2EQXUEE.js → chunk-3KTFS4IA.js} +397 -159
- package/dist/chunks/{chunk-G2JSIUWF.js → chunk-4PD5NWAX.js} +89 -0
- package/dist/chunks/{chunk-HJYVHXUQ.js → chunk-4RAE6JXP.js} +1 -1
- package/dist/chunks/{chunk-CQHGNCK2.js → chunk-72CKAUAG.js} +41 -8
- package/dist/chunks/{chunk-K3RUEEOL.js → chunk-APVFJUQH.js} +2 -2
- package/dist/chunks/{chunk-BJGXMFGD.js → chunk-BBKDXQEC.js} +2 -2
- package/dist/chunks/chunk-BIPYLLVS.js +501 -0
- package/dist/chunks/{chunk-GVPJZXJ2.js → chunk-EKWAQSLU.js} +92 -50
- package/dist/chunks/{chunk-XXJOSZTQ.js → chunk-FOAA27U5.js} +381 -57
- package/dist/chunks/{chunk-I2HB737U.js → chunk-G4UR6NXN.js} +2 -2
- package/dist/chunks/{chunk-YS5DPN4T.js → chunk-GEWYMQME.js} +1 -1
- package/dist/chunks/{chunk-P2DAISYW.js → chunk-GHXCDPTM.js} +1 -1
- package/dist/chunks/{chunk-L4N7B7ZD.js → chunk-IZ7S7QUS.js} +4 -4
- package/dist/chunks/{chunk-NLK2RZKG.js → chunk-JPRO6ER2.js} +122 -2214
- package/dist/chunks/{chunk-36DIFZAH.js → chunk-LADPYOWC.js} +2 -2
- package/dist/chunks/chunk-LDZQPUIA.js +1238 -0
- package/dist/chunks/chunk-NKZRQXST.js +2546 -0
- package/dist/chunks/{chunk-EINRJHQ3.js → chunk-QKNKR4RH.js} +3 -3
- package/dist/chunks/{chunk-T7JDJJ7H.js → chunk-R4CHUSKC.js} +126 -19
- package/dist/chunks/{chunk-EZGFHJFW.js → chunk-SA6VBIHY.js} +88 -15
- package/dist/chunks/{chunk-ZXRNHZTE.js → chunk-TTPJJJC4.js} +2 -2
- package/dist/chunks/{chunk-KTCB5R5H.js → chunk-W7BEHOSR.js} +2 -2
- package/dist/chunks/{chunk-MUQUHKPG.js → chunk-YRSZQUD2.js} +2 -2
- package/dist/chunks/{conflict-DRMDOL56.js → conflict-F2OMZRTO.js} +2 -2
- package/dist/chunks/{contract-73MERZWN.js → contract-5BETHRSG.js} +2 -2
- package/dist/chunks/{cowork-HNGBPD7A.js → cowork-3MN6EQQO.js} +27 -10
- package/dist/chunks/create-flow-5FQNNMVO.js +55 -0
- package/dist/chunks/{ebuilder-form-OY6DVNRK.js → ebuilder-form-SBHOYBUD.js} +3 -3
- package/dist/chunks/{ehr-RAQNEQO7.js → ehr-KMOJULKG.js} +432 -1234
- package/dist/chunks/env-LB7NBL2O.js +19 -0
- package/dist/chunks/{esb-6XV7C7YV.js → esb-W2PJCFCX.js} +4 -4
- package/dist/chunks/factory-6ZFQXQ46.js +30 -0
- package/dist/chunks/{fna-XTSRB7G7.js → fna-ZQPCUV4R.js} +2 -2
- package/dist/chunks/group-chat-2HHAJN46.js +20 -0
- package/dist/chunks/{host-YMBXWFWX.js → host-4CBHGOHT.js} +3 -3
- package/dist/chunks/{host-A7J6GDUP.js → host-ATKVNZAT.js} +3 -3
- package/dist/chunks/{host-3F5UM5GN.js → host-BQH22NTQ.js} +3 -3
- package/dist/chunks/{host-YAQAPYNP.js → host-X2CSLFM6.js} +3 -3
- package/dist/chunks/{host-VGPVP7YF.js → host-YTODKMSL.js} +3 -3
- package/dist/chunks/{hrm-TLHKC545.js → hrm-FFM4UJFS.js} +4 -4
- package/dist/chunks/{im-YOUCFMUK.js → im-VEHKDQPB.js} +8 -8
- package/dist/chunks/{invoice-WNBS2B4C.js → invoice-LVIKSIIF.js} +3 -3
- package/dist/chunks/{jiuchuanhui-XABWS5RN.js → jiuchuanhui-X6DINO5P.js} +2 -2
- package/dist/chunks/{lazy-dispatch-NLZ5UQQI.js → lazy-dispatch-KSWTN5L6.js} +4 -4
- package/dist/chunks/{mail-LRYMBFNL.js → mail-VDKRGUD3.js} +9 -4
- package/dist/chunks/{meeting-ESXL6MFI.js → meeting-CLC6HP3Z.js} +12 -10
- package/dist/chunks/{okr-EOV65SSF.js → okr-CHG4OO3U.js} +2 -2
- package/dist/chunks/{plan-YFPVKW6Y.js → plan-XWR75667.js} +2 -2
- package/dist/chunks/{project-MDVZUYQW.js → project-5BXOOKMG.js} +40 -23
- package/dist/chunks/recruit-stage-DGMFNNDE.js +4507 -0
- package/dist/chunks/{registry-VT5Y2HLA.js → registry-7FFOXIAW.js} +4 -4
- package/dist/chunks/{registry-QCLZEDAP.js → registry-BKBZPBAJ.js} +10 -8
- package/dist/chunks/{registry-YX4CSEMD.js → registry-RHMW6ANW.js} +2 -2
- package/dist/chunks/{registry-NYPYMZXG.js → registry-Z43434VG.js} +1 -1
- package/dist/chunks/{room-recommend-L3MWH4JW.js → room-recommend-62H7ZQQ6.js} +4 -4
- package/dist/chunks/{schedule-UASAVDVD.js → schedule-FNXKH3DQ.js} +4 -4
- package/dist/chunks/{workflow-KDGIUCNW.js → workflow-ADHKCAJK.js} +4 -4
- package/dist/index.js +33 -28
- package/dist/internal/e10/auth/commands.js +110 -56
- package/dist/internal/e10/auth/messages.js +6 -0
- package/dist/internal/e10/auth/session.js +28 -1
- package/dist/internal/e10/auth/xiaoe.js +315 -48
- package/dist/internal/e10/auth-recovery.js +36 -5
- package/dist/internal/e10/client.js +13 -0
- package/dist/internal/e10/context.js +61 -9
- package/dist/internal/e10/index.js +1 -1
- package/dist/internal/e10/network-error.js +93 -0
- package/dist/internal/e10/request-runtime.js +15 -0
- package/dist/shortcuts/calendar/operations/read.js +31 -22
- package/dist/shortcuts/calendar/operations/write.js +45 -1
- package/dist/shortcuts/cowork/operations/discuss-write.js +5 -1
- package/dist/shortcuts/cowork/operations/item-write.js +5 -3
- package/dist/shortcuts/cowork/operations/member-write.js +3 -1
- package/dist/shortcuts/cowork/operations/permission-write.js +5 -1
- package/dist/shortcuts/cowork/operations/shared.js +7 -0
- package/dist/shortcuts/ehr/operations/attend-write.js +43 -1
- package/dist/shortcuts/ehr/operations/hr-contact.js +7 -4
- package/dist/shortcuts/ehr/operations/hr-report.js +13 -7
- package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +134 -59
- package/dist/shortcuts/ehr/operations/recruit-write.js +262 -17
- package/dist/shortcuts/ehr/operations/recruit.js +10 -4
- package/dist/shortcuts/ehr/operations/shared.js +47 -0
- package/dist/shortcuts/hrm/operations/org.js +118 -0
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/mail/operations/mail-send.js +8 -2
- package/dist/shortcuts/meeting/operations/eb/catalog.js +9 -2
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +461 -127
- package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
- package/dist/shortcuts/meeting/operations/eb/factory.js +33 -0
- package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
- package/dist/shortcuts/meeting/operations/eb/group-chat.js +605 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/person-cache.js +96 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +44 -6
- package/dist/shortcuts/meeting/operations/eb/room-summary.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/usage-cache.js +99 -0
- package/dist/shortcuts/meeting/operations/from-group-chat.js +35 -12
- package/dist/shortcuts/project/operations/read.js +6 -25
- package/dist/shortcuts/project/operations/shared.js +22 -0
- package/dist/shortcuts/project/operations/write.js +28 -2
- package/dist/shortcuts/recruit-stage/context.js +119 -0
- package/dist/shortcuts/recruit-stage/continuation.js +73 -0
- package/dist/shortcuts/recruit-stage/errors.js +65 -0
- package/dist/shortcuts/recruit-stage/host.js +109 -0
- package/dist/shortcuts/recruit-stage/index.js +115 -0
- package/dist/shortcuts/recruit-stage/manifest.js +29 -0
- package/dist/shortcuts/recruit-stage/operations/interview-arrange.js +722 -0
- package/dist/shortcuts/recruit-stage/operations/interview-feedback.js +269 -0
- package/dist/shortcuts/recruit-stage/operations/jd-create.js +630 -0
- package/dist/shortcuts/recruit-stage/operations/list.js +431 -0
- package/dist/shortcuts/recruit-stage/operations/registry.js +38 -0
- package/dist/shortcuts/recruit-stage/operations/resume-import.js +1198 -0
- package/dist/shortcuts/recruit-stage/operations/search.js +158 -0
- package/dist/shortcuts/recruit-stage/operations/types.js +25 -0
- package/dist/shortcuts/recruit-stage/school-tiers.json +318 -0
- package/dist/shortcuts/recruit-stage/shared.js +608 -0
- package/dist/shortcuts/workflow/operations/form.js +21 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +52 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +4 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +19 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +17 -24
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +14 -9
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +78 -10
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +3 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +17 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +16 -40
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +3 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +52 -15
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +16 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +151 -63
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.subGroup.js +56 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +53 -16
- package/docs/e10-auth.md +22 -1
- package/docs/operation-manual.md +8 -0
- package/package.json +1 -1
- package/scripts/install-skill-to-xiaoe.mjs +105 -0
- package/scripts/mcp-meeting.mjs +234 -0
- package/scripts/package-skill.mjs +4 -2
- package/skill-template/business-info.json +24 -0
- package/skill-template/domains/recruit-stage.md +29 -0
- package/skills/weaver-e10-calendar/SKILL.md +2 -1
- package/skills/weaver-e10-calendar/references/calendar-write.md +54 -0
- package/skills/weaver-e10-cowork/SKILL.md +1 -1
- package/skills/weaver-e10-cowork/references/cowork-write.md +3 -1
- package/skills/weaver-e10-jucailin/SKILL.md +1 -1
- package/skills/weaver-e10-jucailin/references/attend-write.md +8 -0
- package/skills/weaver-e10-jucailin/references/hr-contact.md +8 -0
- package/skills/weaver-e10-jucailin/references/hr-report.md +8 -0
- package/skills/weaver-e10-jucailin/references/recruit-create.md +2 -2
- package/skills/weaver-e10-jucailin/references/recruit-read.md +1 -1
- package/skills/weaver-e10-jucailin/references/recruit-write.md +4 -3
- package/skills/weaver-e10-mail/SKILL.md +1 -1
- package/skills/weaver-e10-mail/references/mail-send.md +2 -0
- package/skills/weaver-e10-meeting/SKILL.md +119 -26
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-recruit-stage/SKILL.md +226 -0
- package/skills/weaver-e10-recruit-stage/agents/openai.yaml +119 -0
- package/skills/weaver-e10-recruit-stage/lib/recruit-xiaoe-file.mjs +188 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-arrange-interview.md +322 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-capability-check.md +127 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-check-install.md +65 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-detail-process.md +63 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-edit-interview-feedback.md +142 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-jd-create.md +73 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-jd-template.md +56 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-list-candidate.md +101 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-list-interview.md +148 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-match-candidates.md +77 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-output-links.md +59 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resolve-creator.md +69 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resolve-metadata.md +241 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resume-import.md +438 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resume-parse.md +128 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-search-candidate.md +146 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-urge-feedback.md +69 -0
- package/skills/weaver-e10-shared/SKILL.md +2 -2
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -2
- package/skills/weaver-e10-shijingran/SKILL.md +3 -2
- package/skills/weaver-e10-shijingran/references/operations.md +3 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +41 -40
- package/skills/weaver-e10-shijingran/references/write-operations.md +2 -0
- package/skills/weaver-e10-workflow/SKILL.md +7 -1
- package/skills/weaver-e10-workflow/aigroup/default.json +23 -9
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +37 -21
- package/skills/weaver-e10-workflow/tools/todo-summary.cjs +135 -43
- package/skills/weaver-e10-yepiaotong/SKILL.md +9 -10
- package/skills/weaver-e10-yepiaotong/references/invoice-agent-entry.md +6 -3
|
@@ -4,8 +4,14 @@ import { dirname, resolve } from 'node:path';
|
|
|
4
4
|
import { CliError, ExitCode } from '../../../core/errors.js';
|
|
5
5
|
import { isConnectorMode } from '../../../core/connector.js';
|
|
6
6
|
import { writeWithRetry } from '../../../core/write-with-retry.js';
|
|
7
|
+
import { createTimeoutSignal } from '../network-error.js';
|
|
7
8
|
import { encryptAuthData, parseAuthFile } from './crypto.js';
|
|
8
9
|
import { getE10ActiveProfilePath, getE10ProfilesRoot } from './paths.js';
|
|
10
|
+
/**
|
|
11
|
+
* teamsCheck 与会话探测都是「发业务请求之前的必经往返」,没有上限时一个半开
|
|
12
|
+
* 连接就能把整条命令挂死。所有这类小请求统一用这个超时。
|
|
13
|
+
*/
|
|
14
|
+
export const E10_TEAMS_CHECK_TIMEOUT_MS = 15_000;
|
|
9
15
|
const RSA_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
10
16
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCEOk/WyyJKdAHIfMODFnKg7MBoraFBnL3F18hhLx5n01J5g4hCkmpc5+KQFKIQIA/QWk9m7QBzI/OV9ZDFk54LaWm3xP8C+P1TPp7UVGz3iNDLdvgSxuGEYPmML3NaqkbVlvW7/NlNA3ED4Kao7rR5GUYSdFfbaBoxBFae638hzQIDAQAB
|
|
11
17
|
-----END PUBLIC KEY-----`;
|
|
@@ -337,6 +343,15 @@ export class E10AuthSession {
|
|
|
337
343
|
get credentialSourceLabel() {
|
|
338
344
|
return this.data.credentialSourceLabel || '';
|
|
339
345
|
}
|
|
346
|
+
get expiryTimes() {
|
|
347
|
+
const value = Number(this.data.expiryTimes);
|
|
348
|
+
return Number.isFinite(value) && value > 0 ? value : undefined;
|
|
349
|
+
}
|
|
350
|
+
/** XiaoE 换取的 ETEAMSID 是否已经超过其明确的失效时间。 */
|
|
351
|
+
isExpired(now = Date.now()) {
|
|
352
|
+
const expiryTimes = this.expiryTimes;
|
|
353
|
+
return expiryTimes !== undefined && now >= expiryTimes;
|
|
354
|
+
}
|
|
340
355
|
getBaseUrl() {
|
|
341
356
|
return (this.data.baseUrl || '').replace(/\/$/, '');
|
|
342
357
|
}
|
|
@@ -389,6 +404,7 @@ export class E10AuthSession {
|
|
|
389
404
|
stTicket: this.data.stTicket,
|
|
390
405
|
redirectUrl: this.data.redirectUrl,
|
|
391
406
|
cookieSavedAt: this.data.cookieSavedAt,
|
|
407
|
+
expiryTimes: this.expiryTimes,
|
|
392
408
|
mainUrl: this.data.mainUrl,
|
|
393
409
|
...sourceInfo,
|
|
394
410
|
hasEteamsId: Boolean(this.eteamsId),
|
|
@@ -419,12 +435,14 @@ export class E10AuthSession {
|
|
|
419
435
|
return null;
|
|
420
436
|
const session = E10AuthSession.fromFile(authPath, baseUrl, passportUrl);
|
|
421
437
|
// 富化只是"刷新/纠偏":登录时这些字段已落盘。调用方可显式跳过(见 E10SessionLoadOptions)。
|
|
422
|
-
if (session && !(load.skipProfileEnrichment && hasUsableUserId(session))) {
|
|
438
|
+
if (session && !session.isExpired() && !(load.skipProfileEnrichment && hasUsableUserId(session))) {
|
|
423
439
|
await session.fetchEmployeeId();
|
|
424
440
|
}
|
|
425
441
|
return session;
|
|
426
442
|
}
|
|
427
443
|
async fetchEmployeeId() {
|
|
444
|
+
// 富化不是关键路径:超时就放弃,不能让 teamsCheck 无限等待。
|
|
445
|
+
const { signal, clear } = createTimeoutSignal(E10_TEAMS_CHECK_TIMEOUT_MS);
|
|
428
446
|
try {
|
|
429
447
|
const resp = await fetch(`${this.getBaseUrl()}/api/baseserver/layout/teamsCheck?clientType=not_xinchuang&client=WEB&domainName=${encodeURIComponent(this.getBaseUrl())}`, {
|
|
430
448
|
method: 'POST',
|
|
@@ -436,6 +454,7 @@ export class E10AuthSession {
|
|
|
436
454
|
'user-agent': this.uaHeader(),
|
|
437
455
|
origin: this.getBaseUrl(),
|
|
438
456
|
},
|
|
457
|
+
signal,
|
|
439
458
|
});
|
|
440
459
|
const data = await resp.json().catch(() => ({}));
|
|
441
460
|
const userInfo = readTeamsCheckUserInfo(resp, data);
|
|
@@ -455,8 +474,12 @@ export class E10AuthSession {
|
|
|
455
474
|
catch {
|
|
456
475
|
// Non-critical profile enrichment.
|
|
457
476
|
}
|
|
477
|
+
finally {
|
|
478
|
+
clear();
|
|
479
|
+
}
|
|
458
480
|
}
|
|
459
481
|
async checkValid() {
|
|
482
|
+
const { signal, clear } = createTimeoutSignal(E10_TEAMS_CHECK_TIMEOUT_MS);
|
|
460
483
|
try {
|
|
461
484
|
const resp = await fetch(`${this.getBaseUrl()}/api/ecode/tree/list?parentId=0`, {
|
|
462
485
|
headers: {
|
|
@@ -466,12 +489,16 @@ export class E10AuthSession {
|
|
|
466
489
|
'user-agent': this.uaHeader(),
|
|
467
490
|
},
|
|
468
491
|
redirect: 'manual',
|
|
492
|
+
signal,
|
|
469
493
|
});
|
|
470
494
|
return resp.status === 200;
|
|
471
495
|
}
|
|
472
496
|
catch {
|
|
473
497
|
return false;
|
|
474
498
|
}
|
|
499
|
+
finally {
|
|
500
|
+
clear();
|
|
501
|
+
}
|
|
475
502
|
}
|
|
476
503
|
async ensureValid() {
|
|
477
504
|
if (await this.checkValid())
|
|
@@ -5,7 +5,8 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { CliError, ExitCode } from '../../../core/errors.js';
|
|
6
6
|
import { packagePath } from '../../../core/package-paths.js';
|
|
7
7
|
import { isXiaoERuntime, isXiaoERuntimeAgent } from '../../../core/runtime-agent.js';
|
|
8
|
-
import {
|
|
8
|
+
import { createTimeoutSignal, isAbortError, isNetworkErrorCode, looksLikeTransportMessage, readErrorCauseCode, } from '../network-error.js';
|
|
9
|
+
import { buildE10CookieHeader, E10_TEAMS_CHECK_TIMEOUT_MS, E10AuthSession, extractRawSetCookie, extractSessionTicket, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
|
|
9
10
|
export const DEFAULT_E10_BASE_URL = 'https://www.e-cology.com.cn';
|
|
10
11
|
export const XIAOE_AGENT_TYPE = 'XiaoE';
|
|
11
12
|
// `XIAOE_` 是宿主/平台保留前缀(XIAOE_DOMAIN、XIAOE_USER_TOKEN、XIAOE_SKILL_* 等)。
|
|
@@ -57,6 +58,111 @@ export const XIAOE_SKILL_ETEAMID_API_PATH = '/spec-apps/api/app/rpachat/skill/ex
|
|
|
57
58
|
export const XIAOE_CLIENT_SETTINGS_SOURCE = 'XiaoE/Robots/user_settings.json';
|
|
58
59
|
export const XIAOE_USER_TOKEN_SOURCE = 'XIAOE_USER_TOKEN';
|
|
59
60
|
export const XIAOE_BUNDLED_ENV_SCRIPT_SOURCE = 'bundled scripts/xiaoe_env.py';
|
|
61
|
+
/** 与 XiaoE `tokens.py` 保持一致:换票成功后默认有效 2 小时。 */
|
|
62
|
+
export const XIAOE_ETEAMID_TTL_MS = 7_200_000;
|
|
63
|
+
const XIAOE_DIAGNOSTIC_MAX_LENGTH = 300;
|
|
64
|
+
/** 换票失败原因可能夹带服务端回显,落进结构化错误前先抹掉已知密钥。 */
|
|
65
|
+
function redactXiaoEDiagnostic(message, secrets) {
|
|
66
|
+
let text = message;
|
|
67
|
+
for (const secret of secrets) {
|
|
68
|
+
if (secret && secret.length >= 8 && text.includes(secret)) {
|
|
69
|
+
text = text.split(secret).join('<redacted>');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const normalized = text.replace(/\s+/gu, ' ').trim();
|
|
73
|
+
return normalized.length > XIAOE_DIAGNOSTIC_MAX_LENGTH
|
|
74
|
+
? `${normalized.slice(0, XIAOE_DIAGNOSTIC_MAX_LENGTH)}…`
|
|
75
|
+
: normalized;
|
|
76
|
+
}
|
|
77
|
+
function noteXiaoEStep(diagnostics, step) {
|
|
78
|
+
diagnostics?.attemptedSteps.push(step);
|
|
79
|
+
}
|
|
80
|
+
function tagXiaoEError(message, tags) {
|
|
81
|
+
return Object.assign(new CliError(message, ExitCode.AuthError), tags);
|
|
82
|
+
}
|
|
83
|
+
function readTag(error, key, type) {
|
|
84
|
+
if (!error || typeof error !== 'object')
|
|
85
|
+
return undefined;
|
|
86
|
+
const value = error[key];
|
|
87
|
+
return typeof value === type ? value : undefined;
|
|
88
|
+
}
|
|
89
|
+
/** 401/403/3xx = 登录态被拒;其余 4xx/5xx 才算服务端 HTTP 异常。 */
|
|
90
|
+
function classifyXiaoEHttpStatus(status) {
|
|
91
|
+
if (status === 401 || status === 403)
|
|
92
|
+
return 'credential';
|
|
93
|
+
if (status >= 300 && status < 400)
|
|
94
|
+
return 'credential';
|
|
95
|
+
return status >= 400 ? 'http' : 'credential';
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 把任意异常归成三类。已经带标签的错误直接沿用;原始 fetch 异常则顺着
|
|
99
|
+
* `error.cause.code`(Node 把真实原因藏在这一层)区分网络错误与协议错误。
|
|
100
|
+
*/
|
|
101
|
+
function classifyXiaoEError(error) {
|
|
102
|
+
const taggedKind = readTag(error, 'errorKind', 'string');
|
|
103
|
+
const taggedStatus = readTag(error, 'status', 'number');
|
|
104
|
+
const taggedCause = readTag(error, 'causeCode', 'string');
|
|
105
|
+
const causeCode = taggedCause || readErrorCauseCode(error);
|
|
106
|
+
const withCause = causeCode ? { causeCode } : {};
|
|
107
|
+
const withStatus = taggedStatus === undefined ? {} : { status: taggedStatus };
|
|
108
|
+
if (taggedKind)
|
|
109
|
+
return { kind: taggedKind, ...withCause, ...withStatus };
|
|
110
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
+
if (isAbortError(error))
|
|
112
|
+
return { kind: 'network', causeCode: causeCode || 'ETIMEDOUT' };
|
|
113
|
+
if (taggedStatus !== undefined) {
|
|
114
|
+
return { kind: classifyXiaoEHttpStatus(taggedStatus), ...withCause, status: taggedStatus };
|
|
115
|
+
}
|
|
116
|
+
if (causeCode && isNetworkErrorCode(causeCode))
|
|
117
|
+
return { kind: 'network', causeCode };
|
|
118
|
+
const httpMatch = /\bHTTP (\d{3})\b/u.exec(message);
|
|
119
|
+
if (httpMatch) {
|
|
120
|
+
const status = Number(httpMatch[1]);
|
|
121
|
+
return { kind: classifyXiaoEHttpStatus(status), ...withCause, status };
|
|
122
|
+
}
|
|
123
|
+
if (looksLikeTransportMessage(message))
|
|
124
|
+
return { kind: 'network', ...withCause };
|
|
125
|
+
return { kind: 'credential', ...withCause };
|
|
126
|
+
}
|
|
127
|
+
function noteXiaoEError(diagnostics, error, secrets = [], stage = 'e10_session') {
|
|
128
|
+
if (!diagnostics)
|
|
129
|
+
return;
|
|
130
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
131
|
+
const redacted = redactXiaoEDiagnostic(message, secrets);
|
|
132
|
+
if (redacted)
|
|
133
|
+
diagnostics.lastError = redacted;
|
|
134
|
+
const { kind, causeCode, status } = classifyXiaoEError(error);
|
|
135
|
+
const failure = {
|
|
136
|
+
stage: readTag(error, 'stage', 'string') ?? stage,
|
|
137
|
+
kind,
|
|
138
|
+
message: redacted,
|
|
139
|
+
...(causeCode ? { causeCode } : {}),
|
|
140
|
+
...(status === undefined ? {} : { httpStatus: status }),
|
|
141
|
+
};
|
|
142
|
+
if (!diagnostics.failures)
|
|
143
|
+
diagnostics.failures = [];
|
|
144
|
+
diagnostics.failures.push(failure);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 一次恢复失败的整体归因:只要出现过明确的凭证拒绝,就按凭证问题报(用户该去
|
|
148
|
+
* 重新登录);否则 HTTP 异常优先;只有**所有**失败都是网络错误时,才判定为
|
|
149
|
+
* 「暂时连不上 E10」,避免把网络抖动说成登录失效。
|
|
150
|
+
*/
|
|
151
|
+
export function summarizeXiaoEFailureKind(diagnostics) {
|
|
152
|
+
const failures = diagnostics?.failures || [];
|
|
153
|
+
if (!failures.length)
|
|
154
|
+
return undefined;
|
|
155
|
+
if (failures.some((failure) => failure.kind === 'credential'))
|
|
156
|
+
return 'credential';
|
|
157
|
+
if (failures.some((failure) => failure.kind === 'http'))
|
|
158
|
+
return 'http';
|
|
159
|
+
return failures.every((failure) => failure.kind === 'network') ? 'network' : undefined;
|
|
160
|
+
}
|
|
161
|
+
/** 最后一条失败记录,用于把结构化错误的 `stage` 指向真正卡住的那一步。 */
|
|
162
|
+
export function lastXiaoEFailure(diagnostics) {
|
|
163
|
+
const failures = diagnostics?.failures || [];
|
|
164
|
+
return failures.length ? failures[failures.length - 1] : undefined;
|
|
165
|
+
}
|
|
60
166
|
function envString(env, ...keys) {
|
|
61
167
|
for (const key of keys) {
|
|
62
168
|
const value = env[key];
|
|
@@ -89,6 +195,18 @@ export function hasXiaoEEnv(env = process.env, options = {}) {
|
|
|
89
195
|
const explicitAgentType = envString(env, 'WEAVER_AGENT_TYPE');
|
|
90
196
|
return explicitAgentType ? isXiaoERuntimeAgent(explicitAgentType) : false;
|
|
91
197
|
}
|
|
198
|
+
export function resolveXiaoEClientType(env = process.env) {
|
|
199
|
+
const raw = envString(env, 'XIAOE_CLIENT_TYPE').toLowerCase();
|
|
200
|
+
if (raw === 'client' || raw === 'desktop' || raw === 'pc')
|
|
201
|
+
return 'client';
|
|
202
|
+
if (raw === 'cloud' || raw === 'web' || raw === 'browser')
|
|
203
|
+
return 'cloud';
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
/** 网页端运行时没有桌面凭据通道,只有平台票据可换。 */
|
|
207
|
+
export function isXiaoECloudRuntime(env = process.env) {
|
|
208
|
+
return resolveXiaoEClientType(env) === 'cloud';
|
|
209
|
+
}
|
|
92
210
|
export function describeXiaoECredentialSource(source) {
|
|
93
211
|
// Step 1 is the platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN):
|
|
94
212
|
// it is available in every sandbox run and needs no local desktop client.
|
|
@@ -134,6 +252,14 @@ function readString(data, ...keys) {
|
|
|
134
252
|
}
|
|
135
253
|
return '';
|
|
136
254
|
}
|
|
255
|
+
function readExpiryTimes(data) {
|
|
256
|
+
for (const key of ['expiryTimes', 'expiryTime', 'expiresAt', 'expires_at']) {
|
|
257
|
+
const value = Number(data[key]);
|
|
258
|
+
if (Number.isFinite(value) && value > 0)
|
|
259
|
+
return Math.trunc(value);
|
|
260
|
+
}
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
137
263
|
function cookieString(value) {
|
|
138
264
|
if (!value)
|
|
139
265
|
return '';
|
|
@@ -209,7 +335,19 @@ function readXiaoECredentialRecord(value, source, fallbackBaseUrl = '') {
|
|
|
209
335
|
|| cookieValue(rawCookie, 'ETEAMSID');
|
|
210
336
|
if (!baseUrl || !eteamsId)
|
|
211
337
|
return null;
|
|
212
|
-
|
|
338
|
+
const expiryTimes = candidates.map(readExpiryTimes).find((value) => value !== undefined);
|
|
339
|
+
return {
|
|
340
|
+
baseUrl: normalizeUrl(baseUrl),
|
|
341
|
+
eteamsId,
|
|
342
|
+
source,
|
|
343
|
+
rawCookie,
|
|
344
|
+
...(expiryTimes !== undefined ? { expiryTimes } : {}),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function withDefaultXiaoEExpiry(credential) {
|
|
348
|
+
if (!credential || credential.expiryTimes !== undefined)
|
|
349
|
+
return credential;
|
|
350
|
+
return { ...credential, expiryTimes: Date.now() + XIAOE_ETEAMID_TTL_MS };
|
|
213
351
|
}
|
|
214
352
|
function parseXiaoEUnifiedLoginMap(raw, source) {
|
|
215
353
|
const value = raw.trim();
|
|
@@ -533,7 +671,7 @@ async function resolveXiaoETokenScriptCredential(baseUrl, env = process.env) {
|
|
|
533
671
|
const tokenData = await runXiaoETokenScript(baseUrl, env);
|
|
534
672
|
if (!tokenData)
|
|
535
673
|
return null;
|
|
536
|
-
return readXiaoECredentialRecord(tokenData, 'xiaoeUserData/tokens.py', baseUrl);
|
|
674
|
+
return withDefaultXiaoEExpiry(readXiaoECredentialRecord(tokenData, 'xiaoeUserData/tokens.py', baseUrl));
|
|
537
675
|
}
|
|
538
676
|
async function resolveXiaoEBundledEnvScriptCredential(baseUrl, env = process.env) {
|
|
539
677
|
const scriptPath = xiaoEBundledEnvScriptPath();
|
|
@@ -542,7 +680,7 @@ async function resolveXiaoEBundledEnvScriptCredential(baseUrl, env = process.env
|
|
|
542
680
|
const tokenData = await runXiaoEPythonCredentialScript(scriptPath, baseUrl, env);
|
|
543
681
|
if (!tokenData)
|
|
544
682
|
return null;
|
|
545
|
-
return readXiaoECredentialRecord(tokenData, XIAOE_BUNDLED_ENV_SCRIPT_SOURCE, baseUrl);
|
|
683
|
+
return withDefaultXiaoEExpiry(readXiaoECredentialRecord(tokenData, XIAOE_BUNDLED_ENV_SCRIPT_SOURCE, baseUrl));
|
|
546
684
|
}
|
|
547
685
|
export function xiaoEClientRobotsRoot(env = process.env) {
|
|
548
686
|
if (process.platform === 'win32') {
|
|
@@ -684,15 +822,22 @@ export function inspectXiaoEUnifiedLoginSources(options = {}) {
|
|
|
684
822
|
});
|
|
685
823
|
const platformCredential = hasXiaoEPlatformCredential(env);
|
|
686
824
|
const allowLocalDiscovery = options.allowLocalDiscovery !== false;
|
|
687
|
-
const
|
|
825
|
+
const clientType = resolveXiaoEClientType(env);
|
|
826
|
+
// 网页端/沙箱不存在桌面凭据通道,跳过探测也就不会被机器上别人的
|
|
827
|
+
// 客户端登录态误命中。
|
|
828
|
+
const allowDesktopChannels = clientType !== 'cloud';
|
|
829
|
+
const tokenScript = allowDesktopChannels
|
|
830
|
+
&& options.allowTokenScript !== false
|
|
688
831
|
&& hasXiaoETokenScript(env, allowLocalDiscovery);
|
|
689
|
-
const clientCredential =
|
|
832
|
+
const clientCredential = allowDesktopChannels
|
|
833
|
+
&& hasXiaoEClientCredential(env, allowLocalDiscovery);
|
|
690
834
|
const environmentCredential = Boolean(baseUrl && resolveXiaoEUnifiedLoginCredential(baseUrl, env));
|
|
691
835
|
return {
|
|
692
836
|
platformCredential,
|
|
693
837
|
tokenScript,
|
|
694
838
|
clientCredential,
|
|
695
839
|
environmentCredential,
|
|
840
|
+
clientType,
|
|
696
841
|
any: platformCredential || tokenScript || clientCredential || environmentCredential,
|
|
697
842
|
};
|
|
698
843
|
}
|
|
@@ -719,11 +864,16 @@ async function fetchXiaoEClientEteamId(credential, domain) {
|
|
|
719
864
|
if (resp.status < 300 || resp.status >= 400)
|
|
720
865
|
break;
|
|
721
866
|
if (redirects === 5) {
|
|
722
|
-
throw
|
|
867
|
+
throw tagXiaoEError('XiaoE client token endpoint redirected too many times.', {
|
|
868
|
+
errorKind: 'http',
|
|
869
|
+
});
|
|
723
870
|
}
|
|
724
871
|
const location = resp.headers.get('location');
|
|
725
872
|
if (!location) {
|
|
726
|
-
throw
|
|
873
|
+
throw tagXiaoEError('XiaoE client token endpoint returned a redirect without Location.', {
|
|
874
|
+
errorKind: 'http',
|
|
875
|
+
status: resp.status,
|
|
876
|
+
});
|
|
727
877
|
}
|
|
728
878
|
const next = new URL(location, endpoint);
|
|
729
879
|
const sameOrigin = next.origin === endpoint.origin;
|
|
@@ -732,17 +882,32 @@ async function fetchXiaoEClientEteamId(credential, domain) {
|
|
|
732
882
|
&& next.hostname === endpoint.hostname;
|
|
733
883
|
const safeProtocol = next.protocol === 'http:' || next.protocol === 'https:';
|
|
734
884
|
if (!safeProtocol || next.username || next.password || (!sameOrigin && !sameHostHttpsUpgrade)) {
|
|
735
|
-
throw
|
|
885
|
+
throw tagXiaoEError('XiaoE client token endpoint attempted an unsafe cross-origin redirect.', {
|
|
886
|
+
errorKind: 'http',
|
|
887
|
+
status: resp.status,
|
|
888
|
+
});
|
|
736
889
|
}
|
|
737
890
|
await resp.body?.cancel().catch(() => undefined);
|
|
738
891
|
endpoint = next;
|
|
739
892
|
}
|
|
740
893
|
if (!resp?.ok) {
|
|
741
|
-
|
|
894
|
+
const status = resp?.status || 0;
|
|
895
|
+
throw tagXiaoEError(`XiaoE client token endpoint returned HTTP ${status}.`, {
|
|
896
|
+
errorKind: classifyXiaoEHttpStatus(status),
|
|
897
|
+
status,
|
|
898
|
+
});
|
|
742
899
|
}
|
|
743
|
-
const data = await resp.json().catch(() =>
|
|
900
|
+
const data = await resp.json().catch((error) => {
|
|
901
|
+
// 读响应体时被超时打断属于网络错误,不能当成「返回了非法 JSON」。
|
|
902
|
+
if (isAbortError(error))
|
|
903
|
+
throw error;
|
|
904
|
+
return null;
|
|
905
|
+
});
|
|
744
906
|
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
745
|
-
throw
|
|
907
|
+
throw tagXiaoEError('XiaoE client token endpoint returned invalid JSON.', {
|
|
908
|
+
errorKind: 'http',
|
|
909
|
+
status: resp.status,
|
|
910
|
+
});
|
|
746
911
|
}
|
|
747
912
|
const payload = data;
|
|
748
913
|
const businessCode = payload.code;
|
|
@@ -751,10 +916,23 @@ async function fetchXiaoEClientEteamId(credential, domain) {
|
|
|
751
916
|
: {};
|
|
752
917
|
if ((businessCode !== '0000' && businessCode !== 0 && businessCode !== 200) || !readString(businessData, 'eteamId')) {
|
|
753
918
|
const codeText = businessCode === undefined ? 'missing' : String(businessCode);
|
|
754
|
-
throw
|
|
919
|
+
throw tagXiaoEError(`XiaoE client token exchange failed with business code ${codeText}.`, {
|
|
920
|
+
errorKind: 'credential',
|
|
921
|
+
});
|
|
755
922
|
}
|
|
756
923
|
return payload;
|
|
757
924
|
}
|
|
925
|
+
catch (error) {
|
|
926
|
+
if (isAbortError(error)) {
|
|
927
|
+
throw tagXiaoEError(`XiaoE client token endpoint timed out after ${XIAOE_CLIENT_TOKEN_TIMEOUT_MS} ms.`, { errorKind: 'network', causeCode: 'ETIMEDOUT' });
|
|
928
|
+
}
|
|
929
|
+
// 已经带语义的 HTTP / 业务码错误原样上抛,别再包一层网络错误。
|
|
930
|
+
if (error instanceof CliError)
|
|
931
|
+
throw error;
|
|
932
|
+
const causeCode = readErrorCauseCode(error);
|
|
933
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
934
|
+
throw tagXiaoEError(`XiaoE client token endpoint request failed${causeCode ? ` (${causeCode})` : ''}: ${message}`, { errorKind: 'network', ...(causeCode ? { causeCode } : {}) });
|
|
935
|
+
}
|
|
758
936
|
finally {
|
|
759
937
|
clearTimeout(timer);
|
|
760
938
|
}
|
|
@@ -766,7 +944,7 @@ async function resolveXiaoETokenCredential(credential, baseUrl) {
|
|
|
766
944
|
// from the same origin as the token exchange. Newer 0000/200 responses keep
|
|
767
945
|
// the XiaoE service origin separate and use the requested E10 target.
|
|
768
946
|
const resolvedBaseUrl = payload.code === 0 ? credential.erpaUrl : targetBaseUrl;
|
|
769
|
-
return readXiaoECredentialRecord(payload, credential.source, resolvedBaseUrl);
|
|
947
|
+
return withDefaultXiaoEExpiry(readXiaoECredentialRecord(payload, credential.source, resolvedBaseUrl));
|
|
770
948
|
}
|
|
771
949
|
async function resolveXiaoEPlatformCredential(baseUrl, env = process.env) {
|
|
772
950
|
const credential = readXiaoEPlatformCredential(env);
|
|
@@ -809,26 +987,62 @@ export function createXiaoEUnifiedLoginUnavailableError(baseUrl = '') {
|
|
|
809
987
|
});
|
|
810
988
|
}
|
|
811
989
|
async function resolveUserInfo(baseUrl, eteamsId, rawCookie = '', agentType = '') {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
990
|
+
// teamsCheck 是恢复流程的必经往返:没有超时的话,服务端半开连接会把整条命令挂死。
|
|
991
|
+
const { signal, clear } = createTimeoutSignal(E10_TEAMS_CHECK_TIMEOUT_MS);
|
|
992
|
+
try {
|
|
993
|
+
const resp = await fetch(`${baseUrl}/api/baseserver/layout/teamsCheck?clientType=not_xinchuang&client=WEB&domainName=${encodeURIComponent(baseUrl)}`, {
|
|
994
|
+
method: 'POST',
|
|
995
|
+
headers: {
|
|
996
|
+
accept: 'application/json',
|
|
997
|
+
'content-type': 'application/json',
|
|
998
|
+
eteamsid: eteamsId,
|
|
999
|
+
cookie: buildE10CookieHeader(rawCookie, eteamsId, agentType),
|
|
1000
|
+
'user-agent': agentType ? `AgentType=${agentType},IsAgent=true` : 'weaver-work-cli',
|
|
1001
|
+
origin: baseUrl,
|
|
1002
|
+
},
|
|
1003
|
+
redirect: 'manual',
|
|
1004
|
+
signal,
|
|
1005
|
+
});
|
|
1006
|
+
// 读响应体同样在超时窗口内;中途被 abort 不能当成「空响应」吞掉。
|
|
1007
|
+
const data = await resp.json().catch((error) => {
|
|
1008
|
+
if (isAbortError(error))
|
|
1009
|
+
throw error;
|
|
1010
|
+
return {};
|
|
1011
|
+
});
|
|
1012
|
+
return { userInfo: readTeamsCheckUserInfo(resp, data), setCookie: extractRawSetCookie(resp), status: resp.status };
|
|
1013
|
+
}
|
|
1014
|
+
catch (error) {
|
|
1015
|
+
if (isAbortError(error)) {
|
|
1016
|
+
throw tagXiaoEError(`E10 teamsCheck timed out after ${E10_TEAMS_CHECK_TIMEOUT_MS} ms.`, { errorKind: 'network', stage: 'teams_check', causeCode: 'ETIMEDOUT' });
|
|
1017
|
+
}
|
|
1018
|
+
if (error instanceof CliError)
|
|
1019
|
+
throw error;
|
|
1020
|
+
const causeCode = readErrorCauseCode(error);
|
|
1021
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1022
|
+
throw tagXiaoEError(`E10 teamsCheck request failed${causeCode ? ` (${causeCode})` : ''}: ${message}`, { errorKind: 'network', stage: 'teams_check', ...(causeCode ? { causeCode } : {}) });
|
|
1023
|
+
}
|
|
1024
|
+
finally {
|
|
1025
|
+
clear();
|
|
1026
|
+
}
|
|
826
1027
|
}
|
|
827
1028
|
async function saveXiaoEUnifiedLogin(input) {
|
|
828
1029
|
const baseUrl = normalizeUrl(input.baseUrl);
|
|
829
|
-
const { userInfo, setCookie } = await resolveUserInfo(baseUrl, input.eteamsId, input.rawCookie || '', XIAOE_AGENT_TYPE);
|
|
1030
|
+
const { userInfo, setCookie, status } = await resolveUserInfo(baseUrl, input.eteamsId, input.rawCookie || '', XIAOE_AGENT_TYPE);
|
|
830
1031
|
if (!userInfo.userId) {
|
|
831
|
-
|
|
1032
|
+
// 401/403/3xx 是「票不被接受」,用户该去恢复登录;5xx 则是 E10 侧故障,
|
|
1033
|
+
// 报成凭证问题会把人引向无用的重新登录。
|
|
1034
|
+
const statusText = status || 0;
|
|
1035
|
+
throw classifyXiaoEHttpStatus(statusText) === 'http'
|
|
1036
|
+
? tagXiaoEError(`E10 teamsCheck returned HTTP ${statusText}.`, {
|
|
1037
|
+
errorKind: 'http',
|
|
1038
|
+
stage: 'teams_check',
|
|
1039
|
+
status: statusText,
|
|
1040
|
+
})
|
|
1041
|
+
: tagXiaoEError('Could not resolve userId. Check whether XiaoE unified login is valid for this E10 domain.', {
|
|
1042
|
+
errorKind: 'credential',
|
|
1043
|
+
stage: 'teams_check',
|
|
1044
|
+
status: statusText,
|
|
1045
|
+
});
|
|
832
1046
|
}
|
|
833
1047
|
const tenantKey = userInfo.tenantKey || input.tenantKeyFallback || '';
|
|
834
1048
|
const profile = input.profileHint || profileNameFromUrl(baseUrl);
|
|
@@ -850,6 +1064,7 @@ async function saveXiaoEUnifiedLogin(input) {
|
|
|
850
1064
|
baseUrl,
|
|
851
1065
|
passportUrl: baseUrl,
|
|
852
1066
|
cookieSavedAt: Date.now(),
|
|
1067
|
+
...(input.expiryTimes !== undefined ? { expiryTimes: input.expiryTimes } : {}),
|
|
853
1068
|
loginSource: 'xiaoe-unified',
|
|
854
1069
|
credentialSourceStep: input.credentialSource.credentialSourceStep,
|
|
855
1070
|
credentialSourceType: input.credentialSource.credentialSourceType,
|
|
@@ -872,6 +1087,7 @@ async function saveXiaoEUnifiedLogin(input) {
|
|
|
872
1087
|
agentType: XIAOE_AGENT_TYPE,
|
|
873
1088
|
version: '',
|
|
874
1089
|
loginSource: 'xiaoe-unified',
|
|
1090
|
+
...(input.expiryTimes !== undefined ? { expiryTimes: input.expiryTimes } : {}),
|
|
875
1091
|
...input.credentialSource,
|
|
876
1092
|
},
|
|
877
1093
|
};
|
|
@@ -884,19 +1100,32 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
|
|
|
884
1100
|
profileBaseUrl: options.profileBaseUrl,
|
|
885
1101
|
env,
|
|
886
1102
|
});
|
|
887
|
-
|
|
888
|
-
|
|
1103
|
+
// 网页端没有桌面凭据通道:`XIAOE_CLIENT_TYPE=cloud` 时不尝试 tokens.py 与
|
|
1104
|
+
// 本地客户端会话,避免在共享机器上误用他人的客户端登录态。
|
|
1105
|
+
const mayUseTokenScript = options.allowTokenScript === true && !isXiaoECloudRuntime(env);
|
|
1106
|
+
const diagnostics = options.diagnostics;
|
|
1107
|
+
const platformCredentialDetected = hasXiaoEPlatformCredential(env);
|
|
1108
|
+
const secrets = [envString(env, 'XIAOE_USER_TOKEN')];
|
|
1109
|
+
if (diagnostics) {
|
|
1110
|
+
diagnostics.baseUrl = baseUrl;
|
|
1111
|
+
diagnostics.platformCredentialDetected = platformCredentialDetected;
|
|
1112
|
+
diagnostics.clientType = resolveXiaoEClientType(env);
|
|
1113
|
+
}
|
|
1114
|
+
if (!baseUrl && !mayUseTokenScript) {
|
|
1115
|
+
noteXiaoEStep(diagnostics, 'base_url:missing');
|
|
889
1116
|
return null;
|
|
1117
|
+
}
|
|
890
1118
|
let credential = null;
|
|
891
1119
|
// Step 1 — platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN).
|
|
892
1120
|
// Works in sandboxes: no desktop client, no Python, no developer-machine paths.
|
|
893
|
-
if (
|
|
1121
|
+
if (platformCredentialDetected) {
|
|
894
1122
|
options.logger?.info('Trying XiaoE platform credential from XIAOE_USER_TOKEN...');
|
|
895
1123
|
try {
|
|
896
1124
|
credential = await resolveXiaoEPlatformCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
|
|
897
1125
|
}
|
|
898
1126
|
catch (error) {
|
|
899
1127
|
const message = error instanceof Error ? error.message : String(error);
|
|
1128
|
+
noteXiaoEError(diagnostics, error, secrets, 'platform_token');
|
|
900
1129
|
options.logger?.warn('XiaoE platform credential failed. ' + message);
|
|
901
1130
|
}
|
|
902
1131
|
if (!credential) {
|
|
@@ -905,25 +1134,38 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
|
|
|
905
1134
|
: 'XiaoE platform credential did not return usable ETEAMSID; will try xiaoeUserData/tokens.py next.');
|
|
906
1135
|
}
|
|
907
1136
|
}
|
|
1137
|
+
noteXiaoEStep(diagnostics, credential ? 'platform_token:ok' : `platform_token:${platformCredentialDetected ? 'failed' : 'missing'}`);
|
|
908
1138
|
// Step 1b — compatibility path used by the reference login package. The
|
|
909
1139
|
// script is shipped with the CLI npm package (never with a Skill) and its
|
|
910
1140
|
// stdout stays inside this process so credentials are not exposed to agents.
|
|
911
|
-
if (!credential
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1141
|
+
if (!credential) {
|
|
1142
|
+
if (!options.allowBundledEnvScript) {
|
|
1143
|
+
noteXiaoEStep(diagnostics, 'bundled_env_script:disabled');
|
|
1144
|
+
}
|
|
1145
|
+
else if (!platformCredentialDetected) {
|
|
1146
|
+
// 脚本本身也只读 XIAOE_USER_TOKEN/XIAOE_DOMAIN,前置缺失时重跑没有意义。
|
|
1147
|
+
noteXiaoEStep(diagnostics, 'bundled_env_script:missing');
|
|
915
1148
|
}
|
|
916
1149
|
else {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
catch (error) {
|
|
922
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
923
|
-
options.logger?.warn('CLI-bundled xiaoe_env.py failed. ' + message);
|
|
1150
|
+
const scriptPath = xiaoEBundledEnvScriptPath();
|
|
1151
|
+
if (!scriptPath) {
|
|
1152
|
+
noteXiaoEStep(diagnostics, 'bundled_env_script:missing');
|
|
1153
|
+
options.logger?.warn('CLI-bundled xiaoe_env.py was not found; will try desktop XiaoE credential sources next.');
|
|
924
1154
|
}
|
|
925
|
-
|
|
926
|
-
options.logger?.
|
|
1155
|
+
else {
|
|
1156
|
+
options.logger?.info('Trying CLI-bundled xiaoe_env.py compatibility credential...');
|
|
1157
|
+
try {
|
|
1158
|
+
credential = await resolveXiaoEBundledEnvScriptCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
|
|
1159
|
+
}
|
|
1160
|
+
catch (error) {
|
|
1161
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1162
|
+
noteXiaoEError(diagnostics, error, secrets, 'bundled_env_script');
|
|
1163
|
+
options.logger?.warn('CLI-bundled xiaoe_env.py failed. ' + message);
|
|
1164
|
+
}
|
|
1165
|
+
if (!credential) {
|
|
1166
|
+
options.logger?.warn('CLI-bundled xiaoe_env.py did not return usable ETEAMSID; will try xiaoeUserData/tokens.py next.');
|
|
1167
|
+
}
|
|
1168
|
+
noteXiaoEStep(diagnostics, `bundled_env_script:${credential ? 'ok' : 'failed'}`);
|
|
927
1169
|
}
|
|
928
1170
|
}
|
|
929
1171
|
}
|
|
@@ -931,6 +1173,7 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
|
|
|
931
1173
|
if (!credential && mayUseTokenScript) {
|
|
932
1174
|
const tokenScriptPath = xiaoETokenScriptPath(env);
|
|
933
1175
|
if (!tokenScriptPath) {
|
|
1176
|
+
noteXiaoEStep(diagnostics, 'token_script:missing');
|
|
934
1177
|
options.logger?.warn('XiaoE token script not found. Expected xiaoeUserData/tokens.py; will try the local XiaoE client session next.');
|
|
935
1178
|
}
|
|
936
1179
|
else {
|
|
@@ -940,11 +1183,13 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
|
|
|
940
1183
|
}
|
|
941
1184
|
catch (error) {
|
|
942
1185
|
const message = error instanceof Error ? error.message : String(error);
|
|
1186
|
+
noteXiaoEError(diagnostics, error, secrets, 'token_script');
|
|
943
1187
|
options.logger?.warn('XiaoE token script failed. ' + message);
|
|
944
1188
|
}
|
|
945
1189
|
if (!credential) {
|
|
946
1190
|
options.logger?.warn('XiaoE token script did not return usable ETEAMSID; will try the local XiaoE client session next.');
|
|
947
1191
|
}
|
|
1192
|
+
noteXiaoEStep(diagnostics, `token_script:${credential ? 'ok' : 'failed'}`);
|
|
948
1193
|
}
|
|
949
1194
|
// Step 3 — built-in Node equivalent of tokens.py: reads the local XiaoE
|
|
950
1195
|
// client session and exchanges it for an eteamId, so Python availability
|
|
@@ -956,30 +1201,52 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
|
|
|
956
1201
|
}
|
|
957
1202
|
catch (error) {
|
|
958
1203
|
const message = error instanceof Error ? error.message : String(error);
|
|
1204
|
+
noteXiaoEError(diagnostics, error, secrets, 'client_session');
|
|
959
1205
|
options.logger?.warn('Built-in XiaoE client credential failed. ' + message);
|
|
960
1206
|
}
|
|
961
1207
|
if (!credential) {
|
|
962
1208
|
options.logger?.warn('Built-in XiaoE client credential did not return usable ETEAMSID; will try WEAVER_XIAOE_* credentials next.');
|
|
963
1209
|
}
|
|
1210
|
+
noteXiaoEStep(diagnostics, `client_session:${credential ? 'ok' : 'failed'}`);
|
|
964
1211
|
}
|
|
1212
|
+
else if (!credential) {
|
|
1213
|
+
noteXiaoEStep(diagnostics, 'client_session:missing');
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
else if (!credential) {
|
|
1217
|
+
noteXiaoEStep(diagnostics, 'token_script:disabled');
|
|
1218
|
+
noteXiaoEStep(diagnostics, 'client_session:disabled');
|
|
965
1219
|
}
|
|
966
1220
|
if (!credential && baseUrl)
|
|
967
1221
|
credential = resolveXiaoEUnifiedLoginCredential(baseUrl, env);
|
|
968
|
-
if (!credential)
|
|
1222
|
+
if (!credential) {
|
|
1223
|
+
noteXiaoEStep(diagnostics, 'environment_map:empty');
|
|
969
1224
|
return null;
|
|
1225
|
+
}
|
|
1226
|
+
noteXiaoEStep(diagnostics, `credential_source:${credential.source}`);
|
|
970
1227
|
options.logger?.info('Detected XiaoE unified login for this E10 domain. Trying XiaoE auth...');
|
|
971
1228
|
try {
|
|
972
|
-
|
|
1229
|
+
const login = await saveXiaoEUnifiedLogin({
|
|
973
1230
|
baseUrl: credential.baseUrl || baseUrl || DEFAULT_E10_BASE_URL,
|
|
974
1231
|
eteamsId: credential.eteamsId,
|
|
975
1232
|
rawCookie: credential.rawCookie,
|
|
1233
|
+
expiryTimes: credential.expiryTimes,
|
|
976
1234
|
tenantKeyFallback: options.tenantKeyFallback,
|
|
977
1235
|
profileHint: options.profileHint,
|
|
978
1236
|
credentialSource: describeXiaoECredentialSource(credential.source),
|
|
979
1237
|
});
|
|
1238
|
+
noteXiaoEStep(diagnostics, 'e10_session:ok');
|
|
1239
|
+
if (diagnostics) {
|
|
1240
|
+
// 成功换到登录态:前面压着的失败不该再让调用方误判整次恢复失败。
|
|
1241
|
+
delete diagnostics.lastError;
|
|
1242
|
+
delete diagnostics.failures;
|
|
1243
|
+
}
|
|
1244
|
+
return login;
|
|
980
1245
|
}
|
|
981
1246
|
catch (error) {
|
|
982
1247
|
const message = error instanceof Error ? error.message : String(error);
|
|
1248
|
+
noteXiaoEError(diagnostics, error, secrets, 'e10_session');
|
|
1249
|
+
noteXiaoEStep(diagnostics, 'e10_session:failed');
|
|
983
1250
|
options.logger?.warn('XiaoE unified login failed. ' + message);
|
|
984
1251
|
return null;
|
|
985
1252
|
}
|