feat: add send message preview contract

This commit is contained in:
zhaoyilun
2026-07-10 14:04:33 +08:00
parent 1f99681348
commit ac3f10f53c
12 changed files with 686 additions and 55 deletions

View File

@@ -10,7 +10,8 @@ $expectedTools = @(
"isphere_receive_messages",
"isphere_search_contacts",
"isphere_search_groups",
"isphere_receive_files"
"isphere_receive_files",
"isphere_send_message"
)
$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("isphere-go-mcp-verify-" + [guid]::NewGuid().ToString("N"))
@@ -62,7 +63,9 @@ import (
"context"
"crypto/cipher"
"crypto/des"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"os"
@@ -86,9 +89,10 @@ var expectedTools = []string{
"isphere_search_contacts",
"isphere_search_groups",
"isphere_receive_files",
"isphere_send_message",
}
var forbiddenTerms = []string{"send", "login", "conversation", "upload", "download", "autologin"}
var forbiddenTerms = []string{"login", "upload", "autologin"}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
@@ -143,7 +147,7 @@ func main() {
sort.Strings(toolNames)
want := append([]string(nil), expectedTools...)
sort.Strings(want)
if len(toolNames) != 8 || !reflect.DeepEqual(toolNames, want) {
if len(toolNames) != 9 || !reflect.DeepEqual(toolNames, want) {
fail("tools/list", fmt.Errorf("tool names = %#v, want %#v", toolNames, want))
}
@@ -205,6 +209,7 @@ func main() {
defaultContactCount := verifySearchContacts(ctx, session, "sender", "tools/call isphere_search_contacts default", 0)
defaultGroupCount := verifySearchGroups(ctx, session, "project", "tools/call isphere_search_groups default", 0)
defaultFileCount := verifyReceiveFiles(ctx, session, "report", "tools/call isphere_receive_files default", 0, "", "")
sendPreviewOK, productionSendEnabled := verifySendMessagePreview(ctx, session)
configuredReceiveCount, configuredContactCount, configuredGroupCount, configuredFileCount := verifyConfiguredReceiveSource(ctx)
configuredDirReceiveCount, configuredDirContactCount, configuredDirGroupCount, configuredDirFileCount := verifyConfiguredDirectorySource(ctx)
@@ -232,6 +237,8 @@ func main() {
"real_isphere_login_required": false,
"python_runtime_required": false,
"action_tools_present": false,
"send_preview_tool_present": sendPreviewOK,
"production_send_enabled": productionSendEnabled,
"msglib_configured": false,
}
encoded, _ := json.Marshal(result)
@@ -485,6 +492,72 @@ func verifyConfiguredDirectorySource(ctx context.Context) (int, int, int, int) {
return len(messagesValue), contactCount, groupCount, fileCount
}
func verifySendMessagePreview(ctx context.Context, session *mcp.ClientSession) (bool, bool) {
content := "redacted preview body"
contentHash := sha256HexForHarness(content)
callResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_send_message", Arguments: map[string]any{
"target_type": "direct",
"target_id": "sender@imopenfire1-lanzhou",
"content_text": content,
"content_sha256": contentHash,
"idempotency_key": "verify-preview-idempotency-key",
"execution_mode": "preview",
}})
if err != nil {
fail("tools/call isphere_send_message preview", err)
}
if callResult.IsError {
fail("tools/call isphere_send_message preview", fmt.Errorf("tool returned isError=true: %#v", callResult.Content))
}
var previewPayload map[string]any
encoded, _ := json.Marshal(callResult.StructuredContent)
if err := json.Unmarshal(encoded, &previewPayload); err != nil {
fail("tools/call isphere_send_message preview", fmt.Errorf("decode structuredContent %s: %w", encoded, err))
}
if previewPayload["ok"] != true || previewPayload["send_status"] != "planned" || previewPayload["connector"] != "implatform-sidecar" || previewPayload["connector_stage"] != "preview" {
fail("tools/call isphere_send_message preview", fmt.Errorf("unexpected preview status: %s", encoded))
}
contentValue, ok := previewPayload["content"].(map[string]any)
if !ok || contentValue["content_sha256"] != contentHash {
fail("tools/call isphere_send_message preview", fmt.Errorf("unexpected content metadata: %s", encoded))
}
if _, exists := contentValue["content_text"]; exists {
fail("tools/call isphere_send_message preview", fmt.Errorf("preview leaked raw content: %s", encoded))
}
sideEffects, ok := previewPayload["side_effects"].(map[string]any)
if !ok || sideEffects["sent_message"] != false || sideEffects["clicked_ui"] != false || sideEffects["captured_network"] != false {
fail("tools/call isphere_send_message preview", fmt.Errorf("unexpected side effects: %s", encoded))
}
productionResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_send_message", Arguments: map[string]any{
"target_type": "direct",
"target_id": "sender@imopenfire1-lanzhou",
"content_text": content,
"content_sha256": contentHash,
"idempotency_key": "verify-production-idempotency-key",
"execution_mode": "production",
}})
if err != nil {
fail("tools/call isphere_send_message production_blocked", err)
}
if productionResult.IsError {
fail("tools/call isphere_send_message production_blocked", fmt.Errorf("tool returned isError=true: %#v", productionResult.Content))
}
var productionPayload map[string]any
productionEncoded, _ := json.Marshal(productionResult.StructuredContent)
if err := json.Unmarshal(productionEncoded, &productionPayload); err != nil {
fail("tools/call isphere_send_message production_blocked", fmt.Errorf("decode structuredContent %s: %w", productionEncoded, err))
}
if productionPayload["ok"] != false || productionPayload["send_status"] != "blocked" || productionPayload["connector_stage"] != "blocked" {
fail("tools/call isphere_send_message production_blocked", fmt.Errorf("production was not blocked: %s", productionEncoded))
}
productionSideEffects, ok := productionPayload["side_effects"].(map[string]any)
if !ok || productionSideEffects["sent_message"] != false || productionSideEffects["typed_text"] != false || productionSideEffects["uploaded_file"] != false {
fail("tools/call isphere_send_message production_blocked", fmt.Errorf("unexpected production side effects: %s", productionEncoded))
}
return true, false
}
func verifySearchContacts(ctx context.Context, session *mcp.ClientSession, query string, step string, wantCount int) int {
callResult, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "isphere_search_contacts", Arguments: map[string]any{
"query": query,
@@ -627,6 +700,11 @@ func encryptPacketLogLineForHarness(plaintext string) (string, error) {
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
func sha256HexForHarness(value string) string {
sum := sha256.Sum256([]byte(value))
return hex.EncodeToString(sum[:])
}
func padPKCS7ForHarness(data []byte, blockSize int) []byte {
pad := blockSize - len(data)%blockSize
out := append([]byte(nil), data...)
@@ -663,7 +741,7 @@ func fail(step string, err error) {
Assert-True ($script:harnessJson.ok -eq $true) "SDK harness did not return ok=true"
Assert-True ($script:harnessJson.helper_name -eq "ISphereWinHelper") "SDK harness helper_name mismatch: $($script:harnessJson.helper_name)"
Assert-True ($script:harnessJson.tool_count -eq 8) "SDK harness tool_count mismatch: $($script:harnessJson.tool_count)"
Assert-True ($script:harnessJson.tool_count -eq 9) "SDK harness tool_count mismatch: $($script:harnessJson.tool_count)"
Assert-True ($script:harnessJson.receive_message_count -eq 0) "SDK harness receive_message_count mismatch: $($script:harnessJson.receive_message_count)"
Assert-True ($script:harnessJson.contact_count -eq 0) "SDK harness contact_count mismatch: $($script:harnessJson.contact_count)"
Assert-True ($script:harnessJson.group_count -eq 0) "SDK harness group_count mismatch: $($script:harnessJson.group_count)"
@@ -676,6 +754,8 @@ func fail(step string, err error) {
Assert-True ($script:harnessJson.configured_dir_contact_count -eq 1) "SDK harness configured_dir_contact_count mismatch: $($script:harnessJson.configured_dir_contact_count)"
Assert-True ($script:harnessJson.configured_dir_group_count -eq 1) "SDK harness configured_dir_group_count mismatch: $($script:harnessJson.configured_dir_group_count)"
Assert-True ($script:harnessJson.configured_dir_file_count -eq 1) "SDK harness configured_dir_file_count mismatch: $($script:harnessJson.configured_dir_file_count)"
Assert-True ($script:harnessJson.send_preview_tool_present -eq $true) "SDK harness send_preview_tool_present mismatch: $($script:harnessJson.send_preview_tool_present)"
Assert-True ($script:harnessJson.production_send_enabled -eq $false) "SDK harness production_send_enabled mismatch: $($script:harnessJson.production_send_enabled)"
[ordered]@{
ok = $true
@@ -696,6 +776,8 @@ func fail(step string, err error) {
configured_dir_contact_count = $script:harnessJson.configured_dir_contact_count
configured_dir_group_count = $script:harnessJson.configured_dir_group_count
configured_dir_file_count = $script:harnessJson.configured_dir_file_count
send_preview_tool_present = $script:harnessJson.send_preview_tool_present
production_send_enabled = $script:harnessJson.production_send_enabled
packet_log_file_configured = $script:harnessJson.packet_log_file_configured
packet_log_dir_configured = $script:harnessJson.packet_log_dir_configured
python_runtime_required = $false