agent-relay-runner 0.129.11 → 0.129.13
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 +3 -3
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/monitors/relay-monitor.provisioned.mjs +8 -8
- package/src/adapter.ts +113 -11
- package/src/adapters/claude-delivery.ts +1 -7
- package/src/adapters/claude-exit.ts +289 -0
- package/src/adapters/claude-prompt-gates.ts +59 -79
- package/src/adapters/claude-session-capture.ts +13 -2
- package/src/adapters/claude-session-probe.ts +98 -7
- package/src/adapters/claude-shutdown.ts +149 -0
- package/src/adapters/claude-status-detectors.ts +201 -69
- package/src/adapters/claude-tmux.ts +94 -5
- package/src/adapters/claude-transcript-tail.ts +22 -3
- package/src/adapters/claude-transcript.ts +95 -1
- package/src/adapters/claude.ts +179 -76
- package/src/adapters/codex.ts +46 -8
- package/src/busy-reconciler.ts +67 -29
- package/src/config.ts +16 -0
- package/src/launch-assembly.ts +251 -82
- package/src/native-memory-lever.ts +308 -0
- package/src/outbox.ts +22 -4
- package/src/profile-projection.ts +45 -1
- package/src/provider-input-queue.ts +100 -0
- package/src/rate-limit.ts +106 -36
- package/src/relay-injection-events.ts +24 -0
- package/src/relay-instructions.ts +9 -0
- package/src/relay-mcp-proxy.ts +79 -8
- package/src/relay-mcp.ts +217 -8
- package/src/runner-core.ts +323 -47
- package/src/runner-helpers.ts +10 -0
- package/src/transcript-context.ts +83 -0
- package/src/turn-reconcile-event.ts +48 -0
package/src/runner-helpers.ts
CHANGED
|
@@ -299,6 +299,16 @@ export function shouldLogDeliveryFailure(error: unknown): boolean {
|
|
|
299
299
|
&& message !== CODEX_TURN_IN_PROGRESS_MESSAGE;
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
/** The same rejection, seen from the COMMAND side (#1565 F1): a serialized boundary payload that
|
|
303
|
+
* met the turn the payload before it started is deferred until that turn ends, not failed. */
|
|
304
|
+
export function isTurnInProgressDeliveryError(error: unknown): boolean {
|
|
305
|
+
return errMessage(error) === CODEX_TURN_IN_PROGRESS_MESSAGE;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Command types that put a payload into the provider's single input channel, and therefore take a
|
|
309
|
+
* `ProviderInputQueue` ticket at dispatch so two of them can never interleave (#1565 F1). */
|
|
310
|
+
export const PROVIDER_INPUT_COMMAND_TYPES: ReadonlySet<string> = new Set(["agent.injectContext", "prompt.inject"]);
|
|
311
|
+
|
|
302
312
|
export function runtimeTokenRenewDelayMs(expiresAtSeconds: number, nowMs = Date.now()): number | undefined {
|
|
303
313
|
const expiresAtMs = expiresAtSeconds * 1000;
|
|
304
314
|
const ttlMs = expiresAtMs - nowMs;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import type { ContextState } from "agent-relay-sdk";
|
|
3
|
+
import type { ProviderContextUsage } from "./adapter";
|
|
4
|
+
|
|
5
|
+
// #1512: Claude reports context only through its out-of-band status-line probe,
|
|
6
|
+
// which freezes after an in-process compact/clear/native-resume (no new process,
|
|
7
|
+
// so the probe file stops advancing while the agent keeps taking turns). The
|
|
8
|
+
// transcript — handed to the runner every turn by the Stop hook — carries the
|
|
9
|
+
// same per-turn token usage, so the runner derives fresh context from it here as
|
|
10
|
+
// a probe-independent fallback that keeps context.lastUpdatedAt advancing across
|
|
11
|
+
// those in-process generation bumps. Kept out of runner-core to hold that giant's
|
|
12
|
+
// line budget (epic #291); runner-core just threads its per-session cache through.
|
|
13
|
+
|
|
14
|
+
export interface TranscriptContextCache {
|
|
15
|
+
path: string;
|
|
16
|
+
mtimeMs: number;
|
|
17
|
+
/** Byte size alongside mtime: a same-second rewrite (coarse mtime granularity) still changes length. */
|
|
18
|
+
size: number;
|
|
19
|
+
usage: ProviderContextUsage | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TranscriptContextInput {
|
|
23
|
+
/** Latest transcript path the Stop hook reported this session. */
|
|
24
|
+
transcriptPath: string | undefined;
|
|
25
|
+
/** The transcript-backed adapter's per-turn usage parser (absent for others). */
|
|
26
|
+
derive: ((jsonl: string) => ProviderContextUsage | undefined) | undefined;
|
|
27
|
+
/** Context-window size — stable for the session, so reuse what the probe saw. */
|
|
28
|
+
tokensMax: number | undefined;
|
|
29
|
+
/** Prior parse, reused when the transcript's (mtime, size) is unchanged (large files). */
|
|
30
|
+
cache: TranscriptContextCache | undefined;
|
|
31
|
+
now: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Returns the derived context (or undefined when it can't be computed) and the
|
|
35
|
+
// cache to carry forward. The context-window size is stable for a session, so we
|
|
36
|
+
// reuse the tokensMax the status-line probe reported at least once rather than
|
|
37
|
+
// re-deriving it from a model catalog the runner doesn't hold; the transcript
|
|
38
|
+
// supplies the per-turn tokensUsed. The parse is cached by transcript (mtime, size)
|
|
39
|
+
// so a large log isn't re-read every 30s heartbeat while nothing new has been
|
|
40
|
+
// written. Size is paired with mtime because an in-process compact/clear/resume can
|
|
41
|
+
// rewrite the transcript within the same coarse mtime tick (1s on some filesystems),
|
|
42
|
+
// which mtime alone would miss — leaving stale context; the length still changes.
|
|
43
|
+
export function transcriptDerivedContext(input: TranscriptContextInput): {
|
|
44
|
+
context: ContextState | undefined;
|
|
45
|
+
cache: TranscriptContextCache | undefined;
|
|
46
|
+
} {
|
|
47
|
+
const { transcriptPath, derive, tokensMax, cache, now } = input;
|
|
48
|
+
if (!transcriptPath || !derive) return { context: undefined, cache };
|
|
49
|
+
if (typeof tokensMax !== "number" || !Number.isFinite(tokensMax) || tokensMax <= 0) return { context: undefined, cache };
|
|
50
|
+
|
|
51
|
+
let nextCache = cache;
|
|
52
|
+
let usage: ProviderContextUsage | undefined;
|
|
53
|
+
try {
|
|
54
|
+
const stat = statSync(transcriptPath);
|
|
55
|
+
const mtimeMs = stat.mtimeMs;
|
|
56
|
+
const size = stat.size;
|
|
57
|
+
if (cache?.path === transcriptPath && cache.mtimeMs === mtimeMs && cache.size === size) {
|
|
58
|
+
usage = cache.usage;
|
|
59
|
+
} else {
|
|
60
|
+
usage = derive(readFileSync(transcriptPath, "utf8"));
|
|
61
|
+
nextCache = { path: transcriptPath, mtimeMs, size, usage };
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
return { context: undefined, cache };
|
|
65
|
+
}
|
|
66
|
+
if (!usage) return { context: undefined, cache: nextCache };
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
context: {
|
|
70
|
+
utilization: Math.max(0, Math.min(1, usage.tokensUsed / tokensMax)),
|
|
71
|
+
tokensUsed: usage.tokensUsed,
|
|
72
|
+
tokensMax,
|
|
73
|
+
lifecycleState: "working",
|
|
74
|
+
warmTopics: [],
|
|
75
|
+
activeMemories: [],
|
|
76
|
+
tasksSinceCompact: 0,
|
|
77
|
+
lastUpdatedAt: usage.occurredAt ?? now,
|
|
78
|
+
source: "api",
|
|
79
|
+
confidence: "reported",
|
|
80
|
+
},
|
|
81
|
+
cache: nextCache,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { RECONCILED_TURN_TIMELINE_STATUS } from "agent-relay-sdk";
|
|
2
|
+
|
|
3
|
+
// Same shape the runner publishes (structurally identical to the local declarations in
|
|
4
|
+
// runner-core.ts / native-self-resume.ts, which is how the other timeline producers do it).
|
|
5
|
+
interface RunnerTimelineEvent {
|
|
6
|
+
status: string;
|
|
7
|
+
id?: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
title?: string;
|
|
10
|
+
body?: string;
|
|
11
|
+
icon?: string;
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* #1621 — the announcement for a provider-turn claim the busy backstop force-cleared.
|
|
17
|
+
*
|
|
18
|
+
* Reconciliation is a real state change, not bookkeeping repair: the turn the coordinator is
|
|
19
|
+
* waiting on is over and produced no reply, and `busy` is exactly the signal it used to decide
|
|
20
|
+
* NOT to intervene. So the clear is EMITTED (the relay turns this event into an activity row plus
|
|
21
|
+
* an `agent.turn_interrupted` notification for the spawn parent) instead of silently correcting
|
|
22
|
+
* the card — a quietly-fixed card still leaves the coordinator's plan stalled on a turn that
|
|
23
|
+
* ended. Pure builder so the payload the parent's notice is derived from is unit-testable without
|
|
24
|
+
* standing up an AgentRunner.
|
|
25
|
+
*/
|
|
26
|
+
export function reconciledTurnTimelineEvent(input: {
|
|
27
|
+
turnId: string;
|
|
28
|
+
reason: string;
|
|
29
|
+
startedAt: number;
|
|
30
|
+
now?: number;
|
|
31
|
+
}): RunnerTimelineEvent {
|
|
32
|
+
const now = input.now ?? Date.now();
|
|
33
|
+
return {
|
|
34
|
+
status: RECONCILED_TURN_TIMELINE_STATUS,
|
|
35
|
+
id: `provider-turn-reconciled-${input.turnId}`,
|
|
36
|
+
timestamp: now,
|
|
37
|
+
title: "Provider turn cleared by reconciliation",
|
|
38
|
+
body: `The provider turn ended without a turn-end signal (${input.reason}); the agent was observed idle at its prompt and is no longer busy.`,
|
|
39
|
+
icon: "ti-alert-triangle",
|
|
40
|
+
metadata: {
|
|
41
|
+
eventType: RECONCILED_TURN_TIMELINE_STATUS,
|
|
42
|
+
reconcileReason: input.reason,
|
|
43
|
+
turnId: input.turnId,
|
|
44
|
+
turnStartedAt: input.startedAt,
|
|
45
|
+
turnAgeMs: Math.max(0, now - input.startedAt),
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|