mstro-app 0.4.39 → 0.4.43

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 (197) hide show
  1. package/bin/commands/login.js +17 -7
  2. package/bin/commands/logout.js +14 -6
  3. package/bin/commands/status.js +9 -3
  4. package/bin/commands/whoami.js +10 -4
  5. package/bin/mstro.js +11 -1
  6. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -1
  7. package/dist/server/cli/headless/claude-invoker-stream.js +1 -0
  8. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -1
  9. package/dist/server/cli/headless/index.d.ts +1 -0
  10. package/dist/server/cli/headless/index.d.ts.map +1 -1
  11. package/dist/server/cli/headless/index.js +2 -0
  12. package/dist/server/cli/headless/index.js.map +1 -1
  13. package/dist/server/cli/headless/resilient-runner.d.ts +47 -0
  14. package/dist/server/cli/headless/resilient-runner.d.ts.map +1 -0
  15. package/dist/server/cli/headless/resilient-runner.js +234 -0
  16. package/dist/server/cli/headless/resilient-runner.js.map +1 -0
  17. package/dist/server/cli/headless/retry-strategies.d.ts +44 -0
  18. package/dist/server/cli/headless/retry-strategies.d.ts.map +1 -0
  19. package/dist/server/cli/headless/retry-strategies.js +262 -0
  20. package/dist/server/cli/headless/retry-strategies.js.map +1 -0
  21. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  22. package/dist/server/cli/headless/stall-assessor.js +5 -0
  23. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  24. package/dist/server/cli/headless/tool-watchdog.d.ts +2 -0
  25. package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
  26. package/dist/server/cli/headless/tool-watchdog.js +31 -4
  27. package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
  28. package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
  29. package/dist/server/cli/improvisation-retry.js +1 -30
  30. package/dist/server/cli/improvisation-retry.js.map +1 -1
  31. package/dist/server/cli/improvisation-session-manager.d.ts +1 -0
  32. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  33. package/dist/server/cli/improvisation-session-manager.js +16 -3
  34. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  35. package/dist/server/cli/prompt-builders.d.ts.map +1 -1
  36. package/dist/server/cli/prompt-builders.js +31 -13
  37. package/dist/server/cli/prompt-builders.js.map +1 -1
  38. package/dist/server/index.js +1 -9
  39. package/dist/server/index.js.map +1 -1
  40. package/dist/server/mcp/bouncer-cli.js +5 -4
  41. package/dist/server/mcp/bouncer-cli.js.map +1 -1
  42. package/dist/server/mcp/bouncer-haiku.js +1 -1
  43. package/dist/server/mcp/bouncer-haiku.js.map +1 -1
  44. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  45. package/dist/server/mcp/bouncer-integration.js +14 -8
  46. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  47. package/dist/server/mcp/security-patterns.js +1 -1
  48. package/dist/server/mcp/security-patterns.js.map +1 -1
  49. package/dist/server/services/plan/composer.d.ts.map +1 -1
  50. package/dist/server/services/plan/composer.js +19 -9
  51. package/dist/server/services/plan/composer.js.map +1 -1
  52. package/dist/server/services/plan/executor.d.ts +6 -1
  53. package/dist/server/services/plan/executor.d.ts.map +1 -1
  54. package/dist/server/services/plan/executor.js +158 -76
  55. package/dist/server/services/plan/executor.js.map +1 -1
  56. package/dist/server/services/plan/front-matter.d.ts +1 -0
  57. package/dist/server/services/plan/front-matter.d.ts.map +1 -1
  58. package/dist/server/services/plan/front-matter.js +6 -0
  59. package/dist/server/services/plan/front-matter.js.map +1 -1
  60. package/dist/server/services/plan/issue-classification.d.ts +11 -0
  61. package/dist/server/services/plan/issue-classification.d.ts.map +1 -0
  62. package/dist/server/services/plan/issue-classification.js +20 -0
  63. package/dist/server/services/plan/issue-classification.js.map +1 -0
  64. package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
  65. package/dist/server/services/plan/issue-prompt-builder.js +7 -4
  66. package/dist/server/services/plan/issue-prompt-builder.js.map +1 -1
  67. package/dist/server/services/plan/issue-retry.d.ts +0 -5
  68. package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
  69. package/dist/server/services/plan/issue-retry.js +12 -241
  70. package/dist/server/services/plan/issue-retry.js.map +1 -1
  71. package/dist/server/services/plan/parser-core.d.ts.map +1 -1
  72. package/dist/server/services/plan/parser-core.js +1 -0
  73. package/dist/server/services/plan/parser-core.js.map +1 -1
  74. package/dist/server/services/plan/review-gate.d.ts.map +1 -1
  75. package/dist/server/services/plan/review-gate.js +9 -6
  76. package/dist/server/services/plan/review-gate.js.map +1 -1
  77. package/dist/server/services/plan/types.d.ts +1 -0
  78. package/dist/server/services/plan/types.d.ts.map +1 -1
  79. package/dist/server/services/platform-credentials.d.ts.map +1 -1
  80. package/dist/server/services/platform-credentials.js +11 -4
  81. package/dist/server/services/platform-credentials.js.map +1 -1
  82. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  83. package/dist/server/services/terminal/pty-manager.js +7 -1
  84. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  85. package/dist/server/services/websocket/handler-context.d.ts +2 -0
  86. package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
  87. package/dist/server/services/websocket/handler.d.ts +2 -0
  88. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  89. package/dist/server/services/websocket/handler.js +18 -7
  90. package/dist/server/services/websocket/handler.js.map +1 -1
  91. package/dist/server/services/websocket/plan-execution-handlers.js +6 -6
  92. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -1
  93. package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -1
  94. package/dist/server/services/websocket/quality-fix-agent.js +90 -42
  95. package/dist/server/services/websocket/quality-fix-agent.js.map +1 -1
  96. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  97. package/dist/server/services/websocket/quality-handlers.js +48 -7
  98. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  99. package/dist/server/services/websocket/quality-persistence.d.ts +22 -0
  100. package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
  101. package/dist/server/services/websocket/quality-persistence.js +48 -1
  102. package/dist/server/services/websocket/quality-persistence.js.map +1 -1
  103. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
  104. package/dist/server/services/websocket/quality-review-agent.js +74 -32
  105. package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
  106. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
  107. package/dist/server/services/websocket/quality-tools.js +18 -18
  108. package/dist/server/services/websocket/quality-tools.js.map +1 -1
  109. package/dist/server/services/websocket/skill-handlers.d.ts +3 -1
  110. package/dist/server/services/websocket/skill-handlers.d.ts.map +1 -1
  111. package/dist/server/services/websocket/skill-handlers.js +52 -41
  112. package/dist/server/services/websocket/skill-handlers.js.map +1 -1
  113. package/dist/server/services/websocket/skill-watcher.d.ts +17 -0
  114. package/dist/server/services/websocket/skill-watcher.d.ts.map +1 -0
  115. package/dist/server/services/websocket/skill-watcher.js +85 -0
  116. package/dist/server/services/websocket/skill-watcher.js.map +1 -0
  117. package/dist/server/services/websocket/types.d.ts +2 -268
  118. package/dist/server/services/websocket/types.d.ts.map +1 -1
  119. package/dist/server/services/websocket/types.js +0 -4
  120. package/dist/server/services/websocket/types.js.map +1 -1
  121. package/package.json +1 -1
  122. package/server/cli/headless/claude-invoker-stream.ts +1 -0
  123. package/server/cli/headless/index.ts +2 -0
  124. package/server/cli/headless/resilient-runner.ts +354 -0
  125. package/server/cli/headless/retry-strategies.ts +330 -0
  126. package/server/cli/headless/stall-assessor.ts +5 -0
  127. package/server/cli/headless/tool-watchdog.ts +40 -4
  128. package/server/cli/improvisation-retry.ts +1 -32
  129. package/server/cli/improvisation-session-manager.ts +17 -3
  130. package/server/cli/prompt-builders.ts +33 -12
  131. package/server/index.ts +1 -9
  132. package/server/mcp/bouncer-cli.ts +5 -4
  133. package/server/mcp/bouncer-haiku.ts +1 -1
  134. package/server/mcp/bouncer-integration.ts +15 -8
  135. package/server/mcp/security-patterns.ts +1 -1
  136. package/server/services/plan/agents/code-review.md +109 -0
  137. package/server/services/plan/agents/commit-message.md +26 -0
  138. package/server/services/plan/agents/fix-quality.md +24 -0
  139. package/server/services/plan/agents/pr-description.md +28 -0
  140. package/server/services/plan/composer.ts +20 -9
  141. package/server/services/plan/executor.ts +160 -76
  142. package/server/services/plan/front-matter.ts +7 -0
  143. package/server/services/plan/issue-classification.ts +21 -0
  144. package/server/services/plan/issue-prompt-builder.ts +8 -4
  145. package/server/services/plan/issue-retry.ts +15 -330
  146. package/server/services/plan/parser-core.ts +1 -0
  147. package/server/services/plan/review-gate.ts +9 -6
  148. package/server/services/plan/types.ts +3 -0
  149. package/server/services/platform-credentials.ts +10 -4
  150. package/server/services/terminal/pty-manager.ts +7 -1
  151. package/server/services/websocket/handler-context.ts +2 -0
  152. package/server/services/websocket/handler.ts +18 -8
  153. package/server/services/websocket/plan-execution-handlers.ts +7 -7
  154. package/server/services/websocket/quality-fix-agent.ts +86 -44
  155. package/server/services/websocket/quality-handlers.ts +48 -7
  156. package/server/services/websocket/quality-persistence.ts +75 -1
  157. package/server/services/websocket/quality-review-agent.ts +70 -31
  158. package/server/services/websocket/quality-tools.ts +16 -14
  159. package/server/services/websocket/skill-handlers.ts +50 -40
  160. package/server/services/websocket/skill-watcher.ts +79 -0
  161. package/server/services/websocket/types.ts +0 -311
  162. package/dist/server/services/deploy/ai-broker.d.ts +0 -63
  163. package/dist/server/services/deploy/ai-broker.d.ts.map +0 -1
  164. package/dist/server/services/deploy/ai-broker.js +0 -360
  165. package/dist/server/services/deploy/ai-broker.js.map +0 -1
  166. package/dist/server/services/deploy/board-execution-handler.d.ts +0 -114
  167. package/dist/server/services/deploy/board-execution-handler.d.ts.map +0 -1
  168. package/dist/server/services/deploy/board-execution-handler.js +0 -621
  169. package/dist/server/services/deploy/board-execution-handler.js.map +0 -1
  170. package/dist/server/services/deploy/credentials.d.ts +0 -35
  171. package/dist/server/services/deploy/credentials.d.ts.map +0 -1
  172. package/dist/server/services/deploy/credentials.js +0 -177
  173. package/dist/server/services/deploy/credentials.js.map +0 -1
  174. package/dist/server/services/deploy/deploy-ai-service.d.ts +0 -107
  175. package/dist/server/services/deploy/deploy-ai-service.d.ts.map +0 -1
  176. package/dist/server/services/deploy/deploy-ai-service.js +0 -294
  177. package/dist/server/services/deploy/deploy-ai-service.js.map +0 -1
  178. package/dist/server/services/deploy/headless-session-handler.d.ts +0 -94
  179. package/dist/server/services/deploy/headless-session-handler.d.ts.map +0 -1
  180. package/dist/server/services/deploy/headless-session-handler.js +0 -266
  181. package/dist/server/services/deploy/headless-session-handler.js.map +0 -1
  182. package/dist/server/services/websocket/deploy-handlers.d.ts +0 -14
  183. package/dist/server/services/websocket/deploy-handlers.d.ts.map +0 -1
  184. package/dist/server/services/websocket/deploy-handlers.js +0 -409
  185. package/dist/server/services/websocket/deploy-handlers.js.map +0 -1
  186. package/dist/server/services/websocket/handlers/deploy-handlers.d.ts +0 -11
  187. package/dist/server/services/websocket/handlers/deploy-handlers.d.ts.map +0 -1
  188. package/dist/server/services/websocket/handlers/deploy-handlers.js +0 -176
  189. package/dist/server/services/websocket/handlers/deploy-handlers.js.map +0 -1
  190. package/server/cli/headless/RESEARCH.md +0 -627
  191. package/server/services/deploy/ai-broker.ts +0 -512
  192. package/server/services/deploy/board-execution-handler.ts +0 -847
  193. package/server/services/deploy/credentials.ts +0 -200
  194. package/server/services/deploy/deploy-ai-service.ts +0 -401
  195. package/server/services/deploy/headless-session-handler.ts +0 -414
  196. package/server/services/websocket/deploy-handlers.ts +0 -544
  197. package/server/services/websocket/handlers/deploy-handlers.ts +0 -228
@@ -12,7 +12,7 @@ import type { SkillEntry, WSContext } from './types.js';
12
12
 
13
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
14
  const SYSTEM_AGENTS_DIR = join(__dirname, '..', 'plan', 'agents');
15
- const USER_SKILLS_DIR = join(homedir(), '.claude', 'skills');
15
+ export const USER_SKILLS_DIR = join(homedir(), '.claude', 'skills');
16
16
 
17
17
  const PLATFORM_COMMANDS: SkillEntry[] = [
18
18
  {
@@ -118,14 +118,14 @@ function scanSystemAgents(agentsDir: string, seen: Set<string>): SkillEntry[] {
118
118
  return entries;
119
119
  }
120
120
 
121
- export function handleListSkills(ctx: HandlerContext, ws: WSContext, workingDir: string): void {
122
- const skills: SkillEntry[] = [...PLATFORM_COMMANDS];
123
- const seen = new Set(skills.map(s => s.name));
121
+ export function collectAllSkills(workingDir: string): SkillEntry[] {
122
+ const skills: SkillEntry[] = [];
123
+ const seen = new Set<string>();
124
124
 
125
+ // 1. Project skills (highest priority — override everything)
125
126
  const projectSkillsDir = findSkillsDir(workingDir);
126
127
  if (projectSkillsDir) {
127
- const projectSkills = scanProjectSkills(projectSkillsDir);
128
- for (const s of projectSkills) {
128
+ for (const s of scanProjectSkills(projectSkillsDir)) {
129
129
  if (!seen.has(s.name)) {
130
130
  skills.push(s);
131
131
  seen.add(s.name);
@@ -133,12 +133,27 @@ export function handleListSkills(ctx: HandlerContext, ws: WSContext, workingDir:
133
133
  }
134
134
  }
135
135
 
136
+ // 2. User skills (override platform commands and system agents)
136
137
  skills.push(...scanUserSkills(USER_SKILLS_DIR, seen));
137
-
138
138
  for (const s of skills) seen.add(s.name);
139
+
140
+ // 3. Platform commands (only if not overridden by an external skill)
141
+ for (const cmd of PLATFORM_COMMANDS) {
142
+ if (!seen.has(cmd.name)) {
143
+ skills.push(cmd);
144
+ seen.add(cmd.name);
145
+ }
146
+ }
147
+
148
+ // 4. System agents (lowest priority)
139
149
  skills.push(...scanSystemAgents(SYSTEM_AGENTS_DIR, seen));
140
150
 
141
151
  skills.sort((a, b) => a.name.localeCompare(b.name));
152
+ return skills;
153
+ }
154
+
155
+ export function handleListSkills(ctx: HandlerContext, ws: WSContext, workingDir: string): void {
156
+ const skills = collectAllSkills(workingDir);
142
157
  ctx.send(ws, { type: 'skillsList', data: { skills } });
143
158
  }
144
159
 
@@ -155,43 +170,37 @@ function extractSkillContent(fileContent: string): string {
155
170
  interface SkillFile {
156
171
  content: string;
157
172
  skillDir: string;
173
+ source: 'project' | 'user' | 'system';
174
+ }
175
+
176
+ function tryReadSkillFile(filePath: string, skillDir: string, userInvocableOnly: boolean, source: SkillFile['source']): SkillFile | null {
177
+ if (!existsSync(filePath)) return null;
178
+ try {
179
+ const content = readFileSync(filePath, 'utf-8');
180
+ if (userInvocableOnly && parseFrontmatter(content)['user-invocable'] === 'false') return null;
181
+ return { content, skillDir, source };
182
+ } catch { return null; }
158
183
  }
159
184
 
160
185
  /**
161
- * Find and read a skill's SKILL.md by name. Checks project skills first, then system agents.
162
- * Returns the raw file content and the skill directory, or null if not found.
186
+ * Find and read a skill's SKILL.md by name. Checks project skills first, then user skills, then system agents.
187
+ * When `userInvocableOnly` is true, skips entries with `user-invocable: false` and continues
188
+ * to the next source — this ensures internal-only project skills don't shadow user skills.
163
189
  */
164
- function findSkillContent(skillName: string, workingDir: string): SkillFile | null {
165
- // Project skills: .claude/skills/<name>/SKILL.md
190
+ function findSkillContent(skillName: string, workingDir: string, userInvocableOnly = false): SkillFile | null {
166
191
  const projectSkillsDir = findSkillsDir(workingDir);
167
192
  if (projectSkillsDir) {
168
193
  const skillDir = join(projectSkillsDir, skillName);
169
- const skillFile = join(skillDir, 'SKILL.md');
170
- if (existsSync(skillFile)) {
171
- try {
172
- return { content: readFileSync(skillFile, 'utf-8'), skillDir };
173
- } catch { /* fall through */ }
174
- }
194
+ const found = tryReadSkillFile(join(skillDir, 'SKILL.md'), skillDir, userInvocableOnly, 'project');
195
+ if (found) return found;
175
196
  }
176
197
 
177
- // User skills: ~/.claude/skills/<name>/SKILL.md
178
198
  const userSkillDir = join(USER_SKILLS_DIR, skillName);
179
- const userSkillFile = join(userSkillDir, 'SKILL.md');
180
- if (existsSync(userSkillFile)) {
181
- try {
182
- return { content: readFileSync(userSkillFile, 'utf-8'), skillDir: userSkillDir };
183
- } catch { /* fall through */ }
184
- }
199
+ const found = tryReadSkillFile(join(userSkillDir, 'SKILL.md'), userSkillDir, userInvocableOnly, 'user');
200
+ if (found) return found;
185
201
 
186
- // System agents: <agents-dir>/<name>.md
187
202
  const agentFile = join(SYSTEM_AGENTS_DIR, `${skillName}.md`);
188
- if (existsSync(agentFile)) {
189
- try {
190
- return { content: readFileSync(agentFile, 'utf-8'), skillDir: SYSTEM_AGENTS_DIR };
191
- } catch { /* fall through */ }
192
- }
193
-
194
- return null;
203
+ return tryReadSkillFile(agentFile, SYSTEM_AGENTS_DIR, userInvocableOnly, 'system');
195
204
  }
196
205
 
197
206
  /**
@@ -250,7 +259,9 @@ function fillArgumentVariables(content: string, userArgs: string): string {
250
259
  * Also handles fenced ```! blocks for multi-line commands.
251
260
  * Runs in the skill's working directory with a short timeout.
252
261
  */
253
- function executeInlineShellCommands(content: string, workingDir: string): string {
262
+ function executeInlineShellCommands(content: string, workingDir: string, source: 'system' | 'user' | 'project'): string {
263
+ if (source !== 'system' && source !== 'user') return content;
264
+
254
265
  // Fenced ```! blocks — multi-line shell execution
255
266
  let result = content.replace(/```!\n([\s\S]*?)```/g, (_, block: string) => {
256
267
  const cmd = block.trim();
@@ -305,7 +316,7 @@ function parseSlashCommand(trimmed: string): { skillName: string; userArgs: stri
305
316
  const CLAUDE_SKILL_DIR_RE = /\$\{CLAUDE_SKILL_DIR}/g;
306
317
  const CLAUDE_SESSION_ID_RE = /\$\{CLAUDE_SESSION_ID}/g;
307
318
 
308
- function processSkillContent(rawContent: string, userArgs: string, skillDir: string, workingDir: string): string {
319
+ function processSkillContent(rawContent: string, userArgs: string, skillDir: string, workingDir: string, source: 'system' | 'user' | 'project'): string {
309
320
  let content = extractSkillContent(rawContent);
310
321
 
311
322
  content = fillTemplateVariables(content, {
@@ -322,7 +333,7 @@ function processSkillContent(rawContent: string, userArgs: string, skillDir: str
322
333
  content = content.replace(CLAUDE_SESSION_ID_RE, `mstro-${Date.now()}`);
323
334
 
324
335
  if (/!`[^`]+`/.test(content) || /```!\n/.test(content)) {
325
- content = executeInlineShellCommands(content, workingDir);
336
+ content = executeInlineShellCommands(content, workingDir, source);
326
337
  }
327
338
 
328
339
  if (userArgs && !hasArgumentsPlaceholder && !rawContent.includes('{{dirPath}}')) {
@@ -336,12 +347,11 @@ export function resolveSkillPrompt(prompt: string, workingDir: string): Resolved
336
347
  const parsed = parseSlashCommand(prompt.trim());
337
348
  if (!parsed) return null;
338
349
 
339
- const found = findSkillContent(parsed.skillName, workingDir);
350
+ // Only resolve user-invocable skills — internal-only skills (user-invocable: false)
351
+ // are skipped so they don't shadow external skills with the same name.
352
+ const found = findSkillContent(parsed.skillName, workingDir, true);
340
353
  if (!found) return null;
341
354
 
342
- const fm = parseFrontmatter(found.content);
343
- if (fm['user-invocable'] === 'false') return null;
344
-
345
- const skillPrompt = processSkillContent(found.content, parsed.userArgs, found.skillDir, workingDir);
355
+ const skillPrompt = processSkillContent(found.content, parsed.userArgs, found.skillDir, workingDir, found.source);
346
356
  return { prompt: skillPrompt, skillName: parsed.skillName, userArgs: parsed.userArgs };
347
357
  }
@@ -0,0 +1,79 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ import { type FSWatcher, watch } from 'node:fs';
5
+ import { findSkillsDir } from '../../utils/paths.js';
6
+ import type { HandlerContext } from './handler-context.js';
7
+ import { collectAllSkills, USER_SKILLS_DIR } from './skill-handlers.js';
8
+
9
+ export class SkillsWatcher {
10
+ private userWatcher: FSWatcher | null = null;
11
+ private projectWatcher: FSWatcher | null = null;
12
+ private debounceTimer: ReturnType<typeof setTimeout> | null = null;
13
+ private lastSkillsHash = '';
14
+ private started = false;
15
+
16
+ constructor(
17
+ private readonly workingDir: string,
18
+ private readonly ctx: HandlerContext,
19
+ ) {}
20
+
21
+ start(): void {
22
+ if (this.started) return;
23
+
24
+ this.lastSkillsHash = this.computeHash();
25
+
26
+ try {
27
+ this.userWatcher = watch(USER_SKILLS_DIR, { recursive: true }, (_event, filename) => {
28
+ if (!filename || !filename.endsWith('.md')) return;
29
+ this.debounce();
30
+ });
31
+ this.userWatcher.on('error', () => { /* directory deleted or inaccessible */ });
32
+ } catch { /* directory missing or recursive watch unsupported */ }
33
+
34
+ const projectSkillsDir = findSkillsDir(this.workingDir);
35
+ if (projectSkillsDir) {
36
+ try {
37
+ this.projectWatcher = watch(projectSkillsDir, { recursive: true }, (_event, filename) => {
38
+ if (!filename || !filename.endsWith('.md')) return;
39
+ this.debounce();
40
+ });
41
+ this.projectWatcher.on('error', () => { /* directory deleted or inaccessible */ });
42
+ } catch { /* directory missing or recursive watch unsupported */ }
43
+ }
44
+
45
+ this.started = true;
46
+ }
47
+
48
+ stop(): void {
49
+ if (this.userWatcher) { this.userWatcher.close(); this.userWatcher = null; }
50
+ if (this.projectWatcher) { this.projectWatcher.close(); this.projectWatcher = null; }
51
+ if (this.debounceTimer) { clearTimeout(this.debounceTimer); this.debounceTimer = null; }
52
+ this.started = false;
53
+ }
54
+
55
+ private debounce(): void {
56
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
57
+ this.debounceTimer = setTimeout(() => {
58
+ this.handleChange();
59
+ }, 250);
60
+ }
61
+
62
+ private handleChange(): void {
63
+ try {
64
+ const skills = collectAllSkills(this.workingDir);
65
+ const hash = JSON.stringify(skills.map(s => `${s.name}:${s.source}:${s.description}`));
66
+ if (hash === this.lastSkillsHash) return;
67
+ this.lastSkillsHash = hash;
68
+
69
+ this.ctx.broadcastToAll({ type: 'skillsList', data: { skills } });
70
+ } catch {
71
+ // Ignore errors from partial writes or missing files
72
+ }
73
+ }
74
+
75
+ private computeHash(): string {
76
+ const skills = collectAllSkills(this.workingDir);
77
+ return JSON.stringify(skills.map(s => `${s.name}:${s.source}:${s.description}`));
78
+ }
79
+ }
@@ -52,10 +52,6 @@ const PlanBoardMessages = ['planCreateBoard', 'planUpdateBoard', 'planArchiveBoa
52
52
 
53
53
  const PlanSprintMessages = ['planCreateSprint', 'planActivateSprint', 'planCompleteSprint', 'planGetSprintArtifacts'] as const;
54
54
 
55
- const DeployMessages = ['deployCreate', 'deployStop', 'deployResume', 'deployDelete', 'deployList', 'deployGetStatus', 'deployUpdateConfig', 'deploySetApiKey', 'deployValidateApiKey'] as const;
56
-
57
- const DeployRelayMessages = ['deployHttpRequest', 'deployUsageReport', 'deployAiHealthUpdate'] as const;
58
-
59
55
  const SkillMessages = ['listSkills', 'chatToBoard'] as const;
60
56
 
61
57
  type WebSocketMessageType =
@@ -75,8 +71,6 @@ type WebSocketMessageType =
75
71
  | typeof PlanMessages[number]
76
72
  | typeof PlanBoardMessages[number]
77
73
  | typeof PlanSprintMessages[number]
78
- | typeof DeployMessages[number]
79
- | typeof DeployRelayMessages[number]
80
74
  | typeof SkillMessages[number];
81
75
 
82
76
  export interface WebSocketMessage {
@@ -121,10 +115,6 @@ const PlanBoardResponseMessages = ['planBoardCreated', 'planBoardUpdated', 'plan
121
115
 
122
116
  const PlanSprintResponseMessages = ['planSprintCreated', 'planSprintUpdated', 'planSprintCompleted', 'planSprintArtifacts', 'planReviewProgress'] as const;
123
117
 
124
- const DeployResponseMessages = ['deployCreated', 'deployStopped', 'deployResumed', 'deployDeleted', 'deployListResult', 'deployStatusResult', 'deployConfigUpdated', 'deployApiKeyStatus', 'deployError'] as const;
125
-
126
- const DeployRelayResponseMessages = ['deployHttpResponse', 'deployHttpResponseChunk', 'deployStatus', 'deployUsageReportAck', 'deployAiHealthAck'] as const;
127
-
128
118
  const SkillResponseMessages = ['skillsList', 'chatToBoardCreated'] as const;
129
119
 
130
120
  type WebSocketResponseType =
@@ -144,8 +134,6 @@ type WebSocketResponseType =
144
134
  | typeof PlanResponseMessages[number]
145
135
  | typeof PlanBoardResponseMessages[number]
146
136
  | typeof PlanSprintResponseMessages[number]
147
- | typeof DeployResponseMessages[number]
148
- | typeof DeployRelayResponseMessages[number]
149
137
  | typeof SkillResponseMessages[number];
150
138
 
151
139
  export interface WebSocketResponse {
@@ -468,302 +456,3 @@ export interface WorktreeMergeResult {
468
456
  conflictFiles?: string[];
469
457
  }
470
458
 
471
- // ============================================================================
472
- // Deploy Types
473
- // ============================================================================
474
-
475
- /**
476
- * Deployment status
477
- */
478
- export type DeploymentStatus = 'running' | 'stopped' | 'error' | 'starting' | 'stopping';
479
-
480
- /**
481
- * Deployment configuration
482
- */
483
- export interface DeployConfig {
484
- /** Subdomain for the deployment (e.g. "my-app" -> my-app.mstro.app) */
485
- subdomain: string;
486
- /** Local port to expose */
487
- port: number;
488
- /** Whether AI features are enabled for this deployment */
489
- aiEnabled: boolean;
490
- /** Custom domain if configured */
491
- customDomain?: string;
492
- }
493
-
494
- /**
495
- * Deployment info returned in responses
496
- */
497
- export interface DeploymentInfo {
498
- /** Unique deployment identifier */
499
- deploymentId: string;
500
- /** Deployment configuration */
501
- config: DeployConfig;
502
- /** Current deployment status */
503
- status: DeploymentStatus;
504
- /** Public URL of the deployment */
505
- url: string;
506
- /** When the deployment was created (ISO string) */
507
- createdAt: string;
508
- /** When the deployment was last updated (ISO string) */
509
- updatedAt: string;
510
- }
511
-
512
- /**
513
- * Message data for deployCreate request
514
- */
515
- export interface DeployCreateData {
516
- /** Subdomain for the deployment */
517
- subdomain: string;
518
- /** Local port to expose */
519
- port: number;
520
- /** Whether AI features are enabled */
521
- aiEnabled: boolean;
522
- }
523
-
524
- /**
525
- * Message data for deployStop request
526
- */
527
- export interface DeployStopData {
528
- /** ID of the deployment to stop */
529
- deploymentId: string;
530
- }
531
-
532
- /**
533
- * Message data for deployResume request
534
- */
535
- export interface DeployResumeData {
536
- /** ID of the deployment to resume */
537
- deploymentId: string;
538
- }
539
-
540
- /**
541
- * Message data for deployDelete request
542
- */
543
- export interface DeployDeleteData {
544
- /** ID of the deployment to delete */
545
- deploymentId: string;
546
- }
547
-
548
- /**
549
- * Message data for deployGetStatus request
550
- */
551
- export interface DeployGetStatusData {
552
- /** ID of the deployment to get status for */
553
- deploymentId: string;
554
- }
555
-
556
- /**
557
- * Message data for deployUpdateConfig request
558
- */
559
- export interface DeployUpdateConfigData {
560
- /** ID of the deployment to update */
561
- deploymentId: string;
562
- /** Partial config to update */
563
- config: Partial<DeployConfig>;
564
- }
565
-
566
- /**
567
- * Message data for deploySetApiKey request
568
- * Note: API key transits via WSS but is never stored or logged by the server relay
569
- */
570
- export interface DeploySetApiKeyData {
571
- /** The API key to set */
572
- apiKey: string;
573
- }
574
-
575
- /**
576
- * Response data for deployCreated
577
- */
578
- export interface DeployCreatedResponse {
579
- /** The created deployment */
580
- deployment: DeploymentInfo;
581
- }
582
-
583
- /**
584
- * Response data for deployStopped
585
- */
586
- export interface DeployStoppedResponse {
587
- /** ID of the stopped deployment */
588
- deploymentId: string;
589
- }
590
-
591
- /**
592
- * Response data for deployResumed
593
- */
594
- export interface DeployResumedResponse {
595
- /** ID of the resumed deployment */
596
- deploymentId: string;
597
- }
598
-
599
- /**
600
- * Response data for deployDeleted
601
- */
602
- export interface DeployDeletedResponse {
603
- /** ID of the deleted deployment */
604
- deploymentId: string;
605
- }
606
-
607
- /**
608
- * Response data for deployListResult
609
- */
610
- export interface DeployListResultResponse {
611
- /** All deployments for this orchestra */
612
- deployments: DeploymentInfo[];
613
- }
614
-
615
- /**
616
- * Response data for deployStatusResult
617
- */
618
- export interface DeployStatusResultResponse {
619
- /** The requested deployment */
620
- deployment: DeploymentInfo;
621
- }
622
-
623
- /**
624
- * Response data for deployConfigUpdated
625
- */
626
- export interface DeployConfigUpdatedResponse {
627
- /** The updated deployment */
628
- deployment: DeploymentInfo;
629
- }
630
-
631
- /**
632
- * Response data for deployApiKeyStatus
633
- * Note: Never includes the full API key — only the last four characters
634
- */
635
- export interface DeployApiKeyStatusResponse {
636
- /** Validation status of the API key */
637
- status: 'valid' | 'invalid' | 'missing' | 'rate_limited';
638
- /** Last four characters of the API key (for display) */
639
- lastFour?: string;
640
- /** Where the key was detected from */
641
- source?: 'env' | 'stored';
642
- }
643
-
644
- /**
645
- * Response data for deployError
646
- */
647
- export interface DeployErrorResponse {
648
- /** Error message */
649
- error: string;
650
- /** The deployment ID that caused the error, if applicable */
651
- deploymentId?: string;
652
- }
653
-
654
- // ============================================================================
655
- // Deploy HTTP Relay Types
656
- // ============================================================================
657
-
658
- /**
659
- * Message data for deployHttpRequest (server→cli)
660
- * Represents an inbound HTTP request to be handled by the CLI
661
- */
662
- export interface DeployHttpRequestData {
663
- /** UUID for correlating this request with its response */
664
- requestId: string;
665
- /** HTTP method (GET, POST, PUT, DELETE, etc.) */
666
- method: string;
667
- /** Request URL path with query string (e.g. "/api/hello?name=world") */
668
- url: string;
669
- /** HTTP request headers */
670
- headers: Record<string, string>;
671
- /** HTTP request body (may be undefined for GET/HEAD) */
672
- body?: string;
673
- /** ID of the deployment this request is for */
674
- deploymentId: string;
675
- /** Local port of the developer's server to proxy to */
676
- port: number;
677
- }
678
-
679
- /**
680
- * Response data for deployHttpResponse (cli→server)
681
- * The CLI's response to a proxied HTTP request
682
- */
683
- export interface DeployHttpResponseData {
684
- /** UUID matching the original deployHttpRequest */
685
- requestId: string;
686
- /** HTTP status code */
687
- status: number;
688
- /** HTTP response headers */
689
- headers: Record<string, string>;
690
- /** HTTP response body */
691
- body?: string;
692
- }
693
-
694
- /**
695
- * Response data for deployHttpResponseChunk (cli→server)
696
- * A chunk of a large HTTP response streamed over multiple WebSocket messages
697
- */
698
- export interface DeployHttpResponseChunkData {
699
- /** UUID matching the original deployHttpRequest */
700
- requestId: string;
701
- /** Zero-based index of this chunk */
702
- chunkIndex: number;
703
- /** Total number of chunks (known after reading the full response) */
704
- totalChunks: number;
705
- /** Base64-encoded chunk data */
706
- data: string;
707
- /** Whether this is the last chunk */
708
- isLast: boolean;
709
- /** HTTP status code (sent with first chunk) */
710
- status?: number;
711
- /** HTTP response headers (sent with first chunk) */
712
- headers?: Record<string, string>;
713
- }
714
-
715
- /**
716
- * Response data for deployStatus (cli→server)
717
- * Status update for a deployment
718
- */
719
- export interface DeployStatusData {
720
- /** ID of the deployment */
721
- deploymentId: string;
722
- /** Current status of the deployment */
723
- status: DeploymentStatus;
724
- }
725
-
726
- // ============================================================================
727
- // Deploy Usage & Health Types
728
- // ============================================================================
729
-
730
- /**
731
- * Message data for deployUsageReport (cli→server)
732
- * Sent after each AI execution. Contains metadata only — never prompts or responses.
733
- */
734
- export interface DeployUsageReportData {
735
- /** ID of the deployment */
736
- deploymentId: string;
737
- /** End user who triggered the execution */
738
- endUserId: string;
739
- /** AI capability used */
740
- capability: 'headless' | 'pm-board';
741
- /** Total tokens consumed (input + output) */
742
- tokensUsed: number;
743
- /** Model used for execution */
744
- model: string;
745
- /** Execution duration in milliseconds */
746
- durationMs: number;
747
- /** Optional board ID if capability was pm-board */
748
- boardId?: string;
749
- }
750
-
751
- /**
752
- * AI health status values
753
- */
754
- export type DeployAiHealthStatus = 'healthy' | 'invalid_key' | 'no_credits' | 'rate_limited' | 'unknown_error';
755
-
756
- /**
757
- * Message data for deployAiHealthUpdate (cli→server)
758
- * Sent when the CLI detects an API key or AI service issue.
759
- */
760
- export interface DeployAiHealthUpdateData {
761
- /** ID of the deployment */
762
- deploymentId: string;
763
- /** Current AI health status */
764
- status: DeployAiHealthStatus;
765
- /** Human-readable error message */
766
- message: string;
767
- /** Whether AI features are currently disabled locally */
768
- aiDisabled: boolean;
769
- }
@@ -1,63 +0,0 @@
1
- import { Hono } from 'hono';
2
- import { type HealthUpdateData, type UsageReportData } from './headless-session-handler.js';
3
- export interface AiBrokerInvokeBody {
4
- capability: 'headless' | 'pm-board';
5
- deploymentId: string;
6
- endUserId: string;
7
- prompt: string;
8
- boardTemplateId?: string;
9
- systemPrompt?: string;
10
- allowedTools?: string[];
11
- model?: string;
12
- }
13
- interface DeployTokenRecord {
14
- deploymentId: string;
15
- tokenHash: string;
16
- capabilities: ('headless' | 'pm-board')[];
17
- rateLimit: {
18
- maxRequestsPerMinute: number | null;
19
- maxConcurrentSessions: number;
20
- };
21
- aiConfig: {
22
- aiEnabled: boolean;
23
- defaultSystemPrompt: string | null;
24
- defaultModel: string;
25
- maxTokensPerRequest: number | null;
26
- workingDir: string;
27
- allowedBoardTemplateIds: string[];
28
- maxConcurrentBoardExecutions: number;
29
- maxBoardExecutionsPerMinute: number | null;
30
- };
31
- /** When set, the deployment requires payment and this URL is returned on 402 */
32
- paymentUrl?: string;
33
- /** Whether the deployment is currently active */
34
- enabled: boolean;
35
- }
36
- export declare function registerDeployToken(record: DeployTokenRecord): void;
37
- export declare function unregisterDeployToken(deploymentId: string): void;
38
- export declare function getDeployTokenRecord(deploymentId: string): DeployTokenRecord | undefined;
39
- /**
40
- * Update rate limit and AI config on an existing deploy token record.
41
- * Called when the server syncs updated deployment config to the CLI.
42
- */
43
- export declare function updateDeployTokenConfig(deploymentId: string, updates: {
44
- maxRequestsPerMinute?: number | null;
45
- maxConcurrentSessions?: number;
46
- maxTokensPerRequest?: number | null;
47
- aiEnabled?: boolean;
48
- }): boolean;
49
- type UsageReportListener = (report: UsageReportData) => void;
50
- type HealthUpdateListener = (update: HealthUpdateData) => void;
51
- /**
52
- * Register a listener for deploy usage reports.
53
- * Called from the server setup to wire usage reports to the platform connection.
54
- */
55
- export declare function setDeployUsageReportListener(listener: UsageReportListener): void;
56
- /**
57
- * Register a listener for deploy AI health updates.
58
- * Called from the server setup to wire health updates to the platform connection.
59
- */
60
- export declare function setDeployHealthUpdateListener(listener: HealthUpdateListener): void;
61
- export declare function createAiBrokerRoutes(): Hono;
62
- export {};
63
- //# sourceMappingURL=ai-broker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ai-broker.d.ts","sourceRoot":"","sources":["../../../../server/services/deploy/ai-broker.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAgB,IAAI,EAAE,MAAM,MAAM,CAAC;AAO1C,OAAO,EAGL,KAAK,gBAAgB,EAErB,KAAK,eAAe,EACrB,MAAM,+BAA+B,CAAC;AAIvC,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,UAAU,GAAG,UAAU,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,iBAAiB;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,CAAC,UAAU,GAAG,UAAU,CAAC,EAAE,CAAC;IAC1C,SAAS,EAAE;QACT,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;QACpC,qBAAqB,EAAE,MAAM,CAAC;KAC/B,CAAC;IACF,QAAQ,EAAE;QACR,SAAS,EAAE,OAAO,CAAC;QACnB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,YAAY,EAAE,MAAM,CAAC;QACrB,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;QACnC,UAAU,EAAE,MAAM,CAAC;QACnB,uBAAuB,EAAE,MAAM,EAAE,CAAC;QAClC,4BAA4B,EAAE,MAAM,CAAC;QACrC,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5C,CAAC;IACF,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC;CAClB;AAWD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAEnE;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAExF;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE;IACP,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,GACA,OAAO,CAkBT;AAID,KAAK,mBAAmB,GAAG,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;AAC7D,KAAK,oBAAoB,GAAG,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAK/D;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAEhF;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAElF;AA8ID,wBAAgB,oBAAoB,IAAI,IAAI,CAoD3C"}