obsbot-mcp 0.3.0 → 0.4.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/README.md +197 -38
- package/dist/codec/commands.d.ts +42 -4
- package/dist/codec/commands.js +72 -10
- package/dist/codec/commands.js.map +1 -1
- package/dist/codec/frame.d.ts +1 -0
- package/dist/codec/frame.js +1 -1
- package/dist/codec/frame.js.map +1 -1
- package/dist/codec/preset.d.ts +40 -0
- package/dist/codec/preset.js +198 -0
- package/dist/codec/preset.js.map +1 -0
- package/dist/codec/types.d.ts +4 -0
- package/dist/device/helper-factory.d.ts +25 -0
- package/dist/device/helper-factory.js +40 -0
- package/dist/device/helper-factory.js.map +1 -0
- package/dist/device/manager.d.ts +326 -2
- package/dist/device/manager.js +720 -24
- package/dist/device/manager.js.map +1 -1
- package/dist/ipc/client.d.ts +19 -0
- package/dist/ipc/client.js +81 -0
- package/dist/ipc/client.js.map +1 -0
- package/dist/ipc/coordinator.d.ts +29 -0
- package/dist/ipc/coordinator.js +94 -0
- package/dist/ipc/coordinator.js.map +1 -0
- package/dist/ipc/owner.d.ts +21 -0
- package/dist/ipc/owner.js +59 -0
- package/dist/ipc/owner.js.map +1 -0
- package/dist/ipc/protocol.d.ts +21 -0
- package/dist/ipc/protocol.js +56 -0
- package/dist/ipc/protocol.js.map +1 -0
- package/dist/ipc/rendezvous.d.ts +26 -0
- package/dist/ipc/rendezvous.js +93 -0
- package/dist/ipc/rendezvous.js.map +1 -0
- package/dist/mcp/log-sink.d.ts +12 -0
- package/dist/mcp/log-sink.js +27 -0
- package/dist/mcp/log-sink.js.map +1 -0
- package/dist/mcp/ready.d.ts +16 -4
- package/dist/mcp/ready.js +11 -8
- package/dist/mcp/ready.js.map +1 -1
- package/dist/mcp/render.js +1 -1
- package/dist/mcp/render.js.map +1 -1
- package/dist/mcp/server.js +68 -18
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts +5 -3
- package/dist/mcp/tools.js +787 -186
- package/dist/mcp/tools.js.map +1 -1
- package/dist/transport/helper-process.d.ts +36 -0
- package/dist/transport/helper-process.js +190 -10
- package/dist/transport/helper-process.js.map +1 -1
- package/dist/transport/linux.d.ts +40 -0
- package/dist/transport/linux.js +92 -2
- package/dist/transport/linux.js.map +1 -1
- package/dist/transport/macos.d.ts +13 -0
- package/dist/transport/macos.js +59 -3
- package/dist/transport/macos.js.map +1 -1
- package/dist/transport/read-serial.d.ts +26 -0
- package/dist/transport/read-serial.js +87 -0
- package/dist/transport/read-serial.js.map +1 -0
- package/dist/transport/transport.d.ts +24 -0
- package/dist/transport/windows.d.ts +4 -0
- package/dist/transport/windows.js +31 -4
- package/dist/transport/windows.js.map +1 -1
- package/native/prebuilt/darwin-arm64/obsbot-helper +0 -0
- package/native/prebuilt/darwin-x64/obsbot-helper +0 -0
- package/native/prebuilt/linux-x64/obsbot-helper +0 -0
- package/native/prebuilt/win32-x64/obsbot-helper.exe +0 -0
- package/package.json +3 -2
- package/dist/device/session.d.ts +0 -22
- package/dist/device/session.js +0 -37
- package/dist/device/session.js.map +0 -1
package/dist/device/manager.d.ts
CHANGED
|
@@ -1,10 +1,334 @@
|
|
|
1
1
|
import { DeviceInfo } from "../codec/types.js";
|
|
2
2
|
import { HelperProcess } from "../transport/helper-process.js";
|
|
3
3
|
import { ObsbotTransport } from "../transport/transport.js";
|
|
4
|
+
/** Thrown by get() with no selector when more than one camera is attached. */
|
|
5
|
+
export declare class AmbiguousCameraError extends Error {
|
|
6
|
+
readonly available: string[];
|
|
7
|
+
constructor(available: string[]);
|
|
8
|
+
}
|
|
9
|
+
/** Thrown by get(serial) when no attached, bindable camera has that serial. */
|
|
10
|
+
export declare class UnknownCameraError extends Error {
|
|
11
|
+
readonly available: string[];
|
|
12
|
+
constructor(serial: string, available: string[]);
|
|
13
|
+
}
|
|
14
|
+
export interface CameraInfo {
|
|
15
|
+
/** Present only for cameras this process could open and identify. */
|
|
16
|
+
serial?: string;
|
|
17
|
+
/** macOS: USB locationID. A handle for correlation/display only — never identity. */
|
|
18
|
+
locationId?: number;
|
|
19
|
+
name: string;
|
|
20
|
+
status: "available" | "bound" | "busy";
|
|
21
|
+
/**
|
|
22
|
+
* Why a `busy` camera could not be identified — the underlying open or
|
|
23
|
+
* readSerial error. Absent on cameras that bound fine. "busy" covers two very
|
|
24
|
+
* different situations (another process holds the device vs. the vendor
|
|
25
|
+
* mailbox went quiet) and without this they are indistinguishable from
|
|
26
|
+
* outside.
|
|
27
|
+
*/
|
|
28
|
+
reason?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Serial-keyed multi-camera registry. Identity is the serial (read via
|
|
32
|
+
* ObsbotTransport.readSerial() on a freshly-opened device); locationId is
|
|
33
|
+
* only ever a display/correlation hint, never persisted as truth for
|
|
34
|
+
* binding.
|
|
35
|
+
*
|
|
36
|
+
* Steady state is one HelperProcess per bound camera (Map<serial, {helper,
|
|
37
|
+
* transport, locationId}>), spawned lazily — nothing is spawned until a
|
|
38
|
+
* camera is actually needed (get() or listCameras()). While scanning for a
|
|
39
|
+
* camera to bind, candidates are tried on a single scratch helper reused
|
|
40
|
+
* across attempts, mirroring the native helper's own behaviour: `doOpen`
|
|
41
|
+
* unconditionally releases whatever device it previously held before
|
|
42
|
+
* opening the next one, so re-opening a different candidate on the same
|
|
43
|
+
* helper is exactly what one physical device-swap looks like to it. A
|
|
44
|
+
* scratch helper that turns out to be the winning candidate is promoted
|
|
45
|
+
* directly into the registry (no extra spawn); one that never opens a
|
|
46
|
+
* usable camera is kept around for the next scan instead of being spawned
|
|
47
|
+
* again. Consequently DeviceManager never closes a helper it did not spawn
|
|
48
|
+
* itself for a *losing* attempt — ownership of a bound camera's helper
|
|
49
|
+
* transfers to the registry, and callers that hand in an already-started,
|
|
50
|
+
* externally-owned helper (see the .mjs scripts) keep owning its shutdown.
|
|
51
|
+
*
|
|
52
|
+
* USB open is exclusive: a camera another process holds fails `open` with
|
|
53
|
+
* an exclusive-access error. Per multi-camera spec §4.3, ANY open failure
|
|
54
|
+
* during a scan is treated as "not mine, skip" — not fatal — because the
|
|
55
|
+
* helper does not currently surface a clean, stable way to distinguish
|
|
56
|
+
* exclusive-access from other open failures at this layer.
|
|
57
|
+
*
|
|
58
|
+
* invalidate() is the escape hatch out of "once bound, stay bound": it
|
|
59
|
+
* drops a registry entry (best-effort closing its helper first) so the
|
|
60
|
+
* next get()/bind() re-scans through a fresh helper instead of handing back
|
|
61
|
+
* a transport that may be talking to a device that already re-enumerated.
|
|
62
|
+
*/
|
|
4
63
|
export declare class DeviceManager {
|
|
5
|
-
private
|
|
6
|
-
|
|
64
|
+
private makeHelper;
|
|
65
|
+
private registry;
|
|
66
|
+
/** Lazily spawned, reused across scans until promoted into the registry. */
|
|
67
|
+
private scanHelper?;
|
|
68
|
+
/**
|
|
69
|
+
* Long-lived listener. Holds no device and never scans for one — its only job
|
|
70
|
+
* is to still be alive, and still subscribed, when the cable moves.
|
|
71
|
+
*/
|
|
72
|
+
private watcher?;
|
|
73
|
+
/**
|
|
74
|
+
* Reconnect tracking, folded in from the retired DeviceSession. `everBound`
|
|
75
|
+
* records every serial this manager has bound at least once — deliberately
|
|
76
|
+
* SEPARATE from the registry Map so it survives invalidate() dropping a
|
|
77
|
+
* registry entry (the registry says "bound right now", this says "bound at
|
|
78
|
+
* some point"). A promote() of a serial already in `everBound` is therefore a
|
|
79
|
+
* RE-bind — a self-heal after a mid-session disconnect — and lands the serial
|
|
80
|
+
* in `reconnectedSerials`, which the readiness gate drains via
|
|
81
|
+
* takeReconnected() to surface `reconnected: true` on the next command.
|
|
82
|
+
*/
|
|
83
|
+
private everBound;
|
|
84
|
+
private reconnectedSerials;
|
|
85
|
+
/**
|
|
86
|
+
* @param makeHelper Factory for a HelperProcess, invoked whenever a fresh
|
|
87
|
+
* scratch helper is needed (each scan, and each rebind after
|
|
88
|
+
* invalidate()). CONTRACT: it MUST return a freshly-started,
|
|
89
|
+
* exclusively-owned HelperProcess on every call — DeviceManager may call
|
|
90
|
+
* it repeatedly across a session and always expects a clean handle, not
|
|
91
|
+
* a shared one. A factory that instead returns the SAME already-started
|
|
92
|
+
* helper on every call (as the single-camera `.mjs` scripts under
|
|
93
|
+
* scripts/ do, to keep one native session alive for their whole run)
|
|
94
|
+
* MUST NOT bind more than one serial through this manager: a second
|
|
95
|
+
* scan would hand that same shared helper back and silently steal the
|
|
96
|
+
* first bound camera's native session out from under it. No current
|
|
97
|
+
* caller does this (all are single-camera); this is latent and not
|
|
98
|
+
* guarded in code — the proper fix belongs to multi-camera .mjs/CLI
|
|
99
|
+
* wiring, not to DeviceManager itself.
|
|
100
|
+
*/
|
|
101
|
+
/** True while an arrival re-bind ladder is running (see handleCameraArrived). */
|
|
102
|
+
private rebinding;
|
|
103
|
+
/**
|
|
104
|
+
* Delays before each arrival re-bind attempt, in order — so four attempts
|
|
105
|
+
* spanning ~4.5s, the first immediate.
|
|
106
|
+
*
|
|
107
|
+
* Sized from hardware, 2026-07-21: for many seconds after a USB
|
|
108
|
+
* re-enumeration the Tiny 2's vendor mailbox is intermittently not-ready
|
|
109
|
+
* (the reply slot reads back with its magic byte zeroed), and readSerial's
|
|
110
|
+
* own 8 x 30ms poll is too short to ride it out. Measured 22 failures in 80
|
|
111
|
+
* attempts across the first 14s after a replug, against 0 in 120 in steady
|
|
112
|
+
* state — so a single attempt on arrival is roughly a 1-in-4 coin flip, and
|
|
113
|
+
* losing it silently leaves the camera unbound until the user's next call.
|
|
114
|
+
* Four spread attempts make a lost re-bind unlikely without retrying forever
|
|
115
|
+
* at a camera that is simply not answering.
|
|
116
|
+
*/
|
|
117
|
+
private arrivalBackoffMs;
|
|
118
|
+
/**
|
|
119
|
+
* Where a failed or retried arrival re-bind is reported. STDERR by default —
|
|
120
|
+
* never stdout, which is the JSON-RPC channel.
|
|
121
|
+
*
|
|
122
|
+
* Exists because the ladder was silent: a ladder that fired and a clean first
|
|
123
|
+
* attempt looked identical from outside the process, so the retry could not
|
|
124
|
+
* be observed on hardware even once it existed. A self-heal that fails
|
|
125
|
+
* quietly is the failure mode that cost a day of debugging.
|
|
126
|
+
*/
|
|
127
|
+
private log;
|
|
128
|
+
constructor(makeHelper: () => Promise<HelperProcess>, opts?: {
|
|
129
|
+
arrivalBackoffMs?: number[];
|
|
130
|
+
log?: (msg: string) => void;
|
|
131
|
+
});
|
|
7
132
|
private createTransport;
|
|
133
|
+
/**
|
|
134
|
+
* Close and forget the scratch helper. Closing matters as much as forgetting:
|
|
135
|
+
* the process is alive and holding whatever it opened, so dropping only the
|
|
136
|
+
* reference leaks it and leaves the replacement unable to open the camera —
|
|
137
|
+
* the same mistake pruneDeadEntries() makes if it merely deletes an entry.
|
|
138
|
+
*/
|
|
139
|
+
private discardScanHelper;
|
|
140
|
+
/**
|
|
141
|
+
* Keep one helper alive purely to receive bus events.
|
|
142
|
+
*
|
|
143
|
+
* Bus events are delivered per PROCESS (helperFactory subscribes each one it
|
|
144
|
+
* spawns), and every process that could be listening gets closed: a departure
|
|
145
|
+
* closes the registry helper, and a failed bind closes the scratch scanner.
|
|
146
|
+
* Since promote() clears `scanHelper`, the bound steady state is exactly ONE
|
|
147
|
+
* live helper — the registry's — so handleCameraDeparted() alone drops the
|
|
148
|
+
* subscriber count to zero. The arrival that follows is then delivered to
|
|
149
|
+
* nobody and the camera stays unbound until a tool call fails against it.
|
|
150
|
+
*
|
|
151
|
+
* PRIMING IS NOT OPTIONAL. A helper that has never enumerated receives
|
|
152
|
+
* nothing. Measured on hardware (darwin-arm64, Tiny 2, 2026-07-21) across one
|
|
153
|
+
* same-port replug, three processes, none of which opened the device:
|
|
154
|
+
*
|
|
155
|
+
* enumerated while camera present -> departure YES, arrival YES
|
|
156
|
+
* never enumerated -> departure NO, arrival NO
|
|
157
|
+
* enumerated while camera absent -> arrival YES
|
|
158
|
+
*
|
|
159
|
+
* The silent one stayed alive the whole run and answered a later enumerate
|
|
160
|
+
* correctly, so that was genuine non-delivery. Its first enumerate took 71ms
|
|
161
|
+
* against ~1ms for the primed pair — AVFoundation's discovery subsystem
|
|
162
|
+
* starting up for the first time. Registering the observers
|
|
163
|
+
* (helper.m:registerCameraNotifications) is NOT what starts delivery;
|
|
164
|
+
* touching the device list is.
|
|
165
|
+
*
|
|
166
|
+
* Windows enforces the same rule for an unrelated reason: helper.cpp drops
|
|
167
|
+
* any event whose path is missing from `g_knownPaths`, which only enumerate
|
|
168
|
+
* fills, and that cache is per-process. There the path must have been seen
|
|
169
|
+
* WHILE THE CAMERA WAS PRESENT — an enumerate during an absence cannot cache
|
|
170
|
+
* it. macOS has no such constraint: an arm primed during an absence received
|
|
171
|
+
* the arrival in the same millisecond as one primed while present.
|
|
172
|
+
*
|
|
173
|
+
* Both halves are now MEASURED, one platform each, same three-arm experiment:
|
|
174
|
+
*
|
|
175
|
+
* primed-present never-primed primed-during-absence
|
|
176
|
+
* macOS yes NO yes
|
|
177
|
+
* Windows yes NO NO
|
|
178
|
+
*
|
|
179
|
+
* The Windows run logged its own mechanism rather than inferring it — the
|
|
180
|
+
* absent-camera enumerate recorded "Tiny 2 in its list = false", so there was
|
|
181
|
+
* no path to cache and the arrival died at helper.cpp:1022.
|
|
182
|
+
*
|
|
183
|
+
* PRIMING REPEATS ON EVERY CALL, and that is Windows insurance specifically.
|
|
184
|
+
* macOS does NOT need it — measured across two full replug cycles, a watcher
|
|
185
|
+
* primed once at startup kept delivering (arrived=2, departed=2), identical
|
|
186
|
+
* to one that re-primed after every departure. The prime is a one-time
|
|
187
|
+
* activation there, not a lease. On Windows a watcher whose predecessor DIED
|
|
188
|
+
* during an absence gets replaced while the camera is gone, never caches the
|
|
189
|
+
* path, and never self-corrects, because the guard below would otherwise
|
|
190
|
+
* early-return on a watcher that is alive and useless. Hardware-confirmed on
|
|
191
|
+
* Windows: that scenario ends `available`, never `bound`, with no arrival ever
|
|
192
|
+
* seen — where the same code on macOS recovers.
|
|
193
|
+
*
|
|
194
|
+
* So the repeat only helps at a moment the camera is PRESENT. Re-priming
|
|
195
|
+
* during an absence is a no-op on Windows; only the next successful bind can
|
|
196
|
+
* cure a deaf watcher there.
|
|
197
|
+
*
|
|
198
|
+
* Costs one idle process once a camera has been bound. It opens nothing, so
|
|
199
|
+
* it holds no exclusive USB handle and the camera stays available to Zoom,
|
|
200
|
+
* OBS and OBSBOT Center — the constraint that shaped handleCameraArrived.
|
|
201
|
+
*/
|
|
202
|
+
private ensureWatcher;
|
|
203
|
+
private getScanHelper;
|
|
204
|
+
/**
|
|
205
|
+
* Compat: raw enumerate() pass-through (unfiltered, no serials). Used by
|
|
206
|
+
* obsbot_capture_snapshot's virtual/ndi source lookup, not by obsbot_devices
|
|
207
|
+
* (which reports the identified fleet via listCameras() instead).
|
|
208
|
+
*/
|
|
8
209
|
list(): Promise<DeviceInfo[]>;
|
|
210
|
+
/**
|
|
211
|
+
* Scan attached candidates on the scratch helper for a camera to bind.
|
|
212
|
+
*
|
|
213
|
+
* - `wantSerial` given: stop at the first candidate whose serial matches;
|
|
214
|
+
* throws UnknownCameraError (listing every identifiable serial seen)
|
|
215
|
+
* if the scan exhausts without a match.
|
|
216
|
+
* - `wantSerial` omitted: every candidate must be probed so the full
|
|
217
|
+
* fleet is known — a partial scan could under-report ambiguity or
|
|
218
|
+
* silently pick the wrong "only" camera. Binds if exactly one distinct
|
|
219
|
+
* serial turns up; throws AmbiguousCameraError (all distinct serials)
|
|
220
|
+
* if more than one does; throws if none do.
|
|
221
|
+
*/
|
|
222
|
+
private bind;
|
|
223
|
+
/** Move the current scratch helper into the registry under `m.serial`. */
|
|
224
|
+
private promote;
|
|
225
|
+
/**
|
|
226
|
+
* Whether the camera identified by `serial` (or, with no arg, the single
|
|
227
|
+
* bound camera) was RE-bound after a prior bind — i.e. self-healed across a
|
|
228
|
+
* mid-session disconnect — clearing that flag on read. Single-camera
|
|
229
|
+
* semantics (no serial): return true if ANY serial is pending-reconnected,
|
|
230
|
+
* draining them all. Preserves the exact contract the retired
|
|
231
|
+
* DeviceSession.takeReconnected() had.
|
|
232
|
+
*/
|
|
233
|
+
takeReconnected(serial?: string): boolean;
|
|
234
|
+
/**
|
|
235
|
+
* Drop bound camera(s) from the registry so the next get()/bind() spawns a
|
|
236
|
+
* fresh scratch helper and re-scans from scratch — the correct move after
|
|
237
|
+
* a device has re-enumerated (e.g. unplug/replug), since a stale cached
|
|
238
|
+
* transport talks to a helper that may no longer own a live handle.
|
|
239
|
+
* Closing each dropped helper is best-effort: a helper whose native
|
|
240
|
+
* session already died (the common case right after a disconnect) can
|
|
241
|
+
* throw on close(), and that must not stop the entry from being dropped.
|
|
242
|
+
*
|
|
243
|
+
* serial given -> drop just that camera (no-op if it isn't bound)
|
|
244
|
+
* no serial -> drop every bound camera
|
|
245
|
+
*/
|
|
246
|
+
invalidate(serial?: string): Promise<void>;
|
|
247
|
+
/**
|
|
248
|
+
* Drop registry entries whose helper process has died, so the next resolve
|
|
249
|
+
* re-binds instead of handing back a transport that can only ever fail.
|
|
250
|
+
*
|
|
251
|
+
* "Once bound, stay bound" is the right default, but a bound entry whose
|
|
252
|
+
* helper is GONE isn't a binding — it's a corpse. Without this, only the
|
|
253
|
+
* tools that route through ensureReady() (which calls invalidate()) could
|
|
254
|
+
* recover; every other tool resolved straight through get() and returned the
|
|
255
|
+
* same dead transport forever. Recovery then required the caller to happen to
|
|
256
|
+
* invoke a different tool — and the caller here is an LLM, which will instead
|
|
257
|
+
* retry the failing tool or report broken hardware that is actually fine.
|
|
258
|
+
*
|
|
259
|
+
* Deliberately a `dead` check and not a liveness probe: it costs a boolean,
|
|
260
|
+
* adds no round trip to the hot path, and leaves a LIVE binding untouched (a
|
|
261
|
+
* guard that re-scanned every call would spawn a helper per tool call).
|
|
262
|
+
*
|
|
263
|
+
* A wedged-but-alive helper is intentionally NOT caught here — that is the
|
|
264
|
+
* per-request timeout's job, and condemning a session for one slow op would
|
|
265
|
+
* throw away a working binding.
|
|
266
|
+
*/
|
|
267
|
+
private pruneDeadEntries;
|
|
268
|
+
/**
|
|
269
|
+
* Resolve to a bound transport.
|
|
270
|
+
* no serial + one camera attached -> bind & return it
|
|
271
|
+
* no serial + several attached -> AmbiguousCameraError
|
|
272
|
+
* serial given + match -> bind (lazily) & return
|
|
273
|
+
* serial given + no match -> UnknownCameraError
|
|
274
|
+
* Already-bound cameras are returned directly from the registry without
|
|
275
|
+
* rescanning — "bind lazily" means once bound, stay bound.
|
|
276
|
+
*/
|
|
277
|
+
get(serial?: string): Promise<ObsbotTransport>;
|
|
278
|
+
/**
|
|
279
|
+
* The OS reported a camera detached. Drop any binding on that path now,
|
|
280
|
+
* instead of waiting for a tool call to fail against a dead handle.
|
|
281
|
+
*
|
|
282
|
+
* Without this the registry stays authoritative until something errors, so
|
|
283
|
+
* `obsbot_devices` reports a phantom `bound` entry — serial and all — for a
|
|
284
|
+
* camera physically sitting on the desk. The prune is otherwise reactive:
|
|
285
|
+
* `deviceLost` is only set when an op fails, and listCameras() deliberately
|
|
286
|
+
* does not re-open bound entries to check.
|
|
287
|
+
*
|
|
288
|
+
* Closes the helper rather than merely forgetting it, for the same reason
|
|
289
|
+
* pruneDeadEntries() does: a helper left running still holds the device, so
|
|
290
|
+
* the replacement could never open it.
|
|
291
|
+
*/
|
|
292
|
+
handleCameraDeparted(e: {
|
|
293
|
+
path: string;
|
|
294
|
+
}): Promise<void>;
|
|
295
|
+
/**
|
|
296
|
+
* The OS reported a camera attached. Re-establish a binding this process
|
|
297
|
+
* ALREADY held — a genuine self-heal after a replug — and nothing more.
|
|
298
|
+
*
|
|
299
|
+
* Deliberately does not bind a camera we never had. The Tiny 2 is a device
|
|
300
|
+
* Zoom, OBS and OBSBOT Center also want, and a server that grabbed it the
|
|
301
|
+
* moment it appeared would make it busy for them even if nobody ever used
|
|
302
|
+
* this server. `everBound` is the record of what was ours; it survives
|
|
303
|
+
* invalidate() precisely so a re-bind can be recognised as a reconnect.
|
|
304
|
+
*
|
|
305
|
+
* Never throws: the caller is a stdout line handler, and an unhandled
|
|
306
|
+
* rejection there would take down the reader and wedge every in-flight
|
|
307
|
+
* request. A failed re-bind just leaves the next tool call to bind normally.
|
|
308
|
+
*/
|
|
309
|
+
handleCameraArrived(_e: {
|
|
310
|
+
path: string;
|
|
311
|
+
}): Promise<void>;
|
|
312
|
+
/**
|
|
313
|
+
* Close everything this manager is holding: registry helpers, the scratch
|
|
314
|
+
* scanner, and the watcher.
|
|
315
|
+
*
|
|
316
|
+
* Helpers already self-terminate when the parent goes away — they exit on
|
|
317
|
+
* stdin EOF — so this is not what stops them leaking on a crash. It exists
|
|
318
|
+
* because the watcher is the first helper deliberately built to outlive every
|
|
319
|
+
* operation: nothing else bounds its lifetime, so an orderly stop needs
|
|
320
|
+
* something explicit to call. Best-effort throughout; a shutdown path that can
|
|
321
|
+
* itself throw is worse than one that leaves a process for the OS to reap.
|
|
322
|
+
*/
|
|
323
|
+
shutdown(): Promise<void>;
|
|
324
|
+
/** Compat shim for the single-camera API B1 retires. */
|
|
9
325
|
openFirstObsbot(): Promise<ObsbotTransport>;
|
|
326
|
+
/**
|
|
327
|
+
* Per attached camera: serial (where obtainable), locationId, name, and
|
|
328
|
+
* status. A camera this process cannot open is reported `busy` WITHOUT a
|
|
329
|
+
* serial rather than omitted — it is enumerable but not identifiable.
|
|
330
|
+
* Already-bound cameras are reported from the registry without
|
|
331
|
+
* re-opening (avoids a pointless self-conflict against our own handle).
|
|
332
|
+
*/
|
|
333
|
+
listCameras(): Promise<CameraInfo[]>;
|
|
10
334
|
}
|