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
@@ -6,4 +6,7 @@ export interface LsCommandOptions {
6
6
  rootDir?: string;
7
7
  q?: string;
8
8
  }
9
+ /** `...(filter ? { q: filter } : {})` 单源:q 恒为最后一个字段。
10
+ * ls / ws 两个命令共用(ws.ts 从本模块 import),避免同形实现两处维护。 */
11
+ export declare function withQuery(value: Record<string, unknown>, filter: string | undefined): Record<string, unknown>;
9
12
  export declare function buildLsCommand(args: string[], _ctx: CommandContext, options: LsCommandOptions): CommandResult;
@@ -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}` };