Merge remote-tracking branch 'upstream/main'

This commit is contained in:
C3H3-AI 2026-08-18 19:46:22 +08:00
commit 55ed947d9a
22 changed files with 2976 additions and 170 deletions

View file

@ -154,7 +154,36 @@ function consumeInteractionOwnership(ownership, entries) {
if (event.type === 'turn/end' && event.data?.turn === ownership.turn) {
ownership.active = false;
ownership.completed = true;
continue;
}
let toolCall = null;
if (event.type === 'tool/call'
&& ownership.active
&& event.data?.turn === ownership.turn
&& typeof event.data?.callId === 'string'
&& event.data.callId) {
toolCall = {
callId: event.data.callId,
name: event.data?.name,
arguments: event.data?.arguments,
};
} else if (event.type === 'tool/code-dispatch-start'
&& ownership.active
&& typeof event.data?.subCallId === 'string'
&& event.data.subCallId) {
let argumentsText;
try {
argumentsText = JSON.stringify(event.data?.arguments);
} catch {
argumentsText = undefined;
}
toolCall = {
callId: event.data.subCallId,
name: event.data?.name,
arguments: argumentsText,
};
}
if (toolCall) ownership.toolCalls.set(toolCall.callId, Object.freeze(toolCall));
}
}
@ -558,13 +587,12 @@ export class HarnessClient {
.sort((left, right) => left.order - right.order);
if (active.length > 0) return { ownership: active[0], recovered: false };
// Approval recovery must remain fail-closed until #5 can prove the actor
// and conversation that originally requested the decision.
if (kind !== 'question') return null;
// 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.
// Approval adopters receive recovered=true and must reject it without ever
// presenting it as approvable; the original actor/route cannot be proven
// after a runtime restart.
const ownership = owners
.filter((ownership) => !ownership.started && !ownership.completed)
.sort((left, right) => left.order - right.order)[0] ?? null;
@ -614,6 +642,7 @@ export class HarnessClient {
lastSeq: baselineSeq,
reconnect: null,
order: -1,
toolCalls: new Map(),
}
: null;
let interactionTask = null;
@ -774,6 +803,9 @@ export class HarnessClient {
if (claim?.ownership !== ownership) return;
this.#interactionClaims.set(claimKey, claim);
}
const toolCall = kind === 'approval' && ownership && typeof payload.callId === 'string'
? this.#interactionClaims.get(claimKey)?.ownership.toolCalls.get(payload.callId)
: undefined;
dispatch(onInteraction, Object.freeze({
kind,
interactionId,
@ -783,6 +815,7 @@ export class HarnessClient {
recovered: ownership
? this.#interactionClaims.get(claimKey)?.recovered === true
: false,
...(toolCall ? { toolCall } : {}),
reconnect: close,
respond: (result, options = {}) => this.respondInteraction(
envelope.rpcId,