mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-14 06:57:51 +00:00
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
This commit is contained in:
1 parent
911f8986e5
commit
be668564df
35 files changed
+4930
-960
No files matched your search
@@ -0,0 +1,555 @@
|
||||
# SimpleX MCP server 0.2.0: files, /leave and library methods
|
||||
|
||||
Status: draft for review. Design agreed on 2026-10-22; C1-C3 decided on 2026-10-30; C4 decided on 2026-10-31. Code references: master `d7d180a42`; library references: `ep/bot-api` (#7808).
|
||||
|
||||
Prerequisites, in order:
|
||||
1. #7808 merged: bot API commands documented, the Node library typed (`packages/simplex-chat-nodejs/src/api.ts`).
|
||||
2. `simplex-chat` and `@simplex-chat/types` published with those methods; `package.json` of the MCP server pins them.
|
||||
3. Image processing in the core: merged in #7941.
|
||||
|
||||
Phases:
|
||||
1. Library methods, except the wait at stop, and `/leave CHAT`; `ep/bot-api` at `45c77f6aa` is merged into `ep/mcp-files`.
|
||||
2. Files: received files, `download`, `send` with `file`.
|
||||
3. The wait at stop: a fixed wait, decision 15.
|
||||
|
||||
## Decisions
|
||||
|
||||
1. Library methods only:
|
||||
- the 15 command strings of the server are replaced by typed methods
|
||||
- the wait at stop runs without `/sql`: decision 15
|
||||
- exception, decided 2026-11-03: `/_temp_folder DATA/tmp` stays a command string (`setTempFolder`, `serve.ts:205-209`); the typed `apiSetTempFolder` comes with the next library release, so #7962 changes no library code
|
||||
2. `/leave CHAT` is an owner command in the agent control chat; it acts at once.
|
||||
3. A received file reaches the agent at once, in the message's `file` field, with status `offered`.
|
||||
4. `download` is a new action tool; the owner lets the agent receive each file, or all files of one chat.
|
||||
5. A download card shows the file's own preview and names the file's servers.
|
||||
6. Download card commands, decided 2026-11-03: `/receive CHAT N` performs the download for the agent; `/receive CHAT N all` also lets the agent receive all later files of the chat on known servers, ended by `/receive CHAT off`, and leaves other pending cards pending (decided 2026-11-05); `/forward CHAT N` makes the server download the file and forward it into the agent control chat, the card staying pending and the file kept outside the chat's folder until `/receive`; `/reject CHAT N` as for other actions. `/approve CHAT files` and `/revoke CHAT files` are removed; `/approve` is refused on download cards.
|
||||
7. Downloads go to `DATA/files/<folder>/<file ID><extension>`, one folder per chat, `group-N` for #N and `contact-N` for @N; commands keep the chat ID, as in `/delete #3 files`; the extension of the file's name is kept when it has only letters and digits. The file field has a path only for a file downloaded there. A file the core receives without a request is moved there when it is received in full.
|
||||
8. `send` takes `file`, an absolute path; `text` becomes the caption.
|
||||
9. Images are sent with a core-made preview and a core-resized copy; other files unchanged.
|
||||
10. Paths of private keys and of the server's own secrets are refused; the refusal is posted to the agent control chat.
|
||||
11. C1 (a): the download card names unknown servers before approval, from the item's `fileXftpServers` (`apiGetChatItemInfo`, `Messages.hs:1362`) and the user's XFTP servers (`APIGetUserServers`); both methods are added to #7808.
|
||||
12. C2 (a): a GIF or WebP, which the core does not decode (`Image.hs:62-70`), is sent as an image with a fixed preview, the file unchanged.
|
||||
13. C3 (a): a video's preview and duration come from `ffmpeg` and `ffprobe` when installed, otherwise a fixed preview and duration 0.
|
||||
14. `download` waits for the file description (`rcvFileDescrReady`) before it creates the action, at most `wait` seconds; at the end of `wait` it returns a tool error that asks to call again later. Decided 2026-11-05: without a description, no card and no action, for an offered file of any protocol; a file already received keeps its path. Known servers follow the core's rule (`Library/Internal.hs:898-904`, `Operators.hs:455-465`), compared without passwords.
|
||||
15. C4, for now: a fixed wait at stop, `--stop-wait SECONDS`, 0 to 60, 10 by default, only when cards were edited or posted at the stop; a second Ctrl-C ends it. The `/sql` count of pending deliveries is removed. A later change can post a final message and wait for its "sent" status for each owner.
|
||||
|
||||
## Library methods
|
||||
|
||||
```
|
||||
place now method
|
||||
serve.ts:90 /network socks=off smp-proxy=always … apiGetNetworkConfig, apiSetNetworkConfig
|
||||
serve.ts:170-188 /sql chat, /sql agent removed: a fixed wait at stop, decision 15
|
||||
messages.ts:178-180 /_get chat REF PAGINATION apiGetChat with ChatPagination
|
||||
messages.ts:182-184 /_read chat items REF IDS apiChatItemsRead
|
||||
tools.ts:471 /_connect plan USER TARGET apiConnectPlan, whole response
|
||||
tools.ts:539 /_connect USER incognito=… LINK apiConnect, whole response
|
||||
tools.ts:527 /_prepare group … apiPrepareGroup
|
||||
tools.ts:535 /_prepare contact … apiPrepareContact
|
||||
tools.ts:520-521, :530 /_connect contact @N, /_connect group #N apiConnectPreparedContact, apiConnectPreparedGroup
|
||||
tools.ts:533 /_connect contact USER … CONTACT apiConnectContactViaAddress
|
||||
tools.ts:702 /_get chats USER pcc=off count=N apiGetChats
|
||||
tools.ts:771 /_info @N apiContactInfo
|
||||
```
|
||||
|
||||
Phase 1 changes:
|
||||
```
|
||||
file function change
|
||||
serve.ts startAgent apiGetNetworkConfig, then apiSetNetworkConfig with smpProxyMode always and smpProxyFallback allowProtected
|
||||
messages.ts chatType new: the chat type of a chat reference
|
||||
getChat apiGetChat; pagination a T.ChatPagination or a count
|
||||
markRead apiChatItemsRead
|
||||
chatCommand removed
|
||||
tools.ts plan apiConnectPlan
|
||||
classifyPlan input: the result of apiConnectPlan
|
||||
join apiPrepareGroup
|
||||
apiPrepareContact
|
||||
apiConnectPreparedContact
|
||||
apiConnectPreparedGroup
|
||||
apiConnectContactViaAddress
|
||||
apiConnectPreparedLink: APIConnect, the whole response
|
||||
connectPrepared input: the connect call; the prepared chat is deleted when it fails, as now
|
||||
prepare removed
|
||||
connectResponse removed
|
||||
domainParam removed
|
||||
performSend chatType
|
||||
agentChats apiGetChats with {type: "last", count: 1000000}
|
||||
incognitoName apiContactInfo
|
||||
```
|
||||
|
||||
Changed command strings:
|
||||
```
|
||||
now after
|
||||
/_connect contact @N incognito=off[ json MSG] /_connect contact @N[ json MSG]
|
||||
/_connect group #N incognito=off[ json MSG] /_connect group #N[ json MSG]
|
||||
/_connect contact USER incognito=off N /_connect contact USER N
|
||||
/_connect USER incognito=off LINK /_connect USER LINK
|
||||
/_get chats USER pcc=off count=N /_get chats USER count=N {"type":"filters","favorite":false,"unread":false}
|
||||
/network socks=off smp-proxy=always smp-proxy-fallback=protected /network
|
||||
/_network JSON
|
||||
```
|
||||
- `incognito=off` and `pcc=off` are the parser defaults (`Library/Commands.hs:7110`, `:6750`); the query is `clqNoFilters` (`Controller.hs:1169`), the default at `:6752`.
|
||||
- `/network socks=off …` sets every field of `SimpleNetCfg` except the timeout (`netCfgP`, `Library/Commands.hs:7324-7335`). The library starts the core with the same values (`defaultSimpleNetCfg`, `Controller.hs:1329-1340`; `Mobile.hs:292`; `Chat.hs:274`), except `hostMode`: onionViaSocks in place of public. Without a SOCKS proxy, both select the same hosts (simplexmq `8bc4d3601`, `Client.hs:531-543`, `Agent/Client.hs:1263-1265`).
|
||||
|
||||
Wait at stop, decision 15: `waitForCancelledCards` (`serve.ts`) waits `--stop-wait` seconds after the cards of the stopped actions are edited or posted, and a second signal ends the wait.
|
||||
|
||||
## /leave CHAT
|
||||
|
||||
```
|
||||
command who effect
|
||||
/leave @N approving owner deletes the contact, as the leave tool (tools.ts:646-651)
|
||||
/leave #N approving owner leaves the group and deletes the chat
|
||||
```
|
||||
|
||||
- Pending actions for the chat end as rejected by that owner; their calls return.
|
||||
- Reply: `Left group X (#3), requested by Alice.` or `Deleted contact X (@5), requested by Alice.`
|
||||
- The same line goes to the log.
|
||||
- Both modes.
|
||||
|
||||
Phase 1 changes:
|
||||
```
|
||||
file function change
|
||||
control.ts OwnerRequest {type: "leave", chat}
|
||||
ownerRequest /leave CHAT, read as /revoke CHAT
|
||||
ownerCommand leave: Control.leave
|
||||
leave new
|
||||
chatName showName parameter, safeName by default
|
||||
commandsReply /leave added
|
||||
tools.ts leaveChat performLeave, public, by chat reference; perform calls it
|
||||
chatFiles new: the folder of a chat, its file count and bytes
|
||||
deleteChatFiles new: fails and cancels the chat's downloads, also those the core continues after a restart, then deletes its folder
|
||||
messages.ts chatFolder new: DATA/files/group-N for #N, DATA/files/contact-N for @N
|
||||
actions.ts leaveVerbs input: {chat}
|
||||
```
|
||||
|
||||
`Control.leave(sender, CHAT)`:
|
||||
```
|
||||
CHAT not a chat of the agent: reply "No chat CHAT. /list shows the agent's chats."
|
||||
tools.leaveChat(CHAT); DATA/forwarded/<folder> deleted
|
||||
log the line, names on one line
|
||||
each pending action of CHAT with a card: reject, decidedBy the sender
|
||||
reply the line, names as in cards
|
||||
the folder of CHAT with files: after the reply, post "Files from business Ridgeline (#9) are kept in `FOLDER`: 3 files, 2.1 MB. /'delete #9 files'"
|
||||
```
|
||||
- An error of `leaveChat` is logged to stderr, as the errors of other owner commands; no reply.
|
||||
- `Commands: /approve, /reject, /receive, /forward, /revoke, /leave, /delete, /pending, /list. Each card shows its commands.`
|
||||
- A leave action in state done posts the same message after its outcome card.
|
||||
|
||||
`/delete CHAT files`, an approving owner, for a current or a left chat:
|
||||
```
|
||||
step effect
|
||||
each download of CHAT rejected, "Your owner deleted the files of this chat."; apiCancelFile, an error logged; its partial file deleted
|
||||
each other file of CHAT apiCancelFile, an error logged; files in status rcvAccepted, rcvTransfer or rcvWarning, read with apiGetChat in pages of 100; a left chat: none
|
||||
the folders of CHAT with files the folder and DATA/forwarded/<folder> deleted; reply "Deleted 3 files from #9, 2.1 MB.", forwarded files counted
|
||||
no files reply "No files from #9."
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant O as Owner
|
||||
participant C as Control
|
||||
participant T as AgentTools
|
||||
O->>C: /leave #9
|
||||
C->>T: leaveChat(#9)
|
||||
C->>O: Left business Ridgeline (#9), requested by Alice.
|
||||
C->>T: chatFiles(#9)
|
||||
C->>O: Files from business Ridgeline (#9) are kept in `FOLDER`: 3 files, 2.1 MB. /'delete #9 files'
|
||||
O->>C: /delete #9 files
|
||||
C->>T: deleteChatFiles(#9)
|
||||
T->>T: each download of #9: failDownload, apiCancelFile, its partial file deleted
|
||||
T->>T: each other file of #9 in rcvAccepted, rcvTransfer or rcvWarning: apiCancelFile
|
||||
T->>T: the folder of #9 and its forwarded folder deleted
|
||||
C->>O: Deleted 3 files from #9, 2.1 MB.
|
||||
```
|
||||
|
||||
## Received files
|
||||
|
||||
The `file` field of a message (`messageSchema`, `tools.ts:119-139`; `toMessage`, `messages.ts:138-155`):
|
||||
```
|
||||
field value
|
||||
name fileName of the item's CIFile
|
||||
size bytes
|
||||
type image | video | voice | file, from the msgContent type
|
||||
status offered | received | rejected | failed
|
||||
path full path; with status received, for a file downloaded to its download path
|
||||
error with status failed
|
||||
```
|
||||
|
||||
Status from `CIFile.fileStatus`:
|
||||
```
|
||||
fileStatus status
|
||||
rcvInvitation offered
|
||||
rcvAccepted, rcvTransfer offered, while a download action performs
|
||||
rcvComplete received
|
||||
rcvCancelled, rcvAborted, rcvError failed
|
||||
```
|
||||
|
||||
- `rejected`: the last download action of the item was rejected or expired; kept in memory until a restart.
|
||||
- Instructions and the `send`, `receive` and `connect` descriptions: "When a message has a file with status offered, call download when your user's task needs it."
|
||||
|
||||
Phase 2 changes:
|
||||
```
|
||||
file function change
|
||||
messages.ts MessageFile new: name, size, type, status, path, error
|
||||
Message file: received messages with a file
|
||||
toMessage file from chatItem.file and the server's rejected files
|
||||
log.ts fileErrorText new: the apps' words for FileError
|
||||
tools.ts messageSchema file
|
||||
rejectedFiles new: file IDs whose download call returned rejected or expired; a done download removes its ID
|
||||
```
|
||||
|
||||
Status, completed:
|
||||
```
|
||||
fileStatus status
|
||||
rcvInvitation, rcvWarning offered
|
||||
rcvAccepted, rcvTransfer offered
|
||||
rcvComplete failed: "The received file is not in the agent's files folder.", when neither the download path nor the forwarded path has a file
|
||||
received, when the download path has a file and fileSource is the download path or the forwarded path
|
||||
offered otherwise: a file moved to the download path without a download, or a forwarded file before /receive
|
||||
rcvCancelled failed: "The file was cancelled."
|
||||
rcvAborted failed: "The download was aborted."
|
||||
rcvError failed: fileErrorText
|
||||
invalid failed: "Invalid file: TEXT"
|
||||
fileProhibited set failed: "Large file! The file is above the limit of N bytes for its sender.", and the badge issue; before the rows above
|
||||
file ID in rejectedFiles rejected, before the rows above
|
||||
```
|
||||
|
||||
`fileErrorText`, from `FileError.errorInfo` (`apps/multiplatform/.../ChatModel.kt:4949-4966`) and the base strings:
|
||||
```
|
||||
FileError text
|
||||
auth Wrong key or unknown file chunk address - most likely file is deleted.
|
||||
blocked File is blocked by server operator: REASON.
|
||||
noFile File not found - most likely file was deleted or cancelled.
|
||||
relay File server error: SRV, from SrvError: host, version as the apps word them; other: its text
|
||||
other Error: TEXT
|
||||
```
|
||||
|
||||
## download
|
||||
|
||||
Input:
|
||||
```
|
||||
field value
|
||||
chat chat reference
|
||||
item item ID of a message with a file
|
||||
wait seconds to wait for the sender's upload before the action, and for the download after the approval; default 60
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
field value
|
||||
action action number
|
||||
status done | performing | failed | rejected | expired
|
||||
path with done
|
||||
error with failed
|
||||
```
|
||||
|
||||
Card in the agent control chat, mode requests:
|
||||
- image: an image message with the sender's preview, the card text as caption
|
||||
- video: a video message with the sender's preview and duration, the card text as caption
|
||||
- voice and file: a text message
|
||||
- card text:
|
||||
```
|
||||
I want to download a file from Alice in group Builders (#3):
|
||||
`report.pdf`, 2.4 MB
|
||||
Servers: known.
|
||||
/'receive #3 7', /'receive #3 7 all', /'forward #3 7' or /'reject #3 7'
|
||||
```
|
||||
- unknown servers: ``Servers: `xftp.example.com`, unknown: downloading shows it this computer's IP address.``; the commands leave out `/'receive #3 7 all'`
|
||||
|
||||
Owner commands:
|
||||
```
|
||||
command effect
|
||||
/receive CHAT N the agent receives the file: the download is performed and the call returns its path
|
||||
/receive CHAT N all the same, and all later downloads in CHAT of files on known servers, which post no card; its other pending downloads stay pending
|
||||
/receive CHAT off downloads in CHAT need a card again; /revoke CHAT keeps covering messages only
|
||||
/forward CHAT N the server downloads the file to DATA/forwarded/<folder>/ and sends it into the agent control chat; the card stays pending; the agent's message shows the file as offered
|
||||
/reject CHAT N the call returns status rejected
|
||||
/approve CHAT N on a download: reply "Action N is a download: " + the card's commands line + "."
|
||||
```
|
||||
|
||||
- The "files" approval is stored in the chat's custom data, next to the standing approval of messages (`approvalOf`, `control.ts:554-557`).
|
||||
- `/list` shows it per chat.
|
||||
- Mode host: the host confirms the call (`anthropic/requiresUserInteraction`, `tools.ts:361`); no card.
|
||||
- A file on unknown servers gets a card under the files approval too; the card leaves out `/'receive CHAT N all'`. A file in `rejectedFiles` gets a card too.
|
||||
- `/receive CHAT N` after `/forward CHAT N` moves the forwarded file into `DATA/files/<folder>/`, once the copy for the post is made.
|
||||
- The post of `/forward` replies to the card when the message, with the card as its quote, is within 15602 bytes by the length rule of `send`; otherwise it is posted without the quote.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant O as Owner
|
||||
participant C as Control
|
||||
participant T as AgentTools
|
||||
participant K as Core
|
||||
participant A as Agent
|
||||
A->>T: download {chat: #3, item: 30}
|
||||
T->>C: card N: /'receive #3 N', /'receive #3 N all', /'forward #3 N' or /'reject #3 N'
|
||||
O->>C: /forward #3 N
|
||||
C->>T: forwardFile(N)
|
||||
T->>K: apiReceiveFile 7 DATA/forwarded/group-3/7.pdf
|
||||
K->>T: rcvFileComplete
|
||||
T->>T: copy for the post in DATA/files/sent/
|
||||
C->>O: the file, replying to card N; the card stays pending
|
||||
O->>C: /receive #3 N
|
||||
C->>T: approve N: performDownload
|
||||
T->>T: the forwarded file moved to DATA/files/group-3/7.pdf
|
||||
T->>A: {status: done, path: DATA/files/group-3/7.pdf}
|
||||
```
|
||||
- The files approval and the approval of messages are read again just before the action is created.
|
||||
|
||||
Perform:
|
||||
- `apiReceiveFile(fileId, "DATA/files/<folder>/<file ID><extension>", approvedRelays)`: true in mode requests for a download whose card named unknown servers; false otherwise, where the core checks the complete description and downloads a file on unknown servers only when the IP address is protected
|
||||
- `rcvFileComplete` for the file ends it with its path; `rcvFileError` or `rcvFileSndCancelled` fail it
|
||||
|
||||
Restart: undecided cards show "Cancelled: the server stopped"; their files stay offered.
|
||||
|
||||
Phase 2 changes:
|
||||
```
|
||||
file function change
|
||||
actions.ts DownloadAction new: input, subject, chat, item, from, file, startedAt, path, cardImage
|
||||
FileOffer new: fileId, name, size, type, preview, duration
|
||||
Action, NewAction DownloadAction
|
||||
decided until: the latest time to wait, Infinity by default
|
||||
summary download: "download NAME from <chat>"
|
||||
performedText download: "downloaded."
|
||||
messages.ts anyScopeChatRef new: @N or #N, also for a member chat of a group
|
||||
chatRef undefined for a member chat of a group, otherwise anyScopeChatRef
|
||||
filePaths new: the download path and the forwarded path of a received file
|
||||
receivingFiles new: the IDs of the chat's files in status rcvAccepted, rcvTransfer or rcvWarning
|
||||
tools.ts download new: attach; knownChat; the item and its file; action
|
||||
actionResult until; an action still performing at until: {action, status: "performing"}, kept in memory
|
||||
performDownload new: startedAt; downloadFile; path
|
||||
downloadTo new: one download per file ID to a target path, a promise settled by onFile
|
||||
downloadFile new: downloadTo the download path; a forwarded file moved to the download path
|
||||
forwardFile new, public: downloadTo the forwarded path, then its copy for the post
|
||||
fitsMessage the length rule of send, also for the post of /forward
|
||||
onFile new: settles the promise of the file's download
|
||||
onFileComplete new: moves the received file, also of a member chat of a group; settles as onFile
|
||||
onFileCancelled new: deletes the partial file of a download; rejects it
|
||||
serve.ts startAgent Agent.data: the data folder
|
||||
/_temp_folder DATA/tmp, mode 0700, before startChat
|
||||
events rcvFileComplete: tools.onFileComplete
|
||||
rcvFileError: tools.onFileError
|
||||
rcvFileSndCancelled: tools.onFileCancelled
|
||||
chatItemUpdated: tools.onFile
|
||||
control.ts ownerRequest /receive [CHAT] N; /receive CHAT N all; /receive CHAT off; /forward [CHAT] N
|
||||
standings new: messages and files, with the custom data key and the reply words
|
||||
approveChat standing kind
|
||||
revoke standing kind
|
||||
standingApprover standing kind
|
||||
forward new: forwardFile, then the copy posted into the agent control chat, replying to the card when the message fits
|
||||
cardContent new: image and video cards with the sender's preview, fitted to the card size
|
||||
postCard, outcome cardContent
|
||||
outcome no post for a download without a card
|
||||
requestLines download
|
||||
commandsLine download
|
||||
chatsText ", files approved by NAME"
|
||||
```
|
||||
|
||||
Output status, completed:
|
||||
```
|
||||
status meaning
|
||||
performing wait passed after the approval; the download continues; the same call again attaches to the action
|
||||
```
|
||||
|
||||
`downloadTo(action, TARGET)`: TARGET is the download path `DATA/files/<folder>/<file ID><extension>` for `downloadFile`, run by `download` and for each received file that `newChatItems` shows in status rcvAccepted, also in a member chat of a group; for `forwardFile`, run by `/forward`, the forwarded path `DATA/forwarded/<folder>/<file ID><extension>`:
|
||||
```
|
||||
a download of the file ID in memory: its promise
|
||||
otherwise a new promise in memory, then the item's file status:
|
||||
rcvComplete resolved with the download path or the forwarded path, the first that has a file; neither: rejected, "The received file is not in the agent's files folder."
|
||||
failed partial file deleted; rejected
|
||||
rcvAccepted, rcvTransfer the events settle it
|
||||
other the folder of TARGET created with mode 0700; apiReceiveFile(fileId, TARGET); the core creates the file at that path (getRcvFilePath, Library/Internal.hs:958-965)
|
||||
```
|
||||
- `downloadFile` resolved with the forwarded path: after the copy for the post of `/forward`, the file moved to the download path when that has no file.
|
||||
|
||||
Events and checks, for the received file of the event's item:
|
||||
```
|
||||
event file and promise of the file ID
|
||||
rcvFileComplete moved to path; as rcvComplete above
|
||||
rcvFileError as failed above, else rejected with the agent error (Library/Subscriber.hs:440-446)
|
||||
rcvFileSndCancelled partial file deleted; rejected: "The sender cancelled the file.", by rcvFileTransfer.fileId
|
||||
every 60 s, the item read again gone: the partial file at the last fileSource deleted; rejected, "The message or its chat was deleted."; rcvComplete: moved to path; its file as the events above
|
||||
rcvInvitation after the acceptance: rejected, "The file is on unknown servers that were not approved."
|
||||
30 minutes without rcvTransfer progress rejected, "The download made no progress for 30 minutes and was cancelled."; apiCancelFile; the partial file at the last fileSource deleted
|
||||
chatItemUpdated as rcvComplete and failed above: file digest, relays not approved (Library/Subscriber.hs:415-439)
|
||||
```
|
||||
- Moved to path: when fileSource is neither the download path nor the forwarded path, the download path has no file, and a file is at fileSource, with fileSize bytes for a file without a download; also from a member chat of a group into the group's folder.
|
||||
- A move is a rename; across file systems (EXDEV), a copy to `<path>.part`, its rename to path and a delete of the source.
|
||||
- Partial file deleted: only for a file with a download, when the file has at most fileSize bytes.
|
||||
- Every other row changes only a file with a download.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
E[event or 60 s check] --> C{rcvFileComplete, or a check that reads rcvComplete}
|
||||
C -- yes --> M{fileSource neither the download path nor the forwarded path, no file at the download path, a file at fileSource, with fileSize bytes without a download of the file ID}
|
||||
M -- yes --> V[moved to the download path]
|
||||
M -- no --> S{download of the file ID}
|
||||
V --> S
|
||||
C -- no --> S
|
||||
S -- no --> N[unchanged]
|
||||
S -- yes, rcvComplete --> R{a file at the download path or the forwarded path}
|
||||
R -- yes --> D[resolved with the first of them]
|
||||
R -- no --> Y
|
||||
S -- yes, failed, cancelled, gone or stalled --> P{at most fileSize bytes at fileSource}
|
||||
P -- yes --> X[deleted, rejected]
|
||||
P -- no --> Y[rejected]
|
||||
```
|
||||
|
||||
Card size: the image field of a card counts against 15000 bytes with its text:
|
||||
```
|
||||
image budget = 15000 - the largest card text, as aeson encodes it - the JSON of the image and duration fields
|
||||
preview within budget used
|
||||
preview over budget core.chatResizeImageUri(preview bytes, budget); an error: logged, a text card
|
||||
```
|
||||
|
||||
Card text:
|
||||
```
|
||||
form lines
|
||||
pending I want to download a file from Alice in group Builders (#3): (direct chats and channels: from bot X (@4):)
|
||||
`report.pdf`, 2.4 MB
|
||||
/'receive #3 7', /'receive #3 7 all', /'forward #3 7' or /'reject #3 7'
|
||||
done I downloaded a file from ... and the file line
|
||||
```
|
||||
- Sizes: bytes, KB, MB, GB by 1000, one decimal.
|
||||
- The servers line, after the file line: `Servers: known.`, or the unknown servers by their first host as code (decision 14); from `apiGetChatItemInfo` and `apiGetUserServers` of #7808.
|
||||
|
||||
Owner commands, completed:
|
||||
```
|
||||
command reply
|
||||
/receive CHAT N all Files approved for business Ridgeline (#9) by Alice. /'receive #9 off'
|
||||
/receive CHAT off Files from business Ridgeline (#9) need approval again.
|
||||
Files from #9 have no standing approval.
|
||||
/receive [CHAT] N none; the card is edited at the outcome
|
||||
/forward [CHAT] N none; the file arrives, as a reply to the card when the message fits
|
||||
Action N: the file was not received: `<error>`. (the download or the post of the file failed)
|
||||
/receive, /forward Action N is not a download. (N not a download)
|
||||
/approve [CHAT] N Action N is a download: /'receive #9 N', /'receive #9 N all', /'forward #9 N' or /'reject #9 N'. (N a download)
|
||||
```
|
||||
- The files approval: custom data key `simplexMcpFiles`, `{approvedBy, approvedById, approvedAt}`; the approval of messages keeps `simplexMcp`.
|
||||
- The file posted by `/forward` has the item's type and the sender's preview, with no caption; it is a copy under the file's name in `DATA/files/sent/`, deleted as resized copies are, and when its post fails.
|
||||
|
||||
## Sending files
|
||||
|
||||
Input of `send`:
|
||||
```
|
||||
field value
|
||||
file absolute path of a file the server can read
|
||||
as file: send an image or video unchanged as a file
|
||||
text caption; required without file
|
||||
```
|
||||
|
||||
Message type, by extension, unless `as` is `file`:
|
||||
```
|
||||
extension type preview file sent
|
||||
.jpg .jpeg .png image chatResizeImageUri, 14000 chatResizeImage, 261120 bytes, in DATA/files/sent/
|
||||
.gif .webp image fixed unchanged
|
||||
.mp4 .mov video ffmpeg, else fixed unchanged
|
||||
other file none unchanged
|
||||
```
|
||||
|
||||
- A copy in `DATA/files/sent/` is deleted after `sndFileCompleteXFTP` or `sndFileError`; the folder is deleted at start.
|
||||
- The send card adds the file's name, size and full path; an image card is an image message with the preview.
|
||||
|
||||
Refused paths, checked after resolving symbolic links, each folder by its real path:
|
||||
```
|
||||
path
|
||||
~/.ssh/**
|
||||
~/.gnupg/**
|
||||
files named id_*, *.pem, *.key, *.p12, *.pfx
|
||||
the server's data folder and ~/.simplex-mcp/**, except DATA/files/
|
||||
```
|
||||
|
||||
- The refusal is posted to the agent control chat and returned to the agent as a tool error.
|
||||
|
||||
Phase 2 changes:
|
||||
```
|
||||
file function change
|
||||
actions.ts Attachment new: path, name, size, inode, changedAt, type, preview, duration
|
||||
SendAction file: Attachment; cardImage
|
||||
tools.ts sendSchema file, as; text optional
|
||||
send attach check first; attachment before the action
|
||||
attachment new: the checks, type, preview and duration
|
||||
performSend the file to send; ComposedMessage with fileSource and the image, video or file content
|
||||
onSentFile new: the copy in DATA/files/sent/ deleted
|
||||
control.ts refusedFile new: the refusal posted and logged
|
||||
requestLines send: the attaching line
|
||||
serve.ts events sndFileCompleteXFTP, sndFileError: tools.onSentFile; sndFileError: control.fileNotSent, posted and logged
|
||||
```
|
||||
|
||||
`attachment(file, as)`, at the call, before the action:
|
||||
```
|
||||
check tool error
|
||||
file not absolute file: an absolute path is expected.
|
||||
realpath fails file: no file at PATH.
|
||||
not a regular file file: PATH is not a file.
|
||||
not readable file: PATH can't be read.
|
||||
refused path, on the real path file: PATH is refused: private keys and the server's own files are not sent.
|
||||
empty file file: PATH is empty.
|
||||
text and file both absent text: required without file.
|
||||
as without file as: applies only with file.
|
||||
the message over 15602 bytes text: too long for a message with this image; shorten it.
|
||||
(no file: text: too long for the message with its quote; shorten it.)
|
||||
```
|
||||
- The message length counts the quoted content as the core's `quoteContent` builds it, an image's preview included (`Library/Internal.hs:372-398`).
|
||||
- The refusal is posted: ``Refused to send `PATH` to bot SimpleX Directory (@4): private keys and the server's own files are not sent.``; the same line goes to the log.
|
||||
- The card shows the real path.
|
||||
|
||||
Previews:
|
||||
```
|
||||
type preview duration
|
||||
image JPEG core.resizeImage(file, MessagePreview); an error: logged, the fixed preview
|
||||
image PNG as JPEG
|
||||
GIF, WebP the fixed preview
|
||||
video ffmpeg's first frame, then core.resizeImage(frame, MessagePreview); ffprobe's format duration, rounded;
|
||||
ffmpeg absent or failing: logged, the fixed preview absent or failing: 0
|
||||
```
|
||||
- The fixed preview: a 16 × 9 mid-gray PNG data URI.
|
||||
- `ffmpeg -nostdin -v error -protocol_whitelist file -f mov -i PATH -frames:v 1 -f image2pipe -vcodec mjpeg -` and `ffprobe -v error -protocol_whitelist file -f mov -show_entries format=duration -of csv=p=0 PATH`, 30 s each; `-f mov` reads both .mp4 and .mov.
|
||||
|
||||
`performSend` with a file:
|
||||
```
|
||||
the real path, size, inode and change time read again; a difference: failed
|
||||
JPEG and PNG: core.resizeImage(file, Image) written to DATA/files/sent/send-XXXXXX/NAME, .jpg or .png by the result's format (Image.hs:47-57); an error: logged, the file unchanged
|
||||
apiSendMessages with fileSource {filePath}, msgContent {type, text, image, duration}
|
||||
an error of the send: the copy deleted
|
||||
```
|
||||
- `onSentFile`: the copy's folder is deleted when `fileTransferMeta.filePath` is in `DATA/files/sent/` (Library/Subscriber.hs:312, :364).
|
||||
- The send card adds ``attaching image `/home/alice/trail.jpg`, 3.1 MB`` after the text; an image or video send card shows the preview, fitted as download cards.
|
||||
|
||||
## Tests
|
||||
|
||||
- Unit: the `file` field from each `fileStatus`; type by extension; refused paths, symbolic links included; card texts.
|
||||
- End-to-end, with the local SMP and XFTP servers of `TESTING.md`:
|
||||
- a received image: card with the preview; `/forward`: the file arrives in the control chat, the card stays pending, the file is outside the chat's folder; `/receive`: `download` returns the path
|
||||
- `/receive CHAT N all`: a later download posts no card
|
||||
- `/reject` and expiry
|
||||
- sending a PNG: the peer receives an image of at most 261120 bytes with a preview
|
||||
- a refused path: the refusal in the control chat
|
||||
- `/leave #N` with a pending send for #N
|
||||
- stop with an unsent card edit: the edit is delivered before close
|
||||
|
||||
Phase 1 tests:
|
||||
- `test/unit/fixtures.ts`: `FakeChat` takes the library methods of the server from `api.ChatApi.prototype`; they send through its `sendChatCmd`, so the tests check the command strings. `/_get chats` is read with its query.
|
||||
- `test/unit/messages.test.ts`: the changed strings; `classifyPlan` with the result of `apiConnectPlan`.
|
||||
- `test/unit/commands.test.ts`, `/leave`:
|
||||
- `/leave #N` with a pending send and a pending leave for #N: the group is left and deleted; both actions are rejected by the sender; a send to another chat stays pending; the reply and the log line
|
||||
- `/leave @N`: the contact is deleted
|
||||
- names as code in the reply and on one line in the log
|
||||
- an unknown chat, the agent control chat, a member who is not an approver, mode host
|
||||
- `test/e2e/scenarios.test.ts`, `/leave #N` with a pending send for #N: the call returns rejected; the card ends with "Rejected by Alice."; the reply and the log line; the group's owner sees the agent leave; `list` leaves out the chat.
|
||||
- Development: `simplex-chat` and `@simplex-chat/types` built from this branch (`packages/simplex-chat-nodejs`, `packages/simplex-chat-client/types/typescript`) and installed in the MCP package with `npm install --no-save`.
|
||||
|
||||
Phase 2 tests:
|
||||
- `test/unit/messages.test.ts`: the `file` field from each `fileStatus`, and rejected; `download`: attach, a file already received, an item without a file, `performing` at wait, the path at done, a failure by event; `send` with `file`: the type and preview by extension, the fixed preview when the core can't resize, ffmpeg absent, refused paths with symbolic links, the copy deleted at `sndFileCompleteXFTP`.
|
||||
- `test/unit/commands.test.ts`: `/receive CHAT N all` and `/receive CHAT off`, with messages approval kept; `/receive`, `/forward`, and `/approve` of a download refused; `/list` with files approval; the download card forms; an image card fitted to 15000 bytes.
|
||||
- `test/unit/cards.test.ts`: download cards; the attaching line of send cards.
|
||||
- `test/e2e/smpServer.ts`: `startXftpServer`, a local xftp-server from `XFTP_SERVER`, configured as the smp-server; every profile gets `/xftp`.
|
||||
- `test/e2e/scenarios.test.ts`: the end-to-end list above, except the stop wait.
|
||||
|
||||
Phase 1 docs:
|
||||
- `PLAN.md`: the network configuration at start; the commands with `/leave`.
|
||||
- `REFERENCE.md`: the commands with `/leave`.
|
||||
|
||||
Phase 2 docs:
|
||||
- `PLAN.md`: the `file` field, `download`, its cards and commands, `send` with `file`.
|
||||
- `REFERENCE.md`: files.
|
||||
- `TESTING.md`: `XFTP_SERVER`; files in the steps.
|
||||
Reference in new issue
Block a user