# LTJT Business Module API Findings ## Initial Authenticated State - Site: `https://ltjt.yunzhi.run/` - Logged-in page: `https://ltjt.yunzhi.run/System/Mainlt.asp` - Title: `老挝联泰_LW5.1.3` - Main iframe: `https://ltjt.yunzhi.run/System/homelt.asp`, iframe name `MainIframe`, id `Iframe_Home` - Visible account label observed: `测试ai员工账号` - Authorized target scope stated by user: "业务操作" module only. ## Business Module Menu Items Observed - 产品管理 - 独立团计划表 - 散拼团计划表 - 团队安排 - 酒店用房表 - 航班控位表 - 团队备案表 - 退货管理 - 我的付款单 - 我的收款单 - 我的费用报销单 - 我的发票申请 - 我发放的签单 - 我的客户欠款 - 客服回访 ## Endpoint Inventory ### Business Page URLs From Main Menu - `GET /System/Business/Product.asp` - 产品管理 - `GET /System/Business/orders.asp` - 独立团计划表 - `GET /System/Business/plan.asp` - 散拼团计划表 - `GET /System/Business/teams.asp` - 团队安排 - `GET /System/Business/Hotels.asp` - 酒店用房表 - `GET /System/Business/tickets.asp` - 航班控位表 - `GET /System/Business/teams_beians.asp` - 团队备案表 - `GET /System/Business/teams_return.asp` - 退货管理 - `GET /System/Business/Cash_fu.asp` - 我的付款单 - `GET /System/Business/Cash_shou.asp` - 我的收款单 - `GET /System/Business/Cash_zhi.asp` - 我的费用报销单 - `GET /System/Business/Invoice.asp` - 我的发票申请 - `GET /System/Business/Bill_out.asp` - 我发放的签单 - `GET /System/Business/Arrears.asp` - 我的客户欠款 - `GET /System/Business/kefu.asp` - 客服回访 ### Product Management Page - Menu click creates/uses iframe id `jidiao40`, iframe name `MainIframe`, title `产品管理`, src `/System/Business/Product.asp`. - Initial page request: `GET /System/Business/Product.asp`. - List XHR: `POST /System/dat/product.asp`. - Observed list request body: - `Act=PrintGridList` - `Tpage=1` - `P_Size=50` - `S_fabudanwei=老挝联泰` - `S_chanpinming=` - `S_tianshu=` - `S_kehu=` - `S_caozuoren=` - `S_zhuanxianming=` - `S_zhuangtai=` - Autocomplete/field metadata XHR: `POST /System/dat/AjaxPublicFun.asp?Act=ProGetTableZD&tb=Product&zd=chanpinming&s_fabudanwei=老挝联泰`. - Product page loads JS: `/JavaScript/PubFun.js`, `/JavaScript/onmouseover.js`, `/JavaScript/SelectBox/SelectBox.min.js`. - Product iframe visible text confirms list columns include: 行程编码, 业务名称, 产品名称/行程天数, 所属客户, 接待说明, 航班信息, 用房数, 结算价格, 更新人, 操作. ### Business Page URLs With Observed Query Parameters - `/System/Business/orders.asp?S_zhuangtai=预订&wode=0` - 待确认订单数 - `/System/Business/teams.asp?S_chufariqi=2026-7-7&S_chufarizhi=2026-7-7&wode=0` - 今日出发团队 - `/System/Business/orders.asp?S_chufariqi=2026-7-8&S_chufarizhi=2026-7-8&wode=0` - 明日出发团队 - `/System/Business/orders.asp?riqi=xiadanri&S_chufariqi=2026-7-7&S_chufarizhi=2026-7-7&wode=0` - 今日新增团队 - `/System/Business/cash_shou.asp?zt=待认领` - 收款单提交 - `/System/Business/teams.asp?S_zhuangtai=今日回程团` - 今日回程团 - `/System/Business/teams_return.asp` - 退货管理 ### Independent Group Down-Order Flow - On `/System/Business/orders.asp`, the `下单` button calls `OPEN_update(0,0,0)`. - The button opens `GET /system/Business/orders_add.asp?fabudanwei=<分公司>©=0&ddid=0&_=` in a dialog iframe. - The add/edit page title is `计划下单/修改`. - The save action is `POST /System/DAT/orders.asp` with payload `Act=DoInfoJH&` plus `ListForm.serialize()`. - The save response is JavaScript (`dataType: "script"`), not JSON. - `SubmitInfoForm()` checks that passenger count is not zero, then calls `Vidy.CheckMastInput("ListForm")`, then sends the Ajax POST. - The add form is large: about 825 `input/select/textarea` controls were present, due to repeated itinerary, fee, flight, hotel/restaurant/attraction, upload, and hidden metadata rows. - Required primary fields observed include `chufa_ri`, `zhuanxianming`, `TianShu`, `zutuanshe`, `chanpinming`, `gendanren`, `tuanxuhao1`, `tuanxuhao2`, passenger-count fields, and `xiaoshouren`. - The add page loads several lookup endpoints under `/system/dat/AjaxPublicFun.asp`, including product, travel/customer, staff, flight/control, fee item, and partner-resource lookups. ### Static Assets Seen On Main Pages - `/JavaScript/jquery-1.4.2.min.js` and `/JavaScript/jquery-1.8.3.min.js` - `/JavaScript/VidySail.js` - `/JavaScript/lhgdialog/lhgdialog.min.js?skin=blue&self=true` - `/JavaScript/highcharts/js/highcharts.js` See [api_inventory.md](/Users/inmanx/Documents/lwltAPI/api_inventory.md) for the structured endpoint inventory. ## Auth and Session Notes - App is classic ASP on Microsoft IIS. - Login uses an `ASPSESSIONID...` cookie plus multiple custom session cookies. - Cookie values were intentionally not recorded; cookie names/attributes only. - `ASPSESSIONIDAUTTQAQD` was observed as `secure=true`, `httpOnly=false`, session cookie. - Multiple custom cookies are not `HttpOnly` and appear to be session-scoped. - A main-page reload during capture redirected back to `/system/loginlt.html`; avoid destructive reloads during capture and prefer in-frame navigation after re-login. - Real menu clicks do not replace the home iframe. They create additional module iframes such as `jidiao40` for 产品管理 and add a tab in `#Main_TopMenu`. - Direct unauthenticated requests to `/System/Mainlt.asp`, `/System/homelt.asp`, and representative `/System/Business/*.asp` pages all returned a 129-byte HTML/script response that alerts "未登录或登录超时" and redirects to `/system/loginlt.html`. - Login page posts `Action=CheckSystemLogin`, `UserName`, `UserPwd`, `Code`, `set_lang`, and a client-generated `session_id` to `POST /system/dat/AjaxCommand.asp`; response is executed as JavaScript (`dataType: "script"`). - CAPTCHA image endpoint: `GET /Inc/GetCode.asp?`. - Direct `GET/HEAD /system/dat/AjaxCommand.asp` without login/action returns an empty 200 response and sets a fresh ASP session cookie. - Page-context sensitivity: direct `fetch` probes to `/System/dat/*.asp` from the top page and from an existing module iframe returned the 86-byte JavaScript response `alert('登陆失效,请重新登陆!')` after the session became unstable, while natural menu-triggered page loads had successfully called the same dat endpoints. Treat these endpoints as requiring a fresh valid browser session and possibly the same referer/frame/window state as the originating ASP page. ## Integration Risks - There may not be a formal JSON API; the integration may require browser-equivalent ASP form/query calls and HTML parsing unless hidden AJAX endpoints are found. - Session cookies are browser/session based. A stable server-to-server integration would need either an official API, a dedicated integration account, or a controlled login/session renewal strategy approved by the system owner. - We need to inspect individual business pages before concluding whether list/detail operations return HTML, script, JSON, or downloadable files. - The public `VidySail.js` asset appears to be general validation/utility code rather than an API client; it does not expose a centralized business API wrapper. - Simulated login is likely undesirable for production unless the system owner provides an integration account and an approved CAPTCHA/session process. - Successful page loads prove the browser can call the list endpoints, but direct `fetch` probes after session expiry returned a JavaScript login-timeout response. The adapter must detect this response and renew the session. - The adapter design needs a page-context strategy: either replay requests with exact cookies/referer/origin from a freshly opened module page, or run the request inside a controlled browser context that first opens the relevant ASP page. - For write operations such as independent-group down-order, the adapter should initially run in dry-run/read-only discovery mode. Actual `Act=DoInfoJH` submission requires explicit approval and either a sandbox/test order or a rollback policy. - Down-order integration should likely start with a browser-context serializer: open `orders_add.asp`, fill/select required fields, run the page's own lookup/calculation code, inspect `ListForm.serialize()`, and only later enable actual submission. ## Adapter Mapping Findings - First local mapping artifact: `mappings/orders_add.mapping.json`. - First local dry-run serializer: `tools/dry_run_order_create.mjs`. - Browser-context comparison helper: `tools/browser_order_add_dry_run.mjs`. It is designed to fill and serialize the live form only; it does not click submit or call `DoInfoJH`. - The standard operation contract now requires `route`, `trip`, and `order_number` for `team_order_create`, because LTJT requires `zhuanxianming`, `TianShu`, `tuanxuhao1`, and `tuanxuhao2`. - Local dry-run can build a full `Act=DoInfoJH&...` payload shape from captured form fields, but it is still a draft until compared against a live browser `ListForm.serialize()` after LTJT lookup selections. - Lookup-sensitive fields remain the main submit blocker for production: customer (`zutuansheid`, currency/contact metadata), route/order prefix, product pricing metadata (`baojia` and product-loaded fields), OP, and salesperson. - Passenger-list operations remain out of scope for the first mapping. The current mapping only supports `passenger_list.operation = none`. - Previous live browser comparison blocker was resolved by reopening the collaboration Chrome on `127.0.0.1:9223` and logging in again. - After fresh login, browser-context dry-run passed against live `orders_add.asp`: local and browser serialized field sets both contained 822 fields, with zero mapped-field mismatches. - Live form discovery found `yaobeian=要备案` is serialized by default. The standard operation now has `data.system_defaults.filing_required`, mapped to LTJT `yaobeian`. - The browser dry-run is still not a production submit path: it verifies form field compatibility and serialization only. The next production-grade step is deterministic lookup resolution for customer, route, product, OP, and salesperson hidden metadata. ## SelectBox / Lookup Findings - The user's warning is correct: many visible text fields are LTJT `SelectBox` widgets, not free text fields. - The SelectBox data protocol is `◇` for rows and `◆` for columns, confirmed from the live `/JavaScript/SelectBox/SelectBox.min.js`. - Critical create-order fields must be selected from existing LTJT choices: - `TianShu`: static options, 10 rows, maps to itself. - `zhuanxianming`: lookup endpoint returned 16 rows x 11 columns; selection sets `zhuanxianming` and `tuanxuhao1`. - `chanpinming`: lookup endpoint returned 160 rows x 2 columns; selection sets `chanpinming` and should then trigger `GetProduct(cpm)` side effects. - `zutuanshe`: lookup endpoint returned 93 rows x 6 columns; selection sets `bizhong`, `zutuansheid`, `zutuanshe`, `lianxiren`, and `zutuanshegzr`. - staff lookup returned 33 rows x 2 columns; used by `gendanren` and `xiaoshouren`. - Resource fields are also SelectBox-backed: hotels, restaurants, attractions, flights, receivable items, and reserved resource/detail fields each have their own lookup-backed ranges and linked fields. - A serializer-only dry run can prove field names and payload shape, but it must not be treated as production-ready. Production readiness requires lookup validation: every SelectBox-bound standard value must match exactly one LTJT row, and the adapter must apply the page's linked `SetVal` fields. - Fake sample data is now correctly blocked by `tools/validate_order_add_lookups.mjs`: only the static `4D3N` option matched; fake route/product/customer/OP/sales values had zero exact matches. - `tools/resolve_order_add_lookups.mjs` now turns exact-matched SelectBox rows into linked field patches using `SetVal`. It redacts resolved values in its normal report and refuses to write `--resolved-out` when any critical lookup is blocked. - `tools/dry_run_order_create.mjs --lookup-resolution` now blocks payload generation unless lookup resolution has passed, so serializer-only success cannot accidentally be treated as submit readiness. ## Product Side-Effect / Browser Preflight Findings - Product selection has a second required layer beyond the `chanpinming` SelectBox match. The page function `Find_product()` calls `../dat/AjaxPublicFun.asp?Act=GetProduct&cpm=` with `dataType:"script"`, and the returned script mutates the current `ListForm`. - `tools/inspect_order_add_product_effect.mjs` was added to run this behavior without submitting. It can exact-match a product from a standard operation, or use `--sample-first-existing-product` for a redacted non-submit inspection. It stores only field names, counts, byte lengths, and redacted metadata. - Redacted sample inspection of the first existing product observed one successful `GetProduct` script response and 135 changed form fields: 9 itinerary text fields, 7 core fields, 21 pricing/receivable fields, 96 resource fields, and 2 other fields. No `DoInfoJH` submit was attempted. - Product side effects can overwrite fields that were previously treated as direct or lookup fields, including `TianShu`, `zhuanxianming`, `tuanxuhao1`, `tuanxuhao2`, `zutuanshe`, `zutuansheid`, `xiaoshouren`, `ys_*` receivable rows, itinerary text, meals/hotels/attractions, and related resource metadata. - Production filling order should therefore be product-template first, then consistency validation, then controlled overwrite of fields owned by the upstream business system. If the product template's trip, route, customer id, or route prefix does not match the standard operation and lookup resolution, the adapter should block instead of masking the mismatch by overwriting visible text fields. - `tools/browser_order_add_preflight.mjs` now combines lookup exact-match checks, product `Find_product()` execution, product-template consistency checks, controlled post-product field setting, receivable-row clearing/rewrite from standard prices, and final live `ListForm.serialize()` without submitting. - The fake sample is correctly blocked by browser preflight before product side effects: static trip matched once, while fake route/product/customer/OP/sales values matched zero LTJT options. The report is redacted by default and does not write serialized values. - `tools/browser_order_add_submit_intercept.mjs` now tests the page's own `SubmitInfoForm()` branch by monkey-patching `jQuery.ajax` before the function runs. When the fake-filled form is used, the page attempts one `Act=DoInfoJH` request, but the request is intercepted and never reaches the network. The report stores field names, field count, byte length, and a payload hash only. - `tools/browser_order_add_template_selftest.mjs` now proves the non-writing pipeline can pass using a real existing LTJT product template while keeping all option values in browser memory only. The redacted self-test selected an existing product, exact-matched product/trip/route/customer/staff once each, observed 135 product-side-effect field changes, filled obvious test markers (`AI-DRYRUN-NO-SUBMIT`) into the current draft, serialized 822 fields, and captured one prevented `DoInfoJH` submit branch. - `tools/browser_order_add_submit_approved.mjs` is the guarded live-submit tool. It refuses before connecting to the browser unless it has: a `browser_preflight_passed` report, a separate `submit_intercept_captured` report, matching payload SHA-256 values, `--execute-live-submit`, and the exact approval token `APPROVE-LTJT-DOINFOJH-SUBMIT`. - The live-submit refusal path was verified using the template self-test report as an invalid stand-in. It refused before browser access and reported blockers for missing execute flag, wrong report statuses, and missing intercepted payload hash. - `SubmitInfoForm()` can mutate the payload between a plain `ListForm.serialize()` snapshot and the actual `DoInfoJH` request. The approved submit path now treats the submit-intercept payload hash as authoritative and re-checks the actual AJAX request hash immediately before network send. - A controlled live test submit was completed with obvious test markers (`AI-DRYRUN-NO-SUBMIT`) and a hash-matched approved payload. The submit response was classified as successful: one `POST /System/DAT/orders.asp`, `dataType:"script"`, HTTP/AJAX success, no login-timeout text, no permission text, and a success hint in the redacted response. - `tools/verify_order_marker.mjs` verifies the submitted test order through the independent-order list endpoint without storing business row values. Searching `JH_OrderList` for departure date `2026-8-1` and marker `AI-DRYRUN-NO-SUBMIT` found the marker in the response, proving the submitted test order is visible in LTJT. ## Raw Instruction Simulation Findings - `samples/raw_instruction_team_order_20260811.json` records the user's raw-order instruction as a structured test operation with the explicit ERP test marker `RAWTEST-ERP-20260811`. - `tools/browser_order_add_raw_instruction_test.mjs` was added for the test-stage workflow where upstream business-system fields are not yet pre-split. It accepts the parsed raw instruction, opens a fresh `orders_add.asp` form, selects the product from existing LTJT options, runs `Find_product()`, and derives linked route, trip, customer, customer id, currency, and route-prefix fields from the product template in browser memory. - The raw-instruction test validates choice-backed fields before submit. For the `遇见老挝` order dated `2026-08-11`, product, post-template product, trip, route, customer, and salesperson each resolved to exactly one LTJT option. The OP value `测试` was not an exact staff option, but it resolved by deterministic `unique_contains` to exactly one existing staff option; ambiguous or zero-match cases block submit. - The same test generated three receivable rows from the raw counts/prices: adult, child-with-bed, and child-no-bed. Passenger total was 4, room total was 2, and computed receivable total was 1380. - Product template side effects changed 126 form fields for this product: 10 itinerary text fields, 7 core fields, 21 pricing/receivable fields, 86 resource fields, and 2 other fields. The adapter then cleared/rebuilt receivable rows while preserving product-template itinerary/resource fields. - The submit branch was first intercepted with no network write. The intercepted `DoInfoJH` payload had 822 serialized fields and hash `5c56f91d7552797c4cddc8c99d1a206a6c3a83125c5c19dea817c15b8358c2f3`. - The approved live submit succeeded for the same hash: one `POST /System/DAT/orders.asp`, `dataType:"script"`, no login-timeout text, no permission text, success hint present, and no error hint. A dialog alert was emitted by the ERP response and was redacted in the report. - `tools/verify_order_marker.mjs` confirmed the saved raw-instruction ERP test order by searching `JH_OrderList` for departure date `2026-8-11` and marker `RAWTEST-ERP-20260811`; the response contained the marker and did not look empty or unauthorized. ## Chrome Extension Prototype Findings - A local MV3 Chrome extension prototype now lives at `chrome-extension/ltjt-order-assistant`. It can be installed with Chrome's `Load unpacked` flow. - The extension popup parses raw Chinese order instructions or accepts the structured JSON operation format, then locks the newest `orders_add.asp` iframe opened from the logged-in LTJT ERP page. - `inpage.js` runs in Chrome's `MAIN` world so it can call the ERP page's own `Find_product()`, intercept `jQuery.ajax`, and invoke `SubmitInfoForm()` without coordinate-based RPA. - Version `0.1.0` preserves the current safety boundary: live submit is blocked unless the operation is a test order with a non-empty `data.test_marker`, a passed preflight, a captured submit payload hash, and explicit checkbox confirmation. - The packaged local zip is `chrome-extension/ltjt-order-assistant.zip`; for local use, the unpacked folder remains the preferred install target. - A minimal mock business-system page now lives at `mock-business-system`. It has a single raw-instruction text box, simulates a business-system parsing agent, creates one structured ERP order task, and sends it to the extension through `window.postMessage` plus the extension content script `business-bridge.js`. - The intended parsing-agent prompt is stored at `mock-business-system/agent_prompt.md`. It defines the LTJT field meanings, required output schema, passenger/room/price mappings, test-marker policy, and explicitly tells the agent not to guess ERP linked IDs or product-template side-effect fields. - `mock-business-system/server.mjs` now serves the mock page and exposes `/api/agent/parse`, a minimal OpenAI-compatible chat-completions proxy. The page can run in local deterministic mock mode or model API mode, with endpoint/model/key configured in the folded `Agent API 配置` section. - The extension now writes task feedback back into `chrome.storage.local` under the business task id after preflight, live submit, verification, or extension errors. The mock page polls the bridge for those results and displays the latest status. ## Mock Business-System / Agent Findings - The current mock business-system page intentionally represents the future upstream business system with one large raw-instruction text area. During the test phase, a local or model-backed agent decomposes that text into the standard `team_order_create` operation. - The model-agent path is intentionally OpenAI-compatible rather than provider-specific. The browser page stores endpoint/model locally and keeps the API key in session storage; `server.mjs` proxies the request to avoid browser CORS issues. - MiniMax testing showed that endpoint selection matters: the global-looking `api.minimax.io` endpoint returned HTTP 401 for the supplied key, while the China endpoint `api.minimaxi.com` returned HTTP 200 with the same OpenAI-compatible request shape. - The production parsing prompt lives in `mock-business-system/agent_prompt.md`. Its job is business-text decomposition and LTJT field understanding only. It must not invent linked ERP IDs, hidden fields, product template effects, or SelectBox choices; those remain deterministic browser/ERP validations. - Task cards are now the operator-facing surface. The task card is created before plugin/ERP execution, so business users can track waiting, paused, running, blocked, and completed states without opening the extension popup. ## Extension / Bridge Findings - The extension is no longer treated as a standalone data-entry app for normal users. It is a browser-session adapter controlled by the business system and only exposes a small popup for connection state plus the ERP-operation enable switch. - The content-script bridge uses `window.postMessage` between the local business page and `business-bridge.js`, then `chrome.runtime.sendMessage` / `chrome.storage.local` inside the extension. This is page automation, not coordinate-based RPA. - The bridge can report two separate concepts that should not be collapsed: - Business-system/plugin bridge connected. - ERP automation enabled. - When ERP automation is disabled, the business system should show the plugin as connected but mark new executable tasks as paused rather than trying to run ERP and then showing a confusing failure. - The extension reload lifecycle is a real operational risk. After reloading an unpacked extension, old content scripts in already-open pages can throw `Extension context invalidated`. Version `0.2.12` adds reinjection tolerance, but the safest manual test procedure remains: reload extension, refresh business-system page, then open the popup. - Current direct page bridge scope is one browser profile. It can work across multiple windows/tabs in the same Chrome profile, but it cannot see pages in another Chrome profile, Edge, Safari, Firefox, or another machine. - Multi-browser production support should use a local bridge hub or backend task queue: business system submits tasks to the hub/queue, browser plugins register as workers, and task status returns through the same channel. ## Task Deletion and Cancellation Findings (2026-07-12) - The business page currently exposes top-level `查询反馈` and `清空任务` controls; their listeners call `pollResult()` and `clearTasks()` and must be removed together with the controls. - The right task panel currently has only `#taskState` and `#taskDetail`; deletion should target `currentTaskId` and remove that one task from local storage. - The bridge has `CLEAR_TASKS` but no single-task delete/cancel message. A new bridge message must call the background service worker before removing the corresponding local task data. - `background.js` stores active IDs in an in-memory `runningTasks` set. Execution awaits tab loads, frame discovery, preflight, submit, verification, and batch children without cancellation checks. Cancellation can safely stop future phases at boundaries; a network/browser action already in flight cannot be rolled back by a UI delete. - The implemented cancellation flag is checked before/after browser script actions and in tab/frame wait loops. `setResult()` suppresses late writes for cancelled task IDs so bridge cleanup is not undone by a finishing promise. - A delete request is fail-closed for confirmed tasks when the bridge cannot acknowledge cancellation: the local card remains so the operator does not mistake an un-cancelled ERP task for a deleted one. Unconfirmed tasks have no plugin task and can be deleted locally. - A live ERP network submit that was already in flight when cancellation arrived cannot be rolled back by the browser extension; the UI removes the task only after the cancellation request is acknowledged. ## AI Workbench Header Findings (2026-07-12) - The supplied logo is a 1254×1254 square PNG with large white margins around a horizontal wordmark. The header uses `object-fit: cover` in a horizontal frame so the wordmark remains legible without rewriting the source asset. - The local server did not previously map `.png`; it now serves the logo as `image/png`. - AI connection state had no safe probe endpoint. The business server now exposes `GET /api/status`, returning whether `DEERFLOW_OPEN_API_KEY` is configured without exposing the key. Plugin state continues to use the existing bridge PING. - The final header uses a stretched horizontal menu with `AI操作台` as the active item; AI/plugin status tags sit at the far right and intentionally collapse their display to green `已开启` or red `未连接`. Clicking either tag refreshes only its corresponding probe. - The first AI status implementation only checked `Boolean(DEERFLOW_OPEN_API_KEY)`, so it was not a connectivity check. `/api/status` now calls `ExternalAgentParser.checkConnection()`, which sends an authenticated empty validation request to the external session endpoint without creating a session. The browser uses `ai_connected` and refreshes it on load, every five seconds, and on click. - Runtime diagnosis after the status remained red: the process listening on `127.0.0.1:8765` was an older `node server.mjs` instance. Its static page reflected the latest frontend, but its in-memory HTTP routes did not include `GET /api/status` and returned 404; the frontend correctly treated that as disconnected. The service must be restarted after server-side route changes. - The stale process was gone when the requested restart was performed, so the service was started fresh on port 8765. The route is now live, but no server-side Key is available in the current environment; restart with a private `.env`/environment variable to make the AI probe turn green. ## Three-stage Project Status UI Findings (2026-07-12) - The task store already contains the required signals: parser states such as `running` with `stage=parse`, `agent_parse_passed`, and `parse_failed`; handoff/executor states such as `accepted`, `queued`, `running`, `blocked`, and `completed`; and receipt data from `result.erp_receipt`, `result.report.erp_receipt`, alerts, or parsed ERP messages. - The new stage surface can remain presentation-only: it derives state from the selected task and does not alter task lifecycle or plugin messages. - The desktop browser check shows three compact cards fit above the terminal log in the right panel. Empty tasks render neutral pending cards; a parser failure turns the Agent card red while later stages remain pending; completed tasks with `erp_receipt` can turn the business and receipt cards green. ## Confirmation Action Relocation Findings (2026-07-12) - The existing manual confirmation action was generated inside each task card and delegated through `data-task-action="confirm"`. - It is now a single selected-task action in the right detail header. The action is hidden unless `requiresManualConfirmation(selectedTask())` is true, preventing duplicate confirmation controls and keeping the left card focused on type, ID, and status. ## Input Area Copy Findings (2026-07-12) - The left panel's functionality is already wired to `#rawInstruction` and `#createTaskButton`, so the requested label changes are presentation-only and do not alter parsing or task creation behavior. ## Current MVP Definition - MVP success is no longer "the assistant can fill one ERP form once." That has been proven with guarded live test orders. - The current MVP is: a business-system task is created from raw or structured input; the plugin automatically performs the LTJT ERP operation in the logged-in browser session; the business system shows task progress and final result; the operator can disable ERP actions from the plugin. - The main remaining MVP risk is connection/session reliability around extension reloads, browser/profile boundaries, and ERP login/session expiry. These should be tested deliberately before adding more business scenarios. ## External Agent Parsing Refactor — Confirmed 2026-07-12 - The requested change is limited to raw business-input parsing and returning the existing standard `operation` JSON to the business system. ERP/plugin execution is unchanged and out of scope. - Each input task gets an independent Open Agent Session. A retry of the same task should reuse its session and idempotency keys; separate tasks must not share context. - The business-system server will call `POST /api/open/agent-sessions`, then `POST /api/open/agent-sessions/{session_id}/messages/stream`, collect SSE events, and return the existing normalized parse result to the browser UI. - The external Agent Profile's published prompt is the sole prompt source. The local prompt file/editor and browser model configuration are removed. - `DEERFLOW_BASE_URL` and `DEERFLOW_OPEN_API_KEY` are server environment variables. The supplied secret is not written to source, examples, reports, browser storage, or logs. - The local adapter endpoint is renamed to `/api/parse`; all `/api/agent/*` routes and local Agent terminology are removed. - The external API does not receive `profile_id`; the supplied Open API token's external-app policy resolves the published Profile. - Real smoke verification with the private key confirmed Bearer authentication, CSRF double-submit compatibility, session creation, and SSE delivery. No ERP endpoint was called. - The real stream emits `metadata`, `values`, repeated `messages`, and `end`; assistant content is delivered as `AIMessageChunk` objects in `messages` arrays, not as `message.delta` events. - The currently resolved published Profile returned a generic JSON shape (`reply`, `intent`, `missingFields`, `nextStep`, `profileSuggestion`, `keyInfoSuggestions`, `citations`, `riskFlags`) during synthetic parser calls. The local adapter intentionally blocks this as `external_result_contract_mismatch`; the external Profile must be configured/published to return the agreed standard `operation` contract. ## 2026-07-12 — Authorized full ERP/plugin live test - The user explicitly authorized creating temporary ERP orders and manually deleting them afterward. The external Agent was not changed; this pass covers only the root business system, the Chrome plugin, and the logged-in ERP browser session. - Existing create routes were revalidated: team-single task `CDP-PLUGIN-TEST-20260712` completed with group `LLW-260811CDP-PLUGIN-TEST-20260712-1783842347475-A`; the sequential team-batch fallback completed for `2026-09-01` and `2026-09-08` with groups `LLW-260901BATCHT0712-D20260901-1783843465676-A` and `LLW-260908BATCHT0712-D20260908-1783843475645-A`. - The new guarded live split-parent adapter completed task `CDP-PLUGIN-SPLIT-PARENT-LIVE-20260712-R2`, creating `LW-260916PLUGSP0712R2-A` with planned capacity 4. The split-child adapter completed task `CDP-PLUGIN-SPLIT-CHILD-LIVE-20260712-R3`, adding child order `D14271` with pax `2+1+1`; the parent row requery confirmed the child and received count. - Direct and plugin export checks confirmed all three source responses: Xingyou customer confirmation, Liantai customer confirmation, and JOB order. The final plugin task `CDP-PLUGIN-EXPORT-LIVE-20260712-R3` used the correct independent-order `ddid/tid` pair (`14266/14170`), received HTTP 200 Word-like responses, and reported `no_erp_write=true`, `never_resave=true`; no local download or delivery was claimed. - Order-update and traveler-import routes are intentionally still dry-run/read-only. The ERP update POST returned HTTP 200 with an empty response and no persistence in both independent and split-child checks. The native traveler import dialog filled synthetic rows but exposed a phone-field mapping offset; no save was clicked. These routes remain fail-closed until the ERP contract is corrected and reverified. - Final local gate: extension version `0.2.14` is loaded and `PING` reports ERP automation enabled; JavaScript syntax checks pass; `tools/operation-plans.test.mjs` passes 12/12; the standard schema validates; and the packaged ZIP passes `unzip -t`. ### Manual cleanup candidates - Confirmed current-pass test groups/orders: `LLW-260811CDP-PLUGIN-TEST-20260712-1783842347475-A`; `LLW-260901BATCHT0712-D20260901-1783843465676-A`; `LLW-260908BATCHT0712-D20260908-1783843475645-A`; `LW-260915SPT0712-A` with parent record `D14268` and child `D14269`; `LW-260916PLUGSP0712R2-A` with parent record `D14270` and child `D14271`. - Earlier controlled live-submit markers also created test records: `AI-DRYRUN-NO-SUBMIT` (departure `2026-08-01`) and `RAWTEST-ERP-20260811` (departure `2026-08-11`). - The earlier `CDP-BATCH-TEST-20260712` attempt produced an ownership-uncertain record around `LLW-260812A-A`; verify it manually before deleting. No automatic deletion or retry was performed. - 2026-07-12 UI review: the external parser request was already wired through `/api/parse`, but the browser `setOutput()` function intentionally discarded technical responses and the detail pane rendered only the task ID. The pane now exposes safe request-stage diagnostics and formatted response JSON, so users can distinguish request initiation, session creation, SSE completion, contract blocking, and local failures. ## Project Development Standard Findings — 2026-07-12 - The project has two distinct ownership layers: Skill/operation design expresses business meaning, while ERP control scripts perform deterministic product-template effects, SelectBox lookup, form mapping, preflight, and post-operation verification. - The actual team-order raw-instruction test accepts product/date/passenger/room/price/OP/sales facts and derives route, trip, customer, currency, and route prefix from `Find_product/GetProduct`; this must be reflected in maintenance documentation and task-ready contract rules. - The next input protocol should require an exact first non-empty line such as `新增订单` or `修改订单`, map it directly to one standard action, and block unknown/missing first-line commands before Skill invocation. - The handoff package remains a comparison and compatibility source. Current root schemas, mappings, scripts, Skill references, and tests are the maintenance authority for the active external-Agent architecture. ## 2026-07-12 — Productionization discovery - The current repository is a validated prototype, not yet a production business platform: `mock-business-system/server.mjs` exposes only `/api/status` and `/api/parse`; the mock task store is browser `localStorage`; extension task state and results use `chrome.storage.local`. - No formal application database, identity/tenant model, durable task queue, outbox/idempotency layer, plugin registry/version policy, or server-side audit/event store was found in the current project manifests and source. - The existing `system_operation_scope.md` and handoff package already define a useful boundary: the upstream business system/database owns business meaning and standard operation data, while the LTJT adapter deterministically validates and executes against the legacy ERP. - The highest-order architecture decision is therefore the system-of-record boundary: retain LTJT as the initial external operational source of truth and build a production control plane around it, or make the new platform the primary ERP and migrate/replace LTJT capabilities. This must be confirmed before designing the durable schema and plugin runtime. ## 2026-07-12 — Repository cleanup audit findings ### Current structure - The active root implementation is small and coherent enough to preserve in place: `agent设计规范/`, `schemas/`, `mappings/`, `tools/`, `chrome-extension/`, `mock-business-system/`, and `samples/` form the current Agent-to-operation-to-ERP-adapter chain. - `开发维护规范.md` is the current maintenance rule, but there is no root README that explains the entrypoint, source-of-truth order, test gates, or archive policy. - `task_plan.md`, `progress.md`, and `findings.md` are valuable chronological logs, not runtime documentation. They contain superseded statements from earlier architecture stages and should not remain the only project orientation material. ### Legacy handoff boundary - `交接包/` is not imported or executed by current root code. It is a legacy handoff snapshot with useful business rules, route discoveries, fixtures, and safety ideas. - The handoff's executable contract (`erp-task-v1`), eight separate Skills, runner scripts, and deployment instructions are not the current cloud Agent output contract. They should be retained as read-only compatibility reference, not edited as if they were active source. - The handoff contains missing-path references and generated runtime/output folders. A future archive must separate reusable documentation/code from runtime residue instead of moving the entire directory as one undifferentiated source package. ### Risky or disposable material - `交接包/runtime/`, `交接包/diagnostics/`, `交接包/tools/runtime/`, and root `reports/` contain temporary health files, browser/runtime state, audit records, test markers, and exported document artifacts. They should be quarantined and redacted/retained only according to evidence policy. - Root `.DS_Store` files are disposable. The root has no `.gitignore`, so future browser/runtime artifacts can reappear in the project unless ignore rules are added. - The packaged extension ZIP is a release artifact, not source. Skill packages should likewise be rebuilt into a clearly named distribution directory when needed; the current root does not contain a committed portable `.skill` artifact. ### Recommended cleanup invariant Every retained file should have exactly one status: `active-source`, `active-contract`, `active-doc`, `fixture`, `historical-reference`, `validation-evidence`, `release-artifact`, `runtime-residue`, or `secret-sensitive/quarantine`. No file in the last four categories should be used as a maintenance source of truth. ## 2026-07-12 — Cleanup dependency decision - Current root source does not import the legacy handoff directory. References to the handoff occur in documentation, compatibility wording, and historical logs only. - Current root source does not read any existing report file at startup or during the normal task loop. Report paths are CLI output destinations, so removing existing report files does not break the active flow. - The legacy handoff's `runtime/`, `diagnostics/`, and `tools/runtime/` areas contain generated state and output rather than reusable source. They are deletion candidates under the user's confirmed cleanup rule. - The extension ZIP is retained by relocation to `dist/`, because it is a deliverable artifact rather than historical evidence. - Planning files remain in place as active session memory. Their old entries are history and are not allowed to override the current root README, maintenance standard, Agent/Skill contract, schema, mapping, or ERP control code. ## 2026-07-12 — Cleanup completed findings - The project now has a clear active root and a dated legacy archive. Current maintainers should not search the archive for the latest business rule. - The old handoff package retained 153 source/document/fixture files after deleting runtime and generated output areas; its package remains useful for comparison but is not part of the active execution path. - The current root has 22 fewer validation artifacts and no `.DS_Store` residue. The report directory is intentionally empty except for its output-policy README. - The extension release ZIP is now separated from extension source under `dist/`. - Remaining governance work is non-blocking: introduce a periodic stale-artifact check and decide later whether the long planning logs should move into `docs/history/` after planning-file tooling no longer depends on root paths. - User confirmed the first phase will retain LTJT as the external operational source of truth and focus only on architecture-level hardening. New business functions, new ERP routes, and workflow changes are explicitly out of scope for this pass. - User confirmed the first deployment is single-company, single-tenant, and private. Organization isolation fields may be reserved for future expansion, but full multi-tenant SaaS behavior is not part of this hardening pass. - User confirmed a centralized server plus browser-plugin Worker topology: the backend persists and dispatches tasks, while Chrome performs the existing LTJT session-bound operation and reports state. Browser/local extension storage is not authoritative. - User confirmed PostgreSQL as the authoritative application database. Redis may be used only for ephemeral concerns such as cache, locks, or transport acceleration; it must not replace durable task or audit state. - User chose not to integrate enterprise SSO in phase one and explicitly added native platform account login to the hardening scope. Account provisioning, recovery, and role policy remain open decisions. - User confirmed administrator-created/invited accounts with public registration disabled; the first administrator is bootstrapped during deployment. - User confirmed that only administrators will use the platform initially; one administrator role with full permissions is sufficient for phase one. Granular human RBAC is out of scope, while plugin Worker identity remains a separate runtime concern. - User confirmed server-side administrator sessions with secure HttpOnly/SameSite cookies rather than long-lived browser-stored JWTs. Session rotation, expiry, revocation, CSRF protection, and auditability are part of the hardening scope. - User explicitly declined MFA for phase one. The design must compensate with password, rate-limit/lockout, session, audit, and controlled recovery protections without adding an MFA flow. - User confirmed password recovery should use a controlled server-side reset path only; email self-service recovery is out of scope for the private first phase. - User confirmed the extension does not need independent registration, pairing, or Worker credentials in phase one. It runs only within the administrator's already-authenticated browser/ERP session. - User confirmed the existing execution safety chain must remain: preflight/dry-run, explicit administrator confirmation, single submit, and ERP re-query; uncertain outcomes must block automatic retry or duplicate creation. - User confirmed production PostgreSQL starts clean; prototype local storage, extension cache, reports, and test orders are not migrated. Only schema, configuration structure, and standard-operation contracts carry forward. - User explicitly chose direct production deployment without a separate development/staging environment. Release safety must therefore rely on offline/static gates, no-write defaults, explicit confirmation, migration prechecks, and recoverable database backups. - User confirmed automated PostgreSQL backups, a pre-migration backup, independent backup storage, and restore verification as mandatory production safeguards. - User confirmed a minimal private-deployment observability baseline: structured redacted logs, health checks, and alerts for service/database/backup/task/plugin/ERP reconciliation failures, without adding a third-party monitoring platform. - User confirmed production secrets use protected deployment-host injection only and must not appear in source control, images, logs, database records, frontend code, or extension storage; rotation is server-side controlled. ## 2026-07-12 — Consolidated implementation recommendation - The recommended implementation keeps the active Agent/Skill/schema/mapping/ERP chain intact and adds a TypeScript Node/Fastify control plane with PostgreSQL, explicit SQL migrations, Zod validation, Pino logs, server-side sessions, and a DB-backed task queue/lease model. - REST commands plus SSE/polling replace browser-local task authority; the current business-system page remains the operator surface with only the required login/persistence wiring added. - Redis, SSO, MFA, public registration, separate staging deployment, independent plugin credentials, new business routes, and new business screens are intentionally excluded. - Production data protection defaults to encrypted/field-redacted sensitive snapshots, redacted audit logs, configurable retention, independent backups, and restore verification. ## 2026-07-12 — Architecture hardening implementation findings - Added `control-plane/` with a Fastify/TypeScript server, PostgreSQL migration runner, native administrator authentication, encrypted task snapshots, durable task state, idempotency, leases, events, audit, outbox, SSE, and health endpoints. - The existing business page now hides the workbench until administrator authentication succeeds, reads task cards from `/api/tasks`, creates tasks through the durable API, confirms/cancels through server transitions, and persists plugin results back to PostgreSQL. Its task array is an in-memory render cache; raw draft input is session-only. - The root package uses a nested CommonJS package marker inside the extension so the existing UMD operation-plan tests remain compatible with the new root ESM/TypeScript package. - No local PostgreSQL or Docker runtime is available in this workspace. Migration execution, administrator bootstrap against a real database, backup creation, restore verification, and Compose startup remain target-environment gates rather than silently simulated results. - The local `npm run db:migrate` smoke correctly fails closed with a redacted connection error (`ECONNREFUSED 127.0.0.1:5432`) when PostgreSQL is absent; it does not mark the database as migrated. ## 2026-07-13 — Login gate visual fix - The login panel was rendered together with the workbench because `.workbench-grid { display: grid; }` overrode the browser's default `[hidden]` rule. - Added an explicit global `[hidden] { display: none !important; }` rule and bumped the stylesheet cache version. The login state now hides the workbench instead of layering it behind the login card. ## 2026-07-13 — Automatic environment loading findings - The UI uses relative fetches for `/api/auth/login`, `/api/auth/me`, and `/api/status`; it must be opened from the 8786 control-plane origin, not the legacy 8765 parser-only server. - `control-plane/src/config.ts` reads `process.env` but did not load a project `.env` itself. The 8786 process inherited no external Agent variables, so its status correctly reported `external_service_not_configured` even though the 8765 service had the private key. - The local private env file contains parser-oriented variables including a `PORT` value for 8765; it cannot be reused as the root control-plane env without overriding the control-plane port and origin. - The safe project boundary is a root ignored `.env` with local control-plane defaults and the private external Agent values, plus `--env-file=.env` on all root control-plane scripts. `.env.example` and `.env.production.example` remain placeholder templates. ## 2026-07-13 — Automatic environment loading result - The project now has one local control-plane startup command: `npm run dev`; it loads the root `.env` automatically and listens on 8786 with the correct `APP_ORIGIN`. - The parser-only service remains separate at 8765 and uses `mock-business-system/.env`; it is not a login endpoint. - After restart, the control-plane external Agent probe authenticated successfully and the database readiness probe remained healthy. - Root `.env` is local-only and ignored. The production replacement point is `.env.production`/Docker Compose secret injection, not source code or frontend storage. ## 2026-07-13 — Parse stall diagnosis and local hardening - The stuck task `TASK-20260713032610-K5AWZxc` remained `parse_running` with no persisted `parse_response` or `operation`; the control-plane process had an established HTTPS connection to the Agent while the task stream did not complete. - The local Agent client could wait through three separate 120-second phases, exceeding the five-minute task lease. It also waited for the HTTP body to close even after terminal SSE events and did not explicitly handle `data: [DONE]`. - The parse worker could reclaim the same expired task while its original in-process worker was still active; late results were not tied to the lease owner and could overwrite a newer attempt. - `/api/status` previously used a state-changing session-creation POST as a health probe. It now uses the provider's read-only `GET /health` endpoint, caches the result for 30 seconds, and the browser refreshes status every 30 seconds. - Local fixes now enforce a 180-second total parse budget, honor terminal SSE events, exclude active task IDs from local claims, persist the running message, and reject stale parse results by lease owner. The cancelled diagnostic task was not automatically retried. ## 2026-07-13 — Delete task visibility diagnosis - The delete button's request path was wired: `deleteSelectedTask()` appends a cancellation log, calls `POST /api/tasks/:taskId/cancel`, and removes the task from the in-memory card list. - The control-plane cancellation endpoint intentionally keeps the durable row and marks it `cancelled` for audit; however, `listTasks()` returned cancelled rows in its default response. - The cancellation event triggers the page's SSE `syncRemoteTasks()` refresh. That refresh repopulated the just-removed cancelled task, making a successful delete look like a no-op. - The fix preserves cancelled rows in the database and only hides them from the default active-task list; explicit status queries can still request cancelled history. ## 2026-07-13 — Parse Worker root cause and end-to-end resolution - The repeated five-minute reclaims were not a database lock or a missing Agent response. A real Agent request completed in about 50 seconds, but the control plane attempted to insert an empty string into `task_events.actor_user_id`, a PostgreSQL UUID column. SQLSTATE `22P02` rolled the entire result transaction back, leaving the task in `parse_running` until its lease expired. - System-generated task events now persist a null actor. A regression test directly verifies this UUID boundary. - Parse claims now create durable `task_attempts`; the Worker has an independent watchdog and propagated abort signal; hanging SSE cancellation is bounded; expired leases are converted to a durable `parse_failed` terminal state and are never automatically resubmitted to Agent. - Parser outcomes are retried only for database persistence, not by calling Agent again. Lease-owner checks reject late results and the queue is single-flight within the process. - Initial SSE history replay no longer causes a `/api/tasks` request storm: the browser starts from the highest known event ID, the server honors `Last-Event-ID`, and concurrent refreshes are coalesced. - Terminal tasks clear leases, cancelled tasks close any running attempts, and migrations `002_parse_reliability` and `003_terminal_lease_cleanup` reconcile existing rows. - `/api/status` now distinguishes reachability from authentication and uses a successful real parse as non-mutating authentication evidence. ## 2026-07-13 — Real Agent and backend completion evidence - Real task `TASK-20260713043752-_GgRRXg` created one external Agent session, consumed a terminal SSE stream with 2695 events, returned `agent_parse_passed`, and produced the expected test operation for product `遇见老挝`, departure `2026-08-11`, passenger/room/price counts, OP, and sales fields. - The successful task used exactly one parse attempt. Its attempt changed from `running` to `completed`, response and request hashes were stored, the lease was cleared, and task state changed to `awaiting_confirmation`. - An isolated no-ERP-write verification then exercised the authenticated backend transitions `confirmed -> queued -> completed`; the encrypted result records `no_erp_write=true`, with complete task-event and audit chains. - Final runtime status reports `database_ready=true`, `ai_reachable=true`, `ai_authenticated=true`, and `ai_connected=true`. PostgreSQL has no active or expired parse tasks, no running attempts, no terminal leases, no duplicate attempt keys, and no waiting locks. ## 2026-07-13 — ERP duplicate execution incident findings - The incident was local, not an Agent-return failure. Audit and task events showed the same task was browser-claimed four times at approximately 10:38, 10:40, 10:40, and 10:42 UTC while ERP execution was already active. - Three defects combined: the server allowed claim from `confirmed`, `queued`, and `running`; the page sent `CREATE_TASK` to the extension before recording the server claim; and bridge-ready/heartbeat/page-refresh logic automatically resubmitted every confirmed task whose handoff status was no longer exactly `accepted`. - Result collection stopped after the first `running` update because polling selected only `handoff_status === 'accepted'`, while the backend changed the handoff status to `running`. This explains why ERP could finish while PostgreSQL and the UI remained at an earlier stage. - The old extension used only an in-memory `runningTasks` set. A content-script replay or extension service-worker restart could lose that guard and execute the same task again. - The corrected invariant is at-most-once dispatch: server transaction creates one ERP attempt and execution ID before delivery; the page sends only when `claimed=true`; the extension persists that execution ID atomically with the accepted task; and the database has a partial unique index allowing one `phase='erp'` attempt per task. - Exactly-once ERP side effects cannot be inferred after a transport crash. The plugin therefore persists `write_started` before a real write; any failure, cancellation, restart, or unknown response after that point becomes `reconciliation_pending` and cannot be retried automatically. - Terminal results are monotonic and immutable. Replayed identical updates create no event, completed/reconciliation/blocked tasks reject later running updates, and execution-result ownership is checked against both browser connection and execution ID. - Task `TASK-20260713103736-9tLJa4w` has no trustworthy captured ERP receipt because the old page stopped polling. It is intentionally marked `duplicate_execution_detected` and must be reconciled in ERP by group/order number or test marker rather than guessed as completed. - The native ERP deferred dialog callback had an old `opener` runtime error. The callback is now suppressed because explicit verification and return-to-list own that lifecycle; extension `0.3.1` currently reports zero manifest/runtime errors. - A shell-expanded SQL `$1` placeholder caused the first incident-isolation command to fail before any mutation; the corrected command succeeded. A later syntax-check command used a duplicated relative path, but the proper `node --check` and the full regression gate passed.