feat: filter receive messages by conversation and query
This commit is contained in:
@@ -26,6 +26,7 @@ Remote base before local roadmap commits: `b2d839e Merge branch 'codex/n15-repor
|
|||||||
- C# `ISphereWinHelper` exists under `native/ISphereWinHelper` and uses the `isphere.helper.v1` stdin/stdout JSON contract.
|
- C# `ISphereWinHelper` exists under `native/ISphereWinHelper` and uses the `isphere.helper.v1` stdin/stdout JSON contract.
|
||||||
- `scripts/verify-win-helper.ps1` and `scripts/verify-go-mcp.ps1` provide repeatable local checks for the helper, eight-tool MCP surface, default empty-source receive/contact/group/file calls, synthetic configured-file receive/contact/group/file smoke, and synthetic configured-directory receive/contact/group/file smoke.
|
- `scripts/verify-win-helper.ps1` and `scripts/verify-go-mcp.ps1` provide repeatable local checks for the helper, eight-tool MCP surface, default empty-source receive/contact/group/file calls, synthetic configured-file receive/contact/group/file smoke, and synthetic configured-directory receive/contact/group/file smoke.
|
||||||
- `isphere_receive_messages` now returns the contract-facing `ok`, `conversation`, `messages`, `next_cursor`, and `audit` envelope while retaining legacy parser-native message fields for compatibility.
|
- `isphere_receive_messages` now returns the contract-facing `ok`, `conversation`, `messages`, `next_cursor`, and `audit` envelope while retaining legacy parser-native message fields for compatibility.
|
||||||
|
- `isphere_receive_messages` accepts `conversation_id`, `query`, and `limit` for local readonly PacketReader evidence filtering.
|
||||||
- N12-pre and N12R documents define safe offline evidence intake and internal-sandbox live UIA capture procedures.
|
- N12-pre and N12R documents define safe offline evidence intake and internal-sandbox live UIA capture procedures.
|
||||||
- N13/N14/N15 produced selector catalog/report validation infrastructure, but those are supporting validation assets only.
|
- N13/N14/N15 produced selector catalog/report validation infrastructure, but those are supporting validation assets only.
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ N13/N14/N15 are pre-business validation results. They can help identify UI eleme
|
|||||||
|
|
||||||
## Current loop
|
## Current loop
|
||||||
|
|
||||||
Current loop: `Stage C / Loop C18 - receive-message query/filter contract slice`.
|
Current loop: `Stage C / Loop C19 - receive-message since time filter slice`.
|
||||||
|
|
||||||
Plan file: `docs/superpowers/plans/2026-07-09-stage-c-log-backed-receive-messages-loop.md`.
|
Plan file: `docs/superpowers/plans/2026-07-09-stage-c-log-backed-receive-messages-loop.md`.
|
||||||
|
|
||||||
@@ -82,10 +83,11 @@ Current loop output so far:
|
|||||||
15. C15: `isphere_send_message` source precheck found no validated write connector; send implementation is blocked pending bridge/API, UIA action, or protocol evidence.
|
15. C15: `isphere_send_message` source precheck found no validated write connector; send implementation is blocked pending bridge/API, UIA action, or protocol evidence.
|
||||||
16. C16: configured PacketReader source hardening added `ISPHERE_PACKET_LOG_DIR`, deterministic recursive `.log`/`.txt` directory loading, ambiguous file/dir config failure, and configured-directory receive/contact/group/file smoke verification.
|
16. C16: configured PacketReader source hardening added `ISPHERE_PACKET_LOG_DIR`, deterministic recursive `.log`/`.txt` directory loading, ambiguous file/dir config failure, and configured-directory receive/contact/group/file smoke verification.
|
||||||
17. C17: `isphere_receive_messages` output was aligned to the core read contract envelope and message fields while retaining legacy aliases; verify-go-mcp now asserts contract fields for configured file and directory source smokes.
|
17. C17: `isphere_receive_messages` output was aligned to the core read contract envelope and message fields while retaining legacy aliases; verify-go-mcp now asserts contract fields for configured file and directory source smokes.
|
||||||
|
18. C18: `isphere_receive_messages` gained `conversation_id` and `query` filters across source, MCP args, tests, and configured file/directory smoke verification.
|
||||||
|
|
||||||
## Next business mainline
|
## Next business mainline
|
||||||
|
|
||||||
1. Run Stage C Loop C18: add the first safe `isphere_receive_messages` query/filter slice (`conversation_id`, `query`) so digital employees can target configured local-readonly evidence more precisely.
|
1. Run Stage C Loop C19: add the next safe `isphere_receive_messages` filter (`since`) so digital employees can target recent configured local-readonly evidence by time.
|
||||||
2. Leave `isphere_send_message` blocked until bridge/API, UIA action-chain, or protocol connector evidence is validated.
|
2. Leave `isphere_send_message` blocked until bridge/API, UIA action-chain, or protocol connector evidence is validated.
|
||||||
3. Leave actual file download/cache mapping as a later connector node until a message-to-cache hash or client download source is validated.
|
3. Leave actual file download/cache mapping as a later connector node until a message-to-cache hash or client download source is validated.
|
||||||
4. Keep UIA selector/report work as fallback support, not as the primary roadmap.
|
4. Keep UIA selector/report work as fallback support, not as the primary roadmap.
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ Allowed parameters:
|
|||||||
- `win_helper_self_check`: zero business parameters.
|
- `win_helper_self_check`: zero business parameters.
|
||||||
- `win_helper_scan_windows`: `include_all_visible` only.
|
- `win_helper_scan_windows`: `include_all_visible` only.
|
||||||
- `win_helper_dump_uia`: `hwnd`, `max_depth`, `include_text`, `max_children` only.
|
- `win_helper_dump_uia`: `hwnd`, `max_depth`, `include_text`, `max_children` only.
|
||||||
- `isphere_receive_messages`: `limit` only.
|
- `isphere_receive_messages`: `conversation_id`, `query`, `limit` only.
|
||||||
- `isphere_search_contacts`: `query`, `limit` only.
|
- `isphere_search_contacts`: `query`, `limit` only.
|
||||||
- `isphere_search_groups`: `query`, `limit` only.
|
- `isphere_search_groups`: `query`, `limit` only.
|
||||||
- `isphere_receive_files`: `conversation_id`, `message_id`, `file_id`, `name_contains`, `mode`, `limit` only. C14 supports `mode` empty or `list`; download is deferred.
|
- `isphere_receive_files`: `conversation_id`, `message_id`, `file_id`, `name_contains`, `mode`, `limit` only. C14 supports `mode` empty or `list`; download is deferred.
|
||||||
@@ -202,7 +202,7 @@ The current runbook verifies eight tools:
|
|||||||
- `isphere_search_groups`
|
- `isphere_search_groups`
|
||||||
- `isphere_receive_files`
|
- `isphere_receive_files`
|
||||||
|
|
||||||
The current log-backed business read tools have parser/source/tool registration and an empty default server source. Raw N12-pre evidence remains under ignored `runs/` paths and is not committed. The current command entry point can read an operator-local encrypted PacketReader log-line file via `ISPHERE_PACKET_LOG_FILE` or a directory of `.log`/`.txt` files via `ISPHERE_PACKET_LOG_DIR`; this is still not a production ingestion claim because raw evidence remains local and uncommitted. `isphere_receive_messages` now exposes contract-facing fields for digital-employee consumption while preserving older aliases for compatibility. `isphere_receive_files` currently supports list mode only; download/cache mapping is deferred.
|
The current log-backed business read tools have parser/source/tool registration and an empty default server source. Raw N12-pre evidence remains under ignored `runs/` paths and is not committed. The current command entry point can read an operator-local encrypted PacketReader log-line file via `ISPHERE_PACKET_LOG_FILE` or a directory of `.log`/`.txt` files via `ISPHERE_PACKET_LOG_DIR`; this is still not a production ingestion claim because raw evidence remains local and uncommitted. `isphere_receive_messages` now exposes contract-facing fields for digital-employee consumption while preserving older aliases for compatibility, and supports `conversation_id` plus keyword `query` filtering. `isphere_receive_files` currently supports list mode only; download/cache mapping is deferred.
|
||||||
|
|
||||||
Core business tools for contacts, groups, messages, and files are defined in `docs/mcp-core-tools-contract.md`. Send capabilities enter the roadmap through that contract after source discovery, connector selection, preview metadata, execution metadata, and audit records are in place.
|
Core business tools for contacts, groups, messages, and files are defined in `docs/mcp-core-tools-contract.md`. Send capabilities enter the roadmap through that contract after source discovery, connector selection, preview metadata, execution metadata, and audit records are in place.
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Schema notes: `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md`
|
|||||||
|
|
||||||
| MCP tool | Primary source | Fallback source | Evidence file | Decision status | Next implementation slice |
|
| MCP tool | Primary source | Fallback source | Evidence file | Decision status | Next implementation slice |
|
||||||
| --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- |
|
||||||
| `isphere_receive_messages` | decrypted `PacketReader.ProcessPacket` XMPP `<message>` stanzas via `internal/isphere.EncryptedPacketLogSource`; configured by `ISPHERE_PACKET_LOG_FILE` or `ISPHERE_PACKET_LOG_DIR` | decrypted `Smark.SendReceive` transport stanzas; decrypted `SaveToDB` `Chat_OnMessageArrived` traces; wrapped `MsgLib.db` after DB wrapper is solved | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#field-mapping-evidence` | C17 response envelope and message fields align to the read contract while keeping legacy aliases; default source remains empty when unset | add `conversation_id` and `query` filters |
|
| `isphere_receive_messages` | decrypted `PacketReader.ProcessPacket` XMPP `<message>` stanzas via `internal/isphere.EncryptedPacketLogSource`; configured by `ISPHERE_PACKET_LOG_FILE` or `ISPHERE_PACKET_LOG_DIR` | decrypted `Smark.SendReceive` transport stanzas; decrypted `SaveToDB` `Chat_OnMessageArrived` traces; wrapped `MsgLib.db` after DB wrapper is solved | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#field-mapping-evidence` | C18 supports contract-shaped output plus `conversation_id`, `query`, and `limit` filters; default source remains empty when unset | add `since` time filter |
|
||||||
| `isphere_search_contacts` | bare sender/receiver JIDs extracted from normalized log-backed messages loaded from configured PacketReader file or directory source | decrypted roster/contact stanzas from `Smark.SendReceive`; `MsgLib.db` contact/conversation tables after DB wrapper is solved; UIA helper source if display names are missing | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#log-decryption-result` | C16 verification covers default, configured-file, and configured-directory contact smoke; JID-only display/account fallback | enrich display names from roster/db/uia evidence |
|
| `isphere_search_contacts` | bare sender/receiver JIDs extracted from normalized log-backed messages loaded from configured PacketReader file or directory source | decrypted roster/contact stanzas from `Smark.SendReceive`; `MsgLib.db` contact/conversation tables after DB wrapper is solved; UIA helper source if display names are missing | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#log-decryption-result` | C16 verification covers default, configured-file, and configured-directory contact smoke; JID-only display/account fallback | enrich display names from roster/db/uia evidence |
|
||||||
| `isphere_search_groups` | decrypted `PacketReader.ProcessPacket` `type="groupchat"` stanzas and conference/MUC JIDs loaded from configured PacketReader file or directory source | `MsgLib.db` group/conversation tables after DB wrapper is solved; UIA helper source if group display names are missing | C9 ignored-log scan: PacketReader 86 groupchat/86 conference hits; Smark 24 groupchat/658 conference/631 muc hits | C16 verification covers default, configured-file, and configured-directory group smoke; JID-only display fallback | enrich group display/member data from roster/db/uia evidence |
|
| `isphere_search_groups` | decrypted `PacketReader.ProcessPacket` `type="groupchat"` stanzas and conference/MUC JIDs loaded from configured PacketReader file or directory source | `MsgLib.db` group/conversation tables after DB wrapper is solved; UIA helper source if group display names are missing | C9 ignored-log scan: PacketReader 86 groupchat/86 conference hits; Smark 24 groupchat/658 conference/631 muc hits | C16 verification covers default, configured-file, and configured-directory group smoke; JID-only display fallback | enrich group display/member data from roster/db/uia evidence |
|
||||||
| `isphere_receive_files` | decrypted `PacketReader.ProcessPacket` file-transfer message stanzas via `internal/isphere.ListFilesFromMessages` and `isphere_receive_files` list mode; configured PacketReader file or directory source; `zyl\importal\<hash>` cache entries remain unlinked | decrypted `Smark.SendReceive` and `SaveToDB` traces for file-reference reconciliation; future UIA/client connector for download | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#c12-file-transfer-evidence-precheck` | C16 verification covers default, configured-file, and configured-directory file-list smoke; no log-to-cache/download mapping yet | defer download mapping; align read-tool contract output |
|
| `isphere_receive_files` | decrypted `PacketReader.ProcessPacket` file-transfer message stanzas via `internal/isphere.ListFilesFromMessages` and `isphere_receive_files` list mode; configured PacketReader file or directory source; `zyl\importal\<hash>` cache entries remain unlinked | decrypted `Smark.SendReceive` and `SaveToDB` traces for file-reference reconciliation; future UIA/client connector for download | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#c12-file-transfer-evidence-precheck` | C16 verification covers default, configured-file, and configured-directory file-list smoke; no log-to-cache/download mapping yet | defer download mapping; align read-tool contract output |
|
||||||
|
|||||||
@@ -1573,19 +1573,19 @@ Implemented and verified in this loop:
|
|||||||
- Preserve current empty-source behavior and current `limit` behavior.
|
- Preserve current empty-source behavior and current `limit` behavior.
|
||||||
- Do not add write/send/file-download behavior.
|
- Do not add write/send/file-download behavior.
|
||||||
|
|
||||||
- [ ] **Step 1: Write failing source/query test**
|
- [x] **Step 1: Write failing source/query test**
|
||||||
|
|
||||||
Extend `internal/isphere/source_test.go` with redacted fixtures proving `conversation_id` and keyword filtering.
|
Extend `internal/isphere/source_test.go` with redacted fixtures proving `conversation_id` and keyword filtering.
|
||||||
|
|
||||||
- [ ] **Step 2: Implement source query filtering**
|
- [x] **Step 2: Implement source query filtering**
|
||||||
|
|
||||||
Apply filters after decrypt/parse/normalize and before limit truncation, keeping deterministic source order.
|
Apply filters after decrypt/parse/normalize and before limit truncation, keeping deterministic source order.
|
||||||
|
|
||||||
- [ ] **Step 3: Wire MCP args and verification**
|
- [x] **Step 3: Wire MCP args and verification**
|
||||||
|
|
||||||
Expose `conversation_id` and `query` on `isphere_receive_messages`, update tool tests and `verify-go-mcp.ps1` synthetic smoke.
|
Expose `conversation_id` and `query` on `isphere_receive_messages`, update tool tests and `verify-go-mcp.ps1` synthetic smoke.
|
||||||
|
|
||||||
- [ ] **Step 4: Run full verification and commit**
|
- [x] **Step 4: Run full verification and commit**
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
|
|
||||||
@@ -1600,6 +1600,67 @@ Expected: all exit 0. Remove any generated root `isphere-mcp.exe` after build. C
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Loop C18 Result
|
||||||
|
|
||||||
|
Implemented and verified in this loop:
|
||||||
|
|
||||||
|
- `isphere.ReceiveMessagesQuery` now supports `ConversationID` and `Query` in addition to `Limit`.
|
||||||
|
- `EncryptedPacketLogSource.ReceiveMessages` filters normalized messages by exact conversation id and case-insensitive keyword match over message id, conversation id, sender id, receiver id, text, subject, and receipt id.
|
||||||
|
- `isphere_receive_messages` MCP args now accept `conversation_id` and `query`, and pass them through to the source.
|
||||||
|
- `scripts/verify-go-mcp.ps1` now calls configured file and directory source smokes with `conversation_id` plus keyword query arguments.
|
||||||
|
- Existing empty-source and limit behavior remains unchanged.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Loop C19: Receive-message `since` time filter slice
|
||||||
|
|
||||||
|
**Goal:** Add the next safe receive-message filter from the core contract: `since`, using ISO-8601 input from the MCP caller and PacketReader epoch-millisecond timestamps from the normalized local readonly source.
|
||||||
|
|
||||||
|
**Planned files:**
|
||||||
|
- Modify: `internal/isphere/source.go`
|
||||||
|
- Modify: `internal/isphere/source_test.go`
|
||||||
|
- Modify: `internal/tools/isphere_read.go`
|
||||||
|
- Modify: `internal/tools/isphere_read_test.go`
|
||||||
|
- Modify: `scripts/verify-go-mcp.ps1`
|
||||||
|
- Modify: `docs/go-mcp-runbook.md`
|
||||||
|
- Modify: `docs/current-status-card.md`
|
||||||
|
- Modify: `docs/superpowers/plans/2026-07-09-stage-c-log-backed-receive-messages-loop.md`
|
||||||
|
|
||||||
|
**Planned behavior:**
|
||||||
|
- Add `Since` to `ReceiveMessagesQuery` and `ReceiveMessagesArgs`.
|
||||||
|
- Accept RFC3339/ISO-8601 input for `since`.
|
||||||
|
- Compare against message `Timestamp` when it is an epoch-millisecond value.
|
||||||
|
- Return a clear error for invalid `since` input rather than silently ignoring it.
|
||||||
|
- Keep `cursor`, `source_preference`, and broader pagination deferred.
|
||||||
|
- Do not add write/send/file-download behavior.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing source `since` test**
|
||||||
|
|
||||||
|
Use synthetic PacketReader fixtures before and after an RFC3339 threshold.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Implement source `since` filtering**
|
||||||
|
|
||||||
|
Parse `since` once per receive call and filter normalized messages by timestamp.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Wire MCP args and verification**
|
||||||
|
|
||||||
|
Expose `since` on `isphere_receive_messages`, update tool tests and `verify-go-mcp.ps1` synthetic smoke.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run full verification and commit**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git diff --check
|
||||||
|
go test ./...
|
||||||
|
go build ./cmd/isphere-mcp
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/verify-go-mcp.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all exit 0. Remove any generated root `isphere-mcp.exe` after build. Commit exact C19 paths only and rewrite the next loop from actual results.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Self-Review
|
## Self-Review
|
||||||
|
|
||||||
- Spec coverage: the plan follows the user's cyclic requirement: plan first, implement one loop at a time, update the next loop after each result, and ask only on blockers.
|
- Spec coverage: the plan follows the user's cyclic requirement: plan first, implement one loop at a time, update the next loop after each result, and ask only on blockers.
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ type Message struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ReceiveMessagesQuery struct {
|
type ReceiveMessagesQuery struct {
|
||||||
Limit int
|
ConversationID string
|
||||||
|
Query string
|
||||||
|
Limit int
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReceiveMessagesResult struct {
|
type ReceiveMessagesResult struct {
|
||||||
@@ -41,9 +43,6 @@ func (s EncryptedPacketLogSource) ReceiveMessages(ctx context.Context, query Rec
|
|||||||
}
|
}
|
||||||
messages := make([]Message, 0, limit)
|
messages := make([]Message, 0, limit)
|
||||||
for index, line := range s.Lines {
|
for index, line := range s.Lines {
|
||||||
if len(messages) == limit {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ReceiveMessagesResult{}, ctx.Err()
|
return ReceiveMessagesResult{}, ctx.Err()
|
||||||
@@ -57,11 +56,43 @@ func (s EncryptedPacketLogSource) ReceiveMessages(ctx context.Context, query Rec
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ReceiveMessagesResult{}, fmt.Errorf("parse packet log line %d: %w", index, err)
|
return ReceiveMessagesResult{}, fmt.Errorf("parse packet log line %d: %w", index, err)
|
||||||
}
|
}
|
||||||
messages = append(messages, normalizePacketMessage(parsed))
|
message := normalizePacketMessage(parsed)
|
||||||
|
if !receiveMessageMatchesQuery(message, query) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
messages = append(messages, message)
|
||||||
|
if len(messages) == limit {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ReceiveMessagesResult{Messages: messages}, nil
|
return ReceiveMessagesResult{Messages: messages}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func receiveMessageMatchesQuery(message Message, query ReceiveMessagesQuery) bool {
|
||||||
|
if strings.TrimSpace(query.ConversationID) != "" && message.ConversationID != strings.TrimSpace(query.ConversationID) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
keyword := strings.ToLower(strings.TrimSpace(query.Query))
|
||||||
|
if keyword == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
values := []string{
|
||||||
|
message.ID,
|
||||||
|
message.ConversationID,
|
||||||
|
message.SenderID,
|
||||||
|
message.ReceiverID,
|
||||||
|
message.Text,
|
||||||
|
message.Subject,
|
||||||
|
message.ReceiptID,
|
||||||
|
}
|
||||||
|
for _, value := range values {
|
||||||
|
if strings.Contains(strings.ToLower(value), keyword) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func normalizePacketMessage(msg packetlog.Message) Message {
|
func normalizePacketMessage(msg packetlog.Message) Message {
|
||||||
sender := bareJID(msg.From)
|
sender := bareJID(msg.From)
|
||||||
receiver := bareJID(msg.To)
|
receiver := bareJID(msg.To)
|
||||||
|
|||||||
@@ -42,6 +42,58 @@ func TestEncryptedPacketLogSourceReturnsNormalizedMessages(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncryptedPacketLogSourceFiltersByConversationAndQuery(t *testing.T) {
|
||||||
|
first := `--------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
2026/7/7 15:30:07
|
||||||
|
<message id="msg-alpha" from="sender@imopenfire1-lanzhou/imp_pc_4.1.2.6842" to="receiver@imopenfire1-lanzhou" type="chat">
|
||||||
|
<body>alpha status update</body>
|
||||||
|
<received xmlns="urn:xmpp:receipts" id="receipt-alpha" type="1" stamp="" />
|
||||||
|
<subject>TASK_UPDATE</subject>
|
||||||
|
<isphere xmlns="isphere.im" type="1001" sendtime="1783423807000" version="1" />
|
||||||
|
<readed />
|
||||||
|
</message>`
|
||||||
|
second := `--------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
2026/7/7 15:31:07
|
||||||
|
<message id="msg-beta" from="project-room@conference.imopenfire1-lanzhou/imp_pc_4.1.2.6842" to="sender@imopenfire1-lanzhou" type="groupchat">
|
||||||
|
<body>beta budget redacted-report.docx</body>
|
||||||
|
<received xmlns="urn:xmpp:receipts" id="receipt-beta" type="1" stamp="" />
|
||||||
|
<subject>FILE_TRANSFER_CANCEL</subject>
|
||||||
|
<isphere xmlns="isphere.im" type="1001" sendtime="1783423867000" version="1" />
|
||||||
|
<readed />
|
||||||
|
</message>`
|
||||||
|
source := EncryptedPacketLogSource{Lines: []string{
|
||||||
|
encryptPacketLineForTest(t, first),
|
||||||
|
encryptPacketLineForTest(t, second),
|
||||||
|
}}
|
||||||
|
|
||||||
|
got, err := source.ReceiveMessages(context.Background(), ReceiveMessagesQuery{
|
||||||
|
ConversationID: "project-room@conference.imopenfire1-lanzhou|sender@imopenfire1-lanzhou",
|
||||||
|
Query: "BUDGET",
|
||||||
|
Limit: 10,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReceiveMessages returned error: %v", err)
|
||||||
|
}
|
||||||
|
if len(got.Messages) != 1 {
|
||||||
|
t.Fatalf("message count = %d, want 1: %+v", len(got.Messages), got.Messages)
|
||||||
|
}
|
||||||
|
if got.Messages[0].ID != "msg-beta" {
|
||||||
|
t.Fatalf("message id = %q, want msg-beta", got.Messages[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
empty, err := source.ReceiveMessages(context.Background(), ReceiveMessagesQuery{
|
||||||
|
ConversationID: "project-room@conference.imopenfire1-lanzhou|sender@imopenfire1-lanzhou",
|
||||||
|
Query: "alpha",
|
||||||
|
Limit: 10,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReceiveMessages returned error for empty query: %v", err)
|
||||||
|
}
|
||||||
|
if len(empty.Messages) != 0 {
|
||||||
|
t.Fatalf("empty query returned %+v, want none", empty.Messages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func encryptPacketLineForTest(t *testing.T, plaintext string) string {
|
func encryptPacketLineForTest(t *testing.T, plaintext string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
block, err := des.NewCipher([]byte("hyhccdtm"))
|
block, err := des.NewCipher([]byte("hyhccdtm"))
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ type ReceiveMessagesSource interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ReceiveMessagesArgs struct {
|
type ReceiveMessagesArgs struct {
|
||||||
Limit int `json:"limit,omitempty" jsonschema:"maximum number of messages to return; zero or negative returns all available messages"`
|
ConversationID string `json:"conversation_id,omitempty" jsonschema:"exact conversation id filter for local readonly message logs"`
|
||||||
|
Query string `json:"query,omitempty" jsonschema:"case-insensitive keyword filter over message text, subject, ids, and participants"`
|
||||||
|
Limit int `json:"limit,omitempty" jsonschema:"maximum number of messages to return; zero or negative returns all available messages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterISphereReadTools(server *mcp.Server, source ReceiveMessagesSource) {
|
func RegisterISphereReadTools(server *mcp.Server, source ReceiveMessagesSource) {
|
||||||
@@ -31,7 +33,11 @@ func RegisterISphereReadTools(server *mcp.Server, source ReceiveMessagesSource)
|
|||||||
Description: "Return iSphere messages from the configured read-only log-backed source.",
|
Description: "Return iSphere messages from the configured read-only log-backed source.",
|
||||||
}, func(ctx context.Context, _ *mcp.CallToolRequest, input ReceiveMessagesArgs) (*mcp.CallToolResult, map[string]any, error) {
|
}, func(ctx context.Context, _ *mcp.CallToolRequest, input ReceiveMessagesArgs) (*mcp.CallToolResult, map[string]any, error) {
|
||||||
started := time.Now().UTC()
|
started := time.Now().UTC()
|
||||||
result, err := source.ReceiveMessages(ctx, isphere.ReceiveMessagesQuery{Limit: input.Limit})
|
result, err := source.ReceiveMessages(ctx, isphere.ReceiveMessagesQuery{
|
||||||
|
ConversationID: input.ConversationID,
|
||||||
|
Query: input.Query,
|
||||||
|
Limit: input.Limit,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func TestISphereReceiveMessagesToolReturnsSourceMessages(t *testing.T) {
|
|||||||
|
|
||||||
callResult, err := session.CallTool(context.Background(), &mcp.CallToolParams{
|
callResult, err := session.CallTool(context.Background(), &mcp.CallToolParams{
|
||||||
Name: ToolNameReceiveMessages,
|
Name: ToolNameReceiveMessages,
|
||||||
Arguments: map[string]any{"limit": 5},
|
Arguments: map[string]any{"conversation_id": "sender@imopenfire1-lanzhou|receiver@imopenfire1-lanzhou", "query": "REPORT", "limit": 5},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("call %s: %v", ToolNameReceiveMessages, err)
|
t.Fatalf("call %s: %v", ToolNameReceiveMessages, err)
|
||||||
@@ -50,8 +50,8 @@ func TestISphereReceiveMessagesToolReturnsSourceMessages(t *testing.T) {
|
|||||||
if callResult.IsError {
|
if callResult.IsError {
|
||||||
t.Fatalf("call result is error: %+v", callResult)
|
t.Fatalf("call result is error: %+v", callResult)
|
||||||
}
|
}
|
||||||
if len(fake.queries) != 1 || fake.queries[0].Limit != 5 {
|
if len(fake.queries) != 1 || fake.queries[0].Limit != 5 || fake.queries[0].ConversationID != "sender@imopenfire1-lanzhou|receiver@imopenfire1-lanzhou" || fake.queries[0].Query != "REPORT" {
|
||||||
t.Fatalf("queries = %+v, want one limit=5", fake.queries)
|
t.Fatalf("queries = %+v, want conversation_id/query/limit", fake.queries)
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoded struct {
|
var decoded struct {
|
||||||
|
|||||||
@@ -298,7 +298,11 @@ func verifyConfiguredReceiveSource(ctx context.Context) (int, int, int, int) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
callResult, err := session.CallTool(serverCtx, &mcp.CallToolParams{Name: "isphere_receive_messages", Arguments: map[string]any{"limit": 5}})
|
callResult, err := session.CallTool(serverCtx, &mcp.CallToolParams{Name: "isphere_receive_messages", Arguments: map[string]any{
|
||||||
|
"conversation_id": "project-room@conference.imopenfire1-lanzhou|sender@imopenfire1-lanzhou",
|
||||||
|
"query": "report",
|
||||||
|
"limit": 5,
|
||||||
|
}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail("fixture/call isphere_receive_messages", err)
|
fail("fixture/call isphere_receive_messages", err)
|
||||||
}
|
}
|
||||||
@@ -418,7 +422,11 @@ func verifyConfiguredDirectorySource(ctx context.Context) (int, int, int, int) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
callResult, err := session.CallTool(serverCtx, &mcp.CallToolParams{Name: "isphere_receive_messages", Arguments: map[string]any{"limit": 5}})
|
callResult, err := session.CallTool(serverCtx, &mcp.CallToolParams{Name: "isphere_receive_messages", Arguments: map[string]any{
|
||||||
|
"conversation_id": "project-room@conference.imopenfire1-lanzhou|sender@imopenfire1-lanzhou",
|
||||||
|
"query": "dir-report",
|
||||||
|
"limit": 5,
|
||||||
|
}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail("dir_fixture/call isphere_receive_messages", err)
|
fail("dir_fixture/call isphere_receive_messages", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user