Skip to content

Let the first human replier claim task/cron-started threads#188

Merged
LIU9293 merged 3 commits intomainfrom
feat/thread-owner-claim-first-human-623079
Apr 18, 2026
Merged

Let the first human replier claim task/cron-started threads#188
LIU9293 merged 3 commits intomainfrom
feat/thread-owner-claim-first-human-623079

Conversation

@LIU9293
Copy link
Copy Markdown
Contributor

@LIU9293 LIU9293 commented Apr 18, 2026

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 @ode was dropped as not_mentioned_and_inactive, and even with a mention the first human would fail the isThreadOwner gate 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() recognising task:, cron-job:, and legacy cron: 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 as threadOwnerMessage: true.
  • packages/core/kernel/session-bootstrap.ts and packages/core/kernel/pending-question.ts — replace a synthetic threadOwnerUserId with the first real human user, so ownership is persisted against them from that reply onwards.
  • packages/core/tasks/scheduler.ts and packages/core/cron/scheduler.ts — after a top-level channel post, 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 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 is task:....
  • bun run typecheck clean for the changed packages (pre-existing web-ui errors unrelated).

Compatibility

Existing real-user-owned threads are unaffected: isSyntheticOwner only matches the internal synthetic prefixes, so ownership checks for human-owned threads behave exactly as before.

Kai Liu 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).
@LIU9293 LIU9293 merged commit 34d363f into main Apr 18, 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