diff --git a/README.md b/README.md index fdd576b..14ced80 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ ## 当前发布基线 - Chrome 扩展制品版本由 [发布清单](dist/release-manifest.json) 定义;运行中的浏览器版本必须通过实时握手确认,不写入静态交接文档。 -- 五个业务 Skill:`0.5.125`;运营 DOCX:`0.5.125`。 +- 五个业务 Skill:`0.5.126`;运营 DOCX:`0.5.126`。 - Agent Prompt:`ltjt-agent-prompt-v1.8-independent-headcount-categories`。 - 控制面确定性解析器:`ltjt-program-parser-v1.0.6`;18 项路由迁移后仍默认 `ai`,由操作台逐项晋级;两项名单 route 固定为 Program-only。 - 独立团/散拼子单名单固定为 Program-only:文字用于业务定位,名单由单个 `.xls`/`.xlsx` 附件确定性规范化;附件到齐前任务保持等待。 diff --git a/agent设计规范/business-adaptation-registry.md b/agent设计规范/business-adaptation-registry.md index 932b87c..a4998b9 100644 --- a/agent设计规范/business-adaptation-registry.md +++ b/agent设计规范/business-adaptation-registry.md @@ -20,7 +20,7 @@ | `team_order_create` | 独立团单个下单 | [`lwlt-newbooking`](skills/lwlt-newbooking/SKILL.md) / `team_order_create` | [业务入口](businesses/team_order_create.md) | [ERP 流程](businesses/team_order_create.md) | [Skill actions](skills/lwlt-newbooking/references/actions-and-fields.md) | 已真实验证 | | `shared_plan_create` | 散拼团新增计划 | [`lwlt-newbooking`](skills/lwlt-newbooking/SKILL.md) / `shared_plan_create` | [业务入口](businesses/shared_plan_create.md) | [ERP 流程](businesses/shared_plan_create.md) | [Skill actions](skills/lwlt-newbooking/references/actions-and-fields.md) | 单个/批量母团已重复验证;E2E3/E2E4 均证明批量每日期具体子单、客户与 15+1,统一发布仍待批准 | | `shared_child_order_create` | 散拼团单个新增子单/计划 | [`lwlt-newbooking`](skills/lwlt-newbooking/SKILL.md) / `shared_child_order_create` | [业务入口](businesses/shared_child_order_create.md) | [ERP 流程](businesses/shared_child_order_create.md) | [Skill actions](skills/lwlt-newbooking/references/actions-and-fields.md) | 已重复真实创建并完成后续全生命周期;仍受统一发布门槛约束 | -| `passenger_list_import` | 独立团/散拼子单名单导入/补充 | [`lwlt-lifecycle`](skills/lwlt-lifecycle/SKILL.md) / `passenger_list_import`(Program-only) | [统一输入模板 §5—6](templates/business-input-templates.md);文字定位 + 单个 `.xls/.xlsx` 附件;程序在前 100 行自动定位唯一 ERP 字段表头,按精确语义映射任意表头行、列顺序及受支持别名,并读取其下连续名单数据 | 识别 route 后等待附件 → 程序规范化为加密 13 列 TSV,并从唯一严格`领队`备注行提取姓名/电话 → 读取 ERP 游客位 → 未确认的占用序号要求覆盖确认;确认后所有指定序号强制覆写(同值也写)→ `DaoRuDones` → 原生游客位不足时返回“名单 N 人超过 ERP 最多 M 人”且不保存 → 恢复未指定行 → 投影领队联系人 → 完整写前投影 → 父页原生保存;ERP 明确返回成功即完成,不再逐行写后回查 | 用户模板 `.xls` 已只读通过 25 行规范化及单一领队行结构核验;解析阶段不以初始 16/31 行作统一静态上限,5000 为技术上限;执行时以精确 ERP 页面实际物化游客位为准。2026-08-28 一笔散拼子单 25 行及领队联系人已持久化全等;另一次独立团 25 人只物化 16 位并在保存前安全阻断 | 名单与领队联系人真实写入已有一笔证据;`0.5.155` 起确认覆盖同值也写,`0.5.156` 起容量不足使用人数/上限业务提示并仅在技术详情保留原始 blocker;其他对象/边界继续按发布门槛分级 | +| `passenger_list_import` | 独立团/散拼子单名单导入/补充 | [`lwlt-lifecycle`](skills/lwlt-lifecycle/SKILL.md) / `passenger_list_import`(Program-only) | [统一输入模板 §5—6](templates/business-input-templates.md);文字定位 + 单个 `.xls/.xlsx` 附件;程序在前 100 行自动定位唯一 ERP 字段表头,按精确语义从任意表头行和列顺序中只选择必需字段,并读取其下连续名单数据;身份证、年龄、源证件类型和其他普通额外列忽略且不输出 | 识别 route 后等待附件 → 程序规范化为加密 13 列 TSV,并从唯一严格`领队`备注行提取姓名/电话 → 读取 ERP 游客位 → 未确认的占用序号要求覆盖确认;确认后所有指定序号强制覆写(同值也写)→ `DaoRuDones` → 原生游客位不足时返回“名单 N 人超过 ERP 最多 M 人”且不保存 → 恢复未指定行 → 投影领队联系人 → 完整写前投影 → 父页原生保存;ERP 明确返回成功即完成,不再逐行写后回查 | 用户模板 `.xls` 已只读通过 25 行规范化及单一领队行结构核验;解析阶段不以初始 16/31 行作统一静态上限,5000 为技术上限;执行时以精确 ERP 页面实际物化游客位为准。2026-08-28 一笔散拼子单 25 行及领队联系人已持久化全等;另一次独立团 25 人只物化 16 位并在保存前安全阻断 | 名单与领队联系人真实写入已有一笔证据;`0.5.155` 起确认覆盖同值也写,`0.5.156` 起容量不足使用人数/上限业务提示并仅在技术详情保留原始 blocker;其他对象/边界继续按发布门槛分级 | | `order_update_shared_plan` / `order_update_shared_child` | 散拼母团容量/散拼子单住宿说明 | [`lwlt-updating`](skills/lwlt-updating/SKILL.md) | [统一输入模板:修改](templates/business-input-templates.md) | `DoInfoSP` / `DoInfo_order`;写前读取当前值 | 母团容量、子单住宿说明通过;用户只填目标值;无年份定位日期取任务日后的下一次发生日 | Skill 全量解析;仅已验证字段自动执行,其他字段保留并转人工复核 | | `order_update_independent` | 独立团信息修改 | [`lwlt-updating`](skills/lwlt-updating/SKILL.md) / `order_update_independent` | [统一输入模板 §14](templates/business-input-templates.md) | `DoInfoJH`;写前读取当前值;SGL/TWN 分别映射 `frenshu0`/`frenshu1`,成人/占床儿童/不占床儿童/领队分别映射 `darenshu`/`xiaorenshu`/`ertrenshu`/`quanrenshu` | 标间数 `8→9→8` 已有明确响应和 fresh GET;无年份定位/目标日期取下一次发生日;SGL/TWN 与四类人数待新版本授权 ERP 实写复测 | Skill 全量解析;当前插件执行标间数、SGL/TWN 与四类人数,其他目标保留并转人工复核 | | `arrangement_guide` / `arrangement_vehicle` / `arrangement_hotel` / `arrangement_transport` / `arrangement_other` | 安排导游/用车/酒店/大交通/其他备案 | [`lwlt-arrangement`](skills/lwlt-arrangement/SKILL.md) | [统一输入模板 §7—11](templates/business-input-templates.md) | 五类 action-specific `DoInfo_*`,全类 `create|clear`;酒店另有窄 `update` | 两类团队共 10 create + 10 clear;酒店资源/房型由唯一 ERP 模糊匹配结果带入;大交通和其他/备案的结算单位组合候选按下拉顺序首条原生联动,其他/备案数量可选,省略时保留 ERP 默认值;散拼母团无子单时 ERP 禁止安排酒店并隐藏提交入口,插件在三项页面证据同时成立时提示先新增子单 | 已真实验证;非酒店 update 和酒店其他字段阻断 | diff --git a/agent设计规范/skills/lwlt-lifecycle/SKILL.md b/agent设计规范/skills/lwlt-lifecycle/SKILL.md index 2f62625..25dd8b5 100644 --- a/agent设计规范/skills/lwlt-lifecycle/SKILL.md +++ b/agent设计规范/skills/lwlt-lifecycle/SKILL.md @@ -11,7 +11,7 @@ description: 定义 LTJT 的独立团或散拼子单名单导入、按序号补 1. 从首行和正文识别名单导入、取消或恢复。 2. 按 [input-contract.md](references/input-contract.md) 检查用户可见必填字段。 -3. 名单 route 先进入 `awaiting_attachment`;只接收一份 `.xls`/`.xlsx`。控制面程序在前 100 行中自动定位唯一的 ERP 名单字段表头,按精确字段语义映射任意列顺序及受支持别名,再把表头下方的连续名单数据生成内部 13 列 canonical TSV;附件到齐前不启动解析或执行。 +3. 名单 route 先进入 `awaiting_attachment`;只接收一份 `.xls`/`.xlsx`。控制面程序在前 100 行中自动定位唯一的 ERP 名单字段表头,按精确字段语义从任意列顺序中只选择 12 个必需源字段,再把表头下方的连续名单数据生成内部 13 列 canonical TSV;身份证、年龄、源证件类型和其他普通额外列一律忽略且不进入输出。附件到齐前不启动解析或执行。 4. Program Parser 校验内部 13 列表头、每行完整性、正整数唯一序号;允许用户只提供本次需要补录的一个或多个序号。备注去空格后严格等于`领队`的唯一行会生成结构化 `passenger_list.leader_contact`;姓名或电话为空、或出现多行领队时失败关闭。 5. 按 [operation-contract.md](references/operation-contract.md) 生成一个 operation。 6. 返回 `agent_parse_passed`、`agent_parse_needs_input` 或 `agent_parse_blocked`。 diff --git a/agent设计规范/skills/lwlt-lifecycle/references/input-contract.md b/agent设计规范/skills/lwlt-lifecycle/references/input-contract.md index 3e0e09f..8c6b0ca 100644 --- a/agent设计规范/skills/lwlt-lifecycle/references/input-contract.md +++ b/agent设计规范/skills/lwlt-lifecycle/references/input-contract.md @@ -60,11 +60,11 @@ 示例同时附一份符合 ERP 字段规则的名单工作簿。 -附件必须且只能有一个可见工作表。程序在前 100 行中自动定位唯一表头;表头可直接位于第一行,也可位于团信息或说明行之后,列顺序任意,表头下一行起的名单数据必须连续。表头必须各包含一次这些 ERP 语义字段:`序号/姓名/英文姓名(或 NAME)/性别/出生日期/出生地/护照号码(或证件号码)/签发地/签发日期(或签发日)/有效期/电话/备注`。可选兼容字段为`年龄`、`身份证号码(或身份证)`、`证件类型`;同一语义字段不得重复,未知表头或无表头数据列会失败关闭。当前只支持护照名单:身份证列如存在必须为空,证件类型如有值必须为`护照`;`年龄`不进入 ERP。程序把英文姓名映射为 `NAME`、护照号码映射为“证件号码”、固定输出“证件类型=护照”,并把签发日期映射为“签发日”。 +附件必须且只能有一个可见工作表。程序在前 100 行中自动定位唯一表头;表头可直接位于第一行,也可位于团信息或说明行之后,列顺序任意,表头下一行起的名单数据必须连续。表头必须各包含一次这些 ERP 语义字段:`序号/姓名/英文姓名(或 NAME)/性别/出生日期/出生地/护照号码(或证件号码)/签发地/签发日期(或签发日)/有效期/电话/备注`。程序只选择和校验这些必需字段;`年龄`、`身份证号码(或身份证)`、源`证件类型`以及其他普通额外列均被忽略,不参与名单行识别,也不会进入 ERP 或内部 canonical TSV。程序把英文姓名映射为 `NAME`、护照号码映射为“证件号码”、固定输出“证件类型=护照”,并把签发日期映射为“签发日”。 若某位游客是领队,该行`备注`去除首尾空格后必须严格等于`领队`,并同时填写该行`姓名`和`电话`。一份名单最多标记一位领队;标记成功后程序会把这两个值写入 ERP `联系人/领队人`和`电话`,该行仍作为普通游客导入。`领队备注`等其他文字不触发写入;没有严格标记时保留 ERP 原联系人信息。正文里的`领队`字段仍只是订单定位的选填补充关键词,不是联系人写入来源。 -只接收一个 `.xls`/`.xlsx` 文件;旧版 `.xls` 必须经隔离转换得到真实 XLSX,不能回退原始字节。多工作表、隐藏非空数据、宏、外部链接、非模板公式、额外列、断行、空名单、非正整数/重复序号均失败关闭。最多 5000 行且最高序号 5000。附件错误不得把单元格值、文件正文或明文文件名写入日志或 API 错误。 +只接收一个 `.xls`/`.xlsx` 文件;旧版 `.xls` 必须经隔离转换得到真实 XLSX,不能回退原始字节。多工作表、隐藏的必需名单字段数据、宏、外部链接、必需字段中的非模板公式、必需字段断行、空名单、非正整数/重复序号均失败关闭。普通额外列可以有值、公式或被隐藏,但外部公式、超链接及其他主动内容仍按全工作簿安全边界阻断。最多 5000 行且最高序号 5000。附件错误不得把单元格值、文件正文或明文文件名写入日志或 API 错误。 用户可提供一个或多个游客序号。规范化后的每个内部数据行必须完整包含 13 列;未提供的序号不代表删除或清空。ERP 独立团默认 16 行、散拼子单默认 31 行只是初始行基线,不是导入上限;执行端按现有行数与最高序号动态扩行。 diff --git a/agent设计规范/templates/business-input-templates.md b/agent设计规范/templates/business-input-templates.md index 50db1b1..253cdb6 100644 --- a/agent设计规范/templates/business-input-templates.md +++ b/agent设计规范/templates/business-input-templates.md @@ -1,6 +1,6 @@ # 运营业务 AI 输入模板总表 -交付版本:0.5.125 +交付版本:0.5.126 适用范围:当前 18 项运营业务输入;执行能力以生命周期发布门槛为准。 本文件用于向系统提交 ERP 业务需求。复制对应模板填写;示例只供参考,不要直接用于生产业务。 @@ -128,7 +128,7 @@ 名单只通过一份 `.xls` 或 `.xlsx` 附件提交,不再粘贴到文字输入框。系统识别名单业务后会等待附件,附件到齐后只走程序解析。 -系统会在工作表前 100 行中自动定位唯一的名单表头;表头可以直接在第一行,也可以位于团信息或说明行之后,字段顺序任意,表头下一行开始识别连续名单数据。表头必须各包含一次这些 ERP 语义字段:`序号/姓名/英文姓名(或 NAME)/性别/出生日期/出生地/护照号码(或证件号码)/签发地/签发日期(或签发日)/有效期/电话/备注`。可兼容附加列`年龄`、`身份证号码(或身份证)`、`证件类型`;同一语义字段不得重复,未知表头或无表头数据列会失败关闭。当前只支持护照名单:身份证列如存在必须留空,证件类型如有值必须为`护照`;`年龄`不进入 ERP。每次只保留一个可见工作表,最多 5000 行。 +系统会在工作表前 100 行中自动定位唯一的名单表头;表头可以直接在第一行,也可以位于团信息或说明行之后,字段顺序任意,表头下一行开始识别连续名单数据。表头必须各包含一次这些 ERP 语义字段:`序号/姓名/英文姓名(或 NAME)/性别/出生日期/出生地/护照号码(或证件号码)/签发地/签发日期(或签发日)/有效期/电话/备注`。系统只读取和校验这些必需字段;`年龄`、`身份证号码(或身份证)`、源`证件类型`以及其他普通额外列都会被忽略,不进入 ERP,也不会因为有值而导致整份名单失败。每次只保留一个可见工作表,最多 5000 行;宏、外部链接等主动内容仍会被安全阻断。 若游客是领队,在该行`备注`只填`领队`,并确保同一行`姓名`和`电话`已填写;一份名单最多一位。系统会把这两个值同步到 ERP `联系人/领队人`和`电话`。其他备注不触发联系人写入;没有领队标记时保留 ERP 原信息。正文里的`领队`仍只是选填的查单关键词。 diff --git a/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md b/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md index 8d917c0..f1def31 100644 --- a/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md +++ b/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md @@ -5,12 +5,13 @@ - Chrome 插件:`0.5.168` - Agent Prompt:`ltjt-agent-prompt-v1.8-independent-headcount-categories` - 生命周期契约:`ltjt-lifecycle-v2.9-roster-leader-contact-2026-08` -- 五个 Skill:`0.5.125`;运营 DOCX:`0.5.125` +- 五个 Skill:`0.5.126`;运营 DOCX:`0.5.126` - 发布状态:核心窄能力已有真实证据;一笔名单附件 Program-only 任务已把 25 行及唯一领队联系人正确保存到 ERP,后续只读逐字段核验全等。两种写后逐行读取方式都曾在真实成功写入后产生假阴性;名单在全部写前门禁通过后,以父表单 ERP 明确成功响应作为终结证据,不再执行写后逐行回查或自动名单对账。`0.5.155` 规定确认覆盖的指定序号同值也写;`0.5.156` 把原生游客位不足统一映射为包含名单人数和 ERP 实际上限的业务提示,原技术 blocker 仅留技术详情。`0.5.157` 新增散拼母团“整团游客信息”的 `shared_plan + visitor-list + tid-only` 窄分支,静态契约与回归已通过,仍待授权后的新版运行态只读复测。`0.5.158` 把正式控制面 `https://lwlt.nianxx.cn` 同步加入 Popup、后台、Manifest 权限和 content script 精确白名单;其他 HTTPS origin 仍保持阻断。`0.5.159` 修复 MV3 后台恢复被中断后持久化 `running` 永久阻断插件派发的问题:仅在内存恢复 Promise 已不存在时收敛为 `interrupted`,真实活动恢复仍保持阻断,下一次成功只读保活把状态归零为 `idle`。`0.5.160` 为独立团单个下单写前漂移增加仅字段名诊断,并把数字后缀动态应收字段纳入保护。`0.5.161` 等待客户、跟单人和销售人原生 lookup 数据就绪后才开始单团预检;生产反馈证明仅检查 lookup 数据仍不足。`0.5.162` 进一步要求页面 jQuery Ajax 在预检前归零,并在 `GetProduct` 后等待原生 `ajaxStop` 再应用最终业务字段。`0.5.163` 按生产操作方明确要求恢复 `0.5.156` 的实际校验范围:核心订单字段继续写前阻断,带编号的动态 `ys_*` 应收行不再纳入保护哈希,允许 ERP 原生回调在预检后调整这些字段。`0.5.164` 新增平台账号、AgentBus 渠道、唯一云电脑 worker 与 ERP 登录身份四方一致门禁;旧未绑定渠道和历史未归属 AgentBus 任务默认不执行。`0.5.165` 让散拼新增计划和独立团批量下单在本地候选未命中时先使用表单原生非空产品检索,实测两类表单均能由完整关键词唯一返回目标产品;本轮未勾选或保存,自动选择与写入仍由原门禁保护。新增独立团房型/人数映射仍待实写;未验证宽能力继续失败关闭。 - `0.5.166` 在散拼母团列表入口加入立即检查、100ms 条件轮询和 15 秒总超时;每次探测都重新取得当前主 iframe document,按钮一旦可用便立即继续。该等待只发生在点击前,不固定拖慢快速页面,也不触发业务写入重试。 - `0.5.167` 保留插件侧的空闲证明和受控重载安全协议,并继续包含 `0.5.166` 的散拼入口自适应等待。中央服务私有 OSS/ECS 云助手编排及迁移 019 已按用户决定从主线撤回,因此这些插件更新消息在当前主线没有服务端触发方,属于休眠能力;插件仍按现有人工发布/加载流程使用。 - `0.5.168` 将 ERP 身份门禁从整页子串搜索收紧为唯一 `#Lable_UserName` 登录节点的规范化精确全等;缺失、重复、空白、子串或其他页面文字命中均不再让云电脑进入可执行状态,观察到的账号文本不离开扩展。 +- 名单工作簿 normalizer `v1.4.0` 与业务输入/Skill `0.5.126` 改为只选择 12 个必需源字段;身份证、年龄、源证件类型及其他普通额外列不参与行识别、字段校验或 canonical 输出。必需字段完整性、宏/外链、全局超链接和外部公式等安全门槛保持失败关闭。 运行中的浏览器版本必须实时握手确认。当前制品文件名和 SHA-256 只看 [`../../../dist/release-manifest.json`](../../../dist/release-manifest.json),不从历史日志推断。 @@ -19,7 +20,7 @@ | 能力 | 当前边界 | |---|---| | 创建 | 独立团单个/批量、散拼母团单个/批量及具体子单;批量逐日期保留响应和回查 | -| 名单 | 独立团和具体散拼子单;文字定位后等待单个 `.xls`/`.xlsx`,程序在前 100 行自动识别唯一 ERP 字段表头及其下连续名单,规范化为 13 列 TSV 并固定 Program-only;支持明确序号的动态扩行、安全合并、同值幂等和覆盖确认。解析阶段不以初始 16/31 行作统一上限;执行时若原生页实际物化游客位不足,则在保存前以人数/上限业务提示阻断。2026-08-28 已只读确认一笔具体散拼子单的 25 行及领队联系人真实持久化全等;另一次独立团 25 人只物化 16 位且未保存 | +| 名单 | 独立团和具体散拼子单;文字定位后等待单个 `.xls`/`.xlsx`,程序在前 100 行自动识别唯一 ERP 字段表头,只选择必需字段并读取其下连续名单,普通额外列忽略且不输出,随后规范化为 13 列 TSV 并固定 Program-only;支持明确序号的动态扩行、安全合并、同值幂等和覆盖确认。解析阶段不以初始 16/31 行作统一上限;执行时若原生页实际物化游客位不足,则在保存前以人数/上限业务提示阻断。2026-08-28 已只读确认一笔具体散拼子单的 25 行及领队联系人真实持久化全等;另一次独立团 25 人只物化 16 位且未保存 | | 安排 | 导游、车辆、酒店、大交通、其他/备案 create/clear;酒店新增状态为未安排 | | 独立团修改 | `twin_room_count` 已真实持久化;SGL/TWN 与成人/占床儿童/不占床儿童/领队人数已接入映射/回查但待新版本实写 | | 散拼修改 | 母团 `planned_capacity`、具体子单最终长度 ≤50 的 `lodging_note` | diff --git a/archive/releases/2026-09-07/README.md b/archive/releases/2026-09-07/README.md index 3886db4..4584236 100644 --- a/archive/releases/2026-09-07/README.md +++ b/archive/releases/2026-09-07/README.md @@ -4,3 +4,6 @@ - `release-manifest-0.5.167.json`:收紧 ERP 登录账号身份识别前的历史清单快照。 - `ltjt-order-assistant-0.5.167.zip`:被 `0.5.168` 的唯一登录节点精确账号匹配替代;保留用于恢复和追溯。 +- `release-manifest-0.5.168-skills-0.5.125.json`:名单额外字段白名单提取前的 `0.5.168 / 0.5.125 / 0.5.125` 历史清单快照。 +- `lwlt-*-0.5.125.skill`:被 `0.5.126` Skill 发布基线替代;其中 lifecycle 包仍记录旧的额外列失败关闭规则。 +- `老挝联泰AI指令表-0.5.125.docx`:被 `0.5.126` 的名单必需字段白名单说明替代。 diff --git a/dist/lwlt-arrangement-0.5.125.skill b/archive/releases/2026-09-07/lwlt-arrangement-0.5.125.skill similarity index 100% rename from dist/lwlt-arrangement-0.5.125.skill rename to archive/releases/2026-09-07/lwlt-arrangement-0.5.125.skill diff --git a/dist/lwlt-confirmation-0.5.125.skill b/archive/releases/2026-09-07/lwlt-confirmation-0.5.125.skill similarity index 100% rename from dist/lwlt-confirmation-0.5.125.skill rename to archive/releases/2026-09-07/lwlt-confirmation-0.5.125.skill diff --git a/dist/lwlt-lifecycle-0.5.125.skill b/archive/releases/2026-09-07/lwlt-lifecycle-0.5.125.skill similarity index 100% rename from dist/lwlt-lifecycle-0.5.125.skill rename to archive/releases/2026-09-07/lwlt-lifecycle-0.5.125.skill diff --git a/dist/lwlt-newbooking-0.5.125.skill b/archive/releases/2026-09-07/lwlt-newbooking-0.5.125.skill similarity index 100% rename from dist/lwlt-newbooking-0.5.125.skill rename to archive/releases/2026-09-07/lwlt-newbooking-0.5.125.skill diff --git a/dist/lwlt-updating-0.5.125.skill b/archive/releases/2026-09-07/lwlt-updating-0.5.125.skill similarity index 100% rename from dist/lwlt-updating-0.5.125.skill rename to archive/releases/2026-09-07/lwlt-updating-0.5.125.skill diff --git a/archive/releases/2026-09-07/release-manifest-0.5.168-skills-0.5.125.json b/archive/releases/2026-09-07/release-manifest-0.5.168-skills-0.5.125.json new file mode 100644 index 0000000..a5ff104 --- /dev/null +++ b/archive/releases/2026-09-07/release-manifest-0.5.168-skills-0.5.125.json @@ -0,0 +1,69 @@ +{ + "manifest_version": 1, + "generated_on": "2026-09-07", + "baselines": { + "chrome_extension": "0.5.168", + "skills": "0.5.125", + "business_instruction_docx": "0.5.125", + "agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories" + }, + "artifacts": [ + { + "kind": "chrome_extension", + "version": "0.5.168", + "path": "dist/ltjt-order-assistant-0.5.168.zip", + "source": "chrome-extension/ltjt-order-assistant", + "sha256": "2432a882f2fbdcf28bfb6bb8a7fead55986297f86d4cf4ab8e12ea373c72518c" + }, + { + "kind": "skill", + "name": "lwlt-arrangement", + "version": "0.5.125", + "path": "dist/lwlt-arrangement-0.5.125.skill", + "source": "agent设计规范/skills/lwlt-arrangement", + "sha256": "c729bebec84695b8642a6d31cb4dc0d963135b6c6620c2173be095826cd3976d" + }, + { + "kind": "skill", + "name": "lwlt-confirmation", + "version": "0.5.125", + "path": "dist/lwlt-confirmation-0.5.125.skill", + "source": "agent设计规范/skills/lwlt-confirmation", + "sha256": "659f6aca7918a03a29fc78f0b4b938dfa9084ccdbb44ebd71f6399556cfa4609" + }, + { + "kind": "skill", + "name": "lwlt-lifecycle", + "version": "0.5.125", + "path": "dist/lwlt-lifecycle-0.5.125.skill", + "source": "agent设计规范/skills/lwlt-lifecycle", + "sha256": "d964c0a9e482f82757fea25d4713f0c81feb2704486583dfa84f2336ecd930c0" + }, + { + "kind": "skill", + "name": "lwlt-newbooking", + "version": "0.5.125", + "path": "dist/lwlt-newbooking-0.5.125.skill", + "source": "agent设计规范/skills/lwlt-newbooking", + "sha256": "bb610c50dd659fc172aabfa469b3028de98704c249cfd4890463deea0c88cd0c" + }, + { + "kind": "skill", + "name": "lwlt-updating", + "version": "0.5.125", + "path": "dist/lwlt-updating-0.5.125.skill", + "source": "agent设计规范/skills/lwlt-updating", + "sha256": "c6f5bc9008e2f63d170f3f140140003d99411ce5232632c48b38891da1ceee33" + }, + { + "kind": "business_instruction_docx", + "version": "0.5.125", + "path": "dist/老挝联泰AI指令表-0.5.125.docx", + "source": "agent设计规范/templates/business-input-templates.md", + "builder": "tools/build_business_instruction_docx.py", + "sha256": "a0c2fd5f808e3d3f2ac211a652a59f2e435d3824df49c51f763ca4beb7d9b97d", + "source_sha256": "27600fe9bcb0de3898a99550428a9890dd350c701ba704fb3940ca966088dcd9", + "builder_sha256": "c162884210da22e3b78368749b66f1f177df5e9fc6155f4fd9954bd516d187f1" + } + ] +} diff --git a/dist/老挝联泰AI指令表-0.5.125.docx b/archive/releases/2026-09-07/老挝联泰AI指令表-0.5.125.docx similarity index 100% rename from dist/老挝联泰AI指令表-0.5.125.docx rename to archive/releases/2026-09-07/老挝联泰AI指令表-0.5.125.docx diff --git a/control-plane/README.md b/control-plane/README.md index 0614b1e..bf03240 100644 --- a/control-plane/README.md +++ b/control-plane/README.md @@ -10,7 +10,7 @@ - 业务页面通过 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 为技术上限。 +- 名单 route 创建后先进入 `awaiting_attachment`,只接收一份 `.xls/.xlsx`;控制面在前 100 行中自动定位唯一的 ERP 名单字段表头,按精确字段语义从任意表头行和列顺序中只选择 12 个必需源字段,并把表头下方连续名单数据在内存中规范化为 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` 只是临时缓存。 diff --git a/control-plane/src/business-routes.ts b/control-plane/src/business-routes.ts index f11ec09..2d35e60 100644 --- a/control-plane/src/business-routes.ts +++ b/control-plane/src/business-routes.ts @@ -75,7 +75,7 @@ export function effectiveParserModeForRoute(route: BusinessRouteDefinition, conf } const PROGRAM_VERSION = 'ltjt-program-parser-v1.0.6'; -export const BUSINESS_INPUT_CONTRACT_VERSION = 'business-input-templates-0.5.125'; +export const BUSINESS_INPUT_CONTRACT_VERSION = 'business-input-templates-0.5.126'; const lookupFields = ({ customerRequired = true } = {}): ProgramFieldDefinition[] => [ { key: 'order_no', label: '单号', aliases: ['团号', '订单号'], kind: 'text' }, diff --git a/control-plane/src/passenger-roster-workbook.ts b/control-plane/src/passenger-roster-workbook.ts index b050f7a..6e13b73 100644 --- a/control-plane/src/passenger-roster-workbook.ts +++ b/control-plane/src/passenger-roster-workbook.ts @@ -13,39 +13,29 @@ const ABSOLUTE_MAX_DATA_ROWS = 5_000; const MAX_PASSENGER_SEQUENCE = 5_000; const MAX_HEADER_ROW = 100; -export const PASSENGER_ROSTER_WORKBOOK_VERSION = 'ltjt-passenger-roster-workbook-v1.3.0'; - -const SOURCE_FIELDS = [ - '序号', '姓名', '英文姓名', '性别', '身份证号码', '出生日期', '年龄', '出生地', - '证件类型', '护照号码', '签发地', '签发日期', '有效期', '电话', '备注' -] as const; +export const PASSENGER_ROSTER_WORKBOOK_VERSION = 'ltjt-passenger-roster-workbook-v1.4.0'; const REQUIRED_SOURCE_FIELDS = [ '序号', '姓名', '英文姓名', '性别', '出生日期', '出生地', '护照号码', '签发地', '签发日期', '有效期', '电话', '备注' ] as const; -type SourceField = typeof SOURCE_FIELDS[number]; type RequiredSourceField = typeof REQUIRED_SOURCE_FIELDS[number]; -type SourceColumnMap = Partial> & Record; +type SourceColumnMap = Record; /** * Header recognition is intentionally exact and finite. It accepts the - * customer-facing template labels, the canonical ERP labels, and aliases - * observed in approved roster samples; it does not use fuzzy matching. + * required customer-facing labels, canonical ERP labels, and approved aliases. + * Non-import columns are deliberately absent from this map and are ignored. */ -const HEADER_FIELD_ENTRIES: ReadonlyArray = [ +const HEADER_FIELD_ENTRIES: ReadonlyArray = [ ['序号', '序号'], ['姓名', '姓名'], ['英文姓名', '英文姓名'], ['NAME', '英文姓名'], ['性别', '性别'], - ['身份证号码', '身份证号码'], - ['身份证', '身份证号码'], ['出生日期', '出生日期'], - ['年龄', '年龄'], ['出生地', '出生地'], - ['证件类型', '证件类型'], ['护照号码', '护照号码'], ['证件号码', '护照号码'], ['签发地', '签发地'], @@ -56,7 +46,7 @@ const HEADER_FIELD_ENTRIES: ReadonlyArray = [ ['备注', '备注'] ]; -const SOURCE_FIELD_BY_HEADER = new Map(HEADER_FIELD_ENTRIES); +const SOURCE_FIELD_BY_HEADER = new Map(HEADER_FIELD_ENTRIES); export const CANONICAL_PASSENGER_HEADERS = [ '序号', '姓名', 'NAME', '性别', '出生日期', '出生地', '证件类型', '证件号码', @@ -79,15 +69,12 @@ export type PassengerRosterWorkbookErrorCode = | 'roster_workbook_ambiguous_header' | 'roster_workbook_formula_not_allowed' | 'roster_workbook_formula_result_missing' - | 'roster_workbook_unexpected_column' | 'roster_workbook_non_contiguous_rows' | 'roster_workbook_row_limit' | 'roster_workbook_no_data' | 'roster_workbook_invalid_sequence' | 'roster_workbook_sequence_limit' | 'roster_workbook_duplicate_sequence' - | 'roster_workbook_identity_card_not_supported' - | 'roster_workbook_document_type_not_supported' | 'roster_workbook_invalid_date' | 'roster_workbook_invalid_identifier' | 'roster_workbook_invalid_cell'; @@ -103,20 +90,17 @@ const ERROR_MESSAGES: Record = { roster_workbook_archive_limit: '名单工作簿解压规模超过限制', roster_workbook_unsafe_content: '名单工作簿包含不允许的宏、外部链接或嵌入内容', roster_workbook_ambiguous_sheet: '名单工作簿必须且只能包含一个工作表', - roster_workbook_hidden_data: '名单工作簿包含隐藏的非空数据', + roster_workbook_hidden_data: '名单工作簿包含隐藏的必需名单字段数据', roster_workbook_header_not_found: '未找到可映射到 ERP 名单字段的完整表头', roster_workbook_ambiguous_header: '名单工作簿包含多个可识别的名单表头', - roster_workbook_formula_not_allowed: '名单工作簿包含不允许的公式', - roster_workbook_formula_result_missing: '名单工作簿公式没有可验证的缓存结果', - roster_workbook_unexpected_column: '名单表头范围外包含未识别的数据列', + roster_workbook_formula_not_allowed: '名单必需字段包含不允许的公式', + roster_workbook_formula_result_missing: '名单必需字段公式没有可验证的缓存结果', roster_workbook_non_contiguous_rows: '名单数据行不连续', roster_workbook_row_limit: '名单数据行数超过技术限制', roster_workbook_no_data: '名单工作簿没有数据行', roster_workbook_invalid_sequence: '名单序号必须是正整数', roster_workbook_sequence_limit: '名单序号超过技术安全上限', roster_workbook_duplicate_sequence: '名单序号不能重复', - roster_workbook_identity_card_not_supported: '当前名单导入不支持非空身份证号码', - roster_workbook_document_type_not_supported: '当前名单导入仅支持护照证件类型', roster_workbook_invalid_date: '名单日期无效', roster_workbook_invalid_identifier: '名单证件号或电话无法安全转换为文本', roster_workbook_invalid_cell: '名单单元格值无效' @@ -343,10 +327,6 @@ function formulaAllowed( const column = cellColumn(cell); if (row <= headerRow) return false; const formula = normalizedFormula(cell.formula); - const ageColumn = sourceColumns['年龄']; - if (ageColumn !== undefined && column === ageColumn) { - return formula === `DATEDIF(${excelColumnName(sourceColumns['出生日期'])}${row},TODAY(),"Y")`; - } if (column === sourceColumns['签发日期']) { return formula === `EDATE(${excelColumnName(sourceColumns['有效期'])}${row},-10*12)+1`; } @@ -415,11 +395,9 @@ function validateFormulasAndHiddenData( headerRow: number, sourceColumns: SourceColumnMap ): void { - const recognizedColumns = new Set( - Object.values(sourceColumns).filter((column): column is number => column !== undefined) - ); + const selectedColumns = new Set(Object.values(sourceColumns)); sheet.eachRow({ includeEmpty: false }, (row, rowNumber) => { - let hasData = false; + let hasSelectedData = false; row.eachCell({ includeEmpty: false }, (cell, columnNumber) => { if (cell.type === ExcelJS.ValueType.Merge && cell.master.address !== cell.address) return; const formula = formulaText(cell); @@ -427,23 +405,21 @@ function validateFormulasAndHiddenData( if (containsExternalFormulaReference(formula)) { fail('roster_workbook_unsafe_content', { row: rowNumber, column: columnNumber }); } - if (!formulaAllowed(cell, headerRow, sourceColumns)) { + if (selectedColumns.has(columnNumber) && !formulaAllowed(cell, headerRow, sourceColumns)) { fail('roster_workbook_formula_not_allowed', { row: rowNumber, column: columnNumber }); } } if (cell.type === ExcelJS.ValueType.Hyperlink || cell.isHyperlink) { fail('roster_workbook_unsafe_content', { row: rowNumber, column: columnNumber }); } - const containsData = cellHasData(cell); - if (containsData) hasData = true; - if (containsData && rowNumber >= headerRow && !recognizedColumns.has(columnNumber)) { - fail('roster_workbook_unexpected_column', { row: rowNumber, column: columnNumber }); + if (selectedColumns.has(columnNumber) && cellHasData(cell)) { + hasSelectedData = true; } }); - if (row.hidden && hasData) fail('roster_workbook_hidden_data', { row: rowNumber }); + if (row.hidden && hasSelectedData) fail('roster_workbook_hidden_data', { row: rowNumber }); }); - for (let columnNumber = 1; columnNumber <= sheet.columnCount; columnNumber += 1) { + for (const columnNumber of selectedColumns) { const column = sheet.getColumn(columnNumber); if (!column.hidden) continue; let hasData = false; @@ -455,23 +431,23 @@ function validateFormulasAndHiddenData( } function headerColumnsForRow(sheet: ExcelJS.Worksheet, rowNumber: number): SourceColumnMap | null { - const columns = new Map(); + const columns = new Map(); const row = sheet.getRow(rowNumber); for (let columnNumber = 1; columnNumber <= sheet.columnCount; columnNumber += 1) { const cell = row.getCell(columnNumber); - if (cell.type === ExcelJS.ValueType.Merge && cell.master.address !== cell.address) return null; + if (cell.type === ExcelJS.ValueType.Merge && cell.master.address !== cell.address) continue; const value = headerText(cell); if (value === '') continue; const field = SOURCE_FIELD_BY_HEADER.get(value); - if (!field || columns.has(field)) return null; + if (!field) continue; + if (columns.has(field)) return null; columns.set(field, columnNumber); } if (!REQUIRED_SOURCE_FIELDS.every((field) => columns.has(field))) return null; - const result: Partial> = {}; - for (const field of SOURCE_FIELDS) { - const column = columns.get(field); - if (column !== undefined) result[field] = column; + const result: Partial = {}; + for (const field of REQUIRED_SOURCE_FIELDS) { + result[field] = columns.get(field); } return result as SourceColumnMap; } @@ -575,11 +551,10 @@ function dataRows( ): ExcelJS.Row[] { const rows: ExcelJS.Row[] = []; let gapAfterData = false; - const recognizedColumns = Object.values(sourceColumns) - .filter((column): column is number => column !== undefined); + const selectedColumns = Object.values(sourceColumns); for (let rowNumber = headerRow + 1; rowNumber <= sheet.rowCount; rowNumber += 1) { const row = sheet.getRow(rowNumber); - const hasData = rowHasData(row, recognizedColumns); + const hasData = rowHasData(row, selectedColumns); if (!hasData) { if (rows.length > 0) gapAfterData = true; continue; @@ -608,28 +583,6 @@ function canonicalRow( } seenSequences.add(sequence); - const identityCardColumn = sourceColumns['身份证号码']; - if ( - identityCardColumn !== undefined - && canonicalIdentifier(row.getCell(identityCardColumn)) !== '' - ) { - fail('roster_workbook_identity_card_not_supported', { - row: row.number, - column: identityCardColumn - }); - } - - const documentTypeColumn = sourceColumns['证件类型']; - if (documentTypeColumn !== undefined) { - const documentType = canonicalTextCell(row.getCell(documentTypeColumn)); - if (documentType !== '' && documentType !== '护照') { - fail('roster_workbook_document_type_not_supported', { - row: row.number, - column: documentTypeColumn - }); - } - } - const issueDateCell = cell('签发日期'); const expiryCell = cell('有效期'); const englishName = canonicalTextCell(cell('英文姓名')).replace(/,/g, ','); diff --git a/control-plane/test/passenger-roster-workbook.test.ts b/control-plane/test/passenger-roster-workbook.test.ts index 1399831..339d946 100644 --- a/control-plane/test/passenger-roster-workbook.test.ts +++ b/control-plane/test/passenger-roster-workbook.test.ts @@ -263,14 +263,20 @@ test('source header order may vary, but every required label must appear exactly 'roster_workbook_header_not_found' ); - const unheadedExtraData = await syntheticWorkbook((workbook) => { - workbook.getWorksheet('Sheet1')!.getCell('O3').value = '未识别数据'; + const ignoredExtraData = await syntheticWorkbook((workbook) => { + const sheet = workbook.getWorksheet('Sheet1')!; + sheet.getCell('O2').value = '酒店偏好'; + sheet.getCell('O3').value = 'SYNTHETIC-EXTRA-SHOULD-NOT-LEAK'; + sheet.getCell('P3').value = { formula: '1+1' }; + sheet.getCell('Q6').value = '仅额外列尾注'; }); - await expectRosterError( - () => normalizePassengerRosterWorkbook({ content: unheadedExtraData, fileName: 'synthetic.xlsx', contentType: 'application/octet-stream' }), - 'roster_workbook_unexpected_column', - { row: 3, column: 15 } - ); + const ignoredExtraResult = await normalizePassengerRosterWorkbook({ + content: ignoredExtraData, + fileName: 'synthetic.xlsx', + contentType: 'application/octet-stream' + }); + assert.equal(ignoredExtraResult.rowCount, 2); + assert.doesNotMatch(ignoredExtraResult.canonicalTsv, /SYNTHETIC-EXTRA-SHOULD-NOT-LEAK|酒店偏好|仅额外列尾注/); }); test('legacy XLS is accepted only through an injected fail-closed converter', async () => { @@ -305,33 +311,34 @@ test('legacy XLS is accepted only through an injected fail-closed converter', as ); }); -test('non-empty identity cards fail closed without leaking cell values', async () => { +test('identity cards and other non-import columns are ignored without leaking values', async () => { const secret = 'SYNTHETIC-ID-SHOULD-NOT-LEAK'; const content = await syntheticWorkbook((workbook) => { - workbook.getWorksheet('Sheet1')!.getCell('E3').value = secret; + const sheet = workbook.getWorksheet('Sheet1')!; + sheet.getCell('E3').value = secret; + sheet.getColumn(5).hidden = true; }); - const error = await expectRosterError( - () => normalizePassengerRosterWorkbook({ content, fileName: 'synthetic.xlsx', contentType: 'application/octet-stream' }), - 'roster_workbook_identity_card_not_supported', - { row: 3, column: 5 } - ); - assert.doesNotMatch(error.message, new RegExp(secret)); - assert.doesNotMatch(JSON.stringify(error), new RegExp(secret)); + const result = await normalizePassengerRosterWorkbook({ + content, + fileName: 'synthetic.xlsx', + contentType: 'application/octet-stream' + }); + assert.equal(result.rowCount, 2); + assert.doesNotMatch(result.canonicalTsv, new RegExp(secret)); }); -test('canonical ERP document type remains passport-only', async () => { - const unsupportedType = await erpHeaderWorkbook((workbook) => { +test('source document type is ignored and canonical ERP document type remains passport', async () => { + const sourceType = await erpHeaderWorkbook((workbook) => { workbook.getWorksheet('ERP名单')!.getCell('G2').value = '身份证'; }); - await expectRosterError( - () => normalizePassengerRosterWorkbook({ - content: unsupportedType, - fileName: 'erp-roster.xlsx', - contentType: 'application/octet-stream' - }), - 'roster_workbook_document_type_not_supported', - { row: 2, column: 7 } - ); + const result = await normalizePassengerRosterWorkbook({ + content: sourceType, + fileName: 'erp-roster.xlsx', + contentType: 'application/octet-stream' + }); + assert.equal(result.rowCount, 1); + assert.equal(result.canonicalTsv.split('\n')[1]!.split('\t')[6], '护照'); + assert.doesNotMatch(result.canonicalTsv, /身份证/); }); test('sequence numbers must be positive and unique', async () => { @@ -363,7 +370,7 @@ test('sequence numbers must be positive and unique', async () => { ); }); -test('formulas are limited to the row-local age, issue-date and expiry template formulas', async () => { +test('unsafe formulas stay blocked while ignored columns do not affect selected fields', async () => { const networkFormula = await syntheticWorkbook((workbook) => { workbook.getWorksheet('Sheet1')!.getCell('G3').value = { formula: 'WEBSERVICE("https://invalid.example")', diff --git a/dist/lwlt-arrangement-0.5.126.skill b/dist/lwlt-arrangement-0.5.126.skill new file mode 100644 index 0000000..4082709 Binary files /dev/null and b/dist/lwlt-arrangement-0.5.126.skill differ diff --git a/dist/lwlt-confirmation-0.5.126.skill b/dist/lwlt-confirmation-0.5.126.skill new file mode 100644 index 0000000..59922d5 Binary files /dev/null and b/dist/lwlt-confirmation-0.5.126.skill differ diff --git a/dist/lwlt-lifecycle-0.5.126.skill b/dist/lwlt-lifecycle-0.5.126.skill new file mode 100644 index 0000000..42f2f32 Binary files /dev/null and b/dist/lwlt-lifecycle-0.5.126.skill differ diff --git a/dist/lwlt-newbooking-0.5.126.skill b/dist/lwlt-newbooking-0.5.126.skill new file mode 100644 index 0000000..bf6143f Binary files /dev/null and b/dist/lwlt-newbooking-0.5.126.skill differ diff --git a/dist/lwlt-updating-0.5.126.skill b/dist/lwlt-updating-0.5.126.skill new file mode 100644 index 0000000..3bc0887 Binary files /dev/null and b/dist/lwlt-updating-0.5.126.skill differ diff --git a/dist/release-manifest.json b/dist/release-manifest.json index a5ff104..0b3f0c2 100644 --- a/dist/release-manifest.json +++ b/dist/release-manifest.json @@ -3,8 +3,8 @@ "generated_on": "2026-09-07", "baselines": { "chrome_extension": "0.5.168", - "skills": "0.5.125", - "business_instruction_docx": "0.5.125", + "skills": "0.5.126", + "business_instruction_docx": "0.5.126", "agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories" }, "artifacts": [ @@ -18,51 +18,51 @@ { "kind": "skill", "name": "lwlt-arrangement", - "version": "0.5.125", - "path": "dist/lwlt-arrangement-0.5.125.skill", + "version": "0.5.126", + "path": "dist/lwlt-arrangement-0.5.126.skill", "source": "agent设计规范/skills/lwlt-arrangement", - "sha256": "c729bebec84695b8642a6d31cb4dc0d963135b6c6620c2173be095826cd3976d" + "sha256": "83770befe3a8e4048eacaeacb567c5139bf8a0b3a49f2b1513c26383422f2a9c" }, { "kind": "skill", "name": "lwlt-confirmation", - "version": "0.5.125", - "path": "dist/lwlt-confirmation-0.5.125.skill", + "version": "0.5.126", + "path": "dist/lwlt-confirmation-0.5.126.skill", "source": "agent设计规范/skills/lwlt-confirmation", - "sha256": "659f6aca7918a03a29fc78f0b4b938dfa9084ccdbb44ebd71f6399556cfa4609" + "sha256": "543507ed8b68c183d2df6722061b0e927ebb728de4bf254d15f83311c9264018" }, { "kind": "skill", "name": "lwlt-lifecycle", - "version": "0.5.125", - "path": "dist/lwlt-lifecycle-0.5.125.skill", + "version": "0.5.126", + "path": "dist/lwlt-lifecycle-0.5.126.skill", "source": "agent设计规范/skills/lwlt-lifecycle", - "sha256": "d964c0a9e482f82757fea25d4713f0c81feb2704486583dfa84f2336ecd930c0" + "sha256": "fff3879d9a4143c34ae51e091b91411e55382ad3f5d77c99d6202a005b1ddbd9" }, { "kind": "skill", "name": "lwlt-newbooking", - "version": "0.5.125", - "path": "dist/lwlt-newbooking-0.5.125.skill", + "version": "0.5.126", + "path": "dist/lwlt-newbooking-0.5.126.skill", "source": "agent设计规范/skills/lwlt-newbooking", - "sha256": "bb610c50dd659fc172aabfa469b3028de98704c249cfd4890463deea0c88cd0c" + "sha256": "5eb6a7bf5649a0fcee3fbee488a124e1b1d018a642a3a49ebb5439ccf20ccc2f" }, { "kind": "skill", "name": "lwlt-updating", - "version": "0.5.125", - "path": "dist/lwlt-updating-0.5.125.skill", + "version": "0.5.126", + "path": "dist/lwlt-updating-0.5.126.skill", "source": "agent设计规范/skills/lwlt-updating", - "sha256": "c6f5bc9008e2f63d170f3f140140003d99411ce5232632c48b38891da1ceee33" + "sha256": "3bd665207649cbb8e0e5ebb012c916720201fe160eea62bccdf1c3a1f0275d1d" }, { "kind": "business_instruction_docx", - "version": "0.5.125", - "path": "dist/老挝联泰AI指令表-0.5.125.docx", + "version": "0.5.126", + "path": "dist/老挝联泰AI指令表-0.5.126.docx", "source": "agent设计规范/templates/business-input-templates.md", "builder": "tools/build_business_instruction_docx.py", - "sha256": "a0c2fd5f808e3d3f2ac211a652a59f2e435d3824df49c51f763ca4beb7d9b97d", - "source_sha256": "27600fe9bcb0de3898a99550428a9890dd350c701ba704fb3940ca966088dcd9", + "sha256": "052611d9b700afe66b02a9b3e3b456fc245ac46541d338980f2fa4ff2e41de14", + "source_sha256": "3ea8efd5174c0d4ab59972fb30fac2f8c6dfe7308f4e3fdb6e5b5e7c9c76258b", "builder_sha256": "c162884210da22e3b78368749b66f1f177df5e9fc6155f4fd9954bd516d187f1" } ] diff --git a/dist/老挝联泰AI指令表-0.5.126.docx b/dist/老挝联泰AI指令表-0.5.126.docx new file mode 100644 index 0000000..a3da0b1 Binary files /dev/null and b/dist/老挝联泰AI指令表-0.5.126.docx differ diff --git a/mappings/lifecycle.mapping.json b/mappings/lifecycle.mapping.json index 7eb3450..eb4cd25 100644 --- a/mappings/lifecycle.mapping.json +++ b/mappings/lifecycle.mapping.json @@ -205,8 +205,8 @@ "first_import may fill blank rows or accept idempotent rows but cannot change occupied rows", "full_replace requires confirmed=true and forces a native overwrite of every supplied sequence even when its current persisted projection is equal; the plugin must not downgrade confirmed user intent to no_changes", "passenger_list.marker is exactly TEST-202609 only for an explicit historical test context", - "every source row has 证件类型 exactly 护照", - "exactly one workbook row within the first 100 rows maps to the required ERP passenger fields; the header may be row 1 or follow metadata rows, source order is arbitrary, approved source/ERP aliases map to one semantic field each, and unknown or unheaded data columns are rejected; contiguous roster data is read below the detected header", + "every canonical row has implicit 证件类型 exactly 护照; any source 证件类型 column is ignored", + "exactly one workbook row within the first 100 rows maps to the required ERP passenger fields; the header may be row 1 or follow metadata rows, source order is arbitrary, approved source/ERP aliases map to one semantic field each, and ordinary non-import columns are ignored and excluded from canonical output; contiguous roster data is read below the detected header", "at most one row has trimmed 备注 exactly 领队; that row requires nonblank 姓名 and 电话 and must match passenger_list.leader_contact" ], "native_defects": [ @@ -228,7 +228,7 @@ "correct traveler phones within tr[id^=youke]", "restore jj_lianxiren/jj_dianhua and sj_lianxiren/sj_dianhua after native parsing; when a validated leader_contact exists, intentionally overwrite only jj_lianxiren/jj_dianhua with the marked row name/phone", "project document number through independent-order zjhaoma or shared-child haoma, with pinyinxm/fazhengri/youxiaori for the remaining native fields", - "normalize every persisted source value by removing whitespace and report implicit_document_type=护照 plus non_persisted_source_fields=[证件类型]", + "select only required source fields before canonicalization, ignore non-import columns including 身份证号码/年龄/证件类型, normalize every persisted source value by removing whitespace, and report implicit_document_type=护照", "treat the exact parent-form native success response as terminal success for passenger_list_import after every pre-save projection and native blocker has passed", "do not perform a post-save row requery or automatic hydrated reconciliation for a newly submitted passenger list" ], diff --git a/tools/repository-hygiene.test.mjs b/tools/repository-hygiene.test.mjs index 142950d..abfbc67 100644 --- a/tools/repository-hygiene.test.mjs +++ b/tools/repository-hygiene.test.mjs @@ -304,7 +304,7 @@ test('single-source and generated-output boundaries remain explicit', async () = const diagnoseServer = await readFile(path.join(ROOT, 'infra/diagnose-server.sh'), 'utf8'); const gitignore = await readFile(path.join(ROOT, '.gitignore'), 'utf8'); - assert.match(template, /^交付版本:0\.5\.125\s*$/mu); + assert.match(template, /^交付版本:0\.5\.126\s*$/mu); assert.match(template, /^适用范围:当前 18 项运营业务输入;执行能力以生命周期发布门槛为准。\s*$/mu); assert.doesNotMatch(template, /^事实基线:/mu); assert.equal(tsconfig.compilerOptions.outDir, '.build');