tuna-agent 0.1.165 → 0.1.166
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/index.js +71 -39
- package/package.json +1 -1
package/dist/daemon/index.js
CHANGED
|
@@ -137,6 +137,7 @@ export async function startDaemon(config) {
|
|
|
137
137
|
const pendingPermissionResolvers = new Map();
|
|
138
138
|
// Track active tasks per agent (agentId → taskId)
|
|
139
139
|
const activeAgentTasks = new Map();
|
|
140
|
+
const agentQueues = new Map();
|
|
140
141
|
// Track abort controllers per task
|
|
141
142
|
const taskAbortControllers = new Map();
|
|
142
143
|
// Note: currentTaskId/currentTaskAbort removed — use taskAbortControllers + activeAgentTasks instead
|
|
@@ -218,41 +219,13 @@ export async function startDaemon(config) {
|
|
|
218
219
|
if (task.repoPath?.startsWith('~/')) {
|
|
219
220
|
task.repoPath = path.join(os.homedir(), task.repoPath.slice(2));
|
|
220
221
|
}
|
|
221
|
-
//
|
|
222
|
+
// Per-agent concurrency: if busy, queue instead of rejecting.
|
|
222
223
|
const agentId = task.agentId || '__default__';
|
|
223
224
|
if (activeAgentTasks.has(agentId)) {
|
|
224
|
-
|
|
225
|
-
ws.send({ action: 'task_rejected', taskId: task.id, reason: 'agent_busy' });
|
|
225
|
+
enqueueForAgent(agentId, { kind: 'task', task });
|
|
226
226
|
break;
|
|
227
227
|
}
|
|
228
|
-
|
|
229
|
-
activeAgentTasks.set(agentId, task.id);
|
|
230
|
-
const abort = new AbortController();
|
|
231
|
-
taskAbortControllers.set(task.id, abort);
|
|
232
|
-
console.log(`[Daemon] Received task: ${task.id} agent=${agentId} — ${task.description.slice(0, 80)} (attachments: ${task.attachments?.length ?? 0}) [active: ${activeTasks}]`);
|
|
233
|
-
// Run task in background (non-blocking) to allow parallel agent execution
|
|
234
|
-
(async () => {
|
|
235
|
-
try {
|
|
236
|
-
await adapter.handleTask(task, ws, pendingInputResolvers, abort.signal, pendingPermissionResolvers);
|
|
237
|
-
}
|
|
238
|
-
catch (err) {
|
|
239
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
240
|
-
if (abort.signal.aborted) {
|
|
241
|
-
console.log(`[Daemon] Task ${task.id} cancelled`);
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
ws.sendTaskFailed(task.id, errMsg);
|
|
245
|
-
console.error(`[Daemon] Task ${task.id} error:`, errMsg);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
finally {
|
|
249
|
-
activeTasks--;
|
|
250
|
-
activeAgentTasks.delete(agentId);
|
|
251
|
-
taskAbortControllers.delete(task.id);
|
|
252
|
-
pendingInputResolvers.delete(task.id);
|
|
253
|
-
ws.send({ action: 'agent_ready', agentId });
|
|
254
|
-
}
|
|
255
|
-
})();
|
|
228
|
+
runTaskNow(task);
|
|
256
229
|
break;
|
|
257
230
|
}
|
|
258
231
|
case 'task_cancelled': {
|
|
@@ -479,19 +452,15 @@ ${skillContent.slice(0, 15000)}`;
|
|
|
479
452
|
// No resolver — check if we have a persisted session to resume
|
|
480
453
|
const savedState = loadPMState(taskId);
|
|
481
454
|
if (savedState) {
|
|
482
|
-
//
|
|
455
|
+
// Per-agent concurrency: if busy, queue the reply instead of dropping it.
|
|
483
456
|
const resumeAgentId = savedState.agentId || '__default__';
|
|
484
457
|
if (activeAgentTasks.has(resumeAgentId)) {
|
|
485
458
|
const busyTask = activeAgentTasks.get(resumeAgentId);
|
|
486
|
-
console.
|
|
487
|
-
|
|
459
|
+
console.log(`[Daemon] Agent ${resumeAgentId} busy with ${busyTask} — queuing reply for task ${taskId}`);
|
|
460
|
+
enqueueForAgent(resumeAgentId, { kind: 'input', taskId, answer, attachments, savedState });
|
|
488
461
|
ws.sendPMMessage(taskId, {
|
|
489
462
|
sender: 'pm',
|
|
490
|
-
content: '
|
|
491
|
-
});
|
|
492
|
-
ws.sendTaskDone(taskId, {
|
|
493
|
-
result: 'Agent busy — message not processed',
|
|
494
|
-
durationMs: 0,
|
|
463
|
+
content: '⏳ Đang xử lý việc khác, sẽ trả lời ngay khi xong.',
|
|
495
464
|
});
|
|
496
465
|
break;
|
|
497
466
|
}
|
|
@@ -697,6 +666,67 @@ ${skillContent.slice(0, 15000)}`;
|
|
|
697
666
|
console.log(`[Daemon] Unknown message type: ${type}`);
|
|
698
667
|
}
|
|
699
668
|
}, onAuthFailed);
|
|
669
|
+
/** Queue work for an agent that is currently busy. */
|
|
670
|
+
function enqueueForAgent(agentId, item) {
|
|
671
|
+
let q = agentQueues.get(agentId);
|
|
672
|
+
if (!q) {
|
|
673
|
+
q = [];
|
|
674
|
+
agentQueues.set(agentId, q);
|
|
675
|
+
}
|
|
676
|
+
q.push(item);
|
|
677
|
+
console.log(`[Daemon] Agent ${agentId} busy — queued ${item.kind} (queue: ${q.length})`);
|
|
678
|
+
}
|
|
679
|
+
/** Start the next queued item for an agent, if any and the agent is free. */
|
|
680
|
+
function processNextForAgent(agentId) {
|
|
681
|
+
if (activeAgentTasks.has(agentId))
|
|
682
|
+
return; // still busy
|
|
683
|
+
const q = agentQueues.get(agentId);
|
|
684
|
+
if (!q || q.length === 0)
|
|
685
|
+
return;
|
|
686
|
+
const item = q.shift();
|
|
687
|
+
console.log(`[Daemon] Dequeue ${item.kind} for agent ${agentId} (remaining: ${q.length})`);
|
|
688
|
+
if (item.kind === 'task') {
|
|
689
|
+
runTaskNow(item.task);
|
|
690
|
+
}
|
|
691
|
+
else if (item.savedState.mode === 'agent_team') {
|
|
692
|
+
resumeAgentTeamChat(item.taskId, item.answer, item.attachments, item.savedState, ws, pendingInputResolvers);
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
resumePMChat(item.taskId, item.answer, item.attachments, item.savedState, ws, pendingInputResolvers);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
/** Run a task immediately (agent assumed free). Drains the queue when done. */
|
|
699
|
+
function runTaskNow(task) {
|
|
700
|
+
const agentId = task.agentId || '__default__';
|
|
701
|
+
activeTasks++;
|
|
702
|
+
activeAgentTasks.set(agentId, task.id);
|
|
703
|
+
const abort = new AbortController();
|
|
704
|
+
taskAbortControllers.set(task.id, abort);
|
|
705
|
+
console.log(`[Daemon] Received task: ${task.id} agent=${agentId} — ${task.description.slice(0, 80)} (attachments: ${task.attachments?.length ?? 0}) [active: ${activeTasks}]`);
|
|
706
|
+
(async () => {
|
|
707
|
+
try {
|
|
708
|
+
await adapter.handleTask(task, ws, pendingInputResolvers, abort.signal, pendingPermissionResolvers);
|
|
709
|
+
}
|
|
710
|
+
catch (err) {
|
|
711
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
712
|
+
if (abort.signal.aborted) {
|
|
713
|
+
console.log(`[Daemon] Task ${task.id} cancelled`);
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
ws.sendTaskFailed(task.id, errMsg);
|
|
717
|
+
console.error(`[Daemon] Task ${task.id} error:`, errMsg);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
finally {
|
|
721
|
+
activeTasks--;
|
|
722
|
+
activeAgentTasks.delete(agentId);
|
|
723
|
+
taskAbortControllers.delete(task.id);
|
|
724
|
+
pendingInputResolvers.delete(task.id);
|
|
725
|
+
ws.send({ action: 'agent_ready', agentId });
|
|
726
|
+
processNextForAgent(agentId);
|
|
727
|
+
}
|
|
728
|
+
})();
|
|
729
|
+
}
|
|
700
730
|
/**
|
|
701
731
|
* Resume PM chat for a task after agent restart or done task reopen.
|
|
702
732
|
* Loads persisted pmSessionId and runs a chat loop.
|
|
@@ -803,6 +833,7 @@ ${skillContent.slice(0, 15000)}`;
|
|
|
803
833
|
resolvers.delete(taskId);
|
|
804
834
|
cleanupAttachments(taskId);
|
|
805
835
|
wsClient.send({ action: 'agent_ready', agentId });
|
|
836
|
+
processNextForAgent(agentId);
|
|
806
837
|
}
|
|
807
838
|
}
|
|
808
839
|
/**
|
|
@@ -1049,6 +1080,7 @@ ${skillContent.slice(0, 15000)}`;
|
|
|
1049
1080
|
resolvers.delete(taskId);
|
|
1050
1081
|
cleanupAttachments(taskId);
|
|
1051
1082
|
wsClient.send({ action: 'agent_ready', agentId });
|
|
1083
|
+
processNextForAgent(agentId);
|
|
1052
1084
|
}
|
|
1053
1085
|
}
|
|
1054
1086
|
// Wire up agent metrics to heartbeat
|