weaver-work-cli 0.1.42 → 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-J4I6MCKF.js → archive-P6TBQGKL.js} +13 -5
- package/dist/chunks/{archive-oa-BRZKKTO4.js → archive-oa-7TQ2NXOH.js} +12 -5
- package/dist/chunks/{asset-L7D4FAOZ.js → asset-3ELRG37G.js} +11 -4
- package/dist/chunks/{asset-idle-DADLLV75.js → asset-idle-WIGMUFMD.js} +7 -3
- package/dist/chunks/{attachment-content-K4C7VLWQ.js → attachment-content-E3276UOV.js} +5 -4
- package/dist/chunks/{blog-BVGQ7NZL.js → blog-C5NMZAAC.js} +11 -4
- package/dist/chunks/{caizhilian-SAADNFD6.js → caizhilian-5XSM2DFP.js} +13 -6
- package/dist/chunks/{calendar-Q7NQY5Z2.js → calendar-FI2WNSBO.js} +12 -5
- package/dist/chunks/{chunk-YRSZQUD2.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-EKWAQSLU.js → chunk-BCVYBYYT.js} +432 -2248
- package/dist/chunks/{chunk-W7BEHOSR.js → chunk-CWYGFU5B.js} +2 -2
- package/dist/chunks/{chunk-NOODWMCL.js → chunk-D4ADSZ7H.js} +1 -1
- package/dist/chunks/{chunk-SA6VBIHY.js → chunk-ETNJSKMM.js} +214 -16
- package/dist/chunks/{chunk-BBKDXQEC.js → chunk-F4E4EM35.js} +3 -3
- package/dist/chunks/{chunk-IZ7S7QUS.js → chunk-FAZTGX7S.js} +6 -6
- package/dist/chunks/{chunk-NKZRQXST.js → chunk-FRZKBMSW.js} +52 -36
- package/dist/chunks/{chunk-BYBSCN4X.js → chunk-G7PQWD4L.js} +6 -3
- package/dist/chunks/{chunk-GEWYMQME.js → chunk-GAMZLAG2.js} +1 -1
- package/dist/chunks/{chunk-R4CHUSKC.js → chunk-GM6J7Q4B.js} +4 -4
- package/dist/chunks/{chunk-APVFJUQH.js → chunk-GRDRG6HP.js} +3 -3
- package/dist/chunks/{chunk-LDZQPUIA.js → chunk-HIT4Y6OI.js} +1 -1
- package/dist/chunks/{chunk-EZALDOQH.js → chunk-HP7JIYMT.js} +5 -2
- package/dist/chunks/{chunk-LADPYOWC.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-BIPYLLVS.js → chunk-N4M2DDFE.js} +15 -15
- package/dist/chunks/{chunk-QKNKR4RH.js → chunk-OF4S23UB.js} +4 -4
- package/dist/chunks/{chunk-JPRO6ER2.js → chunk-PC7VKEB5.js} +57 -60
- package/dist/chunks/{chunk-4PD5NWAX.js → chunk-PUQS5MWL.js} +2 -2
- package/dist/chunks/{chunk-2LV74IWP.js → chunk-Q4OYAQYH.js} +1 -1
- package/dist/chunks/{chunk-72CKAUAG.js → chunk-QVSWRLLU.js} +7 -5
- package/dist/chunks/{chunk-4RAE6JXP.js → chunk-TA7FXXJD.js} +6 -3
- package/dist/chunks/{chunk-GPMFD4RD.js → chunk-TXUG266F.js} +1 -1
- package/dist/chunks/{chunk-FOAA27U5.js → chunk-UFIT2UU3.js} +16 -4
- 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-G4UR6NXN.js → chunk-WSF4NNMR.js} +3 -3
- package/dist/chunks/chunk-XHK4MDR5.js +2497 -0
- package/dist/chunks/{chunk-3KTFS4IA.js → chunk-XNIK66I4.js} +19 -9
- 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-GHXCDPTM.js → chunk-Z7VYWPZV.js} +1 -1
- package/dist/chunks/{conflict-F2OMZRTO.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-5BETHRSG.js → contract-DNIIW5VJ.js} +13 -5
- package/dist/chunks/{cowork-3MN6EQQO.js → cowork-GMLRGVIW.js} +11 -4
- package/dist/chunks/{create-flow-5FQNNMVO.js → create-flow-J22BO65E.js} +8 -7
- package/dist/chunks/{ebuilder-form-SBHOYBUD.js → ebuilder-form-ANV2FHYV.js} +12 -7
- package/dist/chunks/{ehr-KMOJULKG.js → ehr-GSTGAIY3.js} +266 -24
- package/dist/chunks/{employee-SAK2QYXX.js → employee-22BGJ3QS.js} +3 -3
- package/dist/chunks/{env-LB7NBL2O.js → env-BX3VRFS7.js} +4 -3
- package/dist/chunks/{esb-W2PJCFCX.js → esb-ATAIASX2.js} +12 -7
- package/dist/chunks/{factory-6ZFQXQ46.js → factory-PETTFME6.js} +3 -2
- package/dist/chunks/{fna-ZQPCUV4R.js → fna-YEKBSFSX.js} +14 -8
- package/dist/chunks/{group-chat-2HHAJN46.js → group-chat-BFVXAYFX.js} +5 -4
- package/dist/chunks/{host-ATKVNZAT.js → host-HRBXEPDQ.js} +5 -5
- package/dist/chunks/{host-BQH22NTQ.js → host-LR55CTHP.js} +5 -5
- package/dist/chunks/{host-X2CSLFM6.js → host-SUOY6VYQ.js} +5 -5
- package/dist/chunks/{host-4CBHGOHT.js → host-SZGV4UNT.js} +5 -5
- package/dist/chunks/{host-YTODKMSL.js → host-WIKN2EJ5.js} +5 -5
- package/dist/chunks/{hrm-FFM4UJFS.js → hrm-NRDO4W5G.js} +11 -7
- package/dist/chunks/{im-VEHKDQPB.js → im-IQ2HN23S.js} +8 -7
- package/dist/chunks/{invoice-LVIKSIIF.js → invoice-MNRW76PP.js} +636 -1768
- package/dist/chunks/{jiuchuanhui-X6DINO5P.js → jiuchuanhui-DTRBAX4N.js} +14 -6
- package/dist/chunks/{lazy-dispatch-KSWTN5L6.js → lazy-dispatch-ZVWAI25B.js} +7 -6
- package/dist/chunks/{mail-VDKRGUD3.js → mail-ZVAUXOJA.js} +16 -14
- package/dist/chunks/{meeting-CLC6HP3Z.js → meeting-FEVPZ67S.js} +21 -14
- package/dist/chunks/{okr-CHG4OO3U.js → okr-SDH3E5RA.js} +12 -5
- package/dist/chunks/{operations-5QL3AECO.js → operations-62N6EP3C.js} +4 -3
- package/dist/chunks/{plan-XWR75667.js → plan-E35JFDI7.js} +12 -5
- package/dist/chunks/{project-5BXOOKMG.js → project-73QXBAB2.js} +11 -4
- package/dist/chunks/reception-UWZVACBN.js +1899 -0
- package/dist/chunks/{recruit-stage-DGMFNNDE.js → recruit-stage-4MAOCKFY.js} +320 -57
- package/dist/chunks/registry-IM5G23LS.js +24 -0
- package/dist/chunks/{registry-RHMW6ANW.js → registry-JDUS4E7D.js} +7 -6
- package/dist/chunks/{registry-7FFOXIAW.js → registry-KNKSCJNT.js} +7 -6
- package/dist/chunks/{registry-Z43434VG.js → registry-PO7SI256.js} +4 -4
- package/dist/chunks/{registry-OZSMDVPP.js → registry-PVUZQDDQ.js} +3 -3
- package/dist/chunks/{room-recommend-62H7ZQQ6.js → room-recommend-H7KLXA5R.js} +6 -5
- package/dist/chunks/{schedule-FNXKH3DQ.js → schedule-ESZIBAUO.js} +6 -5
- package/dist/chunks/{workflow-ADHKCAJK.js → workflow-CWJFJLQU.js} +13 -8
- 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 +37 -31
- package/dist/internal/e10/auth/commands.js +163 -8
- package/dist/internal/e10/auth/error-log.js +182 -0
- package/dist/internal/e10/auth/session.js +17 -3
- package/dist/internal/e10/auth-recovery.js +39 -12
- package/dist/internal/e10/context.js +17 -2
- 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/write.js +1 -1
- 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/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 +1 -1
- package/dist/shortcuts/ehr/operations/recruit-candidate-import.js +182 -3
- package/dist/shortcuts/ehr/operations/recruit-position.js +134 -2
- package/dist/shortcuts/ehr/operations/recruit-write.js +1 -1
- 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/im/continuation.js +2 -1
- package/dist/shortcuts/index.js +5 -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/create-flow.js +16 -7
- package/dist/shortcuts/meeting/operations/eb/decision.js +1 -1
- package/dist/shortcuts/meeting/operations/eb/factory.js +1 -1
- 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/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 +145 -33
- package/dist/shortcuts/recruit-stage/continuation.js +2 -1
- package/dist/shortcuts/recruit-stage/operations/jd-create.js +106 -17
- package/dist/shortcuts/recruit-stage/operations/resume-import.js +166 -10
- 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/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 +16 -0
- 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/reception.md +119 -0
- package/package.json +1 -1
- package/scripts/clean.mjs +43 -2
- package/skill-template/business-info.json +18 -2
- package/skill-template/domains/caizhilian.md +1 -1
- package/skill-template/domains/reception-flow.md +18 -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/references/calendar-write.md +1 -1
- package/skills/weaver-e10-cowork/SKILL.md +1 -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 +2 -2
- package/skills/weaver-e10-jucailin/references/recruit-create.md +12 -3
- package/skills/weaver-e10-jucailin/references/recruit-write.md +1 -1
- package/skills/weaver-e10-meeting/SKILL.md +8 -6
- 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 +9 -5
- package/skills/weaver-e10-recruit-stage/lib/recruit-xiaoe-file.mjs +1 -1
- 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-jd-create.md +22 -0
- package/skills/weaver-e10-recruit-stage/references/recruit-resume-import.md +14 -1
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +6 -0
- package/skills/weaver-e10-shijingran/SKILL.md +5 -5
- package/skills/weaver-e10-shijingran/references/source-manifest.json +37 -36
- 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/references/source-manifest.json +32 -32
- package/skills/weaver-e10-yepiaotong/SKILL.md +19 -26
- 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-TTPJJJC4.js +0 -465
- package/dist/chunks/registry-BKBZPBAJ.js +0 -23
- 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,196 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { chmodSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { receptionValidationError } from '../errors.js';
|
|
5
|
+
/** 稳定序列化:键排序,保证同数据同哈希。 */
|
|
6
|
+
export function canonical(value) {
|
|
7
|
+
if (value === null || typeof value !== 'object')
|
|
8
|
+
return JSON.stringify(value ?? null);
|
|
9
|
+
if (Array.isArray(value))
|
|
10
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
11
|
+
const record = value;
|
|
12
|
+
const keys = Object.keys(record).sort();
|
|
13
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonical(record[key])}`).join(',')}}`;
|
|
14
|
+
}
|
|
15
|
+
export function sha256(text) {
|
|
16
|
+
return createHash('sha256').update(String(text)).digest('hex');
|
|
17
|
+
}
|
|
18
|
+
export function ensureDir(path) {
|
|
19
|
+
try {
|
|
20
|
+
mkdirSync(path, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// 已存在或不可写;可写性交给后续真实写入报错
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function readJson(path, fallback = null) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function writeJson(path, value) {
|
|
35
|
+
ensureDir(dirname(path));
|
|
36
|
+
writeFileSync(path, JSON.stringify(value, null, 2));
|
|
37
|
+
}
|
|
38
|
+
/** 最近修改时间,取不到算 0(用于 Python 缓存新鲜度判断)。 */
|
|
39
|
+
export function mtimeMs(path) {
|
|
40
|
+
try {
|
|
41
|
+
return statSync(path).mtimeMs;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function fileSize(path) {
|
|
48
|
+
try {
|
|
49
|
+
return statSync(path).size;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** 只有存在且未超过 ttl 才算新鲜。 */
|
|
56
|
+
export function fresh(path, ttlMs) {
|
|
57
|
+
const mtime = mtimeMs(path);
|
|
58
|
+
return Boolean(mtime) && Date.now() - mtime < ttlMs;
|
|
59
|
+
}
|
|
60
|
+
/** 严紧权限落盘;Windows 上 chmod 无效,忽略即可。 */
|
|
61
|
+
export function writeJsonSecure(path, value, mode = 0o600) {
|
|
62
|
+
writeJson(path, value);
|
|
63
|
+
try {
|
|
64
|
+
chmodSync(path, mode);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Windows 无 POSIX 权限位
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// JSON 入参
|
|
72
|
+
//
|
|
73
|
+
// 三条铁律(2026-11-02 事故的产物,不要简化):
|
|
74
|
+
// 1. 文件优先(--xxx-file),彻底绕开 shell 转义;
|
|
75
|
+
// 2. 解析前清 BOM / 零宽字符,失败再试中文引号 + 尾随逗号;
|
|
76
|
+
// 3. 报错必须自证:区分 missing / file_unreadable / invalid,invalid 带 preview + hasBom。
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
const BOM = /^\uFEFF/;
|
|
79
|
+
const ZERO_WIDTH = /[\u200B-\u200D\uFEFF]/g;
|
|
80
|
+
/** 纯文本 → 对象。不做 IO,便于单测。 */
|
|
81
|
+
export function parseJsonText(raw) {
|
|
82
|
+
const src = String(raw ?? '');
|
|
83
|
+
const hasBom = src.charCodeAt(0) === 0xfeff;
|
|
84
|
+
const cleaned = src.replace(BOM, '').replace(ZERO_WIDTH, '').trim();
|
|
85
|
+
try {
|
|
86
|
+
return { ok: true, value: JSON.parse(cleaned), hasBom };
|
|
87
|
+
}
|
|
88
|
+
catch (firstError) {
|
|
89
|
+
const fixed = cleaned
|
|
90
|
+
.replace(/[\u201C\u201D]/g, '"')
|
|
91
|
+
.replace(/[\u2018\u2019]/g, "'")
|
|
92
|
+
.replace(/,\s*([}\]])/g, '$1');
|
|
93
|
+
try {
|
|
94
|
+
return { ok: true, value: JSON.parse(fixed), hasBom, autoFixed: true };
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
reason: 'invalid',
|
|
100
|
+
error: firstError.message,
|
|
101
|
+
preview: cleaned.slice(0, 120),
|
|
102
|
+
hasBom,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** 从选项对象读 JSON:`--<name>-file` 优先,其次 `--<name>` 内联。 */
|
|
108
|
+
export function readJsonArg(options, name) {
|
|
109
|
+
const filePath = textOf(options, `${name}File`) || textOf(options, `${name}-file`);
|
|
110
|
+
if (filePath) {
|
|
111
|
+
let raw;
|
|
112
|
+
try {
|
|
113
|
+
raw = readFileSync(filePath, 'utf8');
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
reason: 'file_unreadable',
|
|
119
|
+
path: filePath,
|
|
120
|
+
error: error.message,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return { ...parseJsonText(raw), source: 'file', path: filePath };
|
|
124
|
+
}
|
|
125
|
+
const inline = options[name];
|
|
126
|
+
if (inline === undefined || inline === null || inline === true) {
|
|
127
|
+
return { ok: false, reason: 'missing' };
|
|
128
|
+
}
|
|
129
|
+
return { ...parseJsonText(String(inline)), source: 'inline' };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 取一个 JSON 参数,按语义抛错。
|
|
133
|
+
*
|
|
134
|
+
* @param options commander 收集到的选项对象
|
|
135
|
+
* @param name 逻辑名,如 `known`(对应 --known / --known-file)
|
|
136
|
+
* @param config.code 错误码前缀,如 `KNOWN` → KNOWN_REQUIRED / KNOWN_INVALID / KNOWN_FILE_UNREADABLE
|
|
137
|
+
*/
|
|
138
|
+
export function jsonArg(options, name, config = {}) {
|
|
139
|
+
const prefix = config.code || name.toUpperCase().replace(/-/g, '_');
|
|
140
|
+
const result = readJsonArg(options, name);
|
|
141
|
+
if (result.ok)
|
|
142
|
+
return result.value;
|
|
143
|
+
if (result.reason === 'missing') {
|
|
144
|
+
if (!config.required)
|
|
145
|
+
return null;
|
|
146
|
+
throw receptionValidationError(`${prefix}_REQUIRED`, `缺少 --${name} <JSON> 或 --${name}-file <path>`, {
|
|
147
|
+
hint: config.hint || `推荐用文件传参:先写 ${name}.json,再 --${name}-file ${name}.json`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (result.reason === 'file_unreadable') {
|
|
151
|
+
throw receptionValidationError(`${prefix}_FILE_UNREADABLE`, `读不到 --${name}-file:${result.path}`, {
|
|
152
|
+
path: result.path,
|
|
153
|
+
detail: result.error,
|
|
154
|
+
hint: '检查文件是否存在、路径是否正确、是否有读权限;不要因此改回内联传参',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
throw receptionValidationError(`${prefix}_INVALID`, `--${name} 不是合法 JSON:${result.error}`, {
|
|
158
|
+
hint: '常见原因:UTF-8 BOM(hasBom:true)、中文引号、尾随逗号、shell 吃掉双引号。'
|
|
159
|
+
+ '请看 preview 定位,修 JSON 本身,不要换传参方式重试',
|
|
160
|
+
preview: result.preview,
|
|
161
|
+
hasBom: result.hasBom,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/** 从选项对象取字符串;commander 对未传的选项给 undefined,对布尔开关给 true。 */
|
|
165
|
+
export function textOf(options, key) {
|
|
166
|
+
const value = options[key];
|
|
167
|
+
if (value === undefined || value === null || value === true || value === false)
|
|
168
|
+
return '';
|
|
169
|
+
return String(value).trim();
|
|
170
|
+
}
|
|
171
|
+
/** 姓名归一:数组 / 逗号分隔串 / {name} 对象都吃。 */
|
|
172
|
+
export function nameList(value) {
|
|
173
|
+
const list = Array.isArray(value) ? value : String(value ?? '').split(/[,,、;;\s]+/);
|
|
174
|
+
return list
|
|
175
|
+
.map((item) => (typeof item === 'string' ? item : (item && (item.name || item.id)) || ''))
|
|
176
|
+
.map((item) => String(item).trim())
|
|
177
|
+
.filter(Boolean);
|
|
178
|
+
}
|
|
179
|
+
const hasValue = (value) => !(value === undefined || value === null || value === ''
|
|
180
|
+
|| (Array.isArray(value) && value.length === 0));
|
|
181
|
+
/** 7 项缺口:只判「有没有」,语义判断仍由模型完成。 */
|
|
182
|
+
export function computeGaps(data = {}) {
|
|
183
|
+
const filled = [];
|
|
184
|
+
const gap = [];
|
|
185
|
+
const push = (key, value) => (hasValue(value) ? filled.push(key) : gap.push(key));
|
|
186
|
+
push('customer', data.customer || data.customerId);
|
|
187
|
+
push('headcount', data.headcount);
|
|
188
|
+
push('visitDate', data.visitDate);
|
|
189
|
+
push('visitType', data.visitType);
|
|
190
|
+
push('companions', data.companions);
|
|
191
|
+
push('demand', data.demand);
|
|
192
|
+
const logistics = ['headcount', 'meetingRoom', 'pickup', 'meal', 'hotel', 'gift'];
|
|
193
|
+
const logisticsMissing = logistics.filter((key) => !hasValue(data[key]));
|
|
194
|
+
push('logistics', logisticsMissing.length ? null : logistics);
|
|
195
|
+
return { filled, gap, logisticsMissing };
|
|
196
|
+
}
|
|
@@ -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
|
+
}
|