@camstack/addon-post-analysis 1.2.641 → 1.2.643
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/{clip-model-registry-Cw7Iy7Qi.mjs → clip-model-registry-DdgT3gdf.mjs} +301 -25
- package/dist/{clip-model-registry-Qr0XipkX.js → clip-model-registry-lKeBMKlT.js} +301 -25
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Cct0TMzC.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B6X8TAQP.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BIjXlrFH.mjs +26 -0
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CuNXxDlT.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js--XkysBjS.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-B9c2FowI.mjs → hostInit-CDPfOUWf.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +411 -15
- package/dist/pipeline-analytics/index.mjs +411 -15
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-_TUthUZh.mjs +0 -26
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_clip_model_registry = require("../clip-model-registry-
|
|
5
|
+
const require_clip_model_registry = require("../clip-model-registry-lKeBMKlT.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_clip_model_registry.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -757,6 +757,8 @@ var DEFAULT_ACTION_TTL_MS = 15 * 6e4;
|
|
|
757
757
|
* notification that is not fresh.
|
|
758
758
|
*/
|
|
759
759
|
var SNOOZE_ACTION_TTL_MS = 60 * 6e4;
|
|
760
|
+
/** How long a Correct / Wrong button stays live: one day. */
|
|
761
|
+
var VERDICT_ACTION_TTL_MS = 1440 * 6e4;
|
|
760
762
|
/**
|
|
761
763
|
* How long a DISARM button stays live: five minutes.
|
|
762
764
|
*
|
|
@@ -784,6 +786,7 @@ function actionTtlMs(subject) {
|
|
|
784
786
|
case "sequence": return DEFAULT_ACTION_TTL_MS;
|
|
785
787
|
case "addon-update": return DEFAULT_ACTION_TTL_MS;
|
|
786
788
|
case "server-update": return DEFAULT_ACTION_TTL_MS;
|
|
789
|
+
case "detection-verdict": return VERDICT_ACTION_TTL_MS;
|
|
787
790
|
}
|
|
788
791
|
}
|
|
789
792
|
var NcActionTokenRegistry = class {
|
|
@@ -827,6 +830,30 @@ var NcActionTokenRegistry = class {
|
|
|
827
830
|
grant: found.grant
|
|
828
831
|
};
|
|
829
832
|
}
|
|
833
|
+
/**
|
|
834
|
+
* Read the grant without spending it. ONLY the confirm page of a link-only
|
|
835
|
+
* channel may call this: it exists so a link preview that opens the URL
|
|
836
|
+
* cannot spend the token, and it never runs anything.
|
|
837
|
+
*/
|
|
838
|
+
peek(id) {
|
|
839
|
+
const found = this.grants.get(id);
|
|
840
|
+
if (found === void 0) return {
|
|
841
|
+
ok: false,
|
|
842
|
+
reason: "unknown"
|
|
843
|
+
};
|
|
844
|
+
if (found.used) return {
|
|
845
|
+
ok: false,
|
|
846
|
+
reason: "already-used"
|
|
847
|
+
};
|
|
848
|
+
if (found.grant.expiresAt <= this.now()) return {
|
|
849
|
+
ok: false,
|
|
850
|
+
reason: "expired"
|
|
851
|
+
};
|
|
852
|
+
return {
|
|
853
|
+
ok: true,
|
|
854
|
+
grant: found.grant
|
|
855
|
+
};
|
|
856
|
+
}
|
|
830
857
|
/** Drop records that can no longer be claimed. Memory hygiene only — an
|
|
831
858
|
* expired record is already refused by {@link claim}, so a sweep that never
|
|
832
859
|
* ran would cost bytes, never safety. */
|
|
@@ -868,6 +895,7 @@ function describeGrant(grant) {
|
|
|
868
895
|
return grant.action === "disarm" ? "Alarm disarmed" : `Alarm armed${grant.mode !== void 0 ? ` (${grant.mode})` : ""}`;
|
|
869
896
|
case "addon-update": return `${grant.packages.length} addon update(s) on ${grant.nodeId}`;
|
|
870
897
|
case "server-update": return `Server update on ${grant.nodeId}`;
|
|
898
|
+
case "detection-verdict": return grant.verdict === "correct" ? "Marked correct — kept for retraining" : grant.suggestedClass === void 0 ? "Marked wrong — kept for retraining" : `Marked wrong, it was a ${grant.suggestedClass} — kept for retraining`;
|
|
871
899
|
}
|
|
872
900
|
}
|
|
873
901
|
/** The audit line's `meta`, per family. A snooze that logged a `sequence` it
|
|
@@ -921,6 +949,15 @@ function grantMeta(grant) {
|
|
|
921
949
|
...common,
|
|
922
950
|
nodeId: grant.nodeId
|
|
923
951
|
};
|
|
952
|
+
case "detection-verdict": return {
|
|
953
|
+
...common,
|
|
954
|
+
ruleId: grant.ruleId,
|
|
955
|
+
rule: grant.ruleName,
|
|
956
|
+
userId: grant.userId,
|
|
957
|
+
trackId: grant.trackId,
|
|
958
|
+
verdict: grant.verdict,
|
|
959
|
+
suggestedClass: grant.suggestedClass ?? null
|
|
960
|
+
};
|
|
924
961
|
}
|
|
925
962
|
}
|
|
926
963
|
/** What the human standing in front of the page is told. */
|
|
@@ -929,6 +966,11 @@ var FAILURE_COPY = {
|
|
|
929
966
|
expired: "This button has expired. Open CamStack to do it there.",
|
|
930
967
|
"already-used": "Already done — this button had been used."
|
|
931
968
|
};
|
|
969
|
+
/** The question a link-only channel lands on, answered by a POST form. */
|
|
970
|
+
function confirmPage(url, grant) {
|
|
971
|
+
const action = `${url.pathname}?exp=${encodeURIComponent(url.searchParams.get("exp") ?? "")}&sig=${encodeURIComponent(url.searchParams.get("sig") ?? "")}&confirm=1`;
|
|
972
|
+
return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 1rem}button{font:inherit;padding:.6rem 1.4rem;border-radius:.5rem;border:0;background:#3b82f6;color:#fff}</style><main><h1>Confirm</h1><p>${grant.verdict === "correct" ? "Mark this detection as correct?" : grant.suggestedClass === void 0 ? "Mark this detection as wrong?" : `Mark this detection as wrong, it was a ${grant.suggestedClass}?`}</p><form method="post" action="${action}"><button type="submit">Confirm</button></form></main>`;
|
|
973
|
+
}
|
|
932
974
|
function page(title, detail) {
|
|
933
975
|
return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${title}</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 .5rem}p{margin:0;color:#aaa}</style><main><h1>${title}</h1><p>${detail}</p></main>`;
|
|
934
976
|
}
|
|
@@ -942,7 +984,8 @@ var NcActionPlane = class {
|
|
|
942
984
|
handler = async (req, res) => {
|
|
943
985
|
const url = new URL(req.url ?? "/", "http://placeholder");
|
|
944
986
|
const id = decodeURIComponent(url.pathname.split("/").filter(Boolean).pop() ?? "");
|
|
945
|
-
const
|
|
987
|
+
const posted = (req.method ?? "GET").toUpperCase() === "POST";
|
|
988
|
+
const background = posted && url.searchParams.get("confirm") !== "1";
|
|
946
989
|
if (!verifyActionSignature({
|
|
947
990
|
secret: this.deps.secret,
|
|
948
991
|
id,
|
|
@@ -954,6 +997,13 @@ var NcActionPlane = class {
|
|
|
954
997
|
this.reply(res, background, 404, "Not found", "This link is not valid.");
|
|
955
998
|
return;
|
|
956
999
|
}
|
|
1000
|
+
if (!posted) {
|
|
1001
|
+
const peeked = this.deps.registry.peek(id);
|
|
1002
|
+
if (peeked.ok && peeked.grant.kind === "detection-verdict" && peeked.grant.viaLink === true) {
|
|
1003
|
+
this.sendBody(res, 200, "text/html; charset=utf-8", confirmPage(url, peeked.grant));
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
957
1007
|
const claim = this.deps.registry.claim(id);
|
|
958
1008
|
if (!claim.ok) {
|
|
959
1009
|
this.deps.logger.info("nc action callback not claimable", { meta: {
|
|
@@ -1099,6 +1149,7 @@ var ncDurableRecordKinds = require_clip_model_registry.defineRecordKinds({
|
|
|
1099
1149
|
"state-summary-stamps": ledger("digest delivery stamps per rule kind"),
|
|
1100
1150
|
meta: ledger("outbox boot-reconcile watermark and other single-key meta rows", "settings", { none: "one fixed key (watermark), overwritten in place, never deleted" }),
|
|
1101
1151
|
snoozes: ledger("operator opens or closes a snooze window (write-through)"),
|
|
1152
|
+
"detection-verdicts": ledger("operator taps Correct or Wrong on a detection notification; one row per track"),
|
|
1102
1153
|
"snooze-suppressed": {
|
|
1103
1154
|
...QUERIED,
|
|
1104
1155
|
class: "ledger",
|
|
@@ -9234,6 +9285,9 @@ var en_default = {
|
|
|
9234
9285
|
"action.addon-update.uptodate": "Already up to date on {{nodeId}} — nothing to do",
|
|
9235
9286
|
"action.addon-update.unknown": "Could not read what needs updating on {{nodeId}} — nothing was changed. Open CamStack.",
|
|
9236
9287
|
"button.server-update.apply": "Update server on {{nodeId}}",
|
|
9288
|
+
"button.verdict.correct": "Correct",
|
|
9289
|
+
"button.verdict.wrong": "Wrong",
|
|
9290
|
+
"button.verdict.wrongAs": "Wrong: it is {{class}}",
|
|
9237
9291
|
"action.server-update.title": "Server update",
|
|
9238
9292
|
"action.server-update.started": "Updating server on {{nodeId}}…",
|
|
9239
9293
|
"action.server-update.restarting": "Server update staged on {{nodeId}} — the node is restarting. You will hear when it comes back.",
|
|
@@ -9635,6 +9689,9 @@ var it_default = {
|
|
|
9635
9689
|
"action.addon-update.uptodate": "Già aggiornato su {{nodeId}} — niente da fare",
|
|
9636
9690
|
"action.addon-update.unknown": "Non è stato possibile leggere cosa aggiornare su {{nodeId}} — non è stato cambiato nulla. Apri CamStack.",
|
|
9637
9691
|
"button.server-update.apply": "Aggiorna server su {{nodeId}}",
|
|
9692
|
+
"button.verdict.correct": "Corretto",
|
|
9693
|
+
"button.verdict.wrong": "Sbagliato",
|
|
9694
|
+
"button.verdict.wrongAs": "Sbagliato: è {{class}}",
|
|
9638
9695
|
"action.server-update.title": "Aggiornamento server",
|
|
9639
9696
|
"action.server-update.started": "Aggiornamento server su {{nodeId}}…",
|
|
9640
9697
|
"action.server-update.restarting": "Aggiornamento server predisposto su {{nodeId}} — il nodo si riavvia. Ti avviseremo quando torna.",
|
|
@@ -53130,6 +53187,15 @@ var SESSION_STILLNESS_MIN_POINTS = 4;
|
|
|
53130
53187
|
* long (object removed while unobserved, or a long occlusion) → retire it. */
|
|
53131
53188
|
var ENTRY_TTL_MS = 5 * 6e4;
|
|
53132
53189
|
/**
|
|
53190
|
+
* The most one gap between two consecutive processed frames counts toward an
|
|
53191
|
+
* entry's WATCHED unconfirmed time. Frames inside a motion session arrive every
|
|
53192
|
+
* 0.2-2 s; the silence between on-motion sessions is tens of seconds to hours.
|
|
53193
|
+
* 10 s sits above any real in-session cadence (a slower frame still counts
|
|
53194
|
+
* nearly in full) and far below a session gap, so a gap contributes at most
|
|
53195
|
+
* one bound, never its length. Under-counting only delays a retirement.
|
|
53196
|
+
*/
|
|
53197
|
+
var WATCHED_GAP_CAP_MS = 1e4;
|
|
53198
|
+
/**
|
|
53133
53199
|
* How far apart the two reads of a departure must be before the second one
|
|
53134
53200
|
* counts as CONFIRMING the first.
|
|
53135
53201
|
*
|
|
@@ -59797,6 +59863,25 @@ var TrackAlreadyTrainedError = class extends Error {
|
|
|
59797
59863
|
}
|
|
59798
59864
|
};
|
|
59799
59865
|
/**
|
|
59866
|
+
* Classify a failed `setTrackFlags` as one of the lifecycle's DELIBERATE
|
|
59867
|
+
* refusals, or `null` when it is a failure of the call itself.
|
|
59868
|
+
*
|
|
59869
|
+
* Needed because the caller that matters (a notification button) reaches the
|
|
59870
|
+
* method over `ctx.api`, where the error class does not survive the hop and
|
|
59871
|
+
* only the message does. The class check comes first; the message check is the
|
|
59872
|
+
* transported form, and `track-flags.spec.ts` pins it against the real
|
|
59873
|
+
* messages so a reworded refusal cannot silently turn into a generic failure.
|
|
59874
|
+
*/
|
|
59875
|
+
function retrainRefusalReason(err) {
|
|
59876
|
+
if (err instanceof TrackAlreadyTrainedError) return "trained";
|
|
59877
|
+
if (err instanceof StagingBudgetExceededError) return "budget";
|
|
59878
|
+
const message = err instanceof Error ? err.message : "";
|
|
59879
|
+
if (message.includes("is already trained")) return "trained";
|
|
59880
|
+
if (message.includes("in retrain staging")) return "budget";
|
|
59881
|
+
if (message.includes("unknown track")) return "unknown-track";
|
|
59882
|
+
return null;
|
|
59883
|
+
}
|
|
59884
|
+
/**
|
|
59800
59885
|
* The `debugNote` half of the patch — `{}` when nothing should be written.
|
|
59801
59886
|
*
|
|
59802
59887
|
* The note is not an independent column: it is the operator's sentence about
|
|
@@ -62360,6 +62445,19 @@ var StationaryObjectRegistry = class {
|
|
|
62360
62445
|
* so quiet minutes must not age the entries (see {@link sweep}). */
|
|
62361
62446
|
lastFrameAtByDevice = /* @__PURE__ */ new Map();
|
|
62362
62447
|
/**
|
|
62448
|
+
* WATCHED time each entry has gone unconfirmed, in ms. Entry expiry is judged
|
|
62449
|
+
* on this, never on the span between two frame timestamps: a session-dispatch
|
|
62450
|
+
* camera's silence between motion sessions would otherwise be counted in full
|
|
62451
|
+
* the moment the next session's first frame arrives (measured: a car parked
|
|
62452
|
+
* 3.3 h was retired after seconds of watching, 316 s after its last
|
|
62453
|
+
* confirmation). Each frame adds its delta from the previous one, capped at
|
|
62454
|
+
* {@link WATCHED_GAP_CAP_MS}; a confirmation resets it. `confirmedAt` is the
|
|
62455
|
+
* `lastConfirmedAt` the counter was last reset for, so a changed value (any of
|
|
62456
|
+
* the places that confirm an entry) restarts the count without a hook at each.
|
|
62457
|
+
* An entry that was just hydrated has no record here and starts at zero.
|
|
62458
|
+
*/
|
|
62459
|
+
unconfirmedWatched = /* @__PURE__ */ new Map();
|
|
62460
|
+
/**
|
|
62363
62461
|
* Entries carrying ONE unconfirmed departure read, per device (D49, D174,
|
|
62364
62462
|
* D279).
|
|
62365
62463
|
*
|
|
@@ -62473,7 +62571,27 @@ var StationaryObjectRegistry = class {
|
|
|
62473
62571
|
/** Record that a frame was processed for a device — advances the OBSERVED
|
|
62474
62572
|
* clock that drives entry expiry in {@link sweep}. */
|
|
62475
62573
|
noteFrame(deviceId, timestamp) {
|
|
62476
|
-
|
|
62574
|
+
const known = this.lastFrameAtByDevice.get(deviceId);
|
|
62575
|
+
if (timestamp <= (known ?? 0)) return;
|
|
62576
|
+
this.lastFrameAtByDevice.set(deviceId, timestamp);
|
|
62577
|
+
const ids = this.idsByDevice.get(deviceId);
|
|
62578
|
+
if (!ids) return;
|
|
62579
|
+
const delta = known === void 0 ? 0 : Math.min(timestamp - known, WATCHED_GAP_CAP_MS);
|
|
62580
|
+
for (const id of ids) {
|
|
62581
|
+
const e = this.ledger.get(id);
|
|
62582
|
+
if (!e) continue;
|
|
62583
|
+
const rec = this.unconfirmedWatched.get(id);
|
|
62584
|
+
if (!rec || rec.confirmedAt !== e.lastConfirmedAt) this.unconfirmedWatched.set(id, {
|
|
62585
|
+
confirmedAt: e.lastConfirmedAt,
|
|
62586
|
+
ms: 0
|
|
62587
|
+
});
|
|
62588
|
+
else rec.ms += delta;
|
|
62589
|
+
}
|
|
62590
|
+
}
|
|
62591
|
+
/** Watched ms an entry has gone unconfirmed (0 when not yet tracked). */
|
|
62592
|
+
watchedUnconfirmedMs(e) {
|
|
62593
|
+
const rec = this.unconfirmedWatched.get(e.id);
|
|
62594
|
+
return rec && rec.confirmedAt === e.lastConfirmedAt ? rec.ms : 0;
|
|
62477
62595
|
}
|
|
62478
62596
|
/**
|
|
62479
62597
|
* Per-frame gate: partition this frame's detections against the device's
|
|
@@ -62718,12 +62836,14 @@ var StationaryObjectRegistry = class {
|
|
|
62718
62836
|
* logged by `MacMiniAgent.localdomain` and by `agent-a`, the same
|
|
62719
62837
|
* rows, seven seconds apart.
|
|
62720
62838
|
*
|
|
62721
|
-
* Expiry is measured
|
|
62722
|
-
*
|
|
62723
|
-
*
|
|
62724
|
-
*
|
|
62839
|
+
* Expiry is measured in WATCHED time ({@link noteFrame}): the sum of the
|
|
62840
|
+
* gaps between consecutive processed frames, each capped at
|
|
62841
|
+
* {@link WATCHED_GAP_CAP_MS}, since the entry's last confirmation. Neither the
|
|
62842
|
+
* wall clock nor the span between frame timestamps: a session-dispatch
|
|
62843
|
+
* camera emits no frames between motion sessions, and a silent gap must not
|
|
62844
|
+
* age the object. Only when the camera has actually been WATCHING for `ttl`
|
|
62725
62845
|
* beyond the last confirmation (frames flowed, object never matched) is the
|
|
62726
|
-
* object considered removed. A device with no recorded frame yet never
|
|
62846
|
+
* object considered removed. A freshly hydrated entry starts at zero. A device with no recorded frame yet never
|
|
62727
62847
|
* expires its entries. `now` only stamps the departed telemetry.
|
|
62728
62848
|
*/
|
|
62729
62849
|
async sweep(now) {
|
|
@@ -62742,9 +62862,10 @@ var StationaryObjectRegistry = class {
|
|
|
62742
62862
|
ids.delete(id);
|
|
62743
62863
|
continue;
|
|
62744
62864
|
}
|
|
62745
|
-
const
|
|
62865
|
+
const watchedMs = this.watchedUnconfirmedMs(e);
|
|
62866
|
+
const overdue = watchedMs > ttl;
|
|
62746
62867
|
const proven = this.detectorProvedItself(e);
|
|
62747
|
-
const unprovenBackstop =
|
|
62868
|
+
const unprovenBackstop = watchedMs > ttl * 12;
|
|
62748
62869
|
if (overdue && !proven && !unprovenBackstop) {
|
|
62749
62870
|
this.bumpStrikeCounters(deviceId, { ttlHeldUnproven: 1 });
|
|
62750
62871
|
continue;
|
|
@@ -62760,7 +62881,8 @@ var StationaryObjectRegistry = class {
|
|
|
62760
62881
|
className: e.className,
|
|
62761
62882
|
reason: proven ? "ttl" : "ttl-unproven",
|
|
62762
62883
|
ttlMs: ttl,
|
|
62763
|
-
unconfirmedForMs:
|
|
62884
|
+
unconfirmedForMs: watchedMs,
|
|
62885
|
+
wallGapMs: observedAt - e.lastConfirmedAt,
|
|
62764
62886
|
parkedForMs: observedAt - e.becameStationaryAt
|
|
62765
62887
|
}
|
|
62766
62888
|
});
|
|
@@ -62844,6 +62966,7 @@ var StationaryObjectRegistry = class {
|
|
|
62844
62966
|
for (const id of ids) {
|
|
62845
62967
|
this.dirty.delete(id);
|
|
62846
62968
|
this.weakRuns.delete(id);
|
|
62969
|
+
this.unconfirmedWatched.delete(id);
|
|
62847
62970
|
this.ledger.evict(id);
|
|
62848
62971
|
}
|
|
62849
62972
|
this.idsByDevice.delete(deviceId);
|
|
@@ -63031,6 +63154,7 @@ var StationaryObjectRegistry = class {
|
|
|
63031
63154
|
return at !== void 0 && at > entry.lastConfirmedAt;
|
|
63032
63155
|
}
|
|
63033
63156
|
dropEntry(deviceId, id) {
|
|
63157
|
+
this.unconfirmedWatched.delete(id);
|
|
63034
63158
|
const dropped = this.ledger.get(id);
|
|
63035
63159
|
this.idsByDevice.get(deviceId)?.delete(id);
|
|
63036
63160
|
this.dirty.delete(id);
|
|
@@ -81938,6 +82062,10 @@ var NC_DEVICE_ROW_KINDS = [
|
|
|
81938
82062
|
{
|
|
81939
82063
|
key: "nc-artifacts",
|
|
81940
82064
|
kind: ncDurableRecordKinds.artifacts
|
|
82065
|
+
},
|
|
82066
|
+
{
|
|
82067
|
+
key: "nc-detection-verdicts",
|
|
82068
|
+
kind: ncDurableRecordKinds["detection-verdicts"]
|
|
81941
82069
|
}
|
|
81942
82070
|
];
|
|
81943
82071
|
/**
|
|
@@ -82357,6 +82485,82 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82357
82485
|
}
|
|
82358
82486
|
};
|
|
82359
82487
|
//#endregion
|
|
82488
|
+
//#region src/notification-center/detection-verdict-ledger.ts
|
|
82489
|
+
/**
|
|
82490
|
+
* @durable class=ledger owner=notification-center
|
|
82491
|
+
* write="the operator taps Correct / Wrong on a detection notification; one row per track, the latest answer replaces the earlier one"
|
|
82492
|
+
* retention="none — bounded by the tracks a rule asked about; deleted with the camera by the device-removal hook"
|
|
82493
|
+
*/
|
|
82494
|
+
var NC_DETECTION_VERDICTS_COLLECTION = "notification-center:detection-verdicts";
|
|
82495
|
+
var NC_DETECTION_VERDICTS_COLUMNS = [
|
|
82496
|
+
{
|
|
82497
|
+
name: "id",
|
|
82498
|
+
type: "TEXT",
|
|
82499
|
+
primaryKey: true,
|
|
82500
|
+
notNull: true
|
|
82501
|
+
},
|
|
82502
|
+
{
|
|
82503
|
+
name: "ruleId",
|
|
82504
|
+
type: "TEXT",
|
|
82505
|
+
notNull: true
|
|
82506
|
+
},
|
|
82507
|
+
{
|
|
82508
|
+
name: "at",
|
|
82509
|
+
type: "INTEGER",
|
|
82510
|
+
notNull: true
|
|
82511
|
+
},
|
|
82512
|
+
(
|
|
82513
|
+
/** The FULL row (Zod-validated on read). */
|
|
82514
|
+
{
|
|
82515
|
+
name: "row",
|
|
82516
|
+
type: "JSON",
|
|
82517
|
+
notNull: true
|
|
82518
|
+
})
|
|
82519
|
+
];
|
|
82520
|
+
var NcDetectionVerdictRecordSchema = require_clip_model_registry.object({
|
|
82521
|
+
trackId: require_clip_model_registry.string().min(1),
|
|
82522
|
+
deviceId: require_clip_model_registry.number().int(),
|
|
82523
|
+
verdict: require_clip_model_registry._enum(["correct", "wrong"]),
|
|
82524
|
+
/** The class the operator said it really was. Only with `wrong`. */
|
|
82525
|
+
suggestedClass: require_clip_model_registry.string().min(1).optional(),
|
|
82526
|
+
/** Whose notification it was (the token's frozen owner), NOT who tapped. */
|
|
82527
|
+
userId: require_clip_model_registry.string().min(1),
|
|
82528
|
+
ruleId: require_clip_model_registry.string().min(1),
|
|
82529
|
+
ruleName: require_clip_model_registry.string(),
|
|
82530
|
+
at: require_clip_model_registry.number()
|
|
82531
|
+
});
|
|
82532
|
+
var NC_DETECTION_VERDICTS_SPEC = {
|
|
82533
|
+
collection: NC_DETECTION_VERDICTS_COLLECTION,
|
|
82534
|
+
columns: [...NC_DETECTION_VERDICTS_COLUMNS],
|
|
82535
|
+
writeMode: "write-through",
|
|
82536
|
+
keyOf: (row) => row.trackId,
|
|
82537
|
+
toValue: (row) => ({
|
|
82538
|
+
ruleId: row.ruleId,
|
|
82539
|
+
at: row.at,
|
|
82540
|
+
row
|
|
82541
|
+
}),
|
|
82542
|
+
fromRecord: (_key, data) => {
|
|
82543
|
+
const parsed = NcDetectionVerdictRecordSchema.safeParse(data["row"]);
|
|
82544
|
+
return parsed.success ? parsed.data : null;
|
|
82545
|
+
},
|
|
82546
|
+
deviceIdOf: (row) => row.deviceId,
|
|
82547
|
+
loadLimit: 1e4
|
|
82548
|
+
};
|
|
82549
|
+
var NcDetectionVerdictLedger = class {
|
|
82550
|
+
ledger;
|
|
82551
|
+
constructor(deps) {
|
|
82552
|
+
this.ledger = new DurableLedger({
|
|
82553
|
+
spec: NC_DETECTION_VERDICTS_SPEC,
|
|
82554
|
+
kind: deps.records.kind(ncDurableRecordKinds["detection-verdicts"]),
|
|
82555
|
+
logger: deps.logger
|
|
82556
|
+
});
|
|
82557
|
+
}
|
|
82558
|
+
/** Write-through; throws on a failed write so the tap reports it. */
|
|
82559
|
+
async record(row) {
|
|
82560
|
+
await this.ledger.put(NcDetectionVerdictRecordSchema.parse(row));
|
|
82561
|
+
}
|
|
82562
|
+
};
|
|
82563
|
+
//#endregion
|
|
82360
82564
|
//#region src/notification-center/device-prune.ts
|
|
82361
82565
|
function judgeList(list, isDead) {
|
|
82362
82566
|
const dead = list.filter(isDead);
|
|
@@ -83311,6 +83515,64 @@ var NcDeviceStateCache = class {
|
|
|
83311
83515
|
}
|
|
83312
83516
|
};
|
|
83313
83517
|
//#endregion
|
|
83518
|
+
//#region src/notification-center/detection-verdict-buttons.ts
|
|
83519
|
+
var VERDICT_CORRECT_ID = "detection-verdict-correct";
|
|
83520
|
+
var VERDICT_WRONG_ID = "detection-verdict-wrong";
|
|
83521
|
+
/** `detection-verdict-wrong:<class>` — Wrong, and what it really was. */
|
|
83522
|
+
var VERDICT_WRONG_AS_PREFIX = "detection-verdict-wrong:";
|
|
83523
|
+
/** The macro classes offered as "it was really…". Short on purpose. */
|
|
83524
|
+
var VERDICT_CLASS_CHOICES = [
|
|
83525
|
+
"person",
|
|
83526
|
+
"vehicle",
|
|
83527
|
+
"animal"
|
|
83528
|
+
];
|
|
83529
|
+
/** At most this many class buttons, and only when the channel has room. */
|
|
83530
|
+
var VERDICT_MAX_CLASS_CHOICES = 2;
|
|
83531
|
+
function verdictButtonRefusal(subject) {
|
|
83532
|
+
if (!subject.askVerdict) return "rule-off";
|
|
83533
|
+
if (subject.trackId === void 0) return "no-track";
|
|
83534
|
+
if (subject.ownerUserId === void 0) return "unowned-target";
|
|
83535
|
+
return null;
|
|
83536
|
+
}
|
|
83537
|
+
/** The classes worth offering for a subject of `className`. */
|
|
83538
|
+
function verdictClassChoices(className) {
|
|
83539
|
+
return VERDICT_CLASS_CHOICES.filter((c) => c !== className).slice(0, VERDICT_MAX_CLASS_CHOICES);
|
|
83540
|
+
}
|
|
83541
|
+
/**
|
|
83542
|
+
* Which buttons a channel with `maxActions` slots gets. `maxActions` absent
|
|
83543
|
+
* (the kind catalog did not answer) is treated as the common floor of three:
|
|
83544
|
+
* the two plain choices, never the class ones. The class choices appear only
|
|
83545
|
+
* when the channel declared room for a fourth button.
|
|
83546
|
+
*/
|
|
83547
|
+
function verdictButtonSpecs(input) {
|
|
83548
|
+
const base = [{
|
|
83549
|
+
actionId: VERDICT_CORRECT_ID,
|
|
83550
|
+
verdict: "correct"
|
|
83551
|
+
}, {
|
|
83552
|
+
actionId: VERDICT_WRONG_ID,
|
|
83553
|
+
verdict: "wrong"
|
|
83554
|
+
}];
|
|
83555
|
+
if (input.maxActions === void 0 || input.maxActions <= 3) return base;
|
|
83556
|
+
const room = input.maxActions - base.length;
|
|
83557
|
+
const extra = verdictClassChoices(input.className).slice(0, room).map((suggestedClass) => ({
|
|
83558
|
+
actionId: `${VERDICT_WRONG_AS_PREFIX}${suggestedClass}`,
|
|
83559
|
+
verdict: "wrong",
|
|
83560
|
+
suggestedClass
|
|
83561
|
+
}));
|
|
83562
|
+
return [...base, ...extra];
|
|
83563
|
+
}
|
|
83564
|
+
/** Is this one of our buttons (so a link-only channel can lift it into the body)? */
|
|
83565
|
+
function isVerdictAction(action) {
|
|
83566
|
+
return action.id === VERDICT_CORRECT_ID || action.id.startsWith(VERDICT_WRONG_ID);
|
|
83567
|
+
}
|
|
83568
|
+
/**
|
|
83569
|
+
* The body lines that carry the verdict choices on a channel with no buttons.
|
|
83570
|
+
* A bare URL per line: every chat and mail client linkifies it.
|
|
83571
|
+
*/
|
|
83572
|
+
function verdictLinkLines(actions) {
|
|
83573
|
+
return actions.filter(isVerdictAction).map((a) => `${a.label}: ${a.url ?? ""}`).join("\n");
|
|
83574
|
+
}
|
|
83575
|
+
//#endregion
|
|
83314
83576
|
//#region src/notification-center/state-summary/state-model.ts
|
|
83315
83577
|
/**
|
|
83316
83578
|
* The STATE SUMMARY's vocabulary (D647): its sections, and what one reported
|
|
@@ -87019,6 +87281,8 @@ var NcDispatcher = class {
|
|
|
87019
87281
|
targetId: target.id,
|
|
87020
87282
|
...target.ownerUserId !== void 0 ? { ownerUserId: target.ownerUserId } : {},
|
|
87021
87283
|
...entry.payload.subject.className !== void 0 ? { className: entry.payload.subject.className } : {},
|
|
87284
|
+
...entry.payload.subject.trackId !== void 0 ? { trackId: entry.payload.subject.trackId } : {},
|
|
87285
|
+
...target.maxActions !== void 0 ? { maxActions: target.maxActions } : {},
|
|
87022
87286
|
...entry.payload.subject.systemEvent !== void 0 ? { systemEvent: entry.payload.subject.systemEvent } : {}
|
|
87023
87287
|
});
|
|
87024
87288
|
} catch (err) {
|
|
@@ -87114,9 +87378,13 @@ var NcDispatcher = class {
|
|
|
87114
87378
|
...summarySectionOf(systemEvent.data)
|
|
87115
87379
|
} } : {}
|
|
87116
87380
|
}) : null;
|
|
87117
|
-
const
|
|
87381
|
+
const minted = await this.mintActions(entry, target);
|
|
87382
|
+
const linkOnly = target.maxActions === 0;
|
|
87383
|
+
const verdictLinks = linkOnly ? verdictLinkLines(minted) : "";
|
|
87384
|
+
const actions = linkOnly ? minted.filter((a) => !isVerdictAction(a)) : minted;
|
|
87385
|
+
const withLinks = (text) => verdictLinks === "" ? text : `${text}\n\n${verdictLinks}`;
|
|
87118
87386
|
const notification = {
|
|
87119
|
-
body,
|
|
87387
|
+
body: withLinks(body),
|
|
87120
87388
|
title,
|
|
87121
87389
|
priority: clampPriority(paramNumber(entry.payload.params, "priority") ?? entry.payload.priority),
|
|
87122
87390
|
...params,
|
|
@@ -87138,7 +87406,7 @@ var NcDispatcher = class {
|
|
|
87138
87406
|
grouped: resolved.grouped,
|
|
87139
87407
|
withAiText: (aiText) => ({
|
|
87140
87408
|
...notification,
|
|
87141
|
-
body: renderBody(aiText)
|
|
87409
|
+
body: withLinks(renderBody(aiText))
|
|
87142
87410
|
})
|
|
87143
87411
|
};
|
|
87144
87412
|
}
|
|
@@ -99751,6 +100019,55 @@ async function mintButtons(host, input) {
|
|
|
99751
100019
|
const announcement = stored === null && input.ruleId === NC_ALARM_ANNOUNCE_RULE_ID;
|
|
99752
100020
|
if (stored === null && !announcement) return [];
|
|
99753
100021
|
const drafts = [];
|
|
100022
|
+
const verdictRefusal = verdictButtonRefusal({
|
|
100023
|
+
askVerdict: stored?.askVerdict === true,
|
|
100024
|
+
...input.trackId !== void 0 ? { trackId: input.trackId } : {},
|
|
100025
|
+
...input.ownerUserId !== void 0 ? { ownerUserId: input.ownerUserId } : {}
|
|
100026
|
+
});
|
|
100027
|
+
if (stored?.askVerdict === true && verdictRefusal !== null) host.logger.info("detection notification carries no verdict buttons", {
|
|
100028
|
+
tags: { deviceId: input.deviceId },
|
|
100029
|
+
meta: {
|
|
100030
|
+
ruleId: input.ruleId,
|
|
100031
|
+
reason: verdictRefusal,
|
|
100032
|
+
targetId: input.targetId
|
|
100033
|
+
}
|
|
100034
|
+
});
|
|
100035
|
+
const verdictTrackId = input.trackId;
|
|
100036
|
+
const verdictOwner = input.ownerUserId;
|
|
100037
|
+
const verdictButtons = verdictRefusal !== null || verdictTrackId === void 0 || verdictOwner === void 0 ? [] : verdictButtonSpecs({
|
|
100038
|
+
className: input.className,
|
|
100039
|
+
maxActions: input.maxActions
|
|
100040
|
+
}).map((spec) => {
|
|
100041
|
+
const id = (0, node_crypto.randomUUID)();
|
|
100042
|
+
drafts.push({
|
|
100043
|
+
id,
|
|
100044
|
+
ttlMs: actionTtlMs({ kind: "detection-verdict" }),
|
|
100045
|
+
grant: (expiresAt) => ({
|
|
100046
|
+
kind: "detection-verdict",
|
|
100047
|
+
id,
|
|
100048
|
+
verdict: spec.verdict,
|
|
100049
|
+
trackId: verdictTrackId,
|
|
100050
|
+
ruleId: input.ruleId,
|
|
100051
|
+
ruleName: input.ruleName,
|
|
100052
|
+
userId: verdictOwner,
|
|
100053
|
+
...spec.suggestedClass !== void 0 ? { suggestedClass: spec.suggestedClass } : {},
|
|
100054
|
+
...input.maxActions === 0 ? { viaLink: true } : {},
|
|
100055
|
+
deviceId: input.deviceId,
|
|
100056
|
+
actionId: spec.actionId,
|
|
100057
|
+
targetId: input.targetId,
|
|
100058
|
+
expiresAt
|
|
100059
|
+
})
|
|
100060
|
+
});
|
|
100061
|
+
return {
|
|
100062
|
+
id: spec.actionId,
|
|
100063
|
+
label: spec.suggestedClass === void 0 ? host.texts.text({ key: spec.verdict === "correct" ? "button.verdict.correct" : "button.verdict.wrong" }) : host.texts.text({
|
|
100064
|
+
key: "button.verdict.wrongAs",
|
|
100065
|
+
vars: { class: spec.suggestedClass }
|
|
100066
|
+
}),
|
|
100067
|
+
url: id,
|
|
100068
|
+
mode: "background"
|
|
100069
|
+
};
|
|
100070
|
+
});
|
|
99754
100071
|
const sequenceButtons = host.actionRunner === null || stored === null ? [] : buildActionButtons({
|
|
99755
100072
|
actions: stored.actions,
|
|
99756
100073
|
knownSequenceIds: host.sequences.knownIds(),
|
|
@@ -99963,6 +100280,7 @@ async function mintButtons(host, input) {
|
|
|
99963
100280
|
}
|
|
99964
100281
|
});
|
|
99965
100282
|
const out = [
|
|
100283
|
+
...verdictButtons,
|
|
99966
100284
|
...sequenceButtons,
|
|
99967
100285
|
...snoozeButtons,
|
|
99968
100286
|
...alarmButtons,
|
|
@@ -100186,7 +100504,52 @@ async function runGrantedAction(host, grant) {
|
|
|
100186
100504
|
case "sequence": return host.runGrantedSequence(grant);
|
|
100187
100505
|
case "addon-update": return host.applyGrantedAddonUpdate(grant);
|
|
100188
100506
|
case "server-update": return host.applyGrantedServerUpdate(grant);
|
|
100507
|
+
case "detection-verdict": return host.applyGrantedDetectionVerdict(grant);
|
|
100508
|
+
}
|
|
100509
|
+
}
|
|
100510
|
+
/** What a person is told when the retrain lifecycle refuses the mark. */
|
|
100511
|
+
var VERDICT_REFUSAL_COPY = {
|
|
100512
|
+
trained: "This track was already used for training — nothing was changed",
|
|
100513
|
+
budget: "This camera already holds as many tracks for retraining as it may — nothing was changed",
|
|
100514
|
+
"unknown-track": "This track is no longer available — nothing was changed"
|
|
100515
|
+
};
|
|
100516
|
+
/**
|
|
100517
|
+
* Mark the track one notification reported for retraining, and record who was
|
|
100518
|
+
* asked and when.
|
|
100519
|
+
*
|
|
100520
|
+
* Order matters. The MARK goes first and is the authority: a refusal ("already
|
|
100521
|
+
* trained", "budget full") means the operator's answer changed nothing, so no
|
|
100522
|
+
* ledger row is written for an answer that was not applied. The token is
|
|
100523
|
+
* already spent (D47) — the refusal is shown as the reason, not a retry.
|
|
100524
|
+
*/
|
|
100525
|
+
async function applyGrantedDetectionVerdict(host, grant) {
|
|
100526
|
+
const mark = host.deps.markTrackForRetrain;
|
|
100527
|
+
if (mark === void 0) throw new Error("no retrain mark is wired on this node");
|
|
100528
|
+
const outcome = await mark({
|
|
100529
|
+
deviceId: grant.deviceId,
|
|
100530
|
+
trackId: grant.trackId
|
|
100531
|
+
});
|
|
100532
|
+
if (!outcome.ok) {
|
|
100533
|
+
host.logger.info("detection verdict changed nothing — the retrain flow refused the mark", {
|
|
100534
|
+
tags: { deviceId: grant.deviceId },
|
|
100535
|
+
meta: {
|
|
100536
|
+
trackId: grant.trackId,
|
|
100537
|
+
reason: outcome.reason,
|
|
100538
|
+
verdict: grant.verdict
|
|
100539
|
+
}
|
|
100540
|
+
});
|
|
100541
|
+
throw new NcActionRefusedError(VERDICT_REFUSAL_COPY[outcome.reason]);
|
|
100189
100542
|
}
|
|
100543
|
+
await host.verdicts.record({
|
|
100544
|
+
trackId: grant.trackId,
|
|
100545
|
+
deviceId: grant.deviceId,
|
|
100546
|
+
verdict: grant.verdict,
|
|
100547
|
+
...grant.suggestedClass !== void 0 ? { suggestedClass: grant.suggestedClass } : {},
|
|
100548
|
+
userId: grant.userId,
|
|
100549
|
+
ruleId: grant.ruleId,
|
|
100550
|
+
ruleName: grant.ruleName,
|
|
100551
|
+
at: host.now()
|
|
100552
|
+
});
|
|
100190
100553
|
}
|
|
100191
100554
|
/**
|
|
100192
100555
|
* Apply the addon updates one notification was about.
|
|
@@ -105364,6 +105727,7 @@ var NotificationCenter = class {
|
|
|
105364
105727
|
* evaluating one.
|
|
105365
105728
|
*/
|
|
105366
105729
|
pruneLedger;
|
|
105730
|
+
verdicts;
|
|
105367
105731
|
/** D905: which rules cannot work, announced once per distinct reason. */
|
|
105368
105732
|
ruleHealth = new NcRuleHealthMonitor();
|
|
105369
105733
|
/** The prune itself — see `device-prune-runner.ts`. */
|
|
@@ -105592,6 +105956,11 @@ var NotificationCenter = class {
|
|
|
105592
105956
|
applyGrantedArmAnyway: (panel, grant) => self.applyGrantedArmAnyway(panel, grant),
|
|
105593
105957
|
applyGrantedBypassInput: (panel, grant) => self.applyGrantedBypassInput(panel, grant),
|
|
105594
105958
|
applyGrantedServerUpdate: (grant) => self.applyGrantedServerUpdate(grant),
|
|
105959
|
+
applyGrantedDetectionVerdict: (grant) => self.applyGrantedDetectionVerdict(grant),
|
|
105960
|
+
now: self.now,
|
|
105961
|
+
get verdicts() {
|
|
105962
|
+
return self.verdicts;
|
|
105963
|
+
},
|
|
105595
105964
|
applyGrantedSnooze: (grant) => self.applyGrantedSnooze(grant),
|
|
105596
105965
|
get deps() {
|
|
105597
105966
|
return self.deps;
|
|
@@ -105642,6 +106011,9 @@ var NotificationCenter = class {
|
|
|
105642
106011
|
async applyGrantedAddonUpdate(grant) {
|
|
105643
106012
|
return applyGrantedAddonUpdate(this.grantedActionsHost(), grant);
|
|
105644
106013
|
}
|
|
106014
|
+
async applyGrantedDetectionVerdict(grant) {
|
|
106015
|
+
return applyGrantedDetectionVerdict(this.grantedActionsHost(), grant);
|
|
106016
|
+
}
|
|
105645
106017
|
async applyGrantedServerUpdate(grant) {
|
|
105646
106018
|
return applyGrantedServerUpdate(this.grantedActionsHost(), grant);
|
|
105647
106019
|
}
|
|
@@ -105881,6 +106253,10 @@ var NotificationCenter = class {
|
|
|
105881
106253
|
}
|
|
105882
106254
|
this.summaryRules = NcSummaryStore.of(deps.records, this.logger.child("summary"), deps.now);
|
|
105883
106255
|
this.calendarEventsRules = NcCalendarEventsStore.of(deps.records, this.logger.child("calendar-events"), deps.now);
|
|
106256
|
+
this.verdicts = new NcDetectionVerdictLedger({
|
|
106257
|
+
...durableStore,
|
|
106258
|
+
logger: this.logger.child("detection-verdicts")
|
|
106259
|
+
});
|
|
105884
106260
|
this.pruneLedger = new NcPruneLedger({
|
|
105885
106261
|
...durableStore,
|
|
105886
106262
|
logger: this.logger.child("device-prune")
|
|
@@ -108488,6 +108864,23 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
108488
108864
|
};
|
|
108489
108865
|
}
|
|
108490
108866
|
},
|
|
108867
|
+
markTrackForRetrain: async ({ deviceId, trackId }) => {
|
|
108868
|
+
try {
|
|
108869
|
+
await this.ctx.api.pipelineAnalytics.setTrackFlags.mutate({
|
|
108870
|
+
deviceId,
|
|
108871
|
+
trackId,
|
|
108872
|
+
flags: { markForTrain: true }
|
|
108873
|
+
});
|
|
108874
|
+
return { ok: true };
|
|
108875
|
+
} catch (err) {
|
|
108876
|
+
const reason = retrainRefusalReason(err);
|
|
108877
|
+
if (reason === null) throw err;
|
|
108878
|
+
return {
|
|
108879
|
+
ok: false,
|
|
108880
|
+
reason
|
|
108881
|
+
};
|
|
108882
|
+
}
|
|
108883
|
+
},
|
|
108491
108884
|
readDeviceStates: async (ids) => {
|
|
108492
108885
|
const out = /* @__PURE__ */ new Map();
|
|
108493
108886
|
for (const id of ids) try {
|
|
@@ -108559,9 +108952,11 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
108559
108952
|
const targets = await api.notificationOutput.listTargets.query({});
|
|
108560
108953
|
const kinds = await api.notificationOutput.listTargetKinds.query({}).catch(() => []);
|
|
108561
108954
|
const bodyMaxLenByKind = new Map(kinds.map((k) => [k.kind, k.caps.bodyMaxLen]));
|
|
108955
|
+
const maxActionsByKind = new Map(kinds.map((k) => [k.kind, k.caps.actions]));
|
|
108562
108956
|
return targets.map((t) => {
|
|
108563
108957
|
const owner = t.config["ownerUserId"];
|
|
108564
108958
|
const bodyMaxLen = bodyMaxLenByKind.get(t.kind);
|
|
108959
|
+
const maxActions = maxActionsByKind.get(t.kind);
|
|
108565
108960
|
return {
|
|
108566
108961
|
id: t.id,
|
|
108567
108962
|
addonId: t.addonId,
|
|
@@ -108569,7 +108964,8 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
108569
108964
|
kind: t.kind,
|
|
108570
108965
|
enabled: t.enabled,
|
|
108571
108966
|
...typeof owner === "string" && owner.length > 0 ? { ownerUserId: owner } : {},
|
|
108572
|
-
...typeof bodyMaxLen === "number" ? { bodyMaxLen } : {}
|
|
108967
|
+
...typeof bodyMaxLen === "number" ? { bodyMaxLen } : {},
|
|
108968
|
+
...typeof maxActions === "number" ? { maxActions } : {}
|
|
108573
108969
|
};
|
|
108574
108970
|
});
|
|
108575
108971
|
},
|