chatgpt-pro-research 0.2.0 → 0.2.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.
Files changed (2) hide show
  1. package/lib/index.js +78 -15
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/plugin.ts
4
4
  import { homedir } from "node:os";
5
- import { dirname as dirname2, extname, isAbsolute, join as join3, resolve as resolve2, sep } from "node:path";
5
+ import { dirname as dirname2, extname, isAbsolute, join as join3, resolve as resolve3, sep } from "node:path";
6
6
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import z from "@deepseek-ai/schemastery";
@@ -597,7 +597,7 @@ var ResearchEngine = class {
597
597
  if (this.active !== void 0) {
598
598
  await this.active.promise;
599
599
  } else {
600
- await new Promise((resolve3) => setTimeout(resolve3, 0));
600
+ await new Promise((resolve4) => setTimeout(resolve4, 0));
601
601
  }
602
602
  }
603
603
  }
@@ -838,14 +838,16 @@ ${result.summary}
838
838
  if (topic.status === "completed" || topic.status === "cancelled") return;
839
839
  if (error instanceof TransportError) {
840
840
  if (error.failure === "worker") {
841
- await this.transition(topic, "failed");
841
+ await this.transition(topic, "failed", { stopReason: error.message });
842
842
  return;
843
843
  }
844
844
  const reason = error.failure === "login" || error.failure === "upload" || error.failure === "ui" ? error.failure : "timeout";
845
- await this.transition(topic, "stalled", { stallReason: reason });
845
+ await this.transition(topic, "stalled", { stallReason: reason, stopReason: error.message });
846
846
  return;
847
847
  }
848
- await this.transition(topic, "failed");
848
+ await this.transition(topic, "failed", {
849
+ stopReason: error instanceof Error ? error.message : String(error)
850
+ });
849
851
  }
850
852
  async transition(topic, next, extra) {
851
853
  assertTransition(topic.status, next);
@@ -901,7 +903,15 @@ var SELECTORS = {
901
903
  '[data-markdown-text-style="assistant-message"]',
902
904
  '[data-message-author-role="assistant"]'
903
905
  ].join(", "),
904
- fileInput: 'input[type="file"]',
906
+ // The composer has several file inputs (camera, photos, videos). The general
907
+ // attachment input is the one without an `accept` filter; the camera input
908
+ // rejects multiple files.
909
+ fileInput: [
910
+ 'input[type="file"][aria-label="\u9644\u52A0\u6587\u4EF6"]',
911
+ 'input[type="file"][aria-label*="Attach" i]:not([capture])',
912
+ 'form input[type="file"][multiple]:not([accept]):not([capture])',
913
+ 'input[type="file"][multiple]:not([accept]):not([capture])'
914
+ ].join(", "),
905
915
  modelPicker: [
906
916
  'button[aria-label*="\u9009\u62E9 ChatGPT \u6A21\u578B"]',
907
917
  'button[aria-label*="\u6A21\u578B"]',
@@ -947,6 +957,7 @@ async function firstVisible(page, selector, timeoutMs) {
947
957
  }
948
958
  var ChatGptWebTransport = class {
949
959
  context;
960
+ launching;
950
961
  submissions = /* @__PURE__ */ new Map();
951
962
  options;
952
963
  constructor(options) {
@@ -957,6 +968,15 @@ var ChatGptWebTransport = class {
957
968
  }
958
969
  async ensureContext() {
959
970
  if (this.context !== void 0) return this.context;
971
+ if (this.launching !== void 0) return this.launching;
972
+ this.launching = this.launchContext();
973
+ try {
974
+ return await this.launching;
975
+ } finally {
976
+ this.launching = void 0;
977
+ }
978
+ }
979
+ async launchContext() {
960
980
  await mkdir2(this.options.profileDir, { recursive: true });
961
981
  const base = {
962
982
  headless: this.options.headless ?? false,
@@ -964,7 +984,12 @@ var ChatGptWebTransport = class {
964
984
  args: ["--disable-blink-features=AutomationControlled"]
965
985
  };
966
986
  try {
967
- this.context = await chromium.launchPersistentContext(this.options.profileDir, { ...base, channel: "chrome" });
987
+ const context = await chromium.launchPersistentContext(this.options.profileDir, { ...base, channel: "chrome" });
988
+ context.on("close", () => {
989
+ if (this.context === context) this.context = void 0;
990
+ });
991
+ this.context = context;
992
+ return context;
968
993
  } catch (error) {
969
994
  throw new TransportError(
970
995
  `cannot launch Chrome with profile ${this.options.profileDir}; install Google Chrome or pass browser.headless=false plus a Playwright browser. ${error instanceof Error ? error.message : String(error)}`,
@@ -972,7 +997,6 @@ var ChatGptWebTransport = class {
972
997
  { cause: error }
973
998
  );
974
999
  }
975
- return this.context;
976
1000
  }
977
1001
  async probe() {
978
1002
  const context = await this.ensureContext();
@@ -1120,9 +1144,9 @@ ${attachments.join("\n")}`);
1120
1144
  const dataUrl = await page.evaluate(async (src) => {
1121
1145
  const response = await fetch(src);
1122
1146
  const blob = await response.blob();
1123
- return await new Promise((resolve3, reject) => {
1147
+ return await new Promise((resolve4, reject) => {
1124
1148
  const reader = new FileReader();
1125
- reader.onload = () => resolve3(String(reader.result));
1149
+ reader.onload = () => resolve4(String(reader.result));
1126
1150
  reader.onerror = () => reject(reader.error);
1127
1151
  reader.readAsDataURL(blob);
1128
1152
  });
@@ -1224,6 +1248,41 @@ ${attachments.join("\n")}`);
1224
1248
  }
1225
1249
  };
1226
1250
 
1251
+ // ../transport/src/shared.ts
1252
+ import { resolve as resolve2 } from "node:path";
1253
+ var REGISTRY = /* @__PURE__ */ Symbol.for("dsh-pro.transport.shared.v1");
1254
+ function registry() {
1255
+ const holder = globalThis;
1256
+ const existing = holder[REGISTRY];
1257
+ if (existing instanceof Map) return existing;
1258
+ const created = /* @__PURE__ */ new Map();
1259
+ holder[REGISTRY] = created;
1260
+ return created;
1261
+ }
1262
+ function acquireSharedTransport(options, create = () => new ChatGptWebTransport(options)) {
1263
+ const key = resolve2(options.profileDir);
1264
+ const slots = registry();
1265
+ let slot = slots.get(key);
1266
+ if (slot === void 0) {
1267
+ slot = { transport: create(), refs: 0 };
1268
+ slots.set(key, slot);
1269
+ }
1270
+ slot.refs += 1;
1271
+ const target = slot;
1272
+ let released = false;
1273
+ return {
1274
+ transport: target.transport,
1275
+ release: () => {
1276
+ if (released) return;
1277
+ released = true;
1278
+ target.refs -= 1;
1279
+ if (target.refs > 0) return;
1280
+ if (slots.get(key) === target) slots.delete(key);
1281
+ void target.transport.close();
1282
+ }
1283
+ };
1284
+ }
1285
+
1227
1286
  // src/plugin.ts
1228
1287
  var Config = z.object({
1229
1288
  rounds: z.number().step(1).min(1).max(10).default(3).description("\u6BCF\u4E3B\u9898\u9ED8\u8BA4\u8F6E\u6B21\u4E0A\u9650\uFF081\u201310\uFF09\u3002"),
@@ -1331,6 +1390,7 @@ var ProResearchService = class {
1331
1390
  stores = /* @__PURE__ */ new Map();
1332
1391
  topicSessions = /* @__PURE__ */ new Map();
1333
1392
  transport;
1393
+ shared;
1334
1394
  rootFor(cwd) {
1335
1395
  return cwd.trim() === "" ? process.cwd() : cwd;
1336
1396
  }
@@ -1354,10 +1414,11 @@ var ProResearchService = class {
1354
1414
  }
1355
1415
  transportFor() {
1356
1416
  if (this.transport !== void 0) return this.transport;
1357
- this.transport = new ChatGptWebTransport({
1417
+ this.shared = acquireSharedTransport({
1358
1418
  profileDir: this.config.browser.profileDir,
1359
1419
  headless: this.config.browser.headless
1360
1420
  });
1421
+ this.transport = this.shared.transport;
1361
1422
  return this.transport;
1362
1423
  }
1363
1424
  judge() {
@@ -1532,8 +1593,8 @@ ${input.answer.slice(0, 3e4)}`
1532
1593
  if (entry === void 0) throw new Error(`unknown topic: ${id}`);
1533
1594
  const store = this.storeAt(entry.root, entry.mode);
1534
1595
  const dir = await store.dirOf(id);
1535
- const artifactsRoot = resolve2(dir, "artifacts");
1536
- const target = resolve2(dir, relativePath);
1596
+ const artifactsRoot = resolve3(dir, "artifacts");
1597
+ const target = resolve3(dir, relativePath);
1537
1598
  if (target !== artifactsRoot && !target.startsWith(`${artifactsRoot}${sep}`)) {
1538
1599
  throw new Error("artifact path escapes the topic");
1539
1600
  }
@@ -1611,7 +1672,7 @@ ${input.answer.slice(0, 3e4)}`
1611
1672
  withSources(cwd, refs) {
1612
1673
  return refs.map((ref) => ({
1613
1674
  ...ref,
1614
- source: isAbsolute(ref.path) ? ref.path : resolve2(cwd, ref.path)
1675
+ source: isAbsolute(ref.path) ? ref.path : resolve3(cwd, ref.path)
1615
1676
  }));
1616
1677
  }
1617
1678
  withDesignCount(brief, designs) {
@@ -1686,7 +1747,9 @@ ${input.answer.slice(0, 3e4)}`
1686
1747
  };
1687
1748
  }
1688
1749
  dispose() {
1689
- void this.transport?.close();
1750
+ this.shared?.release();
1751
+ this.shared = void 0;
1752
+ this.transport = void 0;
1690
1753
  }
1691
1754
  };
1692
1755
  function registerSkill(ctx, spec2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatgpt-pro-research",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "ChatGPT Pro Research: unattended multi-round research through a real ChatGPT Pro web session, with local records and a DSH sidebar.",
5
5
  "private": false,
6
6
  "type": "module",