LTJT 生产控制平面
这是围绕现有 LTJT 浏览器适配器增加的生产级控制层。它不新增 ERP 业务路线,也不替换 LTJT;它负责把现有任务、解析结果、确认、插件接管、执行回查和审计变成可持久化、可恢复的服务端状态。
服务边界
- PostgreSQL 是任务、事件、幂等、审计和回查状态的唯一事实源。
- 平台账号使用
admin(管理员)、team_lead(组长)和user(普通用户)三种固定角色登录;服务端会话使用 HttpOnly/Secure/SameSite Cookie。管理员维护账号、角色、状态、密码重置、会话撤销和可执行任务类型;平台不提供组织或租户选择。 - 登录会话默认跨浏览器重启持续有效,不按空闲时间或绝对时长自动失效;用户退出、修改密码、账号停用或管理员强制撤销时由服务端立即撤销。
- 业务页面通过 REST 创建任务;Agent 返回结构化结果后,任务归属人在自己的任务上一次点击“确认并提交到 ERP 插件”,再通过 SSE 或轮询读取服务端状态。普通用户与组长的任务 API、SSE、附件和插件执行权限覆盖分配给本人的人工任务与 AgentBus 任务;管理员可查看固定部署范围内的全部任务,但确认、插件领取和执行回执仍必须来自任务归属账号,查看权限不等于执行权限。
- 手工与 AgentBus 的每个新任务都会按同一组织、同一 18 项业务路由固化解析策略及配置 revision;来源不能覆盖模式。其中两项名单 route 固定为
program_only,其余 16 项可配置ai / shadow / auto / program。全消息中的唯一已登记指令可以确定 route;没有指令时,只有全部标签都属于唯一 route、至少两个不同标签且业务定位必填项完整的字段签名才可确定 route。未知、冲突或多 route 输入不猜测。后续补充轮次沿用原任务快照,设置变化只影响新任务和新会话。 - 管理员天然拥有全部 18 类人工业务。组长和普通用户使用逐账号白名单,新账号默认没有任何可执行任务类型;管理员在
/accounts逐项授权后才能提交对应的新任务、补充指令或名单附件。已登记但未授权的业务返回business_not_authorized;无法唯一确认 route 的非管理员输入返回business_type_unresolved。两种拒绝都发生在解析器和 ERP 插件之前,并记录不含明文的授权拒绝审计。权限在补充输入、人工确认、全自动确认和插件领取前再次校验;取消授权后的任务不会进入 ERP 队列。AgentBus 入站使用渠道绑定员工的同一白名单,管理员账号不得成为员工渠道归属人。 - 名单 route 创建后先进入
awaiting_attachment,只接收一份.xls/.xlsx;控制面在前 100 行中自动定位唯一的 ERP 名单字段表头,按精确字段语义映射任意表头行、列顺序及受支持别名,并把表头下方连续名单数据在内存中失败关闭地规范化为 13 列 canonical TSV。未知额外列、重复语义字段和多个候选表头继续阻断。原始工作簿不入库,文件名和 canonical TSV 使用字段加密,解析通过或终止后清除 canonical 中间文本。附件到齐前不会领取解析任务,也不会进入 ERP。独立团初始 16 行、散拼子单初始 31 行仅为 ERP 动态扩行基线,5000 为技术上限。 - 平台的任务 ID、Agent 会话 ID、确认状态、重要摘要、事件和用户通讯内容只存在于平台任务/会话/结果信封中,不回写到 Agent
operation,也不成为 ERP 业务字段。 - Agent
operation只保存解析态业务事实。插件领取已确认任务后先做无需 ERP 查询的前门禁,再在 ERP 内只读唯一解析对象、资源和当前状态,最后对内部 execution operation 执行严格写前门禁。 - Chrome 插件仍在当前平台账号已登录的浏览器/ERP 会话中工作;每个员工平台账号必须配置唯一 ERP 账号,同一平台账号在 90 秒新鲜期内只允许一台云电脑保持执行 worker。心跳携带扩展对期望 ERP 账号的只读匹配结果;账号不匹配、心跳过期、第二台并发云电脑或任务归属不一致都会在领取和回执两端失败关闭。
chrome.storage.local只是临时缓存。 confirmation_export先按对象范围选择 ERP 源:独立团/散拼具体子单的游客名单使用did+tid,散拼母团的整团游客信息只使用tid,且母团只开放这一单一文件类型。附件在TaskArtifactStore写入前按类型处理:游客信息保留原始 ERP.xls作为源归档,同时由平台通过 LibreOffice 生成真实.xlsx;AgentBus/微信只投递.xlsx,若 XLSX 转换失败则阻断外部附件交付。其他类型由平台转换为 PDF,转换失败回退对应 ERP 源文件。生产附件写入 OSS;PostgreSQL 只保存任务归属、文件元数据和 OSS object key。OSS Bucket 按当前生产策略公共可读,但只有后端凭据可写/删;对象 URL 使用随机执行 UUID,不包含客户名或团号。- ERP 写入继续遵循预检、任务归属人确认(或管理员配置的全自动化)、单次提交、ERP 回查;不确定结果禁止自动重试。
- 由于插件不使用独立凭据,执行期间必须保持任务归属人的业务页面和已登录 ERP 浏览器会话可用;页面断开不会触发自动补偿或重复提交。
- 服务端先创建唯一 ERP execution,再允许页面向插件下发;同一任务只有一个
erpattempt。刷新、重连、超时和迟到回执都不能创建第二次执行。 - 插件回执必须携带服务端
execution_id和领取连接;完成、阻断及待回查状态不可被后续running回执覆盖。执行租约过期会进入待回查,不会重新入队。 - Chrome 插件最低兼容版本由正式操作台与根目录发布清单共同门禁。插件包含分段前门禁、ERP 只读唯一解析、严格写前门禁、当前窄生命周期适配、写入前
write_started持久化和写后回查;扩展后台重启后也不会重跑同一任务。本次双轨解析不修改插件执行契约或扩展版本。 - 渠道 Adapter 由 AgentBus 负责;控制平面只作为 AgentBus Bot 连接到文档中的 WebSocket,不实现微信、个人微信或其他渠道协议。
- 微信桥接器把正文放在严格的
New WeChat message/Conversation:/Text:三行传输信封中;AgentBus listener 会在任务快照前只解开这一已知信封,把Text:同行值及后续行作为业务正文,并在帧没有显式conversation_id时使用信封中的Conversation:值。显式字段仍优先;近似、缺失字段或空正文的包装保持原文,不能通过忽略任意未知标签来绕过 Program parser 的失败关闭。 - 微信侧的
[WeChat attachment: 文件名]只是一段传输占位文字,不代表控制面已经收到文件。若同一帧没有符合契约的payload.attachments[],listener 会在进入任务服务前失败关闭、保留原名单任务的等待状态,并返回“附件内容未传到平台”;不会把占位文字创建成新业务任务。附件元数据、HTTPS URL、DNS、大小或摘要校验失败时返回对应的安全摘要,仍不回显 URL、文件字节或名单内容。当前生产部署位于受信内网,入站附件 URL 可以使用内网域名、私网 IPv4/IPv6 或 localhost;因此 AgentBus 渠道和上游桥接器必须被视为受信输入边界。 - AgentBus 入站消息会复用
TaskService的任务/会话/解析队列,并以渠道绑定员工写入created_by与不可变的assigned_user_id,解析完成后通过同一 WebSocket 返回一次task.result。组织级“全自动化”关闭时,手工与 AgentBus 新任务都需要人工确认;开启后,两种来源的合法解析结果都自动进入 ERP 队列,不再按来源或创建、名单、安排、修改、取消/恢复、导出等业务类型保留人工例外。历史未归属 AgentBus 任务不会自动执行。操作台在 EventSource 建连/重连、30 秒后台刷新以及页面重新可见或聚焦时重新读取数据库权威开关。缺资料、解析失败、歧义、插件校验失败或 ERP 回查不确定时仍会停止,不会绕过校验或重试不确定写入。 - 单一部署范围可以维护多个“用户渠道”。每个渠道代表一个外部 AgentBus 用户身份,并且必须一对一绑定一个有效的非管理员平台账号;一个平台账号也只能绑定一个渠道。管理员在
/channels创建、绑定、停用、启用、轮换或删除渠道。只有绑定账号有效且已配置 ERP 账号的启用渠道才启动 listener;未绑定渠道失败关闭。删除会停止对应 listener、移除服务端保存的 key 和该渠道尚存的持久化回执;历史任务本体保留,其channel_id置空而assigned_user_id不变。每个渠道独立保存加密后的 AgentBus key,同一 key 不能被多个渠道复用;列表和日志都不会回显 key。AGENTBUS_WS_URL、重连策略和客户端类型仍是全局连接配置,AGENTBUS_BOT_ADDRESS可作为渠道 bot address 的默认值。 /history使用可恢复的归档/恢复,不提供物理删除。单条兼容路由DELETE /api/tasks/:taskId与批量兼容路由POST /api/tasks/bulk-delete也只执行归档;普通用户和组长只能归档/恢复本人任务,管理员可以处理全部授权任务。任务、输入、事件、尝试、附件元数据与审计记录继续保留,物理清除必须等待单独批准的保留期限和不可逆清除设计。/operations-dashboard是组长和管理员专用的只读业务操作看板。它支持从结果状态、操作人、上海业务日期和业务类型逐层穿透,并可在选定范围内查询姓名、完整初始/补充指令、业务结果、团号或订单号。列表和详情只回答“谁提交了什么指令、完成了什么结果”:详情返回操作人、业务类型、完整指令轮次、输入附件名称/行数和可读业务结果,不返回任务生命周期、解析/执行 JSON、技术阶段、错误码或产物地址。关键词查询先受日期、人员、业务和状态约束,单次解密匹配候选最多 2,000 条,超过时要求继续缩小范围。该路径不授予他人任务修改、ERP 执行、SSE、产物下载、账号维护或全局安全审计权限,并排除 AgentBus/system 任务。- 使用数据库渠道时设置
AGENTBUS_ENABLED=true;此模式不要求AGENTBUS_WS_TOKEN或AGENTBUS_BOT_ADDRESS,但启用的渠道仍需要全局AGENTBUS_WS_URL,并可在渠道上覆盖 bot address。保留旧环境变量配置时,服务会按需创建“默认 AgentBus 渠道”兼容旧单渠道部署;兼容渠道初始为未绑定且不启动,管理员必须在/channels绑定员工账号后再启用。AGENTBUS_ENABLED=auto仅由完整的旧环境连接字段自动启用。 user_channels、tasks.channel_id和agentbus_deliveries共同保存入站归属、accepted 受理回执和最终 result 回执。回执以(channel_id, inbound_frame_id, delivery_kind)幂等,发送失败会重试,进程重启或 WebSocket 重连后仍会继续投递;因此不会因为超过原等待时长而丢掉最终回复。- ERP 插件领取由组织级数据库锁和 FIFO confirmed 队列统一串行化:同一组织/同一 ERP 浏览器会话在任意时刻最多一个 ERP execution,其他任务留在服务端等待;已开始写入但结果不确定的任务会阻塞后续领取,直到人工回查收敛。
Chrome 插件自动更新
插件更新由本控制平面统一编排,不在 Windows Server 上增加单独的 LTJT 更新服务。中央 Node 服务把管理员批准的 ZIP 写入 OSS 私有对象,再通过阿里云 ECS 云助手向目标 Windows Server 下发一次性 PowerShell 命令。云助手只负责本次文件部署;版本判断、空闲门禁、重试、状态和验版均保存在控制平面与 PostgreSQL 中。
更新链路为:
管理员发布版本化 ZIP
→ 服务端校验 ZIP 边界、Manifest 身份/版本、必需文件和 SHA-256
→ 私有对象写入 OSS,并把该版本设为组织活动版本
→ 浏览器心跳上报当前版本与插件内存/持久化空闲证明
→ 服务端汇总同一 ECS 实例上全部账号、任务、执行尝试与待回查状态
→ 完全空闲后,以主机级数据库锁启动 ECS 云助手命令
→ Windows 下载短时、主机绑定的服务端地址并再次校验 SHA-256 与 Manifest
→ 新目录暂存,旧目录保留为 `.previous`,原子切换失败则回滚
→ 平台请求插件后台再次检查空闲状态并执行 `chrome.runtime.reload()`
→ 刷新平台页;目标版本的新心跳到达后标记 `verified`,再开放新 ERP 任务
不存在固定的业务等待时间:已经空闲的主机会立即开始;仍有 accepted/running/write_started/submitted/uncertain/reconciliation_pending 边界时持续等待真实状态收敛。更新处于等待、执行、待重载、配置缺失或最终失败时,页面和服务端领取接口都会阻止新的 ERP 写任务。单个主机版本最多自动尝试三次;失败信息经过 URL/令牌脱敏后才写入状态。
多台、多账号 Windows Server 按以下方式配置:
- 每台 ECS Windows Server 必须安装并正常连接阿里云云助手。中央服务所在机器需要访问 OSS 与 ECS API;每台 Windows Server 需要能访问
APP_ORIGIN的 HTTPS 插件下载接口。该链路不依赖 Google 服务。 - 每台 Windows Server 统一使用
C:\ProgramData\LTJT\chrome-extension\ltjt-order-assistant。同机的每个 Windows/Chrome profile 都只需在chrome://extensions中把这个相同目录“加载已解压的扩展程序”一次。 0.5.167是引导版本,包含安全状态与受控重载协议。现有0.5.166及更旧 profile 必须最后一次人工迁移到上述共享目录;旧代码无法凭服务端单方面获得新协议。完成全部 profile 引导前保持EXTENSION_AUTO_UPDATE_ENABLED=false。- 为中央服务配置仅允许目标 ECS 实例执行命令和读取调用结果的 RAM 身份,并填写
ALIBABA_CLOUD_ACCESS_KEY_ID、ALIBABA_CLOUD_ACCESS_KEY_SECRET,使用临时身份时同时填写ALIBABA_CLOUD_SECURITY_TOKEN。不要复用宽权限 OSS 身份。 - 配置 OSS、
EXTENSION_UPDATE_OSS_KEY_PREFIX、共享安装目录、包大小与命令超时,再启用EXTENSION_AUTO_UPDATE_ENABLED=true并重启控制平面。生产APP_ORIGIN必须是 Windows Server 可达的 HTTPS 地址。 - 管理员在
/accounts给每个非管理员账号填写 ECS 地域 ID 与实例 ID。同一 Windows Server 上的多个账号填写完全相同的一组值,服务端会把它们合并成一个主机级空闲门禁和更新状态。 - 管理员在
/accounts的“插件版本发布”区域选择版本化 ZIP。服务端只接受比当前活动版本更高的版本;同一版本不同哈希会被拒绝。发布后无需逐台登录,在线浏览器的正常心跳会启动更新并完成验版。
更新命令只允许操作配置的 ProgramData\LTJT 子目录,不安装 CRX、不修改 Chrome 策略、不操纵交互式桌面。若某个 profile 长期离线,它不会阻塞在线 profile;再次启动时会从已更新的共享目录加载目标版本并在下一次心跳完成验证。发布、迁移和运行状态由迁移 019_extension_host_updates 中的 extension_releases、extension_host_updates 以及账号 ECS 映射保存。
任务级会话续接
agent_sessions将一个业务任务绑定到一个 Superagent 会话;agent_session_messages加密保存每一轮用户/Agent 消息并使用独立幂等键。- 解析缺少可由用户补充的信息时,任务进入
awaiting_user_input,不会进入 ERP 或 Chrome 插件执行链。不带业务指令的补充信息通过同一任务重新排队,沿用原会话;新任务始终创建新会话。若/api/messages的新消息首个非空行命中已知业务指令,则按新任务处理,不会被旧的等待任务或旧会话吸收。 - 外部渠道或系统集成可使用已认证、CSRF 保护的
POST /api/messages续接任务;操作台在awaiting_user_input时显示文字补充框,在awaiting_attachment时显示单文件.xls/.xlsx补交入口,提交时携带当前task_id。文件字节只保留在浏览器内存到请求完成,不进入sessionStorage或日志;左侧输入区仍始终代表新任务。全消息中的唯一已登记业务指令会开启新任务;字段签名只为已经确定是新任务的输入选择 route,不会单独改变补充消息的会话归属。任务生命周期日志继续展示缺失信息、Agent 阶段、控制面事件和插件回执,原始 JSON 按事件缩进追加在黑框内:
POST /api/messages
Content-Type: application/json
{
"message": "补充后的用户信息",
"task_id": "TASK-...",
"conversation_id": "channel-user-or-thread",
"idempotency_key": "..."
}
名单附件补交使用同一接口,正文可以为空:
{
"message": "",
"task_id": "TASK-...",
"attachments": [{
"name": "passenger-list.xlsx",
"content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"size": 12345,
"content_base64": "..."
}]
}
不带业务指令时,有 task_id 才精确续接;没有 task_id 时只有同一 conversation_id 下唯一一个等待补充任务才会自动匹配,多任务返回 task_selection_required,没有待补充任务则创建新任务。首个非空行带已知业务指令时,无论是否携带旧 task_id/conversation_id 都创建新任务和新会话。普通接口和任务卡不会暴露原始外部 session_id。微信接入和线上 Profile 发布属于后续接入工作。
双轨解析与渐进切换
business-routes.ts 是 18 项路由的唯一机器注册表,手工与 AgentBus 共用同一个全消息 route resolver 和任务级模式快照;program-parser.ts 按“规范化 → 路由 → 字段拆分 → 类型解析 → 业务 builder → 统一 operation 校验”确定性编译输入。AI 和程序结果都经过 external-agent-client.mjs 导出的同一最终契约校验;operation Schema、插件执行契约和 ERP 写入逻辑不因解析模式或任务来源改变。
两项名单 route 是硬性例外:allowed_modes=['program']、mode_locked=true,组织设置、紧急全部切 AI 和一次性 AI 重解析都不能覆盖;紧急开关只作用于其余 16 项可配置业务。
ai:只运行原 AI 路径;迁移后的设置和历史任务均从该模式开始。shadow:AI 是唯一权威结果,程序并行解析;两份候选只加密保存,任务详情只按差异路径解密展示 operation 字段,不公开完整候选或传输元数据。auto:程序优先;仅program_unsupported_syntax、program_unknown_field、program_internal_error、program_timeout、program_contract_invalid可整体切到 AI。缺字段、非法值、冲突、歧义、多动作和业务规则阻断不会调用 AI。program:只运行程序;失败时追问或阻断,不调用 AI。
Auto 一旦发生 AI fallback,任务会永久绑定原 AI 会话。每次解析只有一个完整权威结果,不拼接候选;任务进入确认、插件领取或 ERP 链路后不再自动重解析。尚未进入插件且明确 no_plugin_dispatch=true / no_erp_write=true 的解析失败任务,可由管理员授权一次 AI 重解析。
操作台根路径 /parser-routing 提供逐业务观察统计、即时切换检查、revision 乐观并发控制和事务级“全部切回 AI”。连续天数、任务数量和 fallback 比率不作为等待门槛:18 项已实现业务均可独立进入 Shadow,多个 Shadow 可并行测试且不受固定前序约束;已出现的差异全部判定且本业务程序关键、契约、运行错误为零即可进入 Auto;程序下游失败和上述错误为零即可进入 Program。同一时间最多一个业务处于 Auto,晋级仍只能 AI → Shadow → Auto → Program 逐级进行,回退可直接执行。7/30 日统计仅供观察。组织已开启全自动化时,切入 Auto/Program 不增加人工确认期。
主要接口:
GET /api/settings/parser-routingPUT /api/settings/parser-routing/:routeIdPOST /api/settings/parser-routing/emergency-aiPOST /api/tasks/:taskId/reparsePUT /api/parser-decisions/:decisionId/review
迁移 013_business_parser_modes 增加内部固定范围的路由设置、任务快照和加密的 parse_decisions;迁移 014_task_input_attachments 增加名单输入附件元数据、加密 canonical TSV 与 awaiting_attachment 索引;迁移 015_account_roles_and_task_audit 增加账号角色、密码更新时间、输入/附件操作者、任务归档和账号级幂等(历史 must_change_password 列仅保留兼容,当前流程不启用首次强制改密);迁移 016_team_lead_operations_dashboard 增加组长角色与人工指令看板索引;迁移 017_user_business_route_authorizations 增加逐账号业务白名单、授权人和乐观并发 revision;迁移 018_agentbus_account_workers 增加 ERP 账号、渠道归属、任务执行归属、唯一在线 worker 与 ERP 身份核验字段。原文、完整程序/AI 候选、名单 canonical 中间文本和人工说明使用字段加密保存;统计、全局审计和运行日志不复制明文业务输入。
AgentBus Bot 接入
数据库渠道模式在服务端受保护的环境文件中配置全局 AGENTBUS_WS_URL 并设置 AGENTBUS_ENABLED=true。管理员先在 /accounts 为员工配置唯一 ERP 账号和允许的业务类型,再在 /channels 创建渠道、填写该渠道自己的 AgentBus key,并选择对应员工账号。员工随后在自己的云电脑同时登录该平台账号与所绑定 ERP 账号并打开扩展。只有这四项就绪的启用渠道才启动长期 listener。
旧单渠道环境变量仍可创建一个兼容渠道记录,但该记录初始未绑定、停用且不启动;管理员必须完成员工绑定后手动启用。AGENTBUS_ENABLED=auto 只用于识别完整的旧环境连接字段,不会绕过账号绑定。
每个启用渠道会连接 AGENTBUS_WS_URL?ready=1,使用该渠道自己的 Authorization: Bearer <channel-agentbus-key>,等待 session.ready 后接收普通 event 消息。普通任务发送一次持久化受理通知(task.progress,status=accepted)并在完成时返回一次 task.result。名单任务的首次文字指令改为返回明确的等待附件提示,附件入站改为返回“名单附件已收到,正在校验并处理”;最终结果只归属触发解析的最新附件消息,因此不会因文字与附件两条入站帧重复发送成功回执。解析完成、等待确认或进入 ERP 等内部进度不外发。GET /health/ready 和 GET /api/status 的 agentbus.channels 字段可用于确认每个 listener 与 session 是否建立。
对微信来源,listener 在调用 TaskService.ingestMessage() 前执行上述严格信封解包,因此手工正文与 AgentBus 正文进入同一个业务 route resolver、任务级 mode snapshot 和 parser orchestrator;Conversation 只属于传输路由,不会再污染业务字段签名。
AgentBus 全链路日志使用控制平面 stdout/Pino 输出,同时带 diagnostic_event=agentbus.<agentbus_event> 与原有 agentbus_event。日志覆盖连接尝试、socket 生命周期、session.ready、每个收发帧、帧忽略原因、任务入队、解析队列、持久化回执出队、最终回复和发送错误。名单附件另外记录元数据存在性、DNS 开始/通过、解析地址数量与 IP family、HTTPS 状态、重定向次数、接收字节数、大小/摘要校验和各阶段耗时;不记录 URL、hostname、IP、文件名、附件字节或名单值。开发/测试环境可临时设置 AGENTBUS_LOG_PAYLOADS=true 记录最多 2,000 个字符的正文预览,生产环境会拒绝以该值启动。渠道 key、WebSocket Token、Invoke Token 和 Authorization header 永不写入日志。
监听器只使用每个渠道的 WebSocket key;文档中的 Invoke Token 仅用于另一服务通过 Function Call API 主动向 Bot 投递任务,本服务的监听链路不会使用它。
任务失败时,公共任务响应的 failure 字段和最终生命周期事件会同时给出 error_code、failure_stage、failure_source、failure_message、agent_returned、plugin_dispatch_started、erp_write_started、no_plugin_dispatch 与 no_erp_write。因此可以区分“Agent 未返回”“Agent 已返回但契约校验失败”“插件已接管但 ERP 未写入”和“ERP 已写入但回执待回查”等状态,不再只显示笼统的“外部解析失败”。
任务响应统一提供 important_message,作为“需返回/交互用户的重要消息”出口:awaiting_user_input 返回 Superagent 的 reply,错误优先返回 ERP 业务反馈、否则返回简短错误摘要,成功且有可验证 ERP 证据时按 AgentBus 用户回复契约 生成业务回执。操作台保留完整结构供内部展示,AgentBus task.result 发往渠道时携带 kind、用户可读 text 和符合微信渠道适配器契约的 HTTPS URL-only 附件元数据;不发送 content_base64 或后台鉴权下载地址。外部文件投递要求生产附件使用 OSS 存储,且 URL 域名已加入适配器白名单。错误码、执行阶段、回执校验状态、隐藏 ID 和哈希不外发。awaiting_confirmation 不进入该栏。没有 reply 的 awaiting 结果会在解析阶段转为错误;没有可验证回执的 completed 执行会被归类为 reconciliation_pending,不会返回成功回执。
服务端诊断日志
控制面、迁移、数据保留、数据库异常和管理员命令都输出一行一个 JSON 的结构化日志。公共字段包括 time、level、service、environment、deployment_revision、pid、hostname、diagnostic_event 和 diagnostic_stage;链路字段按事件提供 request_id、task_id、inbound_frame_id、conversation_id、channel_id、状态、结果与 duration_ms。覆盖范围包括服务启动/关闭、HTTP 开始/完成/拒绝、任务状态事件、数据库 audit event、解析队列和 Worker、AgentBus、附件入口、OSS 清理、数据库连接/回滚以及未捕获进程错误。
未知异常只记录受限 error_code、error_name、稳定的 error_fingerprint、errno/syscall 和最多 12 个清理后的代码栈帧;不把第三方异常原文复制进日志。请求 query/body、Cookie、Authorization、CSRF、密码、API key、Token、附件 URL/字节和业务原文不进入生产日志。每个 HTTP 响应返回同一个 x-request-id,可直接用于跨开始、错误和完成事件检索。部署时应把 DEPLOYMENT_REVISION 设置为当前提交或发布编号,避免不同镜像的日志混淆。
Docker Compose 使用 json-file,默认每个容器保留 10 个、每个 20 MB 的轮转文件;可在 .env.production 用 LOG_MAX_SIZE 和 LOG_MAX_FILES 调整。服务端只读排障命令会显示容器状态、/health/ready 和最近日志,不读取或打印环境文件:
sh infra/diagnose-server.sh
sh infra/diagnose-server.sh --since 2h --match TASK-20260830071915-g7INIT0
sh infra/diagnose-server.sh --since 2h --match roster_attachment_metadata_missing
sh infra/diagnose-server.sh --since 30m --match request:example:12345678
sh infra/diagnose-server.sh --since 30m --all-services
--match 是字面量过滤,可使用 request/task/frame/conversation/channel ID、diagnostic_event 或 error_code。脚本默认最多读取 1,000 行控制面日志,--tail 上限为 50,000,且不在仓库生成日志文件。
本地命令
本地命令会自动读取项目根目录 .env,不需要先手动 export 环境变量。首次部署或换环境时,只需替换该文件;生产 Docker Compose 使用 .env.production。
npm install
npm run check
npm run build
npm run db:migrate
ADMIN_USERNAME=admin ADMIN_PASSWORD='replace-with-password' npm run admin -- bootstrap
npm run data:retention
npm run dev
npm run dev 和 npm start 会先执行数据库迁移,再启动控制平面;直接运行 control-plane/src/server.ts 或构建后的 server.js 时,服务也会在启动前检查必需迁移 018_agentbus_account_workers,缺失时拒绝监听端口。db:migrate 和管理员初始化需要可连接的 PostgreSQL。开发机没有数据库时,可以运行 npm run test:control-plane 完成无数据库静态/健康烟测。
/health/ready 同时检查 PostgreSQL 可用性和必需 schema 版本;迁移未完成时返回 503,并标明 required_migration,避免任务在数据库结构未升级时进入解析队列。
生产部署
- 复制
.env.production.example为部署机受保护的.env.production,填入DEPLOYMENT_REVISION、PostgreSQL URL、DATABASE_SCHEMA、字段加密密钥、外部解析 Key 和 OSS 凭据,并确认AGENTBUS_LOG_PAYLOADS=false。生产数据库可使用现有 PostgreSQL 实例中的新 Schema;迁移程序会创建 Schema,不会触碰其他 Schema 的测试表。 - 在正式数据库上线前执行并验证备份:
infra/backup-postgres.sh。 - 使用
docker compose --env-file .env.production up -d --build启动;Compose 会先执行数据库迁移,再启动控制平面。Compose 中的本地 PostgreSQL 仅用于--profile local,生产DATABASE_URL指向受保护的远程数据库。 - 首次启动后在容器内通过
docker compose exec -e ADMIN_USERNAME=admin -e ADMIN_PASSWORD='replace-with-password' control-plane node .build/control-plane/src/admin-cli.js bootstrap创建管理员;不要把密码写入镜像或 Git。 - 配置
infra/Caddyfile中的正式域名和 HTTPS,然后在 Chrome 插件中加载对应生产业务页面。 - 启动后运行
sh infra/diagnose-server.sh --since 10m,确认容器、readiness、service.listening、AgentBus session 与当前deployment_revision。
正式环境没有独立 staging。上线前必须完成离线测试、迁移预检查和迁移前备份;恢复检查使用 infra/restore-check.sh 指向一次性恢复数据库。
生产容器中的保留策略命令为:docker compose exec control-plane node .build/control-plane/src/retention.js。当前生产模板默认 DATA_RETENTION_ENABLED=false;启用后,符合期限的终态任务只会转为归档,并清理已撤销或已过期超过 30 天的登录会话,不会物理删除任务或审计记录。本地开发则使用上面的 npm run data:retention。