@shakudo/opencode-mattermost-control 0.3.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.opencode/command/mattermost-connect.md +5 -0
  2. package/.opencode/command/mattermost-disconnect.md +5 -0
  3. package/.opencode/command/mattermost-monitor.md +12 -0
  4. package/.opencode/command/mattermost-status.md +5 -0
  5. package/.opencode/command/speckit.analyze.md +184 -0
  6. package/.opencode/command/speckit.checklist.md +294 -0
  7. package/.opencode/command/speckit.clarify.md +181 -0
  8. package/.opencode/command/speckit.constitution.md +82 -0
  9. package/.opencode/command/speckit.implement.md +135 -0
  10. package/.opencode/command/speckit.plan.md +89 -0
  11. package/.opencode/command/speckit.specify.md +258 -0
  12. package/.opencode/command/speckit.tasks.md +137 -0
  13. package/.opencode/command/speckit.taskstoissues.md +30 -0
  14. package/.opencode/plugin/mattermost-control/event-handlers/compaction.ts +61 -0
  15. package/.opencode/plugin/mattermost-control/event-handlers/file.ts +36 -0
  16. package/.opencode/plugin/mattermost-control/event-handlers/index.ts +14 -0
  17. package/.opencode/plugin/mattermost-control/event-handlers/message.ts +124 -0
  18. package/.opencode/plugin/mattermost-control/event-handlers/permission.ts +34 -0
  19. package/.opencode/plugin/mattermost-control/event-handlers/question.ts +92 -0
  20. package/.opencode/plugin/mattermost-control/event-handlers/session.ts +100 -0
  21. package/.opencode/plugin/mattermost-control/event-handlers/todo.ts +33 -0
  22. package/.opencode/plugin/mattermost-control/event-handlers/tool.ts +76 -0
  23. package/.opencode/plugin/mattermost-control/formatters.ts +202 -0
  24. package/.opencode/plugin/mattermost-control/index.ts +964 -0
  25. package/.opencode/plugin/mattermost-control/package.json +12 -0
  26. package/.opencode/plugin/mattermost-control/state.ts +180 -0
  27. package/.opencode/plugin/mattermost-control/timers.ts +96 -0
  28. package/.opencode/plugin/mattermost-control/tools/connect.ts +563 -0
  29. package/.opencode/plugin/mattermost-control/tools/file.ts +41 -0
  30. package/.opencode/plugin/mattermost-control/tools/index.ts +12 -0
  31. package/.opencode/plugin/mattermost-control/tools/monitor.ts +183 -0
  32. package/.opencode/plugin/mattermost-control/tools/schedule.ts +253 -0
  33. package/.opencode/plugin/mattermost-control/tools/session.ts +120 -0
  34. package/.opencode/plugin/mattermost-control/types.ts +107 -0
  35. package/LICENSE +21 -0
  36. package/README.md +1280 -0
  37. package/opencode-shared +359 -0
  38. package/opencode-shared-restart +495 -0
  39. package/opencode-shared-stop +90 -0
  40. package/package.json +65 -0
  41. package/src/clients/mattermost-client.ts +221 -0
  42. package/src/clients/websocket-client.ts +199 -0
  43. package/src/command-handler.ts +1035 -0
  44. package/src/config.ts +170 -0
  45. package/src/context-builder.ts +309 -0
  46. package/src/file-completion-handler.ts +521 -0
  47. package/src/file-handler.ts +242 -0
  48. package/src/guest-approval-handler.ts +223 -0
  49. package/src/logger.ts +73 -0
  50. package/src/merge-handler.ts +335 -0
  51. package/src/message-router.ts +151 -0
  52. package/src/models/index.ts +197 -0
  53. package/src/models/routing.ts +50 -0
  54. package/src/models/thread-mapping.ts +40 -0
  55. package/src/monitor-service.ts +222 -0
  56. package/src/notification-service.ts +118 -0
  57. package/src/opencode-session-registry.ts +370 -0
  58. package/src/persistence/team-store.ts +396 -0
  59. package/src/persistence/thread-mapping-store.ts +258 -0
  60. package/src/question-handler.ts +401 -0
  61. package/src/reaction-handler.ts +111 -0
  62. package/src/response-streamer.ts +364 -0
  63. package/src/scheduler/schedule-store.ts +261 -0
  64. package/src/scheduler/scheduler-service.ts +349 -0
  65. package/src/session-manager.ts +142 -0
  66. package/src/session-ownership-handler.ts +253 -0
  67. package/src/status-indicator.ts +279 -0
  68. package/src/thread-manager.ts +231 -0
  69. package/src/todo-manager.ts +162 -0
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "opencode-mattermost-control",
3
+ "version": "0.1.12",
4
+ "type": "module",
5
+ "main": "index.ts",
6
+ "dependencies": {
7
+ "@opencode-ai/plugin": "latest",
8
+ "axios": "^1.7.9",
9
+ "ws": "^8.18.0",
10
+ "zod": "^3.24.1"
11
+ }
12
+ }
@@ -0,0 +1,180 @@
1
+ import type { ResponseContext } from "./types.js";
2
+ import type { MattermostClient } from "../../../src/clients/mattermost-client.js";
3
+ import type { MattermostWebSocketClient } from "../../../src/clients/websocket-client.js";
4
+ import type { SessionManager } from "../../../src/session-manager.js";
5
+ import type { ResponseStreamer } from "../../../src/response-streamer.js";
6
+ import type { NotificationService } from "../../../src/notification-service.js";
7
+ import type { FileHandler } from "../../../src/file-handler.js";
8
+ import type { ReactionHandler } from "../../../src/reaction-handler.js";
9
+ import type { OpenCodeSessionRegistry } from "../../../src/opencode-session-registry.js";
10
+ import type { MessageRouter } from "../../../src/message-router.js";
11
+ import type { CommandHandler } from "../../../src/command-handler.js";
12
+ import type { ThreadMappingStore } from "../../../src/persistence/thread-mapping-store.js";
13
+ import type { ThreadManager } from "../../../src/thread-manager.js";
14
+ import type { TodoManager } from "../../../src/todo-manager.js";
15
+ import type { QuestionHandler } from "../../../src/question-handler.js";
16
+ import type { GuestApprovalHandler } from "../../../src/guest-approval-handler.js";
17
+ import type { SessionOwnershipHandler } from "../../../src/session-ownership-handler.js";
18
+ import type { FileCompletionHandler } from "../../../src/file-completion-handler.js";
19
+ import type { SchedulerService } from "../../../src/scheduler/scheduler-service.js";
20
+ import type { TeamStore } from "../../../src/persistence/team-store.js";
21
+ import type { User } from "../../../src/models/index.js";
22
+
23
+ class PluginStateManager {
24
+ private _isConnected = false;
25
+ private _mmClient: MattermostClient | null = null;
26
+ private _wsClient: MattermostWebSocketClient | null = null;
27
+ private _sessionManager: SessionManager | null = null;
28
+ private _streamer: ResponseStreamer | null = null;
29
+ private _notifications: NotificationService | null = null;
30
+ private _fileHandler: FileHandler | null = null;
31
+ private _reactionHandler: ReactionHandler | null = null;
32
+ private _openCodeSessionRegistry: OpenCodeSessionRegistry | null = null;
33
+ private _messageRouter: MessageRouter | null = null;
34
+ private _commandHandler: CommandHandler | null = null;
35
+ private _threadMappingStore: ThreadMappingStore | null = null;
36
+ private _threadManager: ThreadManager | null = null;
37
+ private _todoManager: TodoManager | null = null;
38
+ private _questionHandler: QuestionHandler | null = null;
39
+ private _guestApprovalHandler: GuestApprovalHandler | null = null;
40
+ private _sessionOwnershipHandler: SessionOwnershipHandler | null = null;
41
+ private _fileCompletionHandler: FileCompletionHandler | null = null;
42
+ private _schedulerService: SchedulerService | null = null;
43
+ private _teamStore: TeamStore | null = null;
44
+ private _botUser: User | null = null;
45
+ private _projectName: string = "";
46
+
47
+ readonly activeResponseContexts: Map<string, ResponseContext> = new Map();
48
+ readonly activeToolTimers: Map<string, ReturnType<typeof setInterval>> = new Map();
49
+ readonly activeResponseTimers: Map<string, ReturnType<typeof setInterval>> = new Map();
50
+ private _questionCleanupTimer: ReturnType<typeof setInterval> | null = null;
51
+
52
+ get isConnected(): boolean { return this._isConnected; }
53
+ get projectName(): string { return this._projectName; }
54
+ get botUser(): User | null { return this._botUser; }
55
+
56
+ get mmClient(): MattermostClient | null { return this._mmClient; }
57
+ get wsClient(): MattermostWebSocketClient | null { return this._wsClient; }
58
+ get sessionManager(): SessionManager | null { return this._sessionManager; }
59
+ get streamer(): ResponseStreamer | null { return this._streamer; }
60
+ get notifications(): NotificationService | null { return this._notifications; }
61
+ get fileHandler(): FileHandler | null { return this._fileHandler; }
62
+ get reactionHandler(): ReactionHandler | null { return this._reactionHandler; }
63
+ get openCodeSessionRegistry(): OpenCodeSessionRegistry | null { return this._openCodeSessionRegistry; }
64
+ get messageRouter(): MessageRouter | null { return this._messageRouter; }
65
+ get commandHandler(): CommandHandler | null { return this._commandHandler; }
66
+ get threadMappingStore(): ThreadMappingStore | null { return this._threadMappingStore; }
67
+ get threadManager(): ThreadManager | null { return this._threadManager; }
68
+ get todoManager(): TodoManager | null { return this._todoManager; }
69
+ get questionHandler(): QuestionHandler | null { return this._questionHandler; }
70
+ get guestApprovalHandler(): GuestApprovalHandler | null { return this._guestApprovalHandler; }
71
+ get sessionOwnershipHandler(): SessionOwnershipHandler | null { return this._sessionOwnershipHandler; }
72
+ get fileCompletionHandler(): FileCompletionHandler | null { return this._fileCompletionHandler; }
73
+ get questionCleanupTimer(): ReturnType<typeof setInterval> | null { return this._questionCleanupTimer; }
74
+ get schedulerService(): SchedulerService | null { return this._schedulerService; }
75
+ get teamStore(): TeamStore | null { return this._teamStore; }
76
+
77
+ setProjectName(name: string): void {
78
+ this._projectName = name;
79
+ }
80
+
81
+ setThreadMappingStore(store: ThreadMappingStore): void {
82
+ this._threadMappingStore = store;
83
+ }
84
+
85
+ setConnected(
86
+ mmClient: MattermostClient,
87
+ wsClient: MattermostWebSocketClient,
88
+ sessionManager: SessionManager,
89
+ streamer: ResponseStreamer,
90
+ notifications: NotificationService,
91
+ fileHandler: FileHandler,
92
+ reactionHandler: ReactionHandler,
93
+ openCodeSessionRegistry: OpenCodeSessionRegistry,
94
+ messageRouter: MessageRouter,
95
+ commandHandler: CommandHandler,
96
+ threadManager: ThreadManager | null,
97
+ todoManager: TodoManager,
98
+ questionHandler: QuestionHandler,
99
+ guestApprovalHandler: GuestApprovalHandler,
100
+ sessionOwnershipHandler: SessionOwnershipHandler,
101
+ botUser: User
102
+ ): void {
103
+ this._isConnected = true;
104
+ this._mmClient = mmClient;
105
+ this._wsClient = wsClient;
106
+ this._sessionManager = sessionManager;
107
+ this._streamer = streamer;
108
+ this._notifications = notifications;
109
+ this._fileHandler = fileHandler;
110
+ this._reactionHandler = reactionHandler;
111
+ this._openCodeSessionRegistry = openCodeSessionRegistry;
112
+ this._messageRouter = messageRouter;
113
+ this._commandHandler = commandHandler;
114
+ this._threadManager = threadManager;
115
+ this._todoManager = todoManager;
116
+ this._questionHandler = questionHandler;
117
+ this._guestApprovalHandler = guestApprovalHandler;
118
+ this._sessionOwnershipHandler = sessionOwnershipHandler;
119
+ this._botUser = botUser;
120
+ }
121
+
122
+ setQuestionCleanupTimer(timer: ReturnType<typeof setInterval> | null): void {
123
+ this._questionCleanupTimer = timer;
124
+ }
125
+
126
+ setFileCompletionHandler(handler: FileCompletionHandler): void {
127
+ this._fileCompletionHandler = handler;
128
+ }
129
+
130
+ setSchedulerService(service: SchedulerService): void {
131
+ this._schedulerService = service;
132
+ }
133
+
134
+ setTeamStore(store: TeamStore): void {
135
+ this._teamStore = store;
136
+ }
137
+
138
+ disconnect(): void {
139
+ // Stop the scheduler first
140
+ if (this._schedulerService) {
141
+ this._schedulerService.stop();
142
+ this._schedulerService = null;
143
+ }
144
+ // Shutdown team store
145
+ if (this._teamStore) {
146
+ this._teamStore.shutdown();
147
+ this._teamStore = null;
148
+ }
149
+ for (const [_, timer] of this.activeToolTimers) {
150
+ clearInterval(timer);
151
+ }
152
+ this.activeToolTimers.clear();
153
+ this.activeResponseContexts.clear();
154
+
155
+ this._wsClient?.disconnect();
156
+ this._sessionManager?.shutdown();
157
+ this._fileHandler?.cleanupTempFiles();
158
+ this._openCodeSessionRegistry?.clear();
159
+ this._threadMappingStore?.shutdown();
160
+
161
+ this._isConnected = false;
162
+ this._mmClient = null;
163
+ this._wsClient = null;
164
+ this._sessionManager = null;
165
+ this._streamer = null;
166
+ this._notifications = null;
167
+ this._fileHandler = null;
168
+ this._reactionHandler = null;
169
+ this._openCodeSessionRegistry = null;
170
+ this._messageRouter = null;
171
+ this._commandHandler = null;
172
+ this._threadManager = null;
173
+ this._questionHandler = null;
174
+ this._guestApprovalHandler = null;
175
+ this._sessionOwnershipHandler = null;
176
+ this._fileCompletionHandler = null;
177
+ }
178
+ }
179
+
180
+ export const PluginState = new PluginStateManager();
@@ -0,0 +1,96 @@
1
+ import { PluginState } from "./state.js";
2
+ import { formatFullResponse } from "./formatters.js";
3
+ import { log } from "../../../src/logger.js";
4
+
5
+ export const TOOL_UPDATE_INTERVAL_MS = 1000;
6
+ export const QUESTION_CLEANUP_INTERVAL_MS = 5 * 60 * 1000;
7
+ export const QUESTION_EXPIRY_MS = 30 * 60 * 1000;
8
+
9
+ export async function updateResponseStream(sessionId: string): Promise<void> {
10
+ const ctx = PluginState.activeResponseContexts.get(sessionId);
11
+ if (!ctx || !PluginState.streamer) return;
12
+
13
+ const formattedOutput = formatFullResponse(ctx);
14
+
15
+ try {
16
+ await PluginState.streamer.updateStream(ctx.streamCtx, formattedOutput);
17
+ } catch (e) {
18
+ log.error("Failed to update stream:", e);
19
+ }
20
+ }
21
+
22
+ export function startActiveToolTimer(sessionId: string): void {
23
+ if (PluginState.activeToolTimers.has(sessionId)) return;
24
+
25
+ const timer = setInterval(async () => {
26
+ const ctx = PluginState.activeResponseContexts.get(sessionId);
27
+ if (!ctx?.activeTool) {
28
+ stopActiveToolTimer(sessionId);
29
+ return;
30
+ }
31
+ await updateResponseStream(sessionId);
32
+ }, TOOL_UPDATE_INTERVAL_MS);
33
+
34
+ PluginState.activeToolTimers.set(sessionId, timer);
35
+ }
36
+
37
+ export function stopActiveToolTimer(sessionId: string): void {
38
+ const timer = PluginState.activeToolTimers.get(sessionId);
39
+ if (timer) {
40
+ clearInterval(timer);
41
+ PluginState.activeToolTimers.delete(sessionId);
42
+ }
43
+ }
44
+
45
+ export function startResponseTimer(sessionId: string): void {
46
+ if (PluginState.activeResponseTimers.has(sessionId)) return;
47
+
48
+ const timer = setInterval(async () => {
49
+ const ctx = PluginState.activeResponseContexts.get(sessionId);
50
+ if (!ctx) {
51
+ stopResponseTimer(sessionId);
52
+ return;
53
+ }
54
+ await updateResponseStream(sessionId);
55
+ }, TOOL_UPDATE_INTERVAL_MS);
56
+
57
+ PluginState.activeResponseTimers.set(sessionId, timer);
58
+ }
59
+
60
+ export function stopResponseTimer(sessionId: string): void {
61
+ const timer = PluginState.activeResponseTimers.get(sessionId);
62
+ if (timer) {
63
+ clearInterval(timer);
64
+ PluginState.activeResponseTimers.delete(sessionId);
65
+ }
66
+ }
67
+
68
+ export function startQuestionCleanupTimer(): void {
69
+ if (PluginState.questionCleanupTimer) return;
70
+
71
+ const timer = setInterval(async () => {
72
+ if (PluginState.questionHandler) {
73
+ const syncResult = await PluginState.questionHandler.syncWithServer();
74
+ if (syncResult.removed > 0) {
75
+ log.info(`[QuestionHandler] Sync removed ${syncResult.removed} stale questions (server has ${syncResult.synced} pending)`);
76
+ }
77
+
78
+ const cleaned = PluginState.questionHandler.cleanupExpired(QUESTION_EXPIRY_MS);
79
+ if (cleaned > 0) {
80
+ log.info(`[QuestionHandler] Cleaned up ${cleaned} expired questions`);
81
+ }
82
+ }
83
+ }, QUESTION_CLEANUP_INTERVAL_MS);
84
+
85
+ PluginState.setQuestionCleanupTimer(timer);
86
+ log.debug("[QuestionHandler] Started cleanup timer");
87
+ }
88
+
89
+ export function stopQuestionCleanupTimer(): void {
90
+ const timer = PluginState.questionCleanupTimer;
91
+ if (timer) {
92
+ clearInterval(timer);
93
+ PluginState.setQuestionCleanupTimer(null);
94
+ log.debug("[QuestionHandler] Stopped cleanup timer");
95
+ }
96
+ }