Add task sidebar workflow

This commit is contained in:
2026-06-24 12:28:56 +01:00
parent 45263d59f8
commit d133839194
33 changed files with 1890 additions and 134 deletions
+33 -13
View File
@@ -24,6 +24,12 @@ type Launcher struct {
cols, rows uint16
}
type LaunchContext struct {
ParentID string
TaskID string
WorkDir string
}
func NewLauncher(sess *Session, mcpSocket string, cols, rows uint16) *Launcher {
bin, err := os.Executable()
if err != nil {
@@ -47,7 +53,7 @@ func (l *Launcher) size() (uint16, uint16) {
// LaunchAgent spawns the agent preset, applies the preset's MCP
// injection, waits for the ready signal, and types initial_prompt into
// the PTY. SPEC §7 spawn_agent, §8 conversation protocol.
func (l *Launcher) LaunchAgent(p *preset.Preset, displayName, initialPrompt, parentID string) (*Child, error) {
func (l *Launcher) LaunchAgent(p *preset.Preset, displayName, initialPrompt string, ctx LaunchContext) (*Child, error) {
if p.Kind != preset.KindAgent {
return nil, fmt.Errorf("launch: %q is not an agent preset", p.Name)
}
@@ -131,7 +137,9 @@ func (l *Launcher) LaunchAgent(p *preset.Preset, displayName, initialPrompt, par
Argv: argv,
Env: env,
Name: displayName,
ParentID: parentID,
ParentID: ctx.ParentID,
TaskID: ctx.TaskID,
WorkDir: firstNonEmpty(ctx.WorkDir, p.WorkingDir),
PresetRef: p.Name,
Identity: identity,
CleanupPaths: cleanupPaths,
@@ -163,7 +171,7 @@ func (l *Launcher) LaunchAgent(p *preset.Preset, displayName, initialPrompt, par
// LaunchCommandPreset spawns a process preset as a SPEC §7 command
// entry. No MCP injection; just argv. The entry is session-persistent
// (survives PTY exit so it can be Restart'd).
func (l *Launcher) LaunchCommandPreset(p *preset.Preset, displayName, parentID string) (*Child, error) {
func (l *Launcher) LaunchCommandPreset(p *preset.Preset, displayName string, ctx LaunchContext) (*Child, error) {
if p.Kind != preset.KindCommand {
return nil, fmt.Errorf("launch: %q is not a command preset", p.Name)
}
@@ -177,8 +185,9 @@ func (l *Launcher) LaunchCommandPreset(p *preset.Preset, displayName, parentID s
Argv: p.ResolvedArgv(),
Env: env,
Name: displayName,
ParentID: parentID,
WorkDir: p.WorkingDir,
ParentID: ctx.ParentID,
TaskID: ctx.TaskID,
WorkDir: firstNonEmpty(ctx.WorkDir, p.WorkingDir),
PresetRef: p.Name,
IdleDetection: resolveIdleDetection(p.IdleDetection),
}, cols, rows)
@@ -191,7 +200,7 @@ func (l *Launcher) LaunchCommandPreset(p *preset.Preset, displayName, parentID s
// LaunchCommandArgv spawns a freeform-argv command entry. Trust gating
// (SPEC §7) lives one level up in toolHost — by the time we get here
// trust is settled (freeform argv is implicitly trusted).
func (l *Launcher) LaunchCommandArgv(argv []string, displayName, parentID, workDir string, env []string, shell bool) (*Child, error) {
func (l *Launcher) LaunchCommandArgv(argv []string, displayName string, ctx LaunchContext, env []string, shell bool) (*Child, error) {
if shell && len(argv) > 0 {
argv = []string{"sh", "-lc", strings.Join(argv, " ")}
}
@@ -204,8 +213,9 @@ func (l *Launcher) LaunchCommandArgv(argv []string, displayName, parentID, workD
Argv: argv,
Env: env,
Name: displayName,
ParentID: parentID,
WorkDir: workDir,
ParentID: ctx.ParentID,
TaskID: ctx.TaskID,
WorkDir: ctx.WorkDir,
}, cols, rows)
}
@@ -223,7 +233,7 @@ func (l *Launcher) RestoreCommand(e persist.Entry, presets preset.Set) (*Child,
if e.PresetRef != "" {
for _, p := range presets.Processes {
if p.Name == e.PresetRef {
return l.LaunchCommandPreset(p, e.Name, "")
return l.LaunchCommandPreset(p, e.Name, LaunchContext{})
}
}
// Preset has been deleted since the entry was saved. Fall
@@ -233,12 +243,12 @@ func (l *Launcher) RestoreCommand(e persist.Entry, presets preset.Set) (*Child,
if len(e.Argv) == 0 {
return nil, fmt.Errorf("restore: entry %s has no argv", e.ID)
}
return l.LaunchCommandArgv(e.Argv, e.Name, "", e.WorkDir, nil, false)
return l.LaunchCommandArgv(e.Argv, e.Name, LaunchContext{WorkDir: e.WorkDir}, nil, false)
}
// LaunchTerminal spawns a bare interactive shell. SPEC §7 kind=terminal.
// argv defaults to $SHELL -i when empty.
func (l *Launcher) LaunchTerminal(argv []string, displayName, parentID, workDir string, env []string) (*Child, error) {
func (l *Launcher) LaunchTerminal(argv []string, displayName string, ctx LaunchContext, env []string) (*Child, error) {
if len(argv) == 0 {
sh := os.Getenv("SHELL")
if sh == "" {
@@ -255,11 +265,21 @@ func (l *Launcher) LaunchTerminal(argv []string, displayName, parentID, workDir
Argv: argv,
Env: env,
Name: displayName,
ParentID: parentID,
WorkDir: workDir,
ParentID: ctx.ParentID,
TaskID: ctx.TaskID,
WorkDir: ctx.WorkDir,
}, cols, rows)
}
func firstNonEmpty(values ...string) string {
for _, v := range values {
if v != "" {
return v
}
}
return ""
}
func (l *Launcher) writeMCPConfig(identity string) (string, error) {
dir, err := mcpRuntimeDir(identity)
if err != nil {