dsh-mini-remote 0.1.6 → 0.1.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/CHANGELOG.md +28 -12
- package/README.en.md +33 -1
- package/README.md +33 -1
- package/client/client.js +272 -1
- package/lib/events.js +30 -4
- package/lib/history.js +15 -2
- package/lib/index.js +658 -10
- package/lib/lark.js +1154 -0
- package/lib/page.html +477 -3
- package/lib/page.js +14 -2
- package/lib/pairing.js +32 -3
- package/lib/server.js +256 -26
- package/lib/store.js +22 -0
- package/lib/tree.js +78 -8
- package/package.json +2 -1
- package/lib/art/out-4legs-fix.gif +0 -0
- package/lib/art/out-anim-all.gif +0 -0
- package/lib/art/out-anim-carousel.gif +0 -0
- package/lib/art/out-anim-run-now-real.gif +0 -0
- package/lib/art/out-anim-run-real.gif +0 -0
- package/lib/art/out-anim-run-slow.gif +0 -0
- package/lib/art/out-anim-run.gif +0 -0
- package/lib/art/out-anim-sprint-real.gif +0 -0
- package/lib/art/out-anim-sprint-slow.gif +0 -0
- package/lib/art/out-anim-sprint.gif +0 -0
- package/lib/art/out-sway-sprint.gif +0 -0
package/lib/tree.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* 它的会话(要读日志折标题,贵)。
|
|
16
16
|
*/
|
|
17
17
|
import { basename } from 'node:path'
|
|
18
|
+
import { readTailEvents } from './log-tail.js'
|
|
18
19
|
|
|
19
20
|
/** 一个工作区默认最多列多少会话。手机上再多也划不动。 */
|
|
20
21
|
export const SESSIONS_PER_WORKSPACE = 20
|
|
@@ -128,6 +129,8 @@ function rememberTitle(cache, id, title, persisted) {
|
|
|
128
129
|
* 手机页面自己)抢。而且这些标题本来就没人等着,晚几百毫秒没有任何代价。
|
|
129
130
|
*/
|
|
130
131
|
export const TITLE_CONCURRENCY = 2
|
|
132
|
+
const TITLE_READ_TIMEOUT_MS = 8_000
|
|
133
|
+
const TITLE_RETRY_MS = 60_000
|
|
131
134
|
|
|
132
135
|
let titleRunning = 0
|
|
133
136
|
const titleWaiting = []
|
|
@@ -192,6 +195,42 @@ function cachedTitle(cache, id, persisted) {
|
|
|
192
195
|
* 同一批冷会话会被读两遍。这里挡住重复的在途请求,读完即释放(失败也释放)。
|
|
193
196
|
*/
|
|
194
197
|
const titleInflight = new WeakMap()
|
|
198
|
+
const titleFailures = new WeakMap()
|
|
199
|
+
|
|
200
|
+
function legacyTitle(sessionId) {
|
|
201
|
+
if (typeof sessionId !== 'string' || !sessionId.startsWith('import-')) return null
|
|
202
|
+
try {
|
|
203
|
+
const result = readTailEvents(sessionId, { windowBytes: 1024 * 1024, maxEvents: 1000 })
|
|
204
|
+
for (const event of result?.events ?? []) {
|
|
205
|
+
if (event?.type !== 'session/title') continue
|
|
206
|
+
const title = event.data?.title ?? event.data?.text
|
|
207
|
+
if (typeof title === 'string') return title.trim()
|
|
208
|
+
}
|
|
209
|
+
return ''
|
|
210
|
+
} catch {
|
|
211
|
+
return ''
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function markTitleFailure(query, ids) {
|
|
216
|
+
let cache = titleFailures.get(query)
|
|
217
|
+
if (!cache) {
|
|
218
|
+
cache = new Map()
|
|
219
|
+
titleFailures.set(query, cache)
|
|
220
|
+
}
|
|
221
|
+
const until = Date.now() + TITLE_RETRY_MS
|
|
222
|
+
for (const id of ids) cache.set(id, until)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function failedTitle(query, id) {
|
|
226
|
+
const until = titleFailures.get(query)?.get(id)
|
|
227
|
+
if (!until) return false
|
|
228
|
+
if (until <= Date.now()) {
|
|
229
|
+
titleFailures.get(query).delete(id)
|
|
230
|
+
return false
|
|
231
|
+
}
|
|
232
|
+
return true
|
|
233
|
+
}
|
|
195
234
|
|
|
196
235
|
/**
|
|
197
236
|
* 后台把缺的标题读出来,写进缓存。**调用方不 await 它**。
|
|
@@ -200,7 +239,7 @@ const titleInflight = new WeakMap()
|
|
|
200
239
|
* 而读它要加载会话日志——实测一个工作区里几条冷会话就是十几秒。那十几秒是用户在
|
|
201
240
|
* 盯着"正在读取…"干等,不该发生。
|
|
202
241
|
*/
|
|
203
|
-
async function fillTitles(query, cache, ids, persisted) {
|
|
242
|
+
async function fillTitles(query, cache, ids, persisted, workspaceId, onTitle) {
|
|
204
243
|
if (!query?.readTitleSnapshots || !ids.length) return
|
|
205
244
|
let flying = titleInflight.get(query)
|
|
206
245
|
if (!flying) {
|
|
@@ -212,13 +251,34 @@ async function fillTitles(query, cache, ids, persisted) {
|
|
|
212
251
|
for (const id of todo) flying.add(id)
|
|
213
252
|
try {
|
|
214
253
|
// 读之前先过全局闸门(见 TITLE_CONCURRENCY):这一步是这次改动里唯一真正吃 CPU 的地方。
|
|
215
|
-
|
|
254
|
+
let timer
|
|
255
|
+
const controller = new AbortController()
|
|
256
|
+
let items
|
|
257
|
+
try {
|
|
258
|
+
timer = setTimeout(() => controller.abort(), TITLE_READ_TIMEOUT_MS)
|
|
259
|
+
items = await withTitleSlot(() => Promise.race([
|
|
260
|
+
query.readTitleSnapshots(todo, controller.signal),
|
|
261
|
+
new Promise((_, reject) => controller.signal.addEventListener(
|
|
262
|
+
'abort', () => reject(new Error('标题读取超时')), { once: true },
|
|
263
|
+
)),
|
|
264
|
+
]))
|
|
265
|
+
} catch {
|
|
266
|
+
markTitleFailure(query, todo)
|
|
267
|
+
return
|
|
268
|
+
} finally {
|
|
269
|
+
if (timer) clearTimeout(timer)
|
|
270
|
+
}
|
|
216
271
|
for (const item of items ?? []) {
|
|
217
|
-
if (item?.status !== 'fulfilled')
|
|
218
|
-
|
|
272
|
+
if (item?.status !== 'fulfilled') {
|
|
273
|
+
markTitleFailure(query, [item?.sessionId].filter(Boolean))
|
|
274
|
+
continue
|
|
275
|
+
}
|
|
276
|
+
const title = titleText(item.value?.title)
|
|
277
|
+
rememberTitle(cache, item.sessionId, title, persisted)
|
|
278
|
+
if (typeof onTitle === 'function') onTitle({ workspaceId, sessionId: item.sessionId, title })
|
|
219
279
|
}
|
|
220
280
|
} catch (err) {
|
|
221
|
-
|
|
281
|
+
markTitleFailure(query, todo)
|
|
222
282
|
} finally {
|
|
223
283
|
for (const id of todo) flying.delete(id)
|
|
224
284
|
}
|
|
@@ -419,7 +479,7 @@ function shapeWorkspace(ws) {
|
|
|
419
479
|
*/
|
|
420
480
|
export async function listSessionsOf({
|
|
421
481
|
registry, query, agents, knownTitles, persisted, workspaceId,
|
|
422
|
-
limit = SESSIONS_PER_WORKSPACE, force,
|
|
482
|
+
limit = SESSIONS_PER_WORKSPACE, force, onTitle,
|
|
423
483
|
}) {
|
|
424
484
|
const ws = safeList(registry).find((w) => w.id === workspaceId)
|
|
425
485
|
if (!ws) return null
|
|
@@ -443,6 +503,16 @@ export async function listSessionsOf({
|
|
|
443
503
|
rememberTitle(cache, id, known, persisted)
|
|
444
504
|
continue
|
|
445
505
|
}
|
|
506
|
+
const legacy = legacyTitle(id)
|
|
507
|
+
if (legacy !== null) {
|
|
508
|
+
titles.set(id, legacy)
|
|
509
|
+
rememberTitle(cache, id, legacy, persisted)
|
|
510
|
+
continue
|
|
511
|
+
}
|
|
512
|
+
if (failedTitle(query, id)) {
|
|
513
|
+
titles.set(id, '')
|
|
514
|
+
continue
|
|
515
|
+
}
|
|
446
516
|
const hit = cachedTitle(cache, id, persisted)
|
|
447
517
|
if (hit === null) missing.push(id)
|
|
448
518
|
else if (hit) titles.set(id, hit)
|
|
@@ -451,7 +521,7 @@ export async function listSessionsOf({
|
|
|
451
521
|
// 那几条先显示日期。缓存里已有的照旧直接用,所以反复展开同一个工作区始终是快的。
|
|
452
522
|
if (missing.length && query?.readTitleSnapshots) {
|
|
453
523
|
await Promise.race([
|
|
454
|
-
fillTitles(query, cache, missing, persisted),
|
|
524
|
+
fillTitles(query, cache, missing, persisted, workspaceId, onTitle),
|
|
455
525
|
new Promise((resolve) => setTimeout(resolve, TITLE_WAIT_MS)),
|
|
456
526
|
])
|
|
457
527
|
for (const id of missing) {
|
|
@@ -476,7 +546,7 @@ export async function listSessionsOf({
|
|
|
476
546
|
if (typeof query?.readTitleSnapshots === 'function') {
|
|
477
547
|
for (const r of rows) {
|
|
478
548
|
const id = r.header.id
|
|
479
|
-
if (!titles.has(id) && cachedTitle(cache, id, persisted) === null) pending += 1
|
|
549
|
+
if (!titles.has(id) && !failedTitle(query, id) && cachedTitle(cache, id, persisted) === null) pending += 1
|
|
480
550
|
}
|
|
481
551
|
}
|
|
482
552
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mini-remote",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "DSH 极简移动端遥控器:只把「你的指令」和「AI 的最终回复」送到手机,屏蔽全部中间过程。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
+
"@larksuiteoapi/node-sdk": "^1.74.0",
|
|
81
82
|
"qrcode": "^1.5.4"
|
|
82
83
|
}
|
|
83
84
|
}
|
|
Binary file
|
package/lib/art/out-anim-all.gif
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/art/out-anim-run.gif
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|