Commit ddae723
Changed files (3)
lib
elelem
server
spec
elelem
server
lib/elelem/server/web_terminal.rb
@@ -23,10 +23,10 @@ module Elelem
end
def ask(prompt)
- id = @prompt_counter += 1
+ queue = Queue.new
+ id = @mutex.synchronize { @prompts[@prompt_counter += 1] = queue; @prompt_counter }
emit("prompt", prompt, id: id)
- @prompts[id] = Queue.new
- @prompts[id].pop
+ queue.pop
end
def thinking(text)
@@ -59,9 +59,11 @@ module Elelem
end
def answer(id, text)
- if @prompts.key?(id)
- @prompts[id] << text
- @prompts.delete(id)
+ @mutex.synchronize do
+ if @prompts.key?(id)
+ @prompts[id] << text
+ @prompts.delete(id)
+ end
end
end
@@ -75,7 +77,7 @@ module Elelem
plain = text.to_s.gsub(/\e\[[0-9;]*[a-zA-Z]/, "")
return if plain.strip.empty?
- broadcast(type: type, text: plain, id: id)
+ broadcast({ type: type, text: plain, id: id }.compact)
end
def broadcast(event)
lib/elelem/server.rb
@@ -173,11 +173,12 @@ module Elelem
scroll(was);
}
- function askPermission(text) {
+ function askPermission(id, text) {
current = null;
const was = atBottom();
const el = document.createElement("div");
el.className = "prompt";
+ el.dataset.id = id;
el.innerHTML = "<div>" + escape(text) + "</div>";
const respond = (answer) => {
el.querySelectorAll("button").forEach((b) => b.remove());
@@ -195,15 +196,13 @@ module Elelem
el.append(allow, deny);
log.appendChild(el);
scroll(was);
- // Store the ID for this prompt
- el.dataset.id = Date.now() + Math.random();
}
new EventSource("/events").onmessage = (e) => {
const event = JSON.parse(e.data);
if (event.type === "content") append("assistant", event.text);
else if (event.type === "thinking") append("thinking", event.text);
- else if (event.type === "prompt") askPermission(event.text);
+ else if (event.type === "prompt") askPermission(event.id, event.text);
else if (event.type === "done") current = null;
};
spec/elelem/server/web_terminal_spec.rb
@@ -101,10 +101,29 @@ RSpec.describe Elelem::Server::WebTerminal do
end
describe "#ask" do
- it "emits a prompt and blocks until answered" do
+ it "emits a prompt with an id and blocks until answered with a matching id" do
thread = Thread.new { subject.ask("Allow?") }
- expect(events.pop).to eq({ type: "prompt", text: "Allow?" })
- subject.answer("n")
+ event = events.pop
+ expect(event).to include(type: "prompt", text: "Allow?")
+ subject.answer(event[:id], "n")
+ expect(thread.value).to eq("n")
+ end
+
+ it "ignores an answer for an id that isn't pending" do
+ thread = Thread.new { subject.ask("Allow?") }
+ event = events.pop
+ subject.answer(event[:id] + 1, "n")
+ subject.answer(event[:id], "y")
+ expect(thread.value).to eq("y")
+ end
+
+ it "does not let an answer sent before any prompt pre-approve the next prompt" do
+ subject.answer(1, "y")
+
+ thread = Thread.new { subject.ask("Allow?") }
+ event = events.pop
+ subject.answer(event[:id], "n")
+
expect(thread.value).to eq("n")
end
end