@downcity/agent 1.1.216 → 1.1.222

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 (175) hide show
  1. package/bin/agent/local/Agent.d.ts +2 -0
  2. package/bin/agent/local/Agent.d.ts.map +1 -1
  3. package/bin/agent/local/Agent.js +6 -0
  4. package/bin/agent/local/Agent.js.map +1 -1
  5. package/bin/agent/local/services/AgentSessions.d.ts +6 -0
  6. package/bin/agent/local/services/AgentSessions.d.ts.map +1 -1
  7. package/bin/agent/local/services/AgentSessions.js +8 -1
  8. package/bin/agent/local/services/AgentSessions.js.map +1 -1
  9. package/bin/agent/remote/RemoteSession.d.ts +11 -7
  10. package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
  11. package/bin/agent/remote/RemoteSession.js +36 -11
  12. package/bin/agent/remote/RemoteSession.js.map +1 -1
  13. package/bin/agent/remote/RemoteTransport.d.ts +9 -3
  14. package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
  15. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +6 -2
  16. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
  17. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +38 -11
  18. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
  19. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +6 -2
  20. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
  21. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +8 -2
  22. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
  23. package/bin/executor/types/SessionHistoryMeta.d.ts +2 -0
  24. package/bin/executor/types/SessionHistoryMeta.d.ts.map +1 -1
  25. package/bin/executor/types/SessionRecords.d.ts +2 -0
  26. package/bin/executor/types/SessionRecords.d.ts.map +1 -1
  27. package/bin/executor/types/SessionRecords.js.map +1 -1
  28. package/bin/index.d.ts +5 -2
  29. package/bin/index.d.ts.map +1 -1
  30. package/bin/index.js +1 -0
  31. package/bin/index.js.map +1 -1
  32. package/bin/rpc/Client.d.ts +18 -4
  33. package/bin/rpc/Client.d.ts.map +1 -1
  34. package/bin/rpc/Client.js +25 -3
  35. package/bin/rpc/Client.js.map +1 -1
  36. package/bin/session/Session.d.ts +13 -3
  37. package/bin/session/Session.d.ts.map +1 -1
  38. package/bin/session/Session.js +48 -18
  39. package/bin/session/Session.js.map +1 -1
  40. package/bin/session/browse/Browse.d.ts.map +1 -1
  41. package/bin/session/browse/Browse.js +3 -0
  42. package/bin/session/browse/Browse.js.map +1 -1
  43. package/bin/session/recorder/JsonlSessionMessageStore.d.ts +45 -0
  44. package/bin/session/recorder/JsonlSessionMessageStore.d.ts.map +1 -0
  45. package/bin/session/recorder/JsonlSessionMessageStore.js +127 -0
  46. package/bin/session/recorder/JsonlSessionMessageStore.js.map +1 -0
  47. package/bin/session/recorder/SessionMessageCodec.d.ts +20 -0
  48. package/bin/session/recorder/SessionMessageCodec.d.ts.map +1 -0
  49. package/bin/session/recorder/SessionMessageCodec.js +252 -0
  50. package/bin/session/recorder/SessionMessageCodec.js.map +1 -0
  51. package/bin/session/recorder/SessionMessageReducer.d.ts +12 -0
  52. package/bin/session/recorder/SessionMessageReducer.d.ts.map +1 -0
  53. package/bin/session/recorder/SessionMessageReducer.js +109 -0
  54. package/bin/session/recorder/SessionMessageReducer.js.map +1 -0
  55. package/bin/session/recorder/SessionRecorder.d.ts +177 -0
  56. package/bin/session/recorder/SessionRecorder.d.ts.map +1 -0
  57. package/bin/session/recorder/SessionRecorder.js +610 -0
  58. package/bin/session/recorder/SessionRecorder.js.map +1 -0
  59. package/bin/session/recorder/SessionRecorderCompaction.d.ts +18 -0
  60. package/bin/session/recorder/SessionRecorderCompaction.d.ts.map +1 -0
  61. package/bin/session/recorder/SessionRecorderCompaction.js +141 -0
  62. package/bin/session/recorder/SessionRecorderCompaction.js.map +1 -0
  63. package/bin/session/recorder/SessionRecorderHistoryStore.d.ts +66 -0
  64. package/bin/session/recorder/SessionRecorderHistoryStore.d.ts.map +1 -0
  65. package/bin/session/recorder/SessionRecorderHistoryStore.js +195 -0
  66. package/bin/session/recorder/SessionRecorderHistoryStore.js.map +1 -0
  67. package/bin/session/runtime/SessionMessageEventHub.d.ts +15 -0
  68. package/bin/session/runtime/SessionMessageEventHub.d.ts.map +1 -0
  69. package/bin/session/runtime/SessionMessageEventHub.js +28 -0
  70. package/bin/session/runtime/SessionMessageEventHub.js.map +1 -0
  71. package/bin/session/runtime/SessionPromptRuntime.d.ts +7 -13
  72. package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
  73. package/bin/session/runtime/SessionPromptRuntime.js +13 -13
  74. package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
  75. package/bin/session/services/SessionStateService.d.ts +15 -3
  76. package/bin/session/services/SessionStateService.d.ts.map +1 -1
  77. package/bin/session/services/SessionStateService.js +165 -76
  78. package/bin/session/services/SessionStateService.js.map +1 -1
  79. package/bin/session/services/SessionTurnService.d.ts +4 -0
  80. package/bin/session/services/SessionTurnService.d.ts.map +1 -1
  81. package/bin/session/services/SessionTurnService.js +82 -52
  82. package/bin/session/services/SessionTurnService.js.map +1 -1
  83. package/bin/session/services/SessionViewService.d.ts +8 -3
  84. package/bin/session/services/SessionViewService.d.ts.map +1 -1
  85. package/bin/session/services/SessionViewService.js +13 -5
  86. package/bin/session/services/SessionViewService.js.map +1 -1
  87. package/bin/session/storage/Metadata.d.ts +2 -0
  88. package/bin/session/storage/Metadata.d.ts.map +1 -1
  89. package/bin/session/storage/Metadata.js +10 -0
  90. package/bin/session/storage/Metadata.js.map +1 -1
  91. package/bin/session/storage/Persistence.d.ts +6 -0
  92. package/bin/session/storage/Persistence.d.ts.map +1 -1
  93. package/bin/session/storage/Persistence.js +12 -0
  94. package/bin/session/storage/Persistence.js.map +1 -1
  95. package/bin/session/storage/RuntimeSessionPort.d.ts +3 -2
  96. package/bin/session/storage/RuntimeSessionPort.d.ts.map +1 -1
  97. package/bin/session/storage/RuntimeSessionPort.js.map +1 -1
  98. package/bin/types/agent/AgentOptions.d.ts +10 -0
  99. package/bin/types/agent/AgentOptions.d.ts.map +1 -1
  100. package/bin/types/agent/SessionActor.d.ts +12 -4
  101. package/bin/types/agent/SessionActor.d.ts.map +1 -1
  102. package/bin/types/agent/SessionTypes.d.ts +13 -1
  103. package/bin/types/agent/SessionTypes.d.ts.map +1 -1
  104. package/bin/types/rpc/RpcProtocol.d.ts +32 -6
  105. package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
  106. package/bin/types/runtime/agent/AgentContext.d.ts +4 -3
  107. package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
  108. package/bin/types/runtime/agent/AgentContext.js.map +1 -1
  109. package/bin/types/sdk/AgentSessionEvent.d.ts +2 -1
  110. package/bin/types/sdk/AgentSessionEvent.d.ts.map +1 -1
  111. package/bin/types/session/SessionMessage.d.ts +217 -0
  112. package/bin/types/session/SessionMessage.d.ts.map +1 -0
  113. package/bin/types/session/SessionMessage.js +8 -0
  114. package/bin/types/session/SessionMessage.js.map +1 -0
  115. package/bin/types/session/SessionMessageMutation.d.ts +92 -0
  116. package/bin/types/session/SessionMessageMutation.d.ts.map +1 -0
  117. package/bin/types/session/SessionMessageMutation.js +21 -0
  118. package/bin/types/session/SessionMessageMutation.js.map +1 -0
  119. package/bin/types/session/SessionOptions.d.ts +3 -0
  120. package/bin/types/session/SessionOptions.d.ts.map +1 -1
  121. package/package.json +3 -3
  122. package/scripts/agent-env.test.mjs +4 -4
  123. package/scripts/remote-session-reconnect.test.mjs +33 -0
  124. package/scripts/session-action-message.test.mjs +148 -27
  125. package/scripts/session-list-title.test.mjs +6 -19
  126. package/scripts/session-prompt-runtime.test.mjs +4 -16
  127. package/scripts/session-recorder.test.mjs +245 -0
  128. package/scripts/session-title-event.test.mjs +10 -18
  129. package/src/agent/local/Agent.ts +6 -0
  130. package/src/agent/local/services/AgentSessions.ts +14 -1
  131. package/src/agent/remote/RemoteSession.ts +52 -16
  132. package/src/agent/remote/RemoteTransport.ts +20 -6
  133. package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +67 -13
  134. package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +27 -6
  135. package/src/executor/types/SessionHistoryMeta.ts +2 -0
  136. package/src/executor/types/SessionRecords.ts +2 -0
  137. package/src/index.ts +23 -5
  138. package/src/rpc/Client.ts +47 -8
  139. package/src/session/Session.ts +66 -31
  140. package/src/session/browse/Browse.ts +3 -0
  141. package/src/session/recorder/JsonlSessionMessageStore.ts +162 -0
  142. package/src/session/recorder/SessionMessageCodec.ts +279 -0
  143. package/src/session/recorder/SessionMessageReducer.ts +155 -0
  144. package/src/session/recorder/SessionRecorder.ts +812 -0
  145. package/src/session/recorder/SessionRecorderCompaction.ts +187 -0
  146. package/src/session/recorder/SessionRecorderHistoryStore.ts +276 -0
  147. package/src/session/runtime/SessionMessageEventHub.ts +35 -0
  148. package/src/session/runtime/SessionPromptRuntime.ts +23 -24
  149. package/src/session/services/SessionStateService.ts +189 -84
  150. package/src/session/services/SessionTurnService.ts +88 -59
  151. package/src/session/services/SessionViewService.ts +28 -13
  152. package/src/session/storage/Metadata.ts +13 -0
  153. package/src/session/storage/Persistence.ts +18 -0
  154. package/src/session/storage/RuntimeSessionPort.ts +6 -4
  155. package/src/types/agent/AgentOptions.ts +12 -0
  156. package/src/types/agent/SessionActor.ts +28 -5
  157. package/src/types/agent/SessionTypes.ts +13 -1
  158. package/src/types/rpc/RpcProtocol.ts +34 -6
  159. package/src/types/runtime/agent/AgentContext.ts +8 -2
  160. package/src/types/sdk/AgentSessionEvent.ts +2 -0
  161. package/src/types/session/SessionMessage.ts +247 -0
  162. package/src/types/session/SessionMessageMutation.ts +121 -0
  163. package/src/types/session/SessionOptions.ts +4 -0
  164. package/tsconfig.tsbuildinfo +1 -1
  165. package/bin/session/SessionEventMapper.d.ts +0 -32
  166. package/bin/session/SessionEventMapper.d.ts.map +0 -1
  167. package/bin/session/SessionEventMapper.js +0 -131
  168. package/bin/session/SessionEventMapper.js.map +0 -1
  169. package/bin/types/sdk/InternalUiChunkEvent.d.ts +0 -100
  170. package/bin/types/sdk/InternalUiChunkEvent.d.ts.map +0 -1
  171. package/bin/types/sdk/InternalUiChunkEvent.js +0 -9
  172. package/bin/types/sdk/InternalUiChunkEvent.js.map +0 -1
  173. package/scripts/session-history-archive.test.mjs +0 -188
  174. package/src/session/SessionEventMapper.ts +0 -148
  175. package/src/types/sdk/InternalUiChunkEvent.ts +0 -108
@@ -15,12 +15,19 @@ import type {
15
15
  AgentArchiveSessionsResult,
16
16
  AgentCleanArchiveResult,
17
17
  AgentSessionForkInput,
18
- AgentSessionRecordsInput,
19
- AgentSessionRecordsPage,
20
18
  AgentSessionInfo,
21
19
  AgentSessionSummaryPage,
22
20
  AgentSessionSystemSnapshot,
21
+ AgentSessionSetInput,
23
22
  } from "@/types/agent/SessionTypes.js";
23
+ import type {
24
+ ListSessionMessagesInput,
25
+ SessionMessagePage,
26
+ } from "@/types/session/SessionMessage.js";
27
+ import type {
28
+ ListSessionMessageChangesInput,
29
+ SessionMessageMutationPage,
30
+ } from "@/types/session/SessionMessageMutation.js";
24
31
  import type {
25
32
  RemoteAgentPluginActionInput,
26
33
  RemoteAgentPluginActionResult,
@@ -60,6 +67,13 @@ export class RpcRemoteAgentTransport implements RemoteAgentTransport {
60
67
  return await this.client.get_session(session_id);
61
68
  }
62
69
 
70
+ async set(
71
+ session_id: string,
72
+ input: AgentSessionSetInput,
73
+ ): Promise<AgentSessionInfo> {
74
+ return await this.client.set_session({ session_id, input });
75
+ }
76
+
63
77
  async prompt(
64
78
  session_id: string,
65
79
  input: AgentSessionPromptInput,
@@ -93,16 +107,23 @@ export class RpcRemoteAgentTransport implements RemoteAgentTransport {
93
107
  };
94
108
  }
95
109
 
96
- async records(
110
+ async messages(
97
111
  session_id: string,
98
- input?: AgentSessionRecordsInput,
99
- ): Promise<AgentSessionRecordsPage> {
100
- return await this.client.get_session_records({
112
+ input?: ListSessionMessagesInput,
113
+ ): Promise<SessionMessagePage> {
114
+ return await this.client.get_session_messages({
101
115
  session_id,
102
116
  input,
103
117
  });
104
118
  }
105
119
 
120
+ async message_changes(
121
+ session_id: string,
122
+ input: ListSessionMessageChangesInput,
123
+ ): Promise<SessionMessageMutationPage> {
124
+ return await this.client.get_session_message_changes({ session_id, input });
125
+ }
126
+
106
127
  async system(session_id: string): Promise<AgentSessionSystemSnapshot> {
107
128
  return await this.client.get_session_system(session_id);
108
129
  }
@@ -23,6 +23,8 @@ export type SessionHistoryMetaV1 = {
23
23
  title?: string;
24
24
  /** 当前 session 绑定模型的可读标签。 */
25
25
  modelLabel?: string;
26
+ /** 当前 session 绑定模型的稳定 ID,用于进程重启后恢复模型实例。 */
27
+ modelId?: string;
26
28
  /** 当前 session 已持久化记录数量,用于列表查询避免扫描完整历史。 */
27
29
  messageCount?: number;
28
30
  /** 当前 session 最后一条记录的用户可见摘要,用于列表预览。 */
@@ -65,6 +65,8 @@ export type SessionMetadataV1 = {
65
65
  ts: number;
66
66
  /** 当前消息所属的 session ID。 */
67
67
  sessionId: string;
68
+ /** 当前消息所属 turn。 */
69
+ turnId?: string;
68
70
  /** 当前消息是普通消息还是摘要消息。 */
69
71
  kind?: SessionMessageKind;
70
72
  /** 当前消息来自入站、出站还是 compact。 */
package/src/index.ts CHANGED
@@ -27,9 +27,6 @@ export type {
27
27
  AgentListSessionsInput,
28
28
  AgentSessionConfigSnapshot,
29
29
  AgentSessionForkInput,
30
- AgentSessionRecordsInput,
31
- AgentSessionRecordsPage,
32
- AgentSessionRecordsView,
33
30
  AgentSessionInfo,
34
31
  AgentSessionSetInput,
35
32
  AgentSessionSummary,
@@ -38,8 +35,29 @@ export type {
38
35
  AgentSessionSystemBlockSource,
39
36
  AgentSessionSystemSessionInfo,
40
37
  AgentSessionSystemSnapshot,
41
- AgentSessionTimelineEvent,
42
38
  } from "./types/agent/SessionTypes.js";
39
+ export type {
40
+ ListSessionMessagesInput,
41
+ SessionActionMessage,
42
+ SessionAssistantMessage,
43
+ SessionAssistantMessagePart,
44
+ SessionAssistantToolPart,
45
+ SessionErrorMessage,
46
+ SessionMessage,
47
+ SessionMessagePage,
48
+ SessionUserMessage,
49
+ SessionUserMessagePart,
50
+ } from "./types/session/SessionMessage.js";
51
+ export {
52
+ is_session_message_mutation,
53
+ } from "./types/session/SessionMessageMutation.js";
54
+ export type {
55
+ ListSessionMessageChangesInput,
56
+ SessionMessageMutation,
57
+ SessionMessageMutationPage,
58
+ SessionMessageMutationSubscriber,
59
+ SessionMessageMutationUnsubscribe,
60
+ } from "./types/session/SessionMessageMutation.js";
43
61
  export type {
44
62
  AgentOptions,
45
63
  AgentSessionConstructor,
@@ -67,7 +85,7 @@ export type {
67
85
  AgentSessionActionRecord,
68
86
  AgentSessionActionState,
69
87
  } from "./types/sdk/AgentSessionAction.js";
70
- export type { AgentSessionPromptInput, SessionUserMessagePart } from "./types/sdk/AgentSessionPrompt.js";
88
+ export type { AgentSessionPromptInput } from "./types/sdk/AgentSessionPrompt.js";
71
89
  export type { AgentSessionStopResult } from "./types/sdk/AgentSessionStop.js";
72
90
  export type {
73
91
  AgentSessionTurnHandle,
package/src/rpc/Client.ts CHANGED
@@ -16,12 +16,19 @@ import type {
16
16
  AgentArchiveSessionResult,
17
17
  AgentArchiveSessionsResult,
18
18
  AgentCleanArchiveResult,
19
- AgentSessionRecordsInput,
20
- AgentSessionRecordsPage,
21
19
  AgentSessionInfo,
22
20
  AgentSessionSummaryPage,
23
21
  AgentSessionSystemSnapshot,
22
+ AgentSessionSetInput,
24
23
  } from "@/types/agent/SessionTypes.js";
24
+ import type {
25
+ ListSessionMessagesInput,
26
+ SessionMessagePage,
27
+ } from "@/types/session/SessionMessage.js";
28
+ import type {
29
+ ListSessionMessageChangesInput,
30
+ SessionMessageMutationPage,
31
+ } from "@/types/session/SessionMessageMutation.js";
25
32
  import type { JsonValue } from "@/types/common/Json.js";
26
33
  import type { JsonObject } from "@/types/common/Json.js";
27
34
  import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
@@ -168,6 +175,23 @@ export class RpcClient {
168
175
  return data.session;
169
176
  }
170
177
 
178
+ /** 按稳定模型 ID 更新远程 Session。 */
179
+ async set_session(params: {
180
+ /** 目标 session id。 */
181
+ session_id: string;
182
+ /** Session 配置更新输入。 */
183
+ input: AgentSessionSetInput;
184
+ }): Promise<AgentSessionInfo> {
185
+ const data = await this.request<{ session: AgentSessionInfo }>({
186
+ method: "sdk.sessions.set",
187
+ params: {
188
+ sessionId: params.session_id,
189
+ input: params.input,
190
+ },
191
+ });
192
+ return data.session;
193
+ }
194
+
171
195
  /**
172
196
  * 发送 prompt。
173
197
  */
@@ -201,18 +225,33 @@ export class RpcClient {
201
225
  /**
202
226
  * 读取 session records。
203
227
  */
204
- async get_session_records(params: {
228
+ async get_session_messages(params: {
229
+ session_id: string;
230
+ input?: ListSessionMessagesInput;
231
+ }): Promise<SessionMessagePage> {
232
+ const data = await this.request<{ messages: SessionMessagePage }>({
233
+ method: "sdk.sessions.messages",
234
+ params: {
235
+ sessionId: params.session_id,
236
+ input: params.input,
237
+ },
238
+ });
239
+ return data.messages;
240
+ }
241
+
242
+ /** 读取指定 cursor 后的 Session Message Mutation。 */
243
+ async get_session_message_changes(params: {
205
244
  session_id: string;
206
- input?: AgentSessionRecordsInput;
207
- }): Promise<AgentSessionRecordsPage> {
208
- const data = await this.request<{ records: AgentSessionRecordsPage }>({
209
- method: "sdk.sessions.records",
245
+ input: ListSessionMessageChangesInput;
246
+ }): Promise<SessionMessageMutationPage> {
247
+ const data = await this.request<{ changes: SessionMessageMutationPage }>({
248
+ method: "sdk.sessions.message_changes",
210
249
  params: {
211
250
  sessionId: params.session_id,
212
251
  input: params.input,
213
252
  },
214
253
  });
215
- return data.records;
254
+ return data.changes;
216
255
  }
217
256
 
218
257
  /**
@@ -10,12 +10,12 @@
10
10
  import { Executor } from "@executor/Executor.js";
11
11
  import type { Tool } from "ai";
12
12
  import { JsonlSessionHistoryComposer } from "@executor/composer/history/jsonl/JsonlSessionHistoryComposer.js";
13
- import { JsonlSessionHistoryStore } from "@/executor/store/history/jsonl/JsonlSessionHistoryStore.js";
13
+ import { SessionRecorderHistoryStore } from "@/session/recorder/SessionRecorderHistoryStore.js";
14
+ import { JsonlSessionMessageStore } from "@/session/recorder/JsonlSessionMessageStore.js";
15
+ import { SessionRecorder } from "@/session/recorder/SessionRecorder.js";
14
16
  import type {
15
17
  AgentSessionConfigSnapshot,
16
18
  AgentSessionForkInput,
17
- AgentSessionRecordsInput,
18
- AgentSessionRecordsPage,
19
19
  AgentSessionInfo,
20
20
  AgentSessionSetInput,
21
21
  AgentSessionSystemBlock,
@@ -23,9 +23,7 @@ import type {
23
23
  } from "@/types/agent/SessionTypes.js";
24
24
  import type { AgentSession } from "@/types/agent/SessionActor.js";
25
25
  import {
26
- getSdkAgentSessionArchiveDirPath,
27
26
  getSdkAgentSessionDirPath,
28
- getSdkAgentSessionInflightPath,
29
27
  } from "@/session/storage/Paths.js";
30
28
  import { resolveSystemTimezone } from "@/session/storage/Metadata.js";
31
29
  import { createRuntimeSessionPort } from "@/session/storage/RuntimeSessionPort.js";
@@ -35,10 +33,21 @@ import type {
35
33
  AgentSessionSubscriber,
36
34
  AgentSessionUnsubscribe,
37
35
  } from "@/types/sdk/AgentSessionEvent.js";
36
+ import type {
37
+ ListSessionMessageChangesInput,
38
+ SessionMessageMutationPage,
39
+ SessionMessageMutationSubscriber,
40
+ SessionMessageMutationUnsubscribe,
41
+ } from "@/types/session/SessionMessageMutation.js";
42
+ import type {
43
+ ListSessionMessagesInput,
44
+ SessionMessagePage,
45
+ } from "@/types/session/SessionMessage.js";
38
46
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
39
47
  import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
40
48
  import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
41
49
  import { SessionEventHub } from "@/session/runtime/SessionEventHub.js";
50
+ import { SessionMessageEventHub } from "@/session/runtime/SessionMessageEventHub.js";
42
51
  import { SessionStateService } from "@/session/services/SessionStateService.js";
43
52
  import { SessionTurnService } from "@/session/services/SessionTurnService.js";
44
53
  import { SessionViewService } from "@/session/services/SessionViewService.js";
@@ -69,10 +78,13 @@ export class Session implements AgentSession {
69
78
  private readonly getPluginSystemBlocks: SessionOptions["getPluginSystemBlocks"];
70
79
  private readonly ensureConfiguredHook?: SessionOptions["ensureConfigured"];
71
80
  private readonly composers?: SessionComposerOptions;
72
- private readonly historyStore: JsonlSessionHistoryStore;
81
+ private readonly historyStore: SessionRecorderHistoryStore;
82
+ private readonly messageStore: JsonlSessionMessageStore;
83
+ private readonly recorder: SessionRecorder;
73
84
  private readonly historyComposer: SessionHistoryComposer;
74
85
  private readonly executor: Executor;
75
86
  private readonly eventHub = new SessionEventHub();
87
+ private readonly messageEventHub = new SessionMessageEventHub();
76
88
  private readonly localState: SessionLocalState;
77
89
  private readonly stateService: SessionStateService;
78
90
  private readonly turnService: SessionTurnService;
@@ -100,6 +112,14 @@ export class Session implements AgentSession {
100
112
  throw new Error("Session requires a non-empty projectRoot");
101
113
  }
102
114
 
115
+ this.messageStore = this.create_message_store();
116
+ this.recorder = new SessionRecorder({
117
+ session_id: this.id,
118
+ store: this.messageStore,
119
+ publish: (mutation) => {
120
+ this.messageEventHub.publish(mutation);
121
+ },
122
+ });
103
123
  this.historyStore = this.create_history_store();
104
124
  this.localState = this.create_local_state();
105
125
  const composer_context = this.create_composer_context();
@@ -118,6 +138,7 @@ export class Session implements AgentSession {
118
138
  project_root: this.projectRoot,
119
139
  session_id: this.id,
120
140
  history_store: this.historyStore,
141
+ recorder: this.recorder,
121
142
  executor: this.executor,
122
143
  state: this.localState,
123
144
  logger: this.logger,
@@ -126,6 +147,7 @@ export class Session implements AgentSession {
126
147
  await this.ensureConfiguredHook?.(this);
127
148
  }
128
149
  : undefined,
150
+ ...(options.resolve_model ? { resolve_model: options.resolve_model } : {}),
129
151
  publish_event: (event) => {
130
152
  this.eventHub.publish(event);
131
153
  },
@@ -136,12 +158,14 @@ export class Session implements AgentSession {
136
158
  executor: this.executor,
137
159
  state_service: this.stateService,
138
160
  event_hub: this.eventHub,
161
+ recorder: this.recorder,
139
162
  });
140
163
  this.viewService = new SessionViewService<this>({
141
164
  agent_id: this.agentId,
142
165
  project_root: this.projectRoot,
143
166
  session_id: this.id,
144
167
  history_store: this.historyStore,
168
+ recorder: this.recorder,
145
169
  state_service: this.stateService,
146
170
  logger: this.logger,
147
171
  is_executing: () => this.isExecuting(),
@@ -157,6 +181,7 @@ export class Session implements AgentSession {
157
181
  return {
158
182
  session,
159
183
  history_store: session.historyStore,
184
+ recorder: session.recorder,
160
185
  state_service: session.stateService,
161
186
  };
162
187
  },
@@ -167,6 +192,7 @@ export class Session implements AgentSession {
167
192
  * 初始化当前 session。
168
193
  */
169
194
  async initialize(): Promise<this> {
195
+ await this.recorder.initialize();
170
196
  await this.stateService.initialize();
171
197
  return this;
172
198
  }
@@ -202,8 +228,20 @@ export class Session implements AgentSession {
202
228
  /**
203
229
  * 订阅当前 Session 的未来事件。
204
230
  */
205
- subscribe(subscriber: AgentSessionSubscriber): AgentSessionUnsubscribe {
206
- return this.turnService.subscribe(subscriber);
231
+ subscribe(
232
+ subscriber: SessionMessageMutationSubscriber,
233
+ ): SessionMessageMutationUnsubscribe {
234
+ return this.messageEventHub.subscribe(subscriber);
235
+ }
236
+
237
+ /** 订阅远程 transport 所需的 Message 与 lifecycle 事件。 */
238
+ subscribe_transport(subscriber: AgentSessionSubscriber): AgentSessionUnsubscribe {
239
+ const unsubscribe_message = this.messageEventHub.subscribe(subscriber);
240
+ const unsubscribe_lifecycle = this.turnService.subscribe(subscriber);
241
+ return () => {
242
+ unsubscribe_message();
243
+ unsubscribe_lifecycle();
244
+ };
207
245
  }
208
246
 
209
247
  /**
@@ -238,8 +276,15 @@ export class Session implements AgentSession {
238
276
  /**
239
277
  * 读取当前 session records 分页。
240
278
  */
241
- async records(input?: AgentSessionRecordsInput): Promise<AgentSessionRecordsPage> {
242
- return await this.viewService.records(input);
279
+ async messages(input?: ListSessionMessagesInput): Promise<SessionMessagePage> {
280
+ return await this.recorder.list_messages(input);
281
+ }
282
+
283
+ /** 读取当前 Session 的增量 Message Mutation。 */
284
+ async message_changes(
285
+ input: ListSessionMessageChangesInput,
286
+ ): Promise<SessionMessageMutationPage> {
287
+ return await this.recorder.list_message_changes(input);
243
288
  }
244
289
 
245
290
  /**
@@ -328,33 +373,23 @@ export class Session implements AgentSession {
328
373
  return new SessionClass(options) as this;
329
374
  }
330
375
 
331
- private create_history_store(): JsonlSessionHistoryStore {
376
+ private create_message_store(): JsonlSessionMessageStore {
332
377
  const session_dir_path = getSdkAgentSessionDirPath(
333
378
  this.projectRoot,
334
379
  this.agentId,
335
380
  this.id,
336
381
  );
337
382
  const messages_dir_path = `${session_dir_path}/messages`;
338
- return new JsonlSessionHistoryStore({
339
- rootPath: this.projectRoot,
340
- agentId: this.agentId,
341
- sessionId: this.id,
342
- paths: {
343
- sessionDirPath: session_dir_path,
344
- messagesDirPath: messages_dir_path,
345
- messagesFilePath: `${messages_dir_path}/messages.jsonl`,
346
- metaFilePath: `${messages_dir_path}/meta.json`,
347
- archiveDirPath: getSdkAgentSessionArchiveDirPath(
348
- this.projectRoot,
349
- this.agentId,
350
- this.id,
351
- ),
352
- inflightFilePath: getSdkAgentSessionInflightPath(
353
- this.projectRoot,
354
- this.agentId,
355
- this.id,
356
- ),
357
- },
383
+ return new JsonlSessionMessageStore({
384
+ session_id: this.id,
385
+ file_path: `${messages_dir_path}/messages.jsonl`,
386
+ });
387
+ }
388
+
389
+ private create_history_store(): SessionRecorderHistoryStore {
390
+ return new SessionRecorderHistoryStore({
391
+ session_id: this.id,
392
+ recorder: this.recorder,
358
393
  });
359
394
  }
360
395
 
@@ -453,6 +453,7 @@ export function buildSessionInfo(
453
453
  ...(input.metadata.modelLabel
454
454
  ? { modelLabel: input.metadata.modelLabel }
455
455
  : {}),
456
+ ...(input.metadata.modelId ? { modelId: input.metadata.modelId } : {}),
456
457
  ...(typeof input.metadata.timezone === "string" && input.metadata.timezone.trim()
457
458
  ? { timezone: input.metadata.timezone.trim() }
458
459
  : {}),
@@ -632,6 +633,7 @@ export async function listAgentSessionSummaryPage(params: {
632
633
  ...(typeof info.createdAt === "number" ? { createdAt: info.createdAt } : {}),
633
634
  ...(typeof info.updatedAt === "number" ? { updatedAt: info.updatedAt } : {}),
634
635
  ...(info.modelLabel ? { modelLabel: info.modelLabel } : {}),
636
+ ...(info.modelId ? { modelId: info.modelId } : {}),
635
637
  ...(info.executing ? { executing: true } : {}),
636
638
  };
637
639
 
@@ -732,6 +734,7 @@ export async function listArchivedAgentSessionSummaryPage(params: {
732
734
  ...(typeof info.createdAt === "number" ? { createdAt: info.createdAt } : {}),
733
735
  ...(typeof info.updatedAt === "number" ? { updatedAt: info.updatedAt } : {}),
734
736
  ...(info.modelLabel ? { modelLabel: info.modelLabel } : {}),
737
+ ...(info.modelId ? { modelId: info.modelId } : {}),
735
738
  };
736
739
 
737
740
  if (query) {
@@ -0,0 +1,162 @@
1
+ /**
2
+ * 基于单一 messages.jsonl 的 SessionMessage mutation store。
3
+ *
4
+ * 文件中每行是一条完整 mutation。消息快照通过 reducer 重放得到,不维护第二份文件。
5
+ */
6
+
7
+ import fs from "fs-extra";
8
+ import { open, readFile, stat } from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { generateId } from "@/utils/Id.js";
11
+ import { reduce_session_messages } from "@/session/recorder/SessionMessageReducer.js";
12
+ import type { SessionMessage } from "@/types/session/SessionMessage.js";
13
+ import type { SessionMessageMutation } from "@/types/session/SessionMessageMutation.js";
14
+
15
+ /** JSONL message store 构造参数。 */
16
+ export interface JsonlSessionMessageStoreOptions {
17
+ /** 当前 session 标识。 */
18
+ session_id: string;
19
+ /** canonical messages.jsonl 的绝对路径。 */
20
+ file_path: string;
21
+ }
22
+
23
+ /** Recorder 在锁内构造下一条 mutation 时可用的序列状态。 */
24
+ export interface SessionMessageCommitState {
25
+ /** 下一条 mutation 应使用的提交顺序。 */
26
+ commit_sequence: number;
27
+ /** 下一条新消息应使用的线性顺序。 */
28
+ message_sequence: number;
29
+ /** 当前已重放的线性消息列表。 */
30
+ messages: SessionMessage[];
31
+ }
32
+
33
+ /** 单一 JSONL mutation store。 */
34
+ export class JsonlSessionMessageStore {
35
+ readonly session_id: string;
36
+ private readonly messages_file_path: string;
37
+ private readonly lock_file_path: string;
38
+
39
+ constructor(options: JsonlSessionMessageStoreOptions) {
40
+ this.session_id = String(options.session_id || "").trim();
41
+ this.messages_file_path = path.resolve(options.file_path);
42
+ this.lock_file_path = `${this.messages_file_path}.lock`;
43
+ if (!this.session_id) {
44
+ throw new Error("JsonlSessionMessageStore requires session_id");
45
+ }
46
+ }
47
+
48
+ /** 创建空文件并校验已有 Mutation 可重放。 */
49
+ async initialize(): Promise<void> {
50
+ await this.ensure_layout();
51
+ reduce_session_messages(await this.read_mutations_unsafe());
52
+ }
53
+
54
+ /** 读取全部合法 mutation;损坏行会带行号抛错,避免静默产生错误历史。 */
55
+ async list_mutations(): Promise<SessionMessageMutation[]> {
56
+ await this.ensure_layout();
57
+ return await this.read_mutations_unsafe();
58
+ }
59
+
60
+ /** 重放全部 mutation,得到当前 canonical 消息列表。 */
61
+ async list_messages(): Promise<SessionMessage[]> {
62
+ return reduce_session_messages(await this.list_mutations());
63
+ }
64
+
65
+ /**
66
+ * 在跨进程文件锁内计算并追加 mutation。
67
+ * 发布动作必须由调用方在此 Promise 成功后执行。
68
+ */
69
+ async commit(
70
+ build_mutation: (state: SessionMessageCommitState) => SessionMessageMutation,
71
+ ): Promise<SessionMessageMutation> {
72
+ return await this.with_write_lock(async () => {
73
+ const mutations = await this.read_mutations_unsafe();
74
+ const messages = reduce_session_messages(mutations);
75
+ const mutation = build_mutation({
76
+ commit_sequence:
77
+ (mutations[mutations.length - 1]?.commit_sequence || 0) + 1,
78
+ message_sequence:
79
+ messages.reduce(
80
+ (value, message) => Math.max(value, message.sequence + 1),
81
+ 1,
82
+ ),
83
+ messages,
84
+ });
85
+ await fs.appendFile(
86
+ this.messages_file_path,
87
+ `${JSON.stringify(mutation)}\n`,
88
+ "utf8",
89
+ );
90
+ return mutation;
91
+ });
92
+ }
93
+
94
+ private async ensure_layout(): Promise<void> {
95
+ await fs.ensureDir(path.dirname(this.messages_file_path));
96
+ await fs.ensureFile(this.messages_file_path);
97
+ }
98
+
99
+ private async read_mutations_unsafe(): Promise<SessionMessageMutation[]> {
100
+ const raw = await fs.readFile(this.messages_file_path, "utf8").catch(() => "");
101
+ const mutations: SessionMessageMutation[] = [];
102
+ const lines = raw.split("\n");
103
+ for (let index = 0; index < lines.length; index += 1) {
104
+ const line = lines[index]?.trim();
105
+ if (!line) continue;
106
+ try {
107
+ const mutation = JSON.parse(line) as SessionMessageMutation;
108
+ if (mutation.session_id !== this.session_id) {
109
+ throw new Error("session_id mismatch");
110
+ }
111
+ mutations.push(mutation);
112
+ } catch (error) {
113
+ throw new Error(
114
+ `Invalid session mutation at line ${String(index + 1)}: ${error instanceof Error ? error.message : String(error)}`,
115
+ );
116
+ }
117
+ }
118
+ return mutations;
119
+ }
120
+
121
+ private async with_write_lock<T>(callback: () => Promise<T>): Promise<T> {
122
+ await this.ensure_layout();
123
+ const token = `${process.pid}:${Date.now()}:${generateId()}`;
124
+ const stale_ms = 30_000;
125
+ const started_at = Date.now();
126
+ while (true) {
127
+ try {
128
+ const file = await open(this.lock_file_path, "wx");
129
+ await file.writeFile(token, "utf8");
130
+ await file.close();
131
+ break;
132
+ } catch (error) {
133
+ const code = (error as NodeJS.ErrnoException).code;
134
+ if (code !== "EEXIST") throw error;
135
+ try {
136
+ const lock_stat = await stat(this.lock_file_path);
137
+ if (Date.now() - lock_stat.mtimeMs > stale_ms) {
138
+ await fs.remove(this.lock_file_path);
139
+ continue;
140
+ }
141
+ } catch {
142
+ continue;
143
+ }
144
+ if (Date.now() - started_at > stale_ms * 2) {
145
+ throw new Error(`Session message lock timeout: ${this.lock_file_path}`);
146
+ }
147
+ await new Promise((resolve) => setTimeout(resolve, 40));
148
+ }
149
+ }
150
+ try {
151
+ return await callback();
152
+ } finally {
153
+ try {
154
+ if ((await readFile(this.lock_file_path, "utf8")).trim() === token) {
155
+ await fs.remove(this.lock_file_path);
156
+ }
157
+ } catch {
158
+ // 锁文件已被清理时无需额外处理。
159
+ }
160
+ }
161
+ }
162
+ }