@lamplitisles/dsh-matrix is a DSH 0.1.2-rc.1 plugin that connects one
Matrix identity and one room to one already-existing DeepSeek Harness Companion
conversation. It never creates a session and never changes the conversation
selected after startup.
- DSH
0.1.2-rc.1with the native web settings surface - Node.js 22 or newer
- A Matrix account that has already joined the allowed room
- The Kepos Speech plugin is optional; voice sends require its Host
keposSpeechservice, while text and workspace-file sends do not. - An access token for that account (password and SSO login are not implemented)
Install the package in the DSH deployment that owns your bundle registry:
npm install @lamplitisles/dsh-matrix@0.1.2-rc.1The package declares the DSH Host/client bundle patch. Restart DSH after installing it, after changing a field, and after revoking/replacing a token.
Open the native plugin settings card and fill in:
- Homeserver URL — the
https://(orhttp://for a deliberately local deployment) base URL, without a Matrix API path. - Matrix user ID — the full ID, such as
@companion:example.org, for the identity represented by the token. - Allowed room ID — one Matrix room ID, such as
!room:example.org. Messages from every other room are ignored and every reply is sent back only to this room. - Companion workspace — selected from the DSH workspace list. The bridge inspects this workspace at startup and locks the most recently human-active eligible existing session (archived, blank, and subagent sessions are excluded). It does not offer a session selector.
- Element access token — a write-only DSH credential. Enter a new value to replace the stored credential; leaving it blank preserves the existing one.
- Respond to all messages — off by default. With it off, a message must
mention the configured Matrix user in
m.mentionsor be a verified reply to a message authored by that user. With it on, every otherwise eligible text message in the room can trigger the bridge.
The card shows only whether the credential is configured, never the token. Readiness is bounded to states such as connecting, bound, unbound, or failed; provider and Matrix errors are not copied into settings or room messages.
In Element, open the account/settings area for the account that will be the bot, find the Help & About (or account security) section, and use the Access Token / Developer token action exposed by that Element build. Copy the token once into the DSH card and save. Element's menus differ between web, desktop, and mobile releases; if the action is not visible, use the account's supported developer/session-token screen rather than entering a password into this plugin. The Matrix identity must already have joined the room; the bridge does not invite or auto-join it.
To revoke a token, use Element's account/session management to sign out or revoke that session (or revoke all sessions if that is the only control), then replace or clear the DSH credential and restart DSH. A revoked token cannot be recovered by the plugin.
After startup, the plugin performs one workspace/session selection and opens one
matrix-js-sdk@42.3.0 client. It begins processing only after initial sync is
prepared. It accepts new m.room.message text events from the allowed room and
ignores history, pagination, notices, the bot's own messages,
edits/replacements, threads, empty text, and duplicate event IDs. Reply
fallback markup and the bot mention are removed before the prompt is sent.
Element reply envelopes may include format/formatted_body; the bridge
verifies the referenced event author and uses only the plaintext body.
Formatted HTML is never rendered or treated as prompt content.
Every eligible external text message is captured in timeline order in a
bounded, in-memory room context buffer. In mention-only mode an ordinary
message adds context but does not open a turn. A mention, verified reply, or
literal occurrence of the identity's current non-empty room display label (for
example, 汐) drains the FIFO buffer atomically (the triggering message
included) into one Matrix-initiated turn; Respond to all messages makes each
eligible message a trigger. The label is read from local room member state and
matched as a literal substring, with no profile lookup. Messages that arrive
while a turn is pending stay in the next buffer.
The resulting ordinary DSH user message contains a deterministic plugin-authored envelope. Each record uses the sender's current room display label as its primary speaker label alongside the stable Matrix user ID and event ID; missing labels fall back to the ID. The envelope identifies the trigger and marks room records as untrusted data. Routing metadata stays bridge-owned rather than plugin-sourcing the composite, so an enabled Hindsight companion-memory plugin applies its normal user-turn recall and retention to the Matrix transcript and final answer; Hindsight's plugin-origin recall injection remains outside that path. The transcript is durable DSH/model input, so the selected room is a deliberate privacy boundary.
Admitted prompts are serialized because they share one locked Agent. A turn's final Assistant text always remains in DSH: it never itself sends a Matrix event. The Companion must explicitly call the Matrix send tool to deliver a message. Its optional reply target may be any event that Matrix verifies in the configured room's server history, including one returned by the recent message tool. Stable Matrix-use policy is installed once as an active-Companion-scoped system prompt; each turn injects only the changing room data and trigger identity.
The locked Companion also receives exactly four native tools in its scoped conversation:
matrix_list_memberstakes no arguments and returns a bounded (at most 128 entries and 16,000 rendered characters), sorted list of{ userId, displayName }entries for current joined members of the configured room only.displayNameis the current local room label (which the Matrix SDK may disambiguate) and falls back touserIdwhen unavailable. It intentionally omits avatars, presence, power levels, membership history, and every other room.matrix_read_recent_messagestakes a required integerlastfrom 1 to 50 and reads up to that many latest ordinary text events from the configured room through Matrix history, rather than relying on local timeline state after a restart. It returns eligible records in chronological order, includes prior bot-authored text for conversational continuity, and excludes notices, edits, threads, media, and formatted HTML. The result is bounded and remains untrusted room data. Calling it does not create a Companion turn.matrix_send_messagetakes one non-empty plain-textbodyof at most 16,000 characters, optionalvoiceboolean, optionalreplyToEventId, and optionalmentionsarray. Withvoiceomitted or false it emits onem.textevent. Withvoice: true,bodyis passed to the optionalctx.get("keposSpeech")service as{ sessionId, text }; the service must return{ mediaType: "audio/mpeg", data: Uint8Array }. The bytes are uploaded first and exactly onem.audioevent named语音消息.mp3is sent; no transcript or text fallback is emitted. If Kepos Speech is not mounted, returns invalid audio, or the upload/send/readiness operation fails, the call fails with a bounded error and sends no fallback event.replyToEventIdmust exactly equal an event Matrix can retrieve from the configured room's server history; omitting it sends an ordinary room message. Each mention must exactly equal one unique current display label in the bounded roster returned bymatrix_list_members; labels are case-sensitive and are not trimmed, normalized, guessed, or looked up remotely. The tool resolves those labels locally to Matrix user IDs and emits one ordinarym.textevent withm.mentions: { user_ids: [...] }. Repeated labels are harmless and produce one ID; omittingmentionsand passing[]are identical no-mention calls. The caller's body is sent unchanged: the tool does not add visible@labeltext, accept Matrix IDs or@room, impersonate, create a thread, or reply to an event outside the configured room. If a label is stale, unknown, or ambiguous (including after a local roster change detected before send), the whole call is rejected before any event is sent. The bounded error includes the requested label and a JSON list of current valid display labels, never roster IDs or raw Matrix errors. The normal DSH tool approval and cancellation pipeline still applies. Tools return a bounded not-ready error before initial sync is PREPARED and after a sync ERROR; other unavailable Matrix connections produce the same bounded failure boundary.matrix_send_filetakes exactly onepathinside the active conversation workspace, optionaldescription, and optionalreplyToEventId. The path is resolved through the live Agent's DSH filesystem service (ctx.get("fs")), checked for canonical containment in the session's workspace root, restricted to a regular file, and read with an 10 MiB bound. PNG, JPEG, WebP, and GIF names becomem.image; every other accepted name becomesm.filewithapplication/octet-stream. The visible Matrixbodyisdescriptionwhen supplied (including an empty string), or the source basename otherwise;filenameis always that basename. The upload precedes one event send, and outside paths, URLs, missing/non-regular files, oversized/unreadable files, cancellation, unavailable services, upload failures, readiness loss, and send failures return bounded errors. There is no room selector, host-path escape, URL input, MIME override, or batch input.
These registrations and the Matrix policy section belong only to the startup-locked Companion and are removed when the bridge stops or ownership is released. A Web/CLI-initiated turn may use the send tool to greet the room or reply to verified room history; its final Assistant text stays in DSH.
If no eligible conversation existed when DSH started, the Matrix client stays connected but the bridge remains unbound until the next restart. It does not emit a room message, because tools are the sole Matrix delivery path. Saving settings never switches the running client or conversation; restart is the explicit boundary.
The access token is resolved only through the package-owned DSH credential reference. It is not part of settings snapshots, readiness RPC responses, provenance, logs, or room replies. The one-room allowlist and one-account client are hard boundaries, not UI suggestions.
This rc.1 release intentionally does not support end-to-end encryption or crypto/device
persistence, formatted HTML rendering (formatted reply HTML is
ignored), threads, reactions, moderation,
invites/auto-join, streaming output or m.replace edits, multiple rooms or
accounts, per-room/session selection, live switching, automatic session
creation, password/SSO login, durable sync tokens, durable deduplication,
transactional outbox, or exactly-once delivery guarantees. The fixed-room
tools do not provide room selection, profile lookup, presence, power levels,
membership history, invitations, moderation, arbitrary Matrix media/HTML,
threads, reactions, or any other Matrix account capability. Media delivery is
limited to the optional Kepos Speech MP3 contract and one bounded workspace file per
call; it does not provide recording, video, albums, retries, progress, or
cleanup of an uploaded-but-unsent object.
- Install the package and restart DSH.
- Select a workspace that already has a normal human prompt, enter the room and account values, write the Element token, and save.
- Restart DSH again and confirm the card reports bound (or the expected bounded missing/unbound state).
- In the allowed room, send two ordinary messages followed by a mention,
verified reply, or the Matrix identity's current room display label. Confirm
the Companion receives the bounded context but Matrix receives nothing until
it explicitly calls
matrix_send_message. Send a notice, edit, thread, and another-room message; confirm none enters context. - Restart DSH, then in the next Companion turn call
matrix_read_recent_messageswith 10 or 20. Confirm it reads bounded chronological ordinary text, including a prior bot message when present. - Ask the locked Companion to call
matrix_list_members; verify only bounded{ userId, displayName }entries from the configured room. Callmatrix_send_messagewith a short greeting and confirm the single plain-text room message and DSH approval prompt. Select an event ID returned bymatrix_read_recent_messagesasreplyToEventId; it must become the Matrix reply relation. Then pass exact roster display labels inmentionsand verify the visible body is unchanged while Matrix receivesm.mentions.user_ids. Invalid anchors or labels must send nothing. In a separate approved turn callmatrix_send_messagewithvoice: true. With Kepos Speech mounted, verify one uploaded语音消息.mp3m.audioevent and nom.texttranscript; with the service absent, verify a bounded error and no Matrix event. Callmatrix_send_filewith an image and a regular report from the active workspace; verify them.image/m.filepayload, MIME mapping, basename/description body, fixed room, reply anchor, and no event for an outside or oversized path. - Stop/reload DSH and confirm the Matrix client, listeners, queued work, and scoped tool registrations stop before a new instance starts. The shared Agent remains under DSH SessionController ownership.
The tag-only workflow in .github/workflows/release.yml verifies the exact
package that it publishes. Before the first automated release, bootstrap a
distinct prerelease and then prepare the first stable release:
-
Create or confirm the public
@lamplitislesnpm scope and manually publish the distinct prerelease@lamplitisles/dsh-matrix@0.1.0-beta.0from a maintainer workstation using local npm authentication. Setversioninpackage.jsonto0.1.0-beta.0, then run the same local checks used by the workflow:bun install --frozen-lockfile bun run typecheck bun run test bun run build GITHUB_REF_NAME=v0.1.0-beta.0 bun run release:check bun run pack-smoke npm publish --access public --tag betaThis bootstrap publication deliberately has no git release tag and is a different npm version from the later stable
0.1.0; do not manually publish or reuse0.1.0for the beta. -
After the beta checks and publication succeed, configure npm Trusted Publishing for
@lamplitisles/dsh-matrixwith GitHub repositoryLamplitIsles/dsh-matrix, workflow.github/workflows/release.yml, and environmentnpm. Create that protected GitHubnpmenvironment with the required release approval policy. -
Push the intended release commit, then create the first OIDC stable tag with the repository-authorized command
og tag v0.1.0(og tag --helpdocuments this as “Create and push a tag”). CI synchronizes the disposable release manifest from that tag before typechecking, testing, packing, and publishing the verified artifact:og push og tag v0.1.0
For each subsequent release, push the intended change and create a new
v<version> tag. The release tag, rather than the committed package.json
version, is the release-version source of truth.
Tags must be v<semver> (for example v0.1.0 or v0.1.0-beta.1). CI
synchronizes its disposable package manifest from the tag before verification.
Stable tags publish with npm's latest dist-tag; prerelease tags publish with
beta. The verify job performs a frozen
Bun install, typecheck, tests, build, release preflight, and disposable DSH
package smoke check before uploading the tarball consumed by the protected
publish job. Publishing uses npm OIDC provenance with id-token: write; no
NPM_TOKEN, NODE_AUTH_TOKEN, or other npm credential is stored in this
repository or required by the workflow. The one-time manual publication uses
the maintainer's local npm authentication only.
For an isolated release check, run the commands above; the packed tarball contains both Host/client entries, declarations, the Cordis patch, this README, license notices, and the inlined client stylesheet.