weaver-work-cli 0.1.39 → 0.1.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/chunks/{archive-ENZYBBME.js → archive-J4I6MCKF.js} +2 -2
- package/dist/chunks/{archive-oa-YWLEC5V4.js → archive-oa-BRZKKTO4.js} +2 -2
- package/dist/chunks/{asset-OOV6DWPA.js → asset-L7D4FAOZ.js} +2 -2
- package/dist/chunks/{asset-idle-RE632MY5.js → asset-idle-DADLLV75.js} +2 -2
- package/dist/chunks/{attachment-content-RWN2FCLL.js → attachment-content-K4C7VLWQ.js} +3 -3
- package/dist/chunks/{blog-PEPOJAL7.js → blog-BVGQ7NZL.js} +2 -2
- package/dist/chunks/{caizhilian-WYIQDVPJ.js → caizhilian-SAADNFD6.js} +2 -2
- package/dist/chunks/{calendar-CIARBAOX.js → calendar-Q7NQY5Z2.js} +61 -23
- package/dist/chunks/{chunk-F2EQXUEE.js → chunk-3KTFS4IA.js} +397 -159
- package/dist/chunks/{chunk-G2JSIUWF.js → chunk-4PD5NWAX.js} +89 -0
- package/dist/chunks/{chunk-HJYVHXUQ.js → chunk-4RAE6JXP.js} +1 -1
- package/dist/chunks/{chunk-CQHGNCK2.js → chunk-72CKAUAG.js} +41 -8
- package/dist/chunks/{chunk-K3RUEEOL.js → chunk-APVFJUQH.js} +2 -2
- package/dist/chunks/{chunk-BJGXMFGD.js → chunk-BBKDXQEC.js} +2 -2
- package/dist/chunks/chunk-BIPYLLVS.js +501 -0
- package/dist/chunks/{chunk-GVPJZXJ2.js → chunk-EKWAQSLU.js} +92 -50
- package/dist/chunks/{chunk-XXJOSZTQ.js → chunk-FOAA27U5.js} +381 -57
- package/dist/chunks/{chunk-I2HB737U.js → chunk-G4UR6NXN.js} +2 -2
- package/dist/chunks/{chunk-YS5DPN4T.js → chunk-GEWYMQME.js} +1 -1
- package/dist/chunks/{chunk-P2DAISYW.js → chunk-GHXCDPTM.js} +1 -1
- package/dist/chunks/{chunk-L4N7B7ZD.js → chunk-IZ7S7QUS.js} +4 -4
- package/dist/chunks/{chunk-NLK2RZKG.js → chunk-JPRO6ER2.js} +122 -2214
- package/dist/chunks/{chunk-36DIFZAH.js → chunk-LADPYOWC.js} +2 -2
- package/dist/chunks/chunk-LDZQPUIA.js +1238 -0
- package/dist/chunks/chunk-NKZRQXST.js +2546 -0
- package/dist/chunks/{chunk-EINRJHQ3.js → chunk-QKNKR4RH.js} +3 -3
- package/dist/chunks/{chunk-T7JDJJ7H.js → chunk-R4CHUSKC.js} +126 -19
- package/dist/chunks/{chunk-EZGFHJFW.js → chunk-SA6VBIHY.js} +88 -15
- package/dist/chunks/{chunk-ZXRNHZTE.js → chunk-TTPJJJC4.js} +2 -2
- package/dist/chunks/{chunk-KTCB5R5H.js → chunk-W7BEHOSR.js} +2 -2
- package/dist/chunks/{chunk-MUQUHKPG.js → chunk-YRSZQUD2.js} +2 -2
- package/dist/chunks/{conflict-DRMDOL56.js → conflict-F2OMZRTO.js} +2 -2
- package/dist/chunks/{contract-73MERZWN.js → contract-5BETHRSG.js} +2 -2
- package/dist/chunks/{cowork-HNGBPD7A.js → cowork-3MN6EQQO.js} +27 -10
- package/dist/chunks/create-flow-5FQNNMVO.js +55 -0
- package/dist/chunks/{ebuilder-form-OY6DVNRK.js → ebuilder-form-SBHOYBUD.js} +3 -3
- package/dist/chunks/{ehr-RAQNEQO7.js → ehr-KMOJULKG.js} +432 -1234
- package/dist/chunks/env-LB7NBL2O.js +19 -0
- package/dist/chunks/{esb-6XV7C7YV.js → esb-W2PJCFCX.js} +4 -4
- package/dist/chunks/factory-6ZFQXQ46.js +30 -0
- package/dist/chunks/{fna-XTSRB7G7.js → fna-ZQPCUV4R.js} +2 -2
- package/dist/chunks/group-chat-2HHAJN46.js +20 -0
- package/dist/chunks/{host-YMBXWFWX.js → host-4CBHGOHT.js} +3 -3
- package/dist/chunks/{host-A7J6GDUP.js → host-ATKVNZAT.js} +3 -3
- package/dist/chunks/{host-3F5UM5GN.js → host-BQH22NTQ.js} +3 -3
- package/dist/chunks/{host-YAQAPYNP.js → host-X2CSLFM6.js} +3 -3
- package/dist/chunks/{host-VGPVP7YF.js → host-YTODKMSL.js} +3 -3
- package/dist/chunks/{hrm-TLHKC545.js → hrm-FFM4UJFS.js} +4 -4
- package/dist/chunks/{im-YOUCFMUK.js → im-VEHKDQPB.js} +8 -8
- package/dist/chunks/{invoice-WNBS2B4C.js → invoice-LVIKSIIF.js} +3 -3
- package/dist/chunks/{jiuchuanhui-XABWS5RN.js → jiuchuanhui-X6DINO5P.js} +2 -2
- package/dist/chunks/{lazy-dispatch-NLZ5UQQI.js → lazy-dispatch-KSWTN5L6.js} +4 -4
- package/dist/chunks/{mail-LRYMBFNL.js → mail-VDKRGUD3.js} +9 -4
- package/dist/chunks/{meeting-ESXL6MFI.js → meeting-CLC6HP3Z.js} +12 -10
- package/dist/chunks/{okr-EOV65SSF.js → okr-CHG4OO3U.js} +2 -2
- package/dist/chunks/{plan-YFPVKW6Y.js → plan-XWR75667.js} +2 -2
- package/dist/chunks/{project-MDVZUYQW.js → project-5BXOOKMG.js} +40 -23
- package/dist/chunks/recruit-stage-DGMFNNDE.js +4507 -0
- package/dist/chunks/{registry-VT5Y2HLA.js → registry-7FFOXIAW.js} +4 -4
- package/dist/chunks/{registry-QCLZEDAP.js → registry-BKBZPBAJ.js} +10 -8
- package/dist/chunks/{registry-YX4CSEMD.js → registry-RHMW6ANW.js} +2 -2
- package/dist/chunks/{registry-NYPYMZXG.js → registry-Z43434VG.js} +1 -1
- package/dist/chunks/{room-recommend-L3MWH4JW.js → room-recommend-62H7ZQQ6.js} +4 -4
- package/dist/chunks/{schedule-UASAVDVD.js → schedule-FNXKH3DQ.js} +4 -4
- package/dist/chunks/{workflow-KDGIUCNW.js → workflow-ADHKCAJK.js} +4 -4
- package/dist/index.js +33 -28
- package/dist/internal/e10/auth/commands.js +110 -56
- package/dist/internal/e10/auth/messages.js +6 -0
- package/dist/internal/e10/auth/session.js +28 -1
- package/dist/internal/e10/auth/xiaoe.js +315 -48
- package/dist/internal/e10/auth-recovery.js +36 -5
- package/dist/internal/e10/client.js +13 -0
- package/dist/internal/e10/context.js +61 -9
- package/dist/internal/e10/index.js +1 -1
- package/dist/internal/e10/network-error.js +93 -0
- package/dist/internal/e10/request-runtime.js +15 -0
- package/dist/shortcuts/calendar/operations/read.js +31 -22
- package/dist/shortcuts/calendar/operations/write.js +45 -1
- package/dist/shortcuts/cowork/operations/discuss-write.js +5 -1
- package/dist/shortcuts/cowork/operations/item-write.js +5 -3
- package/dist/shortcuts/cowork/operations/member-write.js +3 -1
- package/dist/shortcuts/cowork/operations/permission-write.js +5 -1
- package/dist/shortcuts/cowork/operations/shared.js +7 -0
- package/dist/shortcuts/ehr/operations/attend-write.js +43 -1
- package/dist/shortcuts/ehr/operations/hr-contact.js +7 -4
- package/dist/shortcuts/ehr/operations/hr-report.js +13 -7
- package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +134 -59
- package/dist/shortcuts/ehr/operations/recruit-write.js +262 -17
- package/dist/shortcuts/ehr/operations/recruit.js +10 -4
- package/dist/shortcuts/ehr/operations/shared.js +47 -0
- package/dist/shortcuts/hrm/operations/org.js +118 -0
- package/dist/shortcuts/index.js +5 -0
- package/dist/shortcuts/mail/operations/mail-send.js +8 -2
- package/dist/shortcuts/meeting/operations/eb/catalog.js +9 -2
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +461 -127
- package/dist/shortcuts/meeting/operations/eb/crm-search.js +87 -5
- package/dist/shortcuts/meeting/operations/eb/factory.js +33 -0
- package/dist/shortcuts/meeting/operations/eb/flow-memory.js +49 -0
- package/dist/shortcuts/meeting/operations/eb/group-chat.js +605 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/person-cache.js +96 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +44 -6
- package/dist/shortcuts/meeting/operations/eb/room-summary.js +2 -0
- package/dist/shortcuts/meeting/operations/eb/usage-cache.js +99 -0
- package/dist/shortcuts/meeting/operations/from-group-chat.js +35 -12
- package/dist/shortcuts/project/operations/read.js +6 -25
- package/dist/shortcuts/project/operations/shared.js +22 -0
- package/dist/shortcuts/project/operations/write.js +28 -2
- package/dist/shortcuts/recruit-stage/context.js +119 -0
- package/dist/shortcuts/recruit-stage/continuation.js +73 -0
- package/dist/shortcuts/recruit-stage/errors.js +65 -0
- package/dist/shortcuts/recruit-stage/host.js +109 -0
- package/dist/shortcuts/recruit-stage/index.js +115 -0
- package/dist/shortcuts/recruit-stage/manifest.js +29 -0
- package/dist/shortcuts/recruit-stage/operations/interview-arrange.js +722 -0
- package/dist/shortcuts/recruit-stage/operations/interview-feedback.js +269 -0
- package/dist/shortcuts/recruit-stage/operations/jd-create.js +630 -0
- package/dist/shortcuts/recruit-stage/operations/list.js +431 -0
- package/dist/shortcuts/recruit-stage/operations/registry.js +38 -0
- package/dist/shortcuts/recruit-stage/operations/resume-import.js +1198 -0
- package/dist/shortcuts/recruit-stage/operations/search.js +158 -0
- package/dist/shortcuts/recruit-stage/operations/types.js +25 -0
- package/dist/shortcuts/recruit-stage/school-tiers.json +318 -0
- package/dist/shortcuts/recruit-stage/shared.js +608 -0
- package/dist/shortcuts/workflow/operations/form.js +21 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/aigroupRules.js +52 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +4 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +19 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailCache.js +17 -24
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoDetailFetcher.js +14 -9
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.collect.js +78 -10
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +3 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +17 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +16 -40
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +3 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +52 -15
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.pipeline.js +16 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.smartGroup.js +151 -63
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.subGroup.js +56 -0
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.summary.js +53 -16
- package/docs/e10-auth.md +22 -1
- package/docs/operation-manual.md +8 -0
- package/package.json +1 -1
- package/scripts/install-skill-to-xiaoe.mjs +105 -0
- package/scripts/mcp-meeting.mjs +234 -0
- package/scripts/package-skill.mjs +4 -2
- package/skill-template/business-info.json +24 -0
- package/skill-template/domains/recruit-stage.md +29 -0
- package/skills/weaver-e10-calendar/SKILL.md +2 -1
- package/skills/weaver-e10-calendar/references/calendar-write.md +54 -0
- package/skills/weaver-e10-cowork/SKILL.md +1 -1
- package/skills/weaver-e10-cowork/references/cowork-write.md +3 -1
- package/skills/weaver-e10-jucailin/SKILL.md +1 -1
- package/skills/weaver-e10-jucailin/references/attend-write.md +8 -0
- package/skills/weaver-e10-jucailin/references/hr-contact.md +8 -0
- package/skills/weaver-e10-jucailin/references/hr-report.md +8 -0
- package/skills/weaver-e10-jucailin/references/recruit-create.md +2 -2
- package/skills/weaver-e10-jucailin/references/recruit-read.md +1 -1
- package/skills/weaver-e10-jucailin/references/recruit-write.md +4 -3
- package/skills/weaver-e10-mail/SKILL.md +1 -1
- package/skills/weaver-e10-mail/references/mail-send.md +2 -0
- package/skills/weaver-e10-meeting/SKILL.md +119 -26
- package/skills/weaver-e10-meeting/references/meeting-minute-file.md +25 -11
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-recruit-stage/SKILL.md +226 -0
- package/skills/weaver-e10-recruit-stage/agents/openai.yaml +119 -0
- package/skills/weaver-e10-recruit-stage/lib/recruit-xiaoe-file.mjs +188 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-arrange-interview.md +322 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-capability-check.md +127 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-check-install.md +65 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-detail-process.md +63 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-edit-interview-feedback.md +142 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-jd-create.md +73 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-jd-template.md +56 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-list-candidate.md +101 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-list-interview.md +148 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-match-candidates.md +77 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-output-links.md +59 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resolve-creator.md +69 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resolve-metadata.md +241 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resume-import.md +438 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resume-parse.md +128 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-search-candidate.md +146 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-urge-feedback.md +69 -0
- package/skills/weaver-e10-shared/SKILL.md +2 -2
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +4 -2
- package/skills/weaver-e10-shijingran/SKILL.md +3 -2
- package/skills/weaver-e10-shijingran/references/operations.md +3 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +41 -40
- package/skills/weaver-e10-shijingran/references/write-operations.md +2 -0
- package/skills/weaver-e10-workflow/SKILL.md +7 -1
- package/skills/weaver-e10-workflow/aigroup/default.json +23 -9
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +37 -21
- package/skills/weaver-e10-workflow/tools/todo-summary.cjs +135 -43
- package/skills/weaver-e10-yepiaotong/SKILL.md +9 -10
- package/skills/weaver-e10-yepiaotong/references/invoice-agent-entry.md +6 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isXiaoERuntimeAgent } from '../../core/runtime-agent.js';
|
|
2
2
|
import { getActiveProfile, readProfileConfig } from './auth/session.js';
|
|
3
|
-
import { hasXiaoEEnv, resolveXiaoEPreferredBaseUrl, tryXiaoEUnifiedLogin } from './auth/xiaoe.js';
|
|
3
|
+
import { hasXiaoEEnv, resolveXiaoEPreferredBaseUrl, tryXiaoEUnifiedLogin, } from './auth/xiaoe.js';
|
|
4
4
|
const AUTH_REQUIRED_CODES = new Set(['-2', '401']);
|
|
5
5
|
const AUTH_REQUIRED_MARKERS = [
|
|
6
6
|
'未登录',
|
|
@@ -224,6 +224,24 @@ export function canAutomaticallyRecoverXiaoE(ctx, currentSession) {
|
|
|
224
224
|
|| currentSession?.loginSource === 'xiaoe-unified'
|
|
225
225
|
|| isXiaoERuntimeAgent(currentSession?.agentType);
|
|
226
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* 透明恢复默认必须零输出:中间登录步骤绝不能漏进 Agent 的 stdout 或 JSON。
|
|
229
|
+
* 只有显式 `--verbose`(且非 `--json`/`--quiet`)时才把每一步的真实失败原因
|
|
230
|
+
* 经 debug 通道送到 stderr,供人排查。宿主 logger 若没有 debug(测试替身、
|
|
231
|
+
* 旧嵌入方)则继续完全静默。
|
|
232
|
+
*/
|
|
233
|
+
function recoveryLogger(ctx) {
|
|
234
|
+
const options = globalOptions(ctx);
|
|
235
|
+
if (options.json || options.quiet || options.verbose !== true)
|
|
236
|
+
return undefined;
|
|
237
|
+
const adapter = ctx.logger;
|
|
238
|
+
if (!adapter || typeof adapter.debug !== 'function')
|
|
239
|
+
return undefined;
|
|
240
|
+
return {
|
|
241
|
+
info: (message) => adapter.debug(message),
|
|
242
|
+
warn: (message) => adapter.debug(message),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
227
245
|
/**
|
|
228
246
|
* Silently recover a stale or missing XiaoE session. Without a local session,
|
|
229
247
|
* current runtime evidence is required before obtaining credentials.
|
|
@@ -232,8 +250,20 @@ export function canAutomaticallyRecoverXiaoE(ctx, currentSession) {
|
|
|
232
250
|
* does not start a burst of login attempts.
|
|
233
251
|
*/
|
|
234
252
|
export async function recoverXiaoEAuthSession(ctx, currentSession) {
|
|
253
|
+
return (await recoverXiaoEAuthSessionWithDiagnostics(ctx, currentSession)).session;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* `recoverXiaoEAuthSession` 的诊断版:失败时返回本次尝试评估过哪些凭证来源、
|
|
257
|
+
* 最后一条真实原因,供调用方挂到结构化错误上。并发恢复共享同一份诊断
|
|
258
|
+
* (同一个 in-flight Promise),因此每个等待者看到的原因一致。
|
|
259
|
+
*/
|
|
260
|
+
export async function recoverXiaoEAuthSessionWithDiagnostics(ctx, currentSession) {
|
|
261
|
+
const emptyOutcome = () => ({
|
|
262
|
+
session: null,
|
|
263
|
+
diagnostics: { attemptedSteps: [] },
|
|
264
|
+
});
|
|
235
265
|
if (!canAutomaticallyRecoverXiaoE(ctx, currentSession))
|
|
236
|
-
return
|
|
266
|
+
return emptyOutcome();
|
|
237
267
|
const options = globalOptions(ctx);
|
|
238
268
|
const profile = options.profile || getActiveProfile();
|
|
239
269
|
const config = readProfileConfig(profile);
|
|
@@ -246,6 +276,7 @@ export async function recoverXiaoEAuthSession(ctx, currentSession) {
|
|
|
246
276
|
const existing = recoveryInFlight.get(key);
|
|
247
277
|
if (existing)
|
|
248
278
|
return existing;
|
|
279
|
+
const diagnostics = { attemptedSteps: [] };
|
|
249
280
|
const recovery = (async () => {
|
|
250
281
|
const login = await tryXiaoEUnifiedLogin({
|
|
251
282
|
env: ctx.env,
|
|
@@ -255,10 +286,10 @@ export async function recoverXiaoEAuthSession(ctx, currentSession) {
|
|
|
255
286
|
allowTokenScript: true,
|
|
256
287
|
tenantKeyFallback: currentSession?.tenantKey || config.tenantKey,
|
|
257
288
|
profileHint: profile,
|
|
258
|
-
|
|
259
|
-
|
|
289
|
+
diagnostics,
|
|
290
|
+
logger: recoveryLogger(ctx),
|
|
260
291
|
});
|
|
261
|
-
return login?.session || null;
|
|
292
|
+
return { session: login?.session || null, diagnostics };
|
|
262
293
|
})();
|
|
263
294
|
recoveryInFlight.set(key, recovery);
|
|
264
295
|
try {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isE10AuthenticationRequired, isE10LoginRedirect, isE10RedirectStatus, sameE10SessionIdentity, } from './auth-recovery.js';
|
|
2
|
+
import { E10_XIAOE_RECOVERY_FAILED_MESSAGE } from './auth/messages.js';
|
|
2
3
|
class E10AuthenticationContextChangedError extends Error {
|
|
3
4
|
}
|
|
4
5
|
export class E10ApiClient {
|
|
@@ -62,6 +63,18 @@ export class E10ApiClient {
|
|
|
62
63
|
async requestRaw(url, init) {
|
|
63
64
|
let lastError = null;
|
|
64
65
|
let authenticationRecoveryAttempted = false;
|
|
66
|
+
if (this.auth.isExpired?.()) {
|
|
67
|
+
authenticationRecoveryAttempted = true;
|
|
68
|
+
const recovered = await this.recoverAuthentication?.(this.auth) || null;
|
|
69
|
+
if (!recovered || recovered.isExpired?.()) {
|
|
70
|
+
throw new Error(E10_XIAOE_RECOVERY_FAILED_MESSAGE);
|
|
71
|
+
}
|
|
72
|
+
if (!sameE10SessionIdentity(this.auth, recovered)) {
|
|
73
|
+
throw new E10AuthenticationContextChangedError('XiaoE automatic authentication recovery changed the E10 login context');
|
|
74
|
+
}
|
|
75
|
+
this.auth.replaceWith(recovered);
|
|
76
|
+
refreshAuthenticationHeaders(init, this.auth);
|
|
77
|
+
}
|
|
65
78
|
for (let attempt = 1; attempt <= this.retries; attempt++) {
|
|
66
79
|
try {
|
|
67
80
|
const resp = await fetch(url, { ...init, redirect: 'manual' });
|
|
@@ -1,25 +1,77 @@
|
|
|
1
1
|
import { CliError, ExitCode } from '../../core/errors.js';
|
|
2
|
-
import { E10_AUTH_REQUIRED_MESSAGE, E10_XIAOE_RECOVERY_FAILED_MESSAGE } from './auth/messages.js';
|
|
2
|
+
import { E10_AUTH_REQUIRED_MESSAGE, E10_XIAOE_HOST_CREDENTIAL_MISSING_MESSAGE, E10_XIAOE_NETWORK_UNAVAILABLE_MESSAGE, E10_XIAOE_RECOVERY_FAILED_MESSAGE, } from './auth/messages.js';
|
|
3
|
+
import { lastXiaoEFailure, summarizeXiaoEFailureKind, } from './auth/xiaoe.js';
|
|
3
4
|
import { E10AuthSession } from './auth/session.js';
|
|
4
5
|
import { E10ApiClient } from './client.js';
|
|
5
|
-
import { canAutomaticallyRecoverXiaoE, recoverXiaoEAuthSession } from './auth-recovery.js';
|
|
6
|
+
import { canAutomaticallyRecoverXiaoE, recoverXiaoEAuthSession, recoverXiaoEAuthSessionWithDiagnostics, } from './auth-recovery.js';
|
|
7
|
+
/**
|
|
8
|
+
* 把一次失败的静默恢复转成可定位的结构化错误。文案按归因分流:只有网络层
|
|
9
|
+
* 完全没打通时才提示「稍后重试」(此时登录态根本没被验证过,让人去重新登录
|
|
10
|
+
* 是误导);其余情况沿用原文案。诊断放进 `details`:`attemptedSteps` 说明
|
|
11
|
+
* 评估过哪些凭证通道及各自结果,`failures` 保留每一次失败的阶段、类别和
|
|
12
|
+
* `error.cause.code`,`lastError` 是最后一条真实原因(已脱敏截断),
|
|
13
|
+
* `resultSessionExpired` 表示票其实换到了、但被判为已过期。
|
|
14
|
+
*/
|
|
15
|
+
function xiaoERecoveryFailedError(diagnostics, resultSessionExpired = false) {
|
|
16
|
+
const failureKind = summarizeXiaoEFailureKind(diagnostics);
|
|
17
|
+
const lastFailure = lastXiaoEFailure(diagnostics);
|
|
18
|
+
const networkUnavailable = failureKind === 'network';
|
|
19
|
+
// Web sandboxes only have the platform credential channel. If the runtime
|
|
20
|
+
// marker survived but the host credential pair did not, asking the user to
|
|
21
|
+
// log in again is misleading: the sandbox launcher dropped the injection.
|
|
22
|
+
const hostCredentialMissing = diagnostics?.clientType === 'cloud'
|
|
23
|
+
&& diagnostics.platformCredentialDetected === false
|
|
24
|
+
&& diagnostics.attemptedSteps.includes('platform_token:missing')
|
|
25
|
+
&& diagnostics.attemptedSteps.includes('environment_map:empty');
|
|
26
|
+
const details = diagnostics
|
|
27
|
+
? {
|
|
28
|
+
...(diagnostics.baseUrl ? { baseUrl: diagnostics.baseUrl } : {}),
|
|
29
|
+
...(diagnostics.clientType ? { clientType: diagnostics.clientType } : {}),
|
|
30
|
+
...(diagnostics.platformCredentialDetected === undefined
|
|
31
|
+
? {}
|
|
32
|
+
: { platformCredentialDetected: diagnostics.platformCredentialDetected }),
|
|
33
|
+
attemptedSteps: diagnostics.attemptedSteps,
|
|
34
|
+
...(diagnostics.lastError ? { lastError: diagnostics.lastError } : {}),
|
|
35
|
+
...(failureKind ? { errorKind: failureKind } : {}),
|
|
36
|
+
...(lastFailure ? { failureStage: lastFailure.stage } : {}),
|
|
37
|
+
...(diagnostics.failures?.length ? { failures: diagnostics.failures } : {}),
|
|
38
|
+
...(hostCredentialMissing ? { reason: 'host_credential_not_injected' } : {}),
|
|
39
|
+
...(resultSessionExpired ? { resultSessionExpired: true } : {}),
|
|
40
|
+
}
|
|
41
|
+
: undefined;
|
|
42
|
+
return Object.assign(new CliError(networkUnavailable
|
|
43
|
+
? E10_XIAOE_NETWORK_UNAVAILABLE_MESSAGE
|
|
44
|
+
: hostCredentialMissing
|
|
45
|
+
? E10_XIAOE_HOST_CREDENTIAL_MISSING_MESSAGE
|
|
46
|
+
: E10_XIAOE_RECOVERY_FAILED_MESSAGE, ExitCode.AuthError), {
|
|
47
|
+
type: 'authentication',
|
|
48
|
+
subtype: networkUnavailable ? 'xiaoe_network_unavailable' : 'xiaoe_credential_unavailable',
|
|
49
|
+
// 网络错误是可重试的;凭证缺失/被拒重试也一样失败,不能让调用方空转。
|
|
50
|
+
retryable: networkUnavailable,
|
|
51
|
+
stage: lastFailure?.stage || 'xiaoe_unified_login',
|
|
52
|
+
...(details ? { details } : {}),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
6
55
|
export async function getE10Session(ctx, load = {}) {
|
|
7
56
|
const options = ctx.getGlobalOptions();
|
|
8
57
|
return E10AuthSession.fromOptions(options, ctx.cwd, load);
|
|
9
58
|
}
|
|
10
59
|
export async function requireE10Session(ctx, load = {}) {
|
|
11
60
|
let session = await getE10Session(ctx, load);
|
|
61
|
+
if (session?.isExpired() && canAutomaticallyRecoverXiaoE(ctx, session)) {
|
|
62
|
+
const outcome = await recoverXiaoEAuthSessionWithDiagnostics(ctx, session).catch(() => null);
|
|
63
|
+
session = outcome?.session || null;
|
|
64
|
+
if (!session || session.isExpired()) {
|
|
65
|
+
throw xiaoERecoveryFailedError(outcome?.diagnostics, session?.isExpired() === true);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
12
68
|
if (!session && canAutomaticallyRecoverXiaoE(ctx)) {
|
|
13
69
|
// Read-only probes use getE10Session; only a command requiring a session
|
|
14
70
|
// may establish one from the current XiaoE host's credentials.
|
|
15
|
-
|
|
71
|
+
const outcome = await recoverXiaoEAuthSessionWithDiagnostics(ctx).catch(() => null);
|
|
72
|
+
session = outcome?.session || null;
|
|
16
73
|
if (!session) {
|
|
17
|
-
throw
|
|
18
|
-
type: 'authentication',
|
|
19
|
-
subtype: 'xiaoe_credential_unavailable',
|
|
20
|
-
retryable: false,
|
|
21
|
-
stage: 'xiaoe_unified_login',
|
|
22
|
-
});
|
|
74
|
+
throw xiaoERecoveryFailedError(outcome?.diagnostics);
|
|
23
75
|
}
|
|
24
76
|
}
|
|
25
77
|
if (!session) {
|
|
@@ -1,4 +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';
|
|
4
|
+
export { canAutomaticallyRecoverXiaoE, isE10AuthenticationRequired, recoverXiaoEAuthSession, recoverXiaoEAuthSessionWithDiagnostics, } from './auth-recovery.js';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 网络错误的统一识别。
|
|
3
|
+
*
|
|
4
|
+
* Node/undici 的 `fetch` 只抛 `TypeError: fetch failed`,真实原因(拒绝连接、
|
|
5
|
+
* DNS 失败、TLS 失败、连接/响应超时)放在 `error.cause.code` 上。丢掉这条 code,
|
|
6
|
+
* 「连不上 E10」就会被误报成「登录态不可用」,把用户引导去重新登录。
|
|
7
|
+
*/
|
|
8
|
+
/** 明确属于「网络层没打通」的系统错误码(Node 常见 errno + undici 内部码)。 */
|
|
9
|
+
const NETWORK_ERROR_CODES = new Set([
|
|
10
|
+
'ECONNREFUSED',
|
|
11
|
+
'ECONNRESET',
|
|
12
|
+
'ECONNABORTED',
|
|
13
|
+
'EHOSTUNREACH',
|
|
14
|
+
'EHOSTDOWN',
|
|
15
|
+
'ENETUNREACH',
|
|
16
|
+
'ENETDOWN',
|
|
17
|
+
'ENETRESET',
|
|
18
|
+
'ENOTFOUND',
|
|
19
|
+
'EAI_AGAIN',
|
|
20
|
+
'EPIPE',
|
|
21
|
+
'ETIMEDOUT',
|
|
22
|
+
'ESOCKETTIMEDOUT',
|
|
23
|
+
'ERR_SOCKET_CONNECTION_TIMEOUT',
|
|
24
|
+
'UND_ERR_CONNECT_TIMEOUT',
|
|
25
|
+
'UND_ERR_HEADERS_TIMEOUT',
|
|
26
|
+
'UND_ERR_BODY_TIMEOUT',
|
|
27
|
+
'UND_ERR_SOCKET',
|
|
28
|
+
'UND_ERR_CLOSED',
|
|
29
|
+
'UND_ERR_ABORTED',
|
|
30
|
+
'CERT_HAS_EXPIRED',
|
|
31
|
+
'DEPTH_ZERO_SELF_SIGNED_CERT',
|
|
32
|
+
'SELF_SIGNED_CERT_IN_CHAIN',
|
|
33
|
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
|
|
34
|
+
'ERR_TLS_CERT_ALTNAME_INVALID',
|
|
35
|
+
'ERR_SSL_WRONG_VERSION_NUMBER',
|
|
36
|
+
]);
|
|
37
|
+
/** `AbortController.abort()` 抛出的 DOMException 没有 errno,统一用这个码表示「超时」。 */
|
|
38
|
+
export const ABORT_ERROR_CODE = 'ABORT_ERR';
|
|
39
|
+
function asRecord(value) {
|
|
40
|
+
return value && typeof value === 'object' ? value : null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 取错误链上最内层的系统错误码(`error.cause.code`);`AbortError` 归一化成
|
|
44
|
+
* `ABORT_ERR`。用 `seen` 防环:Node 的 cause 链偶尔会指回上层。
|
|
45
|
+
*/
|
|
46
|
+
export function readErrorCauseCode(error, maxDepth = 5) {
|
|
47
|
+
let current = error;
|
|
48
|
+
const seen = new Set();
|
|
49
|
+
for (let depth = 0; depth < maxDepth; depth += 1) {
|
|
50
|
+
if (current === undefined || current === null || seen.has(current))
|
|
51
|
+
return undefined;
|
|
52
|
+
seen.add(current);
|
|
53
|
+
if (current instanceof Error && current.name === 'AbortError')
|
|
54
|
+
return ABORT_ERROR_CODE;
|
|
55
|
+
const record = asRecord(current);
|
|
56
|
+
if (!record)
|
|
57
|
+
return undefined;
|
|
58
|
+
if (record.name === 'AbortError')
|
|
59
|
+
return ABORT_ERROR_CODE;
|
|
60
|
+
const code = record.code;
|
|
61
|
+
if (typeof code === 'string' && code)
|
|
62
|
+
return code;
|
|
63
|
+
const errno = record.errno;
|
|
64
|
+
if (typeof errno === 'string' && errno)
|
|
65
|
+
return errno;
|
|
66
|
+
if (typeof errno === 'number')
|
|
67
|
+
return String(errno);
|
|
68
|
+
current = record.cause;
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
export function isAbortError(error) {
|
|
73
|
+
if (error instanceof Error && error.name === 'AbortError')
|
|
74
|
+
return true;
|
|
75
|
+
return asRecord(error)?.name === 'AbortError';
|
|
76
|
+
}
|
|
77
|
+
export function isNetworkErrorCode(code) {
|
|
78
|
+
return typeof code === 'string' && NETWORK_ERROR_CODES.has(code.toUpperCase());
|
|
79
|
+
}
|
|
80
|
+
/** undici 把底层原因藏起来时只剩这句话,但语义已经很明确了。 */
|
|
81
|
+
const TRANSPORT_MESSAGE_PATTERN = /fetch failed|socket hang up|other side closed|network|connection (?:refused|reset|closed)|timed? ?out|timeout|certificate|getaddrinfo|dns lookup/i;
|
|
82
|
+
export function looksLikeTransportMessage(message) {
|
|
83
|
+
return TRANSPORT_MESSAGE_PATTERN.test(message);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 给一次 fetch(含读响应体)套上超时。调用方必须在 `finally` 里 `clear()`,
|
|
87
|
+
* 否则进程会被这一个定时器拖住。
|
|
88
|
+
*/
|
|
89
|
+
export function createTimeoutSignal(timeoutMs) {
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
92
|
+
return { signal: controller.signal, clear: () => clearTimeout(timer) };
|
|
93
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { CliError, ExitCode } from '../../core/errors.js';
|
|
2
2
|
import { classifyE10Redirect, isE10AuthenticationRequired, recoverXiaoEAuthSession, sameE10SessionIdentity, } from './auth-recovery.js';
|
|
3
3
|
import { startFetchTiming } from './fetch-timing.js';
|
|
4
|
+
import { readErrorCauseCode } from './network-error.js';
|
|
5
|
+
import { E10_XIAOE_RECOVERY_FAILED_MESSAGE } from './auth/messages.js';
|
|
4
6
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
5
7
|
const DEFAULT_BIG_INT_STRINGS = true;
|
|
6
8
|
/** 下载直传最多跟几跳:E10 -> 对象存储通常 1 跳,留一点余量但不允许成环。 */
|
|
@@ -53,6 +55,12 @@ export class E10RequestRuntime {
|
|
|
53
55
|
return this.request({ ...request, responseType: 'binary' });
|
|
54
56
|
}
|
|
55
57
|
async request(request) {
|
|
58
|
+
if (this.session.isExpired?.()) {
|
|
59
|
+
const recovered = await this.recoverExpiredSession(this.session.eteamsId);
|
|
60
|
+
if (!recovered || this.session.isExpired?.()) {
|
|
61
|
+
throw new E10ApiError('authentication', 'session_expired', E10_XIAOE_RECOVERY_FAILED_MESSAGE, { exitCode: ExitCode.AuthError, retryable: false });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
56
64
|
const mutable = this.prepareRequest(request);
|
|
57
65
|
try {
|
|
58
66
|
await this.runBeforeRequest(mutable, this.sessionContext());
|
|
@@ -171,6 +179,8 @@ export class E10RequestRuntime {
|
|
|
171
179
|
}
|
|
172
180
|
if (!recovered)
|
|
173
181
|
return false;
|
|
182
|
+
if (recovered.isExpired?.())
|
|
183
|
+
return false;
|
|
174
184
|
if (!sameE10SessionIdentity(this.session, recovered)) {
|
|
175
185
|
throw new E10ApiError('authentication', 'session_context_changed', 'XiaoE 自动恢复后的登录身份或 E10 环境发生变化,本次请求未重试', { exitCode: ExitCode.AuthError, retryable: false });
|
|
176
186
|
}
|
|
@@ -305,12 +315,17 @@ export class E10RequestRuntime {
|
|
|
305
315
|
throw new E10ApiError('network', 'timeout', `请求超时(${timeoutMs}ms)`, {
|
|
306
316
|
retryable: false,
|
|
307
317
|
exitCode: ExitCode.NetworkError,
|
|
318
|
+
details: { timeoutMs },
|
|
308
319
|
});
|
|
309
320
|
}
|
|
310
321
|
const message = error instanceof Error ? error.message : String(error);
|
|
322
|
+
// Node 的 `fetch failed` 不带原因,真正的 errno(ECONNREFUSED/ENOTFOUND…)
|
|
323
|
+
// 在 `error.cause.code` 上:保留下来才能区分「断网」与「服务端报错」。
|
|
324
|
+
const causeCode = readErrorCauseCode(error);
|
|
311
325
|
throw new E10ApiError('network', 'transport', `请求失败:${message}`, {
|
|
312
326
|
retryable: true,
|
|
313
327
|
exitCode: ExitCode.NetworkError,
|
|
328
|
+
...(causeCode ? { details: { causeCode } } : {}),
|
|
314
329
|
});
|
|
315
330
|
}
|
|
316
331
|
finally {
|
|
@@ -184,6 +184,36 @@ function firstId(value) {
|
|
|
184
184
|
return String(value);
|
|
185
185
|
return undefined;
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* 取「日程」应用 appId 与日程表单 formId(用于详情卡片页链接)。
|
|
189
|
+
* 偶发空返回时 getObjsByTag 原样重试最多 3 次。任一获取失败返回 null。
|
|
190
|
+
* 创建日程后需用 formId 拼卡片页链接,故抽为可复用函数。
|
|
191
|
+
*/
|
|
192
|
+
export async function fetchAgendaAppAndForm(host) {
|
|
193
|
+
const appIdsResponse = await host.request({
|
|
194
|
+
method: 'GET',
|
|
195
|
+
path: '/api/bs/ebuilder/app/tags/getAppIds',
|
|
196
|
+
operation: 'calendar.app.check',
|
|
197
|
+
query: { tag: AGENDA_APP_TAG },
|
|
198
|
+
}).catch(() => undefined);
|
|
199
|
+
const appId = firstId(appIdsResponse?.data);
|
|
200
|
+
let formId;
|
|
201
|
+
if (appId) {
|
|
202
|
+
for (let attempt = 0; attempt < 3 && !formId; attempt++) {
|
|
203
|
+
const formResponse = await host.request({
|
|
204
|
+
method: 'GET',
|
|
205
|
+
path: '/api/ebuilder/form/obj/getObjsByTag',
|
|
206
|
+
operation: 'calendar.app.check',
|
|
207
|
+
query: { tag: AGENDA_FORM_TAG, appId },
|
|
208
|
+
}).catch(() => undefined);
|
|
209
|
+
formId = firstId(formResponse?.data);
|
|
210
|
+
if (!formId && attempt < 2) {
|
|
211
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return { appId: appId ?? null, formId: formId ?? null };
|
|
216
|
+
}
|
|
187
217
|
const calendarAppCheck = {
|
|
188
218
|
name: 'calendar.app.check',
|
|
189
219
|
resource: 'calendar',
|
|
@@ -194,14 +224,7 @@ const calendarAppCheck = {
|
|
|
194
224
|
risk: 'read',
|
|
195
225
|
inputSchema: schemaOf({}),
|
|
196
226
|
handler: async (_args, { host }) => {
|
|
197
|
-
|
|
198
|
-
const appIdsResponse = await host.request({
|
|
199
|
-
method: 'GET',
|
|
200
|
-
path: '/api/bs/ebuilder/app/tags/getAppIds',
|
|
201
|
-
operation: 'calendar.app.check',
|
|
202
|
-
query: { tag: AGENDA_APP_TAG },
|
|
203
|
-
}).catch(() => undefined);
|
|
204
|
-
const appId = firstId(appIdsResponse?.data);
|
|
227
|
+
const { appId, formId } = await fetchAgendaAppAndForm(host);
|
|
205
228
|
if (!appId) {
|
|
206
229
|
return calendarSuccess('calendar.app.check', {
|
|
207
230
|
installed: false,
|
|
@@ -217,20 +240,6 @@ const calendarAppCheck = {
|
|
|
217
240
|
query: { appId },
|
|
218
241
|
}).catch(() => undefined);
|
|
219
242
|
const commitVersion = versionResponse?.data?.commitVersion ?? versionResponse?.commitVersion;
|
|
220
|
-
// 3. 查询日程表单 ID(偶发空返回,参数原样重试最多 3 次)
|
|
221
|
-
let formId;
|
|
222
|
-
for (let attempt = 0; attempt < 3 && !formId; attempt++) {
|
|
223
|
-
const formResponse = await host.request({
|
|
224
|
-
method: 'GET',
|
|
225
|
-
path: '/api/ebuilder/form/obj/getObjsByTag',
|
|
226
|
-
operation: 'calendar.app.check',
|
|
227
|
-
query: { tag: AGENDA_FORM_TAG, appId },
|
|
228
|
-
}).catch(() => undefined);
|
|
229
|
-
formId = firstId(formResponse?.data);
|
|
230
|
-
if (!formId && attempt < 2) {
|
|
231
|
-
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
243
|
const hasVersion = typeof commitVersion === 'string' && commitVersion.trim() !== '';
|
|
235
244
|
const installed = hasVersion && commitVersion !== null && commitVersion !== undefined;
|
|
236
245
|
const versionOk = installed && compareVersion(commitVersion, MIN_APP_VERSION) >= 0;
|
|
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { calendarConfirmationError, calendarPartialError, calendarValidationError, CalendarCliError } from '../errors.js';
|
|
3
3
|
import { calendarSuccess, schemaOf, SCHEMA_ID, SCHEMA_DATETIME_MINUTE, SCHEMA_SWITCH, SCHEMA_DATE } from './types.js';
|
|
4
4
|
import { assertDateTimeMinute, defaultEndTime, optionId, participantIds, pickDefined, } from './shared.js';
|
|
5
|
+
import { fetchAgendaAppAndForm } from './read.js';
|
|
5
6
|
const CONTINUATION_TTL_SECONDS = 600;
|
|
6
7
|
// 日程 mainTable 可选业务字段(创建/更新共用)
|
|
7
8
|
const OPTIONAL_FIELDS = [
|
|
@@ -81,6 +82,37 @@ async function verifyLease(args, action, env) {
|
|
|
81
82
|
function isNetworkError(error) {
|
|
82
83
|
return error instanceof CalendarCliError && error.type === 'network';
|
|
83
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* 从 saveFormData/createAgenda 返回体提取新建日程 ID。
|
|
87
|
+
* 兼容两种后端形态:
|
|
88
|
+
* - 顶层 `datajson.dataIds`(字符串数组)
|
|
89
|
+
* - `datajson.datas[].mainTable.dataIds` / `datajson.datas[].mainTable.id`
|
|
90
|
+
*/
|
|
91
|
+
function extractCreatedIds(response) {
|
|
92
|
+
const datajson = response?.datajson;
|
|
93
|
+
if (!datajson || typeof datajson !== 'object')
|
|
94
|
+
return [];
|
|
95
|
+
if (Array.isArray(datajson.dataIds)) {
|
|
96
|
+
return datajson.dataIds.map((v) => String(v)).filter(Boolean);
|
|
97
|
+
}
|
|
98
|
+
const datas = Array.isArray(datajson.datas) ? datajson.datas : [];
|
|
99
|
+
const ids = [];
|
|
100
|
+
for (const d of datas) {
|
|
101
|
+
const mt = d?.mainTable;
|
|
102
|
+
if (!mt)
|
|
103
|
+
continue;
|
|
104
|
+
if (Array.isArray(mt.dataIds))
|
|
105
|
+
ids.push(...mt.dataIds.map((v) => String(v)));
|
|
106
|
+
else if (mt.id !== undefined && mt.id !== null && mt.id !== '')
|
|
107
|
+
ids.push(String(mt.id));
|
|
108
|
+
}
|
|
109
|
+
return ids;
|
|
110
|
+
}
|
|
111
|
+
/** 拼日程详情卡片页链接(baseUrl 可能带协议头,统一归一成 https://host/...)。 */
|
|
112
|
+
function buildCardUrl(baseUrl, formId, id) {
|
|
113
|
+
const hostPart = String(baseUrl || '').replace(/^https?:\/\//i, '').replace(/\/+$/, '');
|
|
114
|
+
return `https://${hostPart}/sp/ebdfpage/card/0/${formId}/${id}`;
|
|
115
|
+
}
|
|
84
116
|
/** 构造写入接口 datajson 请求体(datas[].mainTable 结构)。 */
|
|
85
117
|
async function buildWriteDatajson(host, mainTableList) {
|
|
86
118
|
const session = await host.getSession();
|
|
@@ -195,9 +227,21 @@ const calendarCreateApply = {
|
|
|
195
227
|
}
|
|
196
228
|
throw error;
|
|
197
229
|
}
|
|
230
|
+
const createdIds = extractCreatedIds(response);
|
|
231
|
+
const createdId = createdIds[0];
|
|
232
|
+
const session = await env.host.getSession();
|
|
233
|
+
let accessUrl = null;
|
|
234
|
+
if (createdId) {
|
|
235
|
+
const { formId } = await fetchAgendaAppAndForm(env.host);
|
|
236
|
+
if (formId) {
|
|
237
|
+
accessUrl = buildCardUrl(session.baseUrl, formId, createdId);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
198
240
|
return calendarSuccess('calendar.create.apply', {
|
|
199
241
|
status: 'COMPLETE',
|
|
200
|
-
|
|
242
|
+
createdId: createdId ?? null,
|
|
243
|
+
createdIds,
|
|
244
|
+
accessUrl,
|
|
201
245
|
});
|
|
202
246
|
},
|
|
203
247
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CoworkCliError, coworkNotFoundError, coworkPartialError, coworkValidationError } from '../errors.js';
|
|
2
2
|
import { READ_BEFORE_WRITE, WRITE, SCHEMA_ID, coworkSuccess, schemaOf, } from './types.js';
|
|
3
|
-
import { assertEnum, assertString, compact, normalizeList, requireConfirm, toBase64Html, toId, toPositiveInt, unwrapData, verifyLease, } from './shared.js';
|
|
3
|
+
import { assertEnum, assertString, compact, coworkViewUrl, normalizeList, requireConfirm, toBase64Html, toId, toPositiveInt, unwrapData, verifyLease, } from './shared.js';
|
|
4
4
|
function isNetworkError(error) {
|
|
5
5
|
return error instanceof CoworkCliError && error.type === 'network';
|
|
6
6
|
}
|
|
@@ -132,6 +132,8 @@ const discussPublishApply = {
|
|
|
132
132
|
coworkId,
|
|
133
133
|
sectionId,
|
|
134
134
|
status: data.status ?? root?.status ?? null,
|
|
135
|
+
viewUrl: coworkViewUrl(lease.baseUrl, coworkId),
|
|
136
|
+
mobileViewUrl: coworkViewUrl(lease.baseUrl, coworkId, true),
|
|
135
137
|
});
|
|
136
138
|
},
|
|
137
139
|
};
|
|
@@ -266,6 +268,8 @@ const discussActionApply = {
|
|
|
266
268
|
coworkId,
|
|
267
269
|
discussId,
|
|
268
270
|
result: unwrapData(response) ?? null,
|
|
271
|
+
viewUrl: coworkViewUrl(lease.baseUrl, coworkId),
|
|
272
|
+
mobileViewUrl: coworkViewUrl(lease.baseUrl, coworkId, true),
|
|
269
273
|
});
|
|
270
274
|
},
|
|
271
275
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CoworkCliError, coworkNotFoundError, coworkPartialError, coworkValidationError } from '../errors.js';
|
|
2
2
|
import { READ_BEFORE_WRITE, WRITE, SCHEMA_ID, SCHEMA_DATETIME, coworkSuccess, schemaOf, } from './types.js';
|
|
3
|
-
import { assertEnum, assertString, requireConfirm, toBase64Html, toId, unwrapData, verifyLease, } from './shared.js';
|
|
3
|
+
import { assertEnum, assertString, coworkViewUrl, requireConfirm, toBase64Html, toId, unwrapData, verifyLease, } from './shared.js';
|
|
4
4
|
function isNetworkError(error) {
|
|
5
5
|
return error instanceof CoworkCliError && error.type === 'network';
|
|
6
6
|
}
|
|
@@ -203,6 +203,8 @@ const itemCreateApply = {
|
|
|
203
203
|
sectionId,
|
|
204
204
|
principal,
|
|
205
205
|
endTime,
|
|
206
|
+
viewUrl: coworkViewUrl(lease.baseUrl, data.id),
|
|
207
|
+
mobileViewUrl: coworkViewUrl(lease.baseUrl, data.id, true),
|
|
206
208
|
});
|
|
207
209
|
},
|
|
208
210
|
};
|
|
@@ -295,7 +297,7 @@ const itemActionApply = {
|
|
|
295
297
|
}
|
|
296
298
|
throw error;
|
|
297
299
|
}
|
|
298
|
-
return coworkSuccess('cowork.item.action.apply', { action, id, result: unwrapData(response) ?? null });
|
|
300
|
+
return coworkSuccess('cowork.item.action.apply', { action, id, result: unwrapData(response) ?? null, viewUrl: coworkViewUrl(lease.baseUrl, id) });
|
|
299
301
|
},
|
|
300
302
|
};
|
|
301
303
|
const ITEM_FLAGS = ['hide', 'vote'];
|
|
@@ -367,7 +369,7 @@ const itemFlagApply = {
|
|
|
367
369
|
form,
|
|
368
370
|
operation: 'cowork.item.flag.apply',
|
|
369
371
|
});
|
|
370
|
-
return coworkSuccess('cowork.item.flag.apply', { action, id, result: unwrapData(response) ?? null });
|
|
372
|
+
return coworkSuccess('cowork.item.flag.apply', { action, id, result: unwrapData(response) ?? null, viewUrl: coworkViewUrl(lease.baseUrl, id) });
|
|
371
373
|
},
|
|
372
374
|
};
|
|
373
375
|
export const coworkItemWriteOperations = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CoworkCliError, coworkNotFoundError, coworkPartialError, coworkValidationError } from '../errors.js';
|
|
2
2
|
import { READ_BEFORE_WRITE, WRITE, SCHEMA_ID, coworkSuccess, schemaOf, } from './types.js';
|
|
3
|
-
import { assertEnum, compact, normalizeList, requireConfirm, toId, toIdList, toPositiveInt, unwrapData, verifyLease, } from './shared.js';
|
|
3
|
+
import { assertEnum, compact, coworkViewUrl, normalizeList, requireConfirm, toId, toIdList, toPositiveInt, unwrapData, verifyLease, } from './shared.js';
|
|
4
4
|
function isNetworkError(error) {
|
|
5
5
|
return error instanceof CoworkCliError && error.type === 'network';
|
|
6
6
|
}
|
|
@@ -153,6 +153,8 @@ const memberActionApply = {
|
|
|
153
153
|
coworkId,
|
|
154
154
|
userIds: action === 'join' ? undefined : String(form.ids).split(','),
|
|
155
155
|
result: unwrapData(response) ?? null,
|
|
156
|
+
viewUrl: coworkViewUrl(lease.baseUrl, coworkId),
|
|
157
|
+
mobileViewUrl: coworkViewUrl(lease.baseUrl, coworkId, true),
|
|
156
158
|
});
|
|
157
159
|
},
|
|
158
160
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CoworkCliError, coworkNotFoundError, coworkPartialError, coworkValidationError } from '../errors.js';
|
|
2
2
|
import { READ_BEFORE_WRITE, WRITE, SCHEMA_ID, coworkSuccess, schemaOf, } from './types.js';
|
|
3
|
-
import { normalizeList, requireConfirm, toId, unwrapData, verifyLease, } from './shared.js';
|
|
3
|
+
import { coworkViewUrl, normalizeList, requireConfirm, toId, unwrapData, verifyLease, } from './shared.js';
|
|
4
4
|
function isNetworkError(error) {
|
|
5
5
|
return error instanceof CoworkCliError && error.type === 'network';
|
|
6
6
|
}
|
|
@@ -163,6 +163,8 @@ const permAddApply = {
|
|
|
163
163
|
userId,
|
|
164
164
|
userName,
|
|
165
165
|
result: unwrapData(response) ?? null,
|
|
166
|
+
viewUrl: coworkViewUrl(lease.baseUrl, coworkId),
|
|
167
|
+
mobileViewUrl: coworkViewUrl(lease.baseUrl, coworkId, true),
|
|
166
168
|
});
|
|
167
169
|
},
|
|
168
170
|
};
|
|
@@ -275,6 +277,8 @@ const permRemoveApply = {
|
|
|
275
277
|
removed: stillThere === false ? true : undefined,
|
|
276
278
|
verified: stillThere !== null,
|
|
277
279
|
result: unwrapData(response) ?? null,
|
|
280
|
+
viewUrl: coworkViewUrl(lease.baseUrl, coworkId),
|
|
281
|
+
mobileViewUrl: coworkViewUrl(lease.baseUrl, coworkId, true),
|
|
278
282
|
});
|
|
279
283
|
},
|
|
280
284
|
};
|
|
@@ -88,6 +88,13 @@ export function unwrapData(response) {
|
|
|
88
88
|
return response;
|
|
89
89
|
return response.data;
|
|
90
90
|
}
|
|
91
|
+
/** 协作查看页地址:PC `/sp/cowork/view/{id}`、移动端 `/mobile/cowork/view/{id}`(真实环境取证形态,禁止改拼)。 */
|
|
92
|
+
export function coworkViewUrl(baseUrl, id, mobile = false) {
|
|
93
|
+
if (!id)
|
|
94
|
+
return null;
|
|
95
|
+
const base = String(baseUrl || '').replace(/\/+$/, '');
|
|
96
|
+
return `${base}/${mobile ? 'mobile' : 'sp'}/cowork/view/${id}`;
|
|
97
|
+
}
|
|
91
98
|
/** 协作列表的 data 形如 { pageSize, total, current, pageUid, columns[], data[] }。 */
|
|
92
99
|
export function normalizePaged(response) {
|
|
93
100
|
const data = unwrapData(response);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { ehrConfirmationError, ehrPartialError, ehrValidationError, EhrCliError } from '../errors.js';
|
|
3
3
|
import { READ, SCHEMA_DATE, SCHEMA_ID, ehrSuccess, schemaOf, WRITE, } from './types.js';
|
|
4
|
-
import { assertEnum, assertEnumArray, compact, pickDefined, toIdList } from './shared.js';
|
|
4
|
+
import { assertEnum, assertEnumArray, compact, pickDefined, toIdList, EHR_PAGES, ehrPageUrl, addDays } from './shared.js';
|
|
5
5
|
const CONTINUATION_TTL_SECONDS = 600;
|
|
6
6
|
const CARD_TYPES = ['CHECKIN', 'CHECKOUT', 'LEAVE', 'RETURN', 'CHECKIN_AND_RETURN', 'CHECKOUT_AND_LEAVE'];
|
|
7
7
|
const APPEAL_TYPES = [
|
|
@@ -316,9 +316,51 @@ const ehrAttendAppealSaveApply = {
|
|
|
316
316
|
}
|
|
317
317
|
throw error;
|
|
318
318
|
}
|
|
319
|
+
// 回执(按 references/attend-appeal-save.md 契约,2026-11-04):
|
|
320
|
+
// ① 回查申诉列表定位本次新建记录(日期前后各放宽 1 天,防跨天/时区边界漏查);
|
|
321
|
+
// ② 返回 appealListUrl(考勤异常/申诉页面,已取证路由)。
|
|
322
|
+
// 回查失败不阻塞提交结果(提交已成功),以 locateWarning 如实提示。
|
|
323
|
+
const session = await env.host.getSession();
|
|
324
|
+
const appealListUrl = ehrPageUrl(session.baseUrl, EHR_PAGES.attendAbnormal);
|
|
325
|
+
let appeal = null;
|
|
326
|
+
let locateWarning;
|
|
327
|
+
try {
|
|
328
|
+
const listResp = await env.host.request({
|
|
329
|
+
method: 'POST',
|
|
330
|
+
path: '/api/attend/web/appeal/getAppealList',
|
|
331
|
+
body: {
|
|
332
|
+
beginDate: addDays(String(args.attendDay), -1),
|
|
333
|
+
endDate: addDays(String(args.attendDay), 1),
|
|
334
|
+
current: 1,
|
|
335
|
+
pageSize: 50,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
const payload = listResp?.data;
|
|
339
|
+
const rows = Array.isArray(payload?.data) ? payload.data
|
|
340
|
+
: Array.isArray(payload?.rows) ? payload.rows
|
|
341
|
+
: Array.isArray(payload) ? payload
|
|
342
|
+
: [];
|
|
343
|
+
const day = String(args.attendDay);
|
|
344
|
+
const desc = String(args.appealDesc ?? '');
|
|
345
|
+
const pick = rows.find((r) => String(r?.attendDay ?? r?.attend_date ?? r?.attendDate ?? '').startsWith(day))
|
|
346
|
+
|| (desc ? rows.find((r) => String(r?.appealDesc ?? r?.appeal_desc ?? '').includes(desc)) : null)
|
|
347
|
+
|| null;
|
|
348
|
+
if (pick)
|
|
349
|
+
appeal = pick;
|
|
350
|
+
else
|
|
351
|
+
locateWarning = '未在申诉列表中定位到本次记录(可能刚提交尚在同步),请稍后用 ehr.attend.appeal.list 复核';
|
|
352
|
+
}
|
|
353
|
+
catch (e) {
|
|
354
|
+
locateWarning = '申诉列表回查失败:' + (e?.message || '未知原因') + ';请稍后用 ehr.attend.appeal.list 复核';
|
|
355
|
+
}
|
|
319
356
|
return ehrSuccess('ehr.attend.appeal.save.apply', {
|
|
320
357
|
status: 'COMPLETE',
|
|
321
358
|
result: response?.data,
|
|
359
|
+
/** 定位到的新建申诉记录(含申诉 ID 等,处理申诉时用其 id) */
|
|
360
|
+
appeal,
|
|
361
|
+
/** 考勤异常/申诉页面地址(已取证路由,禁止改拼) */
|
|
362
|
+
appealListUrl,
|
|
363
|
+
...(locateWarning ? { locateWarning } : {}),
|
|
322
364
|
verifyHint: '可通过 ehr.attend.appeal.list 回查申诉是否已生成',
|
|
323
365
|
});
|
|
324
366
|
},
|