chainlesschain 0.162.164 → 0.162.166

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 (167) hide show
  1. package/package.json +3 -1
  2. package/src/assets/web-panel/assets/{AIOps-BqRkaHGb.js → AIOps-LbDx7jL6.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DWO7OZLC.js → ActionButton-DnurtK34.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-C5oaVezM.js → Analytics-DXSsV1am.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-CsUESDoZ.js → AppLayout-dqRpzNuD.js} +3 -3
  6. package/src/assets/web-panel/assets/{Artifacts-Dragqu5k.js → Artifacts-BwdmYepK.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CQsaHctY.js → Audit-oSX0kOUG.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DdG1jNSM.js → BackgroundAgents-C2KxJahT.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DN6sjK4z.js → Backup-B4TkxWkC.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-Cxm_ZTCl.js → BaseInput-CX5-KxeU.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DVXGmA56.js → Chat-O_HzD-PL.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BdaCL_iE.js → ChatBubbleRenderer-Br8saHQA.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-DEc4vD_A.js → Checkbox-ChepDpC1.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-CKiTcv36.js → Codegen-D9z8kc7q.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-Br9UUNN0.js → Col-B-H5eoNw.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-NeUH_70T.js → Community-Ccyp_Kk5.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BqR-z2Df.js → Compact-BNvBznp_.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-DcWB_SgZ.js → Compliance-D1FjRd8n.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-BfhgcudF.js → Cowork-CV9B9u-8.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-LaU29LgD.js → Cron-BowJ4VIx.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-B2ZB9uwW.js → Crosschain-BJc6jzRF.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BuHl38A2.js → DID-BEp8NV6D.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-m627yqvZ.js → Dashboard-Dls0B3YB.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-C7bxF8F5.js → Dropdown-DQ7zXVtD.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DPLPQzLp.js → EmailListRenderer-B9q79tOl.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-cOiH6ike.js → FamilyGuardDashboard-CD8Ie-88.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-Bvkzg88z.js → Federation-5o7XU5Vf.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-BTsiA7h_.js → FormItemContext-DeKbMvOs.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BGnVE5qu.js → GenericCardRenderer-CZiQvz_z.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DS_j6r25.js → Git-CTvFxVV8.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CKRG3Rsl.js → Governance-DwzFIjHt.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-DW8INoNQ.js → Inference-CJBk59c-.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-BhEYeFhC.js → KnowledgeGraph-DfsaXN2z.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-wqi0uxuR.js → Logs-DVRYf18S.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-BL_WsWZM.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-CMlTuPLT.js → Marketplace-BuIwyE4t.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-CSx4m5JJ.js → McpTools-D3U0o78E.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-vnQCUy-_.js → Memory-QYVmyb3b.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BV2NA742.js → MobileBridge-BFgrj6eZ.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-Bp5QB-46.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-CzK_FPB1.js → Mtc-BZQu8TcH.js} +4 -4
  42. package/src/assets/web-panel/assets/{MtcAudit-Cb7G9oL3.js → MtcAudit-BnFfEkLo.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-qJxlUyzr.js → Multisig-iUnNS9Cm.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-DqaBJ7Te.js → NLProgramming-DNNz1y9Y.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CwhTJkRi.js → Notes-CZezNYRU.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-YDs4Fal4.js → NotificationSettings-DBvrplIR.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-CvEALpq7.js → OrderTableRenderer-CTDtee49.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-CEmQm9n8.js → Organization-CCOUJX1R.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-zby_P_LA.js → Overflow-DcZghdk0.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-DsmDNFNT.js → P2P-jmddWX9q.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-FWwFlZGF.js → PdhVaultBrowser-wfjMcczl.js} +4 -4
  52. package/src/assets/web-panel/assets/{Permissions-DuzQ6me5.js → Permissions-DU1GJjca.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-DZbeN613.js → PersonalDataHub-BzTMzDF9.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-CoNSVAUS.js → Pipeline-DPmjlpVc.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-OYJEAaMC.js → Privacy-B6HRLpfK.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-CWIapV--.js → ProjectInit-Cyy86AsN.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-CX8xMxZn.js → ProjectSettings-Bl_WQxJ5.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-BLCN8YyY.js → Projects-Co9yKThA.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-BV_sxp75.js → Providers-DLa4NlIs.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-BTnRrHRp.js → QrScannerModal-BvzM7q5f.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-D1KZg018.js → QuickAsk-BN8yyyUj.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-COm5cuNg.js → Recommend-QsVN4w0i.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-0saWEkYI.js → RemoteSession-tkDXw-TY.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-Dr7iKLS2.js → Reputation-DAifkGsy.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-C1vpPBcr.js → Row-D7dZEyak.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-CxlWrtNj.js → RssFeed-DB5sImTA.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-BT2V8dPN.js → Search-3py3TO9V.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-bm9V3c5k.js → Security-DhNUfT_k.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-CocH1yyX.js → Services-DjJAA_5D.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-DHItlMdd.js → Skeleton-u95JjK3n.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-C59sLc4w.js → Skills-DPo3L0Tz.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-Crj4BMc8.js → Sla-DWhkAZV1.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-DX4KFceY.js → SpeechSettings-PUegMtqV.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-BD_iwQLr.js → SyncSettings-BMNYrnrU.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-BmCTzYsH.js → Tasks-CYm_oTJC.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-BGUaxZYl.js → Templates-Dw9Er5DN.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-BS0tJx2O.js → Tenant-7P3QIBSo.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal--tHSAsld.js → Terminal-ClPrfWW7.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-D9-MFxS4.js → TimelineRenderer-DbCd1bDH.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CiCB485f.js → Tokens-EQofpA0t.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-SmCFuW_l.js → Trigger-ChNLLypd.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DkK3Rqe0.js → Trust-dlVGUKs5.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-CDM4LGdB.js → UkeySign-BXyBUhNF.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-IDB7DJXt.js → VideoEditing-BE8-68ge.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-Bikj5cmt.js → Wallet-7ZIJOwNg.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-C9abWlAw.js → WebAuthn-DgNd2Amr.js} +3 -3
  87. package/src/assets/web-panel/assets/{WorkflowEditor-DrbRKVky.js → WorkflowEditor-Z-Un-9l5.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CccCSDQf.js → chat-CtVDEkUc.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-CXObXLhb.js → colors-PXtJ2f1B.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-B5kfYAxC.js → compact-item-oN96nuBi.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-DmiPBfip.js → createContext-okCJXQXa.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-BzV6dk1B.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-Q6lYteAc.js → hasIn-pcHm--4o.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-C2FXdboq.js → index-5aO2V0V4.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BJJ5DhKI.js → index-BDR2mi1p.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-bzfw951k.js → index-BEtzqJw3.js} +1 -1
  97. package/src/assets/web-panel/assets/index-BFrAwXHp.js +1 -0
  98. package/src/assets/web-panel/assets/{index-fSUMbMvv.js → index-BHJIfdCV.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BLOzViJ6.js → index-BUTyx-Lk.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Dw4eHfsG.js → index-BUUJ2WfG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DAYCTc07.js → index-BZKk4Lnm.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BRrbUMoG.js → index-BeAWCGEK.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Caghi-yL.js → index-Bht9n8Sm.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CsO3GUAY.js → index-BjVIZfdA.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C5XOlTHN.js → index-Bl6N7ZyX.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BUFpT17Q.js → index-BnhHxZul.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DgxVJ8pg.js → index-BuME9k4Y.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-tFHy5Wol.js → index-C0rfJBU3.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CMq7UeoT.js → index-C4ravjdv.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CLhBDS2w.js → index-CAQktO-6.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DlJvIqoR.js → index-CK0H3pQW.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-4oXBDczR.js → index-CQ1ZzJjN.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BsfvW6my.js → index-CQYes2mk.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Rh-ytwow.js → index-CWUJlCvK.js} +3 -3
  115. package/src/assets/web-panel/assets/{index-CwZBa20Z.js → index-CXhezK4d.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-D8eBw7vW.js → index-Cc_MkksW.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CVMTWNKl.js → index-CopGaCI_.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DgTLApS9.js → index-Cs1fGk1Z.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BJCCa0s_.js → index-D0bfypsF.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CoK8-kWQ.js → index-D1efSh1X.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-33U29UNV.js → index-DBcKBb1E.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-StAEt2pq.js → index-DC0bqrqV.js} +1 -1
  123. package/src/assets/web-panel/assets/index-DJQFP47u.js +1 -0
  124. package/src/assets/web-panel/assets/{index-BGYXvMc5.js → index-DfL10EcN.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BkRDiqGc.js → index-DtsyPnF5.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CdB6FuSH.js → index-Rk0X8lVz.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DF6Q8l2h.js → index-T3QE8LuV.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-D-kASucw.js → index-_SaFHNsG.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-By-kfPED.js → index-jAY55MSx.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-25Wlqps4.js → index-mB1UV7wA.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BzFTcQsc.js → index-nMqLdwao.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D9wgifk4.js → index-vW0lwj0Y.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-BNq7X72N.js → initDefaultProps-CG1jyiI7.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-DOf9viTc.js → motion-C8BYa98D.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-CRHTcW4x.js → move-D1hqwitW.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-Cyoozgm2.js → mtc-parser-C2lol8rp.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-6-TjFNru.js → omit-Ct6Zghfi.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-DlUmO1zi.js → pickAttrs-CSYbYfdc.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-CFOL8mIR.js → placementArrow-WCk8di0z.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-BtaFK5zd.js → responsiveObserve-CEC8CZ-3.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BIbc2bur.js → slide-BsXyd7Lq.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BXX9d6-w.js → statusUtils-tDKldiUT.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BGv10Bpn.js → styleChecker-BIS2unhx.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-bKnKT8tw.js → useFlexGapSupport-yFTVMDUv.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-FY4IL4jn.js → useFs-CZYbJwkO.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-Bwl9TEgu.js → usePersonalDataHub-sbEHRLAM.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-BM3OHxAT.js → vnode-BVjeSEQK.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-D1fbFiHB.js → zoom-BZKVvEZ6.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agent.js +14 -0
  151. package/src/data/changelog.json +12 -2
  152. package/src/lib/background-session-transport.js +24 -2
  153. package/src/lib/governance-coverage.js +179 -0
  154. package/src/lib/packer/smoke-runner.js +85 -15
  155. package/src/lib/project-instructions.js +9 -1
  156. package/src/lib/protocol-replay.js +153 -0
  157. package/src/repl/agent-repl.js +17 -2
  158. package/src/runtime/agent-core.js +64 -11
  159. package/src/runtime/headless-runner.js +9 -1
  160. package/src/runtime/headless-stream.js +10 -1
  161. package/src/runtime/policies/agent-policy.js +5 -0
  162. package/src/runtime/system-prompt.js +21 -8
  163. package/src/assets/web-panel/assets/MarkdownRenderer-DS8dE8ty.js +0 -1
  164. package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +0 -1
  165. package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +0 -1
  166. package/src/assets/web-panel/assets/index-DTdFUWc_.js +0 -1
  167. package/src/assets/web-panel/assets/index-zQ0M_kz-.js +0 -1
@@ -85,6 +85,15 @@ export async function smokeTestExe(ctx) {
85
85
  stdio: ["ignore", "pipe", "pipe"],
86
86
  windowsHide: true,
87
87
  shell: needsShell,
88
+ // POSIX: own process group so teardown can signal the WHOLE tree via the
89
+ // negative pid. Critical when shell:true wraps the launch in `/bin/sh -c`
90
+ // (the `.sh` test shim, and any real exe that re-execs): a plain
91
+ // child.kill() then hits only the wrapper shell and orphans the real
92
+ // `node` server grandchild (ppid→1), which kept serving AND — via the
93
+ // stdio pipe fds it inherited from the worker — pinned the vitest forks
94
+ // pool past its terminate deadline ("Worker exited unexpectedly" flake).
95
+ // No-op on Windows, where killChild uses taskkill /T to walk the tree.
96
+ detached: process.platform !== "win32",
88
97
  });
89
98
 
90
99
  let stdoutBuf = "";
@@ -104,9 +113,26 @@ export async function smokeTestExe(ctx) {
104
113
  // is enough in practice, but /T protects us if that ever changes.
105
114
  spawn("taskkill", ["/F", "/T", "/PID", String(child.pid)], {
106
115
  stdio: "ignore",
107
- });
116
+ }).unref();
117
+ } else if (child.pid) {
118
+ // Negative pid → the whole process group from the detached spawn above,
119
+ // so a shell:true wrapper's exec'd/forked `node` grandchild dies too
120
+ // (a plain child.kill would signal only the wrapper and orphan the
121
+ // server). SIGKILL, not SIGTERM: a smoke teardown has no reason to wait
122
+ // for a graceful close and not every packed/fake server installs a
123
+ // SIGTERM handler. Fall back to a direct kill if the group is already
124
+ // gone (ESRCH).
125
+ try {
126
+ process.kill(-child.pid, "SIGKILL");
127
+ } catch {
128
+ try {
129
+ child.kill("SIGKILL");
130
+ } catch {
131
+ /* already gone */
132
+ }
133
+ }
108
134
  } else {
109
- child.kill("SIGTERM");
135
+ child.kill("SIGKILL");
110
136
  }
111
137
  } catch {
112
138
  /* best effort */
@@ -115,9 +141,10 @@ export async function smokeTestExe(ctx) {
115
141
 
116
142
  // Early-death watchdog: if the child exits before we see a listening
117
143
  // port, report the stdout/stderr so the user sees the real failure
118
- // instead of a generic timeout.
144
+ // instead of a generic timeout. Named so cleanup() can detach it.
145
+ let onExit;
119
146
  const deathPromise = new Promise((_, reject) => {
120
- child.on("exit", (code, signal) => {
147
+ onExit = (code, signal) => {
121
148
  reject(
122
149
  new PackError(
123
150
  `Smoke-test process exited (code=${code}, signal=${signal}) before ports came up.\n` +
@@ -126,16 +153,52 @@ export async function smokeTestExe(ctx) {
126
153
  EXIT.SMOKE,
127
154
  ),
128
155
  );
129
- });
156
+ };
157
+ child.on("exit", onExit);
130
158
  });
131
159
 
160
+ // Full teardown: killing the child is not enough — the parent still holds
161
+ // the child's stdout/stderr PIPE handles and the ChildProcess object, all of
162
+ // which keep the event loop alive (killed-but-unreaped on Windows, or
163
+ // still-open pipes on POSIX). Left dangling across the ~8 smokeTestExe calls
164
+ // this test makes, they pinned the vitest forks-pool worker so it could not
165
+ // terminate → the "Worker exited unexpectedly" / "Timeout terminating forks
166
+ // worker" flake blamed on whatever file ran next on the reused worker. Detach
167
+ // the exit watcher, destroy the pipes, kill, and unref so nothing survives.
168
+ let cleaned = false;
169
+ const cleanup = () => {
170
+ if (cleaned) return;
171
+ cleaned = true;
172
+ try {
173
+ child.removeListener("exit", onExit);
174
+ } catch {
175
+ /* noop */
176
+ }
177
+ killChild();
178
+ try {
179
+ child.stdout?.destroy();
180
+ } catch {
181
+ /* noop */
182
+ }
183
+ try {
184
+ child.stderr?.destroy();
185
+ } catch {
186
+ /* noop */
187
+ }
188
+ try {
189
+ child.unref();
190
+ } catch {
191
+ /* noop */
192
+ }
193
+ };
194
+
132
195
  try {
133
196
  await Promise.race([
134
197
  waitForPorts({ uiPort, wsPort, timeoutMs: bootTimeoutMs }),
135
198
  deathPromise,
136
199
  ]);
137
200
  } catch (e) {
138
- killChild();
201
+ cleanup();
139
202
  throw e;
140
203
  }
141
204
 
@@ -148,7 +211,7 @@ export async function smokeTestExe(ctx) {
148
211
  timeoutMs: probeTimeoutMs,
149
212
  });
150
213
  } catch (e) {
151
- killChild();
214
+ cleanup();
152
215
  throw new PackError(
153
216
  `Smoke-test HTTP probe failed: ${e.message}`,
154
217
  EXIT.SMOKE,
@@ -156,7 +219,7 @@ export async function smokeTestExe(ctx) {
156
219
  }
157
220
 
158
221
  if (uiStatus < 200 || uiStatus >= 300) {
159
- killChild();
222
+ cleanup();
160
223
  throw new PackError(
161
224
  `Smoke-test HTTP probe: http://127.0.0.1:${uiPort}/ returned ${uiStatus} (expected 2xx)`,
162
225
  EXIT.SMOKE,
@@ -191,7 +254,7 @@ export async function smokeTestExe(ctx) {
191
254
  ` skills check: /api/skills returned 404 — skipping (older pack predating Phase 2b)`,
192
255
  );
193
256
  skillsCheck = { ok: true, skipped: "endpoint-404" };
194
- killChild();
257
+ cleanup();
195
258
  return {
196
259
  ok: true,
197
260
  uiStatus,
@@ -200,7 +263,7 @@ export async function smokeTestExe(ctx) {
200
263
  skillsCheck,
201
264
  };
202
265
  }
203
- killChild();
266
+ cleanup();
204
267
  throw new PackError(
205
268
  `Smoke-test /api/skills probe failed: ${e.message}`,
206
269
  EXIT.SMOKE,
@@ -216,7 +279,7 @@ export async function smokeTestExe(ctx) {
216
279
  );
217
280
  const missing = bundledSkillNames.filter((n) => !registeredNames.has(n));
218
281
  if (missing.length > 0) {
219
- killChild();
282
+ cleanup();
220
283
  throw new PackError(
221
284
  `Smoke-test skills check: bundled skill(s) not registered: ${missing.join(", ")}`,
222
285
  EXIT.SMOKE,
@@ -228,7 +291,7 @@ export async function smokeTestExe(ctx) {
228
291
  skillsCheck = { ok: true, checked: bundledSkillNames.length };
229
292
  }
230
293
 
231
- killChild();
294
+ cleanup();
232
295
 
233
296
  return {
234
297
  ok: true,
@@ -280,12 +343,17 @@ function tryConnect(host, port, timeoutMs) {
280
343
  function probeHttp({ host, port, path: urlPath, timeoutMs }) {
281
344
  return new Promise((resolve, reject) => {
282
345
  const req = http.get(
283
- { host, port, path: urlPath, timeout: timeoutMs },
346
+ // agent:false a one-off connection that closes after the response
347
+ // instead of lingering in the global keep-alive pool (leak-free probe).
348
+ { host, port, path: urlPath, timeout: timeoutMs, agent: false },
284
349
  (res) => {
285
350
  const status = res.statusCode || 0;
286
351
  // Drain body so the socket can close cleanly.
287
352
  res.on("data", () => {});
288
- res.on("end", () => resolve(status));
353
+ res.on("end", () => {
354
+ req.destroy(); // release the socket now, don't wait for GC
355
+ resolve(status);
356
+ });
289
357
  res.on("error", reject);
290
358
  },
291
359
  );
@@ -299,10 +367,11 @@ function probeHttp({ host, port, path: urlPath, timeoutMs }) {
299
367
  function probeHttpJson({ host, port, path: urlPath, timeoutMs }) {
300
368
  return new Promise((resolve, reject) => {
301
369
  const req = http.get(
302
- { host, port, path: urlPath, timeout: timeoutMs },
370
+ { host, port, path: urlPath, timeout: timeoutMs, agent: false },
303
371
  (res) => {
304
372
  if (res.statusCode < 200 || res.statusCode >= 300) {
305
373
  res.resume();
374
+ req.destroy();
306
375
  reject(new Error(`HTTP ${res.statusCode} from ${urlPath}`));
307
376
  return;
308
377
  }
@@ -311,6 +380,7 @@ function probeHttpJson({ host, port, path: urlPath, timeoutMs }) {
311
380
  body += d.toString("utf8");
312
381
  });
313
382
  res.on("end", () => {
383
+ req.destroy(); // release the socket now, don't wait for GC
314
384
  try {
315
385
  resolve(JSON.parse(body));
316
386
  } catch (e) {
@@ -178,6 +178,12 @@ export function findInstructionFiles(opts = {}) {
178
178
  const cwd = path.resolve(opts.cwd || process.cwd());
179
179
  const home = opts.home || os.homedir() || "";
180
180
  const excludes = normalizeInstructionExcludes(opts.instructionExcludes);
181
+ // Lean / entry-only mode (`CC_PROJECT_MEMORY=lean`, IDE chat default): keep the
182
+ // primary instruction ENTRY files (user + project cc.md/CLAUDE.md/AGENTS.md
183
+ // hierarchy) but skip the heavy companions — local (cc.local.md/CLAUDE.local.md,
184
+ // gitignored personal status) and path-scoped `.claude/rules/*.md`. The agent
185
+ // can still read those with its tools when a task actually needs them.
186
+ const entryOnly = opts.entryOnly === true;
181
187
 
182
188
  const seen = new Set();
183
189
  const out = [];
@@ -217,7 +223,7 @@ export function findInstructionFiles(opts = {}) {
217
223
  }
218
224
  for (const d of chain) {
219
225
  push(firstExisting(fs, path, d, PROJECT_FILE_NAMES), "project");
220
- push(firstExisting(fs, path, d, LOCAL_FILE_NAMES), "local");
226
+ if (!entryOnly) push(firstExisting(fs, path, d, LOCAL_FILE_NAMES), "local");
221
227
  // NOTE: `.chainlesschain/rules.md` is intentionally NOT loaded here — it is
222
228
  // already injected by buildSystemPrompt() (as "## Project Rules"), which is
223
229
  // the base of every composeSystemPrompt() call. Loading it here too sent it
@@ -225,6 +231,8 @@ export function findInstructionFiles(opts = {}) {
225
231
  // below are NOT covered by buildSystemPrompt, so they stay.
226
232
  // Path-scoped rule files (`.claude/rules/*.md`, YAML frontmatter `paths:`
227
233
  // globs). Glob filtering happens at LOAD time where content is available.
234
+ // Skipped in entry-only mode (they are the bulk of the token weight).
235
+ if (entryOnly) continue;
228
236
  try {
229
237
  const rulesDir = path.join(d, ".claude", "rules");
230
238
  for (const f of fs
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Offline protocol replay — the last open pure item of P1-9 ("离线协议回放") in
3
+ * docs/CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-07-13.md.
4
+ *
5
+ * [[event-seq-replay.js]] replays for TRANSPORT reliability (re-send a gap over a
6
+ * lossy socket). This module replays for PROTOCOL COMPAT: take a recorded
7
+ * stream-json session and a negotiation context (server offer × client offer) and
8
+ * answer two offline questions no live peer is needed for —
9
+ *
10
+ * 1. REPLAY: what would these frames look like on the wire under THIS
11
+ * negotiation? (fields whose feature did not survive negotiation are
12
+ * stripped, exactly as the live emitter's field-gate would strip them.)
13
+ * 2. AUDIT: does a recorded session OBEY a claimed negotiation, i.e. does any
14
+ * frame carry a negotiable wire field the negotiation gated OFF? A vN-only
15
+ * client must never receive a vN+1 field; a recording that violates that is
16
+ * a compat bug this catches deterministically, offline, in CI.
17
+ *
18
+ * The set of gateable wire fields ({seq, trace_id, tool_use_id}) is projected
19
+ * from the ONE canonical [[capability-manifest.js]] via `toFieldGate()` — it can
20
+ * NOT drift from the negotiated feature list. All non-negotiable frame fields
21
+ * (type, content, tool name, …) always pass through untouched.
22
+ *
23
+ * PURE + deterministic: no fs / process / clock / RNG. The `digest` lets a golden
24
+ * recording be pinned so any change to negotiation/gating that alters wire output
25
+ * fails a byte/hash compare (same anti-drift philosophy as the capability doc).
26
+ */
27
+
28
+ import { createHash } from "node:crypto";
29
+ import {
30
+ negotiateProtocol,
31
+ applyNegotiationToGate,
32
+ } from "./capability-negotiation.js";
33
+ import { toFieldGate } from "./capability-manifest.js";
34
+
35
+ /** Negotiable wire field names ({seq, trace_id, tool_use_id}), single-sourced. */
36
+ export const GATEABLE_FIELDS = Object.freeze(
37
+ [...new Set(Object.values(toFieldGate()))].sort(),
38
+ );
39
+
40
+ /**
41
+ * Run negotiation and fold it into a per-field gate the replay/audit read.
42
+ * On an incompatible (ok:false) negotiation the gate stays empty → every gateable
43
+ * field is treated as OFF (fail-closed safe baseline).
44
+ *
45
+ * @param {object} params
46
+ * @param {object} params.server server offer {protocolVersion,minProtocolVersion,features}
47
+ * @param {object|null} [params.client] client offer, or null/undefined = legacy peer
48
+ * @returns {{negotiation:object, gate:object, enabledFields:string[], gatedFields:string[]}}
49
+ */
50
+ export function gateFromNegotiation({ server = {}, client = null } = {}) {
51
+ const negotiation = negotiateProtocol(server, client ?? null);
52
+ const gate = applyNegotiationToGate(negotiation, {});
53
+ const enabledFields = GATEABLE_FIELDS.filter((f) => gate[f] === true);
54
+ const gatedFields = GATEABLE_FIELDS.filter((f) => gate[f] !== true);
55
+ return { negotiation, gate, enabledFields, gatedFields };
56
+ }
57
+
58
+ /**
59
+ * Strip from a single frame every gateable wire field the gate has OFF, exactly
60
+ * as the live emitter would; all other fields pass through in original order.
61
+ * Returns a shallow copy — the input frame is never mutated.
62
+ *
63
+ * @param {object} frame
64
+ * @param {object} gate {seq?:boolean, trace_id?:boolean, tool_use_id?:boolean}
65
+ * @returns {object}
66
+ */
67
+ export function replayFrame(frame, gate = {}) {
68
+ if (!frame || typeof frame !== "object") return frame;
69
+ const out = {};
70
+ for (const [k, v] of Object.entries(frame)) {
71
+ if (GATEABLE_FIELDS.includes(k) && gate[k] !== true) continue;
72
+ out[k] = v;
73
+ }
74
+ return out;
75
+ }
76
+
77
+ /**
78
+ * Replay a recorded session under a negotiation → the frames as they'd appear on
79
+ * the wire (gated-off fields stripped) plus a deterministic digest.
80
+ *
81
+ * @param {object[]} frames recorded stream-json frames
82
+ * @param {{server:object, client?:object|null}} ctx negotiation context
83
+ * @returns {{negotiation:object, gate:object, enabledFields:string[],
84
+ * gatedFields:string[], frames:object[], digest:string}}
85
+ */
86
+ export function replaySession(frames, ctx = {}) {
87
+ const { negotiation, gate, enabledFields, gatedFields } =
88
+ gateFromNegotiation(ctx);
89
+ const emitted = (Array.isArray(frames) ? frames : []).map((f) =>
90
+ replayFrame(f, gate),
91
+ );
92
+ return {
93
+ negotiation,
94
+ gate,
95
+ enabledFields,
96
+ gatedFields,
97
+ frames: emitted,
98
+ digest: sessionDigest(emitted),
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Audit a recorded session against a claimed negotiation WITHOUT stripping:
104
+ * report every frame that carries a gateable wire field the negotiation gated
105
+ * OFF (a forward-compat violation — a field a peer at the agreed version must not
106
+ * have received). Fail-closed: `ok` is true only when there are zero violations.
107
+ *
108
+ * @param {object[]} frames
109
+ * @param {{server:object, client?:object|null}} ctx
110
+ * @returns {{ok:boolean, violations:Array<{index:number, field:string, type:(string|null)}>, gate:object, gatedFields:string[]}}
111
+ */
112
+ export function auditRecordedSession(frames, ctx = {}) {
113
+ const { gate, gatedFields } = gateFromNegotiation(ctx);
114
+ const violations = [];
115
+ (Array.isArray(frames) ? frames : []).forEach((frame, index) => {
116
+ if (!frame || typeof frame !== "object") return;
117
+ for (const field of GATEABLE_FIELDS) {
118
+ if (
119
+ gate[field] !== true &&
120
+ Object.prototype.hasOwnProperty.call(frame, field)
121
+ ) {
122
+ violations.push({
123
+ index,
124
+ field,
125
+ type: typeof frame.type === "string" ? frame.type : null,
126
+ });
127
+ }
128
+ }
129
+ });
130
+ return { ok: violations.length === 0, violations, gate, gatedFields };
131
+ }
132
+
133
+ /** Recursively key-sort a value so JSON.stringify is order-independent. */
134
+ function canonical(value) {
135
+ if (Array.isArray(value)) return value.map(canonical);
136
+ if (value && typeof value === "object") {
137
+ const out = {};
138
+ for (const k of Object.keys(value).sort()) out[k] = canonical(value[k]);
139
+ return out;
140
+ }
141
+ return value;
142
+ }
143
+
144
+ /**
145
+ * Deterministic sha256 over a frame sequence (key-order independent) so a golden
146
+ * recording can be pinned and diffed. Pure.
147
+ * @param {object[]} frames
148
+ * @returns {string} 64-hex digest
149
+ */
150
+ export function sessionDigest(frames) {
151
+ const body = (Array.isArray(frames) ? frames : []).map(canonical);
152
+ return createHash("sha256").update(JSON.stringify(body)).digest("hex");
153
+ }
@@ -1038,11 +1038,22 @@ export async function startAgentRepl(options = {}) {
1038
1038
  // output-style persona can be swapped in/out at runtime via /output-style.
1039
1039
  // `let` (not const): /add-dir rebuilds this mid-session to re-advertise the
1040
1040
  // updated working roots; output-style swaps also read it live.
1041
+ // --no-project-memory (options.projectMemory === false): lean prompt — skip
1042
+ // rules.md (in buildSystemPrompt) + the cc.md/CLAUDE.md block (in
1043
+ // composeSystemPrompt). Map only an explicit `false` to a suppressing value;
1044
+ // anything else (Commander defaults an absent `--no-` flag to `true`) stays
1045
+ // `undefined` so the runtime default-on path — and CC_PROJECT_MEMORY=0 — keep
1046
+ // their existing behaviour byte-identically.
1047
+ const _leanNoProjectMemory = options.projectMemory === false;
1041
1048
  let _replBaseSystem = composeSystemPrompt(
1042
- buildSystemPrompt(process.cwd(), { additionalDirectories }),
1049
+ buildSystemPrompt(process.cwd(), {
1050
+ additionalDirectories,
1051
+ projectMemory: options.projectMemory,
1052
+ }),
1043
1053
  {
1044
1054
  systemPrompt: options.systemPrompt,
1045
1055
  appendSystemPrompt: options.appendSystemPrompt,
1056
+ projectMemory: _leanNoProjectMemory ? false : undefined,
1046
1057
  },
1047
1058
  );
1048
1059
  let _activeOutputStyle = null; // { name, body }
@@ -2393,10 +2404,14 @@ export async function startAgentRepl(options = {}) {
2393
2404
  // Re-advertise the updated roots in the system prompt; keep the
2394
2405
  // active output-style body layered on top (same as /output-style).
2395
2406
  _replBaseSystem = composeSystemPrompt(
2396
- buildSystemPrompt(process.cwd(), { additionalDirectories }),
2407
+ buildSystemPrompt(process.cwd(), {
2408
+ additionalDirectories,
2409
+ projectMemory: options.projectMemory,
2410
+ }),
2397
2411
  {
2398
2412
  systemPrompt: options.systemPrompt,
2399
2413
  appendSystemPrompt: options.appendSystemPrompt,
2414
+ projectMemory: _leanNoProjectMemory ? false : undefined,
2400
2415
  },
2401
2416
  );
2402
2417
  messages[0].content = _activeOutputStyle
@@ -184,6 +184,35 @@ export function listBackgroundShellTasks() {
184
184
  // spawned detached (its own process group), so signal the group via the
185
185
  // negative pid. Windows: `taskkill /T` walks and kills the whole tree.
186
186
  // Returns true if a running task was signalled.
187
+ // Release the parent-side handles of a background task's child: the stdout/
188
+ // stderr PIPES (ref'd — on their own they keep the event loop alive) and the
189
+ // process handle. Called once a task is terminal or has been signalled to die,
190
+ // so a still-draining or slowly-dying child can never pin the process past a
191
+ // teardown. In production the agent's own REPL/run loop holds the loop open, so
192
+ // this changes nothing there; in the vitest forks pool nothing else does, and a
193
+ // SIGTERM'd-but-not-yet-dead child was tripping the worker-terminate deadline
194
+ // (the POSIX-only "Timeout terminating forks worker / Worker exited
195
+ // unexpectedly" unit-shard flake). Idempotent + best-effort.
196
+ function _releaseBgChildHandles(task) {
197
+ const child = task?.child;
198
+ if (!child) return;
199
+ try {
200
+ child.stdout?.destroy();
201
+ } catch {
202
+ /* noop */
203
+ }
204
+ try {
205
+ child.stderr?.destroy();
206
+ } catch {
207
+ /* noop */
208
+ }
209
+ try {
210
+ child.unref?.();
211
+ } catch {
212
+ /* noop */
213
+ }
214
+ }
215
+
187
216
  function _killTask(task) {
188
217
  const child = task?.child;
189
218
  if (!child || child.killed || task?.status !== "running") return false;
@@ -217,6 +246,9 @@ function _killTask(task) {
217
246
  } else {
218
247
  child.kill("SIGTERM");
219
248
  }
249
+ // Drop the ref'd pipe/process handles now — the group signal is away, so a
250
+ // slowly-dying child must not pin the loop while it finishes exiting.
251
+ _releaseBgChildHandles(task);
220
252
  return true;
221
253
  } catch (_err) {
222
254
  return false;
@@ -276,6 +308,7 @@ function _killTaskSync(task) {
276
308
  } else {
277
309
  child.kill("SIGKILL");
278
310
  }
311
+ _releaseBgChildHandles(task);
279
312
  return true;
280
313
  } catch (_err) {
281
314
  return false;
@@ -851,20 +884,38 @@ export function buildSystemPrompt(cwd, opts = {}) {
851
884
  // Non-critical — skill loader may not be available
852
885
  }
853
886
 
854
- // Append rules.md
855
- try {
856
- const projectRoot = findProjectRoot(dir);
857
- if (projectRoot) {
858
- const rulesPath = path.join(projectRoot, ".chainlesschain", "rules.md");
859
- if (fs.existsSync(rulesPath)) {
860
- const content = fs.readFileSync(rulesPath, "utf-8");
861
- if (content.trim()) {
862
- prompt += `\n\n## Project Rules\n${content}`;
887
+ // Append rules.md — unless the caller opted into a lean/off prompt. `rules.md`
888
+ // is coding-convention DETAIL (the entry cc.md/CLAUDE.md references it), so it
889
+ // is shed both when project memory is fully off (`--no-project-memory` →
890
+ // projectMemory === false) AND in entry-only lean mode (projectMemory ===
891
+ // "lean", or the env signal CC_PROJECT_MEMORY=lean when no explicit value is
892
+ // threaded). NOTE: legacy `CC_PROJECT_MEMORY=0` intentionally still KEEPS
893
+ // rules.md (its long-standing contract only dropped the instruction block)
894
+ // so we only honor the "lean" env here, never "0".
895
+ const _pm = opts.projectMemory;
896
+ const _envLean =
897
+ process.env.CC_PROJECT_MEMORY === "lean" ||
898
+ process.env.CC_PROJECT_MEMORY === "entry";
899
+ const _dropRules =
900
+ _pm === false ||
901
+ _pm === "lean" ||
902
+ _pm === "entry" ||
903
+ (_pm == null && _envLean);
904
+ if (!_dropRules) {
905
+ try {
906
+ const projectRoot = findProjectRoot(dir);
907
+ if (projectRoot) {
908
+ const rulesPath = path.join(projectRoot, ".chainlesschain", "rules.md");
909
+ if (fs.existsSync(rulesPath)) {
910
+ const content = fs.readFileSync(rulesPath, "utf-8");
911
+ if (content.trim()) {
912
+ prompt += `\n\n## Project Rules\n${content}`;
913
+ }
863
914
  }
864
915
  }
916
+ } catch {
917
+ // Non-critical
865
918
  }
866
- } catch {
867
- // Non-critical
868
919
  }
869
920
 
870
921
  // Advertise extra workspace roots (--add-dir) so the model knows it may
@@ -2674,6 +2725,7 @@ async function executeToolInner(
2674
2725
  task.status = "error";
2675
2726
  task.error = String(err?.message || err).substring(0, 2000);
2676
2727
  task.endedAt = new Date().toISOString();
2728
+ _releaseBgChildHandles(task);
2677
2729
  });
2678
2730
  // 'close' (not 'exit') so stdout/stderr are fully drained before the
2679
2731
  // status leaves "running" — otherwise a poll can observe completion
@@ -2686,6 +2738,7 @@ async function executeToolInner(
2686
2738
  task.exitCode = code;
2687
2739
  task.signal = signal;
2688
2740
  task.endedAt = new Date().toISOString();
2741
+ _releaseBgChildHandles(task);
2689
2742
  });
2690
2743
  } catch (err) {
2691
2744
  task.status = "error";
@@ -852,13 +852,21 @@ export async function runAgentHeadless(options = {}, deps = {}) {
852
852
  }
853
853
  }
854
854
 
855
+ // --no-project-memory (options.projectMemory === false): lean prompt — skip
856
+ // rules.md (in buildSystemPrompt) + the cc.md/CLAUDE.md block. Absent flag
857
+ // (undefined) leaves both paths byte-identical.
858
+ const _leanNoProjectMemory = options.projectMemory === false;
855
859
  const systemContent = composeSystemPrompt(
856
- buildSystemPrompt(cwd, { additionalDirectories }),
860
+ buildSystemPrompt(cwd, {
861
+ additionalDirectories,
862
+ projectMemory: options.projectMemory,
863
+ }),
857
864
  {
858
865
  systemPrompt: options.systemPrompt,
859
866
  appendSystemPrompt: options.appendSystemPrompt,
860
867
  outputStyle: outputStyleBody,
861
868
  instructionExcludes,
869
+ projectMemory: _leanNoProjectMemory ? false : undefined,
862
870
  },
863
871
  );
864
872
 
@@ -1073,8 +1073,16 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
1073
1073
  }
1074
1074
  }
1075
1075
 
1076
+ // --no-project-memory (options.projectMemory === false) → lean system prompt:
1077
+ // skip both the rules.md append inside buildSystemPrompt AND the auto-loaded
1078
+ // cc.md/CLAUDE.md instruction block. Only `=== false` changes anything; when
1079
+ // the flag is absent (undefined) both paths stay byte-identical.
1080
+ const _leanNoProjectMemory = options.projectMemory === false;
1076
1081
  const systemContent = composeSystemPrompt(
1077
- buildSystemPrompt(cwd, { additionalDirectories }),
1082
+ buildSystemPrompt(cwd, {
1083
+ additionalDirectories,
1084
+ projectMemory: options.projectMemory,
1085
+ }),
1078
1086
  {
1079
1087
  systemPrompt: options.systemPrompt,
1080
1088
  appendSystemPrompt: _jsonSchema
@@ -1084,6 +1092,7 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
1084
1092
  : options.appendSystemPrompt,
1085
1093
  outputStyle: outputStyleBody,
1086
1094
  instructionExcludes,
1095
+ projectMemory: _leanNoProjectMemory ? false : undefined,
1087
1096
  },
1088
1097
  );
1089
1098
  const messages = [{ role: "system", content: systemContent }];
@@ -49,6 +49,11 @@ export function resolveAgentPolicy({
49
49
  // before 2026-07-09, breaking --vim/--think/--thinking-budget/
50
50
  // --fallback-model/--pdh/--permission-mode for interactive sessions.
51
51
  permissionMode: overrides.permissionMode,
52
+ // --no-project-memory tri-state (undefined=default-on / false=lean prompt).
53
+ // Plain passthrough consumed by startAgentRepl → composeSystemPrompt /
54
+ // buildSystemPrompt; without this allowlist entry the flag would be silently
55
+ // dropped for interactive sessions (same class as the vim/think/pdh fixes).
56
+ projectMemory: overrides.projectMemory,
52
57
  vimMode: overrides.vimMode,
53
58
  thinking: overrides.thinking,
54
59
  thinkingBudget: overrides.thinkingBudget,
@@ -58,14 +58,25 @@ export function composeSystemPrompt(base, opts = {}) {
58
58
  // Project memory (cc.md / CLAUDE.md hierarchy) — injected right after the
59
59
  // base so explicit --append-system-prompt / output-style still come later.
60
60
  //
61
- // Default-on at runtime; the implicit default is suppressed under vitest
62
- // (process.env.VITEST) so the long-standing pure contract of this function
63
- // holds for existing unit tests callers/tests that want the block in a
64
- // test pass `projectMemory: true` explicitly.
65
- const pmExplicit = typeof projectMemory === "boolean" ? projectMemory : null;
66
- const pmDefault =
67
- process.env.CC_PROJECT_MEMORY !== "0" && !process.env.VITEST;
68
- if (pmExplicit === true || (pmExplicit === null && pmDefault)) {
61
+ // Tri-state `projectMemory`: true (full) | false (off) | "lean" (entry-only:
62
+ // the cc.md/CLAUDE.md/AGENTS.md ENTRY files, skipping local + .claude/rules/*).
63
+ // undefined resolve from CC_PROJECT_MEMORY ("0"=off, "lean"/"entry"=lean,
64
+ // else full). The full default is suppressed under vitest (process.env.VITEST)
65
+ // so the long-standing pure contract holds for existing unit tests — callers
66
+ // that want the block in a test pass `projectMemory: true`/`"lean"` explicitly.
67
+ let pmMode; // "off" | "lean" | "full"
68
+ if (projectMemory === false) pmMode = "off";
69
+ else if (projectMemory === true) pmMode = "full";
70
+ else if (projectMemory === "lean" || projectMemory === "entry")
71
+ pmMode = "lean";
72
+ else {
73
+ const env = process.env.CC_PROJECT_MEMORY;
74
+ if (env === "0") pmMode = "off";
75
+ else if ((env === "lean" || env === "entry") && !process.env.VITEST)
76
+ pmMode = "lean";
77
+ else pmMode = process.env.VITEST ? "off" : "full";
78
+ }
79
+ if (pmMode !== "off") {
69
80
  const block = loadProjectInstructionsBlock({
70
81
  cwd,
71
82
  home: opts.projectMemoryHome,
@@ -73,6 +84,8 @@ export function composeSystemPrompt(base, opts = {}) {
73
84
  // Large-monorepo reduction lever: subtrees whose instruction/rule/@import
74
85
  // files never load (config `instructionExcludes`, forwarded by the caller).
75
86
  instructionExcludes: opts.instructionExcludes,
87
+ // Lean mode → keep only the entry instruction files.
88
+ entryOnly: pmMode === "lean",
76
89
  });
77
90
  if (block) result = result ? `${result}\n\n${block}` : block;
78
91
  }
@@ -1 +0,0 @@
1
- import{P as c,J as p,b as s}from"./vendor-Bn1JzzAn.js";import{r as a}from"./markdown-Yo_c6Krv.js";import{_ as d}from"./index-Rh-ytwow.js";const _={__name:"MarkdownRenderer",props:{content:{type:String,default:""}},setup(t,{expose:o}){o();const e=t,r=s(()=>a(e.content||"")),n={props:e,rendered:r,computed:s,get renderMarkdown(){return a}};return Object.defineProperty(n,"__isScriptSetup",{enumerable:!1,value:!0}),n}},i=["innerHTML"];function m(t,o,e,r,n,u){return c(),p("div",{class:"markdown-content",innerHTML:r.rendered},null,8,i)}const w=d(_,[["render",m],["__scopeId","data-v-0b0b9f63"],["__file","/tmp/cc-web-panel-rmqPJs/repo/packages/web-panel/src/components/MarkdownRenderer.vue"]]);export{w as M};
@@ -1 +0,0 @@
1
- import{I as n,J as g,c as l,K as r,N as u,P as h,U as o,R as t,Q as m}from"./vendor-Bn1JzzAn.js";import{_ as v,b}from"./index-Rh-ytwow.js";import{a8 as y,M}from"./icons-DG5UD-6K.js";const B={__name:"MobileProjects",setup(p,{expose:i}){i();const c=u(),{t:e}=b();function a(){c.push("/projects")}function _(){c.push("/mobile-bridge")}const s={router:c,t:e,goToProjects:a,goToMobileBridge:_,get useRouter(){return u},get useI18n(){return b},get MobileOutlined(){return M},get FolderOutlined(){return y}};return Object.defineProperty(s,"__isScriptSetup",{enumerable:!1,value:!0}),s}},x={class:"mobile-projects-placeholder"},O={class:"title"},k={class:"subtitle"},w={class:"explainer"};function T(p,i,c,e,a,_){const s=n("a-alert"),d=n("a-button"),f=n("a-space"),P=n("a-empty"),j=n("a-card");return h(),g("div",x,[l(j,null,{default:r(()=>[l(P,{description:!1},{image:r(()=>[l(e.MobileOutlined,{style:{fontSize:"64px",color:"#bfbfbf"}})]),default:r(()=>[o("h2",O,t(e.t("mobileProjects.title")),1),o("p",k,t(e.t("mobileProjects.subtitle")),1),l(s,{message:e.t("mobileProjects.v02Banner"),type:"info","show-icon":"",class:"banner"},null,8,["message"]),o("div",w,[o("h3",null,t(e.t("mobileProjects.currentDirection")),1),o("p",null,t(e.t("mobileProjects.currentDirectionBody")),1),o("ul",null,[o("li",null,t(e.t("mobileProjects.stepPhone")),1),o("li",null,t(e.t("mobileProjects.stepTap")),1),o("li",null,t(e.t("mobileProjects.stepPull")),1)]),o("h3",null,t(e.t("mobileProjects.reverseDirection")),1),o("p",null,t(e.t("mobileProjects.reverseDirectionBody")),1)]),l(f,{class:"actions"},{default:r(()=>[l(d,{type:"primary",onClick:e.goToProjects},{default:r(()=>[l(e.FolderOutlined),m(" "+t(e.t("mobileProjects.viewLocalProjects")),1)]),_:1}),l(d,{onClick:e.goToMobileBridge},{default:r(()=>[l(e.MobileOutlined),m(" "+t(e.t("mobileProjects.checkBridge")),1)]),_:1})]),_:1})]),_:1})]),_:1})])}const N=v(B,[["render",T],["__scopeId","data-v-9262cc45"],["__file","/tmp/cc-web-panel-rmqPJs/repo/packages/web-panel/src/views/MobileProjects.vue"]]);export{N as default};