synapse-cli-agent 0.1.13__py3-none-any.whl

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 (131) hide show
  1. synapse/__init__.py +13 -0
  2. synapse/__main__.py +6 -0
  3. synapse/app/__init__.py +1 -0
  4. synapse/app/agent.py +492 -0
  5. synapse/app/agent_md.py +107 -0
  6. synapse/cli.py +750 -0
  7. synapse/commands/__init__.py +1 -0
  8. synapse/commands/compression.py +573 -0
  9. synapse/commands/helpers.py +22 -0
  10. synapse/commands/mcp.py +406 -0
  11. synapse/commands/model.py +173 -0
  12. synapse/commands/result.py +34 -0
  13. synapse/commands/sessions.py +443 -0
  14. synapse/commands/slash_cmds.py +521 -0
  15. synapse/commands/slash_complete.py +816 -0
  16. synapse/commands/theme.py +99 -0
  17. synapse/config.py +27 -0
  18. synapse/content/__init__.py +1 -0
  19. synapse/content/input_history.py +122 -0
  20. synapse/content/multimodal.py +733 -0
  21. synapse/content/prompts.py +249 -0
  22. synapse/content/skills_catalog.py +128 -0
  23. synapse/integrations/__init__.py +1 -0
  24. synapse/integrations/checkpoint_seed.py +281 -0
  25. synapse/integrations/codex_history.py +375 -0
  26. synapse/integrations/codex_import.py +393 -0
  27. synapse/integrations/codex_sessions.py +629 -0
  28. synapse/integrations/describe_image.py +370 -0
  29. synapse/integrations/http_clients.py +199 -0
  30. synapse/integrations/llm_openai_compat.py +90 -0
  31. synapse/integrations/llm_openai_websocket.py +187 -0
  32. synapse/integrations/mcp_client.py +646 -0
  33. synapse/integrations/vision_middleware.py +62 -0
  34. synapse/models/__init__.py +5 -0
  35. synapse/models/config.py +240 -0
  36. synapse/models/helpers.py +206 -0
  37. synapse/models/profile.py +59 -0
  38. synapse/models/registry.py +722 -0
  39. synapse/models_registry.py +7 -0
  40. synapse/observability/__init__.py +1 -0
  41. synapse/observability/startup_trace.py +127 -0
  42. synapse/runtime/__init__.py +1 -0
  43. synapse/runtime/async_runtime.py +176 -0
  44. synapse/runtime/backends.py +458 -0
  45. synapse/runtime/context_compact.py +249 -0
  46. synapse/runtime/execute_capture.py +48 -0
  47. synapse/runtime/fs_permissions.py +79 -0
  48. synapse/runtime/harness.py +57 -0
  49. synapse/runtime/hitl.py +197 -0
  50. synapse/runtime/interaction_ledger.py +82 -0
  51. synapse/runtime/middleware.py +802 -0
  52. synapse/runtime/model_request_compression_middleware.py +745 -0
  53. synapse/runtime/pathing.py +146 -0
  54. synapse/runtime/safety.py +184 -0
  55. synapse/runtime/steer.py +240 -0
  56. synapse/runtime/subagents.py +207 -0
  57. synapse/runtime/tool_ignore.py +221 -0
  58. synapse/runtime/tool_output_eval.py +118 -0
  59. synapse/runtime/tool_output_middleware.py +585 -0
  60. synapse/runtime/tool_output_usage_middleware.py +60 -0
  61. synapse/sessions/__init__.py +31 -0
  62. synapse/sessions/cancel_repair.py +208 -0
  63. synapse/sessions/session_recap.py +174 -0
  64. synapse/sessions/store.py +695 -0
  65. synapse/sessions/transcript.py +754 -0
  66. synapse/settings/__init__.py +5 -0
  67. synapse/settings/config_paths.py +184 -0
  68. synapse/settings/schema.py +464 -0
  69. synapse/tool_output/__init__.py +59 -0
  70. synapse/tool_output/detection.py +170 -0
  71. synapse/tool_output/metrics.py +32 -0
  72. synapse/tool_output/models.py +173 -0
  73. synapse/tool_output/pipeline.py +330 -0
  74. synapse/tool_output/repository.py +721 -0
  75. synapse/tool_output/transformers.py +648 -0
  76. synapse/tools/__init__.py +5 -0
  77. synapse/tools/session_tools.py +204 -0
  78. synapse/ui/__init__.py +10 -0
  79. synapse/ui/bottombar/__init__.py +73 -0
  80. synapse/ui/bottombar/components/__init__.py +143 -0
  81. synapse/ui/bottombar/components/key_hints.py +30 -0
  82. synapse/ui/bottombar/components/mcp.py +64 -0
  83. synapse/ui/bottombar/components/mode.py +24 -0
  84. synapse/ui/bottombar/components/model.py +28 -0
  85. synapse/ui/bottombar/components/thread.py +29 -0
  86. synapse/ui/bottombar/context.py +36 -0
  87. synapse/ui/bottombar/core.py +74 -0
  88. synapse/ui/dialogs/__init__.py +25 -0
  89. synapse/ui/dialogs/base.py +362 -0
  90. synapse/ui/dialogs/codex_session_list.py +84 -0
  91. synapse/ui/dialogs/compression_diagnostics.py +210 -0
  92. synapse/ui/dialogs/git_explore.py +702 -0
  93. synapse/ui/dialogs/mcp_panel.py +407 -0
  94. synapse/ui/dialogs/model_picker.py +128 -0
  95. synapse/ui/dialogs/safety_panel.py +63 -0
  96. synapse/ui/dialogs/session_list.py +98 -0
  97. synapse/ui/dialogs/theme_designer.py +863 -0
  98. synapse/ui/dialogs/theme_picker.py +113 -0
  99. synapse/ui/git_explore/__init__.py +31 -0
  100. synapse/ui/git_explore/engine.py +82 -0
  101. synapse/ui/git_explore/provider.py +242 -0
  102. synapse/ui/git_explore/unified.py +85 -0
  103. synapse/ui/rendering.py +350 -0
  104. synapse/ui/sink.py +70 -0
  105. synapse/ui/steer_widget.py +367 -0
  106. synapse/ui/stream.py +1207 -0
  107. synapse/ui/stream_events.py +421 -0
  108. synapse/ui/stream_runtime.py +252 -0
  109. synapse/ui/theme.py +1154 -0
  110. synapse/ui/timeline.py +621 -0
  111. synapse/ui/topbar/__init__.py +97 -0
  112. synapse/ui/topbar/components/__init__.py +150 -0
  113. synapse/ui/topbar/components/branch.py +41 -0
  114. synapse/ui/topbar/components/title.py +24 -0
  115. synapse/ui/topbar/components/tool_output.py +24 -0
  116. synapse/ui/topbar/components/usage.py +24 -0
  117. synapse/ui/topbar/components/workspace.py +32 -0
  118. synapse/ui/topbar/context.py +32 -0
  119. synapse/ui/topbar/core.py +979 -0
  120. synapse/ui/topbar/git_changes_popover.py +178 -0
  121. synapse/ui/topbar/git_chrome.py +475 -0
  122. synapse/ui/topbar/tool_output_popover.py +84 -0
  123. synapse/ui/topbar/widget.py +474 -0
  124. synapse/ui/tui.py +5717 -0
  125. synapse/ui/turn_rail.py +71 -0
  126. synapse/ui/user_turn.py +83 -0
  127. synapse/ui/welcome.py +261 -0
  128. synapse_cli_agent-0.1.13.dist-info/METADATA +412 -0
  129. synapse_cli_agent-0.1.13.dist-info/RECORD +131 -0
  130. synapse_cli_agent-0.1.13.dist-info/WHEEL +4 -0
  131. synapse_cli_agent-0.1.13.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,7 @@
1
+ """Compatibility exports for the model domain.
2
+
3
+ New code should import from :mod:`synapse.models.registry`.
4
+ """
5
+
6
+ from synapse.models.registry import * # noqa: F403
7
+ from synapse.models.registry import _profiles_from_mapping as _profiles_from_mapping
@@ -0,0 +1 @@
1
+ """Startup diagnostics and observability helpers."""
@@ -0,0 +1,127 @@
1
+ """Optional startup timing tracer.
2
+
3
+ Enable with env ``AGENT_STARTUP_TRACE=1`` (or ``true`` / ``yes``).
4
+ Prints cumulative stage timings to stderr when the process finishes
5
+ startup-critical work, or on demand via :func:`dump`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import sys
12
+ import threading
13
+ import time
14
+ from collections.abc import Iterator
15
+ from contextlib import contextmanager
16
+ from dataclasses import dataclass, field
17
+ from typing import Any
18
+
19
+
20
+ def _env_enabled() -> bool:
21
+ raw = (os.environ.get("AGENT_STARTUP_TRACE") or "").strip().casefold()
22
+ return raw in {"1", "true", "yes", "on"}
23
+
24
+
25
+ @dataclass
26
+ class StartupTrace:
27
+ enabled: bool = field(default_factory=_env_enabled)
28
+ t0: float = field(default_factory=time.perf_counter)
29
+ # each mark: (name, ms_since_start, ms_step)
30
+ marks: list[tuple[str, float, float]] = field(default_factory=list)
31
+ _last: float = field(default_factory=time.perf_counter)
32
+
33
+ def reset(self) -> None:
34
+ self.t0 = time.perf_counter()
35
+ self._last = self.t0
36
+ self.marks.clear()
37
+
38
+ def mark(self, name: str) -> None:
39
+ if not self.enabled:
40
+ return
41
+ now = time.perf_counter()
42
+ since = (now - self.t0) * 1000
43
+ step = (now - self._last) * 1000
44
+ self._last = now
45
+ self.marks.append((name, since, step))
46
+
47
+ @contextmanager
48
+ def span(self, name: str) -> Iterator[None]:
49
+ if not self.enabled:
50
+ yield
51
+ return
52
+ t0 = time.perf_counter()
53
+ try:
54
+ yield
55
+ finally:
56
+ now = time.perf_counter()
57
+ since = (now - self.t0) * 1000
58
+ step = (now - t0) * 1000
59
+ self._last = now
60
+ self.marks.append((name, since, step))
61
+
62
+ def dump(self, *, header: str = "startup-trace", file=None) -> None:
63
+ if not self.enabled or not self.marks:
64
+ return
65
+ out = file or sys.stderr
66
+ total = (time.perf_counter() - self.t0) * 1000
67
+ print(f"[{header}] total={total:.1f}ms stages={len(self.marks)}", file=out)
68
+ for name, since, step in self.marks:
69
+ print(f" +{step:8.1f}ms @{since:8.1f}ms {name}", file=out)
70
+ # Top offenders by step cost
71
+ top = sorted(self.marks, key=lambda x: x[2], reverse=True)[:8]
72
+ print(f"[{header}] top stages:", file=out)
73
+ for name, _since, step in top:
74
+ print(f" {step:8.1f}ms {name}", file=out)
75
+
76
+
77
+ TRACE = StartupTrace()
78
+ _LOCAL = threading.local()
79
+
80
+
81
+ def _current_trace() -> StartupTrace:
82
+ if threading.current_thread() is threading.main_thread():
83
+ return TRACE
84
+ trace = getattr(_LOCAL, "trace", None)
85
+ if trace is None:
86
+ trace = StartupTrace()
87
+ _LOCAL.trace = trace
88
+ return trace
89
+
90
+
91
+ def reset() -> None:
92
+ _current_trace().reset()
93
+
94
+
95
+ def mark(name: str) -> None:
96
+ _current_trace().mark(name)
97
+
98
+
99
+ def span(name: str):
100
+ return _current_trace().span(name)
101
+
102
+
103
+ def dump(**kwargs) -> None:
104
+ _current_trace().dump(**kwargs)
105
+
106
+
107
+ def duration(name: str, started: float, *, file=None, **fields: Any) -> None:
108
+ """Emit one independent duration line without mutating cumulative stage state."""
109
+ if not _current_trace().enabled:
110
+ return
111
+ elapsed = (time.perf_counter() - started) * 1000
112
+ suffix = " ".join(f"{key}={value}" for key, value in fields.items())
113
+ detail = f" {suffix}" if suffix else ""
114
+ out = file or sys.stderr
115
+ print(
116
+ f"[perf] thread={threading.current_thread().name} event={name} "
117
+ f"elapsed_ms={elapsed:.1f}{detail}",
118
+ file=out,
119
+ )
120
+
121
+
122
+ def ensure_started() -> None:
123
+ """Call near process entry so t0 is close to CLI start when enabled late."""
124
+ trace = _current_trace()
125
+ if trace.enabled and not trace.marks:
126
+ trace.reset()
127
+ trace.mark("trace-enabled")
@@ -0,0 +1 @@
1
+ """Runtime execution, middleware, and safety services."""
@@ -0,0 +1,176 @@
1
+ """Process-lifetime asyncio loop for AsyncSqliteSaver + astream.
2
+
3
+ LangGraph's ``AsyncSqliteSaver`` binds to the event loop that created it.
4
+ TUI / CLI call sites are mostly synchronous and previously used
5
+ ``asyncio.run`` in a worker thread (a *new* loop every turn) — that cannot
6
+ share an AsyncSqliteSaver.
7
+
8
+ This module owns one background loop thread for the whole process:
9
+
10
+ - open/setup AsyncSqliteSaver on that loop
11
+ - schedule ``agent.astream`` coroutines onto the same loop
12
+ - sync ``get_tuple`` (via LangGraph) still works from other threads via
13
+ ``run_coroutine_threadsafe``
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import atexit
20
+ import threading
21
+ import time
22
+ from collections.abc import Coroutine
23
+ from typing import Any, TypeVar
24
+
25
+ T = TypeVar("T")
26
+
27
+
28
+ class AsyncRuntime:
29
+ """Daemon thread running ``loop.run_forever()`` for checkpoint + astream."""
30
+
31
+ def __init__(self, *, name: str = "coding-async-runtime") -> None:
32
+ self._name = name
33
+ self._loop: asyncio.AbstractEventLoop | None = None
34
+ self._thread: threading.Thread | None = None
35
+ self._ready = threading.Event()
36
+ self._lock = threading.Lock()
37
+ self._closed = False
38
+ self._owned_conns: list[Any] = []
39
+
40
+ @property
41
+ def loop(self) -> asyncio.AbstractEventLoop:
42
+ self.start()
43
+ assert self._loop is not None
44
+ return self._loop
45
+
46
+ def start(self) -> None:
47
+ with self._lock:
48
+ if self._loop is not None and self._thread is not None and self._thread.is_alive():
49
+ return
50
+ if self._closed:
51
+ raise RuntimeError("AsyncRuntime is closed")
52
+ self._ready.clear()
53
+ self._thread = threading.Thread(
54
+ target=self._thread_main,
55
+ name=self._name,
56
+ daemon=True,
57
+ )
58
+ self._thread.start()
59
+ if not self._ready.wait(timeout=5.0):
60
+ raise RuntimeError("AsyncRuntime loop failed to start")
61
+
62
+ def _thread_main(self) -> None:
63
+ loop = asyncio.new_event_loop()
64
+ asyncio.set_event_loop(loop)
65
+ self._loop = loop
66
+ self._ready.set()
67
+ try:
68
+ loop.run_forever()
69
+ finally:
70
+ try:
71
+ pending = asyncio.all_tasks(loop)
72
+ for task in pending:
73
+ task.cancel()
74
+ if pending:
75
+ loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
76
+ except Exception: # noqa: BLE001
77
+ pass
78
+ loop.close()
79
+
80
+ def submit(self, coro: Coroutine[Any, Any, T]) -> asyncio.Future[T]:
81
+ """Schedule a coroutine on the runtime loop (thread-safe)."""
82
+ return asyncio.run_coroutine_threadsafe(coro, self.loop)
83
+
84
+ def run(self, coro: Coroutine[Any, Any, T], *, timeout: float | None = None) -> T:
85
+ """Block until the coroutine finishes on the runtime loop."""
86
+ return self.submit(coro).result(timeout=timeout)
87
+
88
+ def track_connection(self, conn: Any) -> None:
89
+ """Remember an async connection/client for shutdown."""
90
+ if conn not in self._owned_conns:
91
+ self._owned_conns.append(conn)
92
+
93
+ def close_connection(self, conn: Any) -> None:
94
+ """Close and forget one tracked async client on the runtime loop."""
95
+ try:
96
+ self._owned_conns.remove(conn)
97
+ except ValueError:
98
+ pass
99
+ close = getattr(conn, "aclose", None) or getattr(conn, "close", None)
100
+ if not callable(close):
101
+ return
102
+
103
+ async def _close() -> None:
104
+ result = close()
105
+ if asyncio.iscoroutine(result):
106
+ await result
107
+
108
+ try:
109
+ self.run(_close(), timeout=3.0)
110
+ except Exception: # noqa: BLE001
111
+ pass
112
+
113
+ def close(self) -> None:
114
+ started = time.perf_counter()
115
+ with self._lock:
116
+ if self._closed:
117
+ return
118
+ self._closed = True
119
+ loop = self._loop
120
+ thread = self._thread
121
+ connection_count = len(self._owned_conns)
122
+ if loop is not None and loop.is_running():
123
+
124
+ async def _shutdown() -> None:
125
+ for conn in list(self._owned_conns):
126
+ try:
127
+ close = getattr(conn, "aclose", None) or getattr(conn, "close", None)
128
+ if not callable(close):
129
+ continue
130
+ result = close()
131
+ if asyncio.iscoroutine(result):
132
+ await result
133
+ except Exception: # noqa: BLE001
134
+ pass
135
+ self._owned_conns.clear()
136
+
137
+ try:
138
+ asyncio.run_coroutine_threadsafe(_shutdown(), loop).result(timeout=3.0)
139
+ except Exception: # noqa: BLE001
140
+ pass
141
+ finally:
142
+ try:
143
+ loop.call_soon_threadsafe(loop.stop)
144
+ except Exception: # noqa: BLE001
145
+ pass
146
+ if thread is not None and thread.is_alive():
147
+ thread.join(timeout=2.0)
148
+ self._loop = None
149
+ self._thread = None
150
+ try:
151
+ from synapse.observability.startup_trace import duration
152
+
153
+ duration("runtime.close", started, connections=connection_count)
154
+ except Exception: # noqa: BLE001
155
+ pass
156
+
157
+
158
+ _RUNTIME = AsyncRuntime()
159
+ atexit.register(_RUNTIME.close)
160
+
161
+
162
+ def get_async_runtime() -> AsyncRuntime:
163
+ """Process-global async runtime (lazy-started)."""
164
+ return _RUNTIME
165
+
166
+
167
+ def reset_async_runtime_for_tests() -> AsyncRuntime:
168
+ """Close and replace the global runtime (tests only)."""
169
+ global _RUNTIME
170
+ try:
171
+ _RUNTIME.close()
172
+ except Exception: # noqa: BLE001
173
+ pass
174
+ _RUNTIME = AsyncRuntime(name="coding-async-runtime-test")
175
+ atexit.register(_RUNTIME.close)
176
+ return _RUNTIME