@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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { modelEvidence, profileDigest } from "./listener-profile-build.mjs";
|
|
2
|
+
|
|
3
|
+
export const LISTENER_PROFILE_CHECK_VERSION = "listener-check/1";
|
|
4
|
+
const MAX_CHECK_ISSUES = 6;
|
|
5
|
+
|
|
6
|
+
export function listenerProfileCandidate(input, profile) {
|
|
7
|
+
const byId = new Map(profile.claims.map(claim => [claim.claim_id, claim]));
|
|
8
|
+
const insights = profile.highlight_claim_ids.map(id => byId.get(id));
|
|
9
|
+
const content = { summary: profile.summary, insights };
|
|
10
|
+
const references = new Set(insights.flatMap(claim => [...claim.supporting_refs, ...claim.contradicting_refs]));
|
|
11
|
+
return {
|
|
12
|
+
candidate_id: profileDigest({ input_digest: input.input_digest, ...content }),
|
|
13
|
+
...content,
|
|
14
|
+
cited_evidence: [...references].map(ref => modelEvidence(input.evidence.get(ref))),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function checkText(value, field) {
|
|
19
|
+
if (typeof value !== "string" || !value.trim() || Array.from(value).length > 500 ||
|
|
20
|
+
/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f--]/u.test(value)) {
|
|
21
|
+
throw new TypeError(`Each check issue needs a ${field} of at most 500 characters`);
|
|
22
|
+
}
|
|
23
|
+
return value.trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* One judgment of the whole candidate. Only material problems are issues;
|
|
28
|
+
* citation completeness and phrasing are deliberately out of scope.
|
|
29
|
+
*/
|
|
30
|
+
export function validateListenerProfileCheck(candidate, value) {
|
|
31
|
+
if (value?.candidate_id !== candidate.candidate_id) {
|
|
32
|
+
throw new TypeError("The check must identify the current profile candidate");
|
|
33
|
+
}
|
|
34
|
+
if (!["pass", "revise"].includes(value.verdict)) throw new TypeError("The check verdict must be pass or revise");
|
|
35
|
+
const issues = value.issues ?? [];
|
|
36
|
+
if (!Array.isArray(issues) || issues.length > MAX_CHECK_ISSUES) {
|
|
37
|
+
throw new TypeError(`The check can list at most ${MAX_CHECK_ISSUES} material issues`);
|
|
38
|
+
}
|
|
39
|
+
if (value.verdict === "revise" && !issues.length) throw new TypeError("A revise verdict needs at least one material issue");
|
|
40
|
+
if (value.verdict === "pass" && issues.length) throw new TypeError("A pass verdict lists no issues; return revise for a material problem");
|
|
41
|
+
const targets = new Set(["summary", ...candidate.insights.map(claim => claim.claim_id)]);
|
|
42
|
+
return {
|
|
43
|
+
version: LISTENER_PROFILE_CHECK_VERSION,
|
|
44
|
+
candidate_id: candidate.candidate_id,
|
|
45
|
+
verdict: value.verdict,
|
|
46
|
+
issues: issues.map(issue => {
|
|
47
|
+
if (!targets.has(issue?.target)) {
|
|
48
|
+
throw new TypeError(`Check issue target ${JSON.stringify(issue?.target)} must be "summary" or a claim_id of this candidate`);
|
|
49
|
+
}
|
|
50
|
+
return { target: issue.target, problem: checkText(issue.problem, "problem"), correction: checkText(issue.correction, "correction") };
|
|
51
|
+
}),
|
|
52
|
+
checked_at: new Date().toISOString(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function searchListenerProfileEvidence(input, { section, query, offset = 0 } = {}) {
|
|
57
|
+
if (section !== undefined && !input.manifest.sections.some(item => item.section === section)) {
|
|
58
|
+
throw new TypeError("Unknown evidence section");
|
|
59
|
+
}
|
|
60
|
+
if ((query !== undefined && (typeof query !== "string" || query.length > 200)) ||
|
|
61
|
+
!Number.isSafeInteger(offset) || offset < 0) throw new TypeError("Invalid evidence search");
|
|
62
|
+
const needle = query?.normalize("NFKC").toLocaleLowerCase();
|
|
63
|
+
const matches = [...input.evidence.values()].filter(entry => !section || entry.section === section)
|
|
64
|
+
.filter(entry => !needle || JSON.stringify(modelEvidence(entry, 0, Number.MAX_SAFE_INTEGER).data)
|
|
65
|
+
.normalize("NFKC").toLocaleLowerCase().includes(needle));
|
|
66
|
+
return { total: matches.length, offset, items: matches.slice(offset, offset + 12).map(entry => modelEvidence(entry)),
|
|
67
|
+
next_offset: offset + 12 < matches.length ? offset + 12 : null };
|
|
68
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHECKED_PROFILE_STATES, compileListenerProfile, listenerProfileDossier, LISTENER_PROFILE_SYNTHESIS_VERSION, modelEvidence,
|
|
3
|
+
sameListenerChoices,
|
|
4
|
+
} from "./listener-profile-build.mjs";
|
|
5
|
+
import {
|
|
6
|
+
LISTENER_PROFILE_CHECK_VERSION, listenerProfileCandidate, searchListenerProfileEvidence, validateListenerProfileCheck,
|
|
7
|
+
} from "./listener-profile-verification.mjs";
|
|
8
|
+
import { createConfiguredProfileBuildRuntime } from "../runtime/pi/profile-build-runtime.mjs";
|
|
9
|
+
|
|
10
|
+
// One draft, one check, at most one revision and its check. Spare sessions let
|
|
11
|
+
// a model that hits its turn limit continue the same stage.
|
|
12
|
+
const MAX_REVISIONS = 1;
|
|
13
|
+
const MAX_SESSIONS = 8;
|
|
14
|
+
|
|
15
|
+
const HOUR_MS = 3_600_000;
|
|
16
|
+
// A reading that only new plays have overtaken is rebuilt after a week or 300 plays.
|
|
17
|
+
// Changed choices or analysis rebuild it at once. Any build attempt, the listener's
|
|
18
|
+
// or this one, holds off the next background attempt for half a day.
|
|
19
|
+
export const PROFILE_REFRESH = { listeningAgeMs: 7 * 24 * HOUR_MS, newPlays: 300, attemptGapMs: 12 * HOUR_MS };
|
|
20
|
+
|
|
21
|
+
/** Rebuild a saved reading that newer evidence has overtaken, without being asked. */
|
|
22
|
+
export async function refreshListenerProfileIfDue({ application, now = Date.now(), ...options } = {}) {
|
|
23
|
+
// The first reading is the listener's to ask for.
|
|
24
|
+
if (!application.profileBuildReady()) return { state: "unavailable" };
|
|
25
|
+
const reading = await application.getListenerProfile({ limit: 1 });
|
|
26
|
+
if (reading.state !== "stale") return { state: reading.state };
|
|
27
|
+
const { input, store } = application.getProfileBuildContext();
|
|
28
|
+
const lastAttempt = Date.parse(store.lastAttemptAt() ?? "");
|
|
29
|
+
if (now - lastAttempt < PROFILE_REFRESH.attemptGapMs) return { state: "waiting" };
|
|
30
|
+
if (reading.newer_listening_only) {
|
|
31
|
+
const newPlays = (input.manifest.coverage?.effective_listening_events ?? 0) -
|
|
32
|
+
(reading.coverage?.coverage?.effective_listening_events ?? 0);
|
|
33
|
+
if (now - Date.parse(reading.built_at) < PROFILE_REFRESH.listeningAgeMs && newPlays < PROFILE_REFRESH.newPlays) {
|
|
34
|
+
return { state: "waiting" };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return runListenerProfileBuild({ ...options, application });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function runListenerProfileBuild({
|
|
41
|
+
application, environment = process.env, signal, onProgress = () => {}, force = false,
|
|
42
|
+
runtimeFactory = createConfiguredProfileBuildRuntime,
|
|
43
|
+
} = {}) {
|
|
44
|
+
signal?.throwIfAborted();
|
|
45
|
+
const { input, store } = application.getProfileBuildContext();
|
|
46
|
+
if (!input.evidence.size) return { state: "no_evidence" };
|
|
47
|
+
const previous = store.current();
|
|
48
|
+
if (!force && previous?.input_digest === input.input_digest && previous.synthesis_version === LISTENER_PROFILE_SYNTHESIS_VERSION &&
|
|
49
|
+
CHECKED_PROFILE_STATES.has(previous.verification?.state)) {
|
|
50
|
+
return { state: "unchanged", revision: await application.getListenerProfile() };
|
|
51
|
+
}
|
|
52
|
+
const runtime = await runtimeFactory(environment);
|
|
53
|
+
const status = runtime.publicStatus();
|
|
54
|
+
if (status.state !== "configured") throw new Error("Connect a model with /model, then use /profile build. Your imported profile is still available.");
|
|
55
|
+
const model = { provider: status.provider, model: status.model, worker_version: status.worker_version };
|
|
56
|
+
const build = store.begin(input, model, { force });
|
|
57
|
+
// Drafts and checks are checkpoints, never current profile revisions.
|
|
58
|
+
let finalization = build.finalization?.synthesis_version === LISTENER_PROFILE_SYNTHESIS_VERSION
|
|
59
|
+
? structuredClone(build.finalization)
|
|
60
|
+
: { synthesis_version: LISTENER_PROFILE_SYNTHESIS_VERSION, draft: null, checks: [], revisions: 0 };
|
|
61
|
+
let compiled = finalization.draft ? compileListenerProfile(input, finalization.draft) : null;
|
|
62
|
+
let candidate = compiled ? listenerProfileCandidate(input, compiled) : null;
|
|
63
|
+
const latestCheck = () => finalization.checks.at(-1) ?? null;
|
|
64
|
+
const phase = () => !candidate ? "synthesis"
|
|
65
|
+
: latestCheck()?.candidate_id !== candidate.candidate_id ? "check"
|
|
66
|
+
: latestCheck().verdict === "revise" && finalization.revisions < MAX_REVISIONS ? "revision" : "complete";
|
|
67
|
+
const progress = () => ({ phase: phase(), checks: finalization.checks.length, revisions: finalization.revisions,
|
|
68
|
+
evidence_rows: input.evidence.size, resumed: build.resumed });
|
|
69
|
+
const inspected = new Set();
|
|
70
|
+
let stageFinished = false;
|
|
71
|
+
// Plays synced during a long build do not stop it: the reading is saved as current up to the
|
|
72
|
+
// plays it read. A changed Like or Avoid does, since the reading could contradict it.
|
|
73
|
+
const assertCurrentInput = () => {
|
|
74
|
+
signal?.throwIfAborted();
|
|
75
|
+
if (!sameListenerChoices(input.evidence, application.getProfileBuildContext().input.evidence)) {
|
|
76
|
+
throw new Error("Your choices changed during the build. Use /profile build to read the updated evidence.");
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
// The checkpoint commits before memory changes, so a lost lease leaves both unchanged.
|
|
80
|
+
const save = (next, apply = () => {}) => {
|
|
81
|
+
store.checkpointFinalization(build, next);
|
|
82
|
+
apply();
|
|
83
|
+
finalization = next;
|
|
84
|
+
stageFinished = true;
|
|
85
|
+
onProgress(progress());
|
|
86
|
+
};
|
|
87
|
+
const session = {
|
|
88
|
+
phase: phase(), manifest: input.manifest, dossier: listenerProfileDossier(input), progress,
|
|
89
|
+
language: application.locale ?? "en",
|
|
90
|
+
get candidate() { return candidate; },
|
|
91
|
+
get check() { return latestCheck(); },
|
|
92
|
+
search: args => {
|
|
93
|
+
signal?.throwIfAborted();
|
|
94
|
+
const result = searchListenerProfileEvidence(input, args);
|
|
95
|
+
for (const item of result.items) inspected.add(item.reference_id);
|
|
96
|
+
return result;
|
|
97
|
+
},
|
|
98
|
+
evidence: async (referenceId, offset = 0) => {
|
|
99
|
+
signal?.throwIfAborted();
|
|
100
|
+
if (!Number.isSafeInteger(offset) || offset < 0) throw new TypeError("Invalid evidence offset");
|
|
101
|
+
const entry = input.evidence.get(referenceId);
|
|
102
|
+
if (!entry) throw new TypeError("Unknown profile evidence reference");
|
|
103
|
+
const result = modelEvidence(entry, offset);
|
|
104
|
+
if (entry.data.evidence_id) {
|
|
105
|
+
try {
|
|
106
|
+
const detail = await application.explainProfileEvidence({ evidenceId: entry.data.evidence_id });
|
|
107
|
+
// The prose basis restates the row's measures, including any this input leaves out as unrecorded.
|
|
108
|
+
result.explanation = { ...(input.unrecorded?.has(referenceId) ? {} : { basis: detail.basis_summary }),
|
|
109
|
+
limitation: detail.interpretation_limit ?? detail.limitation };
|
|
110
|
+
} catch { /* The frozen row remains the source for aggregate-only evidence. */ }
|
|
111
|
+
}
|
|
112
|
+
inspected.add(referenceId);
|
|
113
|
+
return result;
|
|
114
|
+
},
|
|
115
|
+
modelRetry: retry => { if (!signal?.aborted) onProgress({ ...progress(), model_retry: retry }); },
|
|
116
|
+
submit: value => {
|
|
117
|
+
signal?.throwIfAborted();
|
|
118
|
+
if (!["synthesis", "revision"].includes(session.phase)) throw new Error("Submit a profile only while writing or revising it");
|
|
119
|
+
if (stageFinished) throw new Error("Submit one complete profile per session");
|
|
120
|
+
const next = compileListenerProfile(input, value);
|
|
121
|
+
const nextCandidate = listenerProfileCandidate(input, next);
|
|
122
|
+
save({ ...finalization, draft: { summary: nextCandidate.summary, insights: nextCandidate.insights },
|
|
123
|
+
revisions: finalization.revisions + Number(session.phase === "revision") }, () => {
|
|
124
|
+
compiled = next;
|
|
125
|
+
candidate = nextCandidate;
|
|
126
|
+
});
|
|
127
|
+
return { state: "draft_saved", next: "check" };
|
|
128
|
+
},
|
|
129
|
+
record: value => {
|
|
130
|
+
signal?.throwIfAborted();
|
|
131
|
+
if (session.phase !== "check" || stageFinished) throw new Error("Record one check in the separate check session");
|
|
132
|
+
const check = validateListenerProfileCheck(candidate, value);
|
|
133
|
+
save({ ...finalization, checks: [...finalization.checks, check] });
|
|
134
|
+
return { verdict: check.verdict, issues: check.issues.length };
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
const abort = () => runtime.abort?.();
|
|
138
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
139
|
+
try {
|
|
140
|
+
signal?.throwIfAborted();
|
|
141
|
+
onProgress(progress());
|
|
142
|
+
let sessions = 0;
|
|
143
|
+
while (phase() !== "complete") {
|
|
144
|
+
signal?.throwIfAborted();
|
|
145
|
+
session.phase = phase();
|
|
146
|
+
stageFinished = false;
|
|
147
|
+
if (++sessions > MAX_SESSIONS) {
|
|
148
|
+
throw new Error("The profile build reached its session limit. Your previous reading and the saved draft are kept. Use /profile build to continue.");
|
|
149
|
+
}
|
|
150
|
+
const inspectedBefore = inspected.size;
|
|
151
|
+
await runtime.investigate(session);
|
|
152
|
+
signal?.throwIfAborted();
|
|
153
|
+
assertCurrentInput();
|
|
154
|
+
if (!stageFinished && inspected.size === inspectedBefore) {
|
|
155
|
+
throw new Error("The profile model stopped making progress. Your previous reading and any saved draft are kept. Use /profile build to retry.");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const check = latestCheck();
|
|
159
|
+
store.complete(build, { ...compiled, verification: {
|
|
160
|
+
version: LISTENER_PROFILE_CHECK_VERSION,
|
|
161
|
+
state: check.verdict === "pass" ? (finalization.revisions ? "revised" : "passed") : "flagged",
|
|
162
|
+
revisions: finalization.revisions, checks: finalization.checks,
|
|
163
|
+
} }, assertCurrentInput);
|
|
164
|
+
return { state: "ready", revision: await application.getListenerProfile(), ...progress() };
|
|
165
|
+
} catch (error) {
|
|
166
|
+
store.interrupt(build);
|
|
167
|
+
throw error;
|
|
168
|
+
} finally {
|
|
169
|
+
signal?.removeEventListener("abort", abort);
|
|
170
|
+
runtime.abort?.();
|
|
171
|
+
}
|
|
172
|
+
}
|