Skip to content
Open
2 changes: 1 addition & 1 deletion apps/api/src/api/routes/admin-prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const PROMPTS = [
{
id: "super-agent-sandbox",
label: "Super Agent (sandbox)",
path: "apps/api/src/tools/task-board/claude-code-task-run.ts",
path: "packages/shared/src/task-initial-prompt.ts",
},
{
id: "super-agent",
Expand Down
12 changes: 7 additions & 5 deletions apps/api/src/tools/task-board/automations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ export const TASK_BOARD_AUTOMATION_UPSERT = defineTool({
name: "TASK_BOARD_AUTOMATION_UPSERT",
description:
"Run the agent on every card that lands in a column. Replaces the rule " +
"already on that column, if any. Omit `prompt` to use the agent's own " +
"instruction; give one to say what it should do there instead. The card's " +
"title and description are always included, so the prompt is the " +
"instruction, not the whole message.",
"already on that column, if any. `prompt` is the run's whole opening " +
"message, as a template: `{{taskTitle}}`, `{{taskDescription}}`, " +
"`{{jiraId}}` and the rest are substituted per card. Omit it for the " +
"default prompt.",
inputSchema: z.object({
columnKey: z
.string()
Expand All @@ -50,7 +50,9 @@ export const TASK_BOARD_AUTOMATION_UPSERT = defineTool({
.max(MAX_AUTOMATION_PROMPT_LENGTH)
.nullable()
.optional()
.describe("What to do with a card landing here; null for the default."),
.describe(
"The run's opening message for a card landing here, with {{var}} placeholders; null for the default prompt.",
),
}),
outputSchema: z.object({ automation: AutomationSchema }),
handler: async (input, ctx) => {
Expand Down
34 changes: 34 additions & 0 deletions apps/api/src/tools/task-board/claude-code-task-run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,38 @@ describe("buildClaudeCodeTaskPrompt repo choices", () => {
const prompt = buildClaudeCodeTaskPrompt(task, null);
expect(prompt).toContain("with no arguments to list them");
});

// The triggering column's rule IS the template now — an org can shrink the
// whole opening message to a variable, which is the point of the feature.
test("a column rule's prompt replaces the whole message, vars filled", () => {
const prompt = buildClaudeCodeTaskPrompt(task, repo, {
instruction: "Ship {{taskTitle}} ({{jiraId}})",
});
expect(prompt).toBe("Ship Add a health endpoint ()");
});

test("a card from Jira exposes its issue key", () => {
const prompt = buildClaudeCodeTaskPrompt(
{ ...task, externalUrl: "https://acme.atlassian.net/browse/DECO-9" },
repo,
{ instruction: "{{jiraId}} — {{jiraUrl}}" },
);
expect(prompt).toBe("DECO-9 — https://acme.atlassian.net/browse/DECO-9");
});

// A rule on any column but In Progress is still a lead line, not a template.
test("a plain instruction leads the default prompt instead of replacing it", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This test doesn't exercise repo choices, so it doesn't belong in the "repo choices" describe block. Move it into the main buildClaudeCodeTaskPrompt describe (or a dedicated one) so the block stays a truthful grouping for tests that actually pass repoChoices.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/tools/task-board/claude-code-task-run.test.ts, line 284:

<comment>This test doesn't exercise repo choices, so it doesn't belong in the "repo choices" describe block. Move it into the main buildClaudeCodeTaskPrompt describe (or a dedicated one) so the block stays a truthful grouping for tests that actually pass repoChoices.</comment>

<file context>
@@ -280,6 +280,16 @@ describe("buildClaudeCodeTaskPrompt repo choices", () => {
   });
 
+  // A rule on any column but In Progress is still a lead line, not a template.
+  test("a plain instruction leads the default prompt instead of replacing it", () => {
+    const prompt = buildClaudeCodeTaskPrompt(task, repo, {
+      instruction: "Review the diff and leave comments.",
</file context>

const prompt = buildClaudeCodeTaskPrompt(task, repo, {
instruction: "Review the diff and leave comments.",
});
expect(prompt.startsWith("Review the diff and leave comments.")).toBe(true);
expect(prompt).toContain("Title: Add a health endpoint");
expect(prompt).toContain("How to finish:");
});

test("no rule on the column renders the shipped default", () => {
expect(buildClaudeCodeTaskPrompt(task, repo)).toContain(
"You've been assigned this task",
);
});
});
239 changes: 128 additions & 111 deletions apps/api/src/tools/task-board/claude-code-task-run.ts

Large diffs are not rendered by default.

30 changes: 26 additions & 4 deletions apps/api/src/tools/task-board/enqueue-super-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,15 @@ export async function reactToSuperAgentDelegation(
*/
/** Options that steer the Super Agent prompt for a re-run on an existing PR. */
export type SuperAgentPromptOpts = {
/** What the board's rule for this column says to do. Replaces the default
* opening instruction; the task's own title and description still follow,
* or the agent would not know which card it is on. */
/**
* The rule the triggering column carries (`task_board_column_automations`).
*
* For the sandbox harness it is the WHOLE opening message, as a `{{var}}`
* template — that is what makes the task prompt configurable per column
* (`DEFAULT_TASK_INITIAL_PROMPT` is what an unset rule renders). The
* Decopilot fallback still composes its own message and uses a plain
* instruction as its lead line only — see `instructionLead`.
*/
instruction?: string;
/** A reviewer's change request — leads the re-run prompt. */
feedback?: string;
Expand All @@ -81,6 +87,22 @@ export type SuperAgentPromptOpts = {
userInitiated?: boolean;
};

/**
* A column rule's opening instruction, or the Super Agent's own.
*
* A rule's prompt is a `{{var}}` TEMPLATE for the sandbox harness's whole
* message (see `claude-code-task-run.ts`). This builder composes its own
* message instead, so a template pasted in as the lead line would reach the
* model with literal `{{...}}` in it — fall back to the built-in lead.
*/
function instructionLead(instruction: string | undefined): string {
const trimmed = instruction?.trim();
if (!trimmed || trimmed.includes("{{")) {
return "You've been assigned this task. Complete it.";
}
return trimmed;
}

/**
* The autonomous Super Agent prompt for a task. Pure (no I/O) so the branch
* selection is unit-tested: a fresh attempt, a reviewer's change request, or a
Expand All @@ -102,7 +124,7 @@ export function buildSuperAgentTaskPrompt(
return [
// A column's rule supplies its own instruction; without one this is the
// Super Agent's, which is what every run used before rules existed.
opts?.instruction?.trim() || "You've been assigned this task. Complete it.",
instructionLead(opts?.instruction),
"",
"You are running AUTONOMOUSLY — no human is watching this run, so drive it " +
"to completion on your own. Use `user_ask` ONLY for a genuine, " +
Expand Down
26 changes: 23 additions & 3 deletions apps/web/src/i18n/en/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export const settings = {
"settings.boardColumns.description":
"Your tracker owns these columns. Say what should move a card into each one, and which of them should put the agent to work.",
"settings.boardColumns.fieldLabel": "Columns",
"settings.boardColumns.descriptionStudioBoard":
"Say which columns should put the agent to work, and what to tell it when a card lands there.",
"settings.boardColumns.fieldDescriptionStudioBoard":
"Running the agent is what a card does on arrival. Studio decides what moves a card between these lanes.",
"settings.boardColumns.fieldDescription":
"Studio moves a card here when the thing you pick happens. Running the agent is separate: that is what the card does on arrival.",
"settings.boardColumns.moveHereWhen": "Move cards here when",
Expand All @@ -65,10 +69,26 @@ export const settings = {
"Run the agent on cards that arrive here",
"settings.boardColumns.automationOn":
"The agent runs on every card that arrives",
"settings.boardColumns.promptPlaceholder":
"Review the diff and leave comments…",
"settings.boardColumns.promptHelp":
"Optional. The card's title and description always come with it, so this is the instruction, not the whole message.",
"The run's whole opening message — this is where agent work starts. Use these variables to drop in the card's own values:",
"settings.boardColumns.promptReset": "Reset to default",
"settings.boardColumns.var.instruction":
"What the column's rule says to do, or the agent's own opening line.",
"settings.boardColumns.var.taskTitle": "The card's title.",
"settings.boardColumns.var.taskDescription":
"The card's description block, already labelled — empty when it has none.",
"settings.boardColumns.var.taskId":
"The card's id, which the board tools the run calls need.",
"settings.boardColumns.var.jiraId":
"The card's Jira issue key (e.g. DECO-123), or empty.",
"settings.boardColumns.var.jiraUrl":
"Link to the card's Jira issue, or empty.",
"settings.boardColumns.var.repoContext":
"Where the code is: the repository already cloned into the sandbox, or the ones to pick from.",
"settings.boardColumns.var.prBullet":
"How to hand over: open a pull request, or push to the one named above on a re-run.",
"settings.boardColumns.var.prContext":
"Why this is a re-run: a reviewer's change request, a merge conflict, or the open pull request to keep pushing to. Empty on a first attempt.",
"settings.boardColumns.removeAriaLabel": "Stop running the agent in {column}",
"settings.boardColumns.saveFailed": "Could not save this column",
"settings.jira.roleNone": "Nothing special",
Expand Down
25 changes: 22 additions & 3 deletions apps/web/src/i18n/pt-br/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export const settings = {
"settings.boardColumns.description":
"As colunas são do seu tracker. Diga o que deve mover um card para cada uma, e quais delas devem colocar o agente para trabalhar.",
"settings.boardColumns.fieldLabel": "Colunas",
"settings.boardColumns.descriptionStudioBoard":
"Diga quais colunas devem colocar o agente para trabalhar, e o que dizer a ele quando um card chega ali.",
"settings.boardColumns.fieldDescriptionStudioBoard":
"Rodar o agente \u00e9 o que o card faz ao chegar. O Studio decide o que move um card entre estas lanes.",
"settings.boardColumns.fieldDescription":
"O Studio move o card para cá quando a coisa que você escolher acontecer. Rodar o agente é separado: é o que o card faz ao chegar.",
"settings.boardColumns.moveHereWhen": "Mover cards para cá quando",
Expand All @@ -67,10 +71,25 @@ export const settings = {
"settings.boardColumns.addAutomation":
"Rodar o agente nos cards que chegarem aqui",
"settings.boardColumns.automationOn": "O agente roda em todo card que chega",
"settings.boardColumns.promptPlaceholder":
"Revise o diff e deixe comentários…",
"settings.boardColumns.promptHelp":
"Opcional. O título e a descrição do card sempre vão junto, então isto é a instrução, não a mensagem inteira.",
"A mensagem inicial completa do run — é aqui que o trabalho do agente começa. Use estas variáveis para inserir os valores do card:",
"settings.boardColumns.promptReset": "Restaurar padrão",
"settings.boardColumns.var.instruction":
"O que a regra da coluna manda fazer, ou a frase de abertura do pr\u00f3prio agente.",
"settings.boardColumns.var.taskTitle": "O t\u00edtulo do card.",
"settings.boardColumns.var.taskDescription":
"A descri\u00e7\u00e3o do card, j\u00e1 rotulada \u2014 vazio quando n\u00e3o tem nenhuma.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: In the new pt-BR variable descriptions, 'vazio' does not agree with its feminine subject. 'A descrição do card ... vazio' and 'A chave da issue do Jira ... ou vazio' should use 'vazia' to agree with 'descrição' and 'chave'. Change both to 'vazia' for grammatical consistency with the rest of the translation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/i18n/pt-br/settings.ts, line 81:

<comment>In the new pt-BR variable descriptions, 'vazio' does not agree with its feminine subject. 'A descrição do card ... vazio' and 'A chave da issue do Jira ... ou vazio' should use 'vazia' to agree with 'descrição' and 'chave'. Change both to 'vazia' for grammatical consistency with the rest of the translation.</comment>

<file context>
@@ -72,8 +72,24 @@ export const settings = {
+    "O que a regra da coluna manda fazer, ou a frase de abertura do pr\u00f3prio agente.",
+  "settings.boardColumns.var.taskTitle": "O t\u00edtulo do card.",
+  "settings.boardColumns.var.taskDescription":
+    "A descri\u00e7\u00e3o do card, j\u00e1 rotulada \u2014 vazio quando n\u00e3o tem nenhuma.",
+  "settings.boardColumns.var.taskId":
+    "O id do card, de que as ferramentas do board usadas no run precisam.",
</file context>

"settings.boardColumns.var.taskId":
"O id do card, de que as ferramentas do board usadas no run precisam.",
"settings.boardColumns.var.jiraId":
"A chave da issue do Jira (ex.: DECO-123), ou vazio.",
"settings.boardColumns.var.jiraUrl": "Link para a issue do Jira, ou vazio.",
"settings.boardColumns.var.repoContext":
"Onde est\u00e1 o c\u00f3digo: o reposit\u00f3rio j\u00e1 clonado no sandbox, ou os que h\u00e1 para escolher.",
"settings.boardColumns.var.prBullet":
"Como entregar: abrir um pull request, ou dar push no que foi citado acima num re-run.",
"settings.boardColumns.var.prContext":
"Por que este \u00e9 um re-run: um pedido de mudan\u00e7a do revisor, um conflito de merge, ou o pull request aberto para continuar. Vazio numa primeira tentativa.",
"settings.boardColumns.removeAriaLabel":
"Parar de rodar o agente em {column}",
"settings.boardColumns.saveFailed": "Não foi possível salvar esta coluna",
Expand Down
Loading
Loading