feat(agent): unify MCP tools and adopt Responses delivery

Move project and host queries into native MCP with host-owned OAuth/context,
while retaining memory, skills, actions and publication plugins. Add nearby-model
queries and explicit chat/task/learning model defaults.

Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped
session ownership, pending input edits and investigation communication.

Disable local-provider discovery: Server /health allowed native vLLM to probe
/v1/models on port 8000. A native before/after regression prevents recurrence.

Validation: 170 focused Bun tests; service/plugin typechecks; native discovery,
provider-discovery and input-inbox smoke checks; no paid model inference.
This commit is contained in:
2026-10-19 18:46:58 +08:00
parent f5c3cde2c2
commit 6a49d2209e
164 changed files with 12286 additions and 2368 deletions
+16 -22
View File
@@ -2,6 +2,7 @@ import dotenv from "dotenv";
import { z } from "zod";
import { loadAgentModelConfig, type AgentModelConfig } from "./chat/modelConfig.js";
import { DEFAULT_SESSION_WORKSPACE_ROOT } from "./runtime/conversationWorkspace.js";
// 本地开发可在项目根目录放 .local.env;已存在的系统环境变量优先级更高。
dotenv.config({ path: ".local.env", override: false });
@@ -46,6 +47,13 @@ const envSchema = z
TJWATER_SERVER_BASE_URL: z.string().url().default("http://127.0.0.1:8000"),
// 工况任务调用 Server 的最长等待时间,必须短于两次租约心跳之间的间隔。
TJWATER_SERVER_TIMEOUT_MS: z.coerce.number().int().positive().max(30_000).default(10_000),
TJWATER_MCP_ENABLED: optionalBoolean(false),
TJWATER_MCP_ISSUER_URI: optionalString(),
TJWATER_MCP_DEV_HTTP_ISSUER_URI: optionalString(),
TJWATER_MCP_RESOURCE_URI: optionalString(),
TJWATER_MCP_AUDIENCE: z.string().default("tjwater-mcp"),
TJWATER_MCP_CLIENT_ID: z.string().default("tjwater-agent-mcp"),
TJWATER_MCP_CLIENT_SECRET: optionalString(),
// Pino 日志级别。
LOG_LEVEL: z.string().default("info"),
// LLM 工具/技能调用审计日志路径。
@@ -54,8 +62,8 @@ const envSchema = z
.default("./logs/llm-request-audit.log"),
// 内部工具桥鉴权 token;embedded 可自动生成,client 模式必须与外部 OpenCode 显式共享。
AGENT_INTERNAL_TOKEN: optionalString(),
// 每个 OpenCode 会话独占的 Shell 工作目录;与受保护的业务 data/logs 分开。
CONVERSATION_WORKSPACE_ROOT: z.string().default("./.agent-workspaces"),
// 每个 OpenCode 会话独占的文件工作目录;其他 data/logs 仍受保护。
CONVERSATION_WORKSPACE_ROOT: z.string().default(DEFAULT_SESSION_WORKSPACE_ROOT),
SANDBOX_MAX_OUTPUT_BYTES: z.coerce.number().int().positive().max(16 * 1024 * 1024).default(1024 * 1024),
// OpenCode 2 CLI 命令或绝对路径。
OPENCODE_COMMAND: z.string().default("opencode2"),
@@ -87,21 +95,17 @@ const envSchema = z
OPENCODE_CLIENT_BASE_URL: z.string().url().optional(),
// 旧版 client 模式环境变量名,保留兼容,解析时会映射到 OPENCODE_CLIENT_BASE_URL。
OPENCODE_BASE_URL: z.string().url().optional(),
// 后端结果在直接内联返回给模型前允许的最大字节数。
MAX_INLINE_RESULT_BYTES: z.coerce.number().int().positive().default(12000),
// 生成结果 preview 时最多抽样的条目数。
MAX_PREVIEW_SAMPLE_ITEMS: z.coerce.number().int().positive().default(3),
// memory 持久化存储目录。
MEMORY_STORAGE_DIR: z.string().default("./data/memory"),
// 持久化文件写入前保留备份版本的目录。
PERSISTENCE_BACKUP_DIR: z.string().default("./data/backup"),
// 注入到 prompt 的 memory 快照最大字符数,避免上下文过大。
MEMORY_MAX_PROMPT_CHARS: z.coerce.number().int().positive().default(1800),
// session transcript 持久化目录。
// 旧 transcript 迁移来源;新记录位于用户会话工作空间的 chat/。
SESSION_TRANSCRIPT_STORAGE_DIR: z.string().default("./data/session-transcripts"),
// session metadata 持久化目录。
SESSION_METADATA_STORAGE_DIR: z.string().default("./data/session-metadata"),
// session UI state 持久化目录。
// 旧 UI state 迁移来源;新完整聊天状态位于会话工作空间的 chat/。
SESSION_UI_STATE_STORAGE_DIR: z.string().default("./data/session-ui-states"),
// 每个会话最多保留多少轮 transcript,超过后裁剪旧记录。
SESSION_TRANSCRIPT_MAX_TURNS_PER_SESSION: z.coerce
@@ -113,7 +117,7 @@ const envSchema = z
SESSION_SEARCH_MAX_RESULTS: z.coerce.number().int().positive().default(8),
// session_search 查询文本最大长度。
SESSION_SEARCH_MAX_QUERY_CHARS: z.coerce.number().int().positive().default(240),
// 当前 session 的 learning 进度状态目录。
// 旧 learning 进度迁移来源;新进度位于会话 chat/learning.json。
SESSION_LEARNING_STATE_STORAGE_DIR: z.string().default("./data/session-learning-state"),
// learning audit 日志路径。
LEARNING_AUDIT_LOG_PATH: z
@@ -127,16 +131,6 @@ const envSchema = z
LEARNING_REVIEW_MAX_RECENT_TURNS: z.coerce.number().int().positive().default(8),
// review proposal 的最低置信度阈值。
LEARNING_MIN_PROPOSAL_CONFIDENCE: z.coerce.number().min(0).max(1).default(0.8),
// result_ref 持久化存储目录。
RESULT_REF_STORAGE_DIR: z.string().default("./data/result-refs"),
// result_ref 保留时长(小时)。
RESULT_REF_TTL_HOURS: z.coerce.number().int().positive().default(168),
// 定时清理过期 result_ref 的扫描周期(毫秒)。
RESULT_REF_CLEANUP_INTERVAL_MS: z.coerce
.number()
.int()
.positive()
.default(3600000),
})
.superRefine((env, ctx) => {
if (env.OPENCODE_MODE === "client" && !env.OPENCODE_CLIENT_BASE_URL) {
@@ -177,7 +171,7 @@ const environment = envSchema.parse(normalizedEnv);
const modelConfiguration = loadAgentModelConfig(environment.AGENT_MODEL_CONFIG_PATH);
export const config: AppConfig = {
...environment, MODEL_CONFIG: modelConfiguration,
OPENCODE_MODEL: modelConfiguration.default_model,
OPERATIONAL_TASK_MODEL: modelConfiguration.task_model ?? modelConfiguration.default_model,
LEARNING_GATE_MODEL: modelConfiguration.learning_gate_model ?? modelConfiguration.default_model,
OPENCODE_MODEL: modelConfiguration.defaults.chat,
OPERATIONAL_TASK_MODEL: modelConfiguration.defaults.task,
LEARNING_GATE_MODEL: modelConfiguration.defaults.learning_gate,
};