Nests: team rooms with core crows and borrowing

- Server: nests API, persistence, core-first picking, borrowed flag
  (built by the backend agent against EVENTS.md; tests pass with -race)
- UI: nests in the sidebar, one feed per nest, header shows the core
  team, New nest dialog, borrowed crows labeled, per-nest waiting count
- start.command: one-click local server in controlled mode (mock crows,
  localhost only, state in .crows/)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UQnSw4RRWp8uqpj3nsnnXF
This commit is contained in:
SmokeyandClaude Opus 5.5 committed 2026-10-27 13:45:04 -06:00
1 parent e9f20349a1
commit 2d85cab716
14 files changed
+782 -62

No files matched your search

+13 -4
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"slices"
"strings"
"sync"
)
@@ -30,6 +31,7 @@ type Approver interface {
type List struct {
ID, Requester, Source string
Items []string
Nest Nest // zero value means the todo nest
Parallel bool // fly items at once (web); the command line goes one by one
}
@@ -51,13 +53,16 @@ func itemID(listID string, i int) string { return fmt.Sprintf("%s-%d", listID, i
// Run flies a list and returns per-item results and the list budget.
func (p *Pipeline) Run(ctx context.Context, l List, em Emitter) ([]itemResult, *Budget) {
if l.Nest.ID == "" {
l.Nest = defaultNest
}
listBudget := &Budget{name: "list", limit: p.G.TokenBudgetPerList}
items := make([]map[string]any, len(l.Items))
for i, text := range l.Items {
items[i] = map[string]any{"item_id": itemID(l.ID, i), "text": text}
}
p.Log.Write(Event{Kind: "list", Requester: l.Requester, Note: fmt.Sprintf("%s (%d items)", l.Source, len(l.Items))})
em.Emit("list.started", map[string]any{"items": items, "requester": l.Requester})
em.Emit("list.started", map[string]any{"items": items, "requester": l.Requester, "nest_id": l.Nest.ID})
results := make([]itemResult, len(l.Items))
if l.Parallel {
@@ -89,7 +94,7 @@ func (p *Pipeline) item(ctx context.Context, l List, i int, listBudget *Budget,
em.Emit("item.skipped", map[string]any{"item_id": id, "reason": reason})
return r
}
w := &itemWatcher{em: em, itemID: id}
w := &itemWatcher{em: em, itemID: id, core: l.Nest.Crows}
itemBudget := &Budget{name: "item", limit: p.G.TokenBudgetPerItem}
t, err := p.Flock.Triage(ctx, w, item, itemBudget, listBudget)
@@ -115,7 +120,7 @@ func (p *Pipeline) item(ctx context.Context, l List, i int, listBudget *Budget,
em.Emit("verdict", map[string]any{"item_id": id, "target": "triage", "verdict": "auto"})
}
out, err := p.Flock.Execute(ctx, w, item, t, itemBudget, listBudget)
out, err := p.Flock.Execute(ctx, w, item, t, l.Nest, itemBudget, listBudget)
if err != nil {
return skip("flight failed: " + err.Error())
}
@@ -173,10 +178,14 @@ func (p *Pipeline) save() {
type itemWatcher struct {
em Emitter
itemID string
core []string // the nest's core crows; empty for todo
}
// Flying marks a crow borrowed when the nest has a core and the crow is a
// roster crow outside it. The Sentry and Elder are never borrowed.
func (w *itemWatcher) Flying(crow string) {
w.em.Emit("crow.flying", map[string]any{"item_id": w.itemID, "crow": crow})
borrowed := len(w.core) > 0 && slices.Contains(roster, crow) && !slices.Contains(w.core, crow)
w.em.Emit("crow.flying", map[string]any{"item_id": w.itemID, "crow": crow, "borrowed": borrowed})
}
func (w *itemWatcher) Landed(crow string, tokens int, err error) {