Skip to content

Commit d0fb1c2

Browse files
committed
feat: implement 80+ features from 10 OSS repos + research papers
Sources compared: - lacymorrow/lacy (shell UX, terminal context, NL detection) - EleutherAI/lm-evaluation-harness (eval framework, caching, YAML tasks) - higgsfield-ai/skills (skill ecosystem, multi-agent manifests, validation CI) - aaif-goose/goose (recipes, malware check, adversary inspector, OAuth, Telegram) - bmad-code-org/BMAD-METHOD (scale-adaptive, adversarial review, project context) - aaronjmars/aeon (self-improve, reflect, coding soul) - SylphAI-Inc/AdalFlow (prompt optimizer, few-shot selector, textual gradients) - karpathy/autoresearch (autonomous experiment loop) - Aider-AI/aider (auto-commit, directive scanner, edit strategies) - opencode-ai/opencode (event bus, diff sandbox patterns) Key features added: - Terminal context capture (delta-based) - Background preheating + connection warmup - Real-time input classification indicator - Smart NL rerouting on shell command failure - Braille spinner animations with shimmer - Ghost text suggestions (project-aware) - Mode toggling (shell/agent/auto) with persistence - Eval CLI (hawk eval run/list/results/cache-clear) - YAML task definitions + result caching + reproducibility hashes - Recipe system (YAML-based guided workflows) - Extension malware check + adversarial/egress inspector - Langfuse tracing + OAuth device flow + Telegram gateway - Tool monitor + custom distributions support - Scale-adaptive intelligence (patch/minor/major/epic) - Adversarial review + project context + quick-dev workflow - Checkpoint preview + course correction + investigation workflow - Party mode (multi-persona) + brainstorming facilitation - Self-improvement (cross-session learning) + coding soul - Prompt auto-optimizer (textual gradients, few-shot selection) - Autonomous experiment loop (modify/validate/keep/discard) - Agent intelligence (auto-decomposition, pipeline detection, synthesis) - Auto-commit + directive scanner + edit strategy selector - Event bus (pub/sub) + clipboard bridge - Spec-driven development (/spec command) - Assumption tracker + quality gates + degradation detector - Multi-repo context loading - .hawkhints loader + source roots tracking - Tool inspector (confidence-based) + tool confirmation router - Large response handler (pagination) - Background runner (async subagent delegation) - Compaction trigger (proactive context management) - LLMClient interface + Reflector (fixed missing types) All 56 packages pass, 0 failures.
1 parent abc2dfc commit d0fb1c2

92 files changed

Lines changed: 8724 additions & 1183 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.

alerts/cooldown.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package alerts
2+
3+
import (
4+
"sync"
5+
"time"
6+
)
7+
8+
type Alert struct {
9+
ID string `json:"id"`
10+
Type string `json:"type"`
11+
Entity string `json:"entity"`
12+
Message string `json:"message"`
13+
CreatedAt time.Time `json:"created_at"`
14+
Delivered bool `json:"delivered"`
15+
}
16+
17+
type CooldownConfig struct {
18+
Period time.Duration `json:"period"`
19+
MaxPending int `json:"max_pending"`
20+
DrainInterval time.Duration `json:"drain_interval"`
21+
SendDelay time.Duration `json:"send_delay"`
22+
}
23+
24+
func DefaultCooldownConfig() CooldownConfig {
25+
return CooldownConfig{
26+
Period: 24 * time.Hour,
27+
MaxPending: 100,
28+
DrainInterval: 5 * time.Minute,
29+
SendDelay: time.Second,
30+
}
31+
}
32+
33+
type AlertQueue struct {
34+
mu sync.Mutex
35+
pending []*Alert
36+
cooldowns map[string]time.Time // entity+type -> last sent
37+
config CooldownConfig
38+
handler func(*Alert) error
39+
stopCh chan struct{}
40+
}
41+
42+
func NewAlertQueue(config CooldownConfig, handler func(*Alert) error) *AlertQueue {
43+
return &AlertQueue{
44+
pending: make([]*Alert, 0),
45+
cooldowns: make(map[string]time.Time),
46+
config: config,
47+
handler: handler,
48+
stopCh: make(chan struct{}),
49+
}
50+
}
51+
52+
func (q *AlertQueue) Enqueue(alert *Alert) bool {
53+
q.mu.Lock()
54+
defer q.mu.Unlock()
55+
56+
key := alert.Entity + ":" + alert.Type
57+
if last, ok := q.cooldowns[key]; ok && time.Since(last) < q.config.Period {
58+
return false
59+
}
60+
61+
if len(q.pending) >= q.config.MaxPending {
62+
return false
63+
}
64+
65+
alert.CreatedAt = time.Now()
66+
q.pending = append(q.pending, alert)
67+
return true
68+
}
69+
70+
func (q *AlertQueue) Start() {
71+
go q.drainLoop()
72+
}
73+
74+
func (q *AlertQueue) Stop() {
75+
close(q.stopCh)
76+
}
77+
78+
func (q *AlertQueue) drainLoop() {
79+
ticker := time.NewTicker(q.config.DrainInterval)
80+
defer ticker.Stop()
81+
82+
for {
83+
select {
84+
case <-q.stopCh:
85+
return
86+
case <-ticker.C:
87+
q.drain()
88+
}
89+
}
90+
}
91+
92+
func (q *AlertQueue) drain() {
93+
q.mu.Lock()
94+
batch := q.pending
95+
q.pending = make([]*Alert, 0)
96+
q.mu.Unlock()
97+
98+
for _, alert := range batch {
99+
if q.handler != nil {
100+
if err := q.handler(alert); err == nil {
101+
alert.Delivered = true
102+
q.mu.Lock()
103+
q.cooldowns[alert.Entity+":"+alert.Type] = time.Now()
104+
q.mu.Unlock()
105+
}
106+
}
107+
time.Sleep(q.config.SendDelay)
108+
}
109+
}
110+
111+
func (q *AlertQueue) PendingCount() int {
112+
q.mu.Lock()
113+
defer q.mu.Unlock()
114+
return len(q.pending)
115+
}

auth/device_flow.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package auth
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"net/url"
9+
"strings"
10+
"time"
11+
)
12+
13+
// DeviceFlowConfig holds OAuth device flow configuration.
14+
type DeviceFlowConfig struct {
15+
ClientID string
16+
DeviceAuthURL string // e.g. "https://github.com/login/device/code"
17+
TokenURL string // e.g. "https://github.com/login/oauth/access_token"
18+
Scopes []string
19+
PollInterval time.Duration
20+
ExpiresIn time.Duration
21+
}
22+
23+
// DeviceCodeResponse is the initial response from the device auth endpoint.
24+
type DeviceCodeResponse struct {
25+
DeviceCode string `json:"device_code"`
26+
UserCode string `json:"user_code"`
27+
VerificationURI string `json:"verification_uri"`
28+
ExpiresIn int `json:"expires_in"`
29+
Interval int `json:"interval"`
30+
}
31+
32+
// TokenResponse is the final token from the OAuth flow.
33+
type TokenResponse struct {
34+
AccessToken string `json:"access_token"`
35+
TokenType string `json:"token_type"`
36+
Scope string `json:"scope"`
37+
RefreshToken string `json:"refresh_token,omitempty"`
38+
ExpiresIn int `json:"expires_in,omitempty"`
39+
}
40+
41+
// DeviceFlow implements the OAuth 2.0 Device Authorization Grant (RFC 8628).
42+
type DeviceFlow struct {
43+
Config DeviceFlowConfig
44+
HTTPClient *http.Client
45+
}
46+
47+
// NewDeviceFlow creates a device flow handler.
48+
func NewDeviceFlow(cfg DeviceFlowConfig) *DeviceFlow {
49+
if cfg.PollInterval == 0 {
50+
cfg.PollInterval = 5 * time.Second
51+
}
52+
return &DeviceFlow{
53+
Config: cfg,
54+
HTTPClient: &http.Client{Timeout: 10 * time.Second},
55+
}
56+
}
57+
58+
// RequestCode initiates the device flow and returns the user code + verification URL.
59+
func (df *DeviceFlow) RequestCode(ctx context.Context) (*DeviceCodeResponse, error) {
60+
data := url.Values{
61+
"client_id": {df.Config.ClientID},
62+
"scope": {strings.Join(df.Config.Scopes, " ")},
63+
}
64+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, df.Config.DeviceAuthURL, strings.NewReader(data.Encode()))
65+
if err != nil {
66+
return nil, err
67+
}
68+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
69+
req.Header.Set("Accept", "application/json")
70+
71+
resp, err := df.HTTPClient.Do(req)
72+
if err != nil {
73+
return nil, err
74+
}
75+
defer resp.Body.Close()
76+
77+
if resp.StatusCode != http.StatusOK {
78+
return nil, fmt.Errorf("device auth request failed: HTTP %d", resp.StatusCode)
79+
}
80+
81+
var dcr DeviceCodeResponse
82+
if err := json.NewDecoder(resp.Body).Decode(&dcr); err != nil {
83+
return nil, err
84+
}
85+
return &dcr, nil
86+
}
87+
88+
// PollForToken polls the token endpoint until the user authorizes or timeout.
89+
func (df *DeviceFlow) PollForToken(ctx context.Context, deviceCode string) (*TokenResponse, error) {
90+
interval := df.Config.PollInterval
91+
deadline := time.Now().Add(df.Config.ExpiresIn)
92+
if df.Config.ExpiresIn == 0 {
93+
deadline = time.Now().Add(5 * time.Minute)
94+
}
95+
96+
for time.Now().Before(deadline) {
97+
select {
98+
case <-ctx.Done():
99+
return nil, ctx.Err()
100+
case <-time.After(interval):
101+
}
102+
103+
token, err := df.exchangeCode(ctx, deviceCode)
104+
if err != nil {
105+
if strings.Contains(err.Error(), "authorization_pending") || strings.Contains(err.Error(), "slow_down") {
106+
if strings.Contains(err.Error(), "slow_down") {
107+
interval += 5 * time.Second
108+
}
109+
continue
110+
}
111+
return nil, err
112+
}
113+
return token, nil
114+
}
115+
return nil, fmt.Errorf("device flow timed out waiting for authorization")
116+
}
117+
118+
func (df *DeviceFlow) exchangeCode(ctx context.Context, deviceCode string) (*TokenResponse, error) {
119+
data := url.Values{
120+
"client_id": {df.Config.ClientID},
121+
"device_code": {deviceCode},
122+
"grant_type": {"urn:ietf:params:oauth:grant-type:device_code"},
123+
}
124+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, df.Config.TokenURL, strings.NewReader(data.Encode()))
125+
if err != nil {
126+
return nil, err
127+
}
128+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
129+
req.Header.Set("Accept", "application/json")
130+
131+
resp, err := df.HTTPClient.Do(req)
132+
if err != nil {
133+
return nil, err
134+
}
135+
defer resp.Body.Close()
136+
137+
var raw map[string]interface{}
138+
if err := json.NewDecoder(resp.Body).Decode(&raw); err != nil {
139+
return nil, err
140+
}
141+
142+
if errStr, ok := raw["error"].(string); ok {
143+
return nil, fmt.Errorf("%s", errStr)
144+
}
145+
146+
jsonData, _ := json.Marshal(raw)
147+
var token TokenResponse
148+
json.Unmarshal(jsonData, &token)
149+
return &token, nil
150+
}

bus/bus.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package bus
2+
3+
import "context"
4+
5+
// Producer publishes messages to a topic/stream.
6+
type Producer interface {
7+
Publish(ctx context.Context, data []byte) error
8+
Close() error
9+
}
10+
11+
// Consumer receives messages from a topic/stream.
12+
type Consumer interface {
13+
Listen(ctx context.Context, handler func(ctx context.Context, data []byte) error) error
14+
Close() error
15+
}
16+
17+
// ChannelProducer is an in-process Producer backed by a Go channel.
18+
type ChannelProducer struct {
19+
ch chan<- []byte
20+
}
21+
22+
func NewChannelBus(bufSize int) (*ChannelProducer, *ChannelConsumer) {
23+
if bufSize <= 0 {
24+
bufSize = 256
25+
}
26+
ch := make(chan []byte, bufSize)
27+
return &ChannelProducer{ch: ch}, &ChannelConsumer{ch: ch}
28+
}
29+
30+
func (p *ChannelProducer) Publish(_ context.Context, data []byte) error {
31+
cp := make([]byte, len(data))
32+
copy(cp, data)
33+
p.ch <- cp
34+
return nil
35+
}
36+
37+
func (p *ChannelProducer) Close() error {
38+
close(p.ch)
39+
return nil
40+
}
41+
42+
// ChannelConsumer is an in-process Consumer backed by a Go channel.
43+
type ChannelConsumer struct {
44+
ch <-chan []byte
45+
}
46+
47+
func (c *ChannelConsumer) Listen(ctx context.Context, handler func(ctx context.Context, data []byte) error) error {
48+
for {
49+
select {
50+
case <-ctx.Done():
51+
return ctx.Err()
52+
case msg, ok := <-c.ch:
53+
if !ok {
54+
return nil
55+
}
56+
if err := handler(ctx, msg); err != nil {
57+
continue
58+
}
59+
}
60+
}
61+
}
62+
63+
func (c *ChannelConsumer) Close() error {
64+
return nil
65+
}

0 commit comments

Comments
 (0)