diff --git a/Clawleash.Abstractions/README-en.md b/Clawleash.Abstractions/README-en.md new file mode 100644 index 0000000..721a0e6 --- /dev/null +++ b/Clawleash.Abstractions/README-en.md @@ -0,0 +1,433 @@ +# Clawleash.Abstractions + +A library defining shared interfaces and abstractions for Clawleash. Provides the foundation for creating custom chat interface providers. + +## Overview + +This library provides: + +- **IChatInterface**: Chat interface abstraction +- **ChatMessageReceivedEventArgs**: Message received event arguments +- **IStreamingMessageWriter**: For streaming message sending +- **ChatInterfaceSettingsBase**: Base class for settings +- **IE2eeProvider**: E2EE encryption provider + +--- + +## Creating Custom Providers + +### 1. Project Creation + +Create a new class library project and reference `Clawleash.Abstractions`: + +```xml + + + net10.0 + + + + + + +``` + +### 2. Creating Settings Class + +Create a settings class inheriting from `ChatInterfaceSettingsBase`: + +```csharp +using Clawleash.Abstractions.Configuration; + +namespace Clawleash.Interfaces.MyProvider; + +public class MyProviderSettings : ChatInterfaceSettingsBase +{ + /// + /// Server URL to connect to + /// + public string ServerUrl { get; set; } = "https://api.example.com"; + + /// + /// API Key + /// + public string ApiKey { get; set; } = string.Empty; + + /// + /// Reconnection interval (milliseconds) + /// + public int ReconnectIntervalMs { get; set; } = 5000; +} +``` + +### 3. Implementing IChatInterface + +```csharp +using System.Text; +using Clawleash.Abstractions.Services; +using Microsoft.Extensions.Logging; + +namespace Clawleash.Interfaces.MyProvider; + +public class MyProviderChatInterface : IChatInterface +{ + private readonly MyProviderSettings _settings; + private readonly ILogger? _logger; + private bool _isConnected; + private bool _disposed; + + // Required properties + public string Name => "MyProvider"; + public bool IsConnected => _isConnected; + + // Required event + public event EventHandler? MessageReceived; + + public MyProviderChatInterface( + MyProviderSettings settings, + ILogger? logger = null) + { + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); + _logger = logger; + } + + // Required method: Start interface + public async Task StartAsync(CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(_settings.ApiKey)) + { + _logger?.LogWarning("API key is not configured"); + return; + } + + try + { + // Implement connection logic + await ConnectAsync(cancellationToken); + _isConnected = true; + _logger?.LogInformation("{Name} connected", Name); + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to start {Name}", Name); + throw; + } + } + + // Required method: Stop interface + public async Task StopAsync(CancellationToken cancellationToken = default) + { + _isConnected = false; + // Implement disconnection logic + await Task.CompletedTask; + _logger?.LogInformation("{Name} stopped", Name); + } + + // Required method: Send message + public async Task SendMessageAsync( + string message, + string? replyToMessageId = null, + CancellationToken cancellationToken = default) + { + if (!IsConnected) + { + _logger?.LogWarning("{Name} is not connected", Name); + return; + } + + // Implement message sending logic + // If replyToMessageId is present, send as a reply + await Task.CompletedTask; + } + + // Required method: Start streaming message + public IStreamingMessageWriter StartStreamingMessage(CancellationToken cancellationToken = default) + { + return new MyStreamingWriter(this); + } + + // Required method: Dispose resources + public async ValueTask DisposeAsync() + { + if (_disposed) return; + _disposed = true; + + await StopAsync(); + } + + // Internal method: Call when message is received + protected virtual void OnMessageReceived(string content, string senderId, string senderName) + { + var args = new ChatMessageReceivedEventArgs + { + MessageId = Guid.NewGuid().ToString(), + SenderId = senderId, + SenderName = senderName, + Content = content, + ChannelId = "default", + Timestamp = DateTime.UtcNow, + InterfaceName = Name, + RequiresReply = true, + Metadata = new Dictionary + { + // Provider-specific metadata + ["custom_field"] = "value" + } + }; + + MessageReceived?.Invoke(this, args); + } + + private async Task ConnectAsync(CancellationToken ct) + { + // Actual connection logic + await Task.CompletedTask; + } +} + +// Streaming writer implementation +internal class MyStreamingWriter : IStreamingMessageWriter +{ + private readonly StringBuilder _content = new(); + private readonly MyProviderChatInterface _interface; + + public string MessageId { get; } = Guid.NewGuid().ToString(); + + public MyStreamingWriter(MyProviderChatInterface iface) + { + _interface = iface; + } + + public Task AppendTextAsync(string text, CancellationToken cancellationToken = default) + { + _content.Append(text); + return Task.CompletedTask; + } + + public async Task CompleteAsync(CancellationToken cancellationToken = default) + { + var message = _content.ToString(); + if (!string.IsNullOrEmpty(message)) + { + await _interface.SendMessageAsync(message, null, cancellationToken); + } + } + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; +} +``` + +--- + +## Provider Deployment + +### Build + +```bash +cd Clawleash.Interfaces.MyProvider +dotnet build -c Release +``` + +### Installation + +Copy DLL and dependencies to the interfaces directory: + +``` +%LocalAppData%\Clawleash\Interfaces\MyProvider\ +├── Clawleash.Interfaces.MyProvider.dll +├── Clawleash.Abstractions.dll +└── (other dependency DLLs) +``` + +### Auto-Recognition + +When files are placed while the application is running, they are automatically loaded (when hot-reload is enabled). + +--- + +## Settings Integration + +### Adding to appsettings.json + +```json +{ + "ChatInterface": { + "EnableHotReload": true, + "MyProvider": { + "Enabled": true, + "ServerUrl": "https://api.example.com", + "ApiKey": "${MY_PROVIDER_API_KEY}", + "EnableE2ee": false + } + } +} +``` + +### DI Registration + +Providers are instantiated using `ActivatorUtilities.CreateInstance`, so register settings and logger in DI: + +```csharp +// Register settings in Program.cs +services.Configure( + configuration.GetSection("ChatInterface:MyProvider")); +``` + +--- + +## ChatMessageReceivedEventArgs Properties + +| Property | Type | Description | +|-----------|-----|------| +| `MessageId` | `string` | Unique message identifier | +| `SenderId` | `string` | Unique sender identifier | +| `SenderName` | `string` | Sender display name | +| `Content` | `string` | Message content | +| `ChannelId` | `string` | Channel/Room ID | +| `Timestamp` | `DateTime` | Message timestamp (UTC) | +| `ReplyToMessageId` | `string?` | Reply target message ID | +| `RequiresReply` | `bool` | Whether reply is required | +| `InterfaceName` | `string` | Interface name | +| `Metadata` | `Dictionary` | Provider-specific metadata | + +--- + +## IChatInterface Members + +### Properties + +| Property | Type | Description | +|-----------|-----|------| +| `Name` | `string` | Interface name (unique) | +| `IsConnected` | `bool` | Connection state | + +### Events + +| Event | Description | +|----------|------| +| `MessageReceived` | Raised when message is received | + +### Methods + +| Method | Description | +|----------|------| +| `StartAsync(CancellationToken)` | Start interface | +| `StopAsync(CancellationToken)` | Stop interface | +| `SendMessageAsync(message, replyToMessageId?, CancellationToken)` | Send message | +| `StartStreamingMessage(CancellationToken)` | Start streaming message | +| `DisposeAsync()` | Release resources | + +--- + +## E2EE Support + +To support E2EE (End-to-End Encryption): + +### 1. Using IE2eeProvider + +```csharp +using Clawleash.Abstractions.Security; + +public class MySecureProvider : IChatInterface +{ + private readonly IE2eeProvider _e2eeProvider; + + public MySecureProvider(MyProviderSettings settings, IE2eeProvider e2eeProvider) + { + _e2eeProvider = e2eeProvider; + } + + public async Task SendMessageAsync(string message, ...) + { + if (_settings.EnableE2ee && _e2eeProvider.IsEncrypted) + { + var ciphertext = await _e2eeProvider.EncryptAsync(message, channelId); + // Send encrypted data + } + else + { + // Send plaintext + } + } +} +``` + +### 2. Enable in Settings + +```json +{ + "ChatInterface": { + "MyProvider": { + "Enabled": true, + "EnableE2ee": true + } + } +} +``` + +--- + +## Best Practices + +### Error Handling + +```csharp +public async Task StartAsync(CancellationToken cancellationToken = default) +{ + try + { + await ConnectAsync(cancellationToken); + } + catch (Exception ex) + { + _logger?.LogError(ex, "Connection failed"); + // Update connection state + _isConnected = false; + throw; + } +} +``` + +### Logging + +```csharp +// Use structured logging +_logger?.LogInformation("{Name} starting with server: {ServerUrl}", Name, _settings.ServerUrl); +_logger?.LogDebug("Message received: {MessageId} from {SenderName}", args.MessageId, args.SenderName); +``` + +### Resource Management + +```csharp +public async ValueTask DisposeAsync() +{ + if (_disposed) return; + _disposed = true; + + // Unsubscribe from events + // _client.OnMessage -= OnMessage; + + // Disconnect + await StopAsync(); + + // Release other resources + _httpClient?.Dispose(); +} +``` + +--- + +## Reference Implementations + +For implementation reference, see these providers: + +- [Clawleash.Interfaces.Discord](../Clawleash.Interfaces.Discord/README-en.md) - Discord Bot +- [Clawleash.Interfaces.Slack](../Clawleash.Interfaces.Slack/README-en.md) - Slack Bot +- [Clawleash.Interfaces.WebSocket](../Clawleash.Interfaces.WebSocket/README-en.md) - WebSocket (E2EE) +- [Clawleash.Interfaces.WebRTC](../Clawleash.Interfaces.WebRTC/README-en.md) - WebRTC (E2EE) + +--- + +## License + +MIT diff --git a/Clawleash.Abstractions/README.md b/Clawleash.Abstractions/README.md new file mode 100644 index 0000000..e92efca --- /dev/null +++ b/Clawleash.Abstractions/README.md @@ -0,0 +1,433 @@ +# Clawleash.Abstractions + +Clawleash の共有インターフェースと抽象化を定義するライブラリ。カスタムチャットインターフェースプロバイダーを作成するための基盤を提供します。 + +## 概要 + +このライブラリは以下を提供します: + +- **IChatInterface**: チャットインターフェースの抽象化 +- **ChatMessageReceivedEventArgs**: メッセージ受信イベントの引数 +- **IStreamingMessageWriter**: ストリーミングメッセージ送信用 +- **ChatInterfaceSettingsBase**: 設定のベースクラス +- **IE2eeProvider**: E2EE 暗号化プロバイダー + +--- + +## カスタムプロバイダーの作成 + +### 1. プロジェクト作成 + +新しいクラスライブラリプロジェクトを作成し、`Clawleash.Abstractions` を参照します: + +```xml + + + net10.0 + + + + + + +``` + +### 2. 設定クラスの作成 + +`ChatInterfaceSettingsBase` を継承して設定クラスを作成します: + +```csharp +using Clawleash.Abstractions.Configuration; + +namespace Clawleash.Interfaces.MyProvider; + +public class MyProviderSettings : ChatInterfaceSettingsBase +{ + /// + /// 接続先のサーバーURL + /// + public string ServerUrl { get; set; } = "https://api.example.com"; + + /// + /// API キー + /// + public string ApiKey { get; set; } = string.Empty; + + /// + /// 再接続間隔(ミリ秒) + /// + public int ReconnectIntervalMs { get; set; } = 5000; +} +``` + +### 3. IChatInterface の実装 + +```csharp +using System.Text; +using Clawleash.Abstractions.Services; +using Microsoft.Extensions.Logging; + +namespace Clawleash.Interfaces.MyProvider; + +public class MyProviderChatInterface : IChatInterface +{ + private readonly MyProviderSettings _settings; + private readonly ILogger? _logger; + private bool _isConnected; + private bool _disposed; + + // 必須プロパティ + public string Name => "MyProvider"; + public bool IsConnected => _isConnected; + + // 必須イベント + public event EventHandler? MessageReceived; + + public MyProviderChatInterface( + MyProviderSettings settings, + ILogger? logger = null) + { + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); + _logger = logger; + } + + // 必須メソッド: インターフェース開始 + public async Task StartAsync(CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(_settings.ApiKey)) + { + _logger?.LogWarning("API key is not configured"); + return; + } + + try + { + // 接続処理を実装 + await ConnectAsync(cancellationToken); + _isConnected = true; + _logger?.LogInformation("{Name} connected", Name); + } + catch (Exception ex) + { + _logger?.LogError(ex, "Failed to start {Name}", Name); + throw; + } + } + + // 必須メソッド: インターフェース停止 + public async Task StopAsync(CancellationToken cancellationToken = default) + { + _isConnected = false; + // 切断処理を実装 + await Task.CompletedTask; + _logger?.LogInformation("{Name} stopped", Name); + } + + // 必須メソッド: メッセージ送信 + public async Task SendMessageAsync( + string message, + string? replyToMessageId = null, + CancellationToken cancellationToken = default) + { + if (!IsConnected) + { + _logger?.LogWarning("{Name} is not connected", Name); + return; + } + + // メッセージ送信処理を実装 + // replyToMessageId がある場合は返信として送信 + await Task.CompletedTask; + } + + // 必須メソッド: ストリーミング送信 + public IStreamingMessageWriter StartStreamingMessage(CancellationToken cancellationToken = default) + { + return new MyStreamingWriter(this); + } + + // 必須メソッド: リソース解放 + public async ValueTask DisposeAsync() + { + if (_disposed) return; + _disposed = true; + + await StopAsync(); + } + + // 内部メソッド: メッセージ受信時に呼び出す + protected virtual void OnMessageReceived(string content, string senderId, string senderName) + { + var args = new ChatMessageReceivedEventArgs + { + MessageId = Guid.NewGuid().ToString(), + SenderId = senderId, + SenderName = senderName, + Content = content, + ChannelId = "default", + Timestamp = DateTime.UtcNow, + InterfaceName = Name, + RequiresReply = true, + Metadata = new Dictionary + { + // プロバイダー固有のメタデータ + ["custom_field"] = "value" + } + }; + + MessageReceived?.Invoke(this, args); + } + + private async Task ConnectAsync(CancellationToken ct) + { + // 実際の接続処理 + await Task.CompletedTask; + } +} + +// ストリーミングライターの実装 +internal class MyStreamingWriter : IStreamingMessageWriter +{ + private readonly StringBuilder _content = new(); + private readonly MyProviderChatInterface _interface; + + public string MessageId { get; } = Guid.NewGuid().ToString(); + + public MyStreamingWriter(MyProviderChatInterface iface) + { + _interface = iface; + } + + public Task AppendTextAsync(string text, CancellationToken cancellationToken = default) + { + _content.Append(text); + return Task.CompletedTask; + } + + public async Task CompleteAsync(CancellationToken cancellationToken = default) + { + var message = _content.ToString(); + if (!string.IsNullOrEmpty(message)) + { + await _interface.SendMessageAsync(message, null, cancellationToken); + } + } + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; +} +``` + +--- + +## プロバイダーのデプロイ + +### ビルド + +```bash +cd Clawleash.Interfaces.MyProvider +dotnet build -c Release +``` + +### 配置 + +DLL と依存ファイルをインターフェースディレクトリにコピー: + +``` +%LocalAppData%\Clawleash\Interfaces\MyProvider\ +├── Clawleash.Interfaces.MyProvider.dll +├── Clawleash.Abstractions.dll +└── (その他の依存DLL) +``` + +### 自動認識 + +アプリケーション実行中にファイルを配置すると、自動的にロードされます(ホットリロード有効時)。 + +--- + +## 設定の統合 + +### appsettings.json への追加 + +```json +{ + "ChatInterface": { + "EnableHotReload": true, + "MyProvider": { + "Enabled": true, + "ServerUrl": "https://api.example.com", + "ApiKey": "${MY_PROVIDER_API_KEY}", + "EnableE2ee": false + } + } +} +``` + +### DI への登録 + +プロバイダーは `ActivatorUtilities.CreateInstance` でインスタンス化されるため、設定とロガーを DI に登録します: + +```csharp +// Program.cs で設定を登録 +services.Configure( + configuration.GetSection("ChatInterface:MyProvider")); +``` + +--- + +## ChatMessageReceivedEventArgs プロパティ + +| プロパティ | 型 | 説明 | +|-----------|-----|------| +| `MessageId` | `string` | メッセージの一意識別子 | +| `SenderId` | `string` | 送信者の一意識別子 | +| `SenderName` | `string` | 送信者の表示名 | +| `Content` | `string` | メッセージの内容 | +| `ChannelId` | `string` | チャンネル/ルーム ID | +| `Timestamp` | `DateTime` | メッセージのタイムスタンプ (UTC) | +| `ReplyToMessageId` | `string?` | 返信先メッセージ ID | +| `RequiresReply` | `bool` | 返信が必要かどうか | +| `InterfaceName` | `string` | インターフェース名 | +| `Metadata` | `Dictionary` | プロバイダー固有のメタデータ | + +--- + +## IChatInterface メンバー一覧 + +### プロパティ + +| プロパティ | 型 | 説明 | +|-----------|-----|------| +| `Name` | `string` | インターフェース名(一意) | +| `IsConnected` | `bool` | 接続状態 | + +### イベント + +| イベント | 説明 | +|----------|------| +| `MessageReceived` | メッセージ受信時に発生 | + +### メソッド + +| メソッド | 説明 | +|----------|------| +| `StartAsync(CancellationToken)` | インターフェース開始 | +| `StopAsync(CancellationToken)` | インターフェース停止 | +| `SendMessageAsync(message, replyToMessageId?, CancellationToken)` | メッセージ送信 | +| `StartStreamingMessage(CancellationToken)` | ストリーミング送信開始 | +| `DisposeAsync()` | リソース解放 | + +--- + +## E2EE 対応 + +E2EE(エンドツーエンド暗号化)をサポートする場合: + +### 1. IE2eeProvider の使用 + +```csharp +using Clawleash.Abstractions.Security; + +public class MySecureProvider : IChatInterface +{ + private readonly IE2eeProvider _e2eeProvider; + + public MySecureProvider(MyProviderSettings settings, IE2eeProvider e2eeProvider) + { + _e2eeProvider = e2eeProvider; + } + + public async Task SendMessageAsync(string message, ...) + { + if (_settings.EnableE2ee && _e2eeProvider.IsEncrypted) + { + var ciphertext = await _e2eeProvider.EncryptAsync(message, channelId); + // 暗号化されたデータを送信 + } + else + { + // 平文で送信 + } + } +} +``` + +### 2. 設定での有効化 + +```json +{ + "ChatInterface": { + "MyProvider": { + "Enabled": true, + "EnableE2ee": true + } + } +} +``` + +--- + +## ベストプラクティス + +### エラーハンドリング + +```csharp +public async Task StartAsync(CancellationToken cancellationToken = default) +{ + try + { + await ConnectAsync(cancellationToken); + } + catch (Exception ex) + { + _logger?.LogError(ex, "Connection failed"); + // 接続状態を更新 + _isConnected = false; + throw; + } +} +``` + +### ログ出力 + +```csharp +// 構造化ログを使用 +_logger?.LogInformation("{Name} starting with server: {ServerUrl}", Name, _settings.ServerUrl); +_logger?.LogDebug("Message received: {MessageId} from {SenderName}", args.MessageId, args.SenderName); +``` + +### リソース管理 + +```csharp +public async ValueTask DisposeAsync() +{ + if (_disposed) return; + _disposed = true; + + // イベントの購読解除 + // _client.OnMessage -= OnMessage; + + // 接続の切断 + await StopAsync(); + + // その他のリソース解放 + _httpClient?.Dispose(); +} +``` + +--- + +## 既存プロバイダーの参照 + +実装の参考として、以下のプロバイダーを参照してください: + +- [Clawleash.Interfaces.Discord](../Clawleash.Interfaces.Discord/README.md) - Discord Bot +- [Clawleash.Interfaces.Slack](../Clawleash.Interfaces.Slack/README.md) - Slack Bot +- [Clawleash.Interfaces.WebSocket](../Clawleash.Interfaces.WebSocket/README.md) - WebSocket (E2EE) +- [Clawleash.Interfaces.WebRTC](../Clawleash.Interfaces.WebRTC/README.md) - WebRTC (E2EE) + +--- + +## ライセンス + +MIT diff --git a/Clawleash.Interfaces.Discord/README-en.md b/Clawleash.Interfaces.Discord/README-en.md new file mode 100644 index 0000000..ef458af --- /dev/null +++ b/Clawleash.Interfaces.Discord/README-en.md @@ -0,0 +1,162 @@ +# Clawleash.Interfaces.Discord + +A complete implementation of Discord Bot chat interface. Uses Discord.NET to receive messages from Discord servers and DMs, integrating with the AI agent. + +## Features + +- **Real-time Message Reception**: Real-time message monitoring using Gateway Intents +- **Command Prefix Support**: Identify commands with prefixes like `!` +- **Thread Replies**: Respond in reply format to original messages +- **DM Support**: Works in direct messages (no prefix required) +- **Streaming Send**: Support for batch sending of long messages + +## Architecture + +```mermaid +flowchart TB + subgraph Interface["DiscordChatInterface (C#)"] + subgraph Client["DiscordSocketClient (Discord.NET)"] + Intent["Gateway Intents"] + Events["Message Received Events"] + end + end + Client -->|WebSocket| Gateway["Discord Gateway
(WebSocket)"] +``` + +## Usage + +### Settings + +```csharp +var settings = new DiscordSettings +{ + Token = "YOUR_BOT_TOKEN", + CommandPrefix = "!", + UseThreads = false, + UseEmbeds = true +}; +``` + +### Basic Usage + +```csharp +var chatInterface = new DiscordChatInterface(settings, logger); + +// Event handler +chatInterface.MessageReceived += (sender, args) => +{ + Console.WriteLine($"Message from {args.SenderName}: {args.Content}"); + Console.WriteLine($"Guild: {args.Metadata["GuildName"]}"); + Console.WriteLine($"Channel: {args.Metadata["ChannelName"]}"); + Console.WriteLine($"Is DM: {args.Metadata["IsDirectMessage"]}"); +}; + +// Start +await chatInterface.StartAsync(cancellationToken); + +// Send message (reply format) +await chatInterface.SendMessageAsync("Hello!", replyToMessageId); + +// Dispose +await chatInterface.DisposeAsync(); +``` + +## Configuration Options + +| Property | Description | Default | +|-----------|------|-----------| +| `Token` | Discord Bot Token | (Required) | +| `CommandPrefix` | Command prefix (ignored in DMs) | `!` | +| `UseThreads` | Whether to reply in threads | `false` | +| `UseEmbeds` | Whether to use embed messages | `true` | + +## Events + +### MessageReceived + +Event raised when a message is received. + +```csharp +chatInterface.MessageReceived += (sender, args) => +{ + // args.MessageId - Message ID + // args.SenderId - Sender Discord ID + // args.SenderName - Sender name (global name preferred) + // args.Content - Message content (prefix removed) + // args.ChannelId - Channel ID + // args.Timestamp - Timestamp + // args.Metadata["GuildId"] - Server ID + // args.Metadata["GuildName"] - Server name + // args.Metadata["ChannelName"] - Channel name + // args.Metadata["IsDirectMessage"] - Whether it's a DM + // args.Metadata["IsThread"] - Whether it's a thread +}; +``` + +## Bot Setup + +### Required Permissions + +- **Read Messages**: Read messages +- **Send Messages**: Send messages +- **Read Message History**: Read message history (for replies) +- **View Channels**: View channels + +### Gateway Intents + +The following Intents are required: + +- `MessageContent` - Read message content +- `GuildMessages` - Receive server messages +- `DirectMessages` - Receive DMs + +## Troubleshooting + +### "Privileged intent provided is not enabled" + +Enable Message Content Intent in Bot settings: +1. Open Discord Developer Portal +2. Select the target application +3. Bot tab → Privileged Gateway Intents +4. Enable "Message Content Intent" + +### "Discord token is not configured" + +Verify Token is correctly set in `appsettings.json`: + +```json +{ + "ChatInterface": { + "Discord": { + "Enabled": true, + "Token": "${DISCORD_BOT_TOKEN}" + } + } +} +``` + +### Commands Not Responding + +- Verify command prefix is correct +- Check if bot has access to the channel +- In DMs, send without prefix + +## Build + +```bash +cd Clawleash.Interfaces.Discord +dotnet build +``` + +## Dependencies + +- Discord.NET (latest stable) +- Clawleash.Abstractions + +## Related Projects + +- [Clawleash.Abstractions](../Clawleash.Abstractions/README-en.md) - Shared interfaces + +## License + +MIT diff --git a/Clawleash.Interfaces.Discord/README.md b/Clawleash.Interfaces.Discord/README.md new file mode 100644 index 0000000..511c02a --- /dev/null +++ b/Clawleash.Interfaces.Discord/README.md @@ -0,0 +1,162 @@ +# Clawleash.Interfaces.Discord + +Discord Bot チャットインターフェースの完全実装。Discord.NET を使用して Discord サーバーおよび DM からメッセージを受信し、AI エージェントと連携します。 + +## 機能 + +- **リアルタイムメッセージ受信**: Gateway Intents を使用したリアルタイムメッセージ監視 +- **コマンドプレフィックス対応**: `!` などのプレフィックスでコマンドを識別 +- **スレッド返信**: 元のメッセージに対する返信形式で応答 +- **DM 対応**: ダイレクトメッセージでも動作(プレフィックス不要) +- **ストリーミング送信**: 長いメッセージの一括送信対応 + +## アーキテクチャ + +```mermaid +flowchart TB + subgraph Interface["DiscordChatInterface (C#)"] + subgraph Client["DiscordSocketClient (Discord.NET)"] + Intent["Gateway Intents"] + Events["Message Received Events"] + end + end + Client -->|WebSocket| Gateway["Discord Gateway
(WebSocket)"] +``` + +## 使用方法 + +### 設定 + +```csharp +var settings = new DiscordSettings +{ + Token = "YOUR_BOT_TOKEN", + CommandPrefix = "!", + UseThreads = false, + UseEmbeds = true +}; +``` + +### 基本的な使用 + +```csharp +var chatInterface = new DiscordChatInterface(settings, logger); + +// イベントハンドラー +chatInterface.MessageReceived += (sender, args) => +{ + Console.WriteLine($"Message from {args.SenderName}: {args.Content}"); + Console.WriteLine($"Guild: {args.Metadata["GuildName"]}"); + Console.WriteLine($"Channel: {args.Metadata["ChannelName"]}"); + Console.WriteLine($"Is DM: {args.Metadata["IsDirectMessage"]}"); +}; + +// 開始 +await chatInterface.StartAsync(cancellationToken); + +// メッセージ送信(返信形式) +await chatInterface.SendMessageAsync("Hello!", replyToMessageId); + +// 終了 +await chatInterface.DisposeAsync(); +``` + +## 設定オプション + +| プロパティ | 説明 | デフォルト | +|-----------|------|-----------| +| `Token` | Discord Bot Token | (必須) | +| `CommandPrefix` | コマンドプレフィックス(DM では無視) | `!` | +| `UseThreads` | 返信をスレッドで行うかどうか | `false` | +| `UseEmbeds` | Embed メッセージを使用するかどうか | `true` | + +## イベント + +### MessageReceived + +メッセージ受信時に発生するイベント。 + +```csharp +chatInterface.MessageReceived += (sender, args) => +{ + // args.MessageId - メッセージ ID + // args.SenderId - 送信者 Discord ID + // args.SenderName - 送信者名(グローバル名優先) + // args.Content - メッセージ内容(プレフィックス削除済み) + // args.ChannelId - チャンネル ID + // args.Timestamp - タイムスタンプ + // args.Metadata["GuildId"] - サーバー ID + // args.Metadata["GuildName"] - サーバー名 + // args.Metadata["ChannelName"] - チャンネル名 + // args.Metadata["IsDirectMessage"] - DM かどうか + // args.Metadata["IsThread"] - スレッドかどうか +}; +``` + +## Bot 設定 + +### 必要な権限 + +- **Read Messages**: メッセージの読み取り +- **Send Messages**: メッセージの送信 +- **Read Message History**: メッセージ履歴の読み取り(返信用) +- **View Channels**: チャンネルの表示 + +### Gateway Intents + +以下の Intents が必要です: + +- `MessageContent` - メッセージ内容の読み取り +- `GuildMessages` - サーバーメッセージの受信 +- `DirectMessages` - DM の受信 + +## トラブルシューティング + +### "Privileged intent provided is not enabled" + +Bot 設定で Message Content Intent を有効にしてください: +1. Discord Developer Portal を開く +2. 対象のアプリケーションを選択 +3. Bot タブ → Privileged Gateway Intents +4. "Message Content Intent" を有効化 + +### "Discord token is not configured" + +`appsettings.json` で Token が正しく設定されているか確認してください: + +```json +{ + "ChatInterface": { + "Discord": { + "Enabled": true, + "Token": "${DISCORD_BOT_TOKEN}" + } + } +} +``` + +### コマンドが反応しない + +- コマンドプレフィックスが正しいか確認 +- Bot がチャンネルにアクセス権を持っているか確認 +- DM ではプレフィックスなしで送信 + +## ビルド + +```bash +cd Clawleash.Interfaces.Discord +dotnet build +``` + +## 依存関係 + +- Discord.NET (最新安定版) +- Clawleash.Abstractions + +## 関連プロジェクト + +- [Clawleash.Abstractions](../Clawleash.Abstractions/README.md) - 共有インターフェース + +## ライセンス + +MIT diff --git a/Clawleash.Interfaces.Slack/README-en.md b/Clawleash.Interfaces.Slack/README-en.md new file mode 100644 index 0000000..c55db4d --- /dev/null +++ b/Clawleash.Interfaces.Slack/README-en.md @@ -0,0 +1,164 @@ +# Clawleash.Interfaces.Slack + +A complete implementation of Slack Bot chat interface. Uses HTTP API + polling to receive messages from Slack, integrating with the AI agent. + +## Features + +- **HTTP API Polling**: Message retrieval using conversations.history API +- **Thread Replies**: Respond in thread format to original messages +- **DM Support**: Send and receive direct messages +- **Channel Management**: List, join, and leave channels +- **Streaming Send**: Support for batch sending of long messages + +## Architecture + +```mermaid +flowchart TB + subgraph Interface["SlackChatInterface (C#)"] + subgraph Client["HTTP Client + Polling Thread"] + Poll["conversations.history polling"] + Dedup["Message deduplication"] + end + end + Client -->|HTTPS| API["Slack Web API
(HTTPS)"] +``` + +## Usage + +### Settings + +```csharp +var settings = new SlackSettings +{ + BotToken = "xoxb-...", + AppToken = "xapp-...", + UseThreads = true, + UseBlockKit = false +}; +``` + +### Basic Usage + +```csharp +var chatInterface = new SlackChatInterface(settings, logger); + +// Event handler +chatInterface.MessageReceived += (sender, args) => +{ + Console.WriteLine($"Message from {args.SenderName}: {args.Content}"); + Console.WriteLine($"Thread: {args.Metadata["ThreadTs"]}"); + Console.WriteLine($"Is Thread: {args.Metadata["IsThread"]}"); +}; + +// Start +await chatInterface.StartAsync(cancellationToken); + +// Join channel +await chatInterface.JoinChannelAsync("C12345678"); + +// Send message (thread reply) +await chatInterface.SendMessageAsync("Hello!", replyToMessageId); + +// Send DM +await chatInterface.SendDirectMessageAsync("U12345678", "Hello!"); + +// Dispose +await chatInterface.DisposeAsync(); +``` + +## Configuration Options + +| Property | Description | Default | +|-----------|------|-----------| +| `BotToken` | Bot User OAuth Token (`xoxb-...`) | (Required) | +| `AppToken` | App-Level Token (`xapp-...`) | (Empty) | +| `SigningSecret` | For HTTP request validation | `null` | +| `UseThreads` | Whether to reply in threads | `true` | +| `UseBlockKit` | Whether to use Block Kit | `false` | + +## Events + +### MessageReceived + +Event raised when a message is received. + +```csharp +chatInterface.MessageReceived += (sender, args) => +{ + // args.MessageId - Message timestamp (ts) + // args.SenderId - Sender Slack ID + // args.SenderName - Sender display name + // args.Content - Message content + // args.ChannelId - Channel ID + // args.Timestamp - Timestamp + // args.Metadata["ThreadTs"] - Parent thread ts + // args.Metadata["IsThread"] - Whether in thread +}; +``` + +## Slack App Setup + +### Required OAuth Scopes + +- `channels:history` - Read public channel messages +- `groups:history` - Read private channel messages +- `im:history` - Read DM messages +- `mpim:history` - Read group DM messages +- `channels:read` - Get channel list +- `groups:read` - Get private channel list +- `im:read` - Get DM list +- `im:write` - Send DMs +- `chat:write` - Send messages + +### App Setup Steps + +1. Create an app at [Slack API](https://api.slack.com/apps) +2. Add the above Scopes in OAuth & Permissions +3. Install to Workspace +4. Copy the Bot User OAuth Token (`xoxb-...`) + +## Polling Behavior + +- **Polling Interval**: 5 seconds +- **Fetch Period**: Messages from the past 5 minutes +- **Deduplication**: Processed message IDs retained for 10 minutes + +## Troubleshooting + +### "Failed to authenticate with Slack" + +Verify Bot Token is correct: +- Token starts with `xoxb-` +- Appropriate Scopes are granted + +### Messages Not Received + +1. Verify bot is invited to the channel +2. Add channel to monitoring with `JoinChannelAsync` +3. Verify required Scopes are granted + +### "rate_limited" + +Slack API rate limit reached: +- Increase polling interval +- Reduce number of monitored channels + +## Build + +```bash +cd Clawleash.Interfaces.Slack +dotnet build +``` + +## Dependencies + +- System.Text.Json +- Clawleash.Abstractions + +## Related Projects + +- [Clawleash.Abstractions](../Clawleash.Abstractions/README-en.md) - Shared interfaces + +## License + +MIT diff --git a/Clawleash.Interfaces.Slack/README.md b/Clawleash.Interfaces.Slack/README.md new file mode 100644 index 0000000..ea75c6c --- /dev/null +++ b/Clawleash.Interfaces.Slack/README.md @@ -0,0 +1,164 @@ +# Clawleash.Interfaces.Slack + +Slack Bot チャットインターフェースの完全実装。HTTP API + ポーリング方式で Slack からメッセージを受信し、AI エージェントと連携します。 + +## 機能 + +- **HTTP API ポーリング**: conversations.history API を使用したメッセージ取得 +- **スレッド返信**: 元のメッセージに対するスレッド形式で応答 +- **DM 対応**: ダイレクトメッセージの送受信 +- **チャンネル管理**: チャンネル一覧取得・参加・離脱 +- **ストリーミング送信**: 長いメッセージの一括送信対応 + +## アーキテクチャ + +```mermaid +flowchart TB + subgraph Interface["SlackChatInterface (C#)"] + subgraph Client["HTTP Client + Polling Thread"] + Poll["conversations.history polling"] + Dedup["Message deduplication"] + end + end + Client -->|HTTPS| API["Slack Web API
(HTTPS)"] +``` + +## 使用方法 + +### 設定 + +```csharp +var settings = new SlackSettings +{ + BotToken = "xoxb-...", + AppToken = "xapp-...", + UseThreads = true, + UseBlockKit = false +}; +``` + +### 基本的な使用 + +```csharp +var chatInterface = new SlackChatInterface(settings, logger); + +// イベントハンドラー +chatInterface.MessageReceived += (sender, args) => +{ + Console.WriteLine($"Message from {args.SenderName}: {args.Content}"); + Console.WriteLine($"Thread: {args.Metadata["ThreadTs"]}"); + Console.WriteLine($"Is Thread: {args.Metadata["IsThread"]}"); +}; + +// 開始 +await chatInterface.StartAsync(cancellationToken); + +// チャンネルに参加 +await chatInterface.JoinChannelAsync("C12345678"); + +// メッセージ送信(スレッド返信) +await chatInterface.SendMessageAsync("Hello!", replyToMessageId); + +// DM 送信 +await chatInterface.SendDirectMessageAsync("U12345678", "Hello!"); + +// 終了 +await chatInterface.DisposeAsync(); +``` + +## 設定オプション + +| プロパティ | 説明 | デフォルト | +|-----------|------|-----------| +| `BotToken` | Bot User OAuth Token (`xoxb-...`) | (必須) | +| `AppToken` | App-Level Token (`xapp-...`) | (空) | +| `SigningSecret` | HTTP リクエスト検証用 | `null` | +| `UseThreads` | スレッドで返信するかどうか | `true` | +| `UseBlockKit` | Block Kit を使用するかどうか | `false` | + +## イベント + +### MessageReceived + +メッセージ受信時に発生するイベント。 + +```csharp +chatInterface.MessageReceived += (sender, args) => +{ + // args.MessageId - メッセージタイムスタンプ (ts) + // args.SenderId - 送信者 Slack ID + // args.SenderName - 送信者表示名 + // args.Content - メッセージ内容 + // args.ChannelId - チャンネル ID + // args.Timestamp - タイムスタンプ + // args.Metadata["ThreadTs"] - スレッドの親 ts + // args.Metadata["IsThread"] - スレッド内かどうか +}; +``` + +## Slack App 設定 + +### 必要な OAuth Scopes + +- `channels:history` - パブリックチャンネルのメッセージ読み取り +- `groups:history` - プライベートチャンネルのメッセージ読み取り +- `im:history` - DM のメッセージ読み取り +- `mpim:history` - グループ DM のメッセージ読み取り +- `channels:read` - チャンネル一覧取得 +- `groups:read` - プライベートチャンネル一覧取得 +- `im:read` - DM 一覧取得 +- `im:write` - DM 送信 +- `chat:write` - メッセージ送信 + +### App 設定手順 + +1. [Slack API](https://api.slack.com/apps) でアプリを作成 +2. OAuth & Permissions で上記 Scopes を追加 +3. Install to Workspace でインストール +4. Bot User OAuth Token (`xoxb-...`) をコピー + +## ポーリング動作 + +- **ポーリング間隔**: 5 秒 +- **取得期間**: 過去 5 分間のメッセージ +- **重複排除**: 処理済みメッセージ ID を 10 分間保持 + +## トラブルシューティング + +### "Failed to authenticate with Slack" + +Bot Token が正しいか確認してください: +- `xoxb-` で始まるトークン +- 適切な Scopes が付与されている + +### メッセージが受信されない + +1. Bot がチャンネルに招待されているか確認 +2. `JoinChannelAsync` でチャンネルを監視対象に追加 +3. 必要な Scopes が付与されているか確認 + +### "rate_limited" + +Slack API のレート制限に達しました: +- ポーリング間隔を増やしてください +- 監視チャンネル数を減らしてください + +## ビルド + +```bash +cd Clawleash.Interfaces.Slack +dotnet build +``` + +## 依存関係 + +- System.Text.Json +- Clawleash.Abstractions + +## 関連プロジェクト + +- [Clawleash.Abstractions](../Clawleash.Abstractions/README.md) - 共有インターフェース + +## ライセンス + +MIT diff --git a/Clawleash.Interfaces.WebRTC/README-en.md b/Clawleash.Interfaces.WebRTC/README-en.md new file mode 100644 index 0000000..e56a062 --- /dev/null +++ b/Clawleash.Interfaces.WebRTC/README-en.md @@ -0,0 +1,189 @@ +# Clawleash.Interfaces.WebRTC + +A complete implementation of WebRTC chat interface. Establishes WebRTC P2P connections via SignalR signaling server and performs real-time communication through DataChannel. + +## Features + +- **Native WebRTC**: High-speed P2P communication using Rust (webrtc-rs) based native library +- **Fallback Support**: Works in simulation mode when native library is unavailable +- **E2EE Support**: End-to-end encryption for DataChannel communication +- **STUN/TURN**: NAT traversal support (configurable STUN/TURN servers) +- **Auto-Reconnect**: SignalR auto-reconnect and automatic peer connection recovery + +## Architecture + +```mermaid +flowchart TB + subgraph Interface["WebRtcChatInterface (C#)"] + subgraph Native["WebRtcNativeClient (P/Invoke wrapper)"] + Poll["Event polling thread"] + Ser["Message serialization"] + end + DLL["webrtc_client.dll
(Rust cdylib)"] + end + Native -->|P/Invoke| DLL + DLL --> SignalR["SignalR Server
(Signaling)"] +``` + +## Building Native Library + +### Prerequisites + +- Rust 1.70 or later +- Cargo + +### Build Steps + +```bash +# Navigate to webrtc-client repository +cd ../webrtc-client + +# Release build +cargo build --release -p webrtc-client-sys + +# Copy output files to Clowleash +# Windows x64: +copy target\release\webrtc_client.dll ..\Clowleash\Clawleash.Interfaces.WebRTC\Native\win-x64\ + +# Linux x64: +cp target/release/libwebrtc_client.so ../Clowleash/Clawleash.Interfaces.WebRTC/Native/linux-x64/ + +# macOS x64: +cp target/release/libwebrtc_client.dylib ../Clowleash/Clawleash.Interfaces.WebRTC/Native/osx-x64/ +``` + +## Usage + +### Settings + +```csharp +var settings = new WebRtcSettings +{ + SignalingServerUrl = "http://localhost:8080/signaling", + StunServers = new List + { + "stun:stun.l.google.com:19302", + "stun:stun1.l.google.com:19302" + }, + TurnServerUrl = "turn:turn.example.com:3478", // Optional + TurnUsername = "user", + TurnPassword = "password", + EnableE2ee = true, + IceConnectionTimeoutMs = 30000, + MaxReconnectAttempts = 5 +}; +``` + +### Basic Usage + +```csharp +var chatInterface = new WebRtcChatInterface(settings, logger); + +// Event handler +chatInterface.MessageReceived += (sender, args) => +{ + Console.WriteLine($"Message from {args.SenderName}: {args.Content}"); +}; + +// Start +await chatInterface.StartAsync(cancellationToken); + +// Send message +await chatInterface.SendMessageAsync("Hello, World!"); + +// Dispose +await chatInterface.DisposeAsync(); +``` + +## Configuration Options + +| Property | Description | Default | +|-----------|------|-----------| +| `SignalingServerUrl` | SignalR signaling server URL | `ws://localhost:8080/signaling` | +| `StunServers` | STUN server URL list | Google STUN servers | +| `TurnServerUrl` | TURN server URL | `null` | +| `TurnUsername` | TURN username | `null` | +| `TurnPassword` | TURN password | `null` | +| `EnableE2ee` | Enable E2EE | `false` | +| `IceConnectionTimeoutMs` | ICE connection timeout | `30000` | +| `MaxReconnectAttempts` | Maximum reconnection attempts | `5` | +| `ReconnectIntervalMs` | Reconnection interval | `5000` | +| `TryUseNativeClient` | Try using native client | `true` | +| `DataChannelReliable` | DataChannel reliability mode | `true` | +| `HeartbeatIntervalMs` | Heartbeat interval | `30000` | + +## Events + +### MessageReceived + +Event raised when a message is received. + +```csharp +chatInterface.MessageReceived += (sender, args) => +{ + // args.MessageId - Message ID + // args.SenderId - Sender peer ID + // args.SenderName - Sender name + // args.Content - Message content + // args.Timestamp - Timestamp + // args.Metadata["native"] - Native client usage flag + // args.Metadata["encrypted"] - Encryption flag +}; +``` + +## Simulation Mode + +When native library is unavailable, the interface operates in simulation mode: + +- Messages relayed via SignalR +- No actual P2P communication +- Suitable for development and testing environments + +## Troubleshooting + +### "Native WebRTC library not found" + +When native library is not found: +1. Build the `webrtc-client` Rust project +2. Copy DLL/SO/DYLIB to appropriate platform folder +3. Or use simulation mode with `TryUseNativeClient = false` + +### "Architecture mismatch" + +When application and library architectures don't match: +- x64 application → x64 library +- arm64 application → arm64 library + +### ICE Connection Timeout + +When NAT traversal fails: +- Verify STUN servers are configured correctly +- Consider using TURN server +- Increase `IceConnectionTimeoutMs` + +## Testing + +```bash +# Unit tests +dotnet test Clawleash.Interfaces.WebRTC.Tests + +# Integration tests +# Terminal 1: Signaling Server +cd Clawleash.Server && dotnet run + +# Terminal 2: Client 1 +cd Clawleash && dotnet run + +# Terminal 3: Client 2 +cd Clawleash && dotnet run +``` + +## Related Projects + +- [Clawleash.Server](../Clawleash.Server/README-en.md) - Signaling server +- [Clawleash.Interfaces.WebSocket](../Clawleash.Interfaces.WebSocket/README-en.md) - WebSocket interface +- [Clawleash.Abstractions](../Clawleash.Abstractions/README-en.md) - Shared interfaces + +## License + +MIT diff --git a/Clawleash.Interfaces.WebRTC/README.md b/Clawleash.Interfaces.WebRTC/README.md index a2fee14..aa91586 100644 --- a/Clawleash.Interfaces.WebRTC/README.md +++ b/Clawleash.Interfaces.WebRTC/README.md @@ -12,25 +12,17 @@ WebRTC チャットインターフェースの完全実装。SignalR シグナ ## アーキテクチャ -``` -┌──────────────────────────────────────────────────┐ -│ WebRtcChatInterface (C#) │ -│ ┌────────────────────────────────────────────┐ │ -│ │ WebRtcNativeClient (P/Invoke wrapper) │ │ -│ │ - Event polling thread │ │ -│ │ - Message serialization │ │ -│ └────────────────────────────────────────────┘ │ -│ │ P/Invoke │ -│ ▼ │ -│ ┌────────────────────────────────────────────┐ │ -│ │ webrtc_client.dll (Rust cdylib) │ │ -│ └────────────────────────────────────────────┘ │ -└──────────────────────────────────────────────────┘ - │ - ┌───────────────────────┐ - │ SignalR Server │ - │ (Signaling) │ - └───────────────────────┘ +```mermaid +flowchart TB + subgraph Interface["WebRtcChatInterface (C#)"] + subgraph Native["WebRtcNativeClient (P/Invoke wrapper)"] + Poll["Event polling thread"] + Ser["Message serialization"] + end + DLL["webrtc_client.dll
(Rust cdylib)"] + end + Native -->|P/Invoke| DLL + DLL --> SignalR["SignalR Server
(Signaling)"] ``` ## ネイティブライブラリのビルド @@ -186,6 +178,12 @@ cd Clawleash && dotnet run cd Clawleash && dotnet run ``` +## 関連プロジェクト + +- [Clawleash.Server](../Clawleash.Server/README.md) - シグナリングサーバー +- [Clawleash.Interfaces.WebSocket](../Clawleash.Interfaces.WebSocket/README.md) - WebSocket インターフェース +- [Clawleash.Abstractions](../Clawleash.Abstractions/README.md) - 共有インターフェース + ## ライセンス MIT diff --git a/Clawleash.Interfaces.WebSocket/README-en.md b/Clawleash.Interfaces.WebSocket/README-en.md new file mode 100644 index 0000000..241fb41 --- /dev/null +++ b/Clawleash.Interfaces.WebSocket/README-en.md @@ -0,0 +1,188 @@ +# Clawleash.Interfaces.WebSocket + +A complete implementation of WebSocket chat interface. Uses SignalR client for real-time communication with the server, supporting E2EE (End-to-End Encryption). + +## Features + +- **SignalR Communication**: Real-time bidirectional communication via ASP.NET Core SignalR +- **E2EE Support**: End-to-end encryption using X25519 key exchange + AES-256-GCM +- **Channel Keys**: Per-channel encryption key management +- **Auto-Reconnect**: Automatic reconnection with exponential backoff +- **Streaming Send**: Support for batch sending of long messages + +## Architecture + +```mermaid +flowchart TB + subgraph Interface["WebSocketChatInterface (C#)"] + subgraph Hub["HubConnection (SignalR Client)"] + Reconnect["Automatic Reconnect"] + Keys["Message/Channel Key Handling"] + end + subgraph E2EE["AesGcmE2eeProvider"] + X25519["X25519 Key Exchange"] + AES["AES-256-GCM Encryption/Decryption"] + end + end + Hub --> Server["Clawleash.Server
ChatHub (/chat)"] +``` + +## E2EE Encryption Flow + +```mermaid +flowchart LR + subgraph Client["Client"] + KX1["1. Key Exchange
X25519"] + ENC["2. Encrypt
Plaintext → AES-256-GCM → Ciphertext"] + end + subgraph Server["Server"] + KX2["Key Exchange"] + DEC["3. Decrypt
AES-256-GCM → Plaintext"] + end + KX1 <-.->|X25519| KX2 + ENC -->|wss://| DEC +``` + +## Usage + +### Settings + +```csharp +var settings = new WebSocketSettings +{ + ServerUrl = "wss://localhost:8080/chat", + EnableE2ee = true, + ReconnectIntervalMs = 5000, + MaxReconnectAttempts = 10, + HeartbeatIntervalMs = 30000, + ConnectionTimeoutMs = 10000 +}; +``` + +### Basic Usage + +```csharp +var chatInterface = new WebSocketChatInterface(settings, logger); + +// Event handler +chatInterface.MessageReceived += (sender, args) => +{ + Console.WriteLine($"Message from {args.SenderName}: {args.Content}"); + Console.WriteLine($"Encrypted: {args.Metadata["encrypted"]}"); +}; + +// Start (also performs key exchange if E2EE enabled) +await chatInterface.StartAsync(cancellationToken); + +// Join channel +await chatInterface.JoinChannelAsync("general"); + +// Send message +await chatInterface.SendMessageAsync("Hello!", replyToMessageId); + +// Leave channel +await chatInterface.LeaveChannelAsync("general"); + +// Dispose +await chatInterface.DisposeAsync(); +``` + +## Configuration Options + +| Property | Description | Default | +|-----------|------|-----------| +| `ServerUrl` | SignalR server URL | `ws://localhost:8080/chat` | +| `EnableE2ee` | Enable E2EE (inherited from parent settings) | `false` | +| `ReconnectIntervalMs` | Reconnection interval | `5000` | +| `MaxReconnectAttempts` | Maximum reconnection attempts | `10` | +| `HeartbeatIntervalMs` | Heartbeat interval | `30000` | +| `ConnectionTimeoutMs` | Connection timeout | `10000` | + +## Events + +### MessageReceived + +Event raised when a message is received. + +```csharp +chatInterface.MessageReceived += (sender, args) => +{ + // args.MessageId - Message ID + // args.SenderId - Sender ID + // args.SenderName - Sender name + // args.Content - Message content (decrypted) + // args.ChannelId - Channel ID + // args.Timestamp - Timestamp + // args.Metadata["encrypted"] - Whether it was encrypted +}; +``` + +## Reconnection Policy + +Automatic reconnection using exponential backoff: + +``` +Attempt 1: After 5 seconds +Attempt 2: After 10 seconds +Attempt 3: After 20 seconds +... +Maximum 60 second interval +``` + +Stops reconnecting after maximum attempts reached. + +## Troubleshooting + +### "WebSocket server URL is not configured" + +Verify URL is set in `appsettings.json`: + +```json +{ + "ChatInterface": { + "WebSocket": { + "Enabled": true, + "ServerUrl": "wss://localhost:8080/chat", + "EnableE2ee": true + } + } +} +``` + +### "Failed to connect to SignalR hub" + +1. Verify Clawleash.Server is running +2. Verify URL is correct (`ws://` or `wss://`) +3. Check firewall settings + +### "Key exchange failed" + +1. Verify E2EE is enabled on server side +2. Verify server time is correct + +### Messages Not Encrypted + +- Verify `EnableE2ee` is set to `true` +- Verify channel key is set (`HasChannelKey`) + +## Build + +```bash +cd Clawleash.Interfaces.WebSocket +dotnet build +``` + +## Dependencies + +- Microsoft.AspNetCore.SignalR.Client +- Clawleash.Abstractions + +## Related Projects + +- [Clawleash.Server](../Clawleash.Server/README-en.md) - SignalR server +- [Clawleash.Interfaces.WebRTC](../Clawleash.Interfaces.WebRTC/README-en.md) - WebRTC interface +- [Clawleash.Abstractions](../Clawleash.Abstractions/README-en.md) - Shared interfaces + +## License + +MIT diff --git a/Clawleash.Interfaces.WebSocket/README.md b/Clawleash.Interfaces.WebSocket/README.md new file mode 100644 index 0000000..95bac9d --- /dev/null +++ b/Clawleash.Interfaces.WebSocket/README.md @@ -0,0 +1,188 @@ +# Clawleash.Interfaces.WebSocket + +WebSocket チャットインターフェースの完全実装。SignalR クライアントを使用してサーバーとリアルタイム通信を行い、E2EE(エンドツーエンド暗号化)に対応しています。 + +## 機能 + +- **SignalR 通信**: ASP.NET Core SignalR によるリアルタイム双方向通信 +- **E2EE 対応**: X25519 鍵交換 + AES-256-GCM によるエンドツーエンド暗号化 +- **チャンネル鍵**: チャンネルごとの暗号化鍵管理 +- **自動再接続**: 指数バックオフによる自動再接続 +- **ストリーミング送信**: 長いメッセージの一括送信対応 + +## アーキテクチャ + +```mermaid +flowchart TB + subgraph Interface["WebSocketChatInterface (C#)"] + subgraph Hub["HubConnection (SignalR Client)"] + Reconnect["Automatic Reconnect"] + Keys["Message/Channel Key Handling"] + end + subgraph E2EE["AesGcmE2eeProvider"] + X25519["X25519 Key Exchange"] + AES["AES-256-GCM Encryption/Decryption"] + end + end + Hub --> Server["Clawleash.Server
ChatHub (/chat)"] +``` + +## E2EE 暗号化フロー + +```mermaid +flowchart LR + subgraph Client["Client"] + KX1["1. 鍵交換
X25519"] + ENC["2. 暗号化
Plaintext → AES-256-GCM → Ciphertext"] + end + subgraph Server["Server"] + KX2["鍵交換"] + DEC["3. 復号化
AES-256-GCM → Plaintext"] + end + KX1 <-.->|X25519| KX2 + ENC -->|wss://| DEC +``` + +## 使用方法 + +### 設定 + +```csharp +var settings = new WebSocketSettings +{ + ServerUrl = "wss://localhost:8080/chat", + EnableE2ee = true, + ReconnectIntervalMs = 5000, + MaxReconnectAttempts = 10, + HeartbeatIntervalMs = 30000, + ConnectionTimeoutMs = 10000 +}; +``` + +### 基本的な使用 + +```csharp +var chatInterface = new WebSocketChatInterface(settings, logger); + +// イベントハンドラー +chatInterface.MessageReceived += (sender, args) => +{ + Console.WriteLine($"Message from {args.SenderName}: {args.Content}"); + Console.WriteLine($"Encrypted: {args.Metadata["encrypted"]}"); +}; + +// 開始(E2EE 有効時は鍵交換も実行) +await chatInterface.StartAsync(cancellationToken); + +// チャンネルに参加 +await chatInterface.JoinChannelAsync("general"); + +// メッセージ送信 +await chatInterface.SendMessageAsync("Hello!", replyToMessageId); + +// チャンネルから離脱 +await chatInterface.LeaveChannelAsync("general"); + +// 終了 +await chatInterface.DisposeAsync(); +``` + +## 設定オプション + +| プロパティ | 説明 | デフォルト | +|-----------|------|-----------| +| `ServerUrl` | SignalR サーバー URL | `ws://localhost:8080/chat` | +| `EnableE2ee` | E2EE 有効化(親設定から継承) | `false` | +| `ReconnectIntervalMs` | 再接続間隔 | `5000` | +| `MaxReconnectAttempts` | 最大再接続試行回数 | `10` | +| `HeartbeatIntervalMs` | ハートビート間隔 | `30000` | +| `ConnectionTimeoutMs` | 接続タイムアウト | `10000` | + +## イベント + +### MessageReceived + +メッセージ受信時に発生するイベント。 + +```csharp +chatInterface.MessageReceived += (sender, args) => +{ + // args.MessageId - メッセージ ID + // args.SenderId - 送信者 ID + // args.SenderName - 送信者名 + // args.Content - メッセージ内容(復号化済み) + // args.ChannelId - チャンネル ID + // args.Timestamp - タイムスタンプ + // args.Metadata["encrypted"] - 暗号化されていたか +}; +``` + +## 再接続ポリシー + +指数バックオフを使用した自動再接続: + +``` +試行 1: 5 秒後 +試行 2: 10 秒後 +試行 3: 20 秒後 +... +最大 60 秒間隔 +``` + +最大試行回数に達すると再接続を停止します。 + +## トラブルシューティング + +### "WebSocket server URL is not configured" + +`appsettings.json` で URL が設定されているか確認: + +```json +{ + "ChatInterface": { + "WebSocket": { + "Enabled": true, + "ServerUrl": "wss://localhost:8080/chat", + "EnableE2ee": true + } + } +} +``` + +### "Failed to connect to SignalR hub" + +1. Clawleash.Server が起動しているか確認 +2. URL が正しいか確認(`ws://` または `wss://`) +3. ファイアウォール設定を確認 + +### "Key exchange failed" + +1. サーバー側でも E2EE が有効か確認 +2. サーバーの時刻が正しいか確認 + +### メッセージが暗号化されない + +- `EnableE2ee` が `true` に設定されているか確認 +- チャンネル鍵が設定されているか確認(`HasChannelKey`) + +## ビルド + +```bash +cd Clawleash.Interfaces.WebSocket +dotnet build +``` + +## 依存関係 + +- Microsoft.AspNetCore.SignalR.Client +- Clawleash.Abstractions + +## 関連プロジェクト + +- [Clawleash.Server](../Clawleash.Server/README.md) - SignalR サーバー +- [Clawleash.Interfaces.WebRTC](../Clawleash.Interfaces.WebRTC/README.md) - WebRTC インターフェース +- [Clawleash.Abstractions](../Clawleash.Abstractions/README.md) - 共有インターフェース + +## ライセンス + +MIT diff --git a/Clawleash.Server/README-en.md b/Clawleash.Server/README-en.md new file mode 100644 index 0000000..931db8c --- /dev/null +++ b/Clawleash.Server/README-en.md @@ -0,0 +1,194 @@ +# Clawleash.Server + +Clawleash's SignalR server component. Provides real-time communication with WebSocket and WebRTC clients, supporting E2EE (End-to-End Encryption). + +## Features + +- **SignalR Hub**: Real-time bidirectional communication +- **ChatHub**: Chat hub for WebSocket clients (E2EE support) +- **SignalingHub**: WebRTC signaling server +- **E2EE Key Management**: X25519 key exchange and channel key distribution +- **Svelte Client Delivery**: Serves SPA as static files + +## Architecture + +```mermaid +flowchart TB + subgraph Server["Clawleash.Server"] + subgraph Hubs["Hubs"] + ChatHub["ChatHub
(/chat)
- E2EE Support
- Channel Mgmt"] + SignalingHub["SignalingHub
(/signaling)
- SDP/ICE candidate exchange
- Peer connection mgmt"] + end + subgraph Security["Security"] + KeyManager["KeyManager
- Key pair gen
- Session mgmt"] + Middleware["E2eeMiddleware
- Channel key management"] + end + Svelte["Svelte Client (Static Files)"] + end +``` + +## Usage + +### Starting + +```bash +cd Clawleash.Server +dotnet run + +# Or +dotnet run --project Clawleash.Server +``` + +### Endpoints + +| Path | Description | +|------|-------------| +| `/` | Svelte SPA client | +| `/chat` | WebSocket ChatHub | +| `/signaling` | WebRTC signaling hub | + +### Configuration + +`appsettings.json`: + +```json +{ + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, + "AllowedOrigins": "https://yourdomain.com" +} +``` + +## ChatHub API + +### Client → Server + +| Method | Description | +|----------|------| +| `StartKeyExchange()` | Start E2EE key exchange | +| `CompleteKeyExchange(sessionId, publicKey)` | Complete key exchange | +| `SendMessage(content, channelId, senderName, encrypted, ciphertext)` | Send message | +| `JoinChannel(channelId)` | Join channel | +| `LeaveChannel(channelId)` | Leave channel | + +### Server → Client + +| Event | Description | +|----------|------| +| `MessageReceived` | Message received | +| `ChannelKey` | Channel key distribution | +| `KeyExchangeCompleted` | Key exchange completion notification | + +## SignalingHub API + +### Client → Server + +| Method | Description | +|----------|------| +| `Register(peerId, metadata)` | Register peer | +| `Offer(targetPeerId, sdp)` | Send SDP offer | +| `Answer(targetPeerId, sdp)` | Send SDP answer | +| `IceCandidate(targetPeerId, candidate)` | Send ICE candidate | + +### Server → Client + +| Event | Description | +|----------|------| +| `PeerConnected` | Peer connection notification | +| `PeerDisconnected` | Peer disconnection notification | +| `Offer` | SDP offer received | +| `Answer` | SDP answer received | +| `IceCandidate` | ICE candidate received | + +## CORS Configuration + +### Development + +```csharp +policy.WithOrigins("http://localhost:5173", "http://localhost:4173") + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); +``` + +### Production + +Specify allowed origins in `AllowedOrigins` setting: + +```json +{ + "AllowedOrigins": "https://app.yourdomain.com" +} +``` + +## E2EE Key Exchange Flow + +```mermaid +sequenceDiagram + participant Client + participant Server + Client->>Server: StartKeyExchange + Server->>Server: 1. Generate key pair
Issue session ID + Server->>Client: ServerPublicKey + SessionId + Client->>Client: 2. Generate shared secret
Generate channel key + Client->>Server: ClientPublicKey + SessionId + Server->>Server: 3. Generate shared secret + Server->>Client: KeyExchangeCompleted + Server->>Client: ChannelKey (encrypted) +``` + +## Svelte Client + +Place Svelte application in `Client/` directory: + +``` +Client/ +├── index.html +├── _framework/ +│ └── blazor.webassembly.js +└── ... +``` + +Copied to `wwwroot/` during build. + +## Troubleshooting + +### "CORS policy blocked" + +1. `localhost:5173` is allowed in development +2. Configure `AllowedOrigins` in production + +### WebSocket Connection Drops + +1. Verify WebSocket is allowed on proxy server +2. Check Keep-Alive settings + +### E2EE Not Working + +1. Verify `EnableE2ee` is set to `true` on both client and server +2. Verify time synchronization is correct + +## Build + +```bash +cd Clawleash.Server +dotnet build +``` + +## Dependencies + +- Microsoft.AspNetCore.SignalR +- Clawleash.Abstractions + +## Related Projects + +- [Clawleash.Interfaces.WebSocket](../Clawleash.Interfaces.WebSocket/README-en.md) - WebSocket client +- [Clawleash.Interfaces.WebRTC](../Clawleash.Interfaces.WebRTC/README-en.md) - WebRTC client +- [Clawleash.Abstractions](../Clawleash.Abstractions/README-en.md) - Shared interfaces + +## License + +MIT diff --git a/Clawleash.Server/README.md b/Clawleash.Server/README.md new file mode 100644 index 0000000..5469745 --- /dev/null +++ b/Clawleash.Server/README.md @@ -0,0 +1,194 @@ +# Clawleash.Server + +Clawleash の SignalR サーバーコンポーネント。WebSocket および WebRTC クライアントとのリアルタイム通信を提供し、E2EE(エンドツーエンド暗号化)に対応しています。 + +## 機能 + +- **SignalR Hub**: リアルタイム双方向通信 +- **ChatHub**: WebSocket クライアント用チャットハブ(E2EE 対応) +- **SignalingHub**: WebRTC シグナリングサーバー +- **E2EE 鍵管理**: X25519 鍵交換・チャンネル鍵配布 +- **Svelte クライアント配信**: 静的ファイルとして SPA を配信 + +## アーキテクチャ + +```mermaid +flowchart TB + subgraph Server["Clawleash.Server"] + subgraph Hubs["Hubs"] + ChatHub["ChatHub
(/chat)
- E2EE 対応
- チャンネル管理"] + SignalingHub["SignalingHub
(/signaling)
- SDP/ICE 候補交換
- ピア接続管理"] + end + subgraph Security["Security"] + KeyManager["KeyManager
- 鍵ペア生成
- セッション管理"] + Middleware["E2eeMiddleware
- チャンネル鍵管理"] + end + Svelte["Svelte Client (Static Files)"] + end +``` + +## 使用方法 + +### 起動 + +```bash +cd Clawleash.Server +dotnet run + +# または +dotnet run --project Clawleash.Server +``` + +### エンドポイント + +| パス | 説明 | +|------|------| +| `/` | Svelte SPA クライアント | +| `/chat` | WebSocket ChatHub | +| `/signaling` | WebRTC シグナリングハブ | + +### 設定 + +`appsettings.json`: + +```json +{ + "Logging": { + "LogLevel": { + "Default": "Information" + } + }, + "AllowedOrigins": "https://yourdomain.com" +} +``` + +## ChatHub API + +### クライアント → サーバー + +| メソッド | 説明 | +|----------|------| +| `StartKeyExchange()` | E2EE 鍵交換開始 | +| `CompleteKeyExchange(sessionId, publicKey)` | 鍵交換完了 | +| `SendMessage(content, channelId, senderName, encrypted, ciphertext)` | メッセージ送信 | +| `JoinChannel(channelId)` | チャンネル参加 | +| `LeaveChannel(channelId)` | チャンネル離脱 | + +### サーバー → クライアント + +| イベント | 説明 | +|----------|------| +| `MessageReceived` | メッセージ受信 | +| `ChannelKey` | チャンネル鍵配布 | +| `KeyExchangeCompleted` | 鍵交換完了通知 | + +## SignalingHub API + +### クライアント → サーバー + +| メソッド | 説明 | +|----------|------| +| `Register(peerId, metadata)` | ピア登録 | +| `Offer(targetPeerId, sdp)` | SDP オファー送信 | +| `Answer(targetPeerId, sdp)` | SDP アンサー送信 | +| `IceCandidate(targetPeerId, candidate)` | ICE 候補送信 | + +### サーバー → クライアント + +| イベント | 説明 | +|----------|------| +| `PeerConnected` | ピー接続通知 | +| `PeerDisconnected` | ピー切断通知 | +| `Offer` | SDP オファー受信 | +| `Answer` | SDP アンサー受信 | +| `IceCandidate` | ICE 候補受信 | + +## CORS 設定 + +### 開発環境 + +```csharp +policy.WithOrigins("http://localhost:5173", "http://localhost:4173") + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); +``` + +### 本番環境 + +`AllowedOrigins` 設定で許可するオリジンを指定: + +```json +{ + "AllowedOrigins": "https://app.yourdomain.com" +} +``` + +## E2EE 鍵交換フロー + +```mermaid +sequenceDiagram + participant Client + participant Server + Client->>Server: StartKeyExchange + Server->>Server: 1. 鍵ペア生成
セッション ID 発行 + Server->>Client: ServerPublicKey + SessionId + Client->>Client: 2. 共有秘密生成
チャンネル鍵生成 + Client->>Server: ClientPublicKey + SessionId + Server->>Server: 3. 共有秘密生成 + Server->>Client: KeyExchangeCompleted + Server->>Client: ChannelKey (暗号化済み) +``` + +## Svelte クライアント + +`Client/` ディレクトリに Svelte アプリケーションを配置: + +``` +Client/ +├── index.html +├── _framework/ +│ └── blazor.webassembly.js +└── ... +``` + +ビルド時に `wwwroot/` にコピーされます。 + +## トラブルシューティング + +### "CORS policy blocked" + +1. 開発環境では `localhost:5173` が許可されています +2. 本番環境では `AllowedOrigins` を設定してください + +### WebSocket 接続が切れる + +1. プロキシサーバーで WebSocket が許可されているか確認 +2. Keep-Alive 設定を確認 + +### E2EE が動作しない + +1. クライアントとサーバーで `EnableE2ee` が `true` に設定されているか確認 +2. 時刻同期が正しいか確認 + +## ビルド + +```bash +cd Clawleash.Server +dotnet build +``` + +## 依存関係 + +- Microsoft.AspNetCore.SignalR +- Clawleash.Abstractions + +## 関連プロジェクト + +- [Clawleash.Interfaces.WebSocket](../Clawleash.Interfaces.WebSocket/README.md) - WebSocket クライアント +- [Clawleash.Interfaces.WebRTC](../Clawleash.Interfaces.WebRTC/README.md) - WebRTC クライアント +- [Clawleash.Abstractions](../Clawleash.Abstractions/README.md) - 共有インターフェース + +## ライセンス + +MIT diff --git a/Clawleash.Shell/README-en.md b/Clawleash.Shell/README-en.md new file mode 100644 index 0000000..b0e542c --- /dev/null +++ b/Clawleash.Shell/README-en.md @@ -0,0 +1,188 @@ +# Clawleash.Shell + +Clawleash's sandbox execution process. Communicates with the main application via IPC using ZeroMQ + MessagePack, executing commands in a constrained PowerShell environment. + +## Overview + +Clawleash.Shell operates as an isolated process with the following responsibilities: + +- **IPC Client**: Connects to main app via ZeroMQ (DealerSocket) +- **PowerShell Execution**: Executes commands in constrained PowerShell Runspace +- **Sandbox**: Runs isolated in AppContainer (Windows) / Bubblewrap (Linux) + +## Architecture + +```mermaid +flowchart TB + subgraph Main["Clawleash (Main)"] + Server["ShellServer
(RouterSocket)"] + end + subgraph Shell["Clawleash.Shell (Sandboxed)"] + Client["IpcClient
(DealerSocket)"] + subgraph Runspace["ConstrainedRunspaceHost (PowerShell SDK)"] + Mode["ConstrainedLanguage Mode"] + Whitelist["- Command Whitelist"] + Path["- Path Restrictions"] + Policy["- Folder Policies"] + end + end + Server <-.->|ZeroMQ + MessagePack| Client + Client --> Runspace +``` + +## Usage + +### Starting + +Clawleash.Shell is typically started automatically from the main application: + +```bash +# Manual start (for debugging) +Clawleash.Shell --server tcp://localhost:5555 + +# Verbose logging enabled +Clawleash.Shell --server tcp://localhost:5555 --verbose +``` + +### Command Line Arguments + +| Argument | Short | Description | +|------|------|------| +| `--server
` | `-s` | ZeroMQ server address (required) | +| `--verbose` | `-v` | Verbose logging output | + +## IPC Protocol + +### Communication Specification + +| Item | Specification | +|------|---------------| +| Protocol | ZeroMQ (Router/Dealer) | +| Serialization | MessagePack | +| Direction | Main (Server) ← Shell (Client) | + +### Message Types + +| Message | Direction | Description | +|-----------|------|------| +| `ShellInitializeRequest` | S → M | Initialization request | +| `ShellInitializeResponse` | M → S | Initialization response | +| `ShellExecuteRequest` | M → S | Command execution request | +| `ShellExecuteResponse` | S → M | Execution result | +| `ToolInvokeRequest` | M → S | Tool invocation request | +| `ToolInvokeResponse` | S → M | Tool execution result | +| `ShellPingRequest` | M → S | Health check | +| `ShellPingResponse` | S → M | Response | + +## PowerShell Constraints + +### ConstrainedLanguage Mode + +Operates in ConstrainedLanguage mode by default: + +- **Allowed**: Basic commands, pipelines, variables +- **Prohibited**: .NET method calls, Add-Type, script blocks + +### Command Whitelist + +Only allowed commands can be executed: + +```powershell +# Example allowed commands +Get-Content, Set-Content, Get-ChildItem +New-Item, Remove-Item, Copy-Item, Move-Item +Write-Output, Write-Error +``` + +### Path Restrictions + +Access control based on folder policies: + +```json +{ + "FolderPolicies": [ + { + "Path": "C:\\Projects", + "Access": "ReadWrite", + "Network": "Allow", + "Execute": "Allow" + }, + { + "Path": "C:\\Projects\\Secrets", + "Access": "Deny" + } + ] +} +``` + +## Sandbox + +### Windows (AppContainer) + +- Capability-based access control +- File system, network, registry isolation +- Runs at low integrity level + +### Linux (Bubblewrap) + +- Namespace isolation (PID, Network, Mount, User) +- Resource limits via cgroups +- seccomp filter + +## Project Structure + +``` +Clawleash.Shell/ +├── Program.cs # Entry point +├── IPC/ +│ └── IpcClient.cs # ZeroMQ client +├── Hosting/ +│ └── ConstrainedRunspaceHost.cs # PowerShell host +└── Cmdlets/ + └── ... # Custom cmdlets +``` + +## Troubleshooting + +### "Server address not specified" + +Specify the `--server` argument: + +```bash +Clawleash.Shell --server tcp://localhost:5555 +``` + +### "Failed to connect to Main app" + +1. Verify main application is running +2. Verify server address is correct +3. Check firewall settings + +### PowerShell Command Fails + +1. Verify command is in whitelist +2. Verify path is allowed by folder policies +3. Check ConstrainedLanguage mode restrictions + +## Build + +```bash +cd Clawleash.Shell +dotnet build +``` + +## Dependencies + +- NetMQ (ZeroMQ) +- MessagePack +- PowerShell SDK +- Clawleash.Contracts + +## Related Projects + +- [Clawleash](../README-en.md) - Main application +- [Clawleash.Contracts](../Clawleash.Contracts) - IPC message definitions + +## License + +MIT diff --git a/Clawleash.Shell/README.md b/Clawleash.Shell/README.md new file mode 100644 index 0000000..24fb1b0 --- /dev/null +++ b/Clawleash.Shell/README.md @@ -0,0 +1,189 @@ +# Clawleash.Shell + +Clawleash のサンドボックス実行プロセス。ZeroMQ + MessagePack による IPC でメインアプリケーションと通信し、制約付き PowerShell 環境でコマンドを実行します。 + +## 概要 + +Clawleash.Shell は分離されたプロセスとして動作し、以下の役割を持ちます: + +- **IPC クライアント**: ZeroMQ (DealerSocket) でメインアプリに接続 +- **PowerShell 実行**: 制約付き PowerShell Runspace でコマンド実行 +- **サンドボックス**: AppContainer (Windows) / Bubblewrap (Linux) で分離実行 + +## アーキテクチャ + +```mermaid +flowchart TB + subgraph Main["Clawleash (Main)"] + Server["ShellServer
(RouterSocket)"] + end + subgraph Shell["Clawleash.Shell (Sandboxed)"] + Client["IpcClient
(DealerSocket)"] + subgraph Runspace["ConstrainedRunspaceHost (PowerShell SDK)"] + Mode["ConstrainedLanguage Mode"] + Whitelist["- Command Whitelist"] + Path["- Path Restrictions"] + Policy["- Folder Policies"] + end + end + Server <-.->|ZeroMQ + MessagePack| Client + Client --> Runspace +``` + +## 使用方法 + +### 起動 + +Clawleash.Shell は通常、メインアプリケーションから自動的に起動されます: + +```bash +# 手動起動(デバッグ用) +Clawleash.Shell --server tcp://localhost:5555 + +# 詳細ログ有効 +Clawleash.Shell --server tcp://localhost:5555 --verbose +``` + +### コマンドライン引数 + +| 引数 | 短縮 | 説明 | +|------|------|------| +| `--server
` | `-s` | ZeroMQ サーバーアドレス (必須) | +| `--verbose` | `-v` | 詳細ログ出力 | + +## IPC プロトコル + +### 通信仕様 + +| 項目 | 仕様 | +|------|------| +| プロトコル | ZeroMQ (Router/Dealer) | +| シリアライズ | MessagePack | +| 方向 | Main (Server) ← Shell (Client) | + +### メッセージ種別 + +| メッセージ | 方向 | 説明 | +|-----------|------|------| +| `ShellInitializeRequest` | S → M | 初期化要求 | +| `ShellInitializeResponse` | M → S | 初期化応答 | +| `ShellExecuteRequest` | M → S | コマンド実行要求 | +| `ShellExecuteResponse` | S → M | 実行結果 | +| `ToolInvokeRequest` | M → S | ツール呼び出し要求 | +| `ToolInvokeResponse` | S → M | ツール実行結果 | +| `ShellPingRequest` | M → S | 死活監視 | +| `ShellPingResponse` | S → M | 応答 | + +## PowerShell 制約 + +### ConstrainedLanguage モード + +デフォルトで ConstrainedLanguage モードで動作: + +- **許可**: 基本的なコマンド、パイプライン、変数 +- **禁止**: .NET メソッド呼び出し、Add-Type、スクリプトブロック + +### コマンドホワイトリスト + +許可されたコマンドのみ実行可能: + +```powershell +# 許可されるコマンド例 +Get-Content, Set-Content, Get-ChildItem +New-Item, Remove-Item, Copy-Item, Move-Item +Write-Output, Write-Error +``` + +### パス制限 + +フォルダーポリシーに基づくアクセス制御: + +```json +{ + "FolderPolicies": [ + { + "Path": "C:\\Projects", + "Access": "ReadWrite", + "Network": "Allow", + "Execute": "Allow" + }, + { + "Path": "C:\\Projects\\Secrets", + "Access": "Deny" + } + ] +} +``` + +## サンドボックス + +### Windows (AppContainer) + +- ケーパビリティベースのアクセス制御 +- ファイルシステム、ネットワーク、レジストリの分離 +- 低い整合性レベルで実行 + +### Linux (Bubblewrap) + +- 名前空間分離 (PID, Network, Mount, User) +- cgroups によるリソース制限 +- seccomp フィルタ + +## プロジェクト構成 + +``` +Clawleash.Shell/ +├── Program.cs # エントリーポイント +├── IPC/ +│ └── IpcClient.cs # ZeroMQ クライアント +├── Hosting/ +│ └── ConstrainedRunspaceHost.cs # PowerShell ホスト +└── Cmdlets/ + └── ... # カスタムコマンドレット +``` + +## トラブルシューティング + +### "サーバーアドレスが指定されていません" + +`--server` 引数を指定してください: + +```bash +Clawleash.Shell --server tcp://localhost:5555 +``` + +### "Main アプリへの接続に失敗しました" + +1. メインアプリケーションが起動しているか確認 +2. サーバーアドレスが正しいか確認 +3. ファイアウォール設定を確認 + +### PowerShell コマンドが失敗する + +1. コマンドがホワイトリストに含まれているか確認 +2. パスがフォルダーポリシーで許可されているか確認 +3. ConstrainedLanguage モードの制限を確認 + +## ビルド + +```bash +cd Clawleash.Shell +dotnet build +``` + +## 依存関係 + +- NetMQ (ZeroMQ) +- MessagePack +- PowerShell SDK +- Clawleash.Contracts + +## 関連プロジェクト + +- [Clawleash](../README.md) - メインアプリケーション +- [Clawleash.Contracts](../Clawleash.Contracts) - IPC メッセージ定義 +- [Clawleash.Abstractions](../Clawleash.Abstractions/README.md) - 共有インターフェース + +## ライセンス + +MIT diff --git a/README-en.md b/README-en.md index 4888a14..cfbc1c3 100644 --- a/README-en.md +++ b/README-en.md @@ -6,7 +6,7 @@ [![.NET](https://img.shields.io/badge/.NET-10.0-512BD4?style=flat-square&logo=dotnet)](https://dotnet.microsoft.com/) [![License](https://img.shields.io/badge/License-MIT-green?style=flat-square)](LICENSE) -[![Tests](https://img.shields.io/badge/Tests-34%20passed-brightgreen?style=flat-square)](Clawleash.Tests) +[![Tests](https://img.shields.io/badge/Tests-58%20passed-brightgreen?style=flat-square)](Clawleash.Tests) *Semantic Kernel × Playwright × PowerShell × MCP × Sandbox Architecture × Multi-Interface* @@ -48,23 +48,23 @@ Clawleash supports multiple input interfaces simultaneously. | **WebRTC** | P2P communication via DataChannel | ✅ DTLS-SRTP | **Architecture:** -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ Clawleash (Main Application) │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ InterfaceLoader + FileSystemWatcher (Hot Reload) │ │ -│ │ %LocalAppData%\Clawleash\Interfaces\ monitored │ │ -│ │ New DLL → Auto-load → Register with ChatInterfaceManager │ │ -│ └──────────────────────────┬──────────────────────────────────────┘ │ -│ │ │ -│ ┌──────────────────────────┴──────────────────────────────────────┐ │ -│ │ ChatInterfaceManager │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │ │ -│ │ │ CLI │ │ Discord │ │ Slack │ │ WebSocket│ │ WebRTC │ │ │ -│ │ │(Built-in)│ │ (DLL) │ │ (DLL) │ │ (DLL) │ │ (DLL) │ │ │ -│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └────────┘ │ │ -│ └───────────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────┘ + +```mermaid +flowchart TB + subgraph Main["Clawleash (Main Application)"] + subgraph IL["InterfaceLoader + FileSystemWatcher (Hot Reload)"] + D1["%LocalAppData%\Clawleash\Interfaces\ monitored"] + D2["New DLL → Auto-load → Register with ChatInterfaceManager"] + end + subgraph CIM["ChatInterfaceManager"] + CLI["CLI
(Built-in)"] + DISC["Discord
(DLL)"] + SLK["Slack
(DLL)"] + WS["WebSocket
(DLL)"] + WRTC["WebRTC
(DLL)"] + end + IL --> CIM + end ``` **Configuration Example (appsettings.json):** @@ -101,25 +101,18 @@ Clawleash supports multiple input interfaces simultaneously. Enable E2EE for WebSocket and WebRTC communications. -``` -┌──────────────┐ ┌──────────────┐ -│ Client │ │ Server │ -│ │ │ │ -│ 1. Exchange │ ◄─── X25519 ────────► │ │ -│ │ │ │ -│ 2. Encrypt │ │ │ -│ Plaintext │ │ │ -│ │ │ │ │ -│ ▼ │ │ │ -│ AES-256-GCM │ │ │ -│ │ │ │ │ -│ ▼ │ │ │ -│ Ciphertext │ ──── wss:// ────────► │ 3. Decrypt │ -│ │ │ AES-256-GCM │ -│ │ │ │ │ -│ │ │ ▼ │ -│ │ │ Plaintext │ -└──────────────┘ └──────────────┘ +```mermaid +flowchart LR + subgraph Client["Client"] + KX1["1. Key Exchange
X25519"] + ENC["2. Encrypt
Plaintext → AES-256-GCM → Ciphertext"] + end + subgraph Server["Server"] + KX2["Key Exchange"] + DEC["3. Decrypt
AES-256-GCM → Plaintext"] + end + KX1 <-.->|X25519| KX2 + ENC -->|wss://| DEC ``` ### Web Crawler (Firecrawl-style) @@ -216,7 +209,7 @@ Use tools from external MCP servers within Clawleash. **Transport Support:** - **stdio**: Local NPX packages, Docker containers -- **SSE**: Remote MCP servers (coming soon) +- **SSE**: Remote MCP servers (HTTP Server-Sent Events) --- @@ -349,46 +342,42 @@ services.AddSilentApprovalHandler(config); ## Architecture -``` -┌─────────────────────────────────────────────────────────────┐ -│ Clawleash (Main) │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ -│ │ Kernel │ │ ToolLoader │ │ ShellServer │ │ -│ │ (AI Agent) │ │ (ZIP/DLL) │ │ (ZeroMQ Router) │ │ -│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │ -│ │ │ │ IPC │ -│ ├────────────────┼─────────────────────┤ │ -│ │ SkillLoader │ McpClientManager │ │ -│ │ (YAML/JSON) │ (stdio/SSE) │ │ -│ └────────────────┴─────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ ChatInterfaceManager │ │ -│ │ ┌─────┐ ┌─────────┐ ┌────────┐ ┌──────────┐ ┌───────┐ │ │ -│ │ │ CLI │ │ Discord │ │ Slack │ │ WebSocket│ │ WebRTC│ │ │ -│ │ └─────┘ └─────────┘ └────────┘ └──────────┘ └───────┘ │ │ -│ └─────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ MessagePack over ZeroMQ -┌─────────────────────────────────────────────────────────────┐ -│ Clawleash.Shell (Sandboxed) │ -│ ┌─────────────┐ ┌─────────────────────────────────────┐ │ -│ │ IpcClient │ │ ConstrainedRunspaceHost │ │ -│ │ (Dealer) │ │ (PowerShell SDK) │ │ -│ └─────────────┘ └─────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - -┌─────────────────────────────────────────────────────────────┐ -│ Clawleash.Server (Optional) │ -│ ┌─────────────────────┐ ┌─────────────────────────────┐ │ -│ │ ChatHub │ │ SignalingHub │ │ -│ │ (WebSocket/E2EE) │ │ (WebRTC Signaling) │ │ -│ └─────────────────────┘ └─────────────────────────────┘ │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ Svelte Client (Static Files) │ │ -│ └─────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ +```mermaid +flowchart TB + subgraph Main["Clawleash (Main)"] + Kernel["Kernel
(AI Agent)"] + ToolLoader["ToolLoader
(ZIP/DLL)"] + ShellServer["ShellServer
(ZeroMQ Router)"] + SkillLoader["SkillLoader
(YAML/JSON)"] + McpClient["McpClientManager
(stdio/SSE)"] + subgraph CIM["ChatInterfaceManager"] + CLI["CLI"] + DISC["Discord"] + SLK["Slack"] + WS["WebSocket"] + WRTC["WebRTC"] + end + end + Kernel --> SkillLoader + Kernel --> ToolLoader + Kernel --> McpClient + Kernel --> CIM + ToolLoader --> ShellServer + + subgraph Shell["Clawleash.Shell (Sandboxed)"] + IpcClient["IpcClient
(Dealer)"] + Runspace["ConstrainedRunspaceHost
(PowerShell SDK)"] + end + ShellServer <-.->|MessagePack over ZeroMQ| IpcClient + IpcClient --> Runspace + + subgraph Server["Clawleash.Server (Optional)"] + ChatHub["ChatHub
(WebSocket/E2EE)"] + Signaling["SignalingHub
(WebRTC Signaling)"] + Svelte["Svelte Client (Static Files)"] + end + WS --> ChatHub + WRTC --> Signaling ``` ## Project Structure @@ -586,6 +575,23 @@ dotnet run --project Clawleash.Server Hot-reload enabled: New DLLs are automatically loaded when placed in the directory. +### Creating Custom Providers + +You can create and add your own chat interfaces. + +**Steps:** +1. Create a class library project referencing `Clawleash.Abstractions` +2. Implement `IChatInterface` +3. Build and place in `%LocalAppData%\Clawleash\Interfaces\` + +See [Clawleash.Abstractions](Clawleash.Abstractions/README-en.md) for detailed development guide. + +**Example Implementations:** +- [Discord](Clawleash.Interfaces.Discord/README-en.md) - Discord Bot +- [Slack](Clawleash.Interfaces.Slack/README-en.md) - Slack Bot +- [WebSocket](Clawleash.Interfaces.WebSocket/README-en.md) - WebSocket (E2EE) +- [WebRTC](Clawleash.Interfaces.WebRTC/README-en.md) - WebRTC (E2EE) + ### Adding Skills ``` @@ -598,17 +604,473 @@ Hot-reload enabled: New DLLs are automatically loaded when placed in the directo ## IPC Communication +Communication between Main and Shell processes uses ZeroMQ + MessagePack. + +### Communication Specification + | Item | Specification | |------|---------------| -| Protocol | ZeroMQ (Router/Dealer) | -| Serialization | MessagePack | -| Direction | Main (Server) ← Shell (Client) | - -**Message Types:** -- `ShellExecuteRequest/Response` - Command execution -- `ToolInvokeRequest/Response` - Tool invocation -- `ShellInitializeRequest/Response` - Initialization -- `ShellPingRequest/Response` - Health check +| Library | NetMQ (ZeroMQ .NET implementation) | +| Pattern | Router/Dealer | +| Serialization | MessagePack (Union attribute for polymorphism) | +| Transport | TCP (localhost) | +| Direction | Main (Router/Server) ↔ Shell (Dealer/Client) | + +### Architecture + +```mermaid +flowchart TB + subgraph Main["Main Process"] + Router["RouterSocket (Server)
- Bind to random port
- Multiple client connections supported
- Client identification via Identity"] + end + subgraph Shell["Shell Process (Sandboxed)"] + Dealer["DealerSocket (Client)
- Dynamically assigned Identity
- Async request/response"] + end + Router <-.->|ZeroMQ TCP| Dealer +``` + +### Connection Flow + +```mermaid +sequenceDiagram + participant Main + participant Shell + Main->>Main: 1. RouterSocket.BindRandomPort
(e.g., tcp://127.0.0.1:5555) + Main->>Shell: 2. Process start --server "tcp://..." + Shell->>Shell: 3. DealerSocket.Connect() + Shell->>Main: ShellReadyMessage
(ProcessId, Runtime, OS) + Main->>Shell: ShellInitializeRequest
(AllowedCommands, Paths) + Shell->>Main: ShellInitializeResponse
(Success, Version) + Note over Main,Shell: Ready +``` + +### Message Types + +#### Base Message (Common to All Messages) + +```csharp +public abstract class ShellMessage +{ + public string MessageId { get; set; } // Unique identifier + public DateTime Timestamp { get; set; } // UTC timestamp +} +``` + +#### 1. ShellReadyMessage (Shell → Main) + +Ready notification sent on connection completion. + +```csharp +public class ShellReadyMessage : ShellMessage +{ + public int ProcessId { get; set; } // Shell process ID + public string Runtime { get; set; } // .NET runtime info + public string OS { get; set; } // OS information +} +``` + +#### 2. ShellInitializeRequest/Response (Main ↔ Shell) + +Initialize the Shell execution environment. + +**Request:** +```csharp +public class ShellInitializeRequest : ShellMessage +{ + public string[] AllowedCommands { get; set; } // Permitted commands + public string[] AllowedPaths { get; set; } // Read/write allowed paths + public string[] ReadOnlyPaths { get; set; } // Read-only paths + public ShellLanguageMode LanguageMode { get; set; } // ConstrainedLanguage +} +``` + +**Response:** +```csharp +public class ShellInitializeResponse : ShellMessage +{ + public bool Success { get; set; } + public string? Error { get; set; } + public string Version { get; set; } + public string Runtime { get; set; } +} +``` + +#### 3. ShellExecuteRequest/Response (Main ↔ Shell) + +Execute PowerShell commands. + +**Request:** +```csharp +public class ShellExecuteRequest : ShellMessage +{ + public string Command { get; set; } // Command to execute + public Dictionary Parameters { get; set; } + public string? WorkingDirectory { get; set; } + public int TimeoutMs { get; set; } = 30000; + public ShellExecutionMode Mode { get; set; } +} +``` + +**Response:** +```csharp +public class ShellExecuteResponse : ShellMessage +{ + public string RequestId { get; set; } + public bool Success { get; set; } + public string Output { get; set; } // Standard output + public string? Error { get; set; } // Error message + public int ExitCode { get; set; } + public Dictionary Metadata { get; set; } +} +``` + +#### 4. ToolInvokeRequest/Response (Main ↔ Shell) + +Invoke methods from tool packages. + +**Request:** +```csharp +public class ToolInvokeRequest : ShellMessage +{ + public string ToolName { get; set; } // Tool name + public string MethodName { get; set; } // Method name + public object?[] Arguments { get; set; } // Arguments +} +``` + +**Response:** +```csharp +public class ToolInvokeResponse : ShellMessage +{ + public string RequestId { get; set; } + public bool Success { get; set; } + public object? Result { get; set; } // Return value + public string? Error { get; set; } +} +``` + +#### 5. ShellPingRequest/Response (Main ↔ Shell) + +Health monitoring and latency measurement. + +**Request:** +```csharp +public class ShellPingRequest : ShellMessage +{ + public string Payload { get; set; } = "ping"; +} +``` + +**Response:** +```csharp +public class ShellPingResponse : ShellMessage +{ + public string Payload { get; set; } = "pong"; + public long ProcessingTimeMs { get; set; } // Processing time +} +``` + +#### 6. ShellShutdownRequest/Response (Main ↔ Shell) + +Shutdown the Shell process. + +**Request:** +```csharp +public class ShellShutdownRequest : ShellMessage +{ + public bool Force { get; set; } // Force shutdown flag +} +``` + +**Response:** +```csharp +public class ShellShutdownResponse : ShellMessage +{ + public bool Success { get; set; } +} +``` + +### MessagePack Serialization + +```csharp +// Union attribute for polymorphic deserialization +[MessagePackObject] +[Union(0, typeof(ShellExecuteRequest))] +[Union(1, typeof(ShellExecuteResponse))] +[Union(2, typeof(ShellInitializeRequest))] +// ... +public abstract class ShellMessage { ... } + +// Serialize +var data = MessagePackSerializer.Serialize(request); + +// Deserialize +var message = MessagePackSerializer.Deserialize(data); +``` + +### Error Handling + +```csharp +try +{ + var response = await shellServer.ExecuteAsync(request); + if (!response.Success) + { + // Command execution failed + Console.WriteLine($"Error: {response.Error}"); + } +} +catch (TimeoutException) +{ + // No response from Shell +} +catch (InvalidOperationException) +{ + // Shell not connected +} +``` + +### Timeout Configuration + +```csharp +var options = new ShellServerOptions +{ + StartTimeoutMs = 10000, // Startup timeout + CommunicationTimeoutMs = 30000, // Communication timeout + Verbose = true // Verbose logging +}; +``` + +--- + +## Developing Extensions + +### Adding MCP Servers + +Add external MCP servers to use their tools within Clawleash. + +**appsettings.json:** + +```json +{ + "Mcp": { + "Enabled": true, + "Servers": [ + { + "name": "filesystem", + "transport": "stdio", + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"], + "enabled": true, + "timeoutMs": 30000, + "useSandbox": true + }, + { + "name": "github", + "transport": "stdio", + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-github"], + "environment": { + "GITHUB_TOKEN": "${GITHUB_TOKEN}" + }, + "enabled": true + }, + { + "name": "remote-server", + "transport": "sse", + "url": "https://api.example.com/mcp/sse", + "headers": { + "Authorization": "Bearer ${API_KEY}" + }, + "enabled": true, + "timeoutMs": 60000 + } + ] + } +} +``` + +**MCP Configuration Properties:** + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Server name (unique identifier) | ✅ | +| `transport` | `stdio` or `sse` | ✅ | +| `command` | Command to execute (stdio) | stdio | +| `args` | Command arguments | No | +| `environment` | Environment variables | No | +| `url` | Server URL (SSE) | sse | +| `headers` | HTTP headers (SSE) | No | +| `enabled` | Enable/disable server | No | +| `timeoutMs` | Timeout in milliseconds | No | +| `useSandbox` | Run in sandbox | No | + +### Adding Tool Packages (Native Function Calling) + +Create custom tools using Semantic Kernel's `[KernelFunction]` attribute. + +**Project Structure:** + +``` +MyToolPackage/ +├── MyToolPackage.csproj +├── WeatherTools.cs +└── tool-manifest.json (optional) +``` + +**tool-manifest.json:** + +```json +{ + "name": "WeatherTools", + "version": "1.0.0", + "description": "Weather information tools", + "mainAssembly": "MyToolPackage.dll" +} +``` + +**WeatherTools.cs:** + +```csharp +using System.ComponentModel; +using Microsoft.SemanticKernel; + +namespace MyToolPackage; + +public class WeatherTools +{ + [KernelFunction("get_weather")] + [Description("Get weather for specified city")] + public async Task GetWeatherAsync( + [Description("City name")] string city, + [Description("Unit (celsius/fahrenheit)")] string unit = "celsius") + { + // Weather retrieval logic + return $"Weather in {city}: Sunny, Temperature: 25{unit}"; + } +} +``` + +**Deployment:** + +```bash +# Create ZIP package +cd MyToolPackage/bin/Release/net10.0 +zip -r ../../weather-tools.zip . + +# Copy to packages directory +cp ../../weather-tools.zip "%LocalAppData%/Clawleash/Packages/" +``` + +**Tool Package Directory:** `%LocalAppData%\Clawleash\Packages\` + +Hot-reload enabled: New ZIP files are automatically loaded when placed in the directory. + +#### Native Tool Packages vs MCP + +| Aspect | Native Tool Packages | MCP | +|--------|---------------------|-----| +| **Execution** | Direct execution in sandbox | External process | +| **Access Control** | AppContainer + Folder policies | Controlled by MCP server | +| **Network** | Capability-based control | Depends on MCP server | +| **Process Isolation** | Isolated within Shell process | Completely separate process | +| **Auditing** | Detailed logging available | MCP server dependent | +| **Deployment** | Simple ZIP deployment | MCP server setup required | + +**Use Native Tool Packages when:** +- Internal tools or handling sensitive data +- Strict access control is required +- Audit logging is mandatory +- Network access should be restricted + +**Use MCP when:** +- Using existing MCP servers +- Integrating with external services +- Using community-provided tools + +### Sandbox Execution Architecture + +Tools are executed in a sandboxed environment for security. + +**Architecture:** + +```mermaid +flowchart TB + subgraph Main["Clawleash (Main Process)"] + Kernel["Kernel
(AI Agent)"] + ToolLoader["ToolLoader
(ZIP/DLL)"] + ShellServer["ShellServer
(ZeroMQ Router)"] + end + Kernel --> ToolLoader + Kernel --> ShellServer + + subgraph Shell["Clawleash.Shell (Sandboxed Process)"] + subgraph Sandbox["AppContainer (Windows) / Bubblewrap (Linux)"] + ACL["- File system access control"] + NET["- Network access control"] + EXEC["- Process execution control"] + POL["- Folder policy enforcement"] + end + subgraph ALC["AssemblyLoadContext (isCollectible: true)"] + DLL1["Tool DLLs
(isolated context)"] + DLL2["Can be unloaded
when tool is removed"] + end + end + ShellServer <-.->|IPC| ALC +``` + +**Execution Flow:** + +1. **Tool Invocation:** + - Kernel → ToolProxy → ShellServer (IPC) + - ShellServer → Shell (inside sandbox) + - Shell → AssemblyLoadContext → Tool DLL + - Results returned in reverse order + +2. **Isolation Mechanism:** + - Each tool package is loaded in a separate `AssemblyLoadContext` + - Unloadable (`isCollectible: true`) + - Memory released when tool is removed + +3. **Security Boundaries:** + - **Process Isolation**: Main ↔ Shell are separate processes + - **OS-level Isolation**: AppContainer/Bubblewrap restricts resources + - **Folder Policies**: Path-based access control + +**AppContainer Capabilities (Windows):** + +```json +{ + "Sandbox": { + "Type": "AppContainer", + "AppContainerName": "Clawleash.Sandbox", + "Capabilities": "InternetClient, PrivateNetworkClientServer" + } +} +``` + +| Capability | Allowed Operations | +|------------|-------------------| +| `InternetClient` | Outbound internet connections | +| `PrivateNetworkClientServer` | Private network connections | +| None | No network access | + +**Folder Policy Control:** + +```json +{ + "Sandbox": { + "FolderPolicies": [ + { + "Path": "C:\\Work", + "Access": "ReadWrite", + "Execute": "Deny", + "DeniedExtensions": [".exe", ".bat", ".ps1"] + } + ] + } +} +``` --- diff --git a/README.md b/README.md index df094c9..07dd642 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![.NET](https://img.shields.io/badge/.NET-10.0-512BD4?style=flat-square&logo=dotnet)](https://dotnet.microsoft.com/) [![License](https://img.shields.io/badge/License-MIT-green?style=flat-square)](LICENSE) -[![Tests](https://img.shields.io/badge/Tests-34%20passed-brightgreen?style=flat-square)](Clawleash.Tests) +[![Tests](https://img.shields.io/badge/Tests-58%20passed-brightgreen?style=flat-square)](Clawleash.Tests) *Semantic Kernel × Playwright × PowerShell × MCP × Sandbox Architecture × Multi-Interface* @@ -48,23 +48,23 @@ Clawleashは複数の入力インターフェースを同時にサポートし | **WebRTC** | DataChannel経由のP2P通信 | ✅ DTLS-SRTP | **アーキテクチャ:** -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ Clawleash (Main Application) │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ InterfaceLoader + FileSystemWatcher (Hot Reload) │ │ -│ │ %LocalAppData%\Clawleash\Interfaces\ を監視 │ │ -│ │ 新規DLL追加 → 自動ロード → ChatInterfaceManagerに登録 │ │ -│ └──────────────────────────┬──────────────────────────────────────┘ │ -│ │ │ -│ ┌──────────────────────────┴──────────────────────────────────────┐ │ -│ │ ChatInterfaceManager │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │ │ -│ │ │ CLI │ │ Discord │ │ Slack │ │ WebSocket│ │ WebRTC │ │ │ -│ │ │(Built-in)│ │ (DLL) │ │ (DLL) │ │ (DLL) │ │ (DLL) │ │ │ -│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └────────┘ │ │ -│ └───────────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────┘ + +```mermaid +flowchart TB + subgraph Main["Clawleash (Main Application)"] + subgraph IL["InterfaceLoader + FileSystemWatcher (Hot Reload)"] + D1["%LocalAppData%\Clawleash\Interfaces\ を監視"] + D2["新規DLL追加 → 自動ロード → ChatInterfaceManagerに登録"] + end + subgraph CIM["ChatInterfaceManager"] + CLI["CLI
(Built-in)"] + DISC["Discord
(DLL)"] + SLK["Slack
(DLL)"] + WS["WebSocket
(DLL)"] + WRTC["WebRTC
(DLL)"] + end + IL --> CIM + end ``` **設定例 (appsettings.json):** @@ -101,25 +101,18 @@ Clawleashは複数の入力インターフェースを同時にサポートし WebSocket・WebRTC通信でE2EEを有効にできます。 -``` -┌──────────────┐ ┌──────────────┐ -│ Client │ │ Server │ -│ │ │ │ -│ 1. 鍵交換 │ ◄─── X25519 ────────► │ │ -│ │ │ │ -│ 2. 暗号化 │ │ │ -│ Plaintext │ │ │ -│ │ │ │ │ -│ ▼ │ │ │ -│ AES-256-GCM │ │ │ -│ │ │ │ │ -│ ▼ │ │ │ -│ Ciphertext │ ──── wss:// ────────► │ 3. 復号化 │ -│ │ │ AES-256-GCM │ -│ │ │ │ │ -│ │ │ ▼ │ -│ │ │ Plaintext │ -└──────────────┘ └──────────────┘ +```mermaid +flowchart LR + subgraph Client["Client"] + KX1["1. 鍵交換
X25519"] + ENC["2. 暗号化
Plaintext → AES-256-GCM → Ciphertext"] + end + subgraph Server["Server"] + KX2["鍵交換"] + DEC["3. 復号化
AES-256-GCM → Plaintext"] + end + KX1 <-.->|X25519| KX2 + ENC -->|wss://| DEC ``` ### Webクローラー(Firecrawl風) @@ -216,7 +209,7 @@ prompt: | **トランスポート対応:** - **stdio**: ローカルNPXパッケージ、Dockerコンテナ -- **SSE**: リモートMCPサーバー(今後対応) +- **SSE**: リモートMCPサーバー(HTTP Server-Sent Events) --- @@ -349,46 +342,42 @@ services.AddSilentApprovalHandler(config); ## アーキテクチャ -``` -┌─────────────────────────────────────────────────────────────┐ -│ Clawleash (Main) │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ -│ │ Kernel │ │ ToolLoader │ │ ShellServer │ │ -│ │ (AI Agent) │ │ (ZIP/DLL) │ │ (ZeroMQ Router) │ │ -│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │ -│ │ │ │ IPC │ -│ ├────────────────┼─────────────────────┤ │ -│ │ SkillLoader │ McpClientManager │ │ -│ │ (YAML/JSON) │ (stdio/SSE) │ │ -│ └────────────────┴─────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ ChatInterfaceManager │ │ -│ │ ┌─────┐ ┌─────────┐ ┌────────┐ ┌──────────┐ ┌───────┐ │ │ -│ │ │ CLI │ │ Discord │ │ Slack │ │ WebSocket│ │ WebRTC│ │ │ -│ │ └─────┘ └─────────┘ └────────┘ └──────────┘ └───────┘ │ │ -│ └─────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ MessagePack over ZeroMQ -┌─────────────────────────────────────────────────────────────┐ -│ Clawleash.Shell (Sandboxed) │ -│ ┌─────────────┐ ┌─────────────────────────────────────┐ │ -│ │ IpcClient │ │ ConstrainedRunspaceHost │ │ -│ │ (Dealer) │ │ (PowerShell SDK) │ │ -│ └─────────────┘ └─────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - -┌─────────────────────────────────────────────────────────────┐ -│ Clawleash.Server (Optional) │ -│ ┌─────────────────────┐ ┌─────────────────────────────┐ │ -│ │ ChatHub │ │ SignalingHub │ │ -│ │ (WebSocket/E2EE) │ │ (WebRTC Signaling) │ │ -│ └─────────────────────┘ └─────────────────────────────┘ │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ Svelte Client (Static Files) │ │ -│ └─────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ +```mermaid +flowchart TB + subgraph Main["Clawleash (Main)"] + Kernel["Kernel
(AI Agent)"] + ToolLoader["ToolLoader
(ZIP/DLL)"] + ShellServer["ShellServer
(ZeroMQ Router)"] + SkillLoader["SkillLoader
(YAML/JSON)"] + McpClient["McpClientManager
(stdio/SSE)"] + subgraph CIM["ChatInterfaceManager"] + CLI["CLI"] + DISC["Discord"] + SLK["Slack"] + WS["WebSocket"] + WRTC["WebRTC"] + end + end + Kernel --> SkillLoader + Kernel --> ToolLoader + Kernel --> McpClient + Kernel --> CIM + ToolLoader --> ShellServer + + subgraph Shell["Clawleash.Shell (Sandboxed)"] + IpcClient["IpcClient
(Dealer)"] + Runspace["ConstrainedRunspaceHost
(PowerShell SDK)"] + end + ShellServer <-.->|MessagePack over ZeroMQ| IpcClient + IpcClient --> Runspace + + subgraph Server["Clawleash.Server (Optional)"] + ChatHub["ChatHub
(WebSocket/E2EE)"] + Signaling["SignalingHub
(WebRTC Signaling)"] + Svelte["Svelte Client (Static Files)"] + end + WS --> ChatHub + WRTC --> Signaling ``` ## プロジェクト構成 @@ -586,6 +575,23 @@ dotnet run --project Clawleash.Server ホットリロード対応:新しいDLLを配置すると自動的に読み込まれます。 +### カスタムプロバイダーの作成 + +独自のチャットインターフェースを作成して追加できます。 + +**手順:** +1. `Clawleash.Abstractions` を参照したクラスライブラリプロジェクトを作成 +2. `IChatInterface` を実装 +3. ビルドして `%LocalAppData%\Clawleash\Interfaces\` に配置 + +詳細な開発ガイドは [Clawleash.Abstractions](Clawleash.Abstractions/README.md) を参照してください。 + +**実装例:** +- [Discord](Clawleash.Interfaces.Discord/README.md) - Discord Bot +- [Slack](Clawleash.Interfaces.Slack/README.md) - Slack Bot +- [WebSocket](Clawleash.Interfaces.WebSocket/README.md) - WebSocket (E2EE) +- [WebRTC](Clawleash.Interfaces.WebRTC/README.md) - WebRTC (E2EE) + ### スキルの追加 ``` @@ -598,17 +604,503 @@ dotnet run --project Clawleash.Server ## IPC通信 +Main プロセスと Shell プロセス間の通信には ZeroMQ + MessagePack を使用します。 + +### 通信仕様 + | 項目 | 仕様 | |------|------| -| プロトコル | ZeroMQ (Router/Dealer) | -| シリアライズ | MessagePack | -| 方向 | Main (Server) ← Shell (Client) | - -**メッセージ種別:** -- `ShellExecuteRequest/Response` - コマンド実行 -- `ToolInvokeRequest/Response` - ツール呼び出し -- `ShellInitializeRequest/Response` - 初期化 -- `ShellPingRequest/Response` - 死活監視 +| ライブラリ | NetMQ (ZeroMQ .NET実装) | +| パターン | Router/Dealer | +| シリアライズ | MessagePack (Union属性によるポリモーフィズム) | +| トランスポート | TCP (localhost) | +| 方向 | Main (Router/Server) ↔ Shell (Dealer/Client) | + +### アーキテクチャ + +```mermaid +flowchart TB + subgraph Main["Main プロセス"] + Router["RouterSocket (Server)
- ランダムポートでバインド
- 複数クライアント接続可能
- Identity でクライアント識別"] + end + subgraph Shell["Shell プロセス (Sandboxed)"] + Dealer["DealerSocket (Client)
- 動的に割り当てられたIdentity
- 非同期リクエスト/レスポンス"] + end + Router <-.->|ZeroMQ TCP| Dealer +``` + +### 接続フロー + +```mermaid +sequenceDiagram + participant Main + participant Shell + Main->>Main: 1. RouterSocket.BindRandomPort
(例: tcp://127.0.0.1:5555) + Main->>Shell: 2. プロセス起動 --server "tcp://..." + Shell->>Shell: 3. DealerSocket.Connect() + Shell->>Main: ShellReadyMessage
(ProcessId, Runtime, OS) + Main->>Shell: ShellInitializeRequest
(AllowedCommands, Paths) + Shell->>Main: ShellInitializeResponse
(Success, Version) + Note over Main,Shell: 準備完了 +``` + +### メッセージ一覧 + +#### 基本メッセージ(全メッセージ共通) + +```csharp +public abstract class ShellMessage +{ + public string MessageId { get; set; } // 一意識別子 + public DateTime Timestamp { get; set; } // UTC タイムスタンプ +} +``` + +#### 1. ShellReadyMessage (Shell → Main) + +接続完了時に送信される準備完了通知。 + +```csharp +public class ShellReadyMessage : ShellMessage +{ + public int ProcessId { get; set; } // Shell プロセス ID + public string Runtime { get; set; } // .NET ランタイム情報 + public string OS { get; set; } // OS 情報 +} +``` + +#### 2. ShellInitializeRequest/Response (Main ↔ Shell) + +Shell 実行環境の初期化。 + +**Request:** +```csharp +public class ShellInitializeRequest : ShellMessage +{ + public string[] AllowedCommands { get; set; } // 許可コマンド + public string[] AllowedPaths { get; set; } // 読み書き許可パス + public string[] ReadOnlyPaths { get; set; } // 読み取り専用パス + public ShellLanguageMode LanguageMode { get; set; } // ConstrainedLanguage +} +``` + +**Response:** +```csharp +public class ShellInitializeResponse : ShellMessage +{ + public bool Success { get; set; } + public string? Error { get; set; } + public string Version { get; set; } + public string Runtime { get; set; } +} +``` + +#### 3. ShellExecuteRequest/Response (Main ↔ Shell) + +PowerShell コマンドの実行。 + +**Request:** +```csharp +public class ShellExecuteRequest : ShellMessage +{ + public string Command { get; set; } // 実行コマンド + public Dictionary Parameters { get; set; } + public string? WorkingDirectory { get; set; } + public int TimeoutMs { get; set; } = 30000; + public ShellExecutionMode Mode { get; set; } +} +``` + +**Response:** +```csharp +public class ShellExecuteResponse : ShellMessage +{ + public string RequestId { get; set; } + public bool Success { get; set; } + public string Output { get; set; } // 標準出力 + public string? Error { get; set; } // エラーメッセージ + public int ExitCode { get; set; } + public Dictionary Metadata { get; set; } +} +``` + +#### 4. ToolInvokeRequest/Response (Main ↔ Shell) + +ツールパッケージのメソッド呼び出し。 + +**Request:** +```csharp +public class ToolInvokeRequest : ShellMessage +{ + public string ToolName { get; set; } // ツール名 + public string MethodName { get; set; } // メソッド名 + public object?[] Arguments { get; set; } // 引数 +} +``` + +**Response:** +```csharp +public class ToolInvokeResponse : ShellMessage +{ + public string RequestId { get; set; } + public bool Success { get; set; } + public object? Result { get; set; } // 戻り値 + public string? Error { get; set; } +} +``` + +#### 5. ShellPingRequest/Response (Main ↔ Shell) + +死活監視・レイテンシ測定。 + +**Request:** +```csharp +public class ShellPingRequest : ShellMessage +{ + public string Payload { get; set; } = "ping"; +} +``` + +**Response:** +```csharp +public class ShellPingResponse : ShellMessage +{ + public string Payload { get; set; } = "pong"; + public long ProcessingTimeMs { get; set; } // 処理時間 +} +``` + +#### 6. ShellShutdownRequest/Response (Main ↔ Shell) + +Shell プロセスのシャットダウン。 + +**Request:** +```csharp +public class ShellShutdownRequest : ShellMessage +{ + public bool Force { get; set; } // 強制終了フラグ +} +``` + +**Response:** +```csharp +public class ShellShutdownResponse : ShellMessage +{ + public bool Success { get; set; } +} +``` + +### MessagePack シリアライズ + +```csharp +// Union 属性でポリモーフィック デシリアライズ +[MessagePackObject] +[Union(0, typeof(ShellExecuteRequest))] +[Union(1, typeof(ShellExecuteResponse))] +[Union(2, typeof(ShellInitializeRequest))] +// ... +public abstract class ShellMessage { ... } + +// シリアライズ +var data = MessagePackSerializer.Serialize(request); + +// デシリアライズ +var message = MessagePackSerializer.Deserialize(data); +``` + +### エラーハンドリング + +```csharp +try +{ + var response = await shellServer.ExecuteAsync(request); + if (!response.Success) + { + // コマンド実行失敗 + Console.WriteLine($"Error: {response.Error}"); + } +} +catch (TimeoutException) +{ + // Shell からの応答なし +} +catch (InvalidOperationException) +{ + // Shell に接続されていない +} +``` + +### タイムアウト設定 + +```csharp +var options = new ShellServerOptions +{ + StartTimeoutMs = 10000, // 起動タイムアウト + CommunicationTimeoutMs = 30000, // 通信タイムアウト + Verbose = true // 詳細ログ +}; +``` + +--- + +## 拡張機能の開発 + +### MCPサーバーの追加 + +外部MCPサーバーを追加して、そのツールをClawleash内で使用できます。 + +**appsettings.json:** + +```json +{ + "Mcp": { + "Enabled": true, + "Servers": [ + { + "name": "filesystem", + "transport": "stdio", + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"], + "enabled": true, + "timeoutMs": 30000, + "useSandbox": true + }, + { + "name": "github", + "transport": "stdio", + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-github"], + "environment": { + "GITHUB_TOKEN": "${GITHUB_TOKEN}" + }, + "enabled": true + }, + { + "name": "remote-server", + "transport": "sse", + "url": "https://api.example.com/mcp/sse", + "headers": { + "Authorization": "Bearer ${API_KEY}" + }, + "enabled": true, + "timeoutMs": 60000 + } + ] + } +} +``` + +**MCP設定プロパティ:** + +| プロパティ | 説明 | 必須 | +|-----------|------|------| +| `name` | サーバー名(一意識別子) | ✅ | +| `transport` | `stdio` または `sse` | ✅ | +| `command` | 実行コマンド(stdio用) | stdio時 | +| `args` | コマンド引数 | | +| `environment` | 環境変数 | | +| `url` | サーバーURL(SSE用) | SSE時 | +| `headers` | HTTPヘッダー(SSE用) | | +| `enabled` | 有効/無効 | | +| `timeoutMs` | タイムアウト(ミリ秒) | | +| `useSandbox` | サンドボックスで実行 | | + +**使用可能なツールの確認:** + +``` +ユーザー: MCPサーバーのツール一覧を表示して +AI: list_tools ツールを実行してツール一覧を表示 +``` + +### ツールパッケージの作成 + +ネイティブなFunction CallingライブラリをZIPパッケージとして追加できます。 + +**1. プロジェクト作成** + +```xml + + + net10.0 + + + + + + +``` + +**2. ツールクラスの実装** + +```csharp +using Microsoft.SemanticKernel; + +namespace MyTools; + +public class WeatherTools +{ + [KernelFunction("get_weather")] + [Description("指定した都市の天気を取得します")] + public async Task GetWeatherAsync( + [Description("都市名")] string city, + [Description("単位(celsius/fahrenheit)")] string unit = "celsius") + { + // 天気取得ロジック + return $"{city}の天気: 晴れ, 気温: 25{unit}"; + } + + [KernelFunction("get_forecast")] + [Description("天気予報を取得します")] + public async Task GetForecastAsync( + [Description("都市名")] string city, + [Description("予報日数(1-7)")] int days = 3) + { + return $"{city}の{days}日間予報: 晴れ→曇り→雨"; + } +} +``` + +**3. マニフェスト作成(tool-manifest.json)** + +```json +{ + "name": "WeatherTools", + "version": "1.0.0", + "description": "天気情報ツール", + "mainAssembly": "MyTools.dll", + "dependencies": [] +} +``` + +**4. パッケージ作成** + +```bash +# ZIPパッケージを作成 +zip -r WeatherTools.zip MyTools.dll tool-manifest.json + +# 配置 +cp WeatherTools.zip "%LocalAppData%\Clawleash\Packages\" +``` + +**パッケージ構成:** + +``` +WeatherTools.zip +├── tool-manifest.json # マニフェスト +├── MyTools.dll # メインアセンブリ +└── (依存DLL) # 必要に応じて +``` + +**ホットリロード:** PackagesディレクトリにZIPを配置すると自動的にロードされます。 + +#### ネイティブツールパッケージ vs MCP + +| 項目 | ネイティブツールパッケージ | MCP | +|------|--------------------------|-----| +| **実行環境** | サンドボックス内で直接実行 | 外部プロセス | +| **アクセス制御** | AppContainer + フォルダーポリシー | MCPサーバー側で制御 | +| **ネットワーク** | ケーパビリティで制御 | MCPサーバー次第 | +| **プロセス分離** | Shellプロセス内で分離 | 完全に別プロセス | +| **監査** | 詳細ログ可能 | MCPサーバー依存 | +| **デプロイ** | ZIPで簡単配置 | MCPサーバー構築必要 | + +**ネイティブツールパッケージが推奨されるケース:** +- 社内ツール・機密データを扱うツール +- 厳密なアクセス制御が必要な場合 +- 監査ログが必須な環境 +- ネットワークアクセスを制限したい場合 + +**MCPが推奨されるケース:** +- 既存のMCPサーバーを利用したい場合 +- 外部サービスとの連携 +- コミュニティ提供のツールを使用したい場合 + +### サンドボックス実行の仕組み + +ツールパッケージとシェルコマンドは、サンドボックス環境内で安全に実行されます。 + +**アーキテクチャ:** + +```mermaid +flowchart TB + subgraph Main["Clawleash (Main Process)"] + Kernel["Kernel
(AI Agent)"] + ToolLoader["ToolLoader
(ZIP/DLL)"] + ShellServer["ShellServer
(ZeroMQ Router)"] + Proxy["ToolProxy
(経由で呼出)"] + end + Kernel --> Proxy + Proxy --> ToolLoader + Proxy --> ShellServer + + subgraph Shell["Clawleash.Shell (Sandboxed Process)"] + subgraph ALC["AssemblyLoadContext (分離ロード)"] + DLL1["Tool DLL
(分離済み)"] + DLL2["Tool DLL
(分離済み)"] + PS["PowerShell
Constrained"] + end + subgraph Sandbox["AppContainer (Windows) / Bubblewrap (Linux)"] + ACL["- ファイルシステムアクセス制御"] + NET["- ネットワークアクセス制御"] + EXEC["- プロセス実行制御"] + POL["- フォルダーポリシー適用"] + end + end + ShellServer <-.->|IPC| ALC +``` + +**実行フロー:** + +1. **ツール呼び出し時:** + - Kernel → ToolProxy → ShellServer (IPC) + - ShellServer → Shell (サンドボックス内) + - Shell → AssemblyLoadContext → Tool DLL + - 結果を逆順で返却 + +2. **分離の仕組み:** + - 各ツールパッケージは独立した`AssemblyLoadContext`でロード + - アンロード可能(`isCollectible: true`) + - ツール削除時にメモリ解放 + +3. **セキュリティ境界:** + - **プロセス分離**: Main ↔ Shell は別プロセス + - **OSレベル分離**: AppContainer/Bubblewrap でリソース制限 + - **フォルダーポリシー**: パスごとのアクセス制御 + +**AppContainerケーパビリティ(Windows):** + +```json +{ + "Sandbox": { + "Type": "AppContainer", + "AppContainerName": "Clawleash.Sandbox", + "Capabilities": "InternetClient, PrivateNetworkClientServer" + } +} +``` + +| ケーパビリティ | 許可される操作 | +|--------------|---------------| +| `InternetClient` | インターネットへの送信接続 | +| `PrivateNetworkClientServer` | プライベートネットワークへの接続 | +| なし | ネットワークアクセス禁止 | + +**フォルダーポリシーによる制御:** + +```json +{ + "Sandbox": { + "FolderPolicies": [ + { + "Path": "C:\\Work", + "Access": "ReadWrite", + "Execute": "Deny", + "DeniedExtensions": [".exe", ".bat", ".ps1"] + } + ] + } +} +``` ---