lark-remote 0.5.20 → 0.5.21
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 +10 -0
- package/dist/bridge/inbound-media.js +71 -45
- package/dist/bridge/index.d.ts +9 -0
- package/dist/bridge/index.js +230 -203
- package/dist/card/approval-render.js +32 -33
- package/dist/card/card-budget.js +123 -98
- package/dist/card/run-renderer.js +82 -68
- package/dist/card/run-state.d.ts +0 -14
- package/dist/card/run-state.js +335 -207
- package/dist/card/tool-render.js +20 -16
- package/dist/card/usage-format.d.ts +12 -9
- package/dist/card/usage-format.js +111 -97
- package/dist/common/text-width.js +36 -24
- package/dist/common/tool-summary.js +132 -90
- package/dist/index.js +226 -190
- package/dist/message-handlers.js +253 -236
- package/dist/router/commands/ls.d.ts +3 -0
- package/dist/router/commands/ls.js +256 -175
- package/dist/router/commands/order.js +184 -159
- package/dist/router/commands/resume.js +189 -133
- package/dist/router/commands/ws.js +219 -174
- package/dist/router/index.js +192 -201
- package/dist/runner/bash/runner.js +2 -4
- package/dist/runner/codex/app-server/translator.d.ts +2 -0
- package/dist/runner/codex/app-server/translator.js +287 -221
- package/dist/runner/common/acp/base-translator.js +19 -17
- package/dist/runner/common/jsonl-stream.js +154 -187
- package/dist/runner/common/jsonrpc/client.d.ts +29 -0
- package/dist/runner/common/jsonrpc/client.js +43 -12
- package/dist/runner/common/spawning-runner.d.ts +5 -0
- package/dist/runner/common/spawning-runner.js +8 -3
- package/dist/runner/common/turn-started.d.ts +8 -0
- package/dist/runner/common/turn-started.js +11 -0
- package/dist/runner/dsh/approval.js +84 -51
- package/dist/runner/dsh/client.d.ts +7 -9
- package/dist/runner/dsh/client.js +356 -287
- package/dist/runner/dsh/mux-stream.js +28 -8
- package/dist/runner/dsh/runner.d.ts +4 -3
- package/dist/runner/dsh/runner.js +88 -64
- package/dist/runner/dsh/translator.d.ts +1 -31
- package/dist/runner/dsh/translator.js +180 -178
- package/dist/runner/kimi/acp/runner.js +81 -44
- package/dist/runner/opencode/acp/error-monitor.js +5 -5
- package/dist/runner/pi/rpc/client.js +4 -13
- package/dist/runner/pi/rpc/translator.js +61 -60
- package/dist/session/claude/sessions.js +113 -101
- package/dist/session/codex/rollout-reader.js +179 -180
- package/dist/session/dsh/sessions.js +281 -231
- package/dist/session/kimi/sessions.d.ts +0 -7
- package/dist/session/kimi/sessions.js +129 -109
- package/dist/session/opencode/sessions.d.ts +0 -11
- package/dist/session/opencode/sessions.js +161 -137
- package/dist/session/session-store.d.ts +8 -0
- package/dist/session/session-store.js +98 -98
- package/package.json +1 -1
package/dist/router/index.js
CHANGED
|
@@ -362,6 +362,169 @@ function touchesAgentsSubtree(keys, kind) {
|
|
|
362
362
|
function touchesAgentConfig(keys, kind) {
|
|
363
363
|
return (touchesAgentsSubtree(keys, kind) || keys.some((k) => k === kind || k.startsWith(`${kind}.`)));
|
|
364
364
|
}
|
|
365
|
+
const CARD_ACTION_SPECS = new Map([
|
|
366
|
+
['ls.file', { method: 'cardLsFile', args: 'path' }],
|
|
367
|
+
['ls.refresh', { method: 'handleLsRefresh', args: 'voidCall' }],
|
|
368
|
+
['ls.browse', { method: 'handleLsBrowse', args: 'voidCall' }],
|
|
369
|
+
['ls.switch', { method: 'handleLsSwitch', args: 'voidCall' }],
|
|
370
|
+
['ls.page', { method: 'handleLsPage', args: 'returnCall' }],
|
|
371
|
+
['ls.filter', { method: 'handleLsFilter', args: 'returnCall' }],
|
|
372
|
+
['ws.page', { method: 'handleWsPage', args: 'returnCall' }],
|
|
373
|
+
['ws.filter', { method: 'handleWsFilter', args: 'returnCall' }],
|
|
374
|
+
['ws.use', { method: 'handleWsUse', args: 'returnCall' }],
|
|
375
|
+
['ws.sort', { method: 'handleWsSort', args: 'returnCall' }],
|
|
376
|
+
['ws.remove', { method: 'handleWsRemove', args: 'returnCall' }],
|
|
377
|
+
['resume.use', { method: 'handleResumeUse', args: 'voidCall' }],
|
|
378
|
+
['resume.page', { method: 'handleResumePage', args: 'returnCall' }],
|
|
379
|
+
['active.page', { method: 'handleActivePage', args: 'returnCall' }],
|
|
380
|
+
['new-session', { method: 'handleNewSession', args: 'ctxOnly' }],
|
|
381
|
+
['stop', { method: 'handleCardStop', args: 'voidCall' }],
|
|
382
|
+
['job.kill', { method: 'handleJobKill', args: 'voidCall' }],
|
|
383
|
+
['queue.cancel', { method: 'handleQueueCancel', args: 'voidCall' }],
|
|
384
|
+
['queue.immediate', { method: 'handleQueueImmediate', args: 'voidCall' }],
|
|
385
|
+
['queue.diagnose', { method: 'handleQueueDiagnose', args: 'voidCall' }],
|
|
386
|
+
['queue.edit', { method: 'handleQueueEdit', args: 'voidCall' }],
|
|
387
|
+
['queue.input', { method: 'handleQueueInput', args: 'returnCall' }],
|
|
388
|
+
['order.delete', { method: 'handleOrderDelete', args: 'returnCall' }],
|
|
389
|
+
['order.page', { method: 'handleOrderPage', args: 'returnCall' }],
|
|
390
|
+
['order.aliasEdit', { method: 'handleOrderAliasEdit', args: 'returnCall' }],
|
|
391
|
+
['order.aliasInput', { method: 'handleOrderAliasInput', args: 'returnCall' }],
|
|
392
|
+
['order.aliasRemove', { method: 'handleOrderAliasRemove', args: 'returnCall' }],
|
|
393
|
+
['order.textEdit', { method: 'handleOrderTextEdit', args: 'returnCall' }],
|
|
394
|
+
['order.textInput', { method: 'handleOrderTextInput', args: 'returnCall' }],
|
|
395
|
+
['config.set', { method: 'enqueueConfigAction', args: 'returnCall' }],
|
|
396
|
+
['config.input', { method: 'enqueueConfigAction', args: 'returnCall' }],
|
|
397
|
+
['config.save', { method: 'enqueueConfigAction', args: 'returnCall' }],
|
|
398
|
+
['compact', { method: 'handleCompact', args: 'voidCall' }],
|
|
399
|
+
['resume.compact', { method: 'handleResumeCompact', args: 'voidCall' }],
|
|
400
|
+
]);
|
|
401
|
+
const COMMAND_SPECS = new Map([
|
|
402
|
+
['help', { method: 'cmdHelp', args: 'none' }],
|
|
403
|
+
['h', { method: 'cmdHelp', args: 'none' }],
|
|
404
|
+
['status', { method: 'cmdStatus', args: 'ctxOnly' }],
|
|
405
|
+
['s', { method: 'cmdStatus', args: 'ctxOnly' }],
|
|
406
|
+
['ps', { method: 'cmdPs', args: 'ctxOnly' }],
|
|
407
|
+
['stop', { method: 'cmdStop', args: 'ctxOnly' }],
|
|
408
|
+
['t', { method: 'cmdStop', args: 'ctxOnly' }],
|
|
409
|
+
['exit', { method: 'cmdExit', args: 'none' }],
|
|
410
|
+
['quit', { method: 'cmdExit', args: 'none' }],
|
|
411
|
+
['e', { method: 'cmdExit', args: 'none' }],
|
|
412
|
+
['restart', { method: 'cmdRestart', args: 'none' }],
|
|
413
|
+
['new', { method: 'cmdNew', args: 'ctxOnly' }],
|
|
414
|
+
['cd', { method: 'cmdCd', args: 'argsCtx' }],
|
|
415
|
+
['ls', { method: 'cmdLs', args: 'argsCtx' }],
|
|
416
|
+
['download', { method: 'cmdDownload', args: 'argsCtx' }],
|
|
417
|
+
['d', { method: 'cmdDownload', args: 'argsCtx' }],
|
|
418
|
+
['ws', { method: 'cmdWs', args: 'argsCtx' }],
|
|
419
|
+
['resume', { method: 'cmdResume', args: 'argsCtx' }],
|
|
420
|
+
['r', { method: 'cmdResume', args: 'argsCtx' }],
|
|
421
|
+
['active', { method: 'cmdActive', args: 'argsCtx' }],
|
|
422
|
+
['config', { method: 'cmdConfig', args: 'argsCtx' }],
|
|
423
|
+
['c', { method: 'cmdConfig', args: 'argsCtx' }],
|
|
424
|
+
['order', { method: 'cmdOrder', args: 'argsCtx' }],
|
|
425
|
+
['o', { method: 'cmdOrder', args: 'argsCtx' }],
|
|
426
|
+
['update', { method: 'cmdUpdate', args: 'argsCtx' }],
|
|
427
|
+
['u', { method: 'cmdUpdate', args: 'argsCtx' }],
|
|
428
|
+
['clone', { method: 'cmdClone', args: 'argsCtx' }],
|
|
429
|
+
]);
|
|
430
|
+
/** 命令名 → 命令方法查表分发;无匹配时返回与旧 default 分支一致的文案。 */
|
|
431
|
+
function dispatchCommand(router, cmd, args, ctx) {
|
|
432
|
+
const spec = COMMAND_SPECS.get(cmd ?? '');
|
|
433
|
+
if (!spec) {
|
|
434
|
+
return { text: `未知命令 /${cmd},输入 /help 查看可用命令` };
|
|
435
|
+
}
|
|
436
|
+
const handler = router[spec.method];
|
|
437
|
+
if (spec.args === 'none') {
|
|
438
|
+
return handler.call(router);
|
|
439
|
+
}
|
|
440
|
+
if (spec.args === 'ctxOnly') {
|
|
441
|
+
return handler.call(router, ctx);
|
|
442
|
+
}
|
|
443
|
+
return handler.call(router, args, ctx);
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* defaultAgent 切换时的 session 迁移(Step 0-3)。抽为模块级命名函数,
|
|
447
|
+
* 避免其内部分支与判定闭包计入外层 propagateConfigSave 的 CC。
|
|
448
|
+
*/
|
|
449
|
+
function applyDefaultAgentSwitch(sessionStore, userId, oldDefaultAgent, newAgent) {
|
|
450
|
+
// Step 0: 在清空 old 之前计算「用户活动」基线差(arrival 基线):
|
|
451
|
+
const oldSessionId = sessionStore.getSessionId(userId, oldDefaultAgent);
|
|
452
|
+
const oldArrivalSessionId = sessionStore.getArrivalSessionId(userId, oldDefaultAgent);
|
|
453
|
+
const userChangedOld = (oldSessionId ?? '') !== (oldArrivalSessionId ?? '');
|
|
454
|
+
// Step 1: 保存旧 agent 的 session 到 previousSessions(如果有)
|
|
455
|
+
if (oldSessionId) {
|
|
456
|
+
sessionStore.setPreviousSessionId(userId, oldDefaultAgent, oldSessionId);
|
|
457
|
+
}
|
|
458
|
+
// Step 2: 清除旧 agent 的 session(保存后清除,为新会话腾出空间)
|
|
459
|
+
sessionStore.clearSessionId(userId, oldDefaultAgent);
|
|
460
|
+
// Step 2.5: 显式选择判定
|
|
461
|
+
const newSessionId = sessionStore.getSessionId(userId, newAgent);
|
|
462
|
+
const newArrivalSessionId = sessionStore.getArrivalSessionId(userId, newAgent);
|
|
463
|
+
const userSelectedNew = !!newSessionId && (newSessionId ?? '') !== (newArrivalSessionId ?? '');
|
|
464
|
+
// Step 3: 恢复判定
|
|
465
|
+
const previousSessionId = sessionStore.getPreviousSessionId(userId, newAgent);
|
|
466
|
+
const canRestore = !!previousSessionId && !userChangedOld;
|
|
467
|
+
if (userSelectedNew) {
|
|
468
|
+
sessionStore.setArrivalSessionId(userId, newAgent, newSessionId);
|
|
469
|
+
const newAgentName = agentDisplayName(newAgent);
|
|
470
|
+
return {
|
|
471
|
+
notice: `已切换到 ${newAgentName},已使用所选 session,sessionId: ${newSessionId}`,
|
|
472
|
+
newAgent,
|
|
473
|
+
sessionId: newSessionId,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
if (canRestore) {
|
|
477
|
+
sessionStore.setSessionId(userId, newAgent, previousSessionId);
|
|
478
|
+
sessionStore.clearPreviousSessionId(userId, newAgent);
|
|
479
|
+
sessionStore.setArrivalSessionId(userId, newAgent, previousSessionId);
|
|
480
|
+
const newAgentName = agentDisplayName(newAgent);
|
|
481
|
+
return {
|
|
482
|
+
notice: `已切换到 ${newAgentName},将继续之前的 session,sessionId: ${previousSessionId}`,
|
|
483
|
+
newAgent,
|
|
484
|
+
sessionId: previousSessionId,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
sessionStore.clearSessionId(userId, newAgent, { clearSessionCwd: true });
|
|
488
|
+
sessionStore.setArrivalSessionId(userId, newAgent, '');
|
|
489
|
+
const newAgentName = agentDisplayName(newAgent);
|
|
490
|
+
return {
|
|
491
|
+
notice: `已切换到 ${newAgentName},session 已清空,下次消息将启动新对话`,
|
|
492
|
+
newAgent,
|
|
493
|
+
sessionId: '',
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* DSH 会话边界变更(preset/host)共用的 notice 计算。返回更新后的 notice;
|
|
498
|
+
* 条件不满足时原样返回 currentNotice,等价于原闭包「不修改」的语义。
|
|
499
|
+
*/
|
|
500
|
+
function parkDshSessionBoundaryNotice(sessionStore, userId, defaultAgent, changed, label, currentNotice) {
|
|
501
|
+
if (!changed || defaultAgent !== 'dsh' || currentNotice)
|
|
502
|
+
return currentNotice;
|
|
503
|
+
const oldSessionId = sessionStore.getSessionId(userId, 'dsh');
|
|
504
|
+
if (oldSessionId) {
|
|
505
|
+
sessionStore.setPreviousSessionId(userId, 'dsh', oldSessionId);
|
|
506
|
+
sessionStore.clearSessionId(userId, 'dsh');
|
|
507
|
+
return `DSH ${label} 已变更,旧会话已停放到恢复槽(可 /resume 校验后恢复),下次消息将新建会话`;
|
|
508
|
+
}
|
|
509
|
+
return `DSH ${label} 已变更,下次消息将新建会话`;
|
|
510
|
+
}
|
|
511
|
+
function computeHasAgentConfigChange(changedKeys) {
|
|
512
|
+
if (changedKeys.includes('defaultAgent'))
|
|
513
|
+
return true;
|
|
514
|
+
for (const kind of AGENT_KINDS) {
|
|
515
|
+
if (touchesAgentConfig(changedKeys, kind))
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
const APPROVAL_AGENT_KINDS = ['codex', 'kimi', 'opencode'];
|
|
521
|
+
function computeHasApprovalConfigChange(changedKeys) {
|
|
522
|
+
for (const kind of APPROVAL_AGENT_KINDS) {
|
|
523
|
+
if (touchesAgentConfig(changedKeys, kind))
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
return changedKeys.includes('agents.dsh.permissionPreset');
|
|
527
|
+
}
|
|
365
528
|
export class CommandRouter {
|
|
366
529
|
sessionStore;
|
|
367
530
|
/** 飞书桥接实例(public:测试直接访问断言,替代 as unknown as)。 */
|
|
@@ -560,105 +723,31 @@ export class CommandRouter {
|
|
|
560
723
|
if (APPROVAL_ACTION_CMDS.includes(value.cmd)) {
|
|
561
724
|
return this.handleApprovalAction(value, ctx);
|
|
562
725
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
await
|
|
569
|
-
return;
|
|
570
|
-
case 'ls.browse':
|
|
571
|
-
await this.handleLsBrowse(value, ctx);
|
|
572
|
-
return;
|
|
573
|
-
case 'ls.switch':
|
|
574
|
-
await this.handleLsSwitch(value, ctx);
|
|
726
|
+
const spec = CARD_ACTION_SPECS.get(value.cmd);
|
|
727
|
+
if (spec) {
|
|
728
|
+
// Map 查表取出的是未绑定方法,必须显式 .call(this, ...) 保留类实例上下文。
|
|
729
|
+
const handler = this[spec.method];
|
|
730
|
+
if (spec.args === 'path') {
|
|
731
|
+
await handler.call(this, value.path, ctx);
|
|
575
732
|
return;
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
return this.handleLsFilter(value, ctx);
|
|
580
|
-
case 'ws.page':
|
|
581
|
-
return this.handleWsPage(value, ctx);
|
|
582
|
-
case 'ws.filter':
|
|
583
|
-
return this.handleWsFilter(value, ctx);
|
|
584
|
-
case 'ws.use':
|
|
585
|
-
return await this.handleWsUse(value, ctx);
|
|
586
|
-
case 'ws.sort':
|
|
587
|
-
return await this.handleWsSort(value, ctx);
|
|
588
|
-
case 'ws.remove':
|
|
589
|
-
return await this.handleWsRemove(value, ctx);
|
|
590
|
-
case 'resume.use':
|
|
591
|
-
await this.handleResumeUse(value, ctx);
|
|
592
|
-
return;
|
|
593
|
-
case 'resume.page':
|
|
594
|
-
return this.handleResumePage(value, ctx);
|
|
595
|
-
case 'active.page':
|
|
596
|
-
return this.handleActivePage(value, ctx);
|
|
597
|
-
case 'new-session':
|
|
598
|
-
await this.handleNewSession(ctx);
|
|
599
|
-
return;
|
|
600
|
-
case 'stop':
|
|
601
|
-
await this.handleCardStop(value, ctx);
|
|
602
|
-
return;
|
|
603
|
-
case 'job.kill':
|
|
604
|
-
await this.handleJobKill(value, ctx);
|
|
605
|
-
return;
|
|
606
|
-
case 'queue.cancel':
|
|
607
|
-
// 撤销:从队列中删除这个消息
|
|
608
|
-
await this.handleQueueCancel(value, ctx);
|
|
609
|
-
return;
|
|
610
|
-
case 'queue.immediate':
|
|
611
|
-
// 立即执行:停止当前进程,删除队列中这个消息之前的全部消息,立即执行这一条
|
|
612
|
-
await this.handleQueueImmediate(value, ctx);
|
|
613
|
-
return;
|
|
614
|
-
case 'queue.diagnose':
|
|
615
|
-
// 诊断:切换到项目目录,启动 Claude -p 定位问题
|
|
616
|
-
await this.handleQueueDiagnose(value, ctx);
|
|
617
|
-
return;
|
|
618
|
-
case 'queue.edit':
|
|
619
|
-
// 编辑:显示输入框让用户修改消息内容
|
|
620
|
-
await this.handleQueueEdit(value, ctx);
|
|
621
|
-
return;
|
|
622
|
-
case 'queue.input':
|
|
623
|
-
// 输入:提交新消息内容。返回 toast(SDK 作为飞书回调响应给点击用户即时反馈)。
|
|
624
|
-
return await this.handleQueueInput(value, ctx);
|
|
625
|
-
case 'order.delete':
|
|
626
|
-
return await this.handleOrderDelete(value, ctx);
|
|
627
|
-
case 'order.page':
|
|
628
|
-
return await this.handleOrderPage(value, ctx);
|
|
629
|
-
case 'order.aliasEdit':
|
|
630
|
-
return await this.handleOrderAliasEdit(value, ctx);
|
|
631
|
-
case 'order.aliasInput':
|
|
632
|
-
return await this.handleOrderAliasInput(value, ctx);
|
|
633
|
-
case 'order.aliasRemove':
|
|
634
|
-
return await this.handleOrderAliasRemove(value, ctx);
|
|
635
|
-
case 'order.textEdit':
|
|
636
|
-
return await this.handleOrderTextEdit(value, ctx);
|
|
637
|
-
case 'order.textInput':
|
|
638
|
-
return await this.handleOrderTextInput(value, ctx);
|
|
639
|
-
case 'config.set':
|
|
640
|
-
case 'config.input':
|
|
641
|
-
case 'config.save':
|
|
642
|
-
// 串行化所有 config.* 动作。CardKit 2.0 回调经 enqueueImmediate
|
|
643
|
-
// 不进串行队列,多次快速点击下拉/文本框会并发读写 pendingConfig 并同时
|
|
644
|
-
// 调 updateCardInPlace → Feishu API 乱序到达导致控件卡死。
|
|
645
|
-
// 返回 enqueueConfigAction 的结果以支持 toast 响应。
|
|
646
|
-
return this.enqueueConfigAction(value, ctx);
|
|
647
|
-
case 'compact':
|
|
648
|
-
await this.handleCompact(value, ctx);
|
|
649
|
-
return;
|
|
650
|
-
case 'resume.compact':
|
|
651
|
-
await this.handleResumeCompact(value, ctx);
|
|
733
|
+
}
|
|
734
|
+
if (spec.args === 'ctxOnly') {
|
|
735
|
+
await handler.call(this, ctx);
|
|
652
736
|
return;
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
await this.handleUnknownCardAction(value, ctx);
|
|
737
|
+
}
|
|
738
|
+
// voidCall 对应原 switch 的 `await this.xxx(...); return;`,丢弃返回值。
|
|
739
|
+
if (spec.args === 'voidCall') {
|
|
740
|
+
await handler.call(this, value, ctx);
|
|
659
741
|
return;
|
|
660
742
|
}
|
|
743
|
+
return handler.call(this, value, ctx);
|
|
661
744
|
}
|
|
745
|
+
if (value.cmd.startsWith('help.')) {
|
|
746
|
+
await this.handleHelpAction(value, ctx);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
await this.handleUnknownCardAction(value, ctx);
|
|
750
|
+
return;
|
|
662
751
|
}
|
|
663
752
|
/**
|
|
664
753
|
* resume.use: 从 /resume 列表卡切换到所选会话。
|
|
@@ -2022,8 +2111,7 @@ export class CommandRouter {
|
|
|
2022
2111
|
// 检测是否有任何 agent 配置变更或 defaultAgent 切换
|
|
2023
2112
|
const hasDefaultAgentChange = oldDefaultAgent !== this.config.defaultAgent;
|
|
2024
2113
|
const changedKeys = Object.keys(updates);
|
|
2025
|
-
const hasAgentConfigChange = changedKeys
|
|
2026
|
-
AGENT_KINDS.some((kind) => touchesAgentConfig(changedKeys, kind));
|
|
2114
|
+
const hasAgentConfigChange = computeHasAgentConfigChange(changedKeys);
|
|
2027
2115
|
// 当 defaultAgent 改变时,清除旧 agent 的 sessionId,
|
|
2028
2116
|
// 同时清除新 agent 的 sessionId(等效于 /new 命令),
|
|
2029
2117
|
// 并支持 session 恢复 - 切换回来时如果条件满足则恢复之前的 session。
|
|
@@ -2031,46 +2119,10 @@ export class CommandRouter {
|
|
|
2031
2119
|
let switchNewAgent;
|
|
2032
2120
|
let switchSessionId;
|
|
2033
2121
|
if (hasDefaultAgentChange) {
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
const oldArrivalSessionId = this.sessionStore.getArrivalSessionId(ctx.userId, oldDefaultAgent);
|
|
2039
|
-
const userChangedOld = (oldSessionId ?? '') !== (oldArrivalSessionId ?? '');
|
|
2040
|
-
// Step 1: 保存旧 agent 的 session 到 previousSessions(如果有)
|
|
2041
|
-
if (oldSessionId) {
|
|
2042
|
-
this.sessionStore.setPreviousSessionId(ctx.userId, oldDefaultAgent, oldSessionId);
|
|
2043
|
-
}
|
|
2044
|
-
// Step 2: 清除旧 agent 的 session(保存后清除,为新会话腾出空间)
|
|
2045
|
-
this.sessionStore.clearSessionId(ctx.userId, oldDefaultAgent);
|
|
2046
|
-
// Step 2.5: 显式选择判定
|
|
2047
|
-
const newSessionId = this.sessionStore.getSessionId(ctx.userId, newAgent);
|
|
2048
|
-
const newArrivalSessionId = this.sessionStore.getArrivalSessionId(ctx.userId, newAgent);
|
|
2049
|
-
const userSelectedNew = !!newSessionId && (newSessionId ?? '') !== (newArrivalSessionId ?? '');
|
|
2050
|
-
// Step 3: 恢复判定
|
|
2051
|
-
const previousSessionId = this.sessionStore.getPreviousSessionId(ctx.userId, newAgent);
|
|
2052
|
-
const canRestore = !!previousSessionId && !userChangedOld;
|
|
2053
|
-
if (userSelectedNew) {
|
|
2054
|
-
this.sessionStore.setArrivalSessionId(ctx.userId, newAgent, newSessionId);
|
|
2055
|
-
const newAgentName = agentDisplayName(newAgent);
|
|
2056
|
-
switchNotice = `已切换到 ${newAgentName},已使用所选 session,sessionId: ${newSessionId}`;
|
|
2057
|
-
switchSessionId = newSessionId;
|
|
2058
|
-
}
|
|
2059
|
-
else if (canRestore) {
|
|
2060
|
-
this.sessionStore.setSessionId(ctx.userId, newAgent, previousSessionId);
|
|
2061
|
-
this.sessionStore.clearPreviousSessionId(ctx.userId, newAgent);
|
|
2062
|
-
this.sessionStore.setArrivalSessionId(ctx.userId, newAgent, previousSessionId);
|
|
2063
|
-
const newAgentName = agentDisplayName(newAgent);
|
|
2064
|
-
switchNotice = `已切换到 ${newAgentName},将继续之前的 session,sessionId: ${previousSessionId}`;
|
|
2065
|
-
switchSessionId = previousSessionId;
|
|
2066
|
-
}
|
|
2067
|
-
else {
|
|
2068
|
-
this.sessionStore.clearSessionId(ctx.userId, newAgent, { clearSessionCwd: true });
|
|
2069
|
-
this.sessionStore.setArrivalSessionId(ctx.userId, newAgent, '');
|
|
2070
|
-
const newAgentName = agentDisplayName(newAgent);
|
|
2071
|
-
switchNotice = `已切换到 ${newAgentName},session 已清空,下次消息将启动新对话`;
|
|
2072
|
-
switchSessionId = '';
|
|
2073
|
-
}
|
|
2122
|
+
const outcome = applyDefaultAgentSwitch(this.sessionStore, ctx.userId, oldDefaultAgent, this.config.defaultAgent);
|
|
2123
|
+
switchNotice = outcome.notice;
|
|
2124
|
+
switchNewAgent = outcome.newAgent;
|
|
2125
|
+
switchSessionId = outcome.sessionId;
|
|
2074
2126
|
}
|
|
2075
2127
|
// DSH 的两个「会话边界」变更共用一套处理:preset 在 session 创建时固定,
|
|
2076
2128
|
// 中途切换会被服务端拒绝(agent-preset-conflict);host 变更等于换服务,
|
|
@@ -2078,21 +2130,8 @@ export class CommandRouter {
|
|
|
2078
2130
|
// previousSessions 恢复槽(可 /resume 校验后恢复)并清空当前值,下次 run
|
|
2079
2131
|
// 走 session.create 新建。已发生 agent 切换时(switchNotice 已设置)跳过,
|
|
2080
2132
|
// 避免覆盖切换 notice——切换分支对 dsh 的 session 已做恢复/清空处理。
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
return;
|
|
2084
|
-
const oldSessionId = this.sessionStore.getSessionId(ctx.userId, 'dsh');
|
|
2085
|
-
if (oldSessionId) {
|
|
2086
|
-
this.sessionStore.setPreviousSessionId(ctx.userId, 'dsh', oldSessionId);
|
|
2087
|
-
this.sessionStore.clearSessionId(ctx.userId, 'dsh');
|
|
2088
|
-
switchNotice = `DSH ${label} 已变更,旧会话已停放到恢复槽(可 /resume 校验后恢复),下次消息将新建会话`;
|
|
2089
|
-
}
|
|
2090
|
-
else {
|
|
2091
|
-
switchNotice = `DSH ${label} 已变更,下次消息将新建会话`;
|
|
2092
|
-
}
|
|
2093
|
-
};
|
|
2094
|
-
parkDshSessionBoundary(changedKeys.includes('agents.dsh.agentPreset'), 'preset');
|
|
2095
|
-
parkDshSessionBoundary(changedKeys.includes('agents.dsh.host'), 'host');
|
|
2133
|
+
switchNotice = parkDshSessionBoundaryNotice(this.sessionStore, ctx.userId, this.config.defaultAgent, changedKeys.includes('agents.dsh.agentPreset'), 'preset', switchNotice);
|
|
2134
|
+
switchNotice = parkDshSessionBoundaryNotice(this.sessionStore, ctx.userId, this.config.defaultAgent, changedKeys.includes('agents.dsh.host'), 'host', switchNotice);
|
|
2096
2135
|
// 同步 agent 配置到 agentChoices(用于切换 agent 时恢复配置)
|
|
2097
2136
|
const currentAgent = this.config.defaultAgent;
|
|
2098
2137
|
if (touchesAgentsSubtree(changedKeys, currentAgent)) {
|
|
@@ -2107,8 +2146,7 @@ export class CommandRouter {
|
|
|
2107
2146
|
// duck method `updateApprovalMode` (codex thread/settings/update,
|
|
2108
2147
|
// kimi/opencode session/set_mode) so they take effect for subsequent
|
|
2109
2148
|
// turns without waiting for the runner to be recreated.
|
|
2110
|
-
const hasApprovalConfigChange =
|
|
2111
|
-
changedKeys.includes('agents.dsh.permissionPreset');
|
|
2149
|
+
const hasApprovalConfigChange = computeHasApprovalConfigChange(changedKeys);
|
|
2112
2150
|
if (hasApprovalConfigChange) {
|
|
2113
2151
|
this.bridge.syncActiveApprovalModes();
|
|
2114
2152
|
}
|
|
@@ -2132,54 +2170,7 @@ export class CommandRouter {
|
|
|
2132
2170
|
'如果发得太快,也可能被飞书合并成一条消息',
|
|
2133
2171
|
}, ctx);
|
|
2134
2172
|
}
|
|
2135
|
-
|
|
2136
|
-
case 'help':
|
|
2137
|
-
case 'h':
|
|
2138
|
-
return this.cmdHelp();
|
|
2139
|
-
case 'status':
|
|
2140
|
-
case 's':
|
|
2141
|
-
return this.cmdStatus(ctx);
|
|
2142
|
-
case 'ps':
|
|
2143
|
-
return this.cmdPs(ctx);
|
|
2144
|
-
case 'stop':
|
|
2145
|
-
case 't':
|
|
2146
|
-
return await this.cmdStop(ctx);
|
|
2147
|
-
case 'exit':
|
|
2148
|
-
case 'quit':
|
|
2149
|
-
case 'e':
|
|
2150
|
-
return await this.cmdExit();
|
|
2151
|
-
case 'restart':
|
|
2152
|
-
return this.cmdRestart();
|
|
2153
|
-
case 'new':
|
|
2154
|
-
return this.cmdNew(ctx);
|
|
2155
|
-
case 'cd':
|
|
2156
|
-
return this.cmdCd(args, ctx);
|
|
2157
|
-
case 'ls':
|
|
2158
|
-
return this.cmdLs(args, ctx);
|
|
2159
|
-
case 'download':
|
|
2160
|
-
case 'd':
|
|
2161
|
-
return await this.cmdDownload(args, ctx);
|
|
2162
|
-
case 'ws':
|
|
2163
|
-
return this.cmdWs(args, ctx);
|
|
2164
|
-
case 'resume':
|
|
2165
|
-
case 'r':
|
|
2166
|
-
return this.cmdResume(args, ctx);
|
|
2167
|
-
case 'active':
|
|
2168
|
-
return this.cmdActive(args, ctx);
|
|
2169
|
-
case 'config':
|
|
2170
|
-
case 'c':
|
|
2171
|
-
return await this.cmdConfig(args, ctx);
|
|
2172
|
-
case 'order':
|
|
2173
|
-
case 'o':
|
|
2174
|
-
return this.cmdOrder(args, ctx);
|
|
2175
|
-
case 'update':
|
|
2176
|
-
case 'u':
|
|
2177
|
-
return await this.cmdUpdate(args, ctx);
|
|
2178
|
-
case 'clone':
|
|
2179
|
-
return await this.cmdClone(args, ctx);
|
|
2180
|
-
default:
|
|
2181
|
-
return { text: `未知命令 /${cmd},输入 /help 查看可用命令` };
|
|
2182
|
-
}
|
|
2173
|
+
return dispatchCommand(this, cmd, args, ctx);
|
|
2183
2174
|
}
|
|
2184
2175
|
// --- Command implementations ---
|
|
2185
2176
|
cmdHelp() {
|
|
@@ -2,7 +2,7 @@ import { getLogger } from '../../logger/index.js';
|
|
|
2
2
|
import { createShellBackend, ShellUnavailableError, } from '../../platform/shell.js';
|
|
3
3
|
import { createTerminator } from '../../platform/terminator.js';
|
|
4
4
|
import { useDetachedProcessGroup } from '../../platform/spawn.js';
|
|
5
|
-
import { registerExitCleanup, unregisterExitCleanup } from '../common/spawning-runner.js';
|
|
5
|
+
import { isChildProcessRunning, registerExitCleanup, unregisterExitCleanup, } from '../common/spawning-runner.js';
|
|
6
6
|
/**
|
|
7
7
|
* Lightweight bash executor for `!` commands.
|
|
8
8
|
* Spawns `bash -c "<command>"` and yields stdout/stderr/exit as events.
|
|
@@ -38,9 +38,7 @@ export class BashProcessRunner {
|
|
|
38
38
|
this.shell = opts?.shell ?? createShellBackend();
|
|
39
39
|
}
|
|
40
40
|
get isRunning() {
|
|
41
|
-
return (this.currentProcess
|
|
42
|
-
this.currentProcess.exitCode === null &&
|
|
43
|
-
this.currentProcess.signalCode === null);
|
|
41
|
+
return isChildProcessRunning(this.currentProcess);
|
|
44
42
|
}
|
|
45
43
|
async *run(command, opts) {
|
|
46
44
|
if (this.isRunning) {
|
|
@@ -119,4 +119,6 @@ export declare class CodexAppServerTranslator {
|
|
|
119
119
|
private reasoningSnapshotFor;
|
|
120
120
|
/** Receive-time stamp, aligned with the exec JSONL parse path. */
|
|
121
121
|
private now;
|
|
122
|
+
/** 把实例状态打包成模块级 handler 可用的显式快照(.bind 保留 this 绑定)。 */
|
|
123
|
+
private translatorState;
|
|
122
124
|
}
|