@downcity/agent 1.1.217 → 1.1.224

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 (184) hide show
  1. package/bin/agent/local/Agent.d.ts +1 -34
  2. package/bin/agent/local/Agent.d.ts.map +1 -1
  3. package/bin/agent/local/Agent.js +1 -44
  4. package/bin/agent/local/Agent.js.map +1 -1
  5. package/bin/agent/local/services/AgentAssemblyService.d.ts.map +1 -1
  6. package/bin/agent/local/services/AgentAssemblyService.js +35 -2
  7. package/bin/agent/local/services/AgentAssemblyService.js.map +1 -1
  8. package/bin/agent/local/services/AgentSessions.d.ts +6 -0
  9. package/bin/agent/local/services/AgentSessions.d.ts.map +1 -1
  10. package/bin/agent/local/services/AgentSessions.js +43 -4
  11. package/bin/agent/local/services/AgentSessions.js.map +1 -1
  12. package/bin/agent/remote/RemoteAgent.d.ts +1 -35
  13. package/bin/agent/remote/RemoteAgent.d.ts.map +1 -1
  14. package/bin/agent/remote/RemoteAgent.js +1 -37
  15. package/bin/agent/remote/RemoteAgent.js.map +1 -1
  16. package/bin/agent/remote/RemoteSession.d.ts +26 -41
  17. package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
  18. package/bin/agent/remote/RemoteSession.js +75 -107
  19. package/bin/agent/remote/RemoteSession.js.map +1 -1
  20. package/bin/agent/remote/RemoteTransport.d.ts +15 -27
  21. package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
  22. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +10 -21
  23. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
  24. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +24 -46
  25. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
  26. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +10 -20
  27. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
  28. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +10 -16
  29. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
  30. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js +2 -2
  31. package/bin/executor/store/history/jsonl/JsonlSessionHistoryStore.js.map +1 -1
  32. package/bin/executor/types/SessionHistoryPaths.d.ts +1 -1
  33. package/bin/executor/types/SessionRecords.d.ts +2 -0
  34. package/bin/executor/types/SessionRecords.d.ts.map +1 -1
  35. package/bin/executor/types/SessionRecords.js.map +1 -1
  36. package/bin/index.d.ts +6 -3
  37. package/bin/index.d.ts.map +1 -1
  38. package/bin/index.js +1 -0
  39. package/bin/index.js.map +1 -1
  40. package/bin/rpc/Client.d.ts +12 -34
  41. package/bin/rpc/Client.d.ts.map +1 -1
  42. package/bin/rpc/Client.js +31 -70
  43. package/bin/rpc/Client.js.map +1 -1
  44. package/bin/session/Session.d.ts +22 -4
  45. package/bin/session/Session.d.ts.map +1 -1
  46. package/bin/session/Session.js +95 -21
  47. package/bin/session/Session.js.map +1 -1
  48. package/bin/session/browse/Browse.d.ts.map +1 -1
  49. package/bin/session/browse/Browse.js +49 -18
  50. package/bin/session/browse/Browse.js.map +1 -1
  51. package/bin/session/recorder/JsonlSessionMessageStore.d.ts +51 -0
  52. package/bin/session/recorder/JsonlSessionMessageStore.d.ts.map +1 -0
  53. package/bin/session/recorder/JsonlSessionMessageStore.js +231 -0
  54. package/bin/session/recorder/JsonlSessionMessageStore.js.map +1 -0
  55. package/bin/session/recorder/SessionMessageCodec.d.ts +20 -0
  56. package/bin/session/recorder/SessionMessageCodec.d.ts.map +1 -0
  57. package/bin/session/recorder/SessionMessageCodec.js +257 -0
  58. package/bin/session/recorder/SessionMessageCodec.js.map +1 -0
  59. package/bin/session/recorder/SessionRecorder.d.ts +186 -0
  60. package/bin/session/recorder/SessionRecorder.d.ts.map +1 -0
  61. package/bin/session/recorder/SessionRecorder.js +655 -0
  62. package/bin/session/recorder/SessionRecorder.js.map +1 -0
  63. package/bin/session/recorder/SessionRecorderCompaction.d.ts +18 -0
  64. package/bin/session/recorder/SessionRecorderCompaction.d.ts.map +1 -0
  65. package/bin/session/recorder/SessionRecorderCompaction.js +142 -0
  66. package/bin/session/recorder/SessionRecorderCompaction.js.map +1 -0
  67. package/bin/session/recorder/SessionRecorderHistoryStore.d.ts +66 -0
  68. package/bin/session/recorder/SessionRecorderHistoryStore.d.ts.map +1 -0
  69. package/bin/session/recorder/SessionRecorderHistoryStore.js +195 -0
  70. package/bin/session/recorder/SessionRecorderHistoryStore.js.map +1 -0
  71. package/bin/session/runtime/SessionEventHub.d.ts +12 -5
  72. package/bin/session/runtime/SessionEventHub.d.ts.map +1 -1
  73. package/bin/session/runtime/SessionEventHub.js +11 -6
  74. package/bin/session/runtime/SessionEventHub.js.map +1 -1
  75. package/bin/session/runtime/SessionPromptRuntime.d.ts +9 -15
  76. package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
  77. package/bin/session/runtime/SessionPromptRuntime.js +48 -30
  78. package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
  79. package/bin/session/services/SessionStateService.d.ts +9 -5
  80. package/bin/session/services/SessionStateService.d.ts.map +1 -1
  81. package/bin/session/services/SessionStateService.js +138 -74
  82. package/bin/session/services/SessionStateService.js.map +1 -1
  83. package/bin/session/services/SessionTurnService.d.ts +6 -7
  84. package/bin/session/services/SessionTurnService.d.ts.map +1 -1
  85. package/bin/session/services/SessionTurnService.js +84 -60
  86. package/bin/session/services/SessionTurnService.js.map +1 -1
  87. package/bin/session/services/SessionViewService.d.ts +8 -3
  88. package/bin/session/services/SessionViewService.d.ts.map +1 -1
  89. package/bin/session/services/SessionViewService.js +10 -5
  90. package/bin/session/services/SessionViewService.js.map +1 -1
  91. package/bin/session/storage/Paths.d.ts +1 -1
  92. package/bin/session/storage/Paths.d.ts.map +1 -1
  93. package/bin/session/storage/Paths.js +2 -2
  94. package/bin/session/storage/Paths.js.map +1 -1
  95. package/bin/session/storage/Persistence.d.ts +6 -0
  96. package/bin/session/storage/Persistence.d.ts.map +1 -1
  97. package/bin/session/storage/Persistence.js +7 -0
  98. package/bin/session/storage/Persistence.js.map +1 -1
  99. package/bin/session/storage/RuntimeSessionPort.d.ts +3 -3
  100. package/bin/session/storage/RuntimeSessionPort.d.ts.map +1 -1
  101. package/bin/session/storage/RuntimeSessionPort.js +2 -2
  102. package/bin/session/storage/RuntimeSessionPort.js.map +1 -1
  103. package/bin/types/agent/SessionActor.d.ts +15 -5
  104. package/bin/types/agent/SessionActor.d.ts.map +1 -1
  105. package/bin/types/rpc/RpcProtocol.d.ts +48 -61
  106. package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
  107. package/bin/types/runtime/agent/AgentContext.d.ts +5 -4
  108. package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
  109. package/bin/types/runtime/agent/AgentContext.js.map +1 -1
  110. package/bin/types/session/SessionApproval.d.ts +97 -0
  111. package/bin/types/session/SessionApproval.d.ts.map +1 -0
  112. package/bin/types/session/SessionApproval.js +7 -0
  113. package/bin/types/session/SessionApproval.js.map +1 -0
  114. package/bin/types/session/SessionMessage.d.ts +223 -0
  115. package/bin/types/session/SessionMessage.d.ts.map +1 -0
  116. package/bin/types/session/SessionMessage.js +8 -0
  117. package/bin/types/session/SessionMessage.js.map +1 -0
  118. package/bin/types/session/SessionMutation.d.ts +116 -0
  119. package/bin/types/session/SessionMutation.d.ts.map +1 -0
  120. package/bin/types/session/SessionMutation.js +19 -0
  121. package/bin/types/session/SessionMutation.js.map +1 -0
  122. package/bin/types/session/SessionOptions.d.ts +9 -0
  123. package/bin/types/session/SessionOptions.d.ts.map +1 -1
  124. package/package.json +3 -3
  125. package/scripts/agent-env.test.mjs +4 -4
  126. package/scripts/remote-session-reconnect.test.mjs +7 -3
  127. package/scripts/session-action-message.test.mjs +98 -27
  128. package/scripts/session-history-inflight.test.mjs +2 -2
  129. package/scripts/session-list-title.test.mjs +6 -19
  130. package/scripts/session-prompt-runtime.test.mjs +7 -19
  131. package/scripts/session-recorder.test.mjs +199 -0
  132. package/scripts/session-title-event.test.mjs +10 -18
  133. package/src/agent/local/Agent.ts +1 -55
  134. package/src/agent/local/services/AgentAssemblyService.ts +39 -3
  135. package/src/agent/local/services/AgentSessions.ts +48 -3
  136. package/src/agent/remote/RemoteAgent.ts +1 -54
  137. package/src/agent/remote/RemoteSession.ts +106 -136
  138. package/src/agent/remote/RemoteTransport.ts +30 -30
  139. package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +52 -77
  140. package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +30 -34
  141. package/src/executor/store/history/jsonl/JsonlSessionHistoryStore.ts +2 -2
  142. package/src/executor/types/SessionHistoryPaths.ts +1 -1
  143. package/src/executor/types/SessionRecords.ts +2 -0
  144. package/src/index.ts +36 -10
  145. package/src/rpc/Client.ts +58 -95
  146. package/src/session/Session.ts +128 -37
  147. package/src/session/browse/Browse.ts +51 -22
  148. package/src/session/recorder/JsonlSessionMessageStore.ts +246 -0
  149. package/src/session/recorder/SessionMessageCodec.ts +284 -0
  150. package/src/session/recorder/SessionRecorder.ts +871 -0
  151. package/src/session/recorder/SessionRecorderCompaction.ts +188 -0
  152. package/src/session/recorder/SessionRecorderHistoryStore.ts +276 -0
  153. package/src/session/runtime/SessionEventHub.ts +25 -12
  154. package/src/session/runtime/SessionPromptRuntime.ts +60 -43
  155. package/src/session/services/SessionStateService.ts +159 -86
  156. package/src/session/services/SessionTurnService.ts +91 -73
  157. package/src/session/services/SessionViewService.ts +25 -13
  158. package/src/session/storage/Paths.ts +2 -2
  159. package/src/session/storage/Persistence.ts +13 -0
  160. package/src/session/storage/RuntimeSessionPort.ts +8 -9
  161. package/src/types/agent/SessionActor.ts +31 -8
  162. package/src/types/rpc/RpcProtocol.ts +44 -67
  163. package/src/types/runtime/agent/AgentContext.ts +9 -7
  164. package/src/types/session/SessionApproval.ts +108 -0
  165. package/src/types/session/SessionMessage.ts +253 -0
  166. package/src/types/session/SessionMutation.ts +141 -0
  167. package/src/types/session/SessionOptions.ts +25 -0
  168. package/tsconfig.tsbuildinfo +1 -1
  169. package/bin/session/SessionEventMapper.d.ts +0 -32
  170. package/bin/session/SessionEventMapper.d.ts.map +0 -1
  171. package/bin/session/SessionEventMapper.js +0 -131
  172. package/bin/session/SessionEventMapper.js.map +0 -1
  173. package/bin/types/sdk/AgentSessionEvent.d.ts +0 -302
  174. package/bin/types/sdk/AgentSessionEvent.d.ts.map +0 -1
  175. package/bin/types/sdk/AgentSessionEvent.js +0 -9
  176. package/bin/types/sdk/AgentSessionEvent.js.map +0 -1
  177. package/bin/types/sdk/InternalUiChunkEvent.d.ts +0 -100
  178. package/bin/types/sdk/InternalUiChunkEvent.d.ts.map +0 -1
  179. package/bin/types/sdk/InternalUiChunkEvent.js +0 -9
  180. package/bin/types/sdk/InternalUiChunkEvent.js.map +0 -1
  181. package/scripts/session-history-archive.test.mjs +0 -188
  182. package/src/session/SessionEventMapper.ts +0 -148
  183. package/src/types/sdk/AgentSessionEvent.ts +0 -370
  184. package/src/types/sdk/InternalUiChunkEvent.ts +0 -108
package/src/rpc/Client.ts CHANGED
@@ -16,13 +16,15 @@ import type {
16
16
  AgentArchiveSessionResult,
17
17
  AgentArchiveSessionsResult,
18
18
  AgentCleanArchiveResult,
19
- AgentSessionRecordsInput,
20
- AgentSessionRecordsPage,
21
19
  AgentSessionInfo,
22
20
  AgentSessionSummaryPage,
23
21
  AgentSessionSystemSnapshot,
24
22
  AgentSessionSetInput,
25
23
  } from "@/types/agent/SessionTypes.js";
24
+ import type {
25
+ ListSessionMessagesInput,
26
+ SessionMessagePage,
27
+ } from "@/types/session/SessionMessage.js";
26
28
  import type { JsonValue } from "@/types/common/Json.js";
27
29
  import type { JsonObject } from "@/types/common/Json.js";
28
30
  import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
@@ -36,7 +38,14 @@ import type {
36
38
  PluginControlResult,
37
39
  PluginSnapshot,
38
40
  } from "@/types/plugin/PluginState.js";
39
- import type { AgentSessionEvent } from "@/types/sdk/AgentSessionEvent.js";
41
+ import type { SessionMutation } from "@/types/session/SessionMutation.js";
42
+ import type {
43
+ ResolveSessionApprovalInput,
44
+ SessionApproval,
45
+ SessionApprovalModeSnapshot,
46
+ SessionApprovalResult,
47
+ SetSessionApprovalModeInput,
48
+ } from "@/types/session/SessionApproval.js";
40
49
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
41
50
  import type {
42
51
  RpcClientEndpoint,
@@ -45,14 +54,6 @@ import type {
45
54
  RpcSessionSubscription,
46
55
  RpcSystemPromptPayload,
47
56
  } from "@/types/rpc/RpcProtocol.js";
48
- import type {
49
- ShellApprovalMode,
50
- ShellApprovalDecisionResult,
51
- ShellApprovalModeUpdateResult,
52
- ShellApprovalModeOption,
53
- ShellSessionApprovalModeView,
54
- ShellApprovalView,
55
- } from "@downcity/shell";
56
57
 
57
58
  export type {
58
59
  RpcClientEndpoint,
@@ -67,7 +68,7 @@ type PendingRequest = {
67
68
 
68
69
  type RpcSubscription = {
69
70
  on_ready: () => void;
70
- on_event: (event: AgentSessionEvent) => void;
71
+ on_event: (event: SessionMutation) => void;
71
72
  /** 底层 RPC 连接结束后的通知。 */
72
73
  on_close: (error?: unknown) => void;
73
74
  };
@@ -219,18 +220,18 @@ export class RpcClient {
219
220
  /**
220
221
  * 读取 session records。
221
222
  */
222
- async get_session_records(params: {
223
+ async get_session_messages(params: {
223
224
  session_id: string;
224
- input?: AgentSessionRecordsInput;
225
- }): Promise<AgentSessionRecordsPage> {
226
- const data = await this.request<{ records: AgentSessionRecordsPage }>({
227
- method: "sdk.sessions.records",
225
+ input?: ListSessionMessagesInput;
226
+ }): Promise<SessionMessagePage> {
227
+ const data = await this.request<{ messages: SessionMessagePage }>({
228
+ method: "sdk.sessions.messages",
228
229
  params: {
229
230
  sessionId: params.session_id,
230
231
  input: params.input,
231
232
  },
232
233
  });
233
- return data.records;
234
+ return data.messages;
234
235
  }
235
236
 
236
237
  /**
@@ -246,6 +247,44 @@ export class RpcClient {
246
247
  return data.system;
247
248
  }
248
249
 
250
+ async get_session_approvals(session_id: string): Promise<SessionApproval[]> {
251
+ const data = await this.request<{ approvals: SessionApproval[] }>({
252
+ method: "sdk.sessions.approvals",
253
+ params: { sessionId: session_id },
254
+ });
255
+ return data.approvals;
256
+ }
257
+
258
+ async get_session_approval_mode(session_id: string): Promise<SessionApprovalModeSnapshot> {
259
+ const data = await this.request<{ approval_mode: SessionApprovalModeSnapshot }>({
260
+ method: "sdk.sessions.approvalMode",
261
+ params: { sessionId: session_id },
262
+ });
263
+ return data.approval_mode;
264
+ }
265
+
266
+ async set_session_approval_mode(
267
+ session_id: string,
268
+ input: SetSessionApprovalModeInput,
269
+ ): Promise<SessionApprovalModeSnapshot> {
270
+ const data = await this.request<{ approval_mode: SessionApprovalModeSnapshot }>({
271
+ method: "sdk.sessions.setApprovalMode",
272
+ params: { sessionId: session_id, input },
273
+ });
274
+ return data.approval_mode;
275
+ }
276
+
277
+ async resolve_session_approval(
278
+ session_id: string,
279
+ input: ResolveSessionApprovalInput,
280
+ ): Promise<SessionApprovalResult> {
281
+ const data = await this.request<{ result: SessionApprovalResult }>({
282
+ method: "sdk.sessions.resolveApproval",
283
+ params: { sessionId: session_id, input },
284
+ });
285
+ return data.result;
286
+ }
287
+
249
288
  /**
250
289
  * 分叉 session。
251
290
  */
@@ -269,7 +308,7 @@ export class RpcClient {
269
308
  async subscribe_session(params: {
270
309
  session_id: string;
271
310
  on_ready: () => void;
272
- on_event: (event: AgentSessionEvent) => void;
311
+ on_event: (event: SessionMutation) => void;
273
312
  on_close: (error?: unknown) => void;
274
313
  }): Promise<RpcSessionSubscription> {
275
314
  const data = await this.request<{ subscriptionId: string }>({
@@ -450,78 +489,6 @@ export class RpcClient {
450
489
  });
451
490
  }
452
491
 
453
- /**
454
- * 列出 shell approvals。
455
- */
456
- async list_shell_approvals(): Promise<ShellApprovalView[]> {
457
- const data = await this.request<{ approvals: ShellApprovalView[] }>({
458
- method: "internal.shell.approvals",
459
- });
460
- return Array.isArray(data.approvals) ? data.approvals : [];
461
- }
462
-
463
- /**
464
- * 列出 shell approval 模式。
465
- */
466
- async list_shell_approval_modes(): Promise<ShellApprovalModeOption[]> {
467
- const data = await this.request<{ modes: ShellApprovalModeOption[] }>({
468
- method: "internal.shell.approvalModes",
469
- });
470
- return Array.isArray(data.modes) ? data.modes : [];
471
- }
472
-
473
- /**
474
- * 读取 shell approval 模式。
475
- */
476
- async get_shell_approval_mode(session_id: string): Promise<ShellSessionApprovalModeView> {
477
- return await this.request<ShellSessionApprovalModeView>({
478
- method: "internal.shell.approvalMode",
479
- params: {
480
- sessionId: session_id,
481
- },
482
- });
483
- }
484
-
485
- /**
486
- * 设置 shell approval 模式。
487
- */
488
- async set_shell_approval_mode(input: {
489
- session_id: string;
490
- mode: ShellApprovalMode;
491
- }): Promise<ShellApprovalModeUpdateResult> {
492
- return await this.request<ShellApprovalModeUpdateResult>({
493
- method: "internal.shell.setApprovalMode",
494
- params: {
495
- sessionId: input.session_id,
496
- mode: input.mode,
497
- },
498
- });
499
- }
500
-
501
- /**
502
- * 批准 shell approval。
503
- */
504
- async approve_shell_approval(approval_id: string): Promise<ShellApprovalDecisionResult> {
505
- return await this.request<ShellApprovalDecisionResult>({
506
- method: "internal.shell.approve",
507
- params: {
508
- approvalId: approval_id,
509
- },
510
- });
511
- }
512
-
513
- /**
514
- * 拒绝 shell approval。
515
- */
516
- async deny_shell_approval(approval_id: string): Promise<ShellApprovalDecisionResult> {
517
- return await this.request<ShellApprovalDecisionResult>({
518
- method: "internal.shell.deny",
519
- params: {
520
- approvalId: approval_id,
521
- },
522
- });
523
- }
524
-
525
492
  /**
526
493
  * 关闭底层连接。
527
494
  */
@@ -669,10 +636,6 @@ export class RpcClient {
669
636
 
670
637
  private fail_all_subscriptions(message: string): void {
671
638
  for (const subscription of this.subscriptions.values()) {
672
- subscription.on_event({
673
- type: "error",
674
- message,
675
- });
676
639
  subscription.on_close(new Error(message));
677
640
  }
678
641
  this.subscriptions.clear();
@@ -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,18 +23,30 @@ import type {
23
23
  } from "@/types/agent/SessionTypes.js";
24
24
  import type { AgentSession } from "@/types/agent/SessionActor.js";
25
25
  import {
26
- getSdkAgentSessionArchiveDirPath,
26
+ getSdkAgentSessionAssistantMessagePath,
27
27
  getSdkAgentSessionDirPath,
28
- getSdkAgentSessionInflightPath,
29
28
  } from "@/session/storage/Paths.js";
30
29
  import { resolveSystemTimezone } from "@/session/storage/Metadata.js";
31
30
  import { createRuntimeSessionPort } from "@/session/storage/RuntimeSessionPort.js";
32
31
  import { SessionSystemBuilder } from "@/session/SessionSystemBuilder.js";
33
32
  import type { SessionPort } from "@/types/runtime/agent/AgentContext.js";
34
33
  import type {
35
- AgentSessionSubscriber,
36
- AgentSessionUnsubscribe,
37
- } from "@/types/sdk/AgentSessionEvent.js";
34
+ SessionMutation,
35
+ SessionMutationSubscriber,
36
+ SessionMutationUnsubscribe,
37
+ } from "@/types/session/SessionMutation.js";
38
+ import type {
39
+ ResolveSessionApprovalInput,
40
+ SessionApproval,
41
+ SessionApprovalModeSnapshot,
42
+ SessionApprovalResult,
43
+ SetSessionApprovalModeInput,
44
+ SessionApprovalRuntimeEvent,
45
+ } from "@/types/session/SessionApproval.js";
46
+ import type {
47
+ ListSessionMessagesInput,
48
+ SessionMessagePage,
49
+ } from "@/types/session/SessionMessage.js";
38
50
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
39
51
  import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
40
52
  import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
@@ -69,10 +81,16 @@ export class Session implements AgentSession {
69
81
  private readonly getPluginSystemBlocks: SessionOptions["getPluginSystemBlocks"];
70
82
  private readonly ensureConfiguredHook?: SessionOptions["ensureConfigured"];
71
83
  private readonly composers?: SessionComposerOptions;
72
- private readonly historyStore: JsonlSessionHistoryStore;
84
+ private readonly historyStore: SessionRecorderHistoryStore;
85
+ private readonly messageStore: JsonlSessionMessageStore;
86
+ private readonly recorder: SessionRecorder;
73
87
  private readonly historyComposer: SessionHistoryComposer;
74
88
  private readonly executor: Executor;
75
- private readonly eventHub = new SessionEventHub();
89
+ private readonly eventHub: SessionEventHub;
90
+ private readonly list_approvals: SessionOptions["list_approvals"];
91
+ private readonly get_approval_mode: SessionOptions["get_approval_mode"];
92
+ private readonly set_approval_mode_hook: SessionOptions["set_approval_mode"];
93
+ private readonly resolve_approval_hook: SessionOptions["resolve_approval"];
76
94
  private readonly localState: SessionLocalState;
77
95
  private readonly stateService: SessionStateService;
78
96
  private readonly turnService: SessionTurnService;
@@ -90,6 +108,10 @@ export class Session implements AgentSession {
90
108
  this.getPluginSystemBlocks = options.getPluginSystemBlocks;
91
109
  this.ensureConfiguredHook = options.ensureConfigured;
92
110
  this.composers = options.composers;
111
+ this.list_approvals = options.list_approvals;
112
+ this.get_approval_mode = options.get_approval_mode;
113
+ this.set_approval_mode_hook = options.set_approval_mode;
114
+ this.resolve_approval_hook = options.resolve_approval;
93
115
  if (!this.id) {
94
116
  throw new Error("Session requires a non-empty sessionId");
95
117
  }
@@ -100,6 +122,22 @@ export class Session implements AgentSession {
100
122
  throw new Error("Session requires a non-empty projectRoot");
101
123
  }
102
124
 
125
+ this.eventHub = new SessionEventHub({
126
+ create_reply: (mutation) => ({
127
+ approval: async ({ decision }) => {
128
+ const approval_id = require_approval_id(mutation);
129
+ return await this.resolve_approval({ approval_id, decision });
130
+ },
131
+ }),
132
+ });
133
+ this.messageStore = this.create_message_store();
134
+ this.recorder = new SessionRecorder({
135
+ session_id: this.id,
136
+ store: this.messageStore,
137
+ publish: (mutation) => {
138
+ this.eventHub.publish(mutation);
139
+ },
140
+ });
103
141
  this.historyStore = this.create_history_store();
104
142
  this.localState = this.create_local_state();
105
143
  const composer_context = this.create_composer_context();
@@ -118,6 +156,7 @@ export class Session implements AgentSession {
118
156
  project_root: this.projectRoot,
119
157
  session_id: this.id,
120
158
  history_store: this.historyStore,
159
+ recorder: this.recorder,
121
160
  executor: this.executor,
122
161
  state: this.localState,
123
162
  logger: this.logger,
@@ -137,12 +176,14 @@ export class Session implements AgentSession {
137
176
  executor: this.executor,
138
177
  state_service: this.stateService,
139
178
  event_hub: this.eventHub,
179
+ recorder: this.recorder,
140
180
  });
141
181
  this.viewService = new SessionViewService<this>({
142
182
  agent_id: this.agentId,
143
183
  project_root: this.projectRoot,
144
184
  session_id: this.id,
145
185
  history_store: this.historyStore,
186
+ recorder: this.recorder,
146
187
  state_service: this.stateService,
147
188
  logger: this.logger,
148
189
  is_executing: () => this.isExecuting(),
@@ -158,6 +199,7 @@ export class Session implements AgentSession {
158
199
  return {
159
200
  session,
160
201
  history_store: session.historyStore,
202
+ recorder: session.recorder,
161
203
  state_service: session.stateService,
162
204
  };
163
205
  },
@@ -168,6 +210,7 @@ export class Session implements AgentSession {
168
210
  * 初始化当前 session。
169
211
  */
170
212
  async initialize(): Promise<this> {
213
+ await this.recorder.initialize();
171
214
  await this.stateService.initialize();
172
215
  return this;
173
216
  }
@@ -203,8 +246,45 @@ export class Session implements AgentSession {
203
246
  /**
204
247
  * 订阅当前 Session 的未来事件。
205
248
  */
206
- subscribe(subscriber: AgentSessionSubscriber): AgentSessionUnsubscribe {
207
- return this.turnService.subscribe(subscriber);
249
+ subscribe(
250
+ subscriber: SessionMutationSubscriber,
251
+ ): SessionMutationUnsubscribe {
252
+ return this.eventHub.subscribe(subscriber);
253
+ }
254
+
255
+ /** 列出当前 Session 的 pending 工具审批。 */
256
+ async approvals(): Promise<SessionApproval[]> {
257
+ return await this.list_approvals(this.id);
258
+ }
259
+
260
+ /** 读取当前 Session 的工具审批模式。 */
261
+ async approval_mode(): Promise<SessionApprovalModeSnapshot> {
262
+ return await this.get_approval_mode(this.id);
263
+ }
264
+
265
+ /** 更新当前 Session 的工具审批模式。 */
266
+ async set_approval_mode(input: SetSessionApprovalModeInput): Promise<SessionApprovalModeSnapshot> {
267
+ return await this.set_approval_mode_hook(this.id, input);
268
+ }
269
+
270
+ /** 处理当前 Session 的 pending 工具审批。 */
271
+ async resolve_approval(input: ResolveSessionApprovalInput): Promise<SessionApprovalResult> {
272
+ return await this.resolve_approval_hook(this.id, input);
273
+ }
274
+
275
+ private async apply_runtime_event(event: SessionApprovalRuntimeEvent): Promise<void> {
276
+ if (event.type === "tool-approval-request") {
277
+ await this.recorder.require_tool_approval({
278
+ tool_call_id: event.tool_call_id,
279
+ approval_id: event.approval_id,
280
+ });
281
+ return;
282
+ }
283
+ await this.recorder.resolve_tool_approval({
284
+ tool_call_id: event.tool_call_id,
285
+ approval_id: event.approval_id,
286
+ decision: event.decision,
287
+ });
208
288
  }
209
289
 
210
290
  /**
@@ -239,8 +319,8 @@ export class Session implements AgentSession {
239
319
  /**
240
320
  * 读取当前 session records 分页。
241
321
  */
242
- async records(input?: AgentSessionRecordsInput): Promise<AgentSessionRecordsPage> {
243
- return await this.viewService.records(input);
322
+ async messages(input?: ListSessionMessagesInput): Promise<SessionMessagePage> {
323
+ return await this.recorder.list_messages(input);
244
324
  }
245
325
 
246
326
  /**
@@ -275,8 +355,8 @@ export class Session implements AgentSession {
275
355
  prompt: async (input) => await this.prompt(input),
276
356
  stop: async () => await this.stop(),
277
357
  subscribe: (subscriber) => this.subscribe(subscriber),
278
- publishEvent: (event) => {
279
- this.eventHub.publish(event);
358
+ publishEvent: async (event) => {
359
+ await this.apply_runtime_event(event);
280
360
  },
281
361
  append_user_message: async (message_params) => {
282
362
  await this.stateService.append_user_message(message_params);
@@ -312,6 +392,10 @@ export class Session implements AgentSession {
312
392
  getPluginSystemBlocks: this.getPluginSystemBlocks,
313
393
  ensureConfigured: this.ensureConfiguredHook,
314
394
  composers: this.composers,
395
+ list_approvals: this.list_approvals,
396
+ get_approval_mode: this.get_approval_mode,
397
+ set_approval_mode: this.set_approval_mode_hook,
398
+ resolve_approval: this.resolve_approval_hook,
315
399
  });
316
400
  }
317
401
 
@@ -329,33 +413,28 @@ export class Session implements AgentSession {
329
413
  return new SessionClass(options) as this;
330
414
  }
331
415
 
332
- private create_history_store(): JsonlSessionHistoryStore {
416
+ private create_message_store(): JsonlSessionMessageStore {
333
417
  const session_dir_path = getSdkAgentSessionDirPath(
334
418
  this.projectRoot,
335
419
  this.agentId,
336
420
  this.id,
337
421
  );
338
422
  const messages_dir_path = `${session_dir_path}/messages`;
339
- return new JsonlSessionHistoryStore({
340
- rootPath: this.projectRoot,
341
- agentId: this.agentId,
342
- sessionId: this.id,
343
- paths: {
344
- sessionDirPath: session_dir_path,
345
- messagesDirPath: messages_dir_path,
346
- messagesFilePath: `${messages_dir_path}/messages.jsonl`,
347
- metaFilePath: `${messages_dir_path}/meta.json`,
348
- archiveDirPath: getSdkAgentSessionArchiveDirPath(
349
- this.projectRoot,
350
- this.agentId,
351
- this.id,
352
- ),
353
- inflightFilePath: getSdkAgentSessionInflightPath(
354
- this.projectRoot,
355
- this.agentId,
356
- this.id,
357
- ),
358
- },
423
+ return new JsonlSessionMessageStore({
424
+ session_id: this.id,
425
+ file_path: `${messages_dir_path}/messages.jsonl`,
426
+ assistant_message_file_path: getSdkAgentSessionAssistantMessagePath(
427
+ this.projectRoot,
428
+ this.agentId,
429
+ this.id,
430
+ ),
431
+ });
432
+ }
433
+
434
+ private create_history_store(): SessionRecorderHistoryStore {
435
+ return new SessionRecorderHistoryStore({
436
+ session_id: this.id,
437
+ recorder: this.recorder,
359
438
  });
360
439
  }
361
440
 
@@ -471,3 +550,15 @@ export class Session implements AgentSession {
471
550
  return input;
472
551
  }
473
552
  }
553
+
554
+ function require_approval_id(mutation: SessionMutation): string {
555
+ if (
556
+ mutation.variant === "part" &&
557
+ mutation.type === "tool" &&
558
+ mutation.part.state === "approval-required" &&
559
+ mutation.part.approval_id
560
+ ) {
561
+ return mutation.part.approval_id;
562
+ }
563
+ throw new Error("Current Session Mutation is not an approval-required Tool Part");
564
+ }
@@ -45,6 +45,8 @@ import { getSdkAgentArchivedSessionsDirPath } from "@/session/storage/Paths.js";
45
45
  import { getSdkAgentArchivedSessionMessagesPath } from "@/session/storage/Paths.js";
46
46
  import { getSdkAgentArchivedSessionMetaPath } from "@/session/storage/Paths.js";
47
47
  import { readSessionMetadataFromPath } from "@/session/storage/Metadata.js";
48
+ import { to_executor_ui_message } from "@/session/recorder/SessionMessageCodec.js";
49
+ import type { SessionMessage } from "@/types/session/SessionMessage.js";
48
50
 
49
51
  type AnyUiPart = UIMessagePart<Record<string, never>, Record<string, never>>;
50
52
 
@@ -324,46 +326,73 @@ export function toSessionTimelineEvents(
324
326
  export async function loadSessionMessagesFromPath(
325
327
  filePath: string,
326
328
  ): Promise<SessionRecordV1[]> {
327
- const messages: SessionRecordV1[] = [];
329
+ const messages_by_id = new Map<string, SessionMessage>();
328
330
  if (await fs.pathExists(filePath)) {
329
331
  const raw = await fs.readFile(filePath, "utf-8");
330
332
  const lines = raw.split("\n").filter(Boolean);
331
333
  for (const line of lines) {
332
334
  try {
333
- const parsed = JSON.parse(line) as SessionRecordV1;
334
- if (!parsed || typeof parsed !== "object") continue;
335
- const candidate = parsed as { type?: unknown; role?: unknown };
336
- if (
337
- candidate.type !== "action" &&
338
- candidate.role !== "user" &&
339
- candidate.role !== "assistant"
340
- ) {
341
- continue;
335
+ const message = JSON.parse(line) as SessionMessage;
336
+ if (!is_canonical_session_message(message)) continue;
337
+ const previous = messages_by_id.get(message.message_id);
338
+ if (!previous || message.revision > previous.revision) {
339
+ messages_by_id.set(message.message_id, message);
342
340
  }
343
- messages.push(parsed);
344
341
  } catch {
345
342
  // 关键点(中文):单行损坏不影响整个 session 的可读性。
346
343
  }
347
344
  }
348
345
  }
349
346
 
350
- const inflight_path = filePath.replace(/messages\.jsonl$/, "inflight.json");
347
+ const inflight_path = filePath.replace(/messages\.jsonl$/, "assistant_message.json");
351
348
  if (await fs.pathExists(inflight_path)) {
352
349
  try {
353
- const parsed = (await fs.readJson(inflight_path)) as SessionRecordV1;
354
- if (
355
- is_session_message_record(parsed) &&
356
- parsed.role === "assistant" &&
357
- Array.isArray(parsed.parts)
358
- ) {
359
- messages.push(parsed);
350
+ const message = (await fs.readJson(inflight_path)) as SessionMessage;
351
+ if (is_canonical_session_message(message) && message.type === "assistant") {
352
+ messages_by_id.set(message.message_id, message);
360
353
  }
361
354
  } catch {
362
- // ignore invalid inflight snapshot
355
+ // 运行中快照损坏时仍返回已经完成的历史。
363
356
  }
364
357
  }
365
358
 
366
- return messages;
359
+ return [...messages_by_id.values()]
360
+ .sort((left, right) => left.sequence - right.sequence)
361
+ .flatMap(project_canonical_message_record);
362
+ }
363
+
364
+ function is_canonical_session_message(input: unknown): input is SessionMessage {
365
+ if (!input || typeof input !== "object") return false;
366
+ const candidate = input as Partial<SessionMessage>;
367
+ return (
368
+ typeof candidate.message_id === "string" &&
369
+ typeof candidate.session_id === "string" &&
370
+ typeof candidate.sequence === "number" &&
371
+ typeof candidate.revision === "number" &&
372
+ (candidate.type === "user" ||
373
+ candidate.type === "assistant" ||
374
+ candidate.type === "action" ||
375
+ candidate.type === "error")
376
+ );
377
+ }
378
+
379
+ function project_canonical_message_record(message: SessionMessage): SessionRecordV1[] {
380
+ const projected = to_executor_ui_message(message);
381
+ if (projected) return [projected];
382
+ if (message.type !== "action") return [];
383
+ return [{
384
+ type: "action",
385
+ id: message.message_id,
386
+ title: message.title,
387
+ ...(message.description ? { description: message.description } : {}),
388
+ state: message.status,
389
+ metadata: {
390
+ v: 1,
391
+ ts: message.updated_at,
392
+ sessionId: message.session_id,
393
+ ...(message.turn_id ? { turnId: message.turn_id } : {}),
394
+ },
395
+ }];
367
396
  }
368
397
 
369
398
  /**
@@ -481,7 +510,7 @@ async function resolve_session_summary_metadata(input: {
481
510
  const history_bytes = await fs.stat(input.messagesPath)
482
511
  .then((file_stat) => file_stat.size)
483
512
  .catch(() => 0);
484
- const inflight_path = `${input.messagesPath.slice(0, -"messages.jsonl".length)}inflight.json`;
513
+ const inflight_path = `${input.messagesPath.slice(0, -"messages.jsonl".length)}assistant_message.json`;
485
514
  const has_inflight = await fs.pathExists(inflight_path);
486
515
  if (
487
516
  !input.refresh &&