diff --git a/.env.production.example b/.env.production.example index 80e0ca3..67ed33f 100644 --- a/.env.production.example +++ b/.env.production.example @@ -40,19 +40,6 @@ OSS_ENDPOINT=oss-cn-guangzhou.aliyuncs.com OSS_BUCKET_NAME=one-feel-bucket OSS_REGION=cn-guangzhou OSS_KEY_PREFIX=liansyn-platform/attachments -# Unpacked Chrome extension updates. Keep false until every Windows Server -# profile has been bootstrapped from the shared ProgramData directory. -EXTENSION_AUTO_UPDATE_ENABLED=false -EXTENSION_UPDATE_OSS_KEY_PREFIX=liansyn-platform/chrome-extension -EXTENSION_WINDOWS_INSTALL_PATH=C:\ProgramData\LTJT\chrome-extension\ltjt-order-assistant -EXTENSION_UPDATE_MAX_PACKAGE_BYTES=15000000 -EXTENSION_UPDATE_COMMAND_TIMEOUT_SECONDS=600 -EXTENSION_UPDATE_POLL_INTERVAL_MS=5000 -# Use a RAM identity restricted to ecs:RunCommand and invocation-result reads. -# Do not reuse a broad OSS key merely because OSS is also configured above. -ALIBABA_CLOUD_ACCESS_KEY_ID=replace-with-restricted-ecs-access-key-id -ALIBABA_CLOUD_ACCESS_KEY_SECRET=replace-with-restricted-ecs-access-key-secret -ALIBABA_CLOUD_SECURITY_TOKEN= PARSER_REQUEST_TIMEOUT_MS=120000 PARSER_TOTAL_TIMEOUT_MS=180000 PARSER_MAX_EVENT_BYTES=65536 diff --git a/.project-docs/10-decisions/EXT-001-central-service-host-extension-updates.md b/.project-docs/10-decisions/EXT-001-central-service-host-extension-updates.md index 0351323..f6f61ed 100644 --- a/.project-docs/10-decisions/EXT-001-central-service-host-extension-updates.md +++ b/.project-docs/10-decisions/EXT-001-central-service-host-extension-updates.md @@ -2,19 +2,25 @@ ## Status -Accepted +Reverted ## Date 2026-09-03 +## Reversal + +On 2026-09-03 the user explicitly directed that this iteration be backed up and removed from the main branch. The exact integrated state at commit `b2e33e2e5d29138891eabc93969cf24907492dfe` is preserved on remote branch `codex/backup-extension-update-20260903-b2e33e2`; active `main` restores the pre-iteration extension `0.5.165` and migration 018 baseline through a normal revert of merge `3224758`. + +This ADR is retained only as historical design context and no longer governs the active architecture. The reverted code was never deployed, migration 019 was not applied, no OSS extension release was published, and no Cloud Assistant or Chrome reload action was performed by these repository tasks. + ## Context The production control plane runs on a separate Node server while ERP work runs in multiple Chrome profiles across multiple Alibaba ECS Windows Server hosts. The profiles use an unpacked extension and are not managed through AD, Chrome Enterprise policy, or the Chrome Web Store. Maintaining every profile by logging in to each Windows host does not scale, but a remote web page cannot itself install an extension or write silently to a Windows extension directory. The user selected an architecture that keeps release and update logic in the existing control plane, stores packages in OSS, and uses Alibaba Cloud Assistant only as the bounded remote-execution boundary. A separately maintained LTJT updater service on each Windows host is not introduced. -## Decision +## Former Decision - The central control plane owns approved extension releases, version comparison, update state, retry policy, host safety, and post-update verification. Private OSS owns immutable, content-addressed package bytes. - Each non-administrator account may map to one Alibaba ECS region and instance. Accounts on the same instance share one host update row and one idle gate, while every Chrome profile continues to report its actually loaded extension version. @@ -25,7 +31,7 @@ The user selected an architecture that keeps release and update logic in the exi - Extension `0.5.167` is the one-time bootstrap release for the safety and reload protocol. Before enabling automatic updates, every existing profile must manually load the shared `C:\ProgramData\LTJT\chrome-extension\ltjt-order-assistant` directory once. - Automatic updates remain disabled by default. Production activation requires HTTPS `APP_ORIGIN`, private OSS configuration, a least-privilege ECS RAM identity, verified ECS account mappings, Cloud Assistant connectivity, completed bootstrap, backup, migration `019`, and a canary rollout. -## Consequences +## Historical Consequences - Routine releases no longer require logging in to every Windows account after bootstrap; one host file deployment can serve multiple profiles, while each profile remains independently version-gated and verified. - The update path does not depend on Google services or Chrome Web Store review, but it does depend on Alibaba ECS Cloud Assistant and Windows-to-control-plane HTTPS reachability. diff --git a/.project-docs/10-decisions/decision-index.md b/.project-docs/10-decisions/decision-index.md index f90c4be..6e7977f 100644 --- a/.project-docs/10-decisions/decision-index.md +++ b/.project-docs/10-decisions/decision-index.md @@ -12,14 +12,14 @@ | NETWORK-001 | In the trusted internal deployment, AgentBus roster attachment URLs may resolve to internal/private addresses; HTTPS, credential rejection, DNS pinning, redirect validation, bounds, and digest checks remain. | Active | 2026-08-31 | AgentBus attachment ingress | [Reply contract](../../agent设计规范/agentbus-reply-contract.md) | | AUTH-001 | The fixed deployment scope uses administrator-managed `admin`, `team_lead`, and `user` accounts, owner-isolated normal tasks, display-only leadership metrics with explicit filtering, explicit non-admin route grants, and assignee-bound AgentBus/browser/ERP execution. | Active except clauses superseded by AUTH-002 | 2026-09-01 | Authentication, authorization, audit, AgentBus workers, and operations oversight | [ADR](AUTH-001-fixed-scope-account-authorization.md) | | AUTH-002 | ERP execution is serialized per immutable assigned account, administrator visibility is never execution routing, and explicit force delete physically removes authorized tasks regardless of lifecycle state. | Active | 2026-09-03 | ERP claim queues, executable events/results, and task removal | [ADR](AUTH-002-account-scoped-execution-and-force-delete.md) | -| EXT-001 | The separate central control plane publishes private OSS extension releases and uses bounded ECS Cloud Assistant commands to update shared unpacked-extension files only while every mapped account is idle; target-version browser heartbeats are required before ERP execution resumes. | Active | 2026-09-03 | Chrome extension release, Windows host update, and ERP claim safety | [ADR](EXT-001-central-service-host-extension-updates.md) | -## Superseded Decisions +## Superseded And Reverted Decisions -| ID | Decision | Superseded By | Date | +| ID | Decision | Resolution | Date | |---|---|---|---| | DOC-LEGACY-001 | Root `task_plan.md`, `findings.md`, and `progress.md` were the active project-memory system. | DOC-001 | 2026-08-28 | | AUTH-001 (partial) | Organization-wide ERP FIFO and archive-only operator removal. | AUTH-002 | 2026-09-03 | +| EXT-001 | Central-service private-OSS and ECS Cloud Assistant orchestration for shared unpacked-extension updates. | Reverted by explicit user decision; exact implementation preserved on `codex/backup-extension-update-20260903-b2e33e2`. | 2026-09-03 | ## Decision Criteria diff --git a/.project-docs/20-architecture/data-flow.md b/.project-docs/20-architecture/data-flow.md index 23414a8..64df028 100644 --- a/.project-docs/20-architecture/data-flow.md +++ b/.project-docs/20-architecture/data-flow.md @@ -17,7 +17,6 @@ | Operational diagnostics | Service, request, task, parser, AgentBus, attachment, database, and cleanup stages | Structured stdout/stderr and bounded Docker logs | Correlation identifiers, codes, outcomes, and durations only; no secrets or business payloads. | | Platform operations oversight | Manual task creator, encrypted instruction history, and readable outcome | Team-lead/administrator leadership projection | Display-only summaries plus explicit filters drive an aggregate-first task/person/input/output/time/type/completion view; list reads are bounded to one read-only connection and hydrate full details only for the current page. | | Browser worker selection | Immutable task assignee | One fresh account-bound browser connection | The heartbeat must match the account's expected ERP identity; a second fresh worker or identity mismatch is non-executable, with failover only after staleness. | -| Extension release and host update | Administrator ZIP → private OSS release → account-mapped ECS Windows host | Shared unpacked-extension directory → profile reload → verified heartbeat | The central service validates and content-addresses each monotonic release, waits for every mapped account to be ERP-idle, sends one bounded Cloud Assistant command, verifies hash/Manifest and rollback-safe deployment, then blocks new claims until each browser reports the target version. | | Account-scoped ERP queue | Confirmed task assignee | Assigned account's browser worker | Organization-plus-account advisory locking preserves FIFO and at most one active execution for that account; another account's active, queued, stale, or uncertain work is outside this queue. | | Executable event and result routing | Immutable task assignee | Matching authenticated platform page and plugin | SSE history/live events, claims, plugin results, and browser cleanup commands never use administrator-wide visibility and fail closed when the authenticated account is not the assignee. | | Task removal | Authorized operator | Archive/restore or permanent force delete | Archive/restore remains reversible and state-gated. Explicit force delete has no lifecycle-state gate, removes task-owned platform records atomically, retains a minimal deletion audit marker, and performs post-commit artifact/plugin cleanup best effort. | @@ -26,8 +25,8 @@ ## State Ownership -- PostgreSQL owns durable control-plane account, role, expected ERP identity, ECS host mapping, approved extension release metadata, host update state, task-route grant, AgentBus channel owner, immutable task assignee, account-scoped queue/lease state, browser worker, session, confirmation, audit, archive, and outcome state. A force-deleted task no longer exists in task state; only its minimal non-content deletion audit marker remains. -- Production attachment bytes and private immutable extension release packages use the configured OSS provider; normalized sensitive fields remain encrypted. +- PostgreSQL owns durable control-plane account, role, expected ERP identity, task-route grant, AgentBus channel owner, immutable task assignee, account-scoped queue/lease state, browser worker, session, confirmation, audit, archive, and outcome state. A force-deleted task no longer exists in task state; only its minimal non-content deletion audit marker remains. +- Production attachment bytes use the configured OSS provider; normalized sensitive fields remain encrypted. - Chrome extension local state is bounded execution/reconciliation support, not canonical business history. - `.project-docs/30-worklog/tasks/` owns task-local project memory; canonical project state is an integrated projection. @@ -36,7 +35,7 @@ - Operator workbench at the control-plane service. - AgentBus WebSocket channels and attachment delivery. - Logged-in ERP browser pages under the Chrome extension host permissions. -- PostgreSQL, OSS, deployment gateway, authenticated artifact download, Alibaba ECS Cloud Assistant APIs, and the host-bound HTTPS extension package route. +- PostgreSQL, OSS, deployment gateway, and authenticated artifact download. ## Last Updated diff --git a/.project-docs/20-architecture/system-overview.md b/.project-docs/20-architecture/system-overview.md index c36768b..bc1db44 100644 --- a/.project-docs/20-architecture/system-overview.md +++ b/.project-docs/20-architecture/system-overview.md @@ -10,9 +10,9 @@ Authenticated manual or account-bound AgentBus input is routed through task-scop |---|---|---| | `agent设计规范/` | Agent Prompt, five parsing Skills, business templates, business registry, and stable fixtures | Editable source for business semantics; not runtime evidence | | `schemas/` and `mappings/` | Parse-state, execution-state, ERP form, field, and lifecycle contracts | Current contracts only | -| `control-plane/` | Task/session persistence, parser orchestration, confirmation, audit, AgentBus channel ownership, task assignment, browser workers, private extension release/host-update orchestration, attachments, receipts, and structured diagnostics | TypeScript source; build output goes to `.build/` | +| `control-plane/` | Task/session persistence, parser orchestration, confirmation, audit, AgentBus channel ownership, task assignment, browser workers, attachments, receipts, and structured diagnostics | TypeScript source; build output goes to `.build/` | | `LianSyn-platform/` | Operator workbench and external parser adapter | Source and UI, not local task output | -| `chrome-extension/ltjt-order-assistant/` | Logged-in ERP resolution, preflight, native execution, response handling, requery, update-safety proof, and idle runtime reload | Any code change requires synchronized versioned release updates | +| `chrome-extension/ltjt-order-assistant/` | Logged-in ERP resolution, preflight, native execution, response handling, and requery | Any code change requires synchronized versioned release updates | | `dist/` | Versioned current deliverables and machine-readable release manifest | Not a compilation directory | | `.project-docs/` | Task-isolated project memory and integrated canonical context | No runtime dependency | | `archive/` | Date-scoped immutable history and evidence | Never defines current behavior | @@ -28,12 +28,10 @@ Authenticated manual or account-bound AgentBus input is routed through task-scop - Each enabled AgentBus channel owns one active non-admin employee account. Inbound work uses that account and route allowlist, persists the same account as immutable task assignee, and is returned only to that account's executable feed. - Each employee account has one expected ERP identity and at most one fresh browser execution worker. Mismatched ERP identity, concurrent fresh workers, unbound channels, or unassigned tasks fail closed. Browser claims, active-execution checks, and confirmed FIFO are serialized per immutable task assignee, so one account cannot block or occupy another account's queue. - Administrator-wide task visibility is a read model, not an executable feed. Task SSE history/live events, browser claims, plugin-result ingestion, and browser cleanup commands are always scoped to the authenticated account matching `assigned_user_id`, including for administrators. -- The separate central service owns approved extension versions and maps multiple employee accounts to shared Alibaba ECS Windows hosts. Private OSS stores immutable packages; one-shot ECS Cloud Assistant PowerShell updates only the configured `ProgramData\LTJT` directory after every mapped account is idle. Update start and ERP claim share an organization serialization boundary, and a browser below the active release cannot receive new ERP work. -- Extension file deployment is not release acceptance. Every Chrome profile reloads only after its own in-memory and durable execution state is safe, then reports the active version through a fresh heartbeat. Existing profiles require one manual `0.5.167` bootstrap from the shared directory before this automatic path is enabled. - Creator and manual input-turn attribution remain durable while business input stays encrypted at rest. Routine removal is reversible archive/restore. Separately confirmed force delete physically removes an authorized task regardless of lifecycle state, retains only a minimal non-content deletion audit marker, and cannot undo an ERP write that already occurred. - Unknown, ambiguous, unverified, or post-write-uncertain states fail closed; automatic retries must not create duplicate writes. - PostgreSQL is the sole required durable database/state middleware, and the production artifact provider is OSS. Redis, message queues, MongoDB, and search services are not runtime dependencies. -- Migrations through `019_extension_host_updates` must complete before the updated application starts. The current ACK topology starts with one application replica because AgentBus listeners, SSE emission, and bounded extension-update polling are process-local; horizontal scale requires explicit coordination first. +- Migrations through `018_agentbus_account_workers` must complete before the updated application starts. The current ACK topology starts with one application replica because AgentBus listeners and SSE emission are process-local; horizontal scale requires explicit coordination first. - Operational diagnostics are privacy-safe structured JSON on stdout/stderr. Docker owns bounded rotation; repository files and a second mutable log database are not log sinks. - In the trusted internal deployment, AgentBus roster attachment downloads may resolve to private/reserved addresses. Credential-free HTTPS, DNS resolution/pinning, redirect revalidation, size, timeout, and digest checks remain mandatory, and trusted channels/bridges own the network-input boundary. - Canonical project memory is updated only under Integration Gate; feature tasks write only their task-scoped records. @@ -48,7 +46,6 @@ Authenticated manual or account-bound AgentBus input is routed through task-scop - NETWORK-001 - AUTH-001 - AUTH-002 -- EXT-001 ## Last Updated diff --git a/.project-docs/30-worklog/current-state.md b/.project-docs/30-worklog/current-state.md index dd3c6c8..384c347 100644 --- a/.project-docs/30-worklog/current-state.md +++ b/.project-docs/30-worklog/current-state.md @@ -4,8 +4,8 @@ This file is the integrated default-branch snapshot. Feature tasks record progre ## Integrated Through -- Merge commit `3224758` integrating source commits `500034b` and `f08aac0` from tasks `20260903-adaptive-wait-auto-update-7b3e9a2c` and `20260903-service-extension-update-8d42c6f1` for adaptive ERP entry readiness plus central-service/private-OSS/ECS-Cloud-Assistant extension updates, migration 019, and extension `0.5.167`. -- Integration task `20260903-finalize-extension-update-a6c4e192` for acceptance of EXT-001, canonical update/release reconciliation, full release verification, and normal non-force synchronization to `origin/main`. +- Integration task `20260903-backup-revert-extension-update-c71a4e92` for preserving the complete former `0.5.167`/migration-019 iteration at remote branch `codex/backup-extension-update-20260903-b2e33e2`, reverting merge `3224758` without rewriting history, and restoring the active product/release baseline to `f52d9d7` (`0.5.165`, migration 018). +- Merge commit `3224758` and integration task `20260903-finalize-extension-update-a6c4e192` remain historical records of the briefly integrated extension-update design; that design is no longer active and is preserved on the backup branch only. - Commit `c4c469f4441d744627af2d34abe693b6783e833c` for the independently advanced remote deployment/extension line. - Commit `cd45ce17d0fcd25f7fa89ab9f8a391d3e904ecdf` for WeChat attachment correlation and privacy-safe server diagnostics. - Commit `161f90d09d6ad1368973b1a85d51059059495223` for trusted-intranet attachment compatibility and canonical reconciliation. @@ -33,7 +33,7 @@ This file is the integrated default-branch snapshot. Feature tasks record progre ## Current Focus -Operate the repository's current `0.5.167` extension baseline and fixed-scope account model safely, bind each enabled AgentBus channel to one employee/ERP identity and ECS Windows host, provision narrow route grants, use explicit leadership-dashboard filters, and preserve Program/AI plus per-assigned-account ERP execution boundaries. Same-account tasks remain FIFO and single-active; distinct accounts are independent, and administrator visibility never enters another account's executable event/result path. Migration 019, one-time profile bootstrap, restricted RAM/OSS configuration, extension publication, Cloud Assistant canary, and service rollout remain separately authorized runtime work. +Operate the repository's current `0.5.165` extension baseline and fixed-scope account model safely, bind each enabled AgentBus channel to one employee/ERP identity, provision narrow route grants, use explicit leadership-dashboard filters, and preserve Program/AI plus per-assigned-account ERP execution boundaries. Same-account tasks remain FIFO and single-active; distinct accounts are independent, and administrator visibility never enters another account's executable event/result path. Migration 018, extension reload, guarded product-search retry, and service rollout remain separately authorized runtime work. ## Recently Completed @@ -57,29 +57,26 @@ Operate the repository's current `0.5.167` extension baseline and fixed-scope ac - 2026-09-02: Integrated extension `0.5.165`: scatter-plan creation and independent batch-order creation now try loaded product candidates, then the form's native non-empty `S_chanpinming` search, and finally one bounded empty-query compatibility reload. A user-authorized search-only ERP check returned exactly one target row in both forms without selecting or saving it; zero or multiple local matches continue to fail closed. - 2026-09-03: Accepted AUTH-002 and integrated account-scoped ERP queues. Each immutable assignee now owns one FIFO/single-active claim partition, different accounts no longer block one another, and executable SSE/results/cleanup commands are owner-only even when an administrator is signed in. - 2026-09-03: Restored explicit permanent force deletion as a separate operation from reversible archive/restore. It bypasses lifecycle-state gates, physically removes task-owned platform records, retains a minimal deletion audit marker, performs post-commit cleanup best effort, and warns that prior ERP effects are not rolled back. -- 2026-09-03: Added an immediate-first, conditional 100 ms readiness probe for scatter-plan entry. It proceeds without a fixed delay when the current ERP document is ready and fails closed before any write after the bounded timeout. -- 2026-09-03: Integrated extension `0.5.167` and migration 019. The separate central service can publish validated private OSS releases, group multiple accounts by ECS Windows host, wait for all mapped ERP workers to become safe, deploy through one-shot Cloud Assistant PowerShell with hash/staging/rollback/no-downgrade checks, and require profile reload plus target-version heartbeat before reopening ERP claims. +- 2026-09-03: Backed up the complete adaptive-readiness and central extension-update iteration at remote branch `codex/backup-extension-update-20260903-b2e33e2`, then reverted it from `main` with a normal history-preserving commit. The active repository again uses extension `0.5.165` and migration 018; the reverted iteration was never deployed by these tasks. ## In Progress - The standard database currently contains one administrator account and no non-administrator task grants. Multi-account operational smoke testing remains for an administrator-led staging window. -- Migrations `018_agentbus_account_workers` and `019_extension_host_updates`, employee ERP identities/channel/host bindings, extension `0.5.167`, account-scoped queue/routing changes, force-delete behavior, and the merged dashboard/update runtime have not been applied to or restarted on the production service in this integration task. +- Migration `018_agentbus_account_workers`, employee ERP identities/channel bindings, extension `0.5.165`, account-scoped queue/routing changes, force-delete behavior, and the merged dashboard/runtime changes have not been applied to or restarted on the standard service in this integration task. ## Next Recommended Steps -1. In an explicitly authorized rollout window, back up PostgreSQL, apply migrations through 019, deploy the central service with automatic extension updates still disabled, manually load bootstrap extension `0.5.167` from the shared ProgramData directory in every profile, and verify each runtime heartbeat. -2. Configure a least-privilege ECS RAM identity, private OSS release prefix, HTTPS `APP_ORIGIN`, and exact account-to-ECS mappings; then enable updates and run one controlled higher-version canary through waiting → running → deployed → reload → verified before widening rollout. -3. Through the administrator UI, create representative team-lead and ordinary accounts, assign narrow task grants, and verify owner isolation, leadership dashboard reads, grant/revoke behavior, and denial prompts without ERP writes. -4. In the same authorized staging window, verify that an administrator receives no employee executable events/results, then force-delete disposable waiting and active employee tasks and confirm database absence plus cleanup only in the owning employee plugin. -5. With explicit authorization, run a live read-only ERP verification of the shared-mother-plan `tid-only` whole-visitor export path. -6. With explicit authorization, perform ERP write verification for independent-order SGL/TWN and adult/child/leader headcount mappings. +1. In an explicitly authorized staging/rollout window, back up PostgreSQL, apply migration 018, restart the control plane, load extension `0.5.165`, verify its runtime handshake, configure employee ERP identities and channel bindings, and run the multi-cloud-PC/identity/failover plus account-queue matrix before production assurance. +2. Through the administrator UI, create representative team-lead and ordinary accounts, assign narrow task grants, and verify owner isolation, leadership dashboard reads, grant/revoke behavior, and denial prompts without ERP writes. +3. In the same authorized staging window, verify that an administrator receives no employee executable events/results, then force-delete disposable waiting and active employee tasks and confirm database absence plus cleanup only in the owning employee plugin. +4. With explicit authorization, run a live read-only ERP verification of the shared-mother-plan `tid-only` whole-visitor export path. +5. With explicit authorization, perform ERP write verification for independent-order SGL/TWN and adult/child/leader headcount mappings. ## Open Questions / Blockers - Shared-mother-plan whole-visitor export has historical read evidence and static coverage but lacks a fresh authorized runtime ERP read verification. - Independent-order SGL/TWN and four headcount categories lack authorized current-version ERP write evidence. -- The production service has not been verified at merge commit `3224758`; its runtime schema, extension, account UI, queue/routing, force-delete, dashboard, and extension-update behavior must not be represented as the newly integrated repository state until an authorized rollout. -- Extension auto-update cannot be enabled safely until every existing Chrome profile has loaded bootstrap `0.5.167` from the shared ProgramData directory and the restricted ECS/OSS/HTTPS configuration plus one-host canary have been verified. +- The standard service was last restarted before commits `3062ed5`, `b5f5847`, `cc09506`, `336ca6e`, `e4fd916`, and `d09b303`; its runtime schema, extension, account UI, queue/routing, force-delete, and dashboard behavior must not be represented as the newly integrated repository state until an authorized rollout. - AgentBus account-worker routing still lacks a live two-employee/two-cloud-PC staging matrix covering mismatched ERP login, same-account device conflict, 90-second stale failover, same-account FIFO, cross-account independence, administrator executable-feed isolation, and both manual and automatic channel work. - Lifecycle-independent force deletion has repository regression evidence but lacks an authorized runtime smoke test for waiting/active deletion, database absence, OSS cleanup, and owner-plugin-only cleanup. - A live internal AgentBus attachment verification remains separately unperformed. @@ -92,7 +89,6 @@ Operate the repository's current `0.5.167` extension baseline and fixed-scope ac - Account role changes, session revocation, creator-based task-route revocation, cross-user dashboard projection, and encrypted input audit are security-sensitive boundaries. - AgentBus channel ownership, immutable task assignment, expected ERP identity, browser-worker freshness/failover, and administrator non-execution are security- and write-safety-sensitive boundaries. - Account-scoped advisory locking, per-assignee FIFO queries, executable SSE/result routing, and irreversible force deletion are concurrency-, authorization-, and evidence-sensitive boundaries. -- Host-wide idle aggregation, update/claim serialization, short-lived package capabilities, Cloud Assistant idempotency, ProgramData path restriction, rollback, and post-reload version verification are security- and write-availability-sensitive boundaries. - Release synchronization across extension source, minimum platform version, mapping, ZIP, Skills, DOCX, and `dist/release-manifest.json`. ## Last Updated diff --git a/.project-docs/30-worklog/task-history.md b/.project-docs/30-worklog/task-history.md index 82af7b1..08c9504 100644 --- a/.project-docs/30-worklog/task-history.md +++ b/.project-docs/30-worklog/task-history.md @@ -9,4 +9,5 @@ This is integrated history. Feature tasks write only their task-scoped records; | 2026-08-28 | Shared mother-plan whole-visitor export | Released strict `shared_plan + visitor-list + tid-only` routing and execution boundaries in extension `0.5.157`. | [Release gate](../../agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md) | | 2026-09-02 | AgentBus account workers | Integrated one-to-one employee channel ownership, immutable task assignment, expected ERP identity, single-fresh-worker enforcement, migration 018, and extension `0.5.164`. | [Integration task](tasks/20260902-integrate-all-push-c93a7f21.md) | | 2026-09-02 | Leadership dashboard query and filter contract | Integrated bounded single-connection reads, 20-row paging, cancellation/timeout feedback, display-only metrics, and explicit result filtering. | [Integration task](tasks/20260902-integrate-all-push-c93a7f21.md) | -| 2026-09-03 | Adaptive ERP readiness and extension host updates | Integrated immediate-first scatter-plan readiness plus private OSS release publication, multi-account ECS host safety, Cloud Assistant deployment, and heartbeat-verified extension `0.5.167`. | [Integration task](tasks/20260903-finalize-extension-update-a6c4e192.md) | +| 2026-09-03 | Adaptive ERP readiness and extension host updates | Integrated immediate-first scatter-plan readiness plus private OSS/ECS host updating as extension `0.5.167`; this iteration was later reverted from `main` before deployment. | [Original integration task](tasks/20260903-finalize-extension-update-a6c4e192.md) | +| 2026-09-03 | Extension-update iteration backup and rollback | Preserved exact commit `b2e33e2` on remote branch `codex/backup-extension-update-20260903-b2e33e2` and restored active extension `0.5.165` plus migration 018 through a non-force revert. | [Rollback task](tasks/20260903-backup-revert-extension-update-c71a4e92.md) | diff --git a/.project-docs/30-worklog/tasks/20260903-backup-revert-extension-update-c71a4e92.md b/.project-docs/30-worklog/tasks/20260903-backup-revert-extension-update-c71a4e92.md new file mode 100644 index 0000000..56c67ac --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260903-backup-revert-extension-update-c71a4e92.md @@ -0,0 +1,57 @@ +# Task: Back up and revert extension update iteration + +## Identity + +- Task ID: 20260903-backup-revert-extension-update-c71a4e92 +- Mode: Integration +- Branch: codex/20260903-finalize-extension-update-a6c4e192-finalize-extension-update +- Worktree: /Users/inmanx/Documents/lwltAPI-finalize-extension-update-a6c4e192 +- Base commit: b2e33e2e5d29138891eabc93969cf24907492dfe +- Owner: codex +- Status: Planning + +## Scope + +- Preserve the complete extension-update iteration currently at remote `main` commit `b2e33e2` on a dedicated remote backup branch before changing `main`. +- Revert merge commit `3224758` through a normal history-preserving commit, restoring the active product source, migration boundary, extension release, and governed artifacts to the pre-iteration `f52d9d7` baseline. +- Preserve the source and integration task records as historical evidence, mark accepted decision `EXT-001` as reverted, and reconcile canonical project memory to the restored active architecture. +- Run the complete repository, release, document, test, and build gates, then advance remote `main` with a normal non-force push. + +## Intent And Constraints + +- The user explicitly requested a branch backup followed by rollback of this plugin-update iteration. This supplies the required human decision to reverse accepted architecture and product behavior. +- The exact backup ref is `codex/backup-extension-update-20260903-b2e33e2`; it must resolve remotely to `b2e33e2e5d29138891eabc93969cf24907492dfe` before the rollback continues. +- Roll back both commits carried by merge `3224758`: adaptive ERP entry readiness (`500034b`, extension `0.5.166`) and central OSS/ECS Cloud Assistant updates (`f08aac0`, migration 019 and extension `0.5.167`). The restored current release is extension `0.5.165` with required migration 018. +- Use `git revert`, not a force push or history rewrite. Keep prior task records and the reverted ADR so the decision trail remains auditable and recoverable from the backup branch. +- The local `main` checkout is owned by task `20260902-migrate-restart-confirmed-4f8c2a71` and contains its untracked task record. Do not alter, adopt, stash, reset, or clean that worktree. +- Do not deploy, restart, migrate a database, access ERP, publish to OSS, invoke Cloud Assistant, reload Chrome, mutate live tasks, or inspect secrets. + +## Outcome + +- Remote backup branch `codex/backup-extension-update-20260903-b2e33e2` was created first and independently verified at exact commit `b2e33e2e5d29138891eabc93969cf24907492dfe`. +- Reverted merge `3224758` in the isolated Integration worktree while retaining both source task records and the former integration record. Product source, dependencies, migration readiness, extension source, release package, mappings, tests, and operational documentation now match the pre-iteration `f52d9d7` tree exactly. +- Restored extension `0.5.165`, release ZIP SHA-256 `14f3150ab26d99131e32321ddc805a85550438bc2810a1e20921f7922b25aaac`, and required schema migration `018_agentbus_account_workers`; removed active migration 019 and the central OSS/ECS update implementation from the main-line result. +- Marked `EXT-001` as Reverted and reconciled the decision index, system overview, data flow, current state, task history, evidence index, and rollout commitment. Final rollback commit and remote `main` synchronization remain pending. + +## Verification + +- `git ls-remote --heads origin refs/heads/codex/backup-extension-update-20260903-b2e33e2`: returned exact commit `b2e33e2e5d29138891eabc93969cf24907492dfe`. +- `npm ci --no-audit --no-fund`: completed from the restored lockfile. +- `node --run check:repo`: 10/10 passed, including exact release-set hashes and extension ZIP/source equality. +- `node --run check`: passed. +- `node --run test:control-plane`: 162/162 passed. +- `node --run test:legacy`: 268/268 passed. +- `node --run build`: passed. +- `node --check` passed for extension `background.js`, `business-bridge.js`, and `inpage.js`. +- `unzip -t dist/ltjt-order-assistant-0.5.165.zip`: passed; SHA-256 equals the restored release manifest. +- Explicit version check returned source/release/artifact `0.5.165` and required migration `018_agentbus_account_workers`. +- `git diff --exit-code f52d9d7 -- . ':(exclude).project-docs'`: passed, proving the active product tree exactly matches the pre-iteration baseline. +- `check_project_docs.py`, `check_doc_drift.py --task-id 20260903-backup-revert-extension-update-c71a4e92`, and `git diff --check`: passed before commit. + +## Follow-ups + +- None planned. The reverted implementation remains available only on the named backup branch unless a future explicit decision revives it. + +## Promotion Candidates + +- None recorded. diff --git a/.project-docs/50-evidence/evidence-index.md b/.project-docs/50-evidence/evidence-index.md index ace7457..393fe84 100644 --- a/.project-docs/50-evidence/evidence-index.md +++ b/.project-docs/50-evidence/evidence-index.md @@ -20,7 +20,8 @@ Use this index for searchable, traceable evidence records. | 2026-09-02 | Leadership-dashboard filtering and summary interaction | Repository and focused regression verified; merged runtime not restarted | [Integration record](../30-worklog/tasks/20260902-integrate-all-push-c93a7f21.md) | Single-connection bounded reads, SQL prefiltering, selective hydration, 20-row paging, cancellation/timeout feedback, and display-only metric cards with explicit filters passed combined regression. | | 2026-09-02 | Native ERP product search for ordering forms | Root cause and live search-only behavior verified; repository/release verified; extension not reloaded | [Integration task](../30-worklog/tasks/20260902-integrate-product-search-3b7f6a20.md) | Both `plan_add.asp` and `orders_adds.asp` returned exactly one target product for the full keyword with radios left unchecked and no save; extension `0.5.165` now uses native non-empty search before its bounded empty-query fallback while retaining deterministic unique matching. | | 2026-09-03 | Account-scoped ERP queues, assignee-only executable routing, and force delete | Repository and full regression verified; runtime not deployed or mutated | [Integration task](../30-worklog/tasks/20260903-finalize-account-routing-7c4e2a91.md) | Source task `20260902-per-account-queue-hard-delete-a6d9f2c1` replaced global head-of-line blocking with per-assignee FIFO/advisory locks, isolated SSE/plugin-result/browser-command routing from administrator read visibility, and separated reversible archive from lifecycle-independent physical deletion. | -| 2026-09-03 | Adaptive ERP readiness and central extension host updates | Repository, release package, and full regression verified; production migration/bootstrap/publication not performed | [Integration task](../30-worklog/tasks/20260903-finalize-extension-update-a6c4e192.md) | Source commits `500034b` and `f08aac0` added immediate-first bounded ERP entry readiness, extension `0.5.167`, migration 019, private OSS publication, multi-account ECS host safety, bounded Cloud Assistant deployment, rollback/no-downgrade checks, and target-version heartbeat verification. | +| 2026-09-03 | Adaptive ERP readiness and central extension host updates | Historical repository evidence; reverted from `main` before production rollout | [Original integration task](../30-worklog/tasks/20260903-finalize-extension-update-a6c4e192.md) | Source commits `500034b` and `f08aac0`, release `0.5.167`, and their verification remain recoverable at exact commit `b2e33e2` on remote branch `codex/backup-extension-update-20260903-b2e33e2`; they no longer define active source or release behavior. | +| 2026-09-03 | Extension-update iteration rollback | Backup ref verified; active code/release restored and regression rechecked | [Rollback task](../30-worklog/tasks/20260903-backup-revert-extension-update-c71a4e92.md) | The complete iteration was backed up before a normal revert of merge `3224758`; active main returns to extension `0.5.165` and migration 018 without rewriting Git history. | ## When To Add Evidence diff --git a/.project-docs/80-commitments/commitments.md b/.project-docs/80-commitments/commitments.md index 2d7bf76..7481539 100644 --- a/.project-docs/80-commitments/commitments.md +++ b/.project-docs/80-commitments/commitments.md @@ -9,7 +9,7 @@ Track future-facing memory: promised follow-ups, unfinished loops, timed checks, | 2026-08-28 | Resolve AgentBus OSS attachment rejection caused by private/reserved local DNS answers. | User scheduled integration and confirmed the environment is trusted internal networking | Integration task `20260831-integrate-server-diagnostics-8b42c6d1` | Completed in repository | Deploy/restart and run one live internal attachment verification only under separate authorization. | | 2026-09-02 | Roll out and stage-test AgentBus account workers, account-scoped queues, and force deletion. | Explicit authorization for database backup/migration, service restart, extension reload, account/channel configuration, staging traffic, and disposable task mutation | Future authorized rollout task | Pending authorization | Apply migration 018 and extension `0.5.165`; verify two employee accounts/cloud PCs, ERP identity mismatch, same-account worker conflict/FIFO, cross-account independence, 90-second failover, administrator executable-feed isolation, manual/AgentBus routing, and waiting/active force-delete cleanup. | | 2026-09-02 | Verify the integrated native product-search execution path. | Explicit authorization for extension reload and a guarded ERP preflight/retry | Future authorized rollout task | Pending authorization | Load extension `0.5.165`, confirm the runtime handshake, then verify deterministic product selection and normal write gates without automatically retrying the earlier failed task. | -| 2026-09-03 | Bootstrap and canary the central extension-update channel. | Authorized production maintenance window with database backup, migration, service restart, per-profile Chrome access, restricted RAM/OSS configuration, exact ECS mappings, and a disposable higher extension version | Server operator plus future authorized rollout task | Pending rollout | Deploy with updates disabled; apply migration 019; load shared-directory bootstrap `0.5.167` in every profile; verify heartbeats; configure ECS mappings and least privilege; then enable and observe one host through waiting, running, deployed, reload, and verified before fleet rollout. | +| 2026-09-03 | Bootstrap and canary the central extension-update channel. | Former rollout plan for reverted `EXT-001` | None while reverted | Cancelled by user rollback | Do not deploy migration 019 or extension `0.5.167` from `main`; consult backup branch `codex/backup-extension-update-20260903-b2e33e2` only if a future explicit decision revives the design. | ## Use diff --git a/LianSyn-platform/app.js b/LianSyn-platform/app.js index 2c70cf4..e90ad98 100644 --- a/LianSyn-platform/app.js +++ b/LianSyn-platform/app.js @@ -29,8 +29,6 @@ let erpAutomationEnabled = true; let erpHostAccessReady = true; let erpSessionReady = true; let extensionCompatible = false; -let extensionUpdateBlocksExecution = true; -let extensionReloadRequestedVersion = ''; let latestAiStatus = null; let latestBridgeStatus = null; let activeStatusDetails = ''; @@ -51,9 +49,6 @@ let parserRoutingBusy = false; let accountList = []; let accountTaskTypes = []; let accountSettingsBusy = false; -let extensionReleaseBusy = false; -let extensionReleaseEnabled = false; -let extensionReleaseList = []; let accountAuthorizationTargetId = ''; let accountAuthorizationDraft = new Set(); let auditEvents = []; @@ -104,7 +99,7 @@ const persistedExtensionResultVersions = new Map(); let taskCreateInProgress = false; const AUTO_HANDOFF_RETRY_MS = 30_000; -const REQUIRED_EXTENSION_VERSION = '0.5.167'; +const REQUIRED_EXTENSION_VERSION = '0.5.165'; const MANUAL_HANDOFF_LABEL = '确认并提交到 ERP 插件'; const RETRY_HANDOFF_LABEL = '继续提交到 ERP 插件'; const RECONCILE_LABEL = '只读回查 ERP 现有结果'; @@ -958,8 +953,6 @@ function validateAccountCreationValues(values = {}) { const password = String(values.password || ''); const role = String(values.role || ''); const erpAccount = String(values.erpAccount || '').trim(); - const ecsRegion = String(values.ecsRegion || '').trim().toLowerCase(); - const ecsInstance = String(values.ecsInstance || '').trim(); if (!username || username.length > 160) { return { ok: false, field: 'accountUsername', message: '账号必须为 1—160 个字符。' }; } @@ -972,15 +965,6 @@ function validateAccountCreationValues(values = {}) { if (role !== 'admin' && (!erpAccount || erpAccount.length > 200)) { return { ok: false, field: 'accountErpAccount', message: '普通用户或组长必须填写 1—200 个字符的 ERP 账号。' }; } - if (role !== 'admin' && Boolean(ecsRegion) !== Boolean(ecsInstance)) { - return { ok: false, field: ecsRegion ? 'accountEcsInstance' : 'accountEcsRegion', message: 'ECS 地域与实例必须同时填写或同时留空。' }; - } - if (ecsRegion && !/^[a-z0-9][a-z0-9-]{0,63}$/.test(ecsRegion)) { - return { ok: false, field: 'accountEcsRegion', message: 'ECS 地域 ID 格式无效。' }; - } - if (ecsInstance && !/^i-[A-Za-z0-9]{6,64}$/.test(ecsInstance)) { - return { ok: false, field: 'accountEcsInstance', message: 'ECS 实例 ID 格式无效。' }; - } return { ok: true, body: { @@ -988,8 +972,6 @@ function validateAccountCreationValues(values = {}) { password, role, erp_account: role === 'admin' ? '' : erpAccount, - extension_ecs_region_id: role === 'admin' ? '' : ecsRegion, - extension_ecs_instance_id: role === 'admin' ? '' : ecsInstance, business_route_ids: [] } }; @@ -1025,7 +1007,7 @@ function showAccountCreationValidationError(validation) { } function clearAccountCreationValidationErrors() { - for (const fieldId of ['accountUsername', 'accountPassword', 'accountRole', 'accountErpAccount', 'accountEcsRegion', 'accountEcsInstance']) { + for (const fieldId of ['accountUsername', 'accountPassword', 'accountRole', 'accountErpAccount']) { document.getElementById(fieldId)?.removeAttribute('aria-invalid'); } } @@ -1052,15 +1034,6 @@ function renderAccounts() { main.append(el('p', account.erp_account ? 'muted' : 'channel-error', account.role === 'admin' ? 'ERP 执行身份:不绑定' : `ERP 执行身份:${account.erp_account || '未配置(禁止执行)'}`)); - main.append(el( - 'p', - account.extension_ecs_instance_id ? 'muted' : 'channel-error', - account.role === 'admin' - ? '插件云主机:不绑定' - : account.extension_ecs_instance_id - ? `插件云主机:${account.extension_ecs_region_id} / ${account.extension_ecs_instance_id}` - : '插件云主机:未配置(不自动更新)' - )); const authorizedCount = Array.isArray(account.authorized_business_route_ids) ? account.authorized_business_route_ids.length : 0; @@ -1104,12 +1077,7 @@ function renderAccounts() { erpAccount.dataset.accountAction = 'erp-account'; erpAccount.dataset.accountId = account.id; erpAccount.disabled = accountSettingsBusy || account.role === 'admin'; - const extensionHost = el('button', 'secondary-button', '云主机'); - extensionHost.type = 'button'; - extensionHost.dataset.accountAction = 'extension-host'; - extensionHost.dataset.accountId = account.id; - extensionHost.disabled = accountSettingsBusy || account.role === 'admin'; - actions.append(role, authorizations, erpAccount, extensionHost, toggle, reset, revoke); + actions.append(role, authorizations, erpAccount, toggle, reset, revoke); row.append(main, actions); container.append(row); } @@ -1208,67 +1176,6 @@ async function syncAccounts() { accountTaskTypes = Array.isArray(result.task_types) ? result.task_types : []; renderAccounts(); renderChannelOwnerOptions(); - await syncExtensionReleases().catch((error) => { - const message = $('#extensionReleaseMessage'); - if (message) message.textContent = error.message || String(error); - }); -} - -function renderExtensionReleases() { - const summary = $('#extensionReleaseSummary'); - const button = $('#extensionReleasePublish'); - const file = $('#extensionReleaseFile'); - if (!summary || !button || !file) return; - const active = extensionReleaseList.find((release) => release.is_active === true); - summary.textContent = !extensionReleaseEnabled - ? '服务端自动更新尚未启用;启用配置后才能发布。' - : active - ? `当前活动版本 ${active.version} · ${Math.round(Number(active.byte_size || 0) / 1024)} KB · ${new Date(active.created_at).toLocaleString('zh-CN')}` - : '自动更新已启用,尚未发布任何插件包。'; - button.disabled = extensionReleaseBusy || !extensionReleaseEnabled || !file.files?.length; - file.disabled = extensionReleaseBusy || !extensionReleaseEnabled; -} - -async function syncExtensionReleases() { - if (!isAdministrator() || !IS_ACCOUNTS_PAGE) return; - const result = await apiRequest('/api/extension-updates/releases'); - extensionReleaseEnabled = result.enabled === true; - extensionReleaseList = Array.isArray(result.releases) ? result.releases : []; - renderExtensionReleases(); -} - -async function publishExtensionRelease() { - if (!isAdministrator() || extensionReleaseBusy || !extensionReleaseEnabled) return; - const input = $('#extensionReleaseFile'); - const file = input?.files?.[0]; - if (!file) return; - if (!/\.zip$/i.test(String(file.name || '')) || !file.size || file.size > 50_000_000) { - throw new Error('请选择不超过 50 MB 的版本化插件 ZIP。'); - } - extensionReleaseBusy = true; - renderExtensionReleases(); - const message = $('#extensionReleaseMessage'); - if (message) message.textContent = '正在校验并发布到 OSS…'; - try { - const dataUrl = await new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.addEventListener('load', () => resolve(String(reader.result || '')), { once: true }); - reader.addEventListener('error', () => reject(new Error('无法读取插件 ZIP。')), { once: true }); - reader.readAsDataURL(file); - }); - const packageBase64 = String(dataUrl).slice(String(dataUrl).indexOf(',') + 1); - const result = await apiRequest('/api/extension-updates/releases', { - method: 'POST', - timeoutMs: 120_000, - body: { package_base64: packageBase64 } - }); - if (message) message.textContent = `插件 ${result.release?.version || ''} 已发布;各云主机会在完全空闲后更新。`; - if (input) input.value = ''; - await syncExtensionReleases(); - } finally { - extensionReleaseBusy = false; - renderExtensionReleases(); - } } async function createAccountFromForm() { @@ -1277,9 +1184,7 @@ async function createAccountFromForm() { username: $('#accountUsername').value, password: $('#accountPassword').value, role: $('#accountRole').value, - erpAccount: $('#accountErpAccount').value, - ecsRegion: $('#accountEcsRegion').value, - ecsInstance: $('#accountEcsInstance').value + erpAccount: $('#accountErpAccount').value }); if (!validation.ok) { showAccountCreationValidationError(validation); @@ -1299,8 +1204,6 @@ async function createAccountFromForm() { $('#accountUsername').value = ''; $('#accountPassword').value = ''; $('#accountErpAccount').value = ''; - $('#accountEcsRegion').value = ''; - $('#accountEcsInstance').value = ''; if (message) { message.textContent = result.account?.role === 'admin' ? '管理员账号已创建;该角色固定拥有全部任务权限,初始密码不会再次显示。' @@ -1320,36 +1223,6 @@ async function updateManagedAccountErpIdentity(accountId) { await updateManagedAccount(accountId, { erp_account: value.trim().slice(0, 200) }); } -async function updateManagedAccountExtensionHost(accountId) { - const account = accountList.find((item) => item.id === accountId); - if (!account || account.role === 'admin') return; - const region = window.prompt( - '请输入 ECS 地域 ID(例如 cn-hangzhou)。留空会同时清除该账号的云主机绑定:', - account.extension_ecs_region_id || '' - ); - if (region === null) return; - if (!region.trim()) { - await updateManagedAccount(accountId, { - extension_ecs_region_id: null, - extension_ecs_instance_id: null - }); - return; - } - const instance = window.prompt( - '请输入 ECS 实例 ID(例如 i-bp...)。同一台 Windows Server 上的账号填写相同实例:', - account.extension_ecs_instance_id || '' - ); - if (instance === null || !instance.trim()) return; - if ( - region.trim().toLowerCase() === account.extension_ecs_region_id - && instance.trim() === account.extension_ecs_instance_id - ) return; - await updateManagedAccount(accountId, { - extension_ecs_region_id: region.trim().toLowerCase().slice(0, 64), - extension_ecs_instance_id: instance.trim().slice(0, 80) - }); -} - async function updateManagedAccount(accountId, patch) { if (!isAdministrator() || accountSettingsBusy) return; accountSettingsBusy = true; @@ -2729,7 +2602,7 @@ function isAutomaticTask(task) { } async function autoDispatchReadyTasks({ force = false } = {}) { - if (!authUser || autoHandoffInProgress || extensionUpdateBlocksExecution) return; + if (!authUser || autoHandoffInProgress) return; autoHandoffInProgress = true; try { const result = await apiRequest('/api/tasks?status=confirmed&limit=200&include_total=false&executable_by=me'); @@ -4481,9 +4354,7 @@ function renderStatusDetails() { const status = latestBridgeStatus; const recoveryStatus = String(status?.erpLinkRecovery?.status || status?.erpSession?.erp_keepalive?.recovery_status || ''); const recoveryPending = ['scheduled', 'already_running', 'running'].includes(recoveryStatus); - const updateStatus = String(status?.extensionUpdate?.status || ''); - const updateWarning = Boolean(updateStatus && !['current', 'disabled', 'no_release'].includes(updateStatus)); - const hasWarning = status?.connected && (!status?.compatible || !status?.automationEnabled || !status?.erpHostAccessReady || status?.erpSessionReady === false || recoveryPending || updateWarning); + const hasWarning = status?.connected && (!status?.compatible || !status?.automationEnabled || !status?.erpHostAccessReady || status?.erpSessionReady === false || recoveryPending); title.textContent = '插件状态详情'; refresh.textContent = '重新检测插件'; if (!status) { @@ -4504,8 +4375,6 @@ function renderStatusDetails() { statusDetailRow('插件版本', status?.version || '未知'), statusDetailRow('最低版本', REQUIRED_EXTENSION_VERSION), statusDetailRow('版本兼容', statusBoolean(status?.compatible, '兼容', '需要更新')), - statusDetailRow('自动更新', status?.extensionUpdate?.message || '等待服务端检测'), - statusDetailRow('目标版本', status?.extensionUpdate?.target_version || '尚未发布'), statusDetailRow('ERP 自动化', statusBoolean(status?.automationEnabled, '已开启', '已关闭')), statusDetailRow('ERP 页面权限', statusBoolean(status?.erpHostAccessReady, '可访问', '需在扩展设置中允许')), statusDetailRow('ERP 会话', statusBoolean(status?.erpSessionReady, '正常', '异常或待登录')), @@ -4566,6 +4435,8 @@ async function pingAi() { } function applyBridgePayload(payload = {}) { + const wasBridgeConnected = bridgeConnected; + const previousBridgeInstalledAt = String(latestBridgeStatus?.installedAt || ''); bridgeConnected = Boolean(payload.ok); if (!bridgeConnected) { erpAutomationEnabled = false; @@ -4576,7 +4447,6 @@ function applyBridgePayload(payload = {}) { connected: false, compatible: false, automationEnabled: false, - extensionUpdate: null, version: payload.version || '', installedAt: payload.bridge_installed_at || '', error: payload.message || payload.error || '插件桥接未返回成功状态。', @@ -4626,7 +4496,6 @@ function applyBridgePayload(payload = {}) { status: recoveryStatus || 'not_started', pending: erpLinkRecoveryPending }, - extensionUpdate: latestBridgeStatus?.extensionUpdate || null, version: payload.version || '', installedAt: bridgeInstalledAt, error: !extensionCompatible @@ -4650,40 +4519,12 @@ function applyBridgePayload(payload = {}) { setBridgeState('已连接', 'state-ok'); } renderStatusDetails(); + const bridgeReadyForDispatch = !wasBridgeConnected + || Boolean(bridgeInstalledAt && bridgeInstalledAt !== previousBridgeInstalledAt); + queueMicrotask(() => autoDispatchReadyTasks({ force: bridgeReadyForDispatch }).catch(() => {})); return true; } -function extensionUpdateIsBlocking(update = {}) { - return !['current', 'disabled', 'no_release'].includes(String(update?.status || '')); -} - -async function applyPreparedExtensionUpdate(update = {}) { - const targetVersion = String(update.target_version || ''); - if (!targetVersion || extensionReloadRequestedVersion === targetVersion) return; - extensionReloadRequestedVersion = targetVersion; - try { - const result = await sendToExtension('APPLY_EXTENSION_UPDATE', { - target_version: targetVersion - }, 5_000); - if (result?.accepted !== true && result?.status !== 'already_current') { - throw new Error(result?.message || '插件未接受安全重载请求。'); - } - if (result?.status === 'already_current') { - window.location.reload(); - return; - } - window.setTimeout(() => window.location.reload(), 900); - } catch (error) { - extensionReloadRequestedVersion = ''; - latestBridgeStatus = { - ...latestBridgeStatus, - error: error.message || String(error) - }; - setBridgeState('更新待重载', 'state-warn'); - renderStatusDetails(); - } -} - function setTaskState(text) { const node = $('#taskState'); if (!node) return; @@ -4729,8 +4570,7 @@ window.addEventListener('message', (event) => { const message = event.data || {}; if (message.source !== EXTENSION_SOURCE) return; if (message.type === 'BRIDGE_READY') { - if (authUser) void pingBridge(); - else applyBridgePayload(message.payload || { ok: true }); + applyBridgePayload(message.payload || { ok: true }); return; } if (message.type === 'TASK_RESULT_CHANGED') { @@ -5055,7 +4895,7 @@ async function confirmAndSubmitToErpPlugin(task) { throw new Error('该任务分配给其他平台账号,当前账号只能查看,不能确认或执行。'); } const bridgeReady = await pingBridge(); - if (!bridgeReady || !extensionCompatible || extensionUpdateBlocksExecution || !erpAutomationEnabled || !erpHostAccessReady || !erpSessionReady || latestBridgeStatus?.erpLinkRecovery?.pending === true) { + if (!bridgeReady || !extensionCompatible || !erpAutomationEnabled || !erpHostAccessReady || !erpSessionReady || latestBridgeStatus?.erpLinkRecovery?.pending === true) { throw new Error(`插件未连接、版本低于 ${REQUIRED_EXTENSION_VERSION}、ERP 页面/会话不可用或 ERP 操作开关未开启;任务尚未确认,也未提交到插件。`); } const response = await apiRequest(`/api/tasks/${encodeURIComponent(task.task_id)}/confirm`, { method: 'POST', body: {} }); @@ -5099,36 +4939,9 @@ async function pingBridge() { extension_version: result.version || '', erp_account: authUser.erp_account || '', erp_account_matched: result.erp_session?.account_matched === true, - extension_update_safe: result.extension_update?.safe === true, - metadata: { - bridge_installed_at: result.bridge_installed_at || '', - extension_update_reason: result.extension_update?.reason || '' - } + metadata: { bridge_installed_at: result.bridge_installed_at || '' } } }); - const extensionUpdate = heartbeat.extension_update || { - status: 'disabled', - current_version: result.version || '', - target_version: '', - message: '插件自动更新未启用。' - }; - extensionUpdateBlocksExecution = extensionUpdateIsBlocking(extensionUpdate); - latestBridgeStatus = { - ...latestBridgeStatus, - extensionUpdate, - error: extensionUpdateBlocksExecution - ? extensionUpdate.message || '插件正在更新,已暂停新 ERP 任务。' - : latestBridgeStatus?.error || '' - }; - renderStatusDetails(); - if (extensionUpdateBlocksExecution) { - setBridgeState(extensionUpdate.status === 'reload_required' ? '更新待重载' : '插件更新中', 'state-warn'); - renderStatusDetails(); - } - if (extensionUpdate.action === 'reload') { - void applyPreparedExtensionUpdate(extensionUpdate); - return false; - } if (heartbeat.execution_ready !== true) { erpSessionReady = false; latestBridgeStatus = { @@ -5143,11 +4956,7 @@ async function pingBridge() { renderStatusDetails(); return false; } - if (!extensionUpdateBlocksExecution) { - queueMicrotask(() => autoDispatchReadyTasks({ force: true }).catch(() => {})); - } } catch (heartbeatError) { - extensionUpdateBlocksExecution = true; erpSessionReady = false; latestBridgeStatus = { ...latestBridgeStatus, @@ -5159,12 +4968,11 @@ async function pingBridge() { return false; } } - return bridgeConnected && erpSessionReady && !extensionUpdateBlocksExecution; + return bridgeConnected && erpSessionReady; } catch (error) { bridgeConnected = false; erpAutomationEnabled = false; extensionCompatible = false; - extensionUpdateBlocksExecution = true; latestBridgeStatus = { connected: false, compatible: false, @@ -5623,8 +5431,8 @@ async function pollTaskResult(taskId, { allowReconciliation = false } = {}) { async function reconcileTaskReceipt(task) { if (!canReconcileTask(task)) throw new Error('当前任务不在可只读回查的生命周期不确定状态。'); if (!taskAssignedToCurrentAccount(task)) throw new Error('该任务分配给其他平台账号,当前云电脑不能执行回查。'); - await pingBridge(); - if (!bridgeConnected || !erpHostAccessReady) throw new Error('插件未连接或 ERP 页面权限不可用,未执行回查。'); + const bridgeReady = await pingBridge(); + if (!bridgeReady || !extensionCompatible || !erpHostAccessReady) throw new Error(`插件未连接、版本低于 ${REQUIRED_EXTENSION_VERSION} 或 ERP 页面权限不可用,未执行回查。`); taskReconciliationStates.set(task.task_id, 'running'); renderTaskDetail(); try { @@ -5919,9 +5727,6 @@ document.addEventListener('DOMContentLoaded', async () => { taskAttachmentDrafts.clear(); accountList = []; accountTaskTypes = []; - extensionReleaseEnabled = false; - extensionReleaseList = []; - extensionReleaseBusy = false; accountAuthorizationTargetId = ''; accountAuthorizationDraft = new Set(); historySelectedTaskIds.clear(); @@ -5931,8 +5736,6 @@ document.addEventListener('DOMContentLoaded', async () => { historyBatchMessageKind = ''; currentTaskId = ''; browserConnectionId = ''; - extensionUpdateBlocksExecution = true; - extensionReloadRequestedVersion = ''; sessionStorage.removeItem('liansyn_platform_current_task_id'); $('#rosterAttachment').value = ''; $('#rosterAttachmentStatus').textContent = ''; @@ -6027,11 +5830,7 @@ document.addEventListener('DOMContentLoaded', async () => { const account = accountList.find((item) => item.id === control.dataset.accountId); if (!account) return; const patch = { role: control.value }; - if (control.value === 'admin') { - patch.erp_account = null; - patch.extension_ecs_region_id = null; - patch.extension_ecs_instance_id = null; - } + if (control.value === 'admin') patch.erp_account = null; if (control.value !== 'admin' && !account.erp_account) { const erpAccount = window.prompt('该员工角色必须绑定 ERP 账号,请输入 ERP 页面显示的登录账号:', ''); if (!erpAccount?.trim()) { @@ -6058,7 +5857,6 @@ document.addEventListener('DOMContentLoaded', async () => { return; } if (action === 'erp-account') operation = updateManagedAccountErpIdentity(account.id); - else if (action === 'extension-host') operation = updateManagedAccountExtensionHost(account.id); else if (action === 'toggle') operation = updateManagedAccount(account.id, { is_active: !account.is_active }); else if (action === 'reset-password') operation = resetManagedAccountPassword(account.id); else if (action === 'revoke-sessions') operation = revokeManagedAccountSessions(account.id); @@ -6223,25 +6021,10 @@ document.addEventListener('DOMContentLoaded', async () => { }); $('#accountRole')?.addEventListener('change', (event) => { const erpInput = $('#accountErpAccount'); - const ecsRegionInput = $('#accountEcsRegion'); - const ecsInstanceInput = $('#accountEcsInstance'); const admin = event.target.value === 'admin'; erpInput.required = !admin; erpInput.disabled = admin; - ecsRegionInput.disabled = admin; - ecsInstanceInput.disabled = admin; - if (admin) { - erpInput.value = ''; - ecsRegionInput.value = ''; - ecsInstanceInput.value = ''; - } - }); - $('#extensionReleaseFile')?.addEventListener('change', renderExtensionReleases); - $('#extensionReleasePublish')?.addEventListener('click', () => { - void publishExtensionRelease().catch((error) => { - const message = $('#extensionReleaseMessage'); - if (message) message.textContent = error.message || String(error); - }); + if (admin) erpInput.value = ''; }); $('#aiState').addEventListener('click', () => { toggleStatusDetails('ai').catch(() => {}); diff --git a/LianSyn-platform/index.html b/LianSyn-platform/index.html index 4a0b15a..1f1201e 100644 --- a/LianSyn-platform/index.html +++ b/LianSyn-platform/index.html @@ -99,21 +99,8 @@ - - -
-
-

插件版本发布

-

正在读取服务端发布状态…

-
-
- - -
-

-