chainlesschain 0.166.2 → 0.166.4
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/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-BnZ24WRk.js → AIOps-HcOh0ZbH.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-ivXY7MGb.js → ActionButton-DWjDqBhd.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BQS4OD-k.js → Analytics-C6FS-y3W.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-wOBuOKTO.js → AppLayout-DCN5cldF.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BYuC4nw-.js → Artifacts-Crd-KE8P.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BIeVfBt6.js → Audit-BexuTQZc.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-hIuyhmJ7.js → BackgroundAgents-Dydkh-b5.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B6ohdgPQ.js → Backup-BYq21wMP.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BzgYCw6h.js → BaseInput-CmNZP6yM.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CwahvZU1.js → Chat-J6bD0zCl.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-B-1IZ_k8.js → ChatBubbleRenderer-MEoaygnL.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-26EDP2x0.js → Checkbox-Ce-9vNmK.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DYLw1Zvd.js → Codegen-zf7-Y-Eh.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DH_zAvD-.js → Col-D-TkY0FD.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DURrTXJu.js → Community-l300NA-1.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CQyBqN0p.js → Compact-D-9Q_hxc.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-a0DfgKHD.js → Compliance-DAJ8lLiQ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-C6fY_5Ke.js → Cowork-xmzZ967S.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DOwmpbTe.js → Cron-CeGqO5W4.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DEPOIEUs.js → Crosschain-DXN0AE-4.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DaKc-CEV.js → DID-CtIKASPr.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C-0KLXq5.js → Dashboard-DqRwT8NX.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CnsDukzr.js → Dropdown-ddIVljO1.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bi_ACD4N.js → EmailListRenderer-CcsFzV4y.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BZASpnx3.js → FamilyGuardDashboard-PnlAUGcG.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-43oTtzyp.js → Federation-BlmLuQrL.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-oyVadnkZ.js → FormItemContext-dDovBj1Y.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Cwvfd6TZ.js → GenericCardRenderer-DTvGshFr.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Cgi2wvU0.js → Git-DuFR2xgA.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-l8TWDKZ0.js → Governance-9EaNcJuq.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Dc8j6TaO.js → Inference-B-iYWH_r.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DgcO-Fwz.js → KnowledgeGraph-BGBaKiLX.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DjiZpiwa.js → Logs-G3wmXzfa.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-B5MFCi0N.js → MarkdownRenderer-hk0YwjTR.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BeDn1hFc.js → Marketplace-BNARBeAv.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-PGdVfWK6.js → McpTools-DST2cSSK.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-e76lY3a1.js → Memory-piwDLsVH.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DKlQqot_.js → MobileBridge-D-W8zHdm.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-C4g6WOEr.js → MobileProjects-CwaYHlVP.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-1j3NpRQq.js → Mtc-C2dxnN7Q.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-DgwiVd1C.js → MtcAudit-BwPZsV3N.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Dzg5lZZY.js → Multisig-CUt8w2al.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D7GYhst-.js → NLProgramming-DwxErx27.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Blcfvaic.js → Notes-B-mXAZ84.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Dc4Y6vfr.js → NotificationSettings-Bd1fBAzd.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CCInnavw.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DBXq024L.js → Organization-DTTONYZT.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CFPbdx1Y.js → Overflow-H6lCZ1rE.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-w7z61NR5.js → P2P-DyBZzeYW.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-B7eGGZOa.js → PdhVaultBrowser-jj7LhT04.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Df5Cl50D.js → Permissions-B5nSQwsr.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C-We7RF6.js → PersonalDataHub-CACaQtlY.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-B6fHPnKy.js → Pipeline-5y-DR_Bw.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B85BdiIc.js → Privacy-BMSA3Ehd.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CcOyazSe.js → ProjectInit-VCOj1dTa.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BE53KfuU.js → ProjectSettings-CJaXO_iE.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DOvMOoe_.js → Projects-C21-MQ3H.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-5l7sT9lq.js → Providers-DZ1FNKUR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DE37RXy4.js → QrScannerModal-6DKc9UBX.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BGmGCo7j.js → QuickAsk-Dyd0Ckrk.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BFdx8Nrm.js → Recommend-CYW4-GJH.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C1w-j_hB.js → RemoteSession-DLrfPy4F.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-BOvn-10y.js → Reputation-DfqPo1FI.js} +1 -1
- package/src/assets/web-panel/assets/{Row-db-a1YO9.js → Row-CR89H2x5.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DeS43ZGM.js → RssFeed-B9QY7R-F.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BaQYNfeb.js → Search-CUDnzu4w.js} +1 -1
- package/src/assets/web-panel/assets/{Security-yt1ESHDg.js → Security-CJ0CtM_Z.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BcNqPUsS.js → Services-CyrQ6F5U.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-X3ceUQWf.js → Skeleton-DcwnKatQ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DmYeax6C.js → Skills-BhKkYF3_.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C_fdfbuJ.js → Sla-CWh-awh7.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CDqohXXQ.js → SpeechSettings-BaUMNzx7.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B25maYEY.js → SyncSettings-D1Ijwwxl.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-DFqjHi3T.js +1 -0
- package/src/assets/web-panel/assets/{Templates-DUH0iuc4.js → Templates-Ct-9_ki3.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DrvhHf9T.js → Tenant-zBkgyilh.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-C2lJVYHA.js → Terminal-VVO2Hjfi.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C1Hf_jYn.js → TimelineRenderer-CX9pIkx3.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DniS1NpS.js → Tokens-DHaD6e_m.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-4jrcb8oA.js → Trigger-CFq9j3WV.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DiBte55w.js → Trust-DuF7gIYX.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CZRtc4VB.js → UkeySign-DbuidXJX.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BtYGJDmg.js → VideoEditing-B8IA9_qE.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-D1nrTm7H.js → Wallet-DdCglSIK.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DI91acpx.js → WebAuthn-CeteRsA8.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-BtQjI50d.js → WorkflowEditor-DJhIbG__.js} +1 -1
- package/src/assets/web-panel/assets/{chat-I4A-f4FC.js → chat-srOi_Wef.js} +1 -1
- package/src/assets/web-panel/assets/{colors-QZkDkFVt.js → colors-HpVHsNGK.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item--FtCdzpr.js → compact-item-DsLhrqWe.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CaIKIKxP.js → createContext-aOasIUuE.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-B_acr36n.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLYUII6-.js → hasIn-D5BVMimS.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGLHSCHM.js → index-B615KD2o.js} +1 -1
- package/src/assets/web-panel/assets/{index-3rVtgqa2.js → index-BCp70IlO.js} +1 -1
- package/src/assets/web-panel/assets/{index-Deg2S9ZJ.js → index-BD64r0Tx.js} +1 -1
- package/src/assets/web-panel/assets/{index-yayaayEA.js → index-BGBvkNN3.js} +1 -1
- package/src/assets/web-panel/assets/{index-B65QxJaG.js → index-BNbsjeEc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxPoPofq.js → index-BTwDhhvl.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3PnIEOH.js → index-BigtYchO.js} +1 -1
- package/src/assets/web-panel/assets/index-BnLYpdox.js +1 -0
- package/src/assets/web-panel/assets/{index-BLGJmgmx.js → index-Bu6TVdgQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CL4c5D6G.js → index-C4tPtHU0.js} +1 -1
- package/src/assets/web-panel/assets/{index-Du2wUavy.js → index-CGVHW0nF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DA_TPdzX.js → index-CQEoGqMG.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ch0IFpGy.js → index-CQXp_uNd.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBtII-nl.js → index-CSxJ4oRd.js} +1 -1
- package/src/assets/web-panel/assets/{index-21I79XSf.js → index-CUUs12L9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAuf8r5o.js → index-CVp_dHCs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bs0qPPFb.js → index-ChGUPEwS.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVitI3UQ.js → index-CvhVCHk-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DK2r6Bv7.js → index-D1xZMQrN.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ooo_5jHo.js → index-D2XRXkZU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJrd_CR3.js → index-DQ5j1VKg.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXaeZCRP.js → index-DRCIPsdm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQ7OBHka.js → index-DV4y5rUj.js} +1 -1
- package/src/assets/web-panel/assets/{index-eTqL91aV.js → index-DcLBzIiI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CisYjy88.js → index-DiXsis7m.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4CXFU-w.js → index-Dpj-rEoV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BebTT7iR.js → index-Dum_r_oY.js} +1 -1
- package/src/assets/web-panel/assets/index-Dzmwsl_v.js +1 -0
- package/src/assets/web-panel/assets/{index-vfiDxdN2.js → index-LSz0ZOB8.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ygi2rYL1.js → index-Ow3SRXRX.js} +1 -1
- package/src/assets/web-panel/assets/{index-hfiZ-i2b.js → index-P6IMLmLT.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgMm85BY.js → index-V0gpVTbS.js} +1 -1
- package/src/assets/web-panel/assets/{index-0DYBQ-6e.js → index-b0Y0hDhX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGWSmE3G.js → index-dPFzlII1.js} +1 -1
- package/src/assets/web-panel/assets/{index-OQPIS_am.js → index-fZnlPk7H.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkJgWW_L.js → index-kYqi6zYp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvdwNnHC.js → index-lFOfvZDn.js} +3 -3
- package/src/assets/web-panel/assets/{index-BcgtvJbC.js → index-q--LtRh9.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2xETCkj.js → index-vQ6jy8Ew.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DDkTvw8k.js → initDefaultProps-1NU9yNpr.js} +1 -1
- package/src/assets/web-panel/assets/{motion-fkr96tN5.js → motion-BZk2hQ6c.js} +1 -1
- package/src/assets/web-panel/assets/{move-CGBV0SV2.js → move-BNvublME.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DAsCliFt.js → mtc-parser-DWk5BUoL.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BM8PMmKN.js → omit-DztYxX3x.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-C6SdWoTK.js → pickAttrs-DURL8mtg.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BG0EVu-d.js → placementArrow-B820R56z.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-L10IvfYC.js → responsiveObserve-DUAkGJL1.js} +1 -1
- package/src/assets/web-panel/assets/{slide-TddIkbrn.js → slide-uZQeNSo6.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-FlveF2Yn.js → statusUtils-CGR1wFVq.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BrXRyebg.js → styleChecker-BIIPOMsQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-B7_oiDXZ.js → useFlexGapSupport-rIB9c0g2.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CQYeRYWb.js → useFs-CrJe1BJi.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DspIkhVi.js → usePersonalDataHub-DyEoD3Qm.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BCwmwMAW.js → vnode-CjohPxI0.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B0cEGluV.js → zoom-C5POL1_6.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/team.js +165 -53
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/remote-session-protocol.js +20 -15
- package/src/generated/cc-agent-protocol.schema.json +67 -1
- package/src/lib/agent-team/task-lease.js +718 -6
- package/src/lib/agent-team/team-graph-projection.js +303 -0
- package/src/lib/agent-team/team-message-bridge.js +83 -5
- package/src/lib/agent-team/team-message-tools.js +34 -1
- package/src/lib/agent-team/team-runner.js +1131 -9
- package/src/lib/agent-team/team-session-message-adapter.js +720 -0
- package/src/lib/app-server/cli-agent-kernel-adapter.js +1 -0
- package/src/lib/app-server/protocol.js +44 -8
- package/src/lib/app-server/server.js +8 -11
- package/src/lib/approval-grant-ledger.js +187 -0
- package/src/lib/remote-approval-bridge.js +8 -12
- package/src/lib/remote-approval-decision.js +87 -0
- package/src/lib/session-message-fabric.js +265 -3
- package/src/runtime/headless-stream.js +184 -19
- package/src/assets/web-panel/assets/OrderTableRenderer-dgM87XIn.js +0 -1
- package/src/assets/web-panel/assets/Tasks-3ILwauYw.js +0 -1
- package/src/assets/web-panel/assets/devWarning-D7qVrkrO.js +0 -1
- package/src/assets/web-panel/assets/index-D9Y_ZwZn.js +0 -1
- package/src/assets/web-panel/assets/index-D_cPes4G.js +0 -1
|
@@ -38,11 +38,15 @@ export const SESSION_MESSAGE_RECEIPT_STATUSES = Object.freeze([
|
|
|
38
38
|
"full",
|
|
39
39
|
"rate_limited",
|
|
40
40
|
"expired",
|
|
41
|
+
"read",
|
|
42
|
+
"processed",
|
|
43
|
+
"dead_letter",
|
|
41
44
|
]);
|
|
42
45
|
|
|
43
46
|
export const SESSION_MESSAGE_FABRIC_LIMITS = Object.freeze({
|
|
44
47
|
maxMessageBytes: 256 * 1024,
|
|
45
48
|
maxPendingPerRecipient: 100,
|
|
49
|
+
maxPendingBytes: 32 * 1024 * 1024,
|
|
46
50
|
maxReceiptHistory: 5_000,
|
|
47
51
|
maxMessagesPerSenderWindow: 120,
|
|
48
52
|
senderRateWindowMs: 60_000,
|
|
@@ -62,9 +66,21 @@ export const SESSION_MESSAGE_FABRIC_ERROR_CODES = Object.freeze({
|
|
|
62
66
|
});
|
|
63
67
|
|
|
64
68
|
const PENDING_STATUSES = new Set(["held", "delivered"]);
|
|
69
|
+
const MESSAGE_STATUSES = new Set([
|
|
70
|
+
"delivered",
|
|
71
|
+
"held",
|
|
72
|
+
"refused",
|
|
73
|
+
"full",
|
|
74
|
+
"rate_limited",
|
|
75
|
+
"expired",
|
|
76
|
+
]);
|
|
77
|
+
const PROCESSING_STATUSES = new Set(["read", "processed", "dead_letter"]);
|
|
78
|
+
const TERMINAL_PROCESSING_STATUSES = new Set(["processed", "dead_letter"]);
|
|
65
79
|
const RECEIPT_STATUSES = new Set(SESSION_MESSAGE_RECEIPT_STATUSES);
|
|
66
80
|
const POLICIES = new Set(SESSION_MESSAGE_POLICIES);
|
|
67
81
|
const NAME_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
|
|
82
|
+
// Bounded ids explicitly reject ASCII control characters.
|
|
83
|
+
// eslint-disable-next-line no-control-regex
|
|
68
84
|
const ID_PATTERN = /^[^\u0000-\u001f\u007f]{1,256}$/u;
|
|
69
85
|
const MESSAGE_ID_PATTERN = /^[a-zA-Z0-9._:-]{1,128}$/u;
|
|
70
86
|
|
|
@@ -324,6 +340,11 @@ function updateReceipt(state, message, status, reason, now) {
|
|
|
324
340
|
updatedAt: now,
|
|
325
341
|
idleNotifiedAt: null,
|
|
326
342
|
readAt: null,
|
|
343
|
+
processedAt: null,
|
|
344
|
+
deadLetteredAt: null,
|
|
345
|
+
deliveryCount: 0,
|
|
346
|
+
consumerKey: null,
|
|
347
|
+
recipientAttempt: null,
|
|
327
348
|
};
|
|
328
349
|
state.receipts.push(receipt);
|
|
329
350
|
} else {
|
|
@@ -402,6 +423,7 @@ function compactSettledMessages(state) {
|
|
|
402
423
|
state.messages = state.messages.filter((message) => {
|
|
403
424
|
if (!PENDING_STATUSES.has(message.status)) return false;
|
|
404
425
|
if (message.acknowledgedAt == null) return true;
|
|
426
|
+
if (message.retainAfterAck === true) return true;
|
|
405
427
|
if (message.notifyWhenIdle !== true) return false;
|
|
406
428
|
const receipt = receiptFor(
|
|
407
429
|
state,
|
|
@@ -413,6 +435,30 @@ function compactSettledMessages(state) {
|
|
|
413
435
|
return state.messages.length !== before;
|
|
414
436
|
}
|
|
415
437
|
|
|
438
|
+
function trimRetainedSettledMessages(state, limit) {
|
|
439
|
+
const retained = state.messages
|
|
440
|
+
.filter(
|
|
441
|
+
(message) =>
|
|
442
|
+
message.retainAfterAck === true && message.acknowledgedAt != null,
|
|
443
|
+
)
|
|
444
|
+
.sort(
|
|
445
|
+
(left, right) =>
|
|
446
|
+
left.acknowledgedAt - right.acknowledgedAt ||
|
|
447
|
+
left.createdAt - right.createdAt ||
|
|
448
|
+
left.messageId.localeCompare(right.messageId),
|
|
449
|
+
);
|
|
450
|
+
let changed = false;
|
|
451
|
+
while (retained.length > limit) {
|
|
452
|
+
const target = retained.shift();
|
|
453
|
+
const index = state.messages.indexOf(target);
|
|
454
|
+
if (index >= 0) {
|
|
455
|
+
state.messages.splice(index, 1);
|
|
456
|
+
changed = true;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return changed;
|
|
460
|
+
}
|
|
461
|
+
|
|
416
462
|
function applyExpectedMessage(state, channel, message, recipient, now) {
|
|
417
463
|
if (recipient.policy === "refuse") {
|
|
418
464
|
updateReceipt(state, message, "refused", "policy_refuse", now);
|
|
@@ -556,7 +602,7 @@ function validateState(state) {
|
|
|
556
602
|
!MESSAGE_ID_PATTERN.test(message?.messageId || "") ||
|
|
557
603
|
!Number.isSafeInteger(message?.sequence) ||
|
|
558
604
|
message.sequence < 1 ||
|
|
559
|
-
!
|
|
605
|
+
!MESSAGE_STATUSES.has(message?.status) ||
|
|
560
606
|
!channelIds.has(message?.channelId) ||
|
|
561
607
|
typeof message?.payloadDigest !== "string"
|
|
562
608
|
) {
|
|
@@ -572,6 +618,13 @@ function validateState(state) {
|
|
|
572
618
|
) {
|
|
573
619
|
fail("invalid receipt");
|
|
574
620
|
}
|
|
621
|
+
if (
|
|
622
|
+
receipt.deliveryCount != null &&
|
|
623
|
+
(!Number.isSafeInteger(receipt.deliveryCount) ||
|
|
624
|
+
receipt.deliveryCount < 0)
|
|
625
|
+
) {
|
|
626
|
+
fail("invalid receipt delivery count");
|
|
627
|
+
}
|
|
575
628
|
}
|
|
576
629
|
const bucketAuthorities = new Set();
|
|
577
630
|
for (const bucket of state.rateBuckets || []) {
|
|
@@ -606,6 +659,7 @@ export class SessionMessageFabric {
|
|
|
606
659
|
lockTimeoutMs = 10_000,
|
|
607
660
|
maxMessageBytes = SESSION_MESSAGE_FABRIC_LIMITS.maxMessageBytes,
|
|
608
661
|
maxPendingPerRecipient = SESSION_MESSAGE_FABRIC_LIMITS.maxPendingPerRecipient,
|
|
662
|
+
maxPendingBytes = SESSION_MESSAGE_FABRIC_LIMITS.maxPendingBytes,
|
|
609
663
|
maxReceiptHistory = SESSION_MESSAGE_FABRIC_LIMITS.maxReceiptHistory,
|
|
610
664
|
maxMessagesPerSenderWindow = SESSION_MESSAGE_FABRIC_LIMITS.maxMessagesPerSenderWindow,
|
|
611
665
|
senderRateWindowMs = SESSION_MESSAGE_FABRIC_LIMITS.senderRateWindowMs,
|
|
@@ -636,6 +690,10 @@ export class SessionMessageFabric {
|
|
|
636
690
|
maxPendingPerRecipient,
|
|
637
691
|
SESSION_MESSAGE_FABRIC_LIMITS.maxPendingPerRecipient,
|
|
638
692
|
),
|
|
693
|
+
maxPendingBytes: positiveInteger(
|
|
694
|
+
maxPendingBytes,
|
|
695
|
+
SESSION_MESSAGE_FABRIC_LIMITS.maxPendingBytes,
|
|
696
|
+
),
|
|
639
697
|
maxReceiptHistory: positiveInteger(
|
|
640
698
|
maxReceiptHistory,
|
|
641
699
|
SESSION_MESSAGE_FABRIC_LIMITS.maxReceiptHistory,
|
|
@@ -770,6 +828,10 @@ export class SessionMessageFabric {
|
|
|
770
828
|
);
|
|
771
829
|
const outcome = mutator(state, now) || { value: undefined };
|
|
772
830
|
const compacted = compactSettledMessages(state);
|
|
831
|
+
const trimmedSettled = trimRetainedSettledMessages(
|
|
832
|
+
state,
|
|
833
|
+
this._limits.maxReceiptHistory,
|
|
834
|
+
);
|
|
773
835
|
const trimmed = trimReceiptHistory(
|
|
774
836
|
state,
|
|
775
837
|
this._limits.maxReceiptHistory,
|
|
@@ -778,6 +840,7 @@ export class SessionMessageFabric {
|
|
|
778
840
|
swept ||
|
|
779
841
|
prunedRateBuckets ||
|
|
780
842
|
compacted ||
|
|
843
|
+
trimmedSettled ||
|
|
781
844
|
trimmed ||
|
|
782
845
|
outcome.changed === true
|
|
783
846
|
) {
|
|
@@ -985,6 +1048,7 @@ export class SessionMessageFabric {
|
|
|
985
1048
|
sequence = null,
|
|
986
1049
|
ttlMs = this._limits.defaultTtlMs,
|
|
987
1050
|
notifyWhenIdle = false,
|
|
1051
|
+
retainAfterAck = false,
|
|
988
1052
|
} = {}) {
|
|
989
1053
|
const id = messageId(requestedMessageId, this._createId);
|
|
990
1054
|
const cleanSubject = subject == null ? null : String(subject).slice(0, 512);
|
|
@@ -1108,6 +1172,7 @@ export class SessionMessageFabric {
|
|
|
1108
1172
|
status: "held",
|
|
1109
1173
|
reason: "out_of_order",
|
|
1110
1174
|
notifyWhenIdle: notifyWhenIdle === true,
|
|
1175
|
+
retainAfterAck: retainAfterAck === true,
|
|
1111
1176
|
createdAt: now,
|
|
1112
1177
|
updatedAt: now,
|
|
1113
1178
|
deliveredAt: null,
|
|
@@ -1137,10 +1202,17 @@ export class SessionMessageFabric {
|
|
|
1137
1202
|
value: clone(receiptFor(state, sender.authorityId, id)),
|
|
1138
1203
|
};
|
|
1139
1204
|
}
|
|
1205
|
+
const pendingBytes = state.messages
|
|
1206
|
+
.filter(
|
|
1207
|
+
(entry) =>
|
|
1208
|
+
PENDING_STATUSES.has(entry.status) && entry.acknowledgedAt == null,
|
|
1209
|
+
)
|
|
1210
|
+
.reduce((sum, entry) => sum + (entry.messageBytes || 0), 0);
|
|
1140
1211
|
if (
|
|
1141
1212
|
recipient.policy !== "refuse" &&
|
|
1142
|
-
pendingForRecipient(state, recipient).length >=
|
|
1143
|
-
this._limits.maxPendingPerRecipient
|
|
1213
|
+
(pendingForRecipient(state, recipient).length >=
|
|
1214
|
+
this._limits.maxPendingPerRecipient ||
|
|
1215
|
+
pendingBytes + messageBytes > this._limits.maxPendingBytes)
|
|
1144
1216
|
) {
|
|
1145
1217
|
updateReceipt(state, message, "full", "queue_capacity", now);
|
|
1146
1218
|
return {
|
|
@@ -1194,6 +1266,196 @@ export class SessionMessageFabric {
|
|
|
1194
1266
|
});
|
|
1195
1267
|
}
|
|
1196
1268
|
|
|
1269
|
+
/**
|
|
1270
|
+
* Deliver an at-least-once batch without retiring it. This is deliberately
|
|
1271
|
+
* separate from `inbox(..., { acknowledge: true })`, whose historical API
|
|
1272
|
+
* means "read and remove". Processing callers must explicitly settle each
|
|
1273
|
+
* message with `acknowledge()`.
|
|
1274
|
+
*/
|
|
1275
|
+
receive(selector, { limit = 100, markRead = false } = {}) {
|
|
1276
|
+
const boundedLimit = positiveInteger(limit, 100, 100);
|
|
1277
|
+
return this._transaction((state, now) => {
|
|
1278
|
+
const endpoint = requireEndpoint(state, selector);
|
|
1279
|
+
const messages = pendingForRecipient(state, endpoint)
|
|
1280
|
+
.filter((message) => message.status === "delivered")
|
|
1281
|
+
.sort(
|
|
1282
|
+
(left, right) =>
|
|
1283
|
+
(left.senderAuthority === right.senderAuthority
|
|
1284
|
+
? left.sequence - right.sequence
|
|
1285
|
+
: left.createdAt - right.createdAt) ||
|
|
1286
|
+
left.senderAuthority.localeCompare(right.senderAuthority),
|
|
1287
|
+
)
|
|
1288
|
+
.slice(0, boundedLimit);
|
|
1289
|
+
for (const message of messages) {
|
|
1290
|
+
const receipt = receiptFor(
|
|
1291
|
+
state,
|
|
1292
|
+
message.senderAuthority,
|
|
1293
|
+
message.messageId,
|
|
1294
|
+
);
|
|
1295
|
+
if (!receipt) continue;
|
|
1296
|
+
receipt.deliveryCount = (receipt.deliveryCount || 0) + 1;
|
|
1297
|
+
receipt.deliveredAt ||= message.deliveredAt || now;
|
|
1298
|
+
if (markRead) {
|
|
1299
|
+
receipt.status = "read";
|
|
1300
|
+
receipt.readAt ||= now;
|
|
1301
|
+
} else if (!PROCESSING_STATUSES.has(receipt.status)) {
|
|
1302
|
+
receipt.status = "delivered";
|
|
1303
|
+
}
|
|
1304
|
+
receipt.updatedAt = now;
|
|
1305
|
+
}
|
|
1306
|
+
return {
|
|
1307
|
+
changed: messages.length > 0,
|
|
1308
|
+
value: clone(
|
|
1309
|
+
messages.map((message) => ({
|
|
1310
|
+
...message,
|
|
1311
|
+
delivery: receiptFor(
|
|
1312
|
+
state,
|
|
1313
|
+
message.senderAuthority,
|
|
1314
|
+
message.messageId,
|
|
1315
|
+
),
|
|
1316
|
+
})),
|
|
1317
|
+
),
|
|
1318
|
+
};
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/** Persist read/processed/dead-letter evidence for explicit physical ids. */
|
|
1323
|
+
acknowledge(
|
|
1324
|
+
selector,
|
|
1325
|
+
{
|
|
1326
|
+
messageIds = [],
|
|
1327
|
+
consumerKey,
|
|
1328
|
+
status = "processed",
|
|
1329
|
+
reason = null,
|
|
1330
|
+
recipientAttempt = null,
|
|
1331
|
+
} = {},
|
|
1332
|
+
) {
|
|
1333
|
+
if (
|
|
1334
|
+
!Array.isArray(messageIds) ||
|
|
1335
|
+
messageIds.length === 0 ||
|
|
1336
|
+
messageIds.length > 100 ||
|
|
1337
|
+
messageIds.some((id) => !MESSAGE_ID_PATTERN.test(String(id || ""))) ||
|
|
1338
|
+
new Set(messageIds.map(String)).size !== messageIds.length
|
|
1339
|
+
) {
|
|
1340
|
+
throw fabricError(
|
|
1341
|
+
SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
|
|
1342
|
+
"messageIds must contain 1-100 unique message identifiers",
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
if (!PROCESSING_STATUSES.has(status)) {
|
|
1346
|
+
throw fabricError(
|
|
1347
|
+
SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
|
|
1348
|
+
"processing status must be read, processed or dead_letter",
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
const safeConsumerKey = boundedId(consumerKey, "consumerKey");
|
|
1352
|
+
const safeAttempt =
|
|
1353
|
+
recipientAttempt == null ? null : jsonBody(recipientAttempt).value;
|
|
1354
|
+
const safeReason = String(reason || "poison_message").slice(0, 1024);
|
|
1355
|
+
const ids = messageIds.map(String);
|
|
1356
|
+
return this._transaction((state, now) => {
|
|
1357
|
+
const endpoint = requireEndpoint(state, selector);
|
|
1358
|
+
const plans = ids.map((id) => {
|
|
1359
|
+
const receipt = state.receipts.find(
|
|
1360
|
+
(candidate) =>
|
|
1361
|
+
candidate.messageId === id &&
|
|
1362
|
+
candidate.recipientAuthority === endpoint.authorityId &&
|
|
1363
|
+
candidate.recipientEpoch === endpoint.epoch,
|
|
1364
|
+
);
|
|
1365
|
+
const message = state.messages.find(
|
|
1366
|
+
(candidate) =>
|
|
1367
|
+
candidate.messageId === id &&
|
|
1368
|
+
candidate.recipientAuthority === endpoint.authorityId &&
|
|
1369
|
+
candidate.recipientEpoch === endpoint.epoch,
|
|
1370
|
+
);
|
|
1371
|
+
if (
|
|
1372
|
+
!receipt ||
|
|
1373
|
+
(!message && !TERMINAL_PROCESSING_STATUSES.has(receipt.status))
|
|
1374
|
+
) {
|
|
1375
|
+
throw fabricError(
|
|
1376
|
+
SESSION_MESSAGE_FABRIC_ERROR_CODES.INVALID_ARGUMENT,
|
|
1377
|
+
`message is not addressed to the active recipient: ${id}`,
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
if (TERMINAL_PROCESSING_STATUSES.has(receipt.status)) {
|
|
1381
|
+
if (receipt.consumerKey !== safeConsumerKey) {
|
|
1382
|
+
throw fabricError(
|
|
1383
|
+
SESSION_MESSAGE_FABRIC_ERROR_CODES.MESSAGE_ID_CONFLICT,
|
|
1384
|
+
`message was settled by another consumer: ${id}`,
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
if (receipt.status !== status) {
|
|
1388
|
+
throw fabricError(
|
|
1389
|
+
SESSION_MESSAGE_FABRIC_ERROR_CODES.MESSAGE_ID_CONFLICT,
|
|
1390
|
+
`message already has terminal status ${receipt.status}: ${id}`,
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
return { receipt, message, replay: true };
|
|
1394
|
+
}
|
|
1395
|
+
return { receipt, message, replay: false };
|
|
1396
|
+
});
|
|
1397
|
+
const receipts = [];
|
|
1398
|
+
for (const plan of plans) {
|
|
1399
|
+
const { receipt, message, replay } = plan;
|
|
1400
|
+
if (replay) {
|
|
1401
|
+
receipts.push(clone(receipt));
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
receipt.status = status;
|
|
1405
|
+
receipt.consumerKey = safeConsumerKey;
|
|
1406
|
+
receipt.recipientAttempt = safeAttempt;
|
|
1407
|
+
receipt.updatedAt = now;
|
|
1408
|
+
if (status === "read") receipt.readAt ||= now;
|
|
1409
|
+
if (status === "processed") {
|
|
1410
|
+
receipt.readAt ||= now;
|
|
1411
|
+
receipt.processedAt ||= now;
|
|
1412
|
+
message.acknowledgedAt = now;
|
|
1413
|
+
}
|
|
1414
|
+
if (status === "dead_letter") {
|
|
1415
|
+
receipt.deadLetteredAt ||= now;
|
|
1416
|
+
receipt.reason = safeReason;
|
|
1417
|
+
message.acknowledgedAt = now;
|
|
1418
|
+
}
|
|
1419
|
+
receipts.push(clone(receipt));
|
|
1420
|
+
}
|
|
1421
|
+
return { changed: plans.some((plan) => !plan.replay), value: receipts };
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
/** Bounded retained history for authoritative adapters and audit reducers. */
|
|
1426
|
+
history() {
|
|
1427
|
+
return this._transaction((state) => ({
|
|
1428
|
+
value: clone(
|
|
1429
|
+
state.messages.sort(
|
|
1430
|
+
(left, right) =>
|
|
1431
|
+
left.createdAt - right.createdAt ||
|
|
1432
|
+
left.messageId.localeCompare(right.messageId),
|
|
1433
|
+
),
|
|
1434
|
+
),
|
|
1435
|
+
}));
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/**
|
|
1439
|
+
* One-revision authority view for host adapters and deterministic reducers.
|
|
1440
|
+
* Unlike a sequence of projection/history/receipt reads, this cannot combine
|
|
1441
|
+
* fields from different concurrent commits.
|
|
1442
|
+
*/
|
|
1443
|
+
auditSnapshot() {
|
|
1444
|
+
return this._transaction((state) => ({
|
|
1445
|
+
value: {
|
|
1446
|
+
schema: SESSION_MESSAGE_FABRIC_SCHEMA,
|
|
1447
|
+
version: state.version,
|
|
1448
|
+
authority: "cli",
|
|
1449
|
+
revision: state.revision,
|
|
1450
|
+
digest: state.digest,
|
|
1451
|
+
limits: clone(this._limits),
|
|
1452
|
+
endpoints: clone(state.endpoints),
|
|
1453
|
+
messages: clone(state.messages),
|
|
1454
|
+
receipts: clone(state.receipts),
|
|
1455
|
+
},
|
|
1456
|
+
}));
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1197
1459
|
receipts(selector) {
|
|
1198
1460
|
return this._transaction((state) => {
|
|
1199
1461
|
const endpoint = requireEndpoint(state, selector);
|
|
@@ -22,6 +22,12 @@ import {
|
|
|
22
22
|
approvalBindingDigest,
|
|
23
23
|
verifyApprovalBinding,
|
|
24
24
|
} from "../lib/agent-authority.js";
|
|
25
|
+
import { validateApprovalDecision } from "../lib/app-server/protocol.js";
|
|
26
|
+
import {
|
|
27
|
+
APPROVAL_GRANTS_EVENT,
|
|
28
|
+
ApprovalGrantLedger,
|
|
29
|
+
approvalPermissionForContext,
|
|
30
|
+
} from "../lib/approval-grant-ledger.js";
|
|
25
31
|
import {
|
|
26
32
|
normalizeInteractionBinding,
|
|
27
33
|
sameInteractionBinding,
|
|
@@ -572,14 +578,57 @@ export function parseInputEvent(line) {
|
|
|
572
578
|
if (obj && typeof obj === "object" && obj.type === "compact") {
|
|
573
579
|
return { compact: true };
|
|
574
580
|
}
|
|
575
|
-
// Approval verdicts (panel
|
|
576
|
-
//
|
|
581
|
+
// Approval verdicts (panel/SDK for --interactive-approvals). New clients
|
|
582
|
+
// send the canonical structured decision and echo the request binding. The
|
|
583
|
+
// boolean remains an N-1 migration field for older clients only.
|
|
584
|
+
// {"type":"approval","id":"appr-1","decision":{"kind":"acceptOnce"},"binding":"..."}
|
|
585
|
+
// {"type":"approval","id":"appr-1","approve":true|false} // legacy
|
|
577
586
|
if (obj && typeof obj === "object" && obj.type === "approval") {
|
|
578
587
|
if (!obj.id) return null;
|
|
588
|
+
const structured = obj.decision !== undefined;
|
|
589
|
+
let decision;
|
|
590
|
+
let invalidReason = null;
|
|
591
|
+
if (structured) {
|
|
592
|
+
const validation = validateApprovalDecision(obj.decision);
|
|
593
|
+
if (validation.ok) {
|
|
594
|
+
decision = obj.decision;
|
|
595
|
+
} else {
|
|
596
|
+
invalidReason = "invalid-decision";
|
|
597
|
+
decision = {
|
|
598
|
+
kind: "decline",
|
|
599
|
+
reason: "Invalid structured approval decision",
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
} else {
|
|
603
|
+
decision =
|
|
604
|
+
obj.approve === true
|
|
605
|
+
? { kind: "acceptOnce" }
|
|
606
|
+
: { kind: "decline", reason: "Legacy boolean denial" };
|
|
607
|
+
}
|
|
608
|
+
const approve = [
|
|
609
|
+
"acceptOnce",
|
|
610
|
+
"acceptForTurn",
|
|
611
|
+
"acceptForSession",
|
|
612
|
+
].includes(decision.kind);
|
|
613
|
+
if (
|
|
614
|
+
!invalidReason &&
|
|
615
|
+
structured &&
|
|
616
|
+
typeof obj.approve === "boolean" &&
|
|
617
|
+
obj.approve !== approve
|
|
618
|
+
) {
|
|
619
|
+
invalidReason = "decision-boolean-mismatch";
|
|
620
|
+
decision = {
|
|
621
|
+
kind: "decline",
|
|
622
|
+
reason: "Structured and legacy approval verdicts disagree",
|
|
623
|
+
};
|
|
624
|
+
}
|
|
579
625
|
return {
|
|
580
626
|
approval: {
|
|
581
627
|
id: String(obj.id),
|
|
582
|
-
approve:
|
|
628
|
+
approve: invalidReason ? false : approve,
|
|
629
|
+
decision,
|
|
630
|
+
structured,
|
|
631
|
+
invalidReason,
|
|
583
632
|
// Optional approval binding (authority §"权限来源与跨 Agent 授权边界"):
|
|
584
633
|
// when present it must match the digest the matching approval_request
|
|
585
634
|
// advertised, so a stale/mis-routed/param-substituted verdict can't
|
|
@@ -2035,12 +2084,61 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
2035
2084
|
}
|
|
2036
2085
|
};
|
|
2037
2086
|
|
|
2087
|
+
// Structured approval grants are exact tool+args+cwd capabilities. Turn
|
|
2088
|
+
// grants are memory-only and reset before every model turn; session grants
|
|
2089
|
+
// are restored only from a verified authority event and are appended under
|
|
2090
|
+
// the same anchored transcript contract as other session authority state.
|
|
2091
|
+
let approvalGrantLedger = new ApprovalGrantLedger({ sessionId });
|
|
2092
|
+
let approvalGrantPersistenceError = null;
|
|
2093
|
+
if (persist) {
|
|
2094
|
+
try {
|
|
2095
|
+
const event = store.findLatestEvent(
|
|
2096
|
+
sessionId,
|
|
2097
|
+
APPROVAL_GRANTS_EVENT,
|
|
2098
|
+
(candidate) => candidate?.data && typeof candidate.data === "object",
|
|
2099
|
+
);
|
|
2100
|
+
if (event) {
|
|
2101
|
+
approvalGrantLedger = ApprovalGrantLedger.fromJSON(event.data, {
|
|
2102
|
+
sessionId,
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
} catch (error) {
|
|
2106
|
+
approvalGrantPersistenceError = error;
|
|
2107
|
+
approvalGrantLedger = new ApprovalGrantLedger({ sessionId });
|
|
2108
|
+
emit({
|
|
2109
|
+
type: "recovery_degraded",
|
|
2110
|
+
component: "approval_grants",
|
|
2111
|
+
session_id: sessionId,
|
|
2112
|
+
error:
|
|
2113
|
+
"Persisted approval grants could not be verified; all grants were discarded",
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
const persistApprovalGrants = () => {
|
|
2118
|
+
if (!persist || approvalGrantPersistenceError) return false;
|
|
2119
|
+
try {
|
|
2120
|
+
const persisted = store.appendAuthorityEvent(
|
|
2121
|
+
sessionId,
|
|
2122
|
+
APPROVAL_GRANTS_EVENT,
|
|
2123
|
+
approvalGrantLedger.toJSON(),
|
|
2124
|
+
);
|
|
2125
|
+
if (persisted === false) {
|
|
2126
|
+
throw new Error("session store rejected approval grants");
|
|
2127
|
+
}
|
|
2128
|
+
return true;
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
approvalGrantPersistenceError = error;
|
|
2131
|
+
return false;
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
|
|
2038
2135
|
// ── Interactive approvals (--interactive-approvals; chat-panel UX) ────────
|
|
2039
2136
|
// CONFIRM-tier decisions (risky shell via the ApprovalGate, settings/hook
|
|
2040
2137
|
// `ask`) normally fail closed in headless. With this opt-in they become a
|
|
2041
2138
|
// structured round-trip instead: emit `approval_request`, BLOCK the tool
|
|
2042
|
-
// until a
|
|
2043
|
-
// concurrent pump below, so the wait never
|
|
2139
|
+
// until a canonical structured decision (or an N-1 boolean response) arrives
|
|
2140
|
+
// on stdin (handled by the concurrent pump below, so the wait never
|
|
2141
|
+
// deadlocks), fail closed on
|
|
2044
2142
|
// timeout (CC_APPROVAL_TIMEOUT_MS, default 120s) or stdin close. The
|
|
2045
2143
|
// resolution is echoed as `approval_resolved` so UIs can settle their cards.
|
|
2046
2144
|
const interactive = options.interactiveApprovals === true;
|
|
@@ -2050,45 +2148,87 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
2050
2148
|
Number(process.env.CC_APPROVAL_TIMEOUT_MS) > 0
|
|
2051
2149
|
? Number(process.env.CC_APPROVAL_TIMEOUT_MS)
|
|
2052
2150
|
: 120000;
|
|
2053
|
-
const settleApproval = (
|
|
2151
|
+
const settleApproval = (
|
|
2152
|
+
id,
|
|
2153
|
+
decision,
|
|
2154
|
+
via,
|
|
2155
|
+
incomingBinding = null,
|
|
2156
|
+
{ structured = false, invalidReason = null } = {},
|
|
2157
|
+
) => {
|
|
2054
2158
|
const p = pendingApprovals.get(id);
|
|
2055
2159
|
if (!p) return;
|
|
2160
|
+
const approve = [
|
|
2161
|
+
"acceptOnce",
|
|
2162
|
+
"acceptForTurn",
|
|
2163
|
+
"acceptForSession",
|
|
2164
|
+
].includes(decision?.kind);
|
|
2056
2165
|
// Approval binding (authority §"权限来源与跨 Agent 授权边界"): an *approve*
|
|
2057
2166
|
// verdict that carries a binding which does NOT match the one the request
|
|
2058
2167
|
// advertised is stale / mis-routed / argument-tampered — reject it (deny,
|
|
2059
2168
|
// fail closed) instead of green-lighting a different or changed tool call.
|
|
2060
|
-
// A verdict with no binding stays backward-compatible
|
|
2169
|
+
// A legacy boolean verdict with no binding stays backward-compatible. A
|
|
2170
|
+
// structured approval must echo the binding; a deny always wins.
|
|
2061
2171
|
if (
|
|
2062
2172
|
approve === true &&
|
|
2063
|
-
incomingBinding &&
|
|
2064
2173
|
p.binding &&
|
|
2065
|
-
|
|
2174
|
+
((structured && !incomingBinding) ||
|
|
2175
|
+
(incomingBinding && !verifyApprovalBinding(p.binding, incomingBinding)))
|
|
2066
2176
|
) {
|
|
2067
2177
|
pendingApprovals.delete(id);
|
|
2068
2178
|
clearTimeout(p.timer);
|
|
2179
|
+
const rejectionReason = incomingBinding
|
|
2180
|
+
? "binding-mismatch"
|
|
2181
|
+
: "binding-missing";
|
|
2069
2182
|
emit({
|
|
2070
2183
|
type: "approval_resolved",
|
|
2071
2184
|
id,
|
|
2072
2185
|
approved: false,
|
|
2073
|
-
|
|
2186
|
+
decision: {
|
|
2187
|
+
kind: "decline",
|
|
2188
|
+
reason: `Approval ${rejectionReason}`,
|
|
2189
|
+
},
|
|
2190
|
+
via: rejectionReason,
|
|
2074
2191
|
session_id: sessionId,
|
|
2075
2192
|
});
|
|
2076
2193
|
p.resolve(false);
|
|
2077
2194
|
return;
|
|
2078
2195
|
}
|
|
2196
|
+
if (
|
|
2197
|
+
approve &&
|
|
2198
|
+
(decision.kind === "acceptForTurn" ||
|
|
2199
|
+
decision.kind === "acceptForSession")
|
|
2200
|
+
) {
|
|
2201
|
+
const priorSessionGrants = new Map(approvalGrantLedger.sessionGrants);
|
|
2202
|
+
const priorRevision = approvalGrantLedger.revision;
|
|
2203
|
+
const applied = approvalGrantLedger.applyDecision(
|
|
2204
|
+
decision,
|
|
2205
|
+
p.requiredPermission,
|
|
2206
|
+
p.binding,
|
|
2207
|
+
);
|
|
2208
|
+
decision = applied.decision;
|
|
2209
|
+
if (applied.persistedScope && persist && !persistApprovalGrants()) {
|
|
2210
|
+
approvalGrantLedger.sessionGrants = priorSessionGrants;
|
|
2211
|
+
approvalGrantLedger.revision = priorRevision;
|
|
2212
|
+
decision = { kind: "acceptOnce" };
|
|
2213
|
+
via = "session-grant-persistence-failed";
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2079
2216
|
pendingApprovals.delete(id);
|
|
2080
2217
|
clearTimeout(p.timer);
|
|
2081
2218
|
emit({
|
|
2082
2219
|
type: "approval_resolved",
|
|
2083
2220
|
id,
|
|
2084
|
-
approved:
|
|
2085
|
-
|
|
2221
|
+
approved: invalidReason ? false : approve,
|
|
2222
|
+
decision,
|
|
2223
|
+
via: invalidReason || via,
|
|
2086
2224
|
session_id: sessionId,
|
|
2087
2225
|
});
|
|
2088
|
-
p.resolve(
|
|
2226
|
+
p.resolve(!invalidReason && approve);
|
|
2089
2227
|
};
|
|
2090
|
-
const interactiveConfirm = (ctx = {}) =>
|
|
2091
|
-
|
|
2228
|
+
const interactiveConfirm = (ctx = {}) => {
|
|
2229
|
+
const requiredPermission = approvalPermissionForContext(ctx, { cwd });
|
|
2230
|
+
if (approvalGrantLedger.allows(requiredPermission)) return true;
|
|
2231
|
+
return new Promise((resolve) => {
|
|
2092
2232
|
const id = `appr-${++approvalSeq}`;
|
|
2093
2233
|
// Bind this approval request to the exact call it authorizes: the request
|
|
2094
2234
|
// id, its normalized arguments, and the policy/rule in force. The digest
|
|
@@ -2101,11 +2241,21 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
2101
2241
|
policyDigest: ctx.rule || ctx.riskLevel || ctx.risk || null,
|
|
2102
2242
|
});
|
|
2103
2243
|
const timer = setTimeout(
|
|
2104
|
-
() =>
|
|
2244
|
+
() =>
|
|
2245
|
+
settleApproval(
|
|
2246
|
+
id,
|
|
2247
|
+
{ kind: "decline", reason: "Approval timed out" },
|
|
2248
|
+
"timeout",
|
|
2249
|
+
),
|
|
2105
2250
|
approvalTimeoutMs,
|
|
2106
2251
|
);
|
|
2107
2252
|
timer.unref?.();
|
|
2108
|
-
pendingApprovals.set(id, {
|
|
2253
|
+
pendingApprovals.set(id, {
|
|
2254
|
+
resolve,
|
|
2255
|
+
timer,
|
|
2256
|
+
binding,
|
|
2257
|
+
requiredPermission,
|
|
2258
|
+
});
|
|
2109
2259
|
emit({
|
|
2110
2260
|
type: "approval_request",
|
|
2111
2261
|
id,
|
|
@@ -2116,8 +2266,10 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
2116
2266
|
rule: ctx.rule || null,
|
|
2117
2267
|
reason: ctx.reason || null,
|
|
2118
2268
|
binding,
|
|
2269
|
+
requested_permissions: [requiredPermission],
|
|
2119
2270
|
});
|
|
2120
2271
|
});
|
|
2272
|
+
};
|
|
2121
2273
|
|
|
2122
2274
|
let approvalGate = null;
|
|
2123
2275
|
try {
|
|
@@ -2206,6 +2358,10 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
2206
2358
|
type: "approval_resolved",
|
|
2207
2359
|
id,
|
|
2208
2360
|
approved: false,
|
|
2361
|
+
decision: {
|
|
2362
|
+
kind: "decline",
|
|
2363
|
+
reason: `Approval ${via}`,
|
|
2364
|
+
},
|
|
2209
2365
|
via,
|
|
2210
2366
|
session_id: sessionId,
|
|
2211
2367
|
});
|
|
@@ -3260,9 +3416,13 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
3260
3416
|
// Approval verdicts settle a BLOCKED tool — never queued.
|
|
3261
3417
|
settleApproval(
|
|
3262
3418
|
parsed.approval.id,
|
|
3263
|
-
parsed.approval.
|
|
3419
|
+
parsed.approval.decision,
|
|
3264
3420
|
parsed.approval.approve ? "user-approve" : "user-deny",
|
|
3265
3421
|
parsed.approval.binding,
|
|
3422
|
+
{
|
|
3423
|
+
structured: parsed.approval.structured,
|
|
3424
|
+
invalidReason: parsed.approval.invalidReason,
|
|
3425
|
+
},
|
|
3266
3426
|
);
|
|
3267
3427
|
continue;
|
|
3268
3428
|
}
|
|
@@ -3321,7 +3481,11 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
3321
3481
|
// stdin closed while approvals were pending → fail closed so the blocked
|
|
3322
3482
|
// turn can finish and the process can exit.
|
|
3323
3483
|
for (const id of [...pendingApprovals.keys()]) {
|
|
3324
|
-
settleApproval(
|
|
3484
|
+
settleApproval(
|
|
3485
|
+
id,
|
|
3486
|
+
{ kind: "decline", reason: "Approval input closed" },
|
|
3487
|
+
"stdin-closed",
|
|
3488
|
+
);
|
|
3325
3489
|
}
|
|
3326
3490
|
// stdin closed while a question was pending → user_timeout (model proceeds).
|
|
3327
3491
|
for (const id of [...pendingQuestions.keys()]) {
|
|
@@ -4112,6 +4276,7 @@ async function runAgentHeadlessStreamInWorkspace(
|
|
|
4112
4276
|
}
|
|
4113
4277
|
}
|
|
4114
4278
|
turns += 1;
|
|
4279
|
+
approvalGrantLedger.beginTurn(turns);
|
|
4115
4280
|
if (persistenceFailure) {
|
|
4116
4281
|
terminalPersistenceFailure = persistenceFailure;
|
|
4117
4282
|
sawError = true;
|