@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.
- package/README.md +20 -6
- package/package.json +1 -1
- package/server/VERSION +1 -1
- package/server/agent_server.py +5283 -698
- package/server/agentsdock_chats.py +2 -1
- package/server/chat_mailbox.py +23 -6
- package/server/claude_history_repair.py +45 -4
- package/server/claude_model_catalog.py +200 -0
- package/server/claude_sdk_client.py +63 -24
- package/server/claude_side_question.py +1 -8
- package/server/codex_app_server.py +90 -30
- package/server/codex_auth.py +119 -0
- package/server/codex_history_repair.py +79 -14
- package/server/codex_provider.py +160 -77
- package/server/codex_side_question.py +154 -57
- package/server/cursor_agent_client.py +27 -1
- package/server/cursor_api_key.py +64 -0
- package/server/cursor_history.py +219 -0
- package/server/cursor_provider_mcp.py +290 -0
- package/server/execution_durability.py +10 -6
- package/server/execution_preparation.py +6 -2
- package/server/install.sh +282 -78
- package/server/instances.sh +15 -0
- package/server/interactive_chat_native.py +4 -3
- package/server/interactive_chat_projection.py +6 -19
- package/server/interactive_chat_share_routes.py +71 -45
- package/server/interactive_chat_share_web.py +2 -2
- package/server/interactive_chat_shares.py +6 -11
- package/server/local_session_ownership.py +232 -0
- package/server/opencode_agent_client.py +685 -0
- package/server/provider_commands.py +579 -0
- package/server/provider_connections.py +665 -0
- package/server/provider_usage.py +241 -0
- package/server/public_chat_shares.py +60 -62
- package/server/public_chat_transcript.py +9 -31
- package/server/pyproject.toml +1 -0
- package/server/server_instances.py +931 -0
- package/server/shared_chat_video_stream.py +21 -2
- package/server/shared_chat_videos.py +54 -24
- package/server/side_questions.py +493 -10
- package/server/title_generation.py +4 -1
- package/server/uninstall.sh +95 -17
- package/server/update_preparation.py +4 -1
- package/server/update_runner.py +16 -0
- 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
|
|
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"] = "
|
|
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
|
|
1
|
+
"""Read-only, signed references to attachments already published in one chat.
|
|
2
2
|
|
|
3
|
-
No workspace paths, file discovery, registry writes, or
|
|
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
|
|
31
|
-
if not isinstance(value, list)
|
|
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
|
-
|
|
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)
|
|
42
|
-
or
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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)
|