@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,212 @@
1
+ """Claude Code's native /btw control on an already connected SDK client.
2
+
3
+ Claude Code 2.1.261 supports ``side_question`` and request-scoped
4
+ ``control_cancel_request`` on its stream-json control channel. Python Agent SDK
5
+ 0.2.130 does not wrap this control yet. This small compatibility adapter uses
6
+ the SDK's existing response router; it never consumes the main message stream,
7
+ submits a user turn, forks a session, or interrupts/disconnects the parent.
8
+
9
+ The caller must run on the client owner's event loop and hold its lifecycle
10
+ lease for this entire await. The native provider snapshots its current full
11
+ conversation (including tool results), denies tools, and skips transcript writes.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ from contextlib import suppress
17
+ import json
18
+ import re
19
+ import uuid
20
+ from typing import Any
21
+
22
+ from side_questions import (
23
+ MAX_OUTPUT_BYTES,
24
+ MAX_QUESTION_CHARS,
25
+ REQUEST_TIMEOUT_SECONDS,
26
+ SideQuestionError,
27
+ )
28
+
29
+
30
+ CONTEXT_NOTE = (
31
+ "Uses Claude's native conversation context, including completed tool results. "
32
+ "The reply currently being written may not be included. "
33
+ "Side questions have no tools and do not enter the main conversation."
34
+ )
35
+ CANCEL_TIMEOUT_SECONDS = 5.0
36
+ _OWNED_REQUEST_ID = re.compile(r"agentsdock_side_[0-9a-f]{32}\Z")
37
+
38
+
39
+ def is_native_side_question_progress(message: Any) -> bool:
40
+ """Keep only our native control progress out of the main-turn stream.
41
+
42
+ Recognize the reserved request namespace even after cancellation removes
43
+ its pending entry, since a late progress packet still belongs to the side
44
+ request. Other native controls and ordinary system messages remain visible.
45
+ """
46
+ if isinstance(message, dict):
47
+ if message.get("type") != "system":
48
+ return False
49
+ subtype, data = message.get("subtype"), message
50
+ else:
51
+ subtype, data = getattr(message, "subtype", None), getattr(message, "data", None)
52
+ if subtype != "control_request_progress" or not isinstance(data, dict):
53
+ return False
54
+ request_id = data.get("request_id")
55
+ return isinstance(request_id, str) and _OWNED_REQUEST_ID.fullmatch(request_id) is not None
56
+
57
+
58
+ def _native_query(client: Any) -> Any:
59
+ """Fail closed if this SDK cannot route our separate native control."""
60
+ query = getattr(client, "_query", None)
61
+ if (
62
+ query is None
63
+ or not getattr(query, "is_streaming_mode", False)
64
+ or not getattr(query, "_initialized", False)
65
+ or getattr(query, "_closed", True)
66
+ or not isinstance(getattr(query, "pending_control_responses", None), dict)
67
+ or not isinstance(getattr(query, "pending_control_results", None), dict)
68
+ or not callable(getattr(getattr(query, "transport", None), "write", None))
69
+ ):
70
+ raise SideQuestionError(503, "Claude native side questions require a connected compatible SDK session")
71
+ return query
72
+
73
+
74
+ def _request(question: str, history: list[dict] | None) -> dict:
75
+ if not isinstance(question, str) or not question.strip() or len(question) > MAX_QUESTION_CHARS:
76
+ raise SideQuestionError(400, "Side questions must contain 1–8000 characters")
77
+ try:
78
+ question.encode("utf-8")
79
+ except UnicodeEncodeError:
80
+ raise SideQuestionError(400, "Side questions must contain valid Unicode text") from None
81
+ history = [] if history is None else history
82
+ if not isinstance(history, list) or len(history) > 20:
83
+ raise SideQuestionError(400, "Native side history must contain at most 20 exchanges")
84
+ pairs = []
85
+ for item in history:
86
+ if not isinstance(item, dict) or set(item) != {"question", "response"}:
87
+ raise SideQuestionError(400, "Native side history must contain question/response pairs")
88
+ for text in item.values():
89
+ if not isinstance(text, str) or not text.strip():
90
+ raise SideQuestionError(400, "Native side-history exchanges must contain text")
91
+ try:
92
+ text.encode("utf-8")
93
+ except UnicodeEncodeError:
94
+ raise SideQuestionError(400, "Native side history must contain valid Unicode text") from None
95
+ pairs.append(dict(item))
96
+ request = {"subtype": "side_question", "question": question}
97
+ if pairs:
98
+ request["history"] = pairs
99
+ return request
100
+
101
+
102
+ def _answer(result: Any) -> dict:
103
+ # Provider errors can contain paths, credentials or raw upstream content.
104
+ # They are deliberately not returned to an HTTP caller.
105
+ if isinstance(result, Exception):
106
+ raise SideQuestionError(503, "Claude native side question failed or is unsupported by this CLI")
107
+ if not isinstance(result, dict) or result.get("subtype") != "success":
108
+ raise SideQuestionError(502, "Claude returned an invalid native side-question response")
109
+ payload = result.get("response")
110
+ if not isinstance(payload, dict):
111
+ raise SideQuestionError(502, "Claude returned an invalid native side-question response")
112
+ answer = payload.get("response")
113
+ if payload.get("synthetic") is not False:
114
+ raise SideQuestionError(502, "Claude could not answer this side question from its current context")
115
+ if not isinstance(answer, str) or not answer.strip():
116
+ raise SideQuestionError(502, "Claude returned no native side-question answer")
117
+ try:
118
+ encoded = answer.encode("utf-8")
119
+ except UnicodeEncodeError:
120
+ raise SideQuestionError(502, "Claude returned an invalid native side-question answer") from None
121
+ if len(encoded) > MAX_OUTPUT_BYTES:
122
+ raise SideQuestionError(502, "Claude returned an oversized native side-question answer")
123
+ return {"answer": answer, "context_note": CONTEXT_NOTE}
124
+
125
+
126
+ async def ask_native_side_question(
127
+ client: Any,
128
+ question: str,
129
+ *,
130
+ history: list[dict] | None = None,
131
+ timeout_seconds: float = REQUEST_TIMEOUT_SECONDS,
132
+ ) -> dict:
133
+ """Ask native /btw without acquiring the parent's main-turn authority.
134
+
135
+ Cancellation sends only this control's opaque ID. The parent's existing SDK
136
+ reader delivers the response into the same event tables it uses for all
137
+ other controls. No second stream reader or main-turn command is created.
138
+ """
139
+ request = _request(question, history)
140
+ query = _native_query(client)
141
+ if timeout_seconds <= 0:
142
+ raise ValueError("timeout_seconds must be positive")
143
+ request_id = f"agentsdock_side_{uuid.uuid4().hex}"
144
+ event = asyncio.Event()
145
+ query.pending_control_responses[request_id] = event
146
+ wire = json.dumps({"type": "control_request", "request_id": request_id, "request": request}) + "\n"
147
+ sending = asyncio.create_task(query.transport.write(wire), name=f"claude-side-send:{request_id}")
148
+
149
+ async def exchange() -> Any:
150
+ await asyncio.shield(sending)
151
+ await event.wait()
152
+ return query.pending_control_results.get(request_id)
153
+
154
+ receiving = asyncio.create_task(exchange(), name=f"claude-side-result:{request_id}")
155
+
156
+ async def cancel_exact_request() -> None:
157
+ # Join the original write before cancellation so even a slow transport
158
+ # cannot deliver our cancellation before the side question exists.
159
+ with suppress(Exception):
160
+ await asyncio.shield(sending)
161
+ if event.is_set():
162
+ return
163
+ await query.transport.write(json.dumps({
164
+ "type": "control_cancel_request", "request_id": request_id,
165
+ }) + "\n")
166
+ await event.wait()
167
+
168
+ async def cancel_and_settle() -> None:
169
+ cleanup = asyncio.create_task(cancel_exact_request(), name=f"claude-side-cancel:{request_id}")
170
+ deadline = asyncio.get_running_loop().time() + CANCEL_TIMEOUT_SECONDS
171
+ try:
172
+ while not cleanup.done():
173
+ remaining = deadline - asyncio.get_running_loop().time()
174
+ if remaining <= 0:
175
+ break
176
+ try:
177
+ await asyncio.wait_for(asyncio.shield(cleanup), remaining)
178
+ except asyncio.CancelledError:
179
+ # A duplicate disconnect must not interrupt exact-ID cleanup.
180
+ continue
181
+ except Exception:
182
+ break
183
+ finally:
184
+ if not cleanup.done():
185
+ cleanup.cancel()
186
+ with suppress(BaseException):
187
+ await cleanup
188
+
189
+ try:
190
+ result = await asyncio.wait_for(asyncio.shield(receiving), timeout_seconds)
191
+ return _answer(result)
192
+ except asyncio.CancelledError:
193
+ await cancel_and_settle()
194
+ raise
195
+ except TimeoutError:
196
+ await cancel_and_settle()
197
+ raise SideQuestionError(504, "Claude native side question timed out") from None
198
+ except SideQuestionError:
199
+ raise
200
+ except Exception:
201
+ # A transport can report failure after delivering a frame. Attempt the
202
+ # exact native cancellation without changing the main connection.
203
+ await cancel_and_settle()
204
+ raise SideQuestionError(503, "Claude native side-question connection is unavailable") from None
205
+ finally:
206
+ query.pending_control_responses.pop(request_id, None)
207
+ query.pending_control_results.pop(request_id, None)
208
+ for task in (receiving, sending):
209
+ if not task.done():
210
+ task.cancel()
211
+ with suppress(BaseException):
212
+ await task