@shutter-network/concorde/messenger
The HTTP routes this component serves: 2 on the Agent server.
Every path is relative. Each one is printed as the plugin declares it, under whatever prefix the component's constructor registers that plugin at. This page states no mount point, because the constructor is where the mount point is decided.
Generated from what the route plugins in src/messenger/routes.ts declare, which is what a running Gateway serves at GET /openapi.json. Never edited by hand.
Agent server
The Agent server has no authentication of any kind. Everything below is reachable by the agent, and therefore by an injected prompt (ADR-0003). Nothing on it names a credential.
GET /
Read one User's Message log
One User's Messages, both directions, in the single numbered sequence that is their log. user is required. Not for confidentiality: reads are not scoped, and the agent may read every log there is. It is required because seq numbers one person's log and nothing else. An interleaved read would have no cursor to page by. Three cursor cases, one order. No cursor answers the newest page, which is what a client opening a conversation wants. before=N answers the newest page strictly below N, which is scrolling back. after=N walks forwards from N, which is polling, and after=0 is how a log is read from its beginning, since nothing is numbered 0 and no cursor at all means the newest page instead. All three answer ascending by seq, so a client concatenates pages without reversing anything. Passing after and before together is a 400, because it describes two windows rather than one. The envelope carries no more-results flag, because a full page is one. messages.length === limit means there may be more. The next request is this one with the cursor moved on. after takes the largest seq received, to walk forwards. before takes the smallest, to walk back. A short page is the end of that direction for now. There is no read state of any kind (no stored position, no unread count and no receipts). So the cursor a client needs is one it already holds, because it is holding the Messages. limit defaults to 50 and is capped at 200. A larger value is refused with a 400 rather than quietly reduced. The Messages past the cap are reachable by paging rather than lost. This is the one list in the framework with a cursor. A Message log is read by cursor and cannot be searched or filtered. The parameters are a window over one User's seq, and there is no full-text or field matching. An unknown query parameter is a 400, not a filter that did nothing and a request answered with everything.
Tagged Messages.
Query parameters
userstring, required, matching^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$afterinteger, optional, 0 to 2147483647Walk forwards from this
seq, exclusive: the poll.after=0reads the log from its beginning, oldest first, which no other spelling expresses.beforeinteger, optional, 0 to 2147483647The newest page strictly below this
seq: scrolling back. Answers ascending like every other case, so the page before the one in hand arrives the same way up.limitinteger, optional, 1 to 200, default50
Responses
200 application/json
The window that matched, ascending by seq, with both directions interleaved as the one log they are.
messagesarray ofobject, requiredidstring, requireduserIdstring, requireddirectionstring, required, one of"inbound","outbound"Which way it travelled.
inboundis the User to the agent andoutboundis the agent to the User, and only a User can cause an inbound one. Decided by the server the request arrived on rather than by any field, so there is nothing anywhere for a caller to set.seqinteger, requiredThis Message's number in one User's log, from 1, counting both directions. It is the cursor: the largest one held is what
aftertakes to read whatever has arrived since. It is not global and no other User's activity moves it, so two Users' Messages are not orderable against each other by it.textstring, requiredcreatedAtstring, required
400 application/json
user is missing or not a uuid, a cursor or limit is not an integer or is out of range, both cursors were passed, or a parameter this route does not take was written.
statusCodeinteger, requirederrorstring, requiredmessagestring, required
POST /
Send a Message to one User
An outbound Message: the agent to the User userId names. There is no direction field, and no way to write an inbound Message from this server. The server the request arrived on decides which way a Message travelled. An agent talked into speaking as somebody has nowhere to say so. A direction written into the body is stripped before the handler and reaches nothing. The Message is numbered as it is written, with the next seq in that User's log across both directions. The record answered is the stored one, so there is no read-back to do. An unknown query parameter is a 400, not a filter that did nothing and a request answered with everything.
Tagged Messages.
Request body
application/json, required.
userIdstring, required, matching^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$textstring, required, at least 1 character
Responses
201 application/json
The Message as it was stored, including the seq it was given.
idstring, requireduserIdstring, requireddirectionstring, required, one of"inbound","outbound"Which way it travelled.
inboundis the User to the agent andoutboundis the agent to the User, and only a User can cause an inbound one. Decided by the server the request arrived on rather than by any field, so there is nothing anywhere for a caller to set.seqinteger, requiredThis Message's number in one User's log, from 1, counting both directions. It is the cursor: the largest one held is what
aftertakes to read whatever has arrived since. It is not global and no other User's activity moves it, so two Users' Messages are not orderable against each other by it.textstring, requiredcreatedAtstring, required
400 application/json
userId is not a uuid, text is missing or empty, or a query parameter was written. A well-formed id naming nobody is a 404 rather than a 400, since only the write can tell.
statusCodeinteger, requirederrorstring, requiredmessagestring, required
404 application/json
No User has that id, and nothing was stored. There is deliberately no lookup in front of the write: userId is a foreign key onto the Users component's table, so a well-formed uuid naming nobody reaches the insert and the constraint is what refuses it. A malformed one never gets that far: the pattern on userId refuses it as a 400 first, which is what keeps a typo from being a 500 out of PostgreSQL.
statusCodeinteger, requirederrorstring, requiredmessagestring, required
503 application/json
The Message was not recorded, and sending it again is the right thing to do. Nothing is wrong with the request and the log is intact: seq is computed per User inside the insert and a unique constraint makes a lost race visible, so this is one User's own concurrent writers outrunning a bounded retry. A 503 and not a 500 for that reason.
statusCodeinteger, requirederrorstring, requiredmessagestring, required