@audiofool/moondog 0.1.0 → 0.2.0-beta.2
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/LICENSE +202 -0
- package/README.md +153 -68
- package/contracts/v1/definitions.schema.json +1 -169
- package/package.json +7 -23
- package/scripts/contract-lib.mjs +0 -7
- package/scripts/contract-semantics.mjs +0 -1904
- package/scripts/moondog.mjs +285 -74
- package/src/core/apple-library-source-status.mjs +0 -16
- package/src/core/apple-projection-domain-services.mjs +168 -36
- package/src/core/application-update.mjs +320 -0
- package/src/core/capability-catalog.mjs +278 -55
- package/src/core/listening-now.mjs +108 -0
- package/src/core/listening-profile-domain-services.mjs +153 -41
- package/src/core/listening-upkeep.mjs +31 -0
- package/src/core/moondog-application.mjs +1908 -104
- package/src/core/playback-preferences.mjs +97 -0
- package/src/core/spotify-listening-intent.mjs +121 -0
- package/src/core/synthetic-domain-services.mjs +24 -3
- package/src/demo/moondog-demo.mjs +4 -22
- package/src/i18n/index.mjs +285 -0
- package/src/i18n/messages/de.mjs +128 -0
- package/src/i18n/messages/en.mjs +129 -0
- package/src/i18n/messages/es.mjs +128 -0
- package/src/i18n/messages/fr.mjs +128 -0
- package/src/i18n/messages/it.mjs +128 -0
- package/src/i18n/messages/ja.mjs +128 -0
- package/src/i18n/messages/ko.mjs +128 -0
- package/src/i18n/messages/pt.mjs +128 -0
- package/src/i18n/messages/zh-Hant.mjs +128 -0
- package/src/i18n/messages/zh.mjs +128 -0
- package/src/i18n/preferences.mjs +34 -0
- package/src/i18n/screen/es.mjs +966 -0
- package/src/i18n/screen/ja.mjs +966 -0
- package/src/i18n/screen/pt.mjs +966 -0
- package/src/i18n/screen/zh.mjs +966 -0
- package/src/importers/apple-music-library/index.mjs +0 -16
- package/src/importers/apple-music-library/sqlite-projection.mjs +51 -9
- package/src/integrations/apple-music/catalog.mjs +123 -14
- package/src/integrations/apple-music/music-app.mjs +232 -0
- package/src/integrations/apple-music/storefront.mjs +28 -0
- package/src/integrations/apple-music/terminal-preview-player.mjs +221 -0
- package/src/integrations/lastfm/history.mjs +221 -0
- package/src/integrations/lastfm/settings.mjs +75 -0
- package/src/integrations/netease/ncm-cli-player.mjs +321 -0
- package/src/integrations/open-music-similarity/artist-radio.mjs +26 -18
- package/src/integrations/public-playlists.mjs +2 -1
- package/src/integrations/spotify/account-data-profile.mjs +14 -6
- package/src/integrations/spotify/authentication.mjs +149 -15
- package/src/integrations/spotify/catalog-resolver.mjs +8 -4
- package/src/integrations/spotify/connection.mjs +2 -1
- package/src/integrations/spotify/credential-store.mjs +4 -0
- package/src/integrations/spotify/extended-streaming-history.mjs +11 -11
- package/src/integrations/spotify/history-archive.mjs +1 -1
- package/src/integrations/spotify/library-snapshot.mjs +227 -0
- package/src/integrations/spotify/recent-activity.mjs +2 -0
- package/src/integrations/spotify/service.mjs +873 -69
- package/src/integrations/spotify/web-api-client.mjs +407 -127
- package/src/integrations/web/brave-web.mjs +45 -0
- package/src/integrations/web/codex-web.mjs +15 -82
- package/src/integrations/web/deepseek-web.mjs +81 -0
- package/src/integrations/web/web-research.mjs +119 -0
- package/src/integrations/web/web-result.mjs +215 -0
- package/src/integrations/youtube-music/takeout.mjs +3 -1
- package/src/memory/local-memory-store.mjs +469 -41
- package/src/profile/history-import.mjs +42 -6
- package/src/profile/listener-profile-build.mjs +378 -0
- package/src/profile/listener-profile-store.mjs +130 -0
- package/src/profile/listener-profile-verification.mjs +68 -0
- package/src/profile/listener-profile-worker.mjs +172 -0
- package/src/profile/listening-history-store.mjs +424 -8
- package/src/profile/listening-profile-projection.mjs +242 -76
- package/src/profile/local-music-data.mjs +1 -1
- package/src/profile/music-providers.mjs +11 -7
- package/src/profile/profile-exploration.mjs +84 -0
- package/src/profile/recorded-measures.mjs +24 -0
- package/src/profile/spotify-archive-taste.mjs +0 -4
- package/src/runtime/pi/agent-runtime.mjs +2204 -605
- package/src/runtime/pi/authentication.mjs +34 -1
- package/src/runtime/pi/configured-runtime.mjs +22 -6
- package/src/runtime/pi/conversation-name.mjs +53 -0
- package/src/runtime/pi/memory-agent-runtime.mjs +2 -2
- package/src/runtime/pi/model-catalog.mjs +28 -5
- package/src/runtime/pi/model-settings.mjs +202 -0
- package/src/runtime/pi/model-transport.mjs +10 -0
- package/src/runtime/pi/persistent-credential-store.mjs +67 -31
- package/src/runtime/pi/profile-build-runtime.mjs +160 -0
- package/src/runtime/pi/public-model-catalog.mjs +288 -0
- package/src/runtime/pi/runtime-settings.mjs +10 -2
- package/src/runtime/pi/skills/listener-profile/SKILL.md +46 -0
- package/src/runtime/pi/tool-trace.mjs +37 -0
- package/src/surfaces/cli/auth-command.mjs +12 -4
- package/src/surfaces/cli/brand-components.mjs +310 -132
- package/src/surfaces/cli/brand-theme.mjs +51 -28
- package/src/surfaces/cli/command-completions.mjs +58 -42
- package/src/surfaces/cli/format-output.mjs +199 -388
- package/src/surfaces/cli/history-import-view.mjs +292 -139
- package/src/surfaces/cli/import-guides.mjs +2 -0
- package/src/surfaces/cli/listener-profile-command.mjs +92 -0
- package/src/surfaces/cli/listening-menu.mjs +7 -4
- package/src/surfaces/cli/profile-command.mjs +30 -21
- package/src/surfaces/cli/seasons-chart.mjs +87 -0
- package/src/surfaces/cli/spotify-command.mjs +54 -6
- package/src/surfaces/cli/taste-profile-model.mjs +78 -79
- package/src/surfaces/cli/taste-profile-view.mjs +33 -26
- package/src/surfaces/cli/terminal-art.mjs +140 -35
- package/src/surfaces/cli/tui.mjs +1066 -357
- package/src/surfaces/cli/update-command.mjs +32 -0
- package/src/surfaces/cli/web-command.mjs +53 -14
- package/src/surfaces/html/tasteprint.mjs +43 -70
- package/contracts/v1/capability-policy.schema.json +0 -572
- package/contracts/v1/profile-evidence.schema.json +0 -237
- package/contracts/v1/tool-invocation.schema.json +0 -318
- package/contracts/v1/tool-receipt.schema.json +0 -1066
- package/scripts/demo-studio.mjs +0 -58
- package/src/core/apple-data-migration.mjs +0 -50
- package/src/demo/moondog-interactive-tasteprint-demo.mjs +0 -293
- package/src/demo/moondog-tasteprint-demo.mjs +0 -631
- package/src/integrations/spotify/index.mjs +0 -80
- package/src/surfaces/web/showcase.mjs +0 -272
- package/src/surfaces/web/studio-page.mjs +0 -4148
- package/src/surfaces/web/studio.mjs +0 -2288
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { exploreProfileCatalog, profileExplorationInput } from "../profile/profile-exploration.mjs";
|
|
3
|
+
import { createListenerProfileInput } from "../profile/listener-profile-build.mjs";
|
|
2
4
|
import { lyricSeedsFromProfile } from "./lyric-profile.mjs";
|
|
3
5
|
import { collectionCoverage } from "../profile/music-providers.mjs";
|
|
4
6
|
import { lstat } from "node:fs/promises";
|
|
@@ -408,6 +410,11 @@ function safeHistoryCount(value) {
|
|
|
408
410
|
return value;
|
|
409
411
|
}
|
|
410
412
|
|
|
413
|
+
// A pick leaves out minutes or skips that its plays never recorded.
|
|
414
|
+
function optionalHistoryCount(context, key) {
|
|
415
|
+
return context[key] === undefined ? {} : { [key]: safeHistoryCount(context[key]) };
|
|
416
|
+
}
|
|
417
|
+
|
|
411
418
|
function safeHistoryExternalRefs(value) {
|
|
412
419
|
if (value === undefined) return [];
|
|
413
420
|
if (!Array.isArray(value)) {
|
|
@@ -487,10 +494,13 @@ function safeHistoryTrack(raw) {
|
|
|
487
494
|
if (hasRediscovery) {
|
|
488
495
|
const rediscovery = raw.rediscovery;
|
|
489
496
|
const engagedPlayCount = safeHistoryCount(rediscovery.engaged_play_count);
|
|
490
|
-
const explicitSkips =
|
|
491
|
-
const listeningMinutes =
|
|
497
|
+
const explicitSkips = optionalHistoryCount(rediscovery, "explicit_skips");
|
|
498
|
+
const listeningMinutes = optionalHistoryCount(rediscovery, "listening_minutes");
|
|
492
499
|
const quietDays = safeHistoryCount(rediscovery.quiet_days);
|
|
493
|
-
if (
|
|
500
|
+
if (
|
|
501
|
+
engagedPlayCount > playCount ||
|
|
502
|
+
(explicitSkips.explicit_skips ?? 0) > playCount
|
|
503
|
+
) {
|
|
494
504
|
fail("history_candidate_invalid", "A listening-history candidate is invalid.");
|
|
495
505
|
}
|
|
496
506
|
const rediscoverySignal = cleanText(
|
|
@@ -512,7 +522,9 @@ function safeHistoryTrack(raw) {
|
|
|
512
522
|
const peakFields = [
|
|
513
523
|
rediscovery.peak_year,
|
|
514
524
|
rediscovery.peak_year_play_count,
|
|
515
|
-
rediscovery.peak_year_listening_minutes
|
|
525
|
+
...(rediscovery.peak_year_listening_minutes === undefined
|
|
526
|
+
? []
|
|
527
|
+
: [rediscovery.peak_year_listening_minutes]),
|
|
516
528
|
];
|
|
517
529
|
if (peakFields.some((value) => value !== undefined)) {
|
|
518
530
|
if (
|
|
@@ -524,9 +536,9 @@ function safeHistoryTrack(raw) {
|
|
|
524
536
|
}
|
|
525
537
|
}
|
|
526
538
|
track.rediscovery = {
|
|
527
|
-
|
|
539
|
+
...listeningMinutes,
|
|
528
540
|
engaged_play_count: engagedPlayCount,
|
|
529
|
-
|
|
541
|
+
...explicitSkips,
|
|
530
542
|
first_played_at: firstPlayedAt,
|
|
531
543
|
last_played_at: lastPlayedAt,
|
|
532
544
|
quiet_days: quietDays,
|
|
@@ -535,8 +547,7 @@ function safeHistoryTrack(raw) {
|
|
|
535
547
|
? {
|
|
536
548
|
peak_year: rediscovery.peak_year,
|
|
537
549
|
peak_year_play_count: rediscovery.peak_year_play_count,
|
|
538
|
-
peak_year_listening_minutes
|
|
539
|
-
rediscovery.peak_year_listening_minutes,
|
|
550
|
+
...optionalHistoryCount(rediscovery, "peak_year_listening_minutes"),
|
|
540
551
|
}
|
|
541
552
|
: {}),
|
|
542
553
|
evidence_id: rediscovery.evidence_id.toLowerCase(),
|
|
@@ -546,9 +557,10 @@ function safeHistoryTrack(raw) {
|
|
|
546
557
|
const engagedPlayCount = safeHistoryCount(
|
|
547
558
|
historicalReturn.engaged_play_count,
|
|
548
559
|
);
|
|
549
|
-
const explicitSkips =
|
|
550
|
-
const listeningMinutes =
|
|
551
|
-
historicalReturn
|
|
560
|
+
const explicitSkips = optionalHistoryCount(historicalReturn, "explicit_skips");
|
|
561
|
+
const listeningMinutes = optionalHistoryCount(
|
|
562
|
+
historicalReturn,
|
|
563
|
+
"listening_minutes",
|
|
552
564
|
);
|
|
553
565
|
const returnCount = safeHistoryCount(historicalReturn.return_count);
|
|
554
566
|
const longestGapDays = safeHistoryCount(
|
|
@@ -577,7 +589,7 @@ function safeHistoryTrack(raw) {
|
|
|
577
589
|
latestReturnGapDays < 1 ||
|
|
578
590
|
latestReturnGapDays > longestGapDays ||
|
|
579
591
|
engagedPlayCount > playCount ||
|
|
580
|
-
explicitSkips > playCount ||
|
|
592
|
+
(explicitSkips.explicit_skips ?? 0) > playCount ||
|
|
581
593
|
Date.parse(firstPlayedAt) > Date.parse(latestReturnAt) ||
|
|
582
594
|
Date.parse(latestReturnAt) > Date.parse(lastPlayedAt) ||
|
|
583
595
|
!rediscoverySignals.has(historicalReturnSignal) ||
|
|
@@ -586,9 +598,9 @@ function safeHistoryTrack(raw) {
|
|
|
586
598
|
fail("history_candidate_invalid", "A listening-history candidate is invalid.");
|
|
587
599
|
}
|
|
588
600
|
track.historical_return = {
|
|
589
|
-
|
|
601
|
+
...listeningMinutes,
|
|
590
602
|
engaged_play_count: engagedPlayCount,
|
|
591
|
-
|
|
603
|
+
...explicitSkips,
|
|
592
604
|
first_played_at: firstPlayedAt,
|
|
593
605
|
last_played_at: lastPlayedAt,
|
|
594
606
|
return_count: returnCount,
|
|
@@ -605,12 +617,14 @@ function safeHistoryTrack(raw) {
|
|
|
605
617
|
const yearEngagedPlayCount = safeHistoryCount(
|
|
606
618
|
capsule.year_engaged_play_count,
|
|
607
619
|
);
|
|
608
|
-
const yearListeningMinutes =
|
|
609
|
-
capsule
|
|
620
|
+
const yearListeningMinutes = optionalHistoryCount(
|
|
621
|
+
capsule,
|
|
622
|
+
"year_listening_minutes",
|
|
610
623
|
);
|
|
611
|
-
const yearExplicitSkips =
|
|
612
|
-
const lifetimeListeningMinutes =
|
|
613
|
-
capsule
|
|
624
|
+
const yearExplicitSkips = optionalHistoryCount(capsule, "year_explicit_skips");
|
|
625
|
+
const lifetimeListeningMinutes = optionalHistoryCount(
|
|
626
|
+
capsule,
|
|
627
|
+
"lifetime_listening_minutes",
|
|
614
628
|
);
|
|
615
629
|
const representativeSignal = cleanText(
|
|
616
630
|
capsule.representative_signal,
|
|
@@ -622,8 +636,9 @@ function safeHistoryTrack(raw) {
|
|
|
622
636
|
year > 9999 ||
|
|
623
637
|
yearPlayCount > playCount ||
|
|
624
638
|
yearEngagedPlayCount > yearPlayCount ||
|
|
625
|
-
yearExplicitSkips > yearPlayCount ||
|
|
626
|
-
yearListeningMinutes
|
|
639
|
+
(yearExplicitSkips.year_explicit_skips ?? 0) > yearPlayCount ||
|
|
640
|
+
(yearListeningMinutes.year_listening_minutes ?? 0) >
|
|
641
|
+
(lifetimeListeningMinutes.lifetime_listening_minutes ?? Infinity) ||
|
|
627
642
|
!rediscoverySignals.has(representativeSignal) ||
|
|
628
643
|
!isUuid(capsule.evidence_id)
|
|
629
644
|
) {
|
|
@@ -633,16 +648,16 @@ function safeHistoryTrack(raw) {
|
|
|
633
648
|
year,
|
|
634
649
|
year_play_count: yearPlayCount,
|
|
635
650
|
year_engaged_play_count: yearEngagedPlayCount,
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
651
|
+
...yearListeningMinutes,
|
|
652
|
+
...yearExplicitSkips,
|
|
653
|
+
...lifetimeListeningMinutes,
|
|
639
654
|
representative_signal: representativeSignal,
|
|
640
655
|
evidence_id: capsule.evidence_id.toLowerCase(),
|
|
641
656
|
};
|
|
642
657
|
} else {
|
|
643
658
|
const context = raw.back_to_back;
|
|
644
659
|
const engagedPlayCount = safeHistoryCount(context.engaged_play_count);
|
|
645
|
-
const explicitSkips =
|
|
660
|
+
const explicitSkips = optionalHistoryCount(context, "explicit_skips");
|
|
646
661
|
const burstCount = safeHistoryCount(context.burst_count);
|
|
647
662
|
const maximumConsecutivePlays = safeHistoryCount(
|
|
648
663
|
context.maximum_consecutive_plays,
|
|
@@ -664,7 +679,7 @@ function safeHistoryTrack(raw) {
|
|
|
664
679
|
playsInBursts < burstCount * 2 ||
|
|
665
680
|
playsInBursts > playCount ||
|
|
666
681
|
engagedPlayCount > playCount ||
|
|
667
|
-
explicitSkips > playCount ||
|
|
682
|
+
(explicitSkips.explicit_skips ?? 0) > playCount ||
|
|
668
683
|
!backToBackSignals.has(sequenceSignal) ||
|
|
669
684
|
!isUuid(context.evidence_id)
|
|
670
685
|
) {
|
|
@@ -672,7 +687,7 @@ function safeHistoryTrack(raw) {
|
|
|
672
687
|
}
|
|
673
688
|
track.back_to_back = {
|
|
674
689
|
engaged_play_count: engagedPlayCount,
|
|
675
|
-
|
|
690
|
+
...explicitSkips,
|
|
676
691
|
burst_count: burstCount,
|
|
677
692
|
maximum_consecutive_plays: maximumConsecutivePlays,
|
|
678
693
|
plays_in_bursts: playsInBursts,
|
|
@@ -970,7 +985,7 @@ function preferenceSignalLabel(observation = {}) {
|
|
|
970
985
|
return labels.join(", ") || "explicit provider preference signal";
|
|
971
986
|
}
|
|
972
987
|
|
|
973
|
-
function listenerAssertionAvoids(
|
|
988
|
+
export function listenerAssertionAvoids(
|
|
974
989
|
listening,
|
|
975
990
|
entityType,
|
|
976
991
|
label,
|
|
@@ -1195,6 +1210,8 @@ export class AppleProjectionDomainServices {
|
|
|
1195
1210
|
#listeningHistoryStore;
|
|
1196
1211
|
#subjectId;
|
|
1197
1212
|
#candidateSets = new Map();
|
|
1213
|
+
#profileBuildInput;
|
|
1214
|
+
#ownsStores = true;
|
|
1198
1215
|
|
|
1199
1216
|
constructor({ projection, listeningHistoryStore = null, subjectId = null }) {
|
|
1200
1217
|
if (!projection) throw new TypeError("A trusted Apple projection is required");
|
|
@@ -1206,6 +1223,19 @@ export class AppleProjectionDomainServices {
|
|
|
1206
1223
|
this.#subjectId = subjectId?.toLowerCase() ?? null;
|
|
1207
1224
|
}
|
|
1208
1225
|
|
|
1226
|
+
// Each conversation that runs beside another gets a fork: the same projection and history
|
|
1227
|
+
// with its own candidate sets, so one conversation's prompt never clears another's. Only
|
|
1228
|
+
// the original closes them.
|
|
1229
|
+
fork() {
|
|
1230
|
+
const fork = new AppleProjectionDomainServices({
|
|
1231
|
+
projection: this.#projection,
|
|
1232
|
+
listeningHistoryStore: this.#listeningHistoryStore,
|
|
1233
|
+
subjectId: this.#subjectId,
|
|
1234
|
+
});
|
|
1235
|
+
fork.#ownsStores = false;
|
|
1236
|
+
return fork;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1209
1239
|
status() {
|
|
1210
1240
|
return {
|
|
1211
1241
|
state: "ready",
|
|
@@ -1246,6 +1276,14 @@ export class AppleProjectionDomainServices {
|
|
|
1246
1276
|
};
|
|
1247
1277
|
}
|
|
1248
1278
|
|
|
1279
|
+
listeningCoverage() {
|
|
1280
|
+
return this.#listeningHistoryStore?.listeningCoverage?.({ subjectId: this.#subjectId }) ?? null;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
recentAttention() {
|
|
1284
|
+
return this.#listeningHistoryStore?.recentAttention?.({ subjectId: this.#subjectId }) ?? null;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1249
1287
|
rediscoveryReady() {
|
|
1250
1288
|
return (
|
|
1251
1289
|
this.#listeningHistoryStore !== null &&
|
|
@@ -1726,7 +1764,23 @@ export class AppleProjectionDomainServices {
|
|
|
1726
1764
|
};
|
|
1727
1765
|
}
|
|
1728
1766
|
|
|
1729
|
-
|
|
1767
|
+
filterDiscoveryTracks(tracks) {
|
|
1768
|
+
const store = this.#listeningHistoryStore;
|
|
1769
|
+
const avoids = store?.activeAvoidances?.({ subjectId: this.#subjectId }) ??
|
|
1770
|
+
store?.profileSummary?.({ subjectId: this.#subjectId })?.listener_assertions?.avoids ?? [];
|
|
1771
|
+
const listening = normalizeAppleCorrectionLabels(
|
|
1772
|
+
{ listener_assertions: { avoids } }, this.#projection,
|
|
1773
|
+
);
|
|
1774
|
+
return tracks.filter((track) => !listenerAssertionAvoids(
|
|
1775
|
+
listening, "track", track.title, track.artist_credit,
|
|
1776
|
+
));
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
isKnownDiscoveryTrack(track) {
|
|
1780
|
+
return (this.#listeningHistoryStore?.listenedTracks?.({ subjectId: this.#subjectId }) ?? []).some(heard => exactTitleArtistMatch(track, heard));
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
registerExternalCandidateSet({ tracks, source, excludeKnown = true } = {}) {
|
|
1730
1784
|
if (
|
|
1731
1785
|
!Array.isArray(tracks) ||
|
|
1732
1786
|
tracks.length < 1 ||
|
|
@@ -1747,7 +1801,8 @@ export class AppleProjectionDomainServices {
|
|
|
1747
1801
|
}
|
|
1748
1802
|
const accepted = [];
|
|
1749
1803
|
let excludedLibraryMatches = 0;
|
|
1750
|
-
|
|
1804
|
+
const allowed = this.filterDiscoveryTracks(prepared);
|
|
1805
|
+
for (const track of allowed) {
|
|
1751
1806
|
const query = Array.from(track.title).slice(0, 256).join("");
|
|
1752
1807
|
const libraryResults = this.#projection.searchLibrary({
|
|
1753
1808
|
query,
|
|
@@ -1763,14 +1818,14 @@ export class AppleProjectionDomainServices {
|
|
|
1763
1818
|
const libraryMatch = libraryResults.tracks
|
|
1764
1819
|
.map(safeTrack)
|
|
1765
1820
|
.some((candidate) => exactTitleArtistMatch(track, candidate));
|
|
1766
|
-
if (libraryMatch) {
|
|
1821
|
+
if (excludeKnown && libraryMatch) {
|
|
1767
1822
|
excludedLibraryMatches += 1;
|
|
1768
1823
|
continue;
|
|
1769
1824
|
}
|
|
1770
1825
|
accepted.push({
|
|
1771
1826
|
...track,
|
|
1772
1827
|
knownness: {
|
|
1773
|
-
imported_library: "not_found_by_exact_title_artist",
|
|
1828
|
+
imported_library: libraryMatch ? "found_by_exact_title_artist" : "not_found_by_exact_title_artist",
|
|
1774
1829
|
listening_history: "not_checked",
|
|
1775
1830
|
},
|
|
1776
1831
|
});
|
|
@@ -1780,6 +1835,7 @@ export class AppleProjectionDomainServices {
|
|
|
1780
1835
|
candidate_set_id: null,
|
|
1781
1836
|
candidate_scope: "external_catalog",
|
|
1782
1837
|
result_count: 0,
|
|
1838
|
+
excluded_avoided_matches: prepared.length - allowed.length,
|
|
1783
1839
|
excluded_library_matches: excludedLibraryMatches,
|
|
1784
1840
|
expires_on: "prompt_end",
|
|
1785
1841
|
source: trustedSource,
|
|
@@ -1797,6 +1853,7 @@ export class AppleProjectionDomainServices {
|
|
|
1797
1853
|
candidate_set_id: candidateSetId,
|
|
1798
1854
|
candidate_scope: "external_catalog",
|
|
1799
1855
|
result_count: accepted.length,
|
|
1856
|
+
excluded_avoided_matches: prepared.length - allowed.length,
|
|
1800
1857
|
excluded_library_matches: excludedLibraryMatches,
|
|
1801
1858
|
expires_on: "prompt_end",
|
|
1802
1859
|
source: trustedSource,
|
|
@@ -1893,6 +1950,59 @@ export class AppleProjectionDomainServices {
|
|
|
1893
1950
|
};
|
|
1894
1951
|
}
|
|
1895
1952
|
|
|
1953
|
+
#appleProfileCatalog(knownAvoids) {
|
|
1954
|
+
if (typeof this.#projection.getProfileCatalog !== "function") return null;
|
|
1955
|
+
const { listener_assertions: { avoids } } = normalizeAppleCorrectionLabels({
|
|
1956
|
+
listener_assertions: {
|
|
1957
|
+
avoids: knownAvoids ?? this.#listeningHistoryStore?.activeAvoidances?.({ subjectId: this.#subjectId }) ?? [],
|
|
1958
|
+
},
|
|
1959
|
+
}, this.#projection);
|
|
1960
|
+
return this.#projection.getProfileCatalog({ excludePreference: (track) =>
|
|
1961
|
+
listenerAssertionAvoids({ listener_assertions: { avoids } }, "track", track.label, track.artist_credit) });
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
profileExplorationReady() {
|
|
1965
|
+
return typeof this.#projection.getProfileCatalog === "function";
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
async exploreProfile(argumentsValue) {
|
|
1969
|
+
const input = profileExplorationInput(argumentsValue);
|
|
1970
|
+
const listening = this.#listeningHistoryStore?.profileCatalog({ subjectId: this.#subjectId });
|
|
1971
|
+
return exploreProfileCatalog({
|
|
1972
|
+
listening,
|
|
1973
|
+
apple: this.#appleProfileCatalog(listening?.sections.listener_avoids), input,
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
profileBuildReady() {
|
|
1978
|
+
return typeof this.#listeningHistoryStore?.listenerProfileStorage === "function" &&
|
|
1979
|
+
typeof this.#projection.logicalDigest === "function";
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
getListenerProfileStore() {
|
|
1983
|
+
return this.#listeningHistoryStore.listenerProfileStorage({ subjectId: this.#subjectId });
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
getProfileBuildContext() {
|
|
1987
|
+
const appleDigest = this.#projection.logicalDigest();
|
|
1988
|
+
const version = `${appleDigest}:${this.#listeningHistoryStore.profileDataVersion()}`;
|
|
1989
|
+
if (this.#profileBuildInput?.version !== version) {
|
|
1990
|
+
const listening = this.#listeningHistoryStore.profileCatalog({ subjectId: this.#subjectId });
|
|
1991
|
+
const normalized = normalizeAppleCorrectionLabels({ listener_assertions: {
|
|
1992
|
+
preferences: listening.sections.listener_preferences, avoids: listening.sections.listener_avoids,
|
|
1993
|
+
} }, this.#projection).listener_assertions;
|
|
1994
|
+
listening.sections.listener_preferences = normalized.preferences;
|
|
1995
|
+
listening.sections.listener_avoids = normalized.avoids;
|
|
1996
|
+
this.#profileBuildInput = { version, input: createListenerProfileInput({
|
|
1997
|
+
subjectId: this.#subjectId,
|
|
1998
|
+
evidenceRevision: [appleDigest,
|
|
1999
|
+
this.#listeningHistoryStore.profileEvidenceRevision({ subjectId: this.#subjectId })],
|
|
2000
|
+
listening, apple: this.#appleProfileCatalog(listening.sections.listener_avoids),
|
|
2001
|
+
}) };
|
|
2002
|
+
}
|
|
2003
|
+
return { store: this.getListenerProfileStore(), input: this.#profileBuildInput.input };
|
|
2004
|
+
}
|
|
2005
|
+
|
|
1896
2006
|
async getProfileSummary(argumentsValue) {
|
|
1897
2007
|
const input = modelArguments(argumentsValue, new Set(["maxItems"]));
|
|
1898
2008
|
const bounded = boundedLimit(
|
|
@@ -1990,13 +2100,16 @@ export class AppleProjectionDomainServices {
|
|
|
1990
2100
|
play_count: item.play_count,
|
|
1991
2101
|
evidence_id: item.evidence_id,
|
|
1992
2102
|
})) ?? [];
|
|
1993
|
-
const
|
|
2103
|
+
const appleCatalog = this.#appleProfileCatalog();
|
|
2104
|
+
const appleArtistFacets = (appleCatalog
|
|
2105
|
+
? appleCatalog.sections.apple_artists.filter((item) => item.preferred_tracks > 0)
|
|
2106
|
+
: facetItems(
|
|
1994
2107
|
preferenceItems,
|
|
1995
2108
|
(item) => item.artist_credit,
|
|
1996
2109
|
bounded,
|
|
1997
|
-
).filter(
|
|
2110
|
+
)).filter(
|
|
1998
2111
|
(item) => !listenerAssertionAvoids(listening, "artist", item.name),
|
|
1999
|
-
);
|
|
2112
|
+
).slice(0, bounded);
|
|
2000
2113
|
const spotifyArtistFacets = [
|
|
2001
2114
|
...(listening?.curated_preferences.followed_artists ?? []),
|
|
2002
2115
|
...(listening?.listening_behavior.enduring_artists ?? []),
|
|
@@ -2009,7 +2122,9 @@ export class AppleProjectionDomainServices {
|
|
|
2009
2122
|
name: item.name,
|
|
2010
2123
|
evidence_id: item.evidence_id,
|
|
2011
2124
|
}));
|
|
2012
|
-
const appleGenreFacets =
|
|
2125
|
+
const appleGenreFacets = appleCatalog
|
|
2126
|
+
? appleCatalog.sections.apple_genres.filter((item) => item.preferred_tracks > 0).slice(0, bounded)
|
|
2127
|
+
: facetItems(
|
|
2013
2128
|
preferenceItems,
|
|
2014
2129
|
(item) => item.labels?.provider_genre,
|
|
2015
2130
|
bounded,
|
|
@@ -2109,6 +2224,17 @@ export class AppleProjectionDomainServices {
|
|
|
2109
2224
|
if (!isUuid(cleanedId)) {
|
|
2110
2225
|
fail("invalid_evidence_id", "Profile evidence ID is invalid.");
|
|
2111
2226
|
}
|
|
2227
|
+
const catalog = this.#appleProfileCatalog();
|
|
2228
|
+
const facet = [...(catalog?.sections.apple_artists ?? []), ...(catalog?.sections.apple_genres ?? [])]
|
|
2229
|
+
.find((item) => item.evidence_id === cleanedId);
|
|
2230
|
+
if (facet) return {
|
|
2231
|
+
evidence_id: facet.evidence_id,
|
|
2232
|
+
claim: { dimension: "taste.library_facet", value: facet.name, direction: "supports" },
|
|
2233
|
+
basis_summary: `${facet.preferred_tracks} tracks with positive provider preferences among ${facet.library_tracks} library tracks, after active Avoids. ${facet.play_count} aggregate plays across ${facet.tracks_with_play_count} tracks with known counts.`,
|
|
2234
|
+
derivation: { kind: "rule", name: "whole-apple-library-facet", version: "1" },
|
|
2235
|
+
confidence: 0.9,
|
|
2236
|
+
interpretation_limit: "A full-library metadata aggregation. Play counts show familiarity; provider preferences are snapshot states, and genre names do not describe measured audio.",
|
|
2237
|
+
};
|
|
2112
2238
|
const raw = this.#projection.explainProfileEvidence({
|
|
2113
2239
|
evidenceId: cleanedId,
|
|
2114
2240
|
});
|
|
@@ -2258,6 +2384,11 @@ export class AppleProjectionDomainServices {
|
|
|
2258
2384
|
"The playlist track count does not match the explicit user intent.",
|
|
2259
2385
|
);
|
|
2260
2386
|
}
|
|
2387
|
+
const external = selections.map((selection) => trustedTracks.get(selection.trackRefId))
|
|
2388
|
+
.filter((track) => track.candidate_scope === "external_catalog");
|
|
2389
|
+
if (this.filterDiscoveryTracks(external).length !== external.length) {
|
|
2390
|
+
fail("discovery_track_avoided", "An external discovery is currently marked Avoid. Choose another track.");
|
|
2391
|
+
}
|
|
2261
2392
|
const tracks = selections.map((selection, index) => {
|
|
2262
2393
|
const track = trustedTracks.get(selection.trackRefId);
|
|
2263
2394
|
const result = {
|
|
@@ -2315,6 +2446,7 @@ export class AppleProjectionDomainServices {
|
|
|
2315
2446
|
|
|
2316
2447
|
close() {
|
|
2317
2448
|
this.resetCandidateSets();
|
|
2449
|
+
if (!this.#ownsStores) return;
|
|
2318
2450
|
try {
|
|
2319
2451
|
this.#projection.close();
|
|
2320
2452
|
} finally {
|