agent-claw: automated task changes

This commit is contained in:
daniel
2026-05-06 18:55:16 -05:00
parent 38905bb1e9
commit 732b00fb66
8494 changed files with 2018127 additions and 4 deletions

View File

@@ -0,0 +1 @@
"use strict";var f=Object.create,i=Object.defineProperty,I=Object.getOwnPropertyDescriptor,C=Object.getOwnPropertyNames,w=Object.getPrototypeOf,P=Object.prototype.hasOwnProperty,A=(t,e)=>{for(var o in e)i(t,o,{get:e[o],enumerable:!0})},d=(t,e,o,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of C(e))!P.call(t,s)&&s!==o&&i(t,s,{get:()=>e[s],enumerable:!(r=I(e,s))||r.enumerable});return t},l=(t,e,o)=>(o=t!=null?f(w(t)):{},d(e||!t||!t.__esModule?i(o,"default",{value:t,enumerable:!0}):o,t)),B=t=>d(i({},"__esModule",{value:!0}),t),q={};A(q,{autoDeleteHandler:()=>S,handler:()=>H}),module.exports=B(q);var h=require("@aws-sdk/client-s3"),y=l(require("https")),m=l(require("url")),a={sendHttpRequest:D,log:T,includeStackTraces:!0,userHandlerIndex:"./index"},p="AWSCDK::CustomResourceProviderFramework::CREATE_FAILED",L="AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID";function R(t){return async(e,o)=>{let r={...e,ResponseURL:"..."};if(a.log(JSON.stringify(r,void 0,2)),e.RequestType==="Delete"&&e.PhysicalResourceId===p){a.log("ignoring DELETE event caused by a failed CREATE event"),await u("SUCCESS",e);return}try{let s=await t(r,o),n=k(e,s);await u("SUCCESS",n)}catch(s){let n={...e,Reason:a.includeStackTraces?s.stack:s.message};n.PhysicalResourceId||(e.RequestType==="Create"?(a.log("CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored"),n.PhysicalResourceId=p):a.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(e)}`)),await u("FAILED",n)}}}function k(t,e={}){let o=e.PhysicalResourceId??t.PhysicalResourceId??t.RequestId;if(t.RequestType==="Delete"&&o!==t.PhysicalResourceId)throw new Error(`DELETE: cannot change the physical resource ID from "${t.PhysicalResourceId}" to "${e.PhysicalResourceId}" during deletion`);return{...t,...e,PhysicalResourceId:o}}async function u(t,e){let o={Status:t,Reason:e.Reason??t,StackId:e.StackId,RequestId:e.RequestId,PhysicalResourceId:e.PhysicalResourceId||L,LogicalResourceId:e.LogicalResourceId,NoEcho:e.NoEcho,Data:e.Data},r=m.parse(e.ResponseURL),s=`${r.protocol}//${r.hostname}/${r.pathname}?***`;a.log("submit response to cloudformation",s,o);let n=JSON.stringify(o),E={hostname:r.hostname,path:r.path,method:"PUT",headers:{"content-type":"","content-length":Buffer.byteLength(n,"utf8")}};await O({attempts:5,sleep:1e3},a.sendHttpRequest)(E,n)}async function D(t,e){return new Promise((o,r)=>{try{let s=y.request(t,n=>{n.resume(),!n.statusCode||n.statusCode>=400?r(new Error(`Unsuccessful HTTP response: ${n.statusCode}`)):o()});s.on("error",r),s.write(e),s.end()}catch(s){r(s)}})}function T(t,...e){console.log(t,...e)}function O(t,e){return async(...o)=>{let r=t.attempts,s=t.sleep;for(;;)try{return await e(...o)}catch(n){if(r--<=0)throw n;await b(Math.floor(Math.random()*s)),s*=2}}}async function b(t){return new Promise(e=>setTimeout(e,t))}var g="aws-cdk:auto-delete-objects",x=JSON.stringify({Version:"2012-10-17",Statement:[]}),c=new h.S3({}),H=R(S);async function S(t){switch(t.RequestType){case"Create":return;case"Update":return{PhysicalResourceId:(await F(t)).PhysicalResourceId};case"Delete":return N(t.ResourceProperties?.BucketName)}}async function F(t){let e=t,o=e.OldResourceProperties?.BucketName;return{PhysicalResourceId:e.ResourceProperties?.BucketName??o}}async function _(t){try{let e=(await c.getBucketPolicy({Bucket:t}))?.Policy??x,o=JSON.parse(e);o.Statement.push({Principal:"*",Effect:"Deny",Action:["s3:PutObject"],Resource:[`arn:aws:s3:::${t}/*`]}),await c.putBucketPolicy({Bucket:t,Policy:JSON.stringify(o)})}catch(e){if(e.name==="NoSuchBucket")throw e;console.log(`Could not set new object deny policy on bucket '${t}' prior to deletion.`)}}async function U(t){let e;do{e=await c.listObjectVersions({Bucket:t});let o=[...e.Versions??[],...e.DeleteMarkers??[]];if(o.length===0)return;let r=o.map(s=>({Key:s.Key,VersionId:s.VersionId}));await c.deleteObjects({Bucket:t,Delete:{Objects:r}})}while(e?.IsTruncated)}async function N(t){if(!t)throw new Error("No BucketName was provided.");try{if(!await W(t)){console.log(`Bucket does not have '${g}' tag, skipping cleaning.`);return}await _(t),await U(t)}catch(e){if(e.name==="NoSuchBucket"){console.log(`Bucket '${t}' does not exist.`);return}throw e}}async function W(t){return(await c.getBucketTagging({Bucket:t})).TagSet?.some(o=>o.Key===g&&o.Value==="true")}

View File

@@ -0,0 +1,13 @@
import { Construct } from "constructs";
import { CustomResourceProviderBase, CustomResourceProviderOptions } from "../../../core";
export declare class AutoDeleteObjectsProvider extends CustomResourceProviderBase {
/**
* Returns a stack-level singleton ARN (service token) for the custom resource provider.
*/
static getOrCreate(scope: Construct, uniqueid: string, props?: CustomResourceProviderOptions): string;
/**
* Returns a stack-level singleton for the custom resource provider.
*/
static getOrCreateProvider(scope: Construct, uniqueid: string, props?: CustomResourceProviderOptions): AutoDeleteObjectsProvider;
private constructor();
}

View File

@@ -0,0 +1 @@
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.AutoDeleteObjectsProvider=void 0;const path=require("path"),core_1=require("../../../core");class AutoDeleteObjectsProvider extends core_1.CustomResourceProviderBase{static getOrCreate(scope,uniqueid,props){return this.getOrCreateProvider(scope,uniqueid,props).serviceToken}static getOrCreateProvider(scope,uniqueid,props){const id=`${uniqueid}CustomResourceProvider`,stack=core_1.Stack.of(scope);return stack.node.tryFindChild(id)??new AutoDeleteObjectsProvider(stack,id,props)}constructor(scope,id,props){super(scope,id,{...props,codeDirectory:path.join(__dirname,"auto-delete-objects-handler"),runtimeName:(0,core_1.determineLatestNodeRuntimeName)(scope)}),this.node.addMetadata("aws:cdk:is-custom-resource-handler-customResourceProvider",!0)}}exports.AutoDeleteObjectsProvider=AutoDeleteObjectsProvider;

View File

@@ -0,0 +1,123 @@
import boto3 # type: ignore
import json
import logging
import urllib.request
s3 = boto3.client("s3")
EVENTBRIDGE_CONFIGURATION = 'EventBridgeConfiguration'
CONFIGURATION_TYPES = ["TopicConfigurations", "QueueConfigurations", "LambdaFunctionConfigurations"]
def handler(event: dict, context):
response_status = "SUCCESS"
error_message = ""
try:
props = event["ResourceProperties"]
notification_configuration = props["NotificationConfiguration"]
managed = props.get('Managed', 'true').lower() == 'true'
skipDestinationValidation = props.get('SkipDestinationValidation', 'false').lower() == 'true'
stack_id = event['StackId']
old = event.get("OldResourceProperties", {}).get("NotificationConfiguration", {})
if managed:
config = handle_managed(event["RequestType"], notification_configuration)
else:
config = handle_unmanaged(props["BucketName"], stack_id, event["RequestType"], notification_configuration, old)
s3.put_bucket_notification_configuration(Bucket=props["BucketName"], NotificationConfiguration=config, SkipDestinationValidation=skipDestinationValidation)
except Exception as e:
logging.exception("Failed to put bucket notification configuration")
response_status = "FAILED"
error_message = f"Error: {str(e)}. "
finally:
submit_response(event, context, response_status, error_message)
def handle_managed(request_type, notification_configuration):
if request_type == 'Delete':
return {}
return notification_configuration
def handle_unmanaged(bucket, stack_id, request_type, notification_configuration, old):
def get_id(n):
n['Id'] = ''
sorted_notifications = sort_filter_rules(n)
strToHash=json.dumps(sorted_notifications, sort_keys=True).replace('"Name": "prefix"', '"Name": "Prefix"').replace('"Name": "suffix"', '"Name": "Suffix"')
return f"{stack_id}-{hash(strToHash)}"
def with_id(n):
n['Id'] = get_id(n)
return n
# find external notifications
external_notifications = {}
existing_notifications = s3.get_bucket_notification_configuration(Bucket=bucket)
for t in CONFIGURATION_TYPES:
if request_type == 'Update':
old_incoming_ids = [get_id(n) for n in old.get(t, [])]
# if the notification was created by us, we know what id to expect so we can filter by it.
external_notifications[t] = [n for n in existing_notifications.get(t, []) if not get_id(n) in old_incoming_ids]
elif request_type == 'Delete':
# For 'Delete' request, old parameter is an empty dict so we cannot use this to determine which are external
# notifications. Fall back to rely on the stack naming logic.
external_notifications[t] = [n for n in existing_notifications.get(t, []) if not n['Id'].startswith(f"{stack_id}-")]
elif request_type == 'Create':
# if this is a create event then all existing notifications are external
external_notifications[t] = [n for n in existing_notifications.get(t, [])]
# always treat EventBridge configuration as an external config if it already exists
# as there is no way to determine whether it's managed by us or not
if EVENTBRIDGE_CONFIGURATION in existing_notifications:
external_notifications[EVENTBRIDGE_CONFIGURATION] = existing_notifications[EVENTBRIDGE_CONFIGURATION]
# if delete, that's all we need
if request_type == 'Delete':
return external_notifications
# otherwise, merge external with incoming config and augment with id
notifications = {}
for t in CONFIGURATION_TYPES:
external = external_notifications.get(t, [])
incoming = [with_id(n) for n in notification_configuration.get(t, [])]
notifications[t] = external + incoming
# EventBridge configuration is a special case because it's just an empty object if it exists
if EVENTBRIDGE_CONFIGURATION in notification_configuration:
notifications[EVENTBRIDGE_CONFIGURATION] = notification_configuration[EVENTBRIDGE_CONFIGURATION]
elif EVENTBRIDGE_CONFIGURATION in external_notifications:
notifications[EVENTBRIDGE_CONFIGURATION] = external_notifications[EVENTBRIDGE_CONFIGURATION]
return notifications
def submit_response(event: dict, context, response_status: str, error_message: str):
response_body = json.dumps(
{
"Status": response_status,
"Reason": f"{error_message}See the details in CloudWatch Log Stream: {context.log_stream_name}",
"PhysicalResourceId": event.get("PhysicalResourceId") or event["LogicalResourceId"],
"StackId": event["StackId"],
"RequestId": event["RequestId"],
"LogicalResourceId": event["LogicalResourceId"],
"NoEcho": False,
}
).encode("utf-8")
headers = {"content-type": "", "content-length": str(len(response_body))}
try:
req = urllib.request.Request(url=event["ResponseURL"], headers=headers, data=response_body, method="PUT")
with urllib.request.urlopen(req) as response:
print(response.read().decode("utf-8"))
print("Status code: " + response.reason)
except Exception as e:
print("send(..) failed executing request.urlopen(..): " + str(e))
def sort_filter_rules(json_obj):
# Check if the input is a dictionary
if not isinstance(json_obj, dict):
return json_obj
# Recursively sort the filter rules for nested dictionaries
for key, value in json_obj.items():
if isinstance(value, dict):
json_obj[key] = sort_filter_rules(value)
elif isinstance(value, list):
json_obj[key] = [sort_filter_rules(item) for item in value]
# Sort the FilterRules list if it exists
if "Filter" in json_obj and "Key" in json_obj["Filter"] and "FilterRules" in json_obj["Filter"]["Key"]:
filter_rules = json_obj["Filter"]["Key"]["FilterRules"]
sorted_filter_rules = sorted(filter_rules, key=lambda x: x["Name"])
json_obj["Filter"]["Key"]["FilterRules"] = sorted_filter_rules
return json_obj