lark-remote 0.5.7 → 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 +16 -0
- package/dist/bridge/index.d.ts +15 -0
- package/dist/bridge/index.js +61 -0
- 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.js +15 -19
- 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 +3 -9
- package/dist/card/run-renderer.js +99 -27
- package/dist/card/text-truncate.d.ts +3 -5
- package/dist/card/text-truncate.js +3 -5
- package/dist/card/tool-render.js +7 -21
- package/dist/common/tool-summary.d.ts +8 -0
- package/dist/common/tool-summary.js +38 -20
- package/dist/common/turn-failure.d.ts +13 -0
- package/dist/common/turn-failure.js +18 -0
- package/dist/index.js +5 -7
- package/dist/order/index.d.ts +2 -0
- package/dist/order/index.js +2 -0
- package/dist/router/card-helpers.js +23 -42
- 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 +0 -11
- package/dist/router/index.js +108 -349
- package/dist/router/order-exec-dispatch.js +2 -1
- 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/dsh/client.js +1 -2
- package/dist/runner/types.d.ts +19 -0
- 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/dsh/sessions.js +57 -2
- 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.
|
|
@@ -1504,7 +1516,7 @@ export class CommandRouter {
|
|
|
1504
1516
|
return { toast: { type: 'error', content: CARD_PAYLOAD_MISSING } };
|
|
1505
1517
|
}
|
|
1506
1518
|
if (!this.orderStore.has(orderId)) {
|
|
1507
|
-
return { toast: { type: 'error', content:
|
|
1519
|
+
return { toast: { type: 'error', content: ORDER_NOT_FOUND_TEXT } };
|
|
1508
1520
|
}
|
|
1509
1521
|
// Delete the order
|
|
1510
1522
|
this.orderStore.remove(orderId);
|
|
@@ -1542,7 +1554,7 @@ export class CommandRouter {
|
|
|
1542
1554
|
this.orderStore.reload();
|
|
1543
1555
|
const order = this.orderStore.get().find((o) => o.id === orderId);
|
|
1544
1556
|
if (!order) {
|
|
1545
|
-
return { toast: { type: 'error', content:
|
|
1557
|
+
return { toast: { type: 'error', content: ORDER_NOT_FOUND_TEXT } };
|
|
1546
1558
|
}
|
|
1547
1559
|
// 列表卡显示的是截断版(≤100 字符 + ...),编辑卡预览保持一致
|
|
1548
1560
|
const displayText = order.text.length > 100 ? order.text.slice(0, 97) + '...' : order.text;
|
|
@@ -1614,7 +1626,7 @@ export class CommandRouter {
|
|
|
1614
1626
|
*/
|
|
1615
1627
|
async handleOrderAliasInput(value, ctx) {
|
|
1616
1628
|
const name = value.inputValue ?? value.formValue?.['aliasName'];
|
|
1617
|
-
return this.mutateOrderAndRefreshCard(value, ctx, { missingToast:
|
|
1629
|
+
return this.mutateOrderAndRefreshCard(value, ctx, { missingToast: ORDER_NOT_FOUND_TEXT }, (order) => {
|
|
1618
1630
|
const trimmed = name?.trim() ?? '';
|
|
1619
1631
|
// 变更前快照旧别名:setAlias 会原地变异 order 对象(delete entry.alias),
|
|
1620
1632
|
// 删除后 order.alias 已为 undefined,直接用会让成功文案显示 `$undefined`。
|
|
@@ -1676,7 +1688,7 @@ export class CommandRouter {
|
|
|
1676
1688
|
*/
|
|
1677
1689
|
async handleOrderTextInput(value, ctx) {
|
|
1678
1690
|
const raw = value.inputValue ?? value.formValue?.['text'];
|
|
1679
|
-
return this.mutateOrderAndRefreshCard(value, ctx, { missingToast:
|
|
1691
|
+
return this.mutateOrderAndRefreshCard(value, ctx, { missingToast: ORDER_NOT_FOUND_TEXT }, (order) => {
|
|
1680
1692
|
let updated;
|
|
1681
1693
|
try {
|
|
1682
1694
|
updated = this.orderStore.updateText(order.id, raw ?? '');
|
|
@@ -1686,7 +1698,7 @@ export class CommandRouter {
|
|
|
1686
1698
|
}
|
|
1687
1699
|
if (!updated) {
|
|
1688
1700
|
// reload + find 后 updateText 不应返回 undefined;防御性兜底(如并发删除)。
|
|
1689
|
-
return { ok: false, error:
|
|
1701
|
+
return { ok: false, error: ORDER_NOT_FOUND_TEXT };
|
|
1690
1702
|
}
|
|
1691
1703
|
const preview = updated.text.length > 50 ? updated.text.slice(0, 50) + '...' : updated.text;
|
|
1692
1704
|
return { ok: true, toast: `✅ 已更新指令: ${preview}` };
|
|
@@ -2322,13 +2334,7 @@ export class CommandRouter {
|
|
|
2322
2334
|
width: 'weighted',
|
|
2323
2335
|
weight: 2,
|
|
2324
2336
|
elements: [
|
|
2325
|
-
{
|
|
2326
|
-
tag: 'button',
|
|
2327
|
-
text: { tag: 'plain_text', content: c.label },
|
|
2328
|
-
type: 'default',
|
|
2329
|
-
size: 'small',
|
|
2330
|
-
behaviors: [{ type: 'callback', value: { cmd: `help.${c.cmd}` } }],
|
|
2331
|
-
},
|
|
2337
|
+
cardButton(c.label, { cmd: `help.${c.cmd}` }, { type: 'default', size: 'small' }),
|
|
2332
2338
|
],
|
|
2333
2339
|
},
|
|
2334
2340
|
{
|
|
@@ -2804,17 +2810,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2804
2810
|
buildResumeActionButtons(opts) {
|
|
2805
2811
|
const actions = [];
|
|
2806
2812
|
if (opts.isActive) {
|
|
2807
|
-
actions.push({
|
|
2808
|
-
tag: 'button',
|
|
2809
|
-
text: { tag: 'plain_text', content: '⏹ 终止' },
|
|
2810
|
-
type: 'danger',
|
|
2811
|
-
behaviors: [
|
|
2812
|
-
{
|
|
2813
|
-
type: 'callback',
|
|
2814
|
-
value: { cmd: 'stop', runId: opts.activeRunRunId, cwd: opts.cwd },
|
|
2815
|
-
},
|
|
2816
|
-
],
|
|
2817
|
-
});
|
|
2813
|
+
actions.push(cardButton('⏹ 终止', { cmd: 'stop', runId: opts.activeRunRunId, cwd: opts.cwd }, { type: 'danger' }));
|
|
2818
2814
|
}
|
|
2819
2815
|
if (opts.compact && !opts.isActive) {
|
|
2820
2816
|
actions.push(resumeCompactButton(opts.compact.sessionId, opts.compact.agentKind));
|
|
@@ -2930,88 +2926,32 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2930
2926
|
// Build header with parent button
|
|
2931
2927
|
const headerElements = [];
|
|
2932
2928
|
if (hasParent) {
|
|
2933
|
-
headerElements.push({
|
|
2934
|
-
tag: 'button',
|
|
2935
|
-
text: { tag: 'plain_text', content: '上级' },
|
|
2936
|
-
type: 'default',
|
|
2937
|
-
size: 'small',
|
|
2938
|
-
behaviors: [
|
|
2939
|
-
{ type: 'callback', value: { cmd: 'ls.browse', path: parentDir, root: browseRoot } },
|
|
2940
|
-
],
|
|
2941
|
-
});
|
|
2929
|
+
headerElements.push(cardButton('上级', { cmd: 'ls.browse', path: parentDir, root: browseRoot }, { type: 'default', size: 'small' }));
|
|
2942
2930
|
}
|
|
2943
2931
|
// Show "返回" button when browsing away from the /ls root (回到 /ls 指定的目录)
|
|
2944
2932
|
if (canReturn) {
|
|
2945
|
-
headerElements.push({
|
|
2946
|
-
tag: 'button',
|
|
2947
|
-
text: { tag: 'plain_text', content: '返回' },
|
|
2948
|
-
type: 'default',
|
|
2949
|
-
size: 'small',
|
|
2950
|
-
behaviors: [
|
|
2951
|
-
{ type: 'callback', value: { cmd: 'ls.browse', path: browseRoot, root: browseRoot } },
|
|
2952
|
-
],
|
|
2953
|
-
});
|
|
2933
|
+
headerElements.push(cardButton('返回', { cmd: 'ls.browse', path: browseRoot, root: browseRoot }, { type: 'default', size: 'small' }));
|
|
2954
2934
|
}
|
|
2955
|
-
headerElements.push({
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
type: 'callback',
|
|
2963
|
-
value: {
|
|
2964
|
-
cmd: 'ls.refresh',
|
|
2965
|
-
path: targetDir,
|
|
2966
|
-
offset: safeOffset,
|
|
2967
|
-
root: browseRoot,
|
|
2968
|
-
...(filter ? { q: filter } : {}),
|
|
2969
|
-
},
|
|
2970
|
-
},
|
|
2971
|
-
],
|
|
2972
|
-
});
|
|
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' }));
|
|
2973
2942
|
// Show "切换" button when viewing a subdirectory (to switch cwd to this directory)
|
|
2974
2943
|
if (isSubdir) {
|
|
2975
|
-
headerElements.push(
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
size: 'small',
|
|
2980
|
-
// 切换只改 cwd,**不重置浏览起点**:root 一路带到切换后的卡片,
|
|
2981
|
-
// 否则「返回」会退化成「回到刚切换到的目录」(用户反馈的缺陷)。
|
|
2982
|
-
behaviors: [
|
|
2983
|
-
{ type: 'callback', value: { cmd: 'ls.switch', path: targetDir, root: browseRoot } },
|
|
2984
|
-
],
|
|
2985
|
-
});
|
|
2944
|
+
headerElements.push(
|
|
2945
|
+
// 切换只改 cwd,**不重置浏览起点**:root 一路带到切换后的卡片,
|
|
2946
|
+
// 否则「返回」会退化成「回到刚切换到的目录」(用户反馈的缺陷)。
|
|
2947
|
+
cardButton('切换', { cmd: 'ls.switch', path: targetDir, root: browseRoot }, { type: 'primary', size: 'small' }));
|
|
2986
2948
|
}
|
|
2987
2949
|
// Helper to create directory button - use targetDir
|
|
2988
|
-
const dirButton = (name) => ({
|
|
2989
|
-
tag: 'button',
|
|
2990
|
-
text: { tag: 'plain_text', content: `📁 ${name}` },
|
|
2991
|
-
type: 'default',
|
|
2992
|
-
behaviors: [
|
|
2993
|
-
{
|
|
2994
|
-
type: 'callback',
|
|
2995
|
-
value: {
|
|
2996
|
-
cmd: 'ls.browse',
|
|
2997
|
-
path: path.join(targetDir, name),
|
|
2998
|
-
root: browseRoot,
|
|
2999
|
-
},
|
|
3000
|
-
},
|
|
3001
|
-
],
|
|
3002
|
-
});
|
|
2950
|
+
const dirButton = (name) => cardButton(`📁 ${name}`, { cmd: 'ls.browse', path: path.join(targetDir, name), root: browseRoot }, { type: 'default' });
|
|
3003
2951
|
// Helper to create file button with size - use targetDir
|
|
3004
2952
|
const fileButton = (name, size) => {
|
|
3005
2953
|
const sizeStr = size !== undefined ? ` (${formatBytes(size)})` : '';
|
|
3006
|
-
return {
|
|
3007
|
-
tag: 'button',
|
|
3008
|
-
text: { tag: 'plain_text', content: `📄 ${name}${sizeStr}` },
|
|
3009
|
-
type: 'default',
|
|
3010
|
-
size: 'small',
|
|
3011
|
-
behaviors: [
|
|
3012
|
-
{ type: 'callback', value: { cmd: 'ls.file', path: path.join(targetDir, name) } },
|
|
3013
|
-
],
|
|
3014
|
-
};
|
|
2954
|
+
return cardButton(`📄 ${name}${sizeStr}`, { cmd: 'ls.file', path: path.join(targetDir, name) }, { type: 'default', size: 'small' });
|
|
3015
2955
|
};
|
|
3016
2956
|
// Build each section's elements (no tabs - use section headers)
|
|
3017
2957
|
// Note: div+elements is not supported in CardKit 2.0, use column_set instead
|
|
@@ -3176,25 +3116,12 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3176
3116
|
const mtimeText = stat ? stat.mtime.toLocaleString('zh-CN', { hour12: false }) : '未知';
|
|
3177
3117
|
const parentDir = path.dirname(filePath);
|
|
3178
3118
|
const buttons = [
|
|
3179
|
-
{
|
|
3180
|
-
tag: 'button',
|
|
3181
|
-
text: { tag: 'plain_text', content: '📎 下载' },
|
|
3182
|
-
type: 'primary',
|
|
3183
|
-
size: 'small',
|
|
3184
|
-
behaviors: [{ type: 'callback', value: { cmd: 'ls.file', path: filePath } }],
|
|
3185
|
-
},
|
|
3119
|
+
cardButton('📎 下载', { cmd: 'ls.file', path: filePath }, { type: 'primary', size: 'small' }),
|
|
3186
3120
|
];
|
|
3187
3121
|
if (parentDir !== filePath) {
|
|
3188
|
-
buttons.push(
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
type: 'default',
|
|
3192
|
-
size: 'small',
|
|
3193
|
-
// 浏览起点 = 文件所在目录:进入后「返回」回到这里,不回 工作目录
|
|
3194
|
-
behaviors: [
|
|
3195
|
-
{ type: 'callback', value: { cmd: 'ls.browse', path: parentDir, root: parentDir } },
|
|
3196
|
-
],
|
|
3197
|
-
});
|
|
3122
|
+
buttons.push(
|
|
3123
|
+
// 浏览起点 = 文件所在目录:进入后「返回」回到这里,不回 工作目录
|
|
3124
|
+
cardButton('上级', { cmd: 'ls.browse', path: parentDir, root: parentDir }, { type: 'default', size: 'small' }));
|
|
3198
3125
|
}
|
|
3199
3126
|
return {
|
|
3200
3127
|
card: {
|
|
@@ -3373,18 +3300,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3373
3300
|
tag: 'column',
|
|
3374
3301
|
width: 'auto',
|
|
3375
3302
|
elements: [
|
|
3376
|
-
{
|
|
3377
|
-
tag: 'button',
|
|
3378
|
-
text: { tag: 'plain_text', content: `切换为 ${switchLabel}` },
|
|
3379
|
-
type: 'default',
|
|
3380
|
-
size: 'small',
|
|
3381
|
-
behaviors: [
|
|
3382
|
-
{
|
|
3383
|
-
type: 'callback',
|
|
3384
|
-
value: { cmd: 'ws.sort', ...(filter ? { q: filter } : {}) },
|
|
3385
|
-
},
|
|
3386
|
-
],
|
|
3387
|
-
},
|
|
3303
|
+
cardButton(`切换为 ${switchLabel}`, { cmd: 'ws.sort', ...(filter ? { q: filter } : {}) }, { type: 'default', size: 'small' }),
|
|
3388
3304
|
],
|
|
3389
3305
|
},
|
|
3390
3306
|
],
|
|
@@ -3403,46 +3319,24 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3403
3319
|
tag: 'column',
|
|
3404
3320
|
width: 'auto',
|
|
3405
3321
|
elements: [
|
|
3406
|
-
{
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
{
|
|
3413
|
-
type: 'callback',
|
|
3414
|
-
value: {
|
|
3415
|
-
cmd: 'ws.use',
|
|
3416
|
-
name: entry.name,
|
|
3417
|
-
offset: safeOffset,
|
|
3418
|
-
...(filter ? { q: filter } : {}),
|
|
3419
|
-
},
|
|
3420
|
-
},
|
|
3421
|
-
],
|
|
3422
|
-
},
|
|
3322
|
+
cardButton('切换', {
|
|
3323
|
+
cmd: 'ws.use',
|
|
3324
|
+
name: entry.name,
|
|
3325
|
+
offset: safeOffset,
|
|
3326
|
+
...(filter ? { q: filter } : {}),
|
|
3327
|
+
}, { type: 'primary', size: 'small' }),
|
|
3423
3328
|
],
|
|
3424
3329
|
},
|
|
3425
3330
|
{
|
|
3426
3331
|
tag: 'column',
|
|
3427
3332
|
width: 'auto',
|
|
3428
3333
|
elements: [
|
|
3429
|
-
{
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
{
|
|
3436
|
-
type: 'callback',
|
|
3437
|
-
value: {
|
|
3438
|
-
cmd: 'ws.remove',
|
|
3439
|
-
name: entry.name,
|
|
3440
|
-
offset: safeOffset,
|
|
3441
|
-
...(filter ? { q: filter } : {}),
|
|
3442
|
-
},
|
|
3443
|
-
},
|
|
3444
|
-
],
|
|
3445
|
-
},
|
|
3334
|
+
cardButton('删除', {
|
|
3335
|
+
cmd: 'ws.remove',
|
|
3336
|
+
name: entry.name,
|
|
3337
|
+
offset: safeOffset,
|
|
3338
|
+
...(filter ? { q: filter } : {}),
|
|
3339
|
+
}, { type: 'danger', size: 'small' }),
|
|
3446
3340
|
],
|
|
3447
3341
|
},
|
|
3448
3342
|
],
|
|
@@ -3477,17 +3371,6 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3477
3371
|
}
|
|
3478
3372
|
}
|
|
3479
3373
|
}
|
|
3480
|
-
/**
|
|
3481
|
-
* degraded card when a session's export is non-empty but unparseable
|
|
3482
|
-
* (truncated by opencode's pipe cap on huge sessions, or otherwise corrupt).
|
|
3483
|
-
* The session exists, so reuse listSessions metadata (title) instead of the
|
|
3484
|
-
* misleading "未找到". Reuses the same card-builder helpers (markdownDiv) as
|
|
3485
|
-
* the normal resume card; enforceCardBudget still applies at send time.
|
|
3486
|
-
*/
|
|
3487
|
-
/**
|
|
3488
|
-
* /resume 核心实现(public:handleResumePage / handle / resume.use 共用,
|
|
3489
|
-
* 也是测试的直接 seam,避免测试用 @ts-expect-error 摸私有方法)。
|
|
3490
|
-
*/
|
|
3491
3374
|
/**
|
|
3492
3375
|
* DSH /resume preset 一致性校验:返回 mismatch 详情,或 undefined(一致/不可判定)。
|
|
3493
3376
|
*
|
|
@@ -3654,6 +3537,12 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3654
3537
|
const sessionMeta = new Map();
|
|
3655
3538
|
const prefetchCount = Math.min(RESUME_CONTENT_PREFETCH, allSessions.length);
|
|
3656
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;
|
|
3657
3546
|
// 优先走轻量摘要(只取标题/用量,不构造 events)——codex 实现了
|
|
3658
3547
|
// readSessionSummary,避免对前 N 条各做一次全量 JSONL 解析来构造
|
|
3659
3548
|
// 立即被丢弃的 events 数组。其他 agent 无此方法时回退全量读取再取字段。
|
|
@@ -3697,26 +3586,33 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3697
3586
|
const buttonText = isCurrent ? '✓ 当前会话' : '恢复此会话';
|
|
3698
3587
|
const meta = sessionMeta.get(s.sessionId);
|
|
3699
3588
|
// Title priority:
|
|
3700
|
-
// 1.
|
|
3589
|
+
// 1. 行级 title(服务端投影,DSH)→ 「标题」。
|
|
3590
|
+
// 2. Prefetched displayTitle (aiTitle or last user message) keeps its
|
|
3701
3591
|
// original label (AI 标题 / 最近输入).
|
|
3702
|
-
//
|
|
3592
|
+
// 3. Otherwise the lightweight summary renders under the neutral
|
|
3703
3593
|
// "会话摘要" label — summary is the first user message, not the
|
|
3704
3594
|
// last input, so "最近输入" would be semantically wrong.
|
|
3705
|
-
//
|
|
3595
|
+
// 4. Placeholder summaries (no user message) render no title section
|
|
3706
3596
|
// at all — never leak '(无摘要)'/'(no user message)'/'New Session'.
|
|
3707
3597
|
const summaryIsPlaceholder = RESUME_SUMMARY_PLACEHOLDERS.has(s.summary.trim());
|
|
3708
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;
|
|
3709
3603
|
// aiTitle 不能单独作为"预取标题"键:若 displayTitle 缺失(理论上 opencode
|
|
3710
3604
|
// info.title 非空但 export 无 user text),标题兜底是 summary,label 必须
|
|
3711
3605
|
// 是中性"会话摘要"而不是"最近输入"(Review 边缘分支)。
|
|
3712
|
-
const titleText = prefetchedTitle ?? (summaryIsPlaceholder ? '' : s.summary);
|
|
3606
|
+
const titleText = prefetchedTitle ?? rowTitle ?? (summaryIsPlaceholder ? '' : s.summary);
|
|
3713
3607
|
const titleLabel = prefetchedTitle
|
|
3714
3608
|
? meta?.aiTitle
|
|
3715
3609
|
? 'AI 标题'
|
|
3716
3610
|
: '最近输入'
|
|
3717
|
-
:
|
|
3718
|
-
? ''
|
|
3719
|
-
:
|
|
3611
|
+
: rowTitle
|
|
3612
|
+
? '标题'
|
|
3613
|
+
: summaryIsPlaceholder
|
|
3614
|
+
? ''
|
|
3615
|
+
: '会话摘要';
|
|
3720
3616
|
const sections = [];
|
|
3721
3617
|
if (titleText) {
|
|
3722
3618
|
sections.push(`🏷️ **${titleLabel}**\n${titleText.slice(0, 80)}${titleText.length > 80 ? '...' : ''}`);
|
|
@@ -3724,6 +3620,14 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3724
3620
|
if (meta?.recap) {
|
|
3725
3621
|
sections.push(`📝 **Recap**\n${meta.recap.slice(0, 60)}${meta.recap.length > 60 ? '...' : ''}`);
|
|
3726
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
|
+
}
|
|
3727
3631
|
const infoStr = sections.length > 0 ? '\n' + sections.join('\n──\n') : '';
|
|
3728
3632
|
elements.push({ tag: 'hr' });
|
|
3729
3633
|
// Both current and non-current sessions get a button for visual consistency
|
|
@@ -3741,18 +3645,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3741
3645
|
tag: 'column',
|
|
3742
3646
|
width: 'auto',
|
|
3743
3647
|
elements: [
|
|
3744
|
-
{
|
|
3745
|
-
tag: 'button',
|
|
3746
|
-
text: { tag: 'plain_text', content: buttonText },
|
|
3747
|
-
type: isCurrent ? 'default' : 'primary',
|
|
3748
|
-
disabled: isCurrent,
|
|
3749
|
-
behaviors: [
|
|
3750
|
-
{
|
|
3751
|
-
type: 'callback',
|
|
3752
|
-
value: { cmd: 'resume.use', sessionId: s.sessionId, agent: agentKind },
|
|
3753
|
-
},
|
|
3754
|
-
],
|
|
3755
|
-
},
|
|
3648
|
+
cardButton(buttonText, { cmd: 'resume.use', sessionId: s.sessionId, agent: agentKind }, { type: isCurrent ? 'default' : 'primary', disabled: isCurrent }),
|
|
3756
3649
|
],
|
|
3757
3650
|
},
|
|
3758
3651
|
],
|
|
@@ -3830,14 +3723,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3830
3723
|
content: `> session: ${run.sessionId.slice(0, 12)}... \n> 状态: ${statusLabel}`,
|
|
3831
3724
|
},
|
|
3832
3725
|
});
|
|
3833
|
-
|
|
3834
|
-
elements.push({
|
|
3835
|
-
tag: 'button',
|
|
3836
|
-
text: { tag: 'plain_text', content: '⏹ 停止' },
|
|
3837
|
-
type: 'danger',
|
|
3838
|
-
size: 'small',
|
|
3839
|
-
behaviors: [{ type: 'callback', value: { cmd: 'stop', runId: run.runId, cwd: run.cwd } }],
|
|
3840
|
-
});
|
|
3726
|
+
elements.push(cardButton('⏹ 停止', { cmd: 'stop', runId: run.runId, cwd: run.cwd }, { type: 'danger', size: 'small' }));
|
|
3841
3727
|
elements.push({ tag: 'hr' });
|
|
3842
3728
|
}
|
|
3843
3729
|
remaining -= end - start;
|
|
@@ -3862,14 +3748,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3862
3748
|
tag: 'div',
|
|
3863
3749
|
text: { tag: 'lark_md', content: `> 目录: ${run.cwd} \n> 状态: ${statusLabel}` },
|
|
3864
3750
|
});
|
|
3865
|
-
|
|
3866
|
-
elements.push({
|
|
3867
|
-
tag: 'button',
|
|
3868
|
-
text: { tag: 'plain_text', content: '⏹ 停止' },
|
|
3869
|
-
type: 'danger',
|
|
3870
|
-
size: 'small',
|
|
3871
|
-
behaviors: [{ type: 'callback', value: { cmd: 'stop', runId: run.runId, cwd: run.cwd } }],
|
|
3872
|
-
});
|
|
3751
|
+
elements.push(cardButton('⏹ 停止', { cmd: 'stop', runId: run.runId, cwd: run.cwd }, { type: 'danger', size: 'small' }));
|
|
3873
3752
|
elements.push({ tag: 'hr' });
|
|
3874
3753
|
}
|
|
3875
3754
|
}
|
|
@@ -3976,55 +3855,29 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
3976
3855
|
// 使用 agent config builder 获取该 agent 的配置字段
|
|
3977
3856
|
const defaultAgent = displayConfig.defaultAgent ?? 'claude';
|
|
3978
3857
|
const configBuilder = getConfigBuilder(defaultAgent);
|
|
3979
|
-
//
|
|
3980
|
-
//
|
|
3981
|
-
//
|
|
3982
|
-
// a "⚠️ (未安装)" suffix in the label while the value stays clean.
|
|
3983
|
-
// 固定展示顺序(Codex → Claude → OpenCode → Pi → Kimi),
|
|
3858
|
+
// defaultAgent 选择器作为普通 select 字段交给共享渲染(SelectOption 支持
|
|
3859
|
+
// text/value 分离:未安装 agent 的 label 带「⚠️ (未安装)」,value 保持干净)。
|
|
3860
|
+
// 固定展示顺序(Codex → Claude → OpenCode → Pi → Kimi → DSH),
|
|
3984
3861
|
// 明确未安装的 agent 排到后面(getCachedAvailability === false)。
|
|
3985
3862
|
const allAgents = sortAgentsForDisplay(listRegisteredAgents(), getCachedAvailability);
|
|
3986
3863
|
const agentOptions = allAgents.map((kind) => {
|
|
3987
3864
|
const available = getCachedAvailability(kind);
|
|
3988
3865
|
const label = available === false ? `${agentDisplayName(kind)} ⚠️ (未安装)` : agentDisplayName(kind);
|
|
3989
|
-
return { text:
|
|
3866
|
+
return { text: label, value: kind };
|
|
3990
3867
|
});
|
|
3991
3868
|
const selectedAgent = displayConfig.defaultAgent ?? 'claude';
|
|
3992
|
-
const
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
width: 'weighted',
|
|
3999
|
-
weight: 2,
|
|
4000
|
-
vertical_align: 'center',
|
|
4001
|
-
elements: [{ tag: 'div', text: { tag: 'lark_md', content: '默认 Agent' } }],
|
|
4002
|
-
},
|
|
4003
|
-
{
|
|
4004
|
-
tag: 'column',
|
|
4005
|
-
width: 'weighted',
|
|
4006
|
-
weight: 3,
|
|
4007
|
-
vertical_align: 'center',
|
|
4008
|
-
elements: [
|
|
4009
|
-
{
|
|
4010
|
-
tag: 'select_static',
|
|
4011
|
-
placeholder: { tag: 'plain_text', content: '请选择' },
|
|
4012
|
-
options: agentOptions,
|
|
4013
|
-
initial_option: selectedAgent,
|
|
4014
|
-
behaviors: [{ type: 'callback', value: { cmd: 'config.set', key: 'defaultAgent' } }],
|
|
4015
|
-
},
|
|
4016
|
-
],
|
|
4017
|
-
},
|
|
4018
|
-
],
|
|
3869
|
+
const agentSelectorField = {
|
|
3870
|
+
key: 'defaultAgent',
|
|
3871
|
+
label: '默认 Agent',
|
|
3872
|
+
type: 'select',
|
|
3873
|
+
options: agentOptions,
|
|
3874
|
+
currentValue: selectedAgent,
|
|
4019
3875
|
};
|
|
4020
|
-
// Agent config fields — no longer include defaultAgent as a ConfigField
|
|
4021
|
-
// (it's rendered inline above with text/value separation).
|
|
4022
|
-
const agentConfigFieldsNoSelector = configBuilder.buildFields(displayConfig);
|
|
4023
3876
|
const tabs = [
|
|
4024
3877
|
{
|
|
4025
3878
|
id: 'agent',
|
|
4026
3879
|
label: `🤖 ${agentDisplayName(defaultAgent)}`,
|
|
4027
|
-
fields:
|
|
3880
|
+
fields: [agentSelectorField, ...configBuilder.buildFields(displayConfig)],
|
|
4028
3881
|
},
|
|
4029
3882
|
{
|
|
4030
3883
|
id: 'idle',
|
|
@@ -4044,28 +3897,8 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
4044
3897
|
],
|
|
4045
3898
|
},
|
|
4046
3899
|
];
|
|
4047
|
-
//
|
|
4048
|
-
|
|
4049
|
-
// Inject the agent selector as the first element in the agent tab section,
|
|
4050
|
-
// right after the "**🤖 ..." header, so text/value can differ for uninstalled agents.
|
|
4051
|
-
// NOTE: findIndex relies on the agent tab header starting with "**🤖" — if the
|
|
4052
|
-
// tab label format changes, this fallback will fire and the selector moves to top.
|
|
4053
|
-
const body = card.body;
|
|
4054
|
-
if (body && Array.isArray(body.elements)) {
|
|
4055
|
-
const headerIdx = body.elements.findIndex((el) => typeof el === 'object' &&
|
|
4056
|
-
el !== null &&
|
|
4057
|
-
el.tag === 'div' &&
|
|
4058
|
-
el.text?.tag === 'lark_md' &&
|
|
4059
|
-
(el.text?.content ?? '').startsWith('**🤖'));
|
|
4060
|
-
if (headerIdx >= 0) {
|
|
4061
|
-
body.elements.splice(headerIdx + 1, 0, agentSelector);
|
|
4062
|
-
}
|
|
4063
|
-
else {
|
|
4064
|
-
getLogger().warn('[buildConfigCard] agent tab header not found, falling back to unshift');
|
|
4065
|
-
body.elements.unshift(agentSelector);
|
|
4066
|
-
}
|
|
4067
|
-
}
|
|
4068
|
-
return { card };
|
|
3900
|
+
// 使用共享渲染模块构建卡片(Agent 选择器作为普通 select 字段参与渲染)
|
|
3901
|
+
return { card: buildConfigCardFromTabs(tabs, displayConfig) };
|
|
4069
3902
|
}
|
|
4070
3903
|
/** /order 实现(public:测试直接调用,替代 as unknown as)。 */
|
|
4071
3904
|
cmdOrder(args, _ctx, offset = 0) {
|
|
@@ -4096,49 +3929,16 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
4096
3929
|
// 右侧「删除指令」按钮。两列均 weighted 1:1,跨行对齐。
|
|
4097
3930
|
const leftColumn = order.alias
|
|
4098
3931
|
? [
|
|
4099
|
-
{
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
type: 'callback',
|
|
4107
|
-
value: { cmd: 'order.aliasEdit', orderId: order.id, offset: safeOffset },
|
|
4108
|
-
},
|
|
4109
|
-
],
|
|
4110
|
-
},
|
|
4111
|
-
{
|
|
4112
|
-
tag: 'button',
|
|
4113
|
-
text: { tag: 'plain_text', content: '✕' },
|
|
4114
|
-
type: 'default',
|
|
4115
|
-
size: 'small',
|
|
4116
|
-
behaviors: [
|
|
4117
|
-
{
|
|
4118
|
-
type: 'callback',
|
|
4119
|
-
value: {
|
|
4120
|
-
cmd: 'order.aliasRemove',
|
|
4121
|
-
orderId: order.id,
|
|
4122
|
-
aliasName: order.alias,
|
|
4123
|
-
offset: safeOffset,
|
|
4124
|
-
},
|
|
4125
|
-
},
|
|
4126
|
-
],
|
|
4127
|
-
},
|
|
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' }),
|
|
4128
3939
|
]
|
|
4129
3940
|
: [
|
|
4130
|
-
{
|
|
4131
|
-
tag: 'button',
|
|
4132
|
-
text: { tag: 'plain_text', content: '+别名' },
|
|
4133
|
-
type: 'default',
|
|
4134
|
-
size: 'small',
|
|
4135
|
-
behaviors: [
|
|
4136
|
-
{
|
|
4137
|
-
type: 'callback',
|
|
4138
|
-
value: { cmd: 'order.aliasEdit', orderId: order.id, offset: safeOffset },
|
|
4139
|
-
},
|
|
4140
|
-
],
|
|
4141
|
-
},
|
|
3941
|
+
cardButton('+别名', { cmd: 'order.aliasEdit', orderId: order.id, offset: safeOffset }, { type: 'default', size: 'small' }),
|
|
4142
3942
|
];
|
|
4143
3943
|
elements.push({
|
|
4144
3944
|
tag: 'column_set',
|
|
@@ -4162,18 +3962,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
4162
3962
|
tag: 'column',
|
|
4163
3963
|
width: 'auto',
|
|
4164
3964
|
elements: [
|
|
4165
|
-
{
|
|
4166
|
-
tag: 'button',
|
|
4167
|
-
text: { tag: 'plain_text', content: '▶ 执行' },
|
|
4168
|
-
type: 'primary',
|
|
4169
|
-
size: 'small',
|
|
4170
|
-
behaviors: [
|
|
4171
|
-
{
|
|
4172
|
-
type: 'callback',
|
|
4173
|
-
value: { cmd: 'order.exec', orderId: order.id },
|
|
4174
|
-
},
|
|
4175
|
-
],
|
|
4176
|
-
},
|
|
3965
|
+
cardButton('▶ 执行', { cmd: 'order.exec', orderId: order.id }, { type: 'primary', size: 'small' }),
|
|
4177
3966
|
],
|
|
4178
3967
|
},
|
|
4179
3968
|
{
|
|
@@ -4183,44 +3972,14 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
4183
3972
|
tag: 'column',
|
|
4184
3973
|
width: 'auto',
|
|
4185
3974
|
elements: [
|
|
4186
|
-
{
|
|
4187
|
-
tag: 'button',
|
|
4188
|
-
text: { tag: 'plain_text', content: '编辑' },
|
|
4189
|
-
type: 'default',
|
|
4190
|
-
size: 'small',
|
|
4191
|
-
behaviors: [
|
|
4192
|
-
{
|
|
4193
|
-
type: 'callback',
|
|
4194
|
-
value: {
|
|
4195
|
-
cmd: 'order.textEdit',
|
|
4196
|
-
orderId: order.id,
|
|
4197
|
-
offset: safeOffset,
|
|
4198
|
-
},
|
|
4199
|
-
},
|
|
4200
|
-
],
|
|
4201
|
-
},
|
|
3975
|
+
cardButton('编辑', { cmd: 'order.textEdit', orderId: order.id, offset: safeOffset }, { type: 'default', size: 'small' }),
|
|
4202
3976
|
],
|
|
4203
3977
|
},
|
|
4204
3978
|
{
|
|
4205
3979
|
tag: 'column',
|
|
4206
3980
|
width: 'auto',
|
|
4207
3981
|
elements: [
|
|
4208
|
-
{
|
|
4209
|
-
tag: 'button',
|
|
4210
|
-
text: { tag: 'plain_text', content: '删除' },
|
|
4211
|
-
type: 'danger',
|
|
4212
|
-
size: 'small',
|
|
4213
|
-
behaviors: [
|
|
4214
|
-
{
|
|
4215
|
-
type: 'callback',
|
|
4216
|
-
value: {
|
|
4217
|
-
cmd: 'order.delete',
|
|
4218
|
-
orderId: order.id,
|
|
4219
|
-
offset: safeOffset,
|
|
4220
|
-
},
|
|
4221
|
-
},
|
|
4222
|
-
],
|
|
4223
|
-
},
|
|
3982
|
+
cardButton('删除', { cmd: 'order.delete', orderId: order.id, offset: safeOffset }, { type: 'danger', size: 'small' }),
|
|
4224
3983
|
],
|
|
4225
3984
|
},
|
|
4226
3985
|
],
|