@episoda/cli 0.2.162 → 0.2.164
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 +238 -69
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +280 -66
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -343,6 +343,8 @@ var require_git_executor = __commonJS({
|
|
|
343
343
|
case "delete_branch":
|
|
344
344
|
return await this.executeDeleteBranch(command, cwd, options);
|
|
345
345
|
// EP597: Read operations for production local dev mode
|
|
346
|
+
case "list_branches":
|
|
347
|
+
return await this.executeListBranches(cwd, options);
|
|
346
348
|
case "branch_exists":
|
|
347
349
|
return await this.executeBranchExists(command, cwd, options);
|
|
348
350
|
case "branch_has_commits":
|
|
@@ -632,6 +634,65 @@ var require_git_executor = __commonJS({
|
|
|
632
634
|
args.push(command.branch);
|
|
633
635
|
return await this.runGitCommand(args, cwd, options);
|
|
634
636
|
}
|
|
637
|
+
/**
|
|
638
|
+
* List local branches with remote-tracking signal and current branch marker.
|
|
639
|
+
*/
|
|
640
|
+
async executeListBranches(cwd, options) {
|
|
641
|
+
try {
|
|
642
|
+
const timeout = options?.timeout || 1e4;
|
|
643
|
+
const branchMap = /* @__PURE__ */ new Map();
|
|
644
|
+
const addBranch = (name, attrs) => {
|
|
645
|
+
const normalized = name.trim();
|
|
646
|
+
if (!normalized)
|
|
647
|
+
return;
|
|
648
|
+
const existing = branchMap.get(normalized);
|
|
649
|
+
if (existing) {
|
|
650
|
+
existing.current = existing.current || !!attrs?.current;
|
|
651
|
+
existing.remote = existing.remote || !!attrs?.remote;
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
branchMap.set(normalized, {
|
|
655
|
+
name: normalized,
|
|
656
|
+
current: !!attrs?.current,
|
|
657
|
+
remote: !!attrs?.remote
|
|
658
|
+
});
|
|
659
|
+
};
|
|
660
|
+
const statusResult = await this.executeStatus(cwd, options);
|
|
661
|
+
const currentBranch = statusResult.success ? statusResult.details?.branchName : void 0;
|
|
662
|
+
try {
|
|
663
|
+
const { stdout } = await execAsync3(`git for-each-ref --format='%(refname:short)' refs/heads`, { cwd, timeout });
|
|
664
|
+
stdout.split("\n").map((line) => line.trim()).filter(Boolean).forEach((name) => addBranch(name, { current: name === currentBranch }));
|
|
665
|
+
} catch {
|
|
666
|
+
}
|
|
667
|
+
try {
|
|
668
|
+
const { stdout } = await execAsync3(`git for-each-ref --format='%(refname:short)' refs/remotes/origin`, { cwd, timeout });
|
|
669
|
+
stdout.split("\n").map((line) => line.trim()).filter(Boolean).forEach((refName) => {
|
|
670
|
+
if (refName === "origin/HEAD")
|
|
671
|
+
return;
|
|
672
|
+
const normalized = refName.startsWith("origin/") ? refName.slice("origin/".length) : refName;
|
|
673
|
+
addBranch(normalized, { remote: true, current: normalized === currentBranch });
|
|
674
|
+
});
|
|
675
|
+
} catch {
|
|
676
|
+
}
|
|
677
|
+
if (currentBranch && currentBranch !== "HEAD") {
|
|
678
|
+
addBranch(currentBranch, { current: true });
|
|
679
|
+
}
|
|
680
|
+
const branches = Array.from(branchMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
681
|
+
return {
|
|
682
|
+
success: true,
|
|
683
|
+
details: {
|
|
684
|
+
branchName: currentBranch,
|
|
685
|
+
branches
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
} catch (error) {
|
|
689
|
+
return {
|
|
690
|
+
success: false,
|
|
691
|
+
error: "UNKNOWN_ERROR",
|
|
692
|
+
output: error.message || "Failed to list branches"
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
}
|
|
635
696
|
/**
|
|
636
697
|
* EP597: Execute branch_exists command
|
|
637
698
|
* Checks if a branch exists locally and/or remotely
|
|
@@ -2153,6 +2214,8 @@ var require_websocket_client = __commonJS({
|
|
|
2153
2214
|
this.osArch = deviceInfo?.osArch;
|
|
2154
2215
|
this.daemonPid = deviceInfo?.daemonPid;
|
|
2155
2216
|
this.cliVersion = deviceInfo?.cliVersion;
|
|
2217
|
+
this.cliPackageName = deviceInfo?.cliPackageName;
|
|
2218
|
+
this.capabilities = deviceInfo?.capabilities;
|
|
2156
2219
|
this.environment = deviceInfo?.environment;
|
|
2157
2220
|
this.containerId = deviceInfo?.containerId;
|
|
2158
2221
|
this.isDisconnecting = false;
|
|
@@ -2192,6 +2255,8 @@ var require_websocket_client = __commonJS({
|
|
|
2192
2255
|
type: "auth",
|
|
2193
2256
|
token,
|
|
2194
2257
|
version: this.cliVersion || version_1.VERSION,
|
|
2258
|
+
cliPackageName: this.cliPackageName,
|
|
2259
|
+
capabilities: this.capabilities,
|
|
2195
2260
|
environment: this.environment,
|
|
2196
2261
|
machineId,
|
|
2197
2262
|
containerId: this.containerId,
|
|
@@ -2511,6 +2576,8 @@ var require_websocket_client = __commonJS({
|
|
|
2511
2576
|
osArch: this.osArch,
|
|
2512
2577
|
daemonPid: this.daemonPid,
|
|
2513
2578
|
cliVersion: this.cliVersion,
|
|
2579
|
+
cliPackageName: this.cliPackageName,
|
|
2580
|
+
capabilities: this.capabilities,
|
|
2514
2581
|
environment: this.environment,
|
|
2515
2582
|
containerId: this.containerId
|
|
2516
2583
|
}).then(() => {
|
|
@@ -2911,7 +2978,7 @@ var require_package = __commonJS({
|
|
|
2911
2978
|
"package.json"(exports2, module2) {
|
|
2912
2979
|
module2.exports = {
|
|
2913
2980
|
name: "@episoda/cli",
|
|
2914
|
-
version: "0.2.
|
|
2981
|
+
version: "0.2.164",
|
|
2915
2982
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2916
2983
|
main: "dist/index.js",
|
|
2917
2984
|
types: "dist/index.d.ts",
|
|
@@ -7527,7 +7594,7 @@ ${message}`;
|
|
|
7527
7594
|
const args2 = parts.slice(1);
|
|
7528
7595
|
let env;
|
|
7529
7596
|
if (server.name === "github") {
|
|
7530
|
-
env =
|
|
7597
|
+
env = void 0;
|
|
7531
7598
|
} else {
|
|
7532
7599
|
env = {
|
|
7533
7600
|
...baseEnv,
|
|
@@ -8620,8 +8687,8 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
8620
8687
|
const allWorktrees = this.listWorktrees();
|
|
8621
8688
|
const activeSet = new Set(activeModuleUids);
|
|
8622
8689
|
const orphaned = allWorktrees.filter((w) => !activeSet.has(w.moduleUid));
|
|
8623
|
-
const
|
|
8624
|
-
return { orphaned, valid };
|
|
8690
|
+
const valid3 = allWorktrees.filter((w) => activeSet.has(w.moduleUid));
|
|
8691
|
+
return { orphaned, valid: valid3 };
|
|
8625
8692
|
}
|
|
8626
8693
|
/**
|
|
8627
8694
|
* Update last accessed timestamp for a worktree
|
|
@@ -8656,7 +8723,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
8656
8723
|
*/
|
|
8657
8724
|
async validateWorktrees() {
|
|
8658
8725
|
const config = this.readConfig();
|
|
8659
|
-
const
|
|
8726
|
+
const valid3 = [];
|
|
8660
8727
|
const stale = [];
|
|
8661
8728
|
const orphaned = [];
|
|
8662
8729
|
const listResult = await this.gitExecutor.execute({
|
|
@@ -8667,7 +8734,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
8667
8734
|
);
|
|
8668
8735
|
for (const worktree of config?.worktrees || []) {
|
|
8669
8736
|
if (actualWorktrees.has(worktree.worktreePath)) {
|
|
8670
|
-
|
|
8737
|
+
valid3.push(worktree);
|
|
8671
8738
|
actualWorktrees.delete(worktree.worktreePath);
|
|
8672
8739
|
} else {
|
|
8673
8740
|
stale.push(worktree);
|
|
@@ -8678,7 +8745,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
8678
8745
|
orphaned.push(wpath);
|
|
8679
8746
|
}
|
|
8680
8747
|
}
|
|
8681
|
-
return { valid, stale, orphaned };
|
|
8748
|
+
return { valid: valid3, stale, orphaned };
|
|
8682
8749
|
}
|
|
8683
8750
|
/**
|
|
8684
8751
|
* EP1190: Clean up non-module worktrees (like 'main')
|
|
@@ -11551,6 +11618,18 @@ async function deleteWorktree(config, moduleUid) {
|
|
|
11551
11618
|
// src/daemon/package-manager.ts
|
|
11552
11619
|
var fs21 = __toESM(require("fs"));
|
|
11553
11620
|
var path22 = __toESM(require("path"));
|
|
11621
|
+
function pnpmCommand(args) {
|
|
11622
|
+
return [
|
|
11623
|
+
"if command -v pnpm >/dev/null 2>&1; then",
|
|
11624
|
+
` pnpm ${args}`,
|
|
11625
|
+
"elif command -v corepack >/dev/null 2>&1; then",
|
|
11626
|
+
` corepack pnpm ${args}`,
|
|
11627
|
+
"else",
|
|
11628
|
+
' echo "[setup] ERROR: pnpm is not installed and corepack is unavailable" >&2',
|
|
11629
|
+
" exit 127",
|
|
11630
|
+
"fi"
|
|
11631
|
+
].join(" ");
|
|
11632
|
+
}
|
|
11554
11633
|
var PACKAGE_MANAGERS = {
|
|
11555
11634
|
javascript: [
|
|
11556
11635
|
{
|
|
@@ -11572,7 +11651,7 @@ var PACKAGE_MANAGERS = {
|
|
|
11572
11651
|
return null;
|
|
11573
11652
|
},
|
|
11574
11653
|
config: {
|
|
11575
|
-
installCmd: "
|
|
11654
|
+
installCmd: pnpmCommand("install --frozen-lockfile"),
|
|
11576
11655
|
cacheEnvVar: "PNPM_HOME"
|
|
11577
11656
|
}
|
|
11578
11657
|
},
|
|
@@ -11604,7 +11683,7 @@ var PACKAGE_MANAGERS = {
|
|
|
11604
11683
|
name: "pnpm",
|
|
11605
11684
|
detector: (p) => fs21.existsSync(path22.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
|
|
11606
11685
|
config: {
|
|
11607
|
-
installCmd: "
|
|
11686
|
+
installCmd: pnpmCommand("install"),
|
|
11608
11687
|
cacheEnvVar: "PNPM_HOME"
|
|
11609
11688
|
}
|
|
11610
11689
|
}
|
|
@@ -11752,7 +11831,7 @@ async function autoDetectSetupScript(worktreePath) {
|
|
|
11752
11831
|
function getBuildPackagesCommand(packageManagerName) {
|
|
11753
11832
|
switch (packageManagerName) {
|
|
11754
11833
|
case "pnpm":
|
|
11755
|
-
return
|
|
11834
|
+
return 'if command -v pnpm >/dev/null 2>&1; then pnpm run build:packages; elif command -v corepack >/dev/null 2>&1; then corepack pnpm run build:packages; else echo "[setup] ERROR: pnpm is not installed and corepack is unavailable" >&2; exit 127; fi';
|
|
11756
11835
|
case "yarn":
|
|
11757
11836
|
return "yarn build:packages";
|
|
11758
11837
|
case "npm":
|
|
@@ -11763,6 +11842,18 @@ function getBuildPackagesCommand(packageManagerName) {
|
|
|
11763
11842
|
return null;
|
|
11764
11843
|
}
|
|
11765
11844
|
}
|
|
11845
|
+
function hasPackageScript(worktreePath, scriptName) {
|
|
11846
|
+
try {
|
|
11847
|
+
const packageJsonPath = path23.join(worktreePath, "package.json");
|
|
11848
|
+
if (!fs22.existsSync(packageJsonPath)) {
|
|
11849
|
+
return false;
|
|
11850
|
+
}
|
|
11851
|
+
const packageJson2 = JSON.parse(fs22.readFileSync(packageJsonPath, "utf-8"));
|
|
11852
|
+
return typeof packageJson2.scripts?.[scriptName] === "string";
|
|
11853
|
+
} catch {
|
|
11854
|
+
return false;
|
|
11855
|
+
}
|
|
11856
|
+
}
|
|
11766
11857
|
async function handleWorktreeCreate(request2) {
|
|
11767
11858
|
const {
|
|
11768
11859
|
workspaceSlug,
|
|
@@ -11855,14 +11946,13 @@ async function handleWorktreeCreate(request2) {
|
|
|
11855
11946
|
}
|
|
11856
11947
|
const isCloud = process.env.EPISODA_MODE === "cloud";
|
|
11857
11948
|
let effectiveSetupScript = setupScript || await autoDetectSetupScript(worktreePath);
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11949
|
+
const detection = detectPackageManager(worktreePath);
|
|
11950
|
+
const buildCmd = getBuildPackagesCommand(detection.packageManager?.name);
|
|
11951
|
+
const hasBuildPackages = hasPackageScript(worktreePath, "build:packages");
|
|
11952
|
+
if (buildCmd && hasBuildPackages && !effectiveSetupScript?.includes("build:packages")) {
|
|
11953
|
+
effectiveSetupScript = effectiveSetupScript ? `${effectiveSetupScript}
|
|
11863
11954
|
${buildCmd}` : buildCmd;
|
|
11864
|
-
|
|
11865
|
-
}
|
|
11955
|
+
console.log(`[Worktree] EP1386: Added build:packages bootstrap for ${isCloud ? "cloud" : "local"} setup`);
|
|
11866
11956
|
}
|
|
11867
11957
|
if (effectiveSetupScript) {
|
|
11868
11958
|
const setupStartMs = Date.now();
|
|
@@ -11884,30 +11974,8 @@ ${buildCmd}` : buildCmd;
|
|
|
11884
11974
|
let port;
|
|
11885
11975
|
if (moduleType === "ops") {
|
|
11886
11976
|
console.log(`[Worktree] EP1363: Skipping preview for ops module ${moduleUid}`);
|
|
11887
|
-
} else if (finalStatus === "ready"
|
|
11888
|
-
|
|
11889
|
-
console.log(`[Worktree] EP1143: Allocated port ${port} for ${moduleUid}`);
|
|
11890
|
-
const previewManager = getPreviewManager();
|
|
11891
|
-
const previewResult = await previewManager.startPreview({
|
|
11892
|
-
moduleUid,
|
|
11893
|
-
worktreePath,
|
|
11894
|
-
port
|
|
11895
|
-
});
|
|
11896
|
-
if (previewResult.success) {
|
|
11897
|
-
previewUrl = previewResult.previewUrl;
|
|
11898
|
-
console.log(`[Worktree] EP1143: Preview started: ${previewUrl || "pending URL"}`);
|
|
11899
|
-
} else {
|
|
11900
|
-
finalStatus = "error";
|
|
11901
|
-
finalError = previewResult.error || "Preview start failed";
|
|
11902
|
-
console.error(`[Worktree] EP1143: Preview failed: ${finalError}`);
|
|
11903
|
-
try {
|
|
11904
|
-
await previewManager.stopPreview(moduleUid);
|
|
11905
|
-
} catch (cleanupError) {
|
|
11906
|
-
console.warn(`[Worktree] EP1143: Preview cleanup failed: ${cleanupError.message}`);
|
|
11907
|
-
}
|
|
11908
|
-
}
|
|
11909
|
-
} else if (finalStatus === "ready" && isCloud) {
|
|
11910
|
-
console.log(`[Worktree] EP1262: Skipping preview start in cloud mode (server orchestrates)`);
|
|
11977
|
+
} else if (finalStatus === "ready") {
|
|
11978
|
+
console.log(`[Worktree] EP1386: Skipping preview start during worktree create (deferred until module is doing)`);
|
|
11911
11979
|
}
|
|
11912
11980
|
console.log(`[Worktree] EP1373: worktree_create_total phase=worktree_create_total durationMs=${Date.now() - worktreeStartMs} moduleUid=${moduleUid} correlationId=${correlationId || "none"} status=${finalStatus}`);
|
|
11913
11981
|
console.log(`[Worktree] EP1143: Worktree ready for ${moduleUid}`);
|
|
@@ -12782,6 +12850,7 @@ var fs25 = __toESM(require("fs"));
|
|
|
12782
12850
|
var path26 = __toESM(require("path"));
|
|
12783
12851
|
var import_child_process18 = require("child_process");
|
|
12784
12852
|
var import_core17 = __toESM(require_dist());
|
|
12853
|
+
var semver2 = __toESM(require("semver"));
|
|
12785
12854
|
|
|
12786
12855
|
// src/utils/update-checker.ts
|
|
12787
12856
|
var import_child_process17 = require("child_process");
|
|
@@ -12792,6 +12861,7 @@ var import_core16 = __toESM(require_dist());
|
|
|
12792
12861
|
|
|
12793
12862
|
// src/utils/update-checker.ts
|
|
12794
12863
|
var PACKAGE_NAME = "@episoda/cli";
|
|
12864
|
+
var LEGACY_PACKAGE_NAME = "episoda";
|
|
12795
12865
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
12796
12866
|
function isFileLinkedInstall() {
|
|
12797
12867
|
try {
|
|
@@ -12864,6 +12934,39 @@ function getInstalledVersion() {
|
|
|
12864
12934
|
return null;
|
|
12865
12935
|
}
|
|
12866
12936
|
}
|
|
12937
|
+
function getLegacyInstalledVersion() {
|
|
12938
|
+
try {
|
|
12939
|
+
const output = (0, import_child_process17.execSync)(`npm list -g ${LEGACY_PACKAGE_NAME} --json`, {
|
|
12940
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
12941
|
+
timeout: 1e4
|
|
12942
|
+
}).toString();
|
|
12943
|
+
const data = JSON.parse(output);
|
|
12944
|
+
return data?.dependencies?.[LEGACY_PACKAGE_NAME]?.version || null;
|
|
12945
|
+
} catch {
|
|
12946
|
+
return null;
|
|
12947
|
+
}
|
|
12948
|
+
}
|
|
12949
|
+
function detectCliInstallChannel(embeddedVersion) {
|
|
12950
|
+
const scopedVersion = getInstalledVersion();
|
|
12951
|
+
const legacyVersion = getLegacyInstalledVersion();
|
|
12952
|
+
const effectiveVersion = scopedVersion || legacyVersion || embeddedVersion || null;
|
|
12953
|
+
return {
|
|
12954
|
+
scopedVersion,
|
|
12955
|
+
legacyVersion,
|
|
12956
|
+
legacyOnly: !scopedVersion && !!legacyVersion,
|
|
12957
|
+
effectiveVersion
|
|
12958
|
+
};
|
|
12959
|
+
}
|
|
12960
|
+
function resolveEffectiveCliVersion(embeddedVersion) {
|
|
12961
|
+
const installedVersion = detectCliInstallChannel(embeddedVersion).effectiveVersion;
|
|
12962
|
+
if (!installedVersion) {
|
|
12963
|
+
return embeddedVersion;
|
|
12964
|
+
}
|
|
12965
|
+
if (semver.valid(installedVersion) && semver.valid(embeddedVersion)) {
|
|
12966
|
+
return semver.gt(installedVersion, embeddedVersion) ? installedVersion : embeddedVersion;
|
|
12967
|
+
}
|
|
12968
|
+
return installedVersion === embeddedVersion ? embeddedVersion : installedVersion;
|
|
12969
|
+
}
|
|
12867
12970
|
|
|
12868
12971
|
// src/daemon/update-manager.ts
|
|
12869
12972
|
var UpdateManager = class _UpdateManager {
|
|
@@ -12886,6 +12989,19 @@ var UpdateManager = class _UpdateManager {
|
|
|
12886
12989
|
// 4 hours
|
|
12887
12990
|
this.MAX_UPDATE_ATTEMPTS = 3;
|
|
12888
12991
|
}
|
|
12992
|
+
/**
|
|
12993
|
+
* Prefer installed CLI version when it's newer than embedded bundle metadata.
|
|
12994
|
+
* This avoids update churn when daemon bundle version lags package install version.
|
|
12995
|
+
*/
|
|
12996
|
+
getEffectiveCurrentVersion() {
|
|
12997
|
+
return resolveEffectiveCliVersion(this.currentVersion);
|
|
12998
|
+
}
|
|
12999
|
+
isTargetVersionNewer(targetVersion, currentVersion) {
|
|
13000
|
+
if (semver2.valid(targetVersion) && semver2.valid(currentVersion)) {
|
|
13001
|
+
return semver2.gt(targetVersion, currentVersion);
|
|
13002
|
+
}
|
|
13003
|
+
return targetVersion !== currentVersion;
|
|
13004
|
+
}
|
|
12889
13005
|
/**
|
|
12890
13006
|
* Start periodic update checks (every 4 hours).
|
|
12891
13007
|
* Does nothing if EPISODA_CLI_PIN_VERSION is set.
|
|
@@ -12930,7 +13046,8 @@ var UpdateManager = class _UpdateManager {
|
|
|
12930
13046
|
return;
|
|
12931
13047
|
}
|
|
12932
13048
|
try {
|
|
12933
|
-
const
|
|
13049
|
+
const effectiveCurrentVersion = this.getEffectiveCurrentVersion();
|
|
13050
|
+
const result = await checkForUpdates(effectiveCurrentVersion);
|
|
12934
13051
|
if (result.updateAvailable) {
|
|
12935
13052
|
await this.applyUpdateIfIdle(result.latestVersion, "startup");
|
|
12936
13053
|
}
|
|
@@ -12952,8 +13069,9 @@ var UpdateManager = class _UpdateManager {
|
|
|
12952
13069
|
}
|
|
12953
13070
|
async applyUpdateIfIdle(targetVersion, source) {
|
|
12954
13071
|
if (this.updateInProgress) return;
|
|
12955
|
-
|
|
12956
|
-
|
|
13072
|
+
const effectiveCurrentVersion = this.getEffectiveCurrentVersion();
|
|
13073
|
+
if (!this.isTargetVersionNewer(targetVersion, effectiveCurrentVersion)) {
|
|
13074
|
+
console.log(`[Daemon] EP1390: Skipping update target=${targetVersion}, current=${effectiveCurrentVersion} (source=${source})`);
|
|
12957
13075
|
return;
|
|
12958
13076
|
}
|
|
12959
13077
|
if (this.lastFailedUpdateVersion === targetVersion && this.updateFailedAttempts >= _UpdateManager.MAX_UPDATE_ATTEMPTS) {
|
|
@@ -12963,12 +13081,12 @@ var UpdateManager = class _UpdateManager {
|
|
|
12963
13081
|
const activeAgentSessions = this.getActiveAgentSessionCount();
|
|
12964
13082
|
if (activeAgentSessions > 0) {
|
|
12965
13083
|
this.pendingUpdateVersion = targetVersion;
|
|
12966
|
-
console.log(`[Daemon] EP1319: Update available (${
|
|
13084
|
+
console.log(`[Daemon] EP1319: Update available (${effectiveCurrentVersion} \u2192 ${targetVersion}) but ${activeAgentSessions} active agent session(s) \u2014 deferring`);
|
|
12967
13085
|
return;
|
|
12968
13086
|
}
|
|
12969
|
-
const
|
|
12970
|
-
if (
|
|
12971
|
-
console.log(`[Daemon]
|
|
13087
|
+
const latestEffectiveCurrent = this.getEffectiveCurrentVersion();
|
|
13088
|
+
if (!this.isTargetVersionNewer(targetVersion, latestEffectiveCurrent)) {
|
|
13089
|
+
console.log(`[Daemon] EP1390: Update no longer needed target=${targetVersion}, current=${latestEffectiveCurrent}`);
|
|
12972
13090
|
return;
|
|
12973
13091
|
}
|
|
12974
13092
|
this.updateInProgress = true;
|
|
@@ -12994,6 +13112,14 @@ var UpdateManager = class _UpdateManager {
|
|
|
12994
13112
|
stdio: ["ignore", logFd, logFd],
|
|
12995
13113
|
env: { ...process.env, EPISODA_DAEMON_MODE: "1" }
|
|
12996
13114
|
});
|
|
13115
|
+
if (!child.pid) {
|
|
13116
|
+
try {
|
|
13117
|
+
fs25.closeSync(logFd);
|
|
13118
|
+
} catch {
|
|
13119
|
+
}
|
|
13120
|
+
this.recordUpdateFailure(targetVersion, "Failed to spawn replacement daemon (missing pid)");
|
|
13121
|
+
return;
|
|
13122
|
+
}
|
|
12997
13123
|
child.unref();
|
|
12998
13124
|
const pidPath = getPidFilePath();
|
|
12999
13125
|
fs25.writeFileSync(pidPath, child.pid.toString(), "utf-8");
|
|
@@ -13030,7 +13156,8 @@ var UpdateManager = class _UpdateManager {
|
|
|
13030
13156
|
return;
|
|
13031
13157
|
}
|
|
13032
13158
|
try {
|
|
13033
|
-
const
|
|
13159
|
+
const effectiveCurrentVersion = this.getEffectiveCurrentVersion();
|
|
13160
|
+
const result = await checkForUpdates(effectiveCurrentVersion);
|
|
13034
13161
|
if (!result.updateAvailable) {
|
|
13035
13162
|
if (this.pendingUpdateVersion) {
|
|
13036
13163
|
console.log(`[Daemon] EP1319: Pending update to ${this.pendingUpdateVersion} is no longer needed (already current)`);
|
|
@@ -13690,6 +13817,52 @@ var ConnectionManager = class {
|
|
|
13690
13817
|
}
|
|
13691
13818
|
};
|
|
13692
13819
|
|
|
13820
|
+
// src/daemon/ws-endpoint.ts
|
|
13821
|
+
var DEFAULT_API_URL = "https://episoda.dev";
|
|
13822
|
+
var CANONICAL_PROD_WS_HOST = "ws.episoda.dev";
|
|
13823
|
+
var PRODUCTION_API_HOSTS = /* @__PURE__ */ new Set([
|
|
13824
|
+
"episoda.dev",
|
|
13825
|
+
"www.episoda.dev",
|
|
13826
|
+
"api.episoda.dev"
|
|
13827
|
+
]);
|
|
13828
|
+
function parseApiUrl(rawUrl) {
|
|
13829
|
+
try {
|
|
13830
|
+
return new URL(rawUrl);
|
|
13831
|
+
} catch {
|
|
13832
|
+
return new URL(DEFAULT_API_URL);
|
|
13833
|
+
}
|
|
13834
|
+
}
|
|
13835
|
+
function resolveDaemonWsEndpoint(config, env = process.env) {
|
|
13836
|
+
if (config.ws_url) {
|
|
13837
|
+
return {
|
|
13838
|
+
wsUrl: config.ws_url,
|
|
13839
|
+
source: "config.ws_url",
|
|
13840
|
+
serverUrl: config.project_settings?.local_server_url || config.api_url || env.EPISODA_API_URL || DEFAULT_API_URL
|
|
13841
|
+
};
|
|
13842
|
+
}
|
|
13843
|
+
if (env.EPISODA_WS_URL) {
|
|
13844
|
+
return {
|
|
13845
|
+
wsUrl: env.EPISODA_WS_URL,
|
|
13846
|
+
source: "env.EPISODA_WS_URL",
|
|
13847
|
+
serverUrl: config.project_settings?.local_server_url || config.api_url || env.EPISODA_API_URL || DEFAULT_API_URL
|
|
13848
|
+
};
|
|
13849
|
+
}
|
|
13850
|
+
const rawServerUrl = config.project_settings?.local_server_url || config.api_url || env.EPISODA_API_URL || DEFAULT_API_URL;
|
|
13851
|
+
const serverUrl = parseApiUrl(rawServerUrl);
|
|
13852
|
+
const wsProtocol = serverUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
13853
|
+
const useCanonicalProdHost = PRODUCTION_API_HOSTS.has(serverUrl.hostname);
|
|
13854
|
+
const wsHostname = useCanonicalProdHost ? CANONICAL_PROD_WS_HOST : serverUrl.hostname;
|
|
13855
|
+
const explicitPort = env.EPISODA_WS_PORT?.trim();
|
|
13856
|
+
const inheritedServerPort = !useCanonicalProdHost ? serverUrl.port : "";
|
|
13857
|
+
const wsPort = explicitPort || inheritedServerPort;
|
|
13858
|
+
const wsUrl = wsPort ? `${wsProtocol}//${wsHostname}:${wsPort}` : `${wsProtocol}//${wsHostname}`;
|
|
13859
|
+
return {
|
|
13860
|
+
wsUrl,
|
|
13861
|
+
source: "derived",
|
|
13862
|
+
serverUrl: serverUrl.toString()
|
|
13863
|
+
};
|
|
13864
|
+
}
|
|
13865
|
+
|
|
13693
13866
|
// src/daemon/project-message-router.ts
|
|
13694
13867
|
var import_core19 = __toESM(require_dist());
|
|
13695
13868
|
var path28 = __toESM(require("path"));
|
|
@@ -14193,7 +14366,7 @@ function getBuildPackagesCommand2(installCmd) {
|
|
|
14193
14366
|
const runner = installCmd?.command?.[0];
|
|
14194
14367
|
switch (runner) {
|
|
14195
14368
|
case "pnpm":
|
|
14196
|
-
return
|
|
14369
|
+
return 'if command -v pnpm >/dev/null 2>&1; then pnpm run build:packages; elif command -v corepack >/dev/null 2>&1; then corepack pnpm run build:packages; else echo "[setup] ERROR: pnpm is not installed and corepack is unavailable" >&2; exit 127; fi';
|
|
14197
14370
|
case "yarn":
|
|
14198
14371
|
return "yarn build:packages";
|
|
14199
14372
|
case "npm":
|
|
@@ -14228,10 +14401,11 @@ var Daemon = class _Daemon {
|
|
|
14228
14401
|
// 2 minutes
|
|
14229
14402
|
// EP1360: Per-session monotonic event seq for daemon→platform stream gap detection.
|
|
14230
14403
|
this.agentEventSeq = /* @__PURE__ */ new Map();
|
|
14404
|
+
this.cliRuntimeVersion = resolveEffectiveCliVersion(packageJson.version);
|
|
14231
14405
|
this.ipcServer = new IPCServer();
|
|
14232
14406
|
this.connectionManager = new ConnectionManager();
|
|
14233
14407
|
this.ipcRouter = new IPCRouter(this, this.ipcServer);
|
|
14234
|
-
this.updateManager = new UpdateManager(this,
|
|
14408
|
+
this.updateManager = new UpdateManager(this, this.cliRuntimeVersion, __filename);
|
|
14235
14409
|
this.healthOrchestrator = new HealthOrchestrator(this);
|
|
14236
14410
|
this.daemonCore = new DaemonCore(this);
|
|
14237
14411
|
this.projectMessageRouter = new ProjectMessageRouter({
|
|
@@ -14496,23 +14670,15 @@ var Daemon = class _Daemon {
|
|
|
14496
14670
|
if (!config || !config.access_token) {
|
|
14497
14671
|
throw new Error("No access token found. Please run: episoda auth");
|
|
14498
14672
|
}
|
|
14499
|
-
|
|
14500
|
-
if (config.
|
|
14501
|
-
|
|
14502
|
-
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
|
|
14506
|
-
wsUrl = config.ws_url;
|
|
14507
|
-
console.log(`[Daemon] Using configured ws_url: ${wsUrl}`);
|
|
14508
|
-
} else {
|
|
14509
|
-
const serverUrlObj = new URL(serverUrl);
|
|
14510
|
-
const wsProtocol = serverUrlObj.protocol === "https:" ? "wss:" : "ws:";
|
|
14511
|
-
const wsHostname = serverUrlObj.hostname === "episoda.dev" ? "ws.episoda.dev" : serverUrlObj.hostname;
|
|
14512
|
-
const wsPort = process.env.EPISODA_WS_PORT;
|
|
14513
|
-
wsUrl = wsPort ? `${wsProtocol}//${wsHostname}:${wsPort}` : `${wsProtocol}//${wsHostname}`;
|
|
14673
|
+
const wsEndpoint = resolveDaemonWsEndpoint(config);
|
|
14674
|
+
if (wsEndpoint.source === "config.ws_url") {
|
|
14675
|
+
console.log(`[Daemon] Using configured ws_url: ${wsEndpoint.wsUrl}`);
|
|
14676
|
+
} else if (wsEndpoint.source === "env.EPISODA_WS_URL") {
|
|
14677
|
+
console.log(`[Daemon] Using EPISODA_WS_URL override: ${wsEndpoint.wsUrl}`);
|
|
14678
|
+
} else if (config.project_settings?.local_server_url) {
|
|
14679
|
+
console.log(`[Daemon] Using cached server URL: ${wsEndpoint.serverUrl}`);
|
|
14514
14680
|
}
|
|
14515
|
-
console.log(`[Daemon] Connecting to ${wsUrl} for project ${projectId}...`);
|
|
14681
|
+
console.log(`[Daemon] Connecting to ${wsEndpoint.wsUrl} for project ${projectId} (source: ${wsEndpoint.source})...`);
|
|
14516
14682
|
const client = new import_core22.EpisodaClient();
|
|
14517
14683
|
const gitExecutor = new import_core22.GitExecutor();
|
|
14518
14684
|
const connection = {
|
|
@@ -15006,12 +15172,15 @@ var Daemon = class _Daemon {
|
|
|
15006
15172
|
const modeConfig = getDaemonModeConfig();
|
|
15007
15173
|
const environment = modeConfig.mode;
|
|
15008
15174
|
const containerId = process.env.EPISODA_CONTAINER_ID;
|
|
15009
|
-
|
|
15175
|
+
const capabilities = ["worktree_create_v1"];
|
|
15176
|
+
await client.connect(wsEndpoint.wsUrl, config.access_token, this.machineId, {
|
|
15010
15177
|
hostname: os12.hostname(),
|
|
15011
15178
|
osPlatform: os12.platform(),
|
|
15012
15179
|
osArch: os12.arch(),
|
|
15013
15180
|
daemonPid,
|
|
15014
|
-
cliVersion:
|
|
15181
|
+
cliVersion: this.cliRuntimeVersion,
|
|
15182
|
+
cliPackageName: packageJson.name,
|
|
15183
|
+
capabilities,
|
|
15015
15184
|
environment,
|
|
15016
15185
|
containerId
|
|
15017
15186
|
});
|