sintbot 1.4.1 → 1.4.3
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 +42 -0
- package/CHANGELOG.json +42 -0
- package/bin/sintbot-v2.js +33 -2
- package/docs/AI-ASSISTED-INSTALL.md +2 -2
- package/docs/install.md +6 -4
- package/docs/zh-CN/AI-ASSISTED-INSTALL.md +2 -2
- package/docs/zh-CN/install.md +6 -4
- package/install.sh +1 -1
- package/package.json +5 -1
- package/src/c2configcontrol.js +3 -2
- package/src/c2console.js +6 -3
- package/src/c2multigroupservice.js +19 -2
- package/src/c2updater.js +361 -0
- package/src/c2upgradeswitch.js +169 -0
- package/src/core/c2cardterminalpath.js +17 -3
- package/src/core/c2durableruntime.js +6 -0
- package/src/core/turnprogress.js +135 -0
- package/src/core/v2engine.js +141 -19
- package/src/ui/console.html +36 -15
- package/src/updater.js +243 -0
package/CHANGELOG.en.json
CHANGED
|
@@ -1,4 +1,46 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.4.3",
|
|
4
|
+
"date": "2026-11-09",
|
|
5
|
+
"name": "Progress on the reply card",
|
|
6
|
+
"items": [
|
|
7
|
+
{
|
|
8
|
+
"t": "A card appears even before any output",
|
|
9
|
+
"d": "If nothing has been produced after 25 seconds, a \"Received, working on it\" card appears and the answer later replaces it in place. A turn that ends without a reply recalls that card."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"t": "The card shows how far the task has got",
|
|
13
|
+
"d": "While running, one line updates every 15 seconds: how long it has run and what it is doing (reading, editing or writing which file, running a command, searching the web; file names only). After 10 minutes it updates once a minute, and notes when nothing has happened for more than 2 minutes. Delivered steps such as writing files, committing or pushing, and sending files to the group are listed with ▶ at the top of the card and fold into a \"Process\" section when the turn ends."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"t": "/status shows progress",
|
|
17
|
+
"d": "While running it shows how many steps have run, how long ago the last activity was, and what it is doing."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"t": "Fix: a streamed reply could fail the whole turn",
|
|
21
|
+
"d": "Text that arrived while the previous update was still being sent used to fail the turn; it now queues behind it and keeps updating the card."
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"version": "1.4.2",
|
|
27
|
+
"date": "2026-11-09",
|
|
28
|
+
"name": "Check and install new versions from the console",
|
|
29
|
+
"items": [
|
|
30
|
+
{
|
|
31
|
+
"t": "Version checks in the console",
|
|
32
|
+
"d": "Software updates in Settings checks for stable releases at startup and daily and shows release notes when a new version is available."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"t": "One-click upgrade for background runs",
|
|
36
|
+
"d": "When a launchd agent runs SintBot, Upgrade now waits until every group is idle and settled, installs the new version into its own folder and switches to it; if it does not connect every group within 90 seconds, the previous version is restored. New messages wait about a minute during the switch and are asked to be resent."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"t": "Steps for foreground runs",
|
|
40
|
+
"d": "A foreground run shows how to stop, reinstall and start. Upgrades are not announced in groups."
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
2
44
|
{
|
|
3
45
|
"version": "1.4.1",
|
|
4
46
|
"date": "2026-11-09",
|
package/CHANGELOG.json
CHANGED
|
@@ -1,4 +1,46 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.4.3",
|
|
4
|
+
"date": "2026-11-09",
|
|
5
|
+
"name": "回复卡片显示进度",
|
|
6
|
+
"items": [
|
|
7
|
+
{
|
|
8
|
+
"t": "一时没有输出也先回一张卡",
|
|
9
|
+
"d": "25 秒还没有任何输出时,先出一张「已收到,正在处理」卡片;回答出来后在同一张卡里替换。这一轮最后决定不回复时,撤回这张卡。"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"t": "卡片显示做到哪了",
|
|
13
|
+
"d": "运行中每 15 秒更新一行:已跑多久、最近在做什么(看、改、写哪个文件,跑命令,查网页等,只给文件名);跑满 10 分钟后改为每分钟一次,超过 2 分钟没有新动静会注明。写改文件、提交推送、发文件到群这类交付动作以 ▶ 列在卡片上方,结束后收进「过程」折叠区。"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"t": "/status 显示进度",
|
|
17
|
+
"d": "运行中会显示已执行几步、最近一次动静在多久前,以及最近在做什么。"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"t": "修复:流式回复偶发整轮失败",
|
|
21
|
+
"d": "上一段内容还在发送时又来一段文字,原来会让这一轮失败;现在排在后面继续更新卡片。"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"version": "1.4.2",
|
|
27
|
+
"date": "2026-11-09",
|
|
28
|
+
"name": "在控制台查看和安装新版本",
|
|
29
|
+
"items": [
|
|
30
|
+
{
|
|
31
|
+
"t": "控制台检查新版本",
|
|
32
|
+
"d": "设置页的软件更新启动时和每天各检查一次正式版,有新版本时提示并附发布说明。"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"t": "后台运行时一键升级",
|
|
36
|
+
"d": "由 launchd 后台任务运行时,点「现在升级」会等所有群空闲、记录结清后,把新版本装到单独的目录再切换;新版本 90 秒内没连上全部群会自动换回旧版。切换期间约一分钟不处理新消息,会提示稍后重发。"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"t": "前台运行给出升级步骤",
|
|
40
|
+
"d": "前台运行时卡片说明如何停止、重新安装并启动。升级不在群里播报。"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
2
44
|
{
|
|
3
45
|
"version": "1.4.1",
|
|
4
46
|
"date": "2026-11-09",
|
package/bin/sintbot-v2.js
CHANGED
|
@@ -112,6 +112,7 @@ async function startMultiGroupService({ installation, preflightOnly, uiEnabled =
|
|
|
112
112
|
let service
|
|
113
113
|
let ui
|
|
114
114
|
let configControl
|
|
115
|
+
let updater
|
|
115
116
|
let liveTransport
|
|
116
117
|
let onSigint, onSigterm
|
|
117
118
|
try {
|
|
@@ -157,6 +158,7 @@ async function startMultiGroupService({ installation, preflightOnly, uiEnabled =
|
|
|
157
158
|
if (finishing) return
|
|
158
159
|
finishing = true
|
|
159
160
|
ui?.close()
|
|
161
|
+
updater?.close()
|
|
160
162
|
lock.release()
|
|
161
163
|
process.stderr.write(`C2 multi-group · ${result.reason}; platform stopped=${result.platformStopped}; runner drained=${result.drained}\n`,
|
|
162
164
|
() => process.exit(result.code))
|
|
@@ -255,8 +257,24 @@ async function startMultiGroupService({ installation, preflightOnly, uiEnabled =
|
|
|
255
257
|
configControl = require('../src/c2configcontrol').createC2ConfigControl({
|
|
256
258
|
installation, getBindings: () => service.bindings, getHeld: () => service.held,
|
|
257
259
|
assertWriterLock, store,
|
|
258
|
-
activateRoute: route => service.addRoute(route),
|
|
260
|
+
activateRoute: route => service.addRoute(route), isFrozen: () => service.inMaintenance,
|
|
259
261
|
log: message => process.stderr.write(`C2 multi-group · ${message}\n`) })
|
|
262
|
+
if (!preflightOnly) {
|
|
263
|
+
// Console software updates. Only a launchd job that starts this exact
|
|
264
|
+
// entry may upgrade, and only while every group is idle and settled.
|
|
265
|
+
const c2updates = require('../src/c2updater')
|
|
266
|
+
const pkg = require('../package.json')
|
|
267
|
+
const upgradeGate = c2updates.createC2UpgradeGate({ isRunning: () => running,
|
|
268
|
+
service: () => service, capacity, assertJournalScope })
|
|
269
|
+
updater = c2updates.createC2Updater({ currentVersion: pkg.version,
|
|
270
|
+
runMode: c2updates.detectRunMode({ privateBuild: pkg.private === true, entry: __filename }),
|
|
271
|
+
stateFile: path.join(stateDir, 'update.json'), dataDir: stateDir, configPath: cfg._path,
|
|
272
|
+
gate: upgradeGate, beginMaintenance: () => service.beginMaintenance(),
|
|
273
|
+
endMaintenance: () => service.endMaintenance(), expectedGroups: () => service.bindings.size,
|
|
274
|
+
registries: c2updates.registriesFromEnv(),
|
|
275
|
+
fetchImpl: require('../src/core/proxy').getProxyFetch() || globalThis.fetch,
|
|
276
|
+
autoCheck: uiEnabled, log: message => process.stderr.write(`C2 update · ${message}\n`) })
|
|
277
|
+
}
|
|
260
278
|
const stop = reason => { void service.stop(reason, 0).then(finish) }
|
|
261
279
|
onSigint = () => stop('SIGINT')
|
|
262
280
|
onSigterm = () => stop('SIGTERM')
|
|
@@ -268,7 +286,7 @@ async function startMultiGroupService({ installation, preflightOnly, uiEnabled =
|
|
|
268
286
|
getConfig: () => cfg, getBindings: () => service.bindings,
|
|
269
287
|
getHeld: () => service.held, getCapacity: () => capacity.snapshot(),
|
|
270
288
|
getConnHealth: () => liveTransport?.platform.connHealth?.() || {},
|
|
271
|
-
assertWriterLock, ...configControl,
|
|
289
|
+
assertWriterLock, ...configControl, updater,
|
|
272
290
|
log: message => process.stderr.write(`Console · ${message}\n`) })
|
|
273
291
|
await new Promise((resolve, reject) => {
|
|
274
292
|
ui.server.once('error', reject)
|
|
@@ -287,6 +305,10 @@ async function startMultiGroupService({ installation, preflightOnly, uiEnabled =
|
|
|
287
305
|
for (const item of started.held || []) {
|
|
288
306
|
process.stderr.write(`SintBot group ${item.chatId} is held: ${item.reason}\n`)
|
|
289
307
|
}
|
|
308
|
+
// A console upgrade that switched to this version is done once every
|
|
309
|
+
// group is connected; then the daily version check starts.
|
|
310
|
+
if (!started.held?.length) updater?.confirmStarted()
|
|
311
|
+
void updater?.start()
|
|
290
312
|
} catch (error) {
|
|
291
313
|
ui?.close()
|
|
292
314
|
const stopped = await service?.stop('startup-failed', 1)
|
|
@@ -297,6 +319,7 @@ async function startMultiGroupService({ installation, preflightOnly, uiEnabled =
|
|
|
297
319
|
throw error
|
|
298
320
|
} finally {
|
|
299
321
|
if (!running) {
|
|
322
|
+
updater?.close()
|
|
300
323
|
if (onSigint) process.off('SIGINT', onSigint)
|
|
301
324
|
if (onSigterm) process.off('SIGTERM', onSigterm)
|
|
302
325
|
capacity.close(); lock.release()
|
|
@@ -356,6 +379,14 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
356
379
|
process.stdout.write(HELP)
|
|
357
380
|
return
|
|
358
381
|
}
|
|
382
|
+
if (sub === 'upgrade-switch') {
|
|
383
|
+
// Internal: started detached by a console upgrade; never run it by hand.
|
|
384
|
+
const { values } = parseArgs({ args: rest, allowPositionals: false, options: { state: { type: 'string' } } })
|
|
385
|
+
if (!values.state || !path.isAbsolute(values.state)) throw new Error('upgrade-switch needs --state <absolute path>')
|
|
386
|
+
const outcome = await require('../src/c2upgradeswitch').runUpgradeSwitch({ stateFile: values.state })
|
|
387
|
+
process.exitCode = outcome === 'done' ? 0 : 1
|
|
388
|
+
return
|
|
389
|
+
}
|
|
359
390
|
if (!['init', 'start', 'incident'].includes(sub)) {
|
|
360
391
|
throw new Error(`${sub} is unavailable in this release; use sintbot help`)
|
|
361
392
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Install SintBot 1.4.
|
|
1
|
+
# Install SintBot 1.4.3 with an AI assistant
|
|
2
2
|
|
|
3
3
|
English · [简体中文](./zh-CN/AI-ASSISTED-INSTALL.md)
|
|
4
4
|
|
|
@@ -15,5 +15,5 @@ Use the terminal on the Mac that will run SintBot. A cloud shell cannot install
|
|
|
15
15
|
## Copyable request
|
|
16
16
|
|
|
17
17
|
```text
|
|
18
|
-
Install SintBot 1.4.
|
|
18
|
+
Install SintBot 1.4.3 on this Mac using the one-line installation guide. Preserve existing services, configuration, state and Codex or Claude Code account data. Do not expose credentials. Verify the actual installed executable and binary core, configure the Feishu groups with their runners, and initialize only clean new state. Run the foreground preflight and an authorized read-only group test, check the result, then verify clean stop/restart. Stop temporary processes when finished, and distinguish package installation from successful group acceptance.
|
|
19
19
|
```
|
package/docs/install.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Install SintBot 1.4.
|
|
1
|
+
# Install SintBot 1.4.3
|
|
2
2
|
|
|
3
3
|
English · [简体中文](./zh-CN/install.md)
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ You need a Mac, Node.js 22 or newer, and projects stored on that Mac. Install an
|
|
|
10
10
|
curl -fsSL https://sintbot.com/install.sh | bash
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The installer checks the environment, installs `sintbot@1.4.
|
|
13
|
+
The installer checks the environment, installs `sintbot@1.4.3` into `~/.local/share/sintbot`, and verifies the entry, built-in core and Feishu module. Continue when you see `Installed and verified SintBot 1.4.3`. No sudo is needed.
|
|
14
14
|
|
|
15
15
|
To preview, append `-s -- --dry-run`. To choose another location, set `SINTBOT_INSTALL_DIR` and adjust the executable paths below.
|
|
16
16
|
|
|
@@ -86,12 +86,12 @@ In a group, send "Read the first line of the project README and reply with only
|
|
|
86
86
|
|
|
87
87
|
## 5. Use it in a group
|
|
88
88
|
|
|
89
|
-
Any text message is a task. Replies arrive in a card that appears once the model starts answering
|
|
89
|
+
Any text message is a task. Replies arrive in a card that appears once the model starts answering, or after 25 seconds if it is still working without output. While the task runs, the card shows how long it has run, what it is doing and the steps already delivered; at the end those steps fold into a Process section.
|
|
90
90
|
|
|
91
91
|
| Command | Effect |
|
|
92
92
|
| --- | --- |
|
|
93
93
|
| `/help` | List commands |
|
|
94
|
-
| `/status` | Group status: model, project, busy or idle, queue |
|
|
94
|
+
| `/status` | Group status: model, project, busy or idle (steps and current work while busy), queue |
|
|
95
95
|
| `/model` | List models; `/model name` switches (only while the group is idle; starts a new conversation) |
|
|
96
96
|
| `/new` | Start a new conversation (only while the group is idle) |
|
|
97
97
|
| `/stop` | Stop the current task and cancel queued ones |
|
|
@@ -104,6 +104,8 @@ Messages sent while a task runs are added to that task in Codex groups (`codexSt
|
|
|
104
104
|
|
|
105
105
|
With `--ui`, open `http://127.0.0.1:4600` on this Mac. The console shows each group (a paused group shows Needs attention), stops tasks, binds new groups, and switches the model or project folder of an idle group. For LAN access, set a console password in its settings first, then enable LAN access.
|
|
106
106
|
|
|
107
|
+
Settings → Software updates checks for new releases. In the foreground setup above it shows the upgrade steps: press Ctrl-C and wait for exit, rerun the one-line installer, then start again with the same command. If your own launchd agent starts SintBot, Upgrade now waits until every group is idle and settled, installs the new version into `~/.local/share/sintbot/releases/<version>` and switches the agent to it; if the new version does not connect every group within 90 seconds, the previous one is restored. Upgrades are not announced in groups.
|
|
108
|
+
|
|
107
109
|
You can also add a group without the console: add it to the configuration, stop the service and run `start` again; the new group is registered automatically. Bound groups cannot be removed or renamed by editing the configuration.
|
|
108
110
|
|
|
109
111
|
## Stop and restart
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# 用 AI 助手安装 SintBot 1.4.
|
|
1
|
+
# 用 AI 助手安装 SintBot 1.4.3
|
|
2
2
|
|
|
3
3
|
[English](../AI-ASSISTED-INSTALL.md) · 简体中文
|
|
4
4
|
|
|
@@ -15,5 +15,5 @@
|
|
|
15
15
|
## 可复制的请求
|
|
16
16
|
|
|
17
17
|
```text
|
|
18
|
-
按一行安装指南,在这台 Mac 安装 SintBot 1.4.
|
|
18
|
+
按一行安装指南,在这台 Mac 安装 SintBot 1.4.3。保留已有服务、配置、状态和 Codex 或 Claude Code 账号数据,不输出凭据。验证实际入口与二进制核心,配置飞书群及各群的执行器,只对全新状态初始化。运行前台预检和已授权的只读群测试,对照结果,再验证正常停止与重启。完成后停止临时进程,并区分程序包安装与群验收结果。
|
|
19
19
|
```
|
package/docs/zh-CN/install.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# 安装 SintBot 1.4.
|
|
1
|
+
# 安装 SintBot 1.4.3
|
|
2
2
|
|
|
3
3
|
[English](../install.md) · 简体中文
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
curl -fsSL https://sintbot.com/install.sh | bash
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
安装器会自动检查环境,把 `sintbot@1.4.
|
|
13
|
+
安装器会自动检查环境,把 `sintbot@1.4.3` 装到 `~/.local/share/sintbot`,并验证程序入口、内置核心和飞书模块。看到 `Installed and verified SintBot 1.4.3` 就可以继续。不需要 sudo。
|
|
14
14
|
|
|
15
15
|
可选预览:在命令末尾加 `-s -- --dry-run`。要自定义安装位置,设置 `SINTBOT_INSTALL_DIR` 环境变量,并同步修改后面的可执行文件路径。
|
|
16
16
|
|
|
@@ -86,12 +86,12 @@ SINTBOT="$HOME/.local/share/sintbot/bin/sintbot"
|
|
|
86
86
|
|
|
87
87
|
## 5. 在群里使用
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
直接发文字就是给它派任务。回复以卡片呈现:模型开始输出时出现;25 秒还没有输出也会先出现。运行中卡片显示已跑多久、正在做什么和已完成的步骤,结束后这些步骤收进「过程」折叠区。
|
|
90
90
|
|
|
91
91
|
| 命令 | 作用 |
|
|
92
92
|
| --- | --- |
|
|
93
93
|
| `/help` | 查看可用命令 |
|
|
94
|
-
| `/status` |
|
|
94
|
+
| `/status` | 本群状态:模型、项目、是否在忙(在忙时含步数和正在做什么)、排队数 |
|
|
95
95
|
| `/model` | 查看可选模型;`/model 名字` 切换(只在本群空闲时生效,切换后开始新的一段对话) |
|
|
96
96
|
| `/new` | 开始新的一段对话(只在本群空闲时生效) |
|
|
97
97
|
| `/stop` | 停止当前任务,连同排队的一起取消 |
|
|
@@ -104,6 +104,8 @@ SINTBOT="$HOME/.local/share/sintbot/bin/sintbot"
|
|
|
104
104
|
|
|
105
105
|
带 `--ui` 启动后,在本机浏览器打开 `http://127.0.0.1:4600`。控制台可以查看各群状态(暂停的群显示「需要处理」)、停止任务、绑定新群、给空闲的群切换模型或项目目录。要从局域网访问,先在控制台的设置里设访问密码,再开启局域网访问。
|
|
106
106
|
|
|
107
|
+
「设置 → 软件更新」会检查新版本。按上面前台方式运行时,卡片给出升级步骤:按 Ctrl-C 等待退出,重新运行一行安装命令,再用原来的命令启动。如果用你自己的 launchd 后台任务启动 SintBot,「现在升级」会等所有群空闲、记录结清后,把新版本装到 `~/.local/share/sintbot/releases/<版本>` 并切换过去;新版本 90 秒内没连上全部群会自动换回旧版。升级不在群里播报。
|
|
108
|
+
|
|
107
109
|
不用控制台也能加群:在配置里加上新群,停止后重新 `start` 即可,新群会自动登记。已绑定的群不能通过改配置删除或改名。
|
|
108
110
|
|
|
109
111
|
## 停止与重启
|
package/install.sh
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# The function is parsed in full before installation starts, including when piped.
|
|
3
3
|
sintbot_install() (
|
|
4
4
|
set -eu
|
|
5
|
-
version=1.4.
|
|
5
|
+
version=1.4.3
|
|
6
6
|
prefix="${SINTBOT_INSTALL_DIR:-$HOME/.local/share/sintbot}"
|
|
7
7
|
fail() { printf 'SintBot: %s\n' "$*" >&2; exit 1; }
|
|
8
8
|
case "${1:-}" in
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sintbot",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Drive your Mac projects from Feishu groups with Codex or Claude Code, using a built-in agent core.",
|
|
6
6
|
"bin": {
|
|
@@ -63,6 +63,8 @@
|
|
|
63
63
|
"src/c2migration.js",
|
|
64
64
|
"src/c2multigroupcapacity.js",
|
|
65
65
|
"src/c2multigroupservice.js",
|
|
66
|
+
"src/c2updater.js",
|
|
67
|
+
"src/c2upgradeswitch.js",
|
|
66
68
|
"src/cdp.js",
|
|
67
69
|
"src/config.js",
|
|
68
70
|
"src/core/c2cardeffectbridge.js",
|
|
@@ -101,6 +103,7 @@
|
|
|
101
103
|
"src/core/redact.js",
|
|
102
104
|
"src/core/store.js",
|
|
103
105
|
"src/core/time.js",
|
|
106
|
+
"src/core/turnprogress.js",
|
|
104
107
|
"src/core/v2engine.js",
|
|
105
108
|
"src/core/verify.js",
|
|
106
109
|
"src/design.js",
|
|
@@ -129,6 +132,7 @@
|
|
|
129
132
|
"src/ui/serverbase.js",
|
|
130
133
|
"src/ui/sessions.js",
|
|
131
134
|
"src/ui/takeover.js",
|
|
135
|
+
"src/updater.js",
|
|
132
136
|
"src/usage.js"
|
|
133
137
|
],
|
|
134
138
|
"scripts": {
|
package/src/c2configcontrol.js
CHANGED
|
@@ -19,9 +19,9 @@ const RESET_KEYS = ['provider', 'model', 'workdir', 'codexApp']
|
|
|
19
19
|
// It never moves a journal, settles a receipt or reconnects the platform.
|
|
20
20
|
function createC2ConfigControl({ installation, getBindings, getHeld = () => new Map(),
|
|
21
21
|
assertWriterLock, store = null, activateRoute = null, probeRunner = assertRunnerExecutable,
|
|
22
|
-
log = () => {} } = {}) {
|
|
22
|
+
isFrozen = () => false, log = () => {} } = {}) {
|
|
23
23
|
if (!installation?.cfg?._path || typeof getBindings !== 'function' ||
|
|
24
|
-
typeof getHeld !== 'function' ||
|
|
24
|
+
typeof getHeld !== 'function' || typeof isFrozen !== 'function' ||
|
|
25
25
|
typeof assertWriterLock !== 'function' ||
|
|
26
26
|
store !== null && typeof store.resetGroupSession !== 'function' ||
|
|
27
27
|
activateRoute !== null && typeof activateRoute !== 'function' ||
|
|
@@ -47,6 +47,7 @@ function createC2ConfigControl({ installation, getBindings, getHeld = () => new
|
|
|
47
47
|
function validateConfig(raw) {
|
|
48
48
|
prepared = null
|
|
49
49
|
held()
|
|
50
|
+
if (isFrozen()) throw new Error('SintBot is upgrading; settings cannot change until it restarts')
|
|
50
51
|
const next = loadInstallationFromRaw(raw, cfg._path, { dataDir: stateDir,
|
|
51
52
|
allowAddedGroups: !!activateRoute && !!store })
|
|
52
53
|
const bindings = getBindings()
|
package/src/c2console.js
CHANGED
|
@@ -17,7 +17,10 @@ const WRITE_PATHS = ['/api/login', '/api/logout', '/api/logout/all',
|
|
|
17
17
|
'/api/groups/order', '/api/groups/policy', '/api/groups/redact',
|
|
18
18
|
'/api/groups/codex-steer', '/api/groups/pause', '/api/groups/stop',
|
|
19
19
|
'/api/agent', '/api/provider-settings', '/api/codex/verify', '/api/usage/refresh',
|
|
20
|
-
'/api/inbox/dismiss', '/api/issues/status'
|
|
20
|
+
'/api/inbox/dismiss', '/api/issues/status',
|
|
21
|
+
// Software updates: check and ignore anywhere; install only when launchd
|
|
22
|
+
// manages this exact entry (the updater refuses every other run mode).
|
|
23
|
+
'/api/update/check', '/api/update/start', '/api/update/ignore']
|
|
21
24
|
const SUPPORTED_RUNNERS = ['claude', 'codex']
|
|
22
25
|
|
|
23
26
|
function createC2ConsoleFacade({ getBindings, getCapacity, getConfig, getHeld = () => new Map(),
|
|
@@ -32,7 +35,7 @@ function createC2ConsoleFacade({ getBindings, getCapacity, getConfig, getHeld =
|
|
|
32
35
|
const startedAt = now()
|
|
33
36
|
const capabilities = Object.freeze({ mode: 'durable-v2',
|
|
34
37
|
reason: '此操作尚未接入当前版本的持久处理流程,未执行。',
|
|
35
|
-
summary: '
|
|
38
|
+
summary: '实时状态、认证管理、停止任务、绑定新群、空闲群的模型切换(Codex / Claude Code)和项目目录修改,以及后台运行时的软件升级已接入;该群有任务或未结记录时不能修改。开新会话、解除绑定、控制台插话、定时任务和画布暂不可用。',
|
|
36
39
|
allowed: { GET: READ_PATHS, POST: WRITE_PATHS } })
|
|
37
40
|
function held() {
|
|
38
41
|
if (assertWriterLock() !== true) throw new Error('服务写入权限已失效,操作未执行')
|
|
@@ -133,7 +136,7 @@ function createC2Console(options = {}) {
|
|
|
133
136
|
throw new TypeError('C2 console requires explicit installation paths')
|
|
134
137
|
}
|
|
135
138
|
const facade = createC2ConsoleFacade(options)
|
|
136
|
-
const ui = createUiServer({ configPath, host, log, getConnHealth,
|
|
139
|
+
const ui = createUiServer({ configPath, host, log, getConnHealth, updater: options.updater || null,
|
|
137
140
|
onHostChange: options.onHostChange,
|
|
138
141
|
getLiveState: facade.getLiveState, control: facade.control, runtimePolicy: facade,
|
|
139
142
|
issuesPath: path.join(stateDir, 'issues.jsonl'),
|
|
@@ -5,6 +5,7 @@ const { coordinateC2GlobalFault } = require('./core/c2globalfault')
|
|
|
5
5
|
|
|
6
6
|
const RECOVERING_NOTICE = '上一个任务出错,正在收尾,这条消息没有处理,请稍后重发。'
|
|
7
7
|
const HELD_NOTICE = '本群上一个任务结果不明,已暂停处理,等待人工确认;其他群不受影响。'
|
|
8
|
+
const UPGRADING_NOTICE = 'SintBot 正在升级,这条消息没有处理,请一分钟后重发。'
|
|
8
9
|
const reasonOf = error => String(error?.message || error || 'unknown').slice(0, 300)
|
|
9
10
|
|
|
10
11
|
// Turns a group binding still holds: a running turn, a non-idle handoff or a
|
|
@@ -58,6 +59,8 @@ function createC2MultiGroupService({ routes, assertWriterLock,
|
|
|
58
59
|
const bindings = new Map()
|
|
59
60
|
// chatId -> { reason, source: 'startup' | 'runtime', since, binding }
|
|
60
61
|
const suspended = new Map()
|
|
62
|
+
// Set while a console upgrade switches the service: no message is admitted.
|
|
63
|
+
let maintenance = false
|
|
61
64
|
let transport = null
|
|
62
65
|
let started = false
|
|
63
66
|
let starting = false
|
|
@@ -227,7 +230,7 @@ function createC2MultiGroupService({ routes, assertWriterLock,
|
|
|
227
230
|
if (bindings.has(route.chatId)) return Promise.resolve(bindings.get(route.chatId))
|
|
228
231
|
if (activating.has(route.chatId)) return activating.get(route.chatId)
|
|
229
232
|
const opening = (async () => {
|
|
230
|
-
if (!started || starting || stopPromise) throw new Error('C2 service is not serving')
|
|
233
|
+
if (!started || starting || stopPromise || maintenance) throw new Error('C2 service is not serving')
|
|
231
234
|
held()
|
|
232
235
|
const binding = await createBinding({ route, transport })
|
|
233
236
|
if (!started || stopPromise || !writerHeld() || suspended.has(route.chatId) ||
|
|
@@ -255,6 +258,10 @@ function createC2MultiGroupService({ routes, assertWriterLock,
|
|
|
255
258
|
// Foreign/paused group events are not faults and never get a receipt.
|
|
256
259
|
if (!allowed(msg?.chatId)) return { ignored: true, reason: 'unconfigured-or-paused' }
|
|
257
260
|
if (quarantine?.rejects(msg)) return { quarantined: true }
|
|
261
|
+
if (maintenance && started && !stopPromise) {
|
|
262
|
+
return answerC2InputRejection({ error: rejectC2Input('C2 service upgrading',
|
|
263
|
+
{ reason: 'upgrading', notice: UPGRADING_NOTICE }), msg, platform: sourceAdapter, log })
|
|
264
|
+
}
|
|
258
265
|
if (recovering.has(msg.chatId) && started && !stopPromise) {
|
|
259
266
|
// The group's failed turn is being closed; nothing is admitted meanwhile.
|
|
260
267
|
return answerC2InputRejection({ error: rejectC2Input('C2 group recovering',
|
|
@@ -319,6 +326,7 @@ function createC2MultiGroupService({ routes, assertWriterLock,
|
|
|
319
326
|
}
|
|
320
327
|
function recall(event) {
|
|
321
328
|
if (suspended.has(event?.chatId)) return { ignored: true, reason: 'group-held' }
|
|
329
|
+
if (maintenance) return { ignored: true, reason: 'upgrading' }
|
|
322
330
|
if (!allowed(event?.chatId)) return { ignored: true }
|
|
323
331
|
let binding
|
|
324
332
|
try {
|
|
@@ -409,7 +417,16 @@ function createC2MultiGroupService({ routes, assertWriterLock,
|
|
|
409
417
|
return [...suspended].map(([chatId, entry]) => Object.freeze({ chatId,
|
|
410
418
|
reason: entry.reason, source: entry.source, since: entry.since }))
|
|
411
419
|
}
|
|
412
|
-
|
|
420
|
+
function beginMaintenance() {
|
|
421
|
+
if (!maintenance) log('upgrade: admission closed for the service switch')
|
|
422
|
+
maintenance = true
|
|
423
|
+
}
|
|
424
|
+
function endMaintenance() {
|
|
425
|
+
if (maintenance) log('upgrade: admission reopened')
|
|
426
|
+
maintenance = false
|
|
427
|
+
}
|
|
428
|
+
return { start, stop, routeMessage, addRoute, heldGroups, beginMaintenance, endMaintenance,
|
|
429
|
+
get inMaintenance() { return maintenance },
|
|
413
430
|
get bindings() { return new Map(bindings) },
|
|
414
431
|
get held() { return new Map([...suspended].map(([chatId, entry]) =>
|
|
415
432
|
[chatId, { reason: entry.reason, source: entry.source, since: entry.since }])) } }
|