dsh-labscript 0.2.7 → 0.3.0

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 CHANGED
@@ -1,5 +1,77 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ Implements the upstream request written from the field (labscript-suite workflow terms and
6
+ `runmanager.remote` limits). No output field is renamed or removed.
7
+
8
+ - `n_shots` is now documented and rendered as the authoritative shot count. The scan-axes line is
9
+ labelled a heuristic: runmanager expands list/array globals but passes tuples through, and an
10
+ expansion setting of `none` turns an array into a non-axis; neither is visible through
11
+ `runmanager.remote`. A mismatch now reads "n_shots is authoritative (a tuple global or an
12
+ expansion setting of none is not an axis)" instead of printing two numbers with no verdict. The
13
+ bridge's axis guess ignores tuples and dicts, and the mock backend follows the same rule (it used
14
+ to multiply tuples in, pinning the wrong semantics into the tests).
15
+ - Skill and tool descriptions no longer tell the agent to "read the runmanager output": compilation
16
+ errors appear only in runmanager's output tab (the compiler subprocess writes its traceback to
17
+ stderr and runmanager redirects it to an in-memory Qt widget), so the instruction is now to say
18
+ that no shot file appeared, ask the operator to paste the tab, and not guess the cause.
19
+ `labs_set` also warns that a global name must not shadow a Python builtin or a
20
+ `from labscript import *` name: runmanager accepts it and labscript fails at compile time.
21
+ - `compiled_not_executed` explains the other official causes: BLACS may keep a shot out of its
22
+ queue (the shot's connection table must be a subset of the lab connection table) or put it back
23
+ at the head after a device programming timeout; all of that is visible only in the BLACS GUI.
24
+ More shot files than expected now points at a changed panel or BLACS Repeat all / Repeat last,
25
+ which re-runs completed shots without creating new files.
26
+ - `labs_status` reports `shuffle` (from `get_shuffle`; "unknown" when that runmanager version has
27
+ no such method). With shuffle on, shot order is not axis order: read each shot's parameters from
28
+ its own h5 globals.
29
+ - runmanager host/port/timeout default to labscript's labconfig when unset, resolved by
30
+ `runmanager.remote.Client` itself (`[servers]/[ports]/[timeouts]`, port 42523, timeout 60 s);
31
+ configured values still win. The Plugins page now leaves host/port blank for that reason, and
32
+ clearing them hands the endpoint back to labconfig. `labs_doctor` prints the effective endpoint
33
+ and whether it came from config or labconfig.
34
+ - mock backend: `mock.errorInGlobals` makes `error_in_globals` true so the engagement refusal can
35
+ be tested, and `mock.shuffle` sets the reported shuffle flag.
36
+ - Docs: a terminology table maps the plugin's short names to the suite's (shot output folder,
37
+ sequence, shot file, parameter space/axes, acquired data) without renaming fields.
38
+ - Compilation logs stay out of scope: nothing about a compilation failure is written anywhere
39
+ readable (see README), so no `labs_compile_log` was added; a guessed diagnosis would be worse
40
+ than none.
41
+ - Tests: 33 python + 47 node (axis heuristic with tuples/dicts, mock error/shuffle switches,
42
+ labconfig client defaults, extra shot files, scan/shuffle rendering, endpoint clearing).
43
+
44
+ ## 0.2.8
45
+
46
+ Fixes from a real labscript-suite session (2026-11-14, 145 tool calls, no run submitted by the
47
+ plugin). Each defect was reproduced against the stock runmanager source before the fix.
48
+
49
+ - Fix: labs_set failed with "TypeError: 'NoneType' object is not iterable" after the write had
50
+ already landed. runmanager's set_globals RPC is a side-effect call whose handler returns None,
51
+ and the bridge fed that reply to dict(). It now reports the requested values and lets the
52
+ readback decide success. When a write really fails, the error carries `landed` (what the panel
53
+ holds now) and the hint says to read the keys back instead of claiming the panel may be half
54
+ written and pointing at labs_restore.
55
+ - Fix: labs_wait blocked for the whole timeout when compilation aborted. It returns after
56
+ engage.noShotS seconds (default 25) with diagnosis no_shots (no shot file appeared at all) or
57
+ compile_stalled (files stopped appearing), so the runmanager compile error can be read instead
58
+ of waiting 5-15 minutes. The hint says to call labs_wait again if a slow compile is expected.
59
+ - Fix: a pending run that never produced a shot blocked labs_engage forever. A pending submission
60
+ with zero shots and older than engage.staleS (default 180 s) is dropped and reported as
61
+ dropped_stale_pending; the RUN_PENDING error now states the run's age and both ways out.
62
+ - Fix: with pathMap configured, labs_wait(run_folder=<the folder labs_engage printed>) failed with
63
+ RUN_FOLDER_MISSING: the pending run kept the unmapped path and the wait compared strings. Pending
64
+ state now stores the mapped folder (raw kept as remote_run_folder) and both forms match.
65
+ - Fix: shot h5 files that only hold compiled globals were counted as progress, so h5=1/64 looked
66
+ like "a shot ran". Progress reports n_h5 (files) and n_executed (shots with acquisition data,
67
+ located by binary search over the file order), completion requires acquisition data in the last
68
+ shot, and labs_status renders executed=.
69
+ - labs_protocol show prints the closest key names for plan keys missing from the loaded labscript
70
+ (previously only the run path did), so stale protocols can be renamed without guessing.
71
+ - labs_set description: one call can mix scalars, an expression scan axis and reps=N.
72
+ - Tests: 28 python + 46 node, adding a client whose set_globals returns None, a stalled
73
+ compilation, a stale zero-shot pending run, mapped/raw pending folders and the show suggestions.
74
+
3
75
  ## 0.2.7
4
76
 
5
77
  - Fix: labs_wait no longer fails with "value is not lossless JSON". Tool results must be
package/README.md CHANGED
@@ -40,9 +40,9 @@ config). Run `labs_doctor` first if anything looks misconfigured.
40
40
  | Tool | What it does |
41
41
  |---|---|
42
42
  | labs_doctor | Read-only configuration diagnostic: python, numpy/h5py, labscript/runmanager, paths, approval, versions. |
43
- | labs_status | Read the panel: labscript, globals, n_shots, error_in_globals, run_shots, pending run progress, snapshot directory |
43
+ | labs_status | Read the panel: labscript, globals, n_shots, error_in_globals, run_shots, shuffle, pending run progress, snapshot directory |
44
44
  | labs_set | Write panel globals (or raw expressions, or the repetition counter). Snapshots the panel first. Does not run. |
45
- | labs_engage | Submit the current panel for a run; returns immediately with n_shots_at_submit and expected_n_shots |
45
+ | labs_engage | Submit the current panel as one sequence (shot files to BLACS); returns immediately with n_shots_at_submit and expected_n_shots |
46
46
  | labs_wait | Wait (blocking, with timeout) for the pending run; finalizes a pending protocol: postprocess, finish write-back, restore |
47
47
  | labs_abort | Abort the current submission |
48
48
  | labs_data | Read the run folder HDF5: first-shot globals and, with scan, a per-shot table |
@@ -84,7 +84,7 @@ Add one row to your DSH profile patch (see examples/config.example.yml):
84
84
  config:
85
85
  python: /path/to/labscript-suite/python
86
86
  backend: labscript
87
- runmanager: { host: 127.0.0.1, port: 42523, timeoutS: 10 }
87
+ # runmanager: { host: 127.0.0.1, port: 42523, timeoutS: 10 } # optional; labconfig otherwise
88
88
  protocolsDir: /path/to/protocols
89
89
  scriptsFile: /path/to/scripts.json
90
90
  hardLimitsFile: /path/to/hard_limits.json
@@ -103,6 +103,20 @@ already mapped: `labs_status`, `labs_engage`, `labs_wait`, `labs_data` and
103
103
  `labs_protocol` show local paths, while `run_folder` keeps the raw runmanager
104
104
  value next to `mapped_run_folder`.
105
105
 
106
+ `runmanager` may be omitted: host, port and timeout then come from labscript's labconfig
107
+ (`[servers] runmanager`, `[ports] runmanager` — default 42523 — and
108
+ `[timeouts] communication_timeout` — default 60 s), the same way `runmanager.remote.Client`
109
+ resolves them. Configured values override labconfig. The Plugins page leaves host/port blank
110
+ for that reason; typing them pins them.
111
+
112
+ `engage` controls how a submitted run is watched: `timeoutS` (default 900) is the
113
+ total wait budget of one `labs_wait`, `noShotS` (default 25) makes the wait return
114
+ early with a diagnosis when no shot appeared, `staleS` (default 180) drops a pending
115
+ submission that never produced a single shot, `pollS`, `settleS` and `stallS` tune the
116
+ polling and the compiled-not-executed detection.
117
+
118
+ engage: { timeoutS: 900, noShotS: 25, staleS: 180, stallS: 20 }
119
+
106
120
  The python interpreter must be able to import numpy and h5py; the real backend
107
121
  also needs runmanager (labscript-suite). The plugin never stores credentials:
108
122
  zprocess authentication is read by labscript itself from labconfig.
@@ -125,8 +139,8 @@ is not a physics simulation.
125
139
  ## Development
126
140
 
127
141
  npm install # only runtime dependency: yaml
128
- npm test # node --test, 40 tests (fake bridge, no python needed)
129
- python python/tests/run_tests.py # 22 tests (needs numpy + h5py)
142
+ npm test # node --test, 47 tests (fake bridge, no python needed)
143
+ python python/tests/run_tests.py # 33 tests (needs numpy + h5py)
130
144
  LABSCRIPT_PYTHON=/path/to/python3 node scripts/demo.mjs # end-to-end mock demo
131
145
 
132
146
  A fresh clone must run `npm install` before `npm test`; the CI workflow does this.
@@ -136,6 +150,20 @@ A fresh clone must run `npm install` before `npm test`; the CI workflow does thi
136
150
  - docs/README.zh.md - Chinese guide
137
151
  - docs/INSTALL.zh.md - local DSH profile install
138
152
 
153
+ ## Terminology
154
+
155
+ The plugin uses short names; the labscript suite uses its own. Both describe the same objects,
156
+ and the field names are unchanged:
157
+
158
+ | plugin | labscript suite |
159
+ |---|---|
160
+ | run folder, `run_folder` | **shot output folder** (one **sequence**'s shot files) |
161
+ | run, engage | one **sequence**: one shot file per point in the parameter space |
162
+ | shot h5, `n_h5` | **shot file** (`.h5`) |
163
+ | scan, scan axis | **parameter space**, **axis**, **expansion** (`none`/`outer`/zip group/`auto`) |
164
+ | `n_executed` | shots that **acquired data** (`images/`, `results/`) |
165
+ | postprocess | lyse **single-shot / multi-shot routine** |
166
+
139
167
  ## Boundaries
140
168
 
141
169
  - Real hardware: engage/protocol run ask for a DSH approval (one-shot) when the
@@ -146,6 +174,23 @@ A fresh clone must run `npm install` before `npm test`; the CI workflow does thi
146
174
  count as 0/1. They are advisory configuration, not a safety interlock.
147
175
  - labs_protocol show warns when a set/scan key is missing from the currently loaded
148
176
  labscript; protocol runs ask for confirmation before proceeding on real hardware.
177
+ - The panel's `n_shots` is the authoritative shot count. The reported scan axes and
178
+ `n_shots_from_arrays` are a heuristic: runmanager expands list/array globals but passes
179
+ tuples through unwrapped, and an expansion setting of `none` turns an array back into a
180
+ non-axis; neither is visible through the remote API. When the two numbers disagree,
181
+ `n_shots` wins and the difference is reported as such.
182
+ - `labs_status` reports `shuffle`. With shuffle on, shot order is not axis order, so each
183
+ shot's parameters must be read from its own h5 globals.
184
+ - Compilation errors appear only in runmanager's output tab. The compiler subprocess writes the
185
+ traceback to its stderr, which runmanager redirects to that tab (an in-memory Qt widget);
186
+ `runmanager.remote` has no interface for it and nothing is written to a log file. The plugin
187
+ therefore reports that no shot file appeared and asks for the tab to be pasted, instead of
188
+ guessing why compilation failed.
189
+ - A shot file exists as soon as a sequence starts; `images/` and `results/` are written only
190
+ after execution, which is what `n_executed` counts. BLACS can keep a shot out of its queue
191
+ (its connection table must be a subset of the lab connection table) or put it back at the head
192
+ after a device programming timeout, and BLACS Repeat all / Repeat last re-runs completed shots
193
+ without creating new files; none of that is visible to the plugin.
149
194
  - Completion is detected from the run folder on the filesystem (HDF5 count,
150
195
  stable file sizes, and a readable last shot); stock labscript has no
151
196
  run-state query.
@@ -153,9 +198,19 @@ A fresh clone must run `npm install` before `npm test`; the CI workflow does thi
153
198
  explicitly when something must stop; labs_abort also cancels an in-flight labs_wait.
154
199
  labscript abort stops compiling/submitting new shots; shots already queued in BLACS
155
200
  or running are not stopped.
156
- - labs_wait reports a diagnosis (complete, no_shots, compiling, compiled_not_executed,
157
- partial). compiled_not_executed means shots exist but show no acquisition data yet;
158
- BLACS may be paused or not executing the queue.
201
+ - labs_wait reports a diagnosis (complete, no_shots, compile_stalled, compiling,
202
+ compiled_not_executed, partial). no_shots and compile_stalled come back before the
203
+ timeout when nothing new appeared for engage.noShotS seconds: runmanager probably
204
+ aborted the compilation, so read its output. compiled_not_executed means shots were
205
+ compiled but show no acquisition data yet; BLACS may be paused or not executing the queue.
206
+ - labs_engage only reports that runmanager accepted the submission; compilation errors are
207
+ invisible to it. A pending submission with no shots at all is dropped after
208
+ engage.staleS seconds and reported as dropped_stale_pending, so a dead run does not block
209
+ the next one forever.
210
+ - Shot h5 files exist before they are executed: progress reports n_h5 (files) and
211
+ n_executed (shots with acquisition data), and completion requires acquisition data.
212
+ - The pending run is stored under the pathMap-mapped folder (the raw runmanager path is kept
213
+ as remote_run_folder), so labs_wait accepts whichever of the two an agent quotes back.
159
214
  - Real runmanager engage does not return a run folder; the bridge captures the
160
215
  output folder before engage and reports it as the run folder. That shot output
161
216
  folder is the NEXT run's folder and does not exist until engage, so labs_data
package/docs/README.zh.md CHANGED
@@ -11,7 +11,7 @@ hard limits 全部通过配置接入。
11
11
  |---|---|
12
12
  | labs_doctor | 只读诊断:python、numpy/h5py、labscript/runmanager、路径、审批、版本 |
13
13
  | labs_status | 读面板:labscript、globals、n_shots、error_in_globals、run_shots、pending 进度 |
14
- | labs_set | 写面板(标量 / raw 表达式 / reps),写前全量快照;不运行 |
14
+ | labs_set | 写面板(标量 / raw 表达式 / reps 可一次混用),写前全量快照;不运行 |
15
15
  | labs_engage | 提交当前面板,立即返回 n_shots_at_submit 与 expected_n_shots |
16
16
  | labs_wait | 等待 pending run(可超时);若是协议则收尾:postprocess → finish 写回 → restore |
17
17
  | labs_abort | 中止当前提交(不排队,不会被 labs_wait 堵住) |
@@ -27,9 +27,12 @@ hard limits 全部通过配置接入。
27
27
  - 要求:DSH >= 0.2、Node ^22.19/24;Python >= 3.9。mock 模式只要 numpy+h5py;
28
28
  真机用 labscript-suite 环境。详见 docs/INSTALL.zh.md。
29
29
  - 默认 backend 是 mock-labscript;真机必须显式配置 backend: labscript。
30
+ - runmanager 的 host/port/timeout 可以不填:不填就读 labscript 的 labconfig
31
+ (`[servers]/[ports] runmanager`,端口默认 42523;`[timeouts] communication_timeout`,
32
+ 默认 60 s),和 runmanager 自己的客户端一致;填了就以配置为准。插件页留空即走 labconfig。
30
33
  - 安装后在 DSH 插件页填 backend、python、runmanager host/port、protocolsDir;
31
- 其余高级配置(scriptsFile、hard limits、postprocess、approval、pathMap)写在
32
- profile 的 cordis.patch.yml。
34
+ 其余高级配置(scriptsFile、hard limits、postprocess、approval、pathMap、
35
+ engage 的超时/早退参数)写在 profile 的 cordis.patch.yml。
33
36
 
34
37
  ## 协议 YAML
35
38
 
@@ -52,6 +55,25 @@ postprocess 是后处理:跑完后调用配置的 Python 函数(entry + pyth
52
55
  - 真机 engage / protocol run 会请求 DSH 审批(一次),abort 和面板写入不请求;
53
56
  插件不是硬件联锁。
54
57
  - labscript abort 只停止编译/提交新的 shot,已进 BLACS 队列或正在跑的 shot 不会被停。
55
- - 完成判定基于 HDF5 数量、大小稳定和最后一发可读;BLACS paused 会报
56
- compiled_not_executed 诊断。
58
+ - 完成判定基于 HDF5 数量、大小稳定和最后一发可读。shot 的 h5 文件在编译阶段就写了出来,
59
+ 所以 labs_status 分别报 h5(文件数)和 executed(真正采到数据的枪数),完成要求最后一发
60
+ 有采集数据;BLACS paused 会报 compiled_not_executed 诊断。
61
+ - 编译 abort 时 labs_wait 不再等满 timeout:距今没有任何新 shot 超过 engage.noShotS
62
+ (默认 25 s)就返回 no_shots / compile_stalled 诊断,去 runmanager 看编译错误;如果只是
63
+ 编译慢,再调一次 labs_wait 继续等。
64
+ - 一条一枪都没产生的 pending 提交,超过 engage.staleS(默认 180 s)会被下一次 labs_engage
65
+ 丢弃并报 dropped_stale_pending,不再永久挡住提交;RUN_PENDING 的错误里也会写清年龄和两种出路。
66
+ - 配了 pathMap 时 pending 记录映射后的目录(原始路径保留为 remote_run_folder),所以
67
+ labs_wait 传映射后目录或原始目录都能对上,不会再说"run folder does not exist"。
68
+ - **shot 数以面板 `n_shots` 为准**。工具报的 scan axes / n_shots_from_arrays 只是猜测:
69
+ runmanager 会把 list/array 展开成轴,但 **tuple 不展开**,而且 expansion 被设成 `none` 时
70
+ 数组就不再是轴——这两件事从 remote 接口都看不到。两个数字不一致时按 `n_shots` 报,并说明差别。
71
+ - `labs_status` 会显示 `shuffle`。shuffle 打开时 shot 顺序不等于轴顺序,每发参数要从该发 h5 的
72
+ globals 读,不要按序号推。
73
+ - 编译报错只出现在 runmanager 的 output tab:编译器子进程把回溯写到自己的 stderr,runmanager
74
+ 把它转到这个 Qt 界面上(内存里,不落盘),`runmanager.remote` 没有任何读取接口。所以插件只能报
75
+ "n 秒内没有出现 shot 文件",并请操作员贴 output tab,不猜原因。
76
+ - shot 文件在 sequence 开始时就写出来了;`images/`、`results/` 是执行后才写,`n_executed`
77
+ 数的就是这个。BLACS 可能因为连接表不是实验室连接表的子集而拒收、或设备编程超时把 shot 放回队首,
78
+ BLACS 的 Repeat all / Repeat last 会把跑完的 shot 重跑而不新增文件——这些插件都看不到。
57
79
 
@@ -19,7 +19,9 @@
19
19
  # config:
20
20
  # python: /path/to/labscript-suite/python
21
21
  # backend: labscript
22
- # runmanager: { host: 127.0.0.1, port: 42523, timeoutS: 10 }
22
+ # # runmanager 可以不写:host/port/timeout 默认读 labscript 的 labconfig
23
+ # # ([servers]/[ports]/[timeouts],端口默认 42523、超时默认 60 s),填了才覆盖。
24
+ # # runmanager: { host: 127.0.0.1, port: 42523, timeoutS: 10 }
23
25
  # protocolsDir: /path/to/your/protocols
24
26
  # scriptsFile: /path/to/scripts.json
25
27
  # hardLimitsFile: /path/to/hard_limits.json
package/lib/client.js CHANGED
@@ -78,8 +78,8 @@ window.__ModuleLoader__.load({
78
78
  const [version, setVersion] = useState('')
79
79
  const [backend, setBackend] = useState('mock-labscript')
80
80
  const [python, setPython] = useState('')
81
- const [host, setHost] = useState('127.0.0.1')
82
- const [port, setPort] = useState(42523)
81
+ const [host, setHost] = useState('')
82
+ const [port, setPort] = useState('')
83
83
  const [protocolsDir, setProtocolsDir] = useState('')
84
84
  const [busy, setBusy] = useState('')
85
85
  const [message, setMessage] = useState(null)
@@ -94,8 +94,8 @@ window.__ModuleLoader__.load({
94
94
  const current = payload.config || {}
95
95
  setBackend(current.backend || 'mock-labscript')
96
96
  setPython(current.python || '')
97
- setHost((current.runmanager && current.runmanager.host) || '127.0.0.1')
98
- setPort((current.runmanager && current.runmanager.port) || 42523)
97
+ setHost((current.runmanager && current.runmanager.host) || '')
98
+ setPort(current.runmanager && current.runmanager.port ? String(current.runmanager.port) : '')
99
99
  setProtocolsDir(current.protocolsDir || '')
100
100
  } catch (error) {
101
101
  setMessage({ kind: 'err', text: error.message })
@@ -118,7 +118,7 @@ window.__ModuleLoader__.load({
118
118
  body: JSON.stringify({
119
119
  backend,
120
120
  python,
121
- runmanager: { host, port: Number(port) },
121
+ runmanager: { host: host.trim(), port: port === '' ? '' : Number(port) },
122
122
  protocolsDir,
123
123
  }),
124
124
  })
@@ -160,8 +160,8 @@ window.__ModuleLoader__.load({
160
160
  ),
161
161
  ),
162
162
  h(Row, { label: 'python', hint: '真机填 labscript-suite 环境的 python;模拟器只需 numpy+h5py。' }, h('input', { type: 'text', value: python, onChange: (event) => setPython(event.target.value) })),
163
- h(Row, { label: 'runmanager host' }, h('input', { type: 'text', value: host, onChange: (event) => setHost(event.target.value) })),
164
- h(Row, { label: 'runmanager port' }, h('input', { type: 'number', value: port, onChange: (event) => setPort(event.target.value) })),
163
+ h(Row, { label: 'runmanager host', hint: '留空则用 labscript 的 labconfig([servers] runmanager)。' }, h('input', { type: 'text', value: host, placeholder: 'labconfig 里的地址', onChange: (event) => setHost(event.target.value) })),
164
+ h(Row, { label: 'runmanager port', hint: '留空则用 labconfig([ports] runmanager,默认 42523)。' }, h('input', { type: 'number', value: port, placeholder: 'labconfig', onChange: (event) => setPort(event.target.value) })),
165
165
  ),
166
166
  h('section', { className: 'lsc-section' },
167
167
  h('h3', null, '实验'),
package/lib/skill.js CHANGED
@@ -10,8 +10,11 @@ export const SKILL_CONTENT = [
10
10
  'not your memory, is the truth. Never guess a global value or a scan axis.',
11
11
  '',
12
12
  '## Before any write',
13
- '1. Call labs_status. Confirm the loaded labscript, n_shots, error_in_globals, run_shots',
14
- ' and the keys you are about to touch.',
13
+ '1. Call labs_status. Confirm the loaded labscript, n_shots, error_in_globals, run_shots,',
14
+ ' shuffle and the keys you are about to touch. n_shots is the authoritative shot count:',
15
+ ' the scan axes line is only a heuristic (runmanager does not expand tuples, and an',
16
+ ' expansion setting of none makes an array a non-axis), so report a difference instead',
17
+ ' of recomputing the count yourself.',
15
18
  '2. Real hardware: the user must approve this turn before panel writes, engage or abort.',
16
19
  ' The plugin asks for its own one-shot approval on engage/protocol run in the real',
17
20
  ' backend; that approval does not replace the user conversation requirement.',
@@ -21,9 +24,19 @@ export const SKILL_CONTENT = [
21
24
  '## Running',
22
25
  '- labs_set writes the panel. It does not run anything. Always pass note: one sentence',
23
26
  ' saying what this batch is for (it is written to the event log).',
27
+ '- One engage submits one sequence: runmanager writes one shot file per point into the',
28
+ ' shot output folder (called run folder in these tools) and hands each file to BLACS.',
24
29
  '- labs_engage submits a run and returns immediately with n_shots_at_submit and',
25
30
  ' expected_n_shots. Do not poll in a loop; when the user wants results, call labs_wait.',
26
31
  ' After submitting you may end the turn or do independent work.',
32
+ '- labs_engage cannot see compilation errors: runmanager shows them only in its output',
33
+ ' tab, which runmanager.remote cannot read. If nothing runs, see Stop rules.',
34
+ '- n_h5 counts shot files, which labscript writes before compiling each shot; only',
35
+ ' n_executed (or the diagnosis) says whether shots actually acquired data.',
36
+ '- shuffle true means shot order is not axis order: read each shot parameters from its',
37
+ ' own h5 globals, never from the shot index.',
38
+ '- Never name a global like a Python builtin or a name from "from labscript import *":',
39
+ ' runmanager accepts it, labscript fails at compile time, and only the output tab shows it.',
27
40
  '- labs_protocol run submits a fixed protocol; labs_wait finalizes it (postprocess,',
28
41
  ' finish write-back, panel restore).',
29
42
  '- backend defaults to mock-labscript; real hardware is only used when the config says',
@@ -36,15 +49,28 @@ export const SKILL_CONTENT = [
36
49
  ' write touched (pass full=true for the whole panel), so concurrent edits elsewhere are kept.',
37
50
  '',
38
51
  '## Reading results',
39
- '- labs_data reads the run folder HDF5: globals of the first shot and, with scan: a',
40
- ' per-shot table. HDF5 is authoritative; the panel is only intermediate state.',
52
+ '- labs_data reads the shot files HDF5: globals of the first shot and, with scan: a',
53
+ ' per-shot table. HDF5 is authoritative; the panel is only intermediate state. images/',
54
+ ' and results/ are written only after execution.',
41
55
  '- Report facts: run folder, expected vs actual shots, postprocess numbers, warnings.',
42
56
  ' If a number is missing, say so instead of inventing one.',
43
57
  '',
44
58
  '## Stop rules',
45
59
  '- error_in_globals true: fix or ask; never engage.',
46
- '- wait timed out or diagnosis=compiled_not_executed: the shots may be compiled but BLACS',
47
- ' may be paused/not executing. Check BLACS and the panel, then abort or ask; do not resubmit blindly.',
60
+ '- labs_wait returns before the timeout with diagnosis no_shots or compile_stalled when no',
61
+ ' shot file appeared for config.engage.noShotS seconds (default 25). Compilation errors are',
62
+ ' visible only in runmanager output tab, which this tool cannot read: say exactly that, ask',
63
+ ' the operator to paste the tab, and do not guess the cause or resubmit blindly. Call',
64
+ ' labs_wait again only if you expect a slow compile. A submission that never produced a shot',
65
+ ' is dropped by the next labs_engage once it is config.engage.staleS seconds old (default',
66
+ ' 180) and reported as dropped_stale_pending.',
67
+ '- diagnosis=compiled_not_executed, or timed_out with shot files but no acquisition data:',
68
+ ' BLACS did not run that shot. It may be paused, may have refused the shot because its',
69
+ ' connection table is not a subset of the lab connection table, or a device group may have',
70
+ ' failed to program within 5 minutes (BLACS then pauses the queue and puts the shot back at',
71
+ ' its head). None of this is visible to this tool: look at the BLACS GUI, then abort or ask.',
72
+ '- More shots finished than expected: check the panel n_shots and expansion settings first,',
73
+ ' then BLACS Repeat all / Repeat last, which re-run existing shot files.',
48
74
  '- A run is already pending: wait or abort before submitting another.',
49
75
  ].join(String.fromCharCode(10))
50
76
 
package/lib/store.mjs CHANGED
@@ -34,8 +34,13 @@ export function normalizeUiConfig(input) {
34
34
  throw new Error('backend must be one of ' + BACKENDS.join(', ') + ', got: ' + backend)
35
35
  }
36
36
  const runmanager = source.runmanager && typeof source.runmanager === 'object' ? source.runmanager : {}
37
- const port = Number(runmanager.port || 42523)
38
- if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error('runmanager.port must be 1-65535')
37
+ // host/port stay optional: when they are not stored, the bridge lets runmanager.remote
38
+ // resolve them from labscript's labconfig ([servers]/[ports] runmanager), the same way
39
+ // the runmanager GUI does. Storing the old hardcoded defaults here would pin them.
40
+ const host = runmanager.host === undefined || runmanager.host === null ? '' : String(runmanager.host).trim()
41
+ const portText = runmanager.port === undefined || runmanager.port === null ? '' : String(runmanager.port).trim()
42
+ const port = portText === '' ? undefined : Number(portText)
43
+ if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535)) throw new Error('runmanager.port must be 1-65535')
39
44
  const timeoutS = runmanager.timeoutS === undefined || runmanager.timeoutS === null || runmanager.timeoutS === ''
40
45
  ? undefined
41
46
  : Number(runmanager.timeoutS)
@@ -45,12 +50,11 @@ export function normalizeUiConfig(input) {
45
50
  const out = {
46
51
  backend,
47
52
  python: String(source.python || '').trim(),
48
- runmanager: {
49
- host: String(runmanager.host || '127.0.0.1').trim(),
50
- port,
51
- },
53
+ runmanager: {},
52
54
  protocolsDir: String(source.protocolsDir || '').trim(),
53
55
  }
56
+ if (host !== '') out.runmanager.host = host
57
+ if (port !== undefined) out.runmanager.port = port
54
58
  if (timeoutS !== undefined) out.runmanager.timeoutS = timeoutS
55
59
  return out
56
60
  }
@@ -75,9 +79,13 @@ export function readUiConfig(home) {
75
79
  export function writeUiConfig(input, home) {
76
80
  const previous = readUiConfig(home).config || {}
77
81
  const patch = input && typeof input === 'object' ? input : {}
78
- const config = normalizeUiConfig(Object.assign({}, previous, patch, {
79
- runmanager: Object.assign({}, previous.runmanager || {}, patch.runmanager || {}),
80
- }))
82
+ const runmanager = Object.assign({}, previous.runmanager || {}, patch.runmanager || {})
83
+ // An empty host/port means "unset, ask labconfig": drop the stored key instead of
84
+ // keeping the previous value, so a lab that moves runmanager can clear it.
85
+ for (const key of Object.keys(runmanager)) {
86
+ if (runmanager[key] === '' || runmanager[key] === null || runmanager[key] === undefined) delete runmanager[key]
87
+ }
88
+ const config = normalizeUiConfig(Object.assign({}, previous, patch, { runmanager }))
81
89
  const file = uiConfigPath(home)
82
90
  fs.mkdirSync(path.dirname(file), { recursive: true })
83
91
  const temp = file + '.' + process.pid + '.' + Date.now() + '.tmp'