Skip to content

Commit b0dec70

Browse files
authored
docs(plugin-chatbot): useAgents' shape table said the servers are unenveloped — they are not (objectstack#4053) (#3080)
`extractAgentList`'s docstring was written ahead of the server conversion and froze that moment — "mid-migration", "`{ agents }` today's shape, both producers", "reading the envelope BEFORE any producer emits it". Both producers have since converted (objectstack#4124, cloud#929), so `{ agents }` is nobody's shape on a current server. The four-shape tolerance is unchanged and stays: a console release is not pinned to the server it runs against. What changed is the record — the table now leads with the enveloped wire and labels the bare shapes as back-compat, with an explicit warning against narrowing them. An unrecognised shape here yields an empty list, which `useAiSurfaceEnabled` turns into "hide the entire AI surface" — indistinguishable from the legitimate seat-less (ADR-0068) and Community Edition states. Comments and test names only; no behavior change.
1 parent 7f0252e commit b0dec70

2 files changed

Lines changed: 40 additions & 25 deletions

File tree

packages/plugin-chatbot/src/__tests__/agentListShapes.test.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
* `GET /api/v1/ai/agents` shape tolerance (objectstack#4053).
55
*
66
* Two producers serve this route — the framework dispatcher's degraded fallback
7-
* when no AI service is registered, and cloud's `service-ai` — and it is
8-
* mid-migration onto the platform's declared `{ success: true, data }` envelope.
7+
* when no AI service is registered, and cloud's `service-ai`. Both answer in the
8+
* platform's declared `{ success: true, data }` envelope now (objectstack#4124,
9+
* cloud#929); the unenveloped shapes below are back-compat for deployments from
10+
* before that, since a console release is not pinned to the server it runs against.
911
*
1012
* Why this file exists rather than a comment: an unrecognised shape here does not
1113
* throw, warn, or log. It yields an empty list, and `useAiSurfaceEnabled` turns an
@@ -16,9 +18,10 @@
1618
* no visible difference. The tests are the only thing standing between an envelope
1719
* conversion on the server and the AI UI quietly vanishing for every user.
1820
*
19-
* Teaching the reader all three shapes BEFORE any producer converts is what lets
20-
* the server side move on its own schedule instead of landing in lockstep with a
21-
* console release.
21+
* Teaching the reader every shape BEFORE any producer converted is what let the
22+
* server side move on its own schedule instead of landing in lockstep with a
23+
* console release — and it is why the two conversions could land in separate
24+
* repos, as separate PRs, with nothing here to change.
2225
*/
2326

2427
import { describe, expect, it } from 'vitest';
@@ -28,7 +31,7 @@ const ASK = { name: 'ask', label: 'Ask' };
2831
const BUILD = { name: 'build', label: 'Build' };
2932

3033
describe('extractAgentList — every shape this route answers in', () => {
31-
it('reads `{ agents }` — what both producers send today', () => {
34+
it('reads `{ agents }` — a pre-conversion server this console still meets', () => {
3235
expect(extractAgentList({ agents: [ASK, BUILD] })).toEqual([ASK, BUILD]);
3336
});
3437

@@ -38,13 +41,17 @@ describe('extractAgentList — every shape this route answers in', () => {
3841

3942
it('reads the declared envelope with `data` as the array', () => {
4043
// The shape objectstack#3983 set the precedent for: `data` carries the
41-
// payload directly. This is the variant that silently emptied the list.
44+
// payload directly. Neither producer took it — objectstack#4053 settled on
45+
// the relocation below — and this is the variant that would have silently
46+
// emptied the list had one of them. Read anyway: if a producer ever drifts
47+
// here, the console keeps working and the drift surfaces in that repo's own
48+
// envelope pins rather than as a vanished AI surface.
4249
expect(extractAgentList({ success: true, data: [ASK, BUILD] })).toEqual([ASK, BUILD]);
4350
});
4451

45-
it('reads the declared envelope with `data: { agents }`', () => {
46-
// The other plausible conversion — relocating the existing payload under
47-
// `data` rather than flattening it. Both must read the same.
52+
it('reads the declared envelope with `data: { agents }` — the shipped shape', () => {
53+
// The conversion both producers landed: the declared payload RELOCATED under
54+
// `data` rather than flattened. All four shapes must still read the same.
4855
expect(extractAgentList({ success: true, data: { agents: [ASK, BUILD] } })).toEqual([ASK, BUILD]);
4956
});
5057

packages/plugin-chatbot/src/useAgents.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -152,27 +152,35 @@ export function resolveDefaultAgentName(
152152
/**
153153
* Pull the agent list out of whatever `GET /api/v1/ai/agents` answered.
154154
*
155-
* This route is served by more than one producer and is mid-migration onto the
156-
* platform's declared `{ success: true, data }` envelope
157-
* (objectstack#4053), so three shapes have to read the same:
155+
* This route is served by more than one producer, and the conversion onto the
156+
* platform's declared `{ success: true, data }` envelope (objectstack#4053) has
157+
* now landed on BOTH — the framework's degraded fallback in objectstack#4124,
158+
* cloud's `service-ai` in cloud#929. Four shapes still read the same:
158159
*
160+
* { success: true, data: { agents } } what both producers serve today
161+
* { success: true, data: [ … ] } the envelope flattened — no producer
162+
* emits it; read so that one doing so
163+
* fails loudly upstream, not here
164+
* { agents: [ … ] } pre-conversion servers
159165
* [ … ] a bare array
160-
* { agents: [ … ] } today's shape, both producers
161-
* { success: true, data: … } the declared envelope, whose `data`
162-
* may be the array or `{ agents }`
166+
*
167+
* The last two are back-compat, not the current wire: a console release is not
168+
* pinned to the server it runs against, so a deployment from before the
169+
* conversion is ordinary. Do not read them as evidence the servers are
170+
* unenveloped — they are not, and a "cleanup" that re-narrows this to the bare
171+
* shape would break exactly the deployments it looks like it is following.
163172
*
164173
* The envelope is detected the way `ObjectStackClient.unwrapResponse` detects
165174
* it — a **boolean** `success` — so the two agree on what counts as one.
166175
*
167-
* Reading the envelope BEFORE any producer emits it is deliberate, and it is the
168-
* whole point of doing this ahead of the conversion. An unrecognised shape here
169-
* does not throw or warn: it yields an empty list, and `useAiSurfaceEnabled`
170-
* turns an empty list into "hide the entire AI surface". That is also the
171-
* CORRECT behaviour for a seat-less user or a Community-Edition deployment with
172-
* no `service-ai` — so a parse miss is indistinguishable from the legitimate
173-
* hidden state, with no error, no 403 and no log to notice it by. Teaching the
174-
* consumer first means the producer can convert on its own schedule instead of
175-
* having to land in lockstep with this file.
176+
* Why tolerance here rather than one shape and a parse error: an unrecognised
177+
* shape does not throw or warn. It yields an empty list, and `useAiSurfaceEnabled`
178+
* turns an empty list into "hide the entire AI surface". That is also the CORRECT
179+
* behaviour for a seat-less user or a Community-Edition deployment with no
180+
* `service-ai` — so a parse miss is indistinguishable from the legitimate hidden
181+
* state, with no error, no 403 and no log to notice it by. Reading every shape
182+
* this route has ever answered in is what kept the server conversions from having
183+
* to land in lockstep with a console release.
176184
*/
177185
export function extractAgentList(payload: unknown): RawAgent[] {
178186
const isEnvelope =

0 commit comments

Comments
 (0)