dsh-mobile-plugin 0.1.76 → 0.1.77
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.
|
@@ -198,26 +198,6 @@ function retryFailure(failure) {
|
|
|
198
198
|
return out.code !== undefined || out.message !== undefined ? out : null;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
/** 上下文行的 label 提取(与桌面 contextProvenance 同口径;取不到为 null)。 */
|
|
202
|
-
function contextLabelOf(source) {
|
|
203
|
-
const kind = source.kind;
|
|
204
|
-
if (kind === 'session-reference') {
|
|
205
|
-
for (const ref of Array.isArray(source.references) ? source.references : []) {
|
|
206
|
-
if (ref && typeof ref.label === 'string' && ref.label) return ref.label;
|
|
207
|
-
}
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
if (kind === 'agent-instructions') {
|
|
211
|
-
for (const change of Array.isArray(source.changes) ? source.changes : []) {
|
|
212
|
-
if (change && typeof change.path === 'string' && change.path) return change.path;
|
|
213
|
-
}
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
if (kind === 'plugin') return typeof source.plugin === 'string' && source.plugin ? source.plugin : null;
|
|
217
|
-
if (kind === 'skill-invocation') return typeof source.name === 'string' && source.name ? source.name : null;
|
|
218
|
-
return typeof kind === 'string' ? kind : null;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
201
|
/** 列表/概览的 lastMessage 预览只取有 role 的消息项(结构化行不参与预览)。 */
|
|
222
202
|
function lastMessageOf(messages) {
|
|
223
203
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -234,7 +214,6 @@ function lastMessageOf(messages) {
|
|
|
234
214
|
* 渲染成 compaction 行(id=compactionId,state='done'),并用同 compactionId 的 compaction/summary 补 counts;
|
|
235
215
|
* - retry:{ kind:'retry', id: retryId, seq, time, status:'scheduled'|'started', retry, maxRetries, delayMs?, failure? };
|
|
236
216
|
* - turn-error / max-tokens:{ kind:'turn-error', id:'turn-<turn>', message, code? } / { kind:'max-tokens', id:'turn-<turn>' };
|
|
237
|
-
* - context:{ kind:'context', id:'ctx-<msgId>', contextRole:'inject'|'recall', contextLabel, text };
|
|
238
217
|
* - command:{ kind:'command', id:commandId, name, status:'running'|'done', resultKind?, text? }
|
|
239
218
|
* (command/run 发出行,command/done 原地升级:name 保 run 的,resultKind/text 取 done 的)。
|
|
240
219
|
* 结构化项与消息按事件 seq 升序混排返回;旧载荷字段缺失时跳过对应项,绝不抛错。
|
|
@@ -388,18 +367,8 @@ function deriveMessages(events) {
|
|
|
388
367
|
continue;
|
|
389
368
|
}
|
|
390
369
|
if (src && typeof src === 'object' && src.kind !== 'user') {
|
|
391
|
-
// 上下文注入/召回(session-reference→召回,其余 kind
|
|
392
|
-
|
|
393
|
-
if (text === '' || msg.id == null) continue;
|
|
394
|
-
out.push({
|
|
395
|
-
id: `ctx-${String(msg.id)}`,
|
|
396
|
-
kind: 'context',
|
|
397
|
-
contextRole: src.kind === 'session-reference' ? 'recall' : 'inject',
|
|
398
|
-
contextLabel: contextLabelOf(src),
|
|
399
|
-
text,
|
|
400
|
-
seq,
|
|
401
|
-
time,
|
|
402
|
-
});
|
|
370
|
+
// 上下文注入/召回(session-reference→召回,其余 kind→注入):桌面 `isVisibleChatNode`
|
|
371
|
+
// 只放行携带 tool-addition/tool-removal 的上下文节点,这类提示一律不显示 —— 不下发。
|
|
403
372
|
continue;
|
|
404
373
|
}
|
|
405
374
|
// 普通用户消息(source 缺失或 kind==='user')沿用原折叠。
|
|
@@ -601,7 +570,6 @@ module.exports = {
|
|
|
601
570
|
buildToolResultMap,
|
|
602
571
|
parseBlocks,
|
|
603
572
|
blocksToText,
|
|
604
|
-
contextLabelOf,
|
|
605
573
|
lastMessageOf,
|
|
606
574
|
MOBILE_PROMPT_RPC_PREFIX,
|
|
607
575
|
clientMessageIdFromSource,
|
|
@@ -30,15 +30,55 @@ async function* tolerate(frames, what, ctx) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
/**
|
|
34
|
+
* follow 流的一帧 → 0..n 个 mux 帧(事件原样转发;实时增量通知合成 assistant/chunk)。
|
|
35
|
+
*
|
|
36
|
+
* 0.2.0 的宿主把逐块流式增量从「会话事件」里拿了出去:正文/推理的每个 delta 只以 follow 流的
|
|
37
|
+
* `assistant-stream` 通知下发,而且**要显式传 `assistantStream: true` 才发**,事件流里只剩
|
|
38
|
+
* 「一步落定」的 `assistant/message`。不订阅通知的话,正文与运行中明细都只能在一步结束时整块
|
|
39
|
+
* 出现(真机故障 2026-10-21:回复整段蹦出来、运行中那一行没有实时明细)。
|
|
40
|
+
*
|
|
41
|
+
* 合成事件用的是移动协议早已认识的 `assistant/chunk` 形状,但**不带 seq** —— 它没有持久化
|
|
42
|
+
* 事件的身份,不该参与客户端的 seq 序门、去重与续传水位(App 侧对无 seq 帧一律放行),
|
|
43
|
+
* turn/step 由最近一次 start 通知补上。设备级推送只看 turn/start、turn/end、session/title
|
|
44
|
+
* (`lib/mobile/events.js` 的 LIST_EVENT_INNER),合成增量到不了那条通道,不会形成推送风暴。
|
|
45
|
+
*
|
|
46
|
+
* `state` 是这条流的记账({ turn, step, streaming }),由调用方持有 —— 一帧的处理要看
|
|
47
|
+
* 「之前收到过什么」。snapshot 帧不需要处理:历史走 session.history。
|
|
48
|
+
*/
|
|
49
|
+
function* followFramePayloads(frame, sessionId, state) {
|
|
50
|
+
if (!frame) return;
|
|
51
|
+
if (frame.type === 'event') {
|
|
52
|
+
if (!frame.event) return;
|
|
53
|
+
// 增量只交付一次:宿主若同时把 chunk 也当成事件发,以通知为准(通知先到)。
|
|
54
|
+
if (state.streaming && frame.event.type === 'assistant/chunk') return;
|
|
40
55
|
yield { payload: { type: 'session/event', sessionId, event: frame.event } };
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const notice = frame.type === 'assistant-stream' ? frame.frame : null;
|
|
59
|
+
if (!notice) return;
|
|
60
|
+
if (notice.type === 'start') {
|
|
61
|
+
if (Number.isSafeInteger(notice.turn)) state.turn = notice.turn;
|
|
62
|
+
if (Number.isSafeInteger(notice.step)) state.step = notice.step;
|
|
63
|
+
return;
|
|
41
64
|
}
|
|
65
|
+
if (notice.type !== 'chunk' || !notice.chunk || typeof notice.chunk !== 'object') return;
|
|
66
|
+
// 块序号在通知帧上,chunk 自己也带一份;自己没带就补上(App 侧按 chunk.index 拼草稿)。
|
|
67
|
+
const chunk = typeof notice.chunk.index === 'number' || typeof notice.index !== 'number'
|
|
68
|
+
? notice.chunk
|
|
69
|
+
: { ...notice.chunk, index: notice.index };
|
|
70
|
+
state.streaming = true;
|
|
71
|
+
yield {
|
|
72
|
+
payload: {
|
|
73
|
+
type: 'session/event',
|
|
74
|
+
sessionId,
|
|
75
|
+
event: {
|
|
76
|
+
type: 'assistant/chunk',
|
|
77
|
+
time: Number.isFinite(notice.time) ? notice.time : Date.now(),
|
|
78
|
+
data: { turn: state.turn, step: state.step, chunk },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
42
82
|
}
|
|
43
83
|
|
|
44
84
|
/** 后台任务整帧。宿主 job/list 是流,首帧即全量 rows,之后按变化推。 */
|
|
@@ -174,20 +214,23 @@ function muxFrames(ctx, request, signal) {
|
|
|
174
214
|
}
|
|
175
215
|
const control = openStream(ctx, 'session', 'control', {}, signal).then((frames) => tolerate(frames, 'session/control', ctx));
|
|
176
216
|
const jobs = openStream(ctx, 'job', 'list', { request: { sessionId } }, signal).then((frames) => tolerate(frames, `job/${sessionId}`, ctx));
|
|
217
|
+
// assistantStream: 逐块增量只走这条流的 assistant-stream 通知(见 followFramePayloads)。
|
|
177
218
|
const follow = openStream(ctx, 'session', 'follow', {
|
|
178
|
-
request: { address: { kind: 'session', sessionId }, maxMessages: 1 },
|
|
219
|
+
request: { address: { kind: 'session', sessionId }, maxMessages: 1, assistantStream: true },
|
|
179
220
|
}, signal).then((frames) => tolerate(frames, `session/${sessionId}`, ctx));
|
|
180
221
|
const projections = projectionState();
|
|
181
|
-
const
|
|
222
|
+
const followState = { turn: null, step: null, streaming: false };
|
|
223
|
+
const sources = await Promise.all([['control', control], ['jobs', jobs], ['follow', follow]].map(([source, promise]) => promise.then(async function* wrap(iterable) {
|
|
182
224
|
for await (const frame of iterable) {
|
|
183
|
-
if (
|
|
225
|
+
if (source === 'follow') {
|
|
226
|
+
// follow 一帧可能产出 0..n 个 mux 帧(事件原样 + 增量通知合成 chunk 事件)。
|
|
227
|
+
yield* followFramePayloads(frame, sessionId, followState);
|
|
228
|
+
} else if (frame && frame.type === 'rows') {
|
|
184
229
|
yield { payload: { type: 'session/jobs', sessionId, jobs: Array.isArray(frame.jobs) ? frame.jobs : [] } };
|
|
185
230
|
} else if (frame && frame.type === 'baseline') {
|
|
186
231
|
yield* applyBaseline(frame, projections, sessionId, ctx);
|
|
187
232
|
} else if (frame && frame.type === 'projection') {
|
|
188
233
|
yield* applyUpdate(frame, projections, sessionId, ctx);
|
|
189
|
-
} else if (frame && frame.type === 'event' && frame.event) {
|
|
190
|
-
yield { payload: { type: 'session/event', sessionId, event: frame.event } };
|
|
191
234
|
}
|
|
192
235
|
}
|
|
193
236
|
}).catch((error) => {
|
package/lib/mobile/sessions.js
CHANGED
|
@@ -42,7 +42,7 @@ const {
|
|
|
42
42
|
const { resolveEventAttachments } = require('./attachments.js');
|
|
43
43
|
const { updateCompletionStates, confirmCompletionFromHistory } = require('./completion.js');
|
|
44
44
|
const { buildMessagesSnapshot, buildStatsSnapshot, statsProjections, handleMessages } = require('./session-snapshot.js');
|
|
45
|
-
const {
|
|
45
|
+
const { buildModelsSnapshot, handleModels, handleSelectModel } = require('./session-models.js');
|
|
46
46
|
const { buildSingleSessionMirror, buildMirrorWarmSnapshots } = require('./session-mirror.js');
|
|
47
47
|
const { promptOnce, handleSend, handleCommands, handleSessionRead, handleSessionAttachment, handleStop,
|
|
48
48
|
handleRename, handleFork } = require('./session-send.js');
|
|
@@ -404,12 +404,6 @@ async function handleNew(ctx, req, res) {
|
|
|
404
404
|
if (!sessionId) {
|
|
405
405
|
return { status: 500, body: { ok: false, error: { code: 'internal', message: '新建会话未返回 sessionId' } } };
|
|
406
406
|
}
|
|
407
|
-
// 创建响应携带宿主当前选择;APP 可立即显示,完整目录仍可后台刷新。
|
|
408
|
-
let models;
|
|
409
|
-
try {
|
|
410
|
-
const result = unwrap(await callSessions(ctx, 'models', { sessionId }));
|
|
411
|
-
if (!result.error) models = safeModelsValue(result.value);
|
|
412
|
-
} catch { /* 模型目录不可用时仍允许创建会话 */ }
|
|
413
407
|
// 新建的会话要立刻出现在列表里,否则手机端最长 10 秒看不到它。
|
|
414
408
|
invalidateSessionList(ctx);
|
|
415
409
|
return {
|
|
@@ -419,7 +413,6 @@ async function handleNew(ctx, req, res) {
|
|
|
419
413
|
sessionId,
|
|
420
414
|
cwd: payload.cwd || null,
|
|
421
415
|
...(value && typeof value.agentPreset === 'string' ? { agentPreset: value.agentPreset } : {}),
|
|
422
|
-
...(models?.current ? { models } : {}),
|
|
423
416
|
},
|
|
424
417
|
};
|
|
425
418
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mobile-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.77",
|
|
4
4
|
"description": "Mobile API (/api/mobile/v1) and relay client for the DSH mobile app — desktop profile build (0.2.0-rc.2 compatible, peers resolved by the host)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|