clikernel 0.2.17__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.17 → clikernel-0.2.18}/CHANGELOG.md +7 -0
  2. {clikernel-0.2.17/clikernel.egg-info → clikernel-0.2.18}/PKG-INFO +1 -1
  3. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/__init__.py +1 -1
  4. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/core.py +3 -4
  5. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/mcp.py +10 -7
  6. {clikernel-0.2.17 → clikernel-0.2.18/clikernel.egg-info}/PKG-INFO +1 -1
  7. {clikernel-0.2.17 → clikernel-0.2.18}/LICENSE +0 -0
  8. {clikernel-0.2.17 → clikernel-0.2.18}/MANIFEST.in +0 -0
  9. {clikernel-0.2.17 → clikernel-0.2.18}/README.md +0 -0
  10. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/_modidx.py +0 -0
  11. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/cli.py +0 -0
  12. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/rules.py +0 -0
  13. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/skill.py +0 -0
  14. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/stream.py +0 -0
  15. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel.egg-info/SOURCES.txt +0 -0
  16. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel.egg-info/dependency_links.txt +0 -0
  17. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel.egg-info/entry_points.txt +0 -0
  18. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel.egg-info/requires.txt +0 -0
  19. {clikernel-0.2.17 → clikernel-0.2.18}/clikernel.egg-info/top_level.txt +0 -0
  20. {clikernel-0.2.17 → clikernel-0.2.18}/pyproject.toml +0 -0
  21. {clikernel-0.2.17 → clikernel-0.2.18}/setup.cfg +0 -0
  22. {clikernel-0.2.17 → clikernel-0.2.18}/tests/test_rules.py +0 -0
  23. {clikernel-0.2.17 → clikernel-0.2.18}/tests/test_stream.py +0 -0
@@ -2,6 +2,13 @@
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
+
5
12
  ## 0.2.17
6
13
 
7
14
  ### New Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clikernel
3
- Version: 0.2.17
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
@@ -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.17"
11
+ __version__ = "0.2.18"
@@ -76,11 +76,10 @@ def startup_src(cfgdir=None):
76
76
  return '\n'.join(parts)
77
77
 
78
78
  def session_defaults(cfgdir=None, quiet=False, local=True):
79
- "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."
80
80
  d = dict(startup=startup_src(cfgdir), quiet=quiet)
81
- if local:
82
- d['cwd'] = os.getcwd()
83
- 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)
84
83
  return d
85
84
 
86
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.17
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes