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