lark-remote 0.5.12 → 0.5.13

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 (95) hide show
  1. package/CHANGELOG.md +13 -2
  2. package/README.en.md +1 -14
  3. package/README.md +1 -14
  4. package/dist/bridge/approval-coordinator.d.ts +1 -0
  5. package/dist/bridge/approval-coordinator.js +12 -24
  6. package/dist/bridge/index.d.ts +7 -0
  7. package/dist/bridge/index.js +77 -227
  8. package/dist/bridge/run-approval.d.ts +15 -0
  9. package/dist/bridge/run-approval.js +41 -0
  10. package/dist/bridge/run-usage.d.ts +46 -0
  11. package/dist/bridge/run-usage.js +96 -0
  12. package/dist/bridge/session-writeback.d.ts +18 -0
  13. package/dist/bridge/session-writeback.js +36 -0
  14. package/dist/card/bash-card-session.js +2 -5
  15. package/dist/card/card-session.d.ts +2 -0
  16. package/dist/card/card-session.js +20 -22
  17. package/dist/card/card-shared.d.ts +4 -4
  18. package/dist/card/card-shared.js +3 -9
  19. package/dist/card/collapsible.js +3 -2
  20. package/dist/card/final-answer-overflow.d.ts +7 -0
  21. package/dist/card/final-answer-overflow.js +46 -0
  22. package/dist/card/run-card-session.js +2 -7
  23. package/dist/card/run-renderer.d.ts +14 -0
  24. package/dist/card/run-renderer.js +119 -1
  25. package/dist/card/run-state.d.ts +12 -1
  26. package/dist/card/run-state.js +73 -34
  27. package/dist/config/codex-config.js +4 -4
  28. package/dist/config/index.d.ts +4 -2
  29. package/dist/config/index.js +6 -4
  30. package/dist/inbound/turn-assembler.js +6 -0
  31. package/dist/index.js +15 -409
  32. package/dist/message-handlers.d.ts +26 -0
  33. package/dist/message-handlers.js +275 -0
  34. package/dist/router/card-action-payload.d.ts +1 -1
  35. package/dist/router/card-action-payload.js +1 -1
  36. package/dist/router/commands/ls.d.ts +9 -0
  37. package/dist/router/commands/ls.js +243 -0
  38. package/dist/router/commands/order.d.ts +12 -0
  39. package/dist/router/commands/order.js +178 -0
  40. package/dist/router/commands/resume.d.ts +47 -0
  41. package/dist/router/commands/resume.js +246 -0
  42. package/dist/router/commands/ws.d.ts +15 -0
  43. package/dist/router/commands/ws.js +189 -0
  44. package/dist/router/config/claude.js +3 -3
  45. package/dist/router/config/codex.js +2 -6
  46. package/dist/router/config/common/turn-idle.d.ts +2 -0
  47. package/dist/router/config/common/turn-idle.js +9 -0
  48. package/dist/router/config/kimi.js +2 -6
  49. package/dist/router/config/opencode.js +2 -6
  50. package/dist/router/index.d.ts +14 -22
  51. package/dist/router/index.js +70 -1152
  52. package/dist/router/paging.d.ts +13 -0
  53. package/dist/router/paging.js +20 -0
  54. package/dist/router/path-validation.d.ts +27 -0
  55. package/dist/router/path-validation.js +45 -0
  56. package/dist/runner/claude/runner.d.ts +3 -4
  57. package/dist/runner/claude/runner.js +4 -5
  58. package/dist/runner/claude/session.js +1 -1
  59. package/dist/runner/codex/app-server/translator.d.ts +2 -0
  60. package/dist/runner/codex/app-server/translator.js +65 -9
  61. package/dist/runner/common/acp/base-translator.d.ts +3 -8
  62. package/dist/runner/common/acp/base-translator.js +16 -7
  63. package/dist/runner/common/spawning-runner.js +10 -4
  64. package/dist/runner/common/turn-started.d.ts +2 -0
  65. package/dist/runner/common/turn-started.js +10 -0
  66. package/dist/runner/dsh/auth.js +2 -3
  67. package/dist/runner/dsh/client.d.ts +1 -6
  68. package/dist/runner/dsh/client.js +11 -165
  69. package/dist/runner/dsh/daemon.d.ts +0 -2
  70. package/dist/runner/dsh/daemon.js +1 -6
  71. package/dist/runner/dsh/errors.d.ts +5 -0
  72. package/dist/runner/dsh/errors.js +11 -0
  73. package/dist/runner/dsh/exit-cancel.d.ts +0 -2
  74. package/dist/runner/dsh/exit-cancel.js +1 -1
  75. package/dist/runner/dsh/home.d.ts +2 -0
  76. package/dist/runner/dsh/home.js +8 -0
  77. package/dist/runner/dsh/inflight.d.ts +0 -2
  78. package/dist/runner/dsh/inflight.js +1 -1
  79. package/dist/runner/dsh/mux-stream.d.ts +26 -0
  80. package/dist/runner/dsh/mux-stream.js +157 -0
  81. package/dist/runner/dsh/translator.d.ts +0 -10
  82. package/dist/runner/dsh/translator.js +1 -1
  83. package/dist/runner/index.d.ts +1 -1
  84. package/dist/runner/kimi/acp/translator.js +1 -12
  85. package/dist/runner/opencode/acp/translator.js +1 -12
  86. package/dist/runner/pi/rpc/translator.d.ts +2 -8
  87. package/dist/runner/pi/rpc/translator.js +2 -7
  88. package/dist/runner/registry.d.ts +0 -19
  89. package/dist/runner/registry.js +0 -32
  90. package/dist/runner/types.d.ts +17 -1
  91. package/dist/session/claude/session-index.js +4 -2
  92. package/dist/session/kimi/sessions.js +2 -2
  93. package/dist/session/opencode/sessions.js +2 -1
  94. package/dist/session/pi/sessions.js +3 -2
  95. package/package.json +1 -1
@@ -0,0 +1,178 @@
1
+ import { cardButton } from '../../card/card-shared.js';
2
+ import { paginationBar } from '../card-helpers.js';
3
+ import { pageSlice } from '../paging.js';
4
+ export const ORDER_PAGE_SIZE = 8;
5
+ export function buildOrderCommand(args, ctx, offset, deps) {
6
+ const sub = args[0]?.toLowerCase();
7
+ if (!sub || sub === 'list') {
8
+ deps.reload();
9
+ const allOrders = deps.list();
10
+ const elements = [];
11
+ if (allOrders.length === 0) {
12
+ elements.push({ tag: 'div', text: { tag: 'lark_md', content: '暂无指令' } });
13
+ }
14
+ else {
15
+ const { totalCount, totalPages, safeOffset, currentPage, pageItems: pageOrders, hasPagination, } = pageSlice(allOrders, offset, ORDER_PAGE_SIZE);
16
+ for (let i = 0; i < pageOrders.length; i++) {
17
+ const order = pageOrders[i];
18
+ const displayText = order.text.length > 100 ? order.text.slice(0, 97) + '...' : order.text;
19
+ elements.push({
20
+ tag: 'div',
21
+ text: {
22
+ tag: 'lark_md',
23
+ content: order.alias ? `\`$${order.alias}\` ${displayText}` : displayText,
24
+ },
25
+ });
26
+ const leftColumn = order.alias
27
+ ? [
28
+ cardButton(`$${order.alias}`, { cmd: 'order.aliasEdit', orderId: order.id, offset: safeOffset }, { type: 'default', size: 'small' }),
29
+ cardButton('✕', {
30
+ cmd: 'order.aliasRemove',
31
+ orderId: order.id,
32
+ aliasName: order.alias,
33
+ offset: safeOffset,
34
+ }, { type: 'default', size: 'small' }),
35
+ ]
36
+ : [
37
+ cardButton('+别名', { cmd: 'order.aliasEdit', orderId: order.id, offset: safeOffset }, { type: 'default', size: 'small' }),
38
+ ];
39
+ elements.push({
40
+ tag: 'column_set',
41
+ columns: [
42
+ { tag: 'column', width: 'weighted', weight: 1, elements: leftColumn },
43
+ {
44
+ tag: 'column',
45
+ width: 'weighted',
46
+ weight: 1,
47
+ vertical_align: 'center',
48
+ elements: [
49
+ {
50
+ tag: 'column_set',
51
+ columns: [
52
+ {
53
+ tag: 'column',
54
+ width: 'auto',
55
+ elements: [
56
+ cardButton('▶ 执行', { cmd: 'order.exec', orderId: order.id }, { type: 'primary', size: 'small' }),
57
+ ],
58
+ },
59
+ {
60
+ tag: 'column',
61
+ width: 'auto',
62
+ elements: [
63
+ cardButton('编辑', { cmd: 'order.textEdit', orderId: order.id, offset: safeOffset }, { type: 'default', size: 'small' }),
64
+ ],
65
+ },
66
+ {
67
+ tag: 'column',
68
+ width: 'auto',
69
+ elements: [
70
+ cardButton('删除', { cmd: 'order.delete', orderId: order.id, offset: safeOffset }, { type: 'danger', size: 'small' }),
71
+ ],
72
+ },
73
+ ],
74
+ },
75
+ ],
76
+ },
77
+ ],
78
+ });
79
+ if (i < pageOrders.length - 1)
80
+ elements.push({ tag: 'hr' });
81
+ }
82
+ if (hasPagination) {
83
+ elements.push({ tag: 'hr' }, ...paginationBar({
84
+ cmd: 'order.page',
85
+ offset: safeOffset,
86
+ pageSize: ORDER_PAGE_SIZE,
87
+ total: totalCount,
88
+ label: `**第 ${currentPage}/${totalPages} 页**(共 ${totalCount} 条)`,
89
+ }));
90
+ }
91
+ }
92
+ return {
93
+ card: {
94
+ schema: '2.0',
95
+ config: { wide_screen_mode: true, update_multi: true },
96
+ header: { title: { tag: 'plain_text', content: '📋 指令' }, template: 'turquoise' },
97
+ body: { elements },
98
+ },
99
+ };
100
+ }
101
+ if (sub === 'alias') {
102
+ const op = args[1];
103
+ if (op === 'add') {
104
+ const target = args[2];
105
+ const name = args[3];
106
+ if (!target || !name) {
107
+ return { text: '用法: /order alias add <orderId|序号> <别名名>' };
108
+ }
109
+ const order = deps.resolveTarget(target);
110
+ if (!order)
111
+ return { text: `指令不存在: ${target}` };
112
+ try {
113
+ deps.setAlias(order.id, name);
114
+ return { text: `✅ 已给指令绑定别名 $${name}` };
115
+ }
116
+ catch (err) {
117
+ return { text: `保存失败: ${err.message}` };
118
+ }
119
+ }
120
+ if (op === 'rm') {
121
+ const target = args[2];
122
+ if (!target)
123
+ return { text: '用法: /order alias rm <orderId|序号>' };
124
+ const order = deps.resolveTarget(target);
125
+ if (!order)
126
+ return { text: `指令不存在: ${target}` };
127
+ if (!order.alias)
128
+ return { text: '该指令没有别名' };
129
+ const aliasName = order.alias;
130
+ try {
131
+ deps.setAlias(order.id, undefined);
132
+ return { text: `✅ 已移除别名 $${aliasName}` };
133
+ }
134
+ catch (err) {
135
+ return { text: `保存失败: ${err.message}` };
136
+ }
137
+ }
138
+ if (!op || op === 'list')
139
+ return buildOrderCommand([], ctx, offset, deps);
140
+ return {
141
+ text: '用法: /order alias [add <orderId|序号> <别名名> | rm <orderId|序号> | list]',
142
+ };
143
+ }
144
+ if (sub === 'save') {
145
+ const text = args.slice(1).join(' ');
146
+ if (!text)
147
+ return { text: '用法: /order save <指令文本>' };
148
+ try {
149
+ deps.save(text);
150
+ return { text: `✅ 已保存指令: ${text.slice(0, 50)}${text.length > 50 ? '...' : ''}` };
151
+ }
152
+ catch (err) {
153
+ return { text: `保存失败: ${err.message}` };
154
+ }
155
+ }
156
+ if (sub === 'edit') {
157
+ const target = args[1];
158
+ const newText = args.slice(2).join(' ').trim();
159
+ if (!target || !newText) {
160
+ return { text: '用法: /order edit <orderId|序号> <新文本>' };
161
+ }
162
+ const order = deps.resolveTarget(target);
163
+ if (!order)
164
+ return { text: `指令不存在: ${target}` };
165
+ try {
166
+ deps.updateText(order.id, newText);
167
+ const preview = newText.length > 50 ? newText.slice(0, 50) + '...' : newText;
168
+ return { text: `✅ 已更新指令: ${preview}` };
169
+ }
170
+ catch (err) {
171
+ return { text: `保存失败: ${err.message}` };
172
+ }
173
+ }
174
+ return {
175
+ text: '用法: /order [list|save <text>|edit <orderId|序号> <新文本>|alias add <orderId|序号> <别名名>|alias rm <orderId|序号>]',
176
+ };
177
+ }
178
+ //# sourceMappingURL=order.js.map
@@ -0,0 +1,47 @@
1
+ import type { CommandContext, CommandResult } from '../../bridge/index.js';
2
+ import type { AgentKind, AgentSessionContentEvent, AgentSessionReader } from '../../runner/index.js';
3
+ import type { SessionDisplayUsage } from '../usage-format.js';
4
+ export declare const AUTO_RESUME_MAX_EVENTS = 5;
5
+ export declare const RESUME_PAGE_SIZE = 5;
6
+ interface ResumeSessionEntry {
7
+ cwd?: string;
8
+ sessions?: Map<string, string>;
9
+ }
10
+ interface SessionDisplay {
11
+ events: AgentSessionContentEvent[];
12
+ usage?: SessionDisplayUsage;
13
+ isActive: boolean;
14
+ aiTitle?: string;
15
+ recap?: string;
16
+ displayTitle?: string;
17
+ activeRunRunId?: string;
18
+ }
19
+ export interface ResumeCommandDeps {
20
+ defaultAgent: AgentKind;
21
+ validAgents: readonly AgentKind[];
22
+ getSessionEntry(userId: string): ResumeSessionEntry | undefined;
23
+ setSessionId(userId: string, agent: AgentKind, sessionId: string, cwd: string): void;
24
+ defaultReader(): AgentSessionReader;
25
+ readerFor(agent: AgentKind): AgentSessionReader;
26
+ dshPresetMismatch(reader: AgentSessionReader, sessionId: string, cwd: string): {
27
+ sessionPreset: string;
28
+ currentPreset: string;
29
+ } | undefined;
30
+ readDisplay(sessionId: string, cwd: string, opts: {
31
+ agentKind?: string;
32
+ maxEvents?: number;
33
+ }): SessionDisplay;
34
+ buildActions(opts: {
35
+ isActive: boolean;
36
+ activeRunRunId?: string;
37
+ cwd: string;
38
+ compact?: {
39
+ sessionId: string;
40
+ agentKind: string;
41
+ };
42
+ newSession: boolean;
43
+ }): object[];
44
+ canCompact(agent: string, cwd?: string): boolean;
45
+ }
46
+ export declare function buildResumeCommand(args: string[], ctx: CommandContext, offset: number, deps: ResumeCommandDeps): CommandResult;
47
+ export {};
@@ -0,0 +1,246 @@
1
+ import { agentDisplayName } from '../../card/card-shared.js';
2
+ import { buildSessionHistoryCard, paginationBar } from '../card-helpers.js';
3
+ import { pageInfo } from '../paging.js';
4
+ import { clampInt } from '../../common/clamp.js';
5
+ export const AUTO_RESUME_MAX_EVENTS = 5;
6
+ export const RESUME_PAGE_SIZE = 5;
7
+ const RESUME_CONTENT_PREFETCH = 5;
8
+ const RESUME_SUMMARY_PLACEHOLDERS = new Set(['', '(no user message)', '(无摘要)', 'New Session']);
9
+ const RESUME_PREVIEW_MAX = 60;
10
+ function sessionPreviewLine(text, max) {
11
+ const oneLine = text.replace(/\s+/g, ' ').trim();
12
+ return oneLine.length > max ? oneLine.slice(0, max - 1) + '…' : oneLine;
13
+ }
14
+ export function buildResumeCommand(args, ctx, offset, deps) {
15
+ const entry = deps.getSessionEntry(ctx.userId);
16
+ const cwd = entry?.cwd;
17
+ const validAgents = deps.validAgents;
18
+ let agentKind = deps.defaultAgent;
19
+ let limit;
20
+ let sessionIdArg;
21
+ if (args[0]) {
22
+ if (validAgents.includes(args[0])) {
23
+ agentKind = args[0];
24
+ if (args[1]) {
25
+ if (/^\d+$/.test(args[1]))
26
+ limit = parseInt(args[1], 10);
27
+ else
28
+ sessionIdArg = args[1];
29
+ }
30
+ }
31
+ else if (/^\d+$/.test(args[0])) {
32
+ limit = parseInt(args[0], 10);
33
+ }
34
+ else {
35
+ sessionIdArg = args[0];
36
+ }
37
+ }
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) {
48
+ return {
49
+ text: `未找到 session ${sessionIdArg}(当前目录: ${cwd})。请确认该 session 属于此目录,或先 /cd 到正确目录。`,
50
+ };
51
+ }
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
+ }
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);
97
+ let pageOffset = offset;
98
+ let pageResult;
99
+ try {
100
+ 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
+ }
120
+ const sessionMeta = new Map();
121
+ for (const session of allSessions.slice(0, RESUME_CONTENT_PREFETCH)) {
122
+ if (session.title || session.lastUserMessage || session.lastAssistantMessage)
123
+ continue;
124
+ const meta = reader.readSessionSummary
125
+ ? reader.readSessionSummary(session.sessionId, cwd)
126
+ : (() => {
127
+ const content = reader.readSessionContent(session.sessionId, cwd);
128
+ return {
129
+ aiTitle: content.aiTitle,
130
+ recap: content.recap,
131
+ displayTitle: content.displayTitle,
132
+ };
133
+ })();
134
+ sessionMeta.set(session.sessionId, meta);
135
+ }
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' }, {
185
+ tag: 'div',
186
+ text: {
187
+ tag: 'lark_md',
188
+ content: `**${session.sessionId}**${infoStr}\n⏱ ${formatRelativeTime(session.mtime)}${isCurrent ? ' •' : ''}`,
189
+ },
190
+ }, {
191
+ tag: 'column_set',
192
+ columns: [
193
+ {
194
+ tag: 'column',
195
+ width: 'auto',
196
+ elements: [
197
+ {
198
+ tag: 'button',
199
+ text: {
200
+ tag: 'plain_text',
201
+ content: isCurrent ? '✓ 当前会话' : '恢复此会话',
202
+ },
203
+ type: isCurrent ? 'default' : 'primary',
204
+ disabled: isCurrent,
205
+ behaviors: [
206
+ {
207
+ type: 'callback',
208
+ value: {
209
+ cmd: 'resume.use',
210
+ sessionId: session.sessionId,
211
+ agent: agentKind,
212
+ },
213
+ },
214
+ ],
215
+ },
216
+ ],
217
+ },
218
+ ],
219
+ });
220
+ }
221
+ if (total > pageSize) {
222
+ const { totalPages, currentPage } = pageInfo(total, pageOffset, pageSize);
223
+ elements.push({ tag: 'hr' }, ...paginationBar({
224
+ cmd: 'resume.page',
225
+ offset: pageOffset,
226
+ pageSize,
227
+ total,
228
+ extra: { agent: agentKind, pageSize },
229
+ label: `第 ${currentPage}/${totalPages} 页 · 共 ${total} 个会话`,
230
+ }));
231
+ }
232
+ return {
233
+ card: {
234
+ schema: '2.0',
235
+ config: { wide_screen_mode: true },
236
+ header: {
237
+ title: {
238
+ tag: 'plain_text',
239
+ content: `🔁 恢复历史会话 · ${agentDisplayName(agentKind)}`,
240
+ },
241
+ },
242
+ body: { elements },
243
+ },
244
+ };
245
+ }
246
+ //# sourceMappingURL=resume.js.map
@@ -0,0 +1,15 @@
1
+ import type { CommandContext, CommandResult } from '../../bridge/index.js';
2
+ import type { WorkspaceEntry } from '../../workspace/index.js';
3
+ export declare const WS_PAGE_SIZE = 5;
4
+ export interface WsCommandDeps {
5
+ getCwd(userId: string): string | undefined;
6
+ saveWorkspace(name: string, cwd: string): void;
7
+ getWorkspace(name: string): string | undefined;
8
+ hasWorkspace(name: string): boolean;
9
+ removeWorkspace(name: string): void;
10
+ touchWorkspace(name: string): void;
11
+ listWorkspaces(): WorkspaceEntry[];
12
+ getSortMode(userId: string): 'recent' | 'alpha';
13
+ switchCwd(userId: string, cwd: string, ctx: CommandContext): CommandResult;
14
+ }
15
+ export declare function buildWsCommand(args: string[], ctx: CommandContext, offset: number, q: string | undefined, deps: WsCommandDeps): CommandResult;
@@ -0,0 +1,189 @@
1
+ import { cardButton } from '../../card/card-shared.js';
2
+ import { paginationBar, searchBar } from '../card-helpers.js';
3
+ import { pageSlice } from '../paging.js';
4
+ import { resolveDirectory } from '../path-validation.js';
5
+ export const WS_PAGE_SIZE = 5;
6
+ export function buildWsCommand(args, ctx, offset, q, deps) {
7
+ const sub = args[0]?.toLowerCase();
8
+ switch (sub) {
9
+ case 'save': {
10
+ const name = args[1];
11
+ if (!name)
12
+ return { text: '用法: /ws save <name>' };
13
+ const cwd = deps.getCwd(ctx.userId);
14
+ if (!cwd)
15
+ return { text: '请先 /cd 设置工作目录' };
16
+ deps.saveWorkspace(name, cwd);
17
+ return { text: `已保存 workspace "${name}" -> ${cwd}` };
18
+ }
19
+ case 'use': {
20
+ const name = args[1];
21
+ if (!name)
22
+ return { text: '用法: /ws use <name>' };
23
+ const wsPath = deps.getWorkspace(name);
24
+ if (!wsPath)
25
+ return { text: `workspace "${name}" 不存在` };
26
+ const resolution = resolveDirectory(wsPath);
27
+ if (!resolution.ok) {
28
+ if (resolution.reason === 'missing')
29
+ return { text: `路径不存在: ${wsPath}` };
30
+ if (resolution.reason === 'not_directory')
31
+ return { text: `不是目录: ${wsPath}` };
32
+ return { text: `路径无效: ${wsPath}` };
33
+ }
34
+ deps.touchWorkspace(name);
35
+ return deps.switchCwd(ctx.userId, resolution.canonical, ctx);
36
+ }
37
+ case 'remove': {
38
+ const name = args[1];
39
+ if (!name)
40
+ return { text: '用法: /ws remove <name>' };
41
+ if (!deps.hasWorkspace(name))
42
+ return { text: `workspace "${name}" 不存在` };
43
+ deps.removeWorkspace(name);
44
+ return { text: `已删除 workspace "${name}"` };
45
+ }
46
+ case 'list':
47
+ default: {
48
+ const filter = q?.trim() || undefined;
49
+ const entries = deps.listWorkspaces();
50
+ const currentCwd = deps.getCwd(ctx.userId);
51
+ const sortMode = deps.getSortMode(ctx.userId);
52
+ if (sortMode === 'recent') {
53
+ entries.sort((a, b) => {
54
+ if (b.lastUsedAt !== a.lastUsedAt)
55
+ return b.lastUsedAt - a.lastUsedAt;
56
+ return a.name.localeCompare(b.name);
57
+ });
58
+ }
59
+ else {
60
+ entries.sort((a, b) => a.name.localeCompare(b.name));
61
+ }
62
+ const ql = filter?.toLowerCase();
63
+ const visible = ql
64
+ ? entries.filter((entry) => entry.name.toLowerCase().includes(ql) || entry.path.toLowerCase().includes(ql))
65
+ : entries;
66
+ const { totalCount, totalPages, safeOffset, currentPage, pageItems: pageEntries, hasPagination, } = pageSlice(visible, offset, WS_PAGE_SIZE);
67
+ const bodyElements = [
68
+ {
69
+ tag: 'div',
70
+ text: {
71
+ tag: 'lark_md',
72
+ content: `📂 当前工作目录:\`${currentCwd ?? '(未设置)'}\``,
73
+ },
74
+ },
75
+ { tag: 'hr' },
76
+ searchBar({
77
+ cmd: 'ws.filter',
78
+ placeholder: '搜索名称或路径,输完回车',
79
+ currentQuery: filter,
80
+ }),
81
+ ];
82
+ if (filter) {
83
+ bodyElements.push({
84
+ tag: 'div',
85
+ text: {
86
+ tag: 'lark_md',
87
+ content: `🔍 筛选:"${filter}" · 命中 ${totalCount} 个 workspace(点「清除筛选」取消)`,
88
+ },
89
+ });
90
+ }
91
+ if (entries.length === 0 && !filter) {
92
+ bodyElements.push({
93
+ tag: 'div',
94
+ text: { tag: 'lark_md', content: '没有保存的 workspace' },
95
+ });
96
+ }
97
+ else if (filter && visible.length === 0) {
98
+ bodyElements.push({
99
+ tag: 'div',
100
+ text: { tag: 'lark_md', content: `🔍 无匹配条目(关键词:"${filter}")` },
101
+ });
102
+ }
103
+ else {
104
+ const currentLabel = sortMode === 'recent' ? '🕐 最近使用' : '🔤 字母顺序';
105
+ const switchLabel = sortMode === 'recent' ? '🔤 字母顺序' : '🕐 最近使用';
106
+ bodyElements.push({ tag: 'hr' }, {
107
+ tag: 'column_set',
108
+ columns: [
109
+ {
110
+ tag: 'column',
111
+ width: 'weighted',
112
+ weight: 1,
113
+ vertical_align: 'center',
114
+ elements: [
115
+ {
116
+ tag: 'div',
117
+ text: { tag: 'lark_md', content: `排序:${currentLabel}` },
118
+ },
119
+ ],
120
+ },
121
+ {
122
+ tag: 'column',
123
+ width: 'auto',
124
+ elements: [
125
+ cardButton(`切换为 ${switchLabel}`, { cmd: 'ws.sort', ...(filter ? { q: filter } : {}) }, { type: 'default', size: 'small' }),
126
+ ],
127
+ },
128
+ ],
129
+ }, { tag: 'hr' });
130
+ for (const entry of pageEntries) {
131
+ bodyElements.push({
132
+ tag: 'div',
133
+ text: { tag: 'lark_md', content: `**${entry.name}** → \`${entry.path}\`` },
134
+ }, {
135
+ tag: 'column_set',
136
+ columns: [
137
+ {
138
+ tag: 'column',
139
+ width: 'auto',
140
+ elements: [
141
+ cardButton('切换', {
142
+ cmd: 'ws.use',
143
+ name: entry.name,
144
+ offset: safeOffset,
145
+ ...(filter ? { q: filter } : {}),
146
+ }, { type: 'primary', size: 'small' }),
147
+ ],
148
+ },
149
+ {
150
+ tag: 'column',
151
+ width: 'auto',
152
+ elements: [
153
+ cardButton('删除', {
154
+ cmd: 'ws.remove',
155
+ name: entry.name,
156
+ offset: safeOffset,
157
+ ...(filter ? { q: filter } : {}),
158
+ }, { type: 'danger', size: 'small' }),
159
+ ],
160
+ },
161
+ ],
162
+ }, { tag: 'hr' });
163
+ }
164
+ bodyElements.pop();
165
+ if (hasPagination) {
166
+ bodyElements.push({ tag: 'hr' }, ...paginationBar({
167
+ cmd: 'ws.page',
168
+ offset: safeOffset,
169
+ pageSize: WS_PAGE_SIZE,
170
+ total: totalCount,
171
+ extra: filter ? { q: filter } : {},
172
+ label: `**${currentPage}/${totalPages}**(${totalCount})`,
173
+ prevText: '⬅',
174
+ nextText: '➡',
175
+ }));
176
+ }
177
+ }
178
+ return {
179
+ card: {
180
+ schema: '2.0',
181
+ config: { wide_screen_mode: true, update_multi: true },
182
+ header: { title: { tag: 'plain_text', content: 'Workspaces' } },
183
+ body: { elements: bodyElements },
184
+ },
185
+ };
186
+ }
187
+ }
188
+ }
189
+ //# sourceMappingURL=ws.js.map