Keep IM tool approvals in chat and add WhatsApp default model.

Claim in-flight IM turns before ownership flips active so approvals no longer stick only in DSH Web, auto-recover stale session models once, and let WhatsApp bots pick a shared default model for new sessions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-15 20:02:45 +08:00
parent 2723e94541
commit 74c58bdcde
21 changed files with 1388 additions and 395 deletions

View file

@ -399,6 +399,11 @@ function consumeInteractionOwnership(ownership, entries) {
* Decide whether the current Host has a live dsh-im interaction watcher for
* this Session. The modern Harness adapter uses the same ownership registry
* as HarnessClient so it never steals a browser-owned question or approval.
*
* Claim while an IM ask is still open — not only after `user/message` has
* flipped `active`. Waiting for `active` races tool approvals that fire before
* ownership consumption catches up, which silently delegates to DSH Web and
* leaves WhatsApp/etc. hung with no visible prompt.
*/
export function hasActiveHarnessInteractionOwner(scope, sessionId, entries = []) {
if (!scope || !['object', 'function', 'string'].includes(typeof scope)
@ -407,9 +412,9 @@ export function hasActiveHarnessInteractionOwner(scope, sessionId, entries = [])
if (!owners || owners.size === 0) return false;
for (const ownership of owners) consumeInteractionOwnership(ownership, entries);
return [...owners].some((ownership) => (
ownership.active
&& !ownership.completed
!ownership.completed
&& typeof ownership.reconnect === 'function'
&& (ownership.active || ownership.started || ownership.promptRpcId)
));
}
@ -1248,6 +1253,18 @@ export class HarnessClient {
.sort((left, right) => left.order - right.order);
if (active.length > 0) return { ownership: active[0], recovered: false };
// A live IM ask owns in-turn approvals/questions even before user/message
// flips `active`. Marking these recovered would auto-reject without ever
// showing the prompt in WhatsApp (or other text channels).
const live = owners
.filter((ownership) => (
!ownership.completed
&& typeof ownership.reconnect === 'function'
&& Boolean(ownership.promptRpcId)
))
.sort((left, right) => left.order - right.order)[0] ?? null;
if (live) return { ownership: live, recovered: false };
// A newly attached IM conversation may encounter a question left by
// an earlier runtime before its queued prompt starts. Let the oldest such
// ask adopt that replay so the Session can recover instead of deadlocking.