@love-moon/ai-sdk 0.17.2 → 0.18.0
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,73 @@
|
|
|
1
1
|
# @love-moon/ai-sdk
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 72324fd: Add a local multi-chat runtime with isolated Chromium workers, persistent profiles,
|
|
8
|
+
per-account concurrency, resumable events, request deduplication, and SDK/CLI access.
|
|
9
|
+
Add experimental Kimi and Claude webpage adapters and optional AI SDK socket transport.
|
|
10
|
+
|
|
11
|
+
Support Kimi Connect JSON envelopes and DeepSeek XHR SSE patches, with verified
|
|
12
|
+
parallel real-account text chats, independent cancellation, and restart recovery.
|
|
13
|
+
|
|
14
|
+
Add DeepSeek --deepthink / --no-deepthink controls and SDK options with persisted
|
|
15
|
+
chat defaults, per-turn overrides, verified UI state, and runtime capability checks.
|
|
16
|
+
|
|
17
|
+
Connect Conductor web-kimi and web-deepseek to managed isolated Chromium workers
|
|
18
|
+
by default. Add conductor chat-web login, account credential bootstrap, automatic
|
|
19
|
+
daemon startup and idle shutdown, stable cross-process resume IDs, and browser
|
|
20
|
+
release when tasks close. Preserve the shared-profile queue for other providers.
|
|
21
|
+
Retain cancelled conversation URLs and report confirmed stops as interruptions.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- bdbda18: Preserve lightweight conversation identity archives across profile/history GC so
|
|
26
|
+
Conductor can resume the same provider conversation using its existing local ID.
|
|
27
|
+
Archive idle, workerless open chats; persist the completed-turn resume guard; and
|
|
28
|
+
distinguish slow page loading from confirmed authentication failures.
|
|
29
|
+
|
|
30
|
+
Upgrading a CLI while an older managed runtime is running can report
|
|
31
|
+
RUNTIME_UPGRADE_REQUIRED when queuedStartup or durableResume support is missing.
|
|
32
|
+
After active chats finish, run `conductor chat-web runtime stop` and retry;
|
|
33
|
+
the next managed session starts the updated runtime. Alternatively, a runtime
|
|
34
|
+
with no active turns or pinned login windows exits after ten minutes without
|
|
35
|
+
requests. The CLI never terminates running chats automatically.
|
|
36
|
+
|
|
37
|
+
Profile deletion now starts only after the retention checkpoint commits, and
|
|
38
|
+
closed chats awaiting uncertainty review retain their profiles and turn history.
|
|
39
|
+
Accepted prompts refresh idle activity before collection, keeping their parent
|
|
40
|
+
conversation resident even when a client submits directly after a long idle.
|
|
41
|
+
|
|
42
|
+
- 063b97c: Show daemon-host inspection and acknowledgement commands on uncertain chat-web
|
|
43
|
+
errors and REVIEW_REQUIRED, including interrupted sends and timeouts. Keep
|
|
44
|
+
acknowledgement explicit: it does not replay a request, and the user sends the
|
|
45
|
+
next message after checking the provider conversation. The web error message
|
|
46
|
+
contains the commands; acknowledgement currently runs through the host CLI.
|
|
47
|
+
|
|
48
|
+
Compact streamed event history into full text snapshots instead of failing
|
|
49
|
+
after 2000 events. Sequence numbers remain monotonic across compaction and
|
|
50
|
+
restart; replay cursors may cross gaps and recover through a message_snapshot.
|
|
51
|
+
The one-million-character text limit remains enforced.
|
|
52
|
+
|
|
53
|
+
Return CHAT*CLOSED when closing a chat races a headed resume. Strip CONDUCTOR*\*
|
|
54
|
+
identity variables from detached runtime and browser-worker environments while
|
|
55
|
+
preserving browser and proxy configuration.
|
|
56
|
+
|
|
57
|
+
Upgrade note: an already-running shared runtime continues executing its old
|
|
58
|
+
code. After active chats finish, run `conductor chat-web runtime stop` and retry
|
|
59
|
+
to launch the updated runtime. Older runtimes lacking required capabilities
|
|
60
|
+
report RUNTIME_UPGRADE_REQUIRED; unused managed runtimes exit after ten minutes
|
|
61
|
+
without requests or pinned login windows. No running chat is stopped
|
|
62
|
+
automatically during upgrade.
|
|
63
|
+
|
|
64
|
+
- 31b61ea: Bound closed-chat and terminal-turn retention, reclaim browser profiles and orphan
|
|
65
|
+
directories, and keep streaming deltas in memory between durable checkpoints.
|
|
66
|
+
Defer saturated or busy chat startup to the turn queue, validate authentication at
|
|
67
|
+
allocation, and expire queued prompts before crash recovery or delayed submission.
|
|
68
|
+
Keep Gemini on its legacy transport with global runtime sockets and consume the
|
|
69
|
+
Conductor wrapper separator before forwarding a packaged CLI command.
|
|
70
|
+
|
|
3
71
|
## 0.17.2
|
|
4
72
|
|
|
5
73
|
### Patch Changes
|
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI SDK provider that delegates to the chat-web runtime
|
|
3
3
|
* (`@love-moon/chat-web`), which automates a real Chromium browser against
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* chatbot web UIs. Kimi and DeepSeek use isolated persistent chat workers
|
|
5
|
+
* with an automatically started local daemon. ChatGPT/Gemini retain the
|
|
6
|
+
* shared-profile queue unless a runtime socket is explicitly configured.
|
|
7
|
+
* Choose the underlying chat-web provider via:
|
|
6
8
|
*
|
|
7
|
-
* - `options.chatWebProvider`: "chatgpt" | "gemini" | "kimi"
|
|
9
|
+
* - `options.chatWebProvider`: "chatgpt" | "gemini" | "kimi" | "deepseek"
|
|
8
10
|
* - `options.provider`: same surface
|
|
9
11
|
* - `options.model`: same surface (fallback for ergonomic
|
|
10
12
|
* `createAiSession("chat-web", { model: "gemini" })`)
|
|
11
13
|
*
|
|
12
14
|
* Defaults to "chatgpt". Aliases: "openai"/"gpt" → chatgpt, "google" → gemini.
|
|
13
15
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* - `close()` releases this task's tab; the broker closes an idle browser.
|
|
20
|
-
*
|
|
21
|
-
* Resume: chat-web's "session" is a Chromium browser context, not a
|
|
22
|
-
* conversation ID — there is no native cross-process resume. We synthesise
|
|
23
|
-
* an id so the rest of ai-sdk has something stable to thread on, but
|
|
24
|
-
* passing `resumeSessionId` does not reattach to a prior conversation.
|
|
16
|
+
* Login on the daemon host: conductor chat-web login kimi|deepseek.
|
|
17
|
+
* Managed runtime IDs use chat-web-runtime:<id> so restarts restore the same
|
|
18
|
+
* isolated profile and provider conversation. Legacy provider IDs are imported.
|
|
19
|
+
* Set chatWebRuntime: false to explicitly use the shared-profile queue.
|
|
20
|
+
* Explicit chatWebRuntimeSocket clients keep caller-managed lifecycle.
|
|
25
21
|
*/
|
|
26
22
|
export class ChatWebSession extends EventEmitter<[never]> {
|
|
27
23
|
constructor(backend: any, options?: {});
|
|
28
24
|
backend: string;
|
|
29
25
|
options: {};
|
|
30
26
|
logger: any;
|
|
27
|
+
runtimeSocket: any;
|
|
31
28
|
chatWebProvider: string;
|
|
29
|
+
runtimeMode: boolean;
|
|
30
|
+
managedRuntime: boolean;
|
|
31
|
+
runtimeConversationId: any;
|
|
32
|
+
runtimeProviderConversationId: any;
|
|
32
33
|
headless: boolean;
|
|
33
34
|
browserChannel: any;
|
|
34
35
|
browserExecutablePath: any;
|
|
@@ -163,6 +164,8 @@ export class ChatWebSession extends EventEmitter<[never]> {
|
|
|
163
164
|
manualResume: null;
|
|
164
165
|
}>;
|
|
165
166
|
getChatWebModule(): Promise<any>;
|
|
167
|
+
loginCommand(): string;
|
|
168
|
+
reviewInstructions(): string;
|
|
166
169
|
boot(): Promise<void>;
|
|
167
170
|
bootInternal(): Promise<void>;
|
|
168
171
|
/**
|
|
@@ -4,7 +4,8 @@ import { PROVIDER_MEDIA_CAPABILITIES } from "../media-adapters.js";
|
|
|
4
4
|
import { assertMediaCapabilities, resolveTurnMedia } from "../media-input.js";
|
|
5
5
|
import { normalizeContextFiles } from "../context-files.js";
|
|
6
6
|
import { emitLog, normalizeLogger } from "../shared.js";
|
|
7
|
-
const SUPPORTED_CHAT_WEB_PROVIDERS = new Set(["chatgpt", "gemini", "kimi"]);
|
|
7
|
+
const SUPPORTED_CHAT_WEB_PROVIDERS = new Set(["chatgpt", "gemini", "kimi", "deepseek"]);
|
|
8
|
+
const RUNTIME_CHAT_WEB_PROVIDERS = new Set(["chatgpt", "deepseek", "kimi", "claude"]);
|
|
8
9
|
const DEFAULT_CHAT_WEB_PROVIDER = "chatgpt";
|
|
9
10
|
const DEFAULT_TURN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
10
11
|
function normalizeChatWebProvider(value) {
|
|
@@ -19,13 +20,14 @@ function normalizeChatWebProvider(value) {
|
|
|
19
20
|
return raw;
|
|
20
21
|
}
|
|
21
22
|
function resolveChatWebProvider(options = {}) {
|
|
22
|
-
const
|
|
23
|
-
|
|
23
|
+
const supported = options.chatWebRuntimeSocket || process.env.CHAT_WEB_RUNTIME_SOCKET
|
|
24
|
+
? new Set([...SUPPORTED_CHAT_WEB_PROVIDERS, ...RUNTIME_CHAT_WEB_PROVIDERS]) : SUPPORTED_CHAT_WEB_PROVIDERS;
|
|
25
|
+
for (const candidate of [options.chatWebProvider, options.provider, options.model]) {
|
|
24
26
|
const normalized = normalizeChatWebProvider(candidate);
|
|
25
27
|
if (!normalized)
|
|
26
28
|
continue;
|
|
27
|
-
if (!
|
|
28
|
-
const error = new Error(`Unknown chat-web provider "${normalized}". Supported: ${[...
|
|
29
|
+
if (!supported.has(normalized)) {
|
|
30
|
+
const error = new Error(`Unknown chat-web provider "${normalized}". Supported: ${[...supported].join(", ")}`);
|
|
29
31
|
error.reason = "unknown_chat_web_provider";
|
|
30
32
|
throw error;
|
|
31
33
|
}
|
|
@@ -33,6 +35,10 @@ function resolveChatWebProvider(options = {}) {
|
|
|
33
35
|
}
|
|
34
36
|
return DEFAULT_CHAT_WEB_PROVIDER;
|
|
35
37
|
}
|
|
38
|
+
function quoteRuntimeArgument(value) {
|
|
39
|
+
const text = String(value);
|
|
40
|
+
return /^[A-Za-z0-9_./:@%+=,-]+$/.test(text) ? text : "'" + text.replaceAll("'", "'\\''") + "'";
|
|
41
|
+
}
|
|
36
42
|
function extractErrorMessage(error) {
|
|
37
43
|
if (typeof error?.message === "string" && error.message.trim()) {
|
|
38
44
|
return error.message.trim();
|
|
@@ -98,27 +104,23 @@ function isPlaywrightMissingError(error) {
|
|
|
98
104
|
/**
|
|
99
105
|
* AI SDK provider that delegates to the chat-web runtime
|
|
100
106
|
* (`@love-moon/chat-web`), which automates a real Chromium browser against
|
|
101
|
-
*
|
|
102
|
-
*
|
|
107
|
+
* chatbot web UIs. Kimi and DeepSeek use isolated persistent chat workers
|
|
108
|
+
* with an automatically started local daemon. ChatGPT/Gemini retain the
|
|
109
|
+
* shared-profile queue unless a runtime socket is explicitly configured.
|
|
110
|
+
* Choose the underlying chat-web provider via:
|
|
103
111
|
*
|
|
104
|
-
* - `options.chatWebProvider`: "chatgpt" | "gemini" | "kimi"
|
|
112
|
+
* - `options.chatWebProvider`: "chatgpt" | "gemini" | "kimi" | "deepseek"
|
|
105
113
|
* - `options.provider`: same surface
|
|
106
114
|
* - `options.model`: same surface (fallback for ergonomic
|
|
107
115
|
* `createAiSession("chat-web", { model: "gemini" })`)
|
|
108
116
|
*
|
|
109
117
|
* Defaults to "chatgpt". Aliases: "openai"/"gpt" → chatgpt, "google" → gemini.
|
|
110
118
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* - `close()` releases this task's tab; the broker closes an idle browser.
|
|
117
|
-
*
|
|
118
|
-
* Resume: chat-web's "session" is a Chromium browser context, not a
|
|
119
|
-
* conversation ID — there is no native cross-process resume. We synthesise
|
|
120
|
-
* an id so the rest of ai-sdk has something stable to thread on, but
|
|
121
|
-
* passing `resumeSessionId` does not reattach to a prior conversation.
|
|
119
|
+
* Login on the daemon host: conductor chat-web login kimi|deepseek.
|
|
120
|
+
* Managed runtime IDs use chat-web-runtime:<id> so restarts restore the same
|
|
121
|
+
* isolated profile and provider conversation. Legacy provider IDs are imported.
|
|
122
|
+
* Set chatWebRuntime: false to explicitly use the shared-profile queue.
|
|
123
|
+
* Explicit chatWebRuntimeSocket clients keep caller-managed lifecycle.
|
|
122
124
|
*/
|
|
123
125
|
export class ChatWebSession extends EventEmitter {
|
|
124
126
|
constructor(backend, options = {}) {
|
|
@@ -126,17 +128,25 @@ export class ChatWebSession extends EventEmitter {
|
|
|
126
128
|
this.backend = "chat-web";
|
|
127
129
|
this.options = options;
|
|
128
130
|
this.logger = normalizeLogger(options.logger);
|
|
131
|
+
this.runtimeSocket = options.chatWebRuntimeSocket || process.env.CHAT_WEB_RUNTIME_SOCKET || "";
|
|
129
132
|
this.chatWebProvider = resolveChatWebProvider(options);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this.
|
|
133
|
+
if (!RUNTIME_CHAT_WEB_PROVIDERS.has(this.chatWebProvider))
|
|
134
|
+
this.runtimeSocket = "";
|
|
135
|
+
this.runtimeMode = Boolean(this.runtimeSocket) ||
|
|
136
|
+
(["kimi", "deepseek"].includes(this.chatWebProvider) && options.chatWebRuntime !== false);
|
|
137
|
+
this.managedRuntime = this.runtimeMode && !this.runtimeSocket;
|
|
138
|
+
const resumed = typeof options.resumeSessionId === "string" ? options.resumeSessionId : "";
|
|
139
|
+
const prefix = "chat-web-runtime:";
|
|
140
|
+
this.runtimeConversationId = options.chatWebConversationId ||
|
|
141
|
+
(this.managedRuntime ? (resumed.startsWith(prefix) ? resumed.slice(prefix.length) : undefined) : this.runtimeMode ? resumed : undefined);
|
|
142
|
+
this.runtimeProviderConversationId = this.managedRuntime && resumed && !resumed.startsWith(prefix) && !options.chatWebConversationId ? resumed : undefined;
|
|
143
|
+
if (options.chatWebDeepThink !== undefined &&
|
|
144
|
+
(typeof options.chatWebDeepThink !== "boolean" || this.chatWebProvider !== "deepseek" || !this.runtimeMode)) {
|
|
145
|
+
throw new Error("chatWebDeepThink requires a boolean and the DeepSeek chat runtime");
|
|
146
|
+
}
|
|
147
|
+
// Kimi requires headed Chromium with the tested login flow. DeepSeek's
|
|
148
|
+
// managed workers support headless; explicit options override either default.
|
|
149
|
+
this.headless = this.runtimeMode && this.chatWebProvider === "deepseek" ? options.headless !== false : options.headless === true;
|
|
140
150
|
// Optional: use a specific Chromium-family binary (system Chrome /
|
|
141
151
|
// Edge / explicit path) instead of Playwright's bundled
|
|
142
152
|
// `chrome-headless-shell`. Useful when the user's network treats
|
|
@@ -213,12 +223,12 @@ export class ChatWebSession extends EventEmitter {
|
|
|
213
223
|
sessionId: this.sessionId,
|
|
214
224
|
sessionInfo: this.getSessionInfo(),
|
|
215
225
|
useSessionFileReplyStream: this.usesSessionFileReplyStream(),
|
|
216
|
-
resumeReady: Boolean(this.providerConversationId),
|
|
226
|
+
resumeReady: Boolean(this.runtimeMode ? this.runtimeConversationId : this.providerConversationId),
|
|
217
227
|
manualResume: this.providerConversationUrl()
|
|
218
228
|
? { ready: true, command: this.providerConversationUrl() }
|
|
219
229
|
: null,
|
|
220
230
|
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
221
|
-
capabilities: { clear:
|
|
231
|
+
capabilities: { clear: !this.runtimeMode, media: PROVIDER_MEDIA_CAPABILITIES[CHAT_WEB_SESSION_VARIANT] },
|
|
222
232
|
chatWebProvider: this.chatWebProvider,
|
|
223
233
|
providerConversationId: this.providerConversationId,
|
|
224
234
|
providerUrl: this.providerConversationUrl(),
|
|
@@ -235,7 +245,7 @@ export class ChatWebSession extends EventEmitter {
|
|
|
235
245
|
//
|
|
236
246
|
// We expose `sessionIdDeferred: true` so the fire-side announce can
|
|
237
247
|
// hold off until the real id arrives, then re-announce.
|
|
238
|
-
const hasRealId = Boolean(this.providerConversationId);
|
|
248
|
+
const hasRealId = Boolean(this.runtimeMode ? this.runtimeConversationId : this.providerConversationId);
|
|
239
249
|
return {
|
|
240
250
|
...this.sessionInfo,
|
|
241
251
|
sessionIdDeferred: !hasRealId,
|
|
@@ -300,6 +310,21 @@ export class ChatWebSession extends EventEmitter {
|
|
|
300
310
|
throw enriched;
|
|
301
311
|
}
|
|
302
312
|
}
|
|
313
|
+
loginCommand() {
|
|
314
|
+
if (!this.runtimeMode)
|
|
315
|
+
return `chat-web login ${this.chatWebProvider}`;
|
|
316
|
+
if (this.managedRuntime && (!this.options.chatWebAccountId || this.options.chatWebAccountId === "default")) {
|
|
317
|
+
return `conductor chat-web login ${this.chatWebProvider}`;
|
|
318
|
+
}
|
|
319
|
+
return `conductor chat-web chat login ${this.runtimeConversationId} --socket ${this.runtimeSocket}`;
|
|
320
|
+
}
|
|
321
|
+
reviewInstructions() {
|
|
322
|
+
if (!this.runtimeConversationId || !this.runtimeSocket)
|
|
323
|
+
return "";
|
|
324
|
+
const id = quoteRuntimeArgument(this.runtimeConversationId);
|
|
325
|
+
const socket = quoteRuntimeArgument(this.runtimeSocket);
|
|
326
|
+
return ` The previous webpage request has an uncertain outcome. On the daemon host, inspect the same conversation with: conductor chat-web chat login ${id} --socket ${socket}. After checking its result, acknowledge with: conductor chat-web chat resume ${id} --acknowledge --socket ${socket}. Then resend only the message you still want to send; acknowledgement does not replay the previous request.`;
|
|
327
|
+
}
|
|
303
328
|
async boot() {
|
|
304
329
|
if (this.booted)
|
|
305
330
|
return;
|
|
@@ -310,6 +335,13 @@ export class ChatWebSession extends EventEmitter {
|
|
|
310
335
|
await this.bootPromise;
|
|
311
336
|
this.booted = true;
|
|
312
337
|
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
// A failed login must release this task's browser so a fresh account
|
|
340
|
+
// snapshot can be imported when the user retries the same task.
|
|
341
|
+
await this.chatSession?.close().catch(() => undefined);
|
|
342
|
+
this.chatSession = null;
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
313
345
|
finally {
|
|
314
346
|
this.bootPromise = null;
|
|
315
347
|
}
|
|
@@ -322,11 +354,9 @@ export class ChatWebSession extends EventEmitter {
|
|
|
322
354
|
if (typeof mod.registerBuiltinProviders === "function") {
|
|
323
355
|
mod.registerBuiltinProviders();
|
|
324
356
|
}
|
|
325
|
-
|
|
326
|
-
// Keep the existing session surface for older optional chat-web versions.
|
|
327
|
-
const Session = mod.QueuedChatSession ?? mod.ChatSession;
|
|
357
|
+
const Session = this.runtimeMode ? mod.RuntimeChatSession : (mod.QueuedChatSession ?? mod.ChatSession);
|
|
328
358
|
if (typeof Session?.open !== "function") {
|
|
329
|
-
throw new Error(
|
|
359
|
+
throw new Error(`Loaded @love-moon/chat-web is missing ${this.runtimeMode ? "RuntimeChatSession" : "ChatSession"}.open; upgrade chat-web`);
|
|
330
360
|
}
|
|
331
361
|
// Forward optional browser-binary overrides to chat-web. The env vars
|
|
332
362
|
// CHAT_WEB_BROWSER_CHANNEL / CHAT_WEB_BROWSER_EXECUTABLE are also
|
|
@@ -338,6 +368,7 @@ export class ChatWebSession extends EventEmitter {
|
|
|
338
368
|
if (this.browserExecutablePath)
|
|
339
369
|
launch.executablePath = this.browserExecutablePath;
|
|
340
370
|
this.chatSession = await Session.open(this.chatWebProvider, {
|
|
371
|
+
...(this.runtimeMode ? { socketPath: this.managedRuntime ? undefined : this.runtimeSocket || undefined, conversationId: this.runtimeConversationId, providerConversationId: this.runtimeProviderConversationId, accountId: this.options.chatWebAccountId, deepThink: this.options.chatWebDeepThink, releaseOnClose: this.managedRuntime } : {}),
|
|
341
372
|
headless: this.headless,
|
|
342
373
|
// IMPORTANT: chat-web's logger contract is `{ error, warn, info, debug }`;
|
|
343
374
|
// ai-sdk's normalised logger is `{ log }`. Passing the ai-sdk logger
|
|
@@ -351,9 +382,23 @@ export class ChatWebSession extends EventEmitter {
|
|
|
351
382
|
this.chatSession = null;
|
|
352
383
|
throw this.createSessionClosedError();
|
|
353
384
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
385
|
+
if (this.runtimeMode) {
|
|
386
|
+
this.runtimeConversationId = this.chatSession.id;
|
|
387
|
+
this.runtimeSocket = this.chatSession.client?.socketPath || this.runtimeSocket;
|
|
388
|
+
this.sessionId = this.managedRuntime ? "chat-web-runtime:" + this.runtimeConversationId : this.runtimeConversationId;
|
|
389
|
+
if (this.chatSession.providerConversationId)
|
|
390
|
+
this.providerConversationId = this.chatSession.providerConversationId;
|
|
391
|
+
this.sessionInfo = { ...this.sessionInfo, sessionId: this.sessionId };
|
|
392
|
+
this.emit("session", this.getSessionInfo());
|
|
393
|
+
}
|
|
394
|
+
const readiness = this.runtimeMode && typeof this.chatSession.prepare === "function"
|
|
395
|
+
? await this.chatSession.prepare()
|
|
396
|
+
: { ready: this.runtimeMode ? await this.chatSession.isLoggedIn() : await this.chatSession.isLoggedIn().catch(() => false), authRequired: true };
|
|
397
|
+
if (!readiness.ready && !readiness.queued) {
|
|
398
|
+
if (readiness.authRequired !== true) {
|
|
399
|
+
throw Object.assign(new Error("Chat page did not become ready; wait for it to finish loading and retry"), { code: "PAGE_NOT_READY" });
|
|
400
|
+
}
|
|
401
|
+
const error = new Error(`Chat requires login. On this daemon host run: ${this.loginCommand()}`);
|
|
357
402
|
error.reason = "not_logged_in";
|
|
358
403
|
this.emit("auth_required", {
|
|
359
404
|
reason: "login_required",
|
|
@@ -375,6 +420,11 @@ export class ChatWebSession extends EventEmitter {
|
|
|
375
420
|
* for exactly this window.
|
|
376
421
|
*/
|
|
377
422
|
async runClear() {
|
|
423
|
+
if (this.runtimeMode) {
|
|
424
|
+
const error = new Error("Create a new runtime conversation to clear an isolated chat");
|
|
425
|
+
error.reason = "unsupported_clear";
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
378
428
|
if (this.closeRequested) {
|
|
379
429
|
throw this.createSessionClosedError();
|
|
380
430
|
}
|
|
@@ -425,11 +475,12 @@ export class ChatWebSession extends EventEmitter {
|
|
|
425
475
|
if (normalized === this.providerConversationId)
|
|
426
476
|
return;
|
|
427
477
|
this.providerConversationId = normalized;
|
|
428
|
-
this.
|
|
478
|
+
if (!this.runtimeMode)
|
|
479
|
+
this.sessionId = normalized;
|
|
429
480
|
this.sessionInfo = {
|
|
430
481
|
...(this.sessionInfo || {}),
|
|
431
482
|
backend: this.backend,
|
|
432
|
-
sessionId:
|
|
483
|
+
sessionId: this.sessionId,
|
|
433
484
|
model: this.chatWebProvider,
|
|
434
485
|
modelProvider: "chat-web",
|
|
435
486
|
providerConversationId: normalized,
|
|
@@ -448,8 +499,12 @@ export class ChatWebSession extends EventEmitter {
|
|
|
448
499
|
switch (this.chatWebProvider) {
|
|
449
500
|
case "chatgpt":
|
|
450
501
|
return `https://chatgpt.com/c/${this.providerConversationId}`;
|
|
502
|
+
case "deepseek":
|
|
503
|
+
return `https://chat.deepseek.com/a/chat/s/${this.providerConversationId}`;
|
|
451
504
|
case "kimi":
|
|
452
505
|
return `https://www.kimi.com/chat/${encodeURIComponent(this.providerConversationId)}`;
|
|
506
|
+
case "claude":
|
|
507
|
+
return `https://claude.ai/chat/${this.providerConversationId}`;
|
|
453
508
|
case "gemini":
|
|
454
509
|
// chat-web's `gemini` provider targets AI Studio
|
|
455
510
|
// (aistudio.google.com/prompts/new_chat) — that's the free web
|
|
@@ -498,10 +553,20 @@ export class ChatWebSession extends EventEmitter {
|
|
|
498
553
|
await this.boot();
|
|
499
554
|
if (this.closeRequested)
|
|
500
555
|
throw this.createSessionClosedError();
|
|
556
|
+
if (this.currentTurn.aborted)
|
|
557
|
+
throw new DOMException("Aborted", "AbortError");
|
|
501
558
|
this.turnAttempted = true;
|
|
502
559
|
const result = await this.chatSession.send(prompt, {
|
|
503
560
|
timeoutMs: this.turnTimeoutMs,
|
|
504
561
|
signal: this.currentTurn.controller.signal,
|
|
562
|
+
onStatus: (phase) => {
|
|
563
|
+
if (phase === "queued" || phase === "starting")
|
|
564
|
+
void this.emitWorkingStatus({
|
|
565
|
+
phase: phase === "queued" ? "queued" : "turn_started",
|
|
566
|
+
reply_in_progress: true,
|
|
567
|
+
status_line: phase === "queued" ? `chat-web (${this.chatWebProvider}) waiting for a browser worker` : `chat-web (${this.chatWebProvider}) is working`,
|
|
568
|
+
}, onProgress);
|
|
569
|
+
},
|
|
505
570
|
onQueue: (position) => {
|
|
506
571
|
void this.emitWorkingStatus({
|
|
507
572
|
phase: position > 0 ? "queued" : "turn_started",
|
|
@@ -566,6 +631,17 @@ export class ChatWebSession extends EventEmitter {
|
|
|
566
631
|
catch (error) {
|
|
567
632
|
const message = extractErrorMessage(error);
|
|
568
633
|
const code = typeof error?.code === "string" ? error.code : "";
|
|
634
|
+
if (this.runtimeMode && (code === "AUTH_REQUIRED" || /Provider HTTP 401/.test(message))) {
|
|
635
|
+
const login = this.loginCommand();
|
|
636
|
+
error.message = `Browser login needs renewal. On this daemon host run: ${login}.`;
|
|
637
|
+
error.reason = "not_logged_in";
|
|
638
|
+
}
|
|
639
|
+
if (this.runtimeMode && (code === "REVIEW_REQUIRED" || error?.turnStatus === "uncertain")) {
|
|
640
|
+
error.message = (error.message || message) + this.reviewInstructions();
|
|
641
|
+
}
|
|
642
|
+
if (this.runtimeMode && (code === "AUTH_REQUIRED" || /Provider HTTP 401/.test(message))) {
|
|
643
|
+
this.emit("auth_required", { reason: "login_required", message: error.message, provider: this.chatWebProvider });
|
|
644
|
+
}
|
|
569
645
|
// Surface chat-web's typed "needs API key" / "permission denied"
|
|
570
646
|
// errors as auth_required so the UI / daemon can route them
|
|
571
647
|
// through the same flow as ChatGPT's "not logged in" — they're
|
|
@@ -580,7 +656,7 @@ export class ChatWebSession extends EventEmitter {
|
|
|
580
656
|
}
|
|
581
657
|
await this.emitTerminalWorkingStatus({
|
|
582
658
|
phase: this.currentTurn?.aborted ? "turn_interrupted" : "turn_failed",
|
|
583
|
-
status_done_line: message,
|
|
659
|
+
status_done_line: error.message || message,
|
|
584
660
|
}, onProgress);
|
|
585
661
|
throw error;
|
|
586
662
|
}
|
|
@@ -590,12 +666,18 @@ export class ChatWebSession extends EventEmitter {
|
|
|
590
666
|
}
|
|
591
667
|
}
|
|
592
668
|
async interruptCurrentTurn() {
|
|
669
|
+
// Direct chat-web ChatSession doesn't expose a turn abort — the underlying
|
|
670
|
+
// Chromium tab is still busy with the model. We mark the turn as
|
|
671
|
+
// aborted so the next status emission is "interrupted", but the
|
|
672
|
+
// assistant_message may still arrive once the model finishes.
|
|
593
673
|
if (this.currentTurn) {
|
|
594
674
|
this.currentTurn.aborted = true;
|
|
595
675
|
// The shared broker removes a pending turn, or stops/closes the active
|
|
596
676
|
// task's tab before moving to the next task. No queued prompt is sent.
|
|
597
677
|
this.currentTurn.controller.abort();
|
|
598
678
|
}
|
|
679
|
+
if (this.runtimeMode)
|
|
680
|
+
await this.chatSession?.interruptCurrentTurn?.();
|
|
599
681
|
}
|
|
600
682
|
async close() {
|
|
601
683
|
if (this.closed)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* contract by surfacing a synthetic session id and a "best effort" context,
|
|
6
|
-
* but downstream callers should treat resume here as a no-op.
|
|
2
|
+
* Managed Kimi/DeepSeek sessions use a stable chat-web-runtime:<id> token.
|
|
3
|
+
* RuntimeChatSession restores the saved isolated profile and provider URL.
|
|
4
|
+
* Other chat-web modes retain their existing provider-specific resume behavior.
|
|
7
5
|
*/
|
|
8
6
|
export function buildCliArgs(sessionId: any): string[];
|
|
9
7
|
export function findSessionPath(): Promise<null>;
|
package/dist/resume/chat-web.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { buildResumeContext, normalizeSessionId } from "./shared.js";
|
|
2
2
|
export const BACKEND = "chat-web";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* contract by surfacing a synthetic session id and a "best effort" context,
|
|
8
|
-
* but downstream callers should treat resume here as a no-op.
|
|
4
|
+
* Managed Kimi/DeepSeek sessions use a stable chat-web-runtime:<id> token.
|
|
5
|
+
* RuntimeChatSession restores the saved isolated profile and provider URL.
|
|
6
|
+
* Other chat-web modes retain their existing provider-specific resume behavior.
|
|
9
7
|
*/
|
|
10
8
|
export function buildCliArgs(sessionId) {
|
|
11
9
|
// chat-web has no CLI to pass these args to (it's an in-process SDK), but
|
|
@@ -38,7 +36,9 @@ export async function resolveResumeContext(sessionId, options = {}) {
|
|
|
38
36
|
cwd,
|
|
39
37
|
cwdSource: "process_cwd",
|
|
40
38
|
extraDebug: {
|
|
41
|
-
note: "chat-web
|
|
39
|
+
note: normalizedSessionId.startsWith("chat-web-runtime:")
|
|
40
|
+
? "The isolated chat runtime restores this saved conversation; a missing runtime id is an error."
|
|
41
|
+
: "The selected chat-web transport resolves this provider conversation id when opening.",
|
|
42
42
|
},
|
|
43
43
|
});
|
|
44
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/ai-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"zod": "^4.1.5"
|
|
54
54
|
},
|
|
55
55
|
"optionalDependencies": {
|
|
56
|
-
"@love-moon/chat-web": "^0.
|
|
56
|
+
"@love-moon/chat-web": "^0.18.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^22.10.2",
|
|
60
60
|
"tsx": "^4.20.6",
|
|
61
61
|
"typescript": "^5.6.3"
|
|
62
62
|
},
|
|
63
|
-
"gitCommitId": "
|
|
63
|
+
"gitCommitId": "825d90a"
|
|
64
64
|
}
|