Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8079346
feat(agentsessions): read other coding agents' local session transcripts
gnanam1990 Aug 8, 2026
74e84b7
feat(cli): add sessions discover and sessions import
gnanam1990 Aug 8, 2026
e99de74
feat(tui): group /resume by source agent and offer un-imported sessions
gnanam1990 Aug 8, 2026
44ddd34
fix(agentsessions): strip control bytes and stop typing summaries as …
gnanam1990 Aug 10, 2026
1b71d06
fix(agentsessions): address review — redact structural fields, fix fi…
gnanam1990 Aug 11, 2026
db70282
fix(agentsessions): strip control bytes before matching secrets
Vasanthdev2004 Aug 13, 2026
3bfad35
test(agentsessions): the newline case asserts the halves survive, not…
gnanam1990 Aug 21, 2026
0f6045c
test(agentsessions): pin the corpus shapes by fixture instead of by t…
gnanam1990 Aug 21, 2026
054b7ee
fix(tui): the import path is reachable with no local history, and a f…
gnanam1990 Aug 21, 2026
63f3023
fix(agentsessions): an import keeps the whole message, and foreign fi…
gnanam1990 Aug 21, 2026
bcdceb1
fix(agentsessions): honest coverage for the overlong-record gap, and …
gnanam1990 Aug 21, 2026
3d86090
fix(agentsessions): contain the import read as well as the index scan
gnanam1990 Aug 21, 2026
e9c5290
fix(agentsessions): recover a workspace from an oversized record inst…
gnanam1990 Aug 21, 2026
56afa51
fix(agentsessions): sanitize imported metadata at the store, and stop…
gnanam1990 Aug 25, 2026
a5af0ac
fix(agentsessions): harden imported session metadata
gnanam1990 Aug 28, 2026
844d3b0
fix(agent-sessions): harden import lifecycle
gnanam1990 Aug 28, 2026
d852dd1
fix(tui): omit missing foreign session timestamps
gnanam1990 Aug 28, 2026
a2fb7f2
refactor(sessions): scope failed-create cleanup
gnanam1990 Aug 28, 2026
06e6567
test: encode imported session fixtures portably
gnanam1990 Aug 28, 2026
c33ff10
fix(agentsessions): separate workspace identity from display
gnanam1990 Aug 28, 2026
9af2ca6
fix(agentsessions): secure imported session state
gnanam1990 Aug 28, 2026
9281581
fix(agentsessions): close import lifecycle gaps
gnanam1990 Aug 29, 2026
341decd
fix(sessions): bound foreign transcript imports
gnanam1990 Aug 29, 2026
4505485
fix(sessions): honor explicit import caps
gnanam1990 Aug 29, 2026
b0744cf
fix(sessions): preserve operational import semantics
gnanam1990 Aug 30, 2026
257a8e6
fix(sessions): bind imported execution to local workspaces
gnanam1990 Aug 31, 2026
e6b749e
fix(sessions): bind imports to exact foreign source
gnanam1990 Aug 31, 2026
8fb8b04
fix(sessions): bound live imports and sanitize picker labels
gnanam1990 Sep 1, 2026
c6d120a
fix(sessions): enforce imported session trust boundaries
gnanam1990 Sep 2, 2026
fb35d71
fix(agentsessions): preserve foreign trust boundaries
gnanam1990 Sep 7, 2026
ac83bcb
test(agentsessions): preserve lexical Windows identity
gnanam1990 Sep 7, 2026
36eb244
fix(tui): keep foreign sessions on local list failure
gnanam1990 Sep 9, 2026
a78150d
fix(agentsessions): make each safeguard hold at the consumer that dep…
gnanam1990 Sep 10, 2026
8b81448
fix(agentsessions): preserve import contracts across consumers
gnanam1990 Sep 11, 2026
8cc0f4a
fix(imports): align picker and child contracts
gnanam1990 Sep 12, 2026
b716c04
fix(agentsessions): redact normalized display secrets
gnanam1990 Sep 12, 2026
b3bc494
test(acp): align imported model reload with workspace identity
gnanam1990 Sep 12, 2026
6b53f9e
fix(agentsessions): close split-secret display gaps
gnanam1990 Sep 12, 2026
b7e4ed7
fix(agentsessions): bound fragmented secret scans
gnanam1990 Sep 12, 2026
3431511
fix(agentsessions): preserve import safety across projections
gnanam1990 Sep 14, 2026
e6feaec
fix(cli): compare native path aliases on Windows
gnanam1990 Sep 14, 2026
c9ad1cf
fix(search): redact all presented session metadata
gnanam1990 Sep 14, 2026
c68b14d
fix(sessions): authenticate checkpoint workspace roots
gnanam1990 Sep 14, 2026
95418c7
fix(agentsessions): preserve presentation safety boundaries
gnanam1990 Sep 14, 2026
dcc0499
fix(sessions): seal checkpoint roots before blob writes
gnanam1990 Sep 14, 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