@agentsdock/server 1.0.7-beta.5

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 (113) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +19 -0
  3. package/README.md +60 -0
  4. package/npm/cli.cjs +289 -0
  5. package/package.json +32 -0
  6. package/server/LICENSE +202 -0
  7. package/server/NOTICE +19 -0
  8. package/server/VERSION +1 -0
  9. package/server/activation_transaction.py +2458 -0
  10. package/server/agent_server.py +93226 -0
  11. package/server/agentsdock_chats.py +1237 -0
  12. package/server/agentsdock_emergency.py +338 -0
  13. package/server/agentsdock_jobs.py +640 -0
  14. package/server/agentsdock_mail.py +325 -0
  15. package/server/agentsdock_publish.py +404 -0
  16. package/server/agentsdock_team.py +793 -0
  17. package/server/agentsdock_team_hub/__init__.py +33 -0
  18. package/server/agentsdock_team_hub/auth.py +710 -0
  19. package/server/agentsdock_team_hub/cli.py +647 -0
  20. package/server/agentsdock_team_hub/database.py +310 -0
  21. package/server/agentsdock_team_hub/mail_hint_streams.py +176 -0
  22. package/server/agentsdock_team_hub/mail_hints.py +201 -0
  23. package/server/agentsdock_team_hub/migrations/0001_identity_auth.sql +690 -0
  24. package/server/agentsdock_team_hub/migrations/0002_teamspace_ledger.sql +1336 -0
  25. package/server/agentsdock_team_hub/migrations/0003_service_runtime.sql +361 -0
  26. package/server/agentsdock_team_hub/migrations/0004_managed_host_binding.sql +22 -0
  27. package/server/agentsdock_team_hub/migrations/0005_tailnet_bootstrap_delegations.sql +63 -0
  28. package/server/agentsdock_team_hub/migrations/0006_team_network_mailbox.sql +423 -0
  29. package/server/agentsdock_team_hub/migrations/0007_local_agent_mail.sql +63 -0
  30. package/server/agentsdock_team_hub/migrations/0008_managed_server_session.sql +73 -0
  31. package/server/agentsdock_team_hub/migrations/0009_team_messages.sql +324 -0
  32. package/server/agentsdock_team_hub/migrations/0010_team_attachment_orphan_reclamation.sql +52 -0
  33. package/server/agentsdock_team_hub/migrations/0011_human_admin_paging.sql +65 -0
  34. package/server/agentsdock_team_hub/migrations/0012_network_content_deletions.sql +69 -0
  35. package/server/agentsdock_team_hub/migrations/0013_team_message_revisions.sql +48 -0
  36. package/server/agentsdock_team_hub/migrations/0014_managed_network_owner.sql +50 -0
  37. package/server/agentsdock_team_hub/migrations/0015_team_message_inbox_dismissals.sql +3 -0
  38. package/server/agentsdock_team_hub/migrations/0016_team_message_all_servers.sql +4 -0
  39. package/server/agentsdock_team_hub/migrations/0017_skill_announcement_deletions.sql +16 -0
  40. package/server/agentsdock_team_hub/migrations/0018_team_mail_subjects.sql +8 -0
  41. package/server/agentsdock_team_hub/migrations/0019_team_mailbox_state.sql +5 -0
  42. package/server/agentsdock_team_hub/migrations/0020_team_mail_arrivals.sql +44 -0
  43. package/server/agentsdock_team_hub/migrations/0021_team_mail_threads.sql +4 -0
  44. package/server/agentsdock_team_hub/migrations/0022_team_bulletin_changes.sql +49 -0
  45. package/server/agentsdock_team_hub/migrations/0023_team_message_search.sql +93 -0
  46. package/server/agentsdock_team_hub/migrations/__init__.py +1 -0
  47. package/server/agentsdock_team_hub/notification_hints.py +306 -0
  48. package/server/agentsdock_team_hub/secure_peer.py +11215 -0
  49. package/server/agentsdock_team_hub/secure_peer_hub.py +1372 -0
  50. package/server/agentsdock_team_hub/security.py +318 -0
  51. package/server/agentsdock_team_hub/service.py +1737 -0
  52. package/server/agentsdock_team_hub/store.py +16276 -0
  53. package/server/chat_mailbox.py +448 -0
  54. package/server/claude_background_reconciliation.py +92 -0
  55. package/server/claude_goals.py +245 -0
  56. package/server/claude_history_provenance.py +137 -0
  57. package/server/claude_history_repair.py +1601 -0
  58. package/server/claude_sdk_client.py +3731 -0
  59. package/server/claude_side_question.py +212 -0
  60. package/server/codex_app_server.py +3030 -0
  61. package/server/codex_auth.py +93 -0
  62. package/server/codex_history_repair.py +1247 -0
  63. package/server/codex_provider.py +1120 -0
  64. package/server/codex_side_question.py +301 -0
  65. package/server/cursor_agent_client.py +654 -0
  66. package/server/cursor_process_guard.py +347 -0
  67. package/server/execution_activation.py +610 -0
  68. package/server/execution_control.py +233 -0
  69. package/server/execution_durability.py +184 -0
  70. package/server/execution_http.py +256 -0
  71. package/server/execution_install.py +792 -0
  72. package/server/execution_legacy_runner.py +406 -0
  73. package/server/execution_maintenance.py +137 -0
  74. package/server/execution_manage.py +615 -0
  75. package/server/execution_ownership.py +83 -0
  76. package/server/execution_preparation.py +362 -0
  77. package/server/execution_recovery.py +845 -0
  78. package/server/execution_recovery_status.py +195 -0
  79. package/server/execution_service.py +387 -0
  80. package/server/execution_transport.py +622 -0
  81. package/server/execution_uninstall.py +406 -0
  82. package/server/execution_update_status.py +49 -0
  83. package/server/install.sh +7074 -0
  84. package/server/interactive_chat_controls.py +153 -0
  85. package/server/interactive_chat_native.py +66 -0
  86. package/server/interactive_chat_projection.py +152 -0
  87. package/server/interactive_chat_runtime.py +132 -0
  88. package/server/interactive_chat_share_routes.py +534 -0
  89. package/server/interactive_chat_share_web.py +12 -0
  90. package/server/interactive_chat_shares.py +238 -0
  91. package/server/provider_commands.py +425 -0
  92. package/server/public_chat_share_routes.py +426 -0
  93. package/server/public_chat_shares.py +759 -0
  94. package/server/public_chat_transcript.py +357 -0
  95. package/server/pyproject.toml +29 -0
  96. package/server/release-public-key.pem +3 -0
  97. package/server/secure_peer_delivery.py +1236 -0
  98. package/server/secure_peer_runtime.py +7694 -0
  99. package/server/shared_chat_video_stream.py +262 -0
  100. package/server/shared_chat_videos.py +155 -0
  101. package/server/side_questions.py +594 -0
  102. package/server/team_hub_host.py +1354 -0
  103. package/server/team_mail_grants.py +136 -0
  104. package/server/team_mail_runtime.py +302 -0
  105. package/server/team_mail_websocket.py +211 -0
  106. package/server/title_generation.py +333 -0
  107. package/server/uninstall.sh +359 -0
  108. package/server/update_handoff.py +237 -0
  109. package/server/update_preparation.py +264 -0
  110. package/server/update_recovery.py +140 -0
  111. package/server/update_runner.py +1819 -0
  112. package/server/uv.lock +1101 -0
  113. package/server/workspace_git.py +545 -0
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env python3
2
+ """Capability-scoped Team Network mail CLI for AgentsDock agents."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import hashlib
8
+ import ipaddress
9
+ import json
10
+ import os
11
+ import sys
12
+ import urllib.error
13
+ import urllib.parse
14
+ import urllib.request
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+
19
+ MAIL_BODY_MAX_BYTES = 8_192
20
+ PROVIDER_RUNTIME_VALUE_MAX_BYTES = 4096
21
+
22
+
23
+ class MailCLIError(RuntimeError):
24
+ pass
25
+
26
+
27
+ class NoRedirectHandler(urllib.request.HTTPRedirectHandler):
28
+ def redirect_request(self, *_args: Any, **_kwargs: Any) -> None:
29
+ return None
30
+
31
+
32
+ def _canonical_http_origin(value: str, label: str) -> tuple[str, bool]:
33
+ raw = value.strip()
34
+ try:
35
+ parsed = urllib.parse.urlsplit(raw)
36
+ port = parsed.port or 80
37
+ except ValueError as exc:
38
+ raise MailCLIError(f"{label} must be an HTTP origin") from exc
39
+ if (
40
+ parsed.scheme.lower() != "http"
41
+ or not parsed.hostname
42
+ or parsed.username is not None
43
+ or parsed.password is not None
44
+ or parsed.path not in {"", "/"}
45
+ or parsed.query
46
+ or parsed.fragment
47
+ ):
48
+ raise MailCLIError(f"{label} must be an HTTP origin")
49
+ host = parsed.hostname.lower()
50
+ try:
51
+ address = ipaddress.ip_address(host)
52
+ if isinstance(address, ipaddress.IPv6Address) and address.ipv4_mapped:
53
+ address = address.ipv4_mapped
54
+ loopback = address.is_loopback
55
+ host = address.compressed
56
+ url_host = f"[{host}]" if isinstance(address, ipaddress.IPv6Address) else host
57
+ except ValueError:
58
+ loopback = host == "localhost"
59
+ url_host = host
60
+ return f"http://{url_host}:{port}", loopback
61
+
62
+
63
+ def _authority_server_origin(authority_file: str | None) -> str:
64
+ path = _selected_authority_path(authority_file)
65
+ try:
66
+ if path.stat().st_mode & 0o077:
67
+ raise MailCLIError("authority file permissions are unsafe")
68
+ payload = json.loads(path.read_text(encoding="utf-8"))
69
+ except (OSError, json.JSONDecodeError) as exc:
70
+ raise MailCLIError(f"could not read authority file: {exc}") from exc
71
+ return _bounded_identity_value(
72
+ payload.get("provider_server_origin"),
73
+ "authority provider_server_origin",
74
+ )
75
+
76
+
77
+ def _loopback_server_url() -> str:
78
+ raw_server_url = os.environ.get("AGENTSDOCK_SERVER_URL", "").strip()
79
+ if not raw_server_url:
80
+ raise MailCLIError("missing AgentsDock agent environment")
81
+ server_origin, loopback = _canonical_http_origin(
82
+ raw_server_url,
83
+ "AGENTSDOCK_SERVER_URL",
84
+ )
85
+ runtime_origin = _bounded_identity_value(
86
+ os.environ.get("AGENTSDOCK_PROVIDER_SERVER_ORIGIN"),
87
+ "AGENTSDOCK_PROVIDER_SERVER_ORIGIN",
88
+ )
89
+ if runtime_origin:
90
+ canonical_runtime, _runtime_loopback = _canonical_http_origin(
91
+ runtime_origin,
92
+ "AGENTSDOCK_PROVIDER_SERVER_ORIGIN",
93
+ )
94
+ if canonical_runtime != server_origin:
95
+ raise MailCLIError(
96
+ "AGENTSDOCK_SERVER_URL conflicts with the live provider origin"
97
+ )
98
+ if loopback:
99
+ return raw_server_url.rstrip("/")
100
+ authority_origin = _authority_server_origin(None)
101
+ if not authority_origin:
102
+ raise MailCLIError(
103
+ "non-loopback AGENTSDOCK_SERVER_URL must match the authority origin"
104
+ )
105
+ canonical_authority, _authority_loopback = _canonical_http_origin(
106
+ authority_origin,
107
+ "authority provider_server_origin",
108
+ )
109
+ if canonical_authority != server_origin:
110
+ raise MailCLIError(
111
+ "non-loopback AGENTSDOCK_SERVER_URL must match the authority origin"
112
+ )
113
+ return server_origin
114
+
115
+
116
+ def _bounded_identity_value(value: str | None, label: str) -> str:
117
+ clean = str(value or "").strip()
118
+ try:
119
+ size = len(clean.encode("utf-8"))
120
+ except UnicodeEncodeError as exc:
121
+ raise MailCLIError(f"{label} is not valid UTF-8") from exc
122
+ if size > PROVIDER_RUNTIME_VALUE_MAX_BYTES:
123
+ raise MailCLIError(f"{label} exceeds the provider runtime limit")
124
+ return clean
125
+
126
+
127
+ def _selected_authority_path(authority_file: str | None) -> Path:
128
+ explicit = _bounded_identity_value(authority_file, "--authority-file")
129
+ ambient = _bounded_identity_value(
130
+ os.environ.get("AGENTSDOCK_PROVIDER_AUTHORITY_FILE"),
131
+ "AGENTSDOCK_PROVIDER_AUTHORITY_FILE",
132
+ )
133
+ if explicit and ambient:
134
+ explicit_key = os.path.abspath(os.path.expanduser(explicit))
135
+ ambient_key = os.path.abspath(os.path.expanduser(ambient))
136
+ if explicit_key != ambient_key:
137
+ raise MailCLIError(
138
+ "--authority-file conflicts with the live provider authority"
139
+ )
140
+ selected = explicit or ambient
141
+ if not selected:
142
+ raise MailCLIError("--authority-file is required")
143
+ return Path(selected).expanduser()
144
+
145
+
146
+ def _provider_authority(authority_file: str | None) -> tuple[str, str]:
147
+ path = _selected_authority_path(authority_file)
148
+ try:
149
+ if path.stat().st_mode & 0o077:
150
+ raise MailCLIError("authority file permissions are unsafe")
151
+ payload = json.loads(path.read_text(encoding="utf-8"))
152
+ except (OSError, json.JSONDecodeError) as exc:
153
+ raise MailCLIError(f"could not read authority file: {exc}") from exc
154
+ capability = str(payload.get("provider_capability") or payload.get("capability") or "")
155
+ source_session_id = str(payload.get("source_session_id") or "").strip()
156
+ if not capability or not source_session_id:
157
+ raise MailCLIError("authority file is invalid")
158
+ environment_chat_id = _bounded_identity_value(
159
+ os.environ.get("AGENTSDOCK_CHAT_ID"),
160
+ "AGENTSDOCK_CHAT_ID",
161
+ )
162
+ if environment_chat_id and environment_chat_id != source_session_id:
163
+ raise MailCLIError(
164
+ "AGENTSDOCK_CHAT_ID does not match the authority file"
165
+ )
166
+ return capability, source_session_id
167
+
168
+
169
+ def _request_json(
170
+ method: str,
171
+ path: str,
172
+ capability: str,
173
+ payload: dict[str, Any] | None = None,
174
+ ) -> dict[str, Any]:
175
+ body = None if payload is None else json.dumps(payload).encode("utf-8")
176
+ request = urllib.request.Request(
177
+ f"{_loopback_server_url()}{path}",
178
+ data=body,
179
+ headers={
180
+ "Accept": "application/json",
181
+ "Content-Type": "application/json",
182
+ "X-AgentsDock-Provider-Capability": capability,
183
+ },
184
+ method=method,
185
+ )
186
+ opener = urllib.request.build_opener(
187
+ urllib.request.ProxyHandler({}),
188
+ NoRedirectHandler(),
189
+ )
190
+ try:
191
+ with opener.open(request, timeout=30) as response:
192
+ result = json.loads(response.read().decode("utf-8"))
193
+ except urllib.error.HTTPError as exc:
194
+ raw = exc.read().decode("utf-8", errors="replace")
195
+ try:
196
+ detail = json.loads(raw).get("detail") or raw
197
+ except json.JSONDecodeError:
198
+ detail = raw
199
+ raise MailCLIError(
200
+ f"server rejected Team Network mail ({exc.code}): {detail or exc.reason}"
201
+ ) from exc
202
+ except (urllib.error.URLError, TimeoutError, OSError) as exc:
203
+ raise MailCLIError(
204
+ f"could not reach AgentsServer: {getattr(exc, 'reason', exc)}"
205
+ ) from exc
206
+ if not isinstance(result, dict):
207
+ raise MailCLIError("AgentsServer returned an invalid response")
208
+ return result
209
+
210
+
211
+ def list_routes(args: argparse.Namespace) -> dict[str, Any]:
212
+ capability, _session_id = _provider_authority(args.authority_file)
213
+ result = _request_json("GET", "/api/agent/team-mail/routes", capability)
214
+ routes = result.get("routes")
215
+ if (
216
+ not isinstance(routes, list)
217
+ or any(not isinstance(route, dict) for route in routes)
218
+ ):
219
+ raise MailCLIError("AgentsServer returned an invalid Team Network mail route list")
220
+ return result
221
+
222
+
223
+ def send(args: argparse.Namespace) -> dict[str, Any]:
224
+ capability, _session_id = _provider_authority(args.authority_file)
225
+ if sys.stdin.isatty():
226
+ raise MailCLIError("Team Network mail body must be provided on stdin")
227
+ input_stream = getattr(sys.stdin, "buffer", sys.stdin)
228
+ raw_message = input_stream.read(MAIL_BODY_MAX_BYTES + 1)
229
+ if isinstance(raw_message, str):
230
+ raw_message = raw_message.encode("utf-8")
231
+ if len(raw_message) > MAIL_BODY_MAX_BYTES:
232
+ raise MailCLIError("Team Network mail body exceeds the configured size limit")
233
+ try:
234
+ message = raw_message.decode("utf-8").strip()
235
+ except UnicodeDecodeError as exc:
236
+ raise MailCLIError("Team Network mail body must be valid UTF-8") from exc
237
+ if not message:
238
+ raise MailCLIError("Team Network mail body on stdin must not be empty")
239
+ route_id = str(args.route or "").strip()
240
+ kind = str(args.kind or "message")
241
+ if kind != "message":
242
+ raise MailCLIError("AgentsDock agent mail permits messages only")
243
+ stable_key = "mail_cli_" + hashlib.sha256(
244
+ f"{capability}\0{route_id}\0{kind}\0{message}".encode("utf-8")
245
+ ).hexdigest()
246
+ result = _request_json(
247
+ "POST",
248
+ f"/api/agent/team-mail/routes/{urllib.parse.quote(route_id, safe='')}",
249
+ capability,
250
+ {
251
+ "kind": kind,
252
+ "message": message,
253
+ "idempotency_key": args.idempotency_key or stable_key,
254
+ },
255
+ )
256
+ if set(result) != {"ok", "route_id", "kind", "accepted", "duplicate"}:
257
+ raise MailCLIError("AgentsServer returned an invalid Team Network mail receipt")
258
+ if (
259
+ result.get("ok") is not True
260
+ or result.get("route_id") != route_id
261
+ or result.get("kind") != kind
262
+ or result.get("accepted") is not True
263
+ or type(result.get("duplicate")) is not bool
264
+ ):
265
+ raise MailCLIError("AgentsServer returned an invalid Team Network mail receipt")
266
+ return result
267
+
268
+
269
+ def parser() -> argparse.ArgumentParser:
270
+ root = argparse.ArgumentParser(
271
+ description="Send passive Team Network mail using this live agent turn.",
272
+ allow_abbrev=False,
273
+ )
274
+ root.add_argument(
275
+ "--authority-file",
276
+ help="mode-0600 per-run AgentsDock provider authority file",
277
+ )
278
+ commands = root.add_subparsers(dest="command", required=True)
279
+ list_command = commands.add_parser(
280
+ "list", help="list this turn's opaque mail routes", allow_abbrev=False
281
+ )
282
+ list_command.set_defaults(handler=list_routes)
283
+ send_command = commands.add_parser(
284
+ "send",
285
+ help="send one passive mailbox item with its UTF-8 body on stdin",
286
+ allow_abbrev=False,
287
+ )
288
+ send_command.add_argument("--route", required=True, help="opaque route from list")
289
+ send_command.add_argument(
290
+ "--kind",
291
+ choices=("message",),
292
+ default="message",
293
+ )
294
+ send_command.add_argument("--idempotency-key", help=argparse.SUPPRESS)
295
+ send_command.set_defaults(handler=send)
296
+ return root
297
+
298
+
299
+ def main(argv: list[str] | None = None) -> int:
300
+ previous_authority_file = os.environ.get(
301
+ "AGENTSDOCK_PROVIDER_AUTHORITY_FILE"
302
+ )
303
+ try:
304
+ args = parser().parse_args(argv)
305
+ selected_authority = _selected_authority_path(args.authority_file)
306
+ os.environ["AGENTSDOCK_PROVIDER_AUTHORITY_FILE"] = str(
307
+ selected_authority
308
+ )
309
+ result = args.handler(args)
310
+ print(json.dumps(result, indent=2, sort_keys=True))
311
+ return 0
312
+ except MailCLIError as exc:
313
+ print(f"agentsdock-mail: {exc}", file=sys.stderr)
314
+ return 2
315
+ finally:
316
+ if previous_authority_file is None:
317
+ os.environ.pop("AGENTSDOCK_PROVIDER_AUTHORITY_FILE", None)
318
+ else:
319
+ os.environ[
320
+ "AGENTSDOCK_PROVIDER_AUTHORITY_FILE"
321
+ ] = previous_authority_file
322
+
323
+
324
+ if __name__ == "__main__":
325
+ raise SystemExit(main())