Simplify session lifecycle and MCP cleanup

This commit is contained in:
2026-05-14 20:51:37 +01:00
parent 27361f79c4
commit cc4bf9e904
16 changed files with 439 additions and 255 deletions

View File

@@ -56,14 +56,12 @@ func (l *Launcher) LaunchAgent(p *preset.Preset, displayName, initialPrompt, par
env = append(env, k+"="+v)
}
// Mint a per-spawn MCP config file pointing at the mcp-stdio proxy
// with the new child's identity. We don't know the identity until
// we've created the child, but the child needs the env/argv at
// creation time — so we reserve the identity by pre-creating the
// MCP config with a placeholder, then patching it post-spawn.
identity, mcpConfigPath, err := l.writeMCPConfig()
if err != nil {
return nil, err
identity := mintIdentity()
var cleanupPaths []string
cleanup := func() {
for _, path := range cleanupPaths {
_ = os.RemoveAll(path)
}
}
if p.MCPInjection != nil {
@@ -72,24 +70,33 @@ func (l *Launcher) LaunchAgent(p *preset.Preset, displayName, initialPrompt, par
if p.MCPInjection.Flag == "" {
return nil, fmt.Errorf("preset %s: mcp_injection.flag required for kind=flag", p.Name)
}
mcpConfigPath, err := l.writeMCPConfig(identity)
if err != nil {
return nil, err
}
cleanupPaths = append(cleanupPaths, mcpConfigPath)
argv = append(argv, p.MCPInjection.Flag, mcpConfigPath)
case "env_var":
if p.MCPInjection.Var == "" {
return nil, fmt.Errorf("preset %s: mcp_injection.var required for kind=env_var", p.Name)
}
mcpConfigPath, err := l.writeMCPConfig(identity)
if err != nil {
return nil, err
}
cleanupPaths = append(cleanupPaths, mcpConfigPath)
env = append(env, p.MCPInjection.Var+"="+mcpConfigPath)
case "config_file":
// Merge patterm's MCP entry into a vendored copy of the
// user's existing config file, then point the child at the
// vendored copy via the preset's home_var. The real config
// file is never modified.
envAssign, _, mErr := mcpConfigMerge(p, p.MCPInjection, identity, l.bin, l.mcpSocket)
envAssign, homeDir, mErr := mcpConfigMerge(p, p.MCPInjection, identity, l.bin, l.mcpSocket)
if mErr != nil {
_ = os.Remove(mcpConfigPath)
return nil, mErr
}
cleanupPaths = append(cleanupPaths, homeDir)
env = append(env, envAssign)
env = append(env, "PATTERM_MCP_CONFIG="+mcpConfigPath)
case "cli_override":
// Inline -c key=value overrides for agents that accept
// them (codex's `-c mcp_servers.patterm.command=...`). No
@@ -97,7 +104,6 @@ func (l *Launcher) LaunchAgent(p *preset.Preset, displayName, initialPrompt, par
// are untouched.
extra, err := mcpCLIOverrideArgs(p, p.MCPInjection, identity, l.bin, l.mcpSocket)
if err != nil {
_ = os.Remove(mcpConfigPath)
return nil, err
}
argv = append(argv, extra...)
@@ -108,11 +114,11 @@ func (l *Launcher) LaunchAgent(p *preset.Preset, displayName, initialPrompt, par
// XDG_CONFIG_HOME stays as the user set it.
assignment, err := mcpConfigEnv(p, p.MCPInjection, identity, l.bin, l.mcpSocket)
if err != nil {
_ = os.Remove(mcpConfigPath)
return nil, err
}
env = append(env, assignment)
default:
cleanup()
return nil, fmt.Errorf("preset %s: unknown mcp_injection.kind %q", p.Name, p.MCPInjection.Kind)
}
}
@@ -120,16 +126,17 @@ func (l *Launcher) LaunchAgent(p *preset.Preset, displayName, initialPrompt, par
// Spawn with the chosen identity.
cols, rows := l.size()
c, err := l.sess.Spawn(SpawnSpec{
Kind: KindAgent,
Argv: argv,
Env: env,
Name: displayName,
ParentID: parentID,
PresetRef: p.Name,
Identity: identity,
Kind: KindAgent,
Argv: argv,
Env: env,
Name: displayName,
ParentID: parentID,
PresetRef: p.Name,
Identity: identity,
CleanupPaths: cleanupPaths,
}, cols, rows)
if err != nil {
_ = os.Remove(mcpConfigPath)
cleanup()
return nil, err
}
@@ -219,17 +226,16 @@ func (l *Launcher) LaunchTerminal(argv []string, displayName, parentID, workDir
}, cols, rows)
}
func (l *Launcher) writeMCPConfig() (identity, path string, err error) {
identity = mintIdentity()
func (l *Launcher) writeMCPConfig(identity string) (string, error) {
dir, err := preset.ConfigDir()
if err != nil {
return "", "", err
return "", err
}
dir = filepath.Join(dir, "mcp")
if err := os.MkdirAll(dir, 0o700); err != nil {
return "", "", err
return "", err
}
path = filepath.Join(dir, identity+".json")
path := filepath.Join(dir, identity+".json")
cfg := map[string]any{
"mcpServers": map[string]any{
"patterm": map[string]any{
@@ -240,13 +246,13 @@ func (l *Launcher) writeMCPConfig() (identity, path string, err error) {
}
body, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return "", "", err
return "", err
}
body = append(body, '\n')
if err := os.WriteFile(path, body, 0o600); err != nil {
return "", "", err
return "", err
}
return identity, path, nil
return path, nil
}
// waitForIdle polls the child's IdleMS until it exceeds idle, or until