@godman-protocols/signal 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +156 -0
- package/README.md +189 -0
- package/dist/bus.d.ts +53 -0
- package/dist/bus.d.ts.map +1 -0
- package/dist/bus.js +141 -0
- package/dist/bus.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +50 -0
- package/src/bus.ts +180 -0
- package/src/index.ts +29 -0
- package/src/types.ts +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship made available under
|
|
36
|
+
the License, as indicated by a copyright notice that is included in
|
|
37
|
+
or attached to the work (an example is provided in the Appendix below).
|
|
38
|
+
|
|
39
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
40
|
+
form, that is based on (or derived from) the Work and for which the
|
|
41
|
+
editorial revisions, annotations, elaborations, or other transformations
|
|
42
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
43
|
+
of this License, Derivative Works shall not include works that remain
|
|
44
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
45
|
+
the Work and Derivative Works thereof.
|
|
46
|
+
|
|
47
|
+
"Contribution" shall mean, as submitted to the Licensor for inclusion
|
|
48
|
+
in the Work by the copyright owner or by an individual or Legal Entity
|
|
49
|
+
authorized to submit on behalf of the copyright owner.
|
|
50
|
+
|
|
51
|
+
"Contributor" shall mean Licensor and any Legal Entity on behalf of
|
|
52
|
+
whom a Contribution has been received by the Licensor and included
|
|
53
|
+
within the Work.
|
|
54
|
+
|
|
55
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
56
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
57
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
58
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
59
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
60
|
+
Work and such Derivative Works in Source or Object form.
|
|
61
|
+
|
|
62
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
63
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
64
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
65
|
+
(except as stated in this section) patent license to make, have made,
|
|
66
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
67
|
+
where such license applies only to those patent claims licensable
|
|
68
|
+
by such Contributor that are necessarily infringed by their
|
|
69
|
+
Contribution(s) alone or by the combination of their Contribution(s)
|
|
70
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
71
|
+
institute patent litigation against any entity (including a cross-claim
|
|
72
|
+
or counterclaim in a lawsuit) alleging that the Work or any other
|
|
73
|
+
Contribution embodied in the Work constitutes patent or contributory
|
|
74
|
+
patent infringement, then any patent licenses granted to You under
|
|
75
|
+
this License for that Work shall terminate as of the date such
|
|
76
|
+
litigation is filed.
|
|
77
|
+
|
|
78
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
79
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
80
|
+
modifications, and in Source or Object form, provided that You
|
|
81
|
+
meet the following conditions:
|
|
82
|
+
|
|
83
|
+
(a) You must give any other recipients of the Work or Derivative
|
|
84
|
+
Works a copy of this License; and
|
|
85
|
+
|
|
86
|
+
(b) You must cause any modified files to carry prominent notices
|
|
87
|
+
stating that You changed the files; and
|
|
88
|
+
|
|
89
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
90
|
+
that You distribute, all copyright, patent, trademark, and
|
|
91
|
+
attribution notices from the Source form of the Work,
|
|
92
|
+
excluding those notices that do not pertain to any part of
|
|
93
|
+
the Derivative Works; and
|
|
94
|
+
|
|
95
|
+
(d) If the Work includes a "NOTICE" text file, You must include a
|
|
96
|
+
readable copy of the attribution notices contained within such
|
|
97
|
+
NOTICE file, as part of the Derivative Works and at least one
|
|
98
|
+
of the following places: within a NOTICE text file distributed
|
|
99
|
+
as part of the Derivative Works; within the Source form or
|
|
100
|
+
documentation, if provided along with the Derivative Works; or,
|
|
101
|
+
within a display generated by the Derivative Works, if and
|
|
102
|
+
wherever such third-party notices normally appear.
|
|
103
|
+
|
|
104
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
105
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
106
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
107
|
+
this License, without any additional terms or conditions.
|
|
108
|
+
|
|
109
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
110
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
111
|
+
except as required for reasonable and customary use in describing the
|
|
112
|
+
origin of the Work.
|
|
113
|
+
|
|
114
|
+
7. Disclaimer of Warranty. Unless required by applicable law or agreed
|
|
115
|
+
to in writing, Licensor provides the Work on an "AS IS" BASIS,
|
|
116
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
117
|
+
implied, including, without limitation, any warranties or conditions
|
|
118
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
119
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
120
|
+
appropriateness of using or reproducing the Work and assume any
|
|
121
|
+
risks associated with Your exercise of permissions under this License.
|
|
122
|
+
|
|
123
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
124
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
125
|
+
unless required by applicable law (such as deliberate and grossly
|
|
126
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
127
|
+
liable to You for damages, including any direct, indirect, special,
|
|
128
|
+
incidental, or exemplary damages of any character arising as a
|
|
129
|
+
result of this License or out of the use or inability to use the
|
|
130
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
131
|
+
work stoppage, computer failure or malfunction, or all other
|
|
132
|
+
commercial damages or losses), even if such Contributor has been
|
|
133
|
+
advised of the possibility of such damages.
|
|
134
|
+
|
|
135
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
136
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
137
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
138
|
+
or other liability obligations and/or rights consistent with this
|
|
139
|
+
License. However, in accepting such obligations, You may offer only
|
|
140
|
+
conditions consistent with this License.
|
|
141
|
+
|
|
142
|
+
END OF TERMS AND CONDITIONS
|
|
143
|
+
|
|
144
|
+
Copyright 2026 Godman Protocols / skingem1
|
|
145
|
+
|
|
146
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
147
|
+
you may not use this file except in compliance with the License.
|
|
148
|
+
You may obtain a copy of the License at
|
|
149
|
+
|
|
150
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
151
|
+
|
|
152
|
+
Unless required by applicable law or agreed to in writing, software
|
|
153
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
154
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
155
|
+
See the License for the specific language governing permissions and
|
|
156
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# SIGNAL — Event Bus and Pub/Sub for Agent Swarms
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
[](https://www.npmjs.com/package/@godman-protocols/signal)
|
|
5
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
|
|
8
|
+
> **v0.2.0** · Apache 2.0 · `@godman-protocols/signal` · Node 20+ / Deno 1.40+
|
|
9
|
+
|
|
10
|
+
SIGNAL is an open protocol for real-time event delivery between AI agents — with glob-based topic matching, idempotency deduplication, and delivery receipts, so your swarm stays coordinated without polling.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx skills add https://github.com/godman-protocols/signal
|
|
14
|
+
# or
|
|
15
|
+
npm install @godman-protocols/signal
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## The Problem
|
|
21
|
+
|
|
22
|
+
Multi-agent systems need to react to events: a mandate was signed, a task completed, a resource released. Without an event bus:
|
|
23
|
+
|
|
24
|
+
- **Polling everywhere** — agents waste cycles checking each other's state
|
|
25
|
+
- **Lost events** — no delivery guarantees means agents miss critical signals
|
|
26
|
+
- **Duplicate processing** — without idempotency, retries cause double execution
|
|
27
|
+
|
|
28
|
+
SIGNAL is the missing communication backbone for agent swarms.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Core Concepts
|
|
33
|
+
|
|
34
|
+
| Concept | What it is |
|
|
35
|
+
|---------|-----------|
|
|
36
|
+
| **Event** | A signed, timestamped message on a dot-notation topic (e.g. `task.completed`) |
|
|
37
|
+
| **Subscription** | A registration to receive events matching a glob-style topic filter |
|
|
38
|
+
| **EventBus** | In-memory pub/sub engine with topic matching and delivery tracking |
|
|
39
|
+
| **DeliveryReceipt** | Confirmation that an event was delivered and processed (or failed) |
|
|
40
|
+
| **IdempotencyKey** | Deduplication key — same key = same event, skip re-delivery |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Quickstart
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { EventBus, createEvent } from '@godman-protocols/signal';
|
|
48
|
+
|
|
49
|
+
const bus = new EventBus();
|
|
50
|
+
const SECRET = process.env.SIGNAL_SECRET!;
|
|
51
|
+
|
|
52
|
+
// 1. Subscribe to task completion events
|
|
53
|
+
const delivered: string[] = [];
|
|
54
|
+
bus.subscribe('did:kognai:sherlock', 'task.*', (event) => {
|
|
55
|
+
delivered.push(event.topic);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// 2. Publish an event
|
|
59
|
+
const event = createEvent(
|
|
60
|
+
'did:kognai:messi', // publisher
|
|
61
|
+
'task.completed', // topic
|
|
62
|
+
{ taskId: 'sprint-973-01' }, // payload
|
|
63
|
+
SECRET
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const receipts = await bus.publish(event);
|
|
67
|
+
// → [{ status: 'processed', ... }]
|
|
68
|
+
|
|
69
|
+
// 3. Idempotency — publishing the same event again is a no-op
|
|
70
|
+
const dupeReceipts = await bus.publish(event);
|
|
71
|
+
// → [{ status: 'duplicate-skipped', ... }]
|
|
72
|
+
|
|
73
|
+
// 4. Wildcard matching
|
|
74
|
+
bus.subscribe('did:kognai:guardiola', 'mandate.**', (event) => {
|
|
75
|
+
// Matches: mandate.signed, mandate.revoked, mandate.frame.closed
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## API Summary
|
|
82
|
+
|
|
83
|
+
### Event Creation (`src/bus.ts`)
|
|
84
|
+
|
|
85
|
+
| Function | Description |
|
|
86
|
+
|----------|-------------|
|
|
87
|
+
| `createEvent(publisher, topic, payload, secret, options?)` | Create a signed event |
|
|
88
|
+
|
|
89
|
+
### EventBus (`src/bus.ts`)
|
|
90
|
+
|
|
91
|
+
| Method | Description |
|
|
92
|
+
|--------|-------------|
|
|
93
|
+
| `subscribe(agent, topicFilter, handler, deliveryMode?)` | Subscribe to matching events |
|
|
94
|
+
| `unsubscribe(subscriptionId)` | Cancel a subscription |
|
|
95
|
+
| `publish(event)` | Deliver event to all matching subscribers |
|
|
96
|
+
| `getReceipts()` | Get all delivery receipts |
|
|
97
|
+
| `subscriptionCount` | Number of active subscriptions |
|
|
98
|
+
|
|
99
|
+
### Topic Matching (`src/bus.ts`)
|
|
100
|
+
|
|
101
|
+
| Function | Description |
|
|
102
|
+
|----------|-------------|
|
|
103
|
+
| `topicMatches(filter, topic)` | Test if a topic matches a glob filter |
|
|
104
|
+
|
|
105
|
+
### Singleton
|
|
106
|
+
|
|
107
|
+
| Export | Description |
|
|
108
|
+
|--------|-------------|
|
|
109
|
+
| `defaultBus` | Pre-created singleton EventBus for single-process use |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Topic Matching Rules
|
|
114
|
+
|
|
115
|
+
Topics use dot notation: `task.completed`, `mandate.frame.closed`
|
|
116
|
+
|
|
117
|
+
Filters support two wildcards:
|
|
118
|
+
- **`*`** — matches exactly one segment: `task.*` matches `task.completed` but not `task.sub.completed`
|
|
119
|
+
- **`**`** — matches zero or more segments: `mandate.**` matches `mandate`, `mandate.signed`, `mandate.frame.closed`
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Delivery Modes
|
|
124
|
+
|
|
125
|
+
| Mode | Guarantee |
|
|
126
|
+
|------|-----------|
|
|
127
|
+
| `at-least-once` (default) | Event delivered at least once; handler may see retries |
|
|
128
|
+
| `at-most-once` | Event delivered at most once; may be lost on failure |
|
|
129
|
+
|
|
130
|
+
Idempotency deduplication applies regardless of delivery mode — duplicate events (same `idempotencyKey`) are always skipped.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Security Model
|
|
135
|
+
|
|
136
|
+
SIGNAL v0.2 uses HMAC-SHA256 for event signing. The signature covers: event ID, topic, publisher, and timestamp.
|
|
137
|
+
|
|
138
|
+
**Production upgrade path:**
|
|
139
|
+
- Replace HMAC with Ed25519 for publisher identity verification
|
|
140
|
+
- Add Supabase Realtime or Redis Streams transport adapter
|
|
141
|
+
- Add WebSocket transport for cross-process delivery
|
|
142
|
+
- Persistent event log with replay capability
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Compatibility
|
|
147
|
+
|
|
148
|
+
| System | How it connects |
|
|
149
|
+
|--------|----------------|
|
|
150
|
+
| **Kognai** (event bus) | SIGNAL formalises the Supabase `kognai_events` table pattern |
|
|
151
|
+
| **PACT** (mandates) | Mandate lifecycle events: `mandate.signed`, `mandate.revoked`, `mandate.expired` |
|
|
152
|
+
| **SCORE** (evaluations) | Evaluation events: `score.evaluation.completed`, `score.reputation.updated` |
|
|
153
|
+
| **DRS** (resources) | Resource events: `resource.allocated`, `resource.released`, `resource.preempted` |
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Related Protocols
|
|
158
|
+
|
|
159
|
+
| Protocol | Purpose |
|
|
160
|
+
|----------|---------|
|
|
161
|
+
| **PACT** | Agent coordination and trust |
|
|
162
|
+
| **LAX** | Latency-aware execution scheduling |
|
|
163
|
+
| **SCORE** | Scoring and reputation for agent outputs |
|
|
164
|
+
| **AMF** | Agent Message Format |
|
|
165
|
+
| **DRS** | Dynamic Resource Scheduling |
|
|
166
|
+
| **SOUL** | Constitutional constraints and safety |
|
|
167
|
+
| **SIGNAL** (this repo) | Event bus and pub/sub for agent swarms |
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Roadmap
|
|
172
|
+
|
|
173
|
+
- [x] Signed event creation (v0.2)
|
|
174
|
+
- [x] In-memory EventBus with glob topic matching (v0.2)
|
|
175
|
+
- [x] Idempotency deduplication (v0.2)
|
|
176
|
+
- [x] Delivery receipts with status tracking (v0.2)
|
|
177
|
+
- [ ] Supabase Realtime transport adapter (v0.3)
|
|
178
|
+
- [ ] Redis Streams transport adapter (v0.3)
|
|
179
|
+
- [ ] Event replay from persistent log (v0.4)
|
|
180
|
+
- [ ] Python SDK (v0.5)
|
|
181
|
+
- [ ] Cross-process WebSocket delivery (v0.5)
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
Apache License 2.0 — see [LICENSE](./LICENSE)
|
|
188
|
+
|
|
189
|
+
Part of the [Godman Protocols](https://github.com/godman-protocols) portfolio.
|
package/dist/bus.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIGNAL — Event Bus and Pub/Sub for Agent Swarms
|
|
3
|
+
* In-memory EventBus implementation
|
|
4
|
+
* @version 0.2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { AgentId, DeliveryReceipt, Event, Subscription, SubscriptionId, Timestamp } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Match a topic against a glob-style filter.
|
|
9
|
+
* - `*` matches exactly one segment
|
|
10
|
+
* - `**` matches zero or more segments
|
|
11
|
+
* - Exact strings match literally
|
|
12
|
+
*/
|
|
13
|
+
export declare function topicMatches(filter: string, topic: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Create a signed event.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createEvent(publisher: AgentId, topic: string, payload: unknown, secret: string, options?: {
|
|
18
|
+
id?: string;
|
|
19
|
+
idempotencyKey?: string;
|
|
20
|
+
publishedAt?: Timestamp;
|
|
21
|
+
}): Event;
|
|
22
|
+
type EventHandler = (event: Event) => void | Promise<void>;
|
|
23
|
+
export declare class EventBus {
|
|
24
|
+
private subs;
|
|
25
|
+
private seenIdempotencyKeys;
|
|
26
|
+
private receipts;
|
|
27
|
+
/**
|
|
28
|
+
* Subscribe to events matching a topic filter.
|
|
29
|
+
*/
|
|
30
|
+
subscribe(subscriberAgent: AgentId, topicFilter: string, handler: EventHandler, deliveryMode?: 'at-least-once' | 'at-most-once'): Subscription;
|
|
31
|
+
/**
|
|
32
|
+
* Unsubscribe. Marks the subscription as cancelled.
|
|
33
|
+
*/
|
|
34
|
+
unsubscribe(subscriptionId: SubscriptionId): void;
|
|
35
|
+
/**
|
|
36
|
+
* Publish an event to all matching subscribers.
|
|
37
|
+
* Deduplicates by idempotencyKey.
|
|
38
|
+
* Returns delivery receipts.
|
|
39
|
+
*/
|
|
40
|
+
publish(event: Event): Promise<DeliveryReceipt[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Get all delivery receipts.
|
|
43
|
+
*/
|
|
44
|
+
getReceipts(): ReadonlyArray<DeliveryReceipt>;
|
|
45
|
+
/**
|
|
46
|
+
* Get active subscription count.
|
|
47
|
+
*/
|
|
48
|
+
get subscriptionCount(): number;
|
|
49
|
+
}
|
|
50
|
+
/** Default singleton bus for single-process use */
|
|
51
|
+
export declare const defaultBus: EventBus;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../src/bus.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EACV,OAAO,EACP,eAAe,EACf,KAAK,EACL,YAAY,EACZ,cAAc,EACd,SAAS,EACV,MAAM,YAAY,CAAC;AAMpB;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAInE;AAuBD;;GAEG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,OAAO,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,CAAA;CAAO,GAC9E,KAAK,CAOP;AAMD,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAO3D,qBAAa,QAAQ;IACnB,OAAO,CAAC,IAAI,CAA0C;IACtD,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAyB;IAEzC;;OAEG;IACH,SAAS,CACP,eAAe,EAAE,OAAO,EACxB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,YAAY,EACrB,YAAY,GAAE,eAAe,GAAG,cAAgC,GAC/D,YAAY;IAaf;;OAEG;IACH,WAAW,CAAC,cAAc,EAAE,cAAc,GAAG,IAAI;IAOjD;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAwCvD;;OAEG;IACH,WAAW,IAAI,aAAa,CAAC,eAAe,CAAC;IAI7C;;OAEG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;CACF;AAED,mDAAmD;AACnD,eAAO,MAAM,UAAU,UAAiB,CAAC"}
|
package/dist/bus.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIGNAL — Event Bus and Pub/Sub for Agent Swarms
|
|
3
|
+
* In-memory EventBus implementation
|
|
4
|
+
* @version 0.2.0
|
|
5
|
+
*/
|
|
6
|
+
import { createHmac, randomUUID } from 'node:crypto';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Topic matching
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
/**
|
|
11
|
+
* Match a topic against a glob-style filter.
|
|
12
|
+
* - `*` matches exactly one segment
|
|
13
|
+
* - `**` matches zero or more segments
|
|
14
|
+
* - Exact strings match literally
|
|
15
|
+
*/
|
|
16
|
+
export function topicMatches(filter, topic) {
|
|
17
|
+
const filterParts = filter.split('.');
|
|
18
|
+
const topicParts = topic.split('.');
|
|
19
|
+
return matchParts(filterParts, 0, topicParts, 0);
|
|
20
|
+
}
|
|
21
|
+
function matchParts(f, fi, t, ti) {
|
|
22
|
+
if (fi === f.length && ti === t.length)
|
|
23
|
+
return true;
|
|
24
|
+
if (fi === f.length)
|
|
25
|
+
return false;
|
|
26
|
+
if (f[fi] === '**') {
|
|
27
|
+
// ** can match zero or more segments
|
|
28
|
+
for (let i = ti; i <= t.length; i++) {
|
|
29
|
+
if (matchParts(f, fi + 1, t, i))
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (ti === t.length)
|
|
35
|
+
return false;
|
|
36
|
+
if (f[fi] === '*' || f[fi] === t[ti]) {
|
|
37
|
+
return matchParts(f, fi + 1, t, ti + 1);
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Event creation
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
/**
|
|
45
|
+
* Create a signed event.
|
|
46
|
+
*/
|
|
47
|
+
export function createEvent(publisher, topic, payload, secret, options = {}) {
|
|
48
|
+
const id = options.id ?? randomUUID();
|
|
49
|
+
const publishedAt = options.publishedAt ?? new Date().toISOString();
|
|
50
|
+
const idempotencyKey = options.idempotencyKey ?? id;
|
|
51
|
+
const sigPayload = `${id}:${topic}:${publisher}:${publishedAt}`;
|
|
52
|
+
const signature = createHmac('sha256', secret).update(sigPayload, 'utf8').digest('hex');
|
|
53
|
+
return { id, topic, publisher, publishedAt, idempotencyKey, payload, signature };
|
|
54
|
+
}
|
|
55
|
+
export class EventBus {
|
|
56
|
+
subs = new Map();
|
|
57
|
+
seenIdempotencyKeys = new Set();
|
|
58
|
+
receipts = [];
|
|
59
|
+
/**
|
|
60
|
+
* Subscribe to events matching a topic filter.
|
|
61
|
+
*/
|
|
62
|
+
subscribe(subscriberAgent, topicFilter, handler, deliveryMode = 'at-least-once') {
|
|
63
|
+
const sub = {
|
|
64
|
+
id: randomUUID(),
|
|
65
|
+
subscriberAgent,
|
|
66
|
+
topicFilter,
|
|
67
|
+
deliveryMode,
|
|
68
|
+
createdAt: new Date().toISOString(),
|
|
69
|
+
cancelledAt: null,
|
|
70
|
+
};
|
|
71
|
+
this.subs.set(sub.id, { subscription: sub, handler });
|
|
72
|
+
return sub;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Unsubscribe. Marks the subscription as cancelled.
|
|
76
|
+
*/
|
|
77
|
+
unsubscribe(subscriptionId) {
|
|
78
|
+
const entry = this.subs.get(subscriptionId);
|
|
79
|
+
if (!entry)
|
|
80
|
+
throw new Error(`Subscription ${subscriptionId} not found`);
|
|
81
|
+
entry.subscription.cancelledAt = new Date().toISOString();
|
|
82
|
+
this.subs.delete(subscriptionId);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Publish an event to all matching subscribers.
|
|
86
|
+
* Deduplicates by idempotencyKey.
|
|
87
|
+
* Returns delivery receipts.
|
|
88
|
+
*/
|
|
89
|
+
async publish(event) {
|
|
90
|
+
// Idempotency check
|
|
91
|
+
if (this.seenIdempotencyKeys.has(event.idempotencyKey)) {
|
|
92
|
+
const receipt = {
|
|
93
|
+
eventId: event.id,
|
|
94
|
+
subscriptionId: 'none',
|
|
95
|
+
receivedAt: new Date().toISOString(),
|
|
96
|
+
status: 'duplicate-skipped',
|
|
97
|
+
};
|
|
98
|
+
this.receipts.push(receipt);
|
|
99
|
+
return [receipt];
|
|
100
|
+
}
|
|
101
|
+
this.seenIdempotencyKeys.add(event.idempotencyKey);
|
|
102
|
+
const newReceipts = [];
|
|
103
|
+
for (const [, entry] of this.subs) {
|
|
104
|
+
if (entry.subscription.cancelledAt)
|
|
105
|
+
continue;
|
|
106
|
+
if (!topicMatches(entry.subscription.topicFilter, event.topic))
|
|
107
|
+
continue;
|
|
108
|
+
let status = 'processed';
|
|
109
|
+
try {
|
|
110
|
+
await entry.handler(event);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
status = 'failed';
|
|
114
|
+
}
|
|
115
|
+
const receipt = {
|
|
116
|
+
eventId: event.id,
|
|
117
|
+
subscriptionId: entry.subscription.id,
|
|
118
|
+
receivedAt: new Date().toISOString(),
|
|
119
|
+
status,
|
|
120
|
+
};
|
|
121
|
+
newReceipts.push(receipt);
|
|
122
|
+
}
|
|
123
|
+
this.receipts.push(...newReceipts);
|
|
124
|
+
return newReceipts;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get all delivery receipts.
|
|
128
|
+
*/
|
|
129
|
+
getReceipts() {
|
|
130
|
+
return [...this.receipts];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get active subscription count.
|
|
134
|
+
*/
|
|
135
|
+
get subscriptionCount() {
|
|
136
|
+
return this.subs.size;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Default singleton bus for single-process use */
|
|
140
|
+
export const defaultBus = new EventBus();
|
|
141
|
+
//# sourceMappingURL=bus.js.map
|
package/dist/bus.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bus.js","sourceRoot":"","sources":["../src/bus.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIGNAL — Event Bus and Pub/Sub for Agent Swarms
|
|
3
|
+
* Public API surface
|
|
4
|
+
* @version 0.2.0
|
|
5
|
+
*/
|
|
6
|
+
export type { AgentId, Timestamp, Signature, EventId, SubscriptionId, Event, Subscription, TransportConfig, DeliveryReceipt, } from './types.js';
|
|
7
|
+
export { EventBus, defaultBus, createEvent, topicMatches, } from './bus.js';
|
|
8
|
+
/** Protocol version constant */
|
|
9
|
+
export declare const SIGNAL_VERSION: "0.2";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,OAAO,EACP,cAAc,EACd,KAAK,EACL,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,QAAQ,EACR,UAAU,EACV,WAAW,EACX,YAAY,GACb,MAAM,UAAU,CAAC;AAElB,gCAAgC;AAChC,eAAO,MAAM,cAAc,EAAG,KAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIGNAL — Event Bus and Pub/Sub for Agent Swarms
|
|
3
|
+
* Public API surface
|
|
4
|
+
* @version 0.2.0
|
|
5
|
+
*/
|
|
6
|
+
// Event bus
|
|
7
|
+
export { EventBus, defaultBus, createEvent, topicMatches, } from './bus.js';
|
|
8
|
+
/** Protocol version constant */
|
|
9
|
+
export const SIGNAL_VERSION = '0.2';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH,YAAY;AACZ,OAAO,EACL,QAAQ,EACR,UAAU,EACV,WAAW,EACX,YAAY,GACb,MAAM,UAAU,CAAC;AAElB,gCAAgC;AAChC,MAAM,CAAC,MAAM,cAAc,GAAG,KAAc,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIGNAL — Event Bus and Pub/Sub for Agent Swarms
|
|
3
|
+
* Core type definitions (skeleton)
|
|
4
|
+
* @version 0.1.0-skeleton
|
|
5
|
+
*/
|
|
6
|
+
export type AgentId = string;
|
|
7
|
+
export type Timestamp = string;
|
|
8
|
+
export type Signature = string;
|
|
9
|
+
export type EventId = string;
|
|
10
|
+
export type SubscriptionId = string;
|
|
11
|
+
/** A typed, timestamped, signed event */
|
|
12
|
+
export interface Event {
|
|
13
|
+
id: EventId;
|
|
14
|
+
/** Dot-notation topic path, e.g. 'task.completed', 'mandate.revoked' */
|
|
15
|
+
topic: string;
|
|
16
|
+
/** Publishing agent */
|
|
17
|
+
publisher: AgentId;
|
|
18
|
+
/** ISO 8601 */
|
|
19
|
+
publishedAt: Timestamp;
|
|
20
|
+
/** Idempotency key — deduplicate on re-delivery */
|
|
21
|
+
idempotencyKey: string;
|
|
22
|
+
payload: unknown;
|
|
23
|
+
signature: Signature;
|
|
24
|
+
}
|
|
25
|
+
/** A durable subscription to a topic filter */
|
|
26
|
+
export interface Subscription {
|
|
27
|
+
id: SubscriptionId;
|
|
28
|
+
subscriberAgent: AgentId;
|
|
29
|
+
/** Glob-style topic filter, e.g. 'task.*', '**' */
|
|
30
|
+
topicFilter: string;
|
|
31
|
+
/** Delivery guarantee */
|
|
32
|
+
deliveryMode: 'at-least-once' | 'at-most-once';
|
|
33
|
+
/** ISO 8601 */
|
|
34
|
+
createdAt: Timestamp;
|
|
35
|
+
/** ISO 8601 — null if still active */
|
|
36
|
+
cancelledAt: Timestamp | null;
|
|
37
|
+
}
|
|
38
|
+
/** Transport adapter configuration */
|
|
39
|
+
export interface TransportConfig {
|
|
40
|
+
type: 'supabase-realtime' | 'websocket' | 'redis-streams' | 'in-memory';
|
|
41
|
+
connectionString?: string;
|
|
42
|
+
channel?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Delivery receipt — confirms an event was received and processed */
|
|
45
|
+
export interface DeliveryReceipt {
|
|
46
|
+
eventId: EventId;
|
|
47
|
+
subscriptionId: SubscriptionId;
|
|
48
|
+
receivedAt: Timestamp;
|
|
49
|
+
status: 'processed' | 'failed' | 'duplicate-skipped';
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAC7B,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAC7B,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,yCAAyC;AACzC,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,OAAO,CAAC;IACZ,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe;IACf,WAAW,EAAE,SAAS,CAAC;IACvB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,cAAc,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,YAAY,EAAE,eAAe,GAAG,cAAc,CAAC;IAC/C,eAAe;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,sCAAsC;IACtC,WAAW,EAAE,SAAS,GAAG,IAAI,CAAC;CAC/B;AAED,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,GAAG,WAAW,GAAG,eAAe,GAAG,WAAW,CAAC;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,SAAS,CAAC;IACtB,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,mBAAmB,CAAC;CACtD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@godman-protocols/signal",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Event Bus and Pub/Sub for Agent Swarms \u2014 typed events, topic-glob routing, idempotent delivery, delivery receipts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"exports": "./dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
16
|
+
"test": "npx tsx smoke.test.ts",
|
|
17
|
+
"typecheck": "tsc --noEmit"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"event-bus",
|
|
21
|
+
"pub-sub",
|
|
22
|
+
"messaging",
|
|
23
|
+
"agent-communication",
|
|
24
|
+
"idempotent",
|
|
25
|
+
"godman-protocols",
|
|
26
|
+
"multi-agent"
|
|
27
|
+
],
|
|
28
|
+
"author": "skingem1",
|
|
29
|
+
"license": "Apache-2.0",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"registry": "https://registry.npmjs.org/"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/godman-protocols/signal.git"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"typescript": "^5.4.0",
|
|
43
|
+
"tsx": "^4.7.0"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {},
|
|
46
|
+
"homepage": "https://github.com/skingem1/godman-protocols/tree/main/signal#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/skingem1/godman-protocols/issues"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/bus.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIGNAL — Event Bus and Pub/Sub for Agent Swarms
|
|
3
|
+
* In-memory EventBus implementation
|
|
4
|
+
* @version 0.2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createHmac, randomUUID } from 'node:crypto';
|
|
8
|
+
import type {
|
|
9
|
+
AgentId,
|
|
10
|
+
DeliveryReceipt,
|
|
11
|
+
Event,
|
|
12
|
+
Subscription,
|
|
13
|
+
SubscriptionId,
|
|
14
|
+
Timestamp,
|
|
15
|
+
} from './types.js';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Topic matching
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Match a topic against a glob-style filter.
|
|
23
|
+
* - `*` matches exactly one segment
|
|
24
|
+
* - `**` matches zero or more segments
|
|
25
|
+
* - Exact strings match literally
|
|
26
|
+
*/
|
|
27
|
+
export function topicMatches(filter: string, topic: string): boolean {
|
|
28
|
+
const filterParts = filter.split('.');
|
|
29
|
+
const topicParts = topic.split('.');
|
|
30
|
+
return matchParts(filterParts, 0, topicParts, 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function matchParts(f: string[], fi: number, t: string[], ti: number): boolean {
|
|
34
|
+
if (fi === f.length && ti === t.length) return true;
|
|
35
|
+
if (fi === f.length) return false;
|
|
36
|
+
if (f[fi] === '**') {
|
|
37
|
+
// ** can match zero or more segments
|
|
38
|
+
for (let i = ti; i <= t.length; i++) {
|
|
39
|
+
if (matchParts(f, fi + 1, t, i)) return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (ti === t.length) return false;
|
|
44
|
+
if (f[fi] === '*' || f[fi] === t[ti]) {
|
|
45
|
+
return matchParts(f, fi + 1, t, ti + 1);
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Event creation
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a signed event.
|
|
56
|
+
*/
|
|
57
|
+
export function createEvent(
|
|
58
|
+
publisher: AgentId,
|
|
59
|
+
topic: string,
|
|
60
|
+
payload: unknown,
|
|
61
|
+
secret: string,
|
|
62
|
+
options: { id?: string; idempotencyKey?: string; publishedAt?: Timestamp } = {}
|
|
63
|
+
): Event {
|
|
64
|
+
const id = options.id ?? randomUUID();
|
|
65
|
+
const publishedAt = options.publishedAt ?? new Date().toISOString();
|
|
66
|
+
const idempotencyKey = options.idempotencyKey ?? id;
|
|
67
|
+
const sigPayload = `${id}:${topic}:${publisher}:${publishedAt}`;
|
|
68
|
+
const signature = createHmac('sha256', secret).update(sigPayload, 'utf8').digest('hex');
|
|
69
|
+
return { id, topic, publisher, publishedAt, idempotencyKey, payload, signature };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// EventBus
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
type EventHandler = (event: Event) => void | Promise<void>;
|
|
77
|
+
|
|
78
|
+
interface InternalSub {
|
|
79
|
+
subscription: Subscription;
|
|
80
|
+
handler: EventHandler;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class EventBus {
|
|
84
|
+
private subs = new Map<SubscriptionId, InternalSub>();
|
|
85
|
+
private seenIdempotencyKeys = new Set<string>();
|
|
86
|
+
private receipts: DeliveryReceipt[] = [];
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Subscribe to events matching a topic filter.
|
|
90
|
+
*/
|
|
91
|
+
subscribe(
|
|
92
|
+
subscriberAgent: AgentId,
|
|
93
|
+
topicFilter: string,
|
|
94
|
+
handler: EventHandler,
|
|
95
|
+
deliveryMode: 'at-least-once' | 'at-most-once' = 'at-least-once'
|
|
96
|
+
): Subscription {
|
|
97
|
+
const sub: Subscription = {
|
|
98
|
+
id: randomUUID(),
|
|
99
|
+
subscriberAgent,
|
|
100
|
+
topicFilter,
|
|
101
|
+
deliveryMode,
|
|
102
|
+
createdAt: new Date().toISOString(),
|
|
103
|
+
cancelledAt: null,
|
|
104
|
+
};
|
|
105
|
+
this.subs.set(sub.id, { subscription: sub, handler });
|
|
106
|
+
return sub;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Unsubscribe. Marks the subscription as cancelled.
|
|
111
|
+
*/
|
|
112
|
+
unsubscribe(subscriptionId: SubscriptionId): void {
|
|
113
|
+
const entry = this.subs.get(subscriptionId);
|
|
114
|
+
if (!entry) throw new Error(`Subscription ${subscriptionId} not found`);
|
|
115
|
+
entry.subscription.cancelledAt = new Date().toISOString();
|
|
116
|
+
this.subs.delete(subscriptionId);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Publish an event to all matching subscribers.
|
|
121
|
+
* Deduplicates by idempotencyKey.
|
|
122
|
+
* Returns delivery receipts.
|
|
123
|
+
*/
|
|
124
|
+
async publish(event: Event): Promise<DeliveryReceipt[]> {
|
|
125
|
+
// Idempotency check
|
|
126
|
+
if (this.seenIdempotencyKeys.has(event.idempotencyKey)) {
|
|
127
|
+
const receipt: DeliveryReceipt = {
|
|
128
|
+
eventId: event.id,
|
|
129
|
+
subscriptionId: 'none',
|
|
130
|
+
receivedAt: new Date().toISOString(),
|
|
131
|
+
status: 'duplicate-skipped',
|
|
132
|
+
};
|
|
133
|
+
this.receipts.push(receipt);
|
|
134
|
+
return [receipt];
|
|
135
|
+
}
|
|
136
|
+
this.seenIdempotencyKeys.add(event.idempotencyKey);
|
|
137
|
+
|
|
138
|
+
const newReceipts: DeliveryReceipt[] = [];
|
|
139
|
+
|
|
140
|
+
for (const [, entry] of this.subs) {
|
|
141
|
+
if (entry.subscription.cancelledAt) continue;
|
|
142
|
+
if (!topicMatches(entry.subscription.topicFilter, event.topic)) continue;
|
|
143
|
+
|
|
144
|
+
let status: DeliveryReceipt['status'] = 'processed';
|
|
145
|
+
try {
|
|
146
|
+
await entry.handler(event);
|
|
147
|
+
} catch {
|
|
148
|
+
status = 'failed';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const receipt: DeliveryReceipt = {
|
|
152
|
+
eventId: event.id,
|
|
153
|
+
subscriptionId: entry.subscription.id,
|
|
154
|
+
receivedAt: new Date().toISOString(),
|
|
155
|
+
status,
|
|
156
|
+
};
|
|
157
|
+
newReceipts.push(receipt);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.receipts.push(...newReceipts);
|
|
161
|
+
return newReceipts;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get all delivery receipts.
|
|
166
|
+
*/
|
|
167
|
+
getReceipts(): ReadonlyArray<DeliveryReceipt> {
|
|
168
|
+
return [...this.receipts];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get active subscription count.
|
|
173
|
+
*/
|
|
174
|
+
get subscriptionCount(): number {
|
|
175
|
+
return this.subs.size;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Default singleton bus for single-process use */
|
|
180
|
+
export const defaultBus = new EventBus();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIGNAL — Event Bus and Pub/Sub for Agent Swarms
|
|
3
|
+
* Public API surface
|
|
4
|
+
* @version 0.2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export type {
|
|
9
|
+
AgentId,
|
|
10
|
+
Timestamp,
|
|
11
|
+
Signature,
|
|
12
|
+
EventId,
|
|
13
|
+
SubscriptionId,
|
|
14
|
+
Event,
|
|
15
|
+
Subscription,
|
|
16
|
+
TransportConfig,
|
|
17
|
+
DeliveryReceipt,
|
|
18
|
+
} from './types.js';
|
|
19
|
+
|
|
20
|
+
// Event bus
|
|
21
|
+
export {
|
|
22
|
+
EventBus,
|
|
23
|
+
defaultBus,
|
|
24
|
+
createEvent,
|
|
25
|
+
topicMatches,
|
|
26
|
+
} from './bus.js';
|
|
27
|
+
|
|
28
|
+
/** Protocol version constant */
|
|
29
|
+
export const SIGNAL_VERSION = '0.2' as const;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIGNAL — Event Bus and Pub/Sub for Agent Swarms
|
|
3
|
+
* Core type definitions (skeleton)
|
|
4
|
+
* @version 0.1.0-skeleton
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type AgentId = string;
|
|
8
|
+
export type Timestamp = string;
|
|
9
|
+
export type Signature = string;
|
|
10
|
+
export type EventId = string;
|
|
11
|
+
export type SubscriptionId = string;
|
|
12
|
+
|
|
13
|
+
/** A typed, timestamped, signed event */
|
|
14
|
+
export interface Event {
|
|
15
|
+
id: EventId;
|
|
16
|
+
/** Dot-notation topic path, e.g. 'task.completed', 'mandate.revoked' */
|
|
17
|
+
topic: string;
|
|
18
|
+
/** Publishing agent */
|
|
19
|
+
publisher: AgentId;
|
|
20
|
+
/** ISO 8601 */
|
|
21
|
+
publishedAt: Timestamp;
|
|
22
|
+
/** Idempotency key — deduplicate on re-delivery */
|
|
23
|
+
idempotencyKey: string;
|
|
24
|
+
payload: unknown;
|
|
25
|
+
signature: Signature;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A durable subscription to a topic filter */
|
|
29
|
+
export interface Subscription {
|
|
30
|
+
id: SubscriptionId;
|
|
31
|
+
subscriberAgent: AgentId;
|
|
32
|
+
/** Glob-style topic filter, e.g. 'task.*', '**' */
|
|
33
|
+
topicFilter: string;
|
|
34
|
+
/** Delivery guarantee */
|
|
35
|
+
deliveryMode: 'at-least-once' | 'at-most-once';
|
|
36
|
+
/** ISO 8601 */
|
|
37
|
+
createdAt: Timestamp;
|
|
38
|
+
/** ISO 8601 — null if still active */
|
|
39
|
+
cancelledAt: Timestamp | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Transport adapter configuration */
|
|
43
|
+
export interface TransportConfig {
|
|
44
|
+
type: 'supabase-realtime' | 'websocket' | 'redis-streams' | 'in-memory';
|
|
45
|
+
connectionString?: string;
|
|
46
|
+
channel?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Delivery receipt — confirms an event was received and processed */
|
|
50
|
+
export interface DeliveryReceipt {
|
|
51
|
+
eventId: EventId;
|
|
52
|
+
subscriptionId: SubscriptionId;
|
|
53
|
+
receivedAt: Timestamp;
|
|
54
|
+
status: 'processed' | 'failed' | 'duplicate-skipped';
|
|
55
|
+
}
|