thinkwork-cli 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/cli.js +220 -81
- package/dist/terraform/examples/greenfield/main.tf +156 -2
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +10 -0
- package/dist/terraform/modules/app/agentcore-runtime/main.tf +33 -0
- package/dist/terraform/modules/app/job-triggers/main.tf +21 -0
- package/dist/terraform/modules/app/lambda-api/.build/placeholder.zip +0 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +66 -16
- package/dist/terraform/modules/app/lambda-api/main.tf +120 -2
- package/dist/terraform/modules/app/lambda-api/outputs.tf +20 -0
- package/dist/terraform/modules/app/lambda-api/variables.tf +22 -0
- package/dist/terraform/modules/app/ses-email/main.tf +173 -10
- package/dist/terraform/modules/app/static-site/main.tf +37 -14
- package/dist/terraform/modules/app/www-dns/README.md +39 -0
- package/dist/terraform/modules/app/www-dns/main.tf +245 -0
- package/dist/terraform/modules/app/www-dns/outputs.tf +14 -0
- package/dist/terraform/modules/app/www-dns/variables.tf +43 -0
- package/dist/terraform/modules/thinkwork/main.tf +52 -9
- package/dist/terraform/modules/thinkwork/outputs.tf +32 -0
- package/dist/terraform/modules/thinkwork/variables.tf +57 -3
- package/package.json +1 -1
|
@@ -38,6 +38,26 @@ variable "agentcore_memory_id" {
|
|
|
38
38
|
default = ""
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
variable "memory_engine" {
|
|
42
|
+
description = "Active long-term memory engine ('hindsight' or 'agentcore'). Surfaced to the runtime as MEMORY_ENGINE for telemetry/debugging only; engine selection itself happens in the API's normalized memory layer when memory-retain is invoked."
|
|
43
|
+
type = string
|
|
44
|
+
default = "hindsight"
|
|
45
|
+
validation {
|
|
46
|
+
condition = contains(["hindsight", "agentcore"], var.memory_engine)
|
|
47
|
+
error_message = "memory_engine must be 'hindsight' or 'agentcore'."
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# memory-retain Lambda name + ARN are constructed locally rather than
|
|
52
|
+
# taken as inputs to avoid a circular dependency: the lambda-api module
|
|
53
|
+
# already consumes this module's outputs (agentcore_function_name/arn).
|
|
54
|
+
# The Lambda name follows the deterministic pattern from
|
|
55
|
+
# lambda-api/handlers.tf: thinkwork-${stage}-api-${handler_name}.
|
|
56
|
+
locals {
|
|
57
|
+
memory_retain_fn_name = "thinkwork-${var.stage}-api-memory-retain"
|
|
58
|
+
memory_retain_fn_arn = "arn:aws:lambda:${var.region}:${var.account_id}:function:${local.memory_retain_fn_name}"
|
|
59
|
+
}
|
|
60
|
+
|
|
41
61
|
################################################################################
|
|
42
62
|
# ECR Repository
|
|
43
63
|
################################################################################
|
|
@@ -157,6 +177,17 @@ resource "aws_iam_role_policy" "agentcore" {
|
|
|
157
177
|
Action = ["ssm:GetParameter", "ssm:PutParameter"]
|
|
158
178
|
Resource = "arn:aws:ssm:${var.region}:${var.account_id}:parameter/thinkwork/${var.stage}/agentcore/*"
|
|
159
179
|
},
|
|
180
|
+
{
|
|
181
|
+
# Async-invoke the memory-retain Lambda after every chat turn so
|
|
182
|
+
# the API's normalized memory layer can run the active engine's
|
|
183
|
+
# retainTurn() path (Hindsight POST /memories or AgentCore
|
|
184
|
+
# CreateEvent). InvocationType=Event from the Python client; this
|
|
185
|
+
# Lambda is the only target.
|
|
186
|
+
Sid = "MemoryRetainInvoke"
|
|
187
|
+
Effect = "Allow"
|
|
188
|
+
Action = ["lambda:InvokeFunction"]
|
|
189
|
+
Resource = local.memory_retain_fn_arn
|
|
190
|
+
},
|
|
160
191
|
]
|
|
161
192
|
})
|
|
162
193
|
}
|
|
@@ -192,6 +223,8 @@ resource "aws_lambda_function" "agentcore" {
|
|
|
192
223
|
AWS_LWA_PORT = "8080"
|
|
193
224
|
AGENTCORE_MEMORY_ID = var.agentcore_memory_id
|
|
194
225
|
AGENTCORE_FILES_BUCKET = var.bucket_name
|
|
226
|
+
MEMORY_ENGINE = var.memory_engine
|
|
227
|
+
MEMORY_RETAIN_FN_NAME = local.memory_retain_fn_name
|
|
195
228
|
}
|
|
196
229
|
}
|
|
197
230
|
|
|
@@ -55,6 +55,27 @@ resource "aws_iam_role" "job_scheduler" {
|
|
|
55
55
|
})
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
# When job-schedule-manager creates a schedule, it sets this role as the
|
|
59
|
+
# target RoleArn. EventBridge Scheduler assumes it and invokes the
|
|
60
|
+
# job-trigger Lambda. ARN is constructed by naming convention so this
|
|
61
|
+
# module doesn't have to import the lambda-api module's outputs.
|
|
62
|
+
resource "aws_iam_role_policy" "job_scheduler_invoke" {
|
|
63
|
+
name = "invoke-job-trigger"
|
|
64
|
+
role = aws_iam_role.job_scheduler.id
|
|
65
|
+
|
|
66
|
+
policy = jsonencode({
|
|
67
|
+
Version = "2012-10-17"
|
|
68
|
+
Statement = [{
|
|
69
|
+
Effect = "Allow"
|
|
70
|
+
Action = ["lambda:InvokeFunction"]
|
|
71
|
+
Resource = [
|
|
72
|
+
"arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-job-trigger",
|
|
73
|
+
"arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-job-trigger:*",
|
|
74
|
+
]
|
|
75
|
+
}]
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
58
79
|
################################################################################
|
|
59
80
|
# Outputs
|
|
60
81
|
################################################################################
|
|
Binary file
|
|
@@ -28,17 +28,40 @@ locals {
|
|
|
28
28
|
GRAPHQL_API_KEY = var.appsync_api_key
|
|
29
29
|
API_AUTH_SECRET = var.api_auth_secret
|
|
30
30
|
THINKWORK_API_SECRET = var.api_auth_secret
|
|
31
|
-
|
|
31
|
+
EMAIL_HMAC_SECRET = var.api_auth_secret
|
|
32
|
+
THINKWORK_API_URL = "https://${aws_apigatewayv2_api.main.id}.execute-api.${var.region}.amazonaws.com"
|
|
32
33
|
AGENTCORE_FUNCTION_NAME = var.agentcore_function_name
|
|
33
34
|
WORKSPACE_BUCKET = var.bucket_name
|
|
34
35
|
HINDSIGHT_ENDPOINT = var.hindsight_endpoint
|
|
35
36
|
AGENTCORE_MEMORY_ID = var.agentcore_memory_id
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
MEMORY_ENGINE = var.memory_engine
|
|
38
|
+
# Skip the SSM indirection for cross-function ARN lookup. Terraform
|
|
39
|
+
# already knows this ARN at apply time and the Lambda role's SSM
|
|
40
|
+
# permission has been a recurring source of silent failures where
|
|
41
|
+
# getChatAgentInvokeFnArn falls back to null and sendMessage loses
|
|
42
|
+
# message_history on the wakeup-processor fallback path.
|
|
43
|
+
CHAT_AGENT_INVOKE_FN_ARN = "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-chat-agent-invoke"
|
|
44
|
+
ADMIN_URL = var.admin_url
|
|
45
|
+
DOCS_URL = var.docs_url
|
|
46
|
+
APPSYNC_REALTIME_URL = var.appsync_realtime_url
|
|
47
|
+
ECR_REPOSITORY_URL = var.ecr_repository_url
|
|
48
|
+
AWS_ACCOUNT_ID = var.account_id
|
|
49
|
+
NODE_OPTIONS = "--enable-source-maps"
|
|
50
|
+
# LastMile Tasks REST API base URL — feature-flags the outbound sync
|
|
51
|
+
# path. When unset, syncExternalTaskOnCreate writes sync_status='local'
|
|
52
|
+
# and the workflow picker proxy returns 503. Set to the LMI develop /
|
|
53
|
+
# staging / prod base URL per stage to enable real cross-system sync.
|
|
54
|
+
LASTMILE_TASKS_API_URL = var.lastmile_tasks_api_url
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Per-handler env-var overrides. ARNs are constructed from the naming
|
|
58
|
+
# pattern (same trick as lambda_api_cross_invoke in main.tf) so we don't
|
|
59
|
+
# introduce a self-referential dependency inside the handler for_each.
|
|
60
|
+
handler_extra_env = {
|
|
61
|
+
"job-schedule-manager" = {
|
|
62
|
+
JOB_TRIGGER_ARN = "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-job-trigger"
|
|
63
|
+
JOB_TRIGGER_ROLE_ARN = var.job_scheduler_role_arn
|
|
64
|
+
}
|
|
42
65
|
}
|
|
43
66
|
}
|
|
44
67
|
|
|
@@ -69,8 +92,11 @@ resource "aws_lambda_function" "handler" {
|
|
|
69
92
|
"guardrails",
|
|
70
93
|
"scheduled-jobs",
|
|
71
94
|
"job-schedule-manager",
|
|
95
|
+
"job-trigger",
|
|
72
96
|
"webhooks",
|
|
73
97
|
"webhooks-admin",
|
|
98
|
+
"webhook-deliveries-cleanup",
|
|
99
|
+
"task-connectors",
|
|
74
100
|
"workspace-files",
|
|
75
101
|
"knowledge-base-manager",
|
|
76
102
|
"knowledge-base-files",
|
|
@@ -79,10 +105,9 @@ resource "aws_lambda_function" "handler" {
|
|
|
79
105
|
"github-app",
|
|
80
106
|
"github-repos",
|
|
81
107
|
"memory",
|
|
108
|
+
"memory-retain",
|
|
82
109
|
"artifact-deliver",
|
|
83
110
|
"recipe-refresh",
|
|
84
|
-
"connector-installs",
|
|
85
|
-
"connector-secrets",
|
|
86
111
|
"agent-skills-list",
|
|
87
112
|
"bootstrap-workspaces",
|
|
88
113
|
"code-factory",
|
|
@@ -99,9 +124,11 @@ resource "aws_lambda_function" "handler" {
|
|
|
99
124
|
source_code_hash = filebase64sha256("${var.lambda_zips_dir}/${each.key}.zip")
|
|
100
125
|
|
|
101
126
|
environment {
|
|
102
|
-
variables = merge(
|
|
103
|
-
|
|
104
|
-
|
|
127
|
+
variables = merge(
|
|
128
|
+
local.common_env,
|
|
129
|
+
{ FUNCTION_NAME = each.key },
|
|
130
|
+
lookup(local.handler_extra_env, each.key, {}),
|
|
131
|
+
)
|
|
105
132
|
}
|
|
106
133
|
|
|
107
134
|
tags = {
|
|
@@ -195,6 +222,10 @@ locals {
|
|
|
195
222
|
"ANY /api/webhooks/{proxy+}" = "webhooks-admin"
|
|
196
223
|
"ANY /api/webhooks" = "webhooks-admin"
|
|
197
224
|
|
|
225
|
+
# Task Connectors admin
|
|
226
|
+
"ANY /api/task-connectors/{proxy+}" = "task-connectors"
|
|
227
|
+
"ANY /api/task-connectors" = "task-connectors"
|
|
228
|
+
|
|
198
229
|
# Workspace files
|
|
199
230
|
"ANY /api/workspaces/{proxy+}" = "workspace-files"
|
|
200
231
|
|
|
@@ -216,10 +247,6 @@ locals {
|
|
|
216
247
|
# GitHub App
|
|
217
248
|
"ANY /api/github-app/{proxy+}" = "github-app"
|
|
218
249
|
"POST /api/github/webhook" = "github-app"
|
|
219
|
-
|
|
220
|
-
# Connectors
|
|
221
|
-
"ANY /api/connector-installs/{proxy+}" = "connector-installs"
|
|
222
|
-
"ANY /api/connector-secrets/{proxy+}" = "connector-secrets"
|
|
223
250
|
} : {}
|
|
224
251
|
}
|
|
225
252
|
|
|
@@ -272,6 +299,28 @@ resource "aws_scheduler_schedule" "wakeup_processor" {
|
|
|
272
299
|
}
|
|
273
300
|
}
|
|
274
301
|
|
|
302
|
+
# ---------------------------------------------------------------------------
|
|
303
|
+
# webhook_deliveries retention cron — daily delete of rows older than 90 days
|
|
304
|
+
# ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
resource "aws_scheduler_schedule" "webhook_deliveries_cleanup" {
|
|
307
|
+
count = local.use_local_zips ? 1 : 0
|
|
308
|
+
|
|
309
|
+
name = "thinkwork-${var.stage}-webhook-deliveries-cleanup"
|
|
310
|
+
group_name = "default"
|
|
311
|
+
schedule_expression = "cron(0 4 * * ? *)" # daily at 04:00 UTC
|
|
312
|
+
state = "ENABLED"
|
|
313
|
+
|
|
314
|
+
flexible_time_window {
|
|
315
|
+
mode = "OFF"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
target {
|
|
319
|
+
arn = aws_lambda_function.handler["webhook-deliveries-cleanup"].arn
|
|
320
|
+
role_arn = aws_iam_role.scheduler.arn
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
275
324
|
resource "aws_iam_role" "scheduler" {
|
|
276
325
|
name = "thinkwork-${var.stage}-scheduler-role"
|
|
277
326
|
|
|
@@ -308,6 +357,7 @@ resource "aws_ssm_parameter" "lambda_arns" {
|
|
|
308
357
|
"chat-agent-invoke-fn-arn" = aws_lambda_function.handler["chat-agent-invoke"].arn
|
|
309
358
|
"kb-manager-fn-arn" = aws_lambda_function.handler["knowledge-base-manager"].arn
|
|
310
359
|
"job-schedule-manager-fn-arn" = aws_lambda_function.handler["job-schedule-manager"].arn
|
|
360
|
+
"memory-retain-fn-arn" = aws_lambda_function.handler["memory-retain"].arn
|
|
311
361
|
} : {}
|
|
312
362
|
|
|
313
363
|
name = "/thinkwork/${var.stage}/${each.key}"
|
|
@@ -28,7 +28,7 @@ resource "aws_apigatewayv2_api" "main" {
|
|
|
28
28
|
|
|
29
29
|
cors_configuration {
|
|
30
30
|
allow_headers = ["Content-Type", "Authorization", "x-api-key", "x-tenant-id", "x-tenant-slug", "x-principal-id"]
|
|
31
|
-
allow_methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
|
|
31
|
+
allow_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
|
|
32
32
|
allow_origins = var.cors_allowed_origins
|
|
33
33
|
max_age = 3600
|
|
34
34
|
}
|
|
@@ -206,6 +206,30 @@ resource "aws_iam_role_policy" "lambda_bedrock" {
|
|
|
206
206
|
})
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
# SES send permissions for the email-send handler. Scoped to any
|
|
210
|
+
# verified identity in this account+region so the email-send Lambda
|
|
211
|
+
# can SendRawEmail from agents.thinkwork.ai (and any other domain
|
|
212
|
+
# identity a future deployment might add).
|
|
213
|
+
resource "aws_iam_role_policy" "lambda_ses_send" {
|
|
214
|
+
name = "ses-send"
|
|
215
|
+
role = aws_iam_role.lambda.id
|
|
216
|
+
|
|
217
|
+
policy = jsonencode({
|
|
218
|
+
Version = "2012-10-17"
|
|
219
|
+
Statement = [{
|
|
220
|
+
Effect = "Allow"
|
|
221
|
+
Action = [
|
|
222
|
+
"ses:SendEmail",
|
|
223
|
+
"ses:SendRawEmail",
|
|
224
|
+
]
|
|
225
|
+
Resource = [
|
|
226
|
+
"arn:aws:ses:${var.region}:${var.account_id}:identity/*",
|
|
227
|
+
"arn:aws:ses:${var.region}:${var.account_id}:configuration-set/*",
|
|
228
|
+
]
|
|
229
|
+
}]
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
209
233
|
# Allow API Lambdas to directly invoke the AgentCore Lambda. Used by
|
|
210
234
|
# chat-agent-invoke (and future wake-up/retry paths) via InvokeCommand.
|
|
211
235
|
resource "aws_iam_role_policy" "lambda_agentcore_invoke" {
|
|
@@ -232,7 +256,7 @@ resource "aws_iam_role_policy" "lambda_agentcore_invoke" {
|
|
|
232
256
|
# memoryRecords / memorySearch call ListMemoryRecordsCommand to fetch
|
|
233
257
|
# records across the tenant's agents.
|
|
234
258
|
resource "aws_iam_role_policy" "lambda_agentcore_memory" {
|
|
235
|
-
name = "agentcore-memory-
|
|
259
|
+
name = "agentcore-memory-rw"
|
|
236
260
|
role = aws_iam_role.lambda.id
|
|
237
261
|
|
|
238
262
|
policy = jsonencode({
|
|
@@ -243,12 +267,106 @@ resource "aws_iam_role_policy" "lambda_agentcore_memory" {
|
|
|
243
267
|
"bedrock-agentcore:ListMemoryRecords",
|
|
244
268
|
"bedrock-agentcore:RetrieveMemoryRecords",
|
|
245
269
|
"bedrock-agentcore:GetMemoryRecord",
|
|
270
|
+
"bedrock-agentcore:BatchCreateMemoryRecords",
|
|
271
|
+
"bedrock-agentcore:BatchUpdateMemoryRecords",
|
|
272
|
+
"bedrock-agentcore:BatchDeleteMemoryRecords",
|
|
273
|
+
"bedrock-agentcore:DeleteMemoryRecord",
|
|
246
274
|
]
|
|
247
275
|
Resource = "*"
|
|
248
276
|
}]
|
|
249
277
|
})
|
|
250
278
|
}
|
|
251
279
|
|
|
280
|
+
# graphql-http's sendMessage mutation reads SSM parameters like
|
|
281
|
+
# /thinkwork/${stage}/chat-agent-invoke-fn-arn to discover the direct
|
|
282
|
+
# Lambda targets for cross-function invocation. Without this, the SSM
|
|
283
|
+
# GetParameter call fails with AccessDenied, the caller silently
|
|
284
|
+
# catches the error, and sendMessage falls back to the wakeup-processor
|
|
285
|
+
# path — which doesn't load messages_history from Aurora. That's why
|
|
286
|
+
# multi-turn chat was losing prior context: history was only loaded on
|
|
287
|
+
# the direct path, which never ran.
|
|
288
|
+
resource "aws_iam_role_policy" "lambda_ssm_read" {
|
|
289
|
+
name = "ssm-param-read"
|
|
290
|
+
role = aws_iam_role.lambda.id
|
|
291
|
+
|
|
292
|
+
policy = jsonencode({
|
|
293
|
+
Version = "2012-10-17"
|
|
294
|
+
Statement = [{
|
|
295
|
+
Effect = "Allow"
|
|
296
|
+
Action = [
|
|
297
|
+
"ssm:GetParameter",
|
|
298
|
+
"ssm:GetParameters",
|
|
299
|
+
]
|
|
300
|
+
Resource = "arn:aws:ssm:${var.region}:${var.account_id}:parameter/thinkwork/${var.stage}/*"
|
|
301
|
+
}]
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# job-schedule-manager creates/updates/deletes EventBridge Scheduler
|
|
306
|
+
# schedules (and the thinkwork-jobs schedule group on first use). Without
|
|
307
|
+
# these permissions the manager Lambda threw silently and every scheduled
|
|
308
|
+
# automation was orphaned with eb_schedule_name = null.
|
|
309
|
+
resource "aws_iam_role_policy" "lambda_scheduler" {
|
|
310
|
+
name = "eventbridge-scheduler-rw"
|
|
311
|
+
role = aws_iam_role.lambda.id
|
|
312
|
+
|
|
313
|
+
policy = jsonencode({
|
|
314
|
+
Version = "2012-10-17"
|
|
315
|
+
Statement = [
|
|
316
|
+
{
|
|
317
|
+
Effect = "Allow"
|
|
318
|
+
Action = [
|
|
319
|
+
"scheduler:CreateSchedule",
|
|
320
|
+
"scheduler:UpdateSchedule",
|
|
321
|
+
"scheduler:DeleteSchedule",
|
|
322
|
+
"scheduler:GetSchedule",
|
|
323
|
+
"scheduler:ListSchedules",
|
|
324
|
+
"scheduler:CreateScheduleGroup",
|
|
325
|
+
"scheduler:GetScheduleGroup",
|
|
326
|
+
"scheduler:DeleteScheduleGroup",
|
|
327
|
+
"scheduler:TagResource",
|
|
328
|
+
]
|
|
329
|
+
Resource = "*"
|
|
330
|
+
},
|
|
331
|
+
# Scheduler.CreateSchedule takes a RoleArn for the target; AWS requires
|
|
332
|
+
# the caller to have iam:PassRole on that role. Without this the
|
|
333
|
+
# CreateSchedule call fails with AccessDenied even if the scheduler
|
|
334
|
+
# permissions above are set.
|
|
335
|
+
{
|
|
336
|
+
Effect = "Allow"
|
|
337
|
+
Action = ["iam:PassRole"]
|
|
338
|
+
Resource = var.job_scheduler_role_arn != "" ? var.job_scheduler_role_arn : "*"
|
|
339
|
+
},
|
|
340
|
+
]
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
# Allow API handler Lambdas to invoke each other directly. sendMessage
|
|
345
|
+
# dispatches to chat-agent-invoke for instant chat response; the memory
|
|
346
|
+
# resolvers reach knowledge-base-manager and job-schedule-manager for
|
|
347
|
+
# admin-driven operations. The existing lambda_agentcore_invoke policy
|
|
348
|
+
# covers the Strands runtime Lambda only — this one covers internal
|
|
349
|
+
# api-to-api calls. ARNs are constructed deterministically from the
|
|
350
|
+
# handler naming pattern so we don't create a dependency cycle with the
|
|
351
|
+
# handler resource.
|
|
352
|
+
resource "aws_iam_role_policy" "lambda_api_cross_invoke" {
|
|
353
|
+
name = "api-cross-function-invoke"
|
|
354
|
+
role = aws_iam_role.lambda.id
|
|
355
|
+
|
|
356
|
+
policy = jsonencode({
|
|
357
|
+
Version = "2012-10-17"
|
|
358
|
+
Statement = [{
|
|
359
|
+
Effect = "Allow"
|
|
360
|
+
Action = ["lambda:InvokeFunction"]
|
|
361
|
+
Resource = [
|
|
362
|
+
"arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-chat-agent-invoke",
|
|
363
|
+
"arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-knowledge-base-manager",
|
|
364
|
+
"arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-job-schedule-manager",
|
|
365
|
+
]
|
|
366
|
+
}]
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
|
|
252
370
|
################################################################################
|
|
253
371
|
# Placeholder Lambda — proves the infrastructure works
|
|
254
372
|
#
|
|
@@ -22,3 +22,23 @@ output "lambda_role_name" {
|
|
|
22
22
|
description = "Shared Lambda execution role name"
|
|
23
23
|
value = aws_iam_role.lambda.name
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
output "memory_retain_fn_name" {
|
|
27
|
+
description = "Memory-retain Lambda function name. Strands runtime invokes this directly to push conversational turns into the active memory engine."
|
|
28
|
+
value = local.use_local_zips ? aws_lambda_function.handler["memory-retain"].function_name : ""
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
output "memory_retain_fn_arn" {
|
|
32
|
+
description = "Memory-retain Lambda ARN. Used to grant lambda:InvokeFunction to the agentcore-runtime role."
|
|
33
|
+
value = local.use_local_zips ? aws_lambda_function.handler["memory-retain"].arn : ""
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
output "email_inbound_fn_arn" {
|
|
37
|
+
description = "email-inbound Lambda ARN. Used by the SES module to wire the receipt rule Lambda action."
|
|
38
|
+
value = local.use_local_zips ? aws_lambda_function.handler["email-inbound"].arn : ""
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
output "email_inbound_fn_name" {
|
|
42
|
+
description = "email-inbound Lambda function name. Used by the SES module for lambda:InvokeFunction permissions."
|
|
43
|
+
value = local.use_local_zips ? aws_lambda_function.handler["email-inbound"].function_name : ""
|
|
44
|
+
}
|
|
@@ -146,6 +146,16 @@ variable "agentcore_memory_id" {
|
|
|
146
146
|
default = ""
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
variable "memory_engine" {
|
|
150
|
+
description = "Active long-term memory engine for this deployment. Exactly one engine is canonical for recall/inspect/export. Defaults to 'hindsight' for hosted ThinkWork; self-hosted/serverless deployments may choose 'agentcore'."
|
|
151
|
+
type = string
|
|
152
|
+
default = "hindsight"
|
|
153
|
+
validation {
|
|
154
|
+
condition = contains(["hindsight", "agentcore"], var.memory_engine)
|
|
155
|
+
error_message = "memory_engine must be 'hindsight' or 'agentcore'."
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
149
159
|
variable "agentcore_function_name" {
|
|
150
160
|
description = "AgentCore Lambda function name (for direct SDK invoke)"
|
|
151
161
|
type = string
|
|
@@ -187,3 +197,15 @@ variable "cors_allowed_origins" {
|
|
|
187
197
|
type = list(string)
|
|
188
198
|
default = ["*"]
|
|
189
199
|
}
|
|
200
|
+
|
|
201
|
+
variable "job_scheduler_role_arn" {
|
|
202
|
+
description = "IAM role ARN that EventBridge Scheduler assumes to invoke the job-trigger Lambda. Passed from the job-triggers module."
|
|
203
|
+
type = string
|
|
204
|
+
default = ""
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
variable "lastmile_tasks_api_url" {
|
|
208
|
+
description = "Base URL of the LastMile Tasks REST API used by the outbound sync path (POST /tasks, GET /workflows, etc). Leave blank to feature-flag the integration off; mobile-created tasks then land in sync_status='local' until the URL is set."
|
|
209
|
+
type = string
|
|
210
|
+
default = ""
|
|
211
|
+
}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
################################################################################
|
|
2
2
|
# SES Email — App Module
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# Wires up inbound and outbound email for a delegated subdomain
|
|
5
|
+
# (e.g. agents.thinkwork.ai). The module:
|
|
6
|
+
#
|
|
7
|
+
# 1. Creates a Route53 hosted zone for the subdomain (Option A — delegated
|
|
8
|
+
# subzone). The operator pastes the output name servers at whatever hosts
|
|
9
|
+
# the parent domain (Google, Squarespace, Cloudflare, etc.).
|
|
10
|
+
# 2. Creates the SES domain identity + DKIM tokens.
|
|
11
|
+
# 3. Writes the SES verification TXT, DKIM CNAMEs, and an MX record into the
|
|
12
|
+
# new subzone.
|
|
13
|
+
# 4. Creates an SES receipt rule set that stores inbound mail in S3 at
|
|
14
|
+
# `email/inbound/<sesMessageId>` and invokes the email-inbound Lambda.
|
|
7
15
|
################################################################################
|
|
8
16
|
|
|
9
17
|
variable "stage" {
|
|
@@ -16,36 +24,191 @@ variable "account_id" {
|
|
|
16
24
|
type = string
|
|
17
25
|
}
|
|
18
26
|
|
|
27
|
+
variable "region" {
|
|
28
|
+
description = "AWS region (determines the inbound SMTP endpoint)"
|
|
29
|
+
type = string
|
|
30
|
+
default = "us-east-1"
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
variable "email_domain" {
|
|
20
|
-
description = "
|
|
34
|
+
description = "Subdomain used for agent email (e.g. agents.thinkwork.ai). Leave empty to skip all SES resources."
|
|
35
|
+
type = string
|
|
36
|
+
default = ""
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
variable "inbound_bucket_name" {
|
|
40
|
+
description = "S3 bucket that SES writes raw inbound .eml files into. Its policy must already allow ses.amazonaws.com PutObject."
|
|
21
41
|
type = string
|
|
22
42
|
default = ""
|
|
23
43
|
}
|
|
24
44
|
|
|
45
|
+
variable "email_inbound_fn_arn" {
|
|
46
|
+
description = "ARN of the email-inbound Lambda. If empty, receipt rule is still created but without a Lambda action."
|
|
47
|
+
type = string
|
|
48
|
+
default = ""
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
variable "email_inbound_fn_name" {
|
|
52
|
+
description = "Function name of the email-inbound Lambda (for the Lambda permission)."
|
|
53
|
+
type = string
|
|
54
|
+
default = ""
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
variable "manage_active_rule_set" {
|
|
58
|
+
description = "Activate the receipt rule set. Only ONE rule set can be active per region per account, so set false in secondary stages that share an account."
|
|
59
|
+
type = bool
|
|
60
|
+
default = true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
locals {
|
|
64
|
+
enabled = var.email_domain != ""
|
|
65
|
+
inbound_smtp = "inbound-smtp.${var.region}.amazonaws.com"
|
|
66
|
+
rule_set_name = "thinkwork-${var.stage}-email-rules"
|
|
67
|
+
has_lambda = var.email_inbound_fn_arn != ""
|
|
68
|
+
has_bucket = var.inbound_bucket_name != ""
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
################################################################################
|
|
72
|
+
# Route53 — delegated subzone for the agent email subdomain
|
|
73
|
+
################################################################################
|
|
74
|
+
|
|
75
|
+
resource "aws_route53_zone" "agents" {
|
|
76
|
+
count = local.enabled ? 1 : 0
|
|
77
|
+
name = var.email_domain
|
|
78
|
+
|
|
79
|
+
tags = {
|
|
80
|
+
Name = "thinkwork-${var.stage}-email-zone"
|
|
81
|
+
Stage = var.stage
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
25
85
|
################################################################################
|
|
26
|
-
# SES Domain Identity
|
|
86
|
+
# SES Domain Identity + DKIM
|
|
27
87
|
################################################################################
|
|
28
88
|
|
|
29
89
|
resource "aws_ses_domain_identity" "main" {
|
|
30
|
-
count =
|
|
90
|
+
count = local.enabled ? 1 : 0
|
|
31
91
|
domain = var.email_domain
|
|
32
92
|
}
|
|
33
93
|
|
|
34
94
|
resource "aws_ses_domain_dkim" "main" {
|
|
35
|
-
count =
|
|
95
|
+
count = local.enabled ? 1 : 0
|
|
36
96
|
domain = aws_ses_domain_identity.main[0].domain
|
|
37
97
|
}
|
|
38
98
|
|
|
99
|
+
################################################################################
|
|
100
|
+
# DNS records in the subzone — verification TXT, DKIM CNAMEs, MX
|
|
101
|
+
################################################################################
|
|
102
|
+
|
|
103
|
+
resource "aws_route53_record" "ses_verification" {
|
|
104
|
+
count = local.enabled ? 1 : 0
|
|
105
|
+
zone_id = aws_route53_zone.agents[0].zone_id
|
|
106
|
+
name = "_amazonses.${var.email_domain}"
|
|
107
|
+
type = "TXT"
|
|
108
|
+
ttl = 600
|
|
109
|
+
records = [aws_ses_domain_identity.main[0].verification_token]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
resource "aws_route53_record" "dkim" {
|
|
113
|
+
count = local.enabled ? 3 : 0
|
|
114
|
+
zone_id = aws_route53_zone.agents[0].zone_id
|
|
115
|
+
name = "${aws_ses_domain_dkim.main[0].dkim_tokens[count.index]}._domainkey.${var.email_domain}"
|
|
116
|
+
type = "CNAME"
|
|
117
|
+
ttl = 600
|
|
118
|
+
records = ["${aws_ses_domain_dkim.main[0].dkim_tokens[count.index]}.dkim.amazonses.com"]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
resource "aws_route53_record" "mx" {
|
|
122
|
+
count = local.enabled ? 1 : 0
|
|
123
|
+
zone_id = aws_route53_zone.agents[0].zone_id
|
|
124
|
+
name = var.email_domain
|
|
125
|
+
type = "MX"
|
|
126
|
+
ttl = 600
|
|
127
|
+
records = ["10 ${local.inbound_smtp}"]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
################################################################################
|
|
131
|
+
# SES Receipt Rule Set + Rule → S3 + Lambda
|
|
132
|
+
################################################################################
|
|
133
|
+
|
|
134
|
+
resource "aws_ses_receipt_rule_set" "main" {
|
|
135
|
+
count = local.enabled ? 1 : 0
|
|
136
|
+
rule_set_name = local.rule_set_name
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
resource "aws_ses_active_receipt_rule_set" "main" {
|
|
140
|
+
count = local.enabled && var.manage_active_rule_set ? 1 : 0
|
|
141
|
+
rule_set_name = aws_ses_receipt_rule_set.main[0].rule_set_name
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
resource "aws_lambda_permission" "ses_invoke_email_inbound" {
|
|
145
|
+
count = local.enabled && local.has_lambda ? 1 : 0
|
|
146
|
+
statement_id = "AllowSESInvokeEmailInbound"
|
|
147
|
+
action = "lambda:InvokeFunction"
|
|
148
|
+
function_name = var.email_inbound_fn_name
|
|
149
|
+
principal = "ses.amazonaws.com"
|
|
150
|
+
source_account = var.account_id
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
resource "aws_ses_receipt_rule" "inbound" {
|
|
154
|
+
count = local.enabled ? 1 : 0
|
|
155
|
+
name = "thinkwork-${var.stage}-inbound-email"
|
|
156
|
+
rule_set_name = aws_ses_receipt_rule_set.main[0].rule_set_name
|
|
157
|
+
recipients = [var.email_domain]
|
|
158
|
+
enabled = true
|
|
159
|
+
scan_enabled = true
|
|
160
|
+
|
|
161
|
+
dynamic "s3_action" {
|
|
162
|
+
for_each = local.has_bucket ? [1] : []
|
|
163
|
+
content {
|
|
164
|
+
bucket_name = var.inbound_bucket_name
|
|
165
|
+
object_key_prefix = "email/inbound/"
|
|
166
|
+
position = 1
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
dynamic "lambda_action" {
|
|
171
|
+
for_each = local.has_lambda ? [1] : []
|
|
172
|
+
content {
|
|
173
|
+
function_arn = var.email_inbound_fn_arn
|
|
174
|
+
invocation_type = "Event"
|
|
175
|
+
position = local.has_bucket ? 2 : 1
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
depends_on = [aws_lambda_permission.ses_invoke_email_inbound]
|
|
180
|
+
}
|
|
181
|
+
|
|
39
182
|
################################################################################
|
|
40
183
|
# Outputs
|
|
41
184
|
################################################################################
|
|
42
185
|
|
|
43
186
|
output "ses_domain_identity_arn" {
|
|
44
187
|
description = "SES domain identity ARN"
|
|
45
|
-
value =
|
|
188
|
+
value = local.enabled ? aws_ses_domain_identity.main[0].arn : null
|
|
46
189
|
}
|
|
47
190
|
|
|
48
191
|
output "dkim_tokens" {
|
|
49
|
-
description = "DKIM tokens
|
|
50
|
-
value =
|
|
192
|
+
description = "DKIM tokens (already written as CNAMEs in the subzone)"
|
|
193
|
+
value = local.enabled ? aws_ses_domain_dkim.main[0].dkim_tokens : []
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
output "zone_id" {
|
|
197
|
+
description = "Route53 hosted zone ID for the email subdomain"
|
|
198
|
+
value = local.enabled ? aws_route53_zone.agents[0].zone_id : null
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
output "name_servers" {
|
|
202
|
+
description = "Name servers for the delegated email subzone. Paste these as NS records at the registrar that hosts the parent domain (e.g. Google Domains) before SES can verify."
|
|
203
|
+
value = local.enabled ? aws_route53_zone.agents[0].name_servers : []
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
output "mx_target" {
|
|
207
|
+
description = "MX target host for the email subdomain"
|
|
208
|
+
value = local.enabled ? local.inbound_smtp : null
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
output "rule_set_name" {
|
|
212
|
+
description = "SES receipt rule set name"
|
|
213
|
+
value = local.enabled ? aws_ses_receipt_rule_set.main[0].rule_set_name : null
|
|
51
214
|
}
|