@agentsdock/server 1.0.7-beta.5 → 1.0.8-beta.2

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 (45) hide show
  1. package/README.md +20 -6
  2. package/package.json +1 -1
  3. package/server/VERSION +1 -1
  4. package/server/agent_server.py +5283 -698
  5. package/server/agentsdock_chats.py +2 -1
  6. package/server/chat_mailbox.py +23 -6
  7. package/server/claude_history_repair.py +45 -4
  8. package/server/claude_model_catalog.py +200 -0
  9. package/server/claude_sdk_client.py +63 -24
  10. package/server/claude_side_question.py +1 -8
  11. package/server/codex_app_server.py +90 -30
  12. package/server/codex_auth.py +119 -0
  13. package/server/codex_history_repair.py +79 -14
  14. package/server/codex_provider.py +160 -77
  15. package/server/codex_side_question.py +154 -57
  16. package/server/cursor_agent_client.py +27 -1
  17. package/server/cursor_api_key.py +64 -0
  18. package/server/cursor_history.py +219 -0
  19. package/server/cursor_provider_mcp.py +290 -0
  20. package/server/execution_durability.py +10 -6
  21. package/server/execution_preparation.py +6 -2
  22. package/server/install.sh +282 -78
  23. package/server/instances.sh +15 -0
  24. package/server/interactive_chat_native.py +4 -3
  25. package/server/interactive_chat_projection.py +6 -19
  26. package/server/interactive_chat_share_routes.py +71 -45
  27. package/server/interactive_chat_share_web.py +2 -2
  28. package/server/interactive_chat_shares.py +6 -11
  29. package/server/local_session_ownership.py +232 -0
  30. package/server/opencode_agent_client.py +685 -0
  31. package/server/provider_commands.py +579 -0
  32. package/server/provider_connections.py +665 -0
  33. package/server/provider_usage.py +241 -0
  34. package/server/public_chat_shares.py +60 -62
  35. package/server/public_chat_transcript.py +9 -31
  36. package/server/pyproject.toml +1 -0
  37. package/server/server_instances.py +931 -0
  38. package/server/shared_chat_video_stream.py +21 -2
  39. package/server/shared_chat_videos.py +54 -24
  40. package/server/side_questions.py +493 -10
  41. package/server/title_generation.py +4 -1
  42. package/server/uninstall.sh +95 -17
  43. package/server/update_preparation.py +4 -1
  44. package/server/update_runner.py +16 -0
  45. package/server/uv.lock +3 -1
@@ -18,6 +18,7 @@ from starlette.responses import Response, StreamingResponse
18
18
 
19
19
 
20
20
  VIDEO_CONTENT_TYPES = frozenset({"video/mp4", "video/webm", "video/quicktime", "video/ogg"})
21
+ IMAGE_CONTENT_TYPES = frozenset({"image/png", "image/jpeg", "image/gif", "image/webp", "image/avif", "image/bmp"})
21
22
  VIDEO_CHUNK_BYTES = 64 * 1024
22
23
  REAUTHORIZE_INTERVAL_SECONDS = 1.0
23
24
  _RANGE = re.compile(r"bytes=([0-9]*)-([0-9]*)", re.IGNORECASE)
@@ -106,6 +107,9 @@ class SharedVideoResponse(StreamingResponse):
106
107
  at construction, before headers, and before/after every bounded read.
107
108
  """
108
109
 
110
+ content_types = VIDEO_CONTENT_TYPES
111
+ disposition = "inline"
112
+
109
113
  def __init__(self, fd, *, byte_size, content_type, filename, request,
110
114
  reauthorize=None, extra_headers=None, admission=None, file_revision=None):
111
115
  self._fd = fd
@@ -113,7 +117,9 @@ class SharedVideoResponse(StreamingResponse):
113
117
  self._called = False
114
118
  try:
115
119
  if (type(fd) is not int or fd < 0 or type(byte_size) is not int
116
- or byte_size < 0 or content_type not in VIDEO_CONTENT_TYPES):
120
+ or byte_size < 0 or not isinstance(content_type, str)
121
+ or re.fullmatch(r"[a-zA-Z0-9!#$&^_.+-]+/[a-zA-Z0-9!#$&^_.+-]+", content_type) is None
122
+ or (self.content_types is not None and content_type not in self.content_types)):
117
123
  raise ValueError("Invalid shared video")
118
124
  info = os.fstat(fd)
119
125
  if not stat.S_ISREG(info.st_mode) or info.st_size != byte_size:
@@ -149,7 +155,7 @@ class SharedVideoResponse(StreamingResponse):
149
155
  headers["content-range"] = f"bytes */{byte_size}"
150
156
  else:
151
157
  headers["content-type"] = content_type
152
- headers["content-disposition"] = "inline; filename*=UTF-8''" + quote(filename, safe="")
158
+ headers["content-disposition"] = self.disposition + "; filename*=UTF-8''" + quote(filename, safe="")
153
159
  if status == 206:
154
160
  headers["content-range"] = f"bytes {self._start}-{self._start + self._length - 1}/{byte_size}"
155
161
  headers["content-length"] = str(self._length)
@@ -260,3 +266,16 @@ class SharedVideoResponse(StreamingResponse):
260
266
  self.close()
261
267
  if admitted:
262
268
  self._admission.release()
269
+
270
+
271
+ class SharedFileResponse(SharedVideoResponse):
272
+ """Download any registered attachment using the same descriptor lifecycle."""
273
+
274
+ content_types = None
275
+ disposition = "attachment"
276
+
277
+
278
+ class SharedImageResponse(SharedVideoResponse):
279
+ """Only raster images are embedded at the shared chat's origin."""
280
+
281
+ content_types = IMAGE_CONTENT_TYPES
@@ -1,6 +1,6 @@
1
- """Read-only, signed references to videos already published in one chat.
1
+ """Read-only, signed references to attachments already published in one chat.
2
2
 
3
- No workspace paths, file discovery, registry writes, or video copies. Capabilities
3
+ No workspace paths, file discovery, registry writes, or file copies. Capabilities
4
4
  pin the registered copy's metadata and inode revision; changed files fail closed.
5
5
  """
6
6
  from __future__ import annotations
@@ -9,6 +9,7 @@ import base64
9
9
  import hashlib
10
10
  import hmac
11
11
  import json
12
+ import mimetypes
12
13
  import os
13
14
  from pathlib import Path
14
15
  import re
@@ -16,6 +17,8 @@ import stat
16
17
 
17
18
  VIDEO_TYPES = frozenset({"video/mp4", "video/webm", "video/quicktime", "video/ogg"})
18
19
  VIDEO_ID = re.compile(r"video_[A-Za-z0-9_-]+\.[a-f0-9]{64}\Z")
20
+ SHARED_FILE_ID = re.compile(r"shared_file_[A-Za-z0-9_-]+\.[a-f0-9]{64}\Z")
21
+ CONTENT_TYPE = re.compile(r"[a-zA-Z0-9!#$&^_.+-]+/[a-zA-Z0-9!#$&^_.+-]+\Z")
19
22
  FILE_ID = re.compile(r"(?:file|art)_[a-f0-9]{16,32}\Z")
20
23
  SESSION_ID = re.compile(r"[A-Za-z0-9_-]{1,128}\Z")
21
24
  EXTENSIONS = {".mp4": "video/mp4", ".m4v": "video/mp4", ".webm": "video/webm",
@@ -27,28 +30,38 @@ class SharedVideoUnavailable(ValueError):
27
30
  super().__init__("Shared video is unavailable")
28
31
 
29
32
 
30
- def normalize_shared_chat_videos(value):
31
- if not isinstance(value, list) or len(value) > 32:
33
+ def _normalize_shared_chat_files(value, *, videos):
34
+ if not isinstance(value, list):
32
35
  raise ValueError("Invalid shared videos")
33
36
  output, seen = [], set()
34
37
  for item in value:
35
38
  if not isinstance(item, dict) or set(item) != {"id", "filename", "content_type", "size"}:
36
39
  raise ValueError("Invalid shared video")
37
40
  identity, name = item["id"], item["filename"]
38
- if (not isinstance(identity, str) or len(identity) > 1024 or VIDEO_ID.fullmatch(identity) is None
41
+ pattern = VIDEO_ID if videos else SHARED_FILE_ID
42
+ if (not isinstance(identity, str) or len(identity) > 1024 or pattern.fullmatch(identity) is None
39
43
  or identity in seen or not isinstance(name, str) or not 1 <= len(name) <= 255
40
44
  or name in {".", ".."} or any(ord(c) < 32 or ord(c) == 127 or c in "/\\" for c in name)
41
- or len(name.encode("utf-8")) > 1024 or not isinstance(item["content_type"], str) or item["content_type"] not in VIDEO_TYPES
42
- or type(item["size"]) is not int or not 0 < item["size"] <= (1 << 53) - 1):
45
+ or len(name.encode("utf-8")) > 1024 or not isinstance(item["content_type"], str)
46
+ or (item["content_type"] not in VIDEO_TYPES if videos else CONTENT_TYPE.fullmatch(item["content_type"]) is None)
47
+ or type(item["size"]) is not int or not (1 if videos else 0) <= item["size"] <= (1 << 53) - 1):
43
48
  raise ValueError("Invalid shared video")
44
49
  output.append(dict(item)); seen.add(identity)
45
50
  return output
46
51
 
47
52
 
48
- def _key(secret):
53
+ def normalize_shared_chat_videos(value):
54
+ return _normalize_shared_chat_files(value, videos=True)
55
+
56
+
57
+ def normalize_shared_chat_files(value):
58
+ return _normalize_shared_chat_files(value, videos=False)
59
+
60
+
61
+ def _key(secret, *, videos=True):
49
62
  if not isinstance(secret, str) or not secret:
50
63
  raise SharedVideoUnavailable()
51
- return hmac.new(secret.encode("utf-8"), b"agentsdock-chat-video-v1", hashlib.sha256).digest()
64
+ return hmac.new(secret.encode("utf-8"), b"agentsdock-chat-video-v1" if videos else b"agentsdock-chat-file-v1", hashlib.sha256).digest()
52
65
 
53
66
 
54
67
  def _regular(info, owner):
@@ -56,7 +69,7 @@ def _regular(info, owner):
56
69
  raise SharedVideoUnavailable()
57
70
 
58
71
 
59
- def _open_registered(root, session_id, file_id, *, legacy_owner=False):
72
+ def _open_registered(root, session_id, file_id, *, legacy_owner=False, videos=True):
60
73
  """Open through pinned directories; return an owned FD, never reopen a path."""
61
74
  if (not isinstance(session_id, str) or SESSION_ID.fullmatch(session_id) is None
62
75
  or not isinstance(file_id, str) or FILE_ID.fullmatch(file_id) is None):
@@ -91,16 +104,16 @@ def _open_registered(root, session_id, file_id, *, legacy_owner=False):
91
104
  or any(ord(c) < 32 or ord(c) == 127 or c in "/\\" for c in name)
92
105
  or len(name.encode("utf-8")) > 1024 or meta.get("path") != str(root / file_id / name)):
93
106
  raise SharedVideoUnavailable()
94
- guessed = EXTENSIONS.get(Path(name).suffix.lower())
107
+ guessed = EXTENSIONS.get(Path(name).suffix.lower()) if videos else mimetypes.guess_type(name)[0]
95
108
  mime = str(meta.get("content_type") or "").split(";", 1)[0].strip().lower()
96
109
  if mime in {"", "application/octet-stream", "binary/octet-stream"}:
97
- mime = guessed
98
- if mime not in VIDEO_TYPES or mime != guessed:
110
+ mime = guessed or "application/octet-stream"
111
+ if (videos and (mime not in VIDEO_TYPES or mime != guessed)) or CONTENT_TYPE.fullmatch(mime) is None:
99
112
  raise SharedVideoUnavailable()
100
113
  video_fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=directory_fd)
101
114
  info = os.fstat(video_fd)
102
115
  _regular(info, owner)
103
- if type(meta.get("size")) is not int or meta["size"] != info.st_size or not 0 < info.st_size <= (1 << 53) - 1:
116
+ if type(meta.get("size")) is not int or meta["size"] != info.st_size or not (1 if videos else 0) <= info.st_size <= (1 << 53) - 1:
104
117
  raise SharedVideoUnavailable()
105
118
  revision = hashlib.sha256(json.dumps([name, mime, meta.get("session_id"), info.st_dev, info.st_ino,
106
119
  info.st_size, info.st_mtime_ns, info.st_ctime_ns, info.st_uid, info.st_mode],
@@ -118,32 +131,41 @@ def _open_registered(root, session_id, file_id, *, legacy_owner=False):
118
131
  os.close(fd)
119
132
 
120
133
 
121
- def shared_chat_video_descriptor(root, secret, session_id, file_id, *, legacy_owner=False):
122
- key = _key(secret)
123
- opened = _open_registered(root, session_id, file_id, legacy_owner=legacy_owner)
134
+ def _shared_chat_descriptor(root, secret, session_id, file_id, *, legacy_owner=False, videos=True):
135
+ key = _key(secret, videos=videos)
136
+ opened = _open_registered(root, session_id, file_id, legacy_owner=legacy_owner, videos=videos)
124
137
  try:
125
138
  payload = json.dumps([1, session_id, file_id, opened["revision"]], separators=(",", ":")).encode("ascii")
126
139
  encoded = base64.urlsafe_b64encode(payload).rstrip(b"=").decode("ascii")
127
- identity = "video_" + encoded + "." + hmac.new(key, encoded.encode("ascii"), hashlib.sha256).hexdigest()
128
- return normalize_shared_chat_videos([{key: opened[key] for key in ("filename", "content_type", "size")} | {"id": identity}])[0]
140
+ identity = ("video_" if videos else "shared_file_") + encoded + "." + hmac.new(key, encoded.encode("ascii"), hashlib.sha256).hexdigest()
141
+ return _normalize_shared_chat_files([{key: opened[key] for key in ("filename", "content_type", "size")} | {"id": identity}], videos=videos)[0]
129
142
  finally:
130
143
  os.close(opened["file_fd"])
131
144
 
132
145
 
133
- def open_shared_chat_video(root, secret, session_id, identity):
146
+ def shared_chat_video_descriptor(root, secret, session_id, file_id, *, legacy_owner=False):
147
+ return _shared_chat_descriptor(root, secret, session_id, file_id, legacy_owner=legacy_owner)
148
+
149
+
150
+ def shared_chat_file_descriptor(root, secret, session_id, file_id, *, legacy_owner=False):
151
+ return _shared_chat_descriptor(root, secret, session_id, file_id, legacy_owner=legacy_owner, videos=False)
152
+
153
+
154
+ def _open_shared_chat_file(root, secret, session_id, identity, *, videos):
134
155
  opened = None
135
156
  try:
136
- if not isinstance(identity, str) or len(identity) > 1024 or VIDEO_ID.fullmatch(identity) is None:
157
+ pattern = VIDEO_ID if videos else SHARED_FILE_ID
158
+ if not isinstance(identity, str) or len(identity) > 1024 or pattern.fullmatch(identity) is None:
137
159
  raise SharedVideoUnavailable()
138
- encoded, signature = identity[len("video_"):].split(".")
139
- expected = hmac.new(_key(secret), encoded.encode("ascii"), hashlib.sha256).hexdigest()
160
+ encoded, signature = identity[len("video_" if videos else "shared_file_"):].split(".")
161
+ expected = hmac.new(_key(secret, videos=videos), encoded.encode("ascii"), hashlib.sha256).hexdigest()
140
162
  if not hmac.compare_digest(signature, expected):
141
163
  raise SharedVideoUnavailable()
142
164
  payload = json.loads(base64.b64decode(encoded + "=" * (-len(encoded) % 4), altchars=b"-_", validate=True))
143
165
  if (not isinstance(payload, list) or len(payload) != 4 or type(payload[0]) is not int or payload[0] != 1
144
166
  or payload[1] != session_id or not isinstance(payload[3], str)):
145
167
  raise SharedVideoUnavailable()
146
- opened = _open_registered(root, session_id, payload[2], legacy_owner=True)
168
+ opened = _open_registered(root, session_id, payload[2], legacy_owner=True, videos=videos)
147
169
  if not hmac.compare_digest(opened.pop("revision"), payload[3]):
148
170
  raise SharedVideoUnavailable()
149
171
  result, opened = opened, None
@@ -153,3 +175,11 @@ def open_shared_chat_video(root, secret, session_id, identity):
153
175
  finally:
154
176
  if opened is not None:
155
177
  os.close(opened["file_fd"])
178
+
179
+
180
+ def open_shared_chat_video(root, secret, session_id, identity):
181
+ return _open_shared_chat_file(root, secret, session_id, identity, videos=True)
182
+
183
+
184
+ def open_shared_chat_file(root, secret, session_id, identity):
185
+ return _open_shared_chat_file(root, secret, session_id, identity, videos=False)