Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
3f7aafe
feat(mcpl): stamp each accepted inbound item with its source envelope
Oct 7, 2026
f7e14f6
feat(chronicle): RecordJournal — typed records that don't follow bran…
Oct 7, 2026
2153e3d
fix(chronicle): a RecordJournal write that fails after reaching the s…
Oct 7, 2026
eb2a588
fix(mcpl): freeze ordinary inbound envelopes at admission; add lane f…
Oct 7, 2026
7c3d230
Merge Basil-1007's RecordJournal (2153e3d) as the shared base for the…
Oct 7, 2026
7144970
feat(mcpl): type MCPL request failures by what reached the server
Oct 7, 2026
0a8e9c7
Merge typed MCPL request failures (7144970)
Oct 7, 2026
2c591a7
feat(mcpl): one publish executor with structured delivery outcomes
Oct 7, 2026
7a91066
feat(drafts): hold suppressed plain speech as resendable drafts
Oct 7, 2026
15ec398
fix(mcpl): refuse invalid publish selectors; a contradictory receipt …
Oct 7, 2026
8e4146d
fix(drafts): keep uncertainty across attempts; resends own and re-che…
Oct 7, 2026
191ab09
fix(drafts): a re-bounce keeps the whole text and its risk; notices d…
Oct 7, 2026
6c5f054
fix(drafts): owner-scoped notice identity; true risk evidence; idempo…
Oct 7, 2026
e9099e5
fix(drafts): held envelopes keep hybrid state, receipts read draft st…
Oct 7, 2026
467a48f
fix(drafts): a held-draft notice gives a copied draft its risk; "noth…
Oct 7, 2026
a14e25a
fix(drafts): "not sent" only over words not at risk; exact-text claim…
Oct 7, 2026
6c927de
feat(mcpl): stamp each inbound item's delivered-body and stored-copy …
Oct 7, 2026
711b400
fix(mcpl): take the stored-copy digest at each storage site, over the…
Oct 7, 2026
d36a5ad
fix(mcpl): every stored push gets the host's delivered-body digest; e…
Oct 7, 2026
abc8b4d
feat(routing): speech goes to a deliberate or triggering route, never…
Oct 7, 2026
e354622
fix(routing): threads are never routes; unknown servers stay absent; …
Oct 7, 2026
6756af2
feat(routing): publish only where MCPL RFC-011 declares the place; th…
Oct 7, 2026
05bff07
fix(routing): receipts keep the destination as delivered; channel_pub…
Oct 7, 2026
cb93817
fix(routing): every receipt names its destination's server; gate thre…
Oct 7, 2026
42c3f5a
fix(gate): the frozen envelope names a batched candidate's conversati…
Oct 7, 2026
30ae48c
feat(mcpl): every channel item names its conversation in a stored sou…
Oct 7, 2026
8dfe8c7
fix(mcpl): literal header values; host-owned backscroll key; notice a…
Oct 7, 2026
8a06220
test(mcpl): stored-copy digests cover the source header; an edit leav…
Oct 7, 2026
49a7baf
fix(mcpl): a header stays one line — escape line separators and C1 co…
Oct 7, 2026
7c4c723
fix(mcpl): a label can't read as a tail; source is the host's key on …
Oct 8, 2026
1d6d2bb
Merge main (02adf27) into feat/prose-drafts: bring #247 onto current …
Oct 8, 2026
88a24e6
Merge the integrated #247 (1d6d2bb) into feat/ingestion-stamps: bring…
Oct 8, 2026
56827f5
fix(routing): a fork's route answers the message that woke it in its …
Oct 8, 2026
ff899f7
fix(mcpl): publish rechecks its destination after opening a closed ch…
Oct 8, 2026
ff29945
fix(gate): coalesced items are gated with the host envelope their coa…
Oct 8, 2026
eaba9a8
fix(routing): speech goes where the route stood when its words were w…
Oct 8, 2026
ae42e95
fix(streams): outgoing streams carry only published speech
Oct 8, 2026
c899120
Merge #252's integrated head (88a24e6) into feat/speech-routes: bring…
Oct 8, 2026
2287051
docs(push-handler): handlePushEvent's step list matches the handler
Oct 8, 2026
6d6f133
Merge #256's integrated head (2287051) into feat/source-headers: brin…
Oct 8, 2026
4180ccd
fix(lifecycle): late tool results and MCPL arrivals at stop() settle …
Oct 8, 2026
48e5135
Merge #256's shutdown fix (4180ccd) into feat/source-headers
Oct 8, 2026
315d12c
fix(drafts): refuse a dismissal while its draft's attempt is out; the…
Oct 8, 2026
4c73d3f
Merge #247's 315d12c into feat/ingestion-stamps
Oct 8, 2026
a2accc1
fix(mcpl): digest a body as the store keeps it, so an untouched store…
Oct 8, 2026
1bbf3f7
docs(routing): Breaking names scheduled wakes' lost fallback; comment…
Oct 8, 2026
2c83659
fix(streams): a later message on a channel opens a paragraph in its s…
Oct 8, 2026
8f29e62
Merge #252's a2accc1 into feat/speech-routes: #247's 315d12c and the …
Oct 8, 2026
eaf67dd
Merge #256's 8f29e62 into feat/source-headers
Oct 8, 2026
7adf10c
fix(mcpl): header-shaped body text is marked; body readers skip the h…
Oct 8, 2026
d981e49
test(mcpl): pin every branch of the copied raster sniffer with a misl…
Oct 9, 2026
0c4030c
fix(mcpl): header values and openings can't hide structure behind inv…
Oct 9, 2026
b801b10
test(mcpl): pin the class union and the per-unit escape; document the…
Oct 9, 2026
7a80df5
refactor(mcpl): drop the two #269 rule pieces that never change a ren…
Oct 9, 2026
378c2b3
test(drafts): pin that a draft's sending mark ends with its own attem…
Oct 10, 2026
e041853
fix(routing): no guessed server for a shared channel id; the fork rul…
Oct 10, 2026
d0078bc
Merge #256's e041853 into feat/source-headers: the haiku-finding rout…
Oct 10, 2026
4e19f02
fix(routing): routing names each conversation with its server; a shar…
Oct 10, 2026
69cf802
Merge #256's 4e19f02 into feat/source-headers: routing names each con…
Oct 10, 2026
2c198b5
fix(drafts): notices count on the branch they're on, the catch-up nam…
Oct 11, 2026
548071d
fix(publish): a publish always has a deadline, a refused send opens n…
Oct 11, 2026
d80210e
fix(drafts): a refused {{unsent}} holds its envelope, dismissals name…
Oct 11, 2026
9feee54
fix(envelope): only the framework writes the envelope, conversation k…
Oct 11, 2026
7cc9c21
test(publish): the refused-before-open case uses a grant that could o…
Oct 11, 2026
97d860d
test(digests): every storage site and store normalization is pinned, …
Oct 11, 2026
e0e11fd
Merge #247's sweep round (7cc9c21) into feat/ingestion-stamps
Oct 11, 2026
f288525
Merge #252's review round (e0e11fd, carrying #247's 7cc9c21) into fea…
Oct 11, 2026
7ce228e
test(digests): the store refuses an undefined array element, and cano…
Oct 11, 2026
f9f54bf
fix(routing): conversation keys can't collide, labels show as given, …
Oct 11, 2026
a8e3470
Merge #252's 7ce228e into feat/speech-routes
Oct 11, 2026
fdca2b9
fix(routing): forks own their channels' unroutable candidates, pins k…
Oct 11, 2026
535d44f
fix(routing): an evicted event keeps its route candidate, and a mid-t…
Oct 11, 2026
5065142
fix(routing): a silent wake's channel_open sets no route, typing show…
Oct 11, 2026
5036af4
fix(publish): one reading of a thread id, acceptance after the trigge…
Oct 11, 2026
a84c6e5
fix(stop): stop() settles the speech chains, and the tests wait for w…
Oct 11, 2026
11236a5
docs(changelog): #256's fragments say what the review round changed
Oct 11, 2026
fd8cfa4
fix(routing): a coalesced item carries its trigger inputs, and the re…
Oct 11, 2026
6aaeb85
test(routing): explicit-prose-routing and channel-label-history expec…
Oct 11, 2026
de92afb
Merge #256's review round (6aaeb85) into feat/source-headers
Oct 11, 2026
2c6ad2b
fix(source-headers): mark every sender-text opening, read sharded ite…
Oct 11, 2026
26edf15
fix(source-headers): the invitation prefers the registered label, and…
Oct 11, 2026
9dc8239
fix(routing): defaultOutgoing names its place as its publish does: nu…
Oct 11, 2026
967615a
Merge #256's 9dc8239 into feat/source-headers: defaultOutgoing names …
Oct 11, 2026
2690edb
feat(routing): a wake that names no conversation keeps the fallback r…
Oct 12, 2026
f1dde7f
fix(routing): only a wake that names no conversation takes the fallback
Oct 12, 2026
3f09c91
fix(routing): every lane says whether a wake names a conversation; re…
Oct 12, 2026
6114926
fix(routing): a wake that continues a turn names that turn's conversa…
Oct 12, 2026
698c31a
fix(routing): the fork rule judges a route at turn start; a bounce wa…
Oct 12, 2026
5ba5d8c
fix(routing): a fallback turn's continuation takes the fallback afres…
Oct 12, 2026
45cfe46
fix(routing): a continuation reads its turn's record when its own tur…
Oct 12, 2026
501fa63
fix(routing): a silent tick's record holds through its restarts, an u…
Oct 12, 2026
fabdc81
fix(routing): a conversation from a candidate marked unroutable stays…
Oct 12, 2026
0bae78b
fix(routing): a silent tick's restart that a batched wake makes ordin…
Oct 12, 2026
665957d
Merge #256's 0bae78b1 into feat/source-headers: the fallback rounds
Oct 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/269-header-invisible-characters.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- A source header no longer lets invisible or space-lookalike characters pass raw (#269). A value is quoted when it holds a format or default-ignorable character (zero-width characters, bidi overrides and isolates, fillers such as U+3164, the combining grapheme joiner, variation selectors), a space other than U+0020, or a `/` with whitespace on both sides; inside the quotes those characters are escaped as `\uXXXX`, so a quoted bidi override can't reorder what follows it. A label that starts with such a character is quoted, so a header word (`thread`, `reply to`, `unscoped`) behind it can't read as a field. Emoji sequences are exempt, joiners and selectors included, since a label is also a name a resident types back: `❤️ cats` stays as it is. The exemption is a pictographic character (symbols such as © and ® included) with an optional skin-tone modifier or presentation selector, joined to more by ZWJ; it doesn't check that the sequence is one Unicode recommends. Body text's header-shaped openings are marked through invisible and control characters too (`[​source:` with a zero-width space), wherever they stand inside the bracket. discord-mcpl's `source-header.ts` (#63) renders the same header by the same rule.
22 changes: 22 additions & 0 deletions changelog.d/inbound-source-envelope.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
- **Inbound source envelope.** Every inbound item the host accepts now carries
`metadata.inboundSource`, stamped once where it was accepted: the
conversation it belongs to (`channel`: MCPL server, endpoint binding,
canonical channel id, thread, message, reply edge and the channel's label at
receipt; `unscoped` for a push that names no channel; `surface` for console
or API input from a module), the host acceptance time and the adapter's own
timestamp. It covers `channels/incoming` and `push/event` on both the
ordinary and the RFC-006 coalesced paths; a coalesced occurrence freezes its
envelope at acceptance, so deferral, fan-out and replay deliver it unchanged
and a later rename never rewrites it. Adapter-supplied metadata cannot set
the field, and one a module's messages carry is dropped (an adapter's own
`origin.source` string is untouched). `readInboundSource`,
`conversationKey` (a JSON-encoded key, so no server-chosen part can run
into another) and `INBOUND_SOURCE_KEY` are exported.
- **Inbound source envelope: admission facts.** An ordinary `channels/incoming`
message or `push/event` now has its envelope frozen at admission, before it
is queued or acknowledged, so a rename or rebind while it waits cannot
change it. Every channel and unscoped envelope names the admission `lane`
(`channels/incoming` or `push/event`), and one that came through RFC-006
coalesced admission has `coalesced: true` (others have no `coalesced`
field): facts a consumer needs to know what an `eventId` is worth.
`readInboundSource` validates every field it returns.
32 changes: 32 additions & 0 deletions changelog.d/ingestion-body-digests.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
- **Delivered-body digests on every stored MCPL inbound item.** Both MCPL
lanes stamp two fields, outside the frozen source envelope (console and API
input, and the host's own notices, carry neither):
- `metadata.sourceBodyDigest` is the version identity of the body as
delivered, taken at ingestion, after MCPL conversion and before any host
decoration or storage sharding. It is the SHA-256 (hex) of `[blocks]`
as canonical JSON (keys sorted at every level, `undefined` object entries
dropped, and an `undefined` array element written as `null` as JSON
writes it, though the store refuses a body holding one), with the blocks
as the store keeps them: inline media as
the store hands it back (base64 re-encoded from its bytes, an image's
media type taken from its bytes' signature, only `type` and `source`
kept), strings well-formed (a lone surrogate as U+FFFD) and an entry
whose key isn't well-formed dropped, as the store's append drops it. So
the body hashes alike as delivered, as handed to storage and as read
back, and an undecorated, unsharded stored copy hashes to it.
- `metadata.storedBodyDigest` uses the same function over exactly the
blocks handed to storage, decorations included, taken where each path
stores.

Both are always the host's own: values an adapter supplies are replaced.
A stored copy, read back with its blobs resolved, that no longer hashes to
its `storedBodyDigest` was changed after it arrived (`editMessage` keeps
metadata). That check is of an unsharded copy: a body the store shards
(context-manager's adaptive ingress chunking) carries the whole body's two
digests on every shard (`bodyGroupId` and `shardIndex` mark them), and no
shard hashes to them. A `blob_ref` block is digested as given, while the
store resolves it on read, so a copy holding one doesn't match; the MCPL
lanes never store one. `sourceBodyDigest(blocks)` is exported from the
package root as the one implementation consumers share; it carries the
framing and the store's normalization, so check a copy with it rather than
hashing your own serialization.
10 changes: 10 additions & 0 deletions changelog.d/mcpl-request-outcome.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
- **`McplRequestError`: why an MCPL request produced no result.** A request
to an MCPL server that fails now rejects with `McplRequestError` (exported),
whose `outcome` says which of three things happened: `not-sent` (refused
before anything was written: the connection was already closed),
`error-response` (the server answered with a JSON-RPC error; `code` and the
error's `data` are kept), or `no-response` (the request was handed to the
transport and no answer came back: a timeout, or the connection closed
while awaiting; it may or may not have reached the server). Only
`not-sent` proves the server never saw the request. Messages are unchanged,
and it is still an `Error`, so existing handling keeps working.
78 changes: 78 additions & 0 deletions changelog.d/prose-drafts.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
- **Held prose drafts: suppressed speech is kept, not destroyed.** Plain
speech that is not sent is now held as a draft with its exact words,
instead of surviving only as a count: speech suppressed by an
explicit send in the same round (locus and hybrid modes, mid-turn, trailing
and text-only, native and XML tool modes), hybrid prose left without a
destination after a failed envelope, and explicit/hybrid bounces (which
replace the in-memory latest-wins clipboard; `{{unsent}}` keeps meaning the
latest bounce). A draft held beside a send keeps its run exactly as
written, indentation and surrounding newlines included; routing still
publishes trimmed segments. Deliberate privacy is never drafted:
`skip_reply` (the tool, or a hybrid `>>>skip_reply` envelope, which keeps
the rest private until a destination is named), a same-round private
`think` in a native round (where that policy applies: XML tool mode routes
such prose as ordinary speech, so a send holds it as a draft), silent turns
and `proseRouting: disabled` narration stay private and are counted in the
receipt.
- In hybrid mode a send withholds publication, not what an envelope does: a
held `>>>` envelope moves the routing state as live routing would (a
destination it names becomes the sticky target), `>>>skip_reply {{unsent}}`
still sets the latest bounce aside, and the draft holds the message the
envelope would have published, with `{{unsent}}` expanded under the
re-bounce rule (a bare `{{unsent}}` leaves the bounce as the one draft).
- Drafts live in typed Chronicle records (`RecordJournal`) that do not follow
branch switches: undo, rollback and checkout never change them, and they
survive restarts. Nothing is ever sent from them on its own. Each
resident's 100 newest drafts are kept, and an older one for 7 days after
anything last happened to it (held, named, attempted, answered, dismissed);
the journal checkpoints what it keeps every 500 entries, so opening it
replays only what follows. A request naming a draft that was let go is
refused, and nothing is sent.
- A private notice names held drafts at the next tool boundary when the live
stream presents mid-turn messages (a draft holding words copied from one
that may already have been posted is named with that risk and the
`confirmDuplicate` its resend needs), and the turn-end `[delivered]` receipt
names every draft held in the turn by its state at turn end: still held
("held as drafts d-… (not sent …)"), delivered by the resident's resend
(listed where it landed), unconfirmed, or dismissed. A dismissed draft
whose own attempt may already have been posted is named with that risk,
and counts like an unconfirmed one: a receipt that names one, with nothing
confirmed delivered, reads "nothing confirmed", not "nothing". A draft
counts as named while a message naming it is on the current branch, so one
no notice reached (a crash, an aborted turn), or whose notices a rollback
or branch move took away, is named at the next turn's start: every such
draft by id, the newest eight with a preview.
- A draft is never published except by its resident's explicit resend (or
`{{unsent}}`), and only that resident can list or act on it. Its notices go
into the resident's own history, which residents sharing one message slot
already share, as they share the assistant turn the words came from.
- New `drafts` tool (whenever MCPL channels are configured): `list`, `read`,
`resend` and `dismiss`, scoped to the calling resident. Every such
resident's tool list gains it, so each one's first request after upgrading
misses the prompt cache once. `resend` publishes
drafts verbatim, in order, to one destination through the normal publish
path, journaling each attempt durably before the request leaves; it counts
as an explicit send. A delivered draft returns its historical receipt
instead of sending again; a draft with any attempt whose outcome is
unknown (and none confirmed) needs `confirmDuplicate: true`, and a resend
owns its drafts until it finishes, re-checking each before it is sent, and
a draft it doesn't attempt is told why the batch stopped. Dismissing a
draft (with the tool or `>>skip_reply {{unsent}}`) is refused while that
draft's attempt is out, since a dismissal can't call back words already on
their way; the resident is told to wait for that result. A draft only
queued in a resend can still be dismissed, and is then not sent. A
dismissal of a draft that may already have been posted names that risk.
Unused fields may be passed as null.
- `{{unsent}}` whose draft is being sent, queued in a resend, or unconfirmed
sends nothing, and the envelope's own words are held as a bounce draft
with `{{unsent}}` written out and the risk carried, so nothing the
resident wrote has to be typed again.
- Prose segments also break at XML tool mode's refused attempts and their
notices (`tool_attempt`, `tool_notice`): the words before an all-refused
call and after its notice are two messages, routed or held separately.
- `ChannelRegistry.deliverSpeech` (routeSpeech with its `PublishOutcome`) and
`ChannelRegistry.resolveDestination` are new. `routeSpeech` now delegates
to `deliverSpeech` and returns null unless delivery was confirmed, so a
missing receipt and a channel id two servers share now return null (see
the publish-outcomes entry).
- The bounce notice now goes to the bouncing resident rather than the primary.
23 changes: 23 additions & 0 deletions changelog.d/publish-outcomes.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
- **Speech delivery reports what a publish actually established.** Plain
speech now goes through one publish executor (`ChannelRegistry.publish`,
returning a `PublishOutcome`: `delivered`, `failed` or `unknown`, with the
destination the registry resolved). Only a connector's `delivered: true`
confirms a post; a missing or malformed receipt is no longer counted as
delivered (#163's rule). `failed` means nothing was posted: refused before
dispatch, or `delivered: false` naming no message. An error response, a
timeout or a lost connection after dispatch is `unknown`, keeping the
connector's own words (a multi-part send may already be partly posted), and
so is a contradictory receipt (`delivered: false` beside a message id,
valid or not).
The resident's `[discord-send-failed]` marker now says which: "could not be
delivered … Nothing was posted" for a failure, and "was not confirmed … may
or may not have been posted; check the channel before sending it again"
for an uncertain outcome. The marker's metadata and the
`mcpl:speech-route-failed` trace carry `outcome`. A channel id registered
by more than one MCPL server is refused rather than routed through
whichever server registered it first, and a supplied but empty or
non-string server selector is refused rather than read as omitted. A send
refused for its server or grant opens no closed channel. A
`channels/publish` request always has a deadline: with `requestTimeoutMs`
0 it keeps the 60 s default, since its outcome is told to the resident and
recorded for its draft; its expiry reads as unknown.
70 changes: 70 additions & 0 deletions changelog.d/source-headers.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
- **Visible source headers on channel traffic.** Every channel-bearing item
from either MCPL lane begins with a header naming the conversation it came
from: `[source: <server> / <canonical-channel-id> · <label at receipt>]`,
with ` · thread <id>` and ` · reply to <id>` when the item has them. A push
that names no channel is marked `[source: <server> · unscoped]` rather than
given a guessed channel. This covers creates, coalesced deliveries and
corrections. The header is rendered once at ingestion from the item's own
frozen source envelope and stored with the message, so a later rename, a
recompile or a replay never rewrites it, and a message names its channel
when read alone, even the second of two consecutive messages. Console/API
input has no header, but its header-shaped text is marked as a channel
body's is (below). `metadata.sourceHeader` is always the host's: it is set
from the rendered header and removed when none was rendered, so an
adapter-supplied value never survives.
- A header value that could read as structure is written as a quoted JSON
string: one holding a bracket, `·`, ` / `, a quote, a backslash or a
control or line-separator character, or one a reader can't see or tell
from a space (the invisible and space-lookalike characters the #269
fragment lists), and a label whose first word is `thread`, `reply to` or
`unscoped`, in any case, followed by whitespace or by nothing
(`thread:ops` isn't quoted for it). A header stays one line, and a label
never reads as a thread or reply.
- Routing notices, receipts, draft lists and the `[discord-send-failed]`
marker name a conversation by the same rule, so a server-chosen label or
id can't imitate a host notice.
- **Stored body text changes in one way: header-shaped text is marked.**
Every `[source:` or `[source]` in an item's own text blocks, in any case and
with any whitespace, control or invisible character inside the bracket
(between the letters of `source` too), is stored with a backslash before
its bracket (`\[source:`), wherever it falls: at a line start, mid-line, or
split across blocks. Without this, anyone who can post in a channel could
forge a header naming another channel, and a model could take it for the
host's attribution, and for a place to `channel_publish` to. Every position
counts: in XML tool mode the formatter writes `participant: ` before each
message, so the real header sits mid-line too, and a body could otherwise
stage a whole message from another channel. Only the host's header is left
unmarked. A closed-channel invitation, `channel_open` backscroll item
text (an item's string content included), a local surface's input and the
legacy Discord module's stored and edited text are marked the same way.
At the Discord module's first history sync after upgrading, a stored
message that holds an opening is edited once, to its marked text.
`sourceBodyDigest` is still taken from the body as delivered, before the
marking. `storedBodyDigest` is of the blocks as handed to storage, the
header and the marking included. The copy check applies to an unsharded
copy, and a body the store shards carries the whole body's digests on
every shard. Visible look-alike characters can still imitate the
opening.
- `channel_open` backscroll items each gain a `source` field. It is rendered
from the item's own `channelId`, so an item spliced in from another channel
wears its true channel. The label comes from the registry for that id, else
the adapter's item `channelLabel`. `source` is the host's key: an item's
own `source` is kept as `adapterSource`, and an item that names no channel
has no `source`.
- Readers of what was said leave the header out. History `search` (substring
and regex) matches and snips the body, so `^` anchors at the body's start
and a channel's name matches where it was written, not every item from that
channel; the `channelId` filter is how to ask where. The semantic index
embeds the body, and its snippets show it. The tune-out backlog lists
`author: body`. That holds for a long item the store shards too, whose
first shard begins with the header. History `extract` still shows each
item with its header.
- The canonical id is authoritative when a label differs. That rule, that
the host writes a header only as a message's first line, that
`\[source…` in a message is its sender's text, and that any such line in
a connector tool's own output belongs to that tool result, are stated in
the `channel_list`, `channel_open` and `channel_publish` descriptions, and
in a one-time `[source]` notice the first time a resident holds an item
with a header, a channel item or an unscoped push. A notice already in a
resident's context counts as given, so a failed record of it doesn't
repeat it.
30 changes: 30 additions & 0 deletions changelog.d/speech-routes.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
- **Hosts and module authors using `ChannelRegistry` directly:** the registry
no longer decides where speech goes. It still remembers where the last
message a channel delivered came in, which the framework uses only for a
turn whose wake names no conversation. `getDefaultPublishChannel()`
returns its channel id, and `getLastInbound()` its server, channel and
thread. A reaction or a withdrawal doesn't count as a message.
- **Removed:** the `activeChannelResolver` option. The new
`speechRouteResolver(agentName)` option replaces it, and the framework
supplies it.
- **Changed:** `resolveLocus(conversationId)`, which the framework calls
with an agent's name, returns only a conversation fork's home channel,
or null. `getChannelServerId(id)` names a server only when
exactly one registered the id, and returns null for an id several
servers share, where it used to return the first registrant. `buildChannelContext(agent)` advertises the
agent's own route and reply edge, and nothing for a held, surface-only or
absent route.
- **Migration:** read a route from the framework's turn rather than the
registry, and pass `channelId` explicitly when publishing outside a turn.
`routeSpeech`/`deliverSpeech` also accept `{ serverId, channelId }` to
publish to an exact server.
- **Also changed:** `resolveProseTarget` returns the `serverId` its
target resolved to beside the channel id. `startTyping` and
`stopTyping` take `{ serverId, channelId }`. A bare id still works:
`startTyping` resolves it only when one server registers it, and
`stopTyping` stops that id's typing on every server that has it. The registry no longer
decides a coalesced message's trigger before `handleCoalescedIncoming`:
the event carries the filter's inputs as `triggerInputs`, and a host's
own handler decides from them once it admits the occurrence.
- **Unchanged:** fork homes, explicit sends naming a channel, and
`resolveDestination`. No sibling package version changes.
Loading
Loading