Rename bot references from ocw to OpenWorker

Correlation token now emits [ow:id]; legacy [ocw:id] replies still parse.
Invite hints and docstrings say @OpenWorker.
This commit is contained in:
Rohit C Prasad
2026-07-23 07:43:24 -07:00
committed by Rohit P
parent 1032e3a664
commit 71b0df8ea2
13 changed files with 30 additions and 21 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ class Gateway:
)
self._handler = handler
# Tried before the handler: if an inbound message is an Inbox reply (carries an
# [ocw:<id>] token), it resolves the item and is consumed — not routed as a new turn.
# [ow:<id>] token), it resolves the item and is consumed — not routed as a new turn.
self._reply_resolver = reply_resolver
# A button click on an interactive prompt (resolves an Inbox item by id).
self._interaction_handler = interaction_handler
+1 -1
View File
@@ -329,7 +329,7 @@ class SlackRelayAdapter(BasePlatformAdapter):
return
channel = mapped.source.chat_id # bare channel id before qualification
# Resolve friendly names with THIS workspace's bot token (cached per team),
# mirroring the Socket-Mode adapter — so cards read "@ocw"/"Rohit"/"#ocw-test"
# mirroring the Socket-Mode adapter — so cards read "@OpenWorker"/"Rohit"/"#ocw-test"
# not raw U…/C… ids. Best-effort: ids fall through on failure.
if not mapped.source.user_name:
mapped.source.user_name = await self._display_name(
+1 -1
View File
@@ -82,7 +82,7 @@ def _send_slack(
return SendResult(True, message_id=data.get("ts"))
err = data.get("error") or "slack send failed"
if err == "not_in_channel":
err = "not_in_channel — invite @ocw to the channel in Slack, then retry"
err = "not_in_channel — invite @OpenWorker to the channel in Slack, then retry"
return SendResult(False, error=err)
+2 -2
View File
@@ -10,7 +10,7 @@ Slack API notes: `users.list` is Tier-2 (~20 req/min) and Slack's own guidance
is to cache it — one paginated sweep per workspace per TTL, filtered locally.
Private channels only appear where the bot is a MEMBER (API constraint — the
GUI words it honestly); public channels carry `is_member` so the picker can
hint "invite @ocw in Slack" instead of silently failing to listen.
hint "invite @OpenWorker in Slack" instead of silently failing to listen.
"""
from __future__ import annotations
@@ -157,7 +157,7 @@ def list_channels(
refresh: bool = False,
) -> dict[str, Any]:
"""Channels the token can see: all public ones, private only where the bot
is a member. `is_member` lets the GUI hint "invite @ocw" for the rest."""
is a member. `is_member` lets the GUI hint "invite @OpenWorker" for the rest."""
token = _bot_token(secrets, team_id)
if not token:
return {"ok": False, "error": "workspace not connected"}
+1 -1
View File
@@ -106,7 +106,7 @@ def _resolve_slack_channel(
chat_id = str(c["id"]) if team == "default" else f"{team}/{c['id']}"
if not c.get("is_member"):
return None, (
f"found #{query}, but the bot isn't a member — invite @ocw to #{query} "
f"found #{query}, but the bot isn't a member — invite @OpenWorker to #{query} "
"in Slack, then retry"
)
return chat_id, None
+6 -5
View File
@@ -19,9 +19,10 @@ from pathlib import Path
from typing import Callable, Optional
DEFAULT_INBOX = "default"
_ID_TOKEN = re.compile(
r"\[ocw:([0-9a-f]{6,})\]"
) # embeds the item id in a delivered message
# Embeds the item id in a delivered message. Emitted as [ow:…] since the bot's rebrand
# to OpenWorker (2026-07-22); the legacy [ocw:…] spelling stays parseable so replies to
# messages sent before the rename still resolve.
_ID_TOKEN = re.compile(r"\[o(?:c)?w:([0-9a-f]{6,})\]")
@dataclass
@@ -111,7 +112,7 @@ def deliver(item, binding: InboxBinding, sender: Optional[Sender]) -> bool:
channel message was sent."""
if not binding.channel or sender is None:
return False
text = f"{item.title}\n{item.body}\n[ocw:{item.id}]".strip()
text = f"{item.title}\n{item.body}\n[ow:{item.id}]".strip()
sender(binding.channel, binding.target, text)
return True
@@ -121,7 +122,7 @@ def resolve_from_reply(
) -> Optional[bool]:
"""Correlate an inbound channel reply to its item (by the embedded id) and resolve it.
Looks for the ``[ocw:<id>]`` token and an allow/deny intent; falls back to treating the whole
Looks for the ``[ow:<id>]`` token (or legacy ``[ocw:]``) and an allow/deny intent; falls back to treating the whole
message as a free-text answer. ``resolve(item_id, resolution)`` is the InboxStore.resolve.
Returns the resolve() result, or None if no item id was found."""
m = _ID_TOKEN.search(reply or "")
+1 -1
View File
@@ -2,7 +2,7 @@
When an Inbox item is mirrored to a channel, discrete choices (approve/deny, an ask_user option)
render as **buttons**. The item id rides in each button's value, so a click resolves the exact
item no `[ocw:id]`-in-reply fragility, no thread tracking. Free-text answers aren't offered over
item no `[ow:id]`-in-reply fragility, no thread tracking. Free-text answers aren't offered over
messaging (the user opens the app for those).
Provider-agnostic: a `Button` is `(label, value)`; each adapter renders it natively (Slack Block
+1 -1
View File
@@ -1,6 +1,6 @@
"""Mention-thread → session map for the Slack mention router (UX-DECISIONS §31).
When @ocw is tagged in a channel with no subscribed session, the router spawns a
When @OpenWorker is tagged in a channel with no subscribed session, the router spawns a
coworker session that OWNS that thread and replies into it. This store is the
dedupe map: one durable record per thread, keyed by the thread target string
(``"slack:C0123:1700….000100"``; relay: ``"slack:T…/C…:ts"``) byte-identical to
+1 -1
View File
@@ -10,7 +10,7 @@ busy→steer / idle→background-turn path as self-wake — no live socket requi
gateway's `format_target` / `parse_target`.
NOTE: this is *not* Inbox routing. Routing mirrors an agent's approvals/questions OUT to a
DM/channel (requestreply, `[ocw:id]`-correlated); a subscription brings a channel's messages IN
DM/channel (requestreply, `[ow:id]`-correlated); a subscription brings a channel's messages IN
(broadcast). Keep them on different channels pointing your Inbox at a channel you also subscribe
to conflates the two directions.
"""
+1 -1
View File
@@ -1472,7 +1472,7 @@ export interface SlackMember {
}
// One channel from the workspace roster. Private channels appear only where the
// bot is a member (Slack API constraint); is_member=false → "invite @ocw" hint.
// bot is a member (Slack API constraint); is_member=false → "invite @OpenWorker" hint.
export interface SlackChannelEntry {
id: string;
name: string;
@@ -495,7 +495,7 @@ export function AutomationQuickstart({
/>
</div>
<p className="text-[11px] text-warnInk mt-1">
The bot must be a member of the channel invite @ocw in Slack if it isn't.
The bot must be a member of the channel invite @OpenWorker in Slack if it isn't.
</p>
</>
)}
+12 -4
View File
@@ -46,7 +46,7 @@ def test_deliver_to_channel_embeds_item_id(tmp_path):
assert deliver(item, routing.binding_for("ops"), sender) is True
assert sent["channel"] == "slack" and sent["target"] == "#ops"
assert f"[ocw:{item.id}]" in sent["text"]
assert f"[ow:{item.id}]" in sent["text"] # rebrand: emits [ow:…] since 2026-07-22
def test_in_app_only_binding_delivers_nothing(tmp_path):
@@ -64,8 +64,8 @@ def test_in_app_only_binding_delivers_nothing(tmp_path):
def test_inbound_reply_resolves_correct_item(tmp_path):
store = InboxStore(tmp_path / "inbox.json")
item = store.add_approval("s1", "Deploy?", inbox="ops")
# An inbound "approve [ocw:<id>]" resolves exactly that item.
ok = resolve_from_reply(f"approve [ocw:{item.id}]", store.resolve)
# Current token spelling…
ok = resolve_from_reply(f"approve [ow:{item.id}]", store.resolve)
assert ok is True
assert store.get(item.id).resolution == "allow"
@@ -73,10 +73,18 @@ def test_inbound_reply_resolves_correct_item(tmp_path):
def test_inbound_freetext_answer_to_question(tmp_path):
store = InboxStore(tmp_path / "inbox.json")
q = store.add_question("s1", "Which region?")
res = resolve_from_reply(f"us-east-1 [ocw:{q.id}]", store.resolve)
res = resolve_from_reply(f"us-east-1 [ow:{q.id}]", store.resolve)
assert res is True and store.get(q.id).resolution == "us-east-1"
def test_reply_without_token_is_ignored(tmp_path):
store = InboxStore(tmp_path / "inbox.json")
assert resolve_from_reply("random chatter", store.resolve) is None
def test_inbound_legacy_ocw_token_still_resolves(tmp_path):
"""Replies to messages sent BEFORE the @OpenWorker rename carry [ocw:…] — must keep working."""
store = InboxStore(tmp_path / "inbox.json")
item = store.add_approval("s1", "Deploy?", inbox="ops")
assert resolve_from_reply(f"deny [ocw:{item.id}]", store.resolve) is True
assert store.get(item.id).resolution == "deny"
+1 -1
View File
@@ -173,7 +173,7 @@ def test_unknown_ambiguous_and_not_member_names_error_actionably(tmp_path, monke
},
)
secrets.delete("slack:team:T2")
assert "invite @ocw" in tool("slack:#private-ops", "Hi")["error"]
assert "invite @OpenWorker" in tool("slack:#private-ops", "Hi")["error"]
def test_send_file_resolves_names_too(tmp_path, monkeypatch):