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
- src/api/tauri-auth.ts: abstract Keychain (Tauri) / localStorage (Web)
behind a single async API (set/get/clear refresh token). Falls back
to localStorage with a console.warn when the Keychain is unavailable
(KTD-confirmed decision: silent localStorage fallback).
- tests/unit/api/tauri-auth.test.ts: 13 vitest cases covering both
Tauri and Web code paths plus the failure / fallback behaviour.
- vitest.config.ts + tsconfig.test.json: minimal Vitest setup
(happy-dom env, @ alias). Adds test:unit, test:unit:watch, and a
typecheck alias that includes the test tree.
Refs: U6 in docs/plans/2026-06-20-002-feat-centralized-auth-token-persistence-plan.md
Add skill_search tool so agent can search for skills before installing.
Update skill_install description to guide LLM to search first.
Update system prompt to use skill_search -> skill_install flow.
This fixes the issue where agent returns empty when asked to find a skill.
- Settings API: reverse-resolve env vars to preserve ${VAR} refs in yaml,
write new API keys to .env instead of agentkit.yaml, extract env_key
from existing ${VAR} reference when updating providers
- Onboarding: merge-update instead of overwrite when config exists,
use config_arg to determine output path, .env merge instead of overwrite
- Unified templates: bailian-coding provider name, full model_aliases,
docker-compose with postgres, expanded .env.example
- Optional ruamel.yaml for comment/format preservation in Settings API
- clients.yaml: add _deep_resolve for ${VAR} env var references
- All CLI commands use load_config_with_dotenv() consistently
- Tests: mock find_config_path and CWD auto-discovery to avoid env leaks
When path starts with /api/, treat it as an absolute URL instead of
prepending baseUrl. This fixes installSkill/uninstallSkill URL
concatenation that produced /api/v1/skill-management/api/v1/skills/...
- QuadrantPanel: persist activeTab and collapsed state to localStorage via watch
- TopNav: remove v-if="false" dead code for TaskSelector, remove unused ASelect import
- SplitPane: add null guard before containerRef.value access in onMouseDown
- Router: fix /agent/code route loading ChatView instead of WorkflowView
- Router: add default redirect for /legacy to /legacy/chat
- EvolutionView: simplify from 6 sub-panels to 3 tabs (概览+指标, 经验+坑点, 用量)
- SettingsView: group into 4 tabs (LLM, 技能, 知识库, 系统), each with independent save
- SkillsView: replace hardcoded colors with Design Tokens
- All three views: replace hardcoded colors with Design Token references
- TerminalView: replace native HTML confirmation with Ant Design Modal,
make command history sidebar collapsible (default collapsed)
- TerminalEmulator: use One Dark Pro CSS variables for ANSI colors,
replace all hardcoded colors with Design Tokens
- CommandHistory: replace all hardcoded colors with Design Tokens
- Create AgentLayout.vue with CSS Grid four-quadrant layout
- Create SplitPane.vue with draggable divider and localStorage persistence
- Create TopNav.vue with logo, status indicator, and settings entry
- Create QuadrantPanel.vue with tab switching and collapse support
- Restructure router: /agent as main route, legacy routes redirect
- App.vue now uses router-view for layout switching
- Create tokens.css with CSS custom properties for colors, spacing,
radius, fonts, shadows, transitions, and code theme
- Create theme.ts mapping tokens to Ant Design Vue ConfigProvider
- Create styles/index.ts as unified entry point
- Inject theme into App.vue ConfigProvider
- Import styles in main.ts
Primary color unified to #7c3aed (purple gradient brand color).
- Add HeuristicClassifier to replace LLM quick_classify with zero-cost
local heuristic (keyword/length/code-pattern scoring), gated by
router.classifier config (default: heuristic)
- Add parallel tool execution in ReActEngine via asyncio.gather for
multiple independent tool_calls, gated by parallel_tools param
- Add AsyncWriteQueue for non-blocking session persistence with WAL
buffer, gated by async_writes param on SessionManager
- Add httpx.Limits connection pool config to all LLM providers
- Add router config section to ServerConfig and agentkit.yaml
- All optimizations have config switches for safe rollback
DeepSeek-chat has limited/partial function calling support. Qwen3-coder-plus
(DashScope) has robust OpenAI-compatible function calling.
Also added tool usage instructions to system prompt and enhanced logging
to trace tool propagation through the pipeline.
1. Loading indicator: three-dot bouncing animation appears after
sending a message and disappears when server starts responding.
2. Tool descriptions: resolve_skill_routing now appends available
tools (name + description + parameters) to the system prompt so
the LLM knows what tools it can call.
e.isComposing is a standard KeyboardEvent property that's true during
IME composition. More reliable than compositionstart/compositionend
which can fire at unpredictable timing relative to keydown.
Added compositionstart/compositionend event listeners to track IME
composing state. Enter key now only submits when not composing,
so Chinese/Japanese/Korean input methods work correctly.
Phase B:
- U1: CostAwareRouter with 3-layer routing (rule/LLM/capability matching)
- U6: OrganizationContext with agent profiles and capability-based discovery
- U7: AlignmentGuard with constraint injection and cascade detection
Phase C:
- U8: Soul dynamic evolution with version tracking and reflection-triggered updates
- U9: Auction mechanism as optional advanced routing mode
- U10: Server integration + end-to-end integration tests
250 new tests passing across all units.
- Shell whitelist: use exact binary match instead of startswith
- Shell audit log: use deque(maxlen=10000) to cap memory
- Terminal history: use deque(maxlen) for O(1) eviction
- Path optimizer: cap _pending_paths at 50 entries per task_type
- Pitfall detector: only add tips to matching steps, not all
- Experience store: handle non-numeric _parse_time_window input
- Extract shared is_safe_url() to utils/security.py (DRY)
- Workflow condition evaluator: handle float() ValueError
- Enhanced chat CLI with adaptive mode and session management
- Added pipeline reflection and schema extensions
- Upgraded BaiduSearch and WebSearch tools with advanced capabilities
- Expanded server routes for skills and chat
- Added session store enhancements
- New chat module and pipeline reflection support
- Orchestrator accepts optional message_bus parameter; workers publish
task.progress messages via MessageBus after each subtask execution
- AgentPool accepts optional message_bus; auto-registers agents on
create and auto-unregisters on remove
- app.py initializes MessageBus from config and injects into AgentPool
- ServerConfig adds bus configuration field
- 5 new tests, all passing
Add compression config to ServerConfig (following telemetry pattern),
create compressor in create_app, pass through AgentPool to
ConfigDrivenAgent, and inject into ReActEngine.execute() calls.
Add MCPServerConfig dataclass with stdio/streamable_http/sse transport
validation, MCPManager for declarative YAML-driven MCP server lifecycle
(start_all/stop_all), tool discovery and registration. Integrated
into FastAPI lifespan startup/shutdown.
- Added _acquire_status_lock with timeout (30s) to prevent deadlocks
- Added _release_status_lock for safe lock release
- Added config_version tracking on BaseAgent
- Config hot-reload now increments version and propagates to agents
- Audit logging with config version in _on_config_change
- EpisodeModel ORM model with pgvector embedding support
- create_episodic_session_factory for async PostgreSQL sessions
- Server app.py now resolves session_factory from database_url config
- Graceful fallback when database_url not configured