agent-relay-runner 0.10.25 → 0.10.26

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-runner",
3
- "version": "0.10.25",
3
+ "version": "0.10.26",
4
4
  "description": "Unified provider lifecycle runner for Agent Relay",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-relay-runner",
3
3
  "description": "Thin Agent Relay runner bridge for Claude Code",
4
- "version": "0.10.25",
4
+ "version": "0.10.26",
5
5
  "agentRelayContracts": {
6
6
  "providerPluginProtocol": 1
7
7
  }
package/src/adapter.ts CHANGED
@@ -31,6 +31,7 @@ export interface ProviderConfig {
31
31
  defaultCapabilities: string[];
32
32
  defaultApprovalMode: string;
33
33
  defaultTags: string[];
34
+ chatCaptureMode: "final" | "full";
34
35
  headless: {
35
36
  tmuxPrefix: string;
36
37
  shutdownTimeoutMs: number;
@@ -97,6 +97,37 @@ export function extractLastAssistantTurn(jsonl: string): string {
97
97
  return collected.join("\n\n").trim();
98
98
  }
99
99
 
100
+ /**
101
+ * Returns only the text from the LAST assistant entry in the current turn.
102
+ * Unlike extractLastAssistantTurn which collects all intermediate text between
103
+ * tool calls, this returns just the final response — what the user sees as the
104
+ * concluding message.
105
+ */
106
+ export function extractFinalAssistantMessage(jsonl: string): string {
107
+ const lines = jsonl.split("\n");
108
+ let lastText = "";
109
+ let pastLastUserPrompt = false;
110
+ for (const line of lines) {
111
+ const trimmed = line.trim();
112
+ if (!trimmed) continue;
113
+ let entry: TranscriptEntry;
114
+ try {
115
+ entry = JSON.parse(trimmed) as TranscriptEntry;
116
+ } catch {
117
+ continue;
118
+ }
119
+ if (isRealUserPrompt(entry)) {
120
+ pastLastUserPrompt = true;
121
+ lastText = "";
122
+ continue;
123
+ }
124
+ if (!pastLastUserPrompt) continue;
125
+ const text = assistantText(entry);
126
+ if (text) lastText = text;
127
+ }
128
+ return lastText.trim();
129
+ }
130
+
100
131
  /**
101
132
  * Extract text from the `last_assistant_message` field in the Stop hook
102
133
  * payload. This is the content of the final assistant message — either a plain
package/src/config.ts CHANGED
@@ -33,6 +33,7 @@ export function defaultProviderConfig(provider: string): ProviderConfig {
33
33
  defaultCapabilities: ["chat", "code", "review"],
34
34
  defaultApprovalMode: "guarded",
35
35
  defaultTags: [],
36
+ chatCaptureMode: "final",
36
37
  headless: {
37
38
  tmuxPrefix: `${provider}-relay`,
38
39
  shutdownTimeoutMs: 10_000,
@@ -63,6 +64,7 @@ export function loadProviderConfig(provider: string, home = agentRelayHome()): L
63
64
  defaultCapabilities: stringArray(raw.defaultCapabilities) ?? defaults.defaultCapabilities,
64
65
  defaultApprovalMode: stringValue(raw.defaultApprovalMode) ?? defaults.defaultApprovalMode,
65
66
  defaultTags: stringArray(raw.defaultTags) ?? defaults.defaultTags,
67
+ chatCaptureMode: enumValue(raw.chatCaptureMode, ["final", "full"]) ?? defaults.chatCaptureMode,
66
68
  headless: {
67
69
  tmuxPrefix: stringValue(recordValue(raw.headless).tmuxPrefix) ?? defaults.headless.tmuxPrefix,
68
70
  shutdownTimeoutMs: positiveInteger(recordValue(raw.headless).shutdownTimeoutMs) ?? defaults.headless.shutdownTimeoutMs,
@@ -88,6 +90,7 @@ export function providerConfigPublic(config: LoadedProviderConfig): Record<strin
88
90
  defaultCapabilities: config.defaultCapabilities,
89
91
  defaultApprovalMode: config.defaultApprovalMode,
90
92
  defaultTags: config.defaultTags,
93
+ chatCaptureMode: config.chatCaptureMode,
91
94
  headless: config.headless,
92
95
  };
93
96
  }
@@ -120,6 +123,10 @@ function positiveInteger(value: unknown): number | undefined {
120
123
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
121
124
  }
122
125
 
126
+ function enumValue<T extends string>(value: unknown, allowed: T[]): T | undefined {
127
+ return typeof value === "string" && allowed.includes(value as T) ? (value as T) : undefined;
128
+ }
129
+
123
130
  function stringArray(value: unknown): string[] | undefined {
124
131
  return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : undefined;
125
132
  }
package/src/runner.ts CHANGED
@@ -9,7 +9,7 @@ import type { ManagedProcess, ProviderAdapter, ProviderConfig, ProviderPermissio
9
9
  import { messagesWithCachedAttachments } from "./attachment-cache";
10
10
  import { ClaimTracker } from "./claim-tracker";
11
11
  import { startControlServer, type ControlServer } from "./control-server";
12
- import { extractLastAssistantTurn, extractHookAssistantMessage, transcriptLooksComplete } from "./adapters/claude-transcript";
12
+ import { extractLastAssistantTurn, extractFinalAssistantMessage, extractHookAssistantMessage, transcriptLooksComplete } from "./adapters/claude-transcript";
13
13
  import { agentProfileProjectionReport } from "./profile-projection";
14
14
  import { profileUsesHostProviderGlobals } from "./profile-home";
15
15
  import { runtimeMetadata } from "./version";
@@ -702,7 +702,8 @@ export class AgentRunner {
702
702
  try { jsonl = await readFile(input.transcriptPath, "utf8"); } catch { return; }
703
703
  }
704
704
  if (!transcriptLooksComplete(jsonl)) continue;
705
- body = extractLastAssistantTurn(jsonl);
705
+ const extract = this.options.providerConfig.chatCaptureMode === "full" ? extractLastAssistantTurn : extractFinalAssistantMessage;
706
+ body = extract(jsonl);
706
707
  if (body) break;
707
708
  }
708
709
  // Fallback: use last_assistant_message from the Stop hook payload directly.