@openhands/extensions 0.1.0 → 0.2.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/.agents/skills/custom-codereview-guide.md +25 -0
- package/.github/pull_request_template.md +38 -0
- package/.github/release.yml +14 -0
- package/.github/workflows/check-extensions.yml +72 -0
- package/.github/workflows/npm-publish.yml +89 -0
- package/.github/workflows/pr.yml +30 -0
- package/.github/workflows/release.yml +24 -0
- package/.github/workflows/tests.yml +25 -0
- package/.github/workflows/vulnerability-scan.yml +87 -0
- package/.release-please-manifest.json +3 -0
- package/AGENTS.md +132 -0
- package/README.md +10 -0
- package/analysis_results.md +162 -0
- package/marketplaces/large-codebase.json +66 -0
- package/marketplaces/openhands-extensions.json +682 -0
- package/package.json +4 -10
- package/plugins/README.md +30 -0
- package/plugins/city-weather/.plugin/plugin.json +13 -0
- package/plugins/city-weather/README.md +145 -0
- package/plugins/city-weather/commands/now.md +56 -0
- package/plugins/cobol-modernization/.plugin/plugin.json +19 -0
- package/plugins/cobol-modernization/README.md +201 -0
- package/plugins/cobol-modernization/references/troubleshooting.md +18 -0
- package/plugins/cobol-modernization/skills/build-setup/SKILL.md +78 -0
- package/plugins/cobol-modernization/skills/build-setup/scripts/install-gnucobol.sh +32 -0
- package/plugins/cobol-modernization/skills/cobol-modernization-overview/SKILL.md +113 -0
- package/plugins/cobol-modernization/skills/mainfraime-removal/SKILL.md +62 -0
- package/plugins/cobol-modernization/skills/mainfraime-removal/references/cics-transformation-examples.md +45 -0
- package/plugins/cobol-modernization/skills/mainframe-planning/SKILL.md +78 -0
- package/plugins/cobol-modernization/skills/to-java-migration/SKILL.md +59 -0
- package/plugins/cobol-modernization/skills/to-java-migration/references/cobol-to-java-example.md +58 -0
- package/plugins/cobol-modernization/skills/to-java-migration/references/datatype-mappings.md +19 -0
- package/plugins/issue-duplicate-checker/.plugin/plugin.json +13 -0
- package/plugins/issue-duplicate-checker/README.md +51 -0
- package/plugins/issue-duplicate-checker/action.yml +349 -0
- package/plugins/issue-duplicate-checker/scripts/auto_close_duplicate_issues.py +569 -0
- package/plugins/issue-duplicate-checker/scripts/issue_duplicate_check_openhands.py +681 -0
- package/plugins/issue-duplicate-checker/scripts/post_duplicate_notice.js +220 -0
- package/plugins/issue-duplicate-checker/scripts/remove_duplicate_candidate_label.js +27 -0
- package/plugins/magic-test/.plugin/plugin.json +13 -0
- package/plugins/magic-test/skills/magic-word/SKILL.md +33 -0
- package/plugins/migration-scoring/.plugin/plugin.json +19 -0
- package/plugins/migration-scoring/README.md +244 -0
- package/plugins/migration-scoring/skills/migration-mapping/SKILL.md +72 -0
- package/plugins/migration-scoring/skills/migration-report/SKILL.md +118 -0
- package/plugins/migration-scoring/skills/migration-scoring-overview/SKILL.md +126 -0
- package/plugins/migration-scoring/skills/score-quality/SKILL.md +54 -0
- package/plugins/migration-scoring/skills/score-quality/references/scoring-criteria.md +30 -0
- package/plugins/migration-scoring/skills/score-style/SKILL.md +106 -0
- package/plugins/onboarding/.plugin/plugin.json +20 -0
- package/plugins/onboarding/README.md +30 -0
- package/plugins/onboarding/references/criteria.md +144 -0
- package/plugins/onboarding/skills/agent-readiness-report/README.md +23 -0
- package/plugins/onboarding/skills/agent-readiness-report/SKILL.md +122 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_agent_instructions.sh +88 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_build_env.sh +114 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_feedback_loops.sh +133 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_policy.sh +113 -0
- package/plugins/onboarding/skills/agent-readiness-report/scripts/scan_workflows.sh +127 -0
- package/plugins/onboarding/skills/improve-agent-readiness/README.md +19 -0
- package/plugins/onboarding/skills/improve-agent-readiness/SKILL.md +167 -0
- package/plugins/onboarding/skills/setup-agents-md/README.md +15 -0
- package/plugins/onboarding/skills/setup-agents-md/SKILL.md +150 -0
- package/plugins/onboarding/skills/setup-openhands/README.md +20 -0
- package/plugins/onboarding/skills/setup-openhands/SKILL.md +56 -0
- package/plugins/onboarding/skills/setup-pr-review/README.md +23 -0
- package/plugins/onboarding/skills/setup-pr-review/SKILL.md +72 -0
- package/plugins/openhands/.plugin/plugin.json +13 -0
- package/plugins/openhands/README.md +52 -0
- package/plugins/openhands/SKILL.md +61 -0
- package/plugins/openhands/commands/create.md +55 -0
- package/plugins/openhands/commands/openhands-cloud.md +8 -0
- package/plugins/openhands/scripts/run.sh +69 -0
- package/plugins/pr-review/.plugin/plugin.json +13 -0
- package/plugins/pr-review/README.md +393 -0
- package/plugins/pr-review/action.yml +298 -0
- package/plugins/pr-review/scripts/agent_script.py +1282 -0
- package/plugins/pr-review/scripts/evaluate_review.py +655 -0
- package/plugins/pr-review/scripts/prompt.py +260 -0
- package/plugins/pr-review/workflows/pr-review-by-openhands.yml +51 -0
- package/plugins/pr-review/workflows/pr-review-evaluation.yml +85 -0
- package/plugins/qa-changes/.plugin/plugin.json +11 -0
- package/plugins/qa-changes/README.md +185 -0
- package/plugins/qa-changes/action.yml +181 -0
- package/plugins/qa-changes/scripts/agent_script.py +406 -0
- package/plugins/qa-changes/scripts/evaluate_qa_changes.py +385 -0
- package/plugins/qa-changes/scripts/prompt.py +174 -0
- package/plugins/qa-changes/workflows/qa-changes-by-openhands.yml +50 -0
- package/plugins/qa-changes/workflows/qa-changes-evaluation.yml +85 -0
- package/plugins/release-notes/.plugin/plugin.json +19 -0
- package/plugins/release-notes/README.md +283 -0
- package/plugins/release-notes/SKILL.md +83 -0
- package/plugins/release-notes/action.yml +117 -0
- package/plugins/release-notes/commands/release-notes.md +8 -0
- package/plugins/release-notes/scripts/agent_script.py +292 -0
- package/plugins/release-notes/scripts/generate_release_notes.py +733 -0
- package/plugins/release-notes/scripts/prompt.py +90 -0
- package/plugins/release-notes/scripts/validate_release_notes.py +328 -0
- package/plugins/release-notes/workflows/release-notes.yml +76 -0
- package/plugins/vulnerability-remediation/.plugin/plugin.json +19 -0
- package/plugins/vulnerability-remediation/README.md +217 -0
- package/plugins/vulnerability-remediation/action.yml +187 -0
- package/plugins/vulnerability-remediation/scripts/scan_and_remediate.py +561 -0
- package/plugins/vulnerability-remediation/workflows/vulnerability-scan.yml +87 -0
- package/pyproject.toml +12 -0
- package/release-please-config.json +16 -0
- package/scripts/sync_extensions.py +494 -0
- package/scripts/sync_openhands_sdk_skill.py +264 -0
- package/skills/README.md +159 -0
- package/skills/add-javadoc/.plugin/plugin.json +18 -0
- package/skills/add-javadoc/README.md +40 -0
- package/skills/add-javadoc/SKILL.md +35 -0
- package/skills/add-javadoc/references/example.md +32 -0
- package/skills/add-skill/.plugin/plugin.json +18 -0
- package/skills/add-skill/README.md +67 -0
- package/skills/add-skill/SKILL.md +47 -0
- package/skills/add-skill/scripts/fetch_skill.py +259 -0
- package/skills/agent-creator/.plugin/plugin.json +20 -0
- package/skills/agent-creator/README.md +104 -0
- package/skills/agent-creator/SKILL.md +190 -0
- package/skills/agent-creator/commands/agent-creator.md +8 -0
- package/skills/agent-creator/references/fallback.md +117 -0
- package/skills/agent-memory/.plugin/plugin.json +18 -0
- package/skills/agent-memory/README.md +35 -0
- package/skills/agent-memory/SKILL.md +30 -0
- package/skills/agent-memory/commands/remember.md +8 -0
- package/skills/agent-sdk-builder/.plugin/plugin.json +18 -0
- package/skills/agent-sdk-builder/README.md +40 -0
- package/skills/agent-sdk-builder/SKILL.md +37 -0
- package/skills/agent-sdk-builder/commands/agent-builder.md +8 -0
- package/skills/azure-devops/.plugin/plugin.json +18 -0
- package/skills/azure-devops/README.md +55 -0
- package/skills/azure-devops/SKILL.md +50 -0
- package/skills/bitbucket/.plugin/plugin.json +17 -0
- package/skills/bitbucket/README.md +50 -0
- package/skills/bitbucket/SKILL.md +45 -0
- package/skills/code-review/.plugin/plugin.json +19 -0
- package/skills/code-review/README.md +18 -0
- package/skills/code-review/SKILL.md +208 -0
- package/skills/code-review/commands/codereview-roasted.md +8 -0
- package/skills/code-review/commands/codereview.md +8 -0
- package/skills/code-review/references/risk-evaluation.md +41 -0
- package/skills/code-review/references/supply-chain-security.md +31 -0
- package/skills/code-simplifier/.plugin/plugin.json +21 -0
- package/skills/code-simplifier/README.md +30 -0
- package/skills/code-simplifier/SKILL.md +91 -0
- package/skills/code-simplifier/commands/simplify.md +8 -0
- package/skills/code-simplifier/references/code-quality-review.md +86 -0
- package/skills/code-simplifier/references/code-reuse-review.md +63 -0
- package/skills/code-simplifier/references/efficiency-review.md +81 -0
- package/skills/datadog/.plugin/plugin.json +19 -0
- package/skills/datadog/README.md +100 -0
- package/skills/datadog/SKILL.md +95 -0
- package/skills/deno/.plugin/plugin.json +18 -0
- package/skills/deno/README.md +5 -0
- package/skills/deno/SKILL.md +99 -0
- package/skills/deno/references/README.md +6 -0
- package/skills/discord/.plugin/plugin.json +18 -0
- package/skills/discord/README.md +31 -0
- package/skills/discord/SKILL.md +109 -0
- package/skills/discord/__init__.py +0 -0
- package/skills/discord/references/REFERENCE.md +78 -0
- package/skills/discord/scripts/__init__.py +0 -0
- package/skills/discord/scripts/_http.py +127 -0
- package/skills/discord/scripts/post_webhook.py +106 -0
- package/skills/discord/scripts/send_message.py +102 -0
- package/skills/docker/.plugin/plugin.json +17 -0
- package/skills/docker/README.md +34 -0
- package/skills/docker/SKILL.md +29 -0
- package/skills/evidence-based-citations/.plugin/plugin.json +20 -0
- package/skills/evidence-based-citations/README.md +31 -0
- package/skills/evidence-based-citations/SKILL.md +59 -0
- package/skills/flarglebargle/.plugin/plugin.json +16 -0
- package/skills/flarglebargle/README.md +14 -0
- package/skills/flarglebargle/SKILL.md +9 -0
- package/skills/frontend-design/.plugin/plugin.json +21 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/README.md +42 -0
- package/skills/frontend-design/SKILL.md +42 -0
- package/skills/github/.plugin/plugin.json +19 -0
- package/skills/github/README.md +42 -0
- package/skills/github/SKILL.md +106 -0
- package/skills/github-pr-review/.plugin/plugin.json +18 -0
- package/skills/github-pr-review/README.md +145 -0
- package/skills/github-pr-review/SKILL.md +148 -0
- package/skills/github-pr-review/commands/github-pr-review.md +8 -0
- package/skills/github-pr-reviewer/.plugin/plugin.json +20 -0
- package/skills/github-pr-reviewer/README.md +34 -0
- package/skills/github-pr-reviewer/SKILL.md +89 -0
- package/skills/github-pr-reviewer/commands/pr-reviewer:setup.md +8 -0
- package/skills/github-repo-monitor/.plugin/plugin.json +22 -0
- package/skills/github-repo-monitor/README.md +70 -0
- package/skills/github-repo-monitor/SKILL.md +316 -0
- package/skills/github-repo-monitor/commands/github-monitor:poll.md +8 -0
- package/skills/github-repo-monitor/references/github-api.md +241 -0
- package/skills/github-repo-monitor/references/state-schema.md +160 -0
- package/skills/github-repo-monitor/scripts/main.py +915 -0
- package/skills/github-repo-monitor/tests/test_main.py +400 -0
- package/skills/gitlab/.plugin/plugin.json +17 -0
- package/skills/gitlab/README.md +37 -0
- package/skills/gitlab/SKILL.md +32 -0
- package/skills/incident-retrospective/.plugin/plugin.json +21 -0
- package/skills/incident-retrospective/README.md +34 -0
- package/skills/incident-retrospective/SKILL.md +98 -0
- package/skills/incident-retrospective/commands/incident-retro:setup.md +8 -0
- package/skills/iterate/.plugin/plugin.json +13 -0
- package/skills/iterate/README.md +25 -0
- package/skills/iterate/SKILL.md +399 -0
- package/skills/iterate/commands/babysit.md +8 -0
- package/skills/iterate/commands/iterate.md +8 -0
- package/skills/iterate/commands/verify.md +8 -0
- package/skills/iterate/references/heuristics.md +58 -0
- package/skills/iterate/references/verification.md +96 -0
- package/skills/jupyter/.plugin/plugin.json +18 -0
- package/skills/jupyter/README.md +55 -0
- package/skills/jupyter/SKILL.md +50 -0
- package/skills/kubernetes/.plugin/plugin.json +18 -0
- package/skills/kubernetes/README.md +53 -0
- package/skills/kubernetes/SKILL.md +48 -0
- package/skills/learn-from-code-review/.plugin/plugin.json +19 -0
- package/skills/learn-from-code-review/README.md +64 -0
- package/skills/learn-from-code-review/SKILL.md +186 -0
- package/skills/learn-from-code-review/commands/learn-from-reviews.md +8 -0
- package/skills/linear/.plugin/plugin.json +19 -0
- package/skills/linear/README.md +58 -0
- package/skills/linear/SKILL.md +213 -0
- package/skills/linear-triage/.plugin/plugin.json +21 -0
- package/skills/linear-triage/README.md +34 -0
- package/skills/linear-triage/SKILL.md +91 -0
- package/skills/linear-triage/commands/linear-triage:setup.md +8 -0
- package/skills/notion/.plugin/plugin.json +17 -0
- package/skills/notion/README.md +114 -0
- package/skills/notion/SKILL.md +109 -0
- package/skills/npm/.plugin/plugin.json +17 -0
- package/skills/npm/README.md +14 -0
- package/skills/npm/SKILL.md +9 -0
- package/skills/openhands-api/.plugin/plugin.json +22 -0
- package/skills/openhands-api/README.md +48 -0
- package/skills/openhands-api/SKILL.md +399 -0
- package/skills/openhands-api/references/README.md +33 -0
- package/skills/openhands-api/references/TROUBLESHOOTING.md +81 -0
- package/skills/openhands-api/references/example_prompt.md +12 -0
- package/skills/openhands-api/scripts/openhands_api.py +606 -0
- package/skills/openhands-api/scripts/openhands_api.ts +252 -0
- package/skills/openhands-automation/.plugin/plugin.json +19 -0
- package/skills/openhands-automation/README.md +89 -0
- package/skills/openhands-automation/SKILL.md +875 -0
- package/skills/openhands-automation/commands/automation:create.md +8 -0
- package/skills/openhands-automation/references/ab-testing.md +185 -0
- package/skills/openhands-automation/references/custom-automation.md +644 -0
- package/skills/openhands-sdk/.plugin/plugin.json +20 -0
- package/skills/openhands-sdk/README.md +22 -0
- package/skills/openhands-sdk/SKILL.md +229 -0
- package/skills/openhands-sdk/commands/sdk.md +8 -0
- package/skills/pdflatex/.plugin/plugin.json +18 -0
- package/skills/pdflatex/README.md +39 -0
- package/skills/pdflatex/SKILL.md +34 -0
- package/skills/prd/.plugin/plugin.json +19 -0
- package/skills/prd/README.md +28 -0
- package/skills/prd/SKILL.md +237 -0
- package/skills/prd/commands/prd.md +8 -0
- package/skills/qa-changes/README.md +18 -0
- package/skills/qa-changes/SKILL.md +229 -0
- package/skills/qa-changes/commands/qa-changes.md +8 -0
- package/skills/release-notes/README.md +24 -0
- package/skills/release-notes/SKILL.md +19 -0
- package/skills/release-notes/commands/release-notes.md +8 -0
- package/skills/research-brief/.plugin/plugin.json +20 -0
- package/skills/research-brief/README.md +34 -0
- package/skills/research-brief/SKILL.md +99 -0
- package/skills/research-brief/commands/research-brief:setup.md +8 -0
- package/skills/security/.plugin/plugin.json +18 -0
- package/skills/security/README.md +38 -0
- package/skills/security/SKILL.md +33 -0
- package/skills/skill-creator/.plugin/plugin.json +17 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/README.md +182 -0
- package/skills/skill-creator/SKILL.md +545 -0
- package/skills/skill-creator/references/output-patterns.md +82 -0
- package/skills/skill-creator/references/workflows.md +28 -0
- package/skills/skill-creator/scripts/init_skill.py +303 -0
- package/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/skills/slack-channel-monitor/.plugin/plugin.json +21 -0
- package/skills/slack-channel-monitor/README.md +91 -0
- package/skills/slack-channel-monitor/SKILL.md +276 -0
- package/skills/slack-channel-monitor/commands/slack-monitor:poll.md +8 -0
- package/skills/slack-channel-monitor/references/slack-api.md +207 -0
- package/skills/slack-channel-monitor/references/state-schema.md +180 -0
- package/skills/slack-channel-monitor/scripts/main.py +962 -0
- package/skills/slack-standup-digest/.plugin/plugin.json +21 -0
- package/skills/slack-standup-digest/README.md +34 -0
- package/skills/slack-standup-digest/SKILL.md +92 -0
- package/skills/slack-standup-digest/commands/standup-digest:setup.md +8 -0
- package/skills/spark-version-upgrade/.plugin/plugin.json +20 -0
- package/skills/spark-version-upgrade/README.md +54 -0
- package/skills/spark-version-upgrade/SKILL.md +233 -0
- package/skills/ssh/.plugin/plugin.json +18 -0
- package/skills/ssh/README.md +140 -0
- package/skills/ssh/SKILL.md +135 -0
- package/skills/swift-linux/.plugin/plugin.json +17 -0
- package/skills/swift-linux/README.md +86 -0
- package/skills/swift-linux/SKILL.md +81 -0
- package/skills/theme-factory/.plugin/plugin.json +19 -0
- package/skills/theme-factory/LICENSE.txt +202 -0
- package/skills/theme-factory/README.md +58 -0
- package/skills/theme-factory/SKILL.md +59 -0
- package/skills/theme-factory/theme-showcase.pdf +0 -0
- package/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/skills/theme-factory/themes/desert-rose.md +19 -0
- package/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/skills/theme-factory/themes/golden-hour.md +19 -0
- package/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/skills/uv/.plugin/plugin.json +18 -0
- package/skills/uv/README.md +5 -0
- package/skills/uv/SKILL.md +95 -0
- package/skills/uv/references/README.md +5 -0
- package/skills/vercel/.plugin/plugin.json +18 -0
- package/skills/vercel/README.md +108 -0
- package/skills/vercel/SKILL.md +103 -0
- package/tests/test_add_skill_installs_to_agents_dir.py +42 -0
- package/tests/test_catalogs.py +109 -0
- package/tests/test_code_review_risk_evaluation.py +94 -0
- package/tests/test_issue_duplicate_checker.py +240 -0
- package/tests/test_openhands_api_python.py +152 -0
- package/tests/test_plugin_manifest.py +83 -0
- package/tests/test_pr_review_diff_payload.py +202 -0
- package/tests/test_pr_review_feedback.py +263 -0
- package/tests/test_pr_review_prompt.py +152 -0
- package/tests/test_pr_review_review_context.py +253 -0
- package/tests/test_qa_changes.py +232 -0
- package/tests/test_qa_changes_evaluation.py +259 -0
- package/tests/test_release_notes_generator.py +990 -0
- package/tests/test_sdk_loading.py +150 -0
- package/tests/test_skill_plugin_loading.py +149 -0
- package/tests/test_skills_have_readme.py +66 -0
- package/tests/test_sync_extensions.py +292 -0
- package/tests/test_workflow_sync.py +46 -0
- package/utils/analysis/README.md +7 -0
- package/utils/analysis/laminar_signals/README.md +211 -0
- package/utils/analysis/laminar_signals/analyze.py +780 -0
- package/utils/analysis/laminar_signals/templates/default.j2 +49 -0
- package/utils/analysis/laminar_signals/templates/pr_review.j2 +61 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slack-channel-monitor
|
|
3
|
+
description: >
|
|
4
|
+
This skill should be used when the user asks to "monitor a Slack channel",
|
|
5
|
+
"watch Slack for messages", "create a Slack bot that responds to mentions",
|
|
6
|
+
"set up an OpenHands Slack integration", "trigger OpenHands from Slack",
|
|
7
|
+
"respond to @openhands in Slack", or "poll Slack channels for a trigger
|
|
8
|
+
phrase". Guides the user through creating a cron automation that watches up
|
|
9
|
+
to 10 Slack channels and starts an OpenHands conversation whenever a
|
|
10
|
+
configurable trigger phrase is detected.
|
|
11
|
+
triggers:
|
|
12
|
+
- /slack-monitor:poll
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Slack Channel Monitor
|
|
16
|
+
|
|
17
|
+
Create a cron automation that polls up to 10 Slack channels every minute.
|
|
18
|
+
When a message containing the **trigger phrase** (default: `@openhands`) is
|
|
19
|
+
detected it:
|
|
20
|
+
|
|
21
|
+
1. Adds a 👀 reaction to the triggering message.
|
|
22
|
+
2. Opens an OpenHands conversation with the message and recent channel context.
|
|
23
|
+
3. Posts a reply in the Slack thread with a link to the conversation.
|
|
24
|
+
|
|
25
|
+
On every subsequent run:
|
|
26
|
+
- Replies in the thread are forwarded to the running conversation.
|
|
27
|
+
- When the conversation finishes (or errors), the agent's final response is
|
|
28
|
+
posted back to the Slack thread.
|
|
29
|
+
|
|
30
|
+
> **Local mode only.** This automation targets the local OpenHands setup
|
|
31
|
+
> (`dev:automation` stack). A cloud/webhook-based variant is out of scope here.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Prerequisites
|
|
36
|
+
|
|
37
|
+
### Required secrets
|
|
38
|
+
|
|
39
|
+
Verify that at least one of the following secrets is set in
|
|
40
|
+
**OpenHands Settings → Secrets** before proceeding:
|
|
41
|
+
|
|
42
|
+
| Secret name | Token type | Minimum scopes |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `SLACK_BOT_TOKEN` | Bot (`xoxb-…`) | `channels:history`, `channels:read`, `reactions:write`, `chat:write` |
|
|
45
|
+
| `SLACK_USER_TOKEN` | User (`xoxp-…`) | Same as bot, plus `search:read` for multi-channel efficiency |
|
|
46
|
+
|
|
47
|
+
Check with:
|
|
48
|
+
```bash
|
|
49
|
+
# For bot token:
|
|
50
|
+
curl -s https://slack.com/api/auth.test -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
|
|
51
|
+
| python3 -c "import json,sys; d=json.load(sys.stdin); print('ok' if d.get('ok') else d.get('error'))"
|
|
52
|
+
|
|
53
|
+
# For user token:
|
|
54
|
+
curl -s https://slack.com/api/auth.test -H "Authorization: Bearer $SLACK_USER_TOKEN" \
|
|
55
|
+
| python3 -c "import json,sys; d=json.load(sys.stdin); print('ok' if d.get('ok') else d.get('error'))"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If neither token is present, inform the user and stop - the automation cannot
|
|
59
|
+
function without Slack credentials.
|
|
60
|
+
|
|
61
|
+
### Optional secret
|
|
62
|
+
|
|
63
|
+
| Secret name | Default | Purpose |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `OPENHANDS_URL` | `http://localhost:8000` | Base URL used to build conversation links posted in Slack |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Setup Workflow
|
|
70
|
+
|
|
71
|
+
Follow these steps in order.
|
|
72
|
+
|
|
73
|
+
### Step 1 - Collect channels
|
|
74
|
+
|
|
75
|
+
Ask the user: *"Which Slack channels should be monitored? You can provide
|
|
76
|
+
channel names (e.g. `#general`) or IDs (e.g. `C0123456789`)."*
|
|
77
|
+
|
|
78
|
+
**If the user provides channel names**, resolve them to IDs:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
SLACK_TOKEN="${SLACK_BOT_TOKEN:-$SLACK_USER_TOKEN}"
|
|
82
|
+
curl -s "https://slack.com/api/conversations.list?types=public_channel,private_channel&limit=200&exclude_archived=true" \
|
|
83
|
+
-H "Authorization: Bearer $SLACK_TOKEN" \
|
|
84
|
+
| python3 -c "
|
|
85
|
+
import json, sys
|
|
86
|
+
data = json.load(sys.stdin)
|
|
87
|
+
if not data.get('ok'):
|
|
88
|
+
print('ERROR:', data.get('error'))
|
|
89
|
+
exit(1)
|
|
90
|
+
names = set(n.lstrip('#') for n in ['CHANNEL_NAMES_HERE'.split(',')])
|
|
91
|
+
for ch in data.get('channels', []):
|
|
92
|
+
if ch['name'] in names:
|
|
93
|
+
print(f\"{ch['name']} → {ch['id']}\")
|
|
94
|
+
"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Replace `CHANNEL_NAMES_HERE` with the comma-separated names the user provided.
|
|
98
|
+
|
|
99
|
+
**If `conversations.list` returns `missing_scope` or `not_authed`:**
|
|
100
|
+
Inform the user: *"The token doesn't have permission to list channels. Please
|
|
101
|
+
provide the channel IDs directly (right-click a channel in Slack → Copy link -
|
|
102
|
+
the last path segment starting with `C` is the ID)."*
|
|
103
|
+
|
|
104
|
+
**If the bot token lacks `channels:read`** for private channels, the user can
|
|
105
|
+
either invite the bot first (`/invite @botname`) or switch to a user token.
|
|
106
|
+
|
|
107
|
+
Collect up to 10 channel IDs. Record them as a Python list literal, e.g.:
|
|
108
|
+
```python
|
|
109
|
+
["C0123456789", "C9876543210"]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Step 2 - Collect trigger phrase
|
|
113
|
+
|
|
114
|
+
Ask the user: *"What trigger phrase should OpenHands respond to?
|
|
115
|
+
(Press Enter to use the default: `@openhands`)"*
|
|
116
|
+
|
|
117
|
+
Accepted values: any non-empty string unlikely to appear accidentally, e.g.
|
|
118
|
+
`@openhands`, `jazz hands`, `take-me-to-funky-town`.
|
|
119
|
+
|
|
120
|
+
### Step 3 - Generate the automation script
|
|
121
|
+
|
|
122
|
+
Read `scripts/main.py` from this skill's directory. Apply exactly three
|
|
123
|
+
constant substitutions near the top of the file:
|
|
124
|
+
|
|
125
|
+
| Placeholder | Replace with |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `TRIGGER_PHRASE = "@openhands"` | `TRIGGER_PHRASE = "{user_phrase}"` |
|
|
128
|
+
| `CHANNEL_IDS: list[str] = []` | `CHANNEL_IDS: list[str] = {channel_id_list}` |
|
|
129
|
+
| `DEFAULT_OPENHANDS_URL = "http://localhost:8000"` | `DEFAULT_OPENHANDS_URL = "{url}"` (keep default if user has no preference) |
|
|
130
|
+
|
|
131
|
+
Write the customised script to a temporary directory:
|
|
132
|
+
```bash
|
|
133
|
+
mkdir -p /tmp/slack-monitor-build
|
|
134
|
+
# (write the customised main.py to /tmp/slack-monitor-build/main.py)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Validate syntax before packaging:
|
|
138
|
+
```bash
|
|
139
|
+
python3 -m py_compile /tmp/slack-monitor-build/main.py && echo "Syntax OK"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Fix any syntax errors before proceeding.
|
|
143
|
+
|
|
144
|
+
### Step 4 - Package and upload
|
|
145
|
+
|
|
146
|
+
Determine the Automation backend URL and auth from the `<RUNTIME_SERVICES>`
|
|
147
|
+
block in your system context:
|
|
148
|
+
- Use the **Automation backend** `url_from_agent` as `OPENHANDS_HOST`
|
|
149
|
+
- Auth: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`
|
|
150
|
+
|
|
151
|
+
If no Automation backend is listed in `<RUNTIME_SERVICES>`, stop and tell
|
|
152
|
+
the user to start the full automation stack.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
tar -czf /tmp/slack-monitor.tar.gz -C /tmp/slack-monitor-build .
|
|
156
|
+
|
|
157
|
+
# OPENHANDS_HOST: read from <RUNTIME_SERVICES> Automation backend url_from_agent
|
|
158
|
+
OPENHANDS_HOST="<automation-url-from-runtime-services>"
|
|
159
|
+
|
|
160
|
+
TARBALL_PATH=$(curl -s -X POST \
|
|
161
|
+
"${OPENHANDS_HOST}/api/automation/v1/uploads?name=slack-channel-monitor" \
|
|
162
|
+
-H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
|
|
163
|
+
-H "Content-Type: application/gzip" \
|
|
164
|
+
--data-binary @/tmp/slack-monitor.tar.gz \
|
|
165
|
+
| python3 -c "import json,sys; print(json.load(sys.stdin)['tarball_path'])")
|
|
166
|
+
|
|
167
|
+
echo "Uploaded: $TARBALL_PATH"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
If the upload fails with a size error, the tarball must be under 1 MB.
|
|
171
|
+
`main.py` is under 15 KB so this should never trigger.
|
|
172
|
+
|
|
173
|
+
### Step 5 - Create the automation
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1" \
|
|
177
|
+
-H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
|
|
178
|
+
-H "Content-Type: application/json" \
|
|
179
|
+
-d "{
|
|
180
|
+
\"name\": \"Slack Channel Monitor\",
|
|
181
|
+
\"trigger\": {\"type\": \"cron\", \"schedule\": \"* * * * *\"},
|
|
182
|
+
\"tarball_path\": \"$TARBALL_PATH\",
|
|
183
|
+
\"entrypoint\": \"python3 main.py\",
|
|
184
|
+
\"timeout\": 55
|
|
185
|
+
}" | python3 -m json.tool
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
A 55-second timeout keeps runs well within the 60-second cron window.
|
|
189
|
+
|
|
190
|
+
Record the returned `id` - share it with the user as confirmation.
|
|
191
|
+
|
|
192
|
+
### Step 6 - Confirm
|
|
193
|
+
|
|
194
|
+
Tell the user:
|
|
195
|
+
|
|
196
|
+
> ✅ **Slack Channel Monitor** is running!
|
|
197
|
+
>
|
|
198
|
+
> - Automation ID: `{id}`
|
|
199
|
+
> - Channels: `{channel list}`
|
|
200
|
+
> - Trigger phrase: `{phrase}`
|
|
201
|
+
> - Polling every minute via cron `* * * * *`
|
|
202
|
+
> - State file: `~/.openhands/workspaces/automation-state/slack_poller_{id}.json`
|
|
203
|
+
>
|
|
204
|
+
> Send a message containing `{phrase}` in any monitored channel to test it.
|
|
205
|
+
> The bot will react with 👀 and reply with a link to the new conversation.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Runtime Behaviour (per poll)
|
|
210
|
+
|
|
211
|
+
Each cron run executes `main.py`, which runs **10 polling iterations** (every
|
|
212
|
+
5 seconds) within the 55-second timeout window. Each iteration:
|
|
213
|
+
|
|
214
|
+
1. **Loads state** from the JSON file (see `references/state-schema.md`).
|
|
215
|
+
2. **Resolves the Slack token** - checks `SLACK_USER_TOKEN` then `SLACK_BOT_TOKEN`.
|
|
216
|
+
3. **Fetches new messages:**
|
|
217
|
+
- User token + `search:read` + > 1 channel → single `search.messages` call
|
|
218
|
+
(searches for the trigger phrase across all channels).
|
|
219
|
+
- Otherwise → one `conversations.history` call per channel.
|
|
220
|
+
4. **Fetches thread replies** - one `conversations.replies` call per tracked thread.
|
|
221
|
+
5. **Processes messages** in chronological order:
|
|
222
|
+
- Skips messages already in `processed_ts` (dedup across the overlap window).
|
|
223
|
+
- Skips bot messages and any `ts` in `bot_message_ts`.
|
|
224
|
+
- Reply in a tracked thread (active **or** closed) → forwards to the existing
|
|
225
|
+
conversation; re-opens closed conversations automatically.
|
|
226
|
+
- Contains trigger phrase → 👀 reaction, create conversation, post link.
|
|
227
|
+
- Thread replies: agent receives full thread history for context.
|
|
228
|
+
- Root messages: agent receives the trigger text only.
|
|
229
|
+
6. **Checks conversation statuses** - for each active conversation where
|
|
230
|
+
`time.time() - last_activity > 15 s`:
|
|
231
|
+
- If status is `idle`, `finished`, `error`, or `stuck` → fetch the agent's
|
|
232
|
+
final response via `/api/conversations/{id}/agent_final_response` and post
|
|
233
|
+
it to the Slack thread. Mark conversation `closed`.
|
|
234
|
+
7. **Advances `last_poll`** to `now - 10 s` (overlap window prevents boundary
|
|
235
|
+
races). If a conversation creation failed, pins `last_poll` further back to
|
|
236
|
+
retry on the next iteration.
|
|
237
|
+
8. **Saves state** (including `processed_ts`) and continues to the next iteration.
|
|
238
|
+
9. After all iterations, fires the completion callback.
|
|
239
|
+
|
|
240
|
+
Debug output is written to both stdout and a persistent log at:
|
|
241
|
+
```
|
|
242
|
+
{WORKSPACE_BASE_ROOT}/automation-state/slack_poller_debug.log
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Additional Resources
|
|
248
|
+
|
|
249
|
+
### Reference Files
|
|
250
|
+
|
|
251
|
+
- **`references/slack-api.md`** - Slack token types, required scopes, API
|
|
252
|
+
endpoint reference, rate limits, and common error codes.
|
|
253
|
+
- **`references/state-schema.md`** - State JSON schema, field definitions,
|
|
254
|
+
example file, and conversation lifecycle diagram.
|
|
255
|
+
|
|
256
|
+
### Script Template
|
|
257
|
+
|
|
258
|
+
- **`scripts/main.py`** - The complete automation script. Customise the three
|
|
259
|
+
constants at the top (`TRIGGER_PHRASE`, `CHANNEL_IDS`, `DEFAULT_OPENHANDS_URL`)
|
|
260
|
+
before packaging.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Troubleshooting
|
|
265
|
+
|
|
266
|
+
| Symptom | Likely cause | Fix |
|
|
267
|
+
|---|---|---|
|
|
268
|
+
| Bot doesn't react to messages | Token missing or bot not in channel | Verify token with `auth.test`; `/invite @botname` |
|
|
269
|
+
| `not_in_channel` error in run logs | Bot token used but bot not a member | Invite bot or switch to user token |
|
|
270
|
+
| `missing_scope` error | Token lacks required scopes | Re-install Slack app with correct scopes (see `references/slack-api.md`) |
|
|
271
|
+
| No messages detected | `last_poll` timestamp is in the future | Delete the state file to reset; it will be recreated on next run |
|
|
272
|
+
| Conversation link 404 | `OPENHANDS_URL` points to wrong host | Set the `OPENHANDS_URL` secret to the correct base URL |
|
|
273
|
+
| Summary never posted | Conversation stuck in `running` state | Check conversation in the OpenHands UI; the agent may need intervention |
|
|
274
|
+
| Duplicate conversations created | `processed_ts` state missing or corrupted | Delete the state file to reset; dedup will rebuild on next run |
|
|
275
|
+
| Trigger message processed on each cron run | State file deleted between runs | Ensure `automation-state/` directory is persistent across runs |
|
|
276
|
+
| Debug info needed | Need detailed per-message trace | Check `{WORKSPACE_BASE_ROOT}/automation-state/slack_poller_debug.log` |
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
# auto-generated by sync_extensions.py
|
|
3
|
+
description: This skill should be used when the user asks to "monitor a Slack channel", "watch Slack for messages", "create a Slack bot that responds to mentions", "set up an OpenHands Slack integration", "trigger OpenHands from Slack", "respond to @openhands in Slack", or "poll Slack channels for a trigger phrase". Guides the user through creating a cron automation that watches up to 10 Slack channels and starts an OpenHands conversation whenever a configurable trigger phrase is detected.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Read and follow the complete instructions in the SKILL.md file located in this skill's directory.
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Slack API Reference
|
|
2
|
+
|
|
3
|
+
Reference material for the `slack-channel-monitor` skill. Consult this file
|
|
4
|
+
when resolving token issues, diagnosing permission errors, or adjusting the
|
|
5
|
+
polling strategy.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Token Types
|
|
10
|
+
|
|
11
|
+
| Type | Prefix | Typical source | Relevant scopes |
|
|
12
|
+
|------|--------|---------------|-----------------|
|
|
13
|
+
| **Bot token** | `xoxb-` | OAuth install / Slack App → Install App | `channels:history`, `channels:read`, `reactions:write`, `chat:write` |
|
|
14
|
+
| **User token** | `xoxp-` | OAuth flow on behalf of a workspace member | Same as bot + `search:read` for multi-channel search |
|
|
15
|
+
|
|
16
|
+
### Choosing a token
|
|
17
|
+
|
|
18
|
+
- **Prefer a bot token** for single-channel monitoring or when `search:read` is
|
|
19
|
+
unavailable. One `conversations.history` call per channel per minute is fine
|
|
20
|
+
for < 10 channels.
|
|
21
|
+
- **Use a user token** with `search:read` when monitoring multiple channels, to
|
|
22
|
+
reduce API calls by querying all channels in a single `search.messages` request.
|
|
23
|
+
|
|
24
|
+
### Checking token type at runtime
|
|
25
|
+
|
|
26
|
+
The script detects token type by checking which secret name is set:
|
|
27
|
+
|
|
28
|
+
1. `SLACK_USER_TOKEN` (checked first - user token preferred for multi-channel)
|
|
29
|
+
2. `SLACK_BOT_TOKEN`
|
|
30
|
+
|
|
31
|
+
Set the appropriate secret in **OpenHands Settings → Secrets**.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Required Scopes
|
|
36
|
+
|
|
37
|
+
### Bot token (`xoxb-`)
|
|
38
|
+
|
|
39
|
+
| Scope | Used for |
|
|
40
|
+
|-------|----------|
|
|
41
|
+
| `channels:history` | Read messages from public channels |
|
|
42
|
+
| `groups:history` | Read messages from private channels (if monitoring any) |
|
|
43
|
+
| `channels:read` | Resolve channel names → IDs |
|
|
44
|
+
| `reactions:write` | Add 👀 reaction to trigger messages |
|
|
45
|
+
| `chat:write` | Post conversation links and summaries back to Slack |
|
|
46
|
+
|
|
47
|
+
### User token (`xoxp-`) - additional scope
|
|
48
|
+
|
|
49
|
+
| Scope | Used for |
|
|
50
|
+
|-------|----------|
|
|
51
|
+
| `search:read` | `search.messages` across multiple channels in one request |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Relevant API Endpoints
|
|
56
|
+
|
|
57
|
+
### `conversations.history`
|
|
58
|
+
Fetch messages from a single channel newer than a timestamp.
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
GET https://slack.com/api/conversations.history
|
|
62
|
+
?channel=CHANNEL_ID
|
|
63
|
+
&oldest=UNIX_TIMESTAMP (exclusive - messages strictly after this)
|
|
64
|
+
&limit=100
|
|
65
|
+
&inclusive=false
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Returns a `messages` array. Each message has `ts`, `user`, `text`, `thread_ts`
|
|
69
|
+
(present if the message is in a thread or is a threaded reply).
|
|
70
|
+
|
|
71
|
+
**Bot must be invited to the channel** (or the token must have `channels:history`
|
|
72
|
+
for public channels without joining).
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### `conversations.replies`
|
|
77
|
+
Fetch replies inside a specific thread newer than a timestamp.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
GET https://slack.com/api/conversations.replies
|
|
81
|
+
?channel=CHANNEL_ID
|
|
82
|
+
&ts=THREAD_ROOT_TS
|
|
83
|
+
&oldest=UNIX_TIMESTAMP
|
|
84
|
+
&limit=100
|
|
85
|
+
&inclusive=false
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The first item in `messages` is always the parent message - the script drops it
|
|
89
|
+
when comparing `ts == thread_root_ts`.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### `search.messages`
|
|
94
|
+
Search for messages matching a query across channels (user token only).
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
GET https://slack.com/api/search.messages
|
|
98
|
+
?query=QUERY_STRING
|
|
99
|
+
&count=100
|
|
100
|
+
&sort=timestamp
|
|
101
|
+
&sort_dir=asc
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Query syntax used by this skill:**
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
"@openhands" in:<#C0123456> in:<#C9876543> after:2026-01-01
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- `in:<#CHANNEL_ID>` - restrict to a channel (channel ID or name both work)
|
|
111
|
+
- `after:YYYY-MM-DD` - date-level precision only (the script post-filters by
|
|
112
|
+
precise Unix timestamp)
|
|
113
|
+
- Phrase in quotes - exact match
|
|
114
|
+
|
|
115
|
+
**Limitations:**
|
|
116
|
+
- Date-only precision for `after:` - cannot filter to the minute
|
|
117
|
+
- Results sorted by relevance by default; use `sort=timestamp` to get chronological order
|
|
118
|
+
- `count` max is 100 per page (pagination supported via `page` parameter)
|
|
119
|
+
- Requires `search:read` scope - not available to bot tokens
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### `reactions.add`
|
|
124
|
+
Add an emoji reaction to a message.
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
POST https://slack.com/api/reactions.add
|
|
128
|
+
{
|
|
129
|
+
"channel": "CHANNEL_ID",
|
|
130
|
+
"name": "eyes",
|
|
131
|
+
"timestamp": "MESSAGE_TS"
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Error `already_reacted` is safe to ignore.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### `chat.postMessage`
|
|
140
|
+
Post a message to a channel, optionally within a thread.
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
POST https://slack.com/api/chat.postMessage
|
|
144
|
+
{
|
|
145
|
+
"channel": "CHANNEL_ID",
|
|
146
|
+
"text": "Message text",
|
|
147
|
+
"thread_ts": "THREAD_ROOT_TS" // omit for top-level messages
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Returns `ts` of the posted message - **store this in `bot_message_ts`** in the
|
|
152
|
+
state file to prevent the bot from processing its own messages.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### `conversations.list`
|
|
157
|
+
List channels visible to the token (used to resolve names → IDs during setup).
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
GET https://slack.com/api/conversations.list
|
|
161
|
+
?types=public_channel,private_channel
|
|
162
|
+
&limit=200
|
|
163
|
+
&exclude_archived=true
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Supports cursor-based pagination via the `response_metadata.next_cursor` field.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
### `auth.test`
|
|
171
|
+
Verify a token and retrieve the associated user/bot ID.
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
GET https://slack.com/api/auth.test
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Returns `user_id` (used by the script to detect and skip its own messages).
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Common Errors
|
|
182
|
+
|
|
183
|
+
| Error | Cause | Fix |
|
|
184
|
+
|-------|-------|-----|
|
|
185
|
+
| `not_in_channel` | Bot hasn't been invited | `/invite @botname` in the channel |
|
|
186
|
+
| `missing_scope` | Token lacks a required scope | Re-install the Slack app with the correct scopes |
|
|
187
|
+
| `channel_not_found` | Channel ID is wrong | Use `conversations.list` to verify the ID |
|
|
188
|
+
| `ratelimited` | Too many API calls | Slack allows ~50 requests/min per token; < 10 channels is well within limits |
|
|
189
|
+
| `invalid_auth` | Token expired or revoked | Regenerate the token and update the secret |
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Rate Limits
|
|
194
|
+
|
|
195
|
+
Slack applies per-method rate limits (Tier 2 = ~20 req/min, Tier 3 = ~50 req/min).
|
|
196
|
+
With < 10 channels polled every minute:
|
|
197
|
+
|
|
198
|
+
| Method | Tier | Calls/min | Headroom |
|
|
199
|
+
|--------|------|-----------|---------|
|
|
200
|
+
| `conversations.history` | Tier 3 | ≤ 10 | Comfortable |
|
|
201
|
+
| `conversations.replies` | Tier 3 | ≤ active threads | Fine unless hundreds of threads |
|
|
202
|
+
| `search.messages` | Tier 2 | 1 | Fine |
|
|
203
|
+
| `reactions.add` | Tier 2 | ≤ triggers/min | Fine |
|
|
204
|
+
| `chat.postMessage` | Tier 3 | ≤ triggers + summaries | Fine |
|
|
205
|
+
|
|
206
|
+
No rate-limit handling is implemented in the script. If you hit limits the
|
|
207
|
+
run will fail and retry on the next cron tick.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# State File Schema
|
|
2
|
+
|
|
3
|
+
The automation maintains a JSON state file that persists across polling runs.
|
|
4
|
+
This file is the source of truth for which conversations are active, which
|
|
5
|
+
timestamps have been processed, and which messages were posted by the bot.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## File Location
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
{WORKSPACE_BASE_ROOT}/automation-state/slack_poller_{automation_id}.json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Where `WORKSPACE_BASE_ROOT` is derived by going two levels up from the
|
|
16
|
+
`WORKSPACE_BASE` environment variable (stripping `automation-runs/{run_id}`).
|
|
17
|
+
|
|
18
|
+
Example on a local install:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
~/.openhands/workspaces/automation-state/slack_poller_abc12345-….json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The `automation_id` is read from the `AUTOMATION_EVENT_PAYLOAD` environment
|
|
25
|
+
variable (field `automation_id`).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Top-Level Schema
|
|
30
|
+
|
|
31
|
+
```jsonc
|
|
32
|
+
{
|
|
33
|
+
"version": 1, // schema version (integer)
|
|
34
|
+
"bot_user_id": "UBOTID123", // Slack user_id of the bot/token owner
|
|
35
|
+
// cached from auth.test; null until first run
|
|
36
|
+
"last_poll": {
|
|
37
|
+
"C0123456789": "1716576000.123456" // channel_id → float Unix timestamp (string)
|
|
38
|
+
// set to (now - POLL_OVERLAP_SECONDS) at the
|
|
39
|
+
// END of each run; pinned back if triggers fail
|
|
40
|
+
},
|
|
41
|
+
"conversations": { ... }, // see ConversationRecord below
|
|
42
|
+
"bot_message_ts": [ // rolling list of Slack 'ts' values for
|
|
43
|
+
"1716576100.000200" // messages THIS bot posted; used to skip
|
|
44
|
+
], // self-messages during processing
|
|
45
|
+
"processed_ts": [ // rolling list of message ts values that have
|
|
46
|
+
"1716576050.000100" // already been fully handled (dedup across the
|
|
47
|
+
] // overlap window between iterations)
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## `conversations` Map
|
|
54
|
+
|
|
55
|
+
Key: `"{channel_id}:{thread_root_ts}"` - uniquely identifies a Slack thread.
|
|
56
|
+
|
|
57
|
+
Value: **ConversationRecord**
|
|
58
|
+
|
|
59
|
+
```jsonc
|
|
60
|
+
{
|
|
61
|
+
// Required fields
|
|
62
|
+
"conversation_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
63
|
+
// OpenHands conversation UUID
|
|
64
|
+
"channel_id": "C0123456789", // Slack channel ID
|
|
65
|
+
"thread_ts": "1716576000.000100",
|
|
66
|
+
// Slack thread root timestamp
|
|
67
|
+
// (= msg_ts for top-level trigger messages)
|
|
68
|
+
"status": "active", // "active" | "closed"
|
|
69
|
+
"last_activity": 1716576060.0, // float Unix timestamp of the last time the
|
|
70
|
+
// script sent a message to this conversation
|
|
71
|
+
// (creation time, or time a reply was forwarded)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `status` values
|
|
76
|
+
|
|
77
|
+
| Value | Meaning |
|
|
78
|
+
|-------|---------|
|
|
79
|
+
| `active` | Conversation is running or awaiting more input; replies will be forwarded |
|
|
80
|
+
| `closed` | Summary has been posted to Slack; no further processing |
|
|
81
|
+
|
|
82
|
+
Closed conversations are retained in the map indefinitely (the map stays small
|
|
83
|
+
since there are < 10 channels and the trigger rate is typically low). If the
|
|
84
|
+
map grows unexpectedly, closed entries older than a configurable TTL can be
|
|
85
|
+
pruned.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## `bot_message_ts` List
|
|
90
|
+
|
|
91
|
+
A rolling list (max `MAX_BOT_TS = 2000` entries) of Slack `ts` values for
|
|
92
|
+
messages posted BY the bot. This prevents the script from treating its own
|
|
93
|
+
replies as user messages.
|
|
94
|
+
|
|
95
|
+
Entries are added when:
|
|
96
|
+
- The bot posts a conversation link (on trigger detection)
|
|
97
|
+
- The bot posts a summary (on conversation completion)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## `processed_ts` List
|
|
102
|
+
|
|
103
|
+
A rolling list (max `MAX_PROCESSED_TS = 2000` entries) of Slack `ts` values
|
|
104
|
+
for messages that have already been fully handled by this script.
|
|
105
|
+
|
|
106
|
+
Because `last_poll` is set to `(now - POLL_OVERLAP_SECONDS)` rather than
|
|
107
|
+
exactly `now`, messages near the boundary are re-fetched on the next iteration.
|
|
108
|
+
`processed_ts` provides a second deduplication layer that prevents these
|
|
109
|
+
re-fetched messages from being processed twice (e.g., triggering a duplicate
|
|
110
|
+
conversation or forwarding the same reply multiple times).
|
|
111
|
+
|
|
112
|
+
Entries are added when a message is either skipped (not human, already handled)
|
|
113
|
+
or successfully processed (trigger detected and conversation created, or reply
|
|
114
|
+
forwarded).
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Transition Diagram
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
[trigger detected]
|
|
122
|
+
│
|
|
123
|
+
▼
|
|
124
|
+
status = "active"
|
|
125
|
+
last_activity = now
|
|
126
|
+
│
|
|
127
|
+
(next run or later runs)
|
|
128
|
+
│
|
|
129
|
+
┌─────┴──────────────────────────────────────────┐
|
|
130
|
+
│ User sends a reply in the thread │
|
|
131
|
+
│ → send_to_conversation() called │
|
|
132
|
+
│ → last_activity = now │
|
|
133
|
+
└─────────────────────────────────────────────────┘
|
|
134
|
+
│
|
|
135
|
+
(when time.time() - last_activity > DONE_DEBOUNCE
|
|
136
|
+
AND conversation_status ∈ {idle, finished, error, stuck})
|
|
137
|
+
│
|
|
138
|
+
▼
|
|
139
|
+
Post summary to Slack thread
|
|
140
|
+
status = "closed"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Example State File
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"version": 1,
|
|
150
|
+
"bot_user_id": "U04AB1CDEF",
|
|
151
|
+
"last_poll": {
|
|
152
|
+
"C0123456789": "1716576060.000000",
|
|
153
|
+
"C9876543210": "1716576060.000000"
|
|
154
|
+
},
|
|
155
|
+
"conversations": {
|
|
156
|
+
"C0123456789:1716575900.000100": {
|
|
157
|
+
"conversation_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
158
|
+
"channel_id": "C0123456789",
|
|
159
|
+
"thread_ts": "1716575900.000100",
|
|
160
|
+
"status": "active",
|
|
161
|
+
"last_activity": 1716575902.3
|
|
162
|
+
},
|
|
163
|
+
"C9876543210:1716570000.000500": {
|
|
164
|
+
"conversation_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
|
|
165
|
+
"channel_id": "C9876543210",
|
|
166
|
+
"thread_ts": "1716570000.000500",
|
|
167
|
+
"status": "closed",
|
|
168
|
+
"last_activity": 1716572100.0
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"bot_message_ts": [
|
|
172
|
+
"1716575903.000200",
|
|
173
|
+
"1716572105.000100"
|
|
174
|
+
],
|
|
175
|
+
"processed_ts": [
|
|
176
|
+
"1716575900.000100",
|
|
177
|
+
"1716572000.000500"
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
```
|