agent-claw: automated task changes
This commit is contained in:
5
cdk/lib/agent-claw-stack.d.ts
vendored
Normal file
5
cdk/lib/agent-claw-stack.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
export declare class AgentClawStack extends cdk.Stack {
|
||||
constructor(scope: Construct, id: string, props?: cdk.StackProps);
|
||||
}
|
||||
247
cdk/lib/agent-claw-stack.js
Normal file
247
cdk/lib/agent-claw-stack.js
Normal file
@@ -0,0 +1,247 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AgentClawStack = void 0;
|
||||
const cdk = __importStar(require("aws-cdk-lib"));
|
||||
const s3 = __importStar(require("aws-cdk-lib/aws-s3"));
|
||||
const s3deploy = __importStar(require("aws-cdk-lib/aws-s3-deployment"));
|
||||
const dynamodb = __importStar(require("aws-cdk-lib/aws-dynamodb"));
|
||||
const sqs = __importStar(require("aws-cdk-lib/aws-sqs"));
|
||||
const lambda = __importStar(require("aws-cdk-lib/aws-lambda"));
|
||||
const apigatewayv2 = __importStar(require("aws-cdk-lib/aws-apigatewayv2"));
|
||||
const apigatewayv2integrations = __importStar(require("aws-cdk-lib/aws-apigatewayv2-integrations"));
|
||||
const iam = __importStar(require("aws-cdk-lib/aws-iam"));
|
||||
const secretsmanager = __importStar(require("aws-cdk-lib/aws-secretsmanager"));
|
||||
const aws_lambda_event_sources_1 = require("aws-cdk-lib/aws-lambda-event-sources");
|
||||
const path = __importStar(require("path"));
|
||||
class AgentClawStack extends cdk.Stack {
|
||||
constructor(scope, id, props) {
|
||||
super(scope, id, props);
|
||||
// ── Context parameters ─────────────────────────────────────────────────
|
||||
const telegramBotTokenSecretArn = this.node.tryGetContext('telegramBotTokenSecretArn');
|
||||
const braveApiKeySecretArn = this.node.tryGetContext('braveApiKeySecretArn');
|
||||
const existingWorkspaceBucketName = this.node.tryGetContext('workspaceBucketName');
|
||||
const runtime1Arn = this.node.tryGetContext('runtime1Arn');
|
||||
if (!telegramBotTokenSecretArn) {
|
||||
throw new Error('Context param required: telegramBotTokenSecretArn');
|
||||
}
|
||||
if (!braveApiKeySecretArn) {
|
||||
throw new Error('Context param required: braveApiKeySecretArn');
|
||||
}
|
||||
// ── Secrets (reference existing) ───────────────────────────────────────
|
||||
const botTokenSecret = secretsmanager.Secret.fromSecretCompleteArn(this, 'TelegramBotToken', telegramBotTokenSecretArn);
|
||||
const braveApiKeySecret = secretsmanager.Secret.fromSecretCompleteArn(this, 'BraveApiKey', braveApiKeySecretArn);
|
||||
// ── S3 workspace bucket ────────────────────────────────────────────────
|
||||
const workspaceBucket = existingWorkspaceBucketName
|
||||
? s3.Bucket.fromBucketName(this, 'WorkspaceBucket', existingWorkspaceBucketName)
|
||||
: new s3.Bucket(this, 'WorkspaceBucket', {
|
||||
bucketName: `agent-claw-workspace-${this.account}`,
|
||||
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
||||
versioned: false,
|
||||
encryption: s3.BucketEncryption.S3_MANAGED,
|
||||
});
|
||||
// Seed workspace files on deploy (only if bucket was created by us)
|
||||
if (!existingWorkspaceBucketName) {
|
||||
new s3deploy.BucketDeployment(this, 'WorkspaceFiles', {
|
||||
sources: [s3deploy.Source.asset(path.join(__dirname, '../../workspace'))],
|
||||
destinationBucket: workspaceBucket,
|
||||
});
|
||||
}
|
||||
// ── DynamoDB session store ─────────────────────────────────────────────
|
||||
const sessionTable = new dynamodb.Table(this, 'SessionStore', {
|
||||
tableName: 'agent-claw-sessions',
|
||||
partitionKey: { name: 'actor_id', type: dynamodb.AttributeType.STRING },
|
||||
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
||||
timeToLiveAttribute: 'ttl',
|
||||
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
||||
});
|
||||
// ── SQS FIFO message queue ─────────────────────────────────────────────
|
||||
const messageQueue = new sqs.Queue(this, 'MessageQueue', {
|
||||
queueName: 'agent-claw-messages.fifo',
|
||||
fifo: true,
|
||||
contentBasedDeduplication: false,
|
||||
visibilityTimeout: cdk.Duration.seconds(900),
|
||||
receiveMessageWaitTime: cdk.Duration.seconds(20),
|
||||
});
|
||||
// ── Lambda: tg-ingest ─────────────────────────────────────────────────
|
||||
const tgIngestFn = new lambda.Function(this, 'TgIngest', {
|
||||
functionName: 'agent-claw-tg-ingest',
|
||||
runtime: lambda.Runtime.PYTHON_3_12,
|
||||
handler: 'handler.handler',
|
||||
code: lambda.Code.fromAsset(path.join(__dirname, '../../src/lambdas/tg-ingest')),
|
||||
timeout: cdk.Duration.seconds(10),
|
||||
memorySize: 128,
|
||||
environment: {
|
||||
MESSAGE_QUEUE_URL: messageQueue.queueUrl,
|
||||
TELEGRAM_BOT_TOKEN_SECRET_ARN: telegramBotTokenSecretArn,
|
||||
TELEGRAM_WEBHOOK_SECRET: '', // set via SSM or direct env after deploy
|
||||
},
|
||||
});
|
||||
messageQueue.grantSendMessages(tgIngestFn);
|
||||
botTokenSecret.grantRead(tgIngestFn);
|
||||
// ── Lambda: agent-runner ───────────────────────────────────────────────
|
||||
const agentRunnerFn = new lambda.Function(this, 'AgentRunner', {
|
||||
functionName: 'agent-claw-agent-runner',
|
||||
runtime: lambda.Runtime.PYTHON_3_12,
|
||||
handler: 'handler.handler',
|
||||
code: lambda.Code.fromAsset(path.join(__dirname, '../../src/lambdas/agent-runner')),
|
||||
timeout: cdk.Duration.seconds(900),
|
||||
memorySize: 256,
|
||||
environment: {
|
||||
SESSION_TABLE_NAME: sessionTable.tableName,
|
||||
WORKSPACE_BUCKET_NAME: workspaceBucket.bucketName,
|
||||
TELEGRAM_BOT_TOKEN_SECRET_ARN: telegramBotTokenSecretArn,
|
||||
BRAVE_API_KEY_SECRET_ARN: braveApiKeySecretArn,
|
||||
RUNTIME_1_ARN: runtime1Arn ?? 'PLACEHOLDER_SET_AFTER_RUNTIME_DEPLOY',
|
||||
AWS_REGION_NAME: 'us-east-1',
|
||||
},
|
||||
});
|
||||
sessionTable.grantReadWriteData(agentRunnerFn);
|
||||
workspaceBucket.grantRead(agentRunnerFn);
|
||||
botTokenSecret.grantRead(agentRunnerFn);
|
||||
braveApiKeySecret.grantRead(agentRunnerFn);
|
||||
messageQueue.grantConsumeMessages(agentRunnerFn);
|
||||
// AgentCore invoke permission
|
||||
agentRunnerFn.addToRolePolicy(new iam.PolicyStatement({
|
||||
actions: ['bedrock-agentcore:InvokeAgentRuntime'],
|
||||
resources: ['*'],
|
||||
}));
|
||||
// SQS event source
|
||||
agentRunnerFn.addEventSource(new aws_lambda_event_sources_1.SqsEventSource(messageQueue, {
|
||||
batchSize: 10,
|
||||
enabled: true,
|
||||
}));
|
||||
// ── API Gateway HTTP ───────────────────────────────────────────────────
|
||||
const httpApi = new apigatewayv2.HttpApi(this, 'WebhookApi', {
|
||||
apiName: 'agent-claw-webhook',
|
||||
});
|
||||
httpApi.addRoutes({
|
||||
path: '/telegram',
|
||||
methods: [apigatewayv2.HttpMethod.POST],
|
||||
integration: new apigatewayv2integrations.HttpLambdaIntegration('TgIngestIntegration', tgIngestFn),
|
||||
});
|
||||
// ── AgentCore Runtime 1 ────────────────────────────────────────────────
|
||||
// NOTE: AgentCore CDK L2 constructs are in preview. Using CfnResource.
|
||||
// The runtime1Arn output below needs to be fed back as context param
|
||||
// on subsequent deploys so agent-runner can invoke it.
|
||||
// IAM execution role for Runtime 1
|
||||
const runtime1Role = new iam.Role(this, 'Runtime1Role', {
|
||||
assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'),
|
||||
description: 'Execution role for agent-claw Runtime 1 (main assistant)',
|
||||
});
|
||||
runtime1Role.addToPolicy(new iam.PolicyStatement({
|
||||
actions: [
|
||||
'bedrock:InvokeModel',
|
||||
'bedrock:InvokeModelWithResponseStream',
|
||||
],
|
||||
resources: ['*'],
|
||||
}));
|
||||
workspaceBucket.grantRead(runtime1Role);
|
||||
botTokenSecret.grantRead(runtime1Role);
|
||||
braveApiKeySecret.grantRead(runtime1Role);
|
||||
// Google secret grants added after workspace_mcp section below
|
||||
runtime1Role.addToPolicy(new iam.PolicyStatement({
|
||||
actions: [
|
||||
'bedrock-agentcore:CreateEvent',
|
||||
'bedrock-agentcore:ListEvents',
|
||||
'bedrock-agentcore:RetrieveMemoryRecords',
|
||||
],
|
||||
resources: ['*'],
|
||||
}));
|
||||
// AgentCore Runtime 1 resource (CfnResource — L2 in preview)
|
||||
// CodeZip: packages src/runtime-1/ as a zip and uploads to S3
|
||||
// TODO: Replace with L2 construct when aws-cdk-lib includes it stable
|
||||
// For now, the runtime ARN must be created manually or via CLI after first synth
|
||||
// and fed back as context param runtime1Arn.
|
||||
// ── Outputs ────────────────────────────────────────────────────────────
|
||||
// ── Google Workspace MCP ──────────────────────────────────────────────
|
||||
// Secrets pre-populated after OAuth flow
|
||||
const googleCredentialsSecret = secretsmanager.Secret.fromSecretNameV2(this, 'GoogleWorkspaceCredentials', 'agent-claw/google-workspace-credentials');
|
||||
const googleOAuthClientSecret = secretsmanager.Secret.fromSecretNameV2(this, 'GoogleOAuthClient', 'agent-claw/google-oauth-client');
|
||||
// workspace-mcp Lambda execution role (import existing — created during initial setup)
|
||||
// NOTE (tech debt #3): workspaceMcpRole imported but not attached to workspaceMcpFn because
|
||||
// fromFunctionName() returns an IFunction (no role config). Role was set at Lambda creation.
|
||||
// To fully codify: delete the manual Lambda, let CDK create it with Code.fromBucket + role.
|
||||
const _workspaceMcpRole = iam.Role.fromRoleName(this, 'WorkspaceMcpRole', 'agent-claw-workspace-mcp-role');
|
||||
googleCredentialsSecret.grantRead(_workspaceMcpRole);
|
||||
googleOAuthClientSecret.grantRead(_workspaceMcpRole);
|
||||
// workspace-mcp Lambda — import existing (created with zip + layer, no Docker)
|
||||
const workspaceMcpFn = lambda.Function.fromFunctionName(this, 'WorkspaceMcp', 'agent-claw-workspace-mcp');
|
||||
// Function URL — AWS_IAM auth (already created, reference for policy attachment)
|
||||
const workspaceMcpFunctionUrl = 'https://25hugrzw4uwtueeg77jsmft6lq0wunmd.lambda-url.us-east-1.on.aws';
|
||||
const workspaceMcpMcpUrl = workspaceMcpFunctionUrl + '/mcp';
|
||||
// AgentCore execution role — grant InvokeFunctionUrl identity policy
|
||||
runtime1Role.addToPolicy(new iam.PolicyStatement({
|
||||
sid: 'WorkspaceMcpInvoke',
|
||||
actions: ['lambda:InvokeFunctionUrl'],
|
||||
resources: [workspaceMcpFn.functionArn],
|
||||
conditions: { StringEquals: { 'lambda:FunctionUrlAuthType': 'AWS_IAM' } },
|
||||
}));
|
||||
// Pass workspace_mcp MCP URL to agent-runner (informational)
|
||||
agentRunnerFn.addEnvironment('WORKSPACE_MCP_URL', workspaceMcpMcpUrl);
|
||||
// Grant AgentCore execution role read access to Google secrets
|
||||
googleCredentialsSecret.grantRead(runtime1Role);
|
||||
googleOAuthClientSecret.grantRead(runtime1Role);
|
||||
new cdk.CfnOutput(this, 'WorkspaceMcpFunctionUrl', {
|
||||
value: workspaceMcpFunctionUrl,
|
||||
description: 'workspace-mcp Lambda Function URL (MCP endpoint for Gmail/Calendar)',
|
||||
});
|
||||
new cdk.CfnOutput(this, 'GoogleCredentialsSecretArn', {
|
||||
value: googleCredentialsSecret.secretArn,
|
||||
description: 'Google OAuth user credentials secret ARN',
|
||||
});
|
||||
new cdk.CfnOutput(this, 'WebhookUrl', {
|
||||
value: `${httpApi.url}telegram`,
|
||||
description: 'Register this URL with Telegram BotFather as webhook endpoint',
|
||||
});
|
||||
new cdk.CfnOutput(this, 'WorkspaceBucketName', {
|
||||
value: workspaceBucket.bucketName,
|
||||
description: 'S3 bucket containing agent workspace files',
|
||||
});
|
||||
new cdk.CfnOutput(this, 'SessionTableName', {
|
||||
value: sessionTable.tableName,
|
||||
description: 'DynamoDB table for session mapping',
|
||||
});
|
||||
new cdk.CfnOutput(this, 'MessageQueueUrl', {
|
||||
value: messageQueue.queueUrl,
|
||||
description: 'SQS FIFO queue for incoming messages',
|
||||
});
|
||||
new cdk.CfnOutput(this, 'Runtime1RoleArn', {
|
||||
value: runtime1Role.roleArn,
|
||||
description: 'IAM execution role ARN for AgentCore Runtime 1',
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.AgentClawStack = AgentClawStack;
|
||||
@@ -11,6 +11,7 @@ import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
||||
import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
|
||||
import { Construct } from 'constructs';
|
||||
import * as path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
export class AgentClawStack extends cdk.Stack {
|
||||
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
||||
@@ -159,6 +160,7 @@ export class AgentClawStack extends cdk.Stack {
|
||||
workspaceBucket.grantRead(runtime1Role);
|
||||
botTokenSecret.grantRead(runtime1Role);
|
||||
braveApiKeySecret.grantRead(runtime1Role);
|
||||
// Google secret grants added after workspace_mcp section below
|
||||
runtime1Role.addToPolicy(new iam.PolicyStatement({
|
||||
actions: [
|
||||
'bedrock-agentcore:CreateEvent',
|
||||
@@ -175,6 +177,59 @@ export class AgentClawStack extends cdk.Stack {
|
||||
// and fed back as context param runtime1Arn.
|
||||
|
||||
// ── Outputs ────────────────────────────────────────────────────────────
|
||||
|
||||
// ── Google Workspace MCP ──────────────────────────────────────────────
|
||||
// Secrets pre-populated after OAuth flow
|
||||
const googleCredentialsSecret = secretsmanager.Secret.fromSecretNameV2(
|
||||
this, 'GoogleWorkspaceCredentials', 'agent-claw/google-workspace-credentials'
|
||||
);
|
||||
const googleOAuthClientSecret = secretsmanager.Secret.fromSecretNameV2(
|
||||
this, 'GoogleOAuthClient', 'agent-claw/google-oauth-client'
|
||||
);
|
||||
|
||||
// workspace-mcp Lambda execution role (import existing — created during initial setup)
|
||||
// NOTE (tech debt #3): workspaceMcpRole imported but not attached to workspaceMcpFn because
|
||||
// fromFunctionName() returns an IFunction (no role config). Role was set at Lambda creation.
|
||||
// To fully codify: delete the manual Lambda, let CDK create it with Code.fromBucket + role.
|
||||
const _workspaceMcpRole = iam.Role.fromRoleName(
|
||||
this, 'WorkspaceMcpRole', 'agent-claw-workspace-mcp-role'
|
||||
);
|
||||
googleCredentialsSecret.grantRead(_workspaceMcpRole);
|
||||
googleOAuthClientSecret.grantRead(_workspaceMcpRole);
|
||||
|
||||
// workspace-mcp Lambda — import existing (created with zip + layer, no Docker)
|
||||
const workspaceMcpFn = lambda.Function.fromFunctionName(
|
||||
this, 'WorkspaceMcp', 'agent-claw-workspace-mcp'
|
||||
);
|
||||
|
||||
// Function URL — AWS_IAM auth (already created, reference for policy attachment)
|
||||
const workspaceMcpFunctionUrl = 'https://25hugrzw4uwtueeg77jsmft6lq0wunmd.lambda-url.us-east-1.on.aws';
|
||||
const workspaceMcpMcpUrl = workspaceMcpFunctionUrl + '/mcp';
|
||||
|
||||
// AgentCore execution role — grant InvokeFunctionUrl identity policy
|
||||
runtime1Role.addToPolicy(new iam.PolicyStatement({
|
||||
sid: 'WorkspaceMcpInvoke',
|
||||
actions: ['lambda:InvokeFunctionUrl'],
|
||||
resources: [workspaceMcpFn.functionArn],
|
||||
conditions: { StringEquals: { 'lambda:FunctionUrlAuthType': 'AWS_IAM' } },
|
||||
}));
|
||||
|
||||
// Pass workspace_mcp MCP URL to agent-runner (informational)
|
||||
agentRunnerFn.addEnvironment('WORKSPACE_MCP_URL', workspaceMcpMcpUrl);
|
||||
|
||||
// Grant AgentCore execution role read access to Google secrets
|
||||
googleCredentialsSecret.grantRead(runtime1Role);
|
||||
googleOAuthClientSecret.grantRead(runtime1Role);
|
||||
|
||||
new cdk.CfnOutput(this, 'WorkspaceMcpFunctionUrl', {
|
||||
value: workspaceMcpFunctionUrl,
|
||||
description: 'workspace-mcp Lambda Function URL (MCP endpoint for Gmail/Calendar)',
|
||||
});
|
||||
new cdk.CfnOutput(this, 'GoogleCredentialsSecretArn', {
|
||||
value: googleCredentialsSecret.secretArn,
|
||||
description: 'Google OAuth user credentials secret ARN',
|
||||
});
|
||||
|
||||
new cdk.CfnOutput(this, 'WebhookUrl', {
|
||||
value: `${httpApi.url}telegram`,
|
||||
description: 'Register this URL with Telegram BotFather as webhook endpoint',
|
||||
|
||||
Reference in New Issue
Block a user