@mobius-os/mobius 0.3.24 → 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
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Preferences are stored INSIDE the selected Issue (switching issues restores
|
|
11
11
|
* that issue's saved model/language/skill/memory choices).
|
|
12
12
|
*/
|
|
13
|
+
import { randomBytes } from 'crypto'
|
|
13
14
|
import React, { useEffect, useState } from 'react'
|
|
14
15
|
import { Box, Text } from 'ink'
|
|
15
16
|
import { Select, TextInput, type SelectItem } from './primitives.js'
|
|
@@ -121,7 +122,7 @@ export function PrepScreen({ client, onReady, onQuit }: {
|
|
|
121
122
|
// 客户端 cwd 对服务器无意义(Windows 路径如 C:\Users\... 在 Linux 上会被误解析为相对路径)。
|
|
122
123
|
// 用项目名+随机后缀生成唯一服务器端子目录,由服务器 resolveBindPath 拼到用户 work_dir 下。
|
|
123
124
|
const slug = (name || '未命名项目').replace(/[^a-zA-Z0-9一-鿿_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '').slice(0, 56) || 'project'
|
|
124
|
-
const suffix =
|
|
125
|
+
const suffix = randomBytes(3).toString('hex')
|
|
125
126
|
const safeDir = `/${slug}-${suffix}`
|
|
126
127
|
const p = await client.createProject({ name: name || '未命名项目', description, bindPath: safeDir, defaultUseWorktree: false })
|
|
127
128
|
const list = await client.listProjects(); await saveProjectsCache(list); setProjects(list)
|
|
@@ -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,
|
|
4
|
-
*
|
|
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,
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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"
|
|
94
|
+
<Text color="gray">{hint}</Text>
|
|
55
95
|
<Box marginTop={1}>
|
|
56
96
|
{items.length === 0
|
|
57
97
|
? <Text color="gray">(暂无历史会话)</Text>
|