weaver-work-cli 0.1.25 → 0.1.26

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.
Files changed (81) hide show
  1. package/README.md +30 -30
  2. package/dist/cmd/root.js +1 -0
  3. package/dist/core/runtime-agent.js +19 -0
  4. package/dist/internal/e10/auth/commands.js +47 -27
  5. package/dist/internal/e10/auth/xiaoe.js +62 -43
  6. package/dist/internal/e10/context.js +5 -27
  7. package/dist/shortcuts/archive-oa/constants.js +66 -0
  8. package/dist/shortcuts/archive-oa/continuation.js +70 -0
  9. package/dist/shortcuts/archive-oa/errors.js +53 -0
  10. package/dist/shortcuts/archive-oa/host.js +157 -0
  11. package/dist/shortcuts/archive-oa/index.js +225 -0
  12. package/dist/shortcuts/archive-oa/manifest.js +62 -0
  13. package/dist/shortcuts/archive-oa/operations/borrow-car.js +159 -0
  14. package/dist/shortcuts/archive-oa/operations/borrow-url.js +108 -0
  15. package/dist/shortcuts/archive-oa/operations/confirm.js +48 -0
  16. package/dist/shortcuts/archive-oa/operations/contract.js +120 -0
  17. package/dist/shortcuts/archive-oa/operations/locate.js +50 -0
  18. package/dist/shortcuts/archive-oa/operations/registry.js +40 -0
  19. package/dist/shortcuts/archive-oa/operations/shared.js +181 -0
  20. package/dist/shortcuts/archive-oa/operations/types.js +45 -0
  21. package/dist/shortcuts/archive-oa/operations.js +13 -0
  22. package/dist/shortcuts/index.js +2 -0
  23. package/dist/shortcuts/invoice/host.js +2 -1
  24. package/dist/shortcuts/invoice/manifest.js +111 -0
  25. package/dist/shortcuts/invoice/operations/issuing-qys.js +361 -0
  26. package/dist/shortcuts/invoice/operations/issuing.js +5 -5
  27. package/dist/shortcuts/invoice/operations/registry.js +4 -0
  28. package/dist/shortcuts/workflow/endpoints.js +11 -0
  29. package/dist/shortcuts/workflow/host.js +2 -2
  30. package/dist/shortcuts/workflow/index.js +6 -36
  31. package/dist/shortcuts/workflow/operations/chatscene.js +270 -0
  32. package/dist/shortcuts/workflow/operations/form.js +899 -158
  33. package/dist/shortcuts/workflow/operations/read.js +111 -6
  34. package/dist/shortcuts/workflow/operations/sign-input.js +106 -0
  35. package/dist/shortcuts/workflow/operations/write.js +9 -1
  36. package/dist/shortcuts/workflow/stdout-compact.js +9 -1
  37. package/docs/archive-oa.md +79 -0
  38. package/docs/e10-auth.md +7 -6
  39. package/package.json +1 -1
  40. package/skill-template/business-info.json +16 -0
  41. package/skill-template/domains/archive-oa.md +19 -0
  42. package/skill-template/domains/yepiaotong.md +2 -2
  43. package/skills/weaver-e10-archive-oa/SKILL.md +159 -0
  44. package/skills/weaver-e10-archive-oa/references/archive-locate.md +49 -0
  45. package/skills/weaver-e10-archive-oa/references/borrow-car.md +76 -0
  46. package/skills/weaver-e10-archive-oa/references/borrow-flow.md +53 -0
  47. package/skills/weaver-e10-archive-oa/references/contract-search.md +63 -0
  48. package/skills/weaver-e10-archive-oa/references/disabled-capabilities.md +29 -0
  49. package/skills/weaver-e10-archive-oa/references/qys-contract-search.md +47 -0
  50. package/skills/weaver-e10-archive-oa/references/source-manifest.json +400 -0
  51. package/skills/weaver-e10-builder/SKILL.md +22 -0
  52. package/skills/weaver-e10-caizhilian/SKILL.md +37 -15
  53. package/skills/weaver-e10-caizhilian/references/agent-entry.md +7 -3
  54. package/skills/weaver-e10-caizhilian/references/collaboration.md +5 -3
  55. package/skills/weaver-e10-caizhilian/references/document.md +33 -11
  56. package/skills/weaver-e10-caizhilian/references/file-collection.md +4 -3
  57. package/skills/weaver-e10-caizhilian/references/file-upload.md +2 -3
  58. package/skills/weaver-e10-caizhilian/references/files.md +9 -3
  59. package/skills/weaver-e10-caizhilian/references/folder.md +48 -16
  60. package/skills/weaver-e10-caizhilian/references/setting-attach.md +13 -39
  61. package/skills/weaver-e10-caizhilian/references/setting-backup.md +13 -51
  62. package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +20 -51
  63. package/skills/weaver-e10-caizhilian/references/setting-storage.md +82 -147
  64. package/skills/weaver-e10-caizhilian/references/withheld.md +20 -44
  65. package/skills/weaver-e10-shared/SKILL.md +22 -20
  66. package/skills/weaver-e10-shared/references/e10-auth-and-session.md +14 -15
  67. package/skills/weaver-e10-shared/references/non-interactive-environment.md +10 -10
  68. package/skills/weaver-e10-workflow/references/source-manifest.json +32 -22
  69. package/skills/weaver-e10-workflow/references/workflow-create.md +68 -28
  70. package/skills/weaver-e10-yepiaotong/SKILL.md +10 -7
  71. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +49 -1
  72. package/skills/weaver-e10-yepiaotong/references/invoice-issuing-qys-ledger.md +66 -0
  73. package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +59 -36
  74. package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +54 -509
  75. package/skills/weaver-e10-yepiaotong/references/reim-examples.md +182 -345
  76. package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +4 -5
  77. package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +153 -345
  78. package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +5 -5
  79. package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +182 -154
  80. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +96 -0
  81. package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +0 -119
package/README.md CHANGED
@@ -211,41 +211,41 @@ weaver-work-cli auth login --base-url "https://weapp.xxx.cn"
211
211
  weaver-work-cli auth oidc --base-url "https://weapp.xxx.cn"
212
212
  ```
213
213
 
214
- 只有当前智能体身份明确为 XiaoE 时,`auth xiaoe` 和未显式指定认证来源的业务命令才会优先使用平台注入的
215
- `XIAOE_USER_TOKEN` + `XIAOE_DOMAIN` 换取 ETEAMSID:其中 `XIAOE_DOMAIN` 是
216
- XiaoE/erpa 换票服务地址,不是 E10 登录地址。CLI 的 Node 实现会直接调用与
217
- `xiaoe_env.py` / `tokens.py` 相同的换票接口,并校验业务码和 `data.eteamId`;它保留
218
- 系统 CA 校验,且只允许同源重定向或同主机 HTTP→HTTPS 升级。平台凭证不可用时,
219
- CLI 会先在内部调用 npm 包自带的 `scripts/xiaoe_env.py` 兼容路径,再按顺序尝试
220
- 宿主 `xiaoeUserData/tokens.py`、桌面 XiaoE 客户端当前会话
221
- (优先读取宿主注入的 `XIAOE_USER_SETTINGS_PATH`,其次查找
222
- `XIAOE_ROBOT_DATA_ROOT` / `XIAOE_ROBOT_ROOT`,最后回退
223
- `XiaoE/Robots/<id>/user_settings.json`)和 `WEAVER_XIAOE_*` 环境映射。只要会话文件中的
224
- `session_id` 与 `erpa` 有效,Node 路径即可直接换票,不要求宿主存在 `tokens.py`。
225
- `xiaoe_env.py` 由 CLI 独占执行并解析,不放进 shared Skill,也不把 stdout 凭证暴露给
226
- Agent;Skill 只负责调用 `weaver-work-cli auth xiaoe`。CLI 自带脚本仅依赖 Python
227
- 标准库,仍保持 TLS 证书校验,不会采用关闭证书校验的回退。
228
-
229
- XiaoE 桌面客户端不一定注入上述 Token 对;它会注入 `XIAOE_DATA_ROOT`、
230
- `XIAOE_ROBOT_DATA_ROOT`、`XIAOE_USER_SETTINGS_PATH`、`XIAOE_ROBOT_ROOT` 等精确的
231
- 运行时路径标记,CLI 也把这些标记识别为当前 XiaoE 桌面宿主。普通 `XIAOE_SKILL_*`、
232
- `WEAVER_XIAOE_*` 配置或仅在磁盘上发现客户端文件仍不构成宿主身份。
233
-
234
- E10 目标地址按“显式 `--base-url` → 专用 `WEAVER_XIAOE_*` E10 配置/映射 →
235
- 当前 profile → `https://www.e-cology.com.cn`”解析,绝不会把 `XIAOE_DOMAIN`
236
- 当作 E10 地址。匹配成功时直接保存登录态,并固定使用 `agentType=XiaoE`。业务命令
237
- 在已确认的 XiaoE 智能体环境中且未显式指定 `--auth` / `--profile` 时,也会自动优先尝试 XiaoE 统一登录。
238
- `auth xiaoe` 失败后仅在 macOS/Windows 桌面宿主自动降级浏览器 OAuth;Web/Linux
239
- 环境会返回结构化的 `authentication/xiaoe_web_credential_unavailable` 错误,不再
240
- 启动无法回调的 localhost 浏览器登录。确需浏览器授权时,应在可接收本地回调的宿主
241
- 显式运行 `auth login`。也可以显式使用:
214
+ XiaoE 环境保留两种识别入口:桌面客户端由 Agent 根据宿主/任务的明确上下文判断,并在
215
+ 业务命令上传 `--runtime-agent xiaoe`;网页端沿用原有环境变量规则,成对的
216
+ `XIAOE_USER_TOKEN` + `XIAOE_DOMAIN` 会被识别为 XiaoE。判定为 XiaoE 后,Agent 显式执行:
242
217
 
243
218
  ```text
244
219
  weaver-work-cli auth xiaoe
245
220
  ```
246
221
 
247
- `auth login` / `auth oidc` 始终是纯浏览器 OAuth 入口。本机存在 `xiaoeUserData/tokens.py`、XiaoE 客户端目录、`XIAOE_SKILL_*` 或 `WEAVER_XIAOE_*` 配置,都不能单独证明当前智能体是 XiaoE,也不会让 WorkBuddy、Codex 或其他智能体切换到 XiaoE 登录。
248
- `auth xiaoe` 会先输出不含凭证的 preflight 摘要(`hostDetected`、`attempted`、`tokenScriptDetected`、`clientSettingsDetected`、`baseUrl`);命中 `tokens.py` 或内置 Node 客户端会话路径时还会输出明确的尝试日志。统一登录成功后,登录结果以及后续 `auth status` 会显示 `credentialSourceStep`、`credentialSourceType`、`credentialSourceLabel`,可据此确认实际凭证来源;`3 / xiaoe_client / XiaoE client settings` 表示未依赖 `tokens.py`,由 Node 直接换票。
222
+ `auth xiaoe` 本身就是认证方式的显式选择。`hasXiaoEEnv` 通过桌面端显式参数或网页端原有
223
+ 环境变量规则判断;工作目录、客户端文件或历史登录仍不作为依据。CLI 按顺序消费平台凭据、CLI 自带兼容脚本、宿主 `tokens.py`、
224
+ 桌面 XiaoE 客户端会话和 `WEAVER_XIAOE_*` 映射。`XIAOE_DOMAIN` 是 XiaoE/erpa 换票
225
+ 服务地址,不是 E10 地址。E10 目标按“显式 `--base-url` → 专用 `WEAVER_XIAOE_*`
226
+ E10 配置/映射 → 当前 profile → `https://www.e-cology.com.cn`”解析。
227
+
228
+ 统一登录失败时,所有操作系统都返回结构化的
229
+ `authentication/xiaoe_credential_unavailable`,不会自动打开浏览器或降级到
230
+ `auth login`。确需浏览器授权时,调用方必须另行显式执行 `auth login`。
231
+
232
+ 业务命令不会自动建立 XiaoE 登录态。桌面客户端需要 XiaoE 专属业务行为时,把全局参数
233
+ 放在业务子命令之前,例如:
234
+
235
+ ```text
236
+ weaver-work-cli --runtime-agent xiaoe workflow ...
237
+ ```
238
+
239
+ 网页端不要求补这个参数,仍由环境变量规则识别。`--runtime-agent` 只表示当前运行时类型,
240
+ 不携带也不选择凭据。`auth login` / `auth oidc`
241
+ 始终是纯浏览器 OAuth 入口,不会读取 XiaoE 凭据。
242
+
243
+ `auth xiaoe` 会先输出不含凭证的 preflight 摘要,显示 `authMode=xiaoe`、
244
+ `credentialSourceDetected`、`attempted` 及各凭据源检测结果;命中 `tokens.py` 或内置
245
+ Node 客户端会话路径时还会输出明确的尝试日志。统一登录成功后,登录结果以及后续
246
+ `auth status` 会显示 `credentialSourceStep`、`credentialSourceType`、
247
+ `credentialSourceLabel`,可据此确认实际凭证来源;`3 / xiaoe_client / XiaoE client settings`
248
+ 表示未依赖 `tokens.py`,由 Node 直接换票。
249
249
 
250
250
  默认本地回调地址为 `http://127.0.0.1:19800/callback`。如果端口被占用:
251
251
 
package/dist/cmd/root.js CHANGED
@@ -29,6 +29,7 @@ export function createProgram() {
29
29
  .option('--config <path>', 'use a custom weaver-work-cli config file')
30
30
  .option('--auth <path>', 'use a specific E10 auth file')
31
31
  .option('--profile <name>', 'use a specific E10 profile')
32
+ .option('--runtime-agent <name>', 'identify the current agent runtime (for example: xiaoe)')
32
33
  .option('--is-connector [value]', 'run as the WorkBuddy connector: use the fixed E10 profile managed by the connector')
33
34
  .option('--base-url <url>', 'override E10 base URL')
34
35
  .option('--passport-url <url>', 'override E10 passport URL');
@@ -0,0 +1,19 @@
1
+ const XIAOE_RUNTIME_AGENT_ALIASES = new Set([
2
+ 'xiaoe',
3
+ 'xiaoeclient',
4
+ '小e',
5
+ '小e客户端',
6
+ '智能小e',
7
+ '智能小e客户端',
8
+ ]);
9
+ export function normalizeRuntimeAgent(value) {
10
+ if (typeof value !== 'string')
11
+ return '';
12
+ return value.toLowerCase().replace(/[^a-z0-9\p{Script=Han}]+/gu, '');
13
+ }
14
+ export function isXiaoERuntimeAgent(value) {
15
+ return XIAOE_RUNTIME_AGENT_ALIASES.has(normalizeRuntimeAgent(value));
16
+ }
17
+ export function isXiaoERuntime(options) {
18
+ return isXiaoERuntimeAgent(options?.runtimeAgent);
19
+ }
@@ -5,8 +5,8 @@ import { CliError, ExitCode } from '../../../core/errors.js';
5
5
  import { clearInvoiceTokenForSession } from '../../../shortcuts/invoice/host.js';
6
6
  import { buildE10CookieHeader, E10AuthSession, clearProfileAuthFile, extractRawSetCookie, extractSessionTicket, findAuthFile, getActiveProfile, getProfileAuthPath, getProfileDir, listProfiles, profileNameFromUrl, readProfileConfig, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, switchActiveProfile, } from './session.js';
7
7
  import { getE10AuthRoot } from './paths.js';
8
- import { XIAOE_AGENT_TYPE, createXiaoEUnifiedLoginUnavailableError, hasXiaoEClientCredential, hasXiaoEEnv, hasXiaoETokenScript, resolveXiaoEPreferredBaseUrl, shouldUseXiaoEBrowserFallback, tryXiaoEUnifiedLogin as tryXiaoEUnifiedSessionLogin, } from './xiaoe.js';
9
- export { DEFAULT_E10_BASE_URL, XIAOE_AGENT_TYPE, hasXiaoEEnv, hasXiaoETokenScript, resolveXiaoEDefaultBaseUrl, resolveXiaoEPreferredBaseUrl, resolveXiaoEUnifiedLoginCredential, shouldUseXiaoEBrowserFallback, } from './xiaoe.js';
8
+ import { XIAOE_AGENT_TYPE, createXiaoEUnifiedLoginUnavailableError, hasXiaoEEnv, inspectXiaoEUnifiedLoginSources, resolveXiaoEPreferredBaseUrl, tryXiaoEUnifiedLogin as tryXiaoEUnifiedSessionLogin, } from './xiaoe.js';
9
+ export { DEFAULT_E10_BASE_URL, XIAOE_AGENT_TYPE, hasXiaoEEnv, hasXiaoETokenScript, hasXiaoEUnifiedLoginSource, inspectXiaoEUnifiedLoginSources, resolveXiaoEDefaultBaseUrl, resolveXiaoEPreferredBaseUrl, resolveXiaoEUnifiedLoginCredential, } from './xiaoe.js';
10
10
  const DEFAULT_AGENT_TYPE = 'weaver-work-cli';
11
11
  const EXPLICIT_AGENT_TYPE_ENV_KEYS = [
12
12
  'WORKBUDDY_AGENT_TYPE',
@@ -480,13 +480,16 @@ async function oidcExchange(code, redirectUri, baseUrl, tenantKeyFallback, agent
480
480
  version,
481
481
  };
482
482
  }
483
- function xiaoEUnifiedLoginUnavailableMessage(baseUrl, willOpenBrowser) {
483
+ function xiaoEUnifiedLoginUnavailableMessage(baseUrl, attempted) {
484
484
  return [
485
- willOpenBrowser
486
- ? '未获取到可用的 XiaoE 统一登录信息,准备降级打开网页登录。'
487
- : '未获取到可用的 XiaoE 统一登录信息;当前 Web/Linux 环境不支持本机浏览器回调,已停止自动降级。',
485
+ attempted
486
+ ? 'XiaoE 统一登录已尝试,但未获取到可用会话。'
487
+ : '未检测到可用的 XiaoE 统一登录凭据源,本次未尝试统一登录。',
488
+ '`auth xiaoe` 不会降级到浏览器 OAuth。',
488
489
  baseUrl ? `当前 E10 baseUrl: ${baseUrl}。` : '未从 XiaoE 凭证中解析到 E10 baseUrl。',
489
- 'CLI 已依次尝试平台注入凭证的 Node 换票、CLI 内置 xiaoe_env.py 兼容换票、宿主 xiaoeUserData/tokens.py、桌面 XiaoE 客户端凭证(XiaoE/Robots/*/user_settings.json)和 WEAVER_XIAOE_* 环境映射。',
490
+ attempted
491
+ ? 'CLI 已依次尝试平台注入凭证的 Node 换票、CLI 内置 xiaoe_env.py 兼容换票、宿主 xiaoeUserData/tokens.py、桌面 XiaoE 客户端凭证(XiaoE/Robots/*/user_settings.json)和 WEAVER_XIAOE_* 环境映射。'
492
+ : 'CLI 已检查平台注入凭证、宿主 xiaoeUserData/tokens.py、桌面 XiaoE 客户端凭证(XiaoE/Robots/*/user_settings.json)和 WEAVER_XIAOE_* 环境映射。',
490
493
  'CLI 会优先使用 XiaoE 客户端内置 Python 执行 tokens.py;自动发现不可用时,可用 WEAVER_XIAOE_TOKEN_SCRIPT_PYTHON 指定兼容解释器。',
491
494
  ].join(' ');
492
495
  }
@@ -496,30 +499,49 @@ async function runBrowserLogin(ctx, options, behavior = {}) {
496
499
  const profileFromGlobal = globalOptions.profile;
497
500
  const cfg = readProfileConfig(profileFromGlobal || getActiveProfile());
498
501
  const explicitBaseUrl = optionString(commandOpts, 'baseUrl') || globalOptions.baseUrl || '';
499
- const xiaoEHostDetected = hasXiaoEEnv(ctx.env);
500
- const useXiaoELogin = behavior.tryXiaoEUnifiedLogin === true && xiaoEHostDetected;
501
- const baseUrl = useXiaoELogin
502
- ? resolveXiaoEPreferredBaseUrl({
503
- explicitBaseUrl,
504
- profileBaseUrl: cfg.baseUrl,
502
+ const preferredXiaoEBaseUrl = resolveXiaoEPreferredBaseUrl({
503
+ explicitBaseUrl,
504
+ profileBaseUrl: cfg.baseUrl,
505
+ env: ctx.env,
506
+ });
507
+ const xiaoESources = behavior.tryXiaoEUnifiedLogin === true
508
+ ? inspectXiaoEUnifiedLoginSources({
505
509
  env: ctx.env,
510
+ baseUrl: preferredXiaoEBaseUrl,
511
+ profileBaseUrl: cfg.baseUrl,
512
+ allowTokenScript: behavior.allowXiaoETokenScript === true,
506
513
  })
514
+ : {
515
+ platformCredential: false,
516
+ tokenScript: false,
517
+ clientCredential: false,
518
+ environmentCredential: false,
519
+ any: false,
520
+ };
521
+ const useXiaoELogin = behavior.tryXiaoEUnifiedLogin === true && xiaoESources.any;
522
+ const baseUrl = behavior.tryXiaoEUnifiedLogin
523
+ ? preferredXiaoEBaseUrl
507
524
  : normalizeUrl(explicitBaseUrl || cfg.baseUrl || '');
508
525
  const tenantKey = optionString(commandOpts, 'tenantKey');
509
526
  const portText = optionString(commandOpts, 'port') || '19800';
510
527
  const port = Number.parseInt(portText, 10);
511
528
  const explicitAgentType = optionString(commandOpts, 'agentType', 'agent_type');
512
- const agentType = resolveAgentType(commandOpts, ctx.env);
529
+ const agentType = useXiaoELogin
530
+ ? optionString(commandOpts, 'agentType', 'agent_type') || XIAOE_AGENT_TYPE
531
+ : resolveAgentType(commandOpts, ctx.env);
513
532
  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
514
533
  throw new CliError('Invalid --port value', ExitCode.UsageError);
515
534
  }
516
535
  if (behavior.tryXiaoEUnifiedLogin) {
517
536
  ctx.logger.info([
518
537
  'XiaoE unified login preflight:',
519
- `hostDetected=${String(xiaoEHostDetected)}`,
538
+ 'authMode=xiaoe',
539
+ `credentialSourceDetected=${String(xiaoESources.any)}`,
520
540
  `attempted=${String(useXiaoELogin)}`,
521
- `tokenScriptDetected=${String(hasXiaoETokenScript(ctx.env))}`,
522
- `clientSettingsDetected=${String(hasXiaoEClientCredential(ctx.env))}`,
541
+ `platformCredentialDetected=${String(xiaoESources.platformCredential)}`,
542
+ `tokenScriptDetected=${String(xiaoESources.tokenScript)}`,
543
+ `clientSettingsDetected=${String(xiaoESources.clientCredential)}`,
544
+ `environmentCredentialDetected=${String(xiaoESources.environmentCredential)}`,
523
545
  `baseUrl=${baseUrl || '(unresolved)'}`,
524
546
  ].join(' '));
525
547
  }
@@ -528,11 +550,9 @@ async function runBrowserLogin(ctx, options, behavior = {}) {
528
550
  if (xiaoEResult)
529
551
  return xiaoEResult;
530
552
  }
531
- const willOpenBrowser = !useXiaoELogin
532
- || behavior.browserFallback !== 'desktop-only'
533
- || shouldUseXiaoEBrowserFallback();
534
- if (useXiaoELogin && behavior.explainXiaoEFallback) {
535
- ctx.logger.warn(xiaoEUnifiedLoginUnavailableMessage(baseUrl, willOpenBrowser));
553
+ const willOpenBrowser = behavior.browserFallback !== 'never';
554
+ if (behavior.tryXiaoEUnifiedLogin && behavior.explainXiaoEFallback) {
555
+ ctx.logger.warn(xiaoEUnifiedLoginUnavailableMessage(baseUrl, useXiaoELogin));
536
556
  }
537
557
  if (!willOpenBrowser) {
538
558
  throw createXiaoEUnifiedLoginUnavailableError(baseUrl);
@@ -714,15 +734,15 @@ function formatWhoami(title, data) {
714
734
  ].join('\n');
715
735
  }
716
736
  function registerBrowserLoginCommand(auth, action, name, description, behavior = {}) {
717
- auth.command(name)
737
+ const command = auth.command(name)
718
738
  .description(description)
719
739
  .option('--base-url <url>', 'E10 base URL')
720
740
  .option('--tenant-key <key>', 'Tenant key fallback after login')
721
741
  .option('--port <n>', 'Local OAuth callback port', '19800')
722
742
  .option('--agent_type <value>', 'Agent type sent to E10 authorize endpoint')
723
743
  .option('--agent-type <value>', 'Alias of --agent_type')
724
- .option('--agent-return-url <url>', 'Desktop application URL linked by the login success page')
725
- .action(action(async (ctx, options) => {
744
+ .option('--agent-return-url <url>', 'Desktop application URL linked by the login success page');
745
+ command.action(action(async (ctx, options) => {
726
746
  const result = await runBrowserLogin(ctx, options, behavior);
727
747
  ctx.output.write(result, { text: formatLoginResult(result) });
728
748
  }));
@@ -808,11 +828,11 @@ export function registerE10AuthCommands(parent, action) {
808
828
  }));
809
829
  registerBrowserLoginCommand(auth, action, 'login', 'Open browser OAuth2.0 login and save E10 auth');
810
830
  registerBrowserLoginCommand(auth, action, 'oidc', 'Alias of auth login for OAuth2.0 browser login');
811
- registerBrowserLoginCommand(auth, action, 'xiaoe', 'Try XiaoE unified login and explain before browser OAuth fallback', {
831
+ registerBrowserLoginCommand(auth, action, 'xiaoe', 'Use XiaoE unified login without browser OAuth fallback', {
812
832
  tryXiaoEUnifiedLogin: true,
813
833
  allowXiaoETokenScript: true,
814
834
  explainXiaoEFallback: true,
815
- browserFallback: 'desktop-only',
835
+ browserFallback: 'never',
816
836
  });
817
837
  auth.command('logout')
818
838
  .alias('clear')
@@ -4,6 +4,7 @@ import { homedir } from 'node:os';
4
4
  import { dirname, join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { CliError, ExitCode } from '../../../core/errors.js';
7
+ import { isXiaoERuntime, isXiaoERuntimeAgent } from '../../../core/runtime-agent.js';
7
8
  import { buildE10CookieHeader, E10AuthSession, extractRawSetCookie, extractSessionTicket, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
8
9
  export const DEFAULT_E10_BASE_URL = 'https://www.e-cology.com.cn';
9
10
  export const XIAOE_AGENT_TYPE = 'XiaoE';
@@ -52,12 +53,6 @@ const XIAOE_CLIENT_SETTINGS_ROOT_ENV_KEYS = [
52
53
  'XIAOE_ROBOT_DATA_ROOT',
53
54
  'XIAOE_ROBOT_ROOT',
54
55
  ];
55
- const XIAOE_DESKTOP_HOST_ENV_KEYS = [
56
- 'XIAOE_DATA_ROOT',
57
- 'XIAOE_ROBOT_DATA_ROOT',
58
- 'XIAOE_USER_SETTINGS_PATH',
59
- 'XIAOE_ROBOT_ROOT',
60
- ];
61
56
  export const XIAOE_SKILL_ETEAMID_API_PATH = '/spec-apps/api/app/rpachat/skill/extend/eteamId';
62
57
  export const XIAOE_CLIENT_SETTINGS_SOURCE = 'XiaoE/Robots/user_settings.json';
63
58
  export const XIAOE_USER_TOKEN_SOURCE = 'XIAOE_USER_TOKEN';
@@ -70,36 +65,29 @@ function envString(env, ...keys) {
70
65
  }
71
66
  return '';
72
67
  }
73
- export function hasXiaoEEnv(env = process.env) {
74
- const isXiaoEAgentType = (value) => {
75
- const normalized = value.toLowerCase().replace(/[^a-z0-9\p{Script=Han}]+/gu, '');
76
- return ['xiaoe', 'xiaoeclient', '小e', '智能小e'].includes(normalized);
77
- };
78
- // A host-specific identity is authoritative. Generic WORKBUDDY_*/CODEX_*
79
- // variables are only framework markers and can also be present when XiaoE
80
- // launches the CLI through a shared agent runtime.
81
- const explicitHostAgentType = envString(env, 'WORKBUDDY_AGENT_TYPE', 'CODEX_AGENT_TYPE');
82
- if (explicitHostAgentType)
83
- return isXiaoEAgentType(explicitHostAgentType);
84
- // XiaoE injects this pair into the command process for the current run. A
85
- // tokens.py path, client installation, XIAOE_SKILL_* variable, or
86
- // WEAVER_XIAOE_* configuration alone is credential availability, not proof
87
- // that the current agent is XiaoE.
88
- if (hasXiaoEPlatformCredential(env))
68
+ /**
69
+ * Identifies the XiaoE runtime without inspecting credential artifacts:
70
+ * desktop callers may pass --runtime-agent, and any injected XIAOE_* variable
71
+ * is treated as XiaoE host evidence.
72
+ */
73
+ export function hasXiaoEEnv(env = process.env, options = {}) {
74
+ if (isXiaoERuntime(options))
89
75
  return true;
90
- // The desktop XiaoE client injects these exact runtime paths even when it
91
- // does not inject the web/sandbox XIAOE_USER_TOKEN + XIAOE_DOMAIN pair.
92
- // They identify the current command host; unrelated XIAOE_SKILL_* variables
93
- // and merely discovering local XiaoE files still do not.
94
- if (XIAOE_DESKTOP_HOST_ENV_KEYS.some((key) => Boolean(envString(env, key))))
76
+ // XiaoE reserves this prefix for host-injected runtime variables. Presence
77
+ // of any matching key is sufficient; the value may legitimately be empty.
78
+ if (Object.keys(env).some((key) => key.startsWith('XIAOE_')))
95
79
  return true;
96
- // Current-host framework markers win over WEAVER_AGENT_TYPE, which can be
97
- // inherited from a previously saved E10 profile by `weaver-work-cli run`.
80
+ // An explicitly named host remains authoritative over credential signals.
81
+ const explicitHostAgentType = envString(env, 'WORKBUDDY_AGENT_TYPE', 'CODEX_AGENT_TYPE');
82
+ if (explicitHostAgentType)
83
+ return isXiaoERuntimeAgent(explicitHostAgentType);
84
+ // Generic framework markers can coexist with stale profile metadata, so they
85
+ // take precedence over WEAVER_AGENT_TYPE when no XIAOE_* variable is present.
98
86
  if (Object.keys(env).some((key) => key.startsWith('WORKBUDDY_') || key.startsWith('CODEX_'))) {
99
87
  return false;
100
88
  }
101
89
  const explicitAgentType = envString(env, 'WEAVER_AGENT_TYPE');
102
- return explicitAgentType ? isXiaoEAgentType(explicitAgentType) : false;
90
+ return explicitAgentType ? isXiaoERuntimeAgent(explicitAgentType) : false;
103
91
  }
104
92
  export function describeXiaoECredentialSource(source) {
105
93
  // Step 1 is the platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN):
@@ -311,7 +299,7 @@ export function resolveXiaoEDefaultBaseUrl(env = process.env) {
311
299
  const configured = resolveXiaoEConfiguredBaseUrl(env);
312
300
  if (configured)
313
301
  return configured;
314
- return hasXiaoEEnv(env) ? DEFAULT_E10_BASE_URL : '';
302
+ return DEFAULT_E10_BASE_URL;
315
303
  }
316
304
  function uniqueStrings(values) {
317
305
  const seen = new Set();
@@ -350,10 +338,12 @@ export function xiaoEBundledEnvScriptPath() {
350
338
  const scriptPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../scripts/xiaoe_env.py');
351
339
  return existsSync(scriptPath) ? scriptPath : '';
352
340
  }
353
- export function hasXiaoETokenScript(env = process.env) {
341
+ export function hasXiaoETokenScript(env = process.env, allowLocalDiscovery = true) {
354
342
  const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY, XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY);
355
343
  if (explicit)
356
344
  return existsSync(explicit);
345
+ if (!allowLocalDiscovery)
346
+ return false;
357
347
  const scriptPath = xiaoETokenScriptPath(env);
358
348
  return Boolean(scriptPath && existsSync(scriptPath));
359
349
  }
@@ -620,7 +610,7 @@ function sortedXiaoEClientSettingsFiles(root) {
620
610
  const files = listXiaoEClientSettingsFiles(root);
621
611
  return files.sort((left, right) => statSync(right).mtimeMs - statSync(left).mtimeMs);
622
612
  }
623
- function xiaoEClientSettingsCandidateFiles(env) {
613
+ function xiaoEClientSettingsCandidateFiles(env, allowLocalDiscovery = true) {
624
614
  const candidates = [];
625
615
  const explicitSettingsPath = envString(env, XIAOE_USER_SETTINGS_PATH_ENV_KEY);
626
616
  if (isFile(explicitSettingsPath))
@@ -629,7 +619,9 @@ function xiaoEClientSettingsCandidateFiles(env) {
629
619
  const injectedRoot = envString(env, key);
630
620
  candidates.push(...sortedXiaoEClientSettingsFiles(injectedRoot));
631
621
  }
632
- candidates.push(...sortedXiaoEClientSettingsFiles(xiaoEClientRobotsRoot(env)));
622
+ if (allowLocalDiscovery) {
623
+ candidates.push(...sortedXiaoEClientSettingsFiles(xiaoEClientRobotsRoot(env)));
624
+ }
633
625
  return uniqueStrings(candidates);
634
626
  }
635
627
  export function findXiaoEClientSettingsFile(env = process.env) {
@@ -674,8 +666,38 @@ export function readXiaoEClientCredential(env = process.env) {
674
666
  }
675
667
  return null;
676
668
  }
677
- export function hasXiaoEClientCredential(env = process.env) {
678
- return Boolean(findXiaoEClientSettingsFile(env));
669
+ export function hasXiaoEClientCredential(env = process.env, allowLocalDiscovery = true) {
670
+ return Boolean(xiaoEClientSettingsCandidateFiles(env, allowLocalDiscovery)[0]);
671
+ }
672
+ /**
673
+ * Reports usable unified-login credential sources independently from host
674
+ * identity. Installing the XiaoE client is not proof that the current process
675
+ * was launched by XiaoE, but an explicit `auth xiaoe` command may still consume
676
+ * any credential source that is available.
677
+ */
678
+ export function inspectXiaoEUnifiedLoginSources(options = {}) {
679
+ const env = options.env || process.env;
680
+ const baseUrl = resolveXiaoEPreferredBaseUrl({
681
+ explicitBaseUrl: options.baseUrl,
682
+ profileBaseUrl: options.profileBaseUrl,
683
+ env,
684
+ });
685
+ const platformCredential = hasXiaoEPlatformCredential(env);
686
+ const allowLocalDiscovery = options.allowLocalDiscovery !== false;
687
+ const tokenScript = options.allowTokenScript !== false
688
+ && hasXiaoETokenScript(env, allowLocalDiscovery);
689
+ const clientCredential = hasXiaoEClientCredential(env, allowLocalDiscovery);
690
+ const environmentCredential = Boolean(baseUrl && resolveXiaoEUnifiedLoginCredential(baseUrl, env));
691
+ return {
692
+ platformCredential,
693
+ tokenScript,
694
+ clientCredential,
695
+ environmentCredential,
696
+ any: platformCredential || tokenScript || clientCredential || environmentCredential,
697
+ };
698
+ }
699
+ export function hasXiaoEUnifiedLoginSource(options = {}) {
700
+ return inspectXiaoEUnifiedLoginSources(options).any;
679
701
  }
680
702
  async function fetchXiaoEClientEteamId(credential, domain) {
681
703
  const controller = new AbortController();
@@ -769,22 +791,19 @@ export function resolveXiaoEPreferredBaseUrl(options) {
769
791
  const profileBaseUrl = normalizeUrl(options.profileBaseUrl || '');
770
792
  if (profileBaseUrl)
771
793
  return profileBaseUrl;
772
- return hasXiaoEEnv(env) ? DEFAULT_E10_BASE_URL : '';
773
- }
774
- export function shouldUseXiaoEBrowserFallback(platform = process.platform) {
775
- return platform === 'darwin' || platform === 'win32';
794
+ return DEFAULT_E10_BASE_URL;
776
795
  }
777
796
  export function createXiaoEUnifiedLoginUnavailableError(baseUrl = '') {
778
797
  const target = normalizeUrl(baseUrl);
779
798
  const message = [
780
799
  'XiaoE unified login did not return a usable E10 session.',
781
800
  target ? `Resolved E10 baseUrl: ${target}.` : 'No E10 baseUrl could be resolved.',
782
- 'This web/Linux host cannot complete a localhost browser OAuth callback.',
801
+ 'The auth xiaoe command never falls back to browser OAuth.',
783
802
  'Refresh the XiaoE web session or restore host credential injection before retrying.',
784
803
  ].join(' ');
785
804
  return Object.assign(new CliError(message, ExitCode.AuthError), {
786
805
  type: 'authentication',
787
- subtype: 'xiaoe_web_credential_unavailable',
806
+ subtype: 'xiaoe_credential_unavailable',
788
807
  retryable: false,
789
808
  stage: 'xiaoe_unified_login',
790
809
  });
@@ -865,7 +884,7 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
865
884
  profileBaseUrl: options.profileBaseUrl,
866
885
  env,
867
886
  });
868
- const mayUseTokenScript = options.allowTokenScript || hasXiaoEEnv(env);
887
+ const mayUseTokenScript = options.allowTokenScript === true;
869
888
  if (!baseUrl && !mayUseTokenScript)
870
889
  return null;
871
890
  let credential = null;
@@ -1,41 +1,19 @@
1
1
  import { CliError, ExitCode } from '../../core/errors.js';
2
- import { E10AuthSession, getActiveProfile, readProfileConfig } from './auth/session.js';
3
- import { createXiaoEUnifiedLoginUnavailableError, hasXiaoEEnv, resolveXiaoEPreferredBaseUrl, shouldUseXiaoEBrowserFallback, tryXiaoEUnifiedLogin, } from './auth/xiaoe.js';
2
+ import { E10AuthSession } from './auth/session.js';
3
+ import { hasXiaoEEnv } from './auth/xiaoe.js';
4
4
  import { E10ApiClient } from './client.js';
5
- function shouldPreferXiaoEUnifiedLogin(options, env) {
6
- return !options.auth && !options.profile && hasXiaoEEnv(env);
7
- }
8
5
  export async function getE10Session(ctx, load = {}) {
9
6
  const options = ctx.getGlobalOptions();
10
- if (shouldPreferXiaoEUnifiedLogin(options, ctx.env)) {
11
- const profileBaseUrl = readProfileConfig(getActiveProfile()).baseUrl;
12
- const xiaoELogin = await tryXiaoEUnifiedLogin({
13
- env: ctx.env,
14
- baseUrl: options.baseUrl,
15
- profileBaseUrl,
16
- allowBundledEnvScript: true,
17
- allowTokenScript: true,
18
- logger: ctx.logger,
19
- });
20
- if (xiaoELogin)
21
- return xiaoELogin.session;
22
- return null;
23
- }
24
7
  return E10AuthSession.fromOptions(options, ctx.cwd, load);
25
8
  }
26
9
  export async function requireE10Session(ctx, load = {}) {
27
10
  const session = await getE10Session(ctx, load);
28
11
  if (!session) {
29
12
  const options = ctx.getGlobalOptions();
30
- if (hasXiaoEEnv(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);
13
+ if (hasXiaoEEnv(ctx.env, options)) {
14
+ throw new CliError('E10 auth not found. The current runtime is XiaoE; run weaver-work-cli auth xiaoe first.', ExitCode.AuthError);
37
15
  }
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);
16
+ throw new CliError('E10 auth not found. 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);
39
17
  }
40
18
  return session;
41
19
  }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * archive-oa 固定业务参数。
3
+ *
4
+ * 泛微商务合同与契约锁协作采纳是两套独立的应用/列表,参数值不同,禁止交叉混用。
5
+ * 来源:`source/weaver-e10-archive-oa.zip` 内 10 篇接口文档(catalog.md 登记)。
6
+ */
7
+ /** 加入借阅车 / 发起借阅的单次条数上限(接口侧限制)。 */
8
+ export const MAX_BORROW_ITEMS = 6;
9
+ /** 发起借阅流程创建页的流程 ID(前端 `flowpage/fullCreate/<flowId>`)。 */
10
+ export const BORROW_FLOW_ID = '100003460000002090';
11
+ /** 借阅车记录 `fb` 即界面「分部」列:0 = 泛微、1 = 契约锁。 */
12
+ export const FLOW_FB_LABELS = {
13
+ '0': '泛微',
14
+ '1': '契约锁',
15
+ };
16
+ /** 档案系统:按合同号定位档案的自定义接口标识。 */
17
+ export const ARCHIVE_LOCATE_INTERFACE_PK = 'getArcIdByContractNo';
18
+ export const ARCHIVE_LOCATE_PATH = `/api/ebuilder/form/formdata/v2/getFormDataList/${ARCHIVE_LOCATE_INTERFACE_PK}`;
19
+ /** 泛微商务合同(案例台账)。 */
20
+ export const WEAVER_CONTRACT = {
21
+ source: 'weaver',
22
+ passId: '850741972510941185',
23
+ listId: '850741985437786120',
24
+ filterListId: '850741985437786120',
25
+ cusMenuId: '5408862605522555751',
26
+ urlPageTitle: '5rOb5b6u5qGI5L6L',
27
+ };
28
+ export const WEAVER_BORROW_CAR = {
29
+ source: 'weaver',
30
+ passId: '882393476189437952',
31
+ listId: '882393480417296390',
32
+ cusMenuId: '5408862605522555751',
33
+ urlPageTitle: '5rOb5b6u5qGI5L6L',
34
+ };
35
+ export const WEAVER_ESB_ADD = {
36
+ source: 'weaver',
37
+ esbActionIds: '882395911377174530',
38
+ objId: '850741972510941185',
39
+ listId: '850741985437786120',
40
+ successText: '【加入借阅车】执行成功!',
41
+ };
42
+ /** 契约锁协作采纳(用户常称「案例」)。 */
43
+ export const QYS_CONTRACT = {
44
+ source: 'qys',
45
+ passId: '850742853046345728',
46
+ listId: '1190611964371984450',
47
+ cusMenuId: '5408862605522555752',
48
+ urlPageTitle: '5aWR57qm6ZSB5qGI5L6L',
49
+ };
50
+ export const QYS_BORROW_CAR = {
51
+ source: 'qys',
52
+ passId: '882393476189437952',
53
+ listId: '883819516961857839',
54
+ cusMenuId: '5408862605522555752',
55
+ urlPageTitle: '5aWR57qm6ZSB5qGI5L6L',
56
+ };
57
+ export const QYS_ESB_ADD = {
58
+ source: 'qys',
59
+ esbActionIds: '892706856573648911',
60
+ objId: '850742853046345728',
61
+ listId: '1190611964371984450',
62
+ successText: '【契约锁加入借阅车】执行成功!',
63
+ };
64
+ /** 契约锁默认过滤「处理进度 = 已归档」。 */
65
+ export const QYS_DEFAULT_ARCHIVED_FIELD = 'cljd';
66
+ export const QYS_DEFAULT_ARCHIVED_VALUE = '已归档';
@@ -0,0 +1,70 @@
1
+ import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { existsSync } from 'node:fs';
3
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import { resolve } from 'node:path';
6
+ import { archiveOaValidationError } from './errors.js';
7
+ function stateRoot(ctx) {
8
+ const configured = ctx?.config?.path();
9
+ if (configured)
10
+ return resolve(configured, '..');
11
+ return process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
12
+ }
13
+ async function loadKey(ctx) {
14
+ const keysDir = resolve(stateRoot(ctx), 'keys');
15
+ const filename = resolve(keysDir, 'archive-oa-continuation.key');
16
+ await mkdir(keysDir, { recursive: true, mode: 0o700 });
17
+ if (existsSync(filename))
18
+ return readFile(filename);
19
+ const key = randomBytes(32);
20
+ try {
21
+ await writeFile(filename, key, { flag: 'wx', mode: 0o600 });
22
+ if (process.platform !== 'win32')
23
+ await chmod(filename, 0o600).catch(() => undefined);
24
+ return key;
25
+ }
26
+ catch {
27
+ return readFile(filename);
28
+ }
29
+ }
30
+ function encode(value) {
31
+ return Buffer.from(JSON.stringify(value)).toString('base64url');
32
+ }
33
+ function signature(payload, key) {
34
+ return createHmac('sha256', key).update(payload).digest('base64url');
35
+ }
36
+ export function createArchiveOaContinuationSigner(ctx) {
37
+ return {
38
+ async issue(payload, ttlMs = 10 * 60_000) {
39
+ const issuedAt = Date.now();
40
+ const body = encode({ ...payload, issuedAt, expiresAt: issuedAt + ttlMs });
41
+ const sig = signature(body, await loadKey(ctx));
42
+ return `${body}.${sig}`;
43
+ },
44
+ async verify(token, expectedOperation) {
45
+ if (typeof token !== 'string' || !token.includes('.')) {
46
+ throw archiveOaValidationError('continuation_invalid', 'continuation 无效');
47
+ }
48
+ const [body, provided] = token.split('.');
49
+ const expected = signature(body, await loadKey(ctx));
50
+ const valid = provided.length === expected.length
51
+ && timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
52
+ if (!valid)
53
+ throw archiveOaValidationError('continuation_invalid', 'continuation 签名校验失败');
54
+ let value;
55
+ try {
56
+ value = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
57
+ }
58
+ catch {
59
+ throw archiveOaValidationError('continuation_invalid', 'continuation 内容无法解析');
60
+ }
61
+ if (value.operation !== expectedOperation) {
62
+ throw archiveOaValidationError('continuation_mismatch', 'continuation 不属于当前操作');
63
+ }
64
+ if (!Number.isSafeInteger(value.expiresAt) || value.expiresAt < Date.now()) {
65
+ throw archiveOaValidationError('continuation_expired', 'continuation 已过期');
66
+ }
67
+ return value;
68
+ },
69
+ };
70
+ }