hilos-agent 0.11.17 → 0.11.18

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": "hilos-agent",
3
- "version": "0.11.17",
3
+ "version": "0.11.18",
4
4
  "description": "Run your own coding agent (Claude Code, Codex, Cursor, OpenCode, Hermes, or any command) as a teammate in a hilos room. The checkout and credentials stay local; changes go to your configured Git remote as a PR for human review, and bounded progress and reports go to hilos.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/config.mjs CHANGED
@@ -181,8 +181,9 @@ const DEFAULTS = {
181
181
  // on timeout we post an honest "taking longer than expected" line.
182
182
  chatTimeoutMs: 90000,
183
183
  // Work queue: mentions run one at a time — parallel CLI runs on one checkout
184
- // would collide on git state. queueAcks posts "queued behind the current task"
185
- // when a mention lands during a run; set false to keep it quiet.
184
+ // would collide on git state. queueAcks posts a short receipt when a mention
185
+ // lands during a run (replaced by the reply once it starts, 1354); set false
186
+ // to keep it quiet.
186
187
  queueAcks: true,
187
188
  runTimeoutMs: 600000,
188
189
  decisionTimeoutMs: 1800000,
package/src/handler.mjs CHANGED
@@ -1509,7 +1509,7 @@ function agentPreamble(workspaceMemory, cfg) {
1509
1509
  * (threads are where conversations live), else the channel tail. Returns the raw
1510
1510
  * rows (for intent routing) and a transcript string (for the prompt).
1511
1511
  */
1512
- async function fetchContext({ channelId, tool, parentId }) {
1512
+ async function fetchContext({ channelId, tool, parentId, excludeIds = [] }) {
1513
1513
  let rows = [];
1514
1514
  if (parentId) {
1515
1515
  const t = await tool("get_thread", { parentId }).catch(() => null);
@@ -1520,6 +1520,10 @@ async function fetchContext({ channelId, tool, parentId }) {
1520
1520
  }));
1521
1521
  rows = messages;
1522
1522
  }
1523
+ // 1354 — this agent's own queue receipt for THIS mention is plumbing, not
1524
+ // conversation: read back as context it became "I'm waiting on a current
1525
+ // task", and the model repeated it as fact.
1526
+ if (excludeIds.length) rows = rows.filter((m) => !excludeIds.includes(m?.id));
1523
1527
  // 0779: the rows carry resolved `attachments`, and the body carries the
1524
1528
  // unresolvable `![name](attachment:<uuid>)` pill token. Flatten the token and
1525
1529
  // name the file, so the daemon's chat replies and routing decisions stop
@@ -1617,7 +1621,8 @@ async function respondConversationally({
1617
1621
  pathExistsFn = existsSync,
1618
1622
  }) {
1619
1623
  const implicitDm = message?.implicit === "dm";
1620
- const { transcript } = context || (await fetchContext({ channelId, tool, parentId }));
1624
+ const { transcript } =
1625
+ context || (await fetchContext({ channelId, tool, parentId, excludeIds: contextExclusions(message) }));
1621
1626
  // 1249 — the room can ask agents to answer in a thread instead of in the
1622
1627
  // room. The server owns the setting and marks the mention; the daemon only
1623
1628
  // has to post under the message it names. Other mentions retain their
@@ -1685,7 +1690,13 @@ async function respondConversationally({
1685
1690
  // If the reply is slow, post ONE "still thinking…" ping and then edit it into
1686
1691
  // the answer (single message, no dead air). Needs edit_message; otherwise we
1687
1692
  // just post the reply when it's ready. A fast reply never trips the timer.
1688
- let thinkingId = null;
1693
+ // 1354 — a mention that waited in the queue already has such a line: the
1694
+ // queue receipt, posted where the reply goes. Reuse it, so the reply lands
1695
+ // in place of "picking this up now" instead of under it.
1696
+ let thinkingId =
1697
+ testCapabilities.editMessage && message?.queueAckId && replyParentId === (parentId ?? null)
1698
+ ? message.queueAckId
1699
+ : null;
1689
1700
  let beatStopped = false;
1690
1701
  let beatBusy = false;
1691
1702
  const beatMs = Math.min(cfg.chatTimeoutMs || 90000, 30000);
@@ -1768,6 +1779,11 @@ async function respondConversationally({
1768
1779
  await deliver(body);
1769
1780
  }
1770
1781
 
1782
+ /** Rows a mention's context must not carry: its own queue receipt (1354). */
1783
+ function contextExclusions(message) {
1784
+ return message?.queueAckId ? [message.queueAckId] : [];
1785
+ }
1786
+
1771
1787
  /** True when this mention is a request for THIS agent to review a PR (0286/0288). */
1772
1788
  export function isReviewRequest(message) {
1773
1789
  return (
@@ -2779,7 +2795,7 @@ export async function handleTask({ message, channelId, tool, me, testCapabilitie
2779
2795
  // route AND (for a code run) handed to the coding prompt. Context is always
2780
2796
  // crucial: a reply like "yeah, do it" only means something against what was
2781
2797
  // just said.
2782
- const context = await fetchContext({ channelId, tool, parentId });
2798
+ const context = await fetchContext({ channelId, tool, parentId, excludeIds: contextExclusions(message) });
2783
2799
  const mode = agentMode(message);
2784
2800
 
2785
2801
  // REVIEW EXECUTION (0288): a review-request routes to the read-only reviewer
package/src/run.mjs CHANGED
@@ -24,6 +24,11 @@ import {
24
24
 
25
25
  const MAX_IMMEDIATE_SEQUENCE_PAGES = 8;
26
26
 
27
+ /** The queue receipt a mention gets while another job runs (1354). */
28
+ export const QUEUE_ACK_BODY = "Got it — I'll pick this up as soon as I finish what I'm on.";
29
+ /** What that receipt becomes the moment its job starts (1354). */
30
+ export const QUEUE_ACK_STARTED_BODY = "Picking this up now.";
31
+
27
32
  // 1290: the hilos.sh contract. Missing-tool paths exist only for injected tests;
28
33
  // tools/list remains the token's prompt inventory and the recall scope check.
29
34
  const HILOS_DAEMON_TOOLS = Object.freeze({
@@ -519,6 +524,15 @@ export async function run(
519
524
  const withImages = images.files.length
520
525
  ? { ...message, imageDir: images.dir, images: images.files }
521
526
  : message;
527
+ // 1354 — retire the queue receipt as the job starts. The chat path then
528
+ // edits this same message into the reply (one line, in place); a code
529
+ // run leaves it as this truthful "starting" line under its own cards.
530
+ if (message.queueAckId && testCapabilities.editMessage) {
531
+ await taskTool("edit_message", {
532
+ messageId: message.queueAckId,
533
+ body: QUEUE_ACK_STARTED_BODY,
534
+ }).catch(() => {});
535
+ }
522
536
  // 0782 — a person pressed Stop on this run's live card. The server already
523
537
  // settled the run row (status canceled); this end says so in the room and
524
538
  // tears the work down through the SAME cancel path a chat "stop" uses, so a
@@ -660,11 +674,20 @@ export async function run(
660
674
  // agent, so "Got it — queued" over a line that may deserve no reply at all
661
675
  // is exactly the noise the implicit lane promises not to make.
662
676
  if (liveCfg.queueAcks && !r.startedImmediately && !m.deployRequest && !m.implicit) {
663
- await tool("post_message", {
677
+ // 1354 — the ack is a queue receipt, not a status. It used to read
678
+ // "queued behind the current task", and every model in the room (this
679
+ // agent's own included, since the ack sits in its transcript) took the
680
+ // "current task" literally: Ada told Hilo she was "waiting for current
681
+ // task to clear" for an hour over a queue that had drained in seconds,
682
+ // and the work never started. The receipt now says what is true, its id
683
+ // rides on the job, and safeHandle replaces it the moment the job
684
+ // starts, so nothing stale is left for anyone to reason about.
685
+ const ack = await tool("post_message", {
664
686
  channelId,
665
687
  parentId: m.parentId ?? null,
666
- body: "Got it — queued behind the current task.",
667
- }).catch(() => {});
688
+ body: QUEUE_ACK_BODY,
689
+ }).catch(() => null);
690
+ if (ack?.messageId) m.queueAckId = ack.messageId;
668
691
  }
669
692
  return { accepted: true, settled: false };
670
693
  }
@@ -837,7 +860,7 @@ export async function run(
837
860
  await tool("post_message", {
838
861
  channelId: bridge.binding.channelId,
839
862
  parentId: bridge.binding.threadRootId,
840
- body: "Got it — this will continue in the same local session after the current task.",
863
+ body: "Got it — this continues in the same local session as soon as I finish what I'm on.",
841
864
  }).catch(() => {});
842
865
  }
843
866
  }