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 <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user