@mobius-os/mobius 0.3.7 → 0.3.9

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/aimux.ts +37 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobius-os/mobius",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "type": "module",
5
5
  "description": "Mobius terminal client. Reuses Mobius frontend TypeScript types and jsonl entry shapes.",
6
6
  "bin": {
package/src/aimux.ts CHANGED
@@ -88,12 +88,25 @@ async function pythonForAimux(onProgress?: (p: InstallProgress) => void): Promis
88
88
  // 解压到 ~/.mobius/python-bundle/ 后用 `<python> -m aimux` 运行,彻底绕开宿主机
89
89
  // 系统 python(如被精简掉 ensurepip 的容器镜像)。aimux 全部依赖为纯 Python,
90
90
  // 故三平台可共用同一套打包产物,分别按 arch 发布到 CDN。
91
- const BUNDLE_VER = '1'
91
+ const BUNDLE_VER = '2'
92
+ const BUNDLE_AIMUX_VERSION = '0.1.21'
92
93
  const bundleDir = () => path.join(mobiusHome(), 'python-bundle')
93
94
  const bundlePython = () => WIN
94
95
  ? path.join(bundleDir(), 'python', 'python.exe')
95
96
  : path.join(bundleDir(), 'python', 'bin', 'python3')
96
97
 
98
+ /** Persistent child-process diagnostics. Never include the JWT in this file. */
99
+ export const aimuxLogPath = () => path.join(mobiusHome(), 'aimux.log')
100
+
101
+ function appendAimuxLog(write: { queue: Promise<void> }, text: string): void {
102
+ write.queue = write.queue
103
+ .then(async () => {
104
+ await fs.mkdir(mobiusHome(), { recursive: true })
105
+ await fs.appendFile(aimuxLogPath(), text, 'utf8')
106
+ })
107
+ .catch(() => {})
108
+ }
109
+
97
110
  /** 当前平台对应的内置运行时包名;mac-arm64 走 mac-x64(Rosetta 2)。 */
98
111
  export function bundleArch(): string | null {
99
112
  const { platform, arch } = process
@@ -107,8 +120,19 @@ export function bundleArch(): string | null {
107
120
  export const bundleBaseUrl = () => (process.env.MOBIUS_TUI_PYTHON_BUNDLE_URL || 'https://serve.nutshellai.cn/publish/auto/mobius-tui').replace(/\/$/, '')
108
121
  export const bundleUrl = (arch: string) => `${bundleBaseUrl()}/mobius-python-${arch}-v${BUNDLE_VER}.zip`
109
122
 
123
+ export function bundleHealthCheckCode(platform: NodeJS.Platform = process.platform): string {
124
+ const imports = platform === 'win32'
125
+ ? 'import aimux, aimux.bridge_client, win32_setctime'
126
+ : 'import aimux, aimux.bridge_client'
127
+ return `${imports}; assert aimux.__version__ == '${BUNDLE_AIMUX_VERSION}'`
128
+ }
129
+
110
130
  function bundleReady(): boolean {
111
- return existsSync(bundlePython()) && spawnSync(bundlePython(), ['-c', 'import aimux'], { stdio: 'ignore', windowsHide: true }).status === 0
131
+ return existsSync(bundlePython()) && spawnSync(
132
+ bundlePython(),
133
+ ['-c', bundleHealthCheckCode()],
134
+ { stdio: 'ignore', windowsHide: true },
135
+ ).status === 0
112
136
  }
113
137
 
114
138
  async function downloadBundle(arch: string, onProgress?: (p: InstallProgress) => void): Promise<{ ok: boolean; error?: string; zipPath?: string }> {
@@ -288,9 +312,13 @@ export class AimuxSupervisor {
288
312
  this.child = child
289
313
  this.startHeartbeat()
290
314
  let tail = '' // 缓存 aimux 最近输出, 进程异常退出时带进状态行, 便于诊断(code=1 不再是黑盒)
315
+ const logWrite = { queue: Promise.resolve() }
316
+ appendAimuxLog(logWrite, `\n===== AIMUX start ${new Date().toISOString()} =====\n`)
317
+ appendAimuxLog(logWrite, `server=${server} identifier=${identifier} platform=${process.platform} arch=${process.arch}\n`)
291
318
  const classify = (buf: Buffer) => {
292
319
  const text = buf.toString('utf8')
293
320
  tail = (tail + text).slice(-4000)
321
+ appendAimuxLog(logWrite, text)
294
322
  if (!this.bridgeConnected && /connected|registered|event stream|heartbeat|sse/i.test(text)) {
295
323
  onStatus({ state: 'starting', phase: 'heartbeat', detail: 'AIMUX 已启动,等待 bridge 心跳确认…', identifier })
296
324
  } else if (/connection (refused|reset|closed|error)|failed to connect|unauthorized|forbidden|token.*invalid/i.test(text)) {
@@ -298,15 +326,19 @@ export class AimuxSupervisor {
298
326
  }
299
327
  }
300
328
  child.stdout?.on('data', classify); child.stderr?.on('data', classify)
301
- child.on('error', e => onStatus({ state: 'failed', phase: 'retrying', detail: `AIMUX 启动失败: ${e.message}`, identifier }))
329
+ child.on('error', e => {
330
+ appendAimuxLog(logWrite, `\n[spawn error] ${e.stack || e.message}\n`)
331
+ onStatus({ state: 'failed', phase: 'retrying', detail: `AIMUX 启动失败: ${e.message} · 日志: ${aimuxLogPath()}`, identifier })
332
+ })
302
333
  child.on('exit', code => {
303
334
  if (this.child !== child) return
304
335
  this.child = null
305
336
  this.stopHeartbeat()
306
337
  if (this.stopping) { onStatus({ state: 'stopped', phase: 'idle', detail: 'AIMUX 已停止', identifier }); return }
338
+ appendAimuxLog(logWrite, `\n===== AIMUX exit code=${code} ${new Date().toISOString()} =====\n`)
307
339
  const reason = code !== 0 && tail.trim()
308
- ? `AIMUX 进程退出(code=${code}): ${tail.trim().split(/[\r\n]+/).filter(Boolean).slice(-3).join(' ⏎ ').slice(-200)}`
309
- : `AIMUX 进程退出(code=${code})`
340
+ ? `AIMUX 进程退出(code=${code}): ${tail.trim().split(/[\r\n]+/).filter(Boolean).slice(-3).join(' ⏎ ').slice(-220)} · 日志: ${aimuxLogPath()}`
341
+ : `AIMUX 进程退出(code=${code}) · 日志: ${aimuxLogPath()}`
310
342
  this.scheduleReconnect(reason)
311
343
  })
312
344
  }