lark-remote 0.5.6 → 0.5.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +46 -6
  2. package/README.en.md +2 -2
  3. package/README.md +2 -2
  4. package/dist/bridge/index.d.ts +63 -1
  5. package/dist/bridge/index.js +147 -14
  6. package/dist/bridge/queue-manager.js +41 -58
  7. package/dist/card/approval-render.js +2 -1
  8. package/dist/card/bash-card-session.d.ts +0 -1
  9. package/dist/card/bash-card-session.js +0 -8
  10. package/dist/card/bash-renderer.js +2 -1
  11. package/dist/card/card-budget.d.ts +37 -0
  12. package/dist/card/card-budget.js +143 -24
  13. package/dist/card/card-session.d.ts +3 -13
  14. package/dist/card/card-session.js +4 -18
  15. package/dist/card/card-shared.d.ts +11 -0
  16. package/dist/card/card-shared.js +19 -13
  17. package/dist/card/run-card-session.d.ts +0 -1
  18. package/dist/card/run-card-session.js +0 -6
  19. package/dist/card/run-renderer.d.ts +18 -0
  20. package/dist/card/run-renderer.js +330 -62
  21. package/dist/card/run-state.d.ts +26 -0
  22. package/dist/card/run-state.js +45 -0
  23. package/dist/card/text-truncate.d.ts +3 -5
  24. package/dist/card/text-truncate.js +3 -5
  25. package/dist/card/time.d.ts +14 -0
  26. package/dist/card/time.js +30 -0
  27. package/dist/card/tool-render.d.ts +15 -2
  28. package/dist/card/tool-render.js +42 -82
  29. package/dist/common/text-width.d.ts +24 -0
  30. package/dist/common/text-width.js +93 -0
  31. package/dist/common/tool-summary.d.ts +19 -0
  32. package/dist/common/tool-summary.js +171 -0
  33. package/dist/common/turn-failure.d.ts +13 -0
  34. package/dist/common/turn-failure.js +18 -0
  35. package/dist/config/index.d.ts +1 -1
  36. package/dist/config/index.js +1 -1
  37. package/dist/connector/index.js +1 -1
  38. package/dist/index.js +66 -13
  39. package/dist/order/index.d.ts +2 -0
  40. package/dist/order/index.js +2 -0
  41. package/dist/platform/command.js +1 -1
  42. package/dist/platform/shell.d.ts +2 -2
  43. package/dist/platform/spawn.d.ts +1 -1
  44. package/dist/platform/spawn.js +2 -2
  45. package/dist/platform/types.d.ts +1 -1
  46. package/dist/router/card-helpers.d.ts +18 -16
  47. package/dist/router/card-helpers.js +88 -46
  48. package/dist/router/config/codex.js +2 -3
  49. package/dist/router/config/common/model-patch.d.ts +8 -0
  50. package/dist/router/config/common/model-patch.js +13 -0
  51. package/dist/router/config/common/render.js +30 -77
  52. package/dist/router/config/dsh.js +2 -1
  53. package/dist/router/config/kimi.js +3 -3
  54. package/dist/router/index.d.ts +9 -11
  55. package/dist/router/index.js +165 -356
  56. package/dist/router/order-exec-dispatch.js +2 -1
  57. package/dist/runner/claude/session.d.ts +1 -1
  58. package/dist/runner/claude/session.js +3 -3
  59. package/dist/runner/common/defaults.d.ts +2 -0
  60. package/dist/runner/common/defaults.js +3 -0
  61. package/dist/runner/common/jsonrpc/connection-manager.js +1 -1
  62. package/dist/runner/common/jsonrpc/transport.d.ts +1 -1
  63. package/dist/runner/common/jsonrpc/transport.js +3 -3
  64. package/dist/runner/common/spawning-runner.d.ts +1 -1
  65. package/dist/runner/common/spawning-runner.js +2 -2
  66. package/dist/runner/dsh/auth.js +2 -1
  67. package/dist/runner/dsh/client.d.ts +100 -3
  68. package/dist/runner/dsh/client.js +290 -6
  69. package/dist/runner/dsh/exit-cancel.d.ts +32 -0
  70. package/dist/runner/dsh/exit-cancel.js +72 -0
  71. package/dist/runner/dsh/inflight.d.ts +62 -0
  72. package/dist/runner/dsh/inflight.js +140 -0
  73. package/dist/runner/dsh/runner.d.ts +90 -4
  74. package/dist/runner/dsh/runner.js +436 -18
  75. package/dist/runner/dsh/translator.d.ts +1 -1
  76. package/dist/runner/dsh/translator.js +103 -1
  77. package/dist/runner/dsh/types.d.ts +60 -0
  78. package/dist/runner/types.d.ts +95 -2
  79. package/dist/runner/types.js +9 -0
  80. package/dist/session/claude/session-index.d.ts +0 -4
  81. package/dist/session/claude/session-index.js +0 -8
  82. package/dist/session/claude/sessions.js +2 -2
  83. package/dist/session/dsh/sessions.js +157 -17
  84. package/dist/update/install.js +1 -1
  85. package/package.json +1 -1
@@ -24,6 +24,7 @@ import { dshArgs, extractDshToken } from '../../runner/dsh/client.js';
24
24
  import { DSH_COOKIE_LIFETIME_MS, isDshCredentialsNotFound, mintDshBrowserCookie, readDshBrowserSecretSync, resolveDshCredentialsFile, } from '../../runner/dsh/auth.js';
25
25
  import { capEvents, paginate } from '../common/pagination.js';
26
26
  import { truncateUtf8, TOOL_RESULT_MAX_BYTES } from '../../common/truncate.js';
27
+ import { summarizeToolInput } from '../../common/tool-summary.js';
27
28
  /**
28
29
  * history 拉取上限。DSH session/page 的 maxMessages 同时限制返回条数,
29
30
  * 若用它同时充当展示截断,>50 事件的会话累计 usage 会少算。这里拉取一个足够大的
@@ -171,9 +172,9 @@ export class DshSessionReader {
171
172
  .filter((i) => i.cwd !== undefined && samePath(i.cwd, cwd))
172
173
  .map((i) => ({
173
174
  sessionId: String(i.sessionId),
174
- summary: '',
175
175
  mtime: i.updatedAt,
176
176
  ...sessionPreset(i),
177
+ ...sessionPreview(i),
177
178
  }));
178
179
  sortByRecencyDesc(filtered, (i) => i.mtime, (i) => i.sessionId);
179
180
  const { items: page, total } = paginate(filtered, opts ?? {});
@@ -199,6 +200,8 @@ export class DshSessionReader {
199
200
  return { events: [] };
200
201
  }
201
202
  const throughSeq = this.resolveThroughSeq(sessionId, session);
203
+ // 服务端投影带来的标题与末轮输入:详情卡头部用它们,不必再从事件里猜。
204
+ const preview = sessionPreview(session);
202
205
  const value = this.unary('session/page', {
203
206
  address: { kind: 'session', sessionId },
204
207
  throughSeq,
@@ -218,6 +221,8 @@ export class DshSessionReader {
218
221
  // null = 历史里没有 turn 标记(旧数据/截断)→ 退回末条 usage 语义。
219
222
  let turnUsage = null;
220
223
  let lastTurnUsage = null;
224
+ /** 等结果的工具调用(见 MappedEvent.pendingTool)。 */
225
+ let pendingTool;
221
226
  for (const entry of value.records ?? []) {
222
227
  const ev = entry.event;
223
228
  if (!ev)
@@ -231,7 +236,30 @@ export class DshSessionReader {
231
236
  turnUsage = null;
232
237
  }
233
238
  const contentEvents = mapEvent(ev);
234
- events.push(...contentEvents.events);
239
+ if (contentEvents.pendingTool) {
240
+ // 未完成的工具调用最多同时一个(DSH 按 step 串行执行;并行调用时按
241
+ // 到达顺序配对,最坏情况是两次调用共用一个面板,不会丢信息)。
242
+ if (pendingTool)
243
+ events.push(pendingTool);
244
+ pendingTool = contentEvents.pendingTool;
245
+ }
246
+ else if (ev.type === 'tool/result' && pendingTool && contentEvents.events[0]) {
247
+ const result = contentEvents.events[0];
248
+ // 标题沿用调用的 `🔧 name — summary`,失败改成 ❌(折叠态一眼看出结果)。
249
+ const failed = result.title?.startsWith('🔴') === true;
250
+ events.push({
251
+ type: 'tool_use',
252
+ title: failed
253
+ ? (pendingTool.title ?? '❌ 工具').replace('🔧', '❌')
254
+ : pendingTool.title,
255
+ content: result.content,
256
+ timestamp: pendingTool.timestamp,
257
+ });
258
+ pendingTool = undefined;
259
+ }
260
+ else {
261
+ events.push(...contentEvents.events);
262
+ }
235
263
  if (contentEvents.title)
236
264
  displayTitle = contentEvents.title;
237
265
  const u = contentEvents.usage;
@@ -278,12 +306,19 @@ export class DshSessionReader {
278
306
  }),
279
307
  };
280
308
  }
309
+ // 中断/仍在执行的调用没有结果:把它当独立事件留下,别让这一条消失。
310
+ if (pendingTool)
311
+ events.push(pendingTool);
312
+ // 头部信息口径:最近输入优先用服务端末轮 prompt(事件里可能只是当前 turn
313
+ // 的一部分),标题用服务端 title;两者都缺时保持原行为。
314
+ const headerLastInput = preview.lastUserMessage ?? displayTitle;
281
315
  return {
282
316
  // 展示仍按 maxEvents 尾截断(默认 50,保持原行为),usage 已在上面基于
283
317
  // 完整 history 累加。
284
318
  events: capEvents(events, opts?.maxEvents ?? 50),
285
319
  ...(sessionUsage ? { usage: sessionUsage } : {}),
286
- ...(displayTitle ? { displayTitle } : {}),
320
+ ...(headerLastInput ? { displayTitle: headerLastInput } : {}),
321
+ ...(preview.title ? { aiTitle: preview.title } : {}),
287
322
  };
288
323
  }
289
324
  catch (error) {
@@ -324,9 +359,77 @@ function sessionPreset(item) {
324
359
  const preset = item.projections?.values?.agentPreset ?? item.agentPreset;
325
360
  return preset ? { agentPreset: preset } : {};
326
361
  }
362
+ /**
363
+ * 会话预览(列表卡上的「标题 / 最近输入 / 最后输出」)。
364
+ *
365
+ * 全部取自 `session/list` 的 `projections.values`——DSH 0.2.x 已经随列表一起
366
+ * 返回 title 与 turnOutline(每轮的 prompt/response),所以这里不需要再拉
367
+ * `session/page`:那是一次上万条事件的大响应,一页 5 个会话就是 5 次。
368
+ *
369
+ * `turnOutline` 的 prompt/response 由 DSH 侧按长度截断(结尾带 `…`),列表卡
370
+ * 只做单行预览,直接复用即可。缺字段 / 类型不对时全部留空,不影响列表。
371
+ */
372
+ function sessionPreview(item) {
373
+ const values = item.projections?.values;
374
+ const rawTitle = values?.title;
375
+ const title = typeof rawTitle === 'string' ? rawTitle.trim() : '';
376
+ const outline = values?.turnOutline;
377
+ const lastUserMessage = lastOutlineText(outline, 'prompt');
378
+ const lastAssistantMessage = lastOutlineText(outline, 'response');
379
+ return {
380
+ // summary 同时填标题:只认 summary 的消费方(旧卡片路径、其他 agent 的
381
+ // 统一逻辑)也能拿到可读文本,不必区分 agent。
382
+ summary: title,
383
+ ...(title ? { title } : {}),
384
+ ...(lastUserMessage ? { lastUserMessage } : {}),
385
+ ...(lastAssistantMessage ? { lastAssistantMessage } : {}),
386
+ };
387
+ }
388
+ /**
389
+ * 从 turnOutline 末条往回找第一个非空文本。
390
+ *
391
+ * 在途 turn 已经写进 outline,但 `response` 还是空串(实测末条
392
+ * `{turn:6, prompt:"…", response:""}`);直接取末条会让「最后输出」空掉,
393
+ * 所以回溯到最近一条有内容的结果。
394
+ */
395
+ function lastOutlineText(outline, key) {
396
+ if (!Array.isArray(outline))
397
+ return undefined;
398
+ for (let i = outline.length - 1; i >= 0; i -= 1) {
399
+ const entry = outline[i];
400
+ if (!entry || typeof entry !== 'object')
401
+ continue;
402
+ const raw = entry[key];
403
+ if (typeof raw !== 'string')
404
+ continue;
405
+ const text = raw.trim();
406
+ if (text)
407
+ return text;
408
+ }
409
+ return undefined;
410
+ }
327
411
  function emptyUsageTotals() {
328
412
  return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
329
413
  }
414
+ /**
415
+ * `user/message.source.kind` 里属于**纯上下文注入**的种类:没有回顾价值,且
416
+ * 会挤掉真正的对话(5 条事件的窗口里被它们占满就什么都看不到了)。
417
+ */
418
+ const CONTEXT_INJECTION_KINDS = new Set([
419
+ 'time-context',
420
+ 'agent-instructions',
421
+ 'runtime-context',
422
+ 'skill-catalog',
423
+ 'compact-checkpoint',
424
+ ]);
425
+ /** 系统通知(后台任务完成、目标变更)在回顾卡里的正文上限。 */
426
+ const NOTICE_MAX_BYTES = 400;
427
+ /** 单条思考在 resume 卡里的上限:过程信息,留头几百字即可。 */
428
+ const REASONING_MAX_BYTES = 800;
429
+ /** 单条 assistant 正文上限(最终答复通常最重要,给足空间)。 */
430
+ const TEXT_MAX_BYTES = 2_000;
431
+ /** 工具原始参数上限(标题已有摘要,正文只用于看完整命令)。 */
432
+ const TOOL_INPUT_MAX_BYTES = 600;
330
433
  /** Map one DSH SessionEvent to display content events (+ title/usage side effects). */
331
434
  function mapEvent(ev) {
332
435
  const out = { events: [] };
@@ -337,42 +440,79 @@ function mapEvent(ev) {
337
440
  .filter((c) => c.type === 'text')
338
441
  .map((c) => c.text ?? '')
339
442
  .join('');
340
- out.events.push({ type: 'user', content: text, timestamp: new Date(ev.time).toISOString() });
443
+ if (!text.trim())
444
+ break;
445
+ // DSH 把上下文注入也写成 user/message,靠 `source.kind` 区分。真实会话里
446
+ // 30 条 user/message 中只有 8 条是用户本人说的(其余是 time-context /
447
+ // agent-instructions / skill-catalog / goal / tool-jobs…)——照单全收会让
448
+ // 回顾卡里塞满"Time sampled while preparing turn…",还把系统提示标成 👤 你。
449
+ const source = ev.data.source;
450
+ const kind = typeof source?.kind === 'string' ? source.kind : undefined;
451
+ if (kind && CONTEXT_INJECTION_KINDS.has(kind))
452
+ break;
453
+ const isNotice = kind !== undefined && kind !== 'user';
454
+ out.events.push({
455
+ type: isNotice ? 'notice' : 'user',
456
+ ...(isNotice ? { title: '🔔 通知' } : {}),
457
+ content: isNotice ? truncateUtf8(text.trim(), NOTICE_MAX_BYTES) : text,
458
+ timestamp: new Date(ev.time).toISOString(),
459
+ });
341
460
  break;
342
461
  }
343
462
  case 'assistant/message': {
344
463
  const content = ev.data.message
345
464
  ?.content ?? [];
346
- const text = content
347
- .filter((c) => c.type === 'text')
348
- .map((c) => c.text ?? '')
349
- .join('');
350
- if (text)
351
- out.events.push({
352
- type: 'assistant',
353
- content: text,
354
- timestamp: new Date(ev.time).toISOString(),
355
- });
465
+ const timestamp = new Date(ev.time).toISOString();
466
+ // DSH 的块类型是 text / reasoning / tool-call(不是 Anthropic 的
467
+ // thinking)。只认 text 会让 resume 卡"只有工具调用、没有思考与输出"
468
+ // (2026-10-19 反馈:一个 31 turn 的会话里 reasoning 块 371 个、text 12 个,
469
+ // 全被丢掉)。tool-call 块不在这里取——独立的 `tool/call` 事件带完整参数。
470
+ for (const block of content) {
471
+ const text = typeof block.text === 'string' ? block.text.trim() : '';
472
+ if (!text)
473
+ continue;
474
+ if (block.type === 'text') {
475
+ out.events.push({
476
+ type: 'assistant',
477
+ content: truncateUtf8(text, TEXT_MAX_BYTES),
478
+ timestamp,
479
+ });
480
+ }
481
+ else if (block.type === 'reasoning') {
482
+ out.events.push({
483
+ type: 'thinking',
484
+ // 思考是过程信息:resume 卡里留头几百字足够判断"当时在想什么"。
485
+ content: truncateUtf8(text, REASONING_MAX_BYTES),
486
+ timestamp,
487
+ });
488
+ }
489
+ }
356
490
  const usage = ev.data.usage;
357
491
  if (usage && typeof usage === 'object')
358
492
  out.usage = usage;
359
493
  break;
360
494
  }
361
495
  case 'tool/call': {
496
+ // 调用本身先不产出事件:DSH 的 tool/call 与 tool/result 是一对,resume 卡
497
+ // 里合成一个面板(`🔧 bash — cmd` + 输出),而不是两个各占一屏。
362
498
  const name = String(ev.data.name ?? '');
363
499
  const args = typeof ev.data.arguments === 'string' ? ev.data.arguments : '';
364
- out.events.push({
500
+ const summary = summarizeToolInput(name, args);
501
+ out.pendingTool = {
365
502
  type: 'tool_use',
366
- content: `${name}(${truncateUtf8(args, 200)})`,
503
+ title: summary ? `🔧 ${name} — ${summary}` : `🔧 ${name}`,
504
+ content: truncateUtf8(args, TOOL_INPUT_MAX_BYTES),
367
505
  timestamp: new Date(ev.time).toISOString(),
368
- });
506
+ };
369
507
  break;
370
508
  }
371
509
  case 'tool/result': {
372
510
  const msg = ev.data.message;
373
511
  const raw = stringifyContent(msg?.content);
512
+ const isError = msg?.isError === true;
374
513
  out.events.push({
375
514
  type: 'tool_result',
515
+ title: isError ? '🔴 结果(失败)' : '🟢 结果',
376
516
  content: truncateUtf8(raw, TOOL_RESULT_MAX_BYTES),
377
517
  timestamp: new Date(ev.time).toISOString(),
378
518
  });
@@ -102,7 +102,7 @@ export function detectPackageManager(scriptPath) {
102
102
  return inferred;
103
103
  }
104
104
  // 3. availability fallback: 纯 Node PATH 解析(win32 PATHEXT),不再依赖外部 which
105
- // (Windows 无 which);win32 再用 where.exe 兜底罕见的解析失败场景(v2 §4.3)
105
+ // (Windows 无 which);win32 再用 where.exe 兜底罕见的解析失败场景
106
106
  const candidates = ['npm', 'bun', 'pnpm'];
107
107
  for (const cmd of candidates) {
108
108
  if (resolveExecutable(cmd))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lark-remote",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "description": "Feishu (Lark) private-chat bridge to local coding agents — Claude Code / Codex / opencode / pi / kimi, with live-streaming CardKit 2.0 cards",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",