lark-remote 0.1.8 → 0.1.10

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.
@@ -6,7 +6,6 @@ import { getConfigDir, getConfigValue, setConfigValue, setConfigValues, mapAgent
6
6
  import { syncAgentChoices } from '../runner/index.js';
7
7
  import { WorkspaceStore } from '../workspace/index.js';
8
8
  import { OrderStore } from '../order/index.js';
9
- import { AliasStore } from '../order/alias-store.js';
10
9
  import { resolveAlias } from '../order/alias-resolve.js';
11
10
  import { getLogger } from '../logger/index.js';
12
11
  import { activeRunUsage, clampInt } from './utils.js';
@@ -31,8 +30,11 @@ const ACTIVE_PAGE_SIZE = 20;
31
30
  * 2026-08-13 实测:每行 2 个元素(div + column_set,行间 hr),20 行 + 分页栏
32
31
  * = 61 个 body 元素,触发飞书 ErrCode 11310 "element exceeds the limit"
33
32
  * (21+ 条指令时第 1 页必炸)。改为 15 行:3*15 - 1 + 2 = 46 个元素,留足余量。
33
+ * 2026-08-19 别名并卡后每行变 3 个元素(文本 div + 操作 column_set + hr),
34
+ * 从 15 降为 8:3*8 - 1 + 2 = 25 个元素,仍在余量内,同时保证手机窄屏
35
+ * 每条指令的操作控件(执行/别名/删除)不被挤压。
34
36
  */
35
- const ORDER_PAGE_SIZE = 15;
37
+ const ORDER_PAGE_SIZE = 8;
36
38
  /**
37
39
  * /ws 列表页大小。从 15 降为 5:头部 2 + 5 行 × 3(div + column_set + hr)+
38
40
  * 底栏排序 1 + 分页栏 2 ≈ 20 个 body 元素,远低于单卡 60 元素上限(ErrCode 11310),
@@ -110,6 +112,11 @@ export function isImmediateAction(cmd) {
110
112
  cmd === 'queue.input' ||
111
113
  cmd === 'order.delete' ||
112
114
  cmd === 'order.page' ||
115
+ cmd === 'order.aliasEdit' ||
116
+ cmd === 'order.aliasInput' ||
117
+ cmd === 'order.aliasRemove' ||
118
+ cmd === 'order.textEdit' ||
119
+ cmd === 'order.textInput' ||
113
120
  cmd === 'config.toggle' ||
114
121
  cmd === 'config.set' ||
115
122
  cmd === 'config.input' ||
@@ -122,7 +129,8 @@ export function isImmediateAction(cmd) {
122
129
  cmd === 'approval.answer' ||
123
130
  cmd === 'approval.answerSubmit' ||
124
131
  cmd === 'approval.answerCustom' ||
125
- cmd === 'approval.answerNote');
132
+ cmd === 'approval.answerNote' ||
133
+ cmd === 'approval.planFeedback');
126
134
  }
127
135
  export class CommandRouter {
128
136
  /** Valid agent kinds — single source of truth for resume.use / resume.page / cmdResume. */
@@ -136,7 +144,6 @@ export class CommandRouter {
136
144
  configPath;
137
145
  workspaceStore;
138
146
  orderStore;
139
- aliasStore;
140
147
  exitHandler;
141
148
  pendingExit = false;
142
149
  /**
@@ -191,7 +198,6 @@ export class CommandRouter {
191
198
  this.configPath = opts.configPath;
192
199
  this.workspaceStore = new WorkspaceStore(opts.workspacePath);
193
200
  this.orderStore = new OrderStore(opts.ordersPath);
194
- this.aliasStore = new AliasStore(opts.aliasesPath);
195
201
  this.exitHandler = opts.exitHandler ?? (() => process.exit(0));
196
202
  this.restartSpawner = opts.restartSpawner;
197
203
  // idle watchdog 窗口从 config.idle.watchdogMinutes 读取
@@ -255,9 +261,14 @@ export class CommandRouter {
255
261
  /**
256
262
  * 一次别名展开:bridge 收到用户消息后、命令分发前调用。
257
263
  * 仅 `$name` 开头的消息可能被展开;未知别名 / 非 `$` 消息原样返回。
264
+ * 别名来自 order 的 `alias` 字段(1 条指令 = 1 个别名),展开文本即指令文本。
258
265
  */
259
266
  expandAliasMessage(message) {
260
- return resolveAlias(message, this.aliasStore.list()) ?? message;
267
+ const entries = this.orderStore
268
+ .get()
269
+ .filter((o) => typeof o.alias === 'string')
270
+ .map((o) => ({ name: o.alias, text: o.text }));
271
+ return resolveAlias(message, entries) ?? message;
261
272
  }
262
273
  /**
263
274
  * Handle a CardKit card button click (§6.2).
@@ -352,6 +363,16 @@ export class CommandRouter {
352
363
  return await this.handleOrderDelete(value, ctx);
353
364
  case 'order.page':
354
365
  return await this.handleOrderPage(value, ctx);
366
+ case 'order.aliasEdit':
367
+ return await this.handleOrderAliasEdit(value, ctx);
368
+ case 'order.aliasInput':
369
+ return await this.handleOrderAliasInput(value, ctx);
370
+ case 'order.aliasRemove':
371
+ return await this.handleOrderAliasRemove(value, ctx);
372
+ case 'order.textEdit':
373
+ return await this.handleOrderTextEdit(value, ctx);
374
+ case 'order.textInput':
375
+ return await this.handleOrderTextInput(value, ctx);
355
376
  case 'config.toggle':
356
377
  case 'config.set':
357
378
  case 'config.input':
@@ -367,6 +388,7 @@ export class CommandRouter {
367
388
  case 'approval.answerSubmit':
368
389
  case 'approval.answerCustom':
369
390
  case 'approval.answerNote':
391
+ case 'approval.planFeedback':
370
392
  return this.handleApprovalAction(value, ctx);
371
393
  case 'codex.compact':
372
394
  await this.bridge.handleCodexCompact(value, ctx);
@@ -542,6 +564,24 @@ export class CommandRouter {
542
564
  return this.answerFailureToast(msg);
543
565
  }
544
566
  }
567
+ if (value.cmd === 'approval.planFeedback') {
568
+ if (!runId || requestId === undefined || !nonce || !value.inputValue) {
569
+ return { toast: { type: 'error', content: '缺少修改意见参数' } };
570
+ }
571
+ try {
572
+ await this.bridge.handleApprovalPlanFeedback({
573
+ runId,
574
+ requestId,
575
+ text: value.inputValue,
576
+ nonce,
577
+ });
578
+ return { toast: { type: 'success', content: '修改意见已保存' } };
579
+ }
580
+ catch (err) {
581
+ const msg = err instanceof Error ? err.message : String(err);
582
+ return { toast: { type: 'error', content: `修改意见保存失败:${msg}` } };
583
+ }
584
+ }
545
585
  return { toast: { type: 'error', content: '未知的审批操作' } };
546
586
  }
547
587
  /**
@@ -1157,6 +1197,252 @@ export class CommandRouter {
1157
1197
  await this.bridge.updateCardInPlace(result.card, ctx);
1158
1198
  return { toast: { type: 'success', content: '' } };
1159
1199
  }
1200
+ /**
1201
+ * Handle order.aliasEdit: 展示别名编辑卡(input + ✓ 提交图标)。
1202
+ * 有别名时预填;提交 `order.aliasInput` 处理(含校验与唯一性)。
1203
+ */
1204
+ async handleOrderAliasEdit(value, ctx) {
1205
+ const orderId = value.orderId;
1206
+ const offset = Math.max(0, Math.trunc(Number(value.offset) || 0));
1207
+ if (!orderId) {
1208
+ return { toast: { type: 'error', content: '卡片 payload 缺少必要信息' } };
1209
+ }
1210
+ this.orderStore.reload();
1211
+ const order = this.orderStore.get().find((o) => o.id === orderId);
1212
+ if (!order) {
1213
+ return { toast: { type: 'error', content: '指令不存在或已被删除' } };
1214
+ }
1215
+ const displayText = order.text.length > 100 ? order.text.slice(0, 97) + '...' : order.text;
1216
+ // CardKit 2.0 input 自带 ✓ 提交图标(input_value 经 raw 回传,红线)。
1217
+ // 不用 form 容器(触发 300123 无 submit button / 200621 嵌套 column)。
1218
+ const editCard = {
1219
+ schema: '2.0',
1220
+ config: { wide_screen_mode: true },
1221
+ header: {
1222
+ template: 'blue',
1223
+ title: { tag: 'plain_text', content: '✏️ 给指令起别名' },
1224
+ },
1225
+ body: {
1226
+ elements: [
1227
+ {
1228
+ tag: 'div',
1229
+ text: { tag: 'lark_md', content: `**当前指令:**\n\`${displayText}\`` },
1230
+ },
1231
+ { tag: 'hr' },
1232
+ {
1233
+ tag: 'div',
1234
+ text: {
1235
+ tag: 'lark_md',
1236
+ content: '💡 输入别名名后点击右侧 ✓ 提交;留空提交 = 删除该别名',
1237
+ },
1238
+ },
1239
+ {
1240
+ tag: 'column_set',
1241
+ columns: [
1242
+ {
1243
+ tag: 'column',
1244
+ width: 'weighted',
1245
+ weight: 3,
1246
+ elements: [
1247
+ {
1248
+ tag: 'input',
1249
+ name: 'aliasName',
1250
+ placeholder: { tag: 'plain_text', content: '输入 $别名名(如 all)...' },
1251
+ default_value: order.alias ?? '',
1252
+ behaviors: [
1253
+ {
1254
+ type: 'callback',
1255
+ value: { cmd: 'order.aliasInput', orderId, offset },
1256
+ },
1257
+ ],
1258
+ },
1259
+ ],
1260
+ },
1261
+ ],
1262
+ },
1263
+ ],
1264
+ },
1265
+ };
1266
+ await this.bridge.updateCardInPlace(editCard, ctx);
1267
+ }
1268
+ /**
1269
+ * Handle order.aliasInput: 提交别名(绑/改/解绑),随后重渲染 /order 列表卡。
1270
+ * 留空 = 解绑;非空按命名规则 + 全局唯一校验,失败仅 toast 不重渲染。
1271
+ */
1272
+ async handleOrderAliasInput(value, ctx) {
1273
+ const orderId = value.orderId;
1274
+ const offset = Math.max(0, Math.trunc(Number(value.offset) || 0));
1275
+ const name = value.inputValue ?? value.formValue?.['aliasName'];
1276
+ if (!orderId) {
1277
+ return { toast: { type: 'error', content: '卡片 payload 缺少必要信息' } };
1278
+ }
1279
+ this.orderStore.reload();
1280
+ const order = this.orderStore.get().find((o) => o.id === orderId);
1281
+ if (!order) {
1282
+ return { toast: { type: 'error', content: '指令不存在或已被删除' } };
1283
+ }
1284
+ const trimmed = name?.trim() ?? '';
1285
+ // 变更前快照旧别名:setAlias 会原地变异 order 对象(delete entry.alias),
1286
+ // 删除后 order.alias 已为 undefined,直接用会让成功文案显示 `$undefined`。
1287
+ const prevAlias = order.alias;
1288
+ try {
1289
+ this.orderStore.setAlias(order.id, trimmed === '' ? undefined : trimmed);
1290
+ }
1291
+ catch (err) {
1292
+ return { toast: { type: 'error', content: err.message } };
1293
+ }
1294
+ // 成功:重渲染列表卡(保持页码)。必须把 card 放进 callback 响应体让飞书
1295
+ // 原地替换 pre-click 编辑卡——toast-only 响应会停留在编辑界面(飞书保留
1296
+ // 点击前那张卡),见 handleQueueInput 同款注释。
1297
+ const result = this.cmdOrder([], ctx, offset);
1298
+ return {
1299
+ toast: {
1300
+ type: 'success',
1301
+ content: trimmed === '' ? `✅ 已移除别名 $${prevAlias ?? ''}` : `✅ 已绑定别名 $${trimmed}`,
1302
+ },
1303
+ card: { type: 'raw', data: result.card },
1304
+ };
1305
+ }
1306
+ /** Handle order.aliasRemove: 删除某条指令的别名并原地重渲染列表卡。 */
1307
+ async handleOrderAliasRemove(value, ctx) {
1308
+ const orderId = value.orderId;
1309
+ const offset = Math.max(0, Math.trunc(Number(value.offset) || 0));
1310
+ if (!orderId) {
1311
+ return { toast: { type: 'error', content: '卡片 payload 缺少必要信息' } };
1312
+ }
1313
+ this.orderStore.reload();
1314
+ const order = this.orderStore.get().find((o) => o.id === orderId);
1315
+ if (!order || !order.alias) {
1316
+ return { toast: { type: 'error', content: '指令不存在或没有别名' } };
1317
+ }
1318
+ // 变更前快照旧别名:setAlias(undefined) 会原地变异 order(delete alias),
1319
+ // 删除后 order.alias 已为 undefined,直接拼会让成功文案显示 `$undefined`。
1320
+ const removedAlias = order.alias;
1321
+ this.orderStore.setAlias(order.id, undefined);
1322
+ // 必须把 card 放进 callback 响应体让飞书原地替换 pre-click 卡片,否则删除
1323
+ // 后卡片停留在旧状态(别名视觉上未消失)。同 handleQueueInput 语义。
1324
+ const result = this.cmdOrder([], ctx, offset);
1325
+ return {
1326
+ toast: { type: 'success', content: `✅ 已移除别名 $${removedAlias}` },
1327
+ card: { type: 'raw', data: result.card },
1328
+ };
1329
+ }
1330
+ /**
1331
+ * Handle order.textEdit: 展示指令文本编辑卡(input + ✓ 提交图标)。
1332
+ * 预填当前 text(完整内容,不截断——input 组件有自有滚动条);
1333
+ * 提交 `order.textInput` 处理(trim + 长度校验 + OrderStore.updateText)。
1334
+ * 完全镜像 handleOrderAliasEdit 的 4 段结构(reload → find → 构造 editCard →
1335
+ * updateCardInPlace)。
1336
+ */
1337
+ async handleOrderTextEdit(value, ctx) {
1338
+ const orderId = value.orderId;
1339
+ const offset = Math.max(0, Math.trunc(Number(value.offset) || 0));
1340
+ if (!orderId) {
1341
+ return { toast: { type: 'error', content: '卡片 payload 缺少必要信息' } };
1342
+ }
1343
+ this.orderStore.reload();
1344
+ const order = this.orderStore.get().find((o) => o.id === orderId);
1345
+ if (!order) {
1346
+ return { toast: { type: 'error', content: '指令不存在或已被删除' } };
1347
+ }
1348
+ // 列表卡显示的是截断版(≤100 字符 + ...),编辑卡预览也保持一致;
1349
+ // input 组件 default_value 预填完整 text(不受截断影响),用户提交后生效完整文本。
1350
+ const displayText = order.text.length > 100 ? order.text.slice(0, 97) + '...' : order.text;
1351
+ // CardKit 2.0 input 自带 ✓ 提交图标(input_value 经 raw 回传,红线)。
1352
+ // 不用 form 容器(触发 300123 无 submit button / 200621 嵌套 column)。
1353
+ const editCard = {
1354
+ schema: '2.0',
1355
+ config: { wide_screen_mode: true },
1356
+ header: {
1357
+ template: 'blue',
1358
+ title: { tag: 'plain_text', content: '✏️ 编辑指令' },
1359
+ },
1360
+ body: {
1361
+ elements: [
1362
+ {
1363
+ tag: 'div',
1364
+ text: { tag: 'lark_md', content: `**当前指令:**\n\`${displayText}\`` },
1365
+ },
1366
+ { tag: 'hr' },
1367
+ {
1368
+ tag: 'div',
1369
+ text: {
1370
+ tag: 'lark_md',
1371
+ content: '💡 修改后点击右侧 ✓ 提交;空白提交 = 报错;最多 200 字符',
1372
+ },
1373
+ },
1374
+ {
1375
+ tag: 'column_set',
1376
+ columns: [
1377
+ {
1378
+ tag: 'column',
1379
+ width: 'weighted',
1380
+ weight: 3,
1381
+ elements: [
1382
+ {
1383
+ tag: 'input',
1384
+ name: 'text',
1385
+ placeholder: { tag: 'plain_text', content: '输入新的指令文本...' },
1386
+ default_value: order.text,
1387
+ // max_length 与 MAX_TEXT_LENGTH=200 对齐:超长在输入侧直接截断,
1388
+ // 避免提交后才报错(后端 updateText 仍会二次校验,双保险)。
1389
+ max_length: 200,
1390
+ behaviors: [
1391
+ {
1392
+ type: 'callback',
1393
+ value: { cmd: 'order.textInput', orderId, offset },
1394
+ },
1395
+ ],
1396
+ },
1397
+ ],
1398
+ },
1399
+ ],
1400
+ },
1401
+ ],
1402
+ },
1403
+ };
1404
+ await this.bridge.updateCardInPlace(editCard, ctx);
1405
+ }
1406
+ /**
1407
+ * Handle order.textInput: 提交指令文本编辑。校验 orderId → OrderStore.updateText
1408
+ * (store 内部统一 trim + 空/超长校验)。成功 toast + 原地重渲染列表卡(同
1409
+ * handleOrderAliasInput 语义:toast-only 响应会让飞书停留在 pre-click 编辑卡,
1410
+ * 必须把 card 放进 callback 响应体);失败仅 error toast。
1411
+ * 保留 usedAt / alias / createdAt(OrderStore.updateText 只动 text 字段)。
1412
+ */
1413
+ async handleOrderTextInput(value, ctx) {
1414
+ const orderId = value.orderId;
1415
+ const offset = Math.max(0, Math.trunc(Number(value.offset) || 0));
1416
+ const raw = value.inputValue ?? value.formValue?.['text'];
1417
+ if (!orderId) {
1418
+ return { toast: { type: 'error', content: '卡片 payload 缺少必要信息' } };
1419
+ }
1420
+ this.orderStore.reload();
1421
+ const order = this.orderStore.get().find((o) => o.id === orderId);
1422
+ if (!order) {
1423
+ return { toast: { type: 'error', content: '指令不存在或已被删除' } };
1424
+ }
1425
+ let updated;
1426
+ try {
1427
+ updated = this.orderStore.updateText(order.id, raw ?? '');
1428
+ }
1429
+ catch (err) {
1430
+ return { toast: { type: 'error', content: err.message } };
1431
+ }
1432
+ if (!updated) {
1433
+ // reload + find 后 updateText 不应返回 undefined;防御性兜底(如并发删除)。
1434
+ return { toast: { type: 'error', content: '指令不存在或已被删除' } };
1435
+ }
1436
+ // 成功:重渲染列表卡(保持页码)。必须把 card 放进 callback 响应体让飞书
1437
+ // 原地替换 pre-click 编辑卡——toast-only 响应会停留在编辑界面(飞书保留
1438
+ // 点击前那张卡),见 handleQueueInput / handleOrderAliasInput 同款注释。
1439
+ const result = this.cmdOrder([], ctx, offset);
1440
+ const preview = updated.text.length > 50 ? updated.text.slice(0, 50) + '...' : updated.text;
1441
+ return {
1442
+ toast: { type: 'success', content: `✅ 已更新指令: ${preview}` },
1443
+ card: { type: 'raw', data: result.card },
1444
+ };
1445
+ }
1160
1446
  /**
1161
1447
  * Handle ws.use card action: switch to the workspace, then refresh the /ws
1162
1448
  * list card in place so "recent" sort order is immediately visible.
@@ -1606,6 +1892,20 @@ export class CommandRouter {
1606
1892
  switchNotice = `DSH preset 已变更,下次消息将新建会话`;
1607
1893
  }
1608
1894
  }
1895
+ // DSH host 变更 = 换服务(CC-02):旧 host 的 sessionId 发往新 host 是错的。
1896
+ // 视为 session 边界,仿 preset 分支停车 + 清空当前 sessionId,下次 run 新建会话。
1897
+ const hasDshHostChange = Object.keys(updates).some((k) => k === 'agents.dsh.host');
1898
+ if (hasDshHostChange && this.config.defaultAgent === 'dsh' && !switchNotice) {
1899
+ const oldSessionId = this.sessionStore.getSessionId(ctx.userId, 'dsh');
1900
+ if (oldSessionId) {
1901
+ this.sessionStore.setPreviousSessionId(ctx.userId, 'dsh', oldSessionId);
1902
+ this.sessionStore.clearSessionId(ctx.userId, 'dsh');
1903
+ switchNotice = `DSH host 已变更,旧会话已停放到恢复槽(可 /resume 校验后恢复),下次消息将新建会话`;
1904
+ }
1905
+ else {
1906
+ switchNotice = `DSH host 已变更,下次消息将新建会话`;
1907
+ }
1908
+ }
1609
1909
  // 2026-07-13: 同步 agent 配置到 agentChoices(用于切换 agent 时恢复配置)
1610
1910
  const currentAgent = this.config.defaultAgent;
1611
1911
  const currentAgentConfigKey = `agents.${currentAgent}`;
@@ -1719,8 +2019,8 @@ export class CommandRouter {
1719
2019
  { cmd: 'resume', label: '/resume /r [agent] [list|id|N]', desc: '列出或切换 Agent session' },
1720
2020
  {
1721
2021
  cmd: 'order',
1722
- label: '/order /o save|list',
1723
- desc: '收藏指令 /order save,快捷别名 /order alias',
2022
+ label: '/order /o save|list|edit',
2023
+ desc: '收藏指令 /order save,编辑指令 /order edit <id|N> <新文本>,指令可起别名(卡片上 +别名)',
1724
2024
  },
1725
2025
  ];
1726
2026
  const bodyElements = [];
@@ -1784,6 +2084,16 @@ export class CommandRouter {
1784
2084
  content: '**快捷命令**\n`!<bash-command>` — 执行 bash 命令并流式输出到卡片',
1785
2085
  },
1786
2086
  });
2087
+ // 快捷别名($name)
2088
+ bodyElements.push({ tag: 'hr' });
2089
+ bodyElements.push({
2090
+ tag: 'div',
2091
+ text: {
2092
+ tag: 'lark_md',
2093
+ content: '**快捷别名**\n`$<name>` — 触发已保存指令的别名(在 /order 卡片给指令起别名后生效),' +
2094
+ '如 `$all` 等价于执行「跑全量测试」。只匹配消息开头的 `$name`,`!`/`/` 开头不展开',
2095
+ },
2096
+ });
1787
2097
  // Agent 说明
1788
2098
  bodyElements.push({ tag: 'hr' });
1789
2099
  bodyElements.push({
@@ -3249,11 +3559,9 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
3249
3559
  /** /order 实现(public:测试直接调用,替代 as unknown as)。 */
3250
3560
  cmdOrder(args, _ctx, offset = 0) {
3251
3561
  const sub = args[0]?.toLowerCase();
3252
- // /order — list orders + aliases (CardKit 2.0 with pagination)
3253
- // `/order alias`(无参数)也并入列表卡片。
3254
- if (!sub || sub === 'list' || (sub === 'alias' && !args[1])) {
3562
+ // /order — list orders (CardKit 2.0 with pagination)
3563
+ if (!sub || sub === 'list') {
3255
3564
  this.orderStore.reload();
3256
- this.aliasStore.reload();
3257
3565
  const allOrders = this.orderStore.get();
3258
3566
  const elements = [];
3259
3567
  if (allOrders.length === 0) {
@@ -3272,40 +3580,144 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
3272
3580
  for (let i = 0; i < pageOrders.length; i++) {
3273
3581
  const order = pageOrders[i];
3274
3582
  const displayText = order.text.length > 100 ? order.text.slice(0, 97) + '...' : order.text;
3275
- // 文字在上方
3276
- elements.push({ tag: 'div', text: { tag: 'lark_md', content: displayText } });
3277
- // 按钮在下方,一左一右
3583
+ // 文字在上方(有别名时用 `$name` 前缀标识触发词)
3278
3584
  elements.push({
3279
- tag: 'column_set',
3280
- columns: [
3585
+ tag: 'div',
3586
+ text: {
3587
+ tag: 'lark_md',
3588
+ content: order.alias ? `\`$${order.alias}\` ${displayText}` : displayText,
3589
+ },
3590
+ });
3591
+ // 操作行:左侧别名按钮(无别名显示 +别名,有别名点击可改),
3592
+ // 右侧「删除指令」按钮。两列均 weighted 1:1,跨行对齐。
3593
+ const leftColumn = order.alias
3594
+ ? [
3281
3595
  {
3282
- tag: 'column',
3283
- width: 'auto',
3284
- elements: [
3596
+ tag: 'button',
3597
+ text: { tag: 'plain_text', content: `$${order.alias}` },
3598
+ type: 'default',
3599
+ size: 'small',
3600
+ behaviors: [
3285
3601
  {
3286
- tag: 'button',
3287
- text: { tag: 'plain_text', content: '▶ 执行' },
3288
- type: 'primary',
3289
- size: 'small',
3290
- behaviors: [
3291
- { type: 'callback', value: { cmd: 'order.exec', orderId: order.id } },
3292
- ],
3602
+ type: 'callback',
3603
+ value: { cmd: 'order.aliasEdit', orderId: order.id, offset: safeOffset },
3293
3604
  },
3294
3605
  ],
3295
3606
  },
3607
+ {
3608
+ tag: 'button',
3609
+ text: { tag: 'plain_text', content: '✕' },
3610
+ type: 'default',
3611
+ size: 'small',
3612
+ behaviors: [
3613
+ {
3614
+ type: 'callback',
3615
+ value: {
3616
+ cmd: 'order.aliasRemove',
3617
+ orderId: order.id,
3618
+ aliasName: order.alias,
3619
+ offset: safeOffset,
3620
+ },
3621
+ },
3622
+ ],
3623
+ },
3624
+ ]
3625
+ : [
3626
+ {
3627
+ tag: 'button',
3628
+ text: { tag: 'plain_text', content: '+别名' },
3629
+ type: 'default',
3630
+ size: 'small',
3631
+ behaviors: [
3632
+ {
3633
+ type: 'callback',
3634
+ value: { cmd: 'order.aliasEdit', orderId: order.id, offset: safeOffset },
3635
+ },
3636
+ ],
3637
+ },
3638
+ ];
3639
+ elements.push({
3640
+ tag: 'column_set',
3641
+ columns: [
3296
3642
  {
3297
3643
  tag: 'column',
3298
- width: 'auto',
3644
+ width: 'weighted',
3645
+ weight: 1,
3646
+ elements: leftColumn,
3647
+ },
3648
+ {
3649
+ tag: 'column',
3650
+ width: 'weighted',
3651
+ weight: 1,
3652
+ vertical_align: 'center',
3299
3653
  elements: [
3300
3654
  {
3301
- tag: 'button',
3302
- text: { tag: 'plain_text', content: '删除' },
3303
- type: 'danger',
3304
- size: 'small',
3305
- behaviors: [
3655
+ tag: 'column_set',
3656
+ columns: [
3306
3657
  {
3307
- type: 'callback',
3308
- value: { cmd: 'order.delete', orderId: order.id, offset: safeOffset },
3658
+ tag: 'column',
3659
+ width: 'auto',
3660
+ elements: [
3661
+ {
3662
+ tag: 'button',
3663
+ text: { tag: 'plain_text', content: '▶ 执行' },
3664
+ type: 'primary',
3665
+ size: 'small',
3666
+ behaviors: [
3667
+ {
3668
+ type: 'callback',
3669
+ value: { cmd: 'order.exec', orderId: order.id },
3670
+ },
3671
+ ],
3672
+ },
3673
+ ],
3674
+ },
3675
+ {
3676
+ // 编辑:弹蓝色 header 输入卡(CardKit 2.0 input + ✓ 提交),
3677
+ // 镜像 order.aliasEdit 流程。order.textEdit 是 control-only action
3678
+ // (isImmediateAction 注册),不 spawn agent。
3679
+ tag: 'column',
3680
+ width: 'auto',
3681
+ elements: [
3682
+ {
3683
+ tag: 'button',
3684
+ text: { tag: 'plain_text', content: '编辑' },
3685
+ type: 'default',
3686
+ size: 'small',
3687
+ behaviors: [
3688
+ {
3689
+ type: 'callback',
3690
+ value: {
3691
+ cmd: 'order.textEdit',
3692
+ orderId: order.id,
3693
+ offset: safeOffset,
3694
+ },
3695
+ },
3696
+ ],
3697
+ },
3698
+ ],
3699
+ },
3700
+ {
3701
+ tag: 'column',
3702
+ width: 'auto',
3703
+ elements: [
3704
+ {
3705
+ tag: 'button',
3706
+ text: { tag: 'plain_text', content: '删除' },
3707
+ type: 'danger',
3708
+ size: 'small',
3709
+ behaviors: [
3710
+ {
3711
+ type: 'callback',
3712
+ value: {
3713
+ cmd: 'order.delete',
3714
+ orderId: order.id,
3715
+ offset: safeOffset,
3716
+ },
3717
+ },
3718
+ ],
3719
+ },
3720
+ ],
3309
3721
  },
3310
3722
  ],
3311
3723
  },
@@ -3331,22 +3743,6 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
3331
3743
  elements.push(bar);
3332
3744
  }
3333
3745
  }
3334
- // 别名区:并入 /order 卡片(`/order alias` 列出全部)。
3335
- const aliases = this.aliasStore.list();
3336
- if (aliases.length > 0) {
3337
- elements.push({ tag: 'hr' });
3338
- elements.push({
3339
- tag: 'div',
3340
- text: { tag: 'lark_md', content: '**⚡ 别名**(输入 $name 触发)' },
3341
- });
3342
- for (const alias of aliases) {
3343
- const text = alias.text.length > 100 ? alias.text.slice(0, 97) + '...' : alias.text;
3344
- elements.push({
3345
- tag: 'div',
3346
- text: { tag: 'lark_md', content: `$${alias.name} → ${text}` },
3347
- });
3348
- }
3349
- }
3350
3746
  return {
3351
3747
  card: {
3352
3748
  schema: '2.0',
@@ -3356,46 +3752,34 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
3356
3752
  },
3357
3753
  };
3358
3754
  }
3359
- // /order alias <name> <text> — 注册;/order alias <name> — 查看;/order alias remove <name> — 删除
3755
+ // /order alias — CLI 别名管理(卡片是主入口,命令是备选):
3756
+ // /order alias add <orderId|N> <name> — 给某条指令绑定别名
3757
+ // /order alias rm <orderId|N> — 移除某条指令的别名
3758
+ // /order alias list — 列出已绑定的别名(并入列表卡片)
3360
3759
  if (sub === 'alias') {
3361
- const name = args[1];
3362
- if (name === 'remove') {
3760
+ const op = args[1];
3761
+ if (op === 'add') {
3363
3762
  const target = args[2];
3364
- if (!target) {
3365
- return { text: '用法: /order alias remove <name>' };
3366
- }
3367
- // remove 是保留子命令,不能作为别名名:后面跟多词文本说明用户想注册
3368
- // 名为 remove 的别名,明确报错而不是静默删除已存在的 target(P2 review)。
3369
- if (args.length > 3) {
3370
- return { text: '`remove` 是保留子命令,不能作为别名名,请换一个名称' };
3763
+ const name = args[3];
3764
+ if (!target || !name) {
3765
+ return { text: '用法: /order alias add <orderId|序号> <别名名>' };
3371
3766
  }
3372
- const entry = this.aliasStore.get(target);
3373
- if (!entry) {
3374
- return { text: `别名 $${target} 不存在` };
3375
- }
3376
- this.aliasStore.remove(target);
3377
- // 回显原内容:单词场景无法区分「注册 remove」与「删除」,误删后
3378
- // 用户可直接按回显内容重新注册,消除静默破坏(P3 review)。
3379
- const preview = entry.text.length > 50 ? entry.text.slice(0, 50) + '...' : entry.text;
3380
- return { text: `✅ 已删除别名 $${target}(原内容:${preview})` };
3767
+ return this.cmdOrderAliasAdd(target, name);
3381
3768
  }
3382
- const text = args.slice(2).join(' ');
3383
- if (!text) {
3384
- const entry = name ? this.aliasStore.get(name) : undefined;
3385
- if (!entry) {
3386
- return { text: name ? `别名 $${name} 不存在` : '用法: /order alias <name> <text>' };
3769
+ if (op === 'rm') {
3770
+ const target = args[2];
3771
+ if (!target) {
3772
+ return { text: '用法: /order alias rm <orderId|序号>' };
3387
3773
  }
3388
- return { text: `$${entry.name} → ${entry.text}` };
3774
+ return this.cmdOrderAliasRemove(target);
3389
3775
  }
3390
- try {
3391
- const existed = this.aliasStore.has(name);
3392
- this.aliasStore.set(name, text);
3393
- const preview = text.length > 50 ? text.slice(0, 50) + '...' : text;
3394
- return { text: `${existed ? '✅ 已更新别名' : '✅ 已保存别名'} $${name} → ${preview}` };
3395
- }
3396
- catch (err) {
3397
- return { text: `保存失败: ${err.message}` };
3776
+ if (!op || op === 'list') {
3777
+ // 别名管理列表并入列表卡片
3778
+ return this.cmdOrder([], _ctx, offset);
3398
3779
  }
3780
+ return {
3781
+ text: '用法: /order alias [add <orderId|序号> <别名名> | rm <orderId|序号> | list]',
3782
+ };
3399
3783
  }
3400
3784
  // /order save <text>
3401
3785
  if (sub === 'save') {
@@ -3411,7 +3795,67 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
3411
3795
  return { text: `保存失败: ${err.message}` };
3412
3796
  }
3413
3797
  }
3414
- return { text: '用法: /order [list|save <text>|alias <name> <text>|alias remove <name>]' };
3798
+ // /order edit <orderId|序号> <新文本> — CLI 备选路径(卡片是主入口)。
3799
+ // 与 /order save / alias add|rm 对称;复用 resolveOrderTarget。
3800
+ // 与卡片路径(handleOrderTextInput)对齐 trim 语义,避免「卡片 trim、CLI 不 trim」
3801
+ // 两条入口对同一文本存出不同结果。
3802
+ if (sub === 'edit') {
3803
+ const target = args[1];
3804
+ const newText = args.slice(2).join(' ').trim();
3805
+ if (!target || !newText) {
3806
+ return { text: '用法: /order edit <orderId|序号> <新文本>' };
3807
+ }
3808
+ const order = this.resolveOrderTarget(target);
3809
+ if (!order)
3810
+ return { text: `指令不存在: ${target}` };
3811
+ try {
3812
+ this.orderStore.updateText(order.id, newText);
3813
+ const preview = newText.length > 50 ? newText.slice(0, 50) + '...' : newText;
3814
+ return { text: `✅ 已更新指令: ${preview}` };
3815
+ }
3816
+ catch (err) {
3817
+ return { text: `保存失败: ${err.message}` };
3818
+ }
3819
+ }
3820
+ return {
3821
+ text: '用法: /order [list|save <text>|edit <orderId|序号> <新文本>|alias add <orderId|序号> <别名名>|alias rm <orderId|序号>]',
3822
+ };
3823
+ }
3824
+ /** /order alias add:按 orderId 或列表序号(1 基)解析目标指令并绑定别名。 */
3825
+ cmdOrderAliasAdd(target, name) {
3826
+ const order = this.resolveOrderTarget(target);
3827
+ if (!order)
3828
+ return { text: `指令不存在: ${target}` };
3829
+ try {
3830
+ this.orderStore.setAlias(order.id, name);
3831
+ return { text: `✅ 已给指令绑定别名 $${name}` };
3832
+ }
3833
+ catch (err) {
3834
+ return { text: `保存失败: ${err.message}` };
3835
+ }
3836
+ }
3837
+ /** /order alias rm:按 orderId 或列表序号(1 基)移除指令的别名。 */
3838
+ cmdOrderAliasRemove(target) {
3839
+ const order = this.resolveOrderTarget(target);
3840
+ if (!order)
3841
+ return { text: `指令不存在: ${target}` };
3842
+ if (!order.alias)
3843
+ return { text: '该指令没有别名' };
3844
+ // 先读别名名再解绑:setAlias(undefined) 会 delete 同一对象引用上的 alias 字段。
3845
+ const aliasName = order.alias;
3846
+ this.orderStore.setAlias(order.id, undefined);
3847
+ return { text: `✅ 已移除别名 $${aliasName}` };
3848
+ }
3849
+ /** 将 `/order alias add|rm` 的目标解析为 order:优先 orderId 精确匹配,回退列表序号(1 基)。 */
3850
+ resolveOrderTarget(target) {
3851
+ const all = this.orderStore.get();
3852
+ const byId = all.find((o) => o.id === target);
3853
+ if (byId)
3854
+ return byId;
3855
+ const n = Number(target);
3856
+ if (Number.isInteger(n) && n >= 1 && n <= all.length)
3857
+ return all[n - 1];
3858
+ return undefined;
3415
3859
  }
3416
3860
  }
3417
3861
  //# sourceMappingURL=index.js.map