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.
- package/CHANGELOG.md +38 -0
- package/README.en.md +4 -4
- package/README.md +4 -4
- package/dist/bridge/approval-coordinator.d.ts +18 -0
- package/dist/bridge/approval-coordinator.js +64 -3
- package/dist/bridge/index.d.ts +17 -0
- package/dist/bridge/index.js +35 -2
- package/dist/card/approval-render.js +105 -8
- package/dist/index.js +17 -3
- package/dist/order/alias-resolve.d.ts +6 -2
- package/dist/order/index.d.ts +17 -2
- package/dist/order/index.js +57 -0
- package/dist/router/index.d.ts +35 -3
- package/dist/router/index.js +529 -85
- package/dist/runner/claude/runner.js +22 -0
- package/dist/runner/claude/session.d.ts +26 -0
- package/dist/runner/claude/session.js +94 -13
- package/dist/runner/common/connection-based-runner.js +10 -5
- package/dist/runner/dsh/runner.d.ts +7 -0
- package/dist/runner/dsh/runner.js +125 -74
- package/dist/runner/kimi/acp/runner.js +15 -0
- package/dist/runner/pi/rpc/runner.js +6 -1
- package/dist/runner/registry.d.ts +3 -2
- package/dist/runner/registry.js +3 -2
- package/dist/runner/types.d.ts +33 -2
- package/dist/session/common/pagination.d.ts +1 -1
- package/dist/session/common/pagination.js +1 -1
- package/dist/session/dsh/sessions.d.ts +6 -1
- package/dist/session/dsh/sessions.js +20 -5
- package/dist/workspace/index.d.ts +8 -0
- package/dist/workspace/index.js +20 -1
- package/package.json +1 -1
- package/dist/order/alias-store.d.ts +0 -38
- package/dist/order/alias-store.js +0 -106
package/dist/router/index.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
3253
|
-
|
|
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: '
|
|
3280
|
-
|
|
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: '
|
|
3283
|
-
|
|
3284
|
-
|
|
3596
|
+
tag: 'button',
|
|
3597
|
+
text: { tag: 'plain_text', content: `$${order.alias}` },
|
|
3598
|
+
type: 'default',
|
|
3599
|
+
size: 'small',
|
|
3600
|
+
behaviors: [
|
|
3285
3601
|
{
|
|
3286
|
-
|
|
3287
|
-
|
|
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: '
|
|
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: '
|
|
3302
|
-
|
|
3303
|
-
type: 'danger',
|
|
3304
|
-
size: 'small',
|
|
3305
|
-
behaviors: [
|
|
3655
|
+
tag: 'column_set',
|
|
3656
|
+
columns: [
|
|
3306
3657
|
{
|
|
3307
|
-
|
|
3308
|
-
|
|
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
|
|
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
|
|
3362
|
-
if (
|
|
3760
|
+
const op = args[1];
|
|
3761
|
+
if (op === 'add') {
|
|
3363
3762
|
const target = args[2];
|
|
3364
|
-
|
|
3365
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
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
|
|
3774
|
+
return this.cmdOrderAliasRemove(target);
|
|
3389
3775
|
}
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
this.
|
|
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
|
-
|
|
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
|