Reconcile portable Hop prompt records with the latest accepted repository state

Hop-State: A_06FN6X5SEFS9VB6A92K2JE0
Hop-Proposal: R_06FN6X45KVCRM0P2G9SF9N0
Hop-Task: T_06FN6Q6CPY9NYPA25F6DKS8
Hop-Attempt: AT_06FN6WXQF509ZK786R6N7Z0
This commit is contained in:
Hop
2026-07-11 16:13:06 -07:00
parent dfadc67175
commit b04e24a543
77 changed files with 1262 additions and 11 deletions
+45 -7
View File
@@ -244,6 +244,38 @@
return details;
}
async function portablePromptRecords(path) {
const [owner, repo] = path.slice(1).split('/');
let branches = ['main', 'master'];
try {
const repository = await fetch(`/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, {credentials: 'same-origin'});
if (repository.ok) {
const payload = await repository.json();
if (payload.default_branch) branches = [payload.default_branch, ...branches.filter((branch) => branch !== payload.default_branch)];
}
} catch (_) {
// The raw-file fallback below still works for the common default branches.
}
for (const branch of branches) {
const treeResponse = await fetch(`/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/git/trees/${encodeURIComponent(branch)}?recursive=true`, {credentials: 'same-origin'});
if (treeResponse.status === 404) continue;
if (!treeResponse.ok) throw new Error('Could not inspect the published prompt records');
const tree = await treeResponse.json();
const records = (Array.isArray(tree.tree) ? tree.tree : [])
.filter((entry) => entry.type === 'blob' && /^\.hop\/records\/prompts\/[^/]+\.json$/.test(entry.path));
if (!records.length) return [];
const prompts = await Promise.all(records.map(async (entry) => {
const recordPath = entry.path.split('/').map(encodeURIComponent).join('/');
const response = await fetch(`${path}/raw/branch/${encodeURIComponent(branch)}/${recordPath}`, {credentials: 'same-origin'});
if (!response.ok) throw new Error('Could not load a published prompt record');
return response.json();
}));
return prompts.sort((left, right) => String(right.created_at).localeCompare(String(left.created_at)));
}
return [];
}
function renderPromptPage() {
if (!promptsPage()) return;
const main = document.querySelector('[role="main"].page-content.repository.issue-list');
@@ -279,17 +311,23 @@
status.textContent = 'Loading prompt records…';
page.querySelector('.hop-prompt-list, .hop-prompts__empty')?.remove();
try {
const [owner, repo] = path.slice(1).split('/');
const response = await fetch(`/hop/api/v1/prompts?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}`, {credentials: 'same-origin'});
const payload = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(payload?.error?.message || 'Could not load prompt records');
const prompts = Array.isArray(payload.prompts) ? payload.prompts : [];
const prompts = await portablePromptRecords(path);
if (!prompts.length) {
status.textContent = 'No published prompt records';
const empty = makeElement('div', 'hop-prompts__empty');
empty.append(
makeElement('strong', '', 'No prompts have been published yet.'),
document.createTextNode('Hop publishes an immutable record for each prompt when it creates a proposal. Those files travel with the repository and appear here after the push.'),
);
page.append(empty);
return;
}
status.textContent = prompts.length === 1 ? '1 captured request' : `${prompts.length} captured requests`;
if (!prompts.length) {
const empty = makeElement('div', 'hop-prompts__empty');
empty.append(
makeElement('strong', '', 'No prompts have been reported yet.'),
document.createTextNode('When an agent starts work through Hop, its request, execution metadata, and final outcome will appear here.'),
makeElement('strong', '', 'The published prompt record set is empty.'),
document.createTextNode('Capture work through Hop and create a proposal to publish its prompt records.'),
);
page.append(empty);
return;