@oh-my-pi/pi-coding-agent 15.3.2 → 15.4.2
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 +110 -0
- package/dist/types/cli/file-processor.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +45 -3
- package/dist/types/config/settings.d.ts +1 -1
- package/dist/types/debug/raw-sse.d.ts +2 -0
- package/dist/types/edit/file-read-cache.d.ts +15 -4
- package/dist/types/edit/index.d.ts +3 -8
- package/dist/types/edit/renderer.d.ts +1 -2
- package/dist/types/eval/__tests__/shared-executors.test.d.ts +1 -0
- package/dist/types/eval/js/shared/local-module-loader.d.ts +16 -0
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +4 -0
- package/dist/types/eval/js/shared/runtime.d.ts +14 -8
- package/dist/types/eval/py/executor.d.ts +1 -2
- package/dist/types/eval/py/kernel.d.ts +6 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -5
- package/dist/types/eval/session-id.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +1 -3
- package/dist/types/hashline/anchors.d.ts +15 -9
- package/dist/types/hashline/constants.d.ts +0 -2
- package/dist/types/hashline/diff.d.ts +1 -2
- package/dist/types/hashline/executor.d.ts +52 -0
- package/dist/types/hashline/hash.d.ts +44 -93
- package/dist/types/hashline/index.d.ts +2 -1
- package/dist/types/hashline/input.d.ts +2 -9
- package/dist/types/hashline/recovery.d.ts +3 -9
- package/dist/types/hashline/tokenizer.d.ts +91 -0
- package/dist/types/hashline/types.d.ts +5 -7
- package/dist/types/modes/components/extensions/types.d.ts +0 -4
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +11 -15
- package/dist/types/session/agent-storage.d.ts +11 -10
- package/dist/types/slash-commands/acp-builtins.d.ts +3 -3
- package/dist/types/slash-commands/types.d.ts +0 -5
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/tool-discovery/tool-index.d.ts +0 -50
- package/dist/types/tools/index.d.ts +2 -8
- package/dist/types/tools/match-line-format.d.ts +4 -4
- package/dist/types/tools/output-schema-validator.d.ts +64 -0
- package/dist/types/tools/review.d.ts +13 -0
- package/dist/types/tools/search-tool-bm25.d.ts +1 -1
- package/dist/types/tools/search.d.ts +4 -3
- package/dist/types/utils/edit-mode.d.ts +1 -1
- package/dist/types/web/kagi.d.ts +4 -2
- package/dist/types/web/parallel.d.ts +4 -3
- package/dist/types/web/scrapers/types.d.ts +2 -1
- package/dist/types/web/search/index.d.ts +12 -4
- package/dist/types/web/search/provider.d.ts +2 -1
- package/dist/types/web/search/providers/anthropic.d.ts +9 -4
- package/dist/types/web/search/providers/base.d.ts +34 -2
- package/dist/types/web/search/providers/brave.d.ts +8 -1
- package/dist/types/web/search/providers/codex.d.ts +13 -9
- package/dist/types/web/search/providers/exa.d.ts +10 -1
- package/dist/types/web/search/providers/gemini.d.ts +20 -23
- package/dist/types/web/search/providers/jina.d.ts +2 -1
- package/dist/types/web/search/providers/kagi.d.ts +4 -1
- package/dist/types/web/search/providers/kimi.d.ts +10 -1
- package/dist/types/web/search/providers/parallel.d.ts +3 -2
- package/dist/types/web/search/providers/perplexity.d.ts +5 -2
- package/dist/types/web/search/providers/searxng.d.ts +2 -1
- package/dist/types/web/search/providers/synthetic.d.ts +5 -8
- package/dist/types/web/search/providers/tavily.d.ts +11 -4
- package/dist/types/web/search/providers/utils.d.ts +8 -6
- package/dist/types/web/search/providers/zai.d.ts +12 -3
- package/package.json +7 -7
- package/src/cli/file-processor.ts +12 -2
- package/src/cli.ts +0 -8
- package/src/commands/commit.ts +8 -8
- package/src/config/prompt-templates.ts +6 -6
- package/src/config/settings-schema.ts +47 -3
- package/src/config/settings.ts +5 -5
- package/src/debug/raw-sse.ts +68 -3
- package/src/edit/file-read-cache.ts +68 -25
- package/src/edit/index.ts +6 -37
- package/src/edit/renderer.ts +9 -47
- package/src/edit/streaming.ts +43 -56
- package/src/eval/__tests__/shared-executors.test.ts +520 -0
- package/src/eval/js/context-manager.ts +64 -53
- package/src/eval/js/shared/local-module-loader.ts +265 -0
- package/src/eval/js/shared/prelude.txt +4 -0
- package/src/eval/js/shared/rewrite-imports.ts +85 -0
- package/src/eval/js/shared/runtime.ts +129 -86
- package/src/eval/js/worker-core.ts +23 -38
- package/src/eval/py/executor.ts +155 -84
- package/src/eval/py/kernel.ts +10 -1
- package/src/eval/py/prelude.py +22 -24
- package/src/eval/py/runner.py +203 -85
- package/src/eval/py/tool-bridge.ts +17 -10
- package/src/eval/session-id.ts +8 -0
- package/src/exec/bash-executor.ts +27 -16
- package/src/extensibility/extensions/runner.ts +0 -1
- package/src/extensibility/extensions/types.ts +1 -3
- package/src/hashline/anchors.ts +56 -65
- package/src/hashline/apply.ts +29 -31
- package/src/hashline/constants.ts +0 -3
- package/src/hashline/diff-preview.ts +4 -5
- package/src/hashline/diff.ts +30 -4
- package/src/hashline/execute.ts +91 -26
- package/src/hashline/executor.ts +239 -0
- package/src/hashline/grammar.lark +12 -10
- package/src/hashline/hash.ts +69 -114
- package/src/hashline/index.ts +2 -1
- package/src/hashline/input.ts +48 -41
- package/src/hashline/prefixes.ts +21 -11
- package/src/hashline/recovery.ts +63 -71
- package/src/hashline/stream.ts +2 -2
- package/src/hashline/tokenizer.ts +467 -0
- package/src/hashline/types.ts +6 -8
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/modes/components/extensions/types.ts +0 -5
- package/src/modes/components/session-observer-overlay.ts +11 -2
- package/src/modes/components/settings-selector.ts +10 -1
- package/src/modes/components/tree-selector.ts +10 -2
- package/src/modes/controllers/command-controller.ts +1 -3
- package/src/modes/controllers/extension-ui-controller.ts +10 -11
- package/src/modes/controllers/selector-controller.ts +5 -5
- package/src/modes/theme/theme.ts +4 -2
- package/src/modes/types.ts +4 -1
- package/src/modes/utils/ui-helpers.ts +4 -0
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/eval.md +1 -1
- package/src/prompts/tools/hashline.md +73 -94
- package/src/prompts/tools/read.md +4 -4
- package/src/prompts/tools/search.md +3 -3
- package/src/sdk.ts +33 -26
- package/src/session/agent-session.ts +59 -66
- package/src/session/agent-storage.ts +13 -14
- package/src/slash-commands/acp-builtins.ts +3 -3
- package/src/slash-commands/types.ts +0 -6
- package/src/task/executor.ts +26 -57
- package/src/task/index.ts +8 -4
- package/src/tool-discovery/tool-index.ts +0 -134
- package/src/tools/ast-edit.ts +36 -13
- package/src/tools/ast-grep.ts +45 -4
- package/src/tools/browser/tab-worker.ts +3 -2
- package/src/tools/eval.ts +2 -1
- package/src/tools/fetch.ts +23 -14
- package/src/tools/index.ts +2 -8
- package/src/tools/irc.ts +59 -5
- package/src/tools/match-line-format.ts +5 -7
- package/src/tools/output-schema-validator.ts +132 -0
- package/src/tools/read.ts +142 -31
- package/src/tools/review.ts +23 -0
- package/src/tools/search-tool-bm25.ts +3 -30
- package/src/tools/search.ts +48 -16
- package/src/tools/write.ts +3 -3
- package/src/tools/yield.ts +32 -41
- package/src/utils/edit-mode.ts +1 -2
- package/src/utils/file-mentions.ts +2 -2
- package/src/web/kagi.ts +15 -6
- package/src/web/parallel.ts +9 -6
- package/src/web/scrapers/types.ts +7 -1
- package/src/web/scrapers/youtube.ts +13 -7
- package/src/web/search/index.ts +37 -11
- package/src/web/search/provider.ts +5 -3
- package/src/web/search/providers/anthropic.ts +30 -21
- package/src/web/search/providers/base.ts +35 -2
- package/src/web/search/providers/brave.ts +4 -4
- package/src/web/search/providers/codex.ts +118 -89
- package/src/web/search/providers/exa.ts +3 -2
- package/src/web/search/providers/gemini.ts +58 -155
- package/src/web/search/providers/jina.ts +4 -4
- package/src/web/search/providers/kagi.ts +17 -11
- package/src/web/search/providers/kimi.ts +29 -13
- package/src/web/search/providers/parallel.ts +171 -23
- package/src/web/search/providers/perplexity.ts +38 -37
- package/src/web/search/providers/searxng.ts +3 -1
- package/src/web/search/providers/synthetic.ts +16 -19
- package/src/web/search/providers/tavily.ts +23 -18
- package/src/web/search/providers/utils.ts +11 -17
- package/src/web/search/providers/zai.ts +16 -8
- package/dist/types/hashline/parser.d.ts +0 -7
- package/dist/types/mcp/discoverable-tool-metadata.d.ts +0 -7
- package/dist/types/tools/vim.d.ts +0 -58
- package/dist/types/vim/buffer.d.ts +0 -41
- package/dist/types/vim/commands.d.ts +0 -6
- package/dist/types/vim/engine.d.ts +0 -47
- package/dist/types/vim/parser.d.ts +0 -3
- package/dist/types/vim/render.d.ts +0 -25
- package/dist/types/vim/types.d.ts +0 -182
- package/src/hashline/parser.ts +0 -246
- package/src/mcp/discoverable-tool-metadata.ts +0 -24
- package/src/prompts/tools/vim.md +0 -98
- package/src/tools/vim.ts +0 -949
- package/src/vim/buffer.ts +0 -309
- package/src/vim/commands.ts +0 -382
- package/src/vim/engine.ts +0 -2409
- package/src/vim/parser.ts +0 -134
- package/src/vim/render.ts +0 -252
- package/src/vim/types.ts +0 -197
package/src/eval/py/runner.py
CHANGED
|
@@ -5,6 +5,7 @@ wrapper writes typed frames back.
|
|
|
5
5
|
|
|
6
6
|
Host -> wrapper:
|
|
7
7
|
{"id": str, "code": str, "silent": bool?, "storeHistory": bool?}
|
|
8
|
+
{"id": str, "code": str, "silent": bool?, "storeHistory": bool?, "cwd": str?, "env": dict?}
|
|
8
9
|
{"type": "exit"} # graceful shutdown
|
|
9
10
|
|
|
10
11
|
Wrapper -> host:
|
|
@@ -27,6 +28,7 @@ from __future__ import annotations
|
|
|
27
28
|
|
|
28
29
|
import asyncio
|
|
29
30
|
import ast
|
|
31
|
+
import contextvars
|
|
30
32
|
import base64
|
|
31
33
|
import builtins
|
|
32
34
|
import inspect
|
|
@@ -43,7 +45,7 @@ import threading
|
|
|
43
45
|
import time
|
|
44
46
|
import traceback
|
|
45
47
|
from pathlib import Path
|
|
46
|
-
from typing import Any
|
|
48
|
+
from typing import Any
|
|
47
49
|
|
|
48
50
|
# ---------------------------------------------------------------------------
|
|
49
51
|
# Frame writer
|
|
@@ -93,7 +95,7 @@ class _StreamProxy(io.TextIOBase):
|
|
|
93
95
|
data = str(data)
|
|
94
96
|
if not data:
|
|
95
97
|
return 0
|
|
96
|
-
rid =
|
|
98
|
+
rid = _CURRENT_RID.get()
|
|
97
99
|
if rid is None:
|
|
98
100
|
_RAW_STDERR.write(data)
|
|
99
101
|
_RAW_STDERR.flush()
|
|
@@ -112,7 +114,6 @@ class _StreamProxy(io.TextIOBase):
|
|
|
112
114
|
|
|
113
115
|
class _RunnerState:
|
|
114
116
|
def __init__(self) -> None:
|
|
115
|
-
self.current_id: str | None = None
|
|
116
117
|
self.execution_count: int = 0
|
|
117
118
|
self.cancel_requested: bool = False
|
|
118
119
|
# User globals — kept across requests when running in session mode.
|
|
@@ -123,8 +124,11 @@ class _RunnerState:
|
|
|
123
124
|
}
|
|
124
125
|
self.last_install_marker: int = 0
|
|
125
126
|
self.loop: asyncio.AbstractEventLoop | None = None
|
|
127
|
+
self.active_executions: int = 0
|
|
126
128
|
|
|
127
129
|
|
|
130
|
+
_CURRENT_RID: contextvars.ContextVar[str | None] = contextvars.ContextVar("omp_current_rid", default=None)
|
|
131
|
+
|
|
128
132
|
_STATE = _RunnerState()
|
|
129
133
|
|
|
130
134
|
|
|
@@ -284,7 +288,7 @@ def cell_magic(name: str) -> Callable[[Callable[[str, str], Any]], Callable[[str
|
|
|
284
288
|
|
|
285
289
|
def _emit_status(op: str, **data: Any) -> None:
|
|
286
290
|
bundle = {"application/x-omp-status": {"op": op, **data}}
|
|
287
|
-
rid =
|
|
291
|
+
rid = _CURRENT_RID.get()
|
|
288
292
|
if rid is None:
|
|
289
293
|
return
|
|
290
294
|
_emit({"type": "display", "id": rid, "bundle": bundle})
|
|
@@ -424,7 +428,7 @@ def _magic_reset(_args: str) -> None:
|
|
|
424
428
|
def _magic_load(args: str) -> None:
|
|
425
429
|
path = Path(os.path.expanduser(args.strip()))
|
|
426
430
|
source = path.read_text(encoding="utf-8")
|
|
427
|
-
_emit({"type": "display", "id":
|
|
431
|
+
_emit({"type": "display", "id": _CURRENT_RID.get(), "bundle": {"text/plain": source}})
|
|
428
432
|
_exec_source(source, _STATE.user_ns)
|
|
429
433
|
|
|
430
434
|
|
|
@@ -622,7 +626,7 @@ def _mime_bundle(value: Any) -> dict:
|
|
|
622
626
|
|
|
623
627
|
|
|
624
628
|
def _emit_display(bundle: dict, *, kind: str = "display") -> None:
|
|
625
|
-
rid =
|
|
629
|
+
rid = _CURRENT_RID.get()
|
|
626
630
|
if rid is None:
|
|
627
631
|
return
|
|
628
632
|
_emit({"type": kind, "id": rid, "bundle": bundle})
|
|
@@ -681,6 +685,7 @@ def _install_builtins(ns: dict) -> None:
|
|
|
681
685
|
ns["__omp_magic"] = __omp_magic
|
|
682
686
|
ns["__omp_magic_cell"] = __omp_magic_cell
|
|
683
687
|
ns["__omp_shell"] = __omp_shell
|
|
688
|
+
ns["__omp_current_run_id__"] = lambda: _CURRENT_RID.get()
|
|
684
689
|
|
|
685
690
|
|
|
686
691
|
_install_builtins(_STATE.user_ns)
|
|
@@ -694,25 +699,37 @@ _install_builtins(_STATE.user_ns)
|
|
|
694
699
|
_TLA_FLAG = getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x2000)
|
|
695
700
|
|
|
696
701
|
|
|
697
|
-
def
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
702
|
+
def _await_sync(coro) -> Any:
|
|
703
|
+
try:
|
|
704
|
+
running_loop = asyncio.get_running_loop()
|
|
705
|
+
except RuntimeError:
|
|
706
|
+
running_loop = None
|
|
707
|
+
if running_loop is not None and running_loop.is_running():
|
|
708
|
+
raise RuntimeError("top-level await is not supported from synchronous magic execution")
|
|
709
|
+
return asyncio.run(coro)
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def _run_compiled_sync(code, ns: dict, *, want_value: bool) -> Any:
|
|
713
|
+
"""Synchronous execution path used by nested magic helpers."""
|
|
714
|
+
if code.co_flags & inspect.CO_COROUTINE:
|
|
715
|
+
result = _await_sync(eval(code, ns))
|
|
716
|
+
return result if want_value else None
|
|
717
|
+
if want_value:
|
|
718
|
+
return eval(code, ns)
|
|
719
|
+
exec(code, ns)
|
|
720
|
+
return None
|
|
721
|
+
|
|
704
722
|
|
|
705
723
|
|
|
706
|
-
def
|
|
707
|
-
"""Execute a code object
|
|
724
|
+
async def _run_compiled_async(code, ns: dict, *, want_value: bool) -> Any:
|
|
725
|
+
"""Execute a code object in the persistent event loop.
|
|
708
726
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
727
|
+
Coroutine code is awaited in this task so top-level ``await`` interleaves
|
|
728
|
+
with sibling requests. Plain statement/expression code runs on the main
|
|
729
|
+
runner thread so SIGINT can interrupt it reliably.
|
|
712
730
|
"""
|
|
713
731
|
if code.co_flags & inspect.CO_COROUTINE:
|
|
714
|
-
|
|
715
|
-
result = _get_event_loop().run_until_complete(coro)
|
|
732
|
+
result = await eval(code, ns)
|
|
716
733
|
return result if want_value else None
|
|
717
734
|
if want_value:
|
|
718
735
|
return eval(code, ns)
|
|
@@ -720,15 +737,10 @@ def _run_compiled(code, ns: dict, *, want_value: bool) -> Any:
|
|
|
720
737
|
return None
|
|
721
738
|
|
|
722
739
|
|
|
723
|
-
def
|
|
724
|
-
"""Compile + execute ``source``; if the last node is an expression, route
|
|
725
|
-
its value through ``__omp_display`` so dataframes/figures render rich.
|
|
726
|
-
Top-level ``await`` / ``async for`` / ``async with`` is permitted; the
|
|
727
|
-
cell is driven through the runner's persistent event loop."""
|
|
740
|
+
def _compile_source(source: str) -> tuple[Any, Any | None, bool]:
|
|
728
741
|
module = ast.parse(source, mode="exec")
|
|
729
|
-
|
|
730
742
|
if not module.body:
|
|
731
|
-
return
|
|
743
|
+
return None, None, False
|
|
732
744
|
|
|
733
745
|
last = module.body[-1]
|
|
734
746
|
if isinstance(last, ast.Expr):
|
|
@@ -737,14 +749,36 @@ def _exec_source(source: str, ns: dict) -> None:
|
|
|
737
749
|
ast.copy_location(expr_module, last)
|
|
738
750
|
body_code = compile(body_module, "<cell>", "exec", flags=_TLA_FLAG)
|
|
739
751
|
expr_code = compile(expr_module, "<cell>", "eval", flags=_TLA_FLAG)
|
|
740
|
-
|
|
741
|
-
|
|
752
|
+
return body_code, expr_code, True
|
|
753
|
+
|
|
754
|
+
return compile(module, "<cell>", "exec", flags=_TLA_FLAG), None, False
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def _exec_source(source: str, ns: dict) -> None:
|
|
758
|
+
"""Synchronous source execution for legacy magic helpers."""
|
|
759
|
+
body_code, expr_code, has_expr = _compile_source(source)
|
|
760
|
+
if body_code is None:
|
|
761
|
+
return
|
|
762
|
+
_run_compiled_sync(body_code, ns, want_value=False)
|
|
763
|
+
if has_expr and expr_code is not None:
|
|
764
|
+
value = _run_compiled_sync(expr_code, ns, want_value=True)
|
|
742
765
|
if value is not None:
|
|
743
766
|
__omp_display(value, kind="result")
|
|
744
|
-
return
|
|
745
767
|
|
|
746
|
-
|
|
747
|
-
|
|
768
|
+
|
|
769
|
+
async def _exec_source_async(source: str, ns: dict) -> None:
|
|
770
|
+
"""Compile + execute ``source``; if the last node is an expression, route
|
|
771
|
+
its value through ``__omp_display`` so dataframes/figures render rich.
|
|
772
|
+
Top-level ``await`` / ``async for`` / ``async with`` is permitted; awaited
|
|
773
|
+
regions yield to other requests in the runner's persistent event loop."""
|
|
774
|
+
body_code, expr_code, has_expr = _compile_source(source)
|
|
775
|
+
if body_code is None:
|
|
776
|
+
return
|
|
777
|
+
await _run_compiled_async(body_code, ns, want_value=False)
|
|
778
|
+
if has_expr and expr_code is not None:
|
|
779
|
+
value = await _run_compiled_async(expr_code, ns, want_value=True)
|
|
780
|
+
if value is not None:
|
|
781
|
+
__omp_display(value, kind="result")
|
|
748
782
|
|
|
749
783
|
|
|
750
784
|
# ---------------------------------------------------------------------------
|
|
@@ -767,6 +801,46 @@ def _install_exec_sigint() -> None:
|
|
|
767
801
|
pass
|
|
768
802
|
|
|
769
803
|
|
|
804
|
+
def _begin_exec_sigint() -> None:
|
|
805
|
+
_STATE.active_executions += 1
|
|
806
|
+
_install_exec_sigint()
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
def _end_exec_sigint() -> None:
|
|
810
|
+
if _STATE.active_executions > 0:
|
|
811
|
+
_STATE.active_executions -= 1
|
|
812
|
+
if _STATE.active_executions == 0:
|
|
813
|
+
_install_idle_sigint()
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
_MANAGED_ENV_KEYS = (
|
|
817
|
+
"PI_SESSION_FILE",
|
|
818
|
+
"PI_ARTIFACTS_DIR",
|
|
819
|
+
"PI_TOOL_BRIDGE_URL",
|
|
820
|
+
"PI_TOOL_BRIDGE_TOKEN",
|
|
821
|
+
"PI_TOOL_BRIDGE_SESSION",
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
def _apply_request_runtime(req: dict) -> None:
|
|
826
|
+
cwd = req.get("cwd")
|
|
827
|
+
if isinstance(cwd, str) and cwd:
|
|
828
|
+
os.chdir(cwd)
|
|
829
|
+
try:
|
|
830
|
+
sys.path.remove(cwd)
|
|
831
|
+
except ValueError:
|
|
832
|
+
pass
|
|
833
|
+
sys.path.insert(0, cwd)
|
|
834
|
+
|
|
835
|
+
env = req.get("env")
|
|
836
|
+
if isinstance(env, dict):
|
|
837
|
+
for key in _MANAGED_ENV_KEYS:
|
|
838
|
+
value = env.get(key)
|
|
839
|
+
if isinstance(value, str):
|
|
840
|
+
os.environ[key] = value
|
|
841
|
+
elif value is None:
|
|
842
|
+
os.environ.pop(key, None)
|
|
843
|
+
|
|
770
844
|
def _start_parent_watchdog() -> None:
|
|
771
845
|
"""Self-terminate when the host process dies.
|
|
772
846
|
|
|
@@ -802,61 +876,72 @@ def _start_parent_watchdog() -> None:
|
|
|
802
876
|
# ---------------------------------------------------------------------------
|
|
803
877
|
|
|
804
878
|
|
|
805
|
-
def
|
|
806
|
-
if req.get("type") == "exit":
|
|
807
|
-
sys.exit(0)
|
|
808
|
-
|
|
879
|
+
async def _handle_request_async(req: dict) -> None:
|
|
809
880
|
rid = str(req.get("id"))
|
|
810
|
-
|
|
811
|
-
_STATE.
|
|
881
|
+
token = _CURRENT_RID.set(rid)
|
|
882
|
+
_STATE.user_ns["__omp_run_id__"] = rid
|
|
812
883
|
_STATE.cancel_requested = False
|
|
813
884
|
_STATE.execution_count += 1
|
|
885
|
+
execution_count = _STATE.execution_count
|
|
814
886
|
_emit({"type": "started", "id": rid})
|
|
815
887
|
|
|
816
888
|
status: str = "ok"
|
|
817
889
|
cancelled = False
|
|
818
890
|
|
|
819
891
|
try:
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
892
|
+
try:
|
|
893
|
+
_apply_request_runtime(req)
|
|
894
|
+
transformed = transform_cell(req.get("code", ""))
|
|
895
|
+
except SyntaxError as exc:
|
|
896
|
+
_emit_error(rid, exc)
|
|
897
|
+
_emit({
|
|
898
|
+
"type": "done",
|
|
899
|
+
"id": rid,
|
|
900
|
+
"status": "error",
|
|
901
|
+
"executionCount": execution_count,
|
|
902
|
+
"cancelled": False,
|
|
903
|
+
})
|
|
904
|
+
return
|
|
905
|
+
except BaseException as exc: # noqa: BLE001 - runtime setup errors must settle the request
|
|
906
|
+
_emit_error(rid, exc)
|
|
907
|
+
_emit({
|
|
908
|
+
"type": "done",
|
|
909
|
+
"id": rid,
|
|
910
|
+
"status": "error",
|
|
911
|
+
"executionCount": execution_count,
|
|
912
|
+
"cancelled": False,
|
|
913
|
+
})
|
|
914
|
+
return
|
|
915
|
+
|
|
916
|
+
_begin_exec_sigint()
|
|
917
|
+
try:
|
|
918
|
+
await _exec_source_async(transformed, _STATE.user_ns)
|
|
919
|
+
except KeyboardInterrupt:
|
|
920
|
+
cancelled = True
|
|
921
|
+
status = "error"
|
|
922
|
+
_emit_error(rid, KeyboardInterrupt("Execution interrupted"))
|
|
923
|
+
except SystemExit as exc:
|
|
924
|
+
status = "error"
|
|
925
|
+
_emit_error(rid, exc)
|
|
926
|
+
except BaseException as exc: # noqa: BLE001 - we want to surface every user error
|
|
927
|
+
status = "error"
|
|
928
|
+
_emit_error(rid, exc)
|
|
929
|
+
finally:
|
|
930
|
+
_end_exec_sigint()
|
|
931
|
+
try:
|
|
932
|
+
_flush_matplotlib_figures()
|
|
933
|
+
except Exception:
|
|
934
|
+
pass
|
|
935
|
+
|
|
823
936
|
_emit({
|
|
824
937
|
"type": "done",
|
|
825
938
|
"id": rid,
|
|
826
|
-
"status":
|
|
827
|
-
"executionCount":
|
|
828
|
-
"cancelled":
|
|
939
|
+
"status": status,
|
|
940
|
+
"executionCount": execution_count,
|
|
941
|
+
"cancelled": cancelled,
|
|
829
942
|
})
|
|
830
|
-
_STATE.current_id = None
|
|
831
|
-
return
|
|
832
|
-
|
|
833
|
-
_install_exec_sigint()
|
|
834
|
-
try:
|
|
835
|
-
_exec_source(transformed, _STATE.user_ns)
|
|
836
|
-
except KeyboardInterrupt:
|
|
837
|
-
cancelled = True
|
|
838
|
-
status = "error"
|
|
839
|
-
_emit_error(rid, KeyboardInterrupt("Execution interrupted"))
|
|
840
|
-
except SystemExit:
|
|
841
|
-
raise
|
|
842
|
-
except BaseException as exc: # noqa: BLE001 - we want to surface every user error
|
|
843
|
-
status = "error"
|
|
844
|
-
_emit_error(rid, exc)
|
|
845
943
|
finally:
|
|
846
|
-
|
|
847
|
-
try:
|
|
848
|
-
_flush_matplotlib_figures()
|
|
849
|
-
except Exception:
|
|
850
|
-
pass
|
|
851
|
-
|
|
852
|
-
_emit({
|
|
853
|
-
"type": "done",
|
|
854
|
-
"id": rid,
|
|
855
|
-
"status": status,
|
|
856
|
-
"executionCount": _STATE.execution_count,
|
|
857
|
-
"cancelled": cancelled,
|
|
858
|
-
})
|
|
859
|
-
_STATE.current_id = None
|
|
944
|
+
_CURRENT_RID.reset(token)
|
|
860
945
|
|
|
861
946
|
|
|
862
947
|
def _emit_error(rid: str, exc: BaseException) -> None:
|
|
@@ -875,16 +960,7 @@ def _emit_error(rid: str, exc: BaseException) -> None:
|
|
|
875
960
|
# ---------------------------------------------------------------------------
|
|
876
961
|
|
|
877
962
|
|
|
878
|
-
def
|
|
879
|
-
sys.stdout = _StreamProxy("stdout")
|
|
880
|
-
sys.stderr = _StreamProxy("stderr")
|
|
881
|
-
_install_idle_sigint()
|
|
882
|
-
_start_parent_watchdog()
|
|
883
|
-
|
|
884
|
-
stdin = sys.__stdin__
|
|
885
|
-
if stdin is None:
|
|
886
|
-
return
|
|
887
|
-
|
|
963
|
+
def _read_stdin(loop: asyncio.AbstractEventLoop, queue: asyncio.Queue, stdin) -> None:
|
|
888
964
|
for raw_line in stdin:
|
|
889
965
|
line = raw_line.strip()
|
|
890
966
|
if not line:
|
|
@@ -900,10 +976,52 @@ def main() -> None:
|
|
|
900
976
|
"traceback": [],
|
|
901
977
|
})
|
|
902
978
|
continue
|
|
979
|
+
loop.call_soon_threadsafe(queue.put_nowait, req)
|
|
980
|
+
loop.call_soon_threadsafe(queue.put_nowait, {"type": "exit"})
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
async def _main_async() -> None:
|
|
984
|
+
sys.stdout = _StreamProxy("stdout")
|
|
985
|
+
sys.stderr = _StreamProxy("stderr")
|
|
986
|
+
_install_idle_sigint()
|
|
987
|
+
_start_parent_watchdog()
|
|
988
|
+
|
|
989
|
+
stdin = sys.__stdin__
|
|
990
|
+
if stdin is None:
|
|
991
|
+
return
|
|
992
|
+
|
|
993
|
+
loop = asyncio.get_running_loop()
|
|
994
|
+
_STATE.loop = loop
|
|
995
|
+
queue: asyncio.Queue = asyncio.Queue()
|
|
996
|
+
reader = threading.Thread(target=_read_stdin, args=(loop, queue, stdin), name="omp-stdin-reader", daemon=True)
|
|
997
|
+
reader.start()
|
|
998
|
+
|
|
999
|
+
tasks: set[asyncio.Task] = set()
|
|
1000
|
+
def _task_done(task: asyncio.Task) -> None:
|
|
1001
|
+
tasks.discard(task)
|
|
903
1002
|
try:
|
|
904
|
-
|
|
905
|
-
except
|
|
1003
|
+
exc = task.exception()
|
|
1004
|
+
except asyncio.CancelledError:
|
|
906
1005
|
return
|
|
1006
|
+
if exc is not None:
|
|
1007
|
+
_emit_error("", exc)
|
|
1008
|
+
try:
|
|
1009
|
+
while True:
|
|
1010
|
+
req = await queue.get()
|
|
1011
|
+
if req.get("type") == "exit":
|
|
1012
|
+
break
|
|
1013
|
+
task = asyncio.create_task(_handle_request_async(req))
|
|
1014
|
+
tasks.add(task)
|
|
1015
|
+
task.add_done_callback(_task_done)
|
|
1016
|
+
finally:
|
|
1017
|
+
for task in tasks:
|
|
1018
|
+
task.cancel()
|
|
1019
|
+
if tasks:
|
|
1020
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
def main() -> None:
|
|
1024
|
+
asyncio.run(_main_async())
|
|
907
1025
|
|
|
908
1026
|
|
|
909
1027
|
if __name__ == "__main__":
|
|
@@ -44,21 +44,23 @@ async function startServer(): Promise<BridgeServer> {
|
|
|
44
44
|
return new Response("Forbidden", { status: 403 });
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
let body: { session?: unknown; name?: unknown; args?: unknown };
|
|
47
|
+
let body: { session?: unknown; run?: unknown; name?: unknown; args?: unknown };
|
|
48
48
|
try {
|
|
49
|
-
body = (await req.json()) as { session?: unknown; name?: unknown; args?: unknown };
|
|
49
|
+
body = (await req.json()) as { session?: unknown; run?: unknown; name?: unknown; args?: unknown };
|
|
50
50
|
} catch {
|
|
51
51
|
return Response.json({ ok: false, error: "Invalid JSON body" }, { status: 400 });
|
|
52
52
|
}
|
|
53
53
|
const sessionId = typeof body.session === "string" ? body.session : "";
|
|
54
|
+
const runId = typeof body.run === "string" ? body.run : "";
|
|
54
55
|
const name = typeof body.name === "string" ? body.name : "";
|
|
55
|
-
if (!sessionId || !name) {
|
|
56
|
-
return Response.json({ ok: false, error: "Missing session/name" }, { status: 400 });
|
|
56
|
+
if (!sessionId || !runId || !name) {
|
|
57
|
+
return Response.json({ ok: false, error: "Missing session/run/name" }, { status: 400 });
|
|
57
58
|
}
|
|
58
|
-
const
|
|
59
|
+
const registrationKey = bridgeRegistrationKey(sessionId, runId);
|
|
60
|
+
const entry = registrations.get(registrationKey) ?? registrations.get(sessionId);
|
|
59
61
|
if (!entry) {
|
|
60
62
|
return Response.json(
|
|
61
|
-
{ ok: false, error: `No active Python tool bridge session: ${
|
|
63
|
+
{ ok: false, error: `No active Python tool bridge session: ${registrationKey}` },
|
|
62
64
|
{ status: 200 },
|
|
63
65
|
);
|
|
64
66
|
}
|
|
@@ -111,11 +113,16 @@ export async function ensurePyToolBridge(): Promise<PyToolBridgeInfo> {
|
|
|
111
113
|
* Register a tool session for the duration of one execution. The returned
|
|
112
114
|
* function MUST be called to remove the entry once execution finishes.
|
|
113
115
|
*/
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
function bridgeRegistrationKey(sessionId: string, runId: string): string {
|
|
117
|
+
return `${sessionId}:${runId}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function registerPyToolBridge(sessionId: string, runId: string, entry: PyToolBridgeEntry): () => void {
|
|
121
|
+
const key = bridgeRegistrationKey(sessionId, runId);
|
|
122
|
+
registrations.set(key, entry);
|
|
116
123
|
return () => {
|
|
117
|
-
if (registrations.get(
|
|
118
|
-
registrations.delete(
|
|
124
|
+
if (registrations.get(key) === entry) {
|
|
125
|
+
registrations.delete(key);
|
|
119
126
|
}
|
|
120
127
|
};
|
|
121
128
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolSession } from "../tools";
|
|
2
|
+
|
|
3
|
+
export type EvalSessionSource = Pick<ToolSession, "cwd" | "getSessionFile">;
|
|
4
|
+
|
|
5
|
+
export function defaultEvalSessionId(session: EvalSessionSource): string {
|
|
6
|
+
const sessionFile = session.getSessionFile?.() ?? undefined;
|
|
7
|
+
return sessionFile ? `session:${sessionFile}:cwd:${session.cwd}` : `cwd:${session.cwd}`;
|
|
8
|
+
}
|
|
@@ -48,8 +48,6 @@ export interface BashResult {
|
|
|
48
48
|
artifactId?: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const HARD_TIMEOUT_GRACE_MS = 5_000;
|
|
52
|
-
|
|
53
51
|
const shellSessions = new Map<string, Shell>();
|
|
54
52
|
const brokenShellSessions = new Set<string>();
|
|
55
53
|
|
|
@@ -106,8 +104,9 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
106
104
|
// sink.push() is synchronous — buffer management, counters, and onChunk
|
|
107
105
|
// all run inline. File writes (artifact path) are handled asynchronously
|
|
108
106
|
// inside the sink. No promise chain needed.
|
|
107
|
+
let acceptingChunks = true;
|
|
109
108
|
const enqueueChunk = (chunk: string) => {
|
|
110
|
-
sink.push(chunk);
|
|
109
|
+
if (acceptingChunks) sink.push(chunk);
|
|
111
110
|
};
|
|
112
111
|
|
|
113
112
|
if (options?.signal?.aborted) {
|
|
@@ -144,21 +143,22 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
144
143
|
void shellSession.abort();
|
|
145
144
|
}
|
|
146
145
|
};
|
|
146
|
+
const abortDeferred = Promise.withResolvers<"abort">();
|
|
147
147
|
const abortHandler = () => {
|
|
148
148
|
abortCurrentExecution();
|
|
149
|
+
abortDeferred.resolve("abort");
|
|
149
150
|
};
|
|
150
151
|
if (userSignal) {
|
|
151
152
|
userSignal.addEventListener("abort", abortHandler, { once: true });
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
let
|
|
155
|
-
const
|
|
155
|
+
let timeoutTimer: NodeJS.Timeout | undefined;
|
|
156
|
+
const timeoutDeferred = Promise.withResolvers<"timeout">();
|
|
156
157
|
const baseTimeoutMs = Math.max(1_000, options?.timeout ?? 300_000);
|
|
157
|
-
|
|
158
|
-
hardTimeoutTimer = setTimeout(() => {
|
|
158
|
+
timeoutTimer = setTimeout(() => {
|
|
159
159
|
abortCurrentExecution();
|
|
160
|
-
|
|
161
|
-
},
|
|
160
|
+
timeoutDeferred.resolve("timeout");
|
|
161
|
+
}, baseTimeoutMs);
|
|
162
162
|
|
|
163
163
|
let resetSession = false;
|
|
164
164
|
|
|
@@ -198,22 +198,33 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
198
198
|
|
|
199
199
|
const winner = await Promise.race([
|
|
200
200
|
runPromise.then(result => ({ kind: "result" as const, result })),
|
|
201
|
-
|
|
201
|
+
timeoutDeferred.promise.then(kind => ({ kind })),
|
|
202
|
+
abortDeferred.promise.then(kind => ({ kind })),
|
|
202
203
|
]);
|
|
203
204
|
|
|
204
|
-
if (winner.kind === "
|
|
205
|
+
if (winner.kind === "timeout" || winner.kind === "abort") {
|
|
206
|
+
acceptingChunks = false;
|
|
205
207
|
if (shellSession) {
|
|
206
208
|
resetSession = true;
|
|
207
|
-
// Fall back to one-shot execution for the rest of the process once
|
|
208
|
-
// a persistent session has stopped responding to cancellation.
|
|
209
209
|
brokenShellSessions.add(sessionKey);
|
|
210
|
+
void runPromise.finally(() => brokenShellSessions.delete(sessionKey)).catch(() => undefined);
|
|
211
|
+
} else {
|
|
212
|
+
void runPromise.catch(() => undefined);
|
|
210
213
|
}
|
|
211
214
|
return {
|
|
212
215
|
exitCode: undefined,
|
|
213
216
|
cancelled: true,
|
|
214
|
-
...(await sink.dump(
|
|
217
|
+
...(await sink.dump(
|
|
218
|
+
winner.kind === "timeout"
|
|
219
|
+
? `Command timed out after ${Math.round(baseTimeoutMs / 1000)} seconds`
|
|
220
|
+
: "Command cancelled",
|
|
221
|
+
)),
|
|
215
222
|
};
|
|
216
223
|
}
|
|
224
|
+
if (timeoutTimer) {
|
|
225
|
+
clearTimeout(timeoutTimer);
|
|
226
|
+
timeoutTimer = undefined;
|
|
227
|
+
}
|
|
217
228
|
|
|
218
229
|
// Handle timeout
|
|
219
230
|
if (winner.result.timedOut) {
|
|
@@ -268,8 +279,8 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
268
279
|
resetSession = true;
|
|
269
280
|
throw err;
|
|
270
281
|
} finally {
|
|
271
|
-
if (
|
|
272
|
-
clearTimeout(
|
|
282
|
+
if (timeoutTimer) {
|
|
283
|
+
clearTimeout(timeoutTimer);
|
|
273
284
|
}
|
|
274
285
|
if (userSignal) {
|
|
275
286
|
userSignal.removeEventListener("abort", abortHandler);
|
|
@@ -462,7 +462,6 @@ export class ExtensionRunner {
|
|
|
462
462
|
hasPendingMessages: () => this.#hasPendingMessagesFn(),
|
|
463
463
|
shutdown: () => this.#shutdownHandler(),
|
|
464
464
|
getSystemPrompt: () => this.#getSystemPromptFn(),
|
|
465
|
-
hasQueuedMessages: () => this.#hasPendingMessagesFn(), // deprecated alias
|
|
466
465
|
};
|
|
467
466
|
}
|
|
468
467
|
|
|
@@ -283,8 +283,6 @@ export interface ExtensionContext {
|
|
|
283
283
|
shutdown(): void;
|
|
284
284
|
/** Get the current effective system prompt. */
|
|
285
285
|
getSystemPrompt(): string[];
|
|
286
|
-
/** @deprecated Use hasPendingMessages() instead */
|
|
287
|
-
hasQueuedMessages(): boolean;
|
|
288
286
|
}
|
|
289
287
|
|
|
290
288
|
/**
|
|
@@ -968,7 +966,7 @@ export interface ExtensionAPI {
|
|
|
968
966
|
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
|
969
967
|
): void;
|
|
970
968
|
|
|
971
|
-
/** Send a user message to the agent
|
|
969
|
+
/** Send a user message to the agent, or queue it when deliverAs is set. */
|
|
972
970
|
sendUserMessage(
|
|
973
971
|
content: string | (TextContent | ImageContent)[],
|
|
974
972
|
options?: { deliverAs?: "steer" | "followUp" },
|