Stop publishing repository-wide prompt ledgers and enforce per-user prompt isolation through authenticated Gitea IDs.
Hop-State: A_06FN8NFDRFAFN5VCJTHJAAG Hop-Proposal: R_06FN8NEGJ054CPMJF9ZAN70 Hop-Task: T_06FN8HT6XG332C3XY6PYCCG Hop-Attempt: AT_06FN8HT6XGRXW0B4WPFFBJG
This commit is contained in:
@@ -44,6 +44,9 @@ docker compose --env-file "$env_file" cp \
|
||||
docker compose --env-file "$env_file" cp \
|
||||
deploy/gitea/public/assets/js/hop-native.js \
|
||||
gitea:/data/gitea/public/assets/js/hop-native-v16.js
|
||||
docker compose --env-file "$env_file" cp \
|
||||
deploy/gitea/public/assets/js/hop-native.js \
|
||||
gitea:/data/gitea/public/assets/js/hop-native-v17.js
|
||||
docker compose --env-file "$env_file" cp \
|
||||
deploy/gitea/public/assets/css/hop-home.css \
|
||||
gitea:/data/gitea/public/assets/css/hop-home.css
|
||||
|
||||
@@ -184,45 +184,23 @@
|
||||
return details;
|
||||
}
|
||||
|
||||
async function portablePromptRecords(path) {
|
||||
async function personalPromptRecords(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
|
||||
.filter((prompt) => {
|
||||
const request = String(prompt.prompt || '');
|
||||
const outcome = String(prompt.response_summary || '');
|
||||
return prompt.status !== 'active'
|
||||
&& prompt.status !== 'running'
|
||||
&& !request.startsWith('Resolve proposal ')
|
||||
&& !outcome.startsWith('hop-reconciliation-conflicts:');
|
||||
})
|
||||
.sort((left, right) => String(right.created_at).localeCompare(String(left.created_at)));
|
||||
}
|
||||
return [];
|
||||
const query = new URLSearchParams({owner, repo});
|
||||
const response = await fetch(`/hop/api/v1/prompts?${query}`, {credentials: 'same-origin'});
|
||||
if (response.status === 401) throw new Error('Sign in to view your prompt history.');
|
||||
if (!response.ok) throw new Error('Could not load your prompt history.');
|
||||
const payload = await response.json();
|
||||
return (Array.isArray(payload.prompts) ? payload.prompts : [])
|
||||
.filter((prompt) => {
|
||||
const request = String(prompt.prompt || '');
|
||||
const outcome = String(prompt.response_summary || '');
|
||||
return prompt.status !== 'active'
|
||||
&& prompt.status !== 'running'
|
||||
&& !request.startsWith('Resolve proposal ')
|
||||
&& !outcome.startsWith('hop-reconciliation-conflicts:');
|
||||
})
|
||||
.sort((left, right) => String(right.created_at).localeCompare(String(left.created_at)));
|
||||
}
|
||||
|
||||
function renderPromptPage() {
|
||||
@@ -244,7 +222,7 @@
|
||||
heading.append(
|
||||
makeElement('p', 'hop-prompts__eyebrow', 'Hop causal record'),
|
||||
makeElement('h1', '', 'Prompts'),
|
||||
makeElement('p', 'hop-prompts__lede', 'Review what you requested, which agent acted, and the proposal that resulted. Internal agent instructions are not published.'),
|
||||
makeElement('p', 'hop-prompts__lede', 'Review your requests for this repository. Prompt history is private to your signed-in account.'),
|
||||
);
|
||||
heading.querySelector('h1').id = 'hop-prompts-title';
|
||||
const refresh = makeElement('button', 'ui button hop-prompts__refresh', 'Refresh');
|
||||
@@ -260,27 +238,18 @@
|
||||
status.textContent = 'Loading prompt records…';
|
||||
page.querySelector('.hop-prompt-list, .hop-prompts__empty')?.remove();
|
||||
try {
|
||||
const prompts = await portablePromptRecords(path);
|
||||
const prompts = await personalPromptRecords(path);
|
||||
if (!prompts.length) {
|
||||
status.textContent = 'No published prompt records';
|
||||
status.textContent = 'No prompts for your account';
|
||||
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.'),
|
||||
makeElement('strong', '', 'You have no prompt history for this repository.'),
|
||||
document.createTextNode('Only requests attributed to your signed-in Gitea account appear here.'),
|
||||
);
|
||||
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', '', '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;
|
||||
}
|
||||
const list = makeElement('div', 'hop-prompt-list');
|
||||
prompts.forEach((prompt) => list.append(promptRow(prompt)));
|
||||
page.append(list);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/hop-home.css?v=4">
|
||||
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/hop-prompts.css?v=4">
|
||||
<script src="{{AssetUrlPrefix}}/js/hop-native-v16.js?v=1"></script>
|
||||
<script src="{{AssetUrlPrefix}}/js/hop-native-v17.js?v=1"></script>
|
||||
|
||||
Reference in New Issue
Block a user