From 8ad50b7e9307a4fea07f21504a9cfbdc3bac5dcb Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Mon, 27 Apr 2026 17:36:14 +0900 Subject: [PATCH] docs(agents): replace ZWSP policy with sort-shim policy Document the canonical ordering mechanism (Array.prototype.toSorted/sort shim in src/shared/agent-sort-shim.ts) and forbid ZWSP, U+2060, U+00AD, ANSI escape, and ASCII space prefixes. Reference sst/opencode#19127 as the upstream fix that will obsolete the shim. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/plugin-handlers/AGENTS.md | 42 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/plugin-handlers/AGENTS.md b/src/plugin-handlers/AGENTS.md index df6c8bf14..916dc0eb4 100644 --- a/src/plugin-handlers/AGENTS.md +++ b/src/plugin-handlers/AGENTS.md @@ -6,35 +6,41 @@ The canonical agent order is **sisyphus → hephaestus → prometheus → atlas**. -This order is enforced via two mechanisms working together: -1. `CANONICAL_CORE_AGENT_ORDER` in `agent-priority-order.ts` controls object key insertion order -2. `agent-key-remapper.ts` injects ZWSP-prefixed runtime names into the `name` field for OpenCode's `localeCompare` sort +This order is enforced via two cooperating mechanisms: +1. `CANONICAL_CORE_AGENT_ORDER` in `agent-priority-order.ts` controls object key insertion order in the agent map produced by `applyAgentConfig`. +2. `installAgentSortShim()` in `src/shared/agent-sort-shim.ts` narrows `Array.prototype.toSorted` and `Array.prototype.sort` so that whenever the sorted array contains two or more agent objects whose `.name` matches a canonical core display name, OpenCode's `Agent.list()` (and any other sort site) returns the canonical order. The shim is installed once at plugin entry, before any agent registration. -### Why Two Mechanisms +### Why a Sort Shim -OpenCode's `Agent.list()` sorts agents by `name` field via `localeCompare`. Object key order alone is not enough. The `name` field carries ZWSP prefixes (1-4 chars) so core agents sort before alphabetically-named agents. +OpenCode 1.4.x sorts agents purely by `agent.name` via Remeda `sortBy`, which uses native string `<` / `>` comparison (NOT `localeCompare`). It currently ignores the agent `order` field. Until that lands (sst/opencode#19127), object-key insertion order alone does not survive `Agent.list()`, and biasing the sort key with invisible characters all failed: +- ZWSP (U+200B): `Bun.stringWidth` returns 0 but terminals (Ghostty, WezTerm, Alacritty, certain Windows Terminal builds) render it as 1-cell wide. Visible gap in the status bar; column truncation in the agent picker (#3259). +- U+2060 WORD JOINER, U+00AD SOFT HYPHEN, ANSI escape: same width-mismatch class. +- Removing the prefix and relying on insertion order alone falls back to alphabetical Atlas → Hephaestus → Prometheus → Sisyphus. -ZWSP is intentionally used in the `name` field only. It MUST NOT appear in: -- Object keys (used as HTTP header values, causes RFC 7230 violations) -- Display names returned by `getAgentDisplayName()` -- Config keys +The sort shim resolves this by intercepting only the narrow case it cares about, with strict activation guards to prevent collateral damage from a global prototype patch: +- The activation predicate (`isAgentArray`) requires `arr.length >= 2`, every element is a non-null object with a string `.name`, and at least 2 elements have a `.name` matching one of the four canonical core display names. This rejects mixed-type arrays (numbers, strings, plain objects without `.name`) so unrelated `.sort()` / `.toSorted()` calls execute native semantics. +- The comparator never throws on mixed input — it defensively extracts `.name` and falls back to the user-supplied `compareFn`. +- `installAgentSortShim()` is idempotent. ### History -Agent ordering has caused 15+ commits, 8+ PRs, and multiple reverts due to: -1. Early ZWSP attempts that leaked into HTTP headers via object keys -2. Object.entries() iteration order depending on merge sequence -3. Multiple code paths assembling agents differently +Agent ordering has caused 15+ commits, 8+ PRs, and multiple reverts. Notable milestones: +- #3260 (merged): removed ZWSP injection. Reverted by `0d5b08744` because OpenCode 1.4.x ignores `order`, and removal alone causes alphabetical fallback (Atlas → Hephaestus → Prometheus → Sisyphus). +- #3329 (merged): introduced `CANONICAL_CORE_AGENT_ORDER` and locked the policy. Insertion order alone still does not survive OpenCode's `Agent.list()` sort. +- #3267 (closed): proposed a sort shim. Closed at the time on the assumption that #3329 was sufficient. Revived in this commit with cubic P1 mitigations (defensive comparator, strict activation predicate, idempotent install). ### Forbidden Patterns DO NOT introduce: -- ZWSP in object keys or display names (only allowed in `name` field via `getAgentRuntimeName()`) -- Runtime sort shims or comparators -- Alternative ordering constants -- Object.entries() order dependencies +- ZWSP, U+2060, U+00AD, ANSI escape, or any other invisible / control character in agent names, display names, or object keys. +- ASCII spaces or other visible sort prefixes on agent names. +- Alternative ordering constants outside `CANONICAL_CORE_AGENT_ORDER`. +- Object.entries() iteration-order dependencies. +- Agent name string comparisons that skip `getAgentConfigKey` / `stripInvisibleAgentCharacters` (legacy ZWSP-baked data must keep resolving). -PRs attempting these patterns will be rejected. +The sort shim in `src/shared/agent-sort-shim.ts` is the ONLY supported runtime ordering mechanism. Remove it once OpenCode honors the agent `order` field (sst/opencode#19127). + +PRs attempting any of the forbidden patterns will be rejected. ## OVERVIEW