magicrew-auth 0.3.13 → 0.3.15

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,115 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.3.15
4
+
5
+ **修掉「网页已经登好、magpie 里一直转圈等」—— 弹窗永远拉不起来**
6
+
7
+ 现象:点「浏览器登录」,浏览器里登完了、跳到 agent 界面了,magpie 的卡片一直停在
8
+ 「请在浏览器中完成登录」,直到 5 分钟超时。
9
+
10
+ 根因在 `~/.cache/magpie/magicrew-login-<site>.log` 里躺着,每次都是同一行:
11
+
12
+ ```
13
+ launch: 本机没找到 Chrome / Edge / Bravo / Chromium
14
+ ```
15
+
16
+ **这台机器上 Edge 是装着的**(122.0.2365.106,微软官方包)。`findBrowser()` 写死的
17
+ 文件名是 `msedge.exe`,但这台机器上微软落盘的名字是 **`Microsoft Edge.exe`**
18
+ (带空格、带大写)—— 而另一台机器的 156 版只有 `msedge.exe`。**落盘名随安装方式/版本变**,
19
+ 写死任何一个都必有一类机器命中不到。`existsSync` 一路 miss → 返回 `null` →
20
+ 永远走「降级到系统浏览器 + 让用户去 Console 粘一行」。用户看到的就是「登好了但插件还在等」。
21
+
22
+ 顺带两件事:
23
+
24
+ - **国产 Chromium 系一个都没列**。用户日常用的是 360 极速浏览器 X(360ChromeX,
25
+ Chrome 132 内核),实测 `--remote-debugging-port=0` + CDP `Runtime.evaluate`
26
+ 完全可用 —— 没有任何理由不支持。
27
+ - **只挑「第一个存在的」**,一旦那个起不来就彻底失败。改成**逐个试**,
28
+ 第一个真写出 `DevToolsActivePort` 的才算数;全失败时把每个候选的失败原因都带出来。
29
+
30
+ ### 浏览器优先级(按「谁最干净」排,不是按名气)
31
+
32
+ ```
33
+ ① Chrome / Chromium 自己装的,无企业策略、无强制登录 ← 首选
34
+ ② 国产 Chromium 系 用户日常在用的,CDP 实测一样能用
35
+ ③ Edge 垫底:策略最多、最容易被拦、且用户明确不想用它
36
+ ```
37
+
38
+ 本机实测(Chromium 154.0.8037.93,scoop 安装):**起 CDP 650–800ms,页面就绪 ~2s,
39
+ `waitForCreds` 正常读到凭据,关窗后 profile 彻底删除。**
40
+
41
+ - `findBrowser()` 改为返回 `{ name, path }`(原来是裸字符串),新增 `browserCandidates()`
42
+ - 新增 scoop / 便携版 Chromium 路径(本机就是 scoop 装的那条)
43
+ - `waitPort()` 支持「进程已退出就别白等 8 秒」的提前放弃
44
+ - `launchLoginBrowser()` 返回体新增 `name` / `exe`,卡片文案会写明用的是哪个浏览器
45
+ - `local-browser.mjs` 的 `roots()` 补上 360 / QQ / 搜狗等,让「本机已登录就直接认」也能命中
46
+ - 判据:`node tests/browser-detect.mjs`(Edge 真实文件名 / 国产覆盖 / Chromium 优先 / 逐个试)
47
+
48
+ ### 为什么先修「怎么把窗口拉起来」,而不是去补更多登录方式
49
+
50
+ 这条链只有一个出口:**窗口拉不起来,后面的 CDP 读取一步都走不到**。
51
+ 0.3.12 及更早的失败还是**静默**的 —— 它会「成功」降级到剪贴板通道,而那一步要求
52
+ 用户知道有 F12 这回事。所以症状不是「报错」,而是「看起来什么都没发生」。
53
+ 判据落在 `magicrew-login-<site>.log` 里,就是为了这种静默失败下次能一眼看见。
54
+
55
+ ## 0.3.14
56
+
57
+ **两处判据修正:国际站看「话题」而不是「项目」;「免费」不再从倍率反推**
58
+
59
+ 来自 PR #4(`fix/free-rate-and-topic-guard`,白九)。两点判断都对,都收;
60
+ 落地时各改了一处。
61
+
62
+ ### 一、国际站首对话:判据从「有没有项目」换成「有没有话题」
63
+
64
+ 上一版把「已有工作台项目」当成「发过消息」。**这个前提不成立** —— 项目在打开
65
+ 工作台时就会自动建,「建了项目、一句话没说」的新号很常见,而那种号正是要补的。
66
+ 被误判成「用过」之后还会落一次性标记,等于永久跳过。
67
+
68
+ 改成:拿项目 → 列它的话题 → 有话题才跳过,没话题就补一条。
69
+
70
+ ```
71
+ GET /api/v1/super-agent/projects/{id}/topics
72
+ → { code:1000, data:{ total, list:[{ id, chat_topic_id, chat_conversation_id, topic_name, task_status, … }] } }
73
+ ```
74
+
75
+ ### 二、`free` 改成三分支(按 `HOOKS.md` 十一之二)
76
+
77
+ `free` 的判据是「花不花这份订阅的额度」,**不能从 `rate` 反推**。原来的二元
78
+ 判断把「价格为 0」和「价格未知」混在一起,两种情况都变成 `rate: 0`:
79
+
80
+ | `input_points` | 处理 |
81
+ |---|---|
82
+ | > 0 | `rate = n / 2000`,不设 `free` |
83
+ | === 0 | `free: true`,不给 `rate`(厂商声明免费 → 进 Free first 那一档) |
84
+ | 缺失 | **不列** —— 不猜价格,也不谎标免费 |
85
+
86
+ 产出走 `modelEntry` 已有的 `free` / `rate` 出口(`src/context.mjs` 里
87
+ `free` 为真时不输出 `rate`,两者不会同时出现)。
88
+
89
+ ### 与原 PR 的两处不同
90
+
91
+ 1. **查不到 ≠ 没说过话。** 原版 `fetchProjectTopics(...).catch(() => [])`,
92
+ 接口抖一下就当成「没有话题」,于是白花一次积分去补消息。改成:查不到就
93
+ **这次不发**、不落标记、不计失败次数,留到下次登录 / 签到再查。
94
+ 失败方向要选「宁可漏一轮」那一侧 —— 消息发出去是花钱且不可撤的。
95
+ 2. **`Number("") === 0` 会误判免费。** 原版用 `p.inputPoints != null` 判「已知」,
96
+ 空串也会通过并被算成 0 点。现在要求非空且是有限数字,才算已知价格。
97
+
98
+ ### 实测(2026-10-28,两站真接口)
99
+
100
+ ```
101
+ magicrew org=66p0q56qo47j workspace 801580653401091055 projects 1 topics 2 ✓
102
+ magicrew-intl org=67u4oe6fbenj workspace 801566935627653258 projects 1 topics 1 ✓
103
+ ```
104
+
105
+ 两站同一个 workspace 下 `GET /projects/{id}/topics` 都是 `code:1000`、
106
+ `data.list[]` 字段名逐字一致(`chat_topic_id` / `chat_conversation_id` /
107
+ `topic_name` / `task_status`),与国内站的 chat-task 链路同构。
108
+
109
+ ⚠ 当前两个账号的模型表里 **76 条模型的 `input_points` 全部 > 0,没有一条 0**
110
+ (国内 27 + 国际 49,实测快照)—— 所以三分支这批改动眼下不改变任何列表内容,
111
+ 它防的是「以后出现真免费的模型」。这一条未能在本机实测覆盖。
112
+
3
113
  ## 0.3.13
4
114
 
5
115
  **首任务补完:登录那一刻做一次,之后永不再发(补齐 0.3.12 的另一半)**
@@ -33,6 +143,7 @@ magicrew#g8rptf (66p0q…) first_ai_work PENDING/PENDING → COMPLETED/GRANTE
33
143
  再调一次 `ensureFirstTaskOnce` 直接返回 `{ settled: true }`:不打网络、不发消息,
34
144
  一次性闸门生效。
35
145
 
146
+
36
147
  ## 0.3.12
37
148
 
38
149
  **插件不再写 magpie 自己的缓存**
package/README.md CHANGED
@@ -109,7 +109,7 @@
109
109
  装完选站点,然后**点第一个「浏览器登录」**。它先看你平时用的那个浏览器:
110
110
 
111
111
  **① 那边已经登录过 magicrew** → 插件直接读它的 localStorage 接上,**不开窗口、不用再登一次**。
112
- 凭据就明文躺在 `Local Storage/leveldb` 里(Edge / Chrome / Brave / Chromium 都行)。
112
+ 凭据就明文躺在 `Local Storage/leveldb` 里(Edge / Chrome / Chromium / Brave / 360 / QQ / 搜狗 都行)。
113
113
 
114
114
  **② 没登录过,或者这个账号已经加过** → 插件自己拉起一个窗口:
115
115
 
@@ -117,17 +117,29 @@
117
117
  2. 用你平时的方式登进去(手机号 / 密码 / Google / 邮箱验证码 / 飞书 / 钉钉都行)
118
118
  3. **登录完成那一刻它自己就接上了** —— 不用回来点任何按钮,不用开 DevTools
119
119
 
120
- 原理:插件不是调用系统浏览器,而是自己起一个 Edge / Chrome(独立 profile +
121
- `--remote-debugging-port`),所以登录成功后能用 CDP 在页面上下文里把它自己的
120
+ 原理:插件不是调用系统浏览器,而是自己起一个 Chromium 内核的浏览器(独立 profile +
121
+ `--remote-debugging-port=0`),所以登录成功后能用 CDP 在页面上下文里把它自己的
122
122
  localStorage 读走,拿到 JWT 和组织码,签完密钥就关窗。
123
123
 
124
+ **用哪个浏览器?** 按「谁更干净」排,不是按名气:
125
+
126
+ ```
127
+ ① Chrome / Chromium 自己装的,无企业策略、无强制登录 ← 首选
128
+ ② 国产 Chromium 系 360 / QQ / 搜狗 / 2345 / 猎豹 / UC / 夸克
129
+ ③ Edge 垫底(策略最多、最容易被拦住调试端口)
130
+ ```
131
+
132
+ 前一个起不来(策略禁用调试端口、启动就退出)会自动换下一个,
133
+ **第一个真正打开调试通道的才算数**,不用你操心。
134
+ 窗口标题/提示里会写明这次用的是哪个。
135
+
124
136
  **每次登录都是一个新的 profile,用完就删。** 这是必须的:共用一个 profile 的话,
125
137
  添加第二个账号时窗口一打开,页面里已经躺着上一个账号的凭据 —— 插件会以为登录完成、
126
138
  立刻关窗,而 magpie 拿到的还是旧账号(同账号 → 替换,不新增)。
127
139
 
128
140
  > **为什么两条路都要?** magicrew 没有 OAuth 回调通道 —— 登录类型只有 13 种、全是「拿凭据换 token」,没有设备码、没有插件授权、没有一次性 ticket。凭据只可能落在两个地方:你自己浏览器的 localStorage(插件读文件就能拿到,所以优先),或者插件自己起的那个窗口(读不到时才需要)。
129
141
  >
130
- > 本机没装 Chrome / Edge 时自动降级:改用系统浏览器打开,登录完把凭证送进剪贴板,插件从剪贴板读。
142
+ > 本机没有任何 Chromium 内核浏览器时自动降级:改用系统浏览器打开,登录完把凭证送进剪贴板,插件从剪贴板读。
131
143
 
132
144
  不想碰 Console 的话,另外两条:
133
145
 
@@ -172,7 +184,9 @@ magpie plugin list
172
184
 
173
185
  **两个站要装两次吗?** 不用。一个包导出两个插件,装一次两个供应商都在,各自登录。
174
186
 
175
- **我在自己浏览器里登了 magicrew,插件能自动认出来吗?** 能。点「浏览器登录」时插件先去本机 Edge / Chrome / Brave / Chromium 的 localStorage 里找**这个站点**的登录态,找到就直接接上,不弹窗口。读不到、或者这个账号已经加过,才会开窗口让你登新的。
187
+ **我在自己浏览器里登了 magicrew,插件能自动认出来吗?** 能。点「浏览器登录」时插件先去本机 Edge / Chrome / Chromium / Brave / 360 / QQ / 搜狗 等浏览器的 localStorage 里找**这个站点**的登录态,找到就直接接上,不弹窗口。读不到、或者这个账号已经加过,才会开窗口让你登新的。
188
+
189
+ **点了「浏览器登录」,网页上明明登好并跳到 agent 界面了,magpie 里却一直转圈等?** 0.3.12 及更早会这样:插件拉起浏览器的判断依赖一个写死的文件名 `msedge.exe`,而有些机器上微软落盘的名字是 **`Microsoft Edge.exe`**(带空格、带大写;落盘名随安装方式和版本变),于是每次都判定「本机没装浏览器」,永远走「让你去 Console 粘一行」的降级路径 —— 而你不知道有这一步。0.3.15 起:两种落盘名都认、把 360 / QQ / 搜狗 等国产 Chromium 也纳入候选、并且逐个试启动(第一个真能开调试通道的才用)。判据在 `~/.cache/magpie/magicrew-login-<站点>.log`,出问题先看它最后一行。
176
190
 
177
191
  **添加第二个账号时,网页闪一下就关了、magpie 里也没多出账号?** 0.3.5 及更早会这样:登录浏览器复用同一个 profile,第二个窗口一打开就读到上一个账号的凭据,于是立刻「登录成功」并关窗 —— 加到的是已有账号。0.3.6 起每次登录用一个一次性 profile(关窗即删),不会再串号。
178
192
 
package/docs/internals.md CHANGED
@@ -477,19 +477,86 @@ knownOrgs 去重)。真机:`node tests/smoke.mjs` 仍能跑通(凭据从
477
477
 
478
478
  ```
479
479
  launchLoginBrowser(url)
480
- → findBrowser() Edge / Chrome / Brave / Chromium,按平台列路径逐个 existsSync
481
- → spawn(exe, [
482
- --user-data-dir=~/.cache/magpie/magicrew-login-<pid>-<ts>-<rand>, ← 一次性,见下面 ②
483
- --remote-debugging-port=0, ← 0 = 让系统挑空闲端口
484
- --no-first-run, --no-default-browser-check,
485
- url,
486
- ])
487
- → 等 <user-data-dir>/DevToolsActivePort 出现,第一行是端口
480
+ → browserCandidates() 按优先级列出候选,逐个 existsSync 过滤出真实存在的
481
+ → 对每个候选依次 launchOne():
482
+ spawn(exe, [
483
+ --user-data-dir=~/.cache/magpie/magicrew-login-<pid>-<ts>-<rand>, ← 一次性,见下面 ②
484
+ --remote-debugging-port=0, ← 0 = 让系统挑空闲端口
485
+ --no-first-run, --no-default-browser-check,
486
+ url,
487
+ ])
488
+ 等 <user-data-dir>/DevToolsActivePort 出现,第一行是端口
489
+ ├─ 拿到端口 → 就用它,返回 { port, dir, child, name, exe }
490
+ └─ 没拿到 → 杀进程 + 删目录,换下一个候选
491
+ → 全部失败才抛错,错误里带每个候选的失败原因
488
492
  ```
489
493
 
490
494
  ⚠ 启动前**必须删掉** `DevToolsActivePort` —— 上一次残留的文件会让 `waitPort` 立刻
491
495
  返回一个已经失效的旧端口。
492
496
 
497
+ #### 浏览器优先级(0.3.15,按「谁最干净」而不是名气)
498
+
499
+ ```
500
+ ① Chrome / Chromium 自己装的,无企业策略、无强制登录、无侧边栏 ← 首选
501
+ ② 国产 Chromium 系 360 / QQ / 搜狗 / 2345 / 猎豹 / UC / 夸克 —— 用户日常在用的
502
+ ③ Edge 垫底
503
+ ```
504
+
505
+ Edge 放最后的三个理由:
506
+
507
+ 1. 企业/组策略常禁用远程调试,是所有候选里**起不来概率最高**的
508
+ 2. 会被系统账号强制接管、带侧边栏与首次运行引导
509
+ 3. 用户明确反馈不愿意用
510
+
511
+ #### ⚠ 本案根因(2026-10-28):写死单个文件名一定漏
512
+
513
+ 现象:点「浏览器登录」→ 用户在浏览器里登完了、页面跳到 agent 界面了 →
514
+ magpie 卡片一直停在「请在浏览器中完成登录」,5 分钟后超时。
515
+
516
+ 判据(每次登录都往这里写一行):
517
+
518
+ ```
519
+ ~/.cache/magpie/magicrew-login-<site>.log
520
+ [2026-10-27T16:33:25.497Z] launch: 本机没找到 Chrome / Edge / Bravo / Chromium
521
+ ```
522
+
523
+ **但这台机器上 Edge 是装着的**(`122.0.2365.106`,微软官方包)。查下来:
524
+
525
+ ```
526
+ 注册表 HKLM\...\StartMenuInternet\Microsoft Edge\shell\open\command
527
+ → "C:\Program Files (x86)\Microsoft\Edge\Application\Microsoft Edge.exe"
528
+
529
+ 实际目录里: "Microsoft Edge.exe" ← 微软落盘的名字,带空格、带大写
530
+ 代码里写死: "msedge.exe" ← 这个名字在本机**不存在**
531
+ ```
532
+
533
+ **反过来也成立** —— 另一台 Windows(Edge 156.0.4314.42)上照样跑同一份代码,
534
+ 但它 `%ProgramFiles(x86)%\Microsoft\Edge\Application\` 里只有 `msedge.exe`,
535
+ `Microsoft Edge.exe` 不存在。⇒ **两个名字都得留,谁在就用谁**;
536
+ 只留一个,就必有一类机器永远走降级。这一条 2026-10-28 在两台机器上双向验过。
537
+
538
+ `existsSync` 一路 miss → `findBrowser()` 返回 `null` → **永远**走降级路径
539
+ (系统浏览器 + 让用户去 Console 粘一行)。用户不知道有 Console 这一步,
540
+ 只会觉得「我明明登好了,插件没反应」。
541
+
542
+ ⇒ 修法两条,缺一不可:
543
+
544
+ 1. **两个名字都留着**:`Microsoft Edge.exe`(微软真实落盘)+ `msedge.exe`(有些装法是这个)
545
+ 2. **不能只挑「第一个存在的」就认定它能用** —— 存在 ≠ 能起 CDP。
546
+ 改成逐个试,第一个真写出 `DevToolsActivePort` 的才算数。
547
+
548
+ #### 实测数据(2026-10-28,本机)
549
+
550
+ | 浏览器 | 起 CDP | 页面就绪 | 读凭据 | 备注 |
551
+ |---|---|---|---|---|
552
+ | **Chromium 154.0.8037.93**(scoop) | 650–800 ms | ~2 s | ✓ | **当前选中**,端到端 3.4 s 全通 |
553
+ | Edge 122.0.2365.106 | ✓ | ~2 s | ✓ | 可用,但按上面理由垫底 |
554
+ | 360ChromeX(Chrome 132 内核) | ✓ | — | ✓ | 用户日常浏览器,CDP 正常 |
555
+ | 360se6 / 搜狗 / QQ | — | — | — | 本机装了 360se6,未逐一验证 |
556
+
557
+ 真机复跑:`node tools/verify-profile-scope.mjs`(窗口打开 4 秒后 `READ_CREDS` 必须是空串,
558
+ 关窗后目录必须不存在)。单元判据:`node tests/browser-detect.mjs`。
559
+
493
560
  ```js
494
561
  waitForCreds(session, hostFragment)
495
562
  → 每 1.5s:连 /json/list → 找 URL 含 hostFragment 的 page target
@@ -549,11 +616,16 @@ waitForCreds(..., { baseline }) → 第二道防线:窗口打开时就有的
549
616
  ### 降级链
550
617
 
551
618
  ```
552
- 找到浏览器 → 自己拉 + CDP 读 全自动
553
- 找不到 → magpie 开系统浏览器 + 剪贴板 用户粘一行
554
- 都没有 → 报错,让用户改用「登录态(JWT + 组织码)」
619
+ 候选里有一个能起 CDP → 自己拉 + CDP 读 全自动
620
+ 一个都起不来 → magpie 开系统浏览器 + 剪贴板 用户粘一行
621
+ 连浏览器都没有 → 报错,让用户改用「登录态(JWT + 组织码)」
555
622
  ```
556
623
 
624
+ ⚠ 这条链**只有第一环的判断是对的才算数**。0.3.12 及更早,第一环用的是
625
+ 「文件名写死的 `msedge.exe` 是否存在」—— 文件名错了就永远落到第二环,
626
+ 而第二环要求用户去按 F12 开 Console,绝大多数人不会做,看到的就是「一直等」。
627
+ 判据是 `~/.cache/magpie/magicrew-login-<site>.log` 里那行 `launch: ...`。
628
+
557
629
  > 0.2.0 里那条「密码登录」方法已经删掉了:网页登录拿到的 token 一样能签发
558
630
  > `mgw_` key(见第十节的更正),再让用户在小窗里输手机号密码是多余的一步。
559
631
 
@@ -702,7 +774,7 @@ mgw_8f9a…(18:14 又一次续期换的) → 网关请求 HTTP 200 正
702
774
  ① POST /api/v1/login-gift 领每日登录积分(国内 +200、国际 +50;当天已领回 granted:false,幂等)
703
775
  ② 首任务 / 首对话没做过 → 走 Socket.IO 发一条消息(纯 Node,不开浏览器)
704
776
  国内站:onboarding 的 first_ai_work 还是 PENDING → 建话题 + 发一条,轮询等 COMPLETED
705
- 国际站:onboarding 恒返回空任务 → 改看「有没有工作台项目」,一个都没有才发
777
+ 国际站:onboarding 恒返回空任务 → 改看「有没有话题」,一个话题都没有才发
706
778
  ③ 其余新手福利 → PUT /api/v1/onboarding/tasks/<code>/completion
707
779
  ④ 报积分变化
708
780
  ```
@@ -730,6 +802,8 @@ mgw_8f9a…(18:14 又一次续期换的) → 网关请求 HTTP 200 正
730
802
  | 真去发了但失败 | `status: "failed"`,`attempts++` | 发,最多 3 次 |
731
803
  | 连失败 3 次 | `status: "gave-up"` | 不发(别让永远失败的号每次撞一遍) |
732
804
  | 还没有工作台 / 项目 | 不写 | 发,下次再来(只读接口,成本低) |
805
+ | 有项目、但话题一个都没有 | 不写 | **发**(项目是打开工作台时自动建的,不代表说过话) |
806
+ | 话题清单查不到(接口失败) | 不写 | **这次不发**,下次再来(不把「查不到」当「没说过话」白花积分) |
733
807
  | 服务端本来就是 COMPLETED | `status: "sent"` + `reason` | 不发 |
734
808
 
735
809
  老版本(0.3.11)记的是 `{ [org]: "YYYY-MM-DD" }` 字符串日期 —— 读的时候按「已发过」
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magicrew-auth",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "description": "超级麦吉 / MagiCrew for magpie and OpenCode — one package, two providers: letsmagic.cn (国内) and magicrew.ai (international). Password or web-JWT sign-in, the account's own live model list, points balance, newcomer rewards, invite codes.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
package/src/api.mjs CHANGED
@@ -476,6 +476,31 @@ export async function fetchFirstWorkspaceId(token, org, host = SITES.cn.host) {
476
476
  return String(w?.id ?? w?.workspace_id ?? w?.workspaceId ?? "")
477
477
  }
478
478
 
479
+ // ── ④e 某个项目下的话题 ──────────────────────────────────────────────
480
+ //
481
+ // GET /api/v1/super-agent/projects/{id}/topics
482
+ // → { total, list:[{ id, chat_topic_id, chat_conversation_id, topic_name, task_status, … }] }
483
+ //
484
+ // 判断「这个账号到底发没发过消息」看的是它,**不是「有没有项目」** ——
485
+ // 项目在打开工作台时就会自动建,「有项目、没话题」的新号很常见。
486
+ // 实测(2026-10-28):国内站 magicrew / 国际站 magicrew-intl 两站同构,
487
+ // 同一个 workspace 下都返回 { code:1000, data:{ total, list } }。
488
+ export async function fetchProjectTopics(token, org, projectId, host = SITES.cn.host) {
489
+ const res = await fetch(`${host}/api/v1/super-agent/projects/${projectId}/topics`, {
490
+ headers: headersFor(token, org, host),
491
+ })
492
+ const body = await read(res)
493
+ const d = body?.data ?? {}
494
+ const list = Array.isArray(d.list) ? d.list : []
495
+ return list.map((t) => ({
496
+ id: String(t.id ?? ""),
497
+ chatTopicId: String(t.chat_topic_id ?? ""),
498
+ chatConversationId: String(t.chat_conversation_id ?? ""),
499
+ name: String(t.topic_name ?? ""),
500
+ status: String(t.task_status ?? ""),
501
+ }))
502
+ }
503
+
479
504
  // ── ④c 这个登录态是谁 ────────────────────────────────────────────────
480
505
  //
481
506
  // 卡片上原来显示组织码(orgcode67890 / orgcode12345),那是随机串,用户认不出是谁。