cdk-local 0.75.0 → 0.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6483,7 +6483,7 @@ function resolveRuntimeCodeMountPath(runtime) {
6483
6483
 
6484
6484
  //#endregion
6485
6485
  //#region src/local/docker-runner.ts
6486
- const execFileAsync$4 = promisify(execFile);
6486
+ const execFileAsync$5 = promisify(execFile);
6487
6487
  /**
6488
6488
  * Wraps `docker pull` / `docker run` / `docker rm` for `cdkl invoke`.
6489
6489
  *
@@ -6580,7 +6580,7 @@ async function runDetached(opts) {
6580
6580
  args.push(opts.image, ...entryPointTail, ...opts.cmd);
6581
6581
  getLogger().child("docker").debug(`${getDockerCmd()} ${redactAwsCredentialsInArgs(args).join(" ")}`);
6582
6582
  try {
6583
- const { stdout } = await execFileAsync$4(getDockerCmd(), args, {
6583
+ const { stdout } = await execFileAsync$5(getDockerCmd(), args, {
6584
6584
  maxBuffer: 10 * 1024 * 1024,
6585
6585
  ...execEnvForSecrets(passthroughEnv)
6586
6586
  });
@@ -6620,7 +6620,7 @@ async function removeContainer(containerId) {
6620
6620
  if (!containerId) return;
6621
6621
  const logger = getLogger().child("docker");
6622
6622
  try {
6623
- await execFileAsync$4(getDockerCmd(), [
6623
+ await execFileAsync$5(getDockerCmd(), [
6624
6624
  "rm",
6625
6625
  "-f",
6626
6626
  containerId
@@ -6639,7 +6639,7 @@ async function removeContainer(containerId) {
6639
6639
  async function ensureDockerAvailable() {
6640
6640
  const cmd = getDockerCmd();
6641
6641
  try {
6642
- await execFileAsync$4(cmd, [
6642
+ await execFileAsync$5(cmd, [
6643
6643
  "version",
6644
6644
  "--format",
6645
6645
  "{{.Server.Version}}"
@@ -17666,6 +17666,167 @@ function addInvokeSpecificOptions(cmd) {
17666
17666
  return cmd.addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--no-pull", "Skip docker pull. Semantics differ by code path: ZIP Lambdas skip pulling the public Lambda base image; Container Lambdas on the local-build path are a no-op (docker build does not refresh the FROM cache by default); Container Lambdas on the ECR-pull fallback skip docker pull AND error if the image is not in the local cache (re-run without --no-pull or pre-pull manually).")).addOption(new Option("--no-build", "Skip docker build on the IMAGE local-build path (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ZIP Lambdas and the IMAGE ECR-pull path. Compatible with --no-pull.")).addOption(new Option("--debug-port <port>", "Node --inspect-brk port (default: off)")).addOption(new Option("--container-host <host>", "Host IP the host uses to bind the RIE port to. Must be a numeric IP (Docker rejects hostnames here). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--assume-role [arn]", "Assume the Lambda's deployed execution role and forward STS-issued temp credentials to the container so the handler runs with the deployed function's narrow permissions. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) auto-resolves the function's execution role ARN from state (requires an active state source); (3) `--no-assume-role` explicitly opts out. Off by default — when omitted, the developer's shell credentials are forwarded unchanged (SAM-compatible default). STS failures degrade to a warn + dev-creds fallback.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers. Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the resolved stack name; pass an explicit value when CFn stack name differs. Fn::GetAtt is warn-and-dropped in v1 (CFn ListStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
17667
17667
  }
17668
17668
 
17669
+ //#endregion
17670
+ //#region src/local/source-change-classifier.ts
17671
+ /**
17672
+ * Dependency-manifest basenames recognized by the classifier. A change
17673
+ * to any of these forces a rebuild because the running container's
17674
+ * pre-built dependency layer is no longer in sync with the source.
17675
+ *
17676
+ * Coverage: the package managers commonly used inside a Lambda /
17677
+ * container image — Node (pnpm / npm / yarn), Python (pip / poetry /
17678
+ * pipenv), Ruby (bundler), Go (modules), Rust (cargo), Java / Kotlin
17679
+ * (Maven, Gradle). Adding a new ecosystem? Append its lockfile +
17680
+ * manifest here and add a classifier test row.
17681
+ */
17682
+ const REBUILD_TRIGGER_BASENAMES = new Set([
17683
+ "package.json",
17684
+ "package-lock.json",
17685
+ "pnpm-lock.yaml",
17686
+ "yarn.lock",
17687
+ "npm-shrinkwrap.json",
17688
+ "requirements.txt",
17689
+ "requirements-dev.txt",
17690
+ "pyproject.toml",
17691
+ "poetry.lock",
17692
+ "Pipfile",
17693
+ "Pipfile.lock",
17694
+ "uv.lock",
17695
+ "Gemfile",
17696
+ "Gemfile.lock",
17697
+ "go.mod",
17698
+ "go.sum",
17699
+ "Cargo.toml",
17700
+ "Cargo.lock",
17701
+ "pom.xml",
17702
+ "build.gradle",
17703
+ "build.gradle.kts",
17704
+ "settings.gradle",
17705
+ "settings.gradle.kts",
17706
+ "Makefile",
17707
+ "CMakeLists.txt"
17708
+ ]);
17709
+ /**
17710
+ * Compiled-language source extensions that require a build step
17711
+ * inside `docker build` (typically a `RUN go build` / `cargo build` /
17712
+ * `mvn package` etc.). A copy of the source alone would leave the
17713
+ * running binary stale, so the user's intent must be a rebuild.
17714
+ *
17715
+ * TypeScript source (`.ts` / `.tsx` / `.mts` / `.cts`) is treated as
17716
+ * compiled because the dominant production-container pattern is to
17717
+ * pre-compile the source via a Dockerfile `RUN tsc` / `RUN yarn build`
17718
+ * step, with the runtime executing the emitted `dist/*.js`. Soft-reload
17719
+ * would `docker cp` the new `.ts` into the container's WORKDIR while
17720
+ * the running process keeps reading the OLD `dist/` — a silent
17721
+ * stale-code failure that violates the file's "slow-but-correct beats
17722
+ * fast-but-stale" default policy (lines 14-22). Setups that transpile
17723
+ * at runtime lose the soft-reload fast path under this default; an
17724
+ * opt-in flag to restore it is a possible follow-up but is not in
17725
+ * scope here.
17726
+ *
17727
+ * Interpreted-language runtimes (Node — `.js` / `.mjs` / `.cjs`,
17728
+ * Python — `.py`, Ruby — `.rb`, shell — `.sh`) read source at process
17729
+ * start, so a `docker cp` + `docker restart` cycle picks them up.
17730
+ * Those extensions are NOT in this set.
17731
+ */
17732
+ const COMPILED_LANGUAGE_EXTENSIONS = new Set([
17733
+ ".go",
17734
+ ".rs",
17735
+ ".java",
17736
+ ".kt",
17737
+ ".kts",
17738
+ ".scala",
17739
+ ".cs",
17740
+ ".swift",
17741
+ ".fs",
17742
+ ".fsx",
17743
+ ".c",
17744
+ ".cc",
17745
+ ".cpp",
17746
+ ".cxx",
17747
+ ".h",
17748
+ ".hpp",
17749
+ ".zig",
17750
+ ".ml",
17751
+ ".mli",
17752
+ ".elm",
17753
+ ".hs",
17754
+ ".dart",
17755
+ ".ts",
17756
+ ".tsx",
17757
+ ".mts",
17758
+ ".cts"
17759
+ ]);
17760
+ /**
17761
+ * Classify a single watcher firing into rebuild vs soft-reload. Pure
17762
+ * + synchronous. The caller (emulator's reload pathway) invokes this
17763
+ * once per target per firing AFTER `cdk synth` has run and the new
17764
+ * asset manifest is on disk.
17765
+ *
17766
+ * Branching:
17767
+ * 1. No asset context (image isn't a CDK asset, or asset lookup
17768
+ * failed) → `rebuild`.
17769
+ * 2. The asset hash didn't change between old and new synths
17770
+ * (`oldAssetHash === newAssetHash`, or `oldAssetHash` missing)
17771
+ * → `rebuild`. Load-bearing guard for "user edited a CDK
17772
+ * construct file (e.g. `lib/stack.ts`) that flipped the task
17773
+ * spec but didn't touch the asset content". Soft-reload would
17774
+ * `docker cp` identical files and `docker restart` the
17775
+ * container with the OLD task spec (env / memory / mounts /
17776
+ * added sidecars are set at `docker create` time, not on
17777
+ * restart) — the user's intent would silently NOT apply.
17778
+ * Forcing rebuild keeps Phase 1-3 semantics exactly for this
17779
+ * case: the rolling primitive boots a shadow with the new task
17780
+ * spec, the user sees their construct edit take effect.
17781
+ * 3. No changed paths (the watcher fired on a debounce flush with
17782
+ * an empty pending set — shouldn't happen in practice, but
17783
+ * defensive) → `rebuild`.
17784
+ * 4. Any changed path's basename matches the Dockerfile or a
17785
+ * dependency manifest → `rebuild`.
17786
+ * 5. Any changed path's extension is a compiled-language source →
17787
+ * `rebuild`.
17788
+ * 6. Else → `soft-reload`.
17789
+ */
17790
+ function classifySourceChange(changedPaths, ctx) {
17791
+ if (!ctx) return {
17792
+ kind: "rebuild",
17793
+ reason: "target image is not a CDK docker-image asset"
17794
+ };
17795
+ if (!ctx.oldAssetHash || ctx.oldAssetHash === ctx.newAssetHash) return {
17796
+ kind: "rebuild",
17797
+ reason: "asset hash unchanged across the synth (CDK construct edit or unrelated file) — task-spec changes need a fresh `docker create`, which only the rebuild path runs"
17798
+ };
17799
+ if (changedPaths.length === 0) return {
17800
+ kind: "rebuild",
17801
+ reason: "no changed paths reported (defensive default)"
17802
+ };
17803
+ for (const p of changedPaths) {
17804
+ const basename = path.basename(p);
17805
+ if (basename === ctx.dockerFile) return {
17806
+ kind: "rebuild",
17807
+ reason: `Dockerfile edit (${basename})`
17808
+ };
17809
+ if (basename.startsWith("Dockerfile.")) return {
17810
+ kind: "rebuild",
17811
+ reason: `Dockerfile.* edit (${basename})`
17812
+ };
17813
+ if (REBUILD_TRIGGER_BASENAMES.has(basename)) return {
17814
+ kind: "rebuild",
17815
+ reason: `dependency manifest edit (${basename})`
17816
+ };
17817
+ const ext = path.extname(p).toLowerCase();
17818
+ if (COMPILED_LANGUAGE_EXTENSIONS.has(ext)) return {
17819
+ kind: "rebuild",
17820
+ reason: `compiled-language source edit (${basename}) — soft-reload would leave the built binary stale`
17821
+ };
17822
+ }
17823
+ return {
17824
+ kind: "soft-reload",
17825
+ reason: `${changedPaths.length} source-only path(s) — skipping rebuild`,
17826
+ newAssetSourceDir: ctx.newAssetSourceDir
17827
+ };
17828
+ }
17829
+
17669
17830
  //#endregion
17670
17831
  //#region src/local/agentcore-code-build.ts
17671
17832
  /**
@@ -18434,6 +18595,17 @@ async function invokeAgentCoreWs(host, port, event, options) {
18434
18595
  reject(/* @__PURE__ */ new Error(`AgentCore /ws at ${url} timed out after ${options.timeoutMs}ms. The agent may be hung or may not close the stream; check container logs.`));
18435
18596
  });
18436
18597
  }, options.timeoutMs);
18598
+ const onAbort = () => {
18599
+ finish(() => {
18600
+ stopIterator();
18601
+ try {
18602
+ ws.close();
18603
+ } catch {}
18604
+ resolve({ frames });
18605
+ });
18606
+ };
18607
+ if (options.abortSignal) if (options.abortSignal.aborted) onAbort();
18608
+ else options.abortSignal.addEventListener("abort", onAbort, { once: true });
18437
18609
  let iterator;
18438
18610
  const stopIterator = () => {
18439
18611
  if (iterator?.return) try {
@@ -18580,6 +18752,12 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
18580
18752
  if ((isMcp || isA2a) && options.ws) logger.warn(`--ws applies only to the HTTP / AGUI protocols; ignoring it for this ${resolved.protocol} runtime.`);
18581
18753
  if (options.wsInteractive && !options.ws) logger.warn("--ws-interactive is meaningful only with --ws; ignoring.");
18582
18754
  if (options.sigv4 && (isMcp || isA2a || options.ws)) logger.warn("--sigv4 signs the HTTP /invocations request only; ignoring it for the " + (isMcp ? "MCP" : isA2a ? "A2A" : "/ws WebSocket") + " path.");
18755
+ const watchEligible = options.ws === true && !isMcp && !isA2a;
18756
+ const watchActive = options.watch === true && watchEligible;
18757
+ if (options.watch === true && !watchEligible) {
18758
+ const ignoredFor = isMcp ? "MCP" : isA2a ? "A2A" : "single-shot HTTP POST /invocations";
18759
+ logger.warn(`--watch is meaningful only with the long-running --ws / --ws-interactive session paths; ignoring it for the ${ignoredFor} path (single-shot invocations run once and exit).`);
18760
+ }
18583
18761
  const sessionId = options.sessionId ?? randomUUID();
18584
18762
  const event = await readEvent(options);
18585
18763
  const mcpRequest = isMcp ? buildMcpRequest(event) : void 0;
@@ -18627,7 +18805,70 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
18627
18805
  const a2a = await a2aInvokeOnce(containerHost, hostPort, a2aRequest, { requestTimeoutMs: options.timeout });
18628
18806
  await new Promise((r) => setTimeout(r, 250));
18629
18807
  emitA2aResult(a2a);
18630
- } else if (options.ws) {
18808
+ } else if (options.ws) if (watchActive) await runAgentCoreWatchLoop({
18809
+ containerHost,
18810
+ hostPort,
18811
+ event,
18812
+ sessionId,
18813
+ timeoutMs: options.timeout,
18814
+ wsInteractive: options.wsInteractive === true,
18815
+ options,
18816
+ resolvedTarget,
18817
+ resolved,
18818
+ synthesizer,
18819
+ synthOpts,
18820
+ stacks,
18821
+ ...authorization && { authorization },
18822
+ rebuild: async () => {
18823
+ if (stopLogs) {
18824
+ try {
18825
+ stopLogs();
18826
+ } catch {}
18827
+ stopLogs = void 0;
18828
+ }
18829
+ if (containerId) {
18830
+ await removeContainer(containerId);
18831
+ containerId = void 0;
18832
+ }
18833
+ const { stacks: newStacks } = await synthesizer.synthesize(synthOpts);
18834
+ const newCandidate = pickAgentCoreCandidateStack(resolvedTarget, newStacks);
18835
+ const { context: newImageContext, loaded: newLoaded } = stateProvider && newCandidate ? await buildAgentCoreImageContext(newCandidate, stateProvider, options) : {
18836
+ context: void 0,
18837
+ loaded: void 0
18838
+ };
18839
+ const newResolved = resolveAgentCoreTarget(resolvedTarget, newStacks, newImageContext);
18840
+ await resolveFromS3BucketIntrinsic(newResolved, stateProvider, newLoaded, newImageContext);
18841
+ const newImage = await resolveAgentCoreImage(newResolved, options, newLoaded, stateProvider);
18842
+ const { env: newEnv, sensitiveEnvKeys: newSensitive } = await buildContainerEnv(newResolved, options, profileCredentials, profileCredsFile, stateProvider, newLoaded, newImageContext);
18843
+ const newHostPort = await pickFreePort();
18844
+ const newName = `${getEmbedConfig().resourceNamePrefix}-agentcore-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
18845
+ logger.info(`Reload: starting agent container (image=${newImage}, port=${newHostPort} -> 8080)...`);
18846
+ containerId = await runDetached({
18847
+ image: newImage,
18848
+ mounts: [],
18849
+ env: newEnv,
18850
+ cmd: [],
18851
+ hostPort: newHostPort,
18852
+ host: containerHost,
18853
+ platform: options.platform,
18854
+ name: newName,
18855
+ ...newSensitive.size > 0 && { sensitiveEnvKeys: newSensitive }
18856
+ });
18857
+ stopLogs = streamLogs(containerId);
18858
+ return {
18859
+ containerId,
18860
+ hostPort: newHostPort,
18861
+ stacks: newStacks
18862
+ };
18863
+ },
18864
+ softReload: async (newSourceDir) => {
18865
+ if (!containerId) throw new CdkLocalError("softReload: no live container to docker cp / docker restart into.", "LOCAL_INVOKE_AGENTCORE_WATCH_NO_CONTAINER");
18866
+ await softReloadAgentContainer(containerId, newSourceDir);
18867
+ const { stacks: newStacks } = await synthesizer.synthesize(synthOpts);
18868
+ return { stacks: newStacks };
18869
+ }
18870
+ });
18871
+ else {
18631
18872
  await waitForAgentCorePing(containerHost, hostPort);
18632
18873
  const frameSource = options.wsInteractive ? readStdinLines() : void 0;
18633
18874
  logger.info(options.wsInteractive ? "Opening the agent /ws WebSocket (interactive — stdin lines = follow-up frames; Ctrl-D to end)..." : "Opening the agent /ws WebSocket and streaming frames...");
@@ -18640,7 +18881,8 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
18640
18881
  });
18641
18882
  await new Promise((r) => setTimeout(r, 250));
18642
18883
  emitWsResult(wsResult);
18643
- } else {
18884
+ }
18885
+ else {
18644
18886
  await waitForAgentCorePing(containerHost, hostPort);
18645
18887
  const additionalHeaders = await buildSigV4HeadersIfRequested(options, resolved, loadedState, containerHost, hostPort, event, sessionId, stateProvider);
18646
18888
  const result = await invokeAgentCore(containerHost, hostPort, event, {
@@ -19298,6 +19540,309 @@ function readEnvOverridesFile$2(filePath) {
19298
19540
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`--env-vars file '${filePath}' must contain a JSON object at the top level.`);
19299
19541
  return parsed;
19300
19542
  }
19543
+ /**
19544
+ * Issue #255 — `cdkl invoke-agentcore --watch` main loop. Wraps the
19545
+ * `/ws` dispatch in a reload-driven loop:
19546
+ *
19547
+ * 1. Open the WebSocket against the current container; stream frames
19548
+ * to stdout. The connection is bound to an abort signal the
19549
+ * watcher fires when a source change is detected.
19550
+ * 2. When the agent closes the stream naturally (no reload pending),
19551
+ * the loop exits — `--watch` does NOT auto-reconnect on a benign
19552
+ * close (the user can `^C` to leave or just re-run the command).
19553
+ * 3. When a reload fires, the abort signal closes the WS cleanly
19554
+ * (resolves with the current frame count, not a reject), then the
19555
+ * classifier picks `'rebuild'` vs `'soft-reload'` and the
19556
+ * corresponding container-swap helper runs. After the swap, the
19557
+ * loop re-opens the WS against the new / restarted container with
19558
+ * the same `--event` first frame + a fresh stdin reader (for
19559
+ * `--ws-interactive`).
19560
+ *
19561
+ * Active `/ws` socket handling: the AgentCore `/ws` protocol does not
19562
+ * define mid-session container handoff, so the only honest local-dev
19563
+ * semantic is "close the socket cleanly and let the client reconnect."
19564
+ * The reload firing logs that loudly so the user sees it.
19565
+ *
19566
+ * `softReload` and `rebuild` are passed as callbacks so the watch loop
19567
+ * itself stays uncoupled from the boot pipeline + the docker helpers
19568
+ * the outer command owns. The host-side teardown (state provider
19569
+ * dispose, container removal, profile creds-file dispose) is the
19570
+ * outer command's `finally` cleanup — the watch loop only swaps the
19571
+ * one agent container.
19572
+ *
19573
+ * @internal — exported so a unit test can drive the loop without the
19574
+ * full Docker + chokidar pipeline.
19575
+ */
19576
+ async function runAgentCoreWatchLoop(args) {
19577
+ const logger = getLogger();
19578
+ const wsInvoker = args.__wsInvoker ?? invokeAgentCoreWs;
19579
+ const waitForPing = args.__waitForPing ?? waitForAgentCorePing;
19580
+ let currentHostPort = args.hostPort;
19581
+ let currentStacks = args.stacks;
19582
+ let reloadChain = Promise.resolve();
19583
+ let currentAbort;
19584
+ let pendingReload = false;
19585
+ let reloadFailed = false;
19586
+ const watcherFactory = args.__watcherFactory ?? ((onChange) => {
19587
+ const watchRoot = process.cwd();
19588
+ const { ignored, shouldTrigger, excludePatterns } = createWatchPredicates({
19589
+ watchRoot,
19590
+ output: args.options.output,
19591
+ watchConfig: resolveWatchConfig()
19592
+ });
19593
+ logger.info(`Watching ${watchRoot} for source changes (excluding ${excludePatterns.join(", ")}).`);
19594
+ return createFileWatcher({
19595
+ paths: [watchRoot],
19596
+ ignored,
19597
+ shouldTrigger,
19598
+ onChange
19599
+ });
19600
+ });
19601
+ const cdkOutDir = args.options.output;
19602
+ const assetLoader = new AssetManifestLoader();
19603
+ const watcher = watcherFactory((changedPaths) => {
19604
+ reloadChain = reloadChain.then(async () => {
19605
+ let verdict = {
19606
+ kind: "rebuild",
19607
+ reason: "classifier not consulted"
19608
+ };
19609
+ let assetCtx;
19610
+ try {
19611
+ if (args.__classifierContext) assetCtx = await args.__classifierContext(changedPaths);
19612
+ else {
19613
+ const { stacks: freshStacks } = await args.synthesizer.synthesize(args.synthOpts);
19614
+ const oldAssetHash = await deriveOldAssetHash({
19615
+ resolvedTarget: args.resolvedTarget,
19616
+ resolved: args.resolved,
19617
+ stacks: currentStacks,
19618
+ cdkOutDir,
19619
+ assetLoader
19620
+ });
19621
+ assetCtx = await loadAgentCoreAssetContext({
19622
+ resolvedTarget: args.resolvedTarget,
19623
+ resolved: args.resolved,
19624
+ stacks: freshStacks,
19625
+ cdkOutDir,
19626
+ assetLoader,
19627
+ oldAssetHash
19628
+ });
19629
+ }
19630
+ verdict = classifySourceChange(changedPaths, assetCtx);
19631
+ logger.info(`Detected source change (${changedPaths.length} path(s)); verdict=${verdict.kind} (${verdict.reason}).`);
19632
+ } catch (err) {
19633
+ logger.warn(`Reload: classifier context unavailable (${err instanceof Error ? err.message : String(err)}); falling back to rebuild.`);
19634
+ verdict = {
19635
+ kind: "rebuild",
19636
+ reason: "classifier context unavailable; falling back to rebuild"
19637
+ };
19638
+ }
19639
+ pendingReload = true;
19640
+ logger.warn("cdkl invoke-agentcore --watch: source change detected; closing the active /ws socket so the client can reconnect to the rebuilt container.");
19641
+ currentAbort?.abort();
19642
+ try {
19643
+ if (verdict.kind === "soft-reload") {
19644
+ const { stacks: newStacks } = await args.softReload(verdict.newAssetSourceDir);
19645
+ currentStacks = newStacks;
19646
+ logger.info("Reload: soft-reloaded the agent container (docker cp + docker restart; container ID + host port preserved).");
19647
+ } else {
19648
+ const { containerId: _newId, hostPort: newHostPort, stacks: newStacks } = await args.rebuild();
19649
+ currentHostPort = newHostPort;
19650
+ currentStacks = newStacks;
19651
+ logger.info(`Reload: rebuilt the agent container.`);
19652
+ }
19653
+ } catch (err) {
19654
+ 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.`);
19655
+ reloadFailed = true;
19656
+ currentAbort?.abort();
19657
+ }
19658
+ }).catch((err) => {
19659
+ logger.error(`Reload chain threw: ${err instanceof Error ? err.message : String(err)}`);
19660
+ });
19661
+ });
19662
+ try {
19663
+ let firstIteration = true;
19664
+ while (true) {
19665
+ if (reloadFailed) break;
19666
+ await waitForPing(args.containerHost, currentHostPort);
19667
+ if (firstIteration) {
19668
+ logger.info(args.wsInteractive ? "Opening the agent /ws WebSocket (interactive — stdin lines = follow-up frames; Ctrl-D to end)..." : "Opening the agent /ws WebSocket and streaming frames...");
19669
+ firstIteration = false;
19670
+ } else logger.info(args.wsInteractive ? "Re-opening the agent /ws WebSocket (interactive) against the rebuilt container..." : "Re-opening the agent /ws WebSocket against the rebuilt container...");
19671
+ const abort = new AbortController();
19672
+ currentAbort = abort;
19673
+ pendingReload = false;
19674
+ const frameSource = args.wsInteractive ? readStdinLines() : void 0;
19675
+ try {
19676
+ const result = await wsInvoker(args.containerHost, currentHostPort, args.event, {
19677
+ sessionId: args.sessionId,
19678
+ timeoutMs: args.timeoutMs,
19679
+ onMessage: (text) => process.stdout.write(text),
19680
+ abortSignal: abort.signal,
19681
+ ...args.authorization && { authorization: args.authorization },
19682
+ ...frameSource && { frameSource }
19683
+ });
19684
+ await new Promise((r) => setTimeout(r, 250));
19685
+ emitWsResult(result);
19686
+ } finally {
19687
+ currentAbort = void 0;
19688
+ }
19689
+ if (!pendingReload) {
19690
+ await reloadChain.catch(() => void 0);
19691
+ if (!pendingReload) break;
19692
+ }
19693
+ await reloadChain.catch(() => void 0);
19694
+ }
19695
+ } finally {
19696
+ try {
19697
+ await watcher.close();
19698
+ } catch (err) {
19699
+ logger.warn(`Watcher close failed: ${err instanceof Error ? err.message : String(err)}`);
19700
+ }
19701
+ }
19702
+ }
19703
+ /**
19704
+ * Issue #255 — soft-reload the agent container in place. `docker cp`
19705
+ * the freshly-synthed asset directory contents into the running
19706
+ * container's WORKDIR + `docker restart` the container.
19707
+ *
19708
+ * - WORKDIR is resolved from the live container's image config via
19709
+ * `docker inspect --format '{{.Config.WorkingDir}}'`. An empty
19710
+ * WORKDIR (Docker runtime default) maps to `/`. For
19711
+ * CodeConfiguration runtimes the generated Dockerfile sets
19712
+ * `WORKDIR /app`, so the copy lands there.
19713
+ * - The trailing `/.` on the source ensures CONTENTS are copied
19714
+ * (not the directory itself); the trailing `/` on the dest forces
19715
+ * docker cp to treat it as a directory.
19716
+ * - `docker restart` cycles PID 1 — the new source is picked up by
19717
+ * the interpreted-language runtime on its next startup. The
19718
+ * container ID + host port + network are preserved across the
19719
+ * restart, so the surrounding watch loop's host-port reference
19720
+ * stays valid.
19721
+ *
19722
+ * @internal — exported for unit tests of the docker-cp + docker-restart
19723
+ * shape without standing up a real container.
19724
+ */
19725
+ const execFileAsync$4 = promisify(execFile);
19726
+ function describeExecError(err) {
19727
+ if (!(err instanceof Error)) return String(err);
19728
+ const stderr = err.stderr;
19729
+ const stderrText = typeof stderr === "string" ? stderr.trim() : stderr instanceof Buffer ? stderr.toString("utf8").trim() : "";
19730
+ return stderrText ? `${err.message}\n${stderrText}` : err.message;
19731
+ }
19732
+ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
19733
+ const logger = getLogger();
19734
+ const dockerCmd = getDockerCmd();
19735
+ let workdir;
19736
+ try {
19737
+ const { stdout } = await execFileAsync$4(dockerCmd, [
19738
+ "inspect",
19739
+ "--format",
19740
+ "{{.Config.WorkingDir}}",
19741
+ containerId
19742
+ ]);
19743
+ workdir = stdout.trim() || "/";
19744
+ } catch (err) {
19745
+ throw new CdkLocalError(`softReloadAgentContainer: docker inspect of container '${containerId}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_INSPECT_FAILED");
19746
+ }
19747
+ const workdirDest = workdir.endsWith("/") ? workdir : `${workdir}/`;
19748
+ logger.info(`Soft-reload: docker cp ${newAssetSourceDir} -> ${containerId}:${workdirDest}; restart.`);
19749
+ try {
19750
+ await execFileAsync$4(dockerCmd, [
19751
+ "cp",
19752
+ `${newAssetSourceDir}/.`,
19753
+ `${containerId}:${workdirDest}`
19754
+ ], { maxBuffer: 64 * 1024 * 1024 });
19755
+ } catch (err) {
19756
+ throw new CdkLocalError(`softReloadAgentContainer: docker cp into '${containerId}:${workdir}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_CP_FAILED");
19757
+ }
19758
+ try {
19759
+ await execFileAsync$4(dockerCmd, ["restart", containerId]);
19760
+ } catch (err) {
19761
+ throw new CdkLocalError(`softReloadAgentContainer: docker restart of '${containerId}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_RESTART_FAILED");
19762
+ }
19763
+ }
19764
+ /**
19765
+ * Issue #255 — build the per-firing classifier context for the agent
19766
+ * container. Mirrors the ECS service emulator's `loadAssetContextForTarget`
19767
+ * shape: returns `undefined` (and the classifier defaults to `'rebuild'`)
19768
+ * when the runtime's image is not a CDK docker-image asset, or when the
19769
+ * asset manifest lookup misses for either the OLD or NEW synth.
19770
+ *
19771
+ * For a CodeConfiguration (`fromCodeAsset`) runtime we treat the bundle's
19772
+ * `codeAssetHash` as the asset hash + the staged source directory as
19773
+ * `newAssetSourceDir`, and synthesize a `Dockerfile` basename that never
19774
+ * matches a real file (the generated Dockerfile lives in a build tmpdir,
19775
+ * not the source tree, so chokidar can't observe an edit to it). A
19776
+ * `fromS3` bundle has no local source tree, so it returns `undefined` and
19777
+ * the classifier defaults to rebuild.
19778
+ *
19779
+ * Container artifacts go through the same `AssetManifestLoader` lookup
19780
+ * the ECS path uses, then read `source.directory` + `source.dockerFile`
19781
+ * off the matched docker-image entry.
19782
+ *
19783
+ * @internal — exported for the watch loop's classifier dispatch test.
19784
+ */
19785
+ async function loadAgentCoreAssetContext(args) {
19786
+ const { resolvedTarget, resolved, stacks, cdkOutDir, assetLoader, oldAssetHash } = args;
19787
+ const newCandidate = pickAgentCoreCandidateStack(resolvedTarget, stacks);
19788
+ if (!newCandidate) return void 0;
19789
+ if (resolved.codeArtifact) {
19790
+ if (resolved.codeArtifact.s3Source) return void 0;
19791
+ const manifest = await assetLoader.loadManifest(cdkOutDir, newCandidate.stackName);
19792
+ if (!manifest) return void 0;
19793
+ const asset = assetLoader.getFileAssets(manifest).get(resolved.codeArtifact.codeAssetHash);
19794
+ if (!asset) return void 0;
19795
+ const sourceDir = assetLoader.getAssetSourcePath(cdkOutDir, asset);
19796
+ return {
19797
+ ...oldAssetHash !== void 0 && { oldAssetHash },
19798
+ newAssetHash: resolved.codeArtifact.codeAssetHash,
19799
+ newAssetSourceDir: sourceDir,
19800
+ dockerFile: ".cdkl-agentcore-generated-Dockerfile"
19801
+ };
19802
+ }
19803
+ if (resolved.containerUri === void 0) return void 0;
19804
+ const manifest = await assetLoader.loadManifest(cdkOutDir, newCandidate.stackName);
19805
+ if (!manifest) return void 0;
19806
+ const dockerImageEntry = getDockerImageBySourceHash(manifest, resolved.containerUri);
19807
+ if (!dockerImageEntry) return void 0;
19808
+ const newAssetHash = dockerImageEntry.hash;
19809
+ const newDockerImage = dockerImageEntry.asset;
19810
+ if (!newDockerImage.source.directory) return void 0;
19811
+ const newAssetSourceDir = path.resolve(cdkOutDir, newDockerImage.source.directory);
19812
+ return {
19813
+ ...oldAssetHash !== void 0 && { oldAssetHash },
19814
+ newAssetHash,
19815
+ newAssetSourceDir,
19816
+ dockerFile: path.basename(newDockerImage.source.dockerFile ?? "Dockerfile")
19817
+ };
19818
+ }
19819
+ /**
19820
+ * Helper: derive the current asset hash from the live (pre-reload)
19821
+ * stacks for the classifier's `oldAssetHash` field. Returns `undefined`
19822
+ * when the runtime's image is not resolvable as a CDK asset against the
19823
+ * old stacks — the classifier defaults to rebuild in that case, which
19824
+ * is the right answer.
19825
+ */
19826
+ /**
19827
+ * Async helper: derive the OLD (pre-reload) asset hash for the
19828
+ * classifier's `oldAssetHash` field. Code-artifact runtimes carry the
19829
+ * hash on the boot-time `resolved.codeArtifact.codeAssetHash`; container
19830
+ * runtimes need a manifest lookup against the previous-synth stacks to
19831
+ * extract it. Returns `undefined` when the OLD hash can't be derived
19832
+ * (image is not a CDK asset, manifest unreadable, no candidate stack) —
19833
+ * the classifier treats `undefined` as "force rebuild", which is the
19834
+ * conservative default.
19835
+ */
19836
+ async function deriveOldAssetHash(args) {
19837
+ const { resolvedTarget, resolved, stacks, cdkOutDir, assetLoader } = args;
19838
+ if (resolved.codeArtifact) return resolved.codeArtifact.codeAssetHash;
19839
+ if (resolved.containerUri === void 0) return void 0;
19840
+ const candidate = pickAgentCoreCandidateStack(resolvedTarget, stacks);
19841
+ if (!candidate) return void 0;
19842
+ const manifest = await assetLoader.loadManifest(cdkOutDir, candidate.stackName);
19843
+ if (!manifest) return void 0;
19844
+ return getDockerImageBySourceHash(manifest, resolved.containerUri)?.hash;
19845
+ }
19301
19846
  function createLocalInvokeAgentCoreCommand(opts = {}) {
19302
19847
  setEmbedConfig(opts.embedConfig);
19303
19848
  const cmd = new Command("invoke-agentcore").description("Run a Bedrock AgentCore Runtime container locally and invoke it once over its protocol contract: HTTP (POST /invocations + GET /ping on 8080; SSE / WebSocket are HTTP wire-shape variants on the same port), MCP (POST /mcp Streamable HTTP on 8000), A2A, or AGUI. Resolves the AWS::BedrockAgentCore::Runtime, pulls/builds its container, injects env vars + AWS credentials, and prints the response. For an MCP runtime, runs the session handshake then sends one JSON-RPC request (tools/list by default, or the method/params from --event). Target accepts a CDK display path (MyStack/MyAgent) or stack-qualified logical ID (MyStack:MyAgentRuntime1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick from a list. Supports the container artifact and the CodeConfiguration managed-runtime artifact (fromCodeAsset, built from source) on all four protocols; the agent calls real AWS for managed services.").argument("[target]", "CDK display path or stack-qualified logical ID of the AgentCore Runtime to invoke (omit to pick interactively in a TTY)").action(withErrorHandling(async (target, options) => {
@@ -19328,7 +19873,7 @@ function createLocalInvokeAgentCoreCommand(opts = {}) {
19328
19873
  * in three `--help` clusters. Chainable: returns `cmd`.
19329
19874
  */
19330
19875
  function addInvokeAgentCoreSpecificOptions(cmd) {
19331
- return cmd.addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--session-id <id>", "AgentCore runtime session id header value (default: a random UUID)")).addOption(new Option("--ws", "Stream over the HTTP-protocol agent's bidirectional /ws WebSocket endpoint (on 8080) instead of POST /invocations: send --event as the first frame and print every received frame to stdout until the agent closes. Ignored for an MCP runtime.").default(false)).addOption(new Option("--ws-interactive", "REPL mode for --ws: after the initial --event frame, read additional frames from stdin (one frame per line, trailing newline stripped) and send each as a text frame until stdin EOFs (Ctrl-D) or the agent closes. Only meaningful with --ws.").default(false)).addOption(new Option("--bearer-token <jwt>", "Bearer JWT this command SUPPLIES (the supplier role) when the runtime declares a customJwtAuthorizer — `cdkl invoke-agentcore` is the local-dev client making the outbound call, so it always presents this token to its own invocation. Verified against the runtime's OIDC discovery URL (signature / issuer / expiry / audience) before the container starts, then forwarded to /invocations as Authorization: Bearer <jwt>. Contrast with `cdkl start-alb --bearer-token`, where the role is reversed — the ALB front-door RECEIVES inbound requests and injects this token only as a default when an inbound request has none (default-when-missing).")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign the /invocations POST with AWS SigV4 (service bedrock-agentcore) using the resolved credentials, matching the cloud default when the runtime declares no customJwtAuthorizer. Opt-in: default unsigned. Mutually exclusive with --bearer-token; ignored on a JWT-protected runtime.").default(false)).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64). Defaults to linux/arm64 because the cloud AgentCore Runtime requires arm64. Override to linux/amd64 only when iterating against an amd64 dev container locally; note the image will not run on the cloud runtime as-is.").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host IP the host uses to bind the agent port to. Must be a numeric IP (Docker rejects hostnames here). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Per-request timeout in milliseconds. Applied to POST /invocations, POST /mcp, and the /ws open-to-close window. Raise this for long-running agent calls that exceed the default.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container so the agent runs with the deployed role. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's RoleArn when it is a literal ARN; (3) `--no-assume-role` opts out. Off by default — the developer's shell credentials are forwarded unchanged.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. Bare form uses the resolved stack name; pass an explicit value when the CFn stack name differs.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
19876
+ return cmd.addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--session-id <id>", "AgentCore runtime session id header value (default: a random UUID)")).addOption(new Option("--ws", "Stream over the HTTP-protocol agent's bidirectional /ws WebSocket endpoint (on 8080) instead of POST /invocations: send --event as the first frame and print every received frame to stdout until the agent closes. Ignored for an MCP runtime.").default(false)).addOption(new Option("--ws-interactive", "REPL mode for --ws: after the initial --event frame, read additional frames from stdin (one frame per line, trailing newline stripped) and send each as a text frame until stdin EOFs (Ctrl-D) or the agent closes. Only meaningful with --ws.").default(false)).addOption(new Option("--bearer-token <jwt>", "Bearer JWT this command SUPPLIES (the supplier role) when the runtime declares a customJwtAuthorizer — `cdkl invoke-agentcore` is the local-dev client making the outbound call, so it always presents this token to its own invocation. Verified against the runtime's OIDC discovery URL (signature / issuer / expiry / audience) before the container starts, then forwarded to /invocations as Authorization: Bearer <jwt>. Contrast with `cdkl start-alb --bearer-token`, where the role is reversed — the ALB front-door RECEIVES inbound requests and injects this token only as a default when an inbound request has none (default-when-missing).")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign the /invocations POST with AWS SigV4 (service bedrock-agentcore) using the resolved credentials, matching the cloud default when the runtime declares no customJwtAuthorizer. Opt-in: default unsigned. Mutually exclusive with --bearer-token; ignored on a JWT-protected runtime.").default(false)).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64). Defaults to linux/arm64 because the cloud AgentCore Runtime requires arm64. Override to linux/amd64 only when iterating against an amd64 dev container locally; note the image will not run on the cloud runtime as-is.").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host IP the host uses to bind the agent port to. Must be a numeric IP (Docker rejects hostnames here). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Per-request timeout in milliseconds. Applied to POST /invocations, POST /mcp, and the /ws open-to-close window. Raise this for long-running agent calls that exceed the default.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container so the agent runs with the deployed role. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's RoleArn when it is a literal ARN; (3) `--no-assume-role` opts out. Off by default — the developer's shell credentials are forwarded unchanged.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. Bare form uses the resolved stack name; pass an explicit value when the CFn stack name differs.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).addOption(new Option("--watch", "Re-synth and reload the agent container on CDK source changes. Only meaningful with the long-running /ws session paths (--ws / --ws-interactive); single-shot POST /invocations, MCP, and A2A invocations run once and exit, so --watch is logged as a no-op WARN for them and the single shot proceeds. The active /ws socket is closed cleanly on every reload firing so the next session connects to the rebuilt container — the honest local-dev semantic.").default(false));
19332
19877
  }
19333
19878
 
19334
19879
  //#endregion
@@ -21939,167 +22484,6 @@ function sleep(ms) {
21939
22484
  return sleepImpl(ms);
21940
22485
  }
21941
22486
 
21942
- //#endregion
21943
- //#region src/local/source-change-classifier.ts
21944
- /**
21945
- * Dependency-manifest basenames recognized by the classifier. A change
21946
- * to any of these forces a rebuild because the running container's
21947
- * pre-built dependency layer is no longer in sync with the source.
21948
- *
21949
- * Coverage: the package managers commonly used inside a Lambda /
21950
- * container image — Node (pnpm / npm / yarn), Python (pip / poetry /
21951
- * pipenv), Ruby (bundler), Go (modules), Rust (cargo), Java / Kotlin
21952
- * (Maven, Gradle). Adding a new ecosystem? Append its lockfile +
21953
- * manifest here and add a classifier test row.
21954
- */
21955
- const REBUILD_TRIGGER_BASENAMES = new Set([
21956
- "package.json",
21957
- "package-lock.json",
21958
- "pnpm-lock.yaml",
21959
- "yarn.lock",
21960
- "npm-shrinkwrap.json",
21961
- "requirements.txt",
21962
- "requirements-dev.txt",
21963
- "pyproject.toml",
21964
- "poetry.lock",
21965
- "Pipfile",
21966
- "Pipfile.lock",
21967
- "uv.lock",
21968
- "Gemfile",
21969
- "Gemfile.lock",
21970
- "go.mod",
21971
- "go.sum",
21972
- "Cargo.toml",
21973
- "Cargo.lock",
21974
- "pom.xml",
21975
- "build.gradle",
21976
- "build.gradle.kts",
21977
- "settings.gradle",
21978
- "settings.gradle.kts",
21979
- "Makefile",
21980
- "CMakeLists.txt"
21981
- ]);
21982
- /**
21983
- * Compiled-language source extensions that require a build step
21984
- * inside `docker build` (typically a `RUN go build` / `cargo build` /
21985
- * `mvn package` etc.). A copy of the source alone would leave the
21986
- * running binary stale, so the user's intent must be a rebuild.
21987
- *
21988
- * TypeScript source (`.ts` / `.tsx` / `.mts` / `.cts`) is treated as
21989
- * compiled because the dominant production-container pattern is to
21990
- * pre-compile the source via a Dockerfile `RUN tsc` / `RUN yarn build`
21991
- * step, with the runtime executing the emitted `dist/*.js`. Soft-reload
21992
- * would `docker cp` the new `.ts` into the container's WORKDIR while
21993
- * the running process keeps reading the OLD `dist/` — a silent
21994
- * stale-code failure that violates the file's "slow-but-correct beats
21995
- * fast-but-stale" default policy (lines 14-22). Setups that transpile
21996
- * at runtime lose the soft-reload fast path under this default; an
21997
- * opt-in flag to restore it is a possible follow-up but is not in
21998
- * scope here.
21999
- *
22000
- * Interpreted-language runtimes (Node — `.js` / `.mjs` / `.cjs`,
22001
- * Python — `.py`, Ruby — `.rb`, shell — `.sh`) read source at process
22002
- * start, so a `docker cp` + `docker restart` cycle picks them up.
22003
- * Those extensions are NOT in this set.
22004
- */
22005
- const COMPILED_LANGUAGE_EXTENSIONS = new Set([
22006
- ".go",
22007
- ".rs",
22008
- ".java",
22009
- ".kt",
22010
- ".kts",
22011
- ".scala",
22012
- ".cs",
22013
- ".swift",
22014
- ".fs",
22015
- ".fsx",
22016
- ".c",
22017
- ".cc",
22018
- ".cpp",
22019
- ".cxx",
22020
- ".h",
22021
- ".hpp",
22022
- ".zig",
22023
- ".ml",
22024
- ".mli",
22025
- ".elm",
22026
- ".hs",
22027
- ".dart",
22028
- ".ts",
22029
- ".tsx",
22030
- ".mts",
22031
- ".cts"
22032
- ]);
22033
- /**
22034
- * Classify a single watcher firing into rebuild vs soft-reload. Pure
22035
- * + synchronous. The caller (emulator's reload pathway) invokes this
22036
- * once per target per firing AFTER `cdk synth` has run and the new
22037
- * asset manifest is on disk.
22038
- *
22039
- * Branching:
22040
- * 1. No asset context (image isn't a CDK asset, or asset lookup
22041
- * failed) → `rebuild`.
22042
- * 2. The asset hash didn't change between old and new synths
22043
- * (`oldAssetHash === newAssetHash`, or `oldAssetHash` missing)
22044
- * → `rebuild`. Load-bearing guard for "user edited a CDK
22045
- * construct file (e.g. `lib/stack.ts`) that flipped the task
22046
- * spec but didn't touch the asset content". Soft-reload would
22047
- * `docker cp` identical files and `docker restart` the
22048
- * container with the OLD task spec (env / memory / mounts /
22049
- * added sidecars are set at `docker create` time, not on
22050
- * restart) — the user's intent would silently NOT apply.
22051
- * Forcing rebuild keeps Phase 1-3 semantics exactly for this
22052
- * case: the rolling primitive boots a shadow with the new task
22053
- * spec, the user sees their construct edit take effect.
22054
- * 3. No changed paths (the watcher fired on a debounce flush with
22055
- * an empty pending set — shouldn't happen in practice, but
22056
- * defensive) → `rebuild`.
22057
- * 4. Any changed path's basename matches the Dockerfile or a
22058
- * dependency manifest → `rebuild`.
22059
- * 5. Any changed path's extension is a compiled-language source →
22060
- * `rebuild`.
22061
- * 6. Else → `soft-reload`.
22062
- */
22063
- function classifySourceChange(changedPaths, ctx) {
22064
- if (!ctx) return {
22065
- kind: "rebuild",
22066
- reason: "target image is not a CDK docker-image asset"
22067
- };
22068
- if (!ctx.oldAssetHash || ctx.oldAssetHash === ctx.newAssetHash) return {
22069
- kind: "rebuild",
22070
- reason: "asset hash unchanged across the synth (CDK construct edit or unrelated file) — task-spec changes need a fresh `docker create`, which only the rebuild path runs"
22071
- };
22072
- if (changedPaths.length === 0) return {
22073
- kind: "rebuild",
22074
- reason: "no changed paths reported (defensive default)"
22075
- };
22076
- for (const p of changedPaths) {
22077
- const basename = path.basename(p);
22078
- if (basename === ctx.dockerFile) return {
22079
- kind: "rebuild",
22080
- reason: `Dockerfile edit (${basename})`
22081
- };
22082
- if (basename.startsWith("Dockerfile.")) return {
22083
- kind: "rebuild",
22084
- reason: `Dockerfile.* edit (${basename})`
22085
- };
22086
- if (REBUILD_TRIGGER_BASENAMES.has(basename)) return {
22087
- kind: "rebuild",
22088
- reason: `dependency manifest edit (${basename})`
22089
- };
22090
- const ext = path.extname(p).toLowerCase();
22091
- if (COMPILED_LANGUAGE_EXTENSIONS.has(ext)) return {
22092
- kind: "rebuild",
22093
- reason: `compiled-language source edit (${basename}) — soft-reload would leave the built binary stale`
22094
- };
22095
- }
22096
- return {
22097
- kind: "soft-reload",
22098
- reason: `${changedPaths.length} source-only path(s) — skipping rebuild`,
22099
- newAssetSourceDir: ctx.newAssetSourceDir
22100
- };
22101
- }
22102
-
22103
22487
  //#endregion
22104
22488
  //#region src/local/cloud-map-registry.ts
22105
22489
  /**
@@ -27439,5 +27823,5 @@ function addListSpecificOptions(cmd) {
27439
27823
  }
27440
27824
 
27441
27825
  //#endregion
27442
- export { MCP_PROTOCOL_VERSION as $, pickAgentCoreCandidateStack as $n, applyAuthorizerOverlay as $t, mergeForService as A, rejectExplicitCfnStackWithMultipleStacks as An, startApiServer as At, DEFAULT_SHADOW_READY_TIMEOUT_MS as B, listTargets as Bn, buildMethodArn as Bt, parseRestartPolicy as C, substituteAgainstState as Cn, materializeLayerFromArn as Ct, ImageOverrideError as D, LocalStateSourceError as Dn, filterRoutesByApiIdentifiers as Dt, runEcsServiceEmulator as E, substituteEnvVarsFromStateAsync as En, filterRoutesByApiIdentifier as Et, isLocalCdkAssetImage as F, collectSsmParameterRefs as Fn, buildJwksUrlFromIssuer as Ft, addInvokeAgentCoreSpecificOptions as G, pickRefLogicalId as Gn, attachAuthorizers as Gt, setShadowReadyTimeoutMs as H, discoverWebSocketApisOrThrow as Hn, evaluateCachedLambdaPolicy as Ht, listPinnedTargets as I, resolveSsmParameters as In, createJwksCache as It, A2A_CONTAINER_PORT as J, AGENTCORE_AGUI_PROTOCOL as Jn, buildCorsConfigFromCloudFrontChain as Jt, createLocalInvokeAgentCoreCommand as K, resolveLambdaArnIntrinsic as Kn, applyCorsResponseHeaders as Kt, buildCloudMapIndex as L, resolveWatchConfig as Ln, verifyCognitoJwt as Lt, resolveImageOverrides as M, resolveCfnRegion as Mn, resolveServiceIntegrationParameters as Mt, runImageOverrideBuilds as N, resolveCfnStackName as Nn, defaultCredentialsLoader as Nt, buildImageOverrideTag as O, createLocalStateProvider as On, groupRoutesByServer as Ot, describePinnedImageUri as P, CfnLocalStateProvider as Pn, buildCognitoJwksUrl as Pt, MCP_PATH as Q, AgentCoreResolutionError as Qn, translateLambdaResponse as Qt, CloudMapRegistry as R, resolveSingleTarget as Rn, verifyJwtAuthorizer as Rt, parseMaxTasks as S, EcsTaskResolutionError as Sn, buildStageMap as St, resolveSharedSidecarCredentials as T, substituteEnvVarsFromState as Tn, availableApiIdentifiers as Tt, getContainerNetworkIp as U, parseSelectionExpressionPath as Un, invokeRequestAuthorizer as Ut, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as V, discoverWebSocketApis as Vn, computeRequestIdentityHash as Vt, attachContainerLogStreamer as W, discoverRoutes as Wn, invokeTokenAuthorizer as Wt, a2aInvokeOnce as X, AGENTCORE_MCP_PROTOCOL as Xn, matchPreflight as Xt, A2A_PATH as Y, AGENTCORE_HTTP_PROTOCOL as Yn, isFunctionUrlOacFronted as Yt, MCP_CONTAINER_PORT as Z, AGENTCORE_RUNTIME_TYPE as Zn, matchRoute as Zt, addCommonEcsServiceOptions as _, architectureToPlatform as _n, createWatchPredicates as _t, albStrategy as a, tryParseStatus as an, LocalInvokeBuildError as ar, invokeAgentCore as at, buildEcsImageResolutionContext$1 as b, resolveRuntimeFileExtension as bn, createFileWatcher as bt, resolveAlbTarget as c, probeHostGatewaySupport as cn, SUPPORTED_CODE_RUNTIMES as ct, addStartServiceSpecificOptions as d, buildMgmtEndpointEnvUrl as dn, renderCodeDockerfile as dt, buildHttpApiV2Event as en, resolveAgentCoreTarget as er, mcpInvokeOnce as et, createLocalStartServiceCommand as f, handleConnectionsRequest as fn, toCmdArgv as ft, MAX_TASKS_SUBNET_RANGE_CAP as g, buildMessageEvent as gn, createLocalStartApiCommand as gt, createLocalRunTaskCommand as h, buildDisconnectEvent as hn, addStartApiSpecificOptions as ht, addAlbSpecificOptions as i, selectIntegrationResponse as in, tryResolveImageFnJoin as ir, AGENTCORE_SESSION_ID_HEADER as it, parseImageOverrideFlags as j, resolveCfnFallbackRegion as jn, resolveSelectionExpression as jt, enforceImageOverrideOrphans as k, isCfnFlagPresent as kn, readMtlsMaterialsFromDisk as kt, isApplicationLoadBalancer as l, bufferToBody as ln, buildAgentCoreCodeImage as lt, addRunTaskSpecificOptions as m, buildConnectEvent as mn, createLocalInvokeCommand as mt, createLocalListCommand as n, evaluateResponseParameters as nn, formatStateRemedy as nr, AGENTCORE_SIGV4_SERVICE as nt, createLocalStartAlbCommand as o, VtlEvaluationError as on, buildStsClientConfig as or, waitForAgentCorePing as ot, serviceStrategy as p, parseConnectionsPath as pn, addInvokeSpecificOptions as pt, invokeAgentCoreWs as q, AGENTCORE_A2A_PROTOCOL as qn, buildCorsConfigByApiId as qt, formatTargetListing as r, pickResponseTemplate as rn, substituteImagePlaceholders as rr, signAgentCoreInvocation as rt, parseLbPortOverrides as s, HOST_GATEWAY_MIN_VERSION as sn, resolveProfileCredentials as sr, downloadAndExtractS3Bundle as st, addListSpecificOptions as t, buildRestV1Event as tn, derivePseudoParametersFromRegion as tr, parseSseForJsonRpc as tt, resolveAlbFrontDoor as u, ConnectionRegistry as un, computeCodeImageTag as ut, addEcsAssumeRoleOptions as v, buildContainerImage as vn, resolveApiTargetSubset as vt, resolveEcsAssumeRoleOption as w, substituteAgainstStateAsync as wn, resolveEnvVars as wt, ecsClusterOption as x, resolveRuntimeImage as xn, attachStageContext as xt, addImageOverrideOptions as y, resolveRuntimeCodeMountPath as yn, createAuthorizerCache as yt, classifySourceChange as z, countTargets as zn, verifyJwtViaDiscovery as zt };
27443
- //# sourceMappingURL=local-list-CFINvNxo.js.map
27826
+ export { mcpInvokeOnce as $, pickAgentCoreCandidateStack as $n, applyAuthorizerOverlay as $t, mergeForService as A, rejectExplicitCfnStackWithMultipleStacks as An, startApiServer as At, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as B, listTargets as Bn, buildMethodArn as Bt, parseRestartPolicy as C, substituteAgainstState as Cn, materializeLayerFromArn as Ct, ImageOverrideError as D, LocalStateSourceError as Dn, filterRoutesByApiIdentifiers as Dt, runEcsServiceEmulator as E, substituteEnvVarsFromStateAsync as En, filterRoutesByApiIdentifier as Et, isLocalCdkAssetImage as F, collectSsmParameterRefs as Fn, buildJwksUrlFromIssuer as Ft, createLocalInvokeAgentCoreCommand as G, pickRefLogicalId as Gn, attachAuthorizers as Gt, getContainerNetworkIp as H, discoverWebSocketApisOrThrow as Hn, evaluateCachedLambdaPolicy as Ht, listPinnedTargets as I, resolveSsmParameters as In, createJwksCache as It, A2A_PATH as J, AGENTCORE_AGUI_PROTOCOL as Jn, buildCorsConfigFromCloudFrontChain as Jt, invokeAgentCoreWs as K, resolveLambdaArnIntrinsic as Kn, applyCorsResponseHeaders as Kt, buildCloudMapIndex as L, resolveWatchConfig as Ln, verifyCognitoJwt as Lt, resolveImageOverrides as M, resolveCfnRegion as Mn, resolveServiceIntegrationParameters as Mt, runImageOverrideBuilds as N, resolveCfnStackName as Nn, defaultCredentialsLoader as Nt, buildImageOverrideTag as O, createLocalStateProvider as On, groupRoutesByServer as Ot, describePinnedImageUri as P, CfnLocalStateProvider as Pn, buildCognitoJwksUrl as Pt, MCP_PROTOCOL_VERSION as Q, AgentCoreResolutionError as Qn, translateLambdaResponse as Qt, CloudMapRegistry as R, resolveSingleTarget as Rn, verifyJwtAuthorizer as Rt, parseMaxTasks as S, EcsTaskResolutionError as Sn, buildStageMap as St, resolveSharedSidecarCredentials as T, substituteEnvVarsFromState as Tn, availableApiIdentifiers as Tt, attachContainerLogStreamer as U, parseSelectionExpressionPath as Un, invokeRequestAuthorizer as Ut, setShadowReadyTimeoutMs as V, discoverWebSocketApis as Vn, computeRequestIdentityHash as Vt, addInvokeAgentCoreSpecificOptions as W, discoverRoutes as Wn, invokeTokenAuthorizer as Wt, MCP_CONTAINER_PORT as X, AGENTCORE_MCP_PROTOCOL as Xn, matchPreflight as Xt, a2aInvokeOnce as Y, AGENTCORE_HTTP_PROTOCOL as Yn, isFunctionUrlOacFronted as Yt, MCP_PATH as Z, AGENTCORE_RUNTIME_TYPE as Zn, matchRoute as Zt, addCommonEcsServiceOptions as _, architectureToPlatform as _n, createWatchPredicates as _t, albStrategy as a, tryParseStatus as an, LocalInvokeBuildError as ar, waitForAgentCorePing as at, buildEcsImageResolutionContext$1 as b, resolveRuntimeFileExtension as bn, createFileWatcher as bt, resolveAlbTarget as c, probeHostGatewaySupport as cn, buildAgentCoreCodeImage as ct, addStartServiceSpecificOptions as d, buildMgmtEndpointEnvUrl as dn, toCmdArgv as dt, buildHttpApiV2Event as en, resolveAgentCoreTarget as er, parseSseForJsonRpc as et, createLocalStartServiceCommand as f, handleConnectionsRequest as fn, classifySourceChange as ft, MAX_TASKS_SUBNET_RANGE_CAP as g, buildMessageEvent as gn, createLocalStartApiCommand as gt, createLocalRunTaskCommand as h, buildDisconnectEvent as hn, addStartApiSpecificOptions as ht, addAlbSpecificOptions as i, selectIntegrationResponse as in, tryResolveImageFnJoin as ir, invokeAgentCore as it, parseImageOverrideFlags as j, resolveCfnFallbackRegion as jn, resolveSelectionExpression as jt, enforceImageOverrideOrphans as k, isCfnFlagPresent as kn, readMtlsMaterialsFromDisk as kt, isApplicationLoadBalancer as l, bufferToBody as ln, computeCodeImageTag as lt, addRunTaskSpecificOptions as m, buildConnectEvent as mn, createLocalInvokeCommand as mt, createLocalListCommand as n, evaluateResponseParameters as nn, formatStateRemedy as nr, signAgentCoreInvocation as nt, createLocalStartAlbCommand as o, VtlEvaluationError as on, buildStsClientConfig as or, downloadAndExtractS3Bundle as ot, serviceStrategy as p, parseConnectionsPath as pn, addInvokeSpecificOptions as pt, A2A_CONTAINER_PORT as q, AGENTCORE_A2A_PROTOCOL as qn, buildCorsConfigByApiId as qt, formatTargetListing as r, pickResponseTemplate as rn, substituteImagePlaceholders as rr, AGENTCORE_SESSION_ID_HEADER as rt, parseLbPortOverrides as s, HOST_GATEWAY_MIN_VERSION as sn, resolveProfileCredentials as sr, SUPPORTED_CODE_RUNTIMES as st, addListSpecificOptions as t, buildRestV1Event as tn, derivePseudoParametersFromRegion as tr, AGENTCORE_SIGV4_SERVICE as tt, resolveAlbFrontDoor as u, ConnectionRegistry as un, renderCodeDockerfile as ut, addEcsAssumeRoleOptions as v, buildContainerImage as vn, resolveApiTargetSubset as vt, resolveEcsAssumeRoleOption as w, substituteAgainstStateAsync as wn, resolveEnvVars as wt, ecsClusterOption as x, resolveRuntimeImage as xn, attachStageContext as xt, addImageOverrideOptions as y, resolveRuntimeCodeMountPath as yn, createAuthorizerCache as yt, DEFAULT_SHADOW_READY_TIMEOUT_MS as z, countTargets as zn, verifyJwtViaDiscovery as zt };
27827
+ //# sourceMappingURL=local-list-Dh6xjxGf.js.map