@agentconnect.md/daemon 1.0.0-rc.139 → 1.0.0-rc.140
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/index.js +81 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7383,6 +7383,8 @@ const ConfigSchema = object({
|
|
|
7383
7383
|
idleSweepMs: number().int().default(6e4),
|
|
7384
7384
|
shutdownDrainMs: number().int().default(25e3),
|
|
7385
7385
|
cancelBackstopMs: number().int().default(3e4),
|
|
7386
|
+
agentStartAttempts: number().int().min(1).default(3),
|
|
7387
|
+
agentStartBackoffMs: number().int().min(0).default(500),
|
|
7386
7388
|
maxAttachmentBytes: number().int().default(8 * 1024 * 1024)
|
|
7387
7389
|
}).default({
|
|
7388
7390
|
maxAgents: 8,
|
|
@@ -7391,6 +7393,8 @@ const ConfigSchema = object({
|
|
|
7391
7393
|
idleSweepMs: 6e4,
|
|
7392
7394
|
shutdownDrainMs: 25e3,
|
|
7393
7395
|
cancelBackstopMs: 3e4,
|
|
7396
|
+
agentStartAttempts: 3,
|
|
7397
|
+
agentStartBackoffMs: 500,
|
|
7394
7398
|
maxAttachmentBytes: 8 * 1024 * 1024
|
|
7395
7399
|
})
|
|
7396
7400
|
});
|
|
@@ -13651,7 +13655,8 @@ const FRAME_SCHEMAS = {
|
|
|
13651
13655
|
usage: object({
|
|
13652
13656
|
used: number().int().optional(),
|
|
13653
13657
|
cost: number().optional()
|
|
13654
|
-
}).optional()
|
|
13658
|
+
}).optional(),
|
|
13659
|
+
error: string().optional()
|
|
13655
13660
|
}),
|
|
13656
13661
|
"webchat/close": object({ conversationId: string().uuid() }),
|
|
13657
13662
|
"route/assign": RouteAssign,
|
|
@@ -85020,6 +85025,26 @@ var Daemon = class Daemon {
|
|
|
85020
85025
|
for (const cpRule of this.cpRouting?.effectiveRules() ?? []) if (!this.resolveCpAgent(cpRule.agentId)) out.add(cpRule.agentId);
|
|
85021
85026
|
return [...out];
|
|
85022
85027
|
}
|
|
85028
|
+
/**
|
|
85029
|
+
* Surface a turn that failed before producing a reply (agent couldn't start,
|
|
85030
|
+
* ACP handshake/prompt rejected) to whichever transport owns it. Without this
|
|
85031
|
+
* the failure is invisible: Slack keeps its "is thinking…" status with no
|
|
85032
|
+
* message, and a webchat client never receives a terminal `webchat/done` so its
|
|
85033
|
+
* UI spins forever. Best-effort — a dead Slack connection or absent CP client
|
|
85034
|
+
* just means the (already logged) error goes unshown, never a second throw.
|
|
85035
|
+
*/
|
|
85036
|
+
surfaceTurnFailure(err, ctx) {
|
|
85037
|
+
const reason = err?.message ?? String(err);
|
|
85038
|
+
if (ctx.webchat) this.cpClient?.emitWebchatDone({
|
|
85039
|
+
conversationId: ctx.webchat.conversationId,
|
|
85040
|
+
turnId: ctx.webchat.turnId,
|
|
85041
|
+
error: reason
|
|
85042
|
+
});
|
|
85043
|
+
else if (ctx.replyConn) {
|
|
85044
|
+
if (ctx.statusThread) ctx.replyConn.setStatus(ctx.channel, ctx.statusThread, "");
|
|
85045
|
+
ctx.replyConn.postMessage(ctx.channel, `⚠️ Agent failed to respond: ${reason}`, ctx.thread);
|
|
85046
|
+
}
|
|
85047
|
+
}
|
|
85023
85048
|
async dispatch(agentId, msg, integrationId, webchat) {
|
|
85024
85049
|
if (this.draining || this.drainingAgents.has(agentId)) {
|
|
85025
85050
|
this.log.debug(`dispatch: skipped for agent "${agentId}" (draining)`);
|
|
@@ -85037,6 +85062,13 @@ var Daemon = class Daemon {
|
|
|
85037
85062
|
handled = await this.sessions.handle(agentId, msg);
|
|
85038
85063
|
} catch (err) {
|
|
85039
85064
|
this.store.setSessionState(key, "idle", this.clock.now());
|
|
85065
|
+
this.surfaceTurnFailure(err, {
|
|
85066
|
+
webchat,
|
|
85067
|
+
replyConn,
|
|
85068
|
+
channel: msg.channel,
|
|
85069
|
+
thread: msg.thread,
|
|
85070
|
+
statusThread
|
|
85071
|
+
});
|
|
85040
85072
|
throw err;
|
|
85041
85073
|
}
|
|
85042
85074
|
const { sessionId, blocks } = handled;
|
|
@@ -85109,6 +85141,16 @@ var Daemon = class Daemon {
|
|
|
85109
85141
|
}
|
|
85110
85142
|
for (const ev of rec.onFinal()) this.recordEvent(agentId, msg.channel, statusThread, ev);
|
|
85111
85143
|
await p.applyChain;
|
|
85144
|
+
} catch (err) {
|
|
85145
|
+
this.clearIdle(p);
|
|
85146
|
+
this.surfaceTurnFailure(err, {
|
|
85147
|
+
webchat,
|
|
85148
|
+
replyConn,
|
|
85149
|
+
channel: msg.channel,
|
|
85150
|
+
thread: msg.thread,
|
|
85151
|
+
statusThread
|
|
85152
|
+
});
|
|
85153
|
+
throw err;
|
|
85112
85154
|
} finally {
|
|
85113
85155
|
this.clearIdle(p);
|
|
85114
85156
|
this.pending.delete(sessionId);
|
|
@@ -85321,14 +85363,48 @@ var Daemon = class Daemon {
|
|
|
85321
85363
|
async ensureHostAsync(agentId) {
|
|
85322
85364
|
const stopping = this.hostStopping.get(agentId);
|
|
85323
85365
|
if (stopping) await stopping;
|
|
85324
|
-
const host = this.ensureHost(agentId, this.cfg);
|
|
85325
85366
|
let p = this.hostStarts.get(agentId);
|
|
85326
85367
|
if (!p) {
|
|
85327
|
-
p =
|
|
85368
|
+
p = this.startHostWithRetry(agentId);
|
|
85328
85369
|
this.hostStarts.set(agentId, p);
|
|
85370
|
+
p.catch(() => {
|
|
85371
|
+
if (this.hostStarts.get(agentId) === p) this.hostStarts.delete(agentId);
|
|
85372
|
+
});
|
|
85329
85373
|
}
|
|
85330
|
-
await p;
|
|
85331
|
-
|
|
85374
|
+
return await p;
|
|
85375
|
+
}
|
|
85376
|
+
/** Launch an agent's ACP host — spawn + the `initialize` handshake — retrying a
|
|
85377
|
+
* failed start up to `agentStartAttempts` times with a fixed backoff. Each attempt
|
|
85378
|
+
* builds a FRESH host; a failed start()'s half-spawned child is reaped before the
|
|
85379
|
+
* next try. On success the started host is left memoized in `this.hosts`; when every
|
|
85380
|
+
* attempt fails the last error is thrown (dispatch surfaces it to the session). */
|
|
85381
|
+
async startHostWithRetry(agentId) {
|
|
85382
|
+
const attempts = Math.max(1, this.cfg.limits.agentStartAttempts);
|
|
85383
|
+
let lastErr;
|
|
85384
|
+
for (let i = 1; i <= attempts; i++) {
|
|
85385
|
+
const host = this.ensureHost(agentId, this.cfg);
|
|
85386
|
+
try {
|
|
85387
|
+
await host.start();
|
|
85388
|
+
return host;
|
|
85389
|
+
} catch (err) {
|
|
85390
|
+
lastErr = err;
|
|
85391
|
+
this.hosts.delete(agentId);
|
|
85392
|
+
await host.stop().catch(() => {});
|
|
85393
|
+
if (i < attempts) {
|
|
85394
|
+
const backoff = this.cfg.limits.agentStartBackoffMs;
|
|
85395
|
+
this.log.warn(`acp: agent "${agentId}" start attempt ${i}/${attempts} failed (${err.message}) — retrying in ${backoff}ms`);
|
|
85396
|
+
await this.sleep(backoff);
|
|
85397
|
+
} else this.log.error(`acp: agent "${agentId}" failed to start after ${attempts} attempt(s): ${formatErr(err)}`);
|
|
85398
|
+
}
|
|
85399
|
+
}
|
|
85400
|
+
throw lastErr;
|
|
85401
|
+
}
|
|
85402
|
+
/** Clock-driven delay (so a FakeClock stays deterministic in tests). */
|
|
85403
|
+
sleep(ms) {
|
|
85404
|
+
const delay = Math.min(Math.max(0, ms), 2147483647);
|
|
85405
|
+
return new Promise((resolve) => {
|
|
85406
|
+
this.clock.setTimeout(() => resolve(), delay);
|
|
85407
|
+
});
|
|
85332
85408
|
}
|
|
85333
85409
|
replyConnFor(agentId, integrationId) {
|
|
85334
85410
|
const intId = integrationId ?? this.agents.get(agentId)?.integrations[0]?.id;
|