@downcity/agent 1.1.135 → 1.1.148

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 (180) hide show
  1. package/README.md +2 -2
  2. package/bin/agent/local/Agent.d.ts +3 -21
  3. package/bin/agent/local/Agent.d.ts.map +1 -1
  4. package/bin/agent/local/Agent.js +3 -30
  5. package/bin/agent/local/Agent.js.map +1 -1
  6. package/bin/agent/local/ProjectSetup.js +2 -2
  7. package/bin/agent/local/ProjectSetup.js.map +1 -1
  8. package/bin/agent/local/services/AgentAssemblyService.d.ts +1 -6
  9. package/bin/agent/local/services/AgentAssemblyService.d.ts.map +1 -1
  10. package/bin/agent/local/services/AgentAssemblyService.js +13 -20
  11. package/bin/agent/local/services/AgentAssemblyService.js.map +1 -1
  12. package/bin/agent/local/services/AgentSessionManager.d.ts +16 -7
  13. package/bin/agent/local/services/AgentSessionManager.d.ts.map +1 -1
  14. package/bin/agent/local/services/AgentSessionManager.js +83 -8
  15. package/bin/agent/local/services/AgentSessionManager.js.map +1 -1
  16. package/bin/agent/remote/RemoteAgent.d.ts +9 -13
  17. package/bin/agent/remote/RemoteAgent.d.ts.map +1 -1
  18. package/bin/agent/remote/RemoteAgent.js +23 -24
  19. package/bin/agent/remote/RemoteAgent.js.map +1 -1
  20. package/bin/agent/remote/RemoteSession.d.ts +11 -4
  21. package/bin/agent/remote/RemoteSession.d.ts.map +1 -1
  22. package/bin/agent/remote/RemoteSession.js +15 -3
  23. package/bin/agent/remote/RemoteSession.js.map +1 -1
  24. package/bin/agent/remote/RemoteTransport.d.ts +9 -1
  25. package/bin/agent/remote/RemoteTransport.d.ts.map +1 -1
  26. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts +7 -3
  27. package/bin/agent/remote/transports/HttpRemoteAgentTransport.d.ts.map +1 -1
  28. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js +65 -2
  29. package/bin/agent/remote/transports/HttpRemoteAgentTransport.js.map +1 -1
  30. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts +5 -1
  31. package/bin/agent/remote/transports/RpcRemoteAgentTransport.d.ts.map +1 -1
  32. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js +12 -0
  33. package/bin/agent/remote/transports/RpcRemoteAgentTransport.js.map +1 -1
  34. package/bin/config/AgentInitializer.d.ts +2 -2
  35. package/bin/config/AgentInitializer.js +2 -2
  36. package/bin/executor/Executor.d.ts +5 -0
  37. package/bin/executor/Executor.d.ts.map +1 -1
  38. package/bin/executor/Executor.js +34 -0
  39. package/bin/executor/Executor.js.map +1 -1
  40. package/bin/executor/composer/system/default/InitPrompts.d.ts +1 -1
  41. package/bin/executor/composer/system/default/InitPrompts.js +1 -1
  42. package/bin/executor/composer/system/default/SystemDomain.js +2 -2
  43. package/bin/executor/composer/system/default/SystemDomain.js.map +1 -1
  44. package/bin/executor/core-engine/CoreEngineRunner.d.ts.map +1 -1
  45. package/bin/executor/core-engine/CoreEngineRunner.js +16 -0
  46. package/bin/executor/core-engine/CoreEngineRunner.js.map +1 -1
  47. package/bin/executor/messages/ChatMessageMarkupTypes.d.ts +1 -1
  48. package/bin/executor/messages/ChatMessageMarkupTypes.js +1 -1
  49. package/bin/executor/types/SessionExecutor.d.ts +8 -0
  50. package/bin/executor/types/SessionExecutor.d.ts.map +1 -1
  51. package/bin/index.d.ts +2 -2
  52. package/bin/index.d.ts.map +1 -1
  53. package/bin/index.js.map +1 -1
  54. package/bin/plugin/core/Activation.d.ts +0 -2
  55. package/bin/plugin/core/Activation.d.ts.map +1 -1
  56. package/bin/plugin/core/Activation.js +1 -1
  57. package/bin/plugin/core/Activation.js.map +1 -1
  58. package/bin/plugin/core/PluginCommandRequest.d.ts +1 -1
  59. package/bin/plugin/core/PluginCommandRequest.js +1 -1
  60. package/bin/plugin/core/PluginStateController.d.ts +0 -5
  61. package/bin/plugin/core/PluginStateController.d.ts.map +1 -1
  62. package/bin/plugin/core/PluginStateController.js +1 -5
  63. package/bin/plugin/core/PluginStateController.js.map +1 -1
  64. package/bin/plugin/types/PluginApi.d.ts +1 -1
  65. package/bin/plugin/types/PluginApi.js +1 -1
  66. package/bin/rpc/Client.d.ts +17 -1
  67. package/bin/rpc/Client.d.ts.map +1 -1
  68. package/bin/rpc/Client.js +47 -0
  69. package/bin/rpc/Client.js.map +1 -1
  70. package/bin/session/Session.d.ts +5 -0
  71. package/bin/session/Session.d.ts.map +1 -1
  72. package/bin/session/Session.js +7 -0
  73. package/bin/session/Session.js.map +1 -1
  74. package/bin/session/browse/Browse.d.ts +9 -1
  75. package/bin/session/browse/Browse.d.ts.map +1 -1
  76. package/bin/session/browse/Browse.js +81 -3
  77. package/bin/session/browse/Browse.js.map +1 -1
  78. package/bin/session/index.d.ts +3 -3
  79. package/bin/session/index.d.ts.map +1 -1
  80. package/bin/session/index.js +3 -3
  81. package/bin/session/index.js.map +1 -1
  82. package/bin/session/runtime/SessionPromptRuntime.d.ts +13 -0
  83. package/bin/session/runtime/SessionPromptRuntime.d.ts.map +1 -1
  84. package/bin/session/runtime/SessionPromptRuntime.js +82 -5
  85. package/bin/session/runtime/SessionPromptRuntime.js.map +1 -1
  86. package/bin/session/services/SessionTurnService.d.ts +5 -0
  87. package/bin/session/services/SessionTurnService.d.ts.map +1 -1
  88. package/bin/session/services/SessionTurnService.js +10 -1
  89. package/bin/session/services/SessionTurnService.js.map +1 -1
  90. package/bin/session/storage/Metadata.d.ts +15 -0
  91. package/bin/session/storage/Metadata.d.ts.map +1 -1
  92. package/bin/session/storage/Metadata.js +15 -1
  93. package/bin/session/storage/Metadata.js.map +1 -1
  94. package/bin/session/storage/Paths.d.ts +24 -0
  95. package/bin/session/storage/Paths.d.ts.map +1 -1
  96. package/bin/session/storage/Paths.js +34 -0
  97. package/bin/session/storage/Paths.js.map +1 -1
  98. package/bin/session/storage/RuntimeSessionPort.d.ts +5 -0
  99. package/bin/session/storage/RuntimeSessionPort.d.ts.map +1 -1
  100. package/bin/session/storage/RuntimeSessionPort.js +4 -0
  101. package/bin/session/storage/RuntimeSessionPort.js.map +1 -1
  102. package/bin/types/agent/AgentTypes.d.ts +2 -1
  103. package/bin/types/agent/AgentTypes.d.ts.map +1 -1
  104. package/bin/types/agent/SessionActor.d.ts +13 -4
  105. package/bin/types/agent/SessionActor.d.ts.map +1 -1
  106. package/bin/types/agent/SessionTypes.d.ts +62 -0
  107. package/bin/types/agent/SessionTypes.d.ts.map +1 -1
  108. package/bin/types/config/DowncityConfig.d.ts +1 -1
  109. package/bin/types/executor/SessionRunContext.d.ts +8 -0
  110. package/bin/types/executor/SessionRunContext.d.ts.map +1 -1
  111. package/bin/types/platform/Store.d.ts +2 -2
  112. package/bin/types/platform/Store.js +2 -2
  113. package/bin/types/rpc/RpcProtocol.d.ts +37 -5
  114. package/bin/types/rpc/RpcProtocol.d.ts.map +1 -1
  115. package/bin/types/rpc/RpcProtocol.js +1 -1
  116. package/bin/types/runtime/agent/AgentContext.d.ts +36 -11
  117. package/bin/types/runtime/agent/AgentContext.d.ts.map +1 -1
  118. package/bin/types/runtime/agent/AgentContext.js +26 -8
  119. package/bin/types/runtime/agent/AgentContext.js.map +1 -1
  120. package/bin/types/runtime/platform/Platform.d.ts +8 -8
  121. package/bin/types/runtime/platform/PlatformGateway.d.ts +2 -2
  122. package/bin/types/sdk/AgentSessionStop.d.ts +36 -0
  123. package/bin/types/sdk/AgentSessionStop.d.ts.map +1 -0
  124. package/bin/types/sdk/AgentSessionStop.js +9 -0
  125. package/bin/types/sdk/AgentSessionStop.js.map +1 -0
  126. package/package.json +3 -3
  127. package/scripts/city-model-tool-loop.test.mjs +2 -2
  128. package/scripts/session-prompt-runtime.test.mjs +59 -0
  129. package/src/agent/local/Agent.ts +3 -41
  130. package/src/agent/local/ProjectSetup.ts +2 -2
  131. package/src/agent/local/services/AgentAssemblyService.ts +17 -28
  132. package/src/agent/local/services/AgentSessionManager.ts +127 -13
  133. package/src/agent/remote/RemoteAgent.ts +25 -34
  134. package/src/agent/remote/RemoteSession.ts +21 -6
  135. package/src/agent/remote/RemoteTransport.ts +14 -0
  136. package/src/agent/remote/transports/HttpRemoteAgentTransport.ts +100 -2
  137. package/src/agent/remote/transports/RpcRemoteAgentTransport.ts +26 -0
  138. package/src/config/AgentInitializer.ts +2 -2
  139. package/src/executor/Executor.ts +33 -0
  140. package/src/executor/composer/system/default/InitPrompts.ts +1 -1
  141. package/src/executor/composer/system/default/SystemDomain.ts +2 -2
  142. package/src/executor/core-engine/CoreEngineRunner.ts +21 -0
  143. package/src/executor/messages/ChatMessageMarkupTypes.ts +1 -1
  144. package/src/executor/types/SessionExecutor.ts +2 -1
  145. package/src/index.ts +6 -1
  146. package/src/plugin/core/Activation.ts +1 -3
  147. package/src/plugin/core/PluginCommandRequest.ts +1 -1
  148. package/src/plugin/core/PluginStateController.ts +1 -10
  149. package/src/plugin/types/PluginApi.ts +1 -1
  150. package/src/rpc/Client.ts +61 -0
  151. package/src/session/Session.ts +9 -0
  152. package/src/session/browse/Browse.ts +103 -3
  153. package/src/session/index.ts +7 -0
  154. package/src/session/runtime/SessionPromptRuntime.ts +100 -5
  155. package/src/session/services/SessionTurnService.ts +13 -1
  156. package/src/session/storage/Metadata.ts +23 -1
  157. package/src/session/storage/Paths.ts +70 -0
  158. package/src/session/storage/RuntimeSessionPort.ts +9 -0
  159. package/src/types/agent/AgentTypes.ts +6 -0
  160. package/src/types/agent/SessionActor.ts +21 -3
  161. package/src/types/agent/SessionTypes.ts +68 -0
  162. package/src/types/config/DowncityConfig.ts +1 -1
  163. package/src/types/executor/SessionRunContext.ts +9 -0
  164. package/src/types/platform/Store.ts +2 -2
  165. package/src/types/rpc/RpcProtocol.ts +46 -4
  166. package/src/types/runtime/agent/AgentContext.ts +41 -14
  167. package/src/types/runtime/platform/Platform.ts +8 -8
  168. package/src/types/runtime/platform/PlatformGateway.ts +2 -2
  169. package/src/types/sdk/AgentSessionStop.ts +39 -0
  170. package/tsconfig.tsbuildinfo +1 -1
  171. package/bin/agent/local/AgentRuntimeFactory.d.ts +0 -47
  172. package/bin/agent/local/AgentRuntimeFactory.d.ts.map +0 -1
  173. package/bin/agent/local/AgentRuntimeFactory.js +0 -34
  174. package/bin/agent/local/AgentRuntimeFactory.js.map +0 -1
  175. package/bin/types/runtime/agent/AgentRuntime.d.ts +0 -79
  176. package/bin/types/runtime/agent/AgentRuntime.d.ts.map +0 -1
  177. package/bin/types/runtime/agent/AgentRuntime.js +0 -13
  178. package/bin/types/runtime/agent/AgentRuntime.js.map +0 -1
  179. package/src/agent/local/AgentRuntimeFactory.ts +0 -78
  180. package/src/types/runtime/agent/AgentRuntime.ts +0 -84
@@ -4,7 +4,7 @@
4
4
  * 关键点(中文)
5
5
  * - 统一负责 session 列表摘要、session 详情与 history 分页的投影逻辑。
6
6
  * - session title 允许为空;浏览层不会再从首条 user message 推导 fallback title。
7
- * - 面向 SDK / RemoteAgent / Town gateway route 复用,避免在多个入口重复拼列表与分页语义。
7
+ * - 面向 SDK / RemoteAgent / downcity gateway route 复用,避免在多个入口重复拼列表与分页语义。
8
8
  * - 这里不持有运行态状态;执行状态等动态信息通过调用参数显式注入。
9
9
  */
10
10
 
@@ -33,7 +33,11 @@ import type { SessionHistoryMetaV1 } from "@/executor/types/SessionHistoryMeta.j
33
33
  import { pickLastSuccessfulChatSendText } from "@/executor/messages/UserVisibleText.js";
34
34
  import { getSdkAgentSessionMessagesPath } from "@/session/storage/Paths.js";
35
35
  import { getSdkAgentSessionsRootDirPath } from "@/session/storage/Paths.js";
36
+ import { getSdkAgentArchivedSessionsDirPath } from "@/session/storage/Paths.js";
37
+ import { getSdkAgentArchivedSessionMessagesPath } from "@/session/storage/Paths.js";
38
+ import { getSdkAgentArchivedSessionMetaPath } from "@/session/storage/Paths.js";
36
39
  import { readSessionMetadata } from "@/session/storage/Metadata.js";
40
+ import { readSessionMetadataFromPath } from "@/session/storage/Metadata.js";
37
41
  import {
38
42
  ensureSessionTitle,
39
43
  } from "@/session/SessionTitle.js";
@@ -470,6 +474,103 @@ export async function listAgentSessionSummaryPage(params: {
470
474
  const entries = await fs.readdir(sessionsRoot, { withFileTypes: true });
471
475
  const summaries: AgentSessionSummary[] = [];
472
476
 
477
+ for (const entry of entries) {
478
+ if (!entry.isDirectory()) continue;
479
+ const sessionId = decodeMaybe(entry.name);
480
+ if (!sessionId) continue;
481
+ const metadata = await readSessionMetadataFromPath({
482
+ filePath: getSdkAgentArchivedSessionMetaPath(
483
+ params.projectRoot,
484
+ params.agentId,
485
+ sessionId,
486
+ ),
487
+ sessionId,
488
+ agentId: params.agentId,
489
+ });
490
+ const messages = await loadSessionMessagesFromPath(
491
+ getSdkAgentArchivedSessionMessagesPath(
492
+ params.projectRoot,
493
+ params.agentId,
494
+ sessionId,
495
+ ),
496
+ );
497
+ // 关键点(中文):归档 session 不再生成新 title,仅读取已有 meta。
498
+ const metadataWithTitle = metadata;
499
+ const info = buildSessionInfo({
500
+ projectRoot: params.projectRoot,
501
+ agentId: params.agentId,
502
+ sessionId,
503
+ metadata: metadataWithTitle,
504
+ messages,
505
+ executing: params.executingSessionIds?.has(sessionId),
506
+ });
507
+ const summary: AgentSessionSummary = {
508
+ agentId: info.agentId,
509
+ sessionId: info.sessionId,
510
+ ...(info.title ? { title: info.title } : {}),
511
+ ...(info.previewText ? { previewText: info.previewText } : {}),
512
+ messageCount: info.messageCount,
513
+ ...(typeof info.createdAt === "number" ? { createdAt: info.createdAt } : {}),
514
+ ...(typeof info.updatedAt === "number" ? { updatedAt: info.updatedAt } : {}),
515
+ ...(info.modelLabel ? { modelLabel: info.modelLabel } : {}),
516
+ ...(info.executing ? { executing: true } : {}),
517
+ };
518
+
519
+ if (query) {
520
+ const haystack = [
521
+ summary.sessionId,
522
+ summary.title || "",
523
+ summary.previewText || "",
524
+ ]
525
+ .join("\n")
526
+ .toLowerCase();
527
+ if (!haystack.includes(query)) continue;
528
+ }
529
+
530
+ summaries.push(summary);
531
+ }
532
+
533
+ summaries.sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0));
534
+
535
+ const items = summaries.slice(cursor, cursor + limit);
536
+ const nextOffset = cursor + items.length;
537
+ return {
538
+ items,
539
+ total: summaries.length,
540
+ ...(nextOffset < summaries.length
541
+ ? { nextCursor: encodeCursor(nextOffset) }
542
+ : {}),
543
+ hasMore: nextOffset < summaries.length,
544
+ };
545
+ }
546
+
547
+ /**
548
+ * 列出指定 agent 的已归档 session 摘要页。
549
+ */
550
+ export async function listArchivedAgentSessionSummaryPage(params: {
551
+ projectRoot: string;
552
+ agentId: string;
553
+ input?: AgentListSessionsInput;
554
+ }): Promise<AgentSessionSummaryPage> {
555
+ const limit = normalizeLimit(params.input?.limit, 50, 500);
556
+ const cursor = normalizeCursor(params.input?.cursor);
557
+ const query = String(params.input?.query || "").trim().toLowerCase();
558
+ const archivedRoot = getSdkAgentArchivedSessionsDirPath(
559
+ params.projectRoot,
560
+ params.agentId,
561
+ );
562
+
563
+ if (!(await fs.pathExists(archivedRoot))) {
564
+ return {
565
+ items: [],
566
+ total: 0,
567
+ hasMore: false,
568
+ };
569
+ }
570
+
571
+ const entries = await fs.readdir(archivedRoot, { withFileTypes: true });
572
+ const summaries: AgentSessionSummary[] = [];
573
+
473
574
  for (const entry of entries) {
474
575
  if (!entry.isDirectory()) continue;
475
576
  const sessionId = decodeMaybe(entry.name);
@@ -496,7 +597,7 @@ export async function listAgentSessionSummaryPage(params: {
496
597
  sessionId,
497
598
  metadata: metadataWithTitle,
498
599
  messages,
499
- executing: params.executingSessionIds?.has(sessionId),
600
+ executing: false,
500
601
  });
501
602
  const summary: AgentSessionSummary = {
502
603
  agentId: info.agentId,
@@ -507,7 +608,6 @@ export async function listAgentSessionSummaryPage(params: {
507
608
  ...(typeof info.createdAt === "number" ? { createdAt: info.createdAt } : {}),
508
609
  ...(typeof info.updatedAt === "number" ? { updatedAt: info.updatedAt } : {}),
509
610
  ...(info.modelLabel ? { modelLabel: info.modelLabel } : {}),
510
- ...(info.executing ? { executing: true } : {}),
511
611
  };
512
612
 
513
613
  if (query) {
@@ -10,6 +10,11 @@ export { Session } from "./Session.js";
10
10
 
11
11
  export {
12
12
  getSdkAgentDirPath,
13
+ getSdkAgentArchivedSessionDirPath,
14
+ getSdkAgentArchivedSessionMessagesDirPath,
15
+ getSdkAgentArchivedSessionMessagesPath,
16
+ getSdkAgentArchivedSessionMetaPath,
17
+ getSdkAgentArchivedSessionsDirPath,
13
18
  getSdkAgentSessionArchiveDirPath,
14
19
  getSdkAgentSessionDirPath,
15
20
  getSdkAgentSessionInflightPath,
@@ -25,6 +30,7 @@ export {
25
30
  normalizeSessionTitle,
26
31
  patchSessionModelLabel,
27
32
  readSessionMetadata,
33
+ readSessionMetadataFromPath,
28
34
  resolveSystemTimezone,
29
35
  writeSessionMetadata,
30
36
  } from "./storage/Metadata.js";
@@ -41,6 +47,7 @@ export {
41
47
  export {
42
48
  buildSessionHistoryPage,
43
49
  buildSessionInfo,
50
+ listArchivedAgentSessionSummaryPage,
44
51
  listAgentSessionSummaryPage,
45
52
  loadSessionMessagesFromPath,
46
53
  resolveSessionMessagePreview,
@@ -12,11 +12,16 @@ import type { SessionUserMessageV1 } from "@/executor/types/SessionMessages.js";
12
12
  import type { SessionMessageV1 } from "@/executor/types/SessionMessages.js";
13
13
  import type { AgentSessionEvent } from "@/types/sdk/AgentSessionEvent.js";
14
14
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
15
+ import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
15
16
  import type {
16
17
  AgentSessionTurnHandle,
17
18
  AgentSessionTurnResult,
18
19
  } from "@/types/sdk/AgentSessionTurn.js";
19
20
 
21
+ const TURN_STOPPED_MESSAGE = "Turn stopped";
22
+ const QUEUED_PROMPT_CANCELLED_MESSAGE =
23
+ "Prompt cancelled because session was stopped";
24
+
20
25
  type QueuedPrompt = {
21
26
  /**
22
27
  * 当前排队中的 prompt 输入。
@@ -62,6 +67,11 @@ interface ActiveTurnState {
62
67
  * 当前 turn 完成 Promise 控制器。
63
68
  */
64
69
  deferredFinished: Deferred<AgentSessionTurnResult>;
70
+
71
+ /**
72
+ * 当前 turn 的取消控制器。
73
+ */
74
+ abortController: AbortController;
65
75
  }
66
76
 
67
77
  /**
@@ -92,12 +102,18 @@ export interface SessionPromptRuntimeOptions {
92
102
  turnId: string;
93
103
  promptInput: AgentSessionPromptInput;
94
104
  onStepMerge: () => Promise<SessionUserMessageV1[]>;
105
+ abortSignal: AbortSignal;
95
106
  }) => Promise<{
96
107
  text: string;
97
108
  success: boolean;
98
109
  assistantMessage: SessionMessageV1;
99
110
  error?: string;
100
111
  }>;
112
+
113
+ /**
114
+ * 请求底层执行器停止当前 run。
115
+ */
116
+ stopTurn: () => boolean;
101
117
  }
102
118
 
103
119
  /**
@@ -108,14 +124,17 @@ export class SessionPromptRuntime {
108
124
  private readonly publish: SessionPromptRuntimeOptions["publish"];
109
125
  private readonly createAndPersistUserMessage: SessionPromptRuntimeOptions["createAndPersistUserMessage"];
110
126
  private readonly executeTurn: SessionPromptRuntimeOptions["executeTurn"];
127
+ private readonly stopTurn: SessionPromptRuntimeOptions["stopTurn"];
111
128
  private readonly queue: QueuedPrompt[] = [];
112
129
  private processingPromise: Promise<void> | null = null;
130
+ private activeTurn: ActiveTurnState | null = null;
113
131
 
114
132
  constructor(options: SessionPromptRuntimeOptions) {
115
133
  this.sessionId = String(options.sessionId || "").trim();
116
134
  this.publish = options.publish;
117
135
  this.createAndPersistUserMessage = options.createAndPersistUserMessage;
118
136
  this.executeTurn = options.executeTurn;
137
+ this.stopTurn = options.stopTurn;
119
138
  if (!this.sessionId) {
120
139
  throw new Error("SessionPromptRuntime requires a non-empty sessionId");
121
140
  }
@@ -147,6 +166,34 @@ export class SessionPromptRuntime {
147
166
  return this.processingPromise !== null || this.queue.length > 0;
148
167
  }
149
168
 
169
+ /**
170
+ * 停止当前 turn,并取消尚未被吸收的排队 prompt。
171
+ */
172
+ stop(): AgentSessionStopResult {
173
+ const active_turn = this.activeTurn;
174
+ const cancelled_queued_prompts = this.cancelQueuedPrompts();
175
+ let executor_stop_requested = false;
176
+
177
+ if (active_turn) {
178
+ if (!active_turn.abortController.signal.aborted) {
179
+ active_turn.abortController.abort(new Error(TURN_STOPPED_MESSAGE));
180
+ }
181
+ executor_stop_requested = this.stopTurn();
182
+ }
183
+
184
+ const stopped = Boolean(
185
+ active_turn ||
186
+ executor_stop_requested ||
187
+ cancelled_queued_prompts > 0,
188
+ );
189
+ return {
190
+ stopped,
191
+ ...(active_turn ? { turnId: active_turn.turnId } : {}),
192
+ cancelledQueuedPrompts: cancelled_queued_prompts,
193
+ reason: stopped ? "stopped" : "idle",
194
+ };
195
+ }
196
+
150
197
  private ensureProcessing(): void {
151
198
  if (this.processingPromise) return;
152
199
  this.processingPromise = this.processLoop().finally(() => {
@@ -164,6 +211,7 @@ export class SessionPromptRuntime {
164
211
 
165
212
  const turnId = `turn:${this.sessionId}:${Date.now()}:${nanoid(6)}`;
166
213
  const activeTurn = createActiveTurnState(turnId);
214
+ this.activeTurn = activeTurn;
167
215
  this.publish({
168
216
  type: "turn-start",
169
217
  turnId,
@@ -178,7 +226,11 @@ export class SessionPromptRuntime {
178
226
  onStepMerge: async () => {
179
227
  return await this.drainQueuedPromptsAsMessages(activeTurn);
180
228
  },
229
+ abortSignal: activeTurn.abortController.signal,
181
230
  });
231
+ if (activeTurn.abortController.signal.aborted) {
232
+ throw new Error(TURN_STOPPED_MESSAGE);
233
+ }
182
234
  const finalResult: AgentSessionTurnResult = {
183
235
  turnId,
184
236
  text: result.text,
@@ -196,7 +248,9 @@ export class SessionPromptRuntime {
196
248
  });
197
249
  activeTurn.deferredFinished.resolve(finalResult);
198
250
  } catch (error) {
199
- const message = error instanceof Error ? error.message : String(error);
251
+ const message = activeTurn.abortController.signal.aborted
252
+ ? TURN_STOPPED_MESSAGE
253
+ : error instanceof Error ? error.message : String(error);
200
254
  const finalResult: AgentSessionTurnResult = {
201
255
  turnId,
202
256
  text: "",
@@ -208,10 +262,12 @@ export class SessionPromptRuntime {
208
262
  error: message,
209
263
  };
210
264
  activeTurn.result = finalResult;
211
- this.publish({
212
- type: "error",
213
- message,
214
- });
265
+ if (message !== TURN_STOPPED_MESSAGE) {
266
+ this.publish({
267
+ type: "error",
268
+ message,
269
+ });
270
+ }
215
271
  this.publish({
216
272
  type: "turn-finish",
217
273
  turnId,
@@ -220,10 +276,48 @@ export class SessionPromptRuntime {
220
276
  error: message,
221
277
  });
222
278
  activeTurn.deferredFinished.resolve(finalResult);
279
+ } finally {
280
+ if (this.activeTurn === activeTurn) {
281
+ this.activeTurn = null;
282
+ }
223
283
  }
224
284
  }
225
285
  }
226
286
 
287
+ private cancelQueuedPrompts(): number {
288
+ if (this.queue.length <= 0) return 0;
289
+ const cancelled = this.queue.splice(0, this.queue.length);
290
+ for (const item of cancelled) {
291
+ const turnId = `turn:${this.sessionId}:cancelled:${Date.now()}:${nanoid(6)}`;
292
+ const cancelledTurn = createActiveTurnState(turnId);
293
+ const finalResult: AgentSessionTurnResult = {
294
+ turnId,
295
+ text: "",
296
+ success: false,
297
+ assistantMessage: buildPromptRuntimeErrorAssistantMessage({
298
+ sessionId: this.sessionId,
299
+ message: QUEUED_PROMPT_CANCELLED_MESSAGE,
300
+ }),
301
+ error: QUEUED_PROMPT_CANCELLED_MESSAGE,
302
+ };
303
+ cancelledTurn.result = finalResult;
304
+ cancelledTurn.deferredFinished.resolve(finalResult);
305
+ this.publish({
306
+ type: "turn-start",
307
+ turnId,
308
+ });
309
+ this.publish({
310
+ type: "turn-finish",
311
+ turnId,
312
+ text: "",
313
+ success: false,
314
+ error: QUEUED_PROMPT_CANCELLED_MESSAGE,
315
+ });
316
+ item.deferredHandle.resolve(createTurnHandle(cancelledTurn));
317
+ }
318
+ return cancelled.length;
319
+ }
320
+
227
321
  private async drainQueuedPromptsAsMessages(
228
322
  activeTurn: ActiveTurnState,
229
323
  ): Promise<SessionUserMessageV1[]> {
@@ -286,6 +380,7 @@ function createActiveTurnState(turnId: string): ActiveTurnState {
286
380
  turnId,
287
381
  result: null,
288
382
  deferredFinished: createDeferred<AgentSessionTurnResult>(),
383
+ abortController: new AbortController(),
289
384
  };
290
385
  }
291
386
 
@@ -15,6 +15,7 @@ import type {
15
15
  AgentSessionUnsubscribe,
16
16
  } from "@/types/sdk/AgentSessionEvent.js";
17
17
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
18
+ import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
18
19
  import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
19
20
  import type {
20
21
  SessionMessageV1,
@@ -80,13 +81,15 @@ export class SessionTurnService {
80
81
  input,
81
82
  );
82
83
  },
83
- executeTurn: async ({ turnId, promptInput, onStepMerge }) => {
84
+ executeTurn: async ({ turnId, promptInput, onStepMerge, abortSignal }) => {
84
85
  return await this.execute_prompt_turn({
85
86
  turnId,
86
87
  promptInput,
87
88
  onStepMerge,
89
+ abortSignal,
88
90
  });
89
91
  },
92
+ stopTurn: () => this.executor.stop(),
90
93
  });
91
94
  }
92
95
 
@@ -125,6 +128,13 @@ export class SessionTurnService {
125
128
  });
126
129
  }
127
130
 
131
+ /**
132
+ * 停止当前 turn,并取消尚未被吸收的排队 prompt。
133
+ */
134
+ async stop(): Promise<AgentSessionStopResult> {
135
+ return this.prompt_runtime.stop();
136
+ }
137
+
128
138
  /**
129
139
  * 执行单轮 prompt turn。
130
140
  */
@@ -132,6 +142,7 @@ export class SessionTurnService {
132
142
  turnId: string;
133
143
  promptInput: AgentSessionPromptInput;
134
144
  onStepMerge: () => Promise<SessionUserMessageV1[]>;
145
+ abortSignal?: AbortSignal;
135
146
  }): Promise<{
136
147
  text: string;
137
148
  success: boolean;
@@ -192,6 +203,7 @@ export class SessionTurnService {
192
203
  injectedUserMessages: [],
193
204
  deferredPersistedUserMessages: [],
194
205
  pendingAssistantFileParts: [],
206
+ ...(input.abortSignal ? { abortSignal: input.abortSignal } : {}),
195
207
  };
196
208
  const result = await this.executor.run({
197
209
  query: input.promptInput.query,
@@ -93,8 +93,30 @@ export async function readSessionMetadata(
93
93
  input.agentId,
94
94
  input.sessionId,
95
95
  );
96
+ return await readSessionMetadataFromPath({
97
+ filePath,
98
+ sessionId: input.sessionId,
99
+ agentId: input.agentId,
100
+ });
101
+ }
102
+
103
+ /**
104
+ * 从指定路径读取 session meta.json。
105
+ *
106
+ * 关键点(中文)
107
+ * - 供归档 session 等需要脱离默认 `sessions/` 目录的场景复用。
108
+ * - 路径本身不做校验,调用方需保证可访问。
109
+ */
110
+ export async function readSessionMetadataFromPath(input: {
111
+ /** meta.json 文件路径。 */
112
+ filePath: string;
113
+ /** 当前 sessionId。 */
114
+ sessionId: string;
115
+ /** 当前 agentId。 */
116
+ agentId: string;
117
+ }): Promise<SessionHistoryMetaV1> {
96
118
  try {
97
- const raw = (await fs.readJson(filePath)) as Partial<SessionHistoryMetaV1>;
119
+ const raw = (await fs.readJson(input.filePath)) as Partial<SessionHistoryMetaV1>;
98
120
  return {
99
121
  v: 1,
100
122
  sessionId: input.sessionId,
@@ -45,6 +45,76 @@ export function getSdkAgentSessionsRootDirPath(
45
45
  return path.join(getSdkAgentDirPath(projectRoot, agentId), "sessions");
46
46
  }
47
47
 
48
+ /**
49
+ * 单个 agent 的已归档 sessions 根目录路径。
50
+ *
51
+ * 关键点(中文)
52
+ * - `archive_session` 会把整个 session 目录从 `sessions/<sessionId>` 移动到此处。
53
+ * - `clean_archive` 会永久删除该目录下的全部内容。
54
+ */
55
+ export function getSdkAgentArchivedSessionsDirPath(
56
+ projectRoot: string,
57
+ agentId: string,
58
+ ): string {
59
+ return path.join(getSdkAgentDirPath(projectRoot, agentId), "archived-sessions");
60
+ }
61
+
62
+ /**
63
+ * 单个已归档 session 的根目录路径。
64
+ */
65
+ export function getSdkAgentArchivedSessionDirPath(
66
+ projectRoot: string,
67
+ agentId: string,
68
+ sessionId: string,
69
+ ): string {
70
+ return path.join(
71
+ getSdkAgentArchivedSessionsDirPath(projectRoot, agentId),
72
+ encodeURIComponent(String(sessionId || "").trim()),
73
+ );
74
+ }
75
+
76
+ /**
77
+ * 单个已归档 session 的消息目录路径。
78
+ */
79
+ export function getSdkAgentArchivedSessionMessagesDirPath(
80
+ projectRoot: string,
81
+ agentId: string,
82
+ sessionId: string,
83
+ ): string {
84
+ return path.join(
85
+ getSdkAgentArchivedSessionDirPath(projectRoot, agentId, sessionId),
86
+ "messages",
87
+ );
88
+ }
89
+
90
+ /**
91
+ * 单个已归档 session 的消息 JSONL 文件路径。
92
+ */
93
+ export function getSdkAgentArchivedSessionMessagesPath(
94
+ projectRoot: string,
95
+ agentId: string,
96
+ sessionId: string,
97
+ ): string {
98
+ return path.join(
99
+ getSdkAgentArchivedSessionMessagesDirPath(projectRoot, agentId, sessionId),
100
+ "messages.jsonl",
101
+ );
102
+ }
103
+
104
+ /**
105
+ * 单个已归档 session 的 meta.json 路径。
106
+ */
107
+ export function getSdkAgentArchivedSessionMetaPath(
108
+ projectRoot: string,
109
+ agentId: string,
110
+ sessionId: string,
111
+ ): string {
112
+ return path.join(
113
+ getSdkAgentArchivedSessionMessagesDirPath(projectRoot, agentId, sessionId),
114
+ "meta.json",
115
+ );
116
+ }
117
+
48
118
  /**
49
119
  * 单个 session 根目录路径。
50
120
  */
@@ -9,6 +9,7 @@
9
9
  import type { SessionPort } from "@/types/runtime/agent/AgentContext.js";
10
10
  import type { SessionHistoryStore } from "@/executor/store/history/SessionHistoryStore.js";
11
11
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
12
+ import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
12
13
  import type {
13
14
  AgentSessionEvent,
14
15
  AgentSessionSubscriber,
@@ -32,6 +33,10 @@ export interface CreateRuntimeSessionPortParams {
32
33
  * 追加一条新的 session prompt。
33
34
  */
34
35
  prompt: (input: AgentSessionPromptInput) => Promise<AgentSessionTurnHandle>;
36
+ /**
37
+ * 停止当前 turn,并取消尚未被吸收的排队 prompt。
38
+ */
39
+ stop: () => Promise<AgentSessionStopResult>;
35
40
  /**
36
41
  * 订阅当前 session 的 future 事件。
37
42
  */
@@ -90,6 +95,10 @@ export function createRuntimeSessionPort(
90
95
  await params.ensureReadyForExecution();
91
96
  return await params.prompt(input);
92
97
  },
98
+ stop: async () => {
99
+ await params.ensureReadyForExecution();
100
+ return await params.stop();
101
+ },
93
102
  subscribe: (subscriber) => {
94
103
  return params.subscribe(subscriber);
95
104
  },
@@ -27,6 +27,11 @@ export type {
27
27
  RemoteAgentPluginActionResult,
28
28
  } from "@/types/agent/RemoteAgentPluginAction.js";
29
29
  export type {
30
+ AgentArchiveSessionInput,
31
+ AgentArchiveSessionsInput,
32
+ AgentArchiveSessionResult,
33
+ AgentArchiveSessionsResult,
34
+ AgentCleanArchiveResult,
30
35
  AgentCreateSessionInput,
31
36
  AgentListSessionsInput,
32
37
  AgentSessionConfigSnapshot,
@@ -44,6 +49,7 @@ export type {
44
49
  AgentSessionSystemSnapshot,
45
50
  AgentSessionTimelineEvent,
46
51
  } from "@/types/agent/SessionTypes.js";
52
+ export type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
47
53
  export type {
48
54
  AgentSession,
49
55
  AgentSessionActor,
@@ -8,6 +8,11 @@
8
8
 
9
9
  import type {
10
10
  AgentCreateSessionInput,
11
+ AgentArchiveSessionInput,
12
+ AgentArchiveSessionsInput,
13
+ AgentArchiveSessionResult,
14
+ AgentArchiveSessionsResult,
15
+ AgentCleanArchiveResult,
11
16
  AgentListSessionsInput,
12
17
  AgentSessionConfigSnapshot,
13
18
  AgentSessionForkInput,
@@ -23,6 +28,7 @@ import type {
23
28
  AgentSessionUnsubscribe,
24
29
  } from "@/types/sdk/AgentSessionEvent.js";
25
30
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
31
+ import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
26
32
  import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
27
33
 
28
34
  /**
@@ -30,13 +36,22 @@ import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
30
36
  */
31
37
  export interface AgentSessionCollection {
32
38
  /** 新建一个 session。 */
33
- createSession(input?: AgentCreateSessionInput): Promise<AgentSession>;
39
+ create_session(input?: AgentCreateSessionInput): Promise<AgentSession>;
34
40
 
35
41
  /** 获取一个已存在的 session。 */
36
- getSession(sessionId: string): Promise<AgentSession>;
42
+ get_session(sessionId: string): Promise<AgentSession>;
37
43
 
38
44
  /** 列出当前 agent 的 session 摘要页。 */
39
- listSessions(input?: AgentListSessionsInput): Promise<AgentSessionSummaryPage>;
45
+ list_sessions(input?: AgentListSessionsInput): Promise<AgentSessionSummaryPage>;
46
+
47
+ /** 归档单个 session。 */
48
+ archive_session(input: AgentArchiveSessionInput): Promise<AgentArchiveSessionResult>;
49
+
50
+ /** 列出已归档的 session 摘要页。 */
51
+ archive_sessions(input?: AgentArchiveSessionsInput): Promise<AgentArchiveSessionsResult>;
52
+
53
+ /** 永久清空已归档 session。 */
54
+ clean_archive(): Promise<AgentCleanArchiveResult>;
40
55
  }
41
56
 
42
57
  /**
@@ -52,6 +67,9 @@ export interface AgentSessionActor {
52
67
  /** 追加一条新的 prompt。 */
53
68
  prompt(input: AgentSessionPromptInput): Promise<AgentSessionTurnHandle>;
54
69
 
70
+ /** 停止当前 turn,并取消尚未被吸收的排队 prompt。 */
71
+ stop(): Promise<AgentSessionStopResult>;
72
+
55
73
  /** 订阅当前 session 的未来事件。 */
56
74
  subscribe(subscriber: AgentSessionSubscriber): AgentSessionUnsubscribe;
57
75