clikernel 0.2.16__tar.gz → 0.2.18__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.
Files changed (23) hide show
  1. {clikernel-0.2.16 → clikernel-0.2.18}/CHANGELOG.md +14 -0
  2. {clikernel-0.2.16/clikernel.egg-info → clikernel-0.2.18}/PKG-INFO +3 -3
  3. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel/__init__.py +1 -1
  4. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel/_modidx.py +1 -1
  5. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel/core.py +8 -7
  6. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel/mcp.py +10 -7
  7. {clikernel-0.2.16 → clikernel-0.2.18/clikernel.egg-info}/PKG-INFO +3 -3
  8. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel.egg-info/requires.txt +2 -2
  9. {clikernel-0.2.16 → clikernel-0.2.18}/pyproject.toml +2 -2
  10. {clikernel-0.2.16 → clikernel-0.2.18}/LICENSE +0 -0
  11. {clikernel-0.2.16 → clikernel-0.2.18}/MANIFEST.in +0 -0
  12. {clikernel-0.2.16 → clikernel-0.2.18}/README.md +0 -0
  13. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel/cli.py +0 -0
  14. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel/rules.py +0 -0
  15. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel/skill.py +0 -0
  16. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel/stream.py +0 -0
  17. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel.egg-info/SOURCES.txt +0 -0
  18. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel.egg-info/dependency_links.txt +0 -0
  19. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel.egg-info/entry_points.txt +0 -0
  20. {clikernel-0.2.16 → clikernel-0.2.18}/clikernel.egg-info/top_level.txt +0 -0
  21. {clikernel-0.2.16 → clikernel-0.2.18}/setup.cfg +0 -0
  22. {clikernel-0.2.16 → clikernel-0.2.18}/tests/test_rules.py +0 -0
  23. {clikernel-0.2.16 → clikernel-0.2.18}/tests/test_stream.py +0 -0
@@ -2,6 +2,20 @@
2
2
 
3
3
  <!-- do not remove -->
4
4
 
5
+ ## 0.2.18
6
+
7
+ ### New Features
8
+
9
+ - Track current gateway per subagent, route cancellations to the handling gateway, and send Claude Code session id to named hosts ([#60](https://github.com/AnswerDotAI/clikernel/issues/60))
10
+
11
+
12
+ ## 0.2.17
13
+
14
+ ### New Features
15
+
16
+ - Preserve obj.full in Python output metadata via ipymini display hook; rename `_top_level_display` to `_python_display`; bump rustygate, ipymini ([#58](https://github.com/AnswerDotAI/clikernel/issues/58))
17
+
18
+
5
19
  ## 0.2.16
6
20
 
7
21
  ### New Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clikernel
3
- Version: 0.2.16
3
+ Version: 0.2.18
4
4
  Summary: Serve persistent Jupyter kernels to LLMs as concise text, over MCP or a plain stream protocol
5
5
  Author: clikernel contributors
6
6
  License-Expression: Apache-2.0
@@ -15,8 +15,8 @@ Requires-Dist: fastcore>=2.2.2
15
15
  Requires-Dist: jupyasyncclient>=0.2.10
16
16
  Requires-Dist: jupywire>=0.1.9
17
17
  Requires-Dist: mcpmini>=0.0.5
18
- Requires-Dist: rustygate>=0.1.25
19
- Requires-Dist: ipymini>=0.1.23
18
+ Requires-Dist: rustygate>=0.1.27
19
+ Requires-Dist: ipymini>=0.1.26
20
20
  Requires-Dist: httpx
21
21
  Requires-Dist: ipython
22
22
  Provides-Extra: dev
@@ -8,4 +8,4 @@ Modules:
8
8
  - `clikernel.skill`: Use `clikernel` MCP as the default persistent Python workspace for file/notebook reads and edits, exploration, library checks, data reshaping and debugging. Imports and variables carry across calls. Read before writing, running or debugging Python 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.16"
11
+ __version__ = "0.2.18"
@@ -23,7 +23,7 @@ d = { 'settings': { 'branch': 'main',
23
23
  'clikernel.core.Gateway.rpc': ('core.html#gateway.rpc', 'clikernel/core.py'),
24
24
  'clikernel.core.Gateway.text': ('core.html#gateway.text', 'clikernel/core.py'),
25
25
  'clikernel.core.Gateway.tools': ('core.html#gateway.tools', 'clikernel/core.py'),
26
- 'clikernel.core._top_level_display': ('core.html#_top_level_display', 'clikernel/core.py'),
26
+ 'clikernel.core._python_display': ('core.html#_python_display', 'clikernel/core.py'),
27
27
  'clikernel.core._with_file': ('core.html#_with_file', 'clikernel/core.py'),
28
28
  'clikernel.core.cfg_dir': ('core.html#cfg_dir', 'clikernel/core.py'),
29
29
  'clikernel.core.default_gateway': ('core.html#default_gateway', 'clikernel/core.py'),
@@ -53,9 +53,11 @@ try: exec(compile({src!r}, __file__, 'exec'))
53
53
  finally: del __file__'''
54
54
 
55
55
  # %% ../nbs/00_core.ipynb #b0dd40c1
56
- def _top_level_display(shell):
57
- "Make `ast_node_interactivity='all'` display top-level expressions only."
56
+ def _python_display(shell):
57
+ "Display top-level expressions and preserve full result metadata."
58
58
  import ast
59
+ from ipymini.term.display import enable_full_metadata
60
+ enable_full_metadata(shell.display_formatter)
59
61
  run = shell.run_ast_nodes
60
62
  async def run_ast_nodes(nodelist, cell_name, interactivity='last_expr', **kwargs):
61
63
  if interactivity != 'all': return await run(nodelist, cell_name, interactivity, **kwargs)
@@ -69,16 +71,15 @@ def _top_level_display(shell):
69
71
  def startup_src(cfgdir=None):
70
72
  "Build Python defaults and user startup, in that order."
71
73
  d = Path(cfgdir) if cfgdir else cfg_dir()
72
- parts = [inspect.getsource(_top_level_display) + '_top_level_display(get_ipython())\ndel _top_level_display']
74
+ parts = [inspect.getsource(_python_display) + '_python_display(get_ipython())\ndel _python_display']
73
75
  if (p := d/'startup.py').exists(): parts.append(_with_file(p.read_text(), p))
74
76
  return '\n'.join(parts)
75
77
 
76
78
  def session_defaults(cfgdir=None, quiet=False, local=True):
77
- "Return startup and quiet settings for rustygate initialization. Local sessions also include cwd and env."
79
+ "Return startup and quiet settings for rustygate initialization. Local sessions also include cwd and env; named hosts, the Claude Code session id."
78
80
  d = dict(startup=startup_src(cfgdir), quiet=quiet)
79
- if local:
80
- d['cwd'] = os.getcwd()
81
- d['env'] = dict(os.environ)
81
+ if local: d.update(cwd=os.getcwd(), env=dict(os.environ))
82
+ elif sid := os.environ.get('CLAUDE_CODE_SESSION_ID'): d['env'] = dict(CLAUDE_CODE_SESSION_ID=sid)
82
83
  return d
83
84
 
84
85
  # %% ../nbs/00_core.ipynb #10ed59fc
@@ -2,7 +2,7 @@
2
2
 
3
3
  Claude Code starts `clikernel-mcp` for each conversation. Its `Router` accepts MCP messages over stdio and sends tool calls to rustygate gateways. Rustygate implements the tools. The router gets their schemas from the local gateway and adds `host` to `list_kernels`, `use_kernel`, and `create`.
4
4
 
5
- Each host has a separate gateway session. The router remembers one current host for calls without an explicit `host`. It removes `host` before forwarding a call but preserves the JSON-RPC request id. Cancellation notifications also retain their original ids.
5
+ Each host has a separate gateway session. Calls without an explicit `host` go to their caller's current host. The router keeps one for the main conversation and one for each subagent that `clikernel-hook` names with `agent`; a subagent starts on the main conversation's current host. The router removes `host` before forwarding a call but preserves the JSON-RPC request id. Cancellation notifications also retain their original ids.
6
6
 
7
7
  Before forwarding `exec`, the router checks the cell against `clikernel.rules`. Most rules add a note to the reply. Two rules block the cell, which then never reaches a gateway.
8
8
 
@@ -29,13 +29,13 @@ HOST_PARAM = {'type': 'string', 'description': 'Gateway to target: a gateways.to
29
29
  HOSTED = ('list_kernels', 'use_kernel', 'create')
30
30
 
31
31
  class Router:
32
- "Route stdio MCP to rustygate with one session per host and one current host."
32
+ "Route stdio MCP to rustygate with one session per host and a current host for each caller."
33
33
  def __init__(
34
34
  self,
35
35
  cfgdir=None, # Config dir for `session_defaults` and `gateways.toml` (the standard one if None)
36
36
  quiet=False, # Keep startup output and rule notes out of replies?
37
37
  ):
38
- self.cfgdir,self.quiet,self.sessions,self.cur,self.child = cfgdir,quiet,{},'',None
38
+ self.cfgdir,self.quiet,self.sessions,self.cur,self.inflight,self.child = cfgdir,quiet,{},{},{},None
39
39
 
40
40
  async def session(self, host=''):
41
41
  "Return the session for `host`, initializing it on first use. Empty `host` selects the default local gateway."
@@ -77,7 +77,8 @@ async def dispatch(self:Router, msg, requester=None):
77
77
  return jresp(id, dict(protocolVersion=msg['params'].get('protocolVersion', '2025-06-18'), capabilities=dict(tools={}),
78
78
  serverInfo=dict(name='clikernel', version=__version__), instructions=info.get('instructions')))
79
79
  if id is None:
80
- if method == 'notifications/cancelled': await (await self.session(self.cur)).tr.send(msg)
80
+ if method == 'notifications/cancelled' and (h := self.inflight.get(msg['params'].get('requestId'))) is not None:
81
+ await (await self.session(h)).tr.send(msg)
81
82
  return None
82
83
  if method == 'ping': return jresp(id, {})
83
84
  if method == 'tools/list': return jresp(id, dict(tools=await self.tools()))
@@ -86,16 +87,18 @@ async def dispatch(self:Router, msg, requester=None):
86
87
  fired = scan(args.get('code', '')) if msg['params']['name'] == 'exec' else []
87
88
  if blocks := [r.note for r in fired if r.block]:
88
89
  return jresp(id, dict(isError=True, content=[dict(type='text', text=' '.join(blocks)+' (If the block seems wrong here, tell your user.)')]))
89
- has_host = 'host' in args
90
+ has_host,agent = 'host' in args, args.get('agent')
90
91
  host = args.pop('host', '') or ''
91
- h = host if has_host else self.cur
92
+ h = host if has_host else self.cur.setdefault(agent, self.cur.get(None, ''))
92
93
  s = await self.session(h)
93
- if has_host and msg['params']['name'] in ('use_kernel', 'create'): self.cur = host
94
+ if has_host and msg['params']['name'] in ('use_kernel', 'create'): self.cur[agent] = host
95
+ self.inflight[id] = h
94
96
  try: res = await s.tr.send(msg)
95
97
  except httpx.HTTPStatusError as e:
96
98
  if e.response.status_code != 404: raise
97
99
  del self.sessions[h]
98
100
  res = await (await self.session(h)).tr.send(msg)
101
+ finally: self.inflight.pop(id, None)
99
102
  if 'result' in res:
100
103
  content = res['result']['content'] = _cap_images(res['result']['content'])
101
104
  if fired and not self.quiet: content.insert(0, dict(type='text', text=''.join(f'<note>\n{r.note}\n</note>\n' for r in fired)))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clikernel
3
- Version: 0.2.16
3
+ Version: 0.2.18
4
4
  Summary: Serve persistent Jupyter kernels to LLMs as concise text, over MCP or a plain stream protocol
5
5
  Author: clikernel contributors
6
6
  License-Expression: Apache-2.0
@@ -15,8 +15,8 @@ Requires-Dist: fastcore>=2.2.2
15
15
  Requires-Dist: jupyasyncclient>=0.2.10
16
16
  Requires-Dist: jupywire>=0.1.9
17
17
  Requires-Dist: mcpmini>=0.0.5
18
- Requires-Dist: rustygate>=0.1.25
19
- Requires-Dist: ipymini>=0.1.23
18
+ Requires-Dist: rustygate>=0.1.27
19
+ Requires-Dist: ipymini>=0.1.26
20
20
  Requires-Dist: httpx
21
21
  Requires-Dist: ipython
22
22
  Provides-Extra: dev
@@ -2,8 +2,8 @@ fastcore>=2.2.2
2
2
  jupyasyncclient>=0.2.10
3
3
  jupywire>=0.1.9
4
4
  mcpmini>=0.0.5
5
- rustygate>=0.1.25
6
- ipymini>=0.1.23
5
+ rustygate>=0.1.27
6
+ ipymini>=0.1.26
7
7
  httpx
8
8
  ipython
9
9
 
@@ -20,8 +20,8 @@ dependencies = [
20
20
  "jupyasyncclient>=0.2.10",
21
21
  "jupywire>=0.1.9",
22
22
  "mcpmini>=0.0.5",
23
- "rustygate>=0.1.25",
24
- "ipymini>=0.1.23",
23
+ "rustygate>=0.1.27",
24
+ "ipymini>=0.1.26",
25
25
  "httpx",
26
26
  "ipython",
27
27
  ]
File without changes
File without changes
File without changes
File without changes
File without changes