Skip to content

Commit e2d6a15

Browse files
committed
✨ feat(core,daemon,webapp): issue-driven agents, pluggable runtimes with fleet observability, and an incremental transcript engine
Transcript engine performance and a steadier TUI rail - The activity engine now parses only the bytes appended to a session transcript since the last read (per-adapter fold state, LRU-bounded, thread-safe), and a stat-signature memo answers unchanged reads for the cost of a stat. A daemon that pegged CPU and grew memory without bound on large transcript trees now idles near zero with flat RSS. - The TUI transcript rail force-scrolls to the tail only when the viewer is already there, and a degraded read keeps the last good turns instead of flapping to "(no transcript)" β€” no more scroll yanks on busy sessions. Issue-driven agents (GitHub and Gitea) - Comment `@grove <task>` on an issue and an agent picks it up: Grove creates a ticket-linked workspace, and follow-up comments steer the already-running session instead of starting over. Pause/resume/stop/status verbs manage the run from the issue thread. - One sticky status comment per issue stays continuously updated with the agent's state, a live todo checklist, the branch, and a deep link to the workspace session. - Ships as a reusable composite action with GitHub and Gitea caller examples, a permission-gated and deduplicated daemon ingest endpoint, comment I/O on the ticket providers, and a full setup guide. Onboarding CLI - `grove config add-project` registers a repo with Grove from the shell; `grove skills install` drops a bundled fleet-orchestration skill into Claude Code / Codex; `grove mcp install` registers Grove as an MCP server through each tool's own `mcp add`; `grove config init --with-onboarding` runs the whole funnel non-interactively. Agent introspection keeps pace with the tools - Claude Code's split-call Task system (TaskCreate/TaskUpdate) is recognized and folded into the same todo shape TodoWrite produces, so task boards reconstruct correctly on every surface with zero wire changes. - A per-adapter telemetry-env seam auto-enables native tool telemetry at launch. Transcript rendering - File edits render as always-visible inline diff cards: worktree-relative path with a full-path tooltip, +/βˆ’ line counts, a line-number gutter, and neutral code text with tinted add/remove rows. - Single-pane view widens the transcript column to use a desktop viewport; split view keeps its reading measure. Runtime spine, native interception, and observability - Launch, keep-alive, steer, and observe are decoupled from tmux behind a pluggable launch-backend seam; headless and Docker-container backends join the tmux default. - Native interception replaces keystroke hacks: default-on hook push, a native permission-prompt tool, a bidirectional steering channel, and a per-session control surface with a webapp Controls tab. - The sub-agent fleet is visible end to end: an agentic-loop spine, a per-sub-agent fleet tree in the webapp, a live token indicator, and a pinned todo/plan card above the composer. - Observability groundwork: an LLM gateway proxy, an OpenTelemetry trace instrumentor over the loop spine, and telemetry configuration composed into every launch.
1 parent 1a1f45c commit e2d6a15

144 files changed

Lines changed: 21369 additions & 654 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Grove issue-ops (dogfood)
2+
3+
# Grove's own issue-ops caller. `created` only, never `edited` β€” an edited-comment
4+
# trigger is a silent re-fire hole (tickets/CLAUDE.md verdict).
5+
on:
6+
issue_comment:
7+
types: [created]
8+
9+
# Restricted-mode Gitea (>=1.26.0) 403s an undeclared `issues: write`; declare
10+
# both up front even though older instances only parse-and-ignore this block.
11+
permissions:
12+
issues: write
13+
pull-requests: write
14+
15+
jobs:
16+
forward:
17+
name: Forward triggered comment to Grove
18+
# Fast, case-sensitive pre-filter to skip spinning up a runner on obviously
19+
# unrelated comments; the action's own guard re-checks case-insensitively.
20+
if: contains(github.event.comment.body, '@grove')
21+
# A default docker act_runner job can't reach the daemon's host loopback β€”
22+
# this needs the dedicated :host-schema runner (tickets/CLAUDE.md verdict).
23+
runs-on: grove-issueops
24+
steps:
25+
- name: Require GROVE_DAEMON_URL
26+
if: vars.GROVE_DAEMON_URL == ''
27+
run: |
28+
echo "::error::vars.GROVE_DAEMON_URL is not set on this repo β€” configure it in Settings > Actions > Variables"
29+
exit 1
30+
31+
# Local composite action lives in this same repo, so a relative
32+
# `uses: ./actions/issue-ops` needs the checkout first.
33+
- name: Checkout
34+
uses: actions/checkout@v4
35+
36+
- name: Run grove-issue-ops
37+
uses: ./actions/issue-ops
38+
with:
39+
daemon-url: ${{ vars.GROVE_DAEMON_URL }}
40+
daemon-token: ${{ secrets.GROVE_DAEMON_TOKEN }}

β€Ž.github/actionlint.yamlβ€Ž

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# actionlint config (#198). actionlint is GitHub-only, so it can't model two
2+
# things this repo legitimately does on Gitea: a custom self-hosted runner
3+
# label, and Gitea's `https://host/owner/repo@ref` absolute-URL `uses:`
4+
# extension (verified against docs.gitea.com β€” not a typo in our YAML).
5+
self-hosted-runner:
6+
labels:
7+
- grove-issueops
8+
9+
# No `config-variables:` list β€” that flag turns on strict vars-checking
10+
# repo-wide (every workflow, not just this one), which would break existing
11+
# workflows' vars unrelated to issue-ops. Leave it unset (default: no check).
12+
13+
paths:
14+
docs/examples/issue-ops-gitea-caller.yml:
15+
ignore:
16+
- 'specifying action ".+" in invalid format'
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Grove issue-ops (reusable)
2+
3+
# Reusable workflow wrapping the grove-issue-ops composite action for
4+
# consumer repos. `uses: ./actions/issue-ops` only resolves within THIS repo's
5+
# own checkout, so a cross-repo caller needs the repo-qualified form
6+
# (`bearlike/Grove/actions/issue-ops@<ref>` β€” pin a released tag, not a
7+
# branch, once one exists).
8+
9+
on:
10+
workflow_call:
11+
inputs:
12+
trigger:
13+
description: "Mention token that must open a comment"
14+
type: string
15+
required: false
16+
default: "@grove"
17+
allow-users:
18+
description: "SECURITY-SENSITIVE: comma-separated logins trusted without repo write access"
19+
type: string
20+
required: false
21+
default: ""
22+
daemon-url:
23+
description: "Base URL of the caller's Grove daemon, reachable from their self-hosted runner"
24+
type: string
25+
required: true
26+
secrets:
27+
GROVE_DAEMON_TOKEN:
28+
description: "Bearer token for the caller's Grove daemon issue-ops route"
29+
required: true
30+
31+
jobs:
32+
forward:
33+
name: Forward triggered comment to Grove
34+
# Fast, case-sensitive pre-filter so a non-command comment never spins up
35+
# the (self-hosted) runner below; the composite action's own guard
36+
# re-checks case-insensitively. A workflow_call job still sees the
37+
# caller's triggering event via the `github` context.
38+
if: contains(github.event.comment.body, inputs.trigger)
39+
# Must reach the caller's daemon over their own network β€” a GitHub-hosted
40+
# runner can't, so this needs a same-host (or same-network) self-hosted
41+
# runner, mirroring the Gitea `:host` runner constraint (tickets/CLAUDE.md).
42+
runs-on: [self-hosted]
43+
permissions:
44+
issues: write
45+
pull-requests: write
46+
steps:
47+
- name: Run grove-issue-ops
48+
uses: bearlike/Grove/actions/issue-ops@v1 # placeholder ref β€” pin once a tag ships
49+
with:
50+
daemon-url: ${{ inputs.daemon-url }}
51+
daemon-token: ${{ secrets.GROVE_DAEMON_TOKEN }}
52+
trigger: ${{ inputs.trigger }}
53+
allow-users: ${{ inputs.allow-users }}

β€ŽCLAUDE.mdβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ When you own a Gitea issue end-to-end, drive it to a merged PR on this loop. Bia
3434
| `src/grove/core/contracts/` | wire-level Pydantic shapes that cross clients | [contracts/CLAUDE.md](src/grove/core/contracts/CLAUDE.md) |
3535
| `src/grove/core/agents/` | tool-agnostic agent introspection (adapters) | [agents/CLAUDE.md](src/grove/core/agents/CLAUDE.md) |
3636
| `src/grove/core/tickets/` | branch-aware ticket providers (Gitea/GitHub/Linear) | [tickets/CLAUDE.md](src/grove/core/tickets/CLAUDE.md) |
37+
| `src/grove/core/issueops/` | issue-comment events β†’ workspace actions (command grammar + routing) | [issueops/CLAUDE.md](src/grove/core/issueops/CLAUDE.md) |
3738
| `src/grove/core/notifications/` | push notifications on agent-state edges (broker + channels) | [notifications/CLAUDE.md](src/grove/core/notifications/CLAUDE.md) |
3839
| `src/grove/daemon/` | loopback FastAPI daemon (multi-repo, SSE) | [daemon/CLAUDE.md](src/grove/daemon/CLAUDE.md) |
3940
| `src/grove/client/` | transport-agnostic attach (local PTY / SSH) | [client/CLAUDE.md](src/grove/client/CLAUDE.md) |
@@ -58,6 +59,7 @@ Working memory is **distributed and recursive**: every component owns a nested `
5859
β”‚ β”œβ”€ src/grove/core/contracts/CLAUDE.md the wire boundary (Pydantic shapes that cross clients)
5960
β”‚ β”œβ”€ src/grove/core/agents/CLAUDE.md tool-agnostic agent introspection (adapters)
6061
β”‚ β”œβ”€ src/grove/core/tickets/CLAUDE.md branch-aware ticket providers (Gitea/GitHub/Linear)
62+
β”‚ β”œβ”€ src/grove/core/issueops/CLAUDE.md issue-comment events β†’ workspace actions (grammar + routing)
6163
β”‚ └─ src/grove/core/notifications/CLAUDE.md push on agent-state edges (broker + channels)
6264
β”œβ”€ src/grove/daemon/CLAUDE.md loopback HTTP daemon (multi-repo, SSE)
6365
β”œβ”€ src/grove/client/CLAUDE.md transport-agnostic attach (local PTY / SSH)

0 commit comments

Comments
Β (0)