lark-remote 0.1.4 → 0.1.6

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.en.md +0 -1
  3. package/README.md +0 -1
  4. package/dist/bridge/approval-coordinator.d.ts +108 -0
  5. package/dist/bridge/approval-coordinator.js +258 -0
  6. package/dist/bridge/index.d.ts +62 -0
  7. package/dist/bridge/index.js +423 -20
  8. package/dist/bridge/queue-manager.d.ts +9 -0
  9. package/dist/bridge/queue-manager.js +19 -11
  10. package/dist/card/approval-render.d.ts +16 -0
  11. package/dist/card/approval-render.js +247 -0
  12. package/dist/card/card-shared.d.ts +14 -0
  13. package/dist/card/card-shared.js +28 -0
  14. package/dist/card/run-renderer.js +78 -7
  15. package/dist/card/run-state.d.ts +25 -3
  16. package/dist/card/run-state.js +210 -2
  17. package/dist/config/dir.d.ts +1 -0
  18. package/dist/config/dir.js +4 -0
  19. package/dist/config/index.d.ts +43 -0
  20. package/dist/config/index.js +25 -1
  21. package/dist/connector/index.d.ts +5 -0
  22. package/dist/connector/index.js +32 -0
  23. package/dist/index.js +95 -23
  24. package/dist/router/config/codex.d.ts +1 -1
  25. package/dist/router/config/codex.js +49 -0
  26. package/dist/router/config/common/render.js +4 -0
  27. package/dist/router/config/index.d.ts +0 -10
  28. package/dist/router/config/index.js +0 -17
  29. package/dist/router/config/types.d.ts +1 -1
  30. package/dist/router/index.d.ts +61 -1
  31. package/dist/router/index.js +323 -48
  32. package/dist/router/utils.d.ts +2 -0
  33. package/dist/router/utils.js +5 -2
  34. package/dist/runner/codex/app-server/client.d.ts +82 -0
  35. package/dist/runner/codex/app-server/client.js +234 -0
  36. package/dist/runner/codex/app-server/connection-manager.d.ts +56 -0
  37. package/dist/runner/codex/app-server/connection-manager.js +170 -0
  38. package/dist/runner/codex/app-server/protocol-types.d.ts +527 -0
  39. package/dist/runner/codex/app-server/protocol-types.js +67 -0
  40. package/dist/runner/codex/app-server/runner.d.ts +121 -0
  41. package/dist/runner/codex/app-server/runner.js +554 -0
  42. package/dist/runner/codex/app-server/translator.d.ts +109 -0
  43. package/dist/runner/codex/app-server/translator.js +734 -0
  44. package/dist/runner/codex/app-server/transport.d.ts +49 -0
  45. package/dist/runner/codex/app-server/transport.js +231 -0
  46. package/dist/runner/codex/argv.d.ts +1 -1
  47. package/dist/runner/codex/argv.js +1 -1
  48. package/dist/runner/codex/factory.d.ts +29 -0
  49. package/dist/runner/codex/factory.js +39 -0
  50. package/dist/runner/codex/index.d.ts +2 -0
  51. package/dist/runner/codex/index.js +2 -0
  52. package/dist/runner/codex/runner.d.ts +3 -0
  53. package/dist/runner/codex/runner.js +5 -0
  54. package/dist/runner/common/runner-utils.d.ts +12 -0
  55. package/dist/runner/common/runner-utils.js +21 -0
  56. package/dist/runner/common/spawning-runner.js +9 -1
  57. package/dist/runner/common/usage.d.ts +2 -0
  58. package/dist/runner/common/usage.js +2 -0
  59. package/dist/runner/index.d.ts +2 -2
  60. package/dist/runner/index.js +1 -1
  61. package/dist/runner/types.d.ts +123 -2
  62. package/dist/session/claude/session-index.d.ts +93 -0
  63. package/dist/session/claude/session-index.js +374 -0
  64. package/dist/session/claude/sessions.d.ts +0 -18
  65. package/dist/session/claude/sessions.js +102 -319
  66. package/dist/session/codex/rollout-reader.js +46 -3
  67. package/dist/update/index.d.ts +10 -0
  68. package/dist/update/index.js +11 -0
  69. package/dist/update/install.d.ts +29 -0
  70. package/dist/update/install.js +93 -0
  71. package/dist/update/startup-hint.d.ts +5 -0
  72. package/dist/update/startup-hint.js +11 -0
  73. package/dist/update/version-check.d.ts +31 -0
  74. package/dist/update/version-check.js +149 -0
  75. package/package.json +1 -14
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { resolveAgentChoices, } from './runner/index.js';
3
3
  import { ensureConfig } from './config/wizard.js';
4
4
  import { parseCliArgs, resolveConfigDir, setConfigDir, printHelp, printVersion, } from './config/dir.js';
5
5
  import { FeishuConnector } from './connector/index.js';
6
- import { ClaudeRunner, CodexExecRunner, OpencodeExecRunner, PiRunner, KimiRunner, } from './runner/index.js';
6
+ import { ClaudeRunner, createCodexRunner, OpencodeExecRunner, PiRunner, KimiRunner, } from './runner/index.js';
7
7
  import { AgentRegistry } from './runner/registry.js';
8
8
  import { probeAllAgents } from './runner/probe.js';
9
9
  import { SessionReaderRegistry, SessionStore } from './session/index.js';
@@ -16,15 +16,16 @@ import { StartupContactStore, sendStartupHello } from './startup-contact.js';
16
16
  import { OwnerBinder, formatPinGuidance } from './binder.js';
17
17
  import { InstanceAlreadyRunningError, InstanceLock } from './instance-lock.js';
18
18
  import { spawnReplacementBridge, waitForPreviousInstance } from './restart.js';
19
+ import { checkLatestVersion, isNewer, runInstallLatest, formatUpdateHint } from './update/index.js';
19
20
  import { classifyRejection } from './error-classification.js';
20
21
  import { WorkspaceStore } from './workspace/index.js';
21
22
  import { markdownDiv } from './card/collapsible.js';
22
23
  import { sessionEventPanel } from './router/card-helpers.js';
23
- import { newSessionButton, agentDisplayName } from './card/card-shared.js';
24
+ import { newSessionButton, resumeCompactButton, agentDisplayName } from './card/card-shared.js';
24
25
  import path from 'node:path';
25
26
  import fs from 'node:fs';
26
27
  /** Send auto-resume card for a restored session */
27
- async function sendAutoResumeCard(connector, contact, cwd, session, state) {
28
+ async function sendAutoResumeCard(connector, contact, cwd, session, state, compactSupported = false) {
28
29
  if (!contact.userId)
29
30
  return;
30
31
  const recipient = contact.chatId ?? contact.userId;
@@ -63,17 +64,21 @@ async function sendAutoResumeCard(connector, contact, cwd, session, state) {
63
64
  const usageStr = formatUsageStats(state.usage);
64
65
  elements.push(markdownDiv(usageStr));
65
66
  }
66
- // Add new session button - use column_set+column (div+elements not supported in 2.0)
67
- const newSessionBtn = newSessionButton();
67
+ // Add action buttons - use column_set+column (div+elements not supported in 2.0)
68
+ const actionButtons = [];
69
+ // Compact 按钮:仅 codex app-server 模式(runner 有 runCompact)且未在跑。
70
+ // 启动自动恢复时不会存在该会话的 activeRun,直接渲染。
71
+ if (compactSupported) {
72
+ actionButtons.push(resumeCompactButton(session.sessionId, state.agentKind ?? 'codex'));
73
+ }
74
+ actionButtons.push(newSessionButton());
68
75
  elements.push({
69
76
  tag: 'column_set',
70
- columns: [
71
- {
72
- tag: 'column',
73
- width: 'auto',
74
- elements: [newSessionBtn],
75
- },
76
- ],
77
+ columns: actionButtons.map((btn) => ({
78
+ tag: 'column',
79
+ width: 'auto',
80
+ elements: [btn],
81
+ })),
77
82
  });
78
83
  const card = {
79
84
  schema: '2.0',
@@ -162,15 +167,17 @@ function initializeRunner(config, configDir, cliArgs) {
162
167
  workspace: ws,
163
168
  });
164
169
  });
165
- // Register CodexExecRunner (codex exec --json, approval_policy=never).
166
- // Factory reads latest config from container.current so runtime config changes
167
- // (reasoningEffort, model, etc.) take effect after bridge.setConfig() + clearRunners().
170
+ // Register Codex runner. Factory reads latest config from container.current
171
+ // so runtime config changes (serviceMode, model, sandbox, approvalPolicy,
172
+ // reasoningEffort, etc.) take effect after bridge.setConfig() + clearRunners().
173
+ // serviceMode=app-server → CodexAppServerRunner (persistent connection with
174
+ // approval + sandbox); otherwise CodexExecRunner (codex exec --json).
168
175
  const codexSessionReader = new CodexSessionReader({ codexHome: process.env.CODEX_HOME });
169
176
  agentRegistry.register('codex', (ws) => {
170
177
  const container = agentRegistry.getConfigContainer();
171
178
  const latestConfig = container?.current ?? config;
172
179
  const codexConfig = getAgentConfig(latestConfig, 'codex');
173
- return new CodexExecRunner({
180
+ return createCodexRunner({
174
181
  model: codexConfig?.model,
175
182
  modelProvider: codexConfig?.modelProvider,
176
183
  reasoningEffort: codexConfig?.reasoningEffort,
@@ -178,6 +185,13 @@ function initializeRunner(config, configDir, cliArgs) {
178
185
  pidDir: configDir,
179
186
  workspace: ws,
180
187
  sessionReader: codexSessionReader,
188
+ serviceMode: codexConfig?.serviceMode,
189
+ sandbox: codexConfig?.sandbox,
190
+ approvalPolicy: codexConfig?.approvalPolicy,
191
+ appServerBinary: codexConfig?.appServer?.binary,
192
+ appServerRequestTimeoutMs: codexConfig?.appServer?.requestTimeoutMs,
193
+ appServerIdleTtlMs: codexConfig?.appServer?.idleTtlMs,
194
+ appServerTurnIdleTimeoutMinutes: codexConfig?.appServer?.turnIdleTimeoutMinutes,
181
195
  });
182
196
  });
183
197
  // Register OpencodeExecRunner (run mode)
@@ -444,7 +458,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
444
458
  if (actionValue.cmd === 'stop') {
445
459
  if (!actionValue.runId) {
446
460
  logger.warn('[control] ignored stop card action without runId');
447
- // Card button clicks must have visible feedback (AGENTS.md 红线:卡片按钮
461
+ // Card button clicks must have visible feedback (design constraint: card button
448
462
  // 点击必须有可见反馈,见 design.md §6.2)。
449
463
  void bridge
450
464
  .sendResult({ text: '⚠️ 无效的停止请求,缺少必要信息' }, { userId, chatId, messageId })
@@ -478,11 +492,15 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
478
492
  inputValue: action.raw?.action?.input_value ??
479
493
  action.action.input_value,
480
494
  };
481
- // queue.input 和 config.save 返回 CardActionResponse toast 给点击用户即时反馈。
482
- // 必须直接返回值 -- enqueueImmediate / enqueueConfigAction 是 fire-and-forget,
483
- // 会吞掉返回值。queue.input 只做一次卡片更新 + toast,快速且不需要串行队列序列化。
484
- // config.save 内部已有 enqueueConfigAction 串行化保证,但返回值需要直接返回给飞书。
485
- if (actionValue.cmd === 'queue.input' || actionValue.cmd === 'config.save') {
495
+ // queue.input / config.save / approval.respond / approval.toggle 返回
496
+ // CardActionResponse toast 给点击用户即时反馈。必须直接返回值 --
497
+ // enqueueImmediate / enqueueConfigAction 是 fire-and-forget,会吞掉返回值。
498
+ // 审批响应尤其不能落串行队列:run 任务占用队列头直到 turn 结束,审批响应
499
+ // 排在后面会形成死锁(run 不结束不执行、run 结束 coordinator 已删响应空转)。
500
+ if (actionValue.cmd === 'queue.input' ||
501
+ actionValue.cmd === 'config.save' ||
502
+ actionValue.cmd === 'approval.respond' ||
503
+ actionValue.cmd === 'approval.toggle') {
486
504
  return router.handleCardAction(fullValue, { userId, chatId, messageId });
487
505
  }
488
506
  // order.exec → equivalent queued message (Plan A): resolve the order text
@@ -513,6 +531,8 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
513
531
  chatId,
514
532
  messageId,
515
533
  messagePreview: `card action: ${actionValue.cmd}`,
534
+ // Compact 是单向操作,排队卡不允许编辑(编辑预览无意义)。
535
+ editable: actionValue.cmd !== 'codex.compact' && actionValue.cmd !== 'resume.compact',
516
536
  },
517
537
  });
518
538
  }
@@ -531,6 +551,32 @@ async function main() {
531
551
  printVersion();
532
552
  process.exit(0);
533
553
  }
554
+ // --update: upgrade to latest version and exit (for cron/script automation)
555
+ if (cliArgs.update) {
556
+ // --update 在 acquireLock 之前执行;先解析 configDir 并初始化 logger,
557
+ // 让版本检查的日志落到正确目录(默认 ~/.lark-remote 或 --config-dir 指定)。
558
+ const updateConfigDir = resolveConfigDir(cliArgs.configDir);
559
+ initLogger({ dir: path.join(updateConfigDir, 'logs') });
560
+ try {
561
+ const { current, latest } = await checkLatestVersion();
562
+ if (!isNewer(current, latest)) {
563
+ console.log(`Already up to date: ${current}`);
564
+ process.exit(0);
565
+ }
566
+ console.log(`Updating ${current} → ${latest} ...`);
567
+ const result = await runInstallLatest();
568
+ if (!result.success) {
569
+ console.error(`Update failed: ${result.error}`);
570
+ process.exit(1);
571
+ }
572
+ console.log(`✅ Updated to ${latest}. Restart lark-remote to use the new version.`);
573
+ process.exit(0);
574
+ }
575
+ catch (err) {
576
+ console.error(`Update failed: ${err.message}`);
577
+ process.exit(1);
578
+ }
579
+ }
534
580
  const { config, configDir, logger, instanceLock } = await initializeCliAndConfig();
535
581
  // Restart handoff: if spawned as a /restart replacement, wait for the old
536
582
  // bridge to die (and release the instance lock) before acquiring it.
@@ -574,6 +620,8 @@ async function main() {
574
620
  ordersPath: path.join(configDir, 'orders.json'),
575
621
  restartSpawner: () => spawnReplacementBridge(path.join(configDir, 'logs')),
576
622
  sessionReaderRegistry,
623
+ devMode: cliArgs.dev,
624
+ updateCachePath: path.join(configDir, 'update-cache.json'),
577
625
  });
578
626
  setupMessageHandlers(connector, router, bridge, sessionStore, workspaceStore, binder, logger, config);
579
627
  try {
@@ -586,6 +634,27 @@ async function main() {
586
634
  if (binder.isBound()) {
587
635
  // 仅已绑定时发送启动通知;未绑定时不打扰(PIN 引导已在控制台输出)
588
636
  await sendStartupHello(connector, startupContactStore, { dev: cliArgs.dev });
637
+ // 启动时静默检查版本(由 checkUpdateOnStartup 控制,默认关闭)
638
+ if (config.checkUpdateOnStartup) {
639
+ void (async () => {
640
+ try {
641
+ const cachePath = path.join(configDir, 'update-cache.json');
642
+ const { current, latest } = await checkLatestVersion({ cachePath });
643
+ const hint = formatUpdateHint(current, latest);
644
+ if (hint) {
645
+ const contact = startupContactStore.getContact();
646
+ const recipient = contact?.chatId ?? contact?.userId;
647
+ if (recipient) {
648
+ await connector.sendWithRetry(recipient, { text: hint });
649
+ }
650
+ }
651
+ }
652
+ catch (err) {
653
+ // Non-fatal: startup check failure must not crash the bridge
654
+ logger.warn(`[startup] update check failed: ${err.message}`);
655
+ }
656
+ })();
657
+ }
589
658
  }
590
659
  // Auto-restore: resume the persisted sessionId if available
591
660
  const restoredContact = startupContactStore.getContact();
@@ -613,7 +682,10 @@ async function main() {
613
682
  recap: content.recap,
614
683
  displayTitle: content.displayTitle,
615
684
  agentKind: config.defaultAgent,
616
- });
685
+ },
686
+ // Compact 仅 codex app-server 模式有实现(runner.runCompact)。
687
+ config.defaultAgent === 'codex' &&
688
+ getAgentConfig(config, 'codex')?.serviceMode === 'app-server');
617
689
  }
618
690
  catch (err) {
619
691
  getLogger().warn('[startup] sendAutoResumeCard failed:', err);
@@ -2,7 +2,7 @@
2
2
  * Codex config builder — builds fields and handles field changes for Codex agent.
3
3
  */
4
4
  import type { AgentConfigCardBuilder, ConfigField } from './types.js';
5
- import type { AppConfig } from '../../config/index.js';
5
+ import { type AppConfig } from '../../config/index.js';
6
6
  export declare class CodexConfigBuilder implements AgentConfigCardBuilder {
7
7
  buildFields(displayConfig: AppConfig): ConfigField[];
8
8
  handleFieldChange(key: string, value: unknown, config: AppConfig): Array<{
@@ -2,6 +2,7 @@
2
2
  * Codex config builder — builds fields and handles field changes for Codex agent.
3
3
  */
4
4
  import { loadCodexConfig, getReasoningEffortOptions, getDefaultReasoningEffort, } from '../../config/codex-config.js';
5
+ import { DEFAULT_TURN_IDLE_TIMEOUT_MINUTES } from '../../config/index.js';
5
6
  export class CodexConfigBuilder {
6
7
  buildFields(displayConfig) {
7
8
  const fields = [];
@@ -43,6 +44,54 @@ export class CodexConfigBuilder {
43
44
  options: getReasoningEffortOptions(currentCodexModel),
44
45
  currentValue: currentReasoningEffort,
45
46
  });
47
+ // Service mode (exec vs app-server)
48
+ const currentServiceMode = displayConfig.agents?.codex?.serviceMode ?? 'exec';
49
+ fields.push({
50
+ key: 'agents.codex.serviceMode',
51
+ label: '运行模式',
52
+ type: 'select',
53
+ options: ['exec', 'app-server'],
54
+ currentValue: currentServiceMode,
55
+ });
56
+ // 审批策略与沙箱模式仅 app-server 模式可配置(CodexAppServerRunner 读取);
57
+ // exec 模式在 runner 层固定 `--sandbox danger-full-access` +
58
+ // `approval_policy="never"`(src/runner/codex/argv.ts),卡片不暴露选项,
59
+ // 只用说明行把默认语义呈现给用户。
60
+ if (currentServiceMode === 'app-server') {
61
+ // Approval policy (Codex 官方 AskForApproval 标准值;on-request 为 codex 默认)
62
+ const currentApprovalPolicy = displayConfig.agents?.codex?.approvalPolicy ?? 'on-request';
63
+ fields.push({
64
+ key: 'agents.codex.approvalPolicy',
65
+ label: '审批策略',
66
+ type: 'select',
67
+ options: ['untrusted', 'on-request', 'never'],
68
+ currentValue: currentApprovalPolicy,
69
+ });
70
+ // Sandbox mode (Codex 官方 SandboxMode 标准值)
71
+ const currentSandbox = displayConfig.agents?.codex?.sandbox ?? 'danger-full-access';
72
+ fields.push({
73
+ key: 'agents.codex.sandbox',
74
+ label: '沙箱模式',
75
+ type: 'select',
76
+ options: ['read-only', 'workspace-write', 'danger-full-access'],
77
+ currentValue: currentSandbox,
78
+ });
79
+ const currentTurnIdleTimeoutMinutes = displayConfig.agents?.codex?.appServer?.turnIdleTimeoutMinutes ??
80
+ DEFAULT_TURN_IDLE_TIMEOUT_MINUTES;
81
+ fields.push({
82
+ key: 'agents.codex.appServer.turnIdleTimeoutMinutes',
83
+ label: 'Turn 空闲超时(分钟, 0关闭)',
84
+ type: 'input',
85
+ currentValue: String(currentTurnIdleTimeoutMinutes),
86
+ });
87
+ }
88
+ else {
89
+ fields.push({
90
+ key: 'agents.codex.modeNote',
91
+ label: '命令行模式固定为完全访问(danger-full-access)且无需审批(approval never),不提供审批策略/沙箱配置',
92
+ type: 'note',
93
+ });
94
+ }
46
95
  return fields;
47
96
  }
48
97
  handleFieldChange(key, value, config) {
@@ -19,6 +19,10 @@ function buildFieldElements(fields, displayConfig) {
19
19
  else if (field.type === 'select' && field.options) {
20
20
  elements.push(buildSelectElement(field, currentValue));
21
21
  }
22
+ else if (field.type === 'note') {
23
+ // 纯说明行:无 config.* 回调,只渲染 label 为 markdown 文本。
24
+ elements.push({ tag: 'div', text: { tag: 'lark_md', content: field.label } });
25
+ }
22
26
  else {
23
27
  elements.push(buildInputElement(field, currentValue));
24
28
  }
@@ -16,13 +16,3 @@ import type { AgentConfigCardBuilder } from './types.js';
16
16
  export declare function getConfigBuilder(agent: AgentKind): AgentConfigCardBuilder;
17
17
  /** List all registered agent kinds. */
18
18
  export declare function listRegisteredAgents(): AgentKind[];
19
- /**
20
- * Sort agent kinds for UI lists: agents that are definitively unavailable
21
- * (availability === false) sink to the bottom; everything else keeps its
22
- * input order. The sort is stable, so relative order within both groups is
23
- * preserved (e.g. registration order in the config card selector).
24
- *
25
- * `undefined` (probe cache missing/expired) is treated as "keep in place",
26
- * so a stale cache cannot misplace installed agents.
27
- */
28
- export declare function sortAgentsByAvailability(kinds: AgentKind[], isAvailable: (kind: AgentKind) => boolean | undefined): AgentKind[];
@@ -35,21 +35,4 @@ export function getConfigBuilder(agent) {
35
35
  export function listRegisteredAgents() {
36
36
  return Object.keys(builders);
37
37
  }
38
- /**
39
- * Sort agent kinds for UI lists: agents that are definitively unavailable
40
- * (availability === false) sink to the bottom; everything else keeps its
41
- * input order. The sort is stable, so relative order within both groups is
42
- * preserved (e.g. registration order in the config card selector).
43
- *
44
- * `undefined` (probe cache missing/expired) is treated as "keep in place",
45
- * so a stale cache cannot misplace installed agents.
46
- */
47
- export function sortAgentsByAvailability(kinds, isAvailable) {
48
- const unavailable = new Set(kinds.filter((kind) => isAvailable(kind) === false));
49
- return [...kinds].sort((a, b) => {
50
- const rankA = unavailable.has(a) ? 1 : 0;
51
- const rankB = unavailable.has(b) ? 1 : 0;
52
- return rankA - rankB;
53
- });
54
- }
55
38
  //# sourceMappingURL=index.js.map
@@ -9,7 +9,7 @@ import type { AppConfig } from '../../config/index.js';
9
9
  export interface ConfigField {
10
10
  key: string;
11
11
  label: string;
12
- type: 'boolean' | 'select' | 'input';
12
+ type: 'boolean' | 'select' | 'input' | 'note';
13
13
  /** For select type: the list of options shown in the dropdown. */
14
14
  options?: readonly string[];
15
15
  /** Override the currentValue shown in the card (takes priority over getConfigValue). */
@@ -2,6 +2,7 @@ import type { SessionStore, SessionReaderRegistry } from '../session/index.js';
2
2
  import type { AppConfig } from '../config/index.js';
3
3
  import type { Bridge } from '../bridge/index.js';
4
4
  import type { AgentBinding } from '../bridge/queue-manager.js';
5
+ import { type VersionCheckResult, type InstallResult } from '../update/index.js';
5
6
  interface CommandContext {
6
7
  userId: string;
7
8
  chatId: string;
@@ -59,6 +60,18 @@ export interface CardActionPayload {
59
60
  /** CardKit 2.0 input 组件自带提交图标触发回调时回传的输入值 */
60
61
  inputValue?: string;
61
62
  orderId?: string;
63
+ /** Approval request ID for approval card actions. */
64
+ requestId?: number | string;
65
+ /** Decision for approval.respond (e.g. 'accept', 'decline', 'cancel'). */
66
+ decision?: string;
67
+ /** Scope for acceptForSession: 'turn' | 'session'. */
68
+ scope?: 'turn' | 'session';
69
+ /** Unique nonce to prevent duplicate processing. */
70
+ nonce?: string;
71
+ /** Permission item ID for approval.toggle. */
72
+ permId?: string;
73
+ /** Desired selection state for approval.toggle (card renders !current). */
74
+ selected?: boolean;
62
75
  }
63
76
  /**
64
77
  * Response a cardAction handler may return to give the clicking user native
@@ -91,6 +104,12 @@ export declare class CommandRouter {
91
104
  */
92
105
  private restartSpawner?;
93
106
  private idleTimeoutMs;
107
+ /** Dev mode flag: --dev means the bridge was started from source (bun src/index.ts). */
108
+ private devMode;
109
+ /** Cache file for version checks (<configDir>/update-cache.json). */
110
+ private updateCachePath?;
111
+ /** Injected update functions (for testability; defaults to real implementation). */
112
+ private updateFns;
94
113
  private pendingConfig;
95
114
  /**
96
115
  * Monotonic counter minting unique internal keys for order.exec enqueue
@@ -135,6 +154,19 @@ export declare class CommandRouter {
135
154
  idleTimeoutMs?: number;
136
155
  /** Session reader registry (required). */
137
156
  sessionReaderRegistry: SessionReaderRegistry;
157
+ /** Dev mode: bridge was started from source (--dev flag). */
158
+ devMode?: boolean;
159
+ /** Version check cache path (<configDir>/update-cache.json). */
160
+ updateCachePath?: string;
161
+ /** Override update functions for testability. */
162
+ updateFns?: {
163
+ checkLatestVersion?: (opts?: {
164
+ cachePath?: string;
165
+ bypassCache?: boolean;
166
+ }) => Promise<VersionCheckResult>;
167
+ isNewer?: (current: string, latest: string) => boolean | null;
168
+ runInstallLatest?: () => Promise<InstallResult>;
169
+ };
138
170
  });
139
171
  /**
140
172
  * Resolve the active session reader for the current defaultAgent.
@@ -153,6 +185,12 @@ export declare class CommandRouter {
153
185
  * Validates payload and dispatches to the corresponding command.
154
186
  */
155
187
  handleCardAction(value: CardActionPayload, ctx: CommandContext): Promise<CardActionResponse | void>;
188
+ /**
189
+ * Handle approval-related card actions.
190
+ * Routes to the appropriate bridge method based on value.cmd.
191
+ * Returns a toast response for immediate user feedback.
192
+ */
193
+ private handleApprovalAction;
156
194
  /**
157
195
  * Handle queue.cancel: remove this message from the queue.
158
196
  */
@@ -307,7 +345,7 @@ export declare class CommandRouter {
307
345
  *
308
346
  * @param updates 以 config 路径形式给出的变更 key(卡片路径来自 diffConfig;
309
347
  * 直写路径传 mapAgentKey(key),保证 pi./codex./opencode./kimi. 命中 agents.*)。
310
- * @returns defaultAgent 切换时的通知文案(未切换返回 undefined)
348
+ * @returns 结构化切换结果(含 notice 文案 + 新 agent/sessionId 信息)
311
349
  */
312
350
  private propagateConfigSave;
313
351
  private executeCommand;
@@ -317,6 +355,13 @@ export declare class CommandRouter {
317
355
  private cmdStop;
318
356
  private cmdExit;
319
357
  private cmdRestart;
358
+ /**
359
+ * /update: check for newer version and upgrade if available.
360
+ * /update check: only check, don't upgrade.
361
+ *
362
+ * Flow: check version → if newer, install → report success (no auto-restart).
363
+ */
364
+ private cmdUpdate;
320
365
  private cmdNew;
321
366
  private cmdCd;
322
367
  private readSessionDisplayState;
@@ -330,6 +375,21 @@ export declare class CommandRouter {
330
375
  private switchCwdAndNotify;
331
376
  /** Build card for auto-resumed session with new session button */
332
377
  private buildAutoResumeCard;
378
+ /**
379
+ * Build card for config.save agent switch.
380
+ * When the new agent has a restored session, renders a Resume card (mirrors
381
+ * buildAutoResumeCard) with session history + stop/new buttons.
382
+ * When the session was cleared, renders a compact notice card instead.
383
+ */
384
+ private buildConfigSwitchCard;
385
+ /**
386
+ * Whether a session of the given agent kind can be compacted from a resume
387
+ * card. Compact (thread/compact/start) is only implemented by the codex
388
+ * app-server runner (`runCompact`), so the button is gated on
389
+ * `serviceMode === 'app-server'` — exec mode spawns a fresh CLI per message
390
+ * and has no persistent session to compact.
391
+ */
392
+ private canCompactSession;
333
393
  private static readonly LS_PAGE_SIZE;
334
394
  private cmdLs;
335
395
  private cmdWs;