diff --git a/apps/server/src/internal/session-owner-side-effects.ts b/apps/server/src/internal/session-owner-side-effects.ts index bc8fb94acc..65facec672 100644 --- a/apps/server/src/internal/session-owner-side-effects.ts +++ b/apps/server/src/internal/session-owner-side-effects.ts @@ -256,7 +256,7 @@ function completeDaemonActiveWorkDisconnectGrace( interruptActiveThreadsForHost(deps, { hostId: args.hostId, - reason: "host-daemon-restarted", + reason: "host-connection-lost", }); } diff --git a/apps/server/src/services/threads/thread-lifecycle.ts b/apps/server/src/services/threads/thread-lifecycle.ts index b937221579..21fccb8efc 100644 --- a/apps/server/src/services/threads/thread-lifecycle.ts +++ b/apps/server/src/services/threads/thread-lifecycle.ts @@ -340,6 +340,7 @@ function lifecycleEventForInterruptedThread( case "manual-stop": return { type: "stop.settled" }; case "host-daemon-restarted": + case "host-connection-lost": return { type: "run.failed" }; // Legacy persisted watchdog interruption; no current producer. Lands on // "error" like a lost session. @@ -358,6 +359,8 @@ function pendingInteractionStopReason( return "Thread stopped by user request"; case "host-daemon-restarted": return "Host daemon restarted while awaiting user interaction"; + case "host-connection-lost": + return "Connection to host was lost while awaiting user interaction"; // Legacy persisted watchdog interruption; no current producer. case "provider-turn-idle": return "Thread stopped after the provider stopped sending progress"; @@ -374,6 +377,8 @@ function threadCommandFailureMessageForInterruption( return null; case "host-daemon-restarted": return "Thread interrupted because the host daemon disconnected"; + case "host-connection-lost": + return "Thread interrupted because the connection to the host was lost"; // Legacy persisted watchdog interruption; no current producer. case "provider-turn-idle": return "Live runtime work failed because the provider stopped sending progress"; @@ -389,6 +394,7 @@ function threadCommandFailureDetailForInterruption( case "manual-stop": return "Thread stopped by user request"; case "host-daemon-restarted": + case "host-connection-lost": return "Please retry the thread to continue."; // Legacy persisted watchdog interruption; no current producer. case "provider-turn-idle": diff --git a/apps/server/test/internal/background-task-reconciliation.test.ts b/apps/server/test/internal/background-task-reconciliation.test.ts index 51abcb0862..b6f9e22ec4 100644 --- a/apps/server/test/internal/background-task-reconciliation.test.ts +++ b/apps/server/test/internal/background-task-reconciliation.test.ts @@ -503,7 +503,7 @@ describe("active thread disconnect reconciliation triggers", () => { }); }); - it("interrupts active turns when a different daemon instance registers", async () => { + it("records a confirmed daemon restart when a different daemon instance registers", async () => { await withTestHarness(async (harness) => { const { host, session, thread } = seedActiveTurnThread(harness); @@ -553,7 +553,7 @@ describe("active thread disconnect reconciliation triggers", () => { }); }); - it("interrupts active turns after the live event window elapses without a reconnect", async () => { + it("records a lost host connection after the live event window elapses without a reconnect", async () => { await withTestHarness(async (harness) => { const { session, thread } = seedActiveTurnThread(harness); @@ -586,13 +586,14 @@ describe("active thread disconnect reconciliation triggers", () => { expect.objectContaining({ data: expect.objectContaining({ code: "thread_command_failed", - message: "Thread interrupted because the host daemon disconnected", + message: + "Thread interrupted because the connection to the host was lost", detail: "Please retry the thread to continue.", }), type: "system/error", }), expect.objectContaining({ - data: { reason: "host-daemon-restarted" }, + data: { reason: "host-connection-lost" }, type: "system/thread/interrupted", }), ]); diff --git a/packages/domain/src/thread-events.ts b/packages/domain/src/thread-events.ts index d14e509c2a..d9497c98dc 100644 --- a/packages/domain/src/thread-events.ts +++ b/packages/domain/src/thread-events.ts @@ -246,6 +246,7 @@ export const systemUserQuestionLifecycleEventDataSchema = z.object({ const systemThreadInterruptedReasonValues = [ "manual-stop", "host-daemon-restarted", + "host-connection-lost", // Legacy persisted watchdog interruption; retained for read/replay only, // with no current producer. "provider-turn-idle", diff --git a/packages/plugin-api-map/sdk-public-api.json b/packages/plugin-api-map/sdk-public-api.json index a506cd20af..779e53b44e 100644 --- a/packages/plugin-api-map/sdk-public-api.json +++ b/packages/plugin-api-map/sdk-public-api.json @@ -3,7 +3,7 @@ "entries": { ".": { "types": "bundled-types/bb-plugin-sdk.d.ts", - "sha256": "7994e5e3cc8a3f743d1098cb09334201ae359aac5148bd945fb1ac429908c80f" + "sha256": "07b1b84f3eea68d78368be93279ed4bf72934e00b17efa26155d5225a1b8d4cc" }, "./ai-services": { "types": "bundled-types/bb-plugin-sdk-ai-services.d.ts", @@ -27,7 +27,7 @@ }, "./provider-bridge/testing": { "types": "bundled-types/bb-plugin-sdk-provider-bridge-testing.d.ts", - "sha256": "c35f7c70cdde2281a6fc5b51a041ea9281f4c232d6a4d730a823cbd5ef8aa8a9" + "sha256": "d413ca74e5aa4ea57e826438425172d9fe2175a95f0feaa8a0f52f0d4348048e" }, "./testing": { "types": "bundled-types/bb-plugin-sdk-testing.d.ts", diff --git a/packages/thread-view/src/parse-operation-message.ts b/packages/thread-view/src/parse-operation-message.ts index ce4a469cab..25077751d6 100644 --- a/packages/thread-view/src/parse-operation-message.ts +++ b/packages/thread-view/src/parse-operation-message.ts @@ -127,6 +127,8 @@ function threadInterruptedTitle(reason: SystemThreadInterruptedReason): string { return "Stopped manually"; case "host-daemon-restarted": return "Stopped — host daemon restarted"; + case "host-connection-lost": + return "Stopped — connection to host was lost"; // Legacy persisted watchdog interruption; no current producer. case "provider-turn-idle": return "Stopped — provider turn stopped responding"; diff --git a/packages/thread-view/test/parse-operation-message.test.ts b/packages/thread-view/test/parse-operation-message.test.ts index 1d46aab71c..f72f31627c 100644 --- a/packages/thread-view/test/parse-operation-message.test.ts +++ b/packages/thread-view/test/parse-operation-message.test.ts @@ -137,6 +137,9 @@ describe("parseOperationMessage operation titles", () => { expect(interruptedTitle("host-daemon-restarted", THREAD_NAME)).toBe( "Stopped — host daemon restarted", ); + expect(interruptedTitle("host-connection-lost", THREAD_NAME)).toBe( + "Stopped — connection to host was lost", + ); }); });