@agentsdock/server 1.0.11-beta.2 → 1.0.12-beta.1
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/package.json +1 -1
- package/server/VERSION +1 -1
- package/server/agent_server.py +1608 -351
- package/server/agentsdock_notify.py +123 -0
- package/server/chat_mailbox.py +12 -7
- package/server/claude_sdk_client.py +585 -317
- package/server/codex_app_server.py +19 -10
- package/server/codex_side_question.py +85 -7
- package/server/cursor_agent_client.py +22 -2
- package/server/install.sh +20 -3
- package/server/interactive_chat_share_routes.py +1 -1
- package/server/interactive_chat_share_web.py +2 -2
- package/server/npm/agentsdock/QR-NOTICES.txt +56 -0
- package/server/npm/agentsdock/pair.cjs +123 -0
- package/server/npm/agentsdock/qr-renderer.cjs +15 -0
- package/server/public_chat_share_routes.py +8 -6
- package/server/public_chat_shares.py +17 -7
- package/server/public_chat_transcript.py +11 -3
- package/server/session_unread.py +184 -0
- package/server/shared_chat_analytics_web.py +5 -0
- package/server/side_questions.py +46 -14
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Send a run-scoped notification whose level is chosen by the agent."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import urllib.request
|
|
13
|
+
import uuid
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from agentsdock_emergency import (
|
|
17
|
+
EmergencyCLIError,
|
|
18
|
+
NoRedirectHandler,
|
|
19
|
+
clean_emergency_message,
|
|
20
|
+
nonempty_chat_id,
|
|
21
|
+
required_environment,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def send_notification(
|
|
26
|
+
level: str,
|
|
27
|
+
message: str,
|
|
28
|
+
*,
|
|
29
|
+
authority_file: str | None = None,
|
|
30
|
+
chat_id: str | None = None,
|
|
31
|
+
request_id: str | None = None,
|
|
32
|
+
) -> dict[str, Any]:
|
|
33
|
+
clean_message = clean_emergency_message(message)
|
|
34
|
+
if not clean_message or len(clean_message) > 500:
|
|
35
|
+
raise EmergencyCLIError("--message must contain 1 to 500 characters")
|
|
36
|
+
if level not in {"normal", "important", "emergency"}:
|
|
37
|
+
raise EmergencyCLIError("--level must be normal, important, or emergency")
|
|
38
|
+
server_url, authority_chat_id, token = required_environment(authority_file, chat_id)
|
|
39
|
+
request_id = request_id or f"note_{uuid.uuid4().hex}"
|
|
40
|
+
if not re.fullmatch(r"[A-Za-z0-9._:-]{8,128}", request_id):
|
|
41
|
+
raise EmergencyCLIError("--request-id is invalid")
|
|
42
|
+
body = json.dumps({"request_id": request_id, "level": level, "message": clean_message},
|
|
43
|
+
separators=(",", ":")).encode("utf-8")
|
|
44
|
+
url = f"{server_url}/api/agent/sessions/{urllib.parse.quote(authority_chat_id, safe='')}/notifications"
|
|
45
|
+
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirectHandler())
|
|
46
|
+
decoded: Any = None
|
|
47
|
+
ambiguous_error: BaseException | None = None
|
|
48
|
+
for attempt in range(2):
|
|
49
|
+
request = urllib.request.Request(
|
|
50
|
+
url, data=body,
|
|
51
|
+
headers={
|
|
52
|
+
"Accept": "application/json",
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
"X-AgentsDock-Provider-Capability": token,
|
|
55
|
+
**({"X-AgentsDock-Notification-Retry": "1"} if attempt else {}),
|
|
56
|
+
},
|
|
57
|
+
method="POST",
|
|
58
|
+
)
|
|
59
|
+
try:
|
|
60
|
+
with opener.open(request, timeout=30) as response:
|
|
61
|
+
decoded = json.loads(response.read().decode("utf-8"))
|
|
62
|
+
ambiguous_error = None
|
|
63
|
+
break
|
|
64
|
+
except urllib.error.HTTPError as exc:
|
|
65
|
+
raw = exc.read().decode("utf-8", errors="replace")
|
|
66
|
+
try:
|
|
67
|
+
detail = json.loads(raw).get("detail") or raw
|
|
68
|
+
except json.JSONDecodeError:
|
|
69
|
+
detail = raw
|
|
70
|
+
raise EmergencyCLIError(
|
|
71
|
+
f"server rejected notification ({exc.code}): {detail or exc.reason}"
|
|
72
|
+
) from exc
|
|
73
|
+
except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
|
|
74
|
+
ambiguous_error = exc
|
|
75
|
+
if ambiguous_error is not None:
|
|
76
|
+
reason = getattr(ambiguous_error, "reason", ambiguous_error)
|
|
77
|
+
raise EmergencyCLIError(f"could not confirm whether the notification was sent: {reason}") from ambiguous_error
|
|
78
|
+
if (
|
|
79
|
+
not isinstance(decoded, dict)
|
|
80
|
+
or decoded.get("ok") is not True
|
|
81
|
+
or decoded.get("chat_id") != authority_chat_id
|
|
82
|
+
or decoded.get("level") != level
|
|
83
|
+
or not isinstance(decoded.get("event_id"), str)
|
|
84
|
+
or not decoded["event_id"]
|
|
85
|
+
or not isinstance(decoded.get("event_seq"), int)
|
|
86
|
+
or isinstance(decoded["event_seq"], bool)
|
|
87
|
+
or decoded["event_seq"] <= 0
|
|
88
|
+
or not isinstance(decoded.get("duplicate"), bool)
|
|
89
|
+
):
|
|
90
|
+
raise EmergencyCLIError("AgentsServer returned an invalid notification receipt")
|
|
91
|
+
return decoded
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
95
|
+
parser = argparse.ArgumentParser(description="Notify the user from the current AgentsDock turn", allow_abbrev=False)
|
|
96
|
+
parser.add_argument("--authority-file")
|
|
97
|
+
parser.add_argument("--chat-id", type=nonempty_chat_id)
|
|
98
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
99
|
+
send = commands.add_parser("send", allow_abbrev=False)
|
|
100
|
+
send.add_argument("--level", required=True, choices=("normal", "important", "emergency"))
|
|
101
|
+
send.add_argument("--message", required=True)
|
|
102
|
+
send.add_argument("--request-id")
|
|
103
|
+
return parser
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def main(argv: list[str] | None = None) -> int:
|
|
107
|
+
args = build_parser().parse_args(argv)
|
|
108
|
+
try:
|
|
109
|
+
result = send_notification(
|
|
110
|
+
args.level, args.message,
|
|
111
|
+
authority_file=args.authority_file,
|
|
112
|
+
chat_id=args.chat_id,
|
|
113
|
+
request_id=args.request_id,
|
|
114
|
+
)
|
|
115
|
+
except EmergencyCLIError as exc:
|
|
116
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
117
|
+
return 1
|
|
118
|
+
print(json.dumps(result, indent=2, sort_keys=True))
|
|
119
|
+
return 0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
raise SystemExit(main())
|
package/server/chat_mailbox.py
CHANGED
|
@@ -136,6 +136,17 @@ def _message(row: sqlite3.Row) -> dict:
|
|
|
136
136
|
return message
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def validate_message_response_size(message: dict, *, now: str) -> None:
|
|
140
|
+
"""Require the complete readable message to fit with response metadata."""
|
|
141
|
+
readable = {**message, "read_id": "mailread_" + "0" * 32, "read_at": now}
|
|
142
|
+
try:
|
|
143
|
+
size = len(json.dumps(readable, ensure_ascii=False, separators=(",", ":")).encode("utf-8"))
|
|
144
|
+
except UnicodeEncodeError as exc:
|
|
145
|
+
raise MailboxConflict("Message and user delegation must be valid UTF-8") from exc
|
|
146
|
+
if 8192 + size > MAX_PAGE_BYTES:
|
|
147
|
+
raise MailboxConflict("Message and source user instruction exceed the bounded mailbox response")
|
|
148
|
+
|
|
149
|
+
|
|
139
150
|
def store_message(connection: sqlite3.Connection, message_id: str, *, now: str,
|
|
140
151
|
in_reply_to_message_id: str | None = None) -> dict:
|
|
141
152
|
"""Register one already-authorized stored envelope in the same transaction."""
|
|
@@ -173,13 +184,7 @@ def store_message(connection: sqlite3.Connection, message_id: str, *, now: str,
|
|
|
173
184
|
raise MailboxConflict("Stored mailbox identity changed")
|
|
174
185
|
message = _message(row)
|
|
175
186
|
# Include all provenance in the same bounded response before accepting.
|
|
176
|
-
|
|
177
|
-
try:
|
|
178
|
-
size = len(json.dumps(readable, ensure_ascii=False, separators=(",", ":")).encode("utf-8"))
|
|
179
|
-
except UnicodeEncodeError as exc:
|
|
180
|
-
raise MailboxConflict("Message and user delegation must be valid UTF-8") from exc
|
|
181
|
-
if 8192 + size > MAX_PAGE_BYTES:
|
|
182
|
-
raise MailboxConflict("Message and source user instruction exceed the bounded mailbox response")
|
|
187
|
+
validate_message_response_size(message, now=now)
|
|
183
188
|
return message
|
|
184
189
|
|
|
185
190
|
|