Skip to content

Commit 44d5712

Browse files
authored
feat: add pr url to task object when pr made in twig (#538)
### TL;DR Added automatic detection and tracking of GitHub PR URLs created during agent tasks. ### What changed? - Added a new `detectAndAttachPrUrl` method to the `AgentService` class that: - Monitors bash tool execution results for GitHub PR URLs - Extracts PR URLs using regex pattern matching - Attaches detected PR URLs to the corresponding task - Calls the agent's `attachPullRequestToTask` method to update the task - Integrated this detection into the existing message handling flow ### How to test? 1. Run an agent task that creates a GitHub PR using bash commands 2. Verify that the PR URL is automatically detected in the bash output 3. Check that the PR URL is properly attached to the task 4. Confirm that webhook tracking is enabled for the PR ### Why make this change? This enhancement enables webhook tracking by automatically populating the `pr_url` in TaskRun output when an agent creates a GitHub PR. Previously, PR URLs created during agent tasks weren't automatically tracked, requiring manual intervention. This change improves the workflow by automatically detecting and linking PRs to their originating tasks.
1 parent 1a849eb commit 44d5712

1 file changed

Lines changed: 111 additions & 0 deletions

File tree

apps/twig/src/main/services/agent/service.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,9 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
671671
message: message as AcpMessage["message"],
672672
};
673673
emitToRenderer(acpMessage);
674+
675+
// Detect PR URLs in bash tool results and attach to task
676+
this.detectAndAttachPrUrl(taskRunId, message);
674677
};
675678

676679
const tappedReadable = createTappedReadableStream(
@@ -816,4 +819,112 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
816819
private toSessionResponse(session: ManagedSession): SessionResponse {
817820
return { sessionId: session.taskRunId, channel: session.channel };
818821
}
822+
823+
/**
824+
* Detect GitHub PR URLs in bash tool results and attach to task.
825+
* This enables webhook tracking by populating the pr_url in TaskRun output.
826+
*/
827+
private detectAndAttachPrUrl(taskRunId: string, message: unknown): void {
828+
try {
829+
const msg = message as {
830+
method?: string;
831+
params?: {
832+
update?: {
833+
sessionUpdate?: string;
834+
_meta?: {
835+
claudeCode?: {
836+
toolName?: string;
837+
toolResponse?: unknown;
838+
};
839+
};
840+
content?: Array<{ type?: string; text?: string }>;
841+
};
842+
};
843+
};
844+
845+
// Only process session/update notifications for tool_call_update
846+
if (msg.method !== "session/update") return;
847+
if (msg.params?.update?.sessionUpdate !== "tool_call_update") return;
848+
849+
const toolMeta = msg.params.update._meta?.claudeCode;
850+
const toolName = toolMeta?.toolName;
851+
852+
// Only process Bash tool results
853+
if (
854+
!toolName ||
855+
(!toolName.includes("Bash") && !toolName.includes("bash"))
856+
) {
857+
return;
858+
}
859+
860+
// Extract text content from tool response or update content
861+
let textToSearch = "";
862+
863+
// Check toolResponse (hook response with raw output)
864+
const toolResponse = toolMeta?.toolResponse;
865+
if (toolResponse) {
866+
if (typeof toolResponse === "string") {
867+
textToSearch = toolResponse;
868+
} else if (typeof toolResponse === "object" && toolResponse !== null) {
869+
// May be { stdout?: string, stderr?: string } or similar
870+
const respObj = toolResponse as Record<string, unknown>;
871+
textToSearch =
872+
String(respObj.stdout || "") + String(respObj.stderr || "");
873+
if (!textToSearch && respObj.output) {
874+
textToSearch = String(respObj.output);
875+
}
876+
}
877+
}
878+
879+
// Also check content array
880+
const content = msg.params.update.content;
881+
if (Array.isArray(content)) {
882+
for (const item of content) {
883+
if (item.type === "text" && item.text) {
884+
textToSearch += ` ${item.text}`;
885+
}
886+
}
887+
}
888+
889+
if (!textToSearch) return;
890+
891+
// Match GitHub PR URLs
892+
const prUrlMatch = textToSearch.match(
893+
/https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/,
894+
);
895+
if (!prUrlMatch) return;
896+
897+
const prUrl = prUrlMatch[0];
898+
log.info("Detected PR URL in bash output", { taskRunId, prUrl });
899+
900+
// Find session and attach PR URL
901+
const session = this.sessions.get(taskRunId);
902+
if (!session) {
903+
log.warn("Session not found for PR attachment", { taskRunId });
904+
return;
905+
}
906+
907+
// Attach asynchronously without blocking message flow
908+
session.agent
909+
.attachPullRequestToTask(session.taskId, prUrl)
910+
.then(() => {
911+
log.info("PR URL attached to task", {
912+
taskRunId,
913+
taskId: session.taskId,
914+
prUrl,
915+
});
916+
})
917+
.catch((err) => {
918+
log.error("Failed to attach PR URL to task", {
919+
taskRunId,
920+
taskId: session.taskId,
921+
prUrl,
922+
error: err,
923+
});
924+
});
925+
} catch (err) {
926+
// Don't let detection errors break message flow
927+
log.debug("Error in PR URL detection", { taskRunId, error: err });
928+
}
929+
}
819930
}

0 commit comments

Comments
 (0)