@episoda/cli 0.2.169 → 0.2.171
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.
|
@@ -2978,7 +2978,7 @@ var require_package = __commonJS({
|
|
|
2978
2978
|
"package.json"(exports2, module2) {
|
|
2979
2979
|
module2.exports = {
|
|
2980
2980
|
name: "@episoda/cli",
|
|
2981
|
-
version: "0.2.
|
|
2981
|
+
version: "0.2.171",
|
|
2982
2982
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2983
2983
|
main: "dist/index.js",
|
|
2984
2984
|
types: "dist/index.d.ts",
|
|
@@ -7928,10 +7928,12 @@ function getAgentCommandQueue() {
|
|
|
7928
7928
|
}
|
|
7929
7929
|
var AgentCommandQueue = class {
|
|
7930
7930
|
constructor() {
|
|
7931
|
-
/** Map of sessionId ->
|
|
7931
|
+
/** Map of sessionId -> FIFO commands */
|
|
7932
7932
|
this.pendingCommands = /* @__PURE__ */ new Map();
|
|
7933
7933
|
/** Maximum retry attempts before giving up */
|
|
7934
7934
|
this.maxRetries = 3;
|
|
7935
|
+
/** Bounded queue depth per session to avoid unbounded backlog */
|
|
7936
|
+
this.maxDepthPerSession = parseInt(process.env.AGENT_COMMAND_QUEUE_MAX_DEPTH || "8", 10);
|
|
7935
7937
|
/** Command timeout in ms (commands older than this are discarded) */
|
|
7936
7938
|
this.commandTimeout = 6e4;
|
|
7937
7939
|
// 1 minute
|
|
@@ -7943,25 +7945,36 @@ var AgentCommandQueue = class {
|
|
|
7943
7945
|
}
|
|
7944
7946
|
/**
|
|
7945
7947
|
* Add a command to the queue before processing.
|
|
7946
|
-
*
|
|
7948
|
+
* Commands are tracked in FIFO order per session.
|
|
7947
7949
|
*
|
|
7948
7950
|
* @param sessionId - Agent session ID
|
|
7949
7951
|
* @param command - The agent command to execute
|
|
7950
7952
|
* @param commandId - WebSocket message ID for response routing
|
|
7951
7953
|
*/
|
|
7952
7954
|
enqueue(sessionId, command, commandId) {
|
|
7953
|
-
const
|
|
7954
|
-
if (
|
|
7955
|
-
console.
|
|
7955
|
+
const queue = this.pendingCommands.get(sessionId) || [];
|
|
7956
|
+
if (queue.length >= this.maxDepthPerSession) {
|
|
7957
|
+
console.warn(
|
|
7958
|
+
`[AgentCommandQueue] EP1398: Queue full for session ${sessionId} (${queue.length}/${this.maxDepthPerSession})`
|
|
7959
|
+
);
|
|
7960
|
+
return {
|
|
7961
|
+
enqueued: false,
|
|
7962
|
+
queueDepth: queue.length,
|
|
7963
|
+
reason: "QUEUE_DEPTH_EXCEEDED"
|
|
7964
|
+
};
|
|
7956
7965
|
}
|
|
7957
|
-
|
|
7966
|
+
queue.push({
|
|
7958
7967
|
command,
|
|
7959
7968
|
commandId,
|
|
7960
7969
|
receivedAt: Date.now(),
|
|
7961
7970
|
retryCount: 0,
|
|
7962
7971
|
state: "pending"
|
|
7963
7972
|
});
|
|
7964
|
-
|
|
7973
|
+
this.pendingCommands.set(sessionId, queue);
|
|
7974
|
+
console.log(
|
|
7975
|
+
`[AgentCommandQueue] EP1237: Enqueued command for session ${sessionId} (action: ${command.action}, depth: ${queue.length})`
|
|
7976
|
+
);
|
|
7977
|
+
return { enqueued: true, queueDepth: queue.length };
|
|
7965
7978
|
}
|
|
7966
7979
|
/**
|
|
7967
7980
|
* Mark a command as being processed.
|
|
@@ -7969,11 +7982,11 @@ var AgentCommandQueue = class {
|
|
|
7969
7982
|
*
|
|
7970
7983
|
* @param sessionId - Agent session ID
|
|
7971
7984
|
*/
|
|
7972
|
-
markProcessing(sessionId) {
|
|
7973
|
-
const pending = this.
|
|
7985
|
+
markProcessing(sessionId, commandId) {
|
|
7986
|
+
const pending = this.getCommand(sessionId, commandId);
|
|
7974
7987
|
if (pending) {
|
|
7975
7988
|
pending.state = "processing";
|
|
7976
|
-
console.log(`[AgentCommandQueue] EP1237: Marked session ${sessionId} as processing`);
|
|
7989
|
+
console.log(`[AgentCommandQueue] EP1237: Marked session ${sessionId} command ${pending.commandId} as processing`);
|
|
7977
7990
|
}
|
|
7978
7991
|
}
|
|
7979
7992
|
/**
|
|
@@ -7981,12 +7994,8 @@ var AgentCommandQueue = class {
|
|
|
7981
7994
|
*
|
|
7982
7995
|
* @param sessionId - Agent session ID
|
|
7983
7996
|
*/
|
|
7984
|
-
complete(sessionId) {
|
|
7985
|
-
|
|
7986
|
-
if (pending) {
|
|
7987
|
-
console.log(`[AgentCommandQueue] EP1237: Completed command for session ${sessionId}`);
|
|
7988
|
-
this.pendingCommands.delete(sessionId);
|
|
7989
|
-
}
|
|
7997
|
+
complete(sessionId, commandId) {
|
|
7998
|
+
this.removeCommand(sessionId, commandId, "completed");
|
|
7990
7999
|
}
|
|
7991
8000
|
/**
|
|
7992
8001
|
* Mark a command as failed (will not be retried).
|
|
@@ -7994,13 +8003,13 @@ var AgentCommandQueue = class {
|
|
|
7994
8003
|
* @param sessionId - Agent session ID
|
|
7995
8004
|
* @param error - Error message for logging
|
|
7996
8005
|
*/
|
|
7997
|
-
fail(sessionId, error) {
|
|
7998
|
-
const pending = this.
|
|
8006
|
+
fail(sessionId, error, commandId) {
|
|
8007
|
+
const pending = this.getCommand(sessionId, commandId);
|
|
7999
8008
|
if (pending) {
|
|
8000
8009
|
pending.state = "failed";
|
|
8001
|
-
console.log(`[AgentCommandQueue] EP1237: Failed command for session ${sessionId}: ${error}`);
|
|
8002
|
-
this.pendingCommands.delete(sessionId);
|
|
8010
|
+
console.log(`[AgentCommandQueue] EP1237: Failed command for session ${sessionId} (${pending.commandId}): ${error}`);
|
|
8003
8011
|
}
|
|
8012
|
+
this.removeCommand(sessionId, commandId, "failed");
|
|
8004
8013
|
}
|
|
8005
8014
|
/**
|
|
8006
8015
|
* Check if a session has a pending command.
|
|
@@ -8009,7 +8018,7 @@ var AgentCommandQueue = class {
|
|
|
8009
8018
|
* @returns True if there's a pending or processing command
|
|
8010
8019
|
*/
|
|
8011
8020
|
has(sessionId) {
|
|
8012
|
-
return this.pendingCommands.
|
|
8021
|
+
return (this.pendingCommands.get(sessionId)?.length || 0) > 0;
|
|
8013
8022
|
}
|
|
8014
8023
|
/**
|
|
8015
8024
|
* Get a pending command by session ID.
|
|
@@ -8018,7 +8027,7 @@ var AgentCommandQueue = class {
|
|
|
8018
8027
|
* @returns The pending command or undefined
|
|
8019
8028
|
*/
|
|
8020
8029
|
get(sessionId) {
|
|
8021
|
-
return this.pendingCommands.get(sessionId);
|
|
8030
|
+
return this.pendingCommands.get(sessionId)?.[0];
|
|
8022
8031
|
}
|
|
8023
8032
|
/**
|
|
8024
8033
|
* Get all commands that need retry after reconnect.
|
|
@@ -8032,20 +8041,19 @@ var AgentCommandQueue = class {
|
|
|
8032
8041
|
getPendingCommands() {
|
|
8033
8042
|
const now = Date.now();
|
|
8034
8043
|
const retryable = [];
|
|
8035
|
-
for (const [sessionId,
|
|
8036
|
-
|
|
8037
|
-
continue;
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8044
|
+
for (const [sessionId, queue] of this.pendingCommands) {
|
|
8045
|
+
for (const pending of queue) {
|
|
8046
|
+
if (pending.state === "completed" || pending.state === "failed") continue;
|
|
8047
|
+
if (now - pending.receivedAt > this.commandTimeout) {
|
|
8048
|
+
console.log(`[AgentCommandQueue] EP1237: Command for session ${sessionId} timed out`);
|
|
8049
|
+
continue;
|
|
8050
|
+
}
|
|
8051
|
+
if (pending.retryCount >= this.maxRetries) {
|
|
8052
|
+
console.log(`[AgentCommandQueue] EP1237: Command for session ${sessionId} exceeded max retries`);
|
|
8053
|
+
continue;
|
|
8054
|
+
}
|
|
8055
|
+
retryable.push(pending);
|
|
8047
8056
|
}
|
|
8048
|
-
retryable.push(pending);
|
|
8049
8057
|
}
|
|
8050
8058
|
return retryable;
|
|
8051
8059
|
}
|
|
@@ -8055,13 +8063,20 @@ var AgentCommandQueue = class {
|
|
|
8055
8063
|
*
|
|
8056
8064
|
* @param sessionId - Agent session ID
|
|
8057
8065
|
*/
|
|
8058
|
-
incrementRetryCount(sessionId) {
|
|
8059
|
-
const pending = this.
|
|
8066
|
+
incrementRetryCount(sessionId, commandId) {
|
|
8067
|
+
const pending = this.getCommand(sessionId, commandId);
|
|
8060
8068
|
if (pending) {
|
|
8069
|
+
if (pending.retryCount >= this.maxRetries) {
|
|
8070
|
+
return false;
|
|
8071
|
+
}
|
|
8061
8072
|
pending.retryCount++;
|
|
8062
8073
|
pending.state = "pending";
|
|
8063
|
-
console.log(
|
|
8074
|
+
console.log(
|
|
8075
|
+
`[AgentCommandQueue] EP1237: Retry ${pending.retryCount}/${this.maxRetries} for session ${sessionId} (${pending.commandId})`
|
|
8076
|
+
);
|
|
8077
|
+
return true;
|
|
8064
8078
|
}
|
|
8079
|
+
return false;
|
|
8065
8080
|
}
|
|
8066
8081
|
/**
|
|
8067
8082
|
* Get commands that have exceeded the retry limit.
|
|
@@ -8071,9 +8086,11 @@ var AgentCommandQueue = class {
|
|
|
8071
8086
|
*/
|
|
8072
8087
|
getFailedCommands() {
|
|
8073
8088
|
const failed = [];
|
|
8074
|
-
for (const
|
|
8075
|
-
|
|
8076
|
-
|
|
8089
|
+
for (const queue of this.pendingCommands.values()) {
|
|
8090
|
+
for (const pending of queue) {
|
|
8091
|
+
if (pending.retryCount >= this.maxRetries && pending.state !== "completed" && pending.state !== "failed") {
|
|
8092
|
+
failed.push(pending);
|
|
8093
|
+
}
|
|
8077
8094
|
}
|
|
8078
8095
|
}
|
|
8079
8096
|
return failed;
|
|
@@ -8085,10 +8102,13 @@ var AgentCommandQueue = class {
|
|
|
8085
8102
|
cleanup() {
|
|
8086
8103
|
const now = Date.now();
|
|
8087
8104
|
let cleaned = 0;
|
|
8088
|
-
for (const [sessionId,
|
|
8089
|
-
|
|
8105
|
+
for (const [sessionId, queue] of this.pendingCommands) {
|
|
8106
|
+
const valid3 = queue.filter((pending) => now - pending.receivedAt <= this.commandTimeout);
|
|
8107
|
+
cleaned += queue.length - valid3.length;
|
|
8108
|
+
if (valid3.length === 0) {
|
|
8090
8109
|
this.pendingCommands.delete(sessionId);
|
|
8091
|
-
|
|
8110
|
+
} else {
|
|
8111
|
+
this.pendingCommands.set(sessionId, valid3);
|
|
8092
8112
|
}
|
|
8093
8113
|
}
|
|
8094
8114
|
if (cleaned > 0) {
|
|
@@ -8105,7 +8125,14 @@ var AgentCommandQueue = class {
|
|
|
8105
8125
|
* Get the current queue size.
|
|
8106
8126
|
*/
|
|
8107
8127
|
size() {
|
|
8108
|
-
|
|
8128
|
+
let total = 0;
|
|
8129
|
+
for (const queue of this.pendingCommands.values()) {
|
|
8130
|
+
total += queue.length;
|
|
8131
|
+
}
|
|
8132
|
+
return total;
|
|
8133
|
+
}
|
|
8134
|
+
getMaxDepthPerSession() {
|
|
8135
|
+
return this.maxDepthPerSession;
|
|
8109
8136
|
}
|
|
8110
8137
|
/**
|
|
8111
8138
|
* Shutdown the queue (stop cleanup interval).
|
|
@@ -8116,7 +8143,29 @@ var AgentCommandQueue = class {
|
|
|
8116
8143
|
clearInterval(this.cleanupInterval);
|
|
8117
8144
|
this.cleanupInterval = null;
|
|
8118
8145
|
}
|
|
8119
|
-
console.log(`[AgentCommandQueue] EP1237: Shutdown with ${this.
|
|
8146
|
+
console.log(`[AgentCommandQueue] EP1237: Shutdown with ${this.size()} pending command(s)`);
|
|
8147
|
+
}
|
|
8148
|
+
getCommand(sessionId, commandId) {
|
|
8149
|
+
const queue = this.pendingCommands.get(sessionId);
|
|
8150
|
+
if (!queue || queue.length === 0) return void 0;
|
|
8151
|
+
if (!commandId) return queue[0];
|
|
8152
|
+
return queue.find((pending) => pending.commandId === commandId);
|
|
8153
|
+
}
|
|
8154
|
+
removeCommand(sessionId, commandId, state) {
|
|
8155
|
+
const queue = this.pendingCommands.get(sessionId);
|
|
8156
|
+
if (!queue || queue.length === 0) return;
|
|
8157
|
+
const index = commandId ? queue.findIndex((pending) => pending.commandId === commandId) : 0;
|
|
8158
|
+
if (index < 0) return;
|
|
8159
|
+
const [removed] = queue.splice(index, 1);
|
|
8160
|
+
removed.state = state;
|
|
8161
|
+
console.log(
|
|
8162
|
+
`[AgentCommandQueue] EP1237: ${state} command for session ${sessionId} (${removed.commandId}), depth=${queue.length}`
|
|
8163
|
+
);
|
|
8164
|
+
if (queue.length === 0) {
|
|
8165
|
+
this.pendingCommands.delete(sessionId);
|
|
8166
|
+
} else {
|
|
8167
|
+
this.pendingCommands.set(sessionId, queue);
|
|
8168
|
+
}
|
|
8120
8169
|
}
|
|
8121
8170
|
};
|
|
8122
8171
|
|
|
@@ -14710,8 +14759,24 @@ var Daemon = class _Daemon {
|
|
|
14710
14759
|
client.updateActivity();
|
|
14711
14760
|
const commandQueue = getAgentCommandQueue();
|
|
14712
14761
|
if (shouldQueueAgentCommandForRetry(cmd.action)) {
|
|
14713
|
-
commandQueue.enqueue(cmd.sessionId, cmd, message.id);
|
|
14714
|
-
|
|
14762
|
+
const queueResult = commandQueue.enqueue(cmd.sessionId, cmd, message.id);
|
|
14763
|
+
if (!queueResult.enqueued) {
|
|
14764
|
+
const errorMessage = `TURN_IN_PROGRESS: Session queue is full (${queueResult.queueDepth}/${commandQueue.getMaxDepthPerSession()}). Please retry.`;
|
|
14765
|
+
await client.send({
|
|
14766
|
+
type: "agent_result",
|
|
14767
|
+
commandId: message.id,
|
|
14768
|
+
result: {
|
|
14769
|
+
success: false,
|
|
14770
|
+
status: "error",
|
|
14771
|
+
sessionId: cmd.sessionId,
|
|
14772
|
+
seq: this.nextAgentSeq(cmd.sessionId),
|
|
14773
|
+
error: errorMessage
|
|
14774
|
+
}
|
|
14775
|
+
});
|
|
14776
|
+
console.warn(`[Daemon] EP1398: Rejected command due to bounded queue depth for session ${cmd.sessionId}`);
|
|
14777
|
+
return;
|
|
14778
|
+
}
|
|
14779
|
+
commandQueue.markProcessing(cmd.sessionId, message.id);
|
|
14715
14780
|
}
|
|
14716
14781
|
let daemonChunkCount = 0;
|
|
14717
14782
|
let daemonToolUseCount = 0;
|
|
@@ -14792,7 +14857,7 @@ var Daemon = class _Daemon {
|
|
|
14792
14857
|
onComplete: async (claudeSessionId, resultMetadata) => {
|
|
14793
14858
|
const duration = Date.now() - daemonStreamStart;
|
|
14794
14859
|
console.log(`[Daemon] EP1191: Stream complete - ${daemonChunkCount} chunks, ${daemonToolUseCount} tool uses forwarded in ${duration}ms`);
|
|
14795
|
-
commandQueue.complete(sessionId);
|
|
14860
|
+
commandQueue.complete(sessionId, commandId);
|
|
14796
14861
|
try {
|
|
14797
14862
|
await client.send({
|
|
14798
14863
|
type: "agent_result",
|
|
@@ -14806,7 +14871,7 @@ var Daemon = class _Daemon {
|
|
|
14806
14871
|
onError: async (error) => {
|
|
14807
14872
|
const duration = Date.now() - daemonStreamStart;
|
|
14808
14873
|
console.log(`[Daemon] EP1191: Stream error after ${daemonChunkCount} chunks in ${duration}ms - ${error}`);
|
|
14809
|
-
commandQueue.complete(sessionId);
|
|
14874
|
+
commandQueue.complete(sessionId, commandId);
|
|
14810
14875
|
try {
|
|
14811
14876
|
await client.send({
|
|
14812
14877
|
type: "agent_result",
|
|
@@ -14922,7 +14987,7 @@ var Daemon = class _Daemon {
|
|
|
14922
14987
|
error: errorMsg
|
|
14923
14988
|
}
|
|
14924
14989
|
});
|
|
14925
|
-
commandQueue.complete(sessionId);
|
|
14990
|
+
commandQueue.complete(sessionId, message.id);
|
|
14926
14991
|
} catch (sendError) {
|
|
14927
14992
|
console.error(`[Daemon] EP1237: Failed to send error result (WebSocket disconnected), command will be retried on reconnect`);
|
|
14928
14993
|
}
|
|
@@ -15480,7 +15545,7 @@ var Daemon = class _Daemon {
|
|
|
15480
15545
|
error: `Command failed after ${pending.retryCount} retry attempts during WebSocket reconnection`
|
|
15481
15546
|
}
|
|
15482
15547
|
});
|
|
15483
|
-
commandQueue.fail(sessionId, "Max retries exceeded");
|
|
15548
|
+
commandQueue.fail(sessionId, "Max retries exceeded", pending.commandId);
|
|
15484
15549
|
} catch (sendError) {
|
|
15485
15550
|
console.error("[Daemon] EP1237: Failed to report max retry failure:", sendError);
|
|
15486
15551
|
}
|
|
@@ -15490,10 +15555,14 @@ var Daemon = class _Daemon {
|
|
|
15490
15555
|
const sessionId = cmd.sessionId || "unknown";
|
|
15491
15556
|
if (pending.retryCount >= commandQueue.getMaxRetries()) {
|
|
15492
15557
|
console.log(`[Daemon] EP1237: Command for session ${sessionId} will exceed max retries on next attempt`);
|
|
15493
|
-
commandQueue.fail(sessionId, "Max retries exceeded");
|
|
15558
|
+
commandQueue.fail(sessionId, "Max retries exceeded", pending.commandId);
|
|
15559
|
+
continue;
|
|
15560
|
+
}
|
|
15561
|
+
const incremented = commandQueue.incrementRetryCount(sessionId, pending.commandId);
|
|
15562
|
+
if (!incremented) {
|
|
15563
|
+
commandQueue.fail(sessionId, "Max retries exceeded", pending.commandId);
|
|
15494
15564
|
continue;
|
|
15495
15565
|
}
|
|
15496
|
-
commandQueue.incrementRetryCount(sessionId);
|
|
15497
15566
|
console.log(`[Daemon] EP1237: Command for session ${sessionId} ready for retry (attempt ${pending.retryCount + 1}/${commandQueue.getMaxRetries()})`);
|
|
15498
15567
|
}
|
|
15499
15568
|
}
|