feat: proje secimi ve otomatik ekip akisini ekle
This commit is contained in:
22
server/bootstrapPrompt.js
vendored
22
server/bootstrapPrompt.js
vendored
@@ -1,8 +1,16 @@
|
||||
export function buildBootstrapPrompt() {
|
||||
export function buildBootstrapPrompt(projectPath = null) {
|
||||
const projectContext = projectPath
|
||||
? `Aktif proje kok dizini: ${projectPath}. Bundan sonra tum analiz, yorum, gorev parcasi ve dosya referanslarini yalnizca bu proje uzerinden yap. Bu proje disina tasma.`
|
||||
: "Aktif proje henuz secilmedi. Kullanici proje secene kadar dosya baglaminda varsayim yapma.";
|
||||
|
||||
return [
|
||||
'Team agent modunu aktif et.',
|
||||
'Team icerisindeki roller -> Team Lead: "Mazlum" (erkek), Frontend Developer: "Berkecan" (erkek), Backend Developer: "Simsar" (erkek), UI/UX Designer: "Aybuke" (disi), iOS Developer: "Ive" (erkek) ve takim uyelerine kahveleri getirmesi icin Trainee: "Irgatov" (erkek).',
|
||||
"Bu takim yapisini aynen koru.",
|
||||
projectContext,
|
||||
"Irgatov teknik ekip uyesi degildir; yalnizca kahve, icecek, ofis ici lojistik ve basit yardim isleriyle ilgilenir.",
|
||||
"Irgatov kod mimarisi, dosya yapisi, planlama, bug analizi, teknoloji secimi, UI/UX karari, backend karari veya iOS karari vermez.",
|
||||
"Teknik gorev dagitimi yaparken Irgatov'a teknik is yazma. Irgatov sadece kahve ve lojistik destek icin konussun.",
|
||||
"Takim ici tum mesajlarda konusan kisi zorunlu olarak ad etiketiyle baslasin.",
|
||||
"Her cevap yalnizca su formatla baslasin: `Mazlum:` veya `Berkecan:` veya `Simsar:` veya `Aybuke:` veya `Ive:` veya `Irgatov:`.",
|
||||
"Etiketsiz cevap verme. `Ben`, `Team Lead`, `Frontend Developer`, `UI/UX Designer`, `biz`, `takim olarak` gibi baslangiclar kullanma.",
|
||||
@@ -13,3 +21,15 @@ export function buildBootstrapPrompt() {
|
||||
"Ilk cevap olarak yalnizca takimin hazir oldugunu ve rollerin aktiflestigini bildir. Bu ilk cevap da `Mazlum:` ile baslasin."
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
export function buildProjectSelectionPrompt(projectPath) {
|
||||
return [
|
||||
"Proje baglami guncellendi.",
|
||||
`Yeni aktif proje kok dizini: ${projectPath}.`,
|
||||
"Bu andan itibaren tum yorum, plan, gorev ve kod onerilerini yalnizca bu proje uzerinden yap.",
|
||||
"Bu proje disinda dosya, klasor veya kod tabani varsayimi yapma.",
|
||||
"Irgatov bu proje baglaminda da sadece kahve ve lojistik destek verir; teknik gorev almaz.",
|
||||
"Kullanici yeni bir proje secene kadar bu proje varsayilan tek calisma alanidir.",
|
||||
"Bu bildirimi tekrar etme; sadece yeni proje baglamina gore calismaya devam et."
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { Server } from "socket.io";
|
||||
import { getPublicRuntimeConfig, getRuntimeConfig } from "./config.js";
|
||||
import { selectProjectFolder } from "./projectPicker.js";
|
||||
import { SessionManager } from "./sessionManager.js";
|
||||
import { registerSocketHandlers } from "./socketHandlers.js";
|
||||
|
||||
@@ -21,6 +22,8 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const webDistPath = path.resolve(__dirname, "../web/dist");
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.get("/health", (req, res) => {
|
||||
res.json({
|
||||
ok: true,
|
||||
@@ -37,6 +40,22 @@ app.get("/api/session/state", (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/api/project/select", async (req, res) => {
|
||||
try {
|
||||
const selectedPath = req.body?.projectPath ? String(req.body.projectPath) : await selectProjectFolder();
|
||||
await sessionManager.setProjectPath(selectedPath);
|
||||
res.json({
|
||||
ok: true,
|
||||
projectPath: sessionManager.getState().currentProjectPath
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
ok: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (config.nodeEnv === "production") {
|
||||
app.use(express.static(webDistPath));
|
||||
app.get("*", (req, res) => {
|
||||
|
||||
10
server/projectPicker.js
Normal file
10
server/projectPicker.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
export async function selectProjectFolder() {
|
||||
const script = 'POSIX path of (choose folder with prompt "Select project folder")';
|
||||
const { stdout } = await execFileAsync("/usr/bin/osascript", ["-e", script]);
|
||||
return String(stdout ?? "").trim();
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import stripAnsi from "strip-ansi";
|
||||
import { buildBootstrapPrompt } from "./bootstrapPrompt.js";
|
||||
import { buildProjectSelectionPrompt } from "./bootstrapPrompt.js";
|
||||
import { LogService } from "./logService.js";
|
||||
import { PtyService } from "./ptyService.js";
|
||||
import { getClaudeEnv, getPublicRuntimeConfig } from "./config.js";
|
||||
@@ -26,7 +29,14 @@ function buildRoutedPrompt(prompt, lastDirectedMember = null) {
|
||||
if (!targetMember) {
|
||||
return {
|
||||
targetMember: null,
|
||||
routedPrompt: `Not: Bu genel mesajdir. Once Mazlum cevap versin ve konusan herkes ad etiketi kullansin. Kullanici mesaji: ${prompt}`
|
||||
routedPrompt: `Not: Bu genel mesajdir. Once Mazlum cevap versin ve konusan herkes ad etiketi kullansin. Irgatov teknik gorev almaz; sadece kahve ve lojistik destek verir. Kullanici mesaji: ${prompt}`
|
||||
};
|
||||
}
|
||||
|
||||
if (targetMember.name === "Irgatov") {
|
||||
return {
|
||||
targetMember,
|
||||
routedPrompt: `Not: Bu mesaj Irgatov icindir. Irgatov sadece kahve, icecek, servis ve basit lojistik destek konularinda cevap versin. Teknik plan, kod, mimari veya dosya yapisi onermesin. Cevap \`Irgatov:\` ile baslasin. Kullanici mesaji: ${prompt}`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,11 +54,13 @@ export class SessionManager {
|
||||
this.ptyService = null;
|
||||
this.chatOutput = "";
|
||||
this.lastDirectedMember = null;
|
||||
this.currentProjectPath = null;
|
||||
this.state = {
|
||||
status: "idle",
|
||||
startedAt: null,
|
||||
teamActivated: false,
|
||||
lastError: null,
|
||||
currentProjectPath: null,
|
||||
runtime: getPublicRuntimeConfig(config)
|
||||
};
|
||||
}
|
||||
@@ -56,6 +68,7 @@ export class SessionManager {
|
||||
getState() {
|
||||
return {
|
||||
...this.state,
|
||||
currentProjectPath: this.currentProjectPath,
|
||||
runtime: getPublicRuntimeConfig(this.config)
|
||||
};
|
||||
}
|
||||
@@ -94,6 +107,39 @@ export class SessionManager {
|
||||
this.emitState();
|
||||
}
|
||||
|
||||
getActiveWorkspaceDir() {
|
||||
return this.currentProjectPath ?? this.config.workspaceDir;
|
||||
}
|
||||
|
||||
async setProjectPath(projectPath) {
|
||||
const resolved = projectPath ? path.resolve(projectPath) : null;
|
||||
const wasRunning = this.ptyService?.isRunning() ?? false;
|
||||
|
||||
if (resolved && (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory())) {
|
||||
throw new Error(`Selected project path is invalid: ${resolved}`);
|
||||
}
|
||||
|
||||
if (wasRunning) {
|
||||
await this.stop();
|
||||
}
|
||||
|
||||
this.currentProjectPath = resolved;
|
||||
this.lastDirectedMember = null;
|
||||
this.setState({ currentProjectPath: resolved });
|
||||
this.addLog("system", `Current project set to ${resolved ?? "None"}`);
|
||||
|
||||
if (wasRunning) {
|
||||
await this.start();
|
||||
|
||||
if (resolved) {
|
||||
await this.activateTeam();
|
||||
} else {
|
||||
const prompt = buildProjectSelectionPrompt(this.getActiveWorkspaceDir());
|
||||
await this.sendRawPrompt(prompt, { label: `[project] Switched active project to ${this.getActiveWorkspaceDir()}` });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (this.ptyService?.isRunning()) {
|
||||
throw new Error("Session is already running");
|
||||
@@ -105,7 +151,7 @@ export class SessionManager {
|
||||
this.io.emit("chat:reset");
|
||||
|
||||
this.ptyService = new PtyService({
|
||||
cwd: this.config.workspaceDir,
|
||||
cwd: this.getActiveWorkspaceDir(),
|
||||
env: getClaudeEnv(this.config)
|
||||
});
|
||||
|
||||
@@ -116,7 +162,7 @@ export class SessionManager {
|
||||
lastError: null
|
||||
});
|
||||
|
||||
this.addLog("lifecycle", `Starting Claude session in ${this.config.workspaceDir}`);
|
||||
this.addLog("lifecycle", `Starting Claude session in ${this.getActiveWorkspaceDir()}`);
|
||||
|
||||
try {
|
||||
await this.ptyService.start({
|
||||
@@ -174,7 +220,7 @@ export class SessionManager {
|
||||
}
|
||||
|
||||
async activateTeam() {
|
||||
const prompt = buildBootstrapPrompt();
|
||||
const prompt = buildBootstrapPrompt(this.currentProjectPath);
|
||||
this.lastDirectedMember = null;
|
||||
await this.sendRawPrompt(prompt, { label: "[bootstrap] Team activation prompt sent" });
|
||||
this.setState({ teamActivated: true });
|
||||
|
||||
@@ -52,5 +52,15 @@ export function registerSocketHandlers(io, sessionManager) {
|
||||
sessionManager.clearLogs();
|
||||
callback?.({ ok: true });
|
||||
});
|
||||
|
||||
socket.on("project:select", async ({ projectPath }, callback) => {
|
||||
try {
|
||||
await sessionManager.setProjectPath(projectPath);
|
||||
callback?.({ ok: true, projectPath: sessionManager.getState().currentProjectPath });
|
||||
} catch (error) {
|
||||
socket.emit("session:error", { message: error.message });
|
||||
callback?.({ ok: false, error: error.message });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user