weaver-work-cli 0.1.40 → 0.1.44
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 +17 -0
- package/dist/chunks/{archive-JHTONRST.js → archive-P6TBQGKL.js} +13 -5
- package/dist/chunks/{archive-oa-FCHLCPHC.js → archive-oa-7TQ2NXOH.js} +12 -5
- package/dist/chunks/{asset-RLIHC7DB.js → asset-3ELRG37G.js} +11 -4
- package/dist/chunks/{asset-idle-KVOFTLCP.js → asset-idle-WIGMUFMD.js} +7 -3
- package/dist/chunks/{attachment-content-RZYDB3MB.js → attachment-content-E3276UOV.js} +5 -4
- package/dist/chunks/{blog-42OZTJNP.js → blog-C5NMZAAC.js} +11 -4
- package/dist/chunks/{caizhilian-KRK547K5.js → caizhilian-5XSM2DFP.js} +13 -6
- package/dist/chunks/{calendar-RTWAMKHD.js → calendar-FI2WNSBO.js} +71 -26
- package/dist/chunks/{chunk-CXRQWOZ5.js → chunk-3YBDYOC4.js} +3 -3
- package/dist/chunks/{chunk-OOUTBLZ4.js → chunk-56EZERK4.js} +1 -1
- package/dist/chunks/chunk-6N4JPNFL.js +76 -0
- package/dist/chunks/{chunk-ZUX3P6BQ.js → chunk-727FH2NI.js} +1 -1
- package/dist/chunks/chunk-7BDPM5ST.js +11 -0
- package/dist/chunks/{chunk-LN447PY2.js → chunk-BCVYBYYT.js} +432 -2247
- package/dist/chunks/{chunk-7E6MUOEK.js → chunk-CWYGFU5B.js} +2 -2
- package/dist/chunks/{chunk-NOODWMCL.js → chunk-D4ADSZ7H.js} +1 -1
- package/dist/chunks/{chunk-WFFNM65B.js → chunk-ETNJSKMM.js} +281 -29
- package/dist/chunks/{chunk-CAQNBBQA.js → chunk-F4E4EM35.js} +3 -3
- package/dist/chunks/{chunk-4CTESSKI.js → chunk-FAZTGX7S.js} +6 -6
- package/dist/chunks/chunk-FRZKBMSW.js +2562 -0
- package/dist/chunks/{chunk-BYBSCN4X.js → chunk-G7PQWD4L.js} +6 -3
- package/dist/chunks/{chunk-NJKREDA3.js → chunk-GAMZLAG2.js} +1 -1
- package/dist/chunks/{chunk-FLS4DPGP.js → chunk-GM6J7Q4B.js} +28 -15
- package/dist/chunks/{chunk-NETCCARD.js → chunk-GRDRG6HP.js} +3 -3
- package/dist/chunks/chunk-HIT4Y6OI.js +1238 -0
- package/dist/chunks/{chunk-EZALDOQH.js → chunk-HP7JIYMT.js} +5 -2
- package/dist/chunks/{chunk-IZSSVYAG.js → chunk-IAZ3UPY6.js} +3 -3
- package/dist/chunks/{chunk-ZEDWSH32.js → chunk-IC62NFM5.js} +1 -1
- package/dist/chunks/{chunk-P26ZWSKU.js → chunk-KIHNAS25.js} +5 -2
- package/dist/chunks/{chunk-57IECMWX.js → chunk-N4M2DDFE.js} +29 -15
- package/dist/chunks/{chunk-JWRZM2H2.js → chunk-OF4S23UB.js} +4 -4
- package/dist/chunks/{chunk-VNBU55SL.js → chunk-PC7VKEB5.js} +80 -2458
- package/dist/chunks/{chunk-G2JSIUWF.js → chunk-PUQS5MWL.js} +91 -2
- package/dist/chunks/{chunk-E7OK5WD5.js → chunk-Q4OYAQYH.js} +5 -5
- package/dist/chunks/{chunk-7BRU4EFH.js → chunk-QVSWRLLU.js} +35 -6
- package/dist/chunks/{chunk-T6EGVBKU.js → chunk-TA7FXXJD.js} +6 -3
- package/dist/chunks/{chunk-GPMFD4RD.js → chunk-TXUG266F.js} +1 -1
- package/dist/chunks/{chunk-4TEDNCON.js → chunk-UFIT2UU3.js} +354 -56
- package/dist/chunks/{chunk-LUSPKZKY.js → chunk-UG5G54VL.js} +1 -0
- package/dist/chunks/{chunk-NIAJ5ZCG.js → chunk-UL5XUQ25.js} +5 -2
- package/dist/chunks/{chunk-FMPJJYYI.js → chunk-WRXSAVKO.js} +1 -1
- package/dist/chunks/{chunk-7U577Q7U.js → chunk-WSF4NNMR.js} +3 -3
- package/dist/chunks/chunk-XHK4MDR5.js +2497 -0
- package/dist/chunks/{chunk-LFMSDBZN.js → chunk-XNIK66I4.js} +326 -161
- package/dist/chunks/{chunk-3SO6QL6U.js → chunk-YWX377BY.js} +5 -2
- package/dist/chunks/{chunk-BYATLLEF.js → chunk-Z3GDFVJR.js} +1 -1
- package/dist/chunks/{chunk-I566ZYNF.js → chunk-Z7VYWPZV.js} +1 -1
- package/dist/chunks/{conflict-VR55AOOO.js → conflict-ARLSZJ6V.js} +4 -3
- package/dist/chunks/{continuation-2CKSBLA6.js → continuation-3S37AUSH.js} +4 -3
- package/dist/chunks/{continuation-RN6PZN2M.js → continuation-3XLYYTKV.js} +4 -3
- package/dist/chunks/{continuation-HZIZRZBX.js → continuation-HFW2YT5S.js} +4 -3
- package/dist/chunks/{continuation-NLECEJPX.js → continuation-KI7DRRZN.js} +4 -3
- package/dist/chunks/{contract-HI4FNPYE.js → contract-DNIIW5VJ.js} +13 -5
- package/dist/chunks/{cowork-HJLI353D.js → cowork-GMLRGVIW.js} +31 -14
- package/dist/chunks/create-flow-J22BO65E.js +56 -0
- package/dist/chunks/{ebuilder-form-HTGI63UU.js → ebuilder-form-ANV2FHYV.js} +12 -7
- package/dist/chunks/{ehr-GYXIYH3A.js → ehr-GSTGAIY3.js} +695 -1255
- package/dist/chunks/{employee-SAK2QYXX.js → employee-22BGJ3QS.js} +3 -3
- package/dist/chunks/{env-DW3G4HUO.js → env-BX3VRFS7.js} +4 -3
- package/dist/chunks/{esb-I5INCEFE.js → esb-ATAIASX2.js} +12 -7
- package/dist/chunks/{factory-TKXGXS6L.js → factory-PETTFME6.js} +3 -2
- package/dist/chunks/{fna-K2MHTQ24.js → fna-YEKBSFSX.js} +14 -8
- package/dist/chunks/{group-chat-VYBNMJLG.js → group-chat-BFVXAYFX.js} +7 -4
- package/dist/chunks/{host-D5SERMAJ.js → host-HRBXEPDQ.js} +5 -5
- package/dist/chunks/{host-JM4YVHFM.js → host-LR55CTHP.js} +5 -5
- package/dist/chunks/{host-L3VLITMT.js → host-SUOY6VYQ.js} +5 -5
- package/dist/chunks/{host-ZUQTJYYK.js → host-SZGV4UNT.js} +5 -5
- package/dist/chunks/{host-256GVCAH.js → host-WIKN2EJ5.js} +5 -5
- package/dist/chunks/{hrm-VLRBKTZB.js → hrm-NRDO4W5G.js} +11 -7
- package/dist/chunks/{im-QK3VTLXK.js → im-IQ2HN23S.js} +11 -10
- package/dist/chunks/{invoice-T5JTA32H.js → invoice-MNRW76PP.js} +636 -1768
- package/dist/chunks/{jiuchuanhui-P2FNEQVZ.js → jiuchuanhui-DTRBAX4N.js} +14 -6
- package/dist/chunks/{lazy-dispatch-XRRI46M6.js → lazy-dispatch-ZVWAI25B.js} +7 -6
- package/dist/chunks/{mail-3Y7MJ6PS.js → mail-ZVAUXOJA.js} +16 -14
- package/dist/chunks/{meeting-M7LTXGJE.js → meeting-FEVPZ67S.js} +21 -13
- package/dist/chunks/{okr-OBC6S67L.js → okr-SDH3E5RA.js} +12 -5
- package/dist/chunks/{operations-5QL3AECO.js → operations-62N6EP3C.js} +4 -3
- package/dist/chunks/{plan-5H6I53SK.js → plan-E35JFDI7.js} +12 -5
- package/dist/chunks/{project-L75QZV4S.js → project-73QXBAB2.js} +49 -25
- package/dist/chunks/reception-UWZVACBN.js +1899 -0
- package/dist/chunks/recruit-stage-4MAOCKFY.js +4770 -0
- package/dist/chunks/registry-IM5G23LS.js +24 -0
- package/dist/chunks/{registry-UGRBCBHO.js → registry-JDUS4E7D.js} +7 -6
- package/dist/chunks/{registry-RXPN6BBT.js → registry-KNKSCJNT.js} +7 -6
- package/dist/chunks/{registry-NYPYMZXG.js → registry-PO7SI256.js} +4 -4
- package/dist/chunks/{registry-QGHFNUWS.js → registry-PVUZQDDQ.js} +3 -3
- package/dist/chunks/{room-recommend-A3UDC3CA.js → room-recommend-H7KLXA5R.js} +6 -5
- package/dist/chunks/{schedule-5GCACCK7.js → schedule-ESZIBAUO.js} +6 -5
- package/dist/chunks/{workflow-M5ZJ7DWF.js → workflow-CWJFJLQU.js} +15 -10
- package/dist/cmd/action.js +6 -0
- package/dist/cmd/identity/index.js +45 -0
- package/dist/cmd/index.js +2 -0
- package/dist/core/continuation-ttl.js +18 -0
- package/dist/core/error-codes.js +85 -0
- package/dist/core/logger.js +11 -0
- package/dist/index.js +41 -30
- package/dist/internal/e10/auth/commands.js +164 -8
- package/dist/internal/e10/auth/error-log.js +182 -0
- package/dist/internal/e10/auth/messages.js +6 -0
- package/dist/internal/e10/auth/session.js +34 -3
- package/dist/internal/e10/auth/xiaoe.js +286 -44
- package/dist/internal/e10/auth-recovery.js +72 -14
- package/dist/internal/e10/context.js +72 -13
- package/dist/internal/e10/index.js +1 -1
- package/dist/internal/e10/network-error.js +93 -0
- package/dist/internal/e10/request-runtime.js +6 -0
- package/dist/internal/skills/install.js +9 -1
- package/dist/shortcuts/archive/continuation.js +2 -1
- package/dist/shortcuts/archive/index.js +3 -0
- package/dist/shortcuts/archive/operations/confirm.js +2 -1
- package/dist/shortcuts/archive-oa/continuation.js +2 -1
- package/dist/shortcuts/archive-oa/index.js +2 -0
- package/dist/shortcuts/archive-oa/operations/confirm.js +2 -1
- package/dist/shortcuts/asset/continuation.js +2 -1
- package/dist/shortcuts/asset/index.js +2 -0
- package/dist/shortcuts/asset-idle/index.js +2 -0
- package/dist/shortcuts/blog/continuation.js +2 -1
- package/dist/shortcuts/blog/index.js +2 -0
- package/dist/shortcuts/caizhilian/continuation.js +2 -1
- package/dist/shortcuts/caizhilian/index.js +2 -0
- package/dist/shortcuts/caizhilian/operations/confirm.js +3 -2
- package/dist/shortcuts/calendar/continuation.js +2 -1
- package/dist/shortcuts/calendar/index.js +2 -0
- package/dist/shortcuts/calendar/operations/read.js +31 -22
- package/dist/shortcuts/calendar/operations/write.js +46 -2
- package/dist/shortcuts/contract/continuation.js +2 -1
- package/dist/shortcuts/contract/index.js +2 -0
- package/dist/shortcuts/contract/operations/write.js +2 -1
- package/dist/shortcuts/cowork/continuation.js +2 -1
- package/dist/shortcuts/cowork/index.js +2 -0
- package/dist/shortcuts/cowork/operations/discuss-write.js +5 -1
- package/dist/shortcuts/cowork/operations/item-write.js +1 -7
- 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/ebuilder-form/continuation.js +2 -1
- package/dist/shortcuts/ebuilder-form/index.js +2 -0
- package/dist/shortcuts/ebuilder-form/operations/write.js +3 -2
- package/dist/shortcuts/ehr/continuation.js +2 -1
- package/dist/shortcuts/ehr/index.js +2 -0
- package/dist/shortcuts/ehr/operations/attend-write.js +44 -2
- 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 +317 -63
- package/dist/shortcuts/ehr/operations/recruit-position.js +134 -2
- package/dist/shortcuts/ehr/operations/recruit-write.js +263 -18
- package/dist/shortcuts/ehr/operations/recruit.js +10 -4
- package/dist/shortcuts/ehr/operations/shared.js +47 -0
- package/dist/shortcuts/esb/continuation.js +2 -1
- package/dist/shortcuts/esb/index.js +2 -0
- package/dist/shortcuts/esb/operations/trigger.js +2 -1
- package/dist/shortcuts/fna/continuation.js +2 -1
- package/dist/shortcuts/fna/index.js +2 -0
- package/dist/shortcuts/fna/operations/invoice-writeoff.js +1 -1
- package/dist/shortcuts/fna/operations/loan.js +1 -1
- package/dist/shortcuts/hrm/index.js +2 -0
- package/dist/shortcuts/hrm/operations/org.js +118 -0
- package/dist/shortcuts/im/continuation.js +2 -1
- package/dist/shortcuts/index.js +10 -0
- package/dist/shortcuts/invoice/continuation.js +2 -1
- package/dist/shortcuts/invoice/index.js +14 -15
- package/dist/shortcuts/invoice/invoice-record.js +93 -0
- package/dist/shortcuts/invoice/manifest.js +190 -393
- package/dist/shortcuts/invoice/operations/add.js +2 -1
- package/dist/shortcuts/invoice/operations/delete.js +2 -1
- package/dist/shortcuts/invoice/operations/import.js +2 -1
- package/dist/shortcuts/invoice/operations/issuing-contract-detail.js +86 -0
- package/dist/shortcuts/invoice/operations/issuing-contracts.js +210 -0
- package/dist/shortcuts/invoice/operations/registry.js +17 -17
- package/dist/shortcuts/invoice/operations/reim-auto.js +4 -569
- package/dist/shortcuts/invoice/operations/reim.js +52 -341
- package/dist/shortcuts/invoice/operations/shared.js +21 -4
- package/dist/shortcuts/invoice/operations/update.js +2 -1
- package/dist/shortcuts/invoice/projection.js +10 -62
- package/dist/shortcuts/jiuchuanhui/continuation.js +2 -1
- package/dist/shortcuts/jiuchuanhui/index.js +2 -0
- package/dist/shortcuts/jiuchuanhui/operations/datajson.js +2 -1
- package/dist/shortcuts/jiuchuanhui/operations/esb.js +2 -1
- package/dist/shortcuts/mail/continuation.js +2 -1
- package/dist/shortcuts/mail/index.js +2 -0
- package/dist/shortcuts/mail/operations/mail-blacklist.js +1 -1
- package/dist/shortcuts/mail/operations/mail-contacts.js +1 -1
- package/dist/shortcuts/mail/operations/mail-send.js +1 -1
- package/dist/shortcuts/mail/operations/mail-sign.js +1 -1
- package/dist/shortcuts/mail/operations/mail-template.js +1 -1
- package/dist/shortcuts/meeting/continuation.js +2 -1
- package/dist/shortcuts/meeting/index.js +2 -0
- package/dist/shortcuts/meeting/operations/change.js +1 -1
- package/dist/shortcuts/meeting/operations/create.js +1 -1
- package/dist/shortcuts/meeting/operations/eb/catalog.js +4 -1
- package/dist/shortcuts/meeting/operations/eb/create-flow.js +101 -35
- package/dist/shortcuts/meeting/operations/eb/decision.js +1 -1
- package/dist/shortcuts/meeting/operations/eb/factory.js +34 -1
- package/dist/shortcuts/meeting/operations/eb/group-chat.js +25 -3
- package/dist/shortcuts/meeting/operations/eb/person-cache.js +96 -0
- package/dist/shortcuts/meeting/operations/eb/room-recommend.js +33 -11
- package/dist/shortcuts/meeting/operations/lifecycle.js +1 -1
- package/dist/shortcuts/meeting/operations/receipts.js +1 -1
- package/dist/shortcuts/meeting/operations/signs.js +1 -1
- package/dist/shortcuts/okr/continuation.js +2 -1
- package/dist/shortcuts/okr/index.js +2 -0
- package/dist/shortcuts/okr/operations/write.js +1 -1
- package/dist/shortcuts/plan/continuation.js +2 -1
- package/dist/shortcuts/plan/index.js +2 -0
- package/dist/shortcuts/plan/operations/write.js +1 -1
- package/dist/shortcuts/project/continuation.js +2 -1
- package/dist/shortcuts/project/index.js +2 -0
- 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/reception/constants.js +53 -0
- package/dist/shortcuts/reception/errors.js +54 -0
- package/dist/shortcuts/reception/host.js +125 -0
- package/dist/shortcuts/reception/index.js +198 -0
- package/dist/shortcuts/reception/manifest.js +177 -0
- package/dist/shortcuts/reception/operations/ask.js +71 -0
- package/dist/shortcuts/reception/operations/browsertool.js +58 -0
- package/dist/shortcuts/reception/operations/compose.js +129 -0
- package/dist/shortcuts/reception/operations/customparams.js +74 -0
- package/dist/shortcuts/reception/operations/doc.js +238 -0
- package/dist/shortcuts/reception/operations/docrender.js +36 -0
- package/dist/shortcuts/reception/operations/panels.js +179 -0
- package/dist/shortcuts/reception/operations/registry.js +26 -0
- package/dist/shortcuts/reception/operations/render-assets.js +184 -0
- package/dist/shortcuts/reception/operations/render.js +123 -0
- package/dist/shortcuts/reception/operations/runs.js +38 -0
- package/dist/shortcuts/reception/operations/schedule.js +127 -0
- package/dist/shortcuts/reception/operations/shared.js +196 -0
- package/dist/shortcuts/reception/operations/snapshot.js +68 -0
- package/dist/shortcuts/reception/operations/types.js +1 -0
- package/dist/shortcuts/reception/operations.js +33 -0
- package/dist/shortcuts/reception/upload.js +218 -0
- package/dist/shortcuts/recruit-stage/context.js +231 -0
- package/dist/shortcuts/recruit-stage/continuation.js +74 -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 +719 -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 +1354 -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/continuation.js +2 -1
- package/dist/shortcuts/workflow/index.js +2 -0
- package/dist/shortcuts/workflow/operations/form.js +32 -8
- package/dist/shortcuts/workflow/operations/todoSmartConvert/detailFieldRules.js +4 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/summaryCache.js +13 -6
- 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 +50 -8
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.core.js +2 -1
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.detail.js +17 -11
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.group.js +7 -39
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.groupSummary.js +3 -2
- package/dist/shortcuts/workflow/operations/todoSmartConvert/todoSmartConvert.js +51 -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 +39 -14
- package/dist/shortcuts/workflow/operations/write.js +1 -1
- package/docs/_catalog.md +3 -0
- package/docs/agent-invoice.md +1 -1
- package/docs/archive-oa.md +1 -1
- package/docs/caizhilian.md +1 -1
- package/docs/calendar.md +1 -1
- package/docs/contract.md +1 -1
- package/docs/e10-auth.md +23 -1
- package/docs/ehr.md +2 -2
- package/docs/esb.md +1 -1
- package/docs/im.md +1 -1
- package/docs/invoice.md +8 -12
- package/docs/okr.md +1 -1
- package/docs/operation-manual.md +8 -0
- package/docs/reception.md +119 -0
- package/package.json +1 -1
- package/scripts/clean.mjs +43 -2
- package/scripts/install-skill-to-xiaoe.mjs +105 -0
- package/scripts/package-skill.mjs +4 -2
- package/skill-template/business-info.json +42 -2
- package/skill-template/domains/caizhilian.md +1 -1
- package/skill-template/domains/reception-flow.md +18 -0
- package/skill-template/domains/recruit-stage.md +29 -0
- package/skill-template/domains/yepiaotong.md +4 -7
- package/skills/weaver-e10-archive-oa/references/source-manifest.json +14 -14
- package/skills/weaver-e10-blog/references/blog-publish.md +1 -1
- package/skills/weaver-e10-blog/references/source-manifest.json +7 -7
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +21 -21
- package/skills/weaver-e10-caizhilian/references/write-protocol.md +2 -2
- package/skills/weaver-e10-calendar/SKILL.md +2 -1
- package/skills/weaver-e10-calendar/references/calendar-write.md +55 -1
- package/skills/weaver-e10-cowork/SKILL.md +1 -1
- package/skills/weaver-e10-cowork/references/cowork-write.md +3 -1
- package/skills/weaver-e10-ebuilder-form/SKILL.md +1 -1
- package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -1
- package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -1
- package/skills/weaver-e10-esb/SKILL.md +2 -2
- package/skills/weaver-e10-esb/references/source-manifest.json +22 -22
- package/skills/weaver-e10-esb/references/trigger.md +2 -2
- package/skills/weaver-e10-jinchengda/references/contract-clause-library-crud.md +1 -1
- package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +1 -1
- package/skills/weaver-e10-jinchengda/references/contract-request-write.md +1 -1
- package/skills/weaver-e10-jinchengda/references/source-manifest.json +29 -29
- package/skills/weaver-e10-jucailin/SKILL.md +1 -1
- package/skills/weaver-e10-jucailin/references/attend-write.md +10 -2
- 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 +14 -5
- package/skills/weaver-e10-jucailin/references/recruit-read.md +1 -1
- package/skills/weaver-e10-jucailin/references/recruit-write.md +5 -4
- package/skills/weaver-e10-mail/SKILL.md +1 -1
- package/skills/weaver-e10-meeting/SKILL.md +39 -7
- package/skills/weaver-e10-meeting/references/meeting-change.md +1 -1
- package/skills/weaver-e10-meeting/references/meeting-create.md +1 -1
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +1 -1
- package/skills/weaver-e10-meeting/references/meeting-verification.md +1 -1
- package/skills/weaver-e10-okr/references/okr-align.md +1 -1
- package/skills/weaver-e10-okr/references/okr-keyresult.md +1 -1
- package/skills/weaver-e10-okr/references/okr-write.md +1 -1
- package/skills/weaver-e10-okr/references/source-manifest.json +8 -8
- package/skills/weaver-e10-plan/references/plan-report-write.md +1 -1
- package/skills/weaver-e10-plan/references/source-manifest.json +20 -20
- package/skills/weaver-e10-qiyecheng/references/loan-management.md +1 -1
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +18 -18
- package/skills/weaver-e10-reception-flow/SKILL.md +173 -0
- package/skills/weaver-e10-reception-flow/assets//350/241/214/347/250/213/345/215/225/346/250/241/346/235/277-/346/263/233/345/276/256.docx +0 -0
- package/skills/weaver-e10-reception-flow/data/fields-snapshot.json +213 -0
- package/skills/weaver-e10-reception-flow/references/browsertool.md +68 -0
- package/skills/weaver-e10-reception-flow/references/env-pitfalls.md +51 -0
- package/skills/weaver-e10-reception-flow/references/fields-snapshot.md +227 -0
- package/skills/weaver-e10-reception-flow/references/guide.md +179 -0
- package/skills/weaver-e10-reception-flow/scripts/locate.sh +14 -0
- package/skills/weaver-e10-reception-flow/scripts/render.py +724 -0
- package/skills/weaver-e10-recruit-stage/SKILL.md +230 -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/lib/xiaoe-session.mjs +66 -0
- package/skills/weaver-e10-recruit-stage/references/position-templates.md +202 -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 +95 -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 +451 -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 +1 -1
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +10 -2
- package/skills/weaver-e10-shijingran/SKILL.md +7 -6
- package/skills/weaver-e10-shijingran/references/operations.md +3 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +30 -28
- package/skills/weaver-e10-shijingran/references/write-operations.md +2 -0
- package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +2 -0
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +3 -3
- package/skills/weaver-e10-workflow/SKILL.md +4 -2
- package/skills/weaver-e10-workflow/aigroup/default.json +11 -9
- package/skills/weaver-e10-workflow/references/source-manifest.json +32 -32
- package/skills/weaver-e10-workflow/references/todo-smart-convert.md +23 -13
- package/skills/weaver-e10-workflow/tools/todo-summary.cjs +62 -20
- package/skills/weaver-e10-yepiaotong/SKILL.md +28 -36
- package/skills/weaver-e10-yepiaotong/references/invoice-agent-entry.md +6 -3
- package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +13 -10
- package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +2 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-workflow.md +194 -155
- package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +28 -32
- package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +5 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +8 -10
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +211 -616
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +149 -117
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +17 -20
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +20 -39
- package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +55 -22
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +7 -17
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +88 -92
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +144 -354
- package/skills/weaver-e10-yimiaoban/SKILL.md +1 -1
- package/skills/weaver-e10-yimiaoban/references/msg-write.md +1 -1
- package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +1 -1
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +7 -7
- package/dist/chunks/chunk-SPRHHSVC.js +0 -465
- package/dist/chunks/registry-KMHCPAAD.js +0 -22
- package/dist/shortcuts/invoice/markdown.js +0 -172
- package/dist/shortcuts/invoice/operations/issuing-qys.js +0 -424
- package/skills/weaver-e10-meeting.zip +0 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +0 -131
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-form-fill-rules.md +0 -179
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { SNAPSHOT_KIND } from '../constants.js';
|
|
3
|
+
import { readJson } from './shared.js';
|
|
4
|
+
export function resolveSnapshot(explicitPath = '') {
|
|
5
|
+
const candidates = [
|
|
6
|
+
explicitPath,
|
|
7
|
+
process.env.RECEPTION_SNAPSHOT || '',
|
|
8
|
+
process.env.RECEPTION_SKILL_DIR ? join(process.env.RECEPTION_SKILL_DIR, 'data', 'fields-snapshot.json') : '',
|
|
9
|
+
join(process.cwd(), 'data', 'fields-snapshot.json'),
|
|
10
|
+
join(process.cwd(), '..', 'data', 'fields-snapshot.json'),
|
|
11
|
+
].filter(Boolean);
|
|
12
|
+
for (const path of candidates) {
|
|
13
|
+
const parsed = readJson(path);
|
|
14
|
+
if (parsed && parsed.kind === SNAPSHOT_KIND)
|
|
15
|
+
return { path, snapshot: parsed };
|
|
16
|
+
}
|
|
17
|
+
return { path: '', snapshot: null };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 从快照路径反推技能包根目录。
|
|
21
|
+
*
|
|
22
|
+
* 技能包里 `data/fields-snapshot.json` 与 `scripts/render.py` 是固定相对位置,
|
|
23
|
+
* 因此只要拿到快照路径,就能定位渲染脚本,不需要再让调用方传第二个参数。
|
|
24
|
+
*/
|
|
25
|
+
export function skillRootFromSnapshot(snapshotPath) {
|
|
26
|
+
// <skill>/data/fields-snapshot.json → <skill>
|
|
27
|
+
return join(snapshotPath, '..', '..');
|
|
28
|
+
}
|
|
29
|
+
/** 转成与 formctx fields[] 同形的数组,便于 buildMain 直接复用。 */
|
|
30
|
+
export function snapshotFields(snapshot) {
|
|
31
|
+
if (!snapshot)
|
|
32
|
+
return [];
|
|
33
|
+
return (snapshot.fields || []).map((field) => ({
|
|
34
|
+
id: field.id,
|
|
35
|
+
name: field.name,
|
|
36
|
+
componentKey: field.componentKey,
|
|
37
|
+
isBrowser: Boolean(field.isBrowser),
|
|
38
|
+
isMultiple: Boolean(field.isMultiple),
|
|
39
|
+
required: Boolean(field.required),
|
|
40
|
+
options: field.options || [],
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
/** 与实时 fields 对拍:数量、名称、同名字段的 ID 是否一致。 */
|
|
44
|
+
export function diffSnapshot(snapshot, liveFields) {
|
|
45
|
+
if (!snapshot) {
|
|
46
|
+
return { match: null, drift: [], expectedCount: 0, actualCount: (liveFields || []).length };
|
|
47
|
+
}
|
|
48
|
+
const want = new Map((snapshot.fields || []).map((field) => [field.name, field]));
|
|
49
|
+
const live = new Map((liveFields || []).map((field) => [String(field.name), field]));
|
|
50
|
+
const drift = [];
|
|
51
|
+
for (const [name, field] of want) {
|
|
52
|
+
const matched = live.get(name);
|
|
53
|
+
if (!matched)
|
|
54
|
+
drift.push({ type: 'missing-in-form', name, expectedId: field.id });
|
|
55
|
+
else if (matched.id && String(matched.id) !== String(field.id)) {
|
|
56
|
+
drift.push({ type: 'id-changed', name, expectedId: field.id, actualId: String(matched.id) });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const [name, field] of live) {
|
|
60
|
+
if (!want.has(name)) {
|
|
61
|
+
drift.push({ type: 'new-field', name, actualId: field.id ? String(field.id) : '' });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const expectedCount = snapshot.mainFieldCount || 0;
|
|
65
|
+
const actualCount = (liveFields || []).length;
|
|
66
|
+
const countOk = expectedCount === actualCount;
|
|
67
|
+
return { match: countOk && drift.length === 0, countOk, expectedCount, actualCount, drift };
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { receptionValidationError } from './errors.js';
|
|
2
|
+
import { getReceptionOperation } from './operations/registry.js';
|
|
3
|
+
/**
|
|
4
|
+
* operation 调度入口。
|
|
5
|
+
*
|
|
6
|
+
* 这里只做两件事:按名取 handler、拒绝凭据类入参。
|
|
7
|
+
* 业务逻辑一律在 `operations/<action>.ts` 里,不要往这层塞分支。
|
|
8
|
+
*/
|
|
9
|
+
const FORBIDDEN_INPUT_KEYS = new Set([
|
|
10
|
+
'access_token',
|
|
11
|
+
'app_key',
|
|
12
|
+
'app_secret',
|
|
13
|
+
'cookie',
|
|
14
|
+
'eteamsid',
|
|
15
|
+
'token',
|
|
16
|
+
'sid',
|
|
17
|
+
'eteamsId',
|
|
18
|
+
]);
|
|
19
|
+
export function normalizeInput(raw) {
|
|
20
|
+
return raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
|
|
21
|
+
}
|
|
22
|
+
export async function executeReceptionOperation(operation, rawInput, context) {
|
|
23
|
+
const handler = getReceptionOperation(operation);
|
|
24
|
+
const input = normalizeInput(rawInput);
|
|
25
|
+
for (const key of Object.keys(input)) {
|
|
26
|
+
if (FORBIDDEN_INPUT_KEYS.has(key)) {
|
|
27
|
+
throw receptionValidationError('FORBIDDEN_INPUT', `禁止传入凭据字段:${key}`, {
|
|
28
|
+
hint: '凭据由 CLI 运行时统一注入,业务入参不接受',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return handler.execute(input, context);
|
|
33
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文件上传协议:teamsCheck → preUploadCheck → module/upload。
|
|
3
|
+
*
|
|
4
|
+
* **为什么不走 `/api/file/upload`**:这个接口在 E10 侧已废弃,实测会返回 200 但响应里没有
|
|
5
|
+
* fileid(或直接是登录页 HTML),表现为「上传失败但没有原因」。现行链路是三步:
|
|
6
|
+
*
|
|
7
|
+
* 1. `POST /api/baseserver/layout/teamsCheck` —— 校验当前用户身份
|
|
8
|
+
* 2. `POST /api/file/preUploadCheck?sf_request_type=ajax` —— 预检,返回上传策略
|
|
9
|
+
* 3. `POST /api/file/module/upload?sf_request_type=ajax` —— 按策略上传,返回 fileid
|
|
10
|
+
*
|
|
11
|
+
* 策略有三:`second`(秒传,服务端已有同 MD5 文件)、`common`(一次传完)、`chunk`(分片)。
|
|
12
|
+
*
|
|
13
|
+
* 逻辑与 `@weaver/reception-cli@0.7.1` 的 `lib/upload.js` 一致;与 `caizhilian` 的
|
|
14
|
+
* `file-upload/protocol.ts` 同源(那边为了大文件走流式 multipart,这里文件小,用 FormData)。
|
|
15
|
+
*/
|
|
16
|
+
import { createHash } from 'node:crypto';
|
|
17
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
18
|
+
import { basename } from 'node:path';
|
|
19
|
+
/** 上传协议层错误:`subtype` 直接进 CLI 的 error.subtype,便于按码排障。 */
|
|
20
|
+
export class UploadProtocolError extends Error {
|
|
21
|
+
subtype;
|
|
22
|
+
httpStatus;
|
|
23
|
+
detail;
|
|
24
|
+
contentType;
|
|
25
|
+
constructor(subtype, message, httpStatus, detail, contentType) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.subtype = subtype;
|
|
28
|
+
this.httpStatus = httpStatus;
|
|
29
|
+
this.detail = detail;
|
|
30
|
+
this.contentType = contentType;
|
|
31
|
+
this.name = 'UploadProtocolError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const object = (value) => (value && typeof value === 'object' && !Array.isArray(value) ? value : {});
|
|
35
|
+
const hasKeys = (value) => Object.keys(value).length > 0;
|
|
36
|
+
/** 判定响应是否表示成功,返回可用的 data 节点。 */
|
|
37
|
+
function checkedData(response) {
|
|
38
|
+
const contentType = response.headers?.get?.('content-type') || '';
|
|
39
|
+
const text = String(response.text || '');
|
|
40
|
+
const isHtml = /text\/html/i.test(contentType) || /^\s*<!DOCTYPE\s+html|^\s*<html[\s>]/i.test(text);
|
|
41
|
+
if (response.status !== 200) {
|
|
42
|
+
throw new UploadProtocolError(response.status === 401 ? 'upload_auth_expired' : 'upload_http_error', response.status === 401 ? '登录已失效,请在原登录工具中恢复后再试' : `文件服务 HTTP ${response.status}`, response.status, text.slice(0, 300) || undefined, contentType || undefined);
|
|
43
|
+
}
|
|
44
|
+
if (isHtml) {
|
|
45
|
+
throw new UploadProtocolError('upload_response_html', '文件服务返回了 HTML 页面(通常是登录页或错误页)', response.status, text.slice(0, 300), contentType);
|
|
46
|
+
}
|
|
47
|
+
const envelope = object(response.value);
|
|
48
|
+
const data = envelope.data === undefined ? envelope : object(envelope.data);
|
|
49
|
+
if (!hasKeys(data)) {
|
|
50
|
+
throw new UploadProtocolError('upload_response_invalid', '文件服务响应缺少数据对象', response.status, text.slice(0, 300) || undefined, contentType || undefined);
|
|
51
|
+
}
|
|
52
|
+
for (const record of [
|
|
53
|
+
envelope,
|
|
54
|
+
data,
|
|
55
|
+
object(data.fileObj),
|
|
56
|
+
object(data.uploadModuleDto),
|
|
57
|
+
object(data.imUploadInfoDto),
|
|
58
|
+
object(object(data.uploadModuleDto).fileObj),
|
|
59
|
+
object(object(data.imUploadInfoDto).fileObj),
|
|
60
|
+
]) {
|
|
61
|
+
if (!hasKeys(record))
|
|
62
|
+
continue;
|
|
63
|
+
const flag = (value, literal) => value === literal || String(value).toLowerCase() === String(literal);
|
|
64
|
+
const code = record.code == null ? '' : String(record.code).toLowerCase();
|
|
65
|
+
if (flag(record.status, false)
|
|
66
|
+
|| flag(record.success, false)
|
|
67
|
+
|| flag(record.fail, true)
|
|
68
|
+
|| (code && !['0', '200', 'success'].includes(code))) {
|
|
69
|
+
throw new UploadProtocolError('upload_business_failed', String(record.msg || record.message || '文件服务未确认操作成功'), response.status, text.slice(0, 300), contentType);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return data;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 在响应里找文件记录。
|
|
76
|
+
* 不同模块的上传接口把 fileid 放在不同层级:顶层、`fileObj`、`uploadModuleDto`、`imUploadInfoDto`。
|
|
77
|
+
* `sizes` 收齐各层的 size,用于交叉校验服务端是否真的收全了。
|
|
78
|
+
*/
|
|
79
|
+
function fileRecord(data) {
|
|
80
|
+
const candidates = [
|
|
81
|
+
[data, undefined],
|
|
82
|
+
[object(data.fileObj), data],
|
|
83
|
+
[object(data.uploadModuleDto), data],
|
|
84
|
+
[object(object(data.uploadModuleDto).fileObj), object(data.uploadModuleDto)],
|
|
85
|
+
[object(data.imUploadInfoDto), data],
|
|
86
|
+
[object(object(data.imUploadInfoDto).fileObj), object(data.imUploadInfoDto)],
|
|
87
|
+
];
|
|
88
|
+
let result;
|
|
89
|
+
for (const [record, parent] of candidates) {
|
|
90
|
+
if (!hasKeys(record))
|
|
91
|
+
continue;
|
|
92
|
+
if (record.fileId && record.fileid && String(record.fileId) !== String(record.fileid)) {
|
|
93
|
+
throw new UploadProtocolError('upload_file_id_conflict', '文件服务返回冲突的文件 ID');
|
|
94
|
+
}
|
|
95
|
+
const id = record.fileId ?? record.fileid;
|
|
96
|
+
if (id === undefined || id === null || id === '')
|
|
97
|
+
continue;
|
|
98
|
+
if ((typeof id !== 'string' && typeof id !== 'number')
|
|
99
|
+
|| (typeof id === 'number' && (!Number.isSafeInteger(id) || id <= 0))) {
|
|
100
|
+
throw new UploadProtocolError('upload_file_id_invalid', '文件服务返回无效文件 ID');
|
|
101
|
+
}
|
|
102
|
+
const text = String(id).trim();
|
|
103
|
+
if (!text || text === '0')
|
|
104
|
+
continue;
|
|
105
|
+
if (result && result.id !== text) {
|
|
106
|
+
throw new UploadProtocolError('upload_file_id_conflict', '文件服务返回冲突的文件 ID');
|
|
107
|
+
}
|
|
108
|
+
result ??= { id: text, sizes: [] };
|
|
109
|
+
result.sizes.push(record.size, parent?.size, data.size);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
/** 取「已完成」的文件 ID,并校验服务端报告的文件大小。 */
|
|
114
|
+
function terminal(data, expectedSize) {
|
|
115
|
+
const record = fileRecord(data);
|
|
116
|
+
if (!record || data.endChunk === false || data.endChunk === 'false') {
|
|
117
|
+
throw new UploadProtocolError('upload_not_terminal', '上传未返回已完成的文件 ID');
|
|
118
|
+
}
|
|
119
|
+
for (const value of record.sizes) {
|
|
120
|
+
if (value === undefined || value === null || value === '')
|
|
121
|
+
continue;
|
|
122
|
+
const size = Number(value);
|
|
123
|
+
if (!Number.isSafeInteger(size) || size < 0 || (size !== 0 && size !== expectedSize)) {
|
|
124
|
+
throw new UploadProtocolError('upload_size_mismatch', `服务端文件大小与本地不一致(服务端 ${value},本地 ${expectedSize})`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return record.id;
|
|
128
|
+
}
|
|
129
|
+
/** 发一次 multipart 并判定成功,返回 data 节点。 */
|
|
130
|
+
async function send(transport, path, fields, file) {
|
|
131
|
+
const form = new FormData();
|
|
132
|
+
for (const [key, value] of Object.entries(fields))
|
|
133
|
+
form.append(key, value);
|
|
134
|
+
if (file) {
|
|
135
|
+
form.append('file', new Blob([new Uint8Array(file.buffer.subarray(file.start, file.start + file.size))]), `e10upload.${file.ext}`);
|
|
136
|
+
}
|
|
137
|
+
return checkedData(await transport.post(path, form));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 走完三阶段上传,返回 fileid。
|
|
141
|
+
* 失败一律抛 `UploadProtocolError`,由调用方转成 CLI 的 error/warning。
|
|
142
|
+
*/
|
|
143
|
+
export async function uploadToFileService(input) {
|
|
144
|
+
const buffer = await readFile(input.filePath);
|
|
145
|
+
const info = await stat(input.filePath);
|
|
146
|
+
const name = input.name || basename(input.filePath);
|
|
147
|
+
const ext = (name.match(/\.([^.]+)$/) || [, 'bin'])[1].toLowerCase();
|
|
148
|
+
const lastModified = String(Math.floor(info.mtimeMs));
|
|
149
|
+
const fields = {
|
|
150
|
+
compressImg: 'false',
|
|
151
|
+
name,
|
|
152
|
+
size: String(buffer.length),
|
|
153
|
+
module: input.module,
|
|
154
|
+
MD5: createHash('md5').update(buffer).digest('hex'),
|
|
155
|
+
lastModified,
|
|
156
|
+
lastModifiedDate: lastModified,
|
|
157
|
+
};
|
|
158
|
+
if (input.userId)
|
|
159
|
+
fields.userid = String(input.userId);
|
|
160
|
+
// 1) 身份校验:确认服务端认到的用户与会话一致,否则后续上传会以匿名身份落盘
|
|
161
|
+
const identity = await send(input.transport, '/api/baseserver/layout/teamsCheck', {});
|
|
162
|
+
const employeeId = object(identity.currentUser).employeeId;
|
|
163
|
+
const employeeText = employeeId === undefined || employeeId === null ? '' : String(employeeId).trim();
|
|
164
|
+
if (!employeeText) {
|
|
165
|
+
throw new UploadProtocolError('upload_identity_missing', '身份校验未返回 employeeId,无法确认上传身份');
|
|
166
|
+
}
|
|
167
|
+
if (input.userId && employeeText !== String(input.userId).trim()) {
|
|
168
|
+
throw new UploadProtocolError('upload_identity_mismatch', `会话用户(${input.userId})与文件服务认到的用户(${employeeText})不一致,请重新登录`);
|
|
169
|
+
}
|
|
170
|
+
// 2) 预检:拿上传策略
|
|
171
|
+
const pre = await send(input.transport, '/api/file/preUploadCheck?sf_request_type=ajax', fields);
|
|
172
|
+
const type = String(pre.uploadType || '');
|
|
173
|
+
if (!['second', 'common', 'chunk'].includes(type)) {
|
|
174
|
+
throw new UploadProtocolError('upload_type_invalid', `文件服务返回未知上传策略:${type || '(空)'}`);
|
|
175
|
+
}
|
|
176
|
+
// 3) 秒传:服务端已有同 MD5 文件,预检响应里直接带 fileid
|
|
177
|
+
if (type === 'second') {
|
|
178
|
+
return { fileid: terminal(pre, buffer.length), name, size: buffer.length, kind: ext };
|
|
179
|
+
}
|
|
180
|
+
const preUploadId = String(pre.preUploadId ?? '').trim();
|
|
181
|
+
if (!preUploadId || preUploadId === '0') {
|
|
182
|
+
throw new UploadProtocolError('upload_precheck_invalid', '预检未返回有效 preUploadId');
|
|
183
|
+
}
|
|
184
|
+
const uploadFields = { ...fields, preUploadId, uploadType: type };
|
|
185
|
+
// 4) 直传:一次把整个文件发完
|
|
186
|
+
if (type === 'common') {
|
|
187
|
+
const data = await send(input.transport, '/api/file/module/upload?sf_request_type=ajax', uploadFields, {
|
|
188
|
+
name, ext, buffer, start: 0, size: buffer.length,
|
|
189
|
+
});
|
|
190
|
+
return { fileid: terminal(data, buffer.length), name, size: buffer.length, kind: ext };
|
|
191
|
+
}
|
|
192
|
+
// 5) 分片:按服务端给的 MiB 大小切片,逐片上报
|
|
193
|
+
const chunkMiB = Number(pre.chunkDto?.chunkSize);
|
|
194
|
+
if (!Number.isSafeInteger(chunkMiB) || chunkMiB <= 0 || buffer.length <= 0) {
|
|
195
|
+
throw new UploadProtocolError('upload_chunk_size_invalid', '分片上传要求非空文件与正整数 MiB 分片大小');
|
|
196
|
+
}
|
|
197
|
+
const chunkSize = chunkMiB * 1024 * 1024;
|
|
198
|
+
const count = Math.max(1, Math.ceil(buffer.length / chunkSize));
|
|
199
|
+
if (pre.chunkDto?.chunkCount !== undefined && Number(pre.chunkDto.chunkCount) !== count) {
|
|
200
|
+
throw new UploadProtocolError('upload_chunk_count_mismatch', `服务端分片数(${pre.chunkDto.chunkCount})与本地计算(${count})不一致`);
|
|
201
|
+
}
|
|
202
|
+
let fileid = '';
|
|
203
|
+
for (let index = 0; index < count; index += 1) {
|
|
204
|
+
const start = index * chunkSize;
|
|
205
|
+
const size = Math.min(chunkSize, buffer.length - start);
|
|
206
|
+
const data = await send(input.transport, '/api/file/module/upload?sf_request_type=ajax', { ...uploadFields, chunk: String(index), chunks: String(count), uploadShardSize: String(size) }, { name, ext, buffer, start, size });
|
|
207
|
+
if (index === count - 1) {
|
|
208
|
+
fileid = terminal(data, buffer.length);
|
|
209
|
+
}
|
|
210
|
+
else if (data.endChunk !== false || fileRecord(data)) {
|
|
211
|
+
// 中间分片必须有明确的「未完成」标记,且不能提前返回 fileid,否则说明服务端行为异常
|
|
212
|
+
throw new UploadProtocolError('upload_chunk_terminal_early', '中间分片缺少未完成标记或提前返回了文件 ID');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (!fileid)
|
|
216
|
+
throw new UploadProtocolError('upload_not_terminal', '分片上传未确认完成');
|
|
217
|
+
return { fileid, name, size: buffer.length, kind: ext };
|
|
218
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { createRecruitContextLoader, } from '../ehr/operations/recruit.js';
|
|
6
|
+
import { rsFail } from './errors.js';
|
|
7
|
+
const flowMemo = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* 自用系统(selfUse=1)默认招聘流程兜底常量 —— 2026-11-05 用户明确指定写死。
|
|
10
|
+
*
|
|
11
|
+
* 背景:www.e-cology.com.cn 是自用系统,招聘流程**只有一条**「初筛+面试+offer阶段」。
|
|
12
|
+
* 该环境的运行时解析通道全部取不到值(实测):
|
|
13
|
+
* · 环境缓存无 defaultRecruitFlow 键、无 menus 字段
|
|
14
|
+
* · 原接口 /api/recruit/position/recruitFlow/list 不存在(静默失败)
|
|
15
|
+
* · 招聘流程菜单列表经 listInit + getListDatas 仍未返回可用行
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ 与技能「环境 id 绝不入包、跨环境自适应」原则冲突,故**加双重守卫**:
|
|
18
|
+
* 仅当 selfUse=1 且域名等于 selfUseHost 时才使用本常量;其他环境继续走运行时解析,不受影响。
|
|
19
|
+
* 换环境或该流程数据变更时必须同步修改此处。
|
|
20
|
+
*/
|
|
21
|
+
const SELFUSE_DEFAULT_FLOW = {
|
|
22
|
+
selfUseHost: 'www.e-cology.com.cn',
|
|
23
|
+
id: '874896524979806209',
|
|
24
|
+
name: '初筛+面试+offer阶段',
|
|
25
|
+
};
|
|
26
|
+
const digitalMenuMemo = new Map();
|
|
27
|
+
function cacheDirFor(baseUrl) {
|
|
28
|
+
const url = new URL(baseUrl);
|
|
29
|
+
const host = url.hostname + (url.port ? `_${url.port}` : '');
|
|
30
|
+
const envRoot = process.env.E10_SKILL_CACHE_DIR;
|
|
31
|
+
if (envRoot)
|
|
32
|
+
return join(envRoot, host);
|
|
33
|
+
const home = process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
|
|
34
|
+
return join(home, 'env-cache', 'recruit-ops', host);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 磁盘缓存兼容:脚本版(recruit-stage 技能)播种的缓存里若已带 defaultRecruitFlow /
|
|
38
|
+
* lists.digital_human,直接复用(同环境免请求);CLI 侧则运行时解析一次并 memo。
|
|
39
|
+
*/
|
|
40
|
+
async function readLegacyCacheExtra(baseUrl) {
|
|
41
|
+
try {
|
|
42
|
+
const dir = cacheDirFor(baseUrl);
|
|
43
|
+
if (!existsSync(dir))
|
|
44
|
+
return { defaultRecruitFlow: null, digitalHumanMenu: null };
|
|
45
|
+
const files = await (await import('node:fs/promises')).readdir(dir);
|
|
46
|
+
const jsonFiles = files.filter((f) => f.endsWith('.json'));
|
|
47
|
+
// 原实现要求「目录内恰好 1 个 json 才读」,多环境/多应用共存时必然读不到 →
|
|
48
|
+
// 改为遍历全部 json,取第一个带所需键的(2026-11-05 修)。
|
|
49
|
+
let flow = null;
|
|
50
|
+
let dh = null;
|
|
51
|
+
for (const f of jsonFiles) {
|
|
52
|
+
let raw;
|
|
53
|
+
try {
|
|
54
|
+
raw = JSON.parse(await readFile(join(dir, f), 'utf8'));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (!flow && raw?.defaultRecruitFlow && raw.defaultRecruitFlow.id) {
|
|
60
|
+
flow = { id: String(raw.defaultRecruitFlow.id), name: String(raw.defaultRecruitFlow.name || '') };
|
|
61
|
+
}
|
|
62
|
+
if (!dh && raw?.lists?.digital_human?.listId) {
|
|
63
|
+
dh = {
|
|
64
|
+
menuName: String(raw.lists.digital_human.menuName || '面试数字人配置'),
|
|
65
|
+
listId: String(raw.lists.digital_human.listId),
|
|
66
|
+
cusMenuId: String(raw.lists.digital_human.cusMenuId || ''),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (flow && dh)
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
return { defaultRecruitFlow: flow, digitalHumanMenu: dh };
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return { defaultRecruitFlow: null, digitalHumanMenu: null };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** 从菜单树/list 里找「招聘流程」列表参数(本环境实测存在于 lists.all_search) */
|
|
79
|
+
function findFlowMenu(cache) {
|
|
80
|
+
const all = ((cache.lists || {}).all_search || []);
|
|
81
|
+
const hit = all.find((m) => m && m.listId && /招聘流程/.test(String(m.menuName || '')));
|
|
82
|
+
if (hit) {
|
|
83
|
+
return { listId: String(hit.listId), cusMenuId: String(hit.cusMenuId || ''), menuName: String(hit.menuName || '招聘流程') };
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/** 判断某行是否为「默认启用」流程:兼容多种字段命名 */
|
|
88
|
+
function isDefaultFlowRow(x) {
|
|
89
|
+
if (!x)
|
|
90
|
+
return false;
|
|
91
|
+
const cands = [x.is_default, x.isDefault, x.default_flow, x.defaultFlow, x.is_enable, x.enabled, x.status];
|
|
92
|
+
for (const v of cands) {
|
|
93
|
+
if (v === true || v === 1 || v === '1')
|
|
94
|
+
return true;
|
|
95
|
+
if (String(v) === '是' || String(v) === '启用' || String(v) === '默认')
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
/** 默认招聘流程解析:优先 legacy 缓存 → 招聘流程菜单列表 → 旧接口兜底。失败**不缓存**(允许多次重试)。 */
|
|
101
|
+
async function resolveDefaultRecruitFlow(host, baseUrl, appId, cache) {
|
|
102
|
+
const memoKey = baseUrl;
|
|
103
|
+
if (flowMemo.has(memoKey))
|
|
104
|
+
return flowMemo.get(memoKey) ?? null;
|
|
105
|
+
const legacy = await readLegacyCacheExtra(baseUrl);
|
|
106
|
+
if (legacy.defaultRecruitFlow) {
|
|
107
|
+
flowMemo.set(memoKey, legacy.defaultRecruitFlow);
|
|
108
|
+
return legacy.defaultRecruitFlow;
|
|
109
|
+
}
|
|
110
|
+
let result = null;
|
|
111
|
+
// ① 招聘流程菜单列表(本环境实测入口,原实现只调接口必然失败)
|
|
112
|
+
const menu = findFlowMenu(cache);
|
|
113
|
+
if (menu) {
|
|
114
|
+
try {
|
|
115
|
+
// getListDatas 的 passid/cid 必须是**表单 objId**,不是 listId;
|
|
116
|
+
// 先用 listInit 用 listId 反查 objId(口径同 interview-arrange 的数字人菜单解析)
|
|
117
|
+
let flowObjId = '';
|
|
118
|
+
try {
|
|
119
|
+
const liBody = {
|
|
120
|
+
cusMenuId: menu.cusMenuId,
|
|
121
|
+
listId: menu.listId,
|
|
122
|
+
urlPageTitle: menu.menuName,
|
|
123
|
+
};
|
|
124
|
+
liBody['appId_' + menu.listId] = appId;
|
|
125
|
+
const li = await host.request({ path: '/api/ebuilder/form/list/listInit', body: liBody });
|
|
126
|
+
flowObjId = String(li?.data?.objid || '');
|
|
127
|
+
}
|
|
128
|
+
catch { /* listInit 不可用 → 走接口兜底 */ }
|
|
129
|
+
if (flowObjId) {
|
|
130
|
+
const body = {
|
|
131
|
+
current: 1, pageSize: 50, tableuid: Math.random().toString(16).slice(2, 34),
|
|
132
|
+
listId: menu.listId, searchParamData: { searchType: '3', type: 'and' },
|
|
133
|
+
terminal: 'PC', browser: 'Chrome', requiredMatch: 1, mapResult: 1,
|
|
134
|
+
recheckQuery: 1, isFollow: 0, isShareToMe: 0, filterCondition: {},
|
|
135
|
+
cusMenuId: menu.cusMenuId, urlPageTitle: menu.menuName, menuName: menu.menuName,
|
|
136
|
+
};
|
|
137
|
+
const r = await host.request({
|
|
138
|
+
path: `/api/ebuilder/form/list/getListDatas?passid=${flowObjId}&cid=${flowObjId}`,
|
|
139
|
+
body,
|
|
140
|
+
headers: { ebBusinessId: menu.listId },
|
|
141
|
+
});
|
|
142
|
+
const rows = (r?.data?.data || r?.data?.displayData || []);
|
|
143
|
+
const pick = rows.find(isDefaultFlowRow) || (rows.length === 1 ? rows[0] : null);
|
|
144
|
+
if (pick) {
|
|
145
|
+
const id = String(pick.form_data_id || pick.id || '');
|
|
146
|
+
const name = String(pick.name || pick.flow_name || pick.content || '');
|
|
147
|
+
if (id && name)
|
|
148
|
+
result = { id, name };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch { /* 菜单通道不可用 → 走接口兜底 */ }
|
|
153
|
+
}
|
|
154
|
+
// ② 旧接口兜底(原实现唯一通道,部分环境保留)
|
|
155
|
+
if (!result) {
|
|
156
|
+
try {
|
|
157
|
+
const r = await host.request({
|
|
158
|
+
path: '/api/recruit/position/recruitFlow/list',
|
|
159
|
+
method: 'POST',
|
|
160
|
+
body: { appId, current: 1, pageSize: 50 },
|
|
161
|
+
operation: 'context.defaultRecruitFlow',
|
|
162
|
+
});
|
|
163
|
+
const rows = r?.data?.list || r?.data?.rows || r?.data || [];
|
|
164
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
165
|
+
const enabled = list.find(isDefaultFlowRow) || null;
|
|
166
|
+
if (enabled?.id) {
|
|
167
|
+
result = { id: String(enabled.id), name: String(enabled.name || enabled.flow_name || '') };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch { /* 接口形态不符/未开放 */ }
|
|
171
|
+
}
|
|
172
|
+
// ③ 自用系统兜底(用户指定写死):仅 selfUse=1 且域名匹配时生效,不影响其他环境
|
|
173
|
+
if (!result) {
|
|
174
|
+
const isSelfUse = String(cache?.selfUse || '') === '1';
|
|
175
|
+
let host = '';
|
|
176
|
+
try {
|
|
177
|
+
host = new URL(baseUrl).hostname;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
host = '';
|
|
181
|
+
}
|
|
182
|
+
if (isSelfUse && host === SELFUSE_DEFAULT_FLOW.selfUseHost) {
|
|
183
|
+
result = { id: SELFUSE_DEFAULT_FLOW.id, name: SELFUSE_DEFAULT_FLOW.name };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// 只有解析成功才写入 memo:失败若也缓存 null,进程内后续所有请求都拿不到(原实现缺陷)
|
|
187
|
+
if (result)
|
|
188
|
+
flowMemo.set(memoKey, result);
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
async function resolveDigitalHumanMenu(host, baseUrl, cache) {
|
|
192
|
+
const memoKey = baseUrl;
|
|
193
|
+
if (digitalMenuMemo.has(memoKey))
|
|
194
|
+
return digitalMenuMemo.get(memoKey) ?? null;
|
|
195
|
+
const legacy = await readLegacyCacheExtra(baseUrl);
|
|
196
|
+
if (legacy.digitalHumanMenu) {
|
|
197
|
+
digitalMenuMemo.set(memoKey, legacy.digitalHumanMenu);
|
|
198
|
+
return legacy.digitalHumanMenu;
|
|
199
|
+
}
|
|
200
|
+
// 从 all_search 菜单里按别名找(跨基线菜单名不同:面试数字人配置/数字人配置)
|
|
201
|
+
const allSearch = (cache.lists?.all_search || []);
|
|
202
|
+
const candidates = allSearch.filter((m) => m.listId);
|
|
203
|
+
let hit = (candidates.find((m) => (m.menuName || '').includes('面试数字人'))
|
|
204
|
+
|| candidates.find((m) => (m.menuName || '').includes('数字人配置'))) ?? null;
|
|
205
|
+
digitalMenuMemo.set(memoKey, hit);
|
|
206
|
+
return hit;
|
|
207
|
+
}
|
|
208
|
+
const loaders = new Map();
|
|
209
|
+
export function createRecruitStageContext() {
|
|
210
|
+
return async (host) => {
|
|
211
|
+
const loaderKey = 'shared';
|
|
212
|
+
if (!loaders.has(loaderKey))
|
|
213
|
+
loaders.set(loaderKey, createRecruitContextLoader());
|
|
214
|
+
const cache = await loaders.get(loaderKey).load(host);
|
|
215
|
+
const baseUrl = (await host.getSession()).baseUrl.replace(/\/+$/, '');
|
|
216
|
+
const [defaultRecruitFlow, digitalHumanMenu] = await Promise.all([
|
|
217
|
+
resolveDefaultRecruitFlow(host, baseUrl, cache.appId, cache),
|
|
218
|
+
resolveDigitalHumanMenu(host, baseUrl, cache),
|
|
219
|
+
]);
|
|
220
|
+
return { ...cache, defaultRecruitFlow, digitalHumanMenu };
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/** 供 operation 内直接取上下文的辅助(失败统一转 rs 错误)。 */
|
|
224
|
+
export async function loadContext(host) {
|
|
225
|
+
try {
|
|
226
|
+
return await createRecruitStageContext()(host);
|
|
227
|
+
}
|
|
228
|
+
catch (e) {
|
|
229
|
+
throw rsFail(e?.message || '招聘环境上下文解析失败', { stage: 'context' });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { CONTINUATION_TTL_MS } from '../../core/continuation-ttl.js';
|
|
2
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import { rsValidationError } from './errors.js';
|
|
8
|
+
function stateRoot(ctx) {
|
|
9
|
+
const configured = ctx?.config?.path();
|
|
10
|
+
if (configured)
|
|
11
|
+
return resolve(configured, '..');
|
|
12
|
+
return process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
|
|
13
|
+
}
|
|
14
|
+
async function loadKey(ctx) {
|
|
15
|
+
const keysDir = resolve(stateRoot(ctx), 'keys');
|
|
16
|
+
const filename = resolve(keysDir, 'recruit-stage-continuation.key');
|
|
17
|
+
await mkdir(keysDir, { recursive: true, mode: 0o700 });
|
|
18
|
+
if (existsSync(filename))
|
|
19
|
+
return readFile(filename);
|
|
20
|
+
const key = randomBytes(32);
|
|
21
|
+
try {
|
|
22
|
+
await writeFile(filename, key, { flag: 'wx', mode: 0o600 });
|
|
23
|
+
if (process.platform !== 'win32')
|
|
24
|
+
await chmod(filename, 0o600).catch(() => undefined);
|
|
25
|
+
return key;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return readFile(filename);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function encode(value) {
|
|
32
|
+
return Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
33
|
+
}
|
|
34
|
+
function signature(payload, key) {
|
|
35
|
+
return createHmac('sha256', key).update(payload).digest('base64url');
|
|
36
|
+
}
|
|
37
|
+
export function createRecruitStageContinuationSigner(ctx) {
|
|
38
|
+
return {
|
|
39
|
+
async issue(payload, ttlMs = CONTINUATION_TTL_MS) {
|
|
40
|
+
const issuedAt = Date.now();
|
|
41
|
+
const body = encode({ ...payload, issuedAt, expiresAt: issuedAt + ttlMs });
|
|
42
|
+
const sig = signature(body, await loadKey(ctx));
|
|
43
|
+
return `${body}.${sig}`;
|
|
44
|
+
},
|
|
45
|
+
async verify(token, expectedOperation) {
|
|
46
|
+
if (typeof token !== 'string' || !token.includes('.')) {
|
|
47
|
+
throw rsValidationError('continuation_invalid', 'continuation 无效');
|
|
48
|
+
}
|
|
49
|
+
const [body, provided] = token.split('.');
|
|
50
|
+
const expected = signature(body, await loadKey(ctx));
|
|
51
|
+
const valid = provided.length === expected.length
|
|
52
|
+
&& timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
|
|
53
|
+
if (!valid)
|
|
54
|
+
throw rsValidationError('continuation_invalid', 'continuation 签名校验失败');
|
|
55
|
+
let payload;
|
|
56
|
+
try {
|
|
57
|
+
payload = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw rsValidationError('continuation_invalid', 'continuation 载荷解析失败');
|
|
61
|
+
}
|
|
62
|
+
if (!payload || typeof payload !== 'object') {
|
|
63
|
+
throw rsValidationError('continuation_invalid', 'continuation 载荷必须是对象');
|
|
64
|
+
}
|
|
65
|
+
if (typeof payload.expiresAt === 'number' && Date.now() > payload.expiresAt) {
|
|
66
|
+
throw rsValidationError('continuation_expired', 'continuation 已过期,请重新 prepare');
|
|
67
|
+
}
|
|
68
|
+
if (payload.operation && String(payload.operation) !== String(expectedOperation)) {
|
|
69
|
+
throw rsValidationError('continuation_operation_mismatch', `continuation 属于操作 ${String(payload.operation)},不能用于 ${expectedOperation}`);
|
|
70
|
+
}
|
|
71
|
+
return payload;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|