Let the first human replier claim task/cron-started threads#188
Merged
Let the first human replier claim task/cron-started threads#188
Conversation
added 3 commits
April 18, 2026 14:29
When Ode posts a top-level channel message from a one-time Task or Cron
run, the resulting Slack/Discord/Lark thread used to belong to a
"synthetic" owner (e.g. `task:{id}`) that never matches any real user.
Any human who replied without an explicit @-mention was dropped as
`not_mentioned_and_inactive`, and even with a mention the first human
would fail the `isThreadOwner` gate in pending-question flows.
Changes:
- New shared helper `packages/ims/shared/synthetic-owner.ts` recognising
the `task:`, `cron-job:`, and legacy `cron:` owner prefixes.
- Slack/Discord/Lark routing now treat a synthetic owner as "claimable"
— any real user replying is reported as `threadOwnerMessage: true`.
- `prepareRuntimeSession` and `handlePendingQuestionReply` overwrite
a synthetic `threadOwnerUserId` with the first real human user, so
ownership is persisted against them from that reply onwards.
- Task and Cron schedulers capture the real platform thread id returned
by `sendChannelMessage` and seed a mirrored session under
`(channelId, realThreadId)` pointing at the same agent sessionId.
This makes the thread "active" for inbound routing so subsequent
replies are processed without forcing the user to @-mention the bot.
Tests: new unit tests for `isSyntheticOwner` and a pending-question
case where a synthetic owner is claimed by a real user. All 267 tests
pass (was 260).
…im-first-human-623079
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When Ode posts a top-level channel message from a one-time Task or Cron run, the resulting Slack / Discord / Lark thread belonged to a synthetic owner (e.g.
task:{id}) that never matches any real user. Any human who replied without an explicit@odewas dropped asnot_mentioned_and_inactive, and even with a mention the first human would fail theisThreadOwnergate in pending-question flows. This PR lets the first real human responder claim such a thread.Changes
packages/ims/shared/synthetic-owner.ts(new) —isSyntheticOwner()recognisingtask:,cron-job:, and legacycron:owner prefixes.packages/ims/slack/client.ts,packages/ims/discord/client.ts,packages/ims/lark/client.ts— inbound routing now treats a synthetic owner as claimable: any real user replying is reported asthreadOwnerMessage: true.packages/core/kernel/session-bootstrap.tsandpackages/core/kernel/pending-question.ts— replace a syntheticthreadOwnerUserIdwith the first real human user, so ownership is persisted against them from that reply onwards.packages/core/tasks/scheduler.tsandpackages/core/cron/scheduler.ts— after a top-level channel post, capture the real platform thread id returned bysendChannelMessageand seed a mirrored session under(channelId, realThreadId)pointing at the same agent sessionId. This makes the threadactivefor inbound routing so subsequent replies are processed without the user having to@the bot.Testing
bun test→ 267 pass, 0 fail (was 260). New tests:packages/ims/shared/synthetic-owner.test.ts— edge cases (prefix vs. substring, null/empty).packages/core/test/pending-question.test.ts— a real user claims a pending question whose owner istask:....bun run typecheckclean for the changed packages (pre-existingweb-uierrors unrelated).Compatibility
Existing real-user-owned threads are unaffected:
isSyntheticOwneronly matches the internal synthetic prefixes, so ownership checks for human-owned threads behave exactly as before.