sintbot 1.4.16 → 1.4.18

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.
@@ -1,9 +1,9 @@
1
- // ABI v35 adapter for the private binary decision core. Manifest assembly still stays in JS.
1
+ // ABI v37 adapter for the private binary decision core. Manifest assembly still stays in JS.
2
2
  const fs = require('node:fs')
3
3
  const path = require('node:path')
4
4
  const { createHash } = require('node:crypto')
5
5
 
6
- const EXPECTED_SHA256 = 'a7ad2bd9210906ee6f5d7893ed3d9632769b6e2dbec285c1102492e3ba1257b5'
6
+ const EXPECTED_SHA256 = '9705ac11c9ddcd47c9532471603e5c0c21d189ac89e353b33fcea55de91feb51'
7
7
  let bytes
8
8
  try {
9
9
  bytes = fs.readFileSync(path.join(__dirname, 'privatecore.wasm'))
@@ -26,7 +26,7 @@ try {
26
26
  } catch (cause) {
27
27
  throw new Error('SintBot binary core cannot start on this runtime', { cause })
28
28
  }
29
- if (core.abi_version() !== 35) throw new Error('SintBot binary core ABI mismatch')
29
+ if (core.abi_version() !== 37) throw new Error('SintBot binary core ABI mismatch')
30
30
  const encoder = new TextEncoder()
31
31
  const decoder = new TextDecoder('utf-8', { fatal: true })
32
32
 
@@ -771,6 +771,88 @@ function manifestPolicy(permissionMode, isCodex, allowedTools, disallowedTools)
771
771
  return value
772
772
  }
773
773
 
774
+ // ABI 36 JSON decisions (sintbot-core docs/ABI36_CONTRACTS.md). Each wrapper only
775
+ // checks the envelope shape the host relies on; the core owns the rules and wording.
776
+ const isObject = value => !!value && typeof value === 'object' && !Array.isArray(value)
777
+
778
+ function connectManifest(basic, userName, version) {
779
+ const value = coreJson('connect_manifest', { basic: !!basic, userName: String(userName || ''), version: String(version || '') }, 'build the Feishu connect manifest')
780
+ if (!isObject(value) || !isObject(value.appPreset) || !isObject(value.addons) ||
781
+ !Array.isArray(value.addons.scopes) || !Array.isArray(value.addons.events) ||
782
+ !Array.isArray(value.addons.callbacks) || typeof value.hint !== 'string') {
783
+ throw new Error('SintBot binary core returned an invalid connect manifest')
784
+ }
785
+ return value
786
+ }
787
+
788
+ function connectPlan(step, { basic = false, status = null, code = null, msg = '', observed = {} } = {}) {
789
+ const value = coreJson('connect_plan', { step, basic: !!basic, status, code, msg: String(msg || ''), observed: isObject(observed) ? observed : {} }, `decide the connect step ${step}`)
790
+ if (!isObject(value) || !['ok', 'stop', 'wait', 'warn'].includes(value.verdict) || typeof value.notice !== 'string' ||
791
+ !(value.next === null || typeof value.next === 'string') ||
792
+ !(value.retryAfterMs === null || Number.isSafeInteger(value.retryAfterMs))) {
793
+ throw new Error('SintBot binary core returned an invalid connect decision')
794
+ }
795
+ return value
796
+ }
797
+
798
+ function connectConfig(input) {
799
+ const value = coreJson('connect_config', input, 'build the Feishu connect configuration')
800
+ if (!isObject(value) || typeof value.ok !== 'boolean' ||
801
+ (value.ok && (!isObject(value.config) || typeof value.readme !== 'string')) ||
802
+ (!value.ok && typeof value.error !== 'string')) {
803
+ throw new Error('SintBot binary core returned an invalid connect configuration')
804
+ }
805
+ return value
806
+ }
807
+
808
+ function qrMatrix(text, ec = 'M') {
809
+ const value = coreJson('qr_matrix', { text: String(text), ec }, 'encode a QR code')
810
+ if (!isObject(value) || !Number.isInteger(value.size) || !Array.isArray(value.rows) ||
811
+ value.rows.length !== value.size || value.rows.some(row => typeof row !== 'string' || row.length !== value.size)) {
812
+ throw new Error('SintBot binary core returned an invalid QR matrix')
813
+ }
814
+ return value
815
+ }
816
+
817
+ function usageParse(provider, source, status, body, now = Date.now()) {
818
+ const value = coreJson('usage_parse', { provider, source, status, body, now }, `parse ${provider} ${source} usage`)
819
+ if (!isObject(value) || typeof value.ok !== 'boolean' ||
820
+ (value.ok && !Array.isArray(value.windows)) || (!value.ok && typeof value.error !== 'string')) {
821
+ throw new Error('SintBot binary core returned an invalid usage result')
822
+ }
823
+ return value
824
+ }
825
+
826
+ // ABI 37 (docs/ABI37_CONTRACTS.md in the core): idle compaction rules. The host
827
+ // only spawns, stops and logs; start/skip and settle outcomes are decided here.
828
+ function idleCompactPlan(input) {
829
+ const value = coreJson('idle_compact_plan', input, 'plan idle compaction')
830
+ if (!isObject(value) || typeof value.start !== 'boolean' || !Number.isFinite(value.at) ||
831
+ typeof value.reason !== 'string' || typeof value.keep !== 'string' || !Number.isFinite(value.maxMs)) {
832
+ throw new Error('SintBot binary core returned an invalid idle compaction plan')
833
+ }
834
+ return value
835
+ }
836
+
837
+ function idleCompactSettle(input) {
838
+ const value = coreJson('idle_compact_settle', input, 'settle idle compaction')
839
+ if (!isObject(value) || !['compacted', 'stopped', 'failed'].includes(value.status) ||
840
+ !Number.isFinite(value.fails) || typeof value.notice !== 'string' ||
841
+ (value.ctx !== null && !Number.isFinite(value.ctx)) || (value.restUntil !== null && !Number.isFinite(value.restUntil))) {
842
+ throw new Error('SintBot binary core returned an invalid idle compaction result')
843
+ }
844
+ return value
845
+ }
846
+
847
+ function envConflicts(input) {
848
+ const value = coreJson('env_conflicts', input, 'check environment conflicts')
849
+ if (!isObject(value) || !Array.isArray(value.warnings) ||
850
+ value.warnings.some(w => !isObject(w) || typeof w.key !== 'string' || typeof w.where !== 'string' || typeof w.reason !== 'string')) {
851
+ throw new Error('SintBot binary core returned an invalid environment check')
852
+ }
853
+ return value
854
+ }
855
+
774
856
  // A trap or exhausted stack can leave the instance's own stack pointer wrong, and
775
857
  // C1 calls hold no state, so the next call simply gets a fresh instance.
776
858
  function guarded(fn) {
@@ -798,7 +880,7 @@ function followupCall(name, value) {
798
880
  function followupEnabled(group, runner) { return followupCall('followup_policy', { group, runner }) }
799
881
  function claudeFollowupStep(state, event) { return followupCall('claude_followup_step', { state, event }) }
800
882
 
801
- const entries = { followupEnabled, claudeFollowupStep, classifyError, routeBackend, sandboxTier, appErrorOverride, appNormalizeItem, appStreamTail, appNoticeAction, appDeltaNext, appLandedAction, appCompletionAction, appCompletionNoticeAction, manifestFeatures, manifestHeader, manifestContext, manifestSkills, manifestStyleBoard, manifestImagenAsset, manifestRuntime, manifestPolicy, systemAppendix, codexEffortRejection, wecomGatePrompt, imagenPrompt, turnPrompt, compactEvent, engineQueueStep, engineSlotsStep, engineTurnStep, engineSessionStep, engineHandoffStep, engineIntakeStep, engineLifecycleStep, engineTerminalStep, engineSteerStep, engineRecoveryStep, engineFailoverStep, parseResetFields, chooseResetEpoch, codexBuildPrompt, codexBuildArgs, appUsageFromTotal, settleUsage, appBuildRequest, claudeBaseSkillAllows, claudeBuildArgs, claudePluginAllowRules, claudeNormalizeEvent, codexNormalizeEvent, codexParseError }
883
+ const entries = { followupEnabled, claudeFollowupStep, classifyError, routeBackend, sandboxTier, appErrorOverride, appNormalizeItem, appStreamTail, appNoticeAction, appDeltaNext, appLandedAction, appCompletionAction, appCompletionNoticeAction, manifestFeatures, manifestHeader, manifestContext, manifestSkills, manifestStyleBoard, manifestImagenAsset, manifestRuntime, manifestPolicy, systemAppendix, codexEffortRejection, wecomGatePrompt, imagenPrompt, turnPrompt, compactEvent, engineQueueStep, engineSlotsStep, engineTurnStep, engineSessionStep, engineHandoffStep, engineIntakeStep, engineLifecycleStep, engineTerminalStep, engineSteerStep, engineRecoveryStep, engineFailoverStep, parseResetFields, chooseResetEpoch, codexBuildPrompt, codexBuildArgs, appUsageFromTotal, settleUsage, appBuildRequest, claudeBaseSkillAllows, claudeBuildArgs, claudePluginAllowRules, claudeNormalizeEvent, codexNormalizeEvent, codexParseError, connectManifest, connectPlan, connectConfig, qrMatrix, usageParse, envConflicts, idleCompactPlan, idleCompactSettle }
802
884
  for (const name of Object.keys(entries)) entries[name] = guarded(entries[name])
803
885
 
804
886
  module.exports = { ...entries, engineStep, resetBinaryCore, HUMAN_BY_KIND: HUMAN }
Binary file
@@ -22,6 +22,14 @@ function runTurn(opts) {
22
22
  return claudeAgent.runTurn(opts)
23
23
  }
24
24
 
25
+ // 空闲整理只有 Claude 路线有(无头 /compact);GPT ⇒ null = 不支持,引擎只在轮内压缩。
26
+ function compactSession(opts) {
27
+ const route = routeBackend(opts && opts.provider && opts.provider.runner, opts && opts.codexApp, opts && opts.codexSteer, opts && opts.codexProjectDocFallback)
28
+ if (route !== 'claude') return null
29
+ const compact = Number(opts.autocompactTokens ?? opts.provider?.autocompactTokens) || 0
30
+ return claudeAgent.compactSession({ ...opts, autocompactTokens: compact > 0 ? Math.min(1_000_000, Math.max(100_000, Math.floor(compact))) : 0 })
31
+ }
32
+
25
33
  const isClosedRunnerResult = value => [claudeAgent, codexAgent, codexApp].some(agent => agent.isClosedRunnerResult(value))
26
34
  const isClosedRunnerError = value => [claudeAgent, codexAgent, codexApp].some(agent => agent.isClosedRunnerError(value))
27
- module.exports = { runTurn, probeVersion: claudeAgent.probeVersion, isClosedRunnerResult, isClosedRunnerError }
35
+ module.exports = { runTurn, compactSession, probeVersion: claudeAgent.probeVersion, isClosedRunnerResult, isClosedRunnerError }
@@ -200,6 +200,13 @@ function createC2ConfigControl({ installation, getBindings, getHeld = () => new
200
200
  if (!prepared || prepared.raw !== JSON.stringify(raw)) {
201
201
  throw new Error('Configuration was not validated against the current service')
202
202
  }
203
+ const bindings = getBindings()
204
+ // Stop every affected compactor before mutating any session or file.
205
+ let stopping = false
206
+ for (const chatId of prepared.resets) {
207
+ if (bindings.get(chatId)?.engine?.stopIdleCompact?.(chatId, '会话已重置')) stopping = true
208
+ }
209
+ if (stopping) throw new Error('正在停止会话整理,请稍后再保存设置')
203
210
  for (const chatId of prepared.resets) store.resetGroupSession(chatId)
204
211
  if (prepared.added.length) {
205
212
  for (const route of prepared.added) {
package/src/c2console.js CHANGED
@@ -110,6 +110,9 @@ function createC2ConsoleFacade({ getBindings, getCapacity, getConfig, getHeld =
110
110
  try { assertJournalScope(binding.journal, chatId, { requireSettled: true }) } catch (error) {
111
111
  throw new Error(busy, { cause: error })
112
112
  }
113
+ if (binding.engine.stopIdleCompact?.(chatId, '会话已重置')) {
114
+ throw new Error('正在停止会话整理,请稍后再开新会话')
115
+ }
113
116
  binding.engine.store.resetSessionAndStats(chatId)
114
117
  return true
115
118
  }
package/src/c2install.js CHANGED
@@ -323,6 +323,6 @@ function registerAddedGroups(installation) {
323
323
  return addedRoutes.map(route => route.chatId)
324
324
  }
325
325
 
326
- module.exports = { registerAddedGroups, loadInstallation, loadInstallationFromRaw, initializeInstallation, assertPrivateDirectory,
326
+ module.exports = { registerAddedGroups, loadInstallation, loadInstallationFromRaw, initializeInstallation, installationStateDir, assertPrivateDirectory,
327
327
  assertJournalScope, assertInstallationJournals, assertRunnerExecutable, SUPPORTED_RUNNERS, MANIFEST,
328
328
  VALIDATORS }
@@ -164,7 +164,7 @@ function createC2MultiGroupService({ routes, assertWriterLock,
164
164
  binding.engine.shutdown()
165
165
  await bounded(() => binding.engine.drainDurableV2(drainTimeoutMs), drainTimeoutMs + 100)
166
166
  const quiescent = [...binding.engine.rts.values()].every(rt =>
167
- !rt.v2Controller && !rt.v2NormalRouteActive)
167
+ !rt.v2Controller && !rt.v2NormalRouteActive && !rt.v2Compacting)
168
168
  if (!quiescent || !writerHeld() || !route) throw new Error('C2 group fault is not quiescent')
169
169
  const turns = activeTurns(binding.journal.snapshot())
170
170
  const proofs = turns.length ? binding.engine.durableV2ActiveFaultProofs?.(turns) || null : null