lark-remote 0.1.2 → 0.1.3
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/README.en.md +2 -5
- package/README.md +1 -4
- package/dist/bridge/index.js +9 -1
- package/dist/card/card-budget.js +1 -2
- package/dist/card/run-renderer.js +1 -0
- package/dist/card/run-state.d.ts +4 -0
- package/dist/config/codex-config.d.ts +3 -5
- package/dist/config/codex-config.js +7 -7
- package/dist/config/dir.d.ts +1 -0
- package/dist/config/dir.js +4 -0
- package/dist/config/index.d.ts +0 -10
- package/dist/config/index.js +0 -11
- package/dist/connector/index.js +28 -0
- package/dist/index.js +71 -20
- package/dist/router/config/codex.js +8 -12
- package/dist/router/index.d.ts +14 -1
- package/dist/router/index.js +276 -36
- package/dist/router/utils.d.ts +3 -0
- package/dist/router/utils.js +15 -3
- package/dist/runner/claude/runner.d.ts +0 -1
- package/dist/runner/claude/runner.js +1 -1
- package/dist/runner/codex/runner.d.ts +0 -2
- package/dist/runner/codex/runner.js +1 -1
- package/dist/runner/common/spawning-runner.d.ts +0 -1
- package/dist/runner/common/spawning-runner.js +2 -4
- package/dist/runner/kimi/runner.d.ts +0 -1
- package/dist/runner/kimi/runner.js +1 -1
- package/dist/runner/opencode/runner.d.ts +0 -2
- package/dist/runner/opencode/runner.js +1 -1
- package/dist/runner/pi/runner.d.ts +0 -1
- package/dist/runner/pi/runner.js +1 -1
- package/dist/runner/probe.d.ts +45 -0
- package/dist/runner/probe.js +87 -0
- package/dist/runner/types.d.ts +3 -0
- package/dist/session/claude/sessions.d.ts +1 -1
- package/dist/session/claude/sessions.js +77 -31
- package/dist/session/codex/rollout-reader.js +9 -0
- package/dist/session/kimi/sessions.d.ts +3 -1
- package/dist/session/kimi/sessions.js +9 -3
- package/dist/session/opencode/sessions.d.ts +0 -2
- package/dist/session/opencode/sessions.js +1 -1
- package/dist/startup-contact.d.ts +4 -2
- package/dist/startup-contact.js +7 -4
- package/package.json +39 -4
|
@@ -6,14 +6,12 @@ export class CodexConfigBuilder {
|
|
|
6
6
|
buildFields(displayConfig) {
|
|
7
7
|
const fields = [];
|
|
8
8
|
// 从 codex config.toml 动态读取 provider 和 model
|
|
9
|
-
const
|
|
10
|
-
const codexCfg = loadCodexConfig({ binary: codexBinary });
|
|
9
|
+
const codexCfg = loadCodexConfig();
|
|
11
10
|
const codexProviderNames = codexCfg.providerNames;
|
|
12
11
|
const currentModelProvider = displayConfig.agents?.codex?.modelProvider ?? codexCfg.currentProvider;
|
|
13
12
|
const currentCodexModel = displayConfig.agents?.codex?.model ?? codexCfg.currentModel;
|
|
14
|
-
const currentReasoningEffort = displayConfig.agents?.codex?.reasoningEffort ??
|
|
15
|
-
|
|
16
|
-
// 传入当前 provider 过滤模型列���
|
|
13
|
+
const currentReasoningEffort = displayConfig.agents?.codex?.reasoningEffort ?? getDefaultReasoningEffort(currentCodexModel);
|
|
14
|
+
// 传入当前 provider 过滤模型列表
|
|
17
15
|
const codexModelOptions = codexCfg.modelOptions(currentModelProvider);
|
|
18
16
|
fields.push({
|
|
19
17
|
key: 'agents.codex.modelProvider',
|
|
@@ -42,7 +40,7 @@ export class CodexConfigBuilder {
|
|
|
42
40
|
label: '推理强度',
|
|
43
41
|
type: 'select',
|
|
44
42
|
// 档位按目录实际声明透传(P2-5):codex 支持 none/Custom,不再按标准枚举过滤
|
|
45
|
-
options: getReasoningEffortOptions(currentCodexModel
|
|
43
|
+
options: getReasoningEffortOptions(currentCodexModel),
|
|
46
44
|
currentValue: currentReasoningEffort,
|
|
47
45
|
});
|
|
48
46
|
return fields;
|
|
@@ -50,10 +48,9 @@ export class CodexConfigBuilder {
|
|
|
50
48
|
handleFieldChange(key, value, config) {
|
|
51
49
|
const patches = [];
|
|
52
50
|
patches.push({ key, value });
|
|
53
|
-
// provider 变更时,重置 model
|
|
51
|
+
// provider 变更时,重置 model 为新 provider 的首个模型
|
|
54
52
|
if (key === 'agents.codex.modelProvider' && typeof value === 'string') {
|
|
55
|
-
const
|
|
56
|
-
const codexCfg = loadCodexConfig({ binary: codexBinary });
|
|
53
|
+
const codexCfg = loadCodexConfig();
|
|
57
54
|
const newModelOptions = codexCfg.modelOptions(value);
|
|
58
55
|
const currentModel = config.agents?.codex?.model;
|
|
59
56
|
const currentModelIsValid = newModelOptions.some((m) => m === currentModel);
|
|
@@ -86,14 +83,13 @@ export class CodexConfigBuilder {
|
|
|
86
83
|
* 对 undefined 做键删除,P1)。
|
|
87
84
|
*/
|
|
88
85
|
effortPatchForModel(config, newModel) {
|
|
89
|
-
const codexBinary = config.agents?.codex?.binary ?? 'codex';
|
|
90
86
|
const currentReasoningEffort = config.agents?.codex?.reasoningEffort;
|
|
91
|
-
const newModelSupportedEfforts = getReasoningEffortOptions(newModel
|
|
87
|
+
const newModelSupportedEfforts = getReasoningEffortOptions(newModel);
|
|
92
88
|
const isCurrentEffortValid = currentReasoningEffort && newModelSupportedEfforts.includes(currentReasoningEffort);
|
|
93
89
|
if (isCurrentEffortValid)
|
|
94
90
|
return null;
|
|
95
91
|
const middle = newModelSupportedEfforts[Math.floor((newModelSupportedEfforts.length - 1) / 2)];
|
|
96
|
-
const newEffort = middle ?? getDefaultReasoningEffort(newModel
|
|
92
|
+
const newEffort = middle ?? getDefaultReasoningEffort(newModel);
|
|
97
93
|
return { key: 'agents.codex.reasoningEffort', value: newEffort };
|
|
98
94
|
}
|
|
99
95
|
}
|
package/dist/router/index.d.ts
CHANGED
|
@@ -73,6 +73,8 @@ interface CardActionResponse {
|
|
|
73
73
|
[key: string]: unknown;
|
|
74
74
|
}
|
|
75
75
|
export declare class CommandRouter {
|
|
76
|
+
/** Valid agent kinds — single source of truth for resume.use / resume.page / cmdResume. */
|
|
77
|
+
static readonly VALID_AGENTS: readonly ["claude", "codex", "opencode", "pi", "kimi"];
|
|
76
78
|
private sessionStore;
|
|
77
79
|
private bridge;
|
|
78
80
|
private config;
|
|
@@ -187,6 +189,11 @@ export declare class CommandRouter {
|
|
|
187
189
|
* Mirrors handleLsPage — updates the same card, never sends a new one.
|
|
188
190
|
*/
|
|
189
191
|
private handleResumePage;
|
|
192
|
+
/**
|
|
193
|
+
* Handle active.page: paginate the /active run list in place.
|
|
194
|
+
* Mirrors handleResumePage — updates the same card, never sends a new one.
|
|
195
|
+
*/
|
|
196
|
+
private handleActivePage;
|
|
190
197
|
/**
|
|
191
198
|
* Handle ls.browse: browse directory without switching cwd.
|
|
192
199
|
*/
|
|
@@ -223,6 +230,11 @@ export declare class CommandRouter {
|
|
|
223
230
|
* Handle order.delete: remove the order and update card in place.
|
|
224
231
|
*/
|
|
225
232
|
private handleOrderDelete;
|
|
233
|
+
/**
|
|
234
|
+
* Handle order.page: paginate the /order list in place.
|
|
235
|
+
* Mirrors handleLsPage — updates the same card, never sends a new one.
|
|
236
|
+
*/
|
|
237
|
+
private handleOrderPage;
|
|
226
238
|
/**
|
|
227
239
|
* Handle ws.remove: remove the workspace alias and refresh the /ws list card
|
|
228
240
|
* in place (mirrors handleOrderDelete). Without this the stale alias would
|
|
@@ -273,7 +285,7 @@ export declare class CommandRouter {
|
|
|
273
285
|
*/
|
|
274
286
|
private propagateConfigSave;
|
|
275
287
|
private executeCommand;
|
|
276
|
-
|
|
288
|
+
cmdHelp(): CommandResult;
|
|
277
289
|
private cmdStatus;
|
|
278
290
|
private cmdPs;
|
|
279
291
|
private cmdStop;
|
|
@@ -307,6 +319,7 @@ export declare class CommandRouter {
|
|
|
307
319
|
/**
|
|
308
320
|
* Build CardKit 2.0 /active card from bridge memory (not file system).
|
|
309
321
|
* Shows only runs that were started by THIS bridge process.
|
|
322
|
+
* Paginates with ACTIVE_PAGE_SIZE (default 20) items per page.
|
|
310
323
|
*/
|
|
311
324
|
private buildActiveCardFromMemory;
|
|
312
325
|
private cmdReconnect;
|
package/dist/router/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { MAX_FILE_UPLOAD_SIZE } from '../connector/file-limits.js';
|
|
|
13
13
|
import { atomicWrite } from '../persistence/atomic-write.js';
|
|
14
14
|
/** Config card builder - delegates to per-agent builders */
|
|
15
15
|
import { getConfigBuilder, listRegisteredAgents } from './config/index.js';
|
|
16
|
+
import { probeAllAgents, getCachedAvailability } from '../runner/probe.js';
|
|
16
17
|
import { buildConfigCardFromTabs } from './config/common/render.js';
|
|
17
18
|
/** Max events to read for auto-resume and /resume cards.
|
|
18
19
|
* Limits events at the read stage so enforceCardBudget rarely needs to truncate.
|
|
@@ -20,6 +21,10 @@ import { buildConfigCardFromTabs } from './config/common/render.js';
|
|
|
20
21
|
const AUTO_RESUME_MAX_EVENTS = 5;
|
|
21
22
|
/** /resume 列表页大小;`/resume [N]` 的 N clamp 到 [1, RESUME_PAGE_SIZE]。 */
|
|
22
23
|
const RESUME_PAGE_SIZE = 5;
|
|
24
|
+
/** /active 卡片每页显示的最大条目数(agent run + bash run 合计)。 */
|
|
25
|
+
const ACTIVE_PAGE_SIZE = 20;
|
|
26
|
+
/** /order 列表页大小;指令超过此数量时显示分页导航栏。 */
|
|
27
|
+
const ORDER_PAGE_SIZE = 20;
|
|
23
28
|
/** /resume 列表页内全量预取(readSessionContent)的行数上限;其余行用轻量 summary 兜底。 */
|
|
24
29
|
const RESUME_CONTENT_PREFETCH = 5;
|
|
25
30
|
/**
|
|
@@ -61,6 +66,7 @@ export function isImmediateAction(cmd) {
|
|
|
61
66
|
cmd === 'ls.switch' ||
|
|
62
67
|
cmd === 'ls.page' ||
|
|
63
68
|
cmd === 'resume.page' || // control operation: paginate only, never spawns claude
|
|
69
|
+
cmd === 'active.page' || // control operation: paginate active card
|
|
64
70
|
cmd === 'ws.remove' ||
|
|
65
71
|
cmd === 'resume.use' ||
|
|
66
72
|
cmd === 'ws.use' ||
|
|
@@ -70,12 +76,15 @@ export function isImmediateAction(cmd) {
|
|
|
70
76
|
cmd === 'queue.edit' ||
|
|
71
77
|
cmd === 'queue.input' ||
|
|
72
78
|
cmd === 'order.delete' ||
|
|
79
|
+
cmd === 'order.page' ||
|
|
73
80
|
cmd === 'config.toggle' ||
|
|
74
81
|
cmd === 'config.set' ||
|
|
75
82
|
cmd === 'config.input' ||
|
|
76
83
|
cmd === 'config.save');
|
|
77
84
|
}
|
|
78
85
|
export class CommandRouter {
|
|
86
|
+
/** Valid agent kinds — single source of truth for resume.use / resume.page / cmdResume. */
|
|
87
|
+
static VALID_AGENTS = ['claude', 'codex', 'opencode', 'pi', 'kimi'];
|
|
79
88
|
sessionStore;
|
|
80
89
|
bridge;
|
|
81
90
|
config;
|
|
@@ -200,10 +209,10 @@ export class CommandRouter {
|
|
|
200
209
|
case 'ws.remove':
|
|
201
210
|
return await this.handleWsRemove(value, ctx);
|
|
202
211
|
case 'resume.use': {
|
|
203
|
-
// resume.use
|
|
212
|
+
// resume.use carries an agent field from /resume [agent] list cards
|
|
213
|
+
// (both active and completed cards include the agent field).
|
|
204
214
|
const resumeAgent = value.agent ?? this.config.defaultAgent;
|
|
205
|
-
|
|
206
|
-
const validAgents = ['claude', 'codex', 'opencode', 'pi', 'kimi'];
|
|
215
|
+
const validAgents = CommandRouter.VALID_AGENTS;
|
|
207
216
|
const resolvedAgent = validAgents.includes(resumeAgent)
|
|
208
217
|
? resumeAgent
|
|
209
218
|
: this.config.defaultAgent;
|
|
@@ -226,6 +235,8 @@ export class CommandRouter {
|
|
|
226
235
|
}
|
|
227
236
|
case 'resume.page':
|
|
228
237
|
return this.handleResumePage(value, ctx);
|
|
238
|
+
case 'active.page':
|
|
239
|
+
return this.handleActivePage(value, ctx);
|
|
229
240
|
case 'new-session':
|
|
230
241
|
this.sessionStore.clearSessionId(ctx.userId, this.config.defaultAgent, {
|
|
231
242
|
clearSessionCwd: true,
|
|
@@ -260,6 +271,8 @@ export class CommandRouter {
|
|
|
260
271
|
return await this.handleQueueInput(value, ctx);
|
|
261
272
|
case 'order.delete':
|
|
262
273
|
return await this.handleOrderDelete(value, ctx);
|
|
274
|
+
case 'order.page':
|
|
275
|
+
return await this.handleOrderPage(value, ctx);
|
|
263
276
|
case 'config.toggle':
|
|
264
277
|
case 'config.set':
|
|
265
278
|
case 'config.input':
|
|
@@ -633,9 +646,11 @@ export class CommandRouter {
|
|
|
633
646
|
const inputTask = this.bridge.getQueuedTask(workspace, messageId);
|
|
634
647
|
if (!inputTask) {
|
|
635
648
|
// Task already left the queue (began or was cancelled) before we could
|
|
636
|
-
// register the replacement.
|
|
637
|
-
|
|
638
|
-
|
|
649
|
+
// register the replacement. Return a toast so the edit-form card gets
|
|
650
|
+
// dismissed — sendResult alone leaves the card stuck in the edit state
|
|
651
|
+
// (Feishu keeps the pre-click card when the callback response has no
|
|
652
|
+
// toast/card field).
|
|
653
|
+
return { toast: { type: 'info', content: '任务已不在队列中(可能已开始执行或被撤销)' } };
|
|
639
654
|
}
|
|
640
655
|
this.bridge.setTaskReplacement(workspace, messageId, async () => {
|
|
641
656
|
await this.handle(newMessage, ctx, { cwdOverride: workspace, binding: inputTask.binding });
|
|
@@ -767,8 +782,7 @@ export class CommandRouter {
|
|
|
767
782
|
*/
|
|
768
783
|
async handleResumePage(value, ctx) {
|
|
769
784
|
const resumeAgent = value.agent ?? this.config.defaultAgent;
|
|
770
|
-
|
|
771
|
-
const validAgents = ['claude', 'codex', 'opencode', 'pi', 'kimi'];
|
|
785
|
+
const validAgents = CommandRouter.VALID_AGENTS;
|
|
772
786
|
const resolvedAgent = validAgents.includes(resumeAgent)
|
|
773
787
|
? resumeAgent
|
|
774
788
|
: this.config.defaultAgent;
|
|
@@ -804,6 +818,21 @@ export class CommandRouter {
|
|
|
804
818
|
await this.bridge.updateCardInPlace(result.card, ctx);
|
|
805
819
|
return { toast: { type: 'success', content: '' } };
|
|
806
820
|
}
|
|
821
|
+
/**
|
|
822
|
+
* Handle active.page: paginate the /active run list in place.
|
|
823
|
+
* Mirrors handleResumePage — updates the same card, never sends a new one.
|
|
824
|
+
*/
|
|
825
|
+
async handleActivePage(value, ctx) {
|
|
826
|
+
const rawOffset = Number(value.offset);
|
|
827
|
+
const offset = Number.isFinite(rawOffset) ? Math.max(0, Math.trunc(rawOffset)) : 0;
|
|
828
|
+
const alignedOffset = Math.floor(offset / ACTIVE_PAGE_SIZE) * ACTIVE_PAGE_SIZE;
|
|
829
|
+
const result = this.cmdActive([], ctx, alignedOffset);
|
|
830
|
+
if (!result.card) {
|
|
831
|
+
return { toast: { type: 'error', content: '当前没有正在进行中的任务' } };
|
|
832
|
+
}
|
|
833
|
+
await this.bridge.updateCardInPlace(result.card, ctx);
|
|
834
|
+
return { toast: { type: 'success', content: '' } };
|
|
835
|
+
}
|
|
807
836
|
/**
|
|
808
837
|
* Handle ls.browse: browse directory without switching cwd.
|
|
809
838
|
*/
|
|
@@ -886,11 +915,23 @@ export class CommandRouter {
|
|
|
886
915
|
}
|
|
887
916
|
// Delete the order
|
|
888
917
|
this.orderStore.remove(orderId);
|
|
889
|
-
// Refresh the order list and update card in place
|
|
890
|
-
const
|
|
918
|
+
// Refresh the order list and update card in place, preserving current page
|
|
919
|
+
const currentOffset = Math.max(0, Math.trunc(Number(value.offset) || 0));
|
|
920
|
+
const result = this.cmdOrder([], ctx, currentOffset);
|
|
891
921
|
await this.bridge.updateCardInPlace(result.card, ctx);
|
|
892
922
|
return { toast: { type: 'success', content: '已删除指令' } };
|
|
893
923
|
}
|
|
924
|
+
/**
|
|
925
|
+
* Handle order.page: paginate the /order list in place.
|
|
926
|
+
* Mirrors handleLsPage — updates the same card, never sends a new one.
|
|
927
|
+
*/
|
|
928
|
+
async handleOrderPage(value, ctx) {
|
|
929
|
+
const offset = Math.max(0, Math.trunc(Number(value.offset) || 0));
|
|
930
|
+
// cmdOrder internally clamps stale/out-of-range offsets
|
|
931
|
+
const result = this.cmdOrder([], ctx, offset);
|
|
932
|
+
await this.bridge.updateCardInPlace(result.card, ctx);
|
|
933
|
+
return { toast: { type: 'success', content: '' } };
|
|
934
|
+
}
|
|
894
935
|
/**
|
|
895
936
|
* Handle ws.remove: remove the workspace alias and refresh the /ws list card
|
|
896
937
|
* in place (mirrors handleOrderDelete). Without this the stale alias would
|
|
@@ -1323,7 +1364,7 @@ export class CommandRouter {
|
|
|
1323
1364
|
return await this.cmdReconnect();
|
|
1324
1365
|
case 'config':
|
|
1325
1366
|
case 'c':
|
|
1326
|
-
return this.cmdConfig(args, ctx);
|
|
1367
|
+
return await this.cmdConfig(args, ctx);
|
|
1327
1368
|
case 'order':
|
|
1328
1369
|
case 'o':
|
|
1329
1370
|
return this.cmdOrder(args, ctx);
|
|
@@ -2115,7 +2156,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2115
2156
|
const entry = this.sessionStore.get(ctx.userId);
|
|
2116
2157
|
const cwd = entry?.cwd;
|
|
2117
2158
|
// Valid agent kinds for the /resume [agent] [N] feature
|
|
2118
|
-
const VALID_AGENTS =
|
|
2159
|
+
const VALID_AGENTS = CommandRouter.VALID_AGENTS;
|
|
2119
2160
|
// Parse arguments: /resume [agent] [N] or /resume [sessionId] or /resume [agent] [sessionId]
|
|
2120
2161
|
let agentKind = this.config.defaultAgent;
|
|
2121
2162
|
let limit;
|
|
@@ -2470,7 +2511,7 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2470
2511
|
};
|
|
2471
2512
|
return { card };
|
|
2472
2513
|
}
|
|
2473
|
-
cmdActive(_args, _ctx) {
|
|
2514
|
+
cmdActive(_args, _ctx, offset = 0) {
|
|
2474
2515
|
// List all active runs from bridge memory (not from file system scan)
|
|
2475
2516
|
// New semantics (2026-07-20): only shows runs started by THIS bridge process
|
|
2476
2517
|
const activeRuns = this.bridge.getActiveRuns();
|
|
@@ -2479,18 +2520,40 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2479
2520
|
return { text: '当前没有正在进行中的任务' };
|
|
2480
2521
|
}
|
|
2481
2522
|
// Build CardKit 2.0 card showing memory-based active runs
|
|
2482
|
-
return this.buildActiveCardFromMemory(activeRuns, activeBashRuns);
|
|
2523
|
+
return this.buildActiveCardFromMemory(activeRuns, activeBashRuns, offset);
|
|
2483
2524
|
}
|
|
2484
2525
|
/**
|
|
2485
2526
|
* Build CardKit 2.0 /active card from bridge memory (not file system).
|
|
2486
2527
|
* Shows only runs that were started by THIS bridge process.
|
|
2528
|
+
* Paginates with ACTIVE_PAGE_SIZE (default 20) items per page.
|
|
2487
2529
|
*/
|
|
2488
|
-
buildActiveCardFromMemory(activeRuns, activeBashRuns) {
|
|
2530
|
+
buildActiveCardFromMemory(activeRuns, activeBashRuns, offset = 0) {
|
|
2531
|
+
const totalCount = activeRuns.length + activeBashRuns.length;
|
|
2532
|
+
const pageSize = ACTIVE_PAGE_SIZE;
|
|
2533
|
+
const safeOffset = Math.max(0, Math.min(offset, Math.max(0, totalCount - 1)));
|
|
2534
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));
|
|
2535
|
+
const currentPage = Math.floor(safeOffset / pageSize) + 1;
|
|
2536
|
+
// Slice: distribute offset across agent runs first, then bash runs
|
|
2489
2537
|
const elements = [];
|
|
2538
|
+
// Page info
|
|
2539
|
+
if (totalPages > 1) {
|
|
2540
|
+
elements.push({
|
|
2541
|
+
tag: 'div',
|
|
2542
|
+
text: {
|
|
2543
|
+
tag: 'lark_md',
|
|
2544
|
+
content: `**第 ${currentPage}/${totalPages} 页** (共 ${totalCount} 项)`,
|
|
2545
|
+
},
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
let remaining = pageSize;
|
|
2549
|
+
let skipped = safeOffset;
|
|
2490
2550
|
// Agent runs section
|
|
2491
|
-
if (activeRuns.length > 0) {
|
|
2551
|
+
if (activeRuns.length > 0 && skipped < activeRuns.length && remaining > 0) {
|
|
2492
2552
|
elements.push({ tag: 'div', text: { tag: 'lark_md', content: '## 🤖 Agent 任务' } });
|
|
2493
|
-
|
|
2553
|
+
const start = skipped;
|
|
2554
|
+
const end = Math.min(start + remaining, activeRuns.length);
|
|
2555
|
+
for (let i = start; i < end; i++) {
|
|
2556
|
+
const run = activeRuns[i];
|
|
2494
2557
|
const statusLabel = run.terminal === 'running' ? '运行中' : '处理中';
|
|
2495
2558
|
elements.push({
|
|
2496
2559
|
tag: 'div',
|
|
@@ -2513,11 +2576,19 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2513
2576
|
});
|
|
2514
2577
|
elements.push({ tag: 'hr' });
|
|
2515
2578
|
}
|
|
2579
|
+
remaining -= end - start;
|
|
2580
|
+
skipped = 0;
|
|
2581
|
+
}
|
|
2582
|
+
else {
|
|
2583
|
+
skipped -= activeRuns.length;
|
|
2516
2584
|
}
|
|
2517
2585
|
// Bash runs section
|
|
2518
|
-
if (activeBashRuns.length > 0) {
|
|
2586
|
+
if (activeBashRuns.length > 0 && skipped < activeBashRuns.length && remaining > 0) {
|
|
2519
2587
|
elements.push({ tag: 'div', text: { tag: 'lark_md', content: '## 💻 Bash 命令' } });
|
|
2520
|
-
|
|
2588
|
+
const start = skipped;
|
|
2589
|
+
const end = Math.min(start + remaining, activeBashRuns.length);
|
|
2590
|
+
for (let i = start; i < end; i++) {
|
|
2591
|
+
const run = activeBashRuns[i];
|
|
2521
2592
|
const statusLabel = run.terminal === 'running' ? '运行中' : '处理中';
|
|
2522
2593
|
elements.push({
|
|
2523
2594
|
tag: 'div',
|
|
@@ -2538,6 +2609,38 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2538
2609
|
elements.push({ tag: 'hr' });
|
|
2539
2610
|
}
|
|
2540
2611
|
}
|
|
2612
|
+
// Pagination buttons
|
|
2613
|
+
if (totalPages > 1) {
|
|
2614
|
+
const paginationButtons = [];
|
|
2615
|
+
if (currentPage > 1) {
|
|
2616
|
+
const prevOffset = (currentPage - 2) * pageSize;
|
|
2617
|
+
paginationButtons.push({
|
|
2618
|
+
tag: 'button',
|
|
2619
|
+
text: { tag: 'plain_text', content: '◀ 上一页' },
|
|
2620
|
+
type: 'default',
|
|
2621
|
+
size: 'small',
|
|
2622
|
+
behaviors: [{ type: 'callback', value: { cmd: 'active.page', offset: prevOffset } }],
|
|
2623
|
+
});
|
|
2624
|
+
}
|
|
2625
|
+
if (currentPage < totalPages) {
|
|
2626
|
+
const nextOffset = currentPage * pageSize;
|
|
2627
|
+
paginationButtons.push({
|
|
2628
|
+
tag: 'button',
|
|
2629
|
+
text: { tag: 'plain_text', content: '下一页 ▶' },
|
|
2630
|
+
type: 'primary',
|
|
2631
|
+
size: 'small',
|
|
2632
|
+
behaviors: [{ type: 'callback', value: { cmd: 'active.page', offset: nextOffset } }],
|
|
2633
|
+
});
|
|
2634
|
+
}
|
|
2635
|
+
elements.push({
|
|
2636
|
+
tag: 'column_set',
|
|
2637
|
+
columns: paginationButtons.map((btn) => ({
|
|
2638
|
+
tag: 'column',
|
|
2639
|
+
width: 'auto',
|
|
2640
|
+
elements: [btn],
|
|
2641
|
+
})),
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2541
2644
|
const card = {
|
|
2542
2645
|
schema: '2.0',
|
|
2543
2646
|
config: { wide_screen_mode: true },
|
|
@@ -2558,7 +2661,9 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2558
2661
|
return { text: '重连失败,请检查网络和配置' };
|
|
2559
2662
|
}
|
|
2560
2663
|
}
|
|
2561
|
-
cmdConfig(args, ctx) {
|
|
2664
|
+
async cmdConfig(args, ctx) {
|
|
2665
|
+
// Ensure probe cache is populated before building the card.
|
|
2666
|
+
await probeAllAgents();
|
|
2562
2667
|
// Check if this is a direct set command: /config <key> <value>
|
|
2563
2668
|
// Command-style set: immediate write to disk, clear pendingConfig
|
|
2564
2669
|
if (args.length >= 2) {
|
|
@@ -2604,25 +2709,58 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2604
2709
|
// 使用 pendingConfig(若存在)或当前 config
|
|
2605
2710
|
const displayConfig = this.pendingConfig ?? this.config;
|
|
2606
2711
|
// 定义字段分组:defaultAgent 选择器 + 该 agent 的配置字段 + idle.watchdogMinutes +
|
|
2607
|
-
// output/logging。stopGraceMs
|
|
2712
|
+
// output/logging。stopGraceMs 等实现细节走 YAML,不在卡片暴露。
|
|
2608
2713
|
// 根据 defaultAgent 构建完整的 Agent 配置字段组
|
|
2609
2714
|
// 使用 agent config builder 获取该 agent 的配置字段
|
|
2610
2715
|
const defaultAgent = displayConfig.defaultAgent ?? 'claude';
|
|
2611
2716
|
const configBuilder = getConfigBuilder(defaultAgent);
|
|
2612
|
-
|
|
2613
|
-
//
|
|
2614
|
-
//
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2717
|
+
// Build defaultAgent selector with availability annotations.
|
|
2718
|
+
// Direct inline construction (not via ConfigField) so that text (display label)
|
|
2719
|
+
// and value (agentKind for config storage) can differ — uninstalled agents get
|
|
2720
|
+
// a "⚠️ (未安装)" suffix in the label while the value stays clean.
|
|
2721
|
+
const allAgents = listRegisteredAgents();
|
|
2722
|
+
const agentOptions = allAgents.map((kind) => {
|
|
2723
|
+
const available = getCachedAvailability(kind);
|
|
2724
|
+
const label = available === false ? `${agentDisplayName(kind)} ⚠️ (未安装)` : agentDisplayName(kind);
|
|
2725
|
+
return { text: { tag: 'plain_text', content: label }, value: kind };
|
|
2620
2726
|
});
|
|
2727
|
+
const selectedAgent = displayConfig.defaultAgent ?? 'claude';
|
|
2728
|
+
const agentSelector = {
|
|
2729
|
+
tag: 'column_set',
|
|
2730
|
+
flex_mode: 'none',
|
|
2731
|
+
columns: [
|
|
2732
|
+
{
|
|
2733
|
+
tag: 'column',
|
|
2734
|
+
width: 'weighted',
|
|
2735
|
+
weight: 2,
|
|
2736
|
+
vertical_align: 'center',
|
|
2737
|
+
elements: [{ tag: 'div', text: { tag: 'lark_md', content: '默认 Agent' } }],
|
|
2738
|
+
},
|
|
2739
|
+
{
|
|
2740
|
+
tag: 'column',
|
|
2741
|
+
width: 'weighted',
|
|
2742
|
+
weight: 3,
|
|
2743
|
+
vertical_align: 'center',
|
|
2744
|
+
elements: [
|
|
2745
|
+
{
|
|
2746
|
+
tag: 'select_static',
|
|
2747
|
+
placeholder: { tag: 'plain_text', content: '请选择' },
|
|
2748
|
+
options: agentOptions,
|
|
2749
|
+
initial_option: selectedAgent,
|
|
2750
|
+
behaviors: [{ type: 'callback', value: { cmd: 'config.set', key: 'defaultAgent' } }],
|
|
2751
|
+
},
|
|
2752
|
+
],
|
|
2753
|
+
},
|
|
2754
|
+
],
|
|
2755
|
+
};
|
|
2756
|
+
// Agent config fields — no longer include defaultAgent as a ConfigField
|
|
2757
|
+
// (it's rendered inline above with text/value separation).
|
|
2758
|
+
const agentConfigFieldsNoSelector = configBuilder.buildFields(displayConfig);
|
|
2621
2759
|
const tabs = [
|
|
2622
2760
|
{
|
|
2623
2761
|
id: 'agent',
|
|
2624
2762
|
label: `🤖 ${agentDisplayName(defaultAgent)}`,
|
|
2625
|
-
fields:
|
|
2763
|
+
fields: agentConfigFieldsNoSelector,
|
|
2626
2764
|
},
|
|
2627
2765
|
{
|
|
2628
2766
|
id: 'idle',
|
|
@@ -2653,11 +2791,30 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2653
2791
|
];
|
|
2654
2792
|
// 使用共享渲染模块构建卡片
|
|
2655
2793
|
const card = buildConfigCardFromTabs(tabs, displayConfig);
|
|
2794
|
+
// Inject the agent selector as the first element in the agent tab section,
|
|
2795
|
+
// right after the "**🤖 ..." header, so text/value can differ for uninstalled agents.
|
|
2796
|
+
// NOTE: findIndex relies on the agent tab header starting with "**🤖" — if the
|
|
2797
|
+
// tab label format changes, this fallback will fire and the selector moves to top.
|
|
2798
|
+
const body = card.body;
|
|
2799
|
+
if (body && Array.isArray(body.elements)) {
|
|
2800
|
+
const headerIdx = body.elements.findIndex((el) => typeof el === 'object' &&
|
|
2801
|
+
el !== null &&
|
|
2802
|
+
el.tag === 'div' &&
|
|
2803
|
+
el.text?.tag === 'lark_md' &&
|
|
2804
|
+
(el.text?.content ?? '').startsWith('**🤖'));
|
|
2805
|
+
if (headerIdx >= 0) {
|
|
2806
|
+
body.elements.splice(headerIdx + 1, 0, agentSelector);
|
|
2807
|
+
}
|
|
2808
|
+
else {
|
|
2809
|
+
getLogger().warn('[buildConfigCard] agent tab header not found, falling back to unshift');
|
|
2810
|
+
body.elements.unshift(agentSelector);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2656
2813
|
return { card };
|
|
2657
2814
|
}
|
|
2658
|
-
cmdOrder(args, _ctx) {
|
|
2815
|
+
cmdOrder(args, _ctx, offset = 0) {
|
|
2659
2816
|
const sub = args[0]?.toLowerCase();
|
|
2660
|
-
// /order — list orders (
|
|
2817
|
+
// /order — list orders (CardKit 2.0 with pagination)
|
|
2661
2818
|
if (!sub || sub === 'list') {
|
|
2662
2819
|
this.orderStore.reload();
|
|
2663
2820
|
const allOrders = this.orderStore.get();
|
|
@@ -2666,8 +2823,17 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2666
2823
|
elements.push({ tag: 'div', text: { tag: 'lark_md', content: '暂无指令' } });
|
|
2667
2824
|
}
|
|
2668
2825
|
else {
|
|
2669
|
-
|
|
2670
|
-
|
|
2826
|
+
// Pagination calculations
|
|
2827
|
+
const totalCount = allOrders.length;
|
|
2828
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / ORDER_PAGE_SIZE));
|
|
2829
|
+
// Clamp offset to last page boundary to avoid empty pages
|
|
2830
|
+
const maxOffset = Math.max(0, (totalPages - 1) * ORDER_PAGE_SIZE);
|
|
2831
|
+
const safeOffset = Math.min(Math.max(offset, 0), maxOffset);
|
|
2832
|
+
const currentPage = Math.floor(safeOffset / ORDER_PAGE_SIZE) + 1;
|
|
2833
|
+
const pageOrders = allOrders.slice(safeOffset, safeOffset + ORDER_PAGE_SIZE);
|
|
2834
|
+
const hasPagination = totalCount > ORDER_PAGE_SIZE;
|
|
2835
|
+
for (let i = 0; i < pageOrders.length; i++) {
|
|
2836
|
+
const order = pageOrders[i];
|
|
2671
2837
|
const displayText = order.text.length > 100 ? order.text.slice(0, 97) + '...' : order.text;
|
|
2672
2838
|
// 文字在上方
|
|
2673
2839
|
elements.push({ tag: 'div', text: { tag: 'lark_md', content: displayText } });
|
|
@@ -2700,7 +2866,10 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2700
2866
|
type: 'danger',
|
|
2701
2867
|
size: 'small',
|
|
2702
2868
|
behaviors: [
|
|
2703
|
-
{
|
|
2869
|
+
{
|
|
2870
|
+
type: 'callback',
|
|
2871
|
+
value: { cmd: 'order.delete', orderId: order.id, offset: safeOffset },
|
|
2872
|
+
},
|
|
2704
2873
|
],
|
|
2705
2874
|
},
|
|
2706
2875
|
],
|
|
@@ -2708,10 +2877,81 @@ ${sessionCwdLine}${agentLines.map((l) => `- ${l}`).join('\n')}
|
|
|
2708
2877
|
],
|
|
2709
2878
|
});
|
|
2710
2879
|
// 非最后一项,添加分隔线
|
|
2711
|
-
if (i <
|
|
2880
|
+
if (i < pageOrders.length - 1) {
|
|
2712
2881
|
elements.push({ tag: 'hr' });
|
|
2713
2882
|
}
|
|
2714
2883
|
}
|
|
2884
|
+
// Pagination bar (only shown when there are more items than ORDER_PAGE_SIZE)
|
|
2885
|
+
if (hasPagination) {
|
|
2886
|
+
const hasPrev = safeOffset > 0;
|
|
2887
|
+
const hasNext = safeOffset + ORDER_PAGE_SIZE < totalCount;
|
|
2888
|
+
const pageColumns = [];
|
|
2889
|
+
if (hasPrev) {
|
|
2890
|
+
pageColumns.push({
|
|
2891
|
+
tag: 'column',
|
|
2892
|
+
width: 'auto',
|
|
2893
|
+
vertical_align: 'center',
|
|
2894
|
+
elements: [
|
|
2895
|
+
{
|
|
2896
|
+
tag: 'button',
|
|
2897
|
+
text: { tag: 'plain_text', content: '⬅ 上一页' },
|
|
2898
|
+
type: 'default',
|
|
2899
|
+
size: 'small',
|
|
2900
|
+
behaviors: [
|
|
2901
|
+
{
|
|
2902
|
+
type: 'callback',
|
|
2903
|
+
value: {
|
|
2904
|
+
cmd: 'order.page',
|
|
2905
|
+
offset: safeOffset - ORDER_PAGE_SIZE,
|
|
2906
|
+
},
|
|
2907
|
+
},
|
|
2908
|
+
],
|
|
2909
|
+
},
|
|
2910
|
+
],
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
pageColumns.push({
|
|
2914
|
+
tag: 'column',
|
|
2915
|
+
width: 'weighted',
|
|
2916
|
+
weight: 1,
|
|
2917
|
+
vertical_align: 'center',
|
|
2918
|
+
elements: [
|
|
2919
|
+
{
|
|
2920
|
+
tag: 'div',
|
|
2921
|
+
text: {
|
|
2922
|
+
tag: 'lark_md',
|
|
2923
|
+
content: `**第 ${currentPage}/${totalPages} 页**(共 ${totalCount} 条)`,
|
|
2924
|
+
},
|
|
2925
|
+
},
|
|
2926
|
+
],
|
|
2927
|
+
});
|
|
2928
|
+
if (hasNext) {
|
|
2929
|
+
pageColumns.push({
|
|
2930
|
+
tag: 'column',
|
|
2931
|
+
width: 'auto',
|
|
2932
|
+
vertical_align: 'center',
|
|
2933
|
+
elements: [
|
|
2934
|
+
{
|
|
2935
|
+
tag: 'button',
|
|
2936
|
+
text: { tag: 'plain_text', content: '下一页 ➡' },
|
|
2937
|
+
type: 'default',
|
|
2938
|
+
size: 'small',
|
|
2939
|
+
behaviors: [
|
|
2940
|
+
{
|
|
2941
|
+
type: 'callback',
|
|
2942
|
+
value: {
|
|
2943
|
+
cmd: 'order.page',
|
|
2944
|
+
offset: safeOffset + ORDER_PAGE_SIZE,
|
|
2945
|
+
},
|
|
2946
|
+
},
|
|
2947
|
+
],
|
|
2948
|
+
},
|
|
2949
|
+
],
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
elements.push({ tag: 'hr' });
|
|
2953
|
+
elements.push({ tag: 'column_set', columns: pageColumns });
|
|
2954
|
+
}
|
|
2715
2955
|
}
|
|
2716
2956
|
return {
|
|
2717
2957
|
card: {
|
package/dist/router/utils.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import type { ActiveRunSnapshot } from '../bridge/index.js';
|
|
6
6
|
export interface SessionDisplayUsage {
|
|
7
7
|
contextLength?: number;
|
|
8
|
+
/** 当前模型 context window 上限(仅 codex 提供);用于渲染 "Context - X (Y%)"。 */
|
|
9
|
+
contextLimit?: number;
|
|
8
10
|
compactCount?: number;
|
|
9
11
|
cacheReadTokens?: number;
|
|
10
12
|
cacheCreationTokens?: number;
|
|
@@ -18,6 +20,7 @@ export declare function activeRunUsage(activeRun: ActiveRunSnapshot | undefined)
|
|
|
18
20
|
*/
|
|
19
21
|
export declare function formatUsageStats(usage?: {
|
|
20
22
|
contextLength?: number;
|
|
23
|
+
contextLimit?: number;
|
|
21
24
|
compactCount?: number;
|
|
22
25
|
cacheReadTokens?: number;
|
|
23
26
|
cacheCreationTokens?: number;
|
package/dist/router/utils.js
CHANGED
|
@@ -14,10 +14,18 @@ export function activeRunUsage(activeRun) {
|
|
|
14
14
|
}
|
|
15
15
|
// --- Extracted helpers for refactoring ---
|
|
16
16
|
/**
|
|
17
|
-
* Format number to
|
|
18
|
-
* (e.g.,
|
|
17
|
+
* Format number to human-readable unit string.
|
|
18
|
+
* - >= 1M: one decimal, M suffix (e.g., 1200000 → 1.2M, 25000000 → 25M)
|
|
19
|
+
* - >= 1K: rounded to nearest integer K (e.g., 1500 → 2K, 120000 → 120K)
|
|
20
|
+
* - < 1K: as-is (e.g., 500 → "500")
|
|
19
21
|
*/
|
|
20
22
|
function formatTokenK(n) {
|
|
23
|
+
if (n >= 1_000_000) {
|
|
24
|
+
const m = n / 1_000_000;
|
|
25
|
+
const rounded = Math.round(m * 10) / 10;
|
|
26
|
+
// Drop unnecessary ".0" for whole numbers (2.0M → 2M)
|
|
27
|
+
return rounded === Math.floor(rounded) ? `${Math.floor(rounded)}M` : `${rounded}M`;
|
|
28
|
+
}
|
|
21
29
|
if (n >= 1000) {
|
|
22
30
|
const k = Math.round(n / 1000);
|
|
23
31
|
return `${k}K`;
|
|
@@ -40,7 +48,11 @@ export function formatUsageStats(usage, options) {
|
|
|
40
48
|
}
|
|
41
49
|
// 2. Context 长度
|
|
42
50
|
if (usage?.contextLength !== undefined) {
|
|
43
|
-
|
|
51
|
+
const ctxLimit = usage.contextLimit;
|
|
52
|
+
const ctxPercent = ctxLimit !== undefined && ctxLimit > 0
|
|
53
|
+
? ` (${Math.round((usage.contextLength / ctxLimit) * 100)}%)`
|
|
54
|
+
: '';
|
|
55
|
+
lines.push(`Context - ${formatTokenK(usage.contextLength)}${ctxPercent}`);
|
|
44
56
|
}
|
|
45
57
|
// 3. Compact 次数
|
|
46
58
|
if (usage?.compactCount) {
|