@oh-my-pi/pi-coding-agent 14.9.3 → 14.9.7
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.
- package/CHANGELOG.md +96 -0
- package/package.json +7 -7
- package/src/async/job-manager.ts +66 -9
- package/src/capability/rule.ts +20 -0
- package/src/cli/setup-cli.ts +14 -161
- package/src/cli/stats-cli.ts +56 -2
- package/src/cli.ts +0 -1
- package/src/config/model-registry.ts +13 -0
- package/src/config/model-resolver.ts +8 -2
- package/src/config/settings-schema.ts +1 -11
- package/src/edit/index.ts +8 -0
- package/src/edit/renderer.ts +6 -1
- package/src/edit/streaming.ts +53 -2
- package/src/eval/eval.lark +30 -10
- package/src/eval/js/context-manager.ts +334 -601
- package/src/eval/js/shared/helpers.ts +237 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/{prelude.txt → shared/prelude.txt} +0 -2
- package/src/eval/js/shared/rewrite-imports.ts +211 -0
- package/src/eval/js/shared/runtime.ts +168 -0
- package/src/eval/js/shared/types.ts +18 -0
- package/src/eval/js/tool-bridge.ts +2 -4
- package/src/eval/js/worker-core.ts +146 -0
- package/src/eval/js/worker-entry.ts +24 -0
- package/src/eval/js/worker-protocol.ts +41 -0
- package/src/eval/parse.ts +218 -49
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +97 -96
- package/src/eval/py/index.ts +2 -2
- package/src/eval/py/kernel.ts +472 -900
- package/src/eval/py/prelude.py +106 -87
- package/src/eval/py/runner.py +879 -0
- package/src/eval/py/runtime.ts +3 -16
- package/src/eval/py/tool-bridge.ts +137 -0
- package/src/export/html/template.css +12 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +113 -7
- package/src/extensibility/plugins/loader.ts +31 -6
- package/src/extensibility/skills.ts +20 -0
- package/src/internal-urls/agent-protocol.ts +63 -52
- package/src/internal-urls/artifact-protocol.ts +51 -51
- package/src/internal-urls/docs-index.generated.ts +35 -3
- package/src/internal-urls/index.ts +6 -19
- package/src/internal-urls/local-protocol.ts +49 -7
- package/src/internal-urls/mcp-protocol.ts +2 -8
- package/src/internal-urls/memory-protocol.ts +89 -59
- package/src/internal-urls/router.ts +38 -22
- package/src/internal-urls/rule-protocol.ts +2 -20
- package/src/internal-urls/skill-protocol.ts +4 -27
- package/src/main.ts +1 -1
- package/src/mcp/manager.ts +17 -0
- package/src/modes/components/session-observer-overlay.ts +2 -2
- package/src/modes/components/tool-execution.ts +6 -0
- package/src/modes/components/tree-selector.ts +4 -0
- package/src/modes/controllers/command-controller.ts +0 -23
- package/src/modes/controllers/event-controller.ts +23 -2
- package/src/modes/controllers/mcp-command-controller.ts +7 -10
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/theme/theme.ts +27 -27
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +14 -9
- package/src/prompts/commands/orchestrate.md +1 -0
- package/src/prompts/system/project-prompt.md +10 -2
- package/src/prompts/system/subagent-system-prompt.md +8 -8
- package/src/prompts/system/system-prompt.md +13 -7
- package/src/prompts/tools/ask.md +0 -1
- package/src/prompts/tools/bash.md +0 -10
- package/src/prompts/tools/eval.md +15 -30
- package/src/prompts/tools/github.md +6 -5
- package/src/prompts/tools/hashline.md +1 -0
- package/src/prompts/tools/job.md +14 -6
- package/src/prompts/tools/task.md +20 -3
- package/src/registry/agent-registry.ts +2 -1
- package/src/sdk.ts +87 -89
- package/src/session/agent-session.ts +58 -21
- package/src/session/artifacts.ts +7 -4
- package/src/session/history-storage.ts +77 -19
- package/src/session/session-manager.ts +30 -1
- package/src/ssh/connection-manager.ts +32 -16
- package/src/ssh/sshfs-mount.ts +10 -7
- package/src/system-prompt.ts +0 -5
- package/src/task/executor.ts +14 -2
- package/src/task/index.ts +19 -5
- package/src/tool-discovery/tool-index.ts +21 -8
- package/src/tools/ast-edit.ts +3 -2
- package/src/tools/ast-grep.ts +3 -2
- package/src/tools/bash.ts +15 -9
- package/src/tools/browser/tab-protocol.ts +4 -0
- package/src/tools/browser/tab-supervisor.ts +98 -7
- package/src/tools/browser/tab-worker.ts +104 -58
- package/src/tools/eval.ts +49 -11
- package/src/tools/fetch.ts +1 -1
- package/src/tools/gh.ts +140 -4
- package/src/tools/index.ts +12 -11
- package/src/tools/job.ts +48 -12
- package/src/tools/read.ts +5 -4
- package/src/tools/search.ts +3 -2
- package/src/tools/todo-write.ts +1 -1
- package/src/web/scrapers/mastodon.ts +1 -1
- package/src/web/scrapers/repology.ts +7 -7
- package/src/web/search/index.ts +6 -4
- package/src/cli/jupyter-cli.ts +0 -106
- package/src/commands/jupyter.ts +0 -32
- package/src/eval/py/cancellation.ts +0 -28
- package/src/eval/py/gateway-coordinator.ts +0 -424
- package/src/internal-urls/jobs-protocol.ts +0 -120
- package/src/prompts/system/now-prompt.md +0 -7
- /package/src/eval/js/{prelude.ts → shared/prelude.ts} +0 -0
package/src/eval/py/prelude.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
# OMP
|
|
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
|
-
import os, json
|
|
7
|
-
|
|
6
|
+
import os, 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.
|
|
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
|
-
|
|
26
|
+
_omp_display(value)
|
|
24
27
|
return
|
|
25
28
|
if isinstance(value, (dict, list, tuple)):
|
|
26
29
|
try:
|
|
27
|
-
|
|
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
|
-
|
|
35
|
+
_omp_display(value)
|
|
32
36
|
|
|
33
37
|
def _emit_status(op: str, **data):
|
|
34
38
|
"""Emit structured status event for TUI rendering."""
|
|
35
|
-
|
|
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):
|
|
@@ -79,79 +83,6 @@ if "__omp_prelude_loaded__" not in globals():
|
|
|
79
83
|
f.write(content)
|
|
80
84
|
_emit_status("append", path=str(p), chars=len(content))
|
|
81
85
|
return p
|
|
82
|
-
class ShellResult:
|
|
83
|
-
"""Result from shell command execution."""
|
|
84
|
-
__slots__ = ("args", "stdout", "stderr", "returncode")
|
|
85
|
-
def __init__(self, args: str, stdout: str, stderr: str, returncode: int):
|
|
86
|
-
self.args = args
|
|
87
|
-
self.stdout = stdout
|
|
88
|
-
self.stderr = stderr
|
|
89
|
-
self.returncode = returncode
|
|
90
|
-
|
|
91
|
-
@property
|
|
92
|
-
def code(self) -> int:
|
|
93
|
-
return self.returncode
|
|
94
|
-
|
|
95
|
-
@property
|
|
96
|
-
def exit_code(self) -> int:
|
|
97
|
-
return self.returncode
|
|
98
|
-
|
|
99
|
-
def check_returncode(self) -> None:
|
|
100
|
-
if self.returncode != 0:
|
|
101
|
-
raise subprocess.CalledProcessError(
|
|
102
|
-
self.returncode, self.args, output=self.stdout, stderr=self.stderr
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
def __repr__(self):
|
|
106
|
-
if self.returncode == 0:
|
|
107
|
-
return ""
|
|
108
|
-
return f"exit code {self.returncode}"
|
|
109
|
-
|
|
110
|
-
def __bool__(self):
|
|
111
|
-
return self.returncode == 0
|
|
112
|
-
|
|
113
|
-
def _make_shell_result(proc: subprocess.CompletedProcess[str], cmd: str) -> ShellResult:
|
|
114
|
-
"""Create ShellResult and emit status."""
|
|
115
|
-
output = proc.stdout + proc.stderr if proc.stderr else proc.stdout
|
|
116
|
-
_emit_status("sh", cmd=cmd[:80], code=proc.returncode, output=output[:500])
|
|
117
|
-
return ShellResult(cmd, proc.stdout, proc.stderr, proc.returncode)
|
|
118
|
-
|
|
119
|
-
import signal as _signal
|
|
120
|
-
|
|
121
|
-
def _run_with_interrupt(args: list[str], cwd: str | None, timeout: int | None, cmd: str) -> ShellResult:
|
|
122
|
-
"""Run subprocess with proper interrupt handling."""
|
|
123
|
-
proc = subprocess.Popen(
|
|
124
|
-
args,
|
|
125
|
-
cwd=cwd,
|
|
126
|
-
stdout=subprocess.PIPE,
|
|
127
|
-
stderr=subprocess.PIPE,
|
|
128
|
-
text=True,
|
|
129
|
-
start_new_session=True,
|
|
130
|
-
)
|
|
131
|
-
try:
|
|
132
|
-
stdout, stderr = proc.communicate(timeout=timeout)
|
|
133
|
-
except KeyboardInterrupt:
|
|
134
|
-
os.killpg(proc.pid, _signal.SIGINT)
|
|
135
|
-
try:
|
|
136
|
-
stdout, stderr = proc.communicate(timeout=2)
|
|
137
|
-
except subprocess.TimeoutExpired:
|
|
138
|
-
os.killpg(proc.pid, _signal.SIGKILL)
|
|
139
|
-
stdout, stderr = proc.communicate()
|
|
140
|
-
result = subprocess.CompletedProcess(args, -_signal.SIGINT, stdout, stderr)
|
|
141
|
-
return _make_shell_result(result, cmd)
|
|
142
|
-
except subprocess.TimeoutExpired:
|
|
143
|
-
os.killpg(proc.pid, _signal.SIGKILL)
|
|
144
|
-
stdout, stderr = proc.communicate()
|
|
145
|
-
result = subprocess.CompletedProcess(args, -_signal.SIGKILL, stdout, stderr)
|
|
146
|
-
return _make_shell_result(result, cmd)
|
|
147
|
-
result = subprocess.CompletedProcess(args, proc.returncode, stdout, stderr)
|
|
148
|
-
return _make_shell_result(result, cmd)
|
|
149
|
-
|
|
150
|
-
def run(cmd: str, *, cwd: str | Path | None = None, timeout: int | None = None) -> ShellResult:
|
|
151
|
-
"""Run a shell command. Returns ShellResult with stdout/stderr and returncode/exit_code fields."""
|
|
152
|
-
shell_path = shutil.which("bash") or shutil.which("sh") or "/bin/sh"
|
|
153
|
-
args = [shell_path, "-c", cmd]
|
|
154
|
-
return _run_with_interrupt(args, str(cwd) if cwd else None, timeout, cmd)
|
|
155
86
|
|
|
156
87
|
def sort(text: str, *, reverse: bool = False, unique: bool = False) -> str:
|
|
157
88
|
"""Sort lines of text."""
|
|
@@ -268,12 +199,16 @@ if "__omp_prelude_loaded__" not in globals():
|
|
|
268
199
|
output('explore_0', offset=10, limit=20) # Lines 10-29
|
|
269
200
|
output('explore_0', 'reviewer_1') # Read multiple outputs
|
|
270
201
|
"""
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
202
|
+
# Prefer PI_ARTIFACTS_DIR so subagents resolve through the parent's
|
|
203
|
+
# shared artifacts dir; fall back to deriving from PI_SESSION_FILE
|
|
204
|
+
# for legacy callers / top-level sessions where the two coincide.
|
|
205
|
+
artifacts_dir = os.environ.get("PI_ARTIFACTS_DIR")
|
|
206
|
+
if not artifacts_dir:
|
|
207
|
+
session_file = os.environ.get("PI_SESSION_FILE")
|
|
208
|
+
if not session_file:
|
|
209
|
+
_emit_status("output", error="No session file available")
|
|
210
|
+
raise RuntimeError("No session - output artifacts unavailable")
|
|
211
|
+
artifacts_dir = session_file.rsplit(".", 1)[0] # Strip .jsonl extension
|
|
277
212
|
if not Path(artifacts_dir).exists():
|
|
278
213
|
_emit_status("output", error="Artifacts directory not found", path=artifacts_dir)
|
|
279
214
|
raise RuntimeError(f"No artifacts directory found: {artifacts_dir}")
|
|
@@ -441,3 +376,87 @@ if "__omp_prelude_loaded__" not in globals():
|
|
|
441
376
|
|
|
442
377
|
return current
|
|
443
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
|
+
)
|