Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a058c93
fix: deliver generated system prompt files
whoisasx Jun 29, 2026
09b517a
fix: satisfy lint on prompt file permissions
whoisasx Jun 29, 2026
7297307
feat: add project prompt rules
whoisasx Jun 29, 2026
7141105
Merge remote-tracking branch 'origin/main' into ao/reverbcode-2/issue…
whoisasx Jun 29, 2026
5f96ef6
fix: renumber pr reviews migration
whoisasx Jun 29, 2026
ceef121
refactor: move session prompts into prompt package
whoisasx Jun 29, 2026
ffdd78c
refactor: keep session prompts with manager
whoisasx Jun 29, 2026
e1c4e94
fix: enrich worker feedback prompts
whoisasx Jun 29, 2026
e5ab16c
fix: adjust prompt and daemon terminal handling
whoisasx Jun 30, 2026
077042a
Merge branch 'main' into ao/reverbcode-2/issue-2272-prompts
whoisasx Jun 30, 2026
b46f12f
fix: preserve spawn issue context field
whoisasx Jun 30, 2026
5ae01fb
fix: address prompt review feedback
whoisasx Jul 3, 2026
54449ec
Merge remote-tracking branch 'origin/main' into ao/reverbcode-2/issue…
whoisasx Jul 3, 2026
b3f59d6
fix: goimports legacy import mapping
whoisasx Jul 3, 2026
7bd7729
feat: hydrate issue context for worker spawns
whoisasx Jul 3, 2026
b1544c3
fix: prefer inline system prompts when available
whoisasx Jul 3, 2026
fa68474
fix: clean prompt files and reduce nudge noise
whoisasx Jul 3, 2026
fd04e11
fix: reapply system prompts on restore
whoisasx Jul 3, 2026
fc19c48
fix: align adapter prompt injection
whoisasx Jul 3, 2026
91905a2
Merge remote-tracking branch 'origin/main' into ao/reverbcode-2/issue…
whoisasx Jul 3, 2026
8dbc75d
chore: format frontend nightly workflow
whoisasx Jul 3, 2026
9e39df4
Merge remote-tracking branch 'origin/main' into ao/reverbcode-2/issue…
whoisasx Jul 4, 2026
d6863e2
chore: format frontend merge changes
whoisasx Jul 4, 2026
1500782
fix: validate env-prefixed agent launch binaries
whoisasx Jul 4, 2026
8449f5b
Merge remote-tracking branch 'origin/main' into ao/reverbcode-2/issue…
whoisasx Jul 4, 2026
31e5f00
Merge remote-tracking branch 'origin/main' into ao/reverbcode-2/issue…
whoisasx Jul 6, 2026
f151fe8
fix: address prompt review gaps
whoisasx Jul 6, 2026
4bec0db
docs: update project config summary
whoisasx Jul 7, 2026
1c9f03e
Merge remote-tracking branch 'origin/main' into ao/reverbcode-2/issue…
whoisasx Jul 7, 2026
6736d1e
fix: pass prompt files through harness switches
whoisasx Jul 7, 2026
619395c
fix: inject Devin system prompt via hooks
nikhilachale Jul 7, 2026
f0c9cb9
Merge remote-tracking branch 'upstream/main' into ao/reverbcode-2/iss…
nikhilachale Jul 7, 2026
06a9e97
docs: update project config summary
whoisasx Jul 7, 2026
aff3354
Merge upstream feature branch after mainline resolution
nikhilachale Jul 7, 2026
051d1e9
fix: restore prompt enrichment after merge
whoisasx Jul 8, 2026
6207e53
Merge remote-tracking branch 'origin/main' into ao/reverbcode-2/issue…
whoisasx Jul 8, 2026
b558b91
fix: strengthen session prompt guidance
whoisasx Jul 8, 2026
6da8bb6
feat: enhance Devin's launch and workspace trust management
nikhilachale Jul 8, 2026
94443d3
feat: update Aider, Goose, and Kimi adapters for interactive session …
nikhilachale Jul 9, 2026
369c201
feat: implement custom agent handling and messaging for Copilot integ…
nikhilachale Jul 9, 2026
e19af27
feat: update Copilot integration to use session-specific custom agent…
nikhilachale Jul 9, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var Derivers = map[string]DeriveFunc{
"agy": agy.DeriveActivityState,
"opencode": opencode.DeriveActivityState,
"goose": activitystate.StandardDeriveActivityState,
"devin": activitystate.StandardDeriveActivityState,
"cursor": activitystate.StandardDeriveActivityState,
"qwen": activitystate.StandardDeriveActivityState,
"copilot": activitystate.StandardDeriveActivityState,
Expand Down
38 changes: 23 additions & 15 deletions backend/internal/adapters/agent/aider/aider.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package aider implements the Aider agent adapter: launching headless Aider
// Package aider implements the Aider agent adapter: launching interactive Aider
// worker sessions.
//
// Aider is a Tier C adapter: it has no lifecycle hook surface, no native
Expand Down Expand Up @@ -54,38 +54,46 @@ func (p *Plugin) Manifest() adapters.Manifest {
}
}

// GetLaunchCommand builds the argv to start a headless Aider session:
// GetLaunchCommand builds the argv to start an interactive Aider session:
//
// aider -m <prompt> [permission flags] --no-check-update --no-stream --no-pretty [--read <system prompt file>]
// aider [permission flags] --no-check-update --no-stream --no-pretty [--read <context file>]
//
// The prompt is delivered with `-m <prompt>` rather than positionally: Aider
// treats positional arguments as files to add to the chat, so a positional
// prompt would be misread. The `-m` pair is only appended when a prompt is set.
// Prompted tasks are delivered after startup by the session manager rather than
// via `-m`. Aider's `-m <prompt>` mode is one-shot: it runs the message and then
// exits, which makes AO workers disappear as soon as the answer is printed.
//
// Aider has no inline system-prompt mechanism; only SystemPromptFile is honored
// via --read. The --no-check-update --no-stream --no-pretty flags keep Aider
// well-behaved in a non-interactive, captured-output context.
// Aider has no native system-prompt injection mechanism. AO's prompt file is
// supplied with --read as read-only context so the agent can see the standing
// instructions, but this is context fallback rather than system-message
// replacement. The --no-check-update --no-stream --no-pretty flags keep the
// terminal output stable in AO's captured-output context.
func (p *Plugin) GetLaunchCommand(ctx context.Context, cfg ports.LaunchConfig) (cmd []string, err error) {
binary, err := p.aiderBinary(ctx)
if err != nil {
return nil, err
}

cmd = []string{binary}
if cfg.Prompt != "" {
cmd = append(cmd, "-m", cfg.Prompt)
}
appendApprovalFlags(&cmd, cfg.Permissions)
cmd = append(cmd, "--no-check-update", "--no-stream", "--no-pretty")
if cfg.SystemPromptFile != "" {
cmd = append(cmd, "--read", cfg.SystemPromptFile)
}
// aider has no inline system-prompt mechanism; only SystemPromptFile is
// honored via --read. A cfg.SystemPrompt with no file is intentionally
// dropped here rather than written to disk.
// aider has no inline system-prompt mechanism. A cfg.SystemPrompt with no
// file is intentionally dropped here rather than written to disk.
return cmd, nil
}

// GetPromptDeliveryStrategy reports that AO should inject prompted Aider tasks
// into the interactive terminal after startup. Aider's `-m` mode exits after
// the single message completes.
func (p *Plugin) GetPromptDeliveryStrategy(ctx context.Context, _ ports.LaunchConfig) (ports.PromptDeliveryStrategy, error) {
if err := ctx.Err(); err != nil {
return "", err
}
return ports.PromptDeliveryAfterStart, nil
}

// normalizePermissionMode collapses an empty mode onto PermissionModeDefault so
// callers can switch over a stable set of values.
func normalizePermissionMode(mode ports.PermissionMode) ports.PermissionMode {
Expand Down
23 changes: 14 additions & 9 deletions backend/internal/adapters/agent/aider/aider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ func TestGetPromptDeliveryStrategy(t *testing.T) {
if err != nil {
t.Fatalf("err: %v", err)
}
if s != ports.PromptDeliveryInCommand {
t.Fatalf("strategy = %q, want %q", s, ports.PromptDeliveryInCommand)
if s != ports.PromptDeliveryAfterStart {
t.Fatalf("strategy = %q, want %q", s, ports.PromptDeliveryAfterStart)
}
}

func TestGetLaunchCommandDeliversPromptWithFlag(t *testing.T) {
func TestGetLaunchCommandOmitsPromptForInteractiveDelivery(t *testing.T) {
p := &Plugin{resolvedBinary: "aider"}
cmd, err := p.GetLaunchCommand(context.Background(), ports.LaunchConfig{
Prompt: "add a health check",
Expand All @@ -58,10 +58,15 @@ func TestGetLaunchCommandDeliversPromptWithFlag(t *testing.T) {
t.Fatal(err)
}

want := []string{"aider", "-m", "add a health check", "--no-check-update", "--no-stream", "--no-pretty"}
want := []string{"aider", "--no-check-update", "--no-stream", "--no-pretty"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("unexpected command\nwant: %#v\n got: %#v", want, cmd)
}
for _, arg := range cmd {
if arg == "-m" || arg == "add a health check" {
t.Fatalf("cmd = %#v unexpectedly contains prompt argv", cmd)
}
}
}

func TestGetLaunchCommandOmitsPromptFlagWhenEmpty(t *testing.T) {
Expand All @@ -82,7 +87,7 @@ func TestGetLaunchCommandOmitsPromptFlagWhenEmpty(t *testing.T) {
}
}

func TestGetLaunchCommandAlwaysAppendsHeadlessFlags(t *testing.T) {
func TestGetLaunchCommandAlwaysAppendsStableOutputFlags(t *testing.T) {
p := &Plugin{resolvedBinary: "aider"}
cmd, err := p.GetLaunchCommand(context.Background(), ports.LaunchConfig{Prompt: "do the thing"})
if err != nil {
Expand All @@ -98,7 +103,7 @@ func TestGetLaunchCommandAlwaysAppendsHeadlessFlags(t *testing.T) {
}
}
if !found {
t.Fatalf("cmd = %#v missing headless flag %q", cmd, want)
t.Fatalf("cmd = %#v missing stable output flag %q", cmd, want)
}
}
}
Expand Down Expand Up @@ -176,7 +181,7 @@ func TestGetLaunchCommandMapsPermissionModes(t *testing.T) {
}
}

func TestGetLaunchCommandSystemPromptFileUsesRead(t *testing.T) {
func TestGetLaunchCommandSystemPromptFileUsesReadOnlyContext(t *testing.T) {
p := &Plugin{resolvedBinary: "aider"}
cmd, err := p.GetLaunchCommand(context.Background(), ports.LaunchConfig{
Prompt: "do the thing",
Expand All @@ -186,7 +191,7 @@ func TestGetLaunchCommandSystemPromptFileUsesRead(t *testing.T) {
t.Fatal(err)
}

want := []string{"aider", "-m", "do the thing", "--no-check-update", "--no-stream", "--no-pretty", "--read", "/tmp/system.md"}
want := []string{"aider", "--no-check-update", "--no-stream", "--no-pretty", "--read", "/tmp/system.md"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("cmd = %#v, want %#v", cmd, want)
}
Expand All @@ -202,7 +207,7 @@ func TestGetLaunchCommandInlineSystemPromptIsDropped(t *testing.T) {
t.Fatal(err)
}

want := []string{"aider", "-m", "do the thing", "--no-check-update", "--no-stream", "--no-pretty"}
want := []string{"aider", "--no-check-update", "--no-stream", "--no-pretty"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("cmd = %#v, want %#v", cmd, want)
}
Expand Down
29 changes: 6 additions & 23 deletions backend/internal/adapters/agent/amp/amp.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,11 @@ func (p *Plugin) Manifest() adapters.Manifest {

// GetLaunchCommand builds the argv to start a new interactive Amp session:
//
// amp [--permission-mode <mode>] [-- <prompt>]
// amp [-x <prompt>]
//
// The prompt is passed after `--` so a prompt beginning with "-" is not
// mistaken for a flag. Amp has no documented system-prompt flag, so
// SystemPrompt and SystemPromptFile are intentionally ignored until Amp exposes
// a supported instruction mechanism.
// Amp's current CLI has no documented per-run permission or system-prompt flag.
// When AO has an initial prompt, it is sent through execute mode (`-x`), whose
// next argv element is the prompt text so a leading "-" is not parsed as a flag.
func (p *Plugin) GetLaunchCommand(ctx context.Context, cfg ports.LaunchConfig) (cmd []string, err error) {
if err := ctx.Err(); err != nil {
return nil, err
Expand All @@ -66,9 +65,8 @@ func (p *Plugin) GetLaunchCommand(ctx context.Context, cfg ports.LaunchConfig) (
}

cmd = []string{binary}
appendPermissionFlags(&cmd, cfg.Permissions)
if cfg.Prompt != "" {
cmd = append(cmd, "--", cfg.Prompt)
cmd = append(cmd, "-x", cfg.Prompt)
}
return cmd, nil
}
Expand All @@ -89,25 +87,10 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig)
if err != nil {
return nil, false, err
}
// Capacity fits binary + up to two permission flags + --resume + sessionID.
cmd = make([]string, 0, 5)
cmd = append(cmd, binary)
appendPermissionFlags(&cmd, cfg.Permissions)
cmd = append(cmd, "--resume", agentSessionID)
cmd = []string{binary, "threads", "continue", agentSessionID}
return cmd, true, nil
}

func appendPermissionFlags(cmd *[]string, mode ports.PermissionMode) {
switch mode {
case ports.PermissionModeAcceptEdits:
*cmd = append(*cmd, "--permission-mode", "acceptEdits")
case ports.PermissionModeAuto:
*cmd = append(*cmd, "--permission-mode", "auto")
case ports.PermissionModeBypassPermissions:
*cmd = append(*cmd, "--permission-mode", "bypassPermissions")
}
}

var ampBinarySpec = binaryutil.BinarySpec{
Label: "amp",
Names: []string{"amp"},
Expand Down
63 changes: 30 additions & 33 deletions backend/internal/adapters/agent/amp/amp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,69 +59,64 @@ func TestGetLaunchCommandBypassWithPrompt(t *testing.T) {
t.Fatal(err)
}

want := []string{"amp", "--permission-mode", "bypassPermissions", "--", "-add a health check"}
want := []string{"amp", "-x", "-add a health check"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("unexpected command\nwant: %#v\n got: %#v", want, cmd)
}
}

func TestGetLaunchCommandMapsPermissionModes(t *testing.T) {
tests := []struct {
name string
mode ports.PermissionMode
want []string
wantAbsent string
}{
{"default omits flag", ports.PermissionModeDefault, []string{"amp"}, "--permission-mode"},
{"empty omits flag", "", []string{"amp"}, "--permission-mode"},
{"accept edits", ports.PermissionModeAcceptEdits, []string{"amp", "--permission-mode", "acceptEdits"}, ""},
{"auto", ports.PermissionModeAuto, []string{"amp", "--permission-mode", "auto"}, ""},
{"bypass", ports.PermissionModeBypassPermissions, []string{"amp", "--permission-mode", "bypassPermissions"}, ""},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
func TestGetLaunchCommandPermissionModesEmitNoFlag(t *testing.T) {
modes := []ports.PermissionMode{
ports.PermissionModeDefault,
"",
ports.PermissionModeAcceptEdits,
ports.PermissionModeAuto,
ports.PermissionModeBypassPermissions,
}

for _, mode := range modes {
t.Run(string(mode), func(t *testing.T) {
p := &Plugin{resolvedBinary: "amp"}
cmd, err := p.GetLaunchCommand(context.Background(), ports.LaunchConfig{Permissions: tt.mode})
cmd, err := p.GetLaunchCommand(context.Background(), ports.LaunchConfig{Permissions: mode})
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(cmd, tt.want) {
t.Fatalf("cmd = %#v, want %#v", cmd, tt.want)
want := []string{"amp"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("cmd = %#v, want %#v", cmd, want)
}
if tt.wantAbsent != "" {
for _, arg := range cmd {
if arg == tt.wantAbsent {
t.Fatalf("cmd = %#v unexpectedly contains %q", cmd, tt.wantAbsent)
}
for _, arg := range cmd {
if arg == "--permission-mode" {
t.Fatalf("cmd = %#v unexpectedly contains permission flag", cmd)
}
}
})
}
}

func TestGetLaunchCommandIgnoresInlineSystemPrompt(t *testing.T) {
func TestGetLaunchCommandIgnoresSystemPrompt(t *testing.T) {
p := &Plugin{resolvedBinary: "amp"}
cmd, err := p.GetLaunchCommand(context.Background(), ports.LaunchConfig{
SystemPrompt: "follow repo rules",
Prompt: "do the thing",
SystemPrompt: "follow repo rules",
SystemPromptFile: "/tmp/system.md",
Prompt: "do the thing",
})
if err != nil {
t.Fatal(err)
}

want := []string{"amp", "--", "do the thing"}
want := []string{"amp", "-x", "do the thing"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("cmd = %#v, want %#v", cmd, want)
}
assertAmpSystemPromptFlagsAbsent(t, cmd)
}

func TestGetLaunchCommandIgnoresSystemPromptFile(t *testing.T) {
func TestGetLaunchCommandOmitsExecuteModeWithoutPrompt(t *testing.T) {
p := &Plugin{resolvedBinary: "amp"}
cmd, err := p.GetLaunchCommand(context.Background(), ports.LaunchConfig{
SystemPromptFile: "/tmp/system.md",
SystemPrompt: "inline ignored",
SystemPrompt: "inline wins",
})
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -150,7 +145,9 @@ func TestGetRestoreCommand(t *testing.T) {
Session: ports.SessionRef{
Metadata: map[string]string{ports.MetadataKeyAgentSessionID: "T-abc123"},
},
Permissions: ports.PermissionModeBypassPermissions,
Permissions: ports.PermissionModeBypassPermissions,
SystemPrompt: "restore inline wins",
SystemPromptFile: "/tmp/system.md",
})
if err != nil {
t.Fatal(err)
Expand All @@ -159,7 +156,7 @@ func TestGetRestoreCommand(t *testing.T) {
t.Fatal("ok=false, want true")
}

want := []string{"amp", "--permission-mode", "bypassPermissions", "--resume", "T-abc123"}
want := []string{"amp", "threads", "continue", "T-abc123"}
if !reflect.DeepEqual(cmd, want) {
t.Fatalf("cmd = %#v, want %#v", cmd, want)
}
Expand Down
25 changes: 14 additions & 11 deletions backend/internal/adapters/agent/auggie/auggie.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
//
// Launch shape:
//
// auggie --print [--instruction-file <f> | --instruction <s>] [-- <prompt>]
// auggie --print [--rules <f>] [-- <prompt>]
//
// The prompt is the print-mode positional, passed after `--` so a prompt
// beginning with "-" is not mistaken for a flag. A system prompt, when supplied,
// is injected via Auggie's `--instruction-file` / `--instruction` flags, which
// append guidance to the workspace rules.
// beginning with "-" is not mistaken for a flag. A system prompt, when supplied
// as an AO-owned file, is injected via Auggie's `--rules` flag, which appends
// guidance to the workspace rules.
//
// Permissions: Auggie has no single "approve everything" flag. It governs
// unattended tool/file approval through granular `--permission <tool>:<allow|deny>`
Expand Down Expand Up @@ -80,11 +80,12 @@ func (p *Plugin) Manifest() adapters.Manifest {

// GetLaunchCommand builds the argv to start a new headless Auggie session:
//
// auggie --print [--instruction-file <f> | --instruction <s>] [-- <prompt>]
// auggie --print [--rules <f>] [-- <prompt>]
//
// The prompt is passed after `--` so a prompt beginning with "-" is not mistaken
// for a flag. A system prompt is injected via --instruction-file / --instruction,
// mirroring the system-prompt handling of the other adapters.
// for a flag. Auggie's `--instruction` flags are the task input, not a rule or
// system-prompt surface; AO standing instructions use `--rules` when the
// manager provides a prompt file.
func (p *Plugin) GetLaunchCommand(ctx context.Context, cfg ports.LaunchConfig) (cmd []string, err error) {
if err := ctx.Err(); err != nil {
return nil, err
Expand All @@ -96,9 +97,7 @@ func (p *Plugin) GetLaunchCommand(ctx context.Context, cfg ports.LaunchConfig) (

cmd = []string{binary, "--print"}
if cfg.SystemPromptFile != "" {
cmd = append(cmd, "--instruction-file", cfg.SystemPromptFile)
} else if cfg.SystemPrompt != "" {
cmd = append(cmd, "--instruction", cfg.SystemPrompt)
cmd = append(cmd, "--rules", cfg.SystemPromptFile)
}
if cfg.Prompt != "" {
cmd = append(cmd, "--", cfg.Prompt)
Expand Down Expand Up @@ -126,7 +125,11 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig)
if err != nil {
return nil, false, err
}
cmd = []string{binary, "--print", "--resume", agentSessionID}
cmd = []string{binary, "--print"}
if cfg.SystemPromptFile != "" {
cmd = append(cmd, "--rules", cfg.SystemPromptFile)
}
cmd = append(cmd, "--resume", agentSessionID)
return cmd, true, nil
}

Expand Down
Loading
Loading