sylas-edge-worker 0.2.21

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 (163) hide show
  1. package/README.md +293 -0
  2. package/dist/ActivityPoster.d.ts +15 -0
  3. package/dist/ActivityPoster.d.ts.map +1 -0
  4. package/dist/ActivityPoster.js +194 -0
  5. package/dist/ActivityPoster.js.map +1 -0
  6. package/dist/AgentSessionManager.d.ts +280 -0
  7. package/dist/AgentSessionManager.d.ts.map +1 -0
  8. package/dist/AgentSessionManager.js +1412 -0
  9. package/dist/AgentSessionManager.js.map +1 -0
  10. package/dist/AskUserQuestionHandler.d.ts +97 -0
  11. package/dist/AskUserQuestionHandler.d.ts.map +1 -0
  12. package/dist/AskUserQuestionHandler.js +206 -0
  13. package/dist/AskUserQuestionHandler.js.map +1 -0
  14. package/dist/AttachmentService.d.ts +69 -0
  15. package/dist/AttachmentService.d.ts.map +1 -0
  16. package/dist/AttachmentService.js +369 -0
  17. package/dist/AttachmentService.js.map +1 -0
  18. package/dist/ChatSessionHandler.d.ts +87 -0
  19. package/dist/ChatSessionHandler.d.ts.map +1 -0
  20. package/dist/ChatSessionHandler.js +231 -0
  21. package/dist/ChatSessionHandler.js.map +1 -0
  22. package/dist/ConfigManager.d.ts +91 -0
  23. package/dist/ConfigManager.d.ts.map +1 -0
  24. package/dist/ConfigManager.js +227 -0
  25. package/dist/ConfigManager.js.map +1 -0
  26. package/dist/EdgeWorker.d.ts +670 -0
  27. package/dist/EdgeWorker.d.ts.map +1 -0
  28. package/dist/EdgeWorker.js +3801 -0
  29. package/dist/EdgeWorker.js.map +1 -0
  30. package/dist/GitService.d.ts +39 -0
  31. package/dist/GitService.d.ts.map +1 -0
  32. package/dist/GitService.js +432 -0
  33. package/dist/GitService.js.map +1 -0
  34. package/dist/GlobalSessionRegistry.d.ts +142 -0
  35. package/dist/GlobalSessionRegistry.d.ts.map +1 -0
  36. package/dist/GlobalSessionRegistry.js +254 -0
  37. package/dist/GlobalSessionRegistry.js.map +1 -0
  38. package/dist/PromptBuilder.d.ts +175 -0
  39. package/dist/PromptBuilder.d.ts.map +1 -0
  40. package/dist/PromptBuilder.js +884 -0
  41. package/dist/PromptBuilder.js.map +1 -0
  42. package/dist/RepositoryRouter.d.ts +152 -0
  43. package/dist/RepositoryRouter.d.ts.map +1 -0
  44. package/dist/RepositoryRouter.js +480 -0
  45. package/dist/RepositoryRouter.js.map +1 -0
  46. package/dist/RunnerSelectionService.d.ts +62 -0
  47. package/dist/RunnerSelectionService.d.ts.map +1 -0
  48. package/dist/RunnerSelectionService.js +379 -0
  49. package/dist/RunnerSelectionService.js.map +1 -0
  50. package/dist/SharedApplicationServer.d.ts +107 -0
  51. package/dist/SharedApplicationServer.d.ts.map +1 -0
  52. package/dist/SharedApplicationServer.js +247 -0
  53. package/dist/SharedApplicationServer.js.map +1 -0
  54. package/dist/SharedWebhookServer.d.ts +39 -0
  55. package/dist/SharedWebhookServer.d.ts.map +1 -0
  56. package/dist/SharedWebhookServer.js +150 -0
  57. package/dist/SharedWebhookServer.js.map +1 -0
  58. package/dist/SlackChatAdapter.d.ts +25 -0
  59. package/dist/SlackChatAdapter.d.ts.map +1 -0
  60. package/dist/SlackChatAdapter.js +143 -0
  61. package/dist/SlackChatAdapter.js.map +1 -0
  62. package/dist/UserAccessControl.d.ts +69 -0
  63. package/dist/UserAccessControl.d.ts.map +1 -0
  64. package/dist/UserAccessControl.js +171 -0
  65. package/dist/UserAccessControl.js.map +1 -0
  66. package/dist/WorktreeIncludeService.d.ts +32 -0
  67. package/dist/WorktreeIncludeService.d.ts.map +1 -0
  68. package/dist/WorktreeIncludeService.js +123 -0
  69. package/dist/WorktreeIncludeService.js.map +1 -0
  70. package/dist/index.d.ts +22 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +17 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/label-prompt-template.md +27 -0
  75. package/dist/procedures/ProcedureAnalyzer.d.ts +69 -0
  76. package/dist/procedures/ProcedureAnalyzer.d.ts.map +1 -0
  77. package/dist/procedures/ProcedureAnalyzer.js +271 -0
  78. package/dist/procedures/ProcedureAnalyzer.js.map +1 -0
  79. package/dist/procedures/index.d.ts +7 -0
  80. package/dist/procedures/index.d.ts.map +1 -0
  81. package/dist/procedures/index.js +7 -0
  82. package/dist/procedures/index.js.map +1 -0
  83. package/dist/procedures/registry.d.ts +156 -0
  84. package/dist/procedures/registry.d.ts.map +1 -0
  85. package/dist/procedures/registry.js +240 -0
  86. package/dist/procedures/registry.js.map +1 -0
  87. package/dist/procedures/types.d.ts +103 -0
  88. package/dist/procedures/types.d.ts.map +1 -0
  89. package/dist/procedures/types.js +5 -0
  90. package/dist/procedures/types.js.map +1 -0
  91. package/dist/prompt-assembly/types.d.ts +80 -0
  92. package/dist/prompt-assembly/types.d.ts.map +1 -0
  93. package/dist/prompt-assembly/types.js +8 -0
  94. package/dist/prompt-assembly/types.js.map +1 -0
  95. package/dist/prompts/builder.md +191 -0
  96. package/dist/prompts/debugger.md +128 -0
  97. package/dist/prompts/graphite-orchestrator.md +362 -0
  98. package/dist/prompts/orchestrator.md +290 -0
  99. package/dist/prompts/scoper.md +95 -0
  100. package/dist/prompts/standard-issue-assigned-user-prompt.md +33 -0
  101. package/dist/prompts/subroutines/changelog-update.md +79 -0
  102. package/dist/prompts/subroutines/coding-activity.md +12 -0
  103. package/dist/prompts/subroutines/concise-summary.md +67 -0
  104. package/dist/prompts/subroutines/debugger-fix.md +92 -0
  105. package/dist/prompts/subroutines/debugger-reproduction.md +74 -0
  106. package/dist/prompts/subroutines/full-delegation.md +68 -0
  107. package/dist/prompts/subroutines/get-approval.md +175 -0
  108. package/dist/prompts/subroutines/gh-pr.md +80 -0
  109. package/dist/prompts/subroutines/git-commit.md +37 -0
  110. package/dist/prompts/subroutines/plan-summary.md +21 -0
  111. package/dist/prompts/subroutines/preparation.md +16 -0
  112. package/dist/prompts/subroutines/question-answer.md +8 -0
  113. package/dist/prompts/subroutines/question-investigation.md +8 -0
  114. package/dist/prompts/subroutines/release-execution.md +81 -0
  115. package/dist/prompts/subroutines/release-summary.md +60 -0
  116. package/dist/prompts/subroutines/user-testing-summary.md +87 -0
  117. package/dist/prompts/subroutines/user-testing.md +48 -0
  118. package/dist/prompts/subroutines/validation-fixer.md +56 -0
  119. package/dist/prompts/subroutines/verbose-summary.md +46 -0
  120. package/dist/prompts/subroutines/verifications.md +77 -0
  121. package/dist/prompts/todolist-system-prompt-extension.md +15 -0
  122. package/dist/sinks/IActivitySink.d.ts +60 -0
  123. package/dist/sinks/IActivitySink.d.ts.map +1 -0
  124. package/dist/sinks/IActivitySink.js +2 -0
  125. package/dist/sinks/IActivitySink.js.map +1 -0
  126. package/dist/sinks/LinearActivitySink.d.ts +69 -0
  127. package/dist/sinks/LinearActivitySink.d.ts.map +1 -0
  128. package/dist/sinks/LinearActivitySink.js +111 -0
  129. package/dist/sinks/LinearActivitySink.js.map +1 -0
  130. package/dist/sinks/NoopActivitySink.d.ts +13 -0
  131. package/dist/sinks/NoopActivitySink.d.ts.map +1 -0
  132. package/dist/sinks/NoopActivitySink.js +17 -0
  133. package/dist/sinks/NoopActivitySink.js.map +1 -0
  134. package/dist/sinks/index.d.ts +9 -0
  135. package/dist/sinks/index.d.ts.map +1 -0
  136. package/dist/sinks/index.js +8 -0
  137. package/dist/sinks/index.js.map +1 -0
  138. package/dist/types.d.ts +32 -0
  139. package/dist/types.d.ts.map +1 -0
  140. package/dist/types.js +2 -0
  141. package/dist/types.js.map +1 -0
  142. package/dist/validation/ValidationLoopController.d.ts +54 -0
  143. package/dist/validation/ValidationLoopController.d.ts.map +1 -0
  144. package/dist/validation/ValidationLoopController.js +242 -0
  145. package/dist/validation/ValidationLoopController.js.map +1 -0
  146. package/dist/validation/index.d.ts +7 -0
  147. package/dist/validation/index.d.ts.map +1 -0
  148. package/dist/validation/index.js +7 -0
  149. package/dist/validation/index.js.map +1 -0
  150. package/dist/validation/types.d.ts +82 -0
  151. package/dist/validation/types.d.ts.map +1 -0
  152. package/dist/validation/types.js +29 -0
  153. package/dist/validation/types.js.map +1 -0
  154. package/label-prompt-template.md +27 -0
  155. package/package.json +56 -0
  156. package/prompt-template.md +116 -0
  157. package/prompts/builder.md +191 -0
  158. package/prompts/debugger.md +128 -0
  159. package/prompts/graphite-orchestrator.md +362 -0
  160. package/prompts/orchestrator.md +290 -0
  161. package/prompts/scoper.md +95 -0
  162. package/prompts/standard-issue-assigned-user-prompt.md +33 -0
  163. package/prompts/todolist-system-prompt-extension.md +15 -0
@@ -0,0 +1,231 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { ClaudeRunner, getAllTools } from "sylas-claude-runner";
4
+ import { createLogger } from "sylas-core";
5
+ import { AgentSessionManager } from "./AgentSessionManager.js";
6
+ import { NoopActivitySink } from "./sinks/NoopActivitySink.js";
7
+ /**
8
+ * Generic session lifecycle engine for chat platform integrations.
9
+ *
10
+ * Manages the create/resume/inject/reply session lifecycle independent of any
11
+ * specific chat platform. Platform-specific behavior is provided via a
12
+ * ChatPlatformAdapter.
13
+ */
14
+ export class ChatSessionHandler {
15
+ adapter;
16
+ sessionManager;
17
+ threadSessions = new Map();
18
+ deps;
19
+ logger;
20
+ constructor(adapter, deps, logger) {
21
+ this.adapter = adapter;
22
+ this.deps = deps;
23
+ this.logger = logger ?? createLogger({ component: "ChatSessionHandler" });
24
+ // Initialize a dedicated AgentSessionManager (not tied to any repository)
25
+ const activitySink = new NoopActivitySink(adapter.platformName);
26
+ this.sessionManager = new AgentSessionManager(activitySink, undefined, // No parent session lookup
27
+ undefined, // No resume parent session
28
+ undefined, // No procedure analyzer
29
+ undefined);
30
+ }
31
+ /**
32
+ * Main entry point — handles a single chat platform event.
33
+ *
34
+ * Replaces the per-platform handleXxxWebhook method in EdgeWorker.
35
+ */
36
+ async handleEvent(event) {
37
+ this.deps.onWebhookStart();
38
+ try {
39
+ this.logger.info(`Processing ${this.adapter.platformName} webhook: ${this.adapter.getEventId(event)}`);
40
+ // Fire-and-forget acknowledgement (e.g., emoji reaction)
41
+ this.adapter.acknowledgeReceipt(event).catch((err) => {
42
+ this.logger.warn(`Failed to acknowledge ${this.adapter.platformName} event: ${err instanceof Error ? err.message : err}`);
43
+ });
44
+ const taskInstructions = this.adapter.extractTaskInstructions(event);
45
+ const threadKey = this.adapter.getThreadKey(event);
46
+ // Check if there's already an active session for this thread
47
+ const existingSessionId = this.threadSessions.get(threadKey);
48
+ if (existingSessionId) {
49
+ const existingSession = this.sessionManager.getSession(existingSessionId);
50
+ const existingRunner = this.sessionManager.getAgentRunner(existingSessionId);
51
+ if (existingSession && existingRunner?.isRunning()) {
52
+ // Session is actively running — inject the follow-up via streaming input
53
+ if (existingRunner.addStreamMessage &&
54
+ existingRunner.isStreaming?.()) {
55
+ this.logger.info(`Injecting follow-up prompt into running session ${existingSessionId} (thread ${threadKey})`);
56
+ existingRunner.addStreamMessage(taskInstructions);
57
+ }
58
+ else {
59
+ // Runner doesn't support streaming input or isn't in streaming mode — notify user
60
+ this.logger.info(`Session ${existingSessionId} is still running, notifying user (thread ${threadKey})`);
61
+ await this.adapter.notifyBusy(event, threadKey);
62
+ }
63
+ return;
64
+ }
65
+ if (existingSession && existingRunner) {
66
+ // Session exists but is not running — resume with --continue
67
+ this.logger.info(`Resuming completed ${this.adapter.platformName} session ${existingSessionId} (thread ${threadKey})`);
68
+ const resumeSessionId = existingSession.claudeSessionId ||
69
+ existingSession.geminiSessionId ||
70
+ existingSession.codexSessionId ||
71
+ existingSession.cursorSessionId ||
72
+ existingSession.openCodeSessionId;
73
+ if (resumeSessionId) {
74
+ try {
75
+ await this.resumeSession(event, existingSession, existingSessionId, resumeSessionId, taskInstructions);
76
+ }
77
+ catch (error) {
78
+ this.logger.error(`Failed to resume ${this.adapter.platformName} session ${existingSessionId}`, error instanceof Error ? error : new Error(String(error)));
79
+ }
80
+ return;
81
+ }
82
+ }
83
+ // Session exists but runner was lost — fall through to create a new session
84
+ this.logger.info(`Previous session ${existingSessionId} for thread ${threadKey} has no runner, creating new session`);
85
+ }
86
+ // Create an empty workspace directory for this thread
87
+ const workspace = await this.createWorkspace(threadKey);
88
+ if (!workspace) {
89
+ this.logger.error(`Failed to create workspace for ${this.adapter.platformName} thread ${threadKey}`);
90
+ return;
91
+ }
92
+ this.logger.info(`${this.adapter.platformName} workspace created at: ${workspace.path}`);
93
+ // Create a chat session (not tied to any issue or repository)
94
+ const eventId = this.adapter.getEventId(event);
95
+ const sessionId = `${this.adapter.platformName}-${eventId}`;
96
+ this.sessionManager.createChatSession(sessionId, workspace, this.adapter.platformName);
97
+ const session = this.sessionManager.getSession(sessionId);
98
+ if (!session) {
99
+ this.logger.error(`Failed to create session for ${this.adapter.platformName} webhook ${eventId}`);
100
+ return;
101
+ }
102
+ // Track this thread → session mapping for follow-up messages
103
+ this.threadSessions.set(threadKey, sessionId);
104
+ // Initialize procedure metadata
105
+ if (!session.metadata) {
106
+ session.metadata = {};
107
+ }
108
+ // Build the system prompt
109
+ const systemPrompt = this.adapter.buildSystemPrompt(event);
110
+ // Build runner config
111
+ const runnerConfig = this.buildRunnerConfig(session.workspace.path, sessionId, systemPrompt, sessionId);
112
+ const runner = new ClaudeRunner(runnerConfig);
113
+ // Store the runner in the session manager
114
+ this.sessionManager.addAgentRunner(sessionId, runner);
115
+ // Save persisted state
116
+ await this.deps.onStateChange();
117
+ // Fetch thread context for threaded mentions
118
+ const threadContext = await this.adapter.fetchThreadContext(event);
119
+ const userPrompt = threadContext
120
+ ? `${threadContext}\n\n${taskInstructions}`
121
+ : taskInstructions;
122
+ this.logger.info(`Starting Claude runner for ${this.adapter.platformName} event ${eventId}`);
123
+ // Start in streaming mode so follow-up messages in the same thread
124
+ // can be injected via addStreamMessage() while the session is running
125
+ try {
126
+ const sessionInfo = await runner.startStreaming(userPrompt);
127
+ this.logger.info(`${this.adapter.platformName} session started: ${sessionInfo.sessionId}`);
128
+ // When session completes, post the reply back
129
+ await this.adapter.postReply(event, runner);
130
+ }
131
+ catch (error) {
132
+ this.logger.error(`${this.adapter.platformName} session error for event ${eventId}`, error instanceof Error ? error : new Error(String(error)));
133
+ }
134
+ finally {
135
+ await this.deps.onStateChange();
136
+ }
137
+ }
138
+ catch (error) {
139
+ this.logger.error(`Failed to process ${this.adapter.platformName} webhook`, error instanceof Error ? error : new Error(String(error)));
140
+ }
141
+ finally {
142
+ this.deps.onWebhookEnd();
143
+ }
144
+ }
145
+ /** Returns true if any runner managed by this handler is currently busy */
146
+ isAnyRunnerBusy() {
147
+ for (const runner of this.sessionManager.getAllAgentRunners()) {
148
+ if (runner.isRunning()) {
149
+ return true;
150
+ }
151
+ }
152
+ return false;
153
+ }
154
+ /** Returns all runners managed by this handler (for shutdown) */
155
+ getAllRunners() {
156
+ return this.sessionManager.getAllAgentRunners();
157
+ }
158
+ /**
159
+ * Resume an existing session with a new prompt (--continue behavior).
160
+ */
161
+ async resumeSession(event, existingSession, sessionId, resumeSessionId, taskInstructions) {
162
+ const systemPrompt = this.adapter.buildSystemPrompt(event);
163
+ const runnerConfig = this.buildRunnerConfig(existingSession.workspace.path, sessionId, systemPrompt, sessionId, resumeSessionId);
164
+ const runner = new ClaudeRunner(runnerConfig);
165
+ this.sessionManager.addAgentRunner(sessionId, runner);
166
+ try {
167
+ const sessionInfo = await runner.startStreaming(taskInstructions);
168
+ this.logger.info(`${this.adapter.platformName} session resumed: ${sessionInfo.sessionId} (was ${resumeSessionId})`);
169
+ await this.adapter.postReply(event, runner);
170
+ }
171
+ catch (error) {
172
+ this.logger.error(`${this.adapter.platformName} resume session error for ${sessionId}`, error instanceof Error ? error : new Error(String(error)));
173
+ }
174
+ }
175
+ /**
176
+ * Handle Claude messages for chat sessions.
177
+ * Routes to the dedicated AgentSessionManager.
178
+ */
179
+ async handleClaudeMessage(sessionId, message) {
180
+ await this.sessionManager.handleClaudeMessage(sessionId, message);
181
+ }
182
+ /**
183
+ * Create an empty workspace directory for a chat thread.
184
+ * Unlike repository-associated sessions, chat sessions use plain directories (not git worktrees).
185
+ */
186
+ async createWorkspace(threadKey) {
187
+ try {
188
+ const sanitizedKey = threadKey.replace(/[^a-zA-Z0-9.-]/g, "_");
189
+ const workspacePath = join(this.deps.sylasHome, `${this.adapter.platformName}-workspaces`, sanitizedKey);
190
+ await mkdir(workspacePath, { recursive: true });
191
+ return { path: workspacePath, isGitWorktree: false };
192
+ }
193
+ catch (error) {
194
+ this.logger.error(`Failed to create ${this.adapter.platformName} workspace for thread ${threadKey}`, error instanceof Error ? error : new Error(String(error)));
195
+ return null;
196
+ }
197
+ }
198
+ /**
199
+ * Build a ClaudeRunner config for a chat session.
200
+ * Used by both handleEvent (new session) and resumeSession to eliminate duplication.
201
+ */
202
+ buildRunnerConfig(workspacePath, workspaceName, systemPrompt, sessionId, resumeSessionId) {
203
+ // When MCP servers are configured, include their tool permissions
204
+ const mcpToolPermissions = this.deps.mcpConfig
205
+ ? Object.keys(this.deps.mcpConfig).map((server) => `mcp__${server}`)
206
+ : [];
207
+ return {
208
+ workingDirectory: workspacePath,
209
+ allowedTools: [...getAllTools(), ...mcpToolPermissions],
210
+ disallowedTools: [],
211
+ allowedDirectories: [workspacePath],
212
+ workspaceName,
213
+ sylasHome: this.deps.sylasHome,
214
+ appendSystemPrompt: systemPrompt,
215
+ model: this.deps.defaultModel,
216
+ fallbackModel: this.deps.defaultFallbackModel,
217
+ ...(this.deps.mcpConfig ? { mcpConfig: this.deps.mcpConfig } : {}),
218
+ ...(resumeSessionId ? { resumeSessionId } : {}),
219
+ logger: this.logger.withContext({
220
+ sessionId,
221
+ platform: this.adapter.platformName,
222
+ }),
223
+ maxTurns: 200,
224
+ onMessage: (message) => {
225
+ this.handleClaudeMessage(sessionId, message);
226
+ },
227
+ onError: (error) => this.deps.onClaudeError(error),
228
+ };
229
+ }
230
+ }
231
+ //# sourceMappingURL=ChatSessionHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatSessionHandler.js","sourceRoot":"","sources":["../src/ChatSessionHandler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { EdgeWorkerConfig, ILogger, RepositoryConfig } from "sylas-core";
3
+ /**
4
+ * Describes the set of repository-level changes detected after a config
5
+ * file reload. Emitted as the payload of the `configChanged` event.
6
+ */
7
+ export interface RepositoryChanges {
8
+ added: RepositoryConfig[];
9
+ modified: RepositoryConfig[];
10
+ removed: RepositoryConfig[];
11
+ /** The fully-merged new config (caller should replace its reference). */
12
+ newConfig: EdgeWorkerConfig;
13
+ }
14
+ /**
15
+ * Events emitted by ConfigManager.
16
+ */
17
+ export interface ConfigManagerEvents {
18
+ configChanged: (changes: RepositoryChanges) => void;
19
+ }
20
+ /**
21
+ * ConfigManager is responsible for watching, loading, validating, and
22
+ * diffing the EdgeWorker configuration file. It does **not** perform any
23
+ * repository lifecycle operations (adding / updating / removing session
24
+ * managers, issue trackers, etc.) -- instead it emits a `configChanged`
25
+ * event that the EdgeWorker listens to and acts upon.
26
+ *
27
+ * Usage:
28
+ * ```ts
29
+ * const configManager = new ConfigManager(config, logger, configPath, repositories);
30
+ * configManager.on("configChanged", async (changes) => {
31
+ * await removeDeletedRepositories(changes.removed);
32
+ * await updateModifiedRepositories(changes.modified);
33
+ * await addNewRepositories(changes.added);
34
+ * this.config = changes.newConfig;
35
+ * });
36
+ * configManager.startConfigWatcher();
37
+ * ```
38
+ */
39
+ export declare class ConfigManager extends EventEmitter {
40
+ private config;
41
+ private readonly logger;
42
+ private configPath?;
43
+ /** Live reference to EdgeWorker's repository map -- used for diffing. */
44
+ private readonly repositories;
45
+ private configWatcher?;
46
+ constructor(config: EdgeWorkerConfig, logger: ILogger, configPath: string | undefined, repositories: Map<string, RepositoryConfig>);
47
+ /**
48
+ * Start watching the config file for changes. Each detected change
49
+ * triggers a reload-and-diff cycle; if repository-level changes are
50
+ * found a `configChanged` event is emitted.
51
+ */
52
+ startConfigWatcher(): void;
53
+ /**
54
+ * Stop the config file watcher and release resources.
55
+ */
56
+ stop(): Promise<void>;
57
+ /**
58
+ * Return the current (possibly reloaded) config snapshot.
59
+ */
60
+ getConfig(): EdgeWorkerConfig;
61
+ /**
62
+ * Update the internal config reference. This is useful when the
63
+ * EdgeWorker needs to push an externally-modified config back into
64
+ * the ConfigManager (e.g. after applying the changes from a
65
+ * `configChanged` event).
66
+ */
67
+ setConfig(config: EdgeWorkerConfig): void;
68
+ /**
69
+ * Update the config file path (e.g. when set after construction).
70
+ */
71
+ setConfigPath(configPath: string): void;
72
+ /**
73
+ * Handle a config file change event: load, validate, diff, and emit.
74
+ */
75
+ private handleConfigChange;
76
+ /**
77
+ * Safely load configuration from the file, merging with the current
78
+ * in-memory config for fields that are not present in the file.
79
+ */
80
+ private loadConfigSafely;
81
+ /**
82
+ * Detect changes between the current in-memory repository map and
83
+ * the repositories declared in `newConfig`.
84
+ */
85
+ private detectRepositoryChanges;
86
+ /**
87
+ * Deep equality check for repository configs.
88
+ */
89
+ private deepEqual;
90
+ }
91
+ //# sourceMappingURL=ConfigManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigManager.d.ts","sourceRoot":"","sources":["../src/ConfigManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9E;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,yEAAyE;IACzE,SAAS,EAAE,gBAAgB,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,aAAa,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACpD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,aAAc,SAAQ,YAAY;IAC9C,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgC;IAC7D,OAAO,CAAC,aAAa,CAAC,CAAY;gBAGjC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,OAAO,EACf,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAa5C;;;;OAIG;IACH,kBAAkB,IAAI,IAAI;IA2B1B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B;;OAEG;IACH,SAAS,IAAI,gBAAgB;IAI7B;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAIzC;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAQvC;;OAEG;YACW,kBAAkB;IAkChC;;;OAGG;YACW,gBAAgB;IA6E9B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAoC/B;;OAEG;IACH,OAAO,CAAC,SAAS;CAGjB"}
@@ -0,0 +1,227 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { readFile } from "node:fs/promises";
3
+ import { watch as chokidarWatch } from "chokidar";
4
+ /**
5
+ * ConfigManager is responsible for watching, loading, validating, and
6
+ * diffing the EdgeWorker configuration file. It does **not** perform any
7
+ * repository lifecycle operations (adding / updating / removing session
8
+ * managers, issue trackers, etc.) -- instead it emits a `configChanged`
9
+ * event that the EdgeWorker listens to and acts upon.
10
+ *
11
+ * Usage:
12
+ * ```ts
13
+ * const configManager = new ConfigManager(config, logger, configPath, repositories);
14
+ * configManager.on("configChanged", async (changes) => {
15
+ * await removeDeletedRepositories(changes.removed);
16
+ * await updateModifiedRepositories(changes.modified);
17
+ * await addNewRepositories(changes.added);
18
+ * this.config = changes.newConfig;
19
+ * });
20
+ * configManager.startConfigWatcher();
21
+ * ```
22
+ */
23
+ export class ConfigManager extends EventEmitter {
24
+ config;
25
+ logger;
26
+ configPath;
27
+ /** Live reference to EdgeWorker's repository map -- used for diffing. */
28
+ repositories;
29
+ configWatcher;
30
+ constructor(config, logger, configPath, repositories) {
31
+ super();
32
+ this.config = config;
33
+ this.logger = logger;
34
+ this.configPath = configPath;
35
+ this.repositories = repositories;
36
+ }
37
+ // ------------------------------------------------------------------
38
+ // Public API
39
+ // ------------------------------------------------------------------
40
+ /**
41
+ * Start watching the config file for changes. Each detected change
42
+ * triggers a reload-and-diff cycle; if repository-level changes are
43
+ * found a `configChanged` event is emitted.
44
+ */
45
+ startConfigWatcher() {
46
+ if (!this.configPath) {
47
+ this.logger.warn("âš ī¸ No config path set, skipping config file watcher");
48
+ return;
49
+ }
50
+ this.logger.info(`👀 Watching config file for changes: ${this.configPath}`);
51
+ this.configWatcher = chokidarWatch(this.configPath, {
52
+ persistent: true,
53
+ ignoreInitial: true,
54
+ awaitWriteFinish: {
55
+ stabilityThreshold: 500,
56
+ pollInterval: 100,
57
+ },
58
+ });
59
+ this.configWatcher.on("change", async () => {
60
+ this.logger.info("🔄 Config file changed, reloading...");
61
+ await this.handleConfigChange();
62
+ });
63
+ this.configWatcher.on("error", (error) => {
64
+ this.logger.error("❌ Config watcher error:", error);
65
+ });
66
+ }
67
+ /**
68
+ * Stop the config file watcher and release resources.
69
+ */
70
+ async stop() {
71
+ if (this.configWatcher) {
72
+ await this.configWatcher.close();
73
+ this.configWatcher = undefined;
74
+ this.logger.info("✅ Config file watcher stopped");
75
+ }
76
+ }
77
+ /**
78
+ * Return the current (possibly reloaded) config snapshot.
79
+ */
80
+ getConfig() {
81
+ return this.config;
82
+ }
83
+ /**
84
+ * Update the internal config reference. This is useful when the
85
+ * EdgeWorker needs to push an externally-modified config back into
86
+ * the ConfigManager (e.g. after applying the changes from a
87
+ * `configChanged` event).
88
+ */
89
+ setConfig(config) {
90
+ this.config = config;
91
+ }
92
+ /**
93
+ * Update the config file path (e.g. when set after construction).
94
+ */
95
+ setConfigPath(configPath) {
96
+ this.configPath = configPath;
97
+ }
98
+ // ------------------------------------------------------------------
99
+ // Internal helpers
100
+ // ------------------------------------------------------------------
101
+ /**
102
+ * Handle a config file change event: load, validate, diff, and emit.
103
+ */
104
+ async handleConfigChange() {
105
+ try {
106
+ const newConfig = await this.loadConfigSafely();
107
+ if (!newConfig) {
108
+ return;
109
+ }
110
+ const changes = this.detectRepositoryChanges(newConfig);
111
+ if (changes.added.length === 0 &&
112
+ changes.modified.length === 0 &&
113
+ changes.removed.length === 0) {
114
+ this.logger.info("â„šī¸ No repository changes detected");
115
+ return;
116
+ }
117
+ this.logger.info(`📊 Repository changes detected: ${changes.added.length} added, ${changes.modified.length} modified, ${changes.removed.length} removed`);
118
+ // Emit the diff so EdgeWorker can orchestrate the mutations.
119
+ this.emit("configChanged", {
120
+ added: changes.added,
121
+ modified: changes.modified,
122
+ removed: changes.removed,
123
+ newConfig,
124
+ });
125
+ }
126
+ catch (error) {
127
+ this.logger.error("❌ Failed to reload configuration:", error);
128
+ }
129
+ }
130
+ /**
131
+ * Safely load configuration from the file, merging with the current
132
+ * in-memory config for fields that are not present in the file.
133
+ */
134
+ async loadConfigSafely() {
135
+ try {
136
+ if (!this.configPath) {
137
+ this.logger.error("❌ No config path set");
138
+ return null;
139
+ }
140
+ const configContent = await readFile(this.configPath, "utf-8");
141
+ const parsedConfig = JSON.parse(configContent);
142
+ // Merge with current EdgeWorker config structure
143
+ const newConfig = {
144
+ ...this.config,
145
+ repositories: parsedConfig.repositories || [],
146
+ ngrokAuthToken: parsedConfig.ngrokAuthToken || this.config.ngrokAuthToken,
147
+ linearWorkspaceSlug: parsedConfig.linearWorkspaceSlug || this.config.linearWorkspaceSlug,
148
+ claudeDefaultModel: parsedConfig.claudeDefaultModel ||
149
+ parsedConfig.defaultModel ||
150
+ this.config.claudeDefaultModel ||
151
+ this.config.defaultModel,
152
+ claudeDefaultFallbackModel: parsedConfig.claudeDefaultFallbackModel ||
153
+ parsedConfig.defaultFallbackModel ||
154
+ this.config.claudeDefaultFallbackModel ||
155
+ this.config.defaultFallbackModel,
156
+ geminiDefaultModel: parsedConfig.geminiDefaultModel || this.config.geminiDefaultModel,
157
+ codexDefaultModel: parsedConfig.codexDefaultModel || this.config.codexDefaultModel,
158
+ openCodeDefaultModel: parsedConfig.openCodeDefaultModel || this.config.openCodeDefaultModel,
159
+ defaultModel: parsedConfig.defaultModel || this.config.defaultModel,
160
+ defaultFallbackModel: parsedConfig.defaultFallbackModel || this.config.defaultFallbackModel,
161
+ defaultAllowedTools: parsedConfig.defaultAllowedTools || this.config.defaultAllowedTools,
162
+ defaultDisallowedTools: parsedConfig.defaultDisallowedTools ||
163
+ this.config.defaultDisallowedTools,
164
+ // Issue update trigger: use parsed value if explicitly set,
165
+ // otherwise keep current or default to true
166
+ issueUpdateTrigger: parsedConfig.issueUpdateTrigger ?? this.config.issueUpdateTrigger,
167
+ };
168
+ // Basic validation
169
+ if (!Array.isArray(newConfig.repositories)) {
170
+ this.logger.error("❌ Invalid config: repositories must be an array");
171
+ return null;
172
+ }
173
+ // Validate each repository has required fields
174
+ for (const repo of newConfig.repositories) {
175
+ if (!repo.id ||
176
+ !repo.name ||
177
+ !repo.repositoryPath ||
178
+ !repo.baseBranch) {
179
+ this.logger.error(`❌ Invalid repository config: missing required fields (id, name, repositoryPath, baseBranch)`, repo);
180
+ return null;
181
+ }
182
+ }
183
+ return newConfig;
184
+ }
185
+ catch (error) {
186
+ this.logger.error("❌ Failed to load config file:", error);
187
+ return null;
188
+ }
189
+ }
190
+ /**
191
+ * Detect changes between the current in-memory repository map and
192
+ * the repositories declared in `newConfig`.
193
+ */
194
+ detectRepositoryChanges(newConfig) {
195
+ const currentRepos = new Map(this.repositories);
196
+ const newRepos = new Map(newConfig.repositories.map((r) => [r.id, r]));
197
+ const added = [];
198
+ const modified = [];
199
+ const removed = [];
200
+ // Find added and modified repositories
201
+ for (const [id, repo] of newRepos) {
202
+ if (!currentRepos.has(id)) {
203
+ added.push(repo);
204
+ }
205
+ else {
206
+ const currentRepo = currentRepos.get(id);
207
+ if (currentRepo && !this.deepEqual(currentRepo, repo)) {
208
+ modified.push(repo);
209
+ }
210
+ }
211
+ }
212
+ // Find removed repositories
213
+ for (const [id, repo] of currentRepos) {
214
+ if (!newRepos.has(id)) {
215
+ removed.push(repo);
216
+ }
217
+ }
218
+ return { added, modified, removed };
219
+ }
220
+ /**
221
+ * Deep equality check for repository configs.
222
+ */
223
+ deepEqual(obj1, obj2) {
224
+ return JSON.stringify(obj1) === JSON.stringify(obj2);
225
+ }
226
+ }
227
+ //# sourceMappingURL=ConfigManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigManager.js","sourceRoot":"","sources":["../src/ConfigManager.ts"],"names":[],"mappings":"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"}