mirror of
https://github.com/harivansh-afk/clanker-agent.git
synced 2026-04-15 19:05:14 +00:00
move pi-mono into companion-cloud as apps/companion-os
- Copy all pi-mono source into apps/companion-os/ - Update Dockerfile to COPY pre-built binary instead of downloading from GitHub Releases - Update deploy-staging.yml to build pi from source (bun compile) before Docker build - Add apps/companion-os/** to path triggers - No more cross-repo dispatch needed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
0250f72976
579 changed files with 206942 additions and 0 deletions
229
packages/coding-agent/docs/tree.md
Normal file
229
packages/coding-agent/docs/tree.md
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
# Session Tree Navigation
|
||||
|
||||
The `/tree` command provides tree-based navigation of the session history.
|
||||
|
||||
## Overview
|
||||
|
||||
Sessions are stored as trees where each entry has an `id` and `parentId`. The "leaf" pointer tracks the current position. `/tree` lets you navigate to any point and optionally summarize the branch you're leaving.
|
||||
|
||||
### Comparison with `/fork`
|
||||
|
||||
| Feature | `/fork` | `/tree` |
|
||||
| ------- | -------------------------------------- | -------------------------------------- |
|
||||
| View | Flat list of user messages | Full tree structure |
|
||||
| Action | Extracts path to **new session file** | Changes leaf in **same session** |
|
||||
| Summary | Never | Optional (user prompted) |
|
||||
| Events | `session_before_fork` / `session_fork` | `session_before_tree` / `session_tree` |
|
||||
|
||||
## Tree UI
|
||||
|
||||
```
|
||||
├─ user: "Hello, can you help..."
|
||||
│ └─ assistant: "Of course! I can..."
|
||||
│ ├─ user: "Let's try approach A..."
|
||||
│ │ └─ assistant: "For approach A..."
|
||||
│ │ └─ [compaction: 12k tokens]
|
||||
│ │ └─ user: "That worked..." ← active
|
||||
│ └─ user: "Actually, approach B..."
|
||||
│ └─ assistant: "For approach B..."
|
||||
```
|
||||
|
||||
### Controls
|
||||
|
||||
| Key | Action |
|
||||
| ------------- | ------------------------------------------------- |
|
||||
| ↑/↓ | Navigate (depth-first order) |
|
||||
| Enter | Select node |
|
||||
| Escape/Ctrl+C | Cancel |
|
||||
| Ctrl+U | Toggle: user messages only |
|
||||
| Ctrl+O | Toggle: show all (including custom/label entries) |
|
||||
|
||||
### Display
|
||||
|
||||
- Height: half terminal height
|
||||
- Current leaf marked with `← active`
|
||||
- Labels shown inline: `[label-name]`
|
||||
- Default filter hides `label` and `custom` entries (shown in Ctrl+O mode)
|
||||
- Children sorted by timestamp (oldest first)
|
||||
|
||||
## Selection Behavior
|
||||
|
||||
### User Message or Custom Message
|
||||
|
||||
1. Leaf set to **parent** of selected node (or `null` if root)
|
||||
2. Message text placed in **editor** for re-submission
|
||||
3. User edits and submits, creating a new branch
|
||||
|
||||
### Non-User Message (assistant, compaction, etc.)
|
||||
|
||||
1. Leaf set to **selected node**
|
||||
2. Editor stays empty
|
||||
3. User continues from that point
|
||||
|
||||
### Selecting Root User Message
|
||||
|
||||
If user selects the very first message (has no parent):
|
||||
|
||||
1. Leaf reset to `null` (empty conversation)
|
||||
2. Message text placed in editor
|
||||
3. User effectively restarts from scratch
|
||||
|
||||
## Branch Summarization
|
||||
|
||||
When switching branches, user is presented with three options:
|
||||
|
||||
1. **No summary** - Switch immediately without summarizing
|
||||
2. **Summarize** - Generate a summary using the default prompt
|
||||
3. **Summarize with custom prompt** - Opens an editor to enter additional focus instructions that are appended to the default summarization prompt
|
||||
|
||||
### What Gets Summarized
|
||||
|
||||
Path from old leaf back to common ancestor with target:
|
||||
|
||||
```
|
||||
A → B → C → D → E → F ← old leaf
|
||||
↘ G → H ← target
|
||||
```
|
||||
|
||||
Abandoned path: D → E → F (summarized)
|
||||
|
||||
Summarization stops at:
|
||||
|
||||
1. Common ancestor (always)
|
||||
2. Compaction node (if encountered first)
|
||||
|
||||
### Summary Storage
|
||||
|
||||
Stored as `BranchSummaryEntry`:
|
||||
|
||||
```typescript
|
||||
interface BranchSummaryEntry {
|
||||
type: "branch_summary";
|
||||
id: string;
|
||||
parentId: string; // New leaf position
|
||||
timestamp: string;
|
||||
fromId: string; // Old leaf we abandoned
|
||||
summary: string; // LLM-generated summary
|
||||
details?: unknown; // Optional hook data
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### AgentSession.navigateTree()
|
||||
|
||||
```typescript
|
||||
async navigateTree(
|
||||
targetId: string,
|
||||
options?: {
|
||||
summarize?: boolean;
|
||||
customInstructions?: string;
|
||||
replaceInstructions?: boolean;
|
||||
label?: string;
|
||||
}
|
||||
): Promise<{ editorText?: string; cancelled: boolean }>
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `summarize`: Whether to generate a summary of the abandoned branch
|
||||
- `customInstructions`: Custom instructions for the summarizer
|
||||
- `replaceInstructions`: If true, `customInstructions` replaces the default prompt instead of being appended
|
||||
- `label`: Label to attach to the branch summary entry (or target entry if not summarizing)
|
||||
|
||||
Flow:
|
||||
|
||||
1. Validate target, check no-op (target === current leaf)
|
||||
2. Find common ancestor between old leaf and target
|
||||
3. Collect entries to summarize (if requested)
|
||||
4. Fire `session_before_tree` event (hook can cancel or provide summary)
|
||||
5. Run default summarizer if needed
|
||||
6. Switch leaf via `branch()` or `branchWithSummary()`
|
||||
7. Update agent: `agent.replaceMessages(sessionManager.buildSessionContext().messages)`
|
||||
8. Fire `session_tree` event
|
||||
9. Notify custom tools via session event
|
||||
10. Return result with `editorText` if user message was selected
|
||||
|
||||
### SessionManager
|
||||
|
||||
- `getLeafUuid(): string | null` - Current leaf (null if empty)
|
||||
- `resetLeaf(): void` - Set leaf to null (for root user message navigation)
|
||||
- `getTree(): SessionTreeNode[]` - Full tree with children sorted by timestamp
|
||||
- `branch(id)` - Change leaf pointer
|
||||
- `branchWithSummary(id, summary)` - Change leaf and create summary entry
|
||||
|
||||
### InteractiveMode
|
||||
|
||||
`/tree` command shows `TreeSelectorComponent`, then:
|
||||
|
||||
1. Prompt for summarization
|
||||
2. Call `session.navigateTree()`
|
||||
3. Clear and re-render chat
|
||||
4. Set editor text if applicable
|
||||
|
||||
## Hook Events
|
||||
|
||||
### `session_before_tree`
|
||||
|
||||
```typescript
|
||||
interface TreePreparation {
|
||||
targetId: string;
|
||||
oldLeafId: string | null;
|
||||
commonAncestorId: string | null;
|
||||
entriesToSummarize: SessionEntry[];
|
||||
userWantsSummary: boolean;
|
||||
customInstructions?: string;
|
||||
replaceInstructions?: boolean;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
interface SessionBeforeTreeEvent {
|
||||
type: "session_before_tree";
|
||||
preparation: TreePreparation;
|
||||
signal: AbortSignal;
|
||||
}
|
||||
|
||||
interface SessionBeforeTreeResult {
|
||||
cancel?: boolean;
|
||||
summary?: { summary: string; details?: unknown };
|
||||
customInstructions?: string; // Override custom instructions
|
||||
replaceInstructions?: boolean; // Override replace mode
|
||||
label?: string; // Override label
|
||||
}
|
||||
```
|
||||
|
||||
Extensions can override `customInstructions`, `replaceInstructions`, and `label` by returning them from the `session_before_tree` handler.
|
||||
|
||||
### `session_tree`
|
||||
|
||||
```typescript
|
||||
interface SessionTreeEvent {
|
||||
type: "session_tree";
|
||||
newLeafId: string | null;
|
||||
oldLeafId: string | null;
|
||||
summaryEntry?: BranchSummaryEntry;
|
||||
fromHook?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Custom Summarizer
|
||||
|
||||
```typescript
|
||||
export default function (pi: HookAPI) {
|
||||
pi.on("session_before_tree", async (event, ctx) => {
|
||||
if (!event.preparation.userWantsSummary) return;
|
||||
if (event.preparation.entriesToSummarize.length === 0) return;
|
||||
|
||||
const summary = await myCustomSummarizer(
|
||||
event.preparation.entriesToSummarize,
|
||||
);
|
||||
return { summary: { summary, details: { custom: true } } };
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Summarization failure: cancels navigation, shows error
|
||||
- User abort (Escape): cancels navigation
|
||||
- Hook returns `cancel: true`: cancels navigation silently
|
||||
Loading…
Add table
Add a link
Reference in a new issue