Skip to content

v0.6.8: chat_handle schema + auto-mint nicknames + unknown-handle UX (F180-F184)#147

Merged
firstintent merged 2 commits into
mainfrom
v0.6.8-chat-handle
May 25, 2026
Merged

v0.6.8: chat_handle schema + auto-mint nicknames + unknown-handle UX (F180-F184)#147
firstintent merged 2 commits into
mainfrom
v0.6.8-chat-handle

Conversation

@firstintent
Copy link
Copy Markdown
Owner

Summary

Closes the SKILL ↔ code mismatch where ccteam-creator documented scientist-nickname handles (@curie etc.) but build_handle_map actually used the bare role field. Users following the doc would @curie in a Telegram group and get silent drops.

Also adds the unknown-handle reply UX so typos return a helpful list instead of vanishing, and a 6th admin keyword (@ccteam list bots / bots / who) for per-chat bot lookup.

  • F180: AgentSpec.chat_handle: Option<String> workflow.yaml additive field (backward-compatible; omitted = role).
  • F181: BotRegistration.chat_handle persisted via chat_register_bot MCP; daemon auto-mints from agent_naming::pick_unused_bot_name() when omitted (returns the minted handle in the MCP reply).
  • F182: build_handle_map uses chat_handle.unwrap_or(role); cross-slug collisions resolve to <handle>__<slug> (double underscore) for the second claimant in deterministic (slug, role) sort order. The separator stays inside router::parse_first_mention's [a-zA-Z0-9_-] charset so users can type @curie__beta and it routes end-to-end — an @ separator would silently truncate at the second sigil and break the very UX this PR fixes.
  • F183: ccteam-creator Phase 5.5/5.6 updated; auto-mint behavior implemented MCP-side so the skill body stays version-agnostic.
  • F184: router unknown-handle returns Unknown handle '@xxx'. Available bots in this chat: @alice @bob; new admin keyword family @ccteam list bots / @ccteam bots / @ccteam who for the per-chat scoped view. Bare @ccteam list / ls keeps the V0.6.x global registry dump.
  • chore: TODO(V0.7-chat-handle) anchor removed from daemon.rs; no_silent_todo_test count 6 → 5 (test renamed f168_v07_deferred_tag_count_is_six_is_five); dev-coupling-audit chat_handle row moved to a V0.6.8-closed table.

Breaking-behavior note

@ccteam list bots previously aliased to the global list dump; in V0.6.8 it returns the per-chat scoped list (same format the unknown-handle reply renders). Bare @ccteam list / ls keeps the original global semantics for ops use. Pre-v1.0 + CLAUDE.md §五 #4 allows this; calling it out so operators don't get surprised.

Test plan

  • cargo test --workspace --locked --no-fail-fast: 1649 pass / 4 fail (vs V0.6.7 baseline 1639/1). The +10 passes are the new handle_resolution_test (10 cases). 1 of the 4 failures is the documented workflow_summary_reflects_agent_spawn_and_done_events V0.6.7 baseline flake. The other 3 (daemon_dm_no_at_mention_auto_routes_to_single_bot, daemon_wires_mock_channel_to_supervisor_inbox, trigger_file_shutdown_exits_cleanly_and_cleans_pidfile) are env-race transients on this host — each passes in isolation; behaviour matches CLAUDE.md §六 inotify-busy 宿主可见 watcher/SSE 类 transient. CI re-run should report clean.
  • cargo clippy --workspace --all-targets --locked -- -D warnings: 0 warnings.
  • cargo fmt --all -- --check: 0 drift.
  • workflow.yaml chat_handle: foo deserializes; absent → None; serde round-trip with skip_serializing_if = "Option::is_none".
  • BotRegistration JSON round-trips chat_handle (omitted vs explicit).
  • Handle-resolution unit tests: no chat_handle → role fallback; with chat_handle → wins; cross-slug collision → <handle>__<slug>; deterministic across input order; available_handles_for_chat filters by (channel, chat_id).
  • End-to-end routing: router::route("@curie__beta payload", &map, 0) returns Route::Bot { slug: "beta", role: "lead" } when (alpha, lead, curie) + (beta, lead, curie) both registered. Bare @curie still routes to alpha.
  • unknown_handle_inbound_replies_with_available_bots_in_chat: typing @ghost hello in a chat with helper-bot/main registered produces a channel reply containing @ghost and the available @main handle.
  • @ccteam list bots, @ccteam bots, @ccteam who all return the per-chat scoped list; with 0 bots → No bots registered in this chat.
  • Auto-mint: chat_register_bot without chat_handle returns chat_handle: "Euclid" (first unused in SCIENTIST_NAMES); a second register on a separate slug returns Archimedes; caller-supplied handle overrides the mint.

Acceptance

After merge:

  • mcp__ccteam__chat_register_bot without chat_handle → bot defaults to @Euclid / @Archimedes / etc.
  • @unknownhandle hello in a registered group → group receives Unknown handle '@unknownhandle'. Available bots in this chat: @<bot>....
  • @ccteam list bots → per-chat list; @ccteam list → global list.

ccd and others added 2 commits May 25, 2026 02:56
…handle UX

F180: AgentSpec.chat_handle Option<String> workflow.yaml additive field.

F181: BotRegistration.chat_handle + chat_register_bot MCP optional input;
when omitted, daemon auto-mints from agent_naming::pick_nickname() across
currently-registered bots.

F182: build_handle_map uses chat_handle.unwrap_or(role); cross-slug
collisions resolve to <handle>@<slug> for the second claimant.

F183: ccteam-creator SKILL Phase 5.5/5.6 documents auto-mint behavior;
implementation lives in the MCP handler so the skill body has no
version-coupled logic.

F184: router unknown-handle reply lists per-chat available bots;
@ccteam list bots admin keyword (6th in nl_admin's set).

chore: TODO(V0.7-chat-handle) anchor cleared from daemon.rs;
no_silent_todo_test count 6 -> 5; dev-coupling-audit chat_handle row
moved to V0.6.8-closed.

Closes V0.6.8 F180/F181/F182/F183/F184.
The original `<handle>@<slug>` suffix was unreachable via chat:
`router::parse_first_mention` accepts `[a-zA-Z0-9_-]` for handle chars
and stops at any other byte, so typing `@curie@beta` parses as handle
"curiebeta" and routes to UnknownHandle — strictly worse UX than the
silent drop it replaced (listed-but-unreachable).

Switch the separator to `__` (double underscore). The suffixed token
stays inside the IM handle charset so users can type `@curie__beta`
and it routes end-to-end. New `router::collision_suffix` is the
single source of truth; `build_handle_map_from_bots` +
`available_handles_for_chat` both call it.

Add `route_resolves_collision_suffix_handle_end_to_end` regression
test driving the real parse path. Also bump `build_handle_map_from_bots`
to `pub` so the test (and any future integration test) can probe the
collision path without reaching for `pub(crate)`.

Update doc-comments (AgentSpec, BotRegistration, SKILL.md Phase 5.6,
dev-coupling-audit closed-anchor row) to reflect the new suffix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@firstintent firstintent merged commit 4c19229 into main May 25, 2026
2 checks passed
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