@openhands/extensions 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +1 -1
- package/README.md +5 -2
- package/automations/README.md +3 -2
- package/automations/bundle-index.js +9 -0
- package/automations/catalog/github-agents-md-maintainer/manifest.json +96 -0
- package/automations/catalog/github-issue-to-pr/manifest.json +107 -0
- package/automations/catalog/github-pr-reviewer/manifest.json +1 -0
- package/automations/catalog/github-repo-monitor/manifest.json +1 -0
- package/automations/catalog/news-digest/manifest.json +76 -0
- package/automations/catalog-index.js +13 -7
- package/automations/catalog.schema.json +10 -2
- package/automations/index.d.ts +18 -1
- package/automations/interface.json +3 -1
- package/marketplaces/openhands-extensions.json +38 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/python/openhands_extensions/_version.py +1 -1
- package/skills/github-agents-md-maintainer/.plugin/plugin.json +19 -0
- package/skills/github-agents-md-maintainer/README.md +59 -0
- package/skills/github-agents-md-maintainer/SKILL.md +271 -0
- package/skills/github-agents-md-maintainer/commands/agents-md-setup.md +8 -0
- package/skills/github-agents-md-maintainer/references/state-schema.md +113 -0
- package/skills/github-agents-md-maintainer/scripts/main.py +1187 -0
- package/skills/github-issue-to-pr/.plugin/plugin.json +19 -0
- package/skills/github-issue-to-pr/README.md +84 -0
- package/skills/github-issue-to-pr/SKILL.md +373 -0
- package/skills/github-issue-to-pr/commands/issue-to-pr-setup.md +8 -0
- package/skills/github-issue-to-pr/references/state-schema.md +139 -0
- package/skills/github-issue-to-pr/scripts/main.py +1305 -0
- package/skills/index.js +27 -0
- package/skills/news-digest/.plugin/plugin.json +19 -0
- package/skills/news-digest/README.md +71 -0
- package/skills/news-digest/SKILL.md +284 -0
- package/skills/news-digest/commands/news-digest-setup.md +8 -0
- package/skills/news-digest/references/state-schema.md +178 -0
- package/skills/news-digest/scripts/main.py +1250 -0
- package/tests/fixtures/automations/github-repo-monitor.json +77 -0
- package/tests/test_automation_setup.py +34 -0
- package/tests/test_catalogs.py +3 -1
- package/tests/test_github_agents_md_maintainer.py +352 -0
- package/tests/test_github_issue_to_pr.py +539 -0
- package/tests/test_news_digest.py +646 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# State Schema
|
|
2
|
+
|
|
3
|
+
The automation maintains a single JSON state document, persisted across runs.
|
|
4
|
+
It is the source of truth for which days have been handled, which stories have
|
|
5
|
+
already been reported, which conversation is still active, and which working
|
|
6
|
+
directory is still on disk.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Storage
|
|
11
|
+
|
|
12
|
+
**Primary (cloud):** the automation service's KV store, key `state`, available
|
|
13
|
+
when `AUTOMATION_KV_TOKEN` is injected.
|
|
14
|
+
|
|
15
|
+
**Fallback (local/dev):**
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
{WORKSPACE_BASE_ROOT}/automation-state/news_digest_{automation_id}.json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`WORKSPACE_BASE_ROOT` is two levels up from `WORKSPACE_BASE`, stripping
|
|
22
|
+
`automation-runs/{run_id}`; `automation_id` comes from `AUTOMATION_EVENT_PAYLOAD`.
|
|
23
|
+
|
|
24
|
+
The KV store caps a value at 64 KB. Everything below is bounded so the document
|
|
25
|
+
stays comfortably inside it however long the automation runs - which matters
|
|
26
|
+
here more than for a weekly automation, because a daily key writes a record a
|
|
27
|
+
day forever.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Top-Level Schema
|
|
32
|
+
|
|
33
|
+
```jsonc
|
|
34
|
+
{
|
|
35
|
+
"version": 1,
|
|
36
|
+
"updated_at": 1717200000.0,
|
|
37
|
+
|
|
38
|
+
// Set on every run that actually read the feeds. A run that stopped at the
|
|
39
|
+
// "today is done" check does not touch it.
|
|
40
|
+
"last_checked": 1717200000.0,
|
|
41
|
+
|
|
42
|
+
// What each selection stage left, from the same run. A digest that came out
|
|
43
|
+
// empty is explained by whichever of these is zero.
|
|
44
|
+
"last_funnel": { "fetched": 60, "unseen": 12, "fresh": 9 },
|
|
45
|
+
|
|
46
|
+
// Up to ten feed failures from the last run that read them, each truncated
|
|
47
|
+
// to 200 characters. Diagnostic only; nothing branches on it.
|
|
48
|
+
"last_feed_errors": ["https://example.test/feed: HTTP Error 500: Internal Server Error"],
|
|
49
|
+
|
|
50
|
+
// Story fingerprints already reported, oldest first, capped at 1000 - about
|
|
51
|
+
// two per story, so roughly five hundred stories. See "Fingerprints" below.
|
|
52
|
+
"seen": ["3f2a9c1b7d4e8a05", "…"],
|
|
53
|
+
|
|
54
|
+
// The most recent digest, in one slot rather than one per day. Keeping every
|
|
55
|
+
// digest here would overrun the value limit inside a fortnight.
|
|
56
|
+
"last_digest": {
|
|
57
|
+
"period": "2026-08-20",
|
|
58
|
+
"conversation_url": "https://app.all-hands.dev/conversations/…",
|
|
59
|
+
"written_at": 1717200000.0,
|
|
60
|
+
"text": "…first 4000 characters…"
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// One record per day, keyed `news:{YYYY-MM-DD}`, pruned to the last 14.
|
|
64
|
+
"tasks": {}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Task Record
|
|
71
|
+
|
|
72
|
+
```jsonc
|
|
73
|
+
"news:2026-08-20": {
|
|
74
|
+
"period": "2026-08-20",
|
|
75
|
+
"status": "active",
|
|
76
|
+
"conversation_id": "6ceaa97a-…",
|
|
77
|
+
"conversation_url": "https://…/conversations/6ceaa97a-…", // set at finalize
|
|
78
|
+
"workspace_dir": "/workspace/news-digest/2026-08-20", // removed with the directory
|
|
79
|
+
"item_keys": ["3f2a9c1b7d4e8a05", "…"], // dropped at finalize
|
|
80
|
+
"item_count": 12,
|
|
81
|
+
"last_activity": 1717200000.0,
|
|
82
|
+
"completed_at": 1717203600.0, // set once the conversation stopped
|
|
83
|
+
"expired_after": 7213.4 // only on `expired`
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Statuses
|
|
88
|
+
|
|
89
|
+
| Status | Meaning | Stories remembered? |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `starting` | The day is claimed; the conversation does not exist yet | no |
|
|
92
|
+
| `active` | The conversation is running | no |
|
|
93
|
+
| `completed` | A digest was produced and printed | **yes** |
|
|
94
|
+
| `empty` | The conversation stopped without writing anything | no |
|
|
95
|
+
| `failed` | The conversation ended `error` or `stuck` | no |
|
|
96
|
+
| `expired` | Never reached a terminal status within `MAX_ACTIVE_AGE` (2h) | no |
|
|
97
|
+
|
|
98
|
+
Only `completed` widens `seen`. That is the whole recovery story: because the
|
|
99
|
+
lookback window (48h) is wider than the schedule, every other outcome leaves its
|
|
100
|
+
stories to be picked up by the next run rather than losing them.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Lifecycle
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
today already in tasks ──> stop (no feeds fetched)
|
|
108
|
+
│
|
|
109
|
+
run starts ──> fetch feeds ──> select stories
|
|
110
|
+
│
|
|
111
|
+
nothing new ───┴──> record last_checked, leave the day UNCLAIMED
|
|
112
|
+
│
|
|
113
|
+
stories ───────┴──> write `starting` and PERSIST
|
|
114
|
+
│
|
|
115
|
+
create workspace + conversation
|
|
116
|
+
│
|
|
117
|
+
success ──> `active` failure ──> drop the claim,
|
|
118
|
+
│ remove the workspace
|
|
119
|
+
(a later run polls) │
|
|
120
|
+
▼
|
|
121
|
+
terminal ──> read digest.md, else the final message
|
|
122
|
+
│
|
|
123
|
+
digest ─────────┴──> `completed`, widen `seen`,
|
|
124
|
+
print it, remove the workspace
|
|
125
|
+
no digest ──────────> `empty`
|
|
126
|
+
error/stuck ────────> `failed`
|
|
127
|
+
never terminal ─────> `expired` after 2h
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Why the day is claimed before the slow work
|
|
131
|
+
|
|
132
|
+
State is otherwise written only at the end of a run. An overlapping run - a
|
|
133
|
+
retry, a manual dispatch landing on a cron tick - would read no record for today
|
|
134
|
+
and start a second conversation over the same stories. Writing `starting`
|
|
135
|
+
first closes that window. A claim left behind by a run that died before creating
|
|
136
|
+
its conversation is released after `STALLED_CLAIM_SECONDS` (15 minutes), which
|
|
137
|
+
is far longer than fetching a feed list.
|
|
138
|
+
|
|
139
|
+
### Why "nothing new" does not claim the day
|
|
140
|
+
|
|
141
|
+
A feed that has not published yet looks exactly like a feed with nothing to
|
|
142
|
+
say. Claiming the day on an empty result would mean the first run of the morning
|
|
143
|
+
silently cancels the rest of the day. Instead the day stays open: later runs
|
|
144
|
+
re-check, at the cost of one HTTP request per feed and no tokens at all.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Fingerprints
|
|
149
|
+
|
|
150
|
+
Each story contributes up to two 16-character SHA-256 prefixes to `seen`:
|
|
151
|
+
|
|
152
|
+
1. the feed's own identifier (`guid` in RSS, `id` in Atom), and
|
|
153
|
+
2. its link, with the scheme and host lowercased, the fragment and any trailing
|
|
154
|
+
slash removed, and `utm_*` parameters stripped.
|
|
155
|
+
|
|
156
|
+
A story is treated as already covered if **either** matches. Both are needed:
|
|
157
|
+
a feed whose links carry a per-fetch campaign tag is only recognisable by its
|
|
158
|
+
identifier, and two publishers syndicating the same article agree on nothing
|
|
159
|
+
*but* the link.
|
|
160
|
+
|
|
161
|
+
Hashes rather than the values themselves, because identifiers range from a short
|
|
162
|
+
guid to a long URL and the document has a size ceiling. They are one-way, so
|
|
163
|
+
`seen` cannot be read back to reconstruct what was covered - `last_digest` and
|
|
164
|
+
the conversations are where that is kept.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Pruning
|
|
169
|
+
|
|
170
|
+
| Field | Cap | Rule |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `seen` | 1000 fingerprints | Oldest evicted first |
|
|
173
|
+
| `tasks` | 14 records | Oldest evicted first, but never one that is `starting`/`active` or still holds a `workspace_dir` |
|
|
174
|
+
| `last_digest.text` | 4000 characters | Truncated |
|
|
175
|
+
| `last_feed_errors` | 10 entries, 200 characters each | Truncated |
|
|
176
|
+
|
|
177
|
+
A task still holding a `workspace_dir` is never pruned, because that record is
|
|
178
|
+
the only thing that knows a directory is waiting to be removed.
|