@posthog/agent 2.3.656 → 2.3.658
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/dist/agent.js +1 -1
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.js +1 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +4 -0
- package/dist/server/agent-server.js +708 -4
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +716 -7
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +3 -3
- package/src/server/agent-server.test.ts +241 -0
- package/src/server/agent-server.ts +55 -3
- package/src/server/bin.ts +12 -0
- package/src/server/event-stream-sender.test.ts +779 -0
- package/src/server/event-stream-sender.ts +693 -0
- package/src/server/streaming-upload.ts +160 -0
- package/src/server/types.ts +2 -0
|
@@ -9315,7 +9315,7 @@ import { z as z5 } from "zod";
|
|
|
9315
9315
|
// package.json
|
|
9316
9316
|
var package_default = {
|
|
9317
9317
|
name: "@posthog/agent",
|
|
9318
|
-
version: "2.3.
|
|
9318
|
+
version: "2.3.658",
|
|
9319
9319
|
repository: "https://github.com/PostHog/code",
|
|
9320
9320
|
description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
9321
9321
|
exports: {
|
|
@@ -22450,6 +22450,671 @@ function normalizeCloudPromptContent(content) {
|
|
|
22450
22450
|
return content;
|
|
22451
22451
|
}
|
|
22452
22452
|
|
|
22453
|
+
// src/server/event-stream-sender.ts
|
|
22454
|
+
import { Buffer as Buffer4 } from "buffer";
|
|
22455
|
+
|
|
22456
|
+
// src/server/streaming-upload.ts
|
|
22457
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
22458
|
+
import {
|
|
22459
|
+
request as httpRequest
|
|
22460
|
+
} from "http";
|
|
22461
|
+
import { request as httpsRequest } from "https";
|
|
22462
|
+
import { URL as URL2 } from "url";
|
|
22463
|
+
function headersFromIncoming(headers) {
|
|
22464
|
+
const result = new Headers();
|
|
22465
|
+
for (const [name2, value] of Object.entries(headers)) {
|
|
22466
|
+
if (value === void 0) {
|
|
22467
|
+
continue;
|
|
22468
|
+
}
|
|
22469
|
+
if (Array.isArray(value)) {
|
|
22470
|
+
for (const item of value) {
|
|
22471
|
+
result.append(name2, item);
|
|
22472
|
+
}
|
|
22473
|
+
} else {
|
|
22474
|
+
result.set(name2, String(value));
|
|
22475
|
+
}
|
|
22476
|
+
}
|
|
22477
|
+
return result;
|
|
22478
|
+
}
|
|
22479
|
+
function abortError() {
|
|
22480
|
+
const error = new Error("aborted");
|
|
22481
|
+
error.name = "AbortError";
|
|
22482
|
+
return error;
|
|
22483
|
+
}
|
|
22484
|
+
function writeRequestChunk(request, chunk) {
|
|
22485
|
+
return new Promise((resolve8, reject) => {
|
|
22486
|
+
const onError2 = (error) => {
|
|
22487
|
+
request.off("error", onError2);
|
|
22488
|
+
reject(error);
|
|
22489
|
+
};
|
|
22490
|
+
request.once("error", onError2);
|
|
22491
|
+
request.write(Buffer3.from(chunk), (error) => {
|
|
22492
|
+
request.off("error", onError2);
|
|
22493
|
+
if (error) {
|
|
22494
|
+
reject(error);
|
|
22495
|
+
return;
|
|
22496
|
+
}
|
|
22497
|
+
resolve8();
|
|
22498
|
+
});
|
|
22499
|
+
});
|
|
22500
|
+
}
|
|
22501
|
+
function closeRequest(request) {
|
|
22502
|
+
return new Promise((resolve8, reject) => {
|
|
22503
|
+
const onError2 = (error) => {
|
|
22504
|
+
request.off("error", onError2);
|
|
22505
|
+
reject(error);
|
|
22506
|
+
};
|
|
22507
|
+
request.once("error", onError2);
|
|
22508
|
+
request.end(() => {
|
|
22509
|
+
request.off("error", onError2);
|
|
22510
|
+
resolve8();
|
|
22511
|
+
});
|
|
22512
|
+
});
|
|
22513
|
+
}
|
|
22514
|
+
function createNodeStreamingUpload({
|
|
22515
|
+
url,
|
|
22516
|
+
headers,
|
|
22517
|
+
abortController
|
|
22518
|
+
}) {
|
|
22519
|
+
const parsedUrl = new URL2(url);
|
|
22520
|
+
const requestFactory = parsedUrl.protocol === "https:" ? httpsRequest : parsedUrl.protocol === "http:" ? httpRequest : void 0;
|
|
22521
|
+
if (!requestFactory) {
|
|
22522
|
+
throw new Error(`Unsupported event ingest protocol: ${parsedUrl.protocol}`);
|
|
22523
|
+
}
|
|
22524
|
+
const request = requestFactory(parsedUrl, {
|
|
22525
|
+
method: "POST",
|
|
22526
|
+
headers
|
|
22527
|
+
});
|
|
22528
|
+
let closed = false;
|
|
22529
|
+
const responsePromise = new Promise((resolve8, reject) => {
|
|
22530
|
+
request.on("response", (response) => {
|
|
22531
|
+
const chunks = [];
|
|
22532
|
+
response.on("data", (chunk) => {
|
|
22533
|
+
chunks.push(Buffer3.isBuffer(chunk) ? chunk : Buffer3.from(chunk));
|
|
22534
|
+
});
|
|
22535
|
+
response.on("end", () => {
|
|
22536
|
+
resolve8(
|
|
22537
|
+
new Response(Buffer3.concat(chunks), {
|
|
22538
|
+
status: response.statusCode ?? 0,
|
|
22539
|
+
statusText: response.statusMessage,
|
|
22540
|
+
headers: headersFromIncoming(response.headers)
|
|
22541
|
+
})
|
|
22542
|
+
);
|
|
22543
|
+
});
|
|
22544
|
+
response.on("error", reject);
|
|
22545
|
+
});
|
|
22546
|
+
request.on("error", reject);
|
|
22547
|
+
});
|
|
22548
|
+
const abortRequest = () => {
|
|
22549
|
+
closed = true;
|
|
22550
|
+
if (!request.destroyed) {
|
|
22551
|
+
request.destroy(abortError());
|
|
22552
|
+
}
|
|
22553
|
+
};
|
|
22554
|
+
abortController.signal.addEventListener("abort", abortRequest, {
|
|
22555
|
+
once: true
|
|
22556
|
+
});
|
|
22557
|
+
void responsePromise.finally(() => {
|
|
22558
|
+
abortController.signal.removeEventListener("abort", abortRequest);
|
|
22559
|
+
}).catch(() => void 0);
|
|
22560
|
+
return {
|
|
22561
|
+
async write(chunk) {
|
|
22562
|
+
if (closed) {
|
|
22563
|
+
throw new Error("Cannot write to closed event ingest stream");
|
|
22564
|
+
}
|
|
22565
|
+
await writeRequestChunk(request, chunk);
|
|
22566
|
+
},
|
|
22567
|
+
async close() {
|
|
22568
|
+
if (closed) {
|
|
22569
|
+
return;
|
|
22570
|
+
}
|
|
22571
|
+
closed = true;
|
|
22572
|
+
await closeRequest(request);
|
|
22573
|
+
},
|
|
22574
|
+
async abort() {
|
|
22575
|
+
abortRequest();
|
|
22576
|
+
},
|
|
22577
|
+
responsePromise
|
|
22578
|
+
};
|
|
22579
|
+
}
|
|
22580
|
+
|
|
22581
|
+
// src/server/event-stream-sender.ts
|
|
22582
|
+
var DEFAULT_MAX_BUFFERED_EVENTS = 2e4;
|
|
22583
|
+
var DEFAULT_MAX_STREAM_EVENTS = 900;
|
|
22584
|
+
var DEFAULT_MAX_STREAM_BYTES = 4e6;
|
|
22585
|
+
var DEFAULT_MAX_EVENT_BYTES = 9e5;
|
|
22586
|
+
var DEFAULT_FLUSH_DELAY_MS = 0;
|
|
22587
|
+
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
22588
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
|
|
22589
|
+
var DEFAULT_STOP_TIMEOUT_MS = 3e4;
|
|
22590
|
+
var DEFAULT_STREAM_WINDOW_MS = 5 * 60 * 1e3;
|
|
22591
|
+
var STREAM_COMPLETE_CONTROL_TYPE = "_posthog/stream_complete";
|
|
22592
|
+
var TaskRunEventStreamSender = class {
|
|
22593
|
+
constructor(config) {
|
|
22594
|
+
this.config = config;
|
|
22595
|
+
const apiUrl = config.apiUrl.replace(/\/$/, "");
|
|
22596
|
+
this.ingestUrl = `${apiUrl}/api/projects/${config.projectId}/tasks/${encodeURIComponent(
|
|
22597
|
+
config.taskId
|
|
22598
|
+
)}/runs/${encodeURIComponent(config.runId)}/event_stream/`;
|
|
22599
|
+
this.maxBufferedEvents = config.maxBufferedEvents ?? DEFAULT_MAX_BUFFERED_EVENTS;
|
|
22600
|
+
this.maxStreamEvents = config.maxStreamEvents ?? DEFAULT_MAX_STREAM_EVENTS;
|
|
22601
|
+
this.maxStreamBytes = config.maxStreamBytes ?? DEFAULT_MAX_STREAM_BYTES;
|
|
22602
|
+
this.maxEventBytes = config.maxEventBytes ?? DEFAULT_MAX_EVENT_BYTES;
|
|
22603
|
+
this.flushDelayMs = config.flushDelayMs ?? DEFAULT_FLUSH_DELAY_MS;
|
|
22604
|
+
this.retryDelayMs = config.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
22605
|
+
this.requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
22606
|
+
this.stopTimeoutMs = config.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS;
|
|
22607
|
+
this.streamWindowMs = config.streamWindowMs ?? DEFAULT_STREAM_WINDOW_MS;
|
|
22608
|
+
this.createStreamingUpload = config.createStreamingUpload ?? createNodeStreamingUpload;
|
|
22609
|
+
}
|
|
22610
|
+
ingestUrl;
|
|
22611
|
+
maxBufferedEvents;
|
|
22612
|
+
maxStreamEvents;
|
|
22613
|
+
maxStreamBytes;
|
|
22614
|
+
maxEventBytes;
|
|
22615
|
+
flushDelayMs;
|
|
22616
|
+
retryDelayMs;
|
|
22617
|
+
requestTimeoutMs;
|
|
22618
|
+
stopTimeoutMs;
|
|
22619
|
+
streamWindowMs;
|
|
22620
|
+
createStreamingUpload;
|
|
22621
|
+
encoder = new TextEncoder();
|
|
22622
|
+
sequence = 0;
|
|
22623
|
+
lastKnownAcceptedSeq = 0;
|
|
22624
|
+
bufferedEvents = [];
|
|
22625
|
+
flushTimer = null;
|
|
22626
|
+
flushPromise = null;
|
|
22627
|
+
streamClosePromise = null;
|
|
22628
|
+
activeStream = null;
|
|
22629
|
+
stopPromise = null;
|
|
22630
|
+
stopped = false;
|
|
22631
|
+
sequenceSynced = false;
|
|
22632
|
+
sequenceInitialized = false;
|
|
22633
|
+
transportCompleted = false;
|
|
22634
|
+
droppedBeforeSequenceCount = 0;
|
|
22635
|
+
bufferRevision = 0;
|
|
22636
|
+
enqueue(event) {
|
|
22637
|
+
if (this.stopped) return;
|
|
22638
|
+
if (!this.canAcceptEvent(event)) {
|
|
22639
|
+
return;
|
|
22640
|
+
}
|
|
22641
|
+
const envelope = {
|
|
22642
|
+
seq: ++this.sequence,
|
|
22643
|
+
event
|
|
22644
|
+
};
|
|
22645
|
+
this.bufferedEvents.push(envelope);
|
|
22646
|
+
this.scheduleFlush();
|
|
22647
|
+
}
|
|
22648
|
+
async stop() {
|
|
22649
|
+
if (this.stopPromise) {
|
|
22650
|
+
await this.stopPromise;
|
|
22651
|
+
return;
|
|
22652
|
+
}
|
|
22653
|
+
this.stopped = true;
|
|
22654
|
+
if (this.flushTimer) {
|
|
22655
|
+
clearTimeout(this.flushTimer);
|
|
22656
|
+
this.flushTimer = null;
|
|
22657
|
+
}
|
|
22658
|
+
this.stopPromise = this.drainForStop();
|
|
22659
|
+
await this.stopPromise;
|
|
22660
|
+
}
|
|
22661
|
+
scheduleFlush(delayMs = this.flushDelayMs) {
|
|
22662
|
+
if (this.flushTimer || this.flushPromise || this.stopped) return;
|
|
22663
|
+
this.flushTimer = setTimeout(() => {
|
|
22664
|
+
this.flushTimer = null;
|
|
22665
|
+
void this.flush();
|
|
22666
|
+
}, delayMs);
|
|
22667
|
+
}
|
|
22668
|
+
async drainForStop() {
|
|
22669
|
+
const startedAtMs = Date.now();
|
|
22670
|
+
const deadlineAtMs = startedAtMs + this.stopTimeoutMs;
|
|
22671
|
+
while (!this.transportCompleted) {
|
|
22672
|
+
const previousLength = this.bufferedEvents.length;
|
|
22673
|
+
const previousRevision = this.bufferRevision;
|
|
22674
|
+
try {
|
|
22675
|
+
await this.flush();
|
|
22676
|
+
await this.writeCompletionLine();
|
|
22677
|
+
await this.closeActiveStream();
|
|
22678
|
+
this.transportCompleted = true;
|
|
22679
|
+
return;
|
|
22680
|
+
} catch (error) {
|
|
22681
|
+
this.config.logger.warn(
|
|
22682
|
+
"Task run event ingest stop request failed",
|
|
22683
|
+
this.describeError(error)
|
|
22684
|
+
);
|
|
22685
|
+
}
|
|
22686
|
+
const madeProgress = this.bufferedEvents.length < previousLength || this.bufferRevision !== previousRevision;
|
|
22687
|
+
if (!madeProgress && !await this.waitBeforeStopRetry(deadlineAtMs)) {
|
|
22688
|
+
this.warnStopDeadlineReached(startedAtMs);
|
|
22689
|
+
return;
|
|
22690
|
+
}
|
|
22691
|
+
if (Date.now() >= deadlineAtMs && !this.transportCompleted) {
|
|
22692
|
+
this.warnStopDeadlineReached(startedAtMs);
|
|
22693
|
+
return;
|
|
22694
|
+
}
|
|
22695
|
+
}
|
|
22696
|
+
}
|
|
22697
|
+
async flush() {
|
|
22698
|
+
if (this.flushPromise) {
|
|
22699
|
+
await this.flushPromise.catch(() => void 0);
|
|
22700
|
+
}
|
|
22701
|
+
if (this.bufferedEvents.length === 0) {
|
|
22702
|
+
return true;
|
|
22703
|
+
}
|
|
22704
|
+
const previousBufferLength = this.bufferedEvents.length;
|
|
22705
|
+
const flushPromise = this.flushBufferedEvents();
|
|
22706
|
+
this.flushPromise = flushPromise;
|
|
22707
|
+
try {
|
|
22708
|
+
await flushPromise;
|
|
22709
|
+
return this.bufferedEvents.length < previousBufferLength;
|
|
22710
|
+
} catch (error) {
|
|
22711
|
+
this.config.logger.warn(
|
|
22712
|
+
"Task run event ingest stream write failed",
|
|
22713
|
+
this.describeError(error)
|
|
22714
|
+
);
|
|
22715
|
+
await this.abortActiveStream();
|
|
22716
|
+
if (!this.stopped) {
|
|
22717
|
+
this.scheduleFlush(this.retryDelayMs);
|
|
22718
|
+
}
|
|
22719
|
+
return false;
|
|
22720
|
+
} finally {
|
|
22721
|
+
if (this.flushPromise === flushPromise) {
|
|
22722
|
+
this.flushPromise = null;
|
|
22723
|
+
}
|
|
22724
|
+
if (!this.stopped && this.hasUnwrittenBufferedEvents()) {
|
|
22725
|
+
this.scheduleFlush(0);
|
|
22726
|
+
}
|
|
22727
|
+
}
|
|
22728
|
+
}
|
|
22729
|
+
async flushBufferedEvents() {
|
|
22730
|
+
while (true) {
|
|
22731
|
+
const stream = await this.ensureActiveStream();
|
|
22732
|
+
const nextEvent = this.bufferedEvents.find(
|
|
22733
|
+
(event) => event.seq > stream.sentThroughSeq
|
|
22734
|
+
);
|
|
22735
|
+
if (!nextEvent) {
|
|
22736
|
+
return;
|
|
22737
|
+
}
|
|
22738
|
+
const line = `${this.serializeEnvelope(nextEvent)}
|
|
22739
|
+
`;
|
|
22740
|
+
const lineBytes = Buffer4.byteLength(line, "utf8");
|
|
22741
|
+
if (this.shouldRollStreamBeforeWriting(stream, lineBytes)) {
|
|
22742
|
+
await this.closeActiveStream();
|
|
22743
|
+
continue;
|
|
22744
|
+
}
|
|
22745
|
+
await stream.upload.write(this.encoder.encode(line));
|
|
22746
|
+
stream.sentThroughSeq = nextEvent.seq;
|
|
22747
|
+
stream.sentEvents += 1;
|
|
22748
|
+
stream.sentBytes += lineBytes;
|
|
22749
|
+
}
|
|
22750
|
+
}
|
|
22751
|
+
hasUnwrittenBufferedEvents() {
|
|
22752
|
+
const sentThroughSeq = this.activeStream?.sentThroughSeq ?? this.lastKnownAcceptedSeq;
|
|
22753
|
+
return this.bufferedEvents.some((event) => event.seq > sentThroughSeq);
|
|
22754
|
+
}
|
|
22755
|
+
async writeCompletionLine() {
|
|
22756
|
+
await this.syncSequenceWithServer();
|
|
22757
|
+
while (true) {
|
|
22758
|
+
const stream = await this.ensureActiveStream();
|
|
22759
|
+
const hasUnwrittenEvents = this.bufferedEvents.some(
|
|
22760
|
+
(event) => event.seq > stream.sentThroughSeq
|
|
22761
|
+
);
|
|
22762
|
+
if (hasUnwrittenEvents) {
|
|
22763
|
+
await this.flushBufferedEvents();
|
|
22764
|
+
continue;
|
|
22765
|
+
}
|
|
22766
|
+
const line = `${JSON.stringify({
|
|
22767
|
+
type: STREAM_COMPLETE_CONTROL_TYPE,
|
|
22768
|
+
final_seq: this.sequence
|
|
22769
|
+
})}
|
|
22770
|
+
`;
|
|
22771
|
+
const lineBytes = Buffer4.byteLength(line, "utf8");
|
|
22772
|
+
if (this.shouldRollStreamBeforeWriting(stream, lineBytes, {
|
|
22773
|
+
ignoreEventCount: true
|
|
22774
|
+
})) {
|
|
22775
|
+
await this.closeActiveStream();
|
|
22776
|
+
continue;
|
|
22777
|
+
}
|
|
22778
|
+
await stream.upload.write(this.encoder.encode(line));
|
|
22779
|
+
stream.sentBytes += lineBytes;
|
|
22780
|
+
return;
|
|
22781
|
+
}
|
|
22782
|
+
}
|
|
22783
|
+
shouldRollStreamBeforeWriting(stream, lineBytes, options = {}) {
|
|
22784
|
+
if (!options.ignoreEventCount && stream.sentEvents > 0 && stream.sentEvents >= this.maxStreamEvents) {
|
|
22785
|
+
return true;
|
|
22786
|
+
}
|
|
22787
|
+
if (stream.sentBytes > 0 && stream.sentBytes + lineBytes > this.maxStreamBytes) {
|
|
22788
|
+
return true;
|
|
22789
|
+
}
|
|
22790
|
+
return Date.now() - stream.startedAtMs >= this.streamWindowMs;
|
|
22791
|
+
}
|
|
22792
|
+
async ensureActiveStream() {
|
|
22793
|
+
if (this.streamClosePromise) {
|
|
22794
|
+
await this.streamClosePromise.catch(() => void 0);
|
|
22795
|
+
}
|
|
22796
|
+
if (this.activeStream) {
|
|
22797
|
+
return this.activeStream;
|
|
22798
|
+
}
|
|
22799
|
+
await this.syncSequenceWithServer();
|
|
22800
|
+
const abortController = new AbortController();
|
|
22801
|
+
const upload = this.createStreamingUpload({
|
|
22802
|
+
url: this.ingestUrl,
|
|
22803
|
+
headers: this.buildHeaders(),
|
|
22804
|
+
abortController
|
|
22805
|
+
});
|
|
22806
|
+
const activeStream = {
|
|
22807
|
+
abortController,
|
|
22808
|
+
upload,
|
|
22809
|
+
responsePromise: upload.responsePromise,
|
|
22810
|
+
startedAtMs: Date.now(),
|
|
22811
|
+
sentThroughSeq: this.lastKnownAcceptedSeq,
|
|
22812
|
+
sentEvents: 0,
|
|
22813
|
+
sentBytes: 0,
|
|
22814
|
+
windowTimer: null
|
|
22815
|
+
};
|
|
22816
|
+
this.activeStream = activeStream;
|
|
22817
|
+
this.scheduleStreamWindowClose(activeStream);
|
|
22818
|
+
upload.responsePromise.catch((error) => {
|
|
22819
|
+
void this.handleActiveStreamResponseFailure(activeStream, error);
|
|
22820
|
+
});
|
|
22821
|
+
return activeStream;
|
|
22822
|
+
}
|
|
22823
|
+
scheduleStreamWindowClose(stream, delayOverrideMs) {
|
|
22824
|
+
this.clearStreamWindowClose(stream);
|
|
22825
|
+
const delayMs = delayOverrideMs ?? Math.max(0, stream.startedAtMs + this.streamWindowMs - Date.now());
|
|
22826
|
+
stream.windowTimer = setTimeout(() => {
|
|
22827
|
+
stream.windowTimer = null;
|
|
22828
|
+
void this.closeExpiredStream(stream);
|
|
22829
|
+
}, delayMs);
|
|
22830
|
+
}
|
|
22831
|
+
clearStreamWindowClose(stream) {
|
|
22832
|
+
if (!stream.windowTimer) {
|
|
22833
|
+
return;
|
|
22834
|
+
}
|
|
22835
|
+
clearTimeout(stream.windowTimer);
|
|
22836
|
+
stream.windowTimer = null;
|
|
22837
|
+
}
|
|
22838
|
+
async closeExpiredStream(stream) {
|
|
22839
|
+
if (this.activeStream !== stream || this.stopped) {
|
|
22840
|
+
return;
|
|
22841
|
+
}
|
|
22842
|
+
if (this.flushPromise) {
|
|
22843
|
+
this.scheduleStreamWindowClose(stream, 50);
|
|
22844
|
+
return;
|
|
22845
|
+
}
|
|
22846
|
+
try {
|
|
22847
|
+
await this.closeActiveStream();
|
|
22848
|
+
} catch (error) {
|
|
22849
|
+
this.config.logger.warn(
|
|
22850
|
+
"Task run event ingest stream window close failed",
|
|
22851
|
+
this.describeError(error)
|
|
22852
|
+
);
|
|
22853
|
+
if (!this.stopped && this.bufferedEvents.length > 0) {
|
|
22854
|
+
this.scheduleFlush(this.retryDelayMs);
|
|
22855
|
+
}
|
|
22856
|
+
}
|
|
22857
|
+
}
|
|
22858
|
+
async handleActiveStreamResponseFailure(stream, error) {
|
|
22859
|
+
if (this.activeStream !== stream) {
|
|
22860
|
+
return;
|
|
22861
|
+
}
|
|
22862
|
+
this.config.logger.warn(
|
|
22863
|
+
"Task run event ingest stream request failed",
|
|
22864
|
+
this.describeError(error)
|
|
22865
|
+
);
|
|
22866
|
+
try {
|
|
22867
|
+
await this.abortActiveStream();
|
|
22868
|
+
} catch (abortError2) {
|
|
22869
|
+
this.config.logger.warn(
|
|
22870
|
+
"Task run event ingest stream abort failed",
|
|
22871
|
+
this.describeError(abortError2)
|
|
22872
|
+
);
|
|
22873
|
+
}
|
|
22874
|
+
if (!this.stopped && this.bufferedEvents.length > 0) {
|
|
22875
|
+
this.scheduleFlush(this.retryDelayMs);
|
|
22876
|
+
}
|
|
22877
|
+
}
|
|
22878
|
+
async closeActiveStream() {
|
|
22879
|
+
if (this.streamClosePromise) {
|
|
22880
|
+
await this.streamClosePromise;
|
|
22881
|
+
return;
|
|
22882
|
+
}
|
|
22883
|
+
const stream = this.activeStream;
|
|
22884
|
+
if (!stream) {
|
|
22885
|
+
return;
|
|
22886
|
+
}
|
|
22887
|
+
const closePromise = this.closeStream(stream);
|
|
22888
|
+
this.streamClosePromise = closePromise;
|
|
22889
|
+
try {
|
|
22890
|
+
await closePromise;
|
|
22891
|
+
} finally {
|
|
22892
|
+
this.clearStreamWindowClose(stream);
|
|
22893
|
+
if (this.activeStream === stream) {
|
|
22894
|
+
this.activeStream = null;
|
|
22895
|
+
}
|
|
22896
|
+
if (this.streamClosePromise === closePromise) {
|
|
22897
|
+
this.streamClosePromise = null;
|
|
22898
|
+
}
|
|
22899
|
+
}
|
|
22900
|
+
}
|
|
22901
|
+
async closeStream(stream) {
|
|
22902
|
+
try {
|
|
22903
|
+
await stream.upload.close();
|
|
22904
|
+
} catch (error) {
|
|
22905
|
+
stream.abortController.abort();
|
|
22906
|
+
this.sequenceSynced = false;
|
|
22907
|
+
throw error;
|
|
22908
|
+
}
|
|
22909
|
+
let response;
|
|
22910
|
+
try {
|
|
22911
|
+
response = await this.waitForResponseWithTimeout(
|
|
22912
|
+
stream.responsePromise,
|
|
22913
|
+
stream.abortController
|
|
22914
|
+
);
|
|
22915
|
+
} catch (error) {
|
|
22916
|
+
stream.abortController.abort();
|
|
22917
|
+
this.sequenceSynced = false;
|
|
22918
|
+
throw error;
|
|
22919
|
+
}
|
|
22920
|
+
await this.applyIngestResponse(response, "Event ingest stream");
|
|
22921
|
+
this.sequenceSynced = true;
|
|
22922
|
+
}
|
|
22923
|
+
async abortActiveStream() {
|
|
22924
|
+
const stream = this.activeStream;
|
|
22925
|
+
if (!stream) {
|
|
22926
|
+
return;
|
|
22927
|
+
}
|
|
22928
|
+
stream.abortController.abort();
|
|
22929
|
+
this.clearStreamWindowClose(stream);
|
|
22930
|
+
try {
|
|
22931
|
+
await stream.upload.abort();
|
|
22932
|
+
} catch {
|
|
22933
|
+
} finally {
|
|
22934
|
+
if (this.activeStream === stream) {
|
|
22935
|
+
this.activeStream = null;
|
|
22936
|
+
}
|
|
22937
|
+
this.sequenceSynced = false;
|
|
22938
|
+
}
|
|
22939
|
+
}
|
|
22940
|
+
async waitBeforeStopRetry(deadlineAtMs) {
|
|
22941
|
+
const remainingMs = deadlineAtMs - Date.now();
|
|
22942
|
+
if (remainingMs <= 0) {
|
|
22943
|
+
return false;
|
|
22944
|
+
}
|
|
22945
|
+
await new Promise(
|
|
22946
|
+
(resolve8) => setTimeout(resolve8, Math.min(this.retryDelayMs, remainingMs))
|
|
22947
|
+
);
|
|
22948
|
+
return Date.now() < deadlineAtMs;
|
|
22949
|
+
}
|
|
22950
|
+
warnStopDeadlineReached(startedAtMs) {
|
|
22951
|
+
this.config.logger.warn(
|
|
22952
|
+
"Task run event ingest stop deadline reached before fully completing transport",
|
|
22953
|
+
{
|
|
22954
|
+
remaining: this.bufferedEvents.length,
|
|
22955
|
+
stopTimeoutMs: this.stopTimeoutMs,
|
|
22956
|
+
elapsedMs: Date.now() - startedAtMs
|
|
22957
|
+
}
|
|
22958
|
+
);
|
|
22959
|
+
}
|
|
22960
|
+
async syncSequenceWithServer() {
|
|
22961
|
+
if (this.sequenceSynced) return;
|
|
22962
|
+
const response = await this.fetchWithTimeout({
|
|
22963
|
+
method: "POST",
|
|
22964
|
+
headers: this.buildHeaders(),
|
|
22965
|
+
body: ""
|
|
22966
|
+
});
|
|
22967
|
+
const responseBody = await this.parseResponse(response);
|
|
22968
|
+
if (!response.ok) {
|
|
22969
|
+
throw new Error(
|
|
22970
|
+
`Event ingest sequence sync returned HTTP ${response.status}: ${responseBody.text.slice(0, 300)}`
|
|
22971
|
+
);
|
|
22972
|
+
}
|
|
22973
|
+
const lastAcceptedSeq = responseBody.parsed?.last_accepted_seq;
|
|
22974
|
+
if (typeof lastAcceptedSeq === "number" && lastAcceptedSeq > 0) {
|
|
22975
|
+
if (!this.sequenceInitialized) {
|
|
22976
|
+
this.bufferedEvents = this.bufferedEvents.map((event) => ({
|
|
22977
|
+
...event,
|
|
22978
|
+
seq: event.seq + lastAcceptedSeq
|
|
22979
|
+
}));
|
|
22980
|
+
this.sequence += lastAcceptedSeq;
|
|
22981
|
+
this.bufferRevision += 1;
|
|
22982
|
+
} else {
|
|
22983
|
+
this.acceptThrough(lastAcceptedSeq);
|
|
22984
|
+
if (lastAcceptedSeq > this.sequence) {
|
|
22985
|
+
this.sequence = lastAcceptedSeq;
|
|
22986
|
+
}
|
|
22987
|
+
}
|
|
22988
|
+
this.lastKnownAcceptedSeq = lastAcceptedSeq;
|
|
22989
|
+
}
|
|
22990
|
+
this.sequenceSynced = true;
|
|
22991
|
+
this.sequenceInitialized = true;
|
|
22992
|
+
}
|
|
22993
|
+
async fetchWithTimeout(init2) {
|
|
22994
|
+
const abortController = new AbortController();
|
|
22995
|
+
const timeout = setTimeout(() => {
|
|
22996
|
+
abortController.abort();
|
|
22997
|
+
}, this.requestTimeoutMs);
|
|
22998
|
+
try {
|
|
22999
|
+
return await fetch(this.ingestUrl, {
|
|
23000
|
+
...init2,
|
|
23001
|
+
signal: abortController.signal
|
|
23002
|
+
});
|
|
23003
|
+
} finally {
|
|
23004
|
+
clearTimeout(timeout);
|
|
23005
|
+
}
|
|
23006
|
+
}
|
|
23007
|
+
async waitForResponseWithTimeout(responsePromise, abortController) {
|
|
23008
|
+
const timeout = setTimeout(() => {
|
|
23009
|
+
abortController.abort();
|
|
23010
|
+
}, this.requestTimeoutMs);
|
|
23011
|
+
try {
|
|
23012
|
+
return await responsePromise;
|
|
23013
|
+
} finally {
|
|
23014
|
+
clearTimeout(timeout);
|
|
23015
|
+
}
|
|
23016
|
+
}
|
|
23017
|
+
async applyIngestResponse(response, label) {
|
|
23018
|
+
const responseBody = await this.parseResponse(response);
|
|
23019
|
+
const lastAcceptedSeq = responseBody.parsed?.last_accepted_seq;
|
|
23020
|
+
if (typeof lastAcceptedSeq === "number") {
|
|
23021
|
+
this.acceptThrough(lastAcceptedSeq);
|
|
23022
|
+
if (lastAcceptedSeq > this.sequence) {
|
|
23023
|
+
this.sequence = lastAcceptedSeq;
|
|
23024
|
+
}
|
|
23025
|
+
this.lastKnownAcceptedSeq = lastAcceptedSeq;
|
|
23026
|
+
if (response.status === 409) {
|
|
23027
|
+
this.rebaseBufferedEvents(lastAcceptedSeq);
|
|
23028
|
+
}
|
|
23029
|
+
}
|
|
23030
|
+
if (!response.ok) {
|
|
23031
|
+
throw new Error(
|
|
23032
|
+
`${label} returned HTTP ${response.status}: ${responseBody.text.slice(0, 300)}`
|
|
23033
|
+
);
|
|
23034
|
+
}
|
|
23035
|
+
}
|
|
23036
|
+
acceptThrough(lastAcceptedSeq) {
|
|
23037
|
+
const previousLength = this.bufferedEvents.length;
|
|
23038
|
+
this.bufferedEvents = this.bufferedEvents.filter(
|
|
23039
|
+
(event) => event.seq > lastAcceptedSeq
|
|
23040
|
+
);
|
|
23041
|
+
if (this.bufferedEvents.length !== previousLength) {
|
|
23042
|
+
this.bufferRevision += 1;
|
|
23043
|
+
}
|
|
23044
|
+
}
|
|
23045
|
+
buildHeaders() {
|
|
23046
|
+
return {
|
|
23047
|
+
Authorization: `Bearer ${this.config.token}`,
|
|
23048
|
+
"Content-Type": "application/x-ndjson"
|
|
23049
|
+
};
|
|
23050
|
+
}
|
|
23051
|
+
rebaseBufferedEvents(lastAcceptedSeq) {
|
|
23052
|
+
let nextSeq = lastAcceptedSeq + 1;
|
|
23053
|
+
this.bufferedEvents = this.bufferedEvents.map((event) => ({
|
|
23054
|
+
...event,
|
|
23055
|
+
seq: nextSeq++
|
|
23056
|
+
}));
|
|
23057
|
+
this.sequence = nextSeq - 1;
|
|
23058
|
+
this.sequenceSynced = true;
|
|
23059
|
+
this.sequenceInitialized = true;
|
|
23060
|
+
this.lastKnownAcceptedSeq = lastAcceptedSeq;
|
|
23061
|
+
this.bufferRevision += 1;
|
|
23062
|
+
}
|
|
23063
|
+
async parseResponse(response) {
|
|
23064
|
+
const text2 = await response.text();
|
|
23065
|
+
if (!text2) {
|
|
23066
|
+
return { parsed: null, text: text2 };
|
|
23067
|
+
}
|
|
23068
|
+
try {
|
|
23069
|
+
return { parsed: JSON.parse(text2), text: text2 };
|
|
23070
|
+
} catch {
|
|
23071
|
+
return { parsed: null, text: text2 };
|
|
23072
|
+
}
|
|
23073
|
+
}
|
|
23074
|
+
canAcceptEvent(event) {
|
|
23075
|
+
const eventBytes = Buffer4.byteLength(
|
|
23076
|
+
this.serializeEnvelope({ seq: this.sequence + 1, event }),
|
|
23077
|
+
"utf8"
|
|
23078
|
+
);
|
|
23079
|
+
if (eventBytes > this.maxEventBytes) {
|
|
23080
|
+
this.config.logger.warn("Dropped oversized task run event", {
|
|
23081
|
+
eventBytes,
|
|
23082
|
+
maxEventBytes: this.maxEventBytes
|
|
23083
|
+
});
|
|
23084
|
+
return false;
|
|
23085
|
+
}
|
|
23086
|
+
if (this.bufferedEvents.length >= this.maxBufferedEvents) {
|
|
23087
|
+
this.droppedBeforeSequenceCount += 1;
|
|
23088
|
+
if (this.droppedBeforeSequenceCount === 1 || this.droppedBeforeSequenceCount % 100 === 0) {
|
|
23089
|
+
this.config.logger.warn(
|
|
23090
|
+
"Dropped task run event before assigning sequence due to backpressure",
|
|
23091
|
+
{
|
|
23092
|
+
dropped: this.droppedBeforeSequenceCount,
|
|
23093
|
+
maxBufferedEvents: this.maxBufferedEvents
|
|
23094
|
+
}
|
|
23095
|
+
);
|
|
23096
|
+
}
|
|
23097
|
+
return false;
|
|
23098
|
+
}
|
|
23099
|
+
if (this.droppedBeforeSequenceCount > 0) {
|
|
23100
|
+
this.config.logger.warn("Task run event ingest recovered after drops", {
|
|
23101
|
+
dropped: this.droppedBeforeSequenceCount
|
|
23102
|
+
});
|
|
23103
|
+
this.droppedBeforeSequenceCount = 0;
|
|
23104
|
+
}
|
|
23105
|
+
return true;
|
|
23106
|
+
}
|
|
23107
|
+
serializeEnvelope(envelope) {
|
|
23108
|
+
return JSON.stringify({ seq: envelope.seq, event: envelope.event });
|
|
23109
|
+
}
|
|
23110
|
+
describeError(error) {
|
|
23111
|
+
if (error instanceof Error) {
|
|
23112
|
+
return { message: error.message, stack: error.stack };
|
|
23113
|
+
}
|
|
23114
|
+
return error;
|
|
23115
|
+
}
|
|
23116
|
+
};
|
|
23117
|
+
|
|
22453
23118
|
// src/server/jwt.ts
|
|
22454
23119
|
import jwt from "jsonwebtoken";
|
|
22455
23120
|
import { z as z3 } from "zod";
|
|
@@ -22714,6 +23379,7 @@ var AgentServer = class {
|
|
|
22714
23379
|
session = null;
|
|
22715
23380
|
app;
|
|
22716
23381
|
posthogAPI;
|
|
23382
|
+
eventStreamSender = null;
|
|
22717
23383
|
questionRelayedToSlack = false;
|
|
22718
23384
|
detectedPrUrl = null;
|
|
22719
23385
|
lastReportedBranch = null;
|
|
@@ -22757,6 +23423,17 @@ var AgentServer = class {
|
|
|
22757
23423
|
getApiKey: () => config.apiKey,
|
|
22758
23424
|
userAgent: `posthog/cloud.hog.dev; version: ${config.version ?? package_default.version}`
|
|
22759
23425
|
});
|
|
23426
|
+
if (config.eventIngestToken) {
|
|
23427
|
+
this.eventStreamSender = new TaskRunEventStreamSender({
|
|
23428
|
+
apiUrl: config.apiUrl,
|
|
23429
|
+
projectId: config.projectId,
|
|
23430
|
+
taskId: config.taskId,
|
|
23431
|
+
runId: config.runId,
|
|
23432
|
+
token: config.eventIngestToken,
|
|
23433
|
+
logger: this.logger.child("EventIngest"),
|
|
23434
|
+
streamWindowMs: config.eventIngestStreamWindowMs
|
|
23435
|
+
});
|
|
23436
|
+
}
|
|
22760
23437
|
this.app = this.createApp();
|
|
22761
23438
|
}
|
|
22762
23439
|
getRuntimeAdapter() {
|
|
@@ -22974,7 +23651,9 @@ var AgentServer = class {
|
|
|
22974
23651
|
async stop() {
|
|
22975
23652
|
this.logger.debug("Stopping agent server...");
|
|
22976
23653
|
if (this.session) {
|
|
22977
|
-
await this.cleanupSession();
|
|
23654
|
+
await this.cleanupSession({ completeEventStream: true });
|
|
23655
|
+
} else {
|
|
23656
|
+
await this.eventStreamSender?.stop();
|
|
22978
23657
|
}
|
|
22979
23658
|
if (this.server) {
|
|
22980
23659
|
this.server.close();
|
|
@@ -23924,6 +24603,11 @@ ${signedCommitInstructions}
|
|
|
23924
24603
|
return;
|
|
23925
24604
|
}
|
|
23926
24605
|
const status = "failed";
|
|
24606
|
+
this.enqueueTaskTerminalEvent(POSTHOG_NOTIFICATIONS.ERROR, {
|
|
24607
|
+
source: "agent_server",
|
|
24608
|
+
stopReason,
|
|
24609
|
+
error: errorMessage2 ?? "Agent error"
|
|
24610
|
+
});
|
|
23927
24611
|
try {
|
|
23928
24612
|
await this.posthogAPI.updateTaskRun(payload.task_id, payload.run_id, {
|
|
23929
24613
|
status,
|
|
@@ -23932,8 +24616,21 @@ ${signedCommitInstructions}
|
|
|
23932
24616
|
this.logger.debug("Task completion signaled", { status, stopReason });
|
|
23933
24617
|
} catch (error) {
|
|
23934
24618
|
this.logger.error("Failed to signal task completion", error);
|
|
24619
|
+
} finally {
|
|
24620
|
+
await this.eventStreamSender?.stop();
|
|
23935
24621
|
}
|
|
23936
24622
|
}
|
|
24623
|
+
enqueueTaskTerminalEvent(method, params) {
|
|
24624
|
+
this.eventStreamSender?.enqueue({
|
|
24625
|
+
type: "notification",
|
|
24626
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24627
|
+
notification: {
|
|
24628
|
+
jsonrpc: "2.0",
|
|
24629
|
+
method,
|
|
24630
|
+
params
|
|
24631
|
+
}
|
|
24632
|
+
});
|
|
24633
|
+
}
|
|
23937
24634
|
configureEnvironment({
|
|
23938
24635
|
isInternal = false,
|
|
23939
24636
|
originProduct,
|
|
@@ -24200,7 +24897,9 @@ ${signedCommitInstructions}
|
|
|
24200
24897
|
});
|
|
24201
24898
|
}
|
|
24202
24899
|
}
|
|
24203
|
-
async cleanupSession(
|
|
24900
|
+
async cleanupSession({
|
|
24901
|
+
completeEventStream = false
|
|
24902
|
+
} = {}) {
|
|
24204
24903
|
if (!this.session) return;
|
|
24205
24904
|
this.logger.debug("Cleaning up session");
|
|
24206
24905
|
try {
|
|
@@ -24230,6 +24929,9 @@ ${signedCommitInstructions}
|
|
|
24230
24929
|
if (this.session.sseController) {
|
|
24231
24930
|
this.session.sseController.close();
|
|
24232
24931
|
}
|
|
24932
|
+
if (completeEventStream) {
|
|
24933
|
+
await this.eventStreamSender?.stop();
|
|
24934
|
+
}
|
|
24233
24935
|
this.pendingEvents = [];
|
|
24234
24936
|
this.lastReportedBranch = null;
|
|
24235
24937
|
this.session = null;
|
|
@@ -24297,9 +24999,11 @@ ${signedCommitInstructions}
|
|
|
24297
24999
|
);
|
|
24298
25000
|
}
|
|
24299
25001
|
broadcastEvent(event) {
|
|
25002
|
+
if (!this.session) return;
|
|
25003
|
+
this.eventStreamSender?.enqueue(event);
|
|
24300
25004
|
if (this.session?.sseController) {
|
|
24301
25005
|
this.sendSseEvent(this.session.sseController, event);
|
|
24302
|
-
} else
|
|
25006
|
+
} else {
|
|
24303
25007
|
this.pendingEvents.push(event);
|
|
24304
25008
|
}
|
|
24305
25009
|
}
|