lfg Step 3 (ce-simplify-code) — 16 files, -89 net lines.
P1 fixes (functional):
- audit.py / scim/router.py: fix OTel span leak — start_span() returned a
context manager but was used as a plain object, so spans never ended
and never exported. Wrap in `with`.
- helm chart: split combined `redisPgPasswords` slot (single JSON key)
into `redisPassword` + `postgresPassword` slots — the combined `key`
field was dead config never referenced by any template; deployment.yaml
hardcoded `redis-password`/`postgres-password` keys were inconsistent
with values.yaml schema.
P2 dedup (reuse):
- Promote `resolve_auth_db_path()` to models.py (canonical); remove 3
duplicate `_resolve_db_path` defs in oidc.py / saml.py / audit.py.
- Reuse `_now_iso` from models.py; remove 5 duplicates across audit /
oidc / saml / scim / local providers.
- Reuse `_row_to_user` from local.py in oidc.py / saml.py (verbatim
copies removed).
- pii_filter._redact: collapse redundant `finditer` + `sub` (2 regex
passes + 2x sha256 per match on hot path) into a single `sub` with a
callback that collects matches and computes hashes once.
P3 simplifications:
- RoleChangeRequest.role: `str` + manual `if not in (...)` → `Literal`
(Pydantic validates automatically).
- Extract `_record_pii_metrics(status, redacted_count)` helper to dedup
3 nearly-identical OTel metric emission blocks.
- Extract `LLMGateway._record_prompt_cache_metric(usage, model)` to dedup
non-stream + stream cache hit/miss counters; switch to use the
`TokenUsage.cache_hit` property (previously added but unused).
- middleware: split `WHITELIST_PATHS` + `PREFIX_MATCH_PATHS` (redundant
cross-reference) into `WHITELIST_PATHS` (exact) + `PREFIX_WHITELIST_PATHS`
(prefix) — single source of truth per kind.
- OIDC token exchange: replace f-string form body with `urlencode` to
correctly escape `&` / `=` in code / redirect_uri.
- auth.py OIDC redirect route: replace hand-rolled `url.split("state=")`
with `urlparse + parse_qs`.
- PII module docstring: clarify that `PIIMatch.original` is in-memory
only (must not be logged / persisted) — the previous "仅 hash, 不含
原文" wording was misleading.
Skipped findings (false-positive or not worth):
- OIDC/SAML `_find_or_create_user` JIT 60-line dedup — high blast
radius across SQL + transaction boundaries; defers to dedicated refactor.
- SAML `_extract_host` hand-rolled split — already has ponytail comment;
stdlib `urlparse` swap is behavior-equivalent for current inputs but
changes OneLogin `http_host` field semantics; conservative keep.
- gateway.py department-quota / usage-record sequential await —
concurrency gain unclear when dept count is typically ≤2.
Verification:
- ruff check (scoped to 12 touched files) clean
- ruff format (1 file reformatted)
- pytest (PII + OIDC + SAML + SCIM + audit + bitable + team_orchestrator):
265 passed, 145 skipped (PG/Docker), 0 failures
- helm lint clean; helm template renders correctly for both
sealed-secrets + external-secrets backends
Test / backend-test (pull_request) Has been cancelledDetails
Test / frontend-unit (pull_request) Has been cancelledDetails
Test / api-e2e (pull_request) Has been cancelledDetails
Test / frontend-e2e (pull_request) Has been cancelledDetails
P1#1 config_driven: propagate trace_outcome into output_data so
lifecycle._is_failure_path() detects non-success outcomes
P1#2 portal: route through ConfigDrivenAgent.execute_stream (not
react_engine.execute_stream directly) so evolution hooks fire
and trace_outcome propagates; add pre-built messages support in
_build_llm_messages
P1#3 sandbox: make network_block reentrant via module-level reference
counter + threading.Lock - concurrent VERIFICATION phases no
longer permanently block all new connections
P1#4 chat: replace dead isinstance(_PlanExecEngine) check with
hasattr(_spec_review_handler) to wire the spec review gate
P1#5 plan_exec_engine: complete max_reflections threading chain
(PlanExecEngine + ReActStepExecutor constructors)
P1#6 plan_exec_engine: enforce phase budgets (max_steps from
phase_budgets, not hardcoded 5)
P1#7 plan_exec_engine: use current plan (not stale plan var) in
aggregation after replan
P1#8 plan_exec_engine: map failure to failed status (not success)
P1#9 app: add drain timeout for pending evolution tasks on shutdown
P1#10 portal: handle spec_review_reply in WS handler
P1#11 chat: persist spec_review_request/reply/timeout to conversation
store so reload can reconstruct gate state
Tests: 116 related tests pass; 26 pre-existing failures unchanged
(stash-verified). ruff lint clean.
Test / backend-test (pull_request) Has been cancelledDetails
Test / frontend-unit (pull_request) Has been cancelledDetails
Test / api-e2e (pull_request) Has been cancelledDetails
Test / frontend-e2e (pull_request) Has been cancelledDetails
Rebuild index.html after U1-U6 frontend changes. JS bundle hash updated
(index-CHtvprqX.js -> index-agwA6wam.js) to match new build output.
CI runs unit/e2e only and does not rebuild static assets, so the committed
hash must match the bundled JS.
- Add viewSwitcherUtils.ts (5 view types metadata: label/icon/disabled/tooltip)
- Refactor ViewSwitcher: button -> dropdown with 5 types, disabled items show "规划中" tooltip
- Update BitableFileDetailView.handleCreateView to accept viewType parameter (no more hardcoded grid)
- Bind :creating=viewCreating to ViewSwitcher for loading/disabled state during POST
- Extend store createView + API createView to pass view_type field (already in prior commits)
- Add loading/disabled state on create button to prevent duplicate clicks
- Extend e2e/bitable-view.spec.ts with 5 view type scenarios (E1-E5)
Closes R3 (P0): view type selection in UI, backend already supports view_type.
Refs: docs/plans/2026-07-03-001-feat-bitable-p0-ux-and-agent-parity-plan.md U4
Add a spec review gate to PlanExecEngine that pauses execution after the
first Spec is generated, awaiting the user's confirm/reject decision.
On approval execution continues; on rejection the engine replans (capped
at 2 replans); on 30-min timeout the Spec is parked (not failed) so the
user can resume later.
- spec_manager: add parked status + park()/resume() methods
- plan_exec_engine: add spec_review_handler param, wire gate into both
execute_stream and _execute_loop with replan cap, emit
spec_review_request/spec_review_reply events, handle timeout to park
- chat.py: whitelist new events, add spec_review_reply WS handler,
wire _spec_review_handler closure (30-min timeout), cleanup on disconnect
- portal.py: persist spec_review_id/decision/feedback for page reload
- tests: 20 unit tests covering happy path, rejection/replan, timeout,
cancellation, backward compat, handler errors, park/resume round-trips
Replace emoji/glyph characters with Ant Design Vue Outlined icons (frontend), text labels with ANSI colors (CLI/shell), and ASCII art (docstrings). Add pre-commit guard (scripts/check-no-emoji.sh) and style guide to prevent regression.
Closes: docs/plans/2026-07-02-001-refactor-remove-all-emoji-plan.md
Test / backend-test (pull_request) Has been cancelledDetails
Test / frontend-unit (pull_request) Has been cancelledDetails
Test / api-e2e (pull_request) Has been cancelledDetails
Test / frontend-e2e (pull_request) Has been cancelledDetails
message_type: 'board_started' as const (line 93) fixes TS2322 on lines
107 and 122 — TypeScript was inferring message_type as string instead
of the literal 'board_started'.
boardState local variable: replace 'as never' with proper shape +
'status: discussing' as const (line 159-160) fixes TS2339 on line 168
where .topic was accessed on type 'never'.
All 5 transient-state tests still pass. vue-tsc --noEmit now clean.
board_orchestrator.py: include moderator_avatar and moderator_color in
the round_summary event payload so downstream consumers have the
moderator's identity metadata.
chat.py: persist expert_avatar and expert_color from the event data into
the board_summary message metadata, ensuring avatar/color survive page
reload instead of falling back to defaults.
- docker-compose.yaml: production mode uses expose (container-only) for
Redis/PostgreSQL instead of ports (host-mapped)
- docker-compose.dev.yml: dev override maps Redis 6381 and PostgreSQL 5435
to avoid conflicts with other projects (pms-redis 6379, geo_redis 6380,
geo_db 5433)
- config.py: fix empty env var handling — only skip .env override when
os.environ[key] is non-empty; load .env, .env.dev, .env.local in sequence
- scripts/dev-start.sh: manage agentkit-specific Docker containers
- .gitignore: add .env.dev and .env.local (contain API keys)
Test / backend-test (pull_request) Has been cancelledDetails
Test / frontend-unit (pull_request) Has been cancelledDetails
Test / api-e2e (pull_request) Has been cancelledDetails
Test / frontend-e2e (pull_request) Has been cancelledDetails
Replace emoji across codebase: YAML avatars -> first char, frontend banners -> Ant Design Vue components, CLI status -> OK/FAIL/WARN labels, terminal -> [WARN]/[OK]/[PENDING], Bitable DB default -> table, App.vue font cleanup, test fixtures -> first char letters. shell.avatar type upgraded to string | Component.
Test / backend-test (pull_request) Has been cancelledDetails
Test / frontend-unit (pull_request) Has been cancelledDetails
Test / api-e2e (pull_request) Has been cancelledDetails
Test / frontend-e2e (pull_request) Has been cancelledDetails
Addresses 4 actionable findings (1 P1 + 3 P2) from ce-code-review of
feat/ui-ue-enhancement (PR #13), now merged to main (8066e0b).
P1 — expert_step payload alignment (_phase_executor.py)
The thinking/tool_call/tool_result event payloads were missing the
fields the frontend WsServerMessage contract requires
(expert_name/expert_color/content/step). Frontend code consuming these
events silently degraded. Now all expert_step broadcasts carry the
full contract; tool_call/tool_result keep step_data for the raw payload.
P2 #1 — execute_stream CancellationToken registration (config_driven.py)
execute_stream() bypassed BaseAgent.execute() and never registered a
CancellationToken, so cancel_task() could not cooperatively cancel a
streaming task. Now registers the token and cleans it up in finally.
P2 #2 — team_synthesis orphan milestone cleanup (orchestrator.py)
If synthesis streaming was interrupted (cancel/exception), no terminal
team_synthesis event was emitted, leaving the frontend streaming
milestone spinning forever. Now an inner try/except emits a terminal
team_synthesis with status=cancelled|error before re-raising, so the
frontend can finalize the milestone. The success path also carries
the synthesis_id.
P2 #3 — synthesis_id dedup (orchestrator.py + types.ts + chatStream.ts)
Without an identifier, the frontend could not precisely match a
team_synthesis terminal event to its streaming milestone (especially
across retries/concurrent teams). The backend now injects a stable
synthesis_id (`{plan.id}:synthesis`) into both team_synthesis_chunk
and team_synthesis events; the frontend uses it for exact milestone
matching and treats error/cancelled status as terminal.
Test updates
- Updated test_thinking_events_forwarded_as_expert_step to assert the
new payload contract (expert_id/name/color/content/step).
- Added test_tool_call_events_forwarded_as_expert_step covering
tool_call/tool_result payload shape (content=tool_name摘要 +
step_data=原始 payload).
Verification
- ruff check: clean
- pytest tests/unit/experts/test_phase_executor_streaming.py: 14/14
- npm run typecheck: clean
- vitest: 126/127 (1 unrelated baseline failure in tauri-auth.test.ts)
Residuals doc: docs/residual-review-findings/feat-ui-ue-enhancement.md
Test / backend-test (pull_request) Has been cancelledDetails
Test / frontend-unit (pull_request) Has been cancelledDetails
Test / api-e2e (pull_request) Has been cancelledDetails
Test / frontend-e2e (pull_request) Has been cancelledDetails
Six safe fixes from Stage 5c review:
phase.py: delete dead _DEFAULT_BASH_FILTER constant (no references after U1)
chat.py: drop Any from _build_phase_engine params (AGENTS.md prohibits any)
chat.ts: delete stale comment about phase_changed emission
chat-phase.test.ts: rename misleading 'capped at 5' test name
test_chat_plan_exec_ws.py: tighten test_rest_react_mode_still_works assertion
test_plan_exec_e2e.py: clarify test_auto_advance assertion comment
Known limitations documented in PR description (not fixed): loop detector + advance_phase (P1), parallel path phase_violation ordering (P2), REST cancellation_token (P2), Callable filter exceptions (P3).
Extend the frontend to surface PLAN_EXEC phase lifecycle events to the
user:
- WsServerMessage union (types.ts) gains two branches:
`phase_changed` and `phase_violation` (matching backend U2 emission).
- chat.ts Pinia store gains a phase state slice:
`currentPhase`, `phaseViolations` (capped at 5), `isPlanExec`
computed, and `resetPlanExecState()`.
- handleWsMessage adds `case "phase_changed"` (sets currentPhase +
appends a milestone step) and `case "phase_violation"` (sets
currentPhase from violation data, appends to violations, fires an
ant-design-vue message.warning toast, appends an error step).
- `result` handler calls `resetPlanExecState()` to clear the
indicator when the conversation completes.
- New `PhaseIndicator.vue` component: compact badge + 4 dots
(PLANNING/BUILDING/VERIFICATION/DELIVERY) with the current phase
highlighted + violation counter. Renders nothing when
`!isPlanExec` (graceful degradation).
- Mounted in `ChatView.vue` alongside ExpertTeamView and
BoardStatusView.
Tests:
- New `tests/unit/stores/chat-phase.test.ts` verifies the phase state
slice is exposed with correct initial values and `isPlanExec`
derives from `currentPhase`.
- `npm run typecheck` clean.
- Pre-existing `tauri-auth.test.ts` failure is unrelated (fails in
isolation on main).
Extract the WS path's inline phase_policy construction into a shared
_build_phase_engine helper so the REST send_message endpoint can reuse
it. Replace the former 501 stub with actual PLAN_EXEC execution:
- REST POST /chat/sessions/{id}/messages with execution_mode=plan_exec
now builds a phase-policy-backed ReActEngine, calls execute()
(non-streaming), and returns a MessageResponse.
- KTD5: PLAN_EXEC bypasses execute_with_fallback_chain — phase policy
and fallback chain are mutually exclusive.
- When plan_exec.enabled=False, REST falls through to the REACT path
(matching WS behavior).
- WS path refactored to call the same helper; behavior unchanged.
Tests:
- Replace TestRestPlanExec501 with TestRestPlanExec (happy path, bad
config → 500, disabled → falls through to REACT, REACT mode unchanged).
- Add TestBuildPhaseEngineHelper covering all return branches:
not-PLAN_EXEC, disabled, empty-config, invalid-config, tool append,
default-policy fallback.
- All 109 tests pass across the three PLAN_EXEC test files.
Wave 3 only injected the violation error dict back to the LLM as a tool
result. Wave 4 U2 adds a parallel WS event so the frontend PhaseIndicator
can surface violations to the user.
- ReActEngine: add _phase_violations accumulator (list[dict]). Cleared in
reset(). _check_phase_permission appends a structured violation dict
(with new violation_kind field: tool_not_allowed | bash_command_blocked)
before returning the error.
- Add _drain_phase_violations(step) helper that pops pending violations
and returns ReActEvent(event_type="phase_violation", ...) list. Events
carry a shallow copy of the violation dict so callers can't mutate the
accumulator.
- execute_stream: drain after each tool_result yield at all 3 tool
execution sites (parallel, serial-with-confirmation, parsed_calls).
Non-streaming execute() ignores the accumulator (the LLM reinjection
via the error dict is the only signal there).
- chat.py WS handler: new elif branch forwards phase_violation ReActEvents
to the client as {"type": "phase_violation", "data": ...} WS messages.
- Tests: 11 new tests covering accumulator lifecycle, drain semantics,
shallow-copy isolation, and execute_stream event emission for both
tool_block and bash_block paths. 2 new WS forwarding tests pin the
chat.py path (forward + characterization for REACT mode).
Test / backend-test (pull_request) Has been cancelledDetails
Test / frontend-unit (pull_request) Has been cancelledDetails
Test / api-e2e (pull_request) Has been cancelledDetails
Test / frontend-e2e (pull_request) Has been cancelledDetails
Code review fixes for Wave 1:
- W1: ServerConfig.from_dict now wires prompt_cache/streaming/verification sections
from YAML to constructor (previously these params existed but were never read)
- W3: Tool._validate_input filters _-prefixed kwargs (e.g. _skip_dangerous_check)
before jsonschema.validate, preventing additionalProperties:false schemas from
rejecting internal control parameters
- N3: ReActResult.status docstring now lists "empty_fallback" and "verify_failed"
Added test test_internal_kwargs_underscore_prefixed_skipped_by_validation for W3.