clikernel 0.2.4__tar.gz → 0.2.5__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.
- {clikernel-0.2.4 → clikernel-0.2.5}/CHANGELOG.md +7 -0
- {clikernel-0.2.4/clikernel.egg-info → clikernel-0.2.5}/PKG-INFO +1 -1
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel/__init__.py +1 -1
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel/_modidx.py +1 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel/core.py +26 -15
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel/mcp.py +1 -1
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel/skill.py +1 -1
- {clikernel-0.2.4 → clikernel-0.2.5/clikernel.egg-info}/PKG-INFO +1 -1
- {clikernel-0.2.4 → clikernel-0.2.5}/LICENSE +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/MANIFEST.in +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/README.md +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel/cli.py +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel/stream.py +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel.egg-info/SOURCES.txt +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel.egg-info/dependency_links.txt +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel.egg-info/entry_points.txt +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel.egg-info/requires.txt +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/clikernel.egg-info/top_level.txt +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/pyproject.toml +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/setup.cfg +0 -0
- {clikernel-0.2.4 → clikernel-0.2.5}/tests/test_stream.py +0 -0
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- do not remove -->
|
|
4
4
|
|
|
5
|
+
## 0.2.5
|
|
6
|
+
|
|
7
|
+
### New Features
|
|
8
|
+
|
|
9
|
+
- restart now re-runs startup.py and reinstalls inspectors on client-created kernels; attached kernels still restart bare ([#37](https://github.com/AnswerDotAI/clikernel/issues/37))
|
|
10
|
+
|
|
11
|
+
|
|
5
12
|
## 0.2.4
|
|
6
13
|
|
|
7
14
|
### New Features
|
|
@@ -8,4 +8,4 @@ Modules:
|
|
|
8
8
|
- `clikernel.skill`: Use the persistent `clikernel` MCP session as the default workspace for any task advanced through live Python execution -- stateful inspection, file-editing workflows, debugging, experiments, API probes, data transforms, or notebook-style work. Read this before writing, running, or debugging Python code in a session with `clikernel` connected.
|
|
9
9
|
- `clikernel.stream`: Streaming JSON-lines worker protocol: nbformat-shaped output events, and a supervisor for select-based UIs."""
|
|
10
10
|
|
|
11
|
-
__version__ = "0.2.
|
|
11
|
+
__version__ = "0.2.5"
|
|
@@ -16,6 +16,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
16
16
|
'clikernel.cli.serve_stream': ('cli.html#serve_stream', 'clikernel/cli.py')},
|
|
17
17
|
'clikernel.core': { 'clikernel.core.Client': ('core.html#client', 'clikernel/core.py'),
|
|
18
18
|
'clikernel.core.Client.__init__': ('core.html#client.__init__', 'clikernel/core.py'),
|
|
19
|
+
'clikernel.core.Client._setup': ('core.html#client._setup', 'clikernel/core.py'),
|
|
19
20
|
'clikernel.core.Client._use': ('core.html#client._use', 'clikernel/core.py'),
|
|
20
21
|
'clikernel.core.Client.aclose': ('core.html#client.aclose', 'clikernel/core.py'),
|
|
21
22
|
'clikernel.core.Client.connect': ('core.html#client.connect', 'clikernel/core.py'),
|
|
@@ -7,7 +7,7 @@ Docs: https://AnswerDotAI.github.io/clikernel/core.html.md"""
|
|
|
7
7
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.
|
|
8
8
|
|
|
9
9
|
# %% auto #0
|
|
10
|
-
__all__ = ['DEFAULT_URL', 'MAXLEN', 'STATE_LOST', 'cfg_dir', 'gateways', 'resolve', 'Client']
|
|
10
|
+
__all__ = ['DEFAULT_URL', 'MAXLEN', 'STATE_LOST', 'STATE_LOST_SETUP', 'cfg_dir', 'gateways', 'resolve', 'Client']
|
|
11
11
|
|
|
12
12
|
# %% ../nbs/00_core.ipynb #2b3b7f4a
|
|
13
13
|
import os, tomllib
|
|
@@ -92,10 +92,11 @@ def _inspector_setup(src):
|
|
|
92
92
|
|
|
93
93
|
# %% ../nbs/00_core.ipynb #33b7bc28
|
|
94
94
|
STATE_LOST = 'NOTE: the kernel restarted with a fresh interpreter: all session state is lost (imports, variables, monkeypatches). Redo any setup the task still needs.'
|
|
95
|
+
STATE_LOST_SETUP = 'NOTE: the kernel restarted with a fresh interpreter: startup.py has been re-run (its imports are back), but all other session state is lost (variables, monkeypatches, other imports). Redo what the task still needs.'
|
|
95
96
|
|
|
96
97
|
class Client:
|
|
97
98
|
"One conversation's connection: a gateway manager, and the current kernel"
|
|
98
|
-
def __init__(self, cfgdir=None): self.cfgdir,self.mgr,self.kc,self.kid,self.auto = cfgdir,None,None,None,False
|
|
99
|
+
def __init__(self, cfgdir=None): self.cfgdir,self.mgr,self.kc,self.kid,self.auto,self.made = cfgdir,None,None,None,False,False
|
|
99
100
|
|
|
100
101
|
async def _use(self, mgr, kid):
|
|
101
102
|
"Point at `kid` on `mgr`, dropping any previous ws (never the kernel)"
|
|
@@ -106,6 +107,20 @@ class Client:
|
|
|
106
107
|
await self.kc.wait_for_ready(timeout=30)
|
|
107
108
|
|
|
108
109
|
# %% ../nbs/00_core.ipynb #94497e4a
|
|
110
|
+
@patch
|
|
111
|
+
async def _setup(self:Client):
|
|
112
|
+
"Deliver `startup.py` and `inspectors.py` to the current kernel, returning startup's output"
|
|
113
|
+
out = ''
|
|
114
|
+
d = Path(self.cfgdir) if self.cfgdir else cfg_dir()
|
|
115
|
+
if (p := d/'startup.py').exists(): out = await self.execute(_startup_src(p.read_text(), p))
|
|
116
|
+
if (p := d/'inspectors.py').exists():
|
|
117
|
+
res = await self.execute(_inspector_setup(p.read_text()))
|
|
118
|
+
if res: # a load failure is fatal: refusing to run beats running uninspected
|
|
119
|
+
await self.mgr.shutdown_kernel(self.kid)
|
|
120
|
+
self.kc,self.kid,self.auto,self.made = None,None,False,False
|
|
121
|
+
raise RuntimeError(f'inspectors.py failed to load; kernel stopped:\n{res}')
|
|
122
|
+
return out
|
|
123
|
+
|
|
109
124
|
@patch
|
|
110
125
|
async def connect(self:Client, host='', kernel='', auto=False):
|
|
111
126
|
"Connect to a gateway (create a kernel, or attach to `kernel` by id prefix); the pointer aims at it afterwards; `auto` marks a created kernel as client-scoped"
|
|
@@ -113,7 +128,7 @@ async def connect(self:Client, host='', kernel='', auto=False):
|
|
|
113
128
|
if self.auto: # scoped to this client: any new connect ends it, even one already dead
|
|
114
129
|
try: await self.stop()
|
|
115
130
|
except Exception as e:
|
|
116
|
-
self.kc,self.kid,self.auto = None,None,False
|
|
131
|
+
self.kc,self.kid,self.auto,self.made = None,None,False,False
|
|
117
132
|
note = f'\nnote: stopping the auto kernel failed ({e})'
|
|
118
133
|
url,tok,ver = resolve(host, self.cfgdir)
|
|
119
134
|
mgr = JupyAsyncMultiKernelManager(url, token=tok, verify=ver)
|
|
@@ -122,19 +137,13 @@ async def connect(self:Client, host='', kernel='', auto=False):
|
|
|
122
137
|
kid = first(k['id'] for k in ks if k['id'].startswith(kernel))
|
|
123
138
|
if not kid: raise ValueError(f'no kernel matching {kernel!r} on {url}: {[k["id"][:8] for k in ks]}')
|
|
124
139
|
await self._use(mgr, kid)
|
|
140
|
+
self.made = False
|
|
125
141
|
return f'connected to existing kernel {kid} on {url}' + note
|
|
126
142
|
kw = dict(cwd=os.getcwd(), env=dict(os.environ)) if not host else {} # the default gateway is local by definition: kernels start where, and as, the conversation lives (cwd and environment - so kernel-side tools that key state to the conversation, like llmdojo's doc-state, resolve it correctly)
|
|
127
143
|
kid = await mgr.start_kernel(**kw)
|
|
128
144
|
await self._use(mgr, kid)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (p := d/'startup.py').exists(): out = await self.execute(_startup_src(p.read_text(), p))
|
|
132
|
-
if (p := d/'inspectors.py').exists():
|
|
133
|
-
res = await self.execute(_inspector_setup(p.read_text()))
|
|
134
|
-
if res: # a load failure is fatal: refusing to start beats running uninspected
|
|
135
|
-
await self.mgr.shutdown_kernel(kid)
|
|
136
|
-
self.kc,self.kid = None,None
|
|
137
|
-
raise RuntimeError(f'inspectors.py failed to load; kernel stopped:\n{res}')
|
|
145
|
+
self.made = True
|
|
146
|
+
out = await self._setup()
|
|
138
147
|
self.auto = auto
|
|
139
148
|
return f'created kernel {kid} on {url}' + (f'\n{out}' if out.strip() else '') + note
|
|
140
149
|
|
|
@@ -175,16 +184,18 @@ async def stop(self:Client, kernel=''):
|
|
|
175
184
|
await self.mgr.shutdown_kernel(kid)
|
|
176
185
|
if kid == self.kid:
|
|
177
186
|
await self.kc.aclose()
|
|
178
|
-
self.kc,self.kid,self.auto = None,None,False
|
|
187
|
+
self.kc,self.kid,self.auto,self.made = None,None,False,False
|
|
179
188
|
return f'stopped kernel {kid}'
|
|
180
189
|
|
|
181
190
|
@patch
|
|
182
191
|
async def restart(self:Client):
|
|
183
|
-
"Restart the current kernel: same id, fresh interpreter"
|
|
192
|
+
"Restart the current kernel: same id, fresh interpreter; a kernel this client created gets `startup.py` and `inspectors.py` again"
|
|
184
193
|
if not self.kc: return 'no kernel: call `connect` first'
|
|
185
194
|
await self.mgr.restart_kernel(self.kid)
|
|
186
195
|
await self.kc.wait_for_ready(timeout=30)
|
|
187
|
-
return STATE_LOST
|
|
196
|
+
if not self.made: return STATE_LOST
|
|
197
|
+
out = await self._setup()
|
|
198
|
+
return STATE_LOST_SETUP + (f'\n{out}' if out.strip() else '')
|
|
188
199
|
|
|
189
200
|
@patch
|
|
190
201
|
async def interrupt(self:Client):
|
|
@@ -67,7 +67,7 @@ def mk_server(c:Client):
|
|
|
67
67
|
return await c.stop(kernel)
|
|
68
68
|
|
|
69
69
|
async def restart()->str:
|
|
70
|
-
"Kill the current kernel's process and start a fresh one under the same id: `sys.modules` genuinely reset, all session state discarded. Use for a clean slate, after rebuilding a native extension, or after a reload left stale classes behind (symptoms: `isinstance` mysteriously failing, a class missing a method you know it has). Also works when `execute` is stuck.
|
|
70
|
+
"Kill the current kernel's process and start a fresh one under the same id: `sys.modules` genuinely reset, all session state discarded. Use for a clean slate, after rebuilding a native extension, or after a reload left stale classes behind (symptoms: `isinstance` mysteriously failing, a class missing a method you know it has). Also works when `execute` is stuck. A kernel this conversation created gets startup.py re-run and inspectors reinstalled (the reply says so); an attached kernel restarts bare, as found."
|
|
71
71
|
return await c.restart()
|
|
72
72
|
|
|
73
73
|
async def interrupt()->str:
|
|
@@ -14,7 +14,7 @@ The lifecycle has one implicit convenience and no implicit destruction beyond it
|
|
|
14
14
|
- Returning to earlier work (the user asks to continue where a previous conversation left off, or to use their solveit kernel): `list_kernels` to see what's running, then `connect` with the kernel id (or unique prefix). Attach runs nothing -- the kernel's live state is the point.
|
|
15
15
|
- End of work: an auto kernel stops itself with the conversation. `stop_kernel` an explicitly created kernel when it was for this task only; leave it running if the user wants to return to it, and tell the user its id so they can.
|
|
16
16
|
|
|
17
|
-
`restart` gives the current kernel a genuinely fresh interpreter under the same id (redo
|
|
17
|
+
`restart` gives the current kernel a genuinely fresh interpreter under the same id (a kernel this conversation created gets `startup.py` and inspectors again; redo any other setup); `interrupt` stops a too-long `execute` while keeping state. If a reply says the kernel died, `connect` again. If `connect` fails because the gateway is unreachable, the gateway server is not running -- report that to the user rather than working around it.
|
|
18
18
|
|
|
19
19
|
Remote gateways (a rustygate or solveit instance elsewhere) are the same verbs with a `host`: a name from `~/.config/clikernel/gateways.toml` (`[gateways.<name>]` tables with `url`, `token` or `token_env`, and optional `verify = false` for self-signed TLS) or a URL. Tokens live in the config file, never in tool arguments.
|
|
20
20
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|