mu-agent-bridge 1.0.34 → 1.0.39

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.
Files changed (89) hide show
  1. package/README.md +4 -2
  2. package/dist/src-ts/channel-sessions.js +90 -24
  3. package/dist/src-ts/control-server.js +48 -7
  4. package/dist/src-ts/dsh-agent.js +141 -46
  5. package/dist/src-ts/headless-account.patch.yml +8 -1
  6. package/dist/src-ts/plugin-manager.js +69 -3
  7. package/dist/src-ts/plugins/aun/attachments.js +44 -13
  8. package/dist/src-ts/plugins/aun/index.js +5 -6
  9. package/dist/src-ts/plugins/chatgpt/cdp-client.js +342 -9
  10. package/dist/src-ts/plugins/chatgpt/index.js +127 -7
  11. package/dist/src-ts/plugins/desktop-launch.js +19 -0
  12. package/dist/src-ts/plugins/doubao/attachments.js +42 -0
  13. package/dist/src-ts/plugins/doubao/cdp-client.js +530 -48
  14. package/dist/src-ts/plugins/doubao/index.js +114 -25
  15. package/dist/src-ts/plugins/doubao/session-history.js +123 -0
  16. package/dist/src-ts/plugins/dsh/index.js +18 -2
  17. package/dist/src-ts/plugins/dsh/session-history.js +371 -0
  18. package/dist/src-ts/plugins/qwenwork/cdp-client.js +271 -10
  19. package/dist/src-ts/plugins/qwenwork/index.js +42 -3
  20. package/dist/src-ts/plugins/qwenwork/session-history.js +245 -0
  21. package/dist/src-ts/plugins/workbuddy/cdp-client.js +42 -2
  22. package/dist/src-ts/plugins/workbuddy/index.js +23 -3
  23. package/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  24. package/dist/src-ts/session-archive.js +509 -0
  25. package/dist/src-ts/session-history.js +2 -0
  26. package/dist/src-ts/session-management.js +665 -0
  27. package/dist/src-ts/session-summary.js +171 -0
  28. package/dist/tests-ts/aun-attachments.test.js +41 -3
  29. package/dist/tests-ts/aun-plugin.test.js +28 -5
  30. package/dist/tests-ts/bundled-package.test.js +3 -1
  31. package/dist/tests-ts/channel-sessions.test.js +255 -2
  32. package/dist/tests-ts/chatgpt-history.test.js +197 -0
  33. package/dist/tests-ts/chatgpt-plugin.test.js +279 -2
  34. package/dist/tests-ts/control-server.test.js +39 -0
  35. package/dist/tests-ts/desktop-launch.test.js +41 -0
  36. package/dist/tests-ts/doubao-history.test.js +172 -0
  37. package/dist/tests-ts/doubao-plugin.test.js +549 -2
  38. package/dist/tests-ts/dsh-agent.test.js +199 -0
  39. package/dist/tests-ts/dsh-history.test.js +165 -0
  40. package/dist/tests-ts/native-session-management.test.js +195 -0
  41. package/dist/tests-ts/plugin-manager.test.js +354 -4
  42. package/dist/tests-ts/qwenwork-history.test.js +339 -0
  43. package/dist/tests-ts/qwenwork-images.test.js +166 -0
  44. package/dist/tests-ts/qwenwork-plugin.test.js +40 -1
  45. package/dist/tests-ts/session-archive.test.js +422 -0
  46. package/dist/tests-ts/session-management.test.js +323 -0
  47. package/dist/tests-ts/session-summary.test.js +199 -0
  48. package/dist/tests-ts/workbuddy-history.test.js +256 -0
  49. package/dist/tests-ts/workbuddy-plugin.test.js +43 -0
  50. package/package.json +1 -1
  51. package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +44 -13
  52. package/plugins/aun/dist/src-ts/plugins/aun/index.js +5 -6
  53. package/plugins/aun/package.json +1 -1
  54. package/plugins/aun/plugin.json +1 -1
  55. package/plugins/chatgpt/dist/src-ts/attachments.js +62 -0
  56. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +342 -9
  57. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +127 -7
  58. package/plugins/chatgpt/package.json +1 -1
  59. package/plugins/chatgpt/plugin.json +14 -1
  60. package/plugins/doubao/config.schema.json +3 -0
  61. package/plugins/doubao/dist/src-ts/attachments.js +62 -0
  62. package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +19 -0
  63. package/plugins/doubao/dist/src-ts/plugins/doubao/attachments.js +42 -0
  64. package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +530 -48
  65. package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +114 -25
  66. package/plugins/doubao/dist/src-ts/plugins/doubao/session-history.js +123 -0
  67. package/plugins/doubao/package.json +1 -1
  68. package/plugins/doubao/plugin.json +1 -1
  69. package/plugins/dsh/config.schema.json +2 -1
  70. package/plugins/dsh/dist/src-ts/attachments.js +62 -0
  71. package/plugins/dsh/dist/src-ts/dsh-agent.js +141 -46
  72. package/plugins/dsh/dist/src-ts/headless-account.patch.yml +8 -1
  73. package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +18 -2
  74. package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +371 -0
  75. package/plugins/dsh/package.json +5 -2
  76. package/plugins/dsh/plugin.json +1 -1
  77. package/plugins/qwenwork/dist/src-ts/attachments.js +62 -0
  78. package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +19 -0
  79. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +271 -10
  80. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +42 -3
  81. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/session-history.js +245 -0
  82. package/plugins/qwenwork/package.json +1 -1
  83. package/plugins/qwenwork/plugin.json +1 -1
  84. package/plugins/workbuddy/config.schema.json +1 -1
  85. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +42 -2
  86. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +23 -3
  87. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  88. package/plugins/workbuddy/package.json +1 -1
  89. package/plugins/workbuddy/plugin.json +1 -1
@@ -1,9 +1,45 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ChatgptCdpClient = exports.userBubbleSelector = exports.turnSelector = exports.editorSelector = void 0;
4
+ exports.chatgptAttachmentPath = chatgptAttachmentPath;
4
5
  const node_child_process_1 = require("node:child_process");
6
+ const node_crypto_1 = require("node:crypto");
7
+ const attachments_1 = require("../../attachments");
5
8
  const session_store_1 = require("./session-store");
6
9
  const cdp_draft_1 = require("../cdp-draft");
10
+ function chatgptAttachmentPath(href, outputDirectory) {
11
+ let path;
12
+ try {
13
+ if (decodeURIComponent(href).split(/[\\/]/).some(part => part === "." || part === ".."))
14
+ return;
15
+ if (href.startsWith("file://")) {
16
+ const url = new URL(href);
17
+ if (url.hostname || url.search || url.hash || url.username || url.password)
18
+ return;
19
+ path = decodeURIComponent(url.pathname);
20
+ }
21
+ else if (href.startsWith("sandbox:/"))
22
+ path = decodeURIComponent(href.slice("sandbox:".length));
23
+ else if (href.startsWith("/") || /^[a-z]:\//i.test(href))
24
+ path = decodeURIComponent(href);
25
+ else
26
+ return;
27
+ }
28
+ catch {
29
+ return;
30
+ }
31
+ if (/[%?#\u0000-\u001f\u007f]/.test(path) || path.startsWith("//") || path.includes("\\"))
32
+ return;
33
+ if (path.split("/").some(part => part === "." || part === ".."))
34
+ return;
35
+ const normalized = /^\/[a-z]:\//i.test(path) ? path.slice(1) : path;
36
+ const unixRoot = `/tmp/${outputDirectory}/`;
37
+ const windowsRoot = normalized.match(/^[a-z]:\/Users\/[^/]+\/AppData\/Local\/Temp\//i)?.[0];
38
+ const prefix = normalized.startsWith(unixRoot) ? unixRoot : windowsRoot && normalized.startsWith(`${windowsRoot}${outputDirectory}/`) ? `${windowsRoot}${outputDirectory}/` : undefined;
39
+ if (!prefix || normalized.slice(prefix.length).split("/").some(part => !part || part.startsWith(".")))
40
+ return;
41
+ return normalized;
42
+ }
7
43
  exports.editorSelector = '[data-codex-composer="true"][contenteditable="true"]';
8
44
  exports.turnSelector = '[data-chatgpt-conversation-turn="true"]';
9
45
  exports.userBubbleSelector = "[data-user-message-bubble]";
@@ -16,6 +52,7 @@ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
16
52
  const sidebarSelector = "[data-app-action-sidebar-thread-id]";
17
53
  const newSessionSelector = 'button,[role="button"]';
18
54
  class ChatgptCdpClient {
55
+ attachmentInput = true;
19
56
  socket;
20
57
  tunnel;
21
58
  sequence = 0;
@@ -58,6 +95,78 @@ class ChatgptCdpClient {
58
95
  })()`);
59
96
  return (0, session_store_1.validSession)(entry) ? entry : undefined;
60
97
  }
98
+ async historyBusy() {
99
+ return Boolean(await this.evaluate(`Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)}))`));
100
+ }
101
+ async readSessionHistory(session) {
102
+ if (!this.connected())
103
+ return { supported: false, reason: "ChatGPT CDP 当前未连接" };
104
+ const before = await this.activeSession();
105
+ if (!before || before.id !== session.id || before.host !== session.host)
106
+ return { supported: false, reason: "当前活动会话身份与请求不一致" };
107
+ if (await this.historyBusy())
108
+ return { supported: false, reason: "ChatGPT 正在生成回复,暂不读取历史" };
109
+ const raw = await this.evaluate(`(() => {
110
+ const selected = document.querySelector('[data-app-action-sidebar-thread-selected="true"], [data-app-action-sidebar-thread-active="true"]');
111
+ if (selected?.getAttribute('data-app-action-sidebar-thread-id') !== ${JSON.stringify(session.id)}
112
+ || selected?.getAttribute('data-app-action-sidebar-thread-host-id') !== ${JSON.stringify(session.host)}) return { supported: false, reason: '读取期间活动会话身份发生变化' };
113
+ if (document.querySelector(${JSON.stringify(stopButtonSelector)})) return { supported: false, reason: 'ChatGPT 正在生成回复,暂不读取历史' };
114
+ const turns = [...document.querySelectorAll(${JSON.stringify(exports.turnSelector)})];
115
+ const output = [];
116
+ const missingReasons = [];
117
+ let chars = 0;
118
+ const collect = (role, text) => {
119
+ chars += text.length;
120
+ if (chars > 60000) throw new Error('ChatGPT 会话正文超过 60,000 字符上限');
121
+ output.push({ role, text });
122
+ };
123
+ for (const turn of turns) {
124
+ const turnId = turn.getAttribute(${JSON.stringify(turnIdAttribute)});
125
+ if (!turnId) {
126
+ missingReasons.push('部分消息缺少可验证的 turn 身份');
127
+ continue;
128
+ }
129
+ const users = [...turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)})];
130
+ for (const item of users) {
131
+ const body = item.querySelector('[data-markdown-text-tone="user-message"]');
132
+ if (!body) {
133
+ missingReasons.push('部分用户消息缺少专用正文标记,已跳过');
134
+ continue;
135
+ }
136
+ const text = body.innerText?.trim();
137
+ if (text) collect('user', text);
138
+ }
139
+ const assistants = [...turn.querySelectorAll(${JSON.stringify(assistantMessageSelector)})];
140
+ for (const item of assistants) {
141
+ const marker = item.closest('[data-response-annotation-target][data-response-annotation-conversation]');
142
+ const excluded = item.closest('[data-message-author-role="tool"], [data-message-author-role="system"], [data-message-channel="analysis"], [data-message-channel="commentary"], [data-reasoning], [data-is-streaming="true"], [data-streaming="true"], [aria-busy="true"]');
143
+ const text = item.innerText?.trim();
144
+ if (marker?.getAttribute('data-response-annotation-conversation') === ${JSON.stringify(session.id.replace(/^local:/, ""))}
145
+ && marker.getAttribute('data-response-annotation-target') && !excluded && text) collect('assistant', text);
146
+ }
147
+ }
148
+ return { supported: true, turns: output, missingReasons };
149
+ })()`);
150
+ const after = await this.activeSession();
151
+ if (!after || after.id !== session.id || after.host !== session.host)
152
+ return { supported: false, reason: "读取期间活动会话身份发生变化" };
153
+ if (await this.historyBusy())
154
+ return { supported: false, reason: "ChatGPT 正在生成回复,暂不读取历史" };
155
+ const result = raw;
156
+ if (result?.supported === false && typeof result.reason === "string")
157
+ return { supported: false, reason: result.reason };
158
+ if (!result || !Array.isArray(result.turns) || !result.turns.every((turn) => {
159
+ if (!turn || typeof turn !== "object")
160
+ return false;
161
+ const value = turn;
162
+ return (value.role === "user" || value.role === "assistant") && typeof value.text === "string";
163
+ })) {
164
+ return { supported: false, reason: "ChatGPT 会话正文格式无法验证" };
165
+ }
166
+ const missingReasons = Array.isArray(result.missingReasons) ? result.missingReasons.filter((item) => typeof item === "string") : [];
167
+ missingReasons.push("仅包含当前页面已加载的可验证正文,页面虚拟化或未加载历史导致完整性无法确认");
168
+ return { supported: true, turns: result.turns, complete: false, missingReasons };
169
+ }
61
170
  async switchSession(session) {
62
171
  await this.recoverDraft();
63
172
  const locate = `[...document.querySelectorAll(${JSON.stringify(sidebarSelector)})].find(item =>
@@ -190,15 +299,55 @@ class ChatgptCdpClient {
190
299
  this.tunnel = undefined;
191
300
  this.rejectPending(new Error("ChatGPT CDP 已关闭"));
192
301
  }
193
- async direct(text, timeoutMs = 240_000) {
302
+ async direct(text, timeoutMs = 240_000, attachments) {
303
+ (0, attachments_1.validateAttachments)(attachments);
304
+ const slot = `__agentBridgeChatgpt_${(0, node_crypto_1.randomUUID)().replaceAll("-", "")}`;
305
+ const outputDirectory = `agent-bridge-chatgpt-${(0, node_crypto_1.randomUUID)()}`;
306
+ const startedAt = Date.now();
307
+ const instruction = `附件回传约定:如果本轮需要返回文件、图片或视频,请把要返回的文件副本写入本轮新建的临时目录(Linux/macOS:/tmp/${outputDirectory};Windows:C:/Users/<当前用户>/AppData/Local/Temp/${outputDirectory}),并在最终回复给出文件链接。仅需文字时无需创建目录。`;
308
+ let failure;
309
+ try {
310
+ return await this.directTurn(`${text} ${instruction}`, timeoutMs, attachments, slot, outputDirectory, startedAt);
311
+ }
312
+ catch (error) {
313
+ failure = error;
314
+ throw error;
315
+ }
316
+ finally {
317
+ if (attachments?.length) {
318
+ try {
319
+ await this.evaluate(`(async () => {
320
+ const state = globalThis[${JSON.stringify(slot)}];
321
+ if (!state) return;
322
+ state.observer?.disconnect();
323
+ for (const button of state.buttons ?? []) if (button.isConnected) button.click();
324
+ try { await Promise.all(state.paths.map(path => state.services.workspaceFiles.releaseTemporaryFile({ path }))); }
325
+ finally { delete globalThis[${JSON.stringify(slot)}]; }
326
+ })()`);
327
+ }
328
+ catch (cleanupError) {
329
+ if (failure === undefined)
330
+ throw cleanupError;
331
+ throw new AggregateError([failure, cleanupError], `${failure instanceof Error ? failure.message : String(failure)};临时附件清理失败`, { cause: failure });
332
+ }
333
+ }
334
+ }
335
+ }
336
+ async directTurn(text, timeoutMs, attachments, slot, outputDirectory, startedAt) {
194
337
  await this.waitFor(`Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}))`, "ChatGPT 桌面端输入框未就绪");
195
338
  await this.recoverDraft();
196
339
  const beforeTurnsJson = await this.evaluate(`JSON.stringify([...document.querySelectorAll(${JSON.stringify(exports.turnSelector)})].map(turn => ({ id: turn.getAttribute(${JSON.stringify(turnIdAttribute)}), users: turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).length })))`);
197
340
  const beforeTurns = JSON.parse(String(beforeTurnsJson || "[]"));
198
341
  const beforeAssistantCount = Number(await this.evaluate(`document.querySelectorAll(${JSON.stringify(assistantMessageSelector)}).length`));
342
+ const baseline = await this.evaluate(`JSON.stringify([...document.querySelectorAll(${JSON.stringify(assistantMessageSelector)})].map(node => {
343
+ const message = node.closest('[data-response-annotation-target][data-response-annotation-conversation]');
344
+ return message ? [message.getAttribute('data-response-annotation-conversation'), message.getAttribute('data-response-annotation-target')] : null;
345
+ }).filter(Boolean))`);
346
+ const beforeMessages = JSON.parse(String(baseline || "[]"));
347
+ const origin = await this.activeSession();
199
348
  const ready = await this.evaluate(`(() => {
200
349
  const editor = document.querySelector(${JSON.stringify(exports.editorSelector)});
201
- if (!editor || editor.innerText.trim()) return false;
350
+ if (!editor || editor.innerText.trim() || document.querySelector('button[aria-label^="Remove "]')) return false;
202
351
  const findButton = () => {
203
352
  for (let parent = editor; parent && parent !== document.body; parent = parent.parentElement) {
204
353
  const buttons = [...parent.querySelectorAll(${JSON.stringify(sendButtonSelector)})];
@@ -213,6 +362,8 @@ class ChatgptCdpClient {
213
362
  })()`);
214
363
  if (!ready)
215
364
  throw new Error("ChatGPT 输入框含有未发送内容,或桌面端发送按钮未就绪");
365
+ if (attachments?.length)
366
+ await this.injectAttachments(attachments, slot);
216
367
  await this.cdp("Input.insertText", { text });
217
368
  const inputMatches = await this.evaluate(`document.querySelector(${JSON.stringify(exports.editorSelector)})?.innerText.trim() === ${JSON.stringify(text.trim())}`);
218
369
  if (!inputMatches)
@@ -239,22 +390,36 @@ class ChatgptCdpClient {
239
390
  while (Date.now() < deadline) {
240
391
  await sleep(800);
241
392
  const raw = await this.evaluate(`(() => {
393
+ const previous = new Set(${JSON.stringify(Array.isArray(beforeMessages) ? beforeMessages : [])}.map(item => JSON.stringify(item)));
394
+ let hasArtifacts = false;
395
+ const artifacts = [...document.querySelectorAll(${JSON.stringify(assistantMessageSelector)})].flatMap(node => {
396
+ const message = node.closest('[data-response-annotation-target][data-response-annotation-conversation]');
397
+ if (!message) return [];
398
+ const conversationId = message.getAttribute('data-response-annotation-conversation');
399
+ const messageId = message.getAttribute('data-response-annotation-target');
400
+ if (!conversationId || !messageId || previous.has(JSON.stringify([conversationId, messageId]))) return [];
401
+ hasArtifacts ||= Boolean(node.querySelector('a[href], img[src], video[src], source[src], [data-file-reference][data-prompt-link-href]'));
402
+ return [{ conversationId, messageId }];
403
+ });
242
404
  const before = ${JSON.stringify(beforeTurns)};
243
405
  const turns = [...document.querySelectorAll(${JSON.stringify(exports.turnSelector)})];
406
+ const userText = node => (node?.querySelector('[data-markdown-text-tone="user-message"]') ?? node)?.innerText.trim().replace(/\\s+/g, ' ');
407
+ const expectedUserText = ${JSON.stringify(text.trim().replace(/\s+/g, " "))};
244
408
  const turn = turns.findLast((item, index) => {
245
409
  const id = item.getAttribute(${JSON.stringify(turnIdAttribute)});
246
410
  const oldIndex = before.findIndex(entry => entry.id === id);
247
411
  const old = oldIndex < 0 ? undefined : before[oldIndex];
248
412
  const users = item.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)});
249
413
  const fresh = oldIndex < 0 || index >= before.length || users.length > (old?.users ?? 0);
250
- return fresh && users.item(users.length - 1)?.innerText.trim() === ${JSON.stringify(text.trim())};
414
+ return fresh && userText(users.item(users.length - 1)) === expectedUserText;
251
415
  });
252
416
  if (!turn) {
253
417
  const assistants = [...document.querySelectorAll(${JSON.stringify(assistantMessageSelector)})];
254
418
  const fallback = assistants.length > ${beforeAssistantCount}
255
419
  ? assistants.at(-1)?.innerText.trim() ?? ""
256
420
  : "";
257
- return JSON.stringify({ reply: fallback, active: Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)})) });
421
+ const submitted = [...document.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)})].some(node => userText(node) === expectedUserText);
422
+ return JSON.stringify({ reply: fallback, artifacts, hasArtifacts, submitted, active: Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)})) });
258
423
  }
259
424
  const user = turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).item(turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).length - 1);
260
425
  const unit = user?.closest(${JSON.stringify(contentUnitSelector)});
@@ -264,14 +429,20 @@ class ChatgptCdpClient {
264
429
  const legacyReplies = [...turn.querySelectorAll('[data-message-author-role="assistant"]')].map(item => item.innerText.trim()).filter(Boolean);
265
430
  const reply = replies.at(-1) ?? legacyReplies.at(-1) ?? "";
266
431
  const active = Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)}));
267
- return JSON.stringify({ reply, active });
432
+ return JSON.stringify({ reply, active, artifacts, hasArtifacts, submitted: true });
268
433
  })()`);
269
434
  const state = JSON.parse(String(raw));
270
435
  const reply = state.reply.trim();
271
- stable = reply && reply === previous ? stable + 1 : 0;
272
- previous = reply;
273
- if (reply && stable >= 2 && !state.active)
274
- return reply;
436
+ const signature = JSON.stringify([reply, state.artifacts, state.hasArtifacts]);
437
+ stable = (reply || state.hasArtifacts) && signature === previous ? stable + 1 : 0;
438
+ previous = signature;
439
+ if ((reply || state.hasArtifacts) && stable >= 2 && !state.active) {
440
+ const output = state.hasArtifacts && state.artifacts?.length ? await this.readArtifacts(state.artifacts, origin, outputDirectory, startedAt, state.submitted) : [];
441
+ if (output.length)
442
+ return { text: reply, attachments: output };
443
+ if (reply)
444
+ return reply;
445
+ }
275
446
  }
276
447
  throw new Error("ChatGPT UI 回复超时");
277
448
  }
@@ -315,6 +486,168 @@ class ChatgptCdpClient {
315
486
  recoverDraft() {
316
487
  return (0, cdp_draft_1.recoverCdpDraft)(this.evaluate.bind(this), exports.editorSelector, stopButtonSelector, this.dataDir);
317
488
  }
489
+ async injectAttachments(items, slot) {
490
+ const files = items.map(item => ({ filename: (0, attachments_1.attachmentFilename)(item.filename), contentType: item.contentType, data: Buffer.from(item.data).toString("base64") }));
491
+ await this.evaluate(String.raw `(async () => {
492
+ const editor = document.querySelector(${JSON.stringify(exports.editorSelector)});
493
+ if (!editor || document.querySelector('button[aria-label^="Remove "]')) throw new Error('ChatGPT 输入框已有附件或未就绪,未发送消息');
494
+ const entry = [...document.scripts].find(script => /\/assets\/index-[^/]+\.js$/.test(script.src));
495
+ if (!entry) throw new Error('ChatGPT 桌面原生接口入口未找到');
496
+ const source = await (await fetch(entry.src)).text();
497
+ const rpc = source.match(/["'](\.\/rpc-[\w-]+\.js)["']/);
498
+ if (!rpc) throw new Error('ChatGPT 桌面原生附件接口未找到');
499
+ const { appServices: services } = await import(new URL(rpc[1], entry.src).href);
500
+ const state = { services, paths: [], buttons: new Set(), observer: null }; globalThis[${JSON.stringify(slot)}] = state;
501
+ const transfer = new DataTransfer();
502
+ for (const item of ${JSON.stringify(files)}) {
503
+ const binary = atob(item.data); const bytes = new Uint8Array(binary.length);
504
+ for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
505
+ const result = await services.workspaceFiles.createTemporaryFile({ bytes, fileName: item.filename });
506
+ if (!result?.path) throw new Error('ChatGPT 临时附件创建失败');
507
+ state.paths.push(result.path);
508
+ const file = new File([bytes], item.filename, { type: item.contentType });
509
+ Object.defineProperty(file, 'path', { value: result.path }); transfer.items.add(file);
510
+ }
511
+ const names = ${JSON.stringify(files.map(file => `Remove ${file.filename}`))};
512
+ const capture = () => {
513
+ for (const button of document.querySelectorAll('button[aria-label^="Remove "]')) {
514
+ if (names.includes(button.getAttribute('aria-label'))) state.buttons.add(button);
515
+ }
516
+ };
517
+ state.observer = new MutationObserver(capture);
518
+ state.observer.observe(document.body, { childList: true, subtree: true });
519
+ editor.dispatchEvent(new ClipboardEvent('paste', { bubbles: true, cancelable: true, clipboardData: transfer }));
520
+ capture();
521
+ })()`);
522
+ await this.waitFor(`(() => {
523
+ const names = ${JSON.stringify(files.map(file => `Remove ${file.filename}`))};
524
+ const current = [...document.querySelectorAll('button[aria-label^="Remove "]')].map(node => node.getAttribute('aria-label'));
525
+ const ready = current.length === names.length && names.every(name => current.filter(value => value === name).length === names.filter(value => value === name).length);
526
+ if (ready) globalThis[${JSON.stringify(slot)}]?.observer?.disconnect();
527
+ return ready;
528
+ })()`, "ChatGPT 附件上传未确认");
529
+ }
530
+ async readArtifacts(messages, origin, outputDirectory, startedAt, submitted = false) {
531
+ const active = await this.activeSession();
532
+ if (!origin || !active || active.host !== origin.host)
533
+ throw new Error("ChatGPT 附件所属会话无法确认");
534
+ if (active.id !== origin.id)
535
+ throw new Error("ChatGPT 会话已变化,未读取输出附件");
536
+ let conversationId = active.id.replace(/^local:/, "");
537
+ if (conversationId.startsWith("client-new-thread:")) {
538
+ const ids = [...new Set(messages.map(message => message.conversationId))];
539
+ if (active.id !== origin.id || !submitted || ids.length !== 1 || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(ids[0]))
540
+ throw new Error("ChatGPT 新会话身份尚未确认,未读取输出附件");
541
+ conversationId = ids[0];
542
+ }
543
+ const scoped = messages.filter(message => typeof message.conversationId === "string" && typeof message.messageId === "string"
544
+ && message.conversationId === conversationId);
545
+ const sessionGuard = `const selected = document.querySelector('[data-app-action-sidebar-thread-selected="true"], [data-app-action-sidebar-thread-active="true"]');
546
+ if (selected?.getAttribute('data-app-action-sidebar-thread-id') !== ${JSON.stringify(active.id)}
547
+ || selected?.getAttribute('data-app-action-sidebar-thread-host-id') !== ${JSON.stringify(active.host)}) throw new Error('ChatGPT 会话已变化,未读取输出附件');`;
548
+ const references = await this.evaluate(`(() => {
549
+ ${sessionGuard}
550
+ const messages = ${JSON.stringify(scoped)};
551
+ const references = [];
552
+ for (const node of document.querySelectorAll(${JSON.stringify(assistantMessageSelector)})) {
553
+ const message = node.closest('[data-response-annotation-target][data-response-annotation-conversation]');
554
+ if (!message || !messages.some(item => item.conversationId === message.getAttribute('data-response-annotation-conversation') && item.messageId === message.getAttribute('data-response-annotation-target'))) continue;
555
+ for (const link of node.querySelectorAll('a[href], img[src], video[src], source[src], [data-file-reference][data-prompt-link-href]')) {
556
+ const href = link.getAttribute('href') ?? link.getAttribute('src') ?? link.getAttribute('data-prompt-link-href');
557
+ if (href) references.push(href);
558
+ }
559
+ }
560
+ return references;
561
+ })()`);
562
+ const paths = [...new Set(references.map(href => chatgptAttachmentPath(href, outputDirectory)).filter((path) => Boolean(path)))];
563
+ if (paths.length > attachments_1.MAX_ATTACHMENTS)
564
+ throw new Error("单条消息最多包含 8 个附件");
565
+ const output = [];
566
+ let total = 0;
567
+ for (const path of paths) {
568
+ const fileSlot = `__agentBridgeChatgptOutput_${(0, node_crypto_1.randomUUID)().replaceAll("-", "")}`;
569
+ let file;
570
+ let readFailure;
571
+ try {
572
+ const metadata = await this.evaluate(`(async () => {
573
+ ${sessionGuard}
574
+ globalThis[${JSON.stringify(fileSlot)}] = '';
575
+ const call = (name, params) => new Promise((resolve, reject) => {
576
+ const requestId = crypto.randomUUID();
577
+ const finish = () => { clearTimeout(timer); window.removeEventListener('message', receive); };
578
+ const receive = event => {
579
+ const response = event.data;
580
+ if ((event.source !== window && !(event.source === null && event.origin === '')) || response?.type !== 'fetch-response' || response.requestId !== requestId) return;
581
+ finish();
582
+ if (response.responseType !== 'success' || response.status !== 200) { reject(new Error('ChatGPT 输出附件读取失败')); return; }
583
+ try { resolve(JSON.parse(response.bodyJsonString)); } catch { reject(new Error('ChatGPT 输出附件响应无效')); }
584
+ };
585
+ const timer = setTimeout(() => { finish(); reject(new Error('ChatGPT 输出附件读取超时')); }, ${this.timeoutMs});
586
+ window.addEventListener('message', receive);
587
+ try { window.electronBridge.sendMessageFromView({ type: 'fetch', requestId, method: 'POST', url: 'vscode://codex/' + name, body: JSON.stringify(params) }); }
588
+ catch (error) { finish(); reject(error); }
589
+ });
590
+ const params = { hostId: ${JSON.stringify(active.host)}, path: ${JSON.stringify(path)} };
591
+ const metadata = await call('read-file-metadata', params);
592
+ if (!metadata.isFile || !Number.isSafeInteger(metadata.sizeBytes) || metadata.sizeBytes <= 0) throw new Error('ChatGPT 输出附件元数据无效');
593
+ if (!Number.isFinite(metadata.createdAtMs) || metadata.createdAtMs < ${startedAt - 5000}) throw new Error('ChatGPT 输出附件不是本轮创建的文件');
594
+ if (metadata.sizeBytes > ${attachments_1.MAX_ATTACHMENT_BYTES}) throw new Error('单个附件不能超过 32 MiB');
595
+ if (metadata.sizeBytes > ${attachments_1.MAX_TOTAL_ATTACHMENT_BYTES - total}) throw new Error('单条消息附件总大小不能超过 64 MiB');
596
+ { ${sessionGuard} }
597
+ const entry = [...document.scripts].find(script => /\\/assets\\/index-[^/]+\\.js$/.test(script.src));
598
+ if (!entry) throw new Error('ChatGPT 桌面原生接口入口未找到');
599
+ const source = await (await fetch(entry.src)).text();
600
+ const rpc = source.match(/["'](\\.\\/rpc-[\\w-]+\\.js)["']/);
601
+ if (!rpc) throw new Error('ChatGPT 桌面原生附件接口未找到');
602
+ const { appServices: services } = await import(new URL(rpc[1], entry.src).href);
603
+ { ${sessionGuard} }
604
+ const file = await services.workspaceFiles.read({ ...params, representation: 'blob' });
605
+ { ${sessionGuard} }
606
+ if (globalThis[${JSON.stringify(fileSlot)}] !== '') throw new Error('ChatGPT 输出附件读取已取消');
607
+ if (typeof file.blob !== 'string' || file.blob.length !== Math.ceil(metadata.sizeBytes / 3) * 4) throw new Error('ChatGPT 输出附件内容无效');
608
+ globalThis[${JSON.stringify(fileSlot)}] = file.blob;
609
+ return { length: file.blob.length, size: metadata.sizeBytes };
610
+ })()`);
611
+ if (!Number.isSafeInteger(metadata.length) || metadata.length <= 0 || metadata.length > Math.ceil(attachments_1.MAX_ATTACHMENT_BYTES / 3) * 4)
612
+ throw new Error("ChatGPT 输出附件内容无效");
613
+ const chunks = [];
614
+ for (let offset = 0; offset < metadata.length; offset += 256 * 1024) {
615
+ const chunk = await this.evaluate(`(() => { ${sessionGuard} return globalThis[${JSON.stringify(fileSlot)}]?.slice(${offset}, ${offset + 256 * 1024}); })()`);
616
+ if (typeof chunk !== "string" || chunk.length !== Math.min(256 * 1024, metadata.length - offset))
617
+ throw new Error("ChatGPT 输出附件分块读取失败");
618
+ chunks.push(chunk);
619
+ }
620
+ file = { ...metadata, contentsBase64: chunks.join("") };
621
+ }
622
+ catch (error) {
623
+ readFailure = error;
624
+ throw error;
625
+ }
626
+ finally {
627
+ try {
628
+ await this.evaluate(`delete globalThis[${JSON.stringify(fileSlot)}]`);
629
+ }
630
+ catch (cleanupError) {
631
+ if (readFailure === undefined)
632
+ throw cleanupError;
633
+ throw new AggregateError([readFailure, cleanupError], `${readFailure instanceof Error ? readFailure.message : String(readFailure)};输出附件缓存清理失败`, { cause: readFailure });
634
+ }
635
+ }
636
+ if (typeof file.contentsBase64 !== "string" || file.contentsBase64.length > Math.ceil(attachments_1.MAX_ATTACHMENT_BYTES / 3) * 4)
637
+ throw new Error("ChatGPT 输出附件内容无效");
638
+ const data = Buffer.from(file.contentsBase64, "base64");
639
+ if (data.toString("base64") !== file.contentsBase64 || data.byteLength !== file.size)
640
+ throw new Error("ChatGPT 输出附件大小或编码不一致");
641
+ const filename = (0, attachments_1.attachmentFilename)(path);
642
+ const imageType = (0, attachments_1.detectImageContentType)(data);
643
+ const types = { txt: "text/plain", md: "text/markdown", pdf: "application/pdf", json: "application/json", zip: "application/zip", mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime", mp3: "audio/mpeg", wav: "audio/wav" };
644
+ const contentType = imageType ?? file.mimeType ?? types[filename.split(".").at(-1)?.toLowerCase() ?? ""] ?? "application/octet-stream";
645
+ output.push({ kind: imageType ? "image" : "file", data, filename, contentType });
646
+ total += data.byteLength;
647
+ (0, attachments_1.validateAttachments)(output);
648
+ }
649
+ return output;
650
+ }
318
651
  async waitFor(expression, message) {
319
652
  const deadline = Date.now() + this.timeoutMs;
320
653
  while (Date.now() < deadline) {
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ChatgptCdpClient = exports.ChatgptPlugin = exports.chatgptManifest = void 0;
4
+ const node_crypto_1 = require("node:crypto");
4
5
  const cdp_client_1 = require("./cdp-client");
5
6
  const session_store_1 = require("./session-store");
6
7
  exports.chatgptManifest = {
7
8
  id: "chatgpt",
8
- version: "1.0.6",
9
+ version: "1.0.9",
9
10
  displayName: "ChatGPT(Codex)",
10
11
  type: "agent",
11
12
  entry: "dist/index.js",
@@ -23,15 +24,32 @@ class ChatgptPlugin {
23
24
  options;
24
25
  manifest = exports.chatgptManifest;
25
26
  sessionOperations = [];
27
+ get attachmentInput() { return this.client?.attachmentInput === true; }
26
28
  client;
27
29
  failure;
28
30
  remote = false;
29
31
  queue = Promise.resolve();
32
+ queuedOperations = 0;
30
33
  store;
31
34
  logger;
35
+ historyScope = "";
36
+ historyGeneration = 0;
37
+ historyProvider;
32
38
  constructor(options = {}) {
33
39
  this.options = options;
34
40
  }
41
+ get sessionHistory() {
42
+ const client = this.client;
43
+ const unsupported = !client?.listSessions || !client.activeSession || !client.readSessionHistory;
44
+ const generation = this.historyGeneration;
45
+ return this.historyProvider ??= {
46
+ scope: this.historyScope,
47
+ mode: unsupported ? "unsupported" : "sidebar",
48
+ limitation: unsupported ? "当前 ChatGPT 客户端不提供只读历史接口" : "仅列举已加载侧栏;仅读取当前活动会话中已加载且可验证的正文,完整性可能受页面虚拟化影响",
49
+ listSessions: query => generation === this.historyGeneration ? this.listNativeSessions(query) : Promise.resolve({ supported: false, reason: "ChatGPT 插件实例已变化,请重新列举历史" }),
50
+ readSession: query => generation === this.historyGeneration ? this.readNativeSession(query) : Promise.resolve({ supported: false, reason: "ChatGPT 插件实例已变化,请重新列举历史" }),
51
+ };
52
+ }
35
53
  async activate(context) {
36
54
  const config = context.config;
37
55
  const hasSshHost = config.sshHost !== undefined;
@@ -52,6 +70,11 @@ class ChatgptPlugin {
52
70
  localPort: port(config.localCdpPort, 9230),
53
71
  } : undefined;
54
72
  this.remote = Boolean(ssh);
73
+ this.historyGeneration++;
74
+ this.historyProvider = undefined;
75
+ this.historyScope = (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(ssh
76
+ ? [ssh.host, ssh.user, ssh.port, ssh.remotePort]
77
+ : ["local", port(config.localCdpPort, 9230)])).digest("hex").slice(0, 24);
55
78
  this.logger = context.logger;
56
79
  this.store = new session_store_1.ChatgptSessionStore(context.dataDir, JSON.stringify(ssh
57
80
  ? [ssh.host, ssh.user, ssh.port, ssh.remotePort]
@@ -92,9 +115,9 @@ class ChatgptPlugin {
92
115
  throw new Error("消息内容不能为空");
93
116
  if (!this.client || !this.store)
94
117
  throw new Error("ChatGPT 插件未启动");
95
- const run = this.queue.then(() => this.run(request));
96
- this.queue = run.then(() => undefined, () => undefined);
97
- return { text: await run, sessionId: request.sessionId };
118
+ const run = this.enqueue(() => this.run(request));
119
+ const result = await run;
120
+ return { ...(typeof result === "string" ? { text: result } : result), sessionId: request.sessionId };
98
121
  }
99
122
  async controlSession(request) {
100
123
  if (request.operation !== "new")
@@ -104,7 +127,7 @@ class ChatgptPlugin {
104
127
  throw new Error("ChatGPT 新会话缺少 sessionId");
105
128
  if (!this.client?.createSession || !this.client.listSessions || !this.store)
106
129
  throw new Error("ChatGPT 当前传输不支持新建会话");
107
- const run = this.queue.then(async () => {
130
+ const run = this.enqueue(async () => {
108
131
  const existing = new Set((await this.client.listSessions()).map(item => JSON.stringify([item.host, item.id])));
109
132
  const created = await this.client.createSession(request.name);
110
133
  if (created) {
@@ -118,9 +141,106 @@ class ChatgptPlugin {
118
141
  this.persist();
119
142
  return { text: "ChatGPT 已创建新会话", sessionId };
120
143
  });
144
+ return run;
145
+ }
146
+ enqueue(work) {
147
+ this.queuedOperations++;
148
+ const run = this.queue.then(work).finally(() => { this.queuedOperations--; });
121
149
  this.queue = run.then(() => undefined, () => undefined);
122
150
  return run;
123
151
  }
152
+ async listNativeSessions(query) {
153
+ const client = this.client;
154
+ const reject = (reason) => ({ supported: false, reason });
155
+ if (!client?.listSessions || !client.activeSession || !client.readSessionHistory)
156
+ return reject("当前 ChatGPT 客户端不支持只读历史");
157
+ const listSessions = client.listSessions.bind(client);
158
+ if (!client.connected())
159
+ return reject("ChatGPT CDP 当前未连接");
160
+ if (this.queuedOperations)
161
+ return reject("ChatGPT 正在处理其他操作,历史读取已拒绝");
162
+ if (!Number.isInteger(query.limit) || query.limit < 1)
163
+ throw new Error("ChatGPT 历史页条数无效");
164
+ if (query.cursor !== undefined && !/^list:[a-f0-9]{64}:\d+$/.test(query.cursor))
165
+ throw new Error("ChatGPT 历史游标无效");
166
+ return this.enqueue(async () => {
167
+ if (!client.connected())
168
+ return reject("ChatGPT CDP 当前未连接");
169
+ if (await client.historyBusy?.())
170
+ return reject("ChatGPT 正在生成回复,暂不读取历史");
171
+ const entries = await listSessions();
172
+ const fingerprint = (0, node_crypto_1.createHash)("sha256").update(JSON.stringify([this.historyScope, this.historyGeneration, entries.map(({ host, id, title }) => [host, id, title])])).digest("hex");
173
+ const offset = query.cursor ? Number(query.cursor.split(":").at(-1)) : 0;
174
+ if (query.cursor !== undefined && !query.cursor.startsWith(`list:${fingerprint}:`))
175
+ throw new Error("ChatGPT 侧栏已变化,历史游标失效");
176
+ if (!Number.isSafeInteger(offset) || (query.cursor !== undefined && offset >= entries.length))
177
+ throw new Error("ChatGPT 历史游标位置无效");
178
+ const limit = Math.min(query.limit, 50);
179
+ const sessions = entries.slice(offset, offset + limit).map(item => ({ id: JSON.stringify([item.host, item.id]), title: item.title, ...(item.updatedAt ? { updatedAt: item.updatedAt } : {}) }));
180
+ return { supported: true, sessions, ...(offset + limit < entries.length ? { nextCursor: `list:${fingerprint}:${offset + limit}` } : {}), complete: false, missingReasons: ["仅包含 ChatGPT 当前已加载的侧栏项目,不代表全部历史会话"], readAt: new Date().toISOString() };
181
+ });
182
+ }
183
+ async readNativeSession(query) {
184
+ const client = this.client;
185
+ const reject = (reason) => ({ supported: false, reason });
186
+ if (!client?.listSessions || !client.activeSession || !client.readSessionHistory)
187
+ return reject("当前 ChatGPT 客户端不支持只读历史");
188
+ const listSessions = client.listSessions.bind(client);
189
+ const activeSession = client.activeSession.bind(client);
190
+ const readSessionHistory = client.readSessionHistory.bind(client);
191
+ if (!client.connected())
192
+ return reject("ChatGPT CDP 当前未连接");
193
+ if (this.queuedOperations)
194
+ return reject("ChatGPT 正在处理其他操作,历史读取已拒绝");
195
+ if (!Number.isInteger(query.limit) || query.limit < 1)
196
+ throw new Error("ChatGPT 历史页条数无效");
197
+ if (query.cursor !== undefined && !/^read:[a-f0-9]{64}:\d+$/.test(query.cursor))
198
+ throw new Error("ChatGPT 历史游标无效");
199
+ let requested;
200
+ try {
201
+ requested = JSON.parse(query.sessionId);
202
+ }
203
+ catch {
204
+ return reject("ChatGPT 会话 ID 无效");
205
+ }
206
+ if (!Array.isArray(requested) || requested.length !== 2 || requested.some(part => typeof part !== "string" || !part))
207
+ return reject("ChatGPT 会话 ID 无效");
208
+ return this.enqueue(async () => {
209
+ if (!client.connected())
210
+ return reject("ChatGPT CDP 当前未连接");
211
+ if (await client.historyBusy?.())
212
+ return reject("ChatGPT 正在生成回复,暂不读取历史");
213
+ const sessions = await listSessions();
214
+ const session = sessions.find(item => item.host === requested[0] && item.id === requested[1]);
215
+ if (!session)
216
+ return reject("目标会话不在当前 ChatGPT 侧栏中");
217
+ const active = await activeSession();
218
+ if (!active || active.host !== session.host || active.id !== session.id)
219
+ return reject("ChatGPT 只支持读取当前活动会话");
220
+ if (await client.historyBusy?.())
221
+ return reject("ChatGPT 正在生成回复,暂不读取历史");
222
+ const result = await readSessionHistory(session);
223
+ if (!result.supported)
224
+ return reject(result.reason);
225
+ const after = await activeSession();
226
+ if (!after || after.host !== session.host || after.id !== session.id)
227
+ return reject("读取期间活动会话身份发生变化");
228
+ const fingerprint = (0, node_crypto_1.createHash)("sha256").update(JSON.stringify([this.historyScope, this.historyGeneration, session.host, session.id, result.turns])).digest("hex");
229
+ const offset = query.cursor ? Number(query.cursor.split(":").at(-1)) : 0;
230
+ if (query.cursor !== undefined && !query.cursor.startsWith(`read:${fingerprint}:`))
231
+ throw new Error("ChatGPT 会话内容已变化,历史游标失效");
232
+ if (!Number.isSafeInteger(offset) || (query.cursor !== undefined && offset >= result.turns.length))
233
+ throw new Error("ChatGPT 历史游标位置无效");
234
+ const turns = result.turns.map(turn => ({ ...turn }));
235
+ const chars = turns.reduce((total, turn) => total + turn.text.length, 0);
236
+ if (chars > 60_000)
237
+ throw new Error("ChatGPT 会话正文超过 60,000 字符上限");
238
+ const limit = Math.min(query.limit, 100);
239
+ const pageTurns = turns.slice(offset, offset + limit);
240
+ const reasons = [...result.missingReasons, "仅包含当前页面已加载的可验证正文,页面虚拟化或未加载历史导致完整性无法确认"];
241
+ return { supported: true, session: { id: query.sessionId, title: session.title, ...(session.updatedAt ? { updatedAt: session.updatedAt } : {}) }, turns: pageTurns, ...(offset + limit < turns.length ? { nextCursor: `read:${fingerprint}:${offset + limit}` } : {}), complete: false, missingReasons: reasons, readAt: new Date().toISOString() };
242
+ });
243
+ }
124
244
  persist() {
125
245
  void this.store.save().catch(error => this.logger?.warn("ChatGPT 会话缓存保存失败", { error: String(error) }));
126
246
  }
@@ -204,7 +324,7 @@ class ChatgptPlugin {
204
324
  await this.client.connect();
205
325
  selected ??= await this.client.activeSession?.();
206
326
  const message = explicit?.[2]?.trim() || request.task;
207
- const reply = await this.client.direct(message, request.timeoutMs);
327
+ const reply = await this.client.direct(message, request.timeoutMs, request.attachments);
208
328
  const baseline = request.sessionId ? store.pendingBindings[request.sessionId] : undefined;
209
329
  if (baseline) {
210
330
  try {
@@ -226,7 +346,7 @@ class ChatgptPlugin {
226
346
  if (selected) {
227
347
  const current = await this.client.activeSession?.().catch(() => undefined);
228
348
  const entry = current?.id === selected.id && current.host === selected.host ? current : selected;
229
- store.remember(entry, message, reply);
349
+ store.remember(entry, message, typeof reply === "string" ? reply : reply.text);
230
350
  if (request.sessionId)
231
351
  store.bindings[request.sessionId] = entry;
232
352
  this.persist();
@@ -1 +1 @@
1
- {"name":"agent-bridge-chatgpt","version":"1.0.6","description":"agent-bridge 的 ChatGPT Codex CDP 插件。","keywords":["mu-agent-bridge-plugin","mu-agent-bridge-agent"],"main":"dist/src-ts/plugins/chatgpt/index.js","files":["dist/","plugin.json","config.schema.json","README.md"],"agentBridge":{"plugin":"./plugin.json","type":"agent"},"engines":{"node":">=22"}}
1
+ {"name":"agent-bridge-chatgpt","version":"1.0.9","description":"agent-bridge 的 ChatGPT Codex CDP 插件。","keywords":["mu-agent-bridge-plugin","mu-agent-bridge-agent"],"main":"dist/src-ts/plugins/chatgpt/index.js","files":["dist/","plugin.json","config.schema.json","README.md"],"agentBridge":{"plugin":"./plugin.json","type":"agent"},"engines":{"node":">=22"}}