feat: add claw approval MVP with privileged broker
Implement Postgres-backed claw approval flow and integrate gateway methods for create/list/get/approve/reject/execute/audit. Add a minimal systemd-run privileged broker with bearer auth, strict scope and exact-command validation, dangerous-shell blocking, atomic once-grant consumption, and execution audit updates.
This commit is contained in:
@@ -382,6 +382,7 @@
|
|||||||
"opusscript": "^0.1.1",
|
"opusscript": "^0.1.1",
|
||||||
"osc-progress": "^0.3.0",
|
"osc-progress": "^0.3.0",
|
||||||
"pdfjs-dist": "^5.5.207",
|
"pdfjs-dist": "^5.5.207",
|
||||||
|
"pg": "^8.20.0",
|
||||||
"playwright-core": "1.58.2",
|
"playwright-core": "1.58.2",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
@@ -400,6 +401,7 @@
|
|||||||
"@types/express": "^5.0.6",
|
"@types/express": "^5.0.6",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
"@types/node": "^25.5.0",
|
"@types/node": "^25.5.0",
|
||||||
|
"@types/pg": "^8.18.0",
|
||||||
"@types/qrcode-terminal": "^0.12.2",
|
"@types/qrcode-terminal": "^0.12.2",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20260312.1",
|
"@typescript/native-preview": "7.0.0-dev.20260312.1",
|
||||||
|
|||||||
365
pnpm-lock.yaml
generated
365
pnpm-lock.yaml
generated
@@ -163,6 +163,9 @@ importers:
|
|||||||
pdfjs-dist:
|
pdfjs-dist:
|
||||||
specifier: ^5.5.207
|
specifier: ^5.5.207
|
||||||
version: 5.5.207
|
version: 5.5.207
|
||||||
|
pg:
|
||||||
|
specifier: ^8.20.0
|
||||||
|
version: 8.20.0
|
||||||
playwright-core:
|
playwright-core:
|
||||||
specifier: 1.58.2
|
specifier: 1.58.2
|
||||||
version: 1.58.2
|
version: 1.58.2
|
||||||
@@ -212,6 +215,9 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.5.0
|
specifier: ^25.5.0
|
||||||
version: 25.5.0
|
version: 25.5.0
|
||||||
|
'@types/pg':
|
||||||
|
specifier: ^8.18.0
|
||||||
|
version: 8.18.0
|
||||||
'@types/qrcode-terminal':
|
'@types/qrcode-terminal':
|
||||||
specifier: ^0.12.2
|
specifier: ^0.12.2
|
||||||
version: 0.12.2
|
version: 0.12.2
|
||||||
@@ -651,10 +657,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-t8cl+bPLlHZQD2Sw1a4hSLUybqJZU71+m8znkyeU8CHntFqEp2mMbuLKdHKaAYQ1fAApXMsvzenCAkDzNeeJlw==}
|
resolution: {integrity: sha512-t8cl+bPLlHZQD2Sw1a4hSLUybqJZU71+m8znkyeU8CHntFqEp2mMbuLKdHKaAYQ1fAApXMsvzenCAkDzNeeJlw==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@aws-sdk/client-bedrock@3.1007.0':
|
|
||||||
resolution: {integrity: sha512-49hH8o6ALKkCiBUgg20HkwxNamP1yYA/n8Si73Z438EqhZGpCfScP3FfxVhrfD5o+4bV4Whi9BTzPKCa/PfUww==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@aws-sdk/client-bedrock@3.1008.0':
|
'@aws-sdk/client-bedrock@3.1008.0':
|
||||||
resolution: {integrity: sha512-mzxO/DplpZZT7AIZUCG7Q78OlaeHeDybYz+ZlWZPaXFjGDJwUv1E3SKskmaaQvTsMeieie0WX7gzueYrCx4YfQ==}
|
resolution: {integrity: sha512-mzxO/DplpZZT7AIZUCG7Q78OlaeHeDybYz+ZlWZPaXFjGDJwUv1E3SKskmaaQvTsMeieie0WX7gzueYrCx4YfQ==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -711,10 +713,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-dFqh7nfX43B8dO1aPQHOcjC0SnCJ83H3F+1LoCh3X1P7E7N09I+0/taID0asU6GCddfDExqnEvQtDdkuMe5tKQ==}
|
resolution: {integrity: sha512-dFqh7nfX43B8dO1aPQHOcjC0SnCJ83H3F+1LoCh3X1P7E7N09I+0/taID0asU6GCddfDExqnEvQtDdkuMe5tKQ==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-ini@3.972.18':
|
|
||||||
resolution: {integrity: sha512-vthIAXJISZnj2576HeyLBj4WTeX+I7PwWeRkbOa0mVX39K13SCGxCgOFuKj2ytm9qTlLOmXe4cdEnroteFtJfw==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-ini@3.972.19':
|
'@aws-sdk/credential-provider-ini@3.972.19':
|
||||||
resolution: {integrity: sha512-pVJVjWqVrPqjpFq7o0mCmeZu1Y0c94OCHSYgivdCD2wfmYVtBbwQErakruhgOD8pcMcx9SCqRw1pzHKR7OGBcA==}
|
resolution: {integrity: sha512-pVJVjWqVrPqjpFq7o0mCmeZu1Y0c94OCHSYgivdCD2wfmYVtBbwQErakruhgOD8pcMcx9SCqRw1pzHKR7OGBcA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -727,10 +725,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-gf2E5b7LpKb+JX2oQsRIDxdRZjBFZt2olCGlWCdb3vBERbXIPgm2t1R5mEnwd4j0UEO/Tbg5zN2KJbHXttJqwA==}
|
resolution: {integrity: sha512-gf2E5b7LpKb+JX2oQsRIDxdRZjBFZt2olCGlWCdb3vBERbXIPgm2t1R5mEnwd4j0UEO/Tbg5zN2KJbHXttJqwA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-login@3.972.18':
|
|
||||||
resolution: {integrity: sha512-kINzc5BBxdYBkPZ0/i1AMPMOk5b5QaFNbYMElVw5QTX13AKj6jcxnv/YNl9oW9mg+Y08ti19hh01HhyEAxsSJQ==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-login@3.972.19':
|
'@aws-sdk/credential-provider-login@3.972.19':
|
||||||
resolution: {integrity: sha512-jOXdZ1o+CywQKr6gyxgxuUmnGwTTnY2Kxs1PM7fI6AYtDWDnmW/yKXayNqkF8KjP1unflqMWKVbVt5VgmE3L0g==}
|
resolution: {integrity: sha512-jOXdZ1o+CywQKr6gyxgxuUmnGwTTnY2Kxs1PM7fI6AYtDWDnmW/yKXayNqkF8KjP1unflqMWKVbVt5VgmE3L0g==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -743,10 +737,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZDJa2gd1xiPg/nBDGhUlat02O8obaDEnICBAVS8qieZ0+nDfaB0Z3ec6gjZj27OqFTjnB/Q5a0GwQwb7rMVViw==}
|
resolution: {integrity: sha512-ZDJa2gd1xiPg/nBDGhUlat02O8obaDEnICBAVS8qieZ0+nDfaB0Z3ec6gjZj27OqFTjnB/Q5a0GwQwb7rMVViw==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-node@3.972.19':
|
|
||||||
resolution: {integrity: sha512-yDWQ9dFTr+IMxwanFe7+tbN5++q8psZBjlUwOiCXn1EzANoBgtqBwcpYcHaMGtn0Wlfj4NuXdf2JaEx1lz5RaQ==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-node@3.972.20':
|
'@aws-sdk/credential-provider-node@3.972.20':
|
||||||
resolution: {integrity: sha512-0xHca2BnPY0kzjDYPH7vk8YbfdBPpWVS67rtqQMalYDQUCBYS37cZ55K6TuFxCoIyNZgSCFrVKr9PXC5BVvQQw==}
|
resolution: {integrity: sha512-0xHca2BnPY0kzjDYPH7vk8YbfdBPpWVS67rtqQMalYDQUCBYS37cZ55K6TuFxCoIyNZgSCFrVKr9PXC5BVvQQw==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -771,10 +761,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-wGtte+48xnhnhHMl/MsxzacBPs5A+7JJedjiP452IkHY7vsbYKcvQBqFye8LwdTJVeHtBHv+JFeTscnwepoWGg==}
|
resolution: {integrity: sha512-wGtte+48xnhnhHMl/MsxzacBPs5A+7JJedjiP452IkHY7vsbYKcvQBqFye8LwdTJVeHtBHv+JFeTscnwepoWGg==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-sso@3.972.18':
|
|
||||||
resolution: {integrity: sha512-YHYEfj5S2aqInRt5ub8nDOX8vAxgMvd84wm2Y3WVNfFa/53vOv9T7WOAqXI25qjj3uEcV46xxfqdDQk04h5XQA==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-sso@3.972.19':
|
'@aws-sdk/credential-provider-sso@3.972.19':
|
||||||
resolution: {integrity: sha512-kVjQsEU3b///q7EZGrUzol9wzwJFKbEzqJKSq82A9ShrUTEO7FNylTtby3sPV19ndADZh1H3FB3+5ZrvKtEEeg==}
|
resolution: {integrity: sha512-kVjQsEU3b///q7EZGrUzol9wzwJFKbEzqJKSq82A9ShrUTEO7FNylTtby3sPV19ndADZh1H3FB3+5ZrvKtEEeg==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -787,10 +773,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-8aiVJh6fTdl8gcyL+sVNcNwTtWpmoFa1Sh7xlj6Z7L/cZ/tYMEBHq44wTYG8Kt0z/PpGNopD89nbj3FHl9QmTA==}
|
resolution: {integrity: sha512-8aiVJh6fTdl8gcyL+sVNcNwTtWpmoFa1Sh7xlj6Z7L/cZ/tYMEBHq44wTYG8Kt0z/PpGNopD89nbj3FHl9QmTA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-web-identity@3.972.18':
|
|
||||||
resolution: {integrity: sha512-OqlEQpJ+J3T5B96qtC1zLLwkBloechP+fezKbCH0sbd2cCc0Ra55XpxWpk/hRj69xAOYtHvoC4orx6eTa4zU7g==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-web-identity@3.972.19':
|
'@aws-sdk/credential-provider-web-identity@3.972.19':
|
||||||
resolution: {integrity: sha512-BV1BlTFdG4w4tAihxN7iXDBoNcNewXD4q8uZlNQiUrnqxwGWUhKHODIQVSPlQGxXClEj+63m+cqZskw+ESmeZg==}
|
resolution: {integrity: sha512-BV1BlTFdG4w4tAihxN7iXDBoNcNewXD4q8uZlNQiUrnqxwGWUhKHODIQVSPlQGxXClEj+63m+cqZskw+ESmeZg==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -875,10 +857,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-MlGWA8uPaOs5AiTZ5JLM4uuWDm9EEAnm9cqwvqQIc6kEgel/8s1BaOWm9QgUcfc9K8qd7KkC3n43yDbeXOA2tg==}
|
resolution: {integrity: sha512-MlGWA8uPaOs5AiTZ5JLM4uuWDm9EEAnm9cqwvqQIc6kEgel/8s1BaOWm9QgUcfc9K8qd7KkC3n43yDbeXOA2tg==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@aws-sdk/nested-clients@3.996.8':
|
|
||||||
resolution: {integrity: sha512-6HlLm8ciMW8VzfB80kfIx16PBA9lOa9Dl+dmCBi78JDhvGlx3I7Rorwi5PpVRkL31RprXnYna3yBf6UKkD/PqA==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@aws-sdk/nested-clients@3.996.9':
|
'@aws-sdk/nested-clients@3.996.9':
|
||||||
resolution: {integrity: sha512-+RpVtpmQbbtzFOKhMlsRcXM/3f1Z49qTOHaA8gEpHOYruERmog6f2AUtf/oTRLCWjR9H2b3roqryV/hI7QMW8w==}
|
resolution: {integrity: sha512-+RpVtpmQbbtzFOKhMlsRcXM/3f1Z49qTOHaA8gEpHOYruERmog6f2AUtf/oTRLCWjR9H2b3roqryV/hI7QMW8w==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -903,14 +881,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-j9BwZZId9sFp+4GPhf6KrwO8Tben2sXibZA8D1vv2I1zBdvkUHcBA2g4pkqIpTRalMTLC0NPkBPX0gERxfy/iA==}
|
resolution: {integrity: sha512-j9BwZZId9sFp+4GPhf6KrwO8Tben2sXibZA8D1vv2I1zBdvkUHcBA2g4pkqIpTRalMTLC0NPkBPX0gERxfy/iA==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@aws-sdk/token-providers@3.1005.0':
|
|
||||||
resolution: {integrity: sha512-vMxd+ivKqSxU9bHx5vmAlFKDAkjGotFU56IOkDa5DaTu1WWwbcse0yFHEm9I537oVvodaiwMl3VBwgHfzQ2rvw==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@aws-sdk/token-providers@3.1007.0':
|
|
||||||
resolution: {integrity: sha512-kKvVyr53vvVc5k6RbvI6jhafxufxO2SkEw8QeEzJqwOXH/IMY7Cm0IyhnBGdqj80iiIIiIM2jGe7Fn3TIdwdrw==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
|
|
||||||
'@aws-sdk/token-providers@3.1008.0':
|
'@aws-sdk/token-providers@3.1008.0':
|
||||||
resolution: {integrity: sha512-TulwlHQBWcJs668kNUDMZHN51DeLrDsYT59Ux4a/nbvr025gM6HjKJJ3LvnZccam7OS/ZKUVkWomCneRQKJbBg==}
|
resolution: {integrity: sha512-TulwlHQBWcJs668kNUDMZHN51DeLrDsYT59Ux4a/nbvr025gM6HjKJJ3LvnZccam7OS/ZKUVkWomCneRQKJbBg==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -979,15 +949,6 @@ packages:
|
|||||||
aws-crt:
|
aws-crt:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@aws-sdk/util-user-agent-node@3.973.5':
|
|
||||||
resolution: {integrity: sha512-Dyy38O4GeMk7UQ48RupfHif//gqnOPbq/zlvRssc11E2mClT+aUfc3VS2yD8oLtzqO3RsqQ9I3gOBB4/+HjPOw==}
|
|
||||||
engines: {node: '>=20.0.0'}
|
|
||||||
peerDependencies:
|
|
||||||
aws-crt: '>=1.0.0'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
aws-crt:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@aws-sdk/util-user-agent-node@3.973.6':
|
'@aws-sdk/util-user-agent-node@3.973.6':
|
||||||
resolution: {integrity: sha512-iF7G0prk7AvmOK64FcLvc/fW+Ty1H+vttajL7PvJFReU8urMxfYmynTTuFKDTA76Wgpq3FzTPKwabMQIXQHiXQ==}
|
resolution: {integrity: sha512-iF7G0prk7AvmOK64FcLvc/fW+Ty1H+vttajL7PvJFReU8urMxfYmynTTuFKDTA76Wgpq3FzTPKwabMQIXQHiXQ==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -3586,6 +3547,9 @@ packages:
|
|||||||
'@types/node@25.5.0':
|
'@types/node@25.5.0':
|
||||||
resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
|
resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
|
||||||
|
|
||||||
|
'@types/pg@8.18.0':
|
||||||
|
resolution: {integrity: sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==}
|
||||||
|
|
||||||
'@types/qrcode-terminal@0.12.2':
|
'@types/qrcode-terminal@0.12.2':
|
||||||
resolution: {integrity: sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q==}
|
resolution: {integrity: sha512-v+RcIEJ+Uhd6ygSQ0u5YYY7ZM+la7GgPbs0V/7l/kFs2uO4S8BcIUEMoP7za4DNIqNnUD5npf0A/7kBhrCKG5Q==}
|
||||||
|
|
||||||
@@ -5845,6 +5809,40 @@ packages:
|
|||||||
performance-now@2.1.0:
|
performance-now@2.1.0:
|
||||||
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
|
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
|
||||||
|
|
||||||
|
pg-cloudflare@1.3.0:
|
||||||
|
resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
|
||||||
|
|
||||||
|
pg-connection-string@2.12.0:
|
||||||
|
resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==}
|
||||||
|
|
||||||
|
pg-int8@1.0.1:
|
||||||
|
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
|
||||||
|
engines: {node: '>=4.0.0'}
|
||||||
|
|
||||||
|
pg-pool@3.13.0:
|
||||||
|
resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==}
|
||||||
|
peerDependencies:
|
||||||
|
pg: '>=8.0'
|
||||||
|
|
||||||
|
pg-protocol@1.13.0:
|
||||||
|
resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==}
|
||||||
|
|
||||||
|
pg-types@2.2.0:
|
||||||
|
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
pg@8.20.0:
|
||||||
|
resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==}
|
||||||
|
engines: {node: '>= 16.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
pg-native: '>=3.0.1'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
pg-native:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
pgpass@1.0.5:
|
||||||
|
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
|
||||||
|
|
||||||
picocolors@1.1.1:
|
picocolors@1.1.1:
|
||||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
@@ -5892,6 +5890,22 @@ packages:
|
|||||||
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
postgres-array@2.0.0:
|
||||||
|
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
postgres-bytea@1.0.1:
|
||||||
|
resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
postgres-date@1.0.7:
|
||||||
|
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
postgres-interval@1.2.0:
|
||||||
|
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
postgres@3.4.8:
|
postgres@3.4.8:
|
||||||
resolution: {integrity: sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg==}
|
resolution: {integrity: sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -6667,10 +6681,6 @@ packages:
|
|||||||
undici-types@7.18.2:
|
undici-types@7.18.2:
|
||||||
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
||||||
|
|
||||||
undici@7.22.0:
|
|
||||||
resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==}
|
|
||||||
engines: {node: '>=20.18.1'}
|
|
||||||
|
|
||||||
undici@7.24.0:
|
undici@7.24.0:
|
||||||
resolution: {integrity: sha512-jxytwMHhsbdpBXxLAcuu0fzlQeXCNnWdDyRHpvWsUl8vd98UwYdl9YTyn8/HcpcJPC3pwUveefsa3zTxyD/ERg==}
|
resolution: {integrity: sha512-jxytwMHhsbdpBXxLAcuu0fzlQeXCNnWdDyRHpvWsUl8vd98UwYdl9YTyn8/HcpcJPC3pwUveefsa3zTxyD/ERg==}
|
||||||
engines: {node: '>=20.18.1'}
|
engines: {node: '>=20.18.1'}
|
||||||
@@ -6928,6 +6938,10 @@ packages:
|
|||||||
xmlchars@2.2.0:
|
xmlchars@2.2.0:
|
||||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||||
|
|
||||||
|
xtend@4.0.2:
|
||||||
|
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||||
|
engines: {node: '>=0.4'}
|
||||||
|
|
||||||
y18n@5.0.8:
|
y18n@5.0.8:
|
||||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -7120,51 +7134,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@aws-sdk/client-bedrock@3.1007.0':
|
|
||||||
dependencies:
|
|
||||||
'@aws-crypto/sha256-browser': 5.2.0
|
|
||||||
'@aws-crypto/sha256-js': 5.2.0
|
|
||||||
'@aws-sdk/core': 3.973.19
|
|
||||||
'@aws-sdk/credential-provider-node': 3.972.19
|
|
||||||
'@aws-sdk/middleware-host-header': 3.972.7
|
|
||||||
'@aws-sdk/middleware-logger': 3.972.7
|
|
||||||
'@aws-sdk/middleware-recursion-detection': 3.972.7
|
|
||||||
'@aws-sdk/middleware-user-agent': 3.972.20
|
|
||||||
'@aws-sdk/region-config-resolver': 3.972.7
|
|
||||||
'@aws-sdk/token-providers': 3.1007.0
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@aws-sdk/util-endpoints': 3.996.4
|
|
||||||
'@aws-sdk/util-user-agent-browser': 3.972.7
|
|
||||||
'@aws-sdk/util-user-agent-node': 3.973.5
|
|
||||||
'@smithy/config-resolver': 4.4.10
|
|
||||||
'@smithy/core': 3.23.9
|
|
||||||
'@smithy/fetch-http-handler': 5.3.13
|
|
||||||
'@smithy/hash-node': 4.2.11
|
|
||||||
'@smithy/invalid-dependency': 4.2.11
|
|
||||||
'@smithy/middleware-content-length': 4.2.11
|
|
||||||
'@smithy/middleware-endpoint': 4.4.23
|
|
||||||
'@smithy/middleware-retry': 4.4.40
|
|
||||||
'@smithy/middleware-serde': 4.2.12
|
|
||||||
'@smithy/middleware-stack': 4.2.11
|
|
||||||
'@smithy/node-config-provider': 4.3.11
|
|
||||||
'@smithy/node-http-handler': 4.4.14
|
|
||||||
'@smithy/protocol-http': 5.3.11
|
|
||||||
'@smithy/smithy-client': 4.12.3
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
'@smithy/url-parser': 4.2.11
|
|
||||||
'@smithy/util-base64': 4.3.2
|
|
||||||
'@smithy/util-body-length-browser': 4.2.2
|
|
||||||
'@smithy/util-body-length-node': 4.2.3
|
|
||||||
'@smithy/util-defaults-mode-browser': 4.3.39
|
|
||||||
'@smithy/util-defaults-mode-node': 4.2.42
|
|
||||||
'@smithy/util-endpoints': 3.3.2
|
|
||||||
'@smithy/util-middleware': 4.2.11
|
|
||||||
'@smithy/util-retry': 4.2.11
|
|
||||||
'@smithy/util-utf8': 4.2.2
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
|
|
||||||
'@aws-sdk/client-bedrock@3.1008.0':
|
'@aws-sdk/client-bedrock@3.1008.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-crypto/sha256-browser': 5.2.0
|
'@aws-crypto/sha256-browser': 5.2.0
|
||||||
@@ -7424,25 +7393,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-ini@3.972.18':
|
|
||||||
dependencies:
|
|
||||||
'@aws-sdk/core': 3.973.19
|
|
||||||
'@aws-sdk/credential-provider-env': 3.972.17
|
|
||||||
'@aws-sdk/credential-provider-http': 3.972.19
|
|
||||||
'@aws-sdk/credential-provider-login': 3.972.18
|
|
||||||
'@aws-sdk/credential-provider-process': 3.972.17
|
|
||||||
'@aws-sdk/credential-provider-sso': 3.972.18
|
|
||||||
'@aws-sdk/credential-provider-web-identity': 3.972.18
|
|
||||||
'@aws-sdk/nested-clients': 3.996.8
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@smithy/credential-provider-imds': 4.2.11
|
|
||||||
'@smithy/property-provider': 4.2.11
|
|
||||||
'@smithy/shared-ini-file-loader': 4.4.6
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-ini@3.972.19':
|
'@aws-sdk/credential-provider-ini@3.972.19':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/core': 3.973.19
|
'@aws-sdk/core': 3.973.19
|
||||||
@@ -7488,19 +7438,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-login@3.972.18':
|
|
||||||
dependencies:
|
|
||||||
'@aws-sdk/core': 3.973.19
|
|
||||||
'@aws-sdk/nested-clients': 3.996.8
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@smithy/property-provider': 4.2.11
|
|
||||||
'@smithy/protocol-http': 5.3.11
|
|
||||||
'@smithy/shared-ini-file-loader': 4.4.6
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-login@3.972.19':
|
'@aws-sdk/credential-provider-login@3.972.19':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/core': 3.973.19
|
'@aws-sdk/core': 3.973.19
|
||||||
@@ -7548,23 +7485,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-node@3.972.19':
|
|
||||||
dependencies:
|
|
||||||
'@aws-sdk/credential-provider-env': 3.972.17
|
|
||||||
'@aws-sdk/credential-provider-http': 3.972.19
|
|
||||||
'@aws-sdk/credential-provider-ini': 3.972.18
|
|
||||||
'@aws-sdk/credential-provider-process': 3.972.17
|
|
||||||
'@aws-sdk/credential-provider-sso': 3.972.18
|
|
||||||
'@aws-sdk/credential-provider-web-identity': 3.972.18
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@smithy/credential-provider-imds': 4.2.11
|
|
||||||
'@smithy/property-provider': 4.2.11
|
|
||||||
'@smithy/shared-ini-file-loader': 4.4.6
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-node@3.972.20':
|
'@aws-sdk/credential-provider-node@3.972.20':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/credential-provider-env': 3.972.17
|
'@aws-sdk/credential-provider-env': 3.972.17
|
||||||
@@ -7635,19 +7555,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-sso@3.972.18':
|
|
||||||
dependencies:
|
|
||||||
'@aws-sdk/core': 3.973.19
|
|
||||||
'@aws-sdk/nested-clients': 3.996.8
|
|
||||||
'@aws-sdk/token-providers': 3.1005.0
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@smithy/property-provider': 4.2.11
|
|
||||||
'@smithy/shared-ini-file-loader': 4.4.6
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-sso@3.972.19':
|
'@aws-sdk/credential-provider-sso@3.972.19':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/core': 3.973.19
|
'@aws-sdk/core': 3.973.19
|
||||||
@@ -7685,18 +7592,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-web-identity@3.972.18':
|
|
||||||
dependencies:
|
|
||||||
'@aws-sdk/core': 3.973.19
|
|
||||||
'@aws-sdk/nested-clients': 3.996.8
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@smithy/property-provider': 4.2.11
|
|
||||||
'@smithy/shared-ini-file-loader': 4.4.6
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
|
|
||||||
'@aws-sdk/credential-provider-web-identity@3.972.19':
|
'@aws-sdk/credential-provider-web-identity@3.972.19':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/core': 3.973.19
|
'@aws-sdk/core': 3.973.19
|
||||||
@@ -7961,49 +7856,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@aws-sdk/nested-clients@3.996.8':
|
|
||||||
dependencies:
|
|
||||||
'@aws-crypto/sha256-browser': 5.2.0
|
|
||||||
'@aws-crypto/sha256-js': 5.2.0
|
|
||||||
'@aws-sdk/core': 3.973.19
|
|
||||||
'@aws-sdk/middleware-host-header': 3.972.7
|
|
||||||
'@aws-sdk/middleware-logger': 3.972.7
|
|
||||||
'@aws-sdk/middleware-recursion-detection': 3.972.7
|
|
||||||
'@aws-sdk/middleware-user-agent': 3.972.20
|
|
||||||
'@aws-sdk/region-config-resolver': 3.972.7
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@aws-sdk/util-endpoints': 3.996.4
|
|
||||||
'@aws-sdk/util-user-agent-browser': 3.972.7
|
|
||||||
'@aws-sdk/util-user-agent-node': 3.973.5
|
|
||||||
'@smithy/config-resolver': 4.4.10
|
|
||||||
'@smithy/core': 3.23.9
|
|
||||||
'@smithy/fetch-http-handler': 5.3.13
|
|
||||||
'@smithy/hash-node': 4.2.11
|
|
||||||
'@smithy/invalid-dependency': 4.2.11
|
|
||||||
'@smithy/middleware-content-length': 4.2.11
|
|
||||||
'@smithy/middleware-endpoint': 4.4.23
|
|
||||||
'@smithy/middleware-retry': 4.4.40
|
|
||||||
'@smithy/middleware-serde': 4.2.12
|
|
||||||
'@smithy/middleware-stack': 4.2.11
|
|
||||||
'@smithy/node-config-provider': 4.3.11
|
|
||||||
'@smithy/node-http-handler': 4.4.14
|
|
||||||
'@smithy/protocol-http': 5.3.11
|
|
||||||
'@smithy/smithy-client': 4.12.3
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
'@smithy/url-parser': 4.2.11
|
|
||||||
'@smithy/util-base64': 4.3.2
|
|
||||||
'@smithy/util-body-length-browser': 4.2.2
|
|
||||||
'@smithy/util-body-length-node': 4.2.3
|
|
||||||
'@smithy/util-defaults-mode-browser': 4.3.39
|
|
||||||
'@smithy/util-defaults-mode-node': 4.2.42
|
|
||||||
'@smithy/util-endpoints': 3.3.2
|
|
||||||
'@smithy/util-middleware': 4.2.11
|
|
||||||
'@smithy/util-retry': 4.2.11
|
|
||||||
'@smithy/util-utf8': 4.2.2
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
|
|
||||||
'@aws-sdk/nested-clients@3.996.9':
|
'@aws-sdk/nested-clients@3.996.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-crypto/sha256-browser': 5.2.0
|
'@aws-crypto/sha256-browser': 5.2.0
|
||||||
@@ -8095,30 +7947,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@aws-sdk/token-providers@3.1005.0':
|
|
||||||
dependencies:
|
|
||||||
'@aws-sdk/core': 3.973.19
|
|
||||||
'@aws-sdk/nested-clients': 3.996.8
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@smithy/property-provider': 4.2.11
|
|
||||||
'@smithy/shared-ini-file-loader': 4.4.6
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
|
|
||||||
'@aws-sdk/token-providers@3.1007.0':
|
|
||||||
dependencies:
|
|
||||||
'@aws-sdk/core': 3.973.19
|
|
||||||
'@aws-sdk/nested-clients': 3.996.8
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@smithy/property-provider': 4.2.11
|
|
||||||
'@smithy/shared-ini-file-loader': 4.4.6
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- aws-crt
|
|
||||||
|
|
||||||
'@aws-sdk/token-providers@3.1008.0':
|
'@aws-sdk/token-providers@3.1008.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/core': 3.973.19
|
'@aws-sdk/core': 3.973.19
|
||||||
@@ -8225,14 +8053,6 @@ snapshots:
|
|||||||
'@smithy/types': 4.13.0
|
'@smithy/types': 4.13.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@aws-sdk/util-user-agent-node@3.973.5':
|
|
||||||
dependencies:
|
|
||||||
'@aws-sdk/middleware-user-agent': 3.972.20
|
|
||||||
'@aws-sdk/types': 3.973.5
|
|
||||||
'@smithy/node-config-provider': 4.3.11
|
|
||||||
'@smithy/types': 4.13.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@aws-sdk/util-user-agent-node@3.973.6':
|
'@aws-sdk/util-user-agent-node@3.973.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/middleware-user-agent': 3.972.20
|
'@aws-sdk/middleware-user-agent': 3.972.20
|
||||||
@@ -11167,6 +10987,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.18.2
|
undici-types: 7.18.2
|
||||||
|
|
||||||
|
'@types/pg@8.18.0':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 25.5.0
|
||||||
|
pg-protocol: 1.13.0
|
||||||
|
pg-types: 2.2.0
|
||||||
|
|
||||||
'@types/qrcode-terminal@0.12.2': {}
|
'@types/qrcode-terminal@0.12.2': {}
|
||||||
|
|
||||||
'@types/qs@6.14.0': {}
|
'@types/qs@6.14.0': {}
|
||||||
@@ -13500,7 +13326,7 @@ snapshots:
|
|||||||
openclaw@2026.3.11(@discordjs/opus@0.10.0)(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(node-llama-cpp@3.16.2(typescript@5.9.3)):
|
openclaw@2026.3.11(@discordjs/opus@0.10.0)(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(node-llama-cpp@3.16.2(typescript@5.9.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@agentclientprotocol/sdk': 0.16.1(zod@4.3.6)
|
'@agentclientprotocol/sdk': 0.16.1(zod@4.3.6)
|
||||||
'@aws-sdk/client-bedrock': 3.1007.0
|
'@aws-sdk/client-bedrock': 3.1008.0
|
||||||
'@buape/carbon': 0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.12.7)(opusscript@0.1.1)
|
'@buape/carbon': 0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.12.7)(opusscript@0.1.1)
|
||||||
'@clack/prompts': 1.1.0
|
'@clack/prompts': 1.1.0
|
||||||
'@discordjs/voice': 0.19.1(@discordjs/opus@0.10.0)(opusscript@0.1.1)
|
'@discordjs/voice': 0.19.1(@discordjs/opus@0.10.0)(opusscript@0.1.1)
|
||||||
@@ -13551,7 +13377,7 @@ snapshots:
|
|||||||
sqlite-vec: 0.1.7-alpha.2
|
sqlite-vec: 0.1.7-alpha.2
|
||||||
tar: 7.5.11
|
tar: 7.5.11
|
||||||
tslog: 4.10.2
|
tslog: 4.10.2
|
||||||
undici: 7.22.0
|
undici: 7.24.0
|
||||||
ws: 8.19.0
|
ws: 8.19.0
|
||||||
yaml: 2.8.2
|
yaml: 2.8.2
|
||||||
zod: 4.3.6
|
zod: 4.3.6
|
||||||
@@ -13756,6 +13582,41 @@ snapshots:
|
|||||||
|
|
||||||
performance-now@2.1.0: {}
|
performance-now@2.1.0: {}
|
||||||
|
|
||||||
|
pg-cloudflare@1.3.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
pg-connection-string@2.12.0: {}
|
||||||
|
|
||||||
|
pg-int8@1.0.1: {}
|
||||||
|
|
||||||
|
pg-pool@3.13.0(pg@8.20.0):
|
||||||
|
dependencies:
|
||||||
|
pg: 8.20.0
|
||||||
|
|
||||||
|
pg-protocol@1.13.0: {}
|
||||||
|
|
||||||
|
pg-types@2.2.0:
|
||||||
|
dependencies:
|
||||||
|
pg-int8: 1.0.1
|
||||||
|
postgres-array: 2.0.0
|
||||||
|
postgres-bytea: 1.0.1
|
||||||
|
postgres-date: 1.0.7
|
||||||
|
postgres-interval: 1.2.0
|
||||||
|
|
||||||
|
pg@8.20.0:
|
||||||
|
dependencies:
|
||||||
|
pg-connection-string: 2.12.0
|
||||||
|
pg-pool: 3.13.0(pg@8.20.0)
|
||||||
|
pg-protocol: 1.13.0
|
||||||
|
pg-types: 2.2.0
|
||||||
|
pgpass: 1.0.5
|
||||||
|
optionalDependencies:
|
||||||
|
pg-cloudflare: 1.3.0
|
||||||
|
|
||||||
|
pgpass@1.0.5:
|
||||||
|
dependencies:
|
||||||
|
split2: 4.2.0
|
||||||
|
|
||||||
picocolors@1.1.1: {}
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
picomatch@2.3.1: {}
|
picomatch@2.3.1: {}
|
||||||
@@ -13806,6 +13667,16 @@ snapshots:
|
|||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
postgres-array@2.0.0: {}
|
||||||
|
|
||||||
|
postgres-bytea@1.0.1: {}
|
||||||
|
|
||||||
|
postgres-date@1.0.7: {}
|
||||||
|
|
||||||
|
postgres-interval@1.2.0:
|
||||||
|
dependencies:
|
||||||
|
xtend: 4.0.2
|
||||||
|
|
||||||
postgres@3.4.8: {}
|
postgres@3.4.8: {}
|
||||||
|
|
||||||
pretty-bytes@6.1.1: {}
|
pretty-bytes@6.1.1: {}
|
||||||
@@ -14725,8 +14596,6 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@7.18.2: {}
|
undici-types@7.18.2: {}
|
||||||
|
|
||||||
undici@7.22.0: {}
|
|
||||||
|
|
||||||
undici@7.24.0: {}
|
undici@7.24.0: {}
|
||||||
|
|
||||||
unist-util-is@6.0.1:
|
unist-util-is@6.0.1:
|
||||||
@@ -14925,6 +14794,8 @@ snapshots:
|
|||||||
|
|
||||||
xmlchars@2.2.0: {}
|
xmlchars@2.2.0: {}
|
||||||
|
|
||||||
|
xtend@4.0.2: {}
|
||||||
|
|
||||||
y18n@5.0.8: {}
|
y18n@5.0.8: {}
|
||||||
|
|
||||||
yallist@4.0.0: {}
|
yallist@4.0.0: {}
|
||||||
|
|||||||
11
scripts/claw-broker/.env.example
Normal file
11
scripts/claw-broker/.env.example
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
CLAW_BROKER_BIND=127.0.0.1
|
||||||
|
CLAW_BROKER_PORT=8787
|
||||||
|
CLAW_BROKER_TOKEN=change-me
|
||||||
|
CLAW_BROKER_CMD_TIMEOUT_MS=120000
|
||||||
|
CLAW_BROKER_MAX_SUMMARY_CHARS=2000
|
||||||
|
|
||||||
|
PGHOST=147.45.189.234
|
||||||
|
PGPORT=5432
|
||||||
|
PGDATABASE=default_db
|
||||||
|
PGUSER=gen_user
|
||||||
|
PGPASSWORD=change-me
|
||||||
43
scripts/claw-broker/README.md
Normal file
43
scripts/claw-broker/README.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Claw Broker (MVP)
|
||||||
|
|
||||||
|
Minimal privileged broker for claw.approvals.execute.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
- POST /v1/execute
|
||||||
|
- Bearer token via CLAW_BROKER_TOKEN
|
||||||
|
|
||||||
|
Request fields:
|
||||||
|
|
||||||
|
- executionId
|
||||||
|
- approvalRequestId
|
||||||
|
- approvalGrantId
|
||||||
|
- exactCommand
|
||||||
|
- targetHost
|
||||||
|
- targetUser
|
||||||
|
- requestedBy
|
||||||
|
- channel
|
||||||
|
- chatId
|
||||||
|
- humanUserId
|
||||||
|
- sessionId
|
||||||
|
|
||||||
|
Response fields:
|
||||||
|
|
||||||
|
- executionId
|
||||||
|
- status
|
||||||
|
- exitCode
|
||||||
|
- stdoutSummary
|
||||||
|
- stderrSummary
|
||||||
|
- startedAt
|
||||||
|
- finishedAt
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Broker re-checks in Postgres before execution:
|
||||||
|
|
||||||
|
- request/grant exist
|
||||||
|
- status allows execution
|
||||||
|
- once grant atomic consume
|
||||||
|
- command exact match
|
||||||
|
- scope match (targetHost, targetUser, channel, chatId, humanUserId, sessionId)
|
||||||
|
- dangerous shell policy
|
||||||
437
scripts/claw-broker/broker.mjs
Normal file
437
scripts/claw-broker/broker.mjs
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
import http from "node:http";
|
||||||
|
import pg from "pg";
|
||||||
|
|
||||||
|
const { Pool } = pg;
|
||||||
|
|
||||||
|
const MAX_SUMMARY_CHARS = Number(process.env.CLAW_BROKER_MAX_SUMMARY_CHARS ?? "2000");
|
||||||
|
const CMD_TIMEOUT_MS = Number(process.env.CLAW_BROKER_CMD_TIMEOUT_MS ?? "120000");
|
||||||
|
const BIND_HOST = process.env.CLAW_BROKER_BIND ?? "127.0.0.1";
|
||||||
|
const BIND_PORT = Number(process.env.CLAW_BROKER_PORT ?? "8787");
|
||||||
|
const REQUIRED_TOKEN = (process.env.CLAW_BROKER_TOKEN ?? "").trim();
|
||||||
|
|
||||||
|
function env(name, fallback = undefined) {
|
||||||
|
return process.env[`CLAW_${name}`] ?? process.env[name] ?? fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requiredEnv(name) {
|
||||||
|
const value = env(name, "");
|
||||||
|
if (!value || !String(value).trim()) {
|
||||||
|
throw new Error(`missing env: ${name} (or CLAW_${name})`);
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
host: requiredEnv("PGHOST"),
|
||||||
|
port: Number(env("PGPORT", "5432")),
|
||||||
|
user: requiredEnv("PGUSER"),
|
||||||
|
password: requiredEnv("PGPASSWORD"),
|
||||||
|
database: requiredEnv("PGDATABASE"),
|
||||||
|
max: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!REQUIRED_TOKEN) {
|
||||||
|
throw new Error("missing CLAW_BROKER_TOKEN");
|
||||||
|
}
|
||||||
|
|
||||||
|
function json(res, code, body) {
|
||||||
|
const payload = JSON.stringify(body);
|
||||||
|
res.writeHead(code, {
|
||||||
|
"content-type": "application/json; charset=utf-8",
|
||||||
|
"content-length": Buffer.byteLength(payload),
|
||||||
|
});
|
||||||
|
res.end(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCommand(input) {
|
||||||
|
return String(input).trim().replace(/\s+/g, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasDangerousShellConstruct(command) {
|
||||||
|
const source = String(command).toLowerCase();
|
||||||
|
const checks = [
|
||||||
|
/\bbash\s+-c\b/,
|
||||||
|
/\bsh\s+-c\b/,
|
||||||
|
/\bsudo\s+su\b/,
|
||||||
|
/\bsudo\s+-i\b/,
|
||||||
|
/&&/,
|
||||||
|
/\|\|/,
|
||||||
|
/;/,
|
||||||
|
/\|/,
|
||||||
|
/>/,
|
||||||
|
/</,
|
||||||
|
/\$\(/,
|
||||||
|
/`/,
|
||||||
|
/<<[-\w]*/,
|
||||||
|
];
|
||||||
|
return checks.some((r) => r.test(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
function summarize(text) {
|
||||||
|
const value = String(text ?? "");
|
||||||
|
return value.length <= MAX_SUMMARY_CHARS ? value : `${value.slice(0, MAX_SUMMARY_CHARS)}…`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function insertAudit(client, args) {
|
||||||
|
await client.query(
|
||||||
|
`INSERT INTO claw_audit_events (
|
||||||
|
event_type, request_id, grant_id, execution_id,
|
||||||
|
actor_type, actor_id, target_host, target_user,
|
||||||
|
command_snapshot, status, exit_code, stdout_summary, stderr_summary, metadata
|
||||||
|
) VALUES (
|
||||||
|
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14::jsonb
|
||||||
|
)`,
|
||||||
|
[
|
||||||
|
args.eventType,
|
||||||
|
args.requestId ?? null,
|
||||||
|
args.grantId ?? null,
|
||||||
|
args.executionId ?? null,
|
||||||
|
args.actorType,
|
||||||
|
args.actorId,
|
||||||
|
args.targetHost ?? null,
|
||||||
|
args.targetUser ?? null,
|
||||||
|
args.commandSnapshot ?? null,
|
||||||
|
args.status ?? null,
|
||||||
|
args.exitCode ?? null,
|
||||||
|
args.stdoutSummary ?? null,
|
||||||
|
args.stderrSummary ?? null,
|
||||||
|
JSON.stringify(args.metadata ?? {}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireString(body, key) {
|
||||||
|
const value = body?.[key];
|
||||||
|
if (typeof value !== "string" || value.trim().length === 0) {
|
||||||
|
throw new Error(`${key} is required`);
|
||||||
|
}
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyAndMarkStarted(body) {
|
||||||
|
const executionId = requireString(body, "executionId");
|
||||||
|
const approvalRequestId = requireString(body, "approvalRequestId");
|
||||||
|
const approvalGrantId = requireString(body, "approvalGrantId");
|
||||||
|
const exactCommand = requireString(body, "exactCommand");
|
||||||
|
const targetHost = requireString(body, "targetHost");
|
||||||
|
const targetUser = requireString(body, "targetUser");
|
||||||
|
const requestedBy = requireString(body, "requestedBy");
|
||||||
|
const channel = requireString(body, "channel");
|
||||||
|
const chatId = requireString(body, "chatId");
|
||||||
|
const humanUserId = requireString(body, "humanUserId");
|
||||||
|
const sessionId = requireString(body, "sessionId");
|
||||||
|
|
||||||
|
if (hasDangerousShellConstruct(exactCommand)) {
|
||||||
|
throw new Error("dangerous shell policy violation");
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
await client.query("BEGIN");
|
||||||
|
|
||||||
|
const reqRes = await client.query(
|
||||||
|
`SELECT * FROM claw_approval_requests WHERE id = $1 FOR UPDATE`,
|
||||||
|
[approvalRequestId],
|
||||||
|
);
|
||||||
|
if (reqRes.rowCount === 0) {
|
||||||
|
throw new Error("approval request not found");
|
||||||
|
}
|
||||||
|
const request = reqRes.rows[0];
|
||||||
|
|
||||||
|
if (!["approved_once", "approved_always"].includes(String(request.status))) {
|
||||||
|
throw new Error(`request status does not allow execution: ${request.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const grantRes = await client.query(
|
||||||
|
`SELECT * FROM claw_approval_grants WHERE id = $1 AND request_id = $2 FOR UPDATE`,
|
||||||
|
[approvalGrantId, approvalRequestId],
|
||||||
|
);
|
||||||
|
if (grantRes.rowCount === 0) {
|
||||||
|
throw new Error("approval grant not found");
|
||||||
|
}
|
||||||
|
const grant = grantRes.rows[0];
|
||||||
|
|
||||||
|
const dbExact = String(request.exact_command);
|
||||||
|
if (normalizeCommand(dbExact) !== normalizeCommand(exactCommand)) {
|
||||||
|
throw new Error("exact command mismatch");
|
||||||
|
}
|
||||||
|
if (normalizeCommand(String(grant.exact_command)) !== normalizeCommand(exactCommand)) {
|
||||||
|
throw new Error("grant command mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
const scopeChecks = [
|
||||||
|
[String(request.target_host), targetHost, "targetHost"],
|
||||||
|
[String(request.target_user), targetUser, "targetUser"],
|
||||||
|
[String(request.channel), channel, "channel"],
|
||||||
|
[String(request.chat_id), chatId, "chatId"],
|
||||||
|
[String(request.human_user_id), humanUserId, "humanUserId"],
|
||||||
|
[String(request.session_id), sessionId, "sessionId"],
|
||||||
|
[String(grant.target_host), targetHost, "grant.targetHost"],
|
||||||
|
[String(grant.target_user), targetUser, "grant.targetUser"],
|
||||||
|
[String(grant.channel), channel, "grant.channel"],
|
||||||
|
[String(grant.chat_id), chatId, "grant.chatId"],
|
||||||
|
[String(grant.human_user_id), humanUserId, "grant.humanUserId"],
|
||||||
|
[String(grant.session_id), sessionId, "grant.sessionId"],
|
||||||
|
];
|
||||||
|
for (const [db, incoming, label] of scopeChecks) {
|
||||||
|
if (db !== incoming) {
|
||||||
|
throw new Error(`scope mismatch: ${label}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasDangerousShellConstruct(String(request.exact_command))) {
|
||||||
|
throw new Error("dangerous shell policy violation (request)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String(grant.grant_type) === "once") {
|
||||||
|
const consumeRes = await client.query(
|
||||||
|
`UPDATE claw_approval_grants
|
||||||
|
SET used_at = now()
|
||||||
|
WHERE id = $1
|
||||||
|
AND grant_type = 'once'
|
||||||
|
AND used_at IS NULL
|
||||||
|
AND revoked_at IS NULL
|
||||||
|
AND expires_at > now()
|
||||||
|
RETURNING id`,
|
||||||
|
[approvalGrantId],
|
||||||
|
);
|
||||||
|
if (consumeRes.rowCount === 0) {
|
||||||
|
throw new Error("once grant expired/revoked/already used");
|
||||||
|
}
|
||||||
|
await insertAudit(client, {
|
||||||
|
eventType: "grant_consumed",
|
||||||
|
actorType: "broker",
|
||||||
|
actorId: requestedBy,
|
||||||
|
requestId: approvalRequestId,
|
||||||
|
grantId: approvalGrantId,
|
||||||
|
executionId,
|
||||||
|
targetHost,
|
||||||
|
targetUser,
|
||||||
|
commandSnapshot: exactCommand,
|
||||||
|
status: "grant_consumed",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
`UPDATE claw_approval_requests SET execution_id = $2, updated_at = now() WHERE id = $1`,
|
||||||
|
[approvalRequestId, executionId],
|
||||||
|
);
|
||||||
|
|
||||||
|
await insertAudit(client, {
|
||||||
|
eventType: "execution_started",
|
||||||
|
actorType: "broker",
|
||||||
|
actorId: requestedBy,
|
||||||
|
requestId: approvalRequestId,
|
||||||
|
grantId: approvalGrantId,
|
||||||
|
executionId,
|
||||||
|
targetHost,
|
||||||
|
targetUser,
|
||||||
|
commandSnapshot: exactCommand,
|
||||||
|
status: "execution_started",
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.query("COMMIT");
|
||||||
|
return {
|
||||||
|
executionId,
|
||||||
|
approvalRequestId,
|
||||||
|
approvalGrantId,
|
||||||
|
exactCommand,
|
||||||
|
targetHost,
|
||||||
|
targetUser,
|
||||||
|
requestedBy,
|
||||||
|
cwd: request.cwd ? String(request.cwd) : undefined,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runCommand(command, cwd) {
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
const child = spawn("bash", ["-lc", command], {
|
||||||
|
cwd: cwd || undefined,
|
||||||
|
env: process.env,
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
});
|
||||||
|
|
||||||
|
let stdout = "";
|
||||||
|
let stderr = "";
|
||||||
|
let timedOut = false;
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
timedOut = true;
|
||||||
|
child.kill("SIGKILL");
|
||||||
|
}, CMD_TIMEOUT_MS);
|
||||||
|
|
||||||
|
child.stdout.on("data", (chunk) => {
|
||||||
|
stdout += chunk.toString("utf8");
|
||||||
|
});
|
||||||
|
child.stderr.on("data", (chunk) => {
|
||||||
|
stderr += chunk.toString("utf8");
|
||||||
|
});
|
||||||
|
child.on("close", (code) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve({
|
||||||
|
exitCode: timedOut ? 124 : Number(code ?? 1),
|
||||||
|
stdout,
|
||||||
|
stderr: timedOut ? `${stderr}\nCommand timed out.` : stderr,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function finalizeExecution({
|
||||||
|
executionId,
|
||||||
|
approvalRequestId,
|
||||||
|
approvalGrantId,
|
||||||
|
exactCommand,
|
||||||
|
targetHost,
|
||||||
|
targetUser,
|
||||||
|
requestedBy,
|
||||||
|
ok,
|
||||||
|
exitCode,
|
||||||
|
stdoutSummary,
|
||||||
|
stderrSummary,
|
||||||
|
}) {
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
await client.query("BEGIN");
|
||||||
|
const finalStatus = ok ? "executed" : "execution_failed";
|
||||||
|
const lastError = ok ? null : stderrSummary;
|
||||||
|
await client.query(
|
||||||
|
`UPDATE claw_approval_requests
|
||||||
|
SET status = $2::claw_approval_status,
|
||||||
|
executed_at = now(),
|
||||||
|
updated_at = now(),
|
||||||
|
last_error = $3
|
||||||
|
WHERE id = $1`,
|
||||||
|
[approvalRequestId, finalStatus, lastError],
|
||||||
|
);
|
||||||
|
await insertAudit(client, {
|
||||||
|
eventType: ok ? "execution_succeeded" : "execution_failed",
|
||||||
|
actorType: "broker",
|
||||||
|
actorId: requestedBy,
|
||||||
|
requestId: approvalRequestId,
|
||||||
|
grantId: approvalGrantId,
|
||||||
|
executionId,
|
||||||
|
targetHost,
|
||||||
|
targetUser,
|
||||||
|
commandSnapshot: exactCommand,
|
||||||
|
status: ok ? "executed" : "execution_failed",
|
||||||
|
exitCode,
|
||||||
|
stdoutSummary,
|
||||||
|
stderrSummary,
|
||||||
|
});
|
||||||
|
await client.query("COMMIT");
|
||||||
|
} catch (err) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleExecute(body) {
|
||||||
|
const startedAt = new Date().toISOString();
|
||||||
|
const validated = await verifyAndMarkStarted(body);
|
||||||
|
const run = await runCommand(validated.exactCommand, validated.cwd);
|
||||||
|
const ok = run.exitCode === 0;
|
||||||
|
const stdoutSummary = summarize(run.stdout);
|
||||||
|
const stderrSummary = summarize(run.stderr);
|
||||||
|
|
||||||
|
await finalizeExecution({
|
||||||
|
executionId: validated.executionId,
|
||||||
|
approvalRequestId: validated.approvalRequestId,
|
||||||
|
approvalGrantId: validated.approvalGrantId,
|
||||||
|
exactCommand: validated.exactCommand,
|
||||||
|
targetHost: validated.targetHost,
|
||||||
|
targetUser: validated.targetUser,
|
||||||
|
requestedBy: validated.requestedBy,
|
||||||
|
ok,
|
||||||
|
exitCode: run.exitCode,
|
||||||
|
stdoutSummary,
|
||||||
|
stderrSummary,
|
||||||
|
});
|
||||||
|
|
||||||
|
const finishedAt = new Date().toISOString();
|
||||||
|
return {
|
||||||
|
ok,
|
||||||
|
executionId: validated.executionId,
|
||||||
|
status: ok ? "executed" : "execution_failed",
|
||||||
|
exitCode: run.exitCode,
|
||||||
|
stdoutSummary,
|
||||||
|
stderrSummary,
|
||||||
|
startedAt,
|
||||||
|
finishedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBearerToken(req) {
|
||||||
|
const raw = String(req.headers.authorization ?? "");
|
||||||
|
if (!raw.toLowerCase().startsWith("bearer ")) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return raw.slice(7).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "127.0.0.1"}`);
|
||||||
|
|
||||||
|
if (req.method === "GET" && url.pathname === "/healthz") {
|
||||||
|
json(res, 200, { ok: true, service: "claw-broker" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method !== "POST" || url.pathname !== "/v1/execute") {
|
||||||
|
json(res, 404, { ok: false, error: "not_found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getBearerToken(req);
|
||||||
|
if (!token || token !== REQUIRED_TOKEN) {
|
||||||
|
json(res, 401, { ok: false, error: "unauthorized" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw = "";
|
||||||
|
req.on("data", (chunk) => {
|
||||||
|
raw += chunk.toString("utf8");
|
||||||
|
if (raw.length > 1_000_000) {
|
||||||
|
req.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
req.on("end", async () => {
|
||||||
|
const fallbackExecutionId = randomUUID();
|
||||||
|
try {
|
||||||
|
const body = raw.length ? JSON.parse(raw) : {};
|
||||||
|
if (!body.executionId) {
|
||||||
|
body.executionId = fallbackExecutionId;
|
||||||
|
}
|
||||||
|
const result = await handleExecute(body);
|
||||||
|
json(res, 200, result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[claw-broker] execute error:", err);
|
||||||
|
const nowIso = new Date().toISOString();
|
||||||
|
json(res, 400, {
|
||||||
|
ok: false,
|
||||||
|
executionId: fallbackExecutionId,
|
||||||
|
status: "execution_failed",
|
||||||
|
exitCode: 1,
|
||||||
|
stdoutSummary: "",
|
||||||
|
stderrSummary: String(err),
|
||||||
|
startedAt: nowIso,
|
||||||
|
finishedAt: nowIso,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(BIND_PORT, BIND_HOST, () => {
|
||||||
|
process.stdout.write(`claw-broker listening on http://${BIND_HOST}:${BIND_PORT}\n`);
|
||||||
|
});
|
||||||
20
scripts/claw-broker/claw-broker.service
Normal file
20
scripts/claw-broker/claw-broker.service
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=OpenClaw Privileged Broker (MVP)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/home/negodiy/claw-broker
|
||||||
|
EnvironmentFile=/home/negodiy/claw-broker/.env
|
||||||
|
ExecStart=/usr/bin/node /home/negodiy/claw-broker/broker.mjs
|
||||||
|
Restart=always
|
||||||
|
RestartSec=2
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=full
|
||||||
|
ProtectHome=no
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
13
scripts/claw-broker/package.json
Normal file
13
scripts/claw-broker/package.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "claw-broker",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "broker.mjs",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node broker.mjs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"pg": "^8.20.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
682
src/gateway/claw-approvals-store.ts
Normal file
682
src/gateway/claw-approvals-store.ts
Normal file
@@ -0,0 +1,682 @@
|
|||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
import { Pool, type PoolClient } from "pg";
|
||||||
|
|
||||||
|
export type ClawApprovalStatus =
|
||||||
|
| "pending"
|
||||||
|
| "approved_once"
|
||||||
|
| "approved_always"
|
||||||
|
| "rejected"
|
||||||
|
| "expired"
|
||||||
|
| "executed"
|
||||||
|
| "execution_failed";
|
||||||
|
|
||||||
|
export type ClawRiskLevel = "low" | "medium" | "high";
|
||||||
|
|
||||||
|
export type ClawApprovalRequestRow = {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
requestedByAgent: string;
|
||||||
|
sessionId: string;
|
||||||
|
channel: string;
|
||||||
|
chatId: string;
|
||||||
|
humanUserId: string;
|
||||||
|
targetHost: string;
|
||||||
|
targetUser: string;
|
||||||
|
cwd: string | null;
|
||||||
|
humanSummary: string;
|
||||||
|
reason: string;
|
||||||
|
exactCommand: string;
|
||||||
|
normalizedCommand: string;
|
||||||
|
riskLevel: ClawRiskLevel;
|
||||||
|
rollbackHint: string | null;
|
||||||
|
requiresPrivilege: boolean;
|
||||||
|
dangerousFlags: Record<string, boolean>;
|
||||||
|
status: ClawApprovalStatus;
|
||||||
|
statusReason: string | null;
|
||||||
|
approvedBy: string | null;
|
||||||
|
approvedAt: string | null;
|
||||||
|
rejectedBy: string | null;
|
||||||
|
rejectedAt: string | null;
|
||||||
|
expiredAt: string | null;
|
||||||
|
executedAt: string | null;
|
||||||
|
executionId: string | null;
|
||||||
|
lastError: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CreateClawApprovalRequestInput = {
|
||||||
|
requestedByAgent: string;
|
||||||
|
sessionId: string;
|
||||||
|
channel: string;
|
||||||
|
chatId: string;
|
||||||
|
humanUserId: string;
|
||||||
|
targetHost: string;
|
||||||
|
targetUser: string;
|
||||||
|
cwd?: string | null;
|
||||||
|
humanSummary: string;
|
||||||
|
reason: string;
|
||||||
|
exactCommand: string;
|
||||||
|
riskLevel: ClawRiskLevel;
|
||||||
|
rollbackHint?: string | null;
|
||||||
|
dangerousFlags?: Record<string, boolean>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ClawApproveInput = {
|
||||||
|
id: string;
|
||||||
|
actorId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ClawApproveOnceInput = ClawApproveInput & {
|
||||||
|
ttlSeconds: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ClawRejectInput = ClawApproveInput & {
|
||||||
|
reason?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ClawExecuteInput = {
|
||||||
|
id: string;
|
||||||
|
grantId: string;
|
||||||
|
actorId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BrokerExecutePayload = {
|
||||||
|
executionId: string;
|
||||||
|
approvalRequestId: string;
|
||||||
|
approvalGrantId: string;
|
||||||
|
exactCommand: string;
|
||||||
|
targetHost: string;
|
||||||
|
targetUser: string;
|
||||||
|
requestedBy: string;
|
||||||
|
channel: string;
|
||||||
|
chatId: string;
|
||||||
|
humanUserId: string;
|
||||||
|
sessionId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BrokerExecuteResult = {
|
||||||
|
executionId: string;
|
||||||
|
status: string;
|
||||||
|
ok: boolean;
|
||||||
|
exitCode: number;
|
||||||
|
stdoutSummary?: string;
|
||||||
|
stderrSummary?: string;
|
||||||
|
startedAt: string;
|
||||||
|
finishedAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let pool: Pool | null = null;
|
||||||
|
|
||||||
|
function resolveEnv(name: string): string | undefined {
|
||||||
|
return process.env[`CLAW_${name}`] ?? process.env[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireEnv(name: string): string {
|
||||||
|
const v = resolveEnv(name);
|
||||||
|
if (!v || !v.trim()) {
|
||||||
|
throw new Error(`missing required environment variable: ${name} (or CLAW_${name})`);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPool(): Pool {
|
||||||
|
if (pool) {
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = requireEnv("PGHOST");
|
||||||
|
const portRaw = resolveEnv("PGPORT") ?? "5432";
|
||||||
|
const user = requireEnv("PGUSER");
|
||||||
|
const password = requireEnv("PGPASSWORD");
|
||||||
|
const database = requireEnv("PGDATABASE");
|
||||||
|
|
||||||
|
const port = Number(portRaw);
|
||||||
|
if (!Number.isFinite(port) || port <= 0) {
|
||||||
|
throw new Error(`invalid PGPORT: ${portRaw}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
pool = new Pool({
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
database,
|
||||||
|
max: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeCommand(input: string): string {
|
||||||
|
return input.trim().replace(/\s+/g, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasDangerousShellConstruct(command: string): boolean {
|
||||||
|
const source = command.toLowerCase();
|
||||||
|
const checks: RegExp[] = [
|
||||||
|
/\bbash\s+-c\b/,
|
||||||
|
/\bsh\s+-c\b/,
|
||||||
|
/\bsudo\s+su\b/,
|
||||||
|
/\bsudo\s+-i\b/,
|
||||||
|
/&&/,
|
||||||
|
/\|\|/,
|
||||||
|
/;/,
|
||||||
|
/\|/,
|
||||||
|
/>/,
|
||||||
|
/</,
|
||||||
|
/\$\(/,
|
||||||
|
/`/,
|
||||||
|
/<<[-\w]*/,
|
||||||
|
];
|
||||||
|
return checks.some((r) => r.test(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapRequestRow(row: Record<string, unknown>): ClawApprovalRequestRow {
|
||||||
|
return {
|
||||||
|
id: String(row.id),
|
||||||
|
createdAt: String(row.created_at),
|
||||||
|
updatedAt: String(row.updated_at),
|
||||||
|
requestedByAgent: String(row.requested_by_agent),
|
||||||
|
sessionId: String(row.session_id),
|
||||||
|
channel: String(row.channel),
|
||||||
|
chatId: String(row.chat_id),
|
||||||
|
humanUserId: String(row.human_user_id),
|
||||||
|
targetHost: String(row.target_host),
|
||||||
|
targetUser: String(row.target_user),
|
||||||
|
cwd: (row.cwd as string | null) ?? null,
|
||||||
|
humanSummary: String(row.human_summary),
|
||||||
|
reason: String(row.reason),
|
||||||
|
exactCommand: String(row.exact_command),
|
||||||
|
normalizedCommand: String(row.normalized_command),
|
||||||
|
riskLevel: String(row.risk_level) as ClawRiskLevel,
|
||||||
|
rollbackHint: (row.rollback_hint as string | null) ?? null,
|
||||||
|
requiresPrivilege: Boolean(row.requires_privilege),
|
||||||
|
dangerousFlags: (row.dangerous_flags as Record<string, boolean> | null) ?? {},
|
||||||
|
status: String(row.status) as ClawApprovalStatus,
|
||||||
|
statusReason: (row.status_reason as string | null) ?? null,
|
||||||
|
approvedBy: (row.approved_by as string | null) ?? null,
|
||||||
|
approvedAt: (row.approved_at as string | null) ?? null,
|
||||||
|
rejectedBy: (row.rejected_by as string | null) ?? null,
|
||||||
|
rejectedAt: (row.rejected_at as string | null) ?? null,
|
||||||
|
expiredAt: (row.expired_at as string | null) ?? null,
|
||||||
|
executedAt: (row.executed_at as string | null) ?? null,
|
||||||
|
executionId: (row.execution_id as string | null) ?? null,
|
||||||
|
lastError: (row.last_error as string | null) ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function insertAudit(
|
||||||
|
client: PoolClient,
|
||||||
|
args: {
|
||||||
|
eventType: string;
|
||||||
|
actorType: "agent" | "human" | "backend" | "broker" | "system";
|
||||||
|
actorId: string;
|
||||||
|
requestId?: string;
|
||||||
|
grantId?: string;
|
||||||
|
executionId?: string;
|
||||||
|
targetHost?: string;
|
||||||
|
targetUser?: string;
|
||||||
|
commandSnapshot?: string;
|
||||||
|
status?: string;
|
||||||
|
exitCode?: number;
|
||||||
|
stdoutSummary?: string;
|
||||||
|
stderrSummary?: string;
|
||||||
|
metadata?: Record<string, unknown>;
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
await client.query(
|
||||||
|
`INSERT INTO claw_audit_events (
|
||||||
|
event_type,
|
||||||
|
request_id,
|
||||||
|
grant_id,
|
||||||
|
execution_id,
|
||||||
|
actor_type,
|
||||||
|
actor_id,
|
||||||
|
target_host,
|
||||||
|
target_user,
|
||||||
|
command_snapshot,
|
||||||
|
status,
|
||||||
|
exit_code,
|
||||||
|
stdout_summary,
|
||||||
|
stderr_summary,
|
||||||
|
metadata
|
||||||
|
) VALUES (
|
||||||
|
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14::jsonb
|
||||||
|
)`,
|
||||||
|
[
|
||||||
|
args.eventType,
|
||||||
|
args.requestId ?? null,
|
||||||
|
args.grantId ?? null,
|
||||||
|
args.executionId ?? null,
|
||||||
|
args.actorType,
|
||||||
|
args.actorId,
|
||||||
|
args.targetHost ?? null,
|
||||||
|
args.targetUser ?? null,
|
||||||
|
args.commandSnapshot ?? null,
|
||||||
|
args.status ?? null,
|
||||||
|
args.exitCode ?? null,
|
||||||
|
args.stdoutSummary ?? null,
|
||||||
|
args.stderrSummary ?? null,
|
||||||
|
JSON.stringify(args.metadata ?? {}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ClawApprovalsStore {
|
||||||
|
async createApprovalRequest(
|
||||||
|
input: CreateClawApprovalRequestInput,
|
||||||
|
): Promise<ClawApprovalRequestRow> {
|
||||||
|
const normalizedCommand = normalizeCommand(input.exactCommand);
|
||||||
|
const dangerousFlags = {
|
||||||
|
hasDangerousShell: hasDangerousShellConstruct(input.exactCommand),
|
||||||
|
...input.dangerousFlags,
|
||||||
|
};
|
||||||
|
|
||||||
|
const db = getPool();
|
||||||
|
const client = await db.connect();
|
||||||
|
try {
|
||||||
|
await client.query("BEGIN");
|
||||||
|
const res = await client.query(
|
||||||
|
`INSERT INTO claw_approval_requests (
|
||||||
|
requested_by_agent, session_id, channel, chat_id, human_user_id,
|
||||||
|
target_host, target_user, cwd,
|
||||||
|
human_summary, reason, exact_command, normalized_command,
|
||||||
|
risk_level, rollback_hint, requires_privilege, dangerous_flags, status
|
||||||
|
) VALUES (
|
||||||
|
$1,$2,$3,$4,$5,
|
||||||
|
$6,$7,$8,
|
||||||
|
$9,$10,$11,$12,
|
||||||
|
$13,$14,TRUE,$15::jsonb,'pending'
|
||||||
|
) RETURNING *`,
|
||||||
|
[
|
||||||
|
input.requestedByAgent,
|
||||||
|
input.sessionId,
|
||||||
|
input.channel,
|
||||||
|
input.chatId,
|
||||||
|
input.humanUserId,
|
||||||
|
input.targetHost,
|
||||||
|
input.targetUser,
|
||||||
|
input.cwd ?? null,
|
||||||
|
input.humanSummary,
|
||||||
|
input.reason,
|
||||||
|
input.exactCommand,
|
||||||
|
normalizedCommand,
|
||||||
|
input.riskLevel,
|
||||||
|
input.rollbackHint ?? null,
|
||||||
|
JSON.stringify(dangerousFlags),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const row = mapRequestRow(res.rows[0]);
|
||||||
|
await insertAudit(client, {
|
||||||
|
eventType: "request_created",
|
||||||
|
actorType: "agent",
|
||||||
|
actorId: input.requestedByAgent,
|
||||||
|
requestId: row.id,
|
||||||
|
targetHost: row.targetHost,
|
||||||
|
targetUser: row.targetUser,
|
||||||
|
commandSnapshot: row.exactCommand,
|
||||||
|
status: row.status,
|
||||||
|
});
|
||||||
|
await client.query("COMMIT");
|
||||||
|
return row;
|
||||||
|
} catch (err) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async listApprovalRequests(status?: string): Promise<ClawApprovalRequestRow[]> {
|
||||||
|
const db = getPool();
|
||||||
|
const res = await db.query(
|
||||||
|
status && status.trim().length > 0
|
||||||
|
? `SELECT * FROM claw_approval_requests WHERE status = $1 ORDER BY created_at DESC LIMIT 200`
|
||||||
|
: `SELECT * FROM claw_approval_requests ORDER BY created_at DESC LIMIT 200`,
|
||||||
|
status && status.trim().length > 0 ? [status.trim()] : [],
|
||||||
|
);
|
||||||
|
return res.rows.map(mapRequestRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getApprovalRequest(id: string): Promise<ClawApprovalRequestRow | null> {
|
||||||
|
const db = getPool();
|
||||||
|
const res = await db.query(`SELECT * FROM claw_approval_requests WHERE id = $1 LIMIT 1`, [id]);
|
||||||
|
if (res.rowCount === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return mapRequestRow(res.rows[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async approveOnce(
|
||||||
|
input: ClawApproveOnceInput,
|
||||||
|
): Promise<{ request: ClawApprovalRequestRow; grantId: string; expiresAt: string }> {
|
||||||
|
const db = getPool();
|
||||||
|
const client = await db.connect();
|
||||||
|
try {
|
||||||
|
await client.query("BEGIN");
|
||||||
|
const reqRes = await client.query(
|
||||||
|
`SELECT * FROM claw_approval_requests WHERE id = $1 FOR UPDATE`,
|
||||||
|
[input.id],
|
||||||
|
);
|
||||||
|
if (reqRes.rowCount === 0) {
|
||||||
|
throw new Error("approval request not found");
|
||||||
|
}
|
||||||
|
const req = mapRequestRow(reqRes.rows[0]);
|
||||||
|
if (req.status !== "pending") {
|
||||||
|
throw new Error(`cannot approve_once from status=${req.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ttl = Math.max(120, Math.min(300, Math.trunc(input.ttlSeconds || 180)));
|
||||||
|
const grantRes = await client.query(
|
||||||
|
`INSERT INTO claw_approval_grants (
|
||||||
|
request_id, grant_type, match_type,
|
||||||
|
target_host, target_user, channel, chat_id, human_user_id, session_id,
|
||||||
|
exact_command, normalized_command, approved_by, expires_at
|
||||||
|
) VALUES (
|
||||||
|
$1,'once','exact',$2,$3,$4,$5,$6,$7,$8,$9,$10, now() + ($11 || ' seconds')::interval
|
||||||
|
) RETURNING id, expires_at`,
|
||||||
|
[
|
||||||
|
req.id,
|
||||||
|
req.targetHost,
|
||||||
|
req.targetUser,
|
||||||
|
req.channel,
|
||||||
|
req.chatId,
|
||||||
|
req.humanUserId,
|
||||||
|
req.sessionId,
|
||||||
|
req.exactCommand,
|
||||||
|
req.normalizedCommand,
|
||||||
|
input.actorId,
|
||||||
|
String(ttl),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
`UPDATE claw_approval_requests
|
||||||
|
SET status='approved_once', approved_by=$2, approved_at=now(), updated_at=now()
|
||||||
|
WHERE id = $1`,
|
||||||
|
[req.id, input.actorId],
|
||||||
|
);
|
||||||
|
|
||||||
|
await insertAudit(client, {
|
||||||
|
eventType: "request_approved_once",
|
||||||
|
actorType: "human",
|
||||||
|
actorId: input.actorId,
|
||||||
|
requestId: req.id,
|
||||||
|
grantId: String(grantRes.rows[0].id),
|
||||||
|
targetHost: req.targetHost,
|
||||||
|
targetUser: req.targetUser,
|
||||||
|
commandSnapshot: req.exactCommand,
|
||||||
|
status: "approved_once",
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.query("COMMIT");
|
||||||
|
const next = await this.getApprovalRequest(req.id);
|
||||||
|
if (!next) {
|
||||||
|
throw new Error("approval request not found after approve_once");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
request: next,
|
||||||
|
grantId: String(grantRes.rows[0].id),
|
||||||
|
expiresAt: String(grantRes.rows[0].expires_at),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async approveAlways(
|
||||||
|
input: ClawApproveInput,
|
||||||
|
): Promise<{ request: ClawApprovalRequestRow; grantId: string; allowRuleId: string }> {
|
||||||
|
const db = getPool();
|
||||||
|
const client = await db.connect();
|
||||||
|
try {
|
||||||
|
await client.query("BEGIN");
|
||||||
|
const reqRes = await client.query(
|
||||||
|
`SELECT * FROM claw_approval_requests WHERE id = $1 FOR UPDATE`,
|
||||||
|
[input.id],
|
||||||
|
);
|
||||||
|
if (reqRes.rowCount === 0) {
|
||||||
|
throw new Error("approval request not found");
|
||||||
|
}
|
||||||
|
const req = mapRequestRow(reqRes.rows[0]);
|
||||||
|
if (req.status !== "pending") {
|
||||||
|
throw new Error(`cannot approve_always from status=${req.status}`);
|
||||||
|
}
|
||||||
|
if (hasDangerousShellConstruct(req.exactCommand)) {
|
||||||
|
throw new Error("always allow is forbidden for dangerous shell constructs");
|
||||||
|
}
|
||||||
|
|
||||||
|
const grantRes = await client.query(
|
||||||
|
`INSERT INTO claw_approval_grants (
|
||||||
|
request_id, grant_type, match_type,
|
||||||
|
target_host, target_user, channel, chat_id, human_user_id, session_id,
|
||||||
|
exact_command, normalized_command, approved_by
|
||||||
|
) VALUES (
|
||||||
|
$1,'always','exact',$2,$3,$4,$5,$6,$7,$8,$9,$10
|
||||||
|
) RETURNING id`,
|
||||||
|
[
|
||||||
|
req.id,
|
||||||
|
req.targetHost,
|
||||||
|
req.targetUser,
|
||||||
|
req.channel,
|
||||||
|
req.chatId,
|
||||||
|
req.humanUserId,
|
||||||
|
req.sessionId,
|
||||||
|
req.exactCommand,
|
||||||
|
req.normalizedCommand,
|
||||||
|
input.actorId,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const ruleRes = await client.query(
|
||||||
|
`INSERT INTO claw_allow_rules (
|
||||||
|
created_by, source_request_id,
|
||||||
|
target_host, target_user, channel, chat_id, human_user_id,
|
||||||
|
command_pattern_type, command_pattern, normalized_pattern, enabled
|
||||||
|
) VALUES (
|
||||||
|
$1,$2,$3,$4,$5,$6,$7,'exact',$8,$9,TRUE
|
||||||
|
)
|
||||||
|
ON CONFLICT ON CONSTRAINT uq_claw_allow_rules_active_exact
|
||||||
|
DO UPDATE SET updated_at=now(), enabled=TRUE
|
||||||
|
RETURNING id`,
|
||||||
|
[
|
||||||
|
input.actorId,
|
||||||
|
req.id,
|
||||||
|
req.targetHost,
|
||||||
|
req.targetUser,
|
||||||
|
req.channel,
|
||||||
|
req.chatId,
|
||||||
|
req.humanUserId,
|
||||||
|
req.exactCommand,
|
||||||
|
req.normalizedCommand,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
`UPDATE claw_approval_requests
|
||||||
|
SET status='approved_always', approved_by=$2, approved_at=now(), updated_at=now()
|
||||||
|
WHERE id = $1`,
|
||||||
|
[req.id, input.actorId],
|
||||||
|
);
|
||||||
|
|
||||||
|
await insertAudit(client, {
|
||||||
|
eventType: "request_approved_always",
|
||||||
|
actorType: "human",
|
||||||
|
actorId: input.actorId,
|
||||||
|
requestId: req.id,
|
||||||
|
grantId: String(grantRes.rows[0].id),
|
||||||
|
targetHost: req.targetHost,
|
||||||
|
targetUser: req.targetUser,
|
||||||
|
commandSnapshot: req.exactCommand,
|
||||||
|
status: "approved_always",
|
||||||
|
metadata: { allowRuleId: String(ruleRes.rows[0].id) },
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.query("COMMIT");
|
||||||
|
const next = await this.getApprovalRequest(req.id);
|
||||||
|
if (!next) {
|
||||||
|
throw new Error("approval request not found after approve_always");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
request: next,
|
||||||
|
grantId: String(grantRes.rows[0].id),
|
||||||
|
allowRuleId: String(ruleRes.rows[0].id),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async reject(input: ClawRejectInput): Promise<ClawApprovalRequestRow> {
|
||||||
|
const db = getPool();
|
||||||
|
const client = await db.connect();
|
||||||
|
try {
|
||||||
|
await client.query("BEGIN");
|
||||||
|
const reqRes = await client.query(
|
||||||
|
`SELECT * FROM claw_approval_requests WHERE id = $1 FOR UPDATE`,
|
||||||
|
[input.id],
|
||||||
|
);
|
||||||
|
if (reqRes.rowCount === 0) {
|
||||||
|
throw new Error("approval request not found");
|
||||||
|
}
|
||||||
|
const req = mapRequestRow(reqRes.rows[0]);
|
||||||
|
if (req.status !== "pending") {
|
||||||
|
throw new Error(`cannot reject from status=${req.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
`UPDATE claw_approval_requests
|
||||||
|
SET status='rejected', rejected_by=$2, rejected_at=now(), status_reason=$3, updated_at=now()
|
||||||
|
WHERE id = $1`,
|
||||||
|
[req.id, input.actorId, input.reason ?? null],
|
||||||
|
);
|
||||||
|
|
||||||
|
await insertAudit(client, {
|
||||||
|
eventType: "request_rejected",
|
||||||
|
actorType: "human",
|
||||||
|
actorId: input.actorId,
|
||||||
|
requestId: req.id,
|
||||||
|
targetHost: req.targetHost,
|
||||||
|
targetUser: req.targetUser,
|
||||||
|
commandSnapshot: req.exactCommand,
|
||||||
|
status: "rejected",
|
||||||
|
metadata: { reason: input.reason ?? null },
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.query("COMMIT");
|
||||||
|
const next = await this.getApprovalRequest(req.id);
|
||||||
|
if (!next) {
|
||||||
|
throw new Error("approval request not found after reject");
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
} catch (err) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeApproved(
|
||||||
|
input: ClawExecuteInput,
|
||||||
|
runBroker: (payload: BrokerExecutePayload) => Promise<BrokerExecuteResult>,
|
||||||
|
): Promise<{
|
||||||
|
request: ClawApprovalRequestRow;
|
||||||
|
executionId: string;
|
||||||
|
broker: BrokerExecuteResult;
|
||||||
|
}> {
|
||||||
|
const db = getPool();
|
||||||
|
const client = await db.connect();
|
||||||
|
let request: ClawApprovalRequestRow | null = null;
|
||||||
|
try {
|
||||||
|
await client.query("BEGIN");
|
||||||
|
|
||||||
|
const reqRes = await client.query(
|
||||||
|
`SELECT * FROM claw_approval_requests WHERE id = $1 FOR UPDATE`,
|
||||||
|
[input.id],
|
||||||
|
);
|
||||||
|
if (reqRes.rowCount === 0) {
|
||||||
|
throw new Error("approval request not found");
|
||||||
|
}
|
||||||
|
request = mapRequestRow(reqRes.rows[0]);
|
||||||
|
if (request.status !== "approved_once" && request.status !== "approved_always") {
|
||||||
|
throw new Error(`cannot execute from status=${request.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const grantRes = await client.query(
|
||||||
|
`SELECT * FROM claw_approval_grants WHERE id = $1 AND request_id = $2 FOR UPDATE`,
|
||||||
|
[input.grantId, request.id],
|
||||||
|
);
|
||||||
|
if (grantRes.rowCount === 0) {
|
||||||
|
throw new Error("grant not found");
|
||||||
|
}
|
||||||
|
const grant = grantRes.rows[0] as Record<string, unknown>;
|
||||||
|
|
||||||
|
const exactCommand = String(grant.exact_command);
|
||||||
|
if (normalizeCommand(exactCommand) !== request.normalizedCommand) {
|
||||||
|
throw new Error("grant command mismatch with request");
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.query("COMMIT");
|
||||||
|
} catch (err) {
|
||||||
|
await client.query("ROLLBACK");
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
throw new Error("request resolution failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
const broker = await runBroker({
|
||||||
|
executionId: randomUUID(),
|
||||||
|
approvalRequestId: request.id,
|
||||||
|
approvalGrantId: input.grantId,
|
||||||
|
exactCommand: request.exactCommand,
|
||||||
|
targetHost: request.targetHost,
|
||||||
|
targetUser: request.targetUser,
|
||||||
|
requestedBy: input.actorId,
|
||||||
|
channel: request.channel,
|
||||||
|
chatId: request.chatId,
|
||||||
|
humanUserId: request.humanUserId,
|
||||||
|
sessionId: request.sessionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const latest = await this.getApprovalRequest(request.id);
|
||||||
|
if (!latest) {
|
||||||
|
throw new Error("approval request not found after execution");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
request: latest,
|
||||||
|
executionId: broker.executionId,
|
||||||
|
broker,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAuditTrail(requestId: string): Promise<Record<string, unknown>[]> {
|
||||||
|
const db = getPool();
|
||||||
|
const res = await db.query(
|
||||||
|
`SELECT * FROM claw_audit_events WHERE request_id = $1 ORDER BY occurred_at ASC, id ASC`,
|
||||||
|
[requestId],
|
||||||
|
);
|
||||||
|
return res.rows as Record<string, unknown>[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let singleton: ClawApprovalsStore | null = null;
|
||||||
|
|
||||||
|
export function getClawApprovalsStore(): ClawApprovalsStore {
|
||||||
|
if (!singleton) {
|
||||||
|
singleton = new ClawApprovalsStore();
|
||||||
|
}
|
||||||
|
return singleton;
|
||||||
|
}
|
||||||
@@ -34,6 +34,14 @@ const METHOD_SCOPE_GROUPS: Record<OperatorScope, readonly string[]> = {
|
|||||||
"exec.approval.request",
|
"exec.approval.request",
|
||||||
"exec.approval.waitDecision",
|
"exec.approval.waitDecision",
|
||||||
"exec.approval.resolve",
|
"exec.approval.resolve",
|
||||||
|
"claw.approvals.create",
|
||||||
|
"claw.approvals.list",
|
||||||
|
"claw.approvals.get",
|
||||||
|
"claw.approvals.approveOnce",
|
||||||
|
"claw.approvals.approveAlways",
|
||||||
|
"claw.approvals.reject",
|
||||||
|
"claw.approvals.execute",
|
||||||
|
"claw.approvals.audit",
|
||||||
],
|
],
|
||||||
[PAIRING_SCOPE]: [
|
[PAIRING_SCOPE]: [
|
||||||
"node.pair.request",
|
"node.pair.request",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { agentsHandlers } from "./server-methods/agents.js";
|
|||||||
import { browserHandlers } from "./server-methods/browser.js";
|
import { browserHandlers } from "./server-methods/browser.js";
|
||||||
import { channelsHandlers } from "./server-methods/channels.js";
|
import { channelsHandlers } from "./server-methods/channels.js";
|
||||||
import { chatHandlers } from "./server-methods/chat.js";
|
import { chatHandlers } from "./server-methods/chat.js";
|
||||||
|
import { clawApprovalsHandlers } from "./server-methods/claw-approvals.js";
|
||||||
import { configHandlers } from "./server-methods/config.js";
|
import { configHandlers } from "./server-methods/config.js";
|
||||||
import { connectHandlers } from "./server-methods/connect.js";
|
import { connectHandlers } from "./server-methods/connect.js";
|
||||||
import { cronHandlers } from "./server-methods/cron.js";
|
import { cronHandlers } from "./server-methods/cron.js";
|
||||||
@@ -76,6 +77,7 @@ export const coreGatewayHandlers: GatewayRequestHandlers = {
|
|||||||
...deviceHandlers,
|
...deviceHandlers,
|
||||||
...doctorHandlers,
|
...doctorHandlers,
|
||||||
...execApprovalsHandlers,
|
...execApprovalsHandlers,
|
||||||
|
...clawApprovalsHandlers,
|
||||||
...webHandlers,
|
...webHandlers,
|
||||||
...modelsHandlers,
|
...modelsHandlers,
|
||||||
...configHandlers,
|
...configHandlers,
|
||||||
|
|||||||
330
src/gateway/server-methods/claw-approvals.ts
Normal file
330
src/gateway/server-methods/claw-approvals.ts
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
import {
|
||||||
|
getClawApprovalsStore,
|
||||||
|
type BrokerExecutePayload,
|
||||||
|
type BrokerExecuteResult,
|
||||||
|
type ClawRiskLevel,
|
||||||
|
} from "../claw-approvals-store.js";
|
||||||
|
import { ErrorCodes, errorShape } from "../protocol/index.js";
|
||||||
|
import type { GatewayRequestHandlers } from "./types.js";
|
||||||
|
|
||||||
|
type JsonMap = Record<string, unknown>;
|
||||||
|
|
||||||
|
function asObject(value: unknown): JsonMap | null {
|
||||||
|
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonMap) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRequiredString(params: JsonMap, key: string): string {
|
||||||
|
const value = params[key];
|
||||||
|
if (typeof value !== "string" || value.trim().length === 0) {
|
||||||
|
throw new Error(`${key} is required`);
|
||||||
|
}
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptionalString(params: JsonMap, key: string): string | null {
|
||||||
|
const value = params[key];
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
throw new Error(`${key} must be a string`);
|
||||||
|
}
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRiskLevel(params: JsonMap): ClawRiskLevel {
|
||||||
|
const value = params.riskLevel;
|
||||||
|
if (value === "low" || value === "medium" || value === "high") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
throw new Error("riskLevel must be low|medium|high");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function invokeBroker(payload: BrokerExecutePayload): Promise<BrokerExecuteResult> {
|
||||||
|
const brokerUrl = process.env.CLAW_BROKER_URL ?? "http://127.0.0.1:8787/v1/execute";
|
||||||
|
const brokerToken = process.env.CLAW_BROKER_TOKEN?.trim();
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
"content-type": "application/json",
|
||||||
|
};
|
||||||
|
if (brokerToken) {
|
||||||
|
headers.authorization = `Bearer ${brokerToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(brokerUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
let body: JsonMap | null = null;
|
||||||
|
try {
|
||||||
|
body = (await res.json()) as JsonMap;
|
||||||
|
} catch {
|
||||||
|
body = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const nowIso = new Date().toISOString();
|
||||||
|
return {
|
||||||
|
executionId: payload.executionId,
|
||||||
|
status: "broker_error",
|
||||||
|
ok: false,
|
||||||
|
exitCode: 1,
|
||||||
|
stderrSummary: `broker error ${res.status}`,
|
||||||
|
startedAt: nowIso,
|
||||||
|
finishedAt: nowIso,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ok = body?.ok === true;
|
||||||
|
const exitCodeRaw = body?.exitCode;
|
||||||
|
const exitCode = typeof exitCodeRaw === "number" ? exitCodeRaw : ok ? 0 : 1;
|
||||||
|
const startedAt =
|
||||||
|
typeof body?.startedAt === "string" && body.startedAt.length > 0
|
||||||
|
? body.startedAt
|
||||||
|
: new Date().toISOString();
|
||||||
|
const finishedAt =
|
||||||
|
typeof body?.finishedAt === "string" && body.finishedAt.length > 0
|
||||||
|
? body.finishedAt
|
||||||
|
: startedAt;
|
||||||
|
return {
|
||||||
|
executionId:
|
||||||
|
typeof body?.executionId === "string" && body.executionId.length > 0
|
||||||
|
? body.executionId
|
||||||
|
: payload.executionId,
|
||||||
|
status: typeof body?.status === "string" ? body.status : ok ? "executed" : "execution_failed",
|
||||||
|
ok,
|
||||||
|
exitCode,
|
||||||
|
stdoutSummary: typeof body?.stdoutSummary === "string" ? body.stdoutSummary : "",
|
||||||
|
stderrSummary: typeof body?.stderrSummary === "string" ? body.stderrSummary : "",
|
||||||
|
startedAt,
|
||||||
|
finishedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clawApprovalsHandlers: GatewayRequestHandlers = {
|
||||||
|
"claw.approvals.create": async ({ params, respond }) => {
|
||||||
|
try {
|
||||||
|
const p = asObject(params);
|
||||||
|
if (!p) {
|
||||||
|
throw new Error("params object is required");
|
||||||
|
}
|
||||||
|
const store = getClawApprovalsStore();
|
||||||
|
const request = await store.createApprovalRequest({
|
||||||
|
requestedByAgent: getRequiredString(p, "requestedByAgent"),
|
||||||
|
sessionId: getRequiredString(p, "sessionId"),
|
||||||
|
channel: getRequiredString(p, "channel"),
|
||||||
|
chatId: getRequiredString(p, "chatId"),
|
||||||
|
humanUserId: getRequiredString(p, "humanUserId"),
|
||||||
|
targetHost: getRequiredString(p, "targetHost"),
|
||||||
|
targetUser: getRequiredString(p, "targetUser"),
|
||||||
|
cwd: getOptionalString(p, "cwd"),
|
||||||
|
humanSummary: getRequiredString(p, "humanSummary"),
|
||||||
|
reason: getRequiredString(p, "reason"),
|
||||||
|
exactCommand: getRequiredString(p, "exactCommand"),
|
||||||
|
riskLevel: getRiskLevel(p),
|
||||||
|
rollbackHint: getOptionalString(p, "rollbackHint"),
|
||||||
|
dangerousFlags: asObject(p.dangerousFlags) as Record<string, boolean> | undefined,
|
||||||
|
});
|
||||||
|
respond(true, { request }, undefined);
|
||||||
|
} catch (err) {
|
||||||
|
respond(
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
errorShape(ErrorCodes.INVALID_REQUEST, `claw.approvals.create failed: ${String(err)}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"claw.approvals.list": async ({ params, respond }) => {
|
||||||
|
try {
|
||||||
|
const p = asObject(params) ?? {};
|
||||||
|
const status = getOptionalString(p, "status");
|
||||||
|
const store = getClawApprovalsStore();
|
||||||
|
const requests = await store.listApprovalRequests(status ?? undefined);
|
||||||
|
respond(true, { requests }, undefined);
|
||||||
|
} catch (err) {
|
||||||
|
respond(
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
errorShape(ErrorCodes.INVALID_REQUEST, `claw.approvals.list failed: ${String(err)}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"claw.approvals.get": async ({ params, respond }) => {
|
||||||
|
try {
|
||||||
|
const p = asObject(params);
|
||||||
|
if (!p) {
|
||||||
|
throw new Error("params object is required");
|
||||||
|
}
|
||||||
|
const id = getRequiredString(p, "id");
|
||||||
|
const store = getClawApprovalsStore();
|
||||||
|
const request = await store.getApprovalRequest(id);
|
||||||
|
if (!request) {
|
||||||
|
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "request not found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
respond(true, { request }, undefined);
|
||||||
|
} catch (err) {
|
||||||
|
respond(
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
errorShape(ErrorCodes.INVALID_REQUEST, `claw.approvals.get failed: ${String(err)}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"claw.approvals.approveOnce": async ({ params, respond }) => {
|
||||||
|
try {
|
||||||
|
const p = asObject(params);
|
||||||
|
if (!p) {
|
||||||
|
throw new Error("params object is required");
|
||||||
|
}
|
||||||
|
const ttlRaw = p.ttlSeconds;
|
||||||
|
const ttlSeconds = typeof ttlRaw === "number" && Number.isFinite(ttlRaw) ? ttlRaw : 180;
|
||||||
|
const store = getClawApprovalsStore();
|
||||||
|
const result = await store.approveOnce({
|
||||||
|
id: getRequiredString(p, "id"),
|
||||||
|
actorId: getRequiredString(p, "actorId"),
|
||||||
|
ttlSeconds,
|
||||||
|
});
|
||||||
|
respond(
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
request: result.request,
|
||||||
|
grant: {
|
||||||
|
grantId: result.grantId,
|
||||||
|
grantType: "once",
|
||||||
|
expiresAt: result.expiresAt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
respond(
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
errorShape(ErrorCodes.INVALID_REQUEST, `claw.approvals.approveOnce failed: ${String(err)}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"claw.approvals.approveAlways": async ({ params, respond }) => {
|
||||||
|
try {
|
||||||
|
const p = asObject(params);
|
||||||
|
if (!p) {
|
||||||
|
throw new Error("params object is required");
|
||||||
|
}
|
||||||
|
const store = getClawApprovalsStore();
|
||||||
|
const result = await store.approveAlways({
|
||||||
|
id: getRequiredString(p, "id"),
|
||||||
|
actorId: getRequiredString(p, "actorId"),
|
||||||
|
});
|
||||||
|
respond(
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
request: result.request,
|
||||||
|
grant: {
|
||||||
|
grantId: result.grantId,
|
||||||
|
grantType: "always",
|
||||||
|
},
|
||||||
|
allowRuleId: result.allowRuleId,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
respond(
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
errorShape(
|
||||||
|
ErrorCodes.INVALID_REQUEST,
|
||||||
|
`claw.approvals.approveAlways failed: ${String(err)}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"claw.approvals.reject": async ({ params, respond }) => {
|
||||||
|
try {
|
||||||
|
const p = asObject(params);
|
||||||
|
if (!p) {
|
||||||
|
throw new Error("params object is required");
|
||||||
|
}
|
||||||
|
const store = getClawApprovalsStore();
|
||||||
|
const request = await store.reject({
|
||||||
|
id: getRequiredString(p, "id"),
|
||||||
|
actorId: getRequiredString(p, "actorId"),
|
||||||
|
reason: getOptionalString(p, "reason"),
|
||||||
|
});
|
||||||
|
respond(true, { request }, undefined);
|
||||||
|
} catch (err) {
|
||||||
|
respond(
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
errorShape(ErrorCodes.INVALID_REQUEST, `claw.approvals.reject failed: ${String(err)}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"claw.approvals.execute": async ({ params, respond }) => {
|
||||||
|
try {
|
||||||
|
const p = asObject(params);
|
||||||
|
if (!p) {
|
||||||
|
throw new Error("params object is required");
|
||||||
|
}
|
||||||
|
const store = getClawApprovalsStore();
|
||||||
|
const result = await store.executeApproved(
|
||||||
|
{
|
||||||
|
id: getRequiredString(p, "id"),
|
||||||
|
grantId: getRequiredString(p, "grantId"),
|
||||||
|
actorId: getRequiredString(p, "actorId"),
|
||||||
|
},
|
||||||
|
invokeBroker,
|
||||||
|
);
|
||||||
|
respond(
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
request: result.request,
|
||||||
|
execution: {
|
||||||
|
executionId: result.executionId,
|
||||||
|
ok: result.broker.ok,
|
||||||
|
status: result.broker.status,
|
||||||
|
exitCode: result.broker.exitCode,
|
||||||
|
stdoutSummary: result.broker.stdoutSummary ?? "",
|
||||||
|
stderrSummary: result.broker.stderrSummary ?? "",
|
||||||
|
startedAt: result.broker.startedAt,
|
||||||
|
finishedAt: result.broker.finishedAt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
respond(
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
errorShape(ErrorCodes.UNAVAILABLE, `claw.approvals.execute failed: ${String(err)}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"claw.approvals.audit": async ({ params, respond }) => {
|
||||||
|
try {
|
||||||
|
const p = asObject(params);
|
||||||
|
if (!p) {
|
||||||
|
throw new Error("params object is required");
|
||||||
|
}
|
||||||
|
const id = getRequiredString(p, "id");
|
||||||
|
const store = getClawApprovalsStore();
|
||||||
|
const events = await store.getAuditTrail(id);
|
||||||
|
respond(true, { events }, undefined);
|
||||||
|
} catch (err) {
|
||||||
|
respond(
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
errorShape(ErrorCodes.INVALID_REQUEST, `claw.approvals.audit failed: ${String(err)}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user