@mobius-os/mobius 0.2.6 → 0.2.8

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.2.6",
3
+ "version": "0.2.8",
4
4
  "type": "module",
5
5
  "description": "Mobius terminal client. Reuses Mobius frontend TypeScript types and jsonl entry shapes.",
6
6
  "bin": {
@@ -35,8 +35,7 @@ interface TerminalSize {
35
35
  isTty: boolean
36
36
  }
37
37
 
38
- const VERSION = '0.2.6'
39
- const WELCOME_ROWS = 12
38
+ const VERSION = '0.2.8'
40
39
  const CHROME_ROWS = 11
41
40
 
42
41
  const SLASH_COMMANDS = [
@@ -50,6 +49,7 @@ export function ChatScreen({ client, ready, webUserId, resumeSessionId, onClear,
50
49
  const chat = useChat({ client, ready, resumeSessionId })
51
50
  const [showHelp, setShowHelp] = useState(false)
52
51
  const [scrollBack, setScrollBack] = useState(0)
52
+ const [modelLabel, setModelLabel] = useState<string | null>(null)
53
53
  const terminal = useTerminalSize()
54
54
 
55
55
  const runSlash = useCallback((raw: string) => {
@@ -80,11 +80,12 @@ export function ChatScreen({ client, ready, webUserId, resumeSessionId, onClear,
80
80
  () => fitTranscript(chat.entries, transcriptRows, terminal.columns, scrollBack),
81
81
  [chat.entries, transcriptRows, terminal.columns, scrollBack],
82
82
  )
83
- // Welcome card is for fresh / short sessions only. Once the conversation is
84
- // long enough that fitTranscript hides older entries, switch to the compact
85
- // header + full transcript — otherwise the 12-row welcome card crowds out the
86
- // recent messages and the chat area reads as blank after "已隐藏较早的…".
87
- const showWelcome = fitted.hiddenOlder === 0 && scrollBack === 0 && fitted.estimatedRows + WELCOME_ROWS <= transcriptRows
83
+ // Welcome card is for a truly fresh session only. Once there's any
84
+ // conversation (or an in-flight user message) it disappears, handing the
85
+ // compact header + bottom-anchored transcript the full height. Keeping it
86
+ // while chatting left recent messages stranded mid-screen with a blank gap
87
+ // above the composer.
88
+ const showWelcome = chat.entries.length === 0 && chat.pendingUser === null && scrollBack === 0
88
89
 
89
90
  // In-app history pager. Ink redraws only the live frame, so the terminal's
90
91
  // own scrollback holds no past turns — older entries are unreachable unless we
@@ -100,6 +101,19 @@ export function ChatScreen({ client, ready, webUserId, resumeSessionId, onClear,
100
101
  if (cur > prev && scrollBack > 0) setScrollBack(s => s + (cur - prev))
101
102
  }, [chat.entries.length, scrollBack])
102
103
 
104
+ // Show the model's friendly label (e.g. "GPT-5.6-Sol") in the header/status
105
+ // instead of its opaque key (e.g. "codex:mobiusdefaultaabb").
106
+ useEffect(() => {
107
+ const key = ready.prefs.model
108
+ if (!key) { setModelLabel(null); return }
109
+ let cancelled = false
110
+ client.modelOptions()
111
+ .then(opts => { if (!cancelled) setModelLabel(opts.find(o => o.key === key)?.label ?? null) })
112
+ .catch(() => { if (!cancelled) setModelLabel(null) })
113
+ return () => { cancelled = true }
114
+ }, [client, ready.prefs.model])
115
+ const modelDisplay = modelLabel ?? ready.prefs.model ?? 'default'
116
+
103
117
  useInput((_input, key) => {
104
118
  // The composer ignores pageUp/pageDown, so binding them here can't clash
105
119
  // with text entry, history navigation, or the slash-command popup.
@@ -118,7 +132,7 @@ export function ChatScreen({ client, ready, webUserId, resumeSessionId, onClear,
118
132
  >
119
133
  <Box flexDirection="column" flexGrow={1} overflowY="hidden">
120
134
  {showWelcome
121
- ? <WelcomeCard ready={ready} columns={terminal.columns} resumed={Boolean(resumeSessionId)} />
135
+ ? <WelcomeCard ready={ready} columns={terminal.columns} resumed={Boolean(resumeSessionId)} modelDisplay={modelDisplay} />
122
136
  : <CompactHeader ready={ready} sessionId={chat.sessionId} />}
123
137
 
124
138
  <Box flexGrow={1} flexDirection="column" justifyContent={showWelcome ? 'flex-start' : 'flex-end'} overflowY="hidden">
@@ -157,6 +171,7 @@ export function ChatScreen({ client, ready, webUserId, resumeSessionId, onClear,
157
171
  columns={terminal.columns}
158
172
  webUrl={buildWebUrl(client.server, webUserId, ready, chat.sessionId)}
159
173
  aimuxStatus={aimuxStatus}
174
+ modelDisplay={modelDisplay}
160
175
  />
161
176
  </Box>
162
177
  )
@@ -180,7 +195,7 @@ function useTerminalSize(): TerminalSize {
180
195
  return size
181
196
  }
182
197
 
183
- function WelcomeCard({ ready, columns, resumed }: { ready: ReadyState; columns: number; resumed: boolean }) {
198
+ function WelcomeCard({ ready, columns, resumed, modelDisplay }: { ready: ReadyState; columns: number; resumed: boolean; modelDisplay: string }) {
184
199
  const cwd = compactPath(process.cwd())
185
200
  const width = Math.max(38, Math.min(68, columns - 4))
186
201
  const labelWidth = 11
@@ -193,7 +208,7 @@ function WelcomeCard({ ready, columns, resumed }: { ready: ReadyState; columns:
193
208
  <Text dimColor> (v{VERSION})</Text>
194
209
  </Text>
195
210
  <Text> </Text>
196
- <MetaRow label="model:" value={ready.prefs.model ?? 'default'} hint="/help 查看命令" labelWidth={labelWidth} />
211
+ <MetaRow label="model:" value={modelDisplay} hint="/help 查看命令" labelWidth={labelWidth} />
197
212
  <MetaRow label="project:" value={ready.project.name} labelWidth={labelWidth} />
198
213
  <MetaRow label="task:" value={ready.issue.title} labelWidth={labelWidth} />
199
214
  <MetaRow label="directory:" value={cwd} labelWidth={labelWidth} />
@@ -461,14 +476,15 @@ function Composer({ onSubmit, onStop, onQuit, typing, commands }: ComposerProps)
461
476
  )
462
477
  }
463
478
 
464
- function StatusArea({ ready, sessionId, columns, webUrl, aimuxStatus }: {
479
+ function StatusArea({ ready, sessionId, columns, webUrl, aimuxStatus, modelDisplay }: {
465
480
  ready: ReadyState
466
481
  sessionId: string | null
467
482
  columns: number
468
483
  webUrl: string
469
484
  aimuxStatus?: AimuxStatus
485
+ modelDisplay: string
470
486
  }) {
471
- const model = ready.prefs.model ?? 'default'
487
+ const model = modelDisplay
472
488
  const language = ready.prefs.language === 'en' ? 'English' : '中文'
473
489
  const cwd = compactPath(process.cwd())
474
490
  const leftRaw = columns >= 100
@@ -131,11 +131,13 @@ export function PrepScreen({ client, onReady, onQuit }: {
131
131
  setStep(next)
132
132
  if (!next) finish(iss, p)
133
133
  }
134
- async function createIssue(name: string, useWt: boolean) {
134
+ async function createIssue(name: string) {
135
135
  if (!project) return
136
136
  setStatusMsg('创建任务…')
137
137
  try {
138
- const iss = await client.createIssue(project.id, { title: name || '命令行任务', description: '由 TUI 创建', use_worktree: useWt })
138
+ // git worktree is disabled by default and not offered as a choice TUI
139
+ // tasks always run in the bound working tree.
140
+ const iss = await client.createIssue(project.id, { title: name || '命令行任务', description: '由 TUI 创建', use_worktree: false })
139
141
  setIssues(await client.listIssues(project.id, 'active'))
140
142
  await pickIssue(iss)
141
143
  } catch (e: any) { setStatusMsg(`创建任务失败: ${e?.message ?? e}`) }
@@ -278,29 +280,21 @@ function ProjectPicker({ cwd, projects, statusMsg, onPick, onCreate, onQuit }: {
278
280
  function IssuePicker({ issues, onPick, onCreate }: {
279
281
  issues: Issue[]
280
282
  onPick: (i: Issue) => void
281
- onCreate: (name: string, useWt: boolean) => void
283
+ onCreate: (name: string) => void
282
284
  }) {
283
- const [mode, setMode] = useState<'list' | 'create-name' | 'create-wt'>('list')
285
+ const [mode, setMode] = useState<'list' | 'create-name'>('list')
284
286
  const [name, setName] = useState('')
285
287
 
286
288
  if (mode === 'create-name') {
289
+ // git worktree is disabled by default and the option is intentionally not
290
+ // offered — TUI-created tasks always run in the bound working tree.
287
291
  return (
288
292
  <Box flexDirection="column" paddingX={2}>
289
- <Text bold color="cyan">创建新任务 · 第 1 步:名称</Text>
293
+ <Text bold color="cyan">创建新任务</Text>
294
+ <Text color="gray">输入任务名称(不使用 git worktree)</Text>
290
295
  <TextInput value={name} onChange={setName} focused placeholder="命令行任务"
291
- onSubmit={() => setMode('create-wt')} />
292
- <Text color="gray">回车继续 · Esc 返回</Text>
293
- </Box>
294
- )
295
- }
296
- if (mode === 'create-wt') {
297
- return (
298
- <Box flexDirection="column" paddingX={2}>
299
- <Text bold color="cyan">创建新任务 · 第 2 步:是否使用 git worktree?</Text>
300
- <Select
301
- items={[{ label: '否(默认)', value: 'no' }, { label: '是', value: 'yes' }]}
302
- onSelect={v => onCreate(name, v === 'yes')} />
303
- <Text color="gray">回车确认 · Esc 返回</Text>
296
+ onSubmit={() => onCreate(name)} />
297
+ <Text color="gray">回车创建 · Esc 返回</Text>
304
298
  </Box>
305
299
  )
306
300
  }