- Recovered the existing Docker Compose deployment topology and runtime command set.
- Added a self-contained `deploy/k3s` kustomize deployment with namespace, config, placeholder secrets, infra StatefulSets, app Deployments/Services, migration/minio-init Jobs, and Traefik Ingress.
- Included all server-side runtime services: `tenant-api`, `worker-generate`, `kol-assist-worker`, `scheduler`, `ops-api`, plus `frontend` and `ops-web`.
- Added `Dockerfile.ops-web` so the operations frontend can be built as a deployable nginx image.
- Updated the offline packaging script to build/save `ops-api`, `kol-assist-worker`, and `ops-web`, and to include the k3s directory in generated deploy packages.
- Updated the offline compose override and `.env.example` with the missing ops/security knobs.
-`kubectl apply --dry-run=client` could not complete locally because the configured kube-apiserver at `127.0.0.1:26443` is not running; run live API validation on the target k3s node.
- Re-read the `admin-web` tracking page, monitoring API client, and tenant monitoring handler/service chain to isolate why `立即采集` still depended on the removed browser-plugin runtime.
- Confirmed the frontend was still blocking on `monitoring-plugin` readiness and trying to kickoff a plugin cycle even though monitoring collection is now meant to run silently through `desktop-client`.
- Added monitoring dashboard runtime state so the page now knows whether the current logged-in user's own desktop client is online.
- Tightened `collect-now` server validation to only accept requests when the current actor has an online desktop client, instead of falling back to another online client in the same workspace.
- Updated `TrackingView.vue` to remove the plugin kickoff path, call only the backend `collect-now` endpoint, and disable the CTA with an explicit reason tooltip when brand/keyword/client-online requirements are not met.
- Added the missing `desktop_client_offline` frontend error mapping plus new i18n strings for the button gating reasons.
- Re-read the planning files and recovered the current desktop AI monitoring scope from the existing repo changes.
- Audited the desktop runtime controller, process metrics sampler, runtime snapshot, session registry, and monitor adapter surface.
- Confirmed the current desktop task executor is global-serial, monitor-task staleness dropping is already implemented, and no durable local queue/cache exists yet.
- Confirmed `playwright-core` is installed but `connectOverCDP()` is not wired, while hidden Electron execution already exists via `BrowserWindow` and `WebContentsView`.
- Added `monitor-scheduler.ts` as a durable file-backed local scheduler for monitor tasks, including same-day persistence, stale cross-day pruning, per-platform mutual exclusion, question-level cooldowns, and lease-miss backoff.
- Refactored `runtime-controller.ts` so publish tasks keep a conservative explicit queue while monitor tasks are selected through the local scheduler with adaptive max concurrency from Electron process metrics.
- Extended runtime diagnostics to surface the monitor scheduler snapshot and made the lease manager track multiple active leases instead of a single global lease placeholder.
- Added `playwright-cdp.ts` as a reusable hidden-browser manager that launches hidden Electron `BrowserWindow` instances on account-bound sessions and attaches Playwright via `chromium.connectOverCDP()`.
- Enabled Electron remote debugging in bootstrap so the app exposes a CDP endpoint for local Playwright attachment.
- Extended adapter context with an optional Playwright `{ browser, page }` handle and wired the runtime controller to acquire/release hidden Playwright pages when an adapter declares `executionMode: "playwright"`.
- Kept the existing hidden `WebContentsView` path intact so current adapters like Doubao continue to work unchanged.
- Added optional scheduler metadata to desktop task events in shared types and server publishers so the local scheduler can make safer fan-out decisions before leasing.
- Ran `gofmt` on the modified Go files after the event metadata changes.
- Ported the browser-extension `qwen` monitoring flow into a new desktop Playwright adapter that drives Tongyi's in-page managers via `page.evaluate()` and extracts answer/source data from `__qianwenChatAPI`.
- Registered the new `qwen` monitor adapter in the desktop runtime so monitor tasks can execute through the hidden Playwright CDP layer.
- Tightened generic AI auth fingerprinting to ignore CSRF-only cookies like `XSRF-TOKEN`, which Qwen sets even for anonymous visits.
- Added a Qwen-specific bind detector so the auth window can auto-close once a real session footprint is present, even if the page has not yet rendered an obvious avatar/name signal.
- Updated monitor preflight policy so only `yuanbao`, `kimi`, and `deepseek` require active login before execution; `qwen`, `doubao`, and `wenxin` are allowed to run anonymously when needed.
- Verified `pnpm --filter @geo/desktop-client typecheck` passes after the Qwen adapter, bind-flow, and auth-policy updates.
- Verified `pnpm --filter @geo/desktop-client build` passes after the same changes.
- Followed up on a real Qwen bind stall reported by the user, inspected the machine's persisted `pending-qwen-*` partition, and confirmed logged-in cookies like `tongyi_sso_ticket`, `tongyi_sso_ticket_hash`, and `b-user-id` are present.
- Reworked Qwen account detection so bind/probe can succeed from those persisted auth cookies even when the page nickname/avatar heuristics do not trigger.
- Added a dedicated `server/internal/tenant/prompts` package to centralize platform template seed prompts and runtime prompt fragments.
- Refactored `cmd/dev-seed`, `template_prompt.go`, `template_assist.go`, `prompt_generate_service.go`, and `knowledge_service.go` to read prompt text from the new package.
- Corrected invalid JSON quoting in the seed `custom_placeholder` fields while moving the template card-config blobs.
- Ran `gofmt` across all changed Go files after the prompt extraction.
- Re-scanned the runtime and seed callers to confirm the long prompt literals were removed from business functions and now only live under `server/internal/tenant/prompts`.
- Ran `go test ./internal/tenant/... ./cmd/dev-seed` from `server/` and confirmed the affected packages compile and tests pass.
- Re-read the backend router and transport/app layers to inventory every currently exposed tenant-facing HTTP interface.
- Confirmed the remaining frontend-backed API groups are `templates/articles` and `brands`, while `custom/media/tracking/knowledge` still lack matching backend routes in this repo.
- Recovered the reference screenshot layouts with position-aware OCR so the implementation can follow actual placement and grouping instead of just textual labels.
- Files created/modified:
-`task_plan.md` (updated)
-`findings.md` (updated)
-`progress.md` (updated)
### Phase 11: Admin-Web Full Interface Delivery
- **Status:** in_progress
- Actions taken:
- Added a minimal `vue-i18n` setup with `zh-CN` and `en-US` message bundles, locale persistence, and Ant Design locale switching.
- Reworked `LoginView`, `WorkspaceView`, `TemplatesView`, `ArticleDetailDrawer`, `TemplateGenerationDrawer`, `PageHero`, and `BrandsView` to stop using inline styles and to use component-local `<style>` blocks.
- Restored `AppShell.vue` to the user-preferred version and only moved its inline style attributes into named classes.
- Fixed the broken `pnpm dev:admin` startup path by adding the missing `BrandsView.vue`, running `pnpm install`, and re-running `pnpm typecheck:admin` plus `pnpm dev:admin`.
-`apps/admin-web/src/styles.css` (pending next cleanup pass)
-`task_plan.md` (updated)
-`findings.md` (updated)
-`progress.md` (updated)
### Phase 6: Frontend Scope Recovery
- **Status:** complete
- Actions taken:
- Read the planning files, Claude-side plan artifact, and architecture documents to recover the interrupted task context.
- Confirmed the backend chain is complete locally and the repository still has no `apps/` or `packages/` directories.
- Locked the next useful slice to a tenant-facing `admin-web` foundation rather than platform-side frontend work.
- Files created/modified:
-`task_plan.md` (updated)
-`findings.md` (updated)
-`progress.md` (updated)
### Phase 7: Frontend Foundation
- **Status:** complete
- Actions taken:
- Created the root pnpm workspace, root package scripts, and frontend-oriented `.gitignore`.
- Added shared frontend packages for TS config, API types, and an Axios-based client with auth refresh handling.
- Scaffolded `apps/admin-web` with Vite 8, Vue 3, TypeScript, Ant Design Vue, Pinia, Vue Router, and Vue Query.
- Files created/modified:
-`.gitignore` (created)
-`package.json` (created)
-`pnpm-workspace.yaml` (created)
-`packages/tsconfig/base.json` (created)
-`packages/shared-types/package.json` (created)
-`packages/shared-types/src/index.ts` (created)
-`packages/http-client/package.json` (created)
-`packages/http-client/src/index.ts` (created)
-`apps/admin-web/package.json` (created)
-`apps/admin-web/.env.example` (created)
-`apps/admin-web/index.html` (created)
-`apps/admin-web/tsconfig.json` (created)
-`apps/admin-web/vite.config.ts` (created)
### Phase 8: Admin-Web Initial Features
- **Status:** complete
- Actions taken:
- Implemented the app bootstrap, Ant Design locale wiring, session persistence helpers, and auth store.
- Implemented the login page with seeded local credentials and redirect-aware sign-in flow.
- Implemented the protected shell layout, navigation, quota teaser, workspace dashboard, and placeholder routes for the rest of the information architecture.
- Ran `pnpm install` for the new workspace and generated `pnpm-lock.yaml`.
- Fixed the initial frontend build errors related to Node typings and typed Axios wrapper method parameters.
- Switched Ant Design Vue registration from full-plugin mode to per-component registration to reduce the main bundle size.
- Ran `pnpm build:admin` successfully and confirmed production assets were emitted.
- Ran `pnpm --filter admin-web preview --host 127.0.0.1 --port 4173` and verified `/login` returned `HTTP/1.1 200 OK` with built asset references.
- Attempted browser automation verification, then fell back to HTTP verification after the local Playwright MCP environment failed on `/.playwright-mcp`.
- After Playwright became usable again, started the Vite dev server plus `tenant-api`, discovered the login CTA was not submitting on click, fixed the handler binding, and re-ran browser verification successfully with Playwright CLI.
| CI workflow presence | `find . -path '*/.github/workflows/*' -type f` | At least one CI workflow if Step 9 is complete | `.github/workflows/backend-ci.yml` exists | pass |
| Dev init | `make dev-init` | Infra, migrations, and seed complete successfully | Passed after fixing Makefile-managed migrate driver tags | pass |
| Live health | `GET /api/health/live` | HTTP 200 with alive status | Returned `code=0`, `status=alive` | pass |
| Ready health | `GET /api/health/ready` | HTTP 200 with ready status | Returned `code=0`, `status=ready` | pass |
| Login | `POST /api/auth/login` with `admin@geo.local / Admin@123` | Receive access/refresh tokens and user info | Passed | pass |
| Me | `GET /api/auth/me` with fresh access token | Current user payload returned | Passed | pass |
| Refresh | `POST /api/auth/refresh` with original refresh token | Receive rotated access/refresh token pair | Passed | pass |
| Logout revoke | `POST /api/auth/logout`, then `GET /api/auth/me` with old access token | Old access token rejected | Returned `40103 token_revoked` | pass |
| Refresh rotate invalidate | Reuse old refresh token after refresh | Old refresh token rejected | Returned `40121 refresh_session_expired` | pass |
| Frontend preview asset shell | `curl -s http://127.0.0.1:4173/login` | HTML references built CSS/JS assets | Returned built asset tags for `index-CtRpygQP.js` and `index-DFz5-Rsr.css` | pass |
| Frontend browser login flow | `pnpm dlx @playwright/test test .tmp-playwright/admin-web.spec.js --reporter=line` | Login succeeds and workspace renders | Passed after binding the login CTA directly to `handleSubmit` | pass |
## Error Log
| Timestamp | Error | Attempt | Resolution |
|-----------|-------|---------|------------|
| 2026-03-31 | None yet | 1 | N/A |
| 2026-03-31 | `make sqlc-generate` failed due to invalid schema path in `sqlc.yaml` | 1 | Fixed by changing schema path to `../../../migrations/` |
| 2026-03-31 | `make lint` failed on several existing warnings | 1 | Fixed all reported warnings and re-ran lint successfully |
| 2026-03-31 | `make dev-init` failed because go-run migrate lacked the postgres driver tag | 1 | Updated `MIGRATE_CMD` to `go run -tags postgres ...` and reran successfully |
| 2026-03-31 | Gemini CLI returned `429 MODEL_CAPACITY_EXHAUSTED` on `gemini-3.1-pro-preview` | 1 | Stopped pursuing Gemini once the user said not to use Gemini |
| 2026-03-31 | Initial frontend build failed on missing Node typings and implicit `any` parameters | 1 | Removed the Node type dependency from app TS config, simplified the Vite config, and typed the API client methods explicitly |
| 2026-03-31 | Playwright MCP browser open failed on `/.playwright-mcp` | 1 | Used preview HTTP verification instead of blocking on browser environment setup |
| 2026-03-31 | Browser click on the login CTA did not submit the form, so Playwright stayed on `/login` | 1 | Added an explicit `@click=\"handleSubmit\"` binding to the CTA and re-ran the E2E flow successfully |
- Read the existing planning files and restored context for the monitoring scheduler/runtime work.
- Inspected `cmd/scheduler`, the shared cron helper, `ScheduleDispatchWorker`, monitoring handlers, monitoring service, phase2 desktop task dispatch, migrations, and deployment config.
- Queried local Postgres/Monitoring Postgres containers for current task evidence.
- Verified the data-detail/dashboard read path with existing 2026-04-24 monitoring data.
- Findings so far:
- Current DB evidence shows 2026-04-24 monitoring collection tasks are `desktop/manual/plugin_standard`, not daily automatic tasks.
-`tenant_monitoring_quotas` is configured as `collect_frequency = daily`, but the inspected runtime does not contain a daily monitoring task generation worker.
- Wired `MonitoringDailyTaskWorker` into `server/cmd/scheduler/main.go`.
- Added `server/internal/tenant/app/monitoring_daily_task_worker_test.go` for midnight-after materialization, existing-task idempotency, and deterministic plan ordering.
- Implemented stable jitter after midnight, global/plan/brand batch caps, daily hard-cap selection, and brand-level advisory locking.
- Implemented due desktop dispatch claim with `FOR UPDATE SKIP LOCKED`, `last_dispatched_at`, `dispatch_attempts`, and a retry cooldown.
- Kept automatic daily desktop tasks pending when no eligible desktop account/client is available; no legacy fallback is applied for this path.
- Fixed the P0 rollout-disabled case: daily generation now requires `desktop_tasks` rollout plus `primary_client_id`; otherwise it skips the plan and records `SkippedPlanCount` rather than creating orphan `automatic/legacy` rows.
- Removed Go runtime and dev-seed dependency on `tenant_monitoring_quotas`: daily plans now come from `desktop_clients`, dashboard/projection use default monitoring platforms, and primary client selection is derived from recent/registered desktop clients.
- Fixed the P0 RabbitMQ publish context issue in daily worker: `publishMonitorDesktopTaskAvailable` now receives a 5-second child context derived from the worker tick context instead of `context.Background()`.
- Verification:
-`go test ./internal/tenant/app -run 'Test.*MonitoringDaily|Test.*Monitoring|TestEncodeMonitoringProjectionRebuildEnvelope|TestBuildMonitoringRawPayload' -count=1` passed.
-`go test ./cmd/scheduler ./cmd/dev-seed ./internal/tenant/app -count=1` passed.
-`docker exec geo-monitoring-postgres psql ... EXPLAIN ...` passed for the new claim SQL.
- Completed the plan/brand scoped error-isolation path so transient failures increment summary failure counts and do not stop other tenants/brands in the same tick.
- Added `monitoring_daily_task_metrics.go` with atomic counters/gauges for worker runs, fatal and partial failures, plan/brand failures, skipped plans, task stage counts, last timestamps, duration, business date, and last fatal error code/message.
- Recorded metrics on every `runOnce` result, including zero-work ticks and fatal errors.
- Exposed `/metrics` in Prometheus text format and `/api/internal/metrics/monitoring/daily-tasks` as JSON.
- Started the scheduler's health/metrics HTTP server on `scheduler.http_port` default `8081`.
- Reworked daily hard-cap accounting so existing materialized tasks are counted from DB, full candidate horizons are preserved for de-duplication, and remaining capacity is decremented only by rows inserted in the current tick.
- Split daily materialization limit from desktop dispatch limit, so a full daily cap does not stop already-pending tasks from being dispatched.
- Replaced the hard-coded daily plan defaults with subscription/config-driven rules: active subscription required, max brands from plan/config, platforms from live bound desktop accounts, and per-brand task cap from configured active questions capped by `brand_library`.
- Changed dashboard and projection platform selection so unbound platforms are not silently shown or scheduled.
- Verification:
-`go test ./internal/tenant/app -run 'Test.*MonitoringDaily' -count=1` passed.
-`go test ./internal/tenant/app -run 'Test.*MonitoringDaily|TestReconcile|TestResolve|TestIntersect|TestFilter' -count=1` passed.
-`go test ./cmd/scheduler ./internal/bootstrap ./internal/shared/config -count=1` passed.
-`go test ./cmd/scheduler ./cmd/dev-seed ./internal/tenant/app -count=1` passed.
- Replaced heartbeat primary resolution with a per-tenant/per-workspace `desktop_client_primary_leases` lease row.
- The current primary renews the lease on heartbeat; other desktop clients are non-primary while the incumbent lease is live.
- Failover now happens only when the lease has expired or the recorded primary desktop client is unavailable/revoked.
- Updated daily monitoring plan selection to prefer the sticky lease primary before deterministic fallback.
- Removed `last_seen_at` ordering from the current-user registered-client lookup so UI platform selection is not heartbeat-driven.
- Added migration `20260424130000_add_desktop_primary_leases` with the lease table, backfill, and active desktop-client lookup indexes.
- Added a unit test for the lease-claim decision matrix.
- Verification:
-`go test ./internal/tenant/app -run 'TestShouldClaimHeartbeatPrimaryLease|Test.*MonitoringDaily|TestReconcile|TestResolve|TestIntersect|TestFilter' -count=1` passed.
-`go test ./...` passed from `server/`.
-`git diff --check` passed.
### Phase 26: Scheduler Metrics HTTP Hardening
- **Status:** complete
- Actions taken:
- Added `scheduler.http_host` with default `127.0.0.1`.
- Added `scheduler.internal_metrics_token`, with env overrides `SCHEDULER_INTERNAL_METRICS_TOKEN` / `SCHEDULER_METRICS_TOKEN`.
- Protected `/metrics` and `/api/internal/metrics/monitoring/daily-tasks` with token auth.
- Changed `scheduler.http_port < 0` to mean explicit HTTP disablement; `0` still defaults to `8081`.
- Replaced `app.Engine.Run(... Fatalf ...)` with `http.Server.ListenAndServe`, non-fatal error logging, and graceful `Shutdown`.
- Added scheduler HTTP auth/address tests and config normalization/env tests.
- Verification:
-`go test ./cmd/scheduler ./internal/shared/config -count=1` passed.
-`go test ./...` passed from `server/`.
-`git diff --check` passed.
### Phase 27: Scheduler Worker Graceful Shutdown
- **Status:** complete
- Actions taken:
- Added blocking `Run(ctx)` methods to scheduler workers while keeping existing `Start(ctx)` wrappers.
- Updated `cmd/scheduler` to create workers explicitly and run them under a `sync.WaitGroup`.
- On signal, scheduler now stops the metrics HTTP server, cancels worker loops, and waits up to 60 seconds for workers to finish.
- Changed scheduler worker loops so shutdown cancellation prevents the next tick but does not immediately cancel an active `runOnce`; active work still has the existing per-worker timeout.
- Added tests for worker wait completion and timeout behavior.
- Verification:
-`go test ./cmd/scheduler ./internal/scheduler ./internal/tenant/app -run 'TestScheduler|TestShouldClaimHeartbeatPrimaryLease|Test.*MonitoringDaily' -count=1` passed.
- Added explicit monitoring platform authorization status to dashboard runtime and question-detail API responses.
- Added shared frontend type `MonitoringPlatformAuthorizationStatus`.
- Updated `TrackingView` to keep dashboard platform rows stable and disable collect-now for no-authorized or selected-unauthorized platform states without rendering a no-authorized dashboard alert.
- Updated `TrackingQuestionDetailView` to show an explicit unavailable state and suppress the platform tabs/detail grid when no platforms are authorized.
- Added i18n strings for the new no-desktop, no-authorized, and selected-unauthorized states.
- Added a Go unit test for the authorization-status decision matrix.
- Verification:
-`go test ./internal/tenant/app -run 'TestMonitoringPlatformAuthorizationStatus|TestReconcileEnabledMonitoringPlatforms|TestFilterMonitoringPlatforms|TestResolveDashboardDateWindowAt|TestParseDetailDateRangeAt' -count=1` passed.
-`pnpm typecheck:admin` passed.
- Browser regression against admin-web with `platform_authorization_status = no_authorized_platforms` passed for dashboard and question detail; dashboard does not show a no-authorized alert.
-`go test ./...` passed from `server/`.
-`pnpm build:admin` passed; it still reports existing bundle/CSS warnings unrelated to this change.
- Changed `loadMonitoringProjectionPlatforms` to return the canonical six-platform catalog instead of deriving display rows from same-day task rows.
- Changed dashboard platform breakdown to use the same six-platform display catalog, so platforms with zero tasks/samples still appear.
- Added `authorized_monitoring_platform_ids` to dashboard runtime and shared frontend types.
- Updated `TrackingView` collect-now gating to use the explicit authorized platform ID list instead of inferring authorization from visible platform rows.
- Added unit coverage for the six-platform catalog copy and projection platform selection.
- Verification:
-`go test ./internal/tenant/app -run 'TestDefaultMonitoringPlatformMetadata|TestLoadMonitoringProjectionPlatforms|TestMonitoringPlatformAuthorizationStatus|TestReconcileEnabledMonitoringPlatforms|TestFilterMonitoringPlatforms' -count=1` passed.
-`pnpm typecheck:admin` passed.
- Browser regression confirmed a no-authorized-platform dashboard still renders all 6 platform names and keeps collect-now disabled.
### Phase 30: Workspace Scope And Daily Config Visibility
- **Status:** complete
- Actions taken:
- Added current-workspace context helpers and a `WorkspaceScope` middleware with `X-Workspace-ID` support plus membership validation for non-primary workspace requests.
- Wired the tenant route group through `WorkspaceScope`.
- Updated monitoring dashboard, question detail, and collect-now paths to use the current request workspace for quota/client/platform-account/access-state resolution.
- Documented `findLatestRegisteredClientForUser` as created-at ordered and intentionally independent of online health.
- Changed daily worker platform JSON decoding to return errors instead of swallowing malformed JSON.
- Counted malformed daily platform config as a plan failure and logged it with tenant/workspace/business-date context.
- Added unit coverage for current workspace fallback/override, workspace-id parsing, and daily platform JSON decode failures.
- Verification:
-`go test ./internal/shared/auth ./internal/shared/middleware ./internal/tenant/app -run 'TestCurrentWorkspaceID|TestWorkspaceScope|TestRequestedWorkspaceID|TestDecodeDailyMonitoringEnabledPlatforms|Test.*MonitoringDaily|TestMonitoringPlatformAuthorizationStatus|TestDefaultMonitoringPlatformMetadata|TestLoadMonitoringProjectionPlatforms' -count=1` passed.
-`go test ./internal/tenant/transport -run TestProtectedRoutes -count=1` passed.
-`go test ./...` passed from `server/`.
-`git diff --check` passed.
### Phase 31: Heartbeat Primary Lease Scalability
- **Status:** complete
- Actions taken:
- Replaced per-heartbeat `BEGIN` / `SELECT ... FOR UPDATE` / `UPDATE` primary lease handling with read-mostly lease resolution.
- Normal primary heartbeats now return from a plain read unless the renew interval has elapsed.
- Non-primary heartbeats return from a plain read while the incumbent lease is live and available.
- Missing, expired, unavailable, and race cases use single-statement `INSERT` or CAS-style `UPDATE` writes instead of an explicit lock transaction.
- Added heartbeat primary lease metrics for hit/miss outcomes, DB writes, contention, errors, and duration.
- Added token-protected tenant-api internal metrics endpoints for heartbeat primary lease JSON and Prometheus output.
- Documented the renew throttle relative to the 25s desktop heartbeat and 90s TTL.
- Verification:
-`go test ./internal/tenant/app -run 'TestDecideHeartbeatPrimaryLease|TestMonitoringHeartbeatPrimaryLeaseMetrics|TestBuildMonitoringRawPayloadKimi' -count=1` passed.
-`go test ./internal/tenant/transport -run 'TestProtectedRoutes|TestPublicRoutes' -count=1` passed.
- Changed `MonitoringCallbackService.Heartbeat` to batch-load current access snapshot state before deciding whether a monitoring DB transaction is required.
- Stable unchanged platform access reports now skip snapshot upserts; unchanged rows refresh at most every 10 minutes.
- Primary-client inaccessible-platform reconciliation now first checks whether matching pending tasks exist, so stable inaccessible reports with no pending work do not open a write transaction.
- Added heartbeat snapshot metrics for platform reports, transaction opens, snapshot insert/update/refresh/skip counts, reconciled tasks, projection rebuilds, errors, and duration.
- Exposed heartbeat snapshot metrics through token-protected tenant-api internal JSON and Prometheus endpoints.
- Added a monitoring DB partial index for pending-platform reconciliation lookup.
- Verification:
-`go test ./internal/tenant/app -run 'TestDecideMonitoringHeartbeatSnapshot|TestMonitoringHeartbeatSnapshotMetrics|TestDecideHeartbeatPrimaryLease|TestMonitoringHeartbeatPrimaryLeaseMetrics' -count=1` passed.
-`go test ./internal/tenant/app -count=1` passed.
-`go test ./internal/tenant/transport -count=1` passed.
- Added `prometheus/client_golang` and replaced custom hand-written Prometheus text builders with `prometheus.Collector` implementations plus `promhttp`.
- Changed scheduler `/metrics` to serve a dedicated registry containing daily-task metrics plus Go runtime and process collectors.
- Changed tenant-api internal heartbeat Prometheus endpoints to serve through `promhttp` as well.
- Removed raw error-message labels from heartbeat snapshot Prometheus output; detailed messages remain available in JSON snapshots.
- Added explicit carriage-return normalization for label values before creating const metrics, because the local client_golang text path does not escape raw CR.
-`go test ./internal/tenant/app -run 'TestMonitoringDailyTaskMetrics|TestMonitoringDailyTaskPrometheus|TestMonitoringSchedulerMetricsPrometheusHandler|TestMonitoringHeartbeatPrimaryLeaseMetrics|TestMonitoringHeartbeatSnapshotMetrics' -count=1` passed.
-`go test ./cmd/scheduler ./internal/tenant/transport -run 'TestScheduler|TestProtectedRoutes|TestPublicRoutes|TestDesktopClientRoutes' -count=1` passed.
-`go test ./...` passed from `server/`.
-`git diff --check` passed.
### Phase 34: Daily Task Batch Materialization
- **Status:** complete
- Actions taken:
- Changed `ensureDailyMonitoringCollectTasks` from per-candidate `tx.Exec` to a single batched `INSERT ... SELECT FROM unnest(...)`.
- Batched arrays carry `question_id`, `question_hash`, `ai_platform_id`, and `dispatch_after`; tenant/brand/business date/collector/run mode/target client/shard key/execution owner remain constant per call.
- Kept `ON CONFLICT DO NOTHING`, so duplicate materialization still returns only the number of newly inserted rows via `RowsAffected`.
- Verification:
-`go test ./internal/tenant/app -run 'Test.*MonitoringDaily|TestMonitoringDailyTaskMetrics|TestMonitoringDailyTaskPrometheus|TestMonitoringSchedulerMetricsPrometheusHandler' -count=1` passed.
-`go test ./...` passed from `server/`.
-`git diff --check` passed.
### Phase 35: Daily Plan SQL Decomposition
- **Status:** complete
- Actions taken:
- Split `loadDailyMonitoringPlans` into `loadDailyMonitoringPrimaryClientPlans`, `loadDailyMonitoringPlanPlatformJSON`, and a pure assembly helper.
- Kept the first query focused on active subscription admission, lease-preferred primary client selection, workspace scoping, and subscription/config-derived brand/question limits.
- Moved authorized platform aggregation to a second batched query keyed by `(tenant_id, workspace_id, client_id)`.
- Added a helper test covering the race where a selected client loses its platform snapshot between the two reads; that candidate is skipped instead of becoming an empty executable plan.
- Verification:
-`go test ./internal/tenant/app -run 'TestBuildDailyMonitoringPlansFromSelectedClients|Test.*MonitoringDaily' -count=1` passed.
-`go test ./internal/tenant/app -run 'Test.*MonitoringDaily|TestMonitoringDailyTaskMetrics|TestMonitoringDailyTaskPrometheus|TestMonitoringSchedulerMetricsPrometheusHandler' -count=1` passed.
-`go test ./...` passed from `server/`.
-`git diff --check` passed.
### Phase 36: Monitoring Authorization And Detail Display Bug Fix
- **Status:** complete
- Actions taken:
- Changed monitoring platform authorization to mean a bound, non-deleted platform account on a supported AI platform; `platform_accounts.health` no longer has to be `live`.
- Removed `health = 'live'` from daily scheduled plan eligibility and platform aggregation, so daily automatic collection can materialize tasks for offline clients and non-live account states.
- Kept immediate collection gated by desktop-client online state, and made the online check target the same desktop client that owns the authorized platform accounts.
- Changed question detail to display the canonical platform history instead of hiding existing data behind current `platform_authorization_status`.
- Updated frontend date validation to use the monitoring business date, so today's date such as 2026-04-24 is not rejected by local/browser timezone drift.
- Verification:
-`go test ./internal/tenant/app -run 'Test.*MonitoringDaily|TestMonitoringPlatformAuthorizationStatus|TestReconcileEnabledMonitoringPlatforms|TestFilterMonitoringPlatforms|TestDefaultMonitoringPlatformMetadata|TestLoadMonitoringProjectionPlatforms' -count=1` passed.
- Confirmed the current runtime only starts publish when `state.activeExecutions.size === 0`, so an already-running monitor can block media publish.
- Locked the implementation model: publish is foreground, monitor is background, publish receives reserved runtime capacity, and publish platform policy stays in a lightweight in-memory scheduler.
- Added `apps/desktop-client/src/main/publish-scheduler.ts` with in-memory queueing, same-platform active locks, and 3-6 second platform cooldowns.
- Refactored `runtime-controller.ts` so publish can start while monitor is running, monitor pauses behind any publish backlog, and lease-in-flight is tracked separately for publish vs. monitor.
- Added hardware/runtime adaptive total concurrency with `GEO_DESKTOP_MAX_TOTAL_CONCURRENCY` as an optional cap and a reserved publish slot for monitor budgeting.
- Exposed `publishScheduler` in the desktop runtime snapshot and added scheduler unit tests.
-`pnpm --filter @geo/desktop-client typecheck` failed before checking runtime changes because `electron.vite.config.ts` has a Vite 5/8 plugin type mismatch.
- Queried local `geo-postgres` through `docker exec` because `psql` is not installed on the host.
- Verified article `95` reconstructs to normal markdown through `repository.LoadArticleVersionContent`; removed the temporary inspection test after use.
- Updated `apps/desktop-client/src/main/adapters/dongchedi.ts` so body images first try Dongchedi `spice/image`; local/relative images use ImageX upload and then run through `spice/image` before replacement.
- Added redacted Dongchedi publish rejection diagnostics to the adapter error payload/logs.
- Added `apps/desktop-client/src/main/adapters/dongchedi-content.ts` and a unit test to downgrade table HTML to paragraph rows before Dongchedi submit.
- Followed up after another `server exception` retry where diagnostics showed no table tags, no local asset URL, and one body image.
- Removed ` ` entities from Dongchedi table-downgraded body HTML.
- Changed body image handling so ImageX-uploaded local images try Dongchedi `spice/image` through both session and page-context requests before replacement.
- After the platform returned `dongchedi_image_upload_failed:content_images_missing_1_of_1`, tried an ImageX direct URL fallback; the next retry still returned `server exception` with `content_image_upload_strategies = ['imagex_direct_fallback']`.
- Removed the direct ImageX正文图 fallback. Local正文图片 that cannot be converted to a Dongchedi `spice` URL are now removed from `content` instead of failing early or submitting a rejected ImageX URL.
- After a no-body-image retry still returned `server exception`, changed `extra.content_word_cnt` from the reference extension's hardcoded `267` to the actual text count of the final submitted HTML.
- Added `content_word_cnt` and `computed_text_count` to Dongchedi rejection diagnostics.
- Expanded rejection diagnostics with body image host summaries, body image upload strategies, tag/entity counts, content end excerpt, and cover upload dimensions/hosts.
| Where am I going? | Next useful slice is turning the placeholder routes into real pages, starting with template creation and brand management |
| What's the goal? | Resume the interrupted task by adding the missing `admin-web` frontend foundation on top of the finished backend |
| What have I learned? | The tenant backend is stable enough to support a real login + dashboard frontend slice, and a real browser pass was valuable because it caught a submit wiring bug that static checks missed |
| What have I done? | Added the pnpm frontend workspace, built `apps/admin-web`, wired auth and dashboard data to `tenant-api`, reduced the bundle size, and passed install/build/preview plus browser login verification |
- Investigated the "普通通用模版" cards shown in the screenshot.
- Confirmed the four platform templates are stored in `article_templates` and currently seeded by `server/cmd/dev-seed/main.go`.
- Confirmed production deployment jobs only run database migrations, so a fresh environment can miss the four template records unless dev seed is run manually.
- Decision: add a production-safe platform-template initializer and wire it into the migrate job/image instead of running the broad dev seed path.
- Kept `deploy-config-ci.yml` validation-only after the user clarified it should not own images.
- Scoped `frontend-ci.yml` to `admin-web` and `ops-web` plus shared packages/Dockerfiles; removed browser-extension typecheck from this workflow.
- Added Gitea Registry publishing to frontend/backend CI with commit8 tags and skip-if-tag-exists behavior through `deploy/scripts/gitea-registry-images.sh`.
- Added commit8 image verification to compose/k3s/offline deployment paths so CD fails if CI has not produced the required image.
- Changed offline package defaults to use commit8 instead of tag names and to pull business images from the Gitea Registry.
- Verification passed: workflow YAML parse, deploy script `bash -n`, and `sh -n` over all workflow run blocks.
- Confirmed the existing repository topology uses application-side `pgxpool` and direct Postgres service names; no PgBouncer service/config existed before this change.
- Started Phase 40 to add stable PgBouncer deployment with session pooling, app runtime traffic through PgBouncer, and migration jobs remaining direct to Postgres.
- Added `pgbouncer` and `monitoring-pgbouncer` to production Docker Compose and k3s using `edoburu/pgbouncer:v1.24.1-p1`, `POOL_MODE=session`, and `AUTH_TYPE=scram-sha-256`.
- Changed runtime app configs to target PgBouncer while keeping Docker/k3s migration jobs direct to `postgres` / `monitoring-postgres`.
- Made deployment passwords flow from `POSTGRES_PASSWORD` / `OPS_POSTGRES_PASSWORD` placeholders and changed `DatabaseConfig.DSN()` to URL-escape credentials for stronger production passwords.
- Included PgBouncer in offline packaging and docs.
- Started Phase 41 for the Revision 8 content compliance detection design.
- Re-read the design document sections covering data model, API contracts, publish gate ordering, manual-review lifecycle, ops pages, and test requirements.
- Confirmed the current code has no compliance module yet and that publish job creation currently bypasses content compliance entirely.
- Added Phase 41-43 to `task_plan.md` and recorded the initial backend/frontend integration findings.
- Wired `PublishJobService.Create()` through `GateForPublish`, including effective article version resolution, target platform aggregation, block/needs_ack errors with serialized check details, and transactional ack consumption.
- Exposed `ArticleDetailResponse.current_version_id` and extended desktop publish job creation with article/version compliance metadata.
- Added ops compliance service/handlers/routes for dictionaries, terms, policy, master switch, manual reviews, records, and stats.
- Added LLM review AI-point reservation/completion/refund handling in tenant compliance jobs and wired the review worker to the configured LLM provider.
- Extended desktop publish task listing with publish-job compliance block metadata and updated desktop Publish Management to show `合规阻断`, record id, intercepted time, and a readable reason summary.
- Added admin-web publish status mapping/i18n for `blocked_by_compliance`.
- Verification passed:
-`go test ./internal/tenant/compliance ./internal/tenant/transport ./internal/tenant/app ./internal/worker/generate ./cmd/worker-generate`
-`go test ./...`
-`pnpm --filter admin-web typecheck`
-`pnpm --filter ops-web typecheck`
-`pnpm --filter @geo/desktop-client typecheck`
-`pnpm --filter admin-web build`
-`pnpm --filter ops-web build`
-`pnpm --filter @geo/desktop-client build`
-`git diff --check`
- Notes:
- Admin-web and ops-web production builds still warn about large chunks from existing Ant Design / Milkdown bundles.
- Removed the ops-web tenant override route, menu item, API helpers, and `ComplianceTenantOverridesView.vue`.
- Removed the ops-api tenant policy override routes, handlers, service methods, audit action, and tenant override resolver branch.
- Changed `ops.compliance_policies` to an `id=1` global singleton with no `scope` / `tenant_id` columns; seed migrations and tenant-api policy loading now read/update that singleton directly.
- Updated the current compliance design doc, the archived v1 design doc, and planning notes so tenant policy overrides are no longer documented as a supported feature.
- Verification passed:
-`pnpm --filter ops-web typecheck`
-`go test ./internal/ops/app ./internal/ops/transport ./internal/tenant/compliance ./cmd/ops-api`
- Started Phase 44 after the user requested large-company style safeguards: structured logs, verifiable states, no swallowed errors, and automatic alerts.
- Confirmed the remaining weak points are in article body generation worker paths, not title/outline assist paths:
- Initial task `running` updates are ignored in template/custom/imitation generation execution.
- Failure cleanup logs status update errors but still swallows quota ledger/refund errors.
- Recovery requeue/finalization logs do not carry a consistent event/stage/anomaly schema yet.
- Scope locked to generation worker/app observability and state checks, avoiding unrelated deployment/compliance dirty files.
- Added structured article-generation task log context and counters for transitions, failures, cleanup failures, queue failures, recovery actions, state anomalies, and state-check failures.
- Added bounded scheduler-owned generation state consistency checks with configurable interval/timeout/batch/lookback and partial DB indexes; this is a low-frequency safety net, not a full-table polling loop.
- Added internal JSON/Prometheus metrics endpoints and Prometheus alert rules for cleanup failures, state anomalies, state-check failures, queue failures, failure spikes, and recovery finalization.
- Fixed compile/test follow-ups: matched the state-check DB interface to `pgx.Rows` and made the Prometheus label assertion order-independent.
- Verification passed:
-`cd server && go test ./internal/shared/config ./internal/tenant/app ./internal/tenant/transport ./internal/worker/generate ./cmd/scheduler ./cmd/worker-generate`
- Fixed the desktop media account cross-device leak reported from Mac/Windows login:
-`/api/desktop/accounts` now lists accounts by the authenticated desktop `client_id`, not `workspace_id + user_id`.
- Desktop account identity lookup and upsert now use `(workspace_id, client_id, platform_id, platform_uid)`, preventing one machine from overwriting another machine's row for the same third-party UID.
- Desktop-side patch/delete and async health sink updates now also require matching `client_id`.
- Added migration `20260506120000_scope_platform_accounts_to_desktop_client` to update the active uniqueness index.
- Added MAC-address fingerprint fallback for desktop client ID generation while keeping OS machine ID as the preferred stable source.
- Verification passed:
-`cd server && make sqlc-generate`
-`cd server && go test ./internal/tenant/app ./internal/tenant/repository ./internal/tenant/transport -count=1`
-`pnpm --filter @geo/desktop-client typecheck`
-`pnpm --filter @geo/desktop-client exec vitest run src/main/device-id.test.ts`
-`git diff --check`
- Caveat:
-`pnpm --filter @geo/desktop-client test -- src/main/device-id.test.ts` ran unrelated existing tests and failed in `deepseek.test.ts` due to a pre-existing missing `electron/main` mock; the new device-id test passes when run directly with vitest.
- Started Phase 48/49 for the user's content-management "发文管理" request.
- Confirmed desktop-client already has a publish management table with the requested columns/actions, but admin-web needs tenant HTTP APIs because Electron IPC is unavailable in the browser.
- Investigated `page.evaluate: ReferenceError: wO is not defined` reported for desktop-client Bilibili publishing and then confirmed dongchedi has the same risk pattern.
- Root cause: packaged desktop builds run `build:obf`; `scripts/obfuscate.cjs` obfuscated `out/main/*.cjs`, and Playwright `page.evaluate` callbacks then carried obfuscator helper names into the browser page context where those helpers do not exist.
- Updated `apps/desktop-client/scripts/obfuscate.cjs` so the Electron main bundle is left unobfuscated and only renderer assets are obfuscated. This aligns the post-build obfuscation step with the existing Vite main-process `minify: false` guard.
- Verification passed:
-`pnpm --filter @geo/desktop-client typecheck`
-`pnpm --filter @geo/desktop-client build:obf`
-`pnpm --filter @geo/desktop-client exec vitest run src/main/adapters/dongchedi.test.ts`
-`pnpm --filter @geo/desktop-client exec vitest run src/main/adapters/baijiahao.test.ts`
- Searched `apps/desktop-client/out/main/bootstrap.cjs` for `wO`, `_0x...`, `stringArray`, `javascript-obfuscator`, and `['evaluate']`; no obfuscator helper traces were present after `build:obf`.
- Scoped implementation to tenant publish task list/retry endpoints plus a new admin-web `/publish-management` page under 内容管理.
- Noted existing dirty files in i18n/Knowledge/TemplateWizard/config/swagger/knowledge service and will avoid reverting or overwriting unrelated changes.
- Investigated the Doubao monitor failure where the answer page showed "已完成思考 / 搜索 N 个关键词,参考 N 篇资料" links but the adapter returned `unknown`.
- Current Doubao behavior observed from user screenshots:
- citation/source links now live inside the thinking block rather than next to the final answer;
- one answer can contain multiple "搜索 X 个关键词,参考 Y 篇资料" groups;
- "已完成思考,参考 N 篇资料" is a broad thinking summary control, while the search groups hold the concrete blue source links;
- already-expanded groups may still show a chevron, so clicking blindly can collapse useful references.
- Updated `apps/desktop-client/src/main/adapters/doubao.ts` so `/chat/completion` and `text/event-stream` responses remain the preferred SSE source, while a high-quality page answer can be used when SSE has not finished.
- Added helper coverage in `doubao.test.ts` for unfinished SSE + usable page answer, finished SSE precedence, recoverable timeout with anchored page answer, and multi-group thinking references.
- Follow-up fix: the `unknown` message "未从正文或思维链兜底抓到可提交答案,仅抓到参考资料" still happened because the implementation only used `domAnswer` as the fallback answer and did not submit `domReasoning`. The adapter now allows a high-quality thinking-chain text fallback when SSE/page answer text is missing but references and anchored page context were captured.
- Local anonymous Playwright could open Doubao only to the logged-out/home page, so live account-specific MCP/page content was not directly readable in this session.
- Verification passed:
-`pnpm --filter @geo/desktop-client exec vitest run src/main/adapters/doubao.test.ts`
-`pnpm --filter @geo/desktop-client typecheck`
-`pnpm --filter @geo/desktop-client test`
-`git diff --check`
## 2026-06-24T01:20:00+08:00 - Doubao SSE capture and challenge handling
- Confirmed from the latest user screenshot that Doubao can trigger an official-site image captcha after a prompt is submitted. This is an account/platform challenge, not an answer parsing failure.
- capture Doubao `/chat/completion` SSE in-page through fetch/XHR stream cloning and keep SSE as the primary answer/source path;
- keep DOM/page extraction as fallback, including thinking/reference panels for `search_results` without promoting reference lists to `answer`;
- verify composer write/readback before sending, avoid selecting voice/microphone controls as send buttons, and add a Playwright keyboard/mouse submit fallback when the page does not observe the user question;
- detect Doubao captcha/risk-control surfaces such as image selection, drag-to-area, human verification, security verification, and frequent-access prompts;
- return `doubao_challenge_required` as failed/non-retryable AI platform authorization when a captcha is visible, so the desktop UI alerts the user instead of writing `unknown`.
- Updated `apps/desktop-client/src/main/platform-auth-adapters.ts` so `doubao_challenge_required` is classified as a challenge.
- Updated `apps/desktop-client/src/main/runtime-controller.ts` so Doubao challenge states show a specific user-facing activity: complete Doubao human verification in the desktop client before continuing collection.
- Verification passed:
-`pnpm --filter @geo/desktop-client exec vitest run src/main/adapters/doubao.test.ts src/main/platform-auth-adapters.test.ts`
-`pnpm --filter @geo/desktop-client typecheck`
-`git diff --check`
-`pnpm --filter @geo/desktop-client build`
- Remote SSH to `39.105.229.239` with the provided credentials failed with authentication failure, so remote logs/deployment could not be inspected in this pass.
## 2026-06-24T02:22:00+08:00 - Doubao captcha display, admission, and default mode
- Updated desktop HomeView issue aggregation so Doubao human-verification/account challenge failures collapse into one actionable item: "豆包需要人工验证"; the summary count now follows displayed issue rows instead of raw failed task rows.
- Added monitor admission filtering for blocked AI platforms:
- desktop runtime computes an eligible monitor platform whitelist from current local account health;
- Doubao `challenge_required`/expired/revoked states are excluded from local monitor selection, desktop-task monitor lease fallback, legacy monitoring lease fallback, and resume calls;
- other AI platforms remain eligible, so a Doubao captcha no longer blocks Qwen/Kimi/DeepSeek/etc. work.
- Extended `/api/desktop/tasks/lease` with optional `platform_ids` for monitor leases and filtered the server-side monitor candidate SQL by that whitelist.
- Changed Doubao execution to use the default visible mode directly:
- removed active in-page switching to "专家"/"思考" and removed deep-think selection parameters from Doubao query execution;
-`provider_model` now reports `doubao-web-fast` for default/fast and for any observed expert label, with no `doubao-web-expert` remaining.
- Verification passed:
-`pnpm --filter @geo/desktop-client exec vitest run src/main/adapters/doubao.test.ts src/main/platform-auth-adapters.test.ts`
-`pnpm --filter @geo/desktop-client typecheck`
-`pnpm --filter @geo/desktop-client build`
-`cd server && go test ./internal/tenant/app ./internal/tenant/transport`
-`git diff --check`
## 2026-06-24T02:29:25+08:00 - Doubao DOM source strictness
- Refined Doubao citation extraction after the user clarified that captcha/risk-control pages may legitimately have no reference sources.
- Kept SSE as the preferred and broad source path, including structured references that embed Doubao redirect URLs in source payloads.
- Tightened DOM/page fallback so `search_results` only comes from real link/card URLs and redirect URL attributes; gray search-query/explanatory text such as "search N keywords, reference N sources" is no longer converted into citation sources.
- Removed the page-side fallback that scraped arbitrary `http` URLs from element text, so expanded blue reference links are still captured while gray search keyword text is ignored.
- Verification passed:
-`pnpm --filter @geo/desktop-client exec vitest run src/main/adapters/doubao.test.ts src/main/platform-auth-adapters.test.ts`
- Confirmed `npx` and `playwright-core` are available and connected Playwright to the running Electron CDP endpoint on port 9339.
- Located two authenticated Yuanbao chat pages and inspected the live composer/tool controls.
- Reproduced a web-search verification defect: the page had only `深度思考` active while `联网搜索` remained an unselected tools-menu item, but the adapter can optimistically return enabled when the menu item exposes no ARIA state.
- Captured `apps/desktop-client/output/playwright/yuanbao-tools.png` as local visual evidence.
- Existing API-vs-DOM priority regression test remains red pending the implementation fix.
- Submitted a short search-enabled query through the real desktop Yuanbao page over CDP and captured the safe request/response shape.
- Verified the official chat SSE endpoint, real search request flags, final `meta.endConv`, and the large ratio of `deepSearch` process frames to answer `text` frames.
- Added regression coverage for strict API priority, page fallback, current-chat SSE filtering, Markdown blank-line preservation, and exclusion of nested deep-search text from the final answer.
- Initial live-test attempt selected a stale hard-coded conversation ID and failed before adapter execution. The selector was corrected to any current Yuanbao chat page.
- One CDP probe called `browser.close()` and stopped the attached Electron Chromium. The existing `electron-vite` development process restarted Electron automatically; subsequent probes exit without sending a remote close command.
- Opened the bound Yuanbao account again through the desktop AI-platform card and ran the actual updated adapter through CDP.
- Live adapter verification passed: API SSE source selected, web search verified active, exactly one current chat SSE captured, and answer non-empty.
- Final verification passed:
-`pnpm --filter @geo/desktop-client exec vitest run src/main/adapters/yuanbao.test.ts` (16/16)
- targeted Prettier formatting and `git diff --check`
## 2026-07-12T13:30:00+08:00 - AI platform multi-account support started
- Added Phases 53-56 for storage/runtime audit, binding/UI changes, same-platform monitor failover, and verification.
- Initial hypothesis: server uniqueness already permits multiple platform UIDs per desktop client, while desktop projection/binding and monitor task targeting still enforce effective single-account behavior.
- Phase 53 audit completed. No server migration is needed; implementation is scoped to desktop dedupe/binding, platform-blocking, local account-pool failover, diagnostics, and the AI account-management UI.
- Added a tested monitor-account pool with preferred-first/LRU ordering, blocked/cooldown filtering, failover classification, and reason-specific cooldowns.
- Removed desktop AI platform-only deduplication and same-platform replacement cleanup; distinct platform UIDs now retain independent server rows and local session partitions.
- Added same-platform monitor attempts inside one leased task, final-result-only submission diagnostics, account health reporting, runtime switch activities, and per-account risk-control alerts.
- Rebuilt AI Platform Management as a media-account-style multi-account page and verified its desktop layout over Electron CDP.
## 2026-07-12T14:20:00+08:00 - AI platform multi-account server invariant
- Confirmed server monitor targeting remains one target per platform even when several same-platform accounts are bound.
-`uniqueMonitorDesktopTaskPlatformIDs()` de-duplicates requested platforms before account lookup, and `selectMonitorDesktopTaskTargetsWithOptions()` selects a single best target from each platform's account candidates.
- Added focused assertions covering duplicate platform specs and the one-target result for multiple same-platform accounts.
## 2026-07-12T14:35:00+08:00 - AI platform multi-account verification complete
- Focused server targeting tests passed, including duplicate platform de-duplication and one-target selection for multiple same-platform accounts.
- Full desktop verification passed: 30 files / 206 tests, Vue typecheck, production Electron build, targeted Prettier check, and `git diff --check`.
- Electron CDP UI verification remains valid at 1420x960: six platform selectors, three separately managed AI accounts, add-account choices for all six platforms, working Doubao filtering, and no overflow or overlap.
- Residual live-test limitation: the UI was exercised with real bound accounts, but automatic failover could not be triggered against two simultaneously usable Doubao accounts in this environment. The selection, cooldown, error classification, and final-result-only behavior are covered by deterministic tests.
## 2026-07-12T14:50:00+08:00 - AI/media account UI alignment started
- Added Phases 57-58 after the user clarified that AI Platform Management must visually match Media Account Management, not only expose equivalent account-management functions.
- Audited both live Vue views and identified the mismatch: AI used compact selector cards plus a custom account table, while media uses larger authorization cards and a shared hero-card table shell.
- Locked the change to renderer composition and styles; multi-account runtime behavior and account actions remain unchanged.
- First Electron CDP comparison command failed because `playwright-core` is workspace-local and cannot be resolved from the repository root. The retry runs from `apps/desktop-client`, where the dependency is installed.
- Removed the global “添加 AI 账号” dropdown; platform cards are now the only binding entry, matching the media account workflow.
- Replaced compact AI selectors with the media page's large card geometry, status dot, bound/unbound treatment, and “新增账号 / 绑定账号” actions.
- Rebuilt the AI authorized-account list with the same hero-card shell, toolbar, filters, transparent table header, row spacing, avatar/platform cells, status tags, and three-action layout used by Media Account Management.
- Confirmed the existing account-pool classifier returned success as soon as any non-empty answer existed, regardless of empty `citations` and `search_results` arrays.
- Added a red regression test proving a source-less successful answer previously returned no failover reason.
- Added `missing_citations` as a current-task failover reason. It has zero account cooldown because missing sources can be question-specific and should not mark the account unhealthy for later tasks.
## 2026-07-12T15:35:00+08:00 - AI account UI and missing-citation failover verified
- Full desktop tests passed: 30 files / 206 tests.
- Desktop Vue typecheck and production Electron build passed.
- Targeted Prettier checks and `git diff --check` passed.
- CDP confirmed AI and media platform cards have matching geometry at 1420x960; the AI page also passed 1024x800 responsive inspection with no page overflow.
- The live DOM contains only the hero actions “一键检测账号” and “刷新状态”; the removed global “添加 AI 账号” button is absent.
- Successful answers now require at least one item in `citations` or `search_results` to stop same-platform account failover. When every account lacks sources, the existing loop preserves and submits the final non-empty answer with attempt diagnostics.
## 2026-07-12T15:50:00+08:00 - Admin AI account management extraction started
- Added Phases 60-62 for a new tracking-side “AI 账号管理” menu after “外部文章标记”.
- Confirmed the existing tracking status panel collapses multiple accounts to the first account per platform even though the API returns every account.
- Confirmed account removal and delete-request APIs can be reused from Media Account Management; implementation needs no server migration.
- Browser verification fallback note: the first isolated Playwright mock command failed because `run-code` expects one callback expression rather than consecutive top-level statements. No page state or backend data was changed; the retry wraps the setup in one async callback.
- The callback retry reached API interception but the CLI sandbox did not expose the global `URL` constructor. Replaced URL parsing with deterministic request-string normalization before rerunning the isolated fixture.
- The first successful fixture execution remained on login because the SPA auth store had initialized before test local storage was written. Moved the isolated session seed to `page.addInitScript` so it runs before the next document and Pinia bootstrap.
## 2026-07-12T16:40:00+08:00 - Admin AI account management extraction complete
- Added “AI 账号管理” immediately after “外部文章标记” and moved account status out of Data Details.
- The new admin page renders one card per AI account, preserves same-platform accounts independently, and reuses the tenant account health, node, delete-request, and unbind lifecycle.
- Matched `/media` live geometry: toolbar 74.78px and standard account card 270.09px at desktop width; mobile verification showed no horizontal overflow.
- Browser network interception added a second Qwen account temporarily; both Qwen cards rendered independently and total card count increased from three to four.
- Confirmed all three live AI account avatars load as real remote images in the admin browser.
- Fixed the desktop AI authorized-account table to render `record.avatarUrl`; initials now appear only when an account has no avatar.
- Verification passed: admin production build, desktop-client Vue typecheck, targeted Prettier formatting, browser desktop/mobile inspection, and `git diff --check`.