diff --git a/backend/internal/adapters/agent/amp/amp.go b/backend/internal/adapters/agent/amp/amp.go index efd6430e5..9ceac7b12 100644 --- a/backend/internal/adapters/agent/amp/amp.go +++ b/backend/internal/adapters/agent/amp/amp.go @@ -122,6 +122,11 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig) cmd = make([]string, 0, 5) cmd = append(cmd, binary) appendPermissionFlags(&cmd, cfg.Permissions) + if cfg.SystemPrompt != "" { + cmd = append(cmd, "--append-system-prompt", cfg.SystemPrompt) + } else if cfg.SystemPromptFile != "" { + cmd = append(cmd, "--append-system-prompt-file", cfg.SystemPromptFile) + } cmd = append(cmd, "--resume", agentSessionID) return cmd, true, nil } diff --git a/backend/internal/adapters/agent/amp/amp_test.go b/backend/internal/adapters/agent/amp/amp_test.go index 68f21b97e..d0045cb95 100644 --- a/backend/internal/adapters/agent/amp/amp_test.go +++ b/backend/internal/adapters/agent/amp/amp_test.go @@ -138,7 +138,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) @@ -147,7 +149,7 @@ func TestGetRestoreCommand(t *testing.T) { t.Fatal("ok=false, want true") } - want := []string{"amp", "--permission-mode", "bypassPermissions", "--resume", "T-abc123"} + want := []string{"amp", "--permission-mode", "bypassPermissions", "--append-system-prompt", "restore inline wins", "--resume", "T-abc123"} if !reflect.DeepEqual(cmd, want) { t.Fatalf("cmd = %#v, want %#v", cmd, want) } diff --git a/backend/internal/adapters/agent/auggie/auggie.go b/backend/internal/adapters/agent/auggie/auggie.go index 1ef9f2937..0dc813174 100644 --- a/backend/internal/adapters/agent/auggie/auggie.go +++ b/backend/internal/adapters/agent/auggie/auggie.go @@ -152,7 +152,13 @@ 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.SystemPrompt != "" { + cmd = append(cmd, "--instruction", cfg.SystemPrompt) + } else if cfg.SystemPromptFile != "" { + cmd = append(cmd, "--instruction-file", cfg.SystemPromptFile) + } + cmd = append(cmd, "--resume", agentSessionID) return cmd, true, nil } diff --git a/backend/internal/adapters/agent/auggie/auggie_test.go b/backend/internal/adapters/agent/auggie/auggie_test.go index 8906b1670..1c313bfdb 100644 --- a/backend/internal/adapters/agent/auggie/auggie_test.go +++ b/backend/internal/adapters/agent/auggie/auggie_test.go @@ -134,7 +134,9 @@ func TestGetRestoreCommand(t *testing.T) { Session: ports.SessionRef{ Metadata: map[string]string{ports.MetadataKeyAgentSessionID: "sess-abc123"}, }, - Permissions: ports.PermissionModeBypassPermissions, + Permissions: ports.PermissionModeBypassPermissions, + SystemPrompt: "restore inline wins", + SystemPromptFile: "/tmp/system.md", }) if err != nil { t.Fatal(err) @@ -143,7 +145,7 @@ func TestGetRestoreCommand(t *testing.T) { t.Fatal("ok=false, want true") } - want := []string{"auggie", "--print", "--resume", "sess-abc123"} + want := []string{"auggie", "--print", "--instruction", "restore inline wins", "--resume", "sess-abc123"} if !reflect.DeepEqual(cmd, want) { t.Fatalf("cmd = %#v, want %#v", cmd, want) } diff --git a/backend/internal/adapters/agent/autohand/autohand.go b/backend/internal/adapters/agent/autohand/autohand.go index 6fd093192..d7fca5a4d 100644 --- a/backend/internal/adapters/agent/autohand/autohand.go +++ b/backend/internal/adapters/agent/autohand/autohand.go @@ -110,8 +110,9 @@ func (p *Plugin) GetPromptDeliveryStrategy(ctx context.Context, cfg ports.Launch // GetRestoreCommand rebuilds the argv that continues an existing Autohand // session: `autohand resume [--path ] `. ok is false when // the hook-derived native session id has not landed yet, so callers can fall -// back to fresh launch behavior. Autohand's resume sub-command does not accept -// approval flags, so none are appended here. +// back to fresh launch behavior. Autohand's resume sub-command only accepts the +// workspace path and session id, so approval and system-prompt flags are not +// re-applied here. func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig) (cmd []string, ok bool, err error) { if err := ctx.Err(); err != nil { return nil, false, err diff --git a/backend/internal/adapters/agent/autohand/autohand_test.go b/backend/internal/adapters/agent/autohand/autohand_test.go index ddcec5f6d..04b039be0 100644 --- a/backend/internal/adapters/agent/autohand/autohand_test.go +++ b/backend/internal/adapters/agent/autohand/autohand_test.go @@ -152,7 +152,8 @@ func TestGetRestoreCommandReadsAgentSessionID(t *testing.T) { plugin := &Plugin{resolvedBinary: "autohand"} cmd, ok, err := plugin.GetRestoreCommand(context.Background(), ports.RestoreConfig{ - Permissions: ports.PermissionModeAuto, + Permissions: ports.PermissionModeAuto, + SystemPrompt: "restore instructions ignored by resume", Session: ports.SessionRef{ WorkspacePath: "/work/space", Metadata: map[string]string{ports.MetadataKeyAgentSessionID: "sess-123"}, diff --git a/backend/internal/adapters/agent/cline/cline.go b/backend/internal/adapters/agent/cline/cline.go index 512beeb65..21e178309 100644 --- a/backend/internal/adapters/agent/cline/cline.go +++ b/backend/internal/adapters/agent/cline/cline.go @@ -122,6 +122,9 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig) cmd = make([]string, 0, 8) cmd = append(cmd, binary, "--json") appendApprovalFlags(&cmd, cfg.Permissions) + if cfg.SystemPrompt != "" { + cmd = append(cmd, "-s", cfg.SystemPrompt) + } cmd = append(cmd, "--id", agentSessionID) return cmd, true, nil } diff --git a/backend/internal/adapters/agent/cline/cline_test.go b/backend/internal/adapters/agent/cline/cline_test.go index 7b33121f4..e482ac409 100644 --- a/backend/internal/adapters/agent/cline/cline_test.go +++ b/backend/internal/adapters/agent/cline/cline_test.go @@ -241,7 +241,8 @@ func TestGetRestoreCommandReadsAgentSessionID(t *testing.T) { plugin := &Plugin{resolvedBinary: "cline"} cmd, ok, err := plugin.GetRestoreCommand(context.Background(), ports.RestoreConfig{ - Permissions: ports.PermissionModeAuto, + Permissions: ports.PermissionModeAuto, + SystemPrompt: "restore instructions", Session: ports.SessionRef{ Metadata: map[string]string{ports.MetadataKeyAgentSessionID: "session-123"}, }, @@ -256,6 +257,7 @@ func TestGetRestoreCommandReadsAgentSessionID(t *testing.T) { "cline", "--json", "--auto-approve", "true", + "-s", "restore instructions", "--id", "session-123", } if !reflect.DeepEqual(cmd, want) { diff --git a/backend/internal/adapters/agent/codex/codex.go b/backend/internal/adapters/agent/codex/codex.go index 3867efcec..bbb220328 100644 --- a/backend/internal/adapters/agent/codex/codex.go +++ b/backend/internal/adapters/agent/codex/codex.go @@ -125,6 +125,11 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig) appendSessionHookFlags(&cmd) appendTerminalCompatibilityFlags(&cmd) appendWorkspaceTrustFlag(&cmd, cfg.Session.WorkspacePath) + if cfg.SystemPrompt != "" { + cmd = append(cmd, "-c", "developer_instructions="+codexTOMLConfigString(cfg.SystemPrompt)) + } else if cfg.SystemPromptFile != "" { + cmd = append(cmd, "-c", "model_instructions_file="+cfg.SystemPromptFile) + } cmd = append(cmd, agentSessionID) return cmd, true, nil } diff --git a/backend/internal/adapters/agent/codex/codex_test.go b/backend/internal/adapters/agent/codex/codex_test.go index 289feba0e..a8bd58b4a 100644 --- a/backend/internal/adapters/agent/codex/codex_test.go +++ b/backend/internal/adapters/agent/codex/codex_test.go @@ -398,7 +398,9 @@ func TestGetRestoreCommandReadsAgentSessionID(t *testing.T) { workspace := canonicalTempDir(t) cmd, ok, err := plugin.GetRestoreCommand(context.Background(), ports.RestoreConfig{ - Permissions: ports.PermissionModeAuto, + Permissions: ports.PermissionModeAuto, + SystemPrompt: "restore inline wins", + SystemPromptFile: filepath.Join("tmp", "restore-system.md"), Session: ports.SessionRef{ Metadata: map[string]string{ports.MetadataKeyAgentSessionID: "thread-123"}, WorkspacePath: workspace, @@ -425,6 +427,7 @@ func TestGetRestoreCommandReadsAgentSessionID(t *testing.T) { } want = append(want, "-c", `projects={`+codexTOMLConfigString(workspace)+`={trust_level="trusted"}}`, + "-c", "developer_instructions="+codexTOMLConfigString("restore inline wins"), "thread-123", ) if !reflect.DeepEqual(cmd, want) { diff --git a/backend/internal/adapters/agent/droid/droid.go b/backend/internal/adapters/agent/droid/droid.go index 7198d3f27..b306fb94e 100644 --- a/backend/internal/adapters/agent/droid/droid.go +++ b/backend/internal/adapters/agent/droid/droid.go @@ -151,6 +151,11 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig) return nil, false, err } cmd = append(cmd, settingsArgs...) + if cfg.SystemPrompt != "" { + cmd = append(cmd, "--append-system-prompt", cfg.SystemPrompt) + } else if cfg.SystemPromptFile != "" { + cmd = append(cmd, "--append-system-prompt-file", cfg.SystemPromptFile) + } cmd = append(cmd, "-r", agentSessionID) return cmd, true, nil } diff --git a/backend/internal/adapters/agent/droid/droid_test.go b/backend/internal/adapters/agent/droid/droid_test.go index 2607372c3..ce3ed3607 100644 --- a/backend/internal/adapters/agent/droid/droid_test.go +++ b/backend/internal/adapters/agent/droid/droid_test.go @@ -141,6 +141,8 @@ func TestGetLaunchCommandSystemPrompt(t *testing.T) { func TestGetRestoreCommand(t *testing.T) { plugin := &Plugin{resolvedBinary: "droid"} cmd, ok, err := plugin.GetRestoreCommand(context.Background(), ports.RestoreConfig{ + SystemPrompt: "restore inline wins", + SystemPromptFile: "/tmp/system.md", Session: ports.SessionRef{ ID: "mer-4", Metadata: map[string]string{ @@ -154,7 +156,7 @@ func TestGetRestoreCommand(t *testing.T) { if !ok { t.Fatal("ok=false, want true") } - want := []string{"droid", "-r", "droid-ses-1"} + want := []string{"droid", "--append-system-prompt", "restore inline wins", "-r", "droid-ses-1"} if !reflect.DeepEqual(cmd, want) { t.Fatalf("cmd = %#v, want %#v", cmd, want) } diff --git a/backend/internal/adapters/agent/goose/goose.go b/backend/internal/adapters/agent/goose/goose.go index 7964a9a86..d1a968139 100644 --- a/backend/internal/adapters/agent/goose/goose.go +++ b/backend/internal/adapters/agent/goose/goose.go @@ -142,7 +142,15 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig) return nil, false, err } - cmd = append(gooseModeEnvPrefix(cfg.Permissions), binary, "run", "--resume", "--session-id", agentSessionID) + cmd = append(gooseModeEnvPrefix(cfg.Permissions), binary, "run") + systemPrompt, err := restoreSystemPromptText(cfg) + if err != nil { + return nil, false, err + } + if systemPrompt != "" { + cmd = append(cmd, "--system", systemPrompt) + } + cmd = append(cmd, "--resume", "--session-id", agentSessionID) return cmd, true, nil } @@ -167,17 +175,26 @@ func (p *Plugin) SessionInfo(ctx context.Context, session ports.SessionRef) (por // flag takes inline text only (no file variant), so a system-prompt file is read // from disk only when inline instructions are unavailable. func systemPromptText(cfg ports.LaunchConfig) (string, error) { - if cfg.SystemPrompt != "" { - return cfg.SystemPrompt, nil + return systemPromptTextFrom(cfg.SystemPrompt, cfg.SystemPromptFile) +} + +func restoreSystemPromptText(cfg ports.RestoreConfig) (string, error) { + return systemPromptTextFrom(cfg.SystemPrompt, cfg.SystemPromptFile) +} + +func systemPromptTextFrom(inline, file string) (string, error) { + if inline != "" { + return inline, nil } - if cfg.SystemPromptFile != "" { - data, err := os.ReadFile(cfg.SystemPromptFile) //nolint:gosec // path is AO-owned launch config - if err != nil { - return "", fmt.Errorf("read %s: %w", cfg.SystemPromptFile, err) - } - if text := strings.TrimSpace(string(data)); text != "" { - return text, nil - } + if file == "" { + return "", nil + } + data, err := os.ReadFile(file) //nolint:gosec // path is AO-owned launch config + if err != nil { + return "", fmt.Errorf("read %s: %w", file, err) + } + if text := strings.TrimSpace(string(data)); text != "" { + return text, nil } return "", nil } diff --git a/backend/internal/adapters/agent/goose/goose_test.go b/backend/internal/adapters/agent/goose/goose_test.go index 789895424..d40c5fbdb 100644 --- a/backend/internal/adapters/agent/goose/goose_test.go +++ b/backend/internal/adapters/agent/goose/goose_test.go @@ -278,7 +278,9 @@ func TestGetRestoreCommandReadsAgentSessionID(t *testing.T) { plugin := &Plugin{resolvedBinary: "goose"} cmd, ok, err := plugin.GetRestoreCommand(context.Background(), ports.RestoreConfig{ - Permissions: ports.PermissionModeAuto, + Permissions: ports.PermissionModeAuto, + SystemPrompt: "restore inline wins", + SystemPromptFile: filepath.Join(t.TempDir(), "missing.md"), Session: ports.SessionRef{ Metadata: map[string]string{ports.MetadataKeyAgentSessionID: "thread-123"}, }, @@ -291,7 +293,7 @@ func TestGetRestoreCommandReadsAgentSessionID(t *testing.T) { } want := []string{ "env", "GOOSE_MODE=auto", - "goose", "run", "--resume", "--session-id", "thread-123", + "goose", "run", "--system", "restore inline wins", "--resume", "--session-id", "thread-123", } if !reflect.DeepEqual(cmd, want) { t.Fatalf("restore cmd\nwant: %#v\n got: %#v", want, cmd) diff --git a/backend/internal/adapters/agent/pi/pi.go b/backend/internal/adapters/agent/pi/pi.go index e510de671..d00c8c7ae 100644 --- a/backend/internal/adapters/agent/pi/pi.go +++ b/backend/internal/adapters/agent/pi/pi.go @@ -146,6 +146,15 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig) return nil, false, err } cmd = []string{binary, "--print", "--session", agentSessionID} + if cfg.SystemPrompt != "" { + cmd = append(cmd, "--append-system-prompt", cfg.SystemPrompt) + } else if cfg.SystemPromptFile != "" { + data, err := os.ReadFile(cfg.SystemPromptFile) //nolint:gosec // path is AO-owned launch config + if err != nil { + return nil, false, err + } + cmd = append(cmd, "--append-system-prompt", string(data)) + } return cmd, true, nil } diff --git a/backend/internal/adapters/agent/pi/pi_test.go b/backend/internal/adapters/agent/pi/pi_test.go index 55c728752..0f21e5bc6 100644 --- a/backend/internal/adapters/agent/pi/pi_test.go +++ b/backend/internal/adapters/agent/pi/pi_test.go @@ -148,6 +148,8 @@ func TestGetLaunchCommandSystemPromptFileReadError(t *testing.T) { func TestGetRestoreCommand(t *testing.T) { p := &Plugin{resolvedBinary: "pi"} cmd, ok, err := p.GetRestoreCommand(context.Background(), ports.RestoreConfig{ + SystemPrompt: "restore inline wins", + SystemPromptFile: filepath.Join(t.TempDir(), "missing.md"), Session: ports.SessionRef{ Metadata: map[string]string{ports.MetadataKeyAgentSessionID: "019e950e-52e0-7411-961b-d380ca7e610f"}, }, @@ -160,7 +162,7 @@ func TestGetRestoreCommand(t *testing.T) { t.Fatal("ok=false, want true") } - want := []string{"pi", "--print", "--session", "019e950e-52e0-7411-961b-d380ca7e610f"} + want := []string{"pi", "--print", "--session", "019e950e-52e0-7411-961b-d380ca7e610f", "--append-system-prompt", "restore inline wins"} if !reflect.DeepEqual(cmd, want) { t.Fatalf("cmd = %#v, want %#v", cmd, want) } diff --git a/backend/internal/adapters/agent/qwen/qwen.go b/backend/internal/adapters/agent/qwen/qwen.go index 0ceb90681..68c1414d6 100644 --- a/backend/internal/adapters/agent/qwen/qwen.go +++ b/backend/internal/adapters/agent/qwen/qwen.go @@ -122,6 +122,9 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig) cmd = make([]string, 0, 6) cmd = append(cmd, binary) appendApprovalFlags(&cmd, cfg.Permissions) + if cfg.SystemPrompt != "" { + cmd = append(cmd, "--append-system-prompt", cfg.SystemPrompt) + } cmd = append(cmd, "-r", agentSessionID) return cmd, true, nil } diff --git a/backend/internal/adapters/agent/qwen/qwen_test.go b/backend/internal/adapters/agent/qwen/qwen_test.go index 034cfc054..17daad1a4 100644 --- a/backend/internal/adapters/agent/qwen/qwen_test.go +++ b/backend/internal/adapters/agent/qwen/qwen_test.go @@ -271,7 +271,8 @@ func TestGetRestoreCommandReadsAgentSessionID(t *testing.T) { plugin := &Plugin{resolvedBinary: "qwen"} cmd, ok, err := plugin.GetRestoreCommand(context.Background(), ports.RestoreConfig{ - Permissions: ports.PermissionModeAuto, + Permissions: ports.PermissionModeAuto, + SystemPrompt: "restore instructions", Session: ports.SessionRef{ Metadata: map[string]string{ports.MetadataKeyAgentSessionID: "sess-123"}, }, @@ -285,6 +286,7 @@ func TestGetRestoreCommandReadsAgentSessionID(t *testing.T) { want := []string{ "qwen", "--approval-mode", "auto", + "--append-system-prompt", "restore instructions", "-r", "sess-123", } if !reflect.DeepEqual(cmd, want) {