Commit graph

29 commits

Author SHA1 Message Date
Nick Misasi
ddbcc2d164 feat(python-sdk): export Command class and register command in example
- Export Command wrapper class from main package
- Update hello_python example to register /hello command in OnActivate
- Slash commands must be registered via api.register_command() to work

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 23:13:13 -05:00
Nick Misasi
00ec721e33 feat(08-02): implement Python streaming response writer with flush support
Update HTTPResponseWriter class to support streaming responses:
- Add flush() method for best-effort HTTP flush requests
- Track pending writes with flush markers for streaming mode
- Add get_pending_writes() and clear_pending_writes() methods
- Document streaming mode, header locking, and flush semantics
- Add MAX_CHUNK_SIZE constant (64KB, matching Go)

Update ServeHTTP servicer to stream responses:
- Send response chunks incrementally instead of buffering
- Include flush flag on each message when requested
- Check for cancellation between chunks
- Handle empty body case (init only)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:37:30 -05:00
Nick Misasi
5c7aecda49 feat(08-02): extend protobuf contract for response streaming + flush
Add flush field to ServeHTTPResponse for best-effort HTTP flush support.
Update ServeHTTPResponse and ServeHTTPResponseInit with comprehensive
documentation of streaming invariants, message ordering, and status
code validation rules.

Changes:
- Add flush boolean field (field 4) to ServeHTTPResponse
- Document response streaming invariants (init-once, header locking)
- Document status code validation (100-999 range, 0 defaults to 200)
- Regenerate Go and Python protobuf code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:34:36 -05:00
Nick Misasi
503ab08cbc test(08-01): add comprehensive tests for ServeHTTP streaming
Add tests for both Go and Python ServeHTTP implementations covering:
- Body chunking behavior (small, exact, multiple, large bodies)
- Context cancellation during body streaming
- Header conversion utilities
- HTTPRequest/HTTPResponseWriter helper classes
- Request body assembly from multiple chunks
- Error handling (500 for handler errors, 404 for missing handler)

Go tests:
- TestChunking_* for body chunking scenarios
- TestConvertHTTPHeaders for header conversion
- TestBuildRequestInit for request metadata building
- TestWriteResponseHeaders for response header writing
- TestContextCancellation_DuringBodyRead for cancellation

Python tests:
- TestHTTPRequest for request wrapper class
- TestHTTPResponseWriter for response writer class
- TestHeaderConversion for proto<->dict conversion
- TestServeHTTPServicer for gRPC servicer behavior
- TestChunkingBehavior for body assembly
- TestCancellation for request cancellation

Also:
- Fixed sendRequest to properly set body_complete flag on EOF
- Added ServeHTTP to HookName enum

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:31:30 -05:00
Nick Misasi
147bd106a1 feat(08-01): implement Python ServeHTTP streaming handler
Add ServeHTTP bidirectional streaming implementation to the Python
hook servicer with HTTPRequest and HTTPResponseWriter helper classes.

Key features:
- Async generator for bidirectional streaming
- HTTPRequest wrapper with method, url, headers, body
- HTTPResponseWriter mimicking Go's http.ResponseWriter pattern
- Context cancellation detection during body streaming
- Header conversion utilities for proto <-> dict

Handler pattern:
- Receives: (plugin_context, response_writer, request)
- Similar to Go's http.Handler(w, r) pattern
- Response writer collects headers/status/body

Current limitations (deferred to 08-02):
- Request body fully buffered before handler invocation
- Response body fully buffered before streaming back
- No true streaming for large bodies yet

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:26:23 -05:00
Nick Misasi
19c392084b feat(08-01): add protobuf contract for ServeHTTP request streaming
Add bidirectional streaming RPC for ServeHTTP hook to support
efficient HTTP request/response transfer between Go and Python.

Key changes:
- New hooks_http.proto with ServeHTTPRequest/Response messages
- HTTPHeader message for multi-value header support
- ServeHTTPRequestInit with full request metadata
- ServeHTTPResponseInit for status and headers
- Body chunks with completion flag for streaming
- Updated hooks.proto with ServeHTTP streaming RPC
- Regenerated Go and Python code

Design decisions:
- 64KB default chunk size per gRPC best practices
- First message carries metadata, subsequent messages carry body
- body_complete flag signals end of stream
- Headers as repeated HTTPHeader for multi-value support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 14:23:38 -05:00
Nick Misasi
23eae41f7e feat(07-03): implement remaining hook methods in servicer
- Add ExecuteCommand hook with CommandResponse return semantics
- Add ConfigurationWillBeSaved with config modification support
- Add User lifecycle hooks (UserHasBeenCreated, UserWillLogIn,
  UserHasLoggedIn, UserHasBeenDeactivated)
- Add Channel/Team hooks (ChannelHasBeenCreated,
  UserHasJoined/LeftChannel, UserHasJoined/LeftTeam)
- Add Reaction hooks (ReactionHasBeenAdded, ReactionHasBeenRemoved)
- Add Notification hooks (NotificationWillBePushed,
  EmailNotificationWillBeSent) with modify/reject semantics
- Add PreferencesHaveChanged hook
- Add System hooks (OnInstall, OnSendDailyTelemetry,
  RunDataRetention, OnCloudLimitsUpdated)
- Add WebSocket hooks (OnWebSocketConnect, OnWebSocketDisconnect,
  WebSocketMessageHasBeenPosted)
- Add Cluster hook (OnPluginClusterEvent)
- Add Shared Channels hooks (OnSharedChannelsSyncMsg, OnSharedChannelsPing,
  OnSharedChannelsAttachmentSyncMsg, OnSharedChannelsProfileImageSyncMsg)
- Add Support hook (GenerateSupportData)
- Add SAML hook (OnSAMLLogin)
- Add implementation matrix comment documenting all hooks
- Document deferred Phase 8 hooks (ServeHTTP, ServeMetrics, FileWillBeUploaded)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:33:09 -05:00
Nick Misasi
ab345d7e57 test(07-02): add gRPC integration tests for hook servicer
- Add test_hooks_grpc_integration.py with in-process gRPC tests:
  - Test Implemented RPC returns correct hook list
  - Test lifecycle hooks (OnActivate, OnDeactivate, OnConfigurationChange)
  - Test MessageWillBePosted allow/reject/modify scenarios
  - Test notification hooks (MessageHasBeenPosted)
  - Test activation failure propagation via response.error
  - Test async handlers work through gRPC
- Fix lifecycle hooks to encode errors in response, not gRPC status
  - Remove context parameter from runner.invoke() for lifecycle hooks
  - Ensures activation failures are returned as AppError, not gRPC error

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:27:41 -05:00
Nick Misasi
6392b981fe test(07-02): add tests for lifecycle and message hook semantics
- Add test_hooks_lifecycle.py with tests for:
  - Implemented RPC returning correct hook list
  - OnActivate success/failure propagation
  - OnDeactivate best-effort semantics
  - OnConfigurationChange error handling
- Add test_hooks_messages.py with tests for:
  - MessageWillBePosted allow/reject/modify/dismiss semantics
  - MessageWillBeUpdated with old/new post handling
  - Notification hooks (Posted/Updated/Deleted)
  - MessagesWillBeConsumed list filtering/modification
- Fix @hook decorator to preserve async handlers
  - Use separate async wrapper for coroutine functions
  - Enables proper async detection in hook runner

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:26:03 -05:00
Nick Misasi
6d2974c821 feat(07-02): implement hook servicer with lifecycle and message hooks
- Create PluginHooksServicerImpl implementing PluginHooksServicer gRPC service
- Implement Implemented RPC returning canonical hook names from registry
- Implement lifecycle hooks: OnActivate, OnDeactivate, OnConfigurationChange
- Implement message hooks: MessageWillBePosted, MessageWillBeUpdated
- Implement notification hooks: MessageHasBeenPosted/Updated/Deleted
- Implement MessagesWillBeConsumed with post list modification support
- Add DISMISS_POST_ERROR constant for silent post dismissal
- Register hook servicer in PluginServer.start()
- Document error handling convention in module docstring

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:22:56 -05:00
Nick Misasi
49a7a6a121 feat(07-01): export hook system public API and add dependencies
- Export Plugin, hook, HookName, HookRegistrationError from __init__.py
- Export RuntimeConfig, load_runtime_config
- Update module docstring with plugin development example
- Add grpcio-health-checking dependency
- Add typing_extensions for Python 3.9 compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:28 -05:00
Nick Misasi
8bda0eb6ff feat(07-01): add plugin server with health service and handshake
- Create server.py with PluginServer class for gRPC server lifecycle
- Register grpc.health.v1.Health service with plugin status SERVING
- Output go-plugin handshake line (1|1|tcp|127.0.0.1:PORT|grpc)
- Bind to ephemeral port on localhost for security
- Add serve_plugin() and run_plugin() entry points

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:22 -05:00
Nick Misasi
4fb3c4ed4c feat(07-01): add runtime config loader for plugin environment
- Create runtime_config.py to load config from environment variables
- Support MATTERMOST_PLUGIN_ID, MATTERMOST_PLUGIN_API_TARGET
- Support MATTERMOST_PLUGIN_HOOK_TIMEOUT, MATTERMOST_PLUGIN_LOG_LEVEL
- Provide sensible defaults and configure_logging helper

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:16 -05:00
Nick Misasi
ef13892007 feat(07-01): add hook runner utility with timeout support
- Create hook_runner.py with run_hook_async function
- Support both sync and async handlers via asyncio.to_thread
- Enforce timeouts with asyncio.wait_for (default 30s)
- Convert exceptions to gRPC status codes
- Add HookRunner class for convenient invocation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:11 -05:00
Nick Misasi
708e033b57 feat(07-01): add Plugin base class with hook registry
- Create plugin.py with Plugin base class for plugin authors
- Use __init_subclass__ for automatic hook discovery at class definition
- Implement implemented_hooks(), has_hook(), invoke_hook() methods
- Provide api, logger, config properties for plugin instances
- Enforce single handler per hook with clear duplicate error

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:18:05 -05:00
Nick Misasi
a959e8dec0 feat(07-01): add hook decorator and HookName enum
- Create hooks.py with @hook decorator for registration
- Add HookName enum with all canonical hook names from hooks.proto
- Support multiple decorator forms: @hook(HookName.X), @hook("X"), @hook
- Preserve function metadata with functools.wraps
- Validate hook names and raise HookRegistrationError for invalid names

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:17:59 -05:00
Nick Misasi
49fb5d8fd9 feat(06-04): implement remaining API client mixins for full RPC parity
Add comprehensive typed API client methods for all remaining Mattermost
Plugin API RPCs:

New wrapper types in wrappers.py:
- Bot, BotPatch, BotGetOptions for bot management
- Command, CommandArgs, CommandResponse for slash commands
- Preference for user preferences
- OAuthApp for OAuth applications
- Group, GroupMember, GroupSyncable for groups
- SharedChannel for shared channels
- Emoji for custom emojis
- Dialog, DialogElement, OpenDialogRequest for interactive dialogs
- AuditRecord for audit logging
- PushNotification for push notifications

New client mixins:
- BotsMixin: Bot CRUD, activation, EnsureBotUser
- CommandsMixin: Command registration/CRUD, execution, listing
- ConfigMixin: Server/plugin config, plugin management
- PreferencesMixin: User preference CRUD
- OAuthMixin: OAuth app CRUD
- GroupsMixin: Group/membership/syncable CRUD
- PropertiesMixin: Property group/field/value CRUD
- RemainingMixin: License, shared channels, dialogs, mail,
  notifications, emojis, upload sessions, post search

The audit script now reports 100% RPC coverage (236/236).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:17:32 -05:00
Nick Misasi
9bd8a450a5 feat(06-03): integrate Post/File/KV mixins into PluginAPIClient
Update PluginAPIClient to inherit from PostsMixin, FilesMixin,
and KVStoreMixin, providing 31 new typed methods for:
- Post CRUD, ephemeral posts, and reactions
- File metadata, content, and uploads
- Key-value store operations with atomic support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:06:30 -05:00
Nick Misasi
a0149f3dc0 feat(06-03): implement KVStoreMixin with key-value store methods
Add KVStoreMixin with 9 methods:
- kv_set, kv_get, kv_delete, kv_delete_all, kv_list
- kv_set_with_expiry
- kv_compare_and_set, kv_compare_and_delete, kv_set_with_options

Provides complete KV store functionality including atomic operations
and expiring keys for plugin data persistence.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:06:23 -05:00
Nick Misasi
7a171a1149 feat(06-03): implement FilesMixin with file API methods
Add FilesMixin with 8 methods:
- get_file_info, get_file_infos, set_file_searchable_content
- get_file, get_file_link, read_file
- upload_file, copy_file_infos

Covers all file metadata, content retrieval, and upload operations
following the established mixin pattern.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:06:17 -05:00
Nick Misasi
eaad7aff18 feat(06-03): implement PostsMixin with post and reaction methods
Add PostsMixin with 15 methods:
- create_post, get_post, update_post, delete_post
- send_ephemeral_post, update_ephemeral_post, delete_ephemeral_post
- get_post_thread, get_posts_since, get_posts_after, get_posts_before
- add_reaction, remove_reaction, get_reactions

All methods follow the established mixin pattern with proper error
handling and type annotations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:06:11 -05:00
Nick Misasi
31c3bd6a89 feat(06-03): add wrapper dataclasses for Post, Reaction, FileInfo, KV types
Add dataclasses for:
- Post: Mattermost message with full field support
- Reaction: User emoji reaction to a post
- PostList: Ordered list of posts with pagination
- FileInfo: File metadata with all attributes
- UploadSession: Resumable upload session
- PluginKVSetOptions: Options for KV set operations

All wrappers include from_proto() and to_proto() methods for
protobuf serialization.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:06:04 -05:00
Nick Misasi
f598ab0121 fix(06-02): connect mixin classes to PluginAPIClient
Add mixin imports and inheritance to PluginAPIClient so that the
User/Team/Channel API methods are actually available on the client.

This was missing from the previous mixin implementation commit.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 09:55:01 -05:00
Nick Misasi
e1dfd1ec5f feat(06-02): implement User/Team/Channel API client mixins
Add domain-specific mixin classes for PluginAPIClient:
- UsersMixin: 33 user methods (CRUD, status, auth, sessions, permissions)
- TeamsMixin: 20 team methods (CRUD, members, icons, stats)
- ChannelsMixin: 25 channel methods (CRUD, members, sidebar categories)

Each method:
- Builds correct protobuf requests from Python inputs
- Converts responses to Pythonic wrapper types
- Maps gRPC/AppError to SDK exceptions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:55:56 -05:00
Nick Misasi
4411ccbcbf feat(06-02): add wrapper dataclasses for User, Team, Channel types
Add Pythonic wrapper types for core Mattermost entities:
- User, UserStatus, CustomStatus, UserAuth, Session, UserAccessToken
- Team, TeamMember, TeamMemberWithError, TeamUnread, TeamStats
- Channel, ChannelMember, ChannelStats, SidebarCategoryWithChannels
- ViewUsersRestrictions and related enums

All wrappers implement from_proto/to_proto for protobuf conversion.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:55:48 -05:00
Nick Misasi
0c905f4d07 feat(06-01): add generated protobuf and gRPC Python code
Generate Python code from server/public/pluginapi/grpc/proto:
- Protocol buffer message definitions (*_pb2.py)
- gRPC service stubs (*_pb2_grpc.py)
- Type stubs for IDE completion (*_pb2.pyi, *_pb2_grpc.pyi)
- Imports fixed to use package-relative imports

Generated from 18 .proto files using grpcio-tools and mypy-protobuf.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:44:54 -05:00
Nick Misasi
5cff580fcb feat(06-01): add sync and async Plugin API clients
Add client classes for Mattermost Plugin API:
- PluginAPIClient: sync client with context manager support
- AsyncPluginAPIClient: async client with grpc.aio support
- Both implement get_server_version() as smoke test RPC
- Proper error handling converts gRPC errors to SDK exceptions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:44:47 -05:00
Nick Misasi
d52baf9345 feat(06-01): add channel factory and exception hierarchy
Add core SDK runtime components:
- channel.py: gRPC channel factory with keepalive and size options
- exceptions.py: SDK exception hierarchy with gRPC error mapping
  - PluginAPIError (base), NotFoundError, PermissionDeniedError,
    ValidationError, AlreadyExistsError, UnavailableError
- convert_grpc_error() and convert_app_error() utilities

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:44:41 -05:00
Nick Misasi
3cf6e36477 feat(06-01): add Python SDK package scaffold with pyproject.toml
Create the foundational Python SDK package structure:
- pyproject.toml with project metadata and dependencies
- src/mattermost_plugin/ package with src layout
- Configured for Python >=3.9 with grpcio and protobuf deps
- Dev dependencies include grpcio-tools, mypy-protobuf, pytest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:44:29 -05:00