部署适配

This commit is contained in:
2026-08-29 15:02:51 +08:00
parent 7b5d855b09
commit c65f00c941
57 changed files with 1075 additions and 50 deletions

View File

@@ -0,0 +1,29 @@
# Project Positioning
## One-line Positioning
This project is {one-line project positioning}.
## Primary Goal
The project exists to {primary project goal}.
## Target Users / Consumers
- {primary user or consumer}
## Non-goals
This project does not aim to {non-goal or boundary}.
## Core Constraints
- {core constraint}
## Quality Bar
A good solution should {quality bar}.
## Last Reviewed
{YYYY-MM-DD}

View File

@@ -0,0 +1,17 @@
# Success Criteria
## Project Success
- {observable project-level success condition}
## Task Completion Standard
- {condition that means a task is complete}
## Quality Checks
- {verification command, review expectation, or acceptance check}
## Last Reviewed
{YYYY-MM-DD}

View File

@@ -0,0 +1,30 @@
# Concurrent Task Gate
Complete this gate before the Planning Gate.
## Invariants
- One active task owns one worktree.
- Concurrent tasks use different branches and worktrees.
- Never stash, reset, move, delete, or adopt unknown work automatically.
- Feature tasks write only their own task record and uniquely named supporting records.
- Keep the active task record present until ownership is released.
- Treat `start`, `touch`, `complete`, and `release` as serialized registry transactions; lock timeout or malformed registry state blocks the gate.
- Feature-task write boundaries in this gate supersede legacy instructions to update shared or canonical project documents.
## Required Output
- Task ID:
- Mode: Feature | Integration
- Branch:
- Worktree:
- Base commit:
- Ownership result: Claimed | Resumed | Isolated | Blocked
- Other active local tasks:
## Block Conditions
- The worktree belongs to another active task and isolation did not succeed.
- An unowned worktree contains staged, unstaged, or untracked changes.
- No reliable committed base was selected for a new worktree.
- The runtime cannot keep later Git and file operations rooted in the isolated worktree.

View File

@@ -0,0 +1,15 @@
# Context Checklist
Before planning, confirm:
- I know the task ID, mode, branch, worktree, base commit, and ownership result.
- I read the active task record and know its scope.
- I know what this project is and what it is not.
- I treat current-state as the last integrated snapshot rather than live concurrent state.
- I checked active decisions and the architecture overview.
- I identified task-specific docs that need deeper reading.
- I inspected other local task records through `task_context.py status --json`.
- I assessed code overlap separately from semantic or decision conflict.
- I reported missing peer records as unknown coordination state.
- I can name unknown, stale, or conflicting information.
- I know which updates remain task-scoped and which require Integration Gate.

View File

@@ -0,0 +1,15 @@
# Integration Gate
Use this gate to promote completed task facts into canonical project memory.
## Requirements
- Run in an exclusively owned integration worktree.
- Hold the repository integration lock.
- Verify the task commits being integrated are present.
- Review source task records, task-prefixed supporting records, promotion candidates, and semantic conflicts in read-only mode.
- Write integration notes only to the integration task's own task record or `{task_id}__<slug>.md` supporting records.
- Ask before changing architecture direction, product behavior, or accepted decisions.
- Record source task or merge commits under `Integrated Through` in `current-state.md`.
Do not resolve meaningful document conflicts with `ours`, `theirs`, or union merge rules.

View File

@@ -0,0 +1,23 @@
# Memory Index
Use this as the high-density entry point after the Concurrent Task Gate establishes task identity and worktree ownership.
## Startup Set
- Active task: `.project-docs/30-worklog/tasks/{task_id}.md`
- Project identity: `.project-docs/00-brief/project-positioning.md`
- Integrated state: `.project-docs/30-worklog/current-state.md`
- Decision list: `.project-docs/10-decisions/decision-index.md`
- System shape: `.project-docs/20-architecture/system-overview.md`
## Recall Pointers
- Evidence-heavy bugs, experiments, investigations: `.project-docs/50-evidence/evidence-index.md`
- Workflow lessons and repeated agent mistakes: `.project-docs/60-reflection/reflection-index.md`
- Pending promises, loops, timed follow-ups: `.project-docs/80-commitments/commitments.md`
- Integrated stale items: `.project-docs/90-maintenance/stale-items.md`
- Task-scoped conflicts: `.project-docs/90-maintenance/conflicts/{task_id}__<slug>.md`
## Loading Rule
Keep this file short. Add shared pointers only during Integration Gate. Feature tasks keep their working context and promotion candidates in their own task record.

View File

@@ -0,0 +1,66 @@
# Planning Gate
A coding agent must complete this gate after the Concurrent Task Gate and before writing an implementation plan.
## Peer Scope Check
Run `task_context.py status --json`. For each other owner, read only the peer task record at `Path(owner.worktree) / owner.task_record`. Use its `Scope`, `Intent And Constraints`, and `Promotion Candidates` sections to assess overlap.
Do not inspect or modify arbitrary uncommitted files in another task's worktree. Report a missing or unreadable peer record as unknown coordination state; do not silently treat it as no overlap. Code-path overlap alone is a warning. Block when semantic decisions conflict or unresolved overlap could change the plan.
## Required Output
```md
## Project Context Loaded
Task context:
- Task ID:
- Mode:
- Branch:
- Worktree:
- Base commit:
- Other active local tasks:
- Overlap or semantic-conflict assessment:
Read:
- {file path}
Relevant understanding:
- Project goal:
- Current integrated focus:
- Active task scope:
- Active constraints:
- Decisions affecting this task:
- Evidence, reflections, or commitments affecting this task:
- Files or modules likely involved:
- Unknowns, stale docs, or conflicts:
Gate result:
- Passed or Blocked
```
## Pass Criteria
The gate passes only when:
- task identity and worktree ownership are resolved
- the active task record exists and matches the owner task ID
- required documents were read
- task-relevant decisions were checked
- relevant evidence, reflection, and commitment indexes were checked when applicable
- other active local task scopes were assessed
- stale, unknown, or conflicting context was called out
- the plan respects project positioning and constraints
## Block Criteria
Block planning when:
- worktree ownership is unresolved
- an unowned worktree is dirty and has not been explicitly adopted by a human
- required worktree isolation failed or later operations cannot remain rooted there
- required documents are missing or a concurrency upgrade is incomplete
- current integrated state conflicts with the user request
- an existing decision appears to be violated
- semantic decisions conflict across active tasks
- the task changes project positioning or architecture without human confirmation

View File

@@ -0,0 +1,7 @@
# Read Before Coding
Before editing code, verify that the implementation plan passed both the Concurrent Task Gate and Planning Gate. Confirm the task ID, branch, worktree, owner, and active task record still match.
If the plan is stale, ownership changed, or new peer scope affects the plan, return to `read-before-planning.md`. Keep every later file and Git operation rooted in the owned worktree.
Read the source files directly related to the target modules. Record feature progress, discovered constraints, verification, and promotion candidates in `.project-docs/30-worklog/tasks/{task_id}.md`; leave canonical project memory to Integration Gate.

View File

@@ -0,0 +1,24 @@
# Read Before Planning
Before writing any coding plan, follow this order:
1. Run the Concurrent Task Gate.
2. Read memory-index.md.
3. Read the active task record at `.project-docs/30-worklog/tasks/{task_id}.md`.
4. Read project-positioning.md.
5. Read current-state.md as the integrated snapshot.
6. Read decision-index.md and system-overview.md.
7. Inspect other locally active task scopes.
Then read additional files when relevant:
- Architecture or refactor task: `.project-docs/20-architecture/module-map.md` and `.project-docs/20-architecture/data-flow.md`
- Product or behavior task: `.project-docs/40-domain/business-rules.md` and `.project-docs/00-brief/success-criteria.md`
- Ambiguous terms: `.project-docs/40-domain/glossary.md`
- Decision-sensitive task: referenced accepted ADRs in `.project-docs/10-decisions/`
- Evidence-heavy bug, investigation, or experiment: `.project-docs/50-evidence/evidence-index.md`
- Repeated workflow issue, skipped gate, or skill/script candidate: `.project-docs/60-reflection/reflection-index.md`
- Follow-up, loop, timed check, or restart-point task: `.project-docs/80-commitments/commitments.md`
- Suspicious integrated context: `.project-docs/90-maintenance/stale-items.md`
Treat shared files as the last integrated snapshot, not as live state from concurrent feature tasks. Do not write a plan until both the Concurrent Task Gate and Planning Gate pass.

View File

@@ -0,0 +1,33 @@
# ADR-{number}: {decision title}
## Status
Proposed
## Date
{YYYY-MM-DD}
## Context
{context that made the decision necessary}
## Decision
{decision made}
## Rationale
{why this option was chosen}
## Consequences
- {positive or negative consequence}
## Supersedes
- {older ADR or decision, if any}
## Related
- {related doc or source file}

View File

@@ -0,0 +1,22 @@
# Decision Index
## Active Decisions
| ID | Decision | Status | Date | Applies To | Detail |
|---|---|---|---|---|---|
## Superseded Decisions
| ID | Decision | Superseded By | Date |
|---|---|---|---|
## Decision Criteria
Create or update an ADR when a choice affects:
- project positioning
- architecture boundaries
- public behavior
- data model
- long-term maintenance
- user-facing workflow

View File

@@ -0,0 +1,18 @@
# Data Flow
## Primary Flows
| Flow | Source | Destination | Notes |
|---|---|---|---|
## State Ownership
- {state owner or persistence rule}
## External Interfaces
- {API, file, service, or user-facing boundary}
## Last Updated
{YYYY-MM-DD}

View File

@@ -0,0 +1,18 @@
# Module Map
## Source Layout
| Path | Responsibility | Owner Notes |
|---|---|---|
## Dependency Direction
- {dependency direction rule}
## Risky Or Sensitive Areas
- {module or path that needs extra care}
## Last Updated
{YYYY-MM-DD}

View File

@@ -0,0 +1,22 @@
# System Overview
## Current Architecture
{short description of the current system shape}
## Main Components
| Component | Responsibility | Notes |
|---|---|---|
## Important Boundaries
- {boundary that future work should respect}
## Related Decisions
- {ADR reference}
## Last Updated
{YYYY-MM-DD}

View File

@@ -0,0 +1,36 @@
# Current State
This file is the integrated default-branch snapshot. Feature tasks record progress in `30-worklog/tasks/{task_id}.md` and propose canonical changes for the Integration Gate. Feature tasks must not rewrite this file; it changes only in integration mode.
## Integrated Through
- {source task or merge commit}
## Current Focus
The project is currently focused on {current focus}.
## Recently Completed
- {YYYY-MM-DD}: {completed work summary}
## In Progress
- {in-progress item}
## Next Recommended Steps
1. {next recommended step}
2. {next recommended step}
## Open Questions / Blockers
- {open question or blocker}
## Risky Areas
- {risky area}
## Last Updated
{YYYY-MM-DD}

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,10 @@
# Task History
## Completed Tasks
| Date | Task | Outcome | Docs Updated |
|---|---|---|---|
## Notes
This is legacy integrated history. Feature tasks must not append here. Record new work in `30-worklog/tasks/{task_id}.md`; an integration workflow may render or summarize accepted history later.

View File

@@ -0,0 +1,35 @@
# Task: {title}
## Identity
- Task ID: {task_id}
- Mode: {mode}
- Branch: {branch}
- Worktree: {worktree}
- Base commit: {base_commit}
- Owner: {owner}
- Status: Planning
## Scope
- {scope}
## Intent And Constraints
- {intent_or_constraint}
## Outcome
- Not completed.
## Verification
- Not run.
## Follow-ups
- None recorded.
## Promotion Candidates
- None recorded.

View File

@@ -0,0 +1,66 @@
# Task: Verify ACK image build and middleware requirements
## Identity
- Task ID: 20260828-ack-deploy-guide-8c1d
- Mode: Integration
- Branch: main
- Worktree: D:\Datas\OthersProjects\LWLT-AIBOT
- Base commit: 7b5d855b093af39bf834fab4f41f41b37be1170d
- Owner: codex
- Status: Completed
## Scope
- Repair the repository's missing `.project-docs` templates with the official non-overwriting initializer after explicit user approval.
- Read-only verification of the existing Docker build, runtime entrypoint, production configuration, persistence, artifact storage, health probes, and ACK/ACR deployment constraints.
- Provide a build-and-push command and a minimal middleware inventory; do not deploy, push an image, or read secrets.
- Follow up on the production deployment by fixing Chrome extension recognition and bridge injection for the user-confirmed control-plane origin `https://lwlt.nianxx.cn`, then publish a synchronized versioned ZIP.
## Intent And Constraints
- Preserve all existing work and do not read `.env` or `LianSyn-platform/.env`.
- Distinguish repository-confirmed requirements from deployment recommendations and unverified assumptions.
- Treat the Chrome ERP extension as a client-side component, not an ACK workload.
- Allow only the exact production control-plane origin; do not broaden content-script injection to arbitrary HTTPS sites.
- Do not reload the user's browser extension, deploy services, or access ERP as part of the source/release fix.
- Do not create sub-agents.
- Reclaim the same task in Integration mode after the initialized canonical templates caused the Feature-mode drift check to block; the user explicitly approved the forced ownership recovery.
## Outcome
- The official initializer added 34 previously missing `.project-docs` template files without overwriting existing files, and `check_project_docs.py` passed afterward.
- The existing multi-stage `Dockerfile` builds the TypeScript control plane and packages Node.js 22, LibreOffice Writer, Noto CJK fonts, compiled migrations, and the platform adapter into one runtime image exposing port 8786.
- For ACK, the recommended image command is a `docker buildx build --platform <node-architecture> ... --push .` command targeting ACR. ACR login and an ACK image-pull mechanism are separate prerequisites.
- Required production state is PostgreSQL. The current production template selects OSS artifact storage; Redis, Kafka, RabbitMQ, MongoDB, Elasticsearch, and a separate platform-adapter service are not dependencies in the active code/configuration.
- Compose runs migrations before the server, while the image default `CMD` starts only the server. ACK therefore needs a single-run migration Job before the Deployment; starting an unmigrated Deployment fails closed on the required schema version.
- Use an ACK Ingress/managed load balancer and TLS instead of the Compose-only Caddy service. Configure the long-lived HTTP path for SSE behavior.
- Start with one application replica: AgentBus listeners and SSE event emission are process-local, so horizontally scaling the current service would create multiple listeners and incomplete cross-pod event propagation without an explicit coordination design.
- The canonical templates remain neutral placeholders. Deployment findings were retained in this integration task record and were not promoted into accepted architecture without a separate architecture-memory decision.
- Reproduced the reported Popup symptom with an agent-runnable VM harness: localhost was recognized while `https://lwlt.nianxx.cn` returned `未打开业务系统页面`.
- Updated Popup URL matching, background tab discovery, Manifest host permission/content script matching, extension/runtime version declarations, platform minimum version, lifecycle mapping, release gate, tests, and extension documentation to `0.5.158`.
- Archived the superseded `0.5.157` ZIP and manifest snapshot under `archive/releases/2026-08-29/`; generated `dist/ltjt-order-assistant-0.5.158.zip` with SHA-256 `99ba7b29d1d4418cb02fcbb345aeaf8494877dbc397ae8592b4e3fdb68b4cf9d`.
- Verified the new archive contains exactly 19 source files and every entry matches the active extension source byte-for-byte.
- Repaired Windows-reachable verification gaps encountered by the mandated gates: `.project-docs` is now an allowed governed root, text artifact comparison normalizes CRLF/LF only for explicit text extensions, `ajv` and `ajv-formats` are declared direct dev dependencies, URL fixtures use `fileURLToPath`, and Unix fake-converter success tests are explicitly skipped on Windows while remaining active on Linux/Docker.
## Verification
- Read: `README.md`, root planning files, business registry, `Dockerfile`, `.dockerignore`, `docker-compose.yml`, `package.json`, `.env.production.example`, infrastructure scripts, control-plane README, configuration, database, migration, server, artifact-store, and AgentBus-related source.
- Confirmed `.dockerignore` excludes `.env` and `.env.*` from the image build context.
- Confirmed health endpoints `/health/live` and `/health/ready`, service port 8786, required migration `014_task_input_attachments`, PostgreSQL pool usage, OSS production configuration, bundled document conversion, and lack of Redis/MQ/search dependencies.
- The production-origin regression first failed with `false !== true`, then passed after the fix; the original Popup black-box no longer produced `未打开业务系统页面` for `https://lwlt.nianxx.cn`.
- Final release verification includes repository hygiene 9/9, TypeScript check, control-plane tests, legacy tests, build, JavaScript syntax checks, package/source byte comparison, and `git diff --check`.
- Final gate counts: repository hygiene 9/9; control-plane 123 passed, 0 failed, 4 Windows-only skips for Unix fake-converter fixtures; legacy 249/249; TypeScript check and build both exited 0.
- Cross-checked current ACR build/push, private image pull, ACK ALB/Nginx Ingress, and long-connection guidance against Alibaba Cloud official documentation on 2026-08-28.
- The Feature-mode boundary check correctly blocked on initialized canonical templates; after explicit approval, official `release --force` and `start --mode integration --adopt-existing` commands established matching Integration ownership and lock.
- No Docker build, image push, Kubernetes mutation, database migration, browser-extension reload, ERP access/write, or deployment was executed.
## Follow-ups
- If requested, create task-scoped ACK manifests or a Helm chart containing Namespace, Secret/ConfigMap references, migration Job, single-replica Deployment, ClusterIP Service, Ingress, probes, and ACR pull configuration.
- Load or distribute `dist/ltjt-order-assistant-0.5.158.zip`, then refresh `https://lwlt.nianxx.cn` and confirm the live handshake reports `0.5.158`; runtime reload remains a separate user/operations action.
- Reconcile stable project facts from the populated root project-memory files into canonical `.project-docs` only through an explicitly scoped architecture-memory integration task.
## Promotion Candidates
- Target: `.project-docs/20-architecture/system-overview.md` and `.project-docs/30-worklog/current-state.md` through a separately confirmed architecture-memory integration. Proposal: record PostgreSQL as the sole required database/state middleware, OSS as the production artifact backend, the client-side Chrome extension boundary, the migration-before-start requirement, and the current single-replica constraint. Evidence: active Docker, Compose, production config, control-plane source, and README. Future impact: prevents incomplete ACK deployments and unsafe horizontal scaling. Semantic conflicts: canonical files are newly initialized placeholders; root project memory is populated but has not been reconciled into `.project-docs`. Human confirmation: required before promotion because this would establish canonical architecture memory.

View File

@@ -0,0 +1,13 @@
# Business Rules
## Durable Rules
- {business or product rule}
## Open Questions
- {rule that needs human confirmation}
## Last Reviewed
{YYYY-MM-DD}

View File

@@ -0,0 +1,4 @@
# Glossary
| Term | Meaning | Notes |
|---|---|---|

View File

@@ -0,0 +1,12 @@
# Evidence Index
Use this index for searchable, traceable evidence records.
| Date | Topic | Status | Source | Detail |
|---|---|---|---|---|
## When To Add Evidence
Add a topic file when a task depends on logs, commits, test output, external docs, bug reproduction, experiments, or postmortem-level reasoning.
Keep task progress in `30-worklog/`; keep reusable workflow lessons in `60-reflection/`.

View File

@@ -0,0 +1,35 @@
# Evidence Topic: {short title}
## Metadata
- Date:
- Status: Active | Resolved | Superseded | Stale
- Scope:
- Confidence: Fact | Inference | Hypothesis
- Source:
- Last verified:
- Stale trigger:
## Question
What needed evidence?
## Evidence
- Commit:
- Files:
- Commands:
- Logs:
- External source:
## Finding
What does the evidence support?
## Impact
What future planning or implementation should this affect?
## Open Items
-

View File

@@ -0,0 +1,12 @@
# Reflection Index
Use this index for second-order workflow lessons.
| Date | Reflection | Trigger | Action | Detail |
|---|---|---|---|---|
## When To Reflect
Create a reflection only when work reveals a reusable lesson: skipped gates, repeated mistakes, durable debugging patterns, ineffective plans, human corrections, or candidates for new scripts or skills.
Routine task completion belongs in `30-worklog/task-history.md`.

View File

@@ -0,0 +1,54 @@
# Reflection: {short title}
## Trigger
What happened?
## Expected Behavior
What should the agent or workflow have done?
## Actual Behavior
What happened instead?
## Root Cause
Classify the cause:
- Missing trigger
- Weak gate
- Stale docs
- Unclear ownership
- Missing script
- Human decision not promoted
- Agent ignored context
- Other:
## Evidence
- Commit:
- Files:
- Session/thread:
- Command output:
- Docs involved:
## Lesson
What should future agents learn?
## Action
Choose one:
- Update docs
- Update gate
- Create script
- Create/update skill
- Add check/eval
- Ask human to decide
- No action
## Promotion
Should this become a rule, ADR, architecture note, task-history entry, skill change, or script?

View File

@@ -0,0 +1,10 @@
# Skill Candidates
Track repeated workflow lessons that may deserve a reusable skill, script, or stronger gate.
| Date | Candidate | Evidence | Proposed Action | Status |
|---|---|---|---|---|
## Promotion Rule
If the same reflection pattern appears repeatedly or prevents a serious mistake, propose a skill update, new skill, script, or deterministic check.

View File

@@ -0,0 +1,10 @@
# Commitments
Track future-facing memory: promised follow-ups, unfinished loops, timed checks, and restart points.
| Date | Commitment | Trigger / Due | Owner | Status | Next Action |
|---|---|---|---|---|---|
## Use
Record only commitments that should affect future sessions. Routine next steps can stay in `30-worklog/current-state.md`.

View File

@@ -0,0 +1,42 @@
# Doc Update Policy
Use agent judgment and project context to decide what is durable. Do not use fixed keyword matching to decide whether information belongs in project memory.
## Feature Task Writes
Every repository-changing feature task updates `30-worklog/tasks/{task_id}.md`. Keep scope, intent, outcome, verification, follow-ups, and promotion candidates there.
When separate evidence, reflection, commitment, conflict, or decision-proposal records are useful, create uniquely named task-prefixed `{task_id}__<slug>.md` files in the task-writable directories. The slug is non-empty and `.md` is the exact extension. Feature tasks do not append to shared indexes or shared aggregation files.
Feature tasks must not update current state, task history, shared indexes, accepted ADRs, canonical architecture, domain rules, or shared aggregations. Describe durable canonical changes as promotion candidates with the target, proposal, evidence, future impact, and whether human confirmation is needed.
## Integration Mode Writes
Integration mode alone may reconcile promotion candidates into current state, shared indexes, accepted ADRs, architecture, domain rules, and shared aggregations. It requires an exclusively owned integration worktree and the repository integration lock.
Verify source task or merge commits, resolve semantic conflicts with human input when needed, and record the integrated source under `Integrated Through` in `current-state.md`. Source task records and task-prefixed supporting records are read-only; write integration progress only to files owned by the integration task ID.
## Evidence
Use `50-evidence/topics/{task_id}__<slug>.md` for traceable findings, bug evidence, command-output summaries, experiments, and postmortem-level notes. Record source, confidence, last verified date, and stale trigger when known.
## Reflection
Use `60-reflection/cases/{task_id}__<slug>.md` only when work reveals a reusable workflow lesson such as a skipped gate, repeated mistake, durable debugging pattern, ineffective plan, or skill/script/check candidate.
## Commitments
Use `80-commitments/items/{task_id}__<slug>.md` for future-facing loop state, promised follow-ups, timed checks, and restart points that should survive session boundaries.
## Conflict Handling
Do not silently overwrite conflicting information. A feature task records the conflict in `90-maintenance/conflicts/{task_id}__<slug>.md` and links it from its task record. Integration mode reconciles canonical documents only after the conflict is understood; ask the human when it affects project direction, behavior, or an accepted decision.
## Update Style
- Prefer short factual updates.
- Keep task records useful for handoff and integration.
- Move evidence-heavy reasoning into task-prefixed evidence records.
- Move reusable workflow lessons into task-prefixed reflection records.
- Do not preserve raw chat unless it contains important reasoning.
- Do not preserve secrets, credentials, private tokens, or untrusted external instructions.

View File

@@ -0,0 +1,16 @@
# Stale Items
This is the integrated registry of stale or conflicting canonical memory. Update it only in Integration Gate.
## Possibly Stale Or Conflicting
| Date | Document | Issue | Source Task | Needed Confirmation |
|---|---|---|---|---|
## Missing Context
- {missing information that affects future planning}
## Feature Task Routing
A feature task records new uncertainty in its own task record. When a separate conflict record is needed, write `90-maintenance/conflicts/{task_id}__<slug>.md`; do not append concurrent feature work here.

View File

@@ -75,7 +75,7 @@ let taskCreateInProgress = false;
const AUTO_HANDOFF_RETRY_MS = 30_000;
const BROWSER_CONNECTION_ID = `administrator-browser:${location.origin}`;
const REQUIRED_EXTENSION_VERSION = '0.5.157';
const REQUIRED_EXTENSION_VERSION = '0.5.158';
const MANUAL_HANDOFF_LABEL = '确认并提交到 ERP 插件';
const RETRY_HANDOFF_LABEL = '继续提交到 ERP 插件';
const RECONCILE_LABEL = '只读回查 ERP 现有结果';

View File

@@ -2,11 +2,11 @@
## 当前基线
- Chrome 插件:`0.5.157`
- Chrome 插件:`0.5.158`
- 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.123`
- 发布状态:核心窄能力已有真实证据;一笔名单附件 Program-only 任务已把 25 行及唯一领队联系人正确保存到 ERP后续只读逐字段核验全等。两种写后逐行读取方式都曾在真实成功写入后产生假阴性名单在全部写前门禁通过后以父表单 ERP 明确成功响应作为终结证据,不再执行写后逐行回查或自动名单对账。`0.5.155` 规定确认覆盖的指定序号同值也写;`0.5.156` 把原生游客位不足统一映射为包含名单人数和 ERP 实际上限的业务提示,原技术 blocker 仅留技术详情。`0.5.157` 新增散拼母团“整团游客信息”的 `shared_plan + visitor-list + tid-only` 窄分支,静态契约与回归已通过,仍待授权后的新版运行态只读复测。新增独立团房型/人数映射仍待实写;未验证宽能力继续失败关闭。
- 发布状态:核心窄能力已有真实证据;一笔名单附件 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 仍保持阻断。新增独立团房型/人数映射仍待实写;未验证宽能力继续失败关闭。
运行中的浏览器版本必须实时握手确认。当前制品文件名和 SHA-256 只看 [`../../../dist/release-manifest.json`](../../../dist/release-manifest.json),不从历史日志推断。

View File

@@ -0,0 +1,5 @@
# 2026-08-29 项目历史
本目录保存本日治理压缩前的完整项目记忆,只供追溯,不定义当前规则。活动计划、发现和进度仍以仓库根目录文件为准。
- `task_plan-pre-compression-production-domain.md`:加入正式控制面域名修复任务前的完整 `task_plan.md`,源文件与归档文件 SHA-256 均为 `aceca8635e0e00d5f9ddf6577299d7239ccc8c889d7fec043429d7a5037d3a8c`

View File

@@ -0,0 +1,153 @@
# 项目文件治理计划
## 当前任务:重启控制面并恢复 AgentBus 连接(已完成)
- **目标:** 按用户明确授权,优雅重启标准 8786 控制面,使刚出现断线的 AgentBus 渠道重新建立会话并通过稳定性复核。
- **安全边界:** 仅重启控制面并做只读进程、健康、数据库和 AgentBus 状态检查;不重载扩展,不创建、确认、重试或派发任务,不访问或写入 ERP不部署或外发。
### 阶段
- [x] 确认唯一监听、live/ready 当前状态,并确认在途 task 与 `accepted/running` attempt 均为 0。
- [x] 优雅停止当前进程组,并从标准 `pnpm run dev` 入口恢复唯一服务实例。
- [x] 核验数据库、Schema、迁移与 4/4 AgentBus 渠道重连,并观察连接状态保持稳定。
### 完成结论
- 重启前服务端健康接口已显示 4/4 渠道重新连接,但用户侧刚观察到断线;在途 task 与 `accepted/running` attempt 均为 0因此仍按授权执行干净重启。
- 旧进程组 `62152` 已退出;运行会话管理器随即从标准 `pnpm run dev` 入口拉起新进程组 `66218`、服务 PID `66228`没有再启动第二份实例8786 保持唯一监听。
- 新实例 live/ready、数据库、Schema、迁移 `014_task_input_attachments` 均正常;连续 25 秒共 6 次采样均为 AgentBus 总连接与 session ready 正常、4/4 渠道 ready。
- 未创建、确认、重试或派发任务,未重载扩展,未访问或写入 ERP未部署或外发。
- 重启后门禁通过:仓库治理 9/9、TypeScript、控制面 127/127、legacy 248/248、build 与 `git diff --check`
### 错误记录
- 首批并行门禁附带的一次 `/tmp` 临时健康文件清理被安全策略拒绝,导致该批结果未汇总;该操作与服务、数据库和 AgentBus 无关。跳过清理后完整门禁已重新运行并全部通过。
## 当前任务:将运行态切换到整团游客信息新版(已完成)
- **目标:** 按用户明确授权,优雅重启标准 8786 控制面并重载本地 Chrome 扩展,使 Program parser `v1.0.6`、输入契约 `0.5.123` 与扩展 `0.5.157` 在运行态生效。
- **安全边界:** 仅做进程/扩展运行态切换和只读健康、版本检查;不创建、确认、重试或派发任务,不访问 ERP 业务数据,不执行 ERP 写入、部署或外发。
### 阶段
- [x] 确认 8786 live/ready、数据库/Schema/迁移与 4/4 AgentBus 渠道正常,并确认活动任务和 `accepted/running` 在途 attempt 均为 0。
- [x] 优雅停止旧进程组并从项目标准 `pnpm run dev` 入口启动新控制面。
- [x] 核验 live/ready、唯一监听、解析器/输入契约版本,并重载扩展后确认 `0.5.157` 握手。
### 错误记录
- 旧监听停止后首次启动命令未显式传入 bundled Node PATH`pnpm` 在运行迁移前即因 `node: command not found` 退出;没有数据库或任务变更。已改为在启动进程环境中显式设置 bundled Node/fallback PATH。
- 旧标准入口在 TERM 后由其会话管理器自动拉起新进程组 `62152`;因此第二次手工启动完成迁移检查后在绑定前以 `EADDRINUSE` 安全退出。该临时实例的 AgentBus listeners 已自行关闭in-flight tasks 与 pending replies 均为 0保留自动拉起的新实例作为唯一标准运行态。
- 收敛记录后的首轮五道门禁由 bundled Node 主进程启动,但其子脚本 PATH 中没有 `node`,所以均在进入检查/测试前退出;显式加入同一 bundled Node 目录后原样重跑并全部通过。
### 完成结论
- 标准 `pnpm run dev` 进程组 `62152`、服务 PID `62164` 是 8786 的唯一监听live/ready、数据库、Schema、迁移 `014_task_input_attachments` 与 4/4 AgentBus 渠道均正常。
- 已通过已登录操作台的只读 API 确认 `confirmation_export` 运行在 `program` 模式,版本为 `ltjt-program-parser-v1.0.6`,输入契约为 `business-input-templates-0.5.123`
- 已重载精确匹配的本地“联泰下单助手”扩展;操作台握手为插件 `0.5.157` / 最低版本 `0.5.157` / 兼容桥接已连接、ERP 会话正常、最近错误为无。诊断字段“ERP 链路恢复”仍显示 `failed`,本轮未扩大授权去访问 ERP 业务页复测。
- 切换前后活动任务、在途 task 与 `accepted/running` attempt 均为 0未创建、补充、确认、重试、删除或派发任务未访问 ERP 业务数据、执行 ERP 写入、部署或外发。
- 运行态切换后规定门禁再次通过:仓库治理 9/9、TypeScript、控制面 127/127、legacy 248/248、build 与 `git diff --check`
## 当前任务:散拼母团“整团游客信息”导出接入(已完成)
- **目标:** 保留统一 `confirmation_export` action`文件类型:整团游客信息` 时明确锁定 `shared_plan`,只用母团 `tid` 调用 `orders_Visitor.asp`;现有独立团和散拼具体子单“游客名单”继续使用 `did+tid`,不得混淆范围。
- **安全边界:** 本轮只修改源码、契约、测试和本地发布物;不访问 ERP不创建、补充、重试或派发真实任务不重载扩展、重启、部署或外发。
### 阶段
- [x] 先补解析、Schema、执行计划和页内导出的失败回归固定母团 `tid-only` 与子单/独立团 `did+tid` 的分流。
- [x] 修改 Program parser、解析态/执行态契约、ERP 唯一解析与导出适配器,并保持母团只允许该窄文件类型。
- [x] 同步运营模板、`lwlt-confirmation` Skill、业务登记、mapping、平台校验与版本。
- [x] 重建运营 DOCX、Skill 包、Chrome ZIP 和发布清单,逐文件/逐页核验。
- [x] 运行规定全量门禁并收敛 planning files运行态切换仍等待另行授权。
### 已确定的设计
- 历史只读 ERP 证据确认两者共用 `/System/Business/orders_Visitor.asp`,但散拼母团使用 `tid`,独立团/散拼子单使用 `did+tid`
- 不新增业务 action`existing_refs.kind` 表达对象范围。`shared_plan` 只对单一 `visitor-list` 开放,不把“全部”或依赖子单 `did` 的其他文件类型放宽到母团。
- 当前 `program_invalid_value` 属于安全停止,但用户提示应由“不识别文件类型”改为可执行的窄能力或真正的对象定位问题。
### 错误记录
- 测试先行阶段的四组定向回归均按预期红灯Program 仍返回 `agent_parse_needs_input`Agent/插件 strict gate 仍拒绝 `shared_plan`;执行计划仍固定 `did+tid`;页内 exporter 尚无母团分支。这些红灯证明缺口完整覆盖,不属于环境或测试基础设施故障。
- 核心实现后四组回归中仅一条旧测试仍要求 blocker 包含“母团”;新规则已不再一概禁止母团,但该无类型、未唯一解析的请求仍安全阻断。测试已改为断言真实缺失项,并补充对象范围契约断言。
- bundled Python 运行官方 Skill 校验器时缺少 `PyYAML`,在生成任何新包前即停止;本机 Python 已确认具备 `PyYAML 6.0.3`,后续用它运行同一个官方校验器。
- 首次组合定向命令有两项非行为性失败:直接用 Node 执行 TypeScript 测试未加载 `tsx`,以及 lifecycle 中另一个旧位置仍断言平台最低版本 `0.5.156`。前者改用项目规定的 `--import tsx`,后者同步为 `0.5.157`
- 首次 `check:repo` 为 8/9唯一失败是治理测试仍固定运营模板版本 `0.5.122`。已同步为新 DOCX 单一源基线 `0.5.123`,其余目录、哈希、包/源码和链接检查均已通过。
- 首次 `test:control-plane` 为 126/127唯一失败是操作台测试仍固定旧 `app.js` 缓存键。已同步为 `20260828-shared-plan-visitor-export-1`;全部业务/解析测试(含新增整团游客信息用例)本轮均已通过。
- 最后一组补充语法检查首次未显式带 bundled Node PATH因当前非登录 shell 找不到 `node` 而未启动;随后改用 bundled Node 绝对路径重跑,不影响已通过的五道规定门禁。
### 完成结论
- `整团游客信息`现在确定性解析为 `confirmation_export / shared_plan / visitor-list`;母团唯一解析后只生成 `orders_Visitor.asp?tid=<tid>`,且解析态、执行态、插件门禁和页内 exporter 都拒绝其他类型、混合范围及任何子单 `did/ddid`。普通“游客名单”的独立团/具体子单 `did+tid` 路径未改变。
- 发布基线已推进为扩展 `0.5.157`、Program parser `v1.0.6`、输入契约/DOCX `0.5.123`、五 Skill `0.5.125`。扩展 ZIP SHA-256 为 `872640668c52cf1ef0135ff51ec33e3c1f5dee304434fbb58ceee4cb3518fcd7`DOCX 12/12 页视觉检查通过Skill 与扩展包均逐文件匹配源码。
- 门禁通过:仓库治理 9/9、TypeScript、控制面 127/127、legacy 248/248、build、JS/JSON/ZIP/Skill、DOCX 与 `git diff --check`。该任务完成时未访问 ERP、处理历史任务、重载扩展、重启服务、部署或外发后续运行态切换已按用户另行授权完成见本文件顶部任务。
## 当前任务:名单人数超过 ERP 容量时使用业务提示(已完成)
- **目标:** `passenger_list_import` 在 ERP 原生游客位少于名单所需人数时,不再向用户显示 `passenger_native_*` 等技术码;明确提示本次名单人数和该订单的 ERP 可录入上限。
- **安全边界:** 原技术 blocker 继续保留在技术详情中;仍在父表单保存前失败关闭并保持 `no_erp_write=true`。不修改或重试历史任务,不访问 ERP不重载扩展、重启或部署。
### 阶段
- [x] 先补回归固定“16 个游客位 / 25 人名单”的业务错误码、中文提示和技术详情保留规则。
- [x] 修改插件错误映射并同步版本、平台最低版本、mapping、业务登记与发布门槛。
- [x] 生成版本化 ZIP、更新发布清单并运行规定全量门禁。
### 错误记录
- 新回归先按预期红灯,证明旧后台没有容量业务映射;实现后转绿。一次组合门禁因 PATH 只作用于首条命令而未启动后续 Node改为在受控 shell 内导出 bundled Node PATH 后完整通过。
### 完成结论
- `passenger_native_target_row_count_not_reached:16:25` 及 DOM 同类码现在归并为 `erp_passenger_count_limit_exceeded`,用户看到“本次名单共 25 人,超过该独立团在 ERP 中最多可录入的 16 人”;原始 blocker 仍在技术详情。
- 容量失败仍发生在父表单保存前,保持 `no_erp_write=true`。扩展、平台最低版本和 mapping 已推进到 `0.5.156`;新 ZIP SHA-256 为 `9a0d4941728a49a40a8734c560b4dfcb3f5026756ffb46af3f32f38fbbac652a`,旧 `0.5.155` ZIP/清单已归档。
- 门禁通过:仓库治理 9/9、TypeScript、控制面 126/126、legacy 246/246、build、JS/JSON/ZIP 与 diff 检查。未处理历史任务、访问 ERP、重载扩展、重启或部署。
## 当前任务:名单覆盖确认后始终覆写(已完成)
- **目标:** `passenger_list_import` 收到 `full_replace + confirmed=true` 后,不再根据 ERP 当前游客值判定“无变化”附件中所有指定序号都执行原生覆写和父表单保存ERP 明确成功即完成。
- **安全边界:** 未确认时,任一目标序号已占用(包括同值)仍要求覆盖确认;继续保留附件/Program-only、唯一对象、结构、游客与领队写前投影及 ERP 明确成功响应门禁。不处理历史任务,不访问 ERP不重载扩展或重启服务。
### 阶段
- [x] 先补失败回归:确认覆盖的同值行必须进入 changed/write 集合;未确认的同值占用行仍要求确认。
- [x] 修改合并计划、版本和业务契约,确认覆盖不再产生 `passenger_import_no_changes`
- [x] 扩展、平台最低版本和 mapping 推进到 `0.5.155`;旧 `0.5.154` ZIP/清单已归档,新 ZIP 已生成。
- [x] 运行规定全量门禁并收敛发布 SHA、进度和完成结论。
### 错误记录
- 首次补充“未确认同值占用行”回归时,一个多文件补丁因上下文未精确命中而整体未应用;拆成精确小补丁后完成,没有产生部分修改。
- 首轮 `check:repo` 发现根 `task_plan.md` 为 17304 字节,超过 16 KiB。已先完整冻结到 [2026-08-28 计划快照](archive/project-history/2026-08-28/task_plan-pre-compression-confirmed-overwrite.md),再语义压缩本文件;历史未丢失。
### 完成结论
- 合并计划先区分授权而非“值是否相同”:未确认且目标序号已占用时统一要求覆盖确认;`full_replace + confirmed=true` 后,全部附件指定序号都进入 changed/write 集合,同值也执行 `DaoRuDones`、写前完整投影和父表单保存。
- 父表单 ERP 明确成功即完成,仍不执行写后逐行名单回查;其他 lifecycle action 的回查规则未改变。
- 当前扩展为 `0.5.155`ZIP SHA-256 为 `3159e6700fcb9aea6d18099c5c84bccf2578de6bceaf3b7e2f369ec44ff66574``0.5.154` ZIP 与清单已日期化归档。
- 门禁通过:仓库治理 9/9、TypeScript、控制面 125/125、legacy 245/245、build、JS/JSON/ZIP 与 `git diff --check`。未处理历史任务、访问 ERP、重载扩展、重启或部署。
## 最近完成:名单明确成功响应终结与 8786 重启
- `passenger_list_import` 在全部写前门禁通过后只调用一次父表单原生保存ERP 明确成功即生成 `passenger_list_explicit_server_success` 完成凭据,不执行写后逐行回查或自动名单对账。
- 控制面只对名单 action 接受上述专用凭据,其他 lifecycle action 仍要求真实 fresh requery。
- 用户授权后,旧 8786 进程组 `13643` 已优雅退出;新标准入口进程组 `29571`、服务 PID `29588` 成为唯一监听live/ready、数据库、Schema、迁移 014 与 4/4 AgentBus 渠道正常。
- 该次运行态仍是 `0.5.154` 最低版本;本次 `0.5.155` 尚未重载扩展或重启服务。
## 最近有效里程碑
- 两类名单业务固定为 Program-only先以文字识别业务并等待单个 `.xls/.xlsx`,附件到齐后才解析;第一行忽略、第二行固定 14 列表头,规范化为 13 列 canonical TSV。
- 独立团 16 行、散拼子单 31 行只是初始合并基线5000 行是技术上限;未指定行保留,已占用目标行需要覆盖确认。
- 唯一严格`领队`备注行生成 `passenger_list.leader_contact`,并在父表单保存前投影到 `jj_lianxiren/jj_dianhua`;多行、缺姓名/电话或结构不一致均失败关闭。
- 当前运营模板单一源为 `agent设计规范/templates/business-input-templates.md`;运营 DOCX、五 Skill 及其发布物保持各自现有基线,本次只改变 Chrome 执行策略。
## 相邻待处理项
- AgentBus OSS 附件曾因本机 DNS 返回私网/保留地址而被 SSRF 门禁拒绝;与本次平台手工附件和名单覆写规则独立。后续若处理,应采用受控公有 DNS/主机白名单回退,不能全局放宽私网阻断。
## 历史与持续边界
- 本次压缩前完整计划见 [2026-08-28 快照](archive/project-history/2026-08-28/task_plan-pre-compression-confirmed-overwrite.md);更早快照见同目录 README 和 `archive/project-history/`,均只供追溯,不定义当前规则。
- 当前发布版本、文件名和 SHA-256 只以 [发布清单](dist/release-manifest.json) 为准。
- 真实 ERP 写入、任务 mutation、扩展重载、服务重启、部署和外部发送仍需用户另行明确授权。

View File

@@ -0,0 +1,6 @@
# 2026-08-29 发布归档
本目录只保存被新版本替代的历史发布物,不定义当前运行规则。当前制品文件名和 SHA-256 只以根目录 [`../../../dist/release-manifest.json`](../../../dist/release-manifest.json) 为准。
- `release-manifest-0.5.157.json``0.5.157 / 0.5.125 / 0.5.123` 交付基线的历史清单快照。
- `ltjt-order-assistant-0.5.157.zip`:被 `0.5.158` 的正式控制面 `https://lwlt.nianxx.cn` 精确域名白名单支持替代;保留用于恢复和追溯。

View File

@@ -0,0 +1,69 @@
{
"manifest_version": 1,
"generated_on": "2026-08-28",
"baselines": {
"chrome_extension": "0.5.157",
"skills": "0.5.125",
"business_instruction_docx": "0.5.123",
"agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories"
},
"artifacts": [
{
"kind": "chrome_extension",
"version": "0.5.157",
"path": "dist/ltjt-order-assistant-0.5.157.zip",
"source": "chrome-extension/ltjt-order-assistant",
"sha256": "872640668c52cf1ef0135ff51ec33e3c1f5dee304434fbb58ceee4cb3518fcd7"
},
{
"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": "8cc5d94d97abbc68388bf60d4b46ce4cb1aa947dfb174831c0ad2afc421002f0"
},
{
"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.123",
"path": "dist/老挝联泰AI指令表-0.5.123.docx",
"source": "agent设计规范/templates/business-input-templates.md",
"builder": "tools/build_business_instruction_docx.py",
"sha256": "8d94b1f4e3d1cfccf62c1b7d420cbcebf8c915844ae95fa90840963b32b1cccb",
"source_sha256": "8db16b3dce7e3dd8aa88a6273a9a90bbc3c98a087cb863bf0eeaaa5bb3a371ca",
"builder_sha256": "c162884210da22e3b78368749b66f1f177df5e9fc6155f4fd9954bd516d187f1"
}
]
}

View File

@@ -11,9 +11,11 @@ Chrome Manifest V3 扩展,在用户已登录的 LTJT ERP 页面内执行经过
## 当前版本
当前源码版本为 `0.5.157`。版本化 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.158`。版本化 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.157 当前重点:
0.5.158 当前重点:
- 正式控制面 `https://lwlt.nianxx.cn` 现在进入精确业务系统白名单Popup、后台标签页发现、Manifest host permission 和 content script 注入保持同一 origin。相似域名、子域名及其他 HTTPS 站点仍不允许注入。
- 团队文件导出新增散拼母团“整团游客信息”窄分支:解析与唯一定位必须明确 `shared_plan`,只能单独导出 `visitor-list`,且源请求只携带母团 `tid`;任何子单 `did/ddid`、其他文件类型或混合范围都在请求前失败关闭。独立团和散拼具体子单“游客名单”的既有 `did+tid` 路径保持不变。

View File

@@ -55,6 +55,7 @@ const TEAM_SINGLE_PAGE_ACTIONS = new Set([
let storageMutationQueue = Promise.resolve();
const ERP_ORIGIN = 'https://lwlt.hisy.cc';
const ERP_MAIN_URL = `${ERP_ORIGIN}/System/Mainlt.asp`;
const PRODUCTION_PLATFORM_ORIGIN = 'https://lwlt.nianxx.cn';
const LOCAL_PLATFORM_PORTS = new Set(['', '8765', '8786']);
const ERP_PERMISSION_ORIGIN = `${ERP_ORIGIN}/*`;
const ERP_KEEPALIVE_ALARM_NAME = 'ltjt-erp-session-keepalive';
@@ -198,6 +199,7 @@ function throwIfTaskCancelled(taskId) {
function isBusinessSystemUrl(url) {
try {
const parsed = new URL(String(url || ''));
if (parsed.origin === PRODUCTION_PLATFORM_ORIGIN) return true;
return parsed.protocol === 'http:'
&& ['localhost', '127.0.0.1'].includes(parsed.hostname)
&& LOCAL_PLATFORM_PORTS.has(parsed.port);
@@ -1428,6 +1430,7 @@ async function readErpSessionStatus() {
async function injectBridgeIntoBusinessTabs() {
const tabs = await chrome.tabs.query({
url: [
'https://lwlt.nianxx.cn/*',
'http://localhost/*',
'http://127.0.0.1/*'
]

View File

@@ -5958,7 +5958,7 @@
}
window.LTJTOrderAssistant = {
version: '0.5.157',
version: '0.5.158',
resolveNativeListSearchValues,
lookupKeywordMatchesText,
inspectLifecycleSearchCriteria: lifecycleSearchCriteria,

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "联泰下单助手",
"version": "0.5.157",
"version": "0.5.158",
"description": "在已登录 LTJT ERP 页面内规划并受控执行联泰 ERP 业务操作。",
"permissions": [
"activeTab",
@@ -14,6 +14,7 @@
],
"host_permissions": [
"https://lwlt.hisy.cc/*",
"https://lwlt.nianxx.cn/*",
"http://localhost/*",
"http://127.0.0.1/*",
"http://localhost:8786/*",
@@ -22,6 +23,7 @@
"content_scripts": [
{
"matches": [
"https://lwlt.nianxx.cn/*",
"http://localhost/*",
"http://127.0.0.1/*",
"http://localhost:8786/*",

View File

@@ -2,6 +2,7 @@
const BUSINESS_SOURCE = 'LIANSYN_PLATFORM';
const EXTENSION_SOURCE = 'LTJT_ORDER_ASSISTANT_EXTENSION';
const PRODUCTION_PLATFORM_ORIGIN = 'https://lwlt.nianxx.cn';
const $ = (selector) => document.querySelector(selector);
@@ -19,7 +20,15 @@ function setBadge(text, mode = '') {
}
function isBusinessSystemUrl(url) {
return /^http:\/\/(localhost|127\.0\.0\.1)(?::(?:8765|8786))?\//i.test(url || '');
try {
const parsed = new URL(String(url || ''));
if (parsed.origin === PRODUCTION_PLATFORM_ORIGIN) return true;
return parsed.protocol === 'http:'
&& ['localhost', '127.0.0.1'].includes(parsed.hostname)
&& ['', '8765', '8786'].includes(parsed.port);
} catch (error) {
return false;
}
}
function friendlyError(error) {

View File

@@ -919,7 +919,7 @@
window.LTJTOrderAssistant = {
...(window.LTJTOrderAssistant || {}),
version: '0.5.157',
version: '0.5.158',
openTeamBatchForm,
pingTeamBatchFrame,
preflightTeamBatchNative,

View File

@@ -601,7 +601,9 @@ test('backend-attached export requires frozen hashes and explicit no-delivery ev
assert.equal(successReceiptFromResult(mismatchedCount), null);
});
test('confirmation export archives visitor XLS, delivers real XLSX, and converts other types to PDF', async () => {
test('confirmation export archives visitor XLS, delivers real XLSX, and converts other types to PDF', {
skip: process.platform === 'win32' ? 'Unix fake converter fixture is not executable through Windows execFile.' : false
}, async () => {
const temporaryDirectory = await mkdtemp(join(tmpdir(), 'ltjt-selective-conversion-test-'));
const converterPath = join(temporaryDirectory, 'fake-soffice.sh');
await writeFile(converterPath, [

View File

@@ -46,7 +46,9 @@ test('conversion failure falls back to the source file', async () => {
assert.deepEqual(result.content, content);
});
test('a valid converter output replaces the source with a PDF', async () => {
test('a valid converter output replaces the source with a PDF', {
skip: process.platform === 'win32' ? 'Unix fake converter fixture is not executable through Windows execFile.' : false
}, async () => {
const temporaryDirectory = await mkdtemp(join(tmpdir(), 'ltjt-converter-test-'));
const converterPath = join(temporaryDirectory, 'fake-soffice.sh');
const fontConfigFile = join(temporaryDirectory, 'fonts.conf');
@@ -85,7 +87,9 @@ test('a valid converter output replaces the source with a PDF', async () => {
}
});
test('HTML-in-XLS visitor sources become real XLSX workbooks', async () => {
test('HTML-in-XLS visitor sources become real XLSX workbooks', {
skip: process.platform === 'win32' ? 'Unix fake converter fixture is not executable through Windows execFile.' : false
}, async () => {
const temporaryDirectory = await mkdtemp(join(tmpdir(), 'ltjt-converter-xlsx-test-'));
const converterPath = join(temporaryDirectory, 'fake-soffice.sh');
await writeFile(converterPath, [
@@ -140,7 +144,9 @@ test('visitor XLSX conversion failure remains a source fallback', async () => {
assert.deepEqual(result.content, content);
});
test('wide Word HTML tables are scaled to the printable width before conversion', async () => {
test('wide Word HTML tables are scaled to the printable width before conversion', {
skip: process.platform === 'win32' ? 'Unix fake converter fixture is not executable through Windows execFile.' : false
}, async () => {
const temporaryDirectory = await mkdtemp(join(tmpdir(), 'ltjt-converter-html-test-'));
const converterPath = join(temporaryDirectory, 'fake-soffice.sh');
await writeFile(converterPath, [

BIN
dist/ltjt-order-assistant-0.5.158.zip vendored Normal file

Binary file not shown.

View File

@@ -1,8 +1,8 @@
{
"manifest_version": 1,
"generated_on": "2026-08-28",
"generated_on": "2026-08-29",
"baselines": {
"chrome_extension": "0.5.157",
"chrome_extension": "0.5.158",
"skills": "0.5.125",
"business_instruction_docx": "0.5.123",
"agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories"
@@ -10,10 +10,10 @@
"artifacts": [
{
"kind": "chrome_extension",
"version": "0.5.157",
"path": "dist/ltjt-order-assistant-0.5.157.zip",
"version": "0.5.158",
"path": "dist/ltjt-order-assistant-0.5.158.zip",
"source": "chrome-extension/ltjt-order-assistant",
"sha256": "872640668c52cf1ef0135ff51ec33e3c1f5dee304434fbb58ceee4cb3518fcd7"
"sha256": "99ba7b29d1d4418cb02fcbb345aeaf8494877dbc397ae8592b4e3fdb68b4cf9d"
},
{
"kind": "skill",

View File

@@ -1,5 +1,12 @@
# 当前关键发现
## 2026-08-29 — 正式控制面域名插件接入
- 扩展 `0.5.157` 的 Popup 与后台业务系统 URL 判定只允许本地 HTTPManifest 的控制面 content script 匹配也只有 localhost/127.0.0.1;当前 ZIP 与源码一致,因此刷新或重新加载旧包不能识别 `https://lwlt.nianxx.cn`
- `0.5.158` 只把精确 origin `https://lwlt.nianxx.cn` 加入 Popup、后台标签发现、host permission 和 content script相似域名、子域名及其他 HTTPS 站点仍拒绝注入。ERP origin `https://lwlt.hisy.cc` 未改变。
- 新 ZIP 19/19 文件与源码逐字节一致SHA-256 为 `99ba7b29d1d4418cb02fcbb345aeaf8494877dbc397ae8592b4e3fdb68b4cf9d``0.5.157` ZIP 和清单快照已日期化归档。
- 本轮只生成发布物,没有重载用户浏览器中的扩展、部署服务或访问 ERP运行态仍需用户/运维从 `dist/` 加载 `0.5.158` 后刷新正式操作台。
## 2026-08-28 — AgentBus 运行态重连
- 用户侧观察到 AgentBus 断线后,服务端健康接口在重启前已短暂恢复为 4/4 ready说明该现象可能是间歇性连接或页面状态延迟当时在途 task 与 `accepted/running` attempt 均为 0。

View File

@@ -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.157",
"current_extension_version": "0.5.158",
"historical_test_marker": "TEST-202609",
"scope": "/System/Business/",
"erp_session_keepalive": {

2
package-lock.json generated
View File

@@ -26,6 +26,8 @@
"@types/pg": "^8.11.10",
"@types/ws": "^8.18.1",
"@types/yauzl": "^3.4.0",
"ajv": "^8.20.0",
"ajv-formats": "^3.0.1",
"jszip": "^3.10.1",
"tsx": "^4.19.2",
"typescript": "^5.7.3"

View File

@@ -39,6 +39,8 @@
"@types/pg": "^8.11.10",
"@types/ws": "^8.18.1",
"@types/yauzl": "^3.4.0",
"ajv": "^8.20.0",
"ajv-formats": "^3.0.1",
"jszip": "^3.10.1",
"tsx": "^4.19.2",
"typescript": "^5.7.3"

6
pnpm-lock.yaml generated
View File

@@ -57,6 +57,12 @@ importers:
'@types/yauzl':
specifier: ^3.4.0
version: 3.4.0
ajv:
specifier: ^8.20.0
version: 8.20.0
ajv-formats:
specifier: ^3.0.1
version: 3.0.1(ajv@8.20.0)
jszip:
specifier: ^3.10.1
version: 3.10.1

View File

@@ -1,5 +1,12 @@
# 项目进度
## 2026-08-29 — 正式控制面域名插件接入(已完成)
- 已用真实 Popup 代码复现 `https://lwlt.nianxx.cn``0.5.157` 判定为“未打开业务系统页面”,并将该路径固化为回归测试。
- 已精确同步正式 origin 到 Popup、后台和 Manifest版本、平台最低版本、mapping、发布门槛及运行时握手均推进到 `0.5.158`
- `dist/ltjt-order-assistant-0.5.158.zip` 已生成19/19 文件与源码一致SHA-256 为 `99ba7b29d1d4418cb02fcbb345aeaf8494877dbc397ae8592b4e3fdb68b4cf9d`;旧 ZIP/清单已归档。
- Windows 发布门禁的换行、直接测试依赖、URL 路径和 Unix 假转换器问题已在测试边界收敛。未重载扩展、部署服务、访问 ERP 或执行 ERP 写入。
## 2026-08-28 — AgentBus 断线后重启(已完成)
- 用户明确授权重启控制面;预检确认在途 task 与 `accepted/running` attempt 均为 0。旧进程组 `62152` 退出后,运行会话管理器自动从标准入口拉起新进程组 `66218`、服务 PID `66228`8786 只有一个监听。

View File

@@ -1,27 +1,24 @@
# 项目文件治理计划
## 当前任务:重启控制面并恢复 AgentBus 连接(已完成)
## 当前任务:正式控制面域名接入 Chrome 插件(已完成)
- **目标:** 按用户明确授权,优雅重启标准 8786 控制面,使刚出现断线的 AgentBus 渠道重新建立会话并通过稳定性复核
- **安全边界:** 仅重启控制面并做只读进程、健康、数据库和 AgentBus 状态检查;不重载扩展,不创建、确认、重试或派发任务,不访问或写入 ERP,不部署或外发
- **目标:** 修复扩展 `0.5.157` 只识别本地控制面、导致 `https://lwlt.nianxx.cn` 显示“未打开业务系统页面”的问题,并发布 `0.5.158`
- **安全边界:** 只修改源码、测试、版本和本地发布物;精确允许该生产 origin不放宽到其他 HTTPS 站点;不重载浏览器扩展、不部署、不访问或写入 ERP。
### 阶段
### 阶段与结论
- [x] 确认唯一监听、live/ready 当前状态,并确认在途 task 与 `accepted/running` attempt 均为 0
- [x] 优雅停止当前进程组,并从标准 `pnpm run dev` 入口恢复唯一服务实例
- [x] 核验数据库、Schema、迁移与 4/4 AgentBus 渠道重连,并观察连接状态保持稳定
### 完成结论
- 重启前服务端健康接口已显示 4/4 渠道重新连接,但用户侧刚观察到断线;在途 task 与 `accepted/running` attempt 均为 0因此仍按授权执行干净重启。
- 旧进程组 `62152` 已退出;运行会话管理器随即从标准 `pnpm run dev` 入口拉起新进程组 `66218`、服务 PID `66228`没有再启动第二份实例8786 保持唯一监听。
- 新实例 live/ready、数据库、Schema、迁移 `014_task_input_attachments` 均正常;连续 25 秒共 6 次采样均为 AgentBus 总连接与 session ready 正常、4/4 渠道 ready。
- 未创建、确认、重试或派发任务,未重载扩展,未访问或写入 ERP未部署或外发。
- 重启后门禁通过:仓库治理 9/9、TypeScript、控制面 127/127、legacy 248/248、build 与 `git diff --check`
- [x] 用 Popup 黑盒把活动标签固定为正式域名,稳定复现正式域名拒绝而 localhost 识别的红灯
- [x] 同步 Popup URL 判定、后台标签发现、Manifest host permission/content script并保留相似域名和其他站点阻断
- [x] 扩展、平台最低版本和 lifecycle mapping 升至 `0.5.158`;归档 `0.5.157`,重建版本化 ZIP 与发布清单
- [x] ZIP 19/19 文件与源码逐字节一致SHA-256 为 `99ba7b29d1d4418cb02fcbb345aeaf8494877dbc397ae8592b4e3fdb68b4cf9d`
- [x] 修复 Windows 门禁的三项既有可达问题:`.project-docs` 根白名单、文本 CRLF/LF 比较、测试直接依赖与路径/Unix 假转换器边界。
### 错误记录
- 首批并行门禁附带的一次 `/tmp` 临时健康文件清理被安全策略拒绝,导致该批结果未汇总;该操作与服务、数据库和 AgentBus 无关。跳过清理后完整门禁已重新运行并全部通过
- 仓库最初没有安装依赖;锁文件安装需精确批准 `argon2/esbuild` 构建。批准只作用于本地依赖,临时生成的 `pnpm-workspace.yaml` 已移除
- 首轮全量门禁暴露 `ajv/ajv-formats` 未声明为直接测试依赖、Windows URL pathname 重复盘符以及 Unix `.sh` 假转换器不可执行;均在测试/依赖边界收敛,未改生产转换逻辑。
压缩前完整旧计划见 [2026-08-29 快照](archive/project-history/2026-08-29/task_plan-pre-compression-production-domain.md)。
## 当前任务:将运行态切换到整团游客信息新版(已完成)

View File

@@ -8,7 +8,7 @@ import Ajv2020 from 'ajv/dist/2020.js';
import { buildParserMessage } from '../LianSyn-platform/external-agent-client.mjs';
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const read = (relativePath) => readFileSync(path.join(repoRoot, relativePath), 'utf8');
const read = (relativePath) => readFileSync(path.join(repoRoot, relativePath), 'utf8').replace(/\r\n/gu, '\n');
const catalog = read('agent设计规范/templates/business-input-templates.md');
const registry = read('agent设计规范/business-behavior-registry.md');

View File

@@ -1,11 +1,49 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
import vm from 'node:vm';
async function read(relativePath) {
return readFile(new URL(`../${relativePath}`, import.meta.url), 'utf8');
}
function loadPopupBusinessUrlMatcher(popupSource) {
const context = {
URL,
chrome: {},
document: {
addEventListener() {},
querySelector() {
return null;
}
},
window: {
setTimeout() {}
}
};
vm.runInNewContext(popupSource, context, { filename: 'popup.js' });
return context.isBusinessSystemUrl;
}
test('production control-plane origin is recognized and bridge injection is permitted', async () => {
const [manifestText, background, popup] = await Promise.all([
read('chrome-extension/ltjt-order-assistant/manifest.json'),
read('chrome-extension/ltjt-order-assistant/background.js'),
read('chrome-extension/ltjt-order-assistant/popup.js')
]);
const manifest = JSON.parse(manifestText);
const isBusinessSystemUrl = loadPopupBusinessUrlMatcher(popup);
assert.equal(isBusinessSystemUrl('https://lwlt.nianxx.cn/'), true);
assert.equal(isBusinessSystemUrl('https://lwlt.nianxx.cn/history?status=running'), true);
assert.equal(isBusinessSystemUrl('https://lwlt.nianxx.cn.example.com/'), false);
assert.ok(manifest.host_permissions.includes('https://lwlt.nianxx.cn/*'));
assert.ok(manifest.content_scripts.some(({ matches }) => matches.includes('https://lwlt.nianxx.cn/*')));
assert.match(background, /PRODUCTION_PLATFORM_ORIGIN = 'https:\/\/lwlt\.nianxx\.cn'/);
assert.match(background, /parsed\.origin === PRODUCTION_PLATFORM_ORIGIN/);
assert.match(background, /'https:\/\/lwlt\.nianxx\.cn\/\*'/);
});
test('ERP session keepalive is scheduled through alarms with a conservative default', async () => {
const [manifestText, background, mappingText] = await Promise.all([
read('chrome-extension/ltjt-order-assistant/manifest.json'),

View File

@@ -6,6 +6,7 @@ import { createRequire } from 'node:module';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import vm from 'node:vm';
import Ajv2020 from 'ajv/dist/2020.js';
import addFormats from 'ajv-formats';
@@ -1135,7 +1136,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\.157'/);
assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.158'/);
});
test('uncertain lifecycle writes can only converge through a read-only plugin requery', async () => {
@@ -1765,7 +1766,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.157');
assert.equal(mapping.current_extension_version, '0.5.158');
assert.equal(
mapping.updates.order_update_independent.field_mapped_pending_live_validation['pax.child_no_bed'],
'ertrenshu'
@@ -1900,7 +1901,7 @@ test('platform runner independently blocks a tampered multi-date split-order pro
await writeFile(fixturePath, JSON.stringify(operation), 'utf8');
const runnerPath = new URL('./run_lifecycle_fixture_via_platform.mjs', import.meta.url);
const result = spawnSync(process.execPath, [
runnerPath.pathname,
fileURLToPath(runnerPath),
'--fixture', fixturePath,
'--expected-run-id', runId,
'--cdp', '/bin/false'
@@ -2147,10 +2148,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.157');
assert.match(inpage, /version: '0\.5\.157'/);
assert.match(teamBatchInpage, /version: '0\.5\.157'/);
assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.157'/);
assert.equal(extensionManifest.version, '0.5.158');
assert.match(inpage, /version: '0\.5\.158'/);
assert.match(teamBatchInpage, /version: '0\.5\.158'/);
assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.158'/);
assert.match(inpage, /function strictIsoDate\(value\)/);
assert.match(inpage, /const startDate = strictIsoDate\(controlCanonicalValue\(form, 'riqi0'\)\)/);
assert.match(inpage, /const endDate = strictIsoDate\(controlCanonicalValue\(form, 'riqis0'\)\)/);

View File

@@ -18,6 +18,7 @@ const ROOT_ALLOWLIST = new Set([
'.env.production.example',
'.git',
'.gitignore',
'.project-docs',
'AGENTS.md',
'Dockerfile',
'LianSyn-platform',
@@ -47,9 +48,23 @@ const ROOT_ALLOWLIST = new Set([
const SKIP_WALK = new Set(['.git', '.build', 'node_modules']);
const RESIDUE_PATTERN = /(^\.DS_Store$|\.log$|\.tmp$|\.swp$|\.pid$|~$)/i;
const TEXT_ARTIFACT_EXTENSIONS = new Set(['.css', '.html', '.js', '.json', '.md', '.mjs', '.py', '.ts', '.txt', '.yaml', '.yml']);
function hashBytes(value) {
return createHash('sha256').update(value).digest('hex');
}
async function sha256(filePath) {
return createHash('sha256').update(await readFile(filePath)).digest('hex');
return hashBytes(await readFile(filePath));
}
function artifactContentHash(entry, value) {
if (!TEXT_ARTIFACT_EXTENSIONS.has(path.extname(entry).toLowerCase())) return hashBytes(value);
return hashBytes(Buffer.from(value.toString('utf8').replace(/\r\n/gu, '\n'), 'utf8'));
}
async function normalizedTextSha256(filePath) {
return artifactContentHash(filePath, await readFile(filePath));
}
async function walkFiles(directory, options = {}) {
@@ -90,10 +105,8 @@ async function assertArchiveMatchesSource({ artifactPath, sourcePath, ignore = n
assert.deepEqual(artifactEntries, sourceEntries, `${path.basename(artifactPath)} 文件集合与源码不一致`);
for (const entry of sourceEntries) {
const sourceHash = createHash('sha256')
.update(await readFile(path.join(sourcePath, ...entry.split('/'))))
.digest('hex');
const archiveHash = createHash('sha256').update(archiveEntry(artifactPath, entry)).digest('hex');
const sourceHash = artifactContentHash(entry, await readFile(path.join(sourcePath, ...entry.split('/'))));
const archiveHash = artifactContentHash(entry, archiveEntry(artifactPath, entry));
assert.equal(archiveHash, sourceHash, `${path.basename(artifactPath)}:${entry} 与源码内容不一致`);
}
}
@@ -150,8 +163,8 @@ test('dist is an exact versioned release set with valid hashes', async () => {
assert.equal(manifest.artifacts.filter(({ kind }) => kind === 'business_instruction_docx').length, 1);
const docx = manifest.artifacts.find(({ kind }) => kind === 'business_instruction_docx');
assert.equal(await sha256(path.resolve(ROOT, docx.source)), docx.source_sha256);
assert.equal(await sha256(path.resolve(ROOT, docx.builder)), docx.builder_sha256);
assert.equal(await normalizedTextSha256(path.resolve(ROOT, docx.source)), docx.source_sha256);
assert.equal(await normalizedTextSha256(path.resolve(ROOT, docx.builder)), docx.builder_sha256);
});
test('extension and release baselines agree', async () => {