cdk-local 0.163.0 → 0.163.1

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.
@@ -10504,6 +10504,97 @@ function createFileWatcher(options) {
10504
10504
  } };
10505
10505
  }
10506
10506
 
10507
+ //#endregion
10508
+ //#region src/local/reload-queue.ts
10509
+ async function stopWatchReloads(args) {
10510
+ const closing = args.watcher ? args.watcher.close().catch((err) => args.onWatcherCloseError?.(err)) : Promise.resolve();
10511
+ const draining = args.queue ? args.queue.stop() : Promise.resolve();
10512
+ await closing;
10513
+ await draining;
10514
+ }
10515
+ function createReloadQueue(options) {
10516
+ const abort = new AbortController();
10517
+ let stopped = false;
10518
+ let current;
10519
+ let pending;
10520
+ let started = 0;
10521
+ let finished = 0;
10522
+ const runBatch = async (batch) => {
10523
+ try {
10524
+ await (async () => options.run(batch, abort.signal))();
10525
+ } catch (err) {
10526
+ try {
10527
+ options.onError?.(err);
10528
+ } catch {}
10529
+ }
10530
+ };
10531
+ const start = (paths) => {
10532
+ (async () => {
10533
+ let next = paths;
10534
+ while (next) {
10535
+ started += 1;
10536
+ current = runBatch(next);
10537
+ await current;
10538
+ finished += 1;
10539
+ next = !stopped && pending ? [...pending] : void 0;
10540
+ pending = void 0;
10541
+ }
10542
+ current = void 0;
10543
+ })();
10544
+ };
10545
+ const idle = async () => {
10546
+ const target = started + (pending ? 1 : 0);
10547
+ while (current && finished < target) await current;
10548
+ };
10549
+ return {
10550
+ schedule(changedPaths) {
10551
+ if (stopped) return;
10552
+ if (!current) {
10553
+ start(changedPaths);
10554
+ return;
10555
+ }
10556
+ const wasPending = pending !== void 0;
10557
+ pending ??= /* @__PURE__ */ new Set();
10558
+ for (const p of changedPaths) pending.add(p);
10559
+ if (wasPending) options.onCoalesced?.(pending.size);
10560
+ },
10561
+ async stop() {
10562
+ if (!stopped) {
10563
+ stopped = true;
10564
+ pending = void 0;
10565
+ abort.abort();
10566
+ }
10567
+ await idle();
10568
+ },
10569
+ idle,
10570
+ get stopped() {
10571
+ return stopped;
10572
+ }
10573
+ };
10574
+ }
10575
+
10576
+ //#endregion
10577
+ //#region src/utils/shutdown-signal.ts
10578
+ function createShutdownSignalHandler(options) {
10579
+ const exit = options.exit ?? ((code) => process.exit(code));
10580
+ const writeStderr = options.writeStderr ?? ((text) => void process.stderr.write(text));
10581
+ let count = 0;
10582
+ return (signal, exitCode) => {
10583
+ count += 1;
10584
+ if (count === 1) {
10585
+ options.info(options.stoppingMessage(signal));
10586
+ options.cleanup().then(() => exit(exitCode));
10587
+ return;
10588
+ }
10589
+ if (count === 2) {
10590
+ options.warn(`Received ${signal} again; still shutting down — waiting for the cleanup to remove the containers and networks. Send it once more to force-exit (that skips the cleanup).`);
10591
+ return;
10592
+ }
10593
+ writeStderr(`Force-exit on repeated ${signal}; container cleanup skipped. ${options.forceExitHint}\n`);
10594
+ exit(130);
10595
+ };
10596
+ }
10597
+
10507
10598
  //#endregion
10508
10599
  //#region src/local/authorizer-cache.ts
10509
10600
  function createAuthorizerCache(opts = {}) {
@@ -10897,7 +10988,7 @@ async function localStartApiCommand(targets, options, extraStateProviders) {
10897
10988
  }
10898
10989
  process.stdout.write("^C to stop and clean up containers.\n");
10899
10990
  let watcher;
10900
- let reloadChain = Promise.resolve();
10991
+ let reloadQueue;
10901
10992
  if (options.watch) {
10902
10993
  const watchRoot = process.cwd();
10903
10994
  const { ignored, shouldTrigger, excludePatterns } = createWatchPredicates({
@@ -10905,105 +10996,97 @@ async function localStartApiCommand(targets, options, extraStateProviders) {
10905
10996
  output: options.output,
10906
10997
  watchConfig: resolveWatchConfig()
10907
10998
  });
10999
+ const queue = createReloadQueue({
11000
+ run: (_changedPaths, signal) => reloadAllServers({
11001
+ synthesizeAndBuild,
11002
+ servers,
11003
+ buildPool,
11004
+ logger,
11005
+ signal
11006
+ }),
11007
+ onError: (err) => logger.warn(`Reload failed: ${err instanceof Error ? err.message : String(err)}`)
11008
+ });
11009
+ reloadQueue = queue;
10908
11010
  watcher = createFileWatcher({
10909
11011
  paths: [watchRoot],
10910
11012
  ignored,
10911
11013
  shouldTrigger,
10912
- onChange: () => {
11014
+ onChange: (changedPaths) => {
10913
11015
  logger.info("Detected source change; reloading...");
10914
- reloadChain = reloadChain.then(() => reloadAllServers({
10915
- synthesizeAndBuild,
10916
- servers,
10917
- buildPool,
10918
- logger
10919
- })).catch(() => void 0);
11016
+ queue.schedule(changedPaths);
10920
11017
  }
10921
11018
  });
10922
11019
  logger.info(`Watching ${watchRoot} for source changes (excluding ${excludePatterns.join(", ")}).`);
10923
11020
  }
10924
- let shutdownStarted = false;
10925
- let firstSignal;
10926
- let firstExitCode = 0;
10927
- let forceExitArmed = false;
10928
- const runCleanup = singleFlight(async () => {
10929
- logger.info(`Received ${firstSignal}, shutting down...`);
10930
- if (watcher) try {
10931
- await watcher.close();
10932
- } catch (err) {
10933
- logger.warn(`watcher.close() failed: ${err instanceof Error ? err.message : String(err)}`);
10934
- }
10935
- await Promise.allSettled(wsServers.map(async (ws) => {
10936
- try {
10937
- await ws.attached.close();
10938
- } catch (err) {
10939
- logger.warn(`WebSocket close() failed for ${ws.api.apiLogicalId}: ${err instanceof Error ? err.message : String(err)}`);
10940
- }
10941
- }));
10942
- await Promise.allSettled(servers.map(async ({ server, group }) => {
10943
- try {
10944
- await server.close();
10945
- } catch (err) {
10946
- logger.warn(`server.close() failed for ${group.displayName}: ${err instanceof Error ? err.message : String(err)}`);
10947
- }
10948
- }));
10949
- await Promise.allSettled(wsServers.map(async (ws) => {
10950
- try {
10951
- await ws.server.close();
11021
+ const shutdown = createShutdownSignalHandler({
11022
+ cleanup: singleFlight(async () => {
11023
+ await stopWatchReloads({
11024
+ watcher,
11025
+ queue: reloadQueue,
11026
+ onWatcherCloseError: (err) => logger.warn(`watcher.close() failed: ${err instanceof Error ? err.message : String(err)}`)
11027
+ });
11028
+ await Promise.allSettled(wsServers.map(async (ws) => {
11029
+ try {
11030
+ await ws.attached.close();
11031
+ } catch (err) {
11032
+ logger.warn(`WebSocket close() failed for ${ws.api.apiLogicalId}: ${err instanceof Error ? err.message : String(err)}`);
11033
+ }
11034
+ }));
11035
+ await Promise.allSettled(servers.map(async ({ server, group }) => {
11036
+ try {
11037
+ await server.close();
11038
+ } catch (err) {
11039
+ logger.warn(`server.close() failed for ${group.displayName}: ${err instanceof Error ? err.message : String(err)}`);
11040
+ }
11041
+ }));
11042
+ await Promise.allSettled(wsServers.map(async (ws) => {
11043
+ try {
11044
+ await ws.server.close();
11045
+ } catch (err) {
11046
+ logger.warn(`WebSocket server.close() failed for ${ws.api.apiLogicalId}: ${err instanceof Error ? err.message : String(err)}`);
11047
+ }
11048
+ }));
11049
+ await Promise.allSettled(servers.map(async ({ server, group }) => {
11050
+ try {
11051
+ await server.getServerState().pool.dispose();
11052
+ } catch (err) {
11053
+ logger.warn(`pool.dispose() failed for ${group.displayName}: ${err instanceof Error ? err.message : String(err)}`);
11054
+ }
11055
+ }));
11056
+ await Promise.allSettled(wsServers.map(async (ws) => {
11057
+ try {
11058
+ await ws.server.getServerState().pool.dispose();
11059
+ } catch (err) {
11060
+ logger.warn(`WebSocket pool.dispose() failed for ${ws.api.apiLogicalId}: ${err instanceof Error ? err.message : String(err)}`);
11061
+ }
11062
+ }));
11063
+ for (const dir of inlineTmpDirs) try {
11064
+ rmSync(dir, {
11065
+ recursive: true,
11066
+ force: true
11067
+ });
10952
11068
  } catch (err) {
10953
- logger.warn(`WebSocket server.close() failed for ${ws.api.apiLogicalId}: ${err instanceof Error ? err.message : String(err)}`);
11069
+ logger.warn(`Failed to remove inline-code tmpdir ${dir}: ${err instanceof Error ? err.message : String(err)}`);
10954
11070
  }
10955
- }));
10956
- await Promise.allSettled(servers.map(async ({ server, group }) => {
10957
- try {
10958
- await server.getServerState().pool.dispose();
11071
+ for (const dir of layerTmpDirs) try {
11072
+ rmSync(dir, {
11073
+ recursive: true,
11074
+ force: true
11075
+ });
10959
11076
  } catch (err) {
10960
- logger.warn(`pool.dispose() failed for ${group.displayName}: ${err instanceof Error ? err.message : String(err)}`);
11077
+ logger.warn(`Failed to remove merged-layers tmpdir ${dir}: ${err instanceof Error ? err.message : String(err)}`);
10961
11078
  }
10962
- }));
10963
- await Promise.allSettled(wsServers.map(async (ws) => {
10964
- try {
10965
- await ws.server.getServerState().pool.dispose();
11079
+ if (profileCredsFile) try {
11080
+ await profileCredsFile.dispose();
10966
11081
  } catch (err) {
10967
- logger.warn(`WebSocket pool.dispose() failed for ${ws.api.apiLogicalId}: ${err instanceof Error ? err.message : String(err)}`);
11082
+ logger.warn(`Failed to remove profile credentials tmpdir ${profileCredsFile.hostPath}: ${err instanceof Error ? err.message : String(err)}`);
10968
11083
  }
10969
- }));
10970
- for (const dir of inlineTmpDirs) try {
10971
- rmSync(dir, {
10972
- recursive: true,
10973
- force: true
10974
- });
10975
- } catch (err) {
10976
- logger.warn(`Failed to remove inline-code tmpdir ${dir}: ${err instanceof Error ? err.message : String(err)}`);
10977
- }
10978
- for (const dir of layerTmpDirs) try {
10979
- rmSync(dir, {
10980
- recursive: true,
10981
- force: true
10982
- });
10983
- } catch (err) {
10984
- logger.warn(`Failed to remove merged-layers tmpdir ${dir}: ${err instanceof Error ? err.message : String(err)}`);
10985
- }
10986
- if (profileCredsFile) try {
10987
- await profileCredsFile.dispose();
10988
- } catch (err) {
10989
- logger.warn(`Failed to remove profile credentials tmpdir ${profileCredsFile.hostPath}: ${err instanceof Error ? err.message : String(err)}`);
10990
- }
11084
+ }),
11085
+ stoppingMessage: (signal) => `Received ${signal}, shutting down...`,
11086
+ forceExitHint: `Orphan containers may remain — run 'docker ps --filter name=${getEmbedConfig().resourceNamePrefix}-' and 'docker rm -f' to clean up.`,
11087
+ info: (m) => logger.info(m),
11088
+ warn: (m) => logger.warn(m)
10991
11089
  });
10992
- const shutdown = async (signal, exitCode) => {
10993
- if (shutdownStarted) {
10994
- if (!forceExitArmed) {
10995
- forceExitArmed = true;
10996
- logger.warn(`Received second ${signal}; force-exiting. Orphan containers may remain — run 'docker ps --filter name=${getEmbedConfig().resourceNamePrefix}-' and 'docker rm -f' to clean up.`);
10997
- process.exit(130);
10998
- }
10999
- return;
11000
- }
11001
- shutdownStarted = true;
11002
- firstSignal = signal;
11003
- firstExitCode = exitCode;
11004
- await runCleanup();
11005
- process.exit(firstExitCode);
11006
- };
11007
11090
  process.on("SIGINT", () => {
11008
11091
  shutdown("SIGINT", 130);
11009
11092
  });
@@ -11721,7 +11804,7 @@ function warnSsrfRiskyIntegrations(routes, logger) {
11721
11804
  }
11722
11805
  }
11723
11806
  async function reloadAllServers(args) {
11724
- const { synthesizeAndBuild, servers, buildPool, logger } = args;
11807
+ const { synthesizeAndBuild, servers, buildPool, logger, signal } = args;
11725
11808
  let material;
11726
11809
  try {
11727
11810
  material = await synthesizeAndBuild();
@@ -11729,6 +11812,10 @@ async function reloadAllServers(args) {
11729
11812
  logger.warn(`cdk synth failed during reload; keeping previous version. (${err instanceof Error ? err.message : String(err)})`);
11730
11813
  return;
11731
11814
  }
11815
+ if (signal?.aborted) {
11816
+ logger.info("Reload cancelled: shutting down.");
11817
+ return;
11818
+ }
11732
11819
  const newGroups = groupRoutesByServer(material.routes);
11733
11820
  const newByKey = new Map(newGroups.map((g) => [g.serverKey, g]));
11734
11821
  const oldKeys = new Set(servers.map((s) => s.group.serverKey));
@@ -13423,9 +13510,12 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
13423
13510
  let containerId;
13424
13511
  let stopLogs;
13425
13512
  let sigintHandler;
13513
+ let sigtermHandler;
13426
13514
  let profileCredsFile;
13427
13515
  let stateProvider;
13516
+ let stopWatch;
13428
13517
  const cleanup = singleFlight(async () => {
13518
+ if (stopWatch) await stopWatch();
13429
13519
  if (stateProvider) try {
13430
13520
  stateProvider.dispose();
13431
13521
  } catch (err) {
@@ -13530,10 +13620,17 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
13530
13620
  ...sensitiveEnvKeys.size > 0 && { sensitiveEnvKeys }
13531
13621
  });
13532
13622
  stopLogs = streamLogs(containerId);
13533
- sigintHandler = () => {
13534
- cleanup().then(() => process.exit(130));
13535
- };
13623
+ const onSignal = createShutdownSignalHandler({
13624
+ cleanup,
13625
+ stoppingMessage: (signal) => `Received ${signal}, removing the agent container...`,
13626
+ forceExitHint: `Run 'docker ps -a --filter name=${getEmbedConfig().resourceNamePrefix}-' to find what is left.`,
13627
+ info: (m) => logger.info(m),
13628
+ warn: (m) => logger.warn(m)
13629
+ });
13630
+ sigintHandler = () => onSignal("SIGINT", 130);
13631
+ sigtermHandler = () => onSignal("SIGTERM", 143);
13536
13632
  process.on("SIGINT", sigintHandler);
13633
+ process.on("SIGTERM", sigtermHandler);
13537
13634
  if (isMcp && mcpRequest) {
13538
13635
  logger.info(`MCP request: ${mcpRequest.method}`);
13539
13636
  const mcp = await mcpInvokeOnce(containerHost, hostPort, mcpRequest, { requestTimeoutMs: options.timeout });
@@ -13560,7 +13657,10 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
13560
13657
  synthOpts,
13561
13658
  stacks,
13562
13659
  ...authorization && { authorization },
13563
- rebuild: async () => {
13660
+ registerStop: (stop) => {
13661
+ stopWatch = stop;
13662
+ },
13663
+ rebuild: async (signal) => {
13564
13664
  if (stopLogs) {
13565
13665
  try {
13566
13666
  stopLogs();
@@ -13581,6 +13681,7 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
13581
13681
  await resolveFromS3BucketIntrinsic(newResolved, stateProvider, newLoaded, newImageContext);
13582
13682
  const newImage = await resolveAgentCoreImage(newResolved, options, newLoaded, stateProvider);
13583
13683
  const { env: newEnv, sensitiveEnvKeys: newSensitive } = await buildContainerEnv(newResolved, options, profileCredentials, profileCredsFile, stateProvider, newLoaded, newImageContext);
13684
+ if (signal?.aborted) throw new CdkLocalError("Reload cancelled: shutting down.", "LOCAL_INVOKE_AGENTCORE_WATCH_CANCELLED");
13584
13685
  const newHostPort = await pickFreePort();
13585
13686
  const newName = `${getEmbedConfig().resourceNamePrefix}-agentcore-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
13586
13687
  logger.info(`Reload: starting agent container (image=${newImage}, port=${newHostPort} -> 8080)...`);
@@ -13637,8 +13738,9 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
13637
13738
  emitResult(result);
13638
13739
  }
13639
13740
  } finally {
13640
- if (sigintHandler) process.off("SIGINT", sigintHandler);
13641
13741
  await cleanup();
13742
+ if (sigintHandler) process.off("SIGINT", sigintHandler);
13743
+ if (sigtermHandler) process.off("SIGTERM", sigtermHandler);
13642
13744
  }
13643
13745
  }
13644
13746
  async function resolveInboundAuthorization(resolved, options) {
@@ -14190,7 +14292,7 @@ async function runAgentCoreWatchLoop(args) {
14190
14292
  const waitForPing = args.__waitForPing ?? waitForAgentCorePing;
14191
14293
  let currentHostPort = args.hostPort;
14192
14294
  let currentStacks = args.stacks;
14193
- let reloadChain = Promise.resolve();
14295
+ let reloadQueue;
14194
14296
  let currentAbort;
14195
14297
  let pendingReload = false;
14196
14298
  let reloadFailed = false;
@@ -14211,8 +14313,8 @@ async function runAgentCoreWatchLoop(args) {
14211
14313
  });
14212
14314
  const cdkOutDir = args.options.output;
14213
14315
  const assetLoader = new AssetManifestLoader();
14214
- const watcher = watcherFactory((changedPaths) => {
14215
- reloadChain = reloadChain.then(async () => {
14316
+ reloadQueue = createReloadQueue({
14317
+ run: async (changedPaths, signal) => {
14216
14318
  let verdict = {
14217
14319
  kind: "rebuild",
14218
14320
  reason: "classifier not consulted"
@@ -14247,6 +14349,7 @@ async function runAgentCoreWatchLoop(args) {
14247
14349
  reason: "classifier context unavailable; falling back to rebuild"
14248
14350
  };
14249
14351
  }
14352
+ if (signal.aborted) return;
14250
14353
  pendingReload = true;
14251
14354
  logger.warn("cdkl invoke-agentcore --watch: source change detected; closing the active /ws socket so the client can reconnect to the rebuilt container.");
14252
14355
  currentAbort?.abort();
@@ -14256,20 +14359,32 @@ async function runAgentCoreWatchLoop(args) {
14256
14359
  currentStacks = newStacks;
14257
14360
  logger.info("Reload: soft-reloaded the agent container (docker cp + docker restart; container ID + host port preserved).");
14258
14361
  } else {
14259
- const { containerId: _newId, hostPort: newHostPort, stacks: newStacks } = await args.rebuild();
14362
+ const { containerId: _newId, hostPort: newHostPort, stacks: newStacks } = await args.rebuild(signal);
14260
14363
  currentHostPort = newHostPort;
14261
14364
  currentStacks = newStacks;
14262
14365
  logger.info(`Reload: rebuilt the agent container.`);
14263
14366
  }
14264
14367
  } catch (err) {
14368
+ if (signal.aborted) {
14369
+ logger.info("Reload cancelled: shutting down.");
14370
+ reloadFailed = true;
14371
+ currentAbort?.abort();
14372
+ return;
14373
+ }
14265
14374
  logger.error(`Reload failed: ${err instanceof Error ? err.message : String(err)}. The previous container may already be torn down; exiting --watch loop. Re-run cdkl invoke-agentcore --watch to recover.`);
14266
14375
  reloadFailed = true;
14267
14376
  currentAbort?.abort();
14268
14377
  }
14269
- }).catch((err) => {
14270
- logger.error(`Reload chain threw: ${err instanceof Error ? err.message : String(err)}`);
14271
- });
14378
+ },
14379
+ onError: (err) => logger.error(`Reload chain threw: ${err instanceof Error ? err.message : String(err)}`)
14272
14380
  });
14381
+ const queue = reloadQueue;
14382
+ const watcher = watcherFactory((changedPaths) => queue.schedule(changedPaths));
14383
+ args.registerStop?.(() => stopWatchReloads({
14384
+ watcher,
14385
+ queue,
14386
+ onWatcherCloseError: (err) => logger.warn(`Watcher close failed: ${err instanceof Error ? err.message : String(err)}`)
14387
+ }));
14273
14388
  try {
14274
14389
  let firstIteration = true;
14275
14390
  while (true) {
@@ -14298,17 +14413,17 @@ async function runAgentCoreWatchLoop(args) {
14298
14413
  currentAbort = void 0;
14299
14414
  }
14300
14415
  if (!pendingReload) {
14301
- await reloadChain.catch(() => void 0);
14416
+ await queue.idle();
14302
14417
  if (!pendingReload) break;
14303
14418
  }
14304
- await reloadChain.catch(() => void 0);
14419
+ await queue.idle();
14305
14420
  }
14306
14421
  } finally {
14307
- try {
14308
- await watcher.close();
14309
- } catch (err) {
14310
- logger.warn(`Watcher close failed: ${err instanceof Error ? err.message : String(err)}`);
14311
- }
14422
+ await stopWatchReloads({
14423
+ watcher,
14424
+ queue,
14425
+ onWatcherCloseError: (err) => logger.warn(`Watcher close failed: ${err instanceof Error ? err.message : String(err)}`)
14426
+ });
14312
14427
  }
14313
14428
  }
14314
14429
  const execFileAsync$6 = promisify(execFile);
@@ -19011,24 +19126,21 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
19011
19126
  setShadowReadyTimeoutMs(resolveShadowReadyTimeoutMs(options, getEmbedConfig().envPrefix));
19012
19127
  let perTarget = [];
19013
19128
  let sigintHandler;
19014
- let sigintCount = 0;
19129
+ let sigtermHandler;
19015
19130
  let sharedNetwork;
19016
19131
  let profileCredsFile;
19017
19132
  let frontDoorServers = [];
19018
19133
  let frontDoorByService = /* @__PURE__ */ new Map();
19019
19134
  let frontDoorLambdaRunners = [];
19020
19135
  let watcher;
19021
- let reloadChain = Promise.resolve();
19136
+ let reloadQueue;
19022
19137
  const cleanup = singleFlight(async () => {
19023
- if (watcher) {
19024
- try {
19025
- await watcher.close();
19026
- } catch (err) {
19027
- getLogger().warn(`watcher.close() failed: ${err instanceof Error ? err.message : String(err)}`);
19028
- }
19029
- watcher = void 0;
19030
- }
19031
- await reloadChain.catch(() => void 0);
19138
+ await stopWatchReloads({
19139
+ watcher,
19140
+ queue: reloadQueue,
19141
+ onWatcherCloseError: (err) => getLogger().warn(`watcher.close() failed: ${err instanceof Error ? err.message : String(err)}`)
19142
+ });
19143
+ watcher = void 0;
19032
19144
  await Promise.allSettled(perTarget.map(async (pt) => {
19033
19145
  if (pt.controller) await pt.controller.shutdown();
19034
19146
  else {
@@ -19126,17 +19238,17 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
19126
19238
  frontDoorByService = built.frontDoorByService;
19127
19239
  frontDoorLambdaRunners = built.lambdaRunners;
19128
19240
  }
19129
- sigintHandler = () => {
19130
- sigintCount += 1;
19131
- if (sigintCount >= 2) {
19132
- process.stderr.write("Force-exit on second ^C; container cleanup skipped.\n");
19133
- process.exit(130);
19134
- }
19135
- logger.info("Stopping service(s)...");
19136
- cleanup().then(() => process.exit(130));
19137
- };
19241
+ const onSignal = createShutdownSignalHandler({
19242
+ cleanup,
19243
+ stoppingMessage: () => "Stopping service(s)...",
19244
+ forceExitHint: `Run 'docker ps -a --filter name=${getEmbedConfig().resourceNamePrefix}-' and 'docker network ls --filter name=${getEmbedConfig().resourceNamePrefix}-' to find what is left.`,
19245
+ info: (m) => logger.info(m),
19246
+ warn: (m) => logger.warn(m)
19247
+ });
19248
+ sigintHandler = () => onSignal("SIGINT", 130);
19249
+ sigtermHandler = () => onSignal("SIGTERM", 130);
19138
19250
  process.on("SIGINT", sigintHandler);
19139
- process.on("SIGTERM", sigintHandler);
19251
+ process.on("SIGTERM", sigtermHandler);
19140
19252
  const { tags: imageOverrideTags, overrides: imageOverrideMap } = await resolveAndBuildImageOverrides({
19141
19253
  perTarget: perTarget.map((pt) => pt.boot.target),
19142
19254
  stacks,
@@ -19170,47 +19282,50 @@ async function runEcsServiceEmulator(targets, options, strategy, extraStateProvi
19170
19282
  output: options.output,
19171
19283
  watchConfig: resolveWatchConfig()
19172
19284
  });
19285
+ reloadQueue = createReloadQueue({
19286
+ run: (changedPaths, signal) => reloadAllServices({
19287
+ perTarget,
19288
+ synthesizer,
19289
+ synthOpts,
19290
+ strategy,
19291
+ resolvedTargets,
19292
+ cloudMapIndexByStack,
19293
+ options,
19294
+ discovery,
19295
+ skipPull,
19296
+ extraStateProviders,
19297
+ profileCredsFile,
19298
+ frontDoorByService,
19299
+ changedPaths,
19300
+ imageOverrideTags,
19301
+ imageOverrideMap,
19302
+ logger,
19303
+ signal
19304
+ }),
19305
+ onError: (err) => logger.error(`reloadAllServices threw: ${err instanceof Error ? err.message : String(err)}`),
19306
+ onCoalesced: (n) => logger.debug(`Reload in progress; ${n} changed path(s) queued for one follow-up reload.`)
19307
+ });
19308
+ const queue = reloadQueue;
19173
19309
  watcher = createFileWatcher({
19174
19310
  paths: [watchRoot],
19175
19311
  ignored,
19176
19312
  shouldTrigger,
19177
19313
  onChange: (changedPaths) => {
19178
19314
  logger.info(`Detected source change (${changedPaths.length} path(s)); reloading service(s)...`);
19179
- reloadChain = reloadChain.then(() => reloadAllServices({
19180
- perTarget,
19181
- synthesizer,
19182
- synthOpts,
19183
- strategy,
19184
- resolvedTargets,
19185
- cloudMapIndexByStack,
19186
- options,
19187
- discovery,
19188
- skipPull,
19189
- extraStateProviders,
19190
- profileCredsFile,
19191
- frontDoorByService,
19192
- changedPaths,
19193
- imageOverrideTags,
19194
- imageOverrideMap,
19195
- logger
19196
- })).catch((err) => {
19197
- logger.error(`reloadAllServices threw: ${err instanceof Error ? err.message : String(err)}`);
19198
- });
19315
+ queue.schedule(changedPaths);
19199
19316
  }
19200
19317
  });
19201
19318
  logger.info(`Watching ${watchRoot} for source changes (excluding ${excludePatterns.join(", ")}).`);
19202
19319
  }
19203
19320
  await new Promise(() => {});
19204
19321
  } finally {
19205
- if (sigintHandler) {
19206
- process.off("SIGINT", sigintHandler);
19207
- process.off("SIGTERM", sigintHandler);
19208
- }
19209
19322
  await cleanup();
19323
+ if (sigintHandler) process.off("SIGINT", sigintHandler);
19324
+ if (sigtermHandler) process.off("SIGTERM", sigtermHandler);
19210
19325
  }
19211
19326
  }
19212
19327
  async function reloadAllServices(args) {
19213
- const { perTarget, synthesizer, synthOpts, strategy, resolvedTargets, cloudMapIndexByStack, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorByService, changedPaths, imageOverrideTags: boottimeImageOverrideTags, imageOverrideMap, logger } = args;
19328
+ const { signal, perTarget, synthesizer, synthOpts, strategy, resolvedTargets, cloudMapIndexByStack, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorByService, changedPaths, imageOverrideTags: boottimeImageOverrideTags, imageOverrideMap, logger } = args;
19214
19329
  let stacks;
19215
19330
  try {
19216
19331
  let checkSelectedStacks;
@@ -19220,12 +19335,16 @@ async function reloadAllServices(args) {
19220
19335
  logger.warn(`cdk synth failed during reload; keeping previous version. (${err instanceof Error ? err.message : String(err)})`);
19221
19336
  return;
19222
19337
  }
19338
+ if (reloadCancelled(signal, logger)) return;
19223
19339
  const imageOverrideTags = new Map(boottimeImageOverrideTags);
19224
- for (const [target, entry] of imageOverrideMap.entries()) try {
19225
- const newTag = (await runImageOverrideBuilds(/* @__PURE__ */ new Map([[target, entry]]))).get(target);
19226
- if (newTag !== void 0) imageOverrideTags.set(target, newTag);
19227
- } catch (err) {
19228
- logger.warn(`Reload of ${displayStackName(target)}: --image-override rebuild failed; keeping the previous local image. (${err instanceof Error ? err.message : String(err)})`);
19340
+ for (const [target, entry] of imageOverrideMap.entries()) {
19341
+ if (reloadCancelled(signal, logger)) return;
19342
+ try {
19343
+ const newTag = (await runImageOverrideBuilds(/* @__PURE__ */ new Map([[target, entry]]))).get(target);
19344
+ if (newTag !== void 0) imageOverrideTags.set(target, newTag);
19345
+ } catch (err) {
19346
+ logger.warn(`Reload of ${displayStackName(target)}: --image-override rebuild failed; keeping the previous local image. (${err instanceof Error ? err.message : String(err)})`);
19347
+ }
19229
19348
  }
19230
19349
  const { boots: newBoots, warnings } = strategy.resolveBoots(stacks, resolvedTargets);
19231
19350
  for (const w of warnings) logger.warn(w);
@@ -19239,6 +19358,7 @@ async function reloadAllServices(args) {
19239
19358
  const cdkOutDir = options.output;
19240
19359
  const assetLoader = new AssetManifestLoader();
19241
19360
  for (const pt of perTarget) {
19361
+ if (reloadCancelled(signal, logger)) return;
19242
19362
  const newBoot = newBootByTarget.get(pt.boot.target);
19243
19363
  if (!newBoot) {
19244
19364
  logger.warn(`Reload: target ${displayStackName(pt.boot.target)} no longer resolves to a service in the synthesized app; keeping the previous replica(s) serving.`);
@@ -19287,11 +19407,18 @@ async function reloadAllServices(args) {
19287
19407
  suppressLoadBalancerWarning: strategy.suppressLoadBalancerWarning === true,
19288
19408
  verdict,
19289
19409
  ...imageOverrideTags.get(newBoot.target) !== void 0 && { imageOverrideTag: imageOverrideTags.get(newBoot.target) },
19290
- logger
19410
+ logger,
19411
+ ...signal && { signal }
19291
19412
  });
19292
19413
  }
19414
+ if (reloadCancelled(signal, logger)) return;
19293
19415
  logger.info("Reload complete.");
19294
19416
  }
19417
+ function reloadCancelled(signal, logger) {
19418
+ if (!signal?.aborted) return false;
19419
+ logger.info("Reload cancelled: shutting down.");
19420
+ return true;
19421
+ }
19295
19422
  async function loadAssetContextForTarget(args) {
19296
19423
  const { target, controller, stacks, cdkOutDir, assetLoader, logger } = args;
19297
19424
  const parsed = parseEcsTarget(target);
@@ -19358,7 +19485,7 @@ async function loadAssetContextForTarget(args) {
19358
19485
  };
19359
19486
  }
19360
19487
  async function rollOneTarget(args) {
19361
- const { controller, newBoot, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, verdict, imageOverrideTag, logger } = args;
19488
+ const { signal, controller, newBoot, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, verdict, imageOverrideTag, logger } = args;
19362
19489
  const candidate = pickCandidateStack$1(newBoot.target, stacks);
19363
19490
  const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
19364
19491
  try {
@@ -19383,6 +19510,7 @@ async function rollOneTarget(args) {
19383
19510
  if (verdict.kind === "soft-reload") logger.info(`Reload of ${displayStackName(newBoot.target)}: soft-reloading ${oldReplicas.length} replica(s) one at a time (docker cp source → restart → TCP-ready probe; no rebuild).`);
19384
19511
  else logger.info(`Reload of ${displayStackName(newBoot.target)}: rolling ${oldReplicas.length} replica(s) one at a time (start new shadow → swap registrations → stop old).`);
19385
19512
  for (const oldInstance of oldReplicas) {
19513
+ if (signal?.aborted) return;
19386
19514
  const idx = controller.runState.replicas.indexOf(oldInstance);
19387
19515
  if (idx === -1) {
19388
19516
  logger.warn(`Reload of ${displayStackName(newBoot.target)}: replica r${oldInstance.index} (gen ${oldInstance.generation}) vanished before its roll; skipping.`);
@@ -20083,7 +20211,7 @@ async function localRunTaskCommand(target, options, extraStateProviders) {
20083
20211
  if (options.verbose) logger.setLevel("debug");
20084
20212
  const state = createEcsRunState();
20085
20213
  let sigintHandler;
20086
- let sigintCount = 0;
20214
+ let sigtermHandler;
20087
20215
  let stateProvider;
20088
20216
  let profileCredsFile;
20089
20217
  let cleanupPromise;
@@ -20160,17 +20288,17 @@ async function localRunTaskCommand(target, options, extraStateProviders) {
20160
20288
  }
20161
20289
  }
20162
20290
  } else if (!stateProvider && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against deployed state.");
20163
- sigintHandler = () => {
20164
- sigintCount += 1;
20165
- if (sigintCount >= 2) {
20166
- process.stderr.write("Force-exit on second stop signal; container cleanup skipped.\n");
20167
- process.exit(130);
20168
- }
20169
- logger.info("Stopping task...");
20170
- cleanup().then(() => process.exit(130));
20171
- };
20291
+ const onSignal = createShutdownSignalHandler({
20292
+ cleanup,
20293
+ stoppingMessage: () => "Stopping task...",
20294
+ forceExitHint: `Run 'docker ps -a --filter name=${getEmbedConfig().resourceNamePrefix}-' and 'docker network ls --filter name=${getEmbedConfig().resourceNamePrefix}-' to find what is left.`,
20295
+ info: (m) => logger.info(m),
20296
+ warn: (m) => logger.warn(m)
20297
+ });
20298
+ sigintHandler = () => onSignal("SIGINT", 130);
20299
+ sigtermHandler = () => onSignal("SIGTERM", 130);
20172
20300
  process.on("SIGINT", sigintHandler);
20173
- process.on("SIGTERM", sigintHandler);
20301
+ process.on("SIGTERM", sigtermHandler);
20174
20302
  const effectiveAssumeRole = resolveEcsAssumeRoleOption(options);
20175
20303
  let assumedCredentials;
20176
20304
  let resolvedRoleArn;
@@ -20232,18 +20360,15 @@ async function localRunTaskCommand(target, options, extraStateProviders) {
20232
20360
  const containers = result.state.startedContainers.filter((c) => joinedNames.has(c.name)).map((c) => c.id.slice(0, 12)).join(", ");
20233
20361
  logger.info(`Under the loopback AWS endpoint, task containers share the network of a forwarder container, so that filter does not list them. Task container ids: ${containers}. Remove them before the forwarders (${joinedForwarders.map((f) => f.name).join(", ")}).`);
20234
20362
  }
20235
- sigintCount = 99;
20236
20363
  return;
20237
20364
  }
20238
20365
  if (result.essentialContainerName) logger.info(`Essential container ${displayIdent(result.essentialContainerName)} exited with code ${result.exitCode}.`);
20239
20366
  if (result.exitCode !== 0) process.exitCode = result.exitCode;
20240
20367
  } finally {
20241
- if (sigintHandler) {
20242
- process.off("SIGINT", sigintHandler);
20243
- process.off("SIGTERM", sigintHandler);
20244
- }
20245
20368
  if (stateProvider) stateProvider.dispose();
20246
20369
  if (!options.detach) await cleanup();
20370
+ if (sigintHandler) process.off("SIGINT", sigintHandler);
20371
+ if (sigtermHandler) process.off("SIGTERM", sigtermHandler);
20247
20372
  }
20248
20373
  }
20249
20374
  var PlaceholderAccountError = class PlaceholderAccountError extends Error {
@@ -24414,7 +24539,7 @@ async function localStartCloudFrontCommand(target, options, extraStateProviders,
24414
24539
  process.stdout.write(`CloudFront distribution serving on ${server.url} (${displayIdent(initial.distribution.logicalId)})\n`);
24415
24540
  process.stdout.write("^C to stop.\n");
24416
24541
  let watcher;
24417
- let reloadChain = Promise.resolve();
24542
+ let reloadQueue;
24418
24543
  if (options.watch) {
24419
24544
  const watchRoot = process.cwd();
24420
24545
  const { ignored, shouldTrigger, excludePatterns } = createWatchPredicates({
@@ -24422,37 +24547,37 @@ async function localStartCloudFrontCommand(target, options, extraStateProviders,
24422
24547
  output: options.output,
24423
24548
  watchConfig: resolveWatchConfig()
24424
24549
  });
24550
+ const queue = createReloadQueue({ run: async (_changedPaths, signal) => {
24551
+ try {
24552
+ const reloaded = await synthAndResolve();
24553
+ if (signal.aborted) return;
24554
+ annotateDeployedS3Origins(reloaded.distribution, deployedS3.buckets);
24555
+ for (const reader of deployedS3.readers.values()) reader.clearCache();
24556
+ warnUnsupported(reloaded.distribution, extraStateProviders);
24557
+ await attachKvsModules(reloaded.distribution, reloaded.stacks, options, profileCredentials, logger, extraStateProviders);
24558
+ server.update(reloaded.distribution);
24559
+ logger.info("Reload complete.");
24560
+ } catch (err) {
24561
+ logger.warn(`Reload failed; keeping the previous version serving: ${err instanceof Error ? err.message : String(err)}`);
24562
+ }
24563
+ } });
24564
+ reloadQueue = queue;
24425
24565
  watcher = createFileWatcher({
24426
24566
  paths: [watchRoot],
24427
24567
  ignored,
24428
24568
  shouldTrigger,
24429
- onChange: () => {
24569
+ onChange: (changedPaths) => {
24430
24570
  logger.info("Detected source change; reloading...");
24431
- reloadChain = reloadChain.then(async () => {
24432
- try {
24433
- const reloaded = await synthAndResolve();
24434
- annotateDeployedS3Origins(reloaded.distribution, deployedS3.buckets);
24435
- for (const reader of deployedS3.readers.values()) reader.clearCache();
24436
- warnUnsupported(reloaded.distribution, extraStateProviders);
24437
- await attachKvsModules(reloaded.distribution, reloaded.stacks, options, profileCredentials, logger, extraStateProviders);
24438
- server.update(reloaded.distribution);
24439
- logger.info("Reload complete.");
24440
- } catch (err) {
24441
- logger.warn(`Reload failed; keeping the previous version serving: ${err instanceof Error ? err.message : String(err)}`);
24442
- }
24443
- }).catch(() => void 0);
24571
+ queue.schedule(changedPaths);
24444
24572
  }
24445
24573
  });
24446
24574
  logger.info(`Watching ${watchRoot} for source changes (excluding ${excludePatterns.join(", ")}).`);
24447
24575
  }
24448
- let shuttingDown = false;
24449
- const shutdown = async (signal, exitCode) => {
24450
- if (shuttingDown) return;
24451
- shuttingDown = true;
24452
- logger.info(`Received ${signal}, shutting down...`);
24453
- if (watcher) try {
24454
- await watcher.close();
24455
- } catch {}
24576
+ const teardown = async () => {
24577
+ await stopWatchReloads({
24578
+ watcher,
24579
+ queue: reloadQueue
24580
+ });
24456
24581
  try {
24457
24582
  await server.close();
24458
24583
  } catch {}
@@ -24462,10 +24587,16 @@ async function localStartCloudFrontCommand(target, options, extraStateProviders,
24462
24587
  logger.debug(`Web function server teardown failed: ${err instanceof Error ? err.message : String(err)}`);
24463
24588
  }))]);
24464
24589
  await Promise.all([...deployedS3.readers.values()].map((reader) => reader.close()));
24465
- process.exit(exitCode);
24466
24590
  };
24467
- process.on("SIGINT", () => void shutdown("SIGINT", 130));
24468
- process.on("SIGTERM", () => void shutdown("SIGTERM", 0));
24591
+ const shutdown = createShutdownSignalHandler({
24592
+ cleanup: () => teardown().catch(() => void 0),
24593
+ stoppingMessage: (signal) => `Received ${signal}, shutting down...`,
24594
+ forceExitHint: `Run 'docker ps -a --filter name=${getEmbedConfig().resourceNamePrefix}-' to find what is left.`,
24595
+ info: (m) => logger.info(m),
24596
+ warn: (m) => logger.warn(m)
24597
+ });
24598
+ process.on("SIGINT", () => shutdown("SIGINT", 130));
24599
+ process.on("SIGTERM", () => shutdown("SIGTERM", 0));
24469
24600
  await new Promise(() => void 0);
24470
24601
  }
24471
24602
  function createLocalStartCloudFrontCommand(opts = {}) {
@@ -24836,20 +24967,17 @@ async function localStartAgentCoreCommand(target, options, extraStateProviders,
24836
24967
  let profileCredsFile;
24837
24968
  let stateProvider;
24838
24969
  let watcher;
24839
- let reloadChain = Promise.resolve();
24970
+ let reloadQueue;
24840
24971
  let shuttingDown = false;
24841
24972
  let tornDown = false;
24842
24973
  const teardown = async () => {
24843
24974
  if (tornDown) return;
24844
24975
  tornDown = true;
24845
- if (watcher) try {
24846
- await watcher.close();
24847
- } catch (err) {
24848
- logger.debug(`watcher close failed: ${err instanceof Error ? err.message : String(err)}`);
24849
- }
24850
- try {
24851
- await reloadChain;
24852
- } catch {}
24976
+ await stopWatchReloads({
24977
+ watcher,
24978
+ queue: reloadQueue,
24979
+ onWatcherCloseError: (err) => logger.debug(`watcher close failed: ${err instanceof Error ? err.message : String(err)}`)
24980
+ });
24853
24981
  if (server) try {
24854
24982
  await server.close();
24855
24983
  } catch (err) {
@@ -24934,15 +25062,18 @@ async function localStartAgentCoreCommand(target, options, extraStateProviders,
24934
25062
  ...sensitiveEnvKeys.size > 0 && { sensitiveEnvKeys }
24935
25063
  });
24936
25064
  stopLogs = streamLogs(containerId);
24937
- const shutdown = async (signal, exitCode) => {
24938
- if (shuttingDown) return;
24939
- shuttingDown = true;
24940
- logger.info(`Received ${signal}, shutting down...`);
24941
- await teardown();
24942
- process.exit(exitCode);
24943
- };
24944
- process.on("SIGINT", () => void shutdown("SIGINT", 130));
24945
- process.on("SIGTERM", () => void shutdown("SIGTERM", 0));
25065
+ const shutdown = createShutdownSignalHandler({
25066
+ cleanup: async () => {
25067
+ shuttingDown = true;
25068
+ await teardown().catch(() => void 0);
25069
+ },
25070
+ stoppingMessage: (signal) => `Received ${signal}, shutting down...`,
25071
+ forceExitHint: `Run 'docker ps -a --filter name=${getEmbedConfig().resourceNamePrefix}-' to find what is left.`,
25072
+ info: (m) => logger.info(m),
25073
+ warn: (m) => logger.warn(m)
25074
+ });
25075
+ process.on("SIGINT", () => shutdown("SIGINT", 130));
25076
+ process.on("SIGTERM", () => shutdown("SIGTERM", 0));
24946
25077
  try {
24947
25078
  if (plan.readyPath === void 0) await waitForAgentCorePing(containerHost, containerHostPort, options.timeout);
24948
25079
  else await waitForAgentCoreHttpReady(containerHost, containerHostPort, plan.readyPath, options.timeout);
@@ -25012,95 +25143,99 @@ async function localStartAgentCoreCommand(target, options, extraStateProviders,
25012
25143
  watchConfig: resolveWatchConfig()
25013
25144
  });
25014
25145
  logger.info(`Watching ${watchRoot} for source changes (excluding ${excludePatterns.join(", ")}). The warm container reloads in place; the serve stays up.`);
25146
+ reloadQueue = createReloadQueue({
25147
+ run: async (changedPaths) => {
25148
+ if (tornDown || shuttingDown) return;
25149
+ let freshStacks;
25150
+ let freshResolved;
25151
+ let freshLoaded;
25152
+ let freshImageContext;
25153
+ let verdict;
25154
+ let nextAssetHash = currentAssetHash;
25155
+ try {
25156
+ const synthRes = await synthesizer.synthesize(synthOpts);
25157
+ freshStacks = synthRes.stacks;
25158
+ synthRes.checkSelectedStacks?.([resolved.stack.stackName]);
25159
+ const freshCandidate = pickAgentCoreCandidateStack(resolvedTarget, freshStacks);
25160
+ const ctx = stateProvider && freshCandidate ? await buildAgentCoreImageContext(freshCandidate, stateProvider, options) : {
25161
+ context: void 0,
25162
+ loaded: void 0
25163
+ };
25164
+ freshImageContext = ctx.context;
25165
+ freshLoaded = ctx.loaded;
25166
+ freshResolved = resolveAgentCoreTarget(resolvedTarget, freshStacks, freshImageContext);
25167
+ await resolveFromS3BucketIntrinsic(freshResolved, stateProvider, freshLoaded, freshImageContext);
25168
+ const assetCtx = await loadAgentCoreAssetContext({
25169
+ resolvedTarget,
25170
+ resolved: freshResolved,
25171
+ stacks: freshStacks,
25172
+ cdkOutDir,
25173
+ assetLoader,
25174
+ oldAssetHash: currentAssetHash
25175
+ });
25176
+ if (assetCtx?.newAssetHash !== void 0) nextAssetHash = assetCtx.newAssetHash;
25177
+ verdict = classifySourceChange(changedPaths, assetCtx);
25178
+ logger.info(`Detected source change (${changedPaths.length} path(s)); verdict=${verdict.kind} (${verdict.reason}).`);
25179
+ } catch (err) {
25180
+ logger.error(`Reload: re-synth / classify failed (${err instanceof Error ? err.message : String(err)}); skipping this change. The serve stays up against the current container.`);
25181
+ return;
25182
+ }
25183
+ if (tornDown || shuttingDown) return;
25184
+ try {
25185
+ if (verdict.kind === "soft-reload") {
25186
+ if (!containerId) throw new CdkLocalError("Reload: no live container to docker cp / docker restart into.", "LOCAL_START_AGENTCORE_WATCH_NO_CONTAINER");
25187
+ await softReloadAgentContainer(containerId, verdict.newAssetSourceDir);
25188
+ await waitReady(containerHostPort);
25189
+ logger.info("Reload: soft-reloaded the agent container (docker cp + docker restart; container ID + host port preserved).");
25190
+ } else {
25191
+ if (stopLogs) {
25192
+ try {
25193
+ stopLogs();
25194
+ } catch {}
25195
+ stopLogs = void 0;
25196
+ }
25197
+ if (containerId) {
25198
+ await removeContainer(containerId);
25199
+ containerId = void 0;
25200
+ }
25201
+ const newImage = await resolveAgentCoreImage(freshResolved, options, freshLoaded, stateProvider);
25202
+ const { env: newEnv, sensitiveEnvKeys: newSensitive } = await buildContainerEnv(freshResolved, options, profileCredentials, profileCredsFile, stateProvider, freshLoaded, freshImageContext);
25203
+ if (tornDown || shuttingDown) return;
25204
+ const newPort = await pickFreePort();
25205
+ const newName = `${getEmbedConfig().resourceNamePrefix}-agentcore-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
25206
+ logger.info(`Reload: starting agent container (image=${newImage}, port=${newPort} -> ${plan.containerPortLabel})...`);
25207
+ containerId = await runDetached({
25208
+ image: newImage,
25209
+ mounts: [],
25210
+ env: newEnv,
25211
+ cmd: [],
25212
+ hostPort: newPort,
25213
+ host: containerHost,
25214
+ platform: options.platform,
25215
+ name: newName,
25216
+ ...plan.containerPort !== void 0 && { containerPort: plan.containerPort },
25217
+ ...newSensitive.size > 0 && { sensitiveEnvKeys: newSensitive }
25218
+ });
25219
+ stopLogs = streamLogs(containerId);
25220
+ containerHostPort = newPort;
25221
+ await waitReady(newPort);
25222
+ server?.setContainerPort(newPort);
25223
+ logger.info(`Reload: rebuilt the agent container; serve re-pointed to port ${newPort}.`);
25224
+ }
25225
+ currentAssetHash = nextAssetHash;
25226
+ logger.info("Reload complete.");
25227
+ } catch (err) {
25228
+ logger.error(`Reload failed: ${err instanceof Error ? err.message : String(err)}. The serve stays up; edit + save again to retry.`);
25229
+ }
25230
+ },
25231
+ onError: (err) => logger.error(`Reload failed: ${err instanceof Error ? err.message : String(err)}.`)
25232
+ });
25233
+ const queue = reloadQueue;
25015
25234
  watcher = createFileWatcher({
25016
25235
  paths: [watchRoot],
25017
25236
  ignored,
25018
25237
  shouldTrigger,
25019
- onChange: (changedPaths) => {
25020
- reloadChain = reloadChain.then(async () => {
25021
- if (tornDown || shuttingDown) return;
25022
- let freshStacks;
25023
- let freshResolved;
25024
- let freshLoaded;
25025
- let freshImageContext;
25026
- let verdict;
25027
- let nextAssetHash = currentAssetHash;
25028
- try {
25029
- const synthRes = await synthesizer.synthesize(synthOpts);
25030
- freshStacks = synthRes.stacks;
25031
- synthRes.checkSelectedStacks?.([resolved.stack.stackName]);
25032
- const freshCandidate = pickAgentCoreCandidateStack(resolvedTarget, freshStacks);
25033
- const ctx = stateProvider && freshCandidate ? await buildAgentCoreImageContext(freshCandidate, stateProvider, options) : {
25034
- context: void 0,
25035
- loaded: void 0
25036
- };
25037
- freshImageContext = ctx.context;
25038
- freshLoaded = ctx.loaded;
25039
- freshResolved = resolveAgentCoreTarget(resolvedTarget, freshStacks, freshImageContext);
25040
- await resolveFromS3BucketIntrinsic(freshResolved, stateProvider, freshLoaded, freshImageContext);
25041
- const assetCtx = await loadAgentCoreAssetContext({
25042
- resolvedTarget,
25043
- resolved: freshResolved,
25044
- stacks: freshStacks,
25045
- cdkOutDir,
25046
- assetLoader,
25047
- oldAssetHash: currentAssetHash
25048
- });
25049
- if (assetCtx?.newAssetHash !== void 0) nextAssetHash = assetCtx.newAssetHash;
25050
- verdict = classifySourceChange(changedPaths, assetCtx);
25051
- logger.info(`Detected source change (${changedPaths.length} path(s)); verdict=${verdict.kind} (${verdict.reason}).`);
25052
- } catch (err) {
25053
- logger.error(`Reload: re-synth / classify failed (${err instanceof Error ? err.message : String(err)}); skipping this change. The serve stays up against the current container.`);
25054
- return;
25055
- }
25056
- if (tornDown || shuttingDown) return;
25057
- try {
25058
- if (verdict.kind === "soft-reload") {
25059
- if (!containerId) throw new CdkLocalError("Reload: no live container to docker cp / docker restart into.", "LOCAL_START_AGENTCORE_WATCH_NO_CONTAINER");
25060
- await softReloadAgentContainer(containerId, verdict.newAssetSourceDir);
25061
- await waitReady(containerHostPort);
25062
- logger.info("Reload: soft-reloaded the agent container (docker cp + docker restart; container ID + host port preserved).");
25063
- } else {
25064
- if (stopLogs) {
25065
- try {
25066
- stopLogs();
25067
- } catch {}
25068
- stopLogs = void 0;
25069
- }
25070
- if (containerId) {
25071
- await removeContainer(containerId);
25072
- containerId = void 0;
25073
- }
25074
- const newImage = await resolveAgentCoreImage(freshResolved, options, freshLoaded, stateProvider);
25075
- const { env: newEnv, sensitiveEnvKeys: newSensitive } = await buildContainerEnv(freshResolved, options, profileCredentials, profileCredsFile, stateProvider, freshLoaded, freshImageContext);
25076
- const newPort = await pickFreePort();
25077
- const newName = `${getEmbedConfig().resourceNamePrefix}-agentcore-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
25078
- logger.info(`Reload: starting agent container (image=${newImage}, port=${newPort} -> ${plan.containerPortLabel})...`);
25079
- containerId = await runDetached({
25080
- image: newImage,
25081
- mounts: [],
25082
- env: newEnv,
25083
- cmd: [],
25084
- hostPort: newPort,
25085
- host: containerHost,
25086
- platform: options.platform,
25087
- name: newName,
25088
- ...plan.containerPort !== void 0 && { containerPort: plan.containerPort },
25089
- ...newSensitive.size > 0 && { sensitiveEnvKeys: newSensitive }
25090
- });
25091
- stopLogs = streamLogs(containerId);
25092
- containerHostPort = newPort;
25093
- await waitReady(newPort);
25094
- server?.setContainerPort(newPort);
25095
- logger.info(`Reload: rebuilt the agent container; serve re-pointed to port ${newPort}.`);
25096
- }
25097
- currentAssetHash = nextAssetHash;
25098
- logger.info("Reload complete.");
25099
- } catch (err) {
25100
- logger.error(`Reload failed: ${err instanceof Error ? err.message : String(err)}. The serve stays up; edit + save again to retry.`);
25101
- }
25102
- });
25103
- }
25238
+ onChange: (changedPaths) => queue.schedule(changedPaths)
25104
25239
  });
25105
25240
  }
25106
25241
  await new Promise(() => void 0);
@@ -31773,4 +31908,4 @@ function addStudioSpecificOptions(cmd) {
31773
31908
 
31774
31909
  //#endregion
31775
31910
  export { postHook as $, addAwsEndpointOptions as $a, parseConnectionsPath as $i, addImageOverrideOptions as $n, addStartApiSpecificOptions as $r, classifyS3Error as $t, microvmFidelityWarnings as A, parseSelectionExpressionPath as Aa, applyCorsResponseHeaders as Ai, stripCloudFrontImport as An, A2A_CONTAINER_PORT as Ar, readContainerLogTail as At, MICROVM_WS_BASE_PROTOCOL as B, AgentCoreResolutionError as Ba, evaluateResponseParameters as Bi, createLocalStartAlbCommand as Bn, AGENTCORE_SESSION_ID_HEADER as Br, parseWebFunctionBuildConfig as Bt, addListSpecificOptions as C, resolveSingleTarget as Ca, verifyJwtViaDiscovery as Ci, WEB_FUNCTION_REVISION_TYPE as Cn, setShadowReadyTimeoutMs as Cr, parseKvsFileOverrides as Ct, buildMicrovmLambdaView as D, discoverWebSocketApis as Da, invokeRequestAuthorizer as Di, compileCloudFrontFunction as Dn, createLocalInvokeAgentCoreCommand as Dr, WEB_FUNCTION_CONTAINER_PORT as Dt, addStartMicrovmSpecificOptions as E, availableWebSocketApiIdentifiers as Ea, evaluateCachedLambdaPolicy as Ei, resolveWebFunctionTarget as En, addInvokeAgentCoreSpecificOptions as Er, RecentOutput as Et, microvmImageTag as F, AGENTCORE_A2A_PROTOCOL as Fa, matchRoute as Fi, findFileAssetForObjectKey as Fn, MCP_PROTOCOL_VERSION as Fr, buildNodeEntryFileStart as Ft, startMicrovmFrontDoor as G, containsDynamicReference as Ga, HOST_DOCKER_INTERNAL_GATEWAY as Gi, addStartServiceSpecificOptions as Gn, SUPPORTED_CODE_RUNTIMES as Gr, readWebFunctionServiceConfig as Gt, PROXY_PORT_HEADER as H, resolveAgentCoreTarget as Ha, selectIntegrationResponse as Hi, resolveAlbTarget as Hn, waitForAgentCoreHttpReady as Hr, WEB_FUNCTION_HTTP_ENDPOINT_ENV as Ht, planMicrovmPorts as I, AGENTCORE_AGUI_PROTOCOL as Ia, translateLambdaResponse as Ii, isBucketDeploymentResource as In, mcpInvokeOnce as Ir, defaultWebFunctionPlatform as It, MICROVM_HOOK_PATH_PREFIX as J, keysNotFromTemplate as Ja, resolveHostGatewayExtraHosts as Ji, addRunTaskSpecificOptions as Jn, renderCodeDockerfile as Jr, idFromArn as Jt, stripMicrovmProxyHeaders as K, deployedFillMayBeSecret as Ka, HOST_GATEWAY_MIN_VERSION as Ki, createLocalStartServiceCommand as Kn, buildAgentCoreCodeImage as Kr, webFunctionAssumeRole as Kt, readExposedPorts as L, AGENTCORE_HTTP_PROTOCOL as La, applyAuthorizerOverlay as Li, resolveBucketDeploymentObjectKey as Ln, parseSseForJsonRpc as Lr, resolveWebFunctionRuntime as Lt, DEFAULT_TERMINATE_HOOK_TIMEOUT_MS as M, discoverRoutes as Ma, buildCorsConfigFromCloudFrontChain as Mi, createLocalFileKvsDataSource as Mn, a2aInvokeOnce as Mr, waitForWebFunctionReady as Mt, LocalMicrovm as N, pickRefLogicalId as Na, isFunctionUrlOacFronted as Ni, createUnboundCloudFrontModule as Nn, MCP_CONTAINER_PORT as Nr, watchContainerExit as Nt, createLocalStartMicrovmCommand as O, discoverWebSocketApisOrThrow as Oa, invokeTokenAuthorizer as Oi, runViewerRequest as On, bridgeAgentCoreWs as Or, containerFailure as Ot, buildMicrovmImage as P, resolveLambdaArnIntrinsic as Pa, matchPreflight as Pi, bucketDeploymentSourceObjectKeys as Pn, MCP_PATH as Pr, WEB_FUNCTION_RUNTIMES as Pt, pollBuildHook as Q, withoutKeys as Qa, handleConnectionsRequest as Qi, addEcsAssumeRoleOptions as Qn, createLocalInvokeCommand as Qr, resolveDeployedOriginBucket as Qt, setContainerPaused as R, AGENTCORE_MCP_PROTOCOL as Ra, buildHttpApiV2Event as Ri, addAlbSpecificOptions as Rn, AGENTCORE_SIGV4_SERVICE as Rr, supportedWebFunctionRuntimes as Rt, StudioEventBus as S, resolveWatchConfig as Sa, verifyJwtAuthorizer as Si, WEB_FUNCTION_ENDPOINT_TYPE as Sn, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as Sr, normalizeKvsFileKeys as St, formatTargetListing as T, listTargets as Ta, computeRequestIdentityHash as Ti, WebFunctionResolutionError as Tn, attachContainerLogStreamer as Tr, resolveCloudFrontTarget as Tt, rewriteUpgradeResponseHead as U, DynamicReferenceResolutionError as Ua, tryParseStatus as Ui, isApplicationLoadBalancer as Un, waitForAgentCorePing as Ur, buildWebFunctionLambdaView as Ut, MicrovmStateError as V, pickAgentCoreCandidateStack as Va, pickResponseTemplate as Vi, parseLbPortOverrides as Vn, invokeAgentCore as Vr, resolveWebFunctionArtifact as Vt, selectTargetPort as W, DynamicReferenceResolver as Wa, VtlEvaluationError as Wi, resolveAlbFrontDoor as Wn, downloadAndExtractS3Bundle as Wr, finalizeWebFunctionEnv as Wt, callRuntimeHook as X, parseDynamicReference as Xa, ConnectionRegistry as Xi, MAX_TASKS_SUBNET_RANGE_CAP as Xn, classifySourceChange as Xr, createDeployedKvsDataSource as Xt, MicrovmHookError as Y, keysOverriddenBy as Ya, bufferToBody as Yi, createLocalRunTaskCommand as Yn, toCmdArgv as Yr, resolveKvsModulesForDistribution as Yt, generateMicrovmId as Z, resolveDynamicReferencesInEnv as Za, buildMgmtEndpointEnvUrl as Zi, addCommonEcsServiceOptions as Zn, addInvokeSpecificOptions as Zr, resolveDeployedKvsArnByName as Zt, filterStudioTargetGroups as _, resolveCfnRegion as _a, defaultCredentialsLoader as _i, pickKvsLogicalIdFromArn as _n, isLocalCdkAssetImage as _r, attachAgentCoreWsBridge as _t, createLocalStudioCommand as a, resolveRuntimeImage as aa, attachStageContext as ai, serveLambdaUrlOrigin as an, resolveSharedSidecarCredentials as ar, disposeMicrovmCodeArtifact as at, renderStudioHtml as b, collectSsmParameterRefs as ba, createJwksCache as bi, pickWebFunctionEndpointLogicalIdFromOrigin as bn, CloudMapRegistry as br, addStartCloudFrontSpecificOptions as bt, startStudioProxy as c, substituteAgainstState as ca, resolveEnvVars$1 as ci, buildEdgeRequestEvent as cn, buildImageOverrideTag as cr, resolveMicrovmImageTarget as ct, createStudioDispatcher as d, substituteEnvVarsFromStateAsync as da, filterRoutesByApiIdentifiers as di, httpHeadersToEdge as dn, mergeForService as dr, createLocalStartWebFunctionCommand as dt, buildConnectEvent as ea, createLocalStartApiCommand as ei, createS3OriginReader as en, awsEndpointOptions as eo, buildEcsImageResolutionContext$1 as er, runHookBody as et, filterStudioCustomResources as f, LocalStateSourceError as fa, groupRoutesByServer as fi, CLOUDFRONT_DISTRIBUTION_TYPE as fn, parseImageOverrideFlags as fr, addStartAgentCoreSpecificOptions as ft, annotatePinnedEcsTargets as g, resolveCfnFallbackRegion as ga, resolveServiceIntegrationParameters as gi, pickFunctionUrlLogicalIdFromOrigin as gn, imageOverrideKeyAliases as gr, startAgentCoreHttpServer as gt, annotateEcsTaskPinnedTargets as h, rejectExplicitCfnStackWithMultipleStacks as ha, resolveSelectionExpression as hi, isCloudFrontDistribution as hn, describePinnedImageUri as hr, selectServeInboundAuth as ht, coerceStopRequest as i, resolveRuntimeFileExtension as ia, createFileWatcher as ii, serveFromStaticOrigin as in, resolveEcsAssumeRoleOption as ir, MicrovmResolutionError as it, readRunHookPayload as j, webSocketApiMatchesIdentifier as ja, buildCorsConfigByApiId as ji, createCloudFrontModule as jn, A2A_PATH as jr, startWebFunctionContainer as jt, finalizeMicrovmEnv as k, filterWebSocketApisByIdentifiers as ka, attachAuthorizers as ki, runViewerResponse as kn, invokeAgentCoreWs as kr, planWebFunctionContainer as kt, relayServeRequest as l, substituteAgainstStateAsync as la, availableApiIdentifiers as li, buildEdgeResponseEvent as ln, canonicalizeImageOverrideKeys as lr, s3KeyOfUri as lt, annotateAlbPinnedBackingServices as m, isCfnFlagPresent as ma, startApiServer as mi, extractKvsAssociations as mn, runImageOverrideBuilds as mr, buildAgentCoreServeAuthCheck as mt, coerceRunRequest as n, buildMessageEvent as na, resolveApiTargetSubset as ni, startCloudFrontServer as nn, parseMaxTasks as nr, MICROVM_DEFAULT_INGRESS_PORT as nt, resolveServeBaseUrl as o, EcsTaskResolutionError as oa, buildStageMap as oi, applyEdgeRequestResult as on, runEcsServiceEmulator as or, readMicrovmImageConfig as ot, isCustomResourceLambdaTarget as p, createLocalStateProvider as pa, readMtlsMaterialsFromDisk as pi, describeS3OriginDomain as pn, resolveImageOverrides as pr, createLocalStartAgentCoreCommand as pt, MAX_RUN_HOOK_PAYLOAD_BYTES as q, firstUsableRegion as qa, probeHostGatewaySupport as qi, serviceStrategy as qn, computeCodeImageTag as qr, webFunctionFidelityWarnings as qt, coerceServeRequest as r, resolveRuntimeCodeMountPath as ra, createAuthorizerCache as ri, resolveErrorResponseCandidates as rn, parseRestartPolicy as rr, MICROVM_IMAGE_TYPE as rt, createStudioServeManager as s, assertEcsTaskImagesResolvable as sa, materializeLayerFromArn as si, applyEdgeResponseResult as sn, ImageOverrideError as sr, resolveMicrovmCodeArtifact as st, addStudioSpecificOptions as t, buildDisconnectEvent as ta, createWatchPredicates as ti, matchBehavior as tn, ecsClusterOption as tr, waitForAppAnswer as tt, reinvoke as u, substituteEnvVarsFromState as ua, filterRoutesByApiIdentifier as ui, edgeHeadersToHttp as un, enforceImageOverrideOrphans as ur, addStartWebFunctionSpecificOptions as ut, startStudioServer as v, resolveCfnStackName as va, buildCognitoJwksUrl as vi, pickLambdaEdgeFunctionLogicalId as vn, listPinnedTargets as vr, startAgentCoreWsBridge as vt, createLocalListCommand as w, countTargets as wa, buildMethodArn as wi, WEB_FUNCTION_TYPE as wn, getContainerNetworkIp as wr, parseOriginOverrides as wt, createStudioStore as x, resolveSsmParameters as xa, verifyCognitoJwt as xi, resolveCloudFrontDistribution as xn, DEFAULT_SHADOW_READY_TIMEOUT_MS as xr, createLocalStartCloudFrontCommand as xt, toStudioTargetGroups as y, CfnLocalStateProvider as ya, buildJwksUrlFromIssuer as yi, pickTargetFunctionLogicalId as yn, buildCloudMapIndex as yr, LocalStartCloudFrontError as yt, MICROVM_CONTROL_PATH as z, AGENTCORE_RUNTIME_TYPE as za, buildRestV1Event as zi, albStrategy as zn, signAgentCoreInvocation as zr, disposeWebFunctionArtifact as zt };
31776
- //# sourceMappingURL=local-studio-aUkwJL4M.js.map
31911
+ //# sourceMappingURL=local-studio-CFf6tO1e.js.map