Skip to content

Commit a577e94

Browse files
fix(server): delete temporary ACP health probe sessions (#25397)
Co-authored-by: Claude Opus 5.5 <[email protected]>
1 parent bc8583b commit a577e94

4 files changed

Lines changed: 69 additions & 0 deletions

File tree

‎apps/server/scripts/acp-mock-agent.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ const supportsSessionLifecycle = process.env.T3_ACP_SESSION_LIFECYCLE === "1";
8686
const supportsAcpMcp = process.env.T3_ACP_MCP_ACP === "1";
8787
const supportsHttpMcp = process.env.T3_ACP_MCP_HTTP === "1";
8888
const supportsV2Management = process.env.T3_ACP_V2_MANAGEMENT === "1";
89+
const failDeleteSession = process.env.T3_ACP_FAIL_DELETE_SESSION === "1";
8990
const advertisedAuthMethodId = process.env.T3_ACP_AUTH_METHOD_ID?.trim();
9091
const initializeAuthMethodId =
9192
advertisedAuthMethodId ?? (supportsSessionLifecycle ? "test" : undefined);
@@ -742,6 +743,9 @@ const program = Effect.gen(function* () {
742743
yield* agent.handleDeleteSession(() =>
743744
Effect.gen(function* () {
744745
yield* requireAuthentication();
746+
if (failDeleteSession) {
747+
return yield* AcpError.AcpRequestError.internalError("delete failed");
748+
}
745749
return {};
746750
}),
747751
);

‎apps/server/src/provider/AcpRegistryProbe.test.ts‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { ProviderInstanceId } from "@t3tools/contracts";
99
import { AcpRegistrySettings } from "@t3tools/provider-acp-registry/settings";
1010
import { describe, expect, it } from "@effect/vitest";
1111
import * as Effect from "effect/Effect";
12+
import * as FileSystem from "effect/FileSystem";
1213
import * as Schema from "effect/Schema";
1314
import * as EffectAcpErrors from "effect-acp/errors";
1415

@@ -20,6 +21,7 @@ import {
2021
logoutAcpRegistry,
2122
normalizeAcpRegistryAuthMethods,
2223
normalizeAcpRegistryCommands,
24+
probeAcpRegistryConfiguration,
2325
} from "@t3tools/provider-acp-registry/testing";
2426
import * as AcpRegistrySupport from "@t3tools/provider-acp-registry/server/AcpRegistrySupport";
2527

@@ -379,6 +381,60 @@ describe("ACP Registry probe", () => {
379381
);
380382
});
381383

384+
it.live.each(["succeeds", "fails"] as const)(
385+
"deletes its health-probe session when deletion is advertised and %s",
386+
(deleteOutcome) =>
387+
Effect.gen(function* () {
388+
const fs = yield* FileSystem.FileSystem;
389+
const directory = yield* fs.makeTempDirectoryScoped({ prefix: "acp-probe-delete-" });
390+
const requestLogPath = NodePath.join(directory, "requests.ndjson");
391+
const catalog = AcpRegistrySupport.AcpRegistryCatalog.of({
392+
search: () => Effect.die("unused search"),
393+
prepare: () => Effect.die("unused prepare"),
394+
inspect: () => Effect.die("unused inspect"),
395+
uninstallManagedBinary: () => Effect.die("unused uninstall"),
396+
resolve: () =>
397+
Effect.succeed({
398+
agent: {
399+
id: "mock-agent",
400+
name: "Mock Agent",
401+
version: "1.0.0",
402+
description: "ACP probe cleanup test agent",
403+
distribution: { npx: { package: "[email protected]" } },
404+
},
405+
distribution: "npx" as const,
406+
spawn: {
407+
command: "node",
408+
args: [mockAgentPath],
409+
env: {
410+
...process.env,
411+
T3_ACP_V2_MANAGEMENT: "1",
412+
T3_ACP_REQUEST_LOG_PATH: requestLogPath,
413+
...(deleteOutcome === "fails" ? { T3_ACP_FAIL_DELETE_SESSION: "1" } : {}),
414+
},
415+
},
416+
}),
417+
});
418+
419+
const result = yield* probeAcpRegistryConfiguration({
420+
instanceId,
421+
settings: decodeSettings({ agentId: "mock-agent" }),
422+
cwd: process.cwd(),
423+
environment: process.env,
424+
}).pipe(Effect.provideService(AcpRegistrySupport.AcpRegistryCatalog, catalog));
425+
expect(result.probe.ready).toBe(true);
426+
expect(result.probe.sessionManagement.canDelete).toBe(true);
427+
428+
const requests = (yield* fs.readFileString(requestLogPath))
429+
.split("\n")
430+
.filter((line) => line.length > 0)
431+
.map((line) => JSON.parse(line));
432+
expect(requests.filter((request) => request.method === "session/delete")).toEqual([
433+
expect.objectContaining({ params: { sessionId: "mock-session-1" } }),
434+
]);
435+
}).pipe(Effect.scoped, Effect.provide(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer))),
436+
);
437+
382438
it("distinguishes authentication failures from generic probe failures", () => {
383439
const advertised = normalizeAcpRegistryAuthMethods([
384440
{ id: "grok-login", name: "Log in with Grok" },

‎packages/provider-acp-registry/src/server/probe.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const MAX_COMMAND_LINE_LENGTH = 2_048;
4545
const PROBE_TIMEOUT_SECONDS = 60;
4646
const PROBE_TIMEOUT = `${PROBE_TIMEOUT_SECONDS} seconds`;
4747
const COMMAND_ADVERTISEMENT_GRACE = "500 millis";
48+
const PROBE_SESSION_DELETE_TIMEOUT = "5 seconds";
4849

4950
const boundedText = (value: string, maximumLength: number): string =>
5051
value.trim().slice(0, maximumLength);
@@ -419,6 +420,13 @@ export const probeAcpRegistryConfiguration = Effect.fn("AcpRegistryProbe.probeCo
419420
);
420421
}
421422
const started = startResult.success;
423+
// Agents that persist sessions (OpenCode) would otherwise keep one empty session per probe.
424+
// The runtime only sends session/delete when the agent advertises it.
425+
yield* Effect.addFinalizer(() =>
426+
runtime
427+
.deleteSession(started.sessionId)
428+
.pipe(Effect.timeoutOption(PROBE_SESSION_DELETE_TIMEOUT), Effect.ignore),
429+
);
422430
yield* Deferred.await(commandsAdvertised).pipe(
423431
Effect.timeoutOption(COMMAND_ADVERTISEMENT_GRACE),
424432
);

‎packages/provider-acp-registry/src/testing.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export {
1818
logoutAcpRegistry,
1919
normalizeAcpRegistryAuthMethods,
2020
normalizeAcpRegistryCommands,
21+
probeAcpRegistryConfiguration,
2122
type AcpRegistryAvailableCommands,
2223
type AcpRegistryLiveConfiguration,
2324
} from "./server/probe.ts";

0 commit comments

Comments
 (0)