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.
- package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.js +20 -28
- package/dist/server/cli/headless/haiku-assessments.js.map +1 -1
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +17 -3
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
- package/dist/server/cli/improvisation-retry.js +18 -1
- package/dist/server/cli/improvisation-retry.js.map +1 -1
- package/dist/server/cli/improvisation-session-manager.d.ts +5 -0
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +41 -1
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/prompt-builders.d.ts.map +1 -1
- package/dist/server/cli/prompt-builders.js +35 -19
- package/dist/server/cli/prompt-builders.js.map +1 -1
- package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-haiku.js +5 -30
- package/dist/server/mcp/bouncer-haiku.js.map +1 -1
- package/dist/server/mcp/security-analysis.d.ts.map +1 -1
- package/dist/server/mcp/security-analysis.js +19 -11
- package/dist/server/mcp/security-analysis.js.map +1 -1
- package/dist/server/services/deploy/headless-session-handler.d.ts.map +1 -1
- package/dist/server/services/deploy/headless-session-handler.js +61 -69
- package/dist/server/services/deploy/headless-session-handler.js.map +1 -1
- package/dist/server/services/files.d.ts.map +1 -1
- package/dist/server/services/files.js +6 -2
- package/dist/server/services/files.js.map +1 -1
- package/dist/server/services/pathUtils.d.ts.map +1 -1
- package/dist/server/services/pathUtils.js +46 -38
- package/dist/server/services/pathUtils.js.map +1 -1
- package/dist/server/services/plan/agent-loader.d.ts +20 -4
- package/dist/server/services/plan/agent-loader.d.ts.map +1 -1
- package/dist/server/services/plan/agent-loader.js +69 -16
- package/dist/server/services/plan/agent-loader.js.map +1 -1
- package/dist/server/services/plan/issue-retry.d.ts +0 -8
- package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
- package/dist/server/services/plan/issue-retry.js +72 -63
- package/dist/server/services/plan/issue-retry.js.map +1 -1
- package/dist/server/services/plan/review-gate.js +16 -88
- package/dist/server/services/plan/review-gate.js.map +1 -1
- package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-explorer-handlers.js +23 -2
- package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +21 -19
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.js +5 -21
- package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
- package/dist/server/services/websocket/handler.d.ts +2 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +36 -18
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/handlers/deploy-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/handlers/deploy-handlers.js +28 -33
- package/dist/server/services/websocket/handlers/deploy-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.js +31 -25
- package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-fix-agent.js +11 -18
- package/dist/server/services/websocket/quality-fix-agent.js.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.js +13 -150
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
- package/dist/server/services/websocket/session-history.d.ts.map +1 -1
- package/dist/server/services/websocket/session-history.js +10 -8
- package/dist/server/services/websocket/session-history.js.map +1 -1
- package/dist/server/services/websocket/skill-handlers.d.ts +4 -0
- package/dist/server/services/websocket/skill-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/skill-handlers.js +93 -0
- package/dist/server/services/websocket/skill-handlers.js.map +1 -0
- package/dist/server/services/websocket/types.d.ts +8 -2
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/dist/server/utils/paths.d.ts +4 -0
- package/dist/server/utils/paths.d.ts.map +1 -1
- package/dist/server/utils/paths.js +18 -1
- package/dist/server/utils/paths.js.map +1 -1
- package/package.json +1 -1
- package/server/cli/headless/haiku-assessments.ts +21 -28
- package/server/cli/headless/stall-assessor.ts +17 -3
- package/server/cli/improvisation-retry.ts +19 -1
- package/server/cli/improvisation-session-manager.ts +44 -1
- package/server/cli/prompt-builders.ts +34 -23
- package/server/mcp/bouncer-haiku.ts +5 -30
- package/server/mcp/security-analysis.ts +19 -12
- package/server/services/deploy/headless-session-handler.ts +75 -76
- package/server/services/files.ts +7 -2
- package/server/services/pathUtils.ts +55 -42
- package/server/services/plan/agent-loader.ts +73 -15
- package/server/services/plan/issue-retry.ts +93 -68
- package/server/services/plan/review-gate.ts +13 -89
- package/server/services/websocket/file-explorer-handlers.ts +23 -2
- package/server/services/websocket/git-handlers.ts +23 -18
- package/server/services/websocket/git-pr-handlers.ts +5 -20
- package/server/services/websocket/handler.ts +35 -16
- package/server/services/websocket/handlers/deploy-handlers.ts +34 -37
- package/server/services/websocket/plan-board-handlers.ts +36 -21
- package/server/services/websocket/quality-fix-agent.ts +10 -17
- package/server/services/websocket/quality-review-agent.ts +12 -149
- package/server/services/websocket/session-history.ts +10 -8
- package/server/services/websocket/skill-handlers.ts +90 -0
- package/server/services/websocket/types.ts +13 -2
- 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
|
-
|
|
13
|
-
|
|
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
|
|
67
|
-
return buildSessionSummary(
|
|
68
|
+
const raw = await readFile(historyPath, 'utf-8');
|
|
69
|
+
return buildSessionSummary(JSON.parse(raw));
|
|
68
70
|
} catch {
|
|
69
71
|
return null;
|
|
70
72
|
}
|
|
71
|
-
})
|
|
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;
|
package/server/utils/paths.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* Works correctly whether running from source or installed globally.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
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
|
+
|