@love-moon/ai-sdk 0.5.0 → 0.5.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @love-moon/ai-sdk
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 39a49fc: fix: reclaim orphaned chat-web browser and cap chat-web task lifetime
8
+
9
+ chat-web persists one Chromium profile per provider, guarded by a per-profile
10
+ SingletonLock. A task whose browser was not cleaned up (e.g. the ai-sdk worker
11
+ was SIGKILLed) left an orphaned Chromium holding that lock, so the next task for
12
+ the same provider failed to launch with `Opening in existing browser session`.
13
+
14
+ - chat-web now reclaims stale/orphaned profile locks before launching (kills an
15
+ orphan whose owner process is gone, clears dead locks) and refuses with a
16
+ clear `ProfileLockedError` when a genuine live chat still holds the profile.
17
+ - The ai-sdk worker now closes its session (and browser) on SIGTERM/SIGINT and
18
+ bounds the close so it can't hang, preventing browser leaks on shutdown.
19
+ - conductor fire caps a chat-web task's active lifetime (default 24h,
20
+ `CONDUCTOR_CHATWEB_MAX_ACTIVE_MS`) and auto-stops it as
21
+ `KILLED / max_active_duration`; chat history is preserved.
22
+
3
23
  ## 0.5.0
4
24
 
5
25
  ## 0.4.2
package/dist/worker.js CHANGED
@@ -96,7 +96,9 @@ async function handleRequest(message) {
96
96
  result,
97
97
  });
98
98
  if (method === "close") {
99
- process.exit(0);
99
+ // session.close() already ran via the dispatch above; route through the
100
+ // guarded shutdown so a racing signal can't double-handle the exit.
101
+ void gracefulShutdown(0);
100
102
  }
101
103
  }
102
104
  async function dispatchMessage(message) {
@@ -117,38 +119,67 @@ async function dispatchMessage(message) {
117
119
  });
118
120
  }
119
121
  }
122
+ // Upper bound on how long we wait for a graceful session close before
123
+ // giving up and exiting. A browser-backed session (chat-web) can hang in
124
+ // context.close(); without this cap the worker could fail to exit, which
125
+ // only makes orphaned browsers more likely. If close times out, the browser
126
+ // is reclaimed on the next launch via chat-web's profile-lock logic.
127
+ const CLOSE_TIMEOUT_MS = 10_000;
120
128
  async function closeSession() {
121
129
  if (!session || typeof session.close !== "function") {
122
130
  return;
123
131
  }
124
132
  try {
125
- await session.close();
133
+ await Promise.race([
134
+ Promise.resolve().then(() => session.close()),
135
+ new Promise((resolve) => {
136
+ const timer = setTimeout(resolve, CLOSE_TIMEOUT_MS);
137
+ if (typeof timer.unref === "function") {
138
+ timer.unref();
139
+ }
140
+ }),
141
+ ]);
126
142
  }
127
143
  catch {
128
144
  // best effort
129
145
  }
130
146
  }
131
- process.on("uncaughtException", async (error) => {
147
+ // Graceful shutdown on termination signals. Without these handlers a worker
148
+ // killed via SIGTERM (e.g. when the parent fire is stopped) would exit
149
+ // without closing its session, leaking the Chromium browser it spawned.
150
+ let shuttingDown = false;
151
+ async function gracefulShutdown(exitCode) {
152
+ if (shuttingDown) {
153
+ return;
154
+ }
155
+ shuttingDown = true;
156
+ await closeSession();
157
+ process.exit(exitCode);
158
+ }
159
+ process.on("SIGTERM", () => {
160
+ void gracefulShutdown(143);
161
+ });
162
+ process.on("SIGINT", () => {
163
+ void gracefulShutdown(130);
164
+ });
165
+ process.on("uncaughtException", (error) => {
132
166
  send({
133
167
  type: "event",
134
168
  name: "worker_error",
135
169
  payload: serializeError(error),
136
170
  });
137
- await closeSession();
138
- process.exit(1);
171
+ void gracefulShutdown(1);
139
172
  });
140
- process.on("unhandledRejection", async (reason) => {
173
+ process.on("unhandledRejection", (reason) => {
141
174
  send({
142
175
  type: "event",
143
176
  name: "worker_error",
144
177
  payload: serializeError(reason),
145
178
  });
146
- await closeSession();
147
- process.exit(1);
179
+ void gracefulShutdown(1);
148
180
  });
149
- process.stdin.on("end", async () => {
150
- await closeSession();
151
- process.exit(0);
181
+ process.stdin.on("end", () => {
182
+ void gracefulShutdown(0);
152
183
  });
153
184
  const input = readline.createInterface({ input: process.stdin });
154
185
  let workQueue = Promise.resolve();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@love-moon/ai-sdk",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/lovemoon-ai/conductor.git"
@@ -32,12 +32,12 @@
32
32
  "zod": "^4.1.5"
33
33
  },
34
34
  "optionalDependencies": {
35
- "@love-moon/chat-web": "^0.5.0"
35
+ "@love-moon/chat-web": "^0.5.1"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^22.10.2",
39
39
  "tsx": "^4.20.6",
40
40
  "typescript": "^5.6.3"
41
41
  },
42
- "gitCommitId": "7f50aa7"
42
+ "gitCommitId": "119eab6"
43
43
  }