@camstack/addon-terminal 0.1.53 → 0.1.55
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/dist/addon.js +963 -537
- package/dist/addon.mjs +963 -537
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -8117,6 +8117,20 @@ var RelocateJobSchema = object({
|
|
|
8117
8117
|
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
8118
8118
|
*/
|
|
8119
8119
|
rowsReconciled: number().int().nonnegative().optional(),
|
|
8120
|
+
/**
|
|
8121
|
+
* Rows this run FORGOT because the file they name is not on disk.
|
|
8122
|
+
*
|
|
8123
|
+
* The mover derived the path from the row's own fields and `stat`ed it; an
|
|
8124
|
+
* ENOENT there is a per-path confirmation that the segment is gone (D296),
|
|
8125
|
+
* and the durable row is dropped through the same channel eviction uses. It
|
|
8126
|
+
* is reported for the same reason `rowsReconciled` is: this is a durable
|
|
8127
|
+
* mutation nobody asked for, and a migration that quietly erases hour rows is
|
|
8128
|
+
* the same failure as one that quietly skips them (D295).
|
|
8129
|
+
*
|
|
8130
|
+
* The production drain of 2026-08-30 would have reported 11 074 here — the
|
|
8131
|
+
* ledger claimed 5.65 GB of footage that no longer existed.
|
|
8132
|
+
*/
|
|
8133
|
+
rowsForgotten: number().int().nonnegative().optional(),
|
|
8120
8134
|
startedAt: number(),
|
|
8121
8135
|
finishedAt: number().nullable(),
|
|
8122
8136
|
error: string().nullable()
|
|
@@ -8181,6 +8195,13 @@ var MediaRelocateModeSchema = _enum([
|
|
|
8181
8195
|
]);
|
|
8182
8196
|
var RelocateMediaInputSchema = object({
|
|
8183
8197
|
toLocationId: string(),
|
|
8198
|
+
/**
|
|
8199
|
+
* Restrict the pass to rows currently on this location. Omitted / `'*'` =
|
|
8200
|
+
* every row that is not already on `toLocationId` (the historical
|
|
8201
|
+
* behaviour). A named source is what a from→to migration needs: without it
|
|
8202
|
+
* "move events off disk 2" also emptied disk 1.
|
|
8203
|
+
*/
|
|
8204
|
+
fromLocationId: string().optional(),
|
|
8184
8205
|
throttleMbps: number().min(1).max(1e3).optional(),
|
|
8185
8206
|
/** Omitted = `move`, the pre-existing behaviour. */
|
|
8186
8207
|
mode: MediaRelocateModeSchema.optional()
|
|
@@ -8248,6 +8269,19 @@ var StorageMigrationDestinationsSchema = object({
|
|
|
8248
8269
|
galleryMedia: string().min(1).optional()
|
|
8249
8270
|
}).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
|
|
8250
8271
|
/**
|
|
8272
|
+
* Optional named source per class. Omitted = the class's current default
|
|
8273
|
+
* (the historical behaviour). A named source that is NOT the default is a
|
|
8274
|
+
* drain of that disk: bytes move, the default stays, and the source is
|
|
8275
|
+
* disabled when the move finishes.
|
|
8276
|
+
*/
|
|
8277
|
+
var StorageMigrationSourcesSchema = object({
|
|
8278
|
+
recordings: string().min(1).optional(),
|
|
8279
|
+
recordingsLow: string().min(1).optional(),
|
|
8280
|
+
eventMedia: string().min(1).optional(),
|
|
8281
|
+
backups: string().min(1).optional(),
|
|
8282
|
+
galleryMedia: string().min(1).optional()
|
|
8283
|
+
}).optional();
|
|
8284
|
+
/**
|
|
8251
8285
|
* How a migration sequences the cutover against the byte move.
|
|
8252
8286
|
*
|
|
8253
8287
|
* - `blocking` — the historical order: pause, move every byte, repoint,
|
|
@@ -8269,6 +8303,8 @@ var StorageMigrationModeSchema = _enum(["blocking", "nonBlocking"]);
|
|
|
8269
8303
|
/** Shared input for planning and starting an orchestrated storage migration. */
|
|
8270
8304
|
var StorageMigrationInputSchema = object({
|
|
8271
8305
|
destinations: StorageMigrationDestinationsSchema,
|
|
8306
|
+
/** Omitted = each class's current default. */
|
|
8307
|
+
sources: StorageMigrationSourcesSchema,
|
|
8272
8308
|
throttleMbps: number().min(1).max(1e3).optional(),
|
|
8273
8309
|
/** Omitted = `blocking`, which stays the default. */
|
|
8274
8310
|
mode: StorageMigrationModeSchema.optional()
|
|
@@ -8348,6 +8384,13 @@ var StorageMigrationMoveSchema = object({
|
|
|
8348
8384
|
storageClass: StorageMigrationClassSchema,
|
|
8349
8385
|
fromLocationId: string(),
|
|
8350
8386
|
toLocationId: string(),
|
|
8387
|
+
/**
|
|
8388
|
+
* True when `from` was NOT the class default at plan time. The move still
|
|
8389
|
+
* copies bytes, but the default is left alone and the source is disabled
|
|
8390
|
+
* once the copy verifies. Absent on jobs planned before this field existed
|
|
8391
|
+
* — those jobs always repointed, which is `false`.
|
|
8392
|
+
*/
|
|
8393
|
+
freezeSource: boolean().optional(),
|
|
8351
8394
|
moverJobId: string().nullable(),
|
|
8352
8395
|
state: RelocateJobStateSchema.nullable(),
|
|
8353
8396
|
error: string().nullable(),
|
|
@@ -8361,6 +8404,7 @@ var StorageMigrationJobSchema = object({
|
|
|
8361
8404
|
* can tell a seconds-long cutover from a thirty-hour one. */
|
|
8362
8405
|
mode: StorageMigrationModeSchema,
|
|
8363
8406
|
destinations: StorageMigrationDestinationsSchema,
|
|
8407
|
+
sources: StorageMigrationSourcesSchema,
|
|
8364
8408
|
throttleMbps: number(),
|
|
8365
8409
|
moves: array(StorageMigrationMoveSchema),
|
|
8366
8410
|
pauseLeaseId: string().nullable(),
|
|
@@ -8386,6 +8430,7 @@ var StorageMigrationFindingSchema = object({
|
|
|
8386
8430
|
});
|
|
8387
8431
|
var StorageMigrationPlanSchema = object({
|
|
8388
8432
|
destinations: StorageMigrationDestinationsSchema,
|
|
8433
|
+
sources: StorageMigrationSourcesSchema,
|
|
8389
8434
|
/** The mode this plan was built for. A plan is only valid for its mode: the
|
|
8390
8435
|
* `eventMedia` seal gate and the single-cardinality refusal both depend on
|
|
8391
8436
|
* it. */
|
|
@@ -8393,7 +8438,8 @@ var StorageMigrationPlanSchema = object({
|
|
|
8393
8438
|
moves: array(object({
|
|
8394
8439
|
storageClass: StorageMigrationClassSchema,
|
|
8395
8440
|
fromLocationId: string(),
|
|
8396
|
-
toLocationId: string()
|
|
8441
|
+
toLocationId: string(),
|
|
8442
|
+
freezeSource: boolean().optional()
|
|
8397
8443
|
})),
|
|
8398
8444
|
findings: array(StorageMigrationFindingSchema)
|
|
8399
8445
|
});
|
|
@@ -8480,16 +8526,142 @@ var RelocateResidueSchema = object({
|
|
|
8480
8526
|
segments: number().int().nonnegative(),
|
|
8481
8527
|
bytes: number().int().nonnegative()
|
|
8482
8528
|
}).nullable();
|
|
8529
|
+
/**
|
|
8530
|
+
* Ask one location whether its durable hour rows describe the disk — the walk
|
|
8531
|
+
* (D319).
|
|
8532
|
+
*
|
|
8533
|
+
* `apply` DEFAULTS TO FALSE and that default is the product: the operator's
|
|
8534
|
+
* missing tool is the question, and the dry run is how they sanity-check the
|
|
8535
|
+
* destructive run before authorising it.
|
|
8536
|
+
*/
|
|
8537
|
+
var LedgerWalkInputSchema = object({
|
|
8538
|
+
locationId: string().min(1),
|
|
8539
|
+
/** Forget the confirmed-absent rows, rather than only counting them. */
|
|
8540
|
+
apply: boolean().optional(),
|
|
8541
|
+
/** Narrow to one camera. */
|
|
8542
|
+
deviceId: number().int().positive().optional(),
|
|
8543
|
+
/** Narrow to these recording profiles; empty/absent = every profile. */
|
|
8544
|
+
profiles: array(string().min(1)).optional()
|
|
8545
|
+
});
|
|
8546
|
+
/** Why a whole walk did nothing. Every one leaves the ledger untouched. */
|
|
8547
|
+
var LedgerWalkRefusalSchema = _enum([
|
|
8548
|
+
"location-unknown",
|
|
8549
|
+
"source-writable",
|
|
8550
|
+
"no-ledger",
|
|
8551
|
+
"archive-unreadable",
|
|
8552
|
+
"anchor-absent",
|
|
8553
|
+
"anchor-unreadable",
|
|
8554
|
+
"anchor-moved"
|
|
8555
|
+
]);
|
|
8556
|
+
_enum([
|
|
8557
|
+
"live-tail",
|
|
8558
|
+
"listing-error",
|
|
8559
|
+
"path-mismatch",
|
|
8560
|
+
"durable-refused"
|
|
8561
|
+
]);
|
|
8562
|
+
/** Every skip reason, always present, always a number — so a reason that never
|
|
8563
|
+
* fired reports as zero rather than absent and the report shape is constant
|
|
8564
|
+
* between passes. Spelled out rather than `z.record` for exactly that. */
|
|
8565
|
+
var LedgerWalkSkipCountsSchema = object({
|
|
8566
|
+
"live-tail": number().int().nonnegative(),
|
|
8567
|
+
"listing-error": number().int().nonnegative(),
|
|
8568
|
+
"path-mismatch": number().int().nonnegative(),
|
|
8569
|
+
"durable-refused": number().int().nonnegative()
|
|
8570
|
+
});
|
|
8571
|
+
/** One camera's share of a walk, so a report names cameras and not rows. */
|
|
8572
|
+
var LedgerWalkDeviceReportSchema = object({
|
|
8573
|
+
deviceId: number().int(),
|
|
8574
|
+
hoursWalked: number().int().nonnegative(),
|
|
8575
|
+
hoursMissing: number().int().nonnegative(),
|
|
8576
|
+
ghostSegments: number().int().nonnegative(),
|
|
8577
|
+
ghostBytes: number().int().nonnegative(),
|
|
8578
|
+
forgottenSegments: number().int().nonnegative(),
|
|
8579
|
+
orphanFiles: number().int().nonnegative()
|
|
8580
|
+
});
|
|
8581
|
+
/**
|
|
8582
|
+
* What one walk claimed, listed, found and (only when armed) forgot.
|
|
8583
|
+
*
|
|
8584
|
+
* `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
|
|
8585
|
+
* walk that saw a fraction of the location is visible in its own report rather
|
|
8586
|
+
* than in the absence of one.
|
|
8587
|
+
*/
|
|
8588
|
+
var LedgerWalkReportSchema = object({
|
|
8589
|
+
locationId: string(),
|
|
8590
|
+
applied: boolean(),
|
|
8591
|
+
refused: LedgerWalkRefusalSchema.nullable(),
|
|
8592
|
+
archiveSegments: number().int().nonnegative().nullable(),
|
|
8593
|
+
archiveBytes: number().int().nonnegative().nullable(),
|
|
8594
|
+
hoursClaimed: number().int().nonnegative(),
|
|
8595
|
+
hoursWalked: number().int().nonnegative(),
|
|
8596
|
+
hoursMissing: number().int().nonnegative(),
|
|
8597
|
+
/** `readdir` calls issued — the cost, stated in the unit that is paid. */
|
|
8598
|
+
listings: number().int().nonnegative(),
|
|
8599
|
+
segmentsClaimed: number().int().nonnegative(),
|
|
8600
|
+
ghostSegments: number().int().nonnegative(),
|
|
8601
|
+
ghostBytes: number().int().nonnegative(),
|
|
8602
|
+
ghostHoursWhole: number().int().nonnegative(),
|
|
8603
|
+
forgottenSegments: number().int().nonnegative(),
|
|
8604
|
+
forgottenBytes: number().int().nonnegative(),
|
|
8605
|
+
/** Files under a claimed hour that no durable row names. Never deleted. */
|
|
8606
|
+
orphanFiles: number().int().nonnegative(),
|
|
8607
|
+
orphanSample: array(string()).readonly(),
|
|
8608
|
+
hoursSkipped: number().int().nonnegative(),
|
|
8609
|
+
skippedByReason: LedgerWalkSkipCountsSchema,
|
|
8610
|
+
/** The walk stopped at its per-pass hour bound with claims unwalked. */
|
|
8611
|
+
bounded: boolean(),
|
|
8612
|
+
byDevice: array(LedgerWalkDeviceReportSchema).readonly()
|
|
8613
|
+
});
|
|
8483
8614
|
/** How many rows a media pass would still act on against a given target — the
|
|
8484
8615
|
* media lane's denominator AND its residue, from ONE derivation so the two can
|
|
8485
8616
|
* never disagree. `null` = the count could not be taken. */
|
|
8486
8617
|
var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
|
|
8487
8618
|
var RelocatableMediaCountInputSchema = object({
|
|
8488
8619
|
toLocationId: string().min(1),
|
|
8620
|
+
/** Restrict the count to one source; omitted / `'*'` = every non-target row. */
|
|
8621
|
+
fromLocationId: string().optional(),
|
|
8489
8622
|
/** Omitted = `move`. */
|
|
8490
8623
|
mode: MediaRelocateModeSchema.optional()
|
|
8491
8624
|
});
|
|
8492
8625
|
/**
|
|
8626
|
+
* Operator cleanup of leftover analytics rows, optional debug media, and
|
|
8627
|
+
* ghost ledger entries on frozen footage locations.
|
|
8628
|
+
*
|
|
8629
|
+
* A pass is tens of minutes on a standing backlog. The hub method RETURNS
|
|
8630
|
+
* `{ jobId }` immediately; progress is `cleanupStatus`. Awaiting the work
|
|
8631
|
+
* is how `addons.custom` hit the 60 s UDS deadline while reclaim continued
|
|
8632
|
+
* with no operator-visible status.
|
|
8633
|
+
*/
|
|
8634
|
+
var StorageCleanupPhaseSchema = _enum([
|
|
8635
|
+
"orphans",
|
|
8636
|
+
"debug-media",
|
|
8637
|
+
"ghost-ledger",
|
|
8638
|
+
"done",
|
|
8639
|
+
"failed",
|
|
8640
|
+
"cancelled"
|
|
8641
|
+
]);
|
|
8642
|
+
var StorageCleanupInputSchema = object({
|
|
8643
|
+
/** Also walk motion stills / track filmstrips. Off by default. */
|
|
8644
|
+
includeDebugMedia: boolean().optional() });
|
|
8645
|
+
var StorageCleanupJobSchema = object({
|
|
8646
|
+
jobId: string(),
|
|
8647
|
+
phase: StorageCleanupPhaseSchema,
|
|
8648
|
+
includeDebugMedia: boolean(),
|
|
8649
|
+
orphansReclaimed: number().int().nonnegative(),
|
|
8650
|
+
orphanBytesReclaimed: number().int().nonnegative(),
|
|
8651
|
+
debugMediaReclaimed: number().int().nonnegative(),
|
|
8652
|
+
debugMediaBytesReclaimed: number().int().nonnegative(),
|
|
8653
|
+
ghostsForgotten: number().int().nonnegative(),
|
|
8654
|
+
ghostBytesForgotten: number().int().nonnegative(),
|
|
8655
|
+
/** Short operator-facing line: current collection, pass, or location. */
|
|
8656
|
+
detail: string().nullable(),
|
|
8657
|
+
cancelRequested: boolean(),
|
|
8658
|
+
startedAt: number(),
|
|
8659
|
+
updatedAt: number(),
|
|
8660
|
+
finishedAt: number().nullable(),
|
|
8661
|
+
error: string().nullable()
|
|
8662
|
+
});
|
|
8663
|
+
var StorageCleanupStatusInputSchema = object({ jobId: string().optional() });
|
|
8664
|
+
/**
|
|
8493
8665
|
* `StorageLocationType` — an addon-declared id that identifies the *kind* of
|
|
8494
8666
|
* storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
|
|
8495
8667
|
* so the persisted record schema and the consumer-facing cap can both consume it
|
|
@@ -8517,11 +8689,10 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
|
|
|
8517
8689
|
* The default location for a type uses `id === <type>:default` by
|
|
8518
8690
|
* convention (the bare type ref like `'backups'` resolves to it).
|
|
8519
8691
|
*
|
|
8520
|
-
* `isSystem
|
|
8521
|
-
*
|
|
8522
|
-
*
|
|
8523
|
-
*
|
|
8524
|
-
* deleting it is rejected at the cap level.
|
|
8692
|
+
* `isSystem` is a legacy persisted flag. Seed still creates the initial
|
|
8693
|
+
* `<type>:default` locations; the flag is no longer a lock, a badge, or a
|
|
8694
|
+
* prune selector. New writes leave it false. Deletion is gated on uniqueness
|
|
8695
|
+
* / last-enabled, not on this bit.
|
|
8525
8696
|
*/
|
|
8526
8697
|
var StorageLocationSchema = object({
|
|
8527
8698
|
id: string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
|
|
@@ -12992,6 +13163,12 @@ method(object({
|
|
|
12992
13163
|
}), _void(), {
|
|
12993
13164
|
kind: "mutation",
|
|
12994
13165
|
auth: "admin"
|
|
13166
|
+
}), method(object({
|
|
13167
|
+
from: string(),
|
|
13168
|
+
to: string()
|
|
13169
|
+
}), object({ moved: number() }), {
|
|
13170
|
+
kind: "mutation",
|
|
13171
|
+
auth: "admin"
|
|
12995
13172
|
}), method(object({
|
|
12996
13173
|
deviceId: number(),
|
|
12997
13174
|
disabled: boolean()
|
|
@@ -19000,53 +19177,15 @@ var RecentTracksPageSchema = object({
|
|
|
19000
19177
|
/** Cursor for the next page, or null when this page is the last. */
|
|
19001
19178
|
nextCursor: string().nullable()
|
|
19002
19179
|
});
|
|
19003
|
-
var
|
|
19004
|
-
var
|
|
19005
|
-
var AnalyticsGroupRecordSchema = object({
|
|
19006
|
-
id: string(),
|
|
19007
|
-
deviceId: number().int(),
|
|
19008
|
-
openedAt: number().int(),
|
|
19009
|
-
closedAt: number().int(),
|
|
19010
|
-
timestamp: number().int(),
|
|
19011
|
-
memberCount: number().int(),
|
|
19012
|
-
memberTrackIds: array(string()).readonly(),
|
|
19013
|
-
className: string(),
|
|
19014
|
-
classes: array(string()).readonly(),
|
|
19015
|
-
/** Relative event-media path, or null when the group has no picture yet. */
|
|
19016
|
-
mediaUrl: string().nullable(),
|
|
19017
|
-
singleton: boolean()
|
|
19018
|
-
});
|
|
19019
|
-
var AnalyticsGroupMemberSchema = object({
|
|
19020
|
-
trackId: string(),
|
|
19021
|
-
deviceId: number().int(),
|
|
19022
|
-
className: string(),
|
|
19023
|
-
firstSeen: number().int(),
|
|
19024
|
-
lastSeen: number().int(),
|
|
19025
|
-
mediaUrl: string().nullable()
|
|
19026
|
-
});
|
|
19027
|
-
var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
|
|
19028
|
-
var ListGroupsQueryInput = object({
|
|
19029
|
-
/** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
|
|
19030
|
-
deviceIds: array(number()),
|
|
19031
|
-
/** Window lower bound on `closedAt` (inclusive). */
|
|
19032
|
-
since: number().optional(),
|
|
19033
|
-
/** Window upper bound on `openedAt` (inclusive). */
|
|
19034
|
-
until: number().optional(),
|
|
19035
|
-
limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
|
|
19036
|
-
/** Opaque continuation cursor from a previous page's `nextCursor`. */
|
|
19037
|
-
cursor: string().optional()
|
|
19038
|
-
});
|
|
19039
|
-
var ListGroupsPageSchema = object({
|
|
19040
|
-
groups: array(AnalyticsGroupRecordSchema).readonly(),
|
|
19041
|
-
nextCursor: string().nullable()
|
|
19042
|
-
});
|
|
19180
|
+
var KEY_EVENTS_DEFAULT_LIMIT = 50;
|
|
19181
|
+
var KEY_EVENTS_MAX_LIMIT = 200;
|
|
19043
19182
|
var KeyEventQueryInput = object({
|
|
19044
19183
|
deviceId: number(),
|
|
19045
19184
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
19046
19185
|
since: number(),
|
|
19047
19186
|
/** Window upper bound (track firstSeen ≤ until). */
|
|
19048
19187
|
until: number(),
|
|
19049
|
-
limit: number().int().min(1).max(
|
|
19188
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19050
19189
|
/** Drop tracks scoring below this importance. */
|
|
19051
19190
|
minImportance: number().min(0).max(1).optional(),
|
|
19052
19191
|
/** Restrict to a single class (e.g. 'person'). */
|
|
@@ -19068,6 +19207,32 @@ var KeyEventSchema = object({
|
|
|
19068
19207
|
...TrackFlagFields,
|
|
19069
19208
|
...TrackRetrainFields
|
|
19070
19209
|
});
|
|
19210
|
+
/** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
|
|
19211
|
+
var KeyEventBatchQueryInput = object({
|
|
19212
|
+
deviceIds: array(number()).min(1).max(200),
|
|
19213
|
+
since: number(),
|
|
19214
|
+
until: number(),
|
|
19215
|
+
/** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
|
|
19216
|
+
* across the set, which would let a busy camera starve a quiet one of its
|
|
19217
|
+
* rows and change what the merged feed contains. */
|
|
19218
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19219
|
+
minImportance: number().min(0).max(1).optional(),
|
|
19220
|
+
classFilter: string().optional()
|
|
19221
|
+
});
|
|
19222
|
+
/**
|
|
19223
|
+
* One camera's key events in a batch answer.
|
|
19224
|
+
*
|
|
19225
|
+
* The row exists for every requested id. `getKeyEvents` degrades to `[]` on
|
|
19226
|
+
* error rather than throwing, so a camera whose store read failed and one with
|
|
19227
|
+
* no events in the window were ALREADY indistinguishable per camera — the
|
|
19228
|
+
* batch does not make that worse, and the row keeps the deviceId the single
|
|
19229
|
+
* method's output never carried (the caller used to stamp it from the fan-out
|
|
19230
|
+
* key, which only worked because there was one query per camera).
|
|
19231
|
+
*/
|
|
19232
|
+
var KeyEventsForDeviceSchema = object({
|
|
19233
|
+
deviceId: number(),
|
|
19234
|
+
events: array(KeyEventSchema).readonly()
|
|
19235
|
+
});
|
|
19071
19236
|
object({
|
|
19072
19237
|
trackId: string(),
|
|
19073
19238
|
className: string(),
|
|
@@ -19115,9 +19280,7 @@ var TrackCascadeCountsSchema = object({
|
|
|
19115
19280
|
/** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
|
|
19116
19281
|
plates: number().int(),
|
|
19117
19282
|
/** Per-track CLIP search vectors removed (best-effort). */
|
|
19118
|
-
embeddings: number().int()
|
|
19119
|
-
/** Group membership + group rows removed with their last member (best-effort). */
|
|
19120
|
-
groups: number().int()
|
|
19283
|
+
embeddings: number().int()
|
|
19121
19284
|
});
|
|
19122
19285
|
/** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
|
|
19123
19286
|
var DiskReconcileCountsSchema = object({
|
|
@@ -19287,6 +19450,47 @@ var RebuildStatusSchema = object({
|
|
|
19287
19450
|
/** Present when the pass ended by throwing. */
|
|
19288
19451
|
error: string().nullable()
|
|
19289
19452
|
});
|
|
19453
|
+
/**
|
|
19454
|
+
* Acknowledgement that a debug-media reclaim STARTED.
|
|
19455
|
+
*
|
|
19456
|
+
* The pass is paced at ~2 MB/s over a standing 100 GB — tens of minutes — so
|
|
19457
|
+
* it runs detached and this returns immediately. Awaiting it is how the
|
|
19458
|
+
* `addons.custom` door hit the 60 s UDS deadline while the walk carried on
|
|
19459
|
+
* with no status. Poll {@link pipelineAnalyticsCapability.methods.getMediaReclaimStatus}.
|
|
19460
|
+
*/
|
|
19461
|
+
var MediaReclaimStartResultSchema = object({
|
|
19462
|
+
started: boolean(),
|
|
19463
|
+
/** True when a pass was already running; the new request is ignored. */
|
|
19464
|
+
alreadyRunning: boolean()
|
|
19465
|
+
});
|
|
19466
|
+
var MediaReclaimInputSchema = object({
|
|
19467
|
+
mode: _enum(["report", "reclaim"]).default("report"),
|
|
19468
|
+
scopes: array(_enum(["motion-still", "track-filmstrip"])).min(1).optional(),
|
|
19469
|
+
deviceIds: array(number().int()).min(1).optional(),
|
|
19470
|
+
restart: boolean().optional(),
|
|
19471
|
+
pageSize: number().int().min(50).max(5e3).optional(),
|
|
19472
|
+
maxRowsPerDevice: number().int().min(1).max(5e5).optional(),
|
|
19473
|
+
maxReclaimPerDevice: number().int().min(1).max(5e5).optional(),
|
|
19474
|
+
maxBytesPerRun: number().int().min(1).optional(),
|
|
19475
|
+
budgetMinutes: number().int().min(1).max(720).optional(),
|
|
19476
|
+
throttleBytesPerSec: number().int().min(64 * 1024).optional(),
|
|
19477
|
+
graceMinutes: number().int().min(1).max(10080).optional()
|
|
19478
|
+
});
|
|
19479
|
+
var MediaReclaimStatusSchema = object({
|
|
19480
|
+
running: boolean(),
|
|
19481
|
+
mode: _enum(["report", "reclaim"]).nullable(),
|
|
19482
|
+
totalExamined: number(),
|
|
19483
|
+
totalEligible: number(),
|
|
19484
|
+
totalReclaimed: number(),
|
|
19485
|
+
totalBytesReclaimed: number(),
|
|
19486
|
+
totalRefused: number(),
|
|
19487
|
+
/** Device+scope windows finished in this pass. */
|
|
19488
|
+
devicesDone: number(),
|
|
19489
|
+
complete: boolean().nullable(),
|
|
19490
|
+
startedAtMs: number().nullable(),
|
|
19491
|
+
finishedAtMs: number().nullable(),
|
|
19492
|
+
error: string().nullable()
|
|
19493
|
+
});
|
|
19290
19494
|
var ReplayFrameInputSchema = object({
|
|
19291
19495
|
timestamp: number(),
|
|
19292
19496
|
frame: PipelineRunResultBridge
|
|
@@ -19325,10 +19529,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19325
19529
|
* stationary registry). Default false: the timeline lists passages,
|
|
19326
19530
|
* not parking records (operator decision, 2026-08-15). */
|
|
19327
19531
|
includeStationary: boolean().optional()
|
|
19328
|
-
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(
|
|
19329
|
-
deviceId: number(),
|
|
19330
|
-
groupId: string().min(1)
|
|
19331
|
-
}), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
|
|
19532
|
+
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
|
|
19332
19533
|
kind: "mutation",
|
|
19333
19534
|
auth: "admin"
|
|
19334
19535
|
}), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
|
|
@@ -19337,7 +19538,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19337
19538
|
until: number().optional(),
|
|
19338
19539
|
kinds: array(string()).optional(),
|
|
19339
19540
|
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
19340
|
-
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
19541
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
|
|
19341
19542
|
deviceId: number(),
|
|
19342
19543
|
since: number(),
|
|
19343
19544
|
until: number(),
|
|
@@ -19428,6 +19629,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19428
19629
|
}), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
|
|
19429
19630
|
kind: "query",
|
|
19430
19631
|
auth: "admin"
|
|
19632
|
+
}), method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
|
|
19633
|
+
kind: "mutation",
|
|
19634
|
+
auth: "admin"
|
|
19635
|
+
}), method(object({}), MediaReclaimStatusSchema, {
|
|
19636
|
+
kind: "query",
|
|
19637
|
+
auth: "admin"
|
|
19431
19638
|
}), method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
|
|
19432
19639
|
kind: "query",
|
|
19433
19640
|
auth: "admin"
|
|
@@ -21502,7 +21709,13 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
|
|
|
21502
21709
|
}), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
|
|
21503
21710
|
kind: "mutation",
|
|
21504
21711
|
auth: "admin"
|
|
21505
|
-
})
|
|
21712
|
+
}), method(StorageCleanupInputSchema, object({ jobId: string() }), {
|
|
21713
|
+
kind: "mutation",
|
|
21714
|
+
auth: "admin"
|
|
21715
|
+
}), method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
21716
|
+
kind: "mutation",
|
|
21717
|
+
auth: "admin"
|
|
21718
|
+
}), method(object({}), array(StorageMigrationJobSchema).readonly(), { auth: "admin" });
|
|
21506
21719
|
var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
|
|
21507
21720
|
providerId: string().min(1),
|
|
21508
21721
|
displayName: string().min(1),
|
|
@@ -28639,6 +28852,33 @@ var RecordingRebalanceInputSchema = object({
|
|
|
28639
28852
|
minMoveGb: number().min(0).optional()
|
|
28640
28853
|
});
|
|
28641
28854
|
/**
|
|
28855
|
+
* Operator-facing placement of one camera onto a recordings location.
|
|
28856
|
+
*
|
|
28857
|
+
* `pinLocationId` is the operator instruction: a location id, or `null` for
|
|
28858
|
+
* Auto (the planner may move this camera). `locationId` is where high/mid
|
|
28859
|
+
* currently write — the plan, which may disagree with the pin when Auto.
|
|
28860
|
+
*/
|
|
28861
|
+
var RecordingDevicePlacementSchema = object({
|
|
28862
|
+
deviceId: number().int(),
|
|
28863
|
+
profile: string(),
|
|
28864
|
+
locationId: string()
|
|
28865
|
+
});
|
|
28866
|
+
var RecordingDevicePinSchema = object({
|
|
28867
|
+
deviceId: number().int(),
|
|
28868
|
+
/** Recordings-class location this camera is pinned to. */
|
|
28869
|
+
locationId: string()
|
|
28870
|
+
});
|
|
28871
|
+
var RecordingPlacementViewSchema = object({
|
|
28872
|
+
assignments: array(RecordingDevicePlacementSchema),
|
|
28873
|
+
pins: array(RecordingDevicePinSchema),
|
|
28874
|
+
defaultLocations: record(string(), string())
|
|
28875
|
+
});
|
|
28876
|
+
var RecordingSetDevicePlacementInputSchema = object({
|
|
28877
|
+
deviceId: number().int(),
|
|
28878
|
+
/** `null` = Auto. Otherwise a `recordings:*` location id. */
|
|
28879
|
+
locationId: string().nullable()
|
|
28880
|
+
});
|
|
28881
|
+
/**
|
|
28642
28882
|
* Result of locating footage at a wall-clock instant for one device/profile.
|
|
28643
28883
|
* `segment` carries the covering segment's window; `gap` reports the forward
|
|
28644
28884
|
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
@@ -28852,6 +29092,9 @@ method(object({
|
|
|
28852
29092
|
}), method(RelocateResidueInputSchema, RelocateResidueSchema, {
|
|
28853
29093
|
kind: "query",
|
|
28854
29094
|
auth: "admin"
|
|
29095
|
+
}), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
|
|
29096
|
+
kind: "mutation",
|
|
29097
|
+
auth: "admin"
|
|
28855
29098
|
}), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
28856
29099
|
kind: "mutation",
|
|
28857
29100
|
auth: "admin"
|
|
@@ -28861,6 +29104,12 @@ method(object({
|
|
|
28861
29104
|
}), method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
|
|
28862
29105
|
kind: "mutation",
|
|
28863
29106
|
auth: "admin"
|
|
29107
|
+
}), method(object({}), RecordingPlacementViewSchema, {
|
|
29108
|
+
kind: "query",
|
|
29109
|
+
auth: "admin"
|
|
29110
|
+
}), method(RecordingSetDevicePlacementInputSchema, object({ ok: literal(true) }), {
|
|
29111
|
+
kind: "mutation",
|
|
29112
|
+
auth: "admin"
|
|
28864
29113
|
});
|
|
28865
29114
|
/**
|
|
28866
29115
|
* `recording-export` cap — render a footage time range into a single downloadable
|
|
@@ -29243,6 +29492,25 @@ var SceneMonitorStatusSchema = object({
|
|
|
29243
29492
|
monitors: array(SceneMonitorSchema),
|
|
29244
29493
|
lastFetchedAt: number()
|
|
29245
29494
|
});
|
|
29495
|
+
/**
|
|
29496
|
+
* One camera's row in a `listScenesBatch` answer.
|
|
29497
|
+
*
|
|
29498
|
+
* `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
|
|
29499
|
+
* is a `defaultActive` wrapper, so every camera is asked; a camera whose
|
|
29500
|
+
* provider is absent (pipeline-analytics not deployed, the post-processing node
|
|
29501
|
+
* down) cannot answer, and that is not the same fact as a camera with no scenes
|
|
29502
|
+
* configured. Fanned out per camera the difference was visible — one query
|
|
29503
|
+
* errored while the others resolved — and a batch that returned only the rows
|
|
29504
|
+
* it managed would have destroyed it, silently, by making an unreachable camera
|
|
29505
|
+
* indistinguishable from one that answered `monitors: []`.
|
|
29506
|
+
*
|
|
29507
|
+
* So: EVERY requested deviceId gets a row. `status: null` means "this camera
|
|
29508
|
+
* could not be read"; `status.monitors: []` means "read, and it has none".
|
|
29509
|
+
*/
|
|
29510
|
+
var SceneMonitorStatusForDeviceSchema = object({
|
|
29511
|
+
deviceId: number(),
|
|
29512
|
+
status: SceneMonitorStatusSchema.nullable()
|
|
29513
|
+
});
|
|
29246
29514
|
var sceneMonitorCapability = {
|
|
29247
29515
|
name: "scene-monitor",
|
|
29248
29516
|
scope: "device",
|
|
@@ -29252,6 +29520,22 @@ var sceneMonitorCapability = {
|
|
|
29252
29520
|
deviceTypes: [DeviceType.Camera],
|
|
29253
29521
|
methods: {
|
|
29254
29522
|
listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
|
|
29523
|
+
/**
|
|
29524
|
+
* The same answer, for a SET of cameras, in one round trip.
|
|
29525
|
+
*
|
|
29526
|
+
* `/scenes` renders every camera and re-reads them on a 30s safety-net
|
|
29527
|
+
* poll behind the push slice. Fanned out client-side that was one query
|
|
29528
|
+
* per camera — 29 round trips through the browser, the hub and the
|
|
29529
|
+
* post-analysis runner every 30 seconds to read an in-memory map the
|
|
29530
|
+
* owner had already merged. The work is unchanged (`statusFor` per
|
|
29531
|
+
* device, all in-process at the owner); what collapses is the transport.
|
|
29532
|
+
*
|
|
29533
|
+
* A camera that cannot answer still gets a row, with `status: null` —
|
|
29534
|
+
* see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
|
|
29535
|
+
* ids: a caller that asked for twenty-nine and got twenty-seven cannot
|
|
29536
|
+
* tell which two are missing, or that any are.
|
|
29537
|
+
*/
|
|
29538
|
+
listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
|
|
29255
29539
|
createScene: method(object({
|
|
29256
29540
|
deviceId: number(),
|
|
29257
29541
|
label: string(),
|
|
@@ -31087,6 +31371,27 @@ var CameraOccupancySnapshotSchema = object({
|
|
|
31087
31371
|
stationaryObjects: array(StationaryObjectSchema).readonly().optional()
|
|
31088
31372
|
});
|
|
31089
31373
|
/**
|
|
31374
|
+
* One camera's row in a `getCurrentSnapshotBatch` answer.
|
|
31375
|
+
*
|
|
31376
|
+
* THREE outcomes, and the single-camera method could only express two of them
|
|
31377
|
+
* because `snapshot: null` was already spoken for:
|
|
31378
|
+
*
|
|
31379
|
+
* - `read: 'read'`, `snapshot` present — the live occupancy reading.
|
|
31380
|
+
* - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
|
|
31381
|
+
* yet: no frame since boot, and no parked-object registry to hydrate from.
|
|
31382
|
+
* - `read: 'unreadable'` — the owner could not answer for this
|
|
31383
|
+
* camera. `snapshot` is null, and it does NOT mean "nothing parked here".
|
|
31384
|
+
*
|
|
31385
|
+
* Collapsing the last two is the failure this field exists to prevent: a
|
|
31386
|
+
* hydration that threw would otherwise render as an empty Stationary section,
|
|
31387
|
+
* which is a definite claim about a camera nobody could read.
|
|
31388
|
+
*/
|
|
31389
|
+
var CameraOccupancySnapshotForDeviceSchema = object({
|
|
31390
|
+
deviceId: number(),
|
|
31391
|
+
read: _enum(["read", "unreadable"]),
|
|
31392
|
+
snapshot: CameraOccupancySnapshotSchema.nullable()
|
|
31393
|
+
});
|
|
31394
|
+
/**
|
|
31090
31395
|
* Time-series resolution. The history methods return one bucket per
|
|
31091
31396
|
* step over the requested range. Smaller resolutions cost more
|
|
31092
31397
|
* memory + bandwidth; bound to discrete steps so caller cannot ask
|
|
@@ -31143,6 +31448,20 @@ var zoneAnalyticsCapability = {
|
|
|
31143
31448
|
* (no inference result emitted since boot or since binding was
|
|
31144
31449
|
* activated). */
|
|
31145
31450
|
getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
|
|
31451
|
+
/**
|
|
31452
|
+
* The same snapshot, for a SET of cameras, in one round trip.
|
|
31453
|
+
*
|
|
31454
|
+
* The Events page's Stationary section polls this every 15s for every
|
|
31455
|
+
* selected camera. Fanned out client-side that is one query per camera to
|
|
31456
|
+
* read a `Map.get` at the owner — the answer costs nothing, the round trip
|
|
31457
|
+
* costs everything. Batched, N transports become one and the per-device
|
|
31458
|
+
* work is unchanged.
|
|
31459
|
+
*
|
|
31460
|
+
* Every requested deviceId gets a row, tagged `read` — see
|
|
31461
|
+
* {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
|
|
31462
|
+
* not answer for is `'unreadable'`, never an empty reading.
|
|
31463
|
+
*/
|
|
31464
|
+
getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
|
|
31146
31465
|
/** Time-series object count inside one zone. `className` optional —
|
|
31147
31466
|
* omit to count every class in the zone. */
|
|
31148
31467
|
getZoneHistory: method(object({
|
|
@@ -33858,6 +34177,12 @@ Object.freeze({
|
|
|
33858
34177
|
addonId: null,
|
|
33859
34178
|
access: "delete"
|
|
33860
34179
|
},
|
|
34180
|
+
"deviceManager.renameLocation": {
|
|
34181
|
+
capName: "device-manager",
|
|
34182
|
+
capScope: "system",
|
|
34183
|
+
addonId: null,
|
|
34184
|
+
access: "create"
|
|
34185
|
+
},
|
|
33861
34186
|
"deviceManager.runDeviceAction": {
|
|
33862
34187
|
capName: "device-manager",
|
|
33863
34188
|
capScope: "system",
|
|
@@ -35550,13 +35875,19 @@ Object.freeze({
|
|
|
35550
35875
|
addonId: null,
|
|
35551
35876
|
access: "view"
|
|
35552
35877
|
},
|
|
35553
|
-
"pipelineAnalytics.
|
|
35878
|
+
"pipelineAnalytics.getKeyEvents": {
|
|
35554
35879
|
capName: "pipeline-analytics",
|
|
35555
35880
|
capScope: "device",
|
|
35556
35881
|
addonId: null,
|
|
35557
35882
|
access: "view"
|
|
35558
35883
|
},
|
|
35559
|
-
"pipelineAnalytics.
|
|
35884
|
+
"pipelineAnalytics.getKeyEventsBatch": {
|
|
35885
|
+
capName: "pipeline-analytics",
|
|
35886
|
+
capScope: "device",
|
|
35887
|
+
addonId: null,
|
|
35888
|
+
access: "view"
|
|
35889
|
+
},
|
|
35890
|
+
"pipelineAnalytics.getMediaReclaimStatus": {
|
|
35560
35891
|
capName: "pipeline-analytics",
|
|
35561
35892
|
capScope: "device",
|
|
35562
35893
|
addonId: null,
|
|
@@ -35646,12 +35977,6 @@ Object.freeze({
|
|
|
35646
35977
|
addonId: null,
|
|
35647
35978
|
access: "view"
|
|
35648
35979
|
},
|
|
35649
|
-
"pipelineAnalytics.listGroups": {
|
|
35650
|
-
capName: "pipeline-analytics",
|
|
35651
|
-
capScope: "device",
|
|
35652
|
-
addonId: null,
|
|
35653
|
-
access: "view"
|
|
35654
|
-
},
|
|
35655
35980
|
"pipelineAnalytics.listOpsLog": {
|
|
35656
35981
|
capName: "pipeline-analytics",
|
|
35657
35982
|
capScope: "device",
|
|
@@ -35736,6 +36061,12 @@ Object.freeze({
|
|
|
35736
36061
|
addonId: null,
|
|
35737
36062
|
access: "create"
|
|
35738
36063
|
},
|
|
36064
|
+
"pipelineAnalytics.reclaimDebugMedia": {
|
|
36065
|
+
capName: "pipeline-analytics",
|
|
36066
|
+
capScope: "device",
|
|
36067
|
+
addonId: null,
|
|
36068
|
+
access: "create"
|
|
36069
|
+
},
|
|
35739
36070
|
"pipelineAnalytics.reconcileFromDisk": {
|
|
35740
36071
|
capName: "pipeline-analytics",
|
|
35741
36072
|
capScope: "device",
|
|
@@ -36660,6 +36991,12 @@ Object.freeze({
|
|
|
36660
36991
|
addonId: null,
|
|
36661
36992
|
access: "view"
|
|
36662
36993
|
},
|
|
36994
|
+
"recording.getPlacement": {
|
|
36995
|
+
capName: "recording",
|
|
36996
|
+
capScope: "system",
|
|
36997
|
+
addonId: null,
|
|
36998
|
+
access: "view"
|
|
36999
|
+
},
|
|
36663
37000
|
"recording.getPlaybackManifest": {
|
|
36664
37001
|
capName: "recording",
|
|
36665
37002
|
capScope: "system",
|
|
@@ -36738,6 +37075,12 @@ Object.freeze({
|
|
|
36738
37075
|
addonId: null,
|
|
36739
37076
|
access: "view"
|
|
36740
37077
|
},
|
|
37078
|
+
"recording.reconcileLedgerAgainstDisk": {
|
|
37079
|
+
capName: "recording",
|
|
37080
|
+
capScope: "system",
|
|
37081
|
+
addonId: null,
|
|
37082
|
+
access: "create"
|
|
37083
|
+
},
|
|
36741
37084
|
"recording.refreshStorageLocationsForMigration": {
|
|
36742
37085
|
capName: "recording",
|
|
36743
37086
|
capScope: "system",
|
|
@@ -36780,6 +37123,12 @@ Object.freeze({
|
|
|
36780
37123
|
addonId: null,
|
|
36781
37124
|
access: "create"
|
|
36782
37125
|
},
|
|
37126
|
+
"recording.setDevicePlacement": {
|
|
37127
|
+
capName: "recording",
|
|
37128
|
+
capScope: "system",
|
|
37129
|
+
addonId: null,
|
|
37130
|
+
access: "create"
|
|
37131
|
+
},
|
|
36783
37132
|
"recording.startStorageMigrationMove": {
|
|
36784
37133
|
capName: "recording",
|
|
36785
37134
|
capScope: "system",
|
|
@@ -36864,6 +37213,12 @@ Object.freeze({
|
|
|
36864
37213
|
addonId: null,
|
|
36865
37214
|
access: "view"
|
|
36866
37215
|
},
|
|
37216
|
+
"sceneMonitor.listScenesBatch": {
|
|
37217
|
+
capName: "scene-monitor",
|
|
37218
|
+
capScope: "device",
|
|
37219
|
+
addonId: null,
|
|
37220
|
+
access: "view"
|
|
37221
|
+
},
|
|
36867
37222
|
"sceneMonitor.recheckNow": {
|
|
36868
37223
|
capName: "scene-monitor",
|
|
36869
37224
|
capScope: "device",
|
|
@@ -37218,12 +37573,36 @@ Object.freeze({
|
|
|
37218
37573
|
addonId: null,
|
|
37219
37574
|
access: "create"
|
|
37220
37575
|
},
|
|
37576
|
+
"storageMigration.cleanupCancel": {
|
|
37577
|
+
capName: "storage-migration",
|
|
37578
|
+
capScope: "system",
|
|
37579
|
+
addonId: null,
|
|
37580
|
+
access: "create"
|
|
37581
|
+
},
|
|
37582
|
+
"storageMigration.cleanupStart": {
|
|
37583
|
+
capName: "storage-migration",
|
|
37584
|
+
capScope: "system",
|
|
37585
|
+
addonId: null,
|
|
37586
|
+
access: "create"
|
|
37587
|
+
},
|
|
37588
|
+
"storageMigration.cleanupStatus": {
|
|
37589
|
+
capName: "storage-migration",
|
|
37590
|
+
capScope: "system",
|
|
37591
|
+
addonId: null,
|
|
37592
|
+
access: "view"
|
|
37593
|
+
},
|
|
37221
37594
|
"storageMigration.drain": {
|
|
37222
37595
|
capName: "storage-migration",
|
|
37223
37596
|
capScope: "system",
|
|
37224
37597
|
addonId: null,
|
|
37225
37598
|
access: "create"
|
|
37226
37599
|
},
|
|
37600
|
+
"storageMigration.history": {
|
|
37601
|
+
capName: "storage-migration",
|
|
37602
|
+
capScope: "system",
|
|
37603
|
+
addonId: null,
|
|
37604
|
+
access: "view"
|
|
37605
|
+
},
|
|
37227
37606
|
"storageMigration.movers": {
|
|
37228
37607
|
capName: "storage-migration",
|
|
37229
37608
|
capScope: "system",
|
|
@@ -38220,6 +38599,12 @@ Object.freeze({
|
|
|
38220
38599
|
addonId: null,
|
|
38221
38600
|
access: "view"
|
|
38222
38601
|
},
|
|
38602
|
+
"zoneAnalytics.getCurrentSnapshotBatch": {
|
|
38603
|
+
capName: "zone-analytics",
|
|
38604
|
+
capScope: "device",
|
|
38605
|
+
addonId: null,
|
|
38606
|
+
access: "view"
|
|
38607
|
+
},
|
|
38223
38608
|
"zoneAnalytics.getUnzonedHistory": {
|
|
38224
38609
|
capName: "zone-analytics",
|
|
38225
38610
|
capScope: "device",
|
|
@@ -39214,14 +39599,14 @@ Object.freeze({
|
|
|
39214
39599
|
form: "single",
|
|
39215
39600
|
optional: true
|
|
39216
39601
|
}],
|
|
39217
|
-
"pipelineAnalytics.
|
|
39602
|
+
"pipelineAnalytics.getKeyEvents": [{
|
|
39218
39603
|
name: "deviceId",
|
|
39219
39604
|
form: "single",
|
|
39220
39605
|
optional: false
|
|
39221
39606
|
}],
|
|
39222
|
-
"pipelineAnalytics.
|
|
39223
|
-
name: "
|
|
39224
|
-
form: "
|
|
39607
|
+
"pipelineAnalytics.getKeyEventsBatch": [{
|
|
39608
|
+
name: "deviceIds",
|
|
39609
|
+
form: "array",
|
|
39225
39610
|
optional: false
|
|
39226
39611
|
}],
|
|
39227
39612
|
"pipelineAnalytics.getMotionEvents": [{
|
|
@@ -39279,11 +39664,6 @@ Object.freeze({
|
|
|
39279
39664
|
form: "single",
|
|
39280
39665
|
optional: false
|
|
39281
39666
|
}],
|
|
39282
|
-
"pipelineAnalytics.listGroups": [{
|
|
39283
|
-
name: "deviceIds",
|
|
39284
|
-
form: "array",
|
|
39285
|
-
optional: false
|
|
39286
|
-
}],
|
|
39287
39667
|
"pipelineAnalytics.listOpsLog": [{
|
|
39288
39668
|
name: "deviceId",
|
|
39289
39669
|
form: "single",
|
|
@@ -39329,6 +39709,11 @@ Object.freeze({
|
|
|
39329
39709
|
form: "single",
|
|
39330
39710
|
optional: true
|
|
39331
39711
|
}],
|
|
39712
|
+
"pipelineAnalytics.reclaimDebugMedia": [{
|
|
39713
|
+
name: "deviceIds",
|
|
39714
|
+
form: "array",
|
|
39715
|
+
optional: true
|
|
39716
|
+
}],
|
|
39332
39717
|
"pipelineAnalytics.reconcileFromDisk": [{
|
|
39333
39718
|
name: "deviceId",
|
|
39334
39719
|
form: "single",
|
|
@@ -39664,6 +40049,11 @@ Object.freeze({
|
|
|
39664
40049
|
form: "single",
|
|
39665
40050
|
optional: false
|
|
39666
40051
|
}],
|
|
40052
|
+
"recording.reconcileLedgerAgainstDisk": [{
|
|
40053
|
+
name: "deviceId",
|
|
40054
|
+
form: "single",
|
|
40055
|
+
optional: true
|
|
40056
|
+
}],
|
|
39667
40057
|
"recording.relocateFootage": [{
|
|
39668
40058
|
name: "deviceId",
|
|
39669
40059
|
form: "single",
|
|
@@ -39689,6 +40079,11 @@ Object.freeze({
|
|
|
39689
40079
|
form: "single",
|
|
39690
40080
|
optional: false
|
|
39691
40081
|
}],
|
|
40082
|
+
"recording.setDevicePlacement": [{
|
|
40083
|
+
name: "deviceId",
|
|
40084
|
+
form: "single",
|
|
40085
|
+
optional: false
|
|
40086
|
+
}],
|
|
39692
40087
|
"recording.startStorageMigrationMove": [{
|
|
39693
40088
|
name: "deviceId",
|
|
39694
40089
|
form: "single",
|
|
@@ -39729,6 +40124,11 @@ Object.freeze({
|
|
|
39729
40124
|
form: "single",
|
|
39730
40125
|
optional: false
|
|
39731
40126
|
}],
|
|
40127
|
+
"sceneMonitor.listScenesBatch": [{
|
|
40128
|
+
name: "deviceIds",
|
|
40129
|
+
form: "array",
|
|
40130
|
+
optional: false
|
|
40131
|
+
}],
|
|
39732
40132
|
"sceneMonitor.recheckNow": [{
|
|
39733
40133
|
name: "deviceId",
|
|
39734
40134
|
form: "single",
|
|
@@ -39990,6 +40390,11 @@ Object.freeze({
|
|
|
39990
40390
|
form: "single",
|
|
39991
40391
|
optional: false
|
|
39992
40392
|
}],
|
|
40393
|
+
"zoneAnalytics.getCurrentSnapshotBatch": [{
|
|
40394
|
+
name: "deviceIds",
|
|
40395
|
+
form: "array",
|
|
40396
|
+
optional: false
|
|
40397
|
+
}],
|
|
39993
40398
|
"zoneAnalytics.getUnzonedHistory": [{
|
|
39994
40399
|
name: "deviceId",
|
|
39995
40400
|
form: "single",
|
|
@@ -40563,6 +40968,111 @@ function resolveGlancesCursesShimEnv(options) {
|
|
|
40563
40968
|
return { PYTHONPATH: existing ? `${options.shimDir}:${existing}` : options.shimDir };
|
|
40564
40969
|
}
|
|
40565
40970
|
//#endregion
|
|
40971
|
+
//#region src/profile-settings.ts
|
|
40972
|
+
var GLANCES_PLUGINS = [
|
|
40973
|
+
{
|
|
40974
|
+
key: "showCpu",
|
|
40975
|
+
plugin: "cpu",
|
|
40976
|
+
label: "CPU"
|
|
40977
|
+
},
|
|
40978
|
+
{
|
|
40979
|
+
key: "showMem",
|
|
40980
|
+
plugin: "mem",
|
|
40981
|
+
label: "Memory"
|
|
40982
|
+
},
|
|
40983
|
+
{
|
|
40984
|
+
key: "showLoad",
|
|
40985
|
+
plugin: "load",
|
|
40986
|
+
label: "Load"
|
|
40987
|
+
},
|
|
40988
|
+
{
|
|
40989
|
+
key: "showNetwork",
|
|
40990
|
+
plugin: "network",
|
|
40991
|
+
label: "Network"
|
|
40992
|
+
},
|
|
40993
|
+
{
|
|
40994
|
+
key: "showDiskIo",
|
|
40995
|
+
plugin: "diskio",
|
|
40996
|
+
label: "Disk I/O"
|
|
40997
|
+
},
|
|
40998
|
+
{
|
|
40999
|
+
key: "showFs",
|
|
41000
|
+
plugin: "fs",
|
|
41001
|
+
label: "Filesystems"
|
|
41002
|
+
},
|
|
41003
|
+
{
|
|
41004
|
+
key: "showProcessList",
|
|
41005
|
+
plugin: "processlist",
|
|
41006
|
+
label: "Process list"
|
|
41007
|
+
},
|
|
41008
|
+
{
|
|
41009
|
+
key: "showContainers",
|
|
41010
|
+
plugin: "containers",
|
|
41011
|
+
label: "Containers"
|
|
41012
|
+
},
|
|
41013
|
+
{
|
|
41014
|
+
key: "showSensors",
|
|
41015
|
+
plugin: "sensors",
|
|
41016
|
+
label: "Sensors"
|
|
41017
|
+
}
|
|
41018
|
+
];
|
|
41019
|
+
function glancesBooleanField(key, label) {
|
|
41020
|
+
return {
|
|
41021
|
+
type: "boolean",
|
|
41022
|
+
key,
|
|
41023
|
+
label,
|
|
41024
|
+
default: true,
|
|
41025
|
+
style: "switch"
|
|
41026
|
+
};
|
|
41027
|
+
}
|
|
41028
|
+
function glancesSettingsSchema() {
|
|
41029
|
+
return { sections: [{
|
|
41030
|
+
id: "glances-panels",
|
|
41031
|
+
title: "Glances panels",
|
|
41032
|
+
description: "Turn off a panel to pass --disable-plugin to this Terminal only. All on is the measured default (~1% of one core at the camera grid).",
|
|
41033
|
+
columns: 2,
|
|
41034
|
+
fields: GLANCES_PLUGINS.map((plugin) => glancesBooleanField(plugin.key, plugin.label))
|
|
41035
|
+
}] };
|
|
41036
|
+
}
|
|
41037
|
+
function settingsSchemaForProfile(profileId) {
|
|
41038
|
+
if (profileId === "glances") return glancesSettingsSchema();
|
|
41039
|
+
return null;
|
|
41040
|
+
}
|
|
41041
|
+
function glancesSettingsToArgs(settings) {
|
|
41042
|
+
const disabled = GLANCES_PLUGINS.filter((plugin) => settings[plugin.key] === false).map((plugin) => plugin.plugin);
|
|
41043
|
+
if (disabled.length === 0) return [];
|
|
41044
|
+
return ["--disable-plugin", disabled.join(",")];
|
|
41045
|
+
}
|
|
41046
|
+
function sanitizeProfileSettings(profileId, raw) {
|
|
41047
|
+
if (profileId !== "glances") return {};
|
|
41048
|
+
const bag = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
41049
|
+
const out = {
|
|
41050
|
+
showCpu: true,
|
|
41051
|
+
showMem: true,
|
|
41052
|
+
showLoad: true,
|
|
41053
|
+
showNetwork: true,
|
|
41054
|
+
showDiskIo: true,
|
|
41055
|
+
showFs: true,
|
|
41056
|
+
showProcessList: true,
|
|
41057
|
+
showContainers: true,
|
|
41058
|
+
showSensors: true
|
|
41059
|
+
};
|
|
41060
|
+
for (const plugin of GLANCES_PLUGINS) {
|
|
41061
|
+
const value = bag[plugin.key];
|
|
41062
|
+
if (typeof value === "boolean") out[plugin.key] = value;
|
|
41063
|
+
}
|
|
41064
|
+
return out;
|
|
41065
|
+
}
|
|
41066
|
+
function profileSettingsToArgs(profileId, settings) {
|
|
41067
|
+
if (profileId !== "glances") return [];
|
|
41068
|
+
return glancesSettingsToArgs(sanitizeProfileSettings(profileId, settings));
|
|
41069
|
+
}
|
|
41070
|
+
function spawnArgsForInstance(input) {
|
|
41071
|
+
const extra = profileSettingsToArgs(input.profileId, input.profileSettings);
|
|
41072
|
+
if (input.instanceArgs.length > 0) return [...input.instanceArgs, ...extra];
|
|
41073
|
+
if (extra.length > 0) return [...input.profileArgs, ...extra];
|
|
41074
|
+
}
|
|
41075
|
+
//#endregion
|
|
40566
41076
|
//#region src/pty.ts
|
|
40567
41077
|
/**
|
|
40568
41078
|
* Minimal pty abstraction. The manager depends on this interface, never on
|
|
@@ -40744,6 +41254,356 @@ async function silenceAnalysisFor(deps, deviceId) {
|
|
|
40744
41254
|
if (failures.length > 0) throw new Error(`terminal camera ${deviceId}: could not switch off ${failures.length} analyzer(s) — it will run at full detection cost (${failures.join("; ")})`);
|
|
40745
41255
|
}
|
|
40746
41256
|
//#endregion
|
|
41257
|
+
//#region src/terminal-camera-declarations.ts
|
|
41258
|
+
/**
|
|
41259
|
+
* Feed DeclaredDevices every live declaration plus one deterministic orphan
|
|
41260
|
+
* batch. The generic sweep intentionally refuses an over-limit set; selecting
|
|
41261
|
+
* a batch here drains large historical Terminal orphan sets across convergence
|
|
41262
|
+
* passes without weakening that global safety guard.
|
|
41263
|
+
*/
|
|
41264
|
+
function terminalCameraReconciliationIndex(rows, integrationId, declarations, managedStableIds = /* @__PURE__ */ new Set()) {
|
|
41265
|
+
if (!integrationId) return [];
|
|
41266
|
+
const declared = new Set(declarations.map((camera) => camera.stableId));
|
|
41267
|
+
const owned = rows.filter((row) => row.integrationId === integrationId && (declared.has(row.stableId) || managedStableIds.has(row.stableId) || row.stableId.startsWith("terminal-camera-instance-")));
|
|
41268
|
+
return [...owned.filter((row) => declared.has(row.stableId)), ...owned.filter((row) => !declared.has(row.stableId)).sort((left, right) => left.id - right.id).slice(0, 32)];
|
|
41269
|
+
}
|
|
41270
|
+
/** Explicit persisted instances, never the node × profile template matrix. */
|
|
41271
|
+
function buildTerminalInstanceCameraDeclarations(instances) {
|
|
41272
|
+
return instances.filter((instance) => instance.enabled).map((instance) => ({
|
|
41273
|
+
stableId: instance.cameraStableId,
|
|
41274
|
+
name: instance.name,
|
|
41275
|
+
config: {
|
|
41276
|
+
instanceId: instance.id,
|
|
41277
|
+
nodeId: instance.nodeId,
|
|
41278
|
+
profileId: instance.profileId,
|
|
41279
|
+
profileLabel: instance.profileLabel
|
|
41280
|
+
}
|
|
41281
|
+
}));
|
|
41282
|
+
}
|
|
41283
|
+
/**
|
|
41284
|
+
* `DeviceConfig` materializes schema defaults in memory, so comparing
|
|
41285
|
+
* `config.get()` cannot detect a legacy `{ nodeId }` row. Reconciliation must
|
|
41286
|
+
* inspect the raw persisted blob to make the profile migration durable.
|
|
41287
|
+
*/
|
|
41288
|
+
function needsTerminalCameraConfigMigration(persistedConfig, declaration) {
|
|
41289
|
+
return persistedConfig.instanceId !== declaration.config.instanceId || persistedConfig.nodeId !== declaration.config.nodeId || persistedConfig.profileId !== declaration.config.profileId || persistedConfig.profileLabel !== declaration.config.profileLabel;
|
|
41290
|
+
}
|
|
41291
|
+
//#endregion
|
|
41292
|
+
//#region src/terminal-cell-runs.ts
|
|
41293
|
+
var TERMINAL_DEFAULT_FG = "#d7dce2";
|
|
41294
|
+
var TERMINAL_DEFAULT_BG = "#0b0d10";
|
|
41295
|
+
/**
|
|
41296
|
+
* The 16 ANSI colours, in a dark-theme variant chosen to sit with the existing
|
|
41297
|
+
* frame (`#0b0d10` ground, `#d7dce2` text) rather than the raw xterm defaults,
|
|
41298
|
+
* whose pure `#0000ff` blue and `#008000` green are close to unreadable on a
|
|
41299
|
+
* near-black background at 13px. Index 7 IS the default foreground, so plain
|
|
41300
|
+
* `CSI 37m` text renders identically to unstyled text.
|
|
41301
|
+
*/
|
|
41302
|
+
var TERMINAL_ANSI_PALETTE = [
|
|
41303
|
+
"#282c34",
|
|
41304
|
+
"#e06c75",
|
|
41305
|
+
"#98c379",
|
|
41306
|
+
"#e5c07b",
|
|
41307
|
+
"#61afef",
|
|
41308
|
+
"#c678dd",
|
|
41309
|
+
"#56b6c2",
|
|
41310
|
+
TERMINAL_DEFAULT_FG,
|
|
41311
|
+
"#5c6370",
|
|
41312
|
+
"#ef596f",
|
|
41313
|
+
"#89ca78",
|
|
41314
|
+
"#f0c674",
|
|
41315
|
+
"#6cb6ff",
|
|
41316
|
+
"#d55fde",
|
|
41317
|
+
"#2bbac5",
|
|
41318
|
+
"#ffffff"
|
|
41319
|
+
];
|
|
41320
|
+
/** The six levels of the xterm 6×6×6 colour cube (indices 16-231). */
|
|
41321
|
+
var TERMINAL_CUBE_LEVELS = [
|
|
41322
|
+
0,
|
|
41323
|
+
95,
|
|
41324
|
+
135,
|
|
41325
|
+
175,
|
|
41326
|
+
215,
|
|
41327
|
+
255
|
|
41328
|
+
];
|
|
41329
|
+
var TERMINAL_CUBE_FIRST = 16;
|
|
41330
|
+
var TERMINAL_GRAYSCALE_FIRST = 232;
|
|
41331
|
+
var TERMINAL_GRAYSCALE_BASE = 8;
|
|
41332
|
+
var TERMINAL_GRAYSCALE_STEP = 10;
|
|
41333
|
+
/** SGR 2 keeps the foreground legible; it must not become the background. */
|
|
41334
|
+
var TERMINAL_DIM_WEIGHT = .6;
|
|
41335
|
+
function channel(value) {
|
|
41336
|
+
return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0");
|
|
41337
|
+
}
|
|
41338
|
+
function hex(red, green, blue) {
|
|
41339
|
+
return `#${channel(red)}${channel(green)}${channel(blue)}`;
|
|
41340
|
+
}
|
|
41341
|
+
function parseHex(color) {
|
|
41342
|
+
return [
|
|
41343
|
+
Number.parseInt(color.slice(1, 3), 16),
|
|
41344
|
+
Number.parseInt(color.slice(3, 5), 16),
|
|
41345
|
+
Number.parseInt(color.slice(5, 7), 16)
|
|
41346
|
+
];
|
|
41347
|
+
}
|
|
41348
|
+
/** Resolve an xterm palette index (0-255) to a hex colour. */
|
|
41349
|
+
function terminalPaletteColor(index) {
|
|
41350
|
+
const ansi = TERMINAL_ANSI_PALETTE[index];
|
|
41351
|
+
if (ansi !== void 0) return ansi;
|
|
41352
|
+
if (index >= TERMINAL_GRAYSCALE_FIRST) {
|
|
41353
|
+
const level = TERMINAL_GRAYSCALE_BASE + (index - TERMINAL_GRAYSCALE_FIRST) * TERMINAL_GRAYSCALE_STEP;
|
|
41354
|
+
return hex(level, level, level);
|
|
41355
|
+
}
|
|
41356
|
+
if (index >= TERMINAL_CUBE_FIRST) {
|
|
41357
|
+
const offset = index - TERMINAL_CUBE_FIRST;
|
|
41358
|
+
return hex(TERMINAL_CUBE_LEVELS[Math.floor(offset / 36) % 6] ?? 0, TERMINAL_CUBE_LEVELS[Math.floor(offset / 6) % 6] ?? 0, TERMINAL_CUBE_LEVELS[offset % 6] ?? 0);
|
|
41359
|
+
}
|
|
41360
|
+
return TERMINAL_DEFAULT_FG;
|
|
41361
|
+
}
|
|
41362
|
+
/** Resolve a 0xRRGGBB truecolor value to a hex colour. */
|
|
41363
|
+
function terminalRgbColor(value) {
|
|
41364
|
+
return hex(value >> 16 & 255, value >> 8 & 255, value & 255);
|
|
41365
|
+
}
|
|
41366
|
+
function blend(color, toward, weight) {
|
|
41367
|
+
const [red, green, blue] = parseHex(color);
|
|
41368
|
+
const [targetRed, targetGreen, targetBlue] = parseHex(toward);
|
|
41369
|
+
return hex(red * weight + targetRed * (1 - weight), green * weight + targetGreen * (1 - weight), blue * weight + targetBlue * (1 - weight));
|
|
41370
|
+
}
|
|
41371
|
+
function resolveForeground(cell) {
|
|
41372
|
+
if (cell.isFgRGB()) return terminalRgbColor(cell.getFgColor());
|
|
41373
|
+
if (cell.isFgPalette()) return terminalPaletteColor(cell.getFgColor());
|
|
41374
|
+
return TERMINAL_DEFAULT_FG;
|
|
41375
|
+
}
|
|
41376
|
+
function resolveBackground(cell) {
|
|
41377
|
+
if (cell.isBgRGB()) return terminalRgbColor(cell.getBgColor());
|
|
41378
|
+
if (cell.isBgPalette()) return terminalPaletteColor(cell.getBgColor());
|
|
41379
|
+
return TERMINAL_DEFAULT_BG;
|
|
41380
|
+
}
|
|
41381
|
+
/**
|
|
41382
|
+
* Resolve one cell's attributes into concrete colours.
|
|
41383
|
+
*
|
|
41384
|
+
* Inverse is applied by SWAPPING the already-resolved pair, so inverse over two
|
|
41385
|
+
* defaults is still a visible swap rather than a no-op — that is how a selected
|
|
41386
|
+
* or highlighted row in Glances reads. Invisible is then conceal-by-equality
|
|
41387
|
+
* (foreground painted in its own background): the cell keeps its columns, which
|
|
41388
|
+
* a dropped cell would not, and dropping it would shift the whole rest of the
|
|
41389
|
+
* row left.
|
|
41390
|
+
*/
|
|
41391
|
+
function resolveCellStyle(cell) {
|
|
41392
|
+
const inverse = cell.isInverse() !== 0;
|
|
41393
|
+
const plainFg = resolveForeground(cell);
|
|
41394
|
+
const plainBg = resolveBackground(cell);
|
|
41395
|
+
const background = inverse ? plainFg : plainBg;
|
|
41396
|
+
let foreground = inverse ? plainBg : plainFg;
|
|
41397
|
+
if (cell.isDim() !== 0) foreground = blend(foreground, background, TERMINAL_DIM_WEIGHT);
|
|
41398
|
+
if (cell.isInvisible() !== 0) foreground = background;
|
|
41399
|
+
return {
|
|
41400
|
+
fg: foreground === "#d7dce2" ? null : foreground,
|
|
41401
|
+
bg: background === "#0b0d10" ? null : background,
|
|
41402
|
+
bold: cell.isBold() !== 0
|
|
41403
|
+
};
|
|
41404
|
+
}
|
|
41405
|
+
function sameStyle(left, right) {
|
|
41406
|
+
return left.fg === right.fg && left.bg === right.bg && left.bold === right.bold;
|
|
41407
|
+
}
|
|
41408
|
+
/**
|
|
41409
|
+
* Merge adjacent same-style cells into runs, then drop the trailing run of
|
|
41410
|
+
* default-styled whitespace so a row costs what it draws — the same trim
|
|
41411
|
+
* `translateToString(true)` performs. Whitespace carrying a BACKGROUND is kept:
|
|
41412
|
+
* a green bar of spaces out to the right margin is a pixel Glances drew.
|
|
41413
|
+
*/
|
|
41414
|
+
function buildCellRuns(cells) {
|
|
41415
|
+
const runs = [];
|
|
41416
|
+
let text = "";
|
|
41417
|
+
let style = null;
|
|
41418
|
+
for (const cell of cells) {
|
|
41419
|
+
if (style !== null && sameStyle(style, cell.style)) {
|
|
41420
|
+
text += cell.text;
|
|
41421
|
+
continue;
|
|
41422
|
+
}
|
|
41423
|
+
if (style !== null) runs.push({
|
|
41424
|
+
text,
|
|
41425
|
+
...style
|
|
41426
|
+
});
|
|
41427
|
+
text = cell.text;
|
|
41428
|
+
style = cell.style;
|
|
41429
|
+
}
|
|
41430
|
+
if (style !== null) runs.push({
|
|
41431
|
+
text,
|
|
41432
|
+
...style
|
|
41433
|
+
});
|
|
41434
|
+
while (runs.length > 0) {
|
|
41435
|
+
const last = runs[runs.length - 1];
|
|
41436
|
+
if (last === void 0 || last.bg !== null) break;
|
|
41437
|
+
const trimmed = last.text.replace(/\s+$/u, "");
|
|
41438
|
+
if (trimmed === last.text) break;
|
|
41439
|
+
if (trimmed === "") {
|
|
41440
|
+
runs.pop();
|
|
41441
|
+
continue;
|
|
41442
|
+
}
|
|
41443
|
+
runs[runs.length - 1] = {
|
|
41444
|
+
...last,
|
|
41445
|
+
text: trimmed
|
|
41446
|
+
};
|
|
41447
|
+
break;
|
|
41448
|
+
}
|
|
41449
|
+
return runs;
|
|
41450
|
+
}
|
|
41451
|
+
/**
|
|
41452
|
+
* Monospace families to try, in order — NOT one family and a generic.
|
|
41453
|
+
*
|
|
41454
|
+
* A terminal screen is mostly box-drawing and block characters, and a font
|
|
41455
|
+
* without them renders the frame as noise rather than as missing detail.
|
|
41456
|
+
* `DejaVu Sans Mono` covers them and ships in the hub image; macOS has neither
|
|
41457
|
+
* DejaVu nor fontconfig, so the Mac agent fell through to a generic whose
|
|
41458
|
+
* coverage is not, and its Glances camera came out unreadable while the hub's
|
|
41459
|
+
* was pixel-perfect (measured 2026-08-11, same Glances, two nodes).
|
|
41460
|
+
*
|
|
41461
|
+
* `Menlo` is the fix rather than a guess: it is macOS's default terminal face,
|
|
41462
|
+
* present on every install, and derived from DejaVu Sans Mono — the same glyph
|
|
41463
|
+
* coverage by ancestry. Monaco and Courier New follow as older fallbacks; the
|
|
41464
|
+
* generic stays last so a host with none of them still draws something.
|
|
41465
|
+
*/
|
|
41466
|
+
var TERMINAL_FONT_STACK = "DejaVu Sans Mono,Menlo,Monaco,Courier New,monospace";
|
|
41467
|
+
var TERMINAL_FONT_SIZE = 13;
|
|
41468
|
+
var TERMINAL_TEXT_MARGIN_X = 8;
|
|
41469
|
+
var TERMINAL_ROW_HEIGHT = 15;
|
|
41470
|
+
var TERMINAL_BASELINE_Y = 18;
|
|
41471
|
+
/**
|
|
41472
|
+
* Distance from a row's baseline up to the top of its cell box. Chosen so
|
|
41473
|
+
* consecutive rows tile exactly: row N's box runs from `baseline - this` for
|
|
41474
|
+
* `TERMINAL_ROW_HEIGHT`, and row N+1's box starts where it ends. A background
|
|
41475
|
+
* bar that stopped short would draw as stripes across a `CSI 42m` panel.
|
|
41476
|
+
*/
|
|
41477
|
+
var TERMINAL_CELL_ASCENT = 11.5;
|
|
41478
|
+
var TERMINAL_CELL_WIDTH = TERMINAL_FONT_SIZE * (1233 / 2048);
|
|
41479
|
+
/** Two decimals is under a tenth of a pixel and keeps the SVG small. */
|
|
41480
|
+
function coordinate(value) {
|
|
41481
|
+
return String(Number(value.toFixed(2)));
|
|
41482
|
+
}
|
|
41483
|
+
function escapeXml(value) {
|
|
41484
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
41485
|
+
}
|
|
41486
|
+
/**
|
|
41487
|
+
* Render already-interpreted terminal rows into a compact MJPEG frame.
|
|
41488
|
+
*
|
|
41489
|
+
* `xml:space="preserve"` is load-bearing, not tidiness. SVG `<text>` collapses
|
|
41490
|
+
* runs of whitespace by default, and a terminal's entire column alignment IS
|
|
41491
|
+
* runs of whitespace — Glances pads every field with spaces. Without it the
|
|
41492
|
+
* frame drew each line at roughly half its true width, crammed into the
|
|
41493
|
+
* top-left of a mostly-black image, while the SAME session over `attach`
|
|
41494
|
+
* looked perfect — which is exactly how the operator reported it. Measured in
|
|
41495
|
+
* the hub's own rasterizer 2026-08-11: `AAA<10 spaces>BBB<3>CCC` drew 92 px
|
|
41496
|
+
* collapsed against 178 px preserved.
|
|
41497
|
+
*
|
|
41498
|
+
* Every run is anchored at its OWN column (`x = margin + startCol * cellW`),
|
|
41499
|
+
* never appended to the one before it, so the background rects and the glyphs
|
|
41500
|
+
* are placed off the same grid and cannot drift apart. `textLength` is emitted
|
|
41501
|
+
* with it because it is the correct declaration and renderers that honour it
|
|
41502
|
+
* get an exact grid — but it is not what makes this work: librsvg, which sharp
|
|
41503
|
+
* uses, ignores it outright (measured 2026-08-12, a 120-glyph run pinned to
|
|
41504
|
+
* 600 px still drew its natural 937 px). The anchoring is the guarantee.
|
|
41505
|
+
*/
|
|
41506
|
+
function renderTerminalSvg(rows) {
|
|
41507
|
+
const backgrounds = [];
|
|
41508
|
+
const texts = [];
|
|
41509
|
+
rows.slice(0, 40).forEach((row, index) => {
|
|
41510
|
+
const baseline = TERMINAL_BASELINE_Y + index * TERMINAL_ROW_HEIGHT;
|
|
41511
|
+
const top = baseline - TERMINAL_CELL_ASCENT;
|
|
41512
|
+
let column = 0;
|
|
41513
|
+
for (const run of row) {
|
|
41514
|
+
if (column >= 120) break;
|
|
41515
|
+
const clipped = clipRun(run, 120 - column);
|
|
41516
|
+
const columns = [...clipped].length;
|
|
41517
|
+
if (columns === 0) continue;
|
|
41518
|
+
const x = TERMINAL_TEXT_MARGIN_X + column * TERMINAL_CELL_WIDTH;
|
|
41519
|
+
const width = columns * TERMINAL_CELL_WIDTH;
|
|
41520
|
+
if (run.bg !== null) backgrounds.push(`<rect x="${coordinate(x)}" y="${coordinate(top)}" width="${coordinate(width)}" height="${String(TERMINAL_ROW_HEIGHT)}" fill="${run.bg}"/>`);
|
|
41521
|
+
if (clipped.trim() !== "") {
|
|
41522
|
+
const fill = run.fg === null ? "" : ` fill="${run.fg}"`;
|
|
41523
|
+
const weight = run.bold ? " font-weight=\"bold\"" : "";
|
|
41524
|
+
texts.push(`<text xml:space="preserve" x="${coordinate(x)}" y="${coordinate(baseline)}" textLength="${coordinate(width)}" lengthAdjust="spacingAndGlyphs"${fill}${weight}>${escapeXml(clipped)}</text>`);
|
|
41525
|
+
}
|
|
41526
|
+
column += columns;
|
|
41527
|
+
}
|
|
41528
|
+
});
|
|
41529
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${String(960)}" height="${String(640)}"><rect width="100%" height="100%" fill="${TERMINAL_DEFAULT_BG}"/>${backgrounds.join("")}<g fill="${TERMINAL_DEFAULT_FG}" font-family="${TERMINAL_FONT_STACK}" font-size="${String(TERMINAL_FONT_SIZE)}">${texts.join("")}</g></svg>`;
|
|
41530
|
+
}
|
|
41531
|
+
/** Cut a run to the columns still left in the row, by code point not unit. */
|
|
41532
|
+
function clipRun(run, remaining) {
|
|
41533
|
+
const points = [...run.text];
|
|
41534
|
+
return points.length <= remaining ? run.text : points.slice(0, remaining).join("");
|
|
41535
|
+
}
|
|
41536
|
+
async function renderTerminalJpeg(rows) {
|
|
41537
|
+
return (0, sharp.default)(Buffer.from(renderTerminalSvg(rows))).jpeg({
|
|
41538
|
+
quality: 82,
|
|
41539
|
+
chromaSubsampling: "4:2:0"
|
|
41540
|
+
}).toBuffer();
|
|
41541
|
+
}
|
|
41542
|
+
//#endregion
|
|
41543
|
+
//#region src/terminal-camera-device.ts
|
|
41544
|
+
var terminalCameraSchema = object({
|
|
41545
|
+
instanceId: string().min(1).optional(),
|
|
41546
|
+
nodeId: string().min(1),
|
|
41547
|
+
profileId: string().min(1).default("monitor"),
|
|
41548
|
+
profileLabel: string().min(1).default("BTM")
|
|
41549
|
+
});
|
|
41550
|
+
var relay = null;
|
|
41551
|
+
function installTerminalCameraRelay(next) {
|
|
41552
|
+
relay = next;
|
|
41553
|
+
}
|
|
41554
|
+
var TerminalCameraDevice = class extends BaseDevice {
|
|
41555
|
+
features = [DeviceFeature.NativeSnapshot];
|
|
41556
|
+
constructor(ctx) {
|
|
41557
|
+
super(ctx, terminalCameraSchema, { type: ctx.deviceMeta.type });
|
|
41558
|
+
this.ctx.registerNativeCap(streamCatalogCapability, { getCatalog: async ({ deviceId }) => {
|
|
41559
|
+
if (deviceId !== this.id) return [];
|
|
41560
|
+
return this.catalog();
|
|
41561
|
+
} });
|
|
41562
|
+
this.ctx.registerNativeCap(snapshotCapability, {
|
|
41563
|
+
getSnapshot: async ({ deviceId }) => {
|
|
41564
|
+
if (deviceId !== this.id) throw new Error(`TerminalCameraDevice: deviceId mismatch, expected ${String(this.id)}, got ${String(deviceId)}`);
|
|
41565
|
+
const activeRelay = relay;
|
|
41566
|
+
if (!activeRelay) throw new Error("terminal camera relay is unavailable");
|
|
41567
|
+
return {
|
|
41568
|
+
base64: (await activeRelay.snapshot(this.relayInstanceId(), this.config.get("nodeId"), this.config.get("profileId"))).toString("base64"),
|
|
41569
|
+
contentType: "image/jpeg"
|
|
41570
|
+
};
|
|
41571
|
+
},
|
|
41572
|
+
invalidateCache: async () => {}
|
|
41573
|
+
});
|
|
41574
|
+
this.markOnline(true);
|
|
41575
|
+
}
|
|
41576
|
+
async catalog() {
|
|
41577
|
+
const activeRelay = relay;
|
|
41578
|
+
if (!activeRelay) throw new Error("terminal camera relay is unavailable");
|
|
41579
|
+
const nodeId = this.config.get("nodeId");
|
|
41580
|
+
const profileId = this.config.get("profileId");
|
|
41581
|
+
const instanceId = this.relayInstanceId();
|
|
41582
|
+
return [{
|
|
41583
|
+
camStreamId: profileId,
|
|
41584
|
+
kind: "pull-http",
|
|
41585
|
+
url: activeRelay.streamUrl(instanceId, nodeId, profileId),
|
|
41586
|
+
codec: "h264",
|
|
41587
|
+
resolution: {
|
|
41588
|
+
width: 960,
|
|
41589
|
+
height: 640
|
|
41590
|
+
},
|
|
41591
|
+
fps: 2,
|
|
41592
|
+
label: this.config.get("profileLabel")
|
|
41593
|
+
}];
|
|
41594
|
+
}
|
|
41595
|
+
setNodeOnline(online) {
|
|
41596
|
+
this.markOnline(online);
|
|
41597
|
+
if (!online) relay?.closeInstance(this.relayInstanceId());
|
|
41598
|
+
}
|
|
41599
|
+
async removeDevice() {
|
|
41600
|
+
await relay?.closeInstance(this.relayInstanceId());
|
|
41601
|
+
}
|
|
41602
|
+
relayInstanceId() {
|
|
41603
|
+
return this.config.get("instanceId") ?? `legacy:${this.stableId}`;
|
|
41604
|
+
}
|
|
41605
|
+
};
|
|
41606
|
+
//#endregion
|
|
40747
41607
|
//#region ../../node_modules/@xterm/addon-serialize/lib/addon-serialize.js
|
|
40748
41608
|
var require_addon_serialize = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
40749
41609
|
(function(e, t) {
|
|
@@ -45551,174 +46411,9 @@ var require_xterm_headless = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
45551
46411
|
})();
|
|
45552
46412
|
}));
|
|
45553
46413
|
//#endregion
|
|
45554
|
-
//#region src/
|
|
46414
|
+
//#region src/xterm-screen.ts
|
|
45555
46415
|
var import_addon_serialize = require_addon_serialize();
|
|
45556
46416
|
var import_xterm_headless = require_xterm_headless();
|
|
45557
|
-
var TERMINAL_DEFAULT_FG = "#d7dce2";
|
|
45558
|
-
var TERMINAL_DEFAULT_BG = "#0b0d10";
|
|
45559
|
-
/**
|
|
45560
|
-
* The 16 ANSI colours, in a dark-theme variant chosen to sit with the existing
|
|
45561
|
-
* frame (`#0b0d10` ground, `#d7dce2` text) rather than the raw xterm defaults,
|
|
45562
|
-
* whose pure `#0000ff` blue and `#008000` green are close to unreadable on a
|
|
45563
|
-
* near-black background at 13px. Index 7 IS the default foreground, so plain
|
|
45564
|
-
* `CSI 37m` text renders identically to unstyled text.
|
|
45565
|
-
*/
|
|
45566
|
-
var TERMINAL_ANSI_PALETTE = [
|
|
45567
|
-
"#282c34",
|
|
45568
|
-
"#e06c75",
|
|
45569
|
-
"#98c379",
|
|
45570
|
-
"#e5c07b",
|
|
45571
|
-
"#61afef",
|
|
45572
|
-
"#c678dd",
|
|
45573
|
-
"#56b6c2",
|
|
45574
|
-
TERMINAL_DEFAULT_FG,
|
|
45575
|
-
"#5c6370",
|
|
45576
|
-
"#ef596f",
|
|
45577
|
-
"#89ca78",
|
|
45578
|
-
"#f0c674",
|
|
45579
|
-
"#6cb6ff",
|
|
45580
|
-
"#d55fde",
|
|
45581
|
-
"#2bbac5",
|
|
45582
|
-
"#ffffff"
|
|
45583
|
-
];
|
|
45584
|
-
/** The six levels of the xterm 6×6×6 colour cube (indices 16-231). */
|
|
45585
|
-
var TERMINAL_CUBE_LEVELS = [
|
|
45586
|
-
0,
|
|
45587
|
-
95,
|
|
45588
|
-
135,
|
|
45589
|
-
175,
|
|
45590
|
-
215,
|
|
45591
|
-
255
|
|
45592
|
-
];
|
|
45593
|
-
var TERMINAL_CUBE_FIRST = 16;
|
|
45594
|
-
var TERMINAL_GRAYSCALE_FIRST = 232;
|
|
45595
|
-
var TERMINAL_GRAYSCALE_BASE = 8;
|
|
45596
|
-
var TERMINAL_GRAYSCALE_STEP = 10;
|
|
45597
|
-
/** SGR 2 keeps the foreground legible; it must not become the background. */
|
|
45598
|
-
var TERMINAL_DIM_WEIGHT = .6;
|
|
45599
|
-
function channel(value) {
|
|
45600
|
-
return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0");
|
|
45601
|
-
}
|
|
45602
|
-
function hex(red, green, blue) {
|
|
45603
|
-
return `#${channel(red)}${channel(green)}${channel(blue)}`;
|
|
45604
|
-
}
|
|
45605
|
-
function parseHex(color) {
|
|
45606
|
-
return [
|
|
45607
|
-
Number.parseInt(color.slice(1, 3), 16),
|
|
45608
|
-
Number.parseInt(color.slice(3, 5), 16),
|
|
45609
|
-
Number.parseInt(color.slice(5, 7), 16)
|
|
45610
|
-
];
|
|
45611
|
-
}
|
|
45612
|
-
/** Resolve an xterm palette index (0-255) to a hex colour. */
|
|
45613
|
-
function terminalPaletteColor(index) {
|
|
45614
|
-
const ansi = TERMINAL_ANSI_PALETTE[index];
|
|
45615
|
-
if (ansi !== void 0) return ansi;
|
|
45616
|
-
if (index >= TERMINAL_GRAYSCALE_FIRST) {
|
|
45617
|
-
const level = TERMINAL_GRAYSCALE_BASE + (index - TERMINAL_GRAYSCALE_FIRST) * TERMINAL_GRAYSCALE_STEP;
|
|
45618
|
-
return hex(level, level, level);
|
|
45619
|
-
}
|
|
45620
|
-
if (index >= TERMINAL_CUBE_FIRST) {
|
|
45621
|
-
const offset = index - TERMINAL_CUBE_FIRST;
|
|
45622
|
-
return hex(TERMINAL_CUBE_LEVELS[Math.floor(offset / 36) % 6] ?? 0, TERMINAL_CUBE_LEVELS[Math.floor(offset / 6) % 6] ?? 0, TERMINAL_CUBE_LEVELS[offset % 6] ?? 0);
|
|
45623
|
-
}
|
|
45624
|
-
return TERMINAL_DEFAULT_FG;
|
|
45625
|
-
}
|
|
45626
|
-
/** Resolve a 0xRRGGBB truecolor value to a hex colour. */
|
|
45627
|
-
function terminalRgbColor(value) {
|
|
45628
|
-
return hex(value >> 16 & 255, value >> 8 & 255, value & 255);
|
|
45629
|
-
}
|
|
45630
|
-
function blend(color, toward, weight) {
|
|
45631
|
-
const [red, green, blue] = parseHex(color);
|
|
45632
|
-
const [targetRed, targetGreen, targetBlue] = parseHex(toward);
|
|
45633
|
-
return hex(red * weight + targetRed * (1 - weight), green * weight + targetGreen * (1 - weight), blue * weight + targetBlue * (1 - weight));
|
|
45634
|
-
}
|
|
45635
|
-
function resolveForeground(cell) {
|
|
45636
|
-
if (cell.isFgRGB()) return terminalRgbColor(cell.getFgColor());
|
|
45637
|
-
if (cell.isFgPalette()) return terminalPaletteColor(cell.getFgColor());
|
|
45638
|
-
return TERMINAL_DEFAULT_FG;
|
|
45639
|
-
}
|
|
45640
|
-
function resolveBackground(cell) {
|
|
45641
|
-
if (cell.isBgRGB()) return terminalRgbColor(cell.getBgColor());
|
|
45642
|
-
if (cell.isBgPalette()) return terminalPaletteColor(cell.getBgColor());
|
|
45643
|
-
return TERMINAL_DEFAULT_BG;
|
|
45644
|
-
}
|
|
45645
|
-
/**
|
|
45646
|
-
* Resolve one cell's attributes into concrete colours.
|
|
45647
|
-
*
|
|
45648
|
-
* Inverse is applied by SWAPPING the already-resolved pair, so inverse over two
|
|
45649
|
-
* defaults is still a visible swap rather than a no-op — that is how a selected
|
|
45650
|
-
* or highlighted row in Glances reads. Invisible is then conceal-by-equality
|
|
45651
|
-
* (foreground painted in its own background): the cell keeps its columns, which
|
|
45652
|
-
* a dropped cell would not, and dropping it would shift the whole rest of the
|
|
45653
|
-
* row left.
|
|
45654
|
-
*/
|
|
45655
|
-
function resolveCellStyle(cell) {
|
|
45656
|
-
const inverse = cell.isInverse() !== 0;
|
|
45657
|
-
const plainFg = resolveForeground(cell);
|
|
45658
|
-
const plainBg = resolveBackground(cell);
|
|
45659
|
-
const background = inverse ? plainFg : plainBg;
|
|
45660
|
-
let foreground = inverse ? plainBg : plainFg;
|
|
45661
|
-
if (cell.isDim() !== 0) foreground = blend(foreground, background, TERMINAL_DIM_WEIGHT);
|
|
45662
|
-
if (cell.isInvisible() !== 0) foreground = background;
|
|
45663
|
-
return {
|
|
45664
|
-
fg: foreground === "#d7dce2" ? null : foreground,
|
|
45665
|
-
bg: background === "#0b0d10" ? null : background,
|
|
45666
|
-
bold: cell.isBold() !== 0
|
|
45667
|
-
};
|
|
45668
|
-
}
|
|
45669
|
-
function sameStyle(left, right) {
|
|
45670
|
-
return left.fg === right.fg && left.bg === right.bg && left.bold === right.bold;
|
|
45671
|
-
}
|
|
45672
|
-
/**
|
|
45673
|
-
* Merge adjacent same-style cells into runs, then drop the trailing run of
|
|
45674
|
-
* default-styled whitespace so a row costs what it draws — the same trim
|
|
45675
|
-
* `translateToString(true)` performs. Whitespace carrying a BACKGROUND is kept:
|
|
45676
|
-
* a green bar of spaces out to the right margin is a pixel Glances drew.
|
|
45677
|
-
*/
|
|
45678
|
-
function buildCellRuns(cells) {
|
|
45679
|
-
const runs = [];
|
|
45680
|
-
let text = "";
|
|
45681
|
-
let style = null;
|
|
45682
|
-
for (const cell of cells) {
|
|
45683
|
-
if (style !== null && sameStyle(style, cell.style)) {
|
|
45684
|
-
text += cell.text;
|
|
45685
|
-
continue;
|
|
45686
|
-
}
|
|
45687
|
-
if (style !== null) runs.push({
|
|
45688
|
-
text,
|
|
45689
|
-
...style
|
|
45690
|
-
});
|
|
45691
|
-
text = cell.text;
|
|
45692
|
-
style = cell.style;
|
|
45693
|
-
}
|
|
45694
|
-
if (style !== null) runs.push({
|
|
45695
|
-
text,
|
|
45696
|
-
...style
|
|
45697
|
-
});
|
|
45698
|
-
while (runs.length > 0) {
|
|
45699
|
-
const last = runs[runs.length - 1];
|
|
45700
|
-
if (last === void 0 || last.bg !== null) break;
|
|
45701
|
-
const trimmed = last.text.replace(/\s+$/u, "");
|
|
45702
|
-
if (trimmed === last.text) break;
|
|
45703
|
-
if (trimmed === "") {
|
|
45704
|
-
runs.pop();
|
|
45705
|
-
continue;
|
|
45706
|
-
}
|
|
45707
|
-
runs[runs.length - 1] = {
|
|
45708
|
-
...last,
|
|
45709
|
-
text: trimmed
|
|
45710
|
-
};
|
|
45711
|
-
break;
|
|
45712
|
-
}
|
|
45713
|
-
return runs;
|
|
45714
|
-
}
|
|
45715
|
-
//#endregion
|
|
45716
|
-
//#region src/xterm-screen.ts
|
|
45717
|
-
/**
|
|
45718
|
-
* Real {@link ScreenBuffer} backed by `@xterm/headless` — the DOM-less xterm
|
|
45719
|
-
* build — plus the serialize addon, which turns the current buffer into a
|
|
45720
|
-
* self-contained repaint escape sequence for reconnecting clients.
|
|
45721
|
-
*/
|
|
45722
46417
|
var SCROLLBACK_LINES = 2e3;
|
|
45723
46418
|
function createXtermScreen(cols, rows) {
|
|
45724
46419
|
const term = new import_xterm_headless.Terminal({
|
|
@@ -45785,196 +46480,6 @@ function createXtermScreen(cols, rows) {
|
|
|
45785
46480
|
};
|
|
45786
46481
|
}
|
|
45787
46482
|
//#endregion
|
|
45788
|
-
//#region src/terminal-camera-declarations.ts
|
|
45789
|
-
/**
|
|
45790
|
-
* Feed DeclaredDevices every live declaration plus one deterministic orphan
|
|
45791
|
-
* batch. The generic sweep intentionally refuses an over-limit set; selecting
|
|
45792
|
-
* a batch here drains large historical Terminal orphan sets across convergence
|
|
45793
|
-
* passes without weakening that global safety guard.
|
|
45794
|
-
*/
|
|
45795
|
-
function terminalCameraReconciliationIndex(rows, integrationId, declarations, managedStableIds = /* @__PURE__ */ new Set()) {
|
|
45796
|
-
if (!integrationId) return [];
|
|
45797
|
-
const declared = new Set(declarations.map((camera) => camera.stableId));
|
|
45798
|
-
const owned = rows.filter((row) => row.integrationId === integrationId && (declared.has(row.stableId) || managedStableIds.has(row.stableId) || row.stableId.startsWith("terminal-camera-instance-")));
|
|
45799
|
-
return [...owned.filter((row) => declared.has(row.stableId)), ...owned.filter((row) => !declared.has(row.stableId)).sort((left, right) => left.id - right.id).slice(0, 32)];
|
|
45800
|
-
}
|
|
45801
|
-
/** Explicit persisted instances, never the node × profile template matrix. */
|
|
45802
|
-
function buildTerminalInstanceCameraDeclarations(instances) {
|
|
45803
|
-
return instances.filter((instance) => instance.enabled).map((instance) => ({
|
|
45804
|
-
stableId: instance.cameraStableId,
|
|
45805
|
-
name: instance.name,
|
|
45806
|
-
config: {
|
|
45807
|
-
instanceId: instance.id,
|
|
45808
|
-
nodeId: instance.nodeId,
|
|
45809
|
-
profileId: instance.profileId,
|
|
45810
|
-
profileLabel: instance.profileLabel
|
|
45811
|
-
}
|
|
45812
|
-
}));
|
|
45813
|
-
}
|
|
45814
|
-
/**
|
|
45815
|
-
* `DeviceConfig` materializes schema defaults in memory, so comparing
|
|
45816
|
-
* `config.get()` cannot detect a legacy `{ nodeId }` row. Reconciliation must
|
|
45817
|
-
* inspect the raw persisted blob to make the profile migration durable.
|
|
45818
|
-
*/
|
|
45819
|
-
function needsTerminalCameraConfigMigration(persistedConfig, declaration) {
|
|
45820
|
-
return persistedConfig.instanceId !== declaration.config.instanceId || persistedConfig.nodeId !== declaration.config.nodeId || persistedConfig.profileId !== declaration.config.profileId || persistedConfig.profileLabel !== declaration.config.profileLabel;
|
|
45821
|
-
}
|
|
45822
|
-
/**
|
|
45823
|
-
* Monospace families to try, in order — NOT one family and a generic.
|
|
45824
|
-
*
|
|
45825
|
-
* A terminal screen is mostly box-drawing and block characters, and a font
|
|
45826
|
-
* without them renders the frame as noise rather than as missing detail.
|
|
45827
|
-
* `DejaVu Sans Mono` covers them and ships in the hub image; macOS has neither
|
|
45828
|
-
* DejaVu nor fontconfig, so the Mac agent fell through to a generic whose
|
|
45829
|
-
* coverage is not, and its Glances camera came out unreadable while the hub's
|
|
45830
|
-
* was pixel-perfect (measured 2026-08-11, same Glances, two nodes).
|
|
45831
|
-
*
|
|
45832
|
-
* `Menlo` is the fix rather than a guess: it is macOS's default terminal face,
|
|
45833
|
-
* present on every install, and derived from DejaVu Sans Mono — the same glyph
|
|
45834
|
-
* coverage by ancestry. Monaco and Courier New follow as older fallbacks; the
|
|
45835
|
-
* generic stays last so a host with none of them still draws something.
|
|
45836
|
-
*/
|
|
45837
|
-
var TERMINAL_FONT_STACK = "DejaVu Sans Mono,Menlo,Monaco,Courier New,monospace";
|
|
45838
|
-
var TERMINAL_FONT_SIZE = 13;
|
|
45839
|
-
var TERMINAL_TEXT_MARGIN_X = 8;
|
|
45840
|
-
var TERMINAL_ROW_HEIGHT = 15;
|
|
45841
|
-
var TERMINAL_BASELINE_Y = 18;
|
|
45842
|
-
/**
|
|
45843
|
-
* Distance from a row's baseline up to the top of its cell box. Chosen so
|
|
45844
|
-
* consecutive rows tile exactly: row N's box runs from `baseline - this` for
|
|
45845
|
-
* `TERMINAL_ROW_HEIGHT`, and row N+1's box starts where it ends. A background
|
|
45846
|
-
* bar that stopped short would draw as stripes across a `CSI 42m` panel.
|
|
45847
|
-
*/
|
|
45848
|
-
var TERMINAL_CELL_ASCENT = 11.5;
|
|
45849
|
-
var TERMINAL_CELL_WIDTH = TERMINAL_FONT_SIZE * (1233 / 2048);
|
|
45850
|
-
/** Two decimals is under a tenth of a pixel and keeps the SVG small. */
|
|
45851
|
-
function coordinate(value) {
|
|
45852
|
-
return String(Number(value.toFixed(2)));
|
|
45853
|
-
}
|
|
45854
|
-
function escapeXml(value) {
|
|
45855
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
45856
|
-
}
|
|
45857
|
-
/**
|
|
45858
|
-
* Render already-interpreted terminal rows into a compact MJPEG frame.
|
|
45859
|
-
*
|
|
45860
|
-
* `xml:space="preserve"` is load-bearing, not tidiness. SVG `<text>` collapses
|
|
45861
|
-
* runs of whitespace by default, and a terminal's entire column alignment IS
|
|
45862
|
-
* runs of whitespace — Glances pads every field with spaces. Without it the
|
|
45863
|
-
* frame drew each line at roughly half its true width, crammed into the
|
|
45864
|
-
* top-left of a mostly-black image, while the SAME session over `attach`
|
|
45865
|
-
* looked perfect — which is exactly how the operator reported it. Measured in
|
|
45866
|
-
* the hub's own rasterizer 2026-08-11: `AAA<10 spaces>BBB<3>CCC` drew 92 px
|
|
45867
|
-
* collapsed against 178 px preserved.
|
|
45868
|
-
*
|
|
45869
|
-
* Every run is anchored at its OWN column (`x = margin + startCol * cellW`),
|
|
45870
|
-
* never appended to the one before it, so the background rects and the glyphs
|
|
45871
|
-
* are placed off the same grid and cannot drift apart. `textLength` is emitted
|
|
45872
|
-
* with it because it is the correct declaration and renderers that honour it
|
|
45873
|
-
* get an exact grid — but it is not what makes this work: librsvg, which sharp
|
|
45874
|
-
* uses, ignores it outright (measured 2026-08-12, a 120-glyph run pinned to
|
|
45875
|
-
* 600 px still drew its natural 937 px). The anchoring is the guarantee.
|
|
45876
|
-
*/
|
|
45877
|
-
function renderTerminalSvg(rows) {
|
|
45878
|
-
const backgrounds = [];
|
|
45879
|
-
const texts = [];
|
|
45880
|
-
rows.slice(0, 40).forEach((row, index) => {
|
|
45881
|
-
const baseline = TERMINAL_BASELINE_Y + index * TERMINAL_ROW_HEIGHT;
|
|
45882
|
-
const top = baseline - TERMINAL_CELL_ASCENT;
|
|
45883
|
-
let column = 0;
|
|
45884
|
-
for (const run of row) {
|
|
45885
|
-
if (column >= 120) break;
|
|
45886
|
-
const clipped = clipRun(run, 120 - column);
|
|
45887
|
-
const columns = [...clipped].length;
|
|
45888
|
-
if (columns === 0) continue;
|
|
45889
|
-
const x = TERMINAL_TEXT_MARGIN_X + column * TERMINAL_CELL_WIDTH;
|
|
45890
|
-
const width = columns * TERMINAL_CELL_WIDTH;
|
|
45891
|
-
if (run.bg !== null) backgrounds.push(`<rect x="${coordinate(x)}" y="${coordinate(top)}" width="${coordinate(width)}" height="${String(TERMINAL_ROW_HEIGHT)}" fill="${run.bg}"/>`);
|
|
45892
|
-
if (clipped.trim() !== "") {
|
|
45893
|
-
const fill = run.fg === null ? "" : ` fill="${run.fg}"`;
|
|
45894
|
-
const weight = run.bold ? " font-weight=\"bold\"" : "";
|
|
45895
|
-
texts.push(`<text xml:space="preserve" x="${coordinate(x)}" y="${coordinate(baseline)}" textLength="${coordinate(width)}" lengthAdjust="spacingAndGlyphs"${fill}${weight}>${escapeXml(clipped)}</text>`);
|
|
45896
|
-
}
|
|
45897
|
-
column += columns;
|
|
45898
|
-
}
|
|
45899
|
-
});
|
|
45900
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${String(960)}" height="${String(640)}"><rect width="100%" height="100%" fill="${TERMINAL_DEFAULT_BG}"/>${backgrounds.join("")}<g fill="${TERMINAL_DEFAULT_FG}" font-family="${TERMINAL_FONT_STACK}" font-size="${String(TERMINAL_FONT_SIZE)}">${texts.join("")}</g></svg>`;
|
|
45901
|
-
}
|
|
45902
|
-
/** Cut a run to the columns still left in the row, by code point not unit. */
|
|
45903
|
-
function clipRun(run, remaining) {
|
|
45904
|
-
const points = [...run.text];
|
|
45905
|
-
return points.length <= remaining ? run.text : points.slice(0, remaining).join("");
|
|
45906
|
-
}
|
|
45907
|
-
async function renderTerminalJpeg(rows) {
|
|
45908
|
-
return (0, sharp.default)(Buffer.from(renderTerminalSvg(rows))).jpeg({
|
|
45909
|
-
quality: 82,
|
|
45910
|
-
chromaSubsampling: "4:2:0"
|
|
45911
|
-
}).toBuffer();
|
|
45912
|
-
}
|
|
45913
|
-
//#endregion
|
|
45914
|
-
//#region src/terminal-camera-device.ts
|
|
45915
|
-
var terminalCameraSchema = object({
|
|
45916
|
-
instanceId: string().min(1).optional(),
|
|
45917
|
-
nodeId: string().min(1),
|
|
45918
|
-
profileId: string().min(1).default("monitor"),
|
|
45919
|
-
profileLabel: string().min(1).default("BTM")
|
|
45920
|
-
});
|
|
45921
|
-
var relay = null;
|
|
45922
|
-
function installTerminalCameraRelay(next) {
|
|
45923
|
-
relay = next;
|
|
45924
|
-
}
|
|
45925
|
-
var TerminalCameraDevice = class extends BaseDevice {
|
|
45926
|
-
features = [DeviceFeature.NativeSnapshot];
|
|
45927
|
-
constructor(ctx) {
|
|
45928
|
-
super(ctx, terminalCameraSchema, { type: ctx.deviceMeta.type });
|
|
45929
|
-
this.ctx.registerNativeCap(streamCatalogCapability, { getCatalog: async ({ deviceId }) => {
|
|
45930
|
-
if (deviceId !== this.id) return [];
|
|
45931
|
-
return this.catalog();
|
|
45932
|
-
} });
|
|
45933
|
-
this.ctx.registerNativeCap(snapshotCapability, {
|
|
45934
|
-
getSnapshot: async ({ deviceId }) => {
|
|
45935
|
-
if (deviceId !== this.id) throw new Error(`TerminalCameraDevice: deviceId mismatch, expected ${String(this.id)}, got ${String(deviceId)}`);
|
|
45936
|
-
const activeRelay = relay;
|
|
45937
|
-
if (!activeRelay) throw new Error("terminal camera relay is unavailable");
|
|
45938
|
-
return {
|
|
45939
|
-
base64: (await activeRelay.snapshot(this.relayInstanceId(), this.config.get("nodeId"), this.config.get("profileId"))).toString("base64"),
|
|
45940
|
-
contentType: "image/jpeg"
|
|
45941
|
-
};
|
|
45942
|
-
},
|
|
45943
|
-
invalidateCache: async () => {}
|
|
45944
|
-
});
|
|
45945
|
-
this.markOnline(true);
|
|
45946
|
-
}
|
|
45947
|
-
async catalog() {
|
|
45948
|
-
const activeRelay = relay;
|
|
45949
|
-
if (!activeRelay) throw new Error("terminal camera relay is unavailable");
|
|
45950
|
-
const nodeId = this.config.get("nodeId");
|
|
45951
|
-
const profileId = this.config.get("profileId");
|
|
45952
|
-
const instanceId = this.relayInstanceId();
|
|
45953
|
-
return [{
|
|
45954
|
-
camStreamId: profileId,
|
|
45955
|
-
kind: "pull-http",
|
|
45956
|
-
url: activeRelay.streamUrl(instanceId, nodeId, profileId),
|
|
45957
|
-
codec: "h264",
|
|
45958
|
-
resolution: {
|
|
45959
|
-
width: 960,
|
|
45960
|
-
height: 640
|
|
45961
|
-
},
|
|
45962
|
-
fps: 2,
|
|
45963
|
-
label: this.config.get("profileLabel")
|
|
45964
|
-
}];
|
|
45965
|
-
}
|
|
45966
|
-
setNodeOnline(online) {
|
|
45967
|
-
this.markOnline(online);
|
|
45968
|
-
if (!online) relay?.closeInstance(this.relayInstanceId());
|
|
45969
|
-
}
|
|
45970
|
-
async removeDevice() {
|
|
45971
|
-
await relay?.closeInstance(this.relayInstanceId());
|
|
45972
|
-
}
|
|
45973
|
-
relayInstanceId() {
|
|
45974
|
-
return this.config.get("instanceId") ?? `legacy:${this.stableId}`;
|
|
45975
|
-
}
|
|
45976
|
-
};
|
|
45977
|
-
//#endregion
|
|
45978
46483
|
//#region src/terminal-camera-relay.ts
|
|
45979
46484
|
var MJPEG_BOUNDARY = "camstack-terminal-frame";
|
|
45980
46485
|
var SESSION_IDLE_MS = 3e4;
|
|
@@ -46525,13 +47030,34 @@ function newTerminalCameraStableId(instanceId) {
|
|
|
46525
47030
|
* Legacy automatic cameras are migration candidates only. A tombstone is
|
|
46526
47031
|
* durable deletion intent, so a lingering failed device removal must never
|
|
46527
47032
|
* make that camera adoptable again.
|
|
47033
|
+
*
|
|
47034
|
+
* ## `config` is load-bearing — this read can never be `projection: 'slim'`
|
|
47035
|
+
*
|
|
47036
|
+
* `nodeId`, `profileId` and `profileLabel` all live in the device's `config`,
|
|
47037
|
+
* and a row without `nodeId` is skipped. The slim projection returns
|
|
47038
|
+
* `config: {}` for every row, so a slim answer here is shape-identical to a
|
|
47039
|
+
* fleet that has no legacy cameras — the whole migration section disappears
|
|
47040
|
+
* and nothing says why. `legacy-camera-read-shape.spec.ts` is the arm on that.
|
|
47041
|
+
*
|
|
47042
|
+
* `onSkipped` is why the disappearance would now be visible: a row that LOOKS
|
|
47043
|
+
* like a legacy camera (`terminal-camera-*`, not an instance camera, not
|
|
47044
|
+
* tombstoned, not already adopted) but carries no `nodeId` is reported with
|
|
47045
|
+
* its numeric device id, so the caller can log it per-camera. Rows that are
|
|
47046
|
+
* not candidates at all are silent — a fleet of 1 017 devices must not
|
|
47047
|
+
* produce 1 017 lines to say none of them is a Terminal monitor.
|
|
46528
47048
|
*/
|
|
46529
|
-
function listLegacyTerminalCameraCandidates(rows, instanceStableIds, tombstones) {
|
|
47049
|
+
function listLegacyTerminalCameraCandidates(rows, instanceStableIds, tombstones, onSkipped) {
|
|
46530
47050
|
const legacy = [];
|
|
46531
47051
|
for (const row of rows) {
|
|
46532
47052
|
if (tombstones.has(row.stableId) || instanceStableIds.has(row.stableId) || row.stableId.startsWith("terminal-camera-instance-") || !row.stableId.startsWith("terminal-camera-")) continue;
|
|
46533
47053
|
const nodeId = typeof row.config.nodeId === "string" ? row.config.nodeId : null;
|
|
46534
|
-
if (!nodeId)
|
|
47054
|
+
if (!nodeId) {
|
|
47055
|
+
onSkipped?.({
|
|
47056
|
+
deviceId: row.id,
|
|
47057
|
+
stableId: row.stableId
|
|
47058
|
+
});
|
|
47059
|
+
continue;
|
|
47060
|
+
}
|
|
46535
47061
|
const profileId = typeof row.config.profileId === "string" ? row.config.profileId : "monitor";
|
|
46536
47062
|
const profileLabel = typeof row.config.profileLabel === "string" ? row.config.profileLabel : profileId === "monitor" ? "BTM" : profileId;
|
|
46537
47063
|
legacy.push({
|
|
@@ -46692,111 +47218,6 @@ function findProfile(profiles, profileId) {
|
|
|
46692
47218
|
return profiles.find((p) => p.profileId === profileId);
|
|
46693
47219
|
}
|
|
46694
47220
|
//#endregion
|
|
46695
|
-
//#region src/profile-settings.ts
|
|
46696
|
-
var GLANCES_PLUGINS = [
|
|
46697
|
-
{
|
|
46698
|
-
key: "showCpu",
|
|
46699
|
-
plugin: "cpu",
|
|
46700
|
-
label: "CPU"
|
|
46701
|
-
},
|
|
46702
|
-
{
|
|
46703
|
-
key: "showMem",
|
|
46704
|
-
plugin: "mem",
|
|
46705
|
-
label: "Memory"
|
|
46706
|
-
},
|
|
46707
|
-
{
|
|
46708
|
-
key: "showLoad",
|
|
46709
|
-
plugin: "load",
|
|
46710
|
-
label: "Load"
|
|
46711
|
-
},
|
|
46712
|
-
{
|
|
46713
|
-
key: "showNetwork",
|
|
46714
|
-
plugin: "network",
|
|
46715
|
-
label: "Network"
|
|
46716
|
-
},
|
|
46717
|
-
{
|
|
46718
|
-
key: "showDiskIo",
|
|
46719
|
-
plugin: "diskio",
|
|
46720
|
-
label: "Disk I/O"
|
|
46721
|
-
},
|
|
46722
|
-
{
|
|
46723
|
-
key: "showFs",
|
|
46724
|
-
plugin: "fs",
|
|
46725
|
-
label: "Filesystems"
|
|
46726
|
-
},
|
|
46727
|
-
{
|
|
46728
|
-
key: "showProcessList",
|
|
46729
|
-
plugin: "processlist",
|
|
46730
|
-
label: "Process list"
|
|
46731
|
-
},
|
|
46732
|
-
{
|
|
46733
|
-
key: "showContainers",
|
|
46734
|
-
plugin: "containers",
|
|
46735
|
-
label: "Containers"
|
|
46736
|
-
},
|
|
46737
|
-
{
|
|
46738
|
-
key: "showSensors",
|
|
46739
|
-
plugin: "sensors",
|
|
46740
|
-
label: "Sensors"
|
|
46741
|
-
}
|
|
46742
|
-
];
|
|
46743
|
-
function glancesBooleanField(key, label) {
|
|
46744
|
-
return {
|
|
46745
|
-
type: "boolean",
|
|
46746
|
-
key,
|
|
46747
|
-
label,
|
|
46748
|
-
default: true,
|
|
46749
|
-
style: "switch"
|
|
46750
|
-
};
|
|
46751
|
-
}
|
|
46752
|
-
function glancesSettingsSchema() {
|
|
46753
|
-
return { sections: [{
|
|
46754
|
-
id: "glances-panels",
|
|
46755
|
-
title: "Glances panels",
|
|
46756
|
-
description: "Turn off a panel to pass --disable-plugin to this Terminal only. All on is the measured default (~1% of one core at the camera grid).",
|
|
46757
|
-
columns: 2,
|
|
46758
|
-
fields: GLANCES_PLUGINS.map((plugin) => glancesBooleanField(plugin.key, plugin.label))
|
|
46759
|
-
}] };
|
|
46760
|
-
}
|
|
46761
|
-
function settingsSchemaForProfile(profileId) {
|
|
46762
|
-
if (profileId === "glances") return glancesSettingsSchema();
|
|
46763
|
-
return null;
|
|
46764
|
-
}
|
|
46765
|
-
function glancesSettingsToArgs(settings) {
|
|
46766
|
-
const disabled = GLANCES_PLUGINS.filter((plugin) => settings[plugin.key] === false).map((plugin) => plugin.plugin);
|
|
46767
|
-
if (disabled.length === 0) return [];
|
|
46768
|
-
return ["--disable-plugin", disabled.join(",")];
|
|
46769
|
-
}
|
|
46770
|
-
function sanitizeProfileSettings(profileId, raw) {
|
|
46771
|
-
if (profileId !== "glances") return {};
|
|
46772
|
-
const bag = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
46773
|
-
const out = {
|
|
46774
|
-
showCpu: true,
|
|
46775
|
-
showMem: true,
|
|
46776
|
-
showLoad: true,
|
|
46777
|
-
showNetwork: true,
|
|
46778
|
-
showDiskIo: true,
|
|
46779
|
-
showFs: true,
|
|
46780
|
-
showProcessList: true,
|
|
46781
|
-
showContainers: true,
|
|
46782
|
-
showSensors: true
|
|
46783
|
-
};
|
|
46784
|
-
for (const plugin of GLANCES_PLUGINS) {
|
|
46785
|
-
const value = bag[plugin.key];
|
|
46786
|
-
if (typeof value === "boolean") out[plugin.key] = value;
|
|
46787
|
-
}
|
|
46788
|
-
return out;
|
|
46789
|
-
}
|
|
46790
|
-
function profileSettingsToArgs(profileId, settings) {
|
|
46791
|
-
if (profileId !== "glances") return [];
|
|
46792
|
-
return glancesSettingsToArgs(sanitizeProfileSettings(profileId, settings));
|
|
46793
|
-
}
|
|
46794
|
-
function spawnArgsForInstance(input) {
|
|
46795
|
-
const extra = profileSettingsToArgs(input.profileId, input.profileSettings);
|
|
46796
|
-
if (input.instanceArgs.length > 0) return [...input.instanceArgs, ...extra];
|
|
46797
|
-
if (extra.length > 0) return [...input.profileArgs, ...extra];
|
|
46798
|
-
}
|
|
46799
|
-
//#endregion
|
|
46800
47221
|
//#region src/terminal-session-manager.ts
|
|
46801
47222
|
var MIN_GRID = 1;
|
|
46802
47223
|
var MAX_COLS = 1e3;
|
|
@@ -47618,7 +48039,12 @@ var TerminalAddon = class extends BaseAddon {
|
|
|
47618
48039
|
async listLegacyTerminalCameras() {
|
|
47619
48040
|
const instances = this.terminalInstances();
|
|
47620
48041
|
const instanceStableIds = new Set(instances.map((instance) => instance.cameraStableId));
|
|
47621
|
-
return listLegacyTerminalCameraCandidates(await this.ctx.api.deviceManager.listAll.query({ addonId: this.ctx.id }), instanceStableIds, this.terminalCameraTombstones)
|
|
48042
|
+
return listLegacyTerminalCameraCandidates(await this.ctx.api.deviceManager.listAll.query({ addonId: this.ctx.id }), instanceStableIds, this.terminalCameraTombstones, ({ deviceId, stableId }) => {
|
|
48043
|
+
this.ctx.logger.warn("legacy Terminal camera skipped — its device config carries no nodeId, so it cannot be offered for adoption", {
|
|
48044
|
+
tags: { deviceId },
|
|
48045
|
+
meta: { stableId }
|
|
48046
|
+
});
|
|
48047
|
+
});
|
|
47622
48048
|
}
|
|
47623
48049
|
async adoptLegacyMonitor(stableId, requestedName) {
|
|
47624
48050
|
const instance = await this.instanceMutationQueue.run(() => this.adoptLegacyMonitorUnlocked(stableId, requestedName));
|