@downcity/agent 1.1.445 → 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
|
@@ -70,7 +70,8 @@ const QUEUED_PROMPT_CANCELLED_MESSAGE =
|
|
|
70
70
|
export class SessionLoop {
|
|
71
71
|
private readonly session_id: string;
|
|
72
72
|
private readonly session_origin: SessionLoopOptions["session_origin"];
|
|
73
|
-
|
|
73
|
+
/** 当前 Session 的目录解析器。 */
|
|
74
|
+
private readonly get_workspace_path: SessionLoopOptions["get_workspace_path"];
|
|
74
75
|
private readonly step_input: StepInput | null;
|
|
75
76
|
private readonly session_executor: SessionExecutorPort;
|
|
76
77
|
private readonly maintain_context: SessionLoopOptions["maintain_context"];
|
|
@@ -93,7 +94,7 @@ export class SessionLoop {
|
|
|
93
94
|
constructor(options: SessionLoopOptions) {
|
|
94
95
|
this.session_id = String(options.session_id || "").trim();
|
|
95
96
|
this.session_origin = options.session_origin;
|
|
96
|
-
this.
|
|
97
|
+
this.get_workspace_path = options.get_workspace_path;
|
|
97
98
|
this.maintain_context = options.maintain_context;
|
|
98
99
|
this.state = options.state;
|
|
99
100
|
this.messages = options.messages;
|
|
@@ -106,9 +107,6 @@ export class SessionLoop {
|
|
|
106
107
|
if (!this.session_id) {
|
|
107
108
|
throw new Error("SessionLoop requires a non-empty session_id");
|
|
108
109
|
}
|
|
109
|
-
if (!this.workspace_path) {
|
|
110
|
-
throw new Error("SessionLoop requires a non-empty workspace_path");
|
|
111
|
-
}
|
|
112
110
|
this.step_input = options.step_input ?? null;
|
|
113
111
|
this.session_executor = options.executor ??
|
|
114
112
|
new SessionExecutor({
|
|
@@ -535,7 +533,7 @@ export class SessionLoop {
|
|
|
535
533
|
await append_session_turn_file_diff({
|
|
536
534
|
session_id: this.session_id,
|
|
537
535
|
turn_id: input.active_turn.turn_id,
|
|
538
|
-
workspace_path: this.
|
|
536
|
+
workspace_path: this.get_workspace_path(),
|
|
539
537
|
turn_context,
|
|
540
538
|
assistant_output,
|
|
541
539
|
logger: this.logger,
|
|
@@ -608,7 +606,7 @@ export class SessionLoop {
|
|
|
608
606
|
turn_id: active_turn.turn_id,
|
|
609
607
|
session_id: this.session_id,
|
|
610
608
|
session_origin: this.session_origin,
|
|
611
|
-
project_root: this.
|
|
609
|
+
project_root: this.get_workspace_path(),
|
|
612
610
|
commit_step_input: async () => {
|
|
613
611
|
const had_pending_prompt = this.has_pending_prompt();
|
|
614
612
|
await this.drain_queued_inputs();
|
|
@@ -630,7 +628,7 @@ export class SessionLoop {
|
|
|
630
628
|
publish_session_turn_file_diff({
|
|
631
629
|
session_id: this.session_id,
|
|
632
630
|
turn_id: active_turn.turn_id,
|
|
633
|
-
workspace_path: this.
|
|
631
|
+
workspace_path: this.get_workspace_path(),
|
|
634
632
|
effects,
|
|
635
633
|
publish: (mutation) => this.events.publish(mutation),
|
|
636
634
|
logger: this.logger,
|
|
@@ -656,7 +654,7 @@ export class SessionLoop {
|
|
|
656
654
|
message_id?: string,
|
|
657
655
|
): Promise<SessionUserMessage> {
|
|
658
656
|
const message = await this.messages.append_prompt_message({
|
|
659
|
-
project_root: this.
|
|
657
|
+
project_root: this.get_workspace_path(),
|
|
660
658
|
prompt,
|
|
661
659
|
turn_id,
|
|
662
660
|
...(message_id ? { message_id } : {}),
|
|
@@ -28,13 +28,21 @@ import type { SessionStateOptions } from "@/types/session/SessionState.js";
|
|
|
28
28
|
import type { SessionStorage } from "@/types/store/SessionStorage.js";
|
|
29
29
|
import type { SessionApprovalMode } from "@downcity/type";
|
|
30
30
|
import { create_session_model_request_warning } from "@/session/messages/SessionModelRequestWarning.js";
|
|
31
|
+
import type { WorkspaceRuntime } from "@downcity/type";
|
|
31
32
|
|
|
32
33
|
/** 创建单个 Session 唯一的可变内存状态。 */
|
|
33
|
-
export function create_session_local_state(
|
|
34
|
+
export function create_session_local_state(input: {
|
|
35
|
+
/** Session 创建时的初始 Workspace;省略表示没有项目上下文。 */
|
|
36
|
+
workspace?: WorkspaceRuntime;
|
|
37
|
+
/** 没有 Workspace 时使用的目录回退值。 */
|
|
38
|
+
fallback_workspace_path: string;
|
|
39
|
+
}): SessionLocalState {
|
|
34
40
|
return {
|
|
35
41
|
session_config: {},
|
|
36
42
|
effective_session_config: {},
|
|
37
43
|
configured_approval_mode: "ask",
|
|
44
|
+
...(input.workspace ? { current_workspace: input.workspace } : {}),
|
|
45
|
+
fallback_workspace_path: input.fallback_workspace_path,
|
|
38
46
|
created_at: Date.now(),
|
|
39
47
|
timezone: resolve_system_timezone(),
|
|
40
48
|
initialize_promise: null,
|
|
@@ -129,6 +137,11 @@ export class SessionState {
|
|
|
129
137
|
});
|
|
130
138
|
this.state.created_at = created_at;
|
|
131
139
|
this.state.timezone = timezone;
|
|
140
|
+
this.state.updated_at =
|
|
141
|
+
typeof metadata.updated_at === "number" && metadata.updated_at > 0
|
|
142
|
+
? metadata.updated_at
|
|
143
|
+
: undefined;
|
|
144
|
+
this.state.title = normalize_session_title(metadata.title);
|
|
132
145
|
this.state.session_config = {
|
|
133
146
|
...(metadata.model_label ? { model_label: metadata.model_label } : {}),
|
|
134
147
|
};
|
|
@@ -188,8 +201,10 @@ export class SessionState {
|
|
|
188
201
|
...(next_model_label ? { model_label: next_model_label } : {}),
|
|
189
202
|
});
|
|
190
203
|
});
|
|
204
|
+
this.state.updated_at = Date.now();
|
|
191
205
|
}
|
|
192
206
|
this.state.session_config = next_config;
|
|
207
|
+
this.state.updated_at = Date.now();
|
|
193
208
|
return { config: next_config, changed };
|
|
194
209
|
}
|
|
195
210
|
|
|
@@ -226,6 +241,8 @@ export class SessionState {
|
|
|
226
241
|
return true;
|
|
227
242
|
});
|
|
228
243
|
if (changed) {
|
|
244
|
+
this.state.title = title;
|
|
245
|
+
this.state.updated_at = Date.now();
|
|
229
246
|
this.publish_event({
|
|
230
247
|
mutation_id: generate_id(),
|
|
231
248
|
variant: "session",
|
|
@@ -238,6 +255,49 @@ export class SessionState {
|
|
|
238
255
|
return title;
|
|
239
256
|
}
|
|
240
257
|
|
|
258
|
+
/**
|
|
259
|
+
* 写入当前目录,并同步内存实例。
|
|
260
|
+
*
|
|
261
|
+
* 关键点(中文)
|
|
262
|
+
* - 这是 `session.set({ workspace })` 的内部实现,不对 Session 用户单独暴露写入口。
|
|
263
|
+
* - 以持久化值为比较基准:即使内存已持有同一实例,只要元数据不同就补写,
|
|
264
|
+
* 保证恢复时传入的目录真的落盘。
|
|
265
|
+
* - 传空值表示脱离项目上下文。
|
|
266
|
+
*/
|
|
267
|
+
async apply_workspace(workspace?: WorkspaceRuntime): Promise<boolean> {
|
|
268
|
+
const next_workspace_id = String(workspace?.id || "").trim() || undefined;
|
|
269
|
+
const changed = await this.run_metadata_mutation(async () => {
|
|
270
|
+
const metadata = await this.store.read_metadata();
|
|
271
|
+
const current_workspace_id = String(metadata.workspace_id || "").trim() || undefined;
|
|
272
|
+
if (current_workspace_id === next_workspace_id) return false;
|
|
273
|
+
const { workspace_id: _previous_workspace_id, ...rest } = metadata;
|
|
274
|
+
await this.store.write_metadata({
|
|
275
|
+
...rest,
|
|
276
|
+
agent_id: this.agent_id,
|
|
277
|
+
updated_at: Date.now(),
|
|
278
|
+
...(next_workspace_id ? { workspace_id: next_workspace_id } : {}),
|
|
279
|
+
});
|
|
280
|
+
return true;
|
|
281
|
+
});
|
|
282
|
+
if (workspace) {
|
|
283
|
+
this.state.current_workspace = workspace;
|
|
284
|
+
} else {
|
|
285
|
+
delete this.state.current_workspace;
|
|
286
|
+
}
|
|
287
|
+
return changed;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** 返回当前 Session 正在使用的 Workspace;没有项目上下文时为空。 */
|
|
291
|
+
get_workspace_instance(): WorkspaceRuntime | undefined {
|
|
292
|
+
return this.state.current_workspace;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** 返回当前目录路径;没有 Workspace 时使用回退值。 */
|
|
296
|
+
get_workspace_path(): string {
|
|
297
|
+
return String(this.state.current_workspace?.path || "").trim()
|
|
298
|
+
|| this.state.fallback_workspace_path;
|
|
299
|
+
}
|
|
300
|
+
|
|
241
301
|
/** 在 Session Step 检查点提交模型配置。 */
|
|
242
302
|
apply_model_config(config: AgentSessionConfigSnapshot): void {
|
|
243
303
|
this.state.effective_session_config = {
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
normalize_session_origin_type,
|
|
25
25
|
restore_session_origin,
|
|
26
|
+
SESSION_ORIGIN_TYPE_WORK,
|
|
26
27
|
type FileSystem,
|
|
27
28
|
type SessionMessage,
|
|
28
29
|
} from "@downcity/type";
|
|
@@ -219,7 +220,7 @@ async function list_session_summary_page(input: {
|
|
|
219
220
|
/** 是否扫描归档目录。 */
|
|
220
221
|
archived: boolean;
|
|
221
222
|
}): Promise<AgentSessionSummaryPage> {
|
|
222
|
-
const origin_type = normalize_session_origin_type(input.input?.origin_type ??
|
|
223
|
+
const origin_type = normalize_session_origin_type(input.input?.origin_type ?? SESSION_ORIGIN_TYPE_WORK);
|
|
223
224
|
const sessions_path = input.archived
|
|
224
225
|
? get_agent_archived_sessions_path(input.project_root, origin_type)
|
|
225
226
|
: get_agent_sessions_path(input.project_root, origin_type);
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
import type { FileSystem } from "@downcity/type";
|
|
38
38
|
import type { LocalSessionStoreOptions } from "@/types/store/LocalStore.js";
|
|
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
41
|
|
|
42
42
|
/** 解码目录中经过 URL 编码的 Session 标识。 */
|
|
43
43
|
function decode_session_id(input: string): string {
|
|
@@ -113,7 +113,7 @@ export class LocalSessionStore implements SessionStore {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/** 打开已有 Session,并以数据库中的完整 origin 作为唯一事实。 */
|
|
116
|
-
async open_session(session_id: string, origin_type =
|
|
116
|
+
async open_session(session_id: string, origin_type = SESSION_ORIGIN_TYPE_WORK): Promise<SessionStorage> {
|
|
117
117
|
const resolved_session_id = String(session_id || "").trim();
|
|
118
118
|
if (!resolved_session_id) {
|
|
119
119
|
throw new Error("SessionStore.open_session requires a non-empty session_id");
|
|
@@ -151,7 +151,7 @@ export class LocalSessionStore implements SessionStore {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
/** 判断活动 Session 是否存在。 */
|
|
154
|
-
async has_session(session_id: string, origin_type =
|
|
154
|
+
async has_session(session_id: string, origin_type = SESSION_ORIGIN_TYPE_WORK): Promise<boolean> {
|
|
155
155
|
const resolved_origin_type = normalize_session_origin_type(origin_type);
|
|
156
156
|
const cache_key = this.session_cache_key(session_id, resolved_origin_type);
|
|
157
157
|
if (this.database_location.type === "memory") return this.sessions.has(cache_key);
|
|
@@ -166,7 +166,7 @@ export class LocalSessionStore implements SessionStore {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/** 永久删除活动 Session。 */
|
|
169
|
-
async remove_session(session_id: string, origin_type =
|
|
169
|
+
async remove_session(session_id: string, origin_type = SESSION_ORIGIN_TYPE_WORK): Promise<boolean> {
|
|
170
170
|
const resolved_origin_type = normalize_session_origin_type(origin_type);
|
|
171
171
|
const session_path = this.session_path(session_id, resolved_origin_type);
|
|
172
172
|
const cache_key = this.session_cache_key(session_id, resolved_origin_type);
|
|
@@ -188,7 +188,7 @@ export class LocalSessionStore implements SessionStore {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
/** 清空活动 Session Message 数据。 */
|
|
191
|
-
async clear_session_messages(session_id: string, origin_type =
|
|
191
|
+
async clear_session_messages(session_id: string, origin_type = SESSION_ORIGIN_TYPE_WORK): Promise<boolean> {
|
|
192
192
|
const resolved_origin_type = normalize_session_origin_type(origin_type);
|
|
193
193
|
if (!(await this.has_session(session_id, resolved_origin_type))) return false;
|
|
194
194
|
const storage = await this.open_session(session_id, resolved_origin_type);
|
|
@@ -215,7 +215,7 @@ export class LocalSessionStore implements SessionStore {
|
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
/** 将活动 Session 迁入归档区。 */
|
|
218
|
-
async archive_session(session_id: string, origin_type =
|
|
218
|
+
async archive_session(session_id: string, origin_type = SESSION_ORIGIN_TYPE_WORK): Promise<AgentArchiveSessionResult> {
|
|
219
219
|
const resolved_origin_type = normalize_session_origin_type(origin_type);
|
|
220
220
|
const cache_key = this.session_cache_key(session_id, resolved_origin_type);
|
|
221
221
|
if (this.database_location.type === "memory") {
|
|
@@ -372,7 +372,7 @@ export class LocalSessionStore implements SessionStore {
|
|
|
372
372
|
executing_session_ids: ReadonlySet<string>,
|
|
373
373
|
storages = this.sessions,
|
|
374
374
|
): Promise<AgentSessionSummaryPage> {
|
|
375
|
-
const origin_type = normalize_session_origin_type(input?.origin_type ??
|
|
375
|
+
const origin_type = normalize_session_origin_type(input?.origin_type ?? SESSION_ORIGIN_TYPE_WORK);
|
|
376
376
|
const query = String(input?.query || "").trim().toLowerCase();
|
|
377
377
|
const summaries: AgentSessionSummary[] = [];
|
|
378
378
|
for (const storage of storages.values()) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent 会话可用的 Session 工具输入 Schema。
|
|
3
|
+
*
|
|
4
|
+
* 职责说明(中文)
|
|
5
|
+
* - 四个工具都围绕「Agent 自己手上的会话」:开一条、看状态、代答、收回。
|
|
6
|
+
* - Schema 在模型输入边界完成校验,非法输入不会变成待处理状态。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
/** `session_start` 输入:开一条会话并交给它任务。 */
|
|
12
|
+
export const session_start_input = z
|
|
13
|
+
.object({
|
|
14
|
+
query: z.string().trim().min(1).describe(
|
|
15
|
+
"The task handed to the new session. Write it as a complete instruction: the new session only sees this text.",
|
|
16
|
+
),
|
|
17
|
+
title: z.string().trim().min(1).optional().describe(
|
|
18
|
+
"Optional short title for this piece of work, used when listing sessions.",
|
|
19
|
+
),
|
|
20
|
+
})
|
|
21
|
+
.strict();
|
|
22
|
+
|
|
23
|
+
/** `session_check` 输入:列出全部会话,或读取其中一条。 */
|
|
24
|
+
export const session_check_input = z
|
|
25
|
+
.object({
|
|
26
|
+
session_id: z.string().trim().min(1).optional().describe(
|
|
27
|
+
"Read one session in detail. Omit to list every session of this agent and their current state.",
|
|
28
|
+
),
|
|
29
|
+
limit: z.number().int().min(1).max(100).optional().describe(
|
|
30
|
+
"Maximum sessions or messages to return. Defaults to 20.",
|
|
31
|
+
),
|
|
32
|
+
})
|
|
33
|
+
.strict();
|
|
34
|
+
|
|
35
|
+
/** `session_reply` 输入:代答某条会话正在等待的用户响应。 */
|
|
36
|
+
export const session_reply_input = z
|
|
37
|
+
.object({
|
|
38
|
+
session_id: z.string().trim().min(1).describe(
|
|
39
|
+
"The session that raised the pending interaction.",
|
|
40
|
+
),
|
|
41
|
+
interaction_id: z.string().trim().min(1).describe(
|
|
42
|
+
"Pending interaction id, as returned by session_check.",
|
|
43
|
+
),
|
|
44
|
+
outcome: z.enum(["resolved", "denied"]).describe(
|
|
45
|
+
"resolved approves or answers; denied rejects the operation.",
|
|
46
|
+
),
|
|
47
|
+
payload: z.record(z.string(), z.unknown()).optional().describe(
|
|
48
|
+
"Business response body for this interaction type. Question interactions require answers; approval interactions accept a decision.",
|
|
49
|
+
),
|
|
50
|
+
note: z.string().trim().min(1).optional().describe(
|
|
51
|
+
"Optional human-readable explanation passed along with the response.",
|
|
52
|
+
),
|
|
53
|
+
})
|
|
54
|
+
.strict();
|
|
55
|
+
|
|
56
|
+
/** `session_stop` 输入:收回某条会话。 */
|
|
57
|
+
export const session_stop_input = z
|
|
58
|
+
.object({
|
|
59
|
+
session_id: z.string().trim().min(1).describe("The session to stop."),
|
|
60
|
+
})
|
|
61
|
+
.strict();
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent 会话的 Session 工具。
|
|
3
|
+
*
|
|
4
|
+
* 职责说明(中文)
|
|
5
|
+
* - 把「Agent 手上的会话」变成模型可调用的四个动作:开一条、看状态、代答、收回。
|
|
6
|
+
* - 工具与 Agent 会话的工作运行时({@link SessionWork})解耦:运行时由 Agent 在装配时注入,
|
|
7
|
+
* 工具只调用协议,不接触 AgentSessions 或 Session 内部结构。
|
|
8
|
+
* - 工具自带 description 与返回值说明,因此不需要向 instruction 注入额外文本。
|
|
9
|
+
*
|
|
10
|
+
* 关键点(中文)
|
|
11
|
+
* - 这组工具只注册在 `origin.type === "agent"` 的会话上下文里;其他 Session 拿不到,所以不会递归派发。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
define_agent_tool,
|
|
16
|
+
type ActionResult,
|
|
17
|
+
type JsonValue,
|
|
18
|
+
type ToolCallContext,
|
|
19
|
+
} from "@downcity/type";
|
|
20
|
+
import type { SessionWorkContract } from "@/types/agent/SessionWork.js";
|
|
21
|
+
import type {
|
|
22
|
+
SessionCheckToolOutput,
|
|
23
|
+
SessionReplyToolOutput,
|
|
24
|
+
SessionStartToolOutput,
|
|
25
|
+
SessionStopToolOutput,
|
|
26
|
+
} from "@/types/tools/session/SessionTools.js";
|
|
27
|
+
import {
|
|
28
|
+
session_check_input,
|
|
29
|
+
session_reply_input,
|
|
30
|
+
session_start_input,
|
|
31
|
+
session_stop_input,
|
|
32
|
+
} from "@/tools/session/SessionToolSchemas.js";
|
|
33
|
+
|
|
34
|
+
/** `session_start` 的输入。 */
|
|
35
|
+
type SessionStartInput = { query: string; title?: string };
|
|
36
|
+
|
|
37
|
+
/** `session_check` 的输入。 */
|
|
38
|
+
type SessionCheckInput = { session_id?: string; limit?: number };
|
|
39
|
+
|
|
40
|
+
/** `session_reply` 的输入。 */
|
|
41
|
+
type SessionReplyInput = {
|
|
42
|
+
session_id: string;
|
|
43
|
+
interaction_id: string;
|
|
44
|
+
outcome: "resolved" | "denied";
|
|
45
|
+
payload?: Record<string, unknown>;
|
|
46
|
+
note?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** `session_stop` 的输入。 */
|
|
50
|
+
type SessionStopInput = { session_id: string };
|
|
51
|
+
|
|
52
|
+
/** 会话工具集;由 Agent 在 Agent 会话上下文中注入。 */
|
|
53
|
+
export type ChatSessionToolSet = ReturnType<typeof create_session_tools>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 创建 Agent 会话的四个会话工具。
|
|
57
|
+
*
|
|
58
|
+
* 关键点(中文)
|
|
59
|
+
* - 每个工具都没有附加 messages:会话状态通过工具结果返回,不追加到对话 canonical 消息。
|
|
60
|
+
*/
|
|
61
|
+
export function create_session_tools(work: SessionWorkContract) {
|
|
62
|
+
const session_start = define_agent_tool<SessionStartInput, ActionResult<SessionStartToolOutput>>({
|
|
63
|
+
description:
|
|
64
|
+
"Start a new session to carry out one piece of work, then return immediately. Use it when a request is larger than one reply: the user keeps talking with you while the work runs elsewhere. Write the query as a complete instruction, because the new session only sees that text.",
|
|
65
|
+
input_schema: session_start_input,
|
|
66
|
+
execute: async (input, context): Promise<ActionResult<SessionStartToolOutput>> => {
|
|
67
|
+
// 新会话继承当前对话的目录:用户说的「顺便」通常仍指同一个项目。
|
|
68
|
+
const result = await work.spawn({
|
|
69
|
+
...input,
|
|
70
|
+
...(context.workspace ? { workspace: context.workspace } : {}),
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
output: { session_id: result.session_id, status: "started" },
|
|
74
|
+
messages: [],
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const session_check = define_agent_tool<SessionCheckInput, ActionResult<SessionCheckToolOutput>>({
|
|
80
|
+
description:
|
|
81
|
+
"List the sessions you have started and what each one is doing, or pass session_id to read one session's recent messages and pending user interactions. Call this before answering questions about progress; never guess a session's state.",
|
|
82
|
+
input_schema: session_check_input,
|
|
83
|
+
execute: async (input): Promise<ActionResult<SessionCheckToolOutput>> => {
|
|
84
|
+
const session_id = String(input.session_id || "").trim();
|
|
85
|
+
if (!session_id) {
|
|
86
|
+
return {
|
|
87
|
+
output: { mode: "list", sessions: await work.list({ limit: input.limit }) },
|
|
88
|
+
messages: [],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
output: await work.read({ session_id, limit: input.limit }),
|
|
93
|
+
messages: [],
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const session_reply = define_agent_tool<SessionReplyInput, ActionResult<SessionReplyToolOutput>>({
|
|
99
|
+
description:
|
|
100
|
+
"Answer a pending interaction on another session without the user leaving this conversation. Call session_check first to get the interaction_id, and translate the user's own words into the response payload. Use it only for interactions that are genuinely waiting.",
|
|
101
|
+
input_schema: session_reply_input,
|
|
102
|
+
execute: async (input): Promise<ActionResult<SessionReplyToolOutput>> => {
|
|
103
|
+
// 响应载荷必须是可以序列化的 JSON,这里逐项收紧类型后再交给 Interaction 协议。
|
|
104
|
+
const payload: Record<string, JsonValue> = {};
|
|
105
|
+
for (const [key, value] of Object.entries(input.payload ?? {})) {
|
|
106
|
+
payload[key] = value as JsonValue;
|
|
107
|
+
}
|
|
108
|
+
if (input.note) payload.note = input.note;
|
|
109
|
+
await work.reply({
|
|
110
|
+
session_id: input.session_id,
|
|
111
|
+
interaction_id: input.interaction_id,
|
|
112
|
+
response: {
|
|
113
|
+
type: await resolve_interaction_type(work, input.session_id, input.interaction_id),
|
|
114
|
+
outcome: input.outcome,
|
|
115
|
+
payload,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
output: {
|
|
120
|
+
status: input.outcome,
|
|
121
|
+
session_id: input.session_id,
|
|
122
|
+
interaction_id: input.interaction_id,
|
|
123
|
+
},
|
|
124
|
+
messages: [],
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const session_stop = define_agent_tool<SessionStopInput, ActionResult<SessionStopToolOutput>>({
|
|
130
|
+
description:
|
|
131
|
+
"Stop the work running on another session, for example when the user changes their mind. The session is recorded as stopped rather than failed.",
|
|
132
|
+
input_schema: session_stop_input,
|
|
133
|
+
execute: async (input): Promise<ActionResult<SessionStopToolOutput>> => {
|
|
134
|
+
await work.stop({ session_id: input.session_id });
|
|
135
|
+
return {
|
|
136
|
+
output: { session_id: input.session_id, status: "stopped" },
|
|
137
|
+
messages: [],
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return { session_start, session_check, session_reply, session_stop };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 读取待响应 Interaction 的业务类型。
|
|
147
|
+
*
|
|
148
|
+
* 关键点(中文):响应必须与原请求 type 一致,这是 Interaction 协议的硬约束;
|
|
149
|
+
* 由这里从权威状态读出,避免让模型重复转述而写错。
|
|
150
|
+
*/
|
|
151
|
+
async function resolve_interaction_type(
|
|
152
|
+
work: SessionWorkContract,
|
|
153
|
+
session_id: string,
|
|
154
|
+
interaction_id: string,
|
|
155
|
+
): Promise<string> {
|
|
156
|
+
const detail = await work.read({ session_id, limit: 1 });
|
|
157
|
+
const pending = detail.pending_interactions.find(
|
|
158
|
+
(interaction) => interaction.interaction_id === interaction_id,
|
|
159
|
+
);
|
|
160
|
+
if (!pending) {
|
|
161
|
+
throw new Error(`Interaction is not pending on session "${session_id}": ${interaction_id}`);
|
|
162
|
+
}
|
|
163
|
+
return pending.type;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** 供 Agent 装配使用:把工具集暴露为普通 Tool 记录。 */
|
|
167
|
+
export type ChatSessionTools = ReturnType<typeof create_session_tools>;
|
|
168
|
+
|
|
169
|
+
/** 工具调用上下文类型再导出,便于宿主实现自定义会话工具时对齐。 */
|
|
170
|
+
export type ChatSessionToolContext = ToolCallContext;
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* - RemoteAgent 与 Session 数据结构拆到独立类型文件。
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { ModelClient, AgentTool as Tool, ToolHookSet } from "@downcity/type";
|
|
9
|
+
import type { ModelClient, AgentTool as Tool, ToolHookSet, WorkspaceRuntime } from "@downcity/type";
|
|
10
|
+
|
|
10
11
|
import type {
|
|
11
12
|
AgentManagedSession,
|
|
12
13
|
SessionOptions,
|
|
@@ -88,6 +89,16 @@ export interface AgentOptions {
|
|
|
88
89
|
*/
|
|
89
90
|
model?: ModelClient;
|
|
90
91
|
|
|
92
|
+
/**
|
|
93
|
+
* 新建 Session 时注入的默认目录。
|
|
94
|
+
*
|
|
95
|
+
* 关键点(中文)
|
|
96
|
+
* - 它是宿主在构造 Agent 时声明的默认值,不是绑定;单个 Session 可用 `set({ workspace })` 换掉。
|
|
97
|
+
* - 它不参与恢复:历史会话的目录由宿主在 `get()` 时注入。
|
|
98
|
+
* - 省略时新建的 Session 没有项目上下文:没有文件工具与 Shell,纯模型对话仍可用。
|
|
99
|
+
*/
|
|
100
|
+
workspace?: WorkspaceRuntime;
|
|
101
|
+
|
|
91
102
|
/**
|
|
92
103
|
* 当前 agent 使用的本地 Session 类。
|
|
93
104
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent 面向 SDK 用户的 Session 集合类型。
|
|
3
3
|
*
|
|
4
|
-
* Session 归 Agent 所有;Workspace
|
|
4
|
+
* Session 归 Agent 所有;Workspace 是会话的当前目录,不是会话身份。
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { WorkspaceRuntime } from "@downcity/type";
|
|
@@ -20,18 +20,32 @@ import type {
|
|
|
20
20
|
|
|
21
21
|
/** 创建 Agent Session 的公开参数。 */
|
|
22
22
|
export interface AgentCreateSessionOptions extends AgentCreateSessionInput {
|
|
23
|
-
/**
|
|
23
|
+
/** 新 Session 的初始目录;省略时使用 Agent 的默认目录,两者都没有则无项目上下文。 */
|
|
24
24
|
workspace?: WorkspaceRuntime;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/** 恢复 Agent Session
|
|
27
|
+
/** 恢复 Agent Session 时使用的当前目录。 */
|
|
28
28
|
export interface AgentGetSessionOptions {
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* 恢复后要使用的当前目录。
|
|
31
|
+
*
|
|
32
|
+
* 关键点(中文):这与持久化值不一致时不再报错,而是以该值为准成为该 Session 的当前目录,
|
|
33
|
+
* 语义等同 `session.set({ workspace })`。省略时由 Agent 的 `resolve_workspace` 按持久化值还原。
|
|
34
|
+
*/
|
|
30
35
|
workspace?: WorkspaceRuntime;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
/** Agent 公开的 Session 创建入口。 */
|
|
34
39
|
export interface AgentSessionCollection {
|
|
40
|
+
/**
|
|
41
|
+
* 打开 Agent 自己的那段对话;不存在时创建。
|
|
42
|
+
*
|
|
43
|
+
* 关键点(中文):它就是一条普通 Session,角色由 `origin.type = "agent"` 表达;
|
|
44
|
+
* 一个 Agent 至多一条,被归档或删除后下一次调用自然重建。
|
|
45
|
+
*/
|
|
46
|
+
/** 返回 Agent 自己的那条会话;不存在时创建。 */
|
|
47
|
+
self(options?: AgentGetSessionOptions): Promise<AgentSession>;
|
|
48
|
+
|
|
35
49
|
/** 在指定 Workspace 中创建一个属于当前 Agent 的 Session。 */
|
|
36
50
|
create(options?: AgentCreateSessionOptions): Promise<AgentSession>;
|
|
37
51
|
|
|
@@ -54,9 +68,6 @@ export interface AgentSessionCollection {
|
|
|
54
68
|
/** 清空已归档 Session。 */
|
|
55
69
|
clean_archive(): Promise<AgentCleanArchiveResult>;
|
|
56
70
|
|
|
57
|
-
/** 把 Session 重新绑定到另一个 Workspace,并返回新上下文下的 Session 实例。 */
|
|
58
|
-
workspace(session_id: string, workspace: WorkspaceRuntime): Promise<AgentSession>;
|
|
59
|
-
|
|
60
71
|
/** 获取 Session runtime port。 */
|
|
61
72
|
runtime(session_id: string, origin_type?: string): SessionPort;
|
|
62
73
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 取用 Agent 自己那条会话时的可选参数。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - Agent 自己那条会话就是普通 Session,因此这里的语义与创建普通 Session 一致。
|
|
6
|
+
* - 已经存在时该参数被忽略:换目录走 `session.set({ workspace })`,避免静默切目录。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { WorkspaceRuntime } from "@downcity/type";
|
|
10
|
+
|
|
11
|
+
/** 取用 Agent 自己那条会话的输入。 */
|
|
12
|
+
export interface AgentSessionInput {
|
|
13
|
+
/** 首次创建时注入的 Workspace 实例;省略时该会话没有项目上下文。 */
|
|
14
|
+
workspace?: WorkspaceRuntime;
|
|
15
|
+
}
|
|
@@ -36,11 +36,18 @@ import type {
|
|
|
36
36
|
import type {
|
|
37
37
|
ListSessionMessagesInput,
|
|
38
38
|
SessionMessagePage,
|
|
39
|
+
WorkspaceRuntime,
|
|
39
40
|
} from "@downcity/type";
|
|
40
41
|
import type { AgentSessionPromptInput } from "@/types/sdk/AgentSessionPrompt.js";
|
|
41
42
|
import type { AgentSessionStopResult } from "@/types/sdk/AgentSessionStop.js";
|
|
42
43
|
import type { AgentSessionTurnHandle } from "@/types/sdk/AgentSessionTurn.js";
|
|
43
44
|
import type { SessionOrigin } from "@downcity/type";
|
|
45
|
+
import type {
|
|
46
|
+
AgentSessionStateKey,
|
|
47
|
+
AgentSessionStateValues,
|
|
48
|
+
RemoteSessionStateKey,
|
|
49
|
+
RemoteSessionStateValues,
|
|
50
|
+
} from "@/types/agent/SessionState.js";
|
|
44
51
|
|
|
45
52
|
/**
|
|
46
53
|
* SDK Session 集合入口。
|
|
@@ -72,7 +79,7 @@ export interface AgentSessionActor {
|
|
|
72
79
|
/** 当前 session 稳定标识。 */
|
|
73
80
|
readonly id: string;
|
|
74
81
|
|
|
75
|
-
/** 当前 Session
|
|
82
|
+
/** 当前 Session 创建时确定的当前目录;可在会话中切换。 */
|
|
76
83
|
readonly workspace_id?: string;
|
|
77
84
|
/** 当前 Session 的创建来源。 */
|
|
78
85
|
readonly origin: SessionOrigin;
|
|
@@ -114,12 +121,16 @@ export interface AgentSession extends AgentSessionActor {
|
|
|
114
121
|
/** 当前 session 所属 agent_id。 */
|
|
115
122
|
readonly agent_id: string;
|
|
116
123
|
|
|
117
|
-
/**
|
|
118
|
-
readonly config: AgentSessionConfigSnapshot;
|
|
119
|
-
|
|
120
|
-
/** 写入当前 session 默认配置。 */
|
|
124
|
+
/** 写入持久化配置;`model`、`security`、`workspace` 共用这一个入口。 */
|
|
121
125
|
set(input: AgentSessionSetInput, options?: AgentSessionSetOptions): Promise<void>;
|
|
122
126
|
|
|
127
|
+
/**
|
|
128
|
+
* 读取状态;持久化字段与运行时实例共用这一个入口。
|
|
129
|
+
*
|
|
130
|
+
* 关键点(中文):只读且无副作用,不解析也不落盘。
|
|
131
|
+
*/
|
|
132
|
+
get<K extends AgentSessionStateKey>(key: K): AgentSessionStateValues[K];
|
|
133
|
+
|
|
123
134
|
/** 修改当前 Session 的用户可见标题并发布 canonical mutation。 */
|
|
124
135
|
rename(title: string): Promise<string>;
|
|
125
136
|
|
|
@@ -140,6 +151,13 @@ export interface RemoteAgentSession extends AgentSessionActor {
|
|
|
140
151
|
/** 写入当前远程 Session 的可序列化动态配置。 */
|
|
141
152
|
set(input: RemoteSessionSetInput, options?: AgentSessionSetOptions): Promise<void>;
|
|
142
153
|
|
|
154
|
+
/**
|
|
155
|
+
* 读取当前远程 Session 的可序列化状态。
|
|
156
|
+
*
|
|
157
|
+
* 关键点(中文):远程边界只传递可序列化值,因此进程内实例类键不可用。
|
|
158
|
+
*/
|
|
159
|
+
get<K extends RemoteSessionStateKey>(key: K): RemoteSessionStateValues[K];
|
|
160
|
+
|
|
143
161
|
/** 从当前远程 session 创建一个分叉会话。 */
|
|
144
162
|
fork(input?: AgentSessionForkInput | string): Promise<RemoteAgentSession>;
|
|
145
163
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentSessions 解析单个 Session 执行上下文时的输入。
|
|
3
|
+
*
|
|
4
|
+
* 职责说明(中文)
|
|
5
|
+
* - 执行上下文按会话身份解析:Agent 会话比其他 Session 多一组工具。
|
|
6
|
+
* - 会话身份就是它的完整 `origin`,因此不需要新增会话类型字段。
|
|
7
|
+
*
|
|
8
|
+
* 关键点(中文)
|
|
9
|
+
* - 这里描述「创建/恢复这一个 Session 需要什么」,不是 Agent 全局配置。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { SessionOrigin, WorkspaceRuntime } from "@downcity/type";
|
|
13
|
+
|
|
14
|
+
/** 单个 Session 的执行上下文解析输入。 */
|
|
15
|
+
export interface SessionContextResolution {
|
|
16
|
+
/** 本次创建或恢复的 Session 完整来源;`type` 即会话身份。 */
|
|
17
|
+
readonly origin?: SessionOrigin;
|
|
18
|
+
|
|
19
|
+
/** 本次创建或恢复时指定的当前目录;未指定时创建使用 Agent 默认目录。 */
|
|
20
|
+
readonly workspace?: WorkspaceRuntime;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 本次解析是否用于创建新会话。
|
|
24
|
+
*
|
|
25
|
+
* 关键点(中文):Agent 的默认目录只在新会话生效;恢复时必须由宿主注入,
|
|
26
|
+
* 不能用默认值顶替,否则会静默换目录。
|
|
27
|
+
*/
|
|
28
|
+
readonly creating?: boolean;
|
|
29
|
+
}
|