thinkwork-cli 0.12.15 → 0.12.16
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/dist/{api-client-4VT3FWOH.js → api-client-JUBCQQDF.js} +1 -1
- package/dist/{chunk-H7AB42ES.js → chunk-STY44FQO.js} +13 -0
- package/dist/cli.js +814 -46
- package/dist/drizzle/0197_rcm_context_columns.sql +28 -0
- package/dist/terraform/modules/app/agentcore-pi/main.tf +15 -2
- package/package.json +2 -1
|
@@ -310,6 +310,18 @@ function resolveTerraformRoot(startDir = process.cwd()) {
|
|
|
310
310
|
function isTerraformRoot(dir) {
|
|
311
311
|
return existsSync3(path2.join(dir, "examples", "greenfield")) || existsSync3(path2.join(dir, "environments")) || existsSync3(path2.join(dir, "main.tf"));
|
|
312
312
|
}
|
|
313
|
+
function resolveTerraformRootForStage(stage, recordedDir, startDir = process.cwd()) {
|
|
314
|
+
const cwdRoot = resolveTerraformRoot(startDir);
|
|
315
|
+
try {
|
|
316
|
+
resolveTierDir(cwdRoot, stage, "app");
|
|
317
|
+
return cwdRoot;
|
|
318
|
+
} catch {
|
|
319
|
+
if (recordedDir && existsSync3(path2.join(recordedDir, "main.tf"))) {
|
|
320
|
+
return recordedDir;
|
|
321
|
+
}
|
|
322
|
+
return cwdRoot;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
313
325
|
function resolveTierDir(terraformDir, stage, tier) {
|
|
314
326
|
const envDir = path2.join(terraformDir, "environments", stage, tier);
|
|
315
327
|
if (existsSync3(envDir)) {
|
|
@@ -668,6 +680,7 @@ export {
|
|
|
668
680
|
ensureStateBackend,
|
|
669
681
|
parseLockError,
|
|
670
682
|
resolveTerraformRoot,
|
|
683
|
+
resolveTerraformRootForStage,
|
|
671
684
|
resolveTierDir,
|
|
672
685
|
isInitScaffoldedLayout,
|
|
673
686
|
ensureWorkspace,
|
package/dist/cli.js
CHANGED
|
@@ -26,13 +26,14 @@ import {
|
|
|
26
26
|
resolveApiConfig,
|
|
27
27
|
resolveTerraformDir,
|
|
28
28
|
resolveTerraformRoot,
|
|
29
|
+
resolveTerraformRootForStage,
|
|
29
30
|
resolveTierDir,
|
|
30
31
|
runTerraform,
|
|
31
32
|
runTerraformTee,
|
|
32
33
|
saveEnterpriseDeployment,
|
|
33
34
|
saveEnvironment,
|
|
34
35
|
terraformOutput
|
|
35
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-STY44FQO.js";
|
|
36
37
|
|
|
37
38
|
// src/cli.ts
|
|
38
39
|
import { Command } from "commander";
|
|
@@ -319,6 +320,7 @@ function registerPlanCommand(program2) {
|
|
|
319
320
|
|
|
320
321
|
// src/commands/deploy.ts
|
|
321
322
|
import { spawn as spawn2, spawnSync as spawnSync6 } from "child_process";
|
|
323
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
322
324
|
import {
|
|
323
325
|
existsSync as existsSync9,
|
|
324
326
|
mkdirSync as mkdirSync3,
|
|
@@ -402,6 +404,7 @@ function checkAwsIdentity() {
|
|
|
402
404
|
};
|
|
403
405
|
}
|
|
404
406
|
var DOCTOR_BEDROCK_PROBE_MODEL_ID = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
|
|
407
|
+
var DOCTOR_BEDROCK_FALLBACK_MODEL_ID = "us.anthropic.claude-sonnet-4-5-20250929-v1:0";
|
|
405
408
|
function evaluateBedrockProbe(error) {
|
|
406
409
|
if (error === null) {
|
|
407
410
|
return {
|
|
@@ -435,22 +438,34 @@ function evaluateBedrockProbe(error) {
|
|
|
435
438
|
function checkBedrockAccess() {
|
|
436
439
|
return {
|
|
437
440
|
name: "Bedrock model invocation",
|
|
438
|
-
run: () => {
|
|
441
|
+
run: async () => {
|
|
439
442
|
const region = process.env.AWS_REGION || "us-east-1";
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
443
|
+
let lastError = null;
|
|
444
|
+
const models = [
|
|
445
|
+
DOCTOR_BEDROCK_PROBE_MODEL_ID,
|
|
446
|
+
DOCTOR_BEDROCK_PROBE_MODEL_ID,
|
|
447
|
+
DOCTOR_BEDROCK_FALLBACK_MODEL_ID
|
|
448
|
+
];
|
|
449
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
450
|
+
try {
|
|
451
|
+
execSync2(
|
|
452
|
+
`aws bedrock-runtime converse --model-id ${models[attempt - 1]} --messages '[{"role":"user","content":[{"text":"Reply with OK"}]}]' --inference-config '{"maxTokens":1}' --output json --region ${region}`,
|
|
453
|
+
{
|
|
454
|
+
encoding: "utf-8",
|
|
455
|
+
timeout: 3e4,
|
|
456
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
457
|
+
}
|
|
458
|
+
);
|
|
459
|
+
return evaluateBedrockProbe(null);
|
|
460
|
+
} catch (err) {
|
|
461
|
+
lastError = err instanceof Error && "stderr" in err ? String(err.stderr ?? err.message) : String(err);
|
|
462
|
+
if (!lastError.includes("ThrottlingException") || attempt === 3) {
|
|
463
|
+
return evaluateBedrockProbe(lastError);
|
|
447
464
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
} catch (err) {
|
|
451
|
-
const stderr = err instanceof Error && "stderr" in err ? String(err.stderr ?? err.message) : String(err);
|
|
452
|
-
return evaluateBedrockProbe(stderr);
|
|
465
|
+
await new Promise((r) => setTimeout(r, attempt * 5e3));
|
|
466
|
+
}
|
|
453
467
|
}
|
|
468
|
+
return evaluateBedrockProbe(lastError);
|
|
454
469
|
}
|
|
455
470
|
};
|
|
456
471
|
}
|
|
@@ -618,6 +633,62 @@ function checkSesStatus(exec = awsExec) {
|
|
|
618
633
|
}
|
|
619
634
|
};
|
|
620
635
|
}
|
|
636
|
+
function parseEcrImageUri(uri) {
|
|
637
|
+
const match = uri.match(/^(\d+\.dkr\.ecr\.([a-z0-9-]+)\.amazonaws\.com)\//);
|
|
638
|
+
return match ? { registry: match[1], region: match[2] } : null;
|
|
639
|
+
}
|
|
640
|
+
function evaluateAgentcorePiImageProbe(input17) {
|
|
641
|
+
if (!input17.dockerAvailable) {
|
|
642
|
+
return {
|
|
643
|
+
pass: true,
|
|
644
|
+
detail: "docker unavailable \u2014 cannot probe the pinned image; the apply-time seed reports pull failures"
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
if (input17.reachable) {
|
|
648
|
+
return { pass: true, detail: `${input17.uri} reachable` };
|
|
649
|
+
}
|
|
650
|
+
const reason = (input17.error ?? "").trim().split("\n")[0].slice(0, 160);
|
|
651
|
+
return {
|
|
652
|
+
pass: false,
|
|
653
|
+
detail: `pinned AgentCore image ${input17.uri} is not pullable from this machine` + (reason ? ` (${reason})` : "") + ". The apply keeps an already-seeded ECR image or builds from a repo checkout; otherwise set agentcore_pi_source_image_uri in terraform.tfvars to a pullable image."
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function checkAgentcorePiSourceImage(uri) {
|
|
657
|
+
return {
|
|
658
|
+
name: "AgentCore image pullable",
|
|
659
|
+
blocking: false,
|
|
660
|
+
run: () => {
|
|
661
|
+
const docker = spawnSync("docker", ["--version"], { encoding: "utf8" });
|
|
662
|
+
if (docker.status !== 0) {
|
|
663
|
+
return evaluateAgentcorePiImageProbe({
|
|
664
|
+
uri,
|
|
665
|
+
dockerAvailable: false,
|
|
666
|
+
reachable: false
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
const ecr = parseEcrImageUri(uri);
|
|
670
|
+
if (ecr) {
|
|
671
|
+
spawnSync(
|
|
672
|
+
"bash",
|
|
673
|
+
[
|
|
674
|
+
"-c",
|
|
675
|
+
`aws ecr get-login-password --region ${ecr.region} | docker login --username AWS --password-stdin ${ecr.registry}`
|
|
676
|
+
],
|
|
677
|
+
{ encoding: "utf8" }
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
const probe = spawnSync("docker", ["manifest", "inspect", uri], {
|
|
681
|
+
encoding: "utf8"
|
|
682
|
+
});
|
|
683
|
+
return evaluateAgentcorePiImageProbe({
|
|
684
|
+
uri,
|
|
685
|
+
dockerAvailable: true,
|
|
686
|
+
reachable: probe.status === 0,
|
|
687
|
+
error: probe.stderr
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
}
|
|
621
692
|
function doctorChecks() {
|
|
622
693
|
return [
|
|
623
694
|
checkAwsCli(),
|
|
@@ -636,6 +707,9 @@ function preflightChecks(ctx) {
|
|
|
636
707
|
if (ctx.backend) checks.push(checkStateBackend(ctx.backend));
|
|
637
708
|
if (ctx.domain) checks.push(checkDomainDelegation(ctx.domain));
|
|
638
709
|
if (ctx.sesConfigured) checks.push(checkSesStatus());
|
|
710
|
+
if (ctx.agentcorePiSourceImage) {
|
|
711
|
+
checks.push(checkAgentcorePiSourceImage(ctx.agentcorePiSourceImage));
|
|
712
|
+
}
|
|
639
713
|
return checks;
|
|
640
714
|
}
|
|
641
715
|
async function runChecks(checks) {
|
|
@@ -1247,6 +1321,83 @@ COMMIT;`;
|
|
|
1247
1321
|
}
|
|
1248
1322
|
}
|
|
1249
1323
|
|
|
1324
|
+
// src/lib/owner-tenant.ts
|
|
1325
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
1326
|
+
|
|
1327
|
+
// ../../packages/database-pg/src/utils/reserved-slugs.ts
|
|
1328
|
+
var TENANT_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$/;
|
|
1329
|
+
var RESERVED_TENANT_SLUGS = [
|
|
1330
|
+
"admin",
|
|
1331
|
+
"agents",
|
|
1332
|
+
"api",
|
|
1333
|
+
"app",
|
|
1334
|
+
"assets",
|
|
1335
|
+
"canary",
|
|
1336
|
+
"cdn",
|
|
1337
|
+
"dev",
|
|
1338
|
+
"docs",
|
|
1339
|
+
"mail",
|
|
1340
|
+
"mobile",
|
|
1341
|
+
"prod",
|
|
1342
|
+
"staging",
|
|
1343
|
+
"test",
|
|
1344
|
+
"www"
|
|
1345
|
+
];
|
|
1346
|
+
var RESERVED_TENANT_SLUG_SET = new Set(RESERVED_TENANT_SLUGS);
|
|
1347
|
+
function isReservedTenantSlug(slug) {
|
|
1348
|
+
return RESERVED_TENANT_SLUG_SET.has(slug);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// src/lib/owner-tenant.ts
|
|
1352
|
+
function deriveOwnerTenantSlug(stage, random = () => randomBytes2(3).toString("hex")) {
|
|
1353
|
+
const sanitized = stage.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1354
|
+
if (TENANT_SLUG_PATTERN.test(sanitized) && !isReservedTenantSlug(sanitized)) {
|
|
1355
|
+
return sanitized;
|
|
1356
|
+
}
|
|
1357
|
+
return `workspace-${random()}`;
|
|
1358
|
+
}
|
|
1359
|
+
function deriveOwnerTenantName(slug) {
|
|
1360
|
+
return slug.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
1361
|
+
}
|
|
1362
|
+
function sqlQuote(value) {
|
|
1363
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
1364
|
+
}
|
|
1365
|
+
function buildEnsureOwnerTenantSql(input17) {
|
|
1366
|
+
const name = sqlQuote(input17.name);
|
|
1367
|
+
const slug = sqlQuote(input17.slug);
|
|
1368
|
+
const email = sqlQuote(input17.email.toLowerCase());
|
|
1369
|
+
return `WITH new_tenant AS (
|
|
1370
|
+
INSERT INTO tenants (name, slug, issue_prefix, issue_counter, pending_owner_email, first_admin_claim_required)
|
|
1371
|
+
SELECT ${name}, ${slug}, 'TW', 0, ${email}, true
|
|
1372
|
+
WHERE NOT EXISTS (SELECT 1 FROM tenants)
|
|
1373
|
+
RETURNING id
|
|
1374
|
+
)
|
|
1375
|
+
INSERT INTO tenant_settings (tenant_id)
|
|
1376
|
+
SELECT id FROM new_tenant
|
|
1377
|
+
ON CONFLICT DO NOTHING
|
|
1378
|
+
RETURNING tenant_id;`;
|
|
1379
|
+
}
|
|
1380
|
+
async function ensureOwnerTenant(options) {
|
|
1381
|
+
const slug = deriveOwnerTenantSlug(options.stage);
|
|
1382
|
+
const runner = await (options.connect ?? connectPsql)(options.connection);
|
|
1383
|
+
try {
|
|
1384
|
+
const result = await runner.query(
|
|
1385
|
+
buildEnsureOwnerTenantSql({
|
|
1386
|
+
name: deriveOwnerTenantName(slug),
|
|
1387
|
+
slug,
|
|
1388
|
+
email: options.email
|
|
1389
|
+
})
|
|
1390
|
+
);
|
|
1391
|
+
return {
|
|
1392
|
+
created: (result.rows ?? []).length > 0,
|
|
1393
|
+
slug,
|
|
1394
|
+
email: options.email.toLowerCase()
|
|
1395
|
+
};
|
|
1396
|
+
} finally {
|
|
1397
|
+
await runner.end();
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1250
1401
|
// src/commands/bootstrap.ts
|
|
1251
1402
|
import { spawn } from "child_process";
|
|
1252
1403
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -1320,7 +1471,10 @@ function registerBootstrapCommand(program2) {
|
|
|
1320
1471
|
}
|
|
1321
1472
|
const identity = getAwsIdentity();
|
|
1322
1473
|
printHeader("bootstrap", stage, identity);
|
|
1323
|
-
const terraformDir =
|
|
1474
|
+
const terraformDir = resolveTerraformRootForStage(
|
|
1475
|
+
stage,
|
|
1476
|
+
loadEnvironment(stage)?.terraformDir
|
|
1477
|
+
);
|
|
1324
1478
|
const cwd = resolveTierDir(terraformDir, stage, "app");
|
|
1325
1479
|
await ensureInit(cwd);
|
|
1326
1480
|
await ensureWorkspace(cwd, stage);
|
|
@@ -1486,6 +1640,28 @@ function buildVerifyChecks(ctx) {
|
|
|
1486
1640
|
break;
|
|
1487
1641
|
}
|
|
1488
1642
|
}
|
|
1643
|
+
if (url) {
|
|
1644
|
+
const cfg = httpProbe(`${url}/thinkwork-runtime-config.json`);
|
|
1645
|
+
if (cfg.status !== 200 || !cfg.body.includes("cognitoClientId")) {
|
|
1646
|
+
return {
|
|
1647
|
+
pass: false,
|
|
1648
|
+
detail: `${url} serves HTML but /thinkwork-runtime-config.json is ${cfg.status === 200 ? "incomplete" : "missing"} \u2014 the app cannot sign in. Rerun thinkwork deploy to publish it.`
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
const parsed = (() => {
|
|
1652
|
+
try {
|
|
1653
|
+
return JSON.parse(cfg.body);
|
|
1654
|
+
} catch {
|
|
1655
|
+
return {};
|
|
1656
|
+
}
|
|
1657
|
+
})();
|
|
1658
|
+
if (!parsed.viteEnv?.VITE_COGNITO_CLIENT_ID) {
|
|
1659
|
+
return {
|
|
1660
|
+
pass: false,
|
|
1661
|
+
detail: `${url} runtime config has no viteEnv.VITE_COGNITO_CLIENT_ID \u2014 sign-in would be unavailable.`
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1489
1665
|
if (!url && candidates.length > 0) {
|
|
1490
1666
|
url = `https://${candidates[0].Domain}`;
|
|
1491
1667
|
}
|
|
@@ -3173,7 +3349,7 @@ function git(args) {
|
|
|
3173
3349
|
|
|
3174
3350
|
// src/commands/enterprise/secrets.ts
|
|
3175
3351
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
3176
|
-
import { randomBytes as
|
|
3352
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
3177
3353
|
var GhCliEnterpriseSecretSetter = class {
|
|
3178
3354
|
async setEnvironmentSecret(repository, stage, name, value) {
|
|
3179
3355
|
execFileSync5(
|
|
@@ -3241,7 +3417,7 @@ async function setEnterpriseStageSecrets(repository, stageSecrets, setter) {
|
|
|
3241
3417
|
return results;
|
|
3242
3418
|
}
|
|
3243
3419
|
function generateUrlSafeSecret(bytes = 32) {
|
|
3244
|
-
return
|
|
3420
|
+
return randomBytes3(bytes).toString("base64url");
|
|
3245
3421
|
}
|
|
3246
3422
|
async function resolveSecretValue(stage, name, explicit, stdinIsTty, promptSecret, generateSecret2) {
|
|
3247
3423
|
if (explicit) return explicit;
|
|
@@ -4187,6 +4363,167 @@ async function ensureReleaseArtifacts(cwd, identity, stage, versionFlag) {
|
|
|
4187
4363
|
}
|
|
4188
4364
|
return { version, webAssetSource };
|
|
4189
4365
|
}
|
|
4366
|
+
function buildRuntimeConfig(values) {
|
|
4367
|
+
const cognitoDomain = values.authDomain.startsWith("https://") ? values.authDomain : values.authDomain ? `https://${values.authDomain}.auth.${values.region}.amazoncognito.com` : "";
|
|
4368
|
+
const api = values.apiEndpoint.replace(/\/+$/, "");
|
|
4369
|
+
return {
|
|
4370
|
+
stage: values.stage,
|
|
4371
|
+
region: values.region,
|
|
4372
|
+
accountId: values.accountId,
|
|
4373
|
+
releaseVersion: values.releaseVersion,
|
|
4374
|
+
releaseManifestUrl: null,
|
|
4375
|
+
releaseManifestSha256: null,
|
|
4376
|
+
deploymentId: `thinkwork-${values.stage}`,
|
|
4377
|
+
displayName: "ThinkWork",
|
|
4378
|
+
appUrl: values.appUrl,
|
|
4379
|
+
apiEndpoint: values.apiEndpoint,
|
|
4380
|
+
graphqlHttpUrl: api ? `${api}/graphql` : "",
|
|
4381
|
+
appsyncUrl: values.appsyncUrl,
|
|
4382
|
+
appsyncRealtimeUrl: values.appsyncRealtimeUrl,
|
|
4383
|
+
appsyncApiKey: values.appsyncApiKey,
|
|
4384
|
+
cognitoDomain,
|
|
4385
|
+
cognitoUserPoolId: values.userPoolId,
|
|
4386
|
+
cognitoClientId: values.adminClientId,
|
|
4387
|
+
controller: null,
|
|
4388
|
+
issuedAt: values.issuedAt,
|
|
4389
|
+
// The web app consumes ONLY this map (runtime-config.ts reads
|
|
4390
|
+
// raw.viteEnv) — the outer profile is for tooling. Omitting it kept the
|
|
4391
|
+
// sign-in screen dead even with the file published (HCI test).
|
|
4392
|
+
viteEnv: {
|
|
4393
|
+
VITE_API_URL: values.apiEndpoint,
|
|
4394
|
+
VITE_GRAPHQL_HTTP_URL: api ? `${api}/graphql` : "",
|
|
4395
|
+
VITE_GRAPHQL_URL: values.appsyncUrl,
|
|
4396
|
+
VITE_GRAPHQL_WS_URL: values.appsyncRealtimeUrl,
|
|
4397
|
+
VITE_GRAPHQL_API_KEY: values.appsyncApiKey,
|
|
4398
|
+
VITE_COGNITO_DOMAIN: cognitoDomain,
|
|
4399
|
+
VITE_COGNITO_USER_POOL_ID: values.userPoolId,
|
|
4400
|
+
VITE_COGNITO_CLIENT_ID: values.adminClientId,
|
|
4401
|
+
VITE_DEPLOYMENT_ID: `thinkwork-${values.stage}`,
|
|
4402
|
+
VITE_DEPLOYMENT_DISPLAY_NAME: "ThinkWork",
|
|
4403
|
+
VITE_DEPLOYMENT_PROFILE_ISSUED_AT: values.issuedAt,
|
|
4404
|
+
VITE_SPACES_URL: values.appUrl,
|
|
4405
|
+
VITE_STAGE: values.stage,
|
|
4406
|
+
VITE_AWS_REGION: values.region,
|
|
4407
|
+
VITE_AWS_ACCOUNT_ID: values.accountId,
|
|
4408
|
+
VITE_RELEASE_VERSION: values.releaseVersion ?? ""
|
|
4409
|
+
}
|
|
4410
|
+
};
|
|
4411
|
+
}
|
|
4412
|
+
async function publishRuntimeConfig(cwd, bucket, identity, stage, releaseVersion) {
|
|
4413
|
+
const output = async (key) => {
|
|
4414
|
+
try {
|
|
4415
|
+
return await terraformOutput(cwd, key);
|
|
4416
|
+
} catch {
|
|
4417
|
+
return "";
|
|
4418
|
+
}
|
|
4419
|
+
};
|
|
4420
|
+
const config = buildRuntimeConfig({
|
|
4421
|
+
stage,
|
|
4422
|
+
region: identity.region,
|
|
4423
|
+
accountId: identity.account,
|
|
4424
|
+
releaseVersion,
|
|
4425
|
+
apiEndpoint: await output("api_endpoint"),
|
|
4426
|
+
appUrl: await output("app_url"),
|
|
4427
|
+
authDomain: await output("auth_domain"),
|
|
4428
|
+
appsyncUrl: await output("appsync_api_url"),
|
|
4429
|
+
appsyncRealtimeUrl: await output("appsync_realtime_url"),
|
|
4430
|
+
appsyncApiKey: await output("appsync_api_key"),
|
|
4431
|
+
userPoolId: await output("user_pool_id"),
|
|
4432
|
+
adminClientId: await output("admin_client_id") || await output("admin_client_id_out"),
|
|
4433
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4434
|
+
});
|
|
4435
|
+
const tempDir = mkdtempSync5(pathJoinTmp("thinkwork-runtime-config-"));
|
|
4436
|
+
const file = join9(tempDir, "thinkwork-runtime-config.json");
|
|
4437
|
+
writeFileSync7(file, JSON.stringify(config, null, 2) + "\n");
|
|
4438
|
+
const put = spawnSync6(
|
|
4439
|
+
"aws",
|
|
4440
|
+
[
|
|
4441
|
+
"s3",
|
|
4442
|
+
"cp",
|
|
4443
|
+
file,
|
|
4444
|
+
`s3://${bucket}/thinkwork-runtime-config.json`,
|
|
4445
|
+
"--content-type",
|
|
4446
|
+
"application/json",
|
|
4447
|
+
"--cache-control",
|
|
4448
|
+
"no-store",
|
|
4449
|
+
"--region",
|
|
4450
|
+
identity.region
|
|
4451
|
+
],
|
|
4452
|
+
{ encoding: "utf8" }
|
|
4453
|
+
);
|
|
4454
|
+
if (put.status !== 0) {
|
|
4455
|
+
throw new Error(
|
|
4456
|
+
`Could not publish runtime config: ${(put.stderr ?? "").trim().slice(0, 200)}`
|
|
4457
|
+
);
|
|
4458
|
+
}
|
|
4459
|
+
printSuccess(`Runtime config published to s3://${bucket}/thinkwork-runtime-config.json`);
|
|
4460
|
+
const distributionId = await output("app_distribution_id");
|
|
4461
|
+
if (distributionId) {
|
|
4462
|
+
spawnSync6(
|
|
4463
|
+
"aws",
|
|
4464
|
+
[
|
|
4465
|
+
"cloudfront",
|
|
4466
|
+
"create-invalidation",
|
|
4467
|
+
"--distribution-id",
|
|
4468
|
+
distributionId,
|
|
4469
|
+
"--paths",
|
|
4470
|
+
"/thinkwork-runtime-config.json",
|
|
4471
|
+
"/index.html"
|
|
4472
|
+
],
|
|
4473
|
+
{ encoding: "utf8" }
|
|
4474
|
+
);
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
async function ensureOwnerUser(cwd, region) {
|
|
4478
|
+
const email = (readTfvarsSignalsRaw(cwd).platform_operator_emails ?? "").split(",")[0]?.trim();
|
|
4479
|
+
if (!email) return null;
|
|
4480
|
+
const userPoolId = await terraformOutput(cwd, "user_pool_id").catch(() => "");
|
|
4481
|
+
if (!userPoolId) return null;
|
|
4482
|
+
const exists = spawnSync6(
|
|
4483
|
+
"aws",
|
|
4484
|
+
[
|
|
4485
|
+
"cognito-idp",
|
|
4486
|
+
"admin-get-user",
|
|
4487
|
+
"--user-pool-id",
|
|
4488
|
+
userPoolId,
|
|
4489
|
+
"--username",
|
|
4490
|
+
email,
|
|
4491
|
+
"--region",
|
|
4492
|
+
region
|
|
4493
|
+
],
|
|
4494
|
+
{ encoding: "utf8" }
|
|
4495
|
+
);
|
|
4496
|
+
if (exists.status === 0) return { email, tempPassword: null };
|
|
4497
|
+
const tempPassword = `Tw1!${randomBytes4(12).toString("base64url")}`;
|
|
4498
|
+
const created = spawnSync6(
|
|
4499
|
+
"aws",
|
|
4500
|
+
[
|
|
4501
|
+
"cognito-idp",
|
|
4502
|
+
"admin-create-user",
|
|
4503
|
+
"--user-pool-id",
|
|
4504
|
+
userPoolId,
|
|
4505
|
+
"--username",
|
|
4506
|
+
email,
|
|
4507
|
+
"--user-attributes",
|
|
4508
|
+
`Name=email,Value=${email}`,
|
|
4509
|
+
"Name=email_verified,Value=true",
|
|
4510
|
+
"--temporary-password",
|
|
4511
|
+
tempPassword,
|
|
4512
|
+
"--message-action",
|
|
4513
|
+
"SUPPRESS",
|
|
4514
|
+
"--region",
|
|
4515
|
+
region
|
|
4516
|
+
],
|
|
4517
|
+
{ encoding: "utf8" }
|
|
4518
|
+
);
|
|
4519
|
+
if (created.status !== 0) {
|
|
4520
|
+
printWarning(
|
|
4521
|
+
`Could not create the owner user ${email}: ${(created.stderr ?? "").trim().slice(0, 200)}`
|
|
4522
|
+
);
|
|
4523
|
+
return null;
|
|
4524
|
+
}
|
|
4525
|
+
return { email, tempPassword };
|
|
4526
|
+
}
|
|
4190
4527
|
async function publishWebAssets(cwd, webAssetSource) {
|
|
4191
4528
|
const bucket = await terraformOutput(cwd, "app_bucket_name");
|
|
4192
4529
|
if (!bucket) {
|
|
@@ -4247,6 +4584,20 @@ async function applySchemaMigrations(cwd, identity, stage) {
|
|
|
4247
4584
|
);
|
|
4248
4585
|
return;
|
|
4249
4586
|
}
|
|
4587
|
+
const connection = await resolveStageDbConnection(cwd, identity, stage);
|
|
4588
|
+
console.log("\n Applying database schema (full migration history)...");
|
|
4589
|
+
const summary = await applyMigrations({
|
|
4590
|
+
drizzleDir,
|
|
4591
|
+
stage,
|
|
4592
|
+
region: identity.region,
|
|
4593
|
+
connection,
|
|
4594
|
+
log: (line) => console.log(` ${line}`)
|
|
4595
|
+
});
|
|
4596
|
+
console.log(
|
|
4597
|
+
` Schema: ${summary.applied.length} migration(s) applied, ${summary.skipped} already present` + (summary.skippedFiles.length > 0 ? `, ${summary.skippedFiles.length} operator-only file(s) skipped` : "") + "."
|
|
4598
|
+
);
|
|
4599
|
+
}
|
|
4600
|
+
async function resolveStageDbConnection(cwd, identity, stage) {
|
|
4250
4601
|
const endpoint = await terraformOutput(cwd, "db_cluster_endpoint");
|
|
4251
4602
|
if (!endpoint) {
|
|
4252
4603
|
throw new Error(
|
|
@@ -4280,23 +4631,13 @@ async function applySchemaMigrations(cwd, identity, stage) {
|
|
|
4280
4631
|
`Secret thinkwork-${stage}-db-credentials is missing username/password.`
|
|
4281
4632
|
);
|
|
4282
4633
|
}
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
port: 5432,
|
|
4291
|
-
user: parsed.username,
|
|
4292
|
-
password: parsed.password,
|
|
4293
|
-
database: "thinkwork"
|
|
4294
|
-
},
|
|
4295
|
-
log: (line) => console.log(` ${line}`)
|
|
4296
|
-
});
|
|
4297
|
-
console.log(
|
|
4298
|
-
` Schema: ${summary.applied.length} migration(s) applied, ${summary.skipped} already present` + (summary.skippedFiles.length > 0 ? `, ${summary.skippedFiles.length} operator-only file(s) skipped` : "") + "."
|
|
4299
|
-
);
|
|
4634
|
+
return {
|
|
4635
|
+
host: endpoint,
|
|
4636
|
+
port: 5432,
|
|
4637
|
+
user: parsed.username,
|
|
4638
|
+
password: parsed.password,
|
|
4639
|
+
database: "thinkwork"
|
|
4640
|
+
};
|
|
4300
4641
|
}
|
|
4301
4642
|
function resolveBedrockLoggingPin(assignments, logGroupExists) {
|
|
4302
4643
|
if (assignments.manage_bedrock_invocation_logging !== void 0) return null;
|
|
@@ -4396,7 +4737,10 @@ async function runLocalTerraformDeploy(opts) {
|
|
|
4396
4737
|
);
|
|
4397
4738
|
}
|
|
4398
4739
|
}
|
|
4399
|
-
const terraformDir =
|
|
4740
|
+
const terraformDir = resolveTerraformRootForStage(
|
|
4741
|
+
stage,
|
|
4742
|
+
loadEnvironment(stage)?.terraformDir
|
|
4743
|
+
);
|
|
4400
4744
|
const tiers = expandComponent(opts.component);
|
|
4401
4745
|
const cwd0 = resolveTierDir(terraformDir, stage, tiers[0]);
|
|
4402
4746
|
const scaffolded = isInitScaffoldedLayout(cwd0);
|
|
@@ -4408,7 +4752,8 @@ async function runLocalTerraformDeploy(opts) {
|
|
|
4408
4752
|
const ctx = {
|
|
4409
4753
|
backend: caller && scaffolded ? backendTarget(caller.account, caller.region, stage) : void 0,
|
|
4410
4754
|
domain: signals.domain,
|
|
4411
|
-
sesConfigured: signals.sesConfigured
|
|
4755
|
+
sesConfigured: signals.sesConfigured,
|
|
4756
|
+
agentcorePiSourceImage: readTfvarsSignalsRaw(preflightCwd).agentcore_pi_source_image_uri || void 0
|
|
4412
4757
|
};
|
|
4413
4758
|
console.log("\n Preflight checks:");
|
|
4414
4759
|
const summary = await runChecks(preflightChecks(ctx));
|
|
@@ -4432,6 +4777,7 @@ async function runLocalTerraformDeploy(opts) {
|
|
|
4432
4777
|
printWarning("Preflight skipped (--skip-preflight).");
|
|
4433
4778
|
}
|
|
4434
4779
|
let webAssetSource = null;
|
|
4780
|
+
let releaseVersionPin = null;
|
|
4435
4781
|
if (scaffolded && caller) {
|
|
4436
4782
|
ensureBedrockLoggingPin(cwd0, caller.region);
|
|
4437
4783
|
const release = await ensureReleaseArtifacts(
|
|
@@ -4441,6 +4787,7 @@ async function runLocalTerraformDeploy(opts) {
|
|
|
4441
4787
|
opts.releaseVersion
|
|
4442
4788
|
);
|
|
4443
4789
|
webAssetSource = release.webAssetSource;
|
|
4790
|
+
releaseVersionPin = release.version;
|
|
4444
4791
|
}
|
|
4445
4792
|
for (let i = 0; i < tiers.length; i++) {
|
|
4446
4793
|
const tier = tiers[i];
|
|
@@ -4486,9 +4833,45 @@ async function runLocalTerraformDeploy(opts) {
|
|
|
4486
4833
|
if (scaffolded && caller) {
|
|
4487
4834
|
await applySchemaMigrations(cwd0, caller, stage);
|
|
4488
4835
|
}
|
|
4836
|
+
let ownerUser = null;
|
|
4837
|
+
if (scaffolded && caller) {
|
|
4838
|
+
const operatorEmail = (readTfvarsSignalsRaw(cwd0).platform_operator_emails ?? "").split(",")[0]?.trim();
|
|
4839
|
+
if (operatorEmail) {
|
|
4840
|
+
try {
|
|
4841
|
+
const connection = await resolveStageDbConnection(cwd0, caller, stage);
|
|
4842
|
+
const ownerTenant = await ensureOwnerTenant({
|
|
4843
|
+
stage,
|
|
4844
|
+
email: operatorEmail,
|
|
4845
|
+
connection
|
|
4846
|
+
});
|
|
4847
|
+
if (ownerTenant.created) {
|
|
4848
|
+
printSuccess(
|
|
4849
|
+
`Owner tenant "${ownerTenant.slug}" pre-provisioned \u2014 first sign-in by ${ownerTenant.email} claims it.`
|
|
4850
|
+
);
|
|
4851
|
+
}
|
|
4852
|
+
} catch (err) {
|
|
4853
|
+
printWarning(
|
|
4854
|
+
`Could not pre-provision the owner tenant: ${err instanceof Error ? err.message : String(err)}. First sign-in will show "No tenant assigned" until this is resolved \u2014 rerun the deploy.`
|
|
4855
|
+
);
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
ownerUser = await ensureOwnerUser(cwd0, caller.region);
|
|
4859
|
+
}
|
|
4489
4860
|
if (scaffolded && webAssetSource) {
|
|
4490
4861
|
await publishWebAssets(cwd0, webAssetSource);
|
|
4491
4862
|
}
|
|
4863
|
+
if (scaffolded && caller) {
|
|
4864
|
+
const bucket = await terraformOutput(cwd0, "app_bucket_name");
|
|
4865
|
+
if (bucket) {
|
|
4866
|
+
await publishRuntimeConfig(
|
|
4867
|
+
cwd0,
|
|
4868
|
+
bucket,
|
|
4869
|
+
caller,
|
|
4870
|
+
stage,
|
|
4871
|
+
releaseVersionPin ?? null
|
|
4872
|
+
);
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4492
4875
|
if (scaffolded && caller) {
|
|
4493
4876
|
console.log("\n Seeding workspace defaults...");
|
|
4494
4877
|
await runWorkspaceBootstrap(cwd0, stage, caller.region);
|
|
@@ -4506,6 +4889,7 @@ async function runLocalTerraformDeploy(opts) {
|
|
|
4506
4889
|
)
|
|
4507
4890
|
});
|
|
4508
4891
|
if (!verification.passed) {
|
|
4892
|
+
printOwnerCredentials(ownerUser);
|
|
4509
4893
|
printError(
|
|
4510
4894
|
`Deploy applied but the stack failed verification (${verification.failures.length} probe(s)). Fix the items above and rerun \`thinkwork deploy -s ${stage}\` \u2014 reruns converge.`
|
|
4511
4895
|
);
|
|
@@ -4515,6 +4899,19 @@ async function runLocalTerraformDeploy(opts) {
|
|
|
4515
4899
|
printSuccess("Deploy complete");
|
|
4516
4900
|
await runPostDeployProbe(stage);
|
|
4517
4901
|
printSummary("deploy", stage, tiers, startTime);
|
|
4902
|
+
printOwnerCredentials(ownerUser);
|
|
4903
|
+
}
|
|
4904
|
+
function printOwnerCredentials(ownerUser) {
|
|
4905
|
+
if (ownerUser?.tempPassword) {
|
|
4906
|
+
console.log("");
|
|
4907
|
+
printSuccess(`Owner user created: ${ownerUser.email}`);
|
|
4908
|
+
console.log(
|
|
4909
|
+
` Temporary password (shown ONCE \u2014 Cognito requires a change at first sign-in):`
|
|
4910
|
+
);
|
|
4911
|
+
console.log(` ${ownerUser.tempPassword}`);
|
|
4912
|
+
} else if (ownerUser) {
|
|
4913
|
+
console.log(` Owner user ${ownerUser.email} already exists.`);
|
|
4914
|
+
}
|
|
4518
4915
|
}
|
|
4519
4916
|
async function offerStaleLockRecovery(cwd, stage, tier, lock) {
|
|
4520
4917
|
console.log("");
|
|
@@ -5066,7 +5463,10 @@ async function runLocalTerraformDestroy(opts) {
|
|
|
5066
5463
|
process.exit(0);
|
|
5067
5464
|
}
|
|
5068
5465
|
}
|
|
5069
|
-
const terraformDir =
|
|
5466
|
+
const terraformDir = resolveTerraformRootForStage(
|
|
5467
|
+
stage,
|
|
5468
|
+
localEnv?.terraformDir
|
|
5469
|
+
);
|
|
5070
5470
|
const tiers = expandComponent(opts.component).reverse();
|
|
5071
5471
|
const preRegion = identity && identity.region !== "unknown" ? identity.region : "us-east-1";
|
|
5072
5472
|
const cluster = disableClusterDeletionProtection(stage, preRegion);
|
|
@@ -6046,7 +6446,7 @@ function discoverCognitoConfig(stage, region) {
|
|
|
6046
6446
|
import {
|
|
6047
6447
|
createServer
|
|
6048
6448
|
} from "http";
|
|
6049
|
-
import { randomBytes as
|
|
6449
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
6050
6450
|
import { spawn as spawn3 } from "child_process";
|
|
6051
6451
|
import chalk6 from "chalk";
|
|
6052
6452
|
var CLI_LOOPBACK_PORT = 42010;
|
|
@@ -6056,7 +6456,7 @@ async function loginWithCognito(opts) {
|
|
|
6056
6456
|
const port = opts.port ?? CLI_LOOPBACK_PORT;
|
|
6057
6457
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
6058
6458
|
const redirectUri = `http://127.0.0.1:${port}${CALLBACK_PATH}`;
|
|
6059
|
-
const state =
|
|
6459
|
+
const state = randomBytes5(16).toString("hex");
|
|
6060
6460
|
const authorizeUrl = buildAuthorizeUrl(opts.cognito, redirectUri, state);
|
|
6061
6461
|
const code = await waitForCallbackCode({
|
|
6062
6462
|
port,
|
|
@@ -7044,8 +7444,10 @@ function findBundledTerraform() {
|
|
|
7044
7444
|
function parseTfvarsAssignments(content) {
|
|
7045
7445
|
const values = {};
|
|
7046
7446
|
for (const line of content.split("\n")) {
|
|
7047
|
-
const match = line.match(
|
|
7048
|
-
|
|
7447
|
+
const match = line.match(
|
|
7448
|
+
/^\s*([a-zA-Z0-9_]+)\s*=\s*(?:"([^"]*)"|(true|false|[0-9]+))\s*$/
|
|
7449
|
+
);
|
|
7450
|
+
if (match) values[match[1]] = match[2] ?? match[3];
|
|
7049
7451
|
}
|
|
7050
7452
|
const hasContent = content.split("\n").some((l) => l.trim() && !l.trim().startsWith("#"));
|
|
7051
7453
|
if (hasContent && !values.stage) {
|
|
@@ -7127,6 +7529,25 @@ function buildTfvars(config) {
|
|
|
7127
7529
|
`customer_domain_delegated = ${config.customer_domain_delegated === "true"}`
|
|
7128
7530
|
);
|
|
7129
7531
|
}
|
|
7532
|
+
if (config.stage === "prod" || config.stage === "production") {
|
|
7533
|
+
lines.push(``);
|
|
7534
|
+
lines.push(
|
|
7535
|
+
`# \u2500\u2500 Compliance (prod requirement) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`
|
|
7536
|
+
);
|
|
7537
|
+
lines.push(
|
|
7538
|
+
`# Object Lock COMPLIANCE is REQUIRED for prod-named stages (terraform`
|
|
7539
|
+
);
|
|
7540
|
+
lines.push(
|
|
7541
|
+
`# precondition). Retention is irreversible until it expires \u2014 even AWS`
|
|
7542
|
+
);
|
|
7543
|
+
lines.push(
|
|
7544
|
+
`# root cannot delete anchors early. Raise retention for audit posture.`
|
|
7545
|
+
);
|
|
7546
|
+
lines.push(`compliance_anchor_object_lock_mode = "COMPLIANCE"`);
|
|
7547
|
+
lines.push(
|
|
7548
|
+
`compliance_anchor_retention_days = ${config.compliance_anchor_retention_days || "30"}`
|
|
7549
|
+
);
|
|
7550
|
+
}
|
|
7130
7551
|
if (config.platform_operator_emails) {
|
|
7131
7552
|
lines.push(``);
|
|
7132
7553
|
lines.push(
|
|
@@ -7278,7 +7699,7 @@ function registerInitCommand(program2) {
|
|
|
7278
7699
|
config.admin_url = "http://localhost:5174";
|
|
7279
7700
|
config.mobile_scheme = "thinkwork";
|
|
7280
7701
|
config.customer_domain = existing?.customer_domain ?? "";
|
|
7281
|
-
config.customer_domain_delegated = "false";
|
|
7702
|
+
config.customer_domain_delegated = existing?.customer_domain_delegated ?? "false";
|
|
7282
7703
|
config.platform_operator_emails = existing?.platform_operator_emails ?? "";
|
|
7283
7704
|
config.ses_parent_domain = existing?.ses_parent_domain ?? "";
|
|
7284
7705
|
} else {
|
|
@@ -7312,7 +7733,7 @@ function registerInitCommand(program2) {
|
|
|
7312
7733
|
"Domain (e.g. thinkwork.acme.com; empty to skip)",
|
|
7313
7734
|
existing?.customer_domain ?? ""
|
|
7314
7735
|
);
|
|
7315
|
-
config.customer_domain_delegated = "false";
|
|
7736
|
+
config.customer_domain_delegated = existing?.customer_domain_delegated ?? "false";
|
|
7316
7737
|
config.platform_operator_emails = await ask2(
|
|
7317
7738
|
"Operator email(s), comma-separated",
|
|
7318
7739
|
existing?.platform_operator_emails ?? ""
|
|
@@ -7420,10 +7841,24 @@ function registerInitCommand(program2) {
|
|
|
7420
7841
|
if (existsSync13(schemaPath) && !existsSync13(join12(tfDir, "schema.graphql"))) {
|
|
7421
7842
|
cpSync(schemaPath, join12(tfDir, "schema.graphql"));
|
|
7422
7843
|
}
|
|
7423
|
-
|
|
7844
|
+
let tfvars = buildTfvars(config);
|
|
7845
|
+
const generatedKeys = new Set(
|
|
7846
|
+
[...tfvars.matchAll(/^\s*([a-zA-Z0-9_]+)\s*=/gm)].map((m) => m[1])
|
|
7847
|
+
);
|
|
7848
|
+
const preserved = Object.entries(existing ?? {}).filter(
|
|
7849
|
+
([key]) => !generatedKeys.has(key)
|
|
7850
|
+
);
|
|
7851
|
+
if (preserved.length > 0) {
|
|
7852
|
+
tfvars += `
|
|
7853
|
+
# \u2500\u2500 Preserved from previous configuration (init rerun) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7854
|
+
` + preserved.map(
|
|
7855
|
+
([key, value]) => /^(true|false|[0-9]+)$/.test(value) ? `${key} = ${value}` : `${key} = "${value}"`
|
|
7856
|
+
).join("\n") + `
|
|
7857
|
+
`;
|
|
7858
|
+
}
|
|
7424
7859
|
writeFileSync9(tfvarsPath, tfvars);
|
|
7425
7860
|
const mainTfPath = join12(tfDir, "main.tf");
|
|
7426
|
-
|
|
7861
|
+
{
|
|
7427
7862
|
writeFileSync9(
|
|
7428
7863
|
mainTfPath,
|
|
7429
7864
|
`################################################################################
|
|
@@ -7779,6 +8214,20 @@ variable "agentcore_pi_source_image_uri" {
|
|
|
7779
8214
|
default = ""
|
|
7780
8215
|
}
|
|
7781
8216
|
|
|
8217
|
+
# Object Lock posture for the compliance anchor bucket. Stages named
|
|
8218
|
+
# prod/production REQUIRE "COMPLIANCE" (terraform precondition) \u2014 set
|
|
8219
|
+
# automatically by init for those stage names. COMPLIANCE retention is
|
|
8220
|
+
# irreversible until it expires (even AWS root cannot delete early).
|
|
8221
|
+
variable "compliance_anchor_object_lock_mode" {
|
|
8222
|
+
type = string
|
|
8223
|
+
default = "GOVERNANCE"
|
|
8224
|
+
}
|
|
8225
|
+
|
|
8226
|
+
variable "compliance_anchor_retention_days" {
|
|
8227
|
+
type = number
|
|
8228
|
+
default = 365
|
|
8229
|
+
}
|
|
8230
|
+
|
|
7782
8231
|
# Pinned by \`thinkwork deploy\`: true only when this stage is the first
|
|
7783
8232
|
# ThinkWork stack in the account+region (the Bedrock invocation-logging
|
|
7784
8233
|
# resources are account-scoped singletons).
|
|
@@ -7814,6 +8263,9 @@ module "thinkwork" {
|
|
|
7814
8263
|
skill_trust_runner_enabled = false
|
|
7815
8264
|
manage_bedrock_invocation_logging = var.manage_bedrock_invocation_logging
|
|
7816
8265
|
|
|
8266
|
+
compliance_anchor_object_lock_mode = var.compliance_anchor_object_lock_mode
|
|
8267
|
+
compliance_anchor_retention_days = var.compliance_anchor_retention_days
|
|
8268
|
+
|
|
7817
8269
|
db_password = var.db_password
|
|
7818
8270
|
database_engine = var.database_engine
|
|
7819
8271
|
enable_hindsight = var.enable_hindsight
|
|
@@ -7870,6 +8322,38 @@ output "api_endpoint" {
|
|
|
7870
8322
|
value = module.thinkwork.api_endpoint
|
|
7871
8323
|
}
|
|
7872
8324
|
|
|
8325
|
+
# Outputs consumed by \`thinkwork deploy\`'s web runtime-config generation \u2014
|
|
8326
|
+
# the web app fetches /thinkwork-runtime-config.json at boot; without these
|
|
8327
|
+
# the published bundle renders "Sign-in options are unavailable".
|
|
8328
|
+
output "app_url" {
|
|
8329
|
+
value = module.thinkwork.app_url
|
|
8330
|
+
}
|
|
8331
|
+
|
|
8332
|
+
output "auth_domain" {
|
|
8333
|
+
value = module.thinkwork.auth_domain
|
|
8334
|
+
}
|
|
8335
|
+
|
|
8336
|
+
output "appsync_api_url" {
|
|
8337
|
+
value = module.thinkwork.appsync_api_url
|
|
8338
|
+
}
|
|
8339
|
+
|
|
8340
|
+
output "appsync_realtime_url" {
|
|
8341
|
+
value = module.thinkwork.appsync_realtime_url
|
|
8342
|
+
}
|
|
8343
|
+
|
|
8344
|
+
output "appsync_api_key" {
|
|
8345
|
+
value = module.thinkwork.appsync_api_key
|
|
8346
|
+
sensitive = true
|
|
8347
|
+
}
|
|
8348
|
+
|
|
8349
|
+
output "admin_client_id_out" {
|
|
8350
|
+
value = module.thinkwork.admin_client_id
|
|
8351
|
+
}
|
|
8352
|
+
|
|
8353
|
+
output "app_distribution_id" {
|
|
8354
|
+
value = module.thinkwork.app_distribution_id
|
|
8355
|
+
}
|
|
8356
|
+
|
|
7873
8357
|
output "app_bucket_name" {
|
|
7874
8358
|
value = module.thinkwork.app_bucket_name
|
|
7875
8359
|
}
|
|
@@ -10609,6 +11093,182 @@ var CliDeleteBudgetPolicyDocument = {
|
|
|
10609
11093
|
}
|
|
10610
11094
|
]
|
|
10611
11095
|
};
|
|
11096
|
+
var CliCapabilityInspectorDocument = {
|
|
11097
|
+
kind: "Document",
|
|
11098
|
+
definitions: [
|
|
11099
|
+
{
|
|
11100
|
+
kind: "OperationDefinition",
|
|
11101
|
+
operation: "query",
|
|
11102
|
+
name: { kind: "Name", value: "CliCapabilityInspector" },
|
|
11103
|
+
variableDefinitions: [
|
|
11104
|
+
{
|
|
11105
|
+
kind: "VariableDefinition",
|
|
11106
|
+
variable: {
|
|
11107
|
+
kind: "Variable",
|
|
11108
|
+
name: { kind: "Name", value: "tenantId" }
|
|
11109
|
+
},
|
|
11110
|
+
type: {
|
|
11111
|
+
kind: "NonNullType",
|
|
11112
|
+
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } }
|
|
11113
|
+
}
|
|
11114
|
+
},
|
|
11115
|
+
{
|
|
11116
|
+
kind: "VariableDefinition",
|
|
11117
|
+
variable: {
|
|
11118
|
+
kind: "Variable",
|
|
11119
|
+
name: { kind: "Name", value: "agentId" }
|
|
11120
|
+
},
|
|
11121
|
+
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } }
|
|
11122
|
+
},
|
|
11123
|
+
{
|
|
11124
|
+
kind: "VariableDefinition",
|
|
11125
|
+
variable: {
|
|
11126
|
+
kind: "Variable",
|
|
11127
|
+
name: { kind: "Name", value: "spaceId" }
|
|
11128
|
+
},
|
|
11129
|
+
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } }
|
|
11130
|
+
},
|
|
11131
|
+
{
|
|
11132
|
+
kind: "VariableDefinition",
|
|
11133
|
+
variable: {
|
|
11134
|
+
kind: "Variable",
|
|
11135
|
+
name: { kind: "Name", value: "agentProfileId" }
|
|
11136
|
+
},
|
|
11137
|
+
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } }
|
|
11138
|
+
},
|
|
11139
|
+
{
|
|
11140
|
+
kind: "VariableDefinition",
|
|
11141
|
+
variable: {
|
|
11142
|
+
kind: "Variable",
|
|
11143
|
+
name: { kind: "Name", value: "perspectiveUserId" }
|
|
11144
|
+
},
|
|
11145
|
+
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } }
|
|
11146
|
+
}
|
|
11147
|
+
],
|
|
11148
|
+
selectionSet: {
|
|
11149
|
+
kind: "SelectionSet",
|
|
11150
|
+
selections: [
|
|
11151
|
+
{
|
|
11152
|
+
kind: "Field",
|
|
11153
|
+
name: { kind: "Name", value: "capabilityInspector" },
|
|
11154
|
+
arguments: [
|
|
11155
|
+
{
|
|
11156
|
+
kind: "Argument",
|
|
11157
|
+
name: { kind: "Name", value: "tenantId" },
|
|
11158
|
+
value: {
|
|
11159
|
+
kind: "Variable",
|
|
11160
|
+
name: { kind: "Name", value: "tenantId" }
|
|
11161
|
+
}
|
|
11162
|
+
},
|
|
11163
|
+
{
|
|
11164
|
+
kind: "Argument",
|
|
11165
|
+
name: { kind: "Name", value: "agentId" },
|
|
11166
|
+
value: {
|
|
11167
|
+
kind: "Variable",
|
|
11168
|
+
name: { kind: "Name", value: "agentId" }
|
|
11169
|
+
}
|
|
11170
|
+
},
|
|
11171
|
+
{
|
|
11172
|
+
kind: "Argument",
|
|
11173
|
+
name: { kind: "Name", value: "spaceId" },
|
|
11174
|
+
value: {
|
|
11175
|
+
kind: "Variable",
|
|
11176
|
+
name: { kind: "Name", value: "spaceId" }
|
|
11177
|
+
}
|
|
11178
|
+
},
|
|
11179
|
+
{
|
|
11180
|
+
kind: "Argument",
|
|
11181
|
+
name: { kind: "Name", value: "agentProfileId" },
|
|
11182
|
+
value: {
|
|
11183
|
+
kind: "Variable",
|
|
11184
|
+
name: { kind: "Name", value: "agentProfileId" }
|
|
11185
|
+
}
|
|
11186
|
+
},
|
|
11187
|
+
{
|
|
11188
|
+
kind: "Argument",
|
|
11189
|
+
name: { kind: "Name", value: "perspectiveUserId" },
|
|
11190
|
+
value: {
|
|
11191
|
+
kind: "Variable",
|
|
11192
|
+
name: { kind: "Name", value: "perspectiveUserId" }
|
|
11193
|
+
}
|
|
11194
|
+
}
|
|
11195
|
+
],
|
|
11196
|
+
selectionSet: {
|
|
11197
|
+
kind: "SelectionSet",
|
|
11198
|
+
selections: [
|
|
11199
|
+
{ kind: "Field", name: { kind: "Name", value: "state" } },
|
|
11200
|
+
{ kind: "Field", name: { kind: "Name", value: "stateDetail" } },
|
|
11201
|
+
{ kind: "Field", name: { kind: "Name", value: "agentId" } },
|
|
11202
|
+
{
|
|
11203
|
+
kind: "Field",
|
|
11204
|
+
name: { kind: "Name", value: "noUserBaseline" }
|
|
11205
|
+
},
|
|
11206
|
+
{
|
|
11207
|
+
kind: "Field",
|
|
11208
|
+
name: { kind: "Name", value: "predicted" },
|
|
11209
|
+
selectionSet: {
|
|
11210
|
+
kind: "SelectionSet",
|
|
11211
|
+
selections: [
|
|
11212
|
+
{
|
|
11213
|
+
kind: "Field",
|
|
11214
|
+
name: { kind: "Name", value: "computedAt" }
|
|
11215
|
+
},
|
|
11216
|
+
{
|
|
11217
|
+
kind: "Field",
|
|
11218
|
+
name: { kind: "Name", value: "configFingerprint" }
|
|
11219
|
+
},
|
|
11220
|
+
{
|
|
11221
|
+
kind: "Field",
|
|
11222
|
+
name: { kind: "Name", value: "items" },
|
|
11223
|
+
selectionSet: {
|
|
11224
|
+
kind: "SelectionSet",
|
|
11225
|
+
selections: [
|
|
11226
|
+
{
|
|
11227
|
+
kind: "Field",
|
|
11228
|
+
name: { kind: "Name", value: "capabilityClass" }
|
|
11229
|
+
},
|
|
11230
|
+
{
|
|
11231
|
+
kind: "Field",
|
|
11232
|
+
name: { kind: "Name", value: "capabilityId" }
|
|
11233
|
+
},
|
|
11234
|
+
{
|
|
11235
|
+
kind: "Field",
|
|
11236
|
+
name: { kind: "Name", value: "displayName" }
|
|
11237
|
+
},
|
|
11238
|
+
{
|
|
11239
|
+
kind: "Field",
|
|
11240
|
+
name: { kind: "Name", value: "active" }
|
|
11241
|
+
},
|
|
11242
|
+
{
|
|
11243
|
+
kind: "Field",
|
|
11244
|
+
name: { kind: "Name", value: "provenance" }
|
|
11245
|
+
},
|
|
11246
|
+
{
|
|
11247
|
+
kind: "Field",
|
|
11248
|
+
name: { kind: "Name", value: "reason" }
|
|
11249
|
+
},
|
|
11250
|
+
{
|
|
11251
|
+
kind: "Field",
|
|
11252
|
+
name: { kind: "Name", value: "detail" }
|
|
11253
|
+
},
|
|
11254
|
+
{
|
|
11255
|
+
kind: "Field",
|
|
11256
|
+
name: { kind: "Name", value: "tokenStatus" }
|
|
11257
|
+
}
|
|
11258
|
+
]
|
|
11259
|
+
}
|
|
11260
|
+
}
|
|
11261
|
+
]
|
|
11262
|
+
}
|
|
11263
|
+
}
|
|
11264
|
+
]
|
|
11265
|
+
}
|
|
11266
|
+
}
|
|
11267
|
+
]
|
|
11268
|
+
}
|
|
11269
|
+
}
|
|
11270
|
+
]
|
|
11271
|
+
};
|
|
10612
11272
|
var CliCostSummaryDocument = {
|
|
10613
11273
|
kind: "Document",
|
|
10614
11274
|
definitions: [
|
|
@@ -19937,6 +20597,7 @@ var documents = {
|
|
|
19937
20597
|
"\n query CliBudgetStatus($tenantId: ID!) {\n budgetStatus(tenantId: $tenantId) {\n policy {\n id\n scope\n agentId\n userId\n period\n limitUsd\n }\n spentUsd\n remainingUsd\n percentUsed\n status\n }\n }\n": CliBudgetStatusDocument,
|
|
19938
20598
|
"\n mutation CliUpsertBudgetPolicy(\n $tenantId: ID!\n $input: UpsertBudgetPolicyInput!\n ) {\n upsertBudgetPolicy(tenantId: $tenantId, input: $input) {\n id\n scope\n agentId\n userId\n limitUsd\n period\n actionOnExceed\n }\n }\n": CliUpsertBudgetPolicyDocument,
|
|
19939
20599
|
"\n mutation CliDeleteBudgetPolicy($id: ID!) {\n deleteBudgetPolicy(id: $id)\n }\n": CliDeleteBudgetPolicyDocument,
|
|
20600
|
+
"\n query CliCapabilityInspector(\n $tenantId: ID!\n $agentId: ID\n $spaceId: ID\n $agentProfileId: ID\n $perspectiveUserId: ID\n ) {\n capabilityInspector(\n tenantId: $tenantId\n agentId: $agentId\n spaceId: $spaceId\n agentProfileId: $agentProfileId\n perspectiveUserId: $perspectiveUserId\n ) {\n state\n stateDetail\n agentId\n noUserBaseline\n predicted {\n computedAt\n configFingerprint\n items {\n capabilityClass\n capabilityId\n displayName\n active\n provenance\n reason\n detail\n tokenStatus\n }\n }\n }\n }\n": CliCapabilityInspectorDocument,
|
|
19940
20601
|
"\n query CliCostSummary($tenantId: ID!, $from: AWSDateTime, $to: AWSDateTime) {\n costSummary(tenantId: $tenantId, from: $from, to: $to) {\n totalUsd\n llmUsd\n computeUsd\n toolsUsd\n evalUsd\n totalInputTokens\n totalOutputTokens\n eventCount\n }\n }\n": CliCostSummaryDocument,
|
|
19941
20602
|
"\n query CliCostByAgent($tenantId: ID!, $from: AWSDateTime, $to: AWSDateTime) {\n costByAgent(tenantId: $tenantId, from: $from, to: $to) {\n agentId\n agentName\n totalUsd\n eventCount\n }\n }\n": CliCostByAgentDocument,
|
|
19942
20603
|
"\n query CliCostByUser($tenantId: ID!, $from: AWSDateTime, $to: AWSDateTime) {\n costByUser(tenantId: $tenantId, from: $from, to: $to) {\n userId\n userName\n userEmail\n totalUsd\n eventCount\n isSystem\n }\n }\n": CliCostByUserDocument,
|
|
@@ -25107,7 +25768,7 @@ async function runWebhookTest(id, _opts) {
|
|
|
25107
25768
|
try {
|
|
25108
25769
|
const tokenData = await gqlQuery(ctx.client, WebhookForTestDoc, { id });
|
|
25109
25770
|
if (tokenData.webhook?.token) {
|
|
25110
|
-
const { resolveApiConfig: resolveApiConfig2 } = await import("./api-client-
|
|
25771
|
+
const { resolveApiConfig: resolveApiConfig2 } = await import("./api-client-JUBCQQDF.js");
|
|
25111
25772
|
const api = resolveApiConfig2(ctx.stage);
|
|
25112
25773
|
if (api?.apiUrl) {
|
|
25113
25774
|
const base = api.apiUrl.replace(/\/$/, "");
|
|
@@ -26789,6 +27450,112 @@ function registerTraceCommand(program2) {
|
|
|
26789
27450
|
trace.command("turn <turnId>").description("Per-invocation logs for a single thread turn.").option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").action(runTraceTurn);
|
|
26790
27451
|
}
|
|
26791
27452
|
|
|
27453
|
+
// src/commands/capabilities.ts
|
|
27454
|
+
var CapabilityInspectorDoc = graphql(`
|
|
27455
|
+
query CliCapabilityInspector(
|
|
27456
|
+
$tenantId: ID!
|
|
27457
|
+
$agentId: ID
|
|
27458
|
+
$spaceId: ID
|
|
27459
|
+
$agentProfileId: ID
|
|
27460
|
+
$perspectiveUserId: ID
|
|
27461
|
+
) {
|
|
27462
|
+
capabilityInspector(
|
|
27463
|
+
tenantId: $tenantId
|
|
27464
|
+
agentId: $agentId
|
|
27465
|
+
spaceId: $spaceId
|
|
27466
|
+
agentProfileId: $agentProfileId
|
|
27467
|
+
perspectiveUserId: $perspectiveUserId
|
|
27468
|
+
) {
|
|
27469
|
+
state
|
|
27470
|
+
stateDetail
|
|
27471
|
+
agentId
|
|
27472
|
+
noUserBaseline
|
|
27473
|
+
predicted {
|
|
27474
|
+
computedAt
|
|
27475
|
+
configFingerprint
|
|
27476
|
+
items {
|
|
27477
|
+
capabilityClass
|
|
27478
|
+
capabilityId
|
|
27479
|
+
displayName
|
|
27480
|
+
active
|
|
27481
|
+
provenance
|
|
27482
|
+
reason
|
|
27483
|
+
detail
|
|
27484
|
+
tokenStatus
|
|
27485
|
+
}
|
|
27486
|
+
}
|
|
27487
|
+
}
|
|
27488
|
+
}
|
|
27489
|
+
`);
|
|
27490
|
+
async function runCapabilities(opts) {
|
|
27491
|
+
if (opts.json) setJsonMode(true);
|
|
27492
|
+
const ctx = await resolveTenantContext(opts);
|
|
27493
|
+
const data = await gqlQuery(ctx.client, CapabilityInspectorDoc, {
|
|
27494
|
+
tenantId: ctx.tenantId,
|
|
27495
|
+
agentId: opts.agent ?? null,
|
|
27496
|
+
spaceId: opts.space ?? null,
|
|
27497
|
+
agentProfileId: opts.profile ?? null,
|
|
27498
|
+
perspectiveUserId: opts.user ?? null
|
|
27499
|
+
});
|
|
27500
|
+
const inspection = data.capabilityInspector;
|
|
27501
|
+
if (isJsonMode()) {
|
|
27502
|
+
printJson(inspection);
|
|
27503
|
+
if (inspection.state !== "ok") process.exitCode = 1;
|
|
27504
|
+
return;
|
|
27505
|
+
}
|
|
27506
|
+
if (inspection.state === "invalid_selection") {
|
|
27507
|
+
printError(`Invalid selection: ${inspection.stateDetail}`);
|
|
27508
|
+
process.exitCode = 1;
|
|
27509
|
+
return;
|
|
27510
|
+
}
|
|
27511
|
+
if (inspection.state === "resolution_fault") {
|
|
27512
|
+
printError(`Resolution fault: ${inspection.stateDetail}`);
|
|
27513
|
+
process.exitCode = 1;
|
|
27514
|
+
return;
|
|
27515
|
+
}
|
|
27516
|
+
const predicted = inspection.predicted;
|
|
27517
|
+
if (!predicted) {
|
|
27518
|
+
printError("Inspector returned no capability set.");
|
|
27519
|
+
process.exitCode = 1;
|
|
27520
|
+
return;
|
|
27521
|
+
}
|
|
27522
|
+
if (inspection.noUserBaseline) {
|
|
27523
|
+
console.log(
|
|
27524
|
+
"No-user baseline (what a scheduled/wakeup turn gets). Pass --user <id> for a user's perspective.\n"
|
|
27525
|
+
);
|
|
27526
|
+
}
|
|
27527
|
+
printTable(
|
|
27528
|
+
predicted.items.map((item) => ({
|
|
27529
|
+
class: item.capabilityClass,
|
|
27530
|
+
id: item.displayName || item.capabilityId,
|
|
27531
|
+
state: item.active ? "active" : item.reason ?? "inactive",
|
|
27532
|
+
token: item.tokenStatus ?? "\u2014",
|
|
27533
|
+
provenance: item.provenance ?? "\u2014",
|
|
27534
|
+
detail: item.detail ?? "\u2014"
|
|
27535
|
+
})),
|
|
27536
|
+
[
|
|
27537
|
+
{ key: "class", header: "CLASS" },
|
|
27538
|
+
{ key: "id", header: "CAPABILITY" },
|
|
27539
|
+
{ key: "state", header: "STATE" },
|
|
27540
|
+
{ key: "token", header: "TOKEN" },
|
|
27541
|
+
{ key: "provenance", header: "PROVENANCE" },
|
|
27542
|
+
{ key: "detail", header: "DETAIL" }
|
|
27543
|
+
]
|
|
27544
|
+
);
|
|
27545
|
+
console.log(
|
|
27546
|
+
`
|
|
27547
|
+
Computed ${predicted.computedAt} \xB7 fingerprint ${predicted.configFingerprint.slice(0, 12)}`
|
|
27548
|
+
);
|
|
27549
|
+
}
|
|
27550
|
+
function registerCapabilitiesCommand(program2) {
|
|
27551
|
+
program2.command("capabilities").description(
|
|
27552
|
+
"Effective capability set for an agent context \u2014 every skill, tool, MCP server, extension, and plugin with its state and gate reason."
|
|
27553
|
+
).option("-s, --stage <name>", "Deployment stage").option("-t, --tenant <slug>", "Tenant slug").option("--agent <agentId>", "Agent id (defaults to the platform agent)").option("--space <spaceId>", "Space id").option("--profile <agentProfileId>", "Agent Profile id").option(
|
|
27554
|
+
"--user <userId>",
|
|
27555
|
+
"Perspective user id (omit for the no-invoker baseline)"
|
|
27556
|
+
).option("--json", "Raw JSON output").action(runCapabilities);
|
|
27557
|
+
}
|
|
27558
|
+
|
|
26792
27559
|
// src/commands/dashboard.ts
|
|
26793
27560
|
var DashboardDoc = graphql(`
|
|
26794
27561
|
query CliDashboard($tenantId: ID!) {
|
|
@@ -30216,6 +30983,7 @@ registerCostCommand(program);
|
|
|
30216
30983
|
registerBudgetCommand(program);
|
|
30217
30984
|
registerPerformanceCommand(program);
|
|
30218
30985
|
registerTraceCommand(program);
|
|
30986
|
+
registerCapabilitiesCommand(program);
|
|
30219
30987
|
registerDashboardCommand(program);
|
|
30220
30988
|
registerEvalCommand(program);
|
|
30221
30989
|
registerEnterpriseCommand(program);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-- Resolved capability manifest context columns (capability-mapping plan U11).
|
|
2
|
+
-- Additive: turn/context identity + config fingerprint on the append-only
|
|
3
|
+
-- manifest audit table, plus the covering index for "newest manifest matching
|
|
4
|
+
-- context" retrieval (U13) and the retention sweep's tenant+created_at range.
|
|
5
|
+
-- Plain uuids, deliberately NOT foreign keys — matching template_id/user_id on
|
|
6
|
+
-- the same table: audit rows must survive deletion of their source rows.
|
|
7
|
+
-- creates-column: public.resolved_capability_manifests.thread_id
|
|
8
|
+
-- creates-column: public.resolved_capability_manifests.thread_turn_id
|
|
9
|
+
-- creates-column: public.resolved_capability_manifests.space_id
|
|
10
|
+
-- creates-column: public.resolved_capability_manifests.agent_profile_id
|
|
11
|
+
-- creates-column: public.resolved_capability_manifests.config_fingerprint
|
|
12
|
+
-- creates: public.idx_rcm_context
|
|
13
|
+
|
|
14
|
+
ALTER TABLE public.resolved_capability_manifests
|
|
15
|
+
ADD COLUMN IF NOT EXISTS thread_id uuid,
|
|
16
|
+
ADD COLUMN IF NOT EXISTS thread_turn_id uuid,
|
|
17
|
+
ADD COLUMN IF NOT EXISTS space_id uuid,
|
|
18
|
+
ADD COLUMN IF NOT EXISTS agent_profile_id uuid,
|
|
19
|
+
ADD COLUMN IF NOT EXISTS config_fingerprint text;
|
|
20
|
+
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_rcm_context
|
|
22
|
+
ON public.resolved_capability_manifests (
|
|
23
|
+
tenant_id,
|
|
24
|
+
agent_id,
|
|
25
|
+
space_id,
|
|
26
|
+
agent_profile_id,
|
|
27
|
+
created_at
|
|
28
|
+
);
|
|
@@ -24,6 +24,8 @@ locals {
|
|
|
24
24
|
chat_agent_finalize_fn_arn = "arn:aws:lambda:${var.region}:${var.account_id}:function:${local.chat_agent_finalize_fn_name}"
|
|
25
25
|
chat_agent_activity_fn_name = "thinkwork-${var.stage}-api-chat-agent-activity"
|
|
26
26
|
chat_agent_activity_fn_arn = "arn:aws:lambda:${var.region}:${var.account_id}:function:${local.chat_agent_activity_fn_name}"
|
|
27
|
+
manifest_log_fn_name = "thinkwork-${var.stage}-api-manifest-log"
|
|
28
|
+
manifest_log_fn_arn = "arn:aws:lambda:${var.region}:${var.account_id}:function:${local.manifest_log_fn_name}"
|
|
27
29
|
pi_image_uri = "${var.ecr_repository_url}:pi-latest"
|
|
28
30
|
cognee_vpc_enabled = length(var.cognee_subnet_ids) > 0 && length(var.cognee_security_group_ids) > 0
|
|
29
31
|
okf_efs_vpc_enabled = var.okf_efs_enabled && length(var.okf_efs_subnet_ids) > 0 && length(var.okf_efs_security_group_ids) > 0
|
|
@@ -246,8 +248,9 @@ resource "aws_iam_role_policy" "agentcore_pi" {
|
|
|
246
248
|
},
|
|
247
249
|
{
|
|
248
250
|
# Invoke API Lambdas from Pi's private VPC. Memory retain is queued
|
|
249
|
-
# async; chat activity/finalize
|
|
250
|
-
#
|
|
251
|
+
# async; chat activity/finalize and the per-turn capability-manifest
|
|
252
|
+
# POST are RequestResponse callbacks because public API Gateway
|
|
253
|
+
# endpoints are not reachable reliably from this VPC.
|
|
251
254
|
Sid = "ApiLambdaInvoke"
|
|
252
255
|
Effect = "Allow"
|
|
253
256
|
Action = ["lambda:InvokeFunction"]
|
|
@@ -255,6 +258,7 @@ resource "aws_iam_role_policy" "agentcore_pi" {
|
|
|
255
258
|
local.memory_retain_fn_arn,
|
|
256
259
|
local.chat_agent_finalize_fn_arn,
|
|
257
260
|
local.chat_agent_activity_fn_arn,
|
|
261
|
+
local.manifest_log_fn_arn,
|
|
258
262
|
]
|
|
259
263
|
},
|
|
260
264
|
{
|
|
@@ -349,6 +353,14 @@ resource "terraform_data" "seed_pi_image" {
|
|
|
349
353
|
source_id="$(docker image inspect --format '{{.Id}}' "${var.source_image_uri}")"
|
|
350
354
|
docker tag "$source_id" "${local.pi_image_uri}"
|
|
351
355
|
else
|
|
356
|
+
# Unreachable source must not fail a stack whose target tag is already
|
|
357
|
+
# seeded (rerun after a partial deploy, or a registry that went
|
|
358
|
+
# private) — the Lambda only consumes the target ECR tag.
|
|
359
|
+
repo_name="$(basename "${var.ecr_repository_url}")"
|
|
360
|
+
if aws ecr describe-images --repository-name "$repo_name" --image-ids imageTag=pi-latest --region ${var.region} >/dev/null 2>&1; then
|
|
361
|
+
echo "WARN: could not pull ${var.source_image_uri}; ${local.pi_image_uri} is already seeded — keeping the existing image."
|
|
362
|
+
exit 0
|
|
363
|
+
fi
|
|
352
364
|
dockerfile="$repo_root/packages/agentcore-pi/agent-container/Dockerfile"
|
|
353
365
|
if [[ ! -f "$dockerfile" ]]; then
|
|
354
366
|
echo "ERROR: could not pull ${var.source_image_uri} and there is no repo checkout at $repo_root to build from." >&2
|
|
@@ -389,6 +401,7 @@ resource "aws_lambda_function" "agentcore_pi" {
|
|
|
389
401
|
MEMORY_RETAIN_FN_NAME = local.memory_retain_fn_name
|
|
390
402
|
CHAT_AGENT_FINALIZE_FN_NAME = local.chat_agent_finalize_fn_name
|
|
391
403
|
CHAT_AGENT_ACTIVITY_FN_NAME = local.chat_agent_activity_fn_name
|
|
404
|
+
MANIFEST_LOG_FUNCTION_NAME = local.manifest_log_fn_name
|
|
392
405
|
HINDSIGHT_ENDPOINT = var.hindsight_endpoint
|
|
393
406
|
THINKWORK_API_URL = var.api_endpoint
|
|
394
407
|
API_AUTH_SECRET = var.api_auth_secret
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkwork-cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.16",
|
|
4
4
|
"description": "Thinkwork CLI — deploy, manage, and interact with your Thinkwork stack",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@graphql-codegen/cli": "^5.0.6",
|
|
34
34
|
"@graphql-codegen/client-preset": "^4.8.2",
|
|
35
35
|
"@thinkwork/admin-ops": "workspace:*",
|
|
36
|
+
"@thinkwork/database-pg": "workspace:*",
|
|
36
37
|
"@types/node": "^25.6.0",
|
|
37
38
|
"tsup": "^8.0.0",
|
|
38
39
|
"tsx": "^4.0.0",
|