From 07b39efde26c43ec25ba274744d7eeb0177ff6ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Salavisa?= Date: Fri, 6 Mar 2026 10:41:05 +0000 Subject: [PATCH] feat(auggie): enable native session resume with ACP session/load (#380) --- apps/backend/internal/agent/agents/auggie.go | 15 ++++----- .../server/adapter/transport/acp/adapter.go | 33 +++++++++++++++++++ docs/task_session_resume.md | 2 +- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/apps/backend/internal/agent/agents/auggie.go b/apps/backend/internal/agent/agents/auggie.go index 95fdd685de..1c728d4d6d 100644 --- a/apps/backend/internal/agent/agents/auggie.go +++ b/apps/backend/internal/agent/agents/auggie.go @@ -17,7 +17,7 @@ var auggieLogoLight []byte //go:embed logos/auggie_dark.svg var auggieLogoDark []byte -const auggiePkg = "@augmentcode/auggie@0.16.2" +const auggiePkg = "@augmentcode/auggie@0.18.1" var ( _ Agent = (*Auggie)(nil) @@ -74,7 +74,7 @@ func (a *Auggie) IsInstalled(ctx context.Context) (*DiscoveryResult, error) { result.SupportsMCP = true result.InstallationPaths = []string{expandHomePath("~/.augment/.auggie.json")} result.Capabilities = DiscoveryCapabilities{ - SupportsSessionResume: false, + SupportsSessionResume: true, SupportsShell: false, SupportsWorkspaceOnly: false, } @@ -92,16 +92,16 @@ func (a *Auggie) ListModels(ctx context.Context) (*ModelList, error) { } func (a *Auggie) BuildCommand(opts CommandOptions) Command { + // Session resume is handled via ACP session/load, not CLI flags (NativeSessionResume: true) return Cmd("npx", "-y", auggiePkg, "--acp"). Model(NewParam("--model", "{model}"), opts.Model). - Resume(NewParam("--resume"), opts.SessionID, false). Permissions("--permission", auggiePermTools, opts). Settings(auggiePermSettings, opts.PermissionValues). Build() } func (a *Auggie) Runtime() *RuntimeConfig { - canRecover := false + canRecover := true return &RuntimeConfig{ Image: "kandev/multi-agent", Tag: "latest", @@ -117,11 +117,8 @@ func (a *Auggie) Runtime() *RuntimeConfig { ModelFlag: NewParam("--model", "{model}"), WorkspaceFlag: "--workspace-root", SessionConfig: SessionConfig{ - ResumeFlag: NewParam("--resume"), - CanRecover: &canRecover, - HistoryContextInjection: true, - SessionDirTemplate: "{home}/.augment/sessions", - SessionDirTarget: "/root/.augment/sessions", + NativeSessionResume: true, + CanRecover: &canRecover, }, } } diff --git a/apps/backend/internal/agentctl/server/adapter/transport/acp/adapter.go b/apps/backend/internal/agentctl/server/adapter/transport/acp/adapter.go index 6a87e559ca..ae53988da6 100644 --- a/apps/backend/internal/agentctl/server/adapter/transport/acp/adapter.go +++ b/apps/backend/internal/agentctl/server/adapter/transport/acp/adapter.go @@ -74,6 +74,11 @@ type Adapter struct { // When set, this context will be prepended to the first prompt sent to the session. pendingContext string + // isLoadingSession is true during LoadSession() to suppress history replay notifications. + // ACP agents stream the entire conversation history during session/load which should + // not be emitted as new message events. + isLoadingSession bool + // Tool call tracking for result normalization // Maps toolCallId -> NormalizedPayload so we can update with results activeToolCalls map[string]*streams.NormalizedPayload @@ -291,11 +296,23 @@ func (a *Adapter) LoadSession(ctx context.Context, sessionID string) error { ctx, span := shared.TraceProtocolRequest(ctx, shared.ProtocolACP, a.agentID, "session.load") defer span.End() + // Suppress history replay notifications during load. + // ACP agents stream the entire conversation history during session/load. + a.mu.Lock() + a.isLoadingSession = true + a.mu.Unlock() + resp, err := conn.LoadSession(ctx, acp.LoadSessionRequest{ SessionId: acp.SessionId(sessionID), Cwd: a.cfg.WorkDir, McpServers: []acp.McpServer{}, // MCPs configured via CLI args; empty satisfies the required field }) + + // Clear the loading flag (even on error) before processing response + a.mu.Lock() + a.isLoadingSession = false + a.mu.Unlock() + if err != nil { span.RecordError(err) return fmt.Errorf("failed to load session: %w", err) @@ -581,6 +598,22 @@ func (a *Adapter) handleACPUpdate(n acp.SessionNotification) { shared.LogRawEvent(shared.ProtocolACP, a.agentID, "session_notification", rawData) } + // During session/load, suppress history replay notifications. + // ACP agents stream the entire conversation history during load, which should not + // be emitted as new message events to avoid duplicating messages in the UI. + a.mu.RLock() + isLoading := a.isLoadingSession + a.mu.RUnlock() + + if isLoading { + u := n.Update + if u.AgentMessageChunk != nil || u.UserMessageChunk != nil || u.AgentThoughtChunk != nil { + a.logger.Debug("suppressing history replay notification during session load", + zap.String("session_id", string(n.SessionId))) + return + } + } + event := a.convertNotification(n) if event != nil { shared.LogNormalizedEvent(shared.ProtocolACP, a.agentID, event) diff --git a/docs/task_session_resume.md b/docs/task_session_resume.md index 9332af4d3f..5dbe1efd9e 100644 --- a/docs/task_session_resume.md +++ b/docs/task_session_resume.md @@ -308,7 +308,7 @@ The session is NOT marked as failed -- the user can send a new message to start | Claude Code | - | - | Uses `--resume` CLI flag with stored resume token | | Codex | yes | - | ACP `session/load` restores context | | Copilot | yes | - | ACP `session/load` restores context | -| Auggie | - | yes | Fresh start + history injection on first prompt | +| Auggie | yes | - | ACP `session/load` restores context (v0.18.1+) | | Gemini | - | - | Boots idle, no context restoration | | Amp | - | - | Uses `ForkSessionCmd` / `ContinueSessionCmd` | | OpenCode | yes | - | ACP `session/load` restores context |