feat(assistant): #91 steps humains en direct + outils web_search/fetch_url (labels backend, SSE step, garde SSRF)
CI / lint (push) Successful in 1m22s
CI / security (push) Successful in 55s
CI / test (push) Successful in 3m0s
CI / build (push) Successful in 51s
CI / e2e (push) Successful in 10m41s

This commit is contained in:
2026-09-15 17:46:51 -04:00
parent 18b7dee2f6
commit 19d5b931c4
14 changed files with 745 additions and 32 deletions
+41 -16
View File
@@ -2058,22 +2058,35 @@ class BooksLM {
// ── Tool activity & confirmations (agent mode) ──────────────────────
/** Agent tool calls collapse into a discreet “N steps” line (Notion-style). */
_renderToolActivity(toolCalls) {
const wrap = document.createElement('details');
wrap.className = 'bookslm-tool-trace';
const summary = document.createElement('summary');
summary.textContent = t('ai.steps_count', { count: toolCalls.length });
wrap.appendChild(summary);
for (const call of toolCalls) {
const line = document.createElement('div');
line.className = 'bookslm-tool-line' + (call.ok === false ? ' failed' : '');
line.textContent = `${call.ok === false ? '⚠' : '🔧'} ${t('ai.tool_call', { name: call.name })}`;
if (call.ok === false) line.title = t('ai.tool_call_failed', { name: call.name });
wrap.appendChild(line);
/** Human one-liner for a recorded step (backend label key or legacy name). */
_stepText(call) {
const step = call.step;
if (step && step.key) {
const key = `ai.step.${step.key}`;
const label = t(key, step.params || {});
if (label && label !== key) return label;
}
return t('ai.tool_call', { name: call.name });
}
/** Agent tool calls collapse into a discreet “N steps” line (Notion-style). */
_renderToolActivity(toolCalls) {
const wrap = document.createElement('details');
wrap.className = 'bookslm-tool-trace';
const summary = document.createElement('summary');
const countKey = toolCalls.length > 1 ? 'ai.steps_count_plural' : 'ai.steps_count';
summary.textContent = t(countKey, { count: toolCalls.length });
wrap.appendChild(summary);
for (const call of toolCalls) {
const line = document.createElement('div');
line.className = 'bookslm-tool-line' + (call.ok === false ? ' failed' : '');
line.textContent = `${call.ok === false ? '⚠' : '·'} ${this._stepText(call)}`;
if (call.ok === false) line.title = t('ai.tool_call_failed', { name: call.name });
else if (call.step && call.step.params && call.step.params.value) line.title = call.step.params.value;
wrap.appendChild(line);
}
return wrap;
}
return wrap;
}
_renderConfirmationCard(msg) {
const conf = msg.confirmation;
@@ -2393,10 +2406,22 @@ class BooksLM {
}
if (currentEvent === 'tool') {
assistantMsg.toolCalls.push({ name: data.name, ok: data.ok !== false, arguments: data.arguments || {} });
assistantMsg.toolCalls.push({
name: data.name,
ok: data.ok !== false,
arguments: data.arguments || {},
step: data.step || null,
});
this._setActivity('working', t('ai.activity_tool', { name: data.name }));
return;
}
// Intermediate reasoning note streamed by the agent (Notion-style
// ". thought" line inside the steps block).
if (currentEvent === 'step' && data.step) {
assistantMsg.toolCalls.push({ name: '', ok: true, arguments: {}, step: data.step });
this._setActivity('working', t('ai.activity_thinking'));
return;
}
if (currentEvent === 'confirmation') {
assistantMsg.confirmation = {
pending: data.pending || data,
+31 -1
View File
@@ -1780,7 +1780,37 @@
"bookslm.insert_hint": "Append the answer to the document open in the editor",
"bookslm.inserted": "Answer added to the document",
"bookslm.insert_no_editor": "No document open in the editor",
"ai.steps_count": "{count} steps",
"ai.steps_count": "{count} step",
"ai.steps_count_plural": "{count} steps",
"ai.activity_thinking": "Thinking…",
"ai.step.thought": "Thought: {value}",
"ai.step.backlinks": "Analyzed backlinks",
"ai.step.backup_diff": "Compared backup: {value}",
"ai.step.backup_restore": "Restored backup: {value}",
"ai.step.backups": "Listed backups",
"ai.step.dir_create": "Proposed directory: {value}",
"ai.step.dir_delete": "Deleted directory: {value}",
"ai.step.dir_rename": "Renamed directory: {value}",
"ai.step.directory": "Explored directory: {value}",
"ai.step.file_append": "Appended to file: {value}",
"ai.step.file_create": "Proposed file: {value}",
"ai.step.file_delete": "Deleted file: {value}",
"ai.step.file_edit": "Edited file: {value}",
"ai.step.file_read": "Read file: {value}",
"ai.step.file_rename": "Renamed file: {value}",
"ai.step.files": "Listed files",
"ai.step.generic": "Used tool: {tool}",
"ai.step.graph": "Consulted the vault graph",
"ai.step.move": "Moved: {value}",
"ai.step.recent": "Listed recent files",
"ai.step.replace": "Replacements: {value}",
"ai.step.search": "Searched the vault: {value}",
"ai.step.search_paths": "Searched paths: {value}",
"ai.step.tags": "Listed tags",
"ai.step.tags_suggest": "Suggested tags for {value}",
"ai.step.vaults": "Listed the vaults",
"ai.step.fetch_url": "Opened a web page: {value}",
"ai.step.web_search": "Searched the web: {value}",
"bookslm.copied": "Copied to clipboard",
"bookslm.error": "AI service error",
"bookslm.regenerate": "Regenerate",
+31 -1
View File
@@ -1780,7 +1780,37 @@
"bookslm.insert_hint": "Ajouter la réponse au document ouvert dans l'éditeur",
"bookslm.inserted": "Réponse ajoutée au document",
"bookslm.insert_no_editor": "Aucun document ouvert dans l'éditeur",
"ai.steps_count": "{count} étapes",
"ai.steps_count": "{count} étape",
"ai.steps_count_plural": "{count} étapes",
"ai.activity_thinking": "Réflexion…",
"ai.step.thought": "Réflexion : {value}",
"ai.step.backlinks": "Backlinks analysés",
"ai.step.backup_diff": "Comparaison de sauvegarde : {value}",
"ai.step.backup_restore": "Sauvegarde restaurée : {value}",
"ai.step.backups": "Sauvegardes consultées",
"ai.step.dir_create": "Dossier proposé : {value}",
"ai.step.dir_delete": "Dossier supprimé : {value}",
"ai.step.dir_rename": "Dossier renommé : {value}",
"ai.step.directory": "Répertoire exploré : {value}",
"ai.step.file_append": "Fichier complété : {value}",
"ai.step.file_create": "Fichier proposé : {value}",
"ai.step.file_delete": "Fichier supprimé : {value}",
"ai.step.file_edit": "Fichier modifié : {value}",
"ai.step.file_read": "Fichier lu : {value}",
"ai.step.file_rename": "Fichier renommé : {value}",
"ai.step.files": "Fichiers listés",
"ai.step.generic": "Outil utilisé : {tool}",
"ai.step.graph": "Graphe du vault consulté",
"ai.step.move": "Élément déplacé : {value}",
"ai.step.recent": "Fichiers récents consultés",
"ai.step.replace": "Remplacements : {value}",
"ai.step.search": "Recherche dans le vault : {value}",
"ai.step.search_paths": "Chemins recherchés : {value}",
"ai.step.tags": "Tags consultés",
"ai.step.tags_suggest": "Tags suggérés pour {value}",
"ai.step.vaults": "Liste des vaults consultée",
"ai.step.fetch_url": "Page web consultée : {value}",
"ai.step.web_search": "Recherche sur le web : {value}",
"bookslm.copied": "Réponse copiée dans le presse-papiers",
"bookslm.error": "Erreur du service AI",
"bookslm.regenerate": "Régénérer",