agent-relay-runner 0.63.1 → 0.64.0
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
|
@@ -202,6 +202,40 @@ export function extractFinalAssistantMessage(jsonl: string): string {
|
|
|
202
202
|
return lastText.trim();
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Like extractFinalAssistantMessage but skips the first `afterEntry` valid JSON
|
|
207
|
+
* entries. Used when a PreToolUse pre-flush already emitted the text before a
|
|
208
|
+
* blocking control, so final-mode Stop capture only sees later assistant text.
|
|
209
|
+
*/
|
|
210
|
+
export function extractFinalAssistantMessageAfterEntry(jsonl: string, afterEntry: number): string {
|
|
211
|
+
const lines = jsonl.split("\n");
|
|
212
|
+
let lastText = "";
|
|
213
|
+
let pastLastUserPrompt = afterEntry > 0;
|
|
214
|
+
let entryIndex = 0;
|
|
215
|
+
for (const line of lines) {
|
|
216
|
+
const trimmed = line.trim();
|
|
217
|
+
if (!trimmed) continue;
|
|
218
|
+
let entry: TranscriptEntry;
|
|
219
|
+
try {
|
|
220
|
+
entry = JSON.parse(trimmed) as TranscriptEntry;
|
|
221
|
+
} catch {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
entryIndex++;
|
|
225
|
+
if (isSidechainEntry(entry)) continue;
|
|
226
|
+
if (entryIndex <= afterEntry) continue;
|
|
227
|
+
if (isRealUserPrompt(entry)) {
|
|
228
|
+
pastLastUserPrompt = true;
|
|
229
|
+
lastText = "";
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (!pastLastUserPrompt) continue;
|
|
233
|
+
const text = assistantText(entry);
|
|
234
|
+
if (text) lastText = text;
|
|
235
|
+
}
|
|
236
|
+
return lastText.trim();
|
|
237
|
+
}
|
|
238
|
+
|
|
205
239
|
/**
|
|
206
240
|
* Extract text from the `last_assistant_message` field in the Stop hook
|
|
207
241
|
* payload. This is the content of the final assistant message — either a plain
|
package/src/runner-core.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { ClaimTracker } from "./claim-tracker";
|
|
|
12
12
|
import { startControlServer, type ControlServer } from "./control-server";
|
|
13
13
|
import { ReplyObligationCache, obligationRequiresExplicitReply } from "./reply-obligation-cache";
|
|
14
14
|
import { Outbox, type OutboxRecord } from "./outbox";
|
|
15
|
-
import { extractLastAssistantTurn, extractLastAssistantTurnAfterEntry, countTranscriptEntries, extractFinalAssistantMessage, extractHookAssistantMessage, extractLatestTurnSteps, stepDedupKeys, transcriptLooksComplete } from "./adapters/claude-transcript";
|
|
15
|
+
import { extractLastAssistantTurn, extractLastAssistantTurnAfterEntry, countTranscriptEntries, extractFinalAssistantMessage, extractFinalAssistantMessageAfterEntry, extractHookAssistantMessage, extractLatestTurnSteps, stepDedupKeys, transcriptLooksComplete } from "./adapters/claude-transcript";
|
|
16
16
|
import { profileUsesHostProviderGlobals } from "./profile-home";
|
|
17
17
|
import { RELAY_MCP_TOKEN_ENV, relayMcpEndpoint } from "./relay-mcp";
|
|
18
18
|
import { RelayMcpProxy } from "./relay-mcp-proxy";
|
|
@@ -1306,10 +1306,9 @@ export class AgentRunner {
|
|
|
1306
1306
|
// no relay message) are mirrored too. A reply obligation, when present, is still
|
|
1307
1307
|
// used as replyTo so the Stop hook stops nagging the agent to /reply.
|
|
1308
1308
|
//
|
|
1309
|
-
// isPreFlush: true (#435) — mid-turn
|
|
1310
|
-
//
|
|
1311
|
-
//
|
|
1312
|
-
// entry-count cursor so the eventual Stop-hook call (full mode) skips it.
|
|
1309
|
+
// isPreFlush: true (#435/#499) — mid-turn PreToolUse boundary. Drain the live
|
|
1310
|
+
// tail and force-flush session messages before showing the blocking control;
|
|
1311
|
+
// the entry cursor lets Stop capture skip already-emitted text.
|
|
1313
1312
|
private async publishSessionTurn(input: { transcriptPath: string; lastAssistantMessage?: unknown; isPreFlush?: boolean }): Promise<void> {
|
|
1314
1313
|
if (input.transcriptPath) this.lastTranscriptPath = input.transcriptPath;
|
|
1315
1314
|
|
|
@@ -1321,15 +1320,12 @@ export class AgentRunner {
|
|
|
1321
1320
|
const body = extractLastAssistantTurnAfterEntry(jsonl, this.narrativeFlushEntryCount);
|
|
1322
1321
|
await this.drainReasoningTail();
|
|
1323
1322
|
this.narrativeFlushEntryCount = countTranscriptEntries(jsonl);
|
|
1324
|
-
if (
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
body,
|
|
1331
|
-
session: { type: "response", origin: "provider", ...(turnId ? { turnId } : {}) },
|
|
1332
|
-
});
|
|
1323
|
+
if (body && !this.preFlushBodyAlreadyMirroredByReasoningTail(jsonl, body)) {
|
|
1324
|
+
const turnId = this.currentTurnId;
|
|
1325
|
+
this.sessionLog(`pre-flush narrative for turn ${turnId ?? "?"} (${body.length} chars)`);
|
|
1326
|
+
await this.publishSessionEvent({ from: this.agentId, to: "user", body, session: { type: "response", origin: "provider", ...(turnId ? { turnId } : {}) } });
|
|
1327
|
+
}
|
|
1328
|
+
await this.sessionOutbox.flush(2_000).then((ok) => { if (!ok) this.sessionLog(`pre-flush session outbox incomplete before blocking control (${this.sessionOutbox.pendingCount()} pending)`); }, (error) => this.sessionLog(`pre-flush session outbox failed before blocking control: ${errMessage(error)}`));
|
|
1333
1329
|
return;
|
|
1334
1330
|
}
|
|
1335
1331
|
|
|
@@ -1368,11 +1364,10 @@ export class AgentRunner {
|
|
|
1368
1364
|
try { jsonl = await readFile(input.transcriptPath, "utf8"); } catch { break; }
|
|
1369
1365
|
}
|
|
1370
1366
|
if (!transcriptLooksComplete(jsonl)) continue;
|
|
1371
|
-
//
|
|
1372
|
-
// collect text from entries after that mark to avoid double-emit (#435).
|
|
1367
|
+
// If a pre-flush already emitted entries 1..flushCount, resume after that mark to avoid double-emit (#435/#499).
|
|
1373
1368
|
const extract = this.options.providerConfig.chatCaptureMode === "full"
|
|
1374
1369
|
? (j: string) => (flushCount > 0 ? extractLastAssistantTurnAfterEntry(j, flushCount) : extractLastAssistantTurn(j))
|
|
1375
|
-
: extractFinalAssistantMessage;
|
|
1370
|
+
: (j: string) => (flushCount > 0 ? extractFinalAssistantMessageAfterEntry(j, flushCount) : extractFinalAssistantMessage(j));
|
|
1376
1371
|
body = extract(jsonl);
|
|
1377
1372
|
if (body) break;
|
|
1378
1373
|
}
|