sintbot 1.0.8 → 1.0.10
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 +28 -0
- package/CHANGELOG.json +30 -0
- package/bin/sbot.js +54 -18
- package/package.json +1 -1
- package/src/core/engine.js +4 -3
- package/src/core/verify.js +5 -1
- package/src/health.js +2 -2
- package/src/ui/server.js +2 -2
package/CHANGELOG.en.json
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.0.10",
|
|
4
|
+
"name": "A steadier background service",
|
|
5
|
+
"items": [
|
|
6
|
+
{
|
|
7
|
+
"t": "No service without a chat platform",
|
|
8
|
+
"d": "If no Feishu, Slack or WeCom bot is connected yet, installing the background service now stops and points you to the console to connect one and bind a group first. Before, the service would exit as soon as it started."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"t": "Keys from environment variables reach the service",
|
|
12
|
+
"d": "When the config takes keys from environment variables, installing the service now asks whether to write them into the service definition, readable only by you. Without them, models that use those keys do not work in the service."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"t": "Clearer wording",
|
|
16
|
+
"d": "Trial runs show the model from your config instead of the model's own claim (third-party models often call themselves Claude). The health check gives a missing chat platform a row of its own, and in a local terminal trial the model-switch hint says how to switch."
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "1.0.9",
|
|
22
|
+
"name": "A steadier setup wizard",
|
|
23
|
+
"items": [
|
|
24
|
+
{
|
|
25
|
+
"t": "No more looping without a terminal",
|
|
26
|
+
"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."
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
2
30
|
{
|
|
3
31
|
"version": "1.0.8",
|
|
4
32
|
"name": "A clearer console, sharper style extraction",
|
package/CHANGELOG.json
CHANGED
|
@@ -1,4 +1,34 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.0.10",
|
|
4
|
+
"date": "2026-10-26",
|
|
5
|
+
"name": "常驻服务装得更稳",
|
|
6
|
+
"items": [
|
|
7
|
+
{
|
|
8
|
+
"t": "没接聊天平台时不装常驻服务",
|
|
9
|
+
"d": "安装时还没接飞书、Slack 或企业微信,就不再装常驻服务(装了也会一启动就退出),而是提示先在控制台里接好平台、绑群,再装服务。"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"t": "环境变量里的 key 能带进服务",
|
|
13
|
+
"d": "配置里的 key 取自环境变量时,装常驻服务会问一句要不要把它们写进服务定义(只有你自己能读);不写的话,服务里用这些 key 的模型用不了。"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"t": "说法更准",
|
|
17
|
+
"d": "试跑模型时直接显示配置里的型号,不再靠模型自报(第三方模型常自称 Claude);体检里「没配任何聊天平台」单列一行;在本机终端里试跑时,切模型的提示说清楚该怎么切。"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"version": "1.0.9",
|
|
23
|
+
"date": "2026-10-26",
|
|
24
|
+
"name": "安装向导更稳",
|
|
25
|
+
"items": [
|
|
26
|
+
{
|
|
27
|
+
"t": "没有终端时向导不再空转",
|
|
28
|
+
"d": "在脚本、容器或持续集成里运行安装向导、拿不到键盘输入时,向导会用默认值走完,不再在选模型那一步反复转圈、把磁盘写满,也不会自己去装 Claude Code;实在走不下去就停下提示,不写入配置。"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
2
32
|
{
|
|
3
33
|
"version": "1.0.8",
|
|
4
34
|
"date": "2026-10-24",
|
package/bin/sbot.js
CHANGED
|
@@ -213,7 +213,8 @@ const SUBHELP = {
|
|
|
213
213
|
+ ' 把 sintbot start --ui 装成常驻服务:崩溃自动拉起、登录后自启(macOS = launchd,Linux = systemd --user)\n'
|
|
214
214
|
+ ' install 写单元文件并立即启动;服务里固定用当前的 node、sbot 与配置文件的绝对路径,PATH 取当前 shell 的\n'
|
|
215
215
|
+ ' --dry-run 只打印要写的单元文件,不动系统;--ui-host 0.0.0.0 对局域网开控制台(须先 sintbot ui --set-password,否则拒装)\n'
|
|
216
|
-
+ ' 服务拿不到当前 shell 的环境变量:配置里 env:VAR 引用的 key 会被点名;--pass-env 把它们的值写进单元文件(文件权限 600)
|
|
216
|
+
+ ' 服务拿不到当前 shell 的环境变量:配置里 env:VAR 引用的 key 会被点名;--pass-env 把它们的值写进单元文件(文件权限 600),终端里装时不给也会问一句\n'
|
|
217
|
+
+ ' 还没接任何聊天平台时不装(退出码 3):服务会一启动就退出 —— 先 sintbot ui 在控制台接平台\n'
|
|
217
218
|
+ ' Windows 不自动装:打印 NSSM / pm2 的现成命令。日志在 ~/.sbot/logs/;同一份状态只许一个 daemon,第二个会直接报错退出',
|
|
218
219
|
version: '用法: sintbot version (也认 sbot --version / -v)\n'
|
|
219
220
|
+ ' 打印版本号',
|
|
@@ -1225,11 +1226,27 @@ async function cmdService(rest) {
|
|
|
1225
1226
|
let raw
|
|
1226
1227
|
try { raw = JSON.parse(fs.readFileSync(configPath, 'utf8')) } catch (e) { return die(`读不了配置 ${configPath}:${e.message}`) }
|
|
1227
1228
|
if (uiHost && !isLoopbackHost(uiHost) && !((raw.ui || {}).auth)) return die(`控制台对外(--ui-host ${uiHost})要先设密码:sintbot ui --set-password${values.config ? ' --config ' + values.config : ''} —— 否则服务一启动就退出、被反复拉起`)
|
|
1228
|
-
//
|
|
1229
|
+
// 一个聊天平台都没接:sintbot start 一启动就报错退出,装成服务同样是反复拉起的死循环 ⇒ 也在装之前拦。
|
|
1230
|
+
// 退出码 3 单列,install.sh 据此改口「先去控制台接平台」(env: 引用按当前 shell 解析,与服务带 --pass-env 时一致)
|
|
1231
|
+
const plat = platformsConfigured(loadConfig(configPath))
|
|
1232
|
+
if (!plat.feishu && !plat.wecom && !plat.slack) {
|
|
1233
|
+
const cfgArg = values.config ? ' --config ' + values.config : ''
|
|
1234
|
+
process.stderr.write(red(`sbot: 还没接聊天平台 —— 装成服务也会一启动就退出、被反复拉起。先运行 sintbot ui${cfgArg},在控制台「设置」里填飞书 / Slack / 企业微信的凭据并绑群(或重跑 sintbot setup),再装服务`) + '\n')
|
|
1235
|
+
process.exit(3)
|
|
1236
|
+
}
|
|
1237
|
+
// 服务进程拿不到当前 shell 的环境变量:点名配置里的 env: 引用;--pass-env 才把值写进单元文件。
|
|
1238
|
+
// 终端里装又没给 --pass-env 时问一句(默认是):不带进去,服务里用这些 key 的模型 / 平台直接用不了
|
|
1229
1239
|
const refs = svc.envRefs(raw)
|
|
1230
1240
|
const inShell = refs.filter((v) => process.env[v])
|
|
1231
1241
|
const nowhere = refs.filter((v) => !process.env[v])
|
|
1232
|
-
|
|
1242
|
+
let passEnv = !!values['pass-env']
|
|
1243
|
+
if (!passEnv && inShell.length && process.stdin.isTTY) {
|
|
1244
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr })
|
|
1245
|
+
const a = await new Promise((res) => rl.question(`${cyan('?')} 配置用到的环境变量 ${inShell.join(' ')} 服务里拿不到 —— 把它们的值写进服务定义吗?(文件权限 600;以后换 key 要重跑这条命令) ${dim('(Y/n)')} `, res))
|
|
1246
|
+
rl.close()
|
|
1247
|
+
passEnv = !/^n/i.test(String(a).trim())
|
|
1248
|
+
}
|
|
1249
|
+
const env = passEnv ? Object.fromEntries(inShell.map((v) => [v, process.env[v]])) : {}
|
|
1233
1250
|
const tools = ['claude', 'codex'].map((n) => whichSync(n)).filter(Boolean)
|
|
1234
1251
|
const node = svc.stableNodeBin({ execPath: process.execPath, candidates: [whichSync('node')] })
|
|
1235
1252
|
const sbotBin = path.resolve(__dirname, 'sbot.js')
|
|
@@ -1244,7 +1261,7 @@ async function cmdService(rest) {
|
|
|
1244
1261
|
})
|
|
1245
1262
|
if (node.warn) process.stderr.write(yellow('⚠ ' + node.warn) + '\n')
|
|
1246
1263
|
if (svc.versionedPath(sbotBin)) process.stderr.write(yellow(`⚠ sbot 装在带版本号的 node 目录下(${sbotBin}),换 node 版本后要重装 sintbot 并重跑 sintbot service install`) + '\n')
|
|
1247
|
-
if (inShell.length && !
|
|
1264
|
+
if (inShell.length && !passEnv) process.stderr.write(yellow(`⚠ 配置引用的环境变量 ${inShell.join(' ')} 只在当前终端里有,服务里拿不到 —— 加 --pass-env 把值写进服务定义(文件权限 600),或把值直接写进配置(配置本身是 600)`) + '\n')
|
|
1248
1265
|
if (nowhere.length) process.stderr.write(yellow(`⚠ 配置引用的环境变量 ${nowhere.join(' ')} 当前终端里也没有 —— 用到它们的模型 / 平台不会工作`) + '\n')
|
|
1249
1266
|
if (platform !== 'darwin' && platform !== 'linux') { process.stdout.write(svc.windowsHint(spec) + '\n'); return }
|
|
1250
1267
|
if (!tools.length) process.stderr.write(yellow('⚠ PATH 里没找到 claude / codex —— 服务能起,但群里的轮次会失败;装好后重跑 sintbot service install') + '\n')
|
|
@@ -1280,6 +1297,9 @@ async function cmdService(rest) {
|
|
|
1280
1297
|
// in the gitignored config), an optional proxy split, and optional Feishu /
|
|
1281
1298
|
// group binding (with id auto-fetch) — then runs doctor. Shares helpers with the
|
|
1282
1299
|
// console (deriveNoProxy / feishu*), so CLI and UI stay in sync.
|
|
1300
|
+
// 向导走一遍最多问二十来个问题;输入结束后还要问到这么多次,只能是某个循环拿默认值走不出去
|
|
1301
|
+
const SETUP_MAX_ASKS_AFTER_EOF = 60
|
|
1302
|
+
|
|
1283
1303
|
async function cmdSetup(rest) {
|
|
1284
1304
|
const { values } = parseArgs({ args: rest, allowPositionals: true, options: { config: { type: 'string' } } })
|
|
1285
1305
|
const cfgPath = defaultConfigPath(values.config)
|
|
@@ -1319,14 +1339,22 @@ async function cmdSetup(rest) {
|
|
|
1319
1339
|
const nextLine = () => queued.length ? Promise.resolve(queued.shift())
|
|
1320
1340
|
: closed ? Promise.resolve(null)
|
|
1321
1341
|
: new Promise((res) => { waiter = res })
|
|
1322
|
-
|
|
1342
|
+
// 输入读完之后(管道、</dev/null、Docker RUN、CI)每一问都拿默认值。默认值走不出去的循环会原地空转:
|
|
1343
|
+
// 1.0.8 的模型菜单默认「1」→ key 为空 → 回菜单,3 分钟往输出里写了 18 GB。所以 ① 会转圈或有副作用的问题
|
|
1344
|
+
// 另给一个「输入已结束」时的答案(endedDef);② 兜底:输入结束后还问了这么多次就中止,配置不落盘。
|
|
1345
|
+
let askedAfterEnd = 0
|
|
1346
|
+
const ask = async (q, def, endedDef = def) => {
|
|
1323
1347
|
process.stdout.write(`${cyan('?')} ${q}${def ? dim(` [${def}]`) : ''} `)
|
|
1324
1348
|
const a = await nextLine()
|
|
1325
|
-
if (a == null)
|
|
1326
|
-
|
|
1349
|
+
if (a == null) {
|
|
1350
|
+
process.stdout.write('\n')
|
|
1351
|
+
if (++askedAfterEnd > SETUP_MAX_ASKS_AFTER_EOF) throw new Error('输入已经结束,向导还在提问 —— 已中止,配置没有写入。请在终端里直接运行 sintbot setup')
|
|
1352
|
+
return endedDef || ''
|
|
1353
|
+
}
|
|
1354
|
+
return String(a).trim() || def || ''
|
|
1327
1355
|
}
|
|
1328
|
-
const askYN = async (q, defYes) => {
|
|
1329
|
-
const a = (await ask(`${q} ${dim(defYes ? '(Y/n)' : '(y/N)')}`, '')).toLowerCase()
|
|
1356
|
+
const askYN = async (q, defYes, endedYes = defYes) => {
|
|
1357
|
+
const a = (await ask(`${q} ${dim(defYes ? '(Y/n)' : '(y/N)')}`, '', endedYes ? 'y' : 'n')).toLowerCase()
|
|
1330
1358
|
return a ? a[0] === 'y' : !!defYes
|
|
1331
1359
|
}
|
|
1332
1360
|
// Key/secret prompt: echoes visibly (so you can see the paste landed) and then
|
|
@@ -1363,7 +1391,8 @@ async function cmdSetup(rest) {
|
|
|
1363
1391
|
if (claudeBin) process.stdout.write(` ${cyan('✓')} claude ${dim(claudeBin)}\n`)
|
|
1364
1392
|
else {
|
|
1365
1393
|
process.stdout.write(` ${red('✗')} 没找到 claude(Claude Code CLI)—— 每一轮都靠它干活\n`)
|
|
1366
|
-
|
|
1394
|
+
// 没人回答(输入已结束)就不替他装软件
|
|
1395
|
+
if (await askYN(`现在安装 Claude Code(npm i -g ${installdeps.PACKAGES.claude})?`, true, false)) claudeBin = installDep('claude', claudeName)
|
|
1367
1396
|
else process.stdout.write(dim(` 稍后自己装:npm i -g ${installdeps.PACKAGES.claude}`) + '\n')
|
|
1368
1397
|
}
|
|
1369
1398
|
// Claude 登录态留到模型步一起报,那里据此决定推荐走哪条路。官方非交互子命令,0.2s、不联网;
|
|
@@ -1406,7 +1435,7 @@ async function cmdSetup(rest) {
|
|
|
1406
1435
|
const readyKeyed = () => Object.keys(raw.providers).filter((n) => raw.providers[n].baseUrl && !raw.providers[n].hidden && setupmodels.keyPresent(raw.providers[n].apiKey))
|
|
1407
1436
|
const verified = [] // 本次向导里试跑通过的
|
|
1408
1437
|
const tried = new Set() // 本次试跑过的(不论成败),结尾不再重复问
|
|
1409
|
-
// 试跑一轮(与控制台「模型 → 测试」同一个 runTurn)
|
|
1438
|
+
// 试跑一轮(与控制台「模型 → 测试」同一个 runTurn 和提示词):不用工具,只要一句回复。
|
|
1410
1439
|
// 关掉 claude 的自动重试:key 错时它默认反复重试约 3 分钟才放弃,关掉后半秒内就拿到 401 原文
|
|
1411
1440
|
const tryTurn = async (name) => {
|
|
1412
1441
|
const p = raw.providers[name]
|
|
@@ -1416,7 +1445,7 @@ async function cmdSetup(rest) {
|
|
|
1416
1445
|
const t0 = Date.now()
|
|
1417
1446
|
try {
|
|
1418
1447
|
const { result, aborted } = await runner.runTurn({
|
|
1419
|
-
bin: claudeBin || claudeName, prompt:
|
|
1448
|
+
bin: claudeBin || claudeName, prompt: verify.TRIAL_PROMPT, provider, model: provider.model,
|
|
1420
1449
|
workdir: os.tmpdir(), permissionMode: (raw.agent && raw.agent.permissionMode) || 'dontAsk', signal: ac.signal,
|
|
1421
1450
|
extraEnv: { CLAUDE_CODE_MAX_RETRIES: '0' },
|
|
1422
1451
|
})
|
|
@@ -1424,13 +1453,14 @@ async function cmdSetup(rest) {
|
|
|
1424
1453
|
if (aborted || ac.signal.aborted) return { ok: false, text: `timeout:${Math.round((Date.now() - t0) / 1000)} 秒内没有回复`, ms: Date.now() - t0 }
|
|
1425
1454
|
const ok = !!text && !(result && result.isError)
|
|
1426
1455
|
verify.recordTurn(provider, ok, text) // doctor / 控制台据此报「能用」或「key 不对」
|
|
1427
|
-
return { ok, text: text || '没有返回内容', ms: Date.now() - t0 }
|
|
1456
|
+
return { ok, text: text || '没有返回内容', ms: Date.now() - t0, model: provider.model }
|
|
1428
1457
|
} catch (e) {
|
|
1429
1458
|
verify.recordTurn(provider, false, (e && e.message) || String(e))
|
|
1430
1459
|
return { ok: false, text: (e && e.message) || String(e), ms: Date.now() - t0 }
|
|
1431
1460
|
} finally { clearTimeout(timer) }
|
|
1432
1461
|
}
|
|
1433
|
-
|
|
1462
|
+
// 型号取配置里的(r.model),不靠模型自报:走 Claude Code 的第三方模型常自称 Claude,新用户会以为没切成
|
|
1463
|
+
const reportTurn = (label, r) => process.stdout.write(` ${cyan('✓')} ${label}${r.model ? ' · ' + r.model : ''} 能用(${(r.ms / 1000).toFixed(1)} 秒):${r.text.replace(/\s+/g, ' ').slice(0, 80)}\n`)
|
|
1434
1464
|
// 配一家用 API key 的:选站点 → 告诉去哪拿 key → 粘贴 → 当场试跑,失败说人话、可重试
|
|
1435
1465
|
const configureKeyed = async (name) => {
|
|
1436
1466
|
const info = setupmodels.PROVIDER_INFO[name] || { label: name }
|
|
@@ -1455,7 +1485,7 @@ async function cmdSetup(rest) {
|
|
|
1455
1485
|
if (r.ok) { reportTurn(info.label, r); verified.push(name); return true }
|
|
1456
1486
|
const why = setupmodels.explainTestFailure(r.text, { baseUrl: p.baseUrl })
|
|
1457
1487
|
process.stdout.write(` ${red('✗')} ${why.text}\n`)
|
|
1458
|
-
if (!(await askYN('再试一次?(可以重新粘贴 key)', why.kind === 'auth' || why.kind === 'network'))) {
|
|
1488
|
+
if (!(await askYN('再试一次?(可以重新粘贴 key)', why.kind === 'auth' || why.kind === 'network', false))) {
|
|
1459
1489
|
process.stdout.write(dim(' key 先保存;跑通之前这个模型的轮次会失败,之后可在控制台「模型」页再测') + '\n')
|
|
1460
1490
|
return true
|
|
1461
1491
|
}
|
|
@@ -1490,7 +1520,7 @@ async function cmdSetup(rest) {
|
|
|
1490
1520
|
+ ' 3) 用 Claude 订阅 —— 另开一个终端跑 claude auth login,登录好回到这里\n'
|
|
1491
1521
|
+ ` 4) 用 ChatGPT(Codex)—— 另开一个终端跑 codex login(没装先 npm i -g ${installdeps.PACKAGES.codex}),登录好回到这里\n`
|
|
1492
1522
|
+ ' 5) 先跳过(之后在控制台「模型」页添加)\n')
|
|
1493
|
-
const a = await ask('选', '1')
|
|
1523
|
+
const a = await ask('选', '1', '5') // 输入已结束 ⇒ 先跳过(选 1 拿不到 key 会回到这里)
|
|
1494
1524
|
if (a === '1') { if (await configureKeyed(setupmodels.RECOMMENDED)) break; continue }
|
|
1495
1525
|
if (a === '2') { if (await pickOthers() && readyKeyed().length) break; continue }
|
|
1496
1526
|
if (a === '3' || a === '4') {
|
|
@@ -1789,10 +1819,16 @@ async function cmdSetup(rest) {
|
|
|
1789
1819
|
const cfg = loadConfig(cfgPath)
|
|
1790
1820
|
await runDoctor(cfg)
|
|
1791
1821
|
const cfgArg = cfgPath === path.join(os.homedir(), '.sbot', 'config.json') ? '' : ` --config ${cfgPath}`
|
|
1822
|
+
// 一个平台都没接时守护进程起不来(装服务会被拦下):先去控制台接平台,再装服务
|
|
1823
|
+
const plat = platformsConfigured(cfg)
|
|
1824
|
+
const noPlatform = !plat.feishu && !plat.wecom && !plat.slack
|
|
1792
1825
|
process.stdout.write('\n' + gray('— 下一步 —') + '\n'
|
|
1793
1826
|
+ ` sintbot agent run --provider ${cfg.defaultProvider} "用一句话自我介绍"${cfgArg} ${dim('先在终端试一轮')}\n`
|
|
1794
|
-
+
|
|
1795
|
-
|
|
1827
|
+
+ (noPlatform
|
|
1828
|
+
? ` sintbot ui${cfgArg} ${dim('还没接聊天平台:在控制台「设置」里填飞书 / Slack / 企业微信的凭据、绑群')}\n`
|
|
1829
|
+
+ ` sintbot service install${cfgArg} ${dim('接好平台后,装成常驻服务(开机/登录自启,崩溃自动拉起)')}\n`
|
|
1830
|
+
: ` sintbot service install${cfgArg} ${dim('装成常驻服务(开机/登录自启,崩溃自动拉起)')}\n`
|
|
1831
|
+
+ ` sintbot ui${cfgArg} ${dim('打开控制台:绑群、切模型、看实时活动')}\n`)
|
|
1796
1832
|
+ (!((cfg.providers || {})[cfg.defaultProvider] || {}).baseUrl
|
|
1797
1833
|
? '\n' + yellow('⚠ 订阅条款:') + '用个人的 Claude / ChatGPT 订阅登录来服务多人群聊,可能不符合提供方的使用条款;团队使用请改用 API key 或企业账号,并自行确认合规。\n' : ''))
|
|
1798
1834
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sintbot",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
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",
|
package/src/core/engine.js
CHANGED
|
@@ -203,10 +203,11 @@ const T = {
|
|
|
203
203
|
// ⚠ 「唯一写入方」这条约束没作废,只是换了兑现方式:群内切走的是**与控制台同一套写入动作**
|
|
204
204
|
// (配置落盘 + 运行时覆盖 + 清会话一起做),不是新开一条写法。
|
|
205
205
|
// `current`/`list` 都是预格式化的「模型名 · 型号」,免得把名字与型号混为一谈。
|
|
206
|
-
//
|
|
206
|
+
// 没有注入写入器只发生在 CLI 试跑(sintbot chat;守护进程总会注入):如实说这里切不了、群里能切,
|
|
207
|
+
// 别给一条按了没反应的指令,也别说成「群内不支持」(1.0.9 及以前那样说,读起来像群里也切不了)。
|
|
207
208
|
modelInfo: (current, list, canSwitch) => `【当前模型】${current}\n【可用】${list}\n` + (canSwitch
|
|
208
209
|
? '换模型:发「/model 名字」,例如 /model volcano\n⚠ 换完是**新的一段对话**,之前聊的内容不再带着'
|
|
209
|
-
: '
|
|
210
|
+
: '这里是终端试跑,不能切换 —— 换模型用 sintbot chat --provider 名字;在群里发「/model 名字」就能切'),
|
|
210
211
|
modelUnknown: (want, names) => `⚠ 没有「${want}」这个模型\n【可用】${names}\n发「/model 名字」换,名字要一模一样`,
|
|
211
212
|
modelSame: (current) => `本群已经在用 ${current},没有变化`,
|
|
212
213
|
// ⚠ 忙的时候不切:换模型要清会话,而当前这轮正靠它跑;悄悄换掉等于把人手头的活废了。
|
|
@@ -485,7 +486,7 @@ function helpText(group, provider, hasImagen, caps = {}) {
|
|
|
485
486
|
'· /skip — 只放弃手头这件,排队的继续;之前聊的还记得',
|
|
486
487
|
'· /stop — 手头和排队的全取消;之前聊的还记得',
|
|
487
488
|
'· /new — 全取消,并忘掉之前聊的,从头开始',
|
|
488
|
-
'· /model — 本群在用的模型' + (canSwitchModel ? ';换:/model 名字(换完之前聊的不再带着)' : '(
|
|
489
|
+
'· /model — 本群在用的模型' + (canSwitchModel ? ';换:/model 名字(换完之前聊的不再带着)' : '(终端试跑里不能切,换用 --provider)'),
|
|
489
490
|
// 压缩那半句(v0.11.92):群里会冒出「🔄 会话已自动压缩 + 额度」这条通知,/help 里得有它的出处 ——
|
|
490
491
|
// 否则第一次见到的人只会问「这是什么」;放在 /hud 这行,因为会话规模正是它管的。
|
|
491
492
|
'· /hud — 额度与本会话用量;聊太长会自动压缩,群里会提示',
|
package/src/core/verify.js
CHANGED
|
@@ -82,4 +82,8 @@ function when(at, now = Date.now()) {
|
|
|
82
82
|
return d.toDateString() === n.toDateString() ? `今天 ${pad(d.getHours())}:${pad(d.getMinutes())}` : `${d.getMonth() + 1} 月 ${d.getDate()} 日`
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
// 试跑一轮用的提示词(向导试跑与控制台「模型 → 测试」共用)。不让模型自报身份:走 Claude Code 的第三方模型
|
|
86
|
+
// 常照系统提示自称 Claude,新用户会以为没切成;型号由调用方按配置显示
|
|
87
|
+
const TRIAL_PROMPT = '用一句话回复,说明你已准备好开始工作。'
|
|
88
|
+
|
|
89
|
+
module.exports = { fingerprint, providerFp, feishuFp, slackFp, lookup, record, recordTurn, recordPrecheck, when, REFRESH_MS, TRIAL_PROMPT }
|
package/src/health.js
CHANGED
|
@@ -277,10 +277,10 @@ async function checkHealth(cfg, opts = {}) {
|
|
|
277
277
|
if (!cfg.feishu.botOpenId) add('warn', 'feishu.botOpenId', (cfg.feishu.onlyWhenMentioned
|
|
278
278
|
? '没填 — 开着「只在 @ 时回复」却认不出哪个 @ 是机器人,群里一条都不会回'
|
|
279
279
|
: '没填 — 分不清 @机器人 和 @别人,群里 @ 别人的消息也会接') + ';重跑 sintbot setup 选飞书预检可自动填')
|
|
280
|
-
} else if (!hasId && !hasSecret) { if (!plat.wecom && !plat.slack) add('warn', '
|
|
280
|
+
} else if (!hasId && !hasSecret) { if (!plat.wecom && !plat.slack) add('warn', 'platforms', '没配任何聊天平台 — sintbot start 收不到消息') }
|
|
281
281
|
else add('fail', 'feishu', hasId ? '缺 appSecret' : '缺 appId')
|
|
282
282
|
} else if (!plat.wecom && !plat.slack) {
|
|
283
|
-
add('warn', '
|
|
283
|
+
add('warn', 'platforms', '没配任何聊天平台 — sintbot start 收不到消息')
|
|
284
284
|
}
|
|
285
285
|
// 写了 wecom 块就要报:env: 引用解析成空串也算缺(真机踩到 —— 非交互 shell 没 source 凭据文件,doctor 一声不吭地把群报成「没配企微」)
|
|
286
286
|
if (cfg.wecom) {
|
package/src/ui/server.js
CHANGED
|
@@ -627,11 +627,11 @@ function createUiServer({ configPath, getLiveState = null, getConnHealth = null,
|
|
|
627
627
|
try {
|
|
628
628
|
const { result } = await runTurn({
|
|
629
629
|
bin: cfg.agent.bin,
|
|
630
|
-
prompt: prompt ||
|
|
630
|
+
prompt: prompt || verify.TRIAL_PROMPT,
|
|
631
631
|
provider,
|
|
632
632
|
model: provider.model,
|
|
633
633
|
permissionMode: cfg.agent.permissionMode,
|
|
634
|
-
signal: ac.signal, // a one-line
|
|
634
|
+
signal: ac.signal, // a one-line reply — needs no tools, so no allow list
|
|
635
635
|
})
|
|
636
636
|
const ok = !(result && result.isError)
|
|
637
637
|
verify.recordTurn(provider, ok, (result && result.text) || '', vopts) // doctor / 概览据此报「能用」或「key 不对」
|