mirror of
https://github.com/binhong87/obsidian-note-agent.git
synced 2026-07-22 06:52:10 +00:00
- Agent loop no longer awaits per-write approval; queues pending writes
and returns { status: "queued" } to the LLM so multi-step edits finish
in one turn. Diffs render inline under the assistant message that
produced them, with per-block Approve/Reject and a bulk Apply All /
Reject All footer.
- ApprovalQueue rewritten as a synchronous pending-writes store with an
injected commit callback; clear() drops unresolved writes on view
close or new conversation; scheduled (no-UI) runs auto-approveAll
after the loop drains.
- history-trimmer keeps assistant-with-tool_calls + its tool responses
as one atomic group so the provider never sees a tool message without
its preceding tool_calls (fixes DeepSeek 400 on large tool bursts).
- Add pulsing thinking-dots indicator in MessageList whenever the turn
is busy and streamBuf is empty, covering network latency, tool-call
argument accumulation, and inter-iteration gaps.
- Drop [openai] console logs (errors/warns retained); remove superfluous
aria-labels and title tooltips from chat UI.
- Add CLAUDE.md.
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
62 lines
2.5 KiB
TypeScript
62 lines
2.5 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { ApprovalQueue } from "../../src/agent/approval-queue";
|
|
|
|
describe("ApprovalQueue", () => {
|
|
it("enqueue is synchronous, stores entry, fires onChange", () => {
|
|
const commit = vi.fn(async () => {});
|
|
const q = new ApprovalQueue({ commit });
|
|
const changes: number[] = [];
|
|
q.onChange(list => changes.push(list.length));
|
|
q.enqueue({ toolCallId: "1", tool: "edit_note", args: { path: "a.md" }, diff: "" });
|
|
expect(q.list()).toHaveLength(1);
|
|
expect(changes).toEqual([1]);
|
|
expect(commit).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("approve calls commit, removes entry, fires onChange", async () => {
|
|
const commit = vi.fn(async () => {});
|
|
const q = new ApprovalQueue({ commit });
|
|
q.enqueue({ toolCallId: "1", tool: "edit_note", args: { path: "a.md" }, diff: "" });
|
|
await q.approve("1");
|
|
expect(commit).toHaveBeenCalledWith(expect.objectContaining({ toolCallId: "1" }));
|
|
expect(q.list()).toHaveLength(0);
|
|
});
|
|
|
|
it("reject removes entry without calling commit", () => {
|
|
const commit = vi.fn(async () => {});
|
|
const q = new ApprovalQueue({ commit });
|
|
q.enqueue({ toolCallId: "2", tool: "edit_note", args: {}, diff: "" });
|
|
q.reject("2");
|
|
expect(commit).not.toHaveBeenCalled();
|
|
expect(q.list()).toHaveLength(0);
|
|
});
|
|
|
|
it("approveAll drains queue and calls commit for each entry", async () => {
|
|
const commit = vi.fn(async () => {});
|
|
const q = new ApprovalQueue({ commit });
|
|
q.enqueue({ toolCallId: "a", tool: "edit_note", args: {}, diff: "" });
|
|
q.enqueue({ toolCallId: "b", tool: "create_note", args: {}, diff: "" });
|
|
await q.approveAll();
|
|
expect(commit).toHaveBeenCalledTimes(2);
|
|
expect(q.list()).toHaveLength(0);
|
|
});
|
|
|
|
it("rejectAll drains queue without calling commit", () => {
|
|
const commit = vi.fn(async () => {});
|
|
const q = new ApprovalQueue({ commit });
|
|
q.enqueue({ toolCallId: "a", tool: "edit_note", args: {}, diff: "" });
|
|
q.enqueue({ toolCallId: "b", tool: "create_note", args: {}, diff: "" });
|
|
q.rejectAll();
|
|
expect(commit).not.toHaveBeenCalled();
|
|
expect(q.list()).toHaveLength(0);
|
|
});
|
|
|
|
it("clear drops all entries without calling commit", () => {
|
|
const commit = vi.fn(async () => {});
|
|
const q = new ApprovalQueue({ commit });
|
|
q.enqueue({ toolCallId: "x", tool: "edit_note", args: {}, diff: "" });
|
|
q.clear();
|
|
expect(commit).not.toHaveBeenCalled();
|
|
expect(q.list()).toHaveLength(0);
|
|
});
|
|
});
|