@oh-my-pi/pi-coding-agent 14.9.5 → 14.9.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/package.json +7 -7
  3. package/scripts/generate-template.ts +4 -3
  4. package/src/cli/setup-cli.ts +14 -161
  5. package/src/cli/stats-cli.ts +56 -2
  6. package/src/cli.ts +0 -1
  7. package/src/config/settings-schema.ts +0 -10
  8. package/src/eval/eval.lark +30 -10
  9. package/src/eval/js/context-manager.ts +334 -564
  10. package/src/eval/js/shared/helpers.ts +237 -0
  11. package/src/eval/js/shared/indirect-eval.ts +30 -0
  12. package/src/eval/js/shared/rewrite-imports.ts +211 -0
  13. package/src/eval/js/shared/runtime.ts +168 -0
  14. package/src/eval/js/shared/types.ts +18 -0
  15. package/src/eval/js/tool-bridge.ts +2 -4
  16. package/src/eval/js/worker-core.ts +146 -0
  17. package/src/eval/js/worker-entry.ts +24 -0
  18. package/src/eval/js/worker-protocol.ts +41 -0
  19. package/src/eval/parse.ts +218 -49
  20. package/src/eval/py/display.ts +71 -0
  21. package/src/eval/py/executor.ts +74 -89
  22. package/src/eval/py/index.ts +1 -2
  23. package/src/eval/py/kernel.ts +472 -900
  24. package/src/eval/py/prelude.py +95 -7
  25. package/src/eval/py/runner.py +879 -0
  26. package/src/eval/py/runtime.ts +3 -16
  27. package/src/eval/py/tool-bridge.ts +137 -0
  28. package/src/export/html/index.ts +5 -2
  29. package/src/export/html/template.generated.ts +1 -1
  30. package/src/export/html/template.js +93 -5
  31. package/src/export/html/template.macro.ts +4 -3
  32. package/src/internal-urls/docs-index.generated.ts +3 -3
  33. package/src/modes/components/read-tool-group.ts +9 -0
  34. package/src/modes/controllers/command-controller.ts +0 -23
  35. package/src/prompts/tools/eval.md +14 -27
  36. package/src/prompts/tools/read.md +1 -0
  37. package/src/session/agent-session.ts +0 -1
  38. package/src/session/history-storage.ts +77 -19
  39. package/src/tools/browser/tab-protocol.ts +4 -0
  40. package/src/tools/browser/tab-supervisor.ts +86 -5
  41. package/src/tools/browser/tab-worker.ts +104 -58
  42. package/src/tools/conflict-detect.ts +661 -0
  43. package/src/tools/eval.ts +1 -1
  44. package/src/tools/index.ts +6 -0
  45. package/src/tools/path-utils.ts +1 -1
  46. package/src/tools/read.ts +130 -0
  47. package/src/tools/write.ts +204 -0
  48. package/src/web/search/index.ts +6 -4
  49. package/src/cli/jupyter-cli.ts +0 -106
  50. package/src/commands/jupyter.ts +0 -32
  51. package/src/eval/py/cancellation.ts +0 -28
  52. package/src/eval/py/gateway-coordinator.ts +0 -424
  53. /package/src/eval/js/{prelude.ts → shared/prelude.ts} +0 -0
  54. /package/src/eval/js/{prelude.txt → shared/prelude.txt} +0 -0
@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
- # OMP IPython prelude helpers
2
+ # OMP prelude helpers (loaded once into the runner namespace)
3
3
  if "__omp_prelude_loaded__" not in globals():
4
4
  __omp_prelude_loaded__ = True
5
5
  from pathlib import Path
6
6
  import os, json
7
- from IPython.display import display as _ipy_display, JSON
7
+
8
+ # __omp_display is injected by runner.py before the prelude executes; it
9
+ # mirrors IPython's display() semantics with the same MIME bundle output.
10
+ _omp_display = __omp_display # type: ignore[name-defined]
8
11
 
9
12
  _PRESENTABLE_REPRS = (
10
13
  "_repr_mimebundle_",
@@ -18,21 +21,22 @@ if "__omp_prelude_loaded__" not in globals():
18
21
  )
19
22
 
20
23
  def display(value):
21
- """Render a value. Wraps plain dict/list values as interactive JSON."""
24
+ """Render a value. Falls back to a JSON+text/plain bundle for plain dict/list/tuple."""
22
25
  if any(hasattr(value, attr) for attr in _PRESENTABLE_REPRS):
23
- _ipy_display(value)
26
+ _omp_display(value)
24
27
  return
25
28
  if isinstance(value, (dict, list, tuple)):
26
29
  try:
27
- _ipy_display(JSON(value))
30
+ bundle = {"application/json": value, "text/plain": repr(value)}
31
+ _omp_display(bundle, raw=True)
28
32
  return
29
33
  except Exception:
30
34
  pass
31
- _ipy_display(value)
35
+ _omp_display(value)
32
36
 
33
37
  def _emit_status(op: str, **data):
34
38
  """Emit structured status event for TUI rendering."""
35
- _ipy_display({"application/x-omp-status": {"op": op, **data}}, raw=True)
39
+ _omp_display({"application/x-omp-status": {"op": op, **data}}, raw=True)
36
40
 
37
41
 
38
42
  def env(key: str | None = None, value: str | None = None):
@@ -372,3 +376,87 @@ if "__omp_prelude_loaded__" not in globals():
372
376
 
373
377
  return current
374
378
 
379
+
380
+ class _ToolCallable:
381
+ """Invokes one host-side tool via the loopback HTTP bridge."""
382
+
383
+ __slots__ = ("_proxy", "_name")
384
+
385
+ def __init__(self, proxy: "_ToolProxy", name: str):
386
+ self._proxy = proxy
387
+ self._name = name
388
+
389
+ def __repr__(self) -> str:
390
+ return f"<tool.{self._name}>"
391
+
392
+ def __call__(self, args=None, /, **kwargs):
393
+ import urllib.request, urllib.error
394
+ if args is None:
395
+ merged: dict = {}
396
+ elif isinstance(args, dict):
397
+ merged = dict(args)
398
+ else:
399
+ raise TypeError(
400
+ f"tool.{self._name}(...) expects a dict of arguments (got {type(args).__name__})"
401
+ )
402
+ merged.update(kwargs)
403
+ if "_i" not in merged:
404
+ merged["_i"] = "py prelude"
405
+ payload = json.dumps(
406
+ {"session": self._proxy._session, "name": self._name, "args": merged}
407
+ ).encode("utf-8")
408
+ req = urllib.request.Request(
409
+ f"{self._proxy._base}/v1/tool",
410
+ data=payload,
411
+ method="POST",
412
+ headers={
413
+ "Content-Type": "application/json",
414
+ "Authorization": f"Bearer {self._proxy._token}",
415
+ },
416
+ )
417
+ try:
418
+ with urllib.request.urlopen(req) as resp:
419
+ body = resp.read()
420
+ except urllib.error.HTTPError as exc:
421
+ body = exc.read()
422
+ try:
423
+ data = json.loads(body)
424
+ except json.JSONDecodeError:
425
+ raise RuntimeError(
426
+ f"tool.{self._name}: bridge returned non-JSON response: {body[:200]!r}"
427
+ ) from None
428
+ if not isinstance(data, dict) or not data.get("ok"):
429
+ msg = (data or {}).get("error") if isinstance(data, dict) else None
430
+ raise RuntimeError(msg or f"tool.{self._name} failed")
431
+ return data.get("value")
432
+
433
+ class _ToolProxy:
434
+ """`tool.<name>(args)` proxy mirroring the JS runtime bridge."""
435
+
436
+ __slots__ = ("_base", "_token", "_session")
437
+
438
+ def __init__(self, base: str, token: str, session: str):
439
+ self._base = base.rstrip("/")
440
+ self._token = token
441
+ self._session = session
442
+
443
+ def __getattr__(self, name: str) -> _ToolCallable:
444
+ if name.startswith("_"):
445
+ raise AttributeError(name)
446
+ return _ToolCallable(self, name)
447
+
448
+ def __getitem__(self, name: str) -> _ToolCallable:
449
+ return _ToolCallable(self, name)
450
+
451
+ def __repr__(self) -> str:
452
+ return f"<tool proxy session={self._session}>"
453
+
454
+ if all(
455
+ _k in os.environ
456
+ for _k in ("PI_TOOL_BRIDGE_URL", "PI_TOOL_BRIDGE_TOKEN", "PI_TOOL_BRIDGE_SESSION")
457
+ ):
458
+ tool = _ToolProxy(
459
+ os.environ["PI_TOOL_BRIDGE_URL"],
460
+ os.environ["PI_TOOL_BRIDGE_TOKEN"],
461
+ os.environ["PI_TOOL_BRIDGE_SESSION"],
462
+ )