cdk-local 0.153.0 → 0.153.2

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.
@@ -74,6 +74,36 @@ function hostFromAuthority(authority) {
74
74
  return authority.slice(0, firstColon);
75
75
  }
76
76
 
77
+ //#endregion
78
+ //#region src/utils/loopback-host.ts
79
+ function normaliseHostLiteral(hostname) {
80
+ return hostname.trim().replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
81
+ }
82
+ function isLoopbackLiteral(h) {
83
+ if (h === "localhost") return true;
84
+ if (h === "::1" || h === "0:0:0:0:0:0:0:1") return true;
85
+ const mapped = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(h);
86
+ if (mapped) return parseInt(mapped[1], 16) >> 8 === 127;
87
+ const dotted = /^(?:::ffff:)?(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h);
88
+ if (!dotted) return false;
89
+ const octets = dotted.slice(1).map(Number);
90
+ if (octets.some((o) => o > 255)) return false;
91
+ return octets[0] === 127;
92
+ }
93
+ function isWildcardLiteral(h) {
94
+ return h === "0.0.0.0" || h === "::" || h === "0:0:0:0:0:0:0:0" || h === "::ffff:0.0.0.0" || h === "::ffff:0:0";
95
+ }
96
+ function isLoopbackHostname(hostname) {
97
+ return isLoopbackLiteral(normaliseHostLiteral(hostname));
98
+ }
99
+ function isWildcardHostname(hostname) {
100
+ return isWildcardLiteral(normaliseHostLiteral(hostname));
101
+ }
102
+ function isLoopbackHost(hostname) {
103
+ const h = normaliseHostLiteral(hostname).replace(/\.$/, "");
104
+ return h.endsWith(".localhost") || isLoopbackLiteral(h) || isWildcardLiteral(h);
105
+ }
106
+
77
107
  //#endregion
78
108
  //#region src/utils/aws-proxy.ts
79
109
  const AGENT_OPTS = {
@@ -81,8 +111,23 @@ const AGENT_OPTS = {
81
111
  maxSockets: 50
82
112
  };
83
113
  function isProxyEnvConfigured() {
84
- const env = process.env;
85
- return Boolean(env["https_proxy"] || env["HTTPS_PROXY"] || env["http_proxy"] || env["HTTP_PROXY"] || env["all_proxy"] || env["ALL_PROXY"]);
114
+ return PROXY_ENV_VARIABLES.some((key) => Boolean(process.env[key]));
115
+ }
116
+ const PROXY_ENV_VARIABLES = [
117
+ "https_proxy",
118
+ "HTTPS_PROXY",
119
+ "http_proxy",
120
+ "HTTP_PROXY",
121
+ "all_proxy",
122
+ "ALL_PROXY"
123
+ ];
124
+ function isSpeakableProxyEnvValue(value) {
125
+ return !value.includes("://") || isSpeakableProxy(value.trim());
126
+ }
127
+ const AWS_ENDPOINT_PROBES = ["https://sts.amazonaws.com", "http://sts.amazonaws.com"];
128
+ function everySetProxyEnvValueIsUnspeakable() {
129
+ const set = PROXY_ENV_VARIABLES.map((key) => process.env[key] ?? "").filter((v) => v !== "");
130
+ return set.length > 0 && !set.some(isSpeakableProxyEnvValue);
86
131
  }
87
132
  const warnedProxySchemes = /* @__PURE__ */ new Set();
88
133
  function proxySchemeOf(proxyUrl) {
@@ -96,7 +141,7 @@ function warnUnspeakableProxy(proxyUrl) {
96
141
  const scheme = proxySchemeOf(proxyUrl);
97
142
  if (warnedProxySchemes.has(scheme)) return;
98
143
  try {
99
- getLogger().child("aws-proxy").warn(`Unsupported proxy scheme "${scheme}" — cdk-local speaks HTTP CONNECT proxies only, so this request went DIRECT instead of through the proxy. Set HTTPS_PROXY / HTTP_PROXY / ALL_PROXY to an http:// or https:// proxy URL. (The proxy URL is withheld here: it can carry credentials.)`);
144
+ getLogger().child("aws-proxy").warn(`Unsupported proxy scheme "${scheme}" — cdk-local speaks HTTP CONNECT proxies only, so requests it would carry go DIRECT instead of through the proxy. Set HTTPS_PROXY / HTTP_PROXY / ALL_PROXY to an http:// or https:// proxy URL. (The proxy URL is withheld here: it can carry credentials.)`);
100
145
  } catch {
101
146
  return;
102
147
  }
@@ -147,6 +192,10 @@ function buildProxyRequestHandler() {
147
192
  }
148
193
  function buildProxyClientConfig(opts = {}) {
149
194
  if (!isProxyEnvConfigured()) return {};
195
+ if (everySetProxyEnvValueIsUnspeakable()) {
196
+ for (const probe of AWS_ENDPOINT_PROBES) resolveProxyForTarget(probe);
197
+ return {};
198
+ }
150
199
  const profile = opts.profile;
151
200
  let chain;
152
201
  const credentials = async (identityProperties) => {
@@ -177,19 +226,6 @@ const REQUEST_STALL_TIMEOUT_MS = 3e5;
177
226
  function isRedirectStatus(status) {
178
227
  return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
179
228
  }
180
- function isLoopbackHost(hostname) {
181
- const host = hostname.trim().replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
182
- if (host === "localhost" || host.endsWith(".localhost")) return true;
183
- if (host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
184
- if (host === "0.0.0.0" || host === "::" || host === "0:0:0:0:0:0:0:0" || host === "::ffff:0.0.0.0" || host === "::ffff:0:0") return true;
185
- const mapped = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(host);
186
- if (mapped) return parseInt(mapped[1], 16) >> 8 === 127;
187
- const dotted = /^(?:::ffff:)?(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
188
- if (!dotted) return false;
189
- const octets = dotted.slice(1).map(Number);
190
- if (octets.some((o) => o > 255)) return false;
191
- return octets[0] === 127;
192
- }
193
229
  function parseHttpUrl(href) {
194
230
  const parsed = new URL(href);
195
231
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new TypeError(`Unsupported protocol for a proxied request: "${parsed.protocol}"`);
@@ -476,6 +512,15 @@ function deployAccountStsClientConfig(args) {
476
512
  credentials
477
513
  };
478
514
  }
515
+ function accountLookupStsClientConfig(args) {
516
+ return args.readFromCfnStack ? workloadStsClientConfig({
517
+ region: args.region,
518
+ profile: args.profile
519
+ }) : deployAccountStsClientConfig({
520
+ region: args.region,
521
+ profile: args.profile
522
+ });
523
+ }
479
524
  function applyWorkloadEnvCredentials(env) {
480
525
  const rec = callerRecord();
481
526
  if (!rec) return "unchanged";
@@ -13346,9 +13391,10 @@ async function resolvePseudoParametersForStartApi(stateRegion, options) {
13346
13391
  let accountId;
13347
13392
  try {
13348
13393
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
13349
- const sts = new STSClient(deployAccountStsClientConfig({
13394
+ const sts = new STSClient(accountLookupStsClientConfig({
13350
13395
  region,
13351
- profile: options.profile
13396
+ profile: options.profile,
13397
+ readFromCfnStack: isCfnFlagPresent(options)
13352
13398
  }));
13353
13399
  try {
13354
13400
  accountId = (await sts.send(new GetCallerIdentityCommand({}))).Account;
@@ -13731,9 +13777,10 @@ async function resolvePseudoParametersForInvoke(stackRegion, options) {
13731
13777
  let accountId;
13732
13778
  try {
13733
13779
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
13734
- const sts = new STSClient(deployAccountStsClientConfig({
13780
+ const sts = new STSClient(accountLookupStsClientConfig({
13735
13781
  region,
13736
- profile: options.profile
13782
+ profile: options.profile,
13783
+ readFromCfnStack: isCfnFlagPresent(options)
13737
13784
  }));
13738
13785
  try {
13739
13786
  accountId = (await sts.send(new GetCallerIdentityCommand({}))).Account;
@@ -15389,7 +15436,7 @@ async function buildAgentCoreImageContext(candidate, stateProvider, options) {
15389
15436
  const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
15390
15437
  let accountId;
15391
15438
  try {
15392
- accountId = await resolveCallerAccountId$2(region, options.profile);
15439
+ accountId = await resolveCallerAccountId$2(region, options.profile, isCfnFlagPresent(options));
15393
15440
  } catch (err) {
15394
15441
  logger.warn(`--from-cfn-stack: STS GetCallerIdentity failed: ${describeAwsFailureForWarn(err, "STS GetCallerIdentity")}. A same-stack ECR image URI referencing \${AWS::AccountId} may not resolve.`);
15395
15442
  }
@@ -15413,11 +15460,12 @@ async function buildAgentCoreImageContext(candidate, stateProvider, options) {
15413
15460
  loaded: loaded ?? void 0
15414
15461
  };
15415
15462
  }
15416
- async function resolveCallerAccountId$2(region, profile) {
15463
+ async function resolveCallerAccountId$2(region, profile, readFromCfnStack) {
15417
15464
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
15418
- const sts = new STSClient(deployAccountStsClientConfig({
15465
+ const sts = new STSClient(accountLookupStsClientConfig({
15419
15466
  region,
15420
- profile
15467
+ profile,
15468
+ readFromCfnStack
15421
15469
  }));
15422
15470
  try {
15423
15471
  return (await sts.send(new GetCallerIdentityCommand({}))).Account;
@@ -15893,7 +15941,7 @@ function addInvokeAgentCoreSpecificOptions(cmd) {
15893
15941
 
15894
15942
  //#endregion
15895
15943
  //#region src/local/container-log-streamer.ts
15896
- function attachContainerLogStreamer(prefix, containerId) {
15944
+ function attachContainerLogStreamer(prefix, containerId, opts = {}) {
15897
15945
  let stopping = false;
15898
15946
  let current;
15899
15947
  let stdoutBuf = "";
@@ -15928,23 +15976,35 @@ function attachContainerLogStreamer(prefix, containerId) {
15928
15976
  return;
15929
15977
  }
15930
15978
  reattachCount += 1;
15931
- execFile(getDockerCmd(), [
15932
- "inspect",
15933
- "--format",
15934
- "{{.State.Status}}",
15935
- containerId
15936
- ], (err, stdout) => {
15937
- if (stopping) return;
15938
- if (!err) {
15939
- const status = stdout.trim().toLowerCase();
15940
- if (status === "exited" || status === "dead" || status === "removing") return;
15979
+ considerRespawn();
15980
+ });
15981
+ };
15982
+ const considerRespawn = () => {
15983
+ execFile(getDockerCmd(), [
15984
+ "inspect",
15985
+ "--format",
15986
+ "{{.State.Status}}",
15987
+ containerId
15988
+ ], (err, stdout) => {
15989
+ if (stopping) return;
15990
+ if (!err) {
15991
+ const status = stdout.trim().toLowerCase();
15992
+ if (status === "exited" || status === "dead" || status === "removing") {
15993
+ if (opts.expectRestart?.()) {
15994
+ pendingRespawn = setTimeout(() => {
15995
+ pendingRespawn = void 0;
15996
+ if (!stopping) considerRespawn();
15997
+ }, 200);
15998
+ return;
15999
+ }
16000
+ return;
15941
16001
  }
15942
- pendingRespawn = setTimeout(() => {
15943
- pendingRespawn = void 0;
15944
- if (stopping) return;
15945
- spawnOnce("0s");
15946
- }, 200);
15947
- });
16002
+ }
16003
+ pendingRespawn = setTimeout(() => {
16004
+ pendingRespawn = void 0;
16005
+ if (stopping) return;
16006
+ spawnOnce("0s");
16007
+ }, 200);
15948
16008
  });
15949
16009
  };
15950
16010
  spawnOnce(void 0);
@@ -16430,6 +16490,7 @@ async function runEcsTask(task, options, state) {
16430
16490
  ...options.ephemeralPublishContainerPorts && options.ephemeralPublishContainerPorts.length > 0 ? { ephemeralPublishContainerPorts: options.ephemeralPublishContainerPorts } : {},
16431
16491
  ...mergedAddHostFlags.length > 0 ? { addHostFlags: mergedAddHostFlags } : {},
16432
16492
  ...(options.networkAliasesByContainer?.get(container.name)?.length ?? 0) > 0 ? { networkAliases: options.networkAliasesByContainer.get(container.name) } : {},
16493
+ ...options.containerNameAliasOmitted?.has(container.name) ? { omitContainerNameAlias: true } : {},
16433
16494
  ...options.profileCredentialsFile && { profileCredentialsFile: options.profileCredentialsFile }
16434
16495
  });
16435
16496
  dockerCmds.set(container.name, {
@@ -16745,7 +16806,7 @@ function buildDockerRunArgs(opts) {
16745
16806
  const publishedEndpoints = [];
16746
16807
  args.push("--name", `${getEmbedConfig().resourceNamePrefix}-${task.family}-${container.name}-${randHex(3)}`);
16747
16808
  args.push("--network", network);
16748
- args.push("--network-alias", container.name);
16809
+ if (!opts.omitContainerNameAlias) args.push("--network-alias", container.name);
16749
16810
  if (opts.networkAliases && opts.networkAliases.length > 0) {
16750
16811
  const seen = /* @__PURE__ */ new Set([container.name]);
16751
16812
  for (const a of opts.networkAliases) if (!seen.has(a)) {
@@ -17111,6 +17172,7 @@ var EcsServiceRunnerError = class EcsServiceRunnerError extends Error {
17111
17172
  }
17112
17173
  };
17113
17174
  const SOFT_RELOAD_COMPLETION_LOG_SUFFIX = "restart + TCP-ready probe complete; Cloud Map + front-door re-published.";
17175
+ const SOFT_RELOAD_PROBE_TIMEOUT_LOG_SUFFIX = "restarted, but the TCP-ready probe timed out; Cloud Map + front-door re-published anyway.";
17114
17176
  function createServiceRunState() {
17115
17177
  return {
17116
17178
  replicas: [],
@@ -17237,7 +17299,7 @@ function buildNetworkAliasesByContainer(service) {
17237
17299
  }
17238
17300
  return out;
17239
17301
  }
17240
- async function bootReplica(service, options, instance) {
17302
+ async function bootReplica(service, options, instance, bootOpts = {}) {
17241
17303
  const logger = getLogger().child("ecs-service");
17242
17304
  const gen = instance.generation;
17243
17305
  const genSuffix = gen > 0 ? `-g${gen}` : "";
@@ -17249,6 +17311,18 @@ async function bootReplica(service, options, instance) {
17249
17311
  const networkAliasesByContainer = buildNetworkAliasesByContainer(service);
17250
17312
  const skipHostPortPublish = computeReplicaCount(service.desiredCount, options.maxTasks) > 1;
17251
17313
  const ephemeralPublishContainerPorts = options.frontDoor ? [...new Set(options.frontDoor.pools.map((p) => p.targetContainerPort))] : [];
17314
+ const withheldAliasContainers = [];
17315
+ if (bootOpts.deferPublish && sharedNetwork) for (const container of service.task.containers) {
17316
+ if (!networkAliasesByContainer.has(container.name)) continue;
17317
+ if (containerPublishesHostPorts(container, {
17318
+ skipHostPortPublish,
17319
+ ephemeralPublishContainerPorts
17320
+ })) continue;
17321
+ networkAliasesByContainer.delete(container.name);
17322
+ withheldAliasContainers.push(container.name);
17323
+ }
17324
+ const containerNameAliasOmitted = new Set(withheldAliasContainers.filter((name) => (buildNetworkAliasesByContainer(service).get(name) ?? []).includes(name)));
17325
+ instance.withheldAliasContainers = withheldAliasContainers;
17252
17326
  const perReplicaTaskOptions = {
17253
17327
  ...options.taskOptions,
17254
17328
  cluster: perReplicaCluster,
@@ -17257,17 +17331,62 @@ async function bootReplica(service, options, instance) {
17257
17331
  ...sharedNetwork ? { existingNetwork: sharedNetwork } : { subnetOctet: pickSubnetOctet(instance.index) },
17258
17332
  ...addHostFlags.length > 0 ? { addHostFlags } : {},
17259
17333
  ...networkAliasesByContainer.size > 0 ? { networkAliasesByContainer } : {},
17334
+ ...containerNameAliasOmitted.size > 0 ? { containerNameAliasOmitted } : {},
17260
17335
  ...ephemeralPublishContainerPorts.length > 0 ? { ephemeralPublishContainerPorts } : {}
17261
17336
  };
17262
17337
  logger.info(`Booting replica ${instance.index} (${perReplicaCluster})`);
17263
17338
  await runEcsTask(service.task, perReplicaTaskOptions, instance.state);
17264
17339
  if (options.streamLogs !== false) for (const started of instance.state.startedContainers) {
17265
17340
  const prefix = `[svc=${service.serviceDisplayName} r=${instance.index} c=${started.name}] `;
17266
- instance.state.logStoppers.push(attachContainerLogStreamer(prefix, started.id));
17341
+ instance.state.logStoppers.push(attachContainerLogStreamer(prefix, started.id, { expectRestart: () => instance.softReloadInProgress === true }));
17267
17342
  }
17343
+ if (options.discovery && !bootOpts.deferPublish) await publishReplicaToCloudMap(service, instance, options.discovery, ownerKeyPrefix);
17344
+ if (options.frontDoor && !bootOpts.deferPublish) await publishReplicaToFrontDoor(service, instance, options.frontDoor, options.taskOptions.containerHost, ownerKeyPrefix);
17345
+ instance.lastDeployedAssetHash = pickEssentialAssetHash(service);
17346
+ }
17347
+ function containerPublishesHostPorts(container, opts) {
17348
+ if (container.portMappings.length === 0) return false;
17349
+ if (!opts.skipHostPortPublish) return true;
17350
+ return container.portMappings.some((pm) => opts.ephemeralPublishContainerPorts.includes(pm.containerPort));
17351
+ }
17352
+ async function publishGatedReplica(service, options, instance, deadline, isShuttingDown, label = `Shadow replica r${instance.index} (gen ${instance.generation})`) {
17353
+ const network = instance.state.network;
17354
+ const withheld = instance.withheldAliasContainers ?? [];
17355
+ if (network && withheld.length > 0) {
17356
+ const fullAliases = buildNetworkAliasesByContainer(service);
17357
+ for (const name of withheld) {
17358
+ if (isShuttingDown()) return;
17359
+ const container = service.task.containers.find((c) => c.name === name);
17360
+ const started = instance.state.startedContainers.find((c) => c.name === name);
17361
+ if (!container || !started) continue;
17362
+ const port = container.portMappings[0]?.containerPort;
17363
+ if (port !== void 0) {
17364
+ if (await waitForContainerListening(started.id, port, deadline) === "exited") {
17365
+ getLogger().child("ecs-service").debug(`${label}: '${name}' exited before listening; leaving its aliases withheld.`);
17366
+ continue;
17367
+ }
17368
+ }
17369
+ if (isShuttingDown()) return;
17370
+ const ip = await getContainerNetworkIp(started.id, network.networkName);
17371
+ if (!ip) throw new EcsServiceRunnerError(`${label}: no docker IP for '${name}' on ${network.networkName}; cannot attach its Service Connect aliases.`);
17372
+ const aliases = [name];
17373
+ for (const a of fullAliases.get(name) ?? []) if (!aliases.includes(a)) aliases.push(a);
17374
+ await dockerNetworkDisconnectImpl(network.networkName, started.id);
17375
+ await dockerNetworkConnectImpl(network.networkName, started.id, {
17376
+ ip,
17377
+ aliases
17378
+ });
17379
+ }
17380
+ instance.withheldAliasContainers = [];
17381
+ }
17382
+ if (isShuttingDown()) return;
17383
+ const ownerKeyPrefix = replicaOwnerKeyPrefix(service, instance);
17268
17384
  if (options.discovery) await publishReplicaToCloudMap(service, instance, options.discovery, ownerKeyPrefix);
17269
17385
  if (options.frontDoor) await publishReplicaToFrontDoor(service, instance, options.frontDoor, options.taskOptions.containerHost, ownerKeyPrefix);
17270
- instance.lastDeployedAssetHash = pickEssentialAssetHash(service);
17386
+ }
17387
+ function replicaOwnerKeyPrefix(service, instance) {
17388
+ const genSuffix = instance.generation > 0 ? `:g${instance.generation}` : "";
17389
+ return `${service.serviceLogicalId}:r${instance.index}${genSuffix}`;
17271
17390
  }
17272
17391
  function pickEssentialAssetHash(service) {
17273
17392
  const essential = service.task.containers.find((c) => c.essential) ?? service.task.containers[0];
@@ -17421,11 +17540,14 @@ async function rollServiceReplica(args) {
17421
17540
  if (oldIdx !== -1) controller.runState.replicas.splice(oldIdx, 1);
17422
17541
  } else logger.info(`Rolling replica ${shadow.index} (gen ${shadow.generation}): booting shadow before retiring old.`);
17423
17542
  const bootPromise = (async () => {
17424
- await bootReplica(newService, newOptions, shadow);
17543
+ await bootReplica(newService, newOptions, shadow, { deferPublish: !teardownOldFirst });
17544
+ const deadline = Date.now() + shadowReadyTimeoutMs;
17425
17545
  await waitForReplicaTcpReady(newService, shadow, {
17426
- timeoutMs: shadowReadyTimeoutMs,
17427
- intervalMs: shadowReadyIntervalMs
17546
+ timeoutMs: Math.max(1, deadline - Date.now()),
17547
+ intervalMs: shadowReadyIntervalMs,
17548
+ isShuttingDown: () => shadow.shuttingDown || controller.runState.shuttingDown
17428
17549
  });
17550
+ if (!teardownOldFirst) await publishGatedReplica(newService, newOptions, shadow, deadline, () => shadow.shuttingDown || controller.runState.shuttingDown);
17429
17551
  })();
17430
17552
  shadow.inFlightBoot = bootPromise;
17431
17553
  try {
@@ -17433,6 +17555,7 @@ async function rollServiceReplica(args) {
17433
17555
  } catch (err) {
17434
17556
  const shadowIdx = controller.runState.replicas.indexOf(shadow);
17435
17557
  if (shadowIdx !== -1) controller.runState.replicas.splice(shadowIdx, 1);
17558
+ await disconnectOldFromSharedNetwork(shadow).catch(() => void 0);
17436
17559
  try {
17437
17560
  await cleanupEcsRun(shadow.state, { keepRunning: false });
17438
17561
  } catch {}
@@ -17453,7 +17576,8 @@ async function rollServiceReplica(args) {
17453
17576
  oldInstance.cloudMapHandles = [];
17454
17577
  }
17455
17578
  unregisterReplicaFromFrontDoor(oldInstance, newOptions.frontDoor);
17456
- await disconnectOldFromSharedNetwork(oldInstance).catch((err) => {
17579
+ oldInstance.shuttingDown = true;
17580
+ await disconnectOldFromSharedNetwork(oldInstance, { drain: true }).catch((err) => {
17457
17581
  logger.debug(`Rolling replica ${oldInstance.index}: shared-network disconnect of old (gen ${oldInstance.generation}) failed: ${err instanceof Error ? err.message : String(err)}. Proceeding with cleanup (the docker-rm step still tears it down).`);
17458
17582
  });
17459
17583
  oldInstance.shuttingDown = true;
@@ -17480,13 +17604,19 @@ async function softReloadReplica(args) {
17480
17604
  const containersToCycle = essentialContainers.length > 0 ? essentialContainers : newService.task.containers.length > 0 ? [newService.task.containers[0]] : [];
17481
17605
  if (containersToCycle.length === 0) throw new EcsServiceRunnerError(`softReloadReplica: service '${newService.serviceLogicalId}' has no containers to cycle.`);
17482
17606
  const startedById = new Map(instance.state.startedContainers.map((c) => [c.name, c.id]));
17607
+ const networkAliasesByContainer = buildNetworkAliasesByContainer(newService);
17483
17608
  const targets = [];
17484
17609
  for (const container of containersToCycle) {
17485
17610
  const id = startedById.get(container.name);
17486
17611
  if (!id) throw new EcsServiceRunnerError(`softReloadReplica: replica r${instance.index} has no started container named '${container.name}' (started: ${[...startedById.keys()].join(", ") || "none"}).`);
17612
+ const aliases = [container.name];
17613
+ for (const a of networkAliasesByContainer.get(container.name) ?? []) if (!aliases.includes(a)) aliases.push(a);
17614
+ const port = container.portMappings[0]?.containerPort;
17487
17615
  targets.push({
17488
17616
  name: container.name,
17489
- id
17617
+ id,
17618
+ aliases,
17619
+ ...port !== void 0 ? { port } : {}
17490
17620
  });
17491
17621
  }
17492
17622
  const controllerOptions = controller.options;
@@ -17499,6 +17629,7 @@ async function softReloadReplica(args) {
17499
17629
  unregisterReplicaFromFrontDoor(instance, controllerOptions.frontDoor);
17500
17630
  instance.softReloadInProgress = true;
17501
17631
  instance.softReloadGeneration = (instance.softReloadGeneration ?? 0) + 1;
17632
+ const attachPlans = /* @__PURE__ */ new Map();
17502
17633
  try {
17503
17634
  logger.info(`Soft-reloading replica r${instance.index} (gen ${instance.generation}): docker cp ${flattenToOneLine(sourceDirToCopy)} -> ${targets.length} essential container(s); restart.`);
17504
17635
  for (const target of targets) {
@@ -17514,23 +17645,72 @@ async function softReloadReplica(args) {
17514
17645
  } catch (err) {
17515
17646
  throw new EcsServiceRunnerError(`softReloadReplica: docker cp into '${target.name}' (${target.id}:${workdir}) failed: ${err instanceof Error ? err.message : String(err)}. This replica is unregistered from Cloud Map + the front-door pool until the next save triggers another reload.`);
17516
17647
  }
17517
- try {
17518
- await dockerRestartImpl(target.id);
17519
- } catch (err) {
17520
- throw new EcsServiceRunnerError(`softReloadReplica: docker restart of '${target.name}' (${target.id}) failed: ${err instanceof Error ? err.message : String(err)}. This replica is unregistered from Cloud Map + the front-door pool until the next save triggers another reload.`);
17648
+ }
17649
+ const sharedNetworkName = instance.state.network?.ownedByCaller ? instance.state.network.networkName : void 0;
17650
+ if (sharedNetworkName) {
17651
+ for (const target of targets) {
17652
+ const plan = await detachFromSharedNetwork(target, sharedNetworkName);
17653
+ if (plan) attachPlans.set(target.id, plan);
17654
+ }
17655
+ if (await reattachForDrain(attachPlans)) await sleep(reloadDrainMs);
17656
+ }
17657
+ for (const target of targets) try {
17658
+ await dockerStopImpl(target.id);
17659
+ const plan = attachPlans.get(target.id);
17660
+ if (plan && (plan.publishesHostPorts || !plan.attached)) {
17661
+ if (plan.attached) {
17662
+ await dockerNetworkDisconnectImpl(plan.networkName, target.id);
17663
+ plan.attached = false;
17664
+ }
17665
+ await attachToSharedNetwork(target.id, plan, plan.publishesHostPorts);
17521
17666
  }
17667
+ await dockerStartImpl(target.id);
17668
+ } catch (err) {
17669
+ throw new EcsServiceRunnerError(`softReloadReplica: docker stop / start of '${target.name}' (${target.id}) failed: ${err instanceof Error ? err.message : String(err)}. This replica is unregistered from Cloud Map + the front-door pool until the next save triggers another reload.`);
17522
17670
  }
17523
- await waitForReplicaTcpReady(newService, instance, {
17671
+ const aliasDeadline = Date.now() + shadowReadyTimeoutMs;
17672
+ const ready = await waitForReplicaTcpReady(newService, instance, {
17524
17673
  timeoutMs: shadowReadyTimeoutMs,
17525
17674
  intervalMs: shadowReadyIntervalMs,
17526
17675
  label: `Soft-reloaded replica r${instance.index} (gen ${instance.generation})`
17527
- });
17528
- const ownerKeyGenSuffix = instance.generation > 0 ? `:g${instance.generation}` : "";
17529
- const ownerKeyPrefix = `${newService.serviceLogicalId}:r${instance.index}${ownerKeyGenSuffix}`;
17676
+ }) === "ready";
17677
+ for (const target of targets) {
17678
+ const plan = attachPlans.get(target.id);
17679
+ if (!plan || plan.aliasesAttached) continue;
17680
+ if (target.port !== void 0) await waitForContainerListening(target.id, target.port, aliasDeadline);
17681
+ try {
17682
+ await dockerNetworkDisconnectImpl(plan.networkName, target.id);
17683
+ plan.attached = false;
17684
+ await attachToSharedNetwork(target.id, plan, true);
17685
+ } catch (err) {
17686
+ throw new EcsServiceRunnerError(`softReloadReplica: restoring the network aliases of '${target.name}' (${target.id}) failed: ${err instanceof Error ? err.message : String(err)}. This replica is unregistered from Cloud Map + the front-door pool until the next save triggers another reload.`);
17687
+ }
17688
+ }
17689
+ const ownerKeyPrefix = replicaOwnerKeyPrefix(newService, instance);
17530
17690
  if (controllerOptions.discovery) await publishReplicaToCloudMap(newService, instance, controllerOptions.discovery, ownerKeyPrefix);
17531
17691
  if (controllerOptions.frontDoor) await publishReplicaToFrontDoor(newService, instance, controllerOptions.frontDoor, controllerOptions.taskOptions.containerHost, ownerKeyPrefix);
17532
17692
  instance.lastDeployedAssetHash = pickEssentialAssetHash(newService);
17533
- logger.info(`Soft-reloaded replica r${instance.index} (gen ${instance.generation}): ${SOFT_RELOAD_COMPLETION_LOG_SUFFIX}`);
17693
+ logger.info(`Soft-reloaded replica r${instance.index} (gen ${instance.generation}): ` + (ready ? SOFT_RELOAD_COMPLETION_LOG_SUFFIX : SOFT_RELOAD_PROBE_TIMEOUT_LOG_SUFFIX));
17694
+ } catch (err) {
17695
+ for (const [id, plan] of attachPlans) {
17696
+ if (plan.aliasesAttached) continue;
17697
+ try {
17698
+ if (plan.attached) {
17699
+ await dockerNetworkDisconnectImpl(plan.networkName, id);
17700
+ plan.attached = false;
17701
+ }
17702
+ try {
17703
+ await attachToSharedNetwork(id, plan, true);
17704
+ } catch {
17705
+ await dockerNetworkConnectImpl(plan.networkName, id, { aliases: plan.aliases });
17706
+ plan.attached = true;
17707
+ plan.aliasesAttached = true;
17708
+ }
17709
+ } catch (restoreErr) {
17710
+ logger.debug(`Soft-reload of replica r${instance.index}: re-attaching container ${id} to ${plan.networkName} failed: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`);
17711
+ }
17712
+ }
17713
+ throw err;
17534
17714
  } finally {
17535
17715
  instance.softReloadInProgress = false;
17536
17716
  }
@@ -17559,23 +17739,122 @@ const defaultDockerCpImpl = async (src, dst) => {
17559
17739
  ], { maxBuffer: 64 * 1024 * 1024 });
17560
17740
  };
17561
17741
  let dockerCpImpl = defaultDockerCpImpl;
17562
- const defaultDockerRestartImpl = async (containerId) => {
17742
+ const defaultDockerStopImpl = async (containerId) => {
17743
+ const { execFile } = await import("node:child_process");
17744
+ const { promisify } = await import("node:util");
17745
+ const { getDockerCmd } = await import("./docker-cmd-BSdu45mc.js").then((n) => n.t);
17746
+ await promisify(execFile)(getDockerCmd(), ["stop", containerId]);
17747
+ };
17748
+ const defaultDockerStartImpl = async (containerId) => {
17563
17749
  const { execFile } = await import("node:child_process");
17564
17750
  const { promisify } = await import("node:util");
17565
17751
  const { getDockerCmd } = await import("./docker-cmd-BSdu45mc.js").then((n) => n.t);
17566
- await promisify(execFile)(getDockerCmd(), ["restart", containerId]);
17752
+ await promisify(execFile)(getDockerCmd(), ["start", containerId]);
17567
17753
  };
17568
- let dockerRestartImpl = defaultDockerRestartImpl;
17569
- async function disconnectOldFromSharedNetwork(oldInstance) {
17754
+ let dockerStopImpl = defaultDockerStopImpl;
17755
+ let dockerStartImpl = defaultDockerStartImpl;
17756
+ async function detachFromSharedNetwork(target, networkName) {
17757
+ const logger = getLogger().child("ecs-service");
17758
+ let ip;
17759
+ try {
17760
+ ip = await getContainerNetworkIp(target.id, networkName);
17761
+ } catch {
17762
+ ip = void 0;
17763
+ }
17764
+ if (!ip) {
17765
+ logger.debug(`Soft-reload: no IP for '${target.name}' (${target.id}) on ${networkName}; restarting it in place without detaching its network aliases first.`);
17766
+ return;
17767
+ }
17768
+ let publishesHostPorts;
17769
+ try {
17770
+ publishesHostPorts = await dockerInspectPublishesHostPortsImpl(target.id);
17771
+ } catch {
17772
+ publishesHostPorts = true;
17773
+ }
17774
+ try {
17775
+ await dockerNetworkDisconnectImpl(networkName, target.id);
17776
+ } catch (err) {
17777
+ logger.debug(`Soft-reload: detaching '${target.name}' (${target.id}) from ${networkName} failed: ${err instanceof Error ? err.message : String(err)}. Restarting it in place.`);
17778
+ return;
17779
+ }
17780
+ return {
17781
+ networkName,
17782
+ ip,
17783
+ aliases: target.aliases,
17784
+ publishesHostPorts,
17785
+ attached: false,
17786
+ aliasesAttached: false
17787
+ };
17788
+ }
17789
+ const DEFAULT_RELOAD_DRAIN_MS = 1e3;
17790
+ let reloadDrainMs = DEFAULT_RELOAD_DRAIN_MS;
17791
+ async function reattachForDrain(plans) {
17792
+ let any = false;
17793
+ for (const [id, plan] of plans) try {
17794
+ await attachToSharedNetwork(id, plan, false);
17795
+ any = true;
17796
+ } catch (err) {
17797
+ getLogger().child("ecs-service").debug(`Soft-reload: drain re-attach of ${id} to ${plan.networkName} failed: ${err instanceof Error ? err.message : String(err)}. Stopping it without a drain.`);
17798
+ }
17799
+ return any;
17800
+ }
17801
+ async function attachToSharedNetwork(containerId, plan, withAliases) {
17802
+ await dockerNetworkConnectImpl(plan.networkName, containerId, {
17803
+ ip: plan.ip,
17804
+ aliases: withAliases ? plan.aliases : []
17805
+ });
17806
+ plan.attached = true;
17807
+ plan.aliasesAttached = withAliases;
17808
+ }
17809
+ const defaultDockerNetworkConnectImpl = async (networkName, containerId, opts) => {
17810
+ const { execFile } = await import("node:child_process");
17811
+ const { promisify } = await import("node:util");
17812
+ const { getDockerCmd } = await import("./docker-cmd-BSdu45mc.js").then((n) => n.t);
17813
+ const args = ["network", "connect"];
17814
+ if (opts.ip) args.push("--ip", opts.ip);
17815
+ for (const a of opts.aliases) args.push("--alias", a);
17816
+ args.push(networkName, containerId);
17817
+ await promisify(execFile)(getDockerCmd(), args);
17818
+ };
17819
+ let dockerNetworkConnectImpl = defaultDockerNetworkConnectImpl;
17820
+ const defaultDockerInspectPublishesHostPortsImpl = async (containerId) => {
17821
+ const { execFile } = await import("node:child_process");
17822
+ const { promisify } = await import("node:util");
17823
+ const { getDockerCmd } = await import("./docker-cmd-BSdu45mc.js").then((n) => n.t);
17824
+ const { stdout } = await promisify(execFile)(getDockerCmd(), [
17825
+ "inspect",
17826
+ "--format",
17827
+ "{{json .HostConfig.PortBindings}}",
17828
+ containerId
17829
+ ]);
17830
+ const parsed = JSON.parse(stdout.trim() || "null");
17831
+ if (!parsed || typeof parsed !== "object") return false;
17832
+ return Object.values(parsed).some((bindings) => Array.isArray(bindings) && bindings.length > 0);
17833
+ };
17834
+ let dockerInspectPublishesHostPortsImpl = defaultDockerInspectPublishesHostPortsImpl;
17835
+ async function disconnectOldFromSharedNetwork(oldInstance, opts = {}) {
17570
17836
  const network = oldInstance.state.network;
17571
17837
  if (!network || !network.ownedByCaller) return;
17572
17838
  const networkName = network.networkName;
17573
- const targets = [];
17574
- if (network.sidecarContainerId) targets.push(network.sidecarContainerId);
17575
- for (const c of oldInstance.state.startedContainers) targets.push(c.id);
17839
+ const drainIps = /* @__PURE__ */ new Map();
17840
+ if (opts.drain) for (const c of oldInstance.state.startedContainers) {
17841
+ const ip = await getContainerNetworkIp(c.id, networkName).catch(() => void 0);
17842
+ if (ip) drainIps.set(c.id, ip);
17843
+ }
17844
+ const targets = oldInstance.state.startedContainers.map((c) => c.id);
17576
17845
  for (const id of targets) try {
17577
17846
  await dockerNetworkDisconnectImpl(networkName, id);
17578
17847
  } catch (err) {}
17848
+ if (drainIps.size === 0) return;
17849
+ let reattached = false;
17850
+ for (const [id, ip] of drainIps) try {
17851
+ await dockerNetworkConnectImpl(networkName, id, {
17852
+ ip,
17853
+ aliases: []
17854
+ });
17855
+ reattached = true;
17856
+ } catch {}
17857
+ if (reattached) await sleep(reloadDrainMs);
17579
17858
  }
17580
17859
  const defaultDockerNetworkDisconnectImpl = async (networkName, containerId) => {
17581
17860
  const { execFile } = await import("node:child_process");
@@ -17594,36 +17873,129 @@ async function waitForReplicaTcpReady(service, replica, opts) {
17594
17873
  const logger = getLogger().child("ecs-service");
17595
17874
  const label = opts.label ?? `Shadow replica r${replica.index} (gen ${replica.generation})`;
17596
17875
  const networkName = replica.state.network?.networkName;
17597
- if (!networkName) return;
17876
+ if (!networkName) return "ready";
17598
17877
  const essential = service.task.containers.find((c) => c.essential) ?? service.task.containers[0];
17599
- if (!essential || essential.portMappings.length === 0) return;
17878
+ if (!essential || essential.portMappings.length === 0) return "ready";
17600
17879
  const started = replica.state.startedContainers.find((c) => c.name === essential.name);
17601
- if (!started) return;
17602
- let ip;
17603
- try {
17604
- const resolved = await getContainerNetworkIp(started.id, networkName);
17605
- if (!resolved) return;
17606
- ip = resolved;
17607
- } catch (err) {
17608
- logger.warn(`${label}: TCP-ready probe could not resolve docker IP: ${err instanceof Error ? err.message : String(err)}. Proceeding.`);
17609
- return;
17610
- }
17880
+ if (!started) return "ready";
17611
17881
  const port = essential.portMappings[0].containerPort;
17612
17882
  const startedAt = Date.now();
17613
17883
  const deadline = startedAt + opts.timeoutMs;
17614
17884
  let lastErr;
17885
+ let target = formatAuthority(started.name, port);
17886
+ let inContainer = true;
17887
+ let ip;
17615
17888
  while (Date.now() < deadline) {
17616
- try {
17617
- await tcpProbeImpl(ip, port);
17618
- const elapsedMs = Date.now() - startedAt;
17619
- logger.debug(`${label}: TCP probe ${ip}:${port} accepted in ${elapsedMs}ms.`);
17620
- return;
17621
- } catch (err) {
17622
- lastErr = err instanceof Error ? err.message : String(err);
17889
+ if (opts.isShuttingDown?.()) return "not-ready";
17890
+ if (inContainer) {
17891
+ const result = await listenProbeImpl(started.id, port, deadline - Date.now());
17892
+ if (result.kind === "listening") {
17893
+ logger.debug(`${label}: port ${port} listening in ${Date.now() - startedAt}ms (in-container check).`);
17894
+ return "ready";
17895
+ }
17896
+ if (result.kind === "unsupported") {
17897
+ logger.debug(`${label}: in-container LISTEN check unavailable (${result.detail}); falling back to a host-side TCP connect.`);
17898
+ inContainer = false;
17899
+ continue;
17900
+ }
17901
+ if (result.kind === "exited") {
17902
+ logger.warn(`${label}: the container exited before port ${port} listened. Proceeding.`);
17903
+ return "exited";
17904
+ }
17905
+ lastErr = result.kind === "not-listening" ? "not listening yet" : result.detail;
17906
+ } else {
17907
+ if (ip === void 0) {
17908
+ try {
17909
+ ip = await getContainerNetworkIp(started.id, networkName);
17910
+ } catch (err) {
17911
+ logger.warn(`${label}: TCP-ready probe could not resolve docker IP: ${err instanceof Error ? err.message : String(err)}. Proceeding.`);
17912
+ return "not-ready";
17913
+ }
17914
+ if (!ip) {
17915
+ logger.warn(`${label}: TCP-ready probe found no docker IP for ${started.name} on ${networkName}. Proceeding.`);
17916
+ return "not-ready";
17917
+ }
17918
+ target = formatAuthority(ip, port);
17919
+ }
17920
+ try {
17921
+ await tcpProbeImpl(ip, port);
17922
+ logger.debug(`${label}: TCP probe ${target} accepted in ${Date.now() - startedAt}ms.`);
17923
+ return "ready";
17924
+ } catch (err) {
17925
+ lastErr = err instanceof Error ? err.message : String(err);
17926
+ }
17623
17927
  }
17624
17928
  await sleep(opts.intervalMs);
17625
17929
  }
17626
- logger.warn(`${label}: TCP probe ${ip}:${port} did not accept within ${opts.timeoutMs}ms (last: ${lastErr ?? "n/a"}). Proceeding anyway — the new source is the user intent. Initial requests may 502 until the app finishes binding.`);
17930
+ logger.warn(`${label}: TCP probe ${target} did not accept within ${opts.timeoutMs}ms (last: ${lastErr ?? "n/a"}). Proceeding anyway — the new source is the user intent. Initial requests may 502 until the app finishes binding.`);
17931
+ return "not-ready";
17932
+ }
17933
+ const LISTEN_PROBE_SCRIPT = [
17934
+ "p=$(printf \"%04X\" \"$1\")",
17935
+ "seen=0",
17936
+ "for f in /proc/net/tcp /proc/net/tcp6; do",
17937
+ " [ -r \"$f\" ] || continue",
17938
+ " seen=1",
17939
+ " while read -r _ l _ s _; do",
17940
+ " [ \"$s\" = 0A ] || continue",
17941
+ " case \"$l\" in *:\"$p\") ;; *) continue ;; esac",
17942
+ " case \"$l\" in ??????7F:*|0000000000000000FFFF0000??????7F:*) continue ;; esac",
17943
+ " case \"$l\" in 00000000000000000000000001000000:*) continue ;; esac",
17944
+ " exit 0",
17945
+ " done < \"$f\"",
17946
+ "done",
17947
+ "[ \"$seen\" = 1 ] && exit 3",
17948
+ "exit 4"
17949
+ ].join("\n");
17950
+ async function dockerExecListenProbe(containerId, port, timeoutMs = 1e4) {
17951
+ const { execFile } = await import("node:child_process");
17952
+ const { promisify } = await import("node:util");
17953
+ const { getDockerCmd } = await import("./docker-cmd-BSdu45mc.js").then((n) => n.t);
17954
+ try {
17955
+ await promisify(execFile)(getDockerCmd(), [
17956
+ "exec",
17957
+ containerId,
17958
+ "sh",
17959
+ "-c",
17960
+ LISTEN_PROBE_SCRIPT,
17961
+ "sh",
17962
+ String(port)
17963
+ ], { timeout: Math.max(1, Math.min(1e4, timeoutMs)) });
17964
+ return { kind: "listening" };
17965
+ } catch (err) {
17966
+ const e = err;
17967
+ const ran = typeof e.code === "number" || typeof e.signal === "string";
17968
+ const detail = flattenToOneLine(typeof e.stderr === "string" && e.stderr.trim() || (ran ? `exit ${String(e.code ?? e.signal)}` : e.message || String(err))).slice(0, 200);
17969
+ if (e.code === 3) return { kind: "not-listening" };
17970
+ if (e.code === 4) return {
17971
+ kind: "unsupported",
17972
+ detail: "no readable /proc/net/tcp"
17973
+ };
17974
+ const stderr = typeof e.stderr === "string" ? e.stderr : "";
17975
+ if (/is not running|stopped state/i.test(stderr)) return {
17976
+ kind: "exited",
17977
+ detail
17978
+ };
17979
+ if ((e.code === 126 || e.code === 127) && /not found|no such file/i.test(stderr)) return {
17980
+ kind: "unsupported",
17981
+ detail
17982
+ };
17983
+ return {
17984
+ kind: "error",
17985
+ detail
17986
+ };
17987
+ }
17988
+ }
17989
+ let listenProbeImpl = dockerExecListenProbe;
17990
+ async function waitForContainerListening(containerId, port, deadline) {
17991
+ while (Date.now() < deadline) {
17992
+ const result = await listenProbeImpl(containerId, port, deadline - Date.now());
17993
+ if (result.kind === "listening") return "ready";
17994
+ if (result.kind === "exited") return "exited";
17995
+ if (result.kind === "unsupported") return "not-ready";
17996
+ await sleep(shadowReadyIntervalMs);
17997
+ }
17998
+ return "not-ready";
17627
17999
  }
17628
18000
  const defaultTcpProbeImpl = async (host, port) => {
17629
18001
  const { createConnection } = await import("node:net");
@@ -17693,7 +18065,19 @@ async function watchReplica(service, options, instance, runState) {
17693
18065
  }
17694
18066
  instance.state = createEcsRunState();
17695
18067
  instance.restartCount += 1;
17696
- const bootPromise = bootReplica(service, options, instance);
18068
+ const isShuttingDown = () => instance.shuttingDown || runState.shuttingDown;
18069
+ const bootPromise = (async () => {
18070
+ await bootReplica(service, options, instance, { deferPublish: true });
18071
+ const deadline = Date.now() + shadowReadyTimeoutMs;
18072
+ const label = `Restarted replica r${instance.index} (gen ${instance.generation})`;
18073
+ if (await waitForReplicaTcpReady(service, instance, {
18074
+ timeoutMs: shadowReadyTimeoutMs,
18075
+ intervalMs: shadowReadyIntervalMs,
18076
+ label,
18077
+ isShuttingDown
18078
+ }) === "exited") return;
18079
+ await publishGatedReplica(service, options, instance, deadline, isShuttingDown, label);
18080
+ })();
17697
18081
  instance.inFlightBoot = bootPromise;
17698
18082
  try {
17699
18083
  await bootPromise;
@@ -19332,6 +19716,16 @@ function describePinnedImageUri(service) {
19332
19716
  if (image.kind === "cdk-asset") return void 0;
19333
19717
  return image.uri;
19334
19718
  }
19719
+ function imageOverrideKeyAliases(service) {
19720
+ const out = /* @__PURE__ */ new Set([
19721
+ service.serviceLogicalId,
19722
+ `${service.stack.stackName}:${service.serviceLogicalId}`,
19723
+ service.serviceDisplayName,
19724
+ service.serviceName
19725
+ ]);
19726
+ out.delete("");
19727
+ return [...out];
19728
+ }
19335
19729
  function listPinnedTargets(resolvedServices) {
19336
19730
  const out = [];
19337
19731
  for (const { target, service } of resolvedServices) {
@@ -19339,7 +19733,8 @@ function listPinnedTargets(resolvedServices) {
19339
19733
  const label = describePinnedImageUri(service);
19340
19734
  out.push({
19341
19735
  target,
19342
- ...label !== void 0 && { label }
19736
+ ...label !== void 0 && { label },
19737
+ aliases: imageOverrideKeyAliases(service)
19343
19738
  });
19344
19739
  }
19345
19740
  return out;
@@ -19468,6 +19863,70 @@ function parseImageOverrideFlags(input) {
19468
19863
  perService
19469
19864
  };
19470
19865
  }
19866
+ function canonicalizeImageOverrideKeys(rawFlags, pinnedAliases, knownTargets = []) {
19867
+ const exact = /* @__PURE__ */ new Set([...pinnedAliases.keys(), ...knownTargets]);
19868
+ const targetsByAlias = /* @__PURE__ */ new Map();
19869
+ for (const [target, aliases] of pinnedAliases) for (const alias of aliases) {
19870
+ let set = targetsByAlias.get(alias);
19871
+ if (!set) {
19872
+ set = /* @__PURE__ */ new Set();
19873
+ targetsByAlias.set(alias, set);
19874
+ }
19875
+ set.add(target);
19876
+ }
19877
+ const logger = getLogger();
19878
+ const canonical = (key, flag) => {
19879
+ if (exact.has(key)) return key;
19880
+ const matches = targetsByAlias.get(key);
19881
+ if (!matches) return key;
19882
+ if (matches.size > 1) throw new ImageOverrideError(`${flag} key '${key}' matches more than one pinned target (${[...matches].join(", ")}). Name the target in full instead.`);
19883
+ const target = [...matches][0];
19884
+ logger.debug(`${flag} key '${key}' resolved to pinned target '${target}'.`);
19885
+ return target;
19886
+ };
19887
+ const explicit = /* @__PURE__ */ new Map();
19888
+ const explicitKeyByTarget = /* @__PURE__ */ new Map();
19889
+ for (const [key, dockerfile] of rawFlags.explicit) {
19890
+ const target = canonical(key, "--image-override");
19891
+ const earlier = explicitKeyByTarget.get(target);
19892
+ if (earlier !== void 0) throw new ImageOverrideError(`--image-override keys '${earlier}' and '${key}' both name the pinned target '${target}'. Name each service target at most once.`);
19893
+ explicitKeyByTarget.set(target, key);
19894
+ explicit.set(target, dockerfile);
19895
+ }
19896
+ const perService = /* @__PURE__ */ new Map();
19897
+ for (const [key, overlay] of rawFlags.perService) {
19898
+ const target = canonical(key, "Per-service image-override flag");
19899
+ const into = perService.get(target);
19900
+ if (!into) {
19901
+ perService.set(target, {
19902
+ buildArgs: new Map(overlay.buildArgs),
19903
+ buildSecrets: new Map(overlay.buildSecrets),
19904
+ ...overlay.targetStage !== void 0 && { targetStage: overlay.targetStage }
19905
+ });
19906
+ continue;
19907
+ }
19908
+ const conflict = (what) => {
19909
+ throw new ImageOverrideError(`Per-service image-override flags give ${what} two different values for the pinned target '${target}' (one of them keyed '${key}'). Pass it once.`);
19910
+ };
19911
+ for (const [k, v] of overlay.buildArgs) {
19912
+ if (into.buildArgs.has(k) && into.buildArgs.get(k) !== v) conflict(`build arg '${k}'`);
19913
+ into.buildArgs.set(k, v);
19914
+ }
19915
+ for (const [id, src] of overlay.buildSecrets) {
19916
+ if (into.buildSecrets.has(id) && into.buildSecrets.get(id) !== src) conflict(`build secret '${id}'`);
19917
+ into.buildSecrets.set(id, src);
19918
+ }
19919
+ if (overlay.targetStage !== void 0) {
19920
+ if (into.targetStage !== void 0 && into.targetStage !== overlay.targetStage) conflict("the target stage");
19921
+ into.targetStage = overlay.targetStage;
19922
+ }
19923
+ }
19924
+ return {
19925
+ ...rawFlags,
19926
+ explicit,
19927
+ perService
19928
+ };
19929
+ }
19471
19930
  async function resolveImageOverrides(args) {
19472
19931
  const logger = getLogger();
19473
19932
  const { rawFlags, pinnedTargets, noInteractive } = args;
@@ -20117,7 +20576,7 @@ async function rollOneTarget(args) {
20117
20576
  return;
20118
20577
  }
20119
20578
  if (newService.desiredCount !== oldReplicas.length) logger.warn(`Reload of '${newBoot.target}': service DesiredCount=${newService.desiredCount} does not match the ${oldReplicas.length} live replica(s); rolling existing replicas only — scale changes during --watch are not yet supported. \`^C\` and re-run start-service to apply the new replica count.`);
20120
- if (verdict.kind === "soft-reload") logger.info(`Reload of '${newBoot.target}': soft-reloading ${oldReplicas.length} replica(s) one at a time (docker cp source → docker restart → TCP-ready probe; no rebuild).`);
20579
+ if (verdict.kind === "soft-reload") logger.info(`Reload of '${newBoot.target}': soft-reloading ${oldReplicas.length} replica(s) one at a time (docker cp source → restart → TCP-ready probe; no rebuild).`);
20121
20580
  else logger.info(`Reload of '${newBoot.target}': rolling ${oldReplicas.length} replica(s) one at a time (start new shadow → swap registrations → stop old).`);
20122
20581
  for (const oldInstance of oldReplicas) {
20123
20582
  const idx = controller.runState.replicas.indexOf(oldInstance);
@@ -20200,7 +20659,7 @@ async function resolveServiceAndRunnerOpts(boot, stacks, options, discovery, ski
20200
20659
  let resolvedRoleArn;
20201
20660
  if (effectiveAssumeRole === true) {
20202
20661
  if (!service.task.taskRoleArn) throw new LocalStartServiceError(`--assume-role passed without an ARN but service '${service.serviceLogicalId}' has no resolvable TaskRoleArn. Pass the ARN explicitly: --assume-role <arn>`);
20203
- resolvedRoleArn = await resolvePlaceholderAccount$1(service.task.taskRoleArn, options.region, options.profile);
20662
+ resolvedRoleArn = await resolveTaskRoleArnForOptions$1(service.task.taskRoleArn, options);
20204
20663
  assumedCredentials = await assumeTaskRole$1(resolvedRoleArn, options.region, options.profile);
20205
20664
  } else if (typeof effectiveAssumeRole === "string") {
20206
20665
  resolvedRoleArn = effectiveAssumeRole;
@@ -20518,12 +20977,13 @@ function describeRuleActionForSummary(action) {
20518
20977
  function describeForwardTargetForSummary(t) {
20519
20978
  return t.kind === "lambda" ? `<Lambda: ${t.lambda.logicalId}>` : `<ECS: ${t.serviceTarget}>`;
20520
20979
  }
20521
- async function resolvePlaceholderAccount$1(arn, region, profile) {
20980
+ async function resolvePlaceholderAccount$1(arn, region, profile, readFromCfnStack) {
20522
20981
  if (!arn.includes("${AWS::AccountId}")) return arn;
20523
20982
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
20524
- const sts = new STSClient(deployAccountStsClientConfig({
20983
+ const sts = new STSClient(accountLookupStsClientConfig({
20525
20984
  region,
20526
- profile
20985
+ profile,
20986
+ readFromCfnStack
20527
20987
  }));
20528
20988
  try {
20529
20989
  const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
@@ -20536,6 +20996,9 @@ async function resolvePlaceholderAccount$1(arn, region, profile) {
20536
20996
  sts.destroy();
20537
20997
  }
20538
20998
  }
20999
+ async function resolveTaskRoleArnForOptions$1(arn, options) {
21000
+ return resolvePlaceholderAccount$1(arn, options.region, options.profile, isCfnFlagPresent(options));
21001
+ }
20539
21002
  async function assumeTaskRole$1(roleArn, region, profile) {
20540
21003
  return assumeRoleCredentials({
20541
21004
  roleArn,
@@ -20558,7 +21021,7 @@ async function buildEcsImageResolutionContext$1(target, stacks, options, statePr
20558
21021
  if (!region) logger.warn(`Resolver references \${AWS::Region} but ${getEmbedConfig().binaryName} could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.`);
20559
21022
  let accountId;
20560
21023
  try {
20561
- accountId = await resolveCallerAccountId$1(region, options.profile);
21024
+ accountId = await resolveCallerAccountId$1(region, options.profile, isCfnFlagPresent(options));
20562
21025
  } catch (err) {
20563
21026
  logger.warn(`Resolver needs \${AWS::AccountId} but STS GetCallerIdentity failed: ${describeAwsFailureForWarn(err, "STS GetCallerIdentity")}. Substitution will be skipped; affected env / secret entries will be dropped with per-key warnings.`);
20564
21027
  }
@@ -20600,11 +21063,12 @@ function pickCandidateStack$1(stackPattern, stacks) {
20600
21063
  const matched = matchStacks(stacks, [stackPattern]);
20601
21064
  if (matched.length === 1) return matched[0];
20602
21065
  }
20603
- async function resolveCallerAccountId$1(region, profile) {
21066
+ async function resolveCallerAccountId$1(region, profile, readFromCfnStack) {
20604
21067
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
20605
- const sts = new STSClient(deployAccountStsClientConfig({
21068
+ const sts = new STSClient(accountLookupStsClientConfig({
20606
21069
  region,
20607
- profile
21070
+ profile,
21071
+ readFromCfnStack
20608
21072
  }));
20609
21073
  try {
20610
21074
  return (await sts.send(new GetCallerIdentityCommand({}))).Account;
@@ -20709,13 +21173,17 @@ async function resolveAndBuildImageOverrides(args) {
20709
21173
  const pinnedEntries = listPinnedTargets(resolvedForPeek);
20710
21174
  const pinnedTargets = pinnedEntries.map((e) => e.target);
20711
21175
  const pinnedLabels = /* @__PURE__ */ new Map();
20712
- for (const entry of pinnedEntries) if (entry.label !== void 0) pinnedLabels.set(entry.target, entry.label);
20713
- const rawFlags = parseImageOverrideFlags({
21176
+ const pinnedAliases = /* @__PURE__ */ new Map();
21177
+ for (const entry of pinnedEntries) {
21178
+ if (entry.label !== void 0) pinnedLabels.set(entry.target, entry.label);
21179
+ pinnedAliases.set(entry.target, entry.aliases);
21180
+ }
21181
+ const rawFlags = canonicalizeImageOverrideKeys(parseImageOverrideFlags({
20714
21182
  ...options.imageOverride && { imageOverride: options.imageOverride },
20715
21183
  ...options.imageBuildArg && { imageBuildArg: options.imageBuildArg },
20716
21184
  ...options.imageBuildSecret && { imageBuildSecret: options.imageBuildSecret },
20717
21185
  ...options.imageTarget && { imageTarget: options.imageTarget }
20718
- });
21186
+ }), pinnedAliases, perTarget);
20719
21187
  if (pinnedTargets.length === 0 && rawFlags.explicit.size === 0 && rawFlags.pickerPaths.length === 0 && rawFlags.perService.size === 0) return {
20720
21188
  tags: /* @__PURE__ */ new Map(),
20721
21189
  overrides: /* @__PURE__ */ new Map()
@@ -20728,6 +21196,7 @@ async function resolveAndBuildImageOverrides(args) {
20728
21196
  noInteractive: options.interactiveOverrides === false
20729
21197
  });
20730
21198
  enforceImageOverrideOrphans(rawFlags, overrides);
21199
+ enforceStrictOverrides(options.strictOverrides === true, pinnedTargets.filter((t) => !overrides.has(t)));
20731
21200
  if (overrides.size === 0) return {
20732
21201
  tags: /* @__PURE__ */ new Map(),
20733
21202
  overrides: /* @__PURE__ */ new Map()
@@ -20874,7 +21343,7 @@ async function localRunTaskCommand(target, options, extraStateProviders) {
20874
21343
  let resolvedRoleArn;
20875
21344
  if (effectiveAssumeRole === true) {
20876
21345
  if (!task.taskRoleArn) throw new Error(`--assume-role passed without an ARN but the task definition has no resolvable TaskRoleArn. Either the task definition does not set TaskRoleArn, or it points at a resource ${getEmbedConfig().binaryName} cannot resolve to an IAM Role at synth time. Pass the ARN explicitly: --assume-role <arn>`);
20877
- resolvedRoleArn = await resolvePlaceholderAccount(task.taskRoleArn, options.region, options.profile);
21346
+ resolvedRoleArn = await resolveTaskRoleArnForOptions(task.taskRoleArn, options);
20878
21347
  assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region, options.profile);
20879
21348
  } else if (typeof effectiveAssumeRole === "string") {
20880
21349
  resolvedRoleArn = effectiveAssumeRole;
@@ -20944,12 +21413,13 @@ var PlaceholderAccountError = class PlaceholderAccountError extends Error {
20944
21413
  Object.setPrototypeOf(this, PlaceholderAccountError.prototype);
20945
21414
  }
20946
21415
  };
20947
- async function resolvePlaceholderAccount(arn, region, profile) {
21416
+ async function resolvePlaceholderAccount(arn, region, profile, readFromCfnStack) {
20948
21417
  if (!arn.includes("${AWS::AccountId}")) return arn;
20949
21418
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
20950
- const sts = new STSClient(deployAccountStsClientConfig({
21419
+ const sts = new STSClient(accountLookupStsClientConfig({
20951
21420
  region,
20952
- profile
21421
+ profile,
21422
+ readFromCfnStack
20953
21423
  }));
20954
21424
  try {
20955
21425
  const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
@@ -20964,6 +21434,9 @@ async function resolvePlaceholderAccount(arn, region, profile) {
20964
21434
  sts.destroy();
20965
21435
  }
20966
21436
  }
21437
+ async function resolveTaskRoleArnForOptions(arn, options) {
21438
+ return resolvePlaceholderAccount(arn, options.region, options.profile, isCfnFlagPresent(options));
21439
+ }
20967
21440
  async function assumeTaskRole(roleArn, region, profile) {
20968
21441
  return assumeRoleCredentials({
20969
21442
  roleArn,
@@ -20984,7 +21457,7 @@ async function buildEcsImageResolutionContext(candidate, stateProvider, options)
20984
21457
  if (!region) logger.warn(`Resolver references \${AWS::Region} but ${getEmbedConfig().binaryName} could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.`);
20985
21458
  let accountId;
20986
21459
  try {
20987
- accountId = await resolveCallerAccountId(region, options.profile);
21460
+ accountId = await resolveCallerAccountId(region, options.profile, isCfnFlagPresent(options));
20988
21461
  } catch (err) {
20989
21462
  logger.warn(`Resolver needs \${AWS::AccountId} but STS GetCallerIdentity failed: ${describeAwsFailureForWarn(err, "STS GetCallerIdentity")}. Substitution will be skipped; affected env / secret entries will be dropped with per-key warnings.`);
20990
21463
  }
@@ -21023,11 +21496,12 @@ function pickCandidateStack(stackPattern, stacks) {
21023
21496
  const matched = matchStacks(stacks, [stackPattern]);
21024
21497
  if (matched.length === 1) return matched[0];
21025
21498
  }
21026
- async function resolveCallerAccountId(region, profile) {
21499
+ async function resolveCallerAccountId(region, profile, readFromCfnStack) {
21027
21500
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
21028
- const sts = new STSClient(deployAccountStsClientConfig({
21501
+ const sts = new STSClient(accountLookupStsClientConfig({
21029
21502
  region,
21030
- profile
21503
+ profile,
21504
+ readFromCfnStack
21031
21505
  }));
21032
21506
  try {
21033
21507
  return (await sts.send(new GetCallerIdentityCommand({}))).Account;
@@ -21070,6 +21544,16 @@ function createLocalRunTaskCommand(opts = {}) {
21070
21544
  cmd.addOption(regionOption);
21071
21545
  return cmd;
21072
21546
  }
21547
+ function runTaskOverrideKeyAliases(task) {
21548
+ const id = task.taskDefinitionLogicalId;
21549
+ const out = /* @__PURE__ */ new Set([
21550
+ id,
21551
+ `${task.stack.stackName}:${id}`,
21552
+ deriveServiceDisplayName(void 0, id, task.resource.Metadata)
21553
+ ]);
21554
+ out.delete("");
21555
+ return [...out];
21556
+ }
21073
21557
  async function resolveRunTaskImageOverride(args) {
21074
21558
  const { task, target, options, cwd } = args;
21075
21559
  const essential = task.containers.find((c) => c.essential) ?? task.containers[0];
@@ -21077,12 +21561,14 @@ async function resolveRunTaskImageOverride(args) {
21077
21561
  const pinnedTargets = pinned ? [target] : [];
21078
21562
  const pinnedLabels = /* @__PURE__ */ new Map();
21079
21563
  if (pinned) pinnedLabels.set(target, `${task.stack.stackName}/${task.taskDefinitionLogicalId}`);
21080
- const rawFlags = parseImageOverrideFlags({
21564
+ const pinnedAliases = /* @__PURE__ */ new Map();
21565
+ if (pinned) pinnedAliases.set(target, runTaskOverrideKeyAliases(task));
21566
+ const rawFlags = canonicalizeImageOverrideKeys(parseImageOverrideFlags({
21081
21567
  ...options.imageOverride && { imageOverride: options.imageOverride },
21082
21568
  ...options.imageBuildArg && { imageBuildArg: options.imageBuildArg },
21083
21569
  ...options.imageBuildSecret && { imageBuildSecret: options.imageBuildSecret },
21084
21570
  ...options.imageTarget && { imageTarget: options.imageTarget }
21085
- });
21571
+ }), pinnedAliases, [target]);
21086
21572
  if (pinnedTargets.length === 0 && rawFlags.explicit.size === 0 && rawFlags.pickerPaths.length === 0 && rawFlags.perService.size === 0) return {};
21087
21573
  const overrides = await resolveImageOverrides({
21088
21574
  rawFlags,
@@ -21134,7 +21620,7 @@ function createLocalStartServiceCommand(opts = {}) {
21134
21620
  }
21135
21621
  function addStartServiceSpecificOptions(cmd) {
21136
21622
  addImageOverrideOptions(cmd);
21137
- return cmd.addOption(new Option("--host-port <containerPort=hostPort...>", "Publish a container port on a specific host port (e.g. 80=8080); repeatable. Default: host port == container port. Use this on macOS to map a privileged container port (< 1024) to a non-privileged host port and avoid the Docker Desktop admin-password prompt. (Single-replica services only — multi-replica services do not publish host ports.)")).addOption(new Option("--watch", "Hot-reload: re-synth + per-replica reload when the CDK source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). A per-firing classifier picks the per-replica primitive: source-only edits on interpreted-language handlers (Node/Python/Ruby/shell) take a bind-mount FAST PATH (`docker cp` the new source into each replica + `docker restart`; no rebuild). Dockerfile / dependency manifest / compiled-language source / ambiguous edits fall through to the rebuild rolling primitive — boot a shadow under a bumped generation suffix, wait for its container port to accept a TCP connection, atomically swap Service-Connect / Cloud Map registrations, then retire the old container. Either path rolls one replica at a time, so peer services see zero connection refusals across the reload even on multi-replica services. Off by default; existing replica(s) keep serving when synth fails mid-reload.").default(false));
21623
+ return cmd.addOption(new Option("--host-port <containerPort=hostPort...>", "Publish a container port on a specific host port (e.g. 80=8080); repeatable. Default: host port == container port. Use this on macOS to map a privileged container port (< 1024) to a non-privileged host port and avoid the Docker Desktop admin-password prompt. (Single-replica services only — multi-replica services do not publish host ports.)")).addOption(new Option("--watch", "Hot-reload: re-synth + per-replica reload when the CDK source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). A per-firing classifier picks the per-replica primitive: source-only edits on interpreted-language handlers (Node/Python/Ruby/shell) take a bind-mount FAST PATH (`docker cp` the new source into each replica + restart it; no rebuild). Dockerfile / dependency manifest / compiled-language source / ambiguous edits fall through to the rebuild rolling primitive — boot a shadow under a bumped generation suffix, wait for its container port to accept a TCP connection, atomically swap Service-Connect / Cloud Map registrations, then retire the old container. Either path rolls one replica at a time, so peer services see zero connection refusals across the reload even on multi-replica services. Off by default; existing replica(s) keep serving when synth fails mid-reload.").default(false));
21138
21624
  }
21139
21625
 
21140
21626
  //#endregion
@@ -21770,7 +22256,7 @@ function createLocalStartAlbCommand(opts = {}) {
21770
22256
  }
21771
22257
  function addAlbSpecificOptions(cmd) {
21772
22258
  addImageOverrideOptions(cmd);
21773
- return cmd.addOption(new Option("--lb-port <listenerPort=hostPort...>", "Bind the local front-door on a specific host port (e.g. 80=8080); repeatable. Default: host port == ALB listener port. Use this on macOS to remap a privileged listener port (< 1024) to a non-privileged host port.")).addOption(new Option("--tls", "Terminate TLS locally for cloud-HTTPS listeners. Default: a cloud-HTTPS listener is served over plain HTTP locally (X-Forwarded-Proto: https is preserved so the upstream app still sees the deployed listener protocol). Implied by --tls-cert / --tls-key. Use this when local-dev cookies need Secure / SameSite=None, when the upstream app inspects TLS metadata, or for mTLS / SNI testing — otherwise plain HTTP is friendlier (no self-signed cert warnings in curl / browser).")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Implies --tls. Must be set together with --tls-key. Pass --tls alone (without --tls-cert / --tls-key) to auto-generate a self-signed cert (cached under $XDG_CACHE_HOME/cdk-local/alb-https/, default ~/.cache/cdk-local/alb-https/); requires openssl on PATH. The deployed Listener Certificates[] are NOT fetched (ACM private keys are not retrievable by design). The auto-generated cert lists DNS:localhost,IP:127.0.0.1 as SubjectAltName, so a client validating a non-loopback --container-host will fail the SAN check — pass --tls-cert / --tls-key with a SAN covering that host instead.")).addOption(new Option("--tls-key <path>", "PEM-encoded server private key matching --tls-cert. Implies --tls. Must be set together with --tls-cert.")).addOption(new Option("--no-verify-auth", "Disable local enforcement of authenticate-cognito / authenticate-oidc actions. Every request is served as if the auth check passed. Useful for local dev where you do not want to mint a Bearer token at all.")).addOption(new Option("--bearer-token <jwt>", "Default Bearer JWT this command INJECTS only when an inbound request has none (the default-when-missing role) — `cdkl start-alb` is the local ALB front-door RECEIVING outside-in requests, so this token is the fallback the front-door slots in as Authorization: Bearer <jwt> if the caller did not already supply one. Verified against the same JWKS / OIDC discovery URL the deployed ALB would (signature + iss + aud + exp). Cookie pass-through (AWSELBAuthSessionCookie-*) also bypasses the guard. Contrast with `cdkl invoke-agentcore --bearer-token`, where the role is reversed — that command is the outbound client and ALWAYS presents this token (the supplier).")).addOption(new Option("--watch", "Hot-reload: re-synth + per-replica reload of every ECS service behind the ALB when the CDK source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). A per-firing classifier picks the per-replica primitive: source-only edits on interpreted-language handlers (Node/Python/Ruby/shell) take a bind-mount FAST PATH (`docker cp` the new source into each replica + `docker restart`; no rebuild, front-door pool entry unchanged since the IP/port are preserved). Dockerfile / dependency manifest / compiled-language source / ambiguous edits fall through to the rebuild rolling primitive — boot a shadow under a bumped generation suffix, wait for its container port to accept a TCP connection, atomically register it in the front-door pool, then drop the old entry and retire the old container. Either path rolls one replica at a time, so a continuous external request stream against the listener port sees zero connection refusals across the reload. The host front-door (TLS, JWKS cache, Lambda-target containers, listener sockets) stays up across the reload. Lambda target groups behind the ALB are a no-op on reload (the warm RIE container keeps its boot-time image). Off by default; existing replica(s) keep serving when synth fails mid-reload.").default(false));
22259
+ return cmd.addOption(new Option("--lb-port <listenerPort=hostPort...>", "Bind the local front-door on a specific host port (e.g. 80=8080); repeatable. Default: host port == ALB listener port. Use this on macOS to remap a privileged listener port (< 1024) to a non-privileged host port.")).addOption(new Option("--tls", "Terminate TLS locally for cloud-HTTPS listeners. Default: a cloud-HTTPS listener is served over plain HTTP locally (X-Forwarded-Proto: https is preserved so the upstream app still sees the deployed listener protocol). Implied by --tls-cert / --tls-key. Use this when local-dev cookies need Secure / SameSite=None, when the upstream app inspects TLS metadata, or for mTLS / SNI testing — otherwise plain HTTP is friendlier (no self-signed cert warnings in curl / browser).")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Implies --tls. Must be set together with --tls-key. Pass --tls alone (without --tls-cert / --tls-key) to auto-generate a self-signed cert (cached under $XDG_CACHE_HOME/cdk-local/alb-https/, default ~/.cache/cdk-local/alb-https/); requires openssl on PATH. The deployed Listener Certificates[] are NOT fetched (ACM private keys are not retrievable by design). The auto-generated cert lists DNS:localhost,IP:127.0.0.1 as SubjectAltName, so a client validating a non-loopback --container-host will fail the SAN check — pass --tls-cert / --tls-key with a SAN covering that host instead.")).addOption(new Option("--tls-key <path>", "PEM-encoded server private key matching --tls-cert. Implies --tls. Must be set together with --tls-cert.")).addOption(new Option("--no-verify-auth", "Disable local enforcement of authenticate-cognito / authenticate-oidc actions. Every request is served as if the auth check passed. Useful for local dev where you do not want to mint a Bearer token at all.")).addOption(new Option("--bearer-token <jwt>", "Default Bearer JWT this command INJECTS only when an inbound request has none (the default-when-missing role) — `cdkl start-alb` is the local ALB front-door RECEIVING outside-in requests, so this token is the fallback the front-door slots in as Authorization: Bearer <jwt> if the caller did not already supply one. Verified against the same JWKS / OIDC discovery URL the deployed ALB would (signature + iss + aud + exp). Cookie pass-through (AWSELBAuthSessionCookie-*) also bypasses the guard. Contrast with `cdkl invoke-agentcore --bearer-token`, where the role is reversed — that command is the outbound client and ALWAYS presents this token (the supplier).")).addOption(new Option("--watch", "Hot-reload: re-synth + per-replica reload of every ECS service behind the ALB when the CDK source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). A per-firing classifier picks the per-replica primitive: source-only edits on interpreted-language handlers (Node/Python/Ruby/shell) take a bind-mount FAST PATH (`docker cp` the new source into each replica + restart it; no rebuild, its front-door pool entry is drained and re-published once it listens again). Dockerfile / dependency manifest / compiled-language source / ambiguous edits fall through to the rebuild rolling primitive — boot a shadow under a bumped generation suffix, wait for its container port to accept a TCP connection, atomically register it in the front-door pool, then drop the old entry and retire the old container. Either path rolls one replica at a time, so a continuous external request stream against the listener port sees zero connection refusals across the reload. The host front-door (TLS, JWKS cache, Lambda-target containers, listener sockets) stays up across the reload. Lambda target groups behind the ALB are a no-op on reload (the warm RIE container keeps its boot-time image). Off by default; existing replica(s) keep serving when synth fails mid-reload.").default(false));
21774
22260
  }
21775
22261
 
21776
22262
  //#endregion
@@ -28553,25 +29039,9 @@ async function relayServeRequest(input, fetchFn = fetch, clock = Date.now) {
28553
29039
  //#endregion
28554
29040
  //#region src/local/studio-proxy.ts
28555
29041
  let proxyIdCounter = 0;
28556
- function isLoopbackHostname(hostname) {
28557
- const h = hostname.trim().replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
28558
- if (h === "localhost") return true;
28559
- if (h === "::1" || h === "0:0:0:0:0:0:0:1") return true;
28560
- const mapped = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(h);
28561
- if (mapped) return parseInt(mapped[1], 16) >> 8 === 127;
28562
- const dotted = /^(?:::ffff:)?(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h);
28563
- if (!dotted) return false;
28564
- const octets = dotted.slice(1).map(Number);
28565
- if (octets.some((o) => o > 255)) return false;
28566
- return octets[0] === 127;
28567
- }
28568
29042
  function stripHostBrackets(hostname) {
28569
29043
  return hostname.replace(/^\[/, "").replace(/\]$/, "");
28570
29044
  }
28571
- function isWildcardHostname(hostname) {
28572
- const h = hostname.trim().replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
28573
- return h === "0.0.0.0" || h === "::" || h === "0:0:0:0:0:0:0:0" || h === "::ffff:0.0.0.0" || h === "::ffff:0:0";
28574
- }
28575
29045
  function rewriteWildcardHostToken(upstream) {
28576
29046
  const schemeEnd = upstream.indexOf("://");
28577
29047
  if (schemeEnd === -1) return void 0;
@@ -29865,5 +30335,5 @@ function addStudioSpecificOptions(cmd) {
29865
30335
  }
29866
30336
 
29867
30337
  //#endregion
29868
- export { applyEdgeResponseResult as $, buildJwksUrlFromIssuer as $n, resolveCfnStackName as $r, buildCloudMapIndex as $t, startAgentCoreHttpServer as A, DynamicReferenceResolver as Ai, classifySourceChange as An, ConnectionRegistry as Ar, addRunTaskSpecificOptions as At, idFromArn as B, assumedRoleRecorded as Bi, buildStageMap as Bn, resolveRuntimeFileExtension as Br, resolveEcsAssumeRoleOption as Bt, addListSpecificOptions as C, resolveAgentCoreTarget as Ci, waitForAgentCorePing as Cn, tryParseStatus as Cr, parseLbPortOverrides as Ct, createLocalStartAgentCoreCommand as D, tryResolveImageFnJoin as Di, computeCodeImageTag as Dn, probeHostGatewaySupport as Dr, addStartServiceSpecificOptions as Dt, addStartAgentCoreSpecificOptions as E, substituteImagePlaceholders as Ei, buildAgentCoreCodeImage as En, HOST_GATEWAY_MIN_VERSION as Er, resolveAlbFrontDoor as Et, createLocalStartCloudFrontCommand as F, keysOverriddenBy as Fi, createWatchPredicates as Fn, buildDisconnectEvent as Fr, addImageOverrideOptions as Ft, classifyS3Error as G, workloadClientConfig as Gi, filterRoutesByApiIdentifiers as Gn, substituteEnvVarsFromState as Gr, enforceImageOverrideOrphans as Gt, createDeployedKvsDataSource as H, readEnvCallerCredentials as Hi, resolveEnvVars$1 as Hn, EcsTaskResolutionError as Hr, runEcsServiceEmulator as Ht, normalizeKvsFileKeys as I, parseDynamicReference as Ii, resolveApiTargetSubset as In, buildMessageEvent as Ir, buildEcsImageResolutionContext$1 as It, startCloudFrontServer as J, resolveProfileCredentials as Ji, startApiServer as Jn, createLocalStateProvider as Jr, resolveImageOverrides as Jt, createS3OriginReader as K, workloadStsClientConfig as Ki, groupRoutesByServer as Kn, substituteEnvVarsFromStateAsync as Kr, mergeForService as Kt, parseKvsFileOverrides as L, resolveDynamicReferencesInEnv as Li, createAuthorizerCache as Ln, architectureToPlatform as Lr, ecsClusterOption as Lt, startAgentCoreWsBridge as M, deployedFillMayBeSecret as Mi, createLocalInvokeCommand as Mn, handleConnectionsRequest as Mr, MAX_TASKS_SUBNET_RANGE_CAP as Mt, LocalStartCloudFrontError as N, firstUsableRegion as Ni, addStartApiSpecificOptions as Nn, parseConnectionsPath as Nr, addCommonEcsServiceOptions as Nt, buildAgentCoreServeAuthCheck as O, LocalInvokeBuildError as Oi, renderCodeDockerfile as On, resolveHostGatewayExtraHosts as Or, createLocalStartServiceCommand as Ot, addStartCloudFrontSpecificOptions as P, keysNotFromTemplate as Pi, createLocalStartApiCommand as Pn, buildConnectEvent as Pr, addEcsAssumeRoleOptions as Pt, applyEdgeRequestResult as Q, buildCognitoJwksUrl as Qn, resolveCfnRegion as Qr, listPinnedTargets as Qt, parseOriginOverrides as R, withoutKeys as Ri, createFileWatcher as Rn, buildContainerImage as Rr, parseMaxTasks as Rt, StudioEventBus as S, pickAgentCoreCandidateStack as Si, waitForAgentCoreHttpReady as Sn, selectIntegrationResponse as Sr, createLocalStartAlbCommand as St, formatTargetListing as T, formatStateRemedy as Ti, SUPPORTED_CODE_RUNTIMES as Tn, HOST_DOCKER_INTERNAL_GATEWAY as Tr, isApplicationLoadBalancer as Tt, resolveDeployedKvsArnByName as U, recordAssumedRole as Ui, availableApiIdentifiers as Un, substituteAgainstState as Ur, ImageOverrideError as Ut, resolveKvsModulesForDistribution as V, deployAccountStsClientConfig as Vi, materializeLayerFromArn as Vn, resolveRuntimeImage as Vr, resolveSharedSidecarCredentials as Vt, resolveDeployedOriginBucket as W, resetWorkloadIdentity as Wi, filterRoutesByApiIdentifier as Wn, substituteAgainstStateAsync as Wr, buildImageOverrideTag as Wt, serveFromStaticOrigin as X, isProxyEnvConfigured as Xi, resolveServiceIntegrationParameters as Xn, rejectExplicitCfnStackWithMultipleStacks as Xr, describePinnedImageUri as Xt, resolveErrorResponseCandidates as Y, buildProxyClientConfig as Yi, resolveSelectionExpression as Yn, isCfnFlagPresent as Yr, runImageOverrideBuilds as Yt, serveLambdaUrlOrigin as Z, defaultCredentialsLoader as Zn, resolveCfnFallbackRegion as Zr, isLocalCdkAssetImage as Zt, filterStudioTargetGroups as _, AGENTCORE_AGUI_PROTOCOL as _i, parseSseForJsonRpc as _n, applyAuthorizerOverlay as _r, createCloudFrontModule as _t, createLocalStudioCommand as a, countTargets as ai, attachContainerLogStreamer as an, computeRequestIdentityHash as ar, describeS3OriginDomain as at, renderStudioHtml as b, AGENTCORE_RUNTIME_TYPE as bi, AGENTCORE_SESSION_ID_HEADER as bn, evaluateResponseParameters as br, addAlbSpecificOptions as bt, startStudioProxy as c, discoverWebSocketApis as ci, bridgeAgentCoreWs as cn, invokeTokenAuthorizer as cr, pickFunctionUrlLogicalIdFromOrigin as ct, createStudioDispatcher as d, parseSelectionExpressionPath as di, A2A_PATH as dn, buildCorsConfigByApiId as dr, pickTargetFunctionLogicalId as dt, CfnLocalStateProvider as ei, CloudMapRegistry as en, createJwksCache as er, buildEdgeRequestEvent as et, filterStudioCustomResources as f, webSocketApiMatchesIdentifier as fi, a2aInvokeOnce as fn, buildCorsConfigFromCloudFrontChain as fr, resolveCloudFrontDistribution as ft, annotatePinnedEcsTargets as g, AGENTCORE_A2A_PROTOCOL as gi, mcpInvokeOnce as gn, translateLambdaResponse as gr, stripCloudFrontImport as gt, annotateEcsTaskPinnedTargets as h, resolveLambdaArnIntrinsic as hi, MCP_PROTOCOL_VERSION as hn, matchRoute as hr, runViewerResponse as ht, coerceStopRequest as i, resolveSingleTarget as ii, getContainerNetworkIp as in, buildMethodArn as ir, CLOUDFRONT_DISTRIBUTION_TYPE as it, attachAgentCoreWsBridge as j, containsDynamicReference as ji, addInvokeSpecificOptions as jn, buildMgmtEndpointEnvUrl as jr, createLocalRunTaskCommand as jt, selectServeInboundAuth as k, DynamicReferenceResolutionError as ki, toCmdArgv as kn, bufferToBody as kr, serviceStrategy as kt, relayServeRequest as l, discoverWebSocketApisOrThrow as li, invokeAgentCoreWs as ln, attachAuthorizers as lr, pickKvsLogicalIdFromArn as lt, annotateAlbPinnedBackingServices as m, pickRefLogicalId as mi, MCP_PATH as mn, matchPreflight as mr, runViewerRequest as mt, coerceRunRequest as n, resolveSsmParameters as ni, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as nn, verifyJwtAuthorizer as nr, edgeHeadersToHttp as nt, resolveServeBaseUrl as o, listTargets as oi, addInvokeAgentCoreSpecificOptions as on, evaluateCachedLambdaPolicy as or, extractKvsAssociations as ot, isCustomResourceLambdaTarget as p, discoverRoutes as pi, MCP_CONTAINER_PORT as pn, isFunctionUrlOacFronted as pr, compileCloudFrontFunction as pt, matchBehavior as q, buildStsClientConfig as qi, readMtlsMaterialsFromDisk as qn, LocalStateSourceError as qr, parseImageOverrideFlags as qt, coerceServeRequest as r, resolveWatchConfig as ri, setShadowReadyTimeoutMs as rn, verifyJwtViaDiscovery as rr, httpHeadersToEdge as rt, createStudioServeManager as s, availableWebSocketApiIdentifiers as si, createLocalInvokeAgentCoreCommand as sn, invokeRequestAuthorizer as sr, isCloudFrontDistribution as st, addStudioSpecificOptions as t, collectSsmParameterRefs as ti, DEFAULT_SHADOW_READY_TIMEOUT_MS as tn, verifyCognitoJwt as tr, buildEdgeResponseEvent as tt, reinvoke as u, filterWebSocketApisByIdentifiers as ui, A2A_CONTAINER_PORT as un, applyCorsResponseHeaders as ur, pickLambdaEdgeFunctionLogicalId as ut, startStudioServer as v, AGENTCORE_HTTP_PROTOCOL as vi, AGENTCORE_SIGV4_SERVICE as vn, buildHttpApiV2Event as vr, createLocalFileKvsDataSource as vt, createLocalListCommand as w, derivePseudoParametersFromRegion as wi, downloadAndExtractS3Bundle as wn, VtlEvaluationError as wr, resolveAlbTarget as wt, createStudioStore as x, AgentCoreResolutionError as xi, invokeAgentCore as xn, pickResponseTemplate as xr, albStrategy as xt, toStudioTargetGroups as y, AGENTCORE_MCP_PROTOCOL as yi, signAgentCoreInvocation as yn, buildRestV1Event as yr, createUnboundCloudFrontModule as yt, resolveCloudFrontTarget as z, SECOND_ASSUME_MESSAGE as zi, attachStageContext as zn, resolveRuntimeCodeMountPath as zr, parseRestartPolicy as zt };
29869
- //# sourceMappingURL=local-studio-DIB4WEk6.js.map
30338
+ export { applyEdgeResponseResult as $, defaultCredentialsLoader as $n, resolveCfnFallbackRegion as $r, isLocalCdkAssetImage as $t, startAgentCoreHttpServer as A, LocalInvokeBuildError as Ai, renderCodeDockerfile as An, resolveHostGatewayExtraHosts as Ar, addRunTaskSpecificOptions as At, idFromArn as B, withoutKeys as Bi, createFileWatcher as Bn, buildContainerImage as Br, resolveEcsAssumeRoleOption as Bt, addListSpecificOptions as C, AgentCoreResolutionError as Ci, invokeAgentCore as Cn, pickResponseTemplate as Cr, parseLbPortOverrides as Ct, createLocalStartAgentCoreCommand as D, formatStateRemedy as Di, SUPPORTED_CODE_RUNTIMES as Dn, HOST_DOCKER_INTERNAL_GATEWAY as Dr, addStartServiceSpecificOptions as Dt, addStartAgentCoreSpecificOptions as E, derivePseudoParametersFromRegion as Ei, downloadAndExtractS3Bundle as En, VtlEvaluationError as Er, resolveAlbFrontDoor as Et, createLocalStartCloudFrontCommand as F, firstUsableRegion as Fi, addStartApiSpecificOptions as Fn, parseConnectionsPath as Fr, addImageOverrideOptions as Ft, classifyS3Error as G, recordAssumedRole as Gi, availableApiIdentifiers as Gn, substituteAgainstState as Gr, canonicalizeImageOverrideKeys as Gt, createDeployedKvsDataSource as H, assumedRoleRecorded as Hi, buildStageMap as Hn, resolveRuntimeFileExtension as Hr, runEcsServiceEmulator as Ht, normalizeKvsFileKeys as I, keysNotFromTemplate as Ii, createLocalStartApiCommand as In, buildConnectEvent as Ir, buildEcsImageResolutionContext$1 as It, startCloudFrontServer as J, workloadStsClientConfig as Ji, groupRoutesByServer as Jn, substituteEnvVarsFromStateAsync as Jr, parseImageOverrideFlags as Jt, createS3OriginReader as K, resetWorkloadIdentity as Ki, filterRoutesByApiIdentifier as Kn, substituteAgainstStateAsync as Kr, enforceImageOverrideOrphans as Kt, parseKvsFileOverrides as L, keysOverriddenBy as Li, createWatchPredicates as Ln, buildDisconnectEvent as Lr, ecsClusterOption as Lt, startAgentCoreWsBridge as M, DynamicReferenceResolver as Mi, classifySourceChange as Mn, ConnectionRegistry as Mr, MAX_TASKS_SUBNET_RANGE_CAP as Mt, LocalStartCloudFrontError as N, containsDynamicReference as Ni, addInvokeSpecificOptions as Nn, buildMgmtEndpointEnvUrl as Nr, addCommonEcsServiceOptions as Nt, buildAgentCoreServeAuthCheck as O, substituteImagePlaceholders as Oi, buildAgentCoreCodeImage as On, HOST_GATEWAY_MIN_VERSION as Or, createLocalStartServiceCommand as Ot, addStartCloudFrontSpecificOptions as P, deployedFillMayBeSecret as Pi, createLocalInvokeCommand as Pn, handleConnectionsRequest as Pr, addEcsAssumeRoleOptions as Pt, applyEdgeRequestResult as Q, isProxyEnvConfigured as Qi, resolveServiceIntegrationParameters as Qn, rejectExplicitCfnStackWithMultipleStacks as Qr, imageOverrideKeyAliases as Qt, parseOriginOverrides as R, parseDynamicReference as Ri, resolveApiTargetSubset as Rn, buildMessageEvent as Rr, parseMaxTasks as Rt, StudioEventBus as S, AGENTCORE_RUNTIME_TYPE as Si, AGENTCORE_SESSION_ID_HEADER as Sn, evaluateResponseParameters as Sr, createLocalStartAlbCommand as St, formatTargetListing as T, resolveAgentCoreTarget as Ti, waitForAgentCorePing as Tn, tryParseStatus as Tr, isApplicationLoadBalancer as Tt, resolveDeployedKvsArnByName as U, deployAccountStsClientConfig as Ui, materializeLayerFromArn as Un, resolveRuntimeImage as Ur, ImageOverrideError as Ut, resolveKvsModulesForDistribution as V, SECOND_ASSUME_MESSAGE as Vi, attachStageContext as Vn, resolveRuntimeCodeMountPath as Vr, resolveSharedSidecarCredentials as Vt, resolveDeployedOriginBucket as W, readEnvCallerCredentials as Wi, resolveEnvVars$1 as Wn, EcsTaskResolutionError as Wr, buildImageOverrideTag as Wt, serveFromStaticOrigin as X, resolveProfileCredentials as Xi, startApiServer as Xn, createLocalStateProvider as Xr, runImageOverrideBuilds as Xt, resolveErrorResponseCandidates as Y, buildStsClientConfig as Yi, readMtlsMaterialsFromDisk as Yn, LocalStateSourceError as Yr, resolveImageOverrides as Yt, serveLambdaUrlOrigin as Z, buildProxyClientConfig as Zi, resolveSelectionExpression as Zn, isCfnFlagPresent as Zr, describePinnedImageUri as Zt, filterStudioTargetGroups as _, resolveLambdaArnIntrinsic as _i, MCP_PROTOCOL_VERSION as _n, matchRoute as _r, createCloudFrontModule as _t, createLocalStudioCommand as a, resolveWatchConfig as ai, setShadowReadyTimeoutMs as an, verifyJwtViaDiscovery as ar, describeS3OriginDomain as at, renderStudioHtml as b, AGENTCORE_HTTP_PROTOCOL as bi, AGENTCORE_SIGV4_SERVICE as bn, buildHttpApiV2Event as br, addAlbSpecificOptions as bt, startStudioProxy as c, listTargets as ci, addInvokeAgentCoreSpecificOptions as cn, evaluateCachedLambdaPolicy as cr, pickFunctionUrlLogicalIdFromOrigin as ct, createStudioDispatcher as d, discoverWebSocketApisOrThrow as di, invokeAgentCoreWs as dn, attachAuthorizers as dr, pickTargetFunctionLogicalId as dt, resolveCfnRegion as ei, listPinnedTargets as en, buildCognitoJwksUrl as er, buildEdgeRequestEvent as et, filterStudioCustomResources as f, filterWebSocketApisByIdentifiers as fi, A2A_CONTAINER_PORT as fn, applyCorsResponseHeaders as fr, resolveCloudFrontDistribution as ft, annotatePinnedEcsTargets as g, pickRefLogicalId as gi, MCP_PATH as gn, matchPreflight as gr, stripCloudFrontImport as gt, annotateEcsTaskPinnedTargets as h, discoverRoutes as hi, MCP_CONTAINER_PORT as hn, isFunctionUrlOacFronted as hr, runViewerResponse as ht, coerceStopRequest as i, resolveSsmParameters as ii, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as in, verifyJwtAuthorizer as ir, CLOUDFRONT_DISTRIBUTION_TYPE as it, attachAgentCoreWsBridge as j, DynamicReferenceResolutionError as ji, toCmdArgv as jn, bufferToBody as jr, createLocalRunTaskCommand as jt, selectServeInboundAuth as k, tryResolveImageFnJoin as ki, computeCodeImageTag as kn, probeHostGatewaySupport as kr, serviceStrategy as kt, relayServeRequest as l, availableWebSocketApiIdentifiers as li, createLocalInvokeAgentCoreCommand as ln, invokeRequestAuthorizer as lr, pickKvsLogicalIdFromArn as lt, annotateAlbPinnedBackingServices as m, webSocketApiMatchesIdentifier as mi, a2aInvokeOnce as mn, buildCorsConfigFromCloudFrontChain as mr, runViewerRequest as mt, coerceRunRequest as n, CfnLocalStateProvider as ni, CloudMapRegistry as nn, createJwksCache as nr, edgeHeadersToHttp as nt, resolveServeBaseUrl as o, resolveSingleTarget as oi, getContainerNetworkIp as on, buildMethodArn as or, extractKvsAssociations as ot, isCustomResourceLambdaTarget as p, parseSelectionExpressionPath as pi, A2A_PATH as pn, buildCorsConfigByApiId as pr, compileCloudFrontFunction as pt, matchBehavior as q, workloadClientConfig as qi, filterRoutesByApiIdentifiers as qn, substituteEnvVarsFromState as qr, mergeForService as qt, coerceServeRequest as r, collectSsmParameterRefs as ri, DEFAULT_SHADOW_READY_TIMEOUT_MS as rn, verifyCognitoJwt as rr, httpHeadersToEdge as rt, createStudioServeManager as s, countTargets as si, attachContainerLogStreamer as sn, computeRequestIdentityHash as sr, isCloudFrontDistribution as st, addStudioSpecificOptions as t, resolveCfnStackName as ti, buildCloudMapIndex as tn, buildJwksUrlFromIssuer as tr, buildEdgeResponseEvent as tt, reinvoke as u, discoverWebSocketApis as ui, bridgeAgentCoreWs as un, invokeTokenAuthorizer as ur, pickLambdaEdgeFunctionLogicalId as ut, startStudioServer as v, AGENTCORE_A2A_PROTOCOL as vi, mcpInvokeOnce as vn, translateLambdaResponse as vr, createLocalFileKvsDataSource as vt, createLocalListCommand as w, pickAgentCoreCandidateStack as wi, waitForAgentCoreHttpReady as wn, selectIntegrationResponse as wr, resolveAlbTarget as wt, createStudioStore as x, AGENTCORE_MCP_PROTOCOL as xi, signAgentCoreInvocation as xn, buildRestV1Event as xr, albStrategy as xt, toStudioTargetGroups as y, AGENTCORE_AGUI_PROTOCOL as yi, parseSseForJsonRpc as yn, applyAuthorizerOverlay as yr, createUnboundCloudFrontModule as yt, resolveCloudFrontTarget as z, resolveDynamicReferencesInEnv as zi, createAuthorizerCache as zn, architectureToPlatform as zr, parseRestartPolicy as zt };
30339
+ //# sourceMappingURL=local-studio-DCTVck5l.js.map