mirror of
https://github.com/vinta/awesome-python.git
synced 2026-06-28 11:52:16 +00:00
fix: patch all 10 code-review findings in agrifine-extension
- agent.js: add anthropic-dangerous-direct-browser-access header to _callAPI (root cause of 401 CORS errors in AgriAgent tab) - agent.js: handle stop_reason=max_tokens alongside end_turn - ag-refine/index.js: reset isRunning in a try/catch around agent.run() - tools.js: check res.ok before parsing JSON in toolLookupUSDAsoil - tools.js: guard p.name?.toLowerCase() against null field names - field-profile/index.js: guard both lat and lon before toFixed() - reading-list/index.js: escapeHtml + safeHref to prevent XSS from page titles/urls - dashboard/index.js: escapeHtml AI answer and all dynamic list content - data-ingest/index.js: add .xlsx/.xls extension fallbacks for missing MIME type - storage.js: check chrome.runtime.lastError in all four storage helpers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KBD2dN2KEjzz3UQFa9hEpu
This commit is contained in:
parent
b7b832764d
commit
3185b04ed6
8 changed files with 67 additions and 24 deletions
|
|
@ -61,8 +61,7 @@ export class AgrifineAgent {
|
|||
// Append assistant turn
|
||||
messages.push({ role: 'assistant', content: response.content });
|
||||
|
||||
if (response.stop_reason === 'end_turn') {
|
||||
// Extract final text
|
||||
if (response.stop_reason === 'end_turn' || response.stop_reason === 'max_tokens') {
|
||||
const text = response.content
|
||||
.filter((b) => b.type === 'text')
|
||||
.map((b) => b.text)
|
||||
|
|
@ -113,6 +112,7 @@ export class AgrifineAgent {
|
|||
'Content-Type': 'application/json',
|
||||
'x-api-key': apiKey,
|
||||
'anthropic-version': '2023-06-01',
|
||||
'anthropic-dangerous-direct-browser-access': 'true',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -153,7 +153,15 @@ export function AgRefineModule() {
|
|||
},
|
||||
});
|
||||
|
||||
await agent.run(userText);
|
||||
try {
|
||||
await agent.run(userText);
|
||||
} catch (err) {
|
||||
const idx = messages.findIndex((m) => m.id === thinkingId);
|
||||
if (idx >= 0) messages.splice(idx, 1);
|
||||
messages.push({ role: 'error', text: err.message });
|
||||
isRunning = false;
|
||||
this._renderMessages(container);
|
||||
}
|
||||
},
|
||||
|
||||
_renderMessages(container) {
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ async function toolGetReadingList({ tag } = {}) {
|
|||
async function toolGetFieldProfiles({ field_name } = {}) {
|
||||
const profiles = await getFieldProfiles();
|
||||
const filtered = field_name
|
||||
? profiles.filter((p) => p.name.toLowerCase().includes(field_name.toLowerCase()))
|
||||
? profiles.filter((p) => p.name?.toLowerCase().includes(field_name.toLowerCase()))
|
||||
: profiles;
|
||||
return {
|
||||
count: filtered.length,
|
||||
|
|
@ -260,6 +260,7 @@ async function toolLookupUSDAsoil({ latitude, longitude }) {
|
|||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: `request=query&query=${encodeURIComponent(query)}&format=JSON`,
|
||||
});
|
||||
if (!res.ok) throw new Error(`USDA SDA API ${res.status}`);
|
||||
const data = await res.json();
|
||||
const rows = data.Table ?? [];
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import {
|
|||
} from '../../utils/storage.js';
|
||||
import { callAnthropic } from '../../utils/api.js';
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str ?? '')
|
||||
.replace(/&/g, '&').replace(/</g, '<')
|
||||
.replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
const CATEGORIES = ['all', 'land', 'equipment', 'harvest', 'finance', 'carbon', 'weather'];
|
||||
|
||||
function tagCategory(item) {
|
||||
|
|
@ -118,7 +124,7 @@ export function DashboardModule() {
|
|||
maxTokens: 512,
|
||||
});
|
||||
|
||||
answerEl.innerHTML = `<p class="font-medium text-agri-700 mb-1">Answer</p>${answer}`;
|
||||
answerEl.innerHTML = `<p class="font-medium text-agri-700 mb-1">Answer</p><span class="whitespace-pre-wrap">${escapeHtml(answer)}</span>`;
|
||||
} catch (err) {
|
||||
answerEl.textContent = `Error: ${err.message}`;
|
||||
} finally {
|
||||
|
|
@ -167,11 +173,11 @@ export function DashboardModule() {
|
|||
<div class="flex items-start gap-2">
|
||||
<span class="text-lg flex-shrink-0">${sourceIcon}</span>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-semibold text-gray-800 truncate">${title}</p>
|
||||
${sub ? `<p class="text-xs text-gray-500 mt-0.5 leading-relaxed line-clamp-2">${sub}</p>` : ''}
|
||||
<p class="text-sm font-semibold text-gray-800 truncate">${escapeHtml(title)}</p>
|
||||
${sub ? `<p class="text-xs text-gray-500 mt-0.5 leading-relaxed line-clamp-2">${escapeHtml(sub)}</p>` : ''}
|
||||
<div class="flex items-center gap-2 mt-1.5">
|
||||
<span class="tag-pill bg-earth-100 text-earth-700">${item._category}</span>
|
||||
${(item.tags ?? []).filter((t) => t !== item._category).slice(0, 2).map((t) => `<span class="tag-pill">${t}</span>`).join('')}
|
||||
<span class="tag-pill bg-earth-100 text-earth-700">${escapeHtml(item._category)}</span>
|
||||
${(item.tags ?? []).filter((t) => t !== item._category).slice(0, 2).map((t) => `<span class="tag-pill">${escapeHtml(t)}</span>`).join('')}
|
||||
${date ? `<span class="text-xs text-gray-300">${new Date(date).toLocaleDateString()}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,10 @@ export function DataIngestModule() {
|
|||
|
||||
async _processFile(file, container) {
|
||||
const status = container.querySelector('#ingest-status');
|
||||
const typeName = SUPPORTED_TYPES[file.type] ?? (file.name.endsWith('.csv') ? 'CSV' : null);
|
||||
const typeName = SUPPORTED_TYPES[file.type]
|
||||
?? (file.name.endsWith('.csv') ? 'CSV'
|
||||
: (file.name.endsWith('.xlsx') || file.name.endsWith('.xls')) ? 'Excel'
|
||||
: null);
|
||||
|
||||
if (!typeName) {
|
||||
status.textContent = 'Unsupported file type.';
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ export function FieldProfileModule() {
|
|||
|
||||
<!-- Expanded detail -->
|
||||
<div class="fp-detail ${expandedId === p.id ? '' : 'hidden'} mt-3 pt-3 border-t border-gray-100 text-xs text-gray-600 space-y-1">
|
||||
${p.coordinates?.lat ? `<p>📍 ${p.coordinates.lat.toFixed(4)}, ${p.coordinates.lon.toFixed(4)}</p>` : ''}
|
||||
${p.coordinates?.lat != null && p.coordinates?.lon != null ? `<p>📍 ${p.coordinates.lat.toFixed(4)}, ${p.coordinates.lon.toFixed(4)}</p>` : ''}
|
||||
${p.notes ? `<p>📝 ${p.notes}</p>` : ''}
|
||||
<p class="text-gray-300">Weather data: <span class="coming-soon">Phase 6</span></p>
|
||||
<p class="text-gray-300">Carbon potential: <span class="coming-soon">Phase 7</span></p>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,19 @@
|
|||
import { getReadingList, saveReadingItem, deleteReadingItem } from '../../utils/storage.js';
|
||||
import { callAnthropic, AGRICULTURE_TAGS } from '../../utils/api.js';
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str ?? '')
|
||||
.replace(/&/g, '&').replace(/</g, '<')
|
||||
.replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function safeHref(url) {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
return (u.protocol === 'https:' || u.protocol === 'http:') ? escapeHtml(url) : '#';
|
||||
} catch (_) { return '#'; }
|
||||
}
|
||||
|
||||
export function ReadingListModule() {
|
||||
let currentTag = 'all';
|
||||
|
||||
|
|
@ -126,18 +139,18 @@ export function ReadingListModule() {
|
|||
}
|
||||
|
||||
listEl.innerHTML = filtered.map((item) => `
|
||||
<div class="agri-card" data-id="${item.id}">
|
||||
<div class="agri-card" data-id="${escapeHtml(item.id)}">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<a href="${item.url}" target="_blank" class="text-sm font-semibold text-agri-700 hover:underline leading-snug flex-1">${item.title}</a>
|
||||
<button class="rl-delete-btn text-gray-300 hover:text-red-400 transition flex-shrink-0" data-id="${item.id}" title="Remove">
|
||||
<a href="${safeHref(item.url)}" target="_blank" rel="noopener noreferrer" class="text-sm font-semibold text-agri-700 hover:underline leading-snug flex-1">${escapeHtml(item.title)}</a>
|
||||
<button class="rl-delete-btn text-gray-300 hover:text-red-400 transition flex-shrink-0" data-id="${escapeHtml(item.id)}" title="Remove">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
${item.summary ? `<p class="text-xs text-gray-500 mt-1.5 leading-relaxed">${item.summary}</p>` : ''}
|
||||
${item.summary ? `<p class="text-xs text-gray-500 mt-1.5 leading-relaxed">${escapeHtml(item.summary)}</p>` : ''}
|
||||
<div class="mt-2">
|
||||
${(item.tags ?? []).map((t) => `<span class="tag-pill">${t}</span>`).join('')}
|
||||
${(item.tags ?? []).map((t) => `<span class="tag-pill">${escapeHtml(t)}</span>`).join('')}
|
||||
</div>
|
||||
<p class="text-xs text-gray-300 mt-2">${new Date(item.savedAt).toLocaleDateString()}</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,26 +22,38 @@ export const KEYS = {
|
|||
// ── Generic helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
export async function localGet(key) {
|
||||
return new Promise((resolve) => {
|
||||
chrome.storage.local.get(key, (result) => resolve(result[key] ?? null));
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.storage.local.get(key, (result) => {
|
||||
if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); return; }
|
||||
resolve(result[key] ?? null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function localSet(key, value) {
|
||||
return new Promise((resolve) => {
|
||||
chrome.storage.local.set({ [key]: value }, resolve);
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.storage.local.set({ [key]: value }, () => {
|
||||
if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); return; }
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function sessionGet(key) {
|
||||
return new Promise((resolve) => {
|
||||
chrome.storage.session.get(key, (result) => resolve(result[key] ?? null));
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.storage.session.get(key, (result) => {
|
||||
if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); return; }
|
||||
resolve(result[key] ?? null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function sessionSet(key, value) {
|
||||
return new Promise((resolve) => {
|
||||
chrome.storage.session.set({ [key]: value }, resolve);
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.storage.session.set({ [key]: value }, () => {
|
||||
if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); return; }
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue