mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 10:07:41 +00:00
fix: kill stuck ACP child processes on startup and harden sessions in discord threads (#33699)
* Gateway: resolve agent.wait for chat.send runs * Discord: harden ACP thread binding + listener timeout * ACPX: handle already-exited child wait * Gateway/Discord: address PR review findings * Discord: keep ACP error-state thread bindings on startup * gateway: make agent.wait dedupe bridge event-driven * discord: harden ACP probe classification and cap startup fan-out * discord: add cooperative timeout cancellation * discord: fix startup probe concurrency helper typing * plugin-sdk: avoid Windows root-alias shard timeout * plugin-sdk: keep root alias reflection path non-blocking * discord+gateway: resolve remaining PR review findings * gateway+discord: fix codex review regressions * Discord/Gateway: address Codex review findings * Gateway: keep agent.wait lifecycle active with shared run IDs * Discord: clean up status reactions on aborted runs * fix: add changelog note for ACP/Discord startup hardening (#33699) (thanks @dutifulbob) --------- Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
This commit is contained in:
@@ -316,70 +316,85 @@ export class AcpSessionManager {
|
||||
async getSessionStatus(params: {
|
||||
cfg: OpenClawConfig;
|
||||
sessionKey: string;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<AcpSessionStatus> {
|
||||
const sessionKey = normalizeSessionKey(params.sessionKey);
|
||||
if (!sessionKey) {
|
||||
throw new AcpRuntimeError("ACP_SESSION_INIT_FAILED", "ACP session key is required.");
|
||||
}
|
||||
this.throwIfAborted(params.signal);
|
||||
await this.evictIdleRuntimeHandles({ cfg: params.cfg });
|
||||
return await this.withSessionActor(sessionKey, async () => {
|
||||
const resolution = this.resolveSession({
|
||||
cfg: params.cfg,
|
||||
sessionKey,
|
||||
});
|
||||
if (resolution.kind === "none") {
|
||||
throw new AcpRuntimeError(
|
||||
"ACP_SESSION_INIT_FAILED",
|
||||
`Session is not ACP-enabled: ${sessionKey}`,
|
||||
);
|
||||
}
|
||||
if (resolution.kind === "stale") {
|
||||
throw resolution.error;
|
||||
}
|
||||
const {
|
||||
runtime,
|
||||
handle: ensuredHandle,
|
||||
meta: ensuredMeta,
|
||||
} = await this.ensureRuntimeHandle({
|
||||
cfg: params.cfg,
|
||||
sessionKey,
|
||||
meta: resolution.meta,
|
||||
});
|
||||
let handle = ensuredHandle;
|
||||
let meta = ensuredMeta;
|
||||
const capabilities = await this.resolveRuntimeCapabilities({ runtime, handle });
|
||||
let runtimeStatus: AcpRuntimeStatus | undefined;
|
||||
if (runtime.getStatus) {
|
||||
runtimeStatus = await withAcpRuntimeErrorBoundary({
|
||||
run: async () => await runtime.getStatus!({ handle }),
|
||||
fallbackCode: "ACP_TURN_FAILED",
|
||||
fallbackMessage: "Could not read ACP runtime status.",
|
||||
return await this.withSessionActor(
|
||||
sessionKey,
|
||||
async () => {
|
||||
this.throwIfAborted(params.signal);
|
||||
const resolution = this.resolveSession({
|
||||
cfg: params.cfg,
|
||||
sessionKey,
|
||||
});
|
||||
}
|
||||
({ handle, meta, runtimeStatus } = await this.reconcileRuntimeSessionIdentifiers({
|
||||
cfg: params.cfg,
|
||||
sessionKey,
|
||||
runtime,
|
||||
handle,
|
||||
meta,
|
||||
runtimeStatus,
|
||||
failOnStatusError: true,
|
||||
}));
|
||||
const identity = resolveSessionIdentityFromMeta(meta);
|
||||
return {
|
||||
sessionKey,
|
||||
backend: handle.backend || meta.backend,
|
||||
agent: meta.agent,
|
||||
...(identity ? { identity } : {}),
|
||||
state: meta.state,
|
||||
mode: meta.mode,
|
||||
runtimeOptions: resolveRuntimeOptionsFromMeta(meta),
|
||||
capabilities,
|
||||
runtimeStatus,
|
||||
lastActivityAt: meta.lastActivityAt,
|
||||
lastError: meta.lastError,
|
||||
};
|
||||
});
|
||||
if (resolution.kind === "none") {
|
||||
throw new AcpRuntimeError(
|
||||
"ACP_SESSION_INIT_FAILED",
|
||||
`Session is not ACP-enabled: ${sessionKey}`,
|
||||
);
|
||||
}
|
||||
if (resolution.kind === "stale") {
|
||||
throw resolution.error;
|
||||
}
|
||||
const {
|
||||
runtime,
|
||||
handle: ensuredHandle,
|
||||
meta: ensuredMeta,
|
||||
} = await this.ensureRuntimeHandle({
|
||||
cfg: params.cfg,
|
||||
sessionKey,
|
||||
meta: resolution.meta,
|
||||
});
|
||||
let handle = ensuredHandle;
|
||||
let meta = ensuredMeta;
|
||||
const capabilities = await this.resolveRuntimeCapabilities({ runtime, handle });
|
||||
let runtimeStatus: AcpRuntimeStatus | undefined;
|
||||
if (runtime.getStatus) {
|
||||
runtimeStatus = await withAcpRuntimeErrorBoundary({
|
||||
run: async () => {
|
||||
this.throwIfAborted(params.signal);
|
||||
const status = await runtime.getStatus!({
|
||||
handle,
|
||||
...(params.signal ? { signal: params.signal } : {}),
|
||||
});
|
||||
this.throwIfAborted(params.signal);
|
||||
return status;
|
||||
},
|
||||
fallbackCode: "ACP_TURN_FAILED",
|
||||
fallbackMessage: "Could not read ACP runtime status.",
|
||||
});
|
||||
}
|
||||
({ handle, meta, runtimeStatus } = await this.reconcileRuntimeSessionIdentifiers({
|
||||
cfg: params.cfg,
|
||||
sessionKey,
|
||||
runtime,
|
||||
handle,
|
||||
meta,
|
||||
runtimeStatus,
|
||||
failOnStatusError: true,
|
||||
}));
|
||||
const identity = resolveSessionIdentityFromMeta(meta);
|
||||
return {
|
||||
sessionKey,
|
||||
backend: handle.backend || meta.backend,
|
||||
agent: meta.agent,
|
||||
...(identity ? { identity } : {}),
|
||||
state: meta.state,
|
||||
mode: meta.mode,
|
||||
runtimeOptions: resolveRuntimeOptionsFromMeta(meta),
|
||||
capabilities,
|
||||
runtimeStatus,
|
||||
lastActivityAt: meta.lastActivityAt,
|
||||
lastError: meta.lastError,
|
||||
};
|
||||
},
|
||||
params.signal,
|
||||
);
|
||||
}
|
||||
|
||||
async setSessionRuntimeMode(params: {
|
||||
@@ -1295,9 +1310,23 @@ export class AcpSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async withSessionActor<T>(sessionKey: string, op: () => Promise<T>): Promise<T> {
|
||||
private async withSessionActor<T>(
|
||||
sessionKey: string,
|
||||
op: () => Promise<T>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<T> {
|
||||
const actorKey = normalizeActorKey(sessionKey);
|
||||
return await this.actorQueue.run(actorKey, op);
|
||||
return await this.actorQueue.run(actorKey, async () => {
|
||||
this.throwIfAborted(signal);
|
||||
return await op();
|
||||
});
|
||||
}
|
||||
|
||||
private throwIfAborted(signal?: AbortSignal): void {
|
||||
if (!signal?.aborted) {
|
||||
return;
|
||||
}
|
||||
throw new AcpRuntimeError("ACP_TURN_FAILED", "ACP operation aborted.");
|
||||
}
|
||||
|
||||
private getCachedRuntimeState(sessionKey: string): CachedRuntimeState | null {
|
||||
|
||||
@@ -117,7 +117,7 @@ export interface AcpRuntime {
|
||||
handle?: AcpRuntimeHandle;
|
||||
}): Promise<AcpRuntimeCapabilities> | AcpRuntimeCapabilities;
|
||||
|
||||
getStatus?(input: { handle: AcpRuntimeHandle }): Promise<AcpRuntimeStatus>;
|
||||
getStatus?(input: { handle: AcpRuntimeHandle; signal?: AbortSignal }): Promise<AcpRuntimeStatus>;
|
||||
|
||||
setMode?(input: { handle: AcpRuntimeHandle; mode: string }): Promise<void>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user