@orgloop/connector-openclaw 0.1.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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OrgLoop contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # @orgloop/connector-openclaw
2
+
3
+ Delivers OrgLoop events to an OpenClaw agent via HTTP webhook. This is a **target-only** connector (actor) -- it sends events outbound, it does not receive them.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @orgloop/connector-openclaw
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ ```yaml
14
+ actors:
15
+ - id: openclaw-agent
16
+ connector: "@orgloop/connector-openclaw"
17
+ config:
18
+ base_url: "http://127.0.0.1:18789" # OpenClaw API base URL (default)
19
+ agent_id: "my-agent" # optional — target agent identifier
20
+ auth_token_env: "${OPENCLAW_TOKEN}" # optional — bearer token (env var ref)
21
+ default_channel: "engineering" # optional — default delivery channel
22
+ default_to: "team-lead" # optional — default recipient
23
+ ```
24
+
25
+ ### Config options
26
+
27
+ | Field | Type | Required | Default | Description |
28
+ |-------|------|----------|---------|-------------|
29
+ | `base_url` | `string` | no | `http://127.0.0.1:18789` | OpenClaw API base URL |
30
+ | `agent_id` | `string` | no | — | Target agent identifier |
31
+ | `auth_token_env` | `string` | no | — | Bearer token for auth. Supports `${ENV_VAR}` syntax |
32
+ | `default_channel` | `string` | no | — | Default channel for message delivery |
33
+ | `default_to` | `string` | no | — | Default recipient for message delivery |
34
+
35
+ ## Events accepted
36
+
37
+ This connector accepts any OrgLoop event type and delivers it to the OpenClaw `/hooks/agent` endpoint.
38
+
39
+ ### Delivery payload
40
+
41
+ The connector builds a message string from the event and sends it as:
42
+
43
+ ```json
44
+ {
45
+ "message": "[github:my-org/repo] resource.changed (pull_request.merged) by alice | action, pr_title, pr_number\n\nReview this PR",
46
+ "sessionKey": "hook:github:pr-review:engineering",
47
+ "agentId": "my-agent",
48
+ "wakeMode": "now",
49
+ "deliver": true,
50
+ "channel": "engineering",
51
+ "to": "team-lead"
52
+ }
53
+ ```
54
+
55
+ ### Route delivery config
56
+
57
+ These fields can be set in the route's `then.config`:
58
+
59
+ | Field | Type | Default | Description |
60
+ |-------|------|---------|-------------|
61
+ | `session_key` | `string` | `orgloop:<source>:<type>` | OpenClaw session key |
62
+ | `wake_mode` | `string` | `"now"` | When to wake the agent (`"now"`, etc.) |
63
+ | `deliver` | `boolean` | `false` | Whether to deliver the message to a channel |
64
+ | `launch_prompt` | `string` | — | Resolved from route's `with.prompt_file`; appended to the message |
65
+
66
+ ## Example route
67
+
68
+ ```yaml
69
+ routes:
70
+ - name: pr-merged-wake-agent
71
+ when:
72
+ source: github-eng
73
+ events:
74
+ - resource.changed
75
+ filter:
76
+ provenance.platform_event: pull_request.merged
77
+ then:
78
+ actor: openclaw-agent
79
+ config:
80
+ session_key: "hook:github:pr-merged:engineering"
81
+ wake_mode: "now"
82
+ deliver: true
83
+ with:
84
+ prompt_file: sops/review-merged-pr.md
85
+ ```
86
+
87
+ ## Auth / prerequisites
88
+
89
+ - An **OpenClaw instance** running and reachable at the configured `base_url`.
90
+ - If auth is enabled on the OpenClaw instance, set a bearer token as an environment variable and reference it via `auth_token_env`.
91
+
92
+ ## Limitations / known issues
93
+
94
+ - **Message format is fixed** -- The connector builds a single-string message from the event's source, type, provenance, and top-3 payload keys. It does not forward the full structured event.
95
+ - **No retry logic** -- The connector reports `error` status on 429/5xx responses and `rejected` on 4xx, but does not retry internally. Retries are handled by the OrgLoop delivery pipeline if configured on the route.
96
+ - **Local default** -- The default `base_url` points to `127.0.0.1:18789` which assumes OpenClaw is running locally.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=detector.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/detector.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,62 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { OpenClawServiceDetector } from '../detector.js';
3
+ describe('OpenClawServiceDetector', () => {
4
+ let detector;
5
+ let fetchMock;
6
+ beforeEach(() => {
7
+ detector = new OpenClawServiceDetector('http://localhost:18789');
8
+ fetchMock = vi.fn();
9
+ vi.stubGlobal('fetch', fetchMock);
10
+ });
11
+ afterEach(() => {
12
+ vi.restoreAllMocks();
13
+ });
14
+ it('returns running when service responds', async () => {
15
+ fetchMock.mockResolvedValueOnce({
16
+ ok: true,
17
+ status: 200,
18
+ headers: new Headers({ server: 'openclaw/1.2.3' }),
19
+ });
20
+ const result = await detector.detect();
21
+ expect(result.running).toBe(true);
22
+ expect(result.endpoint).toBe('http://localhost:18789');
23
+ expect(result.details?.status).toBe(200);
24
+ expect(result.details?.server).toBe('openclaw/1.2.3');
25
+ });
26
+ it('returns running even on 404 (service is there)', async () => {
27
+ fetchMock.mockResolvedValueOnce({
28
+ ok: false,
29
+ status: 404,
30
+ headers: new Headers({}),
31
+ });
32
+ const result = await detector.detect();
33
+ expect(result.running).toBe(true);
34
+ expect(result.details?.status).toBe(404);
35
+ });
36
+ it('returns not running on connection refused', async () => {
37
+ fetchMock.mockRejectedValueOnce(new Error('connect ECONNREFUSED 127.0.0.1:18789'));
38
+ const result = await detector.detect();
39
+ expect(result.running).toBe(false);
40
+ expect(result.endpoint).toBe('http://localhost:18789');
41
+ expect(result.details?.error).toContain('ECONNREFUSED');
42
+ });
43
+ it('returns not running on timeout', async () => {
44
+ const abortError = new DOMException('The operation was aborted', 'AbortError');
45
+ fetchMock.mockRejectedValueOnce(abortError);
46
+ const result = await detector.detect();
47
+ expect(result.running).toBe(false);
48
+ expect(result.details?.error).toContain('timed out');
49
+ });
50
+ it('uses default base URL', () => {
51
+ const defaultDetector = new OpenClawServiceDetector();
52
+ // The constructor sets the URL; we verify by calling detect
53
+ fetchMock.mockResolvedValueOnce({
54
+ ok: true,
55
+ status: 200,
56
+ headers: new Headers({}),
57
+ });
58
+ defaultDetector.detect();
59
+ expect(fetchMock).toHaveBeenCalledWith('http://127.0.0.1:18789', expect.anything());
60
+ });
61
+ });
62
+ //# sourceMappingURL=detector.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.test.js","sourceRoot":"","sources":["../../src/__tests__/detector.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACxC,IAAI,QAAiC,CAAC;IACtC,IAAI,SAAmC,CAAC;IAExC,UAAU,CAAC,GAAG,EAAE;QACf,QAAQ,GAAG,IAAI,uBAAuB,CAAC,wBAAwB,CAAC,CAAC;QACjE,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACpB,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,eAAe,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACtD,SAAS,CAAC,qBAAqB,CAAC;YAC/B,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,IAAI,OAAO,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;SAClD,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC;QAEvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACvD,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC/D,SAAS,CAAC,qBAAqB,CAAC;YAC/B,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,IAAI,OAAO,CAAC,EAAE,CAAC;SACxB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC;QAEvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QAC1D,SAAS,CAAC,qBAAqB,CAAC,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC,CAAC;QAEnF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC;QAEvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACvD,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,UAAU,GAAG,IAAI,YAAY,CAAC,2BAA2B,EAAE,YAAY,CAAC,CAAC;QAC/E,SAAS,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC;QAE5C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC;QAEvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAChC,MAAM,eAAe,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACtD,4DAA4D;QAC5D,SAAS,CAAC,qBAAqB,CAAC;YAC/B,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,IAAI,OAAO,CAAC,EAAE,CAAC;SACxB,CAAC,CAAC;QAEH,eAAe,CAAC,MAAM,EAAE,CAAC;QAEzB,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,wBAAwB,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=target.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/target.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,161 @@
1
+ import { createTestEvent } from '@orgloop/sdk';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { OpenClawTarget } from '../target.js';
4
+ describe('OpenClawTarget', () => {
5
+ let target;
6
+ let fetchMock;
7
+ beforeEach(async () => {
8
+ target = new OpenClawTarget();
9
+ await target.init({
10
+ id: 'openclaw-engineering-agent',
11
+ connector: '@orgloop/connector-openclaw',
12
+ config: {
13
+ base_url: 'http://localhost:18789',
14
+ agent_id: 'test-agent',
15
+ },
16
+ });
17
+ fetchMock = vi.fn().mockResolvedValue({
18
+ ok: true,
19
+ status: 200,
20
+ statusText: 'OK',
21
+ });
22
+ vi.stubGlobal('fetch', fetchMock);
23
+ });
24
+ afterEach(() => {
25
+ vi.restoreAllMocks();
26
+ });
27
+ it('sends correct OpenClaw payload shape', async () => {
28
+ const event = createTestEvent({
29
+ source: 'github',
30
+ type: 'resource.changed',
31
+ provenance: {
32
+ platform: 'github',
33
+ platform_event: 'pull_request.review_submitted',
34
+ author: 'alice',
35
+ author_type: 'team_member',
36
+ },
37
+ payload: { pr_number: 42 },
38
+ });
39
+ const routeConfig = {
40
+ session_key: 'hook:github:pr-review:engineering',
41
+ wake_mode: 'now',
42
+ deliver: true,
43
+ launch_prompt: 'Review this PR',
44
+ };
45
+ const result = await target.deliver(event, routeConfig);
46
+ expect(result.status).toBe('delivered');
47
+ expect(fetchMock).toHaveBeenCalledOnce();
48
+ const [url, opts] = fetchMock.mock.calls[0];
49
+ expect(url).toBe('http://localhost:18789/hooks/agent');
50
+ const body = JSON.parse(opts.body);
51
+ // Assert correct fields present
52
+ expect(body).toHaveProperty('message');
53
+ expect(body).toHaveProperty('sessionKey', 'hook:github:pr-review:engineering');
54
+ expect(body).toHaveProperty('agentId', 'test-agent');
55
+ expect(body).toHaveProperty('wakeMode', 'now');
56
+ expect(body).toHaveProperty('deliver', true);
57
+ // Assert old fields are NOT present
58
+ expect(body).not.toHaveProperty('event');
59
+ expect(body).not.toHaveProperty('launch_prompt');
60
+ expect(body).not.toHaveProperty('agent_id');
61
+ // Assert message contains event context and payload values
62
+ expect(body.message).toContain('github');
63
+ expect(body.message).toContain('resource.changed');
64
+ expect(body.message).toContain('Review this PR');
65
+ expect(body.message).toContain('pr_number: 42');
66
+ expect(body.message).toContain('Instructions:');
67
+ });
68
+ it('generates fallback sessionKey from event', async () => {
69
+ const event = createTestEvent({
70
+ source: 'linear',
71
+ type: 'resource.changed',
72
+ });
73
+ const routeConfig = {};
74
+ await target.deliver(event, routeConfig);
75
+ const body = JSON.parse(fetchMock.mock.calls[0][1].body);
76
+ expect(body.sessionKey).toBe('orgloop:linear:resource.changed');
77
+ });
78
+ it('includes platform_event and author in message', async () => {
79
+ const event = createTestEvent({
80
+ source: 'github',
81
+ type: 'resource.changed',
82
+ provenance: {
83
+ platform: 'github',
84
+ platform_event: 'pull_request.merged',
85
+ author: 'bob',
86
+ author_type: 'team_member',
87
+ },
88
+ });
89
+ await target.deliver(event, {});
90
+ const body = JSON.parse(fetchMock.mock.calls[0][1].body);
91
+ expect(body.message).toContain('pull_request.merged');
92
+ expect(body.message).toContain('bob');
93
+ });
94
+ it('includes full payload values and provenance context in message', async () => {
95
+ const event = createTestEvent({
96
+ source: 'linear',
97
+ type: 'resource.changed',
98
+ provenance: {
99
+ platform: 'linear',
100
+ platform_event: 'comment.created',
101
+ author: 'Alice',
102
+ author_type: 'team_member',
103
+ issue_id: 'ENG-42',
104
+ url: 'https://linear.app/team/issue/ENG-42#comment-abc',
105
+ },
106
+ payload: {
107
+ action: 'comment_created',
108
+ issue_id: 'ENG-42',
109
+ issue_title: 'Fix the widget',
110
+ comment_body: 'This needs a different approach — see the RFC.',
111
+ },
112
+ });
113
+ await target.deliver(event, { launch_prompt: 'Handle this Linear activity' });
114
+ const body = JSON.parse(fetchMock.mock.calls[0][1].body);
115
+ const msg = body.message;
116
+ // Header
117
+ expect(msg).toContain('[linear] resource.changed (comment.created) by Alice');
118
+ // Provenance context (url, issue_id — not platform/author which are in header)
119
+ expect(msg).toContain('issue_id: ENG-42');
120
+ expect(msg).toContain('url: https://linear.app/team/issue/ENG-42#comment-abc');
121
+ // Payload values — the actual data the LLM needs
122
+ expect(msg).toContain('issue_title: Fix the widget');
123
+ expect(msg).toContain('comment_body: This needs a different approach — see the RFC.');
124
+ // Instructions
125
+ expect(msg).toContain('Handle this Linear activity');
126
+ });
127
+ it('defaults wakeMode to "now"', async () => {
128
+ const event = createTestEvent();
129
+ await target.deliver(event, {});
130
+ const body = JSON.parse(fetchMock.mock.calls[0][1].body);
131
+ expect(body.wakeMode).toBe('now');
132
+ });
133
+ it('defaults deliver to false', async () => {
134
+ const event = createTestEvent();
135
+ await target.deliver(event, {});
136
+ const body = JSON.parse(fetchMock.mock.calls[0][1].body);
137
+ expect(body.deliver).toBe(false);
138
+ });
139
+ it('handles 429 rate limit', async () => {
140
+ fetchMock.mockResolvedValueOnce({
141
+ ok: false,
142
+ status: 429,
143
+ statusText: 'Too Many Requests',
144
+ });
145
+ const event = createTestEvent();
146
+ const result = await target.deliver(event, {});
147
+ expect(result.status).toBe('error');
148
+ expect(result.error?.message).toContain('429');
149
+ });
150
+ it('handles 4xx rejection', async () => {
151
+ fetchMock.mockResolvedValueOnce({
152
+ ok: false,
153
+ status: 400,
154
+ statusText: 'Bad Request',
155
+ });
156
+ const event = createTestEvent();
157
+ const result = await target.deliver(event, {});
158
+ expect(result.status).toBe('rejected');
159
+ });
160
+ });
161
+ //# sourceMappingURL=target.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target.test.js","sourceRoot":"","sources":["../../src/__tests__/target.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/validator.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,72 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { OpenClawCredentialValidator } from '../validator.js';
3
+ describe('OpenClawCredentialValidator', () => {
4
+ let validator;
5
+ let fetchMock;
6
+ beforeEach(() => {
7
+ validator = new OpenClawCredentialValidator('http://localhost:18789');
8
+ fetchMock = vi.fn();
9
+ vi.stubGlobal('fetch', fetchMock);
10
+ });
11
+ afterEach(() => {
12
+ vi.restoreAllMocks();
13
+ });
14
+ it('returns valid on successful response', async () => {
15
+ fetchMock.mockResolvedValueOnce({
16
+ ok: true,
17
+ status: 200,
18
+ });
19
+ const result = await validator.validate('test-token');
20
+ expect(result.valid).toBe(true);
21
+ expect(fetchMock).toHaveBeenCalledWith('http://localhost:18789/hooks/agent', expect.objectContaining({
22
+ method: 'POST',
23
+ headers: expect.objectContaining({
24
+ Authorization: 'Bearer test-token',
25
+ }),
26
+ }));
27
+ });
28
+ it('returns invalid on 401', async () => {
29
+ fetchMock.mockResolvedValueOnce({
30
+ ok: false,
31
+ status: 401,
32
+ statusText: 'Unauthorized',
33
+ });
34
+ const result = await validator.validate('bad-token');
35
+ expect(result.valid).toBe(false);
36
+ expect(result.error).toContain('401');
37
+ });
38
+ it('returns invalid on 403', async () => {
39
+ fetchMock.mockResolvedValueOnce({
40
+ ok: false,
41
+ status: 403,
42
+ statusText: 'Forbidden',
43
+ });
44
+ const result = await validator.validate('bad-token');
45
+ expect(result.valid).toBe(false);
46
+ expect(result.error).toContain('403');
47
+ });
48
+ it('returns valid on other errors (token accepted but other issue)', async () => {
49
+ fetchMock.mockResolvedValueOnce({
50
+ ok: false,
51
+ status: 400,
52
+ statusText: 'Bad Request',
53
+ });
54
+ const result = await validator.validate('good-token');
55
+ // Not a 401/403, so the token was accepted
56
+ expect(result.valid).toBe(true);
57
+ });
58
+ it('fails open on network error', async () => {
59
+ fetchMock.mockRejectedValueOnce(new Error('connect ECONNREFUSED'));
60
+ const result = await validator.validate('any-token');
61
+ expect(result.valid).toBe(true);
62
+ expect(result.error).toContain('Could not reach OpenClaw');
63
+ });
64
+ it('fails open on timeout', async () => {
65
+ const abortError = new DOMException('The operation was aborted', 'AbortError');
66
+ fetchMock.mockRejectedValueOnce(abortError);
67
+ const result = await validator.validate('any-token');
68
+ expect(result.valid).toBe(true);
69
+ expect(result.error).toContain('timed out');
70
+ });
71
+ });
72
+ //# sourceMappingURL=validator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.test.js","sourceRoot":"","sources":["../../src/__tests__/validator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * OpenClaw service detector — checks if OpenClaw is running and reachable.
3
+ *
4
+ * Stage 2 connector maturity: used by `orgloop doctor` to report
5
+ * whether the OpenClaw service is available at the expected endpoint.
6
+ */
7
+ import type { ServiceDetector } from '@orgloop/sdk';
8
+ export declare class OpenClawServiceDetector implements ServiceDetector {
9
+ private baseUrl;
10
+ constructor(baseUrl?: string);
11
+ detect(): Promise<{
12
+ running: boolean;
13
+ version?: string;
14
+ endpoint?: string;
15
+ details?: Record<string, unknown>;
16
+ }>;
17
+ }
18
+ //# sourceMappingURL=detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAIpD,qBAAa,uBAAwB,YAAW,eAAe;IAC9D,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,CAAC,EAAE,MAAM;IAItB,MAAM,IAAI,OAAO,CAAC;QACvB,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,CAAC;CAgDF"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * OpenClaw service detector — checks if OpenClaw is running and reachable.
3
+ *
4
+ * Stage 2 connector maturity: used by `orgloop doctor` to report
5
+ * whether the OpenClaw service is available at the expected endpoint.
6
+ */
7
+ const DEFAULT_BASE_URL = 'http://127.0.0.1:18789';
8
+ export class OpenClawServiceDetector {
9
+ baseUrl;
10
+ constructor(baseUrl) {
11
+ this.baseUrl = baseUrl ?? DEFAULT_BASE_URL;
12
+ }
13
+ async detect() {
14
+ try {
15
+ const controller = new AbortController();
16
+ const timeout = setTimeout(() => controller.abort(), 3000);
17
+ const response = await fetch(this.baseUrl, {
18
+ method: 'GET',
19
+ headers: {
20
+ 'User-Agent': 'orgloop-doctor',
21
+ },
22
+ signal: controller.signal,
23
+ });
24
+ clearTimeout(timeout);
25
+ // Any response (even 404) means the service is running
26
+ const details = {
27
+ status: response.status,
28
+ };
29
+ // Try to extract version from response headers or body
30
+ const serverHeader = response.headers.get('server');
31
+ if (serverHeader) {
32
+ details.server = serverHeader;
33
+ }
34
+ return {
35
+ running: true,
36
+ endpoint: this.baseUrl,
37
+ details,
38
+ };
39
+ }
40
+ catch (err) {
41
+ if (err instanceof DOMException && err.name === 'AbortError') {
42
+ return {
43
+ running: false,
44
+ endpoint: this.baseUrl,
45
+ details: { error: 'Connection timed out' },
46
+ };
47
+ }
48
+ return {
49
+ running: false,
50
+ endpoint: this.baseUrl,
51
+ details: {
52
+ error: err instanceof Error ? err.message : String(err),
53
+ },
54
+ };
55
+ }
56
+ }
57
+ }
58
+ //# sourceMappingURL=detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.js","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,gBAAgB,GAAG,wBAAwB,CAAC;AAElD,MAAM,OAAO,uBAAuB;IAC3B,OAAO,CAAS;IAExB,YAAY,OAAgB;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,MAAM;QAMX,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;YAE3D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE;gBAC1C,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE;oBACR,YAAY,EAAE,gBAAgB;iBAC9B;gBACD,MAAM,EAAE,UAAU,CAAC,MAAM;aACzB,CAAC,CAAC;YAEH,YAAY,CAAC,OAAO,CAAC,CAAC;YAEtB,uDAAuD;YACvD,MAAM,OAAO,GAA4B;gBACxC,MAAM,EAAE,QAAQ,CAAC,MAAM;aACvB,CAAC;YAEF,uDAAuD;YACvD,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACpD,IAAI,YAAY,EAAE,CAAC;gBAClB,OAAO,CAAC,MAAM,GAAG,YAAY,CAAC;YAC/B,CAAC;YAED,OAAO;gBACN,OAAO,EAAE,IAAI;gBACb,QAAQ,EAAE,IAAI,CAAC,OAAO;gBACtB,OAAO;aACP,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,GAAG,YAAY,YAAY,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC9D,OAAO;oBACN,OAAO,EAAE,KAAK;oBACd,QAAQ,EAAE,IAAI,CAAC,OAAO;oBACtB,OAAO,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE;iBAC1C,CAAC;YACH,CAAC;YACD,OAAO;gBACN,OAAO,EAAE,KAAK;gBACd,QAAQ,EAAE,IAAI,CAAC,OAAO;gBACtB,OAAO,EAAE;oBACR,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBACvD;aACD,CAAC;QACH,CAAC;IACF,CAAC;CACD"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @orgloop/connector-openclaw — OpenClaw actor (target) connector registration.
3
+ */
4
+ import type { ConnectorRegistration } from '@orgloop/sdk';
5
+ export default function register(): ConnectorRegistration;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAK1D,MAAM,CAAC,OAAO,UAAU,QAAQ,IAAI,qBAAqB,CAmBxD"}
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @orgloop/connector-openclaw — OpenClaw actor (target) connector registration.
3
+ */
4
+ import { OpenClawServiceDetector } from './detector.js';
5
+ import { OpenClawTarget } from './target.js';
6
+ import { OpenClawCredentialValidator } from './validator.js';
7
+ export default function register() {
8
+ return {
9
+ id: 'openclaw',
10
+ target: OpenClawTarget,
11
+ setup: {
12
+ env_vars: [
13
+ {
14
+ name: 'OPENCLAW_WEBHOOK_TOKEN',
15
+ description: 'OpenClaw webhook authentication token',
16
+ help_url: 'https://openclaw.com/docs/webhooks',
17
+ required: false,
18
+ },
19
+ ],
20
+ },
21
+ credential_validators: {
22
+ OPENCLAW_WEBHOOK_TOKEN: new OpenClawCredentialValidator(),
23
+ },
24
+ service_detector: new OpenClawServiceDetector(),
25
+ };
26
+ }
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAE7D,MAAM,CAAC,OAAO,UAAU,QAAQ;IAC/B,OAAO;QACN,EAAE,EAAE,UAAU;QACd,MAAM,EAAE,cAAc;QACtB,KAAK,EAAE;YACN,QAAQ,EAAE;gBACT;oBACC,IAAI,EAAE,wBAAwB;oBAC9B,WAAW,EAAE,uCAAuC;oBACpD,QAAQ,EAAE,oCAAoC;oBAC9C,QAAQ,EAAE,KAAK;iBACf;aACD;SACD;QACD,qBAAqB,EAAE;YACtB,sBAAsB,EAAE,IAAI,2BAA2B,EAAE;SACzD;QACD,gBAAgB,EAAE,IAAI,uBAAuB,EAAE;KAC/C,CAAC;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * OpenClaw actor (target) connector — delivers events to OpenClaw agent via HTTP webhook.
3
+ */
4
+ import type { ActorConfig, ActorConnector, DeliveryResult, OrgLoopEvent, RouteDeliveryConfig } from '@orgloop/sdk';
5
+ export declare class OpenClawTarget implements ActorConnector {
6
+ readonly id = "openclaw";
7
+ private baseUrl;
8
+ private authToken?;
9
+ private agentId?;
10
+ private defaultChannel?;
11
+ private defaultTo?;
12
+ init(config: ActorConfig): Promise<void>;
13
+ deliver(event: OrgLoopEvent, routeConfig: RouteDeliveryConfig): Promise<DeliveryResult>;
14
+ shutdown(): Promise<void>;
15
+ /**
16
+ * Build the message string for OpenClaw from an OrgLoop event.
17
+ *
18
+ * Structure:
19
+ * 1. Header line: [source] type (platform_event) by author
20
+ * 2. Event context: provenance fields (url, issue_id, etc.)
21
+ * 3. Event payload: the actual data (comment body, ticket title, etc.)
22
+ * 4. Instructions: launch_prompt from route config (SOP)
23
+ */
24
+ private buildMessage;
25
+ }
26
+ //# sourceMappingURL=target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target.d.ts","sourceRoot":"","sources":["../src/target.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACX,WAAW,EACX,cAAc,EACd,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,MAAM,cAAc,CAAC;AAuBtB,qBAAa,cAAe,YAAW,cAAc;IACpD,QAAQ,CAAC,EAAE,cAAc;IACzB,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,cAAc,CAAC,CAAS;IAChC,OAAO,CAAC,SAAS,CAAC,CAAS;IAErB,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxC,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;IA0DvF,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B;;;;;;;;OAQG;IACH,OAAO,CAAC,YAAY;CAwCpB"}
package/dist/target.js ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * OpenClaw actor (target) connector — delivers events to OpenClaw agent via HTTP webhook.
3
+ */
4
+ /** Resolve env var references like ${OPENCLAW_AUTH_TOKEN} */
5
+ function resolveEnvVar(value) {
6
+ const match = value.match(/^\$\{(.+)\}$/);
7
+ if (match) {
8
+ const envValue = process.env[match[1]];
9
+ if (!envValue) {
10
+ throw new Error(`Environment variable ${match[1]} is not set`);
11
+ }
12
+ return envValue;
13
+ }
14
+ return value;
15
+ }
16
+ export class OpenClawTarget {
17
+ id = 'openclaw';
18
+ baseUrl = 'http://127.0.0.1:18789';
19
+ authToken;
20
+ agentId;
21
+ defaultChannel;
22
+ defaultTo;
23
+ async init(config) {
24
+ const cfg = config.config;
25
+ this.baseUrl = cfg.base_url ?? 'http://127.0.0.1:18789';
26
+ this.agentId = cfg.agent_id;
27
+ this.defaultChannel = cfg.default_channel;
28
+ this.defaultTo = cfg.default_to;
29
+ if (cfg.auth_token_env) {
30
+ this.authToken = resolveEnvVar(cfg.auth_token_env);
31
+ }
32
+ }
33
+ async deliver(event, routeConfig) {
34
+ const url = `${this.baseUrl}/hooks/agent`;
35
+ const body = {
36
+ message: this.buildMessage(event, routeConfig),
37
+ sessionKey: routeConfig.session_key ?? `orgloop:${event.source}:${event.type}`,
38
+ agentId: this.agentId,
39
+ wakeMode: routeConfig.wake_mode ?? 'now',
40
+ deliver: routeConfig.deliver ?? false,
41
+ channel: this.defaultChannel,
42
+ to: this.defaultTo,
43
+ };
44
+ const headers = {
45
+ 'Content-Type': 'application/json',
46
+ };
47
+ if (this.authToken) {
48
+ headers.Authorization = `Bearer ${this.authToken}`;
49
+ }
50
+ try {
51
+ const response = await fetch(url, {
52
+ method: 'POST',
53
+ headers,
54
+ body: JSON.stringify(body),
55
+ });
56
+ if (response.ok) {
57
+ return { status: 'delivered' };
58
+ }
59
+ if (response.status === 429) {
60
+ // Rate limited — treat as error for retry
61
+ return {
62
+ status: 'error',
63
+ error: new Error('OpenClaw rate limited (429)'),
64
+ };
65
+ }
66
+ if (response.status >= 400 && response.status < 500) {
67
+ return {
68
+ status: 'rejected',
69
+ error: new Error(`OpenClaw rejected: ${response.status} ${response.statusText}`),
70
+ };
71
+ }
72
+ return {
73
+ status: 'error',
74
+ error: new Error(`OpenClaw error: ${response.status} ${response.statusText}`),
75
+ };
76
+ }
77
+ catch (err) {
78
+ return {
79
+ status: 'error',
80
+ error: err instanceof Error ? err : new Error(String(err)),
81
+ };
82
+ }
83
+ }
84
+ async shutdown() {
85
+ // Nothing to clean up
86
+ }
87
+ /**
88
+ * Build the message string for OpenClaw from an OrgLoop event.
89
+ *
90
+ * Structure:
91
+ * 1. Header line: [source] type (platform_event) by author
92
+ * 2. Event context: provenance fields (url, issue_id, etc.)
93
+ * 3. Event payload: the actual data (comment body, ticket title, etc.)
94
+ * 4. Instructions: launch_prompt from route config (SOP)
95
+ */
96
+ buildMessage(event, routeConfig) {
97
+ const sections = [];
98
+ // 1. Header line
99
+ const header = [`[${event.source}] ${event.type}`];
100
+ if (event.provenance.platform_event) {
101
+ header.push(`(${event.provenance.platform_event})`);
102
+ }
103
+ if (event.provenance.author) {
104
+ header.push(`by ${event.provenance.author}`);
105
+ }
106
+ sections.push(header.join(' '));
107
+ // 2. Event context from provenance (skip standard fields already in header)
108
+ const skipProvenance = new Set(['platform', 'platform_event', 'author', 'author_type']);
109
+ const contextEntries = Object.entries(event.provenance).filter(([k, v]) => !skipProvenance.has(k) && v !== undefined);
110
+ if (contextEntries.length > 0) {
111
+ const lines = contextEntries.map(([k, v]) => ` ${k}: ${v}`);
112
+ sections.push(`Context:\n${lines.join('\n')}`);
113
+ }
114
+ // 3. Event payload — the actual data the LLM needs to act on
115
+ const payloadEntries = Object.entries(event.payload);
116
+ if (payloadEntries.length > 0) {
117
+ const lines = payloadEntries.map(([k, v]) => {
118
+ const val = typeof v === 'string' ? v : JSON.stringify(v);
119
+ return ` ${k}: ${val}`;
120
+ });
121
+ sections.push(`Payload:\n${lines.join('\n')}`);
122
+ }
123
+ // 4. Instructions from route config
124
+ if (routeConfig.launch_prompt) {
125
+ sections.push(`Instructions:\n${routeConfig.launch_prompt}`);
126
+ }
127
+ return sections.join('\n\n');
128
+ }
129
+ }
130
+ //# sourceMappingURL=target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target.js","sourceRoot":"","sources":["../src/target.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,6DAA6D;AAC7D,SAAS,aAAa,CAAC,KAAa;IACnC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC1C,IAAI,KAAK,EAAE,CAAC;QACX,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;QAChE,CAAC;QACD,OAAO,QAAQ,CAAC;IACjB,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAUD,MAAM,OAAO,cAAc;IACjB,EAAE,GAAG,UAAU,CAAC;IACjB,OAAO,GAAG,wBAAwB,CAAC;IACnC,SAAS,CAAU;IACnB,OAAO,CAAU;IACjB,cAAc,CAAU;IACxB,SAAS,CAAU;IAE3B,KAAK,CAAC,IAAI,CAAC,MAAmB;QAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,MAAmC,CAAC;QACvD,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,QAAQ,IAAI,wBAAwB,CAAC;QACxD,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC;QAC5B,IAAI,CAAC,cAAc,GAAG,GAAG,CAAC,eAAe,CAAC;QAC1C,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC,UAAU,CAAC;QAEhC,IAAI,GAAG,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACpD,CAAC;IACF,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAAmB,EAAE,WAAgC;QAClE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,cAAc,CAAC;QAE1C,MAAM,IAAI,GAAG;YACZ,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,WAAW,CAAC;YAC9C,UAAU,EAAG,WAAW,CAAC,WAAsB,IAAI,WAAW,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE;YAC1F,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAG,WAAW,CAAC,SAAoB,IAAI,KAAK;YACpD,OAAO,EAAE,WAAW,CAAC,OAAO,IAAI,KAAK;YACrC,OAAO,EAAE,IAAI,CAAC,cAAc;YAC5B,EAAE,EAAE,IAAI,CAAC,SAAS;SAClB,CAAC;QAEF,MAAM,OAAO,GAA2B;YACvC,cAAc,EAAE,kBAAkB;SAClC,CAAC;QACF,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO,CAAC,aAAa,GAAG,UAAU,IAAI,CAAC,SAAS,EAAE,CAAC;QACpD,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBACjC,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC1B,CAAC,CAAC;YAEH,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;YAChC,CAAC;YAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC7B,0CAA0C;gBAC1C,OAAO;oBACN,MAAM,EAAE,OAAO;oBACf,KAAK,EAAE,IAAI,KAAK,CAAC,6BAA6B,CAAC;iBAC/C,CAAC;YACH,CAAC;YAED,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBACrD,OAAO;oBACN,MAAM,EAAE,UAAU;oBAClB,KAAK,EAAE,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;iBAChF,CAAC;YACH,CAAC;YAED,OAAO;gBACN,MAAM,EAAE,OAAO;gBACf,KAAK,EAAE,IAAI,KAAK,CAAC,mBAAmB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;aAC7E,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,OAAO;gBACN,MAAM,EAAE,OAAO;gBACf,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;aAC1D,CAAC;QACH,CAAC;IACF,CAAC;IAED,KAAK,CAAC,QAAQ;QACb,sBAAsB;IACvB,CAAC;IAED;;;;;;;;OAQG;IACK,YAAY,CAAC,KAAmB,EAAE,WAAgC;QACzE,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,iBAAiB;QACjB,MAAM,MAAM,GAAa,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7D,IAAI,KAAK,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,cAAc,GAAG,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9C,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAEhC,4EAA4E;QAC5E,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC;QACxF,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM,CAC7D,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,SAAS,CACrD,CAAC;QACF,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC7D,QAAQ,CAAC,IAAI,CAAC,aAAa,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChD,CAAC;QAED,6DAA6D;QAC7D,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACrD,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;gBAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;gBAC1D,OAAO,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC;YACzB,CAAC,CAAC,CAAC;YACH,QAAQ,CAAC,IAAI,CAAC,aAAa,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChD,CAAC;QAED,oCAAoC;QACpC,IAAI,WAAW,CAAC,aAAa,EAAE,CAAC;YAC/B,QAAQ,CAAC,IAAI,CAAC,kBAAkB,WAAW,CAAC,aAAa,EAAE,CAAC,CAAC;QAC9D,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;CACD"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * OpenClaw credential validator — probes the OpenClaw endpoint to verify a token works.
3
+ *
4
+ * Stage 2 connector maturity: validates that OPENCLAW_WEBHOOK_TOKEN can
5
+ * authenticate against the OpenClaw webhook endpoint.
6
+ */
7
+ import type { CredentialValidator } from '@orgloop/sdk';
8
+ export declare class OpenClawCredentialValidator implements CredentialValidator {
9
+ private baseUrl;
10
+ constructor(baseUrl?: string);
11
+ validate(value: string): Promise<{
12
+ valid: boolean;
13
+ identity?: string;
14
+ scopes?: string[];
15
+ error?: string;
16
+ }>;
17
+ }
18
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAIxD,qBAAa,2BAA4B,YAAW,mBAAmB;IACtE,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,CAAC,EAAE,MAAM;IAItB,QAAQ,CACb,KAAK,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAuCpF"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * OpenClaw credential validator — probes the OpenClaw endpoint to verify a token works.
3
+ *
4
+ * Stage 2 connector maturity: validates that OPENCLAW_WEBHOOK_TOKEN can
5
+ * authenticate against the OpenClaw webhook endpoint.
6
+ */
7
+ const DEFAULT_BASE_URL = 'http://127.0.0.1:18789';
8
+ export class OpenClawCredentialValidator {
9
+ baseUrl;
10
+ constructor(baseUrl) {
11
+ this.baseUrl = baseUrl ?? DEFAULT_BASE_URL;
12
+ }
13
+ async validate(value) {
14
+ try {
15
+ const controller = new AbortController();
16
+ const timeout = setTimeout(() => controller.abort(), 3000);
17
+ const response = await fetch(`${this.baseUrl}/hooks/agent`, {
18
+ method: 'POST',
19
+ headers: {
20
+ Authorization: `Bearer ${value}`,
21
+ 'Content-Type': 'application/json',
22
+ 'User-Agent': 'orgloop-doctor',
23
+ },
24
+ body: JSON.stringify({ message: 'orgloop-doctor-probe', sessionKey: 'doctor-probe' }),
25
+ signal: controller.signal,
26
+ });
27
+ clearTimeout(timeout);
28
+ if (response.status === 401 || response.status === 403) {
29
+ return { valid: false, error: `Invalid token (${response.status})` };
30
+ }
31
+ // Any other response means the token was accepted (or not required)
32
+ return { valid: true };
33
+ }
34
+ catch (err) {
35
+ if (err instanceof DOMException && err.name === 'AbortError') {
36
+ return {
37
+ valid: true,
38
+ error: 'Validation timed out (OpenClaw may not be running)',
39
+ };
40
+ }
41
+ // Fail-open: network errors (e.g., connection refused) mean OpenClaw isn't running,
42
+ // but we can't say the token is invalid
43
+ return {
44
+ valid: true,
45
+ error: `Could not reach OpenClaw: ${err instanceof Error ? err.message : String(err)}`,
46
+ };
47
+ }
48
+ }
49
+ }
50
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,gBAAgB,GAAG,wBAAwB,CAAC;AAElD,MAAM,OAAO,2BAA2B;IAC/B,OAAO,CAAS;IAExB,YAAY,OAAgB;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,QAAQ,CACb,KAAa;QAEb,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;YAE3D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,cAAc,EAAE;gBAC3D,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACR,aAAa,EAAE,UAAU,KAAK,EAAE;oBAChC,cAAc,EAAE,kBAAkB;oBAClC,YAAY,EAAE,gBAAgB;iBAC9B;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC;gBACrF,MAAM,EAAE,UAAU,CAAC,MAAM;aACzB,CAAC,CAAC;YAEH,YAAY,CAAC,OAAO,CAAC,CAAC;YAEtB,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACxD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,kBAAkB,QAAQ,CAAC,MAAM,GAAG,EAAE,CAAC;YACtE,CAAC;YAED,oEAAoE;YACpE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACxB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,GAAG,YAAY,YAAY,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC9D,OAAO;oBACN,KAAK,EAAE,IAAI;oBACX,KAAK,EAAE,oDAAoD;iBAC3D,CAAC;YACH,CAAC;YACD,oFAAoF;YACpF,wCAAwC;YACxC,OAAO;gBACN,KAAK,EAAE,IAAI;gBACX,KAAK,EAAE,6BAA6B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;aACtF,CAAC;QACH,CAAC;IACF,CAAC;CACD"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@orgloop/connector-openclaw",
3
+ "version": "0.1.0",
4
+ "description": "OrgLoop OpenClaw connector — webhook target for agent waking",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "dependencies": {
9
+ "@orgloop/sdk": "0.1.0"
10
+ },
11
+ "orgloop": {
12
+ "type": "connector",
13
+ "provides": [
14
+ "target"
15
+ ],
16
+ "id": "openclaw"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "license": "MIT",
25
+ "scripts": {
26
+ "build": "tsc",
27
+ "clean": "rm -rf dist",
28
+ "typecheck": "tsc --noEmit",
29
+ "test": "vitest run"
30
+ }
31
+ }