dsh-labscript 0.2.6 → 0.2.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 +15 -0
- package/README.md +2 -2
- package/lib/tools.js +3 -1
- package/package.json +1 -1
- package/python/bridge.py +11 -4
- package/python/tests/run_tests.py +35 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.7
|
|
4
|
+
|
|
5
|
+
- Fix: labs_wait no longer fails with "value is not lossless JSON". Tool results must be
|
|
6
|
+
pure JSON; a completed wait could carry an undefined hint field, which the DSH host
|
|
7
|
+
rejects when recording the result (the run itself was fine). labs_status pending
|
|
8
|
+
progress and the aborted-wait result are cleaned the same way.
|
|
9
|
+
- Fix: non-ASCII arguments (e.g. a Chinese note) no longer break the bridge. Python's
|
|
10
|
+
stdin on Windows uses the locale encoding (GBK); the bridge now reads stdin as UTF-8.
|
|
11
|
+
Such a note used to submit the run and then fail with UnicodeEncodeError in the event
|
|
12
|
+
log, which made a successful engage look failed.
|
|
13
|
+
- Fix: a failed event-log write can no longer turn a successful action into an error; it
|
|
14
|
+
is reported on stderr and the action result is returned as usual.
|
|
15
|
+
- Tests: Unicode note end-to-end through the bridge process, event-log failure
|
|
16
|
+
isolation, and a lossless-JSON check on tool results.
|
|
17
|
+
|
|
3
18
|
## 0.2.6
|
|
4
19
|
|
|
5
20
|
- Fix: labs_data rendered only counts. It now prints selected first-shot globals
|
package/README.md
CHANGED
|
@@ -125,8 +125,8 @@ is not a physics simulation.
|
|
|
125
125
|
## Development
|
|
126
126
|
|
|
127
127
|
npm install # only runtime dependency: yaml
|
|
128
|
-
npm test # node --test,
|
|
129
|
-
python python/tests/run_tests.py #
|
|
128
|
+
npm test # node --test, 40 tests (fake bridge, no python needed)
|
|
129
|
+
python python/tests/run_tests.py # 22 tests (needs numpy + h5py)
|
|
130
130
|
LABSCRIPT_PYTHON=/path/to/python3 node scripts/demo.mjs # end-to-end mock demo
|
|
131
131
|
|
|
132
132
|
A fresh clone must run `npm install` before `npm test`; the CI workflow does this.
|
package/lib/tools.js
CHANGED
|
@@ -133,6 +133,7 @@ async function pendingProgress(runtime, exec) {
|
|
|
133
133
|
note: pending.note,
|
|
134
134
|
elapsed_s: pending.submitted_at ? Math.round((Date.now() - Date.parse(pending.submitted_at)) / 1000) : undefined,
|
|
135
135
|
}
|
|
136
|
+
for (const key of Object.keys(base)) if (base[key] === undefined) delete base[key]
|
|
136
137
|
if (!pending.run_folder) return base
|
|
137
138
|
try {
|
|
138
139
|
const progress = await runtime.runBridge('progress', {
|
|
@@ -350,7 +351,7 @@ export function registerTools(ctx, runtime) {
|
|
|
350
351
|
runtime.state.waitCancelledByAbort = false
|
|
351
352
|
return {
|
|
352
353
|
run_folder: runFolder,
|
|
353
|
-
expected_n_shots: expected,
|
|
354
|
+
...(expected !== undefined ? { expected_n_shots: expected } : {}),
|
|
354
355
|
complete: false,
|
|
355
356
|
timed_out: false,
|
|
356
357
|
aborted: true,
|
|
@@ -376,6 +377,7 @@ export function registerTools(ctx, runtime) {
|
|
|
376
377
|
mapped_run_folder: waited.mapped_run_folder,
|
|
377
378
|
folder_exists: waited.folder_exists,
|
|
378
379
|
}
|
|
380
|
+
for (const key of Object.keys(out)) if (out[key] === undefined) delete out[key]
|
|
379
381
|
if (waited.complete && runtime.state.lastRun && runtime.state.lastRun.run_folder === runFolder) {
|
|
380
382
|
runtime.setLastRun(Object.assign({}, runtime.state.lastRun, {
|
|
381
383
|
completed_at: new Date().toISOString(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-labscript",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Control labscript-suite experiments from DeepSeek Harness: runmanager globals and scans, submits and waits, YAML fixed-operation protocols with postprocess write-back, panel snapshots, a built-in mock backend and a read-only doctor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/python/bridge.py
CHANGED
|
@@ -612,10 +612,13 @@ def snapshot_panel(backend, config, tool, args, touched=None):
|
|
|
612
612
|
|
|
613
613
|
|
|
614
614
|
def append_event(config, event):
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
615
|
+
try:
|
|
616
|
+
folder = history_dir(config)
|
|
617
|
+
path = os.path.join(folder, "events.jsonl")
|
|
618
|
+
with open(path, "a", encoding="utf-8") as handle:
|
|
619
|
+
handle.write(json.dumps(jsonable(event), ensure_ascii=False) + "\n")
|
|
620
|
+
except Exception as exc:
|
|
621
|
+
sys.stderr.write("append_event failed: %s\n" % (exc,))
|
|
619
622
|
|
|
620
623
|
|
|
621
624
|
def load_snapshot(config, name):
|
|
@@ -1530,6 +1533,10 @@ HANDLERS = {
|
|
|
1530
1533
|
|
|
1531
1534
|
|
|
1532
1535
|
def main():
|
|
1536
|
+
try:
|
|
1537
|
+
sys.stdin.reconfigure(encoding="utf-8")
|
|
1538
|
+
except Exception:
|
|
1539
|
+
pass
|
|
1533
1540
|
try:
|
|
1534
1541
|
request = json.load(sys.stdin)
|
|
1535
1542
|
except Exception as exc:
|
|
@@ -432,6 +432,41 @@ def test_data_keys_and_scan_summary():
|
|
|
432
432
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
433
433
|
|
|
434
434
|
|
|
435
|
+
def test_engage_with_unicode_note():
|
|
436
|
+
import subprocess
|
|
437
|
+
tmp = tempfile.mkdtemp(prefix="dsh-labscript-unicode-")
|
|
438
|
+
try:
|
|
439
|
+
cfg = make_config(tmp)
|
|
440
|
+
request = {"cmd": "engage", "config": cfg, "args": {"note": "中文备注:测试"}}
|
|
441
|
+
env = dict((k, v) for k, v in os.environ.items() if k not in ("PYTHONUTF8", "PYTHONIOENCODING"))
|
|
442
|
+
proc = subprocess.run(
|
|
443
|
+
[sys.executable, os.path.join(os.path.dirname(HERE), "bridge.py")],
|
|
444
|
+
input=json.dumps(request).encode("utf-8"),
|
|
445
|
+
stdout=subprocess.PIPE,
|
|
446
|
+
stderr=subprocess.PIPE,
|
|
447
|
+
env=env,
|
|
448
|
+
)
|
|
449
|
+
payload = json.loads(proc.stdout.decode("utf-8"))
|
|
450
|
+
assert payload.get("ok") is True, payload
|
|
451
|
+
assert payload["data"]["run_folder"], payload["data"]
|
|
452
|
+
with open(os.path.join(tmp, "history", "events.jsonl"), "r", encoding="utf-8") as handle:
|
|
453
|
+
assert "中文备注" in handle.read()
|
|
454
|
+
finally:
|
|
455
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def test_append_event_never_raises():
|
|
459
|
+
tmp = tempfile.mkdtemp(prefix="dsh-labscript-events-")
|
|
460
|
+
try:
|
|
461
|
+
blocked = os.path.join(tmp, "blocked")
|
|
462
|
+
with open(blocked, "w", encoding="utf-8") as handle:
|
|
463
|
+
handle.write("not a directory")
|
|
464
|
+
cfg = {"panelHistoryDir": os.path.join(blocked, "history")}
|
|
465
|
+
bridge.append_event(cfg, {"tool": "x", "note": "中文"})
|
|
466
|
+
finally:
|
|
467
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
468
|
+
|
|
469
|
+
|
|
435
470
|
def main():
|
|
436
471
|
tests = [value for name, value in sorted(globals().items()) if name.startswith("test_")]
|
|
437
472
|
failures = 0
|