@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.
Files changed (42) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/README.md +5 -2
  3. package/automations/README.md +3 -2
  4. package/automations/bundle-index.js +9 -0
  5. package/automations/catalog/github-agents-md-maintainer/manifest.json +96 -0
  6. package/automations/catalog/github-issue-to-pr/manifest.json +107 -0
  7. package/automations/catalog/github-pr-reviewer/manifest.json +1 -0
  8. package/automations/catalog/github-repo-monitor/manifest.json +1 -0
  9. package/automations/catalog/news-digest/manifest.json +76 -0
  10. package/automations/catalog-index.js +13 -7
  11. package/automations/catalog.schema.json +10 -2
  12. package/automations/index.d.ts +18 -1
  13. package/automations/interface.json +3 -1
  14. package/marketplaces/openhands-extensions.json +38 -0
  15. package/package.json +1 -1
  16. package/pyproject.toml +1 -1
  17. package/python/openhands_extensions/_version.py +1 -1
  18. package/skills/github-agents-md-maintainer/.plugin/plugin.json +19 -0
  19. package/skills/github-agents-md-maintainer/README.md +59 -0
  20. package/skills/github-agents-md-maintainer/SKILL.md +271 -0
  21. package/skills/github-agents-md-maintainer/commands/agents-md-setup.md +8 -0
  22. package/skills/github-agents-md-maintainer/references/state-schema.md +113 -0
  23. package/skills/github-agents-md-maintainer/scripts/main.py +1187 -0
  24. package/skills/github-issue-to-pr/.plugin/plugin.json +19 -0
  25. package/skills/github-issue-to-pr/README.md +84 -0
  26. package/skills/github-issue-to-pr/SKILL.md +373 -0
  27. package/skills/github-issue-to-pr/commands/issue-to-pr-setup.md +8 -0
  28. package/skills/github-issue-to-pr/references/state-schema.md +139 -0
  29. package/skills/github-issue-to-pr/scripts/main.py +1305 -0
  30. package/skills/index.js +27 -0
  31. package/skills/news-digest/.plugin/plugin.json +19 -0
  32. package/skills/news-digest/README.md +71 -0
  33. package/skills/news-digest/SKILL.md +284 -0
  34. package/skills/news-digest/commands/news-digest-setup.md +8 -0
  35. package/skills/news-digest/references/state-schema.md +178 -0
  36. package/skills/news-digest/scripts/main.py +1250 -0
  37. package/tests/fixtures/automations/github-repo-monitor.json +77 -0
  38. package/tests/test_automation_setup.py +34 -0
  39. package/tests/test_catalogs.py +3 -1
  40. package/tests/test_github_agents_md_maintainer.py +352 -0
  41. package/tests/test_github_issue_to_pr.py +539 -0
  42. 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.