@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.
@@ -908,7 +908,7 @@ import { Hono } from "hono";
908
908
  // package.json
909
909
  var package_default = {
910
910
  name: "@posthog/agent",
911
- version: "2.1.115",
911
+ version: "2.1.120",
912
912
  repository: "https://github.com/PostHog/twig",
913
913
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
914
914
  exports: {
@@ -1528,6 +1528,10 @@ ${chunk.resource.text}
1528
1528
  function promptToClaude(prompt) {
1529
1529
  const content = [];
1530
1530
  const context = [];
1531
+ const prContext = prompt._meta?.prContext;
1532
+ if (typeof prContext === "string") {
1533
+ content.push(sdkText(prContext));
1534
+ }
1531
1535
  for (const chunk of prompt.prompt) {
1532
1536
  processPromptChunk(chunk, content, context);
1533
1537
  }
@@ -2974,9 +2978,10 @@ async function handleAskUserQuestionTool(context) {
2974
2978
  }
2975
2979
  });
2976
2980
  if (response.outcome?.outcome !== "selected") {
2981
+ const customMessage = response._meta?.message;
2977
2982
  return {
2978
2983
  behavior: "deny",
2979
- message: "User cancelled the questions",
2984
+ message: typeof customMessage === "string" ? customMessage : "User cancelled the questions",
2980
2985
  interrupt: true
2981
2986
  };
2982
2987
  }
@@ -3361,12 +3366,10 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3361
3366
  toolUseCache;
3362
3367
  backgroundTerminals = {};
3363
3368
  clientCapabilities;
3364
- logWriter;
3365
3369
  options;
3366
3370
  lastSentConfigOptions;
3367
- constructor(client, logWriter, options) {
3371
+ constructor(client, options) {
3368
3372
  super(client);
3369
- this.logWriter = logWriter;
3370
3373
  this.options = options;
3371
3374
  this.toolUseCache = {};
3372
3375
  this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
@@ -3411,7 +3414,14 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3411
3414
  async newSession(params) {
3412
3415
  this.checkAuthStatus();
3413
3416
  const meta = params._meta;
3417
+ const taskId = meta?.persistence?.taskId;
3414
3418
  const sessionId = uuidv7();
3419
+ this.logger.info("Creating new session", {
3420
+ sessionId,
3421
+ taskId,
3422
+ taskRunId: meta?.taskRunId,
3423
+ cwd: params.cwd
3424
+ });
3415
3425
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3416
3426
  const mcpServers = parseMcpServers(params);
3417
3427
  const options = buildSessionOptions({
@@ -3440,7 +3450,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3440
3450
  options.abortController
3441
3451
  );
3442
3452
  session.taskRunId = meta?.taskRunId;
3443
- this.registerPersistence(sessionId, meta);
3444
3453
  if (meta?.taskRunId) {
3445
3454
  await this.client.extNotification("_posthog/sdk_session", {
3446
3455
  taskRunId: meta.taskRunId,
@@ -3466,6 +3475,7 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3466
3475
  }
3467
3476
  async resumeSession(params) {
3468
3477
  const meta = params._meta;
3478
+ const taskId = meta?.persistence?.taskId;
3469
3479
  const sessionId = meta?.sessionId;
3470
3480
  if (!sessionId) {
3471
3481
  throw new Error("Cannot resume session without sessionId");
@@ -3473,6 +3483,12 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3473
3483
  if (this.sessionId === sessionId) {
3474
3484
  return {};
3475
3485
  }
3486
+ this.logger.info("Resuming session", {
3487
+ sessionId,
3488
+ taskId,
3489
+ taskRunId: meta?.taskRunId,
3490
+ cwd: params.cwd
3491
+ });
3476
3492
  const mcpServers = parseMcpServers(params);
3477
3493
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3478
3494
  const { query: q, session } = await this.initializeQuery({
@@ -3485,15 +3501,36 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3485
3501
  isResume: true,
3486
3502
  additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
3487
3503
  });
3504
+ this.logger.info("Session query initialized, awaiting resumption", {
3505
+ sessionId,
3506
+ taskId,
3507
+ taskRunId: meta?.taskRunId
3508
+ });
3488
3509
  session.taskRunId = meta?.taskRunId;
3489
- this.registerPersistence(sessionId, meta);
3490
- const validation = await withTimeout(
3491
- q.initializationResult(),
3492
- SESSION_VALIDATION_TIMEOUT_MS
3493
- );
3494
- if (validation.result === "timeout") {
3495
- throw new Error("Session validation timed out");
3510
+ try {
3511
+ const result = await withTimeout(
3512
+ q.initializationResult(),
3513
+ SESSION_VALIDATION_TIMEOUT_MS
3514
+ );
3515
+ if (result.result === "timeout") {
3516
+ throw new Error(
3517
+ `Session resumption timed out for sessionId=${sessionId}`
3518
+ );
3519
+ }
3520
+ } catch (err) {
3521
+ this.logger.error("Session resumption failed", {
3522
+ sessionId,
3523
+ taskId,
3524
+ taskRunId: meta?.taskRunId,
3525
+ error: err instanceof Error ? err.message : String(err)
3526
+ });
3527
+ throw err;
3496
3528
  }
3529
+ this.logger.info("Session resumed successfully", {
3530
+ sessionId,
3531
+ taskId,
3532
+ taskRunId: meta?.taskRunId
3533
+ });
3497
3534
  this.deferBackgroundFetches(q, sessionId);
3498
3535
  const configOptions = await this.buildConfigOptions();
3499
3536
  return { configOptions };
@@ -3692,12 +3729,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3692
3729
  this.logger.warn("Failed to fetch deferred session data", { err });
3693
3730
  });
3694
3731
  }
3695
- registerPersistence(sessionId, meta) {
3696
- const persistence = meta?.persistence;
3697
- if (persistence && this.logWriter) {
3698
- this.logWriter.register(sessionId, persistence);
3699
- }
3700
- }
3701
3732
  sendAvailableCommandsUpdate(sessionId, availableCommands) {
3702
3733
  setTimeout(() => {
3703
3734
  this.client.sessionUpdate({
@@ -3985,7 +4016,7 @@ function createClaudeConnection(config) {
3985
4016
  const agentStream = ndJsonStream(agentWritable, streams.agent.readable);
3986
4017
  let agent = null;
3987
4018
  const agentConnection = new AgentSideConnection((client) => {
3988
- agent = new ClaudeAcpAgent(client, logWriter, config.processCallbacks);
4019
+ agent = new ClaudeAcpAgent(client, config.processCallbacks);
3989
4020
  logger.info(`Created ${agent.adapterName} agent`);
3990
4021
  return agent;
3991
4022
  }, agentStream);
@@ -4320,6 +4351,16 @@ var PostHogAPIClient = class {
4320
4351
  }
4321
4352
  );
4322
4353
  }
4354
+ async relayMessage(taskId, runId, text2) {
4355
+ const teamId = this.getTeamId();
4356
+ await this.apiRequest(
4357
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/relay_message/`,
4358
+ {
4359
+ method: "POST",
4360
+ body: JSON.stringify({ text: text2 })
4361
+ }
4362
+ );
4363
+ }
4323
4364
  async uploadTaskArtifacts(taskId, runId, artifacts) {
4324
4365
  if (!artifacts.length) {
4325
4366
  return [];
@@ -4425,11 +4466,15 @@ var SessionLogWriter = class _SessionLogWriter {
4425
4466
  }
4426
4467
  async flushAll() {
4427
4468
  const sessionIds = [...this.sessions.keys()];
4428
- const pendingCounts = sessionIds.map((id) => ({
4429
- id,
4430
- pending: this.pendingEntries.get(id)?.length ?? 0,
4431
- messages: this.messageCounts.get(id) ?? 0
4432
- }));
4469
+ const pendingCounts = sessionIds.map((id) => {
4470
+ const session = this.sessions.get(id);
4471
+ return {
4472
+ taskId: session?.context.taskId,
4473
+ runId: session?.context.runId,
4474
+ pending: this.pendingEntries.get(id)?.length ?? 0,
4475
+ messages: this.messageCounts.get(id) ?? 0
4476
+ };
4477
+ });
4433
4478
  this.logger.info("flushAll called", {
4434
4479
  sessions: sessionIds.length,
4435
4480
  pending: pendingCounts
@@ -4445,8 +4490,8 @@ var SessionLogWriter = class _SessionLogWriter {
4445
4490
  return;
4446
4491
  }
4447
4492
  this.logger.info("Session registered", {
4448
- sessionId,
4449
- taskId: context.taskId
4493
+ taskId: context.taskId,
4494
+ runId: context.runId
4450
4495
  });
4451
4496
  this.sessions.set(sessionId, { context });
4452
4497
  this.lastFlushAttemptTime.set(sessionId, Date.now());
@@ -4480,7 +4525,11 @@ var SessionLogWriter = class _SessionLogWriter {
4480
4525
  const count = (this.messageCounts.get(sessionId) ?? 0) + 1;
4481
4526
  this.messageCounts.set(sessionId, count);
4482
4527
  if (count % 10 === 1) {
4483
- this.logger.info("Messages received", { count, sessionId });
4528
+ this.logger.info("Messages received", {
4529
+ count,
4530
+ taskId: session.context.taskId,
4531
+ runId: session.context.runId
4532
+ });
4484
4533
  }
4485
4534
  try {
4486
4535
  const message = JSON.parse(line);
@@ -4497,6 +4546,10 @@ var SessionLogWriter = class _SessionLogWriter {
4497
4546
  return;
4498
4547
  }
4499
4548
  this.emitCoalescedMessage(sessionId, session);
4549
+ const nonChunkAgentText = this.extractAgentMessageText(message);
4550
+ if (nonChunkAgentText) {
4551
+ session.lastAgentMessage = nonChunkAgentText;
4552
+ }
4500
4553
  const entry = {
4501
4554
  type: "notification",
4502
4555
  timestamp,
@@ -4511,7 +4564,8 @@ var SessionLogWriter = class _SessionLogWriter {
4511
4564
  }
4512
4565
  } catch {
4513
4566
  this.logger.warn("Failed to parse raw line for persistence", {
4514
- sessionId,
4567
+ taskId: session.context.taskId,
4568
+ runId: session.context.runId,
4515
4569
  lineLength: line.length
4516
4570
  });
4517
4571
  }
@@ -4526,7 +4580,8 @@ var SessionLogWriter = class _SessionLogWriter {
4526
4580
  const pending = this.pendingEntries.get(sessionId);
4527
4581
  if (!this.posthogAPI || !pending?.length) {
4528
4582
  this.logger.info("flush: nothing to persist", {
4529
- sessionId,
4583
+ taskId: session.context.taskId,
4584
+ runId: session.context.runId,
4530
4585
  hasPosthogAPI: !!this.posthogAPI,
4531
4586
  pendingCount: pending?.length ?? 0
4532
4587
  });
@@ -4547,7 +4602,8 @@ var SessionLogWriter = class _SessionLogWriter {
4547
4602
  );
4548
4603
  this.retryCounts.set(sessionId, 0);
4549
4604
  this.logger.info("Flushed session logs", {
4550
- sessionId,
4605
+ taskId: session.context.taskId,
4606
+ runId: session.context.runId,
4551
4607
  entryCount: pending.length
4552
4608
  });
4553
4609
  } catch (error) {
@@ -4556,7 +4612,11 @@ var SessionLogWriter = class _SessionLogWriter {
4556
4612
  if (retryCount >= _SessionLogWriter.MAX_FLUSH_RETRIES) {
4557
4613
  this.logger.error(
4558
4614
  `Dropping ${pending.length} session log entries after ${retryCount} failed flush attempts`,
4559
- { sessionId, error }
4615
+ {
4616
+ taskId: session.context.taskId,
4617
+ runId: session.context.runId,
4618
+ error
4619
+ }
4560
4620
  );
4561
4621
  this.retryCounts.set(sessionId, 0);
4562
4622
  } else {
@@ -4589,6 +4649,7 @@ var SessionLogWriter = class _SessionLogWriter {
4589
4649
  if (!session.chunkBuffer) return;
4590
4650
  const { text: text2, firstTimestamp } = session.chunkBuffer;
4591
4651
  session.chunkBuffer = void 0;
4652
+ session.lastAgentMessage = text2;
4592
4653
  const entry = {
4593
4654
  type: "notification",
4594
4655
  timestamp: firstTimestamp,
@@ -4611,6 +4672,29 @@ var SessionLogWriter = class _SessionLogWriter {
4611
4672
  this.scheduleFlush(sessionId);
4612
4673
  }
4613
4674
  }
4675
+ getLastAgentMessage(sessionId) {
4676
+ return this.sessions.get(sessionId)?.lastAgentMessage;
4677
+ }
4678
+ extractAgentMessageText(message) {
4679
+ if (message.method !== "session/update") {
4680
+ return null;
4681
+ }
4682
+ const params = message.params;
4683
+ const update = params?.update;
4684
+ if (update?.sessionUpdate !== "agent_message") {
4685
+ return null;
4686
+ }
4687
+ const content = update.content;
4688
+ if (content?.type === "text" && typeof content.text === "string") {
4689
+ const trimmed2 = content.text.trim();
4690
+ return trimmed2.length > 0 ? trimmed2 : null;
4691
+ }
4692
+ if (typeof update.message === "string") {
4693
+ const trimmed2 = update.message.trim();
4694
+ return trimmed2.length > 0 ? trimmed2 : null;
4695
+ }
4696
+ return null;
4697
+ }
4614
4698
  scheduleFlush(sessionId) {
4615
4699
  const existing = this.flushTimeouts.get(sessionId);
4616
4700
  if (existing) clearTimeout(existing);
@@ -4645,7 +4729,12 @@ var SessionLogWriter = class _SessionLogWriter {
4645
4729
  fs3.appendFileSync(logPath, `${JSON.stringify(entry)}
4646
4730
  `);
4647
4731
  } catch (error) {
4648
- this.logger.warn("Failed to write to local cache", { logPath, error });
4732
+ this.logger.warn("Failed to write to local cache", {
4733
+ taskId: session.context.taskId,
4734
+ runId: session.context.runId,
4735
+ logPath,
4736
+ error
4737
+ });
4649
4738
  }
4650
4739
  }
4651
4740
  };
@@ -10278,6 +10367,8 @@ var AgentServer = class {
10278
10367
  session = null;
10279
10368
  app;
10280
10369
  posthogAPI;
10370
+ questionRelayedToSlack = false;
10371
+ detectedPrUrl = null;
10281
10372
  constructor(config) {
10282
10373
  this.config = config;
10283
10374
  this.logger = new Logger({ debug: true, prefix: "[AgentServer]" });
@@ -10490,11 +10581,21 @@ var AgentServer = class {
10490
10581
  case "user_message": {
10491
10582
  const content = params.content;
10492
10583
  this.logger.info(
10493
- `Processing user message: ${content.substring(0, 100)}...`
10584
+ `Processing user message (detectedPrUrl=${this.detectedPrUrl ?? "none"}): ${content.substring(0, 100)}...`
10494
10585
  );
10495
10586
  const result = await this.session.clientConnection.prompt({
10496
10587
  sessionId: this.session.acpSessionId,
10497
- prompt: [{ type: "text", text: content }]
10588
+ prompt: [{ type: "text", text: content }],
10589
+ ...this.detectedPrUrl && {
10590
+ _meta: {
10591
+ prContext: `IMPORTANT \u2014 OVERRIDE PREVIOUS INSTRUCTIONS ABOUT CREATING BRANCHES/PRs.
10592
+ You already have an open pull request: ${this.detectedPrUrl}
10593
+ You MUST:
10594
+ 1. Check out the existing PR branch with \`gh pr checkout ${this.detectedPrUrl}\`
10595
+ 2. Make changes, commit, and push to that branch
10596
+ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
10597
+ }
10598
+ }
10498
10599
  });
10499
10600
  return { stopReason: result.stopReason };
10500
10601
  }
@@ -10579,13 +10680,29 @@ var AgentServer = class {
10579
10680
  protocolVersion: PROTOCOL_VERSION,
10580
10681
  clientCapabilities: {}
10581
10682
  });
10683
+ let preTaskRun = null;
10684
+ try {
10685
+ preTaskRun = await this.posthogAPI.getTaskRun(
10686
+ payload.task_id,
10687
+ payload.run_id
10688
+ );
10689
+ } catch {
10690
+ this.logger.warn("Failed to fetch task run for session context", {
10691
+ taskId: payload.task_id,
10692
+ runId: payload.run_id
10693
+ });
10694
+ }
10695
+ const prUrl = typeof preTaskRun?.state?.slack_notified_pr_url === "string" ? (preTaskRun?.state).slack_notified_pr_url : null;
10696
+ if (prUrl) {
10697
+ this.detectedPrUrl = prUrl;
10698
+ }
10582
10699
  const sessionResponse = await clientConnection.newSession({
10583
10700
  cwd: this.config.repositoryPath,
10584
10701
  mcpServers: [],
10585
10702
  _meta: {
10586
10703
  sessionId: payload.run_id,
10587
10704
  taskRunId: payload.run_id,
10588
- systemPrompt: { append: this.buildCloudSystemPrompt() }
10705
+ systemPrompt: { append: this.buildCloudSystemPrompt(prUrl) }
10589
10706
  }
10590
10707
  });
10591
10708
  const acpSessionId = sessionResponse.sessionId;
@@ -10609,28 +10726,51 @@ var AgentServer = class {
10609
10726
  }).catch(
10610
10727
  (err) => this.logger.warn("Failed to set task run to in_progress", err)
10611
10728
  );
10612
- await this.sendInitialTaskMessage(payload);
10729
+ await this.sendInitialTaskMessage(payload, preTaskRun);
10613
10730
  }
10614
- async sendInitialTaskMessage(payload) {
10731
+ async sendInitialTaskMessage(payload, prefetchedRun) {
10615
10732
  if (!this.session) return;
10616
10733
  try {
10617
- this.logger.info("Fetching task details", { taskId: payload.task_id });
10618
10734
  const task = await this.posthogAPI.getTask(payload.task_id);
10619
- if (!task.description) {
10735
+ let taskRun = prefetchedRun ?? null;
10736
+ if (!taskRun) {
10737
+ try {
10738
+ taskRun = await this.posthogAPI.getTaskRun(
10739
+ payload.task_id,
10740
+ payload.run_id
10741
+ );
10742
+ } catch (error) {
10743
+ this.logger.warn(
10744
+ "Failed to fetch task run for initial prompt override",
10745
+ {
10746
+ taskId: payload.task_id,
10747
+ runId: payload.run_id,
10748
+ error
10749
+ }
10750
+ );
10751
+ }
10752
+ }
10753
+ const initialPromptOverride = taskRun ? this.getInitialPromptOverride(taskRun) : null;
10754
+ const initialPrompt = initialPromptOverride ?? task.description;
10755
+ if (!initialPrompt) {
10620
10756
  this.logger.warn("Task has no description, skipping initial message");
10621
10757
  return;
10622
10758
  }
10623
10759
  this.logger.info("Sending initial task message", {
10624
10760
  taskId: payload.task_id,
10625
- descriptionLength: task.description.length
10761
+ descriptionLength: initialPrompt.length,
10762
+ usedInitialPromptOverride: !!initialPromptOverride
10626
10763
  });
10627
10764
  const result = await this.session.clientConnection.prompt({
10628
10765
  sessionId: this.session.acpSessionId,
10629
- prompt: [{ type: "text", text: task.description }]
10766
+ prompt: [{ type: "text", text: initialPrompt }]
10630
10767
  });
10631
10768
  this.logger.info("Initial task message completed", {
10632
10769
  stopReason: result.stopReason
10633
10770
  });
10771
+ if (result.stopReason === "end_turn") {
10772
+ await this.relayAgentResponse(payload);
10773
+ }
10634
10774
  } catch (error) {
10635
10775
  this.logger.error("Failed to send initial task message", error);
10636
10776
  if (this.session) {
@@ -10639,7 +10779,33 @@ var AgentServer = class {
10639
10779
  await this.signalTaskComplete(payload, "error");
10640
10780
  }
10641
10781
  }
10642
- buildCloudSystemPrompt() {
10782
+ getInitialPromptOverride(taskRun) {
10783
+ const state = taskRun.state;
10784
+ const override = state?.initial_prompt_override;
10785
+ if (typeof override !== "string") {
10786
+ return null;
10787
+ }
10788
+ const trimmed2 = override.trim();
10789
+ return trimmed2.length > 0 ? trimmed2 : null;
10790
+ }
10791
+ buildCloudSystemPrompt(prUrl) {
10792
+ if (prUrl) {
10793
+ return `
10794
+ # Cloud Task Execution
10795
+
10796
+ This task already has an open pull request: ${prUrl}
10797
+
10798
+ After completing the requested changes:
10799
+ 1. Check out the existing PR branch with \`gh pr checkout ${prUrl}\`
10800
+ 2. Stage and commit all changes with a clear commit message
10801
+ 3. Push to the existing PR branch
10802
+
10803
+ Important:
10804
+ - Do NOT create a new branch or a new pull request.
10805
+ - Do NOT add "Co-Authored-By" trailers to commit messages.
10806
+ - Do NOT add "Generated with [Claude Code]" or similar attribution lines to PR descriptions.
10807
+ `;
10808
+ }
10643
10809
  return `
10644
10810
  # Cloud Task Execution
10645
10811
 
@@ -10709,19 +10875,34 @@ Important:
10709
10875
  }
10710
10876
  createCloudClient(payload) {
10711
10877
  const mode = this.getEffectiveMode(payload);
10878
+ const interactionOrigin = process.env.TWIG_INTERACTION_ORIGIN;
10712
10879
  return {
10713
10880
  requestPermission: async (params) => {
10714
10881
  this.logger.debug("Permission request", {
10715
10882
  mode,
10883
+ interactionOrigin,
10716
10884
  options: params.options
10717
10885
  });
10718
10886
  const allowOption = params.options.find(
10719
10887
  (o) => o.kind === "allow_once" || o.kind === "allow_always"
10720
10888
  );
10889
+ const selectedOptionId = allowOption?.optionId ?? params.options[0].optionId;
10890
+ if (interactionOrigin === "slack") {
10891
+ const twigToolKind = params.toolCall?._meta?.twigToolKind;
10892
+ if (twigToolKind === "question") {
10893
+ this.relaySlackQuestion(payload, params.toolCall?._meta);
10894
+ return {
10895
+ outcome: { outcome: "cancelled" },
10896
+ _meta: {
10897
+ 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."
10898
+ }
10899
+ };
10900
+ }
10901
+ }
10721
10902
  return {
10722
10903
  outcome: {
10723
10904
  outcome: "selected",
10724
- optionId: allowOption?.optionId ?? params.options[0].optionId
10905
+ optionId: selectedOptionId
10725
10906
  }
10726
10907
  };
10727
10908
  },
@@ -10740,6 +10921,97 @@ Important:
10740
10921
  }
10741
10922
  };
10742
10923
  }
10924
+ async relayAgentResponse(payload) {
10925
+ if (!this.session) {
10926
+ return;
10927
+ }
10928
+ if (this.questionRelayedToSlack) {
10929
+ this.questionRelayedToSlack = false;
10930
+ return;
10931
+ }
10932
+ try {
10933
+ await this.session.logWriter.flush(payload.run_id);
10934
+ } catch (error) {
10935
+ this.logger.warn("Failed to flush logs before Slack relay", {
10936
+ taskId: payload.task_id,
10937
+ runId: payload.run_id,
10938
+ error
10939
+ });
10940
+ }
10941
+ const message = this.session.logWriter.getLastAgentMessage(payload.run_id);
10942
+ if (!message) {
10943
+ this.logger.warn("No agent message found for Slack relay", {
10944
+ taskId: payload.task_id,
10945
+ runId: payload.run_id,
10946
+ sessionRegistered: this.session.logWriter.isRegistered(payload.run_id)
10947
+ });
10948
+ return;
10949
+ }
10950
+ try {
10951
+ await this.posthogAPI.relayMessage(
10952
+ payload.task_id,
10953
+ payload.run_id,
10954
+ message
10955
+ );
10956
+ } catch (error) {
10957
+ this.logger.warn("Failed to relay initial agent response to Slack", {
10958
+ taskId: payload.task_id,
10959
+ runId: payload.run_id,
10960
+ error
10961
+ });
10962
+ }
10963
+ }
10964
+ relaySlackQuestion(payload, toolMeta2) {
10965
+ const firstQuestion = this.getFirstQuestionMeta(toolMeta2);
10966
+ if (!this.isQuestionMeta(firstQuestion)) {
10967
+ return;
10968
+ }
10969
+ let message = `*${firstQuestion.question}*
10970
+
10971
+ `;
10972
+ if (firstQuestion.options?.length) {
10973
+ firstQuestion.options.forEach(
10974
+ (opt, i) => {
10975
+ message += `${i + 1}. *${opt.label}*`;
10976
+ if (opt.description) message += ` \u2014 ${opt.description}`;
10977
+ message += "\n";
10978
+ }
10979
+ );
10980
+ }
10981
+ message += "\nReply in this thread with your choice.";
10982
+ this.questionRelayedToSlack = true;
10983
+ this.posthogAPI.relayMessage(payload.task_id, payload.run_id, message).catch(
10984
+ (err) => this.logger.warn("Failed to relay question to Slack", { err })
10985
+ );
10986
+ }
10987
+ getFirstQuestionMeta(toolMeta2) {
10988
+ if (!toolMeta2) {
10989
+ return null;
10990
+ }
10991
+ const questionsValue = toolMeta2.questions;
10992
+ if (!Array.isArray(questionsValue) || questionsValue.length === 0) {
10993
+ return null;
10994
+ }
10995
+ return questionsValue[0];
10996
+ }
10997
+ isQuestionMeta(value) {
10998
+ if (!value || typeof value !== "object") {
10999
+ return false;
11000
+ }
11001
+ const candidate = value;
11002
+ if (typeof candidate.question !== "string") {
11003
+ return false;
11004
+ }
11005
+ if (candidate.options === void 0) {
11006
+ return true;
11007
+ }
11008
+ if (!Array.isArray(candidate.options)) {
11009
+ return false;
11010
+ }
11011
+ return candidate.options.every(
11012
+ (option) => !!option && typeof option === "object" && typeof option.label === "string"
11013
+ );
11014
+ }
10743
11015
  detectAndAttachPrUrl(payload, update) {
10744
11016
  try {
10745
11017
  const meta = update?._meta?.claudeCode;
@@ -10770,6 +11042,7 @@ Important:
10770
11042
  );
10771
11043
  if (!prUrlMatch) return;
10772
11044
  const prUrl = prUrlMatch[0];
11045
+ this.detectedPrUrl = prUrl;
10773
11046
  this.logger.info("Detected PR URL in bash output", {
10774
11047
  runId: payload.run_id,
10775
11048
  prUrl