diff --git a/agrifine-extension/src/ag-refine/agent.js b/agrifine-extension/src/ag-refine/agent.js index 75af4ba..fc2f0a0 100644 --- a/agrifine-extension/src/ag-refine/agent.js +++ b/agrifine-extension/src/ag-refine/agent.js @@ -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), }); diff --git a/agrifine-extension/src/ag-refine/index.js b/agrifine-extension/src/ag-refine/index.js index d870629..a509734 100644 --- a/agrifine-extension/src/ag-refine/index.js +++ b/agrifine-extension/src/ag-refine/index.js @@ -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) { diff --git a/agrifine-extension/src/ag-refine/tools.js b/agrifine-extension/src/ag-refine/tools.js index 6d693cd..f47cf3a 100644 --- a/agrifine-extension/src/ag-refine/tools.js +++ b/agrifine-extension/src/ag-refine/tools.js @@ -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 { diff --git a/agrifine-extension/src/modules/dashboard/index.js b/agrifine-extension/src/modules/dashboard/index.js index 390d7db..bff6de4 100644 --- a/agrifine-extension/src/modules/dashboard/index.js +++ b/agrifine-extension/src/modules/dashboard/index.js @@ -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, '"'); +} + const CATEGORIES = ['all', 'land', 'equipment', 'harvest', 'finance', 'carbon', 'weather']; function tagCategory(item) { @@ -118,7 +124,7 @@ export function DashboardModule() { maxTokens: 512, }); - answerEl.innerHTML = `

Answer

${answer}`; + answerEl.innerHTML = `

Answer

${escapeHtml(answer)}`; } catch (err) { answerEl.textContent = `Error: ${err.message}`; } finally { @@ -167,11 +173,11 @@ export function DashboardModule() {
${sourceIcon}
-

${title}

- ${sub ? `

${sub}

` : ''} +

${escapeHtml(title)}

+ ${sub ? `

${escapeHtml(sub)}

` : ''}
- ${item._category} - ${(item.tags ?? []).filter((t) => t !== item._category).slice(0, 2).map((t) => `${t}`).join('')} + ${escapeHtml(item._category)} + ${(item.tags ?? []).filter((t) => t !== item._category).slice(0, 2).map((t) => `${escapeHtml(t)}`).join('')} ${date ? `${new Date(date).toLocaleDateString()}` : ''}
diff --git a/agrifine-extension/src/modules/data-ingest/index.js b/agrifine-extension/src/modules/data-ingest/index.js index dce5edb..79e4117 100644 --- a/agrifine-extension/src/modules/data-ingest/index.js +++ b/agrifine-extension/src/modules/data-ingest/index.js @@ -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.'; diff --git a/agrifine-extension/src/modules/field-profile/index.js b/agrifine-extension/src/modules/field-profile/index.js index 646f650..958b394 100644 --- a/agrifine-extension/src/modules/field-profile/index.js +++ b/agrifine-extension/src/modules/field-profile/index.js @@ -148,7 +148,7 @@ export function FieldProfileModule() {
- ${p.coordinates?.lat ? `

📍 ${p.coordinates.lat.toFixed(4)}, ${p.coordinates.lon.toFixed(4)}

` : ''} + ${p.coordinates?.lat != null && p.coordinates?.lon != null ? `

📍 ${p.coordinates.lat.toFixed(4)}, ${p.coordinates.lon.toFixed(4)}

` : ''} ${p.notes ? `

📝 ${p.notes}

` : ''}

Weather data: Phase 6

Carbon potential: Phase 7

diff --git a/agrifine-extension/src/modules/reading-list/index.js b/agrifine-extension/src/modules/reading-list/index.js index 8c3db7c..47af440 100644 --- a/agrifine-extension/src/modules/reading-list/index.js +++ b/agrifine-extension/src/modules/reading-list/index.js @@ -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, '"'); +} + +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) => ` -
+
- ${item.summary ? `

${item.summary}

` : ''} + ${item.summary ? `

${escapeHtml(item.summary)}

` : ''}
- ${(item.tags ?? []).map((t) => `${t}`).join('')} + ${(item.tags ?? []).map((t) => `${escapeHtml(t)}`).join('')}

${new Date(item.savedAt).toLocaleDateString()}

diff --git a/agrifine-extension/src/utils/storage.js b/agrifine-extension/src/utils/storage.js index f5f6b95..680a433 100644 --- a/agrifine-extension/src/utils/storage.js +++ b/agrifine-extension/src/utils/storage.js @@ -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(); + }); }); }