@songsid/agend 2.1.4-beta.32 → 2.1.4-beta.34
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/channel/adapters/discord.js +9 -7
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/ipc-bridge.d.ts +2 -1
- package/dist/channel/ipc-bridge.js +33 -4
- package/dist/channel/ipc-bridge.js.map +1 -1
- package/dist/channel/mcp-server.js +11 -2
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/config-validator.js +4 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +7 -0
- package/dist/daemon.js +62 -44
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.js +37 -9
- package/dist/fleet-manager.js.map +1 -1
- package/dist/reply-dedup.d.ts +7 -8
- package/dist/reply-dedup.js +0 -0
- package/dist/reply-dedup.js.map +1 -1
- package/dist/ui/settings.html +82 -11
- package/package.json +2 -2
package/dist/daemon.js
CHANGED
|
@@ -688,6 +688,13 @@ export class Daemon extends EventEmitter {
|
|
|
688
688
|
guardian = null;
|
|
689
689
|
adapter = null;
|
|
690
690
|
pendingIpcRequests = new Map();
|
|
691
|
+
/**
|
|
692
|
+
* Daemon-global request sequence. MCP request ids are only process-local:
|
|
693
|
+
* Claude can have more than one MCP child connected to the same daemon and
|
|
694
|
+
* each starts at 1, so using requestId alone lets one caller steal another's
|
|
695
|
+
* response.
|
|
696
|
+
*/
|
|
697
|
+
fleetRequestSeq = 0;
|
|
691
698
|
// Track chatId/threadId from inbound messages for automatic outbound routing
|
|
692
699
|
lastChatId;
|
|
693
700
|
lastThreadId;
|
|
@@ -3608,7 +3615,10 @@ export class Daemon extends EventEmitter {
|
|
|
3608
3615
|
if (!error && result != null && TURN_REPLY_TOOLS.has(tool)) {
|
|
3609
3616
|
this.turnOutboundDelivered = true;
|
|
3610
3617
|
}
|
|
3611
|
-
this.ipcServer?.send(socket, { requestId, result, error });
|
|
3618
|
+
const sent = this.ipcServer?.send(socket, { requestId, result, error }) ?? false;
|
|
3619
|
+
if (!sent) {
|
|
3620
|
+
this.logger.warn({ tool, requestId }, "Tool result could not be returned — originating MCP socket is closed");
|
|
3621
|
+
}
|
|
3612
3622
|
};
|
|
3613
3623
|
// Repo checkout — handled locally in daemon (no fleet-manager)
|
|
3614
3624
|
if (tool === "checkout_repo") {
|
|
@@ -3621,13 +3631,7 @@ export class Daemon extends EventEmitter {
|
|
|
3621
3631
|
}
|
|
3622
3632
|
if (tool === "set_display_name" || tool === "set_description") {
|
|
3623
3633
|
const type = tool === "set_display_name" ? "fleet_set_display_name" : "fleet_set_description";
|
|
3624
|
-
const fleetReqId = `${tool === "set_display_name" ? "dn" : "desc"}_${requestId}`;
|
|
3625
|
-
this.ipcServer?.broadcast({
|
|
3626
|
-
type,
|
|
3627
|
-
payload: args,
|
|
3628
|
-
meta: { instance_name: this.name },
|
|
3629
|
-
fleetRequestId: fleetReqId,
|
|
3630
|
-
});
|
|
3634
|
+
const fleetReqId = `${tool === "set_display_name" ? "dn" : "desc"}_${++this.fleetRequestSeq}_${requestId}`;
|
|
3631
3635
|
const timeout = setTimeout(() => {
|
|
3632
3636
|
this.pendingIpcRequests.delete(fleetReqId);
|
|
3633
3637
|
respond(null, `${tool} timed out`);
|
|
@@ -3636,16 +3640,16 @@ export class Daemon extends EventEmitter {
|
|
|
3636
3640
|
clearTimeout(timeout);
|
|
3637
3641
|
respond(respMsg.result, respMsg.error);
|
|
3638
3642
|
});
|
|
3639
|
-
return;
|
|
3640
|
-
}
|
|
3641
|
-
if (tool === TASK_TOOL) {
|
|
3642
|
-
const fleetReqId = `task_${requestId}`;
|
|
3643
3643
|
this.ipcServer?.broadcast({
|
|
3644
|
-
type
|
|
3644
|
+
type,
|
|
3645
3645
|
payload: args,
|
|
3646
3646
|
meta: { instance_name: this.name },
|
|
3647
3647
|
fleetRequestId: fleetReqId,
|
|
3648
3648
|
});
|
|
3649
|
+
return;
|
|
3650
|
+
}
|
|
3651
|
+
if (tool === TASK_TOOL) {
|
|
3652
|
+
const fleetReqId = `task_${++this.fleetRequestSeq}_${requestId}`;
|
|
3649
3653
|
const timeout = setTimeout(() => {
|
|
3650
3654
|
this.pendingIpcRequests.delete(fleetReqId);
|
|
3651
3655
|
respond(null, `Task operation timed out after ${daemonBudgetMs(tool) / 1000}s`);
|
|
@@ -3654,6 +3658,12 @@ export class Daemon extends EventEmitter {
|
|
|
3654
3658
|
clearTimeout(timeout);
|
|
3655
3659
|
respond(respMsg.result, respMsg.error);
|
|
3656
3660
|
});
|
|
3661
|
+
this.ipcServer?.broadcast({
|
|
3662
|
+
type: "fleet_task",
|
|
3663
|
+
payload: args,
|
|
3664
|
+
meta: { instance_name: this.name },
|
|
3665
|
+
fleetRequestId: fleetReqId,
|
|
3666
|
+
});
|
|
3657
3667
|
return;
|
|
3658
3668
|
}
|
|
3659
3669
|
if (DECISION_TOOLS.has(tool)) {
|
|
@@ -3662,13 +3672,7 @@ export class Daemon extends EventEmitter {
|
|
|
3662
3672
|
list_decisions: "fleet_decision_list",
|
|
3663
3673
|
update_decision: "fleet_decision_update",
|
|
3664
3674
|
};
|
|
3665
|
-
const fleetReqId = `dec_${requestId}`;
|
|
3666
|
-
this.ipcServer?.broadcast({
|
|
3667
|
-
type: typeMap[tool],
|
|
3668
|
-
payload: args,
|
|
3669
|
-
meta: { instance_name: this.name, working_directory: this.config.working_directory },
|
|
3670
|
-
fleetRequestId: fleetReqId,
|
|
3671
|
-
});
|
|
3675
|
+
const fleetReqId = `dec_${++this.fleetRequestSeq}_${requestId}`;
|
|
3672
3676
|
const timeout = setTimeout(() => {
|
|
3673
3677
|
this.pendingIpcRequests.delete(fleetReqId);
|
|
3674
3678
|
respond(null, `Decision operation timed out after ${daemonBudgetMs(tool) / 1000}s`);
|
|
@@ -3677,6 +3681,12 @@ export class Daemon extends EventEmitter {
|
|
|
3677
3681
|
clearTimeout(timeout);
|
|
3678
3682
|
respond(respMsg.result, respMsg.error);
|
|
3679
3683
|
});
|
|
3684
|
+
this.ipcServer?.broadcast({
|
|
3685
|
+
type: typeMap[tool],
|
|
3686
|
+
payload: args,
|
|
3687
|
+
meta: { instance_name: this.name, working_directory: this.config.working_directory },
|
|
3688
|
+
fleetRequestId: fleetReqId,
|
|
3689
|
+
});
|
|
3680
3690
|
return;
|
|
3681
3691
|
}
|
|
3682
3692
|
if (SCHEDULE_TOOLS.has(tool)) {
|
|
@@ -3688,13 +3698,7 @@ export class Daemon extends EventEmitter {
|
|
|
3688
3698
|
};
|
|
3689
3699
|
// Use fleetRequestId (not requestId) to avoid MCP server resolving the
|
|
3690
3700
|
// pending tool call prematurely when it receives the broadcast.
|
|
3691
|
-
const fleetReqId = `sched_${requestId}`;
|
|
3692
|
-
this.ipcServer?.broadcast({
|
|
3693
|
-
type: typeMap[tool],
|
|
3694
|
-
payload: args,
|
|
3695
|
-
meta: { chat_id: this.lastChatId, thread_id: this.lastThreadId, instance_name: this.name },
|
|
3696
|
-
fleetRequestId: fleetReqId,
|
|
3697
|
-
});
|
|
3701
|
+
const fleetReqId = `sched_${++this.fleetRequestSeq}_${requestId}`;
|
|
3698
3702
|
// Wait for fleet_schedule_response via pending request map
|
|
3699
3703
|
const timeout = setTimeout(() => {
|
|
3700
3704
|
this.pendingIpcRequests.delete(fleetReqId);
|
|
@@ -3704,6 +3708,12 @@ export class Daemon extends EventEmitter {
|
|
|
3704
3708
|
clearTimeout(timeout);
|
|
3705
3709
|
respond(respMsg.result, respMsg.error);
|
|
3706
3710
|
});
|
|
3711
|
+
this.ipcServer?.broadcast({
|
|
3712
|
+
type: typeMap[tool],
|
|
3713
|
+
payload: args,
|
|
3714
|
+
meta: { chat_id: this.lastChatId, thread_id: this.lastThreadId, instance_name: this.name },
|
|
3715
|
+
fleetRequestId: fleetReqId,
|
|
3716
|
+
});
|
|
3707
3717
|
return;
|
|
3708
3718
|
}
|
|
3709
3719
|
if (CROSS_INSTANCE_TOOLS.has(tool)) {
|
|
@@ -3711,15 +3721,8 @@ export class Daemon extends EventEmitter {
|
|
|
3711
3721
|
if (this.topicMode && this.ipcServer) {
|
|
3712
3722
|
// Use fleetRequestId (not requestId) to avoid MCP server resolving the
|
|
3713
3723
|
// pending tool call prematurely when it receives the broadcast.
|
|
3714
|
-
const fleetReqId = `xmsg_${requestId}`;
|
|
3724
|
+
const fleetReqId = `xmsg_${++this.fleetRequestSeq}_${requestId}`;
|
|
3715
3725
|
const senderSessionName = this.socketSessionNames.get(socket);
|
|
3716
|
-
this.ipcServer.broadcast({
|
|
3717
|
-
type: "fleet_outbound",
|
|
3718
|
-
tool,
|
|
3719
|
-
args,
|
|
3720
|
-
fleetRequestId: fleetReqId,
|
|
3721
|
-
senderSessionName,
|
|
3722
|
-
});
|
|
3723
3726
|
// Shared table, so delete_instance and the deployment tools get the same
|
|
3724
3727
|
// budget here as the MCP client expects (the ad-hoc list omitted them).
|
|
3725
3728
|
const crossTimeoutMs = daemonBudgetMs(tool);
|
|
@@ -3731,6 +3734,13 @@ export class Daemon extends EventEmitter {
|
|
|
3731
3734
|
clearTimeout(timeout);
|
|
3732
3735
|
respond(respMsg.result, respMsg.error);
|
|
3733
3736
|
});
|
|
3737
|
+
this.ipcServer.broadcast({
|
|
3738
|
+
type: "fleet_outbound",
|
|
3739
|
+
tool,
|
|
3740
|
+
args,
|
|
3741
|
+
fleetRequestId: fleetReqId,
|
|
3742
|
+
senderSessionName,
|
|
3743
|
+
});
|
|
3734
3744
|
}
|
|
3735
3745
|
else {
|
|
3736
3746
|
respond(null, "Cross-instance messaging requires topic mode");
|
|
@@ -3764,8 +3774,24 @@ export class Daemon extends EventEmitter {
|
|
|
3764
3774
|
// The fleet manager's IPC client receives this and routes to shared adapter.
|
|
3765
3775
|
// Use fleetRequestId (not requestId) to avoid other MCP sessions on this daemon
|
|
3766
3776
|
// from prematurely resolving their pending requests when they receive the broadcast.
|
|
3767
|
-
|
|
3777
|
+
// requestId is local to one MCP server process. Claude may keep multiple
|
|
3778
|
+
// MCP children attached (main session, resumed session, subagent); those
|
|
3779
|
+
// processes all emit requestId=1,2,... and previously overwrote this
|
|
3780
|
+
// shared pending map. The wrong caller could then receive another POST's
|
|
3781
|
+
// success + messageId while its own reply had never been confirmed.
|
|
3782
|
+
const fleetReqId = `tool_${++this.fleetRequestSeq}_${requestId}`;
|
|
3768
3783
|
const outboundKey = fleetReqId;
|
|
3784
|
+
const timeout = setTimeout(() => {
|
|
3785
|
+
this.pendingIpcRequests.delete(outboundKey);
|
|
3786
|
+
respond(null, `Fleet outbound timed out after ${daemonBudgetMs(tool) / 1000}s`);
|
|
3787
|
+
}, daemonBudgetMs(tool));
|
|
3788
|
+
this.pendingIpcRequests.set(outboundKey, (respMsg) => {
|
|
3789
|
+
clearTimeout(timeout);
|
|
3790
|
+
respond(respMsg.result, respMsg.error);
|
|
3791
|
+
});
|
|
3792
|
+
// Register the waiter before broadcasting. A fast/mock adapter can settle
|
|
3793
|
+
// immediately; broadcasting first left a response-before-registration
|
|
3794
|
+
// window that degraded into a timeout despite a completed POST.
|
|
3769
3795
|
this.ipcServer?.broadcast({
|
|
3770
3796
|
type: "fleet_outbound",
|
|
3771
3797
|
tool,
|
|
@@ -3776,14 +3802,6 @@ export class Daemon extends EventEmitter {
|
|
|
3776
3802
|
// from a secondary world, and message ids are not portable across bots.
|
|
3777
3803
|
adapterId: this.lastAdapterId,
|
|
3778
3804
|
});
|
|
3779
|
-
const timeout = setTimeout(() => {
|
|
3780
|
-
this.pendingIpcRequests.delete(outboundKey);
|
|
3781
|
-
respond(null, `Fleet outbound timed out after ${daemonBudgetMs(tool) / 1000}s`);
|
|
3782
|
-
}, daemonBudgetMs(tool));
|
|
3783
|
-
this.pendingIpcRequests.set(outboundKey, (respMsg) => {
|
|
3784
|
-
clearTimeout(timeout);
|
|
3785
|
-
respond(respMsg.result, respMsg.error);
|
|
3786
|
-
});
|
|
3787
3805
|
return;
|
|
3788
3806
|
}
|
|
3789
3807
|
const adapter = adapters[0];
|