Add task sidebar workflow
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
# Task Sidebar Simplification Plan
|
||||
|
||||
## User Decisions
|
||||
|
||||
- First pass is manual project-local tasks only. Future GitHub/Linear linking should not be built now.
|
||||
- The right sidebar should put Tasks first, while keeping existing process, agent tree, and scratchpad navigation secondary.
|
||||
- Generic palette-spawned top-level agents must not inherit a selected task. Only explicit task-scoped actions, such as `Start agent for task`, create task-bound agents.
|
||||
- Agents should receive task context only when they are task-bound, and task-bound agents should be told to register task worktrees through MCP.
|
||||
|
||||
## UX Review: Missing Pieces Today
|
||||
|
||||
- There is no task model. The current right rail in `internal/app/sidebar.go` is `Processes`, `Agent Tree`, and `Scratchpads` only.
|
||||
- There is no task list, task detail view, task-focused navigation entry, or visible way to discover existing tasks.
|
||||
- Task creation is absent. Existing creation flows are process/agent palette actions in `internal/app/palette.go` and `internal/app/app.go`.
|
||||
- There is no explicit task-scoped launch action, so a user cannot intentionally start an agent for a task.
|
||||
- MCP has no task context and no worktree registration tool, so agents cannot tell patterm which worktrees belong to a task.
|
||||
- Current process rows are not grouped by task and do not surface task membership.
|
||||
- Sidebar empty states are generic process/scratchpad placeholders and do not advertise task creation.
|
||||
- The sidebar has no click or row activation model. First pass should stay keyboard/palette driven rather than inventing mouse interactions.
|
||||
- Cleanup/status/archive and external issue links are real task-list UX needs, but they are out of scope for this first simplification pass unless requested later.
|
||||
|
||||
## Recommended Scope
|
||||
|
||||
Implement a minimal first-class task feature:
|
||||
|
||||
- Persist manual tasks per project.
|
||||
- Render Tasks as the first sidebar section.
|
||||
- Let users create, open, and rename tasks through the palette.
|
||||
- Render a focused task detail view in the main viewport.
|
||||
- Add explicit `Start agent for task` actions for focused tasks and task-bound children.
|
||||
- Thread `TaskID` through children spawned from task context.
|
||||
- Expose task context and `task_register_worktree` only to task-bound MCP callers.
|
||||
- Fix PTY working-directory propagation, because task worktree paths are otherwise misleading.
|
||||
|
||||
Do not add GitHub/Linear linking, task import, agent-side task creation, task list MCP tools, click handling, or broad task/project management UI in this pass.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### 1. Add Project Task Store
|
||||
|
||||
Add `internal/task/task.go` and `internal/task/task_test.go`.
|
||||
|
||||
Persist to `$XDG_DATA_HOME/patterm/projects/<projectKey>/tasks.json`, matching the existing project-local persistence pattern in `internal/persist/persist.go`.
|
||||
|
||||
Minimal structs:
|
||||
|
||||
```go
|
||||
type Task struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Worktrees []Worktree `json:"worktrees,omitempty"`
|
||||
}
|
||||
|
||||
type Worktree struct {
|
||||
Path string `json:"path"`
|
||||
Branch string `json:"branch,omitempty"`
|
||||
CreatedByProcessID string `json:"created_by_process_id,omitempty"`
|
||||
RegisteredAt string `json:"registered_at"`
|
||||
}
|
||||
```
|
||||
|
||||
Store API:
|
||||
|
||||
```go
|
||||
Open(projectKey string) (*Store, error)
|
||||
(*Store).List() []Task
|
||||
(*Store).Get(id string) (Task, bool)
|
||||
(*Store).Create(title string) (Task, error)
|
||||
(*Store).Rename(id, title string) (Task, error)
|
||||
(*Store).RegisterWorktree(taskID string, wt Worktree) (Task, error)
|
||||
```
|
||||
|
||||
Keep it deliberately small:
|
||||
|
||||
- Generate opaque IDs like `task_<hex>`.
|
||||
- Validate non-empty titles.
|
||||
- Use stable list ordering.
|
||||
- De-duplicate worktrees by cleaned absolute path.
|
||||
- Atomic write via `tasks.json.tmp` then rename.
|
||||
- No status, assignee, priority, external refs, labels, or issue links yet.
|
||||
|
||||
### 2. Fix PTY Working Directory
|
||||
|
||||
Change `internal/pty/pty.go`:
|
||||
|
||||
- Update `Start(argv, env, cols, rows)` to `Start(argv, env, workDir, cols, rows)`.
|
||||
- Set `cmd.Dir = workDir` when non-empty.
|
||||
|
||||
Update call sites:
|
||||
|
||||
- `internal/app/child.go: startPTY` passes `c.WorkDir`.
|
||||
- `internal/harness/session.go` and `internal/harness/restart_persist_test.go` pass an empty workdir or project dir as appropriate.
|
||||
- `cmd/spike/main.go` passes an empty workdir.
|
||||
|
||||
Add a focused PTY/app test that spawns `sh -lc pwd` with a temp workdir and verifies the child runs there.
|
||||
|
||||
### 3. Thread Task Context Through Runtime State
|
||||
|
||||
Modify `internal/app/child.go`:
|
||||
|
||||
- Add `TaskID string` to `Child`.
|
||||
- Add `taskID` to `newChildEntry`.
|
||||
|
||||
Modify `internal/app/session.go`:
|
||||
|
||||
- Add `TaskID string` to `SpawnSpec`.
|
||||
- Pass it into `newChildEntry`.
|
||||
- Keep persistence limited to top-level command entries. Do not persist agent/task process trees in `processes.json`.
|
||||
|
||||
Modify `internal/app/launch.go`:
|
||||
|
||||
- Avoid adding more positional string parameters. Introduce a small launch context struct, for example:
|
||||
|
||||
```go
|
||||
type LaunchContext struct {
|
||||
ParentID string
|
||||
TaskID string
|
||||
WorkDir string
|
||||
}
|
||||
```
|
||||
|
||||
- Update `LaunchAgent`, `LaunchCommandPreset`, `LaunchCommandArgv`, and `LaunchTerminal` to accept this context or an equivalent minimal option.
|
||||
- Generic palette spawns pass empty `TaskID` even when a task is focused.
|
||||
- Explicit task actions pass `TaskID`.
|
||||
- MCP spawns inherit `TaskID` from the caller only when the caller is task-bound.
|
||||
|
||||
### 4. Wire Task Store Into App State
|
||||
|
||||
Modify `internal/app/app.go`:
|
||||
|
||||
- Open `task.Open(opts.ProjectKey)` during `Run`, near scratchpads/trust/persist stores.
|
||||
- Add `tasks *task.Store` to `uiState`.
|
||||
- Add `focusedTaskID string`, mutually exclusive with `focusedID` and `focusedPad`.
|
||||
- Add `tasksCacheMu` / `tasksCache`, mirroring `padsList()`.
|
||||
- Add `tasksList()`, `invalidateTasksCache()`, and `tasksChanged()`.
|
||||
- Pass the task store to `newToolHost`.
|
||||
|
||||
Modify `internal/app/host.go`:
|
||||
|
||||
- Add `tasks *task.Store` to `toolHost`.
|
||||
- Extend `newToolHost` to accept it.
|
||||
- Add a small `taskSink` interface like `tasksChanged()` so MCP worktree registration can refresh the sidebar/detail view.
|
||||
|
||||
### 5. Make Tasks First In The Sidebar
|
||||
|
||||
Modify `internal/app/sidebar.go:drawSidebar`:
|
||||
|
||||
- Render `Tasks` before `Processes`.
|
||||
- Show empty state as something actionable, for example `(Ctrl-K create task)`.
|
||||
- Show task rows with focus marker, title, and a compact suffix such as `2 worktrees` or `2w`.
|
||||
- Keep current `Processes`, `Agent Tree`, and `Scratchpads` sections after Tasks.
|
||||
- Do not add mouse or click behavior in this pass.
|
||||
|
||||
Modify `internal/app/tree.go`:
|
||||
|
||||
- Extend `navEntry` with `taskID string` and `isTask()`.
|
||||
- Change `empty()` to include task entries.
|
||||
- Change `sidebarNav` and `nextNavEntry` to accept tasks and order entries as `tasks -> processes -> active agent tree -> scratchpads`.
|
||||
- Keep `nextChildID` compatibility tests working for process-only behavior.
|
||||
|
||||
Modify `internal/app/app.go`:
|
||||
|
||||
- Add `focusTask(taskID string)`.
|
||||
- Add `renderTaskView(taskID string)` and `repaintFocusedTask()`.
|
||||
- Update `focusProcess` and `focusScratchpad` to clear `focusedTaskID`.
|
||||
- Update `repaintFocused`, `repaintFocusedWithChrome`, `restoreView` inside `closePalette`, and Ctrl-W/S pending nav handling to route task entries correctly.
|
||||
- Update `drawStatusLine` to show `task: <title>` when a task is focused, and optionally `task: <title>` when a focused child is task-bound.
|
||||
- Update `renderEmptyState` copy to mention tasks, for example `Press Ctrl-K to create a task or spawn an agent/process`.
|
||||
|
||||
Focused task detail view should be read-only and compact:
|
||||
|
||||
- Title and task ID.
|
||||
- Registered worktrees.
|
||||
- Task-bound running children if cheap to derive from `Session.Children()`.
|
||||
- Hints: `Ctrl-K task actions`, `Ctrl-W/S navigate`.
|
||||
|
||||
### 6. Add Palette Task Actions
|
||||
|
||||
Modify `internal/app/palette.go`:
|
||||
|
||||
- Add task list and focused task fields to `paletteState`.
|
||||
- Add `taskID string` to `paletteAction`.
|
||||
- Add task action kinds:
|
||||
- `task-create-form`
|
||||
- `task-create-submit`
|
||||
- `task-switch`
|
||||
- `task-rename-form`
|
||||
- `task-rename-submit`
|
||||
- `task-start-agent`
|
||||
- Add a task macro if useful, but do not make it required for the first patch.
|
||||
- Reuse `renameForm` for create/rename by allowing subject `task` and different submit kinds.
|
||||
|
||||
Palette item behavior:
|
||||
|
||||
- Global/Open group includes `Create task...` and `Open task: <title>`.
|
||||
- When a task is focused, show `Rename task` and `Start agent for task: <preset>` rows.
|
||||
- When a task-bound child is focused, show `Open task: <title>` and `Start another agent for task: <preset>` rows.
|
||||
- Normal `Spawn agent: <preset>` remains unscoped and passes empty `TaskID`.
|
||||
|
||||
Modify `internal/app/app.go:openPaletteLocked`:
|
||||
|
||||
- Pass `tasksList()`, `focusedTaskID`, and current focused child task information into `newPalette`.
|
||||
|
||||
Modify `internal/app/app.go:closePalette`:
|
||||
|
||||
- `task-create-submit`: create task, invalidate cache, focus the new task.
|
||||
- `task-switch`: focus task.
|
||||
- `task-rename-submit`: rename task, invalidate cache, redraw/focus task.
|
||||
- `task-start-agent`: launch selected agent preset with the task ID and a task initial prompt.
|
||||
|
||||
Task initial prompt should be injected only for task-bound agents. Keep it single-line because agent input submission is line-sensitive:
|
||||
|
||||
```text
|
||||
[system: you are working on patterm task "<title>" (<task_id>). If you create or use git worktrees for this task, call task_register_worktree with the path and branch.]
|
||||
```
|
||||
|
||||
Include known registered worktrees in the prompt only if the list is short; otherwise rely on `whoami`.
|
||||
|
||||
### 7. Add Conditional MCP Task Context And Worktree Registration
|
||||
|
||||
Modify `internal/mcp/tools.go`:
|
||||
|
||||
- Add MCP structs `TaskInfo`, `TaskWorktree`, and `TaskRegisterWorktreeArgs`.
|
||||
- Add optional `Task *TaskInfo `json:"task,omitempty"`` to `WhoAmI` and `ProjectStatus`.
|
||||
- Do not add task fields to `ProcessInfo` in this pass, so unbound agents cannot discover task assignments through `list_processes`.
|
||||
- Extend `ToolHost` with:
|
||||
|
||||
```go
|
||||
CallerTask(processID string) (TaskInfo, bool)
|
||||
RegisterTaskWorktree(callerID string, args TaskRegisterWorktreeArgs) (TaskInfo, error)
|
||||
```
|
||||
|
||||
`TaskRegisterWorktreeArgs` should contain only `path` and optional `branch`. Do not accept a task ID from the caller; the host infers the task from `callerID`.
|
||||
|
||||
Modify `internal/mcp/protocol.go`:
|
||||
|
||||
- Replace the constant-only `serverInstructions` path with a helper that can append task-bound instructions only when `host.CallerTask(callerID)` is present.
|
||||
- Change `toolCatalog(role)` to `toolCatalog(role, taskBound bool)`.
|
||||
- Advertise `task_register_worktree` only when `taskBound` is true.
|
||||
- Keep `spawn_agent` hidden from sub-agents as today.
|
||||
|
||||
Modify `internal/mcp/tools.go:callTool`:
|
||||
|
||||
- Add case `task_register_worktree`.
|
||||
- Reject unbound callers with `role_forbidden`.
|
||||
- Call `h.RegisterTaskWorktree(callerID, args)` and return the updated task.
|
||||
|
||||
Modify `internal/app/host.go`:
|
||||
|
||||
- Add helper `callerTaskID(callerID string) string` based on `Child.TaskID`.
|
||||
- Implement `CallerTask` by resolving the caller child and task store entry.
|
||||
- `WhoAmI` includes `Task` only when the caller child has a valid task ID.
|
||||
- `GetProjectStatus` includes the same optional task only for task-bound callers.
|
||||
- `SpawnAgent` and `SpawnProcess` inherit `TaskID` from the caller.
|
||||
- Replace `wrapSubAgentPrompt` with a small prompt builder that can include sub-agent instructions, task instructions, or both. Task instructions should be added whenever the spawned agent is task-bound, even when caller `agent_instructions` is empty.
|
||||
- `RegisterTaskWorktree` resolves relative paths against the caller child `WorkDir`, falling back to the project dir, cleans/absolutizes the path, stores it, and triggers `tasksChanged()`.
|
||||
|
||||
Important security/UX rule:
|
||||
|
||||
- Unbound MCP callers should not receive task context, task tool availability, task worktree data, or task IDs.
|
||||
|
||||
### 8. Tests
|
||||
|
||||
Add or update focused unit tests before broad harness work.
|
||||
|
||||
Task store:
|
||||
|
||||
- `internal/task/task_test.go`: create/list persists across reopen.
|
||||
- Validate empty title errors.
|
||||
- Rename updates title and `updated_at`.
|
||||
- Register worktree de-duplicates cleaned absolute paths.
|
||||
|
||||
Runtime/spawn:
|
||||
|
||||
- `internal/app/session_test.go` or focused launch tests: `TaskID` lands on spawned child.
|
||||
- MCP spawn from task-bound caller propagates `TaskID` to child.
|
||||
- Generic palette spawn while `focusedTaskID` is set keeps `TaskID == ""`.
|
||||
- Explicit `task-start-agent` sets `TaskID` and injects task prompt.
|
||||
|
||||
Sidebar/navigation/palette:
|
||||
|
||||
- `internal/app/tree_test.go`: task nav entries precede processes, agent tree, and scratchpads.
|
||||
- `internal/app/palette_context_test.go`: focused task shows task actions; generic spawn remains unscoped.
|
||||
- Add render-level tests only where existing helpers make that cheap; avoid brittle full-frame snapshots.
|
||||
|
||||
MCP:
|
||||
|
||||
- `internal/mcp/protocol_test.go`: unbound tools/list omits `task_register_worktree`; task-bound tools/list includes it.
|
||||
- `internal/mcp/tools.go` tests or app host tests: unbound registration is rejected.
|
||||
- `internal/app/host_test.go`: task-bound `whoami` includes task; unbound `whoami` omits task.
|
||||
- `internal/app/host_test.go`: registration stores an absolute path and returns updated task info.
|
||||
|
||||
PTY working dir:
|
||||
|
||||
- Add the smallest test proving a PTY child honors configured `WorkDir`.
|
||||
|
||||
Harness:
|
||||
|
||||
- Add one lightweight scenario under `internal/harness/scenarios/`:
|
||||
- Open palette.
|
||||
- Create a task.
|
||||
- Assert sidebar shows `Tasks` and the task title.
|
||||
- Start a fake agent through `Start agent for task`.
|
||||
- Use MCP `whoami` from that fake agent if feasible, or assert the prompt/context path indirectly through captured output.
|
||||
|
||||
### 9. Changelog
|
||||
|
||||
Update `CHANGELOG.md` under `[Unreleased]`.
|
||||
|
||||
Suggested bullets:
|
||||
|
||||
```md
|
||||
### Added
|
||||
- Added project-local manual tasks in the right sidebar, with palette actions to create tasks, open tasks, rename tasks, and start task-scoped agents.
|
||||
- Added task-bound MCP context and `task_register_worktree` so agents launched for a task can register git worktrees they create.
|
||||
|
||||
### Fixed
|
||||
- Child PTYs now honor their configured working directory when launched.
|
||||
```
|
||||
|
||||
## Verification Commands
|
||||
|
||||
Run focused checks first:
|
||||
|
||||
```sh
|
||||
go test ./internal/task/...
|
||||
go test ./internal/pty/...
|
||||
go test ./internal/app/...
|
||||
go test ./internal/mcp/...
|
||||
```
|
||||
|
||||
Then run broader checks:
|
||||
|
||||
```sh
|
||||
go test ./internal/harness/...
|
||||
go test ./...
|
||||
go build -o ./bin/patterm ./cmd/patterm
|
||||
```
|
||||
|
||||
Manual TUI smoke test:
|
||||
|
||||
```sh
|
||||
./bin/patterm --project /home/harry/Dev/patterm
|
||||
```
|
||||
|
||||
Manual flow to verify:
|
||||
|
||||
- Press `Ctrl-K`, create a task, and confirm it appears first in the sidebar.
|
||||
- Focus the task from the sidebar navigation with `Ctrl-W/S`.
|
||||
- Press `Ctrl-K`, start an agent for that task, and confirm the task remains visible and the agent is task-bound.
|
||||
- Start a normal agent through `Spawn agent` while the task is selected and confirm it is not task-bound.
|
||||
- From a task-bound fake/test agent, call `whoami` and confirm task context appears.
|
||||
- From an unbound agent, call `whoami` and confirm no task context appears.
|
||||
- From a task-bound agent, call `task_register_worktree` and confirm the task detail view/sidebar suffix updates.
|
||||
|
||||
If harness tests fail with Unix socket or PTY permission errors, rerun them in an environment that permits sockets and PTYs, as documented in `AGENTS.md`.
|
||||
Reference in New Issue
Block a user