Skip to content

feat: OpenClaw plugin, AO skill, Discord notifier, and setup wizard#631

Merged
illegalcall merged 18 commits intoComposioHQ:mainfrom
illegalcall:feat/openclaw-integration
Mar 27, 2026
Merged

feat: OpenClaw plugin, AO skill, Discord notifier, and setup wizard#631
illegalcall merged 18 commits intoComposioHQ:mainfrom
illegalcall:feat/openclaw-integration

Conversation

@illegalcall
Copy link
Copy Markdown
Collaborator

Adds bidirectional integration between Agent Orchestrator and OpenClaw, enabling AI bots on Discord/Telegram to manage coding agent fleets through natural conversation.

OpenClaw Plugin (openclaw-plugin/):

  • 14 AI tools: ao_spawn, ao_issues, ao_sessions, ao_status, ao_batch_spawn, ao_send, ao_kill, ao_session_restore, ao_session_cleanup, ao_session_claim_pr, ao_review_check, ao_verify, ao_doctor, ao_session_list
  • Hooks: message_received + before_prompt_build for live data injection
  • /ao slash command with subcommands (sessions, spawn, issues, doctor, setup)
  • Background services: health monitor + issue board scanner
  • Security: execFileSync with arg arrays (no shell injection)

AO Skill (skills/agent-orchestrator/):

  • Natural language intent → AO tool mapping
  • Decision heuristic: quick fix → direct, multi-issue → AO
  • Designed for ClawHub publishing (blocked on ClawHub server bug)

CLI: ao setup openclaw (packages/cli/src/commands/setup.ts):

  • Interactive wizard + non-interactive mode
  • Auto-detects OpenClaw gateway on localhost
  • Auto-generates secure token
  • Writes both configs (agent-orchestrator.yaml + openclaw.json)
  • Appends OPENCLAW_HOOKS_TOKEN to shell profile
  • Validates connection end-to-end

CLI: ao doctor notifier checks:

  • Probes OpenClaw gateway reachability + token validity
  • --test-notify flag sends test event through all configured notifiers
  • Failure count propagated to exit code

Discord Notifier (packages/plugins/notifier-discord/):

  • Rich webhook embeds with colors, fields, action links
  • Retry with exponential backoff + Discord Retry-After header
  • Request timeouts, embed truncation, webhook URL validation
  • thread_id via URL query string (Discord API requirement)

OpenClaw Notifier improvements:

  • Added request timeouts via AbortController
  • Improved README

Adds bidirectional integration between Agent Orchestrator and OpenClaw,
enabling AI bots on Discord/Telegram/WhatsApp to manage coding agent
fleets through natural conversation.

OpenClaw Plugin (openclaw-plugin/):
- 14 AI tools: ao_spawn, ao_issues, ao_sessions, ao_status, ao_batch_spawn,
  ao_send, ao_kill, ao_session_restore, ao_session_cleanup,
  ao_session_claim_pr, ao_review_check, ao_verify, ao_doctor, ao_session_list
- Hooks: message_received + before_prompt_build for live data injection
- /ao slash command with subcommands (sessions, spawn, issues, doctor, setup)
- Background services: health monitor + issue board scanner
- Security: execFileSync with arg arrays (no shell injection)

AO Skill (skills/agent-orchestrator/):
- Natural language intent → AO tool mapping
- Decision heuristic: quick fix → direct, multi-issue → AO
- Designed for ClawHub publishing (blocked on ClawHub server bug)

CLI: ao setup openclaw (packages/cli/src/commands/setup.ts):
- Interactive wizard + non-interactive mode
- Auto-detects OpenClaw gateway on localhost
- Auto-generates secure token
- Writes both configs (agent-orchestrator.yaml + openclaw.json)
- Appends OPENCLAW_HOOKS_TOKEN to shell profile
- Validates connection end-to-end

CLI: ao doctor notifier checks:
- Probes OpenClaw gateway reachability + token validity
- --test-notify flag sends test event through all configured notifiers
- Failure count propagated to exit code

Discord Notifier (packages/plugins/notifier-discord/):
- Rich webhook embeds with colors, fields, action links
- Retry with exponential backoff + Discord Retry-After header
- Request timeouts, embed truncation, webhook URL validation
- thread_id via URL query string (Discord API requirement)

OpenClaw Notifier improvements:
- Added request timeouts via AbortController
- Improved README

Reviewed through 5 rounds of Codex code review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread packages/cli/src/commands/setup.ts Outdated
Comment thread openclaw-plugin/index.ts Outdated
Comment thread packages/cli/__tests__/lib/openclaw-probe.test.ts Outdated
Comment thread packages/plugins/notifier-discord/src/index.test.ts
- Fix shell injection in writeShellExport: use single-quoted token with
  escaped embedded single quotes instead of double-quoted interpolation
- Fix board scanner initial setTimeout not cleared on stop: store the
  timeout handle and clear it in the stop handler
- Fix openclaw-probe test asserting deliver:true when code sends false
- Fix Discord notifier thread_id test to check URL query param instead
  of body, and remove redundant thread_id from post() body payload
illegalcall pushed a commit to illegalcall/agent-orchestrator that referenced this pull request Mar 23, 2026
Use single quotes instead of double quotes for the OPENCLAW_HOOKS_TOKEN
export line in shell profiles. This prevents shell expansion of $(),
backticks, and other special characters in the token value.

Also use a replacer function in String.prototype.replace to avoid special
replacement patterns ($&, etc.).

Fixes shell injection vulnerability flagged by Cursor Bugbot on PR ComposioHQ#631.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Config write strips all YAML comments
    • Replaced yamlParse/yamlStringify with parseDocument and Document.toString() to preserve comments during config updates.
  • ✅ Fixed: Setup tests expect validation that code skips
    • Updated tests to match actual non-interactive behavior: no validation and auto-generated tokens when missing.

Create PR

Or push these changes by commenting:

@cursor push 0a3e246e61
Preview (0a3e246e61)
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
--- /dev/null
+++ b/package-lock.json
@@ -1,0 +1,2445 @@
+{
+  "name": "agent-orchestrator",
+  "version": "0.1.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "agent-orchestrator",
+      "version": "0.1.0",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "devDependencies": {
+        "@changesets/cli": "^2.29.8",
+        "@eslint/js": "^10.0.1",
+        "@types/node": "^25.2.3",
+        "eslint": "^10.0.0",
+        "eslint-config-prettier": "^10.1.8",
+        "husky": "^9.1.7",
+        "prettier": "^3.8.1",
+        "typescript-eslint": "^8.55.0"
+      },
+      "engines": {
+        "node": ">=20.0.0"
+      }
+    },
+    "node_modules/@babel/runtime": {
+      "version": "7.29.2",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
+      "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@changesets/apply-release-plan": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.1.0.tgz",
+      "integrity": "sha512-yq8ML3YS7koKQ/9bk1PqO0HMzApIFNwjlwCnwFEXMzNe8NpzeeYYKCmnhWJGkN8g7E51MnWaSbqRcTcdIxUgnQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/config": "^3.1.3",
+        "@changesets/get-version-range-type": "^0.4.0",
+        "@changesets/git": "^3.0.4",
+        "@changesets/should-skip-package": "^0.1.2",
+        "@changesets/types": "^6.1.0",
+        "@manypkg/get-packages": "^1.1.3",
+        "detect-indent": "^6.0.0",
+        "fs-extra": "^7.0.1",
+        "lodash.startcase": "^4.4.0",
+        "outdent": "^0.5.0",
+        "prettier": "^2.7.1",
+        "resolve-from": "^5.0.0",
+        "semver": "^7.5.3"
+      }
+    },
+    "node_modules/@changesets/apply-release-plan/node_modules/prettier": {
+      "version": "2.8.8",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
+      "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "prettier": "bin-prettier.js"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      },
+      "funding": {
+        "url": "https://github.com/prettier/prettier?sponsor=1"
+      }
+    },
+    "node_modules/@changesets/assemble-release-plan": {
+      "version": "6.0.9",
+      "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.9.tgz",
+      "integrity": "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/errors": "^0.2.0",
+        "@changesets/get-dependents-graph": "^2.1.3",
+        "@changesets/should-skip-package": "^0.1.2",
+        "@changesets/types": "^6.1.0",
+        "@manypkg/get-packages": "^1.1.3",
+        "semver": "^7.5.3"
+      }
+    },
+    "node_modules/@changesets/changelog-git": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz",
+      "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/types": "^6.1.0"
+      }
+    },
+    "node_modules/@changesets/cli": {
+      "version": "2.30.0",
+      "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.30.0.tgz",
+      "integrity": "sha512-5D3Nk2JPqMI1wK25pEymeWRSlSMdo5QOGlyfrKg0AOufrUcjEE3RQgaCpHoBiM31CSNrtSgdJ0U6zL1rLDDfBA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/apply-release-plan": "^7.1.0",
+        "@changesets/assemble-release-plan": "^6.0.9",
+        "@changesets/changelog-git": "^0.2.1",
+        "@changesets/config": "^3.1.3",
+        "@changesets/errors": "^0.2.0",
+        "@changesets/get-dependents-graph": "^2.1.3",
+        "@changesets/get-release-plan": "^4.0.15",
+        "@changesets/git": "^3.0.4",
+        "@changesets/logger": "^0.1.1",
+        "@changesets/pre": "^2.0.2",
+        "@changesets/read": "^0.6.7",
+        "@changesets/should-skip-package": "^0.1.2",
+        "@changesets/types": "^6.1.0",
+        "@changesets/write": "^0.4.0",
+        "@inquirer/external-editor": "^1.0.2",
+        "@manypkg/get-packages": "^1.1.3",
+        "ansi-colors": "^4.1.3",
+        "enquirer": "^2.4.1",
+        "fs-extra": "^7.0.1",
+        "mri": "^1.2.0",
+        "package-manager-detector": "^0.2.0",
+        "picocolors": "^1.1.0",
+        "resolve-from": "^5.0.0",
+        "semver": "^7.5.3",
+        "spawndamnit": "^3.0.1",
+        "term-size": "^2.1.0"
+      },
+      "bin": {
+        "changeset": "bin.js"
+      }
+    },
+    "node_modules/@changesets/config": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.3.tgz",
+      "integrity": "sha512-vnXjcey8YgBn2L1OPWd3ORs0bGC4LoYcK/ubpgvzNVr53JXV5GiTVj7fWdMRsoKUH7hhhMAQnsJUqLr21EncNw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/errors": "^0.2.0",
+        "@changesets/get-dependents-graph": "^2.1.3",
+        "@changesets/logger": "^0.1.1",
+        "@changesets/should-skip-package": "^0.1.2",
+        "@changesets/types": "^6.1.0",
+        "@manypkg/get-packages": "^1.1.3",
+        "fs-extra": "^7.0.1",
+        "micromatch": "^4.0.8"
+      }
+    },
+    "node_modules/@changesets/errors": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz",
+      "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "extendable-error": "^0.1.5"
+      }
+    },
+    "node_modules/@changesets/get-dependents-graph": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.3.tgz",
+      "integrity": "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/types": "^6.1.0",
+        "@manypkg/get-packages": "^1.1.3",
+        "picocolors": "^1.1.0",
+        "semver": "^7.5.3"
+      }
+    },
+    "node_modules/@changesets/get-release-plan": {
+      "version": "4.0.15",
+      "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.15.tgz",
+      "integrity": "sha512-Q04ZaRPuEVZtA+auOYgFaVQQSA98dXiVe/yFaZfY7hoSmQICHGvP0TF4u3EDNHWmmCS4ekA/XSpKlSM2PyTS2g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/assemble-release-plan": "^6.0.9",
+        "@changesets/config": "^3.1.3",
+        "@changesets/pre": "^2.0.2",
+        "@changesets/read": "^0.6.7",
+        "@changesets/types": "^6.1.0",
+        "@manypkg/get-packages": "^1.1.3"
+      }
+    },
+    "node_modules/@changesets/get-version-range-type": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz",
+      "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@changesets/git": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz",
+      "integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/errors": "^0.2.0",
+        "@manypkg/get-packages": "^1.1.3",
+        "is-subdir": "^1.1.1",
+        "micromatch": "^4.0.8",
+        "spawndamnit": "^3.0.1"
+      }
+    },
+    "node_modules/@changesets/logger": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz",
+      "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "picocolors": "^1.1.0"
+      }
+    },
+    "node_modules/@changesets/parse": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.3.tgz",
+      "integrity": "sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/types": "^6.1.0",
+        "js-yaml": "^4.1.1"
+      }
+    },
+    "node_modules/@changesets/pre": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz",
+      "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/errors": "^0.2.0",
+        "@changesets/types": "^6.1.0",
+        "@manypkg/get-packages": "^1.1.3",
+        "fs-extra": "^7.0.1"
+      }
+    },
+    "node_modules/@changesets/read": {
+      "version": "0.6.7",
+      "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.7.tgz",
+      "integrity": "sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/git": "^3.0.4",
+        "@changesets/logger": "^0.1.1",
+        "@changesets/parse": "^0.4.3",
+        "@changesets/types": "^6.1.0",
+        "fs-extra": "^7.0.1",
+        "p-filter": "^2.1.0",
+        "picocolors": "^1.1.0"
+      }
+    },
+    "node_modules/@changesets/should-skip-package": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz",
+      "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/types": "^6.1.0",
+        "@manypkg/get-packages": "^1.1.3"
+      }
+    },
+    "node_modules/@changesets/types": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz",
+      "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@changesets/write": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz",
+      "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@changesets/types": "^6.1.0",
+        "fs-extra": "^7.0.1",
+        "human-id": "^4.1.1",
+        "prettier": "^2.7.1"
+      }
+    },
+    "node_modules/@changesets/write/node_modules/prettier": {
+      "version": "2.8.8",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
+      "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "prettier": "bin-prettier.js"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      },
+      "funding": {
+        "url": "https://github.com/prettier/prettier?sponsor=1"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils": {
+      "version": "4.9.1",
+      "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+      "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "eslint-visitor-keys": "^3.4.3"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+      }
+    },
+    "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@eslint-community/regexpp": {
+      "version": "4.12.2",
+      "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+      "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@eslint/config-array": {
+      "version": "0.23.3",
+      "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz",
+      "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@eslint/object-schema": "^3.0.3",
+        "debug": "^4.3.1",
+        "minimatch": "^10.2.4"
+      },
+      "engines": {
+        "node": "^20.19.0 || ^22.13.0 || >=24"
+      }
+    },
+    "node_modules/@eslint/config-helpers": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz",
+      "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@eslint/core": "^1.1.1"
+      },
+      "engines": {
+        "node": "^20.19.0 || ^22.13.0 || >=24"
+      }
+    },
+    "node_modules/@eslint/core": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz",
+      "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@types/json-schema": "^7.0.15"
+      },
+      "engines": {
+        "node": "^20.19.0 || ^22.13.0 || >=24"
+      }
+    },
+    "node_modules/@eslint/js": {
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
+      "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^20.19.0 || ^22.13.0 || >=24"
+      },
+      "funding": {
+        "url": "https://eslint.org/donate"
+      },
+      "peerDependencies": {
+        "eslint": "^10.0.0"
+      },
+      "peerDependenciesMeta": {
+        "eslint": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@eslint/object-schema": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz",
+      "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": "^20.19.0 || ^22.13.0 || >=24"
+      }
+    },
+    "node_modules/@eslint/plugin-kit": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz",
+      "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@eslint/core": "^1.1.1",
+        "levn": "^0.4.1"
+      },
+      "engines": {
+        "node": "^20.19.0 || ^22.13.0 || >=24"
+      }
+    },
+    "node_modules/@humanfs/core": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+      "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=18.18.0"
+      }
+    },
+    "node_modules/@humanfs/node": {
+      "version": "0.16.7",
+      "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+      "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@humanfs/core": "^0.19.1",
+        "@humanwhocodes/retry": "^0.4.0"
+      },
+      "engines": {
+        "node": ">=18.18.0"
+      }
+    },
+    "node_modules/@humanwhocodes/module-importer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=12.22"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@humanwhocodes/retry": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+      "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=18.18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
+    "node_modules/@inquirer/external-editor": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz",
+      "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chardet": "^2.1.1",
+        "iconv-lite": "^0.7.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@types/node": ">=18"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@manypkg/find-root": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz",
+      "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.5.5",
+        "@types/node": "^12.7.1",
+        "find-up": "^4.1.0",
+        "fs-extra": "^8.1.0"
+      }
+    },
+    "node_modules/@manypkg/find-root/node_modules/@types/node": {
+      "version": "12.20.55",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
+      "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@manypkg/find-root/node_modules/fs-extra": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+      "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^4.0.0",
+        "universalify": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=6 <7 || >=8"
+      }
+    },
+    "node_modules/@manypkg/get-packages": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz",
+      "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.5.5",
+        "@changesets/types": "^4.0.1",
+        "@manypkg/find-root": "^1.1.0",
+        "fs-extra": "^8.1.0",
+        "globby": "^11.0.0",
+        "read-yaml-file": "^1.1.0"
+      }
+    },
+    "node_modules/@manypkg/get-packages/node_modules/@changesets/types": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz",
+      "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@manypkg/get-packages/node_modules/fs-extra": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+      "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^4.0.0",
+        "universalify": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=6 <7 || >=8"
+      }
+    },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@types/esrecurse": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
+      "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/json-schema": {
+      "version": "7.0.15",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+      "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/node": {
+      "version": "25.5.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
+      "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~7.18.0"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin": {
+      "version": "8.57.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz",
+      "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.12.2",
+        "@typescript-eslint/scope-manager": "8.57.2",
+        "@typescript-eslint/type-utils": "8.57.2",
+        "@typescript-eslint/utils": "8.57.2",
+        "@typescript-eslint/visitor-keys": "8.57.2",
+        "ignore": "^7.0.5",
+        "natural-compare": "^1.4.0",
+        "ts-api-utils": "^2.4.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "@typescript-eslint/parser": "^8.57.2",
+        "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+      "version": "7.0.5",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+      "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/@typescript-eslint/parser": {
+      "version": "8.57.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz",
+      "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/scope-manager": "8.57.2",
+        "@typescript-eslint/types": "8.57.2",
+        "@typescript-eslint/typescript-estree": "8.57.2",
+        "@typescript-eslint/visitor-keys": "8.57.2",
+        "debug": "^4.4.3"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/project-service": {
+      "version": "8.57.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz",
+      "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/tsconfig-utils": "^8.57.2",
+        "@typescript-eslint/types": "^8.57.2",
+        "debug": "^4.4.3"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/scope-manager": {
+      "version": "8.57.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz",
+      "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/types": "8.57.2",
+        "@typescript-eslint/visitor-keys": "8.57.2"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/tsconfig-utils": {
+      "version": "8.57.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz",
+      "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "typescript": ">=4.8.4 <6.0.0"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils": {
+      "version": "8.57.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz",
+      "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@typescript-eslint/types": "8.57.2",
+        "@typescript-eslint/typescript-estree": "8.57.2",
+        "@typescript-eslint/utils": "8.57.2",
+        "debug": "^4.4.3",
+        "ts-api-utils": "^2.4.0"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependencies": {
+        "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
... diff truncated: showing 800 of 3578 lines

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment thread packages/cli/src/commands/setup.ts Outdated
Comment thread packages/cli/__tests__/commands/setup.test.ts Outdated
- Store board scanner initial setTimeout and clear on stop
- Fix deliver assertion in openclaw-probe test (true -> false)
- Fix Discord thread_id test to check URL query param, not body
- Use yaml Document API to preserve comments in config writes
- Fix setup tests to match actual nonInteractiveSetup behavior
  (skips validation, auto-generates missing tokens)
@i-trytoohard i-trytoohard force-pushed the feat/openclaw-integration branch from ed59b99 to 8bc2a5b Compare March 24, 2026 06:47
@illegalcall
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread packages/cli/src/commands/doctor.ts Outdated
Comment thread packages/plugins/notifier-discord/src/index.ts
Comment thread packages/plugins/notifier-discord/src/index.ts
Comment thread packages/cli/src/commands/setup.ts
Comment thread packages/cli/__tests__/commands/setup.test.ts
Comment thread openclaw-plugin/index.ts Outdated
i-trytoohard pushed a commit that referenced this pull request Mar 25, 2026
… checks (#631)

Adds bidirectional integration between Agent Orchestrator and OpenClaw:

- openclaw-plugin/: 14 AI tools, hooks for live data injection, /ao slash
  command, background health monitor and issue board scanner. Batch-spawn
  follow-up timeouts now tracked and cleared on plugin stop.

- packages/plugins/notifier-discord/: Rich webhook embeds with retry/backoff,
  Retry-After header support, thread_id via URL query param. Fixed 429
  double-delay (skip exponential backoff after Retry-After wait). Fixed
  inconsistent null guard in notifyWithActions to use effectiveUrl.

- packages/cli/src/commands/setup.ts: Interactive + non-interactive wizard
  to configure AO ↔ OpenClaw. Writes ${OPENCLAW_HOOKS_TOKEN} placeholder
  in YAML (not the raw token) to avoid committing secrets to VCS. Shell
  profile export uses single-quote escaping to prevent injection.

- packages/cli/src/commands/doctor.ts: OpenClaw gateway probe + token
  validation, --test-notify flag. Refactored tsFailures from module-level
  mutable state to local counter passed via closure.

- packages/cli/src/lib/openclaw-probe.ts: probeGateway + validateToken helpers.

- skills/agent-orchestrator/: Natural language → AO tool mapping skill.

- docs/openclaw-plugin-setup.md: Full setup guide.

Fixes Cursor Bugbot review comments: shell injection (#1), board scanner
timeout cleanup (#2), YAML comment preservation (#3), test/code alignment
for non-interactive validation (#4, #12), module-level mutable state (#5),
notifyWithActions null guard (#6), plaintext token in YAML (#7), batch-spawn
timeout cleanup (#8), 429 double-delay (#11).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
i-trytoohard pushed a commit that referenced this pull request Mar 25, 2026
Fixes all 12 issues identified in the Cursor Bugbot review:

#4 – Setup tests now assert non-interactive mode skips validation and
  auto-generates tokens; removed incorrect validateToken call expectations.

#5 – Replaced module-level mutable `tsFailures` in doctor.ts with a
  `makeFailCounter()` closure that is local to each command invocation,
  eliminating potential state bleed between invocations.

#6 – Both `notify`, `notifyWithActions`, and `post` in notifier-discord
  now consistently guard on `effectiveUrl` (which includes thread_id),
  not on the raw `webhookUrl`. Removes non-null assertions.

#7/#12 – setup.ts now writes `${OPENCLAW_HOOKS_TOKEN}` as the token
  value in the YAML config instead of the raw token, so credentials are
  never committed to version control. setup.test.ts already expected this
  placeholder; the test was correct, the code was not.

#8 – `ao_batch_spawn` follow-up setTimeout handles are tracked in
  `batchSpawnFollowUpTimeouts[]` and cleared when the health service stops,
  preventing timer leaks after plugin shutdown.

#11 – Discord 429 Retry-After handling no longer double-delays: a
  `skipNextBackoff` flag is set after waiting for Retry-After so the
  following iteration skips the standard exponential backoff.

Also removes the unused `yamlStringify` import from setup.ts.

Issues #1/#2/#3/#9/#10 were already correctly addressed in previous commits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AO Bot and others added 3 commits March 25, 2026 18:33
Fixes all 12 issues identified in the Cursor Bugbot review:

#4 – Setup tests now assert non-interactive mode skips validation and
  auto-generates tokens; removed incorrect validateToken call expectations.

#5 – Replaced module-level mutable `tsFailures` in doctor.ts with a
  `makeFailCounter()` closure that is local to each command invocation,
  eliminating potential state bleed between invocations.

#6 – Both `notify`, `notifyWithActions`, and `post` in notifier-discord
  now consistently guard on `effectiveUrl` (which includes thread_id),
  not on the raw `webhookUrl`. Removes non-null assertions.

#7/#12 – setup.ts now writes `${OPENCLAW_HOOKS_TOKEN}` as the token
  value in the YAML config instead of the raw token, so credentials are
  never committed to version control. setup.test.ts already expected this
  placeholder; the test was correct, the code was not.

#8 – `ao_batch_spawn` follow-up setTimeout handles are tracked in
  `batchSpawnFollowUpTimeouts[]` and cleared when the health service stops,
  preventing timer leaks after plugin shutdown.

#11 – Discord 429 Retry-After handling no longer double-delays: a
  `skipNextBackoff` flag is set after waiting for Retry-After so the
  following iteration skips the standard exponential backoff.

Also removes the unused `yamlStringify` import from setup.ts.

Issues #1/#2/#3/#9/#10 were already correctly addressed in previous commits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use consistent regex for OPENCLAW_HOOKS_TOKEN detection and replacement
  in shell profile (prevents silent no-ops for non-exported lines)
- Broaden token detection regex to match lines with/without export prefix
  and leading whitespace
- Fix misleading --non-interactive help text (token is auto-generated)
- Fix doctor.ts catch block to say "Notifier checks failed" not "load config"
- Fix 204 mock in Discord notifier test (ok: true, not ok: false)
- Fix weak no-duplicate assertion in setup.test.ts (actually count list items)
- Add discord to notifier options comment in config-instruction.ts
- URL-encode threadId in Discord webhook URL construction
- Add aoCwd to required[] in openclaw.plugin.json configSchema
- Add HTTPS recommendation comment to agent-orchestrator.yaml.example
- Add rimraf for cross-platform clean script in notifier-discord
- Rename "Recommended Settings" to "Required: Disable Conflicting Built-in Skills"
  with explicit warning in docs
- Add /ao setup post-setup reminder to manually disable coding-agent skill
- Fix misleading README non-interactive example wording

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When `ao doctor --test-notify` is requested and config loading throws,
use fail() instead of warn() so the process exits non-zero. Consistent
with the existing behavior when no config file exists at all.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/plugins/notifier-discord/src/index.ts
Comment thread openclaw-plugin/index.ts
Comment thread openclaw-plugin/index.ts
Comment thread packages/cli/src/commands/setup.ts
Comment thread packages/cli/src/commands/doctor.ts Outdated
- Add module-level sanitizeCliArg() and apply to all AI tool execute
  handlers (ao_spawn, ao_batch_spawn, ao_send, ao_kill, ao_session_restore,
  ao_review_check, ao_session_cleanup) to prevent CLI flag injection from
  LLM-supplied params
- Clear batchSpawnFollowUpTimeouts in board scanner stop handler too —
  previously leaked when healthPollIntervalMs <= 0 (health service never starts)
- Exclude commented lines from shell profile token regex with (?!\s*#)
  negative lookahead to avoid replacing # OPENCLAW_HOOKS_TOKEN=... lines
- Resolve \${ENV_VAR} placeholders in doctor's OpenClaw token check —
  ao setup openclaw writes the literal string "\${OPENCLAW_HOOKS_TOKEN}"
  which was being sent as the Bearer token instead of the actual value

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread openclaw-plugin/index.ts
Comment thread packages/plugins/notifier-discord/src/index.ts
- Fix YAML project key detection for inline comments: trimmed.endsWith(":")
  fails on valid YAML like "my-app: # description" — strip inline comments
  before the check so getConfiguredRepos works with commented project keys
- Fix Discord retry backoff skipping after 429+5xx sequence: the skipNextBackoff
  flag caused exponential backoff to be skipped on the attempt AFTER a 429,
  even when that attempt failed with an unrelated 5xx error. Removed the flag
  entirely — continue already skips backoff naturally for the 429 iteration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/cli/src/commands/setup.ts
…uting

When notificationRouting didn't exist, the setup wizard created a default
that included desktop only for urgent/action and dropped it for warning/info.
Users with defaults.notifiers: [desktop] would silently lose desktop
notifications for lower-priority events since notificationRouting takes
precedence over defaults.notifiers.

Now seeds all four priority levels from the existing defaults.notifiers array
(plus openclaw) so no previously-configured notifier is lost.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/plugins/notifier-discord/src/index.ts
Each 429 with a Retry-After header was consuming one slot from the error
retry budget. With retries=3, three sustained rate-limits would exhaust
retries before any real error retry could fire.

Track rate-limit waits with a separate rateLimitRetries counter (capped at
retries to prevent infinite loops) and decrement attempt before continue so
the for-loop increment cancels out and the error retry budget is preserved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/plugins/notifier-discord/src/index.ts
illegalcall and others added 2 commits March 26, 2026 23:11
Previously, a 429 with no Retry-After header fell through to response.text()
with no explicit wait, relying on the generic exponential backoff. Now all 429
responses are handled uniformly via rateLimitRetries: use the Retry-After
value when present, otherwise fall back to retryDelayMs as the minimum wait.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- openclaw-probe.ts: fix trailing ": " when response body is empty —
  use conditional interpolation instead of .trim() on the full string
- doctor.ts: split connectivity check and test-notify into separate
  try-catch blocks with distinct error messages so failures are
  attributed correctly ("Notifier connectivity check failed" vs
  "Sending test notifications failed")

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/plugins/notifier-discord/src/index.ts
When rateLimitRetries was exhausted, a 429 fell through to the normal
error path where isRetryableHttpStatus(429)=true caused it to also
consume the error retry budget — giving 2×retries total attempts.

Now throws immediately when rate-limit budget is exhausted so the two
counters remain independent: up to `retries` rate-limit waits, then
up to `retries+1` attempts for genuine errors (5xx), never compounding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/plugins/notifier-discord/src/index.ts Outdated
i-trytoohard pushed a commit that referenced this pull request Mar 26, 2026
- doctor-script: isolate PATH in "applies safe fixes" test to prevent
  real `ao` binary from short-circuiting the fake npm, causing ENOENT
  on npm.log
- setup: replace fragile regex match for duplicate openclaw check with
  YAML parse of defaults.notifiers, and fix unused `matches` lint error

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
illegalcall and others added 2 commits March 27, 2026 01:41
…ediately

Without this, the catch block's `err === lastError` identity check fails and
the error is absorbed into the retry loop instead of propagating.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread packages/plugins/notifier-discord/src/index.ts
illegalcall and others added 2 commits March 27, 2026 02:27
- Add typed interfaces (PluginApi, PluginEvent, CommandContext, CommandResult)
  to replace `any` types in openclaw-plugin/index.ts
- Replace `== null` with strict equality checks (eqeqeq)
- Attach `{ cause: err }` to rethrown ECONNREFUSED error in notifier-openclaw
  to satisfy preserve-caught-error rule

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread openclaw-plugin/index.ts
@illegalcall illegalcall merged commit 5933991 into ComposioHQ:main Mar 27, 2026
9 of 10 checks passed
fastestdevalive pushed a commit to fastestdevalive/agent-orchestrator that referenced this pull request Mar 30, 2026
- Fix shell injection in writeShellExport: use single-quoted token with
  escaped embedded single quotes instead of double-quoted interpolation
- Fix board scanner initial setTimeout not cleared on stop: store the
  timeout handle and clear it in the stop handler
- Fix openclaw-probe test asserting deliver:true when code sends false
- Fix Discord notifier thread_id test to check URL query param instead
  of body, and remove redundant thread_id from post() body payload
fastestdevalive pushed a commit to fastestdevalive/agent-orchestrator that referenced this pull request Mar 30, 2026
- Store board scanner initial setTimeout and clear on stop
- Fix deliver assertion in openclaw-probe test (true -> false)
- Fix Discord thread_id test to check URL query param, not body
- Use yaml Document API to preserve comments in config writes
- Fix setup tests to match actual nonInteractiveSetup behavior
  (skips validation, auto-generates missing tokens)
fastestdevalive pushed a commit to fastestdevalive/agent-orchestrator that referenced this pull request Mar 30, 2026
Fixes all 12 issues identified in the Cursor Bugbot review:

#4 – Setup tests now assert non-interactive mode skips validation and
  auto-generates tokens; removed incorrect validateToken call expectations.

#5 – Replaced module-level mutable `tsFailures` in doctor.ts with a
  `makeFailCounter()` closure that is local to each command invocation,
  eliminating potential state bleed between invocations.

#6 – Both `notify`, `notifyWithActions`, and `post` in notifier-discord
  now consistently guard on `effectiveUrl` (which includes thread_id),
  not on the raw `webhookUrl`. Removes non-null assertions.

#7/#12 – setup.ts now writes `${OPENCLAW_HOOKS_TOKEN}` as the token
  value in the YAML config instead of the raw token, so credentials are
  never committed to version control. setup.test.ts already expected this
  placeholder; the test was correct, the code was not.

#8 – `ao_batch_spawn` follow-up setTimeout handles are tracked in
  `batchSpawnFollowUpTimeouts[]` and cleared when the health service stops,
  preventing timer leaks after plugin shutdown.

#11 – Discord 429 Retry-After handling no longer double-delays: a
  `skipNextBackoff` flag is set after waiting for Retry-After so the
  following iteration skips the standard exponential backoff.

Also removes the unused `yamlStringify` import from setup.ts.

Issues #1/#2/#3/#9/#10 were already correctly addressed in previous commits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
toancong pushed a commit to toancong/agent-orchestrator that referenced this pull request Mar 31, 2026
- Fix shell injection in writeShellExport: use single-quoted token with
  escaped embedded single quotes instead of double-quoted interpolation
- Fix board scanner initial setTimeout not cleared on stop: store the
  timeout handle and clear it in the stop handler
- Fix openclaw-probe test asserting deliver:true when code sends false
- Fix Discord notifier thread_id test to check URL query param instead
  of body, and remove redundant thread_id from post() body payload
toancong pushed a commit to toancong/agent-orchestrator that referenced this pull request Mar 31, 2026
- Store board scanner initial setTimeout and clear on stop
- Fix deliver assertion in openclaw-probe test (true -> false)
- Fix Discord thread_id test to check URL query param, not body
- Use yaml Document API to preserve comments in config writes
- Fix setup tests to match actual nonInteractiveSetup behavior
  (skips validation, auto-generates missing tokens)
toancong pushed a commit to toancong/agent-orchestrator that referenced this pull request Mar 31, 2026
Fixes all 12 issues identified in the Cursor Bugbot review:

ComposioHQ#4 – Setup tests now assert non-interactive mode skips validation and
  auto-generates tokens; removed incorrect validateToken call expectations.

ComposioHQ#5 – Replaced module-level mutable `tsFailures` in doctor.ts with a
  `makeFailCounter()` closure that is local to each command invocation,
  eliminating potential state bleed between invocations.

ComposioHQ#6 – Both `notify`, `notifyWithActions`, and `post` in notifier-discord
  now consistently guard on `effectiveUrl` (which includes thread_id),
  not on the raw `webhookUrl`. Removes non-null assertions.

ComposioHQ#7/ComposioHQ#12 – setup.ts now writes `${OPENCLAW_HOOKS_TOKEN}` as the token
  value in the YAML config instead of the raw token, so credentials are
  never committed to version control. setup.test.ts already expected this
  placeholder; the test was correct, the code was not.

ComposioHQ#8 – `ao_batch_spawn` follow-up setTimeout handles are tracked in
  `batchSpawnFollowUpTimeouts[]` and cleared when the health service stops,
  preventing timer leaks after plugin shutdown.

ComposioHQ#11 – Discord 429 Retry-After handling no longer double-delays: a
  `skipNextBackoff` flag is set after waiting for Retry-After so the
  following iteration skips the standard exponential backoff.

Also removes the unused `yamlStringify` import from setup.ts.

Issues ComposioHQ#1/ComposioHQ#2/ComposioHQ#3/ComposioHQ#9/ComposioHQ#10 were already correctly addressed in previous commits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
zendext pushed a commit to zendext/agent-orchestrator that referenced this pull request Apr 21, 2026
- Fix shell injection in writeShellExport: use single-quoted token with
  escaped embedded single quotes instead of double-quoted interpolation
- Fix board scanner initial setTimeout not cleared on stop: store the
  timeout handle and clear it in the stop handler
- Fix openclaw-probe test asserting deliver:true when code sends false
- Fix Discord notifier thread_id test to check URL query param instead
  of body, and remove redundant thread_id from post() body payload
zendext pushed a commit to zendext/agent-orchestrator that referenced this pull request Apr 21, 2026
- Store board scanner initial setTimeout and clear on stop
- Fix deliver assertion in openclaw-probe test (true -> false)
- Fix Discord thread_id test to check URL query param, not body
- Use yaml Document API to preserve comments in config writes
- Fix setup tests to match actual nonInteractiveSetup behavior
  (skips validation, auto-generates missing tokens)
zendext pushed a commit to zendext/agent-orchestrator that referenced this pull request Apr 21, 2026
Fixes all 12 issues identified in the Cursor Bugbot review:

ComposioHQ#4 – Setup tests now assert non-interactive mode skips validation and
  auto-generates tokens; removed incorrect validateToken call expectations.

ComposioHQ#5 – Replaced module-level mutable `tsFailures` in doctor.ts with a
  `makeFailCounter()` closure that is local to each command invocation,
  eliminating potential state bleed between invocations.

ComposioHQ#6 – Both `notify`, `notifyWithActions`, and `post` in notifier-discord
  now consistently guard on `effectiveUrl` (which includes thread_id),
  not on the raw `webhookUrl`. Removes non-null assertions.

ComposioHQ#7/ComposioHQ#12 – setup.ts now writes `${OPENCLAW_HOOKS_TOKEN}` as the token
  value in the YAML config instead of the raw token, so credentials are
  never committed to version control. setup.test.ts already expected this
  placeholder; the test was correct, the code was not.

ComposioHQ#8 – `ao_batch_spawn` follow-up setTimeout handles are tracked in
  `batchSpawnFollowUpTimeouts[]` and cleared when the health service stops,
  preventing timer leaks after plugin shutdown.

ComposioHQ#11 – Discord 429 Retry-After handling no longer double-delays: a
  `skipNextBackoff` flag is set after waiting for Retry-After so the
  following iteration skips the standard exponential backoff.

Also removes the unused `yamlStringify` import from setup.ts.

Issues #1/#2/ComposioHQ#3/ComposioHQ#9/ComposioHQ#10 were already correctly addressed in previous commits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
zendext pushed a commit to zendext/agent-orchestrator that referenced this pull request Apr 21, 2026
…gration

feat: OpenClaw plugin, AO skill, Discord notifier, and setup wizard
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants