sintbot 1.0.7 → 1.0.9

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.en.json CHANGED
@@ -1,4 +1,36 @@
1
1
  [
2
+ {
3
+ "version": "1.0.9",
4
+ "name": "A steadier setup wizard",
5
+ "items": [
6
+ {
7
+ "t": "No more looping without a terminal",
8
+ "d": "When the setup wizard runs without keyboard input (in a script, a container or CI), it now finishes on the defaults instead of looping at the model step until the disk fills, and it no longer installs Claude Code on its own. If it still cannot finish, it stops and writes no config."
9
+ }
10
+ ]
11
+ },
12
+ {
13
+ "version": "1.0.8",
14
+ "name": "A clearer console, sharper style extraction",
15
+ "items": [
16
+ {
17
+ "t": "\"Needs you\" on the console",
18
+ "d": "The top of Overview lists what is waiting on you: failed tasks with their reason, browser takeovers nobody has claimed, open feedback, leftover background processes and failing health checks. It appears only when something does, and sends nothing extra to any group."
19
+ },
20
+ {
21
+ "t": "Activity survives restarts",
22
+ "d": "After a restart or an upgrade, live activity on Overview is filled back in from recent tasks, and failed tasks show their reason."
23
+ },
24
+ {
25
+ "t": "Sharper style extraction",
26
+ "d": "Extracting from a URL now reads stylesheets a site serves from other domains, so sites like Feishu and Bilibili work, and sites that need a proxy go through the configured one. Colors, fonts and radii are read more accurately, and anything it cannot read leaves your choices as they were."
27
+ },
28
+ {
29
+ "t": "English throughout",
30
+ "d": "The English console now covers the WeCom settings, health checks, scheduled-task descriptions and more, and the built-in design styles have English names and descriptions."
31
+ }
32
+ ]
33
+ },
2
34
  {
3
35
  "version": "1.0.7",
4
36
  "name": "First official release",
package/CHANGELOG.json CHANGED
@@ -1,4 +1,38 @@
1
1
  [
2
+ {
3
+ "version": "1.0.9",
4
+ "date": "2026-10-26",
5
+ "name": "安装向导更稳",
6
+ "items": [
7
+ {
8
+ "t": "没有终端时向导不再空转",
9
+ "d": "在脚本、容器或持续集成里运行安装向导、拿不到键盘输入时,向导会用默认值走完,不再在选模型那一步反复转圈、把磁盘写满,也不会自己去装 Claude Code;实在走不下去就停下提示,不写入配置。"
10
+ }
11
+ ]
12
+ },
13
+ {
14
+ "version": "1.0.8",
15
+ "date": "2026-10-24",
16
+ "name": "控制台更好用,提炼风格更准",
17
+ "items": [
18
+ {
19
+ "t": "控制台「需要你处理」",
20
+ "d": "概览最上面汇总在等你的事:失败的任务和原因、没人认领的浏览器接管、没处理的反馈、残留的后台进程、没通过的健康检查;没事就不出现,也不往群里多发消息。"
21
+ },
22
+ {
23
+ "t": "实时活动重启不丢",
24
+ "d": "重启或升级之后,概览的实时活动会补回最近的任务记录;出错的任务直接显示原因。"
25
+ },
26
+ {
27
+ "t": "从参考提炼风格更准",
28
+ "d": "贴网址提炼时能读到网站放在其他域名上的样式表,飞书、B 站这类网站也能提炼,需要代理才能访问的网站走配置里的代理;颜色、字体和圆角读得更准,读不到的项不会改动你已经选好的。"
29
+ },
30
+ {
31
+ "t": "英文界面补齐",
32
+ "d": "控制台英文界面补全了企业微信设置、健康检查、定时任务说明等处,内置设计风格也有了英文名称和介绍。"
33
+ }
34
+ ]
35
+ },
2
36
  {
3
37
  "version": "1.0.7",
4
38
  "date": "2026-10-24",
package/README.md CHANGED
@@ -79,14 +79,12 @@ Without a Claude subscription, Claude Code is only the agent framework and needs
79
79
  | **Commands** | `/help` · `/status` · `/model <name>` · `/stop` · `/skip` · `/new` · `/hud` · `/issue`. On Slack, use a `!` prefix because Slack consumes messages that start with `/`. |
80
80
  | **Permissions** | Set per group, read-only by default. Thirteen secret and PII patterns are masked before a message reaches the group. |
81
81
  | **Scheduled tasks** | Cron, aliases such as `daily`, or plain "every day at 09:00". A task can wake the agent, post a reminder or run a script. |
82
- | **Design** | A per-project `DESIGN.md` style file the agent must read. Sixteen built-in styles. A check before every hand-off. Iterate by annotating Feishu whiteboards. |
82
+ | **Design** | Distill a style from a site, a project folder or an image you like, or start from sixteen built-in styles. The result is a per-project `DESIGN.md` the agent must read, and every hand-off is checked against it. Iterate by annotating Feishu whiteboards. |
83
83
  | **Images** | Generated with the machine's Codex subscription, so no API key is needed. Edit an existing image, repaint a masked region, or produce a transparent background. |
84
84
  | **Previews & browser** | HTML prototypes are served on your LAN for the group to see. When the bot's browser hits a login or captcha, a group member takes over through a one-time link. |
85
- | **Console** | One local page for every group: projects, models, permissions, design library, live activity and release history. Available in English or Chinese. |
85
+ | **Console** | One local page for every group: a "Needs you" list (failed turns with their reason, takeover requests, open feedback), projects, models, permissions, design library, live activity and release history. Available in English or Chinese. |
86
86
  | **Operations** | Turns are capped at 30 minutes, and at most two run at once. Turns interrupted by a restart are redone automatically. Includes `sintbot doctor`, an ops alert group and quota warnings. |
87
87
 
88
- <p align="center"><img src="https://raw.githubusercontent.com/sintlab/sintbot/main/docs/assets/design-styles.png" alt="The sixteen built-in design styles" width="560"></p>
89
-
90
88
  ## Everyday commands
91
89
 
92
90
  ```bash
@@ -153,14 +151,12 @@ Both are free. [cc-bot](https://github.com/WaterTian/cc-bot) is a Claude Code pl
153
151
 
154
152
  ## Documentation
155
153
 
156
- The feature deep dives marked "(zh)" are in Chinese for now; each English doc links to its Chinese version.
157
-
158
154
  | Topic | Docs |
159
155
  |---|---|
160
156
  | Getting started | [Install](./docs/install.md) · [Models](./docs/models.md) · [Configuration](./docs/config.md) · [Security](./docs/security.md) · [Features](./docs/features.md) |
161
157
  | Chat platforms | [Slack](./docs/platforms/slack.md) · [Feishu](./docs/platforms/feishu.md) · [WeCom](./docs/platforms/wecom.md) |
162
- | Features | [Whiteboard](./docs/BOARD.md) (zh) · [Previews](./docs/PREVIEW.md) (zh) · [Browser](./docs/BROWSER.md) (zh) · [Icons](./docs/ICONS.md) (zh) · [Codex runner](./docs/CODEX-RUNNER.md) (zh) · [Public console access](./docs/PUBLIC-ACCESS.md) (zh) |
163
- | Project | [Architecture (DESIGN.md)](./DESIGN.md) (zh) · [Contributing](./CONTRIBUTING.md) · [Changelog](./CHANGELOG.en.json) |
158
+ | Features | [Whiteboard](./docs/BOARD.md) · [Previews](./docs/PREVIEW.md) · [Browser](./docs/BROWSER.md) · [Icons](./docs/ICONS.md) · [Codex runner](./docs/CODEX-RUNNER.md) · [Public console access](./docs/PUBLIC-ACCESS.md) |
159
+ | Project | [Architecture (DESIGN.md)](./DESIGN.md) · [Contributing](./CONTRIBUTING.md) · [Changelog](./CHANGELOG.en.json) |
164
160
 
165
161
  ## Feedback
166
162
 
package/README.zh-CN.md CHANGED
@@ -87,14 +87,12 @@ sintbot service install # 崩溃自动拉起、登录后自启
87
87
  | **命令** | `/help` · `/status` · `/model <名字>` · `/stop` · `/skip` · `/new` · `/hud` · `/issue`。Slack 会吃掉 `/` 开头的消息,所以在 Slack 里用 `!` 前缀。 |
88
88
  | **权限** | 按群设,默认只读。消息到群之前先遮掉 13 类密钥与隐私信息。 |
89
89
  | **定时任务** | cron、`daily` 这类别名,或直接写「每天 09:00」。到点可以叫醒 agent、只发提醒,或只跑一个脚本。 |
90
- | **设计** | 每个项目一份 agent 必读的风格档 `DESIGN.md`,16 套内置风格,交付前先核对。还可以在飞书画板上标注、迭代。 |
90
+ | **设计** | 从喜欢的网站、项目目录或图片提炼风格,也可以从 16 套内置风格起步。结果是每个项目一份 agent 必读的风格档 `DESIGN.md`,交付前对照它核对。还可以在飞书画板上标注、迭代。 |
91
91
  | **生图** | 借本机的 Codex 订阅生图,不用 API key。可以在原图上改、遮罩局部重绘、透明底。 |
92
92
  | **预览与浏览器** | 把 HTML 原型挂到局域网给群成员看。机器人的浏览器碰到登录或验证码时,群成员用一次性链接接管。 |
93
- | **控制台** | 一个本地网页管所有群:项目、模型、权限、设计库、实时活动、版本历史。界面中英文可切。 |
93
+ | **控制台** | 一个本地网页管所有群:「需要你处理」清单(失败的轮次和原因、等人接管、没处理的反馈)、项目、模型、权限、设计库、实时活动、版本历史。界面中英文可切。 |
94
94
  | **运维** | 每轮最长 30 分钟,全局最多同时两轮。被重启打断的轮次会自动重做。另有 `sintbot doctor` 体检、运维告警群、额度提醒。 |
95
95
 
96
- <p align="center"><img src="https://raw.githubusercontent.com/sintlab/sintbot/main/docs/assets/design-styles.png" alt="16 套内置设计风格" width="560"></p>
97
-
98
96
  ## 常用命令
99
97
 
100
98
  ```bash
@@ -165,8 +163,8 @@ sintbot help # 全部命令
165
163
  |---|---|
166
164
  | 上手 | [安装](./docs/zh-CN/install.md) · [模型](./docs/zh-CN/models.md) · [配置](./docs/zh-CN/config.md) · [安全](./docs/zh-CN/security.md) · [功能详解](./docs/zh-CN/features.md) |
167
165
  | 聊天平台 | [飞书](./docs/zh-CN/platforms/feishu.md) · [Slack](./docs/zh-CN/platforms/slack.md) · [企业微信](./docs/zh-CN/platforms/wecom.md) |
168
- | 功能 | [画板](./docs/BOARD.md) · [预览](./docs/PREVIEW.md) · [浏览器](./docs/BROWSER.md) · [图标](./docs/ICONS.md) · [Codex 后端](./docs/CODEX-RUNNER.md) · [控制台公网访问](./docs/PUBLIC-ACCESS.md) |
169
- | 项目 | [架构与设计(DESIGN.md)](./DESIGN.md) · [参与开发](./CONTRIBUTING.zh-CN.md) · [更新日志](./CHANGELOG.json) |
166
+ | 功能 | [画板](./docs/zh-CN/BOARD.md) · [预览](./docs/zh-CN/PREVIEW.md) · [浏览器](./docs/zh-CN/BROWSER.md) · [图标](./docs/zh-CN/ICONS.md) · [Codex 后端](./docs/zh-CN/CODEX-RUNNER.md) · [控制台公网访问](./docs/zh-CN/PUBLIC-ACCESS.md) |
167
+ | 项目 | [架构与设计(DESIGN.md)](./DESIGN.zh-CN.md) · [参与开发](./CONTRIBUTING.zh-CN.md) · [更新日志](./CHANGELOG.json) |
170
168
 
171
169
  ## 反馈
172
170
 
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 酸性荧光
3
3
  description: 灰白底上的荧光二次色与铬金属——挤压变宽字、2px 硬描边、贴纸式拼贴,吵得有纪律
4
+ name_en: "Acid Pop"
5
+ description_en: "Fluorescent secondaries and chrome on off-white: extended type, 2px hard outlines and sticker-style collage. Loud, but disciplined."
4
6
  suits: "活动推广 · 品牌门面"
5
7
  colors:
6
8
  bg: "#F5F4F2"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 档案索引
3
3
  description: 策展目录那种密排列表——纯白纸、纯黑衬线、未经修饰的超链接蓝,容器是表格行不是卡片
4
+ name_en: "Archive Index"
5
+ description_en: "The dense list of a curated catalogue: plain white paper, black serif text and untouched hyperlink blue. Containers are table rows, not cards."
4
6
  suits: "内容阅读"
5
7
  colors:
6
8
  bg: "#FFFFFF"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 装饰艺术
3
3
  description: 近黑棕底上的金色细双线、阶梯轮廓与日出放射——贵气来自字距与几何,不是金属贴图
4
+ name_en: "Art Deco"
5
+ description_en: "Fine gold double rules, stepped outlines and sunbursts on near-black brown. The luxury comes from letter spacing and geometry, not metallic textures."
4
6
  suits: "品牌门面 · 活动推广"
5
7
  colors:
6
8
  bg: "#12100E"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 暗场影像
3
3
  description: 碳黑底上的满幅影像与巨字——胶片颗粒、四角暗角、骨白文字,像放映厅而不是控制台
4
+ name_en: "Cinematic Noir"
5
+ description_en: "Full-bleed imagery and huge type on carbon black, with film grain, corner vignettes and bone-white text. A screening room, not a dashboard."
4
6
  suits: "品牌门面 · 生图插画"
5
7
  colors:
6
8
  bg: "#08090A"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 杂志编辑
3
3
  description: 独立生活杂志的版式——大图、衬线大标题、不对称留白,安静而有故事感
4
+ name_en: "Editorial"
5
+ description_en: "The layout of an independent lifestyle magazine: big images, large serif headlines and asymmetric white space. Quiet, with a story to tell."
4
6
  suits: "内容阅读"
5
7
  colors:
6
8
  paper: "#FBF9F4"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 几何构成
3
3
  description: 包豪斯与风格派的几何撞色——三原色纪律、大形状、平涂无渐变,明快而有秩序
4
+ name_en: "Geometric Pop"
5
+ description_en: "The bold geometry of Bauhaus and De Stijl: strict primary colors, large shapes and flat fills with no gradients. Bright and orderly."
4
6
  suits: "活动推广 · 生图插画"
5
7
  colors:
6
8
  cream: "#F6EFE3"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 手绘温度
3
3
  description: 旅行手账与刊物插画小品的手绘感——铅笔线稿加水彩淡彩,松弛、真诚、有呼吸
4
+ name_en: "Hand-drawn"
5
+ description_en: "The hand-drawn feel of travel journals and small editorial illustrations: pencil linework with light watercolor washes. Relaxed, sincere and unhurried."
4
6
  suits: "生图插画 · 内容阅读"
5
7
  colors:
6
8
  paper: "#FAF7F2"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 伊斯兰几何
3
3
  description: girih 拼块的 36° 体系与交错编织——全库唯一能自动判对错的风格:角度画错了图案就拼不上
4
+ name_en: "Islamic Geometry"
5
+ description_en: "The 36° system and interlacing of girih tiles. The only style in the library that can be checked automatically: get an angle wrong and the pattern will not close."
4
6
  suits: "品牌门面 · 生图插画"
5
7
  colors:
6
8
  paper: "#F1EBDF"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 液态玻璃
3
3
  description: 半透明材质浮在真实内容之上——模糊加折射、边缘内高光、同心圆角,清透但不牺牲可读
4
+ name_en: "Liquid Glass"
5
+ description_en: "Translucent material floating over real content: blur and refraction, inner edge highlights and concentric corners. Clear, without giving up legibility."
4
6
  suits: "工具产品 · 品牌门面"
5
7
  colors:
6
8
  bg: "#E8EDF4"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 新中式
3
3
  description: 民国书局与笺谱的克制东方——宣纸底、墨色字、朱砂一点,是文人气不是影楼中国风
4
+ name_en: "Neo-Chinese"
5
+ description_en: "The restrained East of Republican-era bookshops and letter-paper albums: rice-paper ground, ink-black text and a single touch of cinnabar. Scholarly, not photo-studio chinoiserie."
4
6
  suits: "内容阅读 · 品牌门面"
5
7
  colors:
6
8
  paper: "#F5F0E6"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 精密仪表
3
3
  description: 开发者工具那种密而准的界面——发丝线分区、等宽数字对齐、4px 小圆角,没有一处多余装饰
4
+ name_en: "Precision Mono"
5
+ description_en: "The dense, exact interface of developer tools: hairline dividers, aligned monospaced numbers and 4px corners, with not one ornament to spare."
4
6
  suits: "工具产品"
5
7
  colors:
6
8
  bg: "#FAFAFA"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 静奢
3
3
  description: 巨大的衬线标题、微型全大写标签、一屏只说一件事——不装饰不发光,贵在敢空
4
+ name_en: "Quiet Luxury"
5
+ description_en: "Huge serif headlines, tiny all-caps labels and one idea per screen. No ornament and no glow: the luxury is in daring to leave space empty."
4
6
  suits: "品牌门面"
5
7
  colors:
6
8
  bg: "#FFF8EF"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 复古印刷
3
3
  description: Riso 孔版双色印刷——真实油墨色、网点纹理、轻微套印错位,独立 zine 的粗粝浪漫
4
+ name_en: "Riso Print"
5
+ description_en: "Two-color Risograph printing: real ink colors, halftone texture and slight misregistration. The rough romance of an indie zine."
4
6
  suits: "生图插画 · 活动推广"
5
7
  colors:
6
8
  paper: "#F3EEE2"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 圆融亲和
3
3
  description: 消费品与教育产品的温软圆润——奶白底、珊瑚与薄荷、大圆角,亲切但不幼稚
4
+ name_en: "Soft & Friendly"
5
+ description_en: "The soft roundness of consumer and education products: milk-white ground, coral and mint, and generous corners. Warm without being childish."
4
6
  suits: "工具产品 · 品牌门面"
5
7
  colors:
6
8
  cream: "#FFFDF8"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 瑞士网格
3
3
  description: 国际主义排版风格——严格网格、大量留白、单一强调色,冷静克制的现代主义
4
+ name_en: "Swiss Grid"
5
+ description_en: "The International Typographic Style: a strict grid, plenty of white space and a single accent color. Calm, restrained modernism."
4
6
  suits: "内容阅读 · 品牌门面"
5
7
  colors:
6
8
  primary: "#111418"
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: 科技深色
3
3
  description: 克制的深色科技界面——石墨蓝底、高可读文字、一点数据青,像专业监控台而非霓虹夜店
4
+ name_en: "Tech Dark"
5
+ description_en: "A restrained dark technical interface: graphite-blue ground, highly legible text and a hint of data cyan. A professional monitoring console, not a neon nightclub."
4
6
  suits: "工具产品"
5
7
  colors:
6
8
  bg: "#0B0E14"
package/bin/sbot.js CHANGED
@@ -132,7 +132,7 @@ const SUBHELP = {
132
132
  + ' 浏览器里的控制台(配置模式,不接 daemon):配置 / 健康 / provider 试跑 / 群绑定;默认 http://127.0.0.1:4600\n'
133
133
  + ' --host 0.0.0.0 对局域网开放 —— 必须先 --set-password 设过密码,否则拒绝启动;--host / --port 只管本次,不写进配置\n'
134
134
  + ' 与 daemon 共宿的实时控制台是 sintbot start --ui\n'
135
- + ' 公网访问不走命令行参数:配置 ui.publicUrl + feishu.admins 名单,经 SSH 隧道与反代终结 HTTPS,公网上只认「用飞书登录」—— 见 https://github.com/sintlab/sintbot/blob/main/docs/PUBLIC-ACCESS.md',
135
+ + ' 公网访问不走命令行参数:配置 ui.publicUrl + feishu.admins 名单,经 SSH 隧道与反代终结 HTTPS,公网上只认「用飞书登录」—— 见 https://github.com/sintlab/sintbot/blob/main/docs/zh-CN/PUBLIC-ACCESS.md',
136
136
  doctor: '用法: sintbot doctor [--config <path>] [--kill-leftovers]\n'
137
137
  + ' 体检:Node / 配置 / claude CLI 与旗子 / 各 provider 的 key 与端点 / 浏览器与 MCP / 飞书凭证与权限预检 / 项目根目录 / 各群目录 / 遗留进程\n'
138
138
  + ' --kill-leftovers 顺手结束「遗留进程」(agent 轮次结束后仍在跑的后台 shell,体检尾巴会列出来;不加只列不杀)\n'
@@ -142,7 +142,7 @@ const SUBHELP = {
142
142
  + ' 飞书守护进程:群消息 → 绑定项目 → agent → 回群;配置文件改了热重载,不用重启\n'
143
143
  + ' --ui 同时提供实时控制台(默认 127.0.0.1:4600;对外绑定须先 sintbot ui --set-password;端口被占只提示,daemon 照常跑)\n'
144
144
  + ' 前置:feishu.appId / appSecret 已配、claude CLI 可用;SIGTERM / Ctrl-C 优雅退出(会中止进行中的轮次)\n'
145
- + ' 公网入口(SSH 隧道 + 反代 + ui.publicUrl + 飞书登录名单)不在参数里,见 https://github.com/sintlab/sintbot/blob/main/docs/PUBLIC-ACCESS.md;launchd 常驻要带 --ui-host 0.0.0.0',
145
+ + ' 公网入口(SSH 隧道 + 反代 + ui.publicUrl + 飞书登录名单)不在参数里,见 https://github.com/sintlab/sintbot/blob/main/docs/zh-CN/PUBLIC-ACCESS.md;launchd 常驻要带 --ui-host 0.0.0.0',
146
146
  chat: '用法: sintbot chat [--workdir|-C <dir>] [--provider <名>] [--model <id>] [--allow <list>] [--deny <list>] [--config <path>]\n'
147
147
  + ' 从终端逐行驱动引擎(不接飞书):每行 = 一条群消息;/help 看群内命令,/exit 退出\n'
148
148
  + ' --allow / --deny 逗号分隔的工具名,两个都不给 ⇒ 只读;会话状态落在 <workdir>/.sbot/',
@@ -1280,6 +1280,9 @@ async function cmdService(rest) {
1280
1280
  // in the gitignored config), an optional proxy split, and optional Feishu /
1281
1281
  // group binding (with id auto-fetch) — then runs doctor. Shares helpers with the
1282
1282
  // console (deriveNoProxy / feishu*), so CLI and UI stay in sync.
1283
+ // 向导走一遍最多问二十来个问题;输入结束后还要问到这么多次,只能是某个循环拿默认值走不出去
1284
+ const SETUP_MAX_ASKS_AFTER_EOF = 60
1285
+
1283
1286
  async function cmdSetup(rest) {
1284
1287
  const { values } = parseArgs({ args: rest, allowPositionals: true, options: { config: { type: 'string' } } })
1285
1288
  const cfgPath = defaultConfigPath(values.config)
@@ -1319,14 +1322,22 @@ async function cmdSetup(rest) {
1319
1322
  const nextLine = () => queued.length ? Promise.resolve(queued.shift())
1320
1323
  : closed ? Promise.resolve(null)
1321
1324
  : new Promise((res) => { waiter = res })
1322
- const ask = async (q, def) => {
1325
+ // 输入读完之后(管道、</dev/null、Docker RUN、CI)每一问都拿默认值。默认值走不出去的循环会原地空转:
1326
+ // 1.0.8 的模型菜单默认「1」→ key 为空 → 回菜单,3 分钟往输出里写了 18 GB。所以 ① 会转圈或有副作用的问题
1327
+ // 另给一个「输入已结束」时的答案(endedDef);② 兜底:输入结束后还问了这么多次就中止,配置不落盘。
1328
+ let askedAfterEnd = 0
1329
+ const ask = async (q, def, endedDef = def) => {
1323
1330
  process.stdout.write(`${cyan('?')} ${q}${def ? dim(` [${def}]`) : ''} `)
1324
1331
  const a = await nextLine()
1325
- if (a == null) process.stdout.write('\n')
1326
- return (a == null ? '' : String(a).trim()) || def || ''
1332
+ if (a == null) {
1333
+ process.stdout.write('\n')
1334
+ if (++askedAfterEnd > SETUP_MAX_ASKS_AFTER_EOF) throw new Error('输入已经结束,向导还在提问 —— 已中止,配置没有写入。请在终端里直接运行 sintbot setup')
1335
+ return endedDef || ''
1336
+ }
1337
+ return String(a).trim() || def || ''
1327
1338
  }
1328
- const askYN = async (q, defYes) => {
1329
- const a = (await ask(`${q} ${dim(defYes ? '(Y/n)' : '(y/N)')}`, '')).toLowerCase()
1339
+ const askYN = async (q, defYes, endedYes = defYes) => {
1340
+ const a = (await ask(`${q} ${dim(defYes ? '(Y/n)' : '(y/N)')}`, '', endedYes ? 'y' : 'n')).toLowerCase()
1330
1341
  return a ? a[0] === 'y' : !!defYes
1331
1342
  }
1332
1343
  // Key/secret prompt: echoes visibly (so you can see the paste landed) and then
@@ -1363,7 +1374,8 @@ async function cmdSetup(rest) {
1363
1374
  if (claudeBin) process.stdout.write(` ${cyan('✓')} claude ${dim(claudeBin)}\n`)
1364
1375
  else {
1365
1376
  process.stdout.write(` ${red('✗')} 没找到 claude(Claude Code CLI)—— 每一轮都靠它干活\n`)
1366
- if (await askYN(`现在安装 Claude Code(npm i -g ${installdeps.PACKAGES.claude})?`, true)) claudeBin = installDep('claude', claudeName)
1377
+ // 没人回答(输入已结束)就不替他装软件
1378
+ if (await askYN(`现在安装 Claude Code(npm i -g ${installdeps.PACKAGES.claude})?`, true, false)) claudeBin = installDep('claude', claudeName)
1367
1379
  else process.stdout.write(dim(` 稍后自己装:npm i -g ${installdeps.PACKAGES.claude}`) + '\n')
1368
1380
  }
1369
1381
  // Claude 登录态留到模型步一起报,那里据此决定推荐走哪条路。官方非交互子命令,0.2s、不联网;
@@ -1455,7 +1467,7 @@ async function cmdSetup(rest) {
1455
1467
  if (r.ok) { reportTurn(info.label, r); verified.push(name); return true }
1456
1468
  const why = setupmodels.explainTestFailure(r.text, { baseUrl: p.baseUrl })
1457
1469
  process.stdout.write(` ${red('✗')} ${why.text}\n`)
1458
- if (!(await askYN('再试一次?(可以重新粘贴 key)', why.kind === 'auth' || why.kind === 'network'))) {
1470
+ if (!(await askYN('再试一次?(可以重新粘贴 key)', why.kind === 'auth' || why.kind === 'network', false))) {
1459
1471
  process.stdout.write(dim(' key 先保存;跑通之前这个模型的轮次会失败,之后可在控制台「模型」页再测') + '\n')
1460
1472
  return true
1461
1473
  }
@@ -1490,7 +1502,7 @@ async function cmdSetup(rest) {
1490
1502
  + ' 3) 用 Claude 订阅 —— 另开一个终端跑 claude auth login,登录好回到这里\n'
1491
1503
  + ` 4) 用 ChatGPT(Codex)—— 另开一个终端跑 codex login(没装先 npm i -g ${installdeps.PACKAGES.codex}),登录好回到这里\n`
1492
1504
  + ' 5) 先跳过(之后在控制台「模型」页添加)\n')
1493
- const a = await ask('选', '1')
1505
+ const a = await ask('选', '1', '5') // 输入已结束 ⇒ 先跳过(选 1 拿不到 key 会回到这里)
1494
1506
  if (a === '1') { if (await configureKeyed(setupmodels.RECOMMENDED)) break; continue }
1495
1507
  if (a === '2') { if (await pickOthers() && readyKeyed().length) break; continue }
1496
1508
  if (a === '3' || a === '4') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sintbot",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Drive headless Claude Code (or Codex) from Slack / Feishu / WeCom group chats — one group, one project, swappable model brain. A standalone daemon, not a Claude Code plugin.",
5
5
  "bin": {
6
6
  "sbot": "bin/sbot.js",
@@ -1357,7 +1357,8 @@ class Engine {
1357
1357
  // 指纹是唯一能把「空轮 / 超时 / 模型报错 / 其实答了但没看见」分开的东西。此前 status
1358
1358
  // 说了「error」却说不出是什么错,拿到反馈的人还得再猜一遍。
1359
1359
  rt.lastTurn = { ts: startedAt, status: o.status, ms: Date.now() - startedAt, tokens: o.tokens || null, provider: o.provider || null, ...(errText ? { err: errText } : {}) }
1360
- this._activity({ ...meta, kind: 'done', status: o.status, ms: Date.now() - startedAt, tokens: o.tokens, preview: o.preview, reply: o.reply, tools: o.tools })
1360
+ // err 与账本同一个值:控制台活动行出错时直接显示原因,不用再去翻账本
1361
+ this._activity({ ...meta, kind: 'done', status: o.status, ms: Date.now() - startedAt, tokens: o.tokens, preview: o.preview, reply: o.reply, tools: o.tools, ...(errText ? { err: errText } : {}) })
1361
1362
  // ⚠ 账本此前只记 `status:"error"`,**一个字的原因都没有**;而日志那边又没有时间戳
1362
1363
  // ⇒ 线上失败**事后无从查起**(2026-09-01 真机:生产群一周 15 次 error,一次都查不出
1363
1364
  // 原因 —— 两边各缺一半)。原因其实一直在手上(preview/subtype),只是落盘时被丢掉了。
package/src/core/proxy.js CHANGED
@@ -192,7 +192,7 @@ class TunnelAgent extends https.Agent {
192
192
  // ── fetch 形状的响应(只实现调用方真用到的那几个成员)──────────────────────────────
193
193
  // ⚠ 刻意不做成"完整的 Response 垫片":假装完整反而会让某天用到未实现成员的人拿到
194
194
  // `undefined` 而不是报错。用到什么补什么,缺的当场就炸得出来。
195
- function makeResponse(status, rawHeaders, buf, url) {
195
+ function makeResponse(status, rawHeaders, buf, url, flags = null) {
196
196
  const lower = new Map()
197
197
  for (const [k, v] of Object.entries(rawHeaders || {})) {
198
198
  lower.set(String(k).toLowerCase(), Array.isArray(v) ? v.join(', ') : String(v))
@@ -205,6 +205,7 @@ function makeResponse(status, rawHeaders, buf, url) {
205
205
  async text() { return buf.toString('utf8') },
206
206
  async json() { return JSON.parse(buf.toString('utf8')) },
207
207
  async arrayBuffer() { return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) },
208
+ ...(flags || {}),
208
209
  }
209
210
  }
210
211
 
@@ -308,9 +309,24 @@ function createProxyFetch(proxyUrl, { noProxy = '', agent: injectedAgent = null,
308
309
  }
309
310
  return resolve(request(next, nextInit, depth + 1))
310
311
  }
312
+ // init.maxBytes(非标准,fetch 会忽略它):响应体边收边数,到线就**截断**并断开 —— 这条路把整个响应
313
+ // 攒进内存,不封顶的话一个几百 MB 的响应就能把 daemon 撑爆(风格提炼会替人去拉任意公网页面)。
314
+ // 截断而不是报错:要的往往只是开头(HTML 的 <head> 里就是全部样式表链接)。响应带 capped / truncated 两个标记。
311
315
  const chunks = []
312
- res.on('data', (c) => chunks.push(c))
313
- res.on('end', () => resolve(makeResponse(status, res.headers, Buffer.concat(chunks), u.toString())))
316
+ let got = 0
317
+ const cap = Number(init.maxBytes) > 0 ? Number(init.maxBytes) : 0
318
+ const flags = cap ? { capped: true, truncated: false } : null
319
+ res.on('data', (c) => {
320
+ if (flags && flags.truncated) return
321
+ if (cap && got + c.length > cap) {
322
+ chunks.push(c.subarray(0, cap - got)); got = cap; flags.truncated = true
323
+ try { res.destroy() } catch {}
324
+ return resolve(makeResponse(status, res.headers, Buffer.concat(chunks), u.toString(), flags))
325
+ }
326
+ got += c.length
327
+ chunks.push(c)
328
+ })
329
+ res.on('end', () => resolve(makeResponse(status, res.headers, Buffer.concat(chunks), u.toString(), flags)))
314
330
  res.on('error', (e) => reject(wrap(e))) // 半截响应 / 中途断线走这条,也得点名代理
315
331
  })
316
332