weaver-work-cli 0.1.38 → 0.1.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 +7 -3
- package/dist/chunks/{archive-PDV724YK.js → archive-JHTONRST.js} +556 -66
- package/dist/chunks/{archive-oa-4XE4MNXQ.js → archive-oa-FCHLCPHC.js} +2 -2
- package/dist/chunks/{asset-PDL2QE2N.js → asset-RLIHC7DB.js} +2 -2
- package/dist/chunks/asset-idle-KVOFTLCP.js +670 -0
- package/dist/chunks/{attachment-content-MDDGOE5E.js → attachment-content-RZYDB3MB.js} +3 -3
- package/dist/chunks/{blog-UYR3EUXW.js → blog-42OZTJNP.js} +2 -2
- package/dist/chunks/{caizhilian-LG2X5NMF.js → caizhilian-KRK547K5.js} +2 -2
- package/dist/chunks/{calendar-FG4BAZHE.js → calendar-RTWAMKHD.js} +2 -2
- package/dist/chunks/{chunk-J7USS3ED.js → chunk-4CTESSKI.js} +4 -4
- package/dist/chunks/{chunk-OJQUJZQF.js → chunk-4TEDNCON.js} +45 -5
- package/dist/chunks/chunk-57IECMWX.js +487 -0
- package/dist/chunks/{chunk-ELYDWBZJ.js → chunk-7BRU4EFH.js} +13 -7
- package/dist/chunks/{chunk-6LTFIFLM.js → chunk-7E6MUOEK.js} +2 -2
- package/dist/chunks/{chunk-KKRL3REV.js → chunk-7U577Q7U.js} +2 -2
- package/dist/chunks/{chunk-UJJD4QWF.js → chunk-CAQNBBQA.js} +2 -2
- package/dist/chunks/{chunk-CORFRD7Q.js → chunk-CXRQWOZ5.js} +2 -2
- package/dist/chunks/{chunk-GGJFMNJM.js → chunk-FLS4DPGP.js} +110 -16
- package/dist/chunks/{chunk-JSMFPO43.js → chunk-I566ZYNF.js} +1 -1
- package/dist/chunks/{chunk-6FTTSA45.js → chunk-IZSSVYAG.js} +2 -2
- package/dist/chunks/{chunk-XJY7G4DD.js → chunk-JWRZM2H2.js} +3 -3
- package/dist/chunks/{chunk-NI6GIPIM.js → chunk-LFMSDBZN.js} +529 -395
- package/dist/chunks/{chunk-VRYHGG6O.js → chunk-LN447PY2.js} +92 -51
- package/dist/chunks/{chunk-NR6RKXPA.js → chunk-NETCCARD.js} +4 -2
- package/dist/chunks/{chunk-Z32KWY6G.js → chunk-NJKREDA3.js} +1 -1
- package/dist/chunks/{chunk-FUWVHMVV.js → chunk-SPRHHSVC.js} +2 -2
- package/dist/chunks/{chunk-IZUDA353.js → chunk-T6EGVBKU.js} +1 -1
- package/dist/chunks/{chunk-DMV2TOMH.js → chunk-VNBU55SL.js} +428 -145
- package/dist/chunks/{chunk-UZZBA6PV.js → chunk-WFFNM65B.js} +47 -6
- package/dist/chunks/{chunk-EG6ZU444.js → chunk-ZUX3P6BQ.js} +10 -5
- package/dist/chunks/{conflict-BQZEQ4SP.js → conflict-VR55AOOO.js} +2 -2
- package/dist/chunks/{contract-XIRAFTEV.js → contract-HI4FNPYE.js} +2 -2
- package/dist/chunks/{cowork-BLG62BL2.js → cowork-HJLI353D.js} +12 -5
- package/dist/chunks/{ebuilder-form-NUKG5QW4.js → ebuilder-form-HTGI63UU.js} +3 -3
- package/dist/chunks/{ehr-2STW5ILH.js → ehr-GYXIYH3A.js} +2 -2
- package/dist/chunks/env-DW3G4HUO.js +19 -0
- package/dist/chunks/{esb-UUDQUNKY.js → esb-I5INCEFE.js} +4 -4
- package/dist/chunks/factory-TKXGXS6L.js +30 -0
- package/dist/chunks/{fna-6RCY4NU5.js → fna-K2MHTQ24.js} +2 -2
- package/dist/chunks/group-chat-VYBNMJLG.js +18 -0
- package/dist/chunks/{host-FWDOSRH5.js → host-256GVCAH.js} +3 -3
- package/dist/chunks/{host-EJZFROU3.js → host-D5SERMAJ.js} +3 -3
- package/dist/chunks/{host-RHM3NPXV.js → host-JM4YVHFM.js} +3 -3
- package/dist/chunks/{host-7RXEDMAH.js → host-L3VLITMT.js} +3 -3
- package/dist/chunks/{host-UFF33TWJ.js → host-ZUQTJYYK.js} +3 -3
- package/dist/chunks/{hrm-PKJ6ELSH.js → hrm-VLRBKTZB.js} +3 -3
- package/dist/chunks/{im-YJAXS7CO.js → im-QK3VTLXK.js} +8 -8
- package/dist/chunks/{invoice-HWVSXGX6.js → invoice-T5JTA32H.js} +3 -3
- package/dist/chunks/{jiuchuanhui-L7VKILZB.js → jiuchuanhui-P2FNEQVZ.js} +2 -2
- package/dist/chunks/{lazy-dispatch-NMYEIHRH.js → lazy-dispatch-XRRI46M6.js} +5 -5
- package/dist/chunks/{mail-B42RRU7G.js → mail-3Y7MJ6PS.js} +9 -4
- package/dist/chunks/{meeting-CFYVBJSS.js → meeting-M7LTXGJE.js} +11 -10
- package/dist/chunks/{okr-GCLF6V7W.js → okr-OBC6S67L.js} +2 -2
- package/dist/chunks/{plan-ZVL7GBRI.js → plan-5H6I53SK.js} +2 -2
- package/dist/chunks/{project-4YWAAE2K.js → project-L75QZV4S.js} +2 -2
- package/dist/chunks/{registry-EP4VTHZS.js → registry-KMHCPAAD.js} +9 -8
- package/dist/chunks/{registry-OZSMDVPP.js → registry-QGHFNUWS.js} +2 -2
- package/dist/chunks/{registry-FRJ6H7BT.js → registry-RXPN6BBT.js} +4 -4
- package/dist/chunks/{registry-SG2PGVCF.js → registry-UGRBCBHO.js} +3 -3
- package/dist/chunks/{room-recommend-R3JVUUAC.js → room-recommend-A3UDC3CA.js} +4 -4
- package/dist/chunks/{schedule-NIJBTYDO.js → schedule-5GCACCK7.js} +4 -4
- package/dist/chunks/{workflow-BB24DFBY.js → workflow-M5ZJ7DWF.js} +11 -8
- package/dist/core/output.js +16 -6
- package/dist/index.js +33 -28
- package/dist/internal/e10/auth/commands.js +109 -56
- package/dist/internal/e10/auth/messages.js +1 -0
- package/dist/internal/e10/auth/session.js +11 -1
- package/dist/internal/e10/auth/xiaoe.js +29 -4
- package/dist/internal/e10/auth-recovery.js +13 -6
- package/dist/internal/e10/client.js +13 -0
- package/dist/internal/e10/context.js +24 -3
- package/dist/internal/e10/request-runtime.js +12 -1
- package/dist/shortcuts/archive/host.js +2 -0
- package/dist/shortcuts/archive/manifest.js +1 -1
- package/dist/shortcuts/archive/operations/borrow-list.js +13 -1
- package/dist/shortcuts/archive/operations/flow-url.js +1 -1
- package/dist/shortcuts/archive/operations/prelib.js +472 -0
- package/dist/shortcuts/archive/operations/registry.js +2 -0
- package/dist/shortcuts/archive/operations/upload.js +1 -1
- package/dist/shortcuts/asset-idle/errors.js +44 -0
- package/dist/shortcuts/asset-idle/host.js +161 -0
- package/dist/shortcuts/asset-idle/index.js +114 -0
- package/dist/shortcuts/asset-idle/manifest.js +159 -0
- package/dist/shortcuts/asset-idle/operations/idle.js +47 -0
- package/dist/shortcuts/asset-idle/operations/registry.js +33 -0
- package/dist/shortcuts/asset-idle/operations/shared.js +142 -0
- package/dist/shortcuts/asset-idle/operations/types.js +9 -0
- package/dist/shortcuts/asset-idle/operations.js +15 -0
- package/dist/shortcuts/cowork/operations/item-write.js +10 -2
- 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 +5 -1
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +381 -104
- package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
- package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
- package/dist/shortcuts/meeting/operations/eb/group-chat.js +583 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +19 -3
- 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/workflow/host.js +3 -0
- package/dist/shortcuts/workflow/index.js +1 -1
- package/dist/shortcuts/workflow/operations/form.js +65 -171
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +129 -27
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupStore.js +6 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +60 -117
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +44 -7
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +37 -29
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +48 -8
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +1 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +22 -18
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +17 -9
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +34 -10
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +5 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +9 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +122 -20
- package/docs/_catalog.md +3 -0
- package/docs/asset-idle.md +45 -0
- package/docs/e10-auth.md +16 -1
- package/docs/todo-smart-convert/subgroup-rules.md +1 -1
- package/package.json +1 -1
- package/scripts/mcp-meeting.mjs +234 -0
- package/scripts/package-skill.mjs +2 -0
- package/skill-template/business-info.json +14 -1
- package/skill-template/domains/asset-idle.md +13 -0
- package/skill-template/domains/wenshuding.md +3 -0
- package/skills/weaver-e10-archive-oa/SKILL.md +1 -0
- package/skills/weaver-e10-archive-oa/references/my-borrow.md +49 -0
- package/skills/weaver-e10-asset-idle/SKILL.md +73 -0
- package/skills/weaver-e10-asset-idle/references/idle-list.md +62 -0
- package/skills/weaver-e10-asset-idle/references/idle-stats-and-charts.md +65 -0
- package/skills/weaver-e10-asset-idle/references/source-manifest.json +193 -0
- package/skills/weaver-e10-asset-idle/references/widget-style.md +205 -0
- package/skills/weaver-e10-caizhilian/SKILL.md +29 -11
- package/skills/weaver-e10-caizhilian/references/auth-and-install.md +12 -0
- package/skills/weaver-e10-caizhilian/references/files.md +1 -1
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +1 -1
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +45 -45
- package/skills/weaver-e10-cowork/SKILL.md +2 -0
- package/skills/weaver-e10-cowork/references/cowork-write.md +1 -1
- package/skills/weaver-e10-jucailin/SKILL.md +2 -0
- package/skills/weaver-e10-jucailin/references/recruit-read.md +2 -0
- package/skills/weaver-e10-jucailin/references/recruit-write.md +1 -1
- package/skills/weaver-e10-mail/SKILL.md +3 -0
- package/skills/weaver-e10-mail/references/mail-send.md +2 -0
- package/skills/weaver-e10-meeting/SKILL.md +88 -25
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-shared/SKILL.md +5 -5
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +3 -3
- package/skills/weaver-e10-wenshuding/SKILL.md +29 -6
- package/skills/weaver-e10-wenshuding/references/archive-borrow-list.md +14 -0
- package/skills/weaver-e10-wenshuding/references/archive-download.md +2 -0
- package/skills/weaver-e10-wenshuding/references/archive-flow.md +1 -1
- package/skills/weaver-e10-wenshuding/references/archive-prelib-arrange.md +76 -0
- package/skills/weaver-e10-wenshuding/references/archive-prelib-list.md +107 -0
- package/skills/weaver-e10-wenshuding/references/archive-prelib-open.md +59 -0
- package/skills/weaver-e10-wenshuding/references/archive-upload.md +4 -1
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +108 -65
- package/skills/weaver-e10-workflow/SKILL.md +9 -2
- package/skills/weaver-e10-workflow/aigroup/default.json +166 -19
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +72 -34
- package/skills/weaver-e10-workflow/tools/todo-serve.cjs +113 -25
- package/skills/weaver-e10-workflow/tools/todo-summary.cjs +350 -50
- package/dist/chunks/{chunk-2LV74IWP.js → chunk-E7OK5WD5.js} +5 -5
|
@@ -17,6 +17,7 @@ const AGENT_RETURN_URL_ENV_KEY = 'WEAVER_AGENT_RETURN_URL';
|
|
|
17
17
|
const AGENT_RETURN_PATH = '/agent-return';
|
|
18
18
|
const AGENT_RETURN_TOKEN_HEADER = 'x-weaver-agent-return-token';
|
|
19
19
|
const AGENT_RETURN_TIMEOUT_MS = 900_000;
|
|
20
|
+
const OAUTH_CALLBACK_PROTOCOL_MISMATCH_GRACE_MS = 30_000;
|
|
20
21
|
const UNSAFE_AGENT_RETURN_PROTOCOLS = new Set([
|
|
21
22
|
'about:',
|
|
22
23
|
'blob:',
|
|
@@ -191,6 +192,7 @@ function formatAuthStatus(status) {
|
|
|
191
192
|
...('credentialSourceStep' in status && status.credentialSourceStep ? ['credentialSourceStep: ' + status.credentialSourceStep] : []),
|
|
192
193
|
...('credentialSourceType' in status && status.credentialSourceType ? ['credentialSourceType: ' + status.credentialSourceType] : []),
|
|
193
194
|
...('credentialSourceLabel' in status && status.credentialSourceLabel ? ['credentialSourceLabel: ' + status.credentialSourceLabel] : []),
|
|
195
|
+
...('expiryTimes' in status && status.expiryTimes ? ['expiryTimes: ' + status.expiryTimes] : []),
|
|
194
196
|
];
|
|
195
197
|
if ('valid' in status) {
|
|
196
198
|
lines.push('valid: ' + String(status.valid));
|
|
@@ -210,6 +212,7 @@ function formatLoginResult(result) {
|
|
|
210
212
|
...(result.credentialSourceStep ? ['credentialSourceStep: ' + result.credentialSourceStep] : []),
|
|
211
213
|
...(result.credentialSourceType ? ['credentialSourceType: ' + result.credentialSourceType] : []),
|
|
212
214
|
...(result.credentialSourceLabel ? ['credentialSourceLabel: ' + result.credentialSourceLabel] : []),
|
|
215
|
+
...(result.expiryTimes ? ['expiryTimes: ' + result.expiryTimes] : []),
|
|
213
216
|
].join('\n');
|
|
214
217
|
}
|
|
215
218
|
async function clearInvoiceTokenCache(profile, authData) {
|
|
@@ -300,6 +303,24 @@ export function buildE10AuthorizeUrl(baseUrl, redirectUri, agentType) {
|
|
|
300
303
|
});
|
|
301
304
|
return `${baseUrl}/papi/sso/oauth2.0/authorize?${authorizeParams.toString()}`;
|
|
302
305
|
}
|
|
306
|
+
/** Node's HTTP parser exposes the first unread bytes when TLS is sent to an HTTP listener. */
|
|
307
|
+
export function isTlsClientHello(error) {
|
|
308
|
+
if (!error || typeof error !== 'object')
|
|
309
|
+
return false;
|
|
310
|
+
const rawPacket = error.rawPacket;
|
|
311
|
+
return Buffer.isBuffer(rawPacket)
|
|
312
|
+
&& rawPacket.length >= 3
|
|
313
|
+
&& rawPacket[0] === 0x16
|
|
314
|
+
&& rawPacket[1] === 0x03;
|
|
315
|
+
}
|
|
316
|
+
export function createOAuthCallbackProtocolMismatchError(port) {
|
|
317
|
+
return Object.assign(new CliError(`OAuth callback protocol mismatch: the browser connected to https://127.0.0.1:${port}, but the CLI callback listener uses HTTP. Retry the login and, on the callback error page, change only the URL scheme to http:// before reloading; keep the path and query unchanged. The E10 authorization service should preserve HTTP loopback redirect URIs.`, ExitCode.AuthError), {
|
|
318
|
+
type: 'authentication',
|
|
319
|
+
subtype: 'oauth_callback_protocol_mismatch',
|
|
320
|
+
retryable: false,
|
|
321
|
+
stage: 'oauth_callback',
|
|
322
|
+
});
|
|
323
|
+
}
|
|
303
324
|
async function readJsonObject(resp) {
|
|
304
325
|
const data = await resp.json().catch(() => ({}));
|
|
305
326
|
return data && typeof data === 'object' && !Array.isArray(data) ? data : {};
|
|
@@ -488,7 +509,7 @@ function xiaoEUnifiedLoginUnavailableMessage(baseUrl, attempted) {
|
|
|
488
509
|
'CLI 会优先使用 XiaoE 客户端内置 Python 执行 tokens.py;自动发现不可用时,可用 WEAVER_XIAOE_TOKEN_SCRIPT_PYTHON 指定兼容解释器。',
|
|
489
510
|
].join(' ');
|
|
490
511
|
}
|
|
491
|
-
async function
|
|
512
|
+
async function runXiaoELogin(ctx, options) {
|
|
492
513
|
const commandOpts = asOptions(options);
|
|
493
514
|
const globalOptions = ctx.getGlobalOptions();
|
|
494
515
|
const profileFromGlobal = globalOptions.profile;
|
|
@@ -499,59 +520,49 @@ async function runBrowserLogin(ctx, options, behavior = {}) {
|
|
|
499
520
|
profileBaseUrl: cfg.baseUrl,
|
|
500
521
|
env: ctx.env,
|
|
501
522
|
});
|
|
502
|
-
const xiaoESources =
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
523
|
+
const xiaoESources = inspectXiaoEUnifiedLoginSources({
|
|
524
|
+
env: ctx.env,
|
|
525
|
+
baseUrl: preferredXiaoEBaseUrl,
|
|
526
|
+
profileBaseUrl: cfg.baseUrl,
|
|
527
|
+
allowTokenScript: true,
|
|
528
|
+
});
|
|
529
|
+
const useXiaoELogin = xiaoESources.any;
|
|
530
|
+
const baseUrl = preferredXiaoEBaseUrl;
|
|
531
|
+
const tenantKey = optionString(commandOpts, 'tenantKey');
|
|
532
|
+
ctx.logger.info([
|
|
533
|
+
'XiaoE unified login preflight:',
|
|
534
|
+
'authMode=xiaoe',
|
|
535
|
+
`credentialSourceDetected=${String(xiaoESources.any)}`,
|
|
536
|
+
`attempted=${String(useXiaoELogin)}`,
|
|
537
|
+
`platformCredentialDetected=${String(xiaoESources.platformCredential)}`,
|
|
538
|
+
`tokenScriptDetected=${String(xiaoESources.tokenScript)}`,
|
|
539
|
+
`clientSettingsDetected=${String(xiaoESources.clientCredential)}`,
|
|
540
|
+
`environmentCredentialDetected=${String(xiaoESources.environmentCredential)}`,
|
|
541
|
+
`baseUrl=${baseUrl || '(unresolved)'}`,
|
|
542
|
+
].join(' '));
|
|
543
|
+
if (useXiaoELogin) {
|
|
544
|
+
const xiaoEResult = await tryXiaoEUnifiedLogin(ctx, baseUrl, tenantKey, profileFromGlobal, true);
|
|
545
|
+
if (xiaoEResult)
|
|
546
|
+
return xiaoEResult;
|
|
547
|
+
}
|
|
548
|
+
ctx.logger.warn(xiaoEUnifiedLoginUnavailableMessage(baseUrl, useXiaoELogin));
|
|
549
|
+
throw createXiaoEUnifiedLoginUnavailableError(baseUrl);
|
|
550
|
+
}
|
|
551
|
+
async function runBrowserLogin(ctx, options) {
|
|
552
|
+
const commandOpts = asOptions(options);
|
|
553
|
+
const globalOptions = ctx.getGlobalOptions();
|
|
554
|
+
const profileFromGlobal = globalOptions.profile;
|
|
555
|
+
const cfg = readProfileConfig(profileFromGlobal || getActiveProfile());
|
|
556
|
+
const explicitBaseUrl = optionString(commandOpts, 'baseUrl') || globalOptions.baseUrl || '';
|
|
557
|
+
const baseUrl = normalizeUrl(explicitBaseUrl || cfg.baseUrl || '');
|
|
520
558
|
const tenantKey = optionString(commandOpts, 'tenantKey');
|
|
521
559
|
const portText = optionString(commandOpts, 'port') || '19800';
|
|
522
560
|
const port = Number.parseInt(portText, 10);
|
|
523
561
|
const explicitAgentType = optionString(commandOpts, 'agentType', 'agent_type');
|
|
524
|
-
const agentType =
|
|
525
|
-
? optionString(commandOpts, 'agentType', 'agent_type') || XIAOE_AGENT_TYPE
|
|
526
|
-
: resolveAgentType(commandOpts, ctx.env);
|
|
562
|
+
const agentType = resolveAgentType(commandOpts, ctx.env);
|
|
527
563
|
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
528
564
|
throw new CliError('Invalid --port value', ExitCode.UsageError);
|
|
529
565
|
}
|
|
530
|
-
if (behavior.tryXiaoEUnifiedLogin) {
|
|
531
|
-
ctx.logger.info([
|
|
532
|
-
'XiaoE unified login preflight:',
|
|
533
|
-
'authMode=xiaoe',
|
|
534
|
-
`credentialSourceDetected=${String(xiaoESources.any)}`,
|
|
535
|
-
`attempted=${String(useXiaoELogin)}`,
|
|
536
|
-
`platformCredentialDetected=${String(xiaoESources.platformCredential)}`,
|
|
537
|
-
`tokenScriptDetected=${String(xiaoESources.tokenScript)}`,
|
|
538
|
-
`clientSettingsDetected=${String(xiaoESources.clientCredential)}`,
|
|
539
|
-
`environmentCredentialDetected=${String(xiaoESources.environmentCredential)}`,
|
|
540
|
-
`baseUrl=${baseUrl || '(unresolved)'}`,
|
|
541
|
-
].join(' '));
|
|
542
|
-
}
|
|
543
|
-
if (useXiaoELogin) {
|
|
544
|
-
const xiaoEResult = await tryXiaoEUnifiedLogin(ctx, baseUrl, tenantKey, profileFromGlobal, behavior.allowXiaoETokenScript === true);
|
|
545
|
-
if (xiaoEResult)
|
|
546
|
-
return xiaoEResult;
|
|
547
|
-
}
|
|
548
|
-
const willOpenBrowser = behavior.browserFallback !== 'never';
|
|
549
|
-
if (behavior.tryXiaoEUnifiedLogin && behavior.explainXiaoEFallback) {
|
|
550
|
-
ctx.logger.warn(xiaoEUnifiedLoginUnavailableMessage(baseUrl, useXiaoELogin));
|
|
551
|
-
}
|
|
552
|
-
if (!willOpenBrowser) {
|
|
553
|
-
throw createXiaoEUnifiedLoginUnavailableError(baseUrl);
|
|
554
|
-
}
|
|
555
566
|
if (!baseUrl)
|
|
556
567
|
throw new CliError('Missing --base-url', ExitCode.UsageError);
|
|
557
568
|
const agentReturnTarget = resolveAgentReturnTarget(commandOpts, ctx.env, agentType);
|
|
@@ -564,6 +575,7 @@ async function runBrowserLogin(ctx, options, behavior = {}) {
|
|
|
564
575
|
let settled = false;
|
|
565
576
|
let timer = null;
|
|
566
577
|
let pendingLoginResult = null;
|
|
578
|
+
let tlsCallbackDetected = false;
|
|
567
579
|
const finish = (error, result) => {
|
|
568
580
|
if (settled)
|
|
569
581
|
return;
|
|
@@ -647,6 +659,13 @@ async function runBrowserLogin(ctx, options, behavior = {}) {
|
|
|
647
659
|
finish(new CliError('No authorization code received.', ExitCode.AuthError));
|
|
648
660
|
return;
|
|
649
661
|
}
|
|
662
|
+
// The user followed the recovery instruction and reached the HTTP
|
|
663
|
+
// callback. Do not let the protocol-mismatch grace timer race the token
|
|
664
|
+
// exchange that is now legitimately in progress.
|
|
665
|
+
if (tlsCallbackDetected && timer) {
|
|
666
|
+
clearTimeout(timer);
|
|
667
|
+
timer = null;
|
|
668
|
+
}
|
|
650
669
|
try {
|
|
651
670
|
const result = await oidcExchange(code, redirectUri, baseUrl, tenantKey, redirectAgentType, profileFromGlobal);
|
|
652
671
|
const responseHeaders = {
|
|
@@ -685,6 +704,26 @@ async function runBrowserLogin(ctx, options, behavior = {}) {
|
|
|
685
704
|
: error.message;
|
|
686
705
|
finish(new CliError(message, ExitCode.UsageError));
|
|
687
706
|
});
|
|
707
|
+
server.on('clientError', (error, socket) => {
|
|
708
|
+
if (isTlsClientHello(error)) {
|
|
709
|
+
if (!tlsCallbackDetected) {
|
|
710
|
+
tlsCallbackDetected = true;
|
|
711
|
+
ctx.logger.warn([
|
|
712
|
+
`OAuth callback protocol mismatch: the browser used HTTPS for 127.0.0.1:${port}, while the CLI listener is HTTP.`,
|
|
713
|
+
'在 30 秒内把浏览器当前地址开头明确改为 http://,保留后面的完整路径和参数,然后回车重试。',
|
|
714
|
+
'不要把地址中的授权码复制到聊天或日志。',
|
|
715
|
+
].join(' '));
|
|
716
|
+
if (timer)
|
|
717
|
+
clearTimeout(timer);
|
|
718
|
+
timer = setTimeout(() => {
|
|
719
|
+
finish(createOAuthCallbackProtocolMismatchError(port));
|
|
720
|
+
}, OAUTH_CALLBACK_PROTOCOL_MISMATCH_GRACE_MS);
|
|
721
|
+
}
|
|
722
|
+
socket.destroy();
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
socket.destroy();
|
|
726
|
+
});
|
|
688
727
|
server.listen(port, '127.0.0.1', () => {
|
|
689
728
|
ctx.logger.info('Local callback server: ' + redirectUri);
|
|
690
729
|
openUrlInBrowser(loginUrl, ctx);
|
|
@@ -728,7 +767,7 @@ function formatWhoami(title, data) {
|
|
|
728
767
|
'User-Agent: ' + (data.userAgent || ''),
|
|
729
768
|
].join('\n');
|
|
730
769
|
}
|
|
731
|
-
function registerBrowserLoginCommand(auth, action, name, description
|
|
770
|
+
function registerBrowserLoginCommand(auth, action, name, description) {
|
|
732
771
|
const command = auth.command(name)
|
|
733
772
|
.description(description)
|
|
734
773
|
.option('--base-url <url>', 'E10 base URL')
|
|
@@ -738,7 +777,23 @@ function registerBrowserLoginCommand(auth, action, name, description, behavior =
|
|
|
738
777
|
.option('--agent-type <value>', 'Alias of --agent_type')
|
|
739
778
|
.option('--agent-return-url <url>', 'Desktop application URL linked by the login success page');
|
|
740
779
|
command.action(action(async (ctx, options) => {
|
|
741
|
-
const result = await runBrowserLogin(ctx, options
|
|
780
|
+
const result = await runBrowserLogin(ctx, options);
|
|
781
|
+
ctx.output.write(result, { text: formatLoginResult(result) });
|
|
782
|
+
}));
|
|
783
|
+
}
|
|
784
|
+
function registerXiaoELoginCommand(auth, action) {
|
|
785
|
+
auth.command('xiaoe')
|
|
786
|
+
.description('Use XiaoE unified login without browser OAuth fallback')
|
|
787
|
+
.option('--base-url <url>', 'E10 base URL')
|
|
788
|
+
.option('--tenant-key <key>', 'Tenant key fallback after login')
|
|
789
|
+
// Keep previously accepted browser flags as no-ops so existing wrappers do
|
|
790
|
+
// not break. The dedicated handler never parses them or opens a callback.
|
|
791
|
+
.option('--port <n>', 'Deprecated compatibility option; ignored by XiaoE unified login', '19800')
|
|
792
|
+
.option('--agent_type <value>', 'Deprecated compatibility option; ignored by XiaoE unified login')
|
|
793
|
+
.option('--agent-type <value>', 'Alias of --agent_type')
|
|
794
|
+
.option('--agent-return-url <url>', 'Deprecated compatibility option; ignored by XiaoE unified login')
|
|
795
|
+
.action(action(async (ctx, options) => {
|
|
796
|
+
const result = await runXiaoELogin(ctx, options);
|
|
742
797
|
ctx.output.write(result, { text: formatLoginResult(result) });
|
|
743
798
|
}));
|
|
744
799
|
}
|
|
@@ -758,7 +813,9 @@ export function registerE10AuthCommands(parent, action) {
|
|
|
758
813
|
const commandOpts = asOptions(options);
|
|
759
814
|
const authFile = findAuthFile(ctx.cwd, opts);
|
|
760
815
|
const session = authFile ? E10AuthSession.fromFile(authFile, opts.baseUrl, opts.passportUrl) : null;
|
|
761
|
-
const valid = session && commandOpts.check !== false
|
|
816
|
+
const valid = session && commandOpts.check !== false
|
|
817
|
+
? session.isExpired() ? false : await session.checkValid()
|
|
818
|
+
: undefined;
|
|
762
819
|
const status = session
|
|
763
820
|
? {
|
|
764
821
|
loggedIn: true,
|
|
@@ -779,6 +836,7 @@ export function registerE10AuthCommands(parent, action) {
|
|
|
779
836
|
...(session.credentialSourceStep ? { credentialSourceStep: session.credentialSourceStep } : {}),
|
|
780
837
|
...(session.credentialSourceType ? { credentialSourceType: session.credentialSourceType } : {}),
|
|
781
838
|
...(session.credentialSourceLabel ? { credentialSourceLabel: session.credentialSourceLabel } : {}),
|
|
839
|
+
...(session.expiryTimes ? { expiryTimes: session.expiryTimes } : {}),
|
|
782
840
|
valid,
|
|
783
841
|
}
|
|
784
842
|
: {
|
|
@@ -823,12 +881,7 @@ export function registerE10AuthCommands(parent, action) {
|
|
|
823
881
|
}));
|
|
824
882
|
registerBrowserLoginCommand(auth, action, 'login', 'Open browser OAuth2.0 login and save E10 auth');
|
|
825
883
|
registerBrowserLoginCommand(auth, action, 'oidc', 'Alias of auth login for OAuth2.0 browser login');
|
|
826
|
-
|
|
827
|
-
tryXiaoEUnifiedLogin: true,
|
|
828
|
-
allowXiaoETokenScript: true,
|
|
829
|
-
explainXiaoEFallback: true,
|
|
830
|
-
browserFallback: 'never',
|
|
831
|
-
});
|
|
884
|
+
registerXiaoELoginCommand(auth, action);
|
|
832
885
|
auth.command('logout')
|
|
833
886
|
.alias('clear')
|
|
834
887
|
.description('Clear saved E10 auth for the current or selected profile')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export const E10_REAUTH_GUIDANCE = '请结合当前宿主环境选择合适的登录方式,恢复登录后重试原请求。';
|
|
2
2
|
export const E10_AUTH_REQUIRED_MESSAGE = `E10 登录态不可用。${E10_REAUTH_GUIDANCE}`;
|
|
3
|
+
export const E10_XIAOE_RECOVERY_FAILED_MESSAGE = 'E10 登录态不可用。已尝试小 e 自动重新获取登录信息,但未取得可用登录态。请在小 e 中恢复登录后重试。';
|
|
3
4
|
export const E10_SESSION_EXPIRED_MESSAGE = `E10 登录态已失效,本次操作已停止。${E10_REAUTH_GUIDANCE}`;
|
|
@@ -337,6 +337,15 @@ export class E10AuthSession {
|
|
|
337
337
|
get credentialSourceLabel() {
|
|
338
338
|
return this.data.credentialSourceLabel || '';
|
|
339
339
|
}
|
|
340
|
+
get expiryTimes() {
|
|
341
|
+
const value = Number(this.data.expiryTimes);
|
|
342
|
+
return Number.isFinite(value) && value > 0 ? value : undefined;
|
|
343
|
+
}
|
|
344
|
+
/** XiaoE 换取的 ETEAMSID 是否已经超过其明确的失效时间。 */
|
|
345
|
+
isExpired(now = Date.now()) {
|
|
346
|
+
const expiryTimes = this.expiryTimes;
|
|
347
|
+
return expiryTimes !== undefined && now >= expiryTimes;
|
|
348
|
+
}
|
|
340
349
|
getBaseUrl() {
|
|
341
350
|
return (this.data.baseUrl || '').replace(/\/$/, '');
|
|
342
351
|
}
|
|
@@ -389,6 +398,7 @@ export class E10AuthSession {
|
|
|
389
398
|
stTicket: this.data.stTicket,
|
|
390
399
|
redirectUrl: this.data.redirectUrl,
|
|
391
400
|
cookieSavedAt: this.data.cookieSavedAt,
|
|
401
|
+
expiryTimes: this.expiryTimes,
|
|
392
402
|
mainUrl: this.data.mainUrl,
|
|
393
403
|
...sourceInfo,
|
|
394
404
|
hasEteamsId: Boolean(this.eteamsId),
|
|
@@ -419,7 +429,7 @@ export class E10AuthSession {
|
|
|
419
429
|
return null;
|
|
420
430
|
const session = E10AuthSession.fromFile(authPath, baseUrl, passportUrl);
|
|
421
431
|
// 富化只是"刷新/纠偏":登录时这些字段已落盘。调用方可显式跳过(见 E10SessionLoadOptions)。
|
|
422
|
-
if (session && !(load.skipProfileEnrichment && hasUsableUserId(session))) {
|
|
432
|
+
if (session && !session.isExpired() && !(load.skipProfileEnrichment && hasUsableUserId(session))) {
|
|
423
433
|
await session.fetchEmployeeId();
|
|
424
434
|
}
|
|
425
435
|
return session;
|
|
@@ -57,6 +57,8 @@ export const XIAOE_SKILL_ETEAMID_API_PATH = '/spec-apps/api/app/rpachat/skill/ex
|
|
|
57
57
|
export const XIAOE_CLIENT_SETTINGS_SOURCE = 'XiaoE/Robots/user_settings.json';
|
|
58
58
|
export const XIAOE_USER_TOKEN_SOURCE = 'XIAOE_USER_TOKEN';
|
|
59
59
|
export const XIAOE_BUNDLED_ENV_SCRIPT_SOURCE = 'bundled scripts/xiaoe_env.py';
|
|
60
|
+
/** 与 XiaoE `tokens.py` 保持一致:换票成功后默认有效 2 小时。 */
|
|
61
|
+
export const XIAOE_ETEAMID_TTL_MS = 7_200_000;
|
|
60
62
|
function envString(env, ...keys) {
|
|
61
63
|
for (const key of keys) {
|
|
62
64
|
const value = env[key];
|
|
@@ -134,6 +136,14 @@ function readString(data, ...keys) {
|
|
|
134
136
|
}
|
|
135
137
|
return '';
|
|
136
138
|
}
|
|
139
|
+
function readExpiryTimes(data) {
|
|
140
|
+
for (const key of ['expiryTimes', 'expiryTime', 'expiresAt', 'expires_at']) {
|
|
141
|
+
const value = Number(data[key]);
|
|
142
|
+
if (Number.isFinite(value) && value > 0)
|
|
143
|
+
return Math.trunc(value);
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
137
147
|
function cookieString(value) {
|
|
138
148
|
if (!value)
|
|
139
149
|
return '';
|
|
@@ -209,7 +219,19 @@ function readXiaoECredentialRecord(value, source, fallbackBaseUrl = '') {
|
|
|
209
219
|
|| cookieValue(rawCookie, 'ETEAMSID');
|
|
210
220
|
if (!baseUrl || !eteamsId)
|
|
211
221
|
return null;
|
|
212
|
-
|
|
222
|
+
const expiryTimes = candidates.map(readExpiryTimes).find((value) => value !== undefined);
|
|
223
|
+
return {
|
|
224
|
+
baseUrl: normalizeUrl(baseUrl),
|
|
225
|
+
eteamsId,
|
|
226
|
+
source,
|
|
227
|
+
rawCookie,
|
|
228
|
+
...(expiryTimes !== undefined ? { expiryTimes } : {}),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function withDefaultXiaoEExpiry(credential) {
|
|
232
|
+
if (!credential || credential.expiryTimes !== undefined)
|
|
233
|
+
return credential;
|
|
234
|
+
return { ...credential, expiryTimes: Date.now() + XIAOE_ETEAMID_TTL_MS };
|
|
213
235
|
}
|
|
214
236
|
function parseXiaoEUnifiedLoginMap(raw, source) {
|
|
215
237
|
const value = raw.trim();
|
|
@@ -533,7 +555,7 @@ async function resolveXiaoETokenScriptCredential(baseUrl, env = process.env) {
|
|
|
533
555
|
const tokenData = await runXiaoETokenScript(baseUrl, env);
|
|
534
556
|
if (!tokenData)
|
|
535
557
|
return null;
|
|
536
|
-
return readXiaoECredentialRecord(tokenData, 'xiaoeUserData/tokens.py', baseUrl);
|
|
558
|
+
return withDefaultXiaoEExpiry(readXiaoECredentialRecord(tokenData, 'xiaoeUserData/tokens.py', baseUrl));
|
|
537
559
|
}
|
|
538
560
|
async function resolveXiaoEBundledEnvScriptCredential(baseUrl, env = process.env) {
|
|
539
561
|
const scriptPath = xiaoEBundledEnvScriptPath();
|
|
@@ -542,7 +564,7 @@ async function resolveXiaoEBundledEnvScriptCredential(baseUrl, env = process.env
|
|
|
542
564
|
const tokenData = await runXiaoEPythonCredentialScript(scriptPath, baseUrl, env);
|
|
543
565
|
if (!tokenData)
|
|
544
566
|
return null;
|
|
545
|
-
return readXiaoECredentialRecord(tokenData, XIAOE_BUNDLED_ENV_SCRIPT_SOURCE, baseUrl);
|
|
567
|
+
return withDefaultXiaoEExpiry(readXiaoECredentialRecord(tokenData, XIAOE_BUNDLED_ENV_SCRIPT_SOURCE, baseUrl));
|
|
546
568
|
}
|
|
547
569
|
export function xiaoEClientRobotsRoot(env = process.env) {
|
|
548
570
|
if (process.platform === 'win32') {
|
|
@@ -766,7 +788,7 @@ async function resolveXiaoETokenCredential(credential, baseUrl) {
|
|
|
766
788
|
// from the same origin as the token exchange. Newer 0000/200 responses keep
|
|
767
789
|
// the XiaoE service origin separate and use the requested E10 target.
|
|
768
790
|
const resolvedBaseUrl = payload.code === 0 ? credential.erpaUrl : targetBaseUrl;
|
|
769
|
-
return readXiaoECredentialRecord(payload, credential.source, resolvedBaseUrl);
|
|
791
|
+
return withDefaultXiaoEExpiry(readXiaoECredentialRecord(payload, credential.source, resolvedBaseUrl));
|
|
770
792
|
}
|
|
771
793
|
async function resolveXiaoEPlatformCredential(baseUrl, env = process.env) {
|
|
772
794
|
const credential = readXiaoEPlatformCredential(env);
|
|
@@ -850,6 +872,7 @@ async function saveXiaoEUnifiedLogin(input) {
|
|
|
850
872
|
baseUrl,
|
|
851
873
|
passportUrl: baseUrl,
|
|
852
874
|
cookieSavedAt: Date.now(),
|
|
875
|
+
...(input.expiryTimes !== undefined ? { expiryTimes: input.expiryTimes } : {}),
|
|
853
876
|
loginSource: 'xiaoe-unified',
|
|
854
877
|
credentialSourceStep: input.credentialSource.credentialSourceStep,
|
|
855
878
|
credentialSourceType: input.credentialSource.credentialSourceType,
|
|
@@ -872,6 +895,7 @@ async function saveXiaoEUnifiedLogin(input) {
|
|
|
872
895
|
agentType: XIAOE_AGENT_TYPE,
|
|
873
896
|
version: '',
|
|
874
897
|
loginSource: 'xiaoe-unified',
|
|
898
|
+
...(input.expiryTimes !== undefined ? { expiryTimes: input.expiryTimes } : {}),
|
|
875
899
|
...input.credentialSource,
|
|
876
900
|
},
|
|
877
901
|
};
|
|
@@ -973,6 +997,7 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
|
|
|
973
997
|
baseUrl: credential.baseUrl || baseUrl || DEFAULT_E10_BASE_URL,
|
|
974
998
|
eteamsId: credential.eteamsId,
|
|
975
999
|
rawCookie: credential.rawCookie,
|
|
1000
|
+
expiryTimes: credential.expiryTimes,
|
|
976
1001
|
tenantKeyFallback: options.tenantKeyFallback,
|
|
977
1002
|
profileHint: options.profileHint,
|
|
978
1003
|
credentialSource: describeXiaoECredentialSource(credential.source),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isXiaoERuntimeAgent } from '../../core/runtime-agent.js';
|
|
2
|
-
import { getActiveProfile } from './auth/session.js';
|
|
3
|
-
import { hasXiaoEEnv, tryXiaoEUnifiedLogin } from './auth/xiaoe.js';
|
|
2
|
+
import { getActiveProfile, readProfileConfig } from './auth/session.js';
|
|
3
|
+
import { hasXiaoEEnv, resolveXiaoEPreferredBaseUrl, tryXiaoEUnifiedLogin } from './auth/xiaoe.js';
|
|
4
4
|
const AUTH_REQUIRED_CODES = new Set(['-2', '401']);
|
|
5
5
|
const AUTH_REQUIRED_MARKERS = [
|
|
6
6
|
'未登录',
|
|
@@ -225,8 +225,10 @@ export function canAutomaticallyRecoverXiaoE(ctx, currentSession) {
|
|
|
225
225
|
|| isXiaoERuntimeAgent(currentSession?.agentType);
|
|
226
226
|
}
|
|
227
227
|
/**
|
|
228
|
-
* Silently
|
|
229
|
-
*
|
|
228
|
+
* Silently recover a stale or missing XiaoE session. Without a local session,
|
|
229
|
+
* current runtime evidence is required before obtaining credentials.
|
|
230
|
+
* Concurrent requests for the same profile/base URL share one credential
|
|
231
|
+
* exchange, so a burst of 401 responses
|
|
230
232
|
* does not start a burst of login attempts.
|
|
231
233
|
*/
|
|
232
234
|
export async function recoverXiaoEAuthSession(ctx, currentSession) {
|
|
@@ -234,7 +236,12 @@ export async function recoverXiaoEAuthSession(ctx, currentSession) {
|
|
|
234
236
|
return null;
|
|
235
237
|
const options = globalOptions(ctx);
|
|
236
238
|
const profile = options.profile || getActiveProfile();
|
|
237
|
-
const
|
|
239
|
+
const config = readProfileConfig(profile);
|
|
240
|
+
const baseUrl = resolveXiaoEPreferredBaseUrl({
|
|
241
|
+
explicitBaseUrl: currentSession?.getBaseUrl() || options.baseUrl,
|
|
242
|
+
profileBaseUrl: config.baseUrl,
|
|
243
|
+
env: ctx.env,
|
|
244
|
+
});
|
|
238
245
|
const key = `${profile}\n${baseUrl}`;
|
|
239
246
|
const existing = recoveryInFlight.get(key);
|
|
240
247
|
if (existing)
|
|
@@ -246,7 +253,7 @@ export async function recoverXiaoEAuthSession(ctx, currentSession) {
|
|
|
246
253
|
profileBaseUrl: baseUrl,
|
|
247
254
|
allowBundledEnvScript: true,
|
|
248
255
|
allowTokenScript: true,
|
|
249
|
-
tenantKeyFallback: currentSession.tenantKey,
|
|
256
|
+
tenantKeyFallback: currentSession?.tenantKey || config.tenantKey,
|
|
250
257
|
profileHint: profile,
|
|
251
258
|
// Intentionally no logger: transparent recovery must not leak an
|
|
252
259
|
// intermediate "login" step into agent stdout/stderr or JSON output.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isE10AuthenticationRequired, isE10LoginRedirect, isE10RedirectStatus, sameE10SessionIdentity, } from './auth-recovery.js';
|
|
2
|
+
import { E10_XIAOE_RECOVERY_FAILED_MESSAGE } from './auth/messages.js';
|
|
2
3
|
class E10AuthenticationContextChangedError extends Error {
|
|
3
4
|
}
|
|
4
5
|
export class E10ApiClient {
|
|
@@ -62,6 +63,18 @@ export class E10ApiClient {
|
|
|
62
63
|
async requestRaw(url, init) {
|
|
63
64
|
let lastError = null;
|
|
64
65
|
let authenticationRecoveryAttempted = false;
|
|
66
|
+
if (this.auth.isExpired?.()) {
|
|
67
|
+
authenticationRecoveryAttempted = true;
|
|
68
|
+
const recovered = await this.recoverAuthentication?.(this.auth) || null;
|
|
69
|
+
if (!recovered || recovered.isExpired?.()) {
|
|
70
|
+
throw new Error(E10_XIAOE_RECOVERY_FAILED_MESSAGE);
|
|
71
|
+
}
|
|
72
|
+
if (!sameE10SessionIdentity(this.auth, recovered)) {
|
|
73
|
+
throw new E10AuthenticationContextChangedError('XiaoE automatic authentication recovery changed the E10 login context');
|
|
74
|
+
}
|
|
75
|
+
this.auth.replaceWith(recovered);
|
|
76
|
+
refreshAuthenticationHeaders(init, this.auth);
|
|
77
|
+
}
|
|
65
78
|
for (let attempt = 1; attempt <= this.retries; attempt++) {
|
|
66
79
|
try {
|
|
67
80
|
const resp = await fetch(url, { ...init, redirect: 'manual' });
|
|
@@ -1,14 +1,35 @@
|
|
|
1
1
|
import { CliError, ExitCode } from '../../core/errors.js';
|
|
2
|
-
import { E10_AUTH_REQUIRED_MESSAGE } from './auth/messages.js';
|
|
2
|
+
import { E10_AUTH_REQUIRED_MESSAGE, E10_XIAOE_RECOVERY_FAILED_MESSAGE } from './auth/messages.js';
|
|
3
3
|
import { E10AuthSession } from './auth/session.js';
|
|
4
4
|
import { E10ApiClient } from './client.js';
|
|
5
|
-
import { recoverXiaoEAuthSession } from './auth-recovery.js';
|
|
5
|
+
import { canAutomaticallyRecoverXiaoE, recoverXiaoEAuthSession } from './auth-recovery.js';
|
|
6
|
+
function xiaoERecoveryFailedError() {
|
|
7
|
+
return Object.assign(new CliError(E10_XIAOE_RECOVERY_FAILED_MESSAGE, ExitCode.AuthError), {
|
|
8
|
+
type: 'authentication',
|
|
9
|
+
subtype: 'xiaoe_credential_unavailable',
|
|
10
|
+
retryable: false,
|
|
11
|
+
stage: 'xiaoe_unified_login',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
6
14
|
export async function getE10Session(ctx, load = {}) {
|
|
7
15
|
const options = ctx.getGlobalOptions();
|
|
8
16
|
return E10AuthSession.fromOptions(options, ctx.cwd, load);
|
|
9
17
|
}
|
|
10
18
|
export async function requireE10Session(ctx, load = {}) {
|
|
11
|
-
|
|
19
|
+
let session = await getE10Session(ctx, load);
|
|
20
|
+
if (session?.isExpired() && canAutomaticallyRecoverXiaoE(ctx, session)) {
|
|
21
|
+
session = await recoverXiaoEAuthSession(ctx, session).catch(() => null);
|
|
22
|
+
if (!session || session.isExpired())
|
|
23
|
+
throw xiaoERecoveryFailedError();
|
|
24
|
+
}
|
|
25
|
+
if (!session && canAutomaticallyRecoverXiaoE(ctx)) {
|
|
26
|
+
// Read-only probes use getE10Session; only a command requiring a session
|
|
27
|
+
// may establish one from the current XiaoE host's credentials.
|
|
28
|
+
session = await recoverXiaoEAuthSession(ctx).catch(() => null);
|
|
29
|
+
if (!session) {
|
|
30
|
+
throw xiaoERecoveryFailedError();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
12
33
|
if (!session) {
|
|
13
34
|
throw new CliError(E10_AUTH_REQUIRED_MESSAGE, ExitCode.AuthError);
|
|
14
35
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CliError, ExitCode } from '../../core/errors.js';
|
|
2
2
|
import { classifyE10Redirect, isE10AuthenticationRequired, recoverXiaoEAuthSession, sameE10SessionIdentity, } from './auth-recovery.js';
|
|
3
3
|
import { startFetchTiming } from './fetch-timing.js';
|
|
4
|
+
import { E10_XIAOE_RECOVERY_FAILED_MESSAGE } from './auth/messages.js';
|
|
4
5
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
5
6
|
const DEFAULT_BIG_INT_STRINGS = true;
|
|
6
7
|
/** 下载直传最多跟几跳:E10 -> 对象存储通常 1 跳,留一点余量但不允许成环。 */
|
|
@@ -53,6 +54,12 @@ export class E10RequestRuntime {
|
|
|
53
54
|
return this.request({ ...request, responseType: 'binary' });
|
|
54
55
|
}
|
|
55
56
|
async request(request) {
|
|
57
|
+
if (this.session.isExpired?.()) {
|
|
58
|
+
const recovered = await this.recoverExpiredSession(this.session.eteamsId);
|
|
59
|
+
if (!recovered || this.session.isExpired?.()) {
|
|
60
|
+
throw new E10ApiError('authentication', 'session_expired', E10_XIAOE_RECOVERY_FAILED_MESSAGE, { exitCode: ExitCode.AuthError, retryable: false });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
56
63
|
const mutable = this.prepareRequest(request);
|
|
57
64
|
try {
|
|
58
65
|
await this.runBeforeRequest(mutable, this.sessionContext());
|
|
@@ -171,6 +178,8 @@ export class E10RequestRuntime {
|
|
|
171
178
|
}
|
|
172
179
|
if (!recovered)
|
|
173
180
|
return false;
|
|
181
|
+
if (recovered.isExpired?.())
|
|
182
|
+
return false;
|
|
174
183
|
if (!sameE10SessionIdentity(this.session, recovered)) {
|
|
175
184
|
throw new E10ApiError('authentication', 'session_context_changed', 'XiaoE 自动恢复后的登录身份或 E10 环境发生变化,本次请求未重试', { exitCode: ExitCode.AuthError, retryable: false });
|
|
176
185
|
}
|
|
@@ -201,6 +210,7 @@ export class E10RequestRuntime {
|
|
|
201
210
|
timeoutMs: request.timeoutMs || DEFAULT_TIMEOUT_MS,
|
|
202
211
|
responseType: request.responseType || 'json',
|
|
203
212
|
bigIntStrings: request.bigIntStrings ?? DEFAULT_BIG_INT_STRINGS,
|
|
213
|
+
followRedirects: request.followRedirects === true,
|
|
204
214
|
};
|
|
205
215
|
}
|
|
206
216
|
sessionContext() {
|
|
@@ -270,7 +280,8 @@ export class E10RequestRuntime {
|
|
|
270
280
|
const init = {
|
|
271
281
|
method: request.method,
|
|
272
282
|
headers,
|
|
273
|
-
|
|
283
|
+
// 文件下载允许跟随重定向(302 → 文件服务);其余请求保持 manual 以便识别登录跳转
|
|
284
|
+
redirect: request.followRedirects ? 'follow' : 'manual',
|
|
274
285
|
};
|
|
275
286
|
if (request.streamBody) {
|
|
276
287
|
init.body = request.streamBody;
|
|
@@ -135,6 +135,8 @@ export function createArchiveHost(ctx, dependencies = {}) {
|
|
|
135
135
|
formData: multipart,
|
|
136
136
|
headers: { 'X-Requested-With': 'XMLHttpRequest', ...extraHeaders },
|
|
137
137
|
timeoutMs,
|
|
138
|
+
// 下载接口常 302 重定向到文件服务/CDN,需跟随,避免被误判为登录失效
|
|
139
|
+
followRedirects: true,
|
|
138
140
|
})
|
|
139
141
|
: await rt.requestJson({
|
|
140
142
|
module: 'archive',
|
|
@@ -25,7 +25,7 @@ export const archiveManifest = {
|
|
|
25
25
|
rules: [
|
|
26
26
|
'写操作(*.apply)必须先执行同名的 prepare 拿到 continuation,再带 confirm=true 执行 apply;CLI 用 --yes 表示确认。',
|
|
27
27
|
'借阅单下载为异步任务:apply 会轮询打包进度,完成后把 zip 保存到当前用户的「下载」目录(可用 output 指定路径)。',
|
|
28
|
-
'发起借阅/续借只返回流程创建页 URL,CLI
|
|
28
|
+
'发起借阅/续借只返回流程创建页 URL,CLI 不自动打开浏览器;应以 Markdown 超链接(a 标签)返回,由客户端决定打开方式。',
|
|
29
29
|
'借阅车/借阅单列表优先自定义接口,接口未发布时自动回退 ebuilder 列表链路,回退时行的字段更少(提供 fields/display)。',
|
|
30
30
|
],
|
|
31
31
|
operations: archiveOperations,
|
|
@@ -194,7 +194,19 @@ const borrowListDetail = {
|
|
|
194
194
|
}
|
|
195
195
|
const session = await host.getSession();
|
|
196
196
|
const detail = await fetchBorrowDetail(host, session.userId, String(dataId));
|
|
197
|
-
|
|
197
|
+
// 档案题名补可点击链接:arcDangan.id 形如 {档案ID}_{表单ID},拼成与全文检索一致的 archiveView
|
|
198
|
+
const root = String(session?.baseUrl ?? '').replace(/\/+$/, '');
|
|
199
|
+
const details = detail.details.map((item) => ({
|
|
200
|
+
...item,
|
|
201
|
+
arcDangan: item.arcDangan.map((arc) => {
|
|
202
|
+
const [arcId, arcFormId = ''] = String(arc.id ?? '').split('_');
|
|
203
|
+
if (!arcId || !arcFormId)
|
|
204
|
+
return arc;
|
|
205
|
+
const viewPath = `/sp/custom/archive/archiveView?arcId=${arcId}&arcFormId=${arcFormId}`;
|
|
206
|
+
return { ...arc, viewPath, url: root ? `${root}${viewPath}` : viewPath };
|
|
207
|
+
}),
|
|
208
|
+
}));
|
|
209
|
+
return archiveSuccess('archive.borrow-list.detail', { ...detail, details }, { count: details.length });
|
|
198
210
|
},
|
|
199
211
|
};
|
|
200
212
|
export const borrowListOperations = [borrowListList, borrowListDetail];
|
|
@@ -67,7 +67,7 @@ function flowResult(action, baseUrl, workflowId, params, extra = {}) {
|
|
|
67
67
|
workflowId,
|
|
68
68
|
url: buildFlowUrl(baseUrl, workflowId, params),
|
|
69
69
|
params,
|
|
70
|
-
hint: 'CLI
|
|
70
|
+
hint: 'CLI 不自动打开浏览器,也不要执行 open/start 自动打开;请把 url 以 Markdown 超链接(a 标签)返回,由客户端决定打开方式',
|
|
71
71
|
}, { wrote: true });
|
|
72
72
|
}
|
|
73
73
|
const flowBorrowCarPrepare = {
|