@downcity/agent 1.1.66 → 1.1.69

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 (40) hide show
  1. package/bin/agent/Agent.d.ts.map +1 -1
  2. package/bin/agent/Agent.js +1 -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/rpc/Client.d.ts +49 -0
  9. package/bin/rpc/Client.d.ts.map +1 -1
  10. package/bin/rpc/Client.js +77 -0
  11. package/bin/rpc/Client.js.map +1 -1
  12. package/bin/rpc/Server.d.ts +3 -0
  13. package/bin/rpc/Server.d.ts.map +1 -1
  14. package/bin/rpc/Server.js +82 -0
  15. package/bin/rpc/Server.js.map +1 -1
  16. package/bin/session/SessionTitle.d.ts +2 -6
  17. package/bin/session/SessionTitle.d.ts.map +1 -1
  18. package/bin/session/SessionTitle.js +5 -27
  19. package/bin/session/SessionTitle.js.map +1 -1
  20. package/bin/session/browse/Browse.d.ts +1 -5
  21. package/bin/session/browse/Browse.d.ts.map +1 -1
  22. package/bin/session/browse/Browse.js +2 -8
  23. package/bin/session/browse/Browse.js.map +1 -1
  24. package/bin/session/index.d.ts +2 -2
  25. package/bin/session/index.d.ts.map +1 -1
  26. package/bin/session/index.js +2 -2
  27. package/bin/session/index.js.map +1 -1
  28. package/bin/types/agent/AgentTypes.d.ts +8 -0
  29. package/bin/types/agent/AgentTypes.d.ts.map +1 -1
  30. package/package.json +2 -2
  31. package/scripts/session-title-event.test.mjs +74 -7
  32. package/src/agent/Agent.ts +1 -0
  33. package/src/agent/RemoteAgent.ts +55 -12
  34. package/src/rpc/Client.ts +162 -0
  35. package/src/rpc/Server.ts +139 -0
  36. package/src/session/SessionTitle.ts +5 -34
  37. package/src/session/browse/Browse.ts +1 -9
  38. package/src/session/index.ts +0 -2
  39. package/src/types/agent/AgentTypes.ts +9 -0
  40. package/tsconfig.tsbuildinfo +1 -1
@@ -115,6 +115,10 @@ type RemoteAgentTransport = RemoteSessionTransport & {
115
115
  * 列出 sessions。
116
116
  */
117
117
  list_sessions(input?: AgentListSessionsInput): Promise<AgentSessionSummaryPage>;
118
+ /**
119
+ * 关闭 transport 持有的长期连接。
120
+ */
121
+ close?(): Promise<void>;
118
122
  };
119
123
 
120
124
  type SdkEventsReadyFrame = {
@@ -336,7 +340,7 @@ export class RemoteAgent {
336
340
  if (!url) {
337
341
  throw new Error("RemoteAgent requires a non-empty url");
338
342
  }
339
- this.transport = create_remote_agent_transport(url);
343
+ this.transport = create_remote_agent_transport(url, options.token);
340
344
  }
341
345
 
342
346
  /**
@@ -369,13 +373,34 @@ export class RemoteAgent {
369
373
  ): Promise<AgentSessionSummaryPage> {
370
374
  return await this.transport.list_sessions(input);
371
375
  }
376
+
377
+ /**
378
+ * 关闭远程 transport。
379
+ *
380
+ * 关键点(中文)
381
+ * - `rpc://` 会关闭底层长连接。
382
+ * - `http://` / `https://` 没有常驻连接,调用时是安全 no-op。
383
+ */
384
+ async close(): Promise<void> {
385
+ await this.transport.close?.();
386
+ }
372
387
  }
373
388
 
374
389
  class HttpRemoteAgentTransport implements RemoteAgentTransport {
375
390
  private readonly base_url: string;
391
+ private readonly token: string;
376
392
 
377
- constructor(url: string) {
393
+ constructor(url: string, token?: string) {
378
394
  this.base_url = url.replace(/\/+$/, "");
395
+ this.token = String(token || "").trim();
396
+ }
397
+
398
+ private headers(input?: Record<string, string>): Headers {
399
+ const headers = new Headers(input);
400
+ if (this.token) {
401
+ headers.set("Authorization", `Bearer ${this.token}`);
402
+ }
403
+ return headers;
379
404
  }
380
405
 
381
406
  async create_session(input?: AgentCreateSessionInput): Promise<AgentSessionInfo> {
@@ -385,9 +410,9 @@ class HttpRemoteAgentTransport implements RemoteAgentTransport {
385
410
  session?: AgentSessionInfo;
386
411
  }>(`${this.base_url}/api/sdk/sessions`, {
387
412
  method: "POST",
388
- headers: {
413
+ headers: this.headers({
389
414
  "Content-Type": "application/json",
390
- },
415
+ }),
391
416
  body: JSON.stringify({
392
417
  ...(input?.sessionId ? { sessionId: input.sessionId } : {}),
393
418
  }),
@@ -403,7 +428,9 @@ class HttpRemoteAgentTransport implements RemoteAgentTransport {
403
428
  success?: boolean;
404
429
  error?: string;
405
430
  session?: AgentSessionInfo;
406
- }>(`${this.base_url}/api/sdk/sessions/${encodeURIComponent(session_id)}`);
431
+ }>(`${this.base_url}/api/sdk/sessions/${encodeURIComponent(session_id)}`, {
432
+ headers: this.headers(),
433
+ });
407
434
  if (!payload.success || !payload.session?.sessionId) {
408
435
  throw new Error(String(payload.error || "Remote session info failed"));
409
436
  }
@@ -422,9 +449,9 @@ class HttpRemoteAgentTransport implements RemoteAgentTransport {
422
449
  };
423
450
  }>(`${this.base_url}/api/sdk/sessions/${encodeURIComponent(session_id)}/prompt`, {
424
451
  method: "POST",
425
- headers: {
452
+ headers: this.headers({
426
453
  "Content-Type": "application/json",
427
- },
454
+ }),
428
455
  body: JSON.stringify({
429
456
  query: input.query,
430
457
  }),
@@ -451,6 +478,7 @@ class HttpRemoteAgentTransport implements RemoteAgentTransport {
451
478
  const response = await fetch(
452
479
  `${this.base_url}/api/sdk/sessions/${encodeURIComponent(params.session_id)}/events`,
453
480
  {
481
+ headers: this.headers(),
454
482
  signal: abort_controller.signal,
455
483
  },
456
484
  );
@@ -495,6 +523,9 @@ class HttpRemoteAgentTransport implements RemoteAgentTransport {
495
523
  `${this.base_url}/api/sdk/sessions/${encodeURIComponent(session_id)}/history${
496
524
  query.size > 0 ? `?${query.toString()}` : ""
497
525
  }`,
526
+ {
527
+ headers: this.headers(),
528
+ },
498
529
  );
499
530
  if (!payload.success || !payload.history || !Array.isArray(payload.history.items)) {
500
531
  throw new Error(String(payload.error || "Remote session history failed"));
@@ -507,7 +538,9 @@ class HttpRemoteAgentTransport implements RemoteAgentTransport {
507
538
  success?: boolean;
508
539
  error?: string;
509
540
  system?: AgentSessionSystemSnapshot;
510
- }>(`${this.base_url}/api/sdk/sessions/${encodeURIComponent(session_id)}/system`);
541
+ }>(`${this.base_url}/api/sdk/sessions/${encodeURIComponent(session_id)}/system`, {
542
+ headers: this.headers(),
543
+ });
511
544
  if (!payload.success || !payload.system || !Array.isArray(payload.system.blocks)) {
512
545
  throw new Error(String(payload.error || "Remote session system failed"));
513
546
  }
@@ -528,9 +561,9 @@ class HttpRemoteAgentTransport implements RemoteAgentTransport {
528
561
  session?: AgentSessionInfo;
529
562
  }>(`${this.base_url}/api/sdk/sessions/${encodeURIComponent(session_id)}/fork`, {
530
563
  method: "POST",
531
- headers: {
564
+ headers: this.headers({
532
565
  "Content-Type": "application/json",
533
- },
566
+ }),
534
567
  body: JSON.stringify({
535
568
  ...(message_id ? { messageId: message_id } : {}),
536
569
  }),
@@ -552,6 +585,9 @@ class HttpRemoteAgentTransport implements RemoteAgentTransport {
552
585
  page?: AgentSessionSummaryPage;
553
586
  }>(
554
587
  `${this.base_url}/api/sdk/sessions${query.size > 0 ? `?${query.toString()}` : ""}`,
588
+ {
589
+ headers: this.headers(),
590
+ },
555
591
  );
556
592
  if (!payload.success || !payload.page) {
557
593
  throw new Error(String(payload.error || "Remote sessions list failed"));
@@ -633,11 +669,18 @@ class RpcRemoteAgentTransport implements RemoteAgentTransport {
633
669
  async list_sessions(input?: AgentListSessionsInput): Promise<AgentSessionSummaryPage> {
634
670
  return await this.client.list_sessions(input);
635
671
  }
672
+
673
+ async close(): Promise<void> {
674
+ await this.client.close();
675
+ }
636
676
  }
637
677
 
638
- function create_remote_agent_transport(url: string): RemoteAgentTransport {
678
+ function create_remote_agent_transport(
679
+ url: string,
680
+ token?: string,
681
+ ): RemoteAgentTransport {
639
682
  if (/^https?:\/\//i.test(url)) {
640
- return new HttpRemoteAgentTransport(url);
683
+ return new HttpRemoteAgentTransport(url, token);
641
684
  }
642
685
  if (/^rpc:\/\//i.test(url)) {
643
686
  return new RpcRemoteAgentTransport(url);
package/src/rpc/Client.ts CHANGED
@@ -17,6 +17,16 @@ 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";
22
32
 
@@ -82,6 +92,52 @@ type RpcClientRequest =
82
92
  params: {
83
93
  subscriptionId: string;
84
94
  };
95
+ }
96
+ | {
97
+ id: string;
98
+ method: "internal.status.get";
99
+ }
100
+ | {
101
+ id: string;
102
+ method: "internal.plugins.catalog";
103
+ }
104
+ | {
105
+ id: string;
106
+ method: "internal.plugins.list";
107
+ }
108
+ | {
109
+ id: string;
110
+ method: "internal.plugins.control";
111
+ params: {
112
+ pluginName: string;
113
+ action: PluginStateControlAction;
114
+ };
115
+ }
116
+ | {
117
+ id: string;
118
+ method: "internal.plugins.command";
119
+ params: {
120
+ pluginName: string;
121
+ command: string;
122
+ payload?: JsonValue;
123
+ schedule?: JsonValue;
124
+ };
125
+ }
126
+ | {
127
+ id: string;
128
+ method: "internal.plugins.availability";
129
+ params: {
130
+ pluginName: string;
131
+ };
132
+ }
133
+ | {
134
+ id: string;
135
+ method: "internal.plugins.action";
136
+ params: {
137
+ pluginName: string;
138
+ actionName: string;
139
+ payload?: JsonValue;
140
+ };
85
141
  };
86
142
 
87
143
  type RpcResponseFrame = {
@@ -288,6 +344,112 @@ export class RpcClient {
288
344
  };
289
345
  }
290
346
 
347
+ /**
348
+ * 读取 Agent 内部状态。
349
+ */
350
+ async get_internal_status(): Promise<{ status: string }> {
351
+ return await this.request<{ status: string }>({
352
+ method: "internal.status.get",
353
+ });
354
+ }
355
+
356
+ /**
357
+ * 列出 Agent runtime 注册的 plugin catalog。
358
+ */
359
+ async list_internal_plugin_catalog(): Promise<PluginView[]> {
360
+ const data = await this.request<{ plugins: PluginView[] }>({
361
+ method: "internal.plugins.catalog",
362
+ });
363
+ return Array.isArray(data.plugins) ? data.plugins : [];
364
+ }
365
+
366
+ /**
367
+ * 列出 Agent runtime 内 plugin 状态。
368
+ */
369
+ async list_internal_plugin_states(): Promise<PluginStateSnapshot[]> {
370
+ const data = await this.request<{ plugins: PluginStateSnapshot[] }>({
371
+ method: "internal.plugins.list",
372
+ });
373
+ return Array.isArray(data.plugins) ? data.plugins : [];
374
+ }
375
+
376
+ /**
377
+ * 控制 Agent runtime 内 plugin 生命周期。
378
+ */
379
+ async control_internal_plugin(params: {
380
+ plugin_name: string;
381
+ action: PluginStateControlAction;
382
+ }): Promise<PluginStateControlResult> {
383
+ return await this.request<PluginStateControlResult>({
384
+ method: "internal.plugins.control",
385
+ params: {
386
+ pluginName: params.plugin_name,
387
+ action: params.action,
388
+ },
389
+ });
390
+ }
391
+
392
+ /**
393
+ * 执行 Agent runtime 内 plugin command。
394
+ */
395
+ async run_internal_plugin_command(params: {
396
+ plugin_name: string;
397
+ command: string;
398
+ payload?: JsonValue;
399
+ schedule?: JsonValue;
400
+ }): Promise<PluginCommandResult & { plugin?: PluginStateSnapshot }> {
401
+ return await this.request<PluginCommandResult & { plugin?: PluginStateSnapshot }>({
402
+ method: "internal.plugins.command",
403
+ params: {
404
+ pluginName: params.plugin_name,
405
+ command: params.command,
406
+ ...(params.payload !== undefined ? { payload: params.payload } : {}),
407
+ ...(params.schedule !== undefined ? { schedule: params.schedule } : {}),
408
+ },
409
+ });
410
+ }
411
+
412
+ /**
413
+ * 检查 Agent runtime 内 plugin 可用性。
414
+ */
415
+ async get_internal_plugin_availability(
416
+ plugin_name: string,
417
+ ): Promise<PluginAvailability> {
418
+ const data = await this.request<{
419
+ availability: PluginAvailability;
420
+ }>({
421
+ method: "internal.plugins.availability",
422
+ params: {
423
+ pluginName: plugin_name,
424
+ },
425
+ });
426
+ return data.availability;
427
+ }
428
+
429
+ /**
430
+ * 执行 Agent runtime 内 plugin action。
431
+ */
432
+ async run_internal_plugin_action(params: {
433
+ plugin_name: string;
434
+ action_name: string;
435
+ payload?: JsonValue;
436
+ }): Promise<PluginActionResult<JsonValue> & {
437
+ pluginName?: string;
438
+ actionName?: string;
439
+ }> {
440
+ return await this.request<PluginActionResult<JsonValue> & {
441
+ pluginName?: string;
442
+ actionName?: string;
443
+ }>({
444
+ method: "internal.plugins.action",
445
+ params: {
446
+ pluginName: params.plugin_name,
447
+ actionName: params.action_name,
448
+ ...(params.payload !== undefined ? { payload: params.payload } : {}),
449
+ },
450
+ });
451
+ }
452
+
291
453
  /**
292
454
  * 关闭底层连接。
293
455
  */
package/src/rpc/Server.ts CHANGED
@@ -14,6 +14,15 @@ import type {
14
14
  } from "@/types/agent/AgentTypes.js";
15
15
  import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
16
16
  import type { AgentSessionEvent } from "@/types/sdk/AgentSessionEvent.js";
17
+ import type { AgentContext } from "@/types/runtime/agent/AgentContext.js";
18
+ import type { JsonValue } from "@/types/common/Json.js";
19
+ import type { PluginStateControlAction } from "@/plugin/types/Plugin.js";
20
+ import {
21
+ controlPluginState,
22
+ listPluginStates,
23
+ } from "@/plugin/core/PluginStateController.js";
24
+ import { parsePluginCommandRequestBody } from "@/plugin/core/PluginCommandRequest.js";
25
+ import { runPluginCommand } from "@/plugin/core/PluginActionRunner.js";
17
26
 
18
27
  type RpcSessionRequest =
19
28
  | {
@@ -84,6 +93,52 @@ type RpcSessionRequest =
84
93
  params: {
85
94
  subscriptionId: string;
86
95
  };
96
+ }
97
+ | {
98
+ id: string;
99
+ method: "internal.status.get";
100
+ }
101
+ | {
102
+ id: string;
103
+ method: "internal.plugins.catalog";
104
+ }
105
+ | {
106
+ id: string;
107
+ method: "internal.plugins.list";
108
+ }
109
+ | {
110
+ id: string;
111
+ method: "internal.plugins.control";
112
+ params: {
113
+ pluginName: string;
114
+ action: PluginStateControlAction;
115
+ };
116
+ }
117
+ | {
118
+ id: string;
119
+ method: "internal.plugins.command";
120
+ params: {
121
+ pluginName: string;
122
+ command: string;
123
+ payload?: JsonValue;
124
+ schedule?: JsonValue;
125
+ };
126
+ }
127
+ | {
128
+ id: string;
129
+ method: "internal.plugins.availability";
130
+ params: {
131
+ pluginName: string;
132
+ };
133
+ }
134
+ | {
135
+ id: string;
136
+ method: "internal.plugins.action";
137
+ params: {
138
+ pluginName: string;
139
+ actionName: string;
140
+ payload?: JsonValue;
141
+ };
87
142
  };
88
143
 
89
144
  type RpcSuccessFrame = {
@@ -119,6 +174,8 @@ export interface RpcServerStartOptions {
119
174
  host: string;
120
175
  /** Session 集合访问口。 */
121
176
  sessionCollection: AgentSessionCollection;
177
+ /** Agent 上下文访问口。 */
178
+ getAgentContext?: () => AgentContext;
122
179
  }
123
180
 
124
181
  /**
@@ -143,7 +200,9 @@ export interface RpcServerInstance {
143
200
  export async function startRpcServer(
144
201
  options: RpcServerStartOptions,
145
202
  ): Promise<RpcServerInstance> {
203
+ const sockets = new Set<net.Socket>();
146
204
  const server = net.createServer((socket) => {
205
+ sockets.add(socket);
147
206
  const subscriptions = new Map<string, SocketSubscription>();
148
207
  let buffered = "";
149
208
 
@@ -241,6 +300,72 @@ export async function startRpcServer(
241
300
  writeSuccess(request.id, { unsubscribed: true });
242
301
  return;
243
302
  }
303
+ case "internal.status.get": {
304
+ writeSuccess(request.id, { status: "ok" });
305
+ return;
306
+ }
307
+ case "internal.plugins.catalog": {
308
+ const context = requireAgentContext(options);
309
+ writeSuccess(request.id, {
310
+ plugins: context.plugins.list(),
311
+ });
312
+ return;
313
+ }
314
+ case "internal.plugins.list": {
315
+ const context = requireAgentContext(options);
316
+ writeSuccess(request.id, {
317
+ plugins: listPluginStates({ context }),
318
+ });
319
+ return;
320
+ }
321
+ case "internal.plugins.control": {
322
+ const context = requireAgentContext(options);
323
+ const result = await controlPluginState({
324
+ pluginName: request.params.pluginName,
325
+ action: request.params.action,
326
+ context,
327
+ });
328
+ writeSuccess(request.id, result);
329
+ return;
330
+ }
331
+ case "internal.plugins.command": {
332
+ const context = requireAgentContext(options);
333
+ const body = parsePluginCommandRequestBody(request.params);
334
+ const result = await runPluginCommand({
335
+ pluginName: body.pluginName,
336
+ command: body.command,
337
+ payload: body.payload,
338
+ schedule: body.schedule,
339
+ context,
340
+ });
341
+ writeSuccess(request.id, result);
342
+ return;
343
+ }
344
+ case "internal.plugins.availability": {
345
+ const context = requireAgentContext(options);
346
+ const availability = await context.plugins.availability(
347
+ request.params.pluginName,
348
+ );
349
+ writeSuccess(request.id, {
350
+ pluginName: request.params.pluginName,
351
+ availability,
352
+ });
353
+ return;
354
+ }
355
+ case "internal.plugins.action": {
356
+ const context = requireAgentContext(options);
357
+ const result = await context.plugins.runAction({
358
+ plugin: request.params.pluginName,
359
+ action: request.params.actionName,
360
+ payload: request.params.payload,
361
+ });
362
+ writeSuccess(request.id, {
363
+ ...result,
364
+ pluginName: request.params.pluginName,
365
+ actionName: request.params.actionName,
366
+ });
367
+ return;
368
+ }
244
369
  }
245
370
  } catch (error) {
246
371
  writeError(request.id, error);
@@ -273,6 +398,7 @@ export async function startRpcServer(
273
398
  cleanupSubscriptions();
274
399
  });
275
400
  socket.on("close", () => {
401
+ sockets.delete(socket);
276
402
  cleanupSubscriptions();
277
403
  });
278
404
  socket.on("end", () => {
@@ -294,9 +420,22 @@ export async function startRpcServer(
294
420
  url: `rpc://${options.host}:${options.port}`,
295
421
  server,
296
422
  async stop(): Promise<void> {
423
+ // 关键点(中文):RPC 是长连接;停止 server 时必须主动关闭现有 socket。
424
+ for (const socket of sockets) {
425
+ socket.destroy();
426
+ }
427
+ sockets.clear();
297
428
  await new Promise<void>((resolve) => {
298
429
  server.close(() => resolve());
299
430
  });
300
431
  },
301
432
  };
302
433
  }
434
+
435
+ function requireAgentContext(options: RpcServerStartOptions): AgentContext {
436
+ const context = options.getAgentContext?.();
437
+ if (!context) {
438
+ throw new Error("Agent RPC server was started without AgentContext");
439
+ }
440
+ return context;
441
+ }
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * 关键点(中文)
5
5
  * - session title 是 `meta.json` 顶层字段,列表与详情都以它为准。
6
- * - 首次用户消息出现后优先使用模型生成标题,失败时回退到首条用户消息截断。
7
- * - session 缺失 title 时可在读取列表/详情时补写 fallback title。
6
+ * - title 默认允许为空;只有模型成功生成标题时才会写入。
7
+ * - title 仍为空时,后续执行链路可以再次尝试生成。
8
8
  */
9
9
 
10
10
  import { generateText, type LanguageModel } from "ai";
@@ -16,7 +16,6 @@ import {
16
16
  writeSessionMetadata,
17
17
  } from "@/session/storage/Metadata.js";
18
18
 
19
- const FALLBACK_SESSION_TITLE_MAX_CHARS = 60;
20
19
  const GENERATED_SESSION_TITLE_MAX_CHARS = 24;
21
20
 
22
21
  /**
@@ -100,20 +99,6 @@ function normalizeGeneratedTitle(input: string): string | undefined {
100
99
  : undefined;
101
100
  }
102
101
 
103
- /**
104
- * 从首条用户消息推导 fallback 标题。
105
- */
106
- export function resolveFallbackSessionTitle(
107
- messages: SessionMessageV1[],
108
- ): string | undefined {
109
- const firstUserText = resolveFirstUserText(messages);
110
- const title = truncateTitle(
111
- firstUserText,
112
- FALLBACK_SESSION_TITLE_MAX_CHARS,
113
- );
114
- return normalizeSessionTitle(title);
115
- }
116
-
117
102
  async function generateSessionTitle(input: {
118
103
  /**
119
104
  * 当前模型实例。
@@ -154,32 +139,18 @@ export async function ensureSessionTitle(
154
139
  if (current.title) return current;
155
140
 
156
141
  const firstUserText = resolveFirstUserText(input.messages);
157
- const fallbackTitle = resolveFallbackSessionTitle(input.messages);
158
- if (!fallbackTitle) return current;
159
-
160
- const fallbackMeta: SessionHistoryMetaV1 = {
161
- ...current,
162
- title: fallbackTitle,
163
- };
164
- await writeSessionMetadata({
165
- projectRoot: input.projectRoot,
166
- agentId: input.agentId,
167
- sessionId: input.sessionId,
168
- meta: fallbackMeta,
169
- });
170
-
171
142
  if (input.generate !== true || !input.model || !firstUserText) {
172
- return fallbackMeta;
143
+ return current;
173
144
  }
174
145
 
175
146
  const generatedTitle = await generateSessionTitle({
176
147
  model: input.model,
177
148
  firstUserText,
178
149
  });
179
- if (!generatedTitle) return fallbackMeta;
150
+ if (!generatedTitle) return current;
180
151
 
181
152
  const generatedMeta: SessionHistoryMetaV1 = {
182
- ...fallbackMeta,
153
+ ...current,
183
154
  title: generatedTitle,
184
155
  };
185
156
  await writeSessionMetadata({
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 关键点(中文)
5
5
  * - 统一负责 session 列表摘要、session 详情与 history 分页的投影逻辑。
6
- * - session 缺失持久化 title 时,会在列表读取阶段补写 fallback title。
6
+ * - session title 允许为空;浏览层不会再从首条 user message 推导 fallback title。
7
7
  * - 面向 SDK / RemoteAgent / HTTP route 复用,避免在多个入口重复拼列表与分页语义。
8
8
  * - 这里不持有运行态状态;执行状态等动态信息通过调用参数显式注入。
9
9
  */
@@ -36,7 +36,6 @@ import { getSdkAgentSessionsRootDirPath } from "@/session/storage/Paths.js";
36
36
  import { readSessionMetadata } from "@/session/storage/Metadata.js";
37
37
  import {
38
38
  ensureSessionTitle,
39
- resolveFallbackSessionTitle,
40
39
  } from "@/session/SessionTitle.js";
41
40
 
42
41
  type AnyUiPart = UIMessagePart<Record<string, never>, Record<string, never>>;
@@ -185,13 +184,6 @@ export function resolveSessionMessagePreview(message: SessionMessageV1): string
185
184
  return extractAssistantToolSummary(message);
186
185
  }
187
186
 
188
- /**
189
- * 推导当前 session 的可读标题。
190
- */
191
- export function resolveSessionTitle(messages: SessionMessageV1[]): string | undefined {
192
- return resolveFallbackSessionTitle(messages);
193
- }
194
-
195
187
  function resolveToolName(part: ToolPartCompatShape, aiToolName?: string): string {
196
188
  const fromAi = String(aiToolName || "").trim();
197
189
  if (fromAi) return fromAi;
@@ -29,7 +29,6 @@ export {
29
29
  } from "./storage/Metadata.js";
30
30
  export {
31
31
  ensureSessionTitle,
32
- resolveFallbackSessionTitle,
33
32
  } from "./SessionTitle.js";
34
33
  export {
35
34
  persistSdkAssistantResult,
@@ -44,6 +43,5 @@ export {
44
43
  listAgentSessionSummaryPage,
45
44
  loadSessionMessagesFromPath,
46
45
  resolveSessionMessagePreview,
47
- resolveSessionTitle,
48
46
  toSessionTimelineEvents,
49
47
  } from "./browse/Browse.js";
@@ -196,6 +196,15 @@ export interface RemoteAgentOptions {
196
196
  * 或 `rpc://127.0.0.1:5314`
197
197
  */
198
198
  url: string;
199
+
200
+ /**
201
+ * 访问远程 HTTP Agent 时使用的 Bearer token。
202
+ *
203
+ * 关键点(中文)
204
+ * - 仅 `http://` / `https://` transport 会携带该 token。
205
+ * - `rpc://` 是本机内部直连通道,不读取也不发送 token。
206
+ */
207
+ token?: string;
199
208
  }
200
209
 
201
210
  /**