soothe-client-python 0.9.9__tar.gz → 0.9.10__tar.gz
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.
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/PKG-INFO +6 -3
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/README.md +5 -2
- soothe_client_python-0.9.10/src/soothe_client/__init__.py +189 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/__init__.py +9 -44
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/daemon_session.py +32 -8
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/helpers.py +5 -1
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/protocol_params.py +5 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/session.py +6 -9
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/stream_terminal.py +3 -3
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/turn_boundary.py +4 -3
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/ws_command_client.py +121 -156
- soothe_client_python-0.9.9/src/soothe_client/__init__.py +0 -194
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/.gitignore +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/LICENSE +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/pyproject.toml +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/attachments.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/broadcaster.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/chunk_filter.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/classifier.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/events.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/managed_client.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/observability.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/pool.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/query_gate.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/session_store.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/thinking_step.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/turn.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/turn_runner.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/errors.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/intent_hints.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/py.typed +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/schemas.py +0 -0
- {soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/websocket.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: soothe-client-python
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.10
|
|
4
4
|
Summary: WebSocket client + appkit for soothe-daemon (Python)
|
|
5
5
|
Project-URL: Homepage, https://github.com/mirasoth/soothe-client-python
|
|
6
6
|
Project-URL: Documentation, https://soothe.readthedocs.io
|
|
@@ -69,10 +69,13 @@ More patterns: [`examples/`](examples/) (hello → streaming → multi-turn →
|
|
|
69
69
|
|
|
70
70
|
| Need | Use |
|
|
71
71
|
|------|-----|
|
|
72
|
-
| One conversation, stream replies | `DaemonSession` |
|
|
72
|
+
| One conversation, stream replies | `DaemonSession` (`soothe_client.appkit`) |
|
|
73
|
+
| Jobs / cron (async) | `AsyncCommandClient` (alias of `WsCommandClient`) |
|
|
74
|
+
| Jobs / cron (scripts / sync) | `CommandClient` (alias of `SyncWsCommandClient`) |
|
|
73
75
|
| Raw WebSocket / custom RPCs | `WebSocketClient` |
|
|
74
76
|
| Many users / HTTP backend | `ConnectionPool` + `TurnRunner` |
|
|
75
|
-
|
|
77
|
+
|
|
78
|
+
Wire request param models live in `soothe_client.protocol_params` (not at package root).
|
|
76
79
|
|
|
77
80
|
## Develop
|
|
78
81
|
|
|
@@ -36,10 +36,13 @@ More patterns: [`examples/`](examples/) (hello → streaming → multi-turn →
|
|
|
36
36
|
|
|
37
37
|
| Need | Use |
|
|
38
38
|
|------|-----|
|
|
39
|
-
| One conversation, stream replies | `DaemonSession` |
|
|
39
|
+
| One conversation, stream replies | `DaemonSession` (`soothe_client.appkit`) |
|
|
40
|
+
| Jobs / cron (async) | `AsyncCommandClient` (alias of `WsCommandClient`) |
|
|
41
|
+
| Jobs / cron (scripts / sync) | `CommandClient` (alias of `SyncWsCommandClient`) |
|
|
40
42
|
| Raw WebSocket / custom RPCs | `WebSocketClient` |
|
|
41
43
|
| Many users / HTTP backend | `ConnectionPool` + `TurnRunner` |
|
|
42
|
-
|
|
44
|
+
|
|
45
|
+
Wire request param models live in `soothe_client.protocol_params` (not at package root).
|
|
43
46
|
|
|
44
47
|
## Develop
|
|
45
48
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Soothe WebSocket client for talking to a running soothe-daemon.
|
|
2
|
+
|
|
3
|
+
Public surface (community apps)::
|
|
4
|
+
|
|
5
|
+
from soothe_client import WebSocketClient, is_daemon_live
|
|
6
|
+
from soothe_client.appkit import DaemonSession
|
|
7
|
+
from soothe_client import AsyncCommandClient, CommandClient # preferred aliases
|
|
8
|
+
|
|
9
|
+
Advanced wire validation lives in ``soothe_client.protocol_params``.
|
|
10
|
+
Higher-level session / multi-user helpers live in ``soothe_client.appkit``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import importlib.metadata
|
|
16
|
+
import warnings
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from soothe_sdk.wire.codec import ProtocolError
|
|
20
|
+
|
|
21
|
+
from soothe_client.errors import (
|
|
22
|
+
DaemonError,
|
|
23
|
+
DisconnectCause,
|
|
24
|
+
ReconnectError,
|
|
25
|
+
StaleLoopError,
|
|
26
|
+
disconnect_cause_name,
|
|
27
|
+
)
|
|
28
|
+
from soothe_client.helpers import (
|
|
29
|
+
check_daemon_status,
|
|
30
|
+
connected_websocket,
|
|
31
|
+
fetch_config_section,
|
|
32
|
+
fetch_loop_cards,
|
|
33
|
+
fetch_loop_history,
|
|
34
|
+
fetch_loop_messages,
|
|
35
|
+
fetch_skills_catalog,
|
|
36
|
+
is_daemon_live,
|
|
37
|
+
protocol1_rpc,
|
|
38
|
+
request_daemon_config_reload,
|
|
39
|
+
request_daemon_shutdown,
|
|
40
|
+
websocket_url_from_config,
|
|
41
|
+
)
|
|
42
|
+
from soothe_client.intent_hints import (
|
|
43
|
+
DEFAULT_DELIVERABLE_PHASES,
|
|
44
|
+
EMBED,
|
|
45
|
+
IMAGE_TO_TEXT,
|
|
46
|
+
OCR,
|
|
47
|
+
TEXT_COMPLETION,
|
|
48
|
+
validate_loop_input_intent_hint,
|
|
49
|
+
)
|
|
50
|
+
from soothe_client.session import (
|
|
51
|
+
bootstrap_loop_session,
|
|
52
|
+
connect_websocket_with_retries,
|
|
53
|
+
)
|
|
54
|
+
from soothe_client.websocket import WebSocketClient
|
|
55
|
+
from soothe_client.ws_command_client import (
|
|
56
|
+
AsyncCommandClient,
|
|
57
|
+
CommandClient,
|
|
58
|
+
SyncWsCommandClient,
|
|
59
|
+
WsCommandClient,
|
|
60
|
+
async_command_client_from_config,
|
|
61
|
+
async_ws_command_client_from_config,
|
|
62
|
+
command_client_from_config,
|
|
63
|
+
ws_command_client_from_config,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
__version__ = importlib.metadata.version("soothe-client-python")
|
|
68
|
+
except importlib.metadata.PackageNotFoundError:
|
|
69
|
+
__version__ = "0.0.0"
|
|
70
|
+
|
|
71
|
+
# Legacy ``*Params`` names formerly re-exported at package root.
|
|
72
|
+
_LEGACY_PARAM_NAMES = frozenset(
|
|
73
|
+
{
|
|
74
|
+
"AuthParams",
|
|
75
|
+
"AuthRefreshParams",
|
|
76
|
+
"AutopilotCancelAllParams",
|
|
77
|
+
"AutopilotCancelGoalParams",
|
|
78
|
+
"AutopilotDreamParams",
|
|
79
|
+
"AutopilotGetGoalParams",
|
|
80
|
+
"AutopilotGetJobParams",
|
|
81
|
+
"AutopilotListGoalsParams",
|
|
82
|
+
"AutopilotListJobsParams",
|
|
83
|
+
"AutopilotResumeParams",
|
|
84
|
+
"AutopilotStatusParams",
|
|
85
|
+
"AutopilotSubmitParams",
|
|
86
|
+
"AutopilotSubscribeParams",
|
|
87
|
+
"AutopilotWakeParams",
|
|
88
|
+
"ConfigGetParams",
|
|
89
|
+
"ConfigReloadParams",
|
|
90
|
+
"CronAddParams",
|
|
91
|
+
"CronCancelParams",
|
|
92
|
+
"CronListParams",
|
|
93
|
+
"CronShowParams",
|
|
94
|
+
"DaemonShutdownParams",
|
|
95
|
+
"DaemonStatusParams",
|
|
96
|
+
"DisconnectParams",
|
|
97
|
+
"InvokeSkillParams",
|
|
98
|
+
"JobCancelParams",
|
|
99
|
+
"JobCreateParams",
|
|
100
|
+
"JobDagParams",
|
|
101
|
+
"JobGuidanceParams",
|
|
102
|
+
"JobPauseParams",
|
|
103
|
+
"JobResumeParams",
|
|
104
|
+
"JobStatusParams",
|
|
105
|
+
"LoopCardsFetchParams",
|
|
106
|
+
"LoopDeleteParams",
|
|
107
|
+
"LoopDetachParams",
|
|
108
|
+
"LoopGetParams",
|
|
109
|
+
"LoopInputParams",
|
|
110
|
+
"LoopListParams",
|
|
111
|
+
"LoopMessagesParams",
|
|
112
|
+
"LoopNewParams",
|
|
113
|
+
"LoopPruneParams",
|
|
114
|
+
"LoopReattachParams",
|
|
115
|
+
"LoopStateGetParams",
|
|
116
|
+
"LoopStateUpdateParams",
|
|
117
|
+
"LoopTreeParams",
|
|
118
|
+
"McpStatusParams",
|
|
119
|
+
"ModelsListParams",
|
|
120
|
+
"RpcCommandParams",
|
|
121
|
+
"SkillsListParams",
|
|
122
|
+
"SlashCommandParams",
|
|
123
|
+
"SubscribeParams",
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
__all__ = [
|
|
128
|
+
"__version__",
|
|
129
|
+
# Transport
|
|
130
|
+
"WebSocketClient",
|
|
131
|
+
"ProtocolError",
|
|
132
|
+
# Errors
|
|
133
|
+
"DaemonError",
|
|
134
|
+
"DisconnectCause",
|
|
135
|
+
"ReconnectError",
|
|
136
|
+
"StaleLoopError",
|
|
137
|
+
"disconnect_cause_name",
|
|
138
|
+
# Command clients (canonical + preferred aliases)
|
|
139
|
+
"WsCommandClient",
|
|
140
|
+
"SyncWsCommandClient",
|
|
141
|
+
"AsyncCommandClient",
|
|
142
|
+
"CommandClient",
|
|
143
|
+
"ws_command_client_from_config",
|
|
144
|
+
"async_ws_command_client_from_config",
|
|
145
|
+
"command_client_from_config",
|
|
146
|
+
"async_command_client_from_config",
|
|
147
|
+
# Session bootstrap
|
|
148
|
+
"bootstrap_loop_session",
|
|
149
|
+
"connect_websocket_with_retries",
|
|
150
|
+
# Helpers
|
|
151
|
+
"websocket_url_from_config",
|
|
152
|
+
"connected_websocket",
|
|
153
|
+
"protocol1_rpc",
|
|
154
|
+
"check_daemon_status",
|
|
155
|
+
"is_daemon_live",
|
|
156
|
+
"request_daemon_config_reload",
|
|
157
|
+
"request_daemon_shutdown",
|
|
158
|
+
"fetch_skills_catalog",
|
|
159
|
+
"fetch_config_section",
|
|
160
|
+
"fetch_loop_history",
|
|
161
|
+
"fetch_loop_cards",
|
|
162
|
+
"fetch_loop_messages",
|
|
163
|
+
# Intent hints
|
|
164
|
+
"TEXT_COMPLETION",
|
|
165
|
+
"IMAGE_TO_TEXT",
|
|
166
|
+
"OCR",
|
|
167
|
+
"EMBED",
|
|
168
|
+
"DEFAULT_DELIVERABLE_PHASES",
|
|
169
|
+
"validate_loop_input_intent_hint",
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def __getattr__(name: str) -> Any:
|
|
174
|
+
"""Lazy-load legacy ``*Params`` with a deprecation warning.
|
|
175
|
+
|
|
176
|
+
Prefer ``from soothe_client.protocol_params import LoopInputParams``.
|
|
177
|
+
"""
|
|
178
|
+
if name in _LEGACY_PARAM_NAMES:
|
|
179
|
+
warnings.warn(
|
|
180
|
+
f"Importing {name!r} from soothe_client is deprecated; "
|
|
181
|
+
f"use soothe_client.protocol_params.{name} instead.",
|
|
182
|
+
DeprecationWarning,
|
|
183
|
+
stacklevel=2,
|
|
184
|
+
)
|
|
185
|
+
from soothe_client import protocol_params as _params
|
|
186
|
+
|
|
187
|
+
return getattr(_params, name)
|
|
188
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
189
|
+
raise AttributeError(msg)
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/__init__.py
RENAMED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"""Application helpers built on WebSocketClient.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Primary building blocks for agent UIs and backends:
|
|
4
4
|
|
|
5
5
|
- ``DaemonSession`` — dual-socket loop session + streamed turns
|
|
6
6
|
- ``ConnectionPool`` / ``TurnRunner`` — multi-session turn execution
|
|
7
7
|
- ``QueryGate`` — one-in-flight query per session
|
|
8
|
-
- ``EventClassifier``
|
|
8
|
+
- ``EventClassifier`` — stream → deliverable mapping
|
|
9
9
|
- ``SSEBroadcaster`` — drop-on-full fan-out to subscribers
|
|
10
10
|
- ``SessionStore`` — persistence seam (Protocol)
|
|
11
|
+
|
|
12
|
+
Advanced stream/pipeline plumbing lives in submodules
|
|
13
|
+
(``events``, ``chunk_filter``, ``turn``, ``managed_client``).
|
|
11
14
|
"""
|
|
12
15
|
|
|
13
16
|
from __future__ import annotations
|
|
@@ -18,7 +21,6 @@ from soothe_client.appkit.attachments import (
|
|
|
18
21
|
compact_image_attachment,
|
|
19
22
|
)
|
|
20
23
|
from soothe_client.appkit.broadcaster import SSEBroadcaster, SSEEvent
|
|
21
|
-
from soothe_client.appkit.chunk_filter import should_drop_stream_chunk_early
|
|
22
24
|
from soothe_client.appkit.classifier import (
|
|
23
25
|
EVENT_FINAL_REPORT,
|
|
24
26
|
ChatEventResult,
|
|
@@ -26,17 +28,7 @@ from soothe_client.appkit.classifier import (
|
|
|
26
28
|
ClassifierConfig,
|
|
27
29
|
EventClassifier,
|
|
28
30
|
)
|
|
29
|
-
from soothe_client.appkit.daemon_session import
|
|
30
|
-
from soothe_client.appkit.events import is_loop_scoped_event, unwrap_next
|
|
31
|
-
from soothe_client.appkit.managed_client import (
|
|
32
|
-
BootstrapFunc,
|
|
33
|
-
ClientFactory,
|
|
34
|
-
ManagedClient,
|
|
35
|
-
WebSocketManagedClient,
|
|
36
|
-
default_bootstrap_func,
|
|
37
|
-
default_client_factory,
|
|
38
|
-
)
|
|
39
|
-
from soothe_client.appkit.observability import TurnEventStats
|
|
31
|
+
from soothe_client.appkit.daemon_session import DaemonSession
|
|
40
32
|
from soothe_client.appkit.pool import (
|
|
41
33
|
ConnectionPool,
|
|
42
34
|
ErrPoolExhausted,
|
|
@@ -50,15 +42,6 @@ from soothe_client.appkit.thinking_step import (
|
|
|
50
42
|
DEFAULT_THINKING_STEP_EVENTS,
|
|
51
43
|
extract_thinking_step,
|
|
52
44
|
)
|
|
53
|
-
from soothe_client.appkit.turn import (
|
|
54
|
-
PRIORITY_CRITICAL,
|
|
55
|
-
PRIORITY_HIGH,
|
|
56
|
-
PRIORITY_LOW,
|
|
57
|
-
PRIORITY_NORMAL,
|
|
58
|
-
TurnApplyBatcher,
|
|
59
|
-
TurnEventPipeline,
|
|
60
|
-
run_turn_pipeline,
|
|
61
|
-
)
|
|
62
45
|
from soothe_client.appkit.turn_runner import (
|
|
63
46
|
STREAM_CLOSE_FAIL,
|
|
64
47
|
STREAM_CLOSE_SOFT_COMPLETE,
|
|
@@ -77,13 +60,13 @@ from soothe_client.appkit.turn_runner import (
|
|
|
77
60
|
)
|
|
78
61
|
|
|
79
62
|
__all__ = [
|
|
80
|
-
"DEFAULT_POST_IDLE_DRAIN_S",
|
|
81
63
|
"DEFAULT_THINKING_STEP_EVENTS",
|
|
82
64
|
"STREAM_CLOSE_FAIL",
|
|
83
65
|
"STREAM_CLOSE_SOFT_COMPLETE",
|
|
84
66
|
"Attachment",
|
|
85
|
-
"
|
|
86
|
-
"
|
|
67
|
+
"ChatEventResult",
|
|
68
|
+
"ChatEventTerminal",
|
|
69
|
+
"ClassifierConfig",
|
|
87
70
|
"CompactImageOptions",
|
|
88
71
|
"ConnectionPool",
|
|
89
72
|
"DaemonSession",
|
|
@@ -92,18 +75,10 @@ __all__ = [
|
|
|
92
75
|
"ErrPoolExhausted",
|
|
93
76
|
"ErrQueryBusy",
|
|
94
77
|
"ErrQueryTimeout",
|
|
95
|
-
"ChatEventResult",
|
|
96
|
-
"ChatEventTerminal",
|
|
97
|
-
"ClassifierConfig",
|
|
98
78
|
"EventClassifier",
|
|
99
79
|
"InputOpts",
|
|
100
|
-
"ManagedClient",
|
|
101
80
|
"OnComplete",
|
|
102
81
|
"OnError",
|
|
103
|
-
"PRIORITY_CRITICAL",
|
|
104
|
-
"PRIORITY_HIGH",
|
|
105
|
-
"PRIORITY_LOW",
|
|
106
|
-
"PRIORITY_NORMAL",
|
|
107
82
|
"PoolConfig",
|
|
108
83
|
"PooledConn",
|
|
109
84
|
"QueryGate",
|
|
@@ -114,22 +89,12 @@ __all__ = [
|
|
|
114
89
|
"SessionStore",
|
|
115
90
|
"StreamClosePolicy",
|
|
116
91
|
"TimeoutPolicy",
|
|
117
|
-
"TurnApplyBatcher",
|
|
118
92
|
"TurnConfig",
|
|
119
|
-
"TurnEventPipeline",
|
|
120
|
-
"TurnEventStats",
|
|
121
93
|
"TurnRunner",
|
|
122
|
-
"WebSocketManagedClient",
|
|
123
94
|
"compact_attachments",
|
|
124
95
|
"compact_image_attachment",
|
|
125
|
-
"default_bootstrap_func",
|
|
126
|
-
"default_client_factory",
|
|
127
96
|
"default_pool_config",
|
|
128
97
|
"extract_thinking_step",
|
|
129
98
|
"idle_timeout_for_turn",
|
|
130
99
|
"input_message_for_loop",
|
|
131
|
-
"is_loop_scoped_event",
|
|
132
|
-
"run_turn_pipeline",
|
|
133
|
-
"should_drop_stream_chunk_early",
|
|
134
|
-
"unwrap_next",
|
|
135
100
|
]
|
|
@@ -37,7 +37,21 @@ StreamDeliveryResolver = Callable[[], str]
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class DaemonSession:
|
|
40
|
-
"""
|
|
40
|
+
"""One conversation with a daemon loop: connect, send turns, stream chunks.
|
|
41
|
+
|
|
42
|
+
Owns a subscribed stream WebSocket plus an RPC sidecar so metadata calls
|
|
43
|
+
do not starve loop events. Typical usage::
|
|
44
|
+
|
|
45
|
+
session = DaemonSession("ws://127.0.0.1:8765")
|
|
46
|
+
await session.connect()
|
|
47
|
+
await session.send_turn("Hello")
|
|
48
|
+
async for _ns, mode, data in session.iter_turn_chunks():
|
|
49
|
+
...
|
|
50
|
+
await session.close()
|
|
51
|
+
|
|
52
|
+
Advanced: ``early_drop_fn`` / ``stats_factory`` customize stream filtering
|
|
53
|
+
and metrics for product UIs; most apps leave the defaults.
|
|
54
|
+
"""
|
|
41
55
|
|
|
42
56
|
def __init__(
|
|
43
57
|
self,
|
|
@@ -67,7 +81,7 @@ class DaemonSession:
|
|
|
67
81
|
self.last_turn_end_state: str | None = None
|
|
68
82
|
self.last_turn_cancellation_seen: bool = False
|
|
69
83
|
self.last_turn_error_message: str | None = None
|
|
70
|
-
#
|
|
84
|
+
# Last completed turn's seq floor (drop stale prior-turn frames).
|
|
71
85
|
self._last_turn_end_seq: int = 0
|
|
72
86
|
self._expected_turn_id: str | None = None
|
|
73
87
|
|
|
@@ -108,7 +122,11 @@ class DaemonSession:
|
|
|
108
122
|
)
|
|
109
123
|
|
|
110
124
|
async def connect(self, *, resume_loop_id: str | None = None) -> dict[str, Any]:
|
|
111
|
-
"""
|
|
125
|
+
"""Open the stream socket and bootstrap (or resume) a loop session.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Status event from the daemon (includes ``loop_id`` on success).
|
|
129
|
+
"""
|
|
112
130
|
await connect_websocket_with_retries(self._client)
|
|
113
131
|
return await self._bootstrap_loop(resume_loop_id=resume_loop_id)
|
|
114
132
|
|
|
@@ -182,7 +200,7 @@ class DaemonSession:
|
|
|
182
200
|
await self._bootstrap_loop(resume_loop_id=resume_loop_id)
|
|
183
201
|
|
|
184
202
|
async def close(self, *, handshake_timeout: float = 2.0) -> None:
|
|
185
|
-
"""Close stream and RPC sockets
|
|
203
|
+
"""Close stream and RPC sockets. Safe to call more than once."""
|
|
186
204
|
if self._closed:
|
|
187
205
|
return
|
|
188
206
|
self._closed = True
|
|
@@ -219,7 +237,11 @@ class DaemonSession:
|
|
|
219
237
|
clarification_answers: list[str] | None = None,
|
|
220
238
|
intent_hint: str | None = None,
|
|
221
239
|
) -> None:
|
|
222
|
-
"""Send a
|
|
240
|
+
"""Send a user message on the active loop (start of a turn).
|
|
241
|
+
|
|
242
|
+
Call ``iter_turn_chunks`` afterward to consume the streamed reply.
|
|
243
|
+
Requires ``connect()`` (or another bootstrap) first.
|
|
244
|
+
"""
|
|
223
245
|
if not self._loop_id:
|
|
224
246
|
raise RuntimeError("No active loop session")
|
|
225
247
|
await self._client.send_input(
|
|
@@ -303,13 +325,15 @@ class DaemonSession:
|
|
|
303
325
|
*,
|
|
304
326
|
max_wait_s: float | None = None,
|
|
305
327
|
) -> AsyncIterator[tuple[tuple[Any, ...], str, Any]]:
|
|
306
|
-
"""Yield ``(namespace, mode, data)`` chunks
|
|
328
|
+
"""Yield ``(namespace, mode, data)`` stream chunks until the turn ends.
|
|
329
|
+
|
|
330
|
+
Handles idle timeout, post-idle drain, and turn boundary filtering.
|
|
331
|
+
Call after ``send_turn``.
|
|
307
332
|
|
|
308
333
|
Args:
|
|
309
334
|
max_wait_s: Optional absolute deadline for the whole turn. When set,
|
|
310
335
|
raises ``TimeoutError`` if the daemon never emits a turn-end
|
|
311
|
-
signal in time
|
|
312
|
-
strange_loop.completed).
|
|
336
|
+
signal in time.
|
|
313
337
|
"""
|
|
314
338
|
self.turn_event_stats = self._new_turn_stats()
|
|
315
339
|
self.last_turn_end_state = None
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""WebSocket
|
|
1
|
+
"""Small WebSocket helpers for CLI and scripts (status, fetch, one-shot RPC).
|
|
2
|
+
|
|
3
|
+
For interactive turns use ``DaemonSession``. For jobs/cron use
|
|
4
|
+
``WsCommandClient`` / ``CommandClient``.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/protocol_params.py
RENAMED
|
@@ -32,6 +32,7 @@ __all__ = [
|
|
|
32
32
|
"AutopilotListGoalsParams",
|
|
33
33
|
"AutopilotGetGoalParams",
|
|
34
34
|
"AutopilotCancelGoalParams",
|
|
35
|
+
"AutopilotCancelAllParams",
|
|
35
36
|
"AutopilotWakeParams",
|
|
36
37
|
"AutopilotDreamParams",
|
|
37
38
|
"AutopilotResumeParams",
|
|
@@ -363,6 +364,10 @@ class AutopilotCancelGoalParams(ParamsBase):
|
|
|
363
364
|
goal_id: str = Field(..., min_length=1)
|
|
364
365
|
|
|
365
366
|
|
|
367
|
+
class AutopilotCancelAllParams(EmptyParams):
|
|
368
|
+
"""Params for ``method=autopilot_cancel_all`` — no required fields."""
|
|
369
|
+
|
|
370
|
+
|
|
366
371
|
class AutopilotWakeParams(EmptyParams):
|
|
367
372
|
"""Params for ``method=autopilot_wake`` — no required fields."""
|
|
368
373
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Session bootstrap helpers for CLI, TUI, and appkit.
|
|
2
|
+
|
|
3
|
+
Prefer ``DaemonSession`` for application code. These helpers are the lower-level
|
|
4
|
+
connect + loop-subscribe steps used when you own the ``WebSocketClient``.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
|
@@ -22,14 +26,7 @@ _LEGACY_LOOP_AUTOPILOT_MODE = "solo"
|
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
async def connect_websocket_with_retries(client: Any) -> None:
|
|
25
|
-
"""Connect
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
client: WebSocketClient instance.
|
|
29
|
-
|
|
30
|
-
Raises:
|
|
31
|
-
ConnectionError: If connection fails after all retries.
|
|
32
|
-
"""
|
|
29
|
+
"""Connect a ``WebSocketClient`` with retries (daemon cold-start safe)."""
|
|
33
30
|
last_error: OSError | ConnectionError | TimeoutError | None = None
|
|
34
31
|
for attempt in range(_CONNECT_RETRY_COUNT):
|
|
35
32
|
try:
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/stream_terminal.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Internal stream/turn terminal frame helpers.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Used by ``WebSocketClient`` and ``DaemonSession`` so leftover prior-turn
|
|
4
|
+
terminals cannot blank the next query. Not part of the community public API.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/turn_boundary.py
RENAMED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Internal turn / stream boundary helpers.
|
|
2
2
|
|
|
3
|
-
``
|
|
4
|
-
|
|
3
|
+
Used by ``DaemonSession`` and ``WebSocketClient`` for ``turn_id`` / ``seq``
|
|
4
|
+
filtering. Not part of the community public API — import only if you are
|
|
5
|
+
extending stream handling.
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
from __future__ import annotations
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/ws_command_client.py
RENAMED
|
@@ -141,6 +141,7 @@ _AUTOPilot_COMMANDS = {
|
|
|
141
141
|
"list_goals": "autopilot_list_goals",
|
|
142
142
|
"get_goal": "autopilot_get_goal",
|
|
143
143
|
"cancel_goal": "autopilot_cancel_goal",
|
|
144
|
+
"cancel_all": "autopilot_cancel_all",
|
|
144
145
|
"wake": "autopilot_wake",
|
|
145
146
|
"dream": "autopilot_dream",
|
|
146
147
|
"resume": "autopilot_resume",
|
|
@@ -162,19 +163,27 @@ _MEMORY_COMMANDS = {
|
|
|
162
163
|
|
|
163
164
|
|
|
164
165
|
class WsCommandClient:
|
|
165
|
-
"""Async
|
|
166
|
+
"""Async client for one-shot daemon RPCs (jobs, autopilot, cron).
|
|
166
167
|
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
Prefer this for asyncio apps. For scripts/CLI without an event loop, use
|
|
169
|
+
``SyncWsCommandClient`` (alias ``CommandClient``). For streaming agent
|
|
170
|
+
turns, use ``DaemonSession`` instead.
|
|
169
171
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
Preferred community alias: ``AsyncCommandClient``.
|
|
173
|
+
|
|
174
|
+
Job lifecycle uses ``job_*`` methods. Autopilot goal helpers use
|
|
175
|
+
``autopilot_*``. ``job_cancel`` cancels a root job and its descendants;
|
|
176
|
+
``autopilot_cancel_all`` cancels every open goal in one call.
|
|
177
|
+
|
|
178
|
+
Example::
|
|
179
|
+
|
|
180
|
+
client = WsCommandClient("ws://127.0.0.1:8765")
|
|
181
|
+
created = await client.job_create("Summarize the README")
|
|
182
|
+
await client.job_cancel(created["job_id"])
|
|
174
183
|
|
|
175
184
|
Args:
|
|
176
|
-
ws_url: WebSocket URL (e.g. ``ws://127.0.0.1:8765``).
|
|
177
|
-
timeout:
|
|
185
|
+
ws_url: Daemon WebSocket URL (e.g. ``ws://127.0.0.1:8765``).
|
|
186
|
+
timeout: Per-command timeout in seconds.
|
|
178
187
|
"""
|
|
179
188
|
|
|
180
189
|
def __init__(self, ws_url: str, *, timeout: float = 30.0) -> None:
|
|
@@ -259,35 +268,43 @@ class WsCommandClient:
|
|
|
259
268
|
except Exception as exc:
|
|
260
269
|
raise RuntimeError(f"Command failed: {exc}") from exc
|
|
261
270
|
|
|
262
|
-
# Autopilot
|
|
271
|
+
# --- Autopilot goals -------------------------------------------------
|
|
263
272
|
|
|
264
273
|
async def autopilot_status(self) -> dict[str, Any]:
|
|
265
|
-
"""
|
|
274
|
+
"""Return autopilot scheduler status (running / dreaming / pool)."""
|
|
266
275
|
return await self._send_command("autopilot_status")
|
|
267
276
|
|
|
268
277
|
async def autopilot_submit(
|
|
269
278
|
self, description: str, *, priority: int = 50, workspace: str | None = None
|
|
270
279
|
) -> dict[str, Any]:
|
|
271
|
-
"""Submit a new autopilot
|
|
280
|
+
"""Submit a new autopilot goal (returns ``goal_id``)."""
|
|
272
281
|
payload: dict[str, Any] = {"description": description, "priority": priority}
|
|
273
282
|
if workspace:
|
|
274
283
|
payload["workspace"] = workspace
|
|
275
284
|
return await self._send_command("autopilot_submit", payload)
|
|
276
285
|
|
|
277
286
|
async def autopilot_list_goals(self) -> dict[str, Any]:
|
|
278
|
-
"""List all goals."""
|
|
287
|
+
"""List all goals (including non-root children)."""
|
|
279
288
|
return await self._send_command("autopilot_list_goals")
|
|
280
289
|
|
|
281
290
|
async def autopilot_get_goal(self, goal_id: str) -> dict[str, Any]:
|
|
282
|
-
"""
|
|
291
|
+
"""Fetch one goal by id."""
|
|
283
292
|
return await self._send_command("autopilot_get_goal", {"goal_id": goal_id})
|
|
284
293
|
|
|
285
294
|
async def autopilot_cancel_goal(self, goal_id: str) -> dict[str, Any]:
|
|
286
|
-
"""Cancel a goal."""
|
|
295
|
+
"""Cancel a goal and its non-terminal descendants."""
|
|
287
296
|
return await self._send_command("autopilot_cancel_goal", {"goal_id": goal_id})
|
|
288
297
|
|
|
298
|
+
async def autopilot_cancel_all(self) -> dict[str, Any]:
|
|
299
|
+
"""Cancel every open (non-terminal) goal in one call.
|
|
300
|
+
|
|
301
|
+
Prefer ``job_cancel(job_id)`` when you know the job root. Use this when
|
|
302
|
+
you need a bulk stop of leftover pending/active goals.
|
|
303
|
+
"""
|
|
304
|
+
return await self._send_command("autopilot_cancel_all")
|
|
305
|
+
|
|
289
306
|
async def autopilot_wake(self) -> dict[str, Any]:
|
|
290
|
-
"""Exit dreaming mode."""
|
|
307
|
+
"""Exit dreaming mode and resume scheduling."""
|
|
291
308
|
return await self._send_command("autopilot_wake")
|
|
292
309
|
|
|
293
310
|
async def autopilot_dream(self) -> dict[str, Any]:
|
|
@@ -295,26 +312,26 @@ class WsCommandClient:
|
|
|
295
312
|
return await self._send_command("autopilot_dream")
|
|
296
313
|
|
|
297
314
|
async def autopilot_resume(self, goal_id: str) -> dict[str, Any]:
|
|
298
|
-
"""Resume a suspended
|
|
315
|
+
"""Resume a suspended or blocked goal."""
|
|
299
316
|
return await self._send_command("autopilot_resume", {"goal_id": goal_id})
|
|
300
317
|
|
|
301
318
|
async def autopilot_list_jobs(self) -> dict[str, Any]:
|
|
302
|
-
"""List root goals (jobs)
|
|
319
|
+
"""List root goals only (jobs). Prefer ``job_*`` for job control."""
|
|
303
320
|
return await self._send_command("autopilot_list_jobs")
|
|
304
321
|
|
|
305
322
|
async def autopilot_get_job(self, job_id: str) -> dict[str, Any]:
|
|
306
|
-
"""Get job
|
|
323
|
+
"""Get a root job with DAG snapshot. Prefer ``job_status`` / ``job_dag``."""
|
|
307
324
|
return await self._send_command("autopilot_get_job", {"job_id": job_id})
|
|
308
325
|
|
|
309
326
|
async def autopilot_subscribe(self) -> dict[str, Any]:
|
|
310
|
-
"""Subscribe to autopilot worker events."""
|
|
327
|
+
"""Subscribe this connection to autopilot worker events."""
|
|
311
328
|
return await self._send_command("autopilot_subscribe")
|
|
312
329
|
|
|
313
330
|
async def autopilot_unsubscribe(self) -> dict[str, Any]:
|
|
314
331
|
"""Unsubscribe from autopilot worker events."""
|
|
315
332
|
return await self._send_command("autopilot_unsubscribe")
|
|
316
333
|
|
|
317
|
-
#
|
|
334
|
+
# --- Jobs (preferred for job lifecycle) ------------------------------
|
|
318
335
|
|
|
319
336
|
async def job_create(
|
|
320
337
|
self,
|
|
@@ -325,18 +342,7 @@ class WsCommandClient:
|
|
|
325
342
|
max_iterations: int | None = None,
|
|
326
343
|
guidance: str | None = None,
|
|
327
344
|
) -> dict[str, Any]:
|
|
328
|
-
"""Create a
|
|
329
|
-
|
|
330
|
-
Args:
|
|
331
|
-
goal: Job goal text (required).
|
|
332
|
-
workspace: Optional workspace path.
|
|
333
|
-
autonomous: Whether to run autonomously.
|
|
334
|
-
max_iterations: Optional iteration limit.
|
|
335
|
-
guidance: Optional initial guidance.
|
|
336
|
-
|
|
337
|
-
Returns:
|
|
338
|
-
Dict with job_id and status.
|
|
339
|
-
"""
|
|
345
|
+
"""Create a background job (root goal). Returns ``job_id`` + status."""
|
|
340
346
|
payload: dict[str, Any] = {"goal": goal}
|
|
341
347
|
if workspace:
|
|
342
348
|
payload["workspace"] = workspace
|
|
@@ -349,79 +355,35 @@ class WsCommandClient:
|
|
|
349
355
|
return await self._send_command("job_create", payload)
|
|
350
356
|
|
|
351
357
|
async def job_status(self, job_id: str) -> dict[str, Any]:
|
|
352
|
-
"""Get job status with goal counts and workers.
|
|
353
|
-
|
|
354
|
-
Args:
|
|
355
|
-
job_id: Job identifier.
|
|
356
|
-
|
|
357
|
-
Returns:
|
|
358
|
-
Dict with job_id, status, active_goals, completed_goals, workers.
|
|
359
|
-
"""
|
|
358
|
+
"""Get job status with goal counts and workers."""
|
|
360
359
|
return await self._send_command("job_status", {"job_id": job_id})
|
|
361
360
|
|
|
362
361
|
async def job_pause(self, job_id: str) -> dict[str, Any]:
|
|
363
|
-
"""Pause a running job.
|
|
364
|
-
|
|
365
|
-
Args:
|
|
366
|
-
job_id: Job identifier.
|
|
367
|
-
|
|
368
|
-
Returns:
|
|
369
|
-
Dict with job_id and status="suspended".
|
|
370
|
-
"""
|
|
362
|
+
"""Pause a running job (status becomes suspended)."""
|
|
371
363
|
return await self._send_command("job_pause", {"job_id": job_id})
|
|
372
364
|
|
|
373
365
|
async def job_resume(self, job_id: str) -> dict[str, Any]:
|
|
374
|
-
"""Resume a paused job.
|
|
375
|
-
|
|
376
|
-
Args:
|
|
377
|
-
job_id: Job identifier.
|
|
378
|
-
|
|
379
|
-
Returns:
|
|
380
|
-
Dict with job_id and status="pending".
|
|
381
|
-
"""
|
|
366
|
+
"""Resume a paused job."""
|
|
382
367
|
return await self._send_command("job_resume", {"job_id": job_id})
|
|
383
368
|
|
|
384
369
|
async def job_cancel(self, job_id: str) -> dict[str, Any]:
|
|
385
|
-
"""Cancel a job.
|
|
386
|
-
|
|
387
|
-
Args:
|
|
388
|
-
job_id: Job identifier.
|
|
389
|
-
|
|
390
|
-
Returns:
|
|
391
|
-
Dict with job_id and status="cancelled".
|
|
392
|
-
"""
|
|
370
|
+
"""Cancel a job root and all non-terminal descendants."""
|
|
393
371
|
return await self._send_command("job_cancel", {"job_id": job_id})
|
|
394
372
|
|
|
395
373
|
async def job_dag(self, job_id: str) -> dict[str, Any]:
|
|
396
|
-
"""Get job DAG
|
|
397
|
-
|
|
398
|
-
Args:
|
|
399
|
-
job_id: Job identifier.
|
|
400
|
-
|
|
401
|
-
Returns:
|
|
402
|
-
Dict with job_id and dag (nodes/edges).
|
|
403
|
-
"""
|
|
374
|
+
"""Get the job goal DAG (nodes/edges) for visualization."""
|
|
404
375
|
return await self._send_command("job_dag", {"job_id": job_id})
|
|
405
376
|
|
|
406
377
|
async def job_guidance(
|
|
407
378
|
self, job_id: str, content: str, *, goal_id: str | None = None
|
|
408
379
|
) -> dict[str, Any]:
|
|
409
|
-
"""Send guidance to a job or specific goal.
|
|
410
|
-
|
|
411
|
-
Args:
|
|
412
|
-
job_id: Job identifier.
|
|
413
|
-
content: Guidance text.
|
|
414
|
-
goal_id: Optional specific goal to target.
|
|
415
|
-
|
|
416
|
-
Returns:
|
|
417
|
-
Dict with job_id, goal_id, absorbed.
|
|
418
|
-
"""
|
|
380
|
+
"""Send guidance text to a job or a specific goal under it."""
|
|
419
381
|
payload: dict[str, Any] = {"job_id": job_id, "content": content}
|
|
420
382
|
if goal_id:
|
|
421
383
|
payload["goal_id"] = goal_id
|
|
422
384
|
return await self._send_command("job_guidance", payload)
|
|
423
385
|
|
|
424
|
-
# Cron
|
|
386
|
+
# --- Cron ------------------------------------------------------------
|
|
425
387
|
|
|
426
388
|
async def cron_add(self, text: str, *, priority: int | None = None) -> dict[str, Any]:
|
|
427
389
|
"""Submit a natural-language scheduled job."""
|
|
@@ -432,75 +394,63 @@ class WsCommandClient:
|
|
|
432
394
|
return _normalize_cron_add_result(result)
|
|
433
395
|
|
|
434
396
|
async def cron_list(self, *, status: str | None = None) -> dict[str, Any]:
|
|
435
|
-
"""List scheduled jobs.
|
|
436
|
-
|
|
437
|
-
Sends the ``cron_list`` method, which the daemon routes to
|
|
438
|
-
``_handle_cron_list``. The former ``cron_list_jobs`` method name is
|
|
439
|
-
deprecated; it sent a method the daemon did not handle.
|
|
440
|
-
|
|
441
|
-
Args:
|
|
442
|
-
status: Optional status filter.
|
|
443
|
-
|
|
444
|
-
Returns:
|
|
445
|
-
Dict with jobs list.
|
|
446
|
-
"""
|
|
397
|
+
"""List scheduled cron jobs (optional ``status`` filter)."""
|
|
447
398
|
payload: dict[str, Any] = {}
|
|
448
399
|
if status:
|
|
449
400
|
payload["status"] = status
|
|
450
401
|
return await self._send_command("cron_list", payload)
|
|
451
402
|
|
|
452
403
|
async def cron_show(self, job_id: str) -> dict[str, Any]:
|
|
453
|
-
"""Get job
|
|
404
|
+
"""Get details for one cron job."""
|
|
454
405
|
result = await self._send_command("cron_show", {"job_id": job_id})
|
|
455
406
|
return _normalize_cron_show_result(result)
|
|
456
407
|
|
|
457
408
|
async def cron_cancel(self, job_id: str) -> dict[str, Any]:
|
|
458
|
-
"""Cancel a scheduled job."""
|
|
409
|
+
"""Cancel a scheduled cron job."""
|
|
459
410
|
return await self._send_command("cron_cancel", {"job_id": job_id})
|
|
460
411
|
|
|
461
|
-
# Memory
|
|
412
|
+
# --- Memory (admin) --------------------------------------------------
|
|
462
413
|
|
|
463
414
|
async def memory_stats(self, mode: str = "daemon") -> dict[str, Any]:
|
|
464
|
-
"""Query daemon memory profiling stats."""
|
|
415
|
+
"""Query daemon memory profiling stats (admin)."""
|
|
465
416
|
return await self._send_command("memory_stats", {"mode": mode})
|
|
466
417
|
|
|
467
418
|
|
|
468
419
|
class SyncWsCommandClient:
|
|
469
|
-
"""Synchronous wrapper for
|
|
420
|
+
"""Synchronous wrapper around ``WsCommandClient`` for scripts and CLI.
|
|
470
421
|
|
|
471
|
-
|
|
472
|
-
|
|
422
|
+
Preferred community alias: ``CommandClient``. Each method opens a short
|
|
423
|
+
WebSocket RPC (same behavior as the async client).
|
|
473
424
|
|
|
474
425
|
Args:
|
|
475
|
-
ws_url: WebSocket URL.
|
|
476
|
-
timeout:
|
|
426
|
+
ws_url: Daemon WebSocket URL.
|
|
427
|
+
timeout: Per-command timeout in seconds.
|
|
477
428
|
"""
|
|
478
429
|
|
|
479
430
|
def __init__(self, ws_url: str, *, timeout: float = 30.0) -> None:
|
|
480
431
|
self._client = WsCommandClient(ws_url, timeout=timeout)
|
|
481
432
|
|
|
482
433
|
def _run_async(self, coro: Any) -> Any:
|
|
483
|
-
"""Run async coroutine
|
|
434
|
+
"""Run an async coroutine from sync code."""
|
|
484
435
|
try:
|
|
485
436
|
loop = asyncio.get_running_loop()
|
|
486
437
|
except RuntimeError:
|
|
487
438
|
loop = None
|
|
488
439
|
|
|
489
440
|
if loop is not None:
|
|
490
|
-
# Already in async context - create task
|
|
491
441
|
return asyncio.ensure_future(coro)
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
442
|
+
return asyncio.run(coro)
|
|
443
|
+
|
|
444
|
+
# --- Autopilot goals -------------------------------------------------
|
|
495
445
|
|
|
496
446
|
def autopilot_status(self) -> dict[str, Any]:
|
|
497
|
-
"""
|
|
447
|
+
"""Return autopilot scheduler status."""
|
|
498
448
|
return cast(dict[str, Any], self._run_async(self._client.autopilot_status()))
|
|
499
449
|
|
|
500
450
|
def autopilot_submit(
|
|
501
451
|
self, description: str, *, priority: int = 50, workspace: str | None = None
|
|
502
452
|
) -> dict[str, Any]:
|
|
503
|
-
"""Submit a new autopilot
|
|
453
|
+
"""Submit a new autopilot goal."""
|
|
504
454
|
return cast(
|
|
505
455
|
dict[str, Any],
|
|
506
456
|
self._run_async(
|
|
@@ -509,47 +459,50 @@ class SyncWsCommandClient:
|
|
|
509
459
|
)
|
|
510
460
|
|
|
511
461
|
def autopilot_list_goals(self) -> dict[str, Any]:
|
|
512
|
-
"""List all goals
|
|
462
|
+
"""List all goals."""
|
|
513
463
|
return cast(dict[str, Any], self._run_async(self._client.autopilot_list_goals()))
|
|
514
464
|
|
|
515
465
|
def autopilot_get_goal(self, goal_id: str) -> dict[str, Any]:
|
|
516
|
-
"""
|
|
466
|
+
"""Fetch one goal by id."""
|
|
517
467
|
return cast(dict[str, Any], self._run_async(self._client.autopilot_get_goal(goal_id)))
|
|
518
468
|
|
|
519
469
|
def autopilot_cancel_goal(self, goal_id: str) -> dict[str, Any]:
|
|
520
|
-
"""Cancel a goal
|
|
470
|
+
"""Cancel a goal and its non-terminal descendants."""
|
|
521
471
|
return cast(dict[str, Any], self._run_async(self._client.autopilot_cancel_goal(goal_id)))
|
|
522
472
|
|
|
473
|
+
def autopilot_cancel_all(self) -> dict[str, Any]:
|
|
474
|
+
"""Cancel every open (non-terminal) goal."""
|
|
475
|
+
return cast(dict[str, Any], self._run_async(self._client.autopilot_cancel_all()))
|
|
476
|
+
|
|
523
477
|
def autopilot_wake(self) -> dict[str, Any]:
|
|
524
|
-
"""Exit dreaming mode
|
|
478
|
+
"""Exit dreaming mode."""
|
|
525
479
|
return cast(dict[str, Any], self._run_async(self._client.autopilot_wake()))
|
|
526
480
|
|
|
527
481
|
def autopilot_dream(self) -> dict[str, Any]:
|
|
528
|
-
"""Force dreaming mode
|
|
482
|
+
"""Force dreaming mode."""
|
|
529
483
|
return cast(dict[str, Any], self._run_async(self._client.autopilot_dream()))
|
|
530
484
|
|
|
531
485
|
def autopilot_resume(self, goal_id: str) -> dict[str, Any]:
|
|
532
|
-
"""Resume a suspended
|
|
486
|
+
"""Resume a suspended or blocked goal."""
|
|
533
487
|
return cast(dict[str, Any], self._run_async(self._client.autopilot_resume(goal_id)))
|
|
534
488
|
|
|
535
489
|
def autopilot_list_jobs(self) -> dict[str, Any]:
|
|
536
|
-
"""List root goals
|
|
490
|
+
"""List root goals only (jobs)."""
|
|
537
491
|
return cast(dict[str, Any], self._run_async(self._client.autopilot_list_jobs()))
|
|
538
492
|
|
|
539
493
|
def autopilot_get_job(self, job_id: str) -> dict[str, Any]:
|
|
540
|
-
"""Get job
|
|
494
|
+
"""Get a root job with DAG snapshot."""
|
|
541
495
|
return cast(dict[str, Any], self._run_async(self._client.autopilot_get_job(job_id)))
|
|
542
496
|
|
|
543
|
-
def
|
|
544
|
-
"""
|
|
545
|
-
return cast(dict[str, Any], self._run_async(self._client.
|
|
497
|
+
def autopilot_subscribe(self) -> dict[str, Any]:
|
|
498
|
+
"""Subscribe to autopilot worker events."""
|
|
499
|
+
return cast(dict[str, Any], self._run_async(self._client.autopilot_subscribe()))
|
|
546
500
|
|
|
547
|
-
def
|
|
548
|
-
"""
|
|
549
|
-
return cast(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
)
|
|
501
|
+
def autopilot_unsubscribe(self) -> dict[str, Any]:
|
|
502
|
+
"""Unsubscribe from autopilot worker events."""
|
|
503
|
+
return cast(dict[str, Any], self._run_async(self._client.autopilot_unsubscribe()))
|
|
504
|
+
|
|
505
|
+
# --- Jobs ------------------------------------------------------------
|
|
553
506
|
|
|
554
507
|
def job_create(
|
|
555
508
|
self,
|
|
@@ -560,7 +513,7 @@ class SyncWsCommandClient:
|
|
|
560
513
|
max_iterations: int | None = None,
|
|
561
514
|
guidance: str | None = None,
|
|
562
515
|
) -> dict[str, Any]:
|
|
563
|
-
"""Create a
|
|
516
|
+
"""Create a background job (root goal)."""
|
|
564
517
|
return cast(
|
|
565
518
|
dict[str, Any],
|
|
566
519
|
self._run_async(
|
|
@@ -578,68 +531,69 @@ class SyncWsCommandClient:
|
|
|
578
531
|
"""Get job status with goal counts and workers."""
|
|
579
532
|
return cast(dict[str, Any], self._run_async(self._client.job_status(job_id)))
|
|
580
533
|
|
|
534
|
+
def job_pause(self, job_id: str) -> dict[str, Any]:
|
|
535
|
+
"""Pause a running job."""
|
|
536
|
+
return cast(dict[str, Any], self._run_async(self._client.job_pause(job_id)))
|
|
537
|
+
|
|
581
538
|
def job_resume(self, job_id: str) -> dict[str, Any]:
|
|
582
|
-
"""Resume a paused
|
|
539
|
+
"""Resume a paused job."""
|
|
583
540
|
return cast(dict[str, Any], self._run_async(self._client.job_resume(job_id)))
|
|
584
541
|
|
|
585
542
|
def job_cancel(self, job_id: str) -> dict[str, Any]:
|
|
586
|
-
"""Cancel
|
|
543
|
+
"""Cancel a job root and all non-terminal descendants."""
|
|
587
544
|
return cast(dict[str, Any], self._run_async(self._client.job_cancel(job_id)))
|
|
588
545
|
|
|
589
546
|
def job_dag(self, job_id: str) -> dict[str, Any]:
|
|
590
|
-
"""Get job DAG
|
|
547
|
+
"""Get the job goal DAG for visualization."""
|
|
591
548
|
return cast(dict[str, Any], self._run_async(self._client.job_dag(job_id)))
|
|
592
549
|
|
|
593
|
-
def
|
|
594
|
-
|
|
595
|
-
|
|
550
|
+
def job_guidance(
|
|
551
|
+
self, job_id: str, content: str, *, goal_id: str | None = None
|
|
552
|
+
) -> dict[str, Any]:
|
|
553
|
+
"""Send guidance text to a job or a specific goal under it."""
|
|
554
|
+
return cast(
|
|
555
|
+
dict[str, Any],
|
|
556
|
+
self._run_async(self._client.job_guidance(job_id, content, goal_id=goal_id)),
|
|
557
|
+
)
|
|
596
558
|
|
|
597
|
-
|
|
598
|
-
"""Unsubscribe from autopilot worker events (sync)."""
|
|
599
|
-
return cast(dict[str, Any], self._run_async(self._client.autopilot_unsubscribe()))
|
|
559
|
+
# --- Cron ------------------------------------------------------------
|
|
600
560
|
|
|
601
561
|
def cron_add(self, text: str, *, priority: int | None = None) -> dict[str, Any]:
|
|
602
|
-
"""Submit a natural-language scheduled job
|
|
562
|
+
"""Submit a natural-language scheduled job."""
|
|
603
563
|
return cast(dict[str, Any], self._run_async(self._client.cron_add(text, priority=priority)))
|
|
604
564
|
|
|
605
565
|
def cron_list(self, *, status: str | None = None) -> dict[str, Any]:
|
|
606
|
-
"""List scheduled jobs
|
|
566
|
+
"""List scheduled cron jobs."""
|
|
607
567
|
return cast(dict[str, Any], self._run_async(self._client.cron_list(status=status)))
|
|
608
568
|
|
|
609
569
|
def cron_show(self, job_id: str) -> dict[str, Any]:
|
|
610
|
-
"""Get
|
|
570
|
+
"""Get details for one cron job."""
|
|
611
571
|
return cast(dict[str, Any], self._run_async(self._client.cron_show(job_id)))
|
|
612
572
|
|
|
613
573
|
def cron_cancel(self, job_id: str) -> dict[str, Any]:
|
|
614
|
-
"""Cancel a scheduled job
|
|
574
|
+
"""Cancel a scheduled cron job."""
|
|
615
575
|
return cast(dict[str, Any], self._run_async(self._client.cron_cancel(job_id)))
|
|
616
576
|
|
|
577
|
+
# --- Memory (admin) --------------------------------------------------
|
|
578
|
+
|
|
617
579
|
def memory_stats(self, mode: str = "daemon") -> dict[str, Any]:
|
|
618
|
-
"""Query daemon memory profiling stats (
|
|
580
|
+
"""Query daemon memory profiling stats (admin)."""
|
|
619
581
|
return cast(dict[str, Any], self._run_async(self._client.memory_stats(mode)))
|
|
620
582
|
|
|
621
583
|
|
|
622
584
|
def ws_command_client_from_config(cfg: Any) -> SyncWsCommandClient:
|
|
623
|
-
"""Build a
|
|
585
|
+
"""Build a sync command client from soothe/CLI config (host/port).
|
|
624
586
|
|
|
625
|
-
|
|
626
|
-
cfg: CLI, daemon, or soothe config exposing websocket host/port.
|
|
627
|
-
|
|
628
|
-
Returns:
|
|
629
|
-
SyncWsCommandClient instance.
|
|
587
|
+
Preferred alias: ``command_client_from_config``.
|
|
630
588
|
"""
|
|
631
589
|
ws_url = websocket_url_from_config(cfg)
|
|
632
590
|
return SyncWsCommandClient(ws_url)
|
|
633
591
|
|
|
634
592
|
|
|
635
593
|
def async_ws_command_client_from_config(cfg: Any) -> WsCommandClient:
|
|
636
|
-
"""Build an async
|
|
637
|
-
|
|
638
|
-
Args:
|
|
639
|
-
cfg: CLI, daemon, or soothe config exposing websocket host/port.
|
|
594
|
+
"""Build an async command client from soothe/CLI config (host/port).
|
|
640
595
|
|
|
641
|
-
|
|
642
|
-
WsCommandClient instance.
|
|
596
|
+
Preferred alias: ``async_command_client_from_config``.
|
|
643
597
|
"""
|
|
644
598
|
ws_url = websocket_url_from_config(cfg)
|
|
645
599
|
return WsCommandClient(ws_url)
|
|
@@ -648,6 +602,17 @@ def async_ws_command_client_from_config(cfg: Any) -> WsCommandClient:
|
|
|
648
602
|
__all__ = [
|
|
649
603
|
"WsCommandClient",
|
|
650
604
|
"SyncWsCommandClient",
|
|
605
|
+
"AsyncCommandClient",
|
|
606
|
+
"CommandClient",
|
|
651
607
|
"ws_command_client_from_config",
|
|
652
608
|
"async_ws_command_client_from_config",
|
|
609
|
+
"command_client_from_config",
|
|
610
|
+
"async_command_client_from_config",
|
|
653
611
|
]
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
# Preferred community names (aliases; wire method names unchanged).
|
|
615
|
+
AsyncCommandClient = WsCommandClient
|
|
616
|
+
CommandClient = SyncWsCommandClient
|
|
617
|
+
command_client_from_config = ws_command_client_from_config
|
|
618
|
+
async_command_client_from_config = async_ws_command_client_from_config
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
"""Soothe WebSocket client for talking to a running soothe-daemon.
|
|
2
|
-
|
|
3
|
-
Transport helpers (connect, loops, RPCs) live here. Higher-level session
|
|
4
|
-
and multi-user turn tools live in ``soothe_client.appkit``.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import importlib.metadata
|
|
10
|
-
|
|
11
|
-
from soothe_sdk.core.types import VerbosityLevel
|
|
12
|
-
from soothe_sdk.wire.codec import ProtocolError
|
|
13
|
-
|
|
14
|
-
from soothe_client.errors import (
|
|
15
|
-
DaemonError,
|
|
16
|
-
DisconnectCause,
|
|
17
|
-
ReconnectError,
|
|
18
|
-
StaleLoopError,
|
|
19
|
-
disconnect_cause_name,
|
|
20
|
-
)
|
|
21
|
-
from soothe_client.helpers import (
|
|
22
|
-
check_daemon_status,
|
|
23
|
-
connected_websocket,
|
|
24
|
-
fetch_config_section,
|
|
25
|
-
fetch_loop_cards,
|
|
26
|
-
fetch_loop_history,
|
|
27
|
-
fetch_loop_messages,
|
|
28
|
-
fetch_skills_catalog,
|
|
29
|
-
is_daemon_live,
|
|
30
|
-
protocol1_rpc,
|
|
31
|
-
request_daemon_config_reload,
|
|
32
|
-
request_daemon_shutdown,
|
|
33
|
-
websocket_url_from_config,
|
|
34
|
-
)
|
|
35
|
-
from soothe_client.intent_hints import (
|
|
36
|
-
DEFAULT_DELIVERABLE_PHASES,
|
|
37
|
-
EMBED,
|
|
38
|
-
IMAGE_TO_TEXT,
|
|
39
|
-
OCR,
|
|
40
|
-
TEXT_COMPLETION,
|
|
41
|
-
validate_loop_input_intent_hint,
|
|
42
|
-
)
|
|
43
|
-
from soothe_client.protocol_params import (
|
|
44
|
-
AuthParams,
|
|
45
|
-
AuthRefreshParams,
|
|
46
|
-
AutopilotCancelGoalParams,
|
|
47
|
-
AutopilotDreamParams,
|
|
48
|
-
AutopilotGetGoalParams,
|
|
49
|
-
AutopilotGetJobParams,
|
|
50
|
-
AutopilotListGoalsParams,
|
|
51
|
-
AutopilotListJobsParams,
|
|
52
|
-
AutopilotResumeParams,
|
|
53
|
-
AutopilotStatusParams,
|
|
54
|
-
AutopilotSubmitParams,
|
|
55
|
-
AutopilotSubscribeParams,
|
|
56
|
-
AutopilotWakeParams,
|
|
57
|
-
ConfigGetParams,
|
|
58
|
-
ConfigReloadParams,
|
|
59
|
-
CronAddParams,
|
|
60
|
-
CronCancelParams,
|
|
61
|
-
CronListParams,
|
|
62
|
-
CronShowParams,
|
|
63
|
-
DaemonShutdownParams,
|
|
64
|
-
DaemonStatusParams,
|
|
65
|
-
DisconnectParams,
|
|
66
|
-
InvokeSkillParams,
|
|
67
|
-
JobCancelParams,
|
|
68
|
-
JobCreateParams,
|
|
69
|
-
JobDagParams,
|
|
70
|
-
JobGuidanceParams,
|
|
71
|
-
JobPauseParams,
|
|
72
|
-
JobResumeParams,
|
|
73
|
-
JobStatusParams,
|
|
74
|
-
LoopCardsFetchParams,
|
|
75
|
-
LoopDeleteParams,
|
|
76
|
-
LoopDetachParams,
|
|
77
|
-
LoopGetParams,
|
|
78
|
-
LoopInputParams,
|
|
79
|
-
LoopListParams,
|
|
80
|
-
LoopMessagesParams,
|
|
81
|
-
LoopNewParams,
|
|
82
|
-
LoopPruneParams,
|
|
83
|
-
LoopReattachParams,
|
|
84
|
-
LoopStateGetParams,
|
|
85
|
-
LoopStateUpdateParams,
|
|
86
|
-
LoopTreeParams,
|
|
87
|
-
McpStatusParams,
|
|
88
|
-
ModelsListParams,
|
|
89
|
-
RpcCommandParams,
|
|
90
|
-
SkillsListParams,
|
|
91
|
-
SlashCommandParams,
|
|
92
|
-
SubscribeParams,
|
|
93
|
-
)
|
|
94
|
-
from soothe_client.session import (
|
|
95
|
-
bootstrap_loop_session,
|
|
96
|
-
connect_websocket_with_retries,
|
|
97
|
-
)
|
|
98
|
-
from soothe_client.websocket import WebSocketClient
|
|
99
|
-
from soothe_client.ws_command_client import (
|
|
100
|
-
SyncWsCommandClient,
|
|
101
|
-
WsCommandClient,
|
|
102
|
-
async_ws_command_client_from_config,
|
|
103
|
-
ws_command_client_from_config,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
try:
|
|
107
|
-
__version__ = importlib.metadata.version("soothe-client-python")
|
|
108
|
-
except importlib.metadata.PackageNotFoundError:
|
|
109
|
-
__version__ = "0.0.0"
|
|
110
|
-
|
|
111
|
-
__all__ = [
|
|
112
|
-
"__version__",
|
|
113
|
-
"WebSocketClient",
|
|
114
|
-
"VerbosityLevel",
|
|
115
|
-
"ProtocolError",
|
|
116
|
-
"DaemonError",
|
|
117
|
-
"DisconnectCause",
|
|
118
|
-
"ReconnectError",
|
|
119
|
-
"StaleLoopError",
|
|
120
|
-
"disconnect_cause_name",
|
|
121
|
-
"WsCommandClient",
|
|
122
|
-
"SyncWsCommandClient",
|
|
123
|
-
"ws_command_client_from_config",
|
|
124
|
-
"async_ws_command_client_from_config",
|
|
125
|
-
"bootstrap_loop_session",
|
|
126
|
-
"connect_websocket_with_retries",
|
|
127
|
-
"websocket_url_from_config",
|
|
128
|
-
"connected_websocket",
|
|
129
|
-
"protocol1_rpc",
|
|
130
|
-
"check_daemon_status",
|
|
131
|
-
"is_daemon_live",
|
|
132
|
-
"request_daemon_config_reload",
|
|
133
|
-
"request_daemon_shutdown",
|
|
134
|
-
"fetch_skills_catalog",
|
|
135
|
-
"fetch_config_section",
|
|
136
|
-
"fetch_loop_history",
|
|
137
|
-
"fetch_loop_cards",
|
|
138
|
-
"fetch_loop_messages",
|
|
139
|
-
"TEXT_COMPLETION",
|
|
140
|
-
"IMAGE_TO_TEXT",
|
|
141
|
-
"OCR",
|
|
142
|
-
"EMBED",
|
|
143
|
-
"DEFAULT_DELIVERABLE_PHASES",
|
|
144
|
-
"validate_loop_input_intent_hint",
|
|
145
|
-
"LoopGetParams",
|
|
146
|
-
"LoopListParams",
|
|
147
|
-
"LoopTreeParams",
|
|
148
|
-
"LoopPruneParams",
|
|
149
|
-
"LoopDeleteParams",
|
|
150
|
-
"LoopNewParams",
|
|
151
|
-
"LoopReattachParams",
|
|
152
|
-
"LoopInputParams",
|
|
153
|
-
"LoopMessagesParams",
|
|
154
|
-
"LoopStateGetParams",
|
|
155
|
-
"LoopStateUpdateParams",
|
|
156
|
-
"LoopCardsFetchParams",
|
|
157
|
-
"LoopDetachParams",
|
|
158
|
-
"SubscribeParams",
|
|
159
|
-
"AutopilotSubscribeParams",
|
|
160
|
-
"AutopilotStatusParams",
|
|
161
|
-
"AutopilotSubmitParams",
|
|
162
|
-
"AutopilotListGoalsParams",
|
|
163
|
-
"AutopilotGetGoalParams",
|
|
164
|
-
"AutopilotCancelGoalParams",
|
|
165
|
-
"AutopilotWakeParams",
|
|
166
|
-
"AutopilotDreamParams",
|
|
167
|
-
"AutopilotResumeParams",
|
|
168
|
-
"AutopilotListJobsParams",
|
|
169
|
-
"AutopilotGetJobParams",
|
|
170
|
-
"JobCreateParams",
|
|
171
|
-
"JobStatusParams",
|
|
172
|
-
"JobPauseParams",
|
|
173
|
-
"JobResumeParams",
|
|
174
|
-
"JobCancelParams",
|
|
175
|
-
"JobDagParams",
|
|
176
|
-
"JobGuidanceParams",
|
|
177
|
-
"CronAddParams",
|
|
178
|
-
"CronListParams",
|
|
179
|
-
"CronShowParams",
|
|
180
|
-
"CronCancelParams",
|
|
181
|
-
"DaemonStatusParams",
|
|
182
|
-
"DaemonShutdownParams",
|
|
183
|
-
"ConfigGetParams",
|
|
184
|
-
"ConfigReloadParams",
|
|
185
|
-
"SkillsListParams",
|
|
186
|
-
"ModelsListParams",
|
|
187
|
-
"InvokeSkillParams",
|
|
188
|
-
"McpStatusParams",
|
|
189
|
-
"AuthParams",
|
|
190
|
-
"AuthRefreshParams",
|
|
191
|
-
"SlashCommandParams",
|
|
192
|
-
"RpcCommandParams",
|
|
193
|
-
"DisconnectParams",
|
|
194
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/attachments.py
RENAMED
|
File without changes
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/broadcaster.py
RENAMED
|
File without changes
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/chunk_filter.py
RENAMED
|
File without changes
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/classifier.py
RENAMED
|
File without changes
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/events.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/observability.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/query_gate.py
RENAMED
|
File without changes
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/session_store.py
RENAMED
|
File without changes
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/thinking_step.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/appkit/turn_runner.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_client_python-0.9.9 → soothe_client_python-0.9.10}/src/soothe_client/intent_hints.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|