agent-relay-runner 0.57.0 → 0.57.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/runner.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, join } from "node:path";
|
|
|
5
5
|
import type { AgentLifecycle, AgentProfile, ContextState, Message, MessageSessionMeta, ProviderCapabilities, SendMessageInput, TaskStatusInput, WorkspaceMetadata } from "agent-relay-sdk";
|
|
6
6
|
import { errMessage, RelayBusClient, RelayHttpClient } from "agent-relay-sdk";
|
|
7
7
|
import { contextStateFromProbeMetrics, readContextProbeState } from "agent-relay-sdk/context-probe";
|
|
8
|
+
import { providerMessageText } from "./adapter";
|
|
8
9
|
import type { ManagedProcess, ProviderAdapter, ProviderConfig, ProviderPermissionDecision, ProviderPermissionDecisionInput, ProviderSessionEvent, ProviderStatusUpdate, RunnerSpawnConfig, SemanticStatus, TerminalAttachSpec } from "./adapter";
|
|
9
10
|
import { messagesWithCachedAttachments } from "./attachment-cache";
|
|
10
11
|
import { resolveProjectName } from "./config";
|
|
@@ -288,6 +289,9 @@ export class AgentRunner {
|
|
|
288
289
|
// from the Claude transcript into chat as discreet session events.
|
|
289
290
|
private reasoningTail?: { timer: ReturnType<typeof setInterval>; seen: Set<string> };
|
|
290
291
|
private scratch?: SessionScratchLayout;
|
|
292
|
+
// #417: for non-claude providers, tracks how many times each spawner obligation has
|
|
293
|
+
// been re-injected so a non-responsive agent can't spin in a re-injection loop.
|
|
294
|
+
private readonly pendingObligationInjections = new Map<number, number>();
|
|
291
295
|
|
|
292
296
|
constructor(private readonly options: RunnerOptions) {
|
|
293
297
|
this.agentId = options.agentId ?? options.runnerId;
|
|
@@ -1170,6 +1174,9 @@ export class AgentRunner {
|
|
|
1170
1174
|
this.compactionMidTurn = false;
|
|
1171
1175
|
this.disarmBusyReconciler();
|
|
1172
1176
|
this.stopReasoningTail();
|
|
1177
|
+
// #417: claude enforces obligations via the stop.sh hook; for every other provider
|
|
1178
|
+
// re-inject any pending spawner obligation so it surfaces the /reply command.
|
|
1179
|
+
if (this.options.provider !== "claude") void this.reInjectPendingObligation();
|
|
1173
1180
|
}
|
|
1174
1181
|
if (status === "busy") {
|
|
1175
1182
|
this.claims.clearTerminalStatus();
|
|
@@ -1202,6 +1209,49 @@ export class AgentRunner {
|
|
|
1202
1209
|
this.publishStatus();
|
|
1203
1210
|
}
|
|
1204
1211
|
|
|
1212
|
+
// #417: when a non-claude provider finishes a turn with a pending spawner obligation
|
|
1213
|
+
// still unresolved, re-inject the obligation as a synthetic message so providerMessageText
|
|
1214
|
+
// surfaces the specific `agent-relay /reply <id>` command. Claude handles this via the
|
|
1215
|
+
// stop.sh hook; every other provider needs this in-process nudge.
|
|
1216
|
+
private async reInjectPendingObligation(): Promise<void> {
|
|
1217
|
+
if (!this.process) return;
|
|
1218
|
+
// Filter to spawner obligations only (from ≠ "user") — user obligations go through
|
|
1219
|
+
// the existing codex prompt-capture path (publishProviderSessionEvent).
|
|
1220
|
+
const obligations = this.obligationCache.get().filter((o) => o.from !== "user");
|
|
1221
|
+
if (!obligations.length) return;
|
|
1222
|
+
const MAX_REINJECTIONS = 3;
|
|
1223
|
+
for (const obligation of obligations) {
|
|
1224
|
+
const count = this.pendingObligationInjections.get(obligation.messageId) ?? 0;
|
|
1225
|
+
if (count >= MAX_REINJECTIONS) continue;
|
|
1226
|
+
this.pendingObligationInjections.set(obligation.messageId, count + 1);
|
|
1227
|
+
this.sessionLog(`re-injecting pending reply obligation #${obligation.messageId} from ${obligation.from} (attempt ${count + 1}/${MAX_REINJECTIONS})`);
|
|
1228
|
+
const replayMessage = {
|
|
1229
|
+
id: obligation.messageId,
|
|
1230
|
+
from: obligation.from,
|
|
1231
|
+
to: this.agentId,
|
|
1232
|
+
kind: obligation.kind,
|
|
1233
|
+
...(obligation.subject ? { subject: obligation.subject } : {}),
|
|
1234
|
+
body: obligation.bodyPreview,
|
|
1235
|
+
payload: {},
|
|
1236
|
+
readBy: [] as string[],
|
|
1237
|
+
createdAt: obligation.createdAt,
|
|
1238
|
+
};
|
|
1239
|
+
// Suppress the userMessage echo that providers (e.g. Codex) reflect back when
|
|
1240
|
+
// a turn is started with relay-formatted content — the echo body is exactly what
|
|
1241
|
+
// providerMessageText produces, which starts with "[relay message #" and is caught
|
|
1242
|
+
// by the RELAY_INJECTION_MARKERS prefix check, but record it explicitly as
|
|
1243
|
+
// defense-in-depth for adapters that might echo only the unformatted body text.
|
|
1244
|
+
this.recordInjectedPrompt(providerMessageText([replayMessage]));
|
|
1245
|
+
await this.options.adapter.deliver(this.process, [replayMessage]);
|
|
1246
|
+
// Kick off a cache refresh so a cleared obligation (agent replied) is detected
|
|
1247
|
+
// before the next idle fires — without this, the stale snapshot re-injects up to
|
|
1248
|
+
// MAX_REINJECTIONS times even after the agent has already /reply-ed.
|
|
1249
|
+
this.obligationCache.markDirty();
|
|
1250
|
+
// One obligation at a time — let the provider reply before nudging again.
|
|
1251
|
+
break;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1205
1255
|
// Session-mirror lane: capture the assistant turn from the Claude transcript and
|
|
1206
1256
|
// post it as a "session" message so it shows in the dashboard chat with zero
|
|
1207
1257
|
// agent tokens. Capture is UNCONDITIONAL — it no longer depends on a triggering
|