Reconcile docs with reality; fix module graph, token compare, health #1

Open
kami wants to merge 216 commits from webui-and-audit-reconciliation into master
6 changed files with 86 additions and 6 deletions
Showing only changes of commit 2417a39a1e - Show all commits
+3 -3
View File
@@ -997,7 +997,7 @@ func (w *worker) renewLeases(ctx context.Context) {
now := time.Now()
for taskID, l := range w.leases {
s, ok := w.sessions[taskID]
if !ok || l.Version == 0 || l.Until.After(now.Add(10*time.Minute)) {
if !ok || l.Version == 0 || l.Until.After(now.Add(domain.LeaseRenewAt)) {
continue
}
adapter := herdr.CLIAdapter{Backend: w.executionBackend(), Harness: w.harness}
@@ -1027,14 +1027,14 @@ func (w *worker) renewLeases(ctx context.Context) {
w.recordError(fmt.Errorf("lease %s not renewed: agent status %s and pane unchanged since the last renewal", taskID, status))
continue
}
if err := w.api.Renew(ctx, taskID, l.Epoch, l.Version, int((30 * time.Minute).Seconds())); err != nil {
if err := w.api.Renew(ctx, taskID, l.Epoch, l.Version, int(domain.LeaseTTL.Seconds())); err != nil {
w.recordError(fmt.Errorf("renew lease %s: %w", taskID, err))
log.Printf("renew lease %s: %v", taskID, err)
} else {
// RenewLease appends one event. Retain that epoch locally until its
// replay arrives so a release transaction uses the same version.
l.Version++
l.Until = now.Add(30 * time.Minute)
l.Until = now.Add(domain.LeaseTTL)
l.ProgressSHA = progress
w.leases[taskID] = l
_ = w.save()
+2 -2
View File
@@ -992,7 +992,7 @@ func main() {
return
}
if p.TTLSeconds <= 0 {
p.TTLSeconds = 1800
p.TTLSeconds = int(domain.LeaseTTL.Seconds())
}
e, err = s.Lease(taskID, p.HarnessID, time.Duration(p.TTLSeconds)*time.Second)
case "release", "complete", "block", "attention":
@@ -1521,7 +1521,7 @@ func main() {
if strings.HasSuffix(r.URL.Path, "/renew") {
ttl := b.TTLSeconds
if ttl == 0 {
ttl = int((30 * time.Minute).Seconds())
ttl = int(domain.LeaseTTL.Seconds())
}
e, err := s.RenewLease(b.TaskID, parts[3], b.LeaseEpoch, b.ExpectedVersion, time.Duration(ttl)*time.Second)
if err != nil {
+11
View File
@@ -26,6 +26,17 @@ var ErrDuplicate = errors.New("duplicate task ingestion")
// authorizing Surface; schema 3 adds lease fencing epochs. Older events stay
// readable so a deployment can recover its existing log before new writes
// are emitted (the store derives a non-renewable legacy epoch on replay).
// LeaseTTL is how long a lease survives without renewal, and LeaseRenewAt is
// how much remaining time makes the worker renew. Both halves read these, so a
// coordinator granting one TTL while a worker assumes another is not possible.
// Short leases are the recovery mechanism for a stalled agent: a stalled pane
// is only reclaimed when its lease runs out, and 30 minutes per window made
// run 5's review stall unbounded in practice.
const (
LeaseTTL = 5 * time.Minute
LeaseRenewAt = LeaseTTL / 2
)
const CurrentEventSchema = 3
type TaskState string
+30
View File
@@ -374,6 +374,11 @@ func (b *TmuxBackend) LaunchTransport(harness string) LaunchTransport {
// what is still unsubmitted; see inputState.
var promptLine = regexp.MustCompile(`(?m)^[ \t]*[>][ \t]*(.*)$`)
// chromeLine matches the status lines Claude Code redraws on its own schedule:
// the spinner summary with its elapsed timer, and the version notice. Neither
// is the agent writing anything, and both change while a pane sits idle.
var chromeLine = regexp.MustCompile(`^[ \t]*(?:[\x{273B}\x{273D}\x{2733}\x{2722}\x{00B7}*]|current:[ \t])`)
// separatorRow matches the rule Claude Code draws above and below its editor.
var separatorRow = regexp.MustCompile(`^[\s─━┄┅┈┉-]+$`)
@@ -453,6 +458,31 @@ func (b *TmuxBackend) PaneProgress(ctx context.Context, s Session) (string, erro
}
kept = append(kept, line)
}
// Everything below the editor's lower rule is harness chrome: the model
// name, the rolling usage percentage, the context counter, the update
// banner. F41, live on run 5: the agent produced nothing after 02:12 and
// the 02:46 renewal was granted anyway, because one of those fields ticked
// inside the window. Progress means output the agent wrote, so the tail
// after the last rule is dropped. AgentStatus reads the raw capture, so
// the busy markers that live down there are unaffected.
// ponytail: last rule wins, the editor is always the bottom-most one.
for i := len(kept) - 1; i >= 0; i-- {
if separatorRow.MatchString(kept[i]) {
kept = kept[:i]
break
}
}
// The spinner summary and the version notice render above the editor, so
// the rule cut alone leaves them in. On run 5 the notice was the one that
// moved: "current: 2.1.247 - latest: 2.1.248" gained "Update installed".
for len(kept) > 0 {
last := kept[len(kept)-1]
if strings.TrimSpace(last) == "" || separatorRow.MatchString(last) || chromeLine.MatchString(last) {
kept = kept[:len(kept)-1]
continue
}
break
}
return strings.Join(kept, "\n"), nil
}
+39
View File
@@ -491,3 +491,42 @@ exit 0
t.Fatalf("err=%v, want ErrPromptNotSubmitted", err)
}
}
// F41, live on run 5: the review agent produced nothing after 02:12 and the
// 02:46 renewal was granted, because the digest covered the harness footer and
// one of its fields ticked. The layout below is the real pane, with the usage
// percentage and the version banner moved on.
func TestPaneProgressIgnoresHarnessFooter(t *testing.T) {
body := "────\n reviewed the diff, no blocking findings\n────\n \n"
footer := func(pct, banner string) string {
return body +
" [Opus 5] 📁 06G4A4F0TFXKZHJE48N05XN1HG | 7d: " + pct + "\n" +
" cf474986 - Orchestra launch instructions | 57.9…\n" +
" ⏵⏵ auto mode on (shift+tab to cycle) · " + banner + "\n"
}
before := paneTmux(t, footer("51%", "current: 2.1.247 · latest: 2.1.248"), 3)
after := paneTmux(t, footer("52%", "✔ Update installed · Restart to update"), 3)
a, err := before.PaneProgress(context.Background(), Session{PaneID: "s:1.0"})
if err != nil {
t.Fatal(err)
}
b, err := after.PaneProgress(context.Background(), Session{PaneID: "s:1.0"})
if err != nil {
t.Fatal(err)
}
if a != b {
t.Fatalf("footer churn changed the progress digest:\n%q\n%q", a, b)
}
if !strings.Contains(a, "reviewed the diff") {
t.Fatalf("progress digest dropped harness output: %q", a)
}
worked := paneTmux(t, "────\n reviewed the diff, wrote .orchestra/done\n────\n \n"+
" [Opus 5] 📁 06G4A4F0TFXKZHJE48N05XN1HG | 7d: 51%\n", 3)
c, err := worked.PaneProgress(context.Background(), Session{PaneID: "s:1.0"})
if err != nil {
t.Fatal(err)
}
if c == a {
t.Fatal("real agent output left the progress digest unchanged")
}
}
+1 -1
View File
@@ -315,7 +315,7 @@ func (r *Router) AssignPending() ([]domain.Event, error) {
r.reject(t.ID, h.ID, "no free concurrency")
continue
}
e, err := r.Store.Lease(t.ID, h.ID, 30*time.Minute)
e, err := r.Store.Lease(t.ID, h.ID, domain.LeaseTTL)
if err != nil {
// Includes a pre-lease reconciliation refusal, which is the
// one gate that fails closed on purpose.