@posthog/agent 2.1.115 → 2.1.120

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.
@@ -904,7 +904,7 @@ var import_hono = require("hono");
904
904
  // package.json
905
905
  var package_default = {
906
906
  name: "@posthog/agent",
907
- version: "2.1.115",
907
+ version: "2.1.120",
908
908
  repository: "https://github.com/PostHog/twig",
909
909
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
910
910
  exports: {
@@ -1520,6 +1520,10 @@ ${chunk.resource.text}
1520
1520
  function promptToClaude(prompt) {
1521
1521
  const content = [];
1522
1522
  const context = [];
1523
+ const prContext = prompt._meta?.prContext;
1524
+ if (typeof prContext === "string") {
1525
+ content.push(sdkText(prContext));
1526
+ }
1523
1527
  for (const chunk of prompt.prompt) {
1524
1528
  processPromptChunk(chunk, content, context);
1525
1529
  }
@@ -2966,9 +2970,10 @@ async function handleAskUserQuestionTool(context) {
2966
2970
  }
2967
2971
  });
2968
2972
  if (response.outcome?.outcome !== "selected") {
2973
+ const customMessage = response._meta?.message;
2969
2974
  return {
2970
2975
  behavior: "deny",
2971
- message: "User cancelled the questions",
2976
+ message: typeof customMessage === "string" ? customMessage : "User cancelled the questions",
2972
2977
  interrupt: true
2973
2978
  };
2974
2979
  }
@@ -3353,12 +3358,10 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3353
3358
  toolUseCache;
3354
3359
  backgroundTerminals = {};
3355
3360
  clientCapabilities;
3356
- logWriter;
3357
3361
  options;
3358
3362
  lastSentConfigOptions;
3359
- constructor(client, logWriter, options) {
3363
+ constructor(client, options) {
3360
3364
  super(client);
3361
- this.logWriter = logWriter;
3362
3365
  this.options = options;
3363
3366
  this.toolUseCache = {};
3364
3367
  this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
@@ -3403,7 +3406,14 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3403
3406
  async newSession(params) {
3404
3407
  this.checkAuthStatus();
3405
3408
  const meta = params._meta;
3409
+ const taskId = meta?.persistence?.taskId;
3406
3410
  const sessionId = (0, import_uuid.v7)();
3411
+ this.logger.info("Creating new session", {
3412
+ sessionId,
3413
+ taskId,
3414
+ taskRunId: meta?.taskRunId,
3415
+ cwd: params.cwd
3416
+ });
3407
3417
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3408
3418
  const mcpServers = parseMcpServers(params);
3409
3419
  const options = buildSessionOptions({
@@ -3432,7 +3442,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3432
3442
  options.abortController
3433
3443
  );
3434
3444
  session.taskRunId = meta?.taskRunId;
3435
- this.registerPersistence(sessionId, meta);
3436
3445
  if (meta?.taskRunId) {
3437
3446
  await this.client.extNotification("_posthog/sdk_session", {
3438
3447
  taskRunId: meta.taskRunId,
@@ -3458,6 +3467,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3458
3467
  }
3459
3468
  async resumeSession(params) {
3460
3469
  const meta = params._meta;
3470
+ const taskId = meta?.persistence?.taskId;
3461
3471
  const sessionId = meta?.sessionId;
3462
3472
  if (!sessionId) {
3463
3473
  throw new Error("Cannot resume session without sessionId");
@@ -3465,6 +3475,12 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3465
3475
  if (this.sessionId === sessionId) {
3466
3476
  return {};
3467
3477
  }
3478
+ this.logger.info("Resuming session", {
3479
+ sessionId,
3480
+ taskId,
3481
+ taskRunId: meta?.taskRunId,
3482
+ cwd: params.cwd
3483
+ });
3468
3484
  const mcpServers = parseMcpServers(params);
3469
3485
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3470
3486
  const { query: q, session } = await this.initializeQuery({
@@ -3477,15 +3493,36 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3477
3493
  isResume: true,
3478
3494
  additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
3479
3495
  });
3496
+ this.logger.info("Session query initialized, awaiting resumption", {
3497
+ sessionId,
3498
+ taskId,
3499
+ taskRunId: meta?.taskRunId
3500
+ });
3480
3501
  session.taskRunId = meta?.taskRunId;
3481
- this.registerPersistence(sessionId, meta);
3482
- const validation = await withTimeout(
3483
- q.initializationResult(),
3484
- SESSION_VALIDATION_TIMEOUT_MS
3485
- );
3486
- if (validation.result === "timeout") {
3487
- throw new Error("Session validation timed out");
3502
+ try {
3503
+ const result = await withTimeout(
3504
+ q.initializationResult(),
3505
+ SESSION_VALIDATION_TIMEOUT_MS
3506
+ );
3507
+ if (result.result === "timeout") {
3508
+ throw new Error(
3509
+ `Session resumption timed out for sessionId=${sessionId}`
3510
+ );
3511
+ }
3512
+ } catch (err) {
3513
+ this.logger.error("Session resumption failed", {
3514
+ sessionId,
3515
+ taskId,
3516
+ taskRunId: meta?.taskRunId,
3517
+ error: err instanceof Error ? err.message : String(err)
3518
+ });
3519
+ throw err;
3488
3520
  }
3521
+ this.logger.info("Session resumed successfully", {
3522
+ sessionId,
3523
+ taskId,
3524
+ taskRunId: meta?.taskRunId
3525
+ });
3489
3526
  this.deferBackgroundFetches(q, sessionId);
3490
3527
  const configOptions = await this.buildConfigOptions();
3491
3528
  return { configOptions };
@@ -3684,12 +3721,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3684
3721
  this.logger.warn("Failed to fetch deferred session data", { err });
3685
3722
  });
3686
3723
  }
3687
- registerPersistence(sessionId, meta) {
3688
- const persistence = meta?.persistence;
3689
- if (persistence && this.logWriter) {
3690
- this.logWriter.register(sessionId, persistence);
3691
- }
3692
- }
3693
3724
  sendAvailableCommandsUpdate(sessionId, availableCommands) {
3694
3725
  setTimeout(() => {
3695
3726
  this.client.sessionUpdate({
@@ -3977,7 +4008,7 @@ function createClaudeConnection(config) {
3977
4008
  const agentStream = (0, import_sdk3.ndJsonStream)(agentWritable, streams.agent.readable);
3978
4009
  let agent = null;
3979
4010
  const agentConnection = new import_sdk3.AgentSideConnection((client) => {
3980
- agent = new ClaudeAcpAgent(client, logWriter, config.processCallbacks);
4011
+ agent = new ClaudeAcpAgent(client, config.processCallbacks);
3981
4012
  logger.info(`Created ${agent.adapterName} agent`);
3982
4013
  return agent;
3983
4014
  }, agentStream);
@@ -4312,6 +4343,16 @@ var PostHogAPIClient = class {
4312
4343
  }
4313
4344
  );
4314
4345
  }
4346
+ async relayMessage(taskId, runId, text2) {
4347
+ const teamId = this.getTeamId();
4348
+ await this.apiRequest(
4349
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/relay_message/`,
4350
+ {
4351
+ method: "POST",
4352
+ body: JSON.stringify({ text: text2 })
4353
+ }
4354
+ );
4355
+ }
4315
4356
  async uploadTaskArtifacts(taskId, runId, artifacts) {
4316
4357
  if (!artifacts.length) {
4317
4358
  return [];
@@ -4417,11 +4458,15 @@ var SessionLogWriter = class _SessionLogWriter {
4417
4458
  }
4418
4459
  async flushAll() {
4419
4460
  const sessionIds = [...this.sessions.keys()];
4420
- const pendingCounts = sessionIds.map((id) => ({
4421
- id,
4422
- pending: this.pendingEntries.get(id)?.length ?? 0,
4423
- messages: this.messageCounts.get(id) ?? 0
4424
- }));
4461
+ const pendingCounts = sessionIds.map((id) => {
4462
+ const session = this.sessions.get(id);
4463
+ return {
4464
+ taskId: session?.context.taskId,
4465
+ runId: session?.context.runId,
4466
+ pending: this.pendingEntries.get(id)?.length ?? 0,
4467
+ messages: this.messageCounts.get(id) ?? 0
4468
+ };
4469
+ });
4425
4470
  this.logger.info("flushAll called", {
4426
4471
  sessions: sessionIds.length,
4427
4472
  pending: pendingCounts
@@ -4437,8 +4482,8 @@ var SessionLogWriter = class _SessionLogWriter {
4437
4482
  return;
4438
4483
  }
4439
4484
  this.logger.info("Session registered", {
4440
- sessionId,
4441
- taskId: context.taskId
4485
+ taskId: context.taskId,
4486
+ runId: context.runId
4442
4487
  });
4443
4488
  this.sessions.set(sessionId, { context });
4444
4489
  this.lastFlushAttemptTime.set(sessionId, Date.now());
@@ -4472,7 +4517,11 @@ var SessionLogWriter = class _SessionLogWriter {
4472
4517
  const count = (this.messageCounts.get(sessionId) ?? 0) + 1;
4473
4518
  this.messageCounts.set(sessionId, count);
4474
4519
  if (count % 10 === 1) {
4475
- this.logger.info("Messages received", { count, sessionId });
4520
+ this.logger.info("Messages received", {
4521
+ count,
4522
+ taskId: session.context.taskId,
4523
+ runId: session.context.runId
4524
+ });
4476
4525
  }
4477
4526
  try {
4478
4527
  const message = JSON.parse(line);
@@ -4489,6 +4538,10 @@ var SessionLogWriter = class _SessionLogWriter {
4489
4538
  return;
4490
4539
  }
4491
4540
  this.emitCoalescedMessage(sessionId, session);
4541
+ const nonChunkAgentText = this.extractAgentMessageText(message);
4542
+ if (nonChunkAgentText) {
4543
+ session.lastAgentMessage = nonChunkAgentText;
4544
+ }
4492
4545
  const entry = {
4493
4546
  type: "notification",
4494
4547
  timestamp,
@@ -4503,7 +4556,8 @@ var SessionLogWriter = class _SessionLogWriter {
4503
4556
  }
4504
4557
  } catch {
4505
4558
  this.logger.warn("Failed to parse raw line for persistence", {
4506
- sessionId,
4559
+ taskId: session.context.taskId,
4560
+ runId: session.context.runId,
4507
4561
  lineLength: line.length
4508
4562
  });
4509
4563
  }
@@ -4518,7 +4572,8 @@ var SessionLogWriter = class _SessionLogWriter {
4518
4572
  const pending = this.pendingEntries.get(sessionId);
4519
4573
  if (!this.posthogAPI || !pending?.length) {
4520
4574
  this.logger.info("flush: nothing to persist", {
4521
- sessionId,
4575
+ taskId: session.context.taskId,
4576
+ runId: session.context.runId,
4522
4577
  hasPosthogAPI: !!this.posthogAPI,
4523
4578
  pendingCount: pending?.length ?? 0
4524
4579
  });
@@ -4539,7 +4594,8 @@ var SessionLogWriter = class _SessionLogWriter {
4539
4594
  );
4540
4595
  this.retryCounts.set(sessionId, 0);
4541
4596
  this.logger.info("Flushed session logs", {
4542
- sessionId,
4597
+ taskId: session.context.taskId,
4598
+ runId: session.context.runId,
4543
4599
  entryCount: pending.length
4544
4600
  });
4545
4601
  } catch (error) {
@@ -4548,7 +4604,11 @@ var SessionLogWriter = class _SessionLogWriter {
4548
4604
  if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
4549
4605
  this.logger.error(
4550
4606
  `Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
4551
- { sessionId, error }
4607
+ {
4608
+ taskId: session.context.taskId,
4609
+ runId: session.context.runId,
4610
+ error
4611
+ }
4552
4612
  );
4553
4613
  this.retryCounts.set(sessionId, 0);
4554
4614
  } else {
@@ -4581,6 +4641,7 @@ var SessionLogWriter = class _SessionLogWriter {
4581
4641
  if (!session.chunkBuffer) return;
4582
4642
  const { text: text2, firstTimestamp } = session.chunkBuffer;
4583
4643
  session.chunkBuffer = void 0;
4644
+ session.lastAgentMessage = text2;
4584
4645
  const entry = {
4585
4646
  type: "notification",
4586
4647
  timestamp: firstTimestamp,
@@ -4603,6 +4664,29 @@ var SessionLogWriter = class _SessionLogWriter {
4603
4664
  this.scheduleFlush(sessionId);
4604
4665
  }
4605
4666
  }
4667
+ getLastAgentMessage(sessionId) {
4668
+ return this.sessions.get(sessionId)?.lastAgentMessage;
4669
+ }
4670
+ extractAgentMessageText(message) {
4671
+ if (message.method !== "session/update") {
4672
+ return null;
4673
+ }
4674
+ const params = message.params;
4675
+ const update = params?.update;
4676
+ if (update?.sessionUpdate !== "agent_message") {
4677
+ return null;
4678
+ }
4679
+ const content = update.content;
4680
+ if (content?.type === "text" && typeof content.text === "string") {
4681
+ const trimmed2 = content.text.trim();
4682
+ return trimmed2.length > 0 ? trimmed2 : null;
4683
+ }
4684
+ if (typeof update.message === "string") {
4685
+ const trimmed2 = update.message.trim();
4686
+ return trimmed2.length > 0 ? trimmed2 : null;
4687
+ }
4688
+ return null;
4689
+ }
4606
4690
  scheduleFlush(sessionId) {
4607
4691
  const existing = this.flushTimeouts.get(sessionId);
4608
4692
  if (existing) clearTimeout(existing);
@@ -4637,7 +4721,12 @@ var SessionLogWriter = class _SessionLogWriter {
4637
4721
  import_node_fs2.default.appendFileSync(logPath, `${JSON.stringify(entry)}
4638
4722
  `);
4639
4723
  } catch (error) {
4640
- this.logger.warn("Failed to write to local cache", { logPath, error });
4724
+ this.logger.warn("Failed to write to local cache", {
4725
+ taskId: session.context.taskId,
4726
+ runId: session.context.runId,
4727
+ logPath,
4728
+ error
4729
+ });
4641
4730
  }
4642
4731
  }
4643
4732
  };
@@ -10270,6 +10359,8 @@ var AgentServer = class {
10270
10359
  session = null;
10271
10360
  app;
10272
10361
  posthogAPI;
10362
+ questionRelayedToSlack = false;
10363
+ detectedPrUrl = null;
10273
10364
  constructor(config) {
10274
10365
  this.config = config;
10275
10366
  this.logger = new Logger({ debug: true, prefix: "[AgentServer]" });
@@ -10482,11 +10573,21 @@ var AgentServer = class {
10482
10573
  case "user_message": {
10483
10574
  const content = params.content;
10484
10575
  this.logger.info(
10485
- `Processing user message: ${content.substring(0, 100)}...`
10576
+ `Processing user message (detectedPrUrl=${this.detectedPrUrl ?? "none"}): ${content.substring(0, 100)}...`
10486
10577
  );
10487
10578
  const result = await this.session.clientConnection.prompt({
10488
10579
  sessionId: this.session.acpSessionId,
10489
- prompt: [{ type: "text", text: content }]
10580
+ prompt: [{ type: "text", text: content }],
10581
+ ...this.detectedPrUrl && {
10582
+ _meta: {
10583
+ prContext: `IMPORTANT \u2014 OVERRIDE PREVIOUS INSTRUCTIONS ABOUT CREATING BRANCHES/PRs.
10584
+ You already have an open pull request: ${this.detectedPrUrl}
10585
+ You MUST:
10586
+ 1. Check out the existing PR branch with \`gh pr checkout ${this.detectedPrUrl}\`
10587
+ 2. Make changes, commit, and push to that branch
10588
+ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
10589
+ }
10590
+ }
10490
10591
  });
10491
10592
  return { stopReason: result.stopReason };
10492
10593
  }
@@ -10571,13 +10672,29 @@ var AgentServer = class {
10571
10672
  protocolVersion: import_sdk4.PROTOCOL_VERSION,
10572
10673
  clientCapabilities: {}
10573
10674
  });
10675
+ let preTaskRun = null;
10676
+ try {
10677
+ preTaskRun = await this.posthogAPI.getTaskRun(
10678
+ payload.task_id,
10679
+ payload.run_id
10680
+ );
10681
+ } catch {
10682
+ this.logger.warn("Failed to fetch task run for session context", {
10683
+ taskId: payload.task_id,
10684
+ runId: payload.run_id
10685
+ });
10686
+ }
10687
+ const prUrl = typeof preTaskRun?.state?.slack_notified_pr_url === "string" ? (preTaskRun?.state).slack_notified_pr_url : null;
10688
+ if (prUrl) {
10689
+ this.detectedPrUrl = prUrl;
10690
+ }
10574
10691
  const sessionResponse = await clientConnection.newSession({
10575
10692
  cwd: this.config.repositoryPath,
10576
10693
  mcpServers: [],
10577
10694
  _meta: {
10578
10695
  sessionId: payload.run_id,
10579
10696
  taskRunId: payload.run_id,
10580
- systemPrompt: { append: this.buildCloudSystemPrompt() }
10697
+ systemPrompt: { append: this.buildCloudSystemPrompt(prUrl) }
10581
10698
  }
10582
10699
  });
10583
10700
  const acpSessionId = sessionResponse.sessionId;
@@ -10601,28 +10718,51 @@ var AgentServer = class {
10601
10718
  }).catch(
10602
10719
  (err) => this.logger.warn("Failed to set task run to in_progress", err)
10603
10720
  );
10604
- await this.sendInitialTaskMessage(payload);
10721
+ await this.sendInitialTaskMessage(payload, preTaskRun);
10605
10722
  }
10606
- async sendInitialTaskMessage(payload) {
10723
+ async sendInitialTaskMessage(payload, prefetchedRun) {
10607
10724
  if (!this.session) return;
10608
10725
  try {
10609
- this.logger.info("Fetching task details", { taskId: payload.task_id });
10610
10726
  const task = await this.posthogAPI.getTask(payload.task_id);
10611
- if (!task.description) {
10727
+ let taskRun = prefetchedRun ?? null;
10728
+ if (!taskRun) {
10729
+ try {
10730
+ taskRun = await this.posthogAPI.getTaskRun(
10731
+ payload.task_id,
10732
+ payload.run_id
10733
+ );
10734
+ } catch (error) {
10735
+ this.logger.warn(
10736
+ "Failed to fetch task run for initial prompt override",
10737
+ {
10738
+ taskId: payload.task_id,
10739
+ runId: payload.run_id,
10740
+ error
10741
+ }
10742
+ );
10743
+ }
10744
+ }
10745
+ const initialPromptOverride = taskRun ? this.getInitialPromptOverride(taskRun) : null;
10746
+ const initialPrompt = initialPromptOverride ?? task.description;
10747
+ if (!initialPrompt) {
10612
10748
  this.logger.warn("Task has no description, skipping initial message");
10613
10749
  return;
10614
10750
  }
10615
10751
  this.logger.info("Sending initial task message", {
10616
10752
  taskId: payload.task_id,
10617
- descriptionLength: task.description.length
10753
+ descriptionLength: initialPrompt.length,
10754
+ usedInitialPromptOverride: !!initialPromptOverride
10618
10755
  });
10619
10756
  const result = await this.session.clientConnection.prompt({
10620
10757
  sessionId: this.session.acpSessionId,
10621
- prompt: [{ type: "text", text: task.description }]
10758
+ prompt: [{ type: "text", text: initialPrompt }]
10622
10759
  });
10623
10760
  this.logger.info("Initial task message completed", {
10624
10761
  stopReason: result.stopReason
10625
10762
  });
10763
+ if (result.stopReason === "end_turn") {
10764
+ await this.relayAgentResponse(payload);
10765
+ }
10626
10766
  } catch (error) {
10627
10767
  this.logger.error("Failed to send initial task message", error);
10628
10768
  if (this.session) {
@@ -10631,7 +10771,33 @@ var AgentServer = class {
10631
10771
  await this.signalTaskComplete(payload, "error");
10632
10772
  }
10633
10773
  }
10634
- buildCloudSystemPrompt() {
10774
+ getInitialPromptOverride(taskRun) {
10775
+ const state = taskRun.state;
10776
+ const override = state?.initial_prompt_override;
10777
+ if (typeof override !== "string") {
10778
+ return null;
10779
+ }
10780
+ const trimmed2 = override.trim();
10781
+ return trimmed2.length > 0 ? trimmed2 : null;
10782
+ }
10783
+ buildCloudSystemPrompt(prUrl) {
10784
+ if (prUrl) {
10785
+ return `
10786
+ # Cloud Task Execution
10787
+
10788
+ This task already has an open pull request: ${prUrl}
10789
+
10790
+ After completing the requested changes:
10791
+ 1. Check out the existing PR branch with \`gh pr checkout ${prUrl}\`
10792
+ 2. Stage and commit all changes with a clear commit message
10793
+ 3. Push to the existing PR branch
10794
+
10795
+ Important:
10796
+ - Do NOT create a new branch or a new pull request.
10797
+ - Do NOT add "Co-Authored-By" trailers to commit messages.
10798
+ - Do NOT add "Generated with [Claude Code]" or similar attribution lines to PR descriptions.
10799
+ `;
10800
+ }
10635
10801
  return `
10636
10802
  # Cloud Task Execution
10637
10803
 
@@ -10701,19 +10867,34 @@ Important:
10701
10867
  }
10702
10868
  createCloudClient(payload) {
10703
10869
  const mode = this.getEffectiveMode(payload);
10870
+ const interactionOrigin = process.env.TWIG_INTERACTION_ORIGIN;
10704
10871
  return {
10705
10872
  requestPermission: async (params) => {
10706
10873
  this.logger.debug("Permission request", {
10707
10874
  mode,
10875
+ interactionOrigin,
10708
10876
  options: params.options
10709
10877
  });
10710
10878
  const allowOption = params.options.find(
10711
10879
  (o) => o.kind === "allow_once" || o.kind === "allow_always"
10712
10880
  );
10881
+ const selectedOptionId = allowOption?.optionId ?? params.options[0].optionId;
10882
+ if (interactionOrigin === "slack") {
10883
+ const twigToolKind = params.toolCall?._meta?.twigToolKind;
10884
+ if (twigToolKind === "question") {
10885
+ this.relaySlackQuestion(payload, params.toolCall?._meta);
10886
+ return {
10887
+ outcome: { outcome: "cancelled" },
10888
+ _meta: {
10889
+ message: "This question has been relayed to the Slack thread where this task originated. The user will reply there. Do NOT re-ask the question or pick an answer yourself. Simply let the user know you are waiting for their reply."
10890
+ }
10891
+ };
10892
+ }
10893
+ }
10713
10894
  return {
10714
10895
  outcome: {
10715
10896
  outcome: "selected",
10716
- optionId: allowOption?.optionId ?? params.options[0].optionId
10897
+ optionId: selectedOptionId
10717
10898
  }
10718
10899
  };
10719
10900
  },
@@ -10732,6 +10913,97 @@ Important:
10732
10913
  }
10733
10914
  };
10734
10915
  }
10916
+ async relayAgentResponse(payload) {
10917
+ if (!this.session) {
10918
+ return;
10919
+ }
10920
+ if (this.questionRelayedToSlack) {
10921
+ this.questionRelayedToSlack = false;
10922
+ return;
10923
+ }
10924
+ try {
10925
+ await this.session.logWriter.flush(payload.run_id);
10926
+ } catch (error) {
10927
+ this.logger.warn("Failed to flush logs before Slack relay", {
10928
+ taskId: payload.task_id,
10929
+ runId: payload.run_id,
10930
+ error
10931
+ });
10932
+ }
10933
+ const message = this.session.logWriter.getLastAgentMessage(payload.run_id);
10934
+ if (!message) {
10935
+ this.logger.warn("No agent message found for Slack relay", {
10936
+ taskId: payload.task_id,
10937
+ runId: payload.run_id,
10938
+ sessionRegistered: this.session.logWriter.isRegistered(payload.run_id)
10939
+ });
10940
+ return;
10941
+ }
10942
+ try {
10943
+ await this.posthogAPI.relayMessage(
10944
+ payload.task_id,
10945
+ payload.run_id,
10946
+ message
10947
+ );
10948
+ } catch (error) {
10949
+ this.logger.warn("Failed to relay initial agent response to Slack", {
10950
+ taskId: payload.task_id,
10951
+ runId: payload.run_id,
10952
+ error
10953
+ });
10954
+ }
10955
+ }
10956
+ relaySlackQuestion(payload, toolMeta2) {
10957
+ const firstQuestion = this.getFirstQuestionMeta(toolMeta2);
10958
+ if (!this.isQuestionMeta(firstQuestion)) {
10959
+ return;
10960
+ }
10961
+ let message = `*${firstQuestion.question}*
10962
+
10963
+ `;
10964
+ if (firstQuestion.options?.length) {
10965
+ firstQuestion.options.forEach(
10966
+ (opt, i) => {
10967
+ message += `${i + 1}. *${opt.label}*`;
10968
+ if (opt.description) message += ` \u2014 ${opt.description}`;
10969
+ message += "\n";
10970
+ }
10971
+ );
10972
+ }
10973
+ message += "\nReply in this thread with your choice.";
10974
+ this.questionRelayedToSlack = true;
10975
+ this.posthogAPI.relayMessage(payload.task_id, payload.run_id, message).catch(
10976
+ (err) => this.logger.warn("Failed to relay question to Slack", { err })
10977
+ );
10978
+ }
10979
+ getFirstQuestionMeta(toolMeta2) {
10980
+ if (!toolMeta2) {
10981
+ return null;
10982
+ }
10983
+ const questionsValue = toolMeta2.questions;
10984
+ if (!Array.isArray(questionsValue) || questionsValue.length === 0) {
10985
+ return null;
10986
+ }
10987
+ return questionsValue[0];
10988
+ }
10989
+ isQuestionMeta(value) {
10990
+ if (!value || typeof value !== "object") {
10991
+ return false;
10992
+ }
10993
+ const candidate = value;
10994
+ if (typeof candidate.question !== "string") {
10995
+ return false;
10996
+ }
10997
+ if (candidate.options === void 0) {
10998
+ return true;
10999
+ }
11000
+ if (!Array.isArray(candidate.options)) {
11001
+ return false;
11002
+ }
11003
+ return candidate.options.every(
11004
+ (option) => !!option && typeof option === "object" && typeof option.label === "string"
11005
+ );
11006
+ }
10735
11007
  detectAndAttachPrUrl(payload, update) {
10736
11008
  try {
10737
11009
  const meta = update?._meta?.claudeCode;
@@ -10762,6 +11034,7 @@ Important:
10762
11034
  );
10763
11035
  if (!prUrlMatch) return;
10764
11036
  const prUrl = prUrlMatch[0];
11037
+ this.detectedPrUrl = prUrl;
10765
11038
  this.logger.info("Detected PR URL in bash output", {
10766
11039
  runId: payload.run_id,
10767
11040
  prUrl