lark-remote 0.1.12 → 0.1.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.
- package/CHANGELOG.md +12 -0
- package/dist/bridge/inbound-media.d.ts +0 -3
- package/dist/bridge/inbound-media.js +0 -1
- package/dist/bridge/index.d.ts +7 -0
- package/dist/bridge/index.js +79 -70
- package/dist/card/card-shared.d.ts +1 -4
- package/dist/card/card-shared.js +2 -4
- package/dist/common/truncate.d.ts +0 -6
- package/dist/common/truncate.js +1 -1
- package/dist/common/ttl-cache.d.ts +24 -0
- package/dist/common/ttl-cache.js +53 -0
- package/dist/config/codex-config.js +12 -11
- package/dist/config/index.d.ts +0 -3
- package/dist/config/index.js +0 -5
- package/dist/config/kimi-config.d.ts +1 -1
- package/dist/config/kimi-config.js +1 -1
- package/dist/config/opencode-config.d.ts +1 -1
- package/dist/config/opencode-config.js +3 -8
- package/dist/router/index.d.ts +7 -0
- package/dist/router/index.js +217 -279
- package/dist/runner/claude/session.d.ts +0 -3
- package/dist/runner/claude/session.js +0 -5
- package/dist/runner/codex/app-server/runner.d.ts +1 -16
- package/dist/runner/codex/app-server/runner.js +2 -2
- package/dist/runner/common/acp/protocol-helpers.d.ts +31 -0
- package/dist/runner/common/acp/protocol-helpers.js +48 -0
- package/dist/runner/common/connection-based-runner.d.ts +0 -2
- package/dist/runner/common/connection-based-runner.js +1 -1
- package/dist/runner/dsh/index.d.ts +0 -2
- package/dist/runner/dsh/index.js +0 -2
- package/dist/runner/dsh/runner.d.ts +0 -2
- package/dist/runner/dsh/runner.js +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +18 -11
- package/dist/runner/kimi/acp/runner.js +95 -87
- package/dist/runner/kimi/acp/translator.d.ts +0 -1
- package/dist/runner/kimi/acp/translator.js +5 -13
- package/dist/runner/opencode/acp/runner.js +2 -42
- package/dist/runner/opencode/acp/translator.d.ts +0 -1
- package/dist/runner/opencode/acp/translator.js +3 -13
- package/dist/runner/pi/rpc/index.d.ts +0 -2
- package/dist/runner/pi/rpc/index.js +0 -2
- package/dist/runner/resolve-agent-choices.js +6 -2
- package/dist/runner/sync-agent-choices.js +1 -1
- package/dist/session/claude/session-index.js +3 -1
- package/dist/session/claude/sessions.js +3 -7
- package/dist/session/codex/rollout-reader.d.ts +0 -14
- package/dist/session/codex/rollout-reader.js +3 -7
- package/dist/session/common/pagination.d.ts +0 -2
- package/dist/session/common/pagination.js +1 -1
- package/dist/session/common/recency.d.ts +7 -0
- package/dist/session/common/recency.js +10 -0
- package/dist/session/common/usage-accumulator.d.ts +14 -0
- package/dist/session/common/usage-accumulator.js +15 -0
- package/dist/session/dsh/sessions.js +9 -6
- package/dist/session/kimi/sessions.d.ts +18 -4
- package/dist/session/kimi/sessions.js +49 -30
- package/dist/session/opencode/sessions.d.ts +2 -14
- package/dist/session/opencode/sessions.js +9 -7
- package/dist/session/pi/sessions.js +10 -22
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +1 -1
- package/package.json +1 -2
package/dist/router/index.js
CHANGED
|
@@ -24,6 +24,29 @@ const AUTO_RESUME_MAX_EVENTS = 5;
|
|
|
24
24
|
/** /resume 列表页大小;`/resume [N]` 的 N clamp 到 [1, RESUME_PAGE_SIZE]。 */
|
|
25
25
|
const RESUME_PAGE_SIZE = 5;
|
|
26
26
|
/** /active 卡片每页显示的最大条目数(agent run + bash run 合计)。 */
|
|
27
|
+
/**
|
|
28
|
+
* 分页算术(原先 5 处内联且 clamp 语义漂移:/ls 不 clamp、/active clamp 到
|
|
29
|
+
* totalCount-1 会把过期 offset 泄漏成残页)。统一 clamp 到最后一页边界。
|
|
30
|
+
*/
|
|
31
|
+
function pageInfo(totalCount, offset, pageSize) {
|
|
32
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));
|
|
33
|
+
const maxOffset = Math.max(0, (totalPages - 1) * pageSize);
|
|
34
|
+
const safeOffset = clampInt(offset, 0, maxOffset);
|
|
35
|
+
const currentPage = Math.floor(safeOffset / pageSize) + 1;
|
|
36
|
+
return { totalPages, safeOffset, currentPage };
|
|
37
|
+
}
|
|
38
|
+
/** 在 pageInfo 之上切出当前页条目。 */
|
|
39
|
+
function pageSlice(items, offset, pageSize) {
|
|
40
|
+
const { totalPages, safeOffset, currentPage } = pageInfo(items.length, offset, pageSize);
|
|
41
|
+
return {
|
|
42
|
+
totalCount: items.length,
|
|
43
|
+
totalPages,
|
|
44
|
+
safeOffset,
|
|
45
|
+
currentPage,
|
|
46
|
+
pageItems: items.slice(safeOffset, safeOffset + pageSize),
|
|
47
|
+
hasPagination: items.length > pageSize,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
27
50
|
const ACTIVE_PAGE_SIZE = 20;
|
|
28
51
|
/**
|
|
29
52
|
* /order 列表页大小;指令超过此数量时显示分页导航栏。
|
|
@@ -132,6 +155,109 @@ export function isImmediateAction(cmd) {
|
|
|
132
155
|
cmd === 'approval.answerNote' ||
|
|
133
156
|
cmd === 'approval.planFeedback');
|
|
134
157
|
}
|
|
158
|
+
const QUESTION_INDEX_PRESENT = (v) => v.questionIndex !== undefined;
|
|
159
|
+
const APPROVAL_ACTION_SPECS = {
|
|
160
|
+
'approval.respond': {
|
|
161
|
+
missing: (v) => !v.runId || v.requestId === undefined || !v.decision || !v.nonce,
|
|
162
|
+
missingToast: '缺少审批响应参数',
|
|
163
|
+
run: (bridge, v) => bridge.handleApprovalRespond({
|
|
164
|
+
runId: v.runId,
|
|
165
|
+
requestId: v.requestId,
|
|
166
|
+
decision: v.decision,
|
|
167
|
+
nonce: v.nonce,
|
|
168
|
+
}),
|
|
169
|
+
successToast: '审批已提交',
|
|
170
|
+
failureToast: (msg) => {
|
|
171
|
+
// 审批已过期:给用户明确反馈,不静默不误导(2026-08-12 事故:点了允许无任何反馈)。
|
|
172
|
+
const content = /state=expired/.test(msg)
|
|
173
|
+
? '⏰ 审批已过期,无法响应'
|
|
174
|
+
: `审批响应失败:${msg}`;
|
|
175
|
+
return { toast: { type: 'error', content } };
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
'approval.toggle': {
|
|
179
|
+
missing: (v) => !v.runId || v.requestId === undefined || !v.permId,
|
|
180
|
+
missingToast: '缺少权限切换参数',
|
|
181
|
+
run: (bridge, v) => bridge.handleApprovalToggle({
|
|
182
|
+
runId: v.runId,
|
|
183
|
+
requestId: v.requestId,
|
|
184
|
+
permId: v.permId,
|
|
185
|
+
selected: v.selected ?? true,
|
|
186
|
+
}),
|
|
187
|
+
successToast: '已切换',
|
|
188
|
+
toastType: 'info',
|
|
189
|
+
failureToast: (msg) => ({ toast: { type: 'error', content: `权限切换失败:${msg}` } }),
|
|
190
|
+
},
|
|
191
|
+
'approval.answer': {
|
|
192
|
+
missing: (v) => !v.runId || v.requestId === undefined || !QUESTION_INDEX_PRESENT(v) || !v.option || !v.nonce,
|
|
193
|
+
missingToast: '缺少问题答案参数',
|
|
194
|
+
run: (bridge, v) => bridge.handleApprovalAnswer({
|
|
195
|
+
runId: v.runId,
|
|
196
|
+
requestId: v.requestId,
|
|
197
|
+
questionIndex: v.questionIndex,
|
|
198
|
+
option: v.option,
|
|
199
|
+
nonce: v.nonce,
|
|
200
|
+
}),
|
|
201
|
+
successToast: '已选择',
|
|
202
|
+
},
|
|
203
|
+
'approval.answerSubmit': {
|
|
204
|
+
missing: (v) => !v.runId || v.requestId === undefined || !QUESTION_INDEX_PRESENT(v) || !v.nonce,
|
|
205
|
+
missingToast: '缺少提交参数',
|
|
206
|
+
run: (bridge, v) => bridge.handleApprovalAnswerSubmit({
|
|
207
|
+
runId: v.runId,
|
|
208
|
+
requestId: v.requestId,
|
|
209
|
+
questionIndex: v.questionIndex,
|
|
210
|
+
nonce: v.nonce,
|
|
211
|
+
}),
|
|
212
|
+
successToast: '答案已提交',
|
|
213
|
+
},
|
|
214
|
+
'approval.answerCustom': {
|
|
215
|
+
missing: (v) => !v.runId ||
|
|
216
|
+
v.requestId === undefined ||
|
|
217
|
+
!QUESTION_INDEX_PRESENT(v) ||
|
|
218
|
+
!v.nonce ||
|
|
219
|
+
!v.inputValue,
|
|
220
|
+
missingToast: '缺少答案文本参数',
|
|
221
|
+
run: (bridge, v) => bridge.handleApprovalAnswerCustom({
|
|
222
|
+
runId: v.runId,
|
|
223
|
+
requestId: v.requestId,
|
|
224
|
+
questionIndex: v.questionIndex,
|
|
225
|
+
text: v.inputValue,
|
|
226
|
+
nonce: v.nonce,
|
|
227
|
+
}),
|
|
228
|
+
successToast: '答案已提交',
|
|
229
|
+
},
|
|
230
|
+
'approval.answerNote': {
|
|
231
|
+
missing: (v) => !v.runId ||
|
|
232
|
+
v.requestId === undefined ||
|
|
233
|
+
!QUESTION_INDEX_PRESENT(v) ||
|
|
234
|
+
!v.nonce ||
|
|
235
|
+
!v.inputValue,
|
|
236
|
+
missingToast: '缺少补充说明参数',
|
|
237
|
+
run: (bridge, v) => bridge.handleApprovalAnswerNote({
|
|
238
|
+
runId: v.runId,
|
|
239
|
+
requestId: v.requestId,
|
|
240
|
+
questionIndex: v.questionIndex,
|
|
241
|
+
text: v.inputValue,
|
|
242
|
+
nonce: v.nonce,
|
|
243
|
+
}),
|
|
244
|
+
successToast: '补充说明已保存',
|
|
245
|
+
},
|
|
246
|
+
'approval.planFeedback': {
|
|
247
|
+
missing: (v) => !v.runId || v.requestId === undefined || !v.nonce || !v.inputValue,
|
|
248
|
+
missingToast: '缺少修改意见参数',
|
|
249
|
+
run: (bridge, v) => bridge.handleApprovalPlanFeedback({
|
|
250
|
+
runId: v.runId,
|
|
251
|
+
requestId: v.requestId,
|
|
252
|
+
text: v.inputValue,
|
|
253
|
+
nonce: v.nonce,
|
|
254
|
+
}),
|
|
255
|
+
successToast: '修改意见已保存',
|
|
256
|
+
failureToast: (msg) => ({ toast: { type: 'error', content: `修改意见保存失败:${msg}` } }),
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
/** 卡片 cardAction payload 缺字段的统一报错文案(原先 12 处手写且已漂移出两种前缀)。 */
|
|
260
|
+
const CARD_PAYLOAD_MISSING = '⚠️ 卡片 payload 缺少必要信息';
|
|
135
261
|
export class CommandRouter {
|
|
136
262
|
/** Valid agent kinds — single source of truth for resume.use / resume.page / cmdResume. */
|
|
137
263
|
static VALID_AGENTS = ['claude', 'codex', 'opencode', 'pi', 'kimi', 'dsh'];
|
|
@@ -427,6 +553,14 @@ export class CommandRouter {
|
|
|
427
553
|
* Routes to the appropriate bridge method based on value.cmd.
|
|
428
554
|
* Returns a toast response for immediate user feedback.
|
|
429
555
|
*/
|
|
556
|
+
/** queue.* 卡片动作通用守卫:workspace/messageId 缺失时回复错误并返回 null。 */
|
|
557
|
+
async queuePayloadOrReply(value, ctx) {
|
|
558
|
+
if (!value.workspace || !value.messageId) {
|
|
559
|
+
await this.bridge.sendResult({ text: CARD_PAYLOAD_MISSING }, ctx);
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
return { workspace: value.workspace, messageId: value.messageId };
|
|
563
|
+
}
|
|
430
564
|
/** 答案类操作的统一失败反馈:重复投递(同一 nonce 第二次点击)是已生效的
|
|
431
565
|
* 中性事件,报 error 会误导(首次点击实际已成功)。 */
|
|
432
566
|
answerFailureToast(msg) {
|
|
@@ -436,164 +570,30 @@ export class CommandRouter {
|
|
|
436
570
|
return { toast: { type: 'error', content: `答案提交失败:${msg}` } };
|
|
437
571
|
}
|
|
438
572
|
async handleApprovalAction(value, _ctx) {
|
|
439
|
-
const
|
|
440
|
-
if (
|
|
441
|
-
|
|
442
|
-
return { toast: { type: 'error', content: '缺少审批响应参数' } };
|
|
443
|
-
}
|
|
444
|
-
try {
|
|
445
|
-
await this.bridge.handleApprovalRespond({
|
|
446
|
-
runId,
|
|
447
|
-
requestId,
|
|
448
|
-
decision,
|
|
449
|
-
nonce,
|
|
450
|
-
});
|
|
451
|
-
return { toast: { type: 'success', content: '审批已提交' } };
|
|
452
|
-
}
|
|
453
|
-
catch (err) {
|
|
454
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
455
|
-
// 审批已过期:给用户明确反馈,不静默不误导(2026-08-12 事故:点了允许无任何反馈)。
|
|
456
|
-
const content = /state=expired/.test(msg)
|
|
457
|
-
? '⏰ 审批已过期,无法响应'
|
|
458
|
-
: `审批响应失败:${msg}`;
|
|
459
|
-
return { toast: { type: 'error', content } };
|
|
460
|
-
}
|
|
573
|
+
const spec = APPROVAL_ACTION_SPECS[value.cmd];
|
|
574
|
+
if (!spec) {
|
|
575
|
+
return { toast: { type: 'error', content: '未知的审批操作' } };
|
|
461
576
|
}
|
|
462
|
-
if (value
|
|
463
|
-
|
|
464
|
-
return { toast: { type: 'error', content: '缺少权限切换参数' } };
|
|
465
|
-
}
|
|
466
|
-
try {
|
|
467
|
-
await this.bridge.handleApprovalToggle({
|
|
468
|
-
runId,
|
|
469
|
-
requestId,
|
|
470
|
-
permId,
|
|
471
|
-
selected: value.selected ?? true,
|
|
472
|
-
});
|
|
473
|
-
return { toast: { type: 'info', content: '已切换' } };
|
|
474
|
-
}
|
|
475
|
-
catch (err) {
|
|
476
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
477
|
-
return { toast: { type: 'error', content: `权限切换失败:${msg}` } };
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
if (value.cmd === 'approval.answer') {
|
|
481
|
-
if (!runId ||
|
|
482
|
-
requestId === undefined ||
|
|
483
|
-
value.questionIndex === undefined ||
|
|
484
|
-
!value.option ||
|
|
485
|
-
!nonce) {
|
|
486
|
-
return { toast: { type: 'error', content: '缺少问题答案参数' } };
|
|
487
|
-
}
|
|
488
|
-
try {
|
|
489
|
-
await this.bridge.handleApprovalAnswer({
|
|
490
|
-
runId,
|
|
491
|
-
requestId,
|
|
492
|
-
questionIndex: value.questionIndex,
|
|
493
|
-
option: value.option,
|
|
494
|
-
nonce,
|
|
495
|
-
});
|
|
496
|
-
return { toast: { type: 'success', content: '已选择' } };
|
|
497
|
-
}
|
|
498
|
-
catch (err) {
|
|
499
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
500
|
-
return this.answerFailureToast(msg);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
if (value.cmd === 'approval.answerSubmit') {
|
|
504
|
-
if (!runId || requestId === undefined || value.questionIndex === undefined || !nonce) {
|
|
505
|
-
return { toast: { type: 'error', content: '缺少提交参数' } };
|
|
506
|
-
}
|
|
507
|
-
try {
|
|
508
|
-
await this.bridge.handleApprovalAnswerSubmit({
|
|
509
|
-
runId,
|
|
510
|
-
requestId,
|
|
511
|
-
questionIndex: value.questionIndex,
|
|
512
|
-
nonce,
|
|
513
|
-
});
|
|
514
|
-
return { toast: { type: 'success', content: '答案已提交' } };
|
|
515
|
-
}
|
|
516
|
-
catch (err) {
|
|
517
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
518
|
-
return this.answerFailureToast(msg);
|
|
519
|
-
}
|
|
577
|
+
if (spec.missing(value)) {
|
|
578
|
+
return { toast: { type: 'error', content: spec.missingToast } };
|
|
520
579
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
value.questionIndex === undefined ||
|
|
525
|
-
!nonce ||
|
|
526
|
-
!value.inputValue) {
|
|
527
|
-
return { toast: { type: 'error', content: '缺少答案文本参数' } };
|
|
528
|
-
}
|
|
529
|
-
try {
|
|
530
|
-
await this.bridge.handleApprovalAnswerCustom({
|
|
531
|
-
runId,
|
|
532
|
-
requestId,
|
|
533
|
-
questionIndex: value.questionIndex,
|
|
534
|
-
text: value.inputValue,
|
|
535
|
-
nonce,
|
|
536
|
-
});
|
|
537
|
-
return { toast: { type: 'success', content: '答案已提交' } };
|
|
538
|
-
}
|
|
539
|
-
catch (err) {
|
|
540
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
541
|
-
return this.answerFailureToast(msg);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
if (value.cmd === 'approval.answerNote') {
|
|
545
|
-
if (!runId ||
|
|
546
|
-
requestId === undefined ||
|
|
547
|
-
value.questionIndex === undefined ||
|
|
548
|
-
!nonce ||
|
|
549
|
-
!value.inputValue) {
|
|
550
|
-
return { toast: { type: 'error', content: '缺少补充说明参数' } };
|
|
551
|
-
}
|
|
552
|
-
try {
|
|
553
|
-
await this.bridge.handleApprovalAnswerNote({
|
|
554
|
-
runId,
|
|
555
|
-
requestId,
|
|
556
|
-
questionIndex: value.questionIndex,
|
|
557
|
-
text: value.inputValue,
|
|
558
|
-
nonce,
|
|
559
|
-
});
|
|
560
|
-
return { toast: { type: 'success', content: '补充说明已保存' } };
|
|
561
|
-
}
|
|
562
|
-
catch (err) {
|
|
563
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
564
|
-
return this.answerFailureToast(msg);
|
|
565
|
-
}
|
|
580
|
+
try {
|
|
581
|
+
await spec.run(this.bridge, value);
|
|
582
|
+
return { toast: { type: spec.toastType ?? 'success', content: spec.successToast } };
|
|
566
583
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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
|
+
catch (err) {
|
|
585
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
586
|
+
return spec.failureToast ? spec.failureToast(msg) : this.answerFailureToast(msg);
|
|
584
587
|
}
|
|
585
|
-
return { toast: { type: 'error', content: '未知的审批操作' } };
|
|
586
588
|
}
|
|
587
589
|
/**
|
|
588
590
|
* Handle queue.cancel: remove this message from the queue.
|
|
589
591
|
*/
|
|
590
592
|
async handleQueueCancel(value, ctx) {
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
if (!workspace || !messageId) {
|
|
594
|
-
await this.bridge.sendResult({ text: '⚠️ 卡片 payload 缺少必要信息' }, ctx);
|
|
593
|
+
const payload = await this.queuePayloadOrReply(value, ctx);
|
|
594
|
+
if (!payload)
|
|
595
595
|
return;
|
|
596
|
-
}
|
|
596
|
+
const { workspace, messageId } = payload;
|
|
597
597
|
const removed = this.bridge.removeFromQueue(workspace, messageId);
|
|
598
598
|
if (removed) {
|
|
599
599
|
// Update the queue card in-place to "cancelled" state
|
|
@@ -610,12 +610,10 @@ export class CommandRouter {
|
|
|
610
610
|
* Instead of asking user to resend, we keep the target task in queue and let it execute.
|
|
611
611
|
*/
|
|
612
612
|
async handleQueueImmediate(value, ctx) {
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
if (!workspace || !messageId) {
|
|
616
|
-
await this.bridge.sendResult({ text: '卡片 payload 缺少必要信息' }, ctx);
|
|
613
|
+
const payload = await this.queuePayloadOrReply(value, ctx);
|
|
614
|
+
if (!payload)
|
|
617
615
|
return;
|
|
618
|
-
}
|
|
616
|
+
const { workspace, messageId } = payload;
|
|
619
617
|
// 1. Check target exists BEFORE any await. The queue chain can advance
|
|
620
618
|
// while later awaits (interruptCurrentRun / markQueueCardExecuting's card
|
|
621
619
|
// send) are in flight, so the target must be read synchronously at entry.
|
|
@@ -751,13 +749,11 @@ export class CommandRouter {
|
|
|
751
749
|
* Handle queue.diagnose: show diagnostic info for why message is queuing.
|
|
752
750
|
*/
|
|
753
751
|
async handleQueueDiagnose(value, ctx) {
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
const targetUserId = value.userId ?? ctx.userId;
|
|
757
|
-
if (!workspace || !messageId) {
|
|
758
|
-
await this.bridge.sendResult({ text: '卡片 payload 缺少必要信息' }, ctx);
|
|
752
|
+
const payload = await this.queuePayloadOrReply(value, ctx);
|
|
753
|
+
if (!payload)
|
|
759
754
|
return;
|
|
760
|
-
}
|
|
755
|
+
const { workspace, messageId } = payload;
|
|
756
|
+
const targetUserId = value.userId ?? ctx.userId;
|
|
761
757
|
const task = this.bridge.getQueuedTask(workspace, messageId);
|
|
762
758
|
const queueInfo = this.bridge.getQueueInfo(workspace);
|
|
763
759
|
const activeRuns = this.bridge.getAllActiveRuns();
|
|
@@ -825,12 +821,10 @@ export class CommandRouter {
|
|
|
825
821
|
* Handle queue.edit: show input field for editing the queued message.
|
|
826
822
|
*/
|
|
827
823
|
async handleQueueEdit(value, ctx) {
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
if (!workspace || !messageId) {
|
|
831
|
-
await this.bridge.sendResult({ text: '⚠️ 卡片 payload 缺少必要信息' }, ctx);
|
|
824
|
+
const payload = await this.queuePayloadOrReply(value, ctx);
|
|
825
|
+
if (!payload)
|
|
832
826
|
return;
|
|
833
|
-
}
|
|
827
|
+
const { workspace, messageId } = payload;
|
|
834
828
|
// Get current message preview
|
|
835
829
|
const task = this.bridge.getQueuedTask(workspace, messageId);
|
|
836
830
|
if (!task) {
|
|
@@ -896,13 +890,11 @@ export class CommandRouter {
|
|
|
896
890
|
* when the callback response has no `card` field).
|
|
897
891
|
*/
|
|
898
892
|
async handleQueueInput(value, ctx) {
|
|
899
|
-
const workspace = value.workspace;
|
|
900
|
-
const messageId = value.messageId;
|
|
901
893
|
const newMessage = value.inputValue ?? value.formValue?.['newMessage'];
|
|
902
|
-
|
|
903
|
-
|
|
894
|
+
const payload = await this.queuePayloadOrReply(value, ctx);
|
|
895
|
+
if (!payload)
|
|
904
896
|
return;
|
|
905
|
-
}
|
|
897
|
+
const { workspace, messageId } = payload;
|
|
906
898
|
if (!newMessage) {
|
|
907
899
|
await this.bridge.sendResult({ text: '⚠️ 缺少新消息内容' }, ctx);
|
|
908
900
|
return;
|
|
@@ -1173,7 +1165,7 @@ export class CommandRouter {
|
|
|
1173
1165
|
async handleOrderDelete(value, ctx) {
|
|
1174
1166
|
const orderId = value.orderId;
|
|
1175
1167
|
if (!orderId) {
|
|
1176
|
-
return { toast: { type: 'error', content:
|
|
1168
|
+
return { toast: { type: 'error', content: CARD_PAYLOAD_MISSING } };
|
|
1177
1169
|
}
|
|
1178
1170
|
if (!this.orderStore.has(orderId)) {
|
|
1179
1171
|
return { toast: { type: 'error', content: '指令不存在或已被删除' } };
|
|
@@ -1201,17 +1193,22 @@ export class CommandRouter {
|
|
|
1201
1193
|
* Handle order.aliasEdit: 展示别名编辑卡(input + ✓ 提交图标)。
|
|
1202
1194
|
* 有别名时预填;提交 `order.aliasInput` 处理(含校验与唯一性)。
|
|
1203
1195
|
*/
|
|
1204
|
-
|
|
1196
|
+
/**
|
|
1197
|
+
* order 编辑卡公共骨架:payload 校验 → 查指令 → 构造 input 编辑卡 → 原地更新。
|
|
1198
|
+
* handleOrderAliasEdit / handleOrderTextEdit 原先各持一份 ~90% 相同的 4 段结构。
|
|
1199
|
+
*/
|
|
1200
|
+
async showOrderEditCard(value, ctx, opts) {
|
|
1205
1201
|
const orderId = value.orderId;
|
|
1206
1202
|
const offset = Math.max(0, Math.trunc(Number(value.offset) || 0));
|
|
1207
1203
|
if (!orderId) {
|
|
1208
|
-
return { toast: { type: 'error', content:
|
|
1204
|
+
return { toast: { type: 'error', content: CARD_PAYLOAD_MISSING } };
|
|
1209
1205
|
}
|
|
1210
1206
|
this.orderStore.reload();
|
|
1211
1207
|
const order = this.orderStore.get().find((o) => o.id === orderId);
|
|
1212
1208
|
if (!order) {
|
|
1213
1209
|
return { toast: { type: 'error', content: '指令不存在或已被删除' } };
|
|
1214
1210
|
}
|
|
1211
|
+
// 列表卡显示的是截断版(≤100 字符 + ...),编辑卡预览保持一致
|
|
1215
1212
|
const displayText = order.text.length > 100 ? order.text.slice(0, 97) + '...' : order.text;
|
|
1216
1213
|
// CardKit 2.0 input 自带 ✓ 提交图标(input_value 经 raw 回传,红线)。
|
|
1217
1214
|
// 不用 form 容器(触发 300123 无 submit button / 200621 嵌套 column)。
|
|
@@ -1220,7 +1217,7 @@ export class CommandRouter {
|
|
|
1220
1217
|
config: { wide_screen_mode: true },
|
|
1221
1218
|
header: {
|
|
1222
1219
|
template: 'blue',
|
|
1223
|
-
title: { tag: 'plain_text', content:
|
|
1220
|
+
title: { tag: 'plain_text', content: opts.title },
|
|
1224
1221
|
},
|
|
1225
1222
|
body: {
|
|
1226
1223
|
elements: [
|
|
@@ -1231,10 +1228,7 @@ export class CommandRouter {
|
|
|
1231
1228
|
{ tag: 'hr' },
|
|
1232
1229
|
{
|
|
1233
1230
|
tag: 'div',
|
|
1234
|
-
text: {
|
|
1235
|
-
tag: 'lark_md',
|
|
1236
|
-
content: '💡 输入别名名后点击右侧 ✓ 提交;留空提交 = 删除该别名',
|
|
1237
|
-
},
|
|
1231
|
+
text: { tag: 'lark_md', content: opts.hint },
|
|
1238
1232
|
},
|
|
1239
1233
|
{
|
|
1240
1234
|
tag: 'column_set',
|
|
@@ -1246,13 +1240,14 @@ export class CommandRouter {
|
|
|
1246
1240
|
elements: [
|
|
1247
1241
|
{
|
|
1248
1242
|
tag: 'input',
|
|
1249
|
-
name:
|
|
1250
|
-
placeholder: { tag: 'plain_text', content:
|
|
1251
|
-
default_value:
|
|
1243
|
+
name: opts.inputName,
|
|
1244
|
+
placeholder: { tag: 'plain_text', content: opts.placeholder },
|
|
1245
|
+
default_value: opts.defaultValue,
|
|
1246
|
+
...(opts.maxLength !== undefined ? { max_length: opts.maxLength } : {}),
|
|
1252
1247
|
behaviors: [
|
|
1253
1248
|
{
|
|
1254
1249
|
type: 'callback',
|
|
1255
|
-
value: { cmd:
|
|
1250
|
+
value: { cmd: opts.inputCmd, orderId, offset },
|
|
1256
1251
|
},
|
|
1257
1252
|
],
|
|
1258
1253
|
},
|
|
@@ -1265,6 +1260,18 @@ export class CommandRouter {
|
|
|
1265
1260
|
};
|
|
1266
1261
|
await this.bridge.updateCardInPlace(editCard, ctx);
|
|
1267
1262
|
}
|
|
1263
|
+
async handleOrderAliasEdit(value, ctx) {
|
|
1264
|
+
this.orderStore.reload();
|
|
1265
|
+
const order = this.orderStore.get().find((o) => o.id === value.orderId);
|
|
1266
|
+
return this.showOrderEditCard(value, ctx, {
|
|
1267
|
+
title: '✏️ 给指令起别名',
|
|
1268
|
+
hint: '💡 输入别名名后点击右侧 ✓ 提交;留空提交 = 删除该别名',
|
|
1269
|
+
inputName: 'aliasName',
|
|
1270
|
+
placeholder: '输入 $别名名(如 all)...',
|
|
1271
|
+
defaultValue: order?.alias ?? '',
|
|
1272
|
+
inputCmd: 'order.aliasInput',
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1268
1275
|
/**
|
|
1269
1276
|
* Handle order.aliasInput: 提交别名(绑/改/解绑),随后重渲染 /order 列表卡。
|
|
1270
1277
|
* 留空 = 解绑;非空按命名规则 + 全局唯一校验,失败仅 toast 不重渲染。
|
|
@@ -1274,7 +1281,7 @@ export class CommandRouter {
|
|
|
1274
1281
|
const offset = Math.max(0, Math.trunc(Number(value.offset) || 0));
|
|
1275
1282
|
const name = value.inputValue ?? value.formValue?.['aliasName'];
|
|
1276
1283
|
if (!orderId) {
|
|
1277
|
-
return { toast: { type: 'error', content:
|
|
1284
|
+
return { toast: { type: 'error', content: CARD_PAYLOAD_MISSING } };
|
|
1278
1285
|
}
|
|
1279
1286
|
this.orderStore.reload();
|
|
1280
1287
|
const order = this.orderStore.get().find((o) => o.id === orderId);
|
|
@@ -1308,7 +1315,7 @@ export class CommandRouter {
|
|
|
1308
1315
|
const orderId = value.orderId;
|
|
1309
1316
|
const offset = Math.max(0, Math.trunc(Number(value.offset) || 0));
|
|
1310
1317
|
if (!orderId) {
|
|
1311
|
-
return { toast: { type: 'error', content:
|
|
1318
|
+
return { toast: { type: 'error', content: CARD_PAYLOAD_MISSING } };
|
|
1312
1319
|
}
|
|
1313
1320
|
this.orderStore.reload();
|
|
1314
1321
|
const order = this.orderStore.get().find((o) => o.id === orderId);
|
|
@@ -1335,73 +1342,20 @@ export class CommandRouter {
|
|
|
1335
1342
|
* updateCardInPlace)。
|
|
1336
1343
|
*/
|
|
1337
1344
|
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
1345
|
this.orderStore.reload();
|
|
1344
|
-
const order = this.orderStore.get().find((o) => o.id === orderId);
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
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);
|
|
1346
|
+
const order = this.orderStore.get().find((o) => o.id === value.orderId);
|
|
1347
|
+
// input 组件 default_value 预填完整 text(不受截断影响),提交后生效完整文本
|
|
1348
|
+
return this.showOrderEditCard(value, ctx, {
|
|
1349
|
+
title: '✏️ 编辑指令',
|
|
1350
|
+
hint: '💡 修改后点击右侧 ✓ 提交;空白提交 = 报错;最多 200 字符',
|
|
1351
|
+
inputName: 'text',
|
|
1352
|
+
placeholder: '输入新的指令文本...',
|
|
1353
|
+
defaultValue: order?.text ?? '',
|
|
1354
|
+
inputCmd: 'order.textInput',
|
|
1355
|
+
// 与 MAX_TEXT_LENGTH=200 对齐:超长在输入侧直接截断,避免提交后才报错
|
|
1356
|
+
// (后端 updateText 仍会二次校验,双保险)
|
|
1357
|
+
maxLength: 200,
|
|
1358
|
+
});
|
|
1405
1359
|
}
|
|
1406
1360
|
/**
|
|
1407
1361
|
* Handle order.textInput: 提交指令文本编辑。校验 orderId → OrderStore.updateText
|
|
@@ -1415,7 +1369,7 @@ export class CommandRouter {
|
|
|
1415
1369
|
const offset = Math.max(0, Math.trunc(Number(value.offset) || 0));
|
|
1416
1370
|
const raw = value.inputValue ?? value.formValue?.['text'];
|
|
1417
1371
|
if (!orderId) {
|
|
1418
|
-
return { toast: { type: 'error', content:
|
|
1372
|
+
return { toast: { type: 'error', content: CARD_PAYLOAD_MISSING } };
|
|
1419
1373
|
}
|
|
1420
1374
|
this.orderStore.reload();
|
|
1421
1375
|
const order = this.orderStore.get().find((o) => o.id === orderId);
|
|
@@ -1494,7 +1448,7 @@ export class CommandRouter {
|
|
|
1494
1448
|
async handleWsRemove(value, ctx) {
|
|
1495
1449
|
const name = value.name;
|
|
1496
1450
|
if (!name) {
|
|
1497
|
-
return { toast: { type: 'error', content:
|
|
1451
|
+
return { toast: { type: 'error', content: CARD_PAYLOAD_MISSING } };
|
|
1498
1452
|
}
|
|
1499
1453
|
// Execute the removal (consumes the write-side effect; the returned text
|
|
1500
1454
|
// is discarded — feedback flows through the toast + refreshed card).
|
|
@@ -2557,12 +2511,8 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2557
2511
|
size: fs.statSync(path.join(targetDir, f.name)).size,
|
|
2558
2512
|
})),
|
|
2559
2513
|
];
|
|
2560
|
-
// Pagination calculations
|
|
2561
|
-
const totalCount = allItems.
|
|
2562
|
-
const totalPages = Math.max(1, Math.ceil(totalCount / CommandRouter.LS_PAGE_SIZE));
|
|
2563
|
-
const currentPage = Math.floor(offset / CommandRouter.LS_PAGE_SIZE) + 1;
|
|
2564
|
-
const pageItems = allItems.slice(offset, offset + CommandRouter.LS_PAGE_SIZE);
|
|
2565
|
-
const hasPagination = totalCount > CommandRouter.LS_PAGE_SIZE;
|
|
2514
|
+
// Pagination calculations(clamp 到最后一页边界,原先不 clamp 会显示空页)
|
|
2515
|
+
const { totalCount, totalPages, safeOffset, currentPage, pageItems, hasPagination } = pageSlice(allItems, offset, CommandRouter.LS_PAGE_SIZE);
|
|
2566
2516
|
// Check if we need to show parent directory button
|
|
2567
2517
|
const parentDir = path.dirname(targetDir);
|
|
2568
2518
|
const hasParent = parentDir !== targetDir;
|
|
@@ -2593,7 +2543,9 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2593
2543
|
text: { tag: 'plain_text', content: '刷新' },
|
|
2594
2544
|
type: 'default',
|
|
2595
2545
|
size: 'small',
|
|
2596
|
-
behaviors: [
|
|
2546
|
+
behaviors: [
|
|
2547
|
+
{ type: 'callback', value: { cmd: 'ls.refresh', path: targetDir, offset: safeOffset } },
|
|
2548
|
+
],
|
|
2597
2549
|
});
|
|
2598
2550
|
// Show "切换" button when viewing a subdirectory (to switch cwd to this directory)
|
|
2599
2551
|
if (isSubdir) {
|
|
@@ -2701,7 +2653,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2701
2653
|
if (hasPagination) {
|
|
2702
2654
|
const bar = paginationBar({
|
|
2703
2655
|
cmd: 'ls.page',
|
|
2704
|
-
offset,
|
|
2656
|
+
offset: safeOffset,
|
|
2705
2657
|
pageSize: CommandRouter.LS_PAGE_SIZE,
|
|
2706
2658
|
total: totalCount,
|
|
2707
2659
|
extra: { path: targetDir },
|
|
@@ -2795,13 +2747,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2795
2747
|
}
|
|
2796
2748
|
// Pagination calculations (mirror cmdOrder): clamp stale/out-of-range
|
|
2797
2749
|
// offsets so prev/next always step by WS_PAGE_SIZE.
|
|
2798
|
-
const totalCount = entries
|
|
2799
|
-
const totalPages = Math.max(1, Math.ceil(totalCount / WS_PAGE_SIZE));
|
|
2800
|
-
const maxOffset = Math.max(0, (totalPages - 1) * WS_PAGE_SIZE);
|
|
2801
|
-
const safeOffset = clampInt(offset, 0, maxOffset);
|
|
2802
|
-
const currentPage = Math.floor(safeOffset / WS_PAGE_SIZE) + 1;
|
|
2803
|
-
const pageEntries = entries.slice(safeOffset, safeOffset + WS_PAGE_SIZE);
|
|
2804
|
-
const hasPagination = totalCount > WS_PAGE_SIZE;
|
|
2750
|
+
const { totalCount, totalPages, safeOffset, currentPage, pageItems: pageEntries, hasPagination, } = pageSlice(entries, offset, WS_PAGE_SIZE);
|
|
2805
2751
|
// Build body elements: current cwd + workspace list with dividers
|
|
2806
2752
|
const bodyElements = [];
|
|
2807
2753
|
// Current working directory section
|
|
@@ -3214,8 +3160,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3214
3160
|
}
|
|
3215
3161
|
// Pagination bar (only shown when there are more sessions than pageSize)
|
|
3216
3162
|
if (total > pageSize) {
|
|
3217
|
-
const totalPages =
|
|
3218
|
-
const currentPage = Math.floor(pageOffset / pageSize) + 1;
|
|
3163
|
+
const { totalPages, currentPage } = pageInfo(total, pageOffset, pageSize);
|
|
3219
3164
|
const bar = paginationBar({
|
|
3220
3165
|
cmd: 'resume.page',
|
|
3221
3166
|
offset: pageOffset,
|
|
@@ -3259,9 +3204,9 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3259
3204
|
buildActiveCardFromMemory(activeRuns, activeBashRuns, offset = 0) {
|
|
3260
3205
|
const totalCount = activeRuns.length + activeBashRuns.length;
|
|
3261
3206
|
const pageSize = ACTIVE_PAGE_SIZE;
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
const currentPage =
|
|
3207
|
+
// 统一 clamp 到最后一页边界(原先 clamp 到 totalCount-1,过期 offset 会
|
|
3208
|
+
// 泄漏成「第 1 页只显示末尾几条」的残页)
|
|
3209
|
+
const { totalPages, safeOffset, currentPage } = pageInfo(totalCount, offset, pageSize);
|
|
3265
3210
|
// Slice: distribute offset across agent runs first, then bash runs
|
|
3266
3211
|
const elements = [];
|
|
3267
3212
|
// Page info
|
|
@@ -3576,14 +3521,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3576
3521
|
}
|
|
3577
3522
|
else {
|
|
3578
3523
|
// Pagination calculations
|
|
3579
|
-
const totalCount = allOrders
|
|
3580
|
-
const totalPages = Math.max(1, Math.ceil(totalCount / ORDER_PAGE_SIZE));
|
|
3581
|
-
// Clamp offset to last page boundary to avoid empty pages
|
|
3582
|
-
const maxOffset = Math.max(0, (totalPages - 1) * ORDER_PAGE_SIZE);
|
|
3583
|
-
const safeOffset = clampInt(offset, 0, maxOffset);
|
|
3584
|
-
const currentPage = Math.floor(safeOffset / ORDER_PAGE_SIZE) + 1;
|
|
3585
|
-
const pageOrders = allOrders.slice(safeOffset, safeOffset + ORDER_PAGE_SIZE);
|
|
3586
|
-
const hasPagination = totalCount > ORDER_PAGE_SIZE;
|
|
3524
|
+
const { totalCount, totalPages, safeOffset, currentPage, pageItems: pageOrders, hasPagination, } = pageSlice(allOrders, offset, ORDER_PAGE_SIZE);
|
|
3587
3525
|
for (let i = 0; i < pageOrders.length; i++) {
|
|
3588
3526
|
const order = pageOrders[i];
|
|
3589
3527
|
const displayText = order.text.length > 100 ? order.text.slice(0, 97) + '...' : order.text;
|