Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a472b67
feat(agentsessions): read other coding agents' local session transcripts
gnanam1990 Aug 8, 2026
9624413
feat(cli): add sessions discover and sessions import
gnanam1990 Aug 8, 2026
171b5d3
feat(tui): group /resume by source agent and offer un-imported sessions
gnanam1990 Aug 8, 2026
884cb7d
fix(agentsessions): strip control bytes and stop typing summaries as …
gnanam1990 Aug 10, 2026
ecdbed4
fix(agentsessions): address review — redact structural fields, fix fi…
gnanam1990 Aug 11, 2026
fa8de4a
fix(agentsessions): strip control bytes before matching secrets
Vasanthdev2004 Aug 13, 2026
ef1dbf0
test(agentsessions): the newline case asserts the halves survive, not…
gnanam1990 Aug 21, 2026
6077a42
test(agentsessions): pin the corpus shapes by fixture instead of by t…
gnanam1990 Aug 21, 2026
0030609
fix(tui): the import path is reachable with no local history, and a f…
gnanam1990 Aug 21, 2026
ffc18e4
fix(agentsessions): an import keeps the whole message, and foreign fi…
gnanam1990 Aug 21, 2026
16caa3b
fix(agentsessions): honest coverage for the overlong-record gap, and …
gnanam1990 Aug 21, 2026
218d746
fix(agentsessions): contain the import read as well as the index scan
gnanam1990 Aug 21, 2026
987efee
fix(agentsessions): recover a workspace from an oversized record inst…
gnanam1990 Aug 21, 2026
886fd11
fix(agentsessions): sanitize imported metadata at the store, and stop…
gnanam1990 Aug 25, 2026
7a3a5a0
fix(agentsessions): harden imported session metadata
gnanam1990 Aug 28, 2026
0f93b5f
fix(agent-sessions): harden import lifecycle
gnanam1990 Aug 28, 2026
d4dde5a
fix(tui): omit missing foreign session timestamps
gnanam1990 Aug 28, 2026
42b11d3
refactor(sessions): scope failed-create cleanup
gnanam1990 Aug 28, 2026
805c102
test: encode imported session fixtures portably
gnanam1990 Aug 28, 2026
bc651c2
fix(agentsessions): separate workspace identity from display
gnanam1990 Aug 28, 2026
9da7ac5
fix(agentsessions): secure imported session state
gnanam1990 Aug 28, 2026
926a999
fix(agentsessions): close import lifecycle gaps
gnanam1990 Aug 29, 2026
73c4fb7
fix(sessions): bound foreign transcript imports
gnanam1990 Aug 29, 2026
e5d901e
fix(sessions): honor explicit import caps
gnanam1990 Aug 29, 2026
f09d1eb
fix(sessions): preserve operational import semantics
gnanam1990 Aug 30, 2026
5d67c18
fix(sessions): bind imported execution to local workspaces
gnanam1990 Aug 31, 2026
5189585
fix(sessions): bind imports to exact foreign source
gnanam1990 Aug 31, 2026
f1bfdd2
fix(sessions): bound live imports and sanitize picker labels
gnanam1990 Sep 1, 2026
7ed1098
fix(sessions): enforce imported session trust boundaries
gnanam1990 Sep 2, 2026
1c74b23
fix(agentsessions): preserve foreign trust boundaries
gnanam1990 Sep 7, 2026
e4ef984
test(agentsessions): preserve lexical Windows identity
gnanam1990 Sep 7, 2026
a86baf5
fix(tui): keep foreign sessions on local list failure
gnanam1990 Sep 9, 2026
4de65ba
fix(agentsessions): make each safeguard hold at the consumer that dep…
gnanam1990 Sep 10, 2026
b6e3cee
fix(agentsessions): preserve import contracts across consumers
gnanam1990 Sep 11, 2026
3429c0c
fix(imports): align picker and child contracts
gnanam1990 Sep 12, 2026
95b0bd0
fix(agentsessions): redact normalized display secrets
gnanam1990 Sep 12, 2026
6fab186
test(acp): align imported model reload with workspace identity
gnanam1990 Sep 12, 2026
100d467
fix(agentsessions): close split-secret display gaps
gnanam1990 Sep 12, 2026
848f9fe
fix(agentsessions): bound fragmented secret scans
gnanam1990 Sep 12, 2026
7c7931e
fix(agentsessions): preserve import safety across projections
gnanam1990 Sep 14, 2026
b73c2b2
fix(cli): compare native path aliases on Windows
gnanam1990 Sep 14, 2026
8452b47
fix(search): redact all presented session metadata
gnanam1990 Sep 14, 2026
0a7c428
fix(sessions): authenticate checkpoint workspace roots
gnanam1990 Sep 14, 2026
f4ed9cd
fix(agentsessions): preserve presentation safety boundaries
gnanam1990 Sep 14, 2026
7871d95
fix(sessions): seal checkpoint roots before blob writes
gnanam1990 Sep 14, 2026
faae5e8
test(tui): await async resume picker discovery
gnanam1990 Sep 16, 2026
e8fba85
fix(tui): refuse a foreign import mid-run on every route, not just th…
gnanam1990 Sep 21, 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
66 changes: 52 additions & 14 deletions internal/acp/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ type Agent struct {
type turnRecord struct {
user string
assistant string
imported bool
boundary bool
}

type acpSession struct {
Expand Down Expand Up @@ -257,18 +259,19 @@ func (a *Agent) activatePersistedSession(ctx context.Context, p LoadSessionParam
if operation == persistedSessionResume && !sessions.IsResumableKind(meta.SessionKind) {
return nil, RPCError(codeInvalidParams, "session is not resumable: "+p.SessionID)
}
if strings.TrimSpace(meta.Cwd) == "" {
persistedCwd := sessions.OperationalCwd(*meta)
if strings.TrimSpace(persistedCwd) == "" {
return nil, RPCError(codeInvalidParams, "session has no persisted workspace: "+p.SessionID)
}
// SAME RULE ON THE WAY IN. Omitting a relative entry from the listing is not
// enough: a stored "." still resolves against this process's directory, so a
// client that hands back that same directory as its cwd would be sold the
// invented root as a match. A workspace that cannot be identified is not one
// this session can be restored into.
if !filepath.IsAbs(meta.Cwd) {
if !filepath.IsAbs(persistedCwd) {
return nil, RPCError(codeInvalidParams, "session workspace is not an absolute path, so it cannot be identified: "+p.SessionID)
}
persistedRoot, err := a.deps.ResolveWorkspaceRoot(meta.Cwd)
persistedRoot, err := a.deps.ResolveWorkspaceRoot(persistedCwd)
if err != nil {
return nil, RPCError(codeInvalidParams, "persisted session workspace is unavailable: "+err.Error())
}
Expand Down Expand Up @@ -305,7 +308,10 @@ func (a *Agent) activatePersistedSession(ctx context.Context, p LoadSessionParam
if err != nil {
return nil, RPCError(codeInternalError, "config: "+err.Error())
}
if persistedModel := strings.TrimSpace(meta.ModelID); persistedModel != "" && (!restrictModels || modelChoiceExists(models, persistedModel)) {
persistedModel := strings.TrimSpace(meta.ModelID)
imported := sessions.IsImportedSession(*meta)
locallySelected := !imported || meta.ModelSelectedLocally
if persistedModel != "" && locallySelected && (!restrictModels || modelChoiceExists(models, persistedModel)) {
model = persistedModel
if !modelChoiceExists(models, persistedModel) {
models = append(models, SessionConfigOptionValue{Value: persistedModel, Name: persistedModel})
Expand Down Expand Up @@ -389,7 +395,8 @@ func (a *Agent) handleSessionList(_ context.Context, params json.RawMessage) (an
// a session whose workspace has since been deleted, and a legacy entry
// holding a relative path, which was then reported as cwd "." even though
// ACP requires SessionInfo.cwd to be absolute.
if strings.TrimSpace(item.Cwd) == "" {
operationalCwd := sessions.OperationalCwd(item)
if strings.TrimSpace(operationalCwd) == "" {
continue
}
// A RELATIVE PERSISTED CWD HAS NO RECOVERABLE IDENTITY. Resolving one
Expand All @@ -399,10 +406,10 @@ func (a *Agent) handleSessionList(_ context.Context, params json.RawMessage) (an
// project's configuration, files and tools. The original base is not
// knowable from the metadata, so the honest answer is to omit the entry
// rather than to guess at it.
if !filepath.IsAbs(item.Cwd) {
if !filepath.IsAbs(operationalCwd) {
continue
}
itemRoot, err := a.deps.ResolveWorkspaceRoot(item.Cwd)
itemRoot, err := a.deps.ResolveWorkspaceRoot(operationalCwd)
if err != nil {
continue
}
Expand Down Expand Up @@ -924,6 +931,7 @@ func (a *Agent) loadHistory(sessionID string, requireHistoryLog bool) ([]turnRec
var messages []persistedMessage
activeToolCalls := make(map[string]string)
var pendingUser string
var pendingImported, pendingBoundary bool
havePending := false
for _, e := range events {
if e.Type == sessions.EventCompaction {
Expand All @@ -935,7 +943,8 @@ func (a *Agent) loadHistory(sessionID string, requireHistoryLog bool) ([]turnRec
continue
}
var payload struct {
Summary string `json:"summary"`
Summary string `json:"summary"`
ImportedContext bool `json:"importedEvent"`
}
if json.Unmarshal(raw, &payload) != nil || strings.TrimSpace(payload.Summary) == "" {
continue
Expand All @@ -945,8 +954,10 @@ func (a *Agent) loadHistory(sessionID string, requireHistoryLog bool) ([]turnRec
role: "assistant",
content: payload.Summary,
})
records = append(records, turnRecord{user: pendingUser, assistant: payload.Summary})
records = append(records, turnRecord{user: pendingUser, assistant: payload.Summary, imported: payload.ImportedContext})
pendingUser = ""
pendingImported = false
pendingBoundary = false
havePending = false
continue
}
Expand Down Expand Up @@ -988,8 +999,10 @@ func (a *Agent) loadHistory(sessionID string, requireHistoryLog bool) ([]turnRec
continue
}
var msg struct {
Role string `json:"role"`
Content string `json:"content"`
Role string `json:"role"`
Content string `json:"content"`
Imported bool `json:"importedEvent"`
Boundary bool `json:"importedReferenceBoundary"`
}
if json.Unmarshal(raw, &msg) != nil {
continue
Expand All @@ -998,19 +1011,27 @@ func (a *Agent) loadHistory(sessionID string, requireHistoryLog bool) ([]turnRec
case "user":
messages = append(messages, persistedMessage{eventID: persistedMessageIdentity(sessionID, e), role: msg.Role, content: msg.Content})
if havePending {
records = append(records, turnRecord{user: pendingUser})
records = append(records, turnRecord{user: pendingUser, imported: pendingImported, boundary: pendingBoundary})
}
pendingUser = msg.Content
pendingImported = msg.Imported
pendingBoundary = msg.Boundary
havePending = true
case "assistant":
messages = append(messages, persistedMessage{eventID: persistedMessageIdentity(sessionID, e), role: msg.Role, content: msg.Content})
records = append(records, turnRecord{user: pendingUser, assistant: msg.Content})
records = append(records, turnRecord{
user: pendingUser, assistant: msg.Content,
imported: pendingImported || msg.Imported,
boundary: pendingBoundary || msg.Boundary,
})
pendingUser = ""
pendingImported = false
pendingBoundary = false
havePending = false
}
}
if havePending {
records = append(records, turnRecord{user: pendingUser})
records = append(records, turnRecord{user: pendingUser, imported: pendingImported, boundary: pendingBoundary})
}
return records, messages, rehydrateWarning, nil
}
Expand Down Expand Up @@ -1064,6 +1085,11 @@ func replayToolUpdate(event sessions.Event) *ToolCallUpdate {
Output: payload.Output,
ChangedFiles: append([]string(nil), payload.ChangedFiles...),
})
if status == tools.StatusUnknown {
// ACP has no unknown result status. Omitting the optional status keeps
// this replay neutral instead of inventing successful completion.
upd.Status = ""
}
return &upd
}

Expand Down Expand Up @@ -1116,7 +1142,14 @@ func buildPrompt(history []turnRecord, userText string) string {
}
var b strings.Builder
b.WriteString("Conversation so far:\n")
importedLabeled := false
for _, t := range history {
if t.imported && !importedLabeled {
b.WriteString("Context boundary: ")
b.WriteString(sessions.ImportedBoundaryText("another agent's"))
b.WriteString("\n")
importedLabeled = true
}
b.WriteString("User: ")
b.WriteString(t.user)
b.WriteString("\n")
Expand All @@ -1125,6 +1158,11 @@ func buildPrompt(history []turnRecord, userText string) string {
b.WriteString(t.assistant)
b.WriteString("\n")
}
if t.boundary {
importedLabeled = true
} else if !t.imported {
importedLabeled = false
}
}
b.WriteString("\n---\nContinue with this request:\n")
b.WriteString(userText)
Expand Down
Loading
Loading