cdk-local 0.100.0 → 0.102.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.
@@ -17358,6 +17358,7 @@ async function localInvokeCommand(target, options, extraStateProviders) {
17358
17358
  const result = await invokeRie(containerHost, hostPort, event, Math.max(3e4, lambda.timeoutSec * 2 * 1e3));
17359
17359
  await new Promise((resolveDelay) => setTimeout(resolveDelay, 250));
17360
17360
  process.stdout.write(`${result.raw}\n`);
17361
+ if (options.responseFile) writeFileSync(options.responseFile, result.raw);
17361
17362
  } finally {
17362
17363
  if (sigintHandler) process.off("SIGINT", sigintHandler);
17363
17364
  await cleanup();
@@ -17731,7 +17732,7 @@ function createLocalInvokeCommand(opts = {}) {
17731
17732
  * `--help` clusters. Chainable: returns `cmd`.
17732
17733
  */
17733
17734
  function addInvokeSpecificOptions(cmd) {
17734
- 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."));
17735
+ 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.")).addOption(new Option("--response-file <path>", "Also write the raw Lambda response payload to this file. Lets a programmatic caller recover the response without parsing it out of stdout (where it interleaves with synth progress + streamed container logs). stdout output is unchanged."));
17735
17736
  }
17736
17737
 
17737
17738
  //#endregion
@@ -28510,6 +28511,7 @@ const STUDIO_CSS = `
28510
28511
  .row.sel { background: #2a3550; }
28511
28512
  .row .ts { color: #777; }
28512
28513
  .row .label { color: #ddd; flex: 1; overflow: hidden; text-overflow: ellipsis; }
28514
+ .row.reinvoke .label::before { content: '\\21A9 '; color: #6aa0ff; margin-right: 2px; }
28513
28515
  .row .status { color: #7bd88f; }
28514
28516
  .row .status.err { color: #e0707a; }
28515
28517
  #workspace { padding: 0 0 24px; }
@@ -28541,6 +28543,7 @@ const STUDIO_CSS = `
28541
28543
  .req-composer .req-status { margin-top: 8px; font: 12px ui-monospace, Menlo, monospace; }
28542
28544
  .req-composer .req-result pre { background: #0e0e0e; }
28543
28545
  .composer button:disabled { background: #333; color: #888; cursor: default; }
28546
+ .composer .reinvoke-btn { margin-top: 6px; padding: 4px 14px; }
28544
28547
  .composer .err { color: #e0707a; margin-top: 6px; min-height: 18px; }
28545
28548
  .section { padding: 8px 12px; border-bottom: 1px solid #222; }
28546
28549
  .section h3 { margin: 0 0 6px; font-size: 11px; color: #888; text-transform: uppercase; }
@@ -28638,6 +28641,7 @@ const STUDIO_SCRIPT = `
28638
28641
  let shownInvId = null; // lambda invocation whose result is in the workspace
28639
28642
  let shownServeId = null; // serve target whose workspace is shown
28640
28643
  let shownDetailId = null; // captured request whose read-only detail is shown
28644
+ let pendingReqPrefill = null; // {method,path,headers,body} to seed the next serve request composer (re-invoke)
28641
28645
  let studioDockerfiles = []; // Dockerfiles scanned at boot (pinned-ecs image-override picker)
28642
28646
 
28643
28647
  function el(tag, cls, text) {
@@ -29270,6 +29274,34 @@ const STUDIO_SCRIPT = `
29270
29274
  bodyTa.spellcheck = false;
29271
29275
  sec.appendChild(bodyTa);
29272
29276
 
29277
+ // Re-invoke prefill (issue #284): seed the fields from a captured request
29278
+ // when the user clicked [Re-invoke] on a served-request detail. The prefill
29279
+ // is address-tagged with its target; consume it UNCONDITIONALLY (so a stray
29280
+ // one from a since-stopped serve never lingers) but only APPLY it when the
29281
+ // target matches this composer.
29282
+ if (pendingReqPrefill) {
29283
+ const pending = pendingReqPrefill;
29284
+ pendingReqPrefill = null;
29285
+ if (pending.targetId === id && pending.req && typeof pending.req === 'object') {
29286
+ const pf = pending.req;
29287
+ if (pf.method) method.value = String(pf.method).toUpperCase();
29288
+ if (pf.path != null) path.value = pf.path;
29289
+ if (pf.headers && typeof pf.headers === 'object') {
29290
+ // Drop hop-by-hop / transport headers the proxy captured verbatim
29291
+ // (host / content-length / etc.) — they are noise in the editor and
29292
+ // the relay sets them itself.
29293
+ const SKIP = ['host', 'connection', 'content-length', 'transfer-encoding', 'accept-encoding'];
29294
+ headers.value = Object.keys(pf.headers)
29295
+ .filter(function (k) { return SKIP.indexOf(k.toLowerCase()) === -1; })
29296
+ .map(function (k) { return k + ': ' + pf.headers[k]; })
29297
+ .join('\\n');
29298
+ }
29299
+ if (pf.body != null) {
29300
+ bodyTa.value = typeof pf.body === 'string' ? pf.body : JSON.stringify(pf.body);
29301
+ }
29302
+ }
29303
+ }
29304
+
29273
29305
  const sendRow = el('div', 'req-send');
29274
29306
  const btn = el('button', null, 'Send');
29275
29307
  sendRow.appendChild(btn);
@@ -29470,21 +29502,31 @@ const STUDIO_SCRIPT = `
29470
29502
  return sec;
29471
29503
  }
29472
29504
 
29473
- function renderComposer(id, kind, eventText) {
29505
+ function renderComposer(id, kind, eventText, reinvokeOf) {
29474
29506
  const ws = document.getElementById('workspace');
29475
29507
  ws.innerHTML = '';
29476
29508
 
29477
29509
  const composer = el('div', 'composer');
29478
- composer.appendChild(el('div', 'target-name', 'Invoke ' + id));
29510
+ composer.appendChild(el('div', 'target-name', (reinvokeOf ? 'Re-invoke ' : 'Invoke ') + id));
29479
29511
  const ta = el('textarea');
29480
29512
  ta.value = eventText;
29481
29513
  ta.spellcheck = false;
29482
29514
  composer.appendChild(ta);
29483
- // Per-run options (e.g. env vars) below the event, above Invoke.
29484
- const opt = buildOptions(kind);
29485
- if (opt.node) composer.appendChild(opt.node);
29515
+ // A re-invoke (issue #284) re-runs the EDITED event against the same
29516
+ // target; per-run options are not carried over, so the options section is
29517
+ // omitted (the payload is the thing being tweaked). A fresh invoke keeps
29518
+ // the per-run options (e.g. env vars) below the event, above Invoke.
29519
+ let opt = { collect: undefined, collectRaw: undefined };
29520
+ if (reinvokeOf) {
29521
+ composer.appendChild(
29522
+ el('div', 'opt-hint', 'Re-invoke runs the edited event through the same target (per-run options use defaults).')
29523
+ );
29524
+ } else {
29525
+ opt = buildOptions(kind);
29526
+ if (opt.node) composer.appendChild(opt.node);
29527
+ }
29486
29528
  composer.appendChild(document.createElement('br'));
29487
- const btn = el('button', null, 'Invoke');
29529
+ const btn = el('button', null, reinvokeOf ? 'Re-invoke' : 'Invoke');
29488
29530
  const msg = el('div', 'err');
29489
29531
  composer.appendChild(btn);
29490
29532
  composer.appendChild(msg);
@@ -29494,7 +29536,17 @@ const STUDIO_SCRIPT = `
29494
29536
  ws.appendChild(composer);
29495
29537
  ws.appendChild(result);
29496
29538
 
29497
- active = { id, kind, ta, btn, msg, result, collectOpts: opt.collect, collectRaw: opt.collectRaw };
29539
+ active = {
29540
+ id,
29541
+ kind,
29542
+ ta,
29543
+ btn,
29544
+ msg,
29545
+ result,
29546
+ collectOpts: opt.collect,
29547
+ collectRaw: opt.collectRaw,
29548
+ reinvokeOf: reinvokeOf || null,
29549
+ };
29498
29550
  btn.onclick = () => runInvoke();
29499
29551
  shownInvId = null;
29500
29552
  shownDetailId = null;
@@ -29511,17 +29563,29 @@ const STUDIO_SCRIPT = `
29511
29563
  msg.textContent = 'Invalid JSON: ' + err.message;
29512
29564
  return;
29513
29565
  }
29566
+ const isReinvoke = !!active.reinvokeOf;
29514
29567
  msg.textContent = '';
29515
29568
  btn.disabled = true;
29516
- btn.textContent = 'Invoking...';
29569
+ btn.textContent = isReinvoke ? 'Re-invoking...' : 'Invoking...';
29517
29570
  result.innerHTML = '';
29518
29571
  try {
29519
- const body = { targetId: id, kind, event };
29520
- const options = active.collectOpts ? active.collectOpts() : undefined;
29521
- if (options) body.options = options;
29522
- const rawArgs = active.collectRaw ? active.collectRaw() : undefined;
29523
- if (rawArgs) body.rawArgs = rawArgs;
29524
- const res = await fetch('/api/run', {
29572
+ // A re-invoke (issue #284) re-runs a recorded row by id with the edited
29573
+ // payload through POST /api/reinvoke; a fresh invoke runs the target by
29574
+ // POST /api/run with the composed options.
29575
+ let url;
29576
+ let body;
29577
+ if (isReinvoke) {
29578
+ url = '/api/reinvoke';
29579
+ body = { invocationId: active.reinvokeOf, payload: event };
29580
+ } else {
29581
+ url = '/api/run';
29582
+ body = { targetId: id, kind, event };
29583
+ const options = active.collectOpts ? active.collectOpts() : undefined;
29584
+ if (options) body.options = options;
29585
+ const rawArgs = active.collectRaw ? active.collectRaw() : undefined;
29586
+ if (rawArgs) body.rawArgs = rawArgs;
29587
+ }
29588
+ const res = await fetch(url, {
29525
29589
  method: 'POST',
29526
29590
  headers: { 'content-type': 'application/json' },
29527
29591
  body: JSON.stringify(body),
@@ -29532,13 +29596,14 @@ const STUDIO_SCRIPT = `
29532
29596
  renderResult(shownInvId);
29533
29597
  }
29534
29598
  if (!res.ok || data.ok === false) {
29535
- msg.textContent = 'Invoke failed: ' + (data.error || ('HTTP ' + res.status));
29599
+ const verb = isReinvoke ? 'Re-invoke' : 'Invoke';
29600
+ msg.textContent = verb + ' failed: ' + (data.error || ('HTTP ' + res.status));
29536
29601
  }
29537
29602
  } catch (err) {
29538
29603
  msg.textContent = 'Request failed: ' + err;
29539
29604
  } finally {
29540
29605
  btn.disabled = false;
29541
- btn.textContent = 'Invoke';
29606
+ btn.textContent = isReinvoke ? 'Re-invoke' : 'Invoke';
29542
29607
  }
29543
29608
  }
29544
29609
 
@@ -29596,6 +29661,12 @@ const STUDIO_SCRIPT = `
29596
29661
  const d = new Date(merged.ts);
29597
29662
  row.querySelector('.ts').textContent = d.toLocaleTimeString();
29598
29663
  row.querySelector('.label').textContent = (merged.target || '') + ' ' + (merged.label || '');
29664
+ // A re-invoke (issue #284) row is visually linked to its source: a CSS
29665
+ // marker on the label + a tooltip naming the source invocation.
29666
+ if (merged.reinvokeOf) {
29667
+ row.classList.add('reinvoke');
29668
+ row.title = 'Re-invoke of ' + merged.reinvokeOf;
29669
+ }
29599
29670
  const statusEl = row.querySelector('.status');
29600
29671
  statusEl.textContent =
29601
29672
  merged.status != null
@@ -29618,10 +29689,11 @@ const STUDIO_SCRIPT = `
29618
29689
  highlightTarget(ev.target);
29619
29690
  if (INVOKE_KINDS.includes(ev.kind)) {
29620
29691
  // A single-shot invocation row (Lambda or AgentCore) reloads into the
29621
- // re-invokable composer.
29692
+ // re-invokable composer, pre-filled with the captured event and wired to
29693
+ // POST /api/reinvoke (issue #284) so the new row links to this source.
29622
29694
  shownDetailId = null;
29623
29695
  shownServeId = null;
29624
- renderComposer(ev.target, ev.kind, ev.request != null ? fmt(ev.request) : '{}');
29696
+ renderComposer(ev.target, ev.kind, ev.request != null ? fmt(ev.request) : '{}', id);
29625
29697
  shownInvId = id;
29626
29698
  renderResult(id);
29627
29699
  } else {
@@ -29644,6 +29716,36 @@ const STUDIO_SCRIPT = `
29644
29716
 
29645
29717
  const head = el('div', 'composer');
29646
29718
  head.appendChild(el('div', 'target-name', (ev.label || 'request') + ' — ' + (ev.target || '')));
29719
+ // Re-invoke (issue #284): a captured served request is re-sent through the
29720
+ // live front door by reusing that serve's request composer. Clicking
29721
+ // navigates to the running serve and pre-fills it with this request; the
29722
+ // serve must be running (restart it first if it has stopped).
29723
+ const serveSt = serveState.get(ev.target);
29724
+ const serveRunning = serveSt && serveSt.status === 'running';
29725
+ const reBtn = el('button', 'reinvoke-btn', 'Re-invoke');
29726
+ if (serveRunning) {
29727
+ reBtn.onclick = function () {
29728
+ // Re-check running at CLICK time: the serve may have stopped since the
29729
+ // detail was rendered (the button is not re-rendered on a stop). If so,
29730
+ // do NOT seed a prefill (it would otherwise leak into the next serve
29731
+ // composer). The prefill is address-tagged with the target so a stray
29732
+ // one is dropped on mismatch (see renderRequestComposer).
29733
+ const cur = serveState.get(ev.target);
29734
+ if (!cur || cur.status !== 'running') {
29735
+ renderCapturedDetail(id); // re-render to reflect the now-stopped state
29736
+ return;
29737
+ }
29738
+ pendingReqPrefill =
29739
+ ev.request && typeof ev.request === 'object'
29740
+ ? { targetId: ev.target, req: ev.request }
29741
+ : null;
29742
+ selectTarget(ev.target, ev.kind);
29743
+ };
29744
+ } else {
29745
+ reBtn.disabled = true;
29746
+ reBtn.title = 'Start the serve to re-invoke this request.';
29747
+ }
29748
+ head.appendChild(reBtn);
29647
29749
  ws.appendChild(head);
29648
29750
 
29649
29751
  const reqSec = el('div', 'section');
@@ -30131,6 +30233,10 @@ function handleRequest(req, res, bus, html, targetsJson, options) {
30131
30233
  handleDispatch(req, res, options.onRun);
30132
30234
  return;
30133
30235
  }
30236
+ if (req.method === "POST" && path === "/api/reinvoke") {
30237
+ handleDispatch(req, res, options.onReinvoke);
30238
+ return;
30239
+ }
30134
30240
  if (req.method === "POST" && path === "/api/request") {
30135
30241
  handleDispatch(req, res, options.onServeRequest);
30136
30242
  return;
@@ -30457,20 +30563,23 @@ function createStudioDispatcher(config) {
30457
30563
  target: req.targetId,
30458
30564
  kind: req.kind,
30459
30565
  label: "invoke",
30460
- request: req.event
30566
+ request: req.event,
30567
+ ...req.reinvokeOf ? { reinvokeOf: req.reinvokeOf } : {}
30461
30568
  });
30462
30569
  let dir;
30463
30570
  try {
30464
30571
  dir = mkdtempSync(join(tmpdir(), "cdkl-studio-run-"));
30465
30572
  const eventFile = join(dir, "event.json");
30466
30573
  writeFileSync(eventFile, JSON.stringify(req.event ?? {}));
30574
+ const responseFilePath = req.kind === "lambda" ? join(dir, "response.json") : void 0;
30467
30575
  const args = [
30468
30576
  verb,
30469
30577
  req.targetId,
30470
30578
  "--event",
30471
30579
  eventFile,
30472
30580
  ...buildSharedChildArgs(config, { preferAssembly: true }),
30473
- ...buildPerRunArgs(req.kind, req.options)
30581
+ ...buildPerRunArgs(req.kind, req.options),
30582
+ ...responseFilePath ? ["--response-file", responseFilePath] : []
30474
30583
  ];
30475
30584
  const envVars = resolveEnvVars(req.kind, req.options);
30476
30585
  if (envVars) {
@@ -30483,7 +30592,12 @@ function createStudioDispatcher(config) {
30483
30592
  const durationMs = clock() - startedAt;
30484
30593
  const ok = code === 0;
30485
30594
  const failure = stderr.trim() || `cdkl ${verb} exited ${code}`;
30486
- const { response, raw, stdoutLogLines } = extractResponse(req.kind, stdout, ok, failure);
30595
+ let fileResponse;
30596
+ if (responseFilePath) try {
30597
+ const content = readFileSync(responseFilePath, "utf8");
30598
+ if (content.length > 0) fileResponse = content;
30599
+ } catch {}
30600
+ const { response, raw, stdoutLogLines } = extractResponse(req.kind, stdout, ok, failure, fileResponse);
30487
30601
  stdoutLogLines.forEach((line) => {
30488
30602
  config.bus.emit("log", {
30489
30603
  ts: clock(),
@@ -30503,7 +30617,8 @@ function createStudioDispatcher(config) {
30503
30617
  request: req.event,
30504
30618
  response,
30505
30619
  status,
30506
- durationMs
30620
+ durationMs,
30621
+ ...req.reinvokeOf ? { reinvokeOf: req.reinvokeOf } : {}
30507
30622
  });
30508
30623
  const result = {
30509
30624
  invocationId,
@@ -30603,7 +30718,7 @@ function runChild(spawnFn, nodeBin, argv, cwd, invocationId, target, bus, clock)
30603
30718
  * whole (a single MCP result), else kept as the raw streamed text. There
30604
30719
  * are no separate stdout log lines.
30605
30720
  */
30606
- function extractResponse(kind, stdout, ok, failure) {
30721
+ function extractResponse(kind, stdout, ok, failure, fileResponse) {
30607
30722
  if (kind === "agentcore") {
30608
30723
  const text = stdout.trim();
30609
30724
  if (!ok) return {
@@ -30624,6 +30739,16 @@ function extractResponse(kind, stdout, ok, failure) {
30624
30739
  raw: "",
30625
30740
  stdoutLogLines: stdoutLines
30626
30741
  };
30742
+ if (fileResponse !== void 0) {
30743
+ const trimmed = fileResponse.trim();
30744
+ const parsed = tryParseJson(trimmed);
30745
+ const stdoutLogLines = stdoutLines.filter((l) => l.trim() !== trimmed);
30746
+ return {
30747
+ response: parsed.ok ? parsed.value : fileResponse,
30748
+ raw: fileResponse,
30749
+ stdoutLogLines
30750
+ };
30751
+ }
30627
30752
  let responseIdx = -1;
30628
30753
  let response;
30629
30754
  for (let i = stdoutLines.length - 1; i >= 0; i -= 1) {
@@ -30665,6 +30790,45 @@ function tryParseJson(raw) {
30665
30790
  }
30666
30791
  }
30667
30792
 
30793
+ //#endregion
30794
+ //#region src/local/studio-reinvoke.ts
30795
+ /**
30796
+ * The target kinds a timeline row can be re-invoked through the dispatcher.
30797
+ * Single-shot invoke kinds only — a served request (api / alb / ecs) is
30798
+ * re-sent client-side through the request composer + the live front door
30799
+ * (issue #322), not re-dispatched here, so the proxy still captures it.
30800
+ */
30801
+ const REINVOKABLE_KINDS = new Set(["lambda", "agentcore"]);
30802
+ /**
30803
+ * Re-invoke a past timeline row with an edited payload (issue #284, studio
30804
+ * Phase 3). Resolves the original target from the recorded invocation and
30805
+ * re-fires the edited payload through the SAME single-shot dispatcher
30806
+ * `POST /api/run` uses, threading `reinvokeOf` so the new row links to its
30807
+ * source. The payload REPLACES the original event (the edit IS the point);
30808
+ * run options are not carried over (re-invoke edits the payload, not the
30809
+ * flags).
30810
+ *
30811
+ * Only `lambda` / `agentcore` rows are re-invokable here — a served request
30812
+ * is re-sent through the request composer instead, so this throws for serve
30813
+ * kinds (and for an id that has aged out of the bounded history window).
30814
+ *
30815
+ * Host-side use case: consumed by `cdkl studio`'s `POST /api/reinvoke`
30816
+ * handler; a host CLI embedding the studio building blocks reuses it to wire
30817
+ * the same re-invoke route over its own store + dispatcher.
30818
+ */
30819
+ async function reinvoke(input, deps) {
30820
+ const original = deps.store.invocation(input.invocationId);
30821
+ if (!original) throw new Error(`No recorded invocation '${input.invocationId}' to re-invoke (it may have aged out of the history window).`);
30822
+ if (!REINVOKABLE_KINDS.has(original.kind)) throw new Error(`Re-invoke from the timeline is server-side only for Lambda / AgentCore targets; re-send a '${original.kind}' request with the request composer instead.`);
30823
+ const req = {
30824
+ targetId: original.target,
30825
+ kind: original.kind,
30826
+ event: input.payload,
30827
+ reinvokeOf: input.invocationId
30828
+ };
30829
+ return deps.dispatcher.run(req);
30830
+ }
30831
+
30668
30832
  //#endregion
30669
30833
  //#region src/local/studio-request-relay.ts
30670
30834
  const DEFAULT_TIMEOUT_MS = 3e4;
@@ -31349,6 +31513,23 @@ function coerceServeRequest(body) {
31349
31513
  return out;
31350
31514
  }
31351
31515
  /**
31516
+ * Validate the `POST /api/reinvoke` body at the HTTP boundary (issue #284):
31517
+ * a non-empty `invocationId` string and a `payload` (the edited event — any
31518
+ * JSON value, including `null`, but the key must be present so an omitted
31519
+ * payload is a clean 4xx rather than a silent re-invoke with `undefined`).
31520
+ */
31521
+ function coerceReinvokeRequest(body) {
31522
+ if (typeof body !== "object" || body === null) throw new Error("Request body must be a JSON object.");
31523
+ const record = body;
31524
+ const { invocationId } = record;
31525
+ if (typeof invocationId !== "string" || invocationId.trim() === "") throw new Error("Request body must include a non-empty \"invocationId\" string.");
31526
+ if (!("payload" in record)) throw new Error("Request body must include a \"payload\" (the edited event).");
31527
+ return {
31528
+ invocationId,
31529
+ payload: record["payload"]
31530
+ };
31531
+ }
31532
+ /**
31352
31533
  * Validate a `PATCH /api/config` body and apply the editable run-time
31353
31534
  * bindings (`fromCfnStack` / `assumeRole`) onto `target` in place. Only the
31354
31535
  * keys PRESENT in the body are touched (a partial update); `null` / `false` /
@@ -31527,6 +31708,16 @@ async function localStudioCommand(options) {
31527
31708
  ...req.body !== void 0 ? { body: req.body } : {}
31528
31709
  });
31529
31710
  },
31711
+ onReinvoke: (body) => {
31712
+ const { invocationId, payload } = coerceReinvokeRequest(body);
31713
+ return reinvoke({
31714
+ invocationId,
31715
+ payload
31716
+ }, {
31717
+ store,
31718
+ dispatcher
31719
+ });
31720
+ },
31530
31721
  getRunning: () => ({ running: serveManager.list() }),
31531
31722
  getConfig: () => sessionConfigSnapshot(),
31532
31723
  patchConfig: (body) => {
@@ -31609,5 +31800,5 @@ function addStudioSpecificOptions(cmd) {
31609
31800
  }
31610
31801
 
31611
31802
  //#endregion
31612
- export { describePinnedImageUri as $, CfnLocalStateProvider as $n, buildCognitoJwksUrl as $t, addStartServiceSpecificOptions as A, buildMgmtEndpointEnvUrl as An, toCmdArgv as At, ecsClusterOption as B, resolveRuntimeImage as Bn, attachStageContext as Bt, addAlbSpecificOptions as C, selectIntegrationResponse as Cn, derivePseudoParametersFromRegion as Cr, invokeAgentCore as Ct, resolveAlbTarget as D, probeHostGatewaySupport as Dn, LocalInvokeBuildError as Dr, buildAgentCoreCodeImage as Dt, parseLbPortOverrides as E, HOST_GATEWAY_MIN_VERSION as En, tryResolveImageFnJoin as Er, SUPPORTED_CODE_RUNTIMES as Et, MAX_TASKS_SUBNET_RANGE_CAP as F, buildMessageEvent as Fn, createLocalStartApiCommand as Ft, runEcsServiceEmulator as G, substituteEnvVarsFromStateAsync as Gn, filterRoutesByApiIdentifier as Gt, parseRestartPolicy as H, substituteAgainstState as Hn, materializeLayerFromArn as Ht, addCommonEcsServiceOptions as I, architectureToPlatform as In, createWatchPredicates as It, enforceImageOverrideOrphans as J, isCfnFlagPresent as Jn, readMtlsMaterialsFromDisk as Jt, ImageOverrideError as K, LocalStateSourceError as Kn, filterRoutesByApiIdentifiers as Kt, addEcsAssumeRoleOptions as L, buildContainerImage as Ln, resolveApiTargetSubset as Lt, serviceStrategy as M, parseConnectionsPath as Mn, addInvokeSpecificOptions as Mt, addRunTaskSpecificOptions as N, buildConnectEvent as Nn, createLocalInvokeCommand as Nt, isApplicationLoadBalancer as O, bufferToBody as On, buildStsClientConfig as Or, computeCodeImageTag as Ot, createLocalRunTaskCommand as P, buildDisconnectEvent as Pn, addStartApiSpecificOptions as Pt, runImageOverrideBuilds as Q, resolveCfnStackName as Qn, defaultCredentialsLoader as Qt, addImageOverrideOptions as R, resolveRuntimeCodeMountPath as Rn, createAuthorizerCache as Rt, formatTargetListing as S, pickResponseTemplate as Sn, resolveAgentCoreTarget as Sr, AGENTCORE_SESSION_ID_HEADER as St, createLocalStartAlbCommand as T, VtlEvaluationError as Tn, substituteImagePlaceholders as Tr, downloadAndExtractS3Bundle as Tt, resolveEcsAssumeRoleOption as U, substituteAgainstStateAsync as Un, resolveEnvVars$1 as Ut, parseMaxTasks as V, EcsTaskResolutionError as Vn, buildStageMap as Vt, resolveSharedSidecarCredentials as W, substituteEnvVarsFromState as Wn, availableApiIdentifiers as Wt, parseImageOverrideFlags as X, resolveCfnFallbackRegion as Xn, resolveSelectionExpression as Xt, mergeForService as Y, rejectExplicitCfnStackWithMultipleStacks as Yn, startApiServer as Yt, resolveImageOverrides as Z, resolveCfnRegion as Zn, resolveServiceIntegrationParameters as Zt, renderStudioHtml as _, translateLambdaResponse as _n, AGENTCORE_HTTP_PROTOCOL as _r, MCP_PROTOCOL_VERSION as _t, createLocalStudioCommand as a, buildMethodArn as an, listTargets as ar, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as at, addListSpecificOptions as b, buildRestV1Event as bn, AgentCoreResolutionError as br, AGENTCORE_SIGV4_SERVICE as bt, startStudioProxy as c, invokeRequestAuthorizer as cn, discoverWebSocketApisOrThrow as cr, attachContainerLogStreamer as ct, filterStudioCustomResources as d, applyCorsResponseHeaders as dn, webSocketApiMatchesIdentifier as dr, invokeAgentCoreWs as dt, buildJwksUrlFromIssuer as en, collectSsmParameterRefs as er, isLocalCdkAssetImage as et, isCustomResourceLambdaTarget as f, buildCorsConfigByApiId as fn, discoverRoutes as fr, A2A_CONTAINER_PORT as ft, toStudioTargetGroups as g, matchRoute as gn, AGENTCORE_AGUI_PROTOCOL as gr, MCP_PATH as gt, startStudioServer as h, matchPreflight as hn, AGENTCORE_A2A_PROTOCOL as hr, MCP_CONTAINER_PORT as ht, coerceStopRequest as i, verifyJwtViaDiscovery as in, countTargets as ir, DEFAULT_SHADOW_READY_TIMEOUT_MS as it, createLocalStartServiceCommand as j, handleConnectionsRequest as jn, classifySourceChange as jt, resolveAlbFrontDoor as k, ConnectionRegistry as kn, resolveProfileCredentials as kr, renderCodeDockerfile as kt, relayServeRequest as l, invokeTokenAuthorizer as ln, filterWebSocketApisByIdentifiers as lr, addInvokeAgentCoreSpecificOptions as lt, filterStudioTargetGroups as m, isFunctionUrlOacFronted as mn, resolveLambdaArnIntrinsic as mr, a2aInvokeOnce as mt, coerceRunRequest as n, verifyCognitoJwt as nn, resolveWatchConfig as nr, buildCloudMapIndex as nt, resolveServeBaseUrl as o, computeRequestIdentityHash as on, availableWebSocketApiIdentifiers as or, setShadowReadyTimeoutMs as ot, annotatePinnedEcsTargets as p, buildCorsConfigFromCloudFrontChain as pn, pickRefLogicalId as pr, A2A_PATH as pt, buildImageOverrideTag as q, createLocalStateProvider as qn, groupRoutesByServer as qt, coerceServeRequest as r, verifyJwtAuthorizer as rn, resolveSingleTarget as rr, CloudMapRegistry as rt, createStudioServeManager as s, evaluateCachedLambdaPolicy as sn, discoverWebSocketApis as sr, getContainerNetworkIp as st, addStudioSpecificOptions as t, createJwksCache as tn, resolveSsmParameters as tr, listPinnedTargets as tt, createStudioDispatcher as u, attachAuthorizers as un, parseSelectionExpressionPath as ur, createLocalInvokeAgentCoreCommand as ut, createStudioStore as v, applyAuthorizerOverlay as vn, AGENTCORE_MCP_PROTOCOL as vr, mcpInvokeOnce as vt, albStrategy as w, tryParseStatus as wn, formatStateRemedy as wr, waitForAgentCorePing as wt, createLocalListCommand as x, evaluateResponseParameters as xn, pickAgentCoreCandidateStack as xr, signAgentCoreInvocation as xt, StudioEventBus as y, buildHttpApiV2Event as yn, AGENTCORE_RUNTIME_TYPE as yr, parseSseForJsonRpc as yt, buildEcsImageResolutionContext$1 as z, resolveRuntimeFileExtension as zn, createFileWatcher as zt };
31613
- //# sourceMappingURL=local-studio-BSdQpOpg.js.map
31803
+ export { runImageOverrideBuilds as $, resolveCfnStackName as $n, defaultCredentialsLoader as $t, resolveAlbFrontDoor as A, ConnectionRegistry as An, resolveProfileCredentials as Ar, renderCodeDockerfile as At, buildEcsImageResolutionContext$1 as B, resolveRuntimeFileExtension as Bn, createFileWatcher as Bt, formatTargetListing as C, pickResponseTemplate as Cn, resolveAgentCoreTarget as Cr, AGENTCORE_SESSION_ID_HEADER as Ct, parseLbPortOverrides as D, HOST_GATEWAY_MIN_VERSION as Dn, tryResolveImageFnJoin as Dr, SUPPORTED_CODE_RUNTIMES as Dt, createLocalStartAlbCommand as E, VtlEvaluationError as En, substituteImagePlaceholders as Er, downloadAndExtractS3Bundle as Et, createLocalRunTaskCommand as F, buildDisconnectEvent as Fn, addStartApiSpecificOptions as Ft, resolveSharedSidecarCredentials as G, substituteEnvVarsFromState as Gn, availableApiIdentifiers as Gt, parseMaxTasks as H, EcsTaskResolutionError as Hn, buildStageMap as Ht, MAX_TASKS_SUBNET_RANGE_CAP as I, buildMessageEvent as In, createLocalStartApiCommand as It, buildImageOverrideTag as J, createLocalStateProvider as Jn, groupRoutesByServer as Jt, runEcsServiceEmulator as K, substituteEnvVarsFromStateAsync as Kn, filterRoutesByApiIdentifier as Kt, addCommonEcsServiceOptions as L, architectureToPlatform as Ln, createWatchPredicates as Lt, createLocalStartServiceCommand as M, handleConnectionsRequest as Mn, classifySourceChange as Mt, serviceStrategy as N, parseConnectionsPath as Nn, addInvokeSpecificOptions as Nt, resolveAlbTarget as O, probeHostGatewaySupport as On, LocalInvokeBuildError as Or, buildAgentCoreCodeImage as Ot, addRunTaskSpecificOptions as P, buildConnectEvent as Pn, createLocalInvokeCommand as Pt, resolveImageOverrides as Q, resolveCfnRegion as Qn, resolveServiceIntegrationParameters as Qt, addEcsAssumeRoleOptions as R, buildContainerImage as Rn, resolveApiTargetSubset as Rt, createLocalListCommand as S, evaluateResponseParameters as Sn, pickAgentCoreCandidateStack as Sr, signAgentCoreInvocation as St, albStrategy as T, tryParseStatus as Tn, formatStateRemedy as Tr, waitForAgentCorePing as Tt, parseRestartPolicy as U, substituteAgainstState as Un, materializeLayerFromArn as Ut, ecsClusterOption as V, resolveRuntimeImage as Vn, attachStageContext as Vt, resolveEcsAssumeRoleOption as W, substituteAgainstStateAsync as Wn, resolveEnvVars$1 as Wt, mergeForService as X, rejectExplicitCfnStackWithMultipleStacks as Xn, startApiServer as Xt, enforceImageOverrideOrphans as Y, isCfnFlagPresent as Yn, readMtlsMaterialsFromDisk as Yt, parseImageOverrideFlags as Z, resolveCfnFallbackRegion as Zn, resolveSelectionExpression as Zt, toStudioTargetGroups as _, matchRoute as _n, AGENTCORE_AGUI_PROTOCOL as _r, MCP_PATH as _t, createLocalStudioCommand as a, verifyJwtViaDiscovery as an, countTargets as ar, DEFAULT_SHADOW_READY_TIMEOUT_MS as at, StudioEventBus as b, buildHttpApiV2Event as bn, AGENTCORE_RUNTIME_TYPE as br, parseSseForJsonRpc as bt, startStudioProxy as c, evaluateCachedLambdaPolicy as cn, discoverWebSocketApis as cr, getContainerNetworkIp as ct, createStudioDispatcher as d, attachAuthorizers as dn, parseSelectionExpressionPath as dr, createLocalInvokeAgentCoreCommand as dt, buildCognitoJwksUrl as en, CfnLocalStateProvider as er, describePinnedImageUri as et, filterStudioCustomResources as f, applyCorsResponseHeaders as fn, webSocketApiMatchesIdentifier as fr, invokeAgentCoreWs as ft, startStudioServer as g, matchPreflight as gn, AGENTCORE_A2A_PROTOCOL as gr, MCP_CONTAINER_PORT as gt, filterStudioTargetGroups as h, isFunctionUrlOacFronted as hn, resolveLambdaArnIntrinsic as hr, a2aInvokeOnce as ht, coerceStopRequest as i, verifyJwtAuthorizer as in, resolveSingleTarget as ir, CloudMapRegistry as it, addStartServiceSpecificOptions as j, buildMgmtEndpointEnvUrl as jn, toCmdArgv as jt, isApplicationLoadBalancer as k, bufferToBody as kn, buildStsClientConfig as kr, computeCodeImageTag as kt, relayServeRequest as l, invokeRequestAuthorizer as ln, discoverWebSocketApisOrThrow as lr, attachContainerLogStreamer as lt, annotatePinnedEcsTargets as m, buildCorsConfigFromCloudFrontChain as mn, pickRefLogicalId as mr, A2A_PATH as mt, coerceRunRequest as n, createJwksCache as nn, resolveSsmParameters as nr, listPinnedTargets as nt, resolveServeBaseUrl as o, buildMethodArn as on, listTargets as or, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as ot, isCustomResourceLambdaTarget as p, buildCorsConfigByApiId as pn, discoverRoutes as pr, A2A_CONTAINER_PORT as pt, ImageOverrideError as q, LocalStateSourceError as qn, filterRoutesByApiIdentifiers as qt, coerceServeRequest as r, verifyCognitoJwt as rn, resolveWatchConfig as rr, buildCloudMapIndex as rt, createStudioServeManager as s, computeRequestIdentityHash as sn, availableWebSocketApiIdentifiers as sr, setShadowReadyTimeoutMs as st, addStudioSpecificOptions as t, buildJwksUrlFromIssuer as tn, collectSsmParameterRefs as tr, isLocalCdkAssetImage as tt, reinvoke as u, invokeTokenAuthorizer as un, filterWebSocketApisByIdentifiers as ur, addInvokeAgentCoreSpecificOptions as ut, renderStudioHtml as v, translateLambdaResponse as vn, AGENTCORE_HTTP_PROTOCOL as vr, MCP_PROTOCOL_VERSION as vt, addAlbSpecificOptions as w, selectIntegrationResponse as wn, derivePseudoParametersFromRegion as wr, invokeAgentCore as wt, addListSpecificOptions as x, buildRestV1Event as xn, AgentCoreResolutionError as xr, AGENTCORE_SIGV4_SERVICE as xt, createStudioStore as y, applyAuthorizerOverlay as yn, AGENTCORE_MCP_PROTOCOL as yr, mcpInvokeOnce as yt, addImageOverrideOptions as z, resolveRuntimeCodeMountPath as zn, createAuthorizerCache as zt };
31804
+ //# sourceMappingURL=local-studio-BQBMYRAg.js.map