@downcity/agent 1.1.446 → 1.5.0
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.
- package/bin/agent/Agent.d.ts +29 -2
- package/bin/agent/Agent.d.ts.map +1 -1
- package/bin/agent/Agent.js +56 -10
- package/bin/agent/Agent.js.map +1 -1
- package/bin/agent/AgentSessionIdentity.d.ts +19 -0
- package/bin/agent/AgentSessionIdentity.d.ts.map +1 -0
- package/bin/agent/AgentSessionIdentity.js +26 -0
- package/bin/agent/AgentSessionIdentity.js.map +1 -0
- package/bin/agent/AgentSessions.d.ts +35 -9
- package/bin/agent/AgentSessions.d.ts.map +1 -1
- package/bin/agent/AgentSessions.js +110 -63
- package/bin/agent/AgentSessions.js.map +1 -1
- package/bin/agent/SessionWork.d.ts +85 -0
- package/bin/agent/SessionWork.d.ts.map +1 -0
- package/bin/agent/SessionWork.js +165 -0
- package/bin/agent/SessionWork.js.map +1 -0
- package/bin/index.d.ts +5 -0
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js.map +1 -1
- package/bin/session/Session.d.ts +34 -8
- package/bin/session/Session.d.ts.map +1 -1
- package/bin/session/Session.js +102 -27
- package/bin/session/Session.js.map +1 -1
- package/bin/session/input/StepInput.d.ts +15 -0
- package/bin/session/input/StepInput.d.ts.map +1 -1
- package/bin/session/input/StepInput.js +39 -5
- package/bin/session/input/StepInput.js.map +1 -1
- package/bin/session/loop/SessionLoop.d.ts +2 -1
- package/bin/session/loop/SessionLoop.d.ts.map +1 -1
- package/bin/session/loop/SessionLoop.js +7 -9
- package/bin/session/loop/SessionLoop.js.map +1 -1
- package/bin/session/loop/SessionState.d.ts +21 -1
- package/bin/session/loop/SessionState.d.ts.map +1 -1
- package/bin/session/loop/SessionState.js +54 -1
- package/bin/session/loop/SessionState.js.map +1 -1
- package/bin/session/storage/Browse.d.ts.map +1 -1
- package/bin/session/storage/Browse.js +2 -2
- package/bin/session/storage/Browse.js.map +1 -1
- package/bin/session/storage/LocalSessionStore.d.ts.map +1 -1
- package/bin/session/storage/LocalSessionStore.js +7 -7
- package/bin/session/storage/LocalSessionStore.js.map +1 -1
- package/bin/tools/session/SessionToolSchemas.d.ts +34 -0
- package/bin/tools/session/SessionToolSchemas.d.ts.map +1 -0
- package/bin/tools/session/SessionToolSchemas.js +39 -0
- package/bin/tools/session/SessionToolSchemas.js.map +1 -0
- package/bin/tools/session/SessionTools.d.ts +57 -0
- package/bin/tools/session/SessionTools.d.ts.map +1 -0
- package/bin/tools/session/SessionTools.js +111 -0
- package/bin/tools/session/SessionTools.js.map +1 -0
- package/bin/types/agent/AgentOptions.d.ts +10 -1
- package/bin/types/agent/AgentOptions.d.ts.map +1 -1
- package/bin/types/agent/AgentSessionCollection.d.ts +17 -6
- package/bin/types/agent/AgentSessionCollection.d.ts.map +1 -1
- package/bin/types/agent/AgentSessionCollection.js +1 -1
- package/bin/types/agent/AgentSessionInput.d.ts +14 -0
- package/bin/types/agent/AgentSessionInput.d.ts.map +1 -0
- package/bin/types/agent/AgentSessionInput.js +9 -0
- package/bin/types/agent/AgentSessionInput.js.map +1 -0
- package/bin/types/agent/SessionActor.d.ts +16 -5
- package/bin/types/agent/SessionActor.d.ts.map +1 -1
- package/bin/types/agent/SessionContext.d.ts +26 -0
- package/bin/types/agent/SessionContext.d.ts.map +1 -0
- package/bin/types/agent/SessionContext.js +12 -0
- package/bin/types/agent/SessionContext.js.map +1 -0
- package/bin/types/agent/SessionState.d.ts +55 -0
- package/bin/types/agent/SessionState.d.ts.map +1 -0
- package/bin/types/agent/SessionState.js +15 -0
- package/bin/types/agent/SessionState.js.map +1 -0
- package/bin/types/agent/SessionTypes.d.ts +12 -1
- package/bin/types/agent/SessionTypes.d.ts.map +1 -1
- package/bin/types/agent/SessionWork.d.ts +53 -0
- package/bin/types/agent/SessionWork.d.ts.map +1 -0
- package/bin/types/agent/SessionWork.js +13 -0
- package/bin/types/agent/SessionWork.js.map +1 -0
- package/bin/types/session/SessionLocalState.d.ts +20 -1
- package/bin/types/session/SessionLocalState.d.ts.map +1 -1
- package/bin/types/session/SessionLoop.d.ts +6 -2
- package/bin/types/session/SessionLoop.d.ts.map +1 -1
- package/bin/types/session/SessionOptions.d.ts +9 -11
- package/bin/types/session/SessionOptions.d.ts.map +1 -1
- package/bin/types/session/StepInput.d.ts +5 -5
- package/bin/types/session/StepInput.d.ts.map +1 -1
- package/bin/types/tools/session/SessionTools.d.ts +92 -0
- package/bin/types/tools/session/SessionTools.d.ts.map +1 -0
- package/bin/types/tools/session/SessionTools.js +12 -0
- package/bin/types/tools/session/SessionTools.js.map +1 -0
- package/package.json +2 -2
- package/scripts/session-executor-context-pressure.test.mjs +1 -1
- package/scripts/session-executor-failure-result.test.mjs +1 -1
- package/scripts/session-executor-max-steps.test.mjs +1 -1
- package/scripts/session-initialization.test.mjs +4 -2
- package/scripts/session-interaction-migration.test.mjs +2 -2
- package/scripts/session-prompt-idempotency.test.mjs +2 -2
- package/scripts/session-storage-sqlite.test.mjs +3 -3
- package/scripts/session-turn-context.test.mjs +4 -4
- package/src/agent/Agent.ts +64 -10
- package/src/agent/AgentSessionIdentity.ts +30 -0
- package/src/agent/AgentSessions.ts +119 -77
- package/src/agent/SessionWork.ts +235 -0
- package/src/index.ts +19 -0
- package/src/session/Session.ts +112 -29
- package/src/session/input/StepInput.ts +42 -7
- package/src/session/loop/SessionLoop.ts +7 -9
- package/src/session/loop/SessionState.ts +61 -1
- package/src/session/storage/Browse.ts +2 -1
- package/src/session/storage/LocalSessionStore.ts +7 -7
- package/src/tools/session/SessionToolSchemas.ts +61 -0
- package/src/tools/session/SessionTools.ts +170 -0
- package/src/types/agent/AgentOptions.ts +12 -1
- package/src/types/agent/AgentSessionCollection.ts +18 -7
- package/src/types/agent/AgentSessionInput.ts +15 -0
- package/src/types/agent/SessionActor.ts +23 -5
- package/src/types/agent/SessionContext.ts +29 -0
- package/src/types/agent/SessionState.ts +74 -0
- package/src/types/agent/SessionTypes.ts +13 -1
- package/src/types/agent/SessionWork.ts +57 -0
- package/src/types/session/SessionLocalState.ts +24 -1
- package/src/types/session/SessionLoop.ts +6 -2
- package/src/types/session/SessionOptions.ts +10 -14
- package/src/types/session/StepInput.ts +5 -5
- package/src/types/tools/session/SessionTools.ts +104 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -17,7 +17,7 @@ test("SessionTurnContext 在检查点消费输入并封装输出缓冲", async (
|
|
|
17
17
|
const internal_messages = [];
|
|
18
18
|
const context = create_session_turn_context({
|
|
19
19
|
session_id: "session-context-test",
|
|
20
|
-
session_origin: { type: "
|
|
20
|
+
session_origin: { type: "work" },
|
|
21
21
|
turn_id: "turn-context-test",
|
|
22
22
|
project_root: "/workspace",
|
|
23
23
|
commit_step_input: async () => {
|
|
@@ -53,7 +53,7 @@ test("SessionTurnContext 在检查点消费输入并封装输出缓冲", async (
|
|
|
53
53
|
test("SessionTurnContext 只按顺序收集当前 Turn 的 Tool effects", () => {
|
|
54
54
|
const context = create_session_turn_context({
|
|
55
55
|
session_id: "session-context-test",
|
|
56
|
-
session_origin: { type: "
|
|
56
|
+
session_origin: { type: "work" },
|
|
57
57
|
turn_id: "turn-context-test",
|
|
58
58
|
});
|
|
59
59
|
context.effects.append([{
|
|
@@ -109,7 +109,7 @@ test("SessionTurnContext 把 Workspace env 与 Agent systems 固化为只读快
|
|
|
109
109
|
test("SessionTurnContext 不再承担 Power hook 装配", () => {
|
|
110
110
|
const context = create_session_turn_context({
|
|
111
111
|
session_id: "session-context-test",
|
|
112
|
-
session_origin: { type: "
|
|
112
|
+
session_origin: { type: "work" },
|
|
113
113
|
turn_id: "turn-context-test",
|
|
114
114
|
});
|
|
115
115
|
for (const removed of ["replace_hooks", "hook_context", "release_extensions", "hooks"]) {
|
|
@@ -120,7 +120,7 @@ test("SessionTurnContext 不再承担 Power hook 装配", () => {
|
|
|
120
120
|
test("SessionTurnContext 在整个 Turn 中只解析一次 Power Context", async () => {
|
|
121
121
|
const context = create_session_turn_context({
|
|
122
122
|
session_id: "session-context-test",
|
|
123
|
-
session_origin: { type: "
|
|
123
|
+
session_origin: { type: "work" },
|
|
124
124
|
turn_id: "turn-context-test",
|
|
125
125
|
});
|
|
126
126
|
let resolve_count = 0;
|
package/src/agent/Agent.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Agent:身份、模型、指令与 Session 的主体对象。
|
|
3
3
|
*
|
|
4
4
|
* 职责说明(中文)
|
|
5
|
-
* - Agent 不绑定 Workspace
|
|
5
|
+
* - Agent 不绑定 Workspace;`workspace` 只是创建 Session 时的默认目录,可在会话中切换。
|
|
6
6
|
* - 能力与 Transport 由容器持有,Agent 只收下容器在绑定时推送的能力,不持有容器引用。
|
|
7
|
-
* - Session 由 AgentSessions 统一持有;Workspace 只在单个 Session
|
|
7
|
+
* - Session 由 AgentSessions 统一持有;Workspace 只在单个 Session 创建或恢复时提供。
|
|
8
|
+
* - Agent 自己那条会话由 `session()` 暴露;它是一条普通 Session。
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import type {
|
|
@@ -16,6 +17,7 @@ import type {
|
|
|
16
17
|
ToolHookSet,
|
|
17
18
|
WorkspaceRuntime,
|
|
18
19
|
} from "@downcity/type";
|
|
20
|
+
|
|
19
21
|
import {
|
|
20
22
|
create_instruction_system_blocks,
|
|
21
23
|
normalize_instruction_input,
|
|
@@ -41,6 +43,11 @@ import {
|
|
|
41
43
|
} from "@/session/input/SessionSystem.js";
|
|
42
44
|
import type { AgentSessionSystemBlock } from "@/types/agent/SessionTypes.js";
|
|
43
45
|
import { resolve_system_timezone } from "@/session/storage/Metadata.js";
|
|
46
|
+
import { SessionWork } from "@/agent/SessionWork.js";
|
|
47
|
+
import { is_agent_session_origin, SESSION_ORIGIN_TYPE_WORK } from "@downcity/type";
|
|
48
|
+
import { create_session_tools } from "@/tools/session/SessionTools.js";
|
|
49
|
+
import type { AgentSessionInput } from "@/types/agent/AgentSessionInput.js";
|
|
50
|
+
import type { AgentSession } from "@/types/agent/SessionActor.js";
|
|
44
51
|
|
|
45
52
|
/** SDK Agent 主体。 */
|
|
46
53
|
export class Agent {
|
|
@@ -56,6 +63,15 @@ export class Agent {
|
|
|
56
63
|
/** Agent 默认模型;Session 可以显式覆盖。 */
|
|
57
64
|
readonly model?: ModelClient;
|
|
58
65
|
|
|
66
|
+
/**
|
|
67
|
+
* 新建 Session 时注入的默认目录。
|
|
68
|
+
*
|
|
69
|
+
* 关键点(中文)
|
|
70
|
+
* - 它是宿主在构造 Agent 时声明的默认值,不是绑定;单个 Session 可用 `set({ workspace })` 换掉。
|
|
71
|
+
* - 省略时新建的 Session 没有项目上下文:没有文件工具与 Shell,纯模型对话仍可用。
|
|
72
|
+
*/
|
|
73
|
+
readonly workspace?: WorkspaceRuntime;
|
|
74
|
+
|
|
59
75
|
/** Agent 面向用户的 Session 创建入口。 */
|
|
60
76
|
readonly sessions: AgentSessionCollection;
|
|
61
77
|
|
|
@@ -77,6 +93,14 @@ export class Agent {
|
|
|
77
93
|
/** Agent 自有检查点处理器;与容器提供的 Power hooks 合并使用。 */
|
|
78
94
|
private readonly custom_hooks: ToolHookSet;
|
|
79
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Agent 会话可用的会话工具;延迟创建并复用。
|
|
98
|
+
*
|
|
99
|
+
* 关键点(中文):工具闭包住 AgentSessions,因此不需要从 ToolCallContext 回查主体;
|
|
100
|
+
* 它们只在 `origin.type === "agent"` 的会话上下文里注册。
|
|
101
|
+
*/
|
|
102
|
+
private session_tools?: Record<string, Tool>;
|
|
103
|
+
|
|
80
104
|
/** Agent 释放状态。 */
|
|
81
105
|
private dispose_promise?: Promise<void>;
|
|
82
106
|
|
|
@@ -98,6 +122,7 @@ export class Agent {
|
|
|
98
122
|
this.name = String(options.name || "").trim() || this.id;
|
|
99
123
|
this.description = String(options.description || "").trim();
|
|
100
124
|
this.model = options.model;
|
|
125
|
+
this.workspace = options.workspace;
|
|
101
126
|
this.instruction = normalize_instruction_input(options.instruction);
|
|
102
127
|
this.session_class = options.session_class;
|
|
103
128
|
this.session_composer = options.session_composer;
|
|
@@ -114,26 +139,29 @@ export class Agent {
|
|
|
114
139
|
get_agent_model: () => this.model,
|
|
115
140
|
session_class: this.session_class,
|
|
116
141
|
session_composer: this.session_composer,
|
|
117
|
-
resolve_session_context: (
|
|
142
|
+
resolve_session_context: (input) => {
|
|
118
143
|
const storage = this.resolve_storage();
|
|
144
|
+
// 默认目录只用于新建会话;恢复时目录由宿主在 get() 时注入,
|
|
145
|
+
// 用默认值顶替会变成一次静默换目录。
|
|
146
|
+
const workspace = input?.workspace
|
|
147
|
+
?? (input?.creating ? this.workspace : undefined);
|
|
148
|
+
const is_agent_session = is_agent_session_origin(input?.origin);
|
|
119
149
|
if (!workspace) return {
|
|
120
150
|
workspace_path: ".",
|
|
121
151
|
logger: this.logger,
|
|
122
|
-
get_tools: () => this.resolve_tools(),
|
|
123
|
-
get_workspace_env: () => ({}),
|
|
152
|
+
get_tools: () => this.resolve_tools(undefined, is_agent_session),
|
|
124
153
|
get_workspace: () => undefined,
|
|
125
154
|
get_hooks: () => this.resolve_hooks(),
|
|
126
155
|
store: storage.sessions,
|
|
127
156
|
};
|
|
128
157
|
// 静态 Workspace Tool 与 Agent Tool 的命名冲突属于 Session 创建不变量,
|
|
129
158
|
// 在装配时立即失败;执行时仍通过 getter 读取最新宿主扩展。
|
|
130
|
-
this.resolve_tools(workspace);
|
|
159
|
+
this.resolve_tools(workspace, is_agent_session);
|
|
131
160
|
return {
|
|
132
161
|
workspace_path: workspace.path,
|
|
133
162
|
workspace_id: workspace.id,
|
|
134
163
|
logger: this.logger,
|
|
135
|
-
get_tools: () => this.resolve_tools(workspace),
|
|
136
|
-
get_workspace_env: () => workspace.get_env(),
|
|
164
|
+
get_tools: () => this.resolve_tools(workspace, is_agent_session),
|
|
137
165
|
get_workspace: () => workspace,
|
|
138
166
|
get_hooks: () => this.resolve_hooks(),
|
|
139
167
|
store: storage.sessions,
|
|
@@ -154,6 +182,18 @@ export class Agent {
|
|
|
154
182
|
return [...this.instruction];
|
|
155
183
|
}
|
|
156
184
|
|
|
185
|
+
/**
|
|
186
|
+
* 取用 Agent 自己那条会话,不存在时先建立。
|
|
187
|
+
*
|
|
188
|
+
* 关键点(中文)
|
|
189
|
+
* - 它就是一条普通 Session:工作区能力、set/get、消息、审批与其他会话完全一致。
|
|
190
|
+
* - 不接收 Workspace:目录在创建时注入,之后用 `session.set({ workspace })` 切换。
|
|
191
|
+
*/ async session(input?: AgentSessionInput): Promise<AgentSession> {
|
|
192
|
+
return await this.session_manager.self(
|
|
193
|
+
input?.workspace ? { workspace: input.workspace } : undefined,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
157
197
|
/** 返回 Agent 级日志器。 */
|
|
158
198
|
get_logger(): Logger {
|
|
159
199
|
return this.logger;
|
|
@@ -256,7 +296,7 @@ export class Agent {
|
|
|
256
296
|
input.instruction_system_blocks.map((block) => block.content),
|
|
257
297
|
),
|
|
258
298
|
session_id: input.session_id,
|
|
259
|
-
session_origin: { type:
|
|
299
|
+
session_origin: { type: SESSION_ORIGIN_TYPE_WORK },
|
|
260
300
|
workspace: input.workspace,
|
|
261
301
|
messages: Object.freeze([]),
|
|
262
302
|
workspace_env: Object.freeze({ ...input.workspace.get_env() }),
|
|
@@ -318,14 +358,28 @@ export class Agent {
|
|
|
318
358
|
* - 容器 Power 工具从环境句柄实时读取,不缓存;容器增删 Power 后下一次求值即生效。
|
|
319
359
|
* - 静态 Workspace Tool 与 Agent Tool 的命名冲突属于 Session 创建不变量,装配时立即失败。
|
|
320
360
|
*/
|
|
321
|
-
private resolve_tools(workspace?: WorkspaceRuntime): Record<string, Tool> {
|
|
361
|
+
private resolve_tools(workspace?: WorkspaceRuntime, is_agent_session = false): Record<string, Tool> {
|
|
322
362
|
const tools: Record<string, Tool> = {};
|
|
323
363
|
if (workspace) register_tools(tools, workspace.tools, "WorkspaceTools");
|
|
324
364
|
register_tools(tools, this.host?.power_tools ?? {}, "Power");
|
|
325
365
|
register_tools(tools, this.custom_tools, "AgentOptions.tools");
|
|
366
|
+
if (is_agent_session) register_tools(tools, this.resolve_session_tools(), "AgentSessionTools");
|
|
326
367
|
return tools;
|
|
327
368
|
}
|
|
328
369
|
|
|
370
|
+
/** 返回 Agent 会话的会话工具集合;首次调用时创建。 */
|
|
371
|
+
private resolve_session_tools(): Record<string, Tool> {
|
|
372
|
+
if (this.session_tools) return this.session_tools;
|
|
373
|
+
const work = new SessionWork({
|
|
374
|
+
list_sessions: async (input) => await this.session_manager.list(input),
|
|
375
|
+
get_session: async (session_id, origin_type) =>
|
|
376
|
+
await this.session_manager.get(session_id, origin_type),
|
|
377
|
+
create_session: async (input) => await this.session_manager.create(input),
|
|
378
|
+
});
|
|
379
|
+
this.session_tools = create_session_tools(work);
|
|
380
|
+
return this.session_tools;
|
|
381
|
+
}
|
|
382
|
+
|
|
329
383
|
/** 合并容器 Power hooks 与 Agent 自有 hooks。 */
|
|
330
384
|
private resolve_hooks(): ToolHookSet {
|
|
331
385
|
const power_hooks = this.host?.power_hooks;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent 自己那条会话的存取规则。
|
|
3
|
+
*
|
|
4
|
+
* 职责说明(中文)
|
|
5
|
+
* - Agent 自己就是一条 Session,与 work / task / group 会话是同一种东西。
|
|
6
|
+
* - 它没有专属字段、专属索引或专属存储:角色由 `origin.type = "agent"` 表达,
|
|
7
|
+
* 分区沿用 `sessions/<origin_type>/<session_id>/`,能力与其他会话完全一致。
|
|
8
|
+
* - 因此这里只有存取所需的两个小函数,不引入新概念。
|
|
9
|
+
*
|
|
10
|
+
* 关键点(中文)
|
|
11
|
+
* - 它的 `session_id` 等于 Agent 的 `id`:会话由它属于谁来标识,不需要人造常量。
|
|
12
|
+
* - 一个 Agent 至多一条,惰性创建;被归档或删除后下一次取用自然重建。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
SESSION_ORIGIN_TYPE_AGENT,
|
|
17
|
+
type SessionOrigin,
|
|
18
|
+
} from "@downcity/type";
|
|
19
|
+
|
|
20
|
+
/** Agent 自己那条会话的来源;不携带额外业务字段。 */
|
|
21
|
+
export function create_agent_session_origin(): SessionOrigin {
|
|
22
|
+
return Object.freeze({ type: SESSION_ORIGIN_TYPE_AGENT });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Agent 自己那条会话的稳定标识:就是 Agent 的 id。 */
|
|
26
|
+
export function resolve_agent_session_id(agent_id: string): string {
|
|
27
|
+
const resolved = String(agent_id || "").trim();
|
|
28
|
+
if (!resolved) throw new Error("Agent session requires a non-empty agent id");
|
|
29
|
+
return resolved;
|
|
30
|
+
}
|
|
@@ -37,7 +37,9 @@ import type { SessionStore } from "@/types/store/SessionStore.js";
|
|
|
37
37
|
import type { SessionStorage } from "@/types/store/SessionStorage.js";
|
|
38
38
|
import type { WorkspaceRuntime } from "@downcity/type";
|
|
39
39
|
import type { SessionOrigin } from "@downcity/type";
|
|
40
|
-
import { normalize_session_origin, normalize_session_origin_type } from "@downcity/type";
|
|
40
|
+
import { normalize_session_origin, normalize_session_origin_type, SESSION_ORIGIN_TYPE_WORK } from "@downcity/type";
|
|
41
|
+
import type { SessionContextResolution } from "@/types/agent/SessionContext.js";
|
|
42
|
+
import { create_agent_session_origin, resolve_agent_session_id } from "@/agent/AgentSessionIdentity.js";
|
|
41
43
|
|
|
42
44
|
type AgentSessionsOptions = {
|
|
43
45
|
/**
|
|
@@ -58,15 +60,14 @@ type AgentSessionsOptions = {
|
|
|
58
60
|
/**
|
|
59
61
|
* 按 Session 解析执行上下文。
|
|
60
62
|
*
|
|
61
|
-
* AgentSessions 是 Agent 唯一的 Session 集合;Workspace
|
|
62
|
-
* 固定在集合实例上,而应在创建或恢复单个 Session
|
|
63
|
+
* AgentSessions 是 Agent 唯一的 Session 集合;Workspace 与工具集合不能
|
|
64
|
+
* 固定在集合实例上,而应在创建或恢复单个 Session 时按会话身份解析。
|
|
63
65
|
*/
|
|
64
|
-
resolve_session_context: (
|
|
66
|
+
resolve_session_context: (input?: SessionContextResolution) => {
|
|
65
67
|
workspace_path: string;
|
|
66
68
|
workspace_id?: string;
|
|
67
69
|
logger: Logger;
|
|
68
70
|
get_tools: () => Record<string, Tool>;
|
|
69
|
-
get_workspace_env: () => Record<string, string>;
|
|
70
71
|
get_workspace: () => WorkspaceRuntime | undefined;
|
|
71
72
|
get_hooks: () => ToolHookSet;
|
|
72
73
|
store: SessionStore;
|
|
@@ -108,6 +109,8 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
108
109
|
private readonly session_composer: SessionComposer;
|
|
109
110
|
private readonly get_agent_model: AgentSessionsOptions["get_agent_model"];
|
|
110
111
|
private readonly sessions_by_id = new Map<string, AgentManagedSession>();
|
|
112
|
+
/** Agent 会话创建链;并发调用共享同一创建过程,避免产生第二条。 */
|
|
113
|
+
private agent_session_creation_chain: Promise<void> = Promise.resolve();
|
|
111
114
|
|
|
112
115
|
constructor(options: AgentSessionsOptions) {
|
|
113
116
|
this.agent_id = options.agent_id;
|
|
@@ -170,7 +173,7 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
170
173
|
* runtime 是执行层的内部投影,不负责创建 Session。需要创建时必须
|
|
171
174
|
* 通过 `create()` 获取内部生成的 ID;需要恢复时必须先调用 `get()`。
|
|
172
175
|
*/
|
|
173
|
-
runtime(session_id: string, origin_type =
|
|
176
|
+
runtime(session_id: string, origin_type = SESSION_ORIGIN_TYPE_WORK): SessionPort {
|
|
174
177
|
const resolved_session_id = String(session_id || "").trim();
|
|
175
178
|
const resolved_origin_type = normalize_session_origin_type(origin_type);
|
|
176
179
|
const session = this.sessions_by_id.get(
|
|
@@ -184,6 +187,72 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
184
187
|
return session.get_runtime_port();
|
|
185
188
|
}
|
|
186
189
|
|
|
190
|
+
/**
|
|
191
|
+
* 取用 Agent 自己那条会话;不存在时创建。
|
|
192
|
+
*
|
|
193
|
+
* 关键点(中文)
|
|
194
|
+
* - 身份就是 `origin.type = "agent"`,`session_id` 就是 Agent 的 `id`:
|
|
195
|
+
* 因此不新增索引,也不维护额外状态。
|
|
196
|
+
* - 同一进程内并发调用会收敛到同一个创建过程,不会产生第二条。
|
|
197
|
+
* - 被归档或删除后,下一次调用自然重建一条。
|
|
198
|
+
*/
|
|
199
|
+
async self(input?: { workspace?: WorkspaceRuntime }): Promise<AgentSession> {
|
|
200
|
+
const existing = await this.find_agent_session();
|
|
201
|
+
if (existing) return existing;
|
|
202
|
+
return await this.run_agent_session_creation(async () => {
|
|
203
|
+
const cached = await this.find_agent_session();
|
|
204
|
+
if (cached) return cached;
|
|
205
|
+
const origin = create_agent_session_origin();
|
|
206
|
+
const context = this.resolve_session_context({
|
|
207
|
+
origin,
|
|
208
|
+
creating: true,
|
|
209
|
+
...(input?.workspace ? { workspace: input.workspace } : {}),
|
|
210
|
+
});
|
|
211
|
+
const session_id = resolve_agent_session_id(this.agent_id);
|
|
212
|
+
const session = this.get_or_create_session({
|
|
213
|
+
session_id,
|
|
214
|
+
origin,
|
|
215
|
+
...(input?.workspace ? { workspace: input.workspace } : {}),
|
|
216
|
+
storage: context.store.create_session(session_id, origin, context.workspace_id),
|
|
217
|
+
});
|
|
218
|
+
await session.initialize();
|
|
219
|
+
return session;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 尝试恢复已存在的 Agent 会话;不存在或已失效时返回空。
|
|
225
|
+
*
|
|
226
|
+
* 关键点(中文):查找不传 Workspace;传 Workspace 只在创建时生效,
|
|
227
|
+
* 否则「已存在时忽略该参数」会退化成一次静默切目录。
|
|
228
|
+
*/
|
|
229
|
+
private async find_agent_session(): Promise<AgentSession | undefined> {
|
|
230
|
+
const origin = create_agent_session_origin();
|
|
231
|
+
const context = this.resolve_session_context({ origin });
|
|
232
|
+
const session_id = resolve_agent_session_id(this.agent_id);
|
|
233
|
+
try {
|
|
234
|
+
if (!(await context.store.has_session(session_id, origin.type))) {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
return await this.get(session_id, origin.type);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** 串行化 Agent 会话创建;失败时释放链,使下一次调用可以重试。 */
|
|
244
|
+
private async run_agent_session_creation(
|
|
245
|
+
create: () => Promise<AgentSession>,
|
|
246
|
+
): Promise<AgentSession> {
|
|
247
|
+
const previous = this.agent_session_creation_chain;
|
|
248
|
+
const operation = previous.then(create, create);
|
|
249
|
+
this.agent_session_creation_chain = operation.then(
|
|
250
|
+
() => undefined,
|
|
251
|
+
() => undefined,
|
|
252
|
+
);
|
|
253
|
+
return await operation;
|
|
254
|
+
}
|
|
255
|
+
|
|
187
256
|
/**
|
|
188
257
|
* 新建一个 session。
|
|
189
258
|
*/
|
|
@@ -192,7 +261,11 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
192
261
|
): Promise<AgentSession> {
|
|
193
262
|
const origin = normalize_session_origin(input?.origin);
|
|
194
263
|
const session_id = `session-${Date.now()}-${nanoid(8)}`;
|
|
195
|
-
const context = this.resolve_session_context(
|
|
264
|
+
const context = this.resolve_session_context({
|
|
265
|
+
origin,
|
|
266
|
+
creating: true,
|
|
267
|
+
...(input?.workspace ? { workspace: input.workspace } : {}),
|
|
268
|
+
});
|
|
196
269
|
const session = this.get_or_create_session({
|
|
197
270
|
session_id,
|
|
198
271
|
workspace: input?.workspace,
|
|
@@ -209,10 +282,15 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
209
282
|
|
|
210
283
|
/**
|
|
211
284
|
* 获取一个已存在的 session。
|
|
285
|
+
*
|
|
286
|
+
* 关键点(中文)
|
|
287
|
+
* - 当前目录不是 Session 身份:传入的 Workspace 与持久化值不一致时不再报错,
|
|
288
|
+
* 而是以传入值为准成为该 Session 的新当前目录。
|
|
289
|
+
* - 省略 Workspace 时沿用持久化值;两者都为空时该 Session 没有项目上下文。
|
|
212
290
|
*/
|
|
213
291
|
async get(
|
|
214
292
|
session_id: string,
|
|
215
|
-
origin_type =
|
|
293
|
+
origin_type = SESSION_ORIGIN_TYPE_WORK,
|
|
216
294
|
input?: { workspace?: WorkspaceRuntime },
|
|
217
295
|
): Promise<AgentSession> {
|
|
218
296
|
const resolved_session_id = String(session_id || "").trim();
|
|
@@ -221,7 +299,10 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
221
299
|
}
|
|
222
300
|
const resolved_origin_type = normalize_session_origin_type(origin_type);
|
|
223
301
|
const cache_key = this.session_cache_key(resolved_session_id, resolved_origin_type);
|
|
224
|
-
const context = this.resolve_session_context(
|
|
302
|
+
const context = this.resolve_session_context({
|
|
303
|
+
origin: { type: resolved_origin_type },
|
|
304
|
+
...(input?.workspace ? { workspace: input.workspace } : {}),
|
|
305
|
+
});
|
|
225
306
|
const store = context.store;
|
|
226
307
|
if (
|
|
227
308
|
!this.sessions_by_id.has(cache_key) &&
|
|
@@ -234,28 +315,21 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
234
315
|
const storage = await store.open_session(resolved_session_id, resolved_origin_type);
|
|
235
316
|
const persisted_metadata = await storage.read_metadata();
|
|
236
317
|
const persisted_workspace_id = String(persisted_metadata.workspace_id || "").trim() || undefined;
|
|
237
|
-
const persisted_origin = persisted_metadata.origin;
|
|
238
|
-
const cached = this.sessions_by_id.get(cache_key);
|
|
239
318
|
const requested_workspace_id = String(input?.workspace?.id || "").trim() || undefined;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
: `Session "${resolved_session_id}" is not bound to a Workspace`,
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
if (cached && cached.workspace_id !== requested_workspace_id) {
|
|
248
|
-
throw new Error(
|
|
249
|
-
`Session "${resolved_session_id}" is already bound to Workspace "${cached.workspace_id || ""}"`,
|
|
250
|
-
);
|
|
251
|
-
}
|
|
319
|
+
// 目录不是身份,也不在 Session 侧还原:
|
|
320
|
+
// - 宿主传了实例 → 用它;
|
|
321
|
+
// - 本进程有缓存 → 沿用内存实例;
|
|
322
|
+
// - 都没有 → 该会话没有项目上下文,但仍可正常读历史与对话。
|
|
252
323
|
const session = this.get_or_create_session({
|
|
253
324
|
session_id: resolved_session_id,
|
|
254
|
-
workspace: input
|
|
255
|
-
origin:
|
|
325
|
+
...(input?.workspace ? { workspace: input.workspace } : {}),
|
|
326
|
+
origin: persisted_metadata.origin,
|
|
256
327
|
storage,
|
|
257
328
|
});
|
|
258
329
|
await session.initialize();
|
|
330
|
+
if (requested_workspace_id && requested_workspace_id !== persisted_workspace_id) {
|
|
331
|
+
await session.set({ workspace: input?.workspace ?? null });
|
|
332
|
+
}
|
|
259
333
|
return session;
|
|
260
334
|
}
|
|
261
335
|
|
|
@@ -266,7 +340,7 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
266
340
|
* - 正在执行的 Session 会先停止,避免删除后继续写入。
|
|
267
341
|
* - 该方法不处理任何 Power 自有数据。
|
|
268
342
|
*/
|
|
269
|
-
async remove(session_id: string, origin_type =
|
|
343
|
+
async remove(session_id: string, origin_type = SESSION_ORIGIN_TYPE_WORK): Promise<boolean> {
|
|
270
344
|
const resolved_session_id = String(session_id || "").trim();
|
|
271
345
|
if (!resolved_session_id) {
|
|
272
346
|
throw new Error("sessions.remove requires a non-empty session_id");
|
|
@@ -289,7 +363,7 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
289
363
|
/**
|
|
290
364
|
* 清空一个 Session 的消息目录。
|
|
291
365
|
*/
|
|
292
|
-
async clear_messages(session_id: string, origin_type =
|
|
366
|
+
async clear_messages(session_id: string, origin_type = SESSION_ORIGIN_TYPE_WORK): Promise<boolean> {
|
|
293
367
|
const resolved_session_id = String(session_id || "").trim();
|
|
294
368
|
if (!resolved_session_id) {
|
|
295
369
|
throw new Error("sessions.clear_messages requires a non-empty session_id");
|
|
@@ -315,8 +389,8 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
315
389
|
async list(
|
|
316
390
|
input?: AgentListSessionsInput,
|
|
317
391
|
): Promise<AgentSessionSummaryPage> {
|
|
318
|
-
const origin_type = normalize_session_origin_type(input?.origin_type ??
|
|
319
|
-
return await this.resolve_session_context().store.list_sessions(
|
|
392
|
+
const origin_type = normalize_session_origin_type(input?.origin_type ?? SESSION_ORIGIN_TYPE_WORK);
|
|
393
|
+
return await this.resolve_session_context({ origin: { type: origin_type } }).store.list_sessions(
|
|
320
394
|
{ ...(input || {}), origin_type },
|
|
321
395
|
new Set(this.list_executing_session_ids(undefined, origin_type)),
|
|
322
396
|
);
|
|
@@ -332,7 +406,7 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
332
406
|
if (!session_id) {
|
|
333
407
|
throw new Error("sessions.archive requires a non-empty id");
|
|
334
408
|
}
|
|
335
|
-
const origin_type = normalize_session_origin_type(input.origin_type ??
|
|
409
|
+
const origin_type = normalize_session_origin_type(input.origin_type ?? SESSION_ORIGIN_TYPE_WORK);
|
|
336
410
|
const cache_key = this.session_cache_key(session_id, origin_type);
|
|
337
411
|
|
|
338
412
|
const cached = this.sessions_by_id.get(cache_key);
|
|
@@ -363,46 +437,11 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
363
437
|
}
|
|
364
438
|
|
|
365
439
|
/**
|
|
366
|
-
*
|
|
440
|
+
* 删除:把 Session 重新绑定到另一个 Workspace。
|
|
367
441
|
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
* - 之后用新 Workspace 恢复时不再触发严格的 workspace 归属校验。
|
|
442
|
+
* 删除理由:它表达的是「Workspace 是 Session 绑定」这个旧模型。目录切换改由
|
|
443
|
+
* `session.set({ workspace })` 承担,恢复时不再校验归属,因此不需要第二条路径。
|
|
371
444
|
*/
|
|
372
|
-
async workspace(
|
|
373
|
-
session_id: string,
|
|
374
|
-
workspace: WorkspaceRuntime,
|
|
375
|
-
): Promise<AgentSession> {
|
|
376
|
-
const resolved_session_id = String(session_id || "").trim();
|
|
377
|
-
if (!resolved_session_id) {
|
|
378
|
-
throw new Error("sessions.workspace requires a non-empty session_id");
|
|
379
|
-
}
|
|
380
|
-
const resolved_origin_type = normalize_session_origin_type("chat");
|
|
381
|
-
const cache_key = this.session_cache_key(resolved_session_id, resolved_origin_type);
|
|
382
|
-
const cached = this.sessions_by_id.get(cache_key);
|
|
383
|
-
if (cached?.is_executing()) {
|
|
384
|
-
throw new Error(`Session "${resolved_session_id}" is currently executing`);
|
|
385
|
-
}
|
|
386
|
-
const context = this.resolve_session_context(workspace);
|
|
387
|
-
if (!(await context.store.has_session(resolved_session_id, resolved_origin_type))) {
|
|
388
|
-
throw new Error(`Session "${resolved_session_id}" not found`);
|
|
389
|
-
}
|
|
390
|
-
const store = await context.store.open_session(
|
|
391
|
-
resolved_session_id,
|
|
392
|
-
resolved_origin_type,
|
|
393
|
-
);
|
|
394
|
-
const metadata = await store.read_metadata();
|
|
395
|
-
if (metadata.agent_id !== this.agent_id) {
|
|
396
|
-
throw new Error(`Session "${resolved_session_id}" belongs to another Agent`);
|
|
397
|
-
}
|
|
398
|
-
await store.write_metadata({
|
|
399
|
-
...metadata,
|
|
400
|
-
workspace_id: context.workspace_id,
|
|
401
|
-
});
|
|
402
|
-
this.sessions_by_id.delete(cache_key);
|
|
403
|
-
cached?.dispose_title_generation?.();
|
|
404
|
-
return await this.get(resolved_session_id, resolved_origin_type, { workspace });
|
|
405
|
-
}
|
|
406
445
|
|
|
407
446
|
private get_or_create_session(input?: {
|
|
408
447
|
/**
|
|
@@ -419,28 +458,31 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
419
458
|
const resolved_session_id = String(
|
|
420
459
|
input?.session_id || input?.storage?.session_id || "",
|
|
421
460
|
).trim() || `session-${Date.now()}-${nanoid(8)}`;
|
|
422
|
-
const
|
|
423
|
-
const cache_key = this.session_cache_key(resolved_session_id,
|
|
461
|
+
const resolved_origin = normalize_session_origin(input?.origin);
|
|
462
|
+
const cache_key = this.session_cache_key(resolved_session_id, resolved_origin.type);
|
|
424
463
|
const cached = this.sessions_by_id.get(cache_key);
|
|
425
464
|
if (cached) return cached;
|
|
426
465
|
|
|
427
|
-
const context = this.resolve_session_context(
|
|
466
|
+
const context = this.resolve_session_context({
|
|
467
|
+
...(input?.origin ? { origin: input.origin } : {}),
|
|
468
|
+
creating: true,
|
|
469
|
+
...(input?.workspace ? { workspace: input.workspace } : {}),
|
|
470
|
+
});
|
|
428
471
|
const created = new this.session_class({
|
|
429
472
|
agent_id: this.agent_id,
|
|
430
473
|
agent_name: this.agent_name,
|
|
431
474
|
agent_description: this.agent_description,
|
|
432
475
|
workspace_path: context.workspace_path,
|
|
433
|
-
...(context.
|
|
434
|
-
origin,
|
|
435
|
-
store: input?.storage || context.store.create_session(resolved_session_id,
|
|
436
|
-
create_session_store: (session_id) => context.store.create_session(session_id,
|
|
476
|
+
...(context.get_workspace() ? { workspace: context.get_workspace() } : {}),
|
|
477
|
+
origin: resolved_origin,
|
|
478
|
+
store: input?.storage || context.store.create_session(resolved_session_id, resolved_origin, context.workspace_id),
|
|
479
|
+
create_session_store: (session_id) => context.store.create_session(session_id, resolved_origin, context.workspace_id),
|
|
437
480
|
register_forked_session: (session) => this.register_forked_session(session),
|
|
438
481
|
session_id: resolved_session_id,
|
|
439
482
|
get_tools: () => context.get_tools(),
|
|
440
483
|
logger: context.logger,
|
|
441
484
|
instruction_system_blocks: this.load_instruction_system_blocks(),
|
|
442
485
|
get_instruction_system_blocks: () => this.load_instruction_system_blocks(),
|
|
443
|
-
get_workspace_env: () => context.get_workspace_env(),
|
|
444
486
|
get_workspace: () => context.get_workspace(),
|
|
445
487
|
get_agent_model: () => this.get_agent_model(),
|
|
446
488
|
get_hooks: () => context.get_hooks(),
|
|
@@ -459,7 +501,7 @@ export class AgentSessions implements AgentSessionsContract<AgentSession> {
|
|
|
459
501
|
* - 返回是否真的释放。调用方必须先自行解除对该实例的订阅,否则重新 `get()` 会拿到新实例
|
|
460
502
|
* 而旧订阅仍然挂在被丢弃的实例上。
|
|
461
503
|
*/
|
|
462
|
-
release(session_id: string, origin_type =
|
|
504
|
+
release(session_id: string, origin_type = SESSION_ORIGIN_TYPE_WORK): boolean {
|
|
463
505
|
const resolved_session_id = String(session_id || "").trim();
|
|
464
506
|
if (!resolved_session_id) return false;
|
|
465
507
|
const cache_key = this.session_cache_key(
|