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.
- package/CHANGELOG.md +10 -0
- package/dist/bridge/inbound-media.js +71 -45
- package/dist/bridge/index.d.ts +9 -0
- package/dist/bridge/index.js +230 -203
- package/dist/card/approval-render.js +32 -33
- package/dist/card/card-budget.js +123 -98
- package/dist/card/run-renderer.js +82 -68
- package/dist/card/run-state.d.ts +0 -14
- package/dist/card/run-state.js +335 -207
- package/dist/card/tool-render.js +20 -16
- package/dist/card/usage-format.d.ts +12 -9
- package/dist/card/usage-format.js +111 -97
- package/dist/common/text-width.js +36 -24
- package/dist/common/tool-summary.js +132 -90
- package/dist/index.js +226 -190
- package/dist/message-handlers.js +253 -236
- package/dist/router/commands/ls.d.ts +3 -0
- package/dist/router/commands/ls.js +256 -175
- package/dist/router/commands/order.js +184 -159
- package/dist/router/commands/resume.js +189 -133
- package/dist/router/commands/ws.js +219 -174
- package/dist/router/index.js +192 -201
- package/dist/runner/bash/runner.js +2 -4
- package/dist/runner/codex/app-server/translator.d.ts +2 -0
- package/dist/runner/codex/app-server/translator.js +287 -221
- package/dist/runner/common/acp/base-translator.js +19 -17
- package/dist/runner/common/jsonl-stream.js +154 -187
- package/dist/runner/common/jsonrpc/client.d.ts +29 -0
- package/dist/runner/common/jsonrpc/client.js +43 -12
- package/dist/runner/common/spawning-runner.d.ts +5 -0
- package/dist/runner/common/spawning-runner.js +8 -3
- package/dist/runner/common/turn-started.d.ts +8 -0
- package/dist/runner/common/turn-started.js +11 -0
- package/dist/runner/dsh/approval.js +84 -51
- package/dist/runner/dsh/client.d.ts +7 -9
- package/dist/runner/dsh/client.js +356 -287
- package/dist/runner/dsh/mux-stream.js +28 -8
- package/dist/runner/dsh/runner.d.ts +4 -3
- package/dist/runner/dsh/runner.js +88 -64
- package/dist/runner/dsh/translator.d.ts +1 -31
- package/dist/runner/dsh/translator.js +180 -178
- package/dist/runner/kimi/acp/runner.js +81 -44
- package/dist/runner/opencode/acp/error-monitor.js +5 -5
- package/dist/runner/pi/rpc/client.js +4 -13
- package/dist/runner/pi/rpc/translator.js +61 -60
- package/dist/session/claude/sessions.js +113 -101
- package/dist/session/codex/rollout-reader.js +179 -180
- package/dist/session/dsh/sessions.js +281 -231
- package/dist/session/kimi/sessions.d.ts +0 -7
- package/dist/session/kimi/sessions.js +129 -109
- package/dist/session/opencode/sessions.d.ts +0 -11
- package/dist/session/opencode/sessions.js +161 -137
- package/dist/session/session-store.d.ts +8 -0
- package/dist/session/session-store.js +98 -98
- 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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|