lark-remote 0.5.20 → 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 (55) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/bridge/inbound-media.js +71 -45
  3. package/dist/bridge/index.d.ts +9 -0
  4. package/dist/bridge/index.js +230 -203
  5. package/dist/card/approval-render.js +32 -33
  6. package/dist/card/card-budget.js +123 -98
  7. package/dist/card/run-renderer.js +82 -68
  8. package/dist/card/run-state.d.ts +0 -14
  9. package/dist/card/run-state.js +335 -207
  10. package/dist/card/tool-render.js +20 -16
  11. package/dist/card/usage-format.d.ts +12 -9
  12. package/dist/card/usage-format.js +111 -97
  13. package/dist/common/text-width.js +36 -24
  14. package/dist/common/tool-summary.js +132 -90
  15. package/dist/index.js +226 -190
  16. package/dist/message-handlers.js +253 -236
  17. package/dist/router/commands/ls.d.ts +3 -0
  18. package/dist/router/commands/ls.js +256 -175
  19. package/dist/router/commands/order.js +184 -159
  20. package/dist/router/commands/resume.js +189 -133
  21. package/dist/router/commands/ws.js +219 -174
  22. package/dist/router/index.js +192 -201
  23. package/dist/runner/bash/runner.js +2 -4
  24. package/dist/runner/codex/app-server/translator.d.ts +2 -0
  25. package/dist/runner/codex/app-server/translator.js +287 -221
  26. package/dist/runner/common/acp/base-translator.js +19 -17
  27. package/dist/runner/common/jsonl-stream.js +154 -187
  28. package/dist/runner/common/jsonrpc/client.d.ts +29 -0
  29. package/dist/runner/common/jsonrpc/client.js +43 -12
  30. package/dist/runner/common/spawning-runner.d.ts +5 -0
  31. package/dist/runner/common/spawning-runner.js +8 -3
  32. package/dist/runner/common/turn-started.d.ts +8 -0
  33. package/dist/runner/common/turn-started.js +11 -0
  34. package/dist/runner/dsh/approval.js +84 -51
  35. package/dist/runner/dsh/client.d.ts +7 -9
  36. package/dist/runner/dsh/client.js +356 -287
  37. package/dist/runner/dsh/mux-stream.js +28 -8
  38. package/dist/runner/dsh/runner.d.ts +4 -3
  39. package/dist/runner/dsh/runner.js +88 -64
  40. package/dist/runner/dsh/translator.d.ts +1 -31
  41. package/dist/runner/dsh/translator.js +180 -178
  42. package/dist/runner/kimi/acp/runner.js +81 -44
  43. package/dist/runner/opencode/acp/error-monitor.js +5 -5
  44. package/dist/runner/pi/rpc/client.js +4 -13
  45. package/dist/runner/pi/rpc/translator.js +61 -60
  46. package/dist/session/claude/sessions.js +113 -101
  47. package/dist/session/codex/rollout-reader.js +179 -180
  48. package/dist/session/dsh/sessions.js +281 -231
  49. package/dist/session/kimi/sessions.d.ts +0 -7
  50. package/dist/session/kimi/sessions.js +129 -109
  51. package/dist/session/opencode/sessions.d.ts +0 -11
  52. package/dist/session/opencode/sessions.js +161 -137
  53. package/dist/session/session-store.d.ts +8 -0
  54. package/dist/session/session-store.js +98 -98
  55. package/package.json +1 -1
@@ -1,6 +1,90 @@
1
1
  import fs from 'node:fs';
2
2
  import { getLogger } from '../logger/index.js';
3
3
  import { atomicWriteJson } from '../persistence/atomic-write.js';
4
+ /**
5
+ * session reader 共用的失败留痕(claude stat / kimi realpath):ENOENT 是两次
6
+ * 查询之间的正常竞态,静默;其余 errno 必须 debug 留痕,否则「文件不可读」
7
+ * 会被误判成「会话不活跃」。文案由调用方按场景给出,逐字保持。
8
+ */
9
+ export function logNonEnoentDebug(err, message) {
10
+ if (err.code === 'ENOENT')
11
+ return;
12
+ getLogger().debug(message(err?.message ?? String(err)));
13
+ }
14
+ /** 四类 getter 共用的单一实现:entry 缺失 / 空串 → undefined。 */
15
+ function readSessionId(entry, key, agent) {
16
+ if (!entry)
17
+ return undefined;
18
+ const value = entry[key].get(agent);
19
+ if (!value)
20
+ return undefined;
21
+ return value;
22
+ }
23
+ /** 四个 session 字段缺一即损坏记录(整条跳过,cwd 允许缺失)。 */
24
+ function hasRequiredSessionFields(entry) {
25
+ return (!!entry.sessions && !!entry.previousSessions && !!entry.arrivalSessions && !!entry.sessionCwds);
26
+ }
27
+ /** 只保留 truthy 值(sessions / previousSessions / sessionCwds 的口径)。 */
28
+ function truthySessionMap(record) {
29
+ const map = new Map();
30
+ for (const [agent, sid] of Object.entries(record)) {
31
+ if (sid)
32
+ map.set(agent, sid);
33
+ }
34
+ return map;
35
+ }
36
+ /** 全保留(arrivalSessions 的 '' 是显式清空基线,必须跨重启存活)。 */
37
+ function fullSessionMap(record) {
38
+ const map = new Map();
39
+ for (const [agent, sid] of Object.entries(record)) {
40
+ map.set(agent, sid);
41
+ }
42
+ return map;
43
+ }
44
+ /** 单条持久化记录 → 内存 entry;损坏 / 完全空记录返回 undefined。 */
45
+ function parsePersistedEntry(data) {
46
+ if (typeof data !== 'object' || data === null)
47
+ return undefined;
48
+ const entry = data;
49
+ // 四个 session 字段缺失 = 损坏记录,整条跳过。cwd 允许缺失:
50
+ // 全新用户(未 /cd)先切 agent 时 previous/arrival 停车位已落盘,
51
+ // cwd 尚为空——不能因 cwd 缺失把停车位整条丢掉
52
+ // (anchor_r7_parked_previous_survives_rebuild_without_cwd)。
53
+ if (!hasRequiredSessionFields(entry))
54
+ return undefined;
55
+ const sessions = truthySessionMap(entry.sessions);
56
+ const previousSessions = truthySessionMap(entry.previousSessions);
57
+ const arrivalSessions = fullSessionMap(entry.arrivalSessions);
58
+ const sessionCwds = truthySessionMap(entry.sessionCwds);
59
+ // Persist any user that has state: cwd set, or any meaningful
60
+ // sessions/previousSessions value, or any arrival baseline entry
61
+ // ('' arrivals are meaningful). Fully empty records are skipped.
62
+ const hasCwd = !!entry.cwd && entry.cwd.length > 0;
63
+ if (hasCwd || sessions.size > 0 || previousSessions.size > 0 || arrivalSessions.size > 0) {
64
+ return {
65
+ sessions,
66
+ previousSessions,
67
+ sessionCwds,
68
+ arrivalSessions,
69
+ cwd: entry.cwd ?? '',
70
+ };
71
+ }
72
+ return undefined;
73
+ }
74
+ /** 损坏现场留痕:日志 + 改名保留(原文件继续躺着会重复告警、丢排障证据)。 */
75
+ function preserveCorruptStore(persistPath, err) {
76
+ getLogger().warn(`[session] persisted session store corrupted (${persistPath ?? '(memory)'}), ignoring:`, err);
77
+ if (persistPath) {
78
+ const brokenPath = `${persistPath}.corrupt-${Date.now()}`;
79
+ try {
80
+ fs.renameSync(persistPath, brokenPath);
81
+ getLogger().warn(`[session] moved corrupt store to ${brokenPath}`);
82
+ }
83
+ catch (renameErr) {
84
+ getLogger().warn('[session] failed to preserve corrupt store:', renameErr);
85
+ }
86
+ }
87
+ }
4
88
  /**
5
89
  * Per-user session store. Persists cwd + per-agent sessions to disk so that
6
90
  * the last cwd AND the last-used sessionId per agent are restored after
@@ -104,20 +188,11 @@ export class SessionStore {
104
188
  }
105
189
  /** Get previous sessionId for a specific agent (stored when switching away) */
106
190
  getPreviousSessionId(userId, agent) {
107
- const entry = this.sessions.get(userId);
108
- if (!entry)
109
- return undefined;
110
- const sessionId = entry.previousSessions.get(agent);
111
- if (!sessionId)
112
- return undefined;
113
- return sessionId;
191
+ return readSessionId(this.sessions.get(userId), 'previousSessions', agent);
114
192
  }
115
193
  /** Set previous sessionId for a specific agent (for potential restore when switching back) */
116
194
  setPreviousSessionId(userId, agent, sessionId) {
117
- this.mutateEntry(userId, (entry) => {
118
- entry.previousSessions.set(agent, sessionId);
119
- return entry;
120
- });
195
+ this.writeSessionId(userId, 'previousSessions', agent, sessionId);
121
196
  }
122
197
  /** Clear previous sessionId for a specific agent */
123
198
  clearPreviousSessionId(userId, agent) {
@@ -128,18 +203,16 @@ export class SessionStore {
128
203
  }
129
204
  /** Get the arrival baseline sessionId for a specific agent ('' and missing → undefined) */
130
205
  getArrivalSessionId(userId, agent) {
131
- const entry = this.sessions.get(userId);
132
- if (!entry)
133
- return undefined;
134
- const sessionId = entry.arrivalSessions.get(agent);
135
- if (!sessionId)
136
- return undefined;
137
- return sessionId;
206
+ return readSessionId(this.sessions.get(userId), 'arrivalSessions', agent);
138
207
  }
139
208
  /** Set the arrival baseline for a specific agent ('' explicitly clears the arrival baseline) */
140
209
  setArrivalSessionId(userId, agent, sessionId) {
210
+ this.writeSessionId(userId, 'arrivalSessions', agent, sessionId);
211
+ }
212
+ /** previous/arrival 两类 setter 共用的单一实现(合并完全重复块)。 */
213
+ writeSessionId(userId, key, agent, sessionId) {
141
214
  this.mutateEntry(userId, (entry) => {
142
- entry.arrivalSessions.set(agent, sessionId);
215
+ entry[key].set(agent, sessionId);
143
216
  return entry;
144
217
  });
145
218
  }
@@ -165,13 +238,7 @@ export class SessionStore {
165
238
  }
166
239
  /** Get sessionId for a specific agent, or undefined if no session or sessionId is empty */
167
240
  getSessionId(userId, agent = 'claude') {
168
- const entry = this.sessions.get(userId);
169
- if (!entry)
170
- return undefined;
171
- const sessionId = entry.sessions.get(agent);
172
- if (!sessionId)
173
- return undefined;
174
- return sessionId;
241
+ return readSessionId(this.sessions.get(userId), 'sessions', agent);
175
242
  }
176
243
  /** Set sessionId for a specific agent, optionally with cwd */
177
244
  setSessionId(userId, agent, sessionId, cwd) {
@@ -196,13 +263,7 @@ export class SessionStore {
196
263
  }
197
264
  /** Get sessionCwd for a specific agent, or undefined if not set */
198
265
  getSessionCwd(userId, agent = 'claude') {
199
- const entry = this.sessions.get(userId);
200
- if (!entry)
201
- return undefined;
202
- const sessionCwd = entry.sessionCwds.get(agent);
203
- if (!sessionCwd)
204
- return undefined;
205
- return sessionCwd;
266
+ return readSessionId(this.sessions.get(userId), 'sessionCwds', agent);
206
267
  }
207
268
  /** Set sessionId and sessionCwd for a specific agent, without touching cwd */
208
269
  setSessionIdAndSessionCwd(userId, agent, sessionId, sessionCwd) {
@@ -224,75 +285,14 @@ export class SessionStore {
224
285
  const parsed = JSON.parse(raw);
225
286
  if (typeof parsed === 'object' && parsed !== null) {
226
287
  for (const [userId, data] of Object.entries(parsed)) {
227
- if (typeof data === 'object' && data !== null) {
228
- const entry = data;
229
- // 四个 session 字段缺失 = 损坏记录,整条跳过。cwd 允许缺失:
230
- // 全新用户(未 /cd)先切 agent 时 previous/arrival 停车位已落盘,
231
- // cwd 尚为空——不能因 cwd 缺失把停车位整条丢掉
232
- // (anchor_r7_parked_previous_survives_rebuild_without_cwd)。
233
- if (!entry.sessions ||
234
- !entry.previousSessions ||
235
- !entry.arrivalSessions ||
236
- !entry.sessionCwds) {
237
- continue;
238
- }
239
- const sessions = new Map();
240
- for (const [agent, sid] of Object.entries(entry.sessions)) {
241
- if (sid) {
242
- sessions.set(agent, sid);
243
- }
244
- }
245
- const previousSessions = new Map();
246
- for (const [agent, sid] of Object.entries(entry.previousSessions)) {
247
- if (sid) {
248
- previousSessions.set(agent, sid);
249
- }
250
- }
251
- const arrivalSessions = new Map();
252
- for (const [agent, sid] of Object.entries(entry.arrivalSessions)) {
253
- // '' entries are meaningful: explicit "cleared arrival" must
254
- // survive a restart instead of being re-defaulted.
255
- arrivalSessions.set(agent, sid);
256
- }
257
- const sessionCwds = new Map();
258
- for (const [agent, scwd] of Object.entries(entry.sessionCwds)) {
259
- if (scwd) {
260
- sessionCwds.set(agent, scwd);
261
- }
262
- }
263
- // Persist any user that has state: cwd set, or any meaningful
264
- // sessions/previousSessions value, or any arrival baseline entry
265
- // ('' arrivals are meaningful). Fully empty records are skipped.
266
- const hasCwd = !!entry.cwd && entry.cwd.length > 0;
267
- if (hasCwd ||
268
- sessions.size > 0 ||
269
- previousSessions.size > 0 ||
270
- arrivalSessions.size > 0) {
271
- this.sessions.set(userId, {
272
- sessions,
273
- previousSessions,
274
- sessionCwds,
275
- arrivalSessions,
276
- cwd: entry.cwd ?? '',
277
- });
278
- }
279
- }
288
+ const entry = parsePersistedEntry(data);
289
+ if (entry)
290
+ this.sessions.set(userId, entry);
280
291
  }
281
292
  }
282
293
  }
283
294
  catch (err) {
284
- getLogger().warn(`[session] persisted session store corrupted (${this.persistPath ?? '(memory)'}), ignoring:`, err);
285
- // 改名保留现场:原文件继续原地躺着会让下次启动重复告警,也丢掉排障证据。
286
- if (this.persistPath) {
287
- const brokenPath = `${this.persistPath}.corrupt-${Date.now()}`;
288
- try {
289
- fs.renameSync(this.persistPath, brokenPath);
290
- getLogger().warn(`[session] moved corrupt store to ${brokenPath}`);
291
- }
292
- catch (renameErr) {
293
- getLogger().warn('[session] failed to preserve corrupt store:', renameErr);
294
- }
295
- }
295
+ preserveCorruptStore(this.persistPath, err);
296
296
  }
297
297
  }
298
298
  autoPersist() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lark-remote",
3
- "version": "0.5.20",
3
+ "version": "0.5.21",
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",