Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ This project follows a [Code of Conduct](CODE_OF_CONDUCT.md). By participating,

### Prerequisites

- **Node.js** 20+
- **Node.js** 22.12+
- **pnpm** 9+
- **GitHub Copilot CLI** installed and authenticated
- **Chrome/Chromium** browser
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ A Chrome/Chromium browser extension that provides DevOps mentoring, infrastructu

## Prerequisites

- **Node.js** 20+
- **Node.js** 22.12+
- **pnpm** 9+
- **GitHub Copilot CLI** installed and authenticated ([Installation Guide](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli))
- **Chrome/Chromium** browser
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ devmentorai-server logs --lines 100

## Requirements

- **Node.js** >= 20.0.0
- **Node.js** 22.12+
- **GitHub Copilot CLI** (optional — server runs in mock mode without it)

## How It Works
Expand Down
49 changes: 24 additions & 25 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"url": "https://github.com/BOTOOM/devmentorai/issues"
},
"engines": {
"node": ">=20.0.0"
"node": ">=22.12.0"
},
"scripts": {
"dev": "tsx watch src/server.ts",
Expand All @@ -49,35 +49,34 @@
"release": "semantic-release"
},
"dependencies": {
"@fastify/cors": "^10.0.2",
"@github/copilot": "^1.0.64",
"@github/copilot-sdk": "^0.3.0",
"better-sqlite3": "^11.7.0",
"drizzle-orm": "^0.45.2",
"fastify": "^5.8.5",
"pino": "^9.6.0",
"pino-pretty": "^13.0.0",
"sharp": "^0.34.5",
"zod": "^3.25.0"
"@fastify/cors": "^11.3.0",
"@github/copilot": "^1.0.88",
"@github/copilot-sdk": "^1.0.14",
"better-sqlite3": "^13.0.3",
"drizzle-orm": "^0.45.3",
"fastify": "^5.12.5",
"pino": "^10.3.1",
"pino-pretty": "^13.1.3",
"sharp": "^0.35.4",
"zod": "^4.6.5"
},
"devDependencies": {
"@devmentorai/shared": "workspace:*",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/changelog": "^7.0.0",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/exec": "^7.1.0",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^12.0.6",
"@semantic-release/npm": "^13.1.4",
"@semantic-release/release-notes-generator": "^14.1.0",
"@types/better-sqlite3": "^7.6.12",
"@types/node": "^22.10.5",
"@types/sharp": "^0.32.0",
"conventional-changelog-conventionalcommits": "^9.1.0",
"drizzle-kit": "^0.31.0",
"semantic-release": "^25.0.3",
"@semantic-release/git": "^11.0.1",
"@semantic-release/github": "^12.0.10",
"@semantic-release/npm": "^13.2.0",
"@semantic-release/release-notes-generator": "^14.1.1",
"@types/better-sqlite3": "^9.6.0",
"@types/node": "^22.20.4",
"conventional-changelog-conventionalcommits": "^10.4.0",
"drizzle-kit": "^0.31.11",
"semantic-release": "^25.0.9",
"tsup": "^8.5.1",
"tsx": "^4.19.2",
"typescript": "^5.7.3",
"vitest": "^4.1.4"
"tsx": "^4.23.15",
"typescript": "^6.0.3",
"vitest": "^5.0.2"
}
}
11 changes: 7 additions & 4 deletions apps/backend/src/cli/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,15 @@ function getStatusIcon(status: CheckResult['status']): string {

function checkNodeVersion(): CheckResult {
const version = process.version;
const major = Number.parseInt(version.slice(1).split('.')[0], 10);
const [major, minor] = version
.slice(1)
.split('.')
.map((part) => Number.parseInt(part, 10));

if (major >= 20) {
return { name: 'Node.js', status: 'pass', message: `${version} (>= 20 required)` };
if (major > 22 || (major === 22 && minor >= 12)) {
return { name: 'Node.js', status: 'pass', message: `${version} (>= 22.12 required)` };
}
return { name: 'Node.js', status: 'fail', message: `${version} — Node.js >= 20 is required` };
return { name: 'Node.js', status: 'fail', message: `${version} — Node.js >= 22.12 is required` };
}

function checkDataDirectory(): CheckResult {
Expand Down
4 changes: 2 additions & 2 deletions apps/backend/src/routes/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ export async function chatRoutes(fastify: FastifyInstance) {
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid request body',
details: { errors: error.errors },
details: { errors: error.issues },
},
});
}
Expand Down Expand Up @@ -586,7 +586,7 @@ export async function chatRoutes(fastify: FastifyInstance) {
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid request body',
details: error.errors,
details: error.issues,
},
});
}
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/routes/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export async function imagesRoutes(fastify: FastifyInstance) {
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid upload body',
details: error.errors,
details: error.issues,
},
});
}
Expand Down
4 changes: 2 additions & 2 deletions apps/backend/src/routes/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export async function sessionRoutes(fastify: FastifyInstance) {
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid request body',
details: { errors: error.errors },
details: { errors: error.issues },
},
});
}
Expand Down Expand Up @@ -221,7 +221,7 @@ export async function sessionRoutes(fastify: FastifyInstance) {
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid request body',
details: { errors: error.errors },
details: { errors: error.issues },
},
});
}
Expand Down
64 changes: 14 additions & 50 deletions apps/backend/src/services/copilot.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { SESSION_TYPE_CONFIGS, getAgentConfig } from '@devmentorai/shared';
import type {
Expand All @@ -11,6 +10,7 @@ import type {
} from '@devmentorai/shared';
import {
CopilotClient,
RuntimeConnection,
type Tool as CopilotTool,
type SessionEvent,
approveAll,
Expand Down Expand Up @@ -75,43 +75,7 @@ export class CopilotService {
try {
let cliPath: string | undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Comprobar una conversación real tras migrar el SDK

La verificación descrita cubre el arranque, pero no un intercambio de chat autenticado. Conviene probar envío normal y SSE con Copilot SDK 1.x antes de publicar el backend.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

De acuerdo. No se puede hacer aquí: la máquina de pruebas no tiene sesión de Copilot, así que solo verifiqué arranque, /api/health (copilotConnected: true) y el modo mock. Antes de publicar, el autor tiene que probar con una cuenta autenticada un chat normal, un chat por SSE y una quick action. Dejo el hilo abierto hasta entonces.

try {
const wrapperPkgUrl = import.meta.resolve('@github/copilot/package.json');
const { createRequire } = await import('node:module');
const req = createRequire(fileURLToPath(wrapperPkgUrl));

const platform = process.platform;
const arch = process.arch;
const candidates = [];

if (platform === 'linux') {
try {
const { isNonGlibcLinuxSync } = req('detect-libc');
if (isNonGlibcLinuxSync()) {
candidates.push(`@github/copilot-linuxmusl-${arch}`);
} else {
candidates.push(`@github/copilot-linux-${arch}`);
}
} catch {
candidates.push(`@github/copilot-linuxmusl-${arch}`);
candidates.push(`@github/copilot-linux-${arch}`);
}
} else {
candidates.push(`@github/copilot-${platform}-${arch}`);
}

for (const pkg of candidates) {
try {
const entry = req.resolve(pkg);
cliPath = join(dirname(entry), 'index.js');
break;
} catch {}
}

if (!cliPath) {
const baseEntry = req.resolve('@github/copilot');
cliPath = join(dirname(baseEntry), 'index.js');
}

cliPath = fileURLToPath(import.meta.resolve('@github/copilot/npm-loader.js'));
console.log(`[CopilotService] Dynamically resolved Copilot CLI Path: ${cliPath}`);
} catch (err) {
console.warn(
Expand All @@ -122,7 +86,7 @@ export class CopilotService {

this.client = new CopilotClient({
gitHubToken: process.env.GITHUB_TOKEN || process.env.COPILOT_TOKEN || undefined,
cliPath,
connection: cliPath ? RuntimeConnection.forStdio({ path: cliPath }) : undefined,
});
await this.client.start();
this.initialized = true;
Expand Down Expand Up @@ -379,14 +343,14 @@ export class CopilotService {
}
}

// Fallback: destroy and recreate session (legacy behavior)
// Fallback: disconnect and recreate session (legacy behavior)
if (existing?.session && !this.mockMode) {
try {
await existing.session.abort().catch(() => undefined);
await existing.session.destroy();
await existing.session.disconnect();
} catch (error) {
console.warn(
`[CopilotService] Failed to destroy previous session before model switch: ${sessionId}`,
`[CopilotService] Failed to disconnect previous session before model switch: ${sessionId}`,
error
);
}
Expand Down Expand Up @@ -968,12 +932,12 @@ export class CopilotService {
try {
// Abort any pending requests first
await copilotSession.session.abort().catch(() => {});
// Destroy the session (releases resources but doesn't delete files)
await copilotSession.session.destroy();
console.log(`[CopilotService] Session ${sessionId} destroyed successfully`);
// Disconnect the session (releases resources but doesn't delete files)
await copilotSession.session.disconnect();
console.log(`[CopilotService] Session ${sessionId} disconnected successfully`);
} catch (error) {
console.error(`[CopilotService] Error destroying session ${sessionId}:`, error);
// Continue with cleanup even if destroy fails
console.error(`[CopilotService] Error disconnecting session ${sessionId}:`, error);
// Continue with cleanup even if disconnect fails
}
}

Expand All @@ -997,14 +961,14 @@ export class CopilotService {
}

async shutdown(): Promise<void> {
// Destroy all sessions
// Disconnect all sessions
for (const [sessionId, copilotSession] of this.sessions) {
try {
if (copilotSession.session && !this.mockMode) {
await copilotSession.session.destroy();
await copilotSession.session.disconnect();
}
} catch (error) {
console.error(`[CopilotService] Failed to destroy session ${sessionId}:`, error);
console.error(`[CopilotService] Failed to disconnect session ${sessionId}:`, error);
}
}
this.sessions.clear();
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/tests/services/thumbnail-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
// Test fixtures
// Small 1x1 red pixel PNG encoded as base64
const RED_PIXEL_PNG =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jH/wAAAABJRU5ErkJggg==';
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAADUlEQVQImWP4z8DwHwAFAAH/q842iQAAAABJRU5ErkJggg==';
const PNG_DATA_URL = `data:image/png;base64,${RED_PIXEL_PNG}`;

// Another valid PNG (slightly different red pixel)
Expand Down
49 changes: 26 additions & 23 deletions apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "@devmentorai/extension",
"version": "1.0.0",
"private": true,
"engines": {
"node": ">=22.12.0"
},
"type": "module",
"scripts": {
"dev": "wxt",
Expand All @@ -20,35 +23,35 @@
"dependencies": {
"@devmentorai/shared": "workspace:*",
"clsx": "^2.1.1",
"lucide-react": "^0.468.0",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"lucide-react": "^1.48.0",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-markdown": "^10.1.0",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1",
"tailwind-merge": "^2.6.0"
"tailwind-merge": "^3.7.0"
},
"devDependencies": {
"@semantic-release/changelog": "^6.0.3",
"@tailwindcss/postcss": "^4.3.3",
"@semantic-release/changelog": "^7.0.0",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/exec": "^7.1.0",
"@semantic-release/git": "^10.0.1",
"@semantic-release/github": "^12.0.6",
"@semantic-release/release-notes-generator": "^14.1.0",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.2",
"@types/chrome": "^0.0.287",
"@types/react": "^19.2.1",
"@types/react-dom": "^19.2.1",
"@wxt-dev/module-react": "^1.1.2",
"autoprefixer": "^10.4.20",
"conventional-changelog-conventionalcommits": "^9.1.0",
"jsdom": "^28.0.0",
"postcss": "^8.4.49",
"semantic-release": "^25.0.3",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.3",
"vitest": "^4.1.4",
"wxt": "^0.20.20"
"@semantic-release/git": "^11.0.1",
"@semantic-release/github": "^12.0.10",
"@semantic-release/release-notes-generator": "^14.1.1",
"@testing-library/dom": "^10.4.2",
"@testing-library/react": "^16.3.3",
"@types/chrome": "^0.3.0",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@wxt-dev/module-react": "^1.2.2",
"conventional-changelog-conventionalcommits": "^10.4.0",
"jsdom": "^30.1.1",
"postcss": "^8.5.28",
"semantic-release": "^25.0.9",
"tailwindcss": "^4.3.3",
"typescript": "^6.0.3",
"vitest": "^5.0.2",
"wxt": "^0.21.4"
}
}
3 changes: 1 addition & 2 deletions apps/extension/postcss.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
'@tailwindcss/postcss': {},
},
};
Loading
Loading