@episoda/cli 0.2.150 → 0.2.152
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 +152 -35
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -2604,7 +2604,7 @@ var require_auth = __commonJS({
|
|
|
2604
2604
|
};
|
|
2605
2605
|
})();
|
|
2606
2606
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
2607
|
-
exports2.getConfigDir =
|
|
2607
|
+
exports2.getConfigDir = getConfigDir9;
|
|
2608
2608
|
exports2.getConfigPath = getConfigPath;
|
|
2609
2609
|
exports2.loadConfig = loadConfig9;
|
|
2610
2610
|
exports2.saveConfig = saveConfig2;
|
|
@@ -2616,14 +2616,14 @@ var require_auth = __commonJS({
|
|
|
2616
2616
|
var DEFAULT_CONFIG_FILE = "config.json";
|
|
2617
2617
|
var hasWarnedMissingProjectId = false;
|
|
2618
2618
|
var hasWarnedMissingRequiredFields = false;
|
|
2619
|
-
function
|
|
2619
|
+
function getConfigDir9() {
|
|
2620
2620
|
return process.env.EPISODA_CONFIG_DIR || path25.join(os10.homedir(), ".episoda");
|
|
2621
2621
|
}
|
|
2622
2622
|
function getConfigPath(configPath) {
|
|
2623
2623
|
if (configPath) {
|
|
2624
2624
|
return configPath;
|
|
2625
2625
|
}
|
|
2626
|
-
return path25.join(
|
|
2626
|
+
return path25.join(getConfigDir9(), DEFAULT_CONFIG_FILE);
|
|
2627
2627
|
}
|
|
2628
2628
|
function ensureConfigDir(configPath) {
|
|
2629
2629
|
const dir = path25.dirname(configPath);
|
|
@@ -2913,7 +2913,7 @@ var require_package = __commonJS({
|
|
|
2913
2913
|
"package.json"(exports2, module2) {
|
|
2914
2914
|
module2.exports = {
|
|
2915
2915
|
name: "@episoda/cli",
|
|
2916
|
-
version: "0.2.
|
|
2916
|
+
version: "0.2.152",
|
|
2917
2917
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2918
2918
|
main: "dist/index.js",
|
|
2919
2919
|
types: "dist/index.d.ts",
|
|
@@ -3178,6 +3178,7 @@ var import_core3 = __toESM(require_dist());
|
|
|
3178
3178
|
function getPidFilePath() {
|
|
3179
3179
|
return path3.join((0, import_core3.getConfigDir)(), "daemon.pid");
|
|
3180
3180
|
}
|
|
3181
|
+
var MAX_LOG_SIZE_BYTES = 10 * 1024 * 1024;
|
|
3181
3182
|
|
|
3182
3183
|
// src/ipc/ipc-server.ts
|
|
3183
3184
|
var net = __toESM(require("net"));
|
|
@@ -9161,12 +9162,21 @@ var AgentManager = class {
|
|
|
9161
9162
|
* EP1173: Added autonomousMode parameter for permission-free execution
|
|
9162
9163
|
*/
|
|
9163
9164
|
async startSession(options) {
|
|
9164
|
-
const { sessionId, moduleId, moduleUid, projectId, projectPath, provider = "claude", autonomousMode = true, canWrite = true, readOnlyReason, mcpMode = "planning", message, credentials, systemPrompt, onChunk, onToolUse, onToolResult, onComplete, onError } = options;
|
|
9165
|
+
const { sessionId, moduleId, moduleUid, projectId, workspaceId, projectPath, provider = "claude", autonomousMode = true, canWrite = true, readOnlyReason, mcpMode = "planning", message, credentials, systemPrompt, onChunk, onToolUse, onToolResult, onComplete, onError } = options;
|
|
9165
9166
|
const normalizedMcpMode = this.normalizeMcpMode(mcpMode);
|
|
9167
|
+
const normalizeWorkspaceId = (id) => {
|
|
9168
|
+
const trimmed = id?.trim();
|
|
9169
|
+
return trimmed ? trimmed : void 0;
|
|
9170
|
+
};
|
|
9171
|
+
const requestedWorkspaceId = normalizeWorkspaceId(workspaceId);
|
|
9166
9172
|
const existingSession = this.sessions.get(sessionId);
|
|
9167
9173
|
if (existingSession) {
|
|
9168
9174
|
if (existingSession.provider === provider && existingSession.moduleId === moduleId) {
|
|
9169
9175
|
console.log(`[AgentManager] EP1232: Session ${sessionId} already exists, treating start as message (idempotent)`);
|
|
9176
|
+
if (requestedWorkspaceId && !normalizeWorkspaceId(existingSession.workspaceId)) {
|
|
9177
|
+
existingSession.workspaceId = requestedWorkspaceId;
|
|
9178
|
+
console.log(`[AgentManager] EP1357: Updated session ${sessionId} workspaceId from start options`);
|
|
9179
|
+
}
|
|
9170
9180
|
return this.sendMessage({
|
|
9171
9181
|
sessionId,
|
|
9172
9182
|
message,
|
|
@@ -9212,11 +9222,22 @@ var AgentManager = class {
|
|
|
9212
9222
|
error: error instanceof Error ? error.message : `${cliName} not available`
|
|
9213
9223
|
};
|
|
9214
9224
|
}
|
|
9225
|
+
let effectiveWorkspaceId = requestedWorkspaceId;
|
|
9226
|
+
if (!effectiveWorkspaceId) {
|
|
9227
|
+
try {
|
|
9228
|
+
const config = await (0, import_core11.loadConfig)();
|
|
9229
|
+
effectiveWorkspaceId = normalizeWorkspaceId(config?.workspace_id);
|
|
9230
|
+
} catch (error) {
|
|
9231
|
+
console.warn("[AgentManager] EP1357: Unable to resolve workspaceId from local config:", error instanceof Error ? error.message : error);
|
|
9232
|
+
}
|
|
9233
|
+
}
|
|
9215
9234
|
const session = {
|
|
9216
9235
|
sessionId,
|
|
9217
9236
|
moduleId,
|
|
9218
9237
|
moduleUid,
|
|
9219
9238
|
projectId,
|
|
9239
|
+
workspaceId: effectiveWorkspaceId,
|
|
9240
|
+
// EP1357: Store workspace ID for MCP server env
|
|
9220
9241
|
projectPath,
|
|
9221
9242
|
provider,
|
|
9222
9243
|
// EP1133: Store provider in session
|
|
@@ -9426,6 +9447,9 @@ If changes are needed, explain what needs to be done.`;
|
|
|
9426
9447
|
EPISODA_PROJECT_ID: session.projectId,
|
|
9427
9448
|
DEV_ENVIRONMENT_ID: devEnvId
|
|
9428
9449
|
};
|
|
9450
|
+
if (session.workspaceId) {
|
|
9451
|
+
episodaMcpEnv.EPISODA_WORKSPACE_ID = session.workspaceId;
|
|
9452
|
+
}
|
|
9429
9453
|
if (sessionToken) {
|
|
9430
9454
|
episodaMcpEnv.EPISODA_SESSION_TOKEN = sessionToken;
|
|
9431
9455
|
}
|
|
@@ -9576,6 +9600,9 @@ If changes are needed, explain what needs to be done.`;
|
|
|
9576
9600
|
EPISODA_PROJECT_ID: session.projectId || "",
|
|
9577
9601
|
DEV_ENVIRONMENT_ID: devEnvId
|
|
9578
9602
|
};
|
|
9603
|
+
if (session.workspaceId) {
|
|
9604
|
+
env.EPISODA_WORKSPACE_ID = session.workspaceId;
|
|
9605
|
+
}
|
|
9579
9606
|
if (sessionToken) {
|
|
9580
9607
|
env.EPISODA_SESSION_TOKEN = sessionToken;
|
|
9581
9608
|
}
|
|
@@ -9659,6 +9686,9 @@ If changes are needed, explain what needs to be done.`;
|
|
|
9659
9686
|
MODULE_UID: session.moduleUid,
|
|
9660
9687
|
EPISODA_PROJECT_ID: session.projectId
|
|
9661
9688
|
};
|
|
9689
|
+
if (session.workspaceId) {
|
|
9690
|
+
envVars.EPISODA_WORKSPACE_ID = session.workspaceId;
|
|
9691
|
+
}
|
|
9662
9692
|
if (provider === "codex") {
|
|
9663
9693
|
envVars.CODEX_HOME = sessionCodexDir;
|
|
9664
9694
|
console.log(`[AgentManager] EP1260: Set CODEX_HOME=${sessionCodexDir}`);
|
|
@@ -10377,7 +10407,7 @@ var path21 = __toESM(require("path"));
|
|
|
10377
10407
|
var MAX_RESTART_ATTEMPTS = 5;
|
|
10378
10408
|
var INITIAL_RESTART_DELAY_MS = 2e3;
|
|
10379
10409
|
var MAX_RESTART_DELAY_MS = 3e4;
|
|
10380
|
-
var
|
|
10410
|
+
var MAX_LOG_SIZE_BYTES2 = 5 * 1024 * 1024;
|
|
10381
10411
|
var NODE_MEMORY_LIMIT_MB = 2048;
|
|
10382
10412
|
var activeServers = /* @__PURE__ */ new Map();
|
|
10383
10413
|
function getLogsDir() {
|
|
@@ -10394,7 +10424,7 @@ function rotateLogIfNeeded(logPath) {
|
|
|
10394
10424
|
try {
|
|
10395
10425
|
if (fs20.existsSync(logPath)) {
|
|
10396
10426
|
const stats = fs20.statSync(logPath);
|
|
10397
|
-
if (stats.size >
|
|
10427
|
+
if (stats.size > MAX_LOG_SIZE_BYTES2) {
|
|
10398
10428
|
const backupPath = `${logPath}.1`;
|
|
10399
10429
|
if (fs20.existsSync(backupPath)) {
|
|
10400
10430
|
fs20.unlinkSync(backupPath);
|
|
@@ -11174,8 +11204,8 @@ var Daemon = class _Daemon {
|
|
|
11174
11204
|
}
|
|
11175
11205
|
console.log(`[Daemon] EP1324: Update to ${targetVersion} installed, restarting daemon...`);
|
|
11176
11206
|
await this.shutdown();
|
|
11177
|
-
const { getConfigDir:
|
|
11178
|
-
const configDir =
|
|
11207
|
+
const { getConfigDir: getConfigDir9 } = require_dist();
|
|
11208
|
+
const configDir = getConfigDir9();
|
|
11179
11209
|
const logPath = path24.join(configDir, "daemon.log");
|
|
11180
11210
|
const logFd = fs23.openSync(logPath, "a");
|
|
11181
11211
|
const child = (0, import_child_process14.spawn)("node", [__filename], {
|
|
@@ -11470,16 +11500,29 @@ var Daemon = class _Daemon {
|
|
|
11470
11500
|
"Content-Type": "application/json",
|
|
11471
11501
|
"X-Project-Id": projectId
|
|
11472
11502
|
};
|
|
11473
|
-
const
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
|
|
11503
|
+
const controller = new AbortController();
|
|
11504
|
+
const timeoutId = setTimeout(() => controller.abort(), 8e3);
|
|
11505
|
+
try {
|
|
11506
|
+
const response = await fetch(`${config.api_url}/api/cli/status`, {
|
|
11507
|
+
headers,
|
|
11508
|
+
signal: controller.signal
|
|
11509
|
+
});
|
|
11510
|
+
if (response.ok) {
|
|
11511
|
+
const data = await response.json();
|
|
11512
|
+
serverConnected = data.connected === true;
|
|
11513
|
+
serverMachineId = data.machine_id || null;
|
|
11514
|
+
} else {
|
|
11515
|
+
serverError = `Server returned ${response.status}`;
|
|
11516
|
+
}
|
|
11517
|
+
} finally {
|
|
11518
|
+
clearTimeout(timeoutId);
|
|
11480
11519
|
}
|
|
11481
11520
|
} catch (err) {
|
|
11482
|
-
|
|
11521
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
11522
|
+
serverError = "Server verification timed out (8s)";
|
|
11523
|
+
} else {
|
|
11524
|
+
serverError = err instanceof Error ? err.message : "Network error";
|
|
11525
|
+
}
|
|
11483
11526
|
}
|
|
11484
11527
|
const machineMatch = serverMachineId === this.machineId;
|
|
11485
11528
|
return {
|
|
@@ -12068,6 +12111,8 @@ var Daemon = class _Daemon {
|
|
|
12068
12111
|
moduleId: cmd.moduleId,
|
|
12069
12112
|
moduleUid: cmd.moduleUid,
|
|
12070
12113
|
projectId: cmd.projectId,
|
|
12114
|
+
workspaceId: cmd.workspaceId,
|
|
12115
|
+
// EP1357: Pass workspace ID for MCP server auth
|
|
12071
12116
|
projectPath: agentWorkingDir,
|
|
12072
12117
|
provider: cmd.provider || "claude",
|
|
12073
12118
|
// EP1133: Multi-provider support
|
|
@@ -12381,23 +12426,6 @@ var Daemon = class _Daemon {
|
|
|
12381
12426
|
} catch (pidError) {
|
|
12382
12427
|
console.warn(`[Daemon] Could not read daemon PID:`, pidError instanceof Error ? pidError.message : pidError);
|
|
12383
12428
|
}
|
|
12384
|
-
const authSuccessPromise = new Promise((resolve4, reject) => {
|
|
12385
|
-
const AUTH_TIMEOUT = 3e4;
|
|
12386
|
-
const timeout = setTimeout(() => {
|
|
12387
|
-
reject(new Error("Authentication timeout after 30s - server may be under heavy load. Try again in a few seconds."));
|
|
12388
|
-
}, AUTH_TIMEOUT);
|
|
12389
|
-
const authHandler = () => {
|
|
12390
|
-
clearTimeout(timeout);
|
|
12391
|
-
resolve4();
|
|
12392
|
-
};
|
|
12393
|
-
client.once("auth_success", authHandler);
|
|
12394
|
-
const errorHandler = (message) => {
|
|
12395
|
-
clearTimeout(timeout);
|
|
12396
|
-
const errorMsg = message;
|
|
12397
|
-
reject(new Error(errorMsg.message || "Authentication failed"));
|
|
12398
|
-
};
|
|
12399
|
-
client.once("auth_error", errorHandler);
|
|
12400
|
-
});
|
|
12401
12429
|
const modeConfig = getDaemonModeConfig();
|
|
12402
12430
|
const environment = modeConfig.mode;
|
|
12403
12431
|
const containerId = process.env.EPISODA_CONTAINER_ID;
|
|
@@ -12410,7 +12438,36 @@ var Daemon = class _Daemon {
|
|
|
12410
12438
|
containerId
|
|
12411
12439
|
});
|
|
12412
12440
|
console.log(`[Daemon] Successfully connected to project ${projectId}`);
|
|
12413
|
-
|
|
12441
|
+
const AUTH_TIMEOUT = 3e4;
|
|
12442
|
+
await new Promise((resolve4, reject) => {
|
|
12443
|
+
let settled = false;
|
|
12444
|
+
const cleanup = () => {
|
|
12445
|
+
clearTimeout(timeout);
|
|
12446
|
+
client.off("auth_success", authHandler);
|
|
12447
|
+
client.off("auth_error", errorHandler);
|
|
12448
|
+
};
|
|
12449
|
+
const timeout = setTimeout(() => {
|
|
12450
|
+
if (settled) return;
|
|
12451
|
+
settled = true;
|
|
12452
|
+
cleanup();
|
|
12453
|
+
reject(new Error("Authentication timeout after 30s - server may be under heavy load. Try again in a few seconds."));
|
|
12454
|
+
}, AUTH_TIMEOUT);
|
|
12455
|
+
const authHandler = () => {
|
|
12456
|
+
if (settled) return;
|
|
12457
|
+
settled = true;
|
|
12458
|
+
cleanup();
|
|
12459
|
+
resolve4();
|
|
12460
|
+
};
|
|
12461
|
+
const errorHandler = (message) => {
|
|
12462
|
+
if (settled) return;
|
|
12463
|
+
settled = true;
|
|
12464
|
+
cleanup();
|
|
12465
|
+
const errorMsg = message;
|
|
12466
|
+
reject(new Error(errorMsg.message || "Authentication failed"));
|
|
12467
|
+
};
|
|
12468
|
+
client.on("auth_success", authHandler);
|
|
12469
|
+
client.on("auth_error", errorHandler);
|
|
12470
|
+
});
|
|
12414
12471
|
console.log(`[Daemon] Authentication complete for project ${projectId}`);
|
|
12415
12472
|
} catch (error) {
|
|
12416
12473
|
console.error(`[Daemon] Failed to connect to ${projectId}:`, error);
|
|
@@ -13203,6 +13260,7 @@ var Daemon = class _Daemon {
|
|
|
13203
13260
|
this.healthCheckCounter = 0;
|
|
13204
13261
|
await this.auditWorktreesOnStartup();
|
|
13205
13262
|
}
|
|
13263
|
+
this.checkAndRotateLog();
|
|
13206
13264
|
} catch (error) {
|
|
13207
13265
|
console.error("[Daemon] EP929: Health check error:", error instanceof Error ? error.message : error);
|
|
13208
13266
|
} finally {
|
|
@@ -13220,6 +13278,50 @@ var Daemon = class _Daemon {
|
|
|
13220
13278
|
console.log("[Daemon] EP929: Health check polling stopped");
|
|
13221
13279
|
}
|
|
13222
13280
|
}
|
|
13281
|
+
static {
|
|
13282
|
+
/**
|
|
13283
|
+
* EP1351: Check daemon.log size and rotate if it exceeds the limit.
|
|
13284
|
+
*
|
|
13285
|
+
* Since the daemon's stdout/stderr are piped to daemon.log via an open FD,
|
|
13286
|
+
* we cannot simply rename the file (the FD follows the inode). Instead:
|
|
13287
|
+
* 1. Copy the current log to daemon.log.1 (shift existing rotated files)
|
|
13288
|
+
* 2. Truncate daemon.log in-place (the open FD continues writing to offset 0)
|
|
13289
|
+
*
|
|
13290
|
+
* This means a small window of duplicate data between .1 and the truncated file,
|
|
13291
|
+
* but that's acceptable for diagnostics logs.
|
|
13292
|
+
*/
|
|
13293
|
+
this.MAX_LOG_SIZE_BYTES = 10 * 1024 * 1024;
|
|
13294
|
+
}
|
|
13295
|
+
static {
|
|
13296
|
+
// 10 MB
|
|
13297
|
+
this.MAX_LOG_FILES = 3;
|
|
13298
|
+
}
|
|
13299
|
+
checkAndRotateLog() {
|
|
13300
|
+
try {
|
|
13301
|
+
const configDir = (0, import_core14.getConfigDir)();
|
|
13302
|
+
const logPath = path24.join(configDir, "daemon.log");
|
|
13303
|
+
if (!fs23.existsSync(logPath)) return;
|
|
13304
|
+
const stats = fs23.statSync(logPath);
|
|
13305
|
+
if (stats.size < _Daemon.MAX_LOG_SIZE_BYTES) return;
|
|
13306
|
+
console.log(`[Daemon] EP1351: Log rotation triggered (size: ${Math.round(stats.size / 1024 / 1024)}MB)`);
|
|
13307
|
+
const oldestPath = `${logPath}.${_Daemon.MAX_LOG_FILES}`;
|
|
13308
|
+
if (fs23.existsSync(oldestPath)) {
|
|
13309
|
+
fs23.unlinkSync(oldestPath);
|
|
13310
|
+
}
|
|
13311
|
+
for (let i = _Daemon.MAX_LOG_FILES - 1; i >= 1; i--) {
|
|
13312
|
+
const src = `${logPath}.${i}`;
|
|
13313
|
+
const dst = `${logPath}.${i + 1}`;
|
|
13314
|
+
if (fs23.existsSync(src)) {
|
|
13315
|
+
fs23.renameSync(src, dst);
|
|
13316
|
+
}
|
|
13317
|
+
}
|
|
13318
|
+
fs23.copyFileSync(logPath, `${logPath}.1`);
|
|
13319
|
+
fs23.truncateSync(logPath, 0);
|
|
13320
|
+
console.log("[Daemon] EP1351: Log rotated successfully");
|
|
13321
|
+
} catch (error) {
|
|
13322
|
+
console.warn("[Daemon] EP1351: Log rotation failed:", error instanceof Error ? error.message : error);
|
|
13323
|
+
}
|
|
13324
|
+
}
|
|
13223
13325
|
/**
|
|
13224
13326
|
* EP822: Clean up orphaned tunnels from previous daemon runs
|
|
13225
13327
|
* EP904: Enhanced to aggressively clean ALL cloudflared processes then restart
|
|
@@ -13742,6 +13844,21 @@ var Daemon = class _Daemon {
|
|
|
13742
13844
|
process.exit(0);
|
|
13743
13845
|
}
|
|
13744
13846
|
};
|
|
13847
|
+
var daemonStartedAt = Date.now();
|
|
13848
|
+
function logCrashDiagnostics(label, error) {
|
|
13849
|
+
const uptimeMs = Date.now() - daemonStartedAt;
|
|
13850
|
+
const mem = process.memoryUsage();
|
|
13851
|
+
console.error(`[Daemon] ${label}:`, error);
|
|
13852
|
+
console.error(`[Daemon] Crash diagnostics: uptime=${Math.round(uptimeMs / 1e3)}s, rss=${Math.round(mem.rss / 1024 / 1024)}MB, heap=${Math.round(mem.heapUsed / 1024 / 1024)}/${Math.round(mem.heapTotal / 1024 / 1024)}MB`);
|
|
13853
|
+
}
|
|
13854
|
+
process.on("unhandledRejection", (reason) => {
|
|
13855
|
+
logCrashDiagnostics("Unhandled rejection", reason);
|
|
13856
|
+
process.exit(1);
|
|
13857
|
+
});
|
|
13858
|
+
process.on("uncaughtException", (error) => {
|
|
13859
|
+
logCrashDiagnostics("Uncaught exception", error);
|
|
13860
|
+
process.exit(1);
|
|
13861
|
+
});
|
|
13745
13862
|
async function main() {
|
|
13746
13863
|
if (!process.env.EPISODA_DAEMON_MODE) {
|
|
13747
13864
|
console.error("This script should only be run by daemon-manager");
|