@posthog/agent 2.3.657 → 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.
@@ -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 import_node_url2 = require("url");
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.657",
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, import_node_url2.pathToFileURL)(artifactPath).toString(), artifact.name, {
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 if (this.session) {
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,