sintbot 1.0.15 → 1.3.0
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 +118 -0
- package/CHANGELOG.json +118 -0
- package/README.md +20 -156
- package/README.zh-CN.md +20 -164
- package/THIRD-PARTY-NOTICES.md +394 -0
- package/bin/sintbot-v2.js +363 -0
- package/docs/AI-ASSISTED-INSTALL.md +19 -0
- package/docs/install.md +93 -0
- package/docs/zh-CN/AI-ASSISTED-INSTALL.md +19 -0
- package/docs/zh-CN/install.md +93 -0
- package/install.sh +84 -0
- package/package.json +74 -34
- package/src/agent/claudecode.js +149 -108
- package/src/agent/codexapp.js +458 -0
- package/src/agent/codexcode.js +210 -107
- package/src/agent/errtag.js +34 -0
- package/src/agent/privatecore.js +790 -0
- package/src/agent/privatecore.wasm +0 -0
- package/src/agent/runner.js +10 -8
- package/src/agent/winexec.js +35 -5
- package/src/c2install.js +117 -0
- package/src/config.js +2 -1
- package/src/core/c2cardeffectbridge.js +51 -0
- package/src/core/c2cardterminalpath.js +216 -0
- package/src/core/c2durableingress.js +148 -0
- package/src/core/c2durablejournal.js +1560 -0
- package/src/core/c2durableruntime.js +2361 -0
- package/src/core/c2eventidentity.js +90 -0
- package/src/core/c2globalfault.js +401 -0
- package/src/core/c2localeffectexecutor.js +193 -0
- package/src/core/c2payloadhandles.js +333 -0
- package/src/core/c2persistrelation.js +179 -0
- package/src/core/c2persiststate.js +342 -0
- package/src/core/c2platformrecoverysender.js +76 -0
- package/src/core/c2recoverygate.js +385 -0
- package/src/core/c2runnerinput.js +63 -0
- package/src/core/c2servicestartgate.js +103 -0
- package/src/core/c2sessionbridge.js +72 -0
- package/src/core/c2sourcequarantine.js +30 -0
- package/src/core/c2startuprecovery.js +105 -0
- package/src/core/c2successorbridge.js +151 -0
- package/src/core/c2terminaleffectbridge.js +154 -0
- package/src/core/c2terminalprune.js +108 -0
- package/src/core/c2turnprojection.js +177 -0
- package/src/core/enginecheckpoint.js +211 -0
- package/src/core/engineeffectcursor.js +394 -0
- package/src/core/enginestepcoordinator.js +183 -0
- package/src/core/store.js +52 -8
- package/src/core/v2engine.js +1469 -0
- package/src/platform/feishu.js +324 -67
- package/src/plugins.js +1 -9
- package/assets/design-styles/acid-pop.md +0 -155
- package/assets/design-styles/archive-index.md +0 -161
- package/assets/design-styles/art-deco.md +0 -164
- package/assets/design-styles/cinematic-noir.md +0 -178
- package/assets/design-styles/editorial.md +0 -121
- package/assets/design-styles/extra/README.md +0 -23
- package/assets/design-styles/extra/constructivism.md +0 -146
- package/assets/design-styles/extra/neo-brutalism.md +0 -175
- package/assets/design-styles/extra/new-wave.md +0 -166
- package/assets/design-styles/extra/nordic-material.md +0 -162
- package/assets/design-styles/extra/paris-couture.md +0 -162
- package/assets/design-styles/extra/tropical-modern.md +0 -168
- package/assets/design-styles/extra/ulm-functional.md +0 -182
- package/assets/design-styles/extra/wa-shikimen.md +0 -160
- package/assets/design-styles/extra/y2k-chrome.md +0 -172
- package/assets/design-styles/geometric-pop.md +0 -122
- package/assets/design-styles/hand-drawn.md +0 -117
- package/assets/design-styles/islamic-geometry.md +0 -155
- package/assets/design-styles/liquid-glass.md +0 -183
- package/assets/design-styles/neo-chinese.md +0 -136
- package/assets/design-styles/precision-mono.md +0 -156
- package/assets/design-styles/quiet-luxury.md +0 -163
- package/assets/design-styles/riso-print.md +0 -116
- package/assets/design-styles/soft-friendly.md +0 -174
- package/assets/design-styles/swiss-grid.md +0 -156
- package/assets/design-styles/tech-dark.md +0 -182
- package/assets/favicon.svg +0 -6
- package/assets/logo.svg +0 -24
- package/bin/sbot.js +0 -4711
- package/docs/slack-app-manifest.yaml +0 -104
- package/sbot.config.example.json +0 -235
- package/src/agent/askonce.js +0 -76
- package/src/agent/claudemodels.js +0 -60
- package/src/agent/claudestatus.js +0 -74
- package/src/agent/claudeusage.js +0 -200
- package/src/agent/codexstatus.js +0 -181
- package/src/agent/imagen.js +0 -458
- package/src/agent/quotaerr.js +0 -44
- package/src/agent/transienterr.js +0 -32
- package/src/cdp.js +0 -254
- package/src/clilang.js +0 -31
- package/src/core/cron.js +0 -182
- package/src/core/cronfire.js +0 -120
- package/src/core/cronrun.js +0 -116
- package/src/core/engine.js +0 -2762
- package/src/core/interfaces.js +0 -114
- package/src/core/issues.js +0 -185
- package/src/core/ledger.js +0 -30
- package/src/core/opsalert.js +0 -214
- package/src/core/pending.js +0 -123
- package/src/core/proxy.js +0 -424
- package/src/core/quotaalert.js +0 -79
- package/src/core/quotaerr.js +0 -55
- package/src/core/scheduler.js +0 -59
- package/src/design.js +0 -5161
- package/src/gitrepo.js +0 -150
- package/src/health.js +0 -353
- package/src/installdeps.js +0 -72
- package/src/leftover.js +0 -174
- package/src/platform/boardnotice.js +0 -77
- package/src/platform/boardops.js +0 -36
- package/src/platform/boardwatch.js +0 -447
- package/src/platform/cli.js +0 -45
- package/src/platform/slack.js +0 -1289
- package/src/platform/takeovercard.js +0 -37
- package/src/platform/wecom.js +0 -938
- package/src/platform/wecomcli.js +0 -128
- package/src/platform/wecomgate.js +0 -108
- package/src/platform/wecomname.js +0 -43
- package/src/platform/wecompoll.js +0 -241
- package/src/preview.js +0 -581
- package/src/projectmemory.js +0 -175
- package/src/service.js +0 -317
- package/src/ui/auth.js +0 -31
- package/src/ui/browser.html +0 -392
- package/src/ui/browserroutes.js +0 -234
- package/src/ui/console.html +0 -5494
- package/src/ui/dashboard.js +0 -168
- package/src/ui/feishulogin.js +0 -136
- package/src/ui/server.js +0 -2314
- package/src/ui/sessions.js +0 -78
- package/src/ui/takeover.js +0 -108
- package/src/updater.js +0 -243
- package/src/upgrade.js +0 -35
- package/src/usage.js +0 -144
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
// Restricted macOS Feishu single-group entry. No legacy daemon or terminal
|
|
5
|
+
// chat dispatch is reachable from this entry.
|
|
6
|
+
const fs = require('node:fs')
|
|
7
|
+
const path = require('node:path')
|
|
8
|
+
const { parseArgs } = require('node:util')
|
|
9
|
+
const { applyProxyEnv, chatPlatform } = require('../src/config')
|
|
10
|
+
const { secureDataDir } = require('../src/core/datadir')
|
|
11
|
+
const { StateStore } = require('../src/core/store')
|
|
12
|
+
const { createFeishuPlatform, feishuClient } = require('../src/platform/feishu')
|
|
13
|
+
const { createMultiPlatform } = require('../src/platform/multi')
|
|
14
|
+
|
|
15
|
+
async function startService(rest) {
|
|
16
|
+
const { values } = parseArgs({ args: rest, allowPositionals: false,
|
|
17
|
+
options: { config: { type: 'string' }, ui: { type: 'boolean' },
|
|
18
|
+
'no-open': { type: 'boolean' }, 'ui-port': { type: 'string' },
|
|
19
|
+
'ui-host': { type: 'string' }, preflight: { type: 'boolean' } } })
|
|
20
|
+
const preflightOnly = values.preflight === true
|
|
21
|
+
if (values.ui || values['no-open'] || values['ui-port'] || values['ui-host']) {
|
|
22
|
+
throw new Error('C2 v2 single-group service has no durable UI route; remove UI options')
|
|
23
|
+
}
|
|
24
|
+
const { loadInstallation, assertPrivateDirectory } = require('../src/c2install')
|
|
25
|
+
const { stateDir, file, cfg, groups, serviceChatId, provider } = loadInstallation(values.config)
|
|
26
|
+
assertPrivateDirectory(stateDir)
|
|
27
|
+
let journalStat
|
|
28
|
+
try { journalStat = fs.lstatSync(file) } catch {}
|
|
29
|
+
if (!journalStat?.isFile() || journalStat.isSymbolicLink()) {
|
|
30
|
+
throw new Error('No initialized journal; use sintbot init for a new installation. Existing state must be reviewed before migration.')
|
|
31
|
+
}
|
|
32
|
+
applyProxyEnv(cfg)
|
|
33
|
+
const { spawnSync } = require('node:child_process')
|
|
34
|
+
const probe = spawnSync(provider.bin || 'codex', ['--version'], {
|
|
35
|
+
encoding: 'utf8', timeout: 10_000, maxBuffer: 64 * 1024,
|
|
36
|
+
env: { ...process.env, ...(provider.env || {}) },
|
|
37
|
+
})
|
|
38
|
+
if (probe.error || probe.status !== 0 || !/codex/i.test(probe.stdout || '')) {
|
|
39
|
+
throw new Error('Codex executable preflight failed; install and sign in to Codex, then check the configured executable.')
|
|
40
|
+
}
|
|
41
|
+
const runner = require('../src/agent/runner')
|
|
42
|
+
secureDataDir(stateDir)
|
|
43
|
+
const lockFile = path.join(stateDir, 'daemon.lock')
|
|
44
|
+
const lock = require('../src/core/instancelock').acquire(lockFile)
|
|
45
|
+
if (!lock.ok) throw new Error('C2 v2 development service cannot hold the single-writer lock')
|
|
46
|
+
let running = false
|
|
47
|
+
try {
|
|
48
|
+
// The old daemon records interrupted work here and consumes it on restart.
|
|
49
|
+
// Starting v2 while such a file exists would leave that work invisible;
|
|
50
|
+
// never consume, delete, or reinterpret it without an explicit migration.
|
|
51
|
+
for (const name of ['pending-turns.json', 'engine-receipts.json']) {
|
|
52
|
+
try {
|
|
53
|
+
fs.lstatSync(path.join(stateDir, name))
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error.code === 'ENOENT') continue
|
|
56
|
+
throw error
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`C2 v2 cannot start with legacy ${name}; preserve it and drain with the old service or use a reviewed migration`)
|
|
59
|
+
}
|
|
60
|
+
const { createC2V2SourceQuarantine } = require('../src/core/c2sourcequarantine')
|
|
61
|
+
const requireQuarantine = process.env.SBOT_C2_V2_REQUIRE_BLOCK_SOURCE_ID
|
|
62
|
+
if (requireQuarantine !== undefined &&
|
|
63
|
+
requireQuarantine !== '0' && requireQuarantine !== '1') {
|
|
64
|
+
throw new Error('C2 v2 source quarantine requirement must be 0 or 1')
|
|
65
|
+
}
|
|
66
|
+
const sourceQuarantine = createC2V2SourceQuarantine([
|
|
67
|
+
process.env.SBOT_C2_V2_BLOCK_SOURCE_ID,
|
|
68
|
+
process.env.SBOT_C2_V2_BLOCK_SOURCE_ID_2],
|
|
69
|
+
{ required: requireQuarantine === '1' })
|
|
70
|
+
const assertWriterLock = () => {
|
|
71
|
+
try { return fs.readFileSync(lockFile, 'utf8').trim() === String(process.pid) }
|
|
72
|
+
catch { return false }
|
|
73
|
+
}
|
|
74
|
+
const { C2DurableJournal } = require('../src/core/c2durablejournal')
|
|
75
|
+
const { validatePersistentState } = require('../src/core/c2persiststate')
|
|
76
|
+
const { validatePersistentReceipts, validatePersistentRelation } = require('../src/core/c2persistrelation')
|
|
77
|
+
const { createC2ServiceStartGate } = require('../src/core/c2servicestartgate')
|
|
78
|
+
const { C2PayloadHandles } = require('../src/core/c2payloadhandles')
|
|
79
|
+
const { createC2LocalEffectExecutor, resolveC2LocalEffectTurn } =
|
|
80
|
+
require('../src/core/c2localeffectexecutor')
|
|
81
|
+
const { createFeishuRecoverySender } = require('../src/core/c2platformrecoverysender')
|
|
82
|
+
const journal = C2DurableJournal.open(file, { validateState: validatePersistentState,
|
|
83
|
+
validateReceipts: validatePersistentReceipts,
|
|
84
|
+
validateRelation: validatePersistentRelation })
|
|
85
|
+
const beforeRecovery = journal.snapshot()
|
|
86
|
+
if (preflightOnly && (beforeRecovery.receipts.pending.length !== 0 ||
|
|
87
|
+
Object.keys(beforeRecovery.state.lanes).length !== 0 ||
|
|
88
|
+
[...beforeRecovery.effects.values()].some(status => status !== 'acked') ||
|
|
89
|
+
[...beforeRecovery.notices.values()].some(notice =>
|
|
90
|
+
notice.status === 'attempting' || notice.status === 'unknown'))) {
|
|
91
|
+
throw new Error('C2 v2 service preflight requires a settled journal; use recovery in a controlled service window')
|
|
92
|
+
}
|
|
93
|
+
const limits = { maxEventCount: 100_000,
|
|
94
|
+
maxJournalBytes: 64 * 1024 * 1024,
|
|
95
|
+
maxCompletionEvents: 1_000, maxCompletionBytes: 8 * 1024 * 1024 }
|
|
96
|
+
const { routeBackend } = require('../src/agent/privatecore')
|
|
97
|
+
const { C2DurableRuntime } = require('../src/core/c2durableruntime')
|
|
98
|
+
const { coordinateC2GlobalFault } = require('../src/core/c2globalfault')
|
|
99
|
+
const closeChecks = {
|
|
100
|
+
claude: require('../src/agent/claudecode').isClosedRunnerResult,
|
|
101
|
+
'codex-exec': require('../src/agent/codexcode').isClosedRunnerResult,
|
|
102
|
+
'codex-app': require('../src/agent/codexapp').isClosedRunnerResult,
|
|
103
|
+
}
|
|
104
|
+
const validators = { validateState: validatePersistentState,
|
|
105
|
+
validateReceipts: validatePersistentReceipts,
|
|
106
|
+
validateRelation: validatePersistentRelation }
|
|
107
|
+
const createTransport = () => {
|
|
108
|
+
const client = feishuClient(cfg.feishu.appId, cfg.feishu.appSecret)
|
|
109
|
+
const adapter = createFeishuPlatform({ appId: cfg.feishu.appId,
|
|
110
|
+
appSecret: cfg.feishu.appSecret, botOpenId: cfg.feishu.botOpenId,
|
|
111
|
+
onlyWhenMentioned: cfg.feishu.onlyWhenMentioned === true,
|
|
112
|
+
ws: cfg.feishu.ws || {}, card: cfg.feishu.card || {}, client })
|
|
113
|
+
const platform = createMultiPlatform({ adapters: { feishu: adapter },
|
|
114
|
+
resolve: chatId => chatPlatform(cfg, chatId) })
|
|
115
|
+
const sender = createFeishuRecoverySender({ cfg, platform, client })
|
|
116
|
+
for (const [chatId] of groups) sender.resolveTarget({ id: '0'.repeat(32),
|
|
117
|
+
chatId, source: 'message', sourceId: 'om_c2_service_probe' })
|
|
118
|
+
return { platform, sender, stopPlatform: () => adapter.stop({ strict: true }) }
|
|
119
|
+
}
|
|
120
|
+
const buildBinding = async authorityJournal => {
|
|
121
|
+
const { platform, sender, stopPlatform } = createTransport()
|
|
122
|
+
const gate = createC2ServiceStartGate({ enabled: true,
|
|
123
|
+
journal: authorityJournal, assertWriterLock,
|
|
124
|
+
resolveTarget: sender.resolveTarget, send: sender.send,
|
|
125
|
+
singleNoticePerGroup: true, ...limits })
|
|
126
|
+
try {
|
|
127
|
+
await gate.start()
|
|
128
|
+
const runtime = gate.runtime
|
|
129
|
+
if (!runtime || runtime.blocked || !assertWriterLock()) {
|
|
130
|
+
throw new Error('C2 v2 development service recovery did not open ingress')
|
|
131
|
+
}
|
|
132
|
+
const handles = new C2PayloadHandles({ journal: authorityJournal })
|
|
133
|
+
handles.reopenAfterRecovery()
|
|
134
|
+
if (handles.blocked || runtime.snapshot().cursor !== authorityJournal.snapshot().cursor) {
|
|
135
|
+
throw new Error('C2 v2 development service payload authority differs after recovery')
|
|
136
|
+
}
|
|
137
|
+
const store = new StateStore(path.join(stateDir, 'state.json'))
|
|
138
|
+
const closedResults = new WeakSet()
|
|
139
|
+
const startTurn = async input => {
|
|
140
|
+
const backend = routeBackend(input.provider?.runner, input.codexApp,
|
|
141
|
+
input.codexSteer, input.codexProjectDocFallback)
|
|
142
|
+
const result = await runner.runTurn(input)
|
|
143
|
+
if (closeChecks[backend]?.(result) === true) closedResults.add(result)
|
|
144
|
+
return result
|
|
145
|
+
}
|
|
146
|
+
let engine
|
|
147
|
+
const applyLocalEffect = createC2LocalEffectExecutor({
|
|
148
|
+
journal: authorityJournal, store,
|
|
149
|
+
verifyFile: path.join(stateDir, 'verify.json'), assertWriterLock,
|
|
150
|
+
resolveTurn: identity => resolveC2LocalEffectTurn(engine, identity) })
|
|
151
|
+
const { Engine } = require('../src/core/v2engine')
|
|
152
|
+
engine = new Engine(cfg, { runner, store, log: message =>
|
|
153
|
+
process.stderr.write(`C2 v2 · ${message}\n`),
|
|
154
|
+
durableV2Runtime: runtime, durableV2Payloads: handles,
|
|
155
|
+
durableV2StartTurn: startTurn, durableV2ApplyLocalEffect: applyLocalEffect,
|
|
156
|
+
durableV2VerifyRunnerClosed: ({ result }) => !!result &&
|
|
157
|
+
typeof result === 'object' && closedResults.has(result),
|
|
158
|
+
durableV2AssertWriterLock: assertWriterLock,
|
|
159
|
+
durableV2Journal: authorityJournal, durableV2NormalRoute: true })
|
|
160
|
+
return { journal: authorityJournal, platform, sender, stopPlatform, gate,
|
|
161
|
+
runtime, handles, store, engine }
|
|
162
|
+
} catch (error) { gate.stop(); throw error }
|
|
163
|
+
}
|
|
164
|
+
let active = await buildBinding(journal)
|
|
165
|
+
if (preflightOnly) {
|
|
166
|
+
active.gate.stop()
|
|
167
|
+
process.stdout.write('SintBot startup preflight passed; Feishu socket stayed disconnected\n')
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
let stopping = false
|
|
171
|
+
let rebuilds = 0
|
|
172
|
+
let finishing = false
|
|
173
|
+
const finish = (reason, code, platformStopped, drained) => {
|
|
174
|
+
if (finishing) return
|
|
175
|
+
finishing = true
|
|
176
|
+
lock.release()
|
|
177
|
+
process.stderr.write(`C2 v2 · ${reason}; platform stopped=${platformStopped}; runner drained=${drained}\n`,
|
|
178
|
+
() => process.exit(code))
|
|
179
|
+
}
|
|
180
|
+
const activeTurns = snapshot => {
|
|
181
|
+
const lanes = snapshot.state.lanes
|
|
182
|
+
const turns = []
|
|
183
|
+
for (const chatId of new Set([...Object.keys(lanes.turn || {}),
|
|
184
|
+
...Object.keys(lanes.handoff || {}),
|
|
185
|
+
...Object.keys(lanes.lifecycle || {})])) {
|
|
186
|
+
const turn = lanes.turn?.[chatId]
|
|
187
|
+
const handoff = lanes.handoff?.[chatId]
|
|
188
|
+
const lifecycle = lanes.lifecycle?.[chatId]
|
|
189
|
+
if (turn?.active != null || handoff?.phase && handoff.phase !== 'idle' ||
|
|
190
|
+
lifecycle?.busy === true || lifecycle?.controller === true) {
|
|
191
|
+
turns.push({ chatId, turnToken: turn?.active ?? handoff?.turnToken ?? null })
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return turns.sort((a, b) => a.chatId.localeCompare(b.chatId))
|
|
195
|
+
}
|
|
196
|
+
const launch = async binding => {
|
|
197
|
+
const onFatal = createC2ReceiptIngressFatalHook({
|
|
198
|
+
log: message => process.stderr.write(`C2 v2 · ${message}\n`),
|
|
199
|
+
shutdown: reason => shutdown(binding, reason, 1) })
|
|
200
|
+
const unsupported = source => () => {
|
|
201
|
+
void onFatal(new Error(`C2 v2 unsupported Feishu ${source} source`))
|
|
202
|
+
}
|
|
203
|
+
await binding.platform.start((msg, sourceAdapter) => {
|
|
204
|
+
if (binding !== active || binding.gate.blocked || !assertWriterLock()) {
|
|
205
|
+
throw new Error('C2 v2 development service ingress lost durable authority')
|
|
206
|
+
}
|
|
207
|
+
if (msg?.chatId !== serviceChatId) {
|
|
208
|
+
throw new Error('C2 v2 development service received a message outside its single group')
|
|
209
|
+
}
|
|
210
|
+
if (sourceQuarantine?.rejects(msg)) {
|
|
211
|
+
process.stderr.write('C2 v2 · quarantined historical source message before receipt\n')
|
|
212
|
+
return { quarantined: true }
|
|
213
|
+
}
|
|
214
|
+
return binding.engine.runDurableV2Message(msg, sourceAdapter)
|
|
215
|
+
}, { onFatal, onRecall: unsupported('recall'),
|
|
216
|
+
onReaction: unsupported('reaction'),
|
|
217
|
+
onCardAction: unsupported('card action'),
|
|
218
|
+
onBoardUpdated: unsupported('board update') })
|
|
219
|
+
process.stderr.write('SintBot single-group Feishu ingress connected\n')
|
|
220
|
+
}
|
|
221
|
+
const shutdown = async (binding, reason, code = 1) => {
|
|
222
|
+
if (stopping || binding !== active) return
|
|
223
|
+
stopping = true
|
|
224
|
+
binding.gate.stop()
|
|
225
|
+
binding.engine.shutdown()
|
|
226
|
+
let stopTimer
|
|
227
|
+
const platformStopped = await Promise.race([
|
|
228
|
+
Promise.resolve().then(() => binding.stopPlatform()).then(value => value === true, () => false),
|
|
229
|
+
new Promise(resolve => {
|
|
230
|
+
stopTimer = setTimeout(() => resolve(false), 2_000)
|
|
231
|
+
}),
|
|
232
|
+
])
|
|
233
|
+
clearTimeout(stopTimer)
|
|
234
|
+
const drained = await binding.engine.drainDurableV2(12_000).catch(() => false)
|
|
235
|
+
// A failed route may have already settled, leaving a closed child and
|
|
236
|
+
// active durable receipt for global-fault reconciliation. A still-live
|
|
237
|
+
// route or child cannot be replaced, even if the drain timer expired.
|
|
238
|
+
const quiescent = [...binding.engine.rts.values()].every(rt =>
|
|
239
|
+
!rt.v2Controller && !rt.v2NormalRouteActive)
|
|
240
|
+
if (code === 0 || !platformStopped || !quiescent || !assertWriterLock() ||
|
|
241
|
+
rebuilds >= 1) {
|
|
242
|
+
finish(reason, code === 0 && platformStopped && drained ? 0 : 1,
|
|
243
|
+
platformStopped, drained)
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
rebuilds++
|
|
247
|
+
try {
|
|
248
|
+
const current = binding.journal.snapshot()
|
|
249
|
+
const turns = activeTurns(current)
|
|
250
|
+
const proofs = turns.length
|
|
251
|
+
? binding.engine.durableV2ActiveFaultProofs?.(turns) || null : null
|
|
252
|
+
const result = await coordinateC2GlobalFault({
|
|
253
|
+
runtime: binding.runtime, journal: binding.journal, assertWriterLock,
|
|
254
|
+
abortTurn: turn => binding.engine.abortDurableV2FaultTurn?.(turn) === true,
|
|
255
|
+
activeTurnProofs: proofs,
|
|
256
|
+
createAuthority: () => {
|
|
257
|
+
const nextJournal = C2DurableJournal.open(file, validators)
|
|
258
|
+
const nextRuntime = new C2DurableRuntime({ journal: nextJournal,
|
|
259
|
+
resolveTarget: binding.sender.resolveTarget,
|
|
260
|
+
send: binding.sender.send, assertWriterLock,
|
|
261
|
+
singleNoticePerGroup: true, ...limits })
|
|
262
|
+
return { journal: nextJournal, runtime: nextRuntime }
|
|
263
|
+
} })
|
|
264
|
+
if (result.status !== 'recovered' || !assertWriterLock()) {
|
|
265
|
+
finish(`C2 v2 recovery sealed: ${result.reason || 'writer lock lost'}`,
|
|
266
|
+
1, platformStopped, drained)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
result.runtime.close()
|
|
270
|
+
const next = await buildBinding(result.journal)
|
|
271
|
+
active = next
|
|
272
|
+
// A fatal callback may arrive synchronously while the fresh platform
|
|
273
|
+
// starts. It must not be swallowed by the old binding's stop guard.
|
|
274
|
+
stopping = false
|
|
275
|
+
await launch(next)
|
|
276
|
+
process.stderr.write('C2 v2 process-local authority rebuilt\n')
|
|
277
|
+
} catch (error) {
|
|
278
|
+
finish(`C2 v2 recovery failed: ${error?.message || String(error)}`,
|
|
279
|
+
1, platformStopped, drained)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
process.on('SIGINT', () => { void shutdown(active, 'SIGINT', 0) })
|
|
283
|
+
process.on('SIGTERM', () => { void shutdown(active, 'SIGTERM', 0) })
|
|
284
|
+
process.on('exit', lock.release)
|
|
285
|
+
try {
|
|
286
|
+
await launch(active)
|
|
287
|
+
running = true
|
|
288
|
+
} catch (error) {
|
|
289
|
+
active.gate.stop()
|
|
290
|
+
active.engine.shutdown()
|
|
291
|
+
let timer
|
|
292
|
+
await Promise.race([
|
|
293
|
+
Promise.resolve().then(() => active.stopPlatform()).catch(() => false),
|
|
294
|
+
new Promise(resolve => { timer = setTimeout(resolve, 2_000) }),
|
|
295
|
+
])
|
|
296
|
+
clearTimeout(timer)
|
|
297
|
+
await active.engine.drainDurableV2(12_000).catch(() => false)
|
|
298
|
+
throw error
|
|
299
|
+
}
|
|
300
|
+
} finally { if (!running) lock.release() }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// The opt-in receipt authority cannot keep accepting Feishu messages after
|
|
304
|
+
// its ingress callback rejects. The adapter closes its socket first; the
|
|
305
|
+
// daemon then aborts running turns and exits nonzero under the supervisor.
|
|
306
|
+
function createC2ReceiptIngressFatalHook({ log, shutdown }) {
|
|
307
|
+
if (typeof log !== 'function' || typeof shutdown !== 'function') {
|
|
308
|
+
throw new TypeError('C2 receipt ingress fatal hook needs log and shutdown')
|
|
309
|
+
}
|
|
310
|
+
let fired = false
|
|
311
|
+
return error => {
|
|
312
|
+
if (fired) return
|
|
313
|
+
fired = true
|
|
314
|
+
log('C2 receipt ingress failed: ' + (error && error.message || String(error)))
|
|
315
|
+
return shutdown('C2 receipt ingress failed', 1)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function fail(message) {
|
|
320
|
+
process.stderr.write(`sintbot: ${message}\n`)
|
|
321
|
+
process.exitCode = 1
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const HELP = `SintBot macOS Feishu single-group release
|
|
325
|
+
Usage:
|
|
326
|
+
sintbot init [--config <absolute path>]
|
|
327
|
+
sintbot start [--config <absolute path>] [--preflight]
|
|
328
|
+
sintbot version
|
|
329
|
+
sintbot help
|
|
330
|
+
|
|
331
|
+
Supports one explicit Feishu group using Codex. Start runs in the foreground.
|
|
332
|
+
Initialization is for a new installation only; existing journals are never reset.
|
|
333
|
+
Canvas, multiple groups, console, scheduled tasks, terminal chat and other platforms are unavailable.
|
|
334
|
+
`
|
|
335
|
+
|
|
336
|
+
async function main(argv = process.argv.slice(2)) {
|
|
337
|
+
const [sub, ...rest] = argv
|
|
338
|
+
if (sub === 'version' || sub === '--version' || sub === '-v') {
|
|
339
|
+
if (rest.length) throw new Error('version does not accept arguments')
|
|
340
|
+
process.stdout.write(require('../package.json').version + '\n')
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h' ||
|
|
344
|
+
['init', 'start'].includes(sub) && rest.length === 1 && ['--help', '-h'].includes(rest[0])) {
|
|
345
|
+
process.stdout.write(HELP)
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
if (!['init', 'start'].includes(sub)) {
|
|
349
|
+
throw new Error(`${sub} is unavailable in this single-group release; use sintbot help`)
|
|
350
|
+
}
|
|
351
|
+
if (process.platform !== 'darwin') throw new Error('this release supports macOS only')
|
|
352
|
+
if (sub === 'init') {
|
|
353
|
+
const { values } = parseArgs({ args: rest, allowPositionals: false,
|
|
354
|
+
options: { config: { type: 'string' } } })
|
|
355
|
+
require('../src/c2install').initializeInstallation(values.config)
|
|
356
|
+
process.stdout.write('SintBot journal initialized; run sintbot start to connect the configured group.\n')
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
await startService(rest)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (require.main === module) main().catch(error => fail(error?.message || String(error)))
|
|
363
|
+
module.exports = { main, createC2ReceiptIngressFatalHook }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Install SintBot 1.3.0 with an AI assistant
|
|
2
|
+
|
|
3
|
+
English · [简体中文](./zh-CN/AI-ASSISTED-INSTALL.md)
|
|
4
|
+
|
|
5
|
+
Use the terminal on the Mac that will run SintBot. A cloud shell cannot install it on the user's Mac. Follow the [installation guide](./install.md); its supported route is one Feishu group, Codex and a foreground process.
|
|
6
|
+
|
|
7
|
+
1. Check macOS, Node 22+, the actual Codex executable/login and any existing SintBot process. Do not print credentials. Preserve existing installations, configuration and state.
|
|
8
|
+
2. Download and inspect the installer, run its `--dry-run`, then install the exact package into a new prefix. Continue within the user's authorization; ask only for missing decisions. The installer does not change Node, Codex, shell profiles or services.
|
|
9
|
+
3. Use the executable under that prefix and verify it reports `1.3.0`. Do not substitute a globally installed command. A successful package install is not a successful group test.
|
|
10
|
+
4. Prepare the single-group configuration in the install guide. Let the user enter secrets locally or use already authorized credential references without revealing them. Use an existing authorized Feishu app only when another process is not connected to it. If app setup or login requires the user, finish other independent preparation first.
|
|
11
|
+
5. For fresh state only, run `init`, then `start --preflight`. Refusal due to old state or unresolved receipts is a stop condition: never delete files, fabricate a new authority or alter receipts to bypass it.
|
|
12
|
+
6. Run the foreground service, send an authorized read-only group request and compare its response with the file. Stop cleanly and verify a restart. If no person is available, use an authorized test account to send the request; document the sender and do not call it independent human acceptance. A bot's own API message may be ignored and is not a substitute for an incoming user message.
|
|
13
|
+
7. Stop temporary processes before ending the task. Report what was installed, what actually ran and any remaining setup. Do not register an automatic background service for this release.
|
|
14
|
+
|
|
15
|
+
## Copyable request
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
Install SintBot 1.3.0 on this Mac using the versioned, isolated installation guide. Preserve existing services, configuration, state and Codex account data. Do not expose credentials. Verify the actual installed executable and binary core, configure one Feishu group for Codex, 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. Do not use the old setup, console or service-install flow. Stop temporary processes when finished, and distinguish package installation from successful group acceptance.
|
|
19
|
+
```
|
package/docs/install.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Install SintBot 1.3.0
|
|
2
|
+
|
|
3
|
+
English · [简体中文](./zh-CN/install.md)
|
|
4
|
+
|
|
5
|
+
This release supports macOS, Node.js 22+, one Feishu group and Codex message turns. It runs in the foreground. Multiple groups, other chat platforms or model routes, the console, automatic service setup, scheduled tasks, terminal chat and whiteboards are not supported in 1.3.0.
|
|
6
|
+
|
|
7
|
+
## 1. Prepare the Mac
|
|
8
|
+
|
|
9
|
+
Use a terminal on the Mac where the project lives. Confirm Node.js 22+ and a working, authenticated Codex installation. Keep the existing Codex installation and account data. The SintBot installer does not install or log in to Codex and does not install Node.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
node --version
|
|
13
|
+
codex --version
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
If this Mac already runs SintBot, leave it running its current package and configuration. A separate package prefix is safe to install, but **1.3.0 does not automatically migrate existing state**. Its initialization refuses existing state, old receipts or an existing journal. Do not delete these files to bypass that check. Do not connect two processes to the same Feishu application.
|
|
17
|
+
|
|
18
|
+
## 2. Install into a separate directory
|
|
19
|
+
|
|
20
|
+
Download and inspect the installer, then run the same file:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
curl -fsSL https://sintbot.com/install.sh -o /tmp/sintbot-install.sh
|
|
24
|
+
bash /tmp/sintbot-install.sh --dry-run
|
|
25
|
+
bash /tmp/sintbot-install.sh
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
It installs **sintbot@1.3.0** into `~/.local/share/sintbot/releases/1.3.0`, checks the actual executable, binary core and Feishu module, and leaves global packages, shell profiles, configuration and services untouched. It refuses an existing target directory. Use `--prefix /new/absolute/directory` for a different location. `--dry-run` makes no changes and does not fetch a package.
|
|
29
|
+
|
|
30
|
+
Keep using the exact executable, so an older global command cannot be selected accidentally:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
SINTBOT="$HOME/.local/share/sintbot/releases/1.3.0/bin/sintbot"
|
|
34
|
+
"$SINTBOT" --version
|
|
35
|
+
"$SINTBOT" help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The expected version is `1.3.0`. Installing the package does not start a bot.
|
|
39
|
+
|
|
40
|
+
## 3. Configure one Feishu group
|
|
41
|
+
|
|
42
|
+
Create a Feishu enterprise custom application on `open.feishu.cn`, enable its bot, grant message reception and reply permissions, choose long-connection event delivery, subscribe to message reception, publish the application and add its bot to your test group. Obtain that group's `oc_…` chat ID from Feishu. Use the China Feishu platform; international Lark is not supported.
|
|
43
|
+
|
|
44
|
+
Enter the app ID and app secret locally as `FEISHU_APP_ID` and `FEISHU_APP_SECRET` environment variables in the terminal that will start SintBot. Do not paste the secret into chat or commit it to your repository. Environment references are resolved at startup; both values must be present in each new terminal session.
|
|
45
|
+
|
|
46
|
+
Create the private configuration directory with `mkdir -p ~/.sbot && chmod 700 ~/.sbot`, then create `~/.sbot/config-1.3.0.json` with the following content. Replace the group ID and project path with actual values. The project must already exist. Keep exactly one group and the two explicit `false` flags.
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"defaultProvider": "codex",
|
|
51
|
+
"providers": { "codex": { "runner": "codex" } },
|
|
52
|
+
"feishu": {
|
|
53
|
+
"appId": "env:FEISHU_APP_ID",
|
|
54
|
+
"appSecret": "env:FEISHU_APP_SECRET",
|
|
55
|
+
"onlyWhenMentioned": false
|
|
56
|
+
},
|
|
57
|
+
"groups": {
|
|
58
|
+
"oc_REPLACE_WITH_YOUR_CHAT_ID": {
|
|
59
|
+
"platform": "feishu",
|
|
60
|
+
"provider": "codex",
|
|
61
|
+
"workdir": "/absolute/path/to/your/project",
|
|
62
|
+
"permissionMode": "dontAsk",
|
|
63
|
+
"codexSteer": false,
|
|
64
|
+
"codexApp": false
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Keep the configuration private (`chmod 600 ~/.sbot/config-1.3.0.json`). This example begins read-only and receives ordinary messages in this one group; use a dedicated test group. To require mentions instead, set `onlyWhenMentioned: true` and supply the actual app-specific `botOpenId`; startup rejects a missing bot ID. Do not add console, other-platform, scheduled-task or whiteboard settings from an older example. The full historical configuration schema is not the supported 1.3.0 setup.
|
|
71
|
+
|
|
72
|
+
## 4. Initialize and start
|
|
73
|
+
|
|
74
|
+
For a **new installation only**:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
"$SINTBOT" init --config "$HOME/.sbot/config-1.3.0.json"
|
|
78
|
+
"$SINTBOT" start --config "$HOME/.sbot/config-1.3.0.json" --preflight
|
|
79
|
+
"$SINTBOT" start --config "$HOME/.sbot/config-1.3.0.json"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`init` creates a new persistent receipt journal at `~/.sbot/c2-v2.log`; it never resets an existing journal or migrates old state. Run it once. `--preflight` checks local configuration and assembly without starting the Feishu connection; it does not prove authentication or message delivery. `start` holds the single-writer lock and connects the configured group. Keep that terminal open.
|
|
83
|
+
|
|
84
|
+
Send a message in the group and request the first line of the project's README. Check the returned line against the file. Press Ctrl-C and wait for the process to exit, then run the same `start` command again. Do not rerun `init` for normal restarts.
|
|
85
|
+
|
|
86
|
+
## Stop, failure and upgrades
|
|
87
|
+
|
|
88
|
+
- Stop with Ctrl-C and wait for shutdown. This version does not provide automatic login startup or a service-install command.
|
|
89
|
+
- If a request fails or startup reports unresolved receipts, preserve the journal and state. Verify what happened before retrying. Do not clear unknown effects, overwrite the journal or run the same task again blindly.
|
|
90
|
+
- The installer never replaces an earlier package. A version change still requires draining the old process, preserving configuration/state and checking receipt compatibility. A spare executable alone is not a safe rollback of state.
|
|
91
|
+
- Existing multi-group installations remain on their previous release. A reviewed migration is required before replacing them with this single-group route.
|
|
92
|
+
|
|
93
|
+
See the [AI-assisted guide](./AI-ASSISTED-INSTALL.md) if an assistant is performing the local steps.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# 用 AI 助手安装 SintBot 1.3.0
|
|
2
|
+
|
|
3
|
+
[English](../AI-ASSISTED-INSTALL.md) · 简体中文
|
|
4
|
+
|
|
5
|
+
使用实际运行 SintBot 的 Mac 本地终端;云端终端不能替用户的 Mac 安装。按[安装指南](./install.md)操作,本版支持一个飞书群、Codex 与前台运行。
|
|
6
|
+
|
|
7
|
+
1. 核对 macOS、Node 22 及以上、Codex 实际程序与登录、是否已有 SintBot 进程。不输出凭据,保留已有安装、配置和状态。
|
|
8
|
+
2. 下载并检查安装器,运行 `--dry-run`,再装到全新目录。用户已经授权的步骤继续执行,只问缺失的决定。安装器不修改 Node、Codex、终端配置或服务。
|
|
9
|
+
3. 使用独立目录中的实际入口,确认输出 `1.3.0`,不要误用全局旧命令。安装程序包成功不等于群测试成功。
|
|
10
|
+
4. 按安装指南准备单群配置。让用户在本地输入密钥,或使用已经授权的凭据引用,不显示秘密。复用授权飞书应用时,确认没有另一个进程连接该应用。应用或登录需要本人操作时,先完成其他独立准备。
|
|
11
|
+
5. 只有全新状态才能执行 `init`,然后执行 `start --preflight`。若已有旧状态或未结收据而拒绝启动,不得删文件、新造日志权威或篡改收据来绕过。
|
|
12
|
+
6. 前台运行,用已经授权的账号在群内发只读请求,把回复和文件对照,再正常停止并验证重启。没有真人协助时可用授权测试账号发送,并记录实际发送方式,不称为独立真人验收;机器人自己发的接口消息可能被忽略,不能当作用户入站消息。
|
|
13
|
+
7. 本轮临时进程用完停止。说明安装了什么、实际验证了什么、还缺什么。本版不注册自动后台服务。
|
|
14
|
+
|
|
15
|
+
## 可复制的请求
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
按独立目录安装指南,在这台 Mac 安装 SintBot 1.3.0。保留已有服务、配置、状态和 Codex 账号数据,不输出凭据。验证实际入口与二进制核心,配置一个飞书群使用 Codex,只对全新状态初始化。运行前台预检和已授权的只读群测试,对照结果,再验证正常停止与重启。不要用旧版向导、控制台或安装服务流程。完成后停止临时进程,并区分程序包安装与群验收结果。
|
|
19
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# 安装 SintBot 1.3.0
|
|
2
|
+
|
|
3
|
+
[English](../install.md) · 简体中文
|
|
4
|
+
|
|
5
|
+
本版支持 macOS、Node.js 22 及以上、一个飞书群和 Codex 消息轮次,以前台方式运行。多群、其他聊天平台或模型通道、控制台、自动后台服务、定时任务、终端聊天和画布不在 1.3.0 支持范围。
|
|
6
|
+
|
|
7
|
+
## 1. 准备 Mac
|
|
8
|
+
|
|
9
|
+
在项目所在 Mac 的本地终端操作,确认 Node.js 22 及以上、Codex 可以实际运行且已完成登录。保留已有 Codex 安装和账号数据。SintBot 安装器不安装或登录 Codex,也不安装 Node。
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
node --version
|
|
13
|
+
codex --version
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
如果这台 Mac 已经运行 SintBot,保留原来的程序包与配置。可以把新包装到独立目录,但 **1.3.0 不自动迁移已有状态**。初始化会拒绝已有状态、旧收据或日志;不要删除它们来绕过检查。不要让两个进程连接同一个飞书应用。
|
|
17
|
+
|
|
18
|
+
## 2. 安装到独立目录
|
|
19
|
+
|
|
20
|
+
下载并检查安装脚本,再执行同一份文件:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
curl -fsSL https://sintbot.com/install.sh -o /tmp/sintbot-install.sh
|
|
24
|
+
bash /tmp/sintbot-install.sh --dry-run
|
|
25
|
+
bash /tmp/sintbot-install.sh
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
脚本把精确的 **sintbot@1.3.0** 安装到 `~/.local/share/sintbot/releases/1.3.0`,验证实际入口、二进制核心和飞书模块,保留全局安装、终端配置、应用配置和服务。目标目录已存在时拒绝覆盖;可用 `--prefix /全新/绝对/目录` 指定其他位置。`--dry-run` 不修改文件,也不下载程序包。
|
|
29
|
+
|
|
30
|
+
始终使用这一个实际入口,避免误用全局旧版本:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
SINTBOT="$HOME/.local/share/sintbot/releases/1.3.0/bin/sintbot"
|
|
34
|
+
"$SINTBOT" --version
|
|
35
|
+
"$SINTBOT" help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
版本输出应为 `1.3.0`。装好程序包不会自动启动机器人。
|
|
39
|
+
|
|
40
|
+
## 3. 配置一个飞书群
|
|
41
|
+
|
|
42
|
+
在 `open.feishu.cn` 创建企业自建应用,启用机器人,授予接收与回复消息的权限,事件订阅选择长连接,订阅消息接收事件,发布应用并把机器人加入测试群。从飞书取得该群的 `oc_…` 群 ID。本版使用中国区飞书,不支持国际版 Lark。
|
|
43
|
+
|
|
44
|
+
在启动 SintBot 的本地终端中,把应用 ID 和应用密钥分别设为 `FEISHU_APP_ID`、`FEISHU_APP_SECRET` 环境变量。不要把密钥发进聊天或提交到仓库。配置中的环境变量引用在启动时解析;每次新开终端都需要保证这两个变量存在。
|
|
45
|
+
|
|
46
|
+
先用 `mkdir -p ~/.sbot && chmod 700 ~/.sbot` 创建私有配置目录,再创建 `~/.sbot/config-1.3.0.json`,内容如下。把群 ID 和项目路径换成真实值;项目目录必须已存在。只留一个群,两个显式 `false` 不要省略。
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"defaultProvider": "codex",
|
|
51
|
+
"providers": { "codex": { "runner": "codex" } },
|
|
52
|
+
"feishu": {
|
|
53
|
+
"appId": "env:FEISHU_APP_ID",
|
|
54
|
+
"appSecret": "env:FEISHU_APP_SECRET",
|
|
55
|
+
"onlyWhenMentioned": false
|
|
56
|
+
},
|
|
57
|
+
"groups": {
|
|
58
|
+
"oc_REPLACE_WITH_YOUR_CHAT_ID": {
|
|
59
|
+
"platform": "feishu",
|
|
60
|
+
"provider": "codex",
|
|
61
|
+
"workdir": "/absolute/path/to/your/project",
|
|
62
|
+
"permissionMode": "dontAsk",
|
|
63
|
+
"codexSteer": false,
|
|
64
|
+
"codexApp": false
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
限制配置文件权限(`chmod 600 ~/.sbot/config-1.3.0.json`)。示例采用只读权限,接收这一个群里的普通消息,请使用专用测试群。若只接收 @ 机器人的消息,须设 `onlyWhenMentioned: true` 并填入该应用实际的 `botOpenId`;缺少机器人 ID 会在启动前被拒绝。不要从旧示例复制控制台、其他平台、定时任务或画布配置;历史完整配置不代表 1.3.0 支持范围。
|
|
71
|
+
|
|
72
|
+
## 4. 初始化并启动
|
|
73
|
+
|
|
74
|
+
**仅全新安装**执行:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
"$SINTBOT" init --config "$HOME/.sbot/config-1.3.0.json"
|
|
78
|
+
"$SINTBOT" start --config "$HOME/.sbot/config-1.3.0.json" --preflight
|
|
79
|
+
"$SINTBOT" start --config "$HOME/.sbot/config-1.3.0.json"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`init` 在 `~/.sbot/c2-v2.log` 创建持久收据日志,只运行一次;不会重置已有日志或迁移旧状态。`--preflight` 检查本地配置与装配,不建立飞书连接,也不能代替认证或消息送达验证。`start` 持有单写锁并连接所配群,需要保持终端开启。
|
|
83
|
+
|
|
84
|
+
在群里直接发消息,要求读取项目 README 的第一行。把返回结果与文件对照。按 Ctrl-C 并等待退出,再用相同的 `start` 命令启动一次。正常重启不要重新执行 `init`。
|
|
85
|
+
|
|
86
|
+
## 停止、故障和升级
|
|
87
|
+
|
|
88
|
+
- 按 Ctrl-C 并等待退出;本版不提供登录自启或安装后台服务命令。
|
|
89
|
+
- 任务失败或启动提示未结收据时,保留日志与状态,核对实际结果后再处置。不要清空未知效果、覆盖日志或盲目重发相同任务。
|
|
90
|
+
- 安装器保留旧程序包,但切版本仍须排空旧进程、备份配置与状态、确认收据兼容。只有旧可执行文件不等于可以安全回退状态。
|
|
91
|
+
- 已有多群安装继续用原版本,替换为单群路径前必须有经过核对的迁移方案。
|
|
92
|
+
|
|
93
|
+
由助手操作时可用 [AI 辅助安装指南](./AI-ASSISTED-INSTALL.md)。
|