lark-remote 0.5.6 → 0.5.8
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 +46 -6
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/dist/bridge/index.d.ts +63 -1
- package/dist/bridge/index.js +147 -14
- package/dist/bridge/queue-manager.js +41 -58
- package/dist/card/approval-render.js +2 -1
- package/dist/card/bash-card-session.d.ts +0 -1
- package/dist/card/bash-card-session.js +0 -8
- package/dist/card/bash-renderer.js +2 -1
- package/dist/card/card-budget.d.ts +37 -0
- package/dist/card/card-budget.js +143 -24
- package/dist/card/card-session.d.ts +3 -13
- package/dist/card/card-session.js +4 -18
- package/dist/card/card-shared.d.ts +11 -0
- package/dist/card/card-shared.js +19 -13
- package/dist/card/run-card-session.d.ts +0 -1
- package/dist/card/run-card-session.js +0 -6
- package/dist/card/run-renderer.d.ts +18 -0
- package/dist/card/run-renderer.js +330 -62
- package/dist/card/run-state.d.ts +26 -0
- package/dist/card/run-state.js +45 -0
- package/dist/card/text-truncate.d.ts +3 -5
- package/dist/card/text-truncate.js +3 -5
- package/dist/card/time.d.ts +14 -0
- package/dist/card/time.js +30 -0
- package/dist/card/tool-render.d.ts +15 -2
- package/dist/card/tool-render.js +42 -82
- package/dist/common/text-width.d.ts +24 -0
- package/dist/common/text-width.js +93 -0
- package/dist/common/tool-summary.d.ts +19 -0
- package/dist/common/tool-summary.js +171 -0
- package/dist/common/turn-failure.d.ts +13 -0
- package/dist/common/turn-failure.js +18 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +1 -1
- package/dist/connector/index.js +1 -1
- package/dist/index.js +66 -13
- package/dist/order/index.d.ts +2 -0
- package/dist/order/index.js +2 -0
- package/dist/platform/command.js +1 -1
- package/dist/platform/shell.d.ts +2 -2
- package/dist/platform/spawn.d.ts +1 -1
- package/dist/platform/spawn.js +2 -2
- package/dist/platform/types.d.ts +1 -1
- package/dist/router/card-helpers.d.ts +18 -16
- package/dist/router/card-helpers.js +88 -46
- package/dist/router/config/codex.js +2 -3
- package/dist/router/config/common/model-patch.d.ts +8 -0
- package/dist/router/config/common/model-patch.js +13 -0
- package/dist/router/config/common/render.js +30 -77
- package/dist/router/config/dsh.js +2 -1
- package/dist/router/config/kimi.js +3 -3
- package/dist/router/index.d.ts +9 -11
- package/dist/router/index.js +165 -356
- package/dist/router/order-exec-dispatch.js +2 -1
- package/dist/runner/claude/session.d.ts +1 -1
- package/dist/runner/claude/session.js +3 -3
- package/dist/runner/common/defaults.d.ts +2 -0
- package/dist/runner/common/defaults.js +3 -0
- package/dist/runner/common/jsonrpc/connection-manager.js +1 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +1 -1
- package/dist/runner/common/jsonrpc/transport.js +3 -3
- package/dist/runner/common/spawning-runner.d.ts +1 -1
- package/dist/runner/common/spawning-runner.js +2 -2
- package/dist/runner/dsh/auth.js +2 -1
- package/dist/runner/dsh/client.d.ts +100 -3
- package/dist/runner/dsh/client.js +290 -6
- package/dist/runner/dsh/exit-cancel.d.ts +32 -0
- package/dist/runner/dsh/exit-cancel.js +72 -0
- package/dist/runner/dsh/inflight.d.ts +62 -0
- package/dist/runner/dsh/inflight.js +140 -0
- package/dist/runner/dsh/runner.d.ts +90 -4
- package/dist/runner/dsh/runner.js +436 -18
- package/dist/runner/dsh/translator.d.ts +1 -1
- package/dist/runner/dsh/translator.js +103 -1
- package/dist/runner/dsh/types.d.ts +60 -0
- package/dist/runner/types.d.ts +95 -2
- package/dist/runner/types.js +9 -0
- package/dist/session/claude/session-index.d.ts +0 -4
- package/dist/session/claude/session-index.js +0 -8
- package/dist/session/claude/sessions.js +2 -2
- package/dist/session/dsh/sessions.js +157 -17
- package/dist/update/install.js +1 -1
- package/package.json +1 -1
package/dist/router/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { getConfigDir, getConfigValue, setConfigValue, setConfigValues, mapAgent
|
|
|
6
6
|
import { syncAgentChoices } from '../runner/index.js';
|
|
7
7
|
import { resolveDshCredentialsFile } from '../runner/dsh/auth.js';
|
|
8
8
|
import { WorkspaceStore } from '../workspace/index.js';
|
|
9
|
-
import { OrderStore } from '../order/index.js';
|
|
9
|
+
import { OrderStore, ORDER_NOT_FOUND_TEXT } from '../order/index.js';
|
|
10
10
|
import { resolveAlias } from '../order/alias-resolve.js';
|
|
11
11
|
import { diffConfig, setNestedValue } from './config-diff.js';
|
|
12
12
|
import { AGENT_KINDS } from '../runner/types.js';
|
|
@@ -16,7 +16,7 @@ import { clampInt } from '../common/clamp.js';
|
|
|
16
16
|
import { activeRunUsage } from './usage-format.js';
|
|
17
17
|
import { markdownDiv, buildSessionHistoryCard, paginationBar, searchBar, parseJumpOffset, SEARCH_INPUT_NAME, PAGE_JUMP_INVALID_HINT, } from './card-helpers.js';
|
|
18
18
|
import { MAX_FILE_UPLOAD_SIZE } from '../connector/file-limits.js';
|
|
19
|
-
import { newSessionButton, resumeCompactButton, agentDisplayName } from '../card/card-shared.js';
|
|
19
|
+
import { cardButton, newSessionButton, resumeCompactButton, agentDisplayName, } from '../card/card-shared.js';
|
|
20
20
|
import { displayName } from '../platform/path.js';
|
|
21
21
|
import { atomicWrite } from '../persistence/atomic-write.js';
|
|
22
22
|
import { checkLatestVersion as defaultCheckLatestVersion, compareVersions as defaultCompareVersions, runInstallLatest as defaultRunInstallLatest, } from '../update/index.js';
|
|
@@ -133,6 +133,18 @@ const RESUME_CONTENT_PREFETCH = 5;
|
|
|
133
133
|
* `New Session`、空串),都不是真实输入,不得渲染成行标题。
|
|
134
134
|
*/
|
|
135
135
|
const RESUME_SUMMARY_PLACEHOLDERS = new Set(['', '(no user message)', '(无摘要)', 'New Session']);
|
|
136
|
+
/** 列表卡「最近输入 / 最后输出」单行预览的长度上限(中文按字符,手机约两行)。 */
|
|
137
|
+
const RESUME_PREVIEW_MAX = 60;
|
|
138
|
+
/**
|
|
139
|
+
* 列表卡单行预览:压平空白、去掉反引号,再按字符截断。
|
|
140
|
+
*
|
|
141
|
+
* 为什么去反引号:agent 的输出里 `code` 很常见,截断点落在一对反引号中间时
|
|
142
|
+
* 后半张卡片的 markdown 会被当成代码块渲染(lark_md 不闭合就不回退)。
|
|
143
|
+
*/
|
|
144
|
+
function sessionPreviewLine(text, max) {
|
|
145
|
+
const flat = text.replace(/`/g, '').replace(/\s+/g, ' ').trim();
|
|
146
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
147
|
+
}
|
|
136
148
|
/**
|
|
137
149
|
* Card-action commands that must run with enqueue({ immediate: true }).
|
|
138
150
|
* These bypass the serial queue and execute immediately.
|
|
@@ -646,6 +658,9 @@ export class CommandRouter {
|
|
|
646
658
|
case 'stop':
|
|
647
659
|
await this.handleCardStop(value, ctx);
|
|
648
660
|
return;
|
|
661
|
+
case 'job.kill':
|
|
662
|
+
await this.handleJobKill(value, ctx);
|
|
663
|
+
return;
|
|
649
664
|
case 'queue.cancel':
|
|
650
665
|
// 撤销:从队列中删除这个消息
|
|
651
666
|
await this.handleQueueCancel(value, ctx);
|
|
@@ -832,12 +847,17 @@ export class CommandRouter {
|
|
|
832
847
|
// 2. Stop current running process
|
|
833
848
|
// (Note: queue executing count is reset inside Bridge.interruptCurrentRun
|
|
834
849
|
// so all stop paths — /stop, /t, card stop, queue.immediate — are covered.)
|
|
835
|
-
const
|
|
850
|
+
const interrupt = await this.bridge.interruptCurrentRunDetailed({
|
|
836
851
|
userId: ctx.userId,
|
|
837
852
|
chatId: ctx.chatId,
|
|
838
853
|
cwd,
|
|
839
854
|
});
|
|
840
|
-
|
|
855
|
+
const stopped = interrupt.stopped;
|
|
856
|
+
// A remote (DSH) stop is only a request; when it cannot be confirmed the
|
|
857
|
+
// target must not be promised "immediate" execution — its prompt would sit
|
|
858
|
+
// behind the still-running turn with no visible progress.
|
|
859
|
+
const stopUnconfirmed = interrupt.stop?.status === 'unconfirmed';
|
|
860
|
+
getLogger().info(`[router] queue.immediate: stopped=${stopped} unconfirmed=${stopUnconfirmed}`);
|
|
841
861
|
// 3. Remove all tasks BEFORE this one (by position in the current queue).
|
|
842
862
|
// The target may have begun while interruptCurrentRun was awaiting (the chain
|
|
843
863
|
// advances concurrently): then it is already out of the snapshot and every
|
|
@@ -910,7 +930,14 @@ export class CommandRouter {
|
|
|
910
930
|
return;
|
|
911
931
|
}
|
|
912
932
|
const workspaceBusy = this.bridge.isBusyFor(cwd);
|
|
913
|
-
if (
|
|
933
|
+
if (stopUnconfirmed) {
|
|
934
|
+
const name = interrupt.agentKind ? agentDisplayName(interrupt.agentKind) : 'agent';
|
|
935
|
+
await this.bridge.sendResult({
|
|
936
|
+
text: `⚠️ 未能确认 ${name} 的 turn 已停止,它可能仍在后台运行。\n` +
|
|
937
|
+
`已清除 ${removedCount} 条排队消息;您的消息已提交,但可能要等该 turn 结束后才开始。`,
|
|
938
|
+
}, ctx);
|
|
939
|
+
}
|
|
940
|
+
else if (!workspaceBusy) {
|
|
914
941
|
// Normal path: nothing new began during the stop, the target is next.
|
|
915
942
|
// Mark its own card as executing now so buttons grey out immediately.
|
|
916
943
|
// Done BEFORE any removal so getQueuedTask still returns the task with
|
|
@@ -1351,17 +1378,43 @@ export class CommandRouter {
|
|
|
1351
1378
|
await this.bridge.sendResult({ text: '⚠️ 无效的停止请求,缺少必要信息' }, ctx);
|
|
1352
1379
|
return;
|
|
1353
1380
|
}
|
|
1354
|
-
const
|
|
1381
|
+
const interrupt = await this.bridge.interruptCurrentRunDetailed({
|
|
1355
1382
|
userId: ctx.userId,
|
|
1356
1383
|
chatId: ctx.chatId,
|
|
1357
1384
|
runId: value.runId,
|
|
1358
1385
|
});
|
|
1386
|
+
const stopped = interrupt.stopped;
|
|
1359
1387
|
// Regression 2026-06-22: card stop button silently no-op'd when the
|
|
1360
1388
|
// run had already exited. Give the user a visible result.
|
|
1361
1389
|
if (!stopped) {
|
|
1362
1390
|
await this.bridge.sendResult({ text: '该任务已结束,无需终止' }, ctx);
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
// DSH: `session/cancel` is only a request. If the host still reports the
|
|
1394
|
+
// turn open, say so instead of letting the card imply a clean stop.
|
|
1395
|
+
if (interrupt.stop?.status === 'unconfirmed') {
|
|
1396
|
+
const name = interrupt.agentKind ? agentDisplayName(interrupt.agentKind) : 'agent';
|
|
1397
|
+
await this.bridge.sendResult({
|
|
1398
|
+
text: `⚠️ 未能确认 ${name} 的 turn 已停止,它可能仍在后台运行。\n` +
|
|
1399
|
+
'请在 DSH Web UI 检查;需要终止时再点一次停止。',
|
|
1400
|
+
}, ctx);
|
|
1363
1401
|
}
|
|
1364
1402
|
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Handle a terminal card's 「停止」 on one background job.
|
|
1405
|
+
*
|
|
1406
|
+
* 任务跨 run 生命周期,所以卡片按钮是唯一入口;失败必须给出可读原因
|
|
1407
|
+
* (任务已自行结束、服务重启后快照被回收),不能点了没反应。
|
|
1408
|
+
*/
|
|
1409
|
+
async handleJobKill(value, ctx) {
|
|
1410
|
+
const runId = typeof value.runId === 'string' ? value.runId : '';
|
|
1411
|
+
const jobId = typeof value.jobId === 'string' ? value.jobId : '';
|
|
1412
|
+
if (!runId || !jobId)
|
|
1413
|
+
return;
|
|
1414
|
+
if (await this.bridge.killBackgroundJob(runId, jobId, ctx))
|
|
1415
|
+
return;
|
|
1416
|
+
await this.bridge.sendResult({ text: '停止失败:任务可能已自行结束,或记录已回收' }, ctx);
|
|
1417
|
+
}
|
|
1365
1418
|
/**
|
|
1366
1419
|
* Resolve an order's text at the enqueue boundary so order.exec can enter
|
|
1367
1420
|
* the serial queue as an equivalent user message (Plan A) — the same path a
|
|
@@ -1463,7 +1516,7 @@ export class CommandRouter {
|
|
|
1463
1516
|
return { toast: { type: 'error', content: CARD_PAYLOAD_MISSING } };
|
|
1464
1517
|
}
|
|
1465
1518
|
if (!this.orderStore.has(orderId)) {
|
|
1466
|
-
return { toast: { type: 'error', content:
|
|
1519
|
+
return { toast: { type: 'error', content: ORDER_NOT_FOUND_TEXT } };
|
|
1467
1520
|
}
|
|
1468
1521
|
// Delete the order
|
|
1469
1522
|
this.orderStore.remove(orderId);
|
|
@@ -1501,7 +1554,7 @@ export class CommandRouter {
|
|
|
1501
1554
|
this.orderStore.reload();
|
|
1502
1555
|
const order = this.orderStore.get().find((o) => o.id === orderId);
|
|
1503
1556
|
if (!order) {
|
|
1504
|
-
return { toast: { type: 'error', content:
|
|
1557
|
+
return { toast: { type: 'error', content: ORDER_NOT_FOUND_TEXT } };
|
|
1505
1558
|
}
|
|
1506
1559
|
// 列表卡显示的是截断版(≤100 字符 + ...),编辑卡预览保持一致
|
|
1507
1560
|
const displayText = order.text.length > 100 ? order.text.slice(0, 97) + '...' : order.text;
|
|
@@ -1573,7 +1626,7 @@ export class CommandRouter {
|
|
|
1573
1626
|
*/
|
|
1574
1627
|
async handleOrderAliasInput(value, ctx) {
|
|
1575
1628
|
const name = value.inputValue ?? value.formValue?.['aliasName'];
|
|
1576
|
-
return this.mutateOrderAndRefreshCard(value, ctx, { missingToast:
|
|
1629
|
+
return this.mutateOrderAndRefreshCard(value, ctx, { missingToast: ORDER_NOT_FOUND_TEXT }, (order) => {
|
|
1577
1630
|
const trimmed = name?.trim() ?? '';
|
|
1578
1631
|
// 变更前快照旧别名:setAlias 会原地变异 order 对象(delete entry.alias),
|
|
1579
1632
|
// 删除后 order.alias 已为 undefined,直接用会让成功文案显示 `$undefined`。
|
|
@@ -1635,7 +1688,7 @@ export class CommandRouter {
|
|
|
1635
1688
|
*/
|
|
1636
1689
|
async handleOrderTextInput(value, ctx) {
|
|
1637
1690
|
const raw = value.inputValue ?? value.formValue?.['text'];
|
|
1638
|
-
return this.mutateOrderAndRefreshCard(value, ctx, { missingToast:
|
|
1691
|
+
return this.mutateOrderAndRefreshCard(value, ctx, { missingToast: ORDER_NOT_FOUND_TEXT }, (order) => {
|
|
1639
1692
|
let updated;
|
|
1640
1693
|
try {
|
|
1641
1694
|
updated = this.orderStore.updateText(order.id, raw ?? '');
|
|
@@ -1645,7 +1698,7 @@ export class CommandRouter {
|
|
|
1645
1698
|
}
|
|
1646
1699
|
if (!updated) {
|
|
1647
1700
|
// reload + find 后 updateText 不应返回 undefined;防御性兜底(如并发删除)。
|
|
1648
|
-
return { ok: false, error:
|
|
1701
|
+
return { ok: false, error: ORDER_NOT_FOUND_TEXT };
|
|
1649
1702
|
}
|
|
1650
1703
|
const preview = updated.text.length > 50 ? updated.text.slice(0, 50) + '...' : updated.text;
|
|
1651
1704
|
return { ok: true, toast: `✅ 已更新指令: ${preview}` };
|
|
@@ -2086,7 +2139,7 @@ export class CommandRouter {
|
|
|
2086
2139
|
switchSessionId = '';
|
|
2087
2140
|
}
|
|
2088
2141
|
}
|
|
2089
|
-
// DSH preset 变更 = 下次 run 新建 session
|
|
2142
|
+
// DSH preset 变更 = 下次 run 新建 session:
|
|
2090
2143
|
// preset 在 session 创建时固定,中途切换会被服务端拒绝(agent-preset-conflict)。
|
|
2091
2144
|
// 旧 sessionId 存入 previousSessions 停车位(可手动 /resume,但 /resume 会做
|
|
2092
2145
|
// preset 一致性校验),当前 sessionId 清空——下次 run 走 session.create 新建。
|
|
@@ -2281,13 +2334,7 @@ export class CommandRouter {
|
|
|
2281
2334
|
width: 'weighted',
|
|
2282
2335
|
weight: 2,
|
|
2283
2336
|
elements: [
|
|
2284
|
-
{
|
|
2285
|
-
tag: 'button',
|
|
2286
|
-
text: { tag: 'plain_text', content: c.label },
|
|
2287
|
-
type: 'default',
|
|
2288
|
-
size: 'small',
|
|
2289
|
-
behaviors: [{ type: 'callback', value: { cmd: `help.${c.cmd}` } }],
|
|
2290
|
-
},
|
|
2337
|
+
cardButton(c.label, { cmd: `help.${c.cmd}` }, { type: 'default', size: 'small' }),
|
|
2291
2338
|
],
|
|
2292
2339
|
},
|
|
2293
2340
|
{
|
|
@@ -2431,11 +2478,20 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2431
2478
|
};
|
|
2432
2479
|
}
|
|
2433
2480
|
async cmdStop(ctx) {
|
|
2434
|
-
const
|
|
2481
|
+
const interrupt = await this.bridge.interruptCurrentRunDetailed({
|
|
2435
2482
|
userId: ctx.userId,
|
|
2436
2483
|
chatId: ctx.chatId,
|
|
2437
2484
|
});
|
|
2438
|
-
|
|
2485
|
+
if (!interrupt.stopped)
|
|
2486
|
+
return { text: '当前没有运行中的进程' };
|
|
2487
|
+
if (interrupt.stop?.status === 'unconfirmed') {
|
|
2488
|
+
const name = interrupt.agentKind ? agentDisplayName(interrupt.agentKind) : 'agent';
|
|
2489
|
+
return {
|
|
2490
|
+
text: `⚠️ 未能确认 ${name} 的 turn 已停止,它可能仍在后台运行。\n` +
|
|
2491
|
+
'请在 DSH Web UI 检查;需要终止时再发一次 /stop。',
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
return null;
|
|
2439
2495
|
}
|
|
2440
2496
|
async cmdExit() {
|
|
2441
2497
|
// Signal handle() to invoke exitHandler after the reply is sent.
|
|
@@ -2754,17 +2810,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2754
2810
|
buildResumeActionButtons(opts) {
|
|
2755
2811
|
const actions = [];
|
|
2756
2812
|
if (opts.isActive) {
|
|
2757
|
-
actions.push({
|
|
2758
|
-
tag: 'button',
|
|
2759
|
-
text: { tag: 'plain_text', content: '⏹ 终止' },
|
|
2760
|
-
type: 'danger',
|
|
2761
|
-
behaviors: [
|
|
2762
|
-
{
|
|
2763
|
-
type: 'callback',
|
|
2764
|
-
value: { cmd: 'stop', runId: opts.activeRunRunId, cwd: opts.cwd },
|
|
2765
|
-
},
|
|
2766
|
-
],
|
|
2767
|
-
});
|
|
2813
|
+
actions.push(cardButton('⏹ 终止', { cmd: 'stop', runId: opts.activeRunRunId, cwd: opts.cwd }, { type: 'danger' }));
|
|
2768
2814
|
}
|
|
2769
2815
|
if (opts.compact && !opts.isActive) {
|
|
2770
2816
|
actions.push(resumeCompactButton(opts.compact.sessionId, opts.compact.agentKind));
|
|
@@ -2880,88 +2926,32 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2880
2926
|
// Build header with parent button
|
|
2881
2927
|
const headerElements = [];
|
|
2882
2928
|
if (hasParent) {
|
|
2883
|
-
headerElements.push({
|
|
2884
|
-
tag: 'button',
|
|
2885
|
-
text: { tag: 'plain_text', content: '上级' },
|
|
2886
|
-
type: 'default',
|
|
2887
|
-
size: 'small',
|
|
2888
|
-
behaviors: [
|
|
2889
|
-
{ type: 'callback', value: { cmd: 'ls.browse', path: parentDir, root: browseRoot } },
|
|
2890
|
-
],
|
|
2891
|
-
});
|
|
2929
|
+
headerElements.push(cardButton('上级', { cmd: 'ls.browse', path: parentDir, root: browseRoot }, { type: 'default', size: 'small' }));
|
|
2892
2930
|
}
|
|
2893
2931
|
// Show "返回" button when browsing away from the /ls root (回到 /ls 指定的目录)
|
|
2894
2932
|
if (canReturn) {
|
|
2895
|
-
headerElements.push({
|
|
2896
|
-
tag: 'button',
|
|
2897
|
-
text: { tag: 'plain_text', content: '返回' },
|
|
2898
|
-
type: 'default',
|
|
2899
|
-
size: 'small',
|
|
2900
|
-
behaviors: [
|
|
2901
|
-
{ type: 'callback', value: { cmd: 'ls.browse', path: browseRoot, root: browseRoot } },
|
|
2902
|
-
],
|
|
2903
|
-
});
|
|
2933
|
+
headerElements.push(cardButton('返回', { cmd: 'ls.browse', path: browseRoot, root: browseRoot }, { type: 'default', size: 'small' }));
|
|
2904
2934
|
}
|
|
2905
|
-
headerElements.push({
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
type: 'callback',
|
|
2913
|
-
value: {
|
|
2914
|
-
cmd: 'ls.refresh',
|
|
2915
|
-
path: targetDir,
|
|
2916
|
-
offset: safeOffset,
|
|
2917
|
-
root: browseRoot,
|
|
2918
|
-
...(filter ? { q: filter } : {}),
|
|
2919
|
-
},
|
|
2920
|
-
},
|
|
2921
|
-
],
|
|
2922
|
-
});
|
|
2935
|
+
headerElements.push(cardButton('刷新', {
|
|
2936
|
+
cmd: 'ls.refresh',
|
|
2937
|
+
path: targetDir,
|
|
2938
|
+
offset: safeOffset,
|
|
2939
|
+
root: browseRoot,
|
|
2940
|
+
...(filter ? { q: filter } : {}),
|
|
2941
|
+
}, { type: 'default', size: 'small' }));
|
|
2923
2942
|
// Show "切换" button when viewing a subdirectory (to switch cwd to this directory)
|
|
2924
2943
|
if (isSubdir) {
|
|
2925
|
-
headerElements.push(
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
size: 'small',
|
|
2930
|
-
// 切换只改 cwd,**不重置浏览起点**:root 一路带到切换后的卡片,
|
|
2931
|
-
// 否则「返回」会退化成「回到刚切换到的目录」(用户反馈的缺陷)。
|
|
2932
|
-
behaviors: [
|
|
2933
|
-
{ type: 'callback', value: { cmd: 'ls.switch', path: targetDir, root: browseRoot } },
|
|
2934
|
-
],
|
|
2935
|
-
});
|
|
2944
|
+
headerElements.push(
|
|
2945
|
+
// 切换只改 cwd,**不重置浏览起点**:root 一路带到切换后的卡片,
|
|
2946
|
+
// 否则「返回」会退化成「回到刚切换到的目录」(用户反馈的缺陷)。
|
|
2947
|
+
cardButton('切换', { cmd: 'ls.switch', path: targetDir, root: browseRoot }, { type: 'primary', size: 'small' }));
|
|
2936
2948
|
}
|
|
2937
2949
|
// Helper to create directory button - use targetDir
|
|
2938
|
-
const dirButton = (name) => ({
|
|
2939
|
-
tag: 'button',
|
|
2940
|
-
text: { tag: 'plain_text', content: `📁 ${name}` },
|
|
2941
|
-
type: 'default',
|
|
2942
|
-
behaviors: [
|
|
2943
|
-
{
|
|
2944
|
-
type: 'callback',
|
|
2945
|
-
value: {
|
|
2946
|
-
cmd: 'ls.browse',
|
|
2947
|
-
path: path.join(targetDir, name),
|
|
2948
|
-
root: browseRoot,
|
|
2949
|
-
},
|
|
2950
|
-
},
|
|
2951
|
-
],
|
|
2952
|
-
});
|
|
2950
|
+
const dirButton = (name) => cardButton(`📁 ${name}`, { cmd: 'ls.browse', path: path.join(targetDir, name), root: browseRoot }, { type: 'default' });
|
|
2953
2951
|
// Helper to create file button with size - use targetDir
|
|
2954
2952
|
const fileButton = (name, size) => {
|
|
2955
2953
|
const sizeStr = size !== undefined ? ` (${formatBytes(size)})` : '';
|
|
2956
|
-
return {
|
|
2957
|
-
tag: 'button',
|
|
2958
|
-
text: { tag: 'plain_text', content: `📄 ${name}${sizeStr}` },
|
|
2959
|
-
type: 'default',
|
|
2960
|
-
size: 'small',
|
|
2961
|
-
behaviors: [
|
|
2962
|
-
{ type: 'callback', value: { cmd: 'ls.file', path: path.join(targetDir, name) } },
|
|
2963
|
-
],
|
|
2964
|
-
};
|
|
2954
|
+
return cardButton(`📄 ${name}${sizeStr}`, { cmd: 'ls.file', path: path.join(targetDir, name) }, { type: 'default', size: 'small' });
|
|
2965
2955
|
};
|
|
2966
2956
|
// Build each section's elements (no tabs - use section headers)
|
|
2967
2957
|
// Note: div+elements is not supported in CardKit 2.0, use column_set instead
|
|
@@ -3126,25 +3116,12 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3126
3116
|
const mtimeText = stat ? stat.mtime.toLocaleString('zh-CN', { hour12: false }) : '未知';
|
|
3127
3117
|
const parentDir = path.dirname(filePath);
|
|
3128
3118
|
const buttons = [
|
|
3129
|
-
{
|
|
3130
|
-
tag: 'button',
|
|
3131
|
-
text: { tag: 'plain_text', content: '📎 下载' },
|
|
3132
|
-
type: 'primary',
|
|
3133
|
-
size: 'small',
|
|
3134
|
-
behaviors: [{ type: 'callback', value: { cmd: 'ls.file', path: filePath } }],
|
|
3135
|
-
},
|
|
3119
|
+
cardButton('📎 下载', { cmd: 'ls.file', path: filePath }, { type: 'primary', size: 'small' }),
|
|
3136
3120
|
];
|
|
3137
3121
|
if (parentDir !== filePath) {
|
|
3138
|
-
buttons.push(
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
type: 'default',
|
|
3142
|
-
size: 'small',
|
|
3143
|
-
// 浏览起点 = 文件所在目录:进入后「返回」回到这里,不回 工作目录
|
|
3144
|
-
behaviors: [
|
|
3145
|
-
{ type: 'callback', value: { cmd: 'ls.browse', path: parentDir, root: parentDir } },
|
|
3146
|
-
],
|
|
3147
|
-
});
|
|
3122
|
+
buttons.push(
|
|
3123
|
+
// 浏览起点 = 文件所在目录:进入后「返回」回到这里,不回 工作目录
|
|
3124
|
+
cardButton('上级', { cmd: 'ls.browse', path: parentDir, root: parentDir }, { type: 'default', size: 'small' }));
|
|
3148
3125
|
}
|
|
3149
3126
|
return {
|
|
3150
3127
|
card: {
|
|
@@ -3323,18 +3300,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3323
3300
|
tag: 'column',
|
|
3324
3301
|
width: 'auto',
|
|
3325
3302
|
elements: [
|
|
3326
|
-
{
|
|
3327
|
-
tag: 'button',
|
|
3328
|
-
text: { tag: 'plain_text', content: `切换为 ${switchLabel}` },
|
|
3329
|
-
type: 'default',
|
|
3330
|
-
size: 'small',
|
|
3331
|
-
behaviors: [
|
|
3332
|
-
{
|
|
3333
|
-
type: 'callback',
|
|
3334
|
-
value: { cmd: 'ws.sort', ...(filter ? { q: filter } : {}) },
|
|
3335
|
-
},
|
|
3336
|
-
],
|
|
3337
|
-
},
|
|
3303
|
+
cardButton(`切换为 ${switchLabel}`, { cmd: 'ws.sort', ...(filter ? { q: filter } : {}) }, { type: 'default', size: 'small' }),
|
|
3338
3304
|
],
|
|
3339
3305
|
},
|
|
3340
3306
|
],
|
|
@@ -3353,46 +3319,24 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3353
3319
|
tag: 'column',
|
|
3354
3320
|
width: 'auto',
|
|
3355
3321
|
elements: [
|
|
3356
|
-
{
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
{
|
|
3363
|
-
type: 'callback',
|
|
3364
|
-
value: {
|
|
3365
|
-
cmd: 'ws.use',
|
|
3366
|
-
name: entry.name,
|
|
3367
|
-
offset: safeOffset,
|
|
3368
|
-
...(filter ? { q: filter } : {}),
|
|
3369
|
-
},
|
|
3370
|
-
},
|
|
3371
|
-
],
|
|
3372
|
-
},
|
|
3322
|
+
cardButton('切换', {
|
|
3323
|
+
cmd: 'ws.use',
|
|
3324
|
+
name: entry.name,
|
|
3325
|
+
offset: safeOffset,
|
|
3326
|
+
...(filter ? { q: filter } : {}),
|
|
3327
|
+
}, { type: 'primary', size: 'small' }),
|
|
3373
3328
|
],
|
|
3374
3329
|
},
|
|
3375
3330
|
{
|
|
3376
3331
|
tag: 'column',
|
|
3377
3332
|
width: 'auto',
|
|
3378
3333
|
elements: [
|
|
3379
|
-
{
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
{
|
|
3386
|
-
type: 'callback',
|
|
3387
|
-
value: {
|
|
3388
|
-
cmd: 'ws.remove',
|
|
3389
|
-
name: entry.name,
|
|
3390
|
-
offset: safeOffset,
|
|
3391
|
-
...(filter ? { q: filter } : {}),
|
|
3392
|
-
},
|
|
3393
|
-
},
|
|
3394
|
-
],
|
|
3395
|
-
},
|
|
3334
|
+
cardButton('删除', {
|
|
3335
|
+
cmd: 'ws.remove',
|
|
3336
|
+
name: entry.name,
|
|
3337
|
+
offset: safeOffset,
|
|
3338
|
+
...(filter ? { q: filter } : {}),
|
|
3339
|
+
}, { type: 'danger', size: 'small' }),
|
|
3396
3340
|
],
|
|
3397
3341
|
},
|
|
3398
3342
|
],
|
|
@@ -3427,17 +3371,6 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3427
3371
|
}
|
|
3428
3372
|
}
|
|
3429
3373
|
}
|
|
3430
|
-
/**
|
|
3431
|
-
* degraded card when a session's export is non-empty but unparseable
|
|
3432
|
-
* (truncated by opencode's pipe cap on huge sessions, or otherwise corrupt).
|
|
3433
|
-
* The session exists, so reuse listSessions metadata (title) instead of the
|
|
3434
|
-
* misleading "未找到". Reuses the same card-builder helpers (markdownDiv) as
|
|
3435
|
-
* the normal resume card; enforceCardBudget still applies at send time.
|
|
3436
|
-
*/
|
|
3437
|
-
/**
|
|
3438
|
-
* /resume 核心实现(public:handleResumePage / handle / resume.use 共用,
|
|
3439
|
-
* 也是测试的直接 seam,避免测试用 @ts-expect-error 摸私有方法)。
|
|
3440
|
-
*/
|
|
3441
3374
|
/**
|
|
3442
3375
|
* DSH /resume preset 一致性校验:返回 mismatch 详情,或 undefined(一致/不可判定)。
|
|
3443
3376
|
*
|
|
@@ -3604,6 +3537,12 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3604
3537
|
const sessionMeta = new Map();
|
|
3605
3538
|
const prefetchCount = Math.min(RESUME_CONTENT_PREFETCH, allSessions.length);
|
|
3606
3539
|
for (const s of allSessions.slice(0, prefetchCount)) {
|
|
3540
|
+
// 行上已经带了服务端投影预览(DSH:title + turnOutline 末条)→ 跳过预取。
|
|
3541
|
+
// DSH 的 readSessionContent 是 3 个 curl 加一次 maxMessages=10000 的
|
|
3542
|
+
// session/page;一页 5 个会话按行预取等于 15 次同步 HTTP,而这些信息
|
|
3543
|
+
// session/list 已经给全了。
|
|
3544
|
+
if (s.title || s.lastUserMessage || s.lastAssistantMessage)
|
|
3545
|
+
continue;
|
|
3607
3546
|
// 优先走轻量摘要(只取标题/用量,不构造 events)——codex 实现了
|
|
3608
3547
|
// readSessionSummary,避免对前 N 条各做一次全量 JSONL 解析来构造
|
|
3609
3548
|
// 立即被丢弃的 events 数组。其他 agent 无此方法时回退全量读取再取字段。
|
|
@@ -3647,26 +3586,33 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3647
3586
|
const buttonText = isCurrent ? '✓ 当前会话' : '恢复此会话';
|
|
3648
3587
|
const meta = sessionMeta.get(s.sessionId);
|
|
3649
3588
|
// Title priority:
|
|
3650
|
-
// 1.
|
|
3589
|
+
// 1. 行级 title(服务端投影,DSH)→ 「标题」。
|
|
3590
|
+
// 2. Prefetched displayTitle (aiTitle or last user message) keeps its
|
|
3651
3591
|
// original label (AI 标题 / 最近输入).
|
|
3652
|
-
//
|
|
3592
|
+
// 3. Otherwise the lightweight summary renders under the neutral
|
|
3653
3593
|
// "会话摘要" label — summary is the first user message, not the
|
|
3654
3594
|
// last input, so "最近输入" would be semantically wrong.
|
|
3655
|
-
//
|
|
3595
|
+
// 4. Placeholder summaries (no user message) render no title section
|
|
3656
3596
|
// at all — never leak '(无摘要)'/'(no user message)'/'New Session'.
|
|
3657
3597
|
const summaryIsPlaceholder = RESUME_SUMMARY_PLACEHOLDERS.has(s.summary.trim());
|
|
3658
3598
|
const prefetchedTitle = meta?.displayTitle;
|
|
3599
|
+
// 行级 title 也过占位符过滤:DSH 对空会话可能给 "New Session",旧路径
|
|
3600
|
+
// 的 summary 会被挡掉,新路径如果不过滤就会把它当真实标题渲染。
|
|
3601
|
+
const rowTitleRaw = s.title?.trim() ?? '';
|
|
3602
|
+
const rowTitle = rowTitleRaw && !RESUME_SUMMARY_PLACEHOLDERS.has(rowTitleRaw) ? rowTitleRaw : undefined;
|
|
3659
3603
|
// aiTitle 不能单独作为"预取标题"键:若 displayTitle 缺失(理论上 opencode
|
|
3660
3604
|
// info.title 非空但 export 无 user text),标题兜底是 summary,label 必须
|
|
3661
3605
|
// 是中性"会话摘要"而不是"最近输入"(Review 边缘分支)。
|
|
3662
|
-
const titleText = prefetchedTitle ?? (summaryIsPlaceholder ? '' : s.summary);
|
|
3606
|
+
const titleText = prefetchedTitle ?? rowTitle ?? (summaryIsPlaceholder ? '' : s.summary);
|
|
3663
3607
|
const titleLabel = prefetchedTitle
|
|
3664
3608
|
? meta?.aiTitle
|
|
3665
3609
|
? 'AI 标题'
|
|
3666
3610
|
: '最近输入'
|
|
3667
|
-
:
|
|
3668
|
-
? ''
|
|
3669
|
-
:
|
|
3611
|
+
: rowTitle
|
|
3612
|
+
? '标题'
|
|
3613
|
+
: summaryIsPlaceholder
|
|
3614
|
+
? ''
|
|
3615
|
+
: '会话摘要';
|
|
3670
3616
|
const sections = [];
|
|
3671
3617
|
if (titleText) {
|
|
3672
3618
|
sections.push(`🏷️ **${titleLabel}**\n${titleText.slice(0, 80)}${titleText.length > 80 ? '...' : ''}`);
|
|
@@ -3674,6 +3620,14 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3674
3620
|
if (meta?.recap) {
|
|
3675
3621
|
sections.push(`📝 **Recap**\n${meta.recap.slice(0, 60)}${meta.recap.length > 60 ? '...' : ''}`);
|
|
3676
3622
|
}
|
|
3623
|
+
// 行级预览(DSH 服务端投影):最后一句我说了什么 / 最后一句它答了什么。
|
|
3624
|
+
// 只对提供了字段的 reader 渲染,其他 agent 的卡片一个字都不变。
|
|
3625
|
+
if (s.lastUserMessage) {
|
|
3626
|
+
sections.push(`✏️ **最近输入**\n${sessionPreviewLine(s.lastUserMessage, RESUME_PREVIEW_MAX)}`);
|
|
3627
|
+
}
|
|
3628
|
+
if (s.lastAssistantMessage) {
|
|
3629
|
+
sections.push(`📤 **最后输出**\n${sessionPreviewLine(s.lastAssistantMessage, RESUME_PREVIEW_MAX)}`);
|
|
3630
|
+
}
|
|
3677
3631
|
const infoStr = sections.length > 0 ? '\n' + sections.join('\n──\n') : '';
|
|
3678
3632
|
elements.push({ tag: 'hr' });
|
|
3679
3633
|
// Both current and non-current sessions get a button for visual consistency
|
|
@@ -3691,18 +3645,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3691
3645
|
tag: 'column',
|
|
3692
3646
|
width: 'auto',
|
|
3693
3647
|
elements: [
|
|
3694
|
-
{
|
|
3695
|
-
tag: 'button',
|
|
3696
|
-
text: { tag: 'plain_text', content: buttonText },
|
|
3697
|
-
type: isCurrent ? 'default' : 'primary',
|
|
3698
|
-
disabled: isCurrent,
|
|
3699
|
-
behaviors: [
|
|
3700
|
-
{
|
|
3701
|
-
type: 'callback',
|
|
3702
|
-
value: { cmd: 'resume.use', sessionId: s.sessionId, agent: agentKind },
|
|
3703
|
-
},
|
|
3704
|
-
],
|
|
3705
|
-
},
|
|
3648
|
+
cardButton(buttonText, { cmd: 'resume.use', sessionId: s.sessionId, agent: agentKind }, { type: isCurrent ? 'default' : 'primary', disabled: isCurrent }),
|
|
3706
3649
|
],
|
|
3707
3650
|
},
|
|
3708
3651
|
],
|
|
@@ -3780,14 +3723,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3780
3723
|
content: `> session: ${run.sessionId.slice(0, 12)}... \n> 状态: ${statusLabel}`,
|
|
3781
3724
|
},
|
|
3782
3725
|
});
|
|
3783
|
-
|
|
3784
|
-
elements.push({
|
|
3785
|
-
tag: 'button',
|
|
3786
|
-
text: { tag: 'plain_text', content: '⏹ 停止' },
|
|
3787
|
-
type: 'danger',
|
|
3788
|
-
size: 'small',
|
|
3789
|
-
behaviors: [{ type: 'callback', value: { cmd: 'stop', runId: run.runId, cwd: run.cwd } }],
|
|
3790
|
-
});
|
|
3726
|
+
elements.push(cardButton('⏹ 停止', { cmd: 'stop', runId: run.runId, cwd: run.cwd }, { type: 'danger', size: 'small' }));
|
|
3791
3727
|
elements.push({ tag: 'hr' });
|
|
3792
3728
|
}
|
|
3793
3729
|
remaining -= end - start;
|
|
@@ -3812,14 +3748,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3812
3748
|
tag: 'div',
|
|
3813
3749
|
text: { tag: 'lark_md', content: `> 目录: ${run.cwd} \n> 状态: ${statusLabel}` },
|
|
3814
3750
|
});
|
|
3815
|
-
|
|
3816
|
-
elements.push({
|
|
3817
|
-
tag: 'button',
|
|
3818
|
-
text: { tag: 'plain_text', content: '⏹ 停止' },
|
|
3819
|
-
type: 'danger',
|
|
3820
|
-
size: 'small',
|
|
3821
|
-
behaviors: [{ type: 'callback', value: { cmd: 'stop', runId: run.runId, cwd: run.cwd } }],
|
|
3822
|
-
});
|
|
3751
|
+
elements.push(cardButton('⏹ 停止', { cmd: 'stop', runId: run.runId, cwd: run.cwd }, { type: 'danger', size: 'small' }));
|
|
3823
3752
|
elements.push({ tag: 'hr' });
|
|
3824
3753
|
}
|
|
3825
3754
|
}
|
|
@@ -3926,55 +3855,29 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3926
3855
|
// 使用 agent config builder 获取该 agent 的配置字段
|
|
3927
3856
|
const defaultAgent = displayConfig.defaultAgent ?? 'claude';
|
|
3928
3857
|
const configBuilder = getConfigBuilder(defaultAgent);
|
|
3929
|
-
//
|
|
3930
|
-
//
|
|
3931
|
-
//
|
|
3932
|
-
// a "⚠️ (未安装)" suffix in the label while the value stays clean.
|
|
3933
|
-
// 固定展示顺序(Codex → Claude → OpenCode → Pi → Kimi),
|
|
3858
|
+
// defaultAgent 选择器作为普通 select 字段交给共享渲染(SelectOption 支持
|
|
3859
|
+
// text/value 分离:未安装 agent 的 label 带「⚠️ (未安装)」,value 保持干净)。
|
|
3860
|
+
// 固定展示顺序(Codex → Claude → OpenCode → Pi → Kimi → DSH),
|
|
3934
3861
|
// 明确未安装的 agent 排到后面(getCachedAvailability === false)。
|
|
3935
3862
|
const allAgents = sortAgentsForDisplay(listRegisteredAgents(), getCachedAvailability);
|
|
3936
3863
|
const agentOptions = allAgents.map((kind) => {
|
|
3937
3864
|
const available = getCachedAvailability(kind);
|
|
3938
3865
|
const label = available === false ? `${agentDisplayName(kind)} ⚠️ (未安装)` : agentDisplayName(kind);
|
|
3939
|
-
return { text:
|
|
3866
|
+
return { text: label, value: kind };
|
|
3940
3867
|
});
|
|
3941
3868
|
const selectedAgent = displayConfig.defaultAgent ?? 'claude';
|
|
3942
|
-
const
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
width: 'weighted',
|
|
3949
|
-
weight: 2,
|
|
3950
|
-
vertical_align: 'center',
|
|
3951
|
-
elements: [{ tag: 'div', text: { tag: 'lark_md', content: '默认 Agent' } }],
|
|
3952
|
-
},
|
|
3953
|
-
{
|
|
3954
|
-
tag: 'column',
|
|
3955
|
-
width: 'weighted',
|
|
3956
|
-
weight: 3,
|
|
3957
|
-
vertical_align: 'center',
|
|
3958
|
-
elements: [
|
|
3959
|
-
{
|
|
3960
|
-
tag: 'select_static',
|
|
3961
|
-
placeholder: { tag: 'plain_text', content: '请选择' },
|
|
3962
|
-
options: agentOptions,
|
|
3963
|
-
initial_option: selectedAgent,
|
|
3964
|
-
behaviors: [{ type: 'callback', value: { cmd: 'config.set', key: 'defaultAgent' } }],
|
|
3965
|
-
},
|
|
3966
|
-
],
|
|
3967
|
-
},
|
|
3968
|
-
],
|
|
3869
|
+
const agentSelectorField = {
|
|
3870
|
+
key: 'defaultAgent',
|
|
3871
|
+
label: '默认 Agent',
|
|
3872
|
+
type: 'select',
|
|
3873
|
+
options: agentOptions,
|
|
3874
|
+
currentValue: selectedAgent,
|
|
3969
3875
|
};
|
|
3970
|
-
// Agent config fields — no longer include defaultAgent as a ConfigField
|
|
3971
|
-
// (it's rendered inline above with text/value separation).
|
|
3972
|
-
const agentConfigFieldsNoSelector = configBuilder.buildFields(displayConfig);
|
|
3973
3876
|
const tabs = [
|
|
3974
3877
|
{
|
|
3975
3878
|
id: 'agent',
|
|
3976
3879
|
label: `🤖 ${agentDisplayName(defaultAgent)}`,
|
|
3977
|
-
fields:
|
|
3880
|
+
fields: [agentSelectorField, ...configBuilder.buildFields(displayConfig)],
|
|
3978
3881
|
},
|
|
3979
3882
|
{
|
|
3980
3883
|
id: 'idle',
|
|
@@ -3994,28 +3897,8 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3994
3897
|
],
|
|
3995
3898
|
},
|
|
3996
3899
|
];
|
|
3997
|
-
//
|
|
3998
|
-
|
|
3999
|
-
// Inject the agent selector as the first element in the agent tab section,
|
|
4000
|
-
// right after the "**🤖 ..." header, so text/value can differ for uninstalled agents.
|
|
4001
|
-
// NOTE: findIndex relies on the agent tab header starting with "**🤖" — if the
|
|
4002
|
-
// tab label format changes, this fallback will fire and the selector moves to top.
|
|
4003
|
-
const body = card.body;
|
|
4004
|
-
if (body && Array.isArray(body.elements)) {
|
|
4005
|
-
const headerIdx = body.elements.findIndex((el) => typeof el === 'object' &&
|
|
4006
|
-
el !== null &&
|
|
4007
|
-
el.tag === 'div' &&
|
|
4008
|
-
el.text?.tag === 'lark_md' &&
|
|
4009
|
-
(el.text?.content ?? '').startsWith('**🤖'));
|
|
4010
|
-
if (headerIdx >= 0) {
|
|
4011
|
-
body.elements.splice(headerIdx + 1, 0, agentSelector);
|
|
4012
|
-
}
|
|
4013
|
-
else {
|
|
4014
|
-
getLogger().warn('[buildConfigCard] agent tab header not found, falling back to unshift');
|
|
4015
|
-
body.elements.unshift(agentSelector);
|
|
4016
|
-
}
|
|
4017
|
-
}
|
|
4018
|
-
return { card };
|
|
3900
|
+
// 使用共享渲染模块构建卡片(Agent 选择器作为普通 select 字段参与渲染)
|
|
3901
|
+
return { card: buildConfigCardFromTabs(tabs, displayConfig) };
|
|
4019
3902
|
}
|
|
4020
3903
|
/** /order 实现(public:测试直接调用,替代 as unknown as)。 */
|
|
4021
3904
|
cmdOrder(args, _ctx, offset = 0) {
|
|
@@ -4046,49 +3929,16 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
4046
3929
|
// 右侧「删除指令」按钮。两列均 weighted 1:1,跨行对齐。
|
|
4047
3930
|
const leftColumn = order.alias
|
|
4048
3931
|
? [
|
|
4049
|
-
{
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
type: 'callback',
|
|
4057
|
-
value: { cmd: 'order.aliasEdit', orderId: order.id, offset: safeOffset },
|
|
4058
|
-
},
|
|
4059
|
-
],
|
|
4060
|
-
},
|
|
4061
|
-
{
|
|
4062
|
-
tag: 'button',
|
|
4063
|
-
text: { tag: 'plain_text', content: '✕' },
|
|
4064
|
-
type: 'default',
|
|
4065
|
-
size: 'small',
|
|
4066
|
-
behaviors: [
|
|
4067
|
-
{
|
|
4068
|
-
type: 'callback',
|
|
4069
|
-
value: {
|
|
4070
|
-
cmd: 'order.aliasRemove',
|
|
4071
|
-
orderId: order.id,
|
|
4072
|
-
aliasName: order.alias,
|
|
4073
|
-
offset: safeOffset,
|
|
4074
|
-
},
|
|
4075
|
-
},
|
|
4076
|
-
],
|
|
4077
|
-
},
|
|
3932
|
+
cardButton(`$${order.alias}`, { cmd: 'order.aliasEdit', orderId: order.id, offset: safeOffset }, { type: 'default', size: 'small' }),
|
|
3933
|
+
cardButton('✕', {
|
|
3934
|
+
cmd: 'order.aliasRemove',
|
|
3935
|
+
orderId: order.id,
|
|
3936
|
+
aliasName: order.alias,
|
|
3937
|
+
offset: safeOffset,
|
|
3938
|
+
}, { type: 'default', size: 'small' }),
|
|
4078
3939
|
]
|
|
4079
3940
|
: [
|
|
4080
|
-
{
|
|
4081
|
-
tag: 'button',
|
|
4082
|
-
text: { tag: 'plain_text', content: '+别名' },
|
|
4083
|
-
type: 'default',
|
|
4084
|
-
size: 'small',
|
|
4085
|
-
behaviors: [
|
|
4086
|
-
{
|
|
4087
|
-
type: 'callback',
|
|
4088
|
-
value: { cmd: 'order.aliasEdit', orderId: order.id, offset: safeOffset },
|
|
4089
|
-
},
|
|
4090
|
-
],
|
|
4091
|
-
},
|
|
3941
|
+
cardButton('+别名', { cmd: 'order.aliasEdit', orderId: order.id, offset: safeOffset }, { type: 'default', size: 'small' }),
|
|
4092
3942
|
];
|
|
4093
3943
|
elements.push({
|
|
4094
3944
|
tag: 'column_set',
|
|
@@ -4112,18 +3962,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
4112
3962
|
tag: 'column',
|
|
4113
3963
|
width: 'auto',
|
|
4114
3964
|
elements: [
|
|
4115
|
-
{
|
|
4116
|
-
tag: 'button',
|
|
4117
|
-
text: { tag: 'plain_text', content: '▶ 执行' },
|
|
4118
|
-
type: 'primary',
|
|
4119
|
-
size: 'small',
|
|
4120
|
-
behaviors: [
|
|
4121
|
-
{
|
|
4122
|
-
type: 'callback',
|
|
4123
|
-
value: { cmd: 'order.exec', orderId: order.id },
|
|
4124
|
-
},
|
|
4125
|
-
],
|
|
4126
|
-
},
|
|
3965
|
+
cardButton('▶ 执行', { cmd: 'order.exec', orderId: order.id }, { type: 'primary', size: 'small' }),
|
|
4127
3966
|
],
|
|
4128
3967
|
},
|
|
4129
3968
|
{
|
|
@@ -4133,44 +3972,14 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
4133
3972
|
tag: 'column',
|
|
4134
3973
|
width: 'auto',
|
|
4135
3974
|
elements: [
|
|
4136
|
-
{
|
|
4137
|
-
tag: 'button',
|
|
4138
|
-
text: { tag: 'plain_text', content: '编辑' },
|
|
4139
|
-
type: 'default',
|
|
4140
|
-
size: 'small',
|
|
4141
|
-
behaviors: [
|
|
4142
|
-
{
|
|
4143
|
-
type: 'callback',
|
|
4144
|
-
value: {
|
|
4145
|
-
cmd: 'order.textEdit',
|
|
4146
|
-
orderId: order.id,
|
|
4147
|
-
offset: safeOffset,
|
|
4148
|
-
},
|
|
4149
|
-
},
|
|
4150
|
-
],
|
|
4151
|
-
},
|
|
3975
|
+
cardButton('编辑', { cmd: 'order.textEdit', orderId: order.id, offset: safeOffset }, { type: 'default', size: 'small' }),
|
|
4152
3976
|
],
|
|
4153
3977
|
},
|
|
4154
3978
|
{
|
|
4155
3979
|
tag: 'column',
|
|
4156
3980
|
width: 'auto',
|
|
4157
3981
|
elements: [
|
|
4158
|
-
{
|
|
4159
|
-
tag: 'button',
|
|
4160
|
-
text: { tag: 'plain_text', content: '删除' },
|
|
4161
|
-
type: 'danger',
|
|
4162
|
-
size: 'small',
|
|
4163
|
-
behaviors: [
|
|
4164
|
-
{
|
|
4165
|
-
type: 'callback',
|
|
4166
|
-
value: {
|
|
4167
|
-
cmd: 'order.delete',
|
|
4168
|
-
orderId: order.id,
|
|
4169
|
-
offset: safeOffset,
|
|
4170
|
-
},
|
|
4171
|
-
},
|
|
4172
|
-
],
|
|
4173
|
-
},
|
|
3982
|
+
cardButton('删除', { cmd: 'order.delete', orderId: order.id, offset: safeOffset }, { type: 'danger', size: 'small' }),
|
|
4174
3983
|
],
|
|
4175
3984
|
},
|
|
4176
3985
|
],
|