@mobius-os/mobius 0.3.25 → 0.3.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobius-os/mobius",
3
- "version": "0.3.25",
3
+ "version": "0.3.26",
4
4
  "type": "module",
5
5
  "description": "Mobius terminal client. Reuses Mobius frontend TypeScript types and jsonl entry shapes.",
6
6
  "bin": {
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * /resume picker — list the ~32 most recently active sessions in the current
3
- * project (aggregated across its issues, ordered by last_active DESC), pick one
4
- * to reconnect its SSE stream.
3
+ * project (aggregated across its issues), sorted local-first then by last_active
4
+ * DESC. Sessions created on the current machine are visually marked.
5
5
  */
6
6
  import React, { useEffect, useState } from 'react'
7
7
  import { Box, Text } from 'ink'
8
8
  import { Select } from './primitives.js'
9
9
  import { MobiusClient } from '../api.js'
10
+ import { tuiAimuxIdentifier } from '../aimux.js'
10
11
  import type { Project, Session } from '../types.js'
11
12
 
12
13
  function relativeTime(iso?: string): string {
@@ -24,6 +25,24 @@ function relativeTime(iso?: string): string {
24
25
  return new Date(iso).toISOString().slice(0, 10)
25
26
  }
26
27
 
28
+ /** Extract aimux_id from pc_client_metadata (object or JSON string). */
29
+ function sessionAimuxId(meta: unknown): string | null {
30
+ if (!meta) return null
31
+ if (typeof meta === 'string') {
32
+ try { meta = JSON.parse(meta) } catch { return null }
33
+ }
34
+ if (typeof meta === 'object' && meta !== null && typeof (meta as any).aimux_id === 'string') {
35
+ return (meta as any).aimux_id.trim() || null
36
+ }
37
+ return null
38
+ }
39
+
40
+ /** Optional: extract hostname hint from aimux_id (tui-<hostname> or desktop-<hostname>). */
41
+ function hostHint(aimuxId: string): string {
42
+ const m = aimuxId.match(/^(?:tui|desktop)-(.+)/)
43
+ return m ? m[1] : aimuxId
44
+ }
45
+
27
46
  export function ResumePicker({ client, project, onPick, onBack }: {
28
47
  client: MobiusClient
29
48
  project: Project
@@ -34,7 +53,7 @@ export function ResumePicker({ client, project, onPick, onBack }: {
34
53
  const [err, setErr] = useState<string | null>(null)
35
54
 
36
55
  useEffect(() => {
37
- client.listProjectSessions(project.id, 32)
56
+ client.listProjectSessions(project.id, 64)
38
57
  .then(setSessions)
39
58
  .catch(e => setErr(e?.message ?? String(e)))
40
59
  }, [client, project.id])
@@ -42,16 +61,37 @@ export function ResumePicker({ client, project, onPick, onBack }: {
42
61
  if (err) return <Box paddingX={2}><Text color="red">加载会话失败: {err}</Text></Box>
43
62
  if (sessions === null) return <Box paddingX={2}><Text color="cyan">加载历史会话…</Text></Box>
44
63
 
45
- const items = sessions.map(s => ({
46
- label: `${s.name}${s.issue_title ? ` · ${s.issue_title}` : ''}`,
47
- value: s.session_id,
48
- desc: `${relativeTime(s.last_active)} · ${s.message_count ?? 0} 条消息 · 模型 ${s.model ?? '?'}`,
49
- }))
64
+ const myId = tuiAimuxIdentifier()
65
+
66
+ // Sort: local sessions first, then remote; each group by last_active DESC.
67
+ const sorted = [...sessions].sort((a, b) => {
68
+ const aLocal = sessionAimuxId(a.pc_client_metadata) === myId ? 0 : 1
69
+ const bLocal = sessionAimuxId(b.pc_client_metadata) === myId ? 0 : 1
70
+ if (aLocal !== bLocal) return aLocal - bLocal
71
+ return (b.last_active || '').localeCompare(a.last_active || '')
72
+ })
73
+
74
+ const localCount = sorted.filter(s => sessionAimuxId(s.pc_client_metadata) === myId).length
75
+
76
+ const items = sorted.map(s => {
77
+ const aid = sessionAimuxId(s.pc_client_metadata)
78
+ const isLocal = aid === myId
79
+ const host = aid ? hostHint(aid) : null
80
+ const marker = isLocal ? '💻 ' : host ? `🌐 ${host} ` : '🌐 ? '
81
+ const label = `${marker}${s.name}${s.issue_title ? ` · ${s.issue_title}` : ''}`
82
+ const time = relativeTime(s.last_active)
83
+ const desc = `${time} · ${s.message_count ?? 0} 条消息 · ${s.model ?? '?'}`
84
+ return { label, value: s.session_id, desc }
85
+ })
86
+
87
+ const hint = localCount > 0
88
+ ? `本机 ${localCount} 个 · 远程 ${sorted.length - localCount} 个,共 ${sorted.length} 个`
89
+ : `全部 ${sorted.length} 个(无本机会话)`
50
90
 
51
91
  return (
52
92
  <Box flexDirection="column" paddingX={2} paddingY={1}>
53
93
  <Text bold color="cyan">恢复历史会话({project.name})</Text>
54
- <Text color="gray">按活跃时间排序,最近 32 个</Text>
94
+ <Text color="gray">{hint}</Text>
55
95
  <Box marginTop={1}>
56
96
  {items.length === 0
57
97
  ? <Text color="gray">(暂无历史会话)</Text>