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
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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user