@songsid/agend 2.1.3-beta.2 → 2.1.3-beta.4
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/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +39 -0
- package/dist/daemon.js +176 -2
- package/dist/daemon.js.map +1 -1
- package/dist/instance-lifecycle.js +6 -0
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/ui/view.html +131 -49
- package/dist/usage/providers.js +1 -1
- package/dist/usage/providers.js.map +1 -1
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -42,6 +42,7 @@ export const DEFAULT_DAILY_SUMMARY = {
|
|
|
42
42
|
export const DEFAULT_INSTANCE_CONFIG = {
|
|
43
43
|
auto_pause_after: 0, // minutes; 0 = disabled (opt-in)
|
|
44
44
|
mcp_auto_restart: true, // restart (idle-gated) when the MCP server dies
|
|
45
|
+
mcp_proxy_reply: false, // opt-in: relaying raw pane text can leak more than regex redaction catches
|
|
45
46
|
kiro_ui: "legacy",
|
|
46
47
|
terminal: {
|
|
47
48
|
enabled: true,
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGlF,SAAS,gBAAgB,CAAmB,MAAS,EAAE,MAAkB;IACvE,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,EAA6B,CAAC;IACxD,MAAM,YAAY,GAAG,MAAiC,CAAC;IAEvD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC5C,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC9B,IACE,SAAS,KAAK,IAAI;YAClB,OAAO,SAAS,KAAK,QAAQ;YAC7B,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;YACzB,OAAO,SAAS,KAAK,QAAQ;YAC7B,SAAS,KAAK,IAAI;YAClB,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EACzB,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAC5B,SAAmB,EACnB,SAA4B,CAC7B,CAAC;QACJ,CAAC;aAAM,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,MAAsB,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,kBAAkB,EAAE,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAoB;IACjD,eAAe,EAAE,CAAC,EAAE,eAAe;IACnC,kBAAkB,EAAE,EAAE;IACtB,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,eAAe,EAAE,CAAC,QAAQ;CAC3D,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAuB;IACvD,OAAO,EAAE,IAAI;IACb,eAAe,EAAE,EAAE;CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAuB;IACvD,OAAO,EAAE,IAAI;IACb,IAAI,EAAE,EAAE;IACR,MAAM,EAAE,CAAC;CACV,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAA8C;IAChF,gBAAgB,EAAE,CAAC,EAAE,iCAAiC;IACtD,gBAAgB,EAAE,IAAI,EAAE,gDAAgD;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon.d.ts
CHANGED
|
@@ -176,6 +176,23 @@ export declare class FirstDeliveryDelay {
|
|
|
176
176
|
}
|
|
177
177
|
/** Redact likely credentials and control sequences before pane text reaches logs. */
|
|
178
178
|
export declare function sanitizePaneTail(pane: string, lineCount?: number): string[];
|
|
179
|
+
/**
|
|
180
|
+
* Distill a pane capture into text worth relaying as a proxy reply.
|
|
181
|
+
*
|
|
182
|
+
* Used when the MCP server died and the turn ended with no reply: the agent's
|
|
183
|
+
* final answer exists only on screen. Everything up to and including the last
|
|
184
|
+
* line of the inbound message we pasted is cut (the reply starts after it),
|
|
185
|
+
* lines with no letters or digits are dropped (borders, separators, spinners,
|
|
186
|
+
* bare prompts), and the ready-prompt line is dropped by pattern. Returns null
|
|
187
|
+
* when what remains is trivial — a proxy message must carry an answer, not
|
|
188
|
+
* chrome. Secrets are redacted by sanitizePaneTail, same as stuck diagnostics.
|
|
189
|
+
*/
|
|
190
|
+
export declare function extractProxyReplyText(pane: string, opts?: {
|
|
191
|
+
inboundMarker?: string;
|
|
192
|
+
readyPattern?: RegExp | null;
|
|
193
|
+
maxLines?: number;
|
|
194
|
+
maxChars?: number;
|
|
195
|
+
}): string | null;
|
|
179
196
|
export type InteractivePromptKind = "sudo_password" | "password" | "confirmation" | "press_enter";
|
|
180
197
|
export interface InteractivePromptDetection {
|
|
181
198
|
kind: InteractivePromptKind;
|
|
@@ -270,6 +287,11 @@ export declare class Daemon extends EventEmitter {
|
|
|
270
287
|
private instanceStateBusyPattern;
|
|
271
288
|
private instanceStateMonitorActive;
|
|
272
289
|
private statePollInFlight;
|
|
290
|
+
private turnHadInbound;
|
|
291
|
+
private turnOutboundDelivered;
|
|
292
|
+
private turnCorrelationId;
|
|
293
|
+
private turnInboundMarker;
|
|
294
|
+
private proxyReplySeq;
|
|
273
295
|
private autoPauseController;
|
|
274
296
|
private pauseRequested;
|
|
275
297
|
/**
|
|
@@ -443,6 +465,23 @@ export declare class Daemon extends EventEmitter {
|
|
|
443
465
|
/** Respawn the CLI in the preserved window and block until its prompt is ready. */
|
|
444
466
|
wake(timeoutMs?: number): Promise<void>;
|
|
445
467
|
private applyInstanceStateSnapshot;
|
|
468
|
+
/**
|
|
469
|
+
* An inbound message is in the CLI — the turn it starts owns the proxy-reply
|
|
470
|
+
* evidence. Called after deliverMessage confirms the paste, not on arrival: a
|
|
471
|
+
* message can arrive mid-turn and sit queued, and marking at arrival would let
|
|
472
|
+
* the PREVIOUS turn's idle edge consume (and reset) the new turn's state.
|
|
473
|
+
*/
|
|
474
|
+
private markTurnStarted;
|
|
475
|
+
/**
|
|
476
|
+
* Turn ended (busy→idle edge): if the MCP server is dead and none of the
|
|
477
|
+
* channel tools verifiably delivered anything this turn, the agent's answer is
|
|
478
|
+
* stranded on screen — relay it. The daemon's own IPC route to the fleet
|
|
479
|
+
* manager does not pass through the dead MCP server. Consuming the turn state
|
|
480
|
+
* here (edge-triggered, then reset) is what makes it at most once per turn.
|
|
481
|
+
*/
|
|
482
|
+
private maybeProxyReplyOnTurnEnd;
|
|
483
|
+
/** Relay the pane's final text to the channel, marked as a daemon proxy reply. */
|
|
484
|
+
private sendProxyReply;
|
|
446
485
|
private clearInstanceStateIdleTimer;
|
|
447
486
|
private clearInstanceStateStuckTimer;
|
|
448
487
|
private scheduleInstanceStateStuckDeadline;
|
package/dist/daemon.js
CHANGED
|
@@ -26,6 +26,9 @@ const CROSS_INSTANCE_TOOLS = new Set(["send_to_instance", "list_instances", "sta
|
|
|
26
26
|
const SCHEDULE_TOOLS = new Set(["create_schedule", "list_schedules", "update_schedule", "delete_schedule"]);
|
|
27
27
|
const DECISION_TOOLS = new Set(["post_decision", "list_decisions", "update_decision"]);
|
|
28
28
|
const TASK_TOOL = "task";
|
|
29
|
+
// Tools whose success proves the agent got a message out this turn. While any
|
|
30
|
+
// of these succeeded, a dead-MCP proxy reply would double-post — suppress it.
|
|
31
|
+
const TURN_REPLY_TOOLS = new Set(["reply", "send_to_instance", "report_result", "request_information", "delegate_task", "broadcast"]);
|
|
29
32
|
/** Point a resumed CLI at its one backend-native instruction source. */
|
|
30
33
|
export function buildInstructionReloadNotice(binaryName, instanceName, instanceDir) {
|
|
31
34
|
const source = binaryName === "codex" || binaryName === "grok"
|
|
@@ -412,6 +415,51 @@ export function sanitizePaneTail(pane, lineCount = 5) {
|
|
|
412
415
|
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "")
|
|
413
416
|
.slice(0, 200));
|
|
414
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Distill a pane capture into text worth relaying as a proxy reply.
|
|
420
|
+
*
|
|
421
|
+
* Used when the MCP server died and the turn ended with no reply: the agent's
|
|
422
|
+
* final answer exists only on screen. Everything up to and including the last
|
|
423
|
+
* line of the inbound message we pasted is cut (the reply starts after it),
|
|
424
|
+
* lines with no letters or digits are dropped (borders, separators, spinners,
|
|
425
|
+
* bare prompts), and the ready-prompt line is dropped by pattern. Returns null
|
|
426
|
+
* when what remains is trivial — a proxy message must carry an answer, not
|
|
427
|
+
* chrome. Secrets are redacted by sanitizePaneTail, same as stuck diagnostics.
|
|
428
|
+
*/
|
|
429
|
+
export function extractProxyReplyText(pane, opts = {}) {
|
|
430
|
+
const lines = sanitizePaneTail(pane, opts.maxLines ?? 40);
|
|
431
|
+
const marker = opts.inboundMarker?.trim();
|
|
432
|
+
// Require a distinctive marker: a short one ("ok") would match agent text.
|
|
433
|
+
if (marker && marker.length >= 8) {
|
|
434
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
435
|
+
if (lines[i].includes(marker)) {
|
|
436
|
+
lines.splice(0, i + 1);
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const ready = opts.readyPattern
|
|
442
|
+
? new RegExp(opts.readyPattern.source, opts.readyPattern.flags.replace(/[gy]/g, ""))
|
|
443
|
+
: null;
|
|
444
|
+
const kept = lines.filter(line => {
|
|
445
|
+
const t = line.trim();
|
|
446
|
+
if (!t)
|
|
447
|
+
return true; // keep paragraph breaks; collapsed below
|
|
448
|
+
if (!/[\p{L}\p{N}]/u.test(t))
|
|
449
|
+
return false;
|
|
450
|
+
if (ready && ready.test(line))
|
|
451
|
+
return false;
|
|
452
|
+
return true;
|
|
453
|
+
});
|
|
454
|
+
const text = kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
455
|
+
// Trivial = nothing an operator could read as an answer.
|
|
456
|
+
const meaningful = text.replace(/[^\p{L}\p{N}]/gu, "");
|
|
457
|
+
if (meaningful.length < 2)
|
|
458
|
+
return null;
|
|
459
|
+
const maxChars = opts.maxChars ?? 3000;
|
|
460
|
+
// The end of the turn is the answer — when over budget, keep the tail.
|
|
461
|
+
return text.length > maxChars ? text.slice(text.length - maxChars) : text;
|
|
462
|
+
}
|
|
415
463
|
const INTERACTIVE_PROMPT_PATTERNS = [
|
|
416
464
|
{ kind: "sudo_password", pattern: /^\s*\[sudo\]\s+password\s+for\s+[^:\n]+:\s*$/im },
|
|
417
465
|
// macOS sudo and several package installers use this exact no-echo prompt.
|
|
@@ -553,6 +601,15 @@ export class Daemon extends EventEmitter {
|
|
|
553
601
|
instanceStateBusyPattern = null;
|
|
554
602
|
instanceStateMonitorActive = false;
|
|
555
603
|
statePollInFlight = false;
|
|
604
|
+
// ── Dead-MCP proxy reply: per-turn evidence ─────────────────────────────
|
|
605
|
+
// Set when an inbound message lands in the CLI; cleared on the idle edge that
|
|
606
|
+
// ends the turn. With a dead MCP server and no successful channel tool call in
|
|
607
|
+
// between, the agent's answer exists only on screen — the daemon relays it.
|
|
608
|
+
turnHadInbound = false;
|
|
609
|
+
turnOutboundDelivered = false;
|
|
610
|
+
turnCorrelationId;
|
|
611
|
+
turnInboundMarker;
|
|
612
|
+
proxyReplySeq = 0;
|
|
556
613
|
autoPauseController;
|
|
557
614
|
pauseRequested = false;
|
|
558
615
|
/**
|
|
@@ -1882,6 +1939,9 @@ export class Daemon extends EventEmitter {
|
|
|
1882
1939
|
// tool_result (interrupted, crashed, cancelled), which would otherwise leave
|
|
1883
1940
|
// the last tool pinned to the progress line for the rest of the session.
|
|
1884
1941
|
this.publishActivity(null);
|
|
1942
|
+
// Must run before the mcpRestartPending branch below: the pane text is the
|
|
1943
|
+
// only copy of the answer, and the revival restart is about to clear it.
|
|
1944
|
+
this.maybeProxyReplyOnTurnEnd(pane);
|
|
1885
1945
|
}
|
|
1886
1946
|
if (snapshot.state !== previous) {
|
|
1887
1947
|
this.logger.info({
|
|
@@ -1914,6 +1974,113 @@ export class Daemon extends EventEmitter {
|
|
|
1914
1974
|
this.emit("auto_pause_requested", { name: this.name, idleSince: snapshot.stateChangedAt });
|
|
1915
1975
|
}
|
|
1916
1976
|
}
|
|
1977
|
+
/**
|
|
1978
|
+
* An inbound message is in the CLI — the turn it starts owns the proxy-reply
|
|
1979
|
+
* evidence. Called after deliverMessage confirms the paste, not on arrival: a
|
|
1980
|
+
* message can arrive mid-turn and sit queued, and marking at arrival would let
|
|
1981
|
+
* the PREVIOUS turn's idle edge consume (and reset) the new turn's state.
|
|
1982
|
+
*/
|
|
1983
|
+
markTurnStarted(meta, deliveredText) {
|
|
1984
|
+
// Channel turns only. A cross-instance inbound (from_instance, empty
|
|
1985
|
+
// chat_id) does not update lastChatId, so its proxy reply would land in
|
|
1986
|
+
// whatever USER topic spoke to this instance last — the wrong audience for
|
|
1987
|
+
// a task result, and a stale one (sol's review of #515).
|
|
1988
|
+
if (meta.from_instance || !meta.chat_id)
|
|
1989
|
+
return;
|
|
1990
|
+
this.turnHadInbound = true;
|
|
1991
|
+
this.turnOutboundDelivered = false;
|
|
1992
|
+
this.turnCorrelationId = meta.correlation_id || undefined;
|
|
1993
|
+
// The last non-empty line of what we pasted: everything on screen after it
|
|
1994
|
+
// is the agent's own output.
|
|
1995
|
+
this.turnInboundMarker = deliveredText.split(/\r?\n/).map(l => l.trim()).filter(Boolean).pop();
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Turn ended (busy→idle edge): if the MCP server is dead and none of the
|
|
1999
|
+
* channel tools verifiably delivered anything this turn, the agent's answer is
|
|
2000
|
+
* stranded on screen — relay it. The daemon's own IPC route to the fleet
|
|
2001
|
+
* manager does not pass through the dead MCP server. Consuming the turn state
|
|
2002
|
+
* here (edge-triggered, then reset) is what makes it at most once per turn.
|
|
2003
|
+
*/
|
|
2004
|
+
maybeProxyReplyOnTurnEnd(pane) {
|
|
2005
|
+
const hadInbound = this.turnHadInbound;
|
|
2006
|
+
const delivered = this.turnOutboundDelivered;
|
|
2007
|
+
const correlationId = this.turnCorrelationId;
|
|
2008
|
+
const inboundMarker = this.turnInboundMarker;
|
|
2009
|
+
this.turnHadInbound = false;
|
|
2010
|
+
this.turnOutboundDelivered = false;
|
|
2011
|
+
this.turnCorrelationId = undefined;
|
|
2012
|
+
this.turnInboundMarker = undefined;
|
|
2013
|
+
if (!hadInbound || delivered || this.isPaused)
|
|
2014
|
+
return;
|
|
2015
|
+
// Opt-in: raw pane text can carry secrets past the regex redaction.
|
|
2016
|
+
if (this.config.mcp_proxy_reply !== true)
|
|
2017
|
+
return;
|
|
2018
|
+
// "dead" only: unknown means not started or exited cleanly — never proxy on it.
|
|
2019
|
+
if (mcpServerState(this.instanceDir).state !== "dead")
|
|
2020
|
+
return;
|
|
2021
|
+
void this.sendProxyReply(pane, inboundMarker, correlationId);
|
|
2022
|
+
}
|
|
2023
|
+
/** Relay the pane's final text to the channel, marked as a daemon proxy reply. */
|
|
2024
|
+
async sendProxyReply(pane, inboundMarker, correlationId) {
|
|
2025
|
+
try {
|
|
2026
|
+
// The idle capture normally hands its pane in; fall back only when the
|
|
2027
|
+
// edge came from a path without one.
|
|
2028
|
+
if (pane === undefined)
|
|
2029
|
+
pane = await this.tmux?.capturePane();
|
|
2030
|
+
if (!pane)
|
|
2031
|
+
return;
|
|
2032
|
+
const text = extractProxyReplyText(pane, { inboundMarker, readyPattern: this.instanceStateReadyPattern });
|
|
2033
|
+
if (!text) {
|
|
2034
|
+
this.logger.debug("Dead-MCP proxy reply skipped — pane tail is trivial");
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
let body = `⚠️ [MCP unavailable — proxy reply]\n\n${text}`;
|
|
2038
|
+
if (correlationId)
|
|
2039
|
+
body += `\n\n(correlation_id: ${correlationId})`;
|
|
2040
|
+
const args = { text: body };
|
|
2041
|
+
// Same context-bound routing as the reply tool in handleToolCall.
|
|
2042
|
+
if (this.lastChatId) {
|
|
2043
|
+
args.chat_id = this.lastChatId;
|
|
2044
|
+
if (this.lastThreadId)
|
|
2045
|
+
args.thread_id = this.lastThreadId;
|
|
2046
|
+
}
|
|
2047
|
+
this.logger.warn({ correlationId }, "MCP server dead and the turn sent no reply — relaying the pane text as a proxy reply");
|
|
2048
|
+
this.emit("mcp_proxy_reply", { name: this.name, correlationId });
|
|
2049
|
+
const adapters = this.messageBus.getAllAdapters();
|
|
2050
|
+
if (adapters.length > 0) {
|
|
2051
|
+
routeToolCall(adapters[0], "reply", args, this.lastThreadId, (_result, error) => {
|
|
2052
|
+
if (error)
|
|
2053
|
+
this.logger.error({ error }, "Dead-MCP proxy reply failed");
|
|
2054
|
+
});
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
if (!this.ipcServer)
|
|
2058
|
+
return;
|
|
2059
|
+
const fleetReqId = `proxyreply_${++this.proxyReplySeq}`;
|
|
2060
|
+
this.ipcServer.broadcast({
|
|
2061
|
+
type: "fleet_outbound",
|
|
2062
|
+
tool: "reply",
|
|
2063
|
+
args,
|
|
2064
|
+
fleetRequestId: fleetReqId,
|
|
2065
|
+
adapterId: this.lastAdapterId,
|
|
2066
|
+
});
|
|
2067
|
+
const timeout = setTimeout(() => {
|
|
2068
|
+
this.pendingIpcRequests.delete(fleetReqId);
|
|
2069
|
+
this.logger.error("Dead-MCP proxy reply timed out waiting for the fleet manager");
|
|
2070
|
+
}, daemonBudgetMs("reply"));
|
|
2071
|
+
timeout.unref?.();
|
|
2072
|
+
this.pendingIpcRequests.set(fleetReqId, (respMsg) => {
|
|
2073
|
+
clearTimeout(timeout);
|
|
2074
|
+
if (respMsg.error)
|
|
2075
|
+
this.logger.error({ error: respMsg.error }, "Dead-MCP proxy reply failed");
|
|
2076
|
+
else
|
|
2077
|
+
this.logger.info("Dead-MCP proxy reply delivered");
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
catch (err) {
|
|
2081
|
+
this.logger.error({ err: err.message }, "Dead-MCP proxy reply attempt failed");
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
1917
2084
|
clearInstanceStateIdleTimer() {
|
|
1918
2085
|
if (this.instanceStateIdleTimer)
|
|
1919
2086
|
clearTimeout(this.instanceStateIdleTimer);
|
|
@@ -2282,7 +2449,8 @@ export class Daemon extends EventEmitter {
|
|
|
2282
2449
|
const rawText = content.slice(5);
|
|
2283
2450
|
this.logger.info({ user }, "Raw paste from topic mode user");
|
|
2284
2451
|
this.pasteLock = this.pasteLock.then(async () => {
|
|
2285
|
-
await this.deliverMessage(rawText)
|
|
2452
|
+
if (await this.deliverMessage(rawText))
|
|
2453
|
+
this.markTurnStarted(meta, rawText);
|
|
2286
2454
|
}).catch(err => {
|
|
2287
2455
|
this.logger.warn({ err: err.message }, "pasteLock raw delivery error");
|
|
2288
2456
|
});
|
|
@@ -2343,7 +2511,8 @@ export class Daemon extends EventEmitter {
|
|
|
2343
2511
|
const status = (chatId && messageId)
|
|
2344
2512
|
? { chatId: meta.thread_id || chatId, messageId }
|
|
2345
2513
|
: undefined;
|
|
2346
|
-
await this.deliverMessage(formatted, status)
|
|
2514
|
+
if (await this.deliverMessage(formatted, status))
|
|
2515
|
+
this.markTurnStarted(meta, formatted);
|
|
2347
2516
|
}
|
|
2348
2517
|
finally {
|
|
2349
2518
|
this.pasteQueueDepth--;
|
|
@@ -2664,6 +2833,11 @@ export class Daemon extends EventEmitter {
|
|
|
2664
2833
|
this.logger.debug({ tool, requestId }, "Tool call from MCP server");
|
|
2665
2834
|
// For now, log and respond. Full adapter routing will be wired in fleet manager.
|
|
2666
2835
|
const respond = (result, error) => {
|
|
2836
|
+
// A message that verifiably went out stands down the dead-MCP proxy reply
|
|
2837
|
+
// for this turn: the agent proved it can still speak for itself.
|
|
2838
|
+
if (!error && result != null && TURN_REPLY_TOOLS.has(tool)) {
|
|
2839
|
+
this.turnOutboundDelivered = true;
|
|
2840
|
+
}
|
|
2667
2841
|
this.ipcServer?.send(socket, { requestId, result, error });
|
|
2668
2842
|
};
|
|
2669
2843
|
// Repo checkout — handled locally in daemon (no fleet-manager)
|