@downcity/agent 1.1.69 → 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.
@@ -96,6 +96,17 @@ function assertNoProjectExtensionsLayer(
96
96
  );
97
97
  }
98
98
 
99
+ function normalizeDefaultAgentId(projectRoot: string): string {
100
+ const basename = path.basename(projectRoot);
101
+ return basename
102
+ .trim()
103
+ .toLowerCase()
104
+ .replace(/[^a-z0-9]+/g, "_")
105
+ .replace(/^_+|_+$/g, "")
106
+ .replace(/_{2,}/g, "_")
107
+ .trim() || basename || "agent";
108
+ }
109
+
99
110
  /**
100
111
  * 加载当前项目最终生效的 `downcity.json` 配置。
101
112
  *
@@ -124,10 +135,12 @@ export function loadDowncityConfig(projectRoot: string): DowncityConfig {
124
135
  }
125
136
 
126
137
  const candidate = merged as Partial<DowncityConfig>;
127
- if (typeof candidate.id !== "string" || typeof candidate.version !== "string") {
128
- throw new Error("Invalid downcity.json: missing required fields id/version");
129
- }
130
- const config = candidate as DowncityConfig;
138
+ // 关键点(中文):运行态 agent id CLI resolveAgentId 保持一致,未显式配置时用目录名兜底。
139
+ const config: DowncityConfig = {
140
+ ...candidate,
141
+ id: String(candidate.id || "").trim() || normalizeDefaultAgentId(projectRoot),
142
+ version: String(candidate.version || "").trim() || "1.0.0",
143
+ };
131
144
  assertProjectExecutionTarget(config);
132
145
  return config;
133
146
  }
package/src/rpc/Client.ts CHANGED
@@ -29,6 +29,7 @@ import type {
29
29
  } from "@/plugin/types/Plugin.js";
30
30
  import type { AgentSessionEvent } from "@/types/sdk/AgentSessionEvent.js";
31
31
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
32
+ import type { ControlSessionExecuteAttachmentInput } from "@/runtime/server/http/control/types/ControlSessionExecute.js";
32
33
 
33
34
  type RpcClientRequest =
34
35
  | {
@@ -97,6 +98,36 @@ type RpcClientRequest =
97
98
  id: string;
98
99
  method: "internal.status.get";
99
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
+ }
100
131
  | {
101
132
  id: string;
102
133
  method: "internal.plugins.catalog";
@@ -188,6 +219,60 @@ export interface RpcSessionSubscription {
188
219
  unsubscribe(): Promise<void>;
189
220
  }
190
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
+
191
276
  /**
192
277
  * RPC Client。
193
278
  */
@@ -353,6 +438,69 @@ export class RpcClient {
353
438
  });
354
439
  }
355
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
+
356
504
  /**
357
505
  * 列出 Agent runtime 注册的 plugin catalog。
358
506
  */
package/src/rpc/Server.ts CHANGED
@@ -8,6 +8,9 @@
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,
@@ -15,14 +18,22 @@ import type {
15
18
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
16
19
  import type { AgentSessionEvent } from "@/types/sdk/AgentSessionEvent.js";
17
20
  import type { AgentContext } from "@/types/runtime/agent/AgentContext.js";
21
+ import type { AgentRuntime } from "@/types/runtime/agent/AgentRuntime.js";
18
22
  import type { JsonValue } from "@/types/common/Json.js";
19
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";
20
30
  import {
21
31
  controlPluginState,
22
32
  listPluginStates,
23
33
  } from "@/plugin/core/PluginStateController.js";
24
34
  import { parsePluginCommandRequestBody } from "@/plugin/core/PluginCommandRequest.js";
25
35
  import { runPluginCommand } from "@/plugin/core/PluginActionRunner.js";
36
+ import { executeBySessionId } from "@/runtime/server/http/control/ExecuteBySession.js";
26
37
 
27
38
  type RpcSessionRequest =
28
39
  | {
@@ -98,6 +109,36 @@ type RpcSessionRequest =
98
109
  id: string;
99
110
  method: "internal.status.get";
100
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
+ }
101
142
  | {
102
143
  id: string;
103
144
  method: "internal.plugins.catalog";
@@ -176,6 +217,8 @@ export interface RpcServerStartOptions {
176
217
  sessionCollection: AgentSessionCollection;
177
218
  /** Agent 上下文访问口。 */
178
219
  getAgentContext?: () => AgentContext;
220
+ /** Agent 运行态访问口。 */
221
+ getAgentRuntime?: () => AgentRuntime;
179
222
  }
180
223
 
181
224
  /**
@@ -304,6 +347,67 @@ export async function startRpcServer(
304
347
  writeSuccess(request.id, { status: "ok" });
305
348
  return;
306
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
+ }
307
411
  case "internal.plugins.catalog": {
308
412
  const context = requireAgentContext(options);
309
413
  writeSuccess(request.id, {
@@ -439,3 +543,65 @@ function requireAgentContext(options: RpcServerStartOptions): AgentContext {
439
543
  }
440
544
  return context;
441
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
+ }