feat: select explicit msglib receive source
This commit is contained in:
@@ -29,7 +29,7 @@ Remote base before local roadmap commits: `b2d839e Merge branch 'codex/n15-repor
|
|||||||
- `isphere_receive_messages` accepts the read-contract argument set for the current local-readonly path: `conversation_id`, `query`, `since`, `limit`, `include_attachment_metadata`, `source_preference`, `preview`, and empty `cursor`.
|
- `isphere_receive_messages` accepts the read-contract argument set for the current local-readonly path: `conversation_id`, `query`, `since`, `limit`, `include_attachment_metadata`, `source_preference`, `preview`, and empty `cursor`.
|
||||||
- `isphere_search_contacts` and `isphere_search_groups` accept and validate the safe search-contract args for the current local-readonly path and can optionally enrich results from the env-configured C29 `MsgLib.db` display-entity reader; default behavior remains log/JID-backed when MsgLib env vars are absent.
|
- `isphere_search_contacts` and `isphere_search_groups` accept and validate the safe search-contract args for the current local-readonly path and can optionally enrich results from the env-configured C29 `MsgLib.db` display-entity reader; default behavior remains log/JID-backed when MsgLib env vars are absent.
|
||||||
- `isphere_receive_files` list mode accepts and validates the safe file-list contract args for the current local-readonly path while keeping download blocked.
|
- `isphere_receive_files` list mode accepts and validates the safe file-list contract args for the current local-readonly path while keeping download blocked.
|
||||||
- `native/MsgLibReadSidecar` provides a bounded x86 .NET read-only boundary for `MsgLib.db` schema/display-source checks, bounded contact/group display-entity reads, message-source metadata, and sidecar-level bounded message listing; `internal/msglib` wraps it from Go, C30 wires display metadata as an optional MCP contact/group enrichment source, and C39 adds a Go receive-source adapter around `ListMessages` without changing the MCP default source.
|
- `native/MsgLibReadSidecar` provides a bounded x86 .NET read-only boundary for `MsgLib.db` schema/display-source checks, bounded contact/group display-entity reads, message-source metadata, and sidecar-level bounded message listing; `internal/msglib` wraps it from Go, C30 wires display metadata as an optional MCP contact/group enrichment source, C39 adds a Go receive-source adapter around `ListMessages`, and C40 adds tool-level explicit `msglib_readonly` source selection without changing the MCP default source.
|
||||||
- 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.
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ N13/N14/N15 are pre-business validation results. They can help identify UI eleme
|
|||||||
|
|
||||||
## Current loop
|
## Current loop
|
||||||
|
|
||||||
Current loop: `Stage C / Loop C40 - explicit MsgLib receive source selection design/test slice`.
|
Current loop: `Stage C / Loop C41 - MsgLib receive env wiring and optional smoke`.
|
||||||
|
|
||||||
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`.
|
||||||
|
|
||||||
@@ -108,10 +108,11 @@ Current loop output so far:
|
|||||||
37. C37: `docs/source-discovery/2026-07-10-db-backed-receive-source-design.md` defines the bounded DB-backed receive-message design: sidecar op `list_messages`, hard limit, no cursor initially, explicit `include_body`, no raw rows, no file path values, PacketReader remains default until reconciliation.
|
37. C37: `docs/source-discovery/2026-07-10-db-backed-receive-source-design.md` defines the bounded DB-backed receive-message design: sidecar op `list_messages`, hard limit, no cursor initially, explicit `include_body`, no raw rows, no file path values, PacketReader remains default until reconciliation.
|
||||||
38. C38: added sidecar/Go-wrapper `list_messages`, clamped to `1..50`, cursor rejected, optional attachment metadata without path/download refs, and copied-DB verification with `include_body=false`; printed evidence is only count/source-table/safety booleans.
|
38. C38: added sidecar/Go-wrapper `list_messages`, clamped to `1..50`, cursor rejected, optional attachment metadata without path/download refs, and copied-DB verification with `include_body=false`; printed evidence is only count/source-table/safety booleans.
|
||||||
39. C39: added `internal/isphere.MsgLibMessageSource`, an injected-provider adapter that maps `msglib.ListMessages` results into the existing receive-message domain model, preserving explicit source refs and attachment metadata while leaving MCP default PacketReader behavior unchanged.
|
39. C39: added `internal/isphere.MsgLibMessageSource`, an injected-provider adapter that maps `msglib.ListMessages` results into the existing receive-message domain model, preserving explicit source refs and attachment metadata while leaving MCP default PacketReader behavior unchanged.
|
||||||
|
40. C40: added tool-level explicit receive source selection: empty/`auto`/`local_readonly` still use the primary PacketReader/log-backed source, while `source_preference="msglib_readonly"` uses a configured MsgLib receive source and is rejected when not configured.
|
||||||
|
|
||||||
## Next business mainline
|
## Next business mainline
|
||||||
|
|
||||||
1. Run Stage C Loop C40: define and test explicit MsgLib receive source selection behavior, keeping `auto`/default on PacketReader and enabling DB rows only through an explicit operator-selected path.
|
1. Run Stage C Loop C41: wire the MsgLib receive adapter into server env configuration behind explicit `source_preference="msglib_readonly"` and add an optional sanitized smoke; keep standard `verify-go-mcp.ps1` deterministic.
|
||||||
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.
|
||||||
|
|||||||
@@ -300,6 +300,13 @@ Committed C39 implementation:
|
|||||||
- It preserves explicit `source`/`raw_ref` values and bounded attachment metadata while rejecting provider results that report non-read-only mode, raw rows, or file path values.
|
- It preserves explicit `source`/`raw_ref` values and bounded attachment metadata while rejecting provider results that report non-read-only mode, raw rows, or file path values.
|
||||||
- It is not wired into MCP default receive behavior; `auto` remains PacketReader/log-backed until an explicit source-selection loop is completed.
|
- It is not wired into MCP default receive behavior; `auto` remains PacketReader/log-backed until an explicit source-selection loop is completed.
|
||||||
|
|
||||||
|
Committed C40 implementation:
|
||||||
|
|
||||||
|
- Tool-layer selector: `RegisterISphereReadToolsWithReceiveSources`.
|
||||||
|
- `source_preference=""`, `auto`, and `local_readonly` continue to use the primary PacketReader/log-backed source.
|
||||||
|
- `source_preference="msglib_readonly"` can select an explicitly configured MsgLib receive source and is rejected when that source is absent.
|
||||||
|
- Server env wiring is deferred to C41, so standard MCP startup and verification remain deterministic.
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
Default verification builds and checks `self_check` only:
|
Default verification builds and checks `self_check` only:
|
||||||
|
|||||||
@@ -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` | copied `MsgLib.db` message tables through x86 read-only sidecar `list_messages`; decrypted `Smark.SendReceive` transport stanzas; decrypted `SaveToDB` `Chat_OnMessageArrived` traces | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#field-mapping-evidence`; `docs/source-discovery/2026-07-10-dotnet-wrapper-static-analysis.md`; `docs/source-discovery/2026-07-10-db-backed-receive-source-design.md` | C20 supports and validates the current safe receive-message contract args; C32 wires optional MsgLib display metadata into receive-message `sender_name` and `conversation.display_name`; C33 proves this path through a real copied-DB MCP smoke; C35 maps MsgLib message tables; C36 adds metadata-only `message_sources`; C37 defines bounded `list_messages` design; C38 implements sidecar/Go-wrapper `list_messages` with sanitized copied-DB verification; C39 adds a Go adapter from `ListMessages` to the receive-message domain model, while PacketReader remains MCP default until explicit source selection is tested | C40 explicit MsgLib receive source selection design/test 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` | copied `MsgLib.db` message tables through x86 read-only sidecar `list_messages`; decrypted `Smark.SendReceive` transport stanzas; decrypted `SaveToDB` `Chat_OnMessageArrived` traces | `docs/source-discovery/2026-07-09-n12-pre-zyl-schema-notes.md#field-mapping-evidence`; `docs/source-discovery/2026-07-10-dotnet-wrapper-static-analysis.md`; `docs/source-discovery/2026-07-10-db-backed-receive-source-design.md` | C20 supports and validates the current safe receive-message contract args; C32 wires optional MsgLib display metadata into receive-message `sender_name` and `conversation.display_name`; C33 proves this path through a real copied-DB MCP smoke; C35 maps MsgLib message tables; C36 adds metadata-only `message_sources`; C37 defines bounded `list_messages` design; C38 implements sidecar/Go-wrapper `list_messages` with sanitized copied-DB verification; C39 adds a Go adapter from `ListMessages` to the receive-message domain model; C40 adds tool-level explicit `msglib_readonly` source selection while `auto` remains PacketReader/log-backed | C41 MsgLib receive env wiring and optional sanitized smoke |
|
||||||
| `isphere_search_contacts` | bare sender/receiver JIDs extracted from normalized log-backed messages loaded from configured PacketReader file or directory source | validated copied `MsgLib.db` tables: `TD_Roster`, `TD_CustomEffigy`, `tblRecent`, `tblChatLevel`, `tblPersonMsg`; decrypted roster/contact stanzas from `Smark.SendReceive`; UIA helper source if display names are still missing | `docs/source-discovery/2026-07-10-msglib-readonly-schema-extraction.md` | C34 documents how to enable optional MsgLib contact display enrichment and verify sanitized MCP output; search enrichment remains available | C37 bounded DB-backed receive-source design |
|
| `isphere_search_contacts` | bare sender/receiver JIDs extracted from normalized log-backed messages loaded from configured PacketReader file or directory source | validated copied `MsgLib.db` tables: `TD_Roster`, `TD_CustomEffigy`, `tblRecent`, `tblChatLevel`, `tblPersonMsg`; decrypted roster/contact stanzas from `Smark.SendReceive`; UIA helper source if display names are still missing | `docs/source-discovery/2026-07-10-msglib-readonly-schema-extraction.md` | C34 documents how to enable optional MsgLib contact display enrichment and verify sanitized MCP output; search enrichment remains available | C37 bounded DB-backed receive-source design |
|
||||||
| `isphere_search_groups` | decrypted `PacketReader.ProcessPacket` `type="groupchat"` stanzas and conference/MUC JIDs loaded from configured PacketReader file or directory source | validated copied `MsgLib.db` tables: `tblMsgGroupPersonMsg`, `TD_WorkGroupAuth`, `tblRecent`; UIA helper source if group display names are still missing | `docs/source-discovery/2026-07-10-msglib-readonly-schema-extraction.md`; C9 ignored-log scan: PacketReader 86 groupchat/86 conference hits; Smark 24 groupchat/658 conference/631 muc hits | C34 documents how to enable optional MsgLib group display enrichment and verify sanitized MCP output; search enrichment remains available | C37 bounded DB-backed receive-source design |
|
| `isphere_search_groups` | decrypted `PacketReader.ProcessPacket` `type="groupchat"` stanzas and conference/MUC JIDs loaded from configured PacketReader file or directory source | validated copied `MsgLib.db` tables: `tblMsgGroupPersonMsg`, `TD_WorkGroupAuth`, `tblRecent`; UIA helper source if group display names are still missing | `docs/source-discovery/2026-07-10-msglib-readonly-schema-extraction.md`; C9 ignored-log scan: PacketReader 86 groupchat/86 conference hits; Smark 24 groupchat/658 conference/631 muc hits | C34 documents how to enable optional MsgLib group display enrichment and verify sanitized MCP output; search enrichment remains available | C37 bounded DB-backed receive-source design |
|
||||||
| `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` | C22 verifies safe file-list contract args; no log-to-cache/download mapping yet | defer download mapping; precheck contact/group display-name 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` | C22 verifies safe file-list contract args; no log-to-cache/download mapping yet | defer download mapping; precheck contact/group display-name evidence |
|
||||||
@@ -39,4 +39,4 @@ Start Stage C with a narrow log-backed `isphere_receive_messages` source abstrac
|
|||||||
|
|
||||||
## Deferred source work
|
## Deferred source work
|
||||||
|
|
||||||
`MsgLib.db` has a validated copied read-only open path through the bundled 32-bit `System.Data.SQLite.dll` and password `123`. C27 adds `MsgLibReadSidecar` as the bounded x86 .NET reader boundary, C28 adds the Go `internal/msglib` process client, C29 adds bounded `display_entities` extraction, C30 wires it as optional contact/group MCP enrichment, C31 proves real copied-DB MCP enrichment with sanitized output, C32 reuses it for receive-message display fields, C33 proves that receive display path through a real copied-DB MCP smoke without printing entity values, C34 documents the operator setup/verification path, C35 maps MsgLib message tables to receive-message contract fields without reading row values, C36 adds metadata-only `message_sources` readiness, C37 defines the bounded DB-backed receive-source design, C38 implements sidecar/Go-wrapper `list_messages` with sanitized verification, and C39 adds a Go adapter from MsgLib list results to the receive-message domain model. The next step is explicit source-selection behavior; do not silently merge MsgLib rows into the MCP default until PacketReader-vs-DB reconciliation and operator selection are explicit.
|
`MsgLib.db` has a validated copied read-only open path through the bundled 32-bit `System.Data.SQLite.dll` and password `123`. C27 adds `MsgLibReadSidecar` as the bounded x86 .NET reader boundary, C28 adds the Go `internal/msglib` process client, C29 adds bounded `display_entities` extraction, C30 wires it as optional contact/group MCP enrichment, C31 proves real copied-DB MCP enrichment with sanitized output, C32 reuses it for receive-message display fields, C33 proves that receive display path through a real copied-DB MCP smoke without printing entity values, C34 documents the operator setup/verification path, C35 maps MsgLib message tables to receive-message contract fields without reading row values, C36 adds metadata-only `message_sources` readiness, C37 defines the bounded DB-backed receive-source design, C38 implements sidecar/Go-wrapper `list_messages` with sanitized verification, C39 adds a Go adapter from MsgLib list results to the receive-message domain model, and C40 adds explicit tool-level `msglib_readonly` source selection. The next step is env wiring and optional smoke; do not silently merge MsgLib rows into the MCP default.
|
||||||
|
|||||||
@@ -2762,17 +2762,59 @@ DB-backed receive source adapter result:
|
|||||||
- Standard `verify-go-mcp.ps1` should remain deterministic and should not require copied DB env.
|
- Standard `verify-go-mcp.ps1` should remain deterministic and should not require copied DB env.
|
||||||
- Optional copied-DB verification can be added only if its printed output stays sanitized.
|
- Optional copied-DB verification can be added only if its printed output stays sanitized.
|
||||||
|
|
||||||
- [ ] **Step 1: Write source-selection RED test**
|
- [x] **Step 1: Write source-selection RED test**
|
||||||
|
|
||||||
Create a fake primary PacketReader source and fake MsgLib receive source; assert default/auto uses primary and explicit MsgLib selection uses the MsgLib source.
|
Added `TestISphereReceiveMessagesToolSelectsExplicitMsgLibSource` with fake primary and fake MsgLib receive sources. RED failed because `RegisterISphereReadToolsWithReceiveSources` did not exist.
|
||||||
|
|
||||||
- [ ] **Step 2: Implement minimal selector**
|
- [x] **Step 2: Implement minimal selector**
|
||||||
|
|
||||||
Add the smallest selector layer needed by the test. Keep current rejection behavior when explicit MsgLib is requested but not configured.
|
Added `RegisterISphereReadToolsWithReceiveSources` and `receiveMessagesSourceForPreference`. Empty/`auto`/`local_readonly` use the primary source; `msglib_readonly` uses the configured MsgLib receive source; explicit MsgLib is rejected when absent.
|
||||||
|
|
||||||
- [ ] **Step 3: Verify and update operator docs**
|
- [x] **Step 3: Verify and update operator docs**
|
||||||
|
|
||||||
Run full verification and update docs to describe the explicit opt-in route and the unchanged default route.
|
Updated status, matrix, contract, and this plan. Operator runbook/env wiring is deferred to C41 because C40 only proves the tool-layer selector.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Loop C40 Result
|
||||||
|
|
||||||
|
Explicit MsgLib receive source selection result:
|
||||||
|
|
||||||
|
- Added tool-level explicit source selection for `isphere_receive_messages`.
|
||||||
|
- Default/`auto` behavior remains PacketReader/log-backed and does not call the MsgLib receive source.
|
||||||
|
- `source_preference="msglib_readonly"` selects a configured MsgLib receive source and preserves `msglib:<table>` raw refs through the response mapping.
|
||||||
|
- Explicit MsgLib selection is rejected when no MsgLib receive source is configured.
|
||||||
|
- No server env wiring or copied-DB MCP smoke is added in C40.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Loop C41: MsgLib receive env wiring and optional smoke
|
||||||
|
|
||||||
|
**Goal:** Wire the C39 adapter and C40 selector into server env configuration so operators can explicitly call `isphere_receive_messages` with `source_preference="msglib_readonly"`, while keeping default verification deterministic.
|
||||||
|
|
||||||
|
**Planned files:**
|
||||||
|
- Modify: `internal/mcpserver/server.go` and tests.
|
||||||
|
- Modify or extend: `scripts/verify-msglib-mcp-enrichment.ps1` or add a focused optional smoke.
|
||||||
|
- Modify docs/status/matrix/runbook/plan.
|
||||||
|
|
||||||
|
**Planned behavior:**
|
||||||
|
- If full MsgLib env is configured, build both display enrichment source and MsgLib receive source from the same sidecar client.
|
||||||
|
- Empty/`auto` remains PacketReader/log-backed.
|
||||||
|
- Explicit `source_preference="msglib_readonly"` uses `MsgLibMessageSource`.
|
||||||
|
- Standard `scripts/verify-go-mcp.ps1` remains deterministic and should still report `msglib_configured=false`.
|
||||||
|
- Optional copied-DB smoke must print only counts/source refs/booleans and must not print message body values, file paths, raw rows, entity values, sends, downloads, hooks, injection, or DB writes.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write server wiring RED test**
|
||||||
|
|
||||||
|
Use injected/fake sources or env-shaped construction to prove the server can register both primary and MsgLib receive sources while default calls stay on primary.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Implement env wiring**
|
||||||
|
|
||||||
|
Reuse `msglib.ConfigFromEnv`/client construction and pass `isphere.MsgLibMessageSource` into the C40 selector. Keep partial-env failure behavior.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add optional sanitized smoke and docs**
|
||||||
|
|
||||||
|
Extend optional verification to call explicit `msglib_readonly` only when copied DB env is present, printing sanitized result only.
|
||||||
|
|
||||||
---
|
---
|
||||||
## Self-Review
|
## Self-Review
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ func RegisterISphereReadTools(server *mcp.Server, source ReceiveMessagesSource)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RegisterISphereReadToolsWithDisplayEntities(server *mcp.Server, source ReceiveMessagesSource, displaySource DisplayEntitySource) {
|
func RegisterISphereReadToolsWithDisplayEntities(server *mcp.Server, source ReceiveMessagesSource, displaySource DisplayEntitySource) {
|
||||||
|
RegisterISphereReadToolsWithReceiveSources(server, source, nil, displaySource)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterISphereReadToolsWithReceiveSources(server *mcp.Server, source ReceiveMessagesSource, msglibSource ReceiveMessagesSource, displaySource DisplayEntitySource) {
|
||||||
if source == nil {
|
if source == nil {
|
||||||
source = isphere.EncryptedPacketLogSource{}
|
source = isphere.EncryptedPacketLogSource{}
|
||||||
}
|
}
|
||||||
@@ -43,11 +47,15 @@ func RegisterISphereReadToolsWithDisplayEntities(server *mcp.Server, source Rece
|
|||||||
Name: ToolNameReceiveMessages,
|
Name: ToolNameReceiveMessages,
|
||||||
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) {
|
||||||
if err := validateReceiveMessagesArgs(input); err != nil {
|
if err := validateReceiveMessagesCursor(input); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
selectedSource, err := receiveMessagesSourceForPreference(input.SourcePreference, source, msglibSource)
|
||||||
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
started := time.Now().UTC()
|
started := time.Now().UTC()
|
||||||
result, err := source.ReceiveMessages(ctx, isphere.ReceiveMessagesQuery{
|
result, err := selectedSource.ReceiveMessages(ctx, isphere.ReceiveMessagesQuery{
|
||||||
ConversationID: input.ConversationID,
|
ConversationID: input.ConversationID,
|
||||||
Query: input.Query,
|
Query: input.Query,
|
||||||
Since: input.Since,
|
Since: input.Since,
|
||||||
@@ -71,8 +79,26 @@ func RegisterISphereReadToolsWithDisplayEntities(server *mcp.Server, source Rece
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateReceiveMessagesArgs(input ReceiveMessagesArgs) error {
|
func validateReceiveMessagesCursor(input ReceiveMessagesArgs) error {
|
||||||
return validateLocalReadonlySourceAndCursor(ToolNameReceiveMessages, input.SourcePreference, input.Cursor)
|
if strings.TrimSpace(input.Cursor) != "" {
|
||||||
|
return fmt.Errorf("%s cursor pagination is not available yet", ToolNameReceiveMessages)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveMessagesSourceForPreference(sourcePreferenceValue string, primarySource ReceiveMessagesSource, msglibSource ReceiveMessagesSource) (ReceiveMessagesSource, error) {
|
||||||
|
sourcePreference := strings.ToLower(strings.TrimSpace(sourcePreferenceValue))
|
||||||
|
switch sourcePreference {
|
||||||
|
case "", "auto", "local_readonly":
|
||||||
|
return primarySource, nil
|
||||||
|
case "msglib_readonly":
|
||||||
|
if msglibSource == nil {
|
||||||
|
return nil, fmt.Errorf("%s source_preference %q is not configured", ToolNameReceiveMessages, sourcePreferenceValue)
|
||||||
|
}
|
||||||
|
return msglibSource, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%s source_preference %q is not available; only auto, local_readonly, and msglib_readonly are supported", ToolNameReceiveMessages, sourcePreferenceValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateLocalReadonlySourceAndCursor(toolName string, sourcePreferenceValue string, cursorValue string) error {
|
func validateLocalReadonlySourceAndCursor(toolName string, sourcePreferenceValue string, cursorValue string) error {
|
||||||
|
|||||||
@@ -213,6 +213,79 @@ func TestISphereReceiveMessagesToolUsesInjectedDisplayEntities(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestISphereReceiveMessagesToolSelectsExplicitMsgLibSource(t *testing.T) {
|
||||||
|
primary := &fakeReceiveMessagesSource{
|
||||||
|
result: isphere.ReceiveMessagesResult{Messages: []isphere.Message{{
|
||||||
|
ID: "packet-msg-1",
|
||||||
|
ConversationID: "packet-conversation",
|
||||||
|
ConversationType: "chat",
|
||||||
|
SenderID: "packet-sender",
|
||||||
|
ReceiverID: "packet-receiver",
|
||||||
|
Text: "packet body",
|
||||||
|
Timestamp: "1783423807000",
|
||||||
|
}}},
|
||||||
|
}
|
||||||
|
msglibSource := &fakeReceiveMessagesSource{
|
||||||
|
result: isphere.ReceiveMessagesResult{Messages: []isphere.Message{{
|
||||||
|
ID: "db-msg-1",
|
||||||
|
ConversationID: "db-conversation",
|
||||||
|
ConversationType: "group",
|
||||||
|
SenderID: "db-sender",
|
||||||
|
ReceiverID: "",
|
||||||
|
Text: "db body",
|
||||||
|
Timestamp: "1783423808000",
|
||||||
|
Source: "local_readonly",
|
||||||
|
RawRef: "msglib:tblMsgGroupPersonMsg",
|
||||||
|
}}},
|
||||||
|
}
|
||||||
|
session, cleanup := connectToolsTestSession(t, func(server *mcp.Server) {
|
||||||
|
RegisterISphereReadToolsWithReceiveSources(server, primary, msglibSource, nil)
|
||||||
|
})
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
defaultResult, err := session.CallTool(context.Background(), &mcp.CallToolParams{
|
||||||
|
Name: ToolNameReceiveMessages,
|
||||||
|
Arguments: map[string]any{"limit": 5},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("default call %s: %v", ToolNameReceiveMessages, err)
|
||||||
|
}
|
||||||
|
defaultPayload := decodeReceiveMessagesForSelectionTest(t, defaultResult.StructuredContent)
|
||||||
|
if len(defaultPayload.Messages) != 1 || defaultPayload.Messages[0].ID != "packet-msg-1" {
|
||||||
|
t.Fatalf("default messages = %+v, want packet source", defaultPayload.Messages)
|
||||||
|
}
|
||||||
|
if len(primary.queries) != 1 || len(msglibSource.queries) != 0 {
|
||||||
|
t.Fatalf("default source calls primary=%d msglib=%d", len(primary.queries), len(msglibSource.queries))
|
||||||
|
}
|
||||||
|
|
||||||
|
dbResult, err := session.CallTool(context.Background(), &mcp.CallToolParams{
|
||||||
|
Name: ToolNameReceiveMessages,
|
||||||
|
Arguments: map[string]any{"source_preference": "msglib_readonly", "query": "db", "limit": 3},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("msglib call %s: %v", ToolNameReceiveMessages, err)
|
||||||
|
}
|
||||||
|
dbPayload := decodeReceiveMessagesForSelectionTest(t, dbResult.StructuredContent)
|
||||||
|
if len(dbPayload.Messages) != 1 || dbPayload.Messages[0].ID != "db-msg-1" || dbPayload.Messages[0].RawRef != "msglib:tblMsgGroupPersonMsg" {
|
||||||
|
t.Fatalf("db messages = %+v, want explicit msglib source", dbPayload.Messages)
|
||||||
|
}
|
||||||
|
if len(msglibSource.queries) != 1 || msglibSource.queries[0].Query != "db" || msglibSource.queries[0].Limit != 3 {
|
||||||
|
t.Fatalf("msglib source queries = %+v", msglibSource.queries)
|
||||||
|
}
|
||||||
|
|
||||||
|
missingMsgLibSession, missingCleanup := connectToolsTestSession(t, func(server *mcp.Server) {
|
||||||
|
RegisterISphereReadToolsWithReceiveSources(server, primary, nil, nil)
|
||||||
|
})
|
||||||
|
defer missingCleanup()
|
||||||
|
missingResult, err := missingMsgLibSession.CallTool(context.Background(), &mcp.CallToolParams{
|
||||||
|
Name: ToolNameReceiveMessages,
|
||||||
|
Arguments: map[string]any{"source_preference": "msglib_readonly", "limit": 1},
|
||||||
|
})
|
||||||
|
if err == nil && !missingResult.IsError {
|
||||||
|
t.Fatalf("explicit msglib source was accepted without a configured MsgLib receive source")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestISphereReceiveMessagesToolValidatesRemainingContractArgs(t *testing.T) {
|
func TestISphereReceiveMessagesToolValidatesRemainingContractArgs(t *testing.T) {
|
||||||
fake := &fakeReceiveMessagesSource{
|
fake := &fakeReceiveMessagesSource{
|
||||||
result: isphere.ReceiveMessagesResult{Messages: []isphere.Message{{
|
result: isphere.ReceiveMessagesResult{Messages: []isphere.Message{{
|
||||||
@@ -282,6 +355,29 @@ func TestISphereReceiveMessagesToolValidatesRemainingContractArgs(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeReceiveMessagesForSelectionTest(t *testing.T, structured any) struct {
|
||||||
|
Messages []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
RawRef string `json:"raw_ref"`
|
||||||
|
} `json:"messages"`
|
||||||
|
} {
|
||||||
|
t.Helper()
|
||||||
|
var decoded struct {
|
||||||
|
Messages []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
RawRef string `json:"raw_ref"`
|
||||||
|
} `json:"messages"`
|
||||||
|
}
|
||||||
|
payload, err := json.Marshal(structured)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal structured content: %v", err)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(payload, &decoded); err != nil {
|
||||||
|
t.Fatalf("decode structured content %s: %v", payload, err)
|
||||||
|
}
|
||||||
|
return decoded
|
||||||
|
}
|
||||||
|
|
||||||
type fakeReceiveMessagesSource struct {
|
type fakeReceiveMessagesSource struct {
|
||||||
queries []isphere.ReceiveMessagesQuery
|
queries []isphere.ReceiveMessagesQuery
|
||||||
result isphere.ReceiveMessagesResult
|
result isphere.ReceiveMessagesResult
|
||||||
|
|||||||
Reference in New Issue
Block a user