chainlesschain 0.166.88 → 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 (178) hide show
  1. package/README.md +101 -14
  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-QzeGep9J.js → AIOps-CZzXRe1-.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-Dfc5bx7S.js → ActionButton-_AZfbW1Q.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-tn-_ZG2E.js → Analytics-CzaUx_wv.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-Deg3WAK8.js → AppLayout-xHEeeD-m.js} +4 -4
  9. package/src/assets/web-panel/assets/{Artifacts-KU05t0a6.js → Artifacts-DmTbtudE.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-CkSZf54T.js → Audit-Cn4ng4fo.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-i7ivfXF0.js → BackgroundAgents-DLUY1VD2.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-QcVUKtf0.js → Backup-B2v--wB6.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-B3h5ZJ0g.js → BaseInput-Bgih7yAv.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-D7AVz0xi.js → Chat-1MiiA3yA.js} +5 -5
  15. package/src/assets/web-panel/assets/{ChatBubbleRenderer-B0-euPW7.js → ChatBubbleRenderer-CPLynmbb.js} +1 -1
  16. package/src/assets/web-panel/assets/{Checkbox-H0p0TMlD.js → Checkbox-DsyBa_NO.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-B6HOmAY3.js → Codegen-B7WCFx9H.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-n_64Fog2.js → Col-D2x4GBGm.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-CX0JGgj0.js → Community-Rnwg9nea.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-C4JWUZlx.js → Compact-BPczlrRh.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-DKyTZakB.js → Compliance-Br1L_wGU.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-C0joBMci.js → Cowork-DlsWB_oS.js} +3 -3
  23. package/src/assets/web-panel/assets/{Cron-D6VkYpxK.js → Cron-BWo_KGpG.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-7b6Uu8jc.js → Crosschain-Cug7N8ge.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-C8YPKqzY.js → DID-CMbyOZJN.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-DJsOiEry.js → Dashboard-C4dl_UaE.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-CyStFY-4.js → Dropdown-CBrC7M-9.js} +1 -1
  28. package/src/assets/web-panel/assets/EmailListRenderer-DTq7tU4b.js +1 -0
  29. package/src/assets/web-panel/assets/{EvolutionSettings-DpxORusH.js → EvolutionSettings-DWP3csxS.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-5BYZdMG0.js → FamilyGuardDashboard-DZl7zuv_.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-s1kS0ekU.js → Federation-W-GSlVK5.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-CmgoH9HV.js → FormItemContext-DMB7xYFV.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-DSRWrvcL.js → GenericCardRenderer-DNv8GEoq.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-Cz3EgnS1.js → Git-BZ0Bbzw6.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-De4atTe7.js → Governance-BNEdcOM5.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-DoitBBxJ.js → Inference-C8249gIz.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-CIdmJ1pH.js → KnowledgeGraph-ngzebU3e.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-Coa_qNhU.js → Logs-CP15th-f.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-CW3B3JDq.js +1 -0
  40. package/src/assets/web-panel/assets/{Marketplace-C6kbAQoM.js → Marketplace-CaH59OsY.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-C2TZn8D-.js → McpTools-C07vnJev.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-DYdEKDuB.js → Memory-CqAOkVAS.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-pKLeVviZ.js → MobileBridge-qp7mZimq.js} +1 -1
  44. package/src/assets/web-panel/assets/MobileProjects-DKhbqvTw.js +1 -0
  45. package/src/assets/web-panel/assets/{Mtc-IEoVRV29.js → Mtc-CRYCnWXv.js} +4 -4
  46. package/src/assets/web-panel/assets/{MtcAudit-Cov-yYTY.js → MtcAudit-Z08IrKws.js} +4 -4
  47. package/src/assets/web-panel/assets/{Multisig--Jr7OCmp.js → Multisig-BWdatUnH.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-D2gXnYAA.js → NLProgramming-BKWiCZ_m.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-hk8pMyve.js → Notes-Bgpzk3sm.js} +3 -3
  50. package/src/assets/web-panel/assets/{NotificationSettings-D26Nc7MU.js → NotificationSettings-DvtZEktR.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-DmzHMEOa.js → OrderTableRenderer-CBcy0szc.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-Cn-o6mHT.js → Organization-DRRL6vW1.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-DP6xpDak.js → Overflow-COmfB8Ns.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-C9WAjG-I.js → P2P-BqMq_DSa.js} +2 -2
  55. package/src/assets/web-panel/assets/PdhVaultBrowser-BX7iNvle.js +7 -0
  56. package/src/assets/web-panel/assets/{Permissions-BFBN4-IL.js → Permissions-DzYkjmV_.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-BEDvoAiz.js → PersonalDataHub-BBTcxBbN.js} +3 -3
  58. package/src/assets/web-panel/assets/{Pipeline-Co2Ze_rR.js → Pipeline-eRkYFSbO.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-BOTn1EId.js → Privacy-Ch7IFKBh.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-CObyrZYm.js → ProjectInit-Ci8EcBYg.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-DVTN31rd.js → ProjectSettings-B4lYHFbI.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-DQSEaVjE.js → Projects-zkE5cCo_.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-BRIKWIdQ.js → Providers-BKDVD3_e.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-3nxaEXxN.js → QrScannerModal-BromrHlf.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-iwZJEE9z.js → QuickAsk-CCgoY1gR.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-D2_EEbvc.js → Recommend-DCPZvpq7.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-S4w2mUfq.js → RemoteSession-DPnFP8HU.js} +3 -3
  68. package/src/assets/web-panel/assets/{Reputation-CstUvfTC.js → Reputation-l6r8ytAs.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-DEGHFpxz.js → Row-ke9Mk1cx.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-DGdZtJQl.js → RssFeed-DQOEzUEn.js} +2 -2
  71. package/src/assets/web-panel/assets/{Search-DhfXi99U.js → Search-BqgA0aUz.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-BbzISrgX.js → Security-FeGD_jqV.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-CFjP2sQ3.js → Services-4kiYYAjZ.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-CFn5wbvm.js → Skeleton-BbU8Xegs.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-fVbT8RcW.js → Skills-Dl2bUMUL.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-D6OtC0MT.js → Sla-xRGALeaZ.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-D0kteBjU.js → SpeechSettings-DZfd4RDm.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-BGEWCtPh.js → SyncSettings-BRtCV9Ga.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-B1xZUG4D.js → Tasks-DVRQklfG.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-COlorjqb.js → Templates-BbhE5jt5.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-yLkwfuo3.js → Tenant-qXPDNKbK.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-CAmkq1wf.js → Terminal-CV48Ks7E.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-CcKYq1Ue.js → TimelineRenderer-BLGfJIDr.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-9hIqn_LE.js → Tokens-7G9H6Hrt.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-Dsdkb74C.js → Trigger--F64N11F.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-DC4GuE3C.js → Trust-Cq1r-D_y.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CSlh00wf.js → UkeySign-Dqyu4RVh.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-QEWLo3QY.js → VideoEditing-DuFmWH_B.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-BkPNQgmg.js → Wallet-C3mg7EWP.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-7---jCBl.js → WebAuthn-CDK-Aj_X.js} +5 -5
  91. package/src/assets/web-panel/assets/{WorkflowEditor-Dq6Yiu8E.js → WorkflowEditor--K7K-T1F.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-CTYIMpWD.js → chat-BQDcS6VP.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-C8JtYlDN.js → colors-BKxJL2-T.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-spXpnsNC.js → compact-item-CFjQAMbY.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-D04ewPuL.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-DZblOKXx.js → hasIn-BhkaOBNv.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DdC0OsNh.js → index-3YutaXnp.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-rVEqWa0_.js → index-3djsY75k.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DSoyEHi2.js → index-3uR0ThJO.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DLUHbPl3.js → index-7BY44UEv.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-B7RA974g.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-BKfR2eSQ.js → index-BNjYPJNU.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-KIoJ2eCK.js → index-BRqlCTrz.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-rU6_An6N.js → index-BfGGsAU7.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-B57W1RB6.js → index-BuWPqSGT.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BLv-KSGc.js → index-C0DzhUOX.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DHOV24VR.js → index-CB4oHynY.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-IFEbaISP.js → index-CE506mgj.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Cgztj1JO.js → index-CJoXgJ2N.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DruABY-g.js → index-COGy6wXn.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CLkua5sJ.js → index-CRtUDiXx.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BSO33_O2.js → index-CSUDlsIr.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CHtwoTKX.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-DtSzmzFF.js → index-CoOu7sdP.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-B-cKXztT.js → index-D-Yh3-1z.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-B_65aY6c.js → index-D2Zb-Kd1.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-749P1pkM.js → index-D9sVcw6v.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Bw-3CtK2.js → index-DEwHywz9.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Dssnz_1x.js → index-DFt48Z-x.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BOuUUW2U.js → index-DJ2OiRvh.js} +3 -3
  124. package/src/assets/web-panel/assets/{index-D_Md0Jhp.js → index-DP1NrM9J.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BjMY3Y_L.js → index-DdcrixaQ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-EM4_wLPi.js → index-De7daEnc.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Cz0gkRz_.js → index-Dhfa_BIl.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CZmBlh5P.js → index-DjmVlpap.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DwIEfKo7.js → index-DmYK9Tqt.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-COvZnoIg.js → index-Dn2QSsuI.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Bm0z1M6s.js → index-Du_SxxBY.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Cfk4o9eI.js → index-DvWWFJHJ.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-Mxj-qi1G.js → index-T9ka3Xj3.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-CE3TYhl0.js → index-aPdVzagA.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DcjouIsi.js → index-bMX8BBLE.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-Dcd-qZ68.js → index-sKfUUldY.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-Dl0wiMxn.js → initDefaultProps-wrqabmui.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-BFmZYrGO.js → motion-BoyB3ilP.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-Cjs64yWq.js → move-Cq31cSFE.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-NeQsnBuP.js → mtc-parser-BkxNt-8H.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-4Rs-rfs9.js → omit-DofGni53.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-CUhx2rDY.js → pickAttrs-BnluLL16.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-DbIM_OB2.js → placementArrow-C-yegJSP.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-B_TGx9AN.js → responsiveObserve-CD3ziNhu.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-C46cEP5t.js → slide-ToZO4gQ1.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-DqeksICX.js → statusUtils-DFVmDmVl.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-2rrypo5u.js → styleChecker-De0Y8g1O.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-RRe1Edsg.js → useFlexGapSupport-wBKIYFKX.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-CB0oxU1U.js → useFs-CP9nmUMJ.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-B98WdlUE.js → usePersonalDataHub-vFGrVDyp.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-CY8AoD95.js → vnode-BcBIjvjH.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-BnT-0MbP.js → zoom-DnXDAQxA.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +5 -5
  155. package/src/commands/agent-controlled-host.js +220 -0
  156. package/src/commands/agent.js +3 -1
  157. package/src/commands/automation.js +35 -8
  158. package/src/commands/hub.js +147 -0
  159. package/src/commands/project.js +162 -0
  160. package/src/data/changelog.json +26 -0
  161. package/src/gateways/ws/personal-data-hub-protocol.js +54 -8
  162. package/src/lib/audit-logger.js +16 -29
  163. package/src/lib/audit-sanitizer.js +279 -0
  164. package/src/lib/automation-center.js +14 -4
  165. package/src/lib/automation-engine.js +151 -39
  166. package/src/lib/eval/verify01-execution.js +9 -0
  167. package/src/lib/eval/verify01-host-capture.js +430 -0
  168. package/src/lib/knowledge-graph.js +27 -0
  169. package/src/lib/personal-data-hub-wiring.js +44 -1
  170. package/src/lib/scheduler-kernel/automation-adapter.js +15 -3
  171. package/src/lib/scheduler-kernel/automation-event-adapter.js +15 -3
  172. package/src/assets/web-panel/assets/EmailListRenderer-COrIx2Rg.js +0 -1
  173. package/src/assets/web-panel/assets/MarkdownRenderer-CeT_RocE.js +0 -1
  174. package/src/assets/web-panel/assets/MobileProjects-D4bWcHA_.js +0 -1
  175. package/src/assets/web-panel/assets/PdhVaultBrowser-DxZb0Ux8.js +0 -7
  176. package/src/assets/web-panel/assets/devWarning-3-s4adfp.js +0 -1
  177. package/src/assets/web-panel/assets/index-ZMf_BGlO.js +0 -1
  178. package/src/assets/web-panel/assets/index-jvp8bJBF.js +0 -1
@@ -0,0 +1,220 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import {
5
+ normalizeAgentSandbox,
6
+ assertSandboxCapabilities,
7
+ assertSandboxAvailable,
8
+ } from "../lib/agent-sandbox.js";
9
+ import { isFormalQualityHermeticRuntime } from "../lib/formal-quality-eval-runtime.js";
10
+ import { resolveCredentialEnvironmentValue } from "../lib/process-execution-broker/credential-transport.js";
11
+
12
+ const MAX_JSON_BYTES = 1024 * 1024;
13
+
14
+ function invalid(message, code = "CC_CONTROLLED_HOST_INVALID") {
15
+ return Object.assign(new Error(message), { code });
16
+ }
17
+
18
+ /** Read one bounded regular file through its opened identity, never create it. */
19
+ function readJson(file) {
20
+ if (typeof file !== "string" || !path.isAbsolute(file))
21
+ throw invalid("Controlled-host JSON paths must be absolute");
22
+ const before = fs.lstatSync(file, { bigint: true });
23
+ if (!before.isFile() || before.isSymbolicLink())
24
+ throw invalid("Controlled-host JSON must be a regular non-symlink file");
25
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
26
+ try {
27
+ const opened = fs.fstatSync(fd, { bigint: true });
28
+ if (
29
+ !opened.isFile() ||
30
+ opened.dev !== before.dev ||
31
+ opened.ino !== before.ino ||
32
+ opened.size < 1n ||
33
+ opened.size > BigInt(MAX_JSON_BYTES)
34
+ )
35
+ throw invalid("Controlled-host JSON identity or size is invalid");
36
+ const bytes = Buffer.alloc(Number(opened.size) + 1);
37
+ let size = 0;
38
+ while (size < bytes.length) {
39
+ const read = fs.readSync(fd, bytes, size, bytes.length - size, null);
40
+ if (!read) break;
41
+ size += read;
42
+ }
43
+ const after = fs.fstatSync(fd, { bigint: true });
44
+ if (
45
+ BigInt(size) !== opened.size ||
46
+ after.size !== opened.size ||
47
+ after.mtimeNs !== opened.mtimeNs ||
48
+ after.ctimeNs !== opened.ctimeNs
49
+ )
50
+ throw invalid("Controlled-host JSON changed while reading");
51
+ const value = JSON.parse(
52
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, size)),
53
+ );
54
+ if (!value || typeof value !== "object" || Array.isArray(value))
55
+ throw invalid("Controlled-host JSON must contain an object");
56
+ return value;
57
+ } finally {
58
+ fs.closeSync(fd);
59
+ }
60
+ }
61
+
62
+ /** Explicit one-shot entry. Provisioning is deliberately not available here. */
63
+ export async function runControlledHost(options, dependencies = {}) {
64
+ const platform = dependencies.platform ?? process.platform;
65
+ if (platform !== "linux")
66
+ throw invalid(
67
+ "Controlled-host permission authority requires Linux",
68
+ "CC_SETTINGS_AUTHORITY_PLATFORM_UNSUPPORTED",
69
+ );
70
+ const env = dependencies.env ?? process.env;
71
+ if (isFormalQualityHermeticRuntime(env))
72
+ throw invalid(
73
+ "Controlled-host authority cannot run in a hermetic evaluator that suppresses permission providers",
74
+ );
75
+ if (!/^[a-zA-Z0-9._:-]{1,128}$/.test(options.context || ""))
76
+ throw invalid("An explicit controlled-host context is required");
77
+ if (
78
+ (options.check === true) ===
79
+ (typeof options.prompt === "string" && options.prompt.trim().length > 0)
80
+ )
81
+ throw invalid("Choose exactly one of --check or a non-empty --prompt");
82
+ if (
83
+ !options.check &&
84
+ ["provider", "model", "baseUrl"].some(
85
+ (key) => typeof options[key] !== "string" || !options[key].trim(),
86
+ )
87
+ )
88
+ throw invalid("A run requires explicit --provider, --model and --base-url");
89
+ const maxTurns =
90
+ options.maxTurns === undefined ? 10 : Number(options.maxTurns);
91
+ if (!Number.isSafeInteger(maxTurns) || maxTurns < 1 || maxTurns > 100)
92
+ throw invalid("--max-turns must be an integer from 1 to 100");
93
+ if (!["text", "json", "stream-json"].includes(options.outputFormat || "text"))
94
+ throw invalid("--output-format must be text, json or stream-json");
95
+ const cwd = dependencies.cwd ?? process.cwd();
96
+ const launch = readJson(options.launch);
97
+ const settings = readJson(options.sandboxSettings);
98
+ if (settings.engine !== "docker-egress" || settings.enabled === false)
99
+ throw invalid(
100
+ "Controlled-host entry requires enabled docker-egress sandbox settings",
101
+ );
102
+ const sandbox = normalizeAgentSandbox(true, { cwd, settings, network: true });
103
+ assertSandboxCapabilities(sandbox, {
104
+ host: { platform, arch: process.arch, release: os.release() },
105
+ });
106
+ const open =
107
+ dependencies.openHost ??
108
+ (await import("../runtime/permission-authority-host.js"))
109
+ .openPermissionAuthorityHost;
110
+ const host = open({ launch, contextId: options.context, env });
111
+ try {
112
+ // Reopening never provisions; runtimeOptions checks the actual invocation
113
+ // workspace and rejects writable roots overlapping the fixed authority.
114
+ host.runtimeOptions({ cwd, sandbox });
115
+ if (options.check) {
116
+ const capabilities = host.capabilities(sandbox);
117
+ const report = {
118
+ schema: "chainlesschain.controlled-host-check/v1",
119
+ authorityIdentityVerified: true,
120
+ backendExecutionVerified: false,
121
+ backendAvailabilityProbed: false,
122
+ capabilities,
123
+ };
124
+ (dependencies.writeOut ?? ((text) => process.stdout.write(text)))(
125
+ JSON.stringify(report) + "\n",
126
+ );
127
+ return { exitCode: 0, report };
128
+ }
129
+ // Availability is checked before resolving credentials or calling a model.
130
+ (dependencies.assertAvailable ?? assertSandboxAvailable)(sandbox);
131
+ let apiKey;
132
+ if (options.apiKeyEnv) {
133
+ apiKey = await resolveCredentialEnvironmentValue(options.apiKeyEnv, {
134
+ env,
135
+ });
136
+ if (!apiKey)
137
+ throw invalid("The selected API key environment source is unavailable");
138
+ }
139
+ return await host.runHeadless(
140
+ {
141
+ cwd,
142
+ sandbox,
143
+ prompt: options.prompt,
144
+ provider: options.provider,
145
+ model: options.model,
146
+ baseUrl: options.baseUrl,
147
+ apiKey,
148
+ maxTurns,
149
+ outputFormat: options.outputFormat || "text",
150
+ permissionMode: "dontAsk",
151
+ useRegisteredMcp: false,
152
+ strictMcpConfig: true,
153
+ ide: false,
154
+ pdh: false,
155
+ jetbrains: false,
156
+ slashMacros: false,
157
+ expandFileRefs: false,
158
+ },
159
+ dependencies.runtimeDependencies,
160
+ );
161
+ } finally {
162
+ host.close();
163
+ }
164
+ }
165
+
166
+ export function registerControlledHostCommand(agent, dependencies = {}) {
167
+ agent
168
+ .command("controlled-host")
169
+ .description(
170
+ "Run against a pre-provisioned Linux permission authority (one-shot, dontAsk)",
171
+ )
172
+ .requiredOption(
173
+ "--launch <file>",
174
+ "Absolute pre-provisioned launch descriptor JSON",
175
+ )
176
+ .requiredOption("--context <id>", "Exact provisioned workspace context ID")
177
+ .requiredOption(
178
+ "--sandbox-settings <file>",
179
+ "Absolute docker-egress sandbox settings JSON",
180
+ )
181
+ .option(
182
+ "--check",
183
+ "Verify configuration and authority identity only; no backend or model execution",
184
+ )
185
+ .option(
186
+ "--prompt <text>",
187
+ "Run one headless task in the bound current workspace",
188
+ )
189
+ .option("--provider <name>", "Explicit model provider")
190
+ .option("--model <name>", "Explicit model name")
191
+ .option("--base-url <url>", "Explicit provider endpoint")
192
+ .option(
193
+ "--api-key-env <name>",
194
+ "Credential environment variable or broker reference",
195
+ )
196
+ .option("--max-turns <n>", "Maximum task turns, 1-100", "10")
197
+ .option("--output-format <format>", "text | json | stream-json", "text")
198
+ .action(async (options, command) => {
199
+ try {
200
+ // Parent agent flags are a different launch contract. Never silently
201
+ // ignore a supplied --yolo, --bg, --settings or model override there.
202
+ if (
203
+ agent.options.some(
204
+ (option) =>
205
+ agent.getOptionValueSource(option.attributeName()) === "cli",
206
+ )
207
+ )
208
+ throw invalid(
209
+ "Place only controlled-host options after the subcommand; parent agent flags are unsupported",
210
+ );
211
+ const outcome = await runControlledHost(command.opts(), dependencies);
212
+ process.exitCode = outcome.exitCode;
213
+ } catch (error) {
214
+ process.stderr.write(
215
+ `${error.code || "CC_CONTROLLED_HOST_FAILED"}: ${error.message}\n`,
216
+ );
217
+ process.exitCode = 1;
218
+ }
219
+ });
220
+ }
@@ -46,6 +46,7 @@ import {
46
46
  isFormalQualityHermeticRuntime,
47
47
  } from "../lib/formal-quality-eval-runtime.js";
48
48
  import { withQuietStdout } from "../runtime/quiet-stdout.js";
49
+ import { registerControlledHostCommand } from "./agent-controlled-host.js";
49
50
  import { resolveCredentialEnvironmentValue } from "../lib/process-execution-broker/credential-transport.js";
50
51
  import {
51
52
  captureClaudeStorageLaunchEnvironment,
@@ -290,7 +291,7 @@ export function registerAgentCommand(program, dependencies = {}) {
290
291
  : captureSkillRetrievalRevocationReader(
291
292
  dependencies.skillRetrievalRevocationReader,
292
293
  );
293
- program
294
+ const agent = program
294
295
  .command("agent")
295
296
  .aliases(["a", "exec"])
296
297
  .description(
@@ -2259,6 +2260,7 @@ export function registerAgentCommand(program, dependencies = {}) {
2259
2260
  // Interactive session ended (REPL closed) — settle the worktree.
2260
2261
  await _finishWorktree();
2261
2262
  });
2263
+ registerControlledHostCommand(agent, dependencies.controlledHost);
2262
2264
  }
2263
2265
 
2264
2266
  export function registerSubAgentV2Command(program) {
@@ -107,6 +107,20 @@ function _dbFromCtx(cmd) {
107
107
  return null;
108
108
  }
109
109
 
110
+ function reportExecution(execution) {
111
+ const message = `Execution ${execution.id} → ${execution.status}`;
112
+ if (execution.status === EXECUTION_STATUS.SUCCESS) {
113
+ logger.success(message);
114
+ } else if (execution.status === EXECUTION_STATUS.SIMULATED) {
115
+ logger.log(`${message} (simulation only; no external actions applied)`);
116
+ } else {
117
+ logger.error(
118
+ `${message}: ${execution.error || "execution did not succeed"}`,
119
+ );
120
+ process.exitCode = 1;
121
+ }
122
+ }
123
+
110
124
  async function readBoundedJsonStdin(options, maximum = 80 * 1024) {
111
125
  if (options?.jsonStdin !== true) {
112
126
  throw new Error("--json-stdin is required for routine definitions");
@@ -665,8 +679,18 @@ function _wire(root) {
665
679
  action,
666
680
  expectedRevision: opts.expectedRevision,
667
681
  });
668
- if (opts.json) console.log(JSON.stringify(result, null, 2));
669
- else logger.success(`${flowId} ${action} completed`);
682
+ if (opts.json) {
683
+ console.log(JSON.stringify(result, null, 2));
684
+ if (
685
+ [EXECUTION_STATUS.FAILED, EXECUTION_STATUS.UNSUPPORTED].includes(
686
+ result.result?.status,
687
+ )
688
+ ) {
689
+ process.exitCode = 1;
690
+ }
691
+ } else if (result.result?.id && result.result?.flowId) {
692
+ reportExecution(result.result);
693
+ } else logger.success(`${flowId} ${action} completed`);
670
694
  } catch (e) {
671
695
  logger.error(e.message);
672
696
  process.exitCode = 1;
@@ -1033,14 +1057,17 @@ function _wire(root) {
1033
1057
 
1034
1058
  root
1035
1059
  .command("fire-trigger <triggerId>")
1036
- .description("Simulate trigger firing (executes flow)")
1060
+ .description("Fire a trigger; use --test for simulation")
1037
1061
  .option("-i, --input <json>", "Input data as JSON")
1062
+ .option("--test", "Simulate only; no external actions are applied")
1038
1063
  .action(async (triggerId, opts, cmd) => {
1039
1064
  const db = _dbFromCtx(cmd);
1040
1065
  try {
1041
1066
  const input = parseJsonOption(opts.input, "--input") || {};
1042
- const exec = fireTrigger(db, triggerId, input);
1043
- logger.success(`Fired trigger → exec ${chalk.cyan(exec.id)}`);
1067
+ const exec = fireTrigger(db, triggerId, input, {
1068
+ testMode: Boolean(opts.test),
1069
+ });
1070
+ reportExecution(exec);
1044
1071
  logger.log(` status: ${exec.status}`);
1045
1072
  logger.log(` duration: ${exec.durationMs}ms`);
1046
1073
  logger.log(` steps: ${exec.stepsLog.length}`);
@@ -1095,9 +1122,9 @@ function _wire(root) {
1095
1122
 
1096
1123
  root
1097
1124
  .command("execute <flowId>")
1098
- .description("Manually execute a flow (simulated)")
1125
+ .description("Execute a flow; use --test for simulation")
1099
1126
  .option("-i, --input <json>", "Input data as JSON")
1100
- .option("--test", "Test mode (marks execution as test)")
1127
+ .option("--test", "Simulate only; no external actions are applied")
1101
1128
  .action(async (flowId, opts, cmd) => {
1102
1129
  const db = _dbFromCtx(cmd);
1103
1130
  try {
@@ -1106,7 +1133,7 @@ function _wire(root) {
1106
1133
  inputData: input,
1107
1134
  testMode: Boolean(opts.test),
1108
1135
  });
1109
- logger.success(`Execution ${chalk.cyan(exec.id)} → ${exec.status}`);
1136
+ reportExecution(exec);
1110
1137
  logger.log(` duration: ${exec.durationMs}ms`);
1111
1138
  logger.log(` steps: ${exec.stepsLog.length}`);
1112
1139
  if (exec.error) logger.log(chalk.red(` error: ${exec.error}`));
@@ -3446,6 +3446,85 @@ function _defaultKnownVendors() {
3446
3446
 
3447
3447
  // ─── Commander wire-up ───────────────────────────────────────────────
3448
3448
 
3449
+ async function cmdDerivation(operation, reference, options = {}) {
3450
+ try {
3451
+ if (
3452
+ reference &&
3453
+ (!["event", "person", "place", "item", "topic"].includes(
3454
+ reference.entityType,
3455
+ ) ||
3456
+ typeof reference.entityId !== "string" ||
3457
+ !reference.entityId.trim() ||
3458
+ reference.entityId.length > 1024)
3459
+ ) {
3460
+ throw new Error("A valid entity type and ID are required");
3461
+ }
3462
+ if (operation === "delete" && options.confirm !== true) {
3463
+ throw new Error(
3464
+ "Normalized entity deletion requires --confirm; raw archive is retained",
3465
+ );
3466
+ }
3467
+ if (operation === "prune" && options.confirm !== true) {
3468
+ throw new Error("Retired derivation receipt pruning requires --confirm");
3469
+ }
3470
+ if (
3471
+ operation === "prune" &&
3472
+ (!Array.isArray(options.consumer) ||
3473
+ options.consumer.length < 1 ||
3474
+ options.consumer.length > 100 ||
3475
+ options.consumer.some(
3476
+ (id) => typeof id !== "string" || !id.trim() || id.length > 1024,
3477
+ ))
3478
+ ) {
3479
+ throw new Error("Select between 1 and 100 retired consumer IDs");
3480
+ }
3481
+ const limit = options.limit === undefined ? 100 : Number(options.limit);
3482
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1000) {
3483
+ throw new Error("Derivation limit must be between 1 and 1000");
3484
+ }
3485
+ const hub = await (options._getHub || getHub)();
3486
+ const filters = { adapter: options.adapter, scope: options.scope };
3487
+ let result;
3488
+ if (operation === "consumers") {
3489
+ result = hub.vault.getDerivationStore().listConsumers({
3490
+ state: options.state,
3491
+ kind: options.kind,
3492
+ afterConsumerId: options.after,
3493
+ limit,
3494
+ });
3495
+ } else if (operation === "prune") {
3496
+ result = hub.vault.getDerivationStore().pruneRetiredConsumers({
3497
+ consumerIds: options.consumer,
3498
+ activeConsumerId: hub.registry.consumerId,
3499
+ limit,
3500
+ });
3501
+ } else if (operation === "retry") {
3502
+ result = await hub.registry.retryDerivations({ ...filters, limit });
3503
+ } else if (operation === "state") {
3504
+ result = hub.vault
3505
+ .getDerivationStore()
3506
+ .getState(reference.entityType, reference.entityId, {
3507
+ consumerId: hub.registry.consumerId,
3508
+ });
3509
+ } else if (operation === "delete") {
3510
+ const deleted = hub.vault.deleteEntity(
3511
+ reference.entityType,
3512
+ reference.entityId,
3513
+ );
3514
+ result = {
3515
+ deleted,
3516
+ derivations: await hub.registry.retryDerivations({ limit: 1000 }),
3517
+ };
3518
+ } else {
3519
+ result = hub.registry.getDerivationStatus(filters);
3520
+ }
3521
+ printJson(result);
3522
+ return result;
3523
+ } catch (error) {
3524
+ fail(null, error, options.json);
3525
+ }
3526
+ }
3527
+
3449
3528
  export function registerHubCommand(program, dependencies = {}) {
3450
3529
  const factory = readEvolutionCompositionFactory(dependencies);
3451
3530
  const invocationOptions = (options) => {
@@ -3571,6 +3650,73 @@ export function registerHubCommand(program, dependencies = {}) {
3571
3650
  .option("--json", "Output JSON")
3572
3651
  .action(cmdListAdapters);
3573
3652
 
3653
+ for (const [name, operation, description] of [
3654
+ [
3655
+ "derivation-status",
3656
+ "status",
3657
+ "Inspect KG/RAG delivery status for this vault",
3658
+ ],
3659
+ [
3660
+ "retry-derivations",
3661
+ "retry",
3662
+ "Retry a bounded page of KG/RAG work from current vault state",
3663
+ ],
3664
+ ]) {
3665
+ const command = hub
3666
+ .command(name)
3667
+ .description(description)
3668
+ .option("--adapter <name>", "Filter by source adapter")
3669
+ .option("--scope <scope>", "Filter by source scope")
3670
+ .option("--json", "Output JSON");
3671
+ if (operation === "retry")
3672
+ command.option("--limit <n>", "Maximum deliveries (1-1000)", "100");
3673
+ command.action((options) => cmdDerivation(operation, null, options));
3674
+ }
3675
+
3676
+ hub
3677
+ .command("derivation-consumers")
3678
+ .description(
3679
+ "List registered derivation consumers without retirement tokens",
3680
+ )
3681
+ .option("--state <state>", "Filter by active or retired")
3682
+ .option("--kind <kind>", "Filter by ephemeral or persistent")
3683
+ .option("--after <id>", "Continue after a consumer ID")
3684
+ .option("--limit <n>", "Maximum consumers (1-1000)", "100")
3685
+ .option("--json", "Output JSON")
3686
+ .action((options) => cmdDerivation("consumers", null, options));
3687
+
3688
+ hub
3689
+ .command("prune-derivations")
3690
+ .description(
3691
+ "Prune non-running receipts of explicitly selected retired ephemeral consumers",
3692
+ )
3693
+ .requiredOption("--consumer <ids...>", "Retired consumer IDs (1-100)")
3694
+ .option("--limit <n>", "Maximum receipts removed (1-1000)", "100")
3695
+ .option("--confirm", "Confirm removal of these retired projection receipts")
3696
+ .option("--json", "Output JSON")
3697
+ .action((options) => cmdDerivation("prune", null, options));
3698
+
3699
+ hub
3700
+ .command("derivation-state <entityType> <entityId>")
3701
+ .description(
3702
+ "Inspect an entity's source revision and KG/RAG receipts without its payload",
3703
+ )
3704
+ .option("--json", "Output JSON")
3705
+ .action((entityType, entityId, options) =>
3706
+ cmdDerivation("state", { entityType, entityId }, options),
3707
+ );
3708
+
3709
+ hub
3710
+ .command("delete-entity <entityType> <entityId>")
3711
+ .description(
3712
+ "Delete a normalized entity and queue index removal; raw archive remains and reimport may restore it",
3713
+ )
3714
+ .option("--confirm", "Confirm deletion of this normalized entity")
3715
+ .option("--json", "Output JSON")
3716
+ .action((entityType, entityId, options) =>
3717
+ cmdDerivation("delete", { entityType, entityId }, options),
3718
+ );
3719
+
3574
3720
  hub
3575
3721
  .command("readiness")
3576
3722
  .description(
@@ -4318,6 +4464,7 @@ export function registerHubCommand(program, dependencies = {}) {
4318
4464
  // `_wizard` / `_factoryDeps` / `_knownVendors` injected, bypassing the
4319
4465
  // real `getHub()` call. The commander wiring above is the runtime path.
4320
4466
  export const _internal = {
4467
+ cmdDerivation,
4321
4468
  cmdAsk,
4322
4469
  cmdRepl,
4323
4470
  cmdRetrieveContext,
@@ -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
  };