roger-roger 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roger-roger",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Bring yourself back to the conversation: your coding agents reach you by Slack, a sound or a spoken line when they finish, get stuck, or need a decision, and you answer from your phone.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "node": ">=18"
16
16
  },
17
17
  "scripts": {
18
- "test": "node --test",
18
+ "test": "node --test --import ./test/setup.mjs",
19
19
  "make-sounds": "node tools/make-sounds.mjs",
20
20
  "version": "node tools/sync-version.mjs && git add skills/roger-roger/herdr-plugin.toml"
21
21
  },
@@ -6,7 +6,7 @@
6
6
 
7
7
  id = "roger-roger"
8
8
  name = "roger-roger"
9
- version = "0.1.13"
9
+ version = "0.1.14"
10
10
  min_herdr_version = "0.9.0"
11
11
  description = "Slack, a sound or a spoken line from the agents in your panes, and questions you answer from your phone"
12
12
  platforms = ["linux", "macos", "windows"]
@@ -151,25 +151,28 @@ export function workFromTitle(title, agent = "") {
151
151
  return t;
152
152
  }
153
153
 
154
- // Herdr is asked for the title at most this often per session; a label follows the topic, not keystrokes.
155
- const TITLE_EVERY_MS = 60_000;
156
- const titleAsked = new Map();
157
-
158
- /** The work to label with: the `--session` name if the agent gave one, else the pane's title. */
159
- async function workFor(session, run, now = Date.now()) {
160
- if (str(session.name)) return str(session.name);
161
- const last = titleAsked.get(session.id);
162
- if (last && now - last.at < TITLE_EVERY_MS) return last.work;
163
- let work = "";
154
+ // Herdr is asked about the pane at most this often per session; a label follows the topic, not keystrokes.
155
+ const ASK_EVERY_MS = 60_000;
156
+ const asked = new Map();
157
+
158
+ /**
159
+ * What Herdr says is in the pane: whether an agent is there at all, and the work its terminal title
160
+ * names. A pane with no agent in it, a shell where someone ran `roger-roger install` say, is never
161
+ * labelled: its tab belongs to whatever else is in it, and nothing there is an agent to name.
162
+ */
163
+ export async function paneAgent(session, run = runHerdr, now = Date.now()) {
164
+ const last = asked.get(session.id);
165
+ if (last && now - last.at < ASK_EVERY_MS) return last.found;
166
+ let found = { agent: false, work: "" };
164
167
  try {
165
168
  const out = await run(["agent", "get", session.herdr.pane], { bin: session.herdr.bin, socket: session.herdr.socket });
166
- const agent = JSON.parse(out)?.result?.agent ?? {};
167
- work = workFromTitle(agent.terminal_title_stripped ?? agent.terminal_title, agent.agent ?? session.agent);
169
+ const agent = JSON.parse(out)?.result?.agent;
170
+ if (agent) found = { agent: true, work: workFromTitle(agent.terminal_title_stripped ?? agent.terminal_title, agent.agent ?? session.agent) };
168
171
  } catch {
169
- // No title is no label, not an error: the tab still gets the badge.
172
+ // Herdr says there is no agent there (agent_not_found), or can't be asked: either way, no labels.
170
173
  }
171
- titleAsked.set(session.id, { at: now, work });
172
- return work;
174
+ asked.set(session.id, { at: now, found });
175
+ return found;
173
176
  }
174
177
 
175
178
  const inFlight = new Set();
@@ -178,14 +181,17 @@ const inFlight = new Set();
178
181
  * Put the session's labels on its Herdr pane, if it has one and they are not there yet. Never
179
182
  * throws: a pane that can't be labelled is a line in the log, not a failed command. `touch` records
180
183
  * what was put on, so it isn't done again; `run` is the herdr invocation (replaced in tests).
181
- * Without a `--session` name, the work is taken from the pane's terminal title.
184
+ * Only a pane Herdr says has an agent in it is labelled. Without a `--session` name, the work is
185
+ * taken from the pane's terminal title.
182
186
  */
183
187
  export async function labelPane(session, { log = () => {}, touch = () => {}, run = runHerdr } = {}) {
184
188
  if (!session?.herdr?.tab || !session?.herdr?.pane || inFlight.has(session.id)) return { labelled: false };
185
189
  inFlight.add(session.id);
186
- let commands;
190
+ let commands = [];
187
191
  try {
188
- commands = labelCommands({ ...session, name: await workFor(session, run) });
192
+ const pane = await paneAgent(session, run);
193
+ // The --session name wins; the terminal title stands in when there is none.
194
+ if (pane.agent) commands = labelCommands({ ...session, name: str(session.name) || pane.work });
189
195
  } catch {
190
196
  commands = [];
191
197
  }
@@ -882,7 +882,7 @@ export async function runInstall(args, { script, hooks, input = process.stdin, o
882
882
  await ui.run("Starting the daemon", () => daemon("ping"), { done: (r) => ({ text: "Daemon running", detail: `pid ${r.data?.pid ?? "?"}, it stays up for every agent on this machine` }) }).catch(() => {});
883
883
  const touchedDelivery = ["reach", "sound", "voice"].some(asking);
884
884
  if (first || touchedDelivery) {
885
- const session = { identity: identity(env, process.cwd(), { session: "roger-roger install" }), about: { name: "roger-roger install", agent: "install" } };
885
+ const session = { identity: identity({ ...env, HERDR_ENV: "" }, process.cwd(), { session: "roger-roger install" }), about: { name: "roger-roger install", agent: "install" } };
886
886
  const test = await ui.confirm({ message: "Send a test notification now?", initial: first });
887
887
  if (test) {
888
888
  await attempt(ui, "Sending the test", () =>
@@ -211,16 +211,24 @@ export function createUI({ input = process.stdin, output = process.stdout, env =
211
211
  write("\x1b[?25l");
212
212
  rawOn();
213
213
  const keys = keyReader(input);
214
+ // Something started by a key can finish after the question is answered (a voice preview still
215
+ // speaking when Enter was pressed). Its redraw must not bring the answered question back, drawn
216
+ // under the next one.
217
+ let open = true;
218
+ const redraw = () => {
219
+ if (open) paint.draw(draw());
220
+ };
214
221
  try {
215
222
  paint.draw(draw());
216
223
  for (;;) {
217
224
  const key = await keys.next();
218
225
  if (cancelKey(key)) throw new Cancelled();
219
- const result = await onKey(key, () => paint.draw(draw()));
226
+ const result = await onKey(key, redraw);
220
227
  if (result === true) break;
221
228
  paint.draw(draw());
222
229
  }
223
230
  } finally {
231
+ open = false;
224
232
  keys.close();
225
233
  rawOff();
226
234
  paint.clear();