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