@go-to-k/cdkd 0.206.0 → 0.207.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/dist/cli.js CHANGED
@@ -24,7 +24,7 @@ import { ACMClient, AddTagsToCertificateCommand, DeleteCertificateCommand, Descr
24
24
  import * as fs from "node:fs";
25
25
  import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
26
26
  import * as path from "node:path";
27
- import { dirname, isAbsolute, join, resolve } from "node:path";
27
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
28
28
  import { execFile, spawn } from "node:child_process";
29
29
  import { tmpdir } from "node:os";
30
30
  import { AssociateVPCWithHostedZoneCommand, ChangeResourceRecordSetsCommand, ChangeTagsForResourceCommand, CreateHostedZoneCommand, CreateQueryLoggingConfigCommand, DeleteHostedZoneCommand, DeleteQueryLoggingConfigCommand, DisassociateVPCFromHostedZoneCommand, GetHostedZoneCommand, ListHostedZonesByNameCommand, ListHostedZonesCommand, ListQueryLoggingConfigsCommand, ListResourceRecordSetsCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$6, Route53Client, UpdateHostedZoneCommentCommand, UpdateHostedZoneFeaturesCommand } from "@aws-sdk/client-route-53";
@@ -62,7 +62,7 @@ import { CreateNamespaceCommand, CreateTableBucketCommand, CreateTableCommand as
62
62
  import { AttachLoadBalancerTargetGroupsCommand, AttachLoadBalancersCommand, AttachTrafficSourcesCommand, AutoScalingClient, CreateAutoScalingGroupCommand, CreateOrUpdateTagsCommand, DeleteAutoScalingGroupCommand, DeleteLifecycleHookCommand, DeleteNotificationConfigurationCommand, DeleteTagsCommand as DeleteTagsCommand$1, DescribeAutoScalingGroupsCommand, DescribeLifecycleHooksCommand, DescribeNotificationConfigurationsCommand, DescribeTrafficSourcesCommand, DetachLoadBalancerTargetGroupsCommand, DetachLoadBalancersCommand, DetachTrafficSourcesCommand, DisableMetricsCollectionCommand, EnableMetricsCollectionCommand, PutLifecycleHookCommand, PutNotificationConfigurationCommand, UpdateAutoScalingGroupCommand } from "@aws-sdk/client-auto-scaling";
63
63
  import { Document, Pair, Scalar, YAMLMap, YAMLSeq, parse as parse$1, stringify } from "yaml";
64
64
  import { createLocalStateProvider, getEmbedConfig, isCfnFlagPresent, listTargets, rejectExplicitCfnStackWithMultipleStacks, resolveCfnFallbackRegion, setEmbedConfig, substituteAgainstState, substituteAgainstStateAsync, substituteEnvVarsFromState, substituteEnvVarsFromStateAsync } from "cdk-local";
65
- import { A2A_CONTAINER_PORT, A2A_PATH, AGENTCORE_A2A_PROTOCOL, AGENTCORE_AGUI_PROTOCOL, AGENTCORE_MCP_PROTOCOL, ConnectionRegistry, EcsTaskResolutionError, HOST_GATEWAY_MIN_VERSION, LocalInvokeBuildError, MCP_CONTAINER_PORT, MCP_PATH, a2aInvokeOnce, addAlbSpecificOptions, addCommonEcsServiceOptions, addStartServiceSpecificOptions, albStrategy, architectureToPlatform, attachAuthorizers, attachStageContext, availableApiIdentifiers, bufferToBody, buildAgentCoreCodeImage, buildCognitoJwksUrl, buildConnectEvent, buildContainerImage, buildCorsConfigByApiId, buildCorsConfigFromCloudFrontChain, buildDisconnectEvent, buildJwksUrlFromIssuer, buildMessageEvent, buildMgmtEndpointEnvUrl, buildStageMap, createAuthorizerCache, createFileWatcher, createJwksCache, createWatchPredicates, defaultCredentialsLoader, derivePseudoParametersFromRegion, discoverRoutes, discoverWebSocketApis, downloadAndExtractS3Bundle, filterRoutesByApiIdentifier, groupRoutesByServer, handleConnectionsRequest, invokeAgentCore, invokeAgentCoreWs, materializeLayerFromArn, mcpInvokeOnce, parseConnectionsPath, parseSelectionExpressionPath, pickAgentCoreCandidateStack, probeHostGatewaySupport, readMtlsMaterialsFromDisk, resolveAgentCoreTarget, resolveEnvVars, resolveRuntimeCodeMountPath, resolveRuntimeFileExtension, resolveRuntimeImage, resolveSingleTarget, resolveWatchConfig, runEcsServiceEmulator, signAgentCoreInvocation, startApiServer, substituteImagePlaceholders, tryResolveImageFnJoin, verifyJwtViaDiscovery, waitForAgentCorePing } from "cdk-local/internal";
65
+ import { A2A_CONTAINER_PORT, A2A_PATH, AGENTCORE_A2A_PROTOCOL, AGENTCORE_AGUI_PROTOCOL, AGENTCORE_MCP_PROTOCOL, ConnectionRegistry, EcsTaskResolutionError, HOST_GATEWAY_MIN_VERSION, LocalInvokeBuildError, MCP_CONTAINER_PORT, MCP_PATH, a2aInvokeOnce, addAlbSpecificOptions, addCommonEcsServiceOptions, addStartServiceSpecificOptions, albStrategy, architectureToPlatform, attachAuthorizers, attachStageContext, availableApiIdentifiers, bufferToBody, buildAgentCoreCodeImage, buildCognitoJwksUrl, buildConnectEvent, buildContainerImage, buildCorsConfigByApiId, buildCorsConfigFromCloudFrontChain, buildDisconnectEvent, buildJwksUrlFromIssuer, buildMessageEvent, buildMgmtEndpointEnvUrl, buildStageMap, classifySourceChange, createAuthorizerCache, createFileWatcher, createFileWatcher as createFileWatcher$1, createJwksCache, createWatchPredicates, defaultCredentialsLoader, derivePseudoParametersFromRegion, discoverRoutes, discoverWebSocketApis, downloadAndExtractS3Bundle, filterRoutesByApiIdentifier, groupRoutesByServer, handleConnectionsRequest, invokeAgentCore, invokeAgentCoreWs, materializeLayerFromArn, mcpInvokeOnce, parseConnectionsPath, parseSelectionExpressionPath, pickAgentCoreCandidateStack, pickAgentCoreCandidateStack as pickAgentCoreCandidateStack$1, probeHostGatewaySupport, readMtlsMaterialsFromDisk, resolveAgentCoreTarget, resolveEnvVars, resolveRuntimeCodeMountPath, resolveRuntimeFileExtension, resolveRuntimeImage, resolveSingleTarget, resolveWatchConfig, runEcsServiceEmulator, signAgentCoreInvocation, startApiServer, substituteImagePlaceholders, tryResolveImageFnJoin, verifyJwtViaDiscovery, waitForAgentCorePing } from "cdk-local/internal";
66
66
  import { createServer } from "node:net";
67
67
  import { promisify } from "node:util";
68
68
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
@@ -44891,7 +44891,7 @@ async function applyCrossStackResolverToTask(task, context) {
44891
44891
 
44892
44892
  //#endregion
44893
44893
  //#region src/local/docker-runner.ts
44894
- const execFileAsync$3 = promisify(execFile);
44894
+ const execFileAsync$4 = promisify(execFile);
44895
44895
  /**
44896
44896
  * Wraps `docker pull` / `docker run` / `docker rm` for `cdkd local invoke`.
44897
44897
  *
@@ -44989,7 +44989,7 @@ async function runDetached(opts) {
44989
44989
  args.push(opts.image, ...entryPointTail, ...opts.cmd);
44990
44990
  getLogger().child("docker").debug(`${getDockerCmd()} ${redactAwsCredentialsInArgs(args).join(" ")}`);
44991
44991
  try {
44992
- const { stdout } = await execFileAsync$3(getDockerCmd(), args, {
44992
+ const { stdout } = await execFileAsync$4(getDockerCmd(), args, {
44993
44993
  maxBuffer: 10 * 1024 * 1024,
44994
44994
  env: {
44995
44995
  ...process.env,
@@ -45032,7 +45032,7 @@ async function removeContainer(containerId) {
45032
45032
  if (!containerId) return;
45033
45033
  const logger = getLogger().child("docker");
45034
45034
  try {
45035
- await execFileAsync$3(getDockerCmd(), [
45035
+ await execFileAsync$4(getDockerCmd(), [
45036
45036
  "rm",
45037
45037
  "-f",
45038
45038
  containerId
@@ -45051,7 +45051,7 @@ async function removeContainer(containerId) {
45051
45051
  async function ensureDockerAvailable() {
45052
45052
  const cmd = getDockerCmd();
45053
45053
  try {
45054
- await execFileAsync$3(cmd, [
45054
+ await execFileAsync$4(cmd, [
45055
45055
  "version",
45056
45056
  "--format",
45057
45057
  "{{.Server.Version}}"
@@ -46952,7 +46952,7 @@ async function localStartApiCommand(target, options) {
46952
46952
  output: options.output,
46953
46953
  watchConfig: resolveWatchConfig()
46954
46954
  });
46955
- watcher = createFileWatcher({
46955
+ watcher = createFileWatcher$1({
46956
46956
  paths: [watchRoot],
46957
46957
  ignored,
46958
46958
  shouldTrigger,
@@ -48122,7 +48122,7 @@ function createLocalStartApiCommand() {
48122
48122
 
48123
48123
  //#endregion
48124
48124
  //#region src/local/ecs-network.ts
48125
- const execFileAsync$2 = promisify(execFile);
48125
+ const execFileAsync$3 = promisify(execFile);
48126
48126
  /**
48127
48127
  * Docker network + AWS-published metadata-endpoints sidecar lifecycle for
48128
48128
  * `cdkd local run-task`. The sidecar (a small Go binary maintained by
@@ -48175,7 +48175,7 @@ async function createNetworkAndSidecar(args) {
48175
48175
  await pullImage(METADATA_ENDPOINT_IMAGE, skipPull);
48176
48176
  logger.info(`Creating docker network ${networkName} (subnet ${cidr})...`);
48177
48177
  try {
48178
- await execFileAsync$2(getDockerCmd(), [
48178
+ await execFileAsync$3(getDockerCmd(), [
48179
48179
  "network",
48180
48180
  "create",
48181
48181
  "--driver",
@@ -48210,7 +48210,7 @@ async function createNetworkAndSidecar(args) {
48210
48210
  sidecarArgs.push(METADATA_ENDPOINT_IMAGE);
48211
48211
  logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
48212
48212
  try {
48213
- const { stdout } = await execFileAsync$2(getDockerCmd(), sidecarArgs, { maxBuffer: 10 * 1024 * 1024 });
48213
+ const { stdout } = await execFileAsync$3(getDockerCmd(), sidecarArgs, { maxBuffer: 10 * 1024 * 1024 });
48214
48214
  return stdout.trim();
48215
48215
  } catch (err) {
48216
48216
  await destroyNetworkOnly(networkName);
@@ -48278,7 +48278,7 @@ async function destroyNetworkOnly(networkName) {
48278
48278
  if (!networkName) return;
48279
48279
  const logger = getLogger().child("ecs-network");
48280
48280
  try {
48281
- await execFileAsync$2(getDockerCmd(), [
48281
+ await execFileAsync$3(getDockerCmd(), [
48282
48282
  "network",
48283
48283
  "rm",
48284
48284
  networkName
@@ -48411,7 +48411,7 @@ async function resolveSsm(entry, shape, client) {
48411
48411
 
48412
48412
  //#endregion
48413
48413
  //#region src/local/ecs-task-runner.ts
48414
- const execFileAsync$1 = promisify(execFile);
48414
+ const execFileAsync$2 = promisify(execFile);
48415
48415
  /**
48416
48416
  * Top-level orchestrator for `cdkd local run-task`. Coordinates image
48417
48417
  * preparation, secret resolution, docker-network bring-up, container
@@ -48471,7 +48471,7 @@ async function cleanupEcsRun(state, options) {
48471
48471
  if (state.network && !state.network.ownedByCaller) await destroyTaskNetwork(state.network);
48472
48472
  state.network = void 0;
48473
48473
  for (const v of state.dockerVolumeNames) try {
48474
- await execFileAsync$1(getDockerCmd(), [
48474
+ await execFileAsync$2(getDockerCmd(), [
48475
48475
  "volume",
48476
48476
  "rm",
48477
48477
  v
@@ -48547,7 +48547,7 @@ async function runEcsTask(task, options, state) {
48547
48547
  logger.info(`Starting container '${container.name}' (image=${imagePlan.get(container.name)})`);
48548
48548
  let id;
48549
48549
  try {
48550
- const { stdout } = await execFileAsync$1(getDockerCmd(), args, { maxBuffer: 10 * 1024 * 1024 });
48550
+ const { stdout } = await execFileAsync$2(getDockerCmd(), args, { maxBuffer: 10 * 1024 * 1024 });
48551
48551
  id = stdout.trim();
48552
48552
  } catch (err) {
48553
48553
  const e = err;
@@ -48656,7 +48656,7 @@ async function waitForContainerHealthy(containerId, displayName) {
48656
48656
  let lastStatus = "";
48657
48657
  while (Date.now() < deadline) {
48658
48658
  try {
48659
- const { stdout } = await execFileAsync$1(getDockerCmd(), [
48659
+ const { stdout } = await execFileAsync$2(getDockerCmd(), [
48660
48660
  "inspect",
48661
48661
  "--format",
48662
48662
  "{{.State.Health.Status}}",
@@ -48679,7 +48679,7 @@ async function waitForContainerHealthy(containerId, displayName) {
48679
48679
  }
48680
48680
  async function waitForContainerExit(containerId) {
48681
48681
  try {
48682
- const { stdout } = await execFileAsync$1(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
48682
+ const { stdout } = await execFileAsync$2(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
48683
48683
  const code = Number.parseInt(stdout.trim(), 10);
48684
48684
  return Number.isFinite(code) ? code : 1;
48685
48685
  } catch (err) {
@@ -48689,7 +48689,7 @@ async function waitForContainerExit(containerId) {
48689
48689
  }
48690
48690
  async function stopContainer(containerId, graceSeconds) {
48691
48691
  try {
48692
- await execFileAsync$1(getDockerCmd(), [
48692
+ await execFileAsync$2(getDockerCmd(), [
48693
48693
  "stop",
48694
48694
  "-t",
48695
48695
  String(graceSeconds),
@@ -48814,7 +48814,7 @@ async function realizeDockerVolumes(volumes, state) {
48814
48814
  const dockerVolumeName = `cdkd-local-${v.name}-${randHex(4)}`;
48815
48815
  args.push(dockerVolumeName);
48816
48816
  try {
48817
- await execFileAsync$1(getDockerCmd(), args);
48817
+ await execFileAsync$2(getDockerCmd(), args);
48818
48818
  state.dockerVolumeNames.push(dockerVolumeName);
48819
48819
  logger.debug(`Created docker volume ${dockerVolumeName} for task volume '${v.name}'`);
48820
48820
  } catch (err) {
@@ -49280,6 +49280,292 @@ function createLocalStartServiceCommand() {
49280
49280
  return addCommonEcsServiceOptions(cmd);
49281
49281
  }
49282
49282
 
49283
+ //#endregion
49284
+ //#region src/local/invoke-agentcore-watch-loop.ts
49285
+ /**
49286
+ * cdkd-owned `cdkd local invoke-agentcore --watch` reload loop.
49287
+ *
49288
+ * cdk-local PR #270's `runAgentCoreWatchLoop` hard-couples to cdk-local's OWN
49289
+ * `Synthesizer` / `LocalInvokeAgentCoreOptions` types, so it cannot be shimmed.
49290
+ * Instead this module re-implements the loop on top of cdk-local's already-
49291
+ * exported watch primitives (`createFileWatcher` / `createWatchPredicates` /
49292
+ * `resolveWatchConfig` / `classifySourceChange` + the `ReloadVerdict` /
49293
+ * `ReloadAssetContext` types) — the SAME pattern `cdkd local start-api --watch`
49294
+ * uses — and drives cdkd's OWN re-synth / image-build / docker pipeline through
49295
+ * the `rebuild` / `softReload` callbacks the command supplies.
49296
+ *
49297
+ * Behavior parity with cdk-local #270:
49298
+ * - re-synth + reload the agent container on CDK source edits (watch the
49299
+ * source tree, honoring `cdk.json` `watch.include` / `watch.exclude`);
49300
+ * - per-firing classifier: an interpreted-language source edit inside a
49301
+ * CodeConfiguration source tree takes a soft-reload FAST PATH (`docker cp`
49302
+ * + `docker restart`, no rebuild); Dockerfile / compiled-source /
49303
+ * asset-hash-changed / ambiguous edits force a full rebuild;
49304
+ * - reload-chain serialization (no two reloads in parallel);
49305
+ * - for the `--ws` session path: close the active socket cleanly on each
49306
+ * reload (via the abort signal) and reopen against the new container;
49307
+ * - for the default one-shot `/invocations` path: re-run the single shot
49308
+ * after each reload (cdkd-specific — cdk-local treats single-shot as a
49309
+ * no-op WARN).
49310
+ */
49311
+ const execFileAsync$1 = promisify(execFile);
49312
+ /**
49313
+ * Run the `--watch` reload loop. Returns when the invocation closes naturally
49314
+ * with no pending reload (one-shot finished, or `--ws` agent closed), or when
49315
+ * a reload callback throws (the previous container may already be torn down,
49316
+ * so blocking on the next ping would hang). SIGINT runs the outer command's
49317
+ * cleanup + `process.exit`, so this loop is not the termination path on
49318
+ * user-initiated shutdown.
49319
+ */
49320
+ async function runAgentCoreWatchLoop(args) {
49321
+ const logger = getLogger();
49322
+ const waitForPing = args.__waitForPing ?? waitForAgentCorePing;
49323
+ let currentHostPort = args.hostPort;
49324
+ let currentStacks = args.stacks;
49325
+ let reloadChain = Promise.resolve();
49326
+ let currentAbort;
49327
+ let pendingReload = false;
49328
+ let reloadFailed = false;
49329
+ const watcherFactory = args.__watcherFactory ?? ((onChange) => {
49330
+ const watchRoot = process.cwd();
49331
+ const { ignored, shouldTrigger, excludePatterns } = createWatchPredicates({
49332
+ watchRoot,
49333
+ output: args.options.output,
49334
+ watchConfig: resolveWatchConfig()
49335
+ });
49336
+ logger.info(`Watching ${watchRoot} for source changes (excluding ${excludePatterns.join(", ")}).`);
49337
+ return createFileWatcher({
49338
+ paths: [watchRoot],
49339
+ ignored,
49340
+ shouldTrigger,
49341
+ onChange
49342
+ });
49343
+ });
49344
+ const cdkOutDir = args.options.output;
49345
+ const assetLoader = new AssetManifestLoader();
49346
+ const watcher = watcherFactory((changedPaths) => {
49347
+ reloadChain = reloadChain.then(async () => {
49348
+ let verdict = {
49349
+ kind: "rebuild",
49350
+ reason: "classifier not consulted"
49351
+ };
49352
+ try {
49353
+ let assetCtx;
49354
+ if (args.__classifierContext) assetCtx = await args.__classifierContext(changedPaths);
49355
+ else {
49356
+ const { stacks: freshStacks } = await args.synthesizer.synthesize(args.synthOpts);
49357
+ const oldAssetHash = await deriveOldAssetHash({
49358
+ resolvedTarget: args.resolvedTarget,
49359
+ resolved: args.resolved,
49360
+ stacks: currentStacks,
49361
+ cdkOutDir,
49362
+ assetLoader
49363
+ });
49364
+ assetCtx = await loadAgentCoreAssetContext({
49365
+ resolvedTarget: args.resolvedTarget,
49366
+ resolved: args.resolved,
49367
+ stacks: freshStacks,
49368
+ cdkOutDir,
49369
+ assetLoader,
49370
+ ...oldAssetHash !== void 0 && { oldAssetHash }
49371
+ });
49372
+ }
49373
+ verdict = classifySourceChange(changedPaths, assetCtx);
49374
+ logger.info(`Detected source change (${changedPaths.length} path(s)); verdict=${verdict.kind} (${verdict.reason}).`);
49375
+ } catch (err) {
49376
+ logger.warn(`Reload: classifier context unavailable (${err instanceof Error ? err.message : String(err)}); falling back to rebuild.`);
49377
+ verdict = {
49378
+ kind: "rebuild",
49379
+ reason: "classifier context unavailable; falling back to rebuild"
49380
+ };
49381
+ }
49382
+ pendingReload = true;
49383
+ logger.warn("cdkd local invoke-agentcore --watch: source change detected; closing the active invocation so the client can reconnect to the rebuilt container.");
49384
+ currentAbort?.abort();
49385
+ try {
49386
+ if (verdict.kind === "soft-reload") {
49387
+ const { stacks: newStacks } = await args.softReload(verdict.newAssetSourceDir);
49388
+ currentStacks = newStacks;
49389
+ logger.info("Reload: soft-reloaded the agent container (docker cp + docker restart; container ID + host port preserved).");
49390
+ } else {
49391
+ const { hostPort: newHostPort, stacks: newStacks } = await args.rebuild();
49392
+ currentHostPort = newHostPort;
49393
+ currentStacks = newStacks;
49394
+ logger.info(`Reload: rebuilt the agent container.`);
49395
+ }
49396
+ } catch (err) {
49397
+ logger.error(`Reload failed: ${err instanceof Error ? err.message : String(err)}. The previous container may already be torn down; exiting --watch loop. Re-run cdkd local invoke-agentcore --watch to recover.`);
49398
+ reloadFailed = true;
49399
+ currentAbort?.abort();
49400
+ }
49401
+ }).catch((err) => {
49402
+ logger.error(`Reload chain threw: ${err instanceof Error ? err.message : String(err)}`);
49403
+ });
49404
+ });
49405
+ try {
49406
+ let firstIteration = true;
49407
+ while (true) {
49408
+ if (reloadFailed) break;
49409
+ await waitForPing(args.containerHost, currentHostPort);
49410
+ const abort = new AbortController();
49411
+ currentAbort = abort;
49412
+ pendingReload = false;
49413
+ try {
49414
+ const outcome = await args.invokeOnce({
49415
+ hostPort: currentHostPort,
49416
+ abortSignal: abort.signal,
49417
+ firstIteration
49418
+ });
49419
+ firstIteration = false;
49420
+ if (outcome.pendingReload) pendingReload = true;
49421
+ } finally {
49422
+ currentAbort = void 0;
49423
+ }
49424
+ if (!pendingReload) {
49425
+ await reloadChain.catch(() => void 0);
49426
+ if (!pendingReload) break;
49427
+ }
49428
+ await reloadChain.catch(() => void 0);
49429
+ }
49430
+ } finally {
49431
+ try {
49432
+ await watcher.close();
49433
+ } catch (err) {
49434
+ logger.warn(`Watcher close failed: ${err instanceof Error ? err.message : String(err)}`);
49435
+ }
49436
+ }
49437
+ }
49438
+ /**
49439
+ * Soft-reload the agent container in place: `docker cp` the freshly-synthed
49440
+ * asset directory contents into the running container's WORKDIR + `docker
49441
+ * restart`.
49442
+ *
49443
+ * - WORKDIR is resolved from the live container's image config via
49444
+ * `docker inspect --format '{{.Config.WorkingDir}}'`. An empty WORKDIR
49445
+ * (Docker runtime default) maps to `/`. For CodeConfiguration runtimes the
49446
+ * generated Dockerfile sets `WORKDIR /app`, so the copy lands there.
49447
+ * - The trailing `/.` on the source ensures CONTENTS are copied (not the
49448
+ * directory itself); the trailing `/` on the dest forces docker cp to treat
49449
+ * it as a directory.
49450
+ * - `docker restart` cycles PID 1 — the new source is picked up by the
49451
+ * interpreted-language runtime on its next startup. The container ID + host
49452
+ * port + network are preserved across the restart.
49453
+ *
49454
+ * @internal — exported for unit tests of the docker-cp + docker-restart shape
49455
+ * without standing up a real container.
49456
+ */
49457
+ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
49458
+ const logger = getLogger();
49459
+ const dockerCmd = getDockerCmd();
49460
+ let workdir;
49461
+ try {
49462
+ const { stdout } = await execFileAsync$1(dockerCmd, [
49463
+ "inspect",
49464
+ "--format",
49465
+ "{{.Config.WorkingDir}}",
49466
+ containerId
49467
+ ]);
49468
+ workdir = stdout.trim() || "/";
49469
+ } catch (err) {
49470
+ throw new CdkdError(`softReloadAgentContainer: docker inspect of container '${containerId}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_INSPECT_FAILED");
49471
+ }
49472
+ const workdirDest = workdir.endsWith("/") ? workdir : `${workdir}/`;
49473
+ logger.info(`Soft-reload: docker cp ${newAssetSourceDir} -> ${containerId}:${workdirDest}; restart.`);
49474
+ try {
49475
+ await execFileAsync$1(dockerCmd, [
49476
+ "cp",
49477
+ `${newAssetSourceDir}/.`,
49478
+ `${containerId}:${workdirDest}`
49479
+ ], { maxBuffer: 64 * 1024 * 1024 });
49480
+ } catch (err) {
49481
+ throw new CdkdError(`softReloadAgentContainer: docker cp into '${containerId}:${workdir}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_CP_FAILED");
49482
+ }
49483
+ try {
49484
+ await execFileAsync$1(dockerCmd, ["restart", containerId]);
49485
+ } catch (err) {
49486
+ throw new CdkdError(`softReloadAgentContainer: docker restart of '${containerId}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_RESTART_FAILED");
49487
+ }
49488
+ }
49489
+ function describeExecError(err) {
49490
+ if (!(err instanceof Error)) return String(err);
49491
+ const stderr = err.stderr;
49492
+ const stderrText = typeof stderr === "string" ? stderr.trim() : stderr instanceof Buffer ? stderr.toString("utf8").trim() : "";
49493
+ return stderrText ? `${err.message}\n${stderrText}` : err.message;
49494
+ }
49495
+ /**
49496
+ * Build the per-firing classifier context for the agent container. Mirrors the
49497
+ * ECS service emulator's `loadAssetContextForTarget` shape: returns `undefined`
49498
+ * (and the classifier defaults to `'rebuild'`) when the runtime's image is not
49499
+ * a CDK docker-image asset, or when the asset manifest lookup misses.
49500
+ *
49501
+ * For a CodeConfiguration (`fromCodeAsset`) runtime we treat the bundle's
49502
+ * `codeAssetHash` as the asset hash + the staged source directory as
49503
+ * `newAssetSourceDir`, and synthesize a `Dockerfile` basename that never
49504
+ * matches a real file (the generated Dockerfile lives in a build tmpdir, not
49505
+ * the source tree, so chokidar can't observe an edit to it). A `fromS3` bundle
49506
+ * has no local source tree, so it returns `undefined` and the classifier
49507
+ * defaults to rebuild.
49508
+ *
49509
+ * NOTE: `loadAgentCoreAssetContext` is NOT exported from `cdk-local/internal`
49510
+ * (verified against `node_modules/cdk-local/dist/internal.d.ts`), so this
49511
+ * helper is copied locally; `deriveOldAssetHash` is copied for the same reason.
49512
+ *
49513
+ * @internal — exported for the watch loop's classifier dispatch test.
49514
+ */
49515
+ async function loadAgentCoreAssetContext(args) {
49516
+ const { resolvedTarget, resolved, stacks, cdkOutDir, assetLoader, oldAssetHash } = args;
49517
+ const newCandidate = pickAgentCoreCandidateStack(resolvedTarget, stacks);
49518
+ if (!newCandidate) return void 0;
49519
+ if (resolved.codeArtifact) {
49520
+ if (resolved.codeArtifact.s3Source) return void 0;
49521
+ const manifest = await assetLoader.loadManifest(cdkOutDir, newCandidate.stackName);
49522
+ if (!manifest) return void 0;
49523
+ const asset = assetLoader.getFileAssets(manifest).get(resolved.codeArtifact.codeAssetHash);
49524
+ if (!asset) return void 0;
49525
+ const sourceDir = assetLoader.getAssetSourcePath(cdkOutDir, asset);
49526
+ return {
49527
+ ...oldAssetHash !== void 0 && { oldAssetHash },
49528
+ newAssetHash: resolved.codeArtifact.codeAssetHash,
49529
+ newAssetSourceDir: sourceDir,
49530
+ dockerFile: ".cdkd-agentcore-generated-Dockerfile"
49531
+ };
49532
+ }
49533
+ if (resolved.containerUri === void 0) return void 0;
49534
+ const manifest = await assetLoader.loadManifest(cdkOutDir, newCandidate.stackName);
49535
+ if (!manifest) return void 0;
49536
+ const dockerImageEntry = getDockerImageBySourceHash(manifest, resolved.containerUri);
49537
+ if (!dockerImageEntry) return void 0;
49538
+ const newDockerImage = dockerImageEntry.asset;
49539
+ if (!newDockerImage.source.directory) return void 0;
49540
+ const newAssetSourceDir = resolve(cdkOutDir, newDockerImage.source.directory);
49541
+ return {
49542
+ ...oldAssetHash !== void 0 && { oldAssetHash },
49543
+ newAssetHash: dockerImageEntry.hash,
49544
+ newAssetSourceDir,
49545
+ dockerFile: basename(newDockerImage.source.dockerFile ?? "Dockerfile")
49546
+ };
49547
+ }
49548
+ /**
49549
+ * Derive the OLD (pre-reload) asset hash for the classifier's `oldAssetHash`
49550
+ * field. Code-artifact runtimes carry the hash on the boot-time
49551
+ * `resolved.codeArtifact.codeAssetHash`; container runtimes need a manifest
49552
+ * lookup against the previous-synth stacks to extract it. Returns `undefined`
49553
+ * when the OLD hash can't be derived — the classifier treats `undefined` as
49554
+ * "force rebuild", which is the conservative default.
49555
+ *
49556
+ * NOT exported from `cdk-local/internal`, so copied locally.
49557
+ */
49558
+ async function deriveOldAssetHash(args) {
49559
+ const { resolvedTarget, resolved, stacks, cdkOutDir, assetLoader } = args;
49560
+ if (resolved.codeArtifact) return resolved.codeArtifact.codeAssetHash;
49561
+ if (resolved.containerUri === void 0) return void 0;
49562
+ const candidate = pickAgentCoreCandidateStack(resolvedTarget, stacks);
49563
+ if (!candidate) return void 0;
49564
+ const manifest = await assetLoader.loadManifest(cdkOutDir, candidate.stackName);
49565
+ if (!manifest) return void 0;
49566
+ return getDockerImageBySourceHash(manifest, resolved.containerUri)?.hash;
49567
+ }
49568
+
49283
49569
  //#endregion
49284
49570
  //#region src/cli/commands/local-invoke-agentcore.ts
49285
49571
  /**
@@ -49361,7 +49647,7 @@ async function localInvokeAgentCoreCommand(target, options) {
49361
49647
  noun: "AgentCore Runtimes",
49362
49648
  onMissing: () => new CdkdError(`${getEmbedConfig().cliName} invoke-agentcore requires a <target> (an AgentCore Runtime display path or logical ID). Run \`${getEmbedConfig().cliName} list\` to see them, or run it in a TTY to pick interactively.`, "LOCAL_INVOKE_AGENTCORE_TARGET_REQUIRED")
49363
49649
  });
49364
- const candidate = pickAgentCoreCandidateStack(resolvedTarget, stacks);
49650
+ const candidate = pickAgentCoreCandidateStack$1(resolvedTarget, stacks);
49365
49651
  stateProvider = createLocalStateProvider$1(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region));
49366
49652
  const { context: imageContext, loaded: loadedState } = stateProvider && candidate ? await buildAgentCoreImageContext(candidate, stateProvider, options) : {
49367
49653
  context: void 0,
@@ -49385,28 +49671,24 @@ async function localInvokeAgentCoreCommand(target, options) {
49385
49671
  logger.info(`${resolved.protocol} runtime: invoking the local container's ${pathLabel} directly (vanilla ${resolved.protocol}). An inbound JWT / --bearer-token is an AgentCore managed-plane concern and is not applied locally.`);
49386
49672
  }
49387
49673
  } else authorization = await resolveInboundAuthorization(resolved, options);
49388
- await resolveFromS3BucketIntrinsic(resolved, stateProvider, loadedState, imageContext);
49389
- const image = await resolveAgentCoreImage(resolved, options, loadedState);
49390
- const { env: dockerEnv, sensitiveEnvKeys } = await buildContainerEnv(resolved, options, profileCredentials, profileCredsFile, stateProvider, loadedState, imageContext);
49391
- const hostPort = await pickFreePort();
49674
+ const watchEligible = isAgentCoreWatchEligible(resolved.protocol);
49675
+ const watchActive = options.watch === true && watchEligible;
49676
+ if (options.watch === true && !watchEligible) logger.warn(`--watch is supported only on the HTTP / AGUI protocols; ignoring it for this ${resolved.protocol} runtime (its single shot runs once and exits).`);
49392
49677
  const containerHost = options.containerHost;
49393
- const containerName = `${getEmbedConfig().resourceNamePrefix}-agentcore-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
49394
- const containerPort = isMcp ? MCP_CONTAINER_PORT : isA2a ? A2A_CONTAINER_PORT : void 0;
49395
- const containerPortLabel = isMcp ? `${MCP_CONTAINER_PORT}${MCP_PATH}` : isA2a ? `${A2A_CONTAINER_PORT}${A2A_PATH}` : "8080";
49396
- logger.info(`Starting agent container (image=${image}, port=${hostPort} -> ${containerPortLabel})...`);
49397
- containerId = await runDetached({
49398
- image,
49399
- mounts: [],
49400
- env: dockerEnv,
49401
- cmd: [],
49402
- hostPort,
49403
- host: containerHost,
49404
- platform: options.platform,
49405
- name: containerName,
49406
- ...containerPort !== void 0 && { containerPort },
49407
- ...sensitiveEnvKeys.size > 0 && { sensitiveEnvKeys }
49678
+ const boot = await bootAgentCoreContainer({
49679
+ resolved,
49680
+ options,
49681
+ profileCredentials,
49682
+ profileCredsFile,
49683
+ stateProvider,
49684
+ loadedState,
49685
+ imageContext,
49686
+ isMcp,
49687
+ isA2a
49408
49688
  });
49409
- stopLogs = streamLogs(containerId);
49689
+ containerId = boot.containerId;
49690
+ const hostPort = boot.hostPort;
49691
+ stopLogs = boot.stopLogs;
49410
49692
  sigintHandler = () => {
49411
49693
  cleanup().then(() => process.exit(130));
49412
49694
  };
@@ -49423,19 +49705,145 @@ async function localInvokeAgentCoreCommand(target, options) {
49423
49705
  emitA2aResult(a2a);
49424
49706
  } else if (options.ws) {
49425
49707
  const interactive = process.stdin.isTTY === true;
49426
- await waitForAgentCorePing(containerHost, hostPort);
49427
- const frameSource = interactive ? readStdinLines() : void 0;
49428
- logger.info(interactive ? "Opening the agent /ws WebSocket (interactive — stdin lines = follow-up frames; Ctrl-D to end)..." : "Opening the agent /ws WebSocket and streaming frames...");
49429
- const wsResult = await invokeAgentCoreWs(containerHost, hostPort, event, {
49430
- sessionId,
49431
- timeoutMs: options.timeout,
49432
- onMessage: wrapWsOnMessage((text) => process.stdout.write(text), interactive),
49433
- ...authorization && { authorization },
49434
- ...frameSource && { frameSource }
49708
+ if (watchActive) await runAgentCoreWatchLoop({
49709
+ containerHost,
49710
+ hostPort,
49711
+ options,
49712
+ resolvedTarget,
49713
+ resolved,
49714
+ synthesizer,
49715
+ synthOpts,
49716
+ stacks,
49717
+ invokeOnce: async ({ hostPort: port, abortSignal, firstIteration }) => {
49718
+ const frameSource = interactive ? readStdinLines() : void 0;
49719
+ logger.info(firstIteration ? interactive ? "Opening the agent /ws WebSocket (interactive — stdin lines = follow-up frames; Ctrl-D to end)..." : "Opening the agent /ws WebSocket and streaming frames..." : interactive ? "Re-opening the agent /ws WebSocket (interactive) against the rebuilt container..." : "Re-opening the agent /ws WebSocket against the rebuilt container...");
49720
+ const wsResult = await invokeAgentCoreWs(containerHost, port, event, {
49721
+ sessionId,
49722
+ timeoutMs: options.timeout,
49723
+ onMessage: wrapWsOnMessage((text) => process.stdout.write(text), interactive),
49724
+ abortSignal,
49725
+ ...authorization && { authorization },
49726
+ ...frameSource && { frameSource }
49727
+ });
49728
+ await new Promise((r) => setTimeout(r, 250));
49729
+ emitWsResult(wsResult);
49730
+ return { pendingReload: abortSignal.aborted };
49731
+ },
49732
+ rebuild: async () => {
49733
+ const result = await rebuildAgentCoreContainer({
49734
+ cleanupBefore: async () => {
49735
+ if (stopLogs) {
49736
+ try {
49737
+ stopLogs();
49738
+ } catch {}
49739
+ stopLogs = void 0;
49740
+ }
49741
+ if (containerId) {
49742
+ await removeContainer(containerId);
49743
+ containerId = void 0;
49744
+ }
49745
+ },
49746
+ resolvedTarget,
49747
+ options,
49748
+ synthesizer,
49749
+ synthOpts,
49750
+ profileCredentials,
49751
+ profileCredsFile,
49752
+ stateProvider,
49753
+ isMcp,
49754
+ isA2a
49755
+ });
49756
+ containerId = result.containerId;
49757
+ stopLogs = result.stopLogs;
49758
+ return {
49759
+ containerId: result.containerId,
49760
+ hostPort: result.hostPort,
49761
+ stacks: result.stacks
49762
+ };
49763
+ },
49764
+ softReload: async (newSourceDir) => {
49765
+ if (!containerId) throw new CdkdError("softReload: no live container to docker cp / docker restart into.", "LOCAL_INVOKE_AGENTCORE_WATCH_NO_CONTAINER");
49766
+ await softReloadAgentContainer(containerId, newSourceDir);
49767
+ const { stacks: newStacks } = await synthesizer.synthesize(synthOpts);
49768
+ return { stacks: newStacks };
49769
+ }
49435
49770
  });
49436
- await new Promise((r) => setTimeout(r, 250));
49437
- emitWsResult(wsResult);
49438
- } else {
49771
+ else {
49772
+ await waitForAgentCorePing(containerHost, hostPort);
49773
+ const frameSource = interactive ? readStdinLines() : void 0;
49774
+ logger.info(interactive ? "Opening the agent /ws WebSocket (interactive — stdin lines = follow-up frames; Ctrl-D to end)..." : "Opening the agent /ws WebSocket and streaming frames...");
49775
+ const wsResult = await invokeAgentCoreWs(containerHost, hostPort, event, {
49776
+ sessionId,
49777
+ timeoutMs: options.timeout,
49778
+ onMessage: wrapWsOnMessage((text) => process.stdout.write(text), interactive),
49779
+ ...authorization && { authorization },
49780
+ ...frameSource && { frameSource }
49781
+ });
49782
+ await new Promise((r) => setTimeout(r, 250));
49783
+ emitWsResult(wsResult);
49784
+ }
49785
+ } else if (watchActive) await runAgentCoreWatchLoop({
49786
+ containerHost,
49787
+ hostPort,
49788
+ options,
49789
+ resolvedTarget,
49790
+ resolved,
49791
+ synthesizer,
49792
+ synthOpts,
49793
+ stacks,
49794
+ invokeOnce: async ({ hostPort: port, abortSignal }) => {
49795
+ const additionalHeaders = await buildSigV4HeadersIfRequested(options, resolved, loadedState, containerHost, port, event, sessionId);
49796
+ const result = await invokeAgentCore(containerHost, port, event, {
49797
+ sessionId,
49798
+ timeoutMs: options.timeout,
49799
+ onChunk: (text) => process.stdout.write(text),
49800
+ ...authorization && { authorization },
49801
+ ...additionalHeaders && { additionalHeaders }
49802
+ });
49803
+ await new Promise((r) => setTimeout(r, 250));
49804
+ emitResult(result);
49805
+ return { pendingReload: abortSignal.aborted };
49806
+ },
49807
+ rebuild: async () => {
49808
+ const result = await rebuildAgentCoreContainer({
49809
+ cleanupBefore: async () => {
49810
+ if (stopLogs) {
49811
+ try {
49812
+ stopLogs();
49813
+ } catch {}
49814
+ stopLogs = void 0;
49815
+ }
49816
+ if (containerId) {
49817
+ await removeContainer(containerId);
49818
+ containerId = void 0;
49819
+ }
49820
+ },
49821
+ resolvedTarget,
49822
+ options,
49823
+ synthesizer,
49824
+ synthOpts,
49825
+ profileCredentials,
49826
+ profileCredsFile,
49827
+ stateProvider,
49828
+ isMcp,
49829
+ isA2a
49830
+ });
49831
+ containerId = result.containerId;
49832
+ stopLogs = result.stopLogs;
49833
+ return {
49834
+ containerId: result.containerId,
49835
+ hostPort: result.hostPort,
49836
+ stacks: result.stacks
49837
+ };
49838
+ },
49839
+ softReload: async (newSourceDir) => {
49840
+ if (!containerId) throw new CdkdError("softReload: no live container to docker cp / docker restart into.", "LOCAL_INVOKE_AGENTCORE_WATCH_NO_CONTAINER");
49841
+ await softReloadAgentContainer(containerId, newSourceDir);
49842
+ const { stacks: newStacks } = await synthesizer.synthesize(synthOpts);
49843
+ return { stacks: newStacks };
49844
+ }
49845
+ });
49846
+ else {
49439
49847
  await waitForAgentCorePing(containerHost, hostPort);
49440
49848
  const additionalHeaders = await buildSigV4HeadersIfRequested(options, resolved, loadedState, containerHost, hostPort, event, sessionId);
49441
49849
  const result = await invokeAgentCore(containerHost, hostPort, event, {
@@ -49454,6 +49862,102 @@ async function localInvokeAgentCoreCommand(target, options) {
49454
49862
  }
49455
49863
  }
49456
49864
  /**
49865
+ * `--watch` is supported only on the long-running HTTP / AGUI paths (the
49866
+ * `--ws` session AND the default one-shot `POST /invocations`, which the
49867
+ * reload re-runs). The MCP `POST /mcp` and A2A `POST /` paths run once and
49868
+ * exit with no reconnect surface, so `--watch` is a no-op WARN for them.
49869
+ *
49870
+ * Exported so a unit test can lock the eligibility contract without driving
49871
+ * the full synth + docker pipeline.
49872
+ */
49873
+ function isAgentCoreWatchEligible(protocol) {
49874
+ return protocol !== AGENTCORE_MCP_PROTOCOL && protocol !== AGENTCORE_A2A_PROTOCOL;
49875
+ }
49876
+ /**
49877
+ * Cold-boot (and rebuild) the agent container from an already-resolved runtime
49878
+ * descriptor: resolve an intrinsic fromS3 bucket, resolve the image, build the
49879
+ * container env, pick a free port, `docker run`, and start the log stream.
49880
+ *
49881
+ * Extracted from the main command so the `--watch` rebuild callback can re-run
49882
+ * the identical sequence against a fresh synth. The non-watch one-shot / `--ws`
49883
+ * paths and the watch loop both go through this helper, so the container boot
49884
+ * sequence is single-sourced.
49885
+ *
49886
+ * Exported so a unit test can drive the boot shape without the full command.
49887
+ */
49888
+ async function bootAgentCoreContainer(args) {
49889
+ const logger = getLogger();
49890
+ const { resolved, options, profileCredentials, profileCredsFile, stateProvider, loadedState, imageContext, isMcp, isA2a } = args;
49891
+ await resolveFromS3BucketIntrinsic(resolved, stateProvider, loadedState, imageContext);
49892
+ const image = await resolveAgentCoreImage(resolved, options, loadedState);
49893
+ const { env: dockerEnv, sensitiveEnvKeys } = await buildContainerEnv(resolved, options, profileCredentials, profileCredsFile, stateProvider, loadedState, imageContext);
49894
+ const hostPort = await pickFreePort();
49895
+ const containerHost = options.containerHost;
49896
+ const containerName = `${getEmbedConfig().resourceNamePrefix}-agentcore-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
49897
+ const containerPort = isMcp ? MCP_CONTAINER_PORT : isA2a ? A2A_CONTAINER_PORT : void 0;
49898
+ const containerPortLabel = isMcp ? `${MCP_CONTAINER_PORT}${MCP_PATH}` : isA2a ? `${A2A_CONTAINER_PORT}${A2A_PATH}` : "8080";
49899
+ logger.info(`Starting agent container (image=${image}, port=${hostPort} -> ${containerPortLabel})...`);
49900
+ const containerId = await runDetached({
49901
+ image,
49902
+ mounts: [],
49903
+ env: dockerEnv,
49904
+ cmd: [],
49905
+ hostPort,
49906
+ host: containerHost,
49907
+ platform: options.platform,
49908
+ name: containerName,
49909
+ ...containerPort !== void 0 && { containerPort },
49910
+ ...sensitiveEnvKeys.size > 0 && { sensitiveEnvKeys }
49911
+ });
49912
+ let stopLogs;
49913
+ try {
49914
+ stopLogs = streamLogs(containerId);
49915
+ } catch (err) {
49916
+ await removeContainer(containerId).catch(() => {});
49917
+ throw err;
49918
+ }
49919
+ return {
49920
+ containerId,
49921
+ hostPort,
49922
+ stopLogs
49923
+ };
49924
+ }
49925
+ /**
49926
+ * Rebuild the agent container for the `--watch` loop: tear down the OLD
49927
+ * container (via the supplied `cleanupBefore`), re-synth the CDK app, re-pick
49928
+ * the candidate stack + re-resolve the runtime (so a new asset hash / env /
49929
+ * image is picked up), then re-run {@link bootAgentCoreContainer}. The inbound
49930
+ * auth (bearer token / OIDC discovery URL) is NOT re-resolved — it is stable
49931
+ * across a CDK source edit, so the caller threads its boot-time `authorization`
49932
+ * back into the next invocation.
49933
+ *
49934
+ * Exported so a unit test can drive the rebuild shape without the full command.
49935
+ */
49936
+ async function rebuildAgentCoreContainer(args) {
49937
+ const { cleanupBefore, resolvedTarget, options, synthesizer, synthOpts, profileCredentials, profileCredsFile, stateProvider, isMcp, isA2a } = args;
49938
+ await cleanupBefore();
49939
+ const { stacks: newStacks } = await synthesizer.synthesize(synthOpts);
49940
+ const newCandidate = pickAgentCoreCandidateStack$1(resolvedTarget, newStacks);
49941
+ const { context: newImageContext, loaded: newLoaded } = stateProvider && newCandidate ? await buildAgentCoreImageContext(newCandidate, stateProvider, options) : {
49942
+ context: void 0,
49943
+ loaded: void 0
49944
+ };
49945
+ return {
49946
+ ...await bootAgentCoreContainer({
49947
+ resolved: resolveAgentCoreTarget(resolvedTarget, newStacks, newImageContext),
49948
+ options,
49949
+ profileCredentials,
49950
+ profileCredsFile,
49951
+ stateProvider,
49952
+ loadedState: newLoaded,
49953
+ imageContext: newImageContext,
49954
+ isMcp,
49955
+ isA2a
49956
+ }),
49957
+ stacks: newStacks
49958
+ };
49959
+ }
49960
+ /**
49457
49961
  * Enforce the runtime's inbound JWT authorizer (when declared) and return
49458
49962
  * the `Authorization` header to forward to `/invocations`.
49459
49963
  *
@@ -50107,7 +50611,7 @@ function readEnvOverridesFile$1(filePath) {
50107
50611
  return parsed;
50108
50612
  }
50109
50613
  function createLocalInvokeAgentCoreCommand() {
50110
- const cmd = new Command("invoke-agentcore").description("Run a Bedrock AgentCore Runtime container locally and invoke it once over its protocol contract: HTTP (POST /invocations + GET /ping on 8080) or MCP (POST /mcp Streamable HTTP on 8000). Resolves the AWS::BedrockAgentCore::Runtime, pulls/builds its container, injects env vars + AWS credentials, and prints the response. For an MCP runtime, runs the session handshake then sends one JSON-RPC request (tools/list by default, or the method/params from --event). Target accepts a CDK display path (MyStack/MyAgent) or stack-qualified logical ID (MyStack:MyAgentRuntime1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick from a list. Supports the container artifact and the CodeConfiguration managed-runtime artifact (fromCodeAsset, built from source) on the HTTP + MCP protocols; the agent calls real AWS for managed services.").argument("[target]", "CDK display path or stack-qualified logical ID of the AgentCore Runtime to invoke (omit to pick interactively in a TTY)").addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--session-id <id>", "AgentCore runtime session id header value (default: a random UUID)")).addOption(new Option("--ws", "Stream over the HTTP-protocol agent's bidirectional /ws WebSocket endpoint (on 8080) instead of POST /invocations: send --event as the first frame and print every received frame to stdout until the agent closes. When stdin is a TTY, auto-enters a REPL — each typed line is sent as a follow-up text frame until Ctrl-D / agent close; pipe from /dev/null to force one-shot in a TTY. Ignored for an MCP runtime.").default(false)).addOption(new Option("--bearer-token <jwt>", "Bearer JWT to present when the runtime declares a customJwtAuthorizer. Verified against the runtime OIDC discovery URL (signature / issuer / expiry / audience) before the container starts, then forwarded to /invocations as Authorization: Bearer <jwt>.")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign the /invocations POST with AWS SigV4 (service bedrock-agentcore) using the resolved credentials, matching the cloud default when the runtime declares no customJwtAuthorizer. Opt-in: default unsigned. Mutually exclusive with --bearer-token; ignored on a JWT-protected runtime.").default(false)).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64)").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host to bind the agent port to").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Per-request timeout in milliseconds. Applied to POST /invocations, POST /mcp, and the /ws open-to-close window. Raise this for long-running agent calls that exceed the default.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container so the agent runs with the deployed role. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's RoleArn when it is a literal ARN; (3) `--no-assume-role` opts out. Off by default — the developer's shell credentials are forwarded unchanged.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-state", "Load cdkd's S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::ImportValue in env vars with the deployed physical IDs / cross-stack exports. Mutually exclusive with --from-cfn-stack.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. For CDK apps deployed via the upstream CDK CLI. Bare form uses the resolved stack name; pass an explicit value when the CFn stack name differs. Mutually exclusive with --from-state.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).action(withErrorHandling(async (target, options) => {
50614
+ const cmd = new Command("invoke-agentcore").description("Run a Bedrock AgentCore Runtime container locally and invoke it once over its protocol contract: HTTP (POST /invocations + GET /ping on 8080) or MCP (POST /mcp Streamable HTTP on 8000). Resolves the AWS::BedrockAgentCore::Runtime, pulls/builds its container, injects env vars + AWS credentials, and prints the response. For an MCP runtime, runs the session handshake then sends one JSON-RPC request (tools/list by default, or the method/params from --event). Target accepts a CDK display path (MyStack/MyAgent) or stack-qualified logical ID (MyStack:MyAgentRuntime1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick from a list. Supports the container artifact and the CodeConfiguration managed-runtime artifact (fromCodeAsset, built from source) on the HTTP + MCP protocols; the agent calls real AWS for managed services.").argument("[target]", "CDK display path or stack-qualified logical ID of the AgentCore Runtime to invoke (omit to pick interactively in a TTY)").addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--session-id <id>", "AgentCore runtime session id header value (default: a random UUID)")).addOption(new Option("--ws", "Stream over the HTTP-protocol agent's bidirectional /ws WebSocket endpoint (on 8080) instead of POST /invocations: send --event as the first frame and print every received frame to stdout until the agent closes. When stdin is a TTY, auto-enters a REPL — each typed line is sent as a follow-up text frame until Ctrl-D / agent close; pipe from /dev/null to force one-shot in a TTY. Ignored for an MCP runtime.").default(false)).addOption(new Option("--watch", "Re-synth and reload the agent container on CDK source changes (follows cdk-local #270). Supported on the HTTP / AGUI protocols, both the --ws session AND the default one-shot POST /invocations (the reload re-runs the single shot). An interpreted-language source edit inside a CodeConfiguration source tree takes a soft-reload fast path (docker cp + docker restart, no rebuild); Dockerfile / compiled-source / asset-hash-changed / ambiguous edits force a full rebuild. For MCP / A2A runtimes --watch is a no-op WARN and the single shot proceeds.").default(false)).addOption(new Option("--bearer-token <jwt>", "Bearer JWT to present when the runtime declares a customJwtAuthorizer. Verified against the runtime OIDC discovery URL (signature / issuer / expiry / audience) before the container starts, then forwarded to /invocations as Authorization: Bearer <jwt>.")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign the /invocations POST with AWS SigV4 (service bedrock-agentcore) using the resolved credentials, matching the cloud default when the runtime declares no customJwtAuthorizer. Opt-in: default unsigned. Mutually exclusive with --bearer-token; ignored on a JWT-protected runtime.").default(false)).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64)").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host to bind the agent port to").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Per-request timeout in milliseconds. Applied to POST /invocations, POST /mcp, and the /ws open-to-close window. Raise this for long-running agent calls that exceed the default.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container so the agent runs with the deployed role. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's RoleArn when it is a literal ARN; (3) `--no-assume-role` opts out. Off by default — the developer's shell credentials are forwarded unchanged.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-state", "Load cdkd's S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::ImportValue in env vars with the deployed physical IDs / cross-stack exports. Mutually exclusive with --from-cfn-stack.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. For CDK apps deployed via the upstream CDK CLI. Bare form uses the resolved stack name; pass an explicit value when the CFn stack name differs. Mutually exclusive with --from-state.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).action(withErrorHandling(async (target, options) => {
50111
50615
  await localInvokeAgentCoreCommand(target, options);
50112
50616
  }));
50113
50617
  [
@@ -52053,7 +52557,7 @@ function reorderArgs(argv) {
52053
52557
  */
52054
52558
  async function main() {
52055
52559
  const program = new Command();
52056
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.206.0");
52560
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.207.0");
52057
52561
  program.addCommand(createBootstrapCommand());
52058
52562
  program.addCommand(createSynthCommand());
52059
52563
  program.addCommand(createListCommand());