chainlesschain 0.162.186 → 0.162.187

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 (173) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-DTXiO-TB.js → AIOps-CETaO3s8.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BbmDW-Pz.js → ActionButton-CAe7E9TW.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-CSfgXJuD.js → Analytics-BGr2mzwO.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BS0FFLb7.js → AppLayout-BEoY-NcT.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-B19WFPK2.js → Artifacts-DUZivCgt.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CQhZBi7n.js → Audit-C3yBs68k.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-BOLZCpGH.js → BackgroundAgents-C9KwiY3O.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Dz3R_ePD.js → Backup-BoSWD_C8.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CYfgMpax.js → BaseInput-Dp09GkxL.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-5CymMr1j.js → Chat-5t9d-Ja0.js} +4 -4
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-DmVku4GH.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-CDXHVYcu.js → Checkbox-8QJFlkqI.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-uq9Z9oiS.js → Codegen-BJVFQ3lL.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-BuW1K_65.js → Col-6hdLD0oB.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-Bbg-PvRY.js → Community-B8opo0uY.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-co0pPPTw.js → Compact-Dv8HudiN.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-B-pFAv7z.js → Compliance-DhxuCS5N.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-54ZQS4gC.js → Cowork-Dggy6VIL.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-B1BF2Seq.js → Cron-DSmDqJTT.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-Dey6qQTj.js → Crosschain-7bZq0j5f.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-ltERvfAj.js → DID-BWY4kh-H.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-DuWxOcqN.js → Dashboard-bOzPZxlu.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-CrZ8ekTo.js → Dropdown-BTFJHqQu.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-fIyHXhUS.js → EmailListRenderer-BC3MOdtT.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BtRyrRPm.js → FamilyGuardDashboard-CFHAdTTY.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-EUC6Wydj.js → Federation-DZ_4aF_n.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-BOpka2G-.js → FormItemContext-BZje6UTj.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BXtrrYHn.js → GenericCardRenderer-D7QfCvjN.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-8WMb7PiR.js → Git-DuG7qANG.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DNWOeDTV.js → Governance-BDmYAgTh.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-Bne9b_Lb.js → Inference-DSXAfc1Q.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-Cp-6Us9b.js → KnowledgeGraph-DusleniP.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-Ck-uSPAx.js → Logs-CpImmA8p.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-DxjsdAXw.js → MarkdownRenderer-BZ6LJBkk.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-Vdq7brrJ.js → Marketplace-DXS8CXkE.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-ggLnw4hY.js → McpTools-D151UfUI.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-BKIjNVCU.js → Memory-DKDxnoA1.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BElCY25a.js → MobileBridge-BFZ95iKM.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-CDAR-grF.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-bNdrF_Zh.js → Mtc-OGJ95xJs.js} +4 -4
  42. package/src/assets/web-panel/assets/{MtcAudit-Dw8w1Cqk.js → MtcAudit-BmGTo1XC.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-CfGKvlqc.js → Multisig-XumJRqOa.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-D6OJf7YN.js → NLProgramming-DHhhRZed.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-B3n_LzIS.js → Notes-B9rGxNNr.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-Uu1V_ICM.js → NotificationSettings-DClLbMYV.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-aGyWwWcp.js → OrderTableRenderer-I8OohT7N.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-qjFFmuwK.js → Organization-M6WzaYVa.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-J4ZgnwZz.js → Overflow-Cb3B5KtM.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-YEZF-uVH.js → P2P-BGk57kya.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-yqx7fwqk.js → PdhVaultBrowser-CXcKyPqK.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DdEjlZ02.js → Permissions-hsfJ-Y0e.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-DbAPYvns.js → PersonalDataHub-BSBii4js.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-Bxlk_ZJU.js → Pipeline-BNw52rey.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-CpE-05tz.js → Privacy-DUpfue4E.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-DWlhcGHT.js → ProjectInit-C68NsP3h.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-BnH6BiTQ.js → ProjectSettings-BJ2It9v4.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-CHw2fXDB.js → Projects-D2FHLUa-.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-Bdz_sTyb.js → Providers-EnU4VWwS.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-BHh2b3pH.js → QrScannerModal-BRt5-B_I.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-D84M09pe.js → QuickAsk-Jd5L9UL2.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-Czc30K6M.js → Recommend-BNDhoVIu.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-q_TU5qxt.js → RemoteSession-BII41Mel.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-CnTce4WM.js → Reputation-D1wQPUNv.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-C60UFLay.js → Row-DnjRegul.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-BOUFEu9_.js → RssFeed-C_T4I-nF.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-D4__blE-.js → Search-C4lrxlLh.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-BoOm7_JK.js → Security-ByYT-VzP.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-AvzrW6HA.js → Services-BHpyEqB5.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-BZlrHozi.js → Skeleton-C0I23vG-.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CKaMLuk0.js → Skills-DGZXz22K.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DYCNJF8h.js → Sla-DL_urP9A.js} +1 -1
  73. package/src/assets/web-panel/assets/SpeechSettings-6x-hUNQ_.js +1 -0
  74. package/src/assets/web-panel/assets/{SyncSettings-B6tW8dVO.js → SyncSettings-Crk5TCXq.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-oNC_2NVz.js → Tasks-rZGq5PZs.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-DvLPvvjL.js → Templates-DT5el8Q8.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-BDt9UBn_.js → Tenant-mlgeyMK8.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-ddCUnH-X.js → Terminal-C8HEA-lM.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-CA4sDL47.js → TimelineRenderer-DvlqBayB.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-By03hnVs.js → Tokens-C7lDcNmT.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-Dz2mH-ZU.js → Trigger-CM8nCnIJ.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DShSWHeJ.js → Trust-BQDLUQ9x.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-JN8lP3gt.js → UkeySign-CVqM0OFE.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BgPy4gV3.js → VideoEditing-BvAcQpzT.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-CQGEsXw_.js → Wallet-zFVFaAZ2.js} +3 -3
  86. package/src/assets/web-panel/assets/{WebAuthn-D8xR-ds_.js → WebAuthn-Cc522ums.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-Df61_p1p.js → WorkflowEditor-Bg_wxpDo.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-fdPby427.js → chat-jg0QwC8K.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-Dimqhufo.js → colors-ALzYz1gO.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-DHayKcqZ.js → compact-item-DXHYUoqi.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-hZUhypRH.js → createContext-BsGsBM5m.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-Dy1GJA4S.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-Dw7zC_dV.js → hasIn-BccMLzxA.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BIxxvaGN.js → index-08diKcTm.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BVHdkz8F.js → index-2VstcImq.js} +17 -17
  96. package/src/assets/web-panel/assets/index-8zbMbbuf.js +1 -0
  97. package/src/assets/web-panel/assets/{index-DAjvkQ4K.js → index-BBbbr1YB.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DX1DwDFI.js → index-BTTeAiOP.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-JE6Udz2r.js → index-BcJzphrP.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BYdNwhkv.js → index-BdCmJ_bG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CTiVrnpR.js → index-BfF0OGKU.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-C1yMYKi-.js → index-BfV8RDlg.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DxoPzPHy.js → index-BvsII10i.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DyEhrvpm.js → index-C0AbXsDy.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-lPjBQJCy.js → index-C3Rf8wIM.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CPZ3U5iH.js → index-C3k3ZwkQ.js} +1 -1
  107. package/src/assets/web-panel/assets/index-C97f71oi.js +1 -0
  108. package/src/assets/web-panel/assets/{index-DJP7TjWm.js → index-CBVIFX-x.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DSlda4-6.js → index-CJg6Wsjc.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DzJXkoZs.js → index-CLzPzN75.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-cEnkyHtX.js → index-CQ1ORdp-.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Bhmpp3cm.js → index-CSFESurl.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BwnUluxu.js → index-CWLWBgfe.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CIOE8tt6.js → index-CahDve8M.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CBQK-LEF.js → index-Cg9cOVe9.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-D7jQEwB6.js → index-Cmg_Npx5.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BuMSdwno.js → index-CrZHUsOZ.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DnwDYBnG.js → index-Cz_s25m3.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DVM_KgzF.js → index-D9oV2iRR.js} +1 -1
  120. package/src/assets/web-panel/assets/{index--DVpUpz3.js → index-DgjQ-kdT.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-y5LHQ5Ck.js → index-Dmk7dD-T.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Urt2R5aE.js → index-Dxqiu9-F.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CvKHpaao.js → index-JS2LXdd0.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CsCo_A0I.js → index-TeCof1s3.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-ulvBurtw.js → index-UQXedWQz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BM5BNqp9.js → index-koT8Q86Z.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BHJUptSx.js → index-mIQUoUx1.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-C8XXPz2n.js → index-oWltafBn.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DC2R4zvM.js → index-rIy96PeM.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-j0pxzOu7.js → index-rcwgNMc0.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CuaVa8tX.js → index-srqW5_-X.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CcpDboUd.js → index-uzykuL_R.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-B4pR3px5.js → initDefaultProps-xafkqrW3.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-CE7dWPPs.js → motion-BrtyQXLk.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-Hw8cUObc.js → move-BunUyg59.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-CvRjE4Fa.js → mtc-parser-BPwjBeuE.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-CkFeLVre.js → omit-BpyqZlNO.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-5ZMqmbbb.js → pickAttrs-DDrV0XjE.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BZ4oeZ0q.js → placementArrow-1A18Pcl2.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-lwoiX_Us.js → responsiveObserve-B8Fnlmso.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-D_as37l8.js → slide-x-Fwb2Zm.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BsA-4Bs7.js → statusUtils-aAG5yJl_.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BVar4sLQ.js → styleChecker-BRQSTMRL.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-j1IwVpGd.js → useFlexGapSupport-D5X_KozY.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-7Q9bEnUp.js → useFs-DN_YIjP-.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-BBwTIRMd.js → usePersonalDataHub-BizxAvjw.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-C7Dk-M8w.js → vnode-B5oAxN93.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-D3UHaTFB.js → zoom-DJhvyzKv.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/team.js +1805 -473
  151. package/src/data/changelog.json +13 -0
  152. package/src/harness/worktree-isolator.js +671 -79
  153. package/src/lib/agent-team/task-lease.js +378 -78
  154. package/src/lib/agent-team/team-agent-stream.js +488 -0
  155. package/src/lib/agent-team/team-budget.js +216 -20
  156. package/src/lib/agent-team/team-mailbox.js +389 -15
  157. package/src/lib/agent-team/team-run-state-lock.js +209 -0
  158. package/src/lib/agent-team/team-runner.js +547 -63
  159. package/src/lib/agent-team/team-scope-lock.js +409 -0
  160. package/src/lib/agent-team/team-task-contract.js +277 -0
  161. package/src/lib/agent-team/team-worktree.js +1171 -62
  162. package/src/lib/agent-worktree.js +19 -20
  163. package/src/lib/collaboration-run-store.js +983 -68
  164. package/src/lib/with-file-lock.js +190 -24
  165. package/src/lib/worktree-isolator.js +3 -0
  166. package/src/lib/worktree-sparse.js +23 -0
  167. package/src/runtime/headless-stream.js +6 -1
  168. package/src/assets/web-panel/assets/ChatBubbleRenderer-B2A_TJtl.js +0 -1
  169. package/src/assets/web-panel/assets/MobileProjects-DpLgovdH.js +0 -1
  170. package/src/assets/web-panel/assets/SpeechSettings-BvCSU6d2.js +0 -1
  171. package/src/assets/web-panel/assets/devWarning-ePxoBp92.js +0 -1
  172. package/src/assets/web-panel/assets/index-BTBDdCoo.js +0 -1
  173. package/src/assets/web-panel/assets/index-CfOGx8Ns.js +0 -1
@@ -0,0 +1,409 @@
1
+ /**
2
+ * Pure, in-memory scope ownership for large Agent Teams.
3
+ *
4
+ * A lock key (normally a task or agent id) owns one or more repository-relative
5
+ * paths. Directory-prefix overlap is intentionally conservative: owning `src`
6
+ * also owns `src/a.js`. An empty scope list means the whole workspace.
7
+ *
8
+ * The implementation does not inspect the filesystem. That keeps planning,
9
+ * scheduling, persistence validation, and tests deterministic across platforms.
10
+ */
11
+
12
+ export const TEAM_SCOPE_LOCK_ERROR_CODES = Object.freeze({
13
+ INVALID_KEY: "TEAM_SCOPE_LOCK_INVALID_KEY",
14
+ INVALID_SCOPES: "TEAM_SCOPE_LOCK_INVALID_SCOPES",
15
+ INVALID_SCOPE_PATH: "TEAM_SCOPE_LOCK_INVALID_SCOPE_PATH",
16
+ SCOPE_CONFLICT: "TEAM_SCOPE_LOCK_SCOPE_CONFLICT",
17
+ KEY_ALREADY_HELD: "TEAM_SCOPE_LOCK_KEY_ALREADY_HELD",
18
+ NOT_HELD: "TEAM_SCOPE_LOCK_NOT_HELD",
19
+ INVALID_SNAPSHOT: "TEAM_SCOPE_LOCK_INVALID_SNAPSHOT",
20
+ });
21
+
22
+ export const TEAM_SCOPE_LOCK_SNAPSHOT_VERSION = 1;
23
+
24
+ export class TeamScopeLockError extends Error {
25
+ constructor(code, message, details = {}) {
26
+ super(message);
27
+ this.name = "TeamScopeLockError";
28
+ this.code = code;
29
+ Object.assign(this, details);
30
+ }
31
+ }
32
+
33
+ function codedError(code, message, details = {}) {
34
+ return new TeamScopeLockError(code, message, details);
35
+ }
36
+
37
+ function compareStrings(left, right) {
38
+ if (left < right) return -1;
39
+ if (left > right) return 1;
40
+ return 0;
41
+ }
42
+
43
+ function normalizeKey(value) {
44
+ if (typeof value !== "string") {
45
+ throw codedError(
46
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_KEY,
47
+ "scope lock key must be a non-empty string",
48
+ );
49
+ }
50
+ const key = value.trim();
51
+ if (!key || key.includes("\0")) {
52
+ throw codedError(
53
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_KEY,
54
+ "scope lock key must be a non-empty string without NUL bytes",
55
+ );
56
+ }
57
+ return key;
58
+ }
59
+
60
+ /**
61
+ * Canonicalize a relative POSIX or Windows path to slash-separated POSIX form.
62
+ *
63
+ * Absolute paths, drive-qualified paths, parent traversal, NUL bytes, and paths
64
+ * that normalize to empty are rejected. `.` and duplicate separators are
65
+ * removed without resolving against a working directory.
66
+ */
67
+ export function normalizeTeamScopePath(value) {
68
+ if (typeof value !== "string") {
69
+ throw codedError(
70
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SCOPE_PATH,
71
+ "scope path must be a non-empty string",
72
+ { scope: value },
73
+ );
74
+ }
75
+
76
+ const input = value.trim();
77
+ if (!input || input.includes("\0")) {
78
+ throw codedError(
79
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SCOPE_PATH,
80
+ "scope path must be non-empty and must not contain NUL bytes",
81
+ { scope: value },
82
+ );
83
+ }
84
+ const withoutLeadingPresentationSpace = value.trimStart();
85
+ if (
86
+ withoutLeadingPresentationSpace.length !== input.length &&
87
+ !input.endsWith("/") &&
88
+ !input.endsWith("\\")
89
+ ) {
90
+ throw codedError(
91
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SCOPE_PATH,
92
+ "scope path must not end in ambiguous whitespace",
93
+ { scope: value },
94
+ );
95
+ }
96
+
97
+ // Reject POSIX absolute paths, Windows root/UNC paths, and both rooted and
98
+ // drive-relative forms (`C:\x`, `C:/x`, and `C:x`).
99
+ if (
100
+ input.startsWith("/") ||
101
+ input.startsWith("\\") ||
102
+ /^[A-Za-z]:/.test(input)
103
+ ) {
104
+ throw codedError(
105
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SCOPE_PATH,
106
+ "scope path must be repository-relative",
107
+ { scope: value },
108
+ );
109
+ }
110
+
111
+ const parts = [];
112
+ for (const rawPart of input.replaceAll("\\", "/").split("/")) {
113
+ if (!rawPart || rawPart === ".") continue;
114
+ if (rawPart === "..") {
115
+ throw codedError(
116
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SCOPE_PATH,
117
+ "scope path must not contain parent traversal",
118
+ { scope: value },
119
+ );
120
+ }
121
+ // Use one conservative cross-platform identity. Windows treats case,
122
+ // trailing dots/spaces, ADS colons, and device names specially; rejecting
123
+ // the ambiguous spellings and case-folding everywhere may serialize a few
124
+ // safe Linux paths, but cannot miss a Windows collision.
125
+ if (
126
+ rawPart.endsWith(".") ||
127
+ rawPart.endsWith(" ") ||
128
+ rawPart.includes(":") ||
129
+ /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i.test(rawPart)
130
+ ) {
131
+ throw codedError(
132
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SCOPE_PATH,
133
+ "scope path contains a cross-platform ambiguous segment",
134
+ { scope: value },
135
+ );
136
+ }
137
+ parts.push(rawPart.normalize("NFC").toLowerCase());
138
+ }
139
+
140
+ if (parts.length === 0) {
141
+ throw codedError(
142
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SCOPE_PATH,
143
+ "scope path must identify a repository-relative location",
144
+ { scope: value },
145
+ );
146
+ }
147
+
148
+ return parts.join("/");
149
+ }
150
+
151
+ function containsScope(parent, candidate) {
152
+ return candidate === parent || candidate.startsWith(`${parent}/`);
153
+ }
154
+
155
+ /**
156
+ * Canonicalize, sort, de-duplicate, and compact a scope list.
157
+ *
158
+ * Descendants are redundant when an ancestor is present, so
159
+ * `["src", "src/a.js"]` canonicalizes to `["src"]`. `[]` is preserved and
160
+ * denotes the whole workspace.
161
+ */
162
+ export function normalizeTeamScopes(scopes = []) {
163
+ if (!Array.isArray(scopes)) {
164
+ throw codedError(
165
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SCOPES,
166
+ "scopes must be an array of repository-relative paths",
167
+ { scopes },
168
+ );
169
+ }
170
+
171
+ const sorted = [
172
+ ...new Set(scopes.map((scope) => normalizeTeamScopePath(scope))),
173
+ ].sort(compareStrings);
174
+ const compact = [];
175
+ for (const scope of sorted) {
176
+ if (compact.some((ancestor) => containsScope(ancestor, scope))) continue;
177
+ compact.push(scope);
178
+ }
179
+ return compact;
180
+ }
181
+
182
+ /**
183
+ * Whether two normalized-or-raw scope lists overlap.
184
+ *
185
+ * Empty scopes mean whole-workspace ownership and therefore overlap every
186
+ * other lock, including another whole-workspace lock.
187
+ */
188
+ export function teamScopesOverlap(leftScopes, rightScopes) {
189
+ const left = normalizeTeamScopes(leftScopes);
190
+ const right = normalizeTeamScopes(rightScopes);
191
+ if (left.length === 0 || right.length === 0) return true;
192
+
193
+ return left.some((leftScope) =>
194
+ right.some(
195
+ (rightScope) =>
196
+ containsScope(leftScope, rightScope) ||
197
+ containsScope(rightScope, leftScope),
198
+ ),
199
+ );
200
+ }
201
+
202
+ function sameScopes(left, right) {
203
+ return (
204
+ left.length === right.length &&
205
+ left.every((scope, index) => scope === right[index])
206
+ );
207
+ }
208
+
209
+ function cloneLock(key, scopes) {
210
+ return {
211
+ key,
212
+ scopes: [...scopes],
213
+ workspace: scopes.length === 0,
214
+ };
215
+ }
216
+
217
+ function failure(error, fallbackCode) {
218
+ if (error instanceof TeamScopeLockError) {
219
+ return {
220
+ ok: false,
221
+ code: error.code,
222
+ message: error.message,
223
+ };
224
+ }
225
+ return {
226
+ ok: false,
227
+ code: fallbackCode,
228
+ message: error instanceof Error ? error.message : String(error),
229
+ };
230
+ }
231
+
232
+ export class TeamScopeLock {
233
+ constructor() {
234
+ this._locks = new Map();
235
+ }
236
+
237
+ /**
238
+ * Validate whether `key` can own `scopes` without mutating lock state.
239
+ *
240
+ * Re-acquiring the same canonical scopes under the same key is idempotent.
241
+ * A key cannot silently replace its current ownership with different scopes;
242
+ * callers must release it first.
243
+ */
244
+ canAcquire(key, scopes = []) {
245
+ let canonicalKey;
246
+ let canonicalScopes;
247
+ try {
248
+ canonicalKey = normalizeKey(key);
249
+ canonicalScopes = normalizeTeamScopes(scopes);
250
+ } catch (error) {
251
+ return failure(error, TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SCOPES);
252
+ }
253
+
254
+ const current = this._locks.get(canonicalKey);
255
+ if (current) {
256
+ if (sameScopes(current, canonicalScopes)) {
257
+ return {
258
+ ok: true,
259
+ key: canonicalKey,
260
+ scopes: [...canonicalScopes],
261
+ alreadyHeld: true,
262
+ conflicts: [],
263
+ };
264
+ }
265
+ return {
266
+ ok: false,
267
+ code: TEAM_SCOPE_LOCK_ERROR_CODES.KEY_ALREADY_HELD,
268
+ message: `scope lock key "${canonicalKey}" already owns different scopes`,
269
+ key: canonicalKey,
270
+ scopes: [...canonicalScopes],
271
+ heldScopes: [...current],
272
+ conflicts: [],
273
+ };
274
+ }
275
+
276
+ const conflicts = [];
277
+ for (const [heldKey, heldScopes] of this._locks) {
278
+ if (teamScopesOverlap(canonicalScopes, heldScopes)) {
279
+ conflicts.push(cloneLock(heldKey, heldScopes));
280
+ }
281
+ }
282
+ conflicts.sort((left, right) => compareStrings(left.key, right.key));
283
+
284
+ if (conflicts.length > 0) {
285
+ return {
286
+ ok: false,
287
+ code: TEAM_SCOPE_LOCK_ERROR_CODES.SCOPE_CONFLICT,
288
+ message: `requested scopes conflict with ${conflicts.length} active lock(s)`,
289
+ key: canonicalKey,
290
+ scopes: [...canonicalScopes],
291
+ conflicts,
292
+ };
293
+ }
294
+
295
+ return {
296
+ ok: true,
297
+ key: canonicalKey,
298
+ scopes: [...canonicalScopes],
299
+ alreadyHeld: false,
300
+ conflicts: [],
301
+ };
302
+ }
303
+
304
+ acquire(key, scopes = []) {
305
+ const check = this.canAcquire(key, scopes);
306
+ if (!check.ok) return check;
307
+ if (check.alreadyHeld) {
308
+ return {
309
+ ...check,
310
+ acquired: false,
311
+ };
312
+ }
313
+
314
+ this._locks.set(check.key, [...check.scopes]);
315
+ return {
316
+ ...check,
317
+ acquired: true,
318
+ };
319
+ }
320
+
321
+ release(key) {
322
+ let canonicalKey;
323
+ try {
324
+ canonicalKey = normalizeKey(key);
325
+ } catch (error) {
326
+ return failure(error, TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_KEY);
327
+ }
328
+
329
+ const scopes = this._locks.get(canonicalKey);
330
+ if (!scopes) {
331
+ return {
332
+ ok: false,
333
+ code: TEAM_SCOPE_LOCK_ERROR_CODES.NOT_HELD,
334
+ message: `scope lock key "${canonicalKey}" does not own a lock`,
335
+ key: canonicalKey,
336
+ };
337
+ }
338
+
339
+ this._locks.delete(canonicalKey);
340
+ return {
341
+ ok: true,
342
+ released: true,
343
+ ...cloneLock(canonicalKey, scopes),
344
+ };
345
+ }
346
+
347
+ status() {
348
+ const locks = [...this._locks]
349
+ .sort(([left], [right]) => compareStrings(left, right))
350
+ .map(([key, scopes]) => cloneLock(key, scopes));
351
+ return {
352
+ count: locks.length,
353
+ locks,
354
+ };
355
+ }
356
+
357
+ snapshot() {
358
+ return {
359
+ version: TEAM_SCOPE_LOCK_SNAPSHOT_VERSION,
360
+ locks: this.status().locks.map(({ key, scopes }) => ({
361
+ key,
362
+ scopes,
363
+ })),
364
+ };
365
+ }
366
+
367
+ /**
368
+ * Restore only snapshots that could have been produced by a valid lock set.
369
+ * Corrupt entries, duplicate keys, and mutually-overlapping owners fail
370
+ * closed with one stable snapshot error code.
371
+ */
372
+ static restore(snapshot) {
373
+ if (
374
+ !snapshot ||
375
+ typeof snapshot !== "object" ||
376
+ Array.isArray(snapshot) ||
377
+ snapshot.version !== TEAM_SCOPE_LOCK_SNAPSHOT_VERSION ||
378
+ !Array.isArray(snapshot.locks)
379
+ ) {
380
+ throw codedError(
381
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SNAPSHOT,
382
+ "invalid team scope lock snapshot",
383
+ );
384
+ }
385
+
386
+ const restored = new TeamScopeLock();
387
+ for (const entry of snapshot.locks) {
388
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
389
+ throw codedError(
390
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SNAPSHOT,
391
+ "team scope lock snapshot contains an invalid lock entry",
392
+ );
393
+ }
394
+
395
+ const result = restored.acquire(entry.key, entry.scopes);
396
+ if (!result.ok || result.alreadyHeld) {
397
+ throw codedError(
398
+ TEAM_SCOPE_LOCK_ERROR_CODES.INVALID_SNAPSHOT,
399
+ "team scope lock snapshot contains invalid or conflicting ownership",
400
+ {
401
+ causeCode:
402
+ result.code ?? TEAM_SCOPE_LOCK_ERROR_CODES.KEY_ALREADY_HELD,
403
+ },
404
+ );
405
+ }
406
+ }
407
+ return restored;
408
+ }
409
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Pure task-level contract resolution for `cc team`.
3
+ *
4
+ * A task may request execution settings in `task.agent` or `task.policy`.
5
+ * Policy has precedence over agent when both declare the same field. The
6
+ * parent contract is always the security/resource ceiling:
7
+ *
8
+ * - permission mode may only become more restrictive;
9
+ * - numeric budgets may only become smaller;
10
+ * - required checkpoint/worktree isolation may not be disabled.
11
+ *
12
+ * Invalid child values inherit the parent value. No I/O, clocks, environment
13
+ * reads, or mutation are performed here, so the resolver is safe to share
14
+ * between planning, execution, resume validation, and UI projection.
15
+ */
16
+
17
+ import {
18
+ SUBAGENT_PERMISSION_MODES,
19
+ capBudget,
20
+ tightenPermissionMode,
21
+ } from "../subagent-contract.js";
22
+
23
+ const hasOwn = (value, key) =>
24
+ value != null && Object.prototype.hasOwnProperty.call(value, key);
25
+
26
+ const asObject = (value) =>
27
+ value && typeof value === "object" && !Array.isArray(value) ? value : {};
28
+
29
+ function finitePositiveNumber(value) {
30
+ const number = Number(value);
31
+ return Number.isFinite(number) && number > 0 ? number : null;
32
+ }
33
+
34
+ function finitePositiveInteger(value) {
35
+ const number = Number(value);
36
+ return Number.isSafeInteger(number) && number > 0 ? number : null;
37
+ }
38
+
39
+ function normalizeModel(value) {
40
+ if (typeof value !== "string") return null;
41
+ const model = value.trim();
42
+ return model || null;
43
+ }
44
+
45
+ function normalizeRequired(value) {
46
+ return value === true || value === false ? value : null;
47
+ }
48
+
49
+ /**
50
+ * Pick a task request. `task.policy` deliberately wins over `task.agent`.
51
+ * An explicit-but-invalid policy value therefore inherits the parent instead
52
+ * of falling through to a less-authoritative agent declaration.
53
+ */
54
+ function requestedField(task, field) {
55
+ const policy = asObject(task?.policy);
56
+ if (hasOwn(policy, field)) {
57
+ return { specified: true, source: "task.policy", raw: policy[field] };
58
+ }
59
+ const agent = asObject(task?.agent);
60
+ if (hasOwn(agent, field)) {
61
+ return { specified: true, source: "task.agent", raw: agent[field] };
62
+ }
63
+ return { specified: false, source: null, raw: null };
64
+ }
65
+
66
+ function addAdjustment(adjustments, request, field, effective, reason) {
67
+ adjustments.push({
68
+ field,
69
+ source: request.source,
70
+ requested: request.raw,
71
+ effective,
72
+ reason,
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Resolve one task's effective execution contract.
78
+ *
79
+ * @param {object} [options]
80
+ * @param {object} [options.parent] Trusted run/team defaults and ceilings.
81
+ * @param {object} [options.task] Task containing optional `agent` / `policy`.
82
+ * @returns {{
83
+ * permissionMode:string,
84
+ * maxTurns:number|null,
85
+ * maxBudgetUsd:number|null,
86
+ * maxTokens:number|null,
87
+ * maxWallMs:number|null,
88
+ * checkpointRequired:boolean,
89
+ * worktreeRequired:boolean,
90
+ * model:string|null,
91
+ * adjustments:Array<{
92
+ * field:string,
93
+ * source:string,
94
+ * requested:unknown,
95
+ * effective:unknown,
96
+ * reason:string
97
+ * }>
98
+ * }}
99
+ */
100
+ export function resolveTeamTaskContract({ parent = {}, task = {} } = {}) {
101
+ const defaults = asObject(parent);
102
+ const work = asObject(task);
103
+ const adjustments = [];
104
+
105
+ const parentPermission = tightenPermissionMode(defaults.permissionMode, null);
106
+ const permissionRequest = requestedField(work, "permissionMode");
107
+ const requestedPermission =
108
+ permissionRequest.specified &&
109
+ SUBAGENT_PERMISSION_MODES.includes(permissionRequest.raw)
110
+ ? permissionRequest.raw
111
+ : null;
112
+ const permissionMode = tightenPermissionMode(
113
+ parentPermission,
114
+ requestedPermission,
115
+ );
116
+ if (permissionRequest.specified) {
117
+ if (requestedPermission == null) {
118
+ addAdjustment(
119
+ adjustments,
120
+ permissionRequest,
121
+ "permissionMode",
122
+ permissionMode,
123
+ "invalid-value-inherited",
124
+ );
125
+ } else if (permissionMode !== requestedPermission) {
126
+ addAdjustment(
127
+ adjustments,
128
+ permissionRequest,
129
+ "permissionMode",
130
+ permissionMode,
131
+ "parent-permission-ceiling",
132
+ );
133
+ }
134
+ }
135
+
136
+ const parentMaxTurns = finitePositiveInteger(defaults.maxTurns);
137
+ const turnsRequest = requestedField(work, "maxTurns");
138
+ const requestedMaxTurns = turnsRequest.specified
139
+ ? finitePositiveInteger(turnsRequest.raw)
140
+ : null;
141
+ const maxTurns =
142
+ requestedMaxTurns == null
143
+ ? parentMaxTurns
144
+ : parentMaxTurns == null
145
+ ? requestedMaxTurns
146
+ : Math.min(parentMaxTurns, requestedMaxTurns);
147
+ if (turnsRequest.specified) {
148
+ if (requestedMaxTurns == null) {
149
+ addAdjustment(
150
+ adjustments,
151
+ turnsRequest,
152
+ "maxTurns",
153
+ maxTurns,
154
+ "invalid-value-inherited",
155
+ );
156
+ } else if (maxTurns !== requestedMaxTurns) {
157
+ addAdjustment(
158
+ adjustments,
159
+ turnsRequest,
160
+ "maxTurns",
161
+ maxTurns,
162
+ "parent-budget-ceiling",
163
+ );
164
+ }
165
+ }
166
+
167
+ const budgetFields = [
168
+ {
169
+ field: "maxBudgetUsd",
170
+ budgetKey: "costUsd",
171
+ normalize: finitePositiveNumber,
172
+ },
173
+ {
174
+ field: "maxTokens",
175
+ budgetKey: "tokens",
176
+ normalize: finitePositiveInteger,
177
+ },
178
+ {
179
+ field: "maxWallMs",
180
+ budgetKey: "timeMs",
181
+ normalize: finitePositiveInteger,
182
+ },
183
+ ];
184
+ const parentBudget = {};
185
+ const requestedBudget = {};
186
+ const budgetRequests = new Map();
187
+ for (const spec of budgetFields) {
188
+ parentBudget[spec.budgetKey] = spec.normalize(defaults[spec.field]);
189
+ const request = requestedField(work, spec.field);
190
+ budgetRequests.set(spec.field, request);
191
+ requestedBudget[spec.budgetKey] = request.specified
192
+ ? spec.normalize(request.raw)
193
+ : null;
194
+ }
195
+ const effectiveBudget = capBudget(requestedBudget, parentBudget) || {
196
+ tokens: null,
197
+ costUsd: null,
198
+ timeMs: null,
199
+ };
200
+
201
+ for (const spec of budgetFields) {
202
+ const request = budgetRequests.get(spec.field);
203
+ if (!request.specified) continue;
204
+ const normalized = requestedBudget[spec.budgetKey];
205
+ const effective = effectiveBudget[spec.budgetKey] ?? null;
206
+ if (normalized == null) {
207
+ addAdjustment(
208
+ adjustments,
209
+ request,
210
+ spec.field,
211
+ effective,
212
+ "invalid-value-inherited",
213
+ );
214
+ } else if (effective !== normalized) {
215
+ addAdjustment(
216
+ adjustments,
217
+ request,
218
+ spec.field,
219
+ effective,
220
+ "parent-budget-ceiling",
221
+ );
222
+ }
223
+ }
224
+
225
+ const resolveRequirement = (field) => {
226
+ const parentRequired = normalizeRequired(defaults[field]) === true;
227
+ const request = requestedField(work, field);
228
+ const requested = request.specified ? normalizeRequired(request.raw) : null;
229
+ const effective =
230
+ requested == null ? parentRequired : parentRequired || requested;
231
+ if (request.specified) {
232
+ if (requested == null) {
233
+ addAdjustment(
234
+ adjustments,
235
+ request,
236
+ field,
237
+ effective,
238
+ "invalid-value-inherited",
239
+ );
240
+ } else if (parentRequired && requested === false) {
241
+ addAdjustment(adjustments, request, field, true, "parent-requirement");
242
+ }
243
+ }
244
+ return effective;
245
+ };
246
+
247
+ const checkpointRequired = resolveRequirement("checkpointRequired");
248
+ const worktreeRequired = resolveRequirement("worktreeRequired");
249
+
250
+ const parentModel = normalizeModel(defaults.model);
251
+ const modelRequest = requestedField(work, "model");
252
+ const requestedModel = modelRequest.specified
253
+ ? normalizeModel(modelRequest.raw)
254
+ : null;
255
+ const model = requestedModel ?? parentModel;
256
+ if (modelRequest.specified && requestedModel == null) {
257
+ addAdjustment(
258
+ adjustments,
259
+ modelRequest,
260
+ "model",
261
+ model,
262
+ "invalid-value-inherited",
263
+ );
264
+ }
265
+
266
+ return {
267
+ permissionMode,
268
+ maxTurns,
269
+ maxBudgetUsd: effectiveBudget.costUsd ?? null,
270
+ maxTokens: effectiveBudget.tokens ?? null,
271
+ maxWallMs: effectiveBudget.timeMs ?? null,
272
+ checkpointRequired,
273
+ worktreeRequired,
274
+ model,
275
+ adjustments,
276
+ };
277
+ }