@hy-sde-org/dsh-code-runtime-kernels 0.1.0-rc.8

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.
@@ -0,0 +1,615 @@
1
+ /**
2
+ * The persistent Python kernel embedded as source. One long-lived `python3`
3
+ * subprocess runs this script; the host and it speak NDJSON over stdio. The
4
+ * script is deliberately SELF-CONTAINED (standard library only) so no
5
+ * virtualenv, package install, or site customization is needed to run model
6
+ * code against a persistent namespace and event loop.
7
+ *
8
+ * Wire protocol (one JSON object per line):
9
+ * - host -> kernel: `{"type":"exec","id":str,"code":str,"namespaces":[...],"cwd"?,"env"?}`,
10
+ * `{"type":"reply","id":str,"seq":int,"ok":bool,"value"?|"message"}`,
11
+ * `{"type":"exit"}`
12
+ * - kernel -> host: `{"type":"ready","pid":int}`, then per exec:
13
+ * `{"type":"started","id"}`, `{"type":"log","id","text","stream"?}`,
14
+ * `{"type":"call","id","seq":int,"global","name","args"}`,
15
+ * `{"type":"error","id","ename","evalue","traceback":[]}`,
16
+ * `{"type":"done","id","status":"ok"|"error","value"?,"executionCount":int,"cancelled":bool}`
17
+ *
18
+ * The runner is a persistent kernel: `user_ns` and one asyncio event loop
19
+ * survive across cells, so top-level `await` interleaves and assignments
20
+ * persist; tool bindings are bridged through wire frames that the host
21
+ * resolves against the CURRENT run's namespaces.
22
+ *
23
+ * @module @deepseek-ai/dsh-code-runtime-python/src/runner
24
+ */
25
+ export const PYTHON_RUNNER = String.raw `"""DSH Python kernel runner (embedded).
26
+
27
+ Persistent subprocess kernel for the dsh code-runtime seam's Python backend.
28
+ Protocol: one JSON object per line on stdin (host->kernel) and stdout
29
+ (kernel->host). Self-contained: standard library only.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import ast
35
+ import asyncio
36
+ import builtins
37
+ import contextvars
38
+ import io
39
+ import json
40
+ import os
41
+ import signal
42
+ import sys
43
+ import threading
44
+ import traceback
45
+ from typing import Any
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Frame output + fd-1 capture (children must not corrupt the NDJSON channel)
49
+ # ---------------------------------------------------------------------------
50
+
51
+ try:
52
+ _FRAME_FD = os.dup(sys.__stdout__.fileno())
53
+ _RAW_FD = os.fdopen(_FRAME_FD, "w", encoding="utf-8", errors="backslashreplace")
54
+ _CAPTURE_READ_FD, _capture_write_fd = os.pipe()
55
+ os.dup2(_capture_write_fd, sys.__stdout__.fileno())
56
+ os.close(_capture_write_fd)
57
+ except (AttributeError, OSError, ValueError, io.UnsupportedOperation):
58
+ _RAW_FD = sys.__stdout__
59
+ _CAPTURE_READ_FD = None
60
+
61
+ _OUT_LOCK = threading.Lock()
62
+
63
+
64
+ def _json_default(value: Any) -> Any:
65
+ try:
66
+ return repr(value)
67
+ except Exception:
68
+ return f"<unrepr {type(value).__name__}>"
69
+
70
+
71
+ def _emit(frame: dict) -> None:
72
+ """Serialize one frame and write it as a single NDJSON line."""
73
+ line = json.dumps(frame, ensure_ascii=False, default=_json_default)
74
+ with _OUT_LOCK:
75
+ _RAW_FD.write(line)
76
+ _RAW_FD.write("\n")
77
+ _RAW_FD.flush()
78
+
79
+
80
+ class _StreamProxy(io.TextIOBase):
81
+ """Route python-side stdout/stderr into per-run log frames.
82
+
83
+ Coalesces writes per run id (one frame per complete line or per 8 KiB)
84
+ so a plain print() plus its newline costs one frame.
85
+ """
86
+
87
+ _MAX_BUFFER = 8192
88
+
89
+ def __init__(self, stream: str) -> None:
90
+ super().__init__()
91
+ self._stream = stream
92
+ self._lock = threading.Lock()
93
+ self._buffers: dict[str, str] = {}
94
+
95
+ def writable(self) -> bool:
96
+ return True
97
+
98
+ def isatty(self) -> bool:
99
+ return False
100
+
101
+ def write(self, data: Any) -> int: # type: ignore[override]
102
+ if not isinstance(data, str):
103
+ data = str(data)
104
+ if not data:
105
+ return 0
106
+ rid = _CURRENT_RID.get()
107
+ if rid is None:
108
+ return len(data)
109
+ emit_text = None
110
+ with self._lock:
111
+ buf = self._buffers.pop(rid, "") + data
112
+ if len(buf) >= self._MAX_BUFFER:
113
+ emit_text = buf
114
+ else:
115
+ nl = buf.rfind("\n")
116
+ if nl >= 0:
117
+ emit_text = buf[: nl + 1]
118
+ rest = buf[nl + 1 :]
119
+ if rest:
120
+ self._buffers[rid] = rest
121
+ else:
122
+ self._buffers[rid] = buf
123
+ if emit_text:
124
+ _emit({"type": "log", "id": rid, "text": emit_text, "stream": self._stream})
125
+ return len(data)
126
+
127
+ def flush(self) -> None:
128
+ rid = _CURRENT_RID.get()
129
+ if rid is not None:
130
+ self.flush_rid(rid)
131
+
132
+ def flush_rid(self, rid: str) -> None:
133
+ with self._lock:
134
+ buf = self._buffers.pop(rid, None)
135
+ if buf:
136
+ _emit({"type": "log", "id": rid, "text": buf, "stream": self._stream})
137
+
138
+
139
+ def _flush_stream_proxies(rid: str) -> None:
140
+ for stream in (sys.stdout, sys.stderr):
141
+ if isinstance(stream, _StreamProxy):
142
+ stream.flush_rid(rid)
143
+
144
+
145
+ _RAW_STDERR = sys.__stderr__
146
+
147
+
148
+ def _start_capture_drain() -> None:
149
+ """Start the fd-1 drain thread (child-process output forwarder)."""
150
+ if _CAPTURE_READ_FD is None:
151
+ return
152
+ thread = threading.Thread(target=_drain_captured_stdout, name="dsh-fd1-capture", daemon=True)
153
+ thread.start()
154
+
155
+
156
+ def _drain_captured_stdout() -> None:
157
+ """Forward bytes written to the captured fd 1 (child processes) as log frames."""
158
+ if _CAPTURE_READ_FD is None:
159
+ return
160
+ codecs = __import__("codecs")
161
+ decoder = codecs.getincrementaldecoder("utf-8")("replace")
162
+ while True:
163
+ try:
164
+ chunk = os.read(_CAPTURE_READ_FD, 65536)
165
+ except OSError:
166
+ return
167
+ if not chunk:
168
+ return
169
+ text = decoder.decode(chunk)
170
+ if text:
171
+ rid = _STATE.capture_rid
172
+ if rid is not None:
173
+ _emit({"type": "log", "id": rid, "text": text, "stream": "stdout"})
174
+ else:
175
+ _RAW_STDERR.write(text)
176
+ _RAW_STDERR.flush()
177
+
178
+
179
+ # ---------------------------------------------------------------------------
180
+ # Persistent runner state
181
+ # ---------------------------------------------------------------------------
182
+
183
+
184
+ class _RunnerState:
185
+ def __init__(self) -> None:
186
+ self.user_ns: dict[str, Any] = {
187
+ "__name__": "__main__",
188
+ "__doc__": None,
189
+ "__builtins__": builtins,
190
+ }
191
+ self.loop: asyncio.AbstractEventLoop | None = None
192
+ self.execution_count: int = 0
193
+ self.cancel_requested: bool = False
194
+ self.capture_rid: str | None = None
195
+ # (run_id, seq) -> Future awaiting the host's reply to a binding call.
196
+ self.pending_calls: dict[tuple[str, int], asyncio.Future] = {}
197
+
198
+
199
+ _CURRENT_RID: contextvars.ContextVar[str | None] = contextvars.ContextVar("dsh_rid", default=None)
200
+ _STATE = _RunnerState()
201
+
202
+ _call_seq = 0
203
+
204
+
205
+ def _next_call_seq() -> int:
206
+ global _call_seq
207
+ _call_seq += 1
208
+ return _call_seq
209
+
210
+
211
+ # ---------------------------------------------------------------------------
212
+ # Tool binding proxy
213
+ # ---------------------------------------------------------------------------
214
+
215
+
216
+ def _make_tool_proxy_run(run_id: str, gname: str, names: list[str], member_prop: str, error_cls: Any) -> Any:
217
+ """Build the program-visible proxy for one binding global of one run.
218
+
219
+ Only the declared member names are exposed; each is an async callable that
220
+ emits a call frame and awaits the host's reply. The injected error class
221
+ name and member-name property mirror the TypeScript worker's ToolCallError /
222
+ toolName contract (the shipped Code Mode consumer's descriptor).
223
+ """
224
+
225
+ class _BindingProxy:
226
+ def __init__(self) -> None:
227
+ for name in names:
228
+ setattr(self, name, _make_binding_callable(run_id, gname, name, member_prop, error_cls))
229
+
230
+ def __getattr__(self, name: str) -> Any:
231
+ raise AttributeError(f"binding {gname}.{name} is not declared for this run")
232
+
233
+ return _BindingProxy()
234
+
235
+
236
+ class _ToolCallError(Exception):
237
+ """Base of every program-visible binding failure raised in this run."""
238
+
239
+
240
+ def _make_binding_callable(run_id: str, global_name: str, name: str, member_prop: str, error_cls: Any):
241
+ async def _call(args: Any = None) -> Any:
242
+ seq = _next_call_seq()
243
+ future = asyncio.get_running_loop().create_future()
244
+ _STATE.pending_calls[(run_id, seq)] = (future, error_cls, member_prop)
245
+ _emit({
246
+ "type": "call",
247
+ "id": run_id,
248
+ "seq": seq,
249
+ "global": global_name,
250
+ "name": name,
251
+ "args": args,
252
+ })
253
+ try:
254
+ return await future
255
+ finally:
256
+ _STATE.pending_calls.pop((run_id, seq), None)
257
+
258
+ return _call
259
+
260
+
261
+ def _deliver_reply(reply: dict) -> None:
262
+ rid = reply.get("id")
263
+ seq = reply.get("seq")
264
+ if not isinstance(rid, str) or not isinstance(seq, int):
265
+ return
266
+ entry = _STATE.pending_calls.get((rid, seq))
267
+ if entry is None:
268
+ return
269
+ future, error_cls, member_prop = entry
270
+ if future.done():
271
+ return
272
+ if reply.get("ok") is True:
273
+ future.set_result(reply.get("value"))
274
+ return
275
+ message = reply.get("message")
276
+ message_text = str(message if isinstance(message, str) else "binding call failed")
277
+ err = error_cls(message_text)
278
+ err.message = message_text
279
+ tool_name = reply.get("name")
280
+ if isinstance(tool_name, str):
281
+ setattr(err, member_prop, tool_name)
282
+ future.set_exception(err)
283
+
284
+
285
+ _MISSING = object()
286
+
287
+
288
+ def _materialize_namespaces(run_id: str, ns: dict, namespaces: list[dict]) -> list[tuple[str, Any]]:
289
+ """Inject one run's binding globals + error classes; return restore ops.
290
+
291
+ Each op restores ns[name] to its pre-run value (or deletes it when the
292
+ name was not previously present). The runner calls them in the run's
293
+ finally, so a stale proxy can never leak into the next cell.
294
+ """
295
+ restores: list[tuple[str, Any]] = []
296
+ for descriptor in namespaces:
297
+ gname = descriptor.get("global")
298
+ if not isinstance(gname, str) or not gname:
299
+ continue
300
+ names = descriptor.get("names")
301
+ if not isinstance(names, list):
302
+ names = []
303
+ member_prop = "toolName"
304
+ error_cls = _ToolCallError
305
+ ec = descriptor.get("errorClass")
306
+ if isinstance(ec, dict):
307
+ ecname = ec.get("name")
308
+ if isinstance(ecname, str) and ecname:
309
+ error_cls = type(ecname, (_ToolCallError,), {})
310
+ member_prop = ec.get("memberNameProperty")
311
+ if not isinstance(member_prop, str) or not member_prop:
312
+ member_prop = "toolName"
313
+ restores.append((ecname, ns.get(ecname, _MISSING)))
314
+ ns[ecname] = error_cls
315
+ restores.append((gname, ns.get(gname, _MISSING)))
316
+ ns[gname] = _make_tool_proxy_run(run_id, gname, [n for n in names if isinstance(n, str)], member_prop, error_cls)
317
+ return restores
318
+
319
+
320
+ # ---------------------------------------------------------------------------
321
+ # Cell execution: persistent namespace + loop, top-level await, last expression
322
+ # ---------------------------------------------------------------------------
323
+
324
+ _TLA_FLAG = getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x2000)
325
+
326
+
327
+ def _compile_source(source: str) -> tuple[Any, Any | None, bool]:
328
+ """Returns (body_code, expr_code, ok). A parse OR compile error yields (None, None, False)."""
329
+ try:
330
+ module = ast.parse(source, "<cell>", "exec")
331
+ except SyntaxError:
332
+ return None, None, False
333
+ if not module.body:
334
+ return None, None, True
335
+ try:
336
+ last = module.body[-1]
337
+ if isinstance(last, ast.Expr):
338
+ body_module = ast.Module(body=module.body[:-1], type_ignores=[])
339
+ expr_module = ast.Expression(body=last.value)
340
+ ast.copy_location(expr_module, last)
341
+ body_code = compile(body_module, "<cell>", "exec", flags=_TLA_FLAG)
342
+ expr_code = compile(expr_module, "<cell>", "eval", flags=_TLA_FLAG)
343
+ return body_code, expr_code, True
344
+ return compile(module, "<cell>", "exec", flags=_TLA_FLAG), None, True
345
+ except SyntaxError:
346
+ # ast.parse alone accepts some AST-valid modules that compile
347
+ # rejects (a top-level 'return', 'break'/'continue' outside a
348
+ # loop, 'yield' outside a function, ...). Route them to the normal
349
+ # per-cell error path so the run settles with "invalid Python
350
+ # source" instead of leaving the kernel waiting for a done frame
351
+ # that never arrives.
352
+ return None, None, False
353
+
354
+
355
+ def _lossless_json(value: Any):
356
+ """Round-trip a completion value through strict JSON.
357
+
358
+ Returns (True, decoded) when lossless, (False, None) when the
359
+ value cannot be represented as lossless JSON. None IS valid JSON
360
+ (null), so the two outcomes must be distinguishable via the ok flag.
361
+ """
362
+ try:
363
+ encoded = json.dumps(value, ensure_ascii=False)
364
+ except (TypeError, ValueError, OverflowError, RecursionError):
365
+ return False, None
366
+ try:
367
+ decoded = json.loads(encoded)
368
+ except (ValueError, RecursionError):
369
+ return False, None
370
+ return True, decoded
371
+
372
+
373
+ def _finish_run(
374
+ run_id: str,
375
+ *,
376
+ status: str,
377
+ value: Any = None,
378
+ has_value: bool = False,
379
+ cancelled: bool = False,
380
+ invalid_output: bool = False,
381
+ message: str = "",
382
+ ) -> None:
383
+ _STATE.execution_count += 1
384
+ frame: dict = {
385
+ "type": "done",
386
+ "id": run_id,
387
+ "status": status,
388
+ "executionCount": _STATE.execution_count,
389
+ "cancelled": cancelled,
390
+ }
391
+ if status == "ok" and has_value and not invalid_output:
392
+ frame["value"] = value
393
+ if invalid_output:
394
+ frame["invalidOutput"] = True
395
+ if message:
396
+ frame["message"] = message
397
+ _emit(frame)
398
+
399
+
400
+ async def _await_code(code_obj: Any, ns: dict) -> Any:
401
+ """Evaluate a compiled code object in the live loop (awaiting coroutines).
402
+
403
+ A cell with top-level await compiles to a coroutine; we await it so
404
+ sibling requests and tool calls interleave. Statement/expression code runs
405
+ synchronously on the loop thread.
406
+ """
407
+ coroutine_flag = getattr(__import__("inspect"), "CO_COROUTINE", 0)
408
+ if code_obj.co_flags & coroutine_flag:
409
+ return await eval(code_obj, ns)
410
+ return eval(code_obj, ns)
411
+
412
+
413
+ async def _run_cell(code: str, run_id: str, namespaces: list[dict]) -> None:
414
+ sys.stdout = _StreamProxy("stdout")
415
+ sys.stderr = _StreamProxy("stderr") # noqa: F841 - kept for symmetry
416
+ ns = _STATE.user_ns
417
+
418
+ # Materialize this run's bindings (fresh proxies + error classes each run).
419
+ restores = _materialize_namespaces(run_id, ns, namespaces)
420
+ loop = asyncio.get_running_loop()
421
+ del loop # the loop is persistent state, not per-run
422
+ _STATE.capture_rid = run_id
423
+ token = _CURRENT_RID.set(run_id)
424
+ try:
425
+ body_code, expr_code, ok = _compile_source(code)
426
+ if not ok:
427
+ _finish_run(run_id, status="error", message="invalid Python source")
428
+ return
429
+ try:
430
+ if body_code is not None:
431
+ await _await_code(body_code, ns)
432
+ value = None
433
+ if expr_code is not None:
434
+ value = await _await_code(expr_code, ns)
435
+ except KeyboardInterrupt:
436
+ _flush_stream_proxies(run_id)
437
+ _STATE.cancel_requested = False
438
+ _finish_run(run_id, status="error", cancelled=True)
439
+ return
440
+ except asyncio.CancelledError:
441
+ _flush_stream_proxies(run_id)
442
+ _STATE.cancel_requested = False
443
+ _finish_run(run_id, status="error", cancelled=True)
444
+ return
445
+ except BaseException as exc: # noqa: BLE001 - model code may raise anything
446
+ _flush_stream_proxies(run_id)
447
+ tb = traceback.format_exception(type(exc), exc, exc.__traceback__)
448
+ _emit({
449
+ "type": "error",
450
+ "id": run_id,
451
+ "ename": type(exc).__name__,
452
+ "evalue": str(exc),
453
+ "traceback": tb,
454
+ })
455
+ _finish_run(run_id, status="error", message=str(exc))
456
+ return
457
+ _flush_stream_proxies(run_id)
458
+ if _STATE.cancel_requested:
459
+ _STATE.cancel_requested = False
460
+ _finish_run(run_id, status="error", cancelled=True)
461
+ return
462
+ if expr_code is not None:
463
+ serialized_ok, serialized = _lossless_json(value)
464
+ if not serialized_ok:
465
+ _finish_run(run_id, status="error", invalid_output=True)
466
+ return
467
+ _finish_run(run_id, status="ok", value=serialized, has_value=True)
468
+ else:
469
+ _finish_run(run_id, status="ok")
470
+ finally:
471
+ _CURRENT_RID.reset(token)
472
+ _STATE.capture_rid = None
473
+ for name, previous in restores:
474
+ if previous is _MISSING:
475
+ ns.pop(name, None)
476
+ else:
477
+ ns[name] = previous
478
+
479
+
480
+ def _install_idle_sigint() -> None:
481
+ """SIGINT (host interrupt/escalation) interrupts the active run.
482
+
483
+ The handler raises KeyboardInterrupt synchronously: for sync exec code
484
+ (a busy loop, a time.sleep) that interrupts the blocking call between
485
+ bytecodes, and for await-ing coroutines it propagates out of the await
486
+ point. Either way _run_cell catches it and settles the run cancelled.
487
+ When SIGINT lands while no cell is active the exception unwinds the main
488
+ loop and the runner exits — a race the host covers by escalating to
489
+ termination, so accepting it keeps interrupt latency at zero.
490
+ """
491
+
492
+ def handler_signum(_signum: int, _frame: Any) -> None:
493
+ _STATE.cancel_requested = True
494
+ raise KeyboardInterrupt
495
+
496
+ try:
497
+ signal.signal(signal.SIGINT, handler_signum)
498
+ except (ValueError, OSError): # not the main thread (tests)
499
+ pass
500
+
501
+
502
+ # ---------------------------------------------------------------------------
503
+ # Request loop
504
+ # ---------------------------------------------------------------------------
505
+
506
+
507
+ def _read_stdin(loop: asyncio.AbstractEventLoop, queue: asyncio.Queue, stdin: Any) -> None:
508
+ for raw in stdin:
509
+ line = raw.rstrip("\n")
510
+ if not line.strip():
511
+ continue
512
+ try:
513
+ req = json.loads(line)
514
+ except json.JSONDecodeError:
515
+ continue
516
+ loop.call_soon_threadsafe(queue.put_nowait, req)
517
+ loop.call_soon_threadsafe(queue.put_nowait, {"type": "exit"})
518
+
519
+
520
+ async def _handle_request_async(req: dict) -> None:
521
+ rid = req.get("id")
522
+ if not isinstance(rid, str) or not rid:
523
+ return
524
+ code = req.get("code")
525
+ if not isinstance(code, str):
526
+ _emit({"type": "error", "id": rid, "ename": "ProtocolError", "evalue": "code must be a string", "traceback": []})
527
+ _finish_run(rid, status="error", message="code must be a string")
528
+ return
529
+ cwd = req.get("cwd")
530
+ if isinstance(cwd, str):
531
+ try:
532
+ os.chdir(cwd)
533
+ if cwd not in sys.path:
534
+ sys.path.insert(0, cwd)
535
+ except OSError:
536
+ pass
537
+ env = req.get("env")
538
+ if isinstance(env, dict):
539
+ for key, value in env.items():
540
+ if isinstance(key, str):
541
+ if isinstance(value, str):
542
+ os.environ[key] = value
543
+ else:
544
+ os.environ.pop(key, None)
545
+ namespaces = req.get("namespaces")
546
+ if not isinstance(namespaces, list):
547
+ namespaces = []
548
+ _STATE.cancel_requested = False
549
+ try:
550
+ await _run_cell(code, rid, namespaces)
551
+ except asyncio.CancelledError:
552
+ _STATE.cancel_requested = False
553
+ _finish_run(rid, status="error", cancelled=True)
554
+
555
+
556
+ async def _main_async() -> None:
557
+ sys.stdout = _StreamProxy("stdout")
558
+ sys.stderr = _StreamProxy("stderr")
559
+ _install_idle_sigint()
560
+ _start_capture_drain()
561
+
562
+ stdin = sys.__stdin__
563
+ if stdin is None:
564
+ return
565
+ loop = asyncio.get_running_loop()
566
+ _STATE.loop = loop
567
+ queue: asyncio.Queue = asyncio.Queue()
568
+ reader = threading.Thread(target=_read_stdin, args=(loop, queue, stdin), name="dsh-stdin-reader", daemon=True)
569
+ reader.start()
570
+
571
+ _emit({"type": "ready", "pid": os.getpid()})
572
+
573
+ tasks: set[asyncio.Task] = set()
574
+
575
+ def _task_done(task: asyncio.Task) -> None:
576
+ tasks.discard(task)
577
+ try:
578
+ exc = task.exception()
579
+ except asyncio.CancelledError:
580
+ return
581
+ if exc is not None and not isinstance(exc, (KeyboardInterrupt, asyncio.CancelledError)):
582
+ _emit({"type": "error", "id": "", "ename": "ProtocolError", "evalue": str(exc), "traceback": []})
583
+
584
+ while True:
585
+ req = await queue.get()
586
+ rtype = req.get("type")
587
+ if rtype == "exit":
588
+ break
589
+ if rtype == "reply":
590
+ _deliver_reply(req)
591
+ continue
592
+ task = asyncio.create_task(_handle_request_async(req))
593
+ tasks.add(task)
594
+ task.add_done_callback(_task_done)
595
+
596
+ # Every request queued before an exit request is already a task; run them
597
+ # all to completion before shutting down (cancelling here would kill execs
598
+ # that never got a turn -- a race when stdin drains faster than the loop
599
+ # runs).
600
+ if tasks:
601
+ await asyncio.gather(*tasks, return_exceptions=True)
602
+ for task in tasks:
603
+ task.cancel()
604
+ if tasks:
605
+ await asyncio.gather(*tasks, return_exceptions=True)
606
+
607
+
608
+ def main() -> None:
609
+ asyncio.run(_main_async())
610
+
611
+
612
+ if __name__ == "__main__":
613
+ main()
614
+ `;
615
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/python/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6kBtC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@hy-sde-org/dsh-code-runtime-kernels",
3
+ "version": "0.1.0-rc.8",
4
+ "description": "The run_kernel_code tool for DeepSeek Harness: persistent Python and JavaScript kernels (a long-lived python3/node subprocess per session, sharing a core host driver) — a standalone plugin with no upstream harness changes required.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./invariant": {
14
+ "types": "./dist/core/invariant.d.ts",
15
+ "default": "./dist/core/invariant.js"
16
+ },
17
+ "./cordis.patch.yml": "./cordis.patch.yml",
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "cordis.patch.yml",
23
+ "README.md",
24
+ "README.zh.md",
25
+ "LICENSE"
26
+ ],
27
+ "engines": {
28
+ "node": "^22.19.0 || >=24.0.0"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/hy-sde/dsh-code-runtime-kernels.git"
36
+ },
37
+ "license": "MIT",
38
+ "dsh": {
39
+ "bundle": {
40
+ "patch": "./cordis.patch.yml"
41
+ }
42
+ },
43
+ "dependencies": {
44
+ "@deepseek-ai/schemastery": "3.18.1"
45
+ },
46
+ "peerDependencies": {
47
+ "@deepseek-ai/cordis": "^4.0.1",
48
+ "@deepseek-ai/dsh-code-runtime": "^0.1.0-rc.8",
49
+ "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
50
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.8",
51
+ "@deepseek-ai/dsh-timeout": "^0.1.0-rc.8",
52
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.8"
53
+ },
54
+ "devDependencies": {
55
+ "@deepseek-ai/cordis": "^4.0.1",
56
+ "@deepseek-ai/dsh-code-runtime": "^0.1.0-rc.8",
57
+ "@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
58
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.8",
59
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.8",
60
+ "@deepseek-ai/dsh-timeout": "^0.1.0-rc.8",
61
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
62
+ "@deepseek-ai/schemastery": "3.18.1",
63
+ "@types/node": "^24.13.3",
64
+ "typescript": "^5.9.3",
65
+ "vitest": "^3.2.7",
66
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.8"
67
+ },
68
+ "keywords": [
69
+ "deepseek",
70
+ "agent",
71
+ "harness",
72
+ "dsh-plugin",
73
+ "code-runtime",
74
+ "persistent",
75
+ "kernel",
76
+ "python",
77
+ "javascript"
78
+ ],
79
+ "scripts": {
80
+ "clean": "rm -rf dist",
81
+ "build": "tsc -p tsconfig.build.json",
82
+ "check": "tsc -p tsconfig.json --noEmit",
83
+ "test": "vitest run"
84
+ }
85
+ }