chainlesschain 0.162.197 → 0.162.198
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-D3Gu1a5d.js → AIOps-CSN-D2xb.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DrwAoKaz.js → ActionButton-CBSMlGzv.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C7Q-gd_3.js → Analytics-3yr3Nt2t.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DgBtae-n.js → AppLayout-1b-o3lMH.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-pQ6LcA-W.js → Artifacts-Dr8rJi17.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DK61tocB.js → Audit-2V689HC2.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BOKgDFRh.js → BackgroundAgents-DIxkNrD_.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Bo9G4vTP.js → Backup-Pg6BcwrE.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Do_dE9_v.js → BaseInput-CLUpqyfH.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-COVvZzOL.js → Chat-qVS8rD4G.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-ti49X12Q.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-Z9MJMolt.js → Checkbox-Bs9AaEay.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BVzWKVbf.js → Codegen-Dvc1mq8q.js} +1 -1
- package/src/assets/web-panel/assets/{Col-C8MnYcbT.js → Col-C4_biO7A.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DQxqw15l.js → Community-al25XQUV.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BAUmrh3y.js → Compact-D-ZDB8M1.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-jPAkQ3zK.js → Compliance-6lIfDz1W.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BBQyxbh8.js → Cowork-DdI48XMJ.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-W5koq8Z5.js → Cron-DF5HZd2g.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BQhnu5hS.js → Crosschain-DuxeSvt3.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DD7PlI18.js → DID-B9yHkUTd.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-w_ILuwKI.js → Dashboard-Z5EuI3IM.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-qiDUAyAo.js → Dropdown-vuRfO3dY.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-PDeBcI5H.js → EmailListRenderer-QFLCc1uL.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-SuArscRM.js → FamilyGuardDashboard-BZ7EXkFJ.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BCovZaUn.js → Federation-DycIEg2s.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DjlFdVFk.js → FormItemContext-Ba0H_7yG.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DoEZ7fcS.js → GenericCardRenderer--JgSTK-C.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CV0-6GPI.js → Git-DEvTbldE.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-B_SFN50w.js → Governance-BJIljy18.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-heI0Eo6k.js → Inference-CVyW3G3q.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Cz5INS5p.js → KnowledgeGraph-v8-borge.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BSFxiHkg.js → Logs-BCdS7gGo.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-CU-JB2Ns.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-0TWTpE7k.js → Marketplace-AN6tGA2B.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CV4-wBLm.js → McpTools-DGViXASN.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-x5bH5rw5.js → Memory-Bpvmx9X4.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DaBmUu9G.js → MobileBridge-CTP-DOTC.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-C8qfktxn.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-B_zKEBif.js → Mtc-ndYr0M1s.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BCfKD2yg.js → MtcAudit-Ci0-YK7W.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DgYfj6c_.js → Multisig-BehSTFW7.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Dz09lFUm.js → NLProgramming-B91_YBLv.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Cuj6ugBE.js → Notes-BQlJ0-gq.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DK2WXjhg.js → NotificationSettings-CDgYMjYK.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-D1IAOQoo.js → OrderTableRenderer-BJkSlKeH.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-9fqnPvTw.js → Organization-B-b3cWCp.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-n5xHkhow.js → Overflow-CILUL4nL.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-B5AUWwH-.js → P2P-BTCFBkF0.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CoUDV6dy.js → PdhVaultBrowser-BAR6AUx5.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-DPRmQf_H.js → Permissions-RRf6HbWJ.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-B37bsbEN.js → PersonalDataHub-BGTMiICa.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CCAuPqu9.js → Pipeline-945qY9YF.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B81msZdv.js → Privacy-DPZwI6lJ.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-B-Py2NbF.js → ProjectInit-BPH2NRKc.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CYsneiRB.js → ProjectSettings-BZCRNCvb.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Y975Mh6R.js → Projects-BTI__tBt.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-uO-10aP1.js → Providers-DDCf_nIw.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Bj_6gJ0l.js → QrScannerModal-N6rbqt1r.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BgpBCMPE.js → QuickAsk-DrwjPS7M.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DhUW3YUA.js → Recommend-Cog9Wd99.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DdeyfbkA.js → RemoteSession-DC9pynjM.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-fxW421xd.js → Reputation-D4th4gKS.js} +1 -1
- package/src/assets/web-panel/assets/{Row-De73w1og.js → Row-ogiWR6v9.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-3US_Q_lj.js → RssFeed-uYlk-2Oj.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DbZ_DU74.js → Search-DYzzwvtM.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DoIU1vMF.js → Security-Da7YOyX6.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DB_RGqpc.js → Services-CLI1VM1X.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DnzLaayC.js → Skeleton-DK77dzDC.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-VzVXHENF.js → Skills-C_z7Lmhl.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DMfvjCAI.js → Sla-D6413Q8L.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DQQOYWbF.js → SpeechSettings-C07ydbj2.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Bmx3FG1P.js → SyncSettings-DGOm2W22.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DOQyfb-2.js → Tasks-Cr_PVyLM.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D6hTRT7c.js → Templates-BhL2mZ0t.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CTPQZAzs.js → Tenant-CPk5oyUr.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B7vII3oH.js → Terminal-ClhqUsqV.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BkiFJeXV.js → TimelineRenderer-BCC7MV5a.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BPPdQMNo.js → Tokens-DD1Jt4oO.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BxG8JAEI.js → Trigger-BCydFvN2.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DHD7nMpM.js → Trust-DDHT1noa.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B4BJpU47.js → UkeySign-CWPZC7oI.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CRiGpFx2.js → VideoEditing-9Ea0nJR0.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BSDSNiem.js → Wallet-BjxhLxTf.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DLTUh5Zv.js → WebAuthn-C6uCye67.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-tW2MwjhD.js → WorkflowEditor-d2g4reYo.js} +1 -1
- package/src/assets/web-panel/assets/{chat-SgsLQ3FC.js → chat-frTc6ScX.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BHjjLIKO.js → colors-B5K6lG3v.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-VceErdKq.js → compact-item-Cdn46r8D.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BZ332MYl.js → createContext-DU1jqVQi.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CP-jEUyw.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CBTjJOjn.js → hasIn-DdNH-aW_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CawpdGTW.js → index-32ajXmTb.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOtvPdHt.js → index-7ehytj1K.js} +1 -1
- package/src/assets/web-panel/assets/{index-vIXOpcjD.js → index-BC4eGkar.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cr4rjlW5.js → index-BVLL-mq_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_SePDWj.js → index-BXKhhx9V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BETEjuNr.js → index-BcGwGIaR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BY1wexYG.js → index-Be4OtL-N.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRWhLbWF.js → index-BoFak7le.js} +1 -1
- package/src/assets/web-panel/assets/{index-29wVzi-k.js → index-BwlIxAmX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BeiqkxJG.js → index-CGiwfwB9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQSmZGgs.js → index-CHmkkdfT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhXZEUAc.js → index-CQorP1Ng.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_B9rFQE.js → index-CUoNAmE8.js} +1 -1
- package/src/assets/web-panel/assets/{index-iYQNU9za.js → index-Cj6P9Nst.js} +1 -1
- package/src/assets/web-panel/assets/{index-DePOeMDM.js → index-CtRPi4YC.js} +1 -1
- package/src/assets/web-panel/assets/index-CzrCHrFy.js +1 -0
- package/src/assets/web-panel/assets/{index-DdefYBSw.js → index-D1qIC4GR.js} +1 -1
- package/src/assets/web-panel/assets/{index-By5jBtkV.js → index-D23xGYjM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMVvxIS8.js → index-D4l82Me1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLc19alH.js → index-D4miNkWv.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2u26AS3.js → index-DIVyTmZ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaCCy6lq.js → index-DM5FmPZU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUkVN794.js → index-DRzBBdQ0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPXFLYdW.js → index-Dca_i7H8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpumS98o.js → index-DftZuyA7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrUgDQkf.js → index-DtBCCkC4.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtjLcCNU.js → index-HjCvXHf5.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaIS_TNX.js → index-LaivuXyF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BC1xuQgX.js → index-MM4Zz2OE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcUenmCX.js → index-NmEXVjzt.js} +1 -1
- package/src/assets/web-panel/assets/index-OJsGskZw.js +1 -0
- package/src/assets/web-panel/assets/{index-_PHrIO4O.js → index-PjAS0sIZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgPvuIn2.js → index-VDM2KKo9.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5KAGxNo.js → index-cYaWV4Mq.js} +1 -1
- package/src/assets/web-panel/assets/{index-B40dQn0G.js → index-gg3QPC3i.js} +3 -3
- package/src/assets/web-panel/assets/{index-BTlc-Bve.js → index-kVO_MDm-.js} +1 -1
- package/src/assets/web-panel/assets/{index-FJ3zC9LK.js → index-mgU3Q-lN.js} +1 -1
- package/src/assets/web-panel/assets/{index-1n7xjKDU.js → index-t_OBzIP4.js} +1 -1
- package/src/assets/web-panel/assets/{index-fU7wNHDB.js → index-yZRuZpwK.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D1oZNz0D.js → initDefaultProps-DwLTRNdo.js} +1 -1
- package/src/assets/web-panel/assets/{motion-C8k6doMO.js → motion-BchZDXnh.js} +1 -1
- package/src/assets/web-panel/assets/{move-DJ4qSbYt.js → move-e8M-zZFl.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CdpJD85j.js → mtc-parser-Ci40fQb3.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B0V4slKA.js → omit-C7110_f2.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Di5NiW1F.js → pickAttrs-skteMgnM.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B5OMHgEM.js → placementArrow-MGZ-lh2B.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-C6U9IWql.js → responsiveObserve-N4Sd7uy-.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BMyTH3rN.js → slide-C7HDGRaa.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-C7d6BG7Z.js → statusUtils-BhRexlgy.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CR50QnQy.js → styleChecker-ChEoQkN2.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DBEeoMb8.js → useFlexGapSupport-CDbG1s2Y.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-3r099cjv.js → useFs-CUIx0tcJ.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-C5IYNdTK.js → usePersonalDataHub-bkO7L7ec.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DmEOLRHf.js → vnode-B_ZvABIG.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DnpUiVCB.js → zoom-D5shjpS8.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +4 -4
- package/src/commands/agent.js +3 -3
- package/src/commands/background-session.js +145 -0
- package/src/commands/mcp.js +125 -7
- package/src/commands/serve.js +1 -1
- package/src/commands/session-show.js +131 -0
- package/src/commands/session.js +50 -106
- package/src/commands/skill.js +87 -50
- package/src/data/changelog.json +14 -1
- package/src/gateways/ws/ws-agent-handler.js +8 -1
- package/src/harness/jsonl-session-store.js +428 -25
- package/src/harness/mcp-client.js +2652 -404
- package/src/harness/session-list-index.js +65 -7
- package/src/lazy-dispatch.js +76 -8
- package/src/lib/abort-utils.js +68 -0
- package/src/lib/agent-core.js +1 -59
- package/src/lib/chat-core.js +24 -6
- package/src/lib/cowork-mcp-tools.js +47 -2
- package/src/lib/cowork-task-runner.js +11 -3
- package/src/lib/doctor-checkup.js +9 -7
- package/src/lib/learning/skill-improver.js +21 -2
- package/src/lib/mcp-call-ledger-store.js +116 -3
- package/src/lib/mcp-call-ledger.js +59 -14
- package/src/lib/mcp-client.js +1 -0
- package/src/lib/mcp-stdio-executable-identity.js +598 -0
- package/src/lib/mcp-stdio-execution-authority.js +363 -0
- package/src/lib/mcp-tool-metadata.js +436 -0
- package/src/lib/mcp-tool-result.js +367 -0
- package/src/lib/observability/index.js +71 -0
- package/src/lib/pr-link-ledger.js +8 -27
- package/src/lib/pr-link-store.js +26 -0
- package/src/lib/process-execution-broker/index.js +14 -0
- package/src/lib/process-tree-termination.js +253 -0
- package/src/lib/session-host-snapshot.js +9 -5
- package/src/lib/session-projection.js +5 -1
- package/src/lib/session-tail.js +31 -1
- package/src/lib/skill-budget.js +222 -0
- package/src/lib/skill-execution-authority.js +381 -0
- package/src/lib/skill-execution-identity.js +39 -2
- package/src/lib/skill-loader.js +322 -86
- package/src/lib/skill-mcp.js +43 -1
- package/src/repl/agent-repl.js +174 -38
- package/src/repl/bg-dashboard.js +81 -28
- package/src/repl/chat-repl.js +77 -12
- package/src/runtime/agent-core.js +340 -108
- package/src/runtime/agent-runtime.js +26 -3
- package/src/runtime/headless-runner.js +150 -23
- package/src/runtime/headless-stream.js +309 -59
- package/src/runtime/mcp-config.js +157 -10
- package/src/runtime/mcp-tool-search.js +0 -0
- package/src/runtime/output-backpressure.js +366 -0
- package/src/runtime/pipe-safety.js +56 -21
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BUUyJ5QO.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-1mNozZ8v.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-DqnoFPgR.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DosMaL-k.js +0 -1
- package/src/assets/web-panel/assets/index-0O5ePnUq.js +0 -1
- package/src/assets/web-panel/assets/index-ChAzPigF.js +0 -1
- package/src/lib/skill-process-broker.js +0 -85
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { executionBroker } from "./process-execution-broker/index.js";
|
|
2
|
+
|
|
3
|
+
export const PROCESS_TREE_CLEANUP_MAX_MS = 2_000;
|
|
4
|
+
export const PROCESS_TREE_GRACE_MAX_MS = 500;
|
|
5
|
+
const TREE_MODES = new Set(["sandbox", "posix-group", "windows-tree"]);
|
|
6
|
+
|
|
7
|
+
function boundedMilliseconds(value, fallback, maximum) {
|
|
8
|
+
const parsed = Number(value);
|
|
9
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
10
|
+
return Math.max(1, Math.min(maximum, Math.floor(parsed)));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function validChildPid(child) {
|
|
14
|
+
const pid = Number(child?.pid);
|
|
15
|
+
return Number.isInteger(pid) && pid > 0 && pid !== process.pid ? pid : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function childHasClosed(child, observedClose) {
|
|
19
|
+
return (
|
|
20
|
+
observedClose() ||
|
|
21
|
+
(child?.exitCode !== null && child?.exitCode !== undefined) ||
|
|
22
|
+
(child?.signalCode !== null && child?.signalCode !== undefined)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function posixGroupIsGone(pid, kill) {
|
|
27
|
+
try {
|
|
28
|
+
kill(-pid, 0);
|
|
29
|
+
return false;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return error?.code === "ESRCH";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function requestDirectSignal(child, signal) {
|
|
36
|
+
try {
|
|
37
|
+
return child?.kill?.(signal) !== false;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function requestPosixGroupSignal(child, pid, signal, kill) {
|
|
44
|
+
try {
|
|
45
|
+
kill(-pid, signal);
|
|
46
|
+
return { requested: true, treeRequested: true };
|
|
47
|
+
} catch {
|
|
48
|
+
return {
|
|
49
|
+
requested: requestDirectSignal(child, signal),
|
|
50
|
+
treeRequested: false,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function requestWindowsTreeSignal(child, pid, force, spawnSync, timeoutMs) {
|
|
56
|
+
try {
|
|
57
|
+
const args = ["/PID", String(pid), "/T"];
|
|
58
|
+
if (force) args.push("/F");
|
|
59
|
+
const result = spawnSync("taskkill", args, {
|
|
60
|
+
shell: false,
|
|
61
|
+
windowsHide: true,
|
|
62
|
+
encoding: "utf8",
|
|
63
|
+
timeout: Math.max(1, timeoutMs),
|
|
64
|
+
origin: "process-tree:taskkill",
|
|
65
|
+
policy: "allow",
|
|
66
|
+
scope: "process-tree",
|
|
67
|
+
});
|
|
68
|
+
if (!result?.error && result?.status === 0) {
|
|
69
|
+
return { requested: true, treeRequested: true };
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// Fall through to the hard-phase fallback below.
|
|
73
|
+
}
|
|
74
|
+
// Never kill only the Windows root during the soft phase: doing so can
|
|
75
|
+
// orphan its descendants and make the later `/T /F` walk impossible.
|
|
76
|
+
if (!force) return { requested: false, treeRequested: false };
|
|
77
|
+
return {
|
|
78
|
+
requested: requestDirectSignal(child, "SIGKILL"),
|
|
79
|
+
treeRequested: false,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Retire one Broker-owned child tree inside a host-capped deadline.
|
|
85
|
+
*
|
|
86
|
+
* `treeMode` describes the ownership established at spawn time:
|
|
87
|
+
* - `sandbox`: a PID namespace / Windows Job close fence owns descendants;
|
|
88
|
+
* - `posix-group`: the child is the leader of a detached process group;
|
|
89
|
+
* - `windows-tree`: taskkill walks the live root's descendant tree.
|
|
90
|
+
*
|
|
91
|
+
* The function never throws and never treats a direct-child fallback as proof
|
|
92
|
+
* that descendants are gone. Callers can preserve their primary error while
|
|
93
|
+
* attaching the structured result, or fail closed on an unconfirmed explicit
|
|
94
|
+
* disconnect.
|
|
95
|
+
*/
|
|
96
|
+
export async function terminateOwnedProcessTree(child, options = {}) {
|
|
97
|
+
const platform = options.platform || process.platform;
|
|
98
|
+
const cleanupTimeoutMs = boundedMilliseconds(
|
|
99
|
+
options.cleanupTimeoutMs,
|
|
100
|
+
PROCESS_TREE_CLEANUP_MAX_MS,
|
|
101
|
+
PROCESS_TREE_CLEANUP_MAX_MS,
|
|
102
|
+
);
|
|
103
|
+
const graceMs = Math.min(
|
|
104
|
+
cleanupTimeoutMs,
|
|
105
|
+
boundedMilliseconds(options.graceMs, 250, PROCESS_TREE_GRACE_MAX_MS),
|
|
106
|
+
);
|
|
107
|
+
const now = options.now || Date.now;
|
|
108
|
+
const sleep =
|
|
109
|
+
options.sleep ||
|
|
110
|
+
((milliseconds) =>
|
|
111
|
+
new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
112
|
+
const kill = options.kill || process.kill.bind(process);
|
|
113
|
+
const spawnSync =
|
|
114
|
+
options.spawnSync || executionBroker.spawnSync.bind(executionBroker);
|
|
115
|
+
const pid = validChildPid(child);
|
|
116
|
+
const inferredTreeMode =
|
|
117
|
+
options.treeMode ||
|
|
118
|
+
(options.sandboxManagedTree === true
|
|
119
|
+
? "sandbox"
|
|
120
|
+
: platform === "win32"
|
|
121
|
+
? "windows-tree"
|
|
122
|
+
: "posix-group");
|
|
123
|
+
const treeMode = TREE_MODES.has(inferredTreeMode)
|
|
124
|
+
? inferredTreeMode
|
|
125
|
+
: platform === "win32"
|
|
126
|
+
? "windows-tree"
|
|
127
|
+
: "posix-group";
|
|
128
|
+
const startedAt = now();
|
|
129
|
+
const deadline = startedAt + cleanupTimeoutMs;
|
|
130
|
+
let closeObserved = options.alreadyClosed === true;
|
|
131
|
+
let softRequested = false;
|
|
132
|
+
let hardRequested = false;
|
|
133
|
+
let treeRequested = false;
|
|
134
|
+
let escalated = false;
|
|
135
|
+
|
|
136
|
+
const onClose = () => {
|
|
137
|
+
closeObserved = true;
|
|
138
|
+
};
|
|
139
|
+
child?.once?.("close", onClose);
|
|
140
|
+
|
|
141
|
+
const remaining = () => Math.max(0, deadline - now());
|
|
142
|
+
const closed = () => childHasClosed(child, () => closeObserved);
|
|
143
|
+
const treeGone = () => {
|
|
144
|
+
if (!pid) return false;
|
|
145
|
+
if (treeMode === "sandbox") return closed();
|
|
146
|
+
if (treeMode === "posix-group") return posixGroupIsGone(pid, kill);
|
|
147
|
+
return treeRequested;
|
|
148
|
+
};
|
|
149
|
+
const confirmed = () => closed() && treeGone();
|
|
150
|
+
const waitForConfirmation = async (maximumMs) => {
|
|
151
|
+
const phaseDeadline = Math.min(deadline, now() + Math.max(0, maximumMs));
|
|
152
|
+
while (!confirmed() && now() < phaseDeadline) {
|
|
153
|
+
await sleep(Math.max(1, Math.min(10, phaseDeadline - now())));
|
|
154
|
+
}
|
|
155
|
+
return confirmed();
|
|
156
|
+
};
|
|
157
|
+
const request = (force) => {
|
|
158
|
+
if (!pid) {
|
|
159
|
+
return {
|
|
160
|
+
requested: requestDirectSignal(child, force ? "SIGKILL" : "SIGTERM"),
|
|
161
|
+
treeRequested: false,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (treeMode === "sandbox") {
|
|
165
|
+
return {
|
|
166
|
+
requested: requestDirectSignal(child, force ? "SIGKILL" : "SIGTERM"),
|
|
167
|
+
treeRequested: true,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (treeMode === "windows-tree") {
|
|
171
|
+
return requestWindowsTreeSignal(
|
|
172
|
+
child,
|
|
173
|
+
pid,
|
|
174
|
+
force,
|
|
175
|
+
spawnSync,
|
|
176
|
+
remaining(),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return requestPosixGroupSignal(
|
|
180
|
+
child,
|
|
181
|
+
pid,
|
|
182
|
+
force ? "SIGKILL" : "SIGTERM",
|
|
183
|
+
kill,
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
if (!pid) {
|
|
189
|
+
// Test doubles and partial spawn failures have no verifiable tree
|
|
190
|
+
// identity. Preserve legacy best-effort direct-child cleanup without
|
|
191
|
+
// upgrading it to a whole-tree claim.
|
|
192
|
+
const attempt = request(true);
|
|
193
|
+
hardRequested = attempt.requested;
|
|
194
|
+
return {
|
|
195
|
+
pid: null,
|
|
196
|
+
treeMode,
|
|
197
|
+
verifiable: false,
|
|
198
|
+
softRequested,
|
|
199
|
+
hardRequested,
|
|
200
|
+
treeRequested: false,
|
|
201
|
+
escalated: true,
|
|
202
|
+
closed: closed(),
|
|
203
|
+
treeTerminated: false,
|
|
204
|
+
confirmed: false,
|
|
205
|
+
deadlineExceeded: false,
|
|
206
|
+
elapsedMs: Math.max(0, now() - startedAt),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// A managed sandbox close fence already proves its namespace/job is empty.
|
|
211
|
+
// A closed unmanaged POSIX root does not: descendants in the owned group
|
|
212
|
+
// must still be signalled and probed.
|
|
213
|
+
if (treeMode === "sandbox" && closed()) {
|
|
214
|
+
treeRequested = true;
|
|
215
|
+
} else if (!closed()) {
|
|
216
|
+
try {
|
|
217
|
+
child?.stdin?.end?.();
|
|
218
|
+
} catch {
|
|
219
|
+
// EOF is only an additional graceful hint; SIGTERM remains authoritative.
|
|
220
|
+
}
|
|
221
|
+
const attempt = request(false);
|
|
222
|
+
softRequested = attempt.requested;
|
|
223
|
+
treeRequested ||= attempt.treeRequested;
|
|
224
|
+
await waitForConfirmation(graceMs);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!confirmed()) {
|
|
228
|
+
escalated = true;
|
|
229
|
+
const attempt = request(true);
|
|
230
|
+
hardRequested = attempt.requested;
|
|
231
|
+
treeRequested ||= attempt.treeRequested;
|
|
232
|
+
if (remaining() > 0) await waitForConfirmation(remaining());
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const didConfirm = confirmed();
|
|
236
|
+
return {
|
|
237
|
+
pid,
|
|
238
|
+
treeMode,
|
|
239
|
+
verifiable: true,
|
|
240
|
+
softRequested,
|
|
241
|
+
hardRequested,
|
|
242
|
+
treeRequested,
|
|
243
|
+
escalated,
|
|
244
|
+
closed: closed(),
|
|
245
|
+
treeTerminated: treeGone(),
|
|
246
|
+
confirmed: didConfirm,
|
|
247
|
+
deadlineExceeded: !didConfirm && remaining() === 0,
|
|
248
|
+
elapsedMs: Math.max(0, now() - startedAt),
|
|
249
|
+
};
|
|
250
|
+
} finally {
|
|
251
|
+
child?.removeListener?.("close", onClose);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
projectWsTurnMessages,
|
|
15
15
|
readVerifiedEvents as storeReadVerifiedEvents,
|
|
16
16
|
readVerifiedProjection as storeReadVerifiedProjection,
|
|
17
|
-
|
|
17
|
+
sessionHasPersistedEvidence as storeSessionHasPersistedEvidence,
|
|
18
18
|
} from "../harness/jsonl-session-store.js";
|
|
19
19
|
import {
|
|
20
20
|
createMcpLedgerEventReducer,
|
|
@@ -344,12 +344,16 @@ function createStreamingSessionHostProjection(sessionId) {
|
|
|
344
344
|
/**
|
|
345
345
|
* Read one real JSONL session for a host resume/attach boundary.
|
|
346
346
|
*
|
|
347
|
-
* `null` means no JSONL
|
|
348
|
-
* A
|
|
349
|
-
* and no raw messages, so a host cannot silently
|
|
347
|
+
* `null` means no canonical JSONL evidence exists and callers may use their
|
|
348
|
+
* legacy store. A tombstoned, missing, damaged, or unanchored transcript
|
|
349
|
+
* returns a fail-closed snapshot and no raw messages, so a host cannot silently
|
|
350
|
+
* fall back to stale history or implicitly recreate a deleted generation.
|
|
350
351
|
*/
|
|
351
352
|
export function readSessionHostResumeState(sessionId, dependencies = {}) {
|
|
352
|
-
const exists =
|
|
353
|
+
const exists =
|
|
354
|
+
dependencies.sessionHasPersistedEvidence ||
|
|
355
|
+
dependencies.sessionExists ||
|
|
356
|
+
storeSessionHasPersistedEvidence;
|
|
353
357
|
const hasCustomLegacyReader =
|
|
354
358
|
typeof dependencies.readVerifiedEvents === "function" &&
|
|
355
359
|
dependencies.readVerifiedEvents !== storeReadVerifiedEvents;
|
|
@@ -260,7 +260,11 @@ function actionPreviews(kind, state, source, sourceId, linkedSessionId) {
|
|
|
260
260
|
);
|
|
261
261
|
}
|
|
262
262
|
if (active && interactive) {
|
|
263
|
-
routes.reply = preview(
|
|
263
|
+
routes.reply = preview(
|
|
264
|
+
"cli",
|
|
265
|
+
["daemon", "reply", sourceId, PROJECTION_PROMPT_PLACEHOLDER, "--json"],
|
|
266
|
+
{ mutates: true, input: "prompt" },
|
|
267
|
+
);
|
|
264
268
|
routes.attach = preview("terminal", ["attach", sourceId]);
|
|
265
269
|
}
|
|
266
270
|
if (active) {
|
package/src/lib/session-tail.js
CHANGED
|
@@ -10,7 +10,31 @@
|
|
|
10
10
|
|
|
11
11
|
import { promises as fsp } from "node:fs";
|
|
12
12
|
import { existsSync, statSync } from "node:fs";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getSessionPresence,
|
|
15
|
+
SESSION_PRESENCE,
|
|
16
|
+
sessionPath,
|
|
17
|
+
} from "../harness/jsonl-session-store.js";
|
|
18
|
+
|
|
19
|
+
function assertLiveTranscriptReadable(sessionId) {
|
|
20
|
+
const presence = getSessionPresence(sessionId);
|
|
21
|
+
if (
|
|
22
|
+
presence === SESSION_PRESENCE.PRESENT ||
|
|
23
|
+
presence === SESSION_PRESENCE.ABSENT
|
|
24
|
+
) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const error = new Error(
|
|
28
|
+
presence === SESSION_PRESENCE.TOMBSTONED
|
|
29
|
+
? `Session ${sessionId} has been deleted`
|
|
30
|
+
: `Session ${sessionId} transcript cannot be verified for live tailing`,
|
|
31
|
+
);
|
|
32
|
+
error.code =
|
|
33
|
+
presence === SESSION_PRESENCE.TOMBSTONED
|
|
34
|
+
? "SESSION_DELETED"
|
|
35
|
+
: "SESSION_TRANSCRIPT_UNVERIFIED";
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
14
38
|
|
|
15
39
|
function parseLine(line) {
|
|
16
40
|
const trimmed = line.trim();
|
|
@@ -53,6 +77,7 @@ function matchesFilter(event, { types, sinceMs }) {
|
|
|
53
77
|
* - fromEnd (default): current EOF, so only new events are yielded
|
|
54
78
|
*/
|
|
55
79
|
export function initialOffset(sessionId, { fromStart = false } = {}) {
|
|
80
|
+
assertLiveTranscriptReadable(sessionId);
|
|
56
81
|
const p = sessionPath(sessionId);
|
|
57
82
|
if (!existsSync(p)) return 0;
|
|
58
83
|
if (fromStart) return 0;
|
|
@@ -98,6 +123,11 @@ export async function* followSession(sessionId, options = {}) {
|
|
|
98
123
|
while (true) {
|
|
99
124
|
if (signal?.aborted) return;
|
|
100
125
|
|
|
126
|
+
// A long-lived tail is a live canonical reader. Re-check the durable
|
|
127
|
+
// sidecar witness on every poll so external deletion or a tombstoned path
|
|
128
|
+
// restoration cannot silently turn into empty output or stale replay.
|
|
129
|
+
assertLiveTranscriptReadable(sessionId);
|
|
130
|
+
|
|
101
131
|
if (existsSync(filePath)) {
|
|
102
132
|
const stat = await fsp.stat(filePath);
|
|
103
133
|
if (stat.size < offset) {
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-owned admission limits for file-backed CLI skills.
|
|
3
|
+
*
|
|
4
|
+
* These are security ceilings, not defaults that callers may raise. Loader
|
|
5
|
+
* options can only tighten them through resolveSkillLimits().
|
|
6
|
+
*/
|
|
7
|
+
export const HOST_SKILL_LIMITS = Object.freeze({
|
|
8
|
+
maxSkillFileBytes: 256 * 1024,
|
|
9
|
+
maxSkillTotalBytes: 512 * 1024,
|
|
10
|
+
maxSkillDiscoveryEntries: 16_384,
|
|
11
|
+
maxSkillDiscoveryFiles: 4096,
|
|
12
|
+
maxSkillDiscoveryBytes: 64 * 1024 * 1024,
|
|
13
|
+
maxSkillNestDepth: 5,
|
|
14
|
+
maxSkillPromptBytes: 48_000,
|
|
15
|
+
// Provider-neutral conservative ceiling: one token cannot require fewer
|
|
16
|
+
// than zero bytes, and byte-level tokenizers cannot produce more tokens than
|
|
17
|
+
// the UTF-8 byte sequence. Hosts with a smaller context may tighten this.
|
|
18
|
+
maxSkillPromptTokens: 48_000,
|
|
19
|
+
maxSkillPromptTotalBytes: 192_000,
|
|
20
|
+
maxSkillPromptTotalTokens: 192_000,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const LIMIT_KEYS = Object.freeze(Object.keys(HOST_SKILL_LIMITS));
|
|
24
|
+
|
|
25
|
+
function tighteningLimit(value, hardLimit) {
|
|
26
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
27
|
+
return hardLimit;
|
|
28
|
+
}
|
|
29
|
+
return Math.min(hardLimit, Math.max(1, Math.floor(value)));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolveSkillLimits(options = {}) {
|
|
33
|
+
const limits = {};
|
|
34
|
+
for (const key of LIMIT_KEYS) {
|
|
35
|
+
limits[key] = tighteningLimit(options?.[key], HOST_SKILL_LIMITS[key]);
|
|
36
|
+
}
|
|
37
|
+
return Object.freeze(limits);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function skillBudgetError(code, message, details = {}) {
|
|
41
|
+
const error = new Error(message);
|
|
42
|
+
error.name = "SkillBudgetError";
|
|
43
|
+
error.code = code;
|
|
44
|
+
if (Number.isFinite(details.limit)) error.limit = details.limit;
|
|
45
|
+
if (Number.isFinite(details.actual)) error.actual = details.actual;
|
|
46
|
+
if (details.component) error.component = details.component;
|
|
47
|
+
return error;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isSkillBudgetError(error) {
|
|
51
|
+
return error?.name === "SkillBudgetError";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function assertSkillFileSize(component, size, limitsInput = {}) {
|
|
55
|
+
const limits = resolveSkillLimits(limitsInput);
|
|
56
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
57
|
+
throw skillBudgetError(
|
|
58
|
+
"CC_SKILL_FILE_SIZE_INVALID",
|
|
59
|
+
`Skill component size is invalid: ${component}`,
|
|
60
|
+
{ component },
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (size > limits.maxSkillFileBytes) {
|
|
64
|
+
throw skillBudgetError(
|
|
65
|
+
"CC_SKILL_FILE_TOO_LARGE",
|
|
66
|
+
`Skill component exceeds the host byte limit: ${component}`,
|
|
67
|
+
{ component, limit: limits.maxSkillFileBytes, actual: size },
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return size;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function assertSkillTotalSize(size, limitsInput = {}) {
|
|
74
|
+
const limits = resolveSkillLimits(limitsInput);
|
|
75
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
76
|
+
throw skillBudgetError(
|
|
77
|
+
"CC_SKILL_TOTAL_SIZE_INVALID",
|
|
78
|
+
"Skill component total is invalid.",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (size > limits.maxSkillTotalBytes) {
|
|
82
|
+
throw skillBudgetError(
|
|
83
|
+
"CC_SKILL_TOTAL_BYTES_EXCEEDED",
|
|
84
|
+
"Skill components exceed the host aggregate byte limit.",
|
|
85
|
+
{ limit: limits.maxSkillTotalBytes, actual: size },
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return size;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createSkillDiscoveryBudget(limitsInput = {}) {
|
|
92
|
+
return {
|
|
93
|
+
limits: resolveSkillLimits(limitsInput),
|
|
94
|
+
entries: 0,
|
|
95
|
+
files: 0,
|
|
96
|
+
bytes: 0,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function debitSkillDiscoveryEntry(budget) {
|
|
101
|
+
const nextEntries = budget.entries + 1;
|
|
102
|
+
if (
|
|
103
|
+
!Number.isSafeInteger(nextEntries) ||
|
|
104
|
+
nextEntries > budget.limits.maxSkillDiscoveryEntries
|
|
105
|
+
) {
|
|
106
|
+
throw skillBudgetError(
|
|
107
|
+
"CC_SKILL_DISCOVERY_ENTRIES_EXCEEDED",
|
|
108
|
+
"Recursive skill discovery exceeds the host directory-entry limit.",
|
|
109
|
+
{
|
|
110
|
+
limit: budget.limits.maxSkillDiscoveryEntries,
|
|
111
|
+
actual: nextEntries,
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
budget.entries = nextEntries;
|
|
116
|
+
return budget;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function debitSkillDiscoveryBudget(budget, fileCount, byteCount) {
|
|
120
|
+
const nextFiles = budget.files + fileCount;
|
|
121
|
+
const nextBytes = budget.bytes + byteCount;
|
|
122
|
+
if (
|
|
123
|
+
!Number.isSafeInteger(nextFiles) ||
|
|
124
|
+
nextFiles > budget.limits.maxSkillDiscoveryFiles
|
|
125
|
+
) {
|
|
126
|
+
throw skillBudgetError(
|
|
127
|
+
"CC_SKILL_DISCOVERY_FILES_EXCEEDED",
|
|
128
|
+
"Recursive skill discovery exceeds the host file-count limit.",
|
|
129
|
+
{
|
|
130
|
+
limit: budget.limits.maxSkillDiscoveryFiles,
|
|
131
|
+
actual: nextFiles,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (
|
|
136
|
+
!Number.isSafeInteger(nextBytes) ||
|
|
137
|
+
nextBytes > budget.limits.maxSkillDiscoveryBytes
|
|
138
|
+
) {
|
|
139
|
+
throw skillBudgetError(
|
|
140
|
+
"CC_SKILL_DISCOVERY_BYTES_EXCEEDED",
|
|
141
|
+
"Recursive skill discovery exceeds the host aggregate byte limit.",
|
|
142
|
+
{
|
|
143
|
+
limit: budget.limits.maxSkillDiscoveryBytes,
|
|
144
|
+
actual: nextBytes,
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
budget.files = nextFiles;
|
|
149
|
+
budget.bytes = nextBytes;
|
|
150
|
+
return budget;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function measureSkillPrompt(body) {
|
|
154
|
+
if (typeof body !== "string") {
|
|
155
|
+
throw skillBudgetError(
|
|
156
|
+
"CC_SKILL_PROMPT_INVALID",
|
|
157
|
+
"Skill instructions must be a string before model projection.",
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const bytes = Buffer.byteLength(body, "utf8");
|
|
161
|
+
return Object.freeze({
|
|
162
|
+
bytes,
|
|
163
|
+
// Conservative provider-neutral upper bound for text-derived tokens. This
|
|
164
|
+
// intentionally does not trust a configurable estimator that could return
|
|
165
|
+
// zero or NaN.
|
|
166
|
+
tokenUpperBound: bytes,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function admitSkillPrompt(body, limitsInput = {}) {
|
|
171
|
+
const limits = resolveSkillLimits(limitsInput);
|
|
172
|
+
const measured = measureSkillPrompt(body);
|
|
173
|
+
if (measured.bytes > limits.maxSkillPromptBytes) {
|
|
174
|
+
throw skillBudgetError(
|
|
175
|
+
"CC_SKILL_PROMPT_BYTES_EXCEEDED",
|
|
176
|
+
"Skill instructions exceed the host model-projection byte limit.",
|
|
177
|
+
{ limit: limits.maxSkillPromptBytes, actual: measured.bytes },
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (measured.tokenUpperBound > limits.maxSkillPromptTokens) {
|
|
181
|
+
throw skillBudgetError(
|
|
182
|
+
"CC_SKILL_PROMPT_TOKENS_EXCEEDED",
|
|
183
|
+
"Skill instructions exceed the host model-projection token limit.",
|
|
184
|
+
{ limit: limits.maxSkillPromptTokens, actual: measured.tokenUpperBound },
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return measured;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function debitSkillPromptBudget(budget, measured) {
|
|
191
|
+
const nextBytes = budget.bytes + measured.bytes;
|
|
192
|
+
const nextTokens = budget.tokens + measured.tokenUpperBound;
|
|
193
|
+
if (
|
|
194
|
+
!Number.isSafeInteger(nextBytes) ||
|
|
195
|
+
nextBytes > budget.limits.maxSkillPromptTotalBytes
|
|
196
|
+
) {
|
|
197
|
+
throw skillBudgetError(
|
|
198
|
+
"CC_SKILL_PROMPT_TOTAL_BYTES_EXCEEDED",
|
|
199
|
+
"Skill instructions exceed the host aggregate model-projection byte limit.",
|
|
200
|
+
{
|
|
201
|
+
limit: budget.limits.maxSkillPromptTotalBytes,
|
|
202
|
+
actual: nextBytes,
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
if (
|
|
207
|
+
!Number.isSafeInteger(nextTokens) ||
|
|
208
|
+
nextTokens > budget.limits.maxSkillPromptTotalTokens
|
|
209
|
+
) {
|
|
210
|
+
throw skillBudgetError(
|
|
211
|
+
"CC_SKILL_PROMPT_TOTAL_TOKENS_EXCEEDED",
|
|
212
|
+
"Skill instructions exceed the host aggregate model-projection token limit.",
|
|
213
|
+
{
|
|
214
|
+
limit: budget.limits.maxSkillPromptTotalTokens,
|
|
215
|
+
actual: nextTokens,
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
budget.bytes = nextBytes;
|
|
220
|
+
budget.tokens = nextTokens;
|
|
221
|
+
return budget;
|
|
222
|
+
}
|