lark-remote 0.1.5 → 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 (190) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.en.md +5 -1
  3. package/README.md +10 -2
  4. package/dist/bridge/approval-coordinator.d.ts +131 -0
  5. package/dist/bridge/approval-coordinator.js +409 -0
  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 +138 -5
  9. package/dist/bridge/index.js +569 -65
  10. package/dist/bridge/queue-manager.d.ts +11 -1
  11. package/dist/bridge/queue-manager.js +20 -11
  12. package/dist/card/approval-render.d.ts +16 -0
  13. package/dist/card/approval-render.js +363 -0
  14. package/dist/card/bash-card-session.d.ts +2 -24
  15. package/dist/card/bash-card-session.js +6 -73
  16. package/dist/card/bash-renderer.js +2 -3
  17. package/dist/card/card-budget.js +29 -75
  18. package/dist/card/card-session.d.ts +48 -0
  19. package/dist/card/card-session.js +86 -0
  20. package/dist/card/card-shared.d.ts +13 -10
  21. package/dist/card/card-shared.js +27 -24
  22. package/dist/card/run-card-session.d.ts +2 -55
  23. package/dist/card/run-card-session.js +6 -104
  24. package/dist/card/run-renderer.js +140 -202
  25. package/dist/card/run-state.d.ts +38 -3
  26. package/dist/card/run-state.js +221 -11
  27. package/dist/card/text-truncate.d.ts +13 -12
  28. package/dist/card/text-truncate.js +13 -63
  29. package/dist/common/fs.d.ts +8 -0
  30. package/dist/common/fs.js +17 -0
  31. package/dist/common/guards.d.ts +0 -20
  32. package/dist/common/guards.js +0 -43
  33. package/dist/common/sleep.d.ts +2 -0
  34. package/dist/common/sleep.js +5 -0
  35. package/dist/common/truncate.d.ts +39 -0
  36. package/dist/common/truncate.js +92 -0
  37. package/dist/config/codex-config.d.ts +1 -1
  38. package/dist/config/codex-config.js +1 -1
  39. package/dist/config/dir.js +1 -1
  40. package/dist/config/index.d.ts +116 -9
  41. package/dist/config/index.js +122 -29
  42. package/dist/connector/index.d.ts +74 -2
  43. package/dist/connector/index.js +119 -1
  44. package/dist/index.js +139 -99
  45. package/dist/instance-lock.js +4 -12
  46. package/dist/order/alias-resolve.d.ts +13 -0
  47. package/dist/order/alias-resolve.js +22 -0
  48. package/dist/order/alias-store.d.ts +38 -0
  49. package/dist/order/alias-store.js +106 -0
  50. package/dist/persistence/atomic-write.d.ts +8 -2
  51. package/dist/persistence/atomic-write.js +23 -7
  52. package/dist/restart.js +2 -1
  53. package/dist/router/card-helpers.d.ts +63 -0
  54. package/dist/router/card-helpers.js +146 -0
  55. package/dist/router/config/claude.js +25 -1
  56. package/dist/router/config/codex.d.ts +1 -1
  57. package/dist/router/config/codex.js +33 -5
  58. package/dist/router/config/common/model-patch.d.ts +22 -0
  59. package/dist/router/config/common/model-patch.js +26 -0
  60. package/dist/router/config/common/render.js +9 -4
  61. package/dist/router/config/index.d.ts +12 -0
  62. package/dist/router/config/index.js +25 -1
  63. package/dist/router/config/kimi.d.ts +4 -1
  64. package/dist/router/config/kimi.js +27 -0
  65. package/dist/router/config/opencode.js +18 -5
  66. package/dist/router/config/pi.js +4 -5
  67. package/dist/router/config/types.d.ts +12 -3
  68. package/dist/router/index.d.ts +77 -17
  69. package/dist/router/index.js +455 -549
  70. package/dist/router/utils.d.ts +4 -0
  71. package/dist/router/utils.js +9 -2
  72. package/dist/runner/agent-choices-common.d.ts +16 -0
  73. package/dist/runner/agent-choices-common.js +31 -0
  74. package/dist/runner/claude/runner.d.ts +47 -10
  75. package/dist/runner/claude/runner.js +117 -46
  76. package/dist/runner/claude/session.d.ts +166 -0
  77. package/dist/runner/claude/session.js +749 -0
  78. package/dist/runner/codex/app-server/protocol-types.d.ts +519 -0
  79. package/dist/runner/codex/app-server/protocol-types.js +56 -0
  80. package/dist/runner/codex/app-server/runner.d.ts +104 -0
  81. package/dist/runner/codex/app-server/runner.js +377 -0
  82. package/dist/runner/codex/app-server/translator.d.ts +89 -0
  83. package/dist/runner/codex/app-server/translator.js +716 -0
  84. package/dist/runner/codex/index.d.ts +1 -1
  85. package/dist/runner/codex/index.js +1 -1
  86. package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
  87. package/dist/runner/common/acp/protocol-helpers.js +21 -0
  88. package/dist/runner/common/acp/protocol-types.d.ts +238 -0
  89. package/dist/runner/common/acp/protocol-types.js +75 -0
  90. package/dist/runner/common/connection-based-runner.d.ts +111 -0
  91. package/dist/runner/common/connection-based-runner.js +261 -0
  92. package/dist/runner/common/jsonrpc/client.d.ts +89 -0
  93. package/dist/runner/common/jsonrpc/client.js +243 -0
  94. package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
  95. package/dist/runner/common/jsonrpc/connection-manager.js +205 -0
  96. package/dist/runner/common/jsonrpc/transport.d.ts +50 -0
  97. package/dist/runner/common/jsonrpc/transport.js +230 -0
  98. package/dist/runner/common/jsonrpc/types.d.ts +32 -0
  99. package/dist/runner/common/jsonrpc/types.js +9 -0
  100. package/dist/runner/common/process-stopper.d.ts +2 -1
  101. package/dist/runner/common/process-stopper.js +1 -0
  102. package/dist/runner/common/runner-utils.d.ts +6 -13
  103. package/dist/runner/common/runner-utils.js +6 -47
  104. package/dist/runner/common/spawning-runner.d.ts +24 -70
  105. package/dist/runner/common/spawning-runner.js +98 -152
  106. package/dist/runner/common/usage.d.ts +2 -0
  107. package/dist/runner/common/usage.js +2 -0
  108. package/dist/runner/index.d.ts +5 -5
  109. package/dist/runner/index.js +4 -4
  110. package/dist/runner/kimi/acp/runner.d.ts +106 -0
  111. package/dist/runner/kimi/acp/runner.js +448 -0
  112. package/dist/runner/kimi/acp/translator.d.ts +124 -0
  113. package/dist/runner/kimi/acp/translator.js +336 -0
  114. package/dist/runner/kimi/index.d.ts +1 -1
  115. package/dist/runner/kimi/index.js +1 -1
  116. package/dist/runner/opencode/acp/index.d.ts +1 -0
  117. package/dist/runner/opencode/acp/index.js +2 -0
  118. package/dist/runner/opencode/acp/runner.d.ts +121 -0
  119. package/dist/runner/opencode/acp/runner.js +429 -0
  120. package/dist/runner/opencode/acp/translator.d.ts +119 -0
  121. package/dist/runner/opencode/acp/translator.js +345 -0
  122. package/dist/runner/opencode/index.d.ts +1 -1
  123. package/dist/runner/opencode/index.js +1 -1
  124. package/dist/runner/pi/index.d.ts +1 -1
  125. package/dist/runner/pi/index.js +1 -1
  126. package/dist/runner/pi/rpc/client.d.ts +52 -0
  127. package/dist/runner/pi/rpc/client.js +114 -0
  128. package/dist/runner/pi/rpc/index.d.ts +4 -0
  129. package/dist/runner/pi/rpc/index.js +4 -0
  130. package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
  131. package/dist/runner/pi/rpc/protocol-types.js +21 -0
  132. package/dist/runner/pi/rpc/runner.d.ts +80 -0
  133. package/dist/runner/pi/rpc/runner.js +199 -0
  134. package/dist/runner/pi/rpc/translator.d.ts +55 -0
  135. package/dist/runner/pi/rpc/translator.js +258 -0
  136. package/dist/runner/registry.d.ts +7 -8
  137. package/dist/runner/registry.js +3 -4
  138. package/dist/runner/resolve-agent-choices.js +18 -59
  139. package/dist/runner/sync-agent-choices.js +16 -67
  140. package/dist/runner/types.d.ts +153 -2
  141. package/dist/session/claude/session-index.js +2 -3
  142. package/dist/session/claude/session-reader.js +3 -6
  143. package/dist/session/claude/sessions.js +6 -13
  144. package/dist/session/codex/rollout-reader.d.ts +2 -2
  145. package/dist/session/codex/rollout-reader.js +57 -17
  146. package/dist/session/codex/sessions.d.ts +1 -1
  147. package/dist/session/codex/sessions.js +2 -2
  148. package/dist/session/common/content-blocks.d.ts +7 -0
  149. package/dist/session/common/content-blocks.js +13 -9
  150. package/dist/session/common/jsonl.d.ts +8 -25
  151. package/dist/session/common/jsonl.js +126 -261
  152. package/dist/session/common/pagination.d.ts +28 -0
  153. package/dist/session/common/pagination.js +33 -0
  154. package/dist/session/kimi/sessions.d.ts +34 -1
  155. package/dist/session/kimi/sessions.js +208 -98
  156. package/dist/session/opencode/sessions.d.ts +17 -3
  157. package/dist/session/opencode/sessions.js +29 -29
  158. package/dist/session/pi/sessions.d.ts +5 -1
  159. package/dist/session/pi/sessions.js +12 -16
  160. package/dist/session/session-store.d.ts +11 -1
  161. package/dist/session/session-store.js +75 -178
  162. package/dist/update/index.d.ts +2 -2
  163. package/dist/update/index.js +2 -2
  164. package/dist/update/install.d.ts +20 -2
  165. package/dist/update/install.js +51 -7
  166. package/package.json +1 -1
  167. package/dist/runner/codex/argv.d.ts +0 -33
  168. package/dist/runner/codex/argv.js +0 -40
  169. package/dist/runner/codex/jsonl.d.ts +0 -37
  170. package/dist/runner/codex/jsonl.js +0 -221
  171. package/dist/runner/codex/runner.d.ts +0 -63
  172. package/dist/runner/codex/runner.js +0 -124
  173. package/dist/runner/common/exec-translator.d.ts +0 -73
  174. package/dist/runner/common/exec-translator.js +0 -105
  175. package/dist/runner/kimi/jsonl.d.ts +0 -28
  176. package/dist/runner/kimi/jsonl.js +0 -143
  177. package/dist/runner/kimi/runner.d.ts +0 -49
  178. package/dist/runner/kimi/runner.js +0 -105
  179. package/dist/runner/opencode/argv.d.ts +0 -32
  180. package/dist/runner/opencode/argv.js +0 -35
  181. package/dist/runner/opencode/jsonl.d.ts +0 -68
  182. package/dist/runner/opencode/jsonl.js +0 -295
  183. package/dist/runner/opencode/runner.d.ts +0 -61
  184. package/dist/runner/opencode/runner.js +0 -93
  185. package/dist/runner/pi/jsonl.d.ts +0 -128
  186. package/dist/runner/pi/jsonl.js +0 -249
  187. package/dist/runner/pi/runner.d.ts +0 -36
  188. package/dist/runner/pi/runner.js +0 -76
  189. package/dist/session/common/catch-up.d.ts +0 -12
  190. package/dist/session/common/catch-up.js +0 -31
@@ -1,6 +1,7 @@
1
+ import { choiceFieldsFor } from './agent-choices-common.js';
1
2
  export function resolveAgentChoices(config) {
2
- const choices = config.agentChoices;
3
3
  const agent = config.defaultAgent;
4
+ const choices = config.agentChoices;
4
5
  if (!choices || !agent) {
5
6
  return config;
6
7
  }
@@ -9,67 +10,25 @@ export function resolveAgentChoices(config) {
9
10
  return config;
10
11
  }
11
12
  const agentChoices = choices[agent];
12
- if (!agentChoices) {
13
+ const fields = choiceFieldsFor(agent);
14
+ if (!agentChoices || !fields) {
13
15
  return config;
14
16
  }
15
17
  // Clone to avoid mutation
16
- const resolved = JSON.parse(JSON.stringify(config));
17
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
- const resolvedAny = resolved;
19
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
- const agentChoicesAny = agentChoices;
21
- switch (agent) {
22
- case 'codex':
23
- if (!resolvedAny.agents)
24
- resolvedAny.agents = {};
25
- if (!resolvedAny.agents.codex)
26
- resolvedAny.agents.codex = {};
27
- if (!resolvedAny.agents.codex.model && agentChoicesAny.model) {
28
- resolvedAny.agents.codex.model = agentChoicesAny.model;
29
- }
30
- if (!resolvedAny.agents.codex.modelProvider && agentChoicesAny.modelProvider) {
31
- resolvedAny.agents.codex.modelProvider = agentChoicesAny.modelProvider;
32
- }
33
- break;
34
- case 'pi':
35
- if (!resolvedAny.agents)
36
- resolvedAny.agents = {};
37
- if (!resolvedAny.agents.pi)
38
- resolvedAny.agents.pi = {};
39
- if (!resolvedAny.agents.pi.model && agentChoicesAny.model) {
40
- resolvedAny.agents.pi.model = agentChoicesAny.model;
41
- }
42
- if (!resolvedAny.agents.pi.provider && agentChoicesAny.provider) {
43
- resolvedAny.agents.pi.provider = agentChoicesAny.provider;
44
- }
45
- if (!resolvedAny.agents.pi.thinking && agentChoicesAny.thinking) {
46
- resolvedAny.agents.pi.thinking = agentChoicesAny.thinking;
47
- }
48
- break;
49
- case 'opencode':
50
- if (!resolvedAny.agents)
51
- resolvedAny.agents = {};
52
- if (!resolvedAny.agents.opencode)
53
- resolvedAny.agents.opencode = {};
54
- if (!resolvedAny.agents.opencode.modelID && agentChoicesAny.modelID) {
55
- resolvedAny.agents.opencode.modelID = agentChoicesAny.modelID;
56
- }
57
- if (!resolvedAny.agents.opencode.providerID && agentChoicesAny.providerID) {
58
- resolvedAny.agents.opencode.providerID = agentChoicesAny.providerID;
59
- }
60
- break;
61
- case 'kimi':
62
- if (!resolvedAny.agents)
63
- resolvedAny.agents = {};
64
- if (!resolvedAny.agents.kimi)
65
- resolvedAny.agents.kimi = {};
66
- if (!resolvedAny.agents.kimi.model && agentChoicesAny.model) {
67
- resolvedAny.agents.kimi.model = agentChoicesAny.model;
68
- }
69
- if (!resolvedAny.agents.kimi.thinkingEffort && agentChoicesAny.thinkingEffort) {
70
- resolvedAny.agents.kimi.thinkingEffort = agentChoicesAny.thinkingEffort;
71
- }
72
- break;
18
+ const resolved = structuredClone(config);
19
+ // agents.codex 在 schema 中必填(带默认值),此处按旧语义补空对象;
20
+ // 运行时只往 [agent] 槽位写字段,不依赖 codex 默认值。
21
+ if (!resolved.agents) {
22
+ resolved.agents = {};
23
+ }
24
+ const agents = resolved.agents;
25
+ const target = (agents[agent] ??= {});
26
+ const source = agentChoices;
27
+ for (const { configKey, choicesKey } of fields) {
28
+ const value = source[choicesKey];
29
+ if (!target[configKey] && value) {
30
+ target[configKey] = value;
31
+ }
73
32
  }
74
33
  return resolved;
75
34
  }
@@ -1,72 +1,21 @@
1
+ import { choiceFieldsFor } from './agent-choices-common.js';
1
2
  export function syncAgentChoices(config, agent) {
2
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
- const configAny = config;
4
- const agents = configAny.agents || {};
5
- const agentCfg = agents[agent];
6
- // claude reads from top-level config.claude, not agents.claude
7
- if (agent !== 'claude' && !agentCfg) {
3
+ // claude 的配置在顶层 config.claude,不在 agents 下,无需同步
4
+ const fields = choiceFieldsFor(agent);
5
+ if (!fields)
8
6
  return config;
9
- }
10
- const updated = JSON.parse(JSON.stringify(config));
11
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
- const updatedAny = updated;
13
- if (!updatedAny.agentChoices) {
14
- updatedAny.agentChoices = {};
15
- }
16
- switch (agent) {
17
- case 'codex':
18
- if (agentCfg.model) {
19
- if (!updatedAny.agentChoices.codex)
20
- updatedAny.agentChoices.codex = {};
21
- updatedAny.agentChoices.codex.model = agentCfg.model;
22
- }
23
- if (agentCfg.modelProvider) {
24
- if (!updatedAny.agentChoices.codex)
25
- updatedAny.agentChoices.codex = {};
26
- updatedAny.agentChoices.codex.modelProvider = agentCfg.modelProvider;
27
- }
28
- break;
29
- case 'pi':
30
- if (agentCfg.model) {
31
- if (!updatedAny.agentChoices.pi)
32
- updatedAny.agentChoices.pi = {};
33
- updatedAny.agentChoices.pi.model = agentCfg.model;
34
- }
35
- if (agentCfg.provider) {
36
- if (!updatedAny.agentChoices.pi)
37
- updatedAny.agentChoices.pi = {};
38
- updatedAny.agentChoices.pi.provider = agentCfg.provider;
39
- }
40
- if (agentCfg.thinking) {
41
- if (!updatedAny.agentChoices.pi)
42
- updatedAny.agentChoices.pi = {};
43
- updatedAny.agentChoices.pi.thinking = agentCfg.thinking;
44
- }
45
- break;
46
- case 'opencode':
47
- if (agentCfg.modelID) {
48
- if (!updatedAny.agentChoices.opencode)
49
- updatedAny.agentChoices.opencode = {};
50
- updatedAny.agentChoices.opencode.modelID = agentCfg.modelID;
51
- }
52
- if (agentCfg.providerID) {
53
- if (!updatedAny.agentChoices.opencode)
54
- updatedAny.agentChoices.opencode = {};
55
- updatedAny.agentChoices.opencode.providerID = agentCfg.providerID;
56
- }
57
- break;
58
- case 'kimi':
59
- if (agentCfg.model) {
60
- if (!updatedAny.agentChoices.kimi)
61
- updatedAny.agentChoices.kimi = {};
62
- updatedAny.agentChoices.kimi.model = agentCfg.model;
63
- }
64
- if (agentCfg.thinkingEffort) {
65
- if (!updatedAny.agentChoices.kimi)
66
- updatedAny.agentChoices.kimi = {};
67
- updatedAny.agentChoices.kimi.thinkingEffort = agentCfg.thinkingEffort;
68
- }
69
- break;
7
+ const agents = config.agents;
8
+ const agentCfg = agents?.[agent];
9
+ if (!agentCfg)
10
+ return config;
11
+ const updated = structuredClone(config);
12
+ const choices = (updated.agentChoices ??= {});
13
+ const agentChoices = (choices[agent] ??= {});
14
+ const source = agentCfg;
15
+ for (const { configKey, choicesKey } of fields) {
16
+ const value = source[configKey];
17
+ if (value)
18
+ agentChoices[choicesKey] = value;
70
19
  }
71
20
  return updated;
72
21
  }
@@ -21,6 +21,105 @@ interface SystemCompactEvent {
21
21
  preTokens: number;
22
22
  };
23
23
  }
24
+ export interface ApprovalRequestedEvent {
25
+ type: 'approval_requested';
26
+ /** JSON-RPC id of the server request. Wire type is string | integer — keep raw. */
27
+ requestId: number | string;
28
+ kind: 'command' | 'file' | 'permissions' | 'question';
29
+ threadId: string;
30
+ turnId: string;
31
+ itemId: string;
32
+ view: ApprovalView;
33
+ timestamp?: string;
34
+ }
35
+ export interface ApprovalResolvedEvent {
36
+ type: 'approval_resolved';
37
+ requestId: number | string;
38
+ outcome: 'resolved' | 'expired';
39
+ timestamp?: string;
40
+ }
41
+ /** Approval card content update (out-of-order item/started arrives late). */
42
+ export interface ApprovalViewUpdatedEvent {
43
+ type: 'approval_view_updated';
44
+ requestId: number | string;
45
+ view: ApprovalView;
46
+ timestamp?: string;
47
+ }
48
+ /** Approval expired locally (bridge-side timeout) — card should show expired UI. */
49
+ export interface ApprovalExpiredEvent {
50
+ type: 'approval_expired';
51
+ requestId: number | string;
52
+ timestamp?: string;
53
+ }
54
+ /** 审批决策(bridge ApprovalCoordinator → runner.respondApproval 的响应载荷)。 */
55
+ export type ApprovalAction = {
56
+ action: 'accept';
57
+ } | {
58
+ action: 'accept_for_session';
59
+ } | {
60
+ action: 'accept_with_execpolicy_amendment';
61
+ }
62
+ /** Claude:允许当前请求 + 本会话后续所有权限请求自动放行(允许所有)。 */
63
+ | {
64
+ action: 'accept_all';
65
+ } | {
66
+ action: 'decline';
67
+ } | {
68
+ action: 'cancel';
69
+ }
70
+ /** Claude AskUserQuestion 答案(question → label 或 label[])。 */
71
+ | {
72
+ action: 'answer';
73
+ answers: Record<string, string | string[]>;
74
+ };
75
+ /** Claude Code AskUserQuestion 的单个选项(control_request input.questions[].options[])。 */
76
+ export interface ClaudeUserQuestionOption {
77
+ label: string;
78
+ description?: string;
79
+ }
80
+ /** Claude Code AskUserQuestion 的单个问题(control_request input.questions[])。 */
81
+ export interface ClaudeUserQuestion {
82
+ question: string;
83
+ header?: string;
84
+ multiSelect?: boolean;
85
+ options: ClaudeUserQuestionOption[];
86
+ /**
87
+ * 当前已选选项 label(卡片渲染与多选切换用;单选在协调器内即时提交)。
88
+ * 与 permissions.items.selected 同模式:协调器原地更新后以
89
+ * approval_view_updated 重新渲染。
90
+ */
91
+ selected?: string[];
92
+ }
93
+ export interface ApprovalView {
94
+ requestId: number | string;
95
+ kind: 'command' | 'file' | 'permissions' | 'question';
96
+ reason?: string;
97
+ command?: string;
98
+ commandCwd?: string;
99
+ fileChanges?: Array<{
100
+ path: string;
101
+ kind: 'add' | 'update' | 'delete';
102
+ diff?: string;
103
+ }>;
104
+ permissions?: {
105
+ items: Array<{
106
+ id: string;
107
+ label: string;
108
+ target: {
109
+ kind: 'network';
110
+ } | {
111
+ kind: 'fsRead' | 'fsWrite';
112
+ path: string;
113
+ };
114
+ selected: boolean;
115
+ }>;
116
+ };
117
+ /** Claude AskUserQuestion 问题列表(kind === 'question' 时存在)。 */
118
+ questions?: ClaudeUserQuestion[];
119
+ availableDecisions: string[];
120
+ /** Raw payloads for structured decisions (e.g. acceptWithExecpolicyAmendment). */
121
+ decisionPayloads?: Record<string, unknown>;
122
+ }
24
123
  interface ThinkingContent {
25
124
  type: 'thinking';
26
125
  thinking: string;
@@ -58,7 +157,7 @@ export interface UserEvent {
58
157
  }
59
158
  export interface ResultEvent {
60
159
  type: 'result';
61
- subtype: 'success' | 'error';
160
+ subtype: 'success' | 'error' | 'interrupted';
62
161
  session_id: string;
63
162
  timestamp?: string;
64
163
  usage?: {
@@ -75,6 +174,9 @@ export interface ResultEvent {
75
174
  cache_creation_input_tokens?: number;
76
175
  /** Agent-declared total; when present the display uses max(total, sum of parts). */
77
176
  total_tokens?: number;
177
+ /** 当前模型 context window 上限(codex app-server tokenUsage.modelContextWindow
178
+ * 透传)。仅 app-server 提供;缺省时卡片只显示绝对量、不显示百分比。 */
179
+ context_limit?: number;
78
180
  };
79
181
  total_cost_usd?: number;
80
182
  errorMessage?: string;
@@ -99,7 +201,50 @@ export interface FileChangeEvent {
99
201
  operation: 'create' | 'edit' | 'delete' | 'read';
100
202
  timestamp?: string;
101
203
  }
102
- export type AgentEvent = SystemInitEvent | SystemCompactEvent | AssistantEvent | UserEvent | ResultEvent | PlanEvent | FileChangeEvent;
204
+ export interface TurnStartedEvent {
205
+ type: 'turn_started';
206
+ threadId: string;
207
+ turnId: string;
208
+ operationKind: 'turn' | 'compaction';
209
+ timestamp?: string;
210
+ }
211
+ export interface TurnDiffEvent {
212
+ type: 'turn_diff';
213
+ /**
214
+ * Thread item id this snapshot belongs to (app-server item-scoped stream).
215
+ * Each content item (reasoning / agentMessage / commandExecution / plan /
216
+ * fileChange) is tracked independently, so interleaved items keep their
217
+ * real chronology and update their own block instead of a shared one.
218
+ */
219
+ itemId: string;
220
+ /** Full accumulated assistant text snapshot for this item (replaces previous). */
221
+ text?: string;
222
+ /** Full accumulated reasoning snapshot for this item (replaces previous). */
223
+ reasoning?: string;
224
+ /** Full accumulated tool output snapshot for this item (replaces previous). */
225
+ toolOutput?: string;
226
+ /** Full accumulated plan text snapshot for this item (replaces previous). */
227
+ plan?: string;
228
+ /** Current set of file changes for this item (replaces previous). */
229
+ fileChanges?: Array<{
230
+ path: string;
231
+ kind: 'add' | 'update' | 'delete';
232
+ diff?: string;
233
+ }>;
234
+ /**
235
+ * Authoritative completion snapshot (item/completed or turn/completed).
236
+ * The reducer finalizes the block in place: thinking/plan active=false,
237
+ * tool status ok, completedAt stamped. Never reorders blocks.
238
+ */
239
+ complete?: boolean;
240
+ /** Authoritative tool status at completion (commandExecution item). */
241
+ toolStatus?: 'ok' | 'error';
242
+ threadId: string;
243
+ turnId: string;
244
+ timestamp?: string;
245
+ }
246
+ export type RunnerLifetime = 'turn' | 'workspace';
247
+ export type AgentEvent = SystemInitEvent | SystemCompactEvent | ApprovalRequestedEvent | ApprovalResolvedEvent | ApprovalViewUpdatedEvent | ApprovalExpiredEvent | AssistantEvent | UserEvent | ResultEvent | PlanEvent | FileChangeEvent | TurnStartedEvent | TurnDiffEvent;
103
248
  export interface SpawnOptions {
104
249
  cwd: string;
105
250
  sessionId?: string;
@@ -117,6 +262,8 @@ export interface SpawnOptions {
117
262
  */
118
263
  export interface Runner {
119
264
  readonly isRunning: boolean;
265
+ readonly lifetime?: RunnerLifetime;
266
+ dispose?(): Promise<void>;
120
267
  run(message: string, opts: SpawnOptions): AsyncGenerator<AgentEvent>;
121
268
  /**
122
269
  * Stop the running process. SIGTERM → grace → SIGKILL on the per-workspace
@@ -158,6 +305,8 @@ export interface AgentSessionUsage {
158
305
  contextLimit?: number;
159
306
  /** Claude auto-compact 次数(数 compact_boundary 事件);codex/opencode 无此概念,留 undefined。 */
160
307
  compactCount?: number;
308
+ /** 压缩前上下文水位(codex compact 后从会话 jsonl 读取,仅会话以压缩收尾时有值)。 */
309
+ compactPreContextLength?: number;
161
310
  /** 从缓存读取的 token 数(节省的费用)。 */
162
311
  cacheReadTokens?: number;
163
312
  /** 新建缓存的 token 数。 */
@@ -236,6 +385,8 @@ export interface AgentRunner extends Runner {
236
385
  readonly sessionReader: AgentSessionReader;
237
386
  /** Return current status info for /status display. */
238
387
  getStatusInfo(): AgentStatusInfo;
388
+ /** Whether this runner provides live usage data (vs. file-based). */
389
+ getUsageAuthority?(): 'live' | 'jsonl';
239
390
  }
240
391
  /** Agent self-describing status info for /status display. */
241
392
  export interface AgentStatusInfo {
@@ -2,7 +2,6 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { scanJsonlLines } from '../common/jsonl.js';
4
4
  import { extractContentBlocks } from '../common/content-blocks.js';
5
- import { truncate } from '../../card/card-shared.js';
6
5
  import { getLogger } from '../../logger/index.js';
7
6
  // ─── Claude content block mapping (shared with sessions.ts) ─
8
7
  /** Claude-specific field-name mapping for content block extraction. */
@@ -224,7 +223,7 @@ export class SessionIndex {
224
223
  path: p,
225
224
  fingerprint: currentFp,
226
225
  mtimeMs: Number(st.mtimeMs),
227
- summary: truncate(parsed.summary, 60, { normalizeWhitespace: true }),
226
+ summary: parsed.summary.replace(/\s+/g, ' ').trim(),
228
227
  cwdSet: parsed.cwdSet,
229
228
  };
230
229
  this.updateEntry(p, updated);
@@ -302,7 +301,7 @@ export class SessionIndex {
302
301
  path: fullPath,
303
302
  fingerprint: fpAfter,
304
303
  mtimeMs: Number(fileStat.mtimeMs),
305
- summary: truncate(parsed.summary, 60, { normalizeWhitespace: true }),
304
+ summary: parsed.summary.replace(/\s+/g, ' ').trim(),
306
305
  cwdSet: parsed.cwdSet,
307
306
  };
308
307
  this.updateEntry(fullPath, newEntry);
@@ -1,3 +1,4 @@
1
+ import { paginate } from '../common/pagination.js';
1
2
  import { listClaudeSessions, getNewestSession, readSessionContent, isClaudeSessionActive, } from '../../session/claude/sessions.js';
2
3
  /**
3
4
  * Adapter that exposes Claude Code's session-reading functions through the
@@ -22,12 +23,8 @@ export class ClaudeSessionReader {
22
23
  // 先拿全集(不传 limit → listClaudeSessions 不切片),total 用全集长度,
23
24
  // 再由 reader 按 [offset, offset+limit) 切片。
24
25
  const all = listClaudeSessions(cwd, this.readerOpts());
25
- const offset = Math.max(0, opts?.offset ?? 0);
26
- const limit = opts?.limit;
27
- return {
28
- sessions: limit === undefined ? all.slice(offset) : all.slice(offset, offset + limit),
29
- total: all.length,
30
- };
26
+ const { items, total } = paginate(all, opts ?? {});
27
+ return { sessions: items, total };
31
28
  }
32
29
  getNewestSession(cwd) {
33
30
  const session = getNewestSession(cwd, this.readerOpts());
@@ -3,9 +3,9 @@ import path from 'node:path';
3
3
  import os from 'node:os';
4
4
  import { findJsonlLine, readLastJsonlLine, scanJsonlLines, readJsonlLinesFromOffset, } from '../common/jsonl.js';
5
5
  import { STALE_MS } from '../common/constants.js';
6
+ import { capEvents } from '../common/pagination.js';
6
7
  import { extractContentBlocks } from '../common/content-blocks.js';
7
8
  import { UsageAccumulator } from '../common/usage-accumulator.js';
8
- import { truncate } from '../../card/card-shared.js';
9
9
  import { SessionIndex, extractText, isTaskNotificationText, CLAUDE_MAPPING, } from './session-index.js';
10
10
  /**
11
11
  * Directory Claude Code uses to store session transcripts for a given cwd.
@@ -262,7 +262,7 @@ function scalarScan(filePath) {
262
262
  lastPostTokens = cm.postTokens;
263
263
  }
264
264
  }
265
- // --- last user message offset (was findLastUserIndex) ---
265
+ // --- last user message offset ---
266
266
  // Record the byte offset where the tail should start: the beginning of
267
267
  // the NEXT line after this user message. We compute it as this line's
268
268
  // start offset + its byte length + 1 (the trailing '\n').
@@ -382,12 +382,7 @@ function extractEventsFromTail(tailLines, maxEvents) {
382
382
  events.push({ type: role, content: `(${role} event)`, timestamp });
383
383
  }
384
384
  }
385
- if (maxEvents !== undefined) {
386
- if (maxEvents <= 0)
387
- return [];
388
- return events.slice(-maxEvents);
389
- }
390
- return events;
385
+ return capEvents(events, maxEvents);
391
386
  }
392
387
  export function readSessionContent(sessionId, cwd, opts = {}) {
393
388
  const { maxEvents } = opts;
@@ -447,11 +442,9 @@ export function readSessionContent(sessionId, cwd, opts = {}) {
447
442
  // show.
448
443
  const tailLines = readJsonlLinesFromOffset(filePath, scan.tailOffset >= 0 ? scan.tailOffset : 0);
449
444
  const events = extractEventsFromTail(tailLines, maxEvents);
450
- // Use aiTitle if available, otherwise fallback to last user message (truncated to 200 chars)
451
- const displayTitle = scan.aiTitle ??
452
- (scan.lastUserMessage
453
- ? truncate(scan.lastUserMessage, 200, { normalizeWhitespace: true })
454
- : undefined);
445
+ // Use aiTitle if available, otherwise fallback to last user message.
446
+ // Truncation is deferred to the consumer (router card building).
447
+ const displayTitle = scan.aiTitle ?? scan.lastUserMessage ?? undefined;
455
448
  return {
456
449
  events,
457
450
  usage,
@@ -11,8 +11,7 @@
11
11
  * ```
12
12
  *
13
13
  * Note: This handles the **rollout file format** (historical logging), NOT the
14
- * `codex exec --json` stdout format. Those are two different JSONL formats
15
- * (see design doc appendix B vs C).
14
+ * `codex exec --json` stdout format(已随 exec 模式移除)。
16
15
  */
17
16
  import type { AgentSessionContentEvent, SessionContent, AgentSessionUsage } from '../../runner/index.js';
18
17
  interface CodexRolloutEntry {
@@ -83,6 +82,7 @@ export declare function readCodexSessionContent(sessionId: string, opts?: {
83
82
  export declare function isCodexSessionActive(sessionId: string, opts?: {
84
83
  codexHome?: string;
85
84
  activeThresholdMs?: number;
85
+ cwd?: string;
86
86
  }): boolean;
87
87
  /** Clear the session index cache. Exposed for tests. */
88
88
  export declare function clearSessionIndexCache(): void;
@@ -11,12 +11,13 @@
11
11
  * ```
12
12
  *
13
13
  * Note: This handles the **rollout file format** (historical logging), NOT the
14
- * `codex exec --json` stdout format. Those are two different JSONL formats
15
- * (see design doc appendix B vs C).
14
+ * `codex exec --json` stdout format(已随 exec 模式移除)。
16
15
  */
17
16
  import fs from 'node:fs';
18
17
  import path from 'node:path';
19
18
  import { readJsonlLines, findJsonlLine } from '../common/jsonl.js';
19
+ import { STALE_MS } from '../common/constants.js';
20
+ import { paginate, capEvents } from '../common/pagination.js';
20
21
  import { getLogger } from '../../logger/index.js';
21
22
  import { isRecord, stringValue } from '../../common/guards.js';
22
23
  import { resolveCodexHome } from '../../config/codex-config.js';
@@ -58,6 +59,18 @@ export function readCodexRollout(filePath) {
58
59
  // Run card's "累计" display. Codex emits this alongside last_token_usage.
59
60
  let lastTotalUsage;
60
61
  let previousTotals;
62
+ // Compaction 统计:codex 的 compact turn 在会话文件里写顶层 `compacted`
63
+ // 事件(含摘要),压缩前后的上下文水位由相邻 token_count 表达。压缩收尾的
64
+ // token_count 增量是 input/cached/output 全 0、只有 total_tokens 有值
65
+ // (窗口被摘要+replacement history 占据),现有 zero-filter 会跳过它,
66
+ // 因此这里必须单独捕获,不能复用 lastRawUsage。
67
+ // 单位口径(review P3-9):前后两侧统一用 total_tokens —— 压缩前取最近一次
68
+ // 非零 token_count 的 total_tokens(input+output,即该 turn 在窗口内的全部
69
+ // token),压缩后取收尾事件的 total_tokens(压缩后窗口)。不得一边 input
70
+ // 一边 total,否则「压缩前 X → 压缩后 Y」两边单位不一致。
71
+ let compactCount = 0;
72
+ let compactPreContextLength;
73
+ let compactPostContextLength;
61
74
  for (const line of lines) {
62
75
  const trimmed = line.trim();
63
76
  if (!trimmed)
@@ -101,6 +114,27 @@ export function readCodexRollout(filePath) {
101
114
  const ctxWindow = info?.model_context_window;
102
115
  lastContextLimit = typeof ctxWindow === 'number' ? ctxWindow : undefined;
103
116
  }
117
+ // 压缩后的水位:压缩收尾 token_count 全 0(raw 不满足上面 zero-filter),
118
+ // 但 total_tokens 表达真实窗口;之后若出现普通 turn(input>0),
119
+ // 水位回归该 turn,post-compact 值失效。
120
+ if (compactCount > 0 && raw) {
121
+ if (raw.total_tokens &&
122
+ !raw.input_tokens &&
123
+ !raw.cached_input_tokens &&
124
+ !raw.output_tokens) {
125
+ compactPostContextLength = raw.total_tokens;
126
+ }
127
+ else if (raw.input_tokens > 0) {
128
+ compactPostContextLength = undefined;
129
+ }
130
+ }
131
+ }
132
+ }
133
+ else if (parsed.type === 'compacted') {
134
+ // 顶层 compacted 事件:压缩次数 +1,压缩前水位取最近一次非零 token_count。
135
+ compactCount++;
136
+ if (lastRawUsage?.total_tokens !== undefined) {
137
+ compactPreContextLength = lastRawUsage.total_tokens;
104
138
  }
105
139
  }
106
140
  else if (parsed.type === 'response_item' && parsed.payload) {
@@ -170,6 +204,16 @@ export function readCodexRollout(filePath) {
170
204
  usage.cumulativeCacheReadTokens = cumCached;
171
205
  usage.cumulativeCacheCreationTokens = 0; // Codex 不报告 cache creation
172
206
  }
207
+ // 压缩统计:会话内出现过 compacted 事件就计数(供 /resume 与 Compact 卡
208
+ // 展示);仅当会话以压缩收尾时(post-compact 水位存在),contextLength
209
+ // 覆盖为压缩后水位并暴露压缩前水位——后续还有普通 turn 时水位回归该 turn。
210
+ if (compactCount > 0) {
211
+ usage.compactCount = compactCount;
212
+ if (compactPostContextLength !== undefined) {
213
+ usage.contextLength = compactPostContextLength;
214
+ usage.compactPreContextLength = compactPreContextLength;
215
+ }
216
+ }
173
217
  }
174
218
  return {
175
219
  threadId: sessionId,
@@ -200,8 +244,6 @@ export function readCodexRollout(filePath) {
200
244
  */
201
245
  export function listCodexRollouts(opts = {}) {
202
246
  const codexHome = resolveCodexHome(opts.codexHome);
203
- const limit = opts.limit ?? 20;
204
- const offset = Math.max(0, opts.offset ?? 0);
205
247
  const filterCwd = opts.cwd;
206
248
  // Full index: every rollout file's session_meta + mtime, no early break.
207
249
  const index = getSessionIndex(codexHome);
@@ -220,8 +262,7 @@ export function listCodexRollouts(opts = {}) {
220
262
  // Same-mtime ties use filePath as a deterministic secondary key so index
221
263
  // rebuilds / walk order changes never reorder the page.
222
264
  matched.sort((a, b) => b.mtimeMs - a.mtimeMs || a.filePath.localeCompare(b.filePath));
223
- const total = matched.length;
224
- const page = matched.slice(offset, offset + limit);
265
+ const { items: page, total } = paginate(matched, { limit: opts.limit, offset: opts.offset });
225
266
  // Full-parse only the page being returned (summary/usage).
226
267
  const entries = [];
227
268
  for (const item of page) {
@@ -274,22 +315,14 @@ export function readCodexSessionContent(sessionId, opts = {}) {
274
315
  }
275
316
  // Apply maxEvents cap: keep the LAST N events (most recent).
276
317
  // See function docstring for rationale.
277
- // P2-7: maxEvents <= 0 returns [] — guards the `slice(-0) === slice(0)`
278
- // full-array trap (no caller passes 0 today, but the contract must hold).
279
- const events = opts.maxEvents !== undefined && opts.maxEvents > 0 && rollout.events.length > opts.maxEvents
280
- ? rollout.events.slice(-opts.maxEvents)
281
- : opts.maxEvents !== undefined && opts.maxEvents <= 0
282
- ? []
283
- : rollout.events;
318
+ const events = capEvents(rollout.events, opts.maxEvents);
284
319
  return {
285
320
  events,
286
321
  // displayTitle = LAST real user message (matches the
287
322
  // "最近输入" label). recap is undefined: codex has no
288
323
  // compact-summary concept, so never fake one from a user
289
324
  // message (was: firstUserMessage = injected project rules).
290
- displayTitle: rollout.lastRealUserMessage
291
- ? rollout.lastRealUserMessage.slice(0, 50)
292
- : undefined,
325
+ displayTitle: rollout.lastRealUserMessage || undefined,
293
326
  recap: undefined,
294
327
  usage: rollout.usage,
295
328
  };
@@ -304,7 +337,9 @@ export function readCodexSessionContent(sessionId, opts = {}) {
304
337
  */
305
338
  export function isCodexSessionActive(sessionId, opts = {}) {
306
339
  const codexHome = resolveCodexHome(opts.codexHome);
307
- const threshold = opts.activeThresholdMs ?? 10 * 60 * 1000; // 10 minutes default
340
+ // Unify with the other readers' 1-hour stale window (claude/pi/opencode/kimi
341
+ // all use STALE_MS). Previously codex used a divergent 10-minute default.
342
+ const threshold = opts.activeThresholdMs ?? STALE_MS;
308
343
  // P2-4: Use session index for direct file lookup + mtime check
309
344
  let index = getSessionIndex(codexHome);
310
345
  let entry = index.get(sessionId);
@@ -323,6 +358,11 @@ export function isCodexSessionActive(sessionId, opts = {}) {
323
358
  if (entry.isSubagent) {
324
359
  return false;
325
360
  }
361
+ // Cwd guard: when a cwd is provided, the session's working directory must
362
+ // match (align with claude/pi/opencode which validate cwd).
363
+ if (opts.cwd && entry.cwd !== opts.cwd) {
364
+ return false;
365
+ }
326
366
  // Re-stat to get fresh mtime (index may be up to INDEX_TTL_MS stale)
327
367
  try {
328
368
  const stat = fs.statSync(entry.filePath);
@@ -16,6 +16,6 @@ export declare class CodexSessionReader implements AgentSessionReader {
16
16
  readSessionContent(sessionId: string, cwd: string, opts?: {
17
17
  maxEvents?: number;
18
18
  }): SessionContent;
19
- isSessionActive(sessionId: string, _cwd: string): boolean;
19
+ isSessionActive(sessionId: string, cwd: string): boolean;
20
20
  }
21
21
  export {};
@@ -34,8 +34,8 @@ export class CodexSessionReader {
34
34
  cwd,
35
35
  });
36
36
  }
37
- isSessionActive(sessionId, _cwd) {
38
- return isCodexSessionActive(sessionId, { codexHome: this.codexHome });
37
+ isSessionActive(sessionId, cwd) {
38
+ return isCodexSessionActive(sessionId, { codexHome: this.codexHome, cwd });
39
39
  }
40
40
  }
41
41
  //# sourceMappingURL=sessions.js.map