Skip to content

Release#481

Merged
yaojin3616 merged 298 commits intomainfrom
release
Apr 26, 2026
Merged

Release#481
yaojin3616 merged 298 commits intomainfrom
release

Conversation

@yaojin3616
Copy link
Copy Markdown
Collaborator

Summary

Checklist

  • Tested locally
  • No unrelated changes included

wisdomqin and others added 30 commits April 12, 2026 22:13
…precated /chat route

Changes:
1. AgentDetail ChatMessageItem: strip [image_data:data:url] markers from
   displayed text, extract image data URLs, and render them as thumbnails.
   Applies to both live WebSocket messages and history loaded from DB.

2. App.tsx: remove deprecated 'agents/:id/chat' route and Chat import.
   The chat interface lives in AgentDetail as a tab (#chat), not as a
   standalone route. Keeping this route caused repeated confusion where
   fixes were applied to the wrong component.
When a2a_async_enabled is False, the send_message_to_agent tool
schema is dynamically simplified to remove the msg_type parameter.
This prevents the LLM from selecting notify/task_delegate modes
(which get silently overridden to consult) and confusing users
who see the raw tool call arguments in the chat UI.
Agent-to-agent sessions store creator's user_id, causing them to be
filtered out from the Other Users admin view. Exempt source_channel=agent
sessions from the user_id filter so they always appear.
- Add dingtalk_token.py: global access_token cache with auto-refresh
- Add dingtalk_reaction.py: thinking indicator (reaction) during LLM processing
- Enhance dingtalk_stream.py: media download pipeline, auto-reconnect with
  exponential backoff, support for picture/richText/audio/video/file messages
- Update dingtalk.py: accept image_base64_list and saved_file_paths in message
  processing, forward media to LLM with vision support
- Update dingtalk_service.py: add download_dingtalk_media convenience wrapper

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
When an agent sends a notify/task_delegate message, the target agent's
reply was only saved to its Reflection Session (source_channel=trigger),
not to the shared A2A chat session. This made A2A conversations invisible
in the admin 'Other Users' chat history.

Now the reply is also saved to the A2A session via _a2a_session_id passed
through _wake_agent_async → wake_agent_with_context → dummy trigger config
→ _invoke_agent_for_triggers.
The 2-round max_tool_rounds_override was too restrictive for A2A wake
invocations, causing agents to hit the limit before completing basic
tasks. Each agent already has a configurable max_tool_rounds setting
(default 50), which provides sufficient protection against runaway
tool calls.
For sessions with thousands of messages, the messages API was returning
the oldest 500 (ORDER BY created_at ASC LIMIT 500), making recent A2A
conversations invisible. Now uses a subquery to fetch the latest 500
messages by created_at DESC, then displays them in chronological order.
scope=all was issuing 1+2N DB queries (1 count + 2 agent-name lookups
per session). scope=mine was issuing 2N queries (1 user-count + 1 total
per session). Both are now replaced with 3-4 bulk queries total,
regardless of session count.

Affected endpoint: GET /api/agents/{id}/sessions?scope=all|mine
- Backend: call_llm returns error strings rather than raising exceptions;
  detect error-prefixed responses and raise RuntimeError to trigger the
  existing fallback model retry logic in the websocket handler
- Frontend: handle 'info' WebSocket message type; show a subtle
  auto-dismissing info banner (6s) when the system switches to fallback
  model, with a manual dismiss button
Previously, seed_okr_agent() was a global startup-time seeder with a
file marker — new tenants enabling OKR via Company Settings never got
an OKR Agent, causing 'OKR Agent not found' errors in the UI.

Fix:
- Add seed_okr_agent_for_tenant(tenant_id, creator_id) in agent_seeder.py:
  tenant-scoped, on-demand seeder with DB-only idempotency (no file
  marker), safe to call multiple times.
- In update_okr_settings (PUT /api/okr/settings): detect False->True
  transition on 'enabled' and fire seed_okr_agent_for_tenant as a
  background asyncio task so the API response is not blocked.
Previously the LLM model was loaded once at WebSocket connect time and
cached for the entire session. Changing the model in Settings required
a page refresh to take effect. Now the agent's primary/fallback model
is re-fetched from the DB on every incoming message, so configuration
changes apply to the next message immediately.
- After enabling OKR, the backend creates the OKR Agent asynchronously.
  The frontend now waits 2.5s then invalidates the members-without-okr
  query so the 'Open OKR Agent Chat' button appears automatically without
  requiring a manual page refresh.
- While the agent is being seeded, shows a spinner 'Creating OKR Agent...'
  instead of the misleading 'OKR Agent not found' text.
- Toggle label now has flexShrink:0 to prevent layout overflow.
…fter company switch

The OKR page was using ['okr-settings'] as query key (no tenantId), so
React Query served a stale cached response when switching companies.
If the previous company had OKR disabled, the new company's OKR page
would still show 'OKR is not enabled' until the cache expired.

Fix: add user.id to queryKey so entries are per-user, and set
staleTime:0 + refetchOnMount:true so navigation to the OKR page
always fetches fresh settings data.
…KR enable

- OKR toggle: replace label/checkbox with <button> element (consistent with
  the rest of the codebase). Eliminates the label implicit-width side-effects
  that made the toggle area appear too wide.
- seeding timeout: also invalidates ['agents'] query so the sidebar Agent
  list updates automatically (OKR Agent appears without manual page refresh).
  Increased timeout to 3000ms for safer backend creation timing.
The is_system column may be NULL for agents created on-demand via
seed_okr_agent_for_tenant() (new tenants enabling OKR via settings).
Filtering by is_system==True silently returned no results, causing
'OKR Agent not found' to appear in the UI even though the Agent
actually existed in the sidebar.

Fix: query by tenant_id + name.ilike('%OKR%') which is unambiguous
within a tenant. Applied to both members-without-okr and
trigger-member-outreach endpoints.
The previous OKR toggle switch was sized 40x24px, making it noticeably
larger than the other standard toggle switches in the Enterprise Settings
(e.g., LLM tools, which are 36x20px), leading to a 'wrong width' visual
anomaly.

Fix: resize the OKR toggle container to 36x20px and its inner circle to
16x16px to match the platform's standard toggle dimensions.
The panel was silently not rendering because:
1. Frontend used 'members' but backend returns 'members_without_okr'
2. Frontend used 'member.name' but backend returns 'display_name'

When data.members was undefined, data.members.length threw TypeError,
causing React to silently remove the component from the render tree.

Fix: update TypeScript interface and component to use the correct
field names that match the API response.
The panel was silently not rendering because:
1. Frontend used 'members' but backend returns 'members_without_okr'
2. Frontend used 'member.name' but backend returns 'display_name'

When data.members was undefined, data.members.length threw TypeError,
causing React to silently remove the component from the render tree.

Fix: update TypeScript interface and component to use the correct
field names that match the API response.
…name

Three backend fixes:
1. Fix User query in members-without-okr: replace User.full_name/User.email
   (which don't exist as plain DB columns; email is an association proxy
   that throws NotImplementedError when used in select()) with User.display_name.
   The endpoint has been 500-crashing silently, so the panel never rendered.

2. Add owner_display_name to ObjectiveOut schema.

3. Add bulk lookup of owner display names in list_objectives: after fetching
   objectives, query User.display_name and Agent.name for member-level objectives
   and pass them through to the response, so member OKR cards correctly show
   who the objective belongs to.

Also add top-level imports for User and Agent in okr.py to support the new
owner name lookup code in list_objectives.
…on old frontends

When the backend was updated (members_without_okr field) without a simultaneous
frontend rebuild, old frontend builds calling data.members.length crashed with
TypeError: Cannot read properties of undefined (reading 'length').

Adding 'members' as an alias alongside 'members_without_okr' keeps old frontend
builds working while new builds are deployed.
Two fixes + one new feature:

1. Fix 'Chat with OKR Agent' button: navigate to /agents/{id} (agent detail
   page) instead of /chat/{id} (which was incorrect).

2. Auto-sync OKR Agent relationships on enable:
   - When OKR is toggled on, after seeding the agent, automatically connect
     it to ALL active OrgMembers (org-structure-synced humans, e.g. Feishu)
     as team_member relationships and ALL company-visible agents as
     collaborator relationships.
   - Regenerates the workspace relationships.md so the context is immediately
     available to the OKR Agent.

3. Add POST /api/okr/sync-relationships endpoint + matching 'Sync Now' button
   in Company Settings → OKR tab so admins can re-sync at any time without
   having to disable and re-enable OKR.
…nant delete

Changes:
1. EnterpriseSettings.tsx — Hash-based tab deep-linking: /enterprise#okr,
   #models, #tools, #skills, #invites, #quotas, #users, #org-structure,
   #approvals, #audit. URL updates on tab switch via replaceState.

2. OKR Agent button — href now includes #chat so users land directly on
   the chat tab of the agent page.

3. Sync Now button — switched from raw fetch() to project-standard fetchJson()
   so the Authorization header is correctly included (was returning 401).

4. OKR.tsx — 'Enable OKR in Company Settings' button now navigates to
   /enterprise#okr (was using ?tab=okr query param which had no effect).

5. tenants.py — Added DELETE /tenants/{id} endpoint (was returning 405
   Method Not Allowed). Performs ordered cascade deletion:
   relationships → tasks → sessions → triggers → channel_configs →
   agent_permissions/credentials → agents → OKR/org data → users → tenant.
   Returns fallback_tenant_id pointing to another company the caller belongs to.
…auto-sync

1. tenants.py — Fix DELETE /tenants/{id} cascade table names:
   - chat_messages (was: messages)
   - agent_triggers (was: triggers)
   - approval_requests with agent_id subquery (was: 'approvals WHERE tenant_id')
   - Move approval_requests + notifications deletions BEFORE agent deletion
     to avoid FK constraint violations.

2. okr.py — Remove auto-sync of relationships on OKR enable.
   The OKR Agent's relationship list is now managed solely by admins:
   manually (via the Relationships tab) or via POST /api/okr/sync-relationships.

3. AgentDetail.tsx — Remove isSystem/Auto-Connect Mode guard from
   RelationshipEditor so OKR Agent's Relationships tab works identically
   to every other agent's — fully editable, supporting add/edit/delete.
@yaojin3616 yaojin3616 merged commit f5b3b7b into main Apr 26, 2026
0 of 2 checks passed
@yaojin3616 yaojin3616 deleted the release branch April 26, 2026 15:46
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b2cf377a5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

def upgrade() -> None:
op.execute("ALTER TYPE channel_type_enum ADD VALUE IF NOT EXISTS 'wechat'")

op.execute("ALTER TYPE channel_type_enum ADD VALUE IF NOT EXISTS 'whatsapp'")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add whatsapp in a new Alembic revision

This rewrites an existing migration (add_wechat_channel_support) so the same revision ID now maps to different schemas depending on when it was applied. Environments that already ran this revision before this commit will not execute this new statement, so they can remain missing whatsapp while fresh installs include it, creating schema drift that Alembic cannot detect from revision IDs alone. Add whatsapp in a new migration from the current head (or merge the branch that already adds it) instead of modifying a historical revision.

Useful? React with 👍 / 👎.

yaojin3616 added a commit that referenced this pull request Apr 27, 2026
* fix(chat): parse [image_data:] markers in ChatMessageItem + remove deprecated /chat route

Changes:
1. AgentDetail ChatMessageItem: strip [image_data:data:url] markers from
   displayed text, extract image data URLs, and render them as thumbnails.
   Applies to both live WebSocket messages and history loaded from DB.

2. App.tsx: remove deprecated 'agents/:id/chat' route and Chat import.
   The chat interface lives in AgentDetail as a tab (#chat), not as a
   standalone route. Keeping this route caused repeated confusion where
   fixes were applied to the wrong component.

* fix(chat): deduplicate image display - strip markers always but only render thumbnail when imageUrl absent

* ui: move A2A async toggle to bottom of Company Info tab, before Danger Zone

* fix: hide msg_type param from LLM when a2a_async is disabled

When a2a_async_enabled is False, the send_message_to_agent tool
schema is dynamically simplified to remove the msg_type parameter.
This prevents the LLM from selecting notify/task_delegate modes
(which get silently overridden to consult) and confusing users
who see the raw tool call arguments in the chat UI.

* fix: show agent-to-agent sessions in Other Users tab

Agent-to-agent sessions store creator's user_id, causing them to be
filtered out from the Other Users admin view. Exempt source_channel=agent
sessions from the user_id filter so they always appear.

* feat: add DingTalk media message support (image, file, voice, video)

- Add dingtalk_token.py: global access_token cache with auto-refresh
- Add dingtalk_reaction.py: thinking indicator (reaction) during LLM processing
- Enhance dingtalk_stream.py: media download pipeline, auto-reconnect with
  exponential backoff, support for picture/richText/audio/video/file messages
- Update dingtalk.py: accept image_base64_list and saved_file_paths in message
  processing, forward media to LLM with vision support
- Update dingtalk_service.py: add download_dingtalk_media convenience wrapper

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* fix: mirror A2A replies to shared chat session for visibility

When an agent sends a notify/task_delegate message, the target agent's
reply was only saved to its Reflection Session (source_channel=trigger),
not to the shared A2A chat session. This made A2A conversations invisible
in the admin 'Other Users' chat history.

Now the reply is also saved to the A2A session via _a2a_session_id passed
through _wake_agent_async → wake_agent_with_context → dummy trigger config
→ _invoke_agent_for_triggers.

* fix: remove hardcoded 2-round tool limit for A2A wake

The 2-round max_tool_rounds_override was too restrictive for A2A wake
invocations, causing agents to hit the limit before completing basic
tasks. Each agent already has a configurable max_tool_rounds setting
(default 50), which provides sufficient protection against runaway
tool calls.

* fix: load latest 500 messages instead of oldest 500

For sessions with thousands of messages, the messages API was returning
the oldest 500 (ORDER BY created_at ASC LIMIT 500), making recent A2A
conversations invisible. Now uses a subquery to fetch the latest 500
messages by created_at DESC, then displays them in chronological order.

* stop tracking .agents files

* perf: fix N+1 queries in session list API

scope=all was issuing 1+2N DB queries (1 count + 2 agent-name lookups
per session). scope=mine was issuing 2N queries (1 user-count + 1 total
per session). Both are now replaced with 3-4 bulk queries total,
regardless of session count.

Affected endpoint: GET /api/agents/{id}/sessions?scope=all|mine

* chore: add deploy.md to .gitignore (contains private server credentials)

* fix: enable fallback model when primary LLM returns error

- Backend: call_llm returns error strings rather than raising exceptions;
  detect error-prefixed responses and raise RuntimeError to trigger the
  existing fallback model retry logic in the websocket handler
- Frontend: handle 'info' WebSocket message type; show a subtle
  auto-dismissing info banner (6s) when the system switches to fallback
  model, with a manual dismiss button

* fix(okr): auto-create OKR Agent per tenant when OKR is first enabled

Previously, seed_okr_agent() was a global startup-time seeder with a
file marker — new tenants enabling OKR via Company Settings never got
an OKR Agent, causing 'OKR Agent not found' errors in the UI.

Fix:
- Add seed_okr_agent_for_tenant(tenant_id, creator_id) in agent_seeder.py:
  tenant-scoped, on-demand seeder with DB-only idempotency (no file
  marker), safe to call multiple times.
- In update_okr_settings (PUT /api/okr/settings): detect False->True
  transition on 'enabled' and fire seed_okr_agent_for_tenant as a
  background asyncio task so the API response is not blocked.

* fix: reload model config per-message for real-time Settings updates

Previously the LLM model was loaded once at WebSocket connect time and
cached for the entire session. Changing the model in Settings required
a page refresh to take effect. Now the agent's primary/fallback model
is re-fetched from the DB on every incoming message, so configuration
changes apply to the next message immediately.

* fix(okr-ui): delay re-fetch after OKR enable + fix toggle flexShrink

- After enabling OKR, the backend creates the OKR Agent asynchronously.
  The frontend now waits 2.5s then invalidates the members-without-okr
  query so the 'Open OKR Agent Chat' button appears automatically without
  requiring a manual page refresh.
- While the agent is being seeded, shows a spinner 'Creating OKR Agent...'
  instead of the misleading 'OKR Agent not found' text.
- Toggle label now has flexShrink:0 to prevent layout overflow.

* fix(okr-ui): fix React import for TS build (add explicit React import)

* fix(okr-ui): force refetch OKR settings on mount to fix stale cache after company switch

The OKR page was using ['okr-settings'] as query key (no tenantId), so
React Query served a stale cached response when switching companies.
If the previous company had OKR disabled, the new company's OKR page
would still show 'OKR is not enabled' until the cache expired.

Fix: add user.id to queryKey so entries are per-user, and set
staleTime:0 + refetchOnMount:true so navigation to the OKR page
always fetches fresh settings data.

* fix(okr-ui): replace label toggle with button; invalidate agents on OKR enable

- OKR toggle: replace label/checkbox with <button> element (consistent with
  the rest of the codebase). Eliminates the label implicit-width side-effects
  that made the toggle area appear too wide.
- seeding timeout: also invalidates ['agents'] query so the sidebar Agent
  list updates automatically (OKR Agent appears without manual page refresh).
  Increased timeout to 3000ms for safer backend creation timing.

* fix(okr): remove is_system filter from OKR Agent lookup queries

The is_system column may be NULL for agents created on-demand via
seed_okr_agent_for_tenant() (new tenants enabling OKR via settings).
Filtering by is_system==True silently returned no results, causing
'OKR Agent not found' to appear in the UI even though the Agent
actually existed in the sidebar.

Fix: query by tenant_id + name.ilike('%OKR%') which is unambiguous
within a tenant. Applied to both members-without-okr and
trigger-member-outreach endpoints.

* fix(okr-ui): resize OKR toggle switch to 36x20px

The previous OKR toggle switch was sized 40x24px, making it noticeably
larger than the other standard toggle switches in the Enterprise Settings
(e.g., LLM tools, which are 36x20px), leading to a 'wrong width' visual
anomaly.

Fix: resize the OKR toggle container to 36x20px and its inner circle to
16x16px to match the platform's standard toggle dimensions.

* fix(okr-ui): fix field name mismatches in MembersWithoutOKRPanel

The panel was silently not rendering because:
1. Frontend used 'members' but backend returns 'members_without_okr'
2. Frontend used 'member.name' but backend returns 'display_name'

When data.members was undefined, data.members.length threw TypeError,
causing React to silently remove the component from the render tree.

Fix: update TypeScript interface and component to use the correct
field names that match the API response.

* fix(okr-ui): fix field name mismatches in MembersWithoutOKRPanel

The panel was silently not rendering because:
1. Frontend used 'members' but backend returns 'members_without_okr'
2. Frontend used 'member.name' but backend returns 'display_name'

When data.members was undefined, data.members.length threw TypeError,
causing React to silently remove the component from the render tree.

Fix: update TypeScript interface and component to use the correct
field names that match the API response.

* fix(okr-api): fix members-without-okr endpoint and add owner_display_name

Three backend fixes:
1. Fix User query in members-without-okr: replace User.full_name/User.email
   (which don't exist as plain DB columns; email is an association proxy
   that throws NotImplementedError when used in select()) with User.display_name.
   The endpoint has been 500-crashing silently, so the panel never rendered.

2. Add owner_display_name to ObjectiveOut schema.

3. Add bulk lookup of owner display names in list_objectives: after fetching
   objectives, query User.display_name and Agent.name for member-level objectives
   and pass them through to the response, so member OKR cards correctly show
   who the objective belongs to.

Also add top-level imports for User and Agent in okr.py to support the new
owner name lookup code in list_objectives.

* fix(okr-api): add backward-compat members alias to prevent TypeError on old frontends

When the backend was updated (members_without_okr field) without a simultaneous
frontend rebuild, old frontend builds calling data.members.length crashed with
TypeError: Cannot read properties of undefined (reading 'length').

Adding 'members' as an alias alongside 'members_without_okr' keeps old frontend
builds working while new builds are deployed.

* feat(okr): fix agent link URL + add auto-sync relationship network

Two fixes + one new feature:

1. Fix 'Chat with OKR Agent' button: navigate to /agents/{id} (agent detail
   page) instead of /chat/{id} (which was incorrect).

2. Auto-sync OKR Agent relationships on enable:
   - When OKR is toggled on, after seeding the agent, automatically connect
     it to ALL active OrgMembers (org-structure-synced humans, e.g. Feishu)
     as team_member relationships and ALL company-visible agents as
     collaborator relationships.
   - Regenerates the workspace relationships.md so the context is immediately
     available to the OKR Agent.

3. Add POST /api/okr/sync-relationships endpoint + matching 'Sync Now' button
   in Company Settings → OKR tab so admins can re-sync at any time without
   having to disable and re-enable OKR.

* feat: hash-based tab links, fix agent chat URL, fix sync auth, add tenant delete

Changes:
1. EnterpriseSettings.tsx — Hash-based tab deep-linking: /enterprise#okr,
   #models, #tools, #skills, #invites, #quotas, #users, #org-structure,
   #approvals, #audit. URL updates on tab switch via replaceState.

2. OKR Agent button — href now includes #chat so users land directly on
   the chat tab of the agent page.

3. Sync Now button — switched from raw fetch() to project-standard fetchJson()
   so the Authorization header is correctly included (was returning 401).

4. OKR.tsx — 'Enable OKR in Company Settings' button now navigates to
   /enterprise#okr (was using ?tab=okr query param which had no effect).

5. tenants.py — Added DELETE /tenants/{id} endpoint (was returning 405
   Method Not Allowed). Performs ordered cascade deletion:
   relationships → tasks → sessions → triggers → channel_configs →
   agent_permissions/credentials → agents → OKR/org data → users → tenant.
   Returns fallback_tenant_id pointing to another company the caller belongs to.

* fix: tenant delete table names, remove OKR auto-connect mode, remove auto-sync

1. tenants.py — Fix DELETE /tenants/{id} cascade table names:
   - chat_messages (was: messages)
   - agent_triggers (was: triggers)
   - approval_requests with agent_id subquery (was: 'approvals WHERE tenant_id')
   - Move approval_requests + notifications deletions BEFORE agent deletion
     to avoid FK constraint violations.

2. okr.py — Remove auto-sync of relationships on OKR enable.
   The OKR Agent's relationship list is now managed solely by admins:
   manually (via the Relationships tab) or via POST /api/okr/sync-relationships.

3. AgentDetail.tsx — Remove isSystem/Auto-Connect Mode guard from
   RelationshipEditor so OKR Agent's Relationships tab works identically
   to every other agent's — fully editable, supporting add/edit/delete.

* feat: OKR relationship-based member filtering + platform user sync

* fix: chat_messages delete uses agent_id not session_id

chat_messages table has no session_id column.
Delete directly via: DELETE FROM chat_messages WHERE agent_id IN (...)
chat_sessions also deletes via agent_id (already correct).

* feat(okr): implement Nudge Members via run_agent_oneshot

- heartbeat.py: add run_agent_oneshot(agent_id, prompt, max_rounds)
- okr.py: rewrite trigger_member_outreach with real member query + chat history injection

* feat(okr): implement Nudge Members via run_agent_oneshot

* fix(okr): restore Sync Now + Nudge Members buttons lost in branch merge

Backend:
- Add _sync_okr_agent_relationships() helper + sync-relationships endpoint
- Fix members_without_okr: relationship-filtered, returns tracked_user_ids/agent_ids
- Lenient OKR Agent lookup (no is_system=True requirement)

Frontend (OKR.tsx): Restore MembersWithoutOKRPanel with Nudge Members button
Frontend (EnterpriseSettings.tsx): Add Sync Relationship Network + Sync Now button

* fix(okr): restore Sync Now + Nudge Members buttons lost in branch merge

* fix(okr): fix ImportError AgentRelationship location + settings returns okr_agent_id

- AgentRelationship/AgentAgentRelationship are in app.models.org (not .agent)
  → was causing 500 on members-without-okr and sync-relationships
- Add okr_agent_id to OKRSettingsOut + get_okr_settings query
  → UI gets OKR Agent ID from reliable /settings endpoint
- members_without_okr: fallback to all members when relationship list empty
- EnterpriseSettings: use settings.okr_agent_id as primary source
- Fix button URL: /agents/{id} instead of /chat/{id}

* fix(okr): fix ImportError AgentRelationship location + settings returns okr_agent_id

* fix(okr): fix AgentAgentRelationship import in trigger_member_outreach (models.org not models.agent)

* fix(okr): fix AgentAgentRelationship import in trigger_member_outreach

* fix(okr): fix User field names in trigger_member_outreach (full_name/email, not username/display_name)

* fix(okr): fix User field names in trigger_member_outreach

* fix(okr): fix all User field refs — use display_name/avatar_url (not full_name/email/username)

* fix(okr): fix all User field refs — display_name not full_name

* fix: correct Feishu free busy scope

* fix: scope agent relationship candidates by tenant

* fix(okr): enforce send_message_to_agent for agents; fire-and-forget to stop infinite loop; reduce max_rounds

* fix(okr): correct agent tool + stop infinite loop

* fix: OKR UI and notification improvements

- Move Nudge Members button to top of member list header (always visible)
- Fix notification title: send_message_to_agent and send_file_to_agent now
  use their own action_type instead of being aliased to send_feishu_message
- Add owner_name to ObjectiveOut: batch-resolve User/Agent names so member
  objectives show real names (e.g. 'PI', 'PM') instead of raw 'agent' label
- Update OKR.tsx Objective type and memberGroups to use owner_name

* build: update dist.zip with OKR UI and notification fixes

* fix(okr): improve outreach \u2014 channel priority, agent OKR recording, correct max_rounds

- channel_hint: prioritize any external channel over web notification
  so Feishu/DingTalk/WeChat Work members receive messages in their daily tool
- agent member block: include agent UUID so OKR Agent can pass correct
  owner_id when calling create_objective after A2A reply
- task prompt: add Step 3/4 for agents \u2014 parse reply, call create_objective
  + create_key_result(s) to record OKR immediately after A2A exchange
- remove contradictory FIRE-AND-FORGET section (only humans are F&F now)
- max_rounds: bumped from N*2+3 to N*5+3 to accommodate OKR write calls

* fix(okr): normalize UUID to str in owner_name resolution to fix type mismatch

* fix(okr): embed real UUID in agent prompts; add OrgMember lookup for Feishu users

* fix(okr): friendly fallback label for unresolvable owners; validate owner_id in create_objective

- OKR.tsx: replace UUID prefix fallback with '用户'/'数字员工' for OKRs
  whose owner_id cannot be resolved (phantom/unlinked UUIDs)
- agent_tools._create_objective: validate owner_id against User/OrgMember/Agent
  tables before accepting it, preventing hallucinated UUIDs from being stored

* fix(okr): add owner_name resolution in create_objective tool

- When owner_id is not provided or fails UUID validation, fall back to
  resolving the owner by display_name / OrgMember.name.
- Allows OKR Agent to record OKRs for Feishu channel users by passing
  owner_name='王珂' instead of an invented UUID.
- Resolution priority: User.display_name → OrgMember.name (for users),
  Agent.name (for agents).

* feat(okr): inject company OKR context into outreach prompt; fix agent UUID template; adaptive max_rounds

- Fetch current-period company OKRs before building the prompt and include
  them under '公司目标' so members can align their individual OKRs
- Restore agent member block with verbatim STEP 1-4 UUID-embedded create_objective
  template (prevents LLM from hallucinating nil UUID)
- max_rounds now adaptive: humans × 2 + agents × 6 + 3 buffer
- Clarify TOOL RULES: agents = STEP 1-4 collect+record; humans = fire-and-forget

* feat(okr): inject company OKR context into outreach prompt; fix agent UUID template; adaptive max_rounds

* fix(okr): expose owner_name to create_objective JSON schema

- The LLM was rejecting Feishu users because it was trying to pass phantom UUIDs
  or complaining about inactive accounts, as it didn't know owner_name was a valid param.
- Added owner_name to the tool JSON schema so the LLM is aware it can use names directly.

* feat(okr-ui): add distinct agent badge in OKR board

- Render agent objectives with a purple icon and styling to distinguish
  digital employees from human members in the dashboard

* fix(frontend): import React to resolve TS compilation error in EnterpriseSettings

* fix(okr): add owner_name resolution to create_objective

* fix(okr-ui): prevent objective owner badge from wrapping on long titles

* fix(feishu): use stable user_id over open_id for user resolution to prevent org-sync duplicates

* style(okr): merge owner badge into inline text flow

* fix(okr-ui): add missing closing div for layout fix

* style(okr): align expand chevron to top of title text

* style(okr): move nudge button and description below Members Without OKR title

* fix(channel-user): deduplicate users by username prefix before creating new account

* feat(okr): sync O+KR context to nudge prompt; update UI to redirect to OKR Agent chat history

* debug(feishu): log user_id_from_event alongside open_id for sender ID verification

* fix(channel-user): use prioritized limit(1) query in _find_org_member to prevent MultipleResultsFound crash

* fix(okr): exclude NULL user_id OrgMembers from members-without-okr so linked users with OKRs are correctly recognized as covered

* fix(system-agents): block deletion of is_system agents; harden seeder dedup to auto-stop extra OKR Agents on startup

* docs: rename Pulse engine references to Aware

* feat(okr): add okr_agent_id to OKRSettings and create DB unique constraint for system agents

* fix(migration): shorten revision ID to fit varchar(32) limit

* fix(okr): resolve OrgMember.id → user_id in create_objective; fix bad owner_id in DB

* fix(okr): show genuine new Feishu-only members in No OKR list

* refactor(okr): replace two-pass member query with generic canonicalization

* fix(okr): improve owner tag UX; staleTime=0 so data always fresh on navigation

* Fix Feishu channel user identifier handling

* Improve migration safety and performance for chat messages

- Add idempotency to 'increase_api_key_length' migration
- Add database index to ChatMessage.conversation_id
- Clean up imports in WeCom API and ChannelUserService

* Refine channel user lookup and UI indentation in AgentDetail

- Optimize DingTalk/Feishu lookup logic in channel_user_service.py
- Fix code indentation and formatting in AgentDetail.tsx

* Restore AgentDetail.tsx to main branch version

* fix: harden channel identity resolution

* 不使用openid进行查询

* user_id 可以缺失

* fix:import error

* fix(migration): merge okr_agent_id and increase_api_key_length heads

* Add Workspace preview and revision support

Co-authored-by: Codex <[email protected]>

* Add Workspace preview and revision support

* Fix Workspace operation panel layout

* Refine Workspace file references and PDF preview

* Move Workspace file tree to right side

* Refine Workspace right sidebar collapse

* fix: heartbeat run_agent_oneshot uses new app.services.llm import path

PR #411 restructured llm_utils.py → llm/__init__.py but the run_agent_oneshot
function in heartbeat.py still referenced the old module, causing
ModuleNotFoundError and silently dropping all OKR member outreach messages.

* Collapse chat navigation when opening Workspace panel

* Move Workspace tree toggle into document area

* Simplify Workspace file tree chrome

* Use public base URL for published pages

* Make Workspace autosave feedback visible

* Refine Workspace tree and download controls

* Improve Workspace tree toggle visibility

* [codex] Add Workspace operation panel (#419)

* Add Workspace preview and revision support

* Fix Workspace operation panel layout

* Refine Workspace file references and PDF preview

* Move Workspace file tree to right side

* Refine Workspace right sidebar collapse

* Collapse chat navigation when opening Workspace panel

* Move Workspace tree toggle into document area

* Simplify Workspace file tree chrome

* Use public base URL for published pages

* Make Workspace autosave feedback visible

* Refine Workspace tree and download controls

* Improve Workspace tree toggle visibility

* fix(migration): merge workspace_revisions and merge_okr_api_key heads

* fix(websocket): emit workspace_activity in tool_call events for file tools

PR #419 added WorkspaceOperationPanel in the frontend with auto-open logic
triggered by workspace_draft / workspace_activity WebSocket events. However,
the backend never emitted these events — the panel could not auto-open.

Adds workspace_activity: {action, path, tool, ok} to tool_call events when
status=done for write_file, edit_file, delete_file and convert_* tools.
This mirrors the existing live_preview pattern used by AgentBay.

* feat(tools): add built-in format conversion tools (Word/Excel/PPT/PDF)

- Added convert_csv_to_xlsx, convert_html_to_pptx, convert_markdown_to_docx and two pdf conversion tools
- Updated tool_seeder to register the tools
- Added WeasyPrint OS dependencies to backend Dockerfile
- Appended parsing dependencies (weasyprint, markdown, beautifulsoup4) to pyproject.toml

* fix(Dockerfile): correct package libgdk-pixbuf-2.0-0

* fix(backend): add chinese fonts to Dockerfile for weasyprint

* feat(ui): add delete button to workspace files on hover

* fix(okr): handle channel-only members (no User account) correctly in covered_ids check

- members_without_okr: channel-only members with OrgMember.id as owner_id
  were never excluded from the 'without OKR' list because the covered_ids
  check was skipped entirely for user_id=None rows.
  Now checks row.id (OrgMember.id) in covered_ids as well.

- create_objective: when owner_type=user and owner_id matches an OrgMember
  that has no linked user_id (channel-only), previously returned HTTP 422.
  Now stores OrgMember.id directly as owner_id so the OKR Agent can
  create OKRs for channel members and have them properly excluded from
  the 'members without OKR' panel.

* fix(okr): resolve OrgMember.id → User.id in _create_objective agent tool

When OKR Agent calls create_objective with an OrgMember.id as owner_id
for a user-type objective, stored OrgMember.id directly causing '?' display.
Now resolves OrgMember.id → linked User.id when available.

* fix(okr): handle OrgMember.id as owner_id in name resolution and covered_ids check

Two defensive fixes for when OKR Agent stores OrgMember.id as owner_id
instead of the linked User.id:

1. list_objectives: after User.id name lookup fails, fall back to querying
   org_members table by id to get display name. Prevents '?' from showing.

2. members_without_okr: for linked members (user_id != NULL), check BOTH
   user_id and OrgMember.id against covered_ids. Previously only checked
   user_id, so a member with owner_id=OrgMember.id in their OKR would still
   appear in the 'without OKR' list even though an OKR existed.

* fix(feishu-ws): add reconnection loop to prevent 4h+ silent message loss

Previously, if _connect() exited (server close or exception), the async
task entered an infinite sleep(3600) loop — keeping the task 'alive' but
with a permanently dead WS connection. No reconnect, no error log, all
messages silently dropped.

Now _run_async_client() retries _connect() in a loop:
- Normal exit (_connect() returns): reconnect after 5s
- Exception: log error and reconnect after 10s
- CancelledError: clean disconnect and exit

Also removes the defunct ping_task creation which was only called after
_connect() exited (i.e., too late to be useful).

* fix(feishu-ws): use health-watch loop instead of aggressive 5s reconnect spam

_connect() is non-blocking — it establishes the WS and immediately starts
_receive_message_loop() as a task. The SDK itself handles reconnects
(infinite retries, 120s interval via _reconnect_count=-1).

Previous fix (5s reconnect loop) caused constant false 'Connection closed'
warnings because _connect() returns quickly after connecting.

New approach:
1. Call _connect() once (starts SDK's receive loop)
2. Start ping loop
3. Health-watch every 60s: if client._conn is None for >3min (SDK stuck),
   force a hard reconnect as last resort

* feat(okr-ui): hash-based tab routing, wider toggle, #Chat nav

- EnterpriseSettings: each tab now has its own hash address
  (e.g. /enterprise#okr). Tab click updates window.location.hash;
  hashchange event keeps state in sync for back/forward navigation.
- OKR page 'Enable OKR' button: navigate to /enterprise#okr
  instead of /enterprise?tab=okr.
- OKR toggle: widen from 40×24 to 52×28 px for a more comfortable
  click target; knob adjusted to 24×24 px.
- 'Chat with OKR Agent' button: href now includes #Chat suffix so
  the agent detail page opens directly in chat mode.

* fix(okr): auto-create OKR Agent on enable, fix #chat hash

1. update_okr_settings: when enabled=True and no okr_agent_id exists,
   auto-call seed_okr_agent_for_tenant() so the OKR Agent is created
   immediately for the current tenant.
2. seed_okr_agent_for_tenant: was missing the critical step of writing
   okr_agent_id back to OKRSettings — added it.
3. Chat link: #Chat → #chat (lowercase to match agent detail routing).

* fix(okr): rewrite period generation — fix duplicate Q4 and missing Q1

The old list_periods logic had multiple bugs:
- _prev_start had broken cross-year quarter math ((month-4)%12 wrong)
- Inner loop called _compute_current_period which always returns TODAY's
  period regardless of the loop variable s
- pe was often None, falling through to wrong fallback calculation
- Result: Q4 2025 appeared twice, Q1 2026 was missing

New approach: step from a known anchor point (2 periods before current)
and generate exactly 4 consecutive periods [prev2, prev1, current, next1]
using simple, correct arithmetic for quarterly/monthly/custom modes.

* fix(okr): lock cadence and expand period history

* fix(okr): clarify cadence lock and dropdown behavior

* fix(okr): improve cadence setup and report triggers

* fix(okr): clarify report triggers and tabs

* fix(okr): stabilize report tab header

* feat(okr): skip daily reports on non-workdays

* feat(okr): redesign daily reporting flow

* feat(okr): add member daily report tool

* feat(okr): restrict daily collection to relationships

* fix(okr): stabilize daily collection replies

* fix(okr): route collection through channel and async a2a

* fix(okr): send agent collections via deterministic a2a

* chore: replace expired QR code with new Clawith_QRcode.png

Updated all 5 README files (EN, ZH, JA, KO, ES) to reference the new
community QR code image. Deleted the old QR_Code.png.

* chore: add new community QR code image

* fix(okr): simplify daily collection flow

* fix(ui): simplify company info layout

* fix(okr): stabilize report capture and clean ui

* refactor(okr): unify daily report reply flow

* fix(feishu-ws): stop creating duplicate connections that cause 'kicked by new connection'

Root cause: the health-watch loop was calling _connect() + _ping_loop()
on every reconnect attempt, creating additional WS connections and ping
tasks alongside the SDK's own internal ones. Feishu's server detected
multiple connections from the same App ID and kicked one every ~5 min,
causing a cycle of connect → kick → reconnect → kick.

Fix: only call _connect() + _ping_loop() once during initial startup.
Let the SDK's built-in auto_reconnect handle all subsequent reconnections.
The health-watch now only logs connection status changes for diagnostics.

* fix(okr): patch all tenant okr agents

* fix(okr): improve report browsing

* fix(ui): refine okr report browsing

* fix(tenant): backfill org members on tenant join flows

* feat(chat): add primary platform sessions and unread badges

* docs(test): translate primary session cases to chinese

* chore(docs): move local test cases out of repo root

* fix(chat): label primary sessions in list

* fix(relationships): label platform org members

* fix(relationships): show platform user badge

* fix(okr): self-heal missing daily report tool rows

* fix(triggers): route trigger replies to owned sessions

* feat: add WeChat, WhatsApp, and Google Workspace channel support

- Add WeChat channel API and service with QR code login flow
- Add WhatsApp channel API integration
- Add Google Workspace OAuth service and API
- Add Alembic migrations for WeChat and WhatsApp channel tables
- Extend ChannelConfig frontend component with new channel types
- Update EnterpriseSettings page with multi-channel management UI
- Add i18n translations (en/zh) for new channel settings
- Extend org_sync_adapter with multi-channel sync support
- Add SVG icons for Google, WeChat, WhatsApp
- Add qrcode type definitions for frontend

* fix(okr-prompt): strengthen daily report tool-calling directive

The OKR Agent was often 'thinking about' calling upsert_member_daily_report
instead of just calling it. Two causes:

1. Prompt was too passive ('immediately call' buried in bullet list)
2. Chat history contained 'Unknown tool' errors from a PM Agent
   that tried to call the OKR-only tool, poisoning the LLM's context

Fix:
- Rewrite prompt with ABSOLUTE RULE block and explicit anti-hallucination
  instructions (never claim tool is unavailable, ignore past errors)
- Cleaned 3 toxic 'Unknown tool' records from chat_messages DB

* fix(triggers): suppress duplicate recap after platform delivery

* fix(chat): suppress unread on active session

* fix(workspace): organize generated docs into folders

* fix(relationships): prevent duplicate member additions

* refactor(tools): rename send_web_message to send_platform_message

* feat: optimize Google Workspace integration with proxy support and UI refinements

* fix(messaging): prefer platform tool for platform users

* fix(okr): show member source in missing okr panel

* fix(okr): restore missing members panel import

* merge main

* feat(feishu): add document search tool

* fix: enforce bwrap-backed execute_code isolation

* refactor(credentials): remove stored website passwords

* fix: align channel tool history with web sessions

* refactor: consolidate okr alembic revisions

* fix(registration): restore ensure_identity_provider helper (#469)

Co-authored-by: yaojin <[email protected]>

* feat: OKR outreach error visibility, Feishu tool history rehydration, org sync error hints

- OKR outreach: admin error banner, auto-refresh, LLM API key decryption fix
- OKR nudge: prioritize send_channel_message for Feishu-linked members
- Feishu: rehydrate tool_call rows into proper LLM assistant+tool messages
- Feishu: extract _save_feishu_tool_call with unified schema
- Feishu: persist thinking buffer in assistant reply
- Feishu: simplify sender identity (remove user_id exposure)
- Org sync: differentiate error 40060 (data authority) from scope errors

* feat(okr): proactive channel warning when agent lacks bot config for member channels

- Backend: detect channel-only members (Feishu, etc.) whose channel is not
  configured on the OKR Agent; return channel_warnings in members-without-okr API
- Frontend: yellow warning banner showing affected members and missing channel,
  with link to Agent Settings; ⚠️ icon on each unreachable member

* fix(okr): improve settings feedback and daily collection debug

* fix(relationships): prevent duplicate agent additions

* feat(okr): add KR content update tool

* Optimize relationship visibility and org path handling

* fix(okr): expand member daily report detail display

* fix(okr): prefer updating existing objectives and KRs

* fix(okr): enforce requester-based edit permissions

* fix(okr): bust stale dashboard bundle cache

* refactor(okr): simplify grouped member objective headers

* fix(okr): resolve owner names in get_okr output

* fix(backend): avoid startup hangs on schema patch locks

* fix(backend): run startup db bootstrap as script

* fix 下拉列表

* feat(workspace): stream draft tool arguments

* feat: improve OKR company report generation

* fix(workspace): defer deleted preview until approval

* feat(workspace): let users lock the active preview

* fix(workspace): preserve edit mode during other file drafts

* fix(workspace): show image files in side preview tree

* fix(workspace): stop preview reloads while typing

* fix(workspace): respect preview lock and prompt on switches

* style(workspace): use focus icon for preview lock

* feat(workspace): improve side panel controls

* fix(workspace): improve rich file previews

* fix(workspace): improve preview history and tree actions

* fix(workspace): improve html preview responsiveness

* fix(workspace): smooth html preview resizing

* fix(workspace): expose full agent tree and markdown companions

* fix(workspace): stabilize previews and pdf export

* fix(workspace): improve document markdown and tree focus

* fix(workspace): harden path resolution and simplify tree toolbar

* style(workspace): align tree scope toggle left

* style(workspace): right align tree action buttons

* feat(relationships): improve add relationship form

* modify alembic

* drone

* drone

* chore: remove build artifacts and vite cache from git tracking

- Remove frontend/dist.zip (build artifact)
- Remove .vite/deps cache files
- Add .vite/ to .gitignore

* drone

* drone

* patch alembic

---------

Co-authored-by: QinRui <[email protected]>
Co-authored-by: nap.liu <[email protected]>
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Co-authored-by: QinRui <[email protected]>
Co-authored-by: Alex <[email protected]>
Co-authored-by: yaojin <[email protected]>
Co-authored-by: Codex <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants