Repository navigation
feat(telemetry): add an execution viewer to the VS Code and desktop UI - #1120
Closed
ParidelPooya wants to merge 5 commits into
Closed
ParidelPooya wants to merge 5 commits into
ParidelPooya wants to merge 5 commits into
Conversation
The UI gains a Lambda executions view: durable functions in the region, a function's configuration and executions, and one execution's summary, payloads, operation tree, invocations, and history. A running execution is re-read every 4 seconds while it is on screen. Query result rows that hold an execution ARN open it in the viewer. The viewer only reads. It cannot start or stop executions. The renderer tests run in CI again.
This was referenced Oct 29, 2026
…y-ide-execution-viewer
#1119 keeps it private because only its own file calls it there. The explorer session checks a renderer-supplied region with it.
…y-ide-execution-viewer
…y-ide-execution-viewer
ParidelPooya
added a commit
that referenced
this pull request
Oct 30, 2026
This is the first part of #1119, split out as the review there asked. It adds only the core read. The MCP viewer and the VS Code and desktop viewer (#1120) build on it in later pull requests. ## Problem The telemetry tools read only what the exporter wrote to a destination. They cannot show one execution's operations, retries and invocations as Lambda recorded them. ## Cause Core has no client for the durable-execution APIs that Lambda serves: `GetDurableExecution` and `GetDurableExecutionHistory`. ## Who calls it A tool that runs on the user's own machine: the VS Code extension, the desktop app or the MCP server. The tool calls Lambda with the user's own credentials, for an ARN that the user or the user's agent supplied. This module accepts no request from another user, machine or app. The user can already call the same APIs directly with the same credentials. So the checks in this module are not a security boundary. They catch mistakes: a value that is not an execution ARN, and a response that would make the history read run without end. The file header says the same. ## Options considered 1. **Read the destination's records instead.** Rejected. The exporter writes one record per execution, and records can be truncated or sampled out. A function without the telemetry plugin has none. 2. **Read history up to a cap.** Rejected. A cut history leaves operations whose end was not read, and the fold cannot know their status. The cap in #1119 (10,000 events) was also below what one execution can hold. 3. **Chosen: read the whole history.** Lambda bounds its size (see Decisions). ## Change `packages/aws-durable-execution-sdk-js-telemetry-core` gains `lambdaExecutions.ts`: - `getExecution(ctx, arn)` reads the execution summary, then every history page, 1,000 events per page. Both calls go to the region named in the execution ARN. If the service returns the same marker twice, the read stops with `historyError`. - If a history page fails, the call still returns the summary. `history`, `operations` and `invocations` are then empty, and `historyError` gives the reason. - `buildOperations(events)` folds the history into an operation tree. Events that share an `Id` are one operation, and `ParentId` nests them. - `buildInvocations(events)` returns one row per `InvocationCompleted` event. - `isDurableExecutionArn` and `regionFromExecutionArn` check the ARN's shape and read its region. The partition must be `aws` or start with `aws-`, and the account must be 12 digits. The module only reads. It has no call that starts or stops an execution. The package gains a dependency on `@aws-sdk/client-lambda`. ### How the fold reports status 1. A terminal event with `RetryDetails.NextAttemptDelaySeconds` schedules another attempt. So the operation is PENDING, with `nextAttemptTime`. This covers a step failure with a retry left, and a `waitForCondition` check whose condition is not met yet. 2. A `*Started` event after a failure is a retry. It clears the earlier attempt's error, the error's truncation flag and the next attempt time. It keeps the result: a `waitForCondition` check's result is the state the next check reads. 3. Each payload keeps its own truncation flag. `OperationNode` has `resultTruncated` and `errorTruncated`. `InvocationRow` has `errorTruncated`. `ExecutionDetail` has both when its result or error comes from the history's end event; the `GetDurableExecution` summary carries no flag. 4. The attempt count is the number of `*Started` events. It is set only when it is above 1. 5. An operation still open in an ended execution is INCOMPLETE. The history is always whole, so an open operation never ended. 6. The summary is read before the history. If the execution ends in between, the history's terminal `Execution*` event gives the status, end time, result and error. An operation's end does not count as the execution's end. Fixes #1118. That issue describes the fold in the unmerged viewer branch. This fold has the fix from the start. ## Changes from the version in #1119 These address the review on #1119 and a first review round here: - **The whole history is read.** #1119 stopped at 10,000 events. A cut history then showed finished operations as INCOMPLETE, and an earlier revision of this pull request showed them as RUNNING. Neither was true. - **A failed history page returns no history.** A partial history has the same problem as a cut one. - **The attempt count uses Started events only.** The `CurrentAttempt` branch handled "earlier events fell outside the history read". The history is read oldest first, which is the API's default ("By default, events are returned in chronological order", `GetDurableExecutionHistoryRequest.ReverseOrder` in client-lambda). So that case cannot happen (rule 4.4). - **No character check on the ARN's execution part.** It existed to keep `)` out of Markdown links. That belongs to the viewer, and the viewer redesign uses an ID in each link instead of an ARN. The SDK percent-encodes the whole ARN into the request path, so the path cannot change. A consumer that puts an ARN into Markdown or HTML escapes it itself. - **No `maxHistoryEvents`, `maxHistoryBytes`, `deadlineMs` or `requestTimeoutMs` options.** No caller in this pull request sets them (rule 1.2). The viewer redesign states which limits it needs. - **Removed branches for cases an oldest-first read cannot produce:** an end event with no Started event before it, and two end events of one operation with no Started between them. The 146 recorded histories have 0 of either in 3,096 events. ## Decisions (rule 7) - **The history is read whole, with payloads.** Lambda allows at most 30,000 durable operations and 100 MB of persisted payload per execution, and neither can be increased ([Lambda quotas](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html)). So a full read holds at most about 100 MB of payload. - **Retries count toward the operation quota.** Each step attempt and each `waitForCondition` poll is one durable operation ([announcement of the 30,000 limit](https://aws.amazon.com/about-aws/whats-new/2026/10/aws-lambda-durable-gen-limit-increase/)). So the 30,000 limit bounds the number of history events, and so the number of calls. - **A large execution takes many calls.** At most 1,000 events per page, and `GetDurableExecutionHistory` allows 15 requests per second per account and Region. An execution with 60,000 events takes about 60 calls and at least 4 seconds. A caller that refreshes a running execution must allow for that. - **A history failure returns the summary with `historyError`.** The summary is still correct. Failing the whole call would hide it. - **A summary failure fails the call.** Without the summary there is nothing to show. - **A repeated marker stops the read.** Lambda is not expected to send one. Without the stop, such a response would read the same page without end, and the caller has no timeout to end it. - **An event type the fold does not know is skipped.** It does not create an operation or change one. - **A malformed ARN is rejected before any AWS call.** The region comes from the ARN, so a value without one cannot be routed. - **`getExecution` has no timeout of its own.** It uses the SDK defaults. A caller that needs a bound adds it with its own design. ## Assumptions about Lambda (rule 6) I ran one execution on Lambda (us-east-1, `nodejs22.x`, `@aws/durable-execution-sdk-js` 3.0.0) and read its history with `GetDurableExecutionHistory`. The handler runs a step that fails once and then succeeds, a step that fails both of its attempts, a `waitForCondition` that checks three times, a map of two items, a wait, and a chained invoke. The history is `src/testdata/lambda-history-retries.json`, with the account id replaced. A test folds it. Verified against that history: 1. Each attempt of an operation writes one `*Started` event, before its end event. 2. A failure with a retry left carries `RetryDetails.NextAttemptDelaySeconds`, and the final failure does not. 3. A `waitForCondition` check whose condition is not met writes `StepSucceeded` with `RetryDetails.NextAttemptDelaySeconds`. The last check has none. 4. An end event carries the same `SubType` as its Started event. 5. `Execution*` events carry an `Id` that is not an operation id, and `InvocationCompleted` events carry none. Also observed: - `RetryDetails.CurrentAttempt` equals the number of Started events on Lambda. The example histories, which the testing library writes, have it one lower. This pull request counts Started events, so it gives the same number either way. - Events arrive in time order, which is not always `EventId` order. `WaitStarted` (id 30) came before the `ContextSucceeded` events of the map (ids 27 to 29). The fold reads events in the order they arrive and does not sort them. - Read in pages of 10, the 37 events came in 4 pages. The last page had no `NextMarker`, and no marker repeated. Still unverified: 6. A history response holds about as much payload as the execution persisted, so the 100 MB quota bounds it. 7. Once the summary reports an ended execution, the history already holds the `Execution*` end event. 8. A malformed `NextAttemptDelaySeconds` (NaN or out of range) is read as a final end. If a Started event then follows, the operation keeps that end time while it runs. This needs a malformed value from Lambda. ## Testing - core: 547 tests pass, 69 of them in `lambdaExecutions.test.ts`. MCP: 487 tests pass. Typecheck, `typecheck:hosts` and biome pass. - One test uses a real `LambdaClient` against a local HTTP server. It checks that both requests are signed for the ARN's region. - With the code of the first revision of this pull request, 3 of the new tests fail: the `awsfoo` partition, reading past 10,000 events, and the failed later page. - Rule 4 audit, round 2: an 82-mutation list that covers every condition in the file. 74 of the 77 that still apply fail a test. The other 5 target code that this revision removed. The 3 survivors change no result under the assumptions above: `executionEndOf` reading the first terminal `Execution*` event instead of the last (assumption 7 allows only one); not skipping events without an `Id` (assumption 5; the unknown-type skip drops `Invocation*` events too); and taking `kind` from the first event only (assumption 4). - The real `LambdaClient` test clears `AWS_IGNORE_CONFIGURED_ENDPOINT_URLS` and `AWS_PROFILE`, and points `AWS_CONFIG_FILE` and `AWS_SHARED_CREDENTIALS_FILE` at a missing path. With `AWS_IGNORE_CONFIGURED_ENDPOINT_URLS=true` set, and a preload that refuses every non-local connection, it passes and connects nowhere else. - Truncation: deleting any one of the 8 lines that set or clear a truncation flag fails a test. - `queryChokePoint.test.ts` classifies `getExecution` as a read that takes an ARN, not a query.
Contributor
Author
|
Closing. Its base, the #1119 branch, is closed, and it was written against the core read API before #1122. Building the viewer again on main is tracked in #1148, which lists what this pull request proposed and what to settle first. The branch |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The VS Code extension and the desktop app only show what the exporter wrote to a destination. Neither shows one execution's operations as they ran. To see a step's result, a retry, or which invocation failed, a user has to call the Lambda API by hand.
This is the second of two PRs. It is based on #1119, which adds
getExecutionto core. Merge that one first. This PR then shows only the UI changes.Change
lambdaExecutions.tsgains three reads:listDurableFunctions,getFunctionInfo, andlistExecutions(ListFunctions,GetFunctionConfiguration,ListDurableExecutionsByFunction).ExplorerSessionexposes them andgetExecutionas messages. So the VS Code and desktop hosts share one code path.Decisions
Testing
typecheck:hosts, biome, the renderer typecheck, and both host builds pass.Notes for reviewers
lambda:ListFunctions,lambda:GetFunctionConfiguration,lambda:ListDurableExecutionsByFunction,lambda:GetDurableExecution, andlambda:GetDurableExecutionHistory. Without them, each page shows the AWS error.