Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2b384c3
feat(agentsessions): read other coding agents' local session transcripts
gnanam1990 Aug 8, 2026
ac5f267
feat(cli): add sessions discover and sessions import
gnanam1990 Aug 8, 2026
645eb4b
feat(tui): group /resume by source agent and offer un-imported sessions
gnanam1990 Aug 8, 2026
e701a25
fix(agentsessions): strip control bytes and stop typing summaries as …
gnanam1990 Aug 10, 2026
9073075
fix(agentsessions): address review — redact structural fields, fix fi…
gnanam1990 Aug 11, 2026
9fdbfd9
fix(agentsessions): strip control bytes before matching secrets
Vasanthdev2004 Aug 13, 2026
9162381
test(agentsessions): the newline case asserts the halves survive, not…
gnanam1990 Aug 21, 2026
fddf536
test(agentsessions): pin the corpus shapes by fixture instead of by t…
gnanam1990 Aug 21, 2026
f853662
fix(tui): the import path is reachable with no local history, and a f…
gnanam1990 Aug 21, 2026
c787569
fix(agentsessions): an import keeps the whole message, and foreign fi…
gnanam1990 Aug 21, 2026
082eff0
fix(agentsessions): honest coverage for the overlong-record gap, and …
gnanam1990 Aug 21, 2026
2484d99
fix(agentsessions): contain the import read as well as the index scan
gnanam1990 Aug 21, 2026
5da871f
fix(agentsessions): recover a workspace from an oversized record inst…
gnanam1990 Aug 21, 2026
c130f32
fix(agentsessions): sanitize imported metadata at the store, and stop…
gnanam1990 Aug 25, 2026
21eab13
fix(agentsessions): harden imported session metadata
gnanam1990 Aug 28, 2026
fadd2ea
fix(agent-sessions): harden import lifecycle
gnanam1990 Aug 28, 2026
680829f
fix(tui): omit missing foreign session timestamps
gnanam1990 Aug 28, 2026
aaca5d7
refactor(sessions): scope failed-create cleanup
gnanam1990 Aug 28, 2026
7c2bb83
test: encode imported session fixtures portably
gnanam1990 Aug 28, 2026
4392e1a
fix(agentsessions): separate workspace identity from display
gnanam1990 Aug 28, 2026
2f5bc97
fix(agentsessions): secure imported session state
gnanam1990 Aug 28, 2026
f8e70c1
fix(agentsessions): close import lifecycle gaps
gnanam1990 Aug 29, 2026
0475b21
fix(sessions): bound foreign transcript imports
gnanam1990 Aug 29, 2026
8ad2850
fix(sessions): honor explicit import caps
gnanam1990 Aug 29, 2026
81ad7b6
fix(sessions): preserve operational import semantics
gnanam1990 Aug 30, 2026
7a75770
fix(sessions): bind imported execution to local workspaces
gnanam1990 Aug 31, 2026
beb0dc4
fix(sessions): bind imports to exact foreign source
gnanam1990 Aug 31, 2026
9707f13
fix(sessions): bound live imports and sanitize picker labels
gnanam1990 Sep 1, 2026
d1cfbb0
fix(sessions): enforce imported session trust boundaries
gnanam1990 Sep 2, 2026
bfee4bf
fix(agentsessions): preserve foreign trust boundaries
gnanam1990 Sep 7, 2026
08236b3
test(agentsessions): preserve lexical Windows identity
gnanam1990 Sep 7, 2026
5231d56
fix(tui): keep foreign sessions on local list failure
gnanam1990 Sep 9, 2026
87b9f77
fix(agentsessions): make each safeguard hold at the consumer that dep…
gnanam1990 Sep 10, 2026
2be55cc
fix(agentsessions): preserve import contracts across consumers
gnanam1990 Sep 11, 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
11 changes: 10 additions & 1 deletion internal/acp/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ func (a *Agent) handleSessionLoad(ctx context.Context, params json.RawMessage) (
}
cwdInput := p.Cwd
if strings.TrimSpace(cwdInput) == "" {
// WorkspaceKey is populated from foreign transcript metadata. It is useful
// for matching/provenance, but it is not execution authority: only the ACP
// client may bind an imported session to a workspace tool boundary.
if strings.TrimSpace(meta.WorkspaceKey) != "" {
return nil, RPCError(codeInvalidParams, "cwd is required when loading an imported session")
}
cwdInput = meta.Cwd
}
root, err := a.deps.ResolveWorkspaceRoot(cwdInput)
Expand All @@ -198,7 +204,10 @@ func (a *Agent) handleSessionLoad(ctx context.Context, params json.RawMessage) (
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
153 changes: 153 additions & 0 deletions internal/acp/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,120 @@ func TestACPCustomProviderAllowsUnadvertisedModel(t *testing.T) {
}
}

func TestACPLoadImportedSessionDoesNotRestoreUnadvertisedForeignModel(t *testing.T) {
deps := testDeps(t)
deps.ResolveConfig = func(_ string, _ config.Overrides) (config.ResolvedConfig, error) {
return config.ResolvedConfig{Provider: config.ProviderProfile{
Name: "Custom", CatalogID: "custom-openai-compatible", Model: "workspace-model",
}}, nil
}
meta, err := deps.Store.Create(sessions.CreateInput{
Title: "legacy imported session",
Cwd: t.TempDir(),
ModelID: "foreign-expensive-model",
Tag: sessions.ImportedSessionTag("claude-code", "foreign-id"),
})
if err != nil {
t.Fatal(err)
}
h := newHarness(t, deps)
defer h.stop()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var loaded LoadSessionResult
if err := h.client.Call(ctx, MethodSessionLoad, LoadSessionParams{
SessionID: meta.SessionID,
Cwd: t.TempDir(),
}, &loaded); err != nil {
t.Fatalf("session/load: %v", err)
}
option := loaded.ConfigOptions[0]
if option.CurrentValue != "workspace-model" || modelChoiceExists(option.Options, "foreign-expensive-model") {
t.Fatalf("imported model gained ACP authority: %+v", option)
}
}

func TestACPLoadImportedSessionRestoresLaterLocalModelSelection(t *testing.T) {
deps := testDeps(t)
deps.ResolveConfig = func(_ string, _ config.Overrides) (config.ResolvedConfig, error) {
return config.ResolvedConfig{Provider: config.ProviderProfile{
Name: "Custom", CatalogID: "custom-openai-compatible", Model: "workspace-model",
}}, nil
}
clientCwd := t.TempDir()
meta, err := deps.Store.Create(sessions.CreateInput{
Title: "modern imported session",
Cwd: "/foreign/display",
WorkspaceKey: "/foreign/exact",
SourceModelID: "foreign-expensive-model",
Tag: sessions.ImportedSessionTag("claude-code", "foreign-id"),
})
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
h := newHarness(t, deps)
var loaded LoadSessionResult
if err := h.client.Call(ctx, MethodSessionLoad, LoadSessionParams{SessionID: meta.SessionID, Cwd: clientCwd}, &loaded); err != nil {
t.Fatalf("initial session/load: %v", err)
}
if got := loaded.ConfigOptions[0].CurrentValue; got != "workspace-model" {
t.Fatalf("initial imported model = %q, want workspace default", got)
}
var selected SetSessionConfigOptionResult
if err := h.client.Call(ctx, MethodSessionSetConfigOption, SetSessionConfigOptionParams{
SessionID: meta.SessionID, ConfigID: configIDModel, Value: "local-choice",
}, &selected); err != nil {
t.Fatalf("set local model: %v", err)
}
if got := selected.ConfigOptions[0].CurrentValue; got != "local-choice" {
t.Fatalf("selected model = %q", got)
}
h.stop()

h = newHarness(t, deps)
defer h.stop()
loaded = LoadSessionResult{}
if err := h.client.Call(ctx, MethodSessionLoad, LoadSessionParams{SessionID: meta.SessionID, Cwd: clientCwd}, &loaded); err != nil {
t.Fatalf("fresh session/load: %v", err)
}
option := loaded.ConfigOptions[0]
if option.CurrentValue != "local-choice" || !modelChoiceExists(option.Options, "local-choice") {
t.Fatalf("fresh load discarded persisted local choice: %+v", option)
}
}

func TestACPLoadNativeImportedPrefixTagRestoresItsModel(t *testing.T) {
deps := testDeps(t)
deps.ResolveConfig = func(_ string, _ config.Overrides) (config.ResolvedConfig, error) {
return config.ResolvedConfig{Provider: config.ProviderProfile{
Name: "Custom", CatalogID: "custom-openai-compatible", Model: "workspace-model",
}}, nil
}
meta, err := deps.Store.Create(sessions.CreateInput{
Title: "native archived session",
Cwd: t.TempDir(),
ModelID: "native-model",
Tag: "imported:archive",
})
if err != nil {
t.Fatal(err)
}
h := newHarness(t, deps)
defer h.stop()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var loaded LoadSessionResult
if err := h.client.Call(ctx, MethodSessionLoad, LoadSessionParams{SessionID: meta.SessionID, Cwd: t.TempDir()}, &loaded); err != nil {
t.Fatalf("session/load: %v", err)
}
option := loaded.ConfigOptions[0]
if option.CurrentValue != "native-model" || !modelChoiceExists(option.Options, "native-model") {
t.Fatalf("native tagged model was discarded as foreign: %+v", option)
}
}

func TestACPModelDiscoveryFiltersProviderIncompatibleModels(t *testing.T) {
a := &Agent{deps: Deps{
ResolveConfig: func(string, config.Overrides) (config.ResolvedConfig, error) {
Expand Down Expand Up @@ -723,6 +837,45 @@ func TestACPLoadWarnsWhenHistoryReadFails(t *testing.T) {
}
}

func TestACPLoadImportedSessionRequiresClientWorkspace(t *testing.T) {
deps := testDeps(t)
displayCwd := "/work/[REDACTED]/repo"
foreignCwd := "/work/sk-ant-api03-AAAAAAAAAAAAAAAAAAAAAAAA/repo"
meta, err := deps.Store.Create(sessions.CreateInput{
Title: "imported session",
Cwd: displayCwd,
WorkspaceKey: foreignCwd,
Tag: sessions.ImportedSessionTag("claude-code", "foreign-id"),
})
if err != nil {
t.Fatalf("create session: %v", err)
}
resolved := ""
deps.ResolveWorkspaceRoot = func(cwd string) (string, error) {
resolved = cwd
return cwd, nil
}
h := newHarness(t, deps)
defer h.stop()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if err := h.client.Call(ctx, MethodSessionLoad, LoadSessionParams{SessionID: meta.SessionID}, &LoadSessionResult{}); err == nil {
t.Fatal("session/load accepted an imported session without an ACP client workspace")
}
if resolved != "" {
t.Fatalf("foreign workspace reached resolver: %q", resolved)
}

clientCwd := t.TempDir()
if err := h.client.Call(ctx, MethodSessionLoad, LoadSessionParams{SessionID: meta.SessionID, Cwd: clientCwd}, &LoadSessionResult{}); err != nil {
t.Fatalf("session/load with client workspace: %v", err)
}
if resolved != clientCwd {
t.Fatalf("resolved workspace = %q, want ACP client workspace %q", resolved, clientCwd)
}
}

// drainText collects streamed chunks for a short window and concatenates them.
func drainText(t *testing.T, ch <-chan string) string {
t.Helper()
Expand Down
Loading
Loading