sintbot 1.0.16 → 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 +99 -0
- package/CHANGELOG.json +99 -0
- package/README.md +20 -158
- package/README.zh-CN.md +20 -166
- package/THIRD-PARTY-NOTICES.md +394 -0
- package/bin/sintbot-v2.js +363 -0
- package/docs/AI-ASSISTED-INSTALL.md +11 -35
- package/docs/install.md +61 -167
- package/docs/zh-CN/AI-ASSISTED-INSTALL.md +11 -35
- package/docs/zh-CN/install.md +61 -165
- package/install.sh +84 -0
- package/package.json +73 -37
- 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 -4716
- 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/cli-secret.js +0 -36
- 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 -2758
- 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 -5526
- 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 }
|
|
@@ -1,43 +1,19 @@
|
|
|
1
|
-
# Install
|
|
1
|
+
# Install SintBot 1.3.0 with an AI assistant
|
|
2
2
|
|
|
3
|
-
[简体中文](./zh-CN/AI-ASSISTED-INSTALL.md)
|
|
3
|
+
English · [简体中文](./zh-CN/AI-ASSISTED-INSTALL.md)
|
|
4
4
|
|
|
5
|
-
|
|
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
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
4. **Run locally.** Use the target computer's interactive terminal. Let the user sign in themselves and type API keys directly at SintBot's masked terminal prompt. Never copy credentials into chat or the agent transcript, and do not use `sudo`.
|
|
15
|
-
5. **Complete the first use.** Check the version, health and service state. In the console, follow **Get started**: confirm a usable model, connect a chat platform if needed, bind one group to one project folder, then send a read-only test request. To test a model before connecting a platform, use **Playground**.
|
|
16
|
-
|
|
17
|
-
If the user needs to create or approve a chat-platform app, guide them with the relevant [platform setup guide](./platforms/). The user enters credentials directly in the local console. Ask for only the next decision needed; do not make them repeat information already provided.
|
|
18
|
-
|
|
19
|
-
## If the assistant cannot use a local terminal
|
|
20
|
-
|
|
21
|
-
Do not run a real installation on another computer or in a cloud shell. A no-change `--dry-run` preview is fine, but without an interactive terminal a real install stops when Node 22+ is missing; if Node is present, it may install only the package and report incomplete setup. Have the user run the [one-line install](./install.md#one-line-install) on the target computer, then continue verification.
|
|
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.
|
|
22
14
|
|
|
23
15
|
## Copyable request
|
|
24
16
|
|
|
25
17
|
```text
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
For a fresh install, download the official installer to a temporary file, inspect it and run its no-change preview; execute that same file after approval. For an upgrade, update only the package; do not rerun model setup or its trial. Before changes, give me one concise summary covering Node / Claude Code installs, model choice and possible trial cost, and background-service install or restart (including any group notice). Ask once for the decisions needed. After I approve, answer matching installer prompts from my choices without asking again; stop if a new change is required.
|
|
29
|
-
|
|
30
|
-
Use the target computer's interactive terminal. I will sign in and type any API key directly at the masked local prompt; never put credentials in chat. Do not use sudo or reset existing data. After installation, verify the version, health and service state, then guide me through the console's Get started steps. We can use Playground first, or connect a platform, bind one group to one project and send a read-only test. Ask only for the next required choice; stop on errors and explain the cause.
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Give an AI agent a clear task
|
|
34
|
-
|
|
35
|
-
For coding work, include the goal, acceptance criteria and constraints. For example:
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
Goal: …
|
|
39
|
-
Done means: …
|
|
40
|
-
Constraints: …
|
|
41
|
-
|
|
42
|
-
Read the project's instructions and current state first. If the request is clear, proceed; ask only when a missing fact blocks the work. Finish with a short summary of changes, checks and anything still open.
|
|
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.
|
|
43
19
|
```
|