Repository navigation
feat(orchestrator): add activity, private-row and caller options to extension tools - #576
ladislav-urban wants to merge 1 commit into
Conversation
…p its audit row private and be asked without a known caller Three things a tool built in core can do that an extension's tool could not: - activity_label: the chat's activity line while it runs. Built-in labels still win; an unlabelled extension tool keeps the fallback line. - private_row: its dispatch audit row is private (_private_tool_row), for a tool whose input or result quotes the speaker's own data, as the Act as me, fact and history tools' rows are. - known_caller=False: asked on a turn with no verified caller too, for a surface an extension adds that names its user without proving who is speaking (a kiosk or embedded widget). register_tool requires such a tool to have an offered(), which decides alone; it is still never offered on an unattended, private, AI-app or replay turn, and the lockdown and outside-content hold apply as before. And one fix: an extension tool whose result is waiting_for_approval got core's generic "Waiting for approval" chip, meant for actions the department gate or a teammate's send holds. The gate never holds an extension tool, so what it waits on (its own approval) is now its chip's to describe. Left out on purpose: letting an extension chip carry a decision_id for the chat to show the card under the message. Upstream's chat only renders the card kinds it knows (ChatActionCards falls through to the meeting and app-action cards), so an extension's id would render nothing until there is a generic card renderer for extensions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
64b7a6c to
3cd7f23
Compare
|
This pull request now has merge conflicts with |
| assert "open_tool" not in offered[0] | ||
| # Still never on an unattended or private turn. | ||
| for kwargs in ({"unattended": True}, {"private_to_principal": True}): | ||
| _, _, offered = _run(_session(caller_person_id=None, origin_channel="kiosk", **kwargs), []) |
There was a problem hiding this comment.
🔴 The docs and PR description promise a known_caller=False tool is "never offered on an unattended, private, AI-app (mcp_client) or replay turn", but this test only covers unattended and private_to_principal. offered_tools itself no longer filters on caller identity, so those exclusions depend entirely on the caller. Before this change the caller_person_id check masked that, because an MCP-client or replay session without a person id got nothing. Add from_mcp_client=True and the replay case here. Check that the chat loop's call site really excludes them. If it doesn't, a kiosk-style offered() that returns True for origin_channel == "kiosk" would hand the tool to an AI-app turn, which the persona section says gets no extension tools.
| return None | ||
| from openexecutive.orchestrator import extensions | ||
|
|
||
| return extensions.activity_label(name) |
There was a problem hiding this comment.
🟡 extensions.activity_label calls load(), which imports every module named in OPENEXECUTIVE_EXTENSIONS and runs its register(). _label_for and _is_nameable now reach that for any tool name not in _LABELS, so the first activity line of a turn (and each _pick over the tool_uses) can run extension imports on the event loop. A raising or slow extension import would then delay the activity event. Read the registry without triggering load() here, since a tool can only be running if the registry has already loaded.
|
Claude review of this fork PR, requested by @johnrufusone: 🔴 1 must fix · 🟡 1 optional · 🟣 0 pre-existing. Push again and it needs a fresh |
Problem
An extension's tool can't do three things a built-in tool can: name the chat's activity line while it runs, keep its dispatch audit row private when it quotes the speaker's own data, or be offered on a surface the extension adds that names its user without a verified caller (a kiosk or embedded widget). And an extension tool whose result is
waiting_for_approvalgets core's generic "Waiting for approval" chip, meant for actions the department gate holds, which never holds an extension tool.Approach
ExtraToolgainsactivity_label,private_rowandknown_caller(defaultTrue).known_caller=Falserequires anoffered(), which then decides alone; such a tool is still never offered on an unattended, private, AI-app or replay turn, and the lockdown and outside-content hold apply as before.summarize_actionleaves a waiting result to the extension's own chip.agents.jsonandarchitecture-facts.yamldescribe the options.Checklist
pytest packages/core/tests/unit/)ruff checkandmypypass (make lint)cd packages/ui && npm run build) — UI not touched(see the "Architecture Docs" section in
CLAUDE.md)🤖 Generated with Claude Code