fix: support native manager dispatch
This commit is contained in:
+3
-3
@@ -4,7 +4,6 @@ import { execFileSync, spawn } from "node:child_process";
|
|||||||
import { closeSync, existsSync, openSync } from "node:fs";
|
import { closeSync, existsSync, openSync } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { startManagerServer } from "../../src/mgr/server.js";
|
import { startManagerServer } from "../../src/mgr/server.js";
|
||||||
import { MemoryAgentRunStore } from "../../src/mgr/store.js";
|
|
||||||
import { ManagerClient } from "../../src/mgr/client.js";
|
import { ManagerClient } from "../../src/mgr/client.js";
|
||||||
import { runOnce } from "../../src/runner/run-once.js";
|
import { runOnce } from "../../src/runner/run-once.js";
|
||||||
import { renderRunnerJobDryRun } from "../../src/runner/k8s-job.js";
|
import { renderRunnerJobDryRun } from "../../src/runner/k8s-job.js";
|
||||||
@@ -2032,8 +2031,9 @@ async function startServer(args: ParsedArgs): Promise<JsonRecord> {
|
|||||||
async function startServerForeground(args: ParsedArgs): Promise<JsonRecord> {
|
async function startServerForeground(args: ParsedArgs): Promise<JsonRecord> {
|
||||||
const port = Number(flag(args, "port", "8080"));
|
const port = Number(flag(args, "port", "8080"));
|
||||||
const host = flag(args, "host", "0.0.0.0");
|
const host = flag(args, "host", "0.0.0.0");
|
||||||
const storeMode = optionalFlag(args, "store") ?? process.env.AGENTRUN_STORE ?? process.env.AGENTRUN_MGR_STORE;
|
const storeMode = optionalFlag(args, "store");
|
||||||
const started = await startManagerServer({ port, host, ...(storeMode === "memory" ? { store: new MemoryAgentRunStore() } : {}) });
|
if (storeMode) process.env.AGENTRUN_STORE = storeMode;
|
||||||
|
const started = await startManagerServer({ port, host });
|
||||||
const database = await started.store.health();
|
const database = await started.store.health();
|
||||||
return { serviceId: "agentrun-mgr", baseUrl: started.baseUrl, pid: process.pid, database, mode: "foreground", note: "foreground process; use server start without --foreground for local background mode" };
|
return { serviceId: "agentrun-mgr", baseUrl: started.baseUrl, pid: process.pid, database, mode: "foreground", note: "foreground process; use server start without --foreground for local background mode" };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { Kafka, logLevel, type Consumer, type EachMessagePayload, type Producer } from "kafkajs";
|
import { Kafka, logLevel, type Consumer, type EachMessagePayload, type ISocketFactory, type Producer } from "kafkajs";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
|
import { Resolver } from "node:dns";
|
||||||
|
import * as net from "node:net";
|
||||||
|
import { isIP, type LookupFunction } from "node:net";
|
||||||
|
import * as tls from "node:tls";
|
||||||
import type { JsonRecord, JsonValue } from "./types.js";
|
import type { JsonRecord, JsonValue } from "./types.js";
|
||||||
import { redactJson, redactText } from "./redaction.js";
|
import { redactJson, redactText } from "./redaction.js";
|
||||||
|
|
||||||
@@ -19,6 +23,8 @@ export interface AgentRunKafkaConfig {
|
|||||||
clientId: string;
|
clientId: string;
|
||||||
agentrunEventTopic: string;
|
agentrunEventTopic: string;
|
||||||
codexStdioTopic: string;
|
codexStdioTopic: string;
|
||||||
|
dnsServers: string[];
|
||||||
|
dnsSearchDomains: string[];
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
valuesPrinted: false;
|
valuesPrinted: false;
|
||||||
}
|
}
|
||||||
@@ -59,6 +65,8 @@ export function agentRunKafkaConfig(env: NodeJS.ProcessEnv = process.env, overri
|
|||||||
clientId,
|
clientId,
|
||||||
agentrunEventTopic: topicOverride && overrides.stream !== "stdio" ? topicOverride : agentrunEventTopic,
|
agentrunEventTopic: topicOverride && overrides.stream !== "stdio" ? topicOverride : agentrunEventTopic,
|
||||||
codexStdioTopic: topicOverride && overrides.stream === "stdio" ? topicOverride : codexStdioTopic,
|
codexStdioTopic: topicOverride && overrides.stream === "stdio" ? topicOverride : codexStdioTopic,
|
||||||
|
dnsServers: csv(env.AGENTRUN_KAFKA_DNS_SERVERS),
|
||||||
|
dnsSearchDomains: csv(env.AGENTRUN_KAFKA_DNS_SEARCH_DOMAINS),
|
||||||
enabled: truthy(env.AGENTRUN_KAFKA_ENABLED) || truthy(env.AGENTRUN_KAFKA_SHADOW_PRODUCE_ENABLED) || truthy(env.AGENTRUN_KAFKA_STDIO_PRODUCE_ENABLED),
|
enabled: truthy(env.AGENTRUN_KAFKA_ENABLED) || truthy(env.AGENTRUN_KAFKA_SHADOW_PRODUCE_ENABLED) || truthy(env.AGENTRUN_KAFKA_STDIO_PRODUCE_ENABLED),
|
||||||
valuesPrinted: false,
|
valuesPrinted: false,
|
||||||
};
|
};
|
||||||
@@ -156,7 +164,7 @@ export async function tailAgentRunKafka(options: KafkaTailOptions): Promise<Json
|
|||||||
const limit = boundedInteger(options.limit ?? 20, 1, 200, "limit");
|
const limit = boundedInteger(options.limit ?? 20, 1, 200, "limit");
|
||||||
const timeoutMs = boundedInteger(options.timeoutMs ?? 5_000, 500, 60_000, "timeout-ms");
|
const timeoutMs = boundedInteger(options.timeoutMs ?? 5_000, 500, 60_000, "timeout-ms");
|
||||||
const groupId = safeText(options.groupId) ?? `agentrun-cli-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
const groupId = safeText(options.groupId) ?? `agentrun-cli-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
const kafka = new Kafka({ clientId: `${config.clientId}-cli`, brokers: config.brokers, logLevel: logLevel.NOTHING });
|
const kafka = createAgentRunKafka(config, `${config.clientId}-cli`);
|
||||||
const consumer = kafka.consumer({ groupId });
|
const consumer = kafka.consumer({ groupId });
|
||||||
const messages: JsonRecord[] = [];
|
const messages: JsonRecord[] = [];
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
@@ -290,10 +298,78 @@ async function producerForConfig(config: AgentRunKafkaConfig): Promise<Producer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
function defaultProducerFactory(config: AgentRunKafkaConfig): Producer {
|
function defaultProducerFactory(config: AgentRunKafkaConfig): Producer {
|
||||||
const kafka = new Kafka({ clientId: config.clientId, brokers: config.brokers, logLevel: logLevel.NOTHING });
|
const kafka = createAgentRunKafka(config);
|
||||||
return kafka.producer({ allowAutoTopicCreation: false });
|
return kafka.producer({ allowAutoTopicCreation: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createAgentRunKafka(config: AgentRunKafkaConfig, clientId = config.clientId): Kafka {
|
||||||
|
const socketFactory = kafkaSocketFactory(config.dnsServers, config.dnsSearchDomains);
|
||||||
|
return new Kafka({ clientId, brokers: config.brokers, logLevel: logLevel.NOTHING, ...(socketFactory ? { socketFactory } : {}) });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function kafkaSocketFactory(dnsServers: string[], dnsSearchDomains: string[] = []): ISocketFactory | undefined {
|
||||||
|
const lookup = kafkaDnsLookup(dnsServers, dnsSearchDomains);
|
||||||
|
if (!lookup) return undefined;
|
||||||
|
return ({ host, port, ssl, onConnect }) => ssl
|
||||||
|
? tls.connect({ host, port, ...ssl, lookup }, onConnect)
|
||||||
|
: net.connect({ host, port, lookup }, onConnect);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function kafkaDnsLookup(dnsServers: string[], dnsSearchDomains: string[] = []): LookupFunction | undefined {
|
||||||
|
if (dnsServers.length === 0) return undefined;
|
||||||
|
const resolver = new Resolver();
|
||||||
|
resolver.setServers(dnsServers);
|
||||||
|
const lookup: LookupFunction = (hostname, options, callback) => {
|
||||||
|
const family = typeof options === "number" ? options : options.family;
|
||||||
|
const all = typeof options === "object" && options.all === true;
|
||||||
|
const literalFamily = isIP(hostname);
|
||||||
|
if (literalFamily > 0) {
|
||||||
|
callback(null, all ? [{ address: hostname, family: literalFamily }] : hostname, literalFamily);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolveKafkaHostname(resolver, hostname, dnsSearchDomains, family === 6 ? 6 : 4, (error, addresses) => {
|
||||||
|
if (error) {
|
||||||
|
callback(error, undefined as never);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (addresses.length === 0) {
|
||||||
|
const emptyError = new Error(`Kafka DNS lookup returned no IPv${family === 6 ? 6 : 4} addresses for ${hostname}.`) as NodeJS.ErrnoException;
|
||||||
|
emptyError.code = "agentrun_kafka_dns_empty";
|
||||||
|
callback(emptyError, undefined as never);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const resolvedFamily = family === 6 ? 6 : 4;
|
||||||
|
callback(null, all ? addresses.map((address) => ({ address, family: resolvedFamily })) : addresses[0]!, resolvedFamily);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return lookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveKafkaHostname(resolver: Resolver, hostname: string, searchDomains: string[], family: 4 | 6, callback: (error: NodeJS.ErrnoException | null, addresses: string[]) => void): void {
|
||||||
|
const candidates = [
|
||||||
|
hostname,
|
||||||
|
...searchDomains
|
||||||
|
.map((domain) => domain.replace(/^\.+|\.+$/gu, ""))
|
||||||
|
.filter(Boolean)
|
||||||
|
.filter((domain) => !hostname.endsWith(`.${domain}`))
|
||||||
|
.map((domain) => `${hostname}.${domain}`),
|
||||||
|
];
|
||||||
|
const resolveNext = (index: number, previousError: NodeJS.ErrnoException | null = null): void => {
|
||||||
|
const candidate = candidates[index];
|
||||||
|
if (!candidate) {
|
||||||
|
callback(previousError ?? new Error(`Kafka DNS lookup failed for ${hostname}`), []);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const done = (error: NodeJS.ErrnoException | null, addresses: string[]): void => {
|
||||||
|
if (!error && addresses.length > 0) callback(null, addresses);
|
||||||
|
else resolveNext(index + 1, error ?? previousError);
|
||||||
|
};
|
||||||
|
if (family === 6) resolver.resolve6(candidate, done);
|
||||||
|
else resolver.resolve4(candidate, done);
|
||||||
|
};
|
||||||
|
resolveNext(0);
|
||||||
|
}
|
||||||
|
|
||||||
async function sendAgentRunKafkaMessage(producer: Producer, message: AgentRunKafkaMessage): Promise<void> {
|
async function sendAgentRunKafkaMessage(producer: Producer, message: AgentRunKafkaMessage): Promise<void> {
|
||||||
await sendAgentRunKafkaMessageBatch(producer, [message]);
|
await sendAgentRunKafkaMessageBatch(producer, [message]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,9 @@ export async function dispatchRunnerIntentsOnce(input: {
|
|||||||
items.push(itemSummary(intent, "stale", null));
|
items.push(itemSummary(intent, "stale", null));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const terminal = intent.attemptCount >= input.options.maxAttempts;
|
|
||||||
const failure = dispatchFailure(error, intent);
|
const failure = dispatchFailure(error, intent);
|
||||||
|
const terminal = isDeterministicDispatchFailure(failure.failureKind)
|
||||||
|
|| intent.attemptCount >= input.options.maxAttempts;
|
||||||
const nextAttemptAt = new Date(Date.now() + input.options.retryBackoffMs).toISOString();
|
const nextAttemptAt = new Date(Date.now() + input.options.retryBackoffMs).toISOString();
|
||||||
try {
|
try {
|
||||||
if (terminal) {
|
if (terminal) {
|
||||||
@@ -115,6 +116,14 @@ function dispatchFailure(error: unknown, intent: RunnerDispatchIntentRecord): Js
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDeterministicDispatchFailure(failureKind: unknown): boolean {
|
||||||
|
return failureKind === "auth-missing"
|
||||||
|
|| failureKind === "auth-failed"
|
||||||
|
|| failureKind === "schema-invalid"
|
||||||
|
|| failureKind === "tenant-policy-denied"
|
||||||
|
|| failureKind === "secret-unavailable";
|
||||||
|
}
|
||||||
|
|
||||||
function itemSummary(intent: RunnerDispatchIntentRecord, state: string, error: JsonRecord | null, completion?: RunnerDispatchCompletion): JsonRecord {
|
function itemSummary(intent: RunnerDispatchIntentRecord, state: string, error: JsonRecord | null, completion?: RunnerDispatchCompletion): JsonRecord {
|
||||||
return { dispatchIntentId: intent.id, runId: intent.runId, commandId: intent.commandId, runnerJobId: intent.runnerJobId, actualRunnerJobId: completion?.actualRunnerJobId ?? intent.actualRunnerJobId, activeRunnerId: completion?.activeRunnerId ?? intent.activeRunnerId, dispatchOutcome: completion?.dispatchOutcome ?? intent.dispatchOutcome, attemptCount: intent.attemptCount, state, failureKind: error?.failureKind ?? null, valuesPrinted: false };
|
return { dispatchIntentId: intent.id, runId: intent.runId, commandId: intent.commandId, runnerJobId: intent.runnerJobId, actualRunnerJobId: completion?.actualRunnerJobId ?? intent.actualRunnerJobId, activeRunnerId: completion?.activeRunnerId ?? intent.activeRunnerId, dispatchOutcome: completion?.dispatchOutcome ?? intent.dispatchOutcome, attemptCount: intent.attemptCount, state, failureKind: error?.failureKind ?? null, valuesPrinted: false };
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -68,7 +68,11 @@ function runnerJobDefaultsForRequest(defaults: ManagerServerOptions["runnerJobDe
|
|||||||
...(defaults?.backendRetryMaxAttempts !== undefined ? { backendRetryMaxAttempts: defaults.backendRetryMaxAttempts } : optionalPositiveIntegerRecord("backendRetryMaxAttempts", process.env.AGENTRUN_BACKEND_RETRY_MAX_ATTEMPTS)),
|
...(defaults?.backendRetryMaxAttempts !== undefined ? { backendRetryMaxAttempts: defaults.backendRetryMaxAttempts } : optionalPositiveIntegerRecord("backendRetryMaxAttempts", process.env.AGENTRUN_BACKEND_RETRY_MAX_ATTEMPTS)),
|
||||||
...(defaults?.backendRetryInitialBackoffMs !== undefined ? { backendRetryInitialBackoffMs: defaults.backendRetryInitialBackoffMs } : optionalPositiveIntegerRecord("backendRetryInitialBackoffMs", process.env.AGENTRUN_BACKEND_RETRY_INITIAL_BACKOFF_MS)),
|
...(defaults?.backendRetryInitialBackoffMs !== undefined ? { backendRetryInitialBackoffMs: defaults.backendRetryInitialBackoffMs } : optionalPositiveIntegerRecord("backendRetryInitialBackoffMs", process.env.AGENTRUN_BACKEND_RETRY_INITIAL_BACKOFF_MS)),
|
||||||
...(defaults?.backendRetryMaxBackoffMs !== undefined ? { backendRetryMaxBackoffMs: defaults.backendRetryMaxBackoffMs } : optionalPositiveIntegerRecord("backendRetryMaxBackoffMs", process.env.AGENTRUN_BACKEND_RETRY_MAX_BACKOFF_MS)),
|
...(defaults?.backendRetryMaxBackoffMs !== undefined ? { backendRetryMaxBackoffMs: defaults.backendRetryMaxBackoffMs } : optionalPositiveIntegerRecord("backendRetryMaxBackoffMs", process.env.AGENTRUN_BACKEND_RETRY_MAX_BACKOFF_MS)),
|
||||||
...(defaults?.kubectlCommand ? { kubectlCommand: defaults.kubectlCommand } : {}),
|
...(defaults?.kubectlCommand
|
||||||
|
? { kubectlCommand: defaults.kubectlCommand }
|
||||||
|
: process.env.AGENTRUN_KUBECTL_COMMAND
|
||||||
|
? { kubectlCommand: process.env.AGENTRUN_KUBECTL_COMMAND }
|
||||||
|
: {}),
|
||||||
...(defaults?.unideskSshEndpointEnv ? { unideskSshEndpointEnv: defaults.unideskSshEndpointEnv } : {}),
|
...(defaults?.unideskSshEndpointEnv ? { unideskSshEndpointEnv: defaults.unideskSshEndpointEnv } : {}),
|
||||||
...(retention ? { retention } : {}),
|
...(retention ? { retention } : {}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -479,6 +479,8 @@ function runnerKafkaEnvVars(env: NodeJS.ProcessEnv): JsonRecord[] {
|
|||||||
...optionalEnvVar("AGENTRUN_KAFKA_STDIO_TOPIC", env.AGENTRUN_KAFKA_STDIO_TOPIC),
|
...optionalEnvVar("AGENTRUN_KAFKA_STDIO_TOPIC", env.AGENTRUN_KAFKA_STDIO_TOPIC),
|
||||||
...optionalEnvVar("AGENTRUN_KAFKA_STDIO_CLIENT_ID", env.AGENTRUN_KAFKA_STDIO_CLIENT_ID),
|
...optionalEnvVar("AGENTRUN_KAFKA_STDIO_CLIENT_ID", env.AGENTRUN_KAFKA_STDIO_CLIENT_ID),
|
||||||
...optionalEnvVar("AGENTRUN_KAFKA_STDIO_PRODUCE_ENABLED", env.AGENTRUN_KAFKA_STDIO_PRODUCE_ENABLED),
|
...optionalEnvVar("AGENTRUN_KAFKA_STDIO_PRODUCE_ENABLED", env.AGENTRUN_KAFKA_STDIO_PRODUCE_ENABLED),
|
||||||
|
...optionalEnvVar("AGENTRUN_KAFKA_DNS_SERVERS", env.AGENTRUN_KAFKA_DNS_SERVERS),
|
||||||
|
...optionalEnvVar("AGENTRUN_KAFKA_DNS_SEARCH_DOMAINS", env.AGENTRUN_KAFKA_DNS_SEARCH_DOMAINS),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,11 @@ async function assertHungKubectlTimesOutAndRecovers(kubectlCommand: string): Pro
|
|||||||
const firstResult = await firstCycle;
|
const firstResult = await firstCycle;
|
||||||
assert.equal(firstResult.retryCount, 1, JSON.stringify({ firstResult, intent: store.getRunnerDispatchIntent(command.id) }));
|
assert.equal(firstResult.retryCount, 1, JSON.stringify({ firstResult, intent: store.getRunnerDispatchIntent(command.id) }));
|
||||||
assert.equal(store.getRunnerDispatchIntent(command.id)?.state, "retry");
|
assert.equal(store.getRunnerDispatchIntent(command.id)?.state, "retry");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 15));
|
await waitFor(
|
||||||
|
() => Date.parse(store.getRunnerDispatchIntent(command.id)?.nextAttemptAt ?? "") <= Date.now(),
|
||||||
|
1_000,
|
||||||
|
"runner dispatch retry did not become due",
|
||||||
|
);
|
||||||
assert.equal((await dispatchRunnerIntentsOnce({ store, defaults: defaults(kubectlCommand), options })).dispatchedCount, 1);
|
assert.equal((await dispatchRunnerIntentsOnce({ store, defaults: defaults(kubectlCommand), options })).dispatchedCount, 1);
|
||||||
assert.equal(store.getRunnerDispatchIntent(command.id)?.attemptCount, 2);
|
assert.equal(store.getRunnerDispatchIntent(command.id)?.attemptCount, 2);
|
||||||
assert.equal(store.getRunnerDispatchIntent(command.id)?.dispatchOutcome, "kubernetes-job");
|
assert.equal(store.getRunnerDispatchIntent(command.id)?.dispatchOutcome, "kubernetes-job");
|
||||||
@@ -292,6 +296,17 @@ async function assertTypedDispatchFailureFacts(kubectlCommand: string): Promise<
|
|||||||
assert.equal(event.payload.reason, "runner-dispatch-source-timeout");
|
assert.equal(event.payload.reason, "runner-dispatch-source-timeout");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deterministicStore = new RunnerCreateFailureStore(new AgentRunError("schema-invalid", "runner assembly is invalid", { httpStatus: 400, details: { reason: "runner-assembly-invalid", valuesPrinted: false } }));
|
||||||
|
const deterministicRun = deterministicStore.createRun(runInput());
|
||||||
|
const deterministicCommand = deterministicStore.createCommand(deterministicRun.id, validateCreateCommand({ type: "turn", payload: {}, dispatch: { kind: "kubernetes-job", input: {} } }));
|
||||||
|
const deterministic = await dispatchRunnerIntentsOnce({ store: deterministicStore, defaults: defaults(kubectlCommand), options: dispatcherOptions(5) });
|
||||||
|
assert.equal(deterministic.failedCount, 1);
|
||||||
|
assert.equal(deterministic.retryCount, 0);
|
||||||
|
assert.equal(deterministicStore.getRunnerDispatchIntent(deterministicCommand.id)?.attemptCount, 1);
|
||||||
|
assert.equal(deterministicStore.getRunnerDispatchIntent(deterministicCommand.id)?.state, "failed");
|
||||||
|
assert.equal(deterministicStore.getRun(deterministicRun.id).failureKind, "schema-invalid");
|
||||||
|
assert.equal(deterministicStore.listEvents(deterministicRun.id, 0, 100).some((event) => event.payload.phase === "runner-dispatch-retry"), false);
|
||||||
|
|
||||||
const untypedStore = new RunnerCreateFailureStore(new Error("kubectl get jobs then kubectl create runner job failed"));
|
const untypedStore = new RunnerCreateFailureStore(new Error("kubectl get jobs then kubectl create runner job failed"));
|
||||||
const untypedRun = untypedStore.createRun(runInput());
|
const untypedRun = untypedStore.createRun(runInput());
|
||||||
const untypedCommand = untypedStore.createCommand(untypedRun.id, validateCreateCommand({ type: "turn", payload: {}, dispatch: { kind: "kubernetes-job", input: {} } }));
|
const untypedCommand = untypedStore.createCommand(untypedRun.id, validateCreateCommand({ type: "turn", payload: {}, dispatch: { kind: "kubernetes-job", input: {} } }));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { closeAgentRunKafkaProducer, kafkaMessageMetadata, publishAgentRunKafkaMessage, publishAgentRunKafkaMessageBatch, rawFrameValue, setAgentRunKafkaProducerFactoryForSelfTest, type AgentRunKafkaConfig } from "../../common/kafka-events.js";
|
import { agentRunKafkaConfig, closeAgentRunKafkaProducer, kafkaMessageMetadata, publishAgentRunKafkaMessage, publishAgentRunKafkaMessageBatch, rawFrameValue, setAgentRunKafkaProducerFactoryForSelfTest, type AgentRunKafkaConfig } from "../../common/kafka-events.js";
|
||||||
import { AgentRunError } from "../../common/errors.js";
|
import { AgentRunError } from "../../common/errors.js";
|
||||||
import { agentRunOtelTraceContext } from "../../common/otel-trace.js";
|
import { agentRunOtelTraceContext } from "../../common/otel-trace.js";
|
||||||
import type { CreateRunInput, JsonRecord, KafkaEventOutboxRecord } from "../../common/types.js";
|
import type { CreateRunInput, JsonRecord, KafkaEventOutboxRecord } from "../../common/types.js";
|
||||||
@@ -15,6 +15,8 @@ const config: AgentRunKafkaConfig = {
|
|||||||
clientId: "agentrun-selftest",
|
clientId: "agentrun-selftest",
|
||||||
agentrunEventTopic: "agentrun.event.v1",
|
agentrunEventTopic: "agentrun.event.v1",
|
||||||
codexStdioTopic: "codex-stdio.raw.v1",
|
codexStdioTopic: "codex-stdio.raw.v1",
|
||||||
|
dnsServers: [],
|
||||||
|
dnsSearchDomains: [],
|
||||||
enabled: true,
|
enabled: true,
|
||||||
valuesPrinted: false,
|
valuesPrinted: false,
|
||||||
};
|
};
|
||||||
@@ -30,6 +32,9 @@ const relayOptions: KafkaOutboxRelayOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selfTest: SelfTestCase = async () => {
|
const selfTest: SelfTestCase = async () => {
|
||||||
|
const nativeDns = agentRunKafkaConfig({ AGENTRUN_KAFKA_DNS_SERVERS: "10.43.0.10,10.43.0.11", AGENTRUN_KAFKA_DNS_SEARCH_DOMAINS: "cluster.local" });
|
||||||
|
assert.deepEqual(nativeDns.dnsServers, ["10.43.0.10", "10.43.0.11"]);
|
||||||
|
assert.deepEqual(nativeDns.dnsSearchDomains, ["cluster.local"]);
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
() => openAgentRunStoreFromEnv({ AGENTRUN_STORE: "memory", AGENTRUN_KAFKA_ENABLED: "true" }),
|
() => openAgentRunStoreFromEnv({ AGENTRUN_STORE: "memory", AGENTRUN_KAFKA_ENABLED: "true" }),
|
||||||
(error) => error instanceof AgentRunError && error.message.includes("configuration is incomplete"),
|
(error) => error instanceof AgentRunError && error.message.includes("configuration is incomplete"),
|
||||||
@@ -180,7 +185,7 @@ const selfTest: SelfTestCase = async () => {
|
|||||||
|
|
||||||
await assertStaleClaimsCannotOverwrite();
|
await assertStaleClaimsCannotOverwrite();
|
||||||
await assertProducerRecreatedAfterDisconnected();
|
await assertProducerRecreatedAfterDisconnected();
|
||||||
return { name: "kafka-durable-outbox", tests: ["explicit-enable", "canonical-event", "warm-run-command-trace-authority", "run-level-trace-fallback", "stdio-command-trace-context", "kafka-tail-correlation-summary", "durable-key-batch", "native-broker-batch", "ack-before-delivered", "claim-duration-visible", "monotonic-backlog-drain", "settle-stale-classification", "cross-key-parallelism", "batch-bounded-parallelism", "failed-key-isolation", "atomic-dispatch-terminal", "terminal-command-idempotent-replay", "terminal-command-replay-no-new-events-or-outbox", "terminal-command-same-key-payload-conflict", "terminal-command-different-key-fails-closed", "stale-claim-fencing", "producer-reconnect"] };
|
return { name: "kafka-durable-outbox", tests: ["explicit-enable", "native-dns-config", "canonical-event", "warm-run-command-trace-authority", "run-level-trace-fallback", "stdio-command-trace-context", "kafka-tail-correlation-summary", "durable-key-batch", "native-broker-batch", "ack-before-delivered", "claim-duration-visible", "monotonic-backlog-drain", "settle-stale-classification", "cross-key-parallelism", "batch-bounded-parallelism", "failed-key-isolation", "atomic-dispatch-terminal", "terminal-command-idempotent-replay", "terminal-command-replay-no-new-events-or-outbox", "terminal-command-same-key-payload-conflict", "terminal-command-different-key-fails-closed", "stale-claim-fencing", "producer-reconnect"] };
|
||||||
};
|
};
|
||||||
|
|
||||||
function assertWarmRunCommandTraceAuthority(): void {
|
function assertWarmRunCommandTraceAuthority(): void {
|
||||||
|
|||||||
@@ -225,6 +225,8 @@ async function assertOneShotPublisherOwnership(frames: ReturnType<typeof parseSt
|
|||||||
clientId: "agentrun-selftest",
|
clientId: "agentrun-selftest",
|
||||||
agentrunEventTopic: "agentrun.event.v1",
|
agentrunEventTopic: "agentrun.event.v1",
|
||||||
codexStdioTopic: "codex-stdio.raw.v1",
|
codexStdioTopic: "codex-stdio.raw.v1",
|
||||||
|
dnsServers: [],
|
||||||
|
dnsSearchDomains: [],
|
||||||
enabled: true,
|
enabled: true,
|
||||||
valuesPrinted: false,
|
valuesPrinted: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ async function assertPostgresBatchRelay(store: Awaited<ReturnType<typeof createP
|
|||||||
leaseMs: 60_000,
|
leaseMs: 60_000,
|
||||||
retryBackoffMs: 1,
|
retryBackoffMs: 1,
|
||||||
owner: "postgres-batch-relay",
|
owner: "postgres-batch-relay",
|
||||||
config: { brokers: ["127.0.0.1:9092"], clientId: "postgres-batch-relay", agentrunEventTopic: topic, codexStdioTopic: "codex-stdio.raw.v1", enabled: true, valuesPrinted: false },
|
config: { brokers: ["127.0.0.1:9092"], clientId: "postgres-batch-relay", agentrunEventTopic: topic, codexStdioTopic: "codex-stdio.raw.v1", dnsServers: [], dnsSearchDomains: [], enabled: true, valuesPrinted: false },
|
||||||
};
|
};
|
||||||
const result = await relayKafkaEventOutboxOnce({
|
const result = await relayKafkaEventOutboxOnce({
|
||||||
store,
|
store,
|
||||||
|
|||||||
Reference in New Issue
Block a user