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
@@ -52,189 +52,270 @@ function buildFileCard(filePath) {
52
52
  },
53
53
  };
54
54
  }
55
- export function buildLsCommand(args, _ctx, options) {
56
- const { cwd, offset = 0, rootDir } = options;
57
- if (!cwd)
58
- return { text: '请先使用 /cd <path> 设置工作目录' };
59
- const filter = options.q?.trim() || undefined;
60
- let targetDir = cwd;
61
- if (args[0]) {
62
- const arg = args[0].startsWith('~') ? path.join(os.homedir(), args[0].slice(1)) : args[0];
63
- const potentialDir = path.resolve(cwd, arg);
64
- const kind = inspectPath(potentialDir);
65
- if (kind === 'missing' || kind === 'unreadable') {
66
- return { text: `ls: ${args[0]}: No such file or directory` };
67
- }
68
- if (kind === 'file')
69
- return buildFileCard(potentialDir);
70
- targetDir = potentialDir;
71
- }
72
- const browseRoot = rootDir ? path.resolve(cwd, rootDir) : targetDir;
73
- try {
74
- const entries = fs.readdirSync(targetDir, { withFileTypes: true });
75
- const dirs = [];
76
- const files = [];
77
- for (const entry of entries) {
78
- let isDir = entry.isDirectory();
79
- let size;
80
- if (!isDir) {
81
- const resolution = statPath(path.join(targetDir, entry.name));
82
- if (resolution.ok) {
83
- isDir = resolution.stat.isDirectory();
84
- if (!isDir)
85
- size = resolution.stat.size;
86
- }
55
+ /** `...(filter ? { q: filter } : {})` 单源:q 恒为最后一个字段。
56
+ * ls / ws 两个命令共用(ws.ts 从本模块 import),避免同形实现两处维护。 */
57
+ export function withQuery(value, filter) {
58
+ return filter ? { ...value, q: filter } : { ...value };
59
+ }
60
+ function lsCompareNames(a, b) {
61
+ return a.localeCompare(b);
62
+ }
63
+ function lsCompareItemNames(a, b) {
64
+ return a.name.localeCompare(b.name);
65
+ }
66
+ /** readdirSync + statPath 分类,返回已排序的 dirs / files。 */
67
+ function lsReadEntries(targetDir) {
68
+ const entries = fs.readdirSync(targetDir, { withFileTypes: true });
69
+ const dirs = [];
70
+ const files = [];
71
+ for (const entry of entries) {
72
+ let isDir = entry.isDirectory();
73
+ let size;
74
+ if (!isDir) {
75
+ const resolution = statPath(path.join(targetDir, entry.name));
76
+ if (resolution.ok) {
77
+ isDir = resolution.stat.isDirectory();
78
+ if (!isDir)
79
+ size = resolution.stat.size;
87
80
  }
88
- if (isDir)
89
- dirs.push(entry.name);
90
- else
91
- files.push({ name: entry.name, size });
92
81
  }
93
- dirs.sort((a, b) => a.localeCompare(b));
94
- files.sort((a, b) => a.name.localeCompare(b.name));
95
- const allItems = [
96
- ...dirs.map((name) => ({ name, isDir: true })),
97
- ...files.map((file) => ({ name: file.name, isDir: false, size: file.size })),
98
- ];
99
- const ql = filter?.toLowerCase();
100
- const visibleItems = ql
101
- ? allItems.filter((item) => item.name.toLowerCase().includes(ql))
102
- : allItems;
103
- const visibleDirCount = visibleItems.reduce((count, item) => count + (item.isDir ? 1 : 0), 0);
104
- const visibleFileCount = visibleItems.length - visibleDirCount;
105
- const { totalCount, totalPages, safeOffset, currentPage, pageItems, hasPagination } = pageSlice(visibleItems, offset, LS_PAGE_SIZE);
106
- const parentDir = path.dirname(targetDir);
107
- const headerElements = [];
108
- if (parentDir !== targetDir) {
109
- headerElements.push(cardButton('上级', { cmd: 'ls.browse', path: parentDir, root: browseRoot }, { type: 'default', size: 'small' }));
110
- }
111
- if (targetDir !== browseRoot) {
112
- headerElements.push(cardButton('返回', { cmd: 'ls.browse', path: browseRoot, root: browseRoot }, { type: 'default', size: 'small' }));
113
- }
114
- headerElements.push(cardButton('刷新', {
115
- cmd: 'ls.refresh',
116
- path: targetDir,
117
- offset: safeOffset,
118
- root: browseRoot,
119
- ...(filter ? { q: filter } : {}),
120
- }, { type: 'default', size: 'small' }));
121
- if (targetDir !== cwd) {
122
- headerElements.push(cardButton('切换', { cmd: 'ls.switch', path: targetDir, root: browseRoot }, { type: 'primary', size: 'small' }));
123
- }
124
- const dirButton = (name) => cardButton(`📁 ${name}`, { cmd: 'ls.browse', path: path.join(targetDir, name), root: browseRoot }, { type: 'default' });
125
- const fileButton = (name, size) => cardButton(`📄 ${name}${size !== undefined ? ` (${formatBytes(size)})` : ''}`, { cmd: 'ls.file', path: path.join(targetDir, name) }, { type: 'default', size: 'small' });
126
- const elements = [
127
- {
128
- tag: 'div',
129
- text: {
130
- tag: 'lark_md',
131
- content: `\`${targetDir}\`\n共 ${visibleDirCount} 目录, ${visibleFileCount} 文件`,
132
- },
133
- },
134
- {
82
+ if (isDir)
83
+ dirs.push(entry.name);
84
+ else
85
+ files.push({ name: entry.name, size });
86
+ }
87
+ dirs.sort(lsCompareNames);
88
+ files.sort(lsCompareItemNames);
89
+ return { dirs, files };
90
+ }
91
+ function lsBuildItems(dirs, files) {
92
+ const allItems = [];
93
+ for (const name of dirs)
94
+ allItems.push({ name, isDir: true });
95
+ for (const file of files)
96
+ allItems.push({ name: file.name, isDir: false, size: file.size });
97
+ return allItems;
98
+ }
99
+ function lsFilterItems(allItems, ql) {
100
+ if (!ql)
101
+ return allItems;
102
+ const visible = [];
103
+ for (const item of allItems) {
104
+ if (item.name.toLowerCase().includes(ql))
105
+ visible.push(item);
106
+ }
107
+ return visible;
108
+ }
109
+ function lsCountDirs(items) {
110
+ let count = 0;
111
+ for (const item of items) {
112
+ if (item.isDir)
113
+ count += 1;
114
+ }
115
+ return count;
116
+ }
117
+ function lsPartitionPage(pageItems) {
118
+ const pageDirs = [];
119
+ const pageFiles = [];
120
+ for (const item of pageItems) {
121
+ if (item.isDir)
122
+ pageDirs.push(item);
123
+ else
124
+ pageFiles.push(item);
125
+ }
126
+ return { pageDirs, pageFiles };
127
+ }
128
+ function lsColumns(elements) {
129
+ const columns = [];
130
+ for (const element of elements) {
131
+ columns.push({ tag: 'column', width: 'auto', elements: [element] });
132
+ }
133
+ return columns;
134
+ }
135
+ function lsDirButton(name, targetDir, browseRoot) {
136
+ return cardButton(`📁 ${name}`, { cmd: 'ls.browse', path: path.join(targetDir, name), root: browseRoot }, { type: 'default' });
137
+ }
138
+ function lsFileButton(name, size, targetDir) {
139
+ return cardButton(`📄 ${name}${size !== undefined ? ` (${formatBytes(size)})` : ''}`, { cmd: 'ls.file', path: path.join(targetDir, name) }, { type: 'default', size: 'small' });
140
+ }
141
+ function lsHeaderButtons(targetDir, cwd, browseRoot, safeOffset, filter) {
142
+ const headerElements = [];
143
+ const parentDir = path.dirname(targetDir);
144
+ if (parentDir !== targetDir) {
145
+ headerElements.push(cardButton('上级', { cmd: 'ls.browse', path: parentDir, root: browseRoot }, { type: 'default', size: 'small' }));
146
+ }
147
+ if (targetDir !== browseRoot) {
148
+ headerElements.push(cardButton('返回', { cmd: 'ls.browse', path: browseRoot, root: browseRoot }, { type: 'default', size: 'small' }));
149
+ }
150
+ headerElements.push(cardButton('刷新', withQuery({ cmd: 'ls.refresh', path: targetDir, offset: safeOffset, root: browseRoot }, filter), { type: 'default', size: 'small' }));
151
+ if (targetDir !== cwd) {
152
+ headerElements.push(cardButton('切换', { cmd: 'ls.switch', path: targetDir, root: browseRoot }, { type: 'primary', size: 'small' }));
153
+ }
154
+ return headerElements;
155
+ }
156
+ function lsDirSection(pageDirs, visibleDirCount, currentPage, targetDir, browseRoot) {
157
+ const section = [
158
+ { tag: 'div', text: { tag: 'lark_md', content: `**📂 目录 (${visibleDirCount})**` } },
159
+ ];
160
+ if (pageDirs.length > 0) {
161
+ for (const dir of pageDirs) {
162
+ section.push({
135
163
  tag: 'column_set',
136
- columns: headerElements.map((element) => ({
137
- tag: 'column',
138
- width: 'auto',
139
- elements: [element],
140
- })),
141
- },
142
- { tag: 'hr' },
143
- searchBar({
144
- cmd: 'ls.filter',
145
- placeholder: '搜索本层目录/文件名,输完回车',
146
- currentQuery: filter,
147
- extra: { path: targetDir, root: browseRoot },
148
- }),
149
- ];
150
- if (filter) {
151
- elements.push({
152
- tag: 'div',
153
- text: {
154
- tag: 'lark_md',
155
- content: `🔍 筛选:"${filter}" · 命中 ${totalCount} 项(点「清除筛选」取消)`,
156
- },
157
- });
158
- }
159
- const pageDirs = pageItems.filter((item) => item.isDir);
160
- const pageFiles = pageItems.filter((item) => !item.isDir);
161
- if (filter && visibleItems.length === 0) {
162
- elements.push({
163
- tag: 'div',
164
- text: { tag: 'lark_md', content: `🔍 无匹配条目(关键词:"${filter}")` },
164
+ flex_mode: 'none',
165
+ columns: [
166
+ {
167
+ tag: 'column',
168
+ width: 'auto',
169
+ elements: [lsDirButton(dir.name, targetDir, browseRoot)],
170
+ },
171
+ ],
165
172
  });
166
173
  }
167
- else {
168
- elements.push({
169
- tag: 'div',
170
- text: { tag: 'lark_md', content: `**📂 目录 (${visibleDirCount})**` },
171
- });
172
- if (pageDirs.length > 0) {
173
- for (const dir of pageDirs) {
174
- elements.push({
175
- tag: 'column_set',
176
- flex_mode: 'none',
177
- columns: [{ tag: 'column', width: 'auto', elements: [dirButton(dir.name)] }],
178
- });
179
- }
180
- }
181
- else if (visibleDirCount === 0) {
182
- elements.push({ tag: 'div', text: { tag: 'lark_md', content: '无子目录' } });
183
- }
184
- else {
185
- elements.push({
186
- tag: 'div',
187
- text: { tag: 'lark_md', content: `(第 ${currentPage} 页无目录)` },
188
- });
189
- }
190
- elements.push({ tag: 'hr' });
191
- elements.push({
192
- tag: 'div',
193
- text: { tag: 'lark_md', content: `**📄 文件 (${visibleFileCount})**` },
174
+ }
175
+ else if (visibleDirCount === 0) {
176
+ section.push({ tag: 'div', text: { tag: 'lark_md', content: '无子目录' } });
177
+ }
178
+ else {
179
+ section.push({
180
+ tag: 'div',
181
+ text: { tag: 'lark_md', content: `(第 ${currentPage} 页无目录)` },
182
+ });
183
+ }
184
+ return section;
185
+ }
186
+ function lsFileSection(pageFiles, visibleFileCount, currentPage, targetDir) {
187
+ const section = [
188
+ { tag: 'div', text: { tag: 'lark_md', content: `**📄 文件 (${visibleFileCount})**` } },
189
+ ];
190
+ if (pageFiles.length > 0) {
191
+ for (const file of pageFiles) {
192
+ section.push({
193
+ tag: 'column_set',
194
+ flex_mode: 'none',
195
+ columns: [
196
+ {
197
+ tag: 'column',
198
+ width: 'auto',
199
+ elements: [lsFileButton(file.name, file.size, targetDir)],
200
+ },
201
+ ],
194
202
  });
195
- if (pageFiles.length > 0) {
196
- for (const file of pageFiles) {
197
- elements.push({
198
- tag: 'column_set',
199
- flex_mode: 'none',
200
- columns: [
201
- { tag: 'column', width: 'auto', elements: [fileButton(file.name, file.size)] },
202
- ],
203
- });
204
- }
205
- }
206
- else if (visibleFileCount === 0) {
207
- elements.push({ tag: 'div', text: { tag: 'lark_md', content: '无文件' } });
208
- }
209
- else {
210
- elements.push({
211
- tag: 'div',
212
- text: { tag: 'lark_md', content: `(第 ${currentPage} 页无文件)` },
213
- });
214
- }
215
203
  }
216
- if (hasPagination) {
217
- elements.push({ tag: 'hr' });
218
- elements.push(...paginationBar({
219
- cmd: 'ls.page',
220
- offset: safeOffset,
221
- pageSize: LS_PAGE_SIZE,
222
- total: totalCount,
223
- extra: { path: targetDir, root: browseRoot, ...(filter ? { q: filter } : {}) },
224
- label: `**第 ${currentPage}/${totalPages} 页**(共 ${totalCount} 项)`,
225
- }));
226
- }
227
- return {
228
- card: {
229
- schema: '2.0',
230
- config: { wide_screen_mode: true, update_multi: true },
231
- header: {
232
- title: { tag: 'plain_text', content: `📁 ${path.basename(targetDir)}` },
233
- template: 'blue',
234
- },
235
- body: { elements },
204
+ }
205
+ else if (visibleFileCount === 0) {
206
+ section.push({ tag: 'div', text: { tag: 'lark_md', content: '无文件' } });
207
+ }
208
+ else {
209
+ section.push({
210
+ tag: 'div',
211
+ text: { tag: 'lark_md', content: `(第 ${currentPage} 页无文件)` },
212
+ });
213
+ }
214
+ return section;
215
+ }
216
+ function lsPagination(totalCount, totalPages, safeOffset, currentPage, targetDir, browseRoot, filter) {
217
+ return [
218
+ { tag: 'hr' },
219
+ ...paginationBar({
220
+ cmd: 'ls.page',
221
+ offset: safeOffset,
222
+ pageSize: LS_PAGE_SIZE,
223
+ total: totalCount,
224
+ extra: withQuery({ path: targetDir, root: browseRoot }, filter),
225
+ label: `**第 ${currentPage}/${totalPages} 页**(共 ${totalCount} 项)`,
226
+ }),
227
+ ];
228
+ }
229
+ /** 目录卡片构造(原 buildLsCommand 的 try 块)。 */
230
+ function lsCard(opts) {
231
+ const { targetDir, cwd, browseRoot, offset, filter } = opts;
232
+ const { dirs, files } = lsReadEntries(targetDir);
233
+ const allItems = lsBuildItems(dirs, files);
234
+ const ql = filter?.toLowerCase();
235
+ const visibleItems = lsFilterItems(allItems, ql);
236
+ const visibleDirCount = lsCountDirs(visibleItems);
237
+ const visibleFileCount = visibleItems.length - visibleDirCount;
238
+ const { totalCount, totalPages, safeOffset, currentPage, pageItems, hasPagination } = pageSlice(visibleItems, offset, LS_PAGE_SIZE);
239
+ const { pageDirs, pageFiles } = lsPartitionPage(pageItems);
240
+ const headerElements = lsHeaderButtons(targetDir, cwd, browseRoot, safeOffset, filter);
241
+ const elements = [
242
+ {
243
+ tag: 'div',
244
+ text: {
245
+ tag: 'lark_md',
246
+ content: `\`${targetDir}\`\n共 ${visibleDirCount} 目录, ${visibleFileCount} 文件`,
247
+ },
248
+ },
249
+ { tag: 'column_set', columns: lsColumns(headerElements) },
250
+ { tag: 'hr' },
251
+ searchBar({
252
+ cmd: 'ls.filter',
253
+ placeholder: '搜索本层目录/文件名,输完回车',
254
+ currentQuery: filter,
255
+ extra: { path: targetDir, root: browseRoot },
256
+ }),
257
+ ];
258
+ if (filter) {
259
+ elements.push({
260
+ tag: 'div',
261
+ text: {
262
+ tag: 'lark_md',
263
+ content: `🔍 筛选:"${filter}" · 命中 ${totalCount} 项(点「清除筛选」取消)`,
236
264
  },
237
- };
265
+ });
266
+ }
267
+ if (filter && visibleItems.length === 0) {
268
+ elements.push({
269
+ tag: 'div',
270
+ text: { tag: 'lark_md', content: `🔍 无匹配条目(关键词:"${filter}")` },
271
+ });
272
+ }
273
+ else {
274
+ elements.push(...lsDirSection(pageDirs, visibleDirCount, currentPage, targetDir, browseRoot));
275
+ elements.push({ tag: 'hr' });
276
+ elements.push(...lsFileSection(pageFiles, visibleFileCount, currentPage, targetDir));
277
+ }
278
+ if (hasPagination) {
279
+ elements.push(...lsPagination(totalCount, totalPages, safeOffset, currentPage, targetDir, browseRoot, filter));
280
+ }
281
+ return {
282
+ card: {
283
+ schema: '2.0',
284
+ config: { wide_screen_mode: true, update_multi: true },
285
+ header: {
286
+ title: { tag: 'plain_text', content: `📁 ${path.basename(targetDir)}` },
287
+ template: 'blue',
288
+ },
289
+ body: { elements },
290
+ },
291
+ };
292
+ }
293
+ /** 解析 /ls 的第一个参数(~ 展开、存在性、文件卡)。 */
294
+ function lsResolveTarget(args, cwd) {
295
+ if (!args[0])
296
+ return { targetDir: cwd };
297
+ const arg = args[0].startsWith('~') ? path.join(os.homedir(), args[0].slice(1)) : args[0];
298
+ const potentialDir = path.resolve(cwd, arg);
299
+ const kind = inspectPath(potentialDir);
300
+ if (kind === 'missing' || kind === 'unreadable') {
301
+ return { result: { text: `ls: ${args[0]}: No such file or directory` } };
302
+ }
303
+ if (kind === 'file')
304
+ return { result: buildFileCard(potentialDir) };
305
+ return { targetDir: potentialDir };
306
+ }
307
+ export function buildLsCommand(args, _ctx, options) {
308
+ const { cwd, offset = 0, rootDir } = options;
309
+ if (!cwd)
310
+ return { text: '请先使用 /cd <path> 设置工作目录' };
311
+ const filter = options.q?.trim() || undefined;
312
+ const resolved = lsResolveTarget(args, cwd);
313
+ if ('result' in resolved)
314
+ return resolved.result;
315
+ const targetDir = resolved.targetDir;
316
+ const browseRoot = rootDir ? path.resolve(cwd, rootDir) : targetDir;
317
+ try {
318
+ return lsCard({ targetDir, cwd, browseRoot, offset, filter });
238
319
  }
239
320
  catch (err) {
240
321
  return { text: `读取目录失败: ${err.message}` };