Files
simplex-chat/packages/simplex-chat-mcp/spec/flows.md
T
Evgeny @ SimpleX Chat be668564df mcp server: files, /leave and library methods (#7962)
* bots, nodejs: document chat, connection, member contact and network commands, typed commands in library

* bots, nodejs: restore apiConnect, wrap domain once, test all connect responses

* nodejs: JSDoc for new methods

* docs: plan for bot API commands and typed Node library methods

* tests: channel prepared with a domain has a verified SimpleX name

* docs: apply review fixes to the plan for bot API commands

* docs: apply second review pass to the plan for bot API commands

* docs: refer the bot API plan to the rewritten test commit

* docs: record decisions in the plan for bot API commands

* docs: plan tests of the generated Python command strings

* python: test the generated strings of the documented commands

* docs: plan for files, /leave and library methods in the MCP server

* docs: wait at stop in the MCP plan

* docs: library methods and /leave mechanics in the MCP plan

* mcp: typed library methods for chat commands

* docs: plan the message information command for bots

* mcp: /leave CHAT owner command to leave a chat at once

* docs: file servers, GIF and video choices decided in the MCP plan

* docs: file mechanics in the MCP plan

* mcp: files of received messages

* bots, nodejs: message information with the file servers for bots

* docs: plan the relay approval option of receiving files in Node

* mcp: download of received files with owner approval

* docs: name the xftp-server test helper in the files plan

* nodejs: receive files without approving unknown relays

* docs: plan the user servers command for bots

* bots, nodejs: user servers and relays by operator for bots

* nodejs: document the message information method

* docs: plan the file upload progress event for bots

* mcp: send files with owner approval, refusing private keys

* bots: file upload progress event for bots

* mcp: /leave template in the agent control chat menu

* docs: sent copies are deleted for contacts too, at sndFileCompleteXFTP

* bots: drop the file upload progress event

* docs: drop the upload progress event plan

* nodejs: document user servers and relay approval of received files

* mcp: download cards name unknown file servers after the upload

* docs: record the wait for the file description in the files plan

* mcp: fixed wait at stop for cancelled cards, without /sql

* docs: record decision C4, a fixed wait at stop, in the files plan

* ci: test the MCP server package on pull requests

* ci: state when the MCP workflow can be dispatched

* ci: run the MCP server tests when its libraries change

* mcp: log the errors of image previews and resizing

* mcp: keep files in transfer in the data folder

* mcp: restrict sent files by the real paths of refused folders

* mcp: validate the file at the send by its inode and change time

* mcp: limit ffmpeg and ffprobe to local files and no input commands

* mcp: ask the owner for unknown file servers; read approvals at creation

* mcp: name downloaded files by their ID; return paths of downloads only

* mcp: end each download with a result for the agent and the card

* mcp: show files above their sender's size limit as failed

* mcp: refuse empty files, as without file and captions too long to send

* mcp: tell the owner about failed uploads and unposted received files

* mcp: delete resized copies left by the last run at start

* mcp: name downloads and leaving in the help and greeting, align docs

* mcp: compute the download path for received files only

* mcp: send a /receive file to the owner under its name

* mcp: keep files received without a download in the chat's folder

* mcp: keep a left chat's files and delete them with /delete CHAT files

* mcp: test that sent images are resized by the core

* mcp: wait 30 minutes for a download's progress before cancelling it

* mcp: end a download as deleted only when its message is not found

* mcp: delete the copy of a /receive file whose post fails

* mcp: count the quoted content in a message's length, as the core sends it

* mcp: name received bytes, unknown-server downloads and left chat files plainly

* mcp: test refused folders, file changes and download approvals

* docs: align MCP server docs with refused folders, leaves and download waits

* mcp: move received files only in full, delete partials only of downloads

* mcp: report a received file missing from its folder as failed

* mcp: move received files of a group's member chats into its folder

* mcp: read a video's preview and duration as an MP4 or QuickTime file

* mcp: 0.2.0 on simplex-chat 7.1.0-beta.10.1

* mcp: file folders by chat type, /delete cancels downloads, files reply

* docs: MCP server specs and plan for 0.2.0

* mcp: /receive gives the agent a file, /forward sends it to the owner

* docs: MCP server specs for /receive and /forward of download cards

* docs: MCP plan records the temp folder command as a library exception

* mcp: /delete cancels resumed downloads; /forward quote fits the limit

* docs: MCP specs and plans for /forward quotes and resumed downloads

* docs: MCP specs name the source commit they are checked against

* docs: MCP PLAN states the /forward quote and move rules

* mcp: a download moves its completed file whatever its size

* mcp: /receive N all covers its card and later files, not pending ones

* docs: MCP files approval scope, reply text and README server wording

* mcp: a download approves relays only for a card naming unknown servers

* mcp: files in member chats of a group are tracked as in the main chat

* docs: MCP relay approval of downloads and files in member chats

* mcp: a rejected file gets a card again under the files approval

* docs: MCP rejected files under the files approval, README scope

* mcp: an offered file without a description gets no card or action

* docs: MCP a file without a description is not downloaded

* mcp: 0.2.0 on simplex-chat 7.1.0-beta.10.2

* ci: test the MCP server as a scope of build-linux
2026-11-06 18:38:34 +00:00

37 KiB
Raw Blame History

SimpleX MCP server: flows

Sequence diagrams of the main flows, each with notes and source locations.

Related specs: README | Architecture | API

Participants:

Letter Participant
P the person at the terminal
A the agent in Claude Code
H the HTTP listener with the MCP SDK
T AgentTools
X Actions
K Control
C the SimpleX core behind ChatApi
O the owner in the SimpleX app
D a peer

#G is the agent control chat, USER the agent's user ID, DATA the data folder. Calls to the core are shown as the ChatApi methods the server calls, with their arguments in short. Card and progress texts with a semicolon are shown with a comma in the diagrams; the exact texts are in API.


Table of contents

  1. First start and the owner joins
  2. register
  3. connect with approval
  4. send with responses
  5. receive with wait
  6. send under a standing approval, and /revoke
  7. leave
  8. Mode host
  9. Expiry
  10. A lost call and its re-attachment
  11. Shutdown with pending actions
  12. --owners setup
  13. download with approval
  14. /forward
  15. send with a file
  16. /leave and /delete CHAT files

1. First start and the owner joins

sequenceDiagram
  participant P as Person
  participant S as serve.ts
  participant C as Core
  participant K as Control
  participant O as Owner
  P->>S: simplex-mcp serve my-agent --display-name "My Agent"
  S->>S: DATA mode 0700, lock, token, headers.json
  S->>C: ChatApi.init DATA/simplex
  S->>P: version line, data folder, approval mode
  S->>C: apiGetActiveUser (none)
  S->>C: apiCreateActiveUser {displayName: "My Agent", fullName: "", peerType: agent}
  S->>C: apiSetNetworkConfig smpProxyMode always, smpProxyFallback allowProtected
  S->>C: /_temp_folder DATA/tmp
  S->>S: DATA/files/sent deleted
  S->>C: startChat, events buffered
  S->>C: apiListGroups USER (no control marker)
  S->>C: apiNewGroup USER {My Agent, history on, menu pending, list and leave}
  S->>C: apiSetGroupCustomData #G {"simplexMcp":{"control":true,"nextAction":1}}
  S->>P: Agent control chat "My Agent" created.
  S->>K: load
  K->>C: apiListMembers #G
  S->>S: HTTP listens on 127.0.0.1:5226, /mcp answers 503 until the control chat is started
  S->>S: server.json
  K->>C: apiDeleteGroupLink #G (groupLinkNotFound ignored)
  K->>C: apiCreateGroupLink #G member
  K->>P: join line, QR code, link, QR hint, owner line
  S->>P: Listening on http://127.0.0.1:5226/mcp
  O->>C: joins by the link
  C->>S: joinedGroupMember
  S->>K: onEvent, queued
  K->>C: apiListMembers #G
  K->>C: apiDeleteGroupLink #G
  K->>C: apiSetMembersRole #G [3] owner
  K->>C: apiListMembers #G
  K->>O: I'm your agent. I ask here before I connect, send, download a file or leave a chat.
  K->>P: Alice joined (member 3) and is now an owner. The link is deleted.
  K->>P: Add the agent to Claude Code: npx -y @simplex-chat/mcp register my-agent

Notes:

  • At first use, libsimplex and the Node.js add-on are downloaded, with one Downloading ... line each on stderr (libs.ts:114-135).
  • With --data, --data <folder> is appended to the register hint, shell-quoted (serve.ts:185-187).
  • With --qr-code small, the line If the QR code does not scan, ... is printed after the link (control.ts:412).
  • Events that arrive before the HTTP listener starts are buffered and handled in order (serve.ts:234-248).
  • The server runs ownerJoined for every joinedGroupMember event of a human without --owners, and the link is deleted in that handler (control.ts:189-190, :371-378).

Source: cli.ts:198-209, serve.ts:65-126, :205-223, files.ts:12-48, http.ts:14-46, control.ts:102-117, :341-345, :406-425, :499-520.


2. register

sequenceDiagram
  participant P as Person
  participant R as register.ts
  participant L as claude CLI
  participant CC as Claude Code
  participant H as HTTP
  P->>R: simplex-mcp register my-agent [--scope local] [--data DIR]
  R->>R: read DATA/server.json
  alt server.json missing
    R->>P: Agent "my-agent" was never started. Start it with: ... (exit 1)
  end
  R->>L: claude mcp remove --scope user simplex-my-agent
  R->>L: claude mcp add-json --scope user simplex-my-agent {"type":"http","url":"http://127.0.0.1:5226/mcp","headersHelper":"cat DATA/headers.json","timeout":169800000}
  alt claude not found
    R->>P: Add this entry under "mcpServers" in ~/.claude.json: {...} (exit 1)
  else add-json exit status other than 0
    R->>P: claude mcp add-json failed: STDERR (exit 1)
  end
  R->>P: Added to Claude Code: scope user, server simplex-my-agent.
  CC->>CC: headersHelper: cat DATA/headers.json
  CC->>H: POST /mcp with Authorization: Bearer TOKEN

Notes:

  • simplex-mcp ignores the exit status of claude mcp remove and stops at that step only when the claude binary is missing (register.ts:38-39).
  • On Windows, a missing claude is run again through cmd.exe, with each argument quoted for it (register.ts:86-101).
  • timeout is (expireHours + 24) × 3600000 + 600000 ms from server.json (register.ts:51).
  • With --scope local, the project folder in the manual entry is the git repository root, the main repository of a worktree, or the current folder outside a repository (register.ts:56, :60-84).

Source: cli.ts:110-125, :207, register.ts:33-58, files.ts:50-53.


3. connect with approval

sequenceDiagram
  participant A as Agent
  participant H as HTTP and SDK
  participant T as AgentTools
  participant X as Actions
  participant K as Control
  participant C as Core
  participant O as Owner
  participant D as Peer
  A->>H: tools/call connect {target: "@directory.simplex", wait, idle}
  H->>T: validated input, abort signal, progress token
  T->>X: find(key): none
  T->>C: apiConnectPlan USER @directory.simplex
  C-->>T: contactAddress ok, planSimplexName
  T->>T: classifyPlan: prepareContact with domain
  T->>X: create(key, connect fields)
  X->>K: refusal: none
  X->>K: saveCounter: apiSetGroupCustomData #G
  X->>K: postCard
  K->>O: I want to connect to bot X (@directory.simplex), as myself. /'approve N' or /'reject N'
  T->>X: decided(action, call)
  X-->>A: progress: Action N waits for an approver until TIME.
  O->>C: /approve N
  C->>K: newChatItems
  K->>X: approve(action, "Alice")
  X->>T: performConnect
  T->>C: apiConnectPlan USER @directory.simplex
  T->>C: apiPrepareContact USER LINK linkData directory.simplex
  T->>C: apiConnectPreparedContact @C incognito false
  C-->>T: startedConnectionToContact
  D->>C: accepts the request
  C->>T: contactConnected: markConnected, collect @C
  T-->>X: perform ends
  X->>K: outcome
  K->>O: card edited: I connected to bot X (@directory.simplex), as myself. Approved by Alice.
  T->>C: apiGetChat @C count 50
  T->>T: wait until idle or startedAt + wait
  T->>C: apiChatItemsRead @C, new item IDs
  T->>X: remove(action)
  T-->>A: {action: N, status: done, chat: @C, connected: true, messages}

Notes:

Plan Methods
known prepared contact or group apiConnectPreparedContact(C, incognito, message)
apiConnectPreparedGroup(G, incognito, message)
channel link (direct false, with link data) apiPrepareGroup(USER, LINK, false, linkData, domain)
apiConnectPreparedGroup(G, incognito)
contact address with link data, and a SimpleX name or a message apiPrepareContact(USER, LINK, linkData, domain)
apiConnectPreparedContact(C, incognito, message), or apiConnectPreparedGroup for a business
contactViaAddress apiConnectContactViaAddress(USER, incognito, CONTACT_ID)
other links apiConnectPreparedLink(USER, incognito, LINK)
  • After a failed prepared connect, the server deletes the prepared chat without notifying (tools.ts:749-756).
  • The connection is matched by contactConnected or userJoinedGroup: by contact or group ID, or by the pending connection ID (tools.ts:590-598, :758-763).
  • In the perform step the server waits for the other side until startedAt + wait, at most until created + 23 h 50 min, and then reports connected: false (tools.ts:143, :689, :713-716).
  • Without message, the idle time is counted from the connection; with message, from the first reply (tools.ts:769, messages.ts:323-331).
  • For a target the agent is connected to, the result is status: known with its chat fields, and no action is created (tools.ts:622).
  • Exact progress texts: Action N waits for an approver until TIME., then Action N waits for the connection until TIME., then K messages in CHAT; waiting until TIME. (actions.ts:343-354, tools.ts:658).

Source: tools.ts:617-672, :686-772, actions.ts:155-186, :206-223, control.ts:141-152.


4. send with responses

sequenceDiagram
  participant A as Agent
  participant T as AgentTools
  participant X as Actions
  participant K as Control
  participant C as Core
  participant O as Owner
  participant D as Peer
  A->>T: send {chat: #15, text, reply_to, mention: [44], wait, idle, limit}
  T->>C: apiGetChat #15 count 0
  T->>C: apiGetChat #15 around REPLY_TO count 0
  T->>C: apiListMembers #15
  T->>C: apiGetChat #15 count 0
  T->>K: standingApprover(chatInfo, messages): none
  T->>X: create(key, send fields)
  X->>K: card size check and approvers
  K->>O: I want to send a message to group X: `text` replying to ... mentioning @Switchback /'approve #15 N', /'approve #15 all' or /'reject #15 N'
  O->>C: /approve #15 N
  K->>X: approve(action, "Alice")
  X->>T: performSend
  T->>T: collect #15 items with ID above the sent item
  T->>C: apiSendMessages #15 [{msgContent, mentions, quotedItemId}]
  C-->>T: sent item S
  D->>C: replies S+1, S+2
  C->>T: newChatItems: collected
  X->>K: outcome
  K->>O: card edited: I sent a message to group X: `text` ... Approved by Alice.
  T->>T: wait until idle seconds after the last reply, or sentAt + wait
  T->>C: apiChatItemsRead #15 [S+1, S+2]
  T->>X: remove(action)
  T-->>A: {action: N, status: done, chat: #15, item: S, responses, more: 0}

Notes:

  • Checks before the action: text or file, the chat connected, role observer or relay refused, reply_to item a message, mentioned members present and the chat a group, and the message length with its quote (tools.ts:779-792).
  • The server starts collecting before the send and, after it, drops items with IDs up to the sent item (tools.ts:825, :838-840).
  • Mentioned names missing from the text are prefixed as @name (tools.ts:1424-1427).
  • While a collected message is live, the server waits for its final update; live items are left out of responses and counted in more (messages.ts:323-331, tools.ts:811, :817).
  • At most 100 replies are kept per action; later ones are counted as overflow (messages.ts:10, :109-110).

Source: tools.ts:774-846, messages.ts:99-114, :323-331, control.ts:583-589.


5. receive with wait

sequenceDiagram
  participant A as Agent
  participant T as AgentTools
  participant W as ChatItemsWatch
  participant C as Core
  participant D as Peer
  A->>T: receive {chat: #12, wait: 3600, limit: 50}
  T->>W: collect #12 into arrived
  T->>C: apiGetChat #12 initial 1
  C-->>T: chatStats.unreadCount 0
  T-->>A: progress: No new messages in #12, waiting until TIME.
  D->>C: post, item 40
  C->>W: newChatItems: arrived
  T->>C: apiChatItemsRead #12 [40]
  T->>W: stop collecting
  T-->>A: {chat: #12, messages: [40], more: 0}

Notes:

  • With unread items the server reads around the first unread item, count 0, then pages of after the last item, count 100, until it holds limit + 1 unread, non-live messages or a page is short (messages.ts:285-299).
  • Unread items other than messages in the read pages are marked read with the returned messages (tools.ts:1125, :1142).
  • more: the arrived messages not returned, the overflow and the unread items not read (tools.ts:1144).
  • Items received while the pages are read are returned once (tools.ts:1124-1127).
  • Exact progress text: No new messages in CHAT; waiting until TIME. (tools.ts:1131).

Source: tools.ts:1114-1148, messages.ts:285-331.


6. send under a standing approval, and /revoke

sequenceDiagram
  participant A as Agent
  participant T as AgentTools
  participant X as Actions
  participant K as Control
  participant C as Core
  participant O as Owner
  O->>C: /approve #9 all (on a pending send card)
  C->>K: newChatItems
  K->>C: apiGetChat #9 count 0
  K->>C: apiSetGroupCustomData #9 {"simplexMcp":{"approvedBy":"Alice","approvedById":3,"approvedAt":"..."}}
  K->>X: approve each pending send to #9 with a card
  K->>O: Approved for business X (#9) by Alice. /'revoke #9'
  A->>T: send {chat: #9, text}
  T->>C: apiGetChat #9 count 0
  T->>K: standingApprover: Alice
  T->>X: create(key, fields, "Alice"): performing, standing
  X->>K: saveCounter (no card)
  X->>T: performSend
  T->>C: apiSendMessages #9 [...]
  X->>K: outcome
  K->>O: I sent a message to business X: `text` Approved for this chat by Alice. /'revoke #9'
  T-->>A: {action, status: done, chat: #9, item, responses, more}
  O->>C: /revoke #9
  K->>C: apiGetChat #9 count 0
  K->>C: apiSetGroupCustomData #9 {other keys}
  K->>O: Messages to business X (#9) need approval again.

Notes:

  • The approval is used in mode requests while member approvedById is an approver (control.ts:130-133).
  • For sends that were pending at /approve CHAT all, the card ends with the plain Approved by Alice. (control.ts:554, :563).
  • connect and leave actions are created without the standing approver, so they stay pending (tools.ts:628, :942).
  • /receive CHAT N all and /receive CHAT off work the same way for downloads, with the key simplexMcpFiles, except that download N alone is performed, and the other pending downloads of the chat stay pending (control.ts:22-25, :251-253, tools.ts:975).

Source: control.ts:243-263, :337-339, tools.ts:796-798, actions.ts:155-186, :265-283.


7. leave

sequenceDiagram
  participant A as Agent
  participant T as AgentTools
  participant X as Actions
  participant K as Control
  participant C as Core
  participant O as Owner
  A->>T: leave {chat: #9}
  T->>X: find(key): none
  T->>C: apiGetChat #9 count 0
  T->>X: create(key, leave fields)
  K->>O: I want to leave business X (#9). /'approve #9 N' or /'reject #9 N'
  O->>C: /approve #9 N
  K->>X: approve(action, "Alice")
  X->>T: leaveChat(#9)
  T->>C: apiGetChat #9 count 0
  T->>C: apiLeaveGroup #9 (membership current or pending)
  T->>C: apiDeleteChat #9 full, notify
  T->>T: DATA/forwarded/group-9 deleted
  X->>K: outcome
  K->>O: card edited: I left business X (#9). Approved by Alice.
  K->>O: Files from business X (#9) are kept in `DATA/files/group-9`: K files, SIZE. /'delete #9 files' (when the folder has files)
  T-->>A: {action: N, status: done, chat: #9}

Notes:

  • For a contact, the card text is I want to delete KIND NAME (@N)., and the perform step calls apiDeleteChat for the contact, full, with notify (control.ts:590-593, tools.ts:951).
  • The delete notifies, so the core notifies the contact or the members (tools.ts:951, :954).
  • The server looks for an attached action before it reads the chat, so a repeated call after the deletion is attached to the done action (tools.ts:937-940).

Source: tools.ts:935-958, control.ts:145-152, :299-302.


8. Mode host

sequenceDiagram
  participant P as Person
  participant A as Agent
  participant H as HTTP and SDK
  participant T as AgentTools
  participant X as Actions
  participant K as Control
  participant O as Owner
  Note over P,H: the person started serve my-agent --approve host
  A->>H: tools/list
  H-->>A: connect, send, download, leave with _meta {"anthropic/requiresUserInteraction": true}
  A->>A: Claude Code asks its user
  A->>T: send {chat: @4, text}
  T->>X: create: performing
  X->>K: saveCounter (no card)
  X->>T: performSend
  X->>P: Action N: performed without approval (mode host).
  X->>K: outcome
  K->>O: I sent a message to bot X: `text` Performed without approval (mode host).
  T-->>A: {action: N, status: done, ...}

Notes:

  • New actions are still refused unless the control chat has an approver (control.ts:124-127).
  • In mode host, standingApprover returns undefined, so standing approvals are ignored by send, download, list and /list, and /approve CHAT all stores nothing; downloads get no card, so /receive CHAT N all has no card to act on (control.ts:132, :213).
  • A download in mode host is received without the approval of unknown servers, and it gets no card and no outcome post (tools.ts:545, :1030, control.ts:495-497).
  • The kept-files message also follows a done leave in mode host (control.ts:150-151).

Source: tools.ts:488-498, actions.ts:166, :281, control.ts:145-152, :554.


9. Expiry

sequenceDiagram
  participant A as Agent
  participant X as Actions
  participant K as Control
  participant O as Owner
  A->>X: send call creates pending action N at T
  K->>O: card N
  Note over X: expires = T + expireHours
  loop every 60 s, and before each owner command
    X->>X: expireDue
  end
  X->>X: pending and due: expired
  X->>K: outcome
  K->>O: card edited: ... Expired.
  X-->>A: {action: N, status: expired}
  X->>X: removed with the result
  O->>K: /approve N
  K->>O: Action N is not pending.

Notes:

  • A decided action with an open count of 0 is removed at the first tick after its expiry time (actions.ts:200-201).
  • Expiry times are listed in the /pending reply (control.ts:618-620).
  • The server ends the connect wait at created + 23 h 50 min, within the 24 hours in which a message can be edited (tools.ts:143, :689).

Source: actions.ts:129, :163, :193-204, control.ts:201, :227.


10. A lost call and its re-attachment

sequenceDiagram
  participant A as Agent
  participant H as HTTP and SDK
  participant T as AgentTools
  participant X as Actions
  participant O as Owner
  A->>H: send {chat: @4, text: "t"}
  H->>T: call
  T->>X: create: pending, open 1
  A->>H: closes the response stream
  H->>T: ctx.mcpReq.signal aborted
  T->>X: decided: false, open 0
  Note over H: result discarded by the SDK
  A->>T: list
  T-->>A: actions: [{action: N, tool: send, input, state: pending, open: false}]
  O->>X: /approve N
  X->>X: perform, done, replies collected
  A->>T: send {chat: @4, text: "t"}
  T->>X: find(key): action N
  T->>T: doneAtCall: wait counted from now
  T->>X: remove(action)
  T-->>A: {action: N, status: done, item, responses, more}

Notes:

Source: tools.ts:636-648, :802-820, :1156, actions.ts:206-217, messages.ts:307-321.


11. Shutdown with pending actions

sequenceDiagram
  participant P as Person
  participant S as serve.ts
  participant X as Actions
  participant K as Control
  participant H as HTTP
  participant C as Core
  participant O as Owner
  P->>S: Ctrl-C (SIGINT)
  S->>S: stopped: events ignored
  par actions
    S->>X: stop()
    X->>X: stopping, timer cleared, cards being posted awaited
    X->>X: pending and performing: interrupted, performs aborted
    X->>K: outcome of each
    K->>O: pending: ... Cancelled: the server stopped.
    K->>O: performing: ... Approved by Alice, interrupted: the server stopped.
  and HTTP
    S->>H: close, closeAllConnections (open calls aborted)
  end
  S->>P: Cancelled 2 actions.
  S->>P: Stopping: sending 2 cancelled cards, up to 10 s. Ctrl-C to stop now.
  S->>S: wait 10 s, or until a second Ctrl-C
  S->>C: close
  S->>S: remove DATA/lock
  S->>P: Stopped.
  Note over P,O: next start: counter continues, /approve of an old number: Action N is not pending.

Notes:

  • The interrupted card line is DECIDER; interrupted: the server stopped. (control.ts:561).
  • An action of mode host or of a standing approval whose counter is being written at the stop is set to pending and cancelled without a perform (actions.ts:265-270).
  • After a crash, the cards of that run are left with their commands; /approve of their numbers gets the reply Action N is not pending. (control.ts:227).
  • The stop signal of the performs is aborted after the actions are marked interrupted: a connect waiting for its connection ends, and an interrupted perform logs no line (actions.ts:245-246, :280, tools.ts:713-714).
  • The wait applies only when cards were edited or posted at the stop, a download without a card excluded, and --stop-wait is above 0 (serve.ts:177, :189-203).

Source: serve.ts:47-63, :171-181, :189-203, actions.ts:240-250, http.ts:48-52, control.ts:552-571.


12. --owners setup

sequenceDiagram
  participant P as Person
  participant K as Control
  participant C as Core
  participant O as Owner Alice
  participant B as Member Bob
  P->>K: serve my-agent --owners
  K->>C: apiCreateGroupLink #G observer
  K->>P: The link adds an observer. Its ID and name are printed when it joins.
  O->>C: joins by the link
  C->>K: joinedGroupMember
  K->>C: apiDeleteGroupLink #G
  K->>P: Alice joined (member 3). Restart the server with --owners 3:Alice
  K->>O: Alice joined (member 3). Restart the server with `--owners 3:Alice`
  P->>K: Ctrl-C, then serve my-agent --owners 3:Alice
  K->>C: apiSetMembersRole #G [3] owner
  K->>C: apiRemoveMembers #G [ID], for each other human
  K->>C: apiDeleteGroupLink #G
  K->>P: Owners: 3:Alice.
  K->>P: Add the agent to Claude Code: ...
  O->>C: adds Bob, makes Bob an owner
  C->>K: memberRole
  K->>P: Bob (member 7) is now an owner and can't approve actions yet. Restart ... --owners 3:Alice,7:Bob
  K->>O: same line, the --owners part as code
  B->>K: /approve N
  K->>B: Only the owners listed in --owners approve actions.

Notes:

  • Until the restart with a list, new actions are refused: The agent has no approving owner. ... (control.ts:125-127).
  • An entry must match a human by member ID and display name, folded to one line as the restart line prints it, or the server exits with code 2 (control.ts:354-358, :758-760).
  • With --reset-owners, the server deletes the control chat for all members and creates a new one with the same counter; the CLI refuses the option with an --owners list (control.ts:522-527, serve.ts:222, cli.ts:104-106).
  • When a listed owner leaves and none remains: ... is no longer an owner. No owners remain: restart the server with --reset-owners --owners (control.ts:393-398).

Source: control.ts:347-369, :380-399, :406-416, :758-784.


13. download with approval

sequenceDiagram
  participant A as Agent
  participant T as AgentTools
  participant X as Actions
  participant K as Control
  participant C as Core
  participant O as Owner
  A->>T: download {chat: #15, item: 30}
  T->>X: find(key): none
  T->>C: apiGetChat #15 count 0
  T->>C: apiGetChat #15 around 30 count 0
  T->>C: apiGetChatItemInfo #15 30: fileXftpServers
  opt no servers yet
    T-->>A: progress: The sender is uploading the file of item 30 in #15, waiting until TIME.
    C->>T: rcvFileDescrReady
    T->>C: apiGetChatItemInfo #15 30
  end
  T->>C: apiGetUserServers USER: no unknown servers
  T->>C: apiGetChat #15 count 0
  T->>K: standingApprover(chatInfo, files): none
  T->>X: create(key, download fields)
  K->>O: I want to download a file from Switchback in group Trail (#15): `report.pdf`, 2.4 MB, Servers: known. /'receive #15 N', /'receive #15 N all', /'forward #15 N' or /'reject #15 N'
  O->>C: /receive #15 N
  K->>X: approve(action, "Alice")
  X->>T: performDownload: downloadFile
  T->>C: apiGetChat #15 around 30 count 0
  T->>C: apiReceiveFile 7 DATA/files/group-15/7.pdf
  C->>T: rcvFileComplete
  T->>T: a file at the download path: resolved
  X->>K: outcome
  K->>O: card edited: I downloaded a file from ... Approved by Alice.
  T-->>A: {action: N, status: done, chat: #15, item: 30, path}

Notes:

  • A check every 60 s reads the item: a deleted message or chat fails the download; 30 minutes without a rise of rcvProgress fail and cancel it (tools.ts:1035-1062).
  • A download still running wait seconds after the perform starts returns status: performing; the same call again attaches to the action (tools.ts:1008-1011).
  • /approve #15 N on a download card gets the reply Action N is a download: ... with the card's command line (control.ts:234).
  • A file on unknown servers gets a card that names their hosts and leaves out /'receive CHAT N all' (control.ts:594-599, :611-614, :664-668). Only such a card approves the relays; the core checks the complete description of every other download, which fails at the acceptance, or at the next check when the description completes later (tools.ts:545, :1043).
  • The card of an image or video shows the sender's preview (control.ts:441-461).
  • A file the core receives without a request, a short voice message, also in a member chat of a group, is moved into the chat's folder at rcvFileComplete; one whose transfer started before a restart, only when it has its full size. Its message then shows it as offered, and download returns its path after the approval (tools.ts:551-571, :1082-1091, messages.ts:194-208).

Source: tools.ts:960-1103, control.ts:573-616, serve.ts:139-155.


14. /forward

sequenceDiagram
  participant A as Agent
  participant T as AgentTools
  participant K as Control
  participant C as Core
  participant O as Owner
  O->>C: /forward #15 N (on a pending download card)
  C->>K: newChatItems
  K->>T: forwardFile(action N)
  T->>C: apiReceiveFile 7 DATA/forwarded/group-15/7.pdf
  C->>T: rcvFileComplete: resolved with the forwarded path
  T->>T: copy for the post: DATA/files/sent/send-XXXXXX/report.pdf
  K->>C: apiSendMessages #G [{fileSource: the copy, msgContent: file, quotedItemId: card N when the message fits}]
  C->>T: sndFileCompleteXFTP: the copy's folder deleted
  Note over K,O: the card stays pending; the agent's message shows the file as offered
  O->>C: /receive #15 N
  K->>T: performDownload: downloadFile
  T->>T: the forwarded file moved to DATA/files/group-15/7.pdf
  T-->>A: {action: N, status: done, chat: #15, item: 30, path}

Notes:

  • /forward gets no reply; a download or post that fails is posted as Action N: the file was not received: `ERROR`., and a failed post deletes the copy (control.ts:265-281).
  • The copy is named by the name the sender gave (tools.ts:898-902).
  • The post keeps the sender's preview of an image or video; it replies to the card when the message, with the card as its quote, is within 15602 bytes by the length rule of send (control.ts:265-281, tools.ts:1433-1449).
  • The download is shared with the agent's: one download per file ID; the move to the download path waits for the copy of the post (tools.ts:522-549).
  • The forwarded path is outside DATA/files/, so send refuses the file there; it is deleted when the agent leaves the chat, and by /delete CHAT files (tools.ts:871-876, :909-927, :950-958).

Source: control.ts:221-239, :265-281, tools.ts:522-539, :848-858, :898-902.


15. send with a file

sequenceDiagram
  participant A as Agent
  participant T as AgentTools
  participant X as Actions
  participant K as Control
  participant C as Core
  participant O as Owner
  A->>T: send {chat: #15, text: "trail map", file: "/home/alice/trail.jpg"}
  T->>C: apiGetChat #15 count 0
  T->>T: real path, size, inode, change time
  T->>T: refused paths checked
  T->>T: preview: core.resizeImage(file, MessagePreview)
  T->>T: message length checked
  T->>X: create(key, send fields with the file)
  K->>O: image card with the preview: I want to send a message to group Trail: `trail map`, attaching image `/home/alice/trail.jpg`, SIZE /'approve #15 N', ...
  O->>C: /approve #15 N
  X->>T: performSend
  T->>T: the file again: same real path, size, inode, change time
  T->>T: core.resizeImage(file, Image): DATA/files/sent/send-XXXXXX/trail.jpg
  T->>C: apiSendMessages #15 [{fileSource: the copy, msgContent: image with the preview}]
  C->>T: sndFileCompleteXFTP: the copy's folder deleted

Notes:

  • A refused file ends the call with an error, and the refusal is posted to the control chat and logged (tools.ts:862-865, control.ts:154-158).
  • A video's preview is the first frame from ffmpeg and its duration comes from ffprobe; a GIF or WebP gets the fixed preview (tools.ts:1512-1546).
  • A changed file fails the perform: file: PATH changed after the request. (tools.ts:878-883).
  • sndFileError deletes the copy too, and the failure is posted to the control chat and logged (serve.ts:158-160, control.ts:160-169).

Source: tools.ts:774-800, :822-896, control.ts:441-461, :583-589.


16. /leave and /delete CHAT files

sequenceDiagram
  participant P as Person
  participant T as AgentTools
  participant X as Actions
  participant K as Control
  participant C as Core
  participant O as Owner
  participant D as Peer
  O->>C: /leave #9
  K->>T: knownChat #9
  K->>T: leaveChat #9
  T->>C: apiLeaveGroup #9, apiDeleteChat #9 full, notify
  C->>D: the agent left the group
  T->>T: DATA/forwarded/group-9 deleted
  K->>P: Left business Ridgeline (#9), requested by Alice.
  K->>X: reject each pending action of #9 with a card
  K->>O: Left business Ridgeline (#9), requested by Alice.
  K->>O: Files from business Ridgeline (#9) are kept in `DATA/files/group-9`: 3 files, 2.1 MB. /'delete #9 files'
  O->>C: /delete #9 files
  K->>T: deleteChatFiles #9
  T->>T: each download of #9: failed, apiCancelFile, its partial file deleted
  T->>C: apiGetChat #9 count 100, then before the first item, while a page has 100
  T->>C: apiCancelFile for each other file in rcvAccepted, rcvTransfer or rcvWarning
  T->>T: DATA/files/group-9 and DATA/forwarded/group-9 deleted
  K->>O: Deleted 3 files from #9, 2.1 MB.

Notes:

  • /leave acts at once in both modes, and works for a contact as Deleted bot X (@4), requested by Alice. (control.ts:283-297).
  • The line Left ... goes to the log and to the owners; the members of #9 see only that the agent left.
  • The files message is posted after the reply, and only when the folder has files (control.ts:294-295, :309-311).
  • /delete CHAT files works for a current or a left chat; the failed downloads return Your owner deleted the files of this chat., and the reply counts the forwarded files too (tools.ts:909-927).
  • The paged reads find the downloads the core continues after a restart, which have no download in memory; for a left chat they find none (messages.ts:271-279, tools.ts:916-922).

Source: control.ts:283-307, tools.ts:904-927, :950-958.