diff --git a/apps/server/src/modules/agent/runner.ts b/apps/server/src/modules/agent/runner.ts index 475e6356..7e6b01e9 100644 --- a/apps/server/src/modules/agent/runner.ts +++ b/apps/server/src/modules/agent/runner.ts @@ -81,9 +81,16 @@ export function scopeFor(mode: "execute" | "plan_only", allowlist?: readonly str return { capabilities: allowlist?.length ? [...allowlist] : all.map((d) => d.id), allowedKinds: null }; } -export function sessionDirOf(env: AgentEnv): string { +/** + * 会话目录优先级:模块装配参数(测试)> settings.agent.sessionDir > AGENT_SESSION_DIR(main 按 DATA_DIR 算好注入) + * > ./data/agent-sessions + */ +export function sessionDirOf(env: AgentEnv, configured?: string | null): string { return resolve( - env.opts.sessionDir ?? Deno.env.get("AGENT_SESSION_DIR") ?? join(Deno.cwd(), "data", "agent-sessions"), + env.opts.sessionDir ?? + configured ?? + Deno.env.get("AGENT_SESSION_DIR") ?? + join(Deno.cwd(), "data", "agent-sessions"), ); } @@ -349,7 +356,7 @@ export async function drive( systemPrompt: systemPromptFor(run, scope), capabilities: scope.capabilities?.length ? scope.capabilities : Object.keys(CAPABILITY_CATALOG), settings, - sessionDir: sessionDirOf(env), + sessionDir: sessionDirOf(env, s.agent.sessionDir), actor, firstSeq: maxSeq?.m === null || maxSeq?.m === undefined ? 0 : Number(maxSeq.m) + 1, }); diff --git a/apps/web/app/routes/console/settings/prefs-tab.tsx b/apps/web/app/routes/console/settings/prefs-tab.tsx index 6576fbac..113e849a 100644 --- a/apps/web/app/routes/console/settings/prefs-tab.tsx +++ b/apps/web/app/routes/console/settings/prefs-tab.tsx @@ -247,6 +247,14 @@ const AGENT_RUNTIME_CARD: CardSpec = { kind: "textarea", hint: '{"match":"收进来","steps":[{"toolCalls":[{"capability":"inventory.query","input":{"query":"沙丘2"}}]}],"finalText":"…"},格式见 docs/runbook/agent.md', }, + { + name: "agent.sessionDir", + label: "会话目录", + kind: "text", + wide: true, + placeholder: "留空用部署默认:DATA_DIR/agent-sessions(容器内 /data/agent-sessions)", + hint: "绝对路径,api 与 worker 进程都要可读写;有运行中的会话时不要改(续跑会找不到旧会话文件)", + }, ], fromSettings: (s) => ({ "agent.maxToolCalls": n(s.agent.maxToolCalls), @@ -256,6 +264,7 @@ const AGENT_RUNTIME_CARD: CardSpec = { "agent.goalPollSec": n(s.agent.goalPollSec), "agent.goalWaitHours": n(s.agent.goalWaitHours), "agent.fakeScript": s.agent.fakeScript ?? "", + "agent.sessionDir": s.agent.sessionDir ?? "", }), toPatch: (v) => ({ agent: { @@ -266,6 +275,7 @@ const AGENT_RUNTIME_CARD: CardSpec = { goalPollSec: num(v["agent.goalPollSec"]), goalWaitHours: num(v["agent.goalWaitHours"]), fakeScript: str(v["agent.fakeScript"]).trim() || null, + sessionDir: str(v["agent.sessionDir"]).trim() || null, }, }), }; diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index 177f10e4..a387c938 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -98,6 +98,11 @@ export const AgentRuntimeSettings = z.object({ .max(24 * 30), /** fake 模式脚本(JSON,FakeScript 的可序列化子集);null 用内置演示脚本 */ fakeScript: z.string().max(100_000).nullable(), + /** + * Agent JSONL 会话目录(绝对路径,api 与 worker 进程都要能读写); + * null = 部署默认(环境变量 AGENT_SESSION_DIR,缺省 DATA_DIR/agent-sessions) + */ + sessionDir: z.string().max(1024).regex(/^\//, "必须是绝对路径").nullable(), }); export type AgentRuntimeSettings = z.infer; @@ -168,6 +173,7 @@ export const DEFAULT_SETTINGS: GlobalSettings = { goalPollSec: 60, goalWaitHours: 72, fakeScript: null, + sessionDir: null, }, metadata: { tmdbApiKeySecretRef: null, tmdbBaseUrl: null }, };