You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: apps/penpal/ERD.md
+20-4Lines changed: 20 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -171,7 +171,7 @@ see-also:
171
171
- <aid="E-PENPAL-COMMENT-ORDER"></a>**E-PENPAL-COMMENT-ORDER**: `OrderComments()` arranges comments in tree order: root comments sorted by time, replies grouped under their parents, siblings sorted by effective time. A comment's effective time is `WorkingStartedAt` when present, otherwise `CreatedAt`. This ensures agent replies are ordered by when the agent started working, not when the reply was posted. Missing parents fall back to root level.
- <aid="E-PENPAL-CHANGE-SEQ"></a>**E-PENPAL-CHANGE-SEQ**: A global monotonic sequence number increments on every comment change. `WaitForChangeSince(ctx, sinceSeq)` blocks on a channel until `changeSeq` advances or context cancels.
174
+
- <aid="E-PENPAL-CHANGE-SEQ"></a>**E-PENPAL-CHANGE-SEQ**: A global monotonic sequence number increments on every comment change. `WaitForChangeSince(ctx, sinceSeq)` blocks on a channel until `changeSeq` advances or context cancels.`WaitAndEnrich(ctx, project, worktree, sinceSeq)` combines the wait with enrichment — on change, it loads pending threads and sets working indicators; on timeout, it refreshes working timestamps. Both the MCP `penpal_wait_for_changes` tool and the REST `handleAgentWait` handler delegate to `WaitAndEnrich`.
- <aid="E-PENPAL-WORKING"></a>**E-PENPAL-WORKING**: An in-memory `working` map (keyed by `"project:path:threadID"`) tracks which threads an agent is actively processing. Each entry stores `startedAt` (time) and `afterCommentID` (the last comment ID when the agent started working). Entries expire after 60s. `SetWorking()`/`ClearWorking()` trigger SSE `comments` events. The API thread response includes `workingAfterCommentId` so the frontend renders the indicator after the correct comment.
182
182
← [P-PENPAL-WORKING](PRODUCT.md#P-PENPAL-WORKING)
183
183
184
-
- <aid="E-PENPAL-HEARTBEAT"></a>**E-PENPAL-HEARTBEAT**: An in-memory `heartbeats` map (keyed by `"project:filePath"`) records agent activity. `IsAgentActive()` returns true if heartbeat is <60s old. MCP tool calls record heartbeats.
184
+
- <aid="E-PENPAL-HEARTBEAT"></a>**E-PENPAL-HEARTBEAT**: Agent presence is determined solely by `agents.Manager.HasActiveAgent()`, which checks both spawned processes (via the process map) and CLI sessions (via the session manager). The previous heartbeat-based tracking in `comments.Store` has been removed — heartbeats are no longer needed because the agent management system is the single source of truth for agent presence.
- <aid="E-PENPAL-AGENT-AUTOSTART"></a>**E-PENPAL-AGENT-AUTOSTART**: `maybeStartAgent()` is called after `handleCreateThread` and `handleAddComment`. If the new comment's Role is `"human"` and no agent is running, one is started.
- <aid="E-PENPAL-AGENT-CLEANUP"></a>**E-PENPAL-AGENT-CLEANUP**: On agent exit: temp MCP config is removed, project heartbeats and working indicators are cleared, and the `onChange` callback fires an SSE event.
219
+
- <aid="E-PENPAL-AGENT-CLEANUP"></a>**E-PENPAL-AGENT-CLEANUP**: On agent exit: temp MCP config is removed, working indicators are cleared, and the `onChange` callback fires an SSE event.
- <aid="E-PENPAL-LAZY-INIT"></a>**E-PENPAL-LAZY-INIT**: First HTTP request triggers `sync.Once` that discovers projects, starts the watcher, then runs `populateProjects()` in a background goroutine. `populateProjects()` refreshes the cache, seeds activity, closes `readyCh`, broadcasts events, then enriches git info.
@@ -407,6 +407,22 @@ see-also:
407
407
408
408
---
409
409
410
+
## CLI Agent Tools
411
+
412
+
- <aid="E-PENPAL-CLI-ATTACH"></a>**E-PENPAL-CLI-ATTACH**: `penpal attach <path>` reads the port file (`ReadPortFile()`), launches the app if not running (same as `penpal open`), resolves the path to an absolute path, and calls `POST /api/agents/attach` with `{"path": "<abs-path>", "force": false}`. The server resolves the path to a project (via `FindProjectByPathWithWorktree`), checks for existing agents (Manager-spawned or external sessions), and either returns `{"project", "worktree", "sessionToken"}` or 409 Conflict. `--force` sends `force: true`, which kills/evicts the existing agent first. The session token is a random UUID generated server-side.
- <aid="E-PENPAL-CLI-AGENT-CMDS"></a>**E-PENPAL-CLI-AGENT-CMDS**: CLI subcommands (`files-in-review`, `list-threads`, `read-thread`, `reply`, `create-thread`, `wait`) each read the port file, call the corresponding REST API endpoint with `?session=<token>` query parameter, and print the JSON response to stdout. The server validates the session token on each request — invalid or evicted tokens return 401. All endpoints record heartbeats for the session's project. `penpal reply` accepts `--body` flag or reads from stdin. `penpal wait` calls `GET /api/agents/wait?project=X&session=T&sinceSeq=N` which uses the same `WaitForChangeSince` mechanism as the MCP `penpal_wait_for_changes` tool.
- <aid="E-PENPAL-SESSION-MGMT"></a>**E-PENPAL-SESSION-MGMT**: The server maintains an in-memory `sessions` map (keyed by token) storing project name, worktree, created-at, and last-heartbeat. Sessions expire after 90 seconds without a heartbeat. `POST /api/agents/attach` creates a session after contention checks. Agent contention checks both `agents.Manager.Status(project).Running` and active sessions. `--force` calls `Manager.Stop()` for spawned agents or marks the existing session as evicted. Evicted sessions return 401 on subsequent use. `Manager.Start()` also checks for active CLI sessions and refuses to spawn if one is attached. `Manager.StopAny()` provides a unified stop that terminates spawned agents and evicts CLI sessions in a single call — used by `POST /api/agents/stop`.
- <aid="E-PENPAL-AGENT-ACTIVE-UNIFIED"></a>**E-PENPAL-AGENT-ACTIVE-UNIFIED**: `AgentActive` in API responses checks both `agents.Manager.Status(project).Running` AND whether an active (non-expired) session exists for the project. This ensures external CLI-attached agents show the same presence indicators as internally-spawned agents.
- <aid="E-PENPAL-ADD-SOURCE"></a>**E-PENPAL-ADD-SOURCE**: `POST /api/sources` accepts a relative path. Directories create "tree" sources; `.md` files are added to a "files" source. Duplicate detection refuses paths already covered by an existing source. Only `.md` files accepted for individual file sources. Used internally by `penpal open` CLI to auto-add files to their containing project.
Copy file name to clipboardExpand all lines: apps/penpal/PRODUCT.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -302,6 +302,12 @@ Global views aggregate content across all projects. They appear as top-level ite
302
302
303
303
- <aid="P-PENPAL-CLI-OPEN"></a>**P-PENPAL-CLI-OPEN**: The `penpal open <path>...` command opens one or more files or directories in the Penpal app, launching the app if it's not running. Directories are resolved to their project; `.md` files are auto-added to their containing project if not already tracked (or a new standalone project is created). Non-`.md` files are rejected.
304
304
305
+
- <aid="P-PENPAL-CLI-ATTACH"></a>**P-PENPAL-CLI-ATTACH**: `penpal attach <path>` registers the calling agent as the active agent for a project. The command discovers the running server, resolves the path to a project, opens the file in the app, and claims agent ownership. If another agent is already attached, the command fails with an error. `--force` evicts the existing agent and takes over. On success, prints JSON with the project name, worktree (if any), and a session token. The session token must be passed to all subsequent CLI commands.
306
+
307
+
- <aid="P-PENPAL-CLI-AGENT-TOOLS"></a>**P-PENPAL-CLI-AGENT-TOOLS**: Agents interact with penpal via CLI subcommands that mirror the MCP tools: `penpal files-in-review`, `penpal list-threads`, `penpal read-thread`, `penpal reply`, `penpal create-thread`, and `penpal wait`. Each command requires `--session <token>` for authentication and prints JSON to stdout. `penpal wait` blocks up to 30 seconds and returns when comments change or on timeout — agents call it in a loop. All commands record agent heartbeats. An invalid or evicted session token causes the command to exit with an error.
308
+
309
+
- <aid="P-PENPAL-CLI-CONTENTION"></a>**P-PENPAL-CLI-CONTENTION**: At most one agent (internal or external) can be active for a project at a time. `penpal attach` fails if an agent is already active. `penpal attach --force` terminates the existing agent (kills an internally-spawned process, or evicts an external session). When an external agent's session is evicted, its next CLI command fails with an "evicted" error. Internally-launched agents and CLI-attached agents use the same contention system.
0 commit comments