lark-remote 0.1.6 → 0.1.7

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 (188) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.en.md +9 -1
  3. package/README.md +14 -2
  4. package/dist/bridge/approval-coordinator.d.ts +57 -34
  5. package/dist/bridge/approval-coordinator.js +191 -40
  6. package/dist/bridge/inbound-media.d.ts +62 -0
  7. package/dist/bridge/inbound-media.js +282 -0
  8. package/dist/bridge/index.d.ts +78 -7
  9. package/dist/bridge/index.js +202 -79
  10. package/dist/bridge/queue-manager.d.ts +2 -1
  11. package/dist/bridge/queue-manager.js +1 -0
  12. package/dist/card/approval-render.js +152 -36
  13. package/dist/card/bash-card-session.d.ts +2 -24
  14. package/dist/card/bash-card-session.js +6 -73
  15. package/dist/card/bash-renderer.js +2 -3
  16. package/dist/card/card-budget.js +29 -75
  17. package/dist/card/card-session.d.ts +48 -0
  18. package/dist/card/card-session.js +86 -0
  19. package/dist/card/card-shared.d.ts +1 -12
  20. package/dist/card/card-shared.js +1 -26
  21. package/dist/card/run-card-session.d.ts +2 -55
  22. package/dist/card/run-card-session.js +6 -104
  23. package/dist/card/run-renderer.js +99 -232
  24. package/dist/card/run-state.d.ts +13 -0
  25. package/dist/card/run-state.js +25 -16
  26. package/dist/card/text-truncate.d.ts +13 -12
  27. package/dist/card/text-truncate.js +13 -63
  28. package/dist/common/fs.d.ts +8 -0
  29. package/dist/common/fs.js +17 -0
  30. package/dist/common/guards.d.ts +0 -20
  31. package/dist/common/guards.js +0 -43
  32. package/dist/common/sleep.d.ts +2 -0
  33. package/dist/common/sleep.js +5 -0
  34. package/dist/common/truncate.d.ts +39 -0
  35. package/dist/common/truncate.js +92 -0
  36. package/dist/config/codex-config.d.ts +1 -1
  37. package/dist/config/codex-config.js +1 -1
  38. package/dist/config/index.d.ts +88 -23
  39. package/dist/config/index.js +105 -34
  40. package/dist/connector/index.d.ts +69 -2
  41. package/dist/connector/index.js +107 -1
  42. package/dist/index.js +123 -105
  43. package/dist/instance-lock.js +4 -12
  44. package/dist/order/alias-resolve.d.ts +13 -0
  45. package/dist/order/alias-resolve.js +22 -0
  46. package/dist/order/alias-store.d.ts +38 -0
  47. package/dist/order/alias-store.js +106 -0
  48. package/dist/persistence/atomic-write.d.ts +8 -2
  49. package/dist/persistence/atomic-write.js +23 -7
  50. package/dist/restart.js +2 -1
  51. package/dist/router/card-helpers.d.ts +63 -0
  52. package/dist/router/card-helpers.js +146 -0
  53. package/dist/router/config/claude.js +25 -1
  54. package/dist/router/config/codex.js +29 -50
  55. package/dist/router/config/common/model-patch.d.ts +22 -0
  56. package/dist/router/config/common/model-patch.js +26 -0
  57. package/dist/router/config/common/render.js +5 -4
  58. package/dist/router/config/index.d.ts +12 -0
  59. package/dist/router/config/index.js +25 -1
  60. package/dist/router/config/kimi.d.ts +4 -1
  61. package/dist/router/config/kimi.js +27 -0
  62. package/dist/router/config/opencode.js +18 -5
  63. package/dist/router/config/pi.js +4 -5
  64. package/dist/router/config/types.d.ts +11 -2
  65. package/dist/router/index.d.ts +56 -21
  66. package/dist/router/index.js +373 -548
  67. package/dist/router/utils.d.ts +2 -0
  68. package/dist/router/utils.js +4 -0
  69. package/dist/runner/agent-choices-common.d.ts +16 -0
  70. package/dist/runner/agent-choices-common.js +31 -0
  71. package/dist/runner/claude/runner.d.ts +47 -10
  72. package/dist/runner/claude/runner.js +117 -46
  73. package/dist/runner/claude/session.d.ts +166 -0
  74. package/dist/runner/claude/session.js +749 -0
  75. package/dist/runner/codex/app-server/protocol-types.d.ts +31 -39
  76. package/dist/runner/codex/app-server/protocol-types.js +1 -12
  77. package/dist/runner/codex/app-server/runner.d.ts +42 -59
  78. package/dist/runner/codex/app-server/runner.js +160 -337
  79. package/dist/runner/codex/app-server/translator.d.ts +1 -21
  80. package/dist/runner/codex/app-server/translator.js +0 -18
  81. package/dist/runner/codex/index.d.ts +0 -2
  82. package/dist/runner/codex/index.js +0 -2
  83. package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
  84. package/dist/runner/common/acp/protocol-helpers.js +21 -0
  85. package/dist/runner/common/acp/protocol-types.d.ts +238 -0
  86. package/dist/runner/common/acp/protocol-types.js +75 -0
  87. package/dist/runner/common/connection-based-runner.d.ts +111 -0
  88. package/dist/runner/common/connection-based-runner.js +261 -0
  89. package/dist/runner/{codex/app-server → common/jsonrpc}/client.d.ts +14 -7
  90. package/dist/runner/{codex/app-server → common/jsonrpc}/client.js +27 -18
  91. package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
  92. package/dist/runner/{codex/app-server → common/jsonrpc}/connection-manager.js +63 -28
  93. package/dist/runner/{codex/app-server → common/jsonrpc}/transport.d.ts +5 -4
  94. package/dist/runner/{codex/app-server → common/jsonrpc}/transport.js +18 -19
  95. package/dist/runner/common/jsonrpc/types.d.ts +32 -0
  96. package/dist/runner/common/jsonrpc/types.js +9 -0
  97. package/dist/runner/common/process-stopper.d.ts +2 -1
  98. package/dist/runner/common/process-stopper.js +1 -0
  99. package/dist/runner/common/runner-utils.d.ts +6 -13
  100. package/dist/runner/common/runner-utils.js +6 -47
  101. package/dist/runner/common/spawning-runner.d.ts +24 -70
  102. package/dist/runner/common/spawning-runner.js +98 -152
  103. package/dist/runner/index.d.ts +4 -4
  104. package/dist/runner/index.js +4 -4
  105. package/dist/runner/kimi/acp/runner.d.ts +106 -0
  106. package/dist/runner/kimi/acp/runner.js +448 -0
  107. package/dist/runner/kimi/acp/translator.d.ts +124 -0
  108. package/dist/runner/kimi/acp/translator.js +336 -0
  109. package/dist/runner/kimi/index.d.ts +1 -1
  110. package/dist/runner/kimi/index.js +1 -1
  111. package/dist/runner/opencode/acp/index.d.ts +1 -0
  112. package/dist/runner/opencode/acp/index.js +2 -0
  113. package/dist/runner/opencode/acp/runner.d.ts +121 -0
  114. package/dist/runner/opencode/acp/runner.js +429 -0
  115. package/dist/runner/opencode/acp/translator.d.ts +119 -0
  116. package/dist/runner/opencode/acp/translator.js +345 -0
  117. package/dist/runner/opencode/index.d.ts +1 -1
  118. package/dist/runner/opencode/index.js +1 -1
  119. package/dist/runner/pi/index.d.ts +1 -1
  120. package/dist/runner/pi/index.js +1 -1
  121. package/dist/runner/pi/rpc/client.d.ts +52 -0
  122. package/dist/runner/pi/rpc/client.js +114 -0
  123. package/dist/runner/pi/rpc/index.d.ts +4 -0
  124. package/dist/runner/pi/rpc/index.js +4 -0
  125. package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
  126. package/dist/runner/pi/rpc/protocol-types.js +21 -0
  127. package/dist/runner/pi/rpc/runner.d.ts +80 -0
  128. package/dist/runner/pi/rpc/runner.js +199 -0
  129. package/dist/runner/pi/rpc/translator.d.ts +55 -0
  130. package/dist/runner/pi/rpc/translator.js +258 -0
  131. package/dist/runner/registry.d.ts +7 -8
  132. package/dist/runner/registry.js +3 -4
  133. package/dist/runner/resolve-agent-choices.js +18 -59
  134. package/dist/runner/sync-agent-choices.js +16 -67
  135. package/dist/runner/types.d.ts +43 -13
  136. package/dist/session/claude/session-index.js +2 -3
  137. package/dist/session/claude/session-reader.js +3 -6
  138. package/dist/session/claude/sessions.js +6 -13
  139. package/dist/session/codex/rollout-reader.d.ts +2 -2
  140. package/dist/session/codex/rollout-reader.js +14 -17
  141. package/dist/session/codex/sessions.d.ts +1 -1
  142. package/dist/session/codex/sessions.js +2 -2
  143. package/dist/session/common/content-blocks.d.ts +7 -0
  144. package/dist/session/common/content-blocks.js +13 -9
  145. package/dist/session/common/jsonl.d.ts +8 -25
  146. package/dist/session/common/jsonl.js +126 -261
  147. package/dist/session/common/pagination.d.ts +28 -0
  148. package/dist/session/common/pagination.js +33 -0
  149. package/dist/session/kimi/sessions.d.ts +34 -1
  150. package/dist/session/kimi/sessions.js +208 -98
  151. package/dist/session/opencode/sessions.d.ts +17 -3
  152. package/dist/session/opencode/sessions.js +29 -29
  153. package/dist/session/pi/sessions.d.ts +5 -1
  154. package/dist/session/pi/sessions.js +12 -16
  155. package/dist/session/session-store.d.ts +11 -1
  156. package/dist/session/session-store.js +75 -178
  157. package/dist/update/index.d.ts +2 -2
  158. package/dist/update/index.js +2 -2
  159. package/dist/update/install.d.ts +20 -2
  160. package/dist/update/install.js +51 -7
  161. package/package.json +1 -1
  162. package/dist/runner/codex/app-server/connection-manager.d.ts +0 -56
  163. package/dist/runner/codex/argv.d.ts +0 -33
  164. package/dist/runner/codex/argv.js +0 -40
  165. package/dist/runner/codex/factory.d.ts +0 -29
  166. package/dist/runner/codex/factory.js +0 -39
  167. package/dist/runner/codex/jsonl.d.ts +0 -37
  168. package/dist/runner/codex/jsonl.js +0 -221
  169. package/dist/runner/codex/runner.d.ts +0 -66
  170. package/dist/runner/codex/runner.js +0 -129
  171. package/dist/runner/common/exec-translator.d.ts +0 -73
  172. package/dist/runner/common/exec-translator.js +0 -105
  173. package/dist/runner/kimi/jsonl.d.ts +0 -28
  174. package/dist/runner/kimi/jsonl.js +0 -143
  175. package/dist/runner/kimi/runner.d.ts +0 -49
  176. package/dist/runner/kimi/runner.js +0 -105
  177. package/dist/runner/opencode/argv.d.ts +0 -32
  178. package/dist/runner/opencode/argv.js +0 -35
  179. package/dist/runner/opencode/jsonl.d.ts +0 -68
  180. package/dist/runner/opencode/jsonl.js +0 -295
  181. package/dist/runner/opencode/runner.d.ts +0 -61
  182. package/dist/runner/opencode/runner.js +0 -93
  183. package/dist/runner/pi/jsonl.d.ts +0 -128
  184. package/dist/runner/pi/jsonl.js +0 -249
  185. package/dist/runner/pi/runner.d.ts +0 -36
  186. package/dist/runner/pi/runner.js +0 -76
  187. package/dist/session/common/catch-up.d.ts +0 -12
  188. package/dist/session/common/catch-up.js +0 -31
@@ -1,13 +1,15 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { silentlyUnlink } from '../common/fs.js';
3
4
  /**
4
- * Atomically write string content to a file via tmp + rename.
5
+ * Atomically write content (string or binary Buffer) to a file via tmp + rename.
5
6
  * Falls back to copy+unlink when rename fails with EXDEV (cross-device).
6
7
  */
7
8
  export function atomicWrite(filePath, content, encoding = 'utf-8') {
8
9
  const dir = path.dirname(filePath);
9
10
  fs.mkdirSync(dir, { recursive: true });
10
11
  const tmpPath = filePath + '.tmp';
12
+ // Buffer 内容忽略 encoding(writeFileSync 对 Buffer 自动以二进制写出)。
11
13
  fs.writeFileSync(tmpPath, content, encoding);
12
14
  // fsync the tmp file before rename so its data is durable on disk;
13
15
  // rename itself is only a metadata change, so without fsync a crash after
@@ -47,12 +49,7 @@ export function atomicWrite(filePath, content, encoding = 'utf-8') {
47
49
  // Best-effort cleanup: on success the tmp was renamed away (ENOENT here,
48
50
  // silently ignored); on EXDEV it was copied and now needs removal; on other
49
51
  // errors we must not leak the tmp file.
50
- try {
51
- fs.unlinkSync(tmpPath);
52
- }
53
- catch {
54
- /* already moved or never created */
55
- }
52
+ silentlyUnlink(tmpPath);
56
53
  }
57
54
  }
58
55
  /**
@@ -62,4 +59,23 @@ export function atomicWrite(filePath, content, encoding = 'utf-8') {
62
59
  export function atomicWriteJson(filePath, data) {
63
60
  atomicWrite(filePath, JSON.stringify(data, null, 2));
64
61
  }
62
+ /**
63
+ * 把已写好的文件原子移动到目标位置(rename 同设备即原子;EXDEV 跨设备
64
+ * 回退 copy + unlink)。失败时抛出,由调用方负责清理源文件(best-effort)。
65
+ * 用于入站媒体:下载已流式写入临时文件,落盘只需一次 move,不再复制进内存。
66
+ */
67
+ export function atomicMoveFile(srcPath, destPath) {
68
+ try {
69
+ fs.renameSync(srcPath, destPath);
70
+ }
71
+ catch (err) {
72
+ if (err.code === 'EXDEV') {
73
+ fs.copyFileSync(srcPath, destPath);
74
+ silentlyUnlink(srcPath);
75
+ }
76
+ else {
77
+ throw err;
78
+ }
79
+ }
80
+ }
65
81
  //# sourceMappingURL=atomic-write.js.map
package/dist/restart.js CHANGED
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { getLogger } from './logger/index.js';
5
+ import { sleep } from './common/sleep.js';
5
6
  /**
6
7
  * Self-restart without an external watchdog/cron: the only process that can
7
8
  * reliably start the successor is the dying bridge itself. The old process
@@ -66,7 +67,7 @@ export async function waitForPreviousInstance() {
66
67
  getLogger().info(`[restart] previous instance pid=${pid} exited, continuing startup`);
67
68
  return;
68
69
  }
69
- await new Promise((resolve) => setTimeout(resolve, POLL_MS));
70
+ await sleep(POLL_MS);
70
71
  }
71
72
  getLogger().warn(`[restart] previous instance pid=${pid} still alive after ${WAIT_TIMEOUT_MS}ms, proceeding anyway`);
72
73
  }
@@ -8,6 +8,8 @@
8
8
  */
9
9
  import { markdownDiv } from '../card/collapsible.js';
10
10
  import type { AgentSessionContentEvent } from '../runner/index.js';
11
+ import { formatUsageStats } from './utils.js';
12
+ type SessionUsageLike = Parameters<typeof formatUsageStats>[0];
11
13
  /**
12
14
  * Build a collapsible panel for a single session content event.
13
15
  *
@@ -26,3 +28,64 @@ import type { AgentSessionContentEvent } from '../runner/index.js';
26
28
  */
27
29
  export declare function sessionEventPanel(ev: AgentSessionContentEvent, index: number, totalEvents: number, tailExpandedCount?: number, agentKind?: string): object;
28
30
  export { markdownDiv };
31
+ /**
32
+ * Build a CardKit 2.0 pagination bar (prev-button / page-label / next-button
33
+ * columns). Shared by /ls, /ws, /resume, /active, /order.
34
+ *
35
+ * Returns the `column_set` element (plus caller appends an `hr`). Each caller
36
+ * supplies its own callback cmd + extra value fields and label text (semantic
37
+ * differences are preserved, not silently unified).
38
+ *
39
+ * @param opts.cmd The pagination callback command (e.g. 'ls.page').
40
+ * @param opts.offset Current page offset.
41
+ * @param opts.pageSize Items per page.
42
+ * @param opts.total Total item count.
43
+ * @param opts.extra Extra callback value fields (e.g. path, agent, pageSize).
44
+ * @param opts.label Page-label markdown text (e.g. `**第 1/3 页**(共 30 项)`).
45
+ * @param opts.prevText / opts.nextText Button labels (default '⬅ 上一页' / '下一页 ➡').
46
+ */
47
+ export declare function paginationBar(opts: {
48
+ cmd: string;
49
+ offset: number;
50
+ pageSize: number;
51
+ total: number;
52
+ extra?: Record<string, unknown>;
53
+ label: string;
54
+ prevText?: string;
55
+ nextText?: string;
56
+ }): object;
57
+ /**
58
+ * Build a session-history resume card (shared by auto-restore, /resume <id>,
59
+ * config-switch resume, and run-completion notification).
60
+ *
61
+ * Encapsulates the common structure: header (cwd + sessionId + displayTitle/
62
+ * recap sections) → optional hidden-count indicator → folded event panels (or
63
+ * empty placeholder) → usage → trailing-hr removal → action column_set → card
64
+ * shell. Callers pass their specific header text, card title, usage result
65
+ * label, placeholders, and already-built action buttons via opts.
66
+ */
67
+ export declare function buildSessionHistoryCard(state: {
68
+ sessionId: string;
69
+ cwd: string;
70
+ displayTitle?: string;
71
+ aiTitle?: string;
72
+ recap?: string;
73
+ events: AgentSessionContentEvent[];
74
+ usage?: SessionUsageLike;
75
+ }, opts: {
76
+ agentKind: string;
77
+ /** Header first line, e.g. `📂 \`${cwd}\`\n已恢复最近会话: **id**`. */
78
+ headerText: string;
79
+ /** Card header title content. */
80
+ title: string;
81
+ /** Optional result label passed to formatUsageStats (showResult:true). */
82
+ usageResult?: string;
83
+ /** Empty-events placeholder (some callers show a hint instead of nothing). */
84
+ emptyPlaceholder?: string;
85
+ /** Hidden earlier-events count (auto-restore only). */
86
+ hiddenCount?: number;
87
+ /** Action buttons to render (already built, protocol-specific). */
88
+ actions?: object[];
89
+ /** Optional card header template (e.g. 'green' for completion cards). */
90
+ headerTemplate?: string;
91
+ }): object;
@@ -9,6 +9,7 @@
9
9
  import { agentDisplayName } from '../card/card-shared.js';
10
10
  import { collapsibleMarkdownPanel, markdownDiv } from '../card/collapsible.js';
11
11
  import { formatTimestamp } from '../card/time.js';
12
+ import { formatUsageStats } from './utils.js';
12
13
  /**
13
14
  * Build a collapsible panel for a single session content event.
14
15
  *
@@ -62,4 +63,149 @@ function eventLabel(ev, agentKind) {
62
63
  }
63
64
  }
64
65
  export { markdownDiv };
66
+ /**
67
+ * Build a CardKit 2.0 pagination bar (prev-button / page-label / next-button
68
+ * columns). Shared by /ls, /ws, /resume, /active, /order.
69
+ *
70
+ * Returns the `column_set` element (plus caller appends an `hr`). Each caller
71
+ * supplies its own callback cmd + extra value fields and label text (semantic
72
+ * differences are preserved, not silently unified).
73
+ *
74
+ * @param opts.cmd The pagination callback command (e.g. 'ls.page').
75
+ * @param opts.offset Current page offset.
76
+ * @param opts.pageSize Items per page.
77
+ * @param opts.total Total item count.
78
+ * @param opts.extra Extra callback value fields (e.g. path, agent, pageSize).
79
+ * @param opts.label Page-label markdown text (e.g. `**第 1/3 页**(共 30 项)`).
80
+ * @param opts.prevText / opts.nextText Button labels (default '⬅ 上一页' / '下一页 ➡').
81
+ */
82
+ export function paginationBar(opts) {
83
+ const hasPrev = opts.offset > 0;
84
+ const hasNext = opts.offset + opts.pageSize < opts.total;
85
+ const prevText = opts.prevText ?? '⬅ 上一页';
86
+ const nextText = opts.nextText ?? '下一页 ➡';
87
+ const pageColumns = [];
88
+ if (hasPrev) {
89
+ pageColumns.push({
90
+ tag: 'column',
91
+ width: 'auto',
92
+ vertical_align: 'center',
93
+ elements: [
94
+ {
95
+ tag: 'button',
96
+ text: { tag: 'plain_text', content: prevText },
97
+ type: 'default',
98
+ size: 'small',
99
+ behaviors: [
100
+ {
101
+ type: 'callback',
102
+ value: { cmd: opts.cmd, offset: opts.offset - opts.pageSize, ...opts.extra },
103
+ },
104
+ ],
105
+ },
106
+ ],
107
+ });
108
+ }
109
+ pageColumns.push({
110
+ tag: 'column',
111
+ width: 'weighted',
112
+ weight: 1,
113
+ vertical_align: 'center',
114
+ elements: [{ tag: 'div', text: { tag: 'lark_md', content: opts.label } }],
115
+ });
116
+ if (hasNext) {
117
+ pageColumns.push({
118
+ tag: 'column',
119
+ width: 'auto',
120
+ vertical_align: 'center',
121
+ elements: [
122
+ {
123
+ tag: 'button',
124
+ text: { tag: 'plain_text', content: nextText },
125
+ type: 'default',
126
+ size: 'small',
127
+ behaviors: [
128
+ {
129
+ type: 'callback',
130
+ value: { cmd: opts.cmd, offset: opts.offset + opts.pageSize, ...opts.extra },
131
+ },
132
+ ],
133
+ },
134
+ ],
135
+ });
136
+ }
137
+ return { tag: 'column_set', columns: pageColumns };
138
+ }
139
+ /**
140
+ * Build a session-history resume card (shared by auto-restore, /resume <id>,
141
+ * config-switch resume, and run-completion notification).
142
+ *
143
+ * Encapsulates the common structure: header (cwd + sessionId + displayTitle/
144
+ * recap sections) → optional hidden-count indicator → folded event panels (or
145
+ * empty placeholder) → usage → trailing-hr removal → action column_set → card
146
+ * shell. Callers pass their specific header text, card title, usage result
147
+ * label, placeholders, and already-built action buttons via opts.
148
+ */
149
+ export function buildSessionHistoryCard(state, opts) {
150
+ const { displayTitle, aiTitle, recap, events, usage } = state;
151
+ let header = opts.headerText;
152
+ const sections = [];
153
+ // Reader 层不再截断 displayTitle/summary;统一在消费侧这里截到 200 字符。
154
+ if (displayTitle) {
155
+ const label = aiTitle ? 'AI 标题' : '最近输入';
156
+ const preview = displayTitle.length > 200 ? displayTitle.slice(0, 197) + '...' : displayTitle;
157
+ sections.push(`🏷️ **${label}**\n${preview}`);
158
+ }
159
+ if (recap) {
160
+ const recapPreview = recap.length > 200 ? recap.slice(0, 197) + '...' : recap;
161
+ sections.push(`📝 **Recap**\n${recapPreview}`);
162
+ }
163
+ if (sections.length > 0) {
164
+ header += '\n\n' + sections.join('\n\n──\n\n');
165
+ }
166
+ const elements = [markdownDiv(header), { tag: 'hr' }];
167
+ if (opts.hiddenCount && opts.hiddenCount > 0) {
168
+ elements.push(markdownDiv(`📜 还有 ${opts.hiddenCount} 个更早的事件未显示`));
169
+ }
170
+ if (events.length === 0 && opts.emptyPlaceholder) {
171
+ elements.push(markdownDiv(opts.emptyPlaceholder));
172
+ }
173
+ else {
174
+ events.forEach((ev, i) => {
175
+ elements.push(sessionEventPanel(ev, i, events.length, 2, opts.agentKind));
176
+ });
177
+ }
178
+ if (usage) {
179
+ const usageStr = opts.usageResult
180
+ ? formatUsageStats(usage, { showResult: true, result: opts.usageResult })
181
+ : formatUsageStats(usage);
182
+ elements.push(markdownDiv(usageStr));
183
+ }
184
+ // Remove trailing hr
185
+ if (elements.length > 0 && elements[elements.length - 1].tag === 'hr') {
186
+ elements.pop();
187
+ }
188
+ if (opts.actions && opts.actions.length > 0) {
189
+ elements.push({
190
+ tag: 'column_set',
191
+ columns: opts.actions.map((btn) => ({
192
+ tag: 'column',
193
+ width: 'auto',
194
+ elements: [btn],
195
+ })),
196
+ });
197
+ }
198
+ return {
199
+ schema: '2.0',
200
+ config: { wide_screen_mode: true },
201
+ header: {
202
+ ...(opts.headerTemplate ? { template: opts.headerTemplate } : {}),
203
+ title: {
204
+ tag: 'plain_text',
205
+ content: opts.title,
206
+ },
207
+ },
208
+ body: { elements },
209
+ };
210
+ }
65
211
  //# sourceMappingURL=card-helpers.js.map
@@ -4,7 +4,21 @@
4
4
  import fs from 'node:fs';
5
5
  import os from 'node:os';
6
6
  import path from 'node:path';
7
- import { MODEL_ID_TO_ALIAS, MODEL_ALIAS_TO_ID, getModelOptionsFromSettings, CLAUDE_EFFORTS, } from '../../config/index.js';
7
+ import { MODEL_ID_TO_ALIAS, MODEL_ALIAS_TO_ID, getModelOptionsFromSettings, CLAUDE_EFFORTS, CLAUDE_PERMISSION_MODES, } from '../../config/index.js';
8
+ /** Claude 官方 --permission-mode 中文注释(value 走 CLAUDE_PERMISSION_MODES SSOT)。 */
9
+ const PERMISSION_MODE_LABELS = {
10
+ default: '默认(敏感操作逐项询问)',
11
+ acceptEdits: '自动接受文件编辑',
12
+ auto: '自动放行低风险操作',
13
+ bypassPermissions: '跳过所有权限检查',
14
+ manual: '手动审批(default 的别名)',
15
+ dontAsk: '仅预先批准的工具',
16
+ plan: '计划模式(只读探索)',
17
+ };
18
+ const PERMISSION_MODE_OPTIONS = CLAUDE_PERMISSION_MODES.map((m) => ({
19
+ text: `${m}(${PERMISSION_MODE_LABELS[m] ?? ''})`,
20
+ value: m,
21
+ }));
8
22
  export class ClaudeConfigBuilder {
9
23
  /** Find Claude settings.json path (for dynamic model reading). */
10
24
  findSettingsPath() {
@@ -61,6 +75,16 @@ export class ClaudeConfigBuilder {
61
75
  options: CLAUDE_EFFORTS,
62
76
  currentValue: currentEffort,
63
77
  });
78
+ // 权限模式(Claude 官方 --permission-mode 枚举;bypassPermissions 为 schema 默认,
79
+ // 保持旧行为无审批卡,配置为其他值后激活交互式审批)
80
+ const currentPermissionMode = displayConfig.claude?.permissionMode ?? 'bypassPermissions';
81
+ fields.push({
82
+ key: 'claude.permissionMode',
83
+ label: '权限模式',
84
+ type: 'select',
85
+ options: PERMISSION_MODE_OPTIONS,
86
+ currentValue: currentPermissionMode,
87
+ });
64
88
  return fields;
65
89
  }
66
90
  handleFieldChange(key, _value, _config) {
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { loadCodexConfig, getReasoningEffortOptions, getDefaultReasoningEffort, } from '../../config/codex-config.js';
5
5
  import { DEFAULT_TURN_IDLE_TIMEOUT_MINUTES } from '../../config/index.js';
6
+ import { resetModelPatch } from './common/model-patch.js';
6
7
  export class CodexConfigBuilder {
7
8
  buildFields(displayConfig) {
8
9
  const fields = [];
@@ -44,54 +45,33 @@ export class CodexConfigBuilder {
44
45
  options: getReasoningEffortOptions(currentCodexModel),
45
46
  currentValue: currentReasoningEffort,
46
47
  });
47
- // Service mode (exec vs app-server)
48
- const currentServiceMode = displayConfig.agents?.codex?.serviceMode ?? 'exec';
48
+ // 审批策略与沙箱模式由 CodexAppServerRunner 读取(Codex 官方枚举标准值)。
49
+ // Approval policy (Codex 官方 AskForApproval 标准值;on-request 为 codex 默认)
50
+ const currentApprovalPolicy = displayConfig.agents?.codex?.approvalPolicy ?? 'on-request';
49
51
  fields.push({
50
- key: 'agents.codex.serviceMode',
51
- label: '运行模式',
52
+ key: 'agents.codex.approvalPolicy',
53
+ label: '审批策略',
52
54
  type: 'select',
53
- options: ['exec', 'app-server'],
54
- currentValue: currentServiceMode,
55
+ options: ['untrusted', 'on-request', 'never'],
56
+ currentValue: currentApprovalPolicy,
57
+ });
58
+ // Sandbox mode (Codex 官方 SandboxMode 标准值;默认 workspace-write)
59
+ const currentSandbox = displayConfig.agents?.codex?.sandbox ?? 'workspace-write';
60
+ fields.push({
61
+ key: 'agents.codex.sandbox',
62
+ label: '沙箱模式',
63
+ type: 'select',
64
+ options: ['read-only', 'workspace-write', 'danger-full-access'],
65
+ currentValue: currentSandbox,
66
+ });
67
+ const currentTurnIdleTimeoutMinutes = displayConfig.agents?.codex?.appServer?.turnIdleTimeoutMinutes ??
68
+ DEFAULT_TURN_IDLE_TIMEOUT_MINUTES;
69
+ fields.push({
70
+ key: 'agents.codex.appServer.turnIdleTimeoutMinutes',
71
+ label: 'Turn 空闲超时(分钟, 0关闭)',
72
+ type: 'input',
73
+ currentValue: String(currentTurnIdleTimeoutMinutes),
55
74
  });
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
- }
95
75
  return fields;
96
76
  }
97
77
  handleFieldChange(key, value, config) {
@@ -100,12 +80,11 @@ export class CodexConfigBuilder {
100
80
  // provider 变更时,重置 model 为新 provider 的首个模型
101
81
  if (key === 'agents.codex.modelProvider' && typeof value === 'string') {
102
82
  const codexCfg = loadCodexConfig();
103
- const newModelOptions = codexCfg.modelOptions(value);
104
83
  const currentModel = config.agents?.codex?.model;
105
- const currentModelIsValid = newModelOptions.some((m) => m === currentModel);
106
- if (!currentModelIsValid && newModelOptions.length > 0) {
107
- const replacedModel = newModelOptions[0];
108
- patches.push({ key: 'agents.codex.model', value: replacedModel });
84
+ const modelPatch = resetModelPatch(key, 'agents.codex.model', currentModel, codexCfg.modelOptions(value));
85
+ if (modelPatch) {
86
+ patches.push(modelPatch);
87
+ const replacedModel = modelPatch.value;
109
88
  // provider 切换自动替换模型后,必须对替换后的模型执行
110
89
  // 同样的档位校验——否则旧模型支持的档位会原样留在卡片/透传给不支持它的模型
111
90
  // (codex 只在会话中 with_model 时钳制,session 创建路径不钳制)。
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Shared field-change patch helpers for agent config cards.
3
+ *
4
+ * `resetModelPatch` captures the "provider changed → reset model to the new
5
+ * provider's first model" behavior that opencode/pi/codex config builders were
6
+ * duplicating.
7
+ */
8
+ /**
9
+ * Build a model-reset patch when the provider field changes: if the current
10
+ * model is not offered by the new provider (and the new provider has models),
11
+ * patch the model to the provider's first option.
12
+ *
13
+ * @param providerKey The field key that changed, e.g. 'agents.codex.modelProvider'.
14
+ * @param modelKey The model field to reset, e.g. 'agents.codex.model'.
15
+ * @param currentModel The currently-configured model value (may be undefined).
16
+ * @param newModelOptions Models offered by the newly-selected provider.
17
+ * @returns A `{ key, value }` patch, or null when no reset is needed.
18
+ */
19
+ export declare function resetModelPatch(providerKey: string, modelKey: string, currentModel: string | undefined, newModelOptions: string[]): {
20
+ key: string;
21
+ value: unknown;
22
+ } | null;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared field-change patch helpers for agent config cards.
3
+ *
4
+ * `resetModelPatch` captures the "provider changed → reset model to the new
5
+ * provider's first model" behavior that opencode/pi/codex config builders were
6
+ * duplicating.
7
+ */
8
+ /**
9
+ * Build a model-reset patch when the provider field changes: if the current
10
+ * model is not offered by the new provider (and the new provider has models),
11
+ * patch the model to the provider's first option.
12
+ *
13
+ * @param providerKey The field key that changed, e.g. 'agents.codex.modelProvider'.
14
+ * @param modelKey The model field to reset, e.g. 'agents.codex.model'.
15
+ * @param currentModel The currently-configured model value (may be undefined).
16
+ * @param newModelOptions Models offered by the newly-selected provider.
17
+ * @returns A `{ key, value }` patch, or null when no reset is needed.
18
+ */
19
+ export function resetModelPatch(providerKey, modelKey, currentModel, newModelOptions) {
20
+ const currentModelIsValid = newModelOptions.some((m) => m === currentModel);
21
+ if (!currentModelIsValid && newModelOptions.length > 0) {
22
+ return { key: modelKey, value: newModelOptions[0] };
23
+ }
24
+ return null;
25
+ }
26
+ //# sourceMappingURL=model-patch.js.map
@@ -86,10 +86,11 @@ function buildSelectElement(field, currentValue) {
86
86
  {
87
87
  tag: 'select_static',
88
88
  placeholder: { tag: 'plain_text', content: '请选择' },
89
- options: field.options.map((opt) => ({
90
- text: { tag: 'plain_text', content: opt },
91
- value: opt,
92
- })),
89
+ options: field.options.map((opt) => {
90
+ const text = typeof opt === 'string' ? opt : opt.text;
91
+ const value = typeof opt === 'string' ? opt : opt.value;
92
+ return { text: { tag: 'plain_text', content: text }, value };
93
+ }),
93
94
  initial_option: selectedValue,
94
95
  behaviors: [{ type: 'callback', value: { cmd: 'config.set', key: field.key } }],
95
96
  },
@@ -6,6 +6,12 @@
6
6
  */
7
7
  import type { AgentKind } from '../../runner/types.js';
8
8
  import type { AgentConfigCardBuilder } from './types.js';
9
+ /**
10
+ * 首次启动默认展示的 Coding Agent 顺序:Codex → Claude → OpenCode → Pi → Kimi。
11
+ * 未安装(availability === false)的 agent 在展示时排到后面
12
+ * (见 sortAgentsForDisplay),同组内保持本顺序。
13
+ */
14
+ export declare const DEFAULT_AGENT_ORDER: readonly AgentKind[];
9
15
  /**
10
16
  * Get the config builder for a specific agent.
11
17
  *
@@ -16,3 +22,9 @@ import type { AgentConfigCardBuilder } from './types.js';
16
22
  export declare function getConfigBuilder(agent: AgentKind): AgentConfigCardBuilder;
17
23
  /** List all registered agent kinds. */
18
24
  export declare function listRegisteredAgents(): AgentKind[];
25
+ /**
26
+ * 按展示规则排序 agent 列表:明确未安装(availability === false)的排后,
27
+ * 已安装/未知的在前;同组内保持传入顺序(canonical order)。
28
+ * Array.prototype.sort 是稳定排序,因此按可用性分组不会打乱组内顺序。
29
+ */
30
+ export declare function sortAgentsForDisplay(agents: readonly AgentKind[], availability: (kind: AgentKind) => boolean | undefined): AgentKind[];
@@ -9,6 +9,18 @@ import { CodexConfigBuilder } from './codex.js';
9
9
  import { PiConfigBuilder } from './pi.js';
10
10
  import { OpencodeConfigBuilder } from './opencode.js';
11
11
  import { KimiConfigBuilder } from './kimi.js';
12
+ /**
13
+ * 首次启动默认展示的 Coding Agent 顺序:Codex → Claude → OpenCode → Pi → Kimi。
14
+ * 未安装(availability === false)的 agent 在展示时排到后面
15
+ * (见 sortAgentsForDisplay),同组内保持本顺序。
16
+ */
17
+ export const DEFAULT_AGENT_ORDER = [
18
+ 'codex',
19
+ 'claude',
20
+ 'opencode',
21
+ 'pi',
22
+ 'kimi',
23
+ ];
12
24
  /** Registry of config builders, keyed by agent kind. */
13
25
  const builders = {
14
26
  claude: new ClaudeConfigBuilder(),
@@ -33,6 +45,18 @@ export function getConfigBuilder(agent) {
33
45
  }
34
46
  /** List all registered agent kinds. */
35
47
  export function listRegisteredAgents() {
36
- return Object.keys(builders);
48
+ return DEFAULT_AGENT_ORDER.filter((kind) => kind in builders);
49
+ }
50
+ /**
51
+ * 按展示规则排序 agent 列表:明确未安装(availability === false)的排后,
52
+ * 已安装/未知的在前;同组内保持传入顺序(canonical order)。
53
+ * Array.prototype.sort 是稳定排序,因此按可用性分组不会打乱组内顺序。
54
+ */
55
+ export function sortAgentsForDisplay(agents, availability) {
56
+ return [...agents].sort((a, b) => {
57
+ const aMissing = availability(a) === false ? 1 : 0;
58
+ const bMissing = availability(b) === false ? 1 : 0;
59
+ return aMissing - bMissing;
60
+ });
37
61
  }
38
62
  //# sourceMappingURL=index.js.map
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * Kimi config builder — builds fields and handles field changes for Kimi agent.
3
+ *
4
+ * Kimi is pure ACP mode: permissionMode (manual/auto/yolo) and the ACP
5
+ * sub-config fields are always shown.
3
6
  */
4
7
  import type { AgentConfigCardBuilder, ConfigField } from './types.js';
5
- import type { AppConfig } from '../../config/index.js';
8
+ import { type AppConfig } from '../../config/index.js';
6
9
  export declare class KimiConfigBuilder implements AgentConfigCardBuilder {
7
10
  buildFields(displayConfig: AppConfig): ConfigField[];
8
11
  handleFieldChange(key: string, value: unknown, config: AppConfig): Array<{
@@ -1,7 +1,16 @@
1
1
  /**
2
2
  * Kimi config builder — builds fields and handles field changes for Kimi agent.
3
+ *
4
+ * Kimi is pure ACP mode: permissionMode (manual/auto/yolo) and the ACP
5
+ * sub-config fields are always shown.
3
6
  */
4
7
  import { loadKimiConfig, KIMI_THINKING_EFFORTS, FALLBACK_EFFORTS, } from '../../config/kimi-config.js';
8
+ import { DEFAULT_TURN_IDLE_TIMEOUT_MINUTES } from '../../config/index.js';
9
+ const PERMISSION_MODE_OPTIONS = [
10
+ { text: 'manual(逐项审批)', value: 'manual' },
11
+ { text: 'auto(引擎裁决)', value: 'auto' },
12
+ { text: 'yolo(全部放行)', value: 'yolo' },
13
+ ];
5
14
  export class KimiConfigBuilder {
6
15
  buildFields(displayConfig) {
7
16
  const fields = [];
@@ -9,6 +18,8 @@ export class KimiConfigBuilder {
9
18
  const kimiCfg = loadKimiConfig();
10
19
  const currentModel = displayConfig.agents?.kimi?.model ?? kimiCfg.currentModel;
11
20
  const currentThinking = displayConfig.agents?.kimi?.thinkingEffort ?? 'max';
21
+ const currentPermissionMode = displayConfig.agents?.kimi?.permissionMode ?? 'manual';
22
+ // §7: 现有 model + thinkingEffort select(顺序不变)
12
23
  fields.push({
13
24
  key: 'agents.kimi.model',
14
25
  label: '使用模型',
@@ -25,6 +36,22 @@ export class KimiConfigBuilder {
25
36
  options: (kimiCfg.modelEfforts[currentModel] ?? [...FALLBACK_EFFORTS]).filter((e) => KIMI_THINKING_EFFORTS.includes(e)),
26
37
  currentValue: currentThinking,
27
38
  });
39
+ // Permission mode + ACP sub-config (pure ACP: always shown)
40
+ // §7: 文案用官方名 + 中文注释
41
+ fields.push({
42
+ key: 'agents.kimi.permissionMode',
43
+ label: '权限模式',
44
+ type: 'select',
45
+ options: PERMISSION_MODE_OPTIONS,
46
+ currentValue: currentPermissionMode,
47
+ });
48
+ const currentTurnIdleTimeoutMinutes = displayConfig.agents?.kimi?.acp?.turnIdleTimeoutMinutes ?? DEFAULT_TURN_IDLE_TIMEOUT_MINUTES;
49
+ fields.push({
50
+ key: 'agents.kimi.acp.turnIdleTimeoutMinutes',
51
+ label: 'Turn 空闲超时(分钟, 0关闭)',
52
+ type: 'input',
53
+ currentValue: String(currentTurnIdleTimeoutMinutes),
54
+ });
28
55
  return fields;
29
56
  }
30
57
  handleFieldChange(key, value, config) {