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
@@ -12,10 +12,8 @@ function sessionPreviewLine(text, max) {
12
12
  const oneLine = text.replace(/\s+/g, ' ').trim();
13
13
  return oneLine.length > max ? oneLine.slice(0, max - 1) + '…' : oneLine;
14
14
  }
15
- export function buildResumeCommand(args, ctx, offset, deps) {
16
- const entry = deps.getSessionEntry(ctx.userId);
17
- const cwd = entry?.cwd;
18
- let agentKind = deps.defaultAgent;
15
+ function parseResumeArgs(args, defaultAgent) {
16
+ let agentKind = defaultAgent;
19
17
  let limit;
20
18
  let sessionIdArg;
21
19
  if (args[0]) {
@@ -35,88 +33,87 @@ export function buildResumeCommand(args, ctx, offset, deps) {
35
33
  sessionIdArg = args[0];
36
34
  }
37
35
  }
38
- if (sessionIdArg) {
39
- if (!entry || !cwd)
40
- return { text: '请先 /cd 设置工作目录' };
41
- const reader = agentKind !== deps.defaultAgent ? deps.readerFor(agentKind) : deps.defaultReader();
42
- const verifyContent = reader.readSessionContent(sessionIdArg, cwd);
43
- if (verifyContent.events.length === 0 &&
44
- !verifyContent.usage &&
45
- !verifyContent.aiTitle &&
46
- !verifyContent.recap &&
47
- !verifyContent.displayTitle) {
36
+ return { agentKind, limit, sessionIdArg };
37
+ }
38
+ function readerForAgent(deps, agentKind) {
39
+ return agentKind !== deps.defaultAgent ? deps.readerFor(agentKind) : deps.defaultReader();
40
+ }
41
+ /** 恢复指定 session 前的校验;返回 CommandResult 表示应当中止。 */
42
+ function verifyResumeTarget(reader, deps, agentKind, sessionIdArg, cwd) {
43
+ const verifyContent = reader.readSessionContent(sessionIdArg, cwd);
44
+ if (verifyContent.events.length === 0 &&
45
+ !verifyContent.usage &&
46
+ !verifyContent.aiTitle &&
47
+ !verifyContent.recap &&
48
+ !verifyContent.displayTitle) {
49
+ return {
50
+ text: `未找到 session ${sessionIdArg}(当前目录: ${cwd})。请确认该 session 属于此目录,或先 /cd 到正确目录。`,
51
+ };
52
+ }
53
+ if (agentKind === 'dsh' && deps.defaultAgent === 'dsh') {
54
+ const mismatch = deps.dshPresetMismatch(reader, sessionIdArg, cwd);
55
+ if (mismatch) {
48
56
  return {
49
- text: `未找到 session ${sessionIdArg}(当前目录: ${cwd})。请确认该 session 属于此目录,或先 /cd 到正确目录。`,
57
+ text: `⚠️ 该 DSH session 的 preset 是「${mismatch.sessionPreset}」,与当前配置「${mismatch.currentPreset}」不一致。\n` +
58
+ `preset 在 session 创建时固定,无法中途切换(DSH 返回 agent-preset-conflict)。\n` +
59
+ `继续恢复将在下次 run 失败。请在 /config 中把预设模式改回「${mismatch.sessionPreset}」后重试,或保持当前配置并发新消息新建会话。`,
50
60
  };
51
61
  }
52
- if (agentKind === 'dsh' && deps.defaultAgent === 'dsh') {
53
- const mismatch = deps.dshPresetMismatch(reader, sessionIdArg, cwd);
54
- if (mismatch) {
55
- return {
56
- text: `⚠️ 该 DSH session 的 preset 是「${mismatch.sessionPreset}」,与当前配置「${mismatch.currentPreset}」不一致。\n` +
57
- `preset 在 session 创建时固定,无法中途切换(DSH 返回 agent-preset-conflict)。\n` +
58
- `继续恢复将在下次 run 失败。请在 /config 中把预设模式改回「${mismatch.sessionPreset}」后重试,或保持当前配置并发新消息新建会话。`,
59
- };
60
- }
61
- }
62
- deps.setSessionId(ctx.userId, agentKind, sessionIdArg, cwd);
63
- const display = deps.readDisplay(sessionIdArg, cwd, {
64
- agentKind,
65
- maxEvents: AUTO_RESUME_MAX_EVENTS,
66
- });
67
- const actions = deps.buildActions({
68
- isActive: display.isActive,
69
- activeRunRunId: display.activeRunRunId,
70
- cwd,
71
- compact: deps.canCompact(agentKind, cwd) ? { sessionId: sessionIdArg, agentKind } : undefined,
72
- newSession: true,
73
- });
74
- return {
75
- card: buildSessionHistoryCard({
76
- sessionId: sessionIdArg,
77
- cwd,
78
- displayTitle: display.displayTitle,
79
- aiTitle: display.aiTitle,
80
- recap: display.recap,
81
- events: display.events,
82
- usage: display.usage,
83
- }, {
84
- agentKind,
85
- headerText: `📂 \`${cwd}\`\n会话: **${sessionIdArg}**`,
86
- title: `${display.isActive ? '⏳ 恢复会话(完成中)' : '🔁 恢复会话'} · ${agentDisplayName(agentKind)}`,
87
- usageResult: 'success',
88
- emptyPlaceholder: '_该会话暂无新消息可显示(最后一条为用户输入)_',
89
- actions,
90
- }),
91
- };
92
62
  }
93
- if (!cwd)
94
- return { text: '请先 /cd 设置工作目录' };
95
- const pageSize = clampInt(limit ?? RESUME_PAGE_SIZE, 1, RESUME_PAGE_SIZE);
96
- const reader = agentKind === deps.defaultAgent ? deps.defaultReader() : deps.readerFor(agentKind);
63
+ return undefined;
64
+ }
65
+ function resumeSessionCard(ctx, deps, agentKind, sessionIdArg, cwd) {
66
+ deps.setSessionId(ctx.userId, agentKind, sessionIdArg, cwd);
67
+ const display = deps.readDisplay(sessionIdArg, cwd, {
68
+ agentKind,
69
+ maxEvents: AUTO_RESUME_MAX_EVENTS,
70
+ });
71
+ const actions = deps.buildActions({
72
+ isActive: display.isActive,
73
+ activeRunRunId: display.activeRunRunId,
74
+ cwd,
75
+ compact: deps.canCompact(agentKind, cwd) ? { sessionId: sessionIdArg, agentKind } : undefined,
76
+ newSession: true,
77
+ });
78
+ return {
79
+ card: buildSessionHistoryCard({
80
+ sessionId: sessionIdArg,
81
+ cwd,
82
+ displayTitle: display.displayTitle,
83
+ aiTitle: display.aiTitle,
84
+ recap: display.recap,
85
+ events: display.events,
86
+ usage: display.usage,
87
+ }, {
88
+ agentKind,
89
+ headerText: `📂 \`${cwd}\`\n会话: **${sessionIdArg}**`,
90
+ title: `${display.isActive ? '⏳ 恢复会话(完成中)' : '🔁 恢复会话'} · ${agentDisplayName(agentKind)}`,
91
+ usageResult: 'success',
92
+ emptyPlaceholder: '_该会话暂无新消息可显示(最后一条为用户输入)_',
93
+ actions,
94
+ }),
95
+ };
96
+ }
97
+ function resumeById(ctx, deps, agentKind, sessionIdArg, cwd) {
98
+ const reader = readerForAgent(deps, agentKind);
99
+ const rejected = verifyResumeTarget(reader, deps, agentKind, sessionIdArg, cwd);
100
+ if (rejected)
101
+ return rejected;
102
+ return resumeSessionCard(ctx, deps, agentKind, sessionIdArg, cwd);
103
+ }
104
+ /** 一次列表读取 + offset 页对齐;异常交给调用方转文案。 */
105
+ function loadResumePage(reader, cwd, pageSize, offset) {
97
106
  let pageOffset = offset;
98
- let pageResult;
99
- try {
107
+ let pageResult = reader.listSessions(cwd, { limit: pageSize, offset: pageOffset });
108
+ const maxOffset = Math.max(0, Math.ceil(pageResult.total / pageSize) - 1) * pageSize;
109
+ if (pageOffset < 0 || pageOffset > maxOffset) {
110
+ pageOffset = clampInt(pageOffset, 0, maxOffset);
100
111
  pageResult = reader.listSessions(cwd, { limit: pageSize, offset: pageOffset });
101
- const maxOffset = Math.max(0, Math.ceil(pageResult.total / pageSize) - 1) * pageSize;
102
- if (pageOffset < 0 || pageOffset > maxOffset) {
103
- pageOffset = clampInt(pageOffset, 0, maxOffset);
104
- pageResult = reader.listSessions(cwd, { limit: pageSize, offset: pageOffset });
105
- }
106
- }
107
- catch (err) {
108
- const msg = err instanceof Error ? err.message : String(err);
109
- return { text: `读取 ${agentDisplayName(agentKind)} session 列表失败: ${msg}` };
110
- }
111
- const { sessions: allSessions, total } = pageResult;
112
- if (allSessions.length === 0) {
113
- const hint = agentKind === deps.defaultAgent
114
- ? `\n提示:可使用 /resume <agent> 切换查看其他 Agent 的 session`
115
- : '';
116
- return {
117
- text: `当前目录没有 ${agentDisplayName(agentKind)} session 记录\n${cwd}${hint}`,
118
- };
119
112
  }
113
+ return { pageOffset, sessions: pageResult.sessions, total: pageResult.total };
114
+ }
115
+ /** 行级预览字段缺失时才预取,且只预取前 5 条。 */
116
+ function prefetchResumeMeta(reader, allSessions, cwd) {
120
117
  const sessionMeta = new Map();
121
118
  for (const session of allSessions.slice(0, RESUME_CONTENT_PREFETCH)) {
122
119
  if (session.title || session.lastUserMessage || session.lastAssistantMessage)
@@ -133,72 +130,88 @@ export function buildResumeCommand(args, ctx, offset, deps) {
133
130
  })();
134
131
  sessionMeta.set(session.sessionId, meta);
135
132
  }
136
- const now = Date.now();
137
- const formatRelativeTime = (mtimeMs) => {
138
- const diffSec = Math.floor((now - mtimeMs) / 1000);
139
- if (diffSec < 60)
140
- return '刚刚';
141
- if (diffSec < 3600)
142
- return `${Math.floor(diffSec / 60)} 分钟前`;
143
- if (diffSec < 86400)
144
- return `${Math.floor(diffSec / 3600)} 小时前`;
145
- if (diffSec < 604800)
146
- return `${Math.floor(diffSec / 86400)} 天前`;
147
- return new Date(mtimeMs).toLocaleDateString('zh-CN');
148
- };
149
- const elements = [
150
- { tag: 'div', text: { tag: 'lark_md', content: `📂 当前工作目录:\`${cwd}\`` } },
151
- ];
152
- const currentId = entry.sessions.get(agentKind) ?? '';
153
- for (const session of allSessions) {
154
- const isCurrent = agentKind === deps.defaultAgent && session.sessionId === currentId;
155
- const meta = sessionMeta.get(session.sessionId);
156
- const summaryIsPlaceholder = RESUME_SUMMARY_PLACEHOLDERS.has(session.summary.trim());
157
- const prefetchedTitle = meta?.displayTitle;
158
- const rowTitleRaw = session.title?.trim() ?? '';
159
- const rowTitle = rowTitleRaw && !RESUME_SUMMARY_PLACEHOLDERS.has(rowTitleRaw) ? rowTitleRaw : undefined;
160
- const titleText = prefetchedTitle ?? rowTitle ?? (summaryIsPlaceholder ? '' : session.summary);
161
- const titleLabel = prefetchedTitle
162
- ? meta?.aiTitle
163
- ? 'AI 标题'
164
- : '最近输入'
165
- : rowTitle
166
- ? '标题'
167
- : summaryIsPlaceholder
168
- ? ''
169
- : '会话摘要';
170
- const sections = [];
171
- if (titleText) {
172
- sections.push(`🏷️ **${titleLabel}**\n${titleText.slice(0, 80)}${titleText.length > 80 ? '...' : ''}`);
173
- }
174
- if (meta?.recap) {
175
- sections.push(`📝 **Recap**\n${meta.recap.slice(0, 60)}${meta.recap.length > 60 ? '...' : ''}`);
176
- }
177
- if (session.lastUserMessage) {
178
- sections.push(`✏️ **最近输入**\n${sessionPreviewLine(session.lastUserMessage, RESUME_PREVIEW_MAX)}`);
179
- }
180
- if (session.lastAssistantMessage) {
181
- sections.push(`📤 **最后输出**\n${sessionPreviewLine(session.lastAssistantMessage, RESUME_PREVIEW_MAX)}`);
182
- }
183
- const infoStr = sections.length > 0 ? '\n' + sections.join('\n──\n') : '';
184
- elements.push({ tag: 'hr' }, {
133
+ return sessionMeta;
134
+ }
135
+ function formatRelativeTime(mtimeMs, now) {
136
+ const diffSec = Math.floor((now - mtimeMs) / 1000);
137
+ if (diffSec < 60)
138
+ return '刚刚';
139
+ if (diffSec < 3600)
140
+ return `${Math.floor(diffSec / 60)} 分钟前`;
141
+ if (diffSec < 86400)
142
+ return `${Math.floor(diffSec / 3600)} 小时前`;
143
+ if (diffSec < 604800)
144
+ return `${Math.floor(diffSec / 86400)} 天前`;
145
+ return new Date(mtimeMs).toLocaleDateString('zh-CN');
146
+ }
147
+ function resumeTitleParts(session, meta) {
148
+ const summaryIsPlaceholder = RESUME_SUMMARY_PLACEHOLDERS.has(session.summary.trim());
149
+ const prefetchedTitle = meta?.displayTitle;
150
+ const rowTitleRaw = session.title?.trim() ?? '';
151
+ const rowTitle = rowTitleRaw && !RESUME_SUMMARY_PLACEHOLDERS.has(rowTitleRaw) ? rowTitleRaw : undefined;
152
+ const titleText = prefetchedTitle ?? rowTitle ?? (summaryIsPlaceholder ? '' : session.summary);
153
+ const titleLabel = prefetchedTitle
154
+ ? meta?.aiTitle
155
+ ? 'AI 标题'
156
+ : '最近输入'
157
+ : rowTitle
158
+ ? '标题'
159
+ : summaryIsPlaceholder
160
+ ? ''
161
+ : '会话摘要';
162
+ return { titleText, titleLabel };
163
+ }
164
+ function resumeInfoSections(session, meta, titleText, titleLabel) {
165
+ const sections = [];
166
+ if (titleText) {
167
+ sections.push(`🏷️ **${titleLabel}**\n${titleText.slice(0, 80)}${titleText.length > 80 ? '...' : ''}`);
168
+ }
169
+ if (meta?.recap) {
170
+ sections.push(`📝 **Recap**\n${meta.recap.slice(0, 60)}${meta.recap.length > 60 ? '...' : ''}`);
171
+ }
172
+ if (session.lastUserMessage) {
173
+ sections.push(`✏️ **最近输入**\n${sessionPreviewLine(session.lastUserMessage, RESUME_PREVIEW_MAX)}`);
174
+ }
175
+ if (session.lastAssistantMessage) {
176
+ sections.push(`📤 **最后输出**\n${sessionPreviewLine(session.lastAssistantMessage, RESUME_PREVIEW_MAX)}`);
177
+ }
178
+ return sections;
179
+ }
180
+ function resumeRowElements(session, opts) {
181
+ const isCurrent = opts.agentKind === opts.defaultAgent && session.sessionId === opts.currentId;
182
+ const meta = opts.meta.get(session.sessionId);
183
+ const { titleText, titleLabel } = resumeTitleParts(session, meta);
184
+ const sections = resumeInfoSections(session, meta, titleText, titleLabel);
185
+ const infoStr = sections.length > 0 ? '\n' + sections.join('\n──\n') : '';
186
+ return [
187
+ { tag: 'hr' },
188
+ {
185
189
  tag: 'div',
186
190
  text: {
187
191
  tag: 'lark_md',
188
- content: `**${session.sessionId}**${infoStr}\n⏱ ${formatRelativeTime(session.mtime)}${isCurrent ? ' •' : ''}`,
192
+ content: `**${session.sessionId}**${infoStr}\n⏱ ${formatRelativeTime(session.mtime, opts.now)}${isCurrent ? ' •' : ''}`,
189
193
  },
190
- }, {
194
+ },
195
+ {
191
196
  tag: 'column_set',
192
197
  columns: [
193
198
  {
194
199
  tag: 'column',
195
200
  width: 'auto',
196
201
  elements: [
197
- cardButton(isCurrent ? '✓ 当前会话' : '恢复此会话', { cmd: 'resume.use', sessionId: session.sessionId, agent: agentKind }, { type: isCurrent ? 'default' : 'primary', disabled: isCurrent }),
202
+ cardButton(isCurrent ? '✓ 当前会话' : '恢复此会话', { cmd: 'resume.use', sessionId: session.sessionId, agent: opts.agentKind }, { type: isCurrent ? 'default' : 'primary', disabled: isCurrent }),
198
203
  ],
199
204
  },
200
205
  ],
201
- });
206
+ },
207
+ ];
208
+ }
209
+ function resumeListElements(allSessions, cwd, total, pageSize, pageOffset, opts) {
210
+ const elements = [
211
+ { tag: 'div', text: { tag: 'lark_md', content: `📂 当前工作目录:\`${cwd}\`` } },
212
+ ];
213
+ for (const session of allSessions) {
214
+ elements.push(...resumeRowElements(session, opts));
202
215
  }
203
216
  if (total > pageSize) {
204
217
  const { totalPages, currentPage } = pageInfo(total, pageOffset, pageSize);
@@ -207,10 +220,40 @@ export function buildResumeCommand(args, ctx, offset, deps) {
207
220
  offset: pageOffset,
208
221
  pageSize,
209
222
  total,
210
- extra: { agent: agentKind, pageSize },
223
+ extra: { agent: opts.agentKind, pageSize },
211
224
  label: `第 ${currentPage}/${totalPages} 页 · 共 ${total} 个会话`,
212
225
  }));
213
226
  }
227
+ return elements;
228
+ }
229
+ function resumeList(deps, entry, agentKind, limit, offset, cwd) {
230
+ const pageSize = clampInt(limit ?? RESUME_PAGE_SIZE, 1, RESUME_PAGE_SIZE);
231
+ const reader = agentKind === deps.defaultAgent ? deps.defaultReader() : deps.readerFor(agentKind);
232
+ let page;
233
+ try {
234
+ page = loadResumePage(reader, cwd, pageSize, offset);
235
+ }
236
+ catch (err) {
237
+ const msg = err instanceof Error ? err.message : String(err);
238
+ return { text: `读取 ${agentDisplayName(agentKind)} session 列表失败: ${msg}` };
239
+ }
240
+ const { sessions: allSessions, total, pageOffset } = page;
241
+ if (allSessions.length === 0) {
242
+ const hint = agentKind === deps.defaultAgent
243
+ ? `\n提示:可使用 /resume <agent> 切换查看其他 Agent 的 session`
244
+ : '';
245
+ return {
246
+ text: `当前目录没有 ${agentDisplayName(agentKind)} session 记录\n${cwd}${hint}`,
247
+ };
248
+ }
249
+ const meta = prefetchResumeMeta(reader, allSessions, cwd);
250
+ const elements = resumeListElements(allSessions, cwd, total, pageSize, pageOffset, {
251
+ agentKind,
252
+ defaultAgent: deps.defaultAgent,
253
+ currentId: entry.sessions.get(agentKind) ?? '',
254
+ meta,
255
+ now: Date.now(),
256
+ });
214
257
  return {
215
258
  card: {
216
259
  schema: '2.0',
@@ -225,4 +268,17 @@ export function buildResumeCommand(args, ctx, offset, deps) {
225
268
  },
226
269
  };
227
270
  }
271
+ export function buildResumeCommand(args, ctx, offset, deps) {
272
+ const entry = deps.getSessionEntry(ctx.userId);
273
+ const cwd = entry?.cwd;
274
+ const { agentKind, limit, sessionIdArg } = parseResumeArgs(args, deps.defaultAgent);
275
+ if (sessionIdArg) {
276
+ if (!entry || !cwd)
277
+ return { text: '请先 /cd 设置工作目录' };
278
+ return resumeById(ctx, deps, agentKind, sessionIdArg, cwd);
279
+ }
280
+ if (!cwd)
281
+ return { text: '请先 /cd 设置工作目录' };
282
+ return resumeList(deps, entry, agentKind, limit, offset, cwd);
283
+ }
228
284
  //# sourceMappingURL=resume.js.map