feat(ai): services partages A2 + SSE streaming B4 + confirmations UI B5 (#79)
CI / lint (push) Successful in 58s
CI / security (push) Successful in 40s
CI / test (push) Successful in 1m15s
CI / build (push) Successful in 37s
CI / e2e (push) Successful in 10m15s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s

This commit is contained in:
2026-09-11 17:06:40 -04:00
parent c55e3e0cbc
commit 4c4e415975
27 changed files with 1465 additions and 386 deletions
+94
View File
@@ -513,6 +513,100 @@ async function main() {
assert.equal(b._messages[0].content, "Ancienne conversation");
});
// ── 14. Agent mode & confirmations (B5) ──
await test("agent mode toggle persists and switches the endpoint", async () => {
localStorage.clear();
const b = new BooksLM();
assert.equal(b._agentMode, false, "agent mode off by default");
b._toggleAgentMode();
assert.equal(b._agentMode, true);
assert.equal(localStorage.getItem("obsigate-bookslm-agent"), "true");
const b2 = new BooksLM();
assert.equal(b2._agentMode, true, "persisted across instances");
let url = null;
globalThis.fetch = async (u) => { url = String(u); return { ok: true, status: 200, json: async () => ({}) }; };
b2._abortCtrl = null;
await b2._postChat({ message: "x" });
assert.ok(url.includes("/api/ai/bookslm/agent"), "agent mode targets /agent");
b2._agentMode = false;
await b2._postChat({ message: "x" });
assert.ok(url.includes("/api/ai/bookslm/chat"), "default targets /chat");
localStorage.clear();
});
await test("header exposes the agent-mode toggle", () => {
const b = new BooksLM();
const panel = b._render();
b._panel = panel;
document.body.appendChild(panel);
const btn = panel.querySelector(".bookslm-btn-agent");
assert.ok(btn, "agent toggle button present");
assert.equal(btn.getAttribute("aria-pressed"), "false");
btn.click();
assert.equal(btn.getAttribute("aria-pressed"), "true");
panel.remove();
localStorage.clear();
});
await test("_lineDiff marks additions and deletions", () => {
const b = new BooksLM();
const diff = b._lineDiff("a\nb\nc", "a\nB\nc\nd");
const adds = diff.filter((l) => l.t === "+").map((l) => l.s);
const dels = diff.filter((l) => l.t === "-").map((l) => l.s);
assert.ok(dels.includes("b"), "removed line marked");
assert.ok(adds.includes("B"), "changed line marked as addition");
assert.ok(adds.includes("d"), "appended line marked");
});
await test("confirmation card resumes the agent run with confirm payload", async () => {
localStorage.clear();
const encoder = new TextEncoder();
let posted = null;
const sse =
'event: tool\ndata: {"name":"edit_file","ok":true,"arguments":{}}\n\n' +
'event: message\ndata: {"token":"Fait."}\n\n' +
"event: done\ndata: {}\n\n";
globalThis.fetch = async (url, opts) => {
if (String(url).includes("/api/ai/bookslm/agent")) {
posted = JSON.parse(opts.body);
const chunks = [encoder.encode(sse)];
const reader = { read: async () => (chunks.length ? { done: false, value: chunks.shift() } : { done: true }) };
return { ok: true, status: 200, body: { getReader: () => reader } };
}
return { ok: true, status: 200, json: async () => ({ raw: "old content" }) };
};
const b = new BooksLM();
b._agentMode = true;
b._panel = b._render();
document.body.appendChild(b._panel);
const msg = {
role: "assistant",
content: "",
sources: [],
toolCalls: [],
confirmation: {
pending: { error: { tool: "edit_file", arguments: { vault: "V", path: "a.md", content: "new content" }, id: "c1" } },
messages: [{ role: "system", content: "s" }],
},
payload: { message: "edit", vault: "V", mode: "general", conversation_history: [] },
};
b._messages = [msg];
await b._applyConfirmation(msg);
assert.equal(msg.confirmation, null, "confirmation resolved");
assert.ok(posted, "resumed via /agent");
assert.equal(posted.confirm.error.tool, "edit_file");
assert.deepEqual(posted.confirm_messages, [{ role: "system", content: "s" }]);
const cont = b._messages[b._messages.length - 1];
assert.equal(cont.content, "Fait.");
assert.equal(cont.toolCalls.length, 1, "tool trace captured");
b._panel.remove();
localStorage.clear();
});
// ── Summary ──
console.log(`\n${passCount}/${testCount} tests passed`);
if (passCount !== testCount) {