@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.mjs
CHANGED
|
@@ -8094,6 +8094,20 @@ var RelocateJobSchema = object({
|
|
|
8094
8094
|
* in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
|
|
8095
8095
|
*/
|
|
8096
8096
|
rowsReconciled: number().int().nonnegative().optional(),
|
|
8097
|
+
/**
|
|
8098
|
+
* Rows this run FORGOT because the file they name is not on disk.
|
|
8099
|
+
*
|
|
8100
|
+
* The mover derived the path from the row's own fields and `stat`ed it; an
|
|
8101
|
+
* ENOENT there is a per-path confirmation that the segment is gone (D296),
|
|
8102
|
+
* and the durable row is dropped through the same channel eviction uses. It
|
|
8103
|
+
* is reported for the same reason `rowsReconciled` is: this is a durable
|
|
8104
|
+
* mutation nobody asked for, and a migration that quietly erases hour rows is
|
|
8105
|
+
* the same failure as one that quietly skips them (D295).
|
|
8106
|
+
*
|
|
8107
|
+
* The production drain of 2026-08-30 would have reported 11 074 here — the
|
|
8108
|
+
* ledger claimed 5.65 GB of footage that no longer existed.
|
|
8109
|
+
*/
|
|
8110
|
+
rowsForgotten: number().int().nonnegative().optional(),
|
|
8097
8111
|
startedAt: number(),
|
|
8098
8112
|
finishedAt: number().nullable(),
|
|
8099
8113
|
error: string().nullable()
|
|
@@ -8158,6 +8172,13 @@ var MediaRelocateModeSchema = _enum([
|
|
|
8158
8172
|
]);
|
|
8159
8173
|
var RelocateMediaInputSchema = object({
|
|
8160
8174
|
toLocationId: string(),
|
|
8175
|
+
/**
|
|
8176
|
+
* Restrict the pass to rows currently on this location. Omitted / `'*'` =
|
|
8177
|
+
* every row that is not already on `toLocationId` (the historical
|
|
8178
|
+
* behaviour). A named source is what a from→to migration needs: without it
|
|
8179
|
+
* "move events off disk 2" also emptied disk 1.
|
|
8180
|
+
*/
|
|
8181
|
+
fromLocationId: string().optional(),
|
|
8161
8182
|
throttleMbps: number().min(1).max(1e3).optional(),
|
|
8162
8183
|
/** Omitted = `move`, the pre-existing behaviour. */
|
|
8163
8184
|
mode: MediaRelocateModeSchema.optional()
|
|
@@ -8225,6 +8246,19 @@ var StorageMigrationDestinationsSchema = object({
|
|
|
8225
8246
|
galleryMedia: string().min(1).optional()
|
|
8226
8247
|
}).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
|
|
8227
8248
|
/**
|
|
8249
|
+
* Optional named source per class. Omitted = the class's current default
|
|
8250
|
+
* (the historical behaviour). A named source that is NOT the default is a
|
|
8251
|
+
* drain of that disk: bytes move, the default stays, and the source is
|
|
8252
|
+
* disabled when the move finishes.
|
|
8253
|
+
*/
|
|
8254
|
+
var StorageMigrationSourcesSchema = object({
|
|
8255
|
+
recordings: string().min(1).optional(),
|
|
8256
|
+
recordingsLow: string().min(1).optional(),
|
|
8257
|
+
eventMedia: string().min(1).optional(),
|
|
8258
|
+
backups: string().min(1).optional(),
|
|
8259
|
+
galleryMedia: string().min(1).optional()
|
|
8260
|
+
}).optional();
|
|
8261
|
+
/**
|
|
8228
8262
|
* How a migration sequences the cutover against the byte move.
|
|
8229
8263
|
*
|
|
8230
8264
|
* - `blocking` — the historical order: pause, move every byte, repoint,
|
|
@@ -8246,6 +8280,8 @@ var StorageMigrationModeSchema = _enum(["blocking", "nonBlocking"]);
|
|
|
8246
8280
|
/** Shared input for planning and starting an orchestrated storage migration. */
|
|
8247
8281
|
var StorageMigrationInputSchema = object({
|
|
8248
8282
|
destinations: StorageMigrationDestinationsSchema,
|
|
8283
|
+
/** Omitted = each class's current default. */
|
|
8284
|
+
sources: StorageMigrationSourcesSchema,
|
|
8249
8285
|
throttleMbps: number().min(1).max(1e3).optional(),
|
|
8250
8286
|
/** Omitted = `blocking`, which stays the default. */
|
|
8251
8287
|
mode: StorageMigrationModeSchema.optional()
|
|
@@ -8325,6 +8361,13 @@ var StorageMigrationMoveSchema = object({
|
|
|
8325
8361
|
storageClass: StorageMigrationClassSchema,
|
|
8326
8362
|
fromLocationId: string(),
|
|
8327
8363
|
toLocationId: string(),
|
|
8364
|
+
/**
|
|
8365
|
+
* True when `from` was NOT the class default at plan time. The move still
|
|
8366
|
+
* copies bytes, but the default is left alone and the source is disabled
|
|
8367
|
+
* once the copy verifies. Absent on jobs planned before this field existed
|
|
8368
|
+
* — those jobs always repointed, which is `false`.
|
|
8369
|
+
*/
|
|
8370
|
+
freezeSource: boolean().optional(),
|
|
8328
8371
|
moverJobId: string().nullable(),
|
|
8329
8372
|
state: RelocateJobStateSchema.nullable(),
|
|
8330
8373
|
error: string().nullable(),
|
|
@@ -8338,6 +8381,7 @@ var StorageMigrationJobSchema = object({
|
|
|
8338
8381
|
* can tell a seconds-long cutover from a thirty-hour one. */
|
|
8339
8382
|
mode: StorageMigrationModeSchema,
|
|
8340
8383
|
destinations: StorageMigrationDestinationsSchema,
|
|
8384
|
+
sources: StorageMigrationSourcesSchema,
|
|
8341
8385
|
throttleMbps: number(),
|
|
8342
8386
|
moves: array(StorageMigrationMoveSchema),
|
|
8343
8387
|
pauseLeaseId: string().nullable(),
|
|
@@ -8363,6 +8407,7 @@ var StorageMigrationFindingSchema = object({
|
|
|
8363
8407
|
});
|
|
8364
8408
|
var StorageMigrationPlanSchema = object({
|
|
8365
8409
|
destinations: StorageMigrationDestinationsSchema,
|
|
8410
|
+
sources: StorageMigrationSourcesSchema,
|
|
8366
8411
|
/** The mode this plan was built for. A plan is only valid for its mode: the
|
|
8367
8412
|
* `eventMedia` seal gate and the single-cardinality refusal both depend on
|
|
8368
8413
|
* it. */
|
|
@@ -8370,7 +8415,8 @@ var StorageMigrationPlanSchema = object({
|
|
|
8370
8415
|
moves: array(object({
|
|
8371
8416
|
storageClass: StorageMigrationClassSchema,
|
|
8372
8417
|
fromLocationId: string(),
|
|
8373
|
-
toLocationId: string()
|
|
8418
|
+
toLocationId: string(),
|
|
8419
|
+
freezeSource: boolean().optional()
|
|
8374
8420
|
})),
|
|
8375
8421
|
findings: array(StorageMigrationFindingSchema)
|
|
8376
8422
|
});
|
|
@@ -8457,16 +8503,142 @@ var RelocateResidueSchema = object({
|
|
|
8457
8503
|
segments: number().int().nonnegative(),
|
|
8458
8504
|
bytes: number().int().nonnegative()
|
|
8459
8505
|
}).nullable();
|
|
8506
|
+
/**
|
|
8507
|
+
* Ask one location whether its durable hour rows describe the disk — the walk
|
|
8508
|
+
* (D319).
|
|
8509
|
+
*
|
|
8510
|
+
* `apply` DEFAULTS TO FALSE and that default is the product: the operator's
|
|
8511
|
+
* missing tool is the question, and the dry run is how they sanity-check the
|
|
8512
|
+
* destructive run before authorising it.
|
|
8513
|
+
*/
|
|
8514
|
+
var LedgerWalkInputSchema = object({
|
|
8515
|
+
locationId: string().min(1),
|
|
8516
|
+
/** Forget the confirmed-absent rows, rather than only counting them. */
|
|
8517
|
+
apply: boolean().optional(),
|
|
8518
|
+
/** Narrow to one camera. */
|
|
8519
|
+
deviceId: number().int().positive().optional(),
|
|
8520
|
+
/** Narrow to these recording profiles; empty/absent = every profile. */
|
|
8521
|
+
profiles: array(string().min(1)).optional()
|
|
8522
|
+
});
|
|
8523
|
+
/** Why a whole walk did nothing. Every one leaves the ledger untouched. */
|
|
8524
|
+
var LedgerWalkRefusalSchema = _enum([
|
|
8525
|
+
"location-unknown",
|
|
8526
|
+
"source-writable",
|
|
8527
|
+
"no-ledger",
|
|
8528
|
+
"archive-unreadable",
|
|
8529
|
+
"anchor-absent",
|
|
8530
|
+
"anchor-unreadable",
|
|
8531
|
+
"anchor-moved"
|
|
8532
|
+
]);
|
|
8533
|
+
_enum([
|
|
8534
|
+
"live-tail",
|
|
8535
|
+
"listing-error",
|
|
8536
|
+
"path-mismatch",
|
|
8537
|
+
"durable-refused"
|
|
8538
|
+
]);
|
|
8539
|
+
/** Every skip reason, always present, always a number — so a reason that never
|
|
8540
|
+
* fired reports as zero rather than absent and the report shape is constant
|
|
8541
|
+
* between passes. Spelled out rather than `z.record` for exactly that. */
|
|
8542
|
+
var LedgerWalkSkipCountsSchema = object({
|
|
8543
|
+
"live-tail": number().int().nonnegative(),
|
|
8544
|
+
"listing-error": number().int().nonnegative(),
|
|
8545
|
+
"path-mismatch": number().int().nonnegative(),
|
|
8546
|
+
"durable-refused": number().int().nonnegative()
|
|
8547
|
+
});
|
|
8548
|
+
/** One camera's share of a walk, so a report names cameras and not rows. */
|
|
8549
|
+
var LedgerWalkDeviceReportSchema = object({
|
|
8550
|
+
deviceId: number().int(),
|
|
8551
|
+
hoursWalked: number().int().nonnegative(),
|
|
8552
|
+
hoursMissing: number().int().nonnegative(),
|
|
8553
|
+
ghostSegments: number().int().nonnegative(),
|
|
8554
|
+
ghostBytes: number().int().nonnegative(),
|
|
8555
|
+
forgottenSegments: number().int().nonnegative(),
|
|
8556
|
+
orphanFiles: number().int().nonnegative()
|
|
8557
|
+
});
|
|
8558
|
+
/**
|
|
8559
|
+
* What one walk claimed, listed, found and (only when armed) forgot.
|
|
8560
|
+
*
|
|
8561
|
+
* `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
|
|
8562
|
+
* walk that saw a fraction of the location is visible in its own report rather
|
|
8563
|
+
* than in the absence of one.
|
|
8564
|
+
*/
|
|
8565
|
+
var LedgerWalkReportSchema = object({
|
|
8566
|
+
locationId: string(),
|
|
8567
|
+
applied: boolean(),
|
|
8568
|
+
refused: LedgerWalkRefusalSchema.nullable(),
|
|
8569
|
+
archiveSegments: number().int().nonnegative().nullable(),
|
|
8570
|
+
archiveBytes: number().int().nonnegative().nullable(),
|
|
8571
|
+
hoursClaimed: number().int().nonnegative(),
|
|
8572
|
+
hoursWalked: number().int().nonnegative(),
|
|
8573
|
+
hoursMissing: number().int().nonnegative(),
|
|
8574
|
+
/** `readdir` calls issued — the cost, stated in the unit that is paid. */
|
|
8575
|
+
listings: number().int().nonnegative(),
|
|
8576
|
+
segmentsClaimed: number().int().nonnegative(),
|
|
8577
|
+
ghostSegments: number().int().nonnegative(),
|
|
8578
|
+
ghostBytes: number().int().nonnegative(),
|
|
8579
|
+
ghostHoursWhole: number().int().nonnegative(),
|
|
8580
|
+
forgottenSegments: number().int().nonnegative(),
|
|
8581
|
+
forgottenBytes: number().int().nonnegative(),
|
|
8582
|
+
/** Files under a claimed hour that no durable row names. Never deleted. */
|
|
8583
|
+
orphanFiles: number().int().nonnegative(),
|
|
8584
|
+
orphanSample: array(string()).readonly(),
|
|
8585
|
+
hoursSkipped: number().int().nonnegative(),
|
|
8586
|
+
skippedByReason: LedgerWalkSkipCountsSchema,
|
|
8587
|
+
/** The walk stopped at its per-pass hour bound with claims unwalked. */
|
|
8588
|
+
bounded: boolean(),
|
|
8589
|
+
byDevice: array(LedgerWalkDeviceReportSchema).readonly()
|
|
8590
|
+
});
|
|
8460
8591
|
/** How many rows a media pass would still act on against a given target — the
|
|
8461
8592
|
* media lane's denominator AND its residue, from ONE derivation so the two can
|
|
8462
8593
|
* never disagree. `null` = the count could not be taken. */
|
|
8463
8594
|
var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
|
|
8464
8595
|
var RelocatableMediaCountInputSchema = object({
|
|
8465
8596
|
toLocationId: string().min(1),
|
|
8597
|
+
/** Restrict the count to one source; omitted / `'*'` = every non-target row. */
|
|
8598
|
+
fromLocationId: string().optional(),
|
|
8466
8599
|
/** Omitted = `move`. */
|
|
8467
8600
|
mode: MediaRelocateModeSchema.optional()
|
|
8468
8601
|
});
|
|
8469
8602
|
/**
|
|
8603
|
+
* Operator cleanup of leftover analytics rows, optional debug media, and
|
|
8604
|
+
* ghost ledger entries on frozen footage locations.
|
|
8605
|
+
*
|
|
8606
|
+
* A pass is tens of minutes on a standing backlog. The hub method RETURNS
|
|
8607
|
+
* `{ jobId }` immediately; progress is `cleanupStatus`. Awaiting the work
|
|
8608
|
+
* is how `addons.custom` hit the 60 s UDS deadline while reclaim continued
|
|
8609
|
+
* with no operator-visible status.
|
|
8610
|
+
*/
|
|
8611
|
+
var StorageCleanupPhaseSchema = _enum([
|
|
8612
|
+
"orphans",
|
|
8613
|
+
"debug-media",
|
|
8614
|
+
"ghost-ledger",
|
|
8615
|
+
"done",
|
|
8616
|
+
"failed",
|
|
8617
|
+
"cancelled"
|
|
8618
|
+
]);
|
|
8619
|
+
var StorageCleanupInputSchema = object({
|
|
8620
|
+
/** Also walk motion stills / track filmstrips. Off by default. */
|
|
8621
|
+
includeDebugMedia: boolean().optional() });
|
|
8622
|
+
var StorageCleanupJobSchema = object({
|
|
8623
|
+
jobId: string(),
|
|
8624
|
+
phase: StorageCleanupPhaseSchema,
|
|
8625
|
+
includeDebugMedia: boolean(),
|
|
8626
|
+
orphansReclaimed: number().int().nonnegative(),
|
|
8627
|
+
orphanBytesReclaimed: number().int().nonnegative(),
|
|
8628
|
+
debugMediaReclaimed: number().int().nonnegative(),
|
|
8629
|
+
debugMediaBytesReclaimed: number().int().nonnegative(),
|
|
8630
|
+
ghostsForgotten: number().int().nonnegative(),
|
|
8631
|
+
ghostBytesForgotten: number().int().nonnegative(),
|
|
8632
|
+
/** Short operator-facing line: current collection, pass, or location. */
|
|
8633
|
+
detail: string().nullable(),
|
|
8634
|
+
cancelRequested: boolean(),
|
|
8635
|
+
startedAt: number(),
|
|
8636
|
+
updatedAt: number(),
|
|
8637
|
+
finishedAt: number().nullable(),
|
|
8638
|
+
error: string().nullable()
|
|
8639
|
+
});
|
|
8640
|
+
var StorageCleanupStatusInputSchema = object({ jobId: string().optional() });
|
|
8641
|
+
/**
|
|
8470
8642
|
* `StorageLocationType` — an addon-declared id that identifies the *kind* of
|
|
8471
8643
|
* storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
|
|
8472
8644
|
* so the persisted record schema and the consumer-facing cap can both consume it
|
|
@@ -8494,11 +8666,10 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
|
|
|
8494
8666
|
* The default location for a type uses `id === <type>:default` by
|
|
8495
8667
|
* convention (the bare type ref like `'backups'` resolves to it).
|
|
8496
8668
|
*
|
|
8497
|
-
* `isSystem
|
|
8498
|
-
*
|
|
8499
|
-
*
|
|
8500
|
-
*
|
|
8501
|
-
* deleting it is rejected at the cap level.
|
|
8669
|
+
* `isSystem` is a legacy persisted flag. Seed still creates the initial
|
|
8670
|
+
* `<type>:default` locations; the flag is no longer a lock, a badge, or a
|
|
8671
|
+
* prune selector. New writes leave it false. Deletion is gated on uniqueness
|
|
8672
|
+
* / last-enabled, not on this bit.
|
|
8502
8673
|
*/
|
|
8503
8674
|
var StorageLocationSchema = object({
|
|
8504
8675
|
id: string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
|
|
@@ -12969,6 +13140,12 @@ method(object({
|
|
|
12969
13140
|
}), _void(), {
|
|
12970
13141
|
kind: "mutation",
|
|
12971
13142
|
auth: "admin"
|
|
13143
|
+
}), method(object({
|
|
13144
|
+
from: string(),
|
|
13145
|
+
to: string()
|
|
13146
|
+
}), object({ moved: number() }), {
|
|
13147
|
+
kind: "mutation",
|
|
13148
|
+
auth: "admin"
|
|
12972
13149
|
}), method(object({
|
|
12973
13150
|
deviceId: number(),
|
|
12974
13151
|
disabled: boolean()
|
|
@@ -18977,53 +19154,15 @@ var RecentTracksPageSchema = object({
|
|
|
18977
19154
|
/** Cursor for the next page, or null when this page is the last. */
|
|
18978
19155
|
nextCursor: string().nullable()
|
|
18979
19156
|
});
|
|
18980
|
-
var
|
|
18981
|
-
var
|
|
18982
|
-
var AnalyticsGroupRecordSchema = object({
|
|
18983
|
-
id: string(),
|
|
18984
|
-
deviceId: number().int(),
|
|
18985
|
-
openedAt: number().int(),
|
|
18986
|
-
closedAt: number().int(),
|
|
18987
|
-
timestamp: number().int(),
|
|
18988
|
-
memberCount: number().int(),
|
|
18989
|
-
memberTrackIds: array(string()).readonly(),
|
|
18990
|
-
className: string(),
|
|
18991
|
-
classes: array(string()).readonly(),
|
|
18992
|
-
/** Relative event-media path, or null when the group has no picture yet. */
|
|
18993
|
-
mediaUrl: string().nullable(),
|
|
18994
|
-
singleton: boolean()
|
|
18995
|
-
});
|
|
18996
|
-
var AnalyticsGroupMemberSchema = object({
|
|
18997
|
-
trackId: string(),
|
|
18998
|
-
deviceId: number().int(),
|
|
18999
|
-
className: string(),
|
|
19000
|
-
firstSeen: number().int(),
|
|
19001
|
-
lastSeen: number().int(),
|
|
19002
|
-
mediaUrl: string().nullable()
|
|
19003
|
-
});
|
|
19004
|
-
var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
|
|
19005
|
-
var ListGroupsQueryInput = object({
|
|
19006
|
-
/** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
|
|
19007
|
-
deviceIds: array(number()),
|
|
19008
|
-
/** Window lower bound on `closedAt` (inclusive). */
|
|
19009
|
-
since: number().optional(),
|
|
19010
|
-
/** Window upper bound on `openedAt` (inclusive). */
|
|
19011
|
-
until: number().optional(),
|
|
19012
|
-
limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
|
|
19013
|
-
/** Opaque continuation cursor from a previous page's `nextCursor`. */
|
|
19014
|
-
cursor: string().optional()
|
|
19015
|
-
});
|
|
19016
|
-
var ListGroupsPageSchema = object({
|
|
19017
|
-
groups: array(AnalyticsGroupRecordSchema).readonly(),
|
|
19018
|
-
nextCursor: string().nullable()
|
|
19019
|
-
});
|
|
19157
|
+
var KEY_EVENTS_DEFAULT_LIMIT = 50;
|
|
19158
|
+
var KEY_EVENTS_MAX_LIMIT = 200;
|
|
19020
19159
|
var KeyEventQueryInput = object({
|
|
19021
19160
|
deviceId: number(),
|
|
19022
19161
|
/** Window lower bound (track firstSeen ≥ since). */
|
|
19023
19162
|
since: number(),
|
|
19024
19163
|
/** Window upper bound (track firstSeen ≤ until). */
|
|
19025
19164
|
until: number(),
|
|
19026
|
-
limit: number().int().min(1).max(
|
|
19165
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19027
19166
|
/** Drop tracks scoring below this importance. */
|
|
19028
19167
|
minImportance: number().min(0).max(1).optional(),
|
|
19029
19168
|
/** Restrict to a single class (e.g. 'person'). */
|
|
@@ -19045,6 +19184,32 @@ var KeyEventSchema = object({
|
|
|
19045
19184
|
...TrackFlagFields,
|
|
19046
19185
|
...TrackRetrainFields
|
|
19047
19186
|
});
|
|
19187
|
+
/** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
|
|
19188
|
+
var KeyEventBatchQueryInput = object({
|
|
19189
|
+
deviceIds: array(number()).min(1).max(200),
|
|
19190
|
+
since: number(),
|
|
19191
|
+
until: number(),
|
|
19192
|
+
/** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
|
|
19193
|
+
* across the set, which would let a busy camera starve a quiet one of its
|
|
19194
|
+
* rows and change what the merged feed contains. */
|
|
19195
|
+
limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
|
|
19196
|
+
minImportance: number().min(0).max(1).optional(),
|
|
19197
|
+
classFilter: string().optional()
|
|
19198
|
+
});
|
|
19199
|
+
/**
|
|
19200
|
+
* One camera's key events in a batch answer.
|
|
19201
|
+
*
|
|
19202
|
+
* The row exists for every requested id. `getKeyEvents` degrades to `[]` on
|
|
19203
|
+
* error rather than throwing, so a camera whose store read failed and one with
|
|
19204
|
+
* no events in the window were ALREADY indistinguishable per camera — the
|
|
19205
|
+
* batch does not make that worse, and the row keeps the deviceId the single
|
|
19206
|
+
* method's output never carried (the caller used to stamp it from the fan-out
|
|
19207
|
+
* key, which only worked because there was one query per camera).
|
|
19208
|
+
*/
|
|
19209
|
+
var KeyEventsForDeviceSchema = object({
|
|
19210
|
+
deviceId: number(),
|
|
19211
|
+
events: array(KeyEventSchema).readonly()
|
|
19212
|
+
});
|
|
19048
19213
|
object({
|
|
19049
19214
|
trackId: string(),
|
|
19050
19215
|
className: string(),
|
|
@@ -19092,9 +19257,7 @@ var TrackCascadeCountsSchema = object({
|
|
|
19092
19257
|
/** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
|
|
19093
19258
|
plates: number().int(),
|
|
19094
19259
|
/** Per-track CLIP search vectors removed (best-effort). */
|
|
19095
|
-
embeddings: number().int()
|
|
19096
|
-
/** Group membership + group rows removed with their last member (best-effort). */
|
|
19097
|
-
groups: number().int()
|
|
19260
|
+
embeddings: number().int()
|
|
19098
19261
|
});
|
|
19099
19262
|
/** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
|
|
19100
19263
|
var DiskReconcileCountsSchema = object({
|
|
@@ -19264,6 +19427,47 @@ var RebuildStatusSchema = object({
|
|
|
19264
19427
|
/** Present when the pass ended by throwing. */
|
|
19265
19428
|
error: string().nullable()
|
|
19266
19429
|
});
|
|
19430
|
+
/**
|
|
19431
|
+
* Acknowledgement that a debug-media reclaim STARTED.
|
|
19432
|
+
*
|
|
19433
|
+
* The pass is paced at ~2 MB/s over a standing 100 GB — tens of minutes — so
|
|
19434
|
+
* it runs detached and this returns immediately. Awaiting it is how the
|
|
19435
|
+
* `addons.custom` door hit the 60 s UDS deadline while the walk carried on
|
|
19436
|
+
* with no status. Poll {@link pipelineAnalyticsCapability.methods.getMediaReclaimStatus}.
|
|
19437
|
+
*/
|
|
19438
|
+
var MediaReclaimStartResultSchema = object({
|
|
19439
|
+
started: boolean(),
|
|
19440
|
+
/** True when a pass was already running; the new request is ignored. */
|
|
19441
|
+
alreadyRunning: boolean()
|
|
19442
|
+
});
|
|
19443
|
+
var MediaReclaimInputSchema = object({
|
|
19444
|
+
mode: _enum(["report", "reclaim"]).default("report"),
|
|
19445
|
+
scopes: array(_enum(["motion-still", "track-filmstrip"])).min(1).optional(),
|
|
19446
|
+
deviceIds: array(number().int()).min(1).optional(),
|
|
19447
|
+
restart: boolean().optional(),
|
|
19448
|
+
pageSize: number().int().min(50).max(5e3).optional(),
|
|
19449
|
+
maxRowsPerDevice: number().int().min(1).max(5e5).optional(),
|
|
19450
|
+
maxReclaimPerDevice: number().int().min(1).max(5e5).optional(),
|
|
19451
|
+
maxBytesPerRun: number().int().min(1).optional(),
|
|
19452
|
+
budgetMinutes: number().int().min(1).max(720).optional(),
|
|
19453
|
+
throttleBytesPerSec: number().int().min(64 * 1024).optional(),
|
|
19454
|
+
graceMinutes: number().int().min(1).max(10080).optional()
|
|
19455
|
+
});
|
|
19456
|
+
var MediaReclaimStatusSchema = object({
|
|
19457
|
+
running: boolean(),
|
|
19458
|
+
mode: _enum(["report", "reclaim"]).nullable(),
|
|
19459
|
+
totalExamined: number(),
|
|
19460
|
+
totalEligible: number(),
|
|
19461
|
+
totalReclaimed: number(),
|
|
19462
|
+
totalBytesReclaimed: number(),
|
|
19463
|
+
totalRefused: number(),
|
|
19464
|
+
/** Device+scope windows finished in this pass. */
|
|
19465
|
+
devicesDone: number(),
|
|
19466
|
+
complete: boolean().nullable(),
|
|
19467
|
+
startedAtMs: number().nullable(),
|
|
19468
|
+
finishedAtMs: number().nullable(),
|
|
19469
|
+
error: string().nullable()
|
|
19470
|
+
});
|
|
19267
19471
|
var ReplayFrameInputSchema = object({
|
|
19268
19472
|
timestamp: number(),
|
|
19269
19473
|
frame: PipelineRunResultBridge
|
|
@@ -19302,10 +19506,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19302
19506
|
* stationary registry). Default false: the timeline lists passages,
|
|
19303
19507
|
* not parking records (operator decision, 2026-08-15). */
|
|
19304
19508
|
includeStationary: boolean().optional()
|
|
19305
|
-
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(
|
|
19306
|
-
deviceId: number(),
|
|
19307
|
-
groupId: string().min(1)
|
|
19308
|
-
}), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
|
|
19509
|
+
}), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
|
|
19309
19510
|
kind: "mutation",
|
|
19310
19511
|
auth: "admin"
|
|
19311
19512
|
}), 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({
|
|
@@ -19314,7 +19515,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19314
19515
|
until: number().optional(),
|
|
19315
19516
|
kinds: array(string()).optional(),
|
|
19316
19517
|
limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
|
|
19317
|
-
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
|
|
19518
|
+
}), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
|
|
19318
19519
|
deviceId: number(),
|
|
19319
19520
|
since: number(),
|
|
19320
19521
|
until: number(),
|
|
@@ -19405,6 +19606,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
19405
19606
|
}), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
|
|
19406
19607
|
kind: "query",
|
|
19407
19608
|
auth: "admin"
|
|
19609
|
+
}), method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
|
|
19610
|
+
kind: "mutation",
|
|
19611
|
+
auth: "admin"
|
|
19612
|
+
}), method(object({}), MediaReclaimStatusSchema, {
|
|
19613
|
+
kind: "query",
|
|
19614
|
+
auth: "admin"
|
|
19408
19615
|
}), method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
|
|
19409
19616
|
kind: "query",
|
|
19410
19617
|
auth: "admin"
|
|
@@ -21479,7 +21686,13 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
|
|
|
21479
21686
|
}), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
|
|
21480
21687
|
kind: "mutation",
|
|
21481
21688
|
auth: "admin"
|
|
21482
|
-
})
|
|
21689
|
+
}), method(StorageCleanupInputSchema, object({ jobId: string() }), {
|
|
21690
|
+
kind: "mutation",
|
|
21691
|
+
auth: "admin"
|
|
21692
|
+
}), method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
21693
|
+
kind: "mutation",
|
|
21694
|
+
auth: "admin"
|
|
21695
|
+
}), method(object({}), array(StorageMigrationJobSchema).readonly(), { auth: "admin" });
|
|
21483
21696
|
var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
|
|
21484
21697
|
providerId: string().min(1),
|
|
21485
21698
|
displayName: string().min(1),
|
|
@@ -28616,6 +28829,33 @@ var RecordingRebalanceInputSchema = object({
|
|
|
28616
28829
|
minMoveGb: number().min(0).optional()
|
|
28617
28830
|
});
|
|
28618
28831
|
/**
|
|
28832
|
+
* Operator-facing placement of one camera onto a recordings location.
|
|
28833
|
+
*
|
|
28834
|
+
* `pinLocationId` is the operator instruction: a location id, or `null` for
|
|
28835
|
+
* Auto (the planner may move this camera). `locationId` is where high/mid
|
|
28836
|
+
* currently write — the plan, which may disagree with the pin when Auto.
|
|
28837
|
+
*/
|
|
28838
|
+
var RecordingDevicePlacementSchema = object({
|
|
28839
|
+
deviceId: number().int(),
|
|
28840
|
+
profile: string(),
|
|
28841
|
+
locationId: string()
|
|
28842
|
+
});
|
|
28843
|
+
var RecordingDevicePinSchema = object({
|
|
28844
|
+
deviceId: number().int(),
|
|
28845
|
+
/** Recordings-class location this camera is pinned to. */
|
|
28846
|
+
locationId: string()
|
|
28847
|
+
});
|
|
28848
|
+
var RecordingPlacementViewSchema = object({
|
|
28849
|
+
assignments: array(RecordingDevicePlacementSchema),
|
|
28850
|
+
pins: array(RecordingDevicePinSchema),
|
|
28851
|
+
defaultLocations: record(string(), string())
|
|
28852
|
+
});
|
|
28853
|
+
var RecordingSetDevicePlacementInputSchema = object({
|
|
28854
|
+
deviceId: number().int(),
|
|
28855
|
+
/** `null` = Auto. Otherwise a `recordings:*` location id. */
|
|
28856
|
+
locationId: string().nullable()
|
|
28857
|
+
});
|
|
28858
|
+
/**
|
|
28619
28859
|
* Result of locating footage at a wall-clock instant for one device/profile.
|
|
28620
28860
|
* `segment` carries the covering segment's window; `gap` reports the forward
|
|
28621
28861
|
* nearest covered edge (`null` past the end of footage / when none exists)
|
|
@@ -28829,6 +29069,9 @@ method(object({
|
|
|
28829
29069
|
}), method(RelocateResidueInputSchema, RelocateResidueSchema, {
|
|
28830
29070
|
kind: "query",
|
|
28831
29071
|
auth: "admin"
|
|
29072
|
+
}), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
|
|
29073
|
+
kind: "mutation",
|
|
29074
|
+
auth: "admin"
|
|
28832
29075
|
}), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
|
|
28833
29076
|
kind: "mutation",
|
|
28834
29077
|
auth: "admin"
|
|
@@ -28838,6 +29081,12 @@ method(object({
|
|
|
28838
29081
|
}), method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
|
|
28839
29082
|
kind: "mutation",
|
|
28840
29083
|
auth: "admin"
|
|
29084
|
+
}), method(object({}), RecordingPlacementViewSchema, {
|
|
29085
|
+
kind: "query",
|
|
29086
|
+
auth: "admin"
|
|
29087
|
+
}), method(RecordingSetDevicePlacementInputSchema, object({ ok: literal(true) }), {
|
|
29088
|
+
kind: "mutation",
|
|
29089
|
+
auth: "admin"
|
|
28841
29090
|
});
|
|
28842
29091
|
/**
|
|
28843
29092
|
* `recording-export` cap — render a footage time range into a single downloadable
|
|
@@ -29220,6 +29469,25 @@ var SceneMonitorStatusSchema = object({
|
|
|
29220
29469
|
monitors: array(SceneMonitorSchema),
|
|
29221
29470
|
lastFetchedAt: number()
|
|
29222
29471
|
});
|
|
29472
|
+
/**
|
|
29473
|
+
* One camera's row in a `listScenesBatch` answer.
|
|
29474
|
+
*
|
|
29475
|
+
* `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
|
|
29476
|
+
* is a `defaultActive` wrapper, so every camera is asked; a camera whose
|
|
29477
|
+
* provider is absent (pipeline-analytics not deployed, the post-processing node
|
|
29478
|
+
* down) cannot answer, and that is not the same fact as a camera with no scenes
|
|
29479
|
+
* configured. Fanned out per camera the difference was visible — one query
|
|
29480
|
+
* errored while the others resolved — and a batch that returned only the rows
|
|
29481
|
+
* it managed would have destroyed it, silently, by making an unreachable camera
|
|
29482
|
+
* indistinguishable from one that answered `monitors: []`.
|
|
29483
|
+
*
|
|
29484
|
+
* So: EVERY requested deviceId gets a row. `status: null` means "this camera
|
|
29485
|
+
* could not be read"; `status.monitors: []` means "read, and it has none".
|
|
29486
|
+
*/
|
|
29487
|
+
var SceneMonitorStatusForDeviceSchema = object({
|
|
29488
|
+
deviceId: number(),
|
|
29489
|
+
status: SceneMonitorStatusSchema.nullable()
|
|
29490
|
+
});
|
|
29223
29491
|
var sceneMonitorCapability = {
|
|
29224
29492
|
name: "scene-monitor",
|
|
29225
29493
|
scope: "device",
|
|
@@ -29229,6 +29497,22 @@ var sceneMonitorCapability = {
|
|
|
29229
29497
|
deviceTypes: [DeviceType.Camera],
|
|
29230
29498
|
methods: {
|
|
29231
29499
|
listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
|
|
29500
|
+
/**
|
|
29501
|
+
* The same answer, for a SET of cameras, in one round trip.
|
|
29502
|
+
*
|
|
29503
|
+
* `/scenes` renders every camera and re-reads them on a 30s safety-net
|
|
29504
|
+
* poll behind the push slice. Fanned out client-side that was one query
|
|
29505
|
+
* per camera — 29 round trips through the browser, the hub and the
|
|
29506
|
+
* post-analysis runner every 30 seconds to read an in-memory map the
|
|
29507
|
+
* owner had already merged. The work is unchanged (`statusFor` per
|
|
29508
|
+
* device, all in-process at the owner); what collapses is the transport.
|
|
29509
|
+
*
|
|
29510
|
+
* A camera that cannot answer still gets a row, with `status: null` —
|
|
29511
|
+
* see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
|
|
29512
|
+
* ids: a caller that asked for twenty-nine and got twenty-seven cannot
|
|
29513
|
+
* tell which two are missing, or that any are.
|
|
29514
|
+
*/
|
|
29515
|
+
listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
|
|
29232
29516
|
createScene: method(object({
|
|
29233
29517
|
deviceId: number(),
|
|
29234
29518
|
label: string(),
|
|
@@ -31064,6 +31348,27 @@ var CameraOccupancySnapshotSchema = object({
|
|
|
31064
31348
|
stationaryObjects: array(StationaryObjectSchema).readonly().optional()
|
|
31065
31349
|
});
|
|
31066
31350
|
/**
|
|
31351
|
+
* One camera's row in a `getCurrentSnapshotBatch` answer.
|
|
31352
|
+
*
|
|
31353
|
+
* THREE outcomes, and the single-camera method could only express two of them
|
|
31354
|
+
* because `snapshot: null` was already spoken for:
|
|
31355
|
+
*
|
|
31356
|
+
* - `read: 'read'`, `snapshot` present — the live occupancy reading.
|
|
31357
|
+
* - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
|
|
31358
|
+
* yet: no frame since boot, and no parked-object registry to hydrate from.
|
|
31359
|
+
* - `read: 'unreadable'` — the owner could not answer for this
|
|
31360
|
+
* camera. `snapshot` is null, and it does NOT mean "nothing parked here".
|
|
31361
|
+
*
|
|
31362
|
+
* Collapsing the last two is the failure this field exists to prevent: a
|
|
31363
|
+
* hydration that threw would otherwise render as an empty Stationary section,
|
|
31364
|
+
* which is a definite claim about a camera nobody could read.
|
|
31365
|
+
*/
|
|
31366
|
+
var CameraOccupancySnapshotForDeviceSchema = object({
|
|
31367
|
+
deviceId: number(),
|
|
31368
|
+
read: _enum(["read", "unreadable"]),
|
|
31369
|
+
snapshot: CameraOccupancySnapshotSchema.nullable()
|
|
31370
|
+
});
|
|
31371
|
+
/**
|
|
31067
31372
|
* Time-series resolution. The history methods return one bucket per
|
|
31068
31373
|
* step over the requested range. Smaller resolutions cost more
|
|
31069
31374
|
* memory + bandwidth; bound to discrete steps so caller cannot ask
|
|
@@ -31120,6 +31425,20 @@ var zoneAnalyticsCapability = {
|
|
|
31120
31425
|
* (no inference result emitted since boot or since binding was
|
|
31121
31426
|
* activated). */
|
|
31122
31427
|
getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
|
|
31428
|
+
/**
|
|
31429
|
+
* The same snapshot, for a SET of cameras, in one round trip.
|
|
31430
|
+
*
|
|
31431
|
+
* The Events page's Stationary section polls this every 15s for every
|
|
31432
|
+
* selected camera. Fanned out client-side that is one query per camera to
|
|
31433
|
+
* read a `Map.get` at the owner — the answer costs nothing, the round trip
|
|
31434
|
+
* costs everything. Batched, N transports become one and the per-device
|
|
31435
|
+
* work is unchanged.
|
|
31436
|
+
*
|
|
31437
|
+
* Every requested deviceId gets a row, tagged `read` — see
|
|
31438
|
+
* {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
|
|
31439
|
+
* not answer for is `'unreadable'`, never an empty reading.
|
|
31440
|
+
*/
|
|
31441
|
+
getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
|
|
31123
31442
|
/** Time-series object count inside one zone. `className` optional —
|
|
31124
31443
|
* omit to count every class in the zone. */
|
|
31125
31444
|
getZoneHistory: method(object({
|
|
@@ -33835,6 +34154,12 @@ Object.freeze({
|
|
|
33835
34154
|
addonId: null,
|
|
33836
34155
|
access: "delete"
|
|
33837
34156
|
},
|
|
34157
|
+
"deviceManager.renameLocation": {
|
|
34158
|
+
capName: "device-manager",
|
|
34159
|
+
capScope: "system",
|
|
34160
|
+
addonId: null,
|
|
34161
|
+
access: "create"
|
|
34162
|
+
},
|
|
33838
34163
|
"deviceManager.runDeviceAction": {
|
|
33839
34164
|
capName: "device-manager",
|
|
33840
34165
|
capScope: "system",
|
|
@@ -35527,13 +35852,19 @@ Object.freeze({
|
|
|
35527
35852
|
addonId: null,
|
|
35528
35853
|
access: "view"
|
|
35529
35854
|
},
|
|
35530
|
-
"pipelineAnalytics.
|
|
35855
|
+
"pipelineAnalytics.getKeyEvents": {
|
|
35531
35856
|
capName: "pipeline-analytics",
|
|
35532
35857
|
capScope: "device",
|
|
35533
35858
|
addonId: null,
|
|
35534
35859
|
access: "view"
|
|
35535
35860
|
},
|
|
35536
|
-
"pipelineAnalytics.
|
|
35861
|
+
"pipelineAnalytics.getKeyEventsBatch": {
|
|
35862
|
+
capName: "pipeline-analytics",
|
|
35863
|
+
capScope: "device",
|
|
35864
|
+
addonId: null,
|
|
35865
|
+
access: "view"
|
|
35866
|
+
},
|
|
35867
|
+
"pipelineAnalytics.getMediaReclaimStatus": {
|
|
35537
35868
|
capName: "pipeline-analytics",
|
|
35538
35869
|
capScope: "device",
|
|
35539
35870
|
addonId: null,
|
|
@@ -35623,12 +35954,6 @@ Object.freeze({
|
|
|
35623
35954
|
addonId: null,
|
|
35624
35955
|
access: "view"
|
|
35625
35956
|
},
|
|
35626
|
-
"pipelineAnalytics.listGroups": {
|
|
35627
|
-
capName: "pipeline-analytics",
|
|
35628
|
-
capScope: "device",
|
|
35629
|
-
addonId: null,
|
|
35630
|
-
access: "view"
|
|
35631
|
-
},
|
|
35632
35957
|
"pipelineAnalytics.listOpsLog": {
|
|
35633
35958
|
capName: "pipeline-analytics",
|
|
35634
35959
|
capScope: "device",
|
|
@@ -35713,6 +36038,12 @@ Object.freeze({
|
|
|
35713
36038
|
addonId: null,
|
|
35714
36039
|
access: "create"
|
|
35715
36040
|
},
|
|
36041
|
+
"pipelineAnalytics.reclaimDebugMedia": {
|
|
36042
|
+
capName: "pipeline-analytics",
|
|
36043
|
+
capScope: "device",
|
|
36044
|
+
addonId: null,
|
|
36045
|
+
access: "create"
|
|
36046
|
+
},
|
|
35716
36047
|
"pipelineAnalytics.reconcileFromDisk": {
|
|
35717
36048
|
capName: "pipeline-analytics",
|
|
35718
36049
|
capScope: "device",
|
|
@@ -36637,6 +36968,12 @@ Object.freeze({
|
|
|
36637
36968
|
addonId: null,
|
|
36638
36969
|
access: "view"
|
|
36639
36970
|
},
|
|
36971
|
+
"recording.getPlacement": {
|
|
36972
|
+
capName: "recording",
|
|
36973
|
+
capScope: "system",
|
|
36974
|
+
addonId: null,
|
|
36975
|
+
access: "view"
|
|
36976
|
+
},
|
|
36640
36977
|
"recording.getPlaybackManifest": {
|
|
36641
36978
|
capName: "recording",
|
|
36642
36979
|
capScope: "system",
|
|
@@ -36715,6 +37052,12 @@ Object.freeze({
|
|
|
36715
37052
|
addonId: null,
|
|
36716
37053
|
access: "view"
|
|
36717
37054
|
},
|
|
37055
|
+
"recording.reconcileLedgerAgainstDisk": {
|
|
37056
|
+
capName: "recording",
|
|
37057
|
+
capScope: "system",
|
|
37058
|
+
addonId: null,
|
|
37059
|
+
access: "create"
|
|
37060
|
+
},
|
|
36718
37061
|
"recording.refreshStorageLocationsForMigration": {
|
|
36719
37062
|
capName: "recording",
|
|
36720
37063
|
capScope: "system",
|
|
@@ -36757,6 +37100,12 @@ Object.freeze({
|
|
|
36757
37100
|
addonId: null,
|
|
36758
37101
|
access: "create"
|
|
36759
37102
|
},
|
|
37103
|
+
"recording.setDevicePlacement": {
|
|
37104
|
+
capName: "recording",
|
|
37105
|
+
capScope: "system",
|
|
37106
|
+
addonId: null,
|
|
37107
|
+
access: "create"
|
|
37108
|
+
},
|
|
36760
37109
|
"recording.startStorageMigrationMove": {
|
|
36761
37110
|
capName: "recording",
|
|
36762
37111
|
capScope: "system",
|
|
@@ -36841,6 +37190,12 @@ Object.freeze({
|
|
|
36841
37190
|
addonId: null,
|
|
36842
37191
|
access: "view"
|
|
36843
37192
|
},
|
|
37193
|
+
"sceneMonitor.listScenesBatch": {
|
|
37194
|
+
capName: "scene-monitor",
|
|
37195
|
+
capScope: "device",
|
|
37196
|
+
addonId: null,
|
|
37197
|
+
access: "view"
|
|
37198
|
+
},
|
|
36844
37199
|
"sceneMonitor.recheckNow": {
|
|
36845
37200
|
capName: "scene-monitor",
|
|
36846
37201
|
capScope: "device",
|
|
@@ -37195,12 +37550,36 @@ Object.freeze({
|
|
|
37195
37550
|
addonId: null,
|
|
37196
37551
|
access: "create"
|
|
37197
37552
|
},
|
|
37553
|
+
"storageMigration.cleanupCancel": {
|
|
37554
|
+
capName: "storage-migration",
|
|
37555
|
+
capScope: "system",
|
|
37556
|
+
addonId: null,
|
|
37557
|
+
access: "create"
|
|
37558
|
+
},
|
|
37559
|
+
"storageMigration.cleanupStart": {
|
|
37560
|
+
capName: "storage-migration",
|
|
37561
|
+
capScope: "system",
|
|
37562
|
+
addonId: null,
|
|
37563
|
+
access: "create"
|
|
37564
|
+
},
|
|
37565
|
+
"storageMigration.cleanupStatus": {
|
|
37566
|
+
capName: "storage-migration",
|
|
37567
|
+
capScope: "system",
|
|
37568
|
+
addonId: null,
|
|
37569
|
+
access: "view"
|
|
37570
|
+
},
|
|
37198
37571
|
"storageMigration.drain": {
|
|
37199
37572
|
capName: "storage-migration",
|
|
37200
37573
|
capScope: "system",
|
|
37201
37574
|
addonId: null,
|
|
37202
37575
|
access: "create"
|
|
37203
37576
|
},
|
|
37577
|
+
"storageMigration.history": {
|
|
37578
|
+
capName: "storage-migration",
|
|
37579
|
+
capScope: "system",
|
|
37580
|
+
addonId: null,
|
|
37581
|
+
access: "view"
|
|
37582
|
+
},
|
|
37204
37583
|
"storageMigration.movers": {
|
|
37205
37584
|
capName: "storage-migration",
|
|
37206
37585
|
capScope: "system",
|
|
@@ -38197,6 +38576,12 @@ Object.freeze({
|
|
|
38197
38576
|
addonId: null,
|
|
38198
38577
|
access: "view"
|
|
38199
38578
|
},
|
|
38579
|
+
"zoneAnalytics.getCurrentSnapshotBatch": {
|
|
38580
|
+
capName: "zone-analytics",
|
|
38581
|
+
capScope: "device",
|
|
38582
|
+
addonId: null,
|
|
38583
|
+
access: "view"
|
|
38584
|
+
},
|
|
38200
38585
|
"zoneAnalytics.getUnzonedHistory": {
|
|
38201
38586
|
capName: "zone-analytics",
|
|
38202
38587
|
capScope: "device",
|
|
@@ -39191,14 +39576,14 @@ Object.freeze({
|
|
|
39191
39576
|
form: "single",
|
|
39192
39577
|
optional: true
|
|
39193
39578
|
}],
|
|
39194
|
-
"pipelineAnalytics.
|
|
39579
|
+
"pipelineAnalytics.getKeyEvents": [{
|
|
39195
39580
|
name: "deviceId",
|
|
39196
39581
|
form: "single",
|
|
39197
39582
|
optional: false
|
|
39198
39583
|
}],
|
|
39199
|
-
"pipelineAnalytics.
|
|
39200
|
-
name: "
|
|
39201
|
-
form: "
|
|
39584
|
+
"pipelineAnalytics.getKeyEventsBatch": [{
|
|
39585
|
+
name: "deviceIds",
|
|
39586
|
+
form: "array",
|
|
39202
39587
|
optional: false
|
|
39203
39588
|
}],
|
|
39204
39589
|
"pipelineAnalytics.getMotionEvents": [{
|
|
@@ -39256,11 +39641,6 @@ Object.freeze({
|
|
|
39256
39641
|
form: "single",
|
|
39257
39642
|
optional: false
|
|
39258
39643
|
}],
|
|
39259
|
-
"pipelineAnalytics.listGroups": [{
|
|
39260
|
-
name: "deviceIds",
|
|
39261
|
-
form: "array",
|
|
39262
|
-
optional: false
|
|
39263
|
-
}],
|
|
39264
39644
|
"pipelineAnalytics.listOpsLog": [{
|
|
39265
39645
|
name: "deviceId",
|
|
39266
39646
|
form: "single",
|
|
@@ -39306,6 +39686,11 @@ Object.freeze({
|
|
|
39306
39686
|
form: "single",
|
|
39307
39687
|
optional: true
|
|
39308
39688
|
}],
|
|
39689
|
+
"pipelineAnalytics.reclaimDebugMedia": [{
|
|
39690
|
+
name: "deviceIds",
|
|
39691
|
+
form: "array",
|
|
39692
|
+
optional: true
|
|
39693
|
+
}],
|
|
39309
39694
|
"pipelineAnalytics.reconcileFromDisk": [{
|
|
39310
39695
|
name: "deviceId",
|
|
39311
39696
|
form: "single",
|
|
@@ -39641,6 +40026,11 @@ Object.freeze({
|
|
|
39641
40026
|
form: "single",
|
|
39642
40027
|
optional: false
|
|
39643
40028
|
}],
|
|
40029
|
+
"recording.reconcileLedgerAgainstDisk": [{
|
|
40030
|
+
name: "deviceId",
|
|
40031
|
+
form: "single",
|
|
40032
|
+
optional: true
|
|
40033
|
+
}],
|
|
39644
40034
|
"recording.relocateFootage": [{
|
|
39645
40035
|
name: "deviceId",
|
|
39646
40036
|
form: "single",
|
|
@@ -39666,6 +40056,11 @@ Object.freeze({
|
|
|
39666
40056
|
form: "single",
|
|
39667
40057
|
optional: false
|
|
39668
40058
|
}],
|
|
40059
|
+
"recording.setDevicePlacement": [{
|
|
40060
|
+
name: "deviceId",
|
|
40061
|
+
form: "single",
|
|
40062
|
+
optional: false
|
|
40063
|
+
}],
|
|
39669
40064
|
"recording.startStorageMigrationMove": [{
|
|
39670
40065
|
name: "deviceId",
|
|
39671
40066
|
form: "single",
|
|
@@ -39706,6 +40101,11 @@ Object.freeze({
|
|
|
39706
40101
|
form: "single",
|
|
39707
40102
|
optional: false
|
|
39708
40103
|
}],
|
|
40104
|
+
"sceneMonitor.listScenesBatch": [{
|
|
40105
|
+
name: "deviceIds",
|
|
40106
|
+
form: "array",
|
|
40107
|
+
optional: false
|
|
40108
|
+
}],
|
|
39709
40109
|
"sceneMonitor.recheckNow": [{
|
|
39710
40110
|
name: "deviceId",
|
|
39711
40111
|
form: "single",
|
|
@@ -39967,6 +40367,11 @@ Object.freeze({
|
|
|
39967
40367
|
form: "single",
|
|
39968
40368
|
optional: false
|
|
39969
40369
|
}],
|
|
40370
|
+
"zoneAnalytics.getCurrentSnapshotBatch": [{
|
|
40371
|
+
name: "deviceIds",
|
|
40372
|
+
form: "array",
|
|
40373
|
+
optional: false
|
|
40374
|
+
}],
|
|
39970
40375
|
"zoneAnalytics.getUnzonedHistory": [{
|
|
39971
40376
|
name: "deviceId",
|
|
39972
40377
|
form: "single",
|
|
@@ -40540,6 +40945,111 @@ function resolveGlancesCursesShimEnv(options) {
|
|
|
40540
40945
|
return { PYTHONPATH: existing ? `${options.shimDir}:${existing}` : options.shimDir };
|
|
40541
40946
|
}
|
|
40542
40947
|
//#endregion
|
|
40948
|
+
//#region src/profile-settings.ts
|
|
40949
|
+
var GLANCES_PLUGINS = [
|
|
40950
|
+
{
|
|
40951
|
+
key: "showCpu",
|
|
40952
|
+
plugin: "cpu",
|
|
40953
|
+
label: "CPU"
|
|
40954
|
+
},
|
|
40955
|
+
{
|
|
40956
|
+
key: "showMem",
|
|
40957
|
+
plugin: "mem",
|
|
40958
|
+
label: "Memory"
|
|
40959
|
+
},
|
|
40960
|
+
{
|
|
40961
|
+
key: "showLoad",
|
|
40962
|
+
plugin: "load",
|
|
40963
|
+
label: "Load"
|
|
40964
|
+
},
|
|
40965
|
+
{
|
|
40966
|
+
key: "showNetwork",
|
|
40967
|
+
plugin: "network",
|
|
40968
|
+
label: "Network"
|
|
40969
|
+
},
|
|
40970
|
+
{
|
|
40971
|
+
key: "showDiskIo",
|
|
40972
|
+
plugin: "diskio",
|
|
40973
|
+
label: "Disk I/O"
|
|
40974
|
+
},
|
|
40975
|
+
{
|
|
40976
|
+
key: "showFs",
|
|
40977
|
+
plugin: "fs",
|
|
40978
|
+
label: "Filesystems"
|
|
40979
|
+
},
|
|
40980
|
+
{
|
|
40981
|
+
key: "showProcessList",
|
|
40982
|
+
plugin: "processlist",
|
|
40983
|
+
label: "Process list"
|
|
40984
|
+
},
|
|
40985
|
+
{
|
|
40986
|
+
key: "showContainers",
|
|
40987
|
+
plugin: "containers",
|
|
40988
|
+
label: "Containers"
|
|
40989
|
+
},
|
|
40990
|
+
{
|
|
40991
|
+
key: "showSensors",
|
|
40992
|
+
plugin: "sensors",
|
|
40993
|
+
label: "Sensors"
|
|
40994
|
+
}
|
|
40995
|
+
];
|
|
40996
|
+
function glancesBooleanField(key, label) {
|
|
40997
|
+
return {
|
|
40998
|
+
type: "boolean",
|
|
40999
|
+
key,
|
|
41000
|
+
label,
|
|
41001
|
+
default: true,
|
|
41002
|
+
style: "switch"
|
|
41003
|
+
};
|
|
41004
|
+
}
|
|
41005
|
+
function glancesSettingsSchema() {
|
|
41006
|
+
return { sections: [{
|
|
41007
|
+
id: "glances-panels",
|
|
41008
|
+
title: "Glances panels",
|
|
41009
|
+
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).",
|
|
41010
|
+
columns: 2,
|
|
41011
|
+
fields: GLANCES_PLUGINS.map((plugin) => glancesBooleanField(plugin.key, plugin.label))
|
|
41012
|
+
}] };
|
|
41013
|
+
}
|
|
41014
|
+
function settingsSchemaForProfile(profileId) {
|
|
41015
|
+
if (profileId === "glances") return glancesSettingsSchema();
|
|
41016
|
+
return null;
|
|
41017
|
+
}
|
|
41018
|
+
function glancesSettingsToArgs(settings) {
|
|
41019
|
+
const disabled = GLANCES_PLUGINS.filter((plugin) => settings[plugin.key] === false).map((plugin) => plugin.plugin);
|
|
41020
|
+
if (disabled.length === 0) return [];
|
|
41021
|
+
return ["--disable-plugin", disabled.join(",")];
|
|
41022
|
+
}
|
|
41023
|
+
function sanitizeProfileSettings(profileId, raw) {
|
|
41024
|
+
if (profileId !== "glances") return {};
|
|
41025
|
+
const bag = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
41026
|
+
const out = {
|
|
41027
|
+
showCpu: true,
|
|
41028
|
+
showMem: true,
|
|
41029
|
+
showLoad: true,
|
|
41030
|
+
showNetwork: true,
|
|
41031
|
+
showDiskIo: true,
|
|
41032
|
+
showFs: true,
|
|
41033
|
+
showProcessList: true,
|
|
41034
|
+
showContainers: true,
|
|
41035
|
+
showSensors: true
|
|
41036
|
+
};
|
|
41037
|
+
for (const plugin of GLANCES_PLUGINS) {
|
|
41038
|
+
const value = bag[plugin.key];
|
|
41039
|
+
if (typeof value === "boolean") out[plugin.key] = value;
|
|
41040
|
+
}
|
|
41041
|
+
return out;
|
|
41042
|
+
}
|
|
41043
|
+
function profileSettingsToArgs(profileId, settings) {
|
|
41044
|
+
if (profileId !== "glances") return [];
|
|
41045
|
+
return glancesSettingsToArgs(sanitizeProfileSettings(profileId, settings));
|
|
41046
|
+
}
|
|
41047
|
+
function spawnArgsForInstance(input) {
|
|
41048
|
+
const extra = profileSettingsToArgs(input.profileId, input.profileSettings);
|
|
41049
|
+
if (input.instanceArgs.length > 0) return [...input.instanceArgs, ...extra];
|
|
41050
|
+
if (extra.length > 0) return [...input.profileArgs, ...extra];
|
|
41051
|
+
}
|
|
41052
|
+
//#endregion
|
|
40543
41053
|
//#region src/pty.ts
|
|
40544
41054
|
/**
|
|
40545
41055
|
* Minimal pty abstraction. The manager depends on this interface, never on
|
|
@@ -40721,6 +41231,356 @@ async function silenceAnalysisFor(deps, deviceId) {
|
|
|
40721
41231
|
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("; ")})`);
|
|
40722
41232
|
}
|
|
40723
41233
|
//#endregion
|
|
41234
|
+
//#region src/terminal-camera-declarations.ts
|
|
41235
|
+
/**
|
|
41236
|
+
* Feed DeclaredDevices every live declaration plus one deterministic orphan
|
|
41237
|
+
* batch. The generic sweep intentionally refuses an over-limit set; selecting
|
|
41238
|
+
* a batch here drains large historical Terminal orphan sets across convergence
|
|
41239
|
+
* passes without weakening that global safety guard.
|
|
41240
|
+
*/
|
|
41241
|
+
function terminalCameraReconciliationIndex(rows, integrationId, declarations, managedStableIds = /* @__PURE__ */ new Set()) {
|
|
41242
|
+
if (!integrationId) return [];
|
|
41243
|
+
const declared = new Set(declarations.map((camera) => camera.stableId));
|
|
41244
|
+
const owned = rows.filter((row) => row.integrationId === integrationId && (declared.has(row.stableId) || managedStableIds.has(row.stableId) || row.stableId.startsWith("terminal-camera-instance-")));
|
|
41245
|
+
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)];
|
|
41246
|
+
}
|
|
41247
|
+
/** Explicit persisted instances, never the node × profile template matrix. */
|
|
41248
|
+
function buildTerminalInstanceCameraDeclarations(instances) {
|
|
41249
|
+
return instances.filter((instance) => instance.enabled).map((instance) => ({
|
|
41250
|
+
stableId: instance.cameraStableId,
|
|
41251
|
+
name: instance.name,
|
|
41252
|
+
config: {
|
|
41253
|
+
instanceId: instance.id,
|
|
41254
|
+
nodeId: instance.nodeId,
|
|
41255
|
+
profileId: instance.profileId,
|
|
41256
|
+
profileLabel: instance.profileLabel
|
|
41257
|
+
}
|
|
41258
|
+
}));
|
|
41259
|
+
}
|
|
41260
|
+
/**
|
|
41261
|
+
* `DeviceConfig` materializes schema defaults in memory, so comparing
|
|
41262
|
+
* `config.get()` cannot detect a legacy `{ nodeId }` row. Reconciliation must
|
|
41263
|
+
* inspect the raw persisted blob to make the profile migration durable.
|
|
41264
|
+
*/
|
|
41265
|
+
function needsTerminalCameraConfigMigration(persistedConfig, declaration) {
|
|
41266
|
+
return persistedConfig.instanceId !== declaration.config.instanceId || persistedConfig.nodeId !== declaration.config.nodeId || persistedConfig.profileId !== declaration.config.profileId || persistedConfig.profileLabel !== declaration.config.profileLabel;
|
|
41267
|
+
}
|
|
41268
|
+
//#endregion
|
|
41269
|
+
//#region src/terminal-cell-runs.ts
|
|
41270
|
+
var TERMINAL_DEFAULT_FG = "#d7dce2";
|
|
41271
|
+
var TERMINAL_DEFAULT_BG = "#0b0d10";
|
|
41272
|
+
/**
|
|
41273
|
+
* The 16 ANSI colours, in a dark-theme variant chosen to sit with the existing
|
|
41274
|
+
* frame (`#0b0d10` ground, `#d7dce2` text) rather than the raw xterm defaults,
|
|
41275
|
+
* whose pure `#0000ff` blue and `#008000` green are close to unreadable on a
|
|
41276
|
+
* near-black background at 13px. Index 7 IS the default foreground, so plain
|
|
41277
|
+
* `CSI 37m` text renders identically to unstyled text.
|
|
41278
|
+
*/
|
|
41279
|
+
var TERMINAL_ANSI_PALETTE = [
|
|
41280
|
+
"#282c34",
|
|
41281
|
+
"#e06c75",
|
|
41282
|
+
"#98c379",
|
|
41283
|
+
"#e5c07b",
|
|
41284
|
+
"#61afef",
|
|
41285
|
+
"#c678dd",
|
|
41286
|
+
"#56b6c2",
|
|
41287
|
+
TERMINAL_DEFAULT_FG,
|
|
41288
|
+
"#5c6370",
|
|
41289
|
+
"#ef596f",
|
|
41290
|
+
"#89ca78",
|
|
41291
|
+
"#f0c674",
|
|
41292
|
+
"#6cb6ff",
|
|
41293
|
+
"#d55fde",
|
|
41294
|
+
"#2bbac5",
|
|
41295
|
+
"#ffffff"
|
|
41296
|
+
];
|
|
41297
|
+
/** The six levels of the xterm 6×6×6 colour cube (indices 16-231). */
|
|
41298
|
+
var TERMINAL_CUBE_LEVELS = [
|
|
41299
|
+
0,
|
|
41300
|
+
95,
|
|
41301
|
+
135,
|
|
41302
|
+
175,
|
|
41303
|
+
215,
|
|
41304
|
+
255
|
|
41305
|
+
];
|
|
41306
|
+
var TERMINAL_CUBE_FIRST = 16;
|
|
41307
|
+
var TERMINAL_GRAYSCALE_FIRST = 232;
|
|
41308
|
+
var TERMINAL_GRAYSCALE_BASE = 8;
|
|
41309
|
+
var TERMINAL_GRAYSCALE_STEP = 10;
|
|
41310
|
+
/** SGR 2 keeps the foreground legible; it must not become the background. */
|
|
41311
|
+
var TERMINAL_DIM_WEIGHT = .6;
|
|
41312
|
+
function channel(value) {
|
|
41313
|
+
return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0");
|
|
41314
|
+
}
|
|
41315
|
+
function hex(red, green, blue) {
|
|
41316
|
+
return `#${channel(red)}${channel(green)}${channel(blue)}`;
|
|
41317
|
+
}
|
|
41318
|
+
function parseHex(color) {
|
|
41319
|
+
return [
|
|
41320
|
+
Number.parseInt(color.slice(1, 3), 16),
|
|
41321
|
+
Number.parseInt(color.slice(3, 5), 16),
|
|
41322
|
+
Number.parseInt(color.slice(5, 7), 16)
|
|
41323
|
+
];
|
|
41324
|
+
}
|
|
41325
|
+
/** Resolve an xterm palette index (0-255) to a hex colour. */
|
|
41326
|
+
function terminalPaletteColor(index) {
|
|
41327
|
+
const ansi = TERMINAL_ANSI_PALETTE[index];
|
|
41328
|
+
if (ansi !== void 0) return ansi;
|
|
41329
|
+
if (index >= TERMINAL_GRAYSCALE_FIRST) {
|
|
41330
|
+
const level = TERMINAL_GRAYSCALE_BASE + (index - TERMINAL_GRAYSCALE_FIRST) * TERMINAL_GRAYSCALE_STEP;
|
|
41331
|
+
return hex(level, level, level);
|
|
41332
|
+
}
|
|
41333
|
+
if (index >= TERMINAL_CUBE_FIRST) {
|
|
41334
|
+
const offset = index - TERMINAL_CUBE_FIRST;
|
|
41335
|
+
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);
|
|
41336
|
+
}
|
|
41337
|
+
return TERMINAL_DEFAULT_FG;
|
|
41338
|
+
}
|
|
41339
|
+
/** Resolve a 0xRRGGBB truecolor value to a hex colour. */
|
|
41340
|
+
function terminalRgbColor(value) {
|
|
41341
|
+
return hex(value >> 16 & 255, value >> 8 & 255, value & 255);
|
|
41342
|
+
}
|
|
41343
|
+
function blend(color, toward, weight) {
|
|
41344
|
+
const [red, green, blue] = parseHex(color);
|
|
41345
|
+
const [targetRed, targetGreen, targetBlue] = parseHex(toward);
|
|
41346
|
+
return hex(red * weight + targetRed * (1 - weight), green * weight + targetGreen * (1 - weight), blue * weight + targetBlue * (1 - weight));
|
|
41347
|
+
}
|
|
41348
|
+
function resolveForeground(cell) {
|
|
41349
|
+
if (cell.isFgRGB()) return terminalRgbColor(cell.getFgColor());
|
|
41350
|
+
if (cell.isFgPalette()) return terminalPaletteColor(cell.getFgColor());
|
|
41351
|
+
return TERMINAL_DEFAULT_FG;
|
|
41352
|
+
}
|
|
41353
|
+
function resolveBackground(cell) {
|
|
41354
|
+
if (cell.isBgRGB()) return terminalRgbColor(cell.getBgColor());
|
|
41355
|
+
if (cell.isBgPalette()) return terminalPaletteColor(cell.getBgColor());
|
|
41356
|
+
return TERMINAL_DEFAULT_BG;
|
|
41357
|
+
}
|
|
41358
|
+
/**
|
|
41359
|
+
* Resolve one cell's attributes into concrete colours.
|
|
41360
|
+
*
|
|
41361
|
+
* Inverse is applied by SWAPPING the already-resolved pair, so inverse over two
|
|
41362
|
+
* defaults is still a visible swap rather than a no-op — that is how a selected
|
|
41363
|
+
* or highlighted row in Glances reads. Invisible is then conceal-by-equality
|
|
41364
|
+
* (foreground painted in its own background): the cell keeps its columns, which
|
|
41365
|
+
* a dropped cell would not, and dropping it would shift the whole rest of the
|
|
41366
|
+
* row left.
|
|
41367
|
+
*/
|
|
41368
|
+
function resolveCellStyle(cell) {
|
|
41369
|
+
const inverse = cell.isInverse() !== 0;
|
|
41370
|
+
const plainFg = resolveForeground(cell);
|
|
41371
|
+
const plainBg = resolveBackground(cell);
|
|
41372
|
+
const background = inverse ? plainFg : plainBg;
|
|
41373
|
+
let foreground = inverse ? plainBg : plainFg;
|
|
41374
|
+
if (cell.isDim() !== 0) foreground = blend(foreground, background, TERMINAL_DIM_WEIGHT);
|
|
41375
|
+
if (cell.isInvisible() !== 0) foreground = background;
|
|
41376
|
+
return {
|
|
41377
|
+
fg: foreground === "#d7dce2" ? null : foreground,
|
|
41378
|
+
bg: background === "#0b0d10" ? null : background,
|
|
41379
|
+
bold: cell.isBold() !== 0
|
|
41380
|
+
};
|
|
41381
|
+
}
|
|
41382
|
+
function sameStyle(left, right) {
|
|
41383
|
+
return left.fg === right.fg && left.bg === right.bg && left.bold === right.bold;
|
|
41384
|
+
}
|
|
41385
|
+
/**
|
|
41386
|
+
* Merge adjacent same-style cells into runs, then drop the trailing run of
|
|
41387
|
+
* default-styled whitespace so a row costs what it draws — the same trim
|
|
41388
|
+
* `translateToString(true)` performs. Whitespace carrying a BACKGROUND is kept:
|
|
41389
|
+
* a green bar of spaces out to the right margin is a pixel Glances drew.
|
|
41390
|
+
*/
|
|
41391
|
+
function buildCellRuns(cells) {
|
|
41392
|
+
const runs = [];
|
|
41393
|
+
let text = "";
|
|
41394
|
+
let style = null;
|
|
41395
|
+
for (const cell of cells) {
|
|
41396
|
+
if (style !== null && sameStyle(style, cell.style)) {
|
|
41397
|
+
text += cell.text;
|
|
41398
|
+
continue;
|
|
41399
|
+
}
|
|
41400
|
+
if (style !== null) runs.push({
|
|
41401
|
+
text,
|
|
41402
|
+
...style
|
|
41403
|
+
});
|
|
41404
|
+
text = cell.text;
|
|
41405
|
+
style = cell.style;
|
|
41406
|
+
}
|
|
41407
|
+
if (style !== null) runs.push({
|
|
41408
|
+
text,
|
|
41409
|
+
...style
|
|
41410
|
+
});
|
|
41411
|
+
while (runs.length > 0) {
|
|
41412
|
+
const last = runs[runs.length - 1];
|
|
41413
|
+
if (last === void 0 || last.bg !== null) break;
|
|
41414
|
+
const trimmed = last.text.replace(/\s+$/u, "");
|
|
41415
|
+
if (trimmed === last.text) break;
|
|
41416
|
+
if (trimmed === "") {
|
|
41417
|
+
runs.pop();
|
|
41418
|
+
continue;
|
|
41419
|
+
}
|
|
41420
|
+
runs[runs.length - 1] = {
|
|
41421
|
+
...last,
|
|
41422
|
+
text: trimmed
|
|
41423
|
+
};
|
|
41424
|
+
break;
|
|
41425
|
+
}
|
|
41426
|
+
return runs;
|
|
41427
|
+
}
|
|
41428
|
+
/**
|
|
41429
|
+
* Monospace families to try, in order — NOT one family and a generic.
|
|
41430
|
+
*
|
|
41431
|
+
* A terminal screen is mostly box-drawing and block characters, and a font
|
|
41432
|
+
* without them renders the frame as noise rather than as missing detail.
|
|
41433
|
+
* `DejaVu Sans Mono` covers them and ships in the hub image; macOS has neither
|
|
41434
|
+
* DejaVu nor fontconfig, so the Mac agent fell through to a generic whose
|
|
41435
|
+
* coverage is not, and its Glances camera came out unreadable while the hub's
|
|
41436
|
+
* was pixel-perfect (measured 2026-08-11, same Glances, two nodes).
|
|
41437
|
+
*
|
|
41438
|
+
* `Menlo` is the fix rather than a guess: it is macOS's default terminal face,
|
|
41439
|
+
* present on every install, and derived from DejaVu Sans Mono — the same glyph
|
|
41440
|
+
* coverage by ancestry. Monaco and Courier New follow as older fallbacks; the
|
|
41441
|
+
* generic stays last so a host with none of them still draws something.
|
|
41442
|
+
*/
|
|
41443
|
+
var TERMINAL_FONT_STACK = "DejaVu Sans Mono,Menlo,Monaco,Courier New,monospace";
|
|
41444
|
+
var TERMINAL_FONT_SIZE = 13;
|
|
41445
|
+
var TERMINAL_TEXT_MARGIN_X = 8;
|
|
41446
|
+
var TERMINAL_ROW_HEIGHT = 15;
|
|
41447
|
+
var TERMINAL_BASELINE_Y = 18;
|
|
41448
|
+
/**
|
|
41449
|
+
* Distance from a row's baseline up to the top of its cell box. Chosen so
|
|
41450
|
+
* consecutive rows tile exactly: row N's box runs from `baseline - this` for
|
|
41451
|
+
* `TERMINAL_ROW_HEIGHT`, and row N+1's box starts where it ends. A background
|
|
41452
|
+
* bar that stopped short would draw as stripes across a `CSI 42m` panel.
|
|
41453
|
+
*/
|
|
41454
|
+
var TERMINAL_CELL_ASCENT = 11.5;
|
|
41455
|
+
var TERMINAL_CELL_WIDTH = TERMINAL_FONT_SIZE * (1233 / 2048);
|
|
41456
|
+
/** Two decimals is under a tenth of a pixel and keeps the SVG small. */
|
|
41457
|
+
function coordinate(value) {
|
|
41458
|
+
return String(Number(value.toFixed(2)));
|
|
41459
|
+
}
|
|
41460
|
+
function escapeXml(value) {
|
|
41461
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
41462
|
+
}
|
|
41463
|
+
/**
|
|
41464
|
+
* Render already-interpreted terminal rows into a compact MJPEG frame.
|
|
41465
|
+
*
|
|
41466
|
+
* `xml:space="preserve"` is load-bearing, not tidiness. SVG `<text>` collapses
|
|
41467
|
+
* runs of whitespace by default, and a terminal's entire column alignment IS
|
|
41468
|
+
* runs of whitespace — Glances pads every field with spaces. Without it the
|
|
41469
|
+
* frame drew each line at roughly half its true width, crammed into the
|
|
41470
|
+
* top-left of a mostly-black image, while the SAME session over `attach`
|
|
41471
|
+
* looked perfect — which is exactly how the operator reported it. Measured in
|
|
41472
|
+
* the hub's own rasterizer 2026-08-11: `AAA<10 spaces>BBB<3>CCC` drew 92 px
|
|
41473
|
+
* collapsed against 178 px preserved.
|
|
41474
|
+
*
|
|
41475
|
+
* Every run is anchored at its OWN column (`x = margin + startCol * cellW`),
|
|
41476
|
+
* never appended to the one before it, so the background rects and the glyphs
|
|
41477
|
+
* are placed off the same grid and cannot drift apart. `textLength` is emitted
|
|
41478
|
+
* with it because it is the correct declaration and renderers that honour it
|
|
41479
|
+
* get an exact grid — but it is not what makes this work: librsvg, which sharp
|
|
41480
|
+
* uses, ignores it outright (measured 2026-08-12, a 120-glyph run pinned to
|
|
41481
|
+
* 600 px still drew its natural 937 px). The anchoring is the guarantee.
|
|
41482
|
+
*/
|
|
41483
|
+
function renderTerminalSvg(rows) {
|
|
41484
|
+
const backgrounds = [];
|
|
41485
|
+
const texts = [];
|
|
41486
|
+
rows.slice(0, 40).forEach((row, index) => {
|
|
41487
|
+
const baseline = TERMINAL_BASELINE_Y + index * TERMINAL_ROW_HEIGHT;
|
|
41488
|
+
const top = baseline - TERMINAL_CELL_ASCENT;
|
|
41489
|
+
let column = 0;
|
|
41490
|
+
for (const run of row) {
|
|
41491
|
+
if (column >= 120) break;
|
|
41492
|
+
const clipped = clipRun(run, 120 - column);
|
|
41493
|
+
const columns = [...clipped].length;
|
|
41494
|
+
if (columns === 0) continue;
|
|
41495
|
+
const x = TERMINAL_TEXT_MARGIN_X + column * TERMINAL_CELL_WIDTH;
|
|
41496
|
+
const width = columns * TERMINAL_CELL_WIDTH;
|
|
41497
|
+
if (run.bg !== null) backgrounds.push(`<rect x="${coordinate(x)}" y="${coordinate(top)}" width="${coordinate(width)}" height="${String(TERMINAL_ROW_HEIGHT)}" fill="${run.bg}"/>`);
|
|
41498
|
+
if (clipped.trim() !== "") {
|
|
41499
|
+
const fill = run.fg === null ? "" : ` fill="${run.fg}"`;
|
|
41500
|
+
const weight = run.bold ? " font-weight=\"bold\"" : "";
|
|
41501
|
+
texts.push(`<text xml:space="preserve" x="${coordinate(x)}" y="${coordinate(baseline)}" textLength="${coordinate(width)}" lengthAdjust="spacingAndGlyphs"${fill}${weight}>${escapeXml(clipped)}</text>`);
|
|
41502
|
+
}
|
|
41503
|
+
column += columns;
|
|
41504
|
+
}
|
|
41505
|
+
});
|
|
41506
|
+
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>`;
|
|
41507
|
+
}
|
|
41508
|
+
/** Cut a run to the columns still left in the row, by code point not unit. */
|
|
41509
|
+
function clipRun(run, remaining) {
|
|
41510
|
+
const points = [...run.text];
|
|
41511
|
+
return points.length <= remaining ? run.text : points.slice(0, remaining).join("");
|
|
41512
|
+
}
|
|
41513
|
+
async function renderTerminalJpeg(rows) {
|
|
41514
|
+
return sharp(Buffer.from(renderTerminalSvg(rows))).jpeg({
|
|
41515
|
+
quality: 82,
|
|
41516
|
+
chromaSubsampling: "4:2:0"
|
|
41517
|
+
}).toBuffer();
|
|
41518
|
+
}
|
|
41519
|
+
//#endregion
|
|
41520
|
+
//#region src/terminal-camera-device.ts
|
|
41521
|
+
var terminalCameraSchema = object({
|
|
41522
|
+
instanceId: string().min(1).optional(),
|
|
41523
|
+
nodeId: string().min(1),
|
|
41524
|
+
profileId: string().min(1).default("monitor"),
|
|
41525
|
+
profileLabel: string().min(1).default("BTM")
|
|
41526
|
+
});
|
|
41527
|
+
var relay = null;
|
|
41528
|
+
function installTerminalCameraRelay(next) {
|
|
41529
|
+
relay = next;
|
|
41530
|
+
}
|
|
41531
|
+
var TerminalCameraDevice = class extends BaseDevice {
|
|
41532
|
+
features = [DeviceFeature.NativeSnapshot];
|
|
41533
|
+
constructor(ctx) {
|
|
41534
|
+
super(ctx, terminalCameraSchema, { type: ctx.deviceMeta.type });
|
|
41535
|
+
this.ctx.registerNativeCap(streamCatalogCapability, { getCatalog: async ({ deviceId }) => {
|
|
41536
|
+
if (deviceId !== this.id) return [];
|
|
41537
|
+
return this.catalog();
|
|
41538
|
+
} });
|
|
41539
|
+
this.ctx.registerNativeCap(snapshotCapability, {
|
|
41540
|
+
getSnapshot: async ({ deviceId }) => {
|
|
41541
|
+
if (deviceId !== this.id) throw new Error(`TerminalCameraDevice: deviceId mismatch, expected ${String(this.id)}, got ${String(deviceId)}`);
|
|
41542
|
+
const activeRelay = relay;
|
|
41543
|
+
if (!activeRelay) throw new Error("terminal camera relay is unavailable");
|
|
41544
|
+
return {
|
|
41545
|
+
base64: (await activeRelay.snapshot(this.relayInstanceId(), this.config.get("nodeId"), this.config.get("profileId"))).toString("base64"),
|
|
41546
|
+
contentType: "image/jpeg"
|
|
41547
|
+
};
|
|
41548
|
+
},
|
|
41549
|
+
invalidateCache: async () => {}
|
|
41550
|
+
});
|
|
41551
|
+
this.markOnline(true);
|
|
41552
|
+
}
|
|
41553
|
+
async catalog() {
|
|
41554
|
+
const activeRelay = relay;
|
|
41555
|
+
if (!activeRelay) throw new Error("terminal camera relay is unavailable");
|
|
41556
|
+
const nodeId = this.config.get("nodeId");
|
|
41557
|
+
const profileId = this.config.get("profileId");
|
|
41558
|
+
const instanceId = this.relayInstanceId();
|
|
41559
|
+
return [{
|
|
41560
|
+
camStreamId: profileId,
|
|
41561
|
+
kind: "pull-http",
|
|
41562
|
+
url: activeRelay.streamUrl(instanceId, nodeId, profileId),
|
|
41563
|
+
codec: "h264",
|
|
41564
|
+
resolution: {
|
|
41565
|
+
width: 960,
|
|
41566
|
+
height: 640
|
|
41567
|
+
},
|
|
41568
|
+
fps: 2,
|
|
41569
|
+
label: this.config.get("profileLabel")
|
|
41570
|
+
}];
|
|
41571
|
+
}
|
|
41572
|
+
setNodeOnline(online) {
|
|
41573
|
+
this.markOnline(online);
|
|
41574
|
+
if (!online) relay?.closeInstance(this.relayInstanceId());
|
|
41575
|
+
}
|
|
41576
|
+
async removeDevice() {
|
|
41577
|
+
await relay?.closeInstance(this.relayInstanceId());
|
|
41578
|
+
}
|
|
41579
|
+
relayInstanceId() {
|
|
41580
|
+
return this.config.get("instanceId") ?? `legacy:${this.stableId}`;
|
|
41581
|
+
}
|
|
41582
|
+
};
|
|
41583
|
+
//#endregion
|
|
40724
41584
|
//#region ../../node_modules/@xterm/addon-serialize/lib/addon-serialize.js
|
|
40725
41585
|
var require_addon_serialize = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
40726
41586
|
(function(e, t) {
|
|
@@ -45528,174 +46388,9 @@ var require_xterm_headless = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
45528
46388
|
})();
|
|
45529
46389
|
}));
|
|
45530
46390
|
//#endregion
|
|
45531
|
-
//#region src/
|
|
46391
|
+
//#region src/xterm-screen.ts
|
|
45532
46392
|
var import_addon_serialize = require_addon_serialize();
|
|
45533
46393
|
var import_xterm_headless = require_xterm_headless();
|
|
45534
|
-
var TERMINAL_DEFAULT_FG = "#d7dce2";
|
|
45535
|
-
var TERMINAL_DEFAULT_BG = "#0b0d10";
|
|
45536
|
-
/**
|
|
45537
|
-
* The 16 ANSI colours, in a dark-theme variant chosen to sit with the existing
|
|
45538
|
-
* frame (`#0b0d10` ground, `#d7dce2` text) rather than the raw xterm defaults,
|
|
45539
|
-
* whose pure `#0000ff` blue and `#008000` green are close to unreadable on a
|
|
45540
|
-
* near-black background at 13px. Index 7 IS the default foreground, so plain
|
|
45541
|
-
* `CSI 37m` text renders identically to unstyled text.
|
|
45542
|
-
*/
|
|
45543
|
-
var TERMINAL_ANSI_PALETTE = [
|
|
45544
|
-
"#282c34",
|
|
45545
|
-
"#e06c75",
|
|
45546
|
-
"#98c379",
|
|
45547
|
-
"#e5c07b",
|
|
45548
|
-
"#61afef",
|
|
45549
|
-
"#c678dd",
|
|
45550
|
-
"#56b6c2",
|
|
45551
|
-
TERMINAL_DEFAULT_FG,
|
|
45552
|
-
"#5c6370",
|
|
45553
|
-
"#ef596f",
|
|
45554
|
-
"#89ca78",
|
|
45555
|
-
"#f0c674",
|
|
45556
|
-
"#6cb6ff",
|
|
45557
|
-
"#d55fde",
|
|
45558
|
-
"#2bbac5",
|
|
45559
|
-
"#ffffff"
|
|
45560
|
-
];
|
|
45561
|
-
/** The six levels of the xterm 6×6×6 colour cube (indices 16-231). */
|
|
45562
|
-
var TERMINAL_CUBE_LEVELS = [
|
|
45563
|
-
0,
|
|
45564
|
-
95,
|
|
45565
|
-
135,
|
|
45566
|
-
175,
|
|
45567
|
-
215,
|
|
45568
|
-
255
|
|
45569
|
-
];
|
|
45570
|
-
var TERMINAL_CUBE_FIRST = 16;
|
|
45571
|
-
var TERMINAL_GRAYSCALE_FIRST = 232;
|
|
45572
|
-
var TERMINAL_GRAYSCALE_BASE = 8;
|
|
45573
|
-
var TERMINAL_GRAYSCALE_STEP = 10;
|
|
45574
|
-
/** SGR 2 keeps the foreground legible; it must not become the background. */
|
|
45575
|
-
var TERMINAL_DIM_WEIGHT = .6;
|
|
45576
|
-
function channel(value) {
|
|
45577
|
-
return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0");
|
|
45578
|
-
}
|
|
45579
|
-
function hex(red, green, blue) {
|
|
45580
|
-
return `#${channel(red)}${channel(green)}${channel(blue)}`;
|
|
45581
|
-
}
|
|
45582
|
-
function parseHex(color) {
|
|
45583
|
-
return [
|
|
45584
|
-
Number.parseInt(color.slice(1, 3), 16),
|
|
45585
|
-
Number.parseInt(color.slice(3, 5), 16),
|
|
45586
|
-
Number.parseInt(color.slice(5, 7), 16)
|
|
45587
|
-
];
|
|
45588
|
-
}
|
|
45589
|
-
/** Resolve an xterm palette index (0-255) to a hex colour. */
|
|
45590
|
-
function terminalPaletteColor(index) {
|
|
45591
|
-
const ansi = TERMINAL_ANSI_PALETTE[index];
|
|
45592
|
-
if (ansi !== void 0) return ansi;
|
|
45593
|
-
if (index >= TERMINAL_GRAYSCALE_FIRST) {
|
|
45594
|
-
const level = TERMINAL_GRAYSCALE_BASE + (index - TERMINAL_GRAYSCALE_FIRST) * TERMINAL_GRAYSCALE_STEP;
|
|
45595
|
-
return hex(level, level, level);
|
|
45596
|
-
}
|
|
45597
|
-
if (index >= TERMINAL_CUBE_FIRST) {
|
|
45598
|
-
const offset = index - TERMINAL_CUBE_FIRST;
|
|
45599
|
-
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);
|
|
45600
|
-
}
|
|
45601
|
-
return TERMINAL_DEFAULT_FG;
|
|
45602
|
-
}
|
|
45603
|
-
/** Resolve a 0xRRGGBB truecolor value to a hex colour. */
|
|
45604
|
-
function terminalRgbColor(value) {
|
|
45605
|
-
return hex(value >> 16 & 255, value >> 8 & 255, value & 255);
|
|
45606
|
-
}
|
|
45607
|
-
function blend(color, toward, weight) {
|
|
45608
|
-
const [red, green, blue] = parseHex(color);
|
|
45609
|
-
const [targetRed, targetGreen, targetBlue] = parseHex(toward);
|
|
45610
|
-
return hex(red * weight + targetRed * (1 - weight), green * weight + targetGreen * (1 - weight), blue * weight + targetBlue * (1 - weight));
|
|
45611
|
-
}
|
|
45612
|
-
function resolveForeground(cell) {
|
|
45613
|
-
if (cell.isFgRGB()) return terminalRgbColor(cell.getFgColor());
|
|
45614
|
-
if (cell.isFgPalette()) return terminalPaletteColor(cell.getFgColor());
|
|
45615
|
-
return TERMINAL_DEFAULT_FG;
|
|
45616
|
-
}
|
|
45617
|
-
function resolveBackground(cell) {
|
|
45618
|
-
if (cell.isBgRGB()) return terminalRgbColor(cell.getBgColor());
|
|
45619
|
-
if (cell.isBgPalette()) return terminalPaletteColor(cell.getBgColor());
|
|
45620
|
-
return TERMINAL_DEFAULT_BG;
|
|
45621
|
-
}
|
|
45622
|
-
/**
|
|
45623
|
-
* Resolve one cell's attributes into concrete colours.
|
|
45624
|
-
*
|
|
45625
|
-
* Inverse is applied by SWAPPING the already-resolved pair, so inverse over two
|
|
45626
|
-
* defaults is still a visible swap rather than a no-op — that is how a selected
|
|
45627
|
-
* or highlighted row in Glances reads. Invisible is then conceal-by-equality
|
|
45628
|
-
* (foreground painted in its own background): the cell keeps its columns, which
|
|
45629
|
-
* a dropped cell would not, and dropping it would shift the whole rest of the
|
|
45630
|
-
* row left.
|
|
45631
|
-
*/
|
|
45632
|
-
function resolveCellStyle(cell) {
|
|
45633
|
-
const inverse = cell.isInverse() !== 0;
|
|
45634
|
-
const plainFg = resolveForeground(cell);
|
|
45635
|
-
const plainBg = resolveBackground(cell);
|
|
45636
|
-
const background = inverse ? plainFg : plainBg;
|
|
45637
|
-
let foreground = inverse ? plainBg : plainFg;
|
|
45638
|
-
if (cell.isDim() !== 0) foreground = blend(foreground, background, TERMINAL_DIM_WEIGHT);
|
|
45639
|
-
if (cell.isInvisible() !== 0) foreground = background;
|
|
45640
|
-
return {
|
|
45641
|
-
fg: foreground === "#d7dce2" ? null : foreground,
|
|
45642
|
-
bg: background === "#0b0d10" ? null : background,
|
|
45643
|
-
bold: cell.isBold() !== 0
|
|
45644
|
-
};
|
|
45645
|
-
}
|
|
45646
|
-
function sameStyle(left, right) {
|
|
45647
|
-
return left.fg === right.fg && left.bg === right.bg && left.bold === right.bold;
|
|
45648
|
-
}
|
|
45649
|
-
/**
|
|
45650
|
-
* Merge adjacent same-style cells into runs, then drop the trailing run of
|
|
45651
|
-
* default-styled whitespace so a row costs what it draws — the same trim
|
|
45652
|
-
* `translateToString(true)` performs. Whitespace carrying a BACKGROUND is kept:
|
|
45653
|
-
* a green bar of spaces out to the right margin is a pixel Glances drew.
|
|
45654
|
-
*/
|
|
45655
|
-
function buildCellRuns(cells) {
|
|
45656
|
-
const runs = [];
|
|
45657
|
-
let text = "";
|
|
45658
|
-
let style = null;
|
|
45659
|
-
for (const cell of cells) {
|
|
45660
|
-
if (style !== null && sameStyle(style, cell.style)) {
|
|
45661
|
-
text += cell.text;
|
|
45662
|
-
continue;
|
|
45663
|
-
}
|
|
45664
|
-
if (style !== null) runs.push({
|
|
45665
|
-
text,
|
|
45666
|
-
...style
|
|
45667
|
-
});
|
|
45668
|
-
text = cell.text;
|
|
45669
|
-
style = cell.style;
|
|
45670
|
-
}
|
|
45671
|
-
if (style !== null) runs.push({
|
|
45672
|
-
text,
|
|
45673
|
-
...style
|
|
45674
|
-
});
|
|
45675
|
-
while (runs.length > 0) {
|
|
45676
|
-
const last = runs[runs.length - 1];
|
|
45677
|
-
if (last === void 0 || last.bg !== null) break;
|
|
45678
|
-
const trimmed = last.text.replace(/\s+$/u, "");
|
|
45679
|
-
if (trimmed === last.text) break;
|
|
45680
|
-
if (trimmed === "") {
|
|
45681
|
-
runs.pop();
|
|
45682
|
-
continue;
|
|
45683
|
-
}
|
|
45684
|
-
runs[runs.length - 1] = {
|
|
45685
|
-
...last,
|
|
45686
|
-
text: trimmed
|
|
45687
|
-
};
|
|
45688
|
-
break;
|
|
45689
|
-
}
|
|
45690
|
-
return runs;
|
|
45691
|
-
}
|
|
45692
|
-
//#endregion
|
|
45693
|
-
//#region src/xterm-screen.ts
|
|
45694
|
-
/**
|
|
45695
|
-
* Real {@link ScreenBuffer} backed by `@xterm/headless` — the DOM-less xterm
|
|
45696
|
-
* build — plus the serialize addon, which turns the current buffer into a
|
|
45697
|
-
* self-contained repaint escape sequence for reconnecting clients.
|
|
45698
|
-
*/
|
|
45699
46394
|
var SCROLLBACK_LINES = 2e3;
|
|
45700
46395
|
function createXtermScreen(cols, rows) {
|
|
45701
46396
|
const term = new import_xterm_headless.Terminal({
|
|
@@ -45762,196 +46457,6 @@ function createXtermScreen(cols, rows) {
|
|
|
45762
46457
|
};
|
|
45763
46458
|
}
|
|
45764
46459
|
//#endregion
|
|
45765
|
-
//#region src/terminal-camera-declarations.ts
|
|
45766
|
-
/**
|
|
45767
|
-
* Feed DeclaredDevices every live declaration plus one deterministic orphan
|
|
45768
|
-
* batch. The generic sweep intentionally refuses an over-limit set; selecting
|
|
45769
|
-
* a batch here drains large historical Terminal orphan sets across convergence
|
|
45770
|
-
* passes without weakening that global safety guard.
|
|
45771
|
-
*/
|
|
45772
|
-
function terminalCameraReconciliationIndex(rows, integrationId, declarations, managedStableIds = /* @__PURE__ */ new Set()) {
|
|
45773
|
-
if (!integrationId) return [];
|
|
45774
|
-
const declared = new Set(declarations.map((camera) => camera.stableId));
|
|
45775
|
-
const owned = rows.filter((row) => row.integrationId === integrationId && (declared.has(row.stableId) || managedStableIds.has(row.stableId) || row.stableId.startsWith("terminal-camera-instance-")));
|
|
45776
|
-
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)];
|
|
45777
|
-
}
|
|
45778
|
-
/** Explicit persisted instances, never the node × profile template matrix. */
|
|
45779
|
-
function buildTerminalInstanceCameraDeclarations(instances) {
|
|
45780
|
-
return instances.filter((instance) => instance.enabled).map((instance) => ({
|
|
45781
|
-
stableId: instance.cameraStableId,
|
|
45782
|
-
name: instance.name,
|
|
45783
|
-
config: {
|
|
45784
|
-
instanceId: instance.id,
|
|
45785
|
-
nodeId: instance.nodeId,
|
|
45786
|
-
profileId: instance.profileId,
|
|
45787
|
-
profileLabel: instance.profileLabel
|
|
45788
|
-
}
|
|
45789
|
-
}));
|
|
45790
|
-
}
|
|
45791
|
-
/**
|
|
45792
|
-
* `DeviceConfig` materializes schema defaults in memory, so comparing
|
|
45793
|
-
* `config.get()` cannot detect a legacy `{ nodeId }` row. Reconciliation must
|
|
45794
|
-
* inspect the raw persisted blob to make the profile migration durable.
|
|
45795
|
-
*/
|
|
45796
|
-
function needsTerminalCameraConfigMigration(persistedConfig, declaration) {
|
|
45797
|
-
return persistedConfig.instanceId !== declaration.config.instanceId || persistedConfig.nodeId !== declaration.config.nodeId || persistedConfig.profileId !== declaration.config.profileId || persistedConfig.profileLabel !== declaration.config.profileLabel;
|
|
45798
|
-
}
|
|
45799
|
-
/**
|
|
45800
|
-
* Monospace families to try, in order — NOT one family and a generic.
|
|
45801
|
-
*
|
|
45802
|
-
* A terminal screen is mostly box-drawing and block characters, and a font
|
|
45803
|
-
* without them renders the frame as noise rather than as missing detail.
|
|
45804
|
-
* `DejaVu Sans Mono` covers them and ships in the hub image; macOS has neither
|
|
45805
|
-
* DejaVu nor fontconfig, so the Mac agent fell through to a generic whose
|
|
45806
|
-
* coverage is not, and its Glances camera came out unreadable while the hub's
|
|
45807
|
-
* was pixel-perfect (measured 2026-08-11, same Glances, two nodes).
|
|
45808
|
-
*
|
|
45809
|
-
* `Menlo` is the fix rather than a guess: it is macOS's default terminal face,
|
|
45810
|
-
* present on every install, and derived from DejaVu Sans Mono — the same glyph
|
|
45811
|
-
* coverage by ancestry. Monaco and Courier New follow as older fallbacks; the
|
|
45812
|
-
* generic stays last so a host with none of them still draws something.
|
|
45813
|
-
*/
|
|
45814
|
-
var TERMINAL_FONT_STACK = "DejaVu Sans Mono,Menlo,Monaco,Courier New,monospace";
|
|
45815
|
-
var TERMINAL_FONT_SIZE = 13;
|
|
45816
|
-
var TERMINAL_TEXT_MARGIN_X = 8;
|
|
45817
|
-
var TERMINAL_ROW_HEIGHT = 15;
|
|
45818
|
-
var TERMINAL_BASELINE_Y = 18;
|
|
45819
|
-
/**
|
|
45820
|
-
* Distance from a row's baseline up to the top of its cell box. Chosen so
|
|
45821
|
-
* consecutive rows tile exactly: row N's box runs from `baseline - this` for
|
|
45822
|
-
* `TERMINAL_ROW_HEIGHT`, and row N+1's box starts where it ends. A background
|
|
45823
|
-
* bar that stopped short would draw as stripes across a `CSI 42m` panel.
|
|
45824
|
-
*/
|
|
45825
|
-
var TERMINAL_CELL_ASCENT = 11.5;
|
|
45826
|
-
var TERMINAL_CELL_WIDTH = TERMINAL_FONT_SIZE * (1233 / 2048);
|
|
45827
|
-
/** Two decimals is under a tenth of a pixel and keeps the SVG small. */
|
|
45828
|
-
function coordinate(value) {
|
|
45829
|
-
return String(Number(value.toFixed(2)));
|
|
45830
|
-
}
|
|
45831
|
-
function escapeXml(value) {
|
|
45832
|
-
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
45833
|
-
}
|
|
45834
|
-
/**
|
|
45835
|
-
* Render already-interpreted terminal rows into a compact MJPEG frame.
|
|
45836
|
-
*
|
|
45837
|
-
* `xml:space="preserve"` is load-bearing, not tidiness. SVG `<text>` collapses
|
|
45838
|
-
* runs of whitespace by default, and a terminal's entire column alignment IS
|
|
45839
|
-
* runs of whitespace — Glances pads every field with spaces. Without it the
|
|
45840
|
-
* frame drew each line at roughly half its true width, crammed into the
|
|
45841
|
-
* top-left of a mostly-black image, while the SAME session over `attach`
|
|
45842
|
-
* looked perfect — which is exactly how the operator reported it. Measured in
|
|
45843
|
-
* the hub's own rasterizer 2026-08-11: `AAA<10 spaces>BBB<3>CCC` drew 92 px
|
|
45844
|
-
* collapsed against 178 px preserved.
|
|
45845
|
-
*
|
|
45846
|
-
* Every run is anchored at its OWN column (`x = margin + startCol * cellW`),
|
|
45847
|
-
* never appended to the one before it, so the background rects and the glyphs
|
|
45848
|
-
* are placed off the same grid and cannot drift apart. `textLength` is emitted
|
|
45849
|
-
* with it because it is the correct declaration and renderers that honour it
|
|
45850
|
-
* get an exact grid — but it is not what makes this work: librsvg, which sharp
|
|
45851
|
-
* uses, ignores it outright (measured 2026-08-12, a 120-glyph run pinned to
|
|
45852
|
-
* 600 px still drew its natural 937 px). The anchoring is the guarantee.
|
|
45853
|
-
*/
|
|
45854
|
-
function renderTerminalSvg(rows) {
|
|
45855
|
-
const backgrounds = [];
|
|
45856
|
-
const texts = [];
|
|
45857
|
-
rows.slice(0, 40).forEach((row, index) => {
|
|
45858
|
-
const baseline = TERMINAL_BASELINE_Y + index * TERMINAL_ROW_HEIGHT;
|
|
45859
|
-
const top = baseline - TERMINAL_CELL_ASCENT;
|
|
45860
|
-
let column = 0;
|
|
45861
|
-
for (const run of row) {
|
|
45862
|
-
if (column >= 120) break;
|
|
45863
|
-
const clipped = clipRun(run, 120 - column);
|
|
45864
|
-
const columns = [...clipped].length;
|
|
45865
|
-
if (columns === 0) continue;
|
|
45866
|
-
const x = TERMINAL_TEXT_MARGIN_X + column * TERMINAL_CELL_WIDTH;
|
|
45867
|
-
const width = columns * TERMINAL_CELL_WIDTH;
|
|
45868
|
-
if (run.bg !== null) backgrounds.push(`<rect x="${coordinate(x)}" y="${coordinate(top)}" width="${coordinate(width)}" height="${String(TERMINAL_ROW_HEIGHT)}" fill="${run.bg}"/>`);
|
|
45869
|
-
if (clipped.trim() !== "") {
|
|
45870
|
-
const fill = run.fg === null ? "" : ` fill="${run.fg}"`;
|
|
45871
|
-
const weight = run.bold ? " font-weight=\"bold\"" : "";
|
|
45872
|
-
texts.push(`<text xml:space="preserve" x="${coordinate(x)}" y="${coordinate(baseline)}" textLength="${coordinate(width)}" lengthAdjust="spacingAndGlyphs"${fill}${weight}>${escapeXml(clipped)}</text>`);
|
|
45873
|
-
}
|
|
45874
|
-
column += columns;
|
|
45875
|
-
}
|
|
45876
|
-
});
|
|
45877
|
-
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>`;
|
|
45878
|
-
}
|
|
45879
|
-
/** Cut a run to the columns still left in the row, by code point not unit. */
|
|
45880
|
-
function clipRun(run, remaining) {
|
|
45881
|
-
const points = [...run.text];
|
|
45882
|
-
return points.length <= remaining ? run.text : points.slice(0, remaining).join("");
|
|
45883
|
-
}
|
|
45884
|
-
async function renderTerminalJpeg(rows) {
|
|
45885
|
-
return sharp(Buffer.from(renderTerminalSvg(rows))).jpeg({
|
|
45886
|
-
quality: 82,
|
|
45887
|
-
chromaSubsampling: "4:2:0"
|
|
45888
|
-
}).toBuffer();
|
|
45889
|
-
}
|
|
45890
|
-
//#endregion
|
|
45891
|
-
//#region src/terminal-camera-device.ts
|
|
45892
|
-
var terminalCameraSchema = object({
|
|
45893
|
-
instanceId: string().min(1).optional(),
|
|
45894
|
-
nodeId: string().min(1),
|
|
45895
|
-
profileId: string().min(1).default("monitor"),
|
|
45896
|
-
profileLabel: string().min(1).default("BTM")
|
|
45897
|
-
});
|
|
45898
|
-
var relay = null;
|
|
45899
|
-
function installTerminalCameraRelay(next) {
|
|
45900
|
-
relay = next;
|
|
45901
|
-
}
|
|
45902
|
-
var TerminalCameraDevice = class extends BaseDevice {
|
|
45903
|
-
features = [DeviceFeature.NativeSnapshot];
|
|
45904
|
-
constructor(ctx) {
|
|
45905
|
-
super(ctx, terminalCameraSchema, { type: ctx.deviceMeta.type });
|
|
45906
|
-
this.ctx.registerNativeCap(streamCatalogCapability, { getCatalog: async ({ deviceId }) => {
|
|
45907
|
-
if (deviceId !== this.id) return [];
|
|
45908
|
-
return this.catalog();
|
|
45909
|
-
} });
|
|
45910
|
-
this.ctx.registerNativeCap(snapshotCapability, {
|
|
45911
|
-
getSnapshot: async ({ deviceId }) => {
|
|
45912
|
-
if (deviceId !== this.id) throw new Error(`TerminalCameraDevice: deviceId mismatch, expected ${String(this.id)}, got ${String(deviceId)}`);
|
|
45913
|
-
const activeRelay = relay;
|
|
45914
|
-
if (!activeRelay) throw new Error("terminal camera relay is unavailable");
|
|
45915
|
-
return {
|
|
45916
|
-
base64: (await activeRelay.snapshot(this.relayInstanceId(), this.config.get("nodeId"), this.config.get("profileId"))).toString("base64"),
|
|
45917
|
-
contentType: "image/jpeg"
|
|
45918
|
-
};
|
|
45919
|
-
},
|
|
45920
|
-
invalidateCache: async () => {}
|
|
45921
|
-
});
|
|
45922
|
-
this.markOnline(true);
|
|
45923
|
-
}
|
|
45924
|
-
async catalog() {
|
|
45925
|
-
const activeRelay = relay;
|
|
45926
|
-
if (!activeRelay) throw new Error("terminal camera relay is unavailable");
|
|
45927
|
-
const nodeId = this.config.get("nodeId");
|
|
45928
|
-
const profileId = this.config.get("profileId");
|
|
45929
|
-
const instanceId = this.relayInstanceId();
|
|
45930
|
-
return [{
|
|
45931
|
-
camStreamId: profileId,
|
|
45932
|
-
kind: "pull-http",
|
|
45933
|
-
url: activeRelay.streamUrl(instanceId, nodeId, profileId),
|
|
45934
|
-
codec: "h264",
|
|
45935
|
-
resolution: {
|
|
45936
|
-
width: 960,
|
|
45937
|
-
height: 640
|
|
45938
|
-
},
|
|
45939
|
-
fps: 2,
|
|
45940
|
-
label: this.config.get("profileLabel")
|
|
45941
|
-
}];
|
|
45942
|
-
}
|
|
45943
|
-
setNodeOnline(online) {
|
|
45944
|
-
this.markOnline(online);
|
|
45945
|
-
if (!online) relay?.closeInstance(this.relayInstanceId());
|
|
45946
|
-
}
|
|
45947
|
-
async removeDevice() {
|
|
45948
|
-
await relay?.closeInstance(this.relayInstanceId());
|
|
45949
|
-
}
|
|
45950
|
-
relayInstanceId() {
|
|
45951
|
-
return this.config.get("instanceId") ?? `legacy:${this.stableId}`;
|
|
45952
|
-
}
|
|
45953
|
-
};
|
|
45954
|
-
//#endregion
|
|
45955
46460
|
//#region src/terminal-camera-relay.ts
|
|
45956
46461
|
var MJPEG_BOUNDARY = "camstack-terminal-frame";
|
|
45957
46462
|
var SESSION_IDLE_MS = 3e4;
|
|
@@ -46502,13 +47007,34 @@ function newTerminalCameraStableId(instanceId) {
|
|
|
46502
47007
|
* Legacy automatic cameras are migration candidates only. A tombstone is
|
|
46503
47008
|
* durable deletion intent, so a lingering failed device removal must never
|
|
46504
47009
|
* make that camera adoptable again.
|
|
47010
|
+
*
|
|
47011
|
+
* ## `config` is load-bearing — this read can never be `projection: 'slim'`
|
|
47012
|
+
*
|
|
47013
|
+
* `nodeId`, `profileId` and `profileLabel` all live in the device's `config`,
|
|
47014
|
+
* and a row without `nodeId` is skipped. The slim projection returns
|
|
47015
|
+
* `config: {}` for every row, so a slim answer here is shape-identical to a
|
|
47016
|
+
* fleet that has no legacy cameras — the whole migration section disappears
|
|
47017
|
+
* and nothing says why. `legacy-camera-read-shape.spec.ts` is the arm on that.
|
|
47018
|
+
*
|
|
47019
|
+
* `onSkipped` is why the disappearance would now be visible: a row that LOOKS
|
|
47020
|
+
* like a legacy camera (`terminal-camera-*`, not an instance camera, not
|
|
47021
|
+
* tombstoned, not already adopted) but carries no `nodeId` is reported with
|
|
47022
|
+
* its numeric device id, so the caller can log it per-camera. Rows that are
|
|
47023
|
+
* not candidates at all are silent — a fleet of 1 017 devices must not
|
|
47024
|
+
* produce 1 017 lines to say none of them is a Terminal monitor.
|
|
46505
47025
|
*/
|
|
46506
|
-
function listLegacyTerminalCameraCandidates(rows, instanceStableIds, tombstones) {
|
|
47026
|
+
function listLegacyTerminalCameraCandidates(rows, instanceStableIds, tombstones, onSkipped) {
|
|
46507
47027
|
const legacy = [];
|
|
46508
47028
|
for (const row of rows) {
|
|
46509
47029
|
if (tombstones.has(row.stableId) || instanceStableIds.has(row.stableId) || row.stableId.startsWith("terminal-camera-instance-") || !row.stableId.startsWith("terminal-camera-")) continue;
|
|
46510
47030
|
const nodeId = typeof row.config.nodeId === "string" ? row.config.nodeId : null;
|
|
46511
|
-
if (!nodeId)
|
|
47031
|
+
if (!nodeId) {
|
|
47032
|
+
onSkipped?.({
|
|
47033
|
+
deviceId: row.id,
|
|
47034
|
+
stableId: row.stableId
|
|
47035
|
+
});
|
|
47036
|
+
continue;
|
|
47037
|
+
}
|
|
46512
47038
|
const profileId = typeof row.config.profileId === "string" ? row.config.profileId : "monitor";
|
|
46513
47039
|
const profileLabel = typeof row.config.profileLabel === "string" ? row.config.profileLabel : profileId === "monitor" ? "BTM" : profileId;
|
|
46514
47040
|
legacy.push({
|
|
@@ -46669,111 +47195,6 @@ function findProfile(profiles, profileId) {
|
|
|
46669
47195
|
return profiles.find((p) => p.profileId === profileId);
|
|
46670
47196
|
}
|
|
46671
47197
|
//#endregion
|
|
46672
|
-
//#region src/profile-settings.ts
|
|
46673
|
-
var GLANCES_PLUGINS = [
|
|
46674
|
-
{
|
|
46675
|
-
key: "showCpu",
|
|
46676
|
-
plugin: "cpu",
|
|
46677
|
-
label: "CPU"
|
|
46678
|
-
},
|
|
46679
|
-
{
|
|
46680
|
-
key: "showMem",
|
|
46681
|
-
plugin: "mem",
|
|
46682
|
-
label: "Memory"
|
|
46683
|
-
},
|
|
46684
|
-
{
|
|
46685
|
-
key: "showLoad",
|
|
46686
|
-
plugin: "load",
|
|
46687
|
-
label: "Load"
|
|
46688
|
-
},
|
|
46689
|
-
{
|
|
46690
|
-
key: "showNetwork",
|
|
46691
|
-
plugin: "network",
|
|
46692
|
-
label: "Network"
|
|
46693
|
-
},
|
|
46694
|
-
{
|
|
46695
|
-
key: "showDiskIo",
|
|
46696
|
-
plugin: "diskio",
|
|
46697
|
-
label: "Disk I/O"
|
|
46698
|
-
},
|
|
46699
|
-
{
|
|
46700
|
-
key: "showFs",
|
|
46701
|
-
plugin: "fs",
|
|
46702
|
-
label: "Filesystems"
|
|
46703
|
-
},
|
|
46704
|
-
{
|
|
46705
|
-
key: "showProcessList",
|
|
46706
|
-
plugin: "processlist",
|
|
46707
|
-
label: "Process list"
|
|
46708
|
-
},
|
|
46709
|
-
{
|
|
46710
|
-
key: "showContainers",
|
|
46711
|
-
plugin: "containers",
|
|
46712
|
-
label: "Containers"
|
|
46713
|
-
},
|
|
46714
|
-
{
|
|
46715
|
-
key: "showSensors",
|
|
46716
|
-
plugin: "sensors",
|
|
46717
|
-
label: "Sensors"
|
|
46718
|
-
}
|
|
46719
|
-
];
|
|
46720
|
-
function glancesBooleanField(key, label) {
|
|
46721
|
-
return {
|
|
46722
|
-
type: "boolean",
|
|
46723
|
-
key,
|
|
46724
|
-
label,
|
|
46725
|
-
default: true,
|
|
46726
|
-
style: "switch"
|
|
46727
|
-
};
|
|
46728
|
-
}
|
|
46729
|
-
function glancesSettingsSchema() {
|
|
46730
|
-
return { sections: [{
|
|
46731
|
-
id: "glances-panels",
|
|
46732
|
-
title: "Glances panels",
|
|
46733
|
-
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).",
|
|
46734
|
-
columns: 2,
|
|
46735
|
-
fields: GLANCES_PLUGINS.map((plugin) => glancesBooleanField(plugin.key, plugin.label))
|
|
46736
|
-
}] };
|
|
46737
|
-
}
|
|
46738
|
-
function settingsSchemaForProfile(profileId) {
|
|
46739
|
-
if (profileId === "glances") return glancesSettingsSchema();
|
|
46740
|
-
return null;
|
|
46741
|
-
}
|
|
46742
|
-
function glancesSettingsToArgs(settings) {
|
|
46743
|
-
const disabled = GLANCES_PLUGINS.filter((plugin) => settings[plugin.key] === false).map((plugin) => plugin.plugin);
|
|
46744
|
-
if (disabled.length === 0) return [];
|
|
46745
|
-
return ["--disable-plugin", disabled.join(",")];
|
|
46746
|
-
}
|
|
46747
|
-
function sanitizeProfileSettings(profileId, raw) {
|
|
46748
|
-
if (profileId !== "glances") return {};
|
|
46749
|
-
const bag = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
46750
|
-
const out = {
|
|
46751
|
-
showCpu: true,
|
|
46752
|
-
showMem: true,
|
|
46753
|
-
showLoad: true,
|
|
46754
|
-
showNetwork: true,
|
|
46755
|
-
showDiskIo: true,
|
|
46756
|
-
showFs: true,
|
|
46757
|
-
showProcessList: true,
|
|
46758
|
-
showContainers: true,
|
|
46759
|
-
showSensors: true
|
|
46760
|
-
};
|
|
46761
|
-
for (const plugin of GLANCES_PLUGINS) {
|
|
46762
|
-
const value = bag[plugin.key];
|
|
46763
|
-
if (typeof value === "boolean") out[plugin.key] = value;
|
|
46764
|
-
}
|
|
46765
|
-
return out;
|
|
46766
|
-
}
|
|
46767
|
-
function profileSettingsToArgs(profileId, settings) {
|
|
46768
|
-
if (profileId !== "glances") return [];
|
|
46769
|
-
return glancesSettingsToArgs(sanitizeProfileSettings(profileId, settings));
|
|
46770
|
-
}
|
|
46771
|
-
function spawnArgsForInstance(input) {
|
|
46772
|
-
const extra = profileSettingsToArgs(input.profileId, input.profileSettings);
|
|
46773
|
-
if (input.instanceArgs.length > 0) return [...input.instanceArgs, ...extra];
|
|
46774
|
-
if (extra.length > 0) return [...input.profileArgs, ...extra];
|
|
46775
|
-
}
|
|
46776
|
-
//#endregion
|
|
46777
47198
|
//#region src/terminal-session-manager.ts
|
|
46778
47199
|
var MIN_GRID = 1;
|
|
46779
47200
|
var MAX_COLS = 1e3;
|
|
@@ -47595,7 +48016,12 @@ var TerminalAddon = class extends BaseAddon {
|
|
|
47595
48016
|
async listLegacyTerminalCameras() {
|
|
47596
48017
|
const instances = this.terminalInstances();
|
|
47597
48018
|
const instanceStableIds = new Set(instances.map((instance) => instance.cameraStableId));
|
|
47598
|
-
return listLegacyTerminalCameraCandidates(await this.ctx.api.deviceManager.listAll.query({ addonId: this.ctx.id }), instanceStableIds, this.terminalCameraTombstones)
|
|
48019
|
+
return listLegacyTerminalCameraCandidates(await this.ctx.api.deviceManager.listAll.query({ addonId: this.ctx.id }), instanceStableIds, this.terminalCameraTombstones, ({ deviceId, stableId }) => {
|
|
48020
|
+
this.ctx.logger.warn("legacy Terminal camera skipped — its device config carries no nodeId, so it cannot be offered for adoption", {
|
|
48021
|
+
tags: { deviceId },
|
|
48022
|
+
meta: { stableId }
|
|
48023
|
+
});
|
|
48024
|
+
});
|
|
47599
48025
|
}
|
|
47600
48026
|
async adoptLegacyMonitor(stableId, requestedName) {
|
|
47601
48027
|
const instance = await this.instanceMutationQueue.run(() => this.adoptLegacyMonitorUnlocked(stableId, requestedName));
|