diff --git a/.project-docs/30-worklog/tasks/20260903-adaptive-wait-auto-update-7b3e9a2c.md b/.project-docs/30-worklog/tasks/20260903-adaptive-wait-auto-update-7b3e9a2c.md new file mode 100644 index 0000000..b22201a --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260903-adaptive-wait-auto-update-7b3e9a2c.md @@ -0,0 +1,70 @@ +# Task: Add adaptive ERP readiness wait and assess extension auto-update + +## Identity + +- Task ID: 20260903-adaptive-wait-auto-update-7b3e9a2c +- Mode: Feature +- Branch: codex/20260903-adaptive-wait-auto-update-7b3e9a2c-adaptive-wait-auto-update +- Worktree: /Users/inmanx/Documents/lwltAPI-adaptive-wait-auto-update-7b3e9a2c +- Base commit: 69ea6d25178d75abf8d7fd728bd31c61764caa7d +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Make `shared_plan_create` wait adaptively for the ERP scatter-plan list add button instead of failing immediately while the list document is still rendering. +- Preserve a zero-delay fast path: check immediately, then poll the current main-frame document at a short interval only while the button is absent, with one bounded pre-write timeout. +- Add focused regression coverage and publish a synchronized Chrome extension `0.5.166` source/package baseline, including the platform minimum version, mappings, release gate, archive, and release manifest. +- Assess a server-published, client-auto-updated extension channel against Chrome's current supported distribution mechanisms; do not invent an extension ID/signing key or ship a partially operable self-update channel before the managed-device/Web Store choice is known. + +## Intent And Constraints + +- The supplied production log and retry behavior identify a pre-write page-readiness race: script injection completed, but `NewBuildB` was queried before the same-path ERP frame finished rendering. +- Reacquire `Iframe_Home.contentDocument` during each readiness probe so a stale same-path `Document` object cannot satisfy navigation; proceed as soon as a connected, enabled add button exists. +- Keep the wait entirely before the click and before any ERP write. Do not add automatic business-operation retry or weaken any existing product, form, preflight, write, or requery gate. +- Follow `RELEASE-001`: extension source, version strings, platform minimum, mappings, tests, ZIP, archived superseded package/manifest, and `dist/release-manifest.json` must remain synchronized. +- Chrome Manifest V3 remote-code restrictions remain intact. True silent updates must use Chrome Web Store distribution or a same-key signed CRX installed through supported enterprise policy; the existing unpacked ZIP workflow cannot silently replace itself. +- Do not access ERP, reload/install extensions, deploy services, mutate tasks/runtime data, inspect secrets, create signing keys, or publish externally in this Feature task. +- Other registered local tasks do not semantically conflict. The only related release-export task was read-only, and the main checkout remains owned by a separate migration/restart task. + +## Outcome + +- Added an adaptive pre-write readiness gate for `shared_plan_create`: the extension checks the current ERP list document immediately, then reacquires `Iframe_Home.contentDocument` every 100 ms only while the connected, enabled `NewBuildB` button is absent, with a 15-second total timeout. +- `createSplitParentLive` now clicks the exact button/document returned by that gate. A timeout remains a fail-closed pre-write blocker and does not retry the business operation or weaken any existing form, save, or requery evidence gate. +- Added a static regression that proves the zero-delay fast path, current-frame reacquisition, connected/enabled checks, bounded polling, and removal of the former immediate one-shot button query. +- Synchronized the extension, platform minimum, lifecycle/plan mappings, business documentation, release gate, tests, and release manifest at `0.5.166`; built `dist/ltjt-order-assistant-0.5.166.zip` with SHA-256 `68aeda857f0bf596eed3c5012fb37c6292977935ff215ce1d133983a8dffe259`. +- Archived the superseded `0.5.165` ZIP and its release manifest under `archive/releases/2026-09-03/`, with a non-current-history notice. No unique release artifact was deleted. +- Confirmed the current manually loaded unpacked/ZIP extension cannot silently replace itself. Chrome-supported unattended updates require either Chrome Web Store distribution or a same-key signed CRX delivered by HTTPS and installed through enterprise policy on Windows/macOS. +- Recommended an unlisted/private Chrome Web Store listing when the cloud PCs are unmanaged; if every PC is enrolled in AD/Azure AD, Chrome Enterprise Core, or another MDM, a self-hosted signed-CRX channel can instead use the project's server for `update.xml` and CRX publication. +- Deliberately did not add a nonfunctional in-extension downloader: an extension cannot install its own ZIP/CRX, remote executable code remains prohibited under Manifest V3, and the correct manifest, signing, publication, and policy design depends on the user's managed-device answer. Either supported route needs one final manual migration away from the existing unpacked installation. +- No ERP page, browser installation, extension runtime, signing key, Web Store listing, service, deployment, live task, or external system was changed. + +## Verification + +- Focused adaptive-entry regression: passed as part of the legacy suite. +- `node --check chrome-extension/ltjt-order-assistant/inpage.js`: passed. +- JSON parsing for the changed mappings and extension manifest: passed. +- `unzip -t dist/ltjt-order-assistant-0.5.166.zip`: passed; the archive contains the same 19 files as the governed extension source. +- `node --run check:repo`: 10/10 passed. +- `node --run check`: passed. +- `node --run test:control-plane`: 159/159 passed. +- `node --run test:legacy`: 269/269 passed. +- `node --run build`: passed. +- `check_project_docs.py`: passed. +- `check_doc_drift.py --task-id 20260903-adaptive-wait-auto-update-7b3e9a2c`: passed. +- `git diff --check`: passed. +- Full verification used a temporary symlink to the existing dependency tree in the isolated worktree; the symlink was removed afterward and is not part of the task output. + +## Follow-ups + +- Integrate and deploy/reload `0.5.166` only under separate authorization. Until then, the running browser extension remains unchanged, and this release still requires the existing manual installation procedure. +- Ask whether all target cloud PCs are enterprise-managed. After that answer, implement exactly one supported unattended-update channel: Web Store publication for unmanaged PCs, or same-key signed CRX + HTTPS update manifest + managed install policy for enterprise-managed PCs. +- For either channel, let Chrome own installation and periodic updates. An optional client action may call `chrome.runtime.requestUpdateCheck()` and report availability, but it must not force-reload the extension while an ERP task is active. + +## Promotion Candidates + +- Target documents: release/distribution architecture, operator deployment guidance, and the extension release decision record. +- Proposed durable fact: unpacked extensions are development installations and cannot be silently self-updated; production unattended updates must use a Chrome-supported Web Store or enterprise-policy signed-CRX channel, preserve Manifest V3's no-remote-code boundary, and avoid reloading during an active ERP operation. +- Evidence: Chrome's official extension distribution, self-hosting, update lifecycle, and Manifest V3 migration documentation; this task's source/package synchronization and full regression results. +- Future-task impact: release automation must retain one stable extension identity and signing authority, publish monotonically increasing versions, expose a recoverable rollback path, and treat the one-time migration from unpacked installs as an operator-controlled rollout. +- Human confirmation required: choose unmanaged/Web Store versus enterprise-managed/self-hosted distribution and identify who owns the stable extension identity/signing credentials. No secret or identity should be generated implicitly. diff --git a/LianSyn-platform/app.js b/LianSyn-platform/app.js index dfbc02d..ae71f7e 100644 --- a/LianSyn-platform/app.js +++ b/LianSyn-platform/app.js @@ -97,7 +97,7 @@ const persistedExtensionResultVersions = new Map(); let taskCreateInProgress = false; const AUTO_HANDOFF_RETRY_MS = 30_000; -const REQUIRED_EXTENSION_VERSION = '0.5.165'; +const REQUIRED_EXTENSION_VERSION = '0.5.166'; const MANUAL_HANDOFF_LABEL = '确认并提交到 ERP 插件'; const RETRY_HANDOFF_LABEL = '继续提交到 ERP 插件'; const RECONCILE_LABEL = '只读回查 ERP 现有结果'; diff --git a/agent设计规范/businesses/shared_plan_create.md b/agent设计规范/businesses/shared_plan_create.md index d56c988..68c51cd 100644 --- a/agent设计规范/businesses/shared_plan_create.md +++ b/agent设计规范/businesses/shared_plan_create.md @@ -75,7 +75,7 @@ 当前页面为已登录的 `https://lwlt.hisy.cc/System/Mainlt.asp`。插件按以下顺序操作: -1. 进入“散拼团计划表”,点击“新增计划”,打开 `/System/Business/plan_add.asp`。 +1. 进入“散拼团计划表”后立即检查“新增计划”按钮;若 ERP 主 iframe 已进入 `/System/Business/plan.asp` 但按钮仍在渲染,则持续重新取得当前 iframe document 并按条件短轮询,按钮一旦存在且可用便立即点击,不使用固定等待。只有在 15 秒总超时后仍不可用才在写前阻断,然后打开 `/System/Business/plan_add.asp`。 2. 填写 `Riqi1=2026-09-01`、`Riqi2=2026-09-30`。 3. 先在散拼团产品列表的产品名称/天数单元格中按 `广东衡阳6D` 做连续或有序唯一匹配并点选 `cp_id`。若当前候选无法解析,调用 `S_chanpinming` 的原生非空检索:完整产品名原样搜索,`10D`/`8天` 等尾部数字时长先剥离后只搜索产品名;再对返回候选本地唯一匹配,必要时以空条件做一次兼容重载。点选后的 `set_th` 必须自动带入团号前缀/后缀。 4. 填写 `jihuashu=40`。 @@ -109,6 +109,7 @@ - 日期枚举无法完整覆盖周期日期,或同一日期会提交多个勾选值; - 计划容量或房型总数为 0,或已提供的可选字段未能正确落到 ERP 控件; - ERP 默认跟团人/销售人为空; +- 散拼团计划表在 15 秒条件等待后仍未出现可用的“新增计划”按钮; - 页面自身校验弹窗、登录失效、权限错误、响应不明确或任一返回团号回查未命中。 ## 3. Skill 适配 diff --git a/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md b/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md index 5cee395..29803f7 100644 --- a/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md +++ b/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md @@ -2,12 +2,14 @@ ## 当前基线 -- Chrome 插件:`0.5.165` +- Chrome 插件:`0.5.166` - 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` - 发布状态:核心窄能力已有真实证据;一笔名单附件 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,按钮一旦可用便立即继续。该等待只发生在点击前,不固定拖慢快速页面,也不触发业务写入重试。 + 运行中的浏览器版本必须实时握手确认。当前制品文件名和 SHA-256 只看 [`../../../dist/release-manifest.json`](../../../dist/release-manifest.json),不从历史日志推断。 ## 已证明的核心能力 diff --git a/archive/releases/2026-09-03/README.md b/archive/releases/2026-09-03/README.md new file mode 100644 index 0000000..5be4585 --- /dev/null +++ b/archive/releases/2026-09-03/README.md @@ -0,0 +1,6 @@ +# 2026-09-03 发布归档 + +本目录只保存被新版本替代的历史发布物,不定义当前运行规则。当前制品文件名和 SHA-256 只以根目录 [`../../../dist/release-manifest.json`](../../../dist/release-manifest.json) 为准。 + +- `release-manifest-0.5.165.json`:加入散拼母团列表入口自适应就绪等待前的历史清单快照。 +- `ltjt-order-assistant-0.5.165.zip`:被 `0.5.166` 的散拼母团“新增计划”按钮自适应条件等待替代;保留用于恢复和追溯。 diff --git a/dist/ltjt-order-assistant-0.5.165.zip b/archive/releases/2026-09-03/ltjt-order-assistant-0.5.165.zip similarity index 100% rename from dist/ltjt-order-assistant-0.5.165.zip rename to archive/releases/2026-09-03/ltjt-order-assistant-0.5.165.zip diff --git a/archive/releases/2026-09-03/release-manifest-0.5.165.json b/archive/releases/2026-09-03/release-manifest-0.5.165.json new file mode 100644 index 0000000..bfdd98d --- /dev/null +++ b/archive/releases/2026-09-03/release-manifest-0.5.165.json @@ -0,0 +1,69 @@ +{ + "manifest_version": 1, + "generated_on": "2026-09-02", + "baselines": { + "chrome_extension": "0.5.165", + "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.165", + "path": "dist/ltjt-order-assistant-0.5.165.zip", + "source": "chrome-extension/ltjt-order-assistant", + "sha256": "14f3150ab26d99131e32321ddc805a85550438bc2810a1e20921f7922b25aaac" + }, + { + "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/chrome-extension/ltjt-order-assistant/README.md b/chrome-extension/ltjt-order-assistant/README.md index f09b077..0576995 100644 --- a/chrome-extension/ltjt-order-assistant/README.md +++ b/chrome-extension/ltjt-order-assistant/README.md @@ -11,9 +11,11 @@ Chrome Manifest V3 扩展,在用户已登录的 LTJT ERP 页面内执行经过 ## 当前版本 -当前源码版本为 `0.5.165`。版本化 ZIP、文件哈希和 Skill/DOCX 基线见 [`../../dist/release-manifest.json`](../../dist/release-manifest.json)。旧版本实现流水已冻结在 [`../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md`](../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md)。 +当前源码版本为 `0.5.166`。版本化 ZIP、文件哈希和 Skill/DOCX 基线见 [`../../dist/release-manifest.json`](../../dist/release-manifest.json)。旧版本实现流水已冻结在 [`../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md`](../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md)。 -0.5.165 当前重点: +0.5.166 当前重点: + +- 散拼新增计划进入计划表后立即检查“新增计划”按钮;若同路径 iframe 尚在渲染,则每 100ms 重新取得当前 document 并检查按钮是否已连接且可用,出现后立即继续,最长 15 秒后才在写前阻断。快速页面没有固定等待,也不会自动重试任何 ERP 写入。 - 散拼新增计划和独立团批量下单在已加载候选无法唯一匹配时,会真正触发 `S_chanpinming` 的原生非空检索并等待 Ajax 返回,再对产品名/时长做本地唯一匹配。完整产品名原样查询;只有尾部 `10D`、`8天` 等数字时长会先剥离。最后仍保留一次空条件兼容重载,零候选或多候选继续在写前阻断。 diff --git a/chrome-extension/ltjt-order-assistant/inpage.js b/chrome-extension/ltjt-order-assistant/inpage.js index d0cd1a4..6d6014b 100644 --- a/chrome-extension/ltjt-order-assistant/inpage.js +++ b/chrome-extension/ltjt-order-assistant/inpage.js @@ -2,6 +2,8 @@ 'use strict'; const SPLIT_PARENT_RESPONSE_REQUERY_DELAYS_MS = Object.freeze([0, 750, 2000]); + const SPLIT_PARENT_ENTRY_READY_TIMEOUT_MS = 15000; + const SPLIT_PARENT_ENTRY_READY_INTERVAL_MS = 100; function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -1351,6 +1353,24 @@ return current; } + async function waitForSplitParentAddButton(initialListDocument) { + return waitForCondition(() => { + // The ERP can expose the destination path before replacing or finishing + // the iframe document. Reacquire it on every probe so a stale same-path + // document cannot make the first execution fail while the button renders. + const candidate = mainIframe() ? mainIframeDocument() : initialListDocument; + if (!candidate) return null; + try { + if (!/\/system\/business\/plan\.asp$/i.test(candidate.location.pathname)) return null; + const addButton = candidate.querySelector('#NewBuildB, [name="NewBuildB"]'); + if (!addButton || addButton.isConnected === false || addButton.disabled === true) return null; + return { document: candidate, addButton }; + } catch (error) { + return null; + } + }, SPLIT_PARENT_ENTRY_READY_TIMEOUT_MS, SPLIT_PARENT_ENTRY_READY_INTERVAL_MS); + } + function nativeListRequestMatches(settings = {}, scopeDocument) { let requestUrl; try { @@ -3879,9 +3899,16 @@ S_chufariqi: dateStart, S_chufarizhi: dateEnd }); - const addButton = listDocument.querySelector('#NewBuildB, [name="NewBuildB"]'); - if (!addButton) return { status: 'split_parent_live_blocked', blockers: ['split parent add button not found'], no_erp_write: true }; - addButton.click(); + const listReady = await waitForSplitParentAddButton(listDocument); + if (!listReady) { + return { + status: 'split_parent_live_blocked', + blockers: ['split parent add button not found after readiness timeout'], + no_erp_write: true + }; + } + listDocument = listReady.document; + listReady.addButton.click(); const formDocument = await waitForDocumentPath(/\/system\/business\/plan_add\.asp$/i, 30000); if (!formDocument) return { status: 'split_parent_live_blocked', blockers: ['split parent form did not load'], no_erp_write: true }; const ready = await waitForCondition(() => { @@ -6091,7 +6118,7 @@ } window.LTJTOrderAssistant = { - version: '0.5.165', + version: '0.5.166', resolveNativeListSearchValues, lookupKeywordMatchesText, inspectLifecycleSearchCriteria: lifecycleSearchCriteria, diff --git a/chrome-extension/ltjt-order-assistant/manifest.json b/chrome-extension/ltjt-order-assistant/manifest.json index 1ff1c19..4cd1a9a 100644 --- a/chrome-extension/ltjt-order-assistant/manifest.json +++ b/chrome-extension/ltjt-order-assistant/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "联泰下单助手", - "version": "0.5.165", + "version": "0.5.166", "description": "在已登录 LTJT ERP 页面内规划并受控执行联泰 ERP 业务操作。", "permissions": [ "activeTab", diff --git a/chrome-extension/ltjt-order-assistant/team-batch-inpage.js b/chrome-extension/ltjt-order-assistant/team-batch-inpage.js index 26784a1..4b7a187 100644 --- a/chrome-extension/ltjt-order-assistant/team-batch-inpage.js +++ b/chrome-extension/ltjt-order-assistant/team-batch-inpage.js @@ -944,7 +944,7 @@ window.LTJTOrderAssistant = { ...(window.LTJTOrderAssistant || {}), - version: '0.5.165', + version: '0.5.166', openTeamBatchForm, pingTeamBatchFrame, preflightTeamBatchNative, diff --git a/dist/ltjt-order-assistant-0.5.166.zip b/dist/ltjt-order-assistant-0.5.166.zip new file mode 100644 index 0000000..3dd375e Binary files /dev/null and b/dist/ltjt-order-assistant-0.5.166.zip differ diff --git a/dist/release-manifest.json b/dist/release-manifest.json index bfdd98d..a8fe92b 100644 --- a/dist/release-manifest.json +++ b/dist/release-manifest.json @@ -2,7 +2,7 @@ "manifest_version": 1, "generated_on": "2026-09-02", "baselines": { - "chrome_extension": "0.5.165", + "chrome_extension": "0.5.166", "skills": "0.5.125", "business_instruction_docx": "0.5.125", "agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories" @@ -10,10 +10,10 @@ "artifacts": [ { "kind": "chrome_extension", - "version": "0.5.165", - "path": "dist/ltjt-order-assistant-0.5.165.zip", + "version": "0.5.166", + "path": "dist/ltjt-order-assistant-0.5.166.zip", "source": "chrome-extension/ltjt-order-assistant", - "sha256": "14f3150ab26d99131e32321ddc805a85550438bc2810a1e20921f7922b25aaac" + "sha256": "68aeda857f0bf596eed3c5012fb37c6292977935ff215ce1d133983a8dffe259" }, { "kind": "skill", diff --git a/mappings/lifecycle.mapping.json b/mappings/lifecycle.mapping.json index 3cd3c15..e7ce215 100644 --- a/mappings/lifecycle.mapping.json +++ b/mappings/lifecycle.mapping.json @@ -1,7 +1,7 @@ { "contract_version": "ltjt-lifecycle-v2.9-roster-leader-contact-2026-08", "current_agent_parse_prompt_version": "ltjt-agent-prompt-v1.8-independent-headcount-categories", - "current_extension_version": "0.5.165", + "current_extension_version": "0.5.166", "historical_test_marker": "TEST-202609", "scope": "/System/Business/", "erp_session_keepalive": { diff --git a/mappings/plan_add.mapping.json b/mappings/plan_add.mapping.json index ecb4cb9..93a392f 100644 --- a/mappings/plan_add.mapping.json +++ b/mappings/plan_add.mapping.json @@ -1,7 +1,7 @@ { "$schema": "https://local.lwltapi/schemas/ltjt_form_mapping.schema.json", - "mapping_version": "2026-09-02.shared_plan_create.plan_add.v0.9", - "status": "native_nonempty_product_search_live_verified_release_gated_0.5.165", + "mapping_version": "2026-09-03.shared_plan_create.plan_add.v0.10", + "status": "adaptive_list_entry_readiness_release_gated_0.5.166", "source": { "schema": "schemas/standard_system_operation.schema.json", "action": "shared_plan_create" @@ -16,6 +16,14 @@ "payload_prefix": "Act=DoInfoSPs&", "response_type": "script" }, + "entry_readiness": { + "page": "/System/Business/plan.asp", + "target": "#NewBuildB, [name=\"NewBuildB\"]", + "policy": "check immediately; while absent reacquire the current main-iframe document and poll every 100ms; click as soon as the button is connected and enabled", + "timeout_ms": 15000, + "fixed_delay": false, + "failure_policy": "block before click and before ERP write" + }, "ordered_steps": [ { "step": 1, "operation": "set_date_range", "source": "data.recurrence.start_date/end_date", "targets": ["Riqi1", "Riqi2"] }, { "step": 2, "operation": "resolve_and_select_product", "source": "data.product.keyword || data.product.name", "search_target": "S_chanpinming", "radio_target": "cp_id", "side_effect": "set_th fills tuanxuhao1/tuanxuhao2", "unique_required": true, "fallback": "match loaded candidates first; if unresolved, issue the native non-empty product-name query and match the returned name/duration fields locally; strip only a trailing numeric duration before the native query; retain one empty-query compatibility reload; never choose the first row" }, diff --git a/tools/lifecycle-contract.test.mjs b/tools/lifecycle-contract.test.mjs index 13052bc..ba60d20 100644 --- a/tools/lifecycle-contract.test.mjs +++ b/tools/lifecycle-contract.test.mjs @@ -307,6 +307,28 @@ test('team-order live gate allows numbered receivable drift like 0.5.156', async clearTimeout(jq.ajax.__ltjt_restore_timer); }); +test('split-parent entry waits adaptively for the current iframe add button before clicking', async () => { + const inpage = await readFile(new URL('../chrome-extension/ltjt-order-assistant/inpage.js', import.meta.url), 'utf8'); + const readinessBody = inpage.slice( + inpage.indexOf('async function waitForSplitParentAddButton'), + inpage.indexOf('function nativeListRequestMatches') + ); + const createBody = inpage.slice( + inpage.indexOf('async function createSplitParentLive'), + inpage.indexOf('async function createSplitChildLive') + ); + assert.match(inpage, /SPLIT_PARENT_ENTRY_READY_TIMEOUT_MS = 15000/); + assert.match(inpage, /SPLIT_PARENT_ENTRY_READY_INTERVAL_MS = 100/); + assert.match(readinessBody, /return waitForCondition/); + assert.match(readinessBody, /const candidate = mainIframe\(\) \? mainIframeDocument\(\) : initialListDocument/); + assert.match(readinessBody, /addButton\.isConnected === false/); + assert.match(readinessBody, /addButton\.disabled === true/); + assert.match(createBody, /const listReady = await waitForSplitParentAddButton\(listDocument\)/); + assert.match(createBody, /listDocument = listReady\.document/); + assert.match(createBody, /listReady\.addButton\.click\(\)/); + assert.doesNotMatch(createBody, /const addButton = listDocument\.querySelector/); +}); + test('split-parent post-write verification retries read-only and falls back after any missed response group', async () => { const inpage = await readFile(new URL('../chrome-extension/ltjt-order-assistant/inpage.js', import.meta.url), 'utf8'); const createBody = inpage.slice( @@ -1296,7 +1318,7 @@ test('passenger explicit server success is terminal without a post-save row requ assert.doesNotMatch(passengerBranch, /passengerRequery|verifyLifecycleOperation/); assert.doesNotMatch(background, /attemptAutomaticPassengerReconciliation/); assert.match(background, /名单已取得 ERP 明确成功响应,按业务规则确认录入成功/); - assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.165'/); + assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.166'/); }); test('uncertain lifecycle writes can only converge through a read-only plugin requery', async () => { @@ -1926,7 +1948,7 @@ test('delete guard distinguishes independent, shared child, and shared parent ro for (const operation of operations) assert.equal(plans.validateOperation(operation).ok, true, plans.validateOperation(operation).blockers.join('; ')); const mapping = JSON.parse(await readFile(new URL('../mappings/lifecycle.mapping.json', import.meta.url), 'utf8')); - assert.equal(mapping.current_extension_version, '0.5.165'); + assert.equal(mapping.current_extension_version, '0.5.166'); assert.equal( mapping.updates.order_update_independent.field_mapped_pending_live_validation['pax.child_no_bed'], 'ertrenshu' @@ -2309,10 +2331,10 @@ test('schema and browser adapters contain the v2 safety fields and no confirm ov assert.match(inpage, /`ys_danweiid\$\{index\}`, resolvedCustomerId/); assert.doesNotMatch(inpage, /product_customer_source_region|sourceRegionCheck|source_reference/); assert.doesNotMatch(teamBatchInpage, /product_customer_source_region|sourceRegionCheck/); - assert.equal(extensionManifest.version, '0.5.165'); - assert.match(inpage, /version: '0\.5\.165'/); - assert.match(teamBatchInpage, /version: '0\.5\.165'/); - assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.165'/); + assert.equal(extensionManifest.version, '0.5.166'); + assert.match(inpage, /version: '0\.5\.166'/); + assert.match(teamBatchInpage, /version: '0\.5\.166'/); + assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.166'/); assert.match(inpage, /function strictIsoDate\(value\)/); assert.match(inpage, /const startDate = strictIsoDate\(controlCanonicalValue\(form, 'riqi0'\)\)/); assert.match(inpage, /const endDate = strictIsoDate\(controlCanonicalValue\(form, 'riqis0'\)\)/);