weaver-work-cli 0.1.26 → 0.1.28
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 -2
- package/dist/cmd/setup/index.js +1 -1
- package/dist/internal/e10/auth/commands.js +1 -6
- package/dist/internal/e10/auth/messages.js +3 -0
- package/dist/internal/e10/auth/session.js +8 -0
- package/dist/internal/e10/auth-recovery.js +148 -0
- package/dist/internal/e10/client.js +47 -15
- package/dist/internal/e10/context.js +8 -8
- package/dist/internal/e10/index.js +1 -0
- package/dist/internal/e10/request-runtime.js +51 -9
- package/dist/shortcuts/archive/host.js +3 -2
- package/dist/shortcuts/archive/manifest.js +1 -1
- package/dist/shortcuts/archive-oa/host.js +2 -1
- package/dist/shortcuts/archive-oa/manifest.js +1 -1
- package/dist/shortcuts/asset/host.js +7 -6
- package/dist/shortcuts/blog/host.js +4 -3
- package/dist/shortcuts/caizhilian/host.js +3 -2
- package/dist/shortcuts/caizhilian/manifest.js +1 -1
- package/dist/shortcuts/calendar/host.js +2 -1
- package/dist/shortcuts/contract/host.js +34 -59
- package/dist/shortcuts/ehr/host.js +2 -1
- package/dist/shortcuts/esb/host.js +2 -1
- package/dist/shortcuts/fna/host.js +6 -3
- package/dist/shortcuts/fna/manifest.js +10 -5
- package/dist/shortcuts/fna/operations/expense-chart.js +162 -0
- package/dist/shortcuts/fna/operations/registry.js +2 -0
- package/dist/shortcuts/hrm/host.js +2 -1
- package/dist/shortcuts/im/host.js +3 -2
- package/dist/shortcuts/invoice/host.js +7 -6
- package/dist/shortcuts/invoice/manifest.js +21 -141
- package/dist/shortcuts/invoice/operations/issuing-qys.js +134 -167
- package/dist/shortcuts/invoice/operations/registry.js +1 -6
- package/dist/shortcuts/jiuchuanhui/host.js +2 -1
- package/dist/shortcuts/mail/host.js +2 -1
- package/dist/shortcuts/meeting/host.js +2 -1
- package/dist/shortcuts/meeting/operations/eb/conflict.js +78 -12
- package/dist/shortcuts/okr/host.js +2 -1
- package/dist/shortcuts/plan/host.js +2 -1
- package/dist/shortcuts/project/host.js +2 -1
- package/dist/shortcuts/workflow/host.js +3 -5
- package/dist/shortcuts/workflow/index.js +1 -1
- package/dist/shortcuts/workflow/operations/chatscene.js +4 -22
- package/dist/shortcuts/workflow/operations/form.js +281 -166
- package/dist/shortcuts/workflow/operations/read.js +26 -2
- package/dist/shortcuts/workflow/stdout-compact.js +7 -0
- package/docs/SKILL.md +1 -1
- package/docs/_catalog.md +1 -1
- package/docs/agent-invoice.md +1 -1
- package/docs/caizhilian.md +1 -1
- package/docs/e10-auth.md +1 -1
- package/docs/fna.md +7 -3
- package/docs/invoice.md +4 -6
- package/package.json +1 -1
- package/skill-template/business-info.json +4 -1
- package/skill-template/domains/caizhilian.md +2 -1
- package/skill-template/domains/qiyecheng.md +1 -0
- package/skill-template/domains/yepiaotong.md +6 -2
- package/skill-template/skill-template.md +1 -0
- package/skills/weaver-e10-blog/SKILL.md +6 -1
- package/skills/weaver-e10-blog/references/blog-query.md +3 -1
- package/skills/weaver-e10-caizhilian/SKILL.md +18 -9
- package/skills/weaver-e10-caizhilian/references/action-flow.md +70 -0
- package/skills/weaver-e10-caizhilian/references/agent-entry.md +6 -2
- package/skills/weaver-e10-caizhilian/references/auth-and-install.md +207 -0
- package/skills/weaver-e10-caizhilian/references/file-collection.md +56 -54
- package/skills/weaver-e10-caizhilian/references/file-upload.md +4 -4
- package/skills/weaver-e10-caizhilian/references/host-executor-prompt.md +32 -0
- package/skills/weaver-e10-caizhilian/references/host-platform-integration.md +46 -0
- package/skills/weaver-e10-caizhilian/references/rule-index.md +56 -0
- package/skills/weaver-e10-caizhilian/references/setting-attach.md +5 -16
- package/skills/weaver-e10-caizhilian/references/setting-backup.md +4 -13
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +6 -15
- package/skills/weaver-e10-caizhilian/references/setting-storage-docs-summary.md +126 -0
- package/skills/weaver-e10-caizhilian/references/setting-storage-field-map.md +57 -0
- package/skills/weaver-e10-caizhilian/references/setting-storage.md +142 -482
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +7 -7
- package/skills/weaver-e10-caizhilian/references/task-workflow.md +87 -0
- package/skills/weaver-e10-caizhilian/references/withheld.md +2 -2
- package/skills/weaver-e10-caizhilian/references/write-protocol.md +7 -0
- package/skills/weaver-e10-calendar/SKILL.md +6 -1
- package/skills/weaver-e10-esb/SKILL.md +8 -0
- package/skills/weaver-e10-esb/references/input-format.md +3 -1
- package/skills/weaver-e10-esb/references/source-manifest.json +5 -5
- package/skills/weaver-e10-hrm/SKILL.md +6 -1
- package/skills/weaver-e10-hrm/references/employee.md +1 -1
- package/skills/weaver-e10-hrm/references/org.md +1 -1
- package/skills/weaver-e10-hrm/references/position.md +1 -1
- package/skills/weaver-e10-jinchengda/SKILL.md +2 -1
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +1 -1
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +6 -1
- package/skills/weaver-e10-jucailin/SKILL.md +6 -1
- package/skills/weaver-e10-jucailin/references/hr-contact.md +1 -1
- package/skills/weaver-e10-jucailin/references/salary.md +1 -1
- package/skills/weaver-e10-mail/SKILL.md +6 -1
- package/skills/weaver-e10-meeting/SKILL.md +11 -6
- package/skills/weaver-e10-meeting/references/meeting-eb.md +7 -3
- package/skills/weaver-e10-meeting/references/meeting-queries.md +3 -1
- package/skills/weaver-e10-meeting/references/meeting-verification.md +4 -2
- package/skills/weaver-e10-okr/SKILL.md +6 -1
- package/skills/weaver-e10-okr/references/source-manifest.json +5 -5
- package/skills/weaver-e10-plan/SKILL.md +6 -1
- package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +3 -1
- package/skills/weaver-e10-plan/references/plan-report-query.md +3 -1
- package/skills/weaver-e10-plan/references/source-manifest.json +7 -7
- package/skills/weaver-e10-qiyecheng/SKILL.md +16 -6
- package/skills/weaver-e10-qiyecheng/references/catalog.md +6 -3
- package/skills/weaver-e10-qiyecheng/references/expense-center.md +2 -0
- package/skills/weaver-e10-qiyecheng/references/expense-chart.md +78 -0
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +87 -33
- package/skills/weaver-e10-shared/SKILL.md +37 -14
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +11 -8
- package/skills/weaver-e10-shared/references/non-interactive-environment.md +4 -4
- package/skills/weaver-e10-shared/references/weaver-e10-installation.md +3 -1
- package/skills/weaver-e10-wenshuding/SKILL.md +6 -1
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +3 -3
- package/skills/weaver-e10-workflow/references/source-manifest.json +1 -2
- package/skills/weaver-e10-workflow/references/workflow-create.md +30 -55
- package/skills/weaver-e10-yepiaotong/SKILL.md +19 -17
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +72 -78
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +152 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +210 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +7 -7
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +11 -5
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +55 -33
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +102 -23
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +1 -1
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +53 -42
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +93 -213
- package/skills/weaver-e10-yimiaoban/SKILL.md +9 -2
- package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +1 -1
- package/skills/weaver-e10-yimiaoban/references/msg-read.md +1 -1
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +9 -9
- package/skills/weaver-e10-ziguanjia/SKILL.md +6 -1
- package/dist/shortcuts/invoice/operations/issuing.js +0 -198
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-field-rules.md +0 -161
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-qys-ledger.md +0 -66
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing.md +0 -107
package/README.md
CHANGED
|
@@ -229,8 +229,13 @@ E10 配置/映射 → 当前 profile → `https://www.e-cology.com.cn`”解析
|
|
|
229
229
|
`authentication/xiaoe_credential_unavailable`,不会自动打开浏览器或降级到
|
|
230
230
|
`auth login`。确需浏览器授权时,调用方必须另行显式执行 `auth login`。
|
|
231
231
|
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
业务命令不会在没有本地会话时主动建立 XiaoE 登录态,因此首次使用仍需显式执行
|
|
233
|
+
`auth xiaoe`。已有会话在接口层被明确判定为失效(HTTP 401/302、业务码 `-2` 或登录页)时,
|
|
234
|
+
公共 E10 请求运行时会静默换取一次新会话并把被拒绝的请求重放一次;这个过程不向
|
|
235
|
+
stdout/stderr 输出中间“登录”步骤,并会合并同一 profile 的并发换票。流式请求、网络错误、
|
|
236
|
+
`partial` / `write_uncertain` 不会重放;恢复后的地址或用户身份变化时也会停止。
|
|
237
|
+
|
|
238
|
+
桌面客户端需要 XiaoE 专属业务行为时,把全局参数放在业务子命令之前,例如:
|
|
234
239
|
|
|
235
240
|
```text
|
|
236
241
|
weaver-work-cli --runtime-agent xiaoe workflow ...
|
package/dist/cmd/setup/index.js
CHANGED
|
@@ -69,7 +69,7 @@ export const setupCommand = {
|
|
|
69
69
|
: null;
|
|
70
70
|
const nextActions = [];
|
|
71
71
|
if (!checks.find((check) => check.name === 'e10Auth')?.ok) {
|
|
72
|
-
nextActions.push('
|
|
72
|
+
nextActions.push('Authenticate using the method required by the current runtime, then rerun setup.');
|
|
73
73
|
}
|
|
74
74
|
if (!skillInstall) {
|
|
75
75
|
nextActions.push('Optional Agent setup: weaver-work-cli skills install yepiaotong');
|
|
@@ -172,12 +172,7 @@ function cookieValue(rawCookie, name) {
|
|
|
172
172
|
}
|
|
173
173
|
function formatAuthStatus(status) {
|
|
174
174
|
if (!status.loggedIn) {
|
|
175
|
-
return
|
|
176
|
-
'Not logged in.',
|
|
177
|
-
'In the XiaoE host, run: weaver-work-cli auth xiaoe',
|
|
178
|
-
'Otherwise: weaver-work-cli auth login --base-url <url> --agent_type <agent_type>',
|
|
179
|
-
'Or: weaver-work-cli auth set --eteamsid <id> --base-url <url> --agent_type <agent_type>',
|
|
180
|
-
].join('\n');
|
|
175
|
+
return 'Not logged in. Select the appropriate authentication method for the current runtime before retrying.';
|
|
181
176
|
}
|
|
182
177
|
const lines = [
|
|
183
178
|
'Logged in.',
|
|
@@ -360,6 +360,14 @@ export class E10AuthSession {
|
|
|
360
360
|
uaHeader() {
|
|
361
361
|
return this.agentType ? `AgentType=${this.agentType},IsAgent=true` : 'weaver-work-cli';
|
|
362
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Adopt a freshly authenticated session in place. Hosts cache this object for
|
|
365
|
+
* the lifetime of one command, so mutating it keeps every subsequent request
|
|
366
|
+
* on the refreshed credential without exposing or re-reading auth data.
|
|
367
|
+
*/
|
|
368
|
+
replaceWith(session) {
|
|
369
|
+
this.data = { ...session.data, cookies: { ...session.data.cookies } };
|
|
370
|
+
}
|
|
363
371
|
toJSON() {
|
|
364
372
|
const sourceInfo = {
|
|
365
373
|
...(this.loginSource ? { loginSource: this.loginSource } : {}),
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { getActiveProfile } from './auth/session.js';
|
|
2
|
+
import { hasXiaoEEnv, tryXiaoEUnifiedLogin } from './auth/xiaoe.js';
|
|
3
|
+
const AUTH_REQUIRED_CODES = new Set(['-2', '401']);
|
|
4
|
+
const AUTH_REQUIRED_MARKERS = [
|
|
5
|
+
'未登录',
|
|
6
|
+
'请登录',
|
|
7
|
+
'重新登录',
|
|
8
|
+
'登录失效',
|
|
9
|
+
'登录已失效',
|
|
10
|
+
'登录过期',
|
|
11
|
+
'登录已过期',
|
|
12
|
+
'登录超时',
|
|
13
|
+
'会话失效',
|
|
14
|
+
'会话过期',
|
|
15
|
+
'认证失败',
|
|
16
|
+
'身份认证失败',
|
|
17
|
+
'token expired',
|
|
18
|
+
'token timeout',
|
|
19
|
+
'token invalid',
|
|
20
|
+
'session expired',
|
|
21
|
+
'login required',
|
|
22
|
+
'not logged in',
|
|
23
|
+
'not login',
|
|
24
|
+
];
|
|
25
|
+
const recoveryInFlight = new Map();
|
|
26
|
+
function asRecord(value) {
|
|
27
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
28
|
+
? value
|
|
29
|
+
: null;
|
|
30
|
+
}
|
|
31
|
+
function responseCodes(value) {
|
|
32
|
+
const root = asRecord(value);
|
|
33
|
+
if (!root)
|
|
34
|
+
return [];
|
|
35
|
+
const data = asRecord(root.data);
|
|
36
|
+
const resBase = asRecord(root.res_base);
|
|
37
|
+
const nestedResBase = asRecord(data?.res_base);
|
|
38
|
+
return [root.code, data?.code, resBase?.ret, nestedResBase?.ret]
|
|
39
|
+
.filter((item) => item !== undefined && item !== null)
|
|
40
|
+
.map(String);
|
|
41
|
+
}
|
|
42
|
+
function authenticationTextFields(value) {
|
|
43
|
+
if (typeof value === 'string')
|
|
44
|
+
return [value];
|
|
45
|
+
const root = asRecord(value);
|
|
46
|
+
if (!root)
|
|
47
|
+
return [];
|
|
48
|
+
const data = asRecord(root.data);
|
|
49
|
+
const values = [];
|
|
50
|
+
for (const record of [root, data]) {
|
|
51
|
+
if (!record)
|
|
52
|
+
continue;
|
|
53
|
+
for (const key of ['message', 'msg', 'error', 'errorMsg', 'rawText'])
|
|
54
|
+
values.push(record[key]);
|
|
55
|
+
}
|
|
56
|
+
return values
|
|
57
|
+
.filter((item) => typeof item === 'string' || typeof item === 'number')
|
|
58
|
+
.map(String);
|
|
59
|
+
}
|
|
60
|
+
function responseText(response) {
|
|
61
|
+
const fields = authenticationTextFields(response.value);
|
|
62
|
+
if (fields.length)
|
|
63
|
+
return fields.join('\n');
|
|
64
|
+
// A parsed object may contain ordinary user/business text such as "请登录".
|
|
65
|
+
// Never scan the complete serialized success payload for auth keywords.
|
|
66
|
+
if (asRecord(response.value))
|
|
67
|
+
return '';
|
|
68
|
+
if (typeof response.text === 'string' && response.text)
|
|
69
|
+
return response.text;
|
|
70
|
+
if (response.content && response.content.length) {
|
|
71
|
+
return response.content.subarray(0, 8 * 1024).toString('utf8');
|
|
72
|
+
}
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Detect only explicit authentication rejection signals. A bare HTTP 403 is
|
|
77
|
+
* deliberately excluded because E10 also uses it for ordinary permission
|
|
78
|
+
* denials, which must never trigger a credential refresh.
|
|
79
|
+
*/
|
|
80
|
+
export function isE10AuthenticationRequired(response) {
|
|
81
|
+
if (response.status === 401 || response.status === 302)
|
|
82
|
+
return true;
|
|
83
|
+
if (responseCodes(response.value).some((code) => AUTH_REQUIRED_CODES.has(code)))
|
|
84
|
+
return true;
|
|
85
|
+
const text = responseText(response).toLowerCase();
|
|
86
|
+
if (!text)
|
|
87
|
+
return false;
|
|
88
|
+
if (text.includes('login.jsp'))
|
|
89
|
+
return true;
|
|
90
|
+
if (text.includes('<input') && text.includes('password'))
|
|
91
|
+
return true;
|
|
92
|
+
return AUTH_REQUIRED_MARKERS.some((marker) => text.includes(marker.toLowerCase()));
|
|
93
|
+
}
|
|
94
|
+
function globalOptions(ctx) {
|
|
95
|
+
return typeof ctx.getGlobalOptions === 'function' ? ctx.getGlobalOptions() : {};
|
|
96
|
+
}
|
|
97
|
+
/** XiaoE automatic recovery is opt-in through explicit runtime evidence. */
|
|
98
|
+
export function canAutomaticallyRecoverXiaoE(ctx) {
|
|
99
|
+
const options = globalOptions(ctx);
|
|
100
|
+
// An explicitly supplied auth file is caller-owned and must not be replaced
|
|
101
|
+
// by a profile-backed XiaoE session behind the caller's back.
|
|
102
|
+
return !options.auth && hasXiaoEEnv(ctx.env || process.env, options);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Silently refresh a stale XiaoE session. Concurrent requests for the same
|
|
106
|
+
* profile/base URL share one credential exchange, so a burst of 401 responses
|
|
107
|
+
* does not start a burst of login attempts.
|
|
108
|
+
*/
|
|
109
|
+
export async function recoverXiaoEAuthSession(ctx, currentSession) {
|
|
110
|
+
if (!canAutomaticallyRecoverXiaoE(ctx))
|
|
111
|
+
return null;
|
|
112
|
+
const options = globalOptions(ctx);
|
|
113
|
+
const profile = options.profile || getActiveProfile();
|
|
114
|
+
const baseUrl = currentSession.getBaseUrl();
|
|
115
|
+
const key = `${profile}\n${baseUrl}`;
|
|
116
|
+
const existing = recoveryInFlight.get(key);
|
|
117
|
+
if (existing)
|
|
118
|
+
return existing;
|
|
119
|
+
const recovery = (async () => {
|
|
120
|
+
const login = await tryXiaoEUnifiedLogin({
|
|
121
|
+
env: ctx.env,
|
|
122
|
+
baseUrl,
|
|
123
|
+
profileBaseUrl: baseUrl,
|
|
124
|
+
allowBundledEnvScript: true,
|
|
125
|
+
allowTokenScript: true,
|
|
126
|
+
tenantKeyFallback: currentSession.tenantKey,
|
|
127
|
+
profileHint: profile,
|
|
128
|
+
// Intentionally no logger: transparent recovery must not leak an
|
|
129
|
+
// intermediate "login" step into agent stdout/stderr or JSON output.
|
|
130
|
+
});
|
|
131
|
+
return login?.session || null;
|
|
132
|
+
})();
|
|
133
|
+
recoveryInFlight.set(key, recovery);
|
|
134
|
+
try {
|
|
135
|
+
return await recovery;
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
if (recoveryInFlight.get(key) === recovery)
|
|
139
|
+
recoveryInFlight.delete(key);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export function sameE10SessionIdentity(current, recovered) {
|
|
143
|
+
const currentBaseUrl = current.getBaseUrl().replace(/\/+$/u, '').toLowerCase();
|
|
144
|
+
const recoveredBaseUrl = recovered.getBaseUrl().replace(/\/+$/u, '').toLowerCase();
|
|
145
|
+
if (currentBaseUrl !== recoveredBaseUrl)
|
|
146
|
+
return false;
|
|
147
|
+
return !current.userId || !recovered.userId || current.userId === recovered.userId;
|
|
148
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { isE10AuthenticationRequired, sameE10SessionIdentity, } from './auth-recovery.js';
|
|
2
|
+
class E10AuthenticationContextChangedError extends Error {
|
|
3
|
+
}
|
|
1
4
|
export class E10ApiClient {
|
|
2
5
|
auth;
|
|
3
6
|
retries;
|
|
4
|
-
|
|
7
|
+
recoverAuthentication;
|
|
8
|
+
constructor(auth, retries = 3, recoverAuthentication) {
|
|
5
9
|
this.auth = auth;
|
|
6
10
|
this.retries = retries;
|
|
11
|
+
this.recoverAuthentication = recoverAuthentication;
|
|
7
12
|
}
|
|
8
13
|
getBaseUrl() {
|
|
9
14
|
return this.auth.getBaseUrl();
|
|
@@ -56,31 +61,51 @@ export class E10ApiClient {
|
|
|
56
61
|
}
|
|
57
62
|
async requestRaw(url, init) {
|
|
58
63
|
let lastError = null;
|
|
64
|
+
let authenticationRecoveryAttempted = false;
|
|
59
65
|
for (let attempt = 1; attempt <= this.retries; attempt++) {
|
|
60
66
|
try {
|
|
61
67
|
const resp = await fetch(url, { ...init, redirect: 'manual' });
|
|
68
|
+
const text = await resp.text();
|
|
69
|
+
let value = null;
|
|
70
|
+
if (text) {
|
|
71
|
+
try {
|
|
72
|
+
value = JSON.parse(text);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
value = text;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!authenticationRecoveryAttempted && isE10AuthenticationRequired({
|
|
79
|
+
status: resp.status,
|
|
80
|
+
headers: resp.headers,
|
|
81
|
+
text,
|
|
82
|
+
value,
|
|
83
|
+
})) {
|
|
84
|
+
authenticationRecoveryAttempted = true;
|
|
85
|
+
const recovered = await this.recoverAuthentication?.(this.auth) || null;
|
|
86
|
+
if (recovered) {
|
|
87
|
+
if (!sameE10SessionIdentity(this.auth, recovered)) {
|
|
88
|
+
throw new E10AuthenticationContextChangedError('XiaoE automatic authentication recovery changed the E10 login context');
|
|
89
|
+
}
|
|
90
|
+
this.auth.replaceWith(recovered);
|
|
91
|
+
refreshAuthenticationHeaders(init, this.auth);
|
|
92
|
+
attempt -= 1; // the one authentication retry does not consume the transport retry budget
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Preserve the legacy password-based refresh for non-XiaoE callers.
|
|
62
97
|
if (resp.status === 302) {
|
|
63
98
|
const ok = await this.auth.ensureValid();
|
|
64
99
|
if (!ok)
|
|
65
100
|
throw new Error('E10 session expired; please login again');
|
|
66
|
-
|
|
67
|
-
headers.eteamsid = this.auth.eteamsId;
|
|
68
|
-
headers.cookie = this.auth.cookieHeader();
|
|
69
|
-
headers['user-agent'] = this.auth.uaHeader();
|
|
70
|
-
init.headers = headers;
|
|
101
|
+
refreshAuthenticationHeaders(init, this.auth);
|
|
71
102
|
continue;
|
|
72
103
|
}
|
|
73
|
-
|
|
74
|
-
if (!text)
|
|
75
|
-
return null;
|
|
76
|
-
try {
|
|
77
|
-
return JSON.parse(text);
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return text;
|
|
81
|
-
}
|
|
104
|
+
return value;
|
|
82
105
|
}
|
|
83
106
|
catch (error) {
|
|
107
|
+
if (error instanceof E10AuthenticationContextChangedError)
|
|
108
|
+
throw error;
|
|
84
109
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
85
110
|
if (attempt < this.retries) {
|
|
86
111
|
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 8000);
|
|
@@ -102,3 +127,10 @@ export class E10ApiClient {
|
|
|
102
127
|
return { body, boundary };
|
|
103
128
|
}
|
|
104
129
|
}
|
|
130
|
+
function refreshAuthenticationHeaders(init, session) {
|
|
131
|
+
const headers = (init.headers || {});
|
|
132
|
+
headers.eteamsid = session.eteamsId;
|
|
133
|
+
headers.cookie = session.cookieHeader();
|
|
134
|
+
headers['user-agent'] = session.uaHeader();
|
|
135
|
+
init.headers = headers;
|
|
136
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CliError, ExitCode } from '../../core/errors.js';
|
|
2
|
+
import { E10_AUTH_REQUIRED_MESSAGE } from './auth/messages.js';
|
|
2
3
|
import { E10AuthSession } from './auth/session.js';
|
|
3
|
-
import { hasXiaoEEnv } from './auth/xiaoe.js';
|
|
4
4
|
import { E10ApiClient } from './client.js';
|
|
5
|
+
import { recoverXiaoEAuthSession } from './auth-recovery.js';
|
|
5
6
|
export async function getE10Session(ctx, load = {}) {
|
|
6
7
|
const options = ctx.getGlobalOptions();
|
|
7
8
|
return E10AuthSession.fromOptions(options, ctx.cwd, load);
|
|
@@ -9,18 +10,17 @@ export async function getE10Session(ctx, load = {}) {
|
|
|
9
10
|
export async function requireE10Session(ctx, load = {}) {
|
|
10
11
|
const session = await getE10Session(ctx, load);
|
|
11
12
|
if (!session) {
|
|
12
|
-
|
|
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);
|
|
15
|
-
}
|
|
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);
|
|
13
|
+
throw new CliError(E10_AUTH_REQUIRED_MESSAGE, ExitCode.AuthError);
|
|
17
14
|
}
|
|
18
15
|
return session;
|
|
19
16
|
}
|
|
20
17
|
export async function getE10Client(ctx) {
|
|
21
18
|
const session = await getE10Session(ctx);
|
|
22
|
-
return session
|
|
19
|
+
return session
|
|
20
|
+
? new E10ApiClient(session, 3, (current) => recoverXiaoEAuthSession(ctx, current))
|
|
21
|
+
: null;
|
|
23
22
|
}
|
|
24
23
|
export async function requireE10Client(ctx) {
|
|
25
|
-
|
|
24
|
+
const session = await requireE10Session(ctx);
|
|
25
|
+
return new E10ApiClient(session, 3, (current) => recoverXiaoEAuthSession(ctx, current));
|
|
26
26
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { E10ApiClient } from './client.js';
|
|
2
2
|
export { getE10Client, getE10Session, requireE10Client, requireE10Session } from './context.js';
|
|
3
3
|
export { E10ApiError, E10RequestRuntime, createE10RequestRuntime } from './request-runtime.js';
|
|
4
|
+
export { canAutomaticallyRecoverXiaoE, isE10AuthenticationRequired, recoverXiaoEAuthSession, } from './auth-recovery.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CliError, ExitCode } from '../../core/errors.js';
|
|
2
|
+
import { isE10AuthenticationRequired, recoverXiaoEAuthSession, sameE10SessionIdentity, } from './auth-recovery.js';
|
|
2
3
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
3
4
|
const DEFAULT_BIG_INT_STRINGS = true;
|
|
4
5
|
const RESERVED_HEADERS = [
|
|
@@ -29,11 +30,14 @@ export class E10RequestRuntime {
|
|
|
29
30
|
interceptors;
|
|
30
31
|
fetchFn;
|
|
31
32
|
attachSessionTicket;
|
|
33
|
+
recoverAuthentication;
|
|
32
34
|
constructor(ctx, session, dependencies = {}) {
|
|
33
35
|
this.ctx = ctx;
|
|
34
36
|
this.session = session;
|
|
35
37
|
this.fetchFn = dependencies.fetch || globalThis.fetch;
|
|
36
38
|
this.attachSessionTicket = dependencies.attachSessionTicket === true;
|
|
39
|
+
this.recoverAuthentication = dependencies.recoverAuthentication
|
|
40
|
+
|| ((current) => recoverXiaoEAuthSession(this.ctx, current));
|
|
37
41
|
this.interceptors = [
|
|
38
42
|
...(this.ctx.hooks?.getE10RequestInterceptors?.() || []),
|
|
39
43
|
...(dependencies.interceptors || []),
|
|
@@ -47,9 +51,8 @@ export class E10RequestRuntime {
|
|
|
47
51
|
}
|
|
48
52
|
async request(request) {
|
|
49
53
|
const mutable = this.prepareRequest(request);
|
|
50
|
-
const sessionContext = this.sessionContext();
|
|
51
54
|
try {
|
|
52
|
-
await this.runBeforeRequest(mutable, sessionContext);
|
|
55
|
+
await this.runBeforeRequest(mutable, this.sessionContext());
|
|
53
56
|
this.assertPath(mutable.path);
|
|
54
57
|
if (mutable.path !== request.path) {
|
|
55
58
|
throw new E10ApiError('policy', 'path_rewrite_denied', 'E10 API 拦截器不能修改请求路径', {
|
|
@@ -58,19 +61,58 @@ export class E10RequestRuntime {
|
|
|
58
61
|
});
|
|
59
62
|
}
|
|
60
63
|
stripReservedHeaders(mutable.headers);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
const requestEteamsId = this.session.eteamsId;
|
|
65
|
+
let parsed = await this.executeRequest(mutable, request.trustedHeaders);
|
|
66
|
+
if (!mutable.streamBody
|
|
67
|
+
&& isE10AuthenticationRequired(parsed)
|
|
68
|
+
&& await this.recoverExpiredSession(requestEteamsId)) {
|
|
69
|
+
// Authentication rejection is a definitive pre-business failure. Retry
|
|
70
|
+
// exactly once with the new session; never retry transport/partial errors.
|
|
71
|
+
parsed = await this.executeRequest(mutable, request.trustedHeaders);
|
|
72
|
+
}
|
|
73
|
+
await this.runAfterResponse(mutable, this.sessionContext(), parsed);
|
|
67
74
|
return parsed;
|
|
68
75
|
}
|
|
69
76
|
catch (error) {
|
|
70
|
-
await this.runOnError(mutable, sessionContext, error);
|
|
77
|
+
await this.runOnError(mutable, this.sessionContext(), error);
|
|
71
78
|
throw error;
|
|
72
79
|
}
|
|
73
80
|
}
|
|
81
|
+
async executeRequest(request, trustedHeaders = {}) {
|
|
82
|
+
const url = withQuery(this.session.getBaseUrl(), request.path, request.query);
|
|
83
|
+
const headers = this.composeHeaders(request, trustedHeaders);
|
|
84
|
+
const init = this.buildFetchInit(request, headers);
|
|
85
|
+
const response = await this.fetchWithTimeout(url, init, request.timeoutMs);
|
|
86
|
+
return this.readResponse(response, request.responseType, request.bigIntStrings, request.maxResponseBytes, request.timeoutMs);
|
|
87
|
+
}
|
|
88
|
+
async recoverExpiredSession(requestEteamsId) {
|
|
89
|
+
// Another concurrent request may already have refreshed the shared session
|
|
90
|
+
// object while this response was in flight.
|
|
91
|
+
if (this.session.eteamsId && this.session.eteamsId !== requestEteamsId)
|
|
92
|
+
return true;
|
|
93
|
+
let recovered;
|
|
94
|
+
try {
|
|
95
|
+
recovered = await this.recoverAuthentication(this.session);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (!recovered)
|
|
101
|
+
return false;
|
|
102
|
+
if (!sameE10SessionIdentity(this.session, recovered)) {
|
|
103
|
+
throw new E10ApiError('authentication', 'session_context_changed', 'XiaoE 自动恢复后的登录身份或 E10 环境发生变化,本次请求未重试', { exitCode: ExitCode.AuthError, retryable: false });
|
|
104
|
+
}
|
|
105
|
+
const original = this.session;
|
|
106
|
+
if (typeof original.replaceWith === 'function') {
|
|
107
|
+
original.replaceWith(recovered);
|
|
108
|
+
this.session = original;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Structural test doubles and older embedders may not expose replaceWith.
|
|
112
|
+
this.session = recovered;
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
74
116
|
prepareRequest(request) {
|
|
75
117
|
return {
|
|
76
118
|
module: request.module,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
|
|
1
2
|
import { requireE10Session } from '../../internal/e10/context.js';
|
|
2
3
|
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
3
4
|
import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
@@ -151,7 +152,7 @@ export function createArchiveHost(ctx, dependencies = {}) {
|
|
|
151
152
|
async function request(request) {
|
|
152
153
|
const response = await doRequest(request, false).catch((error) => mapRuntimeNetworkError(error));
|
|
153
154
|
if (response.status === 401 || response.status === 302) {
|
|
154
|
-
throw archiveAuthError('session_expired',
|
|
155
|
+
throw archiveAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
155
156
|
}
|
|
156
157
|
if (response.parseError) {
|
|
157
158
|
if (response.status < 200 || response.status >= 300) {
|
|
@@ -182,7 +183,7 @@ export function createArchiveHost(ctx, dependencies = {}) {
|
|
|
182
183
|
async function requestBinary(request) {
|
|
183
184
|
const response = await doRequest(request, true).catch((error) => mapRuntimeNetworkError(error));
|
|
184
185
|
if (response.status === 401 || response.status === 302) {
|
|
185
|
-
throw archiveAuthError('session_expired',
|
|
186
|
+
throw archiveAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
186
187
|
}
|
|
187
188
|
if (response.status < 200 || response.status >= 300) {
|
|
188
189
|
throw new ArchiveCliError('api', 'http_error', `档案下载 HTTP ${response.status}`, {
|
|
@@ -20,7 +20,7 @@ export const archiveManifest = {
|
|
|
20
20
|
capabilities: ['fonds', 'prelib', 'search', 'borrowCar', 'borrowList', 'flow', 'download', 'upload'],
|
|
21
21
|
auth: {
|
|
22
22
|
required: 'e10-session',
|
|
23
|
-
note: '所有操作都要求已登录 E10
|
|
23
|
+
note: '所有操作都要求已登录 E10;未登录时返回 auth_error,调用方应根据当前宿主环境选择登录方式。',
|
|
24
24
|
},
|
|
25
25
|
rules: [
|
|
26
26
|
'写操作(*.apply)必须先执行同名的 prepare 拿到 continuation,再带 confirm=true 执行 apply;CLI 用 --yes 表示确认。',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
|
|
1
2
|
import { requireE10Session } from '../../internal/e10/context.js';
|
|
2
3
|
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
3
4
|
import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
@@ -122,7 +123,7 @@ export function createArchiveOaHost(ctx, dependencies = {}) {
|
|
|
122
123
|
timeoutMs,
|
|
123
124
|
}).catch((error) => mapRuntimeNetworkError(error));
|
|
124
125
|
if (response.status === 401 || response.status === 302) {
|
|
125
|
-
throw archiveOaAuthError('session_expired',
|
|
126
|
+
throw archiveOaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
126
127
|
}
|
|
127
128
|
if (response.parseError) {
|
|
128
129
|
if (response.status < 200 || response.status >= 300) {
|
|
@@ -20,7 +20,7 @@ export const archiveOaManifest = {
|
|
|
20
20
|
capabilities: ['archiveLocate', 'contractSearch', 'borrowCar'],
|
|
21
21
|
auth: {
|
|
22
22
|
required: 'e10-session',
|
|
23
|
-
note: '所有操作都要求已登录 E10
|
|
23
|
+
note: '所有操作都要求已登录 E10,具体登录方式由调用方根据当前宿主环境选择。发起借阅不在 CLI 能力范围内,由用户在浏览器登录后于界面自己完成。',
|
|
24
24
|
},
|
|
25
25
|
rules: [
|
|
26
26
|
'写操作(*.apply)必须先执行同名的 prepare 拿到 continuation,再带 confirm=true 执行 apply;CLI 用 --yes 表示确认。',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
|
|
1
2
|
import { requireE10Session } from '../../internal/e10/context.js';
|
|
2
3
|
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
3
4
|
import { createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
@@ -176,7 +177,7 @@ export function createZiguanjiaHost(ctx) {
|
|
|
176
177
|
bigIntStrings: true,
|
|
177
178
|
});
|
|
178
179
|
if (response.status === 401 || response.status === 302) {
|
|
179
|
-
throw ziguanjiaAuthError('session_expired',
|
|
180
|
+
throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
180
181
|
}
|
|
181
182
|
if (response.parseError) {
|
|
182
183
|
throw ziguanjiaNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
|
|
@@ -242,7 +243,7 @@ export function createZiguanjiaHost(ctx) {
|
|
|
242
243
|
bigIntStrings: false,
|
|
243
244
|
});
|
|
244
245
|
if (response.status === 401 || response.status === 302) {
|
|
245
|
-
throw ziguanjiaAuthError('session_expired',
|
|
246
|
+
throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
246
247
|
}
|
|
247
248
|
if (response.parseError) {
|
|
248
249
|
throw ziguanjiaNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
|
|
@@ -315,7 +316,7 @@ export function createZiguanjiaHost(ctx) {
|
|
|
315
316
|
bigIntStrings: true,
|
|
316
317
|
});
|
|
317
318
|
if (response.status === 401 || response.status === 302) {
|
|
318
|
-
throw ziguanjiaAuthError('session_expired',
|
|
319
|
+
throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
319
320
|
}
|
|
320
321
|
if (response.parseError) {
|
|
321
322
|
throw ziguanjiaNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
|
|
@@ -340,7 +341,7 @@ export function createZiguanjiaHost(ctx) {
|
|
|
340
341
|
}
|
|
341
342
|
function handleEbusiness(value) {
|
|
342
343
|
if (isEbSessionExpired(value)) {
|
|
343
|
-
throw ziguanjiaAuthError('session_expired',
|
|
344
|
+
throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { ret: ebCode(value) });
|
|
344
345
|
}
|
|
345
346
|
const code = ebCode(value);
|
|
346
347
|
if (!isEbSuccessCode(code)) {
|
|
@@ -363,7 +364,7 @@ export function createZiguanjiaHost(ctx) {
|
|
|
363
364
|
bigIntStrings: true,
|
|
364
365
|
});
|
|
365
366
|
if (response.status === 401 || response.status === 302) {
|
|
366
|
-
throw ziguanjiaAuthError('session_expired',
|
|
367
|
+
throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
367
368
|
}
|
|
368
369
|
if (response.parseError) {
|
|
369
370
|
throw ziguanjiaNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
|
|
@@ -393,7 +394,7 @@ export function createZiguanjiaHost(ctx) {
|
|
|
393
394
|
bigIntStrings: true,
|
|
394
395
|
});
|
|
395
396
|
if (response.status === 401 || response.status === 302) {
|
|
396
|
-
throw ziguanjiaAuthError('session_expired',
|
|
397
|
+
throw ziguanjiaAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
397
398
|
}
|
|
398
399
|
if (response.parseError) {
|
|
399
400
|
throw ziguanjiaNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ExitCode } from '../../core/errors.js';
|
|
2
|
+
import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
|
|
2
3
|
import { requireE10Session } from '../../internal/e10/context.js';
|
|
3
4
|
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
4
5
|
import { createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
@@ -75,7 +76,7 @@ export function createBlogHost(ctx, dependencies = {}) {
|
|
|
75
76
|
}
|
|
76
77
|
function assertSession(value) {
|
|
77
78
|
if (isSessionExpired(value)) {
|
|
78
|
-
throw blogAuthError('session_expired',
|
|
79
|
+
throw blogAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE);
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
async function blogRequest(request) {
|
|
@@ -91,7 +92,7 @@ export function createBlogHost(ctx, dependencies = {}) {
|
|
|
91
92
|
timeoutMs,
|
|
92
93
|
});
|
|
93
94
|
if (response.status === 401 || response.status === 302) {
|
|
94
|
-
throw blogAuthError('session_expired',
|
|
95
|
+
throw blogAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
95
96
|
}
|
|
96
97
|
if (response.parseError) {
|
|
97
98
|
if (response.status < 200 || response.status >= 300) {
|
|
@@ -134,7 +135,7 @@ export function createBlogHost(ctx, dependencies = {}) {
|
|
|
134
135
|
headers: { Accept: 'application/octet-stream, */*' },
|
|
135
136
|
});
|
|
136
137
|
if (response.status === 401 || response.status === 302) {
|
|
137
|
-
throw blogAuthError('session_expired',
|
|
138
|
+
throw blogAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
138
139
|
}
|
|
139
140
|
const contentType = response.contentType;
|
|
140
141
|
const declaredLength = Number(response.headers.get('content-length'));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
|
|
1
2
|
import { requireE10Session } from '../../internal/e10/context.js';
|
|
2
3
|
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
3
4
|
import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
@@ -152,7 +153,7 @@ export function createCaizhilianHost(ctx, dependencies = {}) {
|
|
|
152
153
|
async function request(request) {
|
|
153
154
|
const response = await doRequest(request, false).catch((error) => mapRuntimeNetworkError(error));
|
|
154
155
|
if (response.status === 401 || response.status === 302) {
|
|
155
|
-
throw caizhilianAuthError('session_expired',
|
|
156
|
+
throw caizhilianAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
156
157
|
}
|
|
157
158
|
if (response.parseError) {
|
|
158
159
|
if (response.status < 200 || response.status >= 300) {
|
|
@@ -183,7 +184,7 @@ export function createCaizhilianHost(ctx, dependencies = {}) {
|
|
|
183
184
|
async function requestBinary(request) {
|
|
184
185
|
const response = await doRequest(request, true).catch((error) => mapRuntimeNetworkError(error));
|
|
185
186
|
if (response.status === 401 || response.status === 302) {
|
|
186
|
-
throw caizhilianAuthError('session_expired',
|
|
187
|
+
throw caizhilianAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
187
188
|
}
|
|
188
189
|
if (response.status < 200 || response.status >= 300) {
|
|
189
190
|
throw new CaizhilianCliError('api', 'http_error', `采知连下载 HTTP ${response.status}`, {
|
|
@@ -19,7 +19,7 @@ const WITHHELD = [
|
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
name: 'caizhilian.system.switch-config',
|
|
22
|
-
reason: '源 switch_config 在 Skill 本地多套环境配置间切换;CLI
|
|
22
|
+
reason: '源 switch_config 在 Skill 本地多套环境配置间切换;CLI 的环境与认证配置体系负责这一职责,业务模块不做环境切换。',
|
|
23
23
|
},
|
|
24
24
|
];
|
|
25
25
|
function buildOperations() {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { E10_SESSION_EXPIRED_MESSAGE } from '../../internal/e10/auth/messages.js';
|
|
1
2
|
import { requireE10Session } from '../../internal/e10/context.js';
|
|
2
3
|
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
3
4
|
import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
@@ -110,7 +111,7 @@ export function createCalendarHost(ctx, dependencies = {}) {
|
|
|
110
111
|
mapRuntimeNetworkError(error);
|
|
111
112
|
}
|
|
112
113
|
if (response.status === 401 || response.status === 302) {
|
|
113
|
-
throw calendarAuthError('session_expired',
|
|
114
|
+
throw calendarAuthError('session_expired', E10_SESSION_EXPIRED_MESSAGE, { httpStatus: response.status });
|
|
114
115
|
}
|
|
115
116
|
if (response.parseError) {
|
|
116
117
|
if (response.status < 200 || response.status >= 300) {
|