Skip to content

feat(agent-handoff): inject identity marker on mid-session agent change#4003

Closed
PeterPonyu wants to merge 9 commits into
code-yeongyu:devfrom
PeterPonyu:feat/agent-handoff-marker
Closed

feat(agent-handoff): inject identity marker on mid-session agent change#4003
PeterPonyu wants to merge 9 commits into
code-yeongyu:devfrom
PeterPonyu:feat/agent-handoff-marker

Conversation

@PeterPonyu

@PeterPonyu PeterPonyu commented May 14, 2026

Copy link
Copy Markdown
Contributor

Summary

When a user Tab-switches the active agent mid-session (e.g. sisyphus → hephaestus), opencode does update input.agent for the next turn, but the LLM keeps introducing itself by the prior name because chat history still contains assistant turns authored under the old persona. This PR re-anchors the new agent on the first turn after a transition by injecting an <identity-handoff> marker as the leading text part.

What's new

  • New src/features/agent-handoff/ module with:
    • marker.ts — pure renderer of the handoff text
    • state.ts — per-session lastObservedAgent map; recordAgentObservation(sessionID, currentAgent) returns the prior agent on a real transition, undefined otherwise
  • chat-message.ts calls recordAgentObservation right after the existing setSessionAgent capture. On a real transition, the marker is unshifted to parts[0] so the LLM sees the identity reset before any user content this turn.

Example marker rendered into the user message:

<identity-handoff prior="sisyphus" current="hephaestus">
You are now hephaestus.
Prior assistant turns in this conversation were authored by sisyphus; read them as handoff context, not as your own past output.
Apply the persona, tools, and policies configured for hephaestus from this turn forward.
</identity-handoff>

Design notes

  • Separate state from setSessionAgent/getSessionAgent. Those are first-write-wins and 7+ callers (delegate-task, call-omo-agent, background-task, prometheus-md-only, no-sisyphus-gpt, …) depend on that as "the session's primary agent identity". Overloading them would silently change behavior in those paths. Handoff detection needs last-observed semantics, which is a different concept — kept in its own map.
  • Normalization via getAgentConfigKey. Display variants like \"Hephaestus - Deep Agent\" resolve to the same key as \"hephaestus\", so renames or UI display variations don't trip false transitions.
  • Idempotent within a turn. The marker fires on transition only; identical agent on consecutive turns is a no-op.

Tests

  • 11 unit tests in src/features/agent-handoff/ cover marker rendering and the state machine (first turn, same-agent, transitions, display-variant normalization, session scoping, clear).
  • 5 integration tests appended to src/plugin/chat-message.test.ts exercise the wiring through the real handler — first turn doesn't fire, same agent doesn't fire, sisyphus→hephaestus fires correctly, display variants don't trip it, two consecutive changes emit two separate markers.
  • A pre-existing /start-work integration test caught a real state-leak across describes; the file-level afterEach now resets the handoff map.

Totals: 41/41 pass across touched suites. bun run typecheck: pass. bun run build: pass.

Out of scope

  • /switch_agent slash command or an agent-self-switch tool — the SDK doesn't expose a way for a plugin tool to swap the active agent of an in-flight session; that would need an upstream opencode change adding client.session.setAgent(...). The existing call_omo_agent and delegate-task tools already cover the cross-agent delegation use case via subagents.
  • Removing the marker once the new agent has acknowledged it (current behavior: marker is in the conversation history permanently, like any other user message). If this proves noisy, a future change could squash it during compaction.

Test plan

  • `bun run typecheck`
  • `bun run build`
  • Agent-handoff + chat-message suites: 41/41 pass
  • Local opencode smoke test: Tab from sisyphus to hephaestus mid-session, verify the next assistant turn introduces itself as Hephaestus and the marker is visible in the conversation
  • CI `test` job: likely surfaces the same ~25 pre-existing failures observed on `upstream/dev` directly — not introduced by this PR

🤖 Generated with Claude Code


Summary by cubic

Injects an <identity-handoff> marker on mid-session agent switches, prepending it to the next user turn so the model resets identity. Handles opencode’s dual-fire and schema rules to avoid duplicate or invalid parts.

  • New Features

    • Added src/features/agent-handoff/ with renderHandoffMarker and per-session tracking via recordAgentObservation/clearAgentObservation.
    • chat-message.ts detects real transitions after updateSessionAgent and unshifts createInternalAgentTextPart(renderHandoffMarker(...)) into output.parts[0].
    • Normalizes with getAgentConfigKey so display variants don’t trigger false switches.
  • Bug Fixes

    • Marker injection creates a valid text part with id/sessionID/messageID; if messageID is missing, skip injection.
    • Dedups by messageID to ignore the stale second call in the dual-fire pattern.
    • XML-escapes agent names to prevent markup corruption.
    • Clears observation state on session.deleted to prevent leaks.
    • Clarified visible vs hidden internal markers: handoff markers are internal but non-synthetic so the model reads the identity change.
    • Updated bun.lock to use oh-my-opencode-* 4.4.0 optional binaries for CI.

Written for commit 637be75. Summary will update on new commits. Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 7 files

Confidence score: 4/5

  • This PR is likely safe to merge, with only a mild-to-moderate risk from one concrete issue rather than a functional blocker.
  • In src/plugin/chat-message.ts, recordAgentObservation writes session data into a module-level Map that is not cleared in production cleanup paths, which can cause gradual memory growth over time.
  • Severity is moderate (4/10) and does not indicate an immediate breakage, but the high confidence (8/10) makes it worth addressing soon to avoid longer-term stability impact.
  • Pay close attention to src/plugin/chat-message.ts - ensure agent-handoff session entries are removed during normal session teardown.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/plugin/chat-message.ts">

<violation number="1" location="src/plugin/chat-message.ts:198">
P2: Unbounded map growth in agent-handoff state: `recordAgentObservation` stores per-session entries in a module-level Map, but `clearAgentObservation` is never called in production code during session cleanup.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/plugin/chat-message.ts Outdated
): Promise<void> => {
if (input.agent) {
setSessionAgent(input.sessionID, input.agent)
const priorAgent = recordAgentObservation(input.sessionID, input.agent)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Unbounded map growth in agent-handoff state: recordAgentObservation stores per-session entries in a module-level Map, but clearAgentObservation is never called in production code during session cleanup.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/plugin/chat-message.ts, line 198:

<comment>Unbounded map growth in agent-handoff state: `recordAgentObservation` stores per-session entries in a module-level Map, but `clearAgentObservation` is never called in production code during session cleanup.</comment>

<file context>
@@ -194,6 +195,13 @@ export function createChatMessageHandler(args: {
   ): Promise<void> => {
     if (input.agent) {
       setSessionAgent(input.sessionID, input.agent)
+      const priorAgent = recordAgentObservation(input.sessionID, input.agent)
+      if (priorAgent) {
+        output.parts.unshift({
</file context>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/plugin/chat-message.test.ts">

<violation number="1" location="src/plugin/chat-message.test.ts:885">
P3: This test does not actually prove the marker is prepended ahead of existing parts; it uses an empty `parts` array, so an append regression would still pass.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/plugin/chat-message.test.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/features/agent-handoff/state.ts">

<violation number="1" location="src/features/agent-handoff/state.ts:4">
P2: `clearAgentObservation` is exported but never called outside tests; module-level per-session Maps grow unbounded and lack lifecycle cleanup</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

import { getAgentConfigKey } from "../../shared/agent-display-names"

const lastObservedAgent = new Map<string, string>()
const lastObservedMessageID = new Map<string, string>()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: clearAgentObservation is exported but never called outside tests; module-level per-session Maps grow unbounded and lack lifecycle cleanup

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/agent-handoff/state.ts, line 4:

<comment>`clearAgentObservation` is exported but never called outside tests; module-level per-session Maps grow unbounded and lack lifecycle cleanup</comment>

<file context>
@@ -1,25 +1,43 @@
 import { getAgentConfigKey } from "../../shared/agent-display-names"
 
 const lastObservedAgent = new Map<string, string>()
+const lastObservedMessageID = new Map<string, string>()
 
 /**
</file context>

Tip: Review your code locally with the cubic CLI to iterate faster.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 2 files (changes from recent commits).

Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.

@PeterPonyu

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai please re-review — both prior P2/P3 findings should now be resolved:

  • Unbounded map growth (state.ts) — fixed in 562d977c8d. clearAgentObservation(sessionID) is now called from the session.deleted handler in src/plugin/event.ts, alongside the existing per-session cleanup helpers (clearSessionAgent, clearSessionModel, etc.). Identified by cubic.
  • Weak prepend assertion (chat-message.test.ts) — fixed in the same commit. The test now seeds output.parts with one existing user-message text part and asserts the marker lands at index 0 AND the original user part is preserved at index 1, so an append regression would fail both halves. Identified by cubic.

Touched-area suites: 49/49 pass. bun run typecheck + bun run build: green.

@cubic-dev-ai

cubic-dev-ai Bot commented May 14, 2026

Copy link
Copy Markdown

@cubic-dev-ai please re-review — both prior P2/P3 findings should now be resolved:

  • Unbounded map growth (state.ts) — fixed in 562d977c8d. clearAgentObservation(sessionID) is now called from the session.deleted handler in src/plugin/event.ts, alongside the existing per-session cleanup helpers (clearSessionAgent, clearSessionModel, etc.). Identified by cubic.
  • Weak prepend assertion (chat-message.test.ts) — fixed in the same commit. The test now seeds output.parts with one existing user-message text part and asserts the marker lands at index 0 AND the original user part is preserved at index 1, so an append regression would fail both halves. Identified by cubic.

...

@PeterPonyu I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 8 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Auto-approved: The handoff marker is purely additive with thorough test coverage (41 passing tests), uses messageID dedup to avoid dual-fire bugs, normalizes agent names to prevent false transitions, and cleans up state on session delete, so there is no path for regressions in existing behavior.

@code-yeongyu code-yeongyu self-assigned this May 16, 2026
@code-yeongyu

Copy link
Copy Markdown
Owner

[sisyphus-bot]

PR sweep status check on dev.

  • Review decision: APPROVED
  • Mergeable state: CONFLICTING (merge conflict with current dev)

Approved but blocked on merge conflicts. Please rebase onto current dev and resolve. Once green, the owner can merge.

Assigning code-yeongyu for owner visibility on the merge decision.

@PeterPonyu

PeterPonyu commented May 17, 2026

Copy link
Copy Markdown
Contributor Author

Merged upstream/dev@75223149 into the branch to clear the CONFLICTING state (chose merge over rebase since the PR has 4 commits and a rebase would replay against intermediate partial states).

Conflict resolution — both files had small import-section conflicts that were also dup-import accidents from the original PR:

  • src/plugin/chat-message.ts (lines 17-25 before merge): kept agent-handoff imports (recordAgentObservation, renderHandoffMarker) AND upstream's new PluginContext + applyUltraworkModelOverrideOnMessage imports; dropped two duplicate imports (parseRalphLoopArguments, CreatedHooks) that were already imported higher up in the file.
  • src/plugin/chat-message.test.ts (lines 16-21): kept _resetAllForTests as resetAgentHandoffState from agent-handoff AND upstream's createChatMessageHandler; dropped duplicate unsafeTestValue (already on line 6).
  • src/plugin/event.ts auto-merged cleanly.

Local verification:

  • bun test src/features/agent-handoff/ src/plugin/chat-message.test.ts51 pass / 0 fail across 3 files
  • bunx tsc --noEmit clean for touched files
  • Mergeable state should flip from CONFLICTING to MERGEABLE on next GitHub sync.

Agent-handoff identity-marker behavior is unchanged; the merge only updated context for the new chat-message structure on dev.

@code-yeongyu

Copy link
Copy Markdown
Owner

[sisyphus-bot] Hi PeterPonyu. 🙏 Thanks for the agent-handoff identity-marker work.

Picking this back up from the 5/16 approval. The PR is medium-scope (422 additions, 8 files) and shows CONFLICTING against current dev. The chat.message + agent-resolution layers have been touched, especially around the recent internal-initiator-marker work in #4223 (still in flight) which targets a similar tagging concern from a different angle.

The intent (inject identity marker on mid-session agent change so the new agent's context is unambiguous) is still valid. Could you rebase against current dev and clarify how it composes with #4223's createInternalAgentTextPart / isSyntheticOrInternalTextPart helpers? They both attach internal-routing markers but in different contexts; the rebase will surface whether they should share a marker module or stay separate.

Once it's clean and the composition is clear, I'll come back for a focused review.

@PeterPonyu PeterPonyu force-pushed the feat/agent-handoff-marker branch from 4a75e06 to 94a3482 Compare May 21, 2026 07:57
@PeterPonyu

Copy link
Copy Markdown
Contributor Author

Rebased onto current dev (force-pushed feat/agent-handoff-marker) and addressed the composition question with #4223.

The handoff marker is now wrapped via createInternalAgentTextPart(renderHandoffMarker(...)) rather than emitted as a bare { type: "text", text } part. Concretely in src/plugin/chat-message.ts:

output.parts.unshift({
  id: `prt_${randomUUID()}`,
  sessionID: input.sessionID,
  messageID: input.messageID,
  ...createInternalAgentTextPart(
    renderHandoffMarker({ prior: priorAgent, current: input.agent }),
  ),
})

This keeps the two concerns separate but composed:

  • renderHandoffMarker (in src/features/agent-handoff/marker.ts) — pure renderer of the <identity-handoff prior=... current=...> body
  • createInternalAgentTextPart (in src/shared/internal-initiator-marker.ts, from fix(runtime-fallback): harden delegated fallback retry flow #4223) — wraps text with OMO_INTERNAL_INITIATOR_MARKER so downstream guards classify it as internal routing rather than user content

So isSyntheticOrInternalTextPart and isSyntheticOrInternalOnlyTextParts now recognize the handoff injection without needing handoff-specific logic. No shared marker module is needed — agent-handoff stays focused on transition detection, and the marker semantics live in internal-initiator-marker where #4223 already centralized them. The opencode part-validation fields (id / sessionID / messageID) are preserved via spread.

Suites pass locally: agent-handoff + chat-message (51/51).

@PeterPonyu PeterPonyu force-pushed the feat/agent-handoff-marker branch from 94a3482 to 52d6951 Compare May 22, 2026 15:53
PeterPonyu added a commit to PeterPonyu/oh-my-openagent that referenced this pull request May 22, 2026
…n internal markers

Design: the same OMO_INTERNAL_INITIATOR_MARKER token serves two
semantically distinct contexts that MUST stay separate.

VISIBLE markers (createInternalAgentTextPart, no synthetic flag):
PR code-yeongyu#4003 agent-handoff identity markers — the agent MUST read its new
identity from the chat stream. If these were synthetic, the LLM would
be blind to its own role change and fail to switch identities.

HIDDEN markers (createInternalAgentContinuationTextPart, synthetic: true):
PR code-yeongyu#4223 internal retry/compaction prompts — invisible to the LLM as
user input so they do not pollute the agent's perceived conversation.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@code-yeongyu code-yeongyu force-pushed the dev branch 2 times, most recently from eb25d29 to 2bfad49 Compare May 23, 2026 16:00
@PeterPonyu

Copy link
Copy Markdown
Contributor Author

Rebased onto current upstream/dev and composed with #4223's createInternalAgentTextPart.

Handoff marker is now wrapped via createInternalAgentTextPart(renderHandoffMarker(...)) — separate concerns, composable. isSyntheticOrInternalTextPart recognizes the injection without handoff-specific logic.

  • bun test src/features/agent-handoff/ src/plugin/chat-message.test.ts → 51/51 pass
  • cubic: APPROVED (5/5 confidence, 0 issues)
  • CI: all green

Ready for merge decision.

@PeterPonyu

Copy link
Copy Markdown
Contributor Author

recheck

PeterPonyu and others added 9 commits May 24, 2026 00:04
When the user Tab-switches the active agent (e.g. sisyphus →
hephaestus), opencode does update input.agent for the next turn, but
the LLM keeps introducing itself by the prior name because chat history
still contains assistant turns authored under the old persona.

This commit re-anchors the new agent on the very first turn after a
transition by prepending an <identity-handoff> marker as a text part:

    <identity-handoff prior="sisyphus" current="hephaestus">
    You are now hephaestus.
    Prior assistant turns in this conversation were authored by
    sisyphus; read them as handoff context, not as your own past output.
    Apply the persona, tools, and policies configured for hephaestus
    from this turn forward.
    </identity-handoff>

Design notes:
- New src/features/agent-handoff/ keeps its own lastObservedAgent map
  rather than reusing setSessionAgent / getSessionAgent. The latter is
  first-write-wins (used by 7+ callers for "this session's primary
  agent" identity) — overloading it would break those.
- Comparison is normalized via getAgentConfigKey, so display variants
  like "Hephaestus - Deep Agent" don't trigger false transitions.
- Marker is unshifted to parts[0] so the LLM sees the identity reset
  before any user content on this turn.
- Pre-existing /start-work integration test caught a real state-leak
  across describes; file-level afterEach now resets the handoff map.

Tests: 41/41 pass across the new feature module + the existing
chat-message integration suite (with the added agent-handoff describe
block covering first-turn / same-agent / transition / display-variant
normalization / consecutive-transitions cases).

bun run typecheck: pass. bun run build: pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ction

Live opencode test surfaced a schema error: when the unshift in
chat-message.ts pushed a bare {type:"text", text:"..."} into
output.parts on an agent transition, opencode 1.14.49's
session.prompt.createUserMessage rejected the request with "Missing
key at [id]/[sessionID]/[messageID]" and the message never saved.
The unit tests passed because the test harness builds the same loose
shape, but the real opencode runtime validates against the full
TextPart schema:

    { id: string, sessionID: string, messageID: string, type: "text", text: string }

Fix:
- Marker injection now includes id (prt_<uuid>), sessionID, messageID.
- ChatMessageInput exposes messageID (already in opencode's hook
  contract; our narrower type didn't carry it through).
- If messageID is undefined (test mocks, edge cases) the transition is
  still RECORDED (so subsequent turns won't re-fire) but the visible
  marker is skipped — we can't construct a valid part without it and
  we'd rather no-op than crash.
- createMockInput now defaults messageID: "msg_test" so the existing
  test suite continues to exercise the injection path.
- New tests verify part shape (id/sessionID/messageID present) and the
  messageID-missing skip path.

bun run build: pass. agent-handoff + chat-message suites: 43/43 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live opencode diagnostic captured what was causing the double-marker
on every turn: opencode fires chat.message twice per user message,
3-5 ms apart, with the same sessionID and messageID but different
input.agent values:

  call 1: agent = the user-selected agent (Hephaestus after Tab)
  call 2: agent = the session-default agent (always Sisyphus -
          Ultraworker on this build), an internal mechanism

Both firings hit recordAgentObservation, so the stale call kept
flipping lastObservedAgent and every subsequent turn looked like a
transition in both directions.

Fix: dedup by messageID. The first call per messageID is canonical
and records normally; subsequent calls with the same messageID are
no-ops (no observation update, no marker). If messageID is undefined
(test mocks, edge cases), every call is treated as a fresh turn and
dedup is bypassed.

- recordAgentObservation now takes an optional messageID arg and
  tracks lastObservedMessageID per session.
- clearAgentObservation and _resetAllForTests both clear the new map.
- chat-message.ts passes input.messageID through.
- createMockInput now generates a fresh messageID per call by default
  so existing tests model distinct turns; tests that need to simulate
  the dual-fire pattern can pass the same messageID explicitly.
- New unit tests for state.ts (5) and one end-to-end integration test
  in chat-message.test.ts exercise the dual-fire scenario directly.

bun run build: pass. agent-handoff + chat-message suites: 49/49 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ngthen prepend test

Two cubic-dev-ai findings. Identified by cubic.

1. Unbounded map growth in agent-handoff state. recordAgentObservation
   stores per-session entries in module-level Maps (lastObservedAgent,
   lastObservedMessageID), but clearAgentObservation was only called
   from tests. In a long-running opencode process that creates and
   discards many sessions, those maps would grow without bound.

   Fix: call clearAgentObservation(sessionID) from the session.deleted
   handler in src/plugin/event.ts, alongside the existing per-session
   cleanup helpers (clearSessionAgent, clearSessionModel,
   clearSessionPromptParams, etc.).

2. The "marker injected as first part" integration test used an empty
   parts array, so an append regression (output.parts.push instead of
   unshift) would still satisfy parts[0] === marker.

   Fix: seed the output with one existing text part ("hi, this is my
   message") and assert both that the marker landed at index 0 AND
   that the original user part is preserved at index 1. An append
   regression would now fail both halves.

bun run build: pass. agent-handoff + chat-message suites: 49/49 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n internal markers

Design: the same OMO_INTERNAL_INITIATOR_MARKER token serves two
semantically distinct contexts that MUST stay separate.

VISIBLE markers (createInternalAgentTextPart, no synthetic flag):
PR code-yeongyu#4003 agent-handoff identity markers — the agent MUST read its new
identity from the chat stream. If these were synthetic, the LLM would
be blind to its own role change and fail to switch identities.

HIDDEN markers (createInternalAgentContinuationTextPart, synthetic: true):
PR code-yeongyu#4223 internal retry/compaction prompts — invisible to the LLM as
user input so they do not pollute the agent's perceived conversation.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@PeterPonyu

Copy link
Copy Markdown
Contributor Author

Closing as too-large feature work with no surgical version.

This PR is a 480-line / 10-file net-new feature (agent identity marker on mid-session agent change). It doesn't fix a specific filed issue.

Will re-propose if maintainer signals interest. The XML-escape security follow-up (originally a75f064 in #4310) is a tiny standalone fix that depends on this feature's foundation existing first — also deferred.

@PeterPonyu PeterPonyu closed this May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants