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