episoda 0.2.21 → 0.2.23
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/daemon/daemon-process.js +403 -29
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/hooks/post-checkout +3 -2
- package/dist/hooks/pre-commit +6 -5
- package/dist/index.js +459 -568
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -537,6 +537,24 @@ var require_git_executor = __commonJS({
|
|
|
537
537
|
* Execute status command
|
|
538
538
|
*/
|
|
539
539
|
async executeStatus(cwd, options) {
|
|
540
|
+
try {
|
|
541
|
+
const isBareResult = await execAsync2("git rev-parse --is-bare-repository", { cwd, timeout: 5e3 });
|
|
542
|
+
if (isBareResult.stdout.trim() === "true") {
|
|
543
|
+
const headResult = await execAsync2("git symbolic-ref --short HEAD", { cwd, timeout: 5e3 });
|
|
544
|
+
const branchName = headResult.stdout.trim();
|
|
545
|
+
return {
|
|
546
|
+
success: true,
|
|
547
|
+
output: `## ${branchName}`,
|
|
548
|
+
details: {
|
|
549
|
+
uncommittedFiles: [],
|
|
550
|
+
// No working tree in bare repo
|
|
551
|
+
branchName,
|
|
552
|
+
currentBranch: branchName
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
} catch {
|
|
557
|
+
}
|
|
540
558
|
const result = await this.runGitCommand(["status", "--porcelain", "-b"], cwd, options);
|
|
541
559
|
if (result.success && result.output) {
|
|
542
560
|
const statusInfo = (0, git_parser_1.parseGitStatus)(result.output);
|
|
@@ -1868,7 +1886,6 @@ var require_git_executor = __commonJS({
|
|
|
1868
1886
|
const fs14 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1869
1887
|
const path15 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1870
1888
|
let currentPath = cwd;
|
|
1871
|
-
let worktreeMode = false;
|
|
1872
1889
|
let projectPath = cwd;
|
|
1873
1890
|
let bareRepoPath;
|
|
1874
1891
|
for (let i = 0; i < 10; i++) {
|
|
@@ -1877,7 +1894,6 @@ var require_git_executor = __commonJS({
|
|
|
1877
1894
|
try {
|
|
1878
1895
|
await fs14.access(bareDir);
|
|
1879
1896
|
await fs14.access(episodaDir);
|
|
1880
|
-
worktreeMode = true;
|
|
1881
1897
|
projectPath = currentPath;
|
|
1882
1898
|
bareRepoPath = bareDir;
|
|
1883
1899
|
break;
|
|
@@ -1891,9 +1907,8 @@ var require_git_executor = __commonJS({
|
|
|
1891
1907
|
}
|
|
1892
1908
|
return {
|
|
1893
1909
|
success: true,
|
|
1894
|
-
output:
|
|
1910
|
+
output: bareRepoPath ? "Episoda project" : "Git repository",
|
|
1895
1911
|
details: {
|
|
1896
|
-
worktreeMode,
|
|
1897
1912
|
projectPath,
|
|
1898
1913
|
bareRepoPath
|
|
1899
1914
|
}
|
|
@@ -2681,7 +2696,7 @@ var require_package = __commonJS({
|
|
|
2681
2696
|
"package.json"(exports2, module2) {
|
|
2682
2697
|
module2.exports = {
|
|
2683
2698
|
name: "episoda",
|
|
2684
|
-
version: "0.2.
|
|
2699
|
+
version: "0.2.22",
|
|
2685
2700
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2686
2701
|
main: "dist/index.js",
|
|
2687
2702
|
types: "dist/index.d.ts",
|
|
@@ -2883,9 +2898,6 @@ function addProject(projectId, projectPath, options) {
|
|
|
2883
2898
|
if (existingByPath) {
|
|
2884
2899
|
existingByPath.id = projectId;
|
|
2885
2900
|
existingByPath.last_active = now;
|
|
2886
|
-
if (options?.worktreeMode !== void 0) {
|
|
2887
|
-
existingByPath.worktreeMode = options.worktreeMode;
|
|
2888
|
-
}
|
|
2889
2901
|
if (options?.bareRepoPath) {
|
|
2890
2902
|
existingByPath.bareRepoPath = options.bareRepoPath;
|
|
2891
2903
|
}
|
|
@@ -2905,8 +2917,7 @@ function addProject(projectId, projectPath, options) {
|
|
|
2905
2917
|
name: projectName,
|
|
2906
2918
|
added_at: now,
|
|
2907
2919
|
last_active: now,
|
|
2908
|
-
//
|
|
2909
|
-
worktreeMode: options?.worktreeMode,
|
|
2920
|
+
// EP971: Bare repo path for worktree architecture
|
|
2910
2921
|
bareRepoPath: options?.bareRepoPath
|
|
2911
2922
|
};
|
|
2912
2923
|
data.projects.push(newProject);
|
|
@@ -5141,12 +5152,15 @@ function calculateRestartDelay(restartCount) {
|
|
|
5141
5152
|
const delay = INITIAL_RESTART_DELAY_MS * Math.pow(2, restartCount);
|
|
5142
5153
|
return Math.min(delay, MAX_RESTART_DELAY_MS);
|
|
5143
5154
|
}
|
|
5144
|
-
function spawnDevServerProcess(projectPath, port, moduleUid, logPath) {
|
|
5155
|
+
function spawnDevServerProcess(projectPath, port, moduleUid, logPath, customCommand) {
|
|
5145
5156
|
rotateLogIfNeeded(logPath);
|
|
5146
5157
|
const nodeOptions = process.env.NODE_OPTIONS || "";
|
|
5147
5158
|
const memoryFlag = `--max-old-space-size=${NODE_MEMORY_LIMIT_MB}`;
|
|
5148
5159
|
const enhancedNodeOptions = nodeOptions.includes("max-old-space-size") ? nodeOptions : `${nodeOptions} ${memoryFlag}`.trim();
|
|
5149
|
-
const
|
|
5160
|
+
const command = customCommand || "npm run dev";
|
|
5161
|
+
const [cmd, ...args] = command.split(" ");
|
|
5162
|
+
console.log(`[DevServer] EP959: Starting with command: ${command}`);
|
|
5163
|
+
const devProcess = (0, import_child_process9.spawn)(cmd, args, {
|
|
5150
5164
|
cwd: projectPath,
|
|
5151
5165
|
env: {
|
|
5152
5166
|
...process.env,
|
|
@@ -5154,7 +5168,9 @@ function spawnDevServerProcess(projectPath, port, moduleUid, logPath) {
|
|
|
5154
5168
|
NODE_OPTIONS: enhancedNodeOptions
|
|
5155
5169
|
},
|
|
5156
5170
|
stdio: ["ignore", "pipe", "pipe"],
|
|
5157
|
-
detached: false
|
|
5171
|
+
detached: false,
|
|
5172
|
+
shell: true
|
|
5173
|
+
// EP959: Use shell to handle complex commands
|
|
5158
5174
|
});
|
|
5159
5175
|
devProcess.stdout?.on("data", (data) => {
|
|
5160
5176
|
const line = data.toString().trim();
|
|
@@ -5200,7 +5216,7 @@ async function handleProcessExit(moduleUid, code, signal) {
|
|
|
5200
5216
|
return;
|
|
5201
5217
|
}
|
|
5202
5218
|
const logPath = serverInfo.logFile || getLogFilePath(moduleUid);
|
|
5203
|
-
const newProcess = spawnDevServerProcess(serverInfo.projectPath, serverInfo.port, moduleUid, logPath);
|
|
5219
|
+
const newProcess = spawnDevServerProcess(serverInfo.projectPath, serverInfo.port, moduleUid, logPath, serverInfo.customCommand);
|
|
5204
5220
|
const updatedInfo = {
|
|
5205
5221
|
...serverInfo,
|
|
5206
5222
|
process: newProcess,
|
|
@@ -5227,6 +5243,7 @@ async function handleProcessExit(moduleUid, code, signal) {
|
|
|
5227
5243
|
}
|
|
5228
5244
|
async function startDevServer(projectPath, port = 3e3, moduleUid = "default", options = {}) {
|
|
5229
5245
|
const autoRestart = options.autoRestart ?? true;
|
|
5246
|
+
const customCommand = options.customCommand;
|
|
5230
5247
|
if (await isPortInUse(port)) {
|
|
5231
5248
|
console.log(`[DevServer] Server already running on port ${port}`);
|
|
5232
5249
|
return { success: true, alreadyRunning: true };
|
|
@@ -5241,7 +5258,7 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
|
|
|
5241
5258
|
console.log(`[DevServer] EP932: Starting dev server for ${moduleUid} on port ${port} (auto-restart: ${autoRestart})...`);
|
|
5242
5259
|
try {
|
|
5243
5260
|
const logPath = getLogFilePath(moduleUid);
|
|
5244
|
-
const devProcess = spawnDevServerProcess(projectPath, port, moduleUid, logPath);
|
|
5261
|
+
const devProcess = spawnDevServerProcess(projectPath, port, moduleUid, logPath, customCommand);
|
|
5245
5262
|
const serverInfo = {
|
|
5246
5263
|
process: devProcess,
|
|
5247
5264
|
moduleUid,
|
|
@@ -5251,7 +5268,9 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
|
|
|
5251
5268
|
restartCount: 0,
|
|
5252
5269
|
lastRestartAt: null,
|
|
5253
5270
|
autoRestartEnabled: autoRestart,
|
|
5254
|
-
logFile: logPath
|
|
5271
|
+
logFile: logPath,
|
|
5272
|
+
customCommand
|
|
5273
|
+
// EP959-m2: Store for restarts
|
|
5255
5274
|
};
|
|
5256
5275
|
activeServers.set(moduleUid, serverInfo);
|
|
5257
5276
|
writeToLog(logPath, `Starting dev server on port ${port}`, false);
|
|
@@ -5329,11 +5348,11 @@ function getDevServerStatus() {
|
|
|
5329
5348
|
logFile: info.logFile
|
|
5330
5349
|
}));
|
|
5331
5350
|
}
|
|
5332
|
-
async function ensureDevServer(projectPath, port = 3e3, moduleUid = "default") {
|
|
5351
|
+
async function ensureDevServer(projectPath, port = 3e3, moduleUid = "default", customCommand) {
|
|
5333
5352
|
if (await isPortInUse(port)) {
|
|
5334
5353
|
return { success: true };
|
|
5335
5354
|
}
|
|
5336
|
-
return startDevServer(projectPath, port, moduleUid, { autoRestart: true });
|
|
5355
|
+
return startDevServer(projectPath, port, moduleUid, { autoRestart: true, customCommand });
|
|
5337
5356
|
}
|
|
5338
5357
|
|
|
5339
5358
|
// src/utils/port-detect.ts
|
|
@@ -5439,7 +5458,8 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
5439
5458
|
}
|
|
5440
5459
|
/**
|
|
5441
5460
|
* Initialize worktree manager from existing project root
|
|
5442
|
-
*
|
|
5461
|
+
* EP971: All projects use worktree architecture
|
|
5462
|
+
* @returns true if valid project, false otherwise
|
|
5443
5463
|
*/
|
|
5444
5464
|
async initialize() {
|
|
5445
5465
|
if (!fs11.existsSync(this.bareRepoPath)) {
|
|
@@ -5450,7 +5470,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
5450
5470
|
}
|
|
5451
5471
|
try {
|
|
5452
5472
|
const config = this.readConfig();
|
|
5453
|
-
return config
|
|
5473
|
+
return config !== null;
|
|
5454
5474
|
} catch {
|
|
5455
5475
|
return false;
|
|
5456
5476
|
}
|
|
@@ -5475,7 +5495,6 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
5475
5495
|
workspaceSlug,
|
|
5476
5496
|
projectSlug,
|
|
5477
5497
|
bareRepoPath: manager.bareRepoPath,
|
|
5478
|
-
worktreeMode: true,
|
|
5479
5498
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5480
5499
|
worktrees: []
|
|
5481
5500
|
};
|
|
@@ -5831,6 +5850,148 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
5831
5850
|
this.releaseLock();
|
|
5832
5851
|
}
|
|
5833
5852
|
}
|
|
5853
|
+
// EP959-11: Worktree setup methods
|
|
5854
|
+
/**
|
|
5855
|
+
* Update the setup status of a worktree
|
|
5856
|
+
*/
|
|
5857
|
+
async updateWorktreeStatus(moduleUid, status, error) {
|
|
5858
|
+
return this.updateConfigSafe((config) => {
|
|
5859
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
5860
|
+
if (worktree) {
|
|
5861
|
+
worktree.setupStatus = status;
|
|
5862
|
+
if (status === "running") {
|
|
5863
|
+
worktree.setupStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5864
|
+
} else if (status === "ready" || status === "error") {
|
|
5865
|
+
worktree.setupCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5866
|
+
}
|
|
5867
|
+
if (error) {
|
|
5868
|
+
worktree.setupError = error;
|
|
5869
|
+
}
|
|
5870
|
+
}
|
|
5871
|
+
return config;
|
|
5872
|
+
});
|
|
5873
|
+
}
|
|
5874
|
+
/**
|
|
5875
|
+
* Get worktree info including setup status
|
|
5876
|
+
*/
|
|
5877
|
+
getWorktreeStatus(moduleUid) {
|
|
5878
|
+
const config = this.readConfig();
|
|
5879
|
+
if (!config) return null;
|
|
5880
|
+
return config.worktrees.find((w) => w.moduleUid === moduleUid) || null;
|
|
5881
|
+
}
|
|
5882
|
+
/**
|
|
5883
|
+
* Copy files from main worktree to module worktree
|
|
5884
|
+
*
|
|
5885
|
+
* @deprecated EP964: This function is deprecated. Use worktree_env_vars for
|
|
5886
|
+
* environment variables or worktree_setup_script for other file operations.
|
|
5887
|
+
* This function now returns success (no-op) when main/ worktree doesn't exist.
|
|
5888
|
+
*/
|
|
5889
|
+
async copyFilesFromMain(moduleUid, files) {
|
|
5890
|
+
console.warn(`[WorktreeManager] EP964: copyFilesFromMain is DEPRECATED.`);
|
|
5891
|
+
console.warn(`[WorktreeManager] EP964: Use worktree_env_vars for .env or worktree_setup_script for other files.`);
|
|
5892
|
+
const config = this.readConfig();
|
|
5893
|
+
if (!config) {
|
|
5894
|
+
return { success: false, error: "Config not found" };
|
|
5895
|
+
}
|
|
5896
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
5897
|
+
if (!worktree) {
|
|
5898
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
5899
|
+
}
|
|
5900
|
+
const mainWorktree = config.worktrees.find((w) => w.moduleUid === "main");
|
|
5901
|
+
if (!mainWorktree) {
|
|
5902
|
+
console.warn(`[WorktreeManager] EP964: No 'main' worktree - skipping file copy (this is expected).`);
|
|
5903
|
+
return { success: true };
|
|
5904
|
+
}
|
|
5905
|
+
try {
|
|
5906
|
+
for (const file of files) {
|
|
5907
|
+
const srcPath = path12.join(mainWorktree.worktreePath, file);
|
|
5908
|
+
const destPath = path12.join(worktree.worktreePath, file);
|
|
5909
|
+
if (fs11.existsSync(srcPath)) {
|
|
5910
|
+
const destDir = path12.dirname(destPath);
|
|
5911
|
+
if (!fs11.existsSync(destDir)) {
|
|
5912
|
+
fs11.mkdirSync(destDir, { recursive: true });
|
|
5913
|
+
}
|
|
5914
|
+
fs11.copyFileSync(srcPath, destPath);
|
|
5915
|
+
console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`);
|
|
5916
|
+
} else {
|
|
5917
|
+
console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`);
|
|
5918
|
+
}
|
|
5919
|
+
}
|
|
5920
|
+
return { success: true };
|
|
5921
|
+
} catch (error) {
|
|
5922
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
/**
|
|
5926
|
+
* Run worktree setup script
|
|
5927
|
+
* EP959-M1: Enhanced logging with working directory, timeout info, and script preview
|
|
5928
|
+
*/
|
|
5929
|
+
async runSetupScript(moduleUid, script) {
|
|
5930
|
+
const config = this.readConfig();
|
|
5931
|
+
if (!config) {
|
|
5932
|
+
return { success: false, error: "Config not found" };
|
|
5933
|
+
}
|
|
5934
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
5935
|
+
if (!worktree) {
|
|
5936
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
5937
|
+
}
|
|
5938
|
+
const TIMEOUT_MINUTES = 10;
|
|
5939
|
+
const scriptPreview = script.length > 200 ? script.slice(0, 200) + "..." : script;
|
|
5940
|
+
console.log(`[WorktreeManager] EP959: Running setup script for ${moduleUid}`);
|
|
5941
|
+
console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`);
|
|
5942
|
+
console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
|
|
5943
|
+
console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
|
|
5944
|
+
try {
|
|
5945
|
+
const { execSync: execSync6 } = require("child_process");
|
|
5946
|
+
execSync6(script, {
|
|
5947
|
+
cwd: worktree.worktreePath,
|
|
5948
|
+
stdio: "inherit",
|
|
5949
|
+
timeout: TIMEOUT_MINUTES * 60 * 1e3,
|
|
5950
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
5951
|
+
});
|
|
5952
|
+
console.log(`[WorktreeManager] EP959: Setup script completed successfully for ${moduleUid}`);
|
|
5953
|
+
return { success: true };
|
|
5954
|
+
} catch (error) {
|
|
5955
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5956
|
+
console.error(`[WorktreeManager] EP959: Setup script failed for ${moduleUid}:`, errorMessage);
|
|
5957
|
+
return { success: false, error: errorMessage };
|
|
5958
|
+
}
|
|
5959
|
+
}
|
|
5960
|
+
/**
|
|
5961
|
+
* Run worktree cleanup script before removal
|
|
5962
|
+
* EP959-m3: Execute cleanup script when worktree is being released
|
|
5963
|
+
*/
|
|
5964
|
+
async runCleanupScript(moduleUid, script) {
|
|
5965
|
+
const config = this.readConfig();
|
|
5966
|
+
if (!config) {
|
|
5967
|
+
return { success: false, error: "Config not found" };
|
|
5968
|
+
}
|
|
5969
|
+
const worktree = config.worktrees.find((w) => w.moduleUid === moduleUid);
|
|
5970
|
+
if (!worktree) {
|
|
5971
|
+
return { success: false, error: `Worktree not found for ${moduleUid}` };
|
|
5972
|
+
}
|
|
5973
|
+
const TIMEOUT_MINUTES = 5;
|
|
5974
|
+
const scriptPreview = script.length > 200 ? script.slice(0, 200) + "..." : script;
|
|
5975
|
+
console.log(`[WorktreeManager] EP959: Running cleanup script for ${moduleUid}`);
|
|
5976
|
+
console.log(`[WorktreeManager] EP959: Working directory: ${worktree.worktreePath}`);
|
|
5977
|
+
console.log(`[WorktreeManager] EP959: Timeout: ${TIMEOUT_MINUTES} minutes`);
|
|
5978
|
+
console.log(`[WorktreeManager] EP959: Script: ${scriptPreview}`);
|
|
5979
|
+
try {
|
|
5980
|
+
const { execSync: execSync6 } = require("child_process");
|
|
5981
|
+
execSync6(script, {
|
|
5982
|
+
cwd: worktree.worktreePath,
|
|
5983
|
+
stdio: "inherit",
|
|
5984
|
+
timeout: TIMEOUT_MINUTES * 60 * 1e3,
|
|
5985
|
+
env: { ...process.env, NODE_ENV: "development" }
|
|
5986
|
+
});
|
|
5987
|
+
console.log(`[WorktreeManager] EP959: Cleanup script completed successfully for ${moduleUid}`);
|
|
5988
|
+
return { success: true };
|
|
5989
|
+
} catch (error) {
|
|
5990
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5991
|
+
console.warn(`[WorktreeManager] EP959: Cleanup script failed for ${moduleUid} (non-blocking):`, errorMessage);
|
|
5992
|
+
return { success: false, error: errorMessage };
|
|
5993
|
+
}
|
|
5994
|
+
}
|
|
5834
5995
|
};
|
|
5835
5996
|
function getEpisodaRoot() {
|
|
5836
5997
|
return process.env.EPISODA_ROOT || path12.join(require("os").homedir(), "episoda");
|
|
@@ -5887,6 +6048,17 @@ async function getWorktreeInfoForModule(moduleUid) {
|
|
|
5887
6048
|
}
|
|
5888
6049
|
return getWorktreeInfo(moduleUid, config.workspace_slug, config.project_slug);
|
|
5889
6050
|
}
|
|
6051
|
+
async function getProjectRootPath() {
|
|
6052
|
+
const config = await (0, import_core9.loadConfig)();
|
|
6053
|
+
if (!config?.workspace_slug || !config?.project_slug) {
|
|
6054
|
+
return null;
|
|
6055
|
+
}
|
|
6056
|
+
return path13.join(
|
|
6057
|
+
getEpisodaRoot2(),
|
|
6058
|
+
config.workspace_slug,
|
|
6059
|
+
config.project_slug
|
|
6060
|
+
);
|
|
6061
|
+
}
|
|
5890
6062
|
|
|
5891
6063
|
// src/utils/port-allocator.ts
|
|
5892
6064
|
var PORT_RANGE_START = 3100;
|
|
@@ -6408,9 +6580,12 @@ var Daemon = class _Daemon {
|
|
|
6408
6580
|
client.updateActivity();
|
|
6409
6581
|
try {
|
|
6410
6582
|
const gitCmd = message.command;
|
|
6411
|
-
const
|
|
6583
|
+
const bareRepoPath = path14.join(projectPath, ".bare");
|
|
6584
|
+
const cwd = gitCmd.worktreePath || bareRepoPath;
|
|
6412
6585
|
if (gitCmd.worktreePath) {
|
|
6413
|
-
console.log(`[Daemon]
|
|
6586
|
+
console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
|
|
6587
|
+
} else {
|
|
6588
|
+
console.log(`[Daemon] Running git command in bare repo: ${bareRepoPath}`);
|
|
6414
6589
|
}
|
|
6415
6590
|
const result = await gitExecutor.execute(gitCmd, {
|
|
6416
6591
|
cwd
|
|
@@ -6694,11 +6869,19 @@ var Daemon = class _Daemon {
|
|
|
6694
6869
|
let result;
|
|
6695
6870
|
if (cmd.action === "start") {
|
|
6696
6871
|
const callbacks = createStreamingCallbacks(cmd.sessionId, message.id);
|
|
6872
|
+
let agentWorkingDir = projectPath;
|
|
6873
|
+
if (cmd.moduleUid) {
|
|
6874
|
+
const worktreeInfo = await getWorktreeInfoForModule(cmd.moduleUid);
|
|
6875
|
+
if (worktreeInfo?.exists) {
|
|
6876
|
+
agentWorkingDir = worktreeInfo.path;
|
|
6877
|
+
console.log(`[Daemon] EP959: Agent for ${cmd.moduleUid} in worktree: ${agentWorkingDir}`);
|
|
6878
|
+
}
|
|
6879
|
+
}
|
|
6697
6880
|
const startResult = await agentManager.startSession({
|
|
6698
6881
|
sessionId: cmd.sessionId,
|
|
6699
6882
|
moduleId: cmd.moduleId,
|
|
6700
6883
|
moduleUid: cmd.moduleUid,
|
|
6701
|
-
projectPath,
|
|
6884
|
+
projectPath: agentWorkingDir,
|
|
6702
6885
|
message: cmd.message,
|
|
6703
6886
|
credentials: cmd.credentials,
|
|
6704
6887
|
systemPrompt: cmd.systemPrompt,
|
|
@@ -6799,6 +6982,9 @@ var Daemon = class _Daemon {
|
|
|
6799
6982
|
this.flyMachineId = authMessage.flyMachineId;
|
|
6800
6983
|
console.log(`[Daemon] Fly Machine ID: ${this.flyMachineId}`);
|
|
6801
6984
|
}
|
|
6985
|
+
this.syncProjectSettings(projectId).catch((err) => {
|
|
6986
|
+
console.warn("[Daemon] EP964: Settings sync failed:", err.message);
|
|
6987
|
+
});
|
|
6802
6988
|
this.autoStartTunnelsForProject(projectPath, projectId).catch((error) => {
|
|
6803
6989
|
console.error(`[Daemon] EP819: Failed to auto-start tunnels:`, error);
|
|
6804
6990
|
});
|
|
@@ -6837,24 +7023,72 @@ var Daemon = class _Daemon {
|
|
|
6837
7023
|
}
|
|
6838
7024
|
console.log(`[Daemon] EP956: Starting tunnel for ${moduleUid} (${previousState} \u2192 ${state})`);
|
|
6839
7025
|
try {
|
|
6840
|
-
|
|
7026
|
+
let worktree = await getWorktreeInfoForModule(moduleUid);
|
|
6841
7027
|
if (!worktree) {
|
|
6842
7028
|
console.error(`[Daemon] EP956: Cannot resolve worktree path for ${moduleUid} (missing config slugs)`);
|
|
6843
7029
|
return;
|
|
6844
7030
|
}
|
|
6845
|
-
if (!worktree.exists) {
|
|
7031
|
+
if (!worktree.exists && startingWork) {
|
|
7032
|
+
console.log(`[Daemon] EP959: Creating worktree for ${moduleUid} at ${worktree.path}`);
|
|
7033
|
+
const projectRoot = await getProjectRootPath();
|
|
7034
|
+
if (!projectRoot) {
|
|
7035
|
+
console.error(`[Daemon] EP959: Cannot determine project root for worktree creation`);
|
|
7036
|
+
return;
|
|
7037
|
+
}
|
|
7038
|
+
const worktreeManager = new WorktreeManager(projectRoot);
|
|
7039
|
+
const initialized = await worktreeManager.initialize();
|
|
7040
|
+
if (!initialized) {
|
|
7041
|
+
console.error(`[Daemon] EP959: Failed to initialize WorktreeManager at ${projectRoot}`);
|
|
7042
|
+
return;
|
|
7043
|
+
}
|
|
7044
|
+
const moduleBranchName = branchName || moduleUid;
|
|
7045
|
+
const createResult = await worktreeManager.createWorktree(moduleUid, moduleBranchName, true);
|
|
7046
|
+
if (!createResult.success) {
|
|
7047
|
+
console.error(`[Daemon] EP959: Failed to create worktree for ${moduleUid}: ${createResult.error}`);
|
|
7048
|
+
return;
|
|
7049
|
+
}
|
|
7050
|
+
console.log(`[Daemon] EP959: Worktree created for ${moduleUid} at ${createResult.worktreePath}`);
|
|
7051
|
+
worktree = await getWorktreeInfoForModule(moduleUid);
|
|
7052
|
+
if (!worktree || !worktree.exists) {
|
|
7053
|
+
console.error(`[Daemon] EP959: Worktree still not found after creation for ${moduleUid}`);
|
|
7054
|
+
return;
|
|
7055
|
+
}
|
|
7056
|
+
const worktreeConfig = await (0, import_core10.loadConfig)();
|
|
7057
|
+
const setupConfig = worktreeConfig?.project_settings;
|
|
7058
|
+
const hasEnvVars = setupConfig?.worktree_env_vars && Object.keys(setupConfig.worktree_env_vars).length > 0;
|
|
7059
|
+
if (setupConfig?.worktree_copy_files?.length || setupConfig?.worktree_setup_script || hasEnvVars) {
|
|
7060
|
+
console.log(`[Daemon] EP959: Starting async worktree setup for ${moduleUid}`);
|
|
7061
|
+
await worktreeManager.updateWorktreeStatus(moduleUid, "pending");
|
|
7062
|
+
this.runWorktreeSetupAsync(
|
|
7063
|
+
moduleUid,
|
|
7064
|
+
worktreeManager,
|
|
7065
|
+
setupConfig.worktree_copy_files || [],
|
|
7066
|
+
setupConfig.worktree_setup_script,
|
|
7067
|
+
worktree.path,
|
|
7068
|
+
setupConfig.worktree_env_vars || {}
|
|
7069
|
+
).then(() => {
|
|
7070
|
+
console.log(`[Daemon] EP959: Setup complete for ${moduleUid}, starting tunnel`);
|
|
7071
|
+
this.startTunnelForModule(moduleUid, worktree.path);
|
|
7072
|
+
}).catch((err) => {
|
|
7073
|
+
console.error(`[Daemon] EP959: Setup failed for ${moduleUid}:`, err);
|
|
7074
|
+
});
|
|
7075
|
+
return;
|
|
7076
|
+
}
|
|
7077
|
+
} else if (!worktree.exists) {
|
|
6846
7078
|
console.log(`[Daemon] EP956: No worktree for ${moduleUid} at ${worktree.path}, skipping tunnel`);
|
|
6847
7079
|
return;
|
|
6848
7080
|
}
|
|
6849
7081
|
const port = allocatePort(moduleUid);
|
|
6850
7082
|
console.log(`[Daemon] EP956: Using worktree ${worktree.path} on port ${port}`);
|
|
6851
|
-
const
|
|
7083
|
+
const devConfig = await (0, import_core10.loadConfig)();
|
|
7084
|
+
const devServerScript = devConfig?.project_settings?.worktree_dev_server_script;
|
|
7085
|
+
const devServerResult = await ensureDevServer(worktree.path, port, moduleUid, devServerScript);
|
|
6852
7086
|
if (!devServerResult.success) {
|
|
6853
7087
|
console.error(`[Daemon] EP956: Dev server failed for ${moduleUid}: ${devServerResult.error}`);
|
|
6854
7088
|
releasePort(moduleUid);
|
|
6855
7089
|
return;
|
|
6856
7090
|
}
|
|
6857
|
-
const config2 =
|
|
7091
|
+
const config2 = devConfig;
|
|
6858
7092
|
const apiUrl = config2?.api_url || "https://episoda.dev";
|
|
6859
7093
|
const startResult = await tunnelManager.startTunnel({
|
|
6860
7094
|
moduleUid,
|
|
@@ -7106,6 +7340,46 @@ var Daemon = class _Daemon {
|
|
|
7106
7340
|
console.warn("[Daemon] Failed to cache device ID:", error instanceof Error ? error.message : error);
|
|
7107
7341
|
}
|
|
7108
7342
|
}
|
|
7343
|
+
/**
|
|
7344
|
+
* EP964: Sync project settings from server on connect/reconnect
|
|
7345
|
+
*
|
|
7346
|
+
* Fetches worktree_env_vars and other settings from the server
|
|
7347
|
+
* and caches them locally for use during worktree checkout.
|
|
7348
|
+
*/
|
|
7349
|
+
async syncProjectSettings(projectId) {
|
|
7350
|
+
try {
|
|
7351
|
+
const config = await (0, import_core10.loadConfig)();
|
|
7352
|
+
if (!config) return;
|
|
7353
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
7354
|
+
const response = await fetchWithAuth(`${apiUrl}/api/projects/${projectId}/settings`);
|
|
7355
|
+
if (!response.ok) {
|
|
7356
|
+
console.warn(`[Daemon] EP964: Failed to sync settings: ${response.status}`);
|
|
7357
|
+
return;
|
|
7358
|
+
}
|
|
7359
|
+
const data = await response.json();
|
|
7360
|
+
const serverSettings = data.settings;
|
|
7361
|
+
if (serverSettings) {
|
|
7362
|
+
const envVars = serverSettings.worktree_env_vars || {};
|
|
7363
|
+
const updatedConfig = {
|
|
7364
|
+
...config,
|
|
7365
|
+
project_settings: {
|
|
7366
|
+
...config.project_settings,
|
|
7367
|
+
worktree_setup_script: serverSettings.worktree_setup_script,
|
|
7368
|
+
worktree_cleanup_script: serverSettings.worktree_cleanup_script,
|
|
7369
|
+
worktree_dev_server_script: serverSettings.worktree_dev_server_script,
|
|
7370
|
+
worktree_env_vars: envVars,
|
|
7371
|
+
// Keep deprecated field for backward compatibility
|
|
7372
|
+
worktree_copy_files: serverSettings.worktree_copy_files,
|
|
7373
|
+
cached_at: Date.now()
|
|
7374
|
+
}
|
|
7375
|
+
};
|
|
7376
|
+
await (0, import_core10.saveConfig)(updatedConfig);
|
|
7377
|
+
console.log(`[Daemon] EP964: Project settings synced (env_vars: ${Object.keys(envVars).length} keys)`);
|
|
7378
|
+
}
|
|
7379
|
+
} catch (error) {
|
|
7380
|
+
console.warn("[Daemon] EP964: Failed to sync project settings:", error instanceof Error ? error.message : error);
|
|
7381
|
+
}
|
|
7382
|
+
}
|
|
7109
7383
|
/**
|
|
7110
7384
|
* EP956: Cleanup module worktree when module moves to done
|
|
7111
7385
|
*
|
|
@@ -7133,6 +7407,105 @@ var Daemon = class _Daemon {
|
|
|
7133
7407
|
throw error;
|
|
7134
7408
|
}
|
|
7135
7409
|
}
|
|
7410
|
+
/**
|
|
7411
|
+
* EP959-11: Run worktree setup asynchronously
|
|
7412
|
+
* EP964: Added envVars parameter to inject .env file
|
|
7413
|
+
* Writes .env, copies files, and runs setup script after worktree creation
|
|
7414
|
+
*/
|
|
7415
|
+
async runWorktreeSetupAsync(moduleUid, worktreeManager, copyFiles, setupScript, worktreePath, envVars = {}) {
|
|
7416
|
+
console.log(`[Daemon] EP959: Running async worktree setup for ${moduleUid}`);
|
|
7417
|
+
try {
|
|
7418
|
+
await worktreeManager.updateWorktreeStatus(moduleUid, "running");
|
|
7419
|
+
if (Object.keys(envVars).length > 0) {
|
|
7420
|
+
console.log(`[Daemon] EP964: Writing .env with ${Object.keys(envVars).length} variables to ${moduleUid}`);
|
|
7421
|
+
const envContent = Object.entries(envVars).map(([key, value]) => {
|
|
7422
|
+
if (/[\s'"#$`\\]/.test(value) || value.includes("\n")) {
|
|
7423
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
7424
|
+
return `${key}="${escaped}"`;
|
|
7425
|
+
}
|
|
7426
|
+
return `${key}=${value}`;
|
|
7427
|
+
}).join("\n") + "\n";
|
|
7428
|
+
const envPath = path14.join(worktreePath, ".env");
|
|
7429
|
+
fs13.writeFileSync(envPath, envContent, { mode: 384 });
|
|
7430
|
+
console.log(`[Daemon] EP964: .env written to ${envPath}`);
|
|
7431
|
+
}
|
|
7432
|
+
if (copyFiles.length > 0) {
|
|
7433
|
+
console.log(`[Daemon] EP964: DEPRECATED - Copying ${copyFiles.length} files to ${moduleUid}`);
|
|
7434
|
+
console.warn(`[Daemon] EP964: worktree_copy_files is deprecated. Use worktree_env_vars or worktree_setup_script instead.`);
|
|
7435
|
+
const copyResult = await worktreeManager.copyFilesFromMain(moduleUid, copyFiles);
|
|
7436
|
+
if (!copyResult.success) {
|
|
7437
|
+
console.warn(`[Daemon] EP964: File copy failed (non-fatal): ${copyResult.error}`);
|
|
7438
|
+
}
|
|
7439
|
+
}
|
|
7440
|
+
if (setupScript) {
|
|
7441
|
+
console.log(`[Daemon] EP959: Running setup script for ${moduleUid}`);
|
|
7442
|
+
const scriptResult = await worktreeManager.runSetupScript(moduleUid, setupScript);
|
|
7443
|
+
if (!scriptResult.success) {
|
|
7444
|
+
throw new Error(`Setup script failed: ${scriptResult.error}`);
|
|
7445
|
+
}
|
|
7446
|
+
}
|
|
7447
|
+
await worktreeManager.updateWorktreeStatus(moduleUid, "ready");
|
|
7448
|
+
console.log(`[Daemon] EP959: Worktree setup complete for ${moduleUid}`);
|
|
7449
|
+
} catch (error) {
|
|
7450
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7451
|
+
console.error(`[Daemon] EP959: Worktree setup failed for ${moduleUid}:`, errorMessage);
|
|
7452
|
+
await worktreeManager.updateWorktreeStatus(moduleUid, "error", errorMessage);
|
|
7453
|
+
throw error;
|
|
7454
|
+
}
|
|
7455
|
+
}
|
|
7456
|
+
/**
|
|
7457
|
+
* EP959-11: Start tunnel for a module after setup completes
|
|
7458
|
+
*/
|
|
7459
|
+
async startTunnelForModule(moduleUid, worktreePath) {
|
|
7460
|
+
const tunnelManager = getTunnelManager();
|
|
7461
|
+
await tunnelManager.initialize();
|
|
7462
|
+
if (tunnelManager.hasTunnel(moduleUid)) {
|
|
7463
|
+
console.log(`[Daemon] EP959: Tunnel already running for ${moduleUid}`);
|
|
7464
|
+
return;
|
|
7465
|
+
}
|
|
7466
|
+
try {
|
|
7467
|
+
const config = await (0, import_core10.loadConfig)();
|
|
7468
|
+
const apiUrl = config?.api_url || "https://episoda.dev";
|
|
7469
|
+
const devServerScript = config?.project_settings?.worktree_dev_server_script;
|
|
7470
|
+
const port = allocatePort(moduleUid);
|
|
7471
|
+
console.log(`[Daemon] EP959: Post-setup tunnel start for ${moduleUid} on port ${port}`);
|
|
7472
|
+
const devServerResult = await ensureDevServer(worktreePath, port, moduleUid, devServerScript);
|
|
7473
|
+
if (!devServerResult.success) {
|
|
7474
|
+
console.error(`[Daemon] EP959: Dev server failed for ${moduleUid}: ${devServerResult.error}`);
|
|
7475
|
+
releasePort(moduleUid);
|
|
7476
|
+
return;
|
|
7477
|
+
}
|
|
7478
|
+
const startResult = await tunnelManager.startTunnel({
|
|
7479
|
+
moduleUid,
|
|
7480
|
+
port,
|
|
7481
|
+
onUrl: async (url) => {
|
|
7482
|
+
console.log(`[Daemon] EP959: Tunnel URL for ${moduleUid}: ${url}`);
|
|
7483
|
+
try {
|
|
7484
|
+
await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}/tunnel`, {
|
|
7485
|
+
method: "POST",
|
|
7486
|
+
body: JSON.stringify({ tunnel_url: url })
|
|
7487
|
+
});
|
|
7488
|
+
} catch (err) {
|
|
7489
|
+
console.warn(`[Daemon] EP959: Failed to report tunnel URL:`, err instanceof Error ? err.message : err);
|
|
7490
|
+
}
|
|
7491
|
+
},
|
|
7492
|
+
onStatusChange: (status, error) => {
|
|
7493
|
+
if (status === "error") {
|
|
7494
|
+
console.error(`[Daemon] EP959: Tunnel error for ${moduleUid}: ${error}`);
|
|
7495
|
+
}
|
|
7496
|
+
}
|
|
7497
|
+
});
|
|
7498
|
+
if (startResult.success) {
|
|
7499
|
+
console.log(`[Daemon] EP959: Tunnel started for ${moduleUid}`);
|
|
7500
|
+
} else {
|
|
7501
|
+
console.error(`[Daemon] EP959: Tunnel failed for ${moduleUid}: ${startResult.error}`);
|
|
7502
|
+
releasePort(moduleUid);
|
|
7503
|
+
}
|
|
7504
|
+
} catch (error) {
|
|
7505
|
+
console.error(`[Daemon] EP959: Error starting tunnel for ${moduleUid}:`, error);
|
|
7506
|
+
releasePort(moduleUid);
|
|
7507
|
+
}
|
|
7508
|
+
}
|
|
7136
7509
|
/**
|
|
7137
7510
|
* EP819: Auto-start tunnels for active local modules on daemon connect/reconnect
|
|
7138
7511
|
*
|
|
@@ -7235,8 +7608,9 @@ var Daemon = class _Daemon {
|
|
|
7235
7608
|
tunnel_error: null
|
|
7236
7609
|
});
|
|
7237
7610
|
try {
|
|
7611
|
+
const devServerScript = config.project_settings?.worktree_dev_server_script;
|
|
7238
7612
|
console.log(`[Daemon] EP956: Ensuring dev server is running for ${moduleUid} at ${worktree.path}...`);
|
|
7239
|
-
const devServerResult = await ensureDevServer(worktree.path, port, moduleUid);
|
|
7613
|
+
const devServerResult = await ensureDevServer(worktree.path, port, moduleUid, devServerScript);
|
|
7240
7614
|
if (!devServerResult.success) {
|
|
7241
7615
|
const errorMsg2 = `Dev server failed to start: ${devServerResult.error}`;
|
|
7242
7616
|
console.error(`[Daemon] EP956: ${errorMsg2}`);
|