Skip to content

Commit 1c14041

Browse files
chore: enhancing prompts
1 parent 37c3436 commit 1c14041

File tree

20 files changed

+664
-8
lines changed

20 files changed

+664
-8
lines changed

apps/api/src/routes/sandbox.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ sandbox.post(
3636
task: payload.task,
3737
taskType: payload.taskType,
3838
model: payload.model,
39+
promptStrategy: payload.promptStrategy,
3940
shouldCommit: Boolean(payload.shouldCommit),
4041
installationId: payload.installationId,
4142
stream: c.req.header("accept")?.includes("text/event-stream"),

apps/api/src/services/apps/sandbox/__test__/execute-stream.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ describe("executeSandboxRunStream", () => {
9696
installationId: 100,
9797
repo: "owner/repo",
9898
task: "Ship it",
99+
promptStrategy: "bug-fix",
99100
shouldCommit: true,
100101
},
101102
});
@@ -108,13 +109,20 @@ describe("executeSandboxRunStream", () => {
108109
repo: "owner/repo",
109110
task: "Ship it",
110111
model: "mistral-large",
112+
promptStrategy: "bug-fix",
111113
shouldCommit: true,
112114
status: "completed",
113115
}),
114116
});
117+
expect(executeSandboxWorker).toHaveBeenCalledWith(
118+
expect.objectContaining({
119+
promptStrategy: "bug-fix",
120+
}),
121+
);
115122
expect(mockUpdateAppData).toHaveBeenCalledTimes(2);
116123
expect(mockUpdateAppData.mock.calls[1]?.[1]).toMatchObject({
117124
status: "completed",
125+
promptStrategy: "bug-fix",
118126
result: {
119127
success: true,
120128
result: { ok: true },

apps/api/src/services/apps/sandbox/__test__/run-data.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,40 @@ describe("sandbox run data helpers", () => {
7777
});
7878
});
7979

80+
it("parses and returns optional prompt strategy metadata", () => {
81+
const parsed = parseSandboxRunData({
82+
runId: "run-3",
83+
installationId: 101,
84+
repo: "owner/repo",
85+
task: "Fix flaky tests",
86+
model: "mistral-large",
87+
promptStrategy: "bug-fix",
88+
shouldCommit: false,
89+
status: "running",
90+
startedAt: "2026-02-17T12:00:00.000Z",
91+
updatedAt: "2026-02-17T12:00:01.000Z",
92+
});
93+
94+
expect(parsed).toMatchObject({
95+
promptStrategy: "bug-fix",
96+
});
97+
98+
expect(
99+
toSandboxRunResponse({
100+
runId: "run-4",
101+
installationId: 100,
102+
repo: "owner/repo",
103+
task: "Refactor duplicate code",
104+
model: "mistral-large",
105+
promptStrategy: "refactor",
106+
shouldCommit: false,
107+
status: "completed",
108+
startedAt: "2026-02-17T12:00:00.000Z",
109+
updatedAt: "2026-02-17T12:00:01.000Z",
110+
}).promptStrategy,
111+
).toBe("refactor");
112+
});
113+
80114
it("limits appended events to max length", () => {
81115
const result = appendSandboxRunEvent(
82116
[{ type: "event-1" }, { type: "event-2" }],

apps/api/src/services/apps/sandbox/execute-stream.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export async function executeSandboxRunStream(
111111
repo: payload.repo,
112112
task: payload.task,
113113
model,
114+
promptStrategy: payload.promptStrategy,
114115
shouldCommit: Boolean(payload.shouldCommit),
115116
status: "queued",
116117
startedAt: now,
@@ -152,6 +153,7 @@ export async function executeSandboxRunStream(
152153
repo: payload.repo,
153154
task: payload.task,
154155
model,
156+
promptStrategy: payload.promptStrategy,
155157
shouldCommit: payload.shouldCommit,
156158
installationId: payload.installationId,
157159
stream: true,

apps/api/src/services/apps/sandbox/run-data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function toSandboxRunResponse(data: SandboxRunData): SandboxRun {
3333
repo: data.repo,
3434
task: data.task,
3535
model: data.model,
36+
promptStrategy: data.promptStrategy,
3637
shouldCommit: data.shouldCommit,
3738
status: data.status,
3839
startedAt: data.startedAt,

apps/api/src/services/apps/sandbox/stream-proxy.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export function createSandboxEventProxyStream(
5656
let errorMessage: string | undefined;
5757
let completedAt: string | undefined;
5858
let cancellationReason: string | undefined;
59+
let promptStrategy = runData.promptStrategy;
5960

6061
const pushEvent = (event: SandboxRunEvent) => {
6162
const next = appendSandboxRunEvent(
@@ -110,6 +111,10 @@ export function createSandboxEventProxyStream(
110111
if (parsed.type === "run_started") {
111112
status = "running";
112113
}
114+
115+
if (parsed.promptStrategy) {
116+
promptStrategy = parsed.promptStrategy;
117+
}
113118
};
114119

115120
try {
@@ -238,6 +243,7 @@ export function createSandboxEventProxyStream(
238243
status,
239244
result,
240245
error: status === "failed" ? errorMessage : undefined,
246+
promptStrategy,
241247
events,
242248
updatedAt: new Date().toISOString(),
243249
completedAt,

apps/api/src/services/functions/sandbox.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { IFunction } from "~/types";
22
import { executeSandboxWorker } from "~/services/sandbox/worker";
3+
import {
4+
sandboxPromptStrategySchema,
5+
type SandboxPromptStrategy,
6+
} from "@assistant/schemas";
37

48
export const run_feature_implementation: IFunction = {
59
name: "run_feature_implementation",
@@ -22,6 +26,11 @@ export const run_feature_implementation: IFunction = {
2226
type: "string",
2327
description: "Model to use (required if not configured in settings)",
2428
},
29+
promptStrategy: {
30+
type: "string",
31+
description:
32+
"Optional prompting strategy (auto, feature-delivery, bug-fix, refactor, test-hardening)",
33+
},
2534
shouldCommit: {
2635
type: "boolean",
2736
description:
@@ -42,13 +51,21 @@ export const run_feature_implementation: IFunction = {
4251
_app_url,
4352
_conversationManager,
4453
) => {
45-
const { repo, task, model, shouldCommit } = args as {
54+
const { repo, task, model, promptStrategy, shouldCommit } = args as {
4655
repo: string;
4756
task: string;
4857
model?: string;
58+
promptStrategy?: string;
4959
shouldCommit?: boolean;
5060
installationId?: number;
5161
};
62+
const parsedPromptStrategyResult = sandboxPromptStrategySchema.safeParse(
63+
typeof promptStrategy === "string" ? promptStrategy.trim() : undefined,
64+
);
65+
const parsedPromptStrategy: SandboxPromptStrategy | undefined =
66+
parsedPromptStrategyResult.success
67+
? parsedPromptStrategyResult.data
68+
: undefined;
5269

5370
if (!request.context || !request.user) {
5471
throw new Error("User context is required for sandbox execution");
@@ -63,6 +80,7 @@ export const run_feature_implementation: IFunction = {
6380
repo,
6481
task,
6582
model,
83+
promptStrategy: parsedPromptStrategy,
6684
shouldCommit,
6785
installationId:
6886
typeof (args as { installationId?: unknown }).installationId ===

apps/api/src/services/sandbox/worker.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ServiceContext } from "~/lib/context/serviceContext";
22
import type {
3+
SandboxPromptStrategy,
34
SandboxTaskType,
45
SandboxWorkerExecuteRequest,
56
} from "@assistant/schemas";
@@ -23,6 +24,7 @@ export interface ExecuteSandboxWorkerOptions {
2324
task: string;
2425
model?: string;
2526
taskType?: SandboxTaskType;
27+
promptStrategy?: SandboxPromptStrategy;
2628
shouldCommit?: boolean;
2729
installationId?: number;
2830
stream?: boolean;
@@ -94,6 +96,7 @@ export async function executeSandboxWorker(
9496
repo,
9597
task,
9698
taskType,
99+
promptStrategy,
97100
shouldCommit,
98101
installationId,
99102
stream,
@@ -139,6 +142,7 @@ export async function executeSandboxWorker(
139142
repo,
140143
task,
141144
model,
145+
promptStrategy,
142146
shouldCommit: Boolean(shouldCommit),
143147
polychatApiUrl: resolveApiBaseUrl(env),
144148
installationId,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {
2+
SANDBOX_PROMPT_STRATEGIES,
3+
type SandboxPromptStrategy,
4+
} from "@assistant/schemas";
5+
6+
interface PromptStrategyOption {
7+
value: SandboxPromptStrategy;
8+
label: string;
9+
description: string;
10+
}
11+
12+
const PROMPT_STRATEGY_LABELS: Record<SandboxPromptStrategy, string> = {
13+
auto: "Auto",
14+
"feature-delivery": "Feature Delivery",
15+
"bug-fix": "Bug Fix",
16+
refactor: "Refactor",
17+
"test-hardening": "Test Hardening",
18+
};
19+
20+
const PROMPT_STRATEGY_DESCRIPTIONS: Record<SandboxPromptStrategy, string> = {
21+
auto: "Let the worker choose based on the task and repository context.",
22+
"feature-delivery": "Optimise for shipping user-facing functionality safely.",
23+
"bug-fix": "Focus on root-cause analysis and regression prevention.",
24+
refactor:
25+
"Prioritise maintainability improvements while preserving behaviour.",
26+
"test-hardening":
27+
"Focus on high-signal validation, state, and error-path coverage.",
28+
};
29+
30+
export const sandboxPromptStrategyOptions: PromptStrategyOption[] =
31+
SANDBOX_PROMPT_STRATEGIES.map((strategy) => ({
32+
value: strategy,
33+
label: PROMPT_STRATEGY_LABELS[strategy],
34+
description: PROMPT_STRATEGY_DESCRIPTIONS[strategy],
35+
}));
36+
37+
export function getSandboxPromptStrategyLabel(
38+
strategy?: SandboxPromptStrategy,
39+
): string {
40+
if (!strategy) {
41+
return PROMPT_STRATEGY_LABELS.auto;
42+
}
43+
return PROMPT_STRATEGY_LABELS[strategy] ?? strategy;
44+
}
45+
46+
export function getSandboxPromptStrategyDescription(
47+
strategy: SandboxPromptStrategy,
48+
): string {
49+
return PROMPT_STRATEGY_DESCRIPTIONS[strategy];
50+
}
51+
52+
export function isSandboxPromptStrategy(
53+
value: string,
54+
): value is SandboxPromptStrategy {
55+
for (const strategy of SANDBOX_PROMPT_STRATEGIES) {
56+
if (strategy === value) {
57+
return true;
58+
}
59+
}
60+
61+
return false;
62+
}
63+
64+
export function parseSandboxPromptStrategy(
65+
value: string,
66+
fallback: SandboxPromptStrategy = "auto",
67+
): SandboxPromptStrategy {
68+
return isSandboxPromptStrategy(value) ? value : fallback;
69+
}

apps/app/src/pages/apps/sandbox/[connectionId].tsx

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
CardHeader,
3131
CardTitle,
3232
Checkbox,
33+
FormSelect,
3334
Input,
3435
Label,
3536
Textarea,
@@ -44,9 +45,19 @@ import {
4445
import { useAuthStatus } from "~/hooks/useAuth";
4546
import { formatRelativeTime } from "~/lib/dates";
4647
import { streamSandboxRun } from "~/lib/api/sandbox";
48+
import {
49+
getSandboxPromptStrategyDescription,
50+
getSandboxPromptStrategyLabel,
51+
parseSandboxPromptStrategy,
52+
sandboxPromptStrategyOptions,
53+
} from "~/lib/sandbox/prompt-strategies";
4754
import { normaliseGitHubRepoInput } from "~/lib/sandbox/repositories";
4855
import { cn } from "~/lib/utils";
49-
import type { SandboxRun, SandboxRunEvent } from "~/types/sandbox";
56+
import type {
57+
SandboxPromptStrategy,
58+
SandboxRun,
59+
SandboxRunEvent,
60+
} from "~/types/sandbox";
5061
import {
5162
REPO_PATTERN,
5263
REPO_STORAGE_PREFIX,
@@ -175,6 +186,8 @@ export default function SandboxConnectionPage() {
175186
const [repo, setRepo] = useState("");
176187
const [task, setTask] = useState("");
177188
const [model, setModel] = useState("");
189+
const [promptStrategy, setPromptStrategy] =
190+
useState<SandboxPromptStrategy>("auto");
178191
const [shouldCommit, setShouldCommit] = useState(true);
179192
const [isSubmitting, setIsSubmitting] = useState(false);
180193
const [activeRunId, setActiveRunId] = useState<string | undefined>();
@@ -412,6 +425,7 @@ export default function SandboxConnectionPage() {
412425
repo: trimmedRepo,
413426
task: trimmedTask,
414427
model: model.trim() || undefined,
428+
promptStrategy,
415429
shouldCommit,
416430
},
417431
{
@@ -593,7 +607,7 @@ export default function SandboxConnectionPage() {
593607
</CardDescription>
594608
</CardHeader>
595609
<CardContent className="space-y-4">
596-
<div className="grid gap-4 md:grid-cols-2">
610+
<div className="grid gap-4 md:grid-cols-3">
597611
<div className="space-y-2">
598612
<Label htmlFor="sandbox-repo-input">Repository</Label>
599613
<Input
@@ -635,6 +649,25 @@ export default function SandboxConnectionPage() {
635649
set, backend defaults to <code>mistral-large</code>.
636650
</p>
637651
</div>
652+
<div className="space-y-2">
653+
<FormSelect
654+
id="sandbox-prompt-strategy"
655+
label="Prompt strategy"
656+
value={promptStrategy}
657+
onChange={(event) =>
658+
setPromptStrategy(
659+
parseSandboxPromptStrategy(event.target.value),
660+
)
661+
}
662+
options={sandboxPromptStrategyOptions.map((option) => ({
663+
value: option.value,
664+
label: option.label,
665+
}))}
666+
/>
667+
<p className="text-xs text-muted-foreground">
668+
{getSandboxPromptStrategyDescription(promptStrategy)}
669+
</p>
670+
</div>
638671
</div>
639672
<div className="space-y-2">
640673
<Label htmlFor="sandbox-task-input">Task</Label>
@@ -838,6 +871,12 @@ export default function SandboxConnectionPage() {
838871
<p className="text-muted-foreground">
839872
{summariseRunResult(selectedRun)}
840873
</p>
874+
<p>
875+
<span className="font-medium">Prompt strategy:</span>{" "}
876+
{getSandboxPromptStrategyLabel(
877+
selectedRun.promptStrategy,
878+
)}
879+
</p>
841880
{typeof selectedRun.result?.branchName === "string" && (
842881
<p>
843882
<span className="font-medium">Branch:</span>{" "}

0 commit comments

Comments
 (0)