Implement _execute_debate_phase() with Lead-facilitated structured debate:
- Lead opens with divergence point + dependency context
- Experts argue in parallel per round (asyncio.gather)
- Lead summarizes each round, then adjudicates final verdict
- Verdict produces decision (adopt/compromise/shelve/inconclusive) + conclusion
- Conclusion written to SharedWorkspace for downstream phases
Escape hatches:
- debate_config.skip=true short-circuits with template text
- MAX_DEBATE_ROUNDS=4 hard cap on rounds
- User /stop intervention ends debate early (U4-compatible via getattr fallback)
- LLM unavailable falls back to template verdict, no crash
New events: debate_started, expert_argument, debate_round_summary,
debate_resolved (plus existing phase_completed for consistency).
Phase dispatcher (_execute_phase) routes by phase_type:
EXECUTION to _execute_execution_phase, DEBATE to _execute_debate_phase.
36 new tests in test_orchestrator_debate.py covering happy path (2 rounds,
2 experts), max_rounds=1 boundary, empty participants, user stop, skip
escape hatch, LLM unavailable, SharedWorkspace integration, event
broadcasting, intervention channel compatibility, and helper methods.
All 377 expert tests pass.
Also includes planning artifacts (brainstorm requirements + implementation
plan with 6 units U1-U6).
U1: Data model foundation for structured debate collaboration.
- Add PhaseType enum (EXECUTION | DEBATE)
- Add phase_type and debate_config fields to PlanPhase
- Update to_dict/from_dict for serialization with backward compatibility
- Add tests for PhaseType, debate phase creation, serialization, and
mixed EXECUTION+DEBATE topological sort
OutlookSyncProvider implementing AbstractSyncProvider for
bidirectional Outlook Calendar sync. Uses Graph API delta query
for incremental pull, auto-refreshes OAuth tokens on 401, and
converts Outlook recurrence patterns to RRULE. Same conflict
resolution as CalDAV (last-write-wins + WS notification).
- src/agentkit/calendar/sync/outlook_provider.py — OutlookSyncProvider
- tests/unit/calendar/test_sync_outlook.py — 8 tests
ICSProvider parses .ics files (icalendar library) and creates local
CalendarEvents, skipping duplicate UIDs. Export builds an iCalendar
from events in a date range, deduplicating recurring event
occurrences back to a single VEVENT with RRULE. REST endpoints:
POST /import-ics (multipart upload), GET /export-ics (download).
- src/agentkit/calendar/sync/__init__.py — sync subpackage init
- src/agentkit/calendar/sync/ics_provider.py — ICSProvider (import/export)
- src/agentkit/calendar/db.py — added get_event_by_external_id() for dedup
- src/agentkit/server/routes/calendar.py — import-ics and export-ics endpoints
- pyproject.toml — added icalendar>=5.0 dependency
- tests/unit/calendar/test_ics_provider.py — 8 tests
Adds PostProcessingExtractor — a zero-LLM keyword gate (Chinese +
English time words) followed by LLM extraction for ambiguous cases.
Events created from extraction carry source="post_extract" so the UI
can style them distinctly (R33). LLM gateway is optional to keep the
constructor testable without a live provider.
- src/agentkit/calendar/extraction.py — PostProcessingExtractor
- tests/unit/calendar/test_extraction.py — 13 tests with MockLLMGateway
Adds CalendarTool implementing the Tool ABC so the ReAct engine can
create, query, update, and delete events autonomously. Resolves
event_type_name and tag_names (look up or create), sets
source="agent" to distinguish agent-created events from manual ones.
- src/agentkit/tools/calendar_tool.py — CalendarTool(Tool)
- tests/unit/tools/test_calendar_tool.py — 13 tests covering all actions
Deploy to Production / deploy (push) Waiting to runDetails
- skills.ts: make category/agent_type/execution_mode/task_mode optional
in ISkillInfo and ISkillDetail for backward compat during rollout
- SkillCard.vue: remove dead size="small" on a-tag, add title attr for
a11y, add isEngine computed, CSS fallback cleanup, category fallback
in class binding
- SkillsView.vue: fix a-empty condition to use grouped counts so orphan
skills (category mismatch) don't render a blank page
- SkillsTab.vue: add type tag (引擎/技能) and category-based icon
(thunderbolt for engine, appstore for business), remove size="small",
add engine icon color variant
Deploy to Production / deploy (push) Waiting to runDetails
The skills tab mixed generic execution-engine templates (react/direct/
rewoo/...) with business-domain skills (monitor/geo_optimizer/...) with
no visual or data distinction. Adds a derived `category` field to the
SkillInfo/SkillDetail API models and groups the frontend display.
Backend:
- SkillInfo/SkillDetail: add category (Literal), agent_type, execution_mode,
task_mode fields
- _skill_to_info: derive category from explicit _ENGINE_TEMPLATE_NAMES set
(not name suffix — trend_agent/deai_agent are business skills despite
the _agent suffix)
- Simplify repetitive hasattr pattern with getattr
Frontend:
- ISkillInfo/ISkillDetail: add category + mode fields
- skills store: agentTemplates/businessSkills computed getters
(businessSkills is defensive: anything not explicitly engine template)
- SkillsView: group into 执行引擎 / 业务技能 sections with counts
- SkillCard: type badge (引擎/技能), category-based icon, mode display,
dark-mode-aware accent color
Tests:
- test_category_derived_from_name_suffix: verifies field exposure
- test_category_no_orphans: invariant — every skill has a valid category
- test_trend_agent_classified_as_business_skill: regression guard for
the _agent suffix misclassification bug
Code review (ce-code-review): 2 P1 + 5 P2 findings applied.
All config file writes now use the write-temp + fsync + os.replace
pattern (KTD-4) so a crash mid-write leaves the original file intact.
- Add src/agentkit/server/utils/atomic_write.py with write_text_atomic
- settings.py: _write_yaml_config and _write_env_var use atomic write
- skill_service.py: import_skill uses atomic write
- skill_service.py: update_skill_config uses atomic write + fcntl.flock
around the read-modify-write cycle to serialize concurrent updates
- Add 11 unit tests covering happy path, crash safety, concurrency, errors
The whoami route accepted rotated/old refresh tokens for cold-start
because it only checked session revocation status, not the token hash.
Now when token_type == "refresh", the route computes hash_token(token)
and compares it with the session's stored refresh_token_hash using
hmac.compare_digest (constant-time). Mismatch returns 401.
- Add SessionService.get_stored_refresh_hash(session_id) helper
- Add hash verification in whoami route (R9)
- Add TestWhoamiTokenHash with 5 integration tests
SkillService: enable/disable (persisted in skill_states table, schema
v4), import from YAML (with path traversal + name validation), reload
from file, update config. GET /skills now filters disabled skills.
KbService: list/upload/delete documents with department_id binding.
Added department_id field to KnowledgeSource + UploadedDocument.
Department visibility: (bound to user depts) ∪ (global = None).
10 new admin endpoints: skill enable/disable/import/reload/update,
KB documents CRUD, source sync/rebuild. All guarded by _require_admin.
Implemented reload stub in skill_management.py (was no-op).
54 new tests (26 unit + 28 integration). Fixed 4 pre-existing lint
errors. 357 admin tests pass, no regressions.
U1: Bump _SCHEMA_VERSION to 3, add 5 department tables (departments,
user_departments, department_skill_bindings, department_kb_bindings,
department_quotas) + 5 ORM models + helpers.
U2: DepartmentService (12 async methods: CRUD + bind/unbind skill/KB +
count_users). Mount admin_router in app.py. 36 unit + 28 integration tests.
U4: DepartmentContext FastAPI dependency (per-route, admin bypasses
filtering). filter_skills_by_department / filter_kb_sources_by_department
helpers. Applied to GET /skills and GET /kb-management/* routes.
15 integration tests for department isolation.
Also includes brainstorm + plan docs. 108 new tests, all pass.
Add create_user method to LocalAuthProvider (bcrypt hash + INSERT,
raises ValueError on duplicate username/email).
Add UserService with 9 async methods: create/list/get/update/delete
(soft)/reset_password/assign_department/remove_department/list_user_departments. reset_password revokes all sessions via SessionService.
delete_user is soft (is_active=0, row preserved).
Add 9 user endpoints to routes/admin.py: POST/GET/PATCH/DELETE users,
reset-password, assign/remove department, list departments. All
guarded by _require_admin.
Tests: 40 unit + 37 integration = 77 new tests. Full admin suite
170 tests pass, no regressions.
The '重试' button used hardcoded `color: #fff` while its background
was `var(--color-primary)`. In dark mode --color-primary resolves to
#fbfbfa (near-white), making the button text invisible.
Switch to `var(--text-inverse)` (which is #1a1a1a in dark mode and
#ffffff in light mode) so the button text is always readable on the
primary background regardless of the active theme.
SplashScreen was the only file in the codebase with this pattern;
all other primary-colored elements either use Ant Design's automatic
colorTextLightSolid handling or a different token combination.
Adds the central business-logic layer for ``auth_sessions`` so routes,
the auth middleware, and the admin endpoints can call a single service
instead of touching the table directly.
Server
- session_service.SessionService: CRUD + lifecycle for auth_sessions.
- create() enforces the per-user cap (default 10): the oldest
active session is evicted with reason=session_cap_eviction.
- rotate() swaps a refresh token, adds the old hash to the
denylist, and raises SessionReuseDetected (revoking all sessions
for the user) when the old token is replayed.
- revoke() / revoke_by_refresh_token() / revoke_all_for_user()
with explicit reasons: user_terminated, admin_revoked,
password_changed, reuse_detected, session_cap_eviction.
- touch() bumps last_active_at (called on /auth/whoami).
- session_cache.SessionValidationCache: bounded LRU+TTL wrapper
(default 30s/1k entries) around SessionService.is_session_valid.
The middleware hits this on every request carrying a V2 sid claim;
one SQLite round-trip per 30s per session instead of per request.
- get_session_service() / get_validation_cache() module-level
singletons overridable in tests via set_session_service() /
set_validation_cache().
Tests
- tests/unit/auth/test_session_service.py: 15 cases covering
create/rotate/revoke/list/cap-eviction/reuse-detection/expired
sessions.
Refs: U3 in docs/plans/2026-06-20-002-feat-centralized-auth-token-persistence-plan.md
Adds V2 JWT claim schema that closes the kicked-out window and enables
refresh-token rotation with reuse detection.
Server
- jwt_utils.create_token_pair now takes ``session_id`` and ``remember_me``
kwargs. When ``session_id`` is provided, both tokens carry a ``sid``
claim and the access token also carries a ``jti`` claim; the refresh
token's jti is intentionally absent (rotation uses the token hash).
- New ``REFRESH_TOKEN_TTL_REMEMBER_ME = 30d`` (default 7d) selected by
the ``remember_me`` flag.
- ``verify_token`` now supports an optional ``expected_type`` filter
(e.g. ``"access"`` / ``"refresh"``); when omitted, both types pass
(used by /auth/whoami's cold-start path).
- New ``auth.denylist`` module: ``InMemoryRecentlyRevoked`` (default for
the Tauri sidecar / dev mode) and ``RedisRecentlyRevoked`` (multi-
process server). Bounded LRU with auto-expiry via ``time.monotonic()``.
Backwards-compat
- Tokens issued before U2 (no ``sid``) are still accepted by
``verify_token``; validation falls through to the legacy
``user_sessions`` table via the U10 shim (next commit).
Tests
- tests/unit/auth/test_jwt_utils.py: 12 cases — V1/V2 claim presence,
default + remember-me TTL, expected_type filter, expiry, wrong secret.
- tests/unit/auth/test_denylist.py: 6 cases — add/contains, TTL expiry,
LRU eviction, re-add refresh, clear, hash stability.
Refs: U2 in docs/plans/2026-06-20-002-feat-centralized-auth-token-persistence-plan.md