mstro-app 0.4.29 → 0.4.33

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 (105) hide show
  1. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -1
  2. package/dist/server/cli/headless/haiku-assessments.js +20 -28
  3. package/dist/server/cli/headless/haiku-assessments.js.map +1 -1
  4. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  5. package/dist/server/cli/headless/stall-assessor.js +17 -3
  6. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  7. package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
  8. package/dist/server/cli/improvisation-retry.js +18 -1
  9. package/dist/server/cli/improvisation-retry.js.map +1 -1
  10. package/dist/server/cli/improvisation-session-manager.d.ts +5 -0
  11. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  12. package/dist/server/cli/improvisation-session-manager.js +41 -1
  13. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  14. package/dist/server/cli/prompt-builders.d.ts.map +1 -1
  15. package/dist/server/cli/prompt-builders.js +35 -19
  16. package/dist/server/cli/prompt-builders.js.map +1 -1
  17. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
  18. package/dist/server/mcp/bouncer-haiku.js +5 -30
  19. package/dist/server/mcp/bouncer-haiku.js.map +1 -1
  20. package/dist/server/mcp/security-analysis.d.ts.map +1 -1
  21. package/dist/server/mcp/security-analysis.js +19 -11
  22. package/dist/server/mcp/security-analysis.js.map +1 -1
  23. package/dist/server/services/deploy/headless-session-handler.d.ts.map +1 -1
  24. package/dist/server/services/deploy/headless-session-handler.js +61 -69
  25. package/dist/server/services/deploy/headless-session-handler.js.map +1 -1
  26. package/dist/server/services/files.d.ts.map +1 -1
  27. package/dist/server/services/files.js +6 -2
  28. package/dist/server/services/files.js.map +1 -1
  29. package/dist/server/services/pathUtils.d.ts.map +1 -1
  30. package/dist/server/services/pathUtils.js +46 -38
  31. package/dist/server/services/pathUtils.js.map +1 -1
  32. package/dist/server/services/plan/agent-loader.d.ts +20 -4
  33. package/dist/server/services/plan/agent-loader.d.ts.map +1 -1
  34. package/dist/server/services/plan/agent-loader.js +69 -16
  35. package/dist/server/services/plan/agent-loader.js.map +1 -1
  36. package/dist/server/services/plan/issue-retry.d.ts +0 -8
  37. package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
  38. package/dist/server/services/plan/issue-retry.js +72 -63
  39. package/dist/server/services/plan/issue-retry.js.map +1 -1
  40. package/dist/server/services/plan/review-gate.js +16 -88
  41. package/dist/server/services/plan/review-gate.js.map +1 -1
  42. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  43. package/dist/server/services/websocket/file-explorer-handlers.js +23 -2
  44. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  45. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  46. package/dist/server/services/websocket/git-handlers.js +21 -19
  47. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  48. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  49. package/dist/server/services/websocket/git-pr-handlers.js +5 -21
  50. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  51. package/dist/server/services/websocket/handler.d.ts +2 -0
  52. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  53. package/dist/server/services/websocket/handler.js +36 -18
  54. package/dist/server/services/websocket/handler.js.map +1 -1
  55. package/dist/server/services/websocket/handlers/deploy-handlers.d.ts.map +1 -1
  56. package/dist/server/services/websocket/handlers/deploy-handlers.js +28 -33
  57. package/dist/server/services/websocket/handlers/deploy-handlers.js.map +1 -1
  58. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
  59. package/dist/server/services/websocket/plan-board-handlers.js +31 -25
  60. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
  61. package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -1
  62. package/dist/server/services/websocket/quality-fix-agent.js +11 -18
  63. package/dist/server/services/websocket/quality-fix-agent.js.map +1 -1
  64. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
  65. package/dist/server/services/websocket/quality-review-agent.js +13 -150
  66. package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
  67. package/dist/server/services/websocket/session-history.d.ts.map +1 -1
  68. package/dist/server/services/websocket/session-history.js +10 -8
  69. package/dist/server/services/websocket/session-history.js.map +1 -1
  70. package/dist/server/services/websocket/skill-handlers.d.ts +4 -0
  71. package/dist/server/services/websocket/skill-handlers.d.ts.map +1 -0
  72. package/dist/server/services/websocket/skill-handlers.js +93 -0
  73. package/dist/server/services/websocket/skill-handlers.js.map +1 -0
  74. package/dist/server/services/websocket/types.d.ts +8 -2
  75. package/dist/server/services/websocket/types.d.ts.map +1 -1
  76. package/dist/server/utils/paths.d.ts +4 -0
  77. package/dist/server/utils/paths.d.ts.map +1 -1
  78. package/dist/server/utils/paths.js +18 -1
  79. package/dist/server/utils/paths.js.map +1 -1
  80. package/package.json +1 -1
  81. package/server/cli/headless/haiku-assessments.ts +21 -28
  82. package/server/cli/headless/stall-assessor.ts +17 -3
  83. package/server/cli/improvisation-retry.ts +19 -1
  84. package/server/cli/improvisation-session-manager.ts +44 -1
  85. package/server/cli/prompt-builders.ts +34 -23
  86. package/server/mcp/bouncer-haiku.ts +5 -30
  87. package/server/mcp/security-analysis.ts +19 -12
  88. package/server/services/deploy/headless-session-handler.ts +75 -76
  89. package/server/services/files.ts +7 -2
  90. package/server/services/pathUtils.ts +55 -42
  91. package/server/services/plan/agent-loader.ts +73 -15
  92. package/server/services/plan/issue-retry.ts +93 -68
  93. package/server/services/plan/review-gate.ts +13 -89
  94. package/server/services/websocket/file-explorer-handlers.ts +23 -2
  95. package/server/services/websocket/git-handlers.ts +23 -18
  96. package/server/services/websocket/git-pr-handlers.ts +5 -20
  97. package/server/services/websocket/handler.ts +35 -16
  98. package/server/services/websocket/handlers/deploy-handlers.ts +34 -37
  99. package/server/services/websocket/plan-board-handlers.ts +36 -21
  100. package/server/services/websocket/quality-fix-agent.ts +10 -17
  101. package/server/services/websocket/quality-review-agent.ts +12 -149
  102. package/server/services/websocket/session-history.ts +10 -8
  103. package/server/services/websocket/skill-handlers.ts +90 -0
  104. package/server/services/websocket/types.ts +13 -2
  105. package/server/utils/paths.ts +17 -1
@@ -2,6 +2,7 @@
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
 
4
4
  import { existsSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
5
+ import { readFile } from 'node:fs/promises';
5
6
  import { join } from 'node:path';
6
7
  import type { HandlerContext } from './handler-context.js';
7
8
  import type { WebSocketMessage, WSContext } from './types.js';
@@ -9,8 +10,9 @@ import type { WebSocketMessage, WSContext } from './types.js';
9
10
  export function handleHistoryMessage(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): void {
10
11
  switch (msg.type) {
11
12
  case 'getSessions': {
12
- const result = getSessionsList(workingDir, msg.data?.limit ?? 20, msg.data?.offset ?? 0);
13
- ctx.send(ws, { type: 'sessions', tabId, data: result });
13
+ getSessionsList(workingDir, msg.data?.limit ?? 20, msg.data?.offset ?? 0).then(result => {
14
+ ctx.send(ws, { type: 'sessions', tabId, data: result });
15
+ });
14
16
  break;
15
17
  }
16
18
  case 'getSessionsCount':
@@ -42,7 +44,7 @@ function getSessionsCount(workingDir: string): number {
42
44
  return readdirSync(sessionsDir).filter((name: string) => name.endsWith('.json')).length;
43
45
  }
44
46
 
45
- function getSessionsList(workingDir: string, limit: number = 20, offset: number = 0): { sessions: Array<Record<string, unknown> | null>; total: number; hasMore: boolean } {
47
+ async function getSessionsList(workingDir: string, limit: number = 20, offset: number = 0): Promise<{ sessions: Array<Record<string, unknown> | null>; total: number; hasMore: boolean }> {
46
48
  const sessionsDir = join(workingDir, '.mstro', 'history');
47
49
 
48
50
  if (!existsSync(sessionsDir)) {
@@ -60,17 +62,17 @@ function getSessionsList(workingDir: string, limit: number = 20, offset: number
60
62
  const total = historyFiles.length;
61
63
  const pageFiles = historyFiles.slice(offset, offset + limit);
62
64
 
63
- const sessions = pageFiles.map((filename: string) => {
65
+ const sessions = await Promise.all(pageFiles.map(async (filename: string) => {
64
66
  const historyPath = join(sessionsDir, filename);
65
67
  try {
66
- const historyData = JSON.parse(readFileSync(historyPath, 'utf-8'));
67
- return buildSessionSummary(historyData);
68
+ const raw = await readFile(historyPath, 'utf-8');
69
+ return buildSessionSummary(JSON.parse(raw));
68
70
  } catch {
69
71
  return null;
70
72
  }
71
- }).filter(Boolean);
73
+ }));
72
74
 
73
- return { sessions, total, hasMore: offset + limit < total };
75
+ return { sessions: sessions.filter(Boolean), total, hasMore: offset + limit < total };
74
76
  }
75
77
 
76
78
  function getSessionById(workingDir: string, sessionId: string): Record<string, unknown> | null {
@@ -0,0 +1,90 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
5
+ import { dirname, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { findSkillsDir } from '../../utils/paths.js';
8
+ import type { HandlerContext } from './handler-context.js';
9
+ import type { SkillEntry, WSContext } from './types.js';
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const SYSTEM_AGENTS_DIR = join(__dirname, '..', 'plan', 'agents');
13
+
14
+ function parseFrontmatter(content: string): Record<string, string> {
15
+ if (!content.startsWith('---')) return {};
16
+ const endIdx = content.indexOf('---', 3);
17
+ if (endIdx === -1) return {};
18
+ const yaml = content.slice(3, endIdx).trim();
19
+ const result: Record<string, string> = {};
20
+ for (const line of yaml.split('\n')) {
21
+ const colonIdx = line.indexOf(':');
22
+ if (colonIdx === -1) continue;
23
+ const key = line.slice(0, colonIdx).trim();
24
+ let val = line.slice(colonIdx + 1).trim();
25
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
26
+ val = val.slice(1, -1);
27
+ }
28
+ result[key] = val;
29
+ }
30
+ return result;
31
+ }
32
+
33
+ function scanProjectSkills(skillsDir: string): SkillEntry[] {
34
+ if (!existsSync(skillsDir)) return [];
35
+ const entries: SkillEntry[] = [];
36
+ for (const name of readdirSync(skillsDir, { withFileTypes: true })) {
37
+ if (!name.isDirectory()) continue;
38
+ const skillFile = join(skillsDir, name.name, 'SKILL.md');
39
+ if (!existsSync(skillFile)) continue;
40
+ try {
41
+ const content = readFileSync(skillFile, 'utf-8');
42
+ const fm = parseFrontmatter(content);
43
+ if (fm['user-invocable'] === 'false') continue;
44
+ entries.push({
45
+ name: fm.name || name.name,
46
+ displayName: `/${fm.name || name.name}`,
47
+ description: fm.description || '',
48
+ source: 'project',
49
+ });
50
+ } catch { /* skip unreadable files */ }
51
+ }
52
+ return entries;
53
+ }
54
+
55
+ function scanSystemAgents(agentsDir: string, seen: Set<string>): SkillEntry[] {
56
+ if (!existsSync(agentsDir)) return [];
57
+ const entries: SkillEntry[] = [];
58
+ for (const file of readdirSync(agentsDir)) {
59
+ if (!file.endsWith('.md')) continue;
60
+ const name = file.replace(/\.md$/, '');
61
+ if (seen.has(name)) continue;
62
+ try {
63
+ const content = readFileSync(join(agentsDir, file), 'utf-8');
64
+ const fm = parseFrontmatter(content);
65
+ if (fm['user-invocable'] === 'false') continue;
66
+ entries.push({
67
+ name: fm.name || name,
68
+ displayName: `/${fm.name || name}`,
69
+ description: fm.description || '',
70
+ source: 'system',
71
+ });
72
+ } catch { /* skip unreadable files */ }
73
+ }
74
+ return entries;
75
+ }
76
+
77
+ export function handleListSkills(ctx: HandlerContext, ws: WSContext, workingDir: string): void {
78
+ const skills: SkillEntry[] = [];
79
+
80
+ const projectSkillsDir = findSkillsDir(workingDir);
81
+ if (projectSkillsDir) {
82
+ skills.push(...scanProjectSkills(projectSkillsDir));
83
+ }
84
+
85
+ const seen = new Set(skills.map(s => s.name));
86
+ skills.push(...scanSystemAgents(SYSTEM_AGENTS_DIR, seen));
87
+
88
+ skills.sort((a, b) => a.name.localeCompare(b.name));
89
+ ctx.send(ws, { type: 'skillsList', data: { skills } });
90
+ }
@@ -168,7 +168,9 @@ export interface WebSocketMessage {
168
168
  | 'deployHttpRequest'
169
169
  // Deploy usage/health message types (cli→server)
170
170
  | 'deployUsageReport'
171
- | 'deployAiHealthUpdate';
171
+ | 'deployAiHealthUpdate'
172
+ // Skill discovery
173
+ | 'listSkills';
172
174
  tabId?: string;
173
175
  terminalId?: string;
174
176
  // biome-ignore lint/suspicious/noExplicitAny: message envelope carries heterogeneous payloads
@@ -346,7 +348,9 @@ export interface WebSocketResponse {
346
348
  | 'deployHttpResponseChunk'
347
349
  | 'deployStatus'
348
350
  | 'deployUsageReportAck'
349
- | 'deployAiHealthAck';
351
+ | 'deployAiHealthAck'
352
+ // Skill discovery response types
353
+ | 'skillsList';
350
354
  tabId?: string;
351
355
  terminalId?: string;
352
356
  // biome-ignore lint/suspicious/noExplicitAny: message envelope carries heterogeneous payloads
@@ -358,6 +362,13 @@ export interface ConnectionData {
358
362
  workingDir: string;
359
363
  }
360
364
 
365
+ export interface SkillEntry {
366
+ name: string;
367
+ displayName: string;
368
+ description: string;
369
+ source: 'project' | 'system' | 'builtin';
370
+ }
371
+
361
372
  // Extended autocomplete option with metadata
362
373
  export interface AutocompleteResult {
363
374
  value: string;
@@ -8,7 +8,8 @@
8
8
  * Works correctly whether running from source or installed globally.
9
9
  */
10
10
 
11
- import { dirname, resolve } from 'node:path';
11
+ import { existsSync } from 'node:fs';
12
+ import { dirname, join, resolve } from 'node:path';
12
13
  import { fileURLToPath } from 'node:url';
13
14
 
14
15
  // ES module equivalent of __dirname for this file
@@ -29,3 +30,18 @@ export const MSTRO_ROOT = resolve(__dirname, '../..');
29
30
  */
30
31
  export const MCP_SERVER_PATH = resolve(MSTRO_ROOT, 'server/mcp/server.ts');
31
32
 
33
+ /**
34
+ * Walk up from startDir looking for `.claude/skills/`. Returns the path if found, null otherwise.
35
+ */
36
+ export function findSkillsDir(startDir: string): string | null {
37
+ let dir = startDir;
38
+ for (let i = 0; i < 10; i++) {
39
+ const candidate = join(dir, '.claude', 'skills');
40
+ if (existsSync(candidate)) return candidate;
41
+ const parent = dirname(dir);
42
+ if (parent === dir) break;
43
+ dir = parent;
44
+ }
45
+ return null;
46
+ }
47
+