@episoda/cli 0.2.176 → 0.2.177
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.
|
@@ -3046,7 +3046,7 @@ var require_package = __commonJS({
|
|
|
3046
3046
|
"package.json"(exports2, module2) {
|
|
3047
3047
|
module2.exports = {
|
|
3048
3048
|
name: "@episoda/cli",
|
|
3049
|
-
version: "0.2.
|
|
3049
|
+
version: "0.2.177",
|
|
3050
3050
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
3051
3051
|
main: "dist/index.js",
|
|
3052
3052
|
types: "dist/index.d.ts",
|
|
@@ -15067,6 +15067,8 @@ var Daemon = class _Daemon {
|
|
|
15067
15067
|
// 2 minutes
|
|
15068
15068
|
// EP1360: Per-session monotonic event seq for daemon→platform stream gap detection.
|
|
15069
15069
|
this.agentEventSeq = /* @__PURE__ */ new Map();
|
|
15070
|
+
// sessionId -> last seq
|
|
15071
|
+
this.agentHeartbeatLoops = /* @__PURE__ */ new Map();
|
|
15070
15072
|
this.cliRuntimeVersion = resolveEffectiveCliVersion(packageJson.version);
|
|
15071
15073
|
this.ipcServer = new IPCServer();
|
|
15072
15074
|
this.connectionManager = new ConnectionManager();
|
|
@@ -15078,6 +15080,12 @@ var Daemon = class _Daemon {
|
|
|
15078
15080
|
handleWorktreeSetup: (command, projectPath, worktreeManager) => this.handleWorktreeSetup(command, projectPath, worktreeManager)
|
|
15079
15081
|
});
|
|
15080
15082
|
}
|
|
15083
|
+
static {
|
|
15084
|
+
this.AGENT_HEARTBEAT_INTERVAL_MS = 15e3;
|
|
15085
|
+
}
|
|
15086
|
+
static {
|
|
15087
|
+
this.AGENT_HEARTBEAT_REQUEST_TIMEOUT_MS = 12e3;
|
|
15088
|
+
}
|
|
15081
15089
|
static {
|
|
15082
15090
|
this.HEALTH_PORT = 9999;
|
|
15083
15091
|
}
|
|
@@ -15097,6 +15105,81 @@ var Daemon = class _Daemon {
|
|
|
15097
15105
|
this.agentEventSeq.set(sessionId, next);
|
|
15098
15106
|
return next;
|
|
15099
15107
|
}
|
|
15108
|
+
stopAgentHeartbeatLoop(sessionId) {
|
|
15109
|
+
const loop = this.agentHeartbeatLoops.get(sessionId);
|
|
15110
|
+
if (!loop) return;
|
|
15111
|
+
loop.stop();
|
|
15112
|
+
this.agentHeartbeatLoops.delete(sessionId);
|
|
15113
|
+
}
|
|
15114
|
+
async handleServerRequestedAgentStop(sessionId) {
|
|
15115
|
+
try {
|
|
15116
|
+
const agentManager = getAgentControlPlane();
|
|
15117
|
+
await agentManager.stopSession(sessionId);
|
|
15118
|
+
console.log(`[Daemon] EP1430: Stopped session ${sessionId} due to server continue=false`);
|
|
15119
|
+
} catch (error) {
|
|
15120
|
+
console.warn(
|
|
15121
|
+
`[Daemon] EP1430: Failed to stop session ${sessionId} after server continue=false:`,
|
|
15122
|
+
error
|
|
15123
|
+
);
|
|
15124
|
+
}
|
|
15125
|
+
}
|
|
15126
|
+
startAgentHeartbeatLoop(sessionId) {
|
|
15127
|
+
if (this.agentHeartbeatLoops.has(sessionId)) return;
|
|
15128
|
+
let stopped = false;
|
|
15129
|
+
let inFlight = false;
|
|
15130
|
+
let timer = null;
|
|
15131
|
+
const runHeartbeat = async () => {
|
|
15132
|
+
if (stopped || inFlight) return;
|
|
15133
|
+
inFlight = true;
|
|
15134
|
+
const abortController = new AbortController();
|
|
15135
|
+
const requestTimeout = setTimeout(() => {
|
|
15136
|
+
abortController.abort();
|
|
15137
|
+
}, _Daemon.AGENT_HEARTBEAT_REQUEST_TIMEOUT_MS);
|
|
15138
|
+
try {
|
|
15139
|
+
const config = await (0, import_core22.loadConfig)();
|
|
15140
|
+
if (!config?.access_token) return;
|
|
15141
|
+
const apiUrl = config.api_url || "https://episoda.dev";
|
|
15142
|
+
const response = await fetchWithAuth(
|
|
15143
|
+
`${apiUrl}/api/agent-sessions/${sessionId}/heartbeat`,
|
|
15144
|
+
{
|
|
15145
|
+
method: "POST",
|
|
15146
|
+
signal: abortController.signal
|
|
15147
|
+
}
|
|
15148
|
+
);
|
|
15149
|
+
if (!response.ok && response.status !== 409) {
|
|
15150
|
+
return;
|
|
15151
|
+
}
|
|
15152
|
+
const payload = await response.json().catch(() => null);
|
|
15153
|
+
const shouldContinue = response.status === 409 ? false : Boolean(payload?.data?.continue ?? payload?.continue ?? true);
|
|
15154
|
+
if (!shouldContinue) {
|
|
15155
|
+
await this.handleServerRequestedAgentStop(sessionId);
|
|
15156
|
+
this.stopAgentHeartbeatLoop(sessionId);
|
|
15157
|
+
}
|
|
15158
|
+
} catch (error) {
|
|
15159
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
15160
|
+
console.warn(
|
|
15161
|
+
`[Daemon] EP1430: Heartbeat request timed out for session ${sessionId} after ${_Daemon.AGENT_HEARTBEAT_REQUEST_TIMEOUT_MS}ms`
|
|
15162
|
+
);
|
|
15163
|
+
}
|
|
15164
|
+
} finally {
|
|
15165
|
+
clearTimeout(requestTimeout);
|
|
15166
|
+
inFlight = false;
|
|
15167
|
+
}
|
|
15168
|
+
};
|
|
15169
|
+
timer = setInterval(() => {
|
|
15170
|
+
void runHeartbeat();
|
|
15171
|
+
}, _Daemon.AGENT_HEARTBEAT_INTERVAL_MS);
|
|
15172
|
+
this.agentHeartbeatLoops.set(sessionId, {
|
|
15173
|
+
stop: () => {
|
|
15174
|
+
stopped = true;
|
|
15175
|
+
if (timer) {
|
|
15176
|
+
clearInterval(timer);
|
|
15177
|
+
timer = null;
|
|
15178
|
+
}
|
|
15179
|
+
}
|
|
15180
|
+
});
|
|
15181
|
+
void runHeartbeat();
|
|
15182
|
+
}
|
|
15100
15183
|
logReliabilityMetric(metric, fields) {
|
|
15101
15184
|
console.log(`[Daemon][Metric] ${JSON.stringify({
|
|
15102
15185
|
metric,
|
|
@@ -15500,6 +15583,7 @@ var Daemon = class _Daemon {
|
|
|
15500
15583
|
}
|
|
15501
15584
|
},
|
|
15502
15585
|
onComplete: async (claudeSessionId, resultMetadata) => {
|
|
15586
|
+
this.stopAgentHeartbeatLoop(sessionId);
|
|
15503
15587
|
const duration = Date.now() - daemonStreamStart;
|
|
15504
15588
|
console.log(`[Daemon] EP1191: Stream complete - ${daemonChunkCount} chunks, ${daemonToolUseCount} tool uses forwarded in ${duration}ms`);
|
|
15505
15589
|
commandQueue.complete(sessionId, commandId);
|
|
@@ -15514,6 +15598,7 @@ var Daemon = class _Daemon {
|
|
|
15514
15598
|
}
|
|
15515
15599
|
},
|
|
15516
15600
|
onError: async (error) => {
|
|
15601
|
+
this.stopAgentHeartbeatLoop(sessionId);
|
|
15517
15602
|
const duration = Date.now() - daemonStreamStart;
|
|
15518
15603
|
console.log(`[Daemon] EP1191: Stream error after ${daemonChunkCount} chunks in ${duration}ms - ${error}`);
|
|
15519
15604
|
commandQueue.complete(sessionId, commandId);
|
|
@@ -15533,6 +15618,7 @@ var Daemon = class _Daemon {
|
|
|
15533
15618
|
await agentManager.initialize();
|
|
15534
15619
|
let result;
|
|
15535
15620
|
if (cmd.action === "start") {
|
|
15621
|
+
this.startAgentHeartbeatLoop(cmd.sessionId);
|
|
15536
15622
|
const callbacks = createStreamingCallbacks(cmd.sessionId, message.id);
|
|
15537
15623
|
const agentWorkingDir = await resolveAgentWorkingDirectory({
|
|
15538
15624
|
command: cmd,
|
|
@@ -15573,6 +15659,7 @@ var Daemon = class _Daemon {
|
|
|
15573
15659
|
error: startResult.error
|
|
15574
15660
|
};
|
|
15575
15661
|
} else if (cmd.action === "message") {
|
|
15662
|
+
this.startAgentHeartbeatLoop(cmd.sessionId);
|
|
15576
15663
|
const callbacks = createStreamingCallbacks(cmd.sessionId, message.id);
|
|
15577
15664
|
const sendResult = await agentManager.sendMessage({
|
|
15578
15665
|
sessionId: cmd.sessionId,
|
|
@@ -15597,9 +15684,11 @@ var Daemon = class _Daemon {
|
|
|
15597
15684
|
error: sendResult.error
|
|
15598
15685
|
};
|
|
15599
15686
|
} else if (cmd.action === "abort") {
|
|
15687
|
+
this.stopAgentHeartbeatLoop(cmd.sessionId);
|
|
15600
15688
|
await agentManager.abortSession(cmd.sessionId);
|
|
15601
15689
|
result = { success: true, status: "aborted", sessionId: cmd.sessionId, seq: this.nextAgentSeq(cmd.sessionId) };
|
|
15602
15690
|
} else if (cmd.action === "stop") {
|
|
15691
|
+
this.stopAgentHeartbeatLoop(cmd.sessionId);
|
|
15603
15692
|
await agentManager.stopSession(cmd.sessionId);
|
|
15604
15693
|
result = { success: true, status: "complete", sessionId: cmd.sessionId, seq: this.nextAgentSeq(cmd.sessionId) };
|
|
15605
15694
|
this.agentEventSeq.delete(cmd.sessionId);
|
|
@@ -15611,6 +15700,9 @@ var Daemon = class _Daemon {
|
|
|
15611
15700
|
error: `Unknown agent action: ${cmd.action}`
|
|
15612
15701
|
};
|
|
15613
15702
|
}
|
|
15703
|
+
if (!result.success && result.sessionId) {
|
|
15704
|
+
this.stopAgentHeartbeatLoop(result.sessionId);
|
|
15705
|
+
}
|
|
15614
15706
|
await client.send({
|
|
15615
15707
|
type: "agent_result",
|
|
15616
15708
|
commandId: message.id,
|
|
@@ -15619,6 +15711,7 @@ var Daemon = class _Daemon {
|
|
|
15619
15711
|
console.log(`[Daemon] EP912: Agent command ${cmd.action} completed for session ${getAgentCommandSessionId(cmd)}`);
|
|
15620
15712
|
} catch (error) {
|
|
15621
15713
|
const sessionId = getAgentCommandSessionId(cmd);
|
|
15714
|
+
this.stopAgentHeartbeatLoop(sessionId);
|
|
15622
15715
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
15623
15716
|
try {
|
|
15624
15717
|
await client.send({
|