chainlesschain 0.166.89 → 0.166.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.md +87 -3
  2. package/package.json +3 -3
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/assets/{AIOps-C8AkN9R5.js → AIOps-CZzXRe1-.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-BrAzSQor.js → ActionButton-_AZfbW1Q.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-Cu56X8Oh.js → Analytics-CzaUx_wv.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-BP1Riofl.js → AppLayout-xHEeeD-m.js} +5 -5
  9. package/src/assets/web-panel/assets/{Artifacts-CIW-nNAz.js → Artifacts-DmTbtudE.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-CJtDabk-.js → Audit-Cn4ng4fo.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-DYIHeQ_P.js → BackgroundAgents-DLUY1VD2.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-B4CsFzj7.js → Backup-B2v--wB6.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-3eoJ5kfb.js → BaseInput-Bgih7yAv.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-BBhX2L_E.js → Chat-1MiiA3yA.js} +4 -4
  15. package/src/assets/web-panel/assets/ChatBubbleRenderer-CPLynmbb.js +1 -0
  16. package/src/assets/web-panel/assets/{Checkbox-5UvmwHVk.js → Checkbox-DsyBa_NO.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-BFy8zsBN.js → Codegen-B7WCFx9H.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-4M6bKRRO.js → Col-D2x4GBGm.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-D6LC0j7_.js → Community-Rnwg9nea.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-Dd48xMcv.js → Compact-BPczlrRh.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-Ba85ggmy.js → Compliance-Br1L_wGU.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-D1BXlkuK.js → Cowork-DlsWB_oS.js} +2 -2
  23. package/src/assets/web-panel/assets/{Cron-Ca2Uy7Ia.js → Cron-BWo_KGpG.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-C2bDRXZg.js → Crosschain-Cug7N8ge.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-DySZKAJf.js → DID-CMbyOZJN.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-DDpYf-TT.js → Dashboard-C4dl_UaE.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-DGjROfPv.js → Dropdown-CBrC7M-9.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-DxX9QYiB.js → EmailListRenderer-DTq7tU4b.js} +1 -1
  29. package/src/assets/web-panel/assets/{EvolutionSettings-TBCZyrSJ.js → EvolutionSettings-DWP3csxS.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BeqwTXBa.js → FamilyGuardDashboard-DZl7zuv_.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-CiCpKJQ1.js → Federation-W-GSlVK5.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-CdTmbLkU.js → FormItemContext-DMB7xYFV.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-BwbkEY-5.js → GenericCardRenderer-DNv8GEoq.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-VnAga2vv.js → Git-BZ0Bbzw6.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-i-4Ebs-H.js → Governance-BNEdcOM5.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-CirjTgNw.js → Inference-C8249gIz.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-CunS7vhA.js → KnowledgeGraph-ngzebU3e.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-DBuTF0c6.js → Logs-CP15th-f.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-B-iE-tt_.js → MarkdownRenderer-CW3B3JDq.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-BK6ZivTS.js → Marketplace-CaH59OsY.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-CUnCRnd3.js → McpTools-C07vnJev.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-CBGGr8Gy.js → Memory-CqAOkVAS.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-reB6iaWD.js → MobileBridge-qp7mZimq.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-o1YdVB7i.js → MobileProjects-DKhbqvTw.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-if5OE3GH.js → Mtc-CRYCnWXv.js} +4 -4
  46. package/src/assets/web-panel/assets/{MtcAudit-BTZWpq0j.js → MtcAudit-Z08IrKws.js} +4 -4
  47. package/src/assets/web-panel/assets/{Multisig-8cvMAYFL.js → Multisig-BWdatUnH.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-BMvUXAWo.js → NLProgramming-BKWiCZ_m.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-BWWjColh.js → Notes-Bgpzk3sm.js} +3 -3
  50. package/src/assets/web-panel/assets/{NotificationSettings-Ccr4lIhS.js → NotificationSettings-DvtZEktR.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-DhksGy6D.js → OrderTableRenderer-CBcy0szc.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-DciXQfdY.js → Organization-DRRL6vW1.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-8DR7uMSx.js → Overflow-COmfB8Ns.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-DE6euHYl.js → P2P-BqMq_DSa.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-ByuWoauq.js → PdhVaultBrowser-BX7iNvle.js} +3 -3
  56. package/src/assets/web-panel/assets/{Permissions-DkjM87ac.js → Permissions-DzYkjmV_.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-2DLp0KTL.js → PersonalDataHub-BBTcxBbN.js} +3 -3
  58. package/src/assets/web-panel/assets/{Pipeline-Ci2Qlkfr.js → Pipeline-eRkYFSbO.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-DlwDnDVA.js → Privacy-Ch7IFKBh.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-CmMifW39.js → ProjectInit-Ci8EcBYg.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-DJVFIh-6.js → ProjectSettings-B4lYHFbI.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-P2nWBS70.js → Projects-zkE5cCo_.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-B2cxaGCU.js → Providers-BKDVD3_e.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-CpO77kcE.js → QrScannerModal-BromrHlf.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-BDQl1hb0.js → QuickAsk-CCgoY1gR.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-DTD2f3IN.js → Recommend-DCPZvpq7.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-DGZAnwvs.js → RemoteSession-DPnFP8HU.js} +3 -3
  68. package/src/assets/web-panel/assets/{Reputation-B_R4vitb.js → Reputation-l6r8ytAs.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-4Jj6JyXN.js → Row-ke9Mk1cx.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-D1t1MCZ-.js → RssFeed-DQOEzUEn.js} +2 -2
  71. package/src/assets/web-panel/assets/{Search-CyOUlIn7.js → Search-BqgA0aUz.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-BmknPT5Q.js → Security-FeGD_jqV.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-DkEK8mpa.js → Services-4kiYYAjZ.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-Bvo5bpuk.js → Skeleton-BbU8Xegs.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-AMmG8fim.js → Skills-Dl2bUMUL.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-Dl7-OKOV.js → Sla-xRGALeaZ.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-DtiGKu0E.js → SpeechSettings-DZfd4RDm.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-Ch7mp3ES.js → SyncSettings-BRtCV9Ga.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-Ck-sEgj-.js → Tasks-DVRQklfG.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-BlV22Il8.js → Templates-BbhE5jt5.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-CaaAu0mX.js → Tenant-qXPDNKbK.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-lA9g3FtJ.js → Terminal-CV48Ks7E.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-CG26IFC3.js → TimelineRenderer-BLGfJIDr.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-iaJUBLBm.js → Tokens-7G9H6Hrt.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-DWQJtLK3.js → Trigger--F64N11F.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-CTjmUwqp.js → Trust-Cq1r-D_y.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CMnxufgD.js → UkeySign-Dqyu4RVh.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-diCFSWJw.js → VideoEditing-DuFmWH_B.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-Ba1oND1K.js → Wallet-C3mg7EWP.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-DZDqPS-S.js → WebAuthn-CDK-Aj_X.js} +5 -5
  91. package/src/assets/web-panel/assets/{WorkflowEditor-CfN8fPMe.js → WorkflowEditor--K7K-T1F.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-BiqMsO_i.js → chat-BQDcS6VP.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-By8LkjqH.js → colors-BKxJL2-T.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-B0mxvRe3.js → compact-item-CFjQAMbY.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-Li2ub348.js → createContext-DbgCsdQd.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-DlIv9Mbc.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-DYbHPkbl.js → hasIn-BhkaOBNv.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-b4O_YBVR.js → index-3YutaXnp.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BcOJEgBE.js → index-3djsY75k.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C5Se6Lnv.js → index-3uR0ThJO.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-ByqFKMWL.js → index-7BY44UEv.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BRzewWeE.js → index-B4LlAJqc.js} +1 -1
  103. package/src/assets/web-panel/assets/index-BC972Grv.js +1 -0
  104. package/src/assets/web-panel/assets/{index-Czt7WAtA.js → index-BNjYPJNU.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-1lVoRUrQ.js → index-BRqlCTrz.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CGZzexNa.js → index-BfGGsAU7.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-D6qL3QBU.js → index-BuWPqSGT.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DAOpBv3C.js → index-C0DzhUOX.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CTVPeUh3.js → index-CB4oHynY.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-KqfqSdAC.js → index-CE506mgj.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DPRgnazi.js → index-CJoXgJ2N.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-beDji-cB.js → index-COGy6wXn.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CMh3VxgO.js → index-CRtUDiXx.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Bm2P2Kgp.js → index-CSUDlsIr.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Cpfn4YbT.js → index-CUs7XpUO.js} +1 -1
  116. package/src/assets/web-panel/assets/index-CVifyQqL.js +1 -0
  117. package/src/assets/web-panel/assets/{index-CYv3JIzl.js → index-CoOu7sdP.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CVZNYRzM.js → index-D-Yh3-1z.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-KWt8Owo8.js → index-D2Zb-Kd1.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CgyrklzQ.js → index-D9sVcw6v.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BFhksbXp.js → index-DEwHywz9.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CEp_3fKJ.js → index-DFt48Z-x.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-ClsJN8sW.js → index-DJ2OiRvh.js} +3 -3
  124. package/src/assets/web-panel/assets/{index-DB4DCzqU.js → index-DP1NrM9J.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DIImi0cd.js → index-DdcrixaQ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-Bd5znvCV.js → index-De7daEnc.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CA0AOURT.js → index-Dhfa_BIl.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-hw3sCYAz.js → index-DjmVlpap.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BSk0xuXP.js → index-DmYK9Tqt.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Bc6bxU24.js → index-Dn2QSsuI.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-B0D0HkkM.js → index-Du_SxxBY.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DgMFnBOv.js → index-DvWWFJHJ.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-zSzXsF9b.js → index-T9ka3Xj3.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-CHgiSV6R.js → index-aPdVzagA.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-BZqTG7fP.js → index-bMX8BBLE.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-C-RE3mFh.js → index-sKfUUldY.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-TgDL_7OY.js → initDefaultProps-wrqabmui.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-fHosyV5c.js → motion-BoyB3ilP.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-C_QfW5qg.js → move-Cq31cSFE.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-u7ywDVOg.js → mtc-parser-BkxNt-8H.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-BEJdUgE9.js → omit-DofGni53.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-DGkQFAJ5.js → pickAttrs-BnluLL16.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-DUyZXAjt.js → placementArrow-C-yegJSP.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-B4xDxO7Q.js → responsiveObserve-CD3ziNhu.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-B8WuJtst.js → slide-ToZO4gQ1.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-DLUqy7JO.js → statusUtils-DFVmDmVl.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-BYOJOyqL.js → styleChecker-De0Y8g1O.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-WChWQURv.js → useFlexGapSupport-wBKIYFKX.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-vLNpm3dv.js → useFs-CP9nmUMJ.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-DSZ8unoU.js → usePersonalDataHub-vFGrVDyp.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-C4Xf7Kg1.js → vnode-BcBIjvjH.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-BulmeAGy.js → zoom-DnXDAQxA.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +4 -4
  155. package/src/commands/automation.js +35 -8
  156. package/src/commands/hub.js +147 -0
  157. package/src/commands/project.js +162 -0
  158. package/src/data/changelog.json +13 -0
  159. package/src/gateways/ws/personal-data-hub-protocol.js +54 -8
  160. package/src/lib/audit-logger.js +16 -29
  161. package/src/lib/audit-sanitizer.js +279 -0
  162. package/src/lib/automation-center.js +14 -4
  163. package/src/lib/automation-engine.js +151 -39
  164. package/src/lib/knowledge-graph.js +27 -0
  165. package/src/lib/personal-data-hub-wiring.js +44 -1
  166. package/src/lib/scheduler-kernel/automation-adapter.js +15 -3
  167. package/src/lib/scheduler-kernel/automation-event-adapter.js +15 -3
  168. package/src/assets/web-panel/assets/ChatBubbleRenderer-RN8cbv7w.js +0 -1
  169. package/src/assets/web-panel/assets/devWarning-D90arbUg.js +0 -1
  170. package/src/assets/web-panel/assets/index-CeP1oROI.js +0 -1
  171. package/src/assets/web-panel/assets/index-Cu9gkyRc.js +0 -1
@@ -14,6 +14,8 @@
14
14
  * cc project list [--user <id>] [--status <s>] [--limit <n>] [--json]
15
15
  * cc project show <id> [--json]
16
16
  * cc project delete <id> [--hard] [--json]
17
+ * cc project risk-evaluate --snapshot <json-file> [--json]
18
+ * cc project task-description-preview --snapshot <json-file> [--json]
17
19
  *
18
20
  * 设计 (per #21 v1.2 GA 反馈 "目标在手机端做ai项目的交互要像在电脑端那样丝滑"):
19
21
  * CLI 直接读写 desktop chainlesschain.db, 立刻在 desktop UI 出现,
@@ -21,6 +23,8 @@
21
23
  */
22
24
 
23
25
  import chalk from "chalk";
26
+ import fs from "node:fs";
27
+ import path from "node:path";
24
28
  import {
25
29
  openProjectsDb,
26
30
  defaultProjectDbPath,
@@ -30,6 +34,114 @@ import {
30
34
  } from "../lib/project-runtime.js";
31
35
 
32
36
  const DEFAULT_USER = "default";
37
+ const MAX_PROJECT_SNAPSHOT_BYTES = 2 * 1024 * 1024;
38
+
39
+ function snapshotFailure(code) {
40
+ const error = new Error(code);
41
+ error.code = code;
42
+ throw error;
43
+ }
44
+
45
+ /** Read a user-selected regular snapshot without following links or devices. */
46
+ function readProjectSnapshot(file, io = fs) {
47
+ if (
48
+ typeof file !== "string" ||
49
+ !file ||
50
+ /^[\\/]{2}[?.][\\/]/u.test(file) ||
51
+ (process.platform === "win32" &&
52
+ /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(
53
+ path.basename(file),
54
+ ))
55
+ )
56
+ snapshotFailure("SNAPSHOT_INVALID_PATH");
57
+ let fd;
58
+ try {
59
+ // Reject FIFO/device paths before opening. O_NONBLOCK also covers a POSIX
60
+ // file replaced by a FIFO between this check and the descriptor open.
61
+ if (!io.lstatSync(file).isFile())
62
+ snapshotFailure("SNAPSHOT_REGULAR_FILE_REQUIRED");
63
+ fd = io.openSync(
64
+ file,
65
+ fs.constants.O_RDONLY |
66
+ (fs.constants.O_NONBLOCK || 0) |
67
+ (fs.constants.O_NOFOLLOW || 0),
68
+ );
69
+ const before = io.fstatSync(fd);
70
+ if (!before.isFile()) snapshotFailure("SNAPSHOT_REGULAR_FILE_REQUIRED");
71
+ if (
72
+ !Number.isSafeInteger(before.size) ||
73
+ before.size < 0 ||
74
+ before.size > MAX_PROJECT_SNAPSHOT_BYTES
75
+ )
76
+ snapshotFailure("SNAPSHOT_FILE_TOO_LARGE");
77
+ const data = Buffer.alloc(before.size + 1);
78
+ let count = 0;
79
+ while (count < data.length) {
80
+ const read = io.readSync(fd, data, count, data.length - count, count);
81
+ if (read === 0) break;
82
+ count += read;
83
+ }
84
+ const after = io.fstatSync(fd);
85
+ if (
86
+ count !== before.size ||
87
+ after.size !== before.size ||
88
+ after.mtimeMs !== before.mtimeMs ||
89
+ after.ctimeMs !== before.ctimeMs
90
+ )
91
+ snapshotFailure("SNAPSHOT_CHANGED_DURING_READ");
92
+ let text;
93
+ try {
94
+ text = new TextDecoder("utf-8", { fatal: true }).decode(
95
+ data.subarray(0, count),
96
+ );
97
+ } catch {
98
+ snapshotFailure("SNAPSHOT_INVALID_UTF8");
99
+ }
100
+ try {
101
+ return JSON.parse(text);
102
+ } catch {
103
+ snapshotFailure("SNAPSHOT_INVALID_JSON");
104
+ }
105
+ } catch (error) {
106
+ if (error.code?.startsWith("SNAPSHOT_")) throw error;
107
+ snapshotFailure("SNAPSHOT_READ_FAILED");
108
+ } finally {
109
+ if (fd !== undefined) io.closeSync(fd);
110
+ }
111
+ }
112
+
113
+ function printSnapshotError(error, options) {
114
+ const code =
115
+ typeof error?.code === "string" &&
116
+ /^(SNAPSHOT|ACTION)_[A-Z_]+$/u.test(error.code)
117
+ ? error.code
118
+ : "SNAPSHOT_EVALUATION_FAILED";
119
+ if (options.json)
120
+ console.log(JSON.stringify({ status: "error", error: code }));
121
+ else console.error(chalk.red(code));
122
+ process.exitCode = 2;
123
+ }
124
+
125
+ function printProjectRisk(result) {
126
+ console.log("Offline project risk evaluation (unverified snapshot)");
127
+ console.log(`Status: ${result.status}`);
128
+ console.log(`Rule: ${result.ruleVersion}`);
129
+ if (result.asOf) console.log(`As of: ${result.asOf}`);
130
+ if (result.inputDigest) console.log(`Input: ${result.inputDigest}`);
131
+ if (result.status !== "evaluated") {
132
+ console.log(`Reasons: ${result.reasonCodes.join(", ")}`);
133
+ return;
134
+ }
135
+ console.log(
136
+ `Tasks: ${result.summary.taskCount}; matching tasks: ${result.summary.riskTaskCount}; overdue: ${result.summary.overdueTaskCount}; blocked: ${result.summary.blockedTaskCount}`,
137
+ );
138
+ for (const task of result.tasks) {
139
+ const blockers = task.blockingTaskRefs.map((ref) => ref.id).join(", ");
140
+ console.log(
141
+ `${task.taskRef.id}: ${task.reasonCodes.join(", ")}${blockers ? ` (dependencies: ${blockers})` : ""}`,
142
+ );
143
+ }
144
+ }
33
145
 
34
146
  function _now() {
35
147
  return Date.now();
@@ -74,6 +186,54 @@ export function registerProjectCommand(program) {
74
186
  "Manage desktop projects (shared SQLite, syncs to mobile via Phase 3d)",
75
187
  );
76
188
 
189
+ cmd
190
+ .command("risk-evaluate")
191
+ .description(
192
+ "Evaluate overdue tasks and direct blockers in an offline snapshot",
193
+ )
194
+ .requiredOption(
195
+ "--snapshot <json-file>",
196
+ "Complete project risk snapshot (max 2 MiB)",
197
+ )
198
+ .option("--json", "Output JSON")
199
+ .action(async (options) => {
200
+ try {
201
+ const snapshot = readProjectSnapshot(options.snapshot);
202
+ const { evaluateProjectRiskSnapshot } =
203
+ await import("@chainlesschain/session-core/project-risk-evaluation");
204
+ const result = evaluateProjectRiskSnapshot(snapshot);
205
+ if (options.json) console.log(JSON.stringify(result, null, 2));
206
+ else printProjectRisk(result);
207
+ if (result.status === "insufficient-data") process.exitCode = 2;
208
+ } catch (error) {
209
+ printSnapshotError(error, options);
210
+ }
211
+ });
212
+
213
+ cmd
214
+ .command("task-description-preview")
215
+ .description(
216
+ "Preview a task description request from an unverified offline snapshot",
217
+ )
218
+ .requiredOption(
219
+ "--snapshot <json-file>",
220
+ "Task/project/description/idempotencyKey JSON (max 2 MiB)",
221
+ )
222
+ .option("--json", "Output JSON")
223
+ .action(async (options) => {
224
+ try {
225
+ const snapshot = readProjectSnapshot(options.snapshot);
226
+ const { createTaskDescriptionPreview } =
227
+ await import("@chainlesschain/session-core/task-description-action-service");
228
+ const result = createTaskDescriptionPreview(snapshot);
229
+ if (!options.json)
230
+ console.log("Offline task description preview (unverified snapshot)");
231
+ console.log(JSON.stringify(result, null, 2));
232
+ } catch (error) {
233
+ printSnapshotError(error, options);
234
+ }
235
+ });
236
+
77
237
  // ===== init =====
78
238
  cmd
79
239
  .command("init <name>")
@@ -255,4 +415,6 @@ export function registerProjectCommand(program) {
255
415
  // 暴露给单测
256
416
  export const _projectInternals = {
257
417
  defaultProjectDbPath,
418
+ readProjectSnapshot,
419
+ MAX_PROJECT_SNAPSHOT_BYTES,
258
420
  };
@@ -1,6 +1,19 @@
1
1
  {
2
2
  "source": "CHANGELOG.md",
3
3
  "releases": [
4
+ {
5
+ "productVersion": null,
6
+ "cliVersion": "0.166.90",
7
+ "cliVersions": ["0.166.90"],
8
+ "date": null,
9
+ "title": "",
10
+ "sections": [
11
+ {
12
+ "heading": "Added - cc CLI 0.166.90: recoverable projections and project risk evidence",
13
+ "body": "- Publish Personal Data Hub `0.4.63` and Session Core `0.3.15` before the CLI,\n with exact dependency pins and public tarball verification. Require the full\n PDH suite with its declared native SQLCipher driver on Linux, Windows and macOS.\n- Publish Agent SDK `0.2.13` with Vitest `4.1.11` test tooling to remove the\n vulnerable Tinypool dependency, and upgrade the workspace proxy-addr to\n `2.0.8`. Require SDK build and tests on the exact Linux, Windows and macOS\n release commit before OIDC publication.\n- Persist PDH projection receipts, recover incomplete derivations and support\n bounded receipt retention. Harden RAG updates, audit redaction and capability\n reporting so unavailable sources do not appear as successful live collection.\n- Add offline project risk evaluation and version-bound task description\n previews. Session Core also supplies native SQLite action/read services and\n saved rule/source reviews for the desktop project workbench; CLI previews\n carry unverified snapshot authority and do not execute those desktop actions.\n- Pair VS Code `0.37.135` and JetBrains `0.4.153` with CLI `0.166.90` after\n exact-commit release checks and public npm readback."
14
+ }
15
+ ]
16
+ },
4
17
  {
5
18
  "productVersion": null,
6
19
  "cliVersion": "0.166.89",
@@ -193,7 +193,8 @@ async function withHub(fn, loadHub = getHub) {
193
193
  }
194
194
 
195
195
  function governedHubLoader(factory) {
196
- if (typeof factory === "function") return () => getGovernedAnalysisHub(factory);
196
+ if (typeof factory === "function")
197
+ return () => getGovernedAnalysisHub(factory);
197
198
  const error = new Error(
198
199
  "Personal Data Hub model egress requires an authenticated evolution composition factory",
199
200
  );
@@ -201,16 +202,24 @@ function governedHubLoader(factory) {
201
202
  throw error;
202
203
  }
203
204
 
205
+ function validateEntityReference(entityType, entityId) {
206
+ if (
207
+ !["event", "person", "place", "item", "topic"].includes(entityType) ||
208
+ typeof entityId !== "string" ||
209
+ !entityId.trim() ||
210
+ entityId.length > 1024
211
+ ) {
212
+ throw new Error("A valid entityType and entityId are required");
213
+ }
214
+ }
215
+
204
216
  export const PERSONAL_DATA_HUB_HANDLERS = {
205
217
  "personal-data-hub.ask": async (msg, context = {}) => {
206
218
  const factory = context.server?.evolutionCompositionFactory;
207
- return withHub(
208
- async (hub) => {
209
- if (!hub.engine) throw new Error("Analysis engine unavailable");
210
- return await hub.engine.ask(msg.question, msg.options || {});
211
- },
212
- governedHubLoader(factory),
213
- );
219
+ return withHub(async (hub) => {
220
+ if (!hub.engine) throw new Error("Analysis engine unavailable");
221
+ return await hub.engine.ask(msg.question, msg.options || {});
222
+ }, governedHubLoader(factory));
214
223
  },
215
224
 
216
225
  // Path Y: prompt context only, no LLM call. Lets web-shell / mobile host
@@ -250,6 +259,43 @@ export const PERSONAL_DATA_HUB_HANDLERS = {
250
259
  "personal-data-hub.list-adapters": async () =>
251
260
  withHub((hub) => hub.registry.list()),
252
261
 
262
+ "personal-data-hub.derivation-status": async (msg) =>
263
+ withHub((hub) =>
264
+ hub.registry.getDerivationStatus({
265
+ adapter: msg.adapter,
266
+ scope: msg.scope,
267
+ }),
268
+ ),
269
+
270
+ "personal-data-hub.derivation-state": async (msg) =>
271
+ withHub((hub) => {
272
+ validateEntityReference(msg.entityType, msg.entityId);
273
+ return hub.vault
274
+ .getDerivationStore()
275
+ .getState(msg.entityType, msg.entityId, {
276
+ consumerId: hub.registry.consumerId,
277
+ });
278
+ }),
279
+
280
+ "personal-data-hub.retry-derivations": async (msg) =>
281
+ withHub((hub) =>
282
+ hub.registry.retryDerivations({
283
+ adapter: msg.adapter,
284
+ scope: msg.scope,
285
+ limit: msg.limit,
286
+ }),
287
+ ),
288
+
289
+ "personal-data-hub.delete-entity": async (msg) =>
290
+ withHub(async (hub) => {
291
+ if (msg.confirm !== true)
292
+ throw new Error("Entity deletion requires confirm: true");
293
+ validateEntityReference(msg.entityType, msg.entityId);
294
+ const deleted = hub.vault.deleteEntity(msg.entityType, msg.entityId);
295
+ const derivations = await hub.registry.retryDerivations({ limit: 1000 });
296
+ return { deleted, derivations };
297
+ }),
298
+
253
299
  // Per-adapter readiness ("能否采集 + 不能的原因"). Probes each adapter's
254
300
  // authenticate({ readinessOnly: true }) — see AdapterRegistry.readiness().
255
301
  // The web-shell PersonalDataHub view renders this as a 就绪状态 column so
@@ -6,6 +6,7 @@
6
6
  import crypto from "crypto";
7
7
  import { safeJsonParse } from "./safe-json.js";
8
8
  import { escapeLike } from "./sql-like.js";
9
+ import { sanitizeAuditDetails } from "./audit-sanitizer.js";
9
10
 
10
11
  /**
11
12
  * Event types for audit logging.
@@ -112,29 +113,15 @@ export function assessRisk(eventType, operation, details) {
112
113
  * Sanitize sensitive data from log details.
113
114
  */
114
115
  export function sanitizeDetails(details) {
115
- if (!details || typeof details !== "object") return details;
116
-
117
- const sanitized = { ...details };
118
- const sensitiveKeys = [
119
- "password",
120
- "secret",
121
- "secretKey",
122
- "secret_key",
123
- "privateKey",
124
- "private_key",
125
- "token",
126
- "apiKey",
127
- "api_key",
128
- "mnemonic",
129
- ];
130
-
131
- for (const key of sensitiveKeys) {
132
- if (sanitized[key]) {
133
- sanitized[key] = "[REDACTED]";
134
- }
135
- }
116
+ return sanitizeAuditDetails(details);
117
+ }
136
118
 
137
- return sanitized;
119
+ function sanitizeErrorMessage(value) {
120
+ const sanitized = sanitizeDetails(value);
121
+ if (sanitized == null) return null;
122
+ return typeof sanitized === "object"
123
+ ? JSON.stringify(sanitized)
124
+ : String(sanitized);
138
125
  }
139
126
 
140
127
  /**
@@ -146,8 +133,7 @@ export function logEvent(db, event) {
146
133
  const id = crypto.randomUUID();
147
134
  const sanitized = sanitizeDetails(event.details);
148
135
  const risk =
149
- event.riskLevel ||
150
- assessRisk(event.eventType, event.operation, event.details);
136
+ event.riskLevel || assessRisk(event.eventType, event.operation, sanitized);
151
137
 
152
138
  db.prepare(
153
139
  `INSERT INTO audit_log (id, event_type, operation, actor, target, details, risk_level, ip_address, user_agent, success, error_message)
@@ -158,12 +144,12 @@ export function logEvent(db, event) {
158
144
  event.operation || "unknown",
159
145
  event.actor || null,
160
146
  event.target || null,
161
- sanitized ? JSON.stringify(sanitized) : null,
147
+ sanitized == null ? null : JSON.stringify(sanitized),
162
148
  risk,
163
149
  event.ipAddress || null,
164
150
  event.userAgent || null,
165
151
  event.success !== false ? 1 : 0,
166
- event.errorMessage || null,
152
+ sanitizeErrorMessage(event.errorMessage),
167
153
  );
168
154
 
169
155
  return { id, riskLevel: risk, createdAt: new Date().toISOString() };
@@ -516,7 +502,8 @@ export function logEventV2(
516
502
  if (_logStatesV2.has(logId)) {
517
503
  throw new Error(`Log already registered: ${logId}`);
518
504
  }
519
- const risk = riskLevel || assessRisk(eventType, operation, details);
505
+ const sanitized = sanitizeDetails(details);
506
+ const risk = riskLevel || assessRisk(eventType, operation, sanitized);
520
507
  if (!RISK_LEVELS_V2.includes(risk)) {
521
508
  throw new Error(`Invalid riskLevel: ${risk}`);
522
509
  }
@@ -527,12 +514,12 @@ export function logEventV2(
527
514
  operation,
528
515
  actor: actor || null,
529
516
  target: target || null,
530
- details: sanitizeDetails(details) || null,
517
+ details: sanitized ?? null,
531
518
  riskLevel: risk,
532
519
  ipAddress: ipAddress || null,
533
520
  userAgent: userAgent || null,
534
521
  success: success !== false,
535
- errorMessage: errorMessage || null,
522
+ errorMessage: sanitizeErrorMessage(errorMessage),
536
523
  status: LOG_STATUS_V2.ACTIVE,
537
524
  integrityStatus: INTEGRITY_STATUS_V2.UNVERIFIED,
538
525
  prevHash: _lastChainHash,
@@ -0,0 +1,279 @@
1
+ /**
2
+ * JSON-safe audit details. Limits apply before string processing and while
3
+ * traversing; getters and user-provided toJSON methods are never invoked.
4
+ * This is field/credential redaction, not detection of arbitrary secrets in prose.
5
+ */
6
+ const REDACTED = "[REDACTED]";
7
+ const TRUNCATED = "[Truncated]";
8
+ const LIMITS = Object.freeze({
9
+ depth: 8,
10
+ entries: 100,
11
+ nodes: 512,
12
+ stringLength: 4096,
13
+ keyLength: 256,
14
+ totalCharacters: 16384,
15
+ serializedBytes: 65536,
16
+ });
17
+
18
+ const SENSITIVE_KEYS = new Set([
19
+ "password",
20
+ "passwd",
21
+ "pwd",
22
+ "passphrase",
23
+ "secret",
24
+ "secretkey",
25
+ "privatekey",
26
+ "apikey",
27
+ "token",
28
+ "authorization",
29
+ "proxyauthorization",
30
+ "cookie",
31
+ "setcookie",
32
+ "mnemonic",
33
+ "seedphrase",
34
+ "credentials",
35
+ "credential",
36
+ "encryptionkey",
37
+ "signingkey",
38
+ "sessionid",
39
+ "auth",
40
+ ]);
41
+
42
+ function normalizeKey(key) {
43
+ return key.toLowerCase().replace(/[\s_.-]/g, "");
44
+ }
45
+
46
+ function isSensitiveKey(key) {
47
+ const normalized = normalizeKey(key);
48
+ return (
49
+ SENSITIVE_KEYS.has(normalized) ||
50
+ /(?:password|secret|privatekey|apikey|token)$/.test(normalized)
51
+ );
52
+ }
53
+
54
+ function isSensitiveQueryKey(key) {
55
+ return isSensitiveKey(key) || /^(?:key|code|sig|signature)$/i.test(key);
56
+ }
57
+
58
+ function sanitizeUrl(text) {
59
+ try {
60
+ const url = new URL(text);
61
+ if (url.username || url.password) {
62
+ url.username = REDACTED;
63
+ url.password = "";
64
+ }
65
+ const params = new URLSearchParams();
66
+ for (const [key, value] of url.searchParams) {
67
+ params.append(key, isSensitiveQueryKey(key) ? REDACTED : value);
68
+ }
69
+ url.search = params.toString();
70
+ // OAuth implicit responses put credentials in the fragment instead of query.
71
+ if (url.hash.includes("=")) {
72
+ const fragment = new URLSearchParams(url.hash.slice(1));
73
+ const sanitized = new URLSearchParams();
74
+ for (const [key, value] of fragment) {
75
+ sanitized.append(key, isSensitiveQueryKey(key) ? REDACTED : value);
76
+ }
77
+ url.hash = sanitized.toString();
78
+ }
79
+ return url.toString();
80
+ } catch {
81
+ return "[REDACTED URL]";
82
+ }
83
+ }
84
+
85
+ function sanitizeString(value) {
86
+ // Drop oversized strings whole: slicing can expose half of a credential.
87
+ if (value.length > LIMITS.stringLength) return TRUNCATED;
88
+ return value
89
+ .replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s<>"'`]+/gi, sanitizeUrl)
90
+ .replace(/\b(?:Bearer|Basic)\s+[a-z0-9._~+/=-]+/gi, REDACTED)
91
+ .replace(
92
+ /\b((?:proxy[-_ ]?)?authorization|(?:set[-_ ]?)?cookie)\s*:\s*[^\r\n]+/gi,
93
+ "$1: [REDACTED]",
94
+ )
95
+ .replace(
96
+ /\b((?:[a-z0-9_.-]{0,64})?(?:password|secret|private[-_.]?key|api[-_.]?key|token)|passwd|pwd|passphrase|mnemonic|seed[-_.]?phrase|credentials?|encryption[-_.]?key|signing[-_.]?key|session[-_.]?id|auth|(?:proxy[-_.]?)?authorization|(?:set[-_.]?)?cookie)(["']?\s*[=:]\s*)("(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*'|[^\s&,;"']+)/gi,
97
+ (_match, key, separator, value) => {
98
+ const quote = value[0] === '"' || value[0] === "'" ? value[0] : "";
99
+ return `${key}${separator}${quote}${REDACTED}${quote}`;
100
+ },
101
+ );
102
+ }
103
+
104
+ function readDataProperty(value, key) {
105
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
106
+ return descriptor && "value" in descriptor
107
+ ? descriptor.value
108
+ : "[Accessor omitted]";
109
+ }
110
+
111
+ function errorName(value) {
112
+ let prototype = value;
113
+ for (let i = 0; prototype && i < LIMITS.depth; i += 1) {
114
+ if (Object.hasOwn(prototype, "name")) {
115
+ const name = readDataProperty(prototype, "name");
116
+ return typeof name === "string" ? name : "Error";
117
+ }
118
+ prototype = Object.getPrototypeOf(prototype);
119
+ }
120
+ return "Error";
121
+ }
122
+
123
+ function setDataProperty(target, key, value) {
124
+ // Defining __proto__ as data cannot mutate the output object's prototype.
125
+ Object.defineProperty(target, key, {
126
+ value,
127
+ enumerable: true,
128
+ configurable: true,
129
+ writable: true,
130
+ });
131
+ }
132
+
133
+ export function sanitizeAuditDetails(details) {
134
+ const ancestors = new WeakSet();
135
+ let remainingNodes = LIMITS.nodes;
136
+ let remainingCharacters = LIMITS.totalCharacters;
137
+
138
+ function text(value) {
139
+ const sanitized = sanitizeString(value);
140
+ if (sanitized.length > remainingCharacters) return TRUNCATED;
141
+ remainingCharacters -= sanitized.length;
142
+ return sanitized;
143
+ }
144
+
145
+ function visit(value, depth = 0, context = "") {
146
+ if (remainingNodes-- <= 0) return TRUNCATED;
147
+ if (value === null || value === undefined) return value;
148
+ if (typeof value === "string") return text(value);
149
+ if (typeof value === "bigint") return text(value.toString());
150
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
151
+ if (typeof value === "boolean") return value;
152
+ if (typeof value === "function") return "[Function omitted]";
153
+ if (typeof value === "symbol") return "[Symbol omitted]";
154
+ if (depth >= LIMITS.depth) return TRUNCATED;
155
+ if (ancestors.has(value)) return "[Circular Reference]";
156
+ ancestors.add(value);
157
+ try {
158
+ if (value instanceof Date) {
159
+ return text(Date.prototype.toISOString.call(value));
160
+ }
161
+ if (value instanceof URL) {
162
+ return text(URL.prototype.toString.call(value));
163
+ }
164
+ if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
165
+ return "[Binary omitted]";
166
+ }
167
+
168
+ // Native fetch headers and query objects have no useful enumerable fields.
169
+ if (value instanceof Headers || value instanceof URLSearchParams) {
170
+ const result = {};
171
+ const iterator =
172
+ value instanceof Headers
173
+ ? Headers.prototype.entries.call(value)
174
+ : URLSearchParams.prototype.entries.call(value);
175
+ let count = 0;
176
+ for (const [key, item] of iterator) {
177
+ if (count++ >= LIMITS.entries || remainingNodes <= 0) {
178
+ setDataProperty(result, "[Truncated]", true);
179
+ break;
180
+ }
181
+ if (key.length > LIMITS.keyLength) continue;
182
+ setDataProperty(
183
+ result,
184
+ text(key),
185
+ isSensitiveQueryKey(key) ? REDACTED : visit(item, depth + 1),
186
+ );
187
+ }
188
+ return result;
189
+ }
190
+
191
+ if (Array.isArray(value)) {
192
+ const result = [];
193
+ const isHeaderList = normalizeKey(context).endsWith("headers");
194
+ // Header tuple arrays are commonly returned by fetch/HTTP clients.
195
+ const first = readDataProperty(value, "0");
196
+ const isSensitivePair =
197
+ value.length === 2 &&
198
+ typeof first === "string" &&
199
+ isSensitiveKey(first);
200
+ for (let i = 0; i < value.length; i += 1) {
201
+ if (i >= LIMITS.entries || remainingNodes <= 0) {
202
+ result.push(TRUNCATED);
203
+ break;
204
+ }
205
+ const previous =
206
+ i % 2 === 1 && isHeaderList
207
+ ? readDataProperty(value, String(i - 1))
208
+ : null;
209
+ const redact =
210
+ (isSensitivePair && i === 1) ||
211
+ (typeof previous === "string" && isSensitiveKey(previous));
212
+ result.push(
213
+ redact
214
+ ? REDACTED
215
+ : visit(readDataProperty(value, String(i)), depth + 1, context),
216
+ );
217
+ }
218
+ return result;
219
+ }
220
+
221
+ const result = {};
222
+ if (value instanceof Error) {
223
+ setDataProperty(result, "name", text(errorName(value)));
224
+ // message/stack/cause are usually non-enumerable. Do not call getters.
225
+ for (const key of ["name", "message", "stack", "cause"]) {
226
+ if (Object.hasOwn(value, key)) {
227
+ setDataProperty(
228
+ result,
229
+ key,
230
+ visit(readDataProperty(value, key), depth + 1),
231
+ );
232
+ }
233
+ }
234
+ }
235
+ let count = 0;
236
+ const headerName = normalizeKey(context).endsWith("headers")
237
+ ? readDataProperty(value, "name")
238
+ : null;
239
+ for (const key in value) {
240
+ if (
241
+ count++ >= LIMITS.entries ||
242
+ remainingNodes <= 0 ||
243
+ remainingCharacters <= 0
244
+ ) {
245
+ setDataProperty(result, "[Truncated]", true);
246
+ break;
247
+ }
248
+ if (!Object.hasOwn(value, key)) continue;
249
+ if (key.length > LIMITS.keyLength) {
250
+ setDataProperty(result, "[Truncated key]", true);
251
+ continue;
252
+ }
253
+ setDataProperty(
254
+ result,
255
+ text(key),
256
+ isSensitiveKey(key) ||
257
+ (key === "value" &&
258
+ typeof headerName === "string" &&
259
+ isSensitiveKey(headerName))
260
+ ? REDACTED
261
+ : visit(readDataProperty(value, key), depth + 1, key),
262
+ );
263
+ }
264
+ return result;
265
+ } catch {
266
+ // Proxies, invalid native objects, etc. must not break event persistence.
267
+ return "[Unserializable]";
268
+ } finally {
269
+ ancestors.delete(value);
270
+ }
271
+ }
272
+
273
+ const sanitized = visit(details);
274
+ const serialized = JSON.stringify(sanitized);
275
+ return serialized &&
276
+ Buffer.byteLength(serialized, "utf8") > LIMITS.serializedBytes
277
+ ? TRUNCATED
278
+ : sanitized;
279
+ }