lark-remote 0.5.19 → 0.5.21

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 (136) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/bridge/approval-actions.d.ts +1 -1
  3. package/dist/bridge/approval-actions.js +1 -1
  4. package/dist/bridge/bash-run.d.ts +71 -0
  5. package/dist/bridge/bash-run.js +217 -0
  6. package/dist/bridge/compact.d.ts +155 -0
  7. package/dist/bridge/compact.js +450 -0
  8. package/dist/bridge/final-usage.js +4 -4
  9. package/dist/bridge/inbound-media.js +72 -46
  10. package/dist/bridge/index.d.ts +106 -153
  11. package/dist/bridge/index.js +400 -1000
  12. package/dist/bridge/job-watch.d.ts +68 -0
  13. package/dist/bridge/job-watch.js +146 -0
  14. package/dist/bridge/queue-manager.d.ts +3 -3
  15. package/dist/bridge/queue-manager.js +10 -29
  16. package/dist/card/approval-render.d.ts +2 -2
  17. package/dist/card/approval-render.js +141 -130
  18. package/dist/card/card-budget.js +123 -98
  19. package/dist/card/card-helpers.d.ts +3 -3
  20. package/dist/card/card-helpers.js +6 -6
  21. package/dist/card/final-answer-file.js +1 -1
  22. package/dist/card/run-renderer.d.ts +3 -3
  23. package/dist/card/run-renderer.js +114 -159
  24. package/dist/card/run-state.d.ts +1 -15
  25. package/dist/card/run-state.js +358 -292
  26. package/dist/card/tool-render.d.ts +1 -1
  27. package/dist/card/tool-render.js +21 -17
  28. package/dist/card/usage-format.d.ts +22 -7
  29. package/dist/card/usage-format.js +119 -100
  30. package/dist/cli.js +15 -3
  31. package/dist/clone.js +2 -2
  32. package/dist/common/text-width.js +36 -24
  33. package/dist/common/tool-summary.js +132 -90
  34. package/dist/config/codex-config.js +130 -106
  35. package/dist/config/index.d.ts +16 -2
  36. package/dist/config/index.js +23 -8
  37. package/dist/connector/index.js +6 -6
  38. package/dist/error-classification.js +3 -2
  39. package/dist/inbound/placeholder.d.ts +1 -1
  40. package/dist/inbound/placeholder.js +1 -1
  41. package/dist/index.js +387 -281
  42. package/dist/logger/index.js +6 -6
  43. package/dist/message-handlers.d.ts +0 -5
  44. package/dist/message-handlers.js +254 -237
  45. package/dist/model-catalog.d.ts +2 -2
  46. package/dist/model-catalog.js +3 -8
  47. package/dist/platform/identity.js +1 -1
  48. package/dist/platform/path.d.ts +1 -1
  49. package/dist/platform/path.js +1 -1
  50. package/dist/platform/sleep-blocker.js +1 -1
  51. package/dist/platform/terminator-win32.js +2 -1
  52. package/dist/router/commands/ls.d.ts +3 -0
  53. package/dist/router/commands/ls.js +256 -175
  54. package/dist/router/commands/order.js +184 -159
  55. package/dist/router/commands/resume.js +190 -152
  56. package/dist/router/commands/ws.js +219 -174
  57. package/dist/router/config/dsh.d.ts +4 -8
  58. package/dist/router/config/dsh.js +3 -10
  59. package/dist/router/config/kimi.js +1 -1
  60. package/dist/router/config/types.d.ts +1 -1
  61. package/dist/router/config-diff.js +1 -1
  62. package/dist/router/index.d.ts +56 -11
  63. package/dist/router/index.js +401 -391
  64. package/dist/runner/bash/runner.js +2 -4
  65. package/dist/runner/claude/session.d.ts +1 -1
  66. package/dist/runner/claude/session.js +6 -4
  67. package/dist/runner/codex/app-server/runner.d.ts +9 -2
  68. package/dist/runner/codex/app-server/runner.js +30 -25
  69. package/dist/runner/codex/app-server/translator.d.ts +2 -5
  70. package/dist/runner/codex/app-server/translator.js +291 -238
  71. package/dist/runner/common/acp/acp-approval.d.ts +1 -1
  72. package/dist/runner/common/acp/acp-approval.js +1 -1
  73. package/dist/runner/common/acp/base-acp-runner.d.ts +5 -2
  74. package/dist/runner/common/acp/base-acp-runner.js +26 -65
  75. package/dist/runner/common/acp/base-translator.d.ts +12 -2
  76. package/dist/runner/common/acp/base-translator.js +50 -26
  77. package/dist/runner/common/acp/protocol-types.d.ts +1 -1
  78. package/dist/runner/common/acp/protocol-types.js +2 -2
  79. package/dist/runner/common/connection-based-runner.d.ts +32 -1
  80. package/dist/runner/common/connection-based-runner.js +32 -2
  81. package/dist/runner/common/jsonl-stream.js +154 -187
  82. package/dist/runner/common/jsonrpc/client.d.ts +29 -0
  83. package/dist/runner/common/jsonrpc/client.js +43 -12
  84. package/dist/runner/common/jsonrpc/connection-manager.d.ts +2 -2
  85. package/dist/runner/common/jsonrpc/connection-manager.js +4 -4
  86. package/dist/runner/common/spawning-runner.d.ts +6 -1
  87. package/dist/runner/common/spawning-runner.js +9 -4
  88. package/dist/runner/common/turn-started.d.ts +8 -0
  89. package/dist/runner/common/turn-started.js +11 -0
  90. package/dist/runner/dsh/approval.js +84 -51
  91. package/dist/runner/dsh/catalog-probe.d.ts +17 -0
  92. package/dist/runner/dsh/catalog-probe.js +10 -0
  93. package/dist/runner/dsh/client.d.ts +7 -11
  94. package/dist/runner/dsh/client.js +356 -289
  95. package/dist/runner/dsh/exit-cancel.d.ts +2 -2
  96. package/dist/runner/dsh/exit-cancel.js +2 -2
  97. package/dist/runner/dsh/inflight.d.ts +1 -1
  98. package/dist/runner/dsh/inflight.js +1 -1
  99. package/dist/runner/dsh/mux-stream.js +28 -8
  100. package/dist/runner/dsh/runner.d.ts +44 -4
  101. package/dist/runner/dsh/runner.js +296 -199
  102. package/dist/runner/dsh/translator.d.ts +1 -31
  103. package/dist/runner/dsh/translator.js +180 -178
  104. package/dist/runner/index.d.ts +1 -1
  105. package/dist/runner/kimi/acp/runner.d.ts +1 -2
  106. package/dist/runner/kimi/acp/runner.js +82 -46
  107. package/dist/runner/kimi/acp/translator.d.ts +1 -1
  108. package/dist/runner/kimi/acp/translator.js +3 -4
  109. package/dist/runner/opencode/acp/error-monitor.js +5 -5
  110. package/dist/runner/opencode/acp/runner.d.ts +2 -2
  111. package/dist/runner/opencode/acp/runner.js +4 -4
  112. package/dist/runner/pi/rpc/client.js +4 -13
  113. package/dist/runner/pi/rpc/protocol-types.d.ts +1 -1
  114. package/dist/runner/pi/rpc/protocol-types.js +1 -1
  115. package/dist/runner/pi/rpc/runner.d.ts +3 -0
  116. package/dist/runner/pi/rpc/runner.js +27 -42
  117. package/dist/runner/pi/rpc/translator.js +61 -60
  118. package/dist/runner/types.d.ts +2 -2
  119. package/dist/runner/types.js +1 -1
  120. package/dist/session/claude/sessions.d.ts +45 -0
  121. package/dist/session/claude/sessions.js +160 -122
  122. package/dist/session/codex/rollout-reader.js +181 -182
  123. package/dist/session/common/throw-session-list-error.d.ts +10 -0
  124. package/dist/session/common/throw-session-list-error.js +16 -0
  125. package/dist/session/dsh/sessions.js +291 -235
  126. package/dist/session/kimi/sessions.d.ts +40 -8
  127. package/dist/session/kimi/sessions.js +373 -301
  128. package/dist/session/opencode/sessions.d.ts +142 -1
  129. package/dist/session/opencode/sessions.js +378 -321
  130. package/dist/session/pi/sessions.js +4 -5
  131. package/dist/session/session-store.d.ts +9 -1
  132. package/dist/session/session-store.js +99 -99
  133. package/dist/update/version-check.js +2 -1
  134. package/dist/workspace/index.d.ts +1 -1
  135. package/dist/workspace/index.js +1 -1
  136. package/package.json +1 -1
@@ -17,7 +17,7 @@ export { displayWidth, truncateMiddleToDisplayWidth, truncateToDisplayWidth, } f
17
17
  * 手机(~390px 内容宽)上折叠面板的标题只有**一行**可用:减去图标与内边距约
18
18
  * 320px,按 ~7.5px/半角算 ≈ 42 个半角宽度(iPhone SE 更窄,超出时按 `·`/空格
19
19
  * 折行仍可读,但不能整段换行)。超过就折行,断点常常落在 URL 或命令
20
- * 中间(2026-10-19 截图反馈)。完整参数在展开后的正文里,标题只负责"哪个工具、
20
+ * 中间。完整参数在展开后的正文里,标题只负责"哪个工具、
21
21
  * 动了什么",并带上 `HH:mm` 时间戳(一起计入预算)。
22
22
  */
23
23
  export declare const HEADER_DISPLAY_MAX = 42;
@@ -20,7 +20,7 @@ export { displayWidth, truncateMiddleToDisplayWidth, truncateToDisplayWidth, } f
20
20
  * 手机(~390px 内容宽)上折叠面板的标题只有**一行**可用:减去图标与内边距约
21
21
  * 320px,按 ~7.5px/半角算 ≈ 42 个半角宽度(iPhone SE 更窄,超出时按 `·`/空格
22
22
  * 折行仍可读,但不能整段换行)。超过就折行,断点常常落在 URL 或命令
23
- * 中间(2026-10-19 截图反馈)。完整参数在展开后的正文里,标题只负责"哪个工具、
23
+ * 中间。完整参数在展开后的正文里,标题只负责"哪个工具、
24
24
  * 动了什么",并带上 `HH:mm` 时间戳(一起计入预算)。
25
25
  */
26
26
  export const HEADER_DISPLAY_MAX = 42;
@@ -98,11 +98,23 @@ export function toolBodyMd(tool, opts = {}) {
98
98
  // 同上:走 truncate 保证切点不落在代理对中间(末尾 '…' 由 truncate 附加以对齐预算)。
99
99
  return `${truncate(body, BODY_TOTAL_MAX)}\n\n_(body 已截断,完整内容查日志)_`;
100
100
  }
101
- // --- Named render bodies (shared by tool-name cases and toolHint branches) ---
102
- function renderBashInput(str) {
103
- const cmd = str('command');
104
- return cmd ? `**Command**\n\`\`\`bash\n${truncate(cmd, BODY_FIELD_MAX)}\n\`\`\`` : '';
101
+ function renderFieldInput(str, spec) {
102
+ const value = str(spec.key);
103
+ if (!value)
104
+ return '';
105
+ const shown = spec.truncate ? truncate(value, BODY_FIELD_MAX) : value;
106
+ return spec.fence
107
+ ? `**${spec.label}**\n\`\`\`${spec.fence}\n${shown}\n\`\`\``
108
+ : `**${spec.label}** \`${shown}\``;
105
109
  }
110
+ const BASH_FIELD = {
111
+ key: 'command',
112
+ label: 'Command',
113
+ truncate: true,
114
+ fence: 'bash',
115
+ };
116
+ const GLOB_FIELD = { key: 'pattern', label: 'Pattern', truncate: true };
117
+ const LS_FIELD = { key: 'path', label: 'Path', truncate: false };
106
118
  function renderFileInput(str) {
107
119
  const fp = str('file_path') || str('path');
108
120
  return fp ? `**File** \`${fp}\`` : '';
@@ -115,14 +127,6 @@ function renderGrepInput(str) {
115
127
  lines.push(`**Path** \`${str('path')}\``);
116
128
  return lines.join('\n');
117
129
  }
118
- function renderGlobInput(str) {
119
- const p = str('pattern');
120
- return p ? `**Pattern** \`${truncate(p, BODY_FIELD_MAX)}\`` : '';
121
- }
122
- function renderLsInput(str) {
123
- const p = str('path');
124
- return p ? `**Path** \`${p}\`` : '';
125
- }
126
130
  function renderWebFetchInput(str) {
127
131
  return str('url') ? `**URL** ${str('url')}` : '';
128
132
  }
@@ -149,7 +153,7 @@ function renderInput(tool) {
149
153
  // 完全一致。hint 分支与 name case 共用同一渲染函数,无复制粘贴。
150
154
  switch (tool.toolHint) {
151
155
  case 'shell':
152
- return renderBashInput(str);
156
+ return renderFieldInput(str, BASH_FIELD);
153
157
  case 'file':
154
158
  case 'patch':
155
159
  return renderFileInput(str);
@@ -162,15 +166,15 @@ function renderInput(tool) {
162
166
  }
163
167
  switch (toolKindOf(tool.name)) {
164
168
  case 'shell':
165
- return renderBashInput(str);
169
+ return renderFieldInput(str, BASH_FIELD);
166
170
  case 'file':
167
171
  return renderFileInput(str);
168
172
  case 'grep':
169
173
  return renderGrepInput(str);
170
174
  case 'glob':
171
- return renderGlobInput(str);
175
+ return renderFieldInput(str, GLOB_FIELD);
172
176
  case 'ls':
173
- return renderLsInput(str);
177
+ return renderFieldInput(str, LS_FIELD);
174
178
  case 'webfetch':
175
179
  return renderWebFetchInput(str);
176
180
  case 'websearch':
@@ -9,17 +9,24 @@ import type { UsageSnapshot } from '../session-contract.js';
9
9
  /** `formatUsageStats` 只读这几项:会话/进度卡片上的紧凑用量行。 */
10
10
  export type SessionDisplayUsage = Pick<UsageSnapshot, 'contextLength' | 'contextLimit' | 'compactCount' | 'cacheReadTokens' | 'cacheCreationTokens'>;
11
11
  /**
12
- * Format usage stats (context length, compact count, cache tokens) into a display string.
13
- * Each stat on its own line with blank lines between sections.
14
- * @param usage - Usage stats object
15
- * @param options - Optional display options (showResult adds result line)
12
+ * Format number to human-readable unit string.
13
+ * - >= 999_500: M with at most one decimal (e.g., 999_600 → 1M, 1200000 → 1.2M,
14
+ * 25000000 → 25M)
15
+ * - >= 1K: rounded to nearest integer K (e.g., 1500 → 2K, 120000 → 120K)
16
+ * - < 1K: as-is (e.g., 500 → "500")
17
+ *
18
+ * 千位边界取 M 档:999_500 起按 K 四舍五入会得到 "1000K",直接按 M 档展示。
19
+ * 此前 run-renderer 另有一份 formatTokens 只在这边带此边界护栏,两处已收敛
20
+ * 到本函数(run-renderer 的状态行 `📊 N` 也走这里)。
16
21
  */
17
- export declare function formatUsageStats(usage?: UsageSnapshot & {
22
+ export declare function formatTokenK(n: number): string;
23
+ interface UsageStatsInput extends UsageSnapshot {
18
24
  /** 实际生效模型(system.init 携带)。非空字符串才显示。 */
19
25
  model?: string;
20
26
  /** 本次 run 花费(USD)。存在才显示。 */
21
27
  costUsd?: number;
22
- }, options?: {
28
+ }
29
+ interface UsageDisplayOptions {
23
30
  showResult?: boolean;
24
31
  result?: string;
25
32
  /**
@@ -27,4 +34,12 @@ export declare function formatUsageStats(usage?: UsageSnapshot & {
27
34
  * 本轮 turn 结束不等于任务结束。
28
35
  */
29
36
  completedLabel?: string;
30
- }): string;
37
+ }
38
+ /**
39
+ * Format usage stats (context length, compact count, cache tokens) into a display string.
40
+ * Each stat on its own line with blank lines between sections.
41
+ * @param usage - Usage stats object
42
+ * @param options - Optional display options (showResult adds result line)
43
+ */
44
+ export declare function formatUsageStats(usage?: UsageStatsInput, options?: UsageDisplayOptions): string;
45
+ export {};
@@ -7,12 +7,17 @@
7
7
  */
8
8
  /**
9
9
  * Format number to human-readable unit string.
10
- * - >= 1M: one decimal, M suffix (e.g., 1200000 → 1.2M, 25000000 → 25M)
10
+ * - >= 999_500: M with at most one decimal (e.g., 999_600 → 1M, 1200000 → 1.2M,
11
+ * 25000000 → 25M)
11
12
  * - >= 1K: rounded to nearest integer K (e.g., 1500 → 2K, 120000 → 120K)
12
13
  * - < 1K: as-is (e.g., 500 → "500")
14
+ *
15
+ * 千位边界取 M 档:999_500 起按 K 四舍五入会得到 "1000K",直接按 M 档展示。
16
+ * 此前 run-renderer 另有一份 formatTokens 只在这边带此边界护栏,两处已收敛
17
+ * 到本函数(run-renderer 的状态行 `📊 N` 也走这里)。
13
18
  */
14
- function formatTokenK(n) {
15
- if (n >= 1_000_000) {
19
+ export function formatTokenK(n) {
20
+ if (n >= 999_500) {
16
21
  const m = n / 1_000_000;
17
22
  const rounded = Math.round(m * 10) / 10;
18
23
  // Drop unnecessary ".0" for whole numbers (2.0M → 2M)
@@ -24,122 +29,136 @@ function formatTokenK(n) {
24
29
  }
25
30
  return n.toString();
26
31
  }
32
+ function cumulativeField(value) {
33
+ return value !== undefined ? ` · 累计 ${formatTokenK(value)}` : '';
34
+ }
35
+ function cumulativeCachePercent(usage) {
36
+ const cumInput = usage?.cumulativeInputTokens;
37
+ const cumCacheRead = usage?.cumulativeCacheReadTokens;
38
+ return cumInput !== undefined && cumCacheRead !== undefined && cumInput + cumCacheRead > 0
39
+ ? Math.round((cumCacheRead / (cumInput + cumCacheRead)) * 100)
40
+ : null;
41
+ }
42
+ function cumulativeParts(usage) {
43
+ const cumCacheRead = usage?.cumulativeCacheReadTokens;
44
+ const percent = cumulativeCachePercent(usage);
45
+ const cacheRead = cumCacheRead !== undefined
46
+ ? cumulativeField(cumCacheRead) + (percent !== null ? ` (${percent}%)` : '')
47
+ : '';
48
+ return {
49
+ input: cumulativeField(usage?.cumulativeInputTokens),
50
+ output: cumulativeField(usage?.cumulativeOutputTokens),
51
+ cacheRead,
52
+ cacheCreate: cumulativeField(usage?.cumulativeCacheCreationTokens),
53
+ };
54
+ }
27
55
  /**
28
- * Format usage stats (context length, compact count, cache tokens) into a display string.
29
- * Each stat on its own line with blank lines between sections.
30
- * @param usage - Usage stats object
31
- * @param options - Optional display options (showResult adds result line)
56
+ * 一致性护栏:本 run ≤ 累计 是硬不变量(累计 = 所有 run 之和,必 ≥ 本 run)。
57
+ * 违反说明来源 scope 混用(如 DeepSeek live result usage 是累积/超大 scope,
58
+ * 大于 jsonl 去重聚合的 session 累计)。只标记不修正——数字保持原样便于排查,
59
+ * 由数据源修复。两侧都有值时校验(避免旧数据/缺失字段误报)。
60
+ * 两个分支(real input/output / 10% 估计)共用,保证"本 run > 累计"在任意
61
+ * 路径都显式暴露。
32
62
  */
33
- export function formatUsageStats(usage, options) {
63
+ function cumulativeTotalSuffix(usage, totalTokens) {
64
+ const cum = usage?.cumulativeTotalTokens;
65
+ if (cum === undefined)
66
+ return '';
67
+ const guard = totalTokens > cum ? ' · ⚠️ 累计异常' : '';
68
+ return cumulativeField(cum) + guard;
69
+ }
70
+ function contextLine(length, usage) {
71
+ const ctxLimit = usage.contextLimit;
72
+ const ctxPercent = ctxLimit !== undefined && ctxLimit > 0 ? ` (${Math.round((length / ctxLimit) * 100)}%)` : '';
73
+ const preCompact = usage.compactPreContextLength !== undefined
74
+ ? `(压缩前 ${formatTokenK(usage.compactPreContextLength)})`
75
+ : '';
76
+ return `Context - ${formatTokenK(length)}${ctxPercent}${preCompact}`;
77
+ }
78
+ function headerLines(usage, options) {
34
79
  const lines = [];
35
- // 1. Success 状态(通用格式,无具体结果值)
36
80
  lines.push(options?.completedLabel ?? '✅ 已完成');
37
- // 1.4. 实际生效模型(非空字符串才显示)
38
- if (usage?.model) {
81
+ if (usage?.model)
39
82
  lines.push(`Model - ${usage.model}`);
40
- }
41
- // 1.5. 可选的结果行(showResult 为 true 时显示)
42
- if (options?.showResult && options?.result) {
83
+ if (options?.showResult && options?.result)
43
84
  lines.push(`结果 - ${options.result}`);
44
- }
45
- // 2. Context 长度(compact 卡同时展示压缩前水位)
46
- if (usage?.contextLength !== undefined) {
47
- const ctxLimit = usage.contextLimit;
48
- const ctxPercent = ctxLimit !== undefined && ctxLimit > 0
49
- ? ` (${Math.round((usage.contextLength / ctxLimit) * 100)}%)`
50
- : '';
51
- const preCompact = usage.compactPreContextLength !== undefined
52
- ? `(压缩前 ${formatTokenK(usage.compactPreContextLength)})`
53
- : '';
54
- lines.push(`Context - ${formatTokenK(usage.contextLength)}${ctxPercent}${preCompact}`);
55
- }
56
- // 3. Compact 次数
57
- if (usage?.compactCount) {
85
+ if (usage?.contextLength !== undefined)
86
+ lines.push(contextLine(usage.contextLength, usage));
87
+ if (usage?.compactCount)
58
88
  lines.push(`Compact - ${usage.compactCount}次`);
89
+ return lines;
90
+ }
91
+ /** 命中率分母由调用方给:real 分支是 input+cacheRead,estimate 分支是 totalInput。 */
92
+ function makeTokenLines(input, output, cacheRead, cacheCreation, totalTokens, cachePercent, parts, usage) {
93
+ const lines = [
94
+ `Input token - ${formatTokenK(input)}${parts.input}`,
95
+ `Output token - ${formatTokenK(output)}${parts.output}`,
96
+ ];
97
+ if (cacheCreation > 0) {
98
+ lines.push(`Cache create - ${formatTokenK(cacheCreation)}${parts.cacheCreate}`);
59
99
  }
60
- // 4-7. Token 统计
100
+ lines.push(`Cached token - ${formatTokenK(cacheRead)} (${cachePercent}%)${parts.cacheRead}`);
101
+ lines.push(`Total token - ${formatTokenK(totalTokens)}${cumulativeTotalSuffix(usage, totalTokens)}`);
102
+ return lines;
103
+ }
104
+ function realTokenLines(usage, realInput, realOutput, parts) {
105
+ // Unified ccusage-aligned formula:
106
+ // total = max(total_tokens, input + output + cacheRead + cacheCreation)
107
+ // cache% = cacheRead / (input + cacheRead)
108
+ // cacheRead/cacheCreation are never double-counted; total_tokens folds in any
109
+ // extra (e.g. opencode reasoning, which is separate from output) via max().
110
+ const cacheRead = usage.cacheReadTokens ?? 0;
111
+ const cacheCreation = usage.cacheCreationTokens ?? 0;
112
+ const sumParts = realInput + realOutput + cacheRead + cacheCreation;
113
+ const totalTokens = usage.totalTokens != null ? Math.max(usage.totalTokens, sumParts) : sumParts;
114
+ const promptTokens = realInput + cacheRead;
115
+ const cachePercent = promptTokens > 0 ? Math.round((cacheRead / promptTokens) * 100) : 0;
116
+ return makeTokenLines(realInput, realOutput, cacheRead, cacheCreation, totalTokens, cachePercent, parts, usage);
117
+ }
118
+ function estimateTokenLines(usage, parts) {
119
+ // Estimate path: when real input/output are absent,
120
+ // fall back to the 10% estimate based on contextLength.
61
121
  const cacheRead = usage?.cacheReadTokens ?? 0;
62
122
  const cacheCreation = usage?.cacheCreationTokens ?? 0;
123
+ const inputRef = usage?.contextLength ?? cacheRead;
124
+ if (inputRef <= 0 && cacheRead <= 0 && cacheCreation <= 0)
125
+ return [];
126
+ const totalInput = inputRef + cacheRead;
127
+ const outputEst = inputRef > 0 ? Math.round(inputRef * 0.1) : 0;
128
+ const cachePercent = totalInput > 0 ? Math.round((cacheRead / totalInput) * 100) : 0;
129
+ const totalTokens = totalInput + outputEst + cacheCreation;
130
+ return makeTokenLines(totalInput, outputEst, cacheRead, cacheCreation, totalTokens, cachePercent, parts, usage);
131
+ }
132
+ function tokenLines(usage, parts) {
63
133
  const realInput = usage?.inputTokens;
64
134
  const realOutput = usage?.outputTokens;
65
- // Cumulative suffix (session-wide totals), appended to Input/Output lines.
66
- const cumInputSuffix = usage?.cumulativeInputTokens !== undefined
67
- ? ` · 累计 ${formatTokenK(usage.cumulativeInputTokens)}`
68
- : '';
69
- const cumOutputSuffix = usage?.cumulativeOutputTokens !== undefined
70
- ? ` · 累计 ${formatTokenK(usage.cumulativeOutputTokens)}`
71
- : '';
72
- const cumTotalSuffix = usage?.cumulativeTotalTokens !== undefined
73
- ? ` · 累计 ${formatTokenK(usage.cumulativeTotalTokens)}`
74
- : '';
75
- // 一致性护栏:本 run ≤ 累计 是硬不变量(累计 = 所有 run 之和,必 ≥ 本 run)。
76
- // 违反说明来源 scope 混用(如 DeepSeek live result usage 是累积/超大 scope,
77
- // 大于 jsonl 去重聚合的 session 累计)。只标记不修正——数字保持原样便于排查,
78
- // 由数据源修复。两侧都有值时校验(避免旧数据/缺失字段误报)。
79
- // 两个分支(real input/output / 10% 估计)共用,保证"本 run > 累计"在任意
80
- // 路径都显式暴露。
81
- const withCumGuard = (totalTokens) => cumTotalSuffix +
82
- (usage?.cumulativeTotalTokens !== undefined && totalTokens > usage.cumulativeTotalTokens
83
- ? ' · ⚠️ 累计异常'
84
- : '');
85
- // 计算累计 cache 命中率
86
- const cumInput = usage?.cumulativeInputTokens;
87
- const cumCacheRead = usage?.cumulativeCacheReadTokens;
88
- const cumCachePercent = cumInput !== undefined && cumCacheRead !== undefined && cumInput + cumCacheRead > 0
89
- ? Math.round((cumCacheRead / (cumInput + cumCacheRead)) * 100)
90
- : null;
91
- // 构建累计 suffix
92
- const cumCacheReadSuffix = cumCacheRead !== undefined
93
- ? ` · 累计 ${formatTokenK(cumCacheRead)}` +
94
- (cumCachePercent !== null ? ` (${cumCachePercent}%)` : '')
95
- : '';
96
- const cumCacheCreateSuffix = usage?.cumulativeCacheCreationTokens !== undefined
97
- ? ` · 累计 ${formatTokenK(usage.cumulativeCacheCreationTokens)}`
98
- : '';
99
135
  if (realInput !== undefined && realOutput !== undefined) {
100
- // Unified ccusage-aligned formula:
101
- // total = max(total_tokens, input + output + cacheRead + cacheCreation)
102
- // cache% = cacheRead / (input + cacheRead)
103
- // cacheRead/cacheCreation are never double-counted; total_tokens folds in any
104
- // extra (e.g. opencode reasoning, which is separate from output) via max().
105
- const promptTokens = realInput + cacheRead;
106
- const cachePercent = promptTokens > 0 ? Math.round((cacheRead / promptTokens) * 100) : 0;
107
- const sumParts = realInput + realOutput + cacheRead + cacheCreation;
108
- const totalTokens = usage?.totalTokens != null ? Math.max(usage.totalTokens, sumParts) : sumParts;
109
- lines.push(`Input token - ${formatTokenK(realInput)}${cumInputSuffix}`);
110
- lines.push(`Output token - ${formatTokenK(realOutput)}${cumOutputSuffix}`);
111
- if (cacheCreation > 0) {
112
- lines.push(`Cache create - ${formatTokenK(cacheCreation)}${cumCacheCreateSuffix}`);
113
- }
114
- lines.push(`Cached token - ${formatTokenK(cacheRead)} (${cachePercent}%)${cumCacheReadSuffix}`);
115
- lines.push(`Total token - ${formatTokenK(totalTokens)}${withCumGuard(totalTokens)}`);
136
+ return realTokenLines(usage, realInput, realOutput, parts);
116
137
  }
117
- else {
118
- // Estimate path: when real input/output are absent,
119
- // fall back to the 10% estimate based on contextLength.
120
- const inputRef = usage?.contextLength ?? cacheRead;
121
- if (inputRef > 0 || cacheRead > 0 || cacheCreation > 0) {
122
- const totalInput = inputRef + cacheRead;
123
- const outputEst = inputRef > 0 ? Math.round(inputRef * 0.1) : 0;
124
- const cachePercent = totalInput > 0 ? Math.round((cacheRead / totalInput) * 100) : 0;
125
- const totalTokens = totalInput + outputEst + cacheCreation;
126
- lines.push(`Input token - ${formatTokenK(totalInput)}${cumInputSuffix}`);
127
- lines.push(`Output token - ${formatTokenK(outputEst)}${cumOutputSuffix}`);
128
- if (cacheCreation > 0) {
129
- lines.push(`Cache create - ${formatTokenK(cacheCreation)}${cumCacheCreateSuffix}`);
130
- }
131
- lines.push(`Cached token - ${formatTokenK(cacheRead)} (${cachePercent}%)${cumCacheReadSuffix}`);
132
- lines.push(`Total token - ${formatTokenK(totalTokens)}${withCumGuard(totalTokens)}`);
133
- }
134
- }
135
- // 8. 本次 run 花费(USD)
138
+ return estimateTokenLines(usage, parts);
139
+ }
140
+ function trailingLines(usage) {
141
+ const lines = [];
136
142
  if (usage?.costUsd !== undefined) {
137
143
  lines.push(`Cost - $${usage.costUsd.toFixed(4)}`);
138
144
  }
139
- // 9. 推理 token(> 0 才显示,格式对齐既有 token 行)
140
145
  if (usage?.reasoningTokens !== undefined && usage.reasoningTokens > 0) {
141
146
  lines.push(`Reasoning token - ${formatTokenK(usage.reasoningTokens)}`);
142
147
  }
143
- return lines.join('\n');
148
+ return lines;
149
+ }
150
+ /**
151
+ * Format usage stats (context length, compact count, cache tokens) into a display string.
152
+ * Each stat on its own line with blank lines between sections.
153
+ * @param usage - Usage stats object
154
+ * @param options - Optional display options (showResult adds result line)
155
+ */
156
+ export function formatUsageStats(usage, options) {
157
+ const parts = cumulativeParts(usage);
158
+ return [
159
+ ...headerLines(usage, options),
160
+ ...tokenLines(usage, parts),
161
+ ...trailingLines(usage),
162
+ ].join('\n');
144
163
  }
145
164
  //# sourceMappingURL=usage-format.js.map
package/dist/cli.js CHANGED
@@ -92,10 +92,17 @@ export async function bootstrap(deps) {
92
92
  forwarders.set(sig, forwarder);
93
93
  onSignal(sig, forwarder);
94
94
  }
95
- child.on('error', () => {
95
+ child.on('error', (err) => {
96
+ // bun 拉起失败(ENOENT/EACCES)会静默切 node 兜底,但错误本身必须留痕,
97
+ // 否则「为什么跑在了 node 上」无从排查。logger 此时尚未初始化,console
98
+ // 是规则允许的启动期例外。
99
+ console.error('[lark-remote] bun spawn failed, falling back to node:', err);
96
100
  for (const [sig, forwarder] of forwarders)
97
101
  offSignal(sig, forwarder);
98
- void importEntry().catch(() => exit(1));
102
+ void importEntry().catch((importErr) => {
103
+ console.error('[lark-remote] node fallback import failed:', importErr);
104
+ exit(1);
105
+ });
99
106
  });
100
107
  child.on('exit', (code, signal) => {
101
108
  if (signal) {
@@ -146,7 +153,12 @@ function main() {
146
153
  killSelf: (sig) => process.kill(process.pid, sig),
147
154
  exit: (code) => process.exit(code),
148
155
  };
149
- void bootstrap(deps).catch(() => process.exit(1));
156
+ // bootstrap 是第一接触面:静默 exit(1) 会让安装损坏/依赖缺失表现为零输出
157
+ // 退出,必须把根因打到 stderr(logger 未初始化,console 为规则允许的例外)。
158
+ void bootstrap(deps).catch((err) => {
159
+ console.error('[lark-remote] bootstrap failed:', err);
160
+ process.exit(1);
161
+ });
150
162
  }
151
163
  if (isDirectEntry()) {
152
164
  main();
package/dist/clone.js CHANGED
@@ -237,7 +237,7 @@ export class CloneSession {
237
237
  // 图片发送失败不阻断流程:给出浏览器打开兜底。
238
238
  await this.connector
239
239
  .sendWithRetry(ctx.chatId, { text: `⚠️ 二维码图片发送失败,请在浏览器打开创建:${info.url}` }, { replyTo: ctx.messageId })
240
- .catch(() => { });
240
+ .catch((err) => getLogger().warn('[clone] fallback notice send failed:', err));
241
241
  return;
242
242
  }
243
243
  finally {
@@ -336,7 +336,7 @@ export class CloneSession {
336
336
  `新应用凭证(请妥善保管):App ID ${result.client_id}\n` +
337
337
  '可修复本机问题后用 /clone 重新走一遍流程(会创建新应用)',
338
338
  }, { replyTo: ctx.messageId })
339
- .catch(() => { });
339
+ .catch((sendErr) => getLogger().warn('[clone] finalize failure notice send failed:', sendErr));
340
340
  }
341
341
  finally {
342
342
  this.state = 'idle';
@@ -33,33 +33,45 @@ export function displayWidth(text) {
33
33
  total += charWidth(ch);
34
34
  return total;
35
35
  }
36
+ /**
37
+ * 显示宽度为 2 的码点区间(闭区间,Map 的键 = 下界、值 = 上界)。
38
+ *
39
+ * 用 Map 而非对象字面量:对象字面量的 `constructor` / `toString` 等键会命中
40
+ * Object.prototype,查表会拿到非预期值;数字键的 Map 无此问题。顺序与原 if 链
41
+ * 一致(所有区间都返回同一宽度,遍历早停即等价),逐字保留:
42
+ * - [0x1100,0x115f] Hangul Jamo;U+2329 / U+232A 单独列为单码点区间;
43
+ * - CJK 统一表意文字 / 假名 / 全角标点 / 兼容表意 / 全角 / Hangul 音节;
44
+ * - 常见 emoji(0x1f300–0x1faff)与 Misc Symbols(0x2600–0x27bf);
45
+ * - Misc Technical 块(U+2300–U+23FF):⏱⏹⏳⏸⏺ 等计时器/播放器符号在
46
+ * 状态行里实际使用,手机端按 emoji 宽度渲染——按 1 计会系统性低估行宽,
47
+ * 窄屏余量比设计值更小。
48
+ */
49
+ const WIDE_CODE_POINT_RANGES = new Map([
50
+ [0x1100, 0x115f],
51
+ [0x2329, 0x2329],
52
+ [0x232a, 0x232a],
53
+ [0x2e80, 0xa4cf],
54
+ [0xac00, 0xd7a3],
55
+ [0xf900, 0xfaff],
56
+ [0xfe30, 0xfe6f],
57
+ [0xff00, 0xff60],
58
+ [0xffe0, 0xffe6],
59
+ [0x1f300, 0x1faff],
60
+ [0x2600, 0x27bf],
61
+ [0x2300, 0x23ff],
62
+ ]);
63
+ /** 码点是否落在任一「宽度 2」区间内(模块级命名函数,供 charWidth 查表)。 */
64
+ function isWideCodePoint(code) {
65
+ for (const [lo, hi] of WIDE_CODE_POINT_RANGES) {
66
+ if (code >= lo && code <= hi)
67
+ return true;
68
+ }
69
+ return false;
70
+ }
36
71
  function charWidth(ch) {
37
72
  const code = ch.codePointAt(0) ?? 0;
38
73
  // CJK 统一表意文字、假名、全角标点、常见 emoji 都按两个半角算。
39
- if (code >= 0x1100 && (code <= 0x115f || code === 0x2329 || code === 0x232a))
40
- return 2;
41
- if (code >= 0x2e80 && code <= 0xa4cf)
42
- return 2;
43
- if (code >= 0xac00 && code <= 0xd7a3)
44
- return 2;
45
- if (code >= 0xf900 && code <= 0xfaff)
46
- return 2;
47
- if (code >= 0xfe30 && code <= 0xfe6f)
48
- return 2;
49
- if (code >= 0xff00 && code <= 0xff60)
50
- return 2;
51
- if (code >= 0xffe0 && code <= 0xffe6)
52
- return 2;
53
- if (code >= 0x1f300 && code <= 0x1faff)
54
- return 2;
55
- if (code >= 0x2600 && code <= 0x27bf)
56
- return 2;
57
- // Misc Technical 块(U+2300–U+23FF):⏱⏹⏳⏸⏺ 等计时器/播放器符号在
58
- // 状态行里实际使用,手机端按 emoji 宽度渲染——按 1 计会系统性低估行宽,
59
- // 窄屏余量比设计值更小。
60
- if (code >= 0x2300 && code <= 0x23ff)
61
- return 2;
62
- return 1;
74
+ return isWideCodePoint(code) ? 2 : 1;
63
75
  }
64
76
  /**
65
77
  * 中间省略截断:保留开头与结尾,只在中间打 `…`。