Skip to content

Conversation

@humanagent
Copy link
Contributor

@humanagent humanagent commented Nov 23, 2025

Add inline actions to agent-sdk and emit actions and intent events in Agent with codecs ActionsCodec and IntentCodec

Introduce a public ./inline-actions export, add codecs and content types for actions and intent, wire message filters and Agent event dispatch, and provide an inlineActionsMiddleware plus builder/utilities for sending and handling action menus.

📍Where to Start

Start with codec definitions in types/ActionsContent.ts and types/IntentContent.ts, then follow integration in src/core/Agent.ts and predicates in src/core/filter.ts.


📊 Macroscope summarized ff73769. 6 files reviewed, 26 issues evaluated, 25 issues filtered, 0 comments posted

🗂️ Filtered Issues

sdks/agent-sdk/src/content-types/inline-actions/inline-actions.ts — 0 comments posted, 15 evaluated, 14 filtered
  • line 18: lastSentActionMessage is a module-global mutable variable used to track the "last" message across executions. In any environment with concurrent or interleaved requests, this can leak state across sessions/users and produce race conditions: the value may be overwritten by a different request before a consumer reads it, or a consumer may read a stale value from an earlier request. This violates at-most-once and provenance invariants for "last" and can cause incorrect behavior in multi-context usage. [ Already posted ]
  • line 18: lastSentActionMessage can retain a reference to arbitrary unknown objects indefinitely at module scope, creating memory retention across requests. Without explicit per-context cleanup on all exit paths (success, error, cancellation), large messages or cyclic graphs may be kept alive longer than intended, causing memory growth/leaks until overwritten or the process restarts. [ Low confidence ]
  • line 21: lastShownMenu is a module-global mutable variable tracking a { config: AppConfig; menuId: string }. In concurrent or multi-session environments, this can leak a menu/config from one session into another and cause races where one request overwrites the "last" menu while another expects to re-show its own menu, resulting in wrong navigation for users and broken invariants. [ Already posted ]
  • line 23: Global actionHandlers registry allows silent handler overwrites based on duplicate actionIds across menus or features. registerAction warns but proceeds to overwrite. During initializeAppFromConfig, multiple menus can define actions with the same id, or user-defined IDs may conflict with auto-registered ones like "main-menu", "help", "back-to-main". This can lead to incorrect handler execution at runtime when an intent arrives for a duplicated ID. [ Low confidence ]
  • line 23: Unbounded growth of actionHandlers leads to a memory leak. Functions like sendConfirmation and sendSelection register per-interaction action IDs (e.g., using Date.now() or caller-provided IDs) via registerAction, but there is no corresponding deregistration after the intent is processed. Over time, the global actionHandlers Map accumulates entries and is never cleared (except manually via clearAllActions), causing memory usage to grow with user interactions. [ Low confidence ]
  • line 31: Global lastSentActionMessage is shared across all conversations and invocations, causing races and cross-conversation/state bleed. Concurrent sends will overwrite the value, and getLastSentActionMessage() returns whichever message was last sent globally rather than the one relevant to the current ctx. This violates at-most-once and uniqueness/association invariants and can surface the wrong message to callers. [ Low confidence ]
  • line 48: showLastMenu claims to "Fallback to main menu" in the comment, but when no last menu is tracked it only sends a text message "Returning to main menu..." and does not actually show the main menu or any actions. This contradicts the documented intent and yields a degraded UX where users cannot navigate. [ Low confidence ]
  • line 56: inlineActionsMiddleware detects intents via ctx.message.contentType?.typeId === "intent". This brittle check can miss valid intents or misclassify messages if the intent content type differs by authorityId or versioning semantics. The SDK typically compares via sameAs(ContentTypeIntent), which accounts for the full content type identifier. Using only typeId risks contract mismatches across versions. [ Low confidence ]
  • line 97: ActionBuilder.add allows duplicate ids within a single ActionsContent without validation. Inline actions typically require unique IDs per menu to correctly route intents. Duplicates create ambiguity and can silently misroute or ignore user selections, violating sequence uniqueness constraints. [ Low confidence ]
  • line 115: lastSentActionMessage is set in ActionBuilder.send, which will overwrite the global value on every send. Under concurrent or multi-conversation use, this causes races and wrong association when retrieved via getLastSentActionMessage(). Each send should either return the message to the caller or store per-conversation/per-user state to avoid cross-talk. [ Low confidence ]
  • line 124: sendActions also sets the same global lastSentActionMessage, producing the same cross-conversation/race issues. Multiple send paths compete to set this shared value unexpectedly. [ Low confidence ]
  • line 176: Both validators use input.trim() for validation but do not expose or return the normalized value. This can cause a mismatch where an input with leading/trailing whitespace (e.g., ' 0xabc...') is deemed valid, yet downstream code consuming the original untrimmed value may fail. To preserve invariants, either return the trimmed value alongside valid, or require callers to supply already-normalized input and avoid trimming here. [ Low confidence ]
  • line 182: ethereumAddress validator accepts any 0x + 40 hex characters and does not enforce EIP-55 checksum casing. If downstream components require checksum addresses, this validator will incorrectly mark non-checksummed addresses as valid, enabling malformed inputs to pass validation. [ Low confidence ]
  • line 290: showNavigationOptions can send an ActionsContent with zero actions if neither customActions is provided nor a main-menu exists in config. This produces an unusable navigation message and may violate the target contract if empty action lists are not allowed, yielding a poor or invalid user artifact without fallback. [ Low confidence ]
sdks/agent-sdk/src/content-types/inline-actions/types/ActionsContent.ts — 0 comments posted, 6 evaluated, 6 filtered
  • line 70: decode blindly reads content.parameters.encoding without checking that content.parameters exists. If parameters is undefined (which is possible for EncodedContent produced by other codecs or sources), this causes a runtime TypeError: Cannot read properties of undefined (reading 'encoding'). Add a guard, e.g., const encoding = content.parameters?.encoding;. [ Low confidence ]
  • line 71: decode only accepts exactly "UTF-8" for the encoding parameter and throws otherwise. Other producers may emit variants like "utf-8", "UTF8", or omit the parameter. The strict case-sensitive check can cause unnecessary runtime errors and interoperability issues. Normalizing case or accepting common aliases would avoid failures. [ Low confidence ]
  • line 138: validateContent rejects action.style === "secondary" even though the Action.style type includes "secondary". The check if (action.style && !["primary", "danger"].includes(action.style)) omits "secondary", and the error message itself claims allowed values are primary, secondary, danger. This will throw on valid inputs and contradicts the declared type and message. [ Already posted ]
  • line 152: validateContent rejects the valid Action.style value "secondary". The type Action declares style?: "primary" | "secondary" | "danger", and the error message says it must be one of primary, secondary, danger, but the check at validateContent only allows ["primary", "danger"]. This will cause a runtime error when style is "secondary". Update the inclusion check to allow "secondary" as declared. [ Already posted ]
  • line 171: isValidISO8601 is overly strict: it creates new Date(timestamp) and requires date.toISOString() === timestamp. This rejects many valid ISO-8601 strings (e.g., those with timezone offsets +01:00, without milliseconds, or lowercase z). As a result, valid expiresAt values for actions and content are incorrectly rejected, causing unnecessary runtime errors in validateContent. Consider a real ISO-8601 parser or a more permissive check. [ Already posted ]
  • line 188: isValidISO8601 uses new Date(timestamp) and compares date.toISOString() === timestamp. This is overly strict and will reject many valid ISO-8601/RFC 3339 timestamps (e.g., those without milliseconds like "2023-08-01T12:00:00Z", or with timezone offsets like "2023-08-01T12:00:00+02:00"). Given the function is labeled "Basic ISO-8601 timestamp validation", this strict equality causes avoidable runtime failures on valid inputs. Consider a more permissive validation (e.g., parsing and checking for validity without requiring exact toISOString() equivalence). [ Already posted ]
sdks/agent-sdk/src/content-types/inline-actions/types/IntentContent.ts — 0 comments posted, 3 evaluated, 3 filtered
  • line 49: decode does not verify that content.type matches ContentTypeIntent before decoding. If a mismatched content type is passed, it may produce confusing errors or incorrectly accept malformed data. Add a type guard to ensure only ContentTypeIntent is decoded. [ Low confidence ]
  • line 50: decode dereferences content.parameters without a null/undefined guard: const encoding = content.parameters.encoding;. If parameters is absent (which is allowed in EncodedContent), this throws at runtime. Add a safe access or default before reading encoding. [ Low confidence ]
  • line 51: decode enforces a case-sensitive check for encoding equal to "UTF-8" and rejects other common variants like "utf-8". This can cause valid content from other senders to be rejected unnecessarily. Normalize or case-insensitively compare the encoding value. [ Low confidence ]
sdks/agent-sdk/src/core/Agent.ts — 0 comments posted, 2 evaluated, 2 filtered
  • line 436: start() sets #isLocked = true at entry and, on failure, calls this.#handleStreamError(error) at line 436. If the error chain does not indicate recovery (recovered === false inside #handleStreamError), the lock is never cleared and no restart is scheduled, leaving the agent permanently locked with no active streams. Subsequent start() calls will early-return due to #isLocked being true. The lock must be cleared on all non-recovery paths or stop() invoked to restore a usable state. [ Out of scope ]
  • line 436: When start() fails and delegates to #handleStreamError at line 436, the automatic restart path inside #handleStreamError uses queueMicrotask(() => this.start()) without forwarding the original AgentStreamingOptions. This silently drops caller-provided streaming options on recovery, changing externally visible behavior (contract parity) after an initialization error. [ Out of scope ]

@humanagent humanagent requested review from a team as code owners November 23, 2025 00:15
@changeset-bot
Copy link

changeset-bot bot commented Nov 23, 2025

⚠️ No Changeset found

Latest commit: ff73769

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Nov 23, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
xmtp-chat-api-service Skipped Skipped Nov 23, 2025 1:01am
xmtp-js-xmtp-chat Skipped Skipped Nov 23, 2025 1:01am

@vercel vercel bot temporarily deployed to Preview – xmtp-js-xmtp-chat November 23, 2025 00:15 Inactive
@vercel vercel bot temporarily deployed to Preview – xmtp-chat-api-service November 23, 2025 00:15 Inactive
@vercel vercel bot temporarily deployed to Preview – xmtp-chat-api-service November 23, 2025 00:15 Inactive
@vercel vercel bot temporarily deployed to Preview – xmtp-js-xmtp-chat November 23, 2025 00:15 Inactive
@vercel vercel bot temporarily deployed to Preview – xmtp-js-xmtp-chat November 23, 2025 00:17 Inactive
@vercel vercel bot temporarily deployed to Preview – xmtp-chat-api-service November 23, 2025 00:17 Inactive
@vercel vercel bot temporarily deployed to Preview – xmtp-chat-api-service November 23, 2025 00:18 Inactive
@vercel vercel bot temporarily deployed to Preview – xmtp-js-xmtp-chat November 23, 2025 00:18 Inactive
@humanagent humanagent changed the title Add inline actions to agent SDK feat: add inline actions to agent SDK Nov 23, 2025
@humanagent humanagent changed the title feat: add inline actions to agent SDK feat: add inline actions to agent-sdk Nov 23, 2025
@vercel vercel bot temporarily deployed to Preview – xmtp-chat-api-service November 23, 2025 01:01 Inactive
@vercel vercel bot temporarily deployed to Preview – xmtp-js-xmtp-chat November 23, 2025 01:01 Inactive
@github-actions github-actions bot added the Stale label Nov 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants