Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ export function buildFastAgentSystemPrompt({
activeTasks = [],
surface = 'slack',
platformEvent = false,
retryTaskStartAvailable = false,
}: {
availableEnvironments: RoutableEnvironment[];
availableIntegrations?: FastAgentIntegration[];
activeTasks?: FastAgentActiveTask[];
surface?: FastAgentSurface;
platformEvent?: boolean;
retryTaskStartAvailable?: boolean;
/** @deprecated GitHub availability is derived from availableIntegrations. */
hasGitHubTools?: boolean;
}): string {
Expand Down Expand Up @@ -133,7 +135,11 @@ ${
- The current input is a trusted platform-generated event about a delegated task, not a human-authored request.
- Decide whether the event is useful to the user now. Use "ignore_event" when it is routine, redundant, or not worth interrupting them for.
- When it is useful, emit exactly one "send_chat_reply" with purpose "closeout" and describe the outcome naturally in the context of the delegated work. Never use "ack" or "progress" for a platform event, and never copy a canned event sentence.
- Do not use integrations or task-control actions for this event.
${
retryTaskStartAvailable
? '- This failed task-settled event includes the full secret-redacted error and its machine-readable errorCode when available. Decide from that evidence whether another startup attempt is worthwhile. Use "retry_task_start" only when the failure appears transient; do not use it for clear configuration, authentication, permission, billing, quota, missing-resource, or other permanent failures.\n- After "retry_task_start", report its result with one closeout. Do not use integrations or any other task-control action for this event.'
: '- No failed-start retry action is available for this event. Report or ignore the event without attempting a retry.'
}
- Artifact events include stable artifact IDs and view URLs. When an image would help the user, include its ID in imageArtifactIds so it renders inline with the same reply. For non-image artifacts, link the supplied view URL when useful.
- Pull-request-opened events contain authoritative, user-presentable pull request metadata and should be presented unless that exact pull request URL was already reported in this conversation. Briefly name and link the pull request, including its repository, number, title, and current status when available.
- Task-settled events include the task's current pullRequests list. Use it in the closeout so a pull request produced by the task is named and linked even when its earlier open event was missed; do not describe the pull request as newly opened if the thread already received that update.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const fastAgentDecisionSchema = z
'cancel_task',
'call_integration',
'ignore_event',
'retry_task_start',
]),
message: z.string().nullable(),
purpose: z
Expand Down Expand Up @@ -174,6 +175,10 @@ export type LaunchFastAgentSlackTask = (params: {
| { success: false; error: string }
>;

export type RetryFastAgentTaskStart = () => Promise<
{ success: true; runId: number } | { success: false; error: string }
>;

function normalizeThreadText(text: string): string {
return text.replace(/\s+/g, ' ').trim();
}
Expand Down Expand Up @@ -512,6 +517,7 @@ export async function answerFastAgentQuestion({
senderSlackUserId,
activeTasks = [],
launchTask,
retryTaskStart,
postSlackReply,
postSlackReaction,
surface = 'slack',
Expand All @@ -530,6 +536,7 @@ export async function answerFastAgentQuestion({
senderSlackUserId?: string;
activeTasks?: FastAgentActiveTask[];
launchTask?: LaunchFastAgentSlackTask;
retryTaskStart?: RetryFastAgentTaskStart;
postSlackReply?: PostFastAgentSlackReply;
postSlackReaction?: PostFastAgentSlackReaction;
surface?: FastAgentSurface;
Expand Down Expand Up @@ -602,13 +609,15 @@ export async function answerFastAgentQuestion({
activeTasks: resolvedActiveTasks,
surface,
platformEvent,
retryTaskStartAvailable: Boolean(retryTaskStart),
});
let prompt = serializeFastAgentMessages(fastAgentMessages);
const integrationCallSignatures = new Set<string>();
const completedTaskActions = new Set<string>();
const currentActiveTasks = new Map(
resolvedActiveTasks.map((task) => [task.taskId, task]),
);
let retriedTaskStart = false;
const flushPendingLifecycleReply = async () => {
if (!pendingLifecycleReply) {
return;
Expand Down Expand Up @@ -740,8 +749,35 @@ export async function answerFastAgentQuestion({
return message;
}

if (decision.action === 'retry_task_start') {
if (!platformEvent || !retryTaskStart) {
prompt += `\n\n[PLATFORM EVENT ACTION REJECTED]\nretry_task_start is only available for an eligible failed delegated-task event.\n[END PLATFORM EVENT ACTION REJECTED]`;
continue;
}
if (retriedTaskStart) {
prompt += `\n\n[FAST ORCHESTRATION TOOL RESULT]\nTool: retry_task_start\nResult: ${JSON.stringify({ success: false, error: 'The task start retry has already been attempted for this event.' })}\n[END FAST ORCHESTRATION TOOL RESULT]\n\nReport the result with one send_chat_reply closeout.`;
continue;
}

retriedTaskStart = true;
let retryResult: Awaited<ReturnType<RetryFastAgentTaskStart>>;
try {
retryResult = await retryTaskStart();
} catch (error) {
console.error(
`[Fast Agent] Failed to retry delegated task start: ${formatErrorForLog(error)}`,
);
retryResult = {
success: false,
error: 'The failed-start retry could not be queued.',
};
}
prompt += `\n\n[FAST ORCHESTRATION TOOL RESULT]\nTool: retry_task_start\nResult: ${JSON.stringify(retryResult)}\n[END FAST ORCHESTRATION TOOL RESULT]\n\nReport the retry outcome with one send_chat_reply closeout.`;
continue;
}

if (platformEvent) {
prompt += `\n\n[PLATFORM EVENT ACTION REJECTED]\nA delegated-task platform event may only use send_chat_reply or ignore_event. Do not launch, message, or cancel tasks, react, or call integrations for this event.\n[END PLATFORM EVENT ACTION REJECTED]`;
prompt += `\n\n[PLATFORM EVENT ACTION REJECTED]\nA delegated-task platform event may only use send_chat_reply, ignore_event, or the offered retry_task_start action. Do not launch, message, or cancel tasks, react, or call integrations for this event.\n[END PLATFORM EVENT ACTION REJECTED]`;
continue;
}

Expand Down
9 changes: 9 additions & 0 deletions packages/sdk/src/server/lib/fast-agent-parent-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
type FastAgentParent,
type PullRequestStatus,
type RunStatus,
type TaskRunErrorCode,
type SlackBlock,
type SourceControlProvider,
} from '@roomote/types';
Expand Down Expand Up @@ -90,6 +91,8 @@ type FastAgentParentEvent =
runId: number;
title?: string;
status: string;
error?: string;
errorCode?: TaskRunErrorCode;
taskUrl: string;
pullRequests: FastAgentPullRequestContext[];
}
Expand Down Expand Up @@ -200,6 +203,9 @@ function buildEventClientMessageSeed(event: FastAgentParentEvent): string {
export async function deliverFastAgentParentEvent(params: {
parent: FastAgentParent;
event: FastAgentParentEvent;
retryTaskStart?: () => Promise<
{ success: true; runId: number } | { success: false; error: string }
>;
/** Cap the turn-lock wait so callers holding an HTTP request can fail fast
* and lean on their own retry instead of blocking. */
lockWaitMs?: number;
Expand Down Expand Up @@ -266,6 +272,9 @@ export async function deliverFastAgentParentEvent(params: {
slackChannel: params.parent.slackChannel,
slackThreadTs: params.parent.slackThreadTs,
platformEvent: true,
...(params.retryTaskStart
? { retryTaskStart: params.retryTaskStart }
: {}),
postSlackReply: async ({ message, imageArtifactIds = [] }) => {
const imageBlocks = await buildSelectedImageBlocks({
artifactIds: imageArtifactIds,
Expand Down
Loading
Loading