multi-tenant phase 3: per-user Home Assistant + enrolled services
- tools/home_assistant.py: remove hardcoded URL/token; read from per-user
config injected via set_ha_config() at invocation time; return helpful
enrollment prompt when HA not configured
- main.py: inject HA config from user_profile.services at startup; add
manage_service tool (enroll/remove/list) that persists to DynamoDB;
show enrolled services in user context; add USERS_TABLE_NAME env var
- agent-runner/handler.py: pass services dict from DDB user record in
user_profile payload; initialize services={} for new users
- cdk/lib/agent-claw-stack.ts: grant usersTable read/write to runtime1Role
so manage_service tool can update user records
- agentclaw/agentcore/agentcore.json: add USERS_TABLE_NAME env var
This commit is contained in:
Binary file not shown.
@@ -16,16 +16,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"6f6fdf79f33a947f3e50ffd783a72d04ab5f29ba299a5d51b3ecd2c2eb311370": {
|
||||
"c0db2a060be885d61722dfe6fbd3967e1c956826682078f6338123bf0c797e5b": {
|
||||
"displayName": "AgentRunner/Code",
|
||||
"source": {
|
||||
"path": "asset.6f6fdf79f33a947f3e50ffd783a72d04ab5f29ba299a5d51b3ecd2c2eb311370",
|
||||
"path": "asset.c0db2a060be885d61722dfe6fbd3967e1c956826682078f6338123bf0c797e5b",
|
||||
"packaging": "zip"
|
||||
},
|
||||
"destinations": {
|
||||
"495395224548-us-east-1-ab491e35": {
|
||||
"495395224548-us-east-1-82b9a17b": {
|
||||
"bucketName": "cdk-hnb659fds-assets-495395224548-us-east-1",
|
||||
"objectKey": "6f6fdf79f33a947f3e50ffd783a72d04ab5f29ba299a5d51b3ecd2c2eb311370.zip",
|
||||
"objectKey": "c0db2a060be885d61722dfe6fbd3967e1c956826682078f6338123bf0c797e5b.zip",
|
||||
"region": "us-east-1",
|
||||
"assumeRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-file-publishing-role-495395224548-us-east-1"
|
||||
}
|
||||
@@ -46,16 +46,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"fdf1ff81e9e0ded898f1c1d03a2bb8bbe0bbf63689426c24072f179b49b527c6": {
|
||||
"a31aaa0bc9eab4fd6f17f10795fba05983dba0c88e83a263fe9fffe930da06b9": {
|
||||
"displayName": "AgentClawStack Template",
|
||||
"source": {
|
||||
"path": "AgentClawStack.template.json",
|
||||
"packaging": "file"
|
||||
},
|
||||
"destinations": {
|
||||
"495395224548-us-east-1-9bba4277": {
|
||||
"495395224548-us-east-1-477d6bc7": {
|
||||
"bucketName": "cdk-hnb659fds-assets-495395224548-us-east-1",
|
||||
"objectKey": "fdf1ff81e9e0ded898f1c1d03a2bb8bbe0bbf63689426c24072f179b49b527c6.json",
|
||||
"objectKey": "a31aaa0bc9eab4fd6f17f10795fba05983dba0c88e83a263fe9fffe930da06b9.json",
|
||||
"region": "us-east-1",
|
||||
"assumeRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-file-publishing-role-495395224548-us-east-1"
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
{
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:289:5)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:290:5)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -46,7 +46,7 @@
|
||||
{
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:293:5)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:294:5)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -60,7 +60,7 @@
|
||||
{
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:297:5)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:298:5)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -74,7 +74,7 @@
|
||||
{
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:302:5)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:303:5)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -88,7 +88,7 @@
|
||||
{
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:307:5)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:308:5)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -102,7 +102,7 @@
|
||||
{
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:312:5)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:313:5)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -116,7 +116,7 @@
|
||||
{
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:317:5)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:318:5)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -130,7 +130,7 @@
|
||||
{
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:322:5)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:323:5)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -144,7 +144,7 @@
|
||||
{
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:327:5)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:328:5)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -290,7 +290,7 @@
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"...new Function2 in aws-cdk-lib...",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:235:28)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:236:28)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -414,7 +414,7 @@
|
||||
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
|
||||
"Array.map (:)",
|
||||
"...WrappedClass.<anonymous> in aws-cdk-lib...",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:263:13)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:264:13)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -431,7 +431,7 @@
|
||||
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
|
||||
"Array.map (:)",
|
||||
"...WrappedClass.<anonymous> in aws-cdk-lib...",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:263:13)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:264:13)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -448,7 +448,7 @@
|
||||
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
|
||||
"Array.map (:)",
|
||||
"...WrappedClass.<anonymous> in aws-cdk-lib...",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:270:13)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:271:13)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -465,7 +465,7 @@
|
||||
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
|
||||
"Array.map (:)",
|
||||
"...WrappedClass.<anonymous> in aws-cdk-lib...",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:270:13)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:271:13)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -495,7 +495,7 @@
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"...SecretBase.grantRead in aws-cdk-lib...",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:198:29)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:199:29)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -510,7 +510,7 @@
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"...new Function2 in aws-cdk-lib...",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:235:28)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:236:28)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -574,7 +574,7 @@
|
||||
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
|
||||
"Array.map (:)",
|
||||
"...WrappedClass.<anonymous> in aws-cdk-lib...",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:263:13)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:264:13)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -591,7 +591,7 @@
|
||||
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
|
||||
"Array.map (:)",
|
||||
"...WrappedClass.<anonymous> in aws-cdk-lib...",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:270:13)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:271:13)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
@@ -606,7 +606,7 @@
|
||||
"type": "aws:cdk:creationStack",
|
||||
"data": [
|
||||
"...SecretBase.grantRead in aws-cdk-lib...",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:249:29)",
|
||||
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:250:29)",
|
||||
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
|
||||
"...node internals, ts-node, ts-node, ts-node..."
|
||||
]
|
||||
|
||||
@@ -387,7 +387,7 @@
|
||||
"Properties": {
|
||||
"Code": {
|
||||
"S3Bucket": "cdk-hnb659fds-assets-495395224548-us-east-1",
|
||||
"S3Key": "6f6fdf79f33a947f3e50ffd783a72d04ab5f29ba299a5d51b3ecd2c2eb311370.zip"
|
||||
"S3Key": "c0db2a060be885d61722dfe6fbd3967e1c956826682078f6338123bf0c797e5b.zip"
|
||||
},
|
||||
"Environment": {
|
||||
"Variables": {
|
||||
@@ -423,7 +423,7 @@
|
||||
],
|
||||
"Metadata": {
|
||||
"aws:cdk:path": "AgentClawStack/AgentRunner/Resource",
|
||||
"aws:asset:path": "asset.6f6fdf79f33a947f3e50ffd783a72d04ab5f29ba299a5d51b3ecd2c2eb311370",
|
||||
"aws:asset:path": "asset.c0db2a060be885d61722dfe6fbd3967e1c956826682078f6338123bf0c797e5b",
|
||||
"aws:asset:is-bundled": false,
|
||||
"aws:asset:property": "Code"
|
||||
}
|
||||
@@ -779,6 +779,44 @@
|
||||
"Effect": "Allow",
|
||||
"Resource": "arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/brave-api-key-uUSgzi"
|
||||
},
|
||||
{
|
||||
"Action": [
|
||||
"dynamodb:BatchGetItem",
|
||||
"dynamodb:Query",
|
||||
"dynamodb:GetItem",
|
||||
"dynamodb:Scan",
|
||||
"dynamodb:ConditionCheckItem",
|
||||
"dynamodb:BatchWriteItem",
|
||||
"dynamodb:PutItem",
|
||||
"dynamodb:UpdateItem",
|
||||
"dynamodb:DeleteItem",
|
||||
"dynamodb:DescribeTable"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": [
|
||||
{
|
||||
"Fn::GetAtt": [
|
||||
"UsersTable9725E9C8",
|
||||
"Arn"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Action": [
|
||||
"dynamodb:GetRecords",
|
||||
"dynamodb:GetShardIterator"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": [
|
||||
{
|
||||
"Fn::GetAtt": [
|
||||
"UsersTable9725E9C8",
|
||||
"Arn"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Action": [
|
||||
"bedrock-agentcore:CreateEvent",
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
import boto3
|
||||
import urllib.request
|
||||
from typing import Any
|
||||
|
||||
# AWS clients
|
||||
_ddb = None
|
||||
_agentcore = None
|
||||
|
||||
|
||||
def get_ddb():
|
||||
global _ddb
|
||||
if _ddb is None:
|
||||
_ddb = boto3.resource('dynamodb')
|
||||
return _ddb
|
||||
|
||||
|
||||
def get_agentcore():
|
||||
global _agentcore
|
||||
if _agentcore is None:
|
||||
_agentcore = boto3.client('bedrock-agentcore', region_name='us-east-1')
|
||||
return _agentcore
|
||||
|
||||
|
||||
def get_or_create_user(actor_id: str, from_info: dict) -> dict:
|
||||
"""Look up user in registry, auto-registering on first contact."""
|
||||
table_name = os.environ.get('USERS_TABLE_NAME', '')
|
||||
if not table_name:
|
||||
return {'actor_id': actor_id, 'display_name': from_info.get('from_name', actor_id)}
|
||||
table = get_ddb().Table(table_name)
|
||||
response = table.get_item(Key={'actor_id': actor_id})
|
||||
item = response.get('Item')
|
||||
if item:
|
||||
return item
|
||||
now = int(time.time())
|
||||
item = {
|
||||
'actor_id': actor_id,
|
||||
'display_name': from_info.get('from_name') or actor_id,
|
||||
'telegram_username': from_info.get('from_username', ''),
|
||||
'created_at': str(now),
|
||||
'status': 'pending',
|
||||
'services': {},
|
||||
}
|
||||
table.put_item(Item=item)
|
||||
print(f'[agent-runner] Registered new user (pending): {actor_id}')
|
||||
return item
|
||||
|
||||
|
||||
def update_user_status(actor_id: str, name: str, status: str) -> None:
|
||||
table_name = os.environ.get('USERS_TABLE_NAME', '')
|
||||
if not table_name:
|
||||
return
|
||||
table = get_ddb().Table(table_name)
|
||||
table.update_item(
|
||||
Key={'actor_id': actor_id},
|
||||
UpdateExpression='SET display_name = :n, #s = :s',
|
||||
ExpressionAttributeNames={'#s': 'status'},
|
||||
ExpressionAttributeValues={':n': name, ':s': status},
|
||||
)
|
||||
|
||||
|
||||
def send_telegram_direct(chat_id: str, token: str, text: str) -> None:
|
||||
url = f'https://api.telegram.org/bot{token}/sendMessage'
|
||||
data = json.dumps({'chat_id': chat_id, 'text': text}).encode()
|
||||
req = urllib.request.Request(url, data=data, headers={'Content-Type': 'application/json'})
|
||||
urllib.request.urlopen(req, timeout=10)
|
||||
|
||||
|
||||
def get_or_create_session(actor_id: str) -> str:
|
||||
"""Look up active session for actor, or create a new one."""
|
||||
table = get_ddb().Table(os.environ['SESSION_TABLE_NAME'])
|
||||
|
||||
response = table.get_item(Key={'actor_id': actor_id})
|
||||
item = response.get('Item')
|
||||
|
||||
now = int(time.time())
|
||||
ttl_8hr = now + (8 * 3600)
|
||||
|
||||
if item and item.get('ttl', 0) > now:
|
||||
# Active session exists — extend TTL
|
||||
table.update_item(
|
||||
Key={'actor_id': actor_id},
|
||||
UpdateExpression='SET #ttl = :ttl',
|
||||
ExpressionAttributeNames={'#ttl': 'ttl'},
|
||||
ExpressionAttributeValues={':ttl': ttl_8hr},
|
||||
)
|
||||
return item['session_id']
|
||||
|
||||
# Create new session
|
||||
session_id = str(uuid.uuid4())
|
||||
table.put_item(Item={
|
||||
'actor_id': actor_id,
|
||||
'session_id': session_id,
|
||||
'created_at': str(now),
|
||||
'ttl': ttl_8hr,
|
||||
})
|
||||
return session_id
|
||||
|
||||
|
||||
def handler(event, context):
|
||||
# ── Parse SQS records (FIFO — all from same actor) ───────────────────
|
||||
records = []
|
||||
for record in event.get('Records', []):
|
||||
try:
|
||||
records.append(json.loads(record['body']))
|
||||
except (json.JSONDecodeError, KeyError):
|
||||
continue
|
||||
|
||||
if not records:
|
||||
return
|
||||
|
||||
first = records[0]
|
||||
channel = first.get('channel', 'telegram')
|
||||
chat_id = first.get('chat_id', '')
|
||||
actor_id = f"{channel}:{chat_id}"
|
||||
|
||||
# ── User registry ─────────────────────────────────────────────────────
|
||||
from_info = first.get('messages', [{}])[0]
|
||||
user_profile = get_or_create_user(actor_id, from_info)
|
||||
|
||||
# ── Onboarding gate ─────────────────────────────────────────────────────
|
||||
table_name = os.environ.get('USERS_TABLE_NAME', '')
|
||||
if table_name and user_profile.get('status', 'active') == 'pending':
|
||||
raw_prompt = records[0]['messages'][0]['text'] if records else ''
|
||||
is_name_msg = bool(raw_prompt and len(raw_prompt.strip()) < 50 and '?' not in raw_prompt)
|
||||
if is_name_msg:
|
||||
name = raw_prompt.strip()
|
||||
update_user_status(actor_id, name=name, status='active')
|
||||
user_profile['display_name'] = name
|
||||
user_profile['status'] = 'active'
|
||||
prompt = f"[System: User just registered with name '{name}'. Welcome them warmly and ask how you can help.]"
|
||||
else:
|
||||
bot_token_secret_arn = os.environ.get('TELEGRAM_BOT_TOKEN_SECRET_ARN', '')
|
||||
bot_token = ''
|
||||
if bot_token_secret_arn:
|
||||
sm = boto3.client('secretsmanager', region_name='us-east-1')
|
||||
bot_token = sm.get_secret_value(SecretId=bot_token_secret_arn)['SecretString']
|
||||
send_telegram_direct(chat_id, bot_token, "Hi! I don't recognize you yet. What's your name?")
|
||||
return
|
||||
# ── Get or create AgentCore session ──────────────────────────────────
|
||||
session_id = get_or_create_session(actor_id)
|
||||
print(f"[agent-runner] actor={actor_id} session={session_id} user={user_profile.get('display_name', '')}")
|
||||
|
||||
# ── Bundle messages ───────────────────────────────────────────────────
|
||||
if len(records) == 1:
|
||||
prompt = records[0]['messages'][0]['text']
|
||||
else:
|
||||
lines = [
|
||||
f"[{i+1}] {r['messages'][0]['text']}"
|
||||
for i, r in enumerate(records)
|
||||
]
|
||||
prompt = f"You have {len(records)} queued messages:\n" + "\n".join(lines)
|
||||
|
||||
# ── Build payload for AgentCore Runtime 1 ────────────────────────────
|
||||
payload: dict[str, Any] = {
|
||||
'prompt': prompt,
|
||||
'actor_id': actor_id,
|
||||
'session_id': session_id,
|
||||
'user_profile': {
|
||||
'display_name': user_profile.get('display_name', actor_id),
|
||||
'telegram_username': user_profile.get('telegram_username', ''),
|
||||
'google_email': user_profile.get('google_email', ''),
|
||||
'allowed': user_profile.get('allowed', True),
|
||||
'services': user_profile.get('services', {}),
|
||||
},
|
||||
'channel_adapter': {
|
||||
'type': channel,
|
||||
'target_id': str(chat_id),
|
||||
'bot_token_secret_arn': os.environ.get('TELEGRAM_BOT_TOKEN_SECRET_ARN', ''),
|
||||
},
|
||||
}
|
||||
|
||||
# ── Invoke AgentCore Runtime 1 ────────────────────────────────────────
|
||||
runtime_arn = os.environ.get('RUNTIME_1_ARN', '')
|
||||
if not runtime_arn or runtime_arn == 'PLACEHOLDER_SET_AFTER_RUNTIME_DEPLOY':
|
||||
print(f"[agent-runner] RUNTIME_1_ARN not set — skipping AgentCore invoke")
|
||||
print(f"[agent-runner] Would have sent: {json.dumps(payload)[:200]}")
|
||||
return
|
||||
|
||||
client = get_agentcore()
|
||||
response = client.invoke_agent_runtime(
|
||||
agentRuntimeArn=runtime_arn,
|
||||
runtimeSessionId=session_id,
|
||||
payload=json.dumps(payload).encode(),
|
||||
)
|
||||
|
||||
# Consume streaming response (agent delivers to Telegram via send_message tool)
|
||||
for chunk in response.get('response', []):
|
||||
pass # intentional no-op — agent handles delivery internally
|
||||
|
||||
print(f"[agent-runner] Completed session={session_id} actor={actor_id}")
|
||||
@@ -0,0 +1 @@
|
||||
boto3>=1.34.0
|
||||
@@ -18,7 +18,7 @@
|
||||
"validateOnSynth": false,
|
||||
"assumeRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-deploy-role-495395224548-us-east-1",
|
||||
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-cfn-exec-role-495395224548-us-east-1",
|
||||
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-495395224548-us-east-1/fdf1ff81e9e0ded898f1c1d03a2bb8bbe0bbf63689426c24072f179b49b527c6.json",
|
||||
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-495395224548-us-east-1/a31aaa0bc9eab4fd6f17f10795fba05983dba0c88e83a263fe9fffe930da06b9.json",
|
||||
"requiresBootstrapStackVersion": 6,
|
||||
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
|
||||
"additionalDependencies": [
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -170,6 +170,7 @@ export class AgentClawStack extends cdk.Stack {
|
||||
workspaceBucket.grantRead(runtime1Role);
|
||||
botTokenSecret.grantRead(runtime1Role);
|
||||
braveApiKeySecret.grantRead(runtime1Role);
|
||||
usersTable.grantReadWriteData(runtime1Role);
|
||||
// Google secret grants added after workspace_mcp section below
|
||||
runtime1Role.addToPolicy(new iam.PolicyStatement({
|
||||
actions: [
|
||||
|
||||
Reference in New Issue
Block a user