@tjamescouch/agentchat 0.22.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/Dockerfile +1 -1
  2. package/dist/bin/agentchat.d.ts +7 -0
  3. package/dist/bin/agentchat.d.ts.map +1 -0
  4. package/dist/bin/agentchat.js +1511 -0
  5. package/dist/bin/agentchat.js.map +1 -0
  6. package/dist/lib/allowlist.d.ts +77 -0
  7. package/dist/lib/allowlist.d.ts.map +1 -0
  8. package/dist/lib/allowlist.js +151 -0
  9. package/dist/lib/allowlist.js.map +1 -0
  10. package/dist/lib/client.d.ts +147 -0
  11. package/dist/lib/client.d.ts.map +1 -0
  12. package/dist/lib/client.js +704 -0
  13. package/dist/lib/client.js.map +1 -0
  14. package/dist/lib/daemon.d.ts +122 -0
  15. package/dist/lib/daemon.d.ts.map +1 -0
  16. package/dist/lib/daemon.js +523 -0
  17. package/dist/lib/daemon.js.map +1 -0
  18. package/dist/lib/deploy/akash.d.ts +271 -0
  19. package/dist/lib/deploy/akash.d.ts.map +1 -0
  20. package/dist/lib/deploy/akash.js +671 -0
  21. package/dist/lib/deploy/akash.js.map +1 -0
  22. package/dist/lib/deploy/config.d.ts +62 -0
  23. package/dist/lib/deploy/config.d.ts.map +1 -0
  24. package/dist/lib/deploy/config.js +116 -0
  25. package/dist/lib/deploy/config.js.map +1 -0
  26. package/dist/lib/deploy/docker.d.ts +37 -0
  27. package/dist/lib/deploy/docker.d.ts.map +1 -0
  28. package/dist/lib/deploy/docker.js +122 -0
  29. package/dist/lib/deploy/docker.js.map +1 -0
  30. package/dist/lib/deploy/index.d.ts +11 -0
  31. package/dist/lib/deploy/index.d.ts.map +1 -0
  32. package/dist/lib/deploy/index.js +11 -0
  33. package/dist/lib/deploy/index.js.map +1 -0
  34. package/dist/lib/escrow-hooks.d.ts +199 -0
  35. package/dist/lib/escrow-hooks.d.ts.map +1 -0
  36. package/dist/lib/escrow-hooks.js +221 -0
  37. package/dist/lib/escrow-hooks.js.map +1 -0
  38. package/dist/lib/identity.d.ts +134 -0
  39. package/dist/lib/identity.d.ts.map +1 -0
  40. package/dist/lib/identity.js +334 -0
  41. package/dist/lib/identity.js.map +1 -0
  42. package/dist/lib/jitter.d.ts +42 -0
  43. package/dist/lib/jitter.d.ts.map +1 -0
  44. package/{lib/jitter.ts → dist/lib/jitter.js} +10 -18
  45. package/dist/lib/jitter.js.map +1 -0
  46. package/dist/lib/proposals.d.ts +223 -0
  47. package/dist/lib/proposals.d.ts.map +1 -0
  48. package/dist/lib/proposals.js +379 -0
  49. package/dist/lib/proposals.js.map +1 -0
  50. package/dist/lib/protocol.d.ts +220 -0
  51. package/dist/lib/protocol.d.ts.map +1 -0
  52. package/dist/lib/protocol.js +507 -0
  53. package/dist/lib/protocol.js.map +1 -0
  54. package/dist/lib/receipts.d.ts +134 -0
  55. package/dist/lib/receipts.d.ts.map +1 -0
  56. package/dist/lib/receipts.js +270 -0
  57. package/dist/lib/receipts.js.map +1 -0
  58. package/dist/lib/reputation.d.ts +250 -0
  59. package/dist/lib/reputation.d.ts.map +1 -0
  60. package/dist/lib/reputation.js +586 -0
  61. package/dist/lib/reputation.js.map +1 -0
  62. package/dist/lib/security.d.ts +27 -0
  63. package/dist/lib/security.d.ts.map +1 -0
  64. package/dist/lib/security.js +150 -0
  65. package/dist/lib/security.js.map +1 -0
  66. package/dist/lib/server/handlers/admin.d.ts +26 -0
  67. package/dist/lib/server/handlers/admin.d.ts.map +1 -0
  68. package/dist/lib/server/handlers/admin.js +76 -0
  69. package/dist/lib/server/handlers/admin.js.map +1 -0
  70. package/dist/lib/server/handlers/identity.d.ts +36 -0
  71. package/dist/lib/server/handlers/identity.d.ts.map +1 -0
  72. package/dist/lib/server/handlers/identity.js +330 -0
  73. package/dist/lib/server/handlers/identity.js.map +1 -0
  74. package/dist/lib/server/handlers/index.d.ts +10 -0
  75. package/dist/lib/server/handlers/index.d.ts.map +1 -0
  76. package/dist/lib/server/handlers/index.js +15 -0
  77. package/dist/lib/server/handlers/index.js.map +1 -0
  78. package/dist/lib/server/handlers/message.d.ts +47 -0
  79. package/dist/lib/server/handlers/message.d.ts.map +1 -0
  80. package/dist/lib/server/handlers/message.js +265 -0
  81. package/dist/lib/server/handlers/message.js.map +1 -0
  82. package/dist/lib/server/handlers/presence.d.ts +18 -0
  83. package/dist/lib/server/handlers/presence.d.ts.map +1 -0
  84. package/dist/lib/server/handlers/presence.js +35 -0
  85. package/dist/lib/server/handlers/presence.js.map +1 -0
  86. package/dist/lib/server/handlers/proposal.d.ts +38 -0
  87. package/dist/lib/server/handlers/proposal.d.ts.map +1 -0
  88. package/dist/lib/server/handlers/proposal.js +273 -0
  89. package/dist/lib/server/handlers/proposal.js.map +1 -0
  90. package/dist/lib/server/handlers/skills.d.ts +22 -0
  91. package/dist/lib/server/handlers/skills.d.ts.map +1 -0
  92. package/dist/lib/server/handlers/skills.js +119 -0
  93. package/dist/lib/server/handlers/skills.js.map +1 -0
  94. package/dist/lib/server-directory.d.ts +85 -0
  95. package/dist/lib/server-directory.d.ts.map +1 -0
  96. package/dist/lib/server-directory.js +177 -0
  97. package/dist/lib/server-directory.js.map +1 -0
  98. package/dist/lib/server.d.ts +162 -0
  99. package/dist/lib/server.d.ts.map +1 -0
  100. package/dist/lib/server.js +602 -0
  101. package/dist/lib/server.js.map +1 -0
  102. package/dist/lib/types.d.ts +461 -0
  103. package/dist/lib/types.d.ts.map +1 -0
  104. package/dist/lib/types.js +98 -0
  105. package/dist/lib/types.js.map +1 -0
  106. package/package.json +22 -13
  107. package/bin/agentchat.js +0 -1617
  108. package/bin/agentchat.ts +0 -1812
  109. package/lib/allowlist.js +0 -162
  110. package/lib/chat.py +0 -241
  111. package/lib/client.js +0 -821
  112. package/lib/client.ts +0 -877
  113. package/lib/daemon.js +0 -562
  114. package/lib/daemon.ts +0 -662
  115. package/lib/deploy/akash.js +0 -811
  116. package/lib/deploy/config.js +0 -128
  117. package/lib/deploy/docker.js +0 -132
  118. package/lib/deploy/index.js +0 -24
  119. package/lib/elo_swarm.py +0 -569
  120. package/lib/escrow-hooks.js +0 -237
  121. package/lib/escrow-hooks.ts +0 -391
  122. package/lib/identity.js +0 -376
  123. package/lib/identity.ts +0 -412
  124. package/lib/jitter.js +0 -54
  125. package/lib/proposals.js +0 -426
  126. package/lib/proposals.ts +0 -612
  127. package/lib/protocol.js +0 -516
  128. package/lib/receipts.js +0 -294
  129. package/lib/receipts.ts +0 -359
  130. package/lib/reputation.js +0 -664
  131. package/lib/reputation.ts +0 -790
  132. package/lib/security.js +0 -183
  133. package/lib/server/handlers/admin.js +0 -94
  134. package/lib/server/handlers/identity.js +0 -258
  135. package/lib/server/handlers/index.js +0 -42
  136. package/lib/server/handlers/message.js +0 -319
  137. package/lib/server/handlers/presence.js +0 -45
  138. package/lib/server/handlers/proposal.js +0 -358
  139. package/lib/server/handlers/skills.js +0 -141
  140. package/lib/server-directory.js +0 -190
  141. package/lib/server-directory.ts +0 -232
  142. package/lib/server.js +0 -633
  143. package/lib/server.ts +0 -698
  144. package/lib/supervisor/USAGE.md +0 -110
  145. package/lib/supervisor/agent-health.sh +0 -107
  146. package/lib/supervisor/agent-monitor.sh +0 -123
  147. package/lib/supervisor/agent-supervisor.sh +0 -135
  148. package/lib/supervisor/agentctl.sh +0 -266
  149. package/lib/supervisor/god-backup.sh +0 -126
  150. package/lib/supervisor/god-watchdog.sh +0 -107
  151. package/lib/supervisor/killswitch.sh +0 -43
  152. package/lib/supervisor/notify.sh +0 -19
  153. package/lib/types.ts +0 -433
package/lib/allowlist.js DELETED
@@ -1,162 +0,0 @@
1
- /**
2
- * Allowlist Module
3
- * Controls which public keys can connect to the server.
4
- * Opt-in via ALLOWLIST_ENABLED=true env var.
5
- */
6
-
7
- import crypto from 'crypto';
8
- import fs from 'fs';
9
- import path from 'path';
10
- import { pubkeyToAgentId } from './protocol.js';
11
-
12
- export class Allowlist {
13
- constructor(options = {}) {
14
- this.enabled = options.enabled || false;
15
- this.strict = options.strict || false; // Block ephemeral connections when true
16
- this.adminKey = options.adminKey || null;
17
- this.filePath = options.filePath || path.join(process.cwd(), 'allowlist.json');
18
- this.entries = new Map(); // pubkey -> { agentId, approvedAt, approvedBy, note }
19
-
20
- if (this.enabled) {
21
- this._load();
22
- }
23
- }
24
-
25
- /**
26
- * Check if a pubkey is allowed to connect.
27
- * Returns { allowed, reason }
28
- */
29
- check(pubkey) {
30
- if (!this.enabled) {
31
- return { allowed: true, reason: 'allowlist disabled' };
32
- }
33
-
34
- if (!pubkey) {
35
- // Ephemeral connection (no pubkey)
36
- if (this.strict) {
37
- return { allowed: false, reason: 'ephemeral connections blocked in strict mode' };
38
- }
39
- return { allowed: true, reason: 'ephemeral allowed (non-strict mode)' };
40
- }
41
-
42
- if (this.entries.has(pubkey)) {
43
- return { allowed: true, reason: 'pubkey approved' };
44
- }
45
-
46
- return { allowed: false, reason: 'pubkey not in allowlist' };
47
- }
48
-
49
- /**
50
- * Approve a pubkey for connection.
51
- * Requires valid admin key.
52
- */
53
- approve(pubkey, adminKey, note = '') {
54
- if (!this._validateAdminKey(adminKey)) {
55
- return { success: false, error: 'invalid admin key' };
56
- }
57
-
58
- const agentId = pubkeyToAgentId(pubkey);
59
- this.entries.set(pubkey, {
60
- agentId,
61
- approvedAt: new Date().toISOString(),
62
- approvedBy: 'admin',
63
- note,
64
- });
65
-
66
- this._save();
67
- return { success: true, agentId };
68
- }
69
-
70
- /**
71
- * Revoke a pubkey from the allowlist.
72
- * Can revoke by pubkey or agentId.
73
- */
74
- revoke(identifier, adminKey) {
75
- if (!this._validateAdminKey(adminKey)) {
76
- return { success: false, error: 'invalid admin key' };
77
- }
78
-
79
- // Try by pubkey first
80
- if (this.entries.has(identifier)) {
81
- this.entries.delete(identifier);
82
- this._save();
83
- return { success: true };
84
- }
85
-
86
- // Try by agentId
87
- for (const [pubkey, entry] of this.entries) {
88
- if (entry.agentId === identifier) {
89
- this.entries.delete(pubkey);
90
- this._save();
91
- return { success: true };
92
- }
93
- }
94
-
95
- return { success: false, error: 'not found' };
96
- }
97
-
98
- /**
99
- * List all approved entries.
100
- */
101
- list() {
102
- const result = [];
103
- for (const [pubkey, entry] of this.entries) {
104
- result.push({
105
- agentId: `@${entry.agentId}`,
106
- pubkeyPrefix: pubkey.slice(0, 40) + '...',
107
- approvedAt: entry.approvedAt,
108
- note: entry.note,
109
- });
110
- }
111
- return result;
112
- }
113
-
114
- /**
115
- * Validate admin key using timing-safe comparison.
116
- * Hash both values first to ensure equal length (avoids length timing oracle).
117
- */
118
- _validateAdminKey(key) {
119
- if (!this.adminKey || !key) return false;
120
- if (typeof key !== 'string') return false;
121
-
122
- const a = crypto.createHash('sha256').update(this.adminKey).digest();
123
- const b = crypto.createHash('sha256').update(key).digest();
124
- return crypto.timingSafeEqual(a, b);
125
- }
126
-
127
- /**
128
- * Load allowlist from disk.
129
- */
130
- _load() {
131
- try {
132
- if (fs.existsSync(this.filePath)) {
133
- const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
134
- for (const entry of data) {
135
- this.entries.set(entry.pubkey, {
136
- agentId: entry.agentId,
137
- approvedAt: entry.approvedAt,
138
- approvedBy: entry.approvedBy || 'admin',
139
- note: entry.note || '',
140
- });
141
- }
142
- }
143
- } catch (err) {
144
- console.error(`Failed to load allowlist from ${this.filePath}: ${err.message}`);
145
- }
146
- }
147
-
148
- /**
149
- * Save allowlist to disk.
150
- */
151
- _save() {
152
- try {
153
- const data = [];
154
- for (const [pubkey, entry] of this.entries) {
155
- data.push({ pubkey, ...entry });
156
- }
157
- fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2));
158
- } catch (err) {
159
- console.error(`Failed to save allowlist to ${this.filePath}: ${err.message}`);
160
- }
161
- }
162
- }
package/lib/chat.py DELETED
@@ -1,241 +0,0 @@
1
- #!/usr/bin/env python3
2
- """AgentChat daemon helper - read/send messages and track timestamps."""
3
-
4
- import argparse
5
- import json
6
- import os
7
- import sys
8
- from pathlib import Path
9
-
10
- # Default daemon directory (can be overridden with --daemon-dir)
11
- DEFAULT_DAEMON_DIR = Path.cwd() / ".agentchat" / "daemons" / "default"
12
-
13
-
14
- def get_paths(daemon_dir: Path):
15
- """Get file paths for a daemon directory."""
16
- return {
17
- "inbox": daemon_dir / "inbox.jsonl",
18
- "outbox": daemon_dir / "outbox.jsonl",
19
- "last_ts": daemon_dir / "last_ts",
20
- "newdata": daemon_dir / "newdata", # Semaphore for new messages
21
- }
22
-
23
-
24
- def get_last_ts(paths: dict) -> int:
25
- """Get last seen timestamp."""
26
- if paths["last_ts"].exists():
27
- return int(paths["last_ts"].read_text().strip())
28
- return 0
29
-
30
-
31
- def set_last_ts(paths: dict, ts: int) -> None:
32
- """Update last seen timestamp."""
33
- paths["last_ts"].write_text(str(ts))
34
-
35
-
36
- def read_inbox(paths: dict, since_ts: int = None, limit: int = 50, include_replay: bool = False) -> list:
37
- """Read messages from inbox, optionally filtering by timestamp."""
38
- if since_ts is None:
39
- since_ts = get_last_ts(paths)
40
-
41
- messages = []
42
- if not paths["inbox"].exists():
43
- return messages
44
-
45
- with open(paths["inbox"]) as f:
46
- for line in f:
47
- line = line.strip()
48
- if not line:
49
- continue
50
- try:
51
- msg = json.loads(line)
52
- ts = msg.get("ts", 0)
53
- is_replay = msg.get("replay", False)
54
-
55
- if ts > since_ts and (include_replay or not is_replay):
56
- messages.append(msg)
57
- except json.JSONDecodeError:
58
- continue
59
-
60
- # Sort by timestamp and limit
61
- messages.sort(key=lambda m: m.get("ts", 0))
62
- return messages[-limit:] if limit else messages
63
-
64
-
65
- def send_message(paths: dict, to: str, content: str) -> None:
66
- """Send a message by appending to outbox."""
67
- msg = {"to": to, "content": content}
68
- with open(paths["outbox"], "a") as f:
69
- f.write(json.dumps(msg) + "\n")
70
-
71
-
72
- def check_new(paths: dict, update_ts: bool = True) -> list:
73
- """Check for new messages and optionally update timestamp."""
74
- messages = read_inbox(paths)
75
-
76
- if messages and update_ts:
77
- max_ts = max(m.get("ts", 0) for m in messages)
78
- set_last_ts(paths, max_ts)
79
-
80
- return messages
81
-
82
-
83
- def poll_new(paths: dict):
84
- """Poll for new messages using semaphore file. Returns None if no new data."""
85
- # Fast path: check if semaphore exists
86
- if not paths["newdata"].exists():
87
- return None
88
-
89
- # Semaphore exists, read messages
90
- messages = read_inbox(paths)
91
-
92
- # Delete semaphore
93
- try:
94
- paths["newdata"].unlink()
95
- except FileNotFoundError:
96
- pass # Race condition, already deleted
97
-
98
- # Update timestamp if we got messages
99
- if messages:
100
- max_ts = max(m.get("ts", 0) for m in messages)
101
- set_last_ts(paths, max_ts)
102
-
103
- return messages
104
-
105
-
106
- def wait_for_messages(paths: dict, interval: float = 2.0, timeout: float = 300.0):
107
- """Block until new messages arrive. Returns messages or empty list on timeout."""
108
- import signal
109
- import time
110
-
111
- stop_file = paths["inbox"].parent.parent.parent / "stop"
112
-
113
- # Handle interrupts gracefully
114
- interrupted = False
115
- def handle_signal(signum, frame):
116
- nonlocal interrupted
117
- interrupted = True
118
-
119
- old_handler = signal.signal(signal.SIGINT, handle_signal)
120
-
121
- try:
122
- start = time.time()
123
- while not interrupted and (time.time() - start) < timeout:
124
- # Check stop file
125
- if stop_file.exists():
126
- try:
127
- stop_file.unlink()
128
- except FileNotFoundError:
129
- pass
130
- return [] # Signal to stop
131
-
132
- # Check semaphore
133
- if paths["newdata"].exists():
134
- messages = read_inbox(paths)
135
- # Filter out @server messages
136
- messages = [m for m in messages if m.get("from") != "@server"]
137
-
138
- if messages:
139
- # Update timestamp
140
- max_ts = max(m.get("ts", 0) for m in messages)
141
- set_last_ts(paths, max_ts)
142
- # Clear semaphore
143
- try:
144
- paths["newdata"].unlink()
145
- except FileNotFoundError:
146
- pass
147
- return messages
148
-
149
- # Semaphore but no messages after filtering - clear and continue
150
- try:
151
- paths["newdata"].unlink()
152
- except FileNotFoundError:
153
- pass
154
-
155
- time.sleep(interval)
156
-
157
- return [] # Timeout
158
- finally:
159
- signal.signal(signal.SIGINT, old_handler)
160
-
161
-
162
- def main():
163
- parser = argparse.ArgumentParser(description="AgentChat daemon helper")
164
- parser.add_argument("--daemon-dir", type=Path, default=DEFAULT_DAEMON_DIR,
165
- help="Daemon directory path")
166
-
167
- subparsers = parser.add_subparsers(dest="command", required=True)
168
-
169
- # read command
170
- read_p = subparsers.add_parser("read", help="Read inbox messages")
171
- read_p.add_argument("--since", type=int, help="Only messages after this timestamp")
172
- read_p.add_argument("--limit", type=int, default=50, help="Max messages to return")
173
- read_p.add_argument("--replay", action="store_true", help="Include replay messages")
174
- read_p.add_argument("--all", action="store_true", help="Read all messages (ignore last_ts)")
175
-
176
- # send command
177
- send_p = subparsers.add_parser("send", help="Send a message")
178
- send_p.add_argument("to", help="Recipient (#channel or @agent)")
179
- send_p.add_argument("content", help="Message content")
180
-
181
- # check command - read new and update timestamp
182
- check_p = subparsers.add_parser("check", help="Check for new messages and update timestamp")
183
- check_p.add_argument("--no-update", action="store_true", help="Don't update last_ts")
184
-
185
- # ts command - get/set timestamp
186
- ts_p = subparsers.add_parser("ts", help="Get or set last timestamp")
187
- ts_p.add_argument("value", nargs="?", type=int, help="Set timestamp to this value")
188
-
189
- # poll command - efficient check using semaphore
190
- poll_p = subparsers.add_parser("poll", help="Poll for new messages (uses semaphore, silent if none)")
191
-
192
- # wait command - block until messages arrive
193
- wait_p = subparsers.add_parser("wait", help="Block until new messages arrive")
194
- wait_p.add_argument("--interval", type=float, default=2.0, help="Poll interval in seconds")
195
- wait_p.add_argument("--timeout", type=float, default=300.0, help="Max wait time in seconds")
196
-
197
- args = parser.parse_args()
198
- paths = get_paths(args.daemon_dir)
199
-
200
- if args.command == "read":
201
- since = 0 if args.all else (args.since if args.since else get_last_ts(paths))
202
- messages = read_inbox(paths, since_ts=since, limit=args.limit, include_replay=args.replay)
203
- for msg in messages:
204
- print(json.dumps(msg))
205
-
206
- elif args.command == "send":
207
- send_message(paths, args.to, args.content)
208
- print(f"Sent to {args.to}")
209
-
210
- elif args.command == "check":
211
- messages = check_new(paths, update_ts=not args.no_update)
212
- for msg in messages:
213
- print(json.dumps(msg))
214
- if not messages:
215
- print("No new messages", file=sys.stderr)
216
-
217
- elif args.command == "ts":
218
- if args.value is not None:
219
- set_last_ts(paths, args.value)
220
- print(f"Set last_ts to {args.value}")
221
- else:
222
- print(get_last_ts(paths))
223
-
224
- elif args.command == "poll":
225
- messages = poll_new(paths)
226
- if messages is None:
227
- # No semaphore = no new data, exit silently
228
- pass
229
- elif messages:
230
- for msg in messages:
231
- print(json.dumps(msg))
232
- # Empty list = semaphore existed but no new messages after filtering
233
-
234
- elif args.command == "wait":
235
- messages = wait_for_messages(paths, interval=args.interval, timeout=args.timeout)
236
- for msg in messages:
237
- print(json.dumps(msg))
238
-
239
-
240
- if __name__ == "__main__":
241
- main()