better-dsh 0.3.0 → 0.3.1-a
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/README.md +49 -177
- package/cordis.patch.yml +5 -36
- package/docs/20_specs/kernel-provisioning/spec.md +2 -0
- package/docs/adr/0015-independent-bundles-agent-remote.md +40 -0
- package/docs/remote-tools.md +56 -0
- package/docs/specs/kernel-provisioning/spec.md +12 -40
- package/docs/specs/omp-python-repl-port/runtime-guide.md +35 -0
- package/docs/specs/omp-python-repl-port/spec.md +68 -0
- package/docs/specs/remote-tool-enhancement/spec.md +203 -0
- package/dsh-docs/baseline.md +13 -0
- package/dsh-docs/subsystems/invariants.md +3 -0
- package/dsh-docs/subsystems/invariants.zh.md +3 -0
- package/eval-description.md +7 -3
- package/lib/LaunchOptions-0Mr6meJc.js +4 -0
- package/lib/{LaunchOptions-kGXcZOe0.js → LaunchOptions-iE9M-STS.js} +1 -1
- package/lib/{bidi-BalpKaJc.js → bidi-CaQeBZHY.js} +1 -1
- package/lib/client/index.js +30 -6
- package/lib/failover/index.js +1 -1
- package/lib/fs-aware/sandbox-plugin.js +3 -3
- package/lib/{index-browser-CysrjMyM.js → index-browser-CJzxT9Vi.js} +4 -4
- package/lib/index.d.ts +16 -50
- package/lib/index.js +482 -1720
- package/lib/kernel-env-x_WkG6Fr.js +76 -0
- package/lib/kernel-env.d.ts +5 -69
- package/lib/kernel-env.js +2 -2
- package/lib/{lsp-server-registry-BXvUTBY7.js → lsp-server-registry-BcdkHROD.js} +1 -1
- package/lib/{lsp-server-registry-CH9WNFFt.js → lsp-server-registry-D4VLs1Sm.js} +1 -1
- package/lib/{main-BQeFrBM1.js → main-taSMz1KX.js} +1 -1
- package/lib/{main-CupOhMp5.js → main-uqGqYYnA.js} +7 -5
- package/lib/mobile/plugin.d.ts +1 -1
- package/lib/omp-python/DILL-LICENSE +35 -0
- package/lib/omp-python/OMP-LICENSE +23 -0
- package/lib/omp-python/better_snapshot.py +391 -0
- package/lib/omp-python/better_support.py +425 -0
- package/lib/omp-python/dill-provenance.json +55 -0
- package/lib/omp-python/provenance.json +13 -0
- package/lib/omp-python/runner.py +2499 -0
- package/lib/omp-python/vendor/dill/__diff.py +234 -0
- package/lib/omp-python/vendor/dill/__info__.py +291 -0
- package/lib/omp-python/vendor/dill/__init__.py +119 -0
- package/lib/omp-python/vendor/dill/_dill.py +2265 -0
- package/lib/omp-python/vendor/dill/_objects.py +545 -0
- package/lib/omp-python/vendor/dill/_shims.py +193 -0
- package/lib/omp-python/vendor/dill/detect.py +287 -0
- package/lib/omp-python/vendor/dill/logger.py +285 -0
- package/lib/omp-python/vendor/dill/objtypes.py +24 -0
- package/lib/omp-python/vendor/dill/pointers.py +122 -0
- package/lib/omp-python/vendor/dill/session.py +612 -0
- package/lib/omp-python/vendor/dill/settings.py +25 -0
- package/lib/omp-python/vendor/dill/source.py +1025 -0
- package/lib/omp-python/vendor/dill/temp.py +252 -0
- package/lib/{puppeteer-core-CSk2PtSm.js → puppeteer-core-C-4NyOpY.js} +8 -8
- package/lib/remote/plugin.d.ts +459 -5
- package/lib/remote/plugin.js +2389 -210
- package/lib/url-schemes/index.d.ts +11 -11
- package/lib/url-schemes/index.js +11 -11
- package/lib/web-password.d.ts +8 -8
- package/lib/web-password.js +5 -1
- package/lib/{web-trust-Dej_RI2B.d.ts → web-trust-C_Y6o8Z2.d.ts} +11 -11
- package/lib/web-trust.d.ts +1 -2
- package/lib/welcome-notice.d.ts +5 -7
- package/lib/welcome-notice.js +9 -48
- package/lib/{wrap-T4I0nfPM.js → wrap-0VA0uFx8.js} +1 -1
- package/package.json +7 -24
- package/scripts/kernel-check.mjs +5 -0
- package/lib/LaunchOptions-B874eBu2.js +0 -4
- package/lib/bundle-delivery.d.ts +0 -20
- package/lib/bundle-delivery.js +0 -209
- package/lib/hmr.d.ts +0 -32
- package/lib/hmr.js +0 -49
- package/lib/kernel-bridge.py +0 -172
- package/lib/kernel-env-D8UOWeji.js +0 -195
- package/lib/webserver.d.ts +0 -10
- package/lib/webserver.js +0 -19
- package/scripts/kernel-provision.mjs +0 -35
- package/scripts/register-bundle.mjs +0 -80
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
"""Better-owned integration around the unchanged OMP runner (see provenance.json)."""
|
|
2
|
+
import ast
|
|
3
|
+
import asyncio
|
|
4
|
+
import builtins
|
|
5
|
+
import contextvars
|
|
6
|
+
import json
|
|
7
|
+
import inspect
|
|
8
|
+
import os
|
|
9
|
+
import signal
|
|
10
|
+
import subprocess
|
|
11
|
+
import time
|
|
12
|
+
import sys
|
|
13
|
+
import threading
|
|
14
|
+
import urllib.request
|
|
15
|
+
import uuid
|
|
16
|
+
|
|
17
|
+
_RUN = contextvars.ContextVar('better_run', default=None)
|
|
18
|
+
_SPECS = contextvars.ContextVar('better_specs', default={})
|
|
19
|
+
_PENDING = {}
|
|
20
|
+
_LOCK = threading.Lock()
|
|
21
|
+
_WRITE_LOCK = threading.Lock()
|
|
22
|
+
_OWNED = {}
|
|
23
|
+
_INJECTED = {}
|
|
24
|
+
_STARTED = False
|
|
25
|
+
_WRITER = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class HostRejected(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _deliver(reply):
|
|
33
|
+
with _LOCK:
|
|
34
|
+
item = _PENDING.get(reply.get('id'))
|
|
35
|
+
if item is None:
|
|
36
|
+
return
|
|
37
|
+
loop, future, run = item
|
|
38
|
+
if reply.get('run') != run:
|
|
39
|
+
return
|
|
40
|
+
def settle():
|
|
41
|
+
if not future.done():
|
|
42
|
+
if reply.get('ok') is True:
|
|
43
|
+
future.set_result(reply.get('result'))
|
|
44
|
+
else:
|
|
45
|
+
future.set_exception(HostRejected(reply.get('message') or 'host request failed'))
|
|
46
|
+
loop.call_soon_threadsafe(settle)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _read_replies():
|
|
50
|
+
with os.fdopen(4, 'r', encoding='utf-8') as reader:
|
|
51
|
+
for line in reader:
|
|
52
|
+
try:
|
|
53
|
+
_deliver(json.loads(line))
|
|
54
|
+
except (ValueError, RuntimeError):
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def unsupported_input(*args, **kwargs):
|
|
59
|
+
raise RuntimeError('Interactive stdin is unsupported in Better Python REPL; pass input as a variable or tool argument')
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def initialize(ns):
|
|
63
|
+
global _STARTED, _WRITER
|
|
64
|
+
__import__('better_snapshot') # preload pinned dill before user imports
|
|
65
|
+
if not _STARTED:
|
|
66
|
+
_OWNED.update(ns)
|
|
67
|
+
if not os.environ.get('BETTER_CALLBACK_URL'):
|
|
68
|
+
_WRITER = os.fdopen(3, 'w', encoding='utf-8', buffering=1)
|
|
69
|
+
threading.Thread(target=_read_replies, name='better-callback-replies', daemon=True).start()
|
|
70
|
+
_STARTED = True
|
|
71
|
+
# Do not mutate the runner's Python builtins or put internal imports in user globals.
|
|
72
|
+
ns['__builtins__'] = dict(vars(builtins), input=unsupported_input)
|
|
73
|
+
_OWNED['__builtins__'] = ns['__builtins__']
|
|
74
|
+
ns['__omp_with_call_site__'] = with_call_site
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def with_call_site(site_id, action, args):
|
|
78
|
+
"""Required runner compiler seam; Better has no speculative read cache."""
|
|
79
|
+
result = action(args)
|
|
80
|
+
return await result if inspect.isawaitable(result) else result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def reset(ns, args):
|
|
84
|
+
"""Invoke the native magic, then immediately restore Better capabilities."""
|
|
85
|
+
__import__('better_snapshot').clear()
|
|
86
|
+
sys.modules['__main__'].__dict__['__omp_magic']('reset', args)
|
|
87
|
+
initialize(ns)
|
|
88
|
+
install_bindings(ns, _SPECS.get())
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ResetAdapter(ast.NodeTransformer):
|
|
92
|
+
def visit_Call(self, node):
|
|
93
|
+
node = self.generic_visit(node)
|
|
94
|
+
if (isinstance(node.func, ast.Name) and node.func.id == '__omp_magic'
|
|
95
|
+
and len(node.args) == 2 and isinstance(node.args[0], ast.Constant)
|
|
96
|
+
and node.args[0].value == 'reset'):
|
|
97
|
+
module = ast.Call(func=ast.Name(id='__import__', ctx=ast.Load()),
|
|
98
|
+
args=[ast.Constant('better_support')], keywords=[])
|
|
99
|
+
adapted = ast.Call(func=ast.Attribute(value=module, attr='reset', ctx=ast.Load()),
|
|
100
|
+
args=[ast.Call(func=ast.Name(id='globals', ctx=ast.Load()), args=[], keywords=[]), node.args[1]], keywords=[])
|
|
101
|
+
return ast.copy_location(adapted, node)
|
|
102
|
+
return node
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def host_request(payload):
|
|
106
|
+
run = _RUN.get()
|
|
107
|
+
if run is None:
|
|
108
|
+
raise HostRejected('host callbacks are only available during an active cell')
|
|
109
|
+
request_id = uuid.uuid4().hex
|
|
110
|
+
request = dict(payload, id=request_id, run=run)
|
|
111
|
+
if os.environ.get('BETTER_CALLBACK_URL'):
|
|
112
|
+
def http_call():
|
|
113
|
+
req = urllib.request.Request(os.environ['BETTER_CALLBACK_URL'],
|
|
114
|
+
data=json.dumps(request, allow_nan=False).encode('utf-8'),
|
|
115
|
+
headers={'Authorization': 'Bearer ' + os.environ['BETTER_CALLBACK_TOKEN'], 'Content-Type': 'application/json'})
|
|
116
|
+
# Explicitly bypass consumer proxy settings for the owned loopback endpoint.
|
|
117
|
+
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
|
118
|
+
with opener.open(req, timeout=30) as response:
|
|
119
|
+
return json.load(response)
|
|
120
|
+
reply = await asyncio.to_thread(http_call)
|
|
121
|
+
if reply.get('ok') is not True:
|
|
122
|
+
raise HostRejected(reply.get('message') or 'host request failed')
|
|
123
|
+
return reply.get('result')
|
|
124
|
+
loop = asyncio.get_running_loop()
|
|
125
|
+
future = loop.create_future()
|
|
126
|
+
with _LOCK:
|
|
127
|
+
_PENDING[request_id] = (loop, future, run)
|
|
128
|
+
try:
|
|
129
|
+
with _WRITE_LOCK:
|
|
130
|
+
_WRITER.write(json.dumps(request, allow_nan=False) + '\n')
|
|
131
|
+
_WRITER.flush()
|
|
132
|
+
return await future
|
|
133
|
+
finally:
|
|
134
|
+
with _LOCK:
|
|
135
|
+
_PENDING.pop(request_id, None)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _error_class(name, member_property):
|
|
139
|
+
def init(self, message, member=None):
|
|
140
|
+
Exception.__init__(self, message)
|
|
141
|
+
if member is not None:
|
|
142
|
+
setattr(self, member_property, member)
|
|
143
|
+
return type(name, (Exception,), {'__init__': init})
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _proxy(ns, global_name, function_name, error_class, flat=False):
|
|
147
|
+
async def invoke(call_args):
|
|
148
|
+
try:
|
|
149
|
+
return await host_request({'type': 'binding.call', 'global': global_name, 'name': function_name, 'args': call_args})
|
|
150
|
+
except HostRejected as rejected:
|
|
151
|
+
cls = ns.get(error_class) if error_class else None
|
|
152
|
+
if cls is None:
|
|
153
|
+
raise
|
|
154
|
+
raise cls(str(rejected), global_name if flat else function_name) from None
|
|
155
|
+
if flat:
|
|
156
|
+
async def callable_binding(args=None, /):
|
|
157
|
+
return await invoke({'args': [] if args is None else [args], 'kwargs': {}})
|
|
158
|
+
callable_binding.__name__ = global_name
|
|
159
|
+
callable_binding.__qualname__ = global_name
|
|
160
|
+
return callable_binding
|
|
161
|
+
async def namespace_binding(*args, **kwargs):
|
|
162
|
+
if kwargs:
|
|
163
|
+
raise TypeError('Better binding calls do not accept keyword arguments')
|
|
164
|
+
return await invoke(args[0] if len(args) == 1 else list(args) if args else None)
|
|
165
|
+
return namespace_binding
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def install_bindings(ns, spec):
|
|
169
|
+
for name, value in list(_INJECTED.items()):
|
|
170
|
+
if ns.get(name) is value:
|
|
171
|
+
ns.pop(name, None)
|
|
172
|
+
_INJECTED.clear()
|
|
173
|
+
classes = {}
|
|
174
|
+
for global_name, config in spec.items():
|
|
175
|
+
error = config.get('errorClass')
|
|
176
|
+
error_name = error['name'] if error else None
|
|
177
|
+
if error and error_name not in classes:
|
|
178
|
+
classes[error_name] = _error_class(error_name, error['memberNameProperty'])
|
|
179
|
+
if config.get('callable'):
|
|
180
|
+
value = _proxy(ns, global_name, next(iter(config['functions'])), error_name, flat=True)
|
|
181
|
+
else:
|
|
182
|
+
def holder(global_name=global_name, config=config, error_name=error_name):
|
|
183
|
+
class BindingHolder:
|
|
184
|
+
def __dir__(self):
|
|
185
|
+
return sorted(config['functions'])
|
|
186
|
+
def __getattr__(self, name):
|
|
187
|
+
return _proxy(ns, global_name, name, error_name)
|
|
188
|
+
return BindingHolder()
|
|
189
|
+
value = holder()
|
|
190
|
+
ns[global_name] = value
|
|
191
|
+
_INJECTED[global_name] = value
|
|
192
|
+
ns.update(classes)
|
|
193
|
+
_INJECTED.update(classes)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def complete(value, sentinel, absent=False):
|
|
197
|
+
envelope = {'ok': True}
|
|
198
|
+
if not absent and value is not None:
|
|
199
|
+
try:
|
|
200
|
+
envelope['json'] = json.dumps(value, allow_nan=False)
|
|
201
|
+
except (TypeError, ValueError, OverflowError):
|
|
202
|
+
envelope['json'] = json.dumps(repr(value))
|
|
203
|
+
print(sentinel + json.dumps(envelope))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _process_tree():
|
|
207
|
+
"""Return descendants and birth identities; no subprocess monkeypatching."""
|
|
208
|
+
if os.name != 'posix':
|
|
209
|
+
return {} # Windows limitation is explicit in the delivery report.
|
|
210
|
+
root = os.getpid()
|
|
211
|
+
if os.path.isdir('/proc/self/task'):
|
|
212
|
+
tree = {}
|
|
213
|
+
visited = {root}
|
|
214
|
+
pending = [root]
|
|
215
|
+
while pending:
|
|
216
|
+
parent = pending.pop()
|
|
217
|
+
try:
|
|
218
|
+
tasks = os.listdir('/proc/%d/task' % parent)
|
|
219
|
+
except FileNotFoundError:
|
|
220
|
+
continue
|
|
221
|
+
for task in tasks:
|
|
222
|
+
try:
|
|
223
|
+
with open('/proc/%d/task/%s/children' % (parent, task)) as stream:
|
|
224
|
+
children = stream.read().split()
|
|
225
|
+
except FileNotFoundError:
|
|
226
|
+
continue
|
|
227
|
+
for child in children:
|
|
228
|
+
pid = int(child)
|
|
229
|
+
if pid in visited:
|
|
230
|
+
continue
|
|
231
|
+
visited.add(pid)
|
|
232
|
+
identity = _process_identity(pid)
|
|
233
|
+
if identity is not None:
|
|
234
|
+
tree[pid] = identity[0]
|
|
235
|
+
pending.append(pid)
|
|
236
|
+
return tree
|
|
237
|
+
result = subprocess.run(['ps', '-axo', 'pid=,ppid=,stat=,lstart='],
|
|
238
|
+
capture_output=True, text=True, check=True, timeout=0.03)
|
|
239
|
+
rows = {}
|
|
240
|
+
for line in result.stdout.splitlines():
|
|
241
|
+
parts = line.split(None, 3)
|
|
242
|
+
if len(parts) == 4:
|
|
243
|
+
rows[int(parts[0])] = (int(parts[1]), parts[3])
|
|
244
|
+
if root not in rows:
|
|
245
|
+
raise RuntimeError('cannot inspect Python process tree')
|
|
246
|
+
found = {root}
|
|
247
|
+
changed = True
|
|
248
|
+
while changed:
|
|
249
|
+
changed = False
|
|
250
|
+
for pid, (parent, _) in rows.items():
|
|
251
|
+
if parent in found and pid not in found:
|
|
252
|
+
found.add(pid)
|
|
253
|
+
changed = True
|
|
254
|
+
return {pid: rows[pid][1] for pid in found if pid != root}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _process_identity(pid):
|
|
258
|
+
if os.path.isdir('/proc/self/task'):
|
|
259
|
+
try:
|
|
260
|
+
with open('/proc/%d/stat' % pid) as stream:
|
|
261
|
+
text = stream.read()
|
|
262
|
+
fields = text[text.rfind(')') + 2:].split()
|
|
263
|
+
return fields[19], fields[0]
|
|
264
|
+
except FileNotFoundError:
|
|
265
|
+
return None
|
|
266
|
+
result = subprocess.run(['ps', '-p', str(pid), '-o', 'stat=,lstart='],
|
|
267
|
+
capture_output=True, text=True, timeout=0.02)
|
|
268
|
+
parts = result.stdout.strip().split(None, 1)
|
|
269
|
+
return (parts[1], parts[0]) if len(parts) == 2 else None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _cancel_new_children(baseline):
|
|
273
|
+
if baseline is None:
|
|
274
|
+
raise RuntimeError('process tree baseline unavailable')
|
|
275
|
+
# Capture identities before parents exit/reparent; never signal a reused PID.
|
|
276
|
+
current = _process_tree()
|
|
277
|
+
owned = {pid: birth for pid, birth in current.items() if baseline.get(pid) != birth}
|
|
278
|
+
deadline = time.monotonic() + 0.10
|
|
279
|
+
term_until = time.monotonic() + 0.025
|
|
280
|
+
killed = set()
|
|
281
|
+
for pid in reversed(list(owned)):
|
|
282
|
+
identity = _process_identity(pid)
|
|
283
|
+
if identity and identity[0] == owned[pid] and identity[1] != 'Z':
|
|
284
|
+
try:
|
|
285
|
+
os.kill(pid, signal.SIGTERM)
|
|
286
|
+
except ProcessLookupError:
|
|
287
|
+
pass
|
|
288
|
+
while True:
|
|
289
|
+
# Pick up children created by a terminating descendant while its
|
|
290
|
+
# parent is still visible; retain identities after reparenting.
|
|
291
|
+
for pid, birth in _process_tree().items():
|
|
292
|
+
if baseline.get(pid) != birth and pid not in owned:
|
|
293
|
+
owned[pid] = birth
|
|
294
|
+
try:
|
|
295
|
+
os.kill(pid, signal.SIGTERM)
|
|
296
|
+
except ProcessLookupError:
|
|
297
|
+
pass
|
|
298
|
+
# Reap only this cell's direct children; earlier persistent children
|
|
299
|
+
# are left to their owners. Grandchild zombies are no longer executing.
|
|
300
|
+
survivors = []
|
|
301
|
+
for pid, birth in owned.items():
|
|
302
|
+
try:
|
|
303
|
+
os.waitpid(pid, os.WNOHANG)
|
|
304
|
+
except ChildProcessError:
|
|
305
|
+
pass
|
|
306
|
+
identity = _process_identity(pid)
|
|
307
|
+
if identity and identity[0] == birth and not identity[1].startswith('Z'):
|
|
308
|
+
survivors.append(pid)
|
|
309
|
+
if not survivors:
|
|
310
|
+
return
|
|
311
|
+
now = time.monotonic()
|
|
312
|
+
if now >= deadline:
|
|
313
|
+
raise RuntimeError('cancelled descendants did not terminate')
|
|
314
|
+
if now >= term_until:
|
|
315
|
+
for pid in survivors:
|
|
316
|
+
if pid not in killed:
|
|
317
|
+
try:
|
|
318
|
+
os.kill(pid, signal.SIGKILL)
|
|
319
|
+
except ProcessLookupError:
|
|
320
|
+
pass
|
|
321
|
+
killed.add(pid)
|
|
322
|
+
time.sleep(min(0.003, deadline - now))
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
async def run(ns, source, spec_json, sentinel):
|
|
326
|
+
"""Retain OMP transforms/compiler/TLA execution; adapt only typed completion."""
|
|
327
|
+
runner = sys.modules['__main__']
|
|
328
|
+
initialize(ns)
|
|
329
|
+
spec = json.loads(spec_json)
|
|
330
|
+
install_bindings(ns, spec)
|
|
331
|
+
spec_token = _SPECS.set(spec)
|
|
332
|
+
token = _RUN.set(runner._CURRENT_RID.get())
|
|
333
|
+
baseline = None
|
|
334
|
+
interrupted = False
|
|
335
|
+
previous_handler = signal.getsignal(signal.SIGINT)
|
|
336
|
+
def on_interrupt(signum, frame):
|
|
337
|
+
nonlocal interrupted
|
|
338
|
+
interrupted = True
|
|
339
|
+
if callable(previous_handler):
|
|
340
|
+
return previous_handler(signum, frame)
|
|
341
|
+
if previous_handler != signal.SIG_IGN:
|
|
342
|
+
return signal.default_int_handler(signum, frame)
|
|
343
|
+
signal.signal(signal.SIGINT, on_interrupt)
|
|
344
|
+
try:
|
|
345
|
+
try:
|
|
346
|
+
baseline = _process_tree()
|
|
347
|
+
except (OSError, ValueError, subprocess.SubprocessError):
|
|
348
|
+
pass
|
|
349
|
+
transformed = runner.transform_cell(source)
|
|
350
|
+
tree = ResetAdapter().visit(ast.parse(transformed or 'pass', '<cell>', 'exec'))
|
|
351
|
+
# OMP owns compilation and execution. Wrapping its final expression only
|
|
352
|
+
# changes the display boundary to the existing lossless JSON contract.
|
|
353
|
+
if tree.body and isinstance(tree.body[-1], ast.Expr):
|
|
354
|
+
expression = tree.body[-1]
|
|
355
|
+
call = ast.Call(func=ast.Attribute(value=ast.Call(func=ast.Name(id='__import__', ctx=ast.Load()),
|
|
356
|
+
args=[ast.Constant('better_support')], keywords=[]), attr='complete', ctx=ast.Load()),
|
|
357
|
+
args=[expression.value, ast.Constant(sentinel)], keywords=[])
|
|
358
|
+
tree.body[-1] = ast.copy_location(ast.Expr(value=call), expression)
|
|
359
|
+
else:
|
|
360
|
+
tree.body.append(ast.Expr(value=ast.Call(func=ast.Attribute(value=ast.Call(func=ast.Name(id='__import__', ctx=ast.Load()),
|
|
361
|
+
args=[ast.Constant('better_support')], keywords=[]), attr='complete', ctx=ast.Load()),
|
|
362
|
+
args=[ast.Constant(None), ast.Constant(sentinel)], keywords=[ast.keyword(arg='absent', value=ast.Constant(True))])))
|
|
363
|
+
ast.fix_missing_locations(tree)
|
|
364
|
+
await runner._exec_source_async(ast.unparse(tree), ns)
|
|
365
|
+
except KeyboardInterrupt:
|
|
366
|
+
interrupted = True
|
|
367
|
+
raise
|
|
368
|
+
finally:
|
|
369
|
+
if interrupted:
|
|
370
|
+
# User code may catch KeyboardInterrupt itself. Receipt tracking
|
|
371
|
+
# still makes cancellation clean current foreground descendants.
|
|
372
|
+
# The host's confirm SIGINT cannot interrupt cleanup.
|
|
373
|
+
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
374
|
+
try:
|
|
375
|
+
_cancel_new_children(baseline)
|
|
376
|
+
except BaseException:
|
|
377
|
+
# Host observes death and sweeps group before queue admission.
|
|
378
|
+
os._exit(130)
|
|
379
|
+
signal.signal(signal.SIGINT, previous_handler)
|
|
380
|
+
_RUN.reset(token)
|
|
381
|
+
_SPECS.reset(spec_token)
|
|
382
|
+
# A detached Python task must not gain admission after its cell ended.
|
|
383
|
+
with _LOCK:
|
|
384
|
+
pending = [item for item in _PENDING.values() if item[2] == runner._CURRENT_RID.get()]
|
|
385
|
+
for loop, future, _ in pending:
|
|
386
|
+
if not future.done():
|
|
387
|
+
future.cancel()
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def user_namespace(ns):
|
|
391
|
+
return {name: value for name, value in ns.items()
|
|
392
|
+
if not name.startswith('__omp_') and name not in ('__name__', '__doc__', '__builtins__')
|
|
393
|
+
and not (name in _OWNED and _OWNED[name] is value)
|
|
394
|
+
and not (name in _INJECTED and _INJECTED[name] is value)}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def query(ns, name_json, sentinel):
|
|
398
|
+
user = user_namespace(ns)
|
|
399
|
+
name = json.loads(name_json)
|
|
400
|
+
if name is None:
|
|
401
|
+
reply = {'ok': True, 'kind': 'names', 'names': sorted(user)}
|
|
402
|
+
elif name not in user:
|
|
403
|
+
reply = {'ok': True, 'kind': 'missing'}
|
|
404
|
+
else:
|
|
405
|
+
try:
|
|
406
|
+
reply = {'ok': True, 'kind': 'json', 'text': json.dumps(user[name], allow_nan=False)}
|
|
407
|
+
except (TypeError, ValueError, OverflowError):
|
|
408
|
+
reply = {'ok': True, 'kind': 'repr', 'text': repr(user[name])}
|
|
409
|
+
print(sentinel + json.dumps(reply))
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def set_var(ns, name, value_json, sentinel):
|
|
413
|
+
ns[name] = json.loads(value_json)
|
|
414
|
+
print(sentinel + json.dumps({'ok': True}))
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def owned_objects():
|
|
418
|
+
return list(_OWNED.values()) + list(_INJECTED.values())
|
|
419
|
+
|
|
420
|
+
def reserved_names():
|
|
421
|
+
return set(_OWNED) | set(_INJECTED)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def injected_names():
|
|
425
|
+
return set(_INJECTED)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.4.1",
|
|
3
|
+
"artifact": {
|
|
4
|
+
"url": "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl",
|
|
5
|
+
"sha256": "1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d"
|
|
6
|
+
},
|
|
7
|
+
"adaptations": "None. Runtime Python sources and BSD-3-Clause license copied unchanged from wheel; tests excluded. Better adapter lives in better_snapshot.py.",
|
|
8
|
+
"files": {
|
|
9
|
+
"vendor/dill/__diff.py": {
|
|
10
|
+
"sha256": "ed70aa5887a54d648b8e278e22edd7b6bd6652e8bd6019a5b4fefeb6fb6b1be4"
|
|
11
|
+
},
|
|
12
|
+
"vendor/dill/__info__.py": {
|
|
13
|
+
"sha256": "106836361f383613e762ad49da3e8400f8b105bde9b15c58fb9ac05b3afca6b9"
|
|
14
|
+
},
|
|
15
|
+
"vendor/dill/__init__.py": {
|
|
16
|
+
"sha256": "63cbac9f3a5fb1c949c53e93bd16066ab117916117a6bbf8942d992746c63366"
|
|
17
|
+
},
|
|
18
|
+
"vendor/dill/_dill.py": {
|
|
19
|
+
"sha256": "35d695c86cce6f94b9eda6d83747855dca20a1c1d0ad306452bbbb72ac367f03"
|
|
20
|
+
},
|
|
21
|
+
"vendor/dill/_objects.py": {
|
|
22
|
+
"sha256": "fd773b3aa9d97ac0e62819ad91b92b041303cc7dd3205cbfbed311b5cee5b122"
|
|
23
|
+
},
|
|
24
|
+
"vendor/dill/_shims.py": {
|
|
25
|
+
"sha256": "92421aee6b961c7a7db88b6935f7ea99b8413b830428f21d7b21ce2b1c76545e"
|
|
26
|
+
},
|
|
27
|
+
"vendor/dill/detect.py": {
|
|
28
|
+
"sha256": "55b0b234c68856e762a8cd672e61b04bec975ba33d0a47696e455aac09a726a5"
|
|
29
|
+
},
|
|
30
|
+
"vendor/dill/logger.py": {
|
|
31
|
+
"sha256": "675c2053c05b48011604aa2e71844762b16154baaaa934e2f811dc7ec79b8bcf"
|
|
32
|
+
},
|
|
33
|
+
"vendor/dill/objtypes.py": {
|
|
34
|
+
"sha256": "de10c49d14b4e34995c80de7309a74b1e222030f5269fc5bbaf1b9b45bed85f1"
|
|
35
|
+
},
|
|
36
|
+
"vendor/dill/pointers.py": {
|
|
37
|
+
"sha256": "cde5fc5ea94b0485bdbcbf971ff7abf475169889e56df5a362dbdae7a7939b0c"
|
|
38
|
+
},
|
|
39
|
+
"vendor/dill/session.py": {
|
|
40
|
+
"sha256": "f3c0c914842b03cef7dcff952a46c2ab47759722737244180ab1b170f79229e1"
|
|
41
|
+
},
|
|
42
|
+
"vendor/dill/settings.py": {
|
|
43
|
+
"sha256": "0d1169d900bcc6ca4f67949bdde201e9a6cfa75c0db8136d0848d9f032d8fb1e"
|
|
44
|
+
},
|
|
45
|
+
"vendor/dill/source.py": {
|
|
46
|
+
"sha256": "1ac9987fa6c43cb33dae2bf3d4473b1b613eb44800a1723de428aca9d7101860"
|
|
47
|
+
},
|
|
48
|
+
"vendor/dill/temp.py": {
|
|
49
|
+
"sha256": "785c8c8192094545607518b66b8236b597798371d25059df9b92b72d5d99f779"
|
|
50
|
+
},
|
|
51
|
+
"DILL-LICENSE": {
|
|
52
|
+
"sha256": "f669d63bebf765baaa0b20d8b0c1193296a7e0ea0c22c50a308b74d76ab3939c"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"upstream": "https://github.com/can1357/oh-my-pi",
|
|
3
|
+
"commit": "fa5ff4a3a977a28b93d025f4cf50f046e019f18c",
|
|
4
|
+
"files": {
|
|
5
|
+
"runner.py": {
|
|
6
|
+
"path": "packages/coding-agent/src/eval/py/runner.py",
|
|
7
|
+
"sha256": "e0e660ca44193735a2de467254b050109bb23028aca71af01b9365b9ab88e974",
|
|
8
|
+
"modified": false
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"license": "OMP-LICENSE",
|
|
12
|
+
"adaptation": "Better-owned better_support.py runs runner.transform_cell and runner._exec_source_async without patching either. It wraps final expression display for lossless JSON, owns bindings, stdlib callback pipes on POSIX and authenticated loopback HTTP on Windows, namespace filtering and unsupported input(). The OMP Agent prelude is neither executed nor shipped. Better supplies the compiler-required __omp_with_call_site__ forwarding helper without speculative caching, restores current bindings/input guard immediately after native reset using its own AST adapter, and uses the default cell filename to avoid synthetic user state. Better tracks per-cell descendant birth identities and SIGINT receipt, cleans new foreground descendants on cancellation with bounded TERM/KILL/reaping, and restores the prior signal handler. Prior persistent children are retained; unproven cleanup retires the runner."
|
|
13
|
+
}
|