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.
- {clikernel-0.2.17 → clikernel-0.2.18}/CHANGELOG.md +7 -0
- {clikernel-0.2.17/clikernel.egg-info → clikernel-0.2.18}/PKG-INFO +1 -1
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/__init__.py +1 -1
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/core.py +3 -4
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/mcp.py +10 -7
- {clikernel-0.2.17 → clikernel-0.2.18/clikernel.egg-info}/PKG-INFO +1 -1
- {clikernel-0.2.17 → clikernel-0.2.18}/LICENSE +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/MANIFEST.in +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/README.md +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/_modidx.py +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/cli.py +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/rules.py +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/skill.py +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel/stream.py +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel.egg-info/SOURCES.txt +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel.egg-info/dependency_links.txt +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel.egg-info/entry_points.txt +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel.egg-info/requires.txt +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/clikernel.egg-info/top_level.txt +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/pyproject.toml +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/setup.cfg +0 -0
- {clikernel-0.2.17 → clikernel-0.2.18}/tests/test_rules.py +0 -0
- {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
|
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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,{},
|
|
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'
|
|
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)))
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|