@downcity/agent 1.1.66 → 1.1.70

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 (44) hide show
  1. package/bin/agent/Agent.d.ts.map +1 -1
  2. package/bin/agent/Agent.js +2 -0
  3. package/bin/agent/Agent.js.map +1 -1
  4. package/bin/agent/RemoteAgent.d.ts +8 -0
  5. package/bin/agent/RemoteAgent.d.ts.map +1 -1
  6. package/bin/agent/RemoteAgent.js +45 -14
  7. package/bin/agent/RemoteAgent.js.map +1 -1
  8. package/bin/config/Config.d.ts.map +1 -1
  9. package/bin/config/Config.js +16 -4
  10. package/bin/config/Config.js.map +1 -1
  11. package/bin/rpc/Client.d.ts +129 -0
  12. package/bin/rpc/Client.d.ts.map +1 -1
  13. package/bin/rpc/Client.js +123 -0
  14. package/bin/rpc/Client.js.map +1 -1
  15. package/bin/rpc/Server.d.ts +6 -0
  16. package/bin/rpc/Server.d.ts.map +1 -1
  17. package/bin/rpc/Server.js +197 -0
  18. package/bin/rpc/Server.js.map +1 -1
  19. package/bin/session/SessionTitle.d.ts +2 -6
  20. package/bin/session/SessionTitle.d.ts.map +1 -1
  21. package/bin/session/SessionTitle.js +5 -27
  22. package/bin/session/SessionTitle.js.map +1 -1
  23. package/bin/session/browse/Browse.d.ts +1 -5
  24. package/bin/session/browse/Browse.d.ts.map +1 -1
  25. package/bin/session/browse/Browse.js +2 -8
  26. package/bin/session/browse/Browse.js.map +1 -1
  27. package/bin/session/index.d.ts +2 -2
  28. package/bin/session/index.d.ts.map +1 -1
  29. package/bin/session/index.js +2 -2
  30. package/bin/session/index.js.map +1 -1
  31. package/bin/types/agent/AgentTypes.d.ts +8 -0
  32. package/bin/types/agent/AgentTypes.d.ts.map +1 -1
  33. package/package.json +2 -2
  34. package/scripts/session-title-event.test.mjs +74 -7
  35. package/src/agent/Agent.ts +2 -0
  36. package/src/agent/RemoteAgent.ts +55 -12
  37. package/src/config/Config.ts +17 -4
  38. package/src/rpc/Client.ts +310 -0
  39. package/src/rpc/Server.ts +305 -0
  40. package/src/session/SessionTitle.ts +5 -34
  41. package/src/session/browse/Browse.ts +1 -9
  42. package/src/session/index.ts +0 -2
  43. package/src/types/agent/AgentTypes.ts +9 -0
  44. package/tsconfig.tsbuildinfo +1 -1
package/src/rpc/Client.ts CHANGED
@@ -17,8 +17,19 @@ import type {
17
17
  AgentSessionSummaryPage,
18
18
  AgentSessionSystemSnapshot,
19
19
  } from "@/types/agent/AgentTypes.js";
20
+ import type { JsonValue } from "@/types/common/Json.js";
21
+ import type {
22
+ PluginActionResult,
23
+ PluginAvailability,
24
+ PluginCommandResult,
25
+ PluginStateControlAction,
26
+ PluginStateControlResult,
27
+ PluginStateSnapshot,
28
+ PluginView,
29
+ } from "@/plugin/types/Plugin.js";
20
30
  import type { AgentSessionEvent } from "@/types/sdk/AgentSessionEvent.js";
21
31
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
32
+ import type { ControlSessionExecuteAttachmentInput } from "@/runtime/server/http/control/types/ControlSessionExecute.js";
22
33
 
23
34
  type RpcClientRequest =
24
35
  | {
@@ -82,6 +93,82 @@ type RpcClientRequest =
82
93
  params: {
83
94
  subscriptionId: string;
84
95
  };
96
+ }
97
+ | {
98
+ id: string;
99
+ method: "internal.status.get";
100
+ }
101
+ | {
102
+ id: string;
103
+ method: "internal.sessions.execute";
104
+ params: {
105
+ sessionId: string;
106
+ instructions: string;
107
+ attachments?: ControlSessionExecuteAttachmentInput[];
108
+ };
109
+ }
110
+ | {
111
+ id: string;
112
+ method: "internal.sessions.clear_messages";
113
+ params: {
114
+ sessionId: string;
115
+ };
116
+ }
117
+ | {
118
+ id: string;
119
+ method: "internal.sessions.clear_chat_history";
120
+ params: {
121
+ sessionId: string;
122
+ };
123
+ }
124
+ | {
125
+ id: string;
126
+ method: "internal.sessions.resolve_system_prompt";
127
+ params: {
128
+ sessionId: string;
129
+ };
130
+ }
131
+ | {
132
+ id: string;
133
+ method: "internal.plugins.catalog";
134
+ }
135
+ | {
136
+ id: string;
137
+ method: "internal.plugins.list";
138
+ }
139
+ | {
140
+ id: string;
141
+ method: "internal.plugins.control";
142
+ params: {
143
+ pluginName: string;
144
+ action: PluginStateControlAction;
145
+ };
146
+ }
147
+ | {
148
+ id: string;
149
+ method: "internal.plugins.command";
150
+ params: {
151
+ pluginName: string;
152
+ command: string;
153
+ payload?: JsonValue;
154
+ schedule?: JsonValue;
155
+ };
156
+ }
157
+ | {
158
+ id: string;
159
+ method: "internal.plugins.availability";
160
+ params: {
161
+ pluginName: string;
162
+ };
163
+ }
164
+ | {
165
+ id: string;
166
+ method: "internal.plugins.action";
167
+ params: {
168
+ pluginName: string;
169
+ actionName: string;
170
+ payload?: JsonValue;
171
+ };
85
172
  };
86
173
 
87
174
  type RpcResponseFrame = {
@@ -132,6 +219,60 @@ export interface RpcSessionSubscription {
132
219
  unsubscribe(): Promise<void>;
133
220
  }
134
221
 
222
+ /**
223
+ * RPC system prompt 分段条目。
224
+ */
225
+ export interface RpcSystemPromptSectionItem {
226
+ /** 消息序号。 */
227
+ index: number;
228
+ /** system message 文本内容。 */
229
+ content: string;
230
+ }
231
+
232
+ /**
233
+ * RPC system prompt 分段。
234
+ */
235
+ export interface RpcSystemPromptSection {
236
+ /** 分段稳定 key。 */
237
+ key: string;
238
+ /** 分段展示标题。 */
239
+ title: string;
240
+ /** 分段内消息条目。 */
241
+ items: RpcSystemPromptSectionItem[];
242
+ }
243
+
244
+ /**
245
+ * RPC system prompt 响应。
246
+ */
247
+ export interface RpcSystemPromptPayload {
248
+ /** 请求是否成功。 */
249
+ success?: boolean;
250
+ /** 当前 session id。 */
251
+ sessionId: string;
252
+ /** system message 总数。 */
253
+ totalMessages: number;
254
+ /** system message 总字符数。 */
255
+ totalChars: number;
256
+ /** system message 分段。 */
257
+ sections: RpcSystemPromptSection[];
258
+ }
259
+
260
+ /**
261
+ * RPC session execute 响应。
262
+ */
263
+ export interface RpcSessionExecuteResult {
264
+ /** 执行是否成功。 */
265
+ success: boolean;
266
+ /** 失败错误信息。 */
267
+ error?: string;
268
+ /** assistant 原始消息。 */
269
+ assistantMessage?: unknown;
270
+ /** 用户可见文本。 */
271
+ userVisible: string;
272
+ /** 是否进入队列。 */
273
+ queued: boolean;
274
+ }
275
+
135
276
  /**
136
277
  * RPC Client。
137
278
  */
@@ -288,6 +429,175 @@ export class RpcClient {
288
429
  };
289
430
  }
290
431
 
432
+ /**
433
+ * 读取 Agent 内部状态。
434
+ */
435
+ async get_internal_status(): Promise<{ status: string }> {
436
+ return await this.request<{ status: string }>({
437
+ method: "internal.status.get",
438
+ });
439
+ }
440
+
441
+ /**
442
+ * 在 Agent runtime 内执行一轮 session 指令。
443
+ */
444
+ async execute_internal_session(params: {
445
+ session_id: string;
446
+ instructions: string;
447
+ attachments?: ControlSessionExecuteAttachmentInput[];
448
+ }): Promise<{ sessionId: string; result: RpcSessionExecuteResult }> {
449
+ return await this.request<{
450
+ sessionId: string;
451
+ result: RpcSessionExecuteResult;
452
+ }>({
453
+ method: "internal.sessions.execute",
454
+ params: {
455
+ sessionId: params.session_id,
456
+ instructions: params.instructions,
457
+ ...(params.attachments !== undefined ? { attachments: params.attachments } : {}),
458
+ },
459
+ });
460
+ }
461
+
462
+ /**
463
+ * 清空 Agent runtime 内指定 session 的消息。
464
+ */
465
+ async clear_internal_session_messages(
466
+ session_id: string,
467
+ ): Promise<{ sessionId: string; cleared: boolean }> {
468
+ return await this.request<{ sessionId: string; cleared: boolean }>({
469
+ method: "internal.sessions.clear_messages",
470
+ params: {
471
+ sessionId: session_id,
472
+ },
473
+ });
474
+ }
475
+
476
+ /**
477
+ * 清空 Agent runtime 内指定 session 的 chat history。
478
+ */
479
+ async clear_internal_chat_history(
480
+ session_id: string,
481
+ ): Promise<{ sessionId: string; cleared: boolean }> {
482
+ return await this.request<{ sessionId: string; cleared: boolean }>({
483
+ method: "internal.sessions.clear_chat_history",
484
+ params: {
485
+ sessionId: session_id,
486
+ },
487
+ });
488
+ }
489
+
490
+ /**
491
+ * 解析 Agent runtime 内指定 session 的 system prompt。
492
+ */
493
+ async resolve_internal_system_prompt(
494
+ session_id: string,
495
+ ): Promise<RpcSystemPromptPayload> {
496
+ return await this.request<RpcSystemPromptPayload>({
497
+ method: "internal.sessions.resolve_system_prompt",
498
+ params: {
499
+ sessionId: session_id,
500
+ },
501
+ });
502
+ }
503
+
504
+ /**
505
+ * 列出 Agent runtime 注册的 plugin catalog。
506
+ */
507
+ async list_internal_plugin_catalog(): Promise<PluginView[]> {
508
+ const data = await this.request<{ plugins: PluginView[] }>({
509
+ method: "internal.plugins.catalog",
510
+ });
511
+ return Array.isArray(data.plugins) ? data.plugins : [];
512
+ }
513
+
514
+ /**
515
+ * 列出 Agent runtime 内 plugin 状态。
516
+ */
517
+ async list_internal_plugin_states(): Promise<PluginStateSnapshot[]> {
518
+ const data = await this.request<{ plugins: PluginStateSnapshot[] }>({
519
+ method: "internal.plugins.list",
520
+ });
521
+ return Array.isArray(data.plugins) ? data.plugins : [];
522
+ }
523
+
524
+ /**
525
+ * 控制 Agent runtime 内 plugin 生命周期。
526
+ */
527
+ async control_internal_plugin(params: {
528
+ plugin_name: string;
529
+ action: PluginStateControlAction;
530
+ }): Promise<PluginStateControlResult> {
531
+ return await this.request<PluginStateControlResult>({
532
+ method: "internal.plugins.control",
533
+ params: {
534
+ pluginName: params.plugin_name,
535
+ action: params.action,
536
+ },
537
+ });
538
+ }
539
+
540
+ /**
541
+ * 执行 Agent runtime 内 plugin command。
542
+ */
543
+ async run_internal_plugin_command(params: {
544
+ plugin_name: string;
545
+ command: string;
546
+ payload?: JsonValue;
547
+ schedule?: JsonValue;
548
+ }): Promise<PluginCommandResult & { plugin?: PluginStateSnapshot }> {
549
+ return await this.request<PluginCommandResult & { plugin?: PluginStateSnapshot }>({
550
+ method: "internal.plugins.command",
551
+ params: {
552
+ pluginName: params.plugin_name,
553
+ command: params.command,
554
+ ...(params.payload !== undefined ? { payload: params.payload } : {}),
555
+ ...(params.schedule !== undefined ? { schedule: params.schedule } : {}),
556
+ },
557
+ });
558
+ }
559
+
560
+ /**
561
+ * 检查 Agent runtime 内 plugin 可用性。
562
+ */
563
+ async get_internal_plugin_availability(
564
+ plugin_name: string,
565
+ ): Promise<PluginAvailability> {
566
+ const data = await this.request<{
567
+ availability: PluginAvailability;
568
+ }>({
569
+ method: "internal.plugins.availability",
570
+ params: {
571
+ pluginName: plugin_name,
572
+ },
573
+ });
574
+ return data.availability;
575
+ }
576
+
577
+ /**
578
+ * 执行 Agent runtime 内 plugin action。
579
+ */
580
+ async run_internal_plugin_action(params: {
581
+ plugin_name: string;
582
+ action_name: string;
583
+ payload?: JsonValue;
584
+ }): Promise<PluginActionResult<JsonValue> & {
585
+ pluginName?: string;
586
+ actionName?: string;
587
+ }> {
588
+ return await this.request<PluginActionResult<JsonValue> & {
589
+ pluginName?: string;
590
+ actionName?: string;
591
+ }>({
592
+ method: "internal.plugins.action",
593
+ params: {
594
+ pluginName: params.plugin_name,
595
+ actionName: params.action_name,
596
+ ...(params.payload !== undefined ? { payload: params.payload } : {}),
597
+ },
598
+ });
599
+ }
600
+
291
601
  /**
292
602
  * 关闭底层连接。
293
603
  */
package/src/rpc/Server.ts CHANGED
@@ -8,12 +8,32 @@
8
8
  */
9
9
 
10
10
  import net from "node:net";
11
+ import fs from "fs-extra";
12
+ import { dirname } from "node:path";
13
+ import type { SystemModelMessage } from "ai";
11
14
  import type {
12
15
  AgentListSessionsInput,
13
16
  AgentSessionCollection,
14
17
  } from "@/types/agent/AgentTypes.js";
15
18
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
16
19
  import type { AgentSessionEvent } from "@/types/sdk/AgentSessionEvent.js";
20
+ import type { AgentContext } from "@/types/runtime/agent/AgentContext.js";
21
+ import type { AgentRuntime } from "@/types/runtime/agent/AgentRuntime.js";
22
+ import type { JsonValue } from "@/types/common/Json.js";
23
+ import type { PluginStateControlAction } from "@/plugin/types/Plugin.js";
24
+ import type { ControlSessionExecuteAttachmentInput } from "@/runtime/server/http/control/types/ControlSessionExecute.js";
25
+ import {
26
+ getDowncityChatHistoryPath,
27
+ getDowncitySessionMessagesPath,
28
+ } from "@/config/Paths.js";
29
+ import { resolveSessionSystemMessages } from "@/executor/composer/system/default/SystemDomain.js";
30
+ import {
31
+ controlPluginState,
32
+ listPluginStates,
33
+ } from "@/plugin/core/PluginStateController.js";
34
+ import { parsePluginCommandRequestBody } from "@/plugin/core/PluginCommandRequest.js";
35
+ import { runPluginCommand } from "@/plugin/core/PluginActionRunner.js";
36
+ import { executeBySessionId } from "@/runtime/server/http/control/ExecuteBySession.js";
17
37
 
18
38
  type RpcSessionRequest =
19
39
  | {
@@ -84,6 +104,82 @@ type RpcSessionRequest =
84
104
  params: {
85
105
  subscriptionId: string;
86
106
  };
107
+ }
108
+ | {
109
+ id: string;
110
+ method: "internal.status.get";
111
+ }
112
+ | {
113
+ id: string;
114
+ method: "internal.sessions.execute";
115
+ params: {
116
+ sessionId: string;
117
+ instructions: string;
118
+ attachments?: ControlSessionExecuteAttachmentInput[];
119
+ };
120
+ }
121
+ | {
122
+ id: string;
123
+ method: "internal.sessions.clear_messages";
124
+ params: {
125
+ sessionId: string;
126
+ };
127
+ }
128
+ | {
129
+ id: string;
130
+ method: "internal.sessions.clear_chat_history";
131
+ params: {
132
+ sessionId: string;
133
+ };
134
+ }
135
+ | {
136
+ id: string;
137
+ method: "internal.sessions.resolve_system_prompt";
138
+ params: {
139
+ sessionId: string;
140
+ };
141
+ }
142
+ | {
143
+ id: string;
144
+ method: "internal.plugins.catalog";
145
+ }
146
+ | {
147
+ id: string;
148
+ method: "internal.plugins.list";
149
+ }
150
+ | {
151
+ id: string;
152
+ method: "internal.plugins.control";
153
+ params: {
154
+ pluginName: string;
155
+ action: PluginStateControlAction;
156
+ };
157
+ }
158
+ | {
159
+ id: string;
160
+ method: "internal.plugins.command";
161
+ params: {
162
+ pluginName: string;
163
+ command: string;
164
+ payload?: JsonValue;
165
+ schedule?: JsonValue;
166
+ };
167
+ }
168
+ | {
169
+ id: string;
170
+ method: "internal.plugins.availability";
171
+ params: {
172
+ pluginName: string;
173
+ };
174
+ }
175
+ | {
176
+ id: string;
177
+ method: "internal.plugins.action";
178
+ params: {
179
+ pluginName: string;
180
+ actionName: string;
181
+ payload?: JsonValue;
182
+ };
87
183
  };
88
184
 
89
185
  type RpcSuccessFrame = {
@@ -119,6 +215,10 @@ export interface RpcServerStartOptions {
119
215
  host: string;
120
216
  /** Session 集合访问口。 */
121
217
  sessionCollection: AgentSessionCollection;
218
+ /** Agent 上下文访问口。 */
219
+ getAgentContext?: () => AgentContext;
220
+ /** Agent 运行态访问口。 */
221
+ getAgentRuntime?: () => AgentRuntime;
122
222
  }
123
223
 
124
224
  /**
@@ -143,7 +243,9 @@ export interface RpcServerInstance {
143
243
  export async function startRpcServer(
144
244
  options: RpcServerStartOptions,
145
245
  ): Promise<RpcServerInstance> {
246
+ const sockets = new Set<net.Socket>();
146
247
  const server = net.createServer((socket) => {
248
+ sockets.add(socket);
147
249
  const subscriptions = new Map<string, SocketSubscription>();
148
250
  let buffered = "";
149
251
 
@@ -241,6 +343,133 @@ export async function startRpcServer(
241
343
  writeSuccess(request.id, { unsubscribed: true });
242
344
  return;
243
345
  }
346
+ case "internal.status.get": {
347
+ writeSuccess(request.id, { status: "ok" });
348
+ return;
349
+ }
350
+ case "internal.sessions.execute": {
351
+ const runtime = requireAgentRuntime(options);
352
+ const context = requireAgentContext(options);
353
+ const result = await executeBySessionId({
354
+ agentState: runtime,
355
+ executionContext: context,
356
+ sessionId: request.params.sessionId,
357
+ instructions: request.params.instructions,
358
+ attachments: request.params.attachments,
359
+ });
360
+ writeSuccess(request.id, {
361
+ sessionId: request.params.sessionId,
362
+ result,
363
+ });
364
+ return;
365
+ }
366
+ case "internal.sessions.clear_messages": {
367
+ const runtime = requireAgentRuntime(options);
368
+ const sessionId = String(request.params.sessionId || "").trim();
369
+ if (!sessionId) throw new Error("Missing sessionId");
370
+ const messagesPath = getDowncitySessionMessagesPath(
371
+ runtime.rootPath,
372
+ runtime.paths.agentId,
373
+ sessionId,
374
+ );
375
+ await fs.remove(dirname(messagesPath));
376
+ runtime.getSession(sessionId).clearExecutor();
377
+ writeSuccess(request.id, {
378
+ sessionId,
379
+ cleared: true,
380
+ });
381
+ return;
382
+ }
383
+ case "internal.sessions.clear_chat_history": {
384
+ const runtime = requireAgentRuntime(options);
385
+ const sessionId = String(request.params.sessionId || "").trim();
386
+ if (!sessionId) throw new Error("Missing sessionId");
387
+ await fs.remove(getDowncityChatHistoryPath(runtime.rootPath, sessionId));
388
+ writeSuccess(request.id, {
389
+ sessionId,
390
+ cleared: true,
391
+ });
392
+ return;
393
+ }
394
+ case "internal.sessions.resolve_system_prompt": {
395
+ const runtime = requireAgentRuntime(options);
396
+ const context = requireAgentContext(options);
397
+ const sessionId = String(request.params.sessionId || "").trim() || "consoleui-chat-main";
398
+ const systemMessages = await resolveSessionSystemMessages({
399
+ projectRoot: runtime.rootPath,
400
+ sessionId,
401
+ profile: "chat",
402
+ staticSystemPrompts: runtime.systems,
403
+ context,
404
+ });
405
+ writeSuccess(request.id, {
406
+ sessionId,
407
+ ...toSystemPromptPayload(systemMessages),
408
+ });
409
+ return;
410
+ }
411
+ case "internal.plugins.catalog": {
412
+ const context = requireAgentContext(options);
413
+ writeSuccess(request.id, {
414
+ plugins: context.plugins.list(),
415
+ });
416
+ return;
417
+ }
418
+ case "internal.plugins.list": {
419
+ const context = requireAgentContext(options);
420
+ writeSuccess(request.id, {
421
+ plugins: listPluginStates({ context }),
422
+ });
423
+ return;
424
+ }
425
+ case "internal.plugins.control": {
426
+ const context = requireAgentContext(options);
427
+ const result = await controlPluginState({
428
+ pluginName: request.params.pluginName,
429
+ action: request.params.action,
430
+ context,
431
+ });
432
+ writeSuccess(request.id, result);
433
+ return;
434
+ }
435
+ case "internal.plugins.command": {
436
+ const context = requireAgentContext(options);
437
+ const body = parsePluginCommandRequestBody(request.params);
438
+ const result = await runPluginCommand({
439
+ pluginName: body.pluginName,
440
+ command: body.command,
441
+ payload: body.payload,
442
+ schedule: body.schedule,
443
+ context,
444
+ });
445
+ writeSuccess(request.id, result);
446
+ return;
447
+ }
448
+ case "internal.plugins.availability": {
449
+ const context = requireAgentContext(options);
450
+ const availability = await context.plugins.availability(
451
+ request.params.pluginName,
452
+ );
453
+ writeSuccess(request.id, {
454
+ pluginName: request.params.pluginName,
455
+ availability,
456
+ });
457
+ return;
458
+ }
459
+ case "internal.plugins.action": {
460
+ const context = requireAgentContext(options);
461
+ const result = await context.plugins.runAction({
462
+ plugin: request.params.pluginName,
463
+ action: request.params.actionName,
464
+ payload: request.params.payload,
465
+ });
466
+ writeSuccess(request.id, {
467
+ ...result,
468
+ pluginName: request.params.pluginName,
469
+ actionName: request.params.actionName,
470
+ });
471
+ return;
472
+ }
244
473
  }
245
474
  } catch (error) {
246
475
  writeError(request.id, error);
@@ -273,6 +502,7 @@ export async function startRpcServer(
273
502
  cleanupSubscriptions();
274
503
  });
275
504
  socket.on("close", () => {
505
+ sockets.delete(socket);
276
506
  cleanupSubscriptions();
277
507
  });
278
508
  socket.on("end", () => {
@@ -294,9 +524,84 @@ export async function startRpcServer(
294
524
  url: `rpc://${options.host}:${options.port}`,
295
525
  server,
296
526
  async stop(): Promise<void> {
527
+ // 关键点(中文):RPC 是长连接;停止 server 时必须主动关闭现有 socket。
528
+ for (const socket of sockets) {
529
+ socket.destroy();
530
+ }
531
+ sockets.clear();
297
532
  await new Promise<void>((resolve) => {
298
533
  server.close(() => resolve());
299
534
  });
300
535
  },
301
536
  };
302
537
  }
538
+
539
+ function requireAgentContext(options: RpcServerStartOptions): AgentContext {
540
+ const context = options.getAgentContext?.();
541
+ if (!context) {
542
+ throw new Error("Agent RPC server was started without AgentContext");
543
+ }
544
+ return context;
545
+ }
546
+
547
+ function requireAgentRuntime(options: RpcServerStartOptions): AgentRuntime {
548
+ const runtime = options.getAgentRuntime?.();
549
+ if (!runtime) {
550
+ throw new Error("Agent RPC server was started without AgentRuntime");
551
+ }
552
+ return runtime;
553
+ }
554
+
555
+ function normalizeSystemText(input: string | null | undefined): string {
556
+ return String(input || "").trim();
557
+ }
558
+
559
+ function toSystemMessageText(message: SystemModelMessage): string {
560
+ const content = message.content as unknown;
561
+ if (typeof content === "string") return normalizeSystemText(content);
562
+ if (!Array.isArray(content)) return "";
563
+ const parts = content as Array<{ text?: unknown }>;
564
+ const texts: string[] = [];
565
+ for (const part of parts) {
566
+ if (!part || typeof part !== "object") continue;
567
+ const text = normalizeSystemText(String(part.text || ""));
568
+ if (!text) continue;
569
+ texts.push(text);
570
+ }
571
+ return texts.join("\n").trim();
572
+ }
573
+
574
+ /**
575
+ * 把 system messages 转成 Console/Town 可直接渲染的结构。
576
+ */
577
+ function toSystemPromptPayload(messages: SystemModelMessage[]): {
578
+ sections: Array<{
579
+ key: string;
580
+ title: string;
581
+ items: Array<{ index: number; content: string }>;
582
+ }>;
583
+ totalMessages: number;
584
+ totalChars: number;
585
+ } {
586
+ const items = messages
587
+ .map((message, index) => ({
588
+ index: index + 1,
589
+ content: toSystemMessageText(message),
590
+ }))
591
+ .filter((item) => item.content);
592
+ const totalChars = items.reduce(
593
+ (acc, item) => acc + String(item.content || "").length,
594
+ 0,
595
+ );
596
+ return {
597
+ sections: [
598
+ {
599
+ key: "resolved",
600
+ title: "Resolved System Messages",
601
+ items,
602
+ },
603
+ ],
604
+ totalMessages: items.length,
605
+ totalChars,
606
+ };
607
+ }