@mobius-os/mobius 0.3.45 → 0.3.47
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 +1 -1
- package/src/api.ts +8 -2
- package/src/components/Chat.tsx +47 -12
- package/src/hooks/useChat.ts +44 -2
- package/src/lib/entry-view.ts +8 -1
- package/src/lib/screen-text.ts +6 -0
- package/src/sse.ts +5 -0
- package/src/types.ts +13 -0
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
AnyEntry,
|
|
10
10
|
AuthConfig,
|
|
11
11
|
HistoryGroup,
|
|
12
|
+
HistoryPendingOpener,
|
|
12
13
|
Issue,
|
|
13
14
|
LoginResponse,
|
|
14
15
|
Memory,
|
|
@@ -163,11 +164,16 @@ export class MobiusClient {
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
// ── agent-history (协议 ①②: 组元数据 + 整组条目) ──────────────────────────
|
|
166
|
-
/** ① 全部组元数据,
|
|
167
|
-
async listHistoryGroups(sessionId: string): Promise<{ session_version: number; groups: HistoryGroup[] }> {
|
|
167
|
+
/** ① 全部组元数据, 一次给全; 顺带返回挂起中的开轮卡 (pending). */
|
|
168
|
+
async listHistoryGroups(sessionId: string): Promise<{ session_version: number; groups: HistoryGroup[]; pending: HistoryPendingOpener[] }> {
|
|
168
169
|
return this.request(`/api/sessions/${encodeURIComponent(sessionId)}/groups`)
|
|
169
170
|
}
|
|
170
171
|
|
|
172
|
+
/** 打断当前 turn 并出队下一条排队指令 (不追加新 prompt). */
|
|
173
|
+
async pauseToDequeue(sessionId: string): Promise<{ ok: boolean }> {
|
|
174
|
+
return this.request(`/api/sessions/${encodeURIComponent(sessionId)}/pause-to-dequeue`, { method: 'POST', body: '{}' })
|
|
175
|
+
}
|
|
176
|
+
|
|
171
177
|
/** ② 某组全部条目 (全量, 无分页, 条目不可变). */
|
|
172
178
|
async listHistoryGroupEntries(sessionId: string, groupId: string): Promise<{ group_id: string; version: number; entries: AnyEntry[] }> {
|
|
173
179
|
return this.request(`/api/sessions/${encodeURIComponent(sessionId)}/groups/${encodeURIComponent(groupId)}/entries`)
|
package/src/components/Chat.tsx
CHANGED
|
@@ -161,15 +161,32 @@ export function ChatScreen({ client, ready, webUserId, resumeSessionId, onClear,
|
|
|
161
161
|
// (type:user / response_item.message[user] / event_msg.user_message) 合并成 1 条,
|
|
162
162
|
// 避免在累积视图里把同一条提问显示多次.
|
|
163
163
|
const dedupedEntries = useMemo(() => dedupeUserEntries(chat.entries), [chat.entries])
|
|
164
|
-
const pendingEntry = useMemo<AnyEntry | null>(() =>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
)
|
|
164
|
+
const pendingEntry = useMemo<AnyEntry | null>(() => {
|
|
165
|
+
// 忙时提交的指令已由排队行统一呈现, 乐观占位退役 (避免 "You:" 与 "排队" 双重显示).
|
|
166
|
+
if (chat.pending.length > 0) return null
|
|
167
|
+
return chat.pendingUser === null ? null : ({
|
|
168
|
+
type: 'user',
|
|
169
|
+
__id: '__pending-user__',
|
|
170
|
+
message: { role: 'user', content: chat.pendingUser },
|
|
171
|
+
})
|
|
172
|
+
}, [chat.pendingUser, chat.pending.length])
|
|
173
|
+
// 排队行 (合成条目): 显示最后一条挂起指令 + 总数, 紧跟对话末尾.
|
|
174
|
+
const pendingQueueEntry = useMemo<AnyEntry | null>(() => {
|
|
175
|
+
if (chat.pending.length === 0) return null
|
|
176
|
+
const last = chat.pending[chat.pending.length - 1]
|
|
177
|
+
return {
|
|
178
|
+
type: '__pending_queue__',
|
|
179
|
+
__id: '__pending-queue__',
|
|
180
|
+
text: last?.user_summary ?? '',
|
|
181
|
+
count: chat.pending.length,
|
|
182
|
+
}
|
|
183
|
+
}, [chat.pending])
|
|
184
|
+
const transcriptEntries = useMemo(() => {
|
|
185
|
+
const parts = [...dedupedEntries]
|
|
186
|
+
if (pendingEntry) parts.push(pendingEntry)
|
|
187
|
+
if (pendingQueueEntry) parts.push(pendingQueueEntry)
|
|
188
|
+
return parts
|
|
189
|
+
}, [dedupedEntries, pendingEntry, pendingQueueEntry])
|
|
173
190
|
|
|
174
191
|
// Markdown parsing and wrapping are paid once per entry/terminal width. Keep
|
|
175
192
|
// the two most recent widths so resize-back does not immediately reparse the
|
|
@@ -372,6 +389,12 @@ export function ChatScreen({ client, ready, webUserId, resumeSessionId, onClear,
|
|
|
372
389
|
? <WelcomeCard ready={ready} columns={terminal.columns} resumed={Boolean(resumeSessionId)} modelDisplay={modelDisplay} />
|
|
373
390
|
: <CompactHeader ready={ready} sessionId={chat.sessionId} columns={terminal.columns} />}
|
|
374
391
|
|
|
392
|
+
{chat.switchedAway ? (
|
|
393
|
+
<Box flexShrink={0} borderStyle="round" borderColor="yellow" paddingX={1}>
|
|
394
|
+
<Text color="yellow" bold>注意:智能体已经离开此设备前往新设备({chat.switchedAway})</Text>
|
|
395
|
+
</Box>
|
|
396
|
+
) : null}
|
|
397
|
+
|
|
375
398
|
{!showWelcome
|
|
376
399
|
? <Box width="100%" flexShrink={0}><Text dimColor wrap="truncate-end"> {navigationPosition}{viewport.hasNewer ? <Text color="yellowBright">↓ 有新内容</Text> : null}{navigationDetail}</Text></Box>
|
|
377
400
|
: null}
|
|
@@ -414,6 +437,8 @@ export function ChatScreen({ client, ready, webUserId, resumeSessionId, onClear,
|
|
|
414
437
|
commands={SLASH_COMMANDS}
|
|
415
438
|
onHeightChange={setComposerRows}
|
|
416
439
|
inputActiveRef={chatInputActiveRef}
|
|
440
|
+
onPauseToDequeue={chat.pauseToDequeue}
|
|
441
|
+
hasPending={chat.pending.length > 0}
|
|
417
442
|
/>
|
|
418
443
|
<StatusArea
|
|
419
444
|
ready={ready}
|
|
@@ -499,7 +524,8 @@ function ScreenText({ row, text }: { row: ScreenRow; text: string }) {
|
|
|
499
524
|
: tone === 'edit_header' || tone === 'reasoning' ? 'magenta'
|
|
500
525
|
: tone === 'edit_new' ? 'green'
|
|
501
526
|
: tone === 'system' ? 'yellow'
|
|
502
|
-
:
|
|
527
|
+
: tone === 'pending' ? 'yellow'
|
|
528
|
+
: undefined
|
|
503
529
|
const dimColor = tone === 'tool_result' || tone === 'tool_error' || tone === 'reasoning' || tone === 'system'
|
|
504
530
|
return <Text wrap="truncate-end" bold={tone === 'user'} dimColor={dimColor} color={color}>{text}</Text>
|
|
505
531
|
}
|
|
@@ -614,9 +640,12 @@ interface ComposerProps {
|
|
|
614
640
|
commands: { cmd: string; desc: string }[]
|
|
615
641
|
onHeightChange?: (rows: number) => void
|
|
616
642
|
inputActiveRef?: React.RefObject<boolean>
|
|
643
|
+
// 排队插队: 有挂起指令时空输入回车触发; hasPending 控制提示文案与触发开关.
|
|
644
|
+
onPauseToDequeue?: () => void
|
|
645
|
+
hasPending?: boolean
|
|
617
646
|
}
|
|
618
647
|
|
|
619
|
-
export function Composer({ onSubmit, onStop, onQuit, typing, commands, onHeightChange, inputActiveRef }: ComposerProps) {
|
|
648
|
+
export function Composer({ onSubmit, onStop, onQuit, typing, commands, onHeightChange, inputActiveRef, onPauseToDequeue, hasPending }: ComposerProps) {
|
|
620
649
|
const [value, setValue] = useState('')
|
|
621
650
|
const [cursor, setCursor] = useState(0)
|
|
622
651
|
const [popupIdx, setPopupIdx] = useState(0)
|
|
@@ -837,6 +866,9 @@ export function Composer({ onSubmit, onStop, onQuit, typing, commands, onHeightC
|
|
|
837
866
|
onSubmit(submitted)
|
|
838
867
|
edit('', 0)
|
|
839
868
|
setHistIdx(null)
|
|
869
|
+
} else if (hasPending) {
|
|
870
|
+
// 空输入回车 = 插队: 打断当前 turn 并出队下一条排队指令.
|
|
871
|
+
void onPauseToDequeue?.()
|
|
840
872
|
}
|
|
841
873
|
return
|
|
842
874
|
}
|
|
@@ -969,7 +1001,10 @@ export function Composer({ onSubmit, onStop, onQuit, typing, commands, onHeightC
|
|
|
969
1001
|
<Box justifyContent="space-between">
|
|
970
1002
|
{confirmQuit
|
|
971
1003
|
? <Text color="yellowBright" bold>请再次按下Ctrl+C退出</Text>
|
|
972
|
-
: <Text dimColor>
|
|
1004
|
+
: <Text dimColor>
|
|
1005
|
+
{hasPending ? <Text color="yellowBright">空回车插队 · </Text> : null}
|
|
1006
|
+
{(stdout.columns ?? 80) >= 72 ? 'Enter 发送 · Shift+Enter / Alt+Enter / Ctrl+J 换行' : 'Enter 发送 · Alt+Enter / Ctrl+J 换行'}
|
|
1007
|
+
</Text>}
|
|
973
1008
|
<Text dimColor>{wrapped.length > maxRows ? `${visualCursor + 1}/${wrapped.length} 行` : `${wrapped.length} 行`}</Text>
|
|
974
1009
|
</Box>
|
|
975
1010
|
</Box>
|
package/src/hooks/useChat.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { SseConnection } from '../sse.js'
|
|
|
19
19
|
import { updateIssuePreference } from '../config.js'
|
|
20
20
|
import { tuiAimuxIdentifier, probeAimuxBridgeConnection } from '../aimux.js'
|
|
21
21
|
import { viewsForEntry } from '../lib/entry-view.js'
|
|
22
|
-
import type { AnyEntry } from '../types.js'
|
|
22
|
+
import type { AnyEntry, HistoryPendingOpener } from '../types.js'
|
|
23
23
|
import type { ReadyState } from '../components/PrepScreen.js'
|
|
24
24
|
|
|
25
25
|
export interface ChatApi {
|
|
@@ -31,12 +31,16 @@ export interface ChatApi {
|
|
|
31
31
|
export interface ChatController {
|
|
32
32
|
entries: AnyEntry[]
|
|
33
33
|
pendingUser: string | null
|
|
34
|
+
pending: HistoryPendingOpener[]
|
|
34
35
|
typing: boolean
|
|
35
36
|
sending: boolean
|
|
36
37
|
error: string | null
|
|
37
38
|
sessionId: string | null
|
|
39
|
+
/** 智能体已离开本 TUI 设备、前往的新设备 ID; null = 未离开 (仍绑定本设备或非本设备会话). */
|
|
40
|
+
switchedAway: string | null
|
|
38
41
|
send: (text: string) => Promise<void>
|
|
39
42
|
stop: () => Promise<void>
|
|
43
|
+
pauseToDequeue: () => Promise<void>
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
let ID = 0
|
|
@@ -110,9 +114,11 @@ export function useChat({ client, ready, resumeSessionId }: ChatApi): ChatContro
|
|
|
110
114
|
const [sessionId, setSessionId] = useState<string | null>(resumeSessionId ?? null)
|
|
111
115
|
const [entries, setEntries] = useState<AnyEntry[]>([])
|
|
112
116
|
const [pendingUser, setPendingUser] = useState<string | null>(null)
|
|
117
|
+
const [pending, setPending] = useState<HistoryPendingOpener[]>([])
|
|
113
118
|
const [typing, setTyping] = useState(false)
|
|
114
119
|
const [sending, setSending] = useState(false)
|
|
115
120
|
const [error, setError] = useState<string | null>(null)
|
|
121
|
+
const [switchedAway, setSwitchedAway] = useState<string | null>(null)
|
|
116
122
|
const sseRef = useRef<SseConnection | null>(null)
|
|
117
123
|
// agent-history mini group store (协议 ①②③ 的 TUI 侧消费形态).
|
|
118
124
|
const groupSlotsRef = useRef<Map<string, GroupSlot>>(new Map())
|
|
@@ -185,6 +191,8 @@ export function useChat({ client, ready, resumeSessionId }: ChatApi): ChatContro
|
|
|
185
191
|
try {
|
|
186
192
|
const data = await client.listHistoryGroups(sid)
|
|
187
193
|
const groups: any[] = Array.isArray(data?.groups) ? data.groups : []
|
|
194
|
+
// 挂起中的开轮卡 (排队指令): /groups 是权威快照, 覆盖本地增量.
|
|
195
|
+
setPending(Array.isArray(data?.pending) ? data.pending : [])
|
|
188
196
|
const local = groupSlotsRef.current
|
|
189
197
|
const targets = local.size === 0 ? groups.slice(-BOOTSTRAP_GROUP_COUNT) : groups
|
|
190
198
|
let changed = false
|
|
@@ -240,6 +248,9 @@ export function useChat({ client, ready, resumeSessionId }: ChatApi): ChatContro
|
|
|
240
248
|
version: Number(group.version) || 1,
|
|
241
249
|
entries: [],
|
|
242
250
|
})
|
|
251
|
+
// 新组开轮 = 后端已把挂起的 pending_round_openers 一次性出队 (flushPendingOpenersToSink).
|
|
252
|
+
// 排队行随之清空; 随后 entries 事件会把这一整组内容补齐.
|
|
253
|
+
setPending([])
|
|
243
254
|
},
|
|
244
255
|
onEntries: ({ group_id, group_id_version, entries }) => {
|
|
245
256
|
if (process.env.MOBIUS_TUI_DEBUG) console.error('[onEntries]', group_id, group_id_version, entries.length)
|
|
@@ -269,6 +280,16 @@ export function useChat({ client, ready, resumeSessionId }: ChatApi): ChatContro
|
|
|
269
280
|
setPendingUser(null)
|
|
270
281
|
}
|
|
271
282
|
},
|
|
283
|
+
onPendingOpener: (opener) => {
|
|
284
|
+
// 忙时提交的新指令被挂起: 追加到排队行 (uuid 去重), 乐观占位随之退役.
|
|
285
|
+
if (!opener || typeof opener !== 'object') return
|
|
286
|
+
const id = String(opener.id ?? '')
|
|
287
|
+
setPending(prev => {
|
|
288
|
+
if (id && prev.some(p => p.id === id)) return prev
|
|
289
|
+
return [...prev, { id, opener_ts: opener.opener_ts ?? null, user_summary: opener.user_summary ?? '' }]
|
|
290
|
+
})
|
|
291
|
+
setPendingUser(null)
|
|
292
|
+
},
|
|
272
293
|
onSubscribed: () => {
|
|
273
294
|
reconnectAttemptRef.current = 0
|
|
274
295
|
// 订阅即对账 (首开 = bootstrap 拉末尾几组; 重连 = stateless 补差额).
|
|
@@ -389,6 +410,14 @@ export function useChat({ client, ready, resumeSessionId }: ChatApi): ChatContro
|
|
|
389
410
|
if (stopped || epoch !== statusEpochRef.current) return
|
|
390
411
|
aliveRef.current = !!status.alive
|
|
391
412
|
|
|
413
|
+
// 设备切换检测: 本会话最初绑定本 TUI 设备 (initial_aimux_id == myId) 但当前已指向
|
|
414
|
+
// 别处 (aimux_id != myId) → 智能体"已离开本设备前往新设备". 用于渲染显眼提示 (仅 TUI 端).
|
|
415
|
+
const myId = tuiAimuxIdentifier()
|
|
416
|
+
const left = status.initial_aimux_id === myId && status.aimux_id && status.aimux_id !== myId
|
|
417
|
+
? status.aimux_id
|
|
418
|
+
: null
|
|
419
|
+
setSwitchedAway(left)
|
|
420
|
+
|
|
392
421
|
if (status.alive && status.working) {
|
|
393
422
|
workingHintUntilRef.current = 0
|
|
394
423
|
updateTyping(true)
|
|
@@ -521,5 +550,18 @@ export function useChat({ client, ready, resumeSessionId }: ChatApi): ChatContro
|
|
|
521
550
|
pollNowRef.current?.()
|
|
522
551
|
}, [sessionId, client, updateTyping])
|
|
523
552
|
|
|
524
|
-
|
|
553
|
+
// 打断当前 turn 并出队下一条排队指令 (空输入回车 / 插队). 不追加新 prompt,
|
|
554
|
+
// 后端对 claude-code/codex 发一次 C-c, deepseek harness 是空实现. 排队行会在
|
|
555
|
+
// 新组开轮 (group_created) 时被清空, 这里只需触发并刷新状态轮询.
|
|
556
|
+
const pauseToDequeue = useCallback(async () => {
|
|
557
|
+
if (!sessionId) return
|
|
558
|
+
statusEpochRef.current += 1
|
|
559
|
+
try { await client.pauseToDequeue(sessionId) } catch (e: any) {
|
|
560
|
+
const msg = e instanceof ApiError ? e.message : `插队失败: ${e?.message ?? e}`
|
|
561
|
+
setError(msg)
|
|
562
|
+
}
|
|
563
|
+
pollNowRef.current?.()
|
|
564
|
+
}, [sessionId, client])
|
|
565
|
+
|
|
566
|
+
return { entries, pendingUser, pending, typing, sending, error, sessionId, switchedAway, send, stop, pauseToDequeue }
|
|
525
567
|
}
|
package/src/lib/entry-view.ts
CHANGED
|
@@ -31,6 +31,7 @@ export type EntryView =
|
|
|
31
31
|
| { kind: 'write_file'; filePath: string; content: string }
|
|
32
32
|
| { kind: 'system'; text: string }
|
|
33
33
|
| { kind: 'error'; text: string }
|
|
34
|
+
| { kind: 'pending'; text: string; count: number }
|
|
34
35
|
|
|
35
36
|
export interface ToolResultView {
|
|
36
37
|
text: string
|
|
@@ -113,7 +114,7 @@ function extractLocalCommandParts(entry: AnyEntry): LocalCommandPart[] {
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
/**
|
|
116
|
-
* 整卡隐藏的噪声: 对齐 web entry-classify.ts isHiddenJsonlNoiseEntry 的
|
|
117
|
+
* 整卡隐藏的噪声: 对齐 web entry-classify.ts isHiddenJsonlNoiseEntry 的 8 类
|
|
117
118
|
* - token_count : codex 每轮 token 用量统计 (event_msg)
|
|
118
119
|
* - environment_context : codex 每轮注入的 <environment_context> 纯系统 user 消息
|
|
119
120
|
* - session_meta : codex 会话首条元数据
|
|
@@ -121,10 +122,12 @@ function extractLocalCommandParts(entry: AnyEntry): LocalCommandPart[] {
|
|
|
121
122
|
* - turn_duration : Claude Code 每轮结束注入的 system 耗时统计
|
|
122
123
|
* - skill_listing : Claude Code 注入的可用 Skill 清单
|
|
123
124
|
* - agent_listing_delta : Claude Code 注入的可用 subagent 清单
|
|
125
|
+
* - mcp_tool_call_end : MCP 工具调用完成生命周期标记
|
|
124
126
|
* 注: context_compacted 不在此列 (对齐 web — 它保留为可见事件, TUI 显示成 system 行).
|
|
125
127
|
*/
|
|
126
128
|
export function isHiddenNoise(entry: AnyEntry): boolean {
|
|
127
129
|
if (entry?.type === 'event_msg' && entry?.payload?.type === 'token_count') return true
|
|
130
|
+
if (entry?.type === 'event_msg' && entry?.payload?.type === 'mcp_tool_call_end') return true
|
|
128
131
|
if (entry?.type === 'session_meta') return true
|
|
129
132
|
if (entry?.type === 'turn_context') return true
|
|
130
133
|
if (entry?.type === 'system' && entry?.subtype === 'turn_duration') return true
|
|
@@ -515,6 +518,10 @@ function parseCustomToolCall(raw: any): { name: string; input: Record<string, an
|
|
|
515
518
|
export function viewsForBlock(block: Block): EntryView[] {
|
|
516
519
|
const entry = block.entry
|
|
517
520
|
if (!entry || typeof entry !== 'object') return [{ kind: 'skip' }]
|
|
521
|
+
// 排队行 (合成条目): 显示忙时挂起的用户指令, 与 web 排队卡片同形.
|
|
522
|
+
if (entry.type === '__pending_queue__') {
|
|
523
|
+
return [{ kind: 'pending', text: String(entry.text ?? ''), count: Number(entry.count) || 1 }]
|
|
524
|
+
}
|
|
518
525
|
if (isHiddenNoise(entry)) return [{ kind: 'skip' }]
|
|
519
526
|
const type = entry.type
|
|
520
527
|
|
package/src/lib/screen-text.ts
CHANGED
|
@@ -118,6 +118,7 @@ export type ScreenRowTone =
|
|
|
118
118
|
| 'reasoning'
|
|
119
119
|
| 'system'
|
|
120
120
|
| 'error'
|
|
121
|
+
| 'pending'
|
|
121
122
|
|
|
122
123
|
export interface ScreenRow {
|
|
123
124
|
/** Visible text used for geometry, hit-testing, and clipboard extraction. */
|
|
@@ -204,6 +205,11 @@ export function viewScreenRows(view: EntryView, columns: number): ScreenRows {
|
|
|
204
205
|
const rows = view.text.split('\n').map((l, i) => `${i === 0 ? '⚠ ' : ' '}${l}`)
|
|
205
206
|
return { marginTop: true, rows: fit(rows, 'error') }
|
|
206
207
|
}
|
|
208
|
+
case 'pending': {
|
|
209
|
+
const summary = clampLines(view.text, width - 6, 1)[0] || '(无内容)'
|
|
210
|
+
const tail = view.count > 1 ? ` · 等 ${view.count} 条指令` : ''
|
|
211
|
+
return { marginTop: true, rows: fit([`⚡ 排队 · ${summary}${tail}`], 'pending') }
|
|
212
|
+
}
|
|
207
213
|
default:
|
|
208
214
|
return { marginTop: false, rows: [] }
|
|
209
215
|
}
|
package/src/sse.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface SseHandlers {
|
|
|
19
19
|
onGroupCreated?: (group: any) => void
|
|
20
20
|
/** ③ 组条目增量: version = 应用该批后的组版本 (调用方水位线判据). */
|
|
21
21
|
onEntries?: (payload: { group_id: string; group_id_version: number; entries: AnyEntry[] }) => void
|
|
22
|
+
/** 挂起开轮卡 (忙时提交、尚未出队的用户指令): { id, opener_ts, user_summary }. */
|
|
23
|
+
onPendingOpener?: (pending: { id: string; opener_ts: string | null; user_summary: string }) => void
|
|
22
24
|
onTyping?: (active: boolean) => void
|
|
23
25
|
onError?: (message: string, category?: string) => void
|
|
24
26
|
onClose?: () => void
|
|
@@ -117,6 +119,9 @@ export class SseConnection {
|
|
|
117
119
|
entries: Array.isArray(p.entries) ? p.entries : [],
|
|
118
120
|
})
|
|
119
121
|
break
|
|
122
|
+
case 'pending_opener':
|
|
123
|
+
this.handlers.onPendingOpener?.(p.entry)
|
|
124
|
+
break
|
|
120
125
|
case 'typing':
|
|
121
126
|
this.handlers.onTyping?.(!!p.active)
|
|
122
127
|
break
|
package/src/types.ts
CHANGED
|
@@ -84,6 +84,7 @@ export interface Issue {
|
|
|
84
84
|
export interface PcClientMetadata {
|
|
85
85
|
work_mode: 'hub' | 'pc' | 'dual'
|
|
86
86
|
aimux_id: string
|
|
87
|
+
initial_aimux_id?: string
|
|
87
88
|
local_path?: string
|
|
88
89
|
is_tui: boolean
|
|
89
90
|
add_remote_aimux_mcp?: boolean
|
|
@@ -137,6 +138,8 @@ export interface SessionRuntimeStatus {
|
|
|
137
138
|
agent_backend?: string
|
|
138
139
|
real_time_info?: string
|
|
139
140
|
model_available?: boolean
|
|
141
|
+
aimux_id?: string | null
|
|
142
|
+
initial_aimux_id?: string | null
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
// ── Preferences lookups ──────────────────────────────────────────────────────
|
|
@@ -203,6 +206,16 @@ export interface HistoryGroup {
|
|
|
203
206
|
entry_count: number
|
|
204
207
|
}
|
|
205
208
|
|
|
209
|
+
/**
|
|
210
|
+
* 挂起中的开轮卡 (pending_round_openers): 忙时提交、尚未出队的用户指令.
|
|
211
|
+
* /groups 端点返回的 `pending` 数组与 SSE `pending_opener` 事件的 `entry` 字段同形.
|
|
212
|
+
*/
|
|
213
|
+
export interface HistoryPendingOpener {
|
|
214
|
+
id: string
|
|
215
|
+
opener_ts: string | null
|
|
216
|
+
user_summary: string
|
|
217
|
+
}
|
|
218
|
+
|
|
206
219
|
// ── SSE envelope events (GET /api/sessions/:id/events) ───────────────────────
|
|
207
220
|
// Each SSE frame's data is a JSON object with an `event` discriminator.
|
|
208
221
|
export type SseEvent =
|