weaver-work-cli 0.1.20 → 0.1.22
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 +19 -11
- package/dist/core/refs.js +39 -36
- package/dist/internal/e10/auth/commands.js +33 -18
- package/dist/internal/e10/auth/session.js +60 -2
- package/dist/internal/e10/auth/xiaoe.js +253 -53
- package/dist/internal/e10/context.js +18 -6
- package/dist/internal/e10/request-runtime.js +3 -1
- package/dist/shortcuts/caizhilian/operations/confirm.js +1 -1
- package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +901 -20
- package/dist/shortcuts/caizhilian/operations/handlers/folder.js +55 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +4 -4
- package/dist/shortcuts/caizhilian/operations/specs.js +75 -0
- package/dist/shortcuts/hrm/manifest.js +16 -9
- package/dist/shortcuts/hrm/operations/registry.js +2 -0
- package/dist/shortcuts/hrm/operations/resolve.js +449 -0
- package/dist/shortcuts/invoice/host.js +5 -4
- package/dist/shortcuts/meeting/operations/eb/association.js +151 -0
- package/dist/shortcuts/meeting/operations/eb/attendance-summary.js +153 -0
- package/dist/shortcuts/meeting/operations/eb/catalog.js +185 -13
- package/dist/shortcuts/meeting/operations/eb/conflict.js +17 -11
- package/dist/shortcuts/meeting/operations/eb/decision.js +385 -0
- package/dist/shortcuts/meeting/operations/eb/factory.js +222 -35
- package/dist/shortcuts/meeting/operations/eb/index.js +18 -1
- package/dist/shortcuts/meeting/operations/eb/link.js +212 -0
- package/dist/shortcuts/meeting/operations/eb/task-progress.js +123 -0
- package/dist/shortcuts/meeting/operations/env.js +78 -61
- package/dist/shortcuts/meeting/operations/link.js +125 -0
- package/dist/shortcuts/meeting/operations/registry.js +2 -0
- package/dist/shortcuts/meeting/operations/shared.js +13 -0
- package/dist/shortcuts/workflow/host.js +5 -1
- package/dist/shortcuts/workflow/operations/form.js +95 -11
- package/dist/shortcuts/workflow/operations/read.js +785 -26
- package/docs/e10-auth.md +8 -2
- package/docs/hrm.md +34 -2
- package/docs/invoice.md +3 -0
- package/package.json +2 -2
- package/scripts/xiaoe_env.py +118 -0
- package/skill-template/domains/hrm.md +2 -1
- package/skill-template/skill-template.md +1 -1
- package/skills/weaver-e10-blog/SKILL.md +1 -1
- package/skills/weaver-e10-caizhilian/SKILL.md +5 -4
- package/skills/weaver-e10-caizhilian/references/file-collection.md +37 -0
- package/skills/weaver-e10-caizhilian/references/folder.md +5 -1
- package/skills/weaver-e10-caizhilian/references/setting-attach.md +35 -5
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +620 -5
- package/skills/weaver-e10-calendar/SKILL.md +1 -1
- package/skills/weaver-e10-ebuilder-form/SKILL.md +1 -1
- package/skills/weaver-e10-esb/SKILL.md +1 -1
- package/skills/weaver-e10-hrm/SKILL.md +52 -5
- package/skills/weaver-e10-hrm/references/org.md +107 -1
- package/skills/weaver-e10-jinchengda/SKILL.md +2 -1
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +1 -1
- package/skills/weaver-e10-jucailin/SKILL.md +1 -1
- package/skills/weaver-e10-mail/SKILL.md +1 -1
- package/skills/weaver-e10-meeting/SKILL.md +27 -11
- package/skills/weaver-e10-meeting/references/meeting-eb.md +162 -7
- package/skills/weaver-e10-meeting/references/meeting-verification.md +271 -0
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-okr/SKILL.md +1 -1
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-plan/SKILL.md +1 -1
- package/skills/weaver-e10-qiyecheng/SKILL.md +1 -1
- package/skills/weaver-e10-shared/SKILL.md +10 -13
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +11 -17
- package/skills/weaver-e10-shared/references/non-interactive-environment.md +21 -14
- package/skills/weaver-e10-shijingran/SKILL.md +1 -1
- package/skills/weaver-e10-skill-maker/SKILL.md +1 -1
- package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +1 -1
- package/skills/weaver-e10-wenshuding/SKILL.md +1 -1
- package/skills/weaver-e10-workflow/SKILL.md +19 -19
- package/skills/weaver-e10-workflow/references/source-manifest.json +13 -13
- package/skills/weaver-e10-workflow/references/workflow-create.md +11 -7
- package/skills/weaver-e10-workflow/references/workflow-query.md +9 -14
- package/skills/weaver-e10-yepiaotong/SKILL.md +1 -2
- package/skills/weaver-e10-yimiaoban/SKILL.md +1 -1
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
- package/skills/weaver-e10-ziguanjia/SKILL.md +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { accessSync, constants as fsConstants, existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { CliError, ExitCode } from '../../../core/errors.js';
|
|
6
|
-
import { buildE10CookieHeader, E10AuthSession, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
|
|
7
|
+
import { buildE10CookieHeader, E10AuthSession, extractRawSetCookie, extractSessionTicket, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
|
|
7
8
|
export const DEFAULT_E10_BASE_URL = 'https://www.e-cology.com.cn';
|
|
8
9
|
export const XIAOE_AGENT_TYPE = 'XiaoE';
|
|
9
10
|
// `XIAOE_` 是宿主/平台保留前缀(XIAOE_DOMAIN、XIAOE_USER_TOKEN、XIAOE_SKILL_* 等)。
|
|
@@ -20,7 +21,6 @@ const XIAOE_UNIFIED_LOGIN_MAP_ENV_KEYS = [
|
|
|
20
21
|
'XIAOE_UNIFIED_LOGIN_MAP',
|
|
21
22
|
];
|
|
22
23
|
const XIAOE_SINGLE_DOMAIN_ENV_KEYS = [
|
|
23
|
-
'XIAOE_DOMAIN',
|
|
24
24
|
'WEAVER_XIAOE_E10_DOMAIN',
|
|
25
25
|
'WEAVER_XIAOE_BASE_URL',
|
|
26
26
|
'WEAVER_XIAOE_E10_BASE_URL',
|
|
@@ -50,6 +50,7 @@ const XIAOE_CLIENT_TOKEN_TIMEOUT_MS = 15_000;
|
|
|
50
50
|
export const XIAOE_SKILL_ETEAMID_API_PATH = '/spec-apps/api/app/rpachat/skill/extend/eteamId';
|
|
51
51
|
export const XIAOE_CLIENT_SETTINGS_SOURCE = 'XiaoE/Robots/user_settings.json';
|
|
52
52
|
export const XIAOE_USER_TOKEN_SOURCE = 'XIAOE_USER_TOKEN';
|
|
53
|
+
export const XIAOE_BUNDLED_ENV_SCRIPT_SOURCE = 'bundled scripts/xiaoe_env.py';
|
|
53
54
|
function envString(env, ...keys) {
|
|
54
55
|
for (const key of keys) {
|
|
55
56
|
const value = env[key];
|
|
@@ -69,11 +70,13 @@ export function hasXiaoEEnv(env = process.env) {
|
|
|
69
70
|
export function describeXiaoECredentialSource(source) {
|
|
70
71
|
// Step 1 is the platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN):
|
|
71
72
|
// it is available in every sandbox run and needs no local desktop client.
|
|
72
|
-
if (source === XIAOE_USER_TOKEN_SOURCE) {
|
|
73
|
+
if (source === XIAOE_USER_TOKEN_SOURCE || source === XIAOE_BUNDLED_ENV_SCRIPT_SOURCE) {
|
|
73
74
|
return {
|
|
74
75
|
credentialSourceStep: 1,
|
|
75
76
|
credentialSourceType: 'xiaoe_platform',
|
|
76
|
-
credentialSourceLabel:
|
|
77
|
+
credentialSourceLabel: source === XIAOE_BUNDLED_ENV_SCRIPT_SOURCE
|
|
78
|
+
? 'XiaoE platform user token (bundled Python fallback)'
|
|
79
|
+
: 'XiaoE platform user token',
|
|
77
80
|
};
|
|
78
81
|
}
|
|
79
82
|
if (source === 'xiaoeUserData/tokens.py') {
|
|
@@ -256,18 +259,24 @@ function normalizeBaseUrlValue(value) {
|
|
|
256
259
|
return '';
|
|
257
260
|
return /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
|
|
258
261
|
}
|
|
259
|
-
|
|
262
|
+
function resolveXiaoEConfiguredBaseUrl(env) {
|
|
260
263
|
const explicitDefault = envString(env, ...XIAOE_DEFAULT_DOMAIN_ENV_KEYS);
|
|
261
264
|
if (explicitDefault)
|
|
262
265
|
return normalizeBaseUrlValue(explicitDefault);
|
|
263
|
-
//
|
|
264
|
-
//
|
|
266
|
+
// XIAOE_DOMAIN is the XiaoE/erpa service address used to exchange
|
|
267
|
+
// XIAOE_USER_TOKEN. Only dedicated E10 variables may select the target E10.
|
|
265
268
|
const singleDomain = envString(env, ...XIAOE_SINGLE_DOMAIN_ENV_KEYS);
|
|
266
269
|
if (singleDomain)
|
|
267
270
|
return normalizeBaseUrlValue(singleDomain);
|
|
268
271
|
const firstCredential = listXiaoEUnifiedLoginCredentials(env)[0];
|
|
269
272
|
if (firstCredential?.baseUrl)
|
|
270
273
|
return normalizeUrl(firstCredential.baseUrl);
|
|
274
|
+
return '';
|
|
275
|
+
}
|
|
276
|
+
export function resolveXiaoEDefaultBaseUrl(env = process.env) {
|
|
277
|
+
const configured = resolveXiaoEConfiguredBaseUrl(env);
|
|
278
|
+
if (configured)
|
|
279
|
+
return configured;
|
|
271
280
|
return hasXiaoEEnv(env) ? DEFAULT_E10_BASE_URL : '';
|
|
272
281
|
}
|
|
273
282
|
function uniqueStrings(values) {
|
|
@@ -294,7 +303,7 @@ function xiaoETokenScriptCandidatePaths(env = process.env) {
|
|
|
294
303
|
]);
|
|
295
304
|
}
|
|
296
305
|
const envHome = envString(env, 'HOME');
|
|
297
|
-
const nodeHome = homedir();
|
|
306
|
+
const nodeHome = envHome ? '' : homedir();
|
|
298
307
|
return uniqueStrings([
|
|
299
308
|
envHome ? join(envHome, 'Library', 'Application Support', 'xiaoeUserData', 'tokens.py') : '',
|
|
300
309
|
nodeHome ? join(nodeHome, 'Library', 'Application Support', 'xiaoeUserData', 'tokens.py') : '',
|
|
@@ -303,6 +312,10 @@ function xiaoETokenScriptCandidatePaths(env = process.env) {
|
|
|
303
312
|
function xiaoETokenScriptPath(env = process.env) {
|
|
304
313
|
return xiaoETokenScriptCandidatePaths(env).find((path) => existsSync(path)) || '';
|
|
305
314
|
}
|
|
315
|
+
export function xiaoEBundledEnvScriptPath() {
|
|
316
|
+
const scriptPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../scripts/xiaoe_env.py');
|
|
317
|
+
return existsSync(scriptPath) ? scriptPath : '';
|
|
318
|
+
}
|
|
306
319
|
export function hasXiaoETokenScript(env = process.env) {
|
|
307
320
|
const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY, XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY);
|
|
308
321
|
if (explicit)
|
|
@@ -310,22 +323,114 @@ export function hasXiaoETokenScript(env = process.env) {
|
|
|
310
323
|
const scriptPath = xiaoETokenScriptPath(env);
|
|
311
324
|
return Boolean(scriptPath && existsSync(scriptPath));
|
|
312
325
|
}
|
|
313
|
-
function
|
|
326
|
+
function isExecutableFile(path) {
|
|
327
|
+
try {
|
|
328
|
+
if (!statSync(path).isFile())
|
|
329
|
+
return false;
|
|
330
|
+
accessSync(path, fsConstants.X_OK);
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function listXiaoEPythonRuntimeDirectories(resourcesRoot) {
|
|
338
|
+
if (!resourcesRoot || !existsSync(resourcesRoot))
|
|
339
|
+
return [];
|
|
340
|
+
try {
|
|
341
|
+
return readdirSync(resourcesRoot, { withFileTypes: true })
|
|
342
|
+
.filter((entry) => entry.isDirectory() && /^Python3(?:[-_.]\d[\d.]*)?$/iu.test(entry.name))
|
|
343
|
+
.map((entry) => join(resourcesRoot, entry.name))
|
|
344
|
+
.sort((left, right) => right.localeCompare(left, undefined, { numeric: true }));
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function listExecutablePythonFiles(directory, platform) {
|
|
351
|
+
if (!directory || !existsSync(directory))
|
|
352
|
+
return [];
|
|
353
|
+
try {
|
|
354
|
+
const pattern = platform === 'win32' ? /^python(?:3(?:\.\d+)*)?\.exe$/iu : /^python3(?:\.\d+)*$/u;
|
|
355
|
+
return readdirSync(directory, { withFileTypes: true })
|
|
356
|
+
.filter((entry) => entry.isFile() && pattern.test(entry.name))
|
|
357
|
+
.map((entry) => join(directory, entry.name))
|
|
358
|
+
.filter(isExecutableFile)
|
|
359
|
+
.sort((left, right) => right.localeCompare(left, undefined, { numeric: true }));
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Locate the Python runtime bundled with the desktop XiaoE client. The client
|
|
367
|
+
* ships the runtime expected by xiaoeUserData/tokens.py; the system Python may
|
|
368
|
+
* still be unavailable or incompatible with a host-provided script.
|
|
369
|
+
*/
|
|
370
|
+
export function xiaoEClientPythonCandidates(env = process.env, platform = process.platform) {
|
|
371
|
+
const appRoots = [];
|
|
372
|
+
if (platform === 'darwin') {
|
|
373
|
+
const envHome = envString(env, 'HOME');
|
|
374
|
+
const nodeHome = homedir();
|
|
375
|
+
for (const home of uniqueStrings([envHome, nodeHome])) {
|
|
376
|
+
appRoots.push(join(home, 'Applications', '智能小e.app'));
|
|
377
|
+
}
|
|
378
|
+
appRoots.push('/Applications/智能小e.app');
|
|
379
|
+
}
|
|
380
|
+
else if (platform === 'win32') {
|
|
381
|
+
const localAppData = envString(env, 'LOCALAPPDATA');
|
|
382
|
+
const programFiles = uniqueStrings([
|
|
383
|
+
envString(env, 'ProgramW6432'),
|
|
384
|
+
envString(env, 'ProgramFiles'),
|
|
385
|
+
envString(env, 'ProgramFiles(x86)'),
|
|
386
|
+
]);
|
|
387
|
+
const appNames = ['智能小e', 'XiaoE', 'XiaoEClient', 'XiaoEAI'];
|
|
388
|
+
if (localAppData) {
|
|
389
|
+
for (const appName of appNames) {
|
|
390
|
+
appRoots.push(join(localAppData, 'Programs', appName), join(localAppData, appName));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
for (const programFilesRoot of programFiles) {
|
|
394
|
+
for (const appName of appNames)
|
|
395
|
+
appRoots.push(join(programFilesRoot, appName));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const candidates = [];
|
|
399
|
+
for (const appRoot of uniqueStrings(appRoots)) {
|
|
400
|
+
const resourcesRoots = platform === 'darwin'
|
|
401
|
+
? [join(appRoot, 'Contents', 'Resources')]
|
|
402
|
+
: [join(appRoot, 'Resources'), appRoot];
|
|
403
|
+
for (const resourcesRoot of resourcesRoots) {
|
|
404
|
+
for (const runtimeRoot of listXiaoEPythonRuntimeDirectories(resourcesRoot)) {
|
|
405
|
+
const executableDirectories = platform === 'win32'
|
|
406
|
+
? [runtimeRoot, join(runtimeRoot, 'bin')]
|
|
407
|
+
: [join(runtimeRoot, 'bin')];
|
|
408
|
+
for (const executableDirectory of executableDirectories) {
|
|
409
|
+
candidates.push(...listExecutablePythonFiles(executableDirectory, platform));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return uniqueStrings(candidates);
|
|
415
|
+
}
|
|
416
|
+
export function xiaoETokenScriptPythonCandidates(env = process.env, platform = process.platform) {
|
|
314
417
|
const explicit = envString(env, XIAOE_TOKEN_SCRIPT_PYTHON_ENV_KEY, XIAOE_TOKEN_SCRIPT_PYTHON_LEGACY_ENV_KEY);
|
|
315
418
|
if (explicit)
|
|
316
419
|
return [explicit];
|
|
317
|
-
return uniqueStrings(
|
|
318
|
-
? ['python', 'python3', 'py']
|
|
319
|
-
: ['python3', '/usr/bin/python3', 'python']);
|
|
420
|
+
return uniqueStrings(platform === 'win32'
|
|
421
|
+
? [...xiaoEClientPythonCandidates(env, platform), 'python', 'python3', 'py']
|
|
422
|
+
: [...xiaoEClientPythonCandidates(env, platform), 'python3', '/usr/bin/python3', 'python']);
|
|
320
423
|
}
|
|
321
424
|
function isXiaoETokenScriptEnvFailure(message) {
|
|
322
|
-
return /No module named|ModuleNotFoundError|ENOENT|command not found|not recognized as an internal|cannot find the file/i
|
|
425
|
+
return /No module named|ModuleNotFoundError|ENOENT|EACCES|permission denied|command not found|not recognized as an internal|cannot find the file/i
|
|
323
426
|
.test(message);
|
|
324
427
|
}
|
|
325
|
-
async function runXiaoETokenScriptOnce(pythonCommand, scriptPath, env) {
|
|
428
|
+
async function runXiaoETokenScriptOnce(pythonCommand, scriptPath, baseUrl, env) {
|
|
326
429
|
const wrapper = [
|
|
327
430
|
'import ast,contextlib,io,json,runpy,sys',
|
|
328
431
|
`path = ${JSON.stringify(scriptPath)}`,
|
|
432
|
+
`base_url = ${JSON.stringify(baseUrl)}`,
|
|
433
|
+
'sys.argv = [path, base_url]',
|
|
329
434
|
'buf = io.StringIO()',
|
|
330
435
|
'data = None',
|
|
331
436
|
'with contextlib.redirect_stdout(buf):',
|
|
@@ -347,9 +452,9 @@ async function runXiaoETokenScriptOnce(pythonCommand, scriptPath, env) {
|
|
|
347
452
|
' except Exception as exc:',
|
|
348
453
|
' last_error = exc',
|
|
349
454
|
' if data is None:',
|
|
350
|
-
' raise SystemExit("
|
|
455
|
+
' raise SystemExit("XiaoE credential script stdout is not valid JSON object")',
|
|
351
456
|
'else:',
|
|
352
|
-
' raise SystemExit("
|
|
457
|
+
' raise SystemExit("XiaoE credential script produced empty stdout")',
|
|
353
458
|
'print(json.dumps(data, ensure_ascii=False))',
|
|
354
459
|
].join('\n');
|
|
355
460
|
return await new Promise((resolve, reject) => {
|
|
@@ -360,12 +465,12 @@ async function runXiaoETokenScriptOnce(pythonCommand, scriptPath, env) {
|
|
|
360
465
|
}, (error, stdout, stderr) => {
|
|
361
466
|
if (error) {
|
|
362
467
|
const detail = (stderr || error.message || 'unknown error').trim().split('\n').slice(-1)[0];
|
|
363
|
-
reject(new CliError('XiaoE
|
|
468
|
+
reject(new CliError('XiaoE credential script failed: ' + detail, ExitCode.AuthError));
|
|
364
469
|
return;
|
|
365
470
|
}
|
|
366
471
|
const text = stdout.trim();
|
|
367
472
|
if (!text) {
|
|
368
|
-
reject(new CliError('XiaoE
|
|
473
|
+
reject(new CliError('XiaoE credential script returned empty stdout.', ExitCode.AuthError));
|
|
369
474
|
return;
|
|
370
475
|
}
|
|
371
476
|
try {
|
|
@@ -373,19 +478,18 @@ async function runXiaoETokenScriptOnce(pythonCommand, scriptPath, env) {
|
|
|
373
478
|
resolve(parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null);
|
|
374
479
|
}
|
|
375
480
|
catch {
|
|
376
|
-
reject(new CliError('XiaoE
|
|
481
|
+
reject(new CliError('XiaoE credential script stdout is not valid JSON.', ExitCode.AuthError));
|
|
377
482
|
}
|
|
378
483
|
});
|
|
379
484
|
});
|
|
380
485
|
}
|
|
381
|
-
async function
|
|
382
|
-
const scriptPath = xiaoETokenScriptPath(env);
|
|
486
|
+
async function runXiaoEPythonCredentialScript(scriptPath, baseUrl, env) {
|
|
383
487
|
if (!scriptPath || !existsSync(scriptPath))
|
|
384
488
|
return null;
|
|
385
489
|
let lastError = null;
|
|
386
490
|
for (const pythonCommand of xiaoETokenScriptPythonCandidates(env)) {
|
|
387
491
|
try {
|
|
388
|
-
return await runXiaoETokenScriptOnce(pythonCommand, scriptPath, env);
|
|
492
|
+
return await runXiaoETokenScriptOnce(pythonCommand, scriptPath, baseUrl, env);
|
|
389
493
|
}
|
|
390
494
|
catch (error) {
|
|
391
495
|
lastError = error;
|
|
@@ -396,14 +500,26 @@ async function runXiaoETokenScript(env = process.env) {
|
|
|
396
500
|
break;
|
|
397
501
|
}
|
|
398
502
|
}
|
|
399
|
-
throw lastError instanceof Error ? lastError : new CliError('XiaoE
|
|
503
|
+
throw lastError instanceof Error ? lastError : new CliError('XiaoE credential script failed.', ExitCode.AuthError);
|
|
504
|
+
}
|
|
505
|
+
async function runXiaoETokenScript(baseUrl, env = process.env) {
|
|
506
|
+
return runXiaoEPythonCredentialScript(xiaoETokenScriptPath(env), baseUrl, env);
|
|
400
507
|
}
|
|
401
508
|
async function resolveXiaoETokenScriptCredential(baseUrl, env = process.env) {
|
|
402
|
-
const tokenData = await runXiaoETokenScript(env);
|
|
509
|
+
const tokenData = await runXiaoETokenScript(baseUrl, env);
|
|
403
510
|
if (!tokenData)
|
|
404
511
|
return null;
|
|
405
512
|
return readXiaoECredentialRecord(tokenData, 'xiaoeUserData/tokens.py', baseUrl);
|
|
406
513
|
}
|
|
514
|
+
async function resolveXiaoEBundledEnvScriptCredential(baseUrl, env = process.env) {
|
|
515
|
+
const scriptPath = xiaoEBundledEnvScriptPath();
|
|
516
|
+
if (!scriptPath)
|
|
517
|
+
return null;
|
|
518
|
+
const tokenData = await runXiaoEPythonCredentialScript(scriptPath, baseUrl, env);
|
|
519
|
+
if (!tokenData)
|
|
520
|
+
return null;
|
|
521
|
+
return readXiaoECredentialRecord(tokenData, XIAOE_BUNDLED_ENV_SCRIPT_SOURCE, baseUrl);
|
|
522
|
+
}
|
|
407
523
|
export function xiaoEClientRobotsRoot(env = process.env) {
|
|
408
524
|
if (process.platform === 'win32') {
|
|
409
525
|
const localAppData = envString(env, 'LOCALAPPDATA');
|
|
@@ -437,10 +553,6 @@ export function findXiaoEClientSettingsFile(env = process.env) {
|
|
|
437
553
|
return '';
|
|
438
554
|
return files.sort((left, right) => statSync(right).mtimeMs - statSync(left).mtimeMs)[0];
|
|
439
555
|
}
|
|
440
|
-
/**
|
|
441
|
-
* Mirror of the host `xiaoeUserData/tokens.py` credential chain, implemented in
|
|
442
|
-
* Node so the CLI does not depend on a Python runtime that has `requests`.
|
|
443
|
-
*/
|
|
444
556
|
/**
|
|
445
557
|
* Platform-injected credential: XIAOE_USER_TOKEN (+ the XiaoE service address).
|
|
446
558
|
* This is the only source that works in a Linux sandbox with no desktop client
|
|
@@ -488,21 +600,57 @@ async function fetchXiaoEClientEteamId(credential, domain) {
|
|
|
488
600
|
const controller = new AbortController();
|
|
489
601
|
const timer = setTimeout(() => controller.abort(), XIAOE_CLIENT_TOKEN_TIMEOUT_MS);
|
|
490
602
|
try {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
603
|
+
let endpoint = new URL(`${credential.erpaUrl}${XIAOE_SKILL_ETEAMID_API_PATH}`);
|
|
604
|
+
let resp = null;
|
|
605
|
+
for (let redirects = 0; redirects <= 5; redirects += 1) {
|
|
606
|
+
resp = await fetch(endpoint, {
|
|
607
|
+
method: 'POST',
|
|
608
|
+
headers: {
|
|
609
|
+
'content-type': 'application/json',
|
|
610
|
+
authorization: credential.authToken,
|
|
611
|
+
},
|
|
612
|
+
body: JSON.stringify({ domain: domain || DEFAULT_E10_BASE_URL }),
|
|
613
|
+
redirect: 'manual',
|
|
614
|
+
signal: controller.signal,
|
|
615
|
+
});
|
|
616
|
+
if (resp.status < 300 || resp.status >= 400)
|
|
617
|
+
break;
|
|
618
|
+
if (redirects === 5) {
|
|
619
|
+
throw new CliError('XiaoE client token endpoint redirected too many times.', ExitCode.AuthError);
|
|
620
|
+
}
|
|
621
|
+
const location = resp.headers.get('location');
|
|
622
|
+
if (!location) {
|
|
623
|
+
throw new CliError('XiaoE client token endpoint returned a redirect without Location.', ExitCode.AuthError);
|
|
624
|
+
}
|
|
625
|
+
const next = new URL(location, endpoint);
|
|
626
|
+
const sameOrigin = next.origin === endpoint.origin;
|
|
627
|
+
const sameHostHttpsUpgrade = endpoint.protocol === 'http:'
|
|
628
|
+
&& next.protocol === 'https:'
|
|
629
|
+
&& next.hostname === endpoint.hostname;
|
|
630
|
+
const safeProtocol = next.protocol === 'http:' || next.protocol === 'https:';
|
|
631
|
+
if (!safeProtocol || next.username || next.password || (!sameOrigin && !sameHostHttpsUpgrade)) {
|
|
632
|
+
throw new CliError('XiaoE client token endpoint attempted an unsafe cross-origin redirect.', ExitCode.AuthError);
|
|
633
|
+
}
|
|
634
|
+
await resp.body?.cancel().catch(() => undefined);
|
|
635
|
+
endpoint = next;
|
|
636
|
+
}
|
|
637
|
+
if (!resp?.ok) {
|
|
638
|
+
throw new CliError(`XiaoE client token endpoint returned HTTP ${resp?.status || 0}.`, ExitCode.AuthError);
|
|
503
639
|
}
|
|
504
640
|
const data = await resp.json().catch(() => null);
|
|
505
|
-
|
|
641
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
642
|
+
throw new CliError('XiaoE client token endpoint returned invalid JSON.', ExitCode.AuthError);
|
|
643
|
+
}
|
|
644
|
+
const payload = data;
|
|
645
|
+
const businessCode = payload.code;
|
|
646
|
+
const businessData = payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)
|
|
647
|
+
? payload.data
|
|
648
|
+
: {};
|
|
649
|
+
if ((businessCode !== '0000' && businessCode !== 0 && businessCode !== 200) || !readString(businessData, 'eteamId')) {
|
|
650
|
+
const codeText = businessCode === undefined ? 'missing' : String(businessCode);
|
|
651
|
+
throw new CliError(`XiaoE client token exchange failed with business code ${codeText}.`, ExitCode.AuthError);
|
|
652
|
+
}
|
|
653
|
+
return payload;
|
|
506
654
|
}
|
|
507
655
|
finally {
|
|
508
656
|
clearTimeout(timer);
|
|
@@ -511,7 +659,11 @@ async function fetchXiaoEClientEteamId(credential, domain) {
|
|
|
511
659
|
async function resolveXiaoETokenCredential(credential, baseUrl) {
|
|
512
660
|
const targetBaseUrl = baseUrl || DEFAULT_E10_BASE_URL;
|
|
513
661
|
const payload = await fetchXiaoEClientEteamId(credential, targetBaseUrl);
|
|
514
|
-
|
|
662
|
+
// The legacy gateway contract uses numeric code 0 and serves the E10 APIs
|
|
663
|
+
// from the same origin as the token exchange. Newer 0000/200 responses keep
|
|
664
|
+
// the XiaoE service origin separate and use the requested E10 target.
|
|
665
|
+
const resolvedBaseUrl = payload.code === 0 ? credential.erpaUrl : targetBaseUrl;
|
|
666
|
+
return readXiaoECredentialRecord(payload, credential.source, resolvedBaseUrl);
|
|
515
667
|
}
|
|
516
668
|
async function resolveXiaoEPlatformCredential(baseUrl, env = process.env) {
|
|
517
669
|
const credential = readXiaoEPlatformCredential(env);
|
|
@@ -530,10 +682,31 @@ export function resolveXiaoEPreferredBaseUrl(options) {
|
|
|
530
682
|
const explicit = normalizeUrl(options.explicitBaseUrl || '');
|
|
531
683
|
if (explicit)
|
|
532
684
|
return explicit;
|
|
533
|
-
const
|
|
534
|
-
if (
|
|
535
|
-
return
|
|
536
|
-
|
|
685
|
+
const configured = resolveXiaoEConfiguredBaseUrl(env);
|
|
686
|
+
if (configured)
|
|
687
|
+
return configured;
|
|
688
|
+
const profileBaseUrl = normalizeUrl(options.profileBaseUrl || '');
|
|
689
|
+
if (profileBaseUrl)
|
|
690
|
+
return profileBaseUrl;
|
|
691
|
+
return hasXiaoEEnv(env) ? DEFAULT_E10_BASE_URL : '';
|
|
692
|
+
}
|
|
693
|
+
export function shouldUseXiaoEBrowserFallback(platform = process.platform) {
|
|
694
|
+
return platform === 'darwin' || platform === 'win32';
|
|
695
|
+
}
|
|
696
|
+
export function createXiaoEUnifiedLoginUnavailableError(baseUrl = '') {
|
|
697
|
+
const target = normalizeUrl(baseUrl);
|
|
698
|
+
const message = [
|
|
699
|
+
'XiaoE unified login did not return a usable E10 session.',
|
|
700
|
+
target ? `Resolved E10 baseUrl: ${target}.` : 'No E10 baseUrl could be resolved.',
|
|
701
|
+
'This web/Linux host cannot complete a localhost browser OAuth callback.',
|
|
702
|
+
'Refresh the XiaoE web session or restore host credential injection before retrying.',
|
|
703
|
+
].join(' ');
|
|
704
|
+
return Object.assign(new CliError(message, ExitCode.AuthError), {
|
|
705
|
+
type: 'authentication',
|
|
706
|
+
subtype: 'xiaoe_web_credential_unavailable',
|
|
707
|
+
retryable: false,
|
|
708
|
+
stage: 'xiaoe_unified_login',
|
|
709
|
+
});
|
|
537
710
|
}
|
|
538
711
|
async function resolveUserInfo(baseUrl, eteamsId, rawCookie = '', agentType = '') {
|
|
539
712
|
const resp = await fetch(`${baseUrl}/api/baseserver/layout/teamsCheck?clientType=not_xinchuang&client=WEB&domainName=${encodeURIComponent(baseUrl)}`, {
|
|
@@ -549,11 +722,11 @@ async function resolveUserInfo(baseUrl, eteamsId, rawCookie = '', agentType = ''
|
|
|
549
722
|
redirect: 'manual',
|
|
550
723
|
});
|
|
551
724
|
const data = await resp.json().catch(() => ({}));
|
|
552
|
-
return readTeamsCheckUserInfo(resp, data);
|
|
725
|
+
return { userInfo: readTeamsCheckUserInfo(resp, data), setCookie: extractRawSetCookie(resp) };
|
|
553
726
|
}
|
|
554
727
|
async function saveXiaoEUnifiedLogin(input) {
|
|
555
728
|
const baseUrl = normalizeUrl(input.baseUrl);
|
|
556
|
-
const userInfo = await resolveUserInfo(baseUrl, input.eteamsId, input.rawCookie || '', XIAOE_AGENT_TYPE);
|
|
729
|
+
const { userInfo, setCookie } = await resolveUserInfo(baseUrl, input.eteamsId, input.rawCookie || '', XIAOE_AGENT_TYPE);
|
|
557
730
|
if (!userInfo.userId) {
|
|
558
731
|
throw new CliError('Could not resolve userId. Check whether XiaoE unified login is valid for this E10 domain.', ExitCode.AuthError);
|
|
559
732
|
}
|
|
@@ -563,6 +736,7 @@ async function saveXiaoEUnifiedLogin(input) {
|
|
|
563
736
|
const cookies = { ETEAMSID: input.eteamsId };
|
|
564
737
|
if (input.rawCookie)
|
|
565
738
|
cookies.raw = input.rawCookie;
|
|
739
|
+
const ztSession = extractSessionTicket(setCookie, input.rawCookie || '');
|
|
566
740
|
const authData = {
|
|
567
741
|
cookies,
|
|
568
742
|
userId: userInfo.userId,
|
|
@@ -580,6 +754,7 @@ async function saveXiaoEUnifiedLogin(input) {
|
|
|
580
754
|
credentialSourceStep: input.credentialSource.credentialSourceStep,
|
|
581
755
|
credentialSourceType: input.credentialSource.credentialSourceType,
|
|
582
756
|
credentialSourceLabel: input.credentialSource.credentialSourceLabel,
|
|
757
|
+
...(ztSession ? { ztSession } : {}),
|
|
583
758
|
};
|
|
584
759
|
const authFile = saveAuthFile(authData, profile);
|
|
585
760
|
saveProfileConfig(baseUrl, baseUrl, profile, tenantKey);
|
|
@@ -606,6 +781,7 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
|
|
|
606
781
|
const explicitBaseUrl = normalizeUrl(options.baseUrl || '');
|
|
607
782
|
const baseUrl = resolveXiaoEPreferredBaseUrl({
|
|
608
783
|
explicitBaseUrl,
|
|
784
|
+
profileBaseUrl: options.profileBaseUrl,
|
|
609
785
|
env,
|
|
610
786
|
});
|
|
611
787
|
const mayUseTokenScript = options.allowTokenScript || hasXiaoEEnv(env);
|
|
@@ -624,7 +800,31 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
|
|
|
624
800
|
options.logger?.warn('XiaoE platform credential failed. ' + message);
|
|
625
801
|
}
|
|
626
802
|
if (!credential) {
|
|
627
|
-
options.logger?.warn(
|
|
803
|
+
options.logger?.warn(options.allowBundledEnvScript
|
|
804
|
+
? 'XiaoE platform credential did not return usable ETEAMSID; will try the CLI-bundled xiaoe_env.py compatibility path next.'
|
|
805
|
+
: 'XiaoE platform credential did not return usable ETEAMSID; will try xiaoeUserData/tokens.py next.');
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
// Step 1b — compatibility path used by the reference login package. The
|
|
809
|
+
// script is shipped with the CLI npm package (never with a Skill) and its
|
|
810
|
+
// stdout stays inside this process so credentials are not exposed to agents.
|
|
811
|
+
if (!credential && options.allowBundledEnvScript && hasXiaoEPlatformCredential(env)) {
|
|
812
|
+
const scriptPath = xiaoEBundledEnvScriptPath();
|
|
813
|
+
if (!scriptPath) {
|
|
814
|
+
options.logger?.warn('CLI-bundled xiaoe_env.py was not found; will try desktop XiaoE credential sources next.');
|
|
815
|
+
}
|
|
816
|
+
else {
|
|
817
|
+
options.logger?.info('Trying CLI-bundled xiaoe_env.py compatibility credential...');
|
|
818
|
+
try {
|
|
819
|
+
credential = await resolveXiaoEBundledEnvScriptCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
823
|
+
options.logger?.warn('CLI-bundled xiaoe_env.py failed. ' + message);
|
|
824
|
+
}
|
|
825
|
+
if (!credential) {
|
|
826
|
+
options.logger?.warn('CLI-bundled xiaoe_env.py did not return usable ETEAMSID; will try xiaoeUserData/tokens.py next.');
|
|
827
|
+
}
|
|
628
828
|
}
|
|
629
829
|
}
|
|
630
830
|
// Step 2 — host tokens.py (desktop host only; Linux sandboxes have no such path).
|
|
@@ -647,8 +847,8 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
|
|
|
647
847
|
}
|
|
648
848
|
}
|
|
649
849
|
// Step 3 — built-in Node equivalent of tokens.py: reads the local XiaoE
|
|
650
|
-
// client session and exchanges it for an eteamId, so
|
|
651
|
-
//
|
|
850
|
+
// client session and exchanges it for an eteamId, so Python availability
|
|
851
|
+
// no longer blocks unified login.
|
|
652
852
|
if (!credential && hasXiaoEClientCredential(env)) {
|
|
653
853
|
try {
|
|
654
854
|
credential = await resolveXiaoEClientCredential(baseUrl || DEFAULT_E10_BASE_URL, env);
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { CliError, ExitCode } from '../../core/errors.js';
|
|
2
|
-
import { E10AuthSession } from './auth/session.js';
|
|
3
|
-
import { hasXiaoEEnv, hasXiaoETokenScript, tryXiaoEUnifiedLogin } from './auth/xiaoe.js';
|
|
2
|
+
import { E10AuthSession, getActiveProfile, readProfileConfig } from './auth/session.js';
|
|
3
|
+
import { createXiaoEUnifiedLoginUnavailableError, hasXiaoEEnv, hasXiaoETokenScript, resolveXiaoEPreferredBaseUrl, shouldUseXiaoEBrowserFallback, tryXiaoEUnifiedLogin, } from './auth/xiaoe.js';
|
|
4
4
|
import { E10ApiClient } from './client.js';
|
|
5
5
|
function shouldPreferXiaoEUnifiedLogin(options, env) {
|
|
6
6
|
return !options.auth && !options.profile && (hasXiaoEEnv(env) || hasXiaoETokenScript(env));
|
|
7
7
|
}
|
|
8
|
-
export async function getE10Session(ctx) {
|
|
8
|
+
export async function getE10Session(ctx, load = {}) {
|
|
9
9
|
const options = ctx.getGlobalOptions();
|
|
10
10
|
if (shouldPreferXiaoEUnifiedLogin(options, ctx.env)) {
|
|
11
|
+
const profileBaseUrl = readProfileConfig(getActiveProfile()).baseUrl;
|
|
11
12
|
const xiaoELogin = await tryXiaoEUnifiedLogin({
|
|
12
13
|
env: ctx.env,
|
|
13
14
|
baseUrl: options.baseUrl,
|
|
15
|
+
profileBaseUrl,
|
|
16
|
+
allowBundledEnvScript: true,
|
|
14
17
|
allowTokenScript: true,
|
|
15
18
|
logger: ctx.logger,
|
|
16
19
|
});
|
|
@@ -18,11 +21,20 @@ export async function getE10Session(ctx) {
|
|
|
18
21
|
return xiaoELogin.session;
|
|
19
22
|
return null;
|
|
20
23
|
}
|
|
21
|
-
return E10AuthSession.fromOptions(options, ctx.cwd);
|
|
24
|
+
return E10AuthSession.fromOptions(options, ctx.cwd, load);
|
|
22
25
|
}
|
|
23
|
-
export async function requireE10Session(ctx) {
|
|
24
|
-
const session = await getE10Session(ctx);
|
|
26
|
+
export async function requireE10Session(ctx, load = {}) {
|
|
27
|
+
const session = await getE10Session(ctx, load);
|
|
25
28
|
if (!session) {
|
|
29
|
+
const options = ctx.getGlobalOptions();
|
|
30
|
+
if ((hasXiaoEEnv(ctx.env) || hasXiaoETokenScript(ctx.env)) && !shouldUseXiaoEBrowserFallback()) {
|
|
31
|
+
const baseUrl = resolveXiaoEPreferredBaseUrl({
|
|
32
|
+
explicitBaseUrl: options.baseUrl,
|
|
33
|
+
profileBaseUrl: readProfileConfig(getActiveProfile()).baseUrl,
|
|
34
|
+
env: ctx.env,
|
|
35
|
+
});
|
|
36
|
+
throw createXiaoEUnifiedLoginUnavailableError(baseUrl);
|
|
37
|
+
}
|
|
26
38
|
throw new CliError('E10 auth not found. In XiaoE, run weaver-work-cli auth xiaoe first; if unified login is unavailable, ask the user for the E10 login domain (base URL), then run: weaver-work-cli auth login --base-url <url> --agent_type <agent_type>.', ExitCode.AuthError);
|
|
27
39
|
}
|
|
28
40
|
return session;
|
|
@@ -28,10 +28,12 @@ export class E10RequestRuntime {
|
|
|
28
28
|
session;
|
|
29
29
|
interceptors;
|
|
30
30
|
fetchFn;
|
|
31
|
+
attachSessionTicket;
|
|
31
32
|
constructor(ctx, session, dependencies = {}) {
|
|
32
33
|
this.ctx = ctx;
|
|
33
34
|
this.session = session;
|
|
34
35
|
this.fetchFn = dependencies.fetch || globalThis.fetch;
|
|
36
|
+
this.attachSessionTicket = dependencies.attachSessionTicket === true;
|
|
35
37
|
this.interceptors = [
|
|
36
38
|
...(this.ctx.hooks?.getE10RequestInterceptors?.() || []),
|
|
37
39
|
...(dependencies.interceptors || []),
|
|
@@ -137,7 +139,7 @@ export class E10RequestRuntime {
|
|
|
137
139
|
langType: 'zh_CN',
|
|
138
140
|
timeZoneOffset: String(new Date().getTimezoneOffset()),
|
|
139
141
|
...request.headers,
|
|
140
|
-
Cookie: this.session.cookieHeader(),
|
|
142
|
+
Cookie: this.attachSessionTicket ? this.session.cookieHeaderWithSessionTicket() : this.session.cookieHeader(),
|
|
141
143
|
eteamsid: this.session.eteamsId,
|
|
142
144
|
'User-Agent': this.session.uaHeader(),
|
|
143
145
|
Origin: this.session.getBaseUrl(),
|
|
@@ -30,7 +30,7 @@ export async function verifyLease(args, action, env) {
|
|
|
30
30
|
}
|
|
31
31
|
const lease = await env.continuation.verify(args.continuation.trim(), action);
|
|
32
32
|
const session = await env.host.getSession();
|
|
33
|
-
if (lease.baseUrl !== session.baseUrl || lease.userId !== session.userId) {
|
|
33
|
+
if ((lease.baseUrl && lease.baseUrl !== session.baseUrl) || (lease.userId && lease.userId !== session.userId)) {
|
|
34
34
|
throw caizhilianValidationError('continuation_context_mismatch', '确认上下文已变化(环境或登录账号不同),请重新执行 prepare');
|
|
35
35
|
}
|
|
36
36
|
return lease;
|