episoda 0.2.31 → 0.2.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/daemon/daemon-process.js +221 -4
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1635,6 +1635,9 @@ var require_git_executor = __commonJS({
|
|
|
1635
1635
|
const args = ["worktree", "add"];
|
|
1636
1636
|
if (command.create) {
|
|
1637
1637
|
args.push("-b", command.branch, command.path);
|
|
1638
|
+
if (command.startPoint) {
|
|
1639
|
+
args.push(command.startPoint);
|
|
1640
|
+
}
|
|
1638
1641
|
} else {
|
|
1639
1642
|
args.push(command.path, command.branch);
|
|
1640
1643
|
}
|
|
@@ -2696,7 +2699,7 @@ var require_package = __commonJS({
|
|
|
2696
2699
|
"package.json"(exports2, module2) {
|
|
2697
2700
|
module2.exports = {
|
|
2698
2701
|
name: "episoda",
|
|
2699
|
-
version: "0.2.
|
|
2702
|
+
version: "0.2.32",
|
|
2700
2703
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2701
2704
|
main: "dist/index.js",
|
|
2702
2705
|
types: "dist/index.d.ts",
|
|
@@ -5533,14 +5536,19 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
5533
5536
|
action: "fetch",
|
|
5534
5537
|
remote: "origin"
|
|
5535
5538
|
}, { cwd: this.bareRepoPath });
|
|
5536
|
-
if (!fetchResult.success) {
|
|
5537
|
-
console.
|
|
5539
|
+
if (!fetchResult.success && createBranch) {
|
|
5540
|
+
console.error("[worktree-manager] Failed to fetch from origin:", fetchResult.output);
|
|
5541
|
+
return {
|
|
5542
|
+
success: false,
|
|
5543
|
+
error: "Failed to fetch latest refs from origin. Cannot create worktree with stale refs."
|
|
5544
|
+
};
|
|
5538
5545
|
}
|
|
5539
5546
|
const result = await this.gitExecutor.execute({
|
|
5540
5547
|
action: "worktree_add",
|
|
5541
5548
|
path: worktreePath,
|
|
5542
5549
|
branch: branchName,
|
|
5543
|
-
create: createBranch
|
|
5550
|
+
create: createBranch,
|
|
5551
|
+
startPoint: createBranch ? "origin/main" : void 0
|
|
5544
5552
|
}, { cwd: this.bareRepoPath });
|
|
5545
5553
|
if (!result.success) {
|
|
5546
5554
|
return {
|
|
@@ -7150,6 +7158,9 @@ var Daemon = class _Daemon {
|
|
|
7150
7158
|
this.syncProjectSettings(projectId).catch((err) => {
|
|
7151
7159
|
console.warn("[Daemon] EP964: Settings sync failed:", err.message);
|
|
7152
7160
|
});
|
|
7161
|
+
this.syncMachineProjectPath(projectId, projectPath).catch((err) => {
|
|
7162
|
+
console.warn("[Daemon] EP995: Project path sync failed:", err.message);
|
|
7163
|
+
});
|
|
7153
7164
|
this.autoStartTunnelsForProject(projectPath, projectId).catch((error) => {
|
|
7154
7165
|
console.error(`[Daemon] EP819: Failed to auto-start tunnels:`, error);
|
|
7155
7166
|
});
|
|
@@ -7160,6 +7171,9 @@ var Daemon = class _Daemon {
|
|
|
7160
7171
|
}).catch((err) => {
|
|
7161
7172
|
console.warn("[Daemon] EP950: Cleanup on connect failed:", err.message);
|
|
7162
7173
|
});
|
|
7174
|
+
this.reconcileWorktrees(projectId, projectPath).catch((err) => {
|
|
7175
|
+
console.warn("[Daemon] EP995: Reconciliation failed:", err.message);
|
|
7176
|
+
});
|
|
7163
7177
|
});
|
|
7164
7178
|
client.on("module_state_changed", async (message) => {
|
|
7165
7179
|
if (message.type === "module_state_changed") {
|
|
@@ -7243,6 +7257,7 @@ var Daemon = class _Daemon {
|
|
|
7243
7257
|
{
|
|
7244
7258
|
console.log(`[Daemon] EP986: Starting async worktree setup for ${moduleUid}${hasSetupConfig ? " (with config)" : " (for dependency installation)"}`);
|
|
7245
7259
|
await worktreeManager.updateWorktreeStatus(moduleUid, "pending");
|
|
7260
|
+
await this.updateModuleWorktreeStatus(moduleUid, "pending", worktree.path);
|
|
7246
7261
|
this.runWorktreeSetupAsync(
|
|
7247
7262
|
moduleUid,
|
|
7248
7263
|
worktreeManager,
|
|
@@ -7575,6 +7590,189 @@ var Daemon = class _Daemon {
|
|
|
7575
7590
|
console.warn("[Daemon] EP964: Failed to sync project settings:", error instanceof Error ? error.message : error);
|
|
7576
7591
|
}
|
|
7577
7592
|
}
|
|
7593
|
+
/**
|
|
7594
|
+
* EP995: Sync project path to server (local_machine.project_paths)
|
|
7595
|
+
*
|
|
7596
|
+
* Reports the local filesystem path for this project to the server,
|
|
7597
|
+
* enabling server-side visibility into where projects are checked out.
|
|
7598
|
+
* Uses atomic RPC to prevent race conditions.
|
|
7599
|
+
*/
|
|
7600
|
+
async syncMachineProjectPath(projectId, projectPath) {
|
|
7601
|
+
try {
|
|
7602
|
+
if (!this.deviceId) {
|
|
7603
|
+
console.warn("[Daemon] EP995: Cannot sync project path - deviceId not available");
|
|
7604
|
+
return;
|
|
7605
|
+
}
|
|
7606
|
+
const config = await (0, import_core10.loadConfig)();
|
|
7607
|
+
if (!config) return;
|
|
7608
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
7609
|
+
const response = await fetchWithAuth(`${apiUrl}/api/account/machines/${this.deviceId}`, {
|
|
7610
|
+
method: "PATCH",
|
|
7611
|
+
headers: {
|
|
7612
|
+
"Content-Type": "application/json"
|
|
7613
|
+
},
|
|
7614
|
+
body: JSON.stringify({
|
|
7615
|
+
project_id: projectId,
|
|
7616
|
+
project_path: projectPath
|
|
7617
|
+
})
|
|
7618
|
+
});
|
|
7619
|
+
if (!response.ok) {
|
|
7620
|
+
const errorData = await response.json().catch(() => ({}));
|
|
7621
|
+
console.warn(`[Daemon] EP995: Failed to sync project path: ${response.status}`, errorData);
|
|
7622
|
+
return;
|
|
7623
|
+
}
|
|
7624
|
+
console.log(`[Daemon] EP995: Synced project path to server: ${projectPath}`);
|
|
7625
|
+
} catch (error) {
|
|
7626
|
+
console.warn("[Daemon] EP995: Failed to sync project path:", error instanceof Error ? error.message : error);
|
|
7627
|
+
}
|
|
7628
|
+
}
|
|
7629
|
+
/**
|
|
7630
|
+
* EP995: Update module worktree status on server
|
|
7631
|
+
*
|
|
7632
|
+
* Reports worktree setup progress to the server, enabling:
|
|
7633
|
+
* - Server-side visibility into worktree state
|
|
7634
|
+
* - UI progress display (compatible with EP978)
|
|
7635
|
+
* - Reconciliation queries on daemon reconnect
|
|
7636
|
+
*/
|
|
7637
|
+
async updateModuleWorktreeStatus(moduleUid, status, worktreePath, errorMessage) {
|
|
7638
|
+
try {
|
|
7639
|
+
const config = await (0, import_core10.loadConfig)();
|
|
7640
|
+
if (!config) return;
|
|
7641
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
7642
|
+
const body = {
|
|
7643
|
+
worktree_status: status
|
|
7644
|
+
};
|
|
7645
|
+
if (worktreePath) {
|
|
7646
|
+
body.worktree_path = worktreePath;
|
|
7647
|
+
}
|
|
7648
|
+
if (status === "error" && errorMessage) {
|
|
7649
|
+
body.worktree_error = errorMessage;
|
|
7650
|
+
}
|
|
7651
|
+
if (status !== "error") {
|
|
7652
|
+
body.worktree_error = null;
|
|
7653
|
+
}
|
|
7654
|
+
const response = await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}`, {
|
|
7655
|
+
method: "PATCH",
|
|
7656
|
+
headers: {
|
|
7657
|
+
"Content-Type": "application/json"
|
|
7658
|
+
},
|
|
7659
|
+
body: JSON.stringify(body)
|
|
7660
|
+
});
|
|
7661
|
+
if (!response.ok) {
|
|
7662
|
+
const errorData = await response.json().catch(() => ({}));
|
|
7663
|
+
console.warn(`[Daemon] EP995: Failed to update worktree status: ${response.status}`, errorData);
|
|
7664
|
+
return;
|
|
7665
|
+
}
|
|
7666
|
+
console.log(`[Daemon] EP995: Updated module ${moduleUid} worktree_status=${status}`);
|
|
7667
|
+
} catch (error) {
|
|
7668
|
+
console.warn("[Daemon] EP995: Failed to update worktree status:", error instanceof Error ? error.message : error);
|
|
7669
|
+
}
|
|
7670
|
+
}
|
|
7671
|
+
/**
|
|
7672
|
+
* EP995: Reconcile worktrees on daemon connect/reconnect
|
|
7673
|
+
*
|
|
7674
|
+
* Figma-style "fresh snapshot on reconnect" approach:
|
|
7675
|
+
* 1. Query server for modules that should have worktrees on this machine
|
|
7676
|
+
* 2. For modules missing local worktrees, create and setup
|
|
7677
|
+
* 3. Log orphaned worktrees (local exists but module not in doing/review)
|
|
7678
|
+
*
|
|
7679
|
+
* This self-healing mechanism catches modules that transitioned
|
|
7680
|
+
* while the daemon was disconnected.
|
|
7681
|
+
*/
|
|
7682
|
+
async reconcileWorktrees(projectId, projectPath) {
|
|
7683
|
+
console.log(`[Daemon] EP995: Starting worktree reconciliation for project ${projectId}`);
|
|
7684
|
+
try {
|
|
7685
|
+
if (!this.deviceId) {
|
|
7686
|
+
console.log("[Daemon] EP995: Cannot reconcile - deviceId not available yet");
|
|
7687
|
+
return;
|
|
7688
|
+
}
|
|
7689
|
+
const config = await (0, import_core10.loadConfig)();
|
|
7690
|
+
if (!config) return;
|
|
7691
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
7692
|
+
const modulesResponse = await fetchWithAuth(
|
|
7693
|
+
`${apiUrl}/api/modules?state=doing,review&dev_mode=local&checkout_machine_id=${this.deviceId}&project_id=${projectId}`
|
|
7694
|
+
);
|
|
7695
|
+
if (!modulesResponse.ok) {
|
|
7696
|
+
console.warn(`[Daemon] EP995: Failed to fetch modules for reconciliation: ${modulesResponse.status}`);
|
|
7697
|
+
return;
|
|
7698
|
+
}
|
|
7699
|
+
const modulesData = await modulesResponse.json();
|
|
7700
|
+
const modules = modulesData.modules || [];
|
|
7701
|
+
if (modules.length === 0) {
|
|
7702
|
+
console.log("[Daemon] EP995: No modules need reconciliation");
|
|
7703
|
+
return;
|
|
7704
|
+
}
|
|
7705
|
+
console.log(`[Daemon] EP995: Found ${modules.length} module(s) to check`);
|
|
7706
|
+
const worktreeManager = new WorktreeManager(projectPath);
|
|
7707
|
+
const initialized = await worktreeManager.initialize();
|
|
7708
|
+
if (!initialized) {
|
|
7709
|
+
console.error(`[Daemon] EP995: Failed to initialize WorktreeManager`);
|
|
7710
|
+
return;
|
|
7711
|
+
}
|
|
7712
|
+
for (const module2 of modules) {
|
|
7713
|
+
const moduleUid = module2.uid;
|
|
7714
|
+
const branchName = module2.branch_name;
|
|
7715
|
+
const worktree = await getWorktreeInfoForModule(moduleUid);
|
|
7716
|
+
if (!worktree?.exists) {
|
|
7717
|
+
console.log(`[Daemon] EP995: Module ${moduleUid} missing local worktree - creating...`);
|
|
7718
|
+
const moduleBranchName = branchName || moduleUid;
|
|
7719
|
+
const createResult = await worktreeManager.createWorktree(moduleUid, moduleBranchName, true);
|
|
7720
|
+
if (!createResult.success) {
|
|
7721
|
+
console.error(`[Daemon] EP995: Failed to create worktree for ${moduleUid}: ${createResult.error}`);
|
|
7722
|
+
continue;
|
|
7723
|
+
}
|
|
7724
|
+
console.log(`[Daemon] EP995: Created worktree for ${moduleUid} at ${createResult.worktreePath}`);
|
|
7725
|
+
if (this.deviceId) {
|
|
7726
|
+
try {
|
|
7727
|
+
await fetchWithAuth(`${apiUrl}/api/modules/${moduleUid}`, {
|
|
7728
|
+
method: "PATCH",
|
|
7729
|
+
body: JSON.stringify({ checkout_machine_id: this.deviceId })
|
|
7730
|
+
});
|
|
7731
|
+
console.log(`[Daemon] EP995: Claimed ownership of ${moduleUid}`);
|
|
7732
|
+
} catch (ownershipError) {
|
|
7733
|
+
console.warn(`[Daemon] EP995: Failed to claim ownership of ${moduleUid}`);
|
|
7734
|
+
}
|
|
7735
|
+
}
|
|
7736
|
+
const newWorktree = await getWorktreeInfoForModule(moduleUid);
|
|
7737
|
+
if (!newWorktree?.exists) {
|
|
7738
|
+
console.error(`[Daemon] EP995: Worktree still not found after creation`);
|
|
7739
|
+
continue;
|
|
7740
|
+
}
|
|
7741
|
+
const setupConfig = config.project_settings;
|
|
7742
|
+
const envVars = await fetchEnvVars();
|
|
7743
|
+
console.log(`[Daemon] EP995: Starting setup for reconciled module ${moduleUid}`);
|
|
7744
|
+
await worktreeManager.updateWorktreeStatus(moduleUid, "pending");
|
|
7745
|
+
await this.updateModuleWorktreeStatus(moduleUid, "pending", newWorktree.path);
|
|
7746
|
+
this.runWorktreeSetupAsync(
|
|
7747
|
+
moduleUid,
|
|
7748
|
+
worktreeManager,
|
|
7749
|
+
setupConfig?.worktree_copy_files || [],
|
|
7750
|
+
setupConfig?.worktree_setup_script,
|
|
7751
|
+
newWorktree.path,
|
|
7752
|
+
envVars
|
|
7753
|
+
).then(() => {
|
|
7754
|
+
console.log(`[Daemon] EP995: Setup complete for reconciled ${moduleUid}`);
|
|
7755
|
+
this.startTunnelForModule(moduleUid, newWorktree.path);
|
|
7756
|
+
}).catch((err) => {
|
|
7757
|
+
console.error(`[Daemon] EP995: Setup failed for reconciled ${moduleUid}:`, err);
|
|
7758
|
+
});
|
|
7759
|
+
} else {
|
|
7760
|
+
const tunnelManager = getTunnelManager();
|
|
7761
|
+
await tunnelManager.initialize();
|
|
7762
|
+
if (!tunnelManager.hasTunnel(moduleUid)) {
|
|
7763
|
+
console.log(`[Daemon] EP995: Module ${moduleUid} has worktree but no tunnel - starting...`);
|
|
7764
|
+
await this.startTunnelForModule(moduleUid, worktree.path);
|
|
7765
|
+
} else {
|
|
7766
|
+
console.log(`[Daemon] EP995: Module ${moduleUid} OK - worktree and tunnel exist`);
|
|
7767
|
+
}
|
|
7768
|
+
}
|
|
7769
|
+
}
|
|
7770
|
+
console.log("[Daemon] EP995: Reconciliation complete");
|
|
7771
|
+
} catch (error) {
|
|
7772
|
+
console.error("[Daemon] EP995: Reconciliation error:", error instanceof Error ? error.message : error);
|
|
7773
|
+
throw error;
|
|
7774
|
+
}
|
|
7775
|
+
}
|
|
7578
7776
|
/**
|
|
7579
7777
|
* EP956: Cleanup module worktree when module moves to done
|
|
7580
7778
|
*
|
|
@@ -7614,6 +7812,22 @@ var Daemon = class _Daemon {
|
|
|
7614
7812
|
} else {
|
|
7615
7813
|
console.log(`[Daemon] EP994: No worktree to remove for ${moduleUid}`);
|
|
7616
7814
|
}
|
|
7815
|
+
try {
|
|
7816
|
+
const cleanupConfig = await (0, import_core10.loadConfig)();
|
|
7817
|
+
const cleanupApiUrl = cleanupConfig?.api_url || "https://episoda.dev";
|
|
7818
|
+
await fetchWithAuth(`${cleanupApiUrl}/api/modules/${moduleUid}`, {
|
|
7819
|
+
method: "PATCH",
|
|
7820
|
+
headers: { "Content-Type": "application/json" },
|
|
7821
|
+
body: JSON.stringify({
|
|
7822
|
+
worktree_status: null,
|
|
7823
|
+
worktree_path: null,
|
|
7824
|
+
worktree_error: null
|
|
7825
|
+
})
|
|
7826
|
+
});
|
|
7827
|
+
console.log(`[Daemon] EP995: Cleared worktree status for ${moduleUid}`);
|
|
7828
|
+
} catch (clearError) {
|
|
7829
|
+
console.warn(`[Daemon] EP995: Failed to clear worktree status for ${moduleUid}:`, clearError);
|
|
7830
|
+
}
|
|
7617
7831
|
console.log(`[Daemon] EP956: Async cleanup complete for ${moduleUid}`);
|
|
7618
7832
|
} catch (error) {
|
|
7619
7833
|
console.error(`[Daemon] EP956: Cleanup error for ${moduleUid}:`, error instanceof Error ? error.message : error);
|
|
@@ -7629,6 +7843,7 @@ var Daemon = class _Daemon {
|
|
|
7629
7843
|
console.log(`[Daemon] EP959: Running async worktree setup for ${moduleUid}`);
|
|
7630
7844
|
try {
|
|
7631
7845
|
await worktreeManager.updateWorktreeStatus(moduleUid, "running");
|
|
7846
|
+
await this.updateModuleWorktreeStatus(moduleUid, "setup", worktreePath);
|
|
7632
7847
|
if (Object.keys(envVars).length > 0) {
|
|
7633
7848
|
console.log(`[Daemon] EP988: Writing .env with ${Object.keys(envVars).length} variables to ${moduleUid}`);
|
|
7634
7849
|
writeEnvFile(worktreePath, envVars);
|
|
@@ -7672,11 +7887,13 @@ var Daemon = class _Daemon {
|
|
|
7672
7887
|
}
|
|
7673
7888
|
}
|
|
7674
7889
|
await worktreeManager.updateWorktreeStatus(moduleUid, "ready");
|
|
7890
|
+
await this.updateModuleWorktreeStatus(moduleUid, "ready", worktreePath);
|
|
7675
7891
|
console.log(`[Daemon] EP959: Worktree setup complete for ${moduleUid}`);
|
|
7676
7892
|
} catch (error) {
|
|
7677
7893
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7678
7894
|
console.error(`[Daemon] EP959: Worktree setup failed for ${moduleUid}:`, errorMessage);
|
|
7679
7895
|
await worktreeManager.updateWorktreeStatus(moduleUid, "error", errorMessage);
|
|
7896
|
+
await this.updateModuleWorktreeStatus(moduleUid, "error", worktreePath, errorMessage);
|
|
7680
7897
|
throw error;
|
|
7681
7898
|
}
|
|
7682
7899
|
}
|