Skip to content

feat: add /new and /reset session commands for IM channels#342

Open
nap-liu wants to merge 4 commits intodataelement:mainfrom
nap-liu:pr/channel-session-reset
Open

feat: add /new and /reset session commands for IM channels#342
nap-liu wants to merge 4 commits intodataelement:mainfrom
nap-liu:pr/channel-session-reset

Conversation

@nap-liu
Copy link
Copy Markdown

@nap-liu nap-liu commented Apr 8, 2026

Summary

  • New channel_commands.py: parses /new and /reset, archives old session, creates fresh one
  • Integrated into DingTalk and Feishu message handlers
  • Replies via channel webhook, skips LLM processing

Test plan

  • DingTalk E2E: /new command recognized, session archived

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
nap.liu and others added 2 commits April 15, 2026 16:20
… in feishu

Review concern dataelement#1 from dataelement#368: in the Feishu text flow, /new and /reset were
handled before channel_user_service.resolve_channel_user() ran, so
handle_channel_command() received creator_id as user_id. That made the
replacement P2P session owned by the agent creator instead of the real
sender — a mismatch against normal Feishu P2P sessions, which are
attributed to platform_user_id.

Fix: detect the command early (so we can still short-circuit before LLM
work), but defer the actual archive/create until after resolve_channel_user
has produced platform_user_id. Mirror the same user_id selection rule that
find_or_create_channel_session uses: creator_id for group chats (placeholder
owner), platform_user_id for P2P.

DingTalk already resolved the user before the command check, so no change
there. WeCom does not implement /new /reset, so no change there.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review concern dataelement#2 from dataelement#368: handle_channel_command() was finding the old
session using only (agent_id, external_conv_id). Today the external IDs
are prefixed per channel (e.g. feishu_p2p_*, dingtalk_p2p_*) so a
collision is unlikely, but if the prefix scheme ever changes a /new
issued on one channel could archive a session from a different channel.

Add source_channel to the WHERE clause of the archive-lookup SELECT.
The source_channel parameter was already on the function signature, so
no caller changes are needed.

Adds unit tests covering both review concerns:
- source_channel is included in the lookup filter
- new session is created with source_channel from the caller
- replacement session's user_id comes from the caller (locks the
  contract the Feishu fix relies on)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@nap-liu
Copy link
Copy Markdown
Author

nap-liu commented Apr 15, 2026

Pushed fixes addressing both concerns from #368.

Concern 1 — sender attribution (CRITICAL): In backend/app/api/feishu.py the /new / /reset block is now deferred until after channel_user_service.resolve_channel_user() has produced platform_user_id. The command is still detected early (just a string check) so we can short-circuit before any LLM work, but the actual archive + new-session creation now happens after user resolution. The user_id passed to handle_channel_command() mirrors the rule used by find_or_create_channel_session in the normal path — creator_id for group chats (placeholder owner), platform_user_id for P2P — so a Feishu /new in P2P now produces a session owned by the real sender, matching regular Feishu P2P session ownership.

DingTalk (backend/app/api/dingtalk.py) already called resolve_channel_user() before the command check, so its P2P attribution is correct and I left it as-is. WeCom does not wire up /new / /reset in this PR, so nothing to change there.

Concern 2 — source_channel scoping: In backend/app/services/channel_commands.py, the archive-lookup SELECT now filters on source_channel in addition to agent_id and external_conv_id. The source_channel parameter was already on handle_channel_command()'s signature, so callers are unchanged. This makes the command defensive against any future change to the per-channel external-ID prefix scheme — a /new on one channel can never accidentally archive a same-external-id session belonging to a different channel.

Tests: backend/tests/test_channel_commands.py covers:

  • test_handle_channel_command_scopes_lookup_by_source_channel — asserts the archive-lookup WHERE clause includes source_channel with the caller-supplied value.
  • test_handle_channel_command_only_archives_same_channel_session — when the lookup returns nothing, no existing row is mutated and the new session carries the right source_channel.
  • test_handle_channel_command_uses_caller_user_id_for_new_session — locks the contract the Feishu fix relies on: the replacement session's user_id is whatever the caller passed (so Feishu passing platform_user_id actually sticks).
  • test_is_channel_command_recognises_slash_commands — sanity checks the command detector.

Could you take another look?

/new and /reset used to immediately create a replacement ChatSession with
title="New Session". Since IM channels don't go through the websocket
auto-title path (that only fires for web clients), the placeholder stuck
forever and every /new'd session showed up as "New Session" in the
sidebar regardless of what the user typed next.

Defer creation to the user's next message: archive the old session's
external_conv_id, then let find_or_create_channel_session build a fresh
one on the next inbound message — which already titles it from the first
message content (up to 40 chars).

The handler's return value no longer includes a session_id (none has been
created yet); the only caller (dingtalk.py) only consumes ["message"].

Tests updated: removed pre-creation assertions, added a regression that
asserts db.added == [] and no session_id leaks in the response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

1 participant